frr-7.2.1/0000755000000000000000000000000013610377563007274 500000000000000frr-7.2.1/COPYING0000644000000000000000000004325413610377563010257 00000000000000 GNU GENERAL PUBLIC LICENSE Version 2, June 1991 Copyright (C) 1989, 1991 Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA Everyone is permitted to copy and distribute verbatim copies of this license document, but changing it is not allowed. Preamble The licenses for most software are designed to take away your freedom to share and change it. By contrast, the GNU General Public License is intended to guarantee your freedom to share and change free software--to make sure the software is free for all its users. This General Public License applies to most of the Free Software Foundation's software and to any other program whose authors commit to using it. (Some other Free Software Foundation software is covered by the GNU Lesser General Public License instead.) You can apply it to your programs, too. When we speak of free software, we are referring to freedom, not price. Our General Public Licenses are designed to make sure that you have the freedom to distribute copies of free software (and charge for this service if you wish), that you receive source code or can get it if you want it, that you can change the software or use pieces of it in new free programs; and that you know you can do these things. To protect your rights, we need to make restrictions that forbid anyone to deny you these rights or to ask you to surrender the rights. These restrictions translate to certain responsibilities for you if you distribute copies of the software, or if you modify it. For example, if you distribute copies of such a program, whether gratis or for a fee, you must give the recipients all the rights that you have. You must make sure that they, too, receive or can get the source code. And you must show them these terms so they know their rights. We protect your rights with two steps: (1) copyright the software, and (2) offer you this license which gives you legal permission to copy, distribute and/or modify the software. Also, for each author's protection and ours, we want to make certain that everyone understands that there is no warranty for this free software. If the software is modified by someone else and passed on, we want its recipients to know that what they have is not the original, so that any problems introduced by others will not reflect on the original authors' reputations. Finally, any free program is threatened constantly by software patents. We wish to avoid the danger that redistributors of a free program will individually obtain patent licenses, in effect making the program proprietary. To prevent this, we have made it clear that any patent must be licensed for everyone's free use or not licensed at all. The precise terms and conditions for copying, distribution and modification follow. GNU GENERAL PUBLIC LICENSE TERMS AND CONDITIONS FOR COPYING, DISTRIBUTION AND MODIFICATION 0. This License applies to any program or other work which contains a notice placed by the copyright holder saying it may be distributed under the terms of this General Public License. The "Program", below, refers to any such program or work, and a "work based on the Program" means either the Program or any derivative work under copyright law: that is to say, a work containing the Program or a portion of it, either verbatim or with modifications and/or translated into another language. (Hereinafter, translation is included without limitation in the term "modification".) Each licensee is addressed as "you". Activities other than copying, distribution and modification are not covered by this License; they are outside its scope. The act of running the Program is not restricted, and the output from the Program is covered only if its contents constitute a work based on the Program (independent of having been made by running the Program). Whether that is true depends on what the Program does. 1. You may copy and distribute verbatim copies of the Program's source code as you receive it, in any medium, provided that you conspicuously and appropriately publish on each copy an appropriate copyright notice and disclaimer of warranty; keep intact all the notices that refer to this License and to the absence of any warranty; and give any other recipients of the Program a copy of this License along with the Program. You may charge a fee for the physical act of transferring a copy, and you may at your option offer warranty protection in exchange for a fee. 2. You may modify your copy or copies of the Program or any portion of it, thus forming a work based on the Program, and copy and distribute such modifications or work under the terms of Section 1 above, provided that you also meet all of these conditions: a) You must cause the modified files to carry prominent notices stating that you changed the files and the date of any change. b) You must cause any work that you distribute or publish, that in whole or in part contains or is derived from the Program or any part thereof, to be licensed as a whole at no charge to all third parties under the terms of this License. c) If the modified program normally reads commands interactively when run, you must cause it, when started running for such interactive use in the most ordinary way, to print or display an announcement including an appropriate copyright notice and a notice that there is no warranty (or else, saying that you provide a warranty) and that users may redistribute the program under these conditions, and telling the user how to view a copy of this License. (Exception: if the Program itself is interactive but does not normally print such an announcement, your work based on the Program is not required to print an announcement.) These requirements apply to the modified work as a whole. If identifiable sections of that work are not derived from the Program, and can be reasonably considered independent and separate works in themselves, then this License, and its terms, do not apply to those sections when you distribute them as separate works. But when you distribute the same sections as part of a whole which is a work based on the Program, the distribution of the whole must be on the terms of this License, whose permissions for other licensees extend to the entire whole, and thus to each and every part regardless of who wrote it. Thus, it is not the intent of this section to claim rights or contest your rights to work written entirely by you; rather, the intent is to exercise the right to control the distribution of derivative or collective works based on the Program. In addition, mere aggregation of another work not based on the Program with the Program (or with a work based on the Program) on a volume of a storage or distribution medium does not bring the other work under the scope of this License. 3. You may copy and distribute the Program (or a work based on it, under Section 2) in object code or executable form under the terms of Sections 1 and 2 above provided that you also do one of the following: a) Accompany it with the complete corresponding machine-readable source code, which must be distributed under the terms of Sections 1 and 2 above on a medium customarily used for software interchange; or, b) Accompany it with a written offer, valid for at least three years, to give any third party, for a charge no more than your cost of physically performing source distribution, a complete machine-readable copy of the corresponding source code, to be distributed under the terms of Sections 1 and 2 above on a medium customarily used for software interchange; or, c) Accompany it with the information you received as to the offer to distribute corresponding source code. (This alternative is allowed only for noncommercial distribution and only if you received the program in object code or executable form with such an offer, in accord with Subsection b above.) The source code for a work means the preferred form of the work for making modifications to it. For an executable work, complete source code means all the source code for all modules it contains, plus any associated interface definition files, plus the scripts used to control compilation and installation of the executable. However, as a special exception, the source code distributed need not include anything that is normally distributed (in either source or binary form) with the major components (compiler, kernel, and so on) of the operating system on which the executable runs, unless that component itself accompanies the executable. If distribution of executable or object code is made by offering access to copy from a designated place, then offering equivalent access to copy the source code from the same place counts as distribution of the source code, even though third parties are not compelled to copy the source along with the object code. 4. You may not copy, modify, sublicense, or distribute the Program except as expressly provided under this License. Any attempt otherwise to copy, modify, sublicense or distribute the Program is void, and will automatically terminate your rights under this License. However, parties who have received copies, or rights, from you under this License will not have their licenses terminated so long as such parties remain in full compliance. 5. You are not required to accept this License, since you have not signed it. However, nothing else grants you permission to modify or distribute the Program or its derivative works. These actions are prohibited by law if you do not accept this License. Therefore, by modifying or distributing the Program (or any work based on the Program), you indicate your acceptance of this License to do so, and all its terms and conditions for copying, distributing or modifying the Program or works based on it. 6. Each time you redistribute the Program (or any work based on the Program), the recipient automatically receives a license from the original licensor to copy, distribute or modify the Program subject to these terms and conditions. You may not impose any further restrictions on the recipients' exercise of the rights granted herein. You are not responsible for enforcing compliance by third parties to this License. 7. If, as a consequence of a court judgment or allegation of patent infringement or for any other reason (not limited to patent issues), conditions are imposed on you (whether by court order, agreement or otherwise) that contradict the conditions of this License, they do not excuse you from the conditions of this License. If you cannot distribute so as to satisfy simultaneously your obligations under this License and any other pertinent obligations, then as a consequence you may not distribute the Program at all. For example, if a patent license would not permit royalty-free redistribution of the Program by all those who receive copies directly or indirectly through you, then the only way you could satisfy both it and this License would be to refrain entirely from distribution of the Program. If any portion of this section is held invalid or unenforceable under any particular circumstance, the balance of the section is intended to apply and the section as a whole is intended to apply in other circumstances. It is not the purpose of this section to induce you to infringe any patents or other property right claims or to contest validity of any such claims; this section has the sole purpose of protecting the integrity of the free software distribution system, which is implemented by public license practices. Many people have made generous contributions to the wide range of software distributed through that system in reliance on consistent application of that system; it is up to the author/donor to decide if he or she is willing to distribute software through any other system and a licensee cannot impose that choice. This section is intended to make thoroughly clear what is believed to be a consequence of the rest of this License. 8. If the distribution and/or use of the Program is restricted in certain countries either by patents or by copyrighted interfaces, the original copyright holder who places the Program under this License may add an explicit geographical distribution limitation excluding those countries, so that distribution is permitted only in or among countries not thus excluded. In such case, this License incorporates the limitation as if written in the body of this License. 9. The Free Software Foundation may publish revised and/or new versions of the General Public License from time to time. Such new versions will be similar in spirit to the present version, but may differ in detail to address new problems or concerns. Each version is given a distinguishing version number. If the Program specifies a version number of this License which applies to it and "any later version", you have the option of following the terms and conditions either of that version or of any later version published by the Free Software Foundation. If the Program does not specify a version number of this License, you may choose any version ever published by the Free Software Foundation. 10. If you wish to incorporate parts of the Program into other free programs whose distribution conditions are different, write to the author to ask for permission. For software which is copyrighted by the Free Software Foundation, write to the Free Software Foundation; we sometimes make exceptions for this. Our decision will be guided by the two goals of preserving the free status of all derivatives of our free software and of promoting the sharing and reuse of software generally. NO WARRANTY 11. BECAUSE THE PROGRAM IS LICENSED FREE OF CHARGE, THERE IS NO WARRANTY FOR THE PROGRAM, TO THE EXTENT PERMITTED BY APPLICABLE LAW. EXCEPT WHEN OTHERWISE STATED IN WRITING THE COPYRIGHT HOLDERS AND/OR OTHER PARTIES PROVIDE THE PROGRAM "AS IS" WITHOUT WARRANTY OF ANY KIND, EITHER EXPRESSED OR IMPLIED, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE. THE ENTIRE RISK AS TO THE QUALITY AND PERFORMANCE OF THE PROGRAM IS WITH YOU. SHOULD THE PROGRAM PROVE DEFECTIVE, YOU ASSUME THE COST OF ALL NECESSARY SERVICING, REPAIR OR CORRECTION. 12. IN NO EVENT UNLESS REQUIRED BY APPLICABLE LAW OR AGREED TO IN WRITING WILL ANY COPYRIGHT HOLDER, OR ANY OTHER PARTY WHO MAY MODIFY AND/OR REDISTRIBUTE THE PROGRAM AS PERMITTED ABOVE, BE LIABLE TO YOU FOR DAMAGES, INCLUDING ANY GENERAL, SPECIAL, INCIDENTAL OR CONSEQUENTIAL DAMAGES ARISING OUT OF THE USE OR INABILITY TO USE THE PROGRAM (INCLUDING BUT NOT LIMITED TO LOSS OF DATA OR DATA BEING RENDERED INACCURATE OR LOSSES SUSTAINED BY YOU OR THIRD PARTIES OR A FAILURE OF THE PROGRAM TO OPERATE WITH ANY OTHER PROGRAMS), EVEN IF SUCH HOLDER OR OTHER PARTY HAS BEEN ADVISED OF THE POSSIBILITY OF SUCH DAMAGES. END OF TERMS AND CONDITIONS How to Apply These Terms to Your New Programs If you develop a new program, and you want it to be of the greatest possible use to the public, the best way to achieve this is to make it free software which everyone can redistribute and change under these terms. To do so, attach the following notices to the program. It is safest to attach them to the start of each source file to most effectively convey the exclusion of warranty; and each file should have at least the "copyright" line and a pointer to where the full notice is found. Copyright (C) This program is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation; either version 2 of the License, or (at your option) any later version. This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with this program; if not, write to the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. Also add information on how to contact you by electronic and paper mail. If the program is interactive, make it output a short notice like this when it starts in an interactive mode: Gnomovision version 69, Copyright (C) year name of author Gnomovision comes with ABSOLUTELY NO WARRANTY; for details type `show w'. This is free software, and you are welcome to redistribute it under certain conditions; type `show c' for details. The hypothetical commands `show w' and `show c' should show the appropriate parts of the General Public License. Of course, the commands you use may be called something other than `show w' and `show c'; they could even be mouse-clicks or menu items--whatever suits your program. You should also get your employer (if you work as a programmer) or your school, if any, to sign a "copyright disclaimer" for the program, if necessary. Here is a sample; alter the names: Yoyodyne, Inc., hereby disclaims all copyright interest in the program `Gnomovision' (which makes passes at compilers) written by James Hacker. , 1 April 1989 Ty Coon, President of Vice This General Public License does not permit incorporating your program into proprietary programs. If your program is a subroutine library, you may consider it more useful to permit linking proprietary applications with the library. If this is what you want to do, use the GNU Lesser General Public License instead of this License. frr-7.2.1/Makefile.am0000644000000000000000000001670713610377563011263 00000000000000## Process this file with automake to produce Makefile.in. AUTOMAKE_OPTIONS = subdir-objects 1.12 ACLOCAL_AMFLAGS = -I m4 AM_CFLAGS = \ $(LIBYANG_CFLAGS) \ $(SQLITE3_CFLAGS) \ $(UNWIND_CFLAGS) \ $(SAN_FLAGS) \ $(WERROR) \ # end AM_CPPFLAGS = \ -I$(top_srcdir) -I$(top_srcdir)/include -I$(top_srcdir)/lib \ -I$(top_builddir) -I$(top_builddir)/include -I$(top_builddir)/lib \ $(LUA_INCLUDE) \ # end AM_LDFLAGS = \ -export-dynamic \ $(AC_LDFLAGS) \ $(SAN_FLAGS) \ # end DEFS = @DEFS@ -DSYSCONFDIR=\"$(sysconfdir)/\" -DCONFDATE=$(CONFDATE) AR_FLAGS = @AR_FLAGS@ ARFLAGS = @ARFLAGS@ RANLIB = @RANLIB@ # these two targets are provided to easily grab autoconf/Makefile variables # you can use either: # eval `make VARFD=3 shvar-CFLAGS 3>&1 1>&2` # CFLAGS="`make VARFD=3 var-CFLAGS 3>&1 1>&2`" # where the former can be used to set several variables at once. Note the # fd redirections -- this is to prevent garbage from make rebuilding other # targets from causing issues. .PHONY: shvar-% var-% VARFD ?= 1 shvar-%: @echo "$*=\"$($*)\"" >&$(VARFD) var-%: @echo "$($*)" >&$(VARFD) if ONLY_CLIPPY .DEFAULT_GOAL := clippy-only endif clippy-only: Makefile lib/clippy config.h .PHONY: clippy-only # overwriting these vars breaks cross-compilation. let's be helpful and warn. # # note: "#AUTODERP# " will be removed from Makefile by configure. These are # GNU make directives & automake will f*ck them up by trying to process them # as automake directives. # #AUTODERP# null= #AUTODERP# SPACE=$(null) $(null) #AUTODERP# mkcheck_CC = $(findstring $(SPACE)CC=, $(SPACE)$(MAKEOVERRIDES)) #AUTODERP# mkcheck_CFLAGS = $(findstring $(SPACE)CFLAGS=, $(SPACE)$(MAKEOVERRIDES)) #AUTODERP# mkcheck_CPPFLAGS = $(findstring $(SPACE)CPPFLAGS=,$(SPACE)$(MAKEOVERRIDES)) #AUTODERP# mkcheck_CCLD = $(findstring $(SPACE)CCLD=, $(SPACE)$(MAKEOVERRIDES)) #AUTODERP# mkcheck_LD = $(findstring $(SPACE)LD=, $(SPACE)$(MAKEOVERRIDES)) #AUTODERP# mkcheck_LDFLAGS = $(findstring $(SPACE)LDFLAGS=, $(SPACE)$(MAKEOVERRIDES)) #AUTODERP# # #AUTODERP# ifneq ($(mkcheck_CC),) #AUTODERP# $(warning WARNING: you have overwritten the "CC" variable on the make command line.) #AUTODERP# endif #AUTODERP# ifneq ($(mkcheck_CFLAGS),) #AUTODERP# $(warning WARNING: you have overwritten the "CFLAGS" variable on the make command line.) #AUTODERP# endif #AUTODERP# ifneq ($(mkcheck_CPPFLAGS),) #AUTODERP# $(warning WARNING: you have overwritten the "CPPFLAGS" variable on the make command line.) #AUTODERP# endif #AUTODERP# ifneq ($(mkcheck_CCLD),) #AUTODERP# $(warning WARNING: you have overwritten the "CCLD" variable on the make command line.) #AUTODERP# endif #AUTODERP# ifneq ($(mkcheck_LD),) #AUTODERP# $(warning WARNING: you have overwritten the "LD" variable on the make command line.) #AUTODERP# endif #AUTODERP# ifneq ($(mkcheck_LDFLAGS),) #AUTODERP# $(warning WARNING: you have overwritten the "LDFLAGS" variable on the make command line.) #AUTODERP# endif #AUTODERP# # #AUTODERP# ifneq ($(mkcheck_CC)$(mkcheck_CFLAGS)$(mkcheck_CPPFLAGS)$(mkcheck_CCLD)$(mkcheck_LD)$(mkcheck_LDFLAGS),) #AUTODERP# $(warning ------) #AUTODERP# $(warning While overwriting these variables works most of the time, it is not recommended and can cause confusing build errors.) #AUTODERP# $(warning This is especially problematic when cross-compiling, since tools that run on the build system during the build process will not be compiled correctly.) #AUTODERP# $(warning All of these variables should be supplied to 'configure', and they will be remembered and correctly applied during 'make'.) #AUTODERP# $(warning ------) #AUTODERP# endif EXTRA_DIST = BUILT_SOURCES = CLEANFILES = DISTCLEANFILES = examplesdir = $(exampledir) bin_PROGRAMS = sbin_PROGRAMS = sbin_SCRIPTS = noinst_PROGRAMS = noinst_HEADERS = noinst_LIBRARIES = nodist_noinst_DATA = lib_LTLIBRARIES = module_LTLIBRARIES = pkginclude_HEADERS = nodist_pkginclude_HEADERS = dist_examples_DATA = dist_yangmodels_DATA = man_MANS = vtysh_scan = ## libtool, the self-made GNU scourge ## ... this should fix relinking ## ... and AUTOMAKE_DUMMY is needed to prevent automake from treating this ## as overriding the normal targets... $(AUTOMAKE_DUMMY)install-moduleLTLIBRARIES: install-libLTLIBRARIES $(AUTOMAKE_DUMMY)install-binPROGRAMS: install-libLTLIBRARIES $(AUTOMAKE_DUMMY)install-sbinPROGRAMS: install-libLTLIBRARIES include doc/subdir.am include doc/user/subdir.am include doc/manpages/subdir.am include doc/developer/subdir.am include include/subdir.am include lib/subdir.am include zebra/subdir.am include watchfrr/subdir.am include qpb/subdir.am include fpm/subdir.am include grpc/subdir.am include tools/subdir.am include solaris/subdir.am include bgpd/subdir.am include bgpd/rfp-example/librfp/subdir.am include bgpd/rfp-example/rfptest/subdir.am include ripd/subdir.am include ripngd/subdir.am include ospfd/subdir.am include ospf6d/subdir.am include ospfclient/subdir.am include isisd/subdir.am include nhrpd/subdir.am include ldpd/subdir.am include babeld/subdir.am include eigrpd/subdir.am include sharpd/subdir.am include pimd/subdir.am include pbrd/subdir.am include staticd/subdir.am include bfdd/subdir.am include yang/subdir.am include yang/libyang_plugins/subdir.am include vrrpd/subdir.am include vtysh/subdir.am include tests/subdir.am include tests/topotests/subdir.am if PKGSRC rcdir=@pkgsrcrcdir@ rc_SCRIPTS = \ pkgsrc/bgpd.sh \ pkgsrc/ospf6d.sh \ pkgsrc/ospfd.sh \ pkgsrc/ripd.sh \ pkgsrc/ripngd.sh \ pkgsrc/zebra.sh \ # end endif EXTRA_DIST += \ aclocal.m4 \ README.md \ m4/README.txt \ m4/libtool-whole-archive.patch \ config.version \ changelog-auto \ changelog-auto.in \ \ python/clidef.py \ python/clippy/__init__.py \ \ redhat/frr.logrotate \ redhat/frr.pam \ redhat/frr.spec \ \ snapcraft/snapcraft.yaml \ snapcraft/README.snap_build.md \ snapcraft/README.usage.md \ snapcraft/extra_version_info.txt \ snapcraft/scripts \ snapcraft/defaults \ snapcraft/helpers \ snapcraft/snap \ babeld/Makefile \ bgpd/Makefile \ bgpd/rfp-example/librfp/Makefile \ bgpd/rfp-example/rfptest/Makefile \ doc/Makefile \ doc/developer/Makefile \ doc/manpages/Makefile \ doc/user/Makefile \ eigrpd/Makefile \ fpm/Makefile \ grpc/Makefile \ isisd/Makefile \ ldpd/Makefile \ lib/Makefile \ nhrpd/Makefile \ ospf6d/Makefile \ ospfclient/Makefile \ ospfd/Makefile \ pbrd/Makefile \ pimd/Makefile \ qpb/Makefile \ ripd/Makefile \ ripngd/Makefile \ staticd/Makefile \ tests/Makefile \ tools/Makefile \ vtysh/Makefile \ watchfrr/Makefile \ zebra/Makefile \ vrrpd/Makefile \ # end noinst_HEADERS += defaults.h clean-local: clean-python .PHONY: clean-python clean-python: find . -name __pycache__ -o -name .pytest_cache | xargs rm -rf find . -name "*.pyc" -o -name "*_clippy.c" | xargs rm -f redistclean: $(MAKE) distclean CONFIG_CLEAN_FILES="$(filter-out $(EXTRA_DIST), $(CONFIG_CLEAN_FILES))" indent: tools/indent.py `find sharpd bgpd eigrpd include isisd lib nhrpd ospf6d ospfd pimd qpb ripd vtysh zebra -name '*.[ch]' | grep -v include/linux` if HAVE_GCOV coverage: check @ find . -name '*.o' -exec gcov {} \; yorn: @ echo "OK to upload coverage to https://coverage.io [y/N]:" @ read yn; test "$$yn" = "y" upload-check-coverage: @ if [ "x${COMMIT}" = "x" ]; then echo "COMMIT required"; exit 1; fi @ if [ "x${TOKEN}" = "x" ]; then echo "TOKEN required"; exit 1; fi curl -s https://codecov.io/bash | bash -s - -C ${COMMIT} -t ${TOKEN} force-check-coverage: coverage upload-check-coverage check-coverage: coverage yorn upload-check-coverage endif frr-7.2.1/Makefile.in0000644000000000000000000262565513610377563011306 00000000000000# Makefile.in generated by automake 1.16.1 from Makefile.am. # @configure_input@ # Copyright (C) 1994-2018 Free Software Foundation, Inc. # This Makefile.in is free software; the Free Software Foundation # gives unlimited permission to copy and/or distribute it, # with or without modifications, as long as this notice is preserved. # This program is distributed in the hope that it will be useful, # but WITHOUT ANY WARRANTY, to the extent permitted by law; without # even the implied warranty of MERCHANTABILITY or FITNESS FOR A # PARTICULAR PURPOSE. @SET_MAKE@ # # doc # # # doc/user # # # doc/manpages # # # doc/developer # # # zebra # # # watchfrr # # # tools # # # solaris # # # bgpd # # # librfp # # # libtest # # # ripd # # # ripngd # # # ospfd # # # ospf6d # # # ospfclient # # # isisd # # # nhrpd # # # ldpd # # # babeld # # # eigrpd # # # sharpd # # # pimd # # # pbrd # # # staticd # # # bfdd # # # libyang user types # # # vrrpd # # # vtysh # # # tests # VPATH = @srcdir@ am__is_gnu_make = { \ if test -z '$(MAKELEVEL)'; then \ false; \ elif test -n '$(MAKE_HOST)'; then \ true; \ elif test -n '$(MAKE_VERSION)' && test -n '$(CURDIR)'; then \ true; \ else \ false; \ fi; \ } am__make_running_with_option = \ case $${target_option-} in \ ?) ;; \ *) echo "am__make_running_with_option: internal error: invalid" \ "target option '$${target_option-}' specified" >&2; \ exit 1;; \ esac; \ has_opt=no; \ sane_makeflags=$$MAKEFLAGS; \ if $(am__is_gnu_make); then \ sane_makeflags=$$MFLAGS; \ else \ case $$MAKEFLAGS in \ *\\[\ \ ]*) \ bs=\\; \ sane_makeflags=`printf '%s\n' "$$MAKEFLAGS" \ | sed "s/$$bs$$bs[$$bs $$bs ]*//g"`;; \ esac; \ fi; \ skip_next=no; \ strip_trailopt () \ { \ flg=`printf '%s\n' "$$flg" | sed "s/$$1.*$$//"`; \ }; \ for flg in $$sane_makeflags; do \ test $$skip_next = yes && { skip_next=no; continue; }; \ case $$flg in \ *=*|--*) continue;; \ -*I) strip_trailopt 'I'; skip_next=yes;; \ -*I?*) strip_trailopt 'I';; \ -*O) strip_trailopt 'O'; skip_next=yes;; \ -*O?*) strip_trailopt 'O';; \ -*l) strip_trailopt 'l'; skip_next=yes;; \ -*l?*) strip_trailopt 'l';; \ -[dEDm]) skip_next=yes;; \ -[JT]) skip_next=yes;; \ esac; \ case $$flg in \ *$$target_option*) has_opt=yes; break;; \ esac; \ done; \ test $$has_opt = yes am__make_dryrun = (target_option=n; $(am__make_running_with_option)) am__make_keepgoing = (target_option=k; $(am__make_running_with_option)) pkgdatadir = $(datadir)/@PACKAGE@ pkgincludedir = $(includedir)/@PACKAGE@ pkglibdir = $(libdir)/@PACKAGE@ pkglibexecdir = $(libexecdir)/@PACKAGE@ am__cd = CDPATH="$${ZSH_VERSION+.}$(PATH_SEPARATOR)" && cd install_sh_DATA = $(install_sh) -c -m 644 install_sh_PROGRAM = $(install_sh) -c install_sh_SCRIPT = $(install_sh) -c INSTALL_HEADER = $(INSTALL_DATA) transform = $(program_transform_name) NORMAL_INSTALL = : PRE_INSTALL = : POST_INSTALL = : NORMAL_UNINSTALL = : PRE_UNINSTALL = : POST_UNINSTALL = : build_triplet = @build@ host_triplet = @host@ bin_PROGRAMS = $(am__EXEEXT_1) $(am__EXEEXT_2) sbin_PROGRAMS = $(am__EXEEXT_12) $(am__EXEEXT_13) tools/ssd$(EXEEXT) \ $(am__EXEEXT_14) $(am__EXEEXT_15) $(am__EXEEXT_16) \ $(am__EXEEXT_17) $(am__EXEEXT_18) $(am__EXEEXT_19) \ $(am__EXEEXT_20) $(am__EXEEXT_21) $(am__EXEEXT_22) \ $(am__EXEEXT_23) $(am__EXEEXT_24) $(am__EXEEXT_25) \ $(am__EXEEXT_26) $(am__EXEEXT_27) $(am__EXEEXT_28) \ $(am__EXEEXT_29) $(am__EXEEXT_30) noinst_PROGRAMS = lib/grammar_sandbox$(EXEEXT) $(am__EXEEXT_7) \ tools/permutations$(EXEEXT) \ tools/gen_northbound_callbacks$(EXEEXT) \ tools/gen_yang_deviations$(EXEEXT) $(am__EXEEXT_8) \ $(am__EXEEXT_9) $(am__EXEEXT_10) $(am__EXEEXT_11) # # automake integration (things that should be built in "all") # @DOC_TRUE@am__append_1 = $(USERBUILD)/texinfo/frr.info @DOC_HTML_TRUE@am__append_2 = $(USERBUILD)/html/.buildinfo @DOC_TRUE@am__append_3 = $(man1) @DOC_TRUE@am__append_4 = $(man8) @SQLITE3_TRUE@am__append_5 = $(SQLITE3_LIBS) @SQLITE3_TRUE@am__append_6 = lib/db.c # General note about module and module helper library (libfrrsnmp, libfrrzmq) # linking: If we're linking libfrr statically into daemons, we *must* remove # libfrr from modules because modules will always link it in dynamically and # thus 2 copies of libfrr will be loaded... hilarity ensues. # # Not linking libfrr into modules should generally work fine because the # executable refers to libfrr either way and the dynamic linker should make # libfrr available to modules. If some OS platform has a dynamic linker that # doesn't do that, libfrr needs to be readded to modules, but _only_ _if_ # it's not linked into daemons statically. # # SNMP support # @SNMP_TRUE@am__append_7 = lib/libfrrsnmp.la # # c-ares support # @CARES_TRUE@am__append_8 = lib/libfrrcares.la @CARES_TRUE@am__append_9 = lib/resolver.h # # ZeroMQ support # @ZEROMQ_TRUE@am__append_10 = lib/libfrrzmq.la @ZEROMQ_TRUE@am__append_11 = lib/frr_zmq.h # # Tail-f's ConfD support # @CONFD_TRUE@am__append_12 = lib/confd.la # # Sysrepo support # @SYSREPO_TRUE@am__append_13 = lib/sysrepo.la # # gRPC northbound plugin # @GRPC_TRUE@am__append_14 = lib/grpc.la @BUILD_CLIPPY_TRUE@am__append_15 = lib/clippy @ZEBRA_TRUE@am__append_16 = zebra/zebra @ZEBRA_TRUE@am__append_17 = zebra/zebra.conf.sample # can be loaded as DSO - always include for vtysh @ZEBRA_TRUE@am__append_18 = $(top_srcdir)/zebra/debug.c \ @ZEBRA_TRUE@ $(top_srcdir)/zebra/interface.c \ @ZEBRA_TRUE@ $(top_srcdir)/zebra/router-id.c \ @ZEBRA_TRUE@ $(top_srcdir)/zebra/rtadv.c \ @ZEBRA_TRUE@ $(top_srcdir)/zebra/zebra_mlag.c \ @ZEBRA_TRUE@ $(top_srcdir)/zebra/zebra_mpls_vty.c \ @ZEBRA_TRUE@ $(top_srcdir)/zebra/zebra_ptm.c \ @ZEBRA_TRUE@ $(top_srcdir)/zebra/zebra_pw.c \ @ZEBRA_TRUE@ $(top_srcdir)/zebra/zebra_routemap.c \ @ZEBRA_TRUE@ $(top_srcdir)/zebra/zebra_vty.c \ @ZEBRA_TRUE@ $(top_srcdir)/zebra/zserv.c \ @ZEBRA_TRUE@ $(top_srcdir)/zebra/irdp_interface.c \ @ZEBRA_TRUE@ $(top_srcdir)/zebra/zebra_fpm.c @IRDP_TRUE@@ZEBRA_TRUE@am__append_19 = zebra/zebra_irdp.la @SNMP_TRUE@@ZEBRA_TRUE@am__append_20 = zebra/zebra_snmp.la @FPM_TRUE@@ZEBRA_TRUE@am__append_21 = zebra/zebra_fpm.la @ZEBRA_TRUE@am__append_22 = $(MANBUILD)/frr-zebra.8 @HAVE_PROTOBUF_TRUE@am__append_23 = fpm/libfrrfpm_pb.la qpb/libfrr_pb.la $(PROTOBUF_C_LIBS) @HAVE_PROTOBUF_TRUE@am__append_24 = zebra/zebra_fpm_protobuf.c @DEV_BUILD_TRUE@@HAVE_PROTOBUF_TRUE@am__append_25 = zebra/zebra_fpm_dt.c @WATCHFRR_TRUE@am__append_26 = watchfrr/watchfrr @WATCHFRR_TRUE@am__append_27 = $(top_srcdir)/watchfrr/watchfrr_vty.c @WATCHFRR_TRUE@am__append_28 = $(MANBUILD)/frr-watchfrr.8 @HAVE_PROTOBUF_TRUE@am__append_29 = qpb/libfrr_pb.la @FPM_TRUE@@HAVE_PROTOBUF_TRUE@am__append_30 = fpm/libfrrfpm_pb.la @GRPC_TRUE@am__append_31 = grpc/libfrrgrpc_pb.la @BGPD_TRUE@am__append_32 = bgpd/libbgp.a @BGPD_TRUE@am__append_33 = bgpd/bgpd @BGPD_TRUE@am__append_34 = bgpd/bgp_btoa @BGPD_TRUE@am__append_35 = \ @BGPD_TRUE@ bgpd/bgpd.conf.sample \ @BGPD_TRUE@ bgpd/bgpd.conf.sample2 \ @BGPD_TRUE@ bgpd/bgpd.conf.vnc.sample \ @BGPD_TRUE@ # end # can be loaded as DSO - always include for vtysh @BGPD_TRUE@am__append_36 = $(top_srcdir)/bgpd/bgp_bfd.c \ @BGPD_TRUE@ $(top_srcdir)/bgpd/bgp_debug.c \ @BGPD_TRUE@ $(top_srcdir)/bgpd/bgp_dump.c \ @BGPD_TRUE@ $(top_srcdir)/bgpd/bgp_evpn_vty.c \ @BGPD_TRUE@ $(top_srcdir)/bgpd/bgp_filter.c \ @BGPD_TRUE@ $(top_srcdir)/bgpd/bgp_mplsvpn.c \ @BGPD_TRUE@ $(top_srcdir)/bgpd/bgp_nexthop.c \ @BGPD_TRUE@ $(top_srcdir)/bgpd/bgp_route.c \ @BGPD_TRUE@ $(top_srcdir)/bgpd/bgp_routemap.c \ @BGPD_TRUE@ $(top_srcdir)/bgpd/bgp_vty.c \ @BGPD_TRUE@ $(top_srcdir)/bgpd/bgp_flowspec_vty.c \ @BGPD_TRUE@ $(top_srcdir)/bgpd/bgp_rpki.c \ @BGPD_TRUE@ $(top_srcdir)/bgpd/bgp_bmp.c @BGPD_TRUE@@ENABLE_BGP_VNC_TRUE@am__append_37 = \ @BGPD_TRUE@@ENABLE_BGP_VNC_TRUE@ $(top_srcdir)/bgpd/rfapi/bgp_rfapi_cfg.c \ @BGPD_TRUE@@ENABLE_BGP_VNC_TRUE@ $(top_srcdir)/bgpd/rfapi/rfapi.c \ @BGPD_TRUE@@ENABLE_BGP_VNC_TRUE@ $(top_srcdir)/bgpd/rfapi/rfapi_vty.c \ @BGPD_TRUE@@ENABLE_BGP_VNC_TRUE@ $(top_srcdir)/bgpd/rfapi/vnc_debug.c \ @BGPD_TRUE@@ENABLE_BGP_VNC_TRUE@ # end @BGPD_TRUE@@SNMP_TRUE@am__append_38 = bgpd/bgpd_snmp.la @BGPD_TRUE@@RPKI_TRUE@am__append_39 = bgpd/bgpd_rpki.la @BGPD_TRUE@@BGP_BMP_TRUE@am__append_40 = bgpd/bgpd_bmp.la @BGPD_TRUE@am__append_41 = $(MANBUILD)/frr-bgpd.8 @ENABLE_BGP_VNC_TRUE@am__append_42 = \ @ENABLE_BGP_VNC_TRUE@ bgpd/rfapi/bgp_rfapi_cfg.c \ @ENABLE_BGP_VNC_TRUE@ bgpd/rfapi/rfapi_import.c \ @ENABLE_BGP_VNC_TRUE@ bgpd/rfapi/rfapi.c \ @ENABLE_BGP_VNC_TRUE@ bgpd/rfapi/rfapi_ap.c \ @ENABLE_BGP_VNC_TRUE@ bgpd/rfapi/rfapi_descriptor_rfp_utils.c \ @ENABLE_BGP_VNC_TRUE@ bgpd/rfapi/rfapi_encap_tlv.c \ @ENABLE_BGP_VNC_TRUE@ bgpd/rfapi/rfapi_nve_addr.c \ @ENABLE_BGP_VNC_TRUE@ bgpd/rfapi/rfapi_monitor.c \ @ENABLE_BGP_VNC_TRUE@ bgpd/rfapi/rfapi_rib.c \ @ENABLE_BGP_VNC_TRUE@ bgpd/rfapi/rfapi_vty.c \ @ENABLE_BGP_VNC_TRUE@ bgpd/rfapi/vnc_debug.c \ @ENABLE_BGP_VNC_TRUE@ bgpd/rfapi/vnc_export_bgp.c \ @ENABLE_BGP_VNC_TRUE@ bgpd/rfapi/vnc_export_table.c \ @ENABLE_BGP_VNC_TRUE@ bgpd/rfapi/vnc_import_bgp.c \ @ENABLE_BGP_VNC_TRUE@ bgpd/rfapi/vnc_zebra.c \ @ENABLE_BGP_VNC_TRUE@ # end @ENABLE_BGP_VNC_TRUE@am__append_43 = bgpd/rfapi/rfapi_descriptor_rfp_utils.c @ENABLE_BGP_VNC_TRUE@am__append_44 = -Irfapi -I@top_srcdir@/$(RFPINC) @ENABLE_BGP_VNC_TRUE@am__append_45 = bgpd/rfapi/rfapi_descriptor_rfp_utils.c @ENABLE_BGP_VNC_TRUE@am__append_46 = -Irfapi -I@top_srcdir@/$(RFPINC) @ENABLE_BGP_VNC_TRUE@am__append_47 = bgpd/rfp-example/librfp/librfp.a @ENABLE_BGP_VNC_TRUE@am__append_48 = bgpd/rfp-example/rfptest/rfptest @RIPD_TRUE@am__append_49 = ripd/librip.a @RIPD_TRUE@am__append_50 = ripd/ripd @RIPD_TRUE@am__append_51 = ripd/ripd.conf.sample @RIPD_TRUE@am__append_52 = \ @RIPD_TRUE@ $(top_srcdir)/ripd/rip_cli.c \ @RIPD_TRUE@ $(top_srcdir)/ripd/rip_debug.c \ @RIPD_TRUE@ $(top_srcdir)/ripd/ripd.c \ @RIPD_TRUE@ # end @RIPD_TRUE@@SNMP_TRUE@am__append_53 = ripd/ripd_snmp.la @RIPD_TRUE@am__append_54 = $(MANBUILD)/frr-ripd.8 @RIPNGD_TRUE@am__append_55 = ripngd/libripng.a @RIPNGD_TRUE@am__append_56 = ripngd/ripngd @RIPNGD_TRUE@am__append_57 = \ @RIPNGD_TRUE@ $(top_srcdir)/ripngd/ripng_cli.c \ @RIPNGD_TRUE@ $(top_srcdir)/ripngd/ripng_debug.c \ @RIPNGD_TRUE@ $(top_srcdir)/ripngd/ripngd.c \ @RIPNGD_TRUE@ # end @RIPNGD_TRUE@am__append_58 = $(MANBUILD)/frr-ripngd.8 @OSPFD_TRUE@am__append_59 = ospfd/libfrrospf.a @OSPFD_TRUE@am__append_60 = ospfd/ospfd @OSPFD_TRUE@am__append_61 = ospfd/ospfd.conf.sample @OSPFD_TRUE@am__append_62 = \ @OSPFD_TRUE@ $(top_srcdir)/ospfd/ospf_bfd.c \ @OSPFD_TRUE@ $(top_srcdir)/ospfd/ospf_dump.c \ @OSPFD_TRUE@ $(top_srcdir)/ospfd/ospf_opaque.c \ @OSPFD_TRUE@ $(top_srcdir)/ospfd/ospf_ri.c \ @OSPFD_TRUE@ $(top_srcdir)/ospfd/ospf_routemap.c \ @OSPFD_TRUE@ $(top_srcdir)/ospfd/ospf_te.c \ @OSPFD_TRUE@ $(top_srcdir)/ospfd/ospf_sr.c \ @OSPFD_TRUE@ $(top_srcdir)/ospfd/ospf_vty.c \ @OSPFD_TRUE@ # end @OSPFD_TRUE@@SNMP_TRUE@am__append_63 = ospfd/ospfd_snmp.la @OSPFD_TRUE@am__append_64 = $(MANBUILD)/frr-ospfd.8 @OSPF6D_TRUE@am__append_65 = ospf6d/libospf6.a @OSPF6D_TRUE@am__append_66 = ospf6d/ospf6d @OSPF6D_TRUE@am__append_67 = ospf6d/ospf6d.conf.sample @OSPF6D_TRUE@am__append_68 = \ @OSPF6D_TRUE@ $(top_srcdir)/ospf6d/ospf6_abr.c \ @OSPF6D_TRUE@ $(top_srcdir)/ospf6d/ospf6_asbr.c \ @OSPF6D_TRUE@ $(top_srcdir)/ospf6d/ospf6_area.c \ @OSPF6D_TRUE@ $(top_srcdir)/ospf6d/ospf6_bfd.c \ @OSPF6D_TRUE@ $(top_srcdir)/ospf6d/ospf6_flood.c \ @OSPF6D_TRUE@ $(top_srcdir)/ospf6d/ospf6_interface.c \ @OSPF6D_TRUE@ $(top_srcdir)/ospf6d/ospf6_intra.c \ @OSPF6D_TRUE@ $(top_srcdir)/ospf6d/ospf6_lsa.c \ @OSPF6D_TRUE@ $(top_srcdir)/ospf6d/ospf6_message.c \ @OSPF6D_TRUE@ $(top_srcdir)/ospf6d/ospf6_neighbor.c \ @OSPF6D_TRUE@ $(top_srcdir)/ospf6d/ospf6_route.c \ @OSPF6D_TRUE@ $(top_srcdir)/ospf6d/ospf6_spf.c \ @OSPF6D_TRUE@ $(top_srcdir)/ospf6d/ospf6_top.c \ @OSPF6D_TRUE@ $(top_srcdir)/ospf6d/ospf6_zebra.c \ @OSPF6D_TRUE@ $(top_srcdir)/ospf6d/ospf6d.c \ @OSPF6D_TRUE@ # end @OSPF6D_TRUE@@SNMP_TRUE@am__append_69 = ospf6d/ospf6d_snmp.la @OSPF6D_TRUE@am__append_70 = $(MANBUILD)/frr-ospf6d.8 @OSPFCLIENT_TRUE@am__append_71 = ospfclient/libfrrospfapiclient.la @OSPFCLIENT_TRUE@am__append_72 = ospfclient/ospfclient # libfrr is linked in through libfrrospfapiclient. If we list it here too, # it gets linked twice and we get a ton of symbol collisions. # For most systems we don't need this, except Debian, who patch their linker # to disallow transitive references *while* *als* not patching their libtool # to work appropriately. RedHat has the same linker behaviour, but things # work as expected since they also patch libtool. @STATIC_BIN_FALSE@am__append_73 = lib/libfrr.la @ISISD_TRUE@am__append_74 = isisd/libisis.a @ISISD_TRUE@am__append_75 = isisd/isisd @ISISD_TRUE@am__append_76 = isisd/isisd.conf.sample @ISISD_TRUE@am__append_77 = \ @ISISD_TRUE@ $(top_srcdir)/isisd/isis_cli.c \ @ISISD_TRUE@ $(top_srcdir)/isisd/isis_redist.c \ @ISISD_TRUE@ $(top_srcdir)/isisd/isis_spf.c \ @ISISD_TRUE@ $(top_srcdir)/isisd/isis_te.c \ @ISISD_TRUE@ $(top_srcdir)/isisd/isis_vty_fabricd.c \ @ISISD_TRUE@ $(top_srcdir)/isisd/isisd.c \ @ISISD_TRUE@ # end @ISISD_TRUE@am__append_78 = $(MANBUILD)/frr-isisd.8 @FABRICD_TRUE@am__append_79 = isisd/libfabric.a @FABRICD_TRUE@am__append_80 = isisd/fabricd @FABRICD_TRUE@am__append_81 = isisd/fabricd.conf.sample @NHRPD_TRUE@am__append_82 = nhrpd/nhrpd @NHRPD_TRUE@am__append_83 = $(top_srcdir)/nhrpd/nhrp_vty.c @NHRPD_TRUE@am__append_84 = $(MANBUILD)/frr-nhrpd.8 @LDPD_TRUE@am__append_85 = ldpd/libldp.a @LDPD_TRUE@am__append_86 = ldpd/ldpd @LDPD_TRUE@am__append_87 = ldpd/ldpd.conf.sample @LDPD_TRUE@am__append_88 = $(top_srcdir)/ldpd/ldp_vty_cmds.c @LDPD_TRUE@am__append_89 = $(MANBUILD)/frr-ldpd.8 @BABELD_TRUE@am__append_90 = babeld/libbabel.a @BABELD_TRUE@am__append_91 = babeld/babeld @BABELD_TRUE@am__append_92 = babeld/babeld.conf.sample @BABELD_TRUE@am__append_93 = \ @BABELD_TRUE@ $(top_srcdir)/babeld/babel_interface.c \ @BABELD_TRUE@ $(top_srcdir)/babeld/babel_zebra.c \ @BABELD_TRUE@ $(top_srcdir)/babeld/babeld.c \ @BABELD_TRUE@ # end @EIGRPD_TRUE@am__append_94 = eigrpd/libeigrp.a @EIGRPD_TRUE@am__append_95 = eigrpd/eigrpd @EIGRPD_TRUE@am__append_96 = eigrpd/eigrpd.conf.sample @EIGRPD_TRUE@am__append_97 = \ @EIGRPD_TRUE@ $(top_srcdir)/eigrpd/eigrp_cli.c \ @EIGRPD_TRUE@ $(top_srcdir)/eigrpd/eigrp_dump.c \ @EIGRPD_TRUE@ $(top_srcdir)/eigrpd/eigrp_vty.c \ @EIGRPD_TRUE@ # end # $(top_srcdir)/eigrpd/eigrp_routemap.c @EIGRPD_TRUE@am__append_98 = $(MANBUILD)/frr-eigrpd.8 @SHARPD_TRUE@am__append_99 = sharpd/libsharp.a @SHARPD_TRUE@am__append_100 = sharpd/sharpd @SHARPD_TRUE@am__append_101 = sharpd/sharpd.conf.sample @SHARPD_TRUE@am__append_102 = $(top_srcdir)/sharpd/sharp_vty.c @SHARPD_TRUE@am__append_103 = $(MANBUILD)/frr-sharpd.8 @PIMD_TRUE@am__append_104 = pimd/libpim.a @PIMD_TRUE@am__append_105 = pimd/pimd @PIMD_TRUE@am__append_106 = pimd/mtracebis @PIMD_TRUE@am__append_107 = pimd/test_igmpv3_join @PIMD_TRUE@am__append_108 = pimd/pimd.conf.sample @PIMD_TRUE@am__append_109 = $(top_srcdir)/pimd/pim_cmd.c @PIMD_TRUE@am__append_110 = $(MANBUILD)/frr-pimd.8 \ @PIMD_TRUE@ $(MANBUILD)/mtracebis.8 @PBRD_TRUE@am__append_111 = pbrd/libpbr.a @PBRD_TRUE@am__append_112 = pbrd/pbrd @PBRD_TRUE@am__append_113 = pbrd/pbrd.conf.sample @PBRD_TRUE@am__append_114 = \ @PBRD_TRUE@ $(top_srcdir)/pbrd/pbr_vty.c \ @PBRD_TRUE@ $(top_srcdir)/pbrd/pbr_debug.c \ @PBRD_TRUE@ # end @PBRD_TRUE@am__append_115 = $(MANBUILD)/frr-pbrd.8 @STATICD_TRUE@am__append_116 = staticd/libstatic.a @STATICD_TRUE@am__append_117 = staticd/staticd @STATICD_TRUE@am__append_118 = staticd/staticd.conf.sample @STATICD_TRUE@am__append_119 = $(top_srcdir)/staticd/static_vty.c @STATICD_TRUE@am__append_120 = $(MANBUILD)/frr-staticd.8 @BFDD_TRUE@am__append_121 = bfdd/libbfd.a @BFDD_TRUE@am__append_122 = bfdd/bfdd @BFDD_TRUE@am__append_123 = bfdd/bfdd.conf.sample @BFDD_TRUE@am__append_124 = $(top_srcdir)/bfdd/bfdd_vty.c \ @BFDD_TRUE@ $(top_srcdir)/bfdd/bfdd_cli.c @BFDD_TRUE@am__append_125 = $(MANBUILD)/frr-bfdd.8 @BFDD_TRUE@am__append_126 = yang/frr-bfdd.yang @EIGRPD_TRUE@am__append_127 = yang/frr-eigrpd.yang @RIPD_TRUE@am__append_128 = yang/frr-ripd.yang @RIPNGD_TRUE@am__append_129 = yang/frr-ripngd.yang @ISISD_TRUE@am__append_130 = yang/frr-isisd.yang @VRRPD_TRUE@am__append_131 = vrrpd/libvrrp.a @VRRPD_TRUE@am__append_132 = vrrpd/vrrpd # dist_examples_DATA += staticd/staticd.conf.sample @VRRPD_TRUE@am__append_133 = $(top_srcdir)/vrrpd/vrrp_vty.c @VRRPD_TRUE@am__append_134 = $(MANBUILD)/frr-vrrpd.8 @VTYSH_TRUE@am__append_135 = vtysh/vtysh @VTYSH_TRUE@am__append_136 = vtysh/vtysh.conf.sample @VTYSH_TRUE@am__append_137 = $(MANBUILD)/vtysh.1 check_PROGRAMS = tests/lib/cxxcompat$(EXEEXT) \ tests/lib/test_atomlist$(EXEEXT) \ tests/lib/test_buffer$(EXEEXT) \ tests/lib/test_checksum$(EXEEXT) \ tests/lib/test_heavy_thread$(EXEEXT) \ tests/lib/test_heavy_wq$(EXEEXT) tests/lib/test_heavy$(EXEEXT) \ tests/lib/test_idalloc$(EXEEXT) tests/lib/test_memory$(EXEEXT) \ tests/lib/test_nexthop_iter$(EXEEXT) \ tests/lib/test_ntop$(EXEEXT) \ tests/lib/test_prefix2str$(EXEEXT) \ tests/lib/test_printfrr$(EXEEXT) tests/lib/test_privs$(EXEEXT) \ tests/lib/test_ringbuf$(EXEEXT) \ tests/lib/test_srcdest_table$(EXEEXT) \ tests/lib/test_segv$(EXEEXT) tests/lib/test_seqlock$(EXEEXT) \ tests/lib/test_sig$(EXEEXT) tests/lib/test_stream$(EXEEXT) \ tests/lib/test_table$(EXEEXT) \ tests/lib/test_timer_correctness$(EXEEXT) \ tests/lib/test_timer_performance$(EXEEXT) \ tests/lib/test_ttable$(EXEEXT) \ tests/lib/test_typelist$(EXEEXT) tests/lib/test_zlog$(EXEEXT) \ tests/lib/test_graph$(EXEEXT) tests/lib/cli/test_cli$(EXEEXT) \ tests/lib/cli/test_commands$(EXEEXT) \ tests/lib/northbound/test_oper_data$(EXEEXT) $(am__EXEEXT_3) \ $(am__EXEEXT_4) $(am__EXEEXT_5) $(am__EXEEXT_6) @ZEROMQ_TRUE@am__append_138 = \ @ZEROMQ_TRUE@ tests/lib/test_zmq \ @ZEROMQ_TRUE@ # end subdir = . ACLOCAL_M4 = $(top_srcdir)/aclocal.m4 am__aclocal_m4_deps = $(top_srcdir)/m4/ax_compare_version.m4 \ $(top_srcdir)/m4/ax_lua.m4 $(top_srcdir)/m4/ax_pthread.m4 \ $(top_srcdir)/m4/ax_python.m4 $(top_srcdir)/m4/libtool.m4 \ $(top_srcdir)/m4/ltoptions.m4 $(top_srcdir)/m4/ltsugar.m4 \ $(top_srcdir)/m4/ltversion.m4 $(top_srcdir)/m4/lt~obsolete.m4 \ $(top_srcdir)/m4/pkg.m4 $(top_srcdir)/configure.ac am__configure_deps = $(am__aclocal_m4_deps) $(CONFIGURE_DEPENDENCIES) \ $(ACLOCAL_M4) DIST_COMMON = $(srcdir)/Makefile.am $(top_srcdir)/configure \ $(am__configure_deps) $(am__dist_examples_DATA_DIST) \ $(am__dist_yangmodels_DATA_DIST) $(eigrpdheader_HEADERS) \ $(noinst_HEADERS) $(am__ospfapiheader_HEADERS_DIST) \ $(am__ospfdheader_HEADERS_DIST) $(am__pkginclude_HEADERS_DIST) \ $(am__DIST_COMMON) am__CONFIG_DISTCLEAN_FILES = config.status config.cache config.log \ configure.lineno config.status.lineno mkinstalldirs = $(install_sh) -d CONFIG_HEADER = config.h CONFIG_CLEAN_FILES = config.version changelog-auto redhat/frr.spec \ solaris/Makefile alpine/APKBUILD snapcraft/snapcraft.yaml \ lib/version.h tests/lib/cli/test_cli.refout pkgsrc/bgpd.sh \ pkgsrc/ospf6d.sh pkgsrc/ospfd.sh pkgsrc/ripd.sh \ pkgsrc/ripngd.sh pkgsrc/zebra.sh pkgsrc/eigrpd.sh \ vtysh/extract.pl tools/frr tools/watchfrr.sh tools/frrinit.sh \ tools/frrcommon.sh CONFIG_CLEAN_VPATH_FILES = @PIMD_TRUE@am__EXEEXT_1 = pimd/mtracebis$(EXEEXT) @VTYSH_TRUE@am__EXEEXT_2 = vtysh/vtysh$(EXEEXT) am__installdirs = "$(DESTDIR)$(bindir)" "$(DESTDIR)$(sbindir)" \ "$(DESTDIR)$(libdir)" "$(DESTDIR)$(moduledir)" \ "$(DESTDIR)$(rcdir)" "$(DESTDIR)$(sbindir)" \ "$(DESTDIR)$(examplesdir)" "$(DESTDIR)$(yangmodelsdir)" \ "$(DESTDIR)$(rstman1dir)" "$(DESTDIR)$(rstman8dir)" \ "$(DESTDIR)$(eigrpdheaderdir)" "$(DESTDIR)$(pkgincludedir)" \ "$(DESTDIR)$(ospfapiheaderdir)" "$(DESTDIR)$(ospfdheaderdir)" \ "$(DESTDIR)$(pkgincludedir)" @BGPD_TRUE@am__EXEEXT_3 = tests/bgpd/test_aspath$(EXEEXT) \ @BGPD_TRUE@ tests/bgpd/test_capability$(EXEEXT) \ @BGPD_TRUE@ tests/bgpd/test_packet$(EXEEXT) \ @BGPD_TRUE@ tests/bgpd/test_peer_attr$(EXEEXT) \ @BGPD_TRUE@ tests/bgpd/test_ecommunity$(EXEEXT) \ @BGPD_TRUE@ tests/bgpd/test_mp_attr$(EXEEXT) \ @BGPD_TRUE@ tests/bgpd/test_mpath$(EXEEXT) \ @BGPD_TRUE@ tests/bgpd/test_bgp_table$(EXEEXT) @ISISD_TRUE@@SOLARIS_FALSE@am__EXEEXT_4 = tests/isisd/test_fuzz_isis_tlv$(EXEEXT) \ @ISISD_TRUE@@SOLARIS_FALSE@ tests/isisd/test_isis_lspdb$(EXEEXT) \ @ISISD_TRUE@@SOLARIS_FALSE@ tests/isisd/test_isis_vertex_queue$(EXEEXT) @OSPF6D_TRUE@am__EXEEXT_5 = tests/ospf6d/test_lsdb$(EXEEXT) @ZEROMQ_TRUE@am__EXEEXT_6 = tests/lib/test_zmq$(EXEEXT) @BUILD_CLIPPY_TRUE@am__EXEEXT_7 = lib/clippy$(EXEEXT) @BGPD_TRUE@am__EXEEXT_8 = bgpd/bgp_btoa$(EXEEXT) @ENABLE_BGP_VNC_TRUE@am__EXEEXT_9 = bgpd/rfp-example/rfptest/rfptest$(EXEEXT) @OSPFCLIENT_TRUE@am__EXEEXT_10 = ospfclient/ospfclient$(EXEEXT) @PIMD_TRUE@am__EXEEXT_11 = pimd/test_igmpv3_join$(EXEEXT) @ZEBRA_TRUE@am__EXEEXT_12 = zebra/zebra$(EXEEXT) @WATCHFRR_TRUE@am__EXEEXT_13 = watchfrr/watchfrr$(EXEEXT) @BGPD_TRUE@am__EXEEXT_14 = bgpd/bgpd$(EXEEXT) @RIPD_TRUE@am__EXEEXT_15 = ripd/ripd$(EXEEXT) @RIPNGD_TRUE@am__EXEEXT_16 = ripngd/ripngd$(EXEEXT) @OSPFD_TRUE@am__EXEEXT_17 = ospfd/ospfd$(EXEEXT) @OSPF6D_TRUE@am__EXEEXT_18 = ospf6d/ospf6d$(EXEEXT) @ISISD_TRUE@am__EXEEXT_19 = isisd/isisd$(EXEEXT) @FABRICD_TRUE@am__EXEEXT_20 = isisd/fabricd$(EXEEXT) @NHRPD_TRUE@am__EXEEXT_21 = nhrpd/nhrpd$(EXEEXT) @LDPD_TRUE@am__EXEEXT_22 = ldpd/ldpd$(EXEEXT) @BABELD_TRUE@am__EXEEXT_23 = babeld/babeld$(EXEEXT) @EIGRPD_TRUE@am__EXEEXT_24 = eigrpd/eigrpd$(EXEEXT) @SHARPD_TRUE@am__EXEEXT_25 = sharpd/sharpd$(EXEEXT) @PIMD_TRUE@am__EXEEXT_26 = pimd/pimd$(EXEEXT) @PBRD_TRUE@am__EXEEXT_27 = pbrd/pbrd$(EXEEXT) @STATICD_TRUE@am__EXEEXT_28 = staticd/staticd$(EXEEXT) @BFDD_TRUE@am__EXEEXT_29 = bfdd/bfdd$(EXEEXT) @VRRPD_TRUE@am__EXEEXT_30 = vrrpd/vrrpd$(EXEEXT) PROGRAMS = $(bin_PROGRAMS) $(noinst_PROGRAMS) $(sbin_PROGRAMS) LIBRARIES = $(noinst_LIBRARIES) am__vpath_adj_setup = srcdirstrip=`echo "$(srcdir)" | sed 's|.|.|g'`; am__vpath_adj = case $$p in \ $(srcdir)/*) f=`echo "$$p" | sed "s|^$$srcdirstrip/||"`;; \ *) f=$$p;; \ esac; am__strip_dir = f=`echo $$p | sed -e 's|^.*/||'`; am__install_max = 40 am__nobase_strip_setup = \ srcdirstrip=`echo "$(srcdir)" | sed 's/[].[^$$\\*|]/\\\\&/g'` am__nobase_strip = \ for p in $$list; do echo "$$p"; done | sed -e "s|$$srcdirstrip/||" am__nobase_list = $(am__nobase_strip_setup); \ for p in $$list; do echo "$$p $$p"; done | \ sed "s| $$srcdirstrip/| |;"' / .*\//!s/ .*/ ./; s,\( .*\)/[^/]*$$,\1,' | \ $(AWK) 'BEGIN { files["."] = "" } { files[$$2] = files[$$2] " " $$1; \ if (++n[$$2] == $(am__install_max)) \ { print $$2, files[$$2]; n[$$2] = 0; files[$$2] = "" } } \ END { for (dir in files) print dir, files[dir] }' am__base_list = \ sed '$$!N;$$!N;$$!N;$$!N;$$!N;$$!N;$$!N;s/\n/ /g' | \ sed '$$!N;$$!N;$$!N;$$!N;s/\n/ /g' am__uninstall_files_from_dir = { \ test -z "$$files" \ || { test ! -d "$$dir" && test ! -f "$$dir" && test ! -r "$$dir"; } \ || { echo " ( cd '$$dir' && rm -f" $$files ")"; \ $(am__cd) "$$dir" && rm -f $$files; }; \ } LTLIBRARIES = $(lib_LTLIBRARIES) $(module_LTLIBRARIES) AM_V_AR = $(am__v_AR_@AM_V@) am__v_AR_ = $(am__v_AR_@AM_DEFAULT_V@) am__v_AR_0 = @echo " AR " $@; am__v_AR_1 = babeld_libbabel_a_AR = $(AR) $(ARFLAGS) babeld_libbabel_a_LIBADD = am__dirstamp = $(am__leading_dot)dirstamp am_babeld_libbabel_a_OBJECTS = babeld/babel_errors.$(OBJEXT) \ babeld/babel_filter.$(OBJEXT) babeld/babel_interface.$(OBJEXT) \ babeld/babel_zebra.$(OBJEXT) babeld/babeld.$(OBJEXT) \ babeld/kernel.$(OBJEXT) babeld/message.$(OBJEXT) \ babeld/neighbour.$(OBJEXT) babeld/net.$(OBJEXT) \ babeld/resend.$(OBJEXT) babeld/route.$(OBJEXT) \ babeld/source.$(OBJEXT) babeld/util.$(OBJEXT) \ babeld/xroute.$(OBJEXT) babeld_libbabel_a_OBJECTS = $(am_babeld_libbabel_a_OBJECTS) bfdd_libbfd_a_AR = $(AR) $(ARFLAGS) bfdd_libbfd_a_LIBADD = am_bfdd_libbfd_a_OBJECTS = bfdd/bfd.$(OBJEXT) \ bfdd/bfdd_northbound.$(OBJEXT) bfdd/bfdd_vty.$(OBJEXT) \ bfdd/bfdd_cli.$(OBJEXT) bfdd/bfd_packet.$(OBJEXT) \ bfdd/config.$(OBJEXT) bfdd/control.$(OBJEXT) \ bfdd/event.$(OBJEXT) bfdd/log.$(OBJEXT) \ bfdd/ptm_adapter.$(OBJEXT) bfdd_libbfd_a_OBJECTS = $(am_bfdd_libbfd_a_OBJECTS) bgpd_libbgp_a_AR = $(AR) $(ARFLAGS) bgpd_libbgp_a_LIBADD = am__bgpd_libbgp_a_SOURCES_DIST = bgpd/bgp_addpath.c \ bgpd/bgp_advertise.c bgpd/bgp_aspath.c bgpd/bgp_attr.c \ bgpd/bgp_attr_evpn.c bgpd/bgp_bfd.c bgpd/bgp_clist.c \ bgpd/bgp_community.c bgpd/bgp_damp.c bgpd/bgp_debug.c \ bgpd/bgp_dump.c bgpd/bgp_ecommunity.c bgpd/bgp_encap_tlv.c \ bgpd/bgp_errors.c bgpd/bgp_evpn.c bgpd/bgp_evpn_vty.c \ bgpd/bgp_filter.c bgpd/bgp_flowspec.c bgpd/bgp_flowspec_util.c \ bgpd/bgp_flowspec_vty.c bgpd/bgp_fsm.c bgpd/bgp_io.c \ bgpd/bgp_keepalives.c bgpd/bgp_label.c bgpd/bgp_labelpool.c \ bgpd/bgp_lcommunity.c bgpd/bgp_mac.c bgpd/bgp_memory.c \ bgpd/bgp_mpath.c bgpd/bgp_mplsvpn.c bgpd/bgp_network.c \ bgpd/bgp_nexthop.c bgpd/bgp_nht.c bgpd/bgp_open.c \ bgpd/bgp_packet.c bgpd/bgp_pbr.c bgpd/bgp_rd.c \ bgpd/bgp_regex.c bgpd/bgp_route.c bgpd/bgp_routemap.c \ bgpd/bgp_table.c bgpd/bgp_updgrp.c bgpd/bgp_updgrp_adv.c \ bgpd/bgp_updgrp_packet.c bgpd/bgp_vpn.c bgpd/bgp_vty.c \ bgpd/bgp_zebra.c bgpd/bgpd.c bgpd/rfapi/bgp_rfapi_cfg.c \ bgpd/rfapi/rfapi_import.c bgpd/rfapi/rfapi.c \ bgpd/rfapi/rfapi_ap.c bgpd/rfapi/rfapi_descriptor_rfp_utils.c \ bgpd/rfapi/rfapi_encap_tlv.c bgpd/rfapi/rfapi_nve_addr.c \ bgpd/rfapi/rfapi_monitor.c bgpd/rfapi/rfapi_rib.c \ bgpd/rfapi/rfapi_vty.c bgpd/rfapi/vnc_debug.c \ bgpd/rfapi/vnc_export_bgp.c bgpd/rfapi/vnc_export_table.c \ bgpd/rfapi/vnc_import_bgp.c bgpd/rfapi/vnc_zebra.c @ENABLE_BGP_VNC_TRUE@am__objects_1 = \ @ENABLE_BGP_VNC_TRUE@ bgpd/rfapi/bgp_rfapi_cfg.$(OBJEXT) \ @ENABLE_BGP_VNC_TRUE@ bgpd/rfapi/rfapi_import.$(OBJEXT) \ @ENABLE_BGP_VNC_TRUE@ bgpd/rfapi/rfapi.$(OBJEXT) \ @ENABLE_BGP_VNC_TRUE@ bgpd/rfapi/rfapi_ap.$(OBJEXT) \ @ENABLE_BGP_VNC_TRUE@ bgpd/rfapi/rfapi_descriptor_rfp_utils.$(OBJEXT) \ @ENABLE_BGP_VNC_TRUE@ bgpd/rfapi/rfapi_encap_tlv.$(OBJEXT) \ @ENABLE_BGP_VNC_TRUE@ bgpd/rfapi/rfapi_nve_addr.$(OBJEXT) \ @ENABLE_BGP_VNC_TRUE@ bgpd/rfapi/rfapi_monitor.$(OBJEXT) \ @ENABLE_BGP_VNC_TRUE@ bgpd/rfapi/rfapi_rib.$(OBJEXT) \ @ENABLE_BGP_VNC_TRUE@ bgpd/rfapi/rfapi_vty.$(OBJEXT) \ @ENABLE_BGP_VNC_TRUE@ bgpd/rfapi/vnc_debug.$(OBJEXT) \ @ENABLE_BGP_VNC_TRUE@ bgpd/rfapi/vnc_export_bgp.$(OBJEXT) \ @ENABLE_BGP_VNC_TRUE@ bgpd/rfapi/vnc_export_table.$(OBJEXT) \ @ENABLE_BGP_VNC_TRUE@ bgpd/rfapi/vnc_import_bgp.$(OBJEXT) \ @ENABLE_BGP_VNC_TRUE@ bgpd/rfapi/vnc_zebra.$(OBJEXT) am_bgpd_libbgp_a_OBJECTS = bgpd/bgp_addpath.$(OBJEXT) \ bgpd/bgp_advertise.$(OBJEXT) bgpd/bgp_aspath.$(OBJEXT) \ bgpd/bgp_attr.$(OBJEXT) bgpd/bgp_attr_evpn.$(OBJEXT) \ bgpd/bgp_bfd.$(OBJEXT) bgpd/bgp_clist.$(OBJEXT) \ bgpd/bgp_community.$(OBJEXT) bgpd/bgp_damp.$(OBJEXT) \ bgpd/bgp_debug.$(OBJEXT) bgpd/bgp_dump.$(OBJEXT) \ bgpd/bgp_ecommunity.$(OBJEXT) bgpd/bgp_encap_tlv.$(OBJEXT) \ bgpd/bgp_errors.$(OBJEXT) bgpd/bgp_evpn.$(OBJEXT) \ bgpd/bgp_evpn_vty.$(OBJEXT) bgpd/bgp_filter.$(OBJEXT) \ bgpd/bgp_flowspec.$(OBJEXT) bgpd/bgp_flowspec_util.$(OBJEXT) \ bgpd/bgp_flowspec_vty.$(OBJEXT) bgpd/bgp_fsm.$(OBJEXT) \ bgpd/bgp_io.$(OBJEXT) bgpd/bgp_keepalives.$(OBJEXT) \ bgpd/bgp_label.$(OBJEXT) bgpd/bgp_labelpool.$(OBJEXT) \ bgpd/bgp_lcommunity.$(OBJEXT) bgpd/bgp_mac.$(OBJEXT) \ bgpd/bgp_memory.$(OBJEXT) bgpd/bgp_mpath.$(OBJEXT) \ bgpd/bgp_mplsvpn.$(OBJEXT) bgpd/bgp_network.$(OBJEXT) \ bgpd/bgp_nexthop.$(OBJEXT) bgpd/bgp_nht.$(OBJEXT) \ bgpd/bgp_open.$(OBJEXT) bgpd/bgp_packet.$(OBJEXT) \ bgpd/bgp_pbr.$(OBJEXT) bgpd/bgp_rd.$(OBJEXT) \ bgpd/bgp_regex.$(OBJEXT) bgpd/bgp_route.$(OBJEXT) \ bgpd/bgp_routemap.$(OBJEXT) bgpd/bgp_table.$(OBJEXT) \ bgpd/bgp_updgrp.$(OBJEXT) bgpd/bgp_updgrp_adv.$(OBJEXT) \ bgpd/bgp_updgrp_packet.$(OBJEXT) bgpd/bgp_vpn.$(OBJEXT) \ bgpd/bgp_vty.$(OBJEXT) bgpd/bgp_zebra.$(OBJEXT) \ bgpd/bgpd.$(OBJEXT) $(am__objects_1) bgpd_libbgp_a_OBJECTS = $(am_bgpd_libbgp_a_OBJECTS) bgpd_rfp_example_librfp_librfp_a_AR = $(AR) $(ARFLAGS) bgpd_rfp_example_librfp_librfp_a_LIBADD = am_bgpd_rfp_example_librfp_librfp_a_OBJECTS = \ bgpd/rfp-example/librfp/rfp_example.$(OBJEXT) bgpd_rfp_example_librfp_librfp_a_OBJECTS = \ $(am_bgpd_rfp_example_librfp_librfp_a_OBJECTS) eigrpd_libeigrp_a_AR = $(AR) $(ARFLAGS) eigrpd_libeigrp_a_LIBADD = am_eigrpd_libeigrp_a_OBJECTS = eigrpd/eigrp_cli.$(OBJEXT) \ eigrpd/eigrp_dump.$(OBJEXT) eigrpd/eigrp_errors.$(OBJEXT) \ eigrpd/eigrp_filter.$(OBJEXT) eigrpd/eigrp_fsm.$(OBJEXT) \ eigrpd/eigrp_hello.$(OBJEXT) eigrpd/eigrp_interface.$(OBJEXT) \ eigrpd/eigrp_memory.$(OBJEXT) eigrpd/eigrp_neighbor.$(OBJEXT) \ eigrpd/eigrp_network.$(OBJEXT) \ eigrpd/eigrp_northbound.$(OBJEXT) \ eigrpd/eigrp_packet.$(OBJEXT) eigrpd/eigrp_query.$(OBJEXT) \ eigrpd/eigrp_reply.$(OBJEXT) eigrpd/eigrp_siaquery.$(OBJEXT) \ eigrpd/eigrp_siareply.$(OBJEXT) eigrpd/eigrp_snmp.$(OBJEXT) \ eigrpd/eigrp_topology.$(OBJEXT) eigrpd/eigrp_update.$(OBJEXT) \ eigrpd/eigrp_vrf.$(OBJEXT) eigrpd/eigrp_vty.$(OBJEXT) \ eigrpd/eigrp_zebra.$(OBJEXT) eigrpd/eigrpd.$(OBJEXT) eigrpd_libeigrp_a_OBJECTS = $(am_eigrpd_libeigrp_a_OBJECTS) isisd_libfabric_a_AR = $(AR) $(ARFLAGS) isisd_libfabric_a_LIBADD = am__objects_2 = isisd/libfabric_a-isis_adjacency.$(OBJEXT) \ isisd/libfabric_a-isis_bfd.$(OBJEXT) \ isisd/libfabric_a-isis_circuit.$(OBJEXT) \ isisd/libfabric_a-isis_csm.$(OBJEXT) \ isisd/libfabric_a-isis_dr.$(OBJEXT) \ isisd/libfabric_a-isis_dynhn.$(OBJEXT) \ isisd/libfabric_a-isis_errors.$(OBJEXT) \ isisd/libfabric_a-isis_events.$(OBJEXT) \ isisd/libfabric_a-isis_flags.$(OBJEXT) \ isisd/libfabric_a-isis_lsp.$(OBJEXT) \ isisd/libfabric_a-isis_memory.$(OBJEXT) \ isisd/libfabric_a-isis_misc.$(OBJEXT) \ isisd/libfabric_a-isis_mt.$(OBJEXT) \ isisd/libfabric_a-isis_pdu.$(OBJEXT) \ isisd/libfabric_a-isis_pdu_counter.$(OBJEXT) \ isisd/libfabric_a-isis_redist.$(OBJEXT) \ isisd/libfabric_a-isis_route.$(OBJEXT) \ isisd/libfabric_a-isis_routemap.$(OBJEXT) \ isisd/libfabric_a-isis_spf.$(OBJEXT) \ isisd/libfabric_a-isis_te.$(OBJEXT) \ isisd/libfabric_a-isis_tlvs.$(OBJEXT) \ isisd/libfabric_a-isis_tx_queue.$(OBJEXT) \ isisd/libfabric_a-isis_zebra.$(OBJEXT) \ isisd/libfabric_a-isisd.$(OBJEXT) \ isisd/libfabric_a-iso_checksum.$(OBJEXT) \ isisd/libfabric_a-fabricd.$(OBJEXT) am_isisd_libfabric_a_OBJECTS = $(am__objects_2) \ isisd/libfabric_a-isis_vty_fabricd.$(OBJEXT) isisd_libfabric_a_OBJECTS = $(am_isisd_libfabric_a_OBJECTS) isisd_libisis_a_AR = $(AR) $(ARFLAGS) isisd_libisis_a_LIBADD = am__objects_3 = isisd/isis_adjacency.$(OBJEXT) \ isisd/isis_bfd.$(OBJEXT) isisd/isis_circuit.$(OBJEXT) \ isisd/isis_csm.$(OBJEXT) isisd/isis_dr.$(OBJEXT) \ isisd/isis_dynhn.$(OBJEXT) isisd/isis_errors.$(OBJEXT) \ isisd/isis_events.$(OBJEXT) isisd/isis_flags.$(OBJEXT) \ isisd/isis_lsp.$(OBJEXT) isisd/isis_memory.$(OBJEXT) \ isisd/isis_misc.$(OBJEXT) isisd/isis_mt.$(OBJEXT) \ isisd/isis_pdu.$(OBJEXT) isisd/isis_pdu_counter.$(OBJEXT) \ isisd/isis_redist.$(OBJEXT) isisd/isis_route.$(OBJEXT) \ isisd/isis_routemap.$(OBJEXT) isisd/isis_spf.$(OBJEXT) \ isisd/isis_te.$(OBJEXT) isisd/isis_tlvs.$(OBJEXT) \ isisd/isis_tx_queue.$(OBJEXT) isisd/isis_zebra.$(OBJEXT) \ isisd/isisd.$(OBJEXT) isisd/iso_checksum.$(OBJEXT) \ isisd/fabricd.$(OBJEXT) am_isisd_libisis_a_OBJECTS = $(am__objects_3) \ isisd/isis_northbound.$(OBJEXT) isisd/isis_cli.$(OBJEXT) isisd_libisis_a_OBJECTS = $(am_isisd_libisis_a_OBJECTS) ldpd_libldp_a_AR = $(AR) $(ARFLAGS) ldpd_libldp_a_LIBADD = am_ldpd_libldp_a_OBJECTS = ldpd/accept.$(OBJEXT) \ ldpd/address.$(OBJEXT) ldpd/adjacency.$(OBJEXT) \ ldpd/control.$(OBJEXT) ldpd/hello.$(OBJEXT) \ ldpd/init.$(OBJEXT) ldpd/interface.$(OBJEXT) \ ldpd/keepalive.$(OBJEXT) ldpd/l2vpn.$(OBJEXT) \ ldpd/labelmapping.$(OBJEXT) ldpd/lde.$(OBJEXT) \ ldpd/lde_lib.$(OBJEXT) ldpd/ldp_debug.$(OBJEXT) \ ldpd/ldp_vty_cmds.$(OBJEXT) ldpd/ldp_vty_conf.$(OBJEXT) \ ldpd/ldp_vty_exec.$(OBJEXT) ldpd/ldp_zebra.$(OBJEXT) \ ldpd/ldpd.$(OBJEXT) ldpd/ldpe.$(OBJEXT) ldpd/log.$(OBJEXT) \ ldpd/logmsg.$(OBJEXT) ldpd/neighbor.$(OBJEXT) \ ldpd/notification.$(OBJEXT) ldpd/packet.$(OBJEXT) \ ldpd/pfkey.$(OBJEXT) ldpd/socket.$(OBJEXT) ldpd/util.$(OBJEXT) ldpd_libldp_a_OBJECTS = $(am_ldpd_libldp_a_OBJECTS) ospf6d_libospf6_a_AR = $(AR) $(ARFLAGS) ospf6d_libospf6_a_LIBADD = am_ospf6d_libospf6_a_OBJECTS = ospf6d/ospf6_abr.$(OBJEXT) \ ospf6d/ospf6_area.$(OBJEXT) ospf6d/ospf6_asbr.$(OBJEXT) \ ospf6d/ospf6_bfd.$(OBJEXT) ospf6d/ospf6_flood.$(OBJEXT) \ ospf6d/ospf6_interface.$(OBJEXT) ospf6d/ospf6_intra.$(OBJEXT) \ ospf6d/ospf6_lsa.$(OBJEXT) ospf6d/ospf6_lsdb.$(OBJEXT) \ ospf6d/ospf6_memory.$(OBJEXT) ospf6d/ospf6_message.$(OBJEXT) \ ospf6d/ospf6_neighbor.$(OBJEXT) ospf6d/ospf6_network.$(OBJEXT) \ ospf6d/ospf6_proto.$(OBJEXT) ospf6d/ospf6_route.$(OBJEXT) \ ospf6d/ospf6_spf.$(OBJEXT) ospf6d/ospf6_top.$(OBJEXT) \ ospf6d/ospf6_zebra.$(OBJEXT) ospf6d/ospf6d.$(OBJEXT) ospf6d_libospf6_a_OBJECTS = $(am_ospf6d_libospf6_a_OBJECTS) ospfd_libfrrospf_a_AR = $(AR) $(ARFLAGS) ospfd_libfrrospf_a_LIBADD = am_ospfd_libfrrospf_a_OBJECTS = ospfd/ospf_abr.$(OBJEXT) \ ospfd/ospf_api.$(OBJEXT) ospfd/ospf_apiserver.$(OBJEXT) \ ospfd/ospf_asbr.$(OBJEXT) ospfd/ospf_ase.$(OBJEXT) \ ospfd/ospf_bfd.$(OBJEXT) ospfd/ospf_dump.$(OBJEXT) \ ospfd/ospf_dump_api.$(OBJEXT) ospfd/ospf_errors.$(OBJEXT) \ ospfd/ospf_ext.$(OBJEXT) ospfd/ospf_flood.$(OBJEXT) \ ospfd/ospf_ia.$(OBJEXT) ospfd/ospf_interface.$(OBJEXT) \ ospfd/ospf_ism.$(OBJEXT) ospfd/ospf_lsa.$(OBJEXT) \ ospfd/ospf_lsdb.$(OBJEXT) ospfd/ospf_memory.$(OBJEXT) \ ospfd/ospf_neighbor.$(OBJEXT) ospfd/ospf_network.$(OBJEXT) \ ospfd/ospf_nsm.$(OBJEXT) ospfd/ospf_opaque.$(OBJEXT) \ ospfd/ospf_packet.$(OBJEXT) ospfd/ospf_ri.$(OBJEXT) \ ospfd/ospf_route.$(OBJEXT) ospfd/ospf_routemap.$(OBJEXT) \ ospfd/ospf_spf.$(OBJEXT) ospfd/ospf_sr.$(OBJEXT) \ ospfd/ospf_te.$(OBJEXT) ospfd/ospf_vty.$(OBJEXT) \ ospfd/ospf_zebra.$(OBJEXT) ospfd/ospfd.$(OBJEXT) ospfd_libfrrospf_a_OBJECTS = $(am_ospfd_libfrrospf_a_OBJECTS) pbrd_libpbr_a_AR = $(AR) $(ARFLAGS) pbrd_libpbr_a_LIBADD = am_pbrd_libpbr_a_OBJECTS = pbrd/pbr_zebra.$(OBJEXT) \ pbrd/pbr_vty.$(OBJEXT) pbrd/pbr_map.$(OBJEXT) \ pbrd/pbr_memory.$(OBJEXT) pbrd/pbr_nht.$(OBJEXT) \ pbrd/pbr_debug.$(OBJEXT) pbrd_libpbr_a_OBJECTS = $(am_pbrd_libpbr_a_OBJECTS) pimd_libpim_a_AR = $(AR) $(ARFLAGS) pimd_libpim_a_LIBADD = am_pimd_libpim_a_OBJECTS = pimd/pim_assert.$(OBJEXT) \ pimd/pim_bfd.$(OBJEXT) pimd/pim_br.$(OBJEXT) \ pimd/pim_bsm.$(OBJEXT) pimd/pim_cmd.$(OBJEXT) \ pimd/pim_errors.$(OBJEXT) pimd/pim_hello.$(OBJEXT) \ pimd/pim_iface.$(OBJEXT) pimd/pim_ifchannel.$(OBJEXT) \ pimd/pim_igmp.$(OBJEXT) pimd/pim_igmp_mtrace.$(OBJEXT) \ pimd/pim_igmp_stats.$(OBJEXT) pimd/pim_igmpv2.$(OBJEXT) \ pimd/pim_igmpv3.$(OBJEXT) pimd/pim_instance.$(OBJEXT) \ pimd/pim_int.$(OBJEXT) pimd/pim_join.$(OBJEXT) \ pimd/pim_jp_agg.$(OBJEXT) pimd/pim_macro.$(OBJEXT) \ pimd/pim_memory.$(OBJEXT) pimd/pim_mroute.$(OBJEXT) \ pimd/pim_msdp.$(OBJEXT) pimd/pim_msdp_packet.$(OBJEXT) \ pimd/pim_msdp_socket.$(OBJEXT) pimd/pim_msg.$(OBJEXT) \ pimd/pim_neighbor.$(OBJEXT) pimd/pim_nht.$(OBJEXT) \ pimd/pim_oil.$(OBJEXT) pimd/pim_pim.$(OBJEXT) \ pimd/pim_register.$(OBJEXT) pimd/pim_routemap.$(OBJEXT) \ pimd/pim_rp.$(OBJEXT) pimd/pim_rpf.$(OBJEXT) \ pimd/pim_signals.$(OBJEXT) pimd/pim_sock.$(OBJEXT) \ pimd/pim_ssm.$(OBJEXT) pimd/pim_ssmpingd.$(OBJEXT) \ pimd/pim_static.$(OBJEXT) pimd/pim_str.$(OBJEXT) \ pimd/pim_time.$(OBJEXT) pimd/pim_tlv.$(OBJEXT) \ pimd/pim_upstream.$(OBJEXT) pimd/pim_util.$(OBJEXT) \ pimd/pim_version.$(OBJEXT) pimd/pim_vty.$(OBJEXT) \ pimd/pim_zebra.$(OBJEXT) pimd/pim_zlookup.$(OBJEXT) \ pimd/pim_vxlan.$(OBJEXT) pimd/pimd.$(OBJEXT) pimd_libpim_a_OBJECTS = $(am_pimd_libpim_a_OBJECTS) ripd_librip_a_AR = $(AR) $(ARFLAGS) ripd_librip_a_LIBADD = am_ripd_librip_a_OBJECTS = ripd/rip_cli.$(OBJEXT) \ ripd/rip_debug.$(OBJEXT) ripd/rip_errors.$(OBJEXT) \ ripd/rip_interface.$(OBJEXT) ripd/rip_offset.$(OBJEXT) \ ripd/rip_northbound.$(OBJEXT) ripd/rip_peer.$(OBJEXT) \ ripd/rip_routemap.$(OBJEXT) ripd/rip_zebra.$(OBJEXT) \ ripd/ripd.$(OBJEXT) ripd_librip_a_OBJECTS = $(am_ripd_librip_a_OBJECTS) ripngd_libripng_a_AR = $(AR) $(ARFLAGS) ripngd_libripng_a_LIBADD = am_ripngd_libripng_a_OBJECTS = ripngd/ripng_cli.$(OBJEXT) \ ripngd/ripng_debug.$(OBJEXT) ripngd/ripng_interface.$(OBJEXT) \ ripngd/ripng_nexthop.$(OBJEXT) ripngd/ripng_offset.$(OBJEXT) \ ripngd/ripng_northbound.$(OBJEXT) ripngd/ripng_peer.$(OBJEXT) \ ripngd/ripng_route.$(OBJEXT) ripngd/ripng_routemap.$(OBJEXT) \ ripngd/ripng_zebra.$(OBJEXT) ripngd/ripngd.$(OBJEXT) ripngd_libripng_a_OBJECTS = $(am_ripngd_libripng_a_OBJECTS) sharpd_libsharp_a_AR = $(AR) $(ARFLAGS) sharpd_libsharp_a_LIBADD = am_sharpd_libsharp_a_OBJECTS = sharpd/sharp_nht.$(OBJEXT) \ sharpd/sharp_zebra.$(OBJEXT) sharpd/sharp_vty.$(OBJEXT) sharpd_libsharp_a_OBJECTS = $(am_sharpd_libsharp_a_OBJECTS) staticd_libstatic_a_AR = $(AR) $(ARFLAGS) staticd_libstatic_a_LIBADD = am_staticd_libstatic_a_OBJECTS = staticd/static_memory.$(OBJEXT) \ staticd/static_nht.$(OBJEXT) staticd/static_routes.$(OBJEXT) \ staticd/static_zebra.$(OBJEXT) staticd/static_vrf.$(OBJEXT) \ staticd/static_vty.$(OBJEXT) staticd_libstatic_a_OBJECTS = $(am_staticd_libstatic_a_OBJECTS) vrrpd_libvrrp_a_AR = $(AR) $(ARFLAGS) vrrpd_libvrrp_a_LIBADD = am_vrrpd_libvrrp_a_OBJECTS = vrrpd/vrrp.$(OBJEXT) \ vrrpd/vrrp_arp.$(OBJEXT) vrrpd/vrrp_debug.$(OBJEXT) \ vrrpd/vrrp_ndisc.$(OBJEXT) vrrpd/vrrp_packet.$(OBJEXT) \ vrrpd/vrrp_vty.$(OBJEXT) vrrpd/vrrp_zebra.$(OBJEXT) vrrpd_libvrrp_a_OBJECTS = $(am_vrrpd_libvrrp_a_OBJECTS) bgpd_bgpd_bmp_la_DEPENDENCIES = lib/libfrrcares.la am_bgpd_bgpd_bmp_la_OBJECTS = bgpd/bgp_bmp.lo bgpd_bgpd_bmp_la_OBJECTS = $(am_bgpd_bgpd_bmp_la_OBJECTS) AM_V_lt = $(am__v_lt_@AM_V@) am__v_lt_ = $(am__v_lt_@AM_DEFAULT_V@) am__v_lt_0 = --silent am__v_lt_1 = bgpd_bgpd_bmp_la_LINK = $(LIBTOOL) $(AM_V_lt) --tag=CC \ $(AM_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=link $(CCLD) \ $(AM_CFLAGS) $(CFLAGS) $(bgpd_bgpd_bmp_la_LDFLAGS) $(LDFLAGS) \ -o $@ @BGPD_TRUE@@BGP_BMP_TRUE@am_bgpd_bgpd_bmp_la_rpath = -rpath \ @BGPD_TRUE@@BGP_BMP_TRUE@ $(moduledir) am__DEPENDENCIES_1 = bgpd_bgpd_rpki_la_DEPENDENCIES = $(am__DEPENDENCIES_1) am_bgpd_bgpd_rpki_la_OBJECTS = bgpd/bgpd_rpki_la-bgp_rpki.lo bgpd_bgpd_rpki_la_OBJECTS = $(am_bgpd_bgpd_rpki_la_OBJECTS) bgpd_bgpd_rpki_la_LINK = $(LIBTOOL) $(AM_V_lt) --tag=CC \ $(AM_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=link $(CCLD) \ $(bgpd_bgpd_rpki_la_CFLAGS) $(CFLAGS) \ $(bgpd_bgpd_rpki_la_LDFLAGS) $(LDFLAGS) -o $@ @BGPD_TRUE@@RPKI_TRUE@am_bgpd_bgpd_rpki_la_rpath = -rpath $(moduledir) bgpd_bgpd_snmp_la_DEPENDENCIES = lib/libfrrsnmp.la am_bgpd_bgpd_snmp_la_OBJECTS = bgpd/bgpd_snmp_la-bgp_snmp.lo bgpd_bgpd_snmp_la_OBJECTS = $(am_bgpd_bgpd_snmp_la_OBJECTS) bgpd_bgpd_snmp_la_LINK = $(LIBTOOL) $(AM_V_lt) --tag=CC \ $(AM_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=link $(CCLD) \ $(bgpd_bgpd_snmp_la_CFLAGS) $(CFLAGS) \ $(bgpd_bgpd_snmp_la_LDFLAGS) $(LDFLAGS) -o $@ @BGPD_TRUE@@SNMP_TRUE@am_bgpd_bgpd_snmp_la_rpath = -rpath $(moduledir) fpm_libfrrfpm_pb_la_LIBADD = am_fpm_libfrrfpm_pb_la_OBJECTS = fpm/libfrrfpm_pb_la-fpm_pb.lo nodist_fpm_libfrrfpm_pb_la_OBJECTS = fpm/libfrrfpm_pb_la-fpm.pb-c.lo fpm_libfrrfpm_pb_la_OBJECTS = $(am_fpm_libfrrfpm_pb_la_OBJECTS) \ $(nodist_fpm_libfrrfpm_pb_la_OBJECTS) fpm_libfrrfpm_pb_la_LINK = $(LIBTOOL) $(AM_V_lt) --tag=CC \ $(AM_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=link $(CCLD) \ $(AM_CFLAGS) $(CFLAGS) $(fpm_libfrrfpm_pb_la_LDFLAGS) \ $(LDFLAGS) -o $@ @FPM_TRUE@@HAVE_PROTOBUF_TRUE@am_fpm_libfrrfpm_pb_la_rpath = -rpath \ @FPM_TRUE@@HAVE_PROTOBUF_TRUE@ $(libdir) grpc_libfrrgrpc_pb_la_LIBADD = nodist_grpc_libfrrgrpc_pb_la_OBJECTS = \ grpc/libfrrgrpc_pb_la-frr-northbound.pb.lo \ grpc/libfrrgrpc_pb_la-frr-northbound.grpc.pb.lo grpc_libfrrgrpc_pb_la_OBJECTS = \ $(nodist_grpc_libfrrgrpc_pb_la_OBJECTS) grpc_libfrrgrpc_pb_la_LINK = $(LIBTOOL) $(AM_V_lt) --tag=CXX \ $(AM_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=link $(CXXLD) \ $(AM_CXXFLAGS) $(CXXFLAGS) $(grpc_libfrrgrpc_pb_la_LDFLAGS) \ $(LDFLAGS) -o $@ @GRPC_TRUE@am_grpc_libfrrgrpc_pb_la_rpath = -rpath $(libdir) lib_confd_la_DEPENDENCIES = lib/libfrr.la $(am__DEPENDENCIES_1) am_lib_confd_la_OBJECTS = lib/confd_la-northbound_confd.lo lib_confd_la_OBJECTS = $(am_lib_confd_la_OBJECTS) lib_confd_la_LINK = $(LIBTOOL) $(AM_V_lt) --tag=CC $(AM_LIBTOOLFLAGS) \ $(LIBTOOLFLAGS) --mode=link $(CCLD) $(lib_confd_la_CFLAGS) \ $(CFLAGS) $(lib_confd_la_LDFLAGS) $(LDFLAGS) -o $@ @CONFD_TRUE@am_lib_confd_la_rpath = -rpath $(moduledir) lib_grpc_la_DEPENDENCIES = lib/libfrr.la grpc/libfrrgrpc_pb.la \ $(am__DEPENDENCIES_1) am_lib_grpc_la_OBJECTS = lib/grpc_la-northbound_grpc.lo lib_grpc_la_OBJECTS = $(am_lib_grpc_la_OBJECTS) lib_grpc_la_LINK = $(LIBTOOL) $(AM_V_lt) --tag=CXX $(AM_LIBTOOLFLAGS) \ $(LIBTOOLFLAGS) --mode=link $(CXXLD) $(lib_grpc_la_CXXFLAGS) \ $(CXXFLAGS) $(lib_grpc_la_LDFLAGS) $(LDFLAGS) -o $@ @GRPC_TRUE@am_lib_grpc_la_rpath = -rpath $(moduledir) @SQLITE3_TRUE@am__DEPENDENCIES_2 = $(am__DEPENDENCIES_1) lib_libfrr_la_DEPENDENCIES = $(am__DEPENDENCIES_1) \ $(am__DEPENDENCIES_1) $(am__DEPENDENCIES_1) \ $(am__DEPENDENCIES_1) $(am__DEPENDENCIES_1) \ $(am__DEPENDENCIES_2) am__lib_libfrr_la_SOURCES_DIST = lib/agg_table.c lib/atomlist.c \ lib/bfd.c lib/buffer.c lib/checksum.c lib/command.c \ lib/command_graph.c lib/command_lex.l lib/command_match.c \ lib/command_parse.y lib/csv.c lib/debug.c lib/distribute.c \ lib/ferr.c lib/filter.c lib/frrcu.c lib/frrlua.c \ lib/frr_pthread.c lib/frrstr.c lib/getopt.c lib/getopt1.c \ lib/grammar_sandbox.c lib/graph.c lib/hash.c lib/hook.c \ lib/id_alloc.c lib/if.c lib/if_rmap.c lib/imsg-buffer.c \ lib/imsg.c lib/jhash.c lib/json.c lib/keychain.c \ lib/lib_errors.c lib/libfrr.c lib/linklist.c lib/log.c \ lib/log_vty.c lib/md5.c lib/memory.c lib/memory_vty.c \ lib/mlag.c lib/module.c lib/mpls.c lib/network.c lib/nexthop.c \ lib/netns_linux.c lib/netns_other.c lib/nexthop_group.c \ lib/northbound.c lib/northbound_cli.c lib/northbound_db.c \ lib/ntop.c lib/openbsd-tree.c lib/pid_output.c lib/plist.c \ lib/prefix.c lib/privs.c lib/ptm_lib.c lib/pullwr.c lib/qobj.c \ lib/ringbuf.c lib/routemap.c lib/sbuf.c lib/seqlock.c \ lib/sha256.c lib/sigevent.c lib/skiplist.c lib/sockopt.c \ lib/sockunion.c lib/spf_backoff.c lib/srcdest_table.c \ lib/stream.c lib/strlcat.c lib/strlcpy.c lib/systemd.c \ lib/table.c lib/termtable.c lib/thread.c lib/typerb.c \ lib/typesafe.c lib/vector.c lib/vrf.c lib/vty.c lib/wheel.c \ lib/workqueue.c lib/yang.c lib/yang_translator.c \ lib/yang_wrappers.c lib/zclient.c lib/printf/printf-pos.c \ lib/printf/vfprintf.c lib/printf/glue.c lib/db.c @SQLITE3_TRUE@am__objects_4 = lib/db.lo am_lib_libfrr_la_OBJECTS = lib/agg_table.lo lib/atomlist.lo lib/bfd.lo \ lib/buffer.lo lib/checksum.lo lib/command.lo \ lib/command_graph.lo lib/command_lex.lo lib/command_match.lo \ lib/command_parse.lo lib/csv.lo lib/debug.lo lib/distribute.lo \ lib/ferr.lo lib/filter.lo lib/frrcu.lo lib/frrlua.lo \ lib/frr_pthread.lo lib/frrstr.lo lib/getopt.lo lib/getopt1.lo \ lib/grammar_sandbox.lo lib/graph.lo lib/hash.lo lib/hook.lo \ lib/id_alloc.lo lib/if.lo lib/if_rmap.lo lib/imsg-buffer.lo \ lib/imsg.lo lib/jhash.lo lib/json.lo lib/keychain.lo \ lib/lib_errors.lo lib/libfrr.lo lib/linklist.lo lib/log.lo \ lib/log_vty.lo lib/md5.lo lib/memory.lo lib/memory_vty.lo \ lib/mlag.lo lib/module.lo lib/mpls.lo lib/network.lo \ lib/nexthop.lo lib/netns_linux.lo lib/netns_other.lo \ lib/nexthop_group.lo lib/northbound.lo lib/northbound_cli.lo \ lib/northbound_db.lo lib/ntop.lo lib/openbsd-tree.lo \ lib/pid_output.lo lib/plist.lo lib/prefix.lo lib/privs.lo \ lib/ptm_lib.lo lib/pullwr.lo lib/qobj.lo lib/ringbuf.lo \ lib/routemap.lo lib/sbuf.lo lib/seqlock.lo lib/sha256.lo \ lib/sigevent.lo lib/skiplist.lo lib/sockopt.lo \ lib/sockunion.lo lib/spf_backoff.lo lib/srcdest_table.lo \ lib/stream.lo lib/strlcat.lo lib/strlcpy.lo lib/systemd.lo \ lib/table.lo lib/termtable.lo lib/thread.lo lib/typerb.lo \ lib/typesafe.lo lib/vector.lo lib/vrf.lo lib/vty.lo \ lib/wheel.lo lib/workqueue.lo lib/yang.lo \ lib/yang_translator.lo lib/yang_wrappers.lo lib/zclient.lo \ lib/printf/printf-pos.lo lib/printf/vfprintf.lo \ lib/printf/glue.lo $(am__objects_4) nodist_lib_libfrr_la_OBJECTS = yang/frr-interface.yang.lo \ yang/frr-route-types.yang.lo \ yang/frr-module-translator.yang.lo lib_libfrr_la_OBJECTS = $(am_lib_libfrr_la_OBJECTS) \ $(nodist_lib_libfrr_la_OBJECTS) lib_libfrr_la_LINK = $(LIBTOOL) $(AM_V_lt) --tag=CC $(AM_LIBTOOLFLAGS) \ $(LIBTOOLFLAGS) --mode=link $(CCLD) $(AM_CFLAGS) $(CFLAGS) \ $(lib_libfrr_la_LDFLAGS) $(LDFLAGS) -o $@ lib_libfrrcares_la_DEPENDENCIES = $(am__DEPENDENCIES_1) am_lib_libfrrcares_la_OBJECTS = lib/libfrrcares_la-resolver.lo lib_libfrrcares_la_OBJECTS = $(am_lib_libfrrcares_la_OBJECTS) lib_libfrrcares_la_LINK = $(LIBTOOL) $(AM_V_lt) --tag=CC \ $(AM_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=link $(CCLD) \ $(lib_libfrrcares_la_CFLAGS) $(CFLAGS) \ $(lib_libfrrcares_la_LDFLAGS) $(LDFLAGS) -o $@ @CARES_TRUE@am_lib_libfrrcares_la_rpath = -rpath $(libdir) lib_libfrrsnmp_la_DEPENDENCIES = $(am__DEPENDENCIES_1) am_lib_libfrrsnmp_la_OBJECTS = lib/libfrrsnmp_la-agentx.lo \ lib/libfrrsnmp_la-snmp.lo lib_libfrrsnmp_la_OBJECTS = $(am_lib_libfrrsnmp_la_OBJECTS) lib_libfrrsnmp_la_LINK = $(LIBTOOL) $(AM_V_lt) --tag=CC \ $(AM_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=link $(CCLD) \ $(lib_libfrrsnmp_la_CFLAGS) $(CFLAGS) \ $(lib_libfrrsnmp_la_LDFLAGS) $(LDFLAGS) -o $@ @SNMP_TRUE@am_lib_libfrrsnmp_la_rpath = -rpath $(libdir) lib_libfrrzmq_la_DEPENDENCIES = $(am__DEPENDENCIES_1) am_lib_libfrrzmq_la_OBJECTS = lib/libfrrzmq_la-frr_zmq.lo lib_libfrrzmq_la_OBJECTS = $(am_lib_libfrrzmq_la_OBJECTS) lib_libfrrzmq_la_LINK = $(LIBTOOL) $(AM_V_lt) --tag=CC \ $(AM_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=link $(CCLD) \ $(lib_libfrrzmq_la_CFLAGS) $(CFLAGS) \ $(lib_libfrrzmq_la_LDFLAGS) $(LDFLAGS) -o $@ @ZEROMQ_TRUE@am_lib_libfrrzmq_la_rpath = -rpath $(libdir) lib_sysrepo_la_DEPENDENCIES = lib/libfrr.la $(am__DEPENDENCIES_1) am_lib_sysrepo_la_OBJECTS = lib/sysrepo_la-northbound_sysrepo.lo lib_sysrepo_la_OBJECTS = $(am_lib_sysrepo_la_OBJECTS) lib_sysrepo_la_LINK = $(LIBTOOL) $(AM_V_lt) --tag=CC \ $(AM_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=link $(CCLD) \ $(lib_sysrepo_la_CFLAGS) $(CFLAGS) $(lib_sysrepo_la_LDFLAGS) \ $(LDFLAGS) -o $@ @SYSREPO_TRUE@am_lib_sysrepo_la_rpath = -rpath $(moduledir) ospf6d_ospf6d_snmp_la_DEPENDENCIES = lib/libfrrsnmp.la am_ospf6d_ospf6d_snmp_la_OBJECTS = \ ospf6d/ospf6d_snmp_la-ospf6_snmp.lo ospf6d_ospf6d_snmp_la_OBJECTS = $(am_ospf6d_ospf6d_snmp_la_OBJECTS) ospf6d_ospf6d_snmp_la_LINK = $(LIBTOOL) $(AM_V_lt) --tag=CC \ $(AM_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=link $(CCLD) \ $(ospf6d_ospf6d_snmp_la_CFLAGS) $(CFLAGS) \ $(ospf6d_ospf6d_snmp_la_LDFLAGS) $(LDFLAGS) -o $@ @OSPF6D_TRUE@@SNMP_TRUE@am_ospf6d_ospf6d_snmp_la_rpath = -rpath \ @OSPF6D_TRUE@@SNMP_TRUE@ $(moduledir) ospfclient_libfrrospfapiclient_la_DEPENDENCIES = lib/libfrr.la am_ospfclient_libfrrospfapiclient_la_OBJECTS = \ ospfclient/ospf_apiclient.lo ospfclient_libfrrospfapiclient_la_OBJECTS = \ $(am_ospfclient_libfrrospfapiclient_la_OBJECTS) ospfclient_libfrrospfapiclient_la_LINK = $(LIBTOOL) $(AM_V_lt) \ --tag=CC $(AM_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=link \ $(CCLD) $(AM_CFLAGS) $(CFLAGS) \ $(ospfclient_libfrrospfapiclient_la_LDFLAGS) $(LDFLAGS) -o $@ @OSPFCLIENT_TRUE@am_ospfclient_libfrrospfapiclient_la_rpath = -rpath \ @OSPFCLIENT_TRUE@ $(libdir) ospfd_ospfd_snmp_la_DEPENDENCIES = lib/libfrrsnmp.la am_ospfd_ospfd_snmp_la_OBJECTS = ospfd/ospfd_snmp_la-ospf_snmp.lo ospfd_ospfd_snmp_la_OBJECTS = $(am_ospfd_ospfd_snmp_la_OBJECTS) ospfd_ospfd_snmp_la_LINK = $(LIBTOOL) $(AM_V_lt) --tag=CC \ $(AM_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=link $(CCLD) \ $(ospfd_ospfd_snmp_la_CFLAGS) $(CFLAGS) \ $(ospfd_ospfd_snmp_la_LDFLAGS) $(LDFLAGS) -o $@ @OSPFD_TRUE@@SNMP_TRUE@am_ospfd_ospfd_snmp_la_rpath = -rpath \ @OSPFD_TRUE@@SNMP_TRUE@ $(moduledir) qpb_libfrr_pb_la_DEPENDENCIES = $(am__DEPENDENCIES_1) am_qpb_libfrr_pb_la_OBJECTS = qpb/libfrr_pb_la-qpb.lo \ qpb/libfrr_pb_la-qpb_allocator.lo nodist_qpb_libfrr_pb_la_OBJECTS = qpb/libfrr_pb_la-qpb.pb-c.lo qpb_libfrr_pb_la_OBJECTS = $(am_qpb_libfrr_pb_la_OBJECTS) \ $(nodist_qpb_libfrr_pb_la_OBJECTS) qpb_libfrr_pb_la_LINK = $(LIBTOOL) $(AM_V_lt) --tag=CC \ $(AM_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=link $(CCLD) \ $(AM_CFLAGS) $(CFLAGS) $(qpb_libfrr_pb_la_LDFLAGS) $(LDFLAGS) \ -o $@ @HAVE_PROTOBUF_TRUE@am_qpb_libfrr_pb_la_rpath = -rpath $(libdir) ripd_ripd_snmp_la_DEPENDENCIES = lib/libfrrsnmp.la am_ripd_ripd_snmp_la_OBJECTS = ripd/ripd_snmp_la-rip_snmp.lo ripd_ripd_snmp_la_OBJECTS = $(am_ripd_ripd_snmp_la_OBJECTS) ripd_ripd_snmp_la_LINK = $(LIBTOOL) $(AM_V_lt) --tag=CC \ $(AM_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=link $(CCLD) \ $(ripd_ripd_snmp_la_CFLAGS) $(CFLAGS) \ $(ripd_ripd_snmp_la_LDFLAGS) $(LDFLAGS) -o $@ @RIPD_TRUE@@SNMP_TRUE@am_ripd_ripd_snmp_la_rpath = -rpath $(moduledir) @HAVE_PROTOBUF_TRUE@am__DEPENDENCIES_3 = fpm/libfrrfpm_pb.la \ @HAVE_PROTOBUF_TRUE@ qpb/libfrr_pb.la $(am__DEPENDENCIES_1) zebra_zebra_fpm_la_DEPENDENCIES = $(am__DEPENDENCIES_3) am__zebra_zebra_fpm_la_SOURCES_DIST = zebra/zebra_fpm.c \ zebra/zebra_fpm_netlink.c zebra/zebra_fpm_protobuf.c \ zebra/zebra_fpm_dt.c @HAVE_PROTOBUF_TRUE@am__objects_5 = zebra/zebra_fpm_protobuf.lo @DEV_BUILD_TRUE@@HAVE_PROTOBUF_TRUE@am__objects_6 = \ @DEV_BUILD_TRUE@@HAVE_PROTOBUF_TRUE@ zebra/zebra_fpm_dt.lo am_zebra_zebra_fpm_la_OBJECTS = zebra/zebra_fpm.lo \ zebra/zebra_fpm_netlink.lo $(am__objects_5) $(am__objects_6) zebra_zebra_fpm_la_OBJECTS = $(am_zebra_zebra_fpm_la_OBJECTS) zebra_zebra_fpm_la_LINK = $(LIBTOOL) $(AM_V_lt) --tag=CC \ $(AM_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=link $(CCLD) \ $(AM_CFLAGS) $(CFLAGS) $(zebra_zebra_fpm_la_LDFLAGS) \ $(LDFLAGS) -o $@ @FPM_TRUE@@ZEBRA_TRUE@am_zebra_zebra_fpm_la_rpath = -rpath \ @FPM_TRUE@@ZEBRA_TRUE@ $(moduledir) zebra_zebra_irdp_la_LIBADD = am_zebra_zebra_irdp_la_OBJECTS = zebra/irdp_interface.lo \ zebra/irdp_main.lo zebra/irdp_packet.lo zebra_zebra_irdp_la_OBJECTS = $(am_zebra_zebra_irdp_la_OBJECTS) zebra_zebra_irdp_la_LINK = $(LIBTOOL) $(AM_V_lt) --tag=CC \ $(AM_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=link $(CCLD) \ $(AM_CFLAGS) $(CFLAGS) $(zebra_zebra_irdp_la_LDFLAGS) \ $(LDFLAGS) -o $@ @IRDP_TRUE@@ZEBRA_TRUE@am_zebra_zebra_irdp_la_rpath = -rpath \ @IRDP_TRUE@@ZEBRA_TRUE@ $(moduledir) zebra_zebra_snmp_la_DEPENDENCIES = lib/libfrrsnmp.la am_zebra_zebra_snmp_la_OBJECTS = zebra/zebra_snmp_la-zebra_snmp.lo zebra_zebra_snmp_la_OBJECTS = $(am_zebra_zebra_snmp_la_OBJECTS) zebra_zebra_snmp_la_LINK = $(LIBTOOL) $(AM_V_lt) --tag=CC \ $(AM_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=link $(CCLD) \ $(zebra_zebra_snmp_la_CFLAGS) $(CFLAGS) \ $(zebra_zebra_snmp_la_LDFLAGS) $(LDFLAGS) -o $@ @SNMP_TRUE@@ZEBRA_TRUE@am_zebra_zebra_snmp_la_rpath = -rpath \ @SNMP_TRUE@@ZEBRA_TRUE@ $(moduledir) am_babeld_babeld_OBJECTS = babeld/babel_main.$(OBJEXT) babeld_babeld_OBJECTS = $(am_babeld_babeld_OBJECTS) babeld_babeld_DEPENDENCIES = babeld/libbabel.a lib/libfrr.la \ $(am__DEPENDENCIES_1) am_bfdd_bfdd_OBJECTS = bfdd/bfdd.$(OBJEXT) nodist_bfdd_bfdd_OBJECTS = yang/frr-bfdd.yang.$(OBJEXT) bfdd_bfdd_OBJECTS = $(am_bfdd_bfdd_OBJECTS) \ $(nodist_bfdd_bfdd_OBJECTS) bfdd_bfdd_DEPENDENCIES = bfdd/libbfd.a lib/libfrr.la am__bgpd_bgp_btoa_SOURCES_DIST = bgpd/bgp_btoa.c \ bgpd/rfapi/rfapi_descriptor_rfp_utils.c @ENABLE_BGP_VNC_TRUE@am__objects_7 = bgpd/rfapi/bgp_btoa-rfapi_descriptor_rfp_utils.$(OBJEXT) am_bgpd_bgp_btoa_OBJECTS = bgpd/bgp_btoa-bgp_btoa.$(OBJEXT) \ $(am__objects_7) bgpd_bgp_btoa_OBJECTS = $(am_bgpd_bgp_btoa_OBJECTS) bgpd_bgp_btoa_DEPENDENCIES = bgpd/libbgp.a $(RFPLDADD) lib/libfrr.la \ $(am__DEPENDENCIES_1) $(am__DEPENDENCIES_1) bgpd_bgp_btoa_LINK = $(LIBTOOL) $(AM_V_lt) --tag=CC $(AM_LIBTOOLFLAGS) \ $(LIBTOOLFLAGS) --mode=link $(CCLD) $(bgpd_bgp_btoa_CFLAGS) \ $(CFLAGS) $(AM_LDFLAGS) $(LDFLAGS) -o $@ am__bgpd_bgpd_SOURCES_DIST = bgpd/bgp_main.c \ bgpd/rfapi/rfapi_descriptor_rfp_utils.c @ENABLE_BGP_VNC_TRUE@am__objects_8 = bgpd/rfapi/bgpd-rfapi_descriptor_rfp_utils.$(OBJEXT) am_bgpd_bgpd_OBJECTS = bgpd/bgpd-bgp_main.$(OBJEXT) $(am__objects_8) bgpd_bgpd_OBJECTS = $(am_bgpd_bgpd_OBJECTS) bgpd_bgpd_DEPENDENCIES = bgpd/libbgp.a $(RFPLDADD) lib/libfrr.la \ $(am__DEPENDENCIES_1) $(am__DEPENDENCIES_1) bgpd_bgpd_LINK = $(LIBTOOL) $(AM_V_lt) --tag=CC $(AM_LIBTOOLFLAGS) \ $(LIBTOOLFLAGS) --mode=link $(CCLD) $(bgpd_bgpd_CFLAGS) \ $(CFLAGS) $(AM_LDFLAGS) $(LDFLAGS) -o $@ am_bgpd_rfp_example_rfptest_rfptest_OBJECTS = \ bgpd/rfp-example/rfptest/rfptest-rfptest.$(OBJEXT) bgpd_rfp_example_rfptest_rfptest_OBJECTS = \ $(am_bgpd_rfp_example_rfptest_rfptest_OBJECTS) bgpd_rfp_example_rfptest_rfptest_DEPENDENCIES = lib/libfrr.la \ $(RFPLDADD) bgpd_rfp_example_rfptest_rfptest_LINK = $(LIBTOOL) $(AM_V_lt) --tag=CC \ $(AM_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=link $(CCLD) \ $(bgpd_rfp_example_rfptest_rfptest_CFLAGS) $(CFLAGS) \ $(AM_LDFLAGS) $(LDFLAGS) -o $@ am_eigrpd_eigrpd_OBJECTS = eigrpd/eigrp_main.$(OBJEXT) nodist_eigrpd_eigrpd_OBJECTS = yang/frr-eigrpd.yang.$(OBJEXT) eigrpd_eigrpd_OBJECTS = $(am_eigrpd_eigrpd_OBJECTS) \ $(nodist_eigrpd_eigrpd_OBJECTS) eigrpd_eigrpd_DEPENDENCIES = eigrpd/libeigrp.a lib/libfrr.la \ $(am__DEPENDENCIES_1) am__objects_9 = isisd/fabricd-isis_bpf.$(OBJEXT) \ isisd/fabricd-isis_dlpi.$(OBJEXT) \ isisd/fabricd-isis_main.$(OBJEXT) \ isisd/fabricd-isis_pfpacket.$(OBJEXT) am_isisd_fabricd_OBJECTS = $(am__objects_9) isisd_fabricd_OBJECTS = $(am_isisd_fabricd_OBJECTS) am__DEPENDENCIES_4 = lib/libfrr.la $(am__DEPENDENCIES_1) isisd_fabricd_DEPENDENCIES = isisd/libfabric.a $(am__DEPENDENCIES_4) am__objects_10 = isisd/isis_bpf.$(OBJEXT) isisd/isis_dlpi.$(OBJEXT) \ isisd/isis_main.$(OBJEXT) isisd/isis_pfpacket.$(OBJEXT) am_isisd_isisd_OBJECTS = $(am__objects_10) nodist_isisd_isisd_OBJECTS = yang/frr-isisd.yang.$(OBJEXT) isisd_isisd_OBJECTS = $(am_isisd_isisd_OBJECTS) \ $(nodist_isisd_isisd_OBJECTS) isisd_isisd_DEPENDENCIES = isisd/libisis.a $(am__DEPENDENCIES_4) am_ldpd_ldpd_OBJECTS = ldpd/ldpd.$(OBJEXT) ldpd_ldpd_OBJECTS = $(am_ldpd_ldpd_OBJECTS) ldpd_ldpd_DEPENDENCIES = ldpd/libldp.a lib/libfrr.la \ $(am__DEPENDENCIES_1) am_lib_clippy_OBJECTS = lib/clippy-clippy.$(OBJEXT) \ lib/clippy-command_graph.$(OBJEXT) \ lib/clippy-command_lex.$(OBJEXT) \ lib/clippy-command_parse.$(OBJEXT) \ lib/clippy-command_py.$(OBJEXT) lib/clippy-defun_lex.$(OBJEXT) \ lib/clippy-graph.$(OBJEXT) lib/clippy-memory.$(OBJEXT) \ lib/clippy-vector.$(OBJEXT) lib_clippy_OBJECTS = $(am_lib_clippy_OBJECTS) lib_clippy_DEPENDENCIES = $(am__DEPENDENCIES_1) lib_clippy_LINK = $(LIBTOOL) $(AM_V_lt) --tag=CC $(AM_LIBTOOLFLAGS) \ $(LIBTOOLFLAGS) --mode=link $(CCLD) $(lib_clippy_CFLAGS) \ $(CFLAGS) $(lib_clippy_LDFLAGS) $(LDFLAGS) -o $@ am_lib_grammar_sandbox_OBJECTS = lib/grammar_sandbox_main.$(OBJEXT) lib_grammar_sandbox_OBJECTS = $(am_lib_grammar_sandbox_OBJECTS) lib_grammar_sandbox_DEPENDENCIES = lib/libfrr.la am_nhrpd_nhrpd_OBJECTS = nhrpd/linux.$(OBJEXT) \ nhrpd/netlink_arp.$(OBJEXT) nhrpd/netlink_gre.$(OBJEXT) \ nhrpd/nhrp_cache.$(OBJEXT) nhrpd/nhrp_errors.$(OBJEXT) \ nhrpd/nhrp_event.$(OBJEXT) nhrpd/nhrp_interface.$(OBJEXT) \ nhrpd/nhrp_main.$(OBJEXT) nhrpd/nhrp_nhs.$(OBJEXT) \ nhrpd/nhrp_packet.$(OBJEXT) nhrpd/nhrp_peer.$(OBJEXT) \ nhrpd/nhrp_route.$(OBJEXT) nhrpd/nhrp_shortcut.$(OBJEXT) \ nhrpd/nhrp_vc.$(OBJEXT) nhrpd/nhrp_vty.$(OBJEXT) \ nhrpd/reqid.$(OBJEXT) nhrpd/vici.$(OBJEXT) \ nhrpd/zbuf.$(OBJEXT) nhrpd/znl.$(OBJEXT) nhrpd_nhrpd_OBJECTS = $(am_nhrpd_nhrpd_OBJECTS) nhrpd_nhrpd_DEPENDENCIES = lib/libfrr.la lib/libfrrcares.la \ $(am__DEPENDENCIES_1) am_ospf6d_ospf6d_OBJECTS = ospf6d/ospf6_main.$(OBJEXT) ospf6d_ospf6d_OBJECTS = $(am_ospf6d_ospf6d_OBJECTS) ospf6d_ospf6d_DEPENDENCIES = ospf6d/libospf6.a lib/libfrr.la \ $(am__DEPENDENCIES_1) am_ospfclient_ospfclient_OBJECTS = ospfclient/ospfclient.$(OBJEXT) ospfclient_ospfclient_OBJECTS = $(am_ospfclient_ospfclient_OBJECTS) ospfclient_ospfclient_DEPENDENCIES = \ ospfclient/libfrrospfapiclient.la $(am__DEPENDENCIES_1) \ $(am__append_73) am_ospfd_ospfd_OBJECTS = ospfd/ospf_main.$(OBJEXT) ospfd_ospfd_OBJECTS = $(am_ospfd_ospfd_OBJECTS) ospfd_ospfd_DEPENDENCIES = ospfd/libfrrospf.a lib/libfrr.la \ $(am__DEPENDENCIES_1) $(am__DEPENDENCIES_1) am_pbrd_pbrd_OBJECTS = pbrd/pbr_main.$(OBJEXT) pbrd_pbrd_OBJECTS = $(am_pbrd_pbrd_OBJECTS) pbrd_pbrd_DEPENDENCIES = pbrd/libpbr.a lib/libfrr.la \ $(am__DEPENDENCIES_1) am_pimd_mtracebis_OBJECTS = pimd/mtracebis.$(OBJEXT) \ pimd/mtracebis_netlink.$(OBJEXT) \ pimd/mtracebis_routeget.$(OBJEXT) pimd_mtracebis_OBJECTS = $(am_pimd_mtracebis_OBJECTS) pimd_mtracebis_DEPENDENCIES = lib/libfrr.la am_pimd_pimd_OBJECTS = pimd/pim_main.$(OBJEXT) pimd_pimd_OBJECTS = $(am_pimd_pimd_OBJECTS) pimd_pimd_DEPENDENCIES = pimd/libpim.a lib/libfrr.la \ $(am__DEPENDENCIES_1) am_pimd_test_igmpv3_join_OBJECTS = pimd/test_igmpv3_join.$(OBJEXT) pimd_test_igmpv3_join_OBJECTS = $(am_pimd_test_igmpv3_join_OBJECTS) pimd_test_igmpv3_join_DEPENDENCIES = lib/libfrr.la am_ripd_ripd_OBJECTS = ripd/rip_main.$(OBJEXT) nodist_ripd_ripd_OBJECTS = yang/frr-ripd.yang.$(OBJEXT) ripd_ripd_OBJECTS = $(am_ripd_ripd_OBJECTS) \ $(nodist_ripd_ripd_OBJECTS) ripd_ripd_DEPENDENCIES = ripd/librip.a lib/libfrr.la \ $(am__DEPENDENCIES_1) am_ripngd_ripngd_OBJECTS = ripngd/ripng_main.$(OBJEXT) nodist_ripngd_ripngd_OBJECTS = yang/frr-ripngd.yang.$(OBJEXT) ripngd_ripngd_OBJECTS = $(am_ripngd_ripngd_OBJECTS) \ $(nodist_ripngd_ripngd_OBJECTS) ripngd_ripngd_DEPENDENCIES = ripngd/libripng.a lib/libfrr.la \ $(am__DEPENDENCIES_1) am_sharpd_sharpd_OBJECTS = sharpd/sharp_main.$(OBJEXT) sharpd_sharpd_OBJECTS = $(am_sharpd_sharpd_OBJECTS) sharpd_sharpd_DEPENDENCIES = sharpd/libsharp.a lib/libfrr.la \ $(am__DEPENDENCIES_1) am_staticd_staticd_OBJECTS = staticd/static_main.$(OBJEXT) staticd_staticd_OBJECTS = $(am_staticd_staticd_OBJECTS) staticd_staticd_DEPENDENCIES = staticd/libstatic.a lib/libfrr.la \ $(am__DEPENDENCIES_1) am_tests_bgpd_test_aspath_OBJECTS = \ tests/bgpd/test_aspath-test_aspath.$(OBJEXT) tests_bgpd_test_aspath_OBJECTS = $(am_tests_bgpd_test_aspath_OBJECTS) am__DEPENDENCIES_5 = bgpd/libbgp.a $(RFPLDADD) $(am__DEPENDENCIES_4) tests_bgpd_test_aspath_DEPENDENCIES = $(am__DEPENDENCIES_5) tests_bgpd_test_aspath_LINK = $(LIBTOOL) $(AM_V_lt) --tag=CC \ $(AM_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=link $(CCLD) \ $(tests_bgpd_test_aspath_CFLAGS) $(CFLAGS) $(AM_LDFLAGS) \ $(LDFLAGS) -o $@ am_tests_bgpd_test_bgp_table_OBJECTS = \ tests/bgpd/test_bgp_table-test_bgp_table.$(OBJEXT) tests_bgpd_test_bgp_table_OBJECTS = \ $(am_tests_bgpd_test_bgp_table_OBJECTS) tests_bgpd_test_bgp_table_DEPENDENCIES = $(am__DEPENDENCIES_5) tests_bgpd_test_bgp_table_LINK = $(LIBTOOL) $(AM_V_lt) --tag=CC \ $(AM_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=link $(CCLD) \ $(tests_bgpd_test_bgp_table_CFLAGS) $(CFLAGS) $(AM_LDFLAGS) \ $(LDFLAGS) -o $@ am_tests_bgpd_test_capability_OBJECTS = \ tests/bgpd/test_capability-test_capability.$(OBJEXT) tests_bgpd_test_capability_OBJECTS = \ $(am_tests_bgpd_test_capability_OBJECTS) tests_bgpd_test_capability_DEPENDENCIES = $(am__DEPENDENCIES_5) tests_bgpd_test_capability_LINK = $(LIBTOOL) $(AM_V_lt) --tag=CC \ $(AM_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=link $(CCLD) \ $(tests_bgpd_test_capability_CFLAGS) $(CFLAGS) $(AM_LDFLAGS) \ $(LDFLAGS) -o $@ am_tests_bgpd_test_ecommunity_OBJECTS = \ tests/bgpd/test_ecommunity-test_ecommunity.$(OBJEXT) tests_bgpd_test_ecommunity_OBJECTS = \ $(am_tests_bgpd_test_ecommunity_OBJECTS) tests_bgpd_test_ecommunity_DEPENDENCIES = $(am__DEPENDENCIES_5) tests_bgpd_test_ecommunity_LINK = $(LIBTOOL) $(AM_V_lt) --tag=CC \ $(AM_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=link $(CCLD) \ $(tests_bgpd_test_ecommunity_CFLAGS) $(CFLAGS) $(AM_LDFLAGS) \ $(LDFLAGS) -o $@ am_tests_bgpd_test_mp_attr_OBJECTS = \ tests/bgpd/test_mp_attr-test_mp_attr.$(OBJEXT) tests_bgpd_test_mp_attr_OBJECTS = \ $(am_tests_bgpd_test_mp_attr_OBJECTS) tests_bgpd_test_mp_attr_DEPENDENCIES = $(am__DEPENDENCIES_5) tests_bgpd_test_mp_attr_LINK = $(LIBTOOL) $(AM_V_lt) --tag=CC \ $(AM_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=link $(CCLD) \ $(tests_bgpd_test_mp_attr_CFLAGS) $(CFLAGS) $(AM_LDFLAGS) \ $(LDFLAGS) -o $@ am_tests_bgpd_test_mpath_OBJECTS = \ tests/bgpd/test_mpath-test_mpath.$(OBJEXT) tests_bgpd_test_mpath_OBJECTS = $(am_tests_bgpd_test_mpath_OBJECTS) tests_bgpd_test_mpath_DEPENDENCIES = $(am__DEPENDENCIES_5) tests_bgpd_test_mpath_LINK = $(LIBTOOL) $(AM_V_lt) --tag=CC \ $(AM_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=link $(CCLD) \ $(tests_bgpd_test_mpath_CFLAGS) $(CFLAGS) $(AM_LDFLAGS) \ $(LDFLAGS) -o $@ am_tests_bgpd_test_packet_OBJECTS = \ tests/bgpd/test_packet-test_packet.$(OBJEXT) tests_bgpd_test_packet_OBJECTS = $(am_tests_bgpd_test_packet_OBJECTS) tests_bgpd_test_packet_DEPENDENCIES = $(am__DEPENDENCIES_5) tests_bgpd_test_packet_LINK = $(LIBTOOL) $(AM_V_lt) --tag=CC \ $(AM_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=link $(CCLD) \ $(tests_bgpd_test_packet_CFLAGS) $(CFLAGS) $(AM_LDFLAGS) \ $(LDFLAGS) -o $@ am_tests_bgpd_test_peer_attr_OBJECTS = \ tests/bgpd/test_peer_attr-test_peer_attr.$(OBJEXT) tests_bgpd_test_peer_attr_OBJECTS = \ $(am_tests_bgpd_test_peer_attr_OBJECTS) tests_bgpd_test_peer_attr_DEPENDENCIES = $(am__DEPENDENCIES_5) tests_bgpd_test_peer_attr_LINK = $(LIBTOOL) $(AM_V_lt) --tag=CC \ $(AM_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=link $(CCLD) \ $(tests_bgpd_test_peer_attr_CFLAGS) $(CFLAGS) $(AM_LDFLAGS) \ $(LDFLAGS) -o $@ am_tests_isisd_test_fuzz_isis_tlv_OBJECTS = \ tests/isisd/test_fuzz_isis_tlv-test_fuzz_isis_tlv.$(OBJEXT) nodist_tests_isisd_test_fuzz_isis_tlv_OBJECTS = tests_isisd_test_fuzz_isis_tlv_OBJECTS = \ $(am_tests_isisd_test_fuzz_isis_tlv_OBJECTS) \ $(nodist_tests_isisd_test_fuzz_isis_tlv_OBJECTS) am__DEPENDENCIES_6 = isisd/libisis.a $(am__DEPENDENCIES_4) tests_isisd_test_fuzz_isis_tlv_DEPENDENCIES = $(am__DEPENDENCIES_6) tests_isisd_test_fuzz_isis_tlv_LINK = $(LIBTOOL) $(AM_V_lt) --tag=CC \ $(AM_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=link $(CCLD) \ $(tests_isisd_test_fuzz_isis_tlv_CFLAGS) $(CFLAGS) \ $(AM_LDFLAGS) $(LDFLAGS) -o $@ am_tests_isisd_test_isis_lspdb_OBJECTS = \ tests/isisd/test_isis_lspdb-test_isis_lspdb.$(OBJEXT) tests_isisd_test_isis_lspdb_OBJECTS = \ $(am_tests_isisd_test_isis_lspdb_OBJECTS) tests_isisd_test_isis_lspdb_DEPENDENCIES = $(am__DEPENDENCIES_6) tests_isisd_test_isis_lspdb_LINK = $(LIBTOOL) $(AM_V_lt) --tag=CC \ $(AM_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=link $(CCLD) \ $(tests_isisd_test_isis_lspdb_CFLAGS) $(CFLAGS) $(AM_LDFLAGS) \ $(LDFLAGS) -o $@ am_tests_isisd_test_isis_vertex_queue_OBJECTS = tests/isisd/test_isis_vertex_queue-test_isis_vertex_queue.$(OBJEXT) tests_isisd_test_isis_vertex_queue_OBJECTS = \ $(am_tests_isisd_test_isis_vertex_queue_OBJECTS) tests_isisd_test_isis_vertex_queue_DEPENDENCIES = \ $(am__DEPENDENCIES_6) tests_isisd_test_isis_vertex_queue_LINK = $(LIBTOOL) $(AM_V_lt) \ --tag=CC $(AM_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=link \ $(CCLD) $(tests_isisd_test_isis_vertex_queue_CFLAGS) $(CFLAGS) \ $(AM_LDFLAGS) $(LDFLAGS) -o $@ am_tests_lib_cli_test_cli_OBJECTS = \ tests/lib/cli/test_cli-test_cli.$(OBJEXT) \ tests/lib/cli/test_cli-common_cli.$(OBJEXT) tests_lib_cli_test_cli_OBJECTS = $(am_tests_lib_cli_test_cli_OBJECTS) tests_lib_cli_test_cli_DEPENDENCIES = $(am__DEPENDENCIES_4) tests_lib_cli_test_cli_LINK = $(LIBTOOL) $(AM_V_lt) --tag=CC \ $(AM_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=link $(CCLD) \ $(tests_lib_cli_test_cli_CFLAGS) $(CFLAGS) $(AM_LDFLAGS) \ $(LDFLAGS) -o $@ am_tests_lib_cli_test_commands_OBJECTS = \ tests/lib/cli/test_commands-test_commands.$(OBJEXT) \ tests/helpers/c/lib_cli_test_commands-prng.$(OBJEXT) nodist_tests_lib_cli_test_commands_OBJECTS = \ tests/lib/cli/test_commands-test_commands_defun.$(OBJEXT) tests_lib_cli_test_commands_OBJECTS = \ $(am_tests_lib_cli_test_commands_OBJECTS) \ $(nodist_tests_lib_cli_test_commands_OBJECTS) tests_lib_cli_test_commands_DEPENDENCIES = $(am__DEPENDENCIES_4) tests_lib_cli_test_commands_LINK = $(LIBTOOL) $(AM_V_lt) --tag=CC \ $(AM_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=link $(CCLD) \ $(tests_lib_cli_test_commands_CFLAGS) $(CFLAGS) $(AM_LDFLAGS) \ $(LDFLAGS) -o $@ am_tests_lib_cxxcompat_OBJECTS = \ tests/lib/cxxcompat-cxxcompat.$(OBJEXT) tests_lib_cxxcompat_OBJECTS = $(am_tests_lib_cxxcompat_OBJECTS) tests_lib_cxxcompat_DEPENDENCIES = $(am__DEPENDENCIES_4) tests_lib_cxxcompat_LINK = $(LIBTOOL) $(AM_V_lt) --tag=CC \ $(AM_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=link $(CCLD) \ $(tests_lib_cxxcompat_CFLAGS) $(CFLAGS) $(AM_LDFLAGS) \ $(LDFLAGS) -o $@ am_tests_lib_northbound_test_oper_data_OBJECTS = \ tests/lib/northbound/test_oper_data-test_oper_data.$(OBJEXT) nodist_tests_lib_northbound_test_oper_data_OBJECTS = yang/tests_lib_northbound_test_oper_data-frr-test-module.yang.$(OBJEXT) tests_lib_northbound_test_oper_data_OBJECTS = \ $(am_tests_lib_northbound_test_oper_data_OBJECTS) \ $(nodist_tests_lib_northbound_test_oper_data_OBJECTS) tests_lib_northbound_test_oper_data_DEPENDENCIES = \ $(am__DEPENDENCIES_4) tests_lib_northbound_test_oper_data_LINK = $(LIBTOOL) $(AM_V_lt) \ --tag=CC $(AM_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=link \ $(CCLD) $(tests_lib_northbound_test_oper_data_CFLAGS) \ $(CFLAGS) $(AM_LDFLAGS) $(LDFLAGS) -o $@ am_tests_lib_test_atomlist_OBJECTS = \ tests/lib/test_atomlist-test_atomlist.$(OBJEXT) tests_lib_test_atomlist_OBJECTS = \ $(am_tests_lib_test_atomlist_OBJECTS) tests_lib_test_atomlist_DEPENDENCIES = $(am__DEPENDENCIES_4) tests_lib_test_atomlist_LINK = $(LIBTOOL) $(AM_V_lt) --tag=CC \ $(AM_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=link $(CCLD) \ $(tests_lib_test_atomlist_CFLAGS) $(CFLAGS) $(AM_LDFLAGS) \ $(LDFLAGS) -o $@ am_tests_lib_test_buffer_OBJECTS = \ tests/lib/test_buffer-test_buffer.$(OBJEXT) tests_lib_test_buffer_OBJECTS = $(am_tests_lib_test_buffer_OBJECTS) tests_lib_test_buffer_DEPENDENCIES = $(am__DEPENDENCIES_4) tests_lib_test_buffer_LINK = $(LIBTOOL) $(AM_V_lt) --tag=CC \ $(AM_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=link $(CCLD) \ $(tests_lib_test_buffer_CFLAGS) $(CFLAGS) $(AM_LDFLAGS) \ $(LDFLAGS) -o $@ am_tests_lib_test_checksum_OBJECTS = \ tests/lib/test_checksum-test_checksum.$(OBJEXT) tests_lib_test_checksum_OBJECTS = \ $(am_tests_lib_test_checksum_OBJECTS) tests_lib_test_checksum_DEPENDENCIES = $(am__DEPENDENCIES_4) tests_lib_test_checksum_LINK = $(LIBTOOL) $(AM_V_lt) --tag=CC \ $(AM_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=link $(CCLD) \ $(tests_lib_test_checksum_CFLAGS) $(CFLAGS) $(AM_LDFLAGS) \ $(LDFLAGS) -o $@ am_tests_lib_test_graph_OBJECTS = \ tests/lib/test_graph-test_graph.$(OBJEXT) tests_lib_test_graph_OBJECTS = $(am_tests_lib_test_graph_OBJECTS) tests_lib_test_graph_DEPENDENCIES = $(am__DEPENDENCIES_4) tests_lib_test_graph_LINK = $(LIBTOOL) $(AM_V_lt) --tag=CC \ $(AM_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=link $(CCLD) \ $(tests_lib_test_graph_CFLAGS) $(CFLAGS) $(AM_LDFLAGS) \ $(LDFLAGS) -o $@ am_tests_lib_test_heavy_OBJECTS = \ tests/lib/test_heavy-test_heavy.$(OBJEXT) \ tests/helpers/c/lib_test_heavy-main.$(OBJEXT) tests_lib_test_heavy_OBJECTS = $(am_tests_lib_test_heavy_OBJECTS) tests_lib_test_heavy_DEPENDENCIES = $(am__DEPENDENCIES_4) tests_lib_test_heavy_LINK = $(LIBTOOL) $(AM_V_lt) --tag=CC \ $(AM_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=link $(CCLD) \ $(tests_lib_test_heavy_CFLAGS) $(CFLAGS) $(AM_LDFLAGS) \ $(LDFLAGS) -o $@ am_tests_lib_test_heavy_thread_OBJECTS = \ tests/lib/test_heavy_thread-test_heavy_thread.$(OBJEXT) \ tests/helpers/c/lib_test_heavy_thread-main.$(OBJEXT) tests_lib_test_heavy_thread_OBJECTS = \ $(am_tests_lib_test_heavy_thread_OBJECTS) tests_lib_test_heavy_thread_DEPENDENCIES = $(am__DEPENDENCIES_4) tests_lib_test_heavy_thread_LINK = $(LIBTOOL) $(AM_V_lt) --tag=CC \ $(AM_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=link $(CCLD) \ $(tests_lib_test_heavy_thread_CFLAGS) $(CFLAGS) $(AM_LDFLAGS) \ $(LDFLAGS) -o $@ am_tests_lib_test_heavy_wq_OBJECTS = \ tests/lib/test_heavy_wq-test_heavy_wq.$(OBJEXT) \ tests/helpers/c/lib_test_heavy_wq-main.$(OBJEXT) tests_lib_test_heavy_wq_OBJECTS = \ $(am_tests_lib_test_heavy_wq_OBJECTS) tests_lib_test_heavy_wq_DEPENDENCIES = $(am__DEPENDENCIES_4) tests_lib_test_heavy_wq_LINK = $(LIBTOOL) $(AM_V_lt) --tag=CC \ $(AM_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=link $(CCLD) \ $(tests_lib_test_heavy_wq_CFLAGS) $(CFLAGS) $(AM_LDFLAGS) \ $(LDFLAGS) -o $@ am_tests_lib_test_idalloc_OBJECTS = \ tests/lib/test_idalloc-test_idalloc.$(OBJEXT) tests_lib_test_idalloc_OBJECTS = $(am_tests_lib_test_idalloc_OBJECTS) tests_lib_test_idalloc_DEPENDENCIES = $(am__DEPENDENCIES_4) tests_lib_test_idalloc_LINK = $(LIBTOOL) $(AM_V_lt) --tag=CC \ $(AM_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=link $(CCLD) \ $(tests_lib_test_idalloc_CFLAGS) $(CFLAGS) $(AM_LDFLAGS) \ $(LDFLAGS) -o $@ am_tests_lib_test_memory_OBJECTS = \ tests/lib/test_memory-test_memory.$(OBJEXT) tests_lib_test_memory_OBJECTS = $(am_tests_lib_test_memory_OBJECTS) tests_lib_test_memory_DEPENDENCIES = $(am__DEPENDENCIES_4) tests_lib_test_memory_LINK = $(LIBTOOL) $(AM_V_lt) --tag=CC \ $(AM_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=link $(CCLD) \ $(tests_lib_test_memory_CFLAGS) $(CFLAGS) $(AM_LDFLAGS) \ $(LDFLAGS) -o $@ am_tests_lib_test_nexthop_iter_OBJECTS = \ tests/lib/test_nexthop_iter-test_nexthop_iter.$(OBJEXT) \ tests/helpers/c/lib_test_nexthop_iter-prng.$(OBJEXT) tests_lib_test_nexthop_iter_OBJECTS = \ $(am_tests_lib_test_nexthop_iter_OBJECTS) tests_lib_test_nexthop_iter_DEPENDENCIES = $(am__DEPENDENCIES_4) tests_lib_test_nexthop_iter_LINK = $(LIBTOOL) $(AM_V_lt) --tag=CC \ $(AM_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=link $(CCLD) \ $(tests_lib_test_nexthop_iter_CFLAGS) $(CFLAGS) $(AM_LDFLAGS) \ $(LDFLAGS) -o $@ am_tests_lib_test_ntop_OBJECTS = \ tests/lib/test_ntop-test_ntop.$(OBJEXT) \ tests/helpers/c/lib_test_ntop-prng.$(OBJEXT) tests_lib_test_ntop_OBJECTS = $(am_tests_lib_test_ntop_OBJECTS) tests_lib_test_ntop_DEPENDENCIES = tests_lib_test_ntop_LINK = $(LIBTOOL) $(AM_V_lt) --tag=CC \ $(AM_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=link $(CCLD) \ $(tests_lib_test_ntop_CFLAGS) $(CFLAGS) $(AM_LDFLAGS) \ $(LDFLAGS) -o $@ am_tests_lib_test_prefix2str_OBJECTS = \ tests/lib/test_prefix2str-test_prefix2str.$(OBJEXT) \ tests/helpers/c/lib_test_prefix2str-prng.$(OBJEXT) tests_lib_test_prefix2str_OBJECTS = \ $(am_tests_lib_test_prefix2str_OBJECTS) tests_lib_test_prefix2str_DEPENDENCIES = $(am__DEPENDENCIES_4) tests_lib_test_prefix2str_LINK = $(LIBTOOL) $(AM_V_lt) --tag=CC \ $(AM_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=link $(CCLD) \ $(tests_lib_test_prefix2str_CFLAGS) $(CFLAGS) $(AM_LDFLAGS) \ $(LDFLAGS) -o $@ am_tests_lib_test_printfrr_OBJECTS = \ tests/lib/test_printfrr-test_printfrr.$(OBJEXT) tests_lib_test_printfrr_OBJECTS = \ $(am_tests_lib_test_printfrr_OBJECTS) tests_lib_test_printfrr_DEPENDENCIES = $(am__DEPENDENCIES_4) tests_lib_test_printfrr_LINK = $(LIBTOOL) $(AM_V_lt) --tag=CC \ $(AM_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=link $(CCLD) \ $(tests_lib_test_printfrr_CFLAGS) $(CFLAGS) $(AM_LDFLAGS) \ $(LDFLAGS) -o $@ am_tests_lib_test_privs_OBJECTS = \ tests/lib/test_privs-test_privs.$(OBJEXT) tests_lib_test_privs_OBJECTS = $(am_tests_lib_test_privs_OBJECTS) tests_lib_test_privs_DEPENDENCIES = $(am__DEPENDENCIES_4) tests_lib_test_privs_LINK = $(LIBTOOL) $(AM_V_lt) --tag=CC \ $(AM_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=link $(CCLD) \ $(tests_lib_test_privs_CFLAGS) $(CFLAGS) $(AM_LDFLAGS) \ $(LDFLAGS) -o $@ am_tests_lib_test_ringbuf_OBJECTS = \ tests/lib/test_ringbuf-test_ringbuf.$(OBJEXT) tests_lib_test_ringbuf_OBJECTS = $(am_tests_lib_test_ringbuf_OBJECTS) tests_lib_test_ringbuf_DEPENDENCIES = $(am__DEPENDENCIES_4) tests_lib_test_ringbuf_LINK = $(LIBTOOL) $(AM_V_lt) --tag=CC \ $(AM_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=link $(CCLD) \ $(tests_lib_test_ringbuf_CFLAGS) $(CFLAGS) $(AM_LDFLAGS) \ $(LDFLAGS) -o $@ am_tests_lib_test_segv_OBJECTS = \ tests/lib/test_segv-test_segv.$(OBJEXT) tests_lib_test_segv_OBJECTS = $(am_tests_lib_test_segv_OBJECTS) tests_lib_test_segv_DEPENDENCIES = $(am__DEPENDENCIES_4) tests_lib_test_segv_LINK = $(LIBTOOL) $(AM_V_lt) --tag=CC \ $(AM_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=link $(CCLD) \ $(tests_lib_test_segv_CFLAGS) $(CFLAGS) $(AM_LDFLAGS) \ $(LDFLAGS) -o $@ am_tests_lib_test_seqlock_OBJECTS = \ tests/lib/test_seqlock-test_seqlock.$(OBJEXT) tests_lib_test_seqlock_OBJECTS = $(am_tests_lib_test_seqlock_OBJECTS) tests_lib_test_seqlock_DEPENDENCIES = $(am__DEPENDENCIES_4) tests_lib_test_seqlock_LINK = $(LIBTOOL) $(AM_V_lt) --tag=CC \ $(AM_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=link $(CCLD) \ $(tests_lib_test_seqlock_CFLAGS) $(CFLAGS) $(AM_LDFLAGS) \ $(LDFLAGS) -o $@ am_tests_lib_test_sig_OBJECTS = tests/lib/test_sig-test_sig.$(OBJEXT) tests_lib_test_sig_OBJECTS = $(am_tests_lib_test_sig_OBJECTS) tests_lib_test_sig_DEPENDENCIES = $(am__DEPENDENCIES_4) tests_lib_test_sig_LINK = $(LIBTOOL) $(AM_V_lt) --tag=CC \ $(AM_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=link $(CCLD) \ $(tests_lib_test_sig_CFLAGS) $(CFLAGS) $(AM_LDFLAGS) \ $(LDFLAGS) -o $@ am_tests_lib_test_srcdest_table_OBJECTS = \ tests/lib/test_srcdest_table-test_srcdest_table.$(OBJEXT) \ tests/helpers/c/lib_test_srcdest_table-prng.$(OBJEXT) tests_lib_test_srcdest_table_OBJECTS = \ $(am_tests_lib_test_srcdest_table_OBJECTS) tests_lib_test_srcdest_table_DEPENDENCIES = $(am__DEPENDENCIES_4) tests_lib_test_srcdest_table_LINK = $(LIBTOOL) $(AM_V_lt) --tag=CC \ $(AM_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=link $(CCLD) \ $(tests_lib_test_srcdest_table_CFLAGS) $(CFLAGS) $(AM_LDFLAGS) \ $(LDFLAGS) -o $@ am_tests_lib_test_stream_OBJECTS = \ tests/lib/test_stream-test_stream.$(OBJEXT) tests_lib_test_stream_OBJECTS = $(am_tests_lib_test_stream_OBJECTS) tests_lib_test_stream_DEPENDENCIES = $(am__DEPENDENCIES_4) tests_lib_test_stream_LINK = $(LIBTOOL) $(AM_V_lt) --tag=CC \ $(AM_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=link $(CCLD) \ $(tests_lib_test_stream_CFLAGS) $(CFLAGS) $(AM_LDFLAGS) \ $(LDFLAGS) -o $@ am_tests_lib_test_table_OBJECTS = \ tests/lib/test_table-test_table.$(OBJEXT) tests_lib_test_table_OBJECTS = $(am_tests_lib_test_table_OBJECTS) tests_lib_test_table_DEPENDENCIES = $(am__DEPENDENCIES_4) tests_lib_test_table_LINK = $(LIBTOOL) $(AM_V_lt) --tag=CC \ $(AM_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=link $(CCLD) \ $(tests_lib_test_table_CFLAGS) $(CFLAGS) $(AM_LDFLAGS) \ $(LDFLAGS) -o $@ am_tests_lib_test_timer_correctness_OBJECTS = tests/lib/test_timer_correctness-test_timer_correctness.$(OBJEXT) \ tests/helpers/c/lib_test_timer_correctness-prng.$(OBJEXT) tests_lib_test_timer_correctness_OBJECTS = \ $(am_tests_lib_test_timer_correctness_OBJECTS) tests_lib_test_timer_correctness_DEPENDENCIES = $(am__DEPENDENCIES_4) tests_lib_test_timer_correctness_LINK = $(LIBTOOL) $(AM_V_lt) --tag=CC \ $(AM_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=link $(CCLD) \ $(tests_lib_test_timer_correctness_CFLAGS) $(CFLAGS) \ $(AM_LDFLAGS) $(LDFLAGS) -o $@ am_tests_lib_test_timer_performance_OBJECTS = tests/lib/test_timer_performance-test_timer_performance.$(OBJEXT) \ tests/helpers/c/lib_test_timer_performance-prng.$(OBJEXT) tests_lib_test_timer_performance_OBJECTS = \ $(am_tests_lib_test_timer_performance_OBJECTS) tests_lib_test_timer_performance_DEPENDENCIES = $(am__DEPENDENCIES_4) tests_lib_test_timer_performance_LINK = $(LIBTOOL) $(AM_V_lt) --tag=CC \ $(AM_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=link $(CCLD) \ $(tests_lib_test_timer_performance_CFLAGS) $(CFLAGS) \ $(AM_LDFLAGS) $(LDFLAGS) -o $@ am_tests_lib_test_ttable_OBJECTS = \ tests/lib/test_ttable-test_ttable.$(OBJEXT) tests_lib_test_ttable_OBJECTS = $(am_tests_lib_test_ttable_OBJECTS) tests_lib_test_ttable_DEPENDENCIES = $(am__DEPENDENCIES_4) tests_lib_test_ttable_LINK = $(LIBTOOL) $(AM_V_lt) --tag=CC \ $(AM_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=link $(CCLD) \ $(tests_lib_test_ttable_CFLAGS) $(CFLAGS) $(AM_LDFLAGS) \ $(LDFLAGS) -o $@ am_tests_lib_test_typelist_OBJECTS = \ tests/lib/test_typelist-test_typelist.$(OBJEXT) \ tests/helpers/c/lib_test_typelist-prng.$(OBJEXT) tests_lib_test_typelist_OBJECTS = \ $(am_tests_lib_test_typelist_OBJECTS) tests_lib_test_typelist_DEPENDENCIES = $(am__DEPENDENCIES_4) tests_lib_test_typelist_LINK = $(LIBTOOL) $(AM_V_lt) --tag=CC \ $(AM_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=link $(CCLD) \ $(tests_lib_test_typelist_CFLAGS) $(CFLAGS) $(AM_LDFLAGS) \ $(LDFLAGS) -o $@ am_tests_lib_test_zlog_OBJECTS = \ tests/lib/test_zlog-test_zlog.$(OBJEXT) tests_lib_test_zlog_OBJECTS = $(am_tests_lib_test_zlog_OBJECTS) tests_lib_test_zlog_DEPENDENCIES = $(am__DEPENDENCIES_4) tests_lib_test_zlog_LINK = $(LIBTOOL) $(AM_V_lt) --tag=CC \ $(AM_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=link $(CCLD) \ $(tests_lib_test_zlog_CFLAGS) $(CFLAGS) $(AM_LDFLAGS) \ $(LDFLAGS) -o $@ am_tests_lib_test_zmq_OBJECTS = tests/lib/test_zmq-test_zmq.$(OBJEXT) tests_lib_test_zmq_OBJECTS = $(am_tests_lib_test_zmq_OBJECTS) tests_lib_test_zmq_DEPENDENCIES = lib/libfrrzmq.la \ $(am__DEPENDENCIES_4) $(am__DEPENDENCIES_1) tests_lib_test_zmq_LINK = $(LIBTOOL) $(AM_V_lt) --tag=CC \ $(AM_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=link $(CCLD) \ $(tests_lib_test_zmq_CFLAGS) $(CFLAGS) $(AM_LDFLAGS) \ $(LDFLAGS) -o $@ am_tests_ospf6d_test_lsdb_OBJECTS = \ tests/ospf6d/test_lsdb-test_lsdb.$(OBJEXT) \ tests/lib/cli/ospf6d_test_lsdb-common_cli.$(OBJEXT) tests_ospf6d_test_lsdb_OBJECTS = $(am_tests_ospf6d_test_lsdb_OBJECTS) am__DEPENDENCIES_7 = ospf6d/libospf6.a $(am__DEPENDENCIES_4) tests_ospf6d_test_lsdb_DEPENDENCIES = $(am__DEPENDENCIES_7) tests_ospf6d_test_lsdb_LINK = $(LIBTOOL) $(AM_V_lt) --tag=CC \ $(AM_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=link $(CCLD) \ $(tests_ospf6d_test_lsdb_CFLAGS) $(CFLAGS) $(AM_LDFLAGS) \ $(LDFLAGS) -o $@ am_tools_gen_northbound_callbacks_OBJECTS = \ tools/gen_northbound_callbacks.$(OBJEXT) tools_gen_northbound_callbacks_OBJECTS = \ $(am_tools_gen_northbound_callbacks_OBJECTS) tools_gen_northbound_callbacks_DEPENDENCIES = lib/libfrr.la \ $(am__DEPENDENCIES_1) am_tools_gen_yang_deviations_OBJECTS = \ tools/gen_yang_deviations.$(OBJEXT) tools_gen_yang_deviations_OBJECTS = \ $(am_tools_gen_yang_deviations_OBJECTS) tools_gen_yang_deviations_DEPENDENCIES = lib/libfrr.la \ $(am__DEPENDENCIES_1) am_tools_permutations_OBJECTS = tools/permutations.$(OBJEXT) tools_permutations_OBJECTS = $(am_tools_permutations_OBJECTS) tools_permutations_DEPENDENCIES = lib/libfrr.la am_tools_ssd_OBJECTS = tools/start-stop-daemon.$(OBJEXT) tools_ssd_OBJECTS = $(am_tools_ssd_OBJECTS) tools_ssd_LDADD = $(LDADD) am_vrrpd_vrrpd_OBJECTS = vrrpd/vrrp_main.$(OBJEXT) vrrpd_vrrpd_OBJECTS = $(am_vrrpd_vrrpd_OBJECTS) vrrpd_vrrpd_DEPENDENCIES = vrrpd/libvrrp.a lib/libfrr.la am_vtysh_vtysh_OBJECTS = vtysh/vtysh_main.$(OBJEXT) \ vtysh/vtysh.$(OBJEXT) vtysh/vtysh_user.$(OBJEXT) \ vtysh/vtysh_config.$(OBJEXT) nodist_vtysh_vtysh_OBJECTS = vtysh/vtysh_cmd.$(OBJEXT) vtysh_vtysh_OBJECTS = $(am_vtysh_vtysh_OBJECTS) \ $(nodist_vtysh_vtysh_OBJECTS) vtysh_vtysh_DEPENDENCIES = lib/libfrr.la $(am__DEPENDENCIES_1) \ $(am__DEPENDENCIES_1) $(am__DEPENDENCIES_1) \ $(am__DEPENDENCIES_1) am_watchfrr_watchfrr_OBJECTS = watchfrr/watchfrr.$(OBJEXT) \ watchfrr/watchfrr_errors.$(OBJEXT) \ watchfrr/watchfrr_vty.$(OBJEXT) watchfrr_watchfrr_OBJECTS = $(am_watchfrr_watchfrr_OBJECTS) watchfrr_watchfrr_DEPENDENCIES = lib/libfrr.la $(am__DEPENDENCIES_1) am_zebra_zebra_OBJECTS = zebra/connected.$(OBJEXT) \ zebra/debug.$(OBJEXT) zebra/if_ioctl.$(OBJEXT) \ zebra/if_ioctl_solaris.$(OBJEXT) zebra/if_netlink.$(OBJEXT) \ zebra/if_sysctl.$(OBJEXT) zebra/interface.$(OBJEXT) \ zebra/ioctl.$(OBJEXT) zebra/ioctl_solaris.$(OBJEXT) \ zebra/ipforward_proc.$(OBJEXT) \ zebra/ipforward_solaris.$(OBJEXT) \ zebra/ipforward_sysctl.$(OBJEXT) \ zebra/kernel_netlink.$(OBJEXT) zebra/kernel_socket.$(OBJEXT) \ zebra/label_manager.$(OBJEXT) zebra/main.$(OBJEXT) \ zebra/redistribute.$(OBJEXT) zebra/router-id.$(OBJEXT) \ zebra/rt_netlink.$(OBJEXT) zebra/rt_socket.$(OBJEXT) \ zebra/rtadv.$(OBJEXT) zebra/rtread_getmsg.$(OBJEXT) \ zebra/rtread_netlink.$(OBJEXT) zebra/rtread_sysctl.$(OBJEXT) \ zebra/rule_netlink.$(OBJEXT) zebra/rule_socket.$(OBJEXT) \ zebra/zebra_mlag.$(OBJEXT) zebra/zebra_l2.$(OBJEXT) \ zebra/zebra_memory.$(OBJEXT) zebra/zebra_dplane.$(OBJEXT) \ zebra/zebra_mpls.$(OBJEXT) zebra/zebra_mpls_netlink.$(OBJEXT) \ zebra/zebra_mpls_openbsd.$(OBJEXT) \ zebra/zebra_mpls_null.$(OBJEXT) zebra/zebra_mpls_vty.$(OBJEXT) \ zebra/zebra_mroute.$(OBJEXT) zebra/zebra_nhg.$(OBJEXT) \ zebra/zebra_ns.$(OBJEXT) zebra/zebra_pbr.$(OBJEXT) \ zebra/zebra_ptm.$(OBJEXT) \ zebra/zebra_ptm_redistribute.$(OBJEXT) \ zebra/zebra_pw.$(OBJEXT) zebra/zebra_rib.$(OBJEXT) \ zebra/zebra_router.$(OBJEXT) zebra/zebra_rnh.$(OBJEXT) \ zebra/zebra_routemap.$(OBJEXT) zebra/zebra_vrf.$(OBJEXT) \ zebra/zebra_vty.$(OBJEXT) zebra/zebra_vxlan.$(OBJEXT) \ zebra/zserv.$(OBJEXT) zebra/zebra_netns_id.$(OBJEXT) \ zebra/zebra_netns_notify.$(OBJEXT) \ zebra/table_manager.$(OBJEXT) zebra/zapi_msg.$(OBJEXT) \ zebra/zebra_errors.$(OBJEXT) zebra_zebra_OBJECTS = $(am_zebra_zebra_OBJECTS) zebra_zebra_DEPENDENCIES = lib/libfrr.la $(am__DEPENDENCIES_1) SCRIPTS = $(rc_SCRIPTS) $(sbin_SCRIPTS) AM_V_P = $(am__v_P_@AM_V@) am__v_P_ = $(am__v_P_@AM_DEFAULT_V@) am__v_P_0 = false am__v_P_1 = : AM_V_GEN = $(am__v_GEN_@AM_V@) am__v_GEN_ = $(am__v_GEN_@AM_DEFAULT_V@) am__v_GEN_0 = @echo " GEN " $@; am__v_GEN_1 = AM_V_at = $(am__v_at_@AM_V@) am__v_at_ = $(am__v_at_@AM_DEFAULT_V@) am__v_at_0 = @ am__v_at_1 = DEFAULT_INCLUDES = -I.@am__isrc@ depcomp = $(SHELL) $(top_srcdir)/depcomp am__maybe_remake_depfiles = depfiles am__depfiles_remade = babeld/$(DEPDIR)/babel_errors.Po \ babeld/$(DEPDIR)/babel_filter.Po \ babeld/$(DEPDIR)/babel_interface.Po \ babeld/$(DEPDIR)/babel_main.Po babeld/$(DEPDIR)/babel_zebra.Po \ babeld/$(DEPDIR)/babeld.Po babeld/$(DEPDIR)/kernel.Po \ babeld/$(DEPDIR)/message.Po babeld/$(DEPDIR)/neighbour.Po \ babeld/$(DEPDIR)/net.Po babeld/$(DEPDIR)/resend.Po \ babeld/$(DEPDIR)/route.Po babeld/$(DEPDIR)/source.Po \ babeld/$(DEPDIR)/util.Po babeld/$(DEPDIR)/xroute.Po \ bfdd/$(DEPDIR)/bfd.Po bfdd/$(DEPDIR)/bfd_packet.Po \ bfdd/$(DEPDIR)/bfdd.Po bfdd/$(DEPDIR)/bfdd_cli.Po \ bfdd/$(DEPDIR)/bfdd_northbound.Po bfdd/$(DEPDIR)/bfdd_vty.Po \ bfdd/$(DEPDIR)/config.Po bfdd/$(DEPDIR)/control.Po \ bfdd/$(DEPDIR)/event.Po bfdd/$(DEPDIR)/log.Po \ bfdd/$(DEPDIR)/ptm_adapter.Po bgpd/$(DEPDIR)/bgp_addpath.Po \ bgpd/$(DEPDIR)/bgp_advertise.Po bgpd/$(DEPDIR)/bgp_aspath.Po \ bgpd/$(DEPDIR)/bgp_attr.Po bgpd/$(DEPDIR)/bgp_attr_evpn.Po \ bgpd/$(DEPDIR)/bgp_bfd.Po bgpd/$(DEPDIR)/bgp_bmp.Plo \ bgpd/$(DEPDIR)/bgp_btoa-bgp_btoa.Po \ bgpd/$(DEPDIR)/bgp_clist.Po bgpd/$(DEPDIR)/bgp_community.Po \ bgpd/$(DEPDIR)/bgp_damp.Po bgpd/$(DEPDIR)/bgp_debug.Po \ bgpd/$(DEPDIR)/bgp_dump.Po bgpd/$(DEPDIR)/bgp_ecommunity.Po \ bgpd/$(DEPDIR)/bgp_encap_tlv.Po bgpd/$(DEPDIR)/bgp_errors.Po \ bgpd/$(DEPDIR)/bgp_evpn.Po bgpd/$(DEPDIR)/bgp_evpn_vty.Po \ bgpd/$(DEPDIR)/bgp_filter.Po bgpd/$(DEPDIR)/bgp_flowspec.Po \ bgpd/$(DEPDIR)/bgp_flowspec_util.Po \ bgpd/$(DEPDIR)/bgp_flowspec_vty.Po bgpd/$(DEPDIR)/bgp_fsm.Po \ bgpd/$(DEPDIR)/bgp_io.Po bgpd/$(DEPDIR)/bgp_keepalives.Po \ bgpd/$(DEPDIR)/bgp_label.Po bgpd/$(DEPDIR)/bgp_labelpool.Po \ bgpd/$(DEPDIR)/bgp_lcommunity.Po bgpd/$(DEPDIR)/bgp_mac.Po \ bgpd/$(DEPDIR)/bgp_memory.Po bgpd/$(DEPDIR)/bgp_mpath.Po \ bgpd/$(DEPDIR)/bgp_mplsvpn.Po bgpd/$(DEPDIR)/bgp_network.Po \ bgpd/$(DEPDIR)/bgp_nexthop.Po bgpd/$(DEPDIR)/bgp_nht.Po \ bgpd/$(DEPDIR)/bgp_open.Po bgpd/$(DEPDIR)/bgp_packet.Po \ bgpd/$(DEPDIR)/bgp_pbr.Po bgpd/$(DEPDIR)/bgp_rd.Po \ bgpd/$(DEPDIR)/bgp_regex.Po bgpd/$(DEPDIR)/bgp_route.Po \ bgpd/$(DEPDIR)/bgp_routemap.Po bgpd/$(DEPDIR)/bgp_table.Po \ bgpd/$(DEPDIR)/bgp_updgrp.Po bgpd/$(DEPDIR)/bgp_updgrp_adv.Po \ bgpd/$(DEPDIR)/bgp_updgrp_packet.Po bgpd/$(DEPDIR)/bgp_vpn.Po \ bgpd/$(DEPDIR)/bgp_vty.Po bgpd/$(DEPDIR)/bgp_zebra.Po \ bgpd/$(DEPDIR)/bgpd-bgp_main.Po bgpd/$(DEPDIR)/bgpd.Po \ bgpd/$(DEPDIR)/bgpd_rpki_la-bgp_rpki.Plo \ bgpd/$(DEPDIR)/bgpd_snmp_la-bgp_snmp.Plo \ bgpd/rfapi/$(DEPDIR)/bgp_btoa-rfapi_descriptor_rfp_utils.Po \ bgpd/rfapi/$(DEPDIR)/bgp_rfapi_cfg.Po \ bgpd/rfapi/$(DEPDIR)/bgpd-rfapi_descriptor_rfp_utils.Po \ bgpd/rfapi/$(DEPDIR)/rfapi.Po bgpd/rfapi/$(DEPDIR)/rfapi_ap.Po \ bgpd/rfapi/$(DEPDIR)/rfapi_descriptor_rfp_utils.Po \ bgpd/rfapi/$(DEPDIR)/rfapi_encap_tlv.Po \ bgpd/rfapi/$(DEPDIR)/rfapi_import.Po \ bgpd/rfapi/$(DEPDIR)/rfapi_monitor.Po \ bgpd/rfapi/$(DEPDIR)/rfapi_nve_addr.Po \ bgpd/rfapi/$(DEPDIR)/rfapi_rib.Po \ bgpd/rfapi/$(DEPDIR)/rfapi_vty.Po \ bgpd/rfapi/$(DEPDIR)/vnc_debug.Po \ bgpd/rfapi/$(DEPDIR)/vnc_export_bgp.Po \ bgpd/rfapi/$(DEPDIR)/vnc_export_table.Po \ bgpd/rfapi/$(DEPDIR)/vnc_import_bgp.Po \ bgpd/rfapi/$(DEPDIR)/vnc_zebra.Po \ bgpd/rfp-example/librfp/$(DEPDIR)/rfp_example.Po \ bgpd/rfp-example/rfptest/$(DEPDIR)/rfptest-rfptest.Po \ eigrpd/$(DEPDIR)/eigrp_cli.Po eigrpd/$(DEPDIR)/eigrp_dump.Po \ eigrpd/$(DEPDIR)/eigrp_errors.Po \ eigrpd/$(DEPDIR)/eigrp_filter.Po eigrpd/$(DEPDIR)/eigrp_fsm.Po \ eigrpd/$(DEPDIR)/eigrp_hello.Po \ eigrpd/$(DEPDIR)/eigrp_interface.Po \ eigrpd/$(DEPDIR)/eigrp_main.Po \ eigrpd/$(DEPDIR)/eigrp_memory.Po \ eigrpd/$(DEPDIR)/eigrp_neighbor.Po \ eigrpd/$(DEPDIR)/eigrp_network.Po \ eigrpd/$(DEPDIR)/eigrp_northbound.Po \ eigrpd/$(DEPDIR)/eigrp_packet.Po \ eigrpd/$(DEPDIR)/eigrp_query.Po \ eigrpd/$(DEPDIR)/eigrp_reply.Po \ eigrpd/$(DEPDIR)/eigrp_siaquery.Po \ eigrpd/$(DEPDIR)/eigrp_siareply.Po \ eigrpd/$(DEPDIR)/eigrp_snmp.Po \ eigrpd/$(DEPDIR)/eigrp_topology.Po \ eigrpd/$(DEPDIR)/eigrp_update.Po eigrpd/$(DEPDIR)/eigrp_vrf.Po \ eigrpd/$(DEPDIR)/eigrp_vty.Po eigrpd/$(DEPDIR)/eigrp_zebra.Po \ eigrpd/$(DEPDIR)/eigrpd.Po \ fpm/$(DEPDIR)/libfrrfpm_pb_la-fpm.pb-c.Plo \ fpm/$(DEPDIR)/libfrrfpm_pb_la-fpm_pb.Plo \ grpc/$(DEPDIR)/libfrrgrpc_pb_la-frr-northbound.grpc.pb.Plo \ grpc/$(DEPDIR)/libfrrgrpc_pb_la-frr-northbound.pb.Plo \ isisd/$(DEPDIR)/fabricd-isis_bpf.Po \ isisd/$(DEPDIR)/fabricd-isis_dlpi.Po \ isisd/$(DEPDIR)/fabricd-isis_main.Po \ isisd/$(DEPDIR)/fabricd-isis_pfpacket.Po \ isisd/$(DEPDIR)/fabricd.Po isisd/$(DEPDIR)/isis_adjacency.Po \ isisd/$(DEPDIR)/isis_bfd.Po isisd/$(DEPDIR)/isis_bpf.Po \ isisd/$(DEPDIR)/isis_circuit.Po isisd/$(DEPDIR)/isis_cli.Po \ isisd/$(DEPDIR)/isis_csm.Po isisd/$(DEPDIR)/isis_dlpi.Po \ isisd/$(DEPDIR)/isis_dr.Po isisd/$(DEPDIR)/isis_dynhn.Po \ isisd/$(DEPDIR)/isis_errors.Po isisd/$(DEPDIR)/isis_events.Po \ isisd/$(DEPDIR)/isis_flags.Po isisd/$(DEPDIR)/isis_lsp.Po \ isisd/$(DEPDIR)/isis_main.Po isisd/$(DEPDIR)/isis_memory.Po \ isisd/$(DEPDIR)/isis_misc.Po isisd/$(DEPDIR)/isis_mt.Po \ isisd/$(DEPDIR)/isis_northbound.Po isisd/$(DEPDIR)/isis_pdu.Po \ isisd/$(DEPDIR)/isis_pdu_counter.Po \ isisd/$(DEPDIR)/isis_pfpacket.Po \ isisd/$(DEPDIR)/isis_redist.Po isisd/$(DEPDIR)/isis_route.Po \ isisd/$(DEPDIR)/isis_routemap.Po isisd/$(DEPDIR)/isis_spf.Po \ isisd/$(DEPDIR)/isis_te.Po isisd/$(DEPDIR)/isis_tlvs.Po \ isisd/$(DEPDIR)/isis_tx_queue.Po isisd/$(DEPDIR)/isis_zebra.Po \ isisd/$(DEPDIR)/isisd.Po isisd/$(DEPDIR)/iso_checksum.Po \ isisd/$(DEPDIR)/libfabric_a-fabricd.Po \ isisd/$(DEPDIR)/libfabric_a-isis_adjacency.Po \ isisd/$(DEPDIR)/libfabric_a-isis_bfd.Po \ isisd/$(DEPDIR)/libfabric_a-isis_circuit.Po \ isisd/$(DEPDIR)/libfabric_a-isis_csm.Po \ isisd/$(DEPDIR)/libfabric_a-isis_dr.Po \ isisd/$(DEPDIR)/libfabric_a-isis_dynhn.Po \ isisd/$(DEPDIR)/libfabric_a-isis_errors.Po \ isisd/$(DEPDIR)/libfabric_a-isis_events.Po \ isisd/$(DEPDIR)/libfabric_a-isis_flags.Po \ isisd/$(DEPDIR)/libfabric_a-isis_lsp.Po \ isisd/$(DEPDIR)/libfabric_a-isis_memory.Po \ isisd/$(DEPDIR)/libfabric_a-isis_misc.Po \ isisd/$(DEPDIR)/libfabric_a-isis_mt.Po \ isisd/$(DEPDIR)/libfabric_a-isis_pdu.Po \ isisd/$(DEPDIR)/libfabric_a-isis_pdu_counter.Po \ isisd/$(DEPDIR)/libfabric_a-isis_redist.Po \ isisd/$(DEPDIR)/libfabric_a-isis_route.Po \ isisd/$(DEPDIR)/libfabric_a-isis_routemap.Po \ isisd/$(DEPDIR)/libfabric_a-isis_spf.Po \ isisd/$(DEPDIR)/libfabric_a-isis_te.Po \ isisd/$(DEPDIR)/libfabric_a-isis_tlvs.Po \ isisd/$(DEPDIR)/libfabric_a-isis_tx_queue.Po \ isisd/$(DEPDIR)/libfabric_a-isis_vty_fabricd.Po \ isisd/$(DEPDIR)/libfabric_a-isis_zebra.Po \ isisd/$(DEPDIR)/libfabric_a-isisd.Po \ isisd/$(DEPDIR)/libfabric_a-iso_checksum.Po \ ldpd/$(DEPDIR)/accept.Po ldpd/$(DEPDIR)/address.Po \ ldpd/$(DEPDIR)/adjacency.Po ldpd/$(DEPDIR)/control.Po \ ldpd/$(DEPDIR)/hello.Po ldpd/$(DEPDIR)/init.Po \ ldpd/$(DEPDIR)/interface.Po ldpd/$(DEPDIR)/keepalive.Po \ ldpd/$(DEPDIR)/l2vpn.Po ldpd/$(DEPDIR)/labelmapping.Po \ ldpd/$(DEPDIR)/lde.Po ldpd/$(DEPDIR)/lde_lib.Po \ ldpd/$(DEPDIR)/ldp_debug.Po ldpd/$(DEPDIR)/ldp_vty_cmds.Po \ ldpd/$(DEPDIR)/ldp_vty_conf.Po ldpd/$(DEPDIR)/ldp_vty_exec.Po \ ldpd/$(DEPDIR)/ldp_zebra.Po ldpd/$(DEPDIR)/ldpd.Po \ ldpd/$(DEPDIR)/ldpe.Po ldpd/$(DEPDIR)/log.Po \ ldpd/$(DEPDIR)/logmsg.Po ldpd/$(DEPDIR)/neighbor.Po \ ldpd/$(DEPDIR)/notification.Po ldpd/$(DEPDIR)/packet.Po \ ldpd/$(DEPDIR)/pfkey.Po ldpd/$(DEPDIR)/socket.Po \ ldpd/$(DEPDIR)/util.Po lib/$(DEPDIR)/agg_table.Plo \ lib/$(DEPDIR)/atomlist.Plo lib/$(DEPDIR)/bfd.Plo \ lib/$(DEPDIR)/buffer.Plo lib/$(DEPDIR)/checksum.Plo \ lib/$(DEPDIR)/clippy-clippy.Po \ lib/$(DEPDIR)/clippy-command_graph.Po \ lib/$(DEPDIR)/clippy-command_lex.Po \ lib/$(DEPDIR)/clippy-command_parse.Po \ lib/$(DEPDIR)/clippy-command_py.Po \ lib/$(DEPDIR)/clippy-defun_lex.Po \ lib/$(DEPDIR)/clippy-graph.Po lib/$(DEPDIR)/clippy-memory.Po \ lib/$(DEPDIR)/clippy-vector.Po lib/$(DEPDIR)/command.Plo \ lib/$(DEPDIR)/command_graph.Plo lib/$(DEPDIR)/command_lex.Plo \ lib/$(DEPDIR)/command_match.Plo \ lib/$(DEPDIR)/command_parse.Plo \ lib/$(DEPDIR)/confd_la-northbound_confd.Plo \ lib/$(DEPDIR)/csv.Plo lib/$(DEPDIR)/db.Plo \ lib/$(DEPDIR)/debug.Plo lib/$(DEPDIR)/distribute.Plo \ lib/$(DEPDIR)/ferr.Plo lib/$(DEPDIR)/filter.Plo \ lib/$(DEPDIR)/frr_pthread.Plo lib/$(DEPDIR)/frrcu.Plo \ lib/$(DEPDIR)/frrlua.Plo lib/$(DEPDIR)/frrstr.Plo \ lib/$(DEPDIR)/getopt.Plo lib/$(DEPDIR)/getopt1.Plo \ lib/$(DEPDIR)/grammar_sandbox.Plo \ lib/$(DEPDIR)/grammar_sandbox_main.Po lib/$(DEPDIR)/graph.Plo \ lib/$(DEPDIR)/grpc_la-northbound_grpc.Plo \ lib/$(DEPDIR)/hash.Plo lib/$(DEPDIR)/hook.Plo \ lib/$(DEPDIR)/id_alloc.Plo lib/$(DEPDIR)/if.Plo \ lib/$(DEPDIR)/if_rmap.Plo lib/$(DEPDIR)/imsg-buffer.Plo \ lib/$(DEPDIR)/imsg.Plo lib/$(DEPDIR)/jhash.Plo \ lib/$(DEPDIR)/json.Plo lib/$(DEPDIR)/keychain.Plo \ lib/$(DEPDIR)/lib_errors.Plo lib/$(DEPDIR)/libfrr.Plo \ lib/$(DEPDIR)/libfrrcares_la-resolver.Plo \ lib/$(DEPDIR)/libfrrsnmp_la-agentx.Plo \ lib/$(DEPDIR)/libfrrsnmp_la-snmp.Plo \ lib/$(DEPDIR)/libfrrzmq_la-frr_zmq.Plo \ lib/$(DEPDIR)/linklist.Plo lib/$(DEPDIR)/log.Plo \ lib/$(DEPDIR)/log_vty.Plo lib/$(DEPDIR)/md5.Plo \ lib/$(DEPDIR)/memory.Plo lib/$(DEPDIR)/memory_vty.Plo \ lib/$(DEPDIR)/mlag.Plo lib/$(DEPDIR)/module.Plo \ lib/$(DEPDIR)/mpls.Plo lib/$(DEPDIR)/netns_linux.Plo \ lib/$(DEPDIR)/netns_other.Plo lib/$(DEPDIR)/network.Plo \ lib/$(DEPDIR)/nexthop.Plo lib/$(DEPDIR)/nexthop_group.Plo \ lib/$(DEPDIR)/northbound.Plo lib/$(DEPDIR)/northbound_cli.Plo \ lib/$(DEPDIR)/northbound_db.Plo lib/$(DEPDIR)/ntop.Plo \ lib/$(DEPDIR)/openbsd-tree.Plo lib/$(DEPDIR)/pid_output.Plo \ lib/$(DEPDIR)/plist.Plo lib/$(DEPDIR)/prefix.Plo \ lib/$(DEPDIR)/privs.Plo lib/$(DEPDIR)/ptm_lib.Plo \ lib/$(DEPDIR)/pullwr.Plo lib/$(DEPDIR)/qobj.Plo \ lib/$(DEPDIR)/ringbuf.Plo lib/$(DEPDIR)/routemap.Plo \ lib/$(DEPDIR)/sbuf.Plo lib/$(DEPDIR)/seqlock.Plo \ lib/$(DEPDIR)/sha256.Plo lib/$(DEPDIR)/sigevent.Plo \ lib/$(DEPDIR)/skiplist.Plo lib/$(DEPDIR)/sockopt.Plo \ lib/$(DEPDIR)/sockunion.Plo lib/$(DEPDIR)/spf_backoff.Plo \ lib/$(DEPDIR)/srcdest_table.Plo lib/$(DEPDIR)/stream.Plo \ lib/$(DEPDIR)/strlcat.Plo lib/$(DEPDIR)/strlcpy.Plo \ lib/$(DEPDIR)/sysrepo_la-northbound_sysrepo.Plo \ lib/$(DEPDIR)/systemd.Plo lib/$(DEPDIR)/table.Plo \ lib/$(DEPDIR)/termtable.Plo lib/$(DEPDIR)/thread.Plo \ lib/$(DEPDIR)/typerb.Plo lib/$(DEPDIR)/typesafe.Plo \ lib/$(DEPDIR)/vector.Plo lib/$(DEPDIR)/vrf.Plo \ lib/$(DEPDIR)/vty.Plo lib/$(DEPDIR)/wheel.Plo \ lib/$(DEPDIR)/workqueue.Plo lib/$(DEPDIR)/yang.Plo \ lib/$(DEPDIR)/yang_translator.Plo \ lib/$(DEPDIR)/yang_wrappers.Plo lib/$(DEPDIR)/zclient.Plo \ lib/printf/$(DEPDIR)/glue.Plo \ lib/printf/$(DEPDIR)/printf-pos.Plo \ lib/printf/$(DEPDIR)/vfprintf.Plo nhrpd/$(DEPDIR)/linux.Po \ nhrpd/$(DEPDIR)/netlink_arp.Po nhrpd/$(DEPDIR)/netlink_gre.Po \ nhrpd/$(DEPDIR)/nhrp_cache.Po nhrpd/$(DEPDIR)/nhrp_errors.Po \ nhrpd/$(DEPDIR)/nhrp_event.Po \ nhrpd/$(DEPDIR)/nhrp_interface.Po nhrpd/$(DEPDIR)/nhrp_main.Po \ nhrpd/$(DEPDIR)/nhrp_nhs.Po nhrpd/$(DEPDIR)/nhrp_packet.Po \ nhrpd/$(DEPDIR)/nhrp_peer.Po nhrpd/$(DEPDIR)/nhrp_route.Po \ nhrpd/$(DEPDIR)/nhrp_shortcut.Po nhrpd/$(DEPDIR)/nhrp_vc.Po \ nhrpd/$(DEPDIR)/nhrp_vty.Po nhrpd/$(DEPDIR)/reqid.Po \ nhrpd/$(DEPDIR)/vici.Po nhrpd/$(DEPDIR)/zbuf.Po \ nhrpd/$(DEPDIR)/znl.Po ospf6d/$(DEPDIR)/ospf6_abr.Po \ ospf6d/$(DEPDIR)/ospf6_area.Po ospf6d/$(DEPDIR)/ospf6_asbr.Po \ ospf6d/$(DEPDIR)/ospf6_bfd.Po ospf6d/$(DEPDIR)/ospf6_flood.Po \ ospf6d/$(DEPDIR)/ospf6_interface.Po \ ospf6d/$(DEPDIR)/ospf6_intra.Po ospf6d/$(DEPDIR)/ospf6_lsa.Po \ ospf6d/$(DEPDIR)/ospf6_lsdb.Po ospf6d/$(DEPDIR)/ospf6_main.Po \ ospf6d/$(DEPDIR)/ospf6_memory.Po \ ospf6d/$(DEPDIR)/ospf6_message.Po \ ospf6d/$(DEPDIR)/ospf6_neighbor.Po \ ospf6d/$(DEPDIR)/ospf6_network.Po \ ospf6d/$(DEPDIR)/ospf6_proto.Po \ ospf6d/$(DEPDIR)/ospf6_route.Po ospf6d/$(DEPDIR)/ospf6_spf.Po \ ospf6d/$(DEPDIR)/ospf6_top.Po ospf6d/$(DEPDIR)/ospf6_zebra.Po \ ospf6d/$(DEPDIR)/ospf6d.Po \ ospf6d/$(DEPDIR)/ospf6d_snmp_la-ospf6_snmp.Plo \ ospfclient/$(DEPDIR)/ospf_apiclient.Plo \ ospfclient/$(DEPDIR)/ospfclient.Po ospfd/$(DEPDIR)/ospf_abr.Po \ ospfd/$(DEPDIR)/ospf_api.Po ospfd/$(DEPDIR)/ospf_apiserver.Po \ ospfd/$(DEPDIR)/ospf_asbr.Po ospfd/$(DEPDIR)/ospf_ase.Po \ ospfd/$(DEPDIR)/ospf_bfd.Po ospfd/$(DEPDIR)/ospf_dump.Po \ ospfd/$(DEPDIR)/ospf_dump_api.Po \ ospfd/$(DEPDIR)/ospf_errors.Po ospfd/$(DEPDIR)/ospf_ext.Po \ ospfd/$(DEPDIR)/ospf_flood.Po ospfd/$(DEPDIR)/ospf_ia.Po \ ospfd/$(DEPDIR)/ospf_interface.Po ospfd/$(DEPDIR)/ospf_ism.Po \ ospfd/$(DEPDIR)/ospf_lsa.Po ospfd/$(DEPDIR)/ospf_lsdb.Po \ ospfd/$(DEPDIR)/ospf_main.Po ospfd/$(DEPDIR)/ospf_memory.Po \ ospfd/$(DEPDIR)/ospf_neighbor.Po \ ospfd/$(DEPDIR)/ospf_network.Po ospfd/$(DEPDIR)/ospf_nsm.Po \ ospfd/$(DEPDIR)/ospf_opaque.Po ospfd/$(DEPDIR)/ospf_packet.Po \ ospfd/$(DEPDIR)/ospf_ri.Po ospfd/$(DEPDIR)/ospf_route.Po \ ospfd/$(DEPDIR)/ospf_routemap.Po ospfd/$(DEPDIR)/ospf_spf.Po \ ospfd/$(DEPDIR)/ospf_sr.Po ospfd/$(DEPDIR)/ospf_te.Po \ ospfd/$(DEPDIR)/ospf_vty.Po ospfd/$(DEPDIR)/ospf_zebra.Po \ ospfd/$(DEPDIR)/ospfd.Po \ ospfd/$(DEPDIR)/ospfd_snmp_la-ospf_snmp.Plo \ pbrd/$(DEPDIR)/pbr_debug.Po pbrd/$(DEPDIR)/pbr_main.Po \ pbrd/$(DEPDIR)/pbr_map.Po pbrd/$(DEPDIR)/pbr_memory.Po \ pbrd/$(DEPDIR)/pbr_nht.Po pbrd/$(DEPDIR)/pbr_vty.Po \ pbrd/$(DEPDIR)/pbr_zebra.Po pimd/$(DEPDIR)/mtracebis.Po \ pimd/$(DEPDIR)/mtracebis_netlink.Po \ pimd/$(DEPDIR)/mtracebis_routeget.Po \ pimd/$(DEPDIR)/pim_assert.Po pimd/$(DEPDIR)/pim_bfd.Po \ pimd/$(DEPDIR)/pim_br.Po pimd/$(DEPDIR)/pim_bsm.Po \ pimd/$(DEPDIR)/pim_cmd.Po pimd/$(DEPDIR)/pim_errors.Po \ pimd/$(DEPDIR)/pim_hello.Po pimd/$(DEPDIR)/pim_iface.Po \ pimd/$(DEPDIR)/pim_ifchannel.Po pimd/$(DEPDIR)/pim_igmp.Po \ pimd/$(DEPDIR)/pim_igmp_mtrace.Po \ pimd/$(DEPDIR)/pim_igmp_stats.Po pimd/$(DEPDIR)/pim_igmpv2.Po \ pimd/$(DEPDIR)/pim_igmpv3.Po pimd/$(DEPDIR)/pim_instance.Po \ pimd/$(DEPDIR)/pim_int.Po pimd/$(DEPDIR)/pim_join.Po \ pimd/$(DEPDIR)/pim_jp_agg.Po pimd/$(DEPDIR)/pim_macro.Po \ pimd/$(DEPDIR)/pim_main.Po pimd/$(DEPDIR)/pim_memory.Po \ pimd/$(DEPDIR)/pim_mroute.Po pimd/$(DEPDIR)/pim_msdp.Po \ pimd/$(DEPDIR)/pim_msdp_packet.Po \ pimd/$(DEPDIR)/pim_msdp_socket.Po pimd/$(DEPDIR)/pim_msg.Po \ pimd/$(DEPDIR)/pim_neighbor.Po pimd/$(DEPDIR)/pim_nht.Po \ pimd/$(DEPDIR)/pim_oil.Po pimd/$(DEPDIR)/pim_pim.Po \ pimd/$(DEPDIR)/pim_register.Po pimd/$(DEPDIR)/pim_routemap.Po \ pimd/$(DEPDIR)/pim_rp.Po pimd/$(DEPDIR)/pim_rpf.Po \ pimd/$(DEPDIR)/pim_signals.Po pimd/$(DEPDIR)/pim_sock.Po \ pimd/$(DEPDIR)/pim_ssm.Po pimd/$(DEPDIR)/pim_ssmpingd.Po \ pimd/$(DEPDIR)/pim_static.Po pimd/$(DEPDIR)/pim_str.Po \ pimd/$(DEPDIR)/pim_time.Po pimd/$(DEPDIR)/pim_tlv.Po \ pimd/$(DEPDIR)/pim_upstream.Po pimd/$(DEPDIR)/pim_util.Po \ pimd/$(DEPDIR)/pim_version.Po pimd/$(DEPDIR)/pim_vty.Po \ pimd/$(DEPDIR)/pim_vxlan.Po pimd/$(DEPDIR)/pim_zebra.Po \ pimd/$(DEPDIR)/pim_zlookup.Po pimd/$(DEPDIR)/pimd.Po \ pimd/$(DEPDIR)/test_igmpv3_join.Po \ qpb/$(DEPDIR)/libfrr_pb_la-qpb.Plo \ qpb/$(DEPDIR)/libfrr_pb_la-qpb.pb-c.Plo \ qpb/$(DEPDIR)/libfrr_pb_la-qpb_allocator.Plo \ ripd/$(DEPDIR)/rip_cli.Po ripd/$(DEPDIR)/rip_debug.Po \ ripd/$(DEPDIR)/rip_errors.Po ripd/$(DEPDIR)/rip_interface.Po \ ripd/$(DEPDIR)/rip_main.Po ripd/$(DEPDIR)/rip_northbound.Po \ ripd/$(DEPDIR)/rip_offset.Po ripd/$(DEPDIR)/rip_peer.Po \ ripd/$(DEPDIR)/rip_routemap.Po ripd/$(DEPDIR)/rip_zebra.Po \ ripd/$(DEPDIR)/ripd.Po \ ripd/$(DEPDIR)/ripd_snmp_la-rip_snmp.Plo \ ripngd/$(DEPDIR)/ripng_cli.Po ripngd/$(DEPDIR)/ripng_debug.Po \ ripngd/$(DEPDIR)/ripng_interface.Po \ ripngd/$(DEPDIR)/ripng_main.Po \ ripngd/$(DEPDIR)/ripng_nexthop.Po \ ripngd/$(DEPDIR)/ripng_northbound.Po \ ripngd/$(DEPDIR)/ripng_offset.Po \ ripngd/$(DEPDIR)/ripng_peer.Po ripngd/$(DEPDIR)/ripng_route.Po \ ripngd/$(DEPDIR)/ripng_routemap.Po \ ripngd/$(DEPDIR)/ripng_zebra.Po ripngd/$(DEPDIR)/ripngd.Po \ sharpd/$(DEPDIR)/sharp_main.Po sharpd/$(DEPDIR)/sharp_nht.Po \ sharpd/$(DEPDIR)/sharp_vty.Po sharpd/$(DEPDIR)/sharp_zebra.Po \ staticd/$(DEPDIR)/static_main.Po \ staticd/$(DEPDIR)/static_memory.Po \ staticd/$(DEPDIR)/static_nht.Po \ staticd/$(DEPDIR)/static_routes.Po \ staticd/$(DEPDIR)/static_vrf.Po \ staticd/$(DEPDIR)/static_vty.Po \ staticd/$(DEPDIR)/static_zebra.Po \ tests/bgpd/$(DEPDIR)/test_aspath-test_aspath.Po \ tests/bgpd/$(DEPDIR)/test_bgp_table-test_bgp_table.Po \ tests/bgpd/$(DEPDIR)/test_capability-test_capability.Po \ tests/bgpd/$(DEPDIR)/test_ecommunity-test_ecommunity.Po \ tests/bgpd/$(DEPDIR)/test_mp_attr-test_mp_attr.Po \ tests/bgpd/$(DEPDIR)/test_mpath-test_mpath.Po \ tests/bgpd/$(DEPDIR)/test_packet-test_packet.Po \ tests/bgpd/$(DEPDIR)/test_peer_attr-test_peer_attr.Po \ tests/helpers/c/$(DEPDIR)/lib_cli_test_commands-prng.Po \ tests/helpers/c/$(DEPDIR)/lib_test_heavy-main.Po \ tests/helpers/c/$(DEPDIR)/lib_test_heavy_thread-main.Po \ tests/helpers/c/$(DEPDIR)/lib_test_heavy_wq-main.Po \ tests/helpers/c/$(DEPDIR)/lib_test_nexthop_iter-prng.Po \ tests/helpers/c/$(DEPDIR)/lib_test_ntop-prng.Po \ tests/helpers/c/$(DEPDIR)/lib_test_prefix2str-prng.Po \ tests/helpers/c/$(DEPDIR)/lib_test_srcdest_table-prng.Po \ tests/helpers/c/$(DEPDIR)/lib_test_timer_correctness-prng.Po \ tests/helpers/c/$(DEPDIR)/lib_test_timer_performance-prng.Po \ tests/helpers/c/$(DEPDIR)/lib_test_typelist-prng.Po \ tests/isisd/$(DEPDIR)/test_fuzz_isis_tlv-test_fuzz_isis_tlv.Po \ tests/isisd/$(DEPDIR)/test_isis_lspdb-test_isis_lspdb.Po \ tests/isisd/$(DEPDIR)/test_isis_vertex_queue-test_isis_vertex_queue.Po \ tests/lib/$(DEPDIR)/cxxcompat-cxxcompat.Po \ tests/lib/$(DEPDIR)/test_atomlist-test_atomlist.Po \ tests/lib/$(DEPDIR)/test_buffer-test_buffer.Po \ tests/lib/$(DEPDIR)/test_checksum-test_checksum.Po \ tests/lib/$(DEPDIR)/test_graph-test_graph.Po \ tests/lib/$(DEPDIR)/test_heavy-test_heavy.Po \ tests/lib/$(DEPDIR)/test_heavy_thread-test_heavy_thread.Po \ tests/lib/$(DEPDIR)/test_heavy_wq-test_heavy_wq.Po \ tests/lib/$(DEPDIR)/test_idalloc-test_idalloc.Po \ tests/lib/$(DEPDIR)/test_memory-test_memory.Po \ tests/lib/$(DEPDIR)/test_nexthop_iter-test_nexthop_iter.Po \ tests/lib/$(DEPDIR)/test_ntop-test_ntop.Po \ tests/lib/$(DEPDIR)/test_prefix2str-test_prefix2str.Po \ tests/lib/$(DEPDIR)/test_printfrr-test_printfrr.Po \ tests/lib/$(DEPDIR)/test_privs-test_privs.Po \ tests/lib/$(DEPDIR)/test_ringbuf-test_ringbuf.Po \ tests/lib/$(DEPDIR)/test_segv-test_segv.Po \ tests/lib/$(DEPDIR)/test_seqlock-test_seqlock.Po \ tests/lib/$(DEPDIR)/test_sig-test_sig.Po \ tests/lib/$(DEPDIR)/test_srcdest_table-test_srcdest_table.Po \ tests/lib/$(DEPDIR)/test_stream-test_stream.Po \ tests/lib/$(DEPDIR)/test_table-test_table.Po \ tests/lib/$(DEPDIR)/test_timer_correctness-test_timer_correctness.Po \ tests/lib/$(DEPDIR)/test_timer_performance-test_timer_performance.Po \ tests/lib/$(DEPDIR)/test_ttable-test_ttable.Po \ tests/lib/$(DEPDIR)/test_typelist-test_typelist.Po \ tests/lib/$(DEPDIR)/test_zlog-test_zlog.Po \ tests/lib/$(DEPDIR)/test_zmq-test_zmq.Po \ tests/lib/cli/$(DEPDIR)/ospf6d_test_lsdb-common_cli.Po \ tests/lib/cli/$(DEPDIR)/test_cli-common_cli.Po \ tests/lib/cli/$(DEPDIR)/test_cli-test_cli.Po \ tests/lib/cli/$(DEPDIR)/test_commands-test_commands.Po \ tests/lib/cli/$(DEPDIR)/test_commands-test_commands_defun.Po \ tests/lib/northbound/$(DEPDIR)/test_oper_data-test_oper_data.Po \ tests/ospf6d/$(DEPDIR)/test_lsdb-test_lsdb.Po \ tools/$(DEPDIR)/gen_northbound_callbacks.Po \ tools/$(DEPDIR)/gen_yang_deviations.Po \ tools/$(DEPDIR)/permutations.Po \ tools/$(DEPDIR)/start-stop-daemon.Po vrrpd/$(DEPDIR)/vrrp.Po \ vrrpd/$(DEPDIR)/vrrp_arp.Po vrrpd/$(DEPDIR)/vrrp_debug.Po \ vrrpd/$(DEPDIR)/vrrp_main.Po vrrpd/$(DEPDIR)/vrrp_ndisc.Po \ vrrpd/$(DEPDIR)/vrrp_packet.Po vrrpd/$(DEPDIR)/vrrp_vty.Po \ vrrpd/$(DEPDIR)/vrrp_zebra.Po vtysh/$(DEPDIR)/vtysh.Po \ vtysh/$(DEPDIR)/vtysh_cmd.Po vtysh/$(DEPDIR)/vtysh_config.Po \ vtysh/$(DEPDIR)/vtysh_main.Po vtysh/$(DEPDIR)/vtysh_user.Po \ watchfrr/$(DEPDIR)/watchfrr.Po \ watchfrr/$(DEPDIR)/watchfrr_errors.Po \ watchfrr/$(DEPDIR)/watchfrr_vty.Po \ yang/$(DEPDIR)/frr-bfdd.yang.Po \ yang/$(DEPDIR)/frr-eigrpd.yang.Po \ yang/$(DEPDIR)/frr-interface.yang.Plo \ yang/$(DEPDIR)/frr-isisd.yang.Po \ yang/$(DEPDIR)/frr-module-translator.yang.Plo \ yang/$(DEPDIR)/frr-ripd.yang.Po \ yang/$(DEPDIR)/frr-ripngd.yang.Po \ yang/$(DEPDIR)/frr-route-types.yang.Plo \ yang/$(DEPDIR)/tests_lib_northbound_test_oper_data-frr-test-module.yang.Po \ zebra/$(DEPDIR)/connected.Po zebra/$(DEPDIR)/debug.Po \ zebra/$(DEPDIR)/if_ioctl.Po \ zebra/$(DEPDIR)/if_ioctl_solaris.Po \ zebra/$(DEPDIR)/if_netlink.Po zebra/$(DEPDIR)/if_sysctl.Po \ zebra/$(DEPDIR)/interface.Po zebra/$(DEPDIR)/ioctl.Po \ zebra/$(DEPDIR)/ioctl_solaris.Po \ zebra/$(DEPDIR)/ipforward_proc.Po \ zebra/$(DEPDIR)/ipforward_solaris.Po \ zebra/$(DEPDIR)/ipforward_sysctl.Po \ zebra/$(DEPDIR)/irdp_interface.Plo \ zebra/$(DEPDIR)/irdp_main.Plo zebra/$(DEPDIR)/irdp_packet.Plo \ zebra/$(DEPDIR)/kernel_netlink.Po \ zebra/$(DEPDIR)/kernel_socket.Po \ zebra/$(DEPDIR)/label_manager.Po zebra/$(DEPDIR)/main.Po \ zebra/$(DEPDIR)/redistribute.Po zebra/$(DEPDIR)/router-id.Po \ zebra/$(DEPDIR)/rt_netlink.Po zebra/$(DEPDIR)/rt_socket.Po \ zebra/$(DEPDIR)/rtadv.Po zebra/$(DEPDIR)/rtread_getmsg.Po \ zebra/$(DEPDIR)/rtread_netlink.Po \ zebra/$(DEPDIR)/rtread_sysctl.Po \ zebra/$(DEPDIR)/rule_netlink.Po zebra/$(DEPDIR)/rule_socket.Po \ zebra/$(DEPDIR)/table_manager.Po zebra/$(DEPDIR)/zapi_msg.Po \ zebra/$(DEPDIR)/zebra_dplane.Po \ zebra/$(DEPDIR)/zebra_errors.Po zebra/$(DEPDIR)/zebra_fpm.Plo \ zebra/$(DEPDIR)/zebra_fpm_dt.Plo \ zebra/$(DEPDIR)/zebra_fpm_netlink.Plo \ zebra/$(DEPDIR)/zebra_fpm_protobuf.Plo \ zebra/$(DEPDIR)/zebra_l2.Po zebra/$(DEPDIR)/zebra_memory.Po \ zebra/$(DEPDIR)/zebra_mlag.Po zebra/$(DEPDIR)/zebra_mpls.Po \ zebra/$(DEPDIR)/zebra_mpls_netlink.Po \ zebra/$(DEPDIR)/zebra_mpls_null.Po \ zebra/$(DEPDIR)/zebra_mpls_openbsd.Po \ zebra/$(DEPDIR)/zebra_mpls_vty.Po \ zebra/$(DEPDIR)/zebra_mroute.Po \ zebra/$(DEPDIR)/zebra_netns_id.Po \ zebra/$(DEPDIR)/zebra_netns_notify.Po \ zebra/$(DEPDIR)/zebra_nhg.Po zebra/$(DEPDIR)/zebra_ns.Po \ zebra/$(DEPDIR)/zebra_pbr.Po zebra/$(DEPDIR)/zebra_ptm.Po \ zebra/$(DEPDIR)/zebra_ptm_redistribute.Po \ zebra/$(DEPDIR)/zebra_pw.Po zebra/$(DEPDIR)/zebra_rib.Po \ zebra/$(DEPDIR)/zebra_rnh.Po zebra/$(DEPDIR)/zebra_routemap.Po \ zebra/$(DEPDIR)/zebra_router.Po \ zebra/$(DEPDIR)/zebra_snmp_la-zebra_snmp.Plo \ zebra/$(DEPDIR)/zebra_vrf.Po zebra/$(DEPDIR)/zebra_vty.Po \ zebra/$(DEPDIR)/zebra_vxlan.Po zebra/$(DEPDIR)/zserv.Po am__mv = mv -f COMPILE = $(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(AM_CPPFLAGS) \ $(CPPFLAGS) $(AM_CFLAGS) $(CFLAGS) LTCOMPILE = $(LIBTOOL) $(AM_V_lt) --tag=CC $(AM_LIBTOOLFLAGS) \ $(LIBTOOLFLAGS) --mode=compile $(CC) $(DEFS) \ $(DEFAULT_INCLUDES) $(INCLUDES) $(AM_CPPFLAGS) $(CPPFLAGS) \ $(AM_CFLAGS) $(CFLAGS) AM_V_CC = $(am__v_CC_@AM_V@) am__v_CC_ = $(am__v_CC_@AM_DEFAULT_V@) am__v_CC_0 = @echo " CC " $@; am__v_CC_1 = CCLD = $(CC) LINK = $(LIBTOOL) $(AM_V_lt) --tag=CC $(AM_LIBTOOLFLAGS) \ $(LIBTOOLFLAGS) --mode=link $(CCLD) $(AM_CFLAGS) $(CFLAGS) \ $(AM_LDFLAGS) $(LDFLAGS) -o $@ AM_V_CCLD = $(am__v_CCLD_@AM_V@) am__v_CCLD_ = $(am__v_CCLD_@AM_DEFAULT_V@) am__v_CCLD_0 = @echo " CCLD " $@; am__v_CCLD_1 = CXXCOMPILE = $(CXX) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) \ $(AM_CPPFLAGS) $(CPPFLAGS) $(AM_CXXFLAGS) $(CXXFLAGS) LTCXXCOMPILE = $(LIBTOOL) $(AM_V_lt) --tag=CXX $(AM_LIBTOOLFLAGS) \ $(LIBTOOLFLAGS) --mode=compile $(CXX) $(DEFS) \ $(DEFAULT_INCLUDES) $(INCLUDES) $(AM_CPPFLAGS) $(CPPFLAGS) \ $(AM_CXXFLAGS) $(CXXFLAGS) AM_V_CXX = $(am__v_CXX_@AM_V@) am__v_CXX_ = $(am__v_CXX_@AM_DEFAULT_V@) am__v_CXX_0 = @echo " CXX " $@; am__v_CXX_1 = CXXLD = $(CXX) CXXLINK = $(LIBTOOL) $(AM_V_lt) --tag=CXX $(AM_LIBTOOLFLAGS) \ $(LIBTOOLFLAGS) --mode=link $(CXXLD) $(AM_CXXFLAGS) \ $(CXXFLAGS) $(AM_LDFLAGS) $(LDFLAGS) -o $@ AM_V_CXXLD = $(am__v_CXXLD_@AM_V@) am__v_CXXLD_ = $(am__v_CXXLD_@AM_DEFAULT_V@) am__v_CXXLD_0 = @echo " CXXLD " $@; am__v_CXXLD_1 = LEXCOMPILE = $(LEX) $(AM_LFLAGS) $(LFLAGS) LTLEXCOMPILE = $(LIBTOOL) $(AM_V_lt) $(AM_LIBTOOLFLAGS) \ $(LIBTOOLFLAGS) --mode=compile $(LEX) $(AM_LFLAGS) $(LFLAGS) AM_V_LEX = $(am__v_LEX_@AM_V@) am__v_LEX_ = $(am__v_LEX_@AM_DEFAULT_V@) am__v_LEX_0 = @echo " LEX " $@; am__v_LEX_1 = YLWRAP = $(top_srcdir)/ylwrap am__yacc_c2h = sed -e s/cc$$/hh/ -e s/cpp$$/hpp/ -e s/cxx$$/hxx/ \ -e s/c++$$/h++/ -e s/c$$/h/ YACCCOMPILE = $(YACC) $(AM_YFLAGS) $(YFLAGS) LTYACCCOMPILE = $(LIBTOOL) $(AM_V_lt) $(AM_LIBTOOLFLAGS) \ $(LIBTOOLFLAGS) --mode=compile $(YACC) $(AM_YFLAGS) $(YFLAGS) AM_V_YACC = $(am__v_YACC_@AM_V@) am__v_YACC_ = $(am__v_YACC_@AM_DEFAULT_V@) am__v_YACC_0 = @echo " YACC " $@; am__v_YACC_1 = SOURCES = $(babeld_libbabel_a_SOURCES) $(bfdd_libbfd_a_SOURCES) \ $(bgpd_libbgp_a_SOURCES) \ $(bgpd_rfp_example_librfp_librfp_a_SOURCES) \ $(eigrpd_libeigrp_a_SOURCES) $(isisd_libfabric_a_SOURCES) \ $(isisd_libisis_a_SOURCES) $(ldpd_libldp_a_SOURCES) \ $(ospf6d_libospf6_a_SOURCES) $(ospfd_libfrrospf_a_SOURCES) \ $(pbrd_libpbr_a_SOURCES) $(pimd_libpim_a_SOURCES) \ $(ripd_librip_a_SOURCES) $(ripngd_libripng_a_SOURCES) \ $(sharpd_libsharp_a_SOURCES) $(staticd_libstatic_a_SOURCES) \ $(vrrpd_libvrrp_a_SOURCES) $(bgpd_bgpd_bmp_la_SOURCES) \ $(bgpd_bgpd_rpki_la_SOURCES) $(bgpd_bgpd_snmp_la_SOURCES) \ $(fpm_libfrrfpm_pb_la_SOURCES) \ $(nodist_fpm_libfrrfpm_pb_la_SOURCES) \ $(nodist_grpc_libfrrgrpc_pb_la_SOURCES) \ $(lib_confd_la_SOURCES) $(lib_grpc_la_SOURCES) \ $(lib_libfrr_la_SOURCES) $(nodist_lib_libfrr_la_SOURCES) \ $(lib_libfrrcares_la_SOURCES) $(lib_libfrrsnmp_la_SOURCES) \ $(lib_libfrrzmq_la_SOURCES) $(lib_sysrepo_la_SOURCES) \ $(ospf6d_ospf6d_snmp_la_SOURCES) \ $(ospfclient_libfrrospfapiclient_la_SOURCES) \ $(ospfd_ospfd_snmp_la_SOURCES) $(qpb_libfrr_pb_la_SOURCES) \ $(nodist_qpb_libfrr_pb_la_SOURCES) \ $(ripd_ripd_snmp_la_SOURCES) $(zebra_zebra_fpm_la_SOURCES) \ $(zebra_zebra_irdp_la_SOURCES) $(zebra_zebra_snmp_la_SOURCES) \ $(babeld_babeld_SOURCES) $(bfdd_bfdd_SOURCES) \ $(nodist_bfdd_bfdd_SOURCES) $(bgpd_bgp_btoa_SOURCES) \ $(bgpd_bgpd_SOURCES) \ $(bgpd_rfp_example_rfptest_rfptest_SOURCES) \ $(eigrpd_eigrpd_SOURCES) $(nodist_eigrpd_eigrpd_SOURCES) \ $(isisd_fabricd_SOURCES) $(isisd_isisd_SOURCES) \ $(nodist_isisd_isisd_SOURCES) $(ldpd_ldpd_SOURCES) \ $(lib_clippy_SOURCES) $(lib_grammar_sandbox_SOURCES) \ $(nhrpd_nhrpd_SOURCES) $(ospf6d_ospf6d_SOURCES) \ $(ospfclient_ospfclient_SOURCES) $(ospfd_ospfd_SOURCES) \ $(pbrd_pbrd_SOURCES) $(pimd_mtracebis_SOURCES) \ $(pimd_pimd_SOURCES) $(pimd_test_igmpv3_join_SOURCES) \ $(ripd_ripd_SOURCES) $(nodist_ripd_ripd_SOURCES) \ $(ripngd_ripngd_SOURCES) $(nodist_ripngd_ripngd_SOURCES) \ $(sharpd_sharpd_SOURCES) $(staticd_staticd_SOURCES) \ $(tests_bgpd_test_aspath_SOURCES) \ $(tests_bgpd_test_bgp_table_SOURCES) \ $(tests_bgpd_test_capability_SOURCES) \ $(tests_bgpd_test_ecommunity_SOURCES) \ $(tests_bgpd_test_mp_attr_SOURCES) \ $(tests_bgpd_test_mpath_SOURCES) \ $(tests_bgpd_test_packet_SOURCES) \ $(tests_bgpd_test_peer_attr_SOURCES) \ $(tests_isisd_test_fuzz_isis_tlv_SOURCES) \ $(nodist_tests_isisd_test_fuzz_isis_tlv_SOURCES) \ $(tests_isisd_test_isis_lspdb_SOURCES) \ $(tests_isisd_test_isis_vertex_queue_SOURCES) \ $(tests_lib_cli_test_cli_SOURCES) \ $(tests_lib_cli_test_commands_SOURCES) \ $(nodist_tests_lib_cli_test_commands_SOURCES) \ $(tests_lib_cxxcompat_SOURCES) \ $(tests_lib_northbound_test_oper_data_SOURCES) \ $(nodist_tests_lib_northbound_test_oper_data_SOURCES) \ $(tests_lib_test_atomlist_SOURCES) \ $(tests_lib_test_buffer_SOURCES) \ $(tests_lib_test_checksum_SOURCES) \ $(tests_lib_test_graph_SOURCES) \ $(tests_lib_test_heavy_SOURCES) \ $(tests_lib_test_heavy_thread_SOURCES) \ $(tests_lib_test_heavy_wq_SOURCES) \ $(tests_lib_test_idalloc_SOURCES) \ $(tests_lib_test_memory_SOURCES) \ $(tests_lib_test_nexthop_iter_SOURCES) \ $(tests_lib_test_ntop_SOURCES) \ $(tests_lib_test_prefix2str_SOURCES) \ $(tests_lib_test_printfrr_SOURCES) \ $(tests_lib_test_privs_SOURCES) \ $(tests_lib_test_ringbuf_SOURCES) \ $(tests_lib_test_segv_SOURCES) \ $(tests_lib_test_seqlock_SOURCES) \ $(tests_lib_test_sig_SOURCES) \ $(tests_lib_test_srcdest_table_SOURCES) \ $(tests_lib_test_stream_SOURCES) \ $(tests_lib_test_table_SOURCES) \ $(tests_lib_test_timer_correctness_SOURCES) \ $(tests_lib_test_timer_performance_SOURCES) \ $(tests_lib_test_ttable_SOURCES) \ $(tests_lib_test_typelist_SOURCES) \ $(tests_lib_test_zlog_SOURCES) $(tests_lib_test_zmq_SOURCES) \ $(tests_ospf6d_test_lsdb_SOURCES) \ $(tools_gen_northbound_callbacks_SOURCES) \ $(tools_gen_yang_deviations_SOURCES) \ $(tools_permutations_SOURCES) $(tools_ssd_SOURCES) \ $(vrrpd_vrrpd_SOURCES) $(vtysh_vtysh_SOURCES) \ $(nodist_vtysh_vtysh_SOURCES) $(watchfrr_watchfrr_SOURCES) \ $(zebra_zebra_SOURCES) DIST_SOURCES = $(babeld_libbabel_a_SOURCES) $(bfdd_libbfd_a_SOURCES) \ $(am__bgpd_libbgp_a_SOURCES_DIST) \ $(bgpd_rfp_example_librfp_librfp_a_SOURCES) \ $(eigrpd_libeigrp_a_SOURCES) $(isisd_libfabric_a_SOURCES) \ $(isisd_libisis_a_SOURCES) $(ldpd_libldp_a_SOURCES) \ $(ospf6d_libospf6_a_SOURCES) $(ospfd_libfrrospf_a_SOURCES) \ $(pbrd_libpbr_a_SOURCES) $(pimd_libpim_a_SOURCES) \ $(ripd_librip_a_SOURCES) $(ripngd_libripng_a_SOURCES) \ $(sharpd_libsharp_a_SOURCES) $(staticd_libstatic_a_SOURCES) \ $(vrrpd_libvrrp_a_SOURCES) $(bgpd_bgpd_bmp_la_SOURCES) \ $(bgpd_bgpd_rpki_la_SOURCES) $(bgpd_bgpd_snmp_la_SOURCES) \ $(fpm_libfrrfpm_pb_la_SOURCES) $(lib_confd_la_SOURCES) \ $(lib_grpc_la_SOURCES) $(am__lib_libfrr_la_SOURCES_DIST) \ $(lib_libfrrcares_la_SOURCES) $(lib_libfrrsnmp_la_SOURCES) \ $(lib_libfrrzmq_la_SOURCES) $(lib_sysrepo_la_SOURCES) \ $(ospf6d_ospf6d_snmp_la_SOURCES) \ $(ospfclient_libfrrospfapiclient_la_SOURCES) \ $(ospfd_ospfd_snmp_la_SOURCES) $(qpb_libfrr_pb_la_SOURCES) \ $(ripd_ripd_snmp_la_SOURCES) \ $(am__zebra_zebra_fpm_la_SOURCES_DIST) \ $(zebra_zebra_irdp_la_SOURCES) $(zebra_zebra_snmp_la_SOURCES) \ $(babeld_babeld_SOURCES) $(bfdd_bfdd_SOURCES) \ $(am__bgpd_bgp_btoa_SOURCES_DIST) \ $(am__bgpd_bgpd_SOURCES_DIST) \ $(bgpd_rfp_example_rfptest_rfptest_SOURCES) \ $(eigrpd_eigrpd_SOURCES) $(isisd_fabricd_SOURCES) \ $(isisd_isisd_SOURCES) $(ldpd_ldpd_SOURCES) \ $(lib_clippy_SOURCES) $(lib_grammar_sandbox_SOURCES) \ $(nhrpd_nhrpd_SOURCES) $(ospf6d_ospf6d_SOURCES) \ $(ospfclient_ospfclient_SOURCES) $(ospfd_ospfd_SOURCES) \ $(pbrd_pbrd_SOURCES) $(pimd_mtracebis_SOURCES) \ $(pimd_pimd_SOURCES) $(pimd_test_igmpv3_join_SOURCES) \ $(ripd_ripd_SOURCES) $(ripngd_ripngd_SOURCES) \ $(sharpd_sharpd_SOURCES) $(staticd_staticd_SOURCES) \ $(tests_bgpd_test_aspath_SOURCES) \ $(tests_bgpd_test_bgp_table_SOURCES) \ $(tests_bgpd_test_capability_SOURCES) \ $(tests_bgpd_test_ecommunity_SOURCES) \ $(tests_bgpd_test_mp_attr_SOURCES) \ $(tests_bgpd_test_mpath_SOURCES) \ $(tests_bgpd_test_packet_SOURCES) \ $(tests_bgpd_test_peer_attr_SOURCES) \ $(tests_isisd_test_fuzz_isis_tlv_SOURCES) \ $(tests_isisd_test_isis_lspdb_SOURCES) \ $(tests_isisd_test_isis_vertex_queue_SOURCES) \ $(tests_lib_cli_test_cli_SOURCES) \ $(tests_lib_cli_test_commands_SOURCES) \ $(tests_lib_cxxcompat_SOURCES) \ $(tests_lib_northbound_test_oper_data_SOURCES) \ $(tests_lib_test_atomlist_SOURCES) \ $(tests_lib_test_buffer_SOURCES) \ $(tests_lib_test_checksum_SOURCES) \ $(tests_lib_test_graph_SOURCES) \ $(tests_lib_test_heavy_SOURCES) \ $(tests_lib_test_heavy_thread_SOURCES) \ $(tests_lib_test_heavy_wq_SOURCES) \ $(tests_lib_test_idalloc_SOURCES) \ $(tests_lib_test_memory_SOURCES) \ $(tests_lib_test_nexthop_iter_SOURCES) \ $(tests_lib_test_ntop_SOURCES) \ $(tests_lib_test_prefix2str_SOURCES) \ $(tests_lib_test_printfrr_SOURCES) \ $(tests_lib_test_privs_SOURCES) \ $(tests_lib_test_ringbuf_SOURCES) \ $(tests_lib_test_segv_SOURCES) \ $(tests_lib_test_seqlock_SOURCES) \ $(tests_lib_test_sig_SOURCES) \ $(tests_lib_test_srcdest_table_SOURCES) \ $(tests_lib_test_stream_SOURCES) \ $(tests_lib_test_table_SOURCES) \ $(tests_lib_test_timer_correctness_SOURCES) \ $(tests_lib_test_timer_performance_SOURCES) \ $(tests_lib_test_ttable_SOURCES) \ $(tests_lib_test_typelist_SOURCES) \ $(tests_lib_test_zlog_SOURCES) $(tests_lib_test_zmq_SOURCES) \ $(tests_ospf6d_test_lsdb_SOURCES) \ $(tools_gen_northbound_callbacks_SOURCES) \ $(tools_gen_yang_deviations_SOURCES) \ $(tools_permutations_SOURCES) $(tools_ssd_SOURCES) \ $(vrrpd_vrrpd_SOURCES) $(vtysh_vtysh_SOURCES) \ $(watchfrr_watchfrr_SOURCES) $(zebra_zebra_SOURCES) am__can_run_installinfo = \ case $$AM_UPDATE_INFO_DIR in \ n|no|NO) false;; \ *) (install-info --version) >/dev/null 2>&1;; \ esac am__dist_examples_DATA_DIST = zebra/zebra.conf.sample \ bgpd/bgpd.conf.sample bgpd/bgpd.conf.sample2 \ bgpd/bgpd.conf.vnc.sample ripd/ripd.conf.sample \ ripngd/ripngd.conf.sample ospfd/ospfd.conf.sample \ ospf6d/ospf6d.conf.sample isisd/isisd.conf.sample \ isisd/fabricd.conf.sample ldpd/ldpd.conf.sample \ babeld/babeld.conf.sample eigrpd/eigrpd.conf.sample \ sharpd/sharpd.conf.sample pimd/pimd.conf.sample \ pbrd/pbrd.conf.sample staticd/staticd.conf.sample \ bfdd/bfdd.conf.sample vtysh/vtysh.conf.sample am__dist_yangmodels_DATA_DIST = yang/frr-module-translator.yang \ yang/frr-test-module.yang yang/frr-interface.yang \ yang/frr-route-types.yang yang/frr-bfdd.yang \ yang/frr-eigrpd.yang yang/frr-ripd.yang yang/frr-ripngd.yang \ yang/frr-isisd.yang DATA = $(dist_examples_DATA) $(dist_yangmodels_DATA) \ $(nodist_noinst_DATA) $(rstman1_DATA) $(rstman8_DATA) am__ospfapiheader_HEADERS_DIST = ospfclient/ospf_apiclient.h am__ospfdheader_HEADERS_DIST = ospfd/ospf_api.h ospfd/ospf_asbr.h \ ospfd/ospf_dump.h ospfd/ospf_dump_api.h ospfd/ospf_ism.h \ ospfd/ospf_lsa.h ospfd/ospf_lsdb.h ospfd/ospf_nsm.h \ ospfd/ospf_opaque.h ospfd/ospfd.h am__pkginclude_HEADERS_DIST = lib/agg_table.h lib/atomlist.h lib/bfd.h \ lib/bitfield.h lib/buffer.h lib/checksum.h lib/mlag.h \ lib/command.h lib/command_graph.h lib/command_match.h \ lib/compiler.h lib/csv.h lib/db.h lib/debug.h lib/distribute.h \ lib/ferr.h lib/filter.h lib/freebsd-queue.h lib/frrlua.h \ lib/frr_pthread.h lib/frratomic.h lib/frrcu.h lib/frrstr.h \ lib/getopt.h lib/graph.h lib/hash.h lib/hook.h lib/iana_afi.h \ lib/id_alloc.h lib/if.h lib/if_rmap.h lib/imsg.h lib/ipaddr.h \ lib/jhash.h lib/json.h lib/keychain.h lib/lib_errors.h \ lib/libfrr.h lib/libospf.h lib/linklist.h lib/log.h \ lib/log_vty.h lib/md5.h lib/memory.h lib/memory_vty.h \ lib/module.h lib/monotime.h lib/mpls.h lib/network.h \ lib/nexthop.h lib/nexthop_group.h lib/nexthop_group_private.h \ lib/northbound.h lib/northbound_cli.h lib/northbound_db.h \ lib/ns.h lib/openbsd-queue.h lib/openbsd-tree.h lib/plist.h \ lib/prefix.h lib/printfrr.h lib/privs.h lib/ptm_lib.h \ lib/pullwr.h lib/pw.h lib/qobj.h lib/queue.h lib/ringbuf.h \ lib/routemap.h lib/sbuf.h lib/seqlock.h lib/sha256.h \ lib/sigevent.h lib/skiplist.h lib/smux.h lib/sockopt.h \ lib/sockunion.h lib/spf_backoff.h lib/srcdest_table.h \ lib/stream.h lib/systemd.h lib/table.h lib/termtable.h \ lib/thread.h lib/typerb.h lib/typesafe.h lib/vector.h \ lib/vlan.h lib/vrf.h lib/vrf_int.h lib/vty.h lib/vxlan.h \ lib/wheel.h lib/workqueue.h lib/yang.h lib/yang_translator.h \ lib/yang_wrappers.h lib/zassert.h lib/zclient.h lib/zebra.h \ lib/pbr.h lib/resolver.h lib/frr_zmq.h HEADERS = $(eigrpdheader_HEADERS) $(nodist_pkginclude_HEADERS) \ $(noinst_HEADERS) $(ospfapiheader_HEADERS) \ $(ospfdheader_HEADERS) $(pkginclude_HEADERS) am__tagged_files = $(HEADERS) $(SOURCES) $(TAGS_FILES) \ $(LISP)config.h.in # Read a list of newline-separated strings from the standard input, # and print each of them once, without duplicates. Input order is # *not* preserved. am__uniquify_input = $(AWK) '\ BEGIN { nonempty = 0; } \ { items[$$0] = 1; nonempty = 1; } \ END { if (nonempty) { for (i in items) print i; }; } \ ' # Make sure the list of sources is unique. This is necessary because, # e.g., the same source file might be shared among _SOURCES variables # for different programs/libraries. am__define_uniq_tagged_files = \ list='$(am__tagged_files)'; \ unique=`for i in $$list; do \ if test -f "$$i"; then echo $$i; else echo $(srcdir)/$$i; fi; \ done | $(am__uniquify_input)` ETAGS = etags CTAGS = ctags CSCOPE = cscope AM_RECURSIVE_TARGETS = cscope am__DIST_COMMON = $(srcdir)/Makefile.in $(srcdir)/babeld/subdir.am \ $(srcdir)/bfdd/subdir.am \ $(srcdir)/bgpd/rfp-example/librfp/subdir.am \ $(srcdir)/bgpd/rfp-example/rfptest/subdir.am \ $(srcdir)/bgpd/subdir.am $(srcdir)/changelog-auto.in \ $(srcdir)/config.h.in $(srcdir)/config.version.in \ $(srcdir)/doc/developer/subdir.am \ $(srcdir)/doc/manpages/subdir.am $(srcdir)/doc/subdir.am \ $(srcdir)/doc/user/subdir.am $(srcdir)/eigrpd/subdir.am \ $(srcdir)/fpm/subdir.am $(srcdir)/grpc/subdir.am \ $(srcdir)/include/subdir.am $(srcdir)/isisd/subdir.am \ $(srcdir)/ldpd/subdir.am $(srcdir)/lib/subdir.am \ $(srcdir)/nhrpd/subdir.am $(srcdir)/ospf6d/subdir.am \ $(srcdir)/ospfclient/subdir.am $(srcdir)/ospfd/subdir.am \ $(srcdir)/pbrd/subdir.am $(srcdir)/pimd/subdir.am \ $(srcdir)/qpb/subdir.am $(srcdir)/ripd/subdir.am \ $(srcdir)/ripngd/subdir.am $(srcdir)/sharpd/subdir.am \ $(srcdir)/solaris/subdir.am $(srcdir)/staticd/subdir.am \ $(srcdir)/tests/subdir.am $(srcdir)/tests/topotests/subdir.am \ $(srcdir)/tools/subdir.am $(srcdir)/vrrpd/subdir.am \ $(srcdir)/vtysh/subdir.am $(srcdir)/watchfrr/subdir.am \ $(srcdir)/yang/libyang_plugins/subdir.am \ $(srcdir)/yang/subdir.am $(srcdir)/zebra/subdir.am \ $(top_srcdir)/alpine/APKBUILD.in \ $(top_srcdir)/lib/version.h.in $(top_srcdir)/pkgsrc/bgpd.sh.in \ $(top_srcdir)/pkgsrc/eigrpd.sh.in \ $(top_srcdir)/pkgsrc/ospf6d.sh.in \ $(top_srcdir)/pkgsrc/ospfd.sh.in \ $(top_srcdir)/pkgsrc/ripd.sh.in \ $(top_srcdir)/pkgsrc/ripngd.sh.in \ $(top_srcdir)/pkgsrc/zebra.sh.in \ $(top_srcdir)/redhat/frr.spec.in \ $(top_srcdir)/snapcraft/snapcraft.yaml.in \ $(top_srcdir)/solaris/Makefile.in \ $(top_srcdir)/tests/lib/cli/test_cli.refout.in \ $(top_srcdir)/tools/frr.in $(top_srcdir)/tools/frrcommon.sh.in \ $(top_srcdir)/tools/frrinit.sh.in \ $(top_srcdir)/tools/watchfrr.sh.in \ $(top_srcdir)/vtysh/extract.pl.in COPYING compile config.guess \ config.sub depcomp install-sh lib/command_lex.c \ lib/command_parse.c lib/command_parse.h lib/defun_lex.c \ ltmain.sh missing ylwrap DISTFILES = $(DIST_COMMON) $(DIST_SOURCES) $(TEXINFOS) $(EXTRA_DIST) distdir = $(PACKAGE)-$(VERSION) top_distdir = $(distdir) am__remove_distdir = \ if test -d "$(distdir)"; then \ find "$(distdir)" -type d ! -perm -200 -exec chmod u+w {} ';' \ && rm -rf "$(distdir)" \ || { sleep 5 && rm -rf "$(distdir)"; }; \ else :; fi am__post_remove_distdir = $(am__remove_distdir) DIST_ARCHIVES = $(distdir).tar.gz GZIP_ENV = --best DIST_TARGETS = dist-gzip distuninstallcheck_listfiles = find . -type f -print am__distuninstallcheck_listfiles = $(distuninstallcheck_listfiles) \ | sed 's|^\./|$(prefix)/|' | grep -v '$(infodir)/dir$$' distcleancheck_listfiles = find . -type f -print ACLOCAL = @ACLOCAL@ AC_LDFLAGS = @AC_LDFLAGS@ AMTAR = @AMTAR@ AM_DEFAULT_VERBOSITY = @AM_DEFAULT_VERBOSITY@ AR = @AR@ ARFLAGS = @ARFLAGS@ AR_FLAGS = @AR_FLAGS@ AUTOCONF = @AUTOCONF@ AUTOHEADER = @AUTOHEADER@ AUTOMAKE = @AUTOMAKE@ AWK = @AWK@ BISON_CLOSEBRACE = @BISON_CLOSEBRACE@ BISON_OPENBRACE = @BISON_OPENBRACE@ BISON_VERBOSE = @BISON_VERBOSE@ CARES_CFLAGS = @CARES_CFLAGS@ CARES_LIBS = @CARES_LIBS@ CC = @CC@ CCDEPMODE = @CCDEPMODE@ CFG_MODULE = @CFG_MODULE@ CFG_SBIN = @CFG_SBIN@ CFG_STATE = @CFG_STATE@ CFG_SYSCONF = @CFG_SYSCONF@ CFG_YANGMODELS = @CFG_YANGMODELS@ CFLAGS = @CFLAGS@ CLIPPY = @CLIPPY@ CONFD = @CONFD@ CONFDATE = @CONFDATE@ CONFD_CFLAGS = @CONFD_CFLAGS@ CONFD_LIBS = @CONFD_LIBS@ CONFIG_ARGS = @CONFIG_ARGS@ CPP = @CPP@ CPPFLAGS = @CPPFLAGS@ CXX = @CXX@ CXXCPP = @CXXCPP@ CXXDEPMODE = @CXXDEPMODE@ CXXFLAGS = @CXXFLAGS@ CXX_COMPAT_CFLAGS = @CXX_COMPAT_CFLAGS@ CYGPATH_W = @CYGPATH_W@ DEFS = @DEFS@ -DSYSCONFDIR=\"$(sysconfdir)/\" -DCONFDATE=$(CONFDATE) DEPDIR = @DEPDIR@ DFLT_NAME = @DFLT_NAME@ DLLTOOL = @DLLTOOL@ DSYMUTIL = @DSYMUTIL@ DUMPBIN = @DUMPBIN@ ECHO_C = @ECHO_C@ ECHO_N = @ECHO_N@ ECHO_T = @ECHO_T@ EGREP = @EGREP@ EXEEXT = @EXEEXT@ EXTRAVERSION = @EXTRAVERSION@ FGREP = @FGREP@ GREP = @GREP@ GRPC_CFLAGS = @GRPC_CFLAGS@ GRPC_LIBS = @GRPC_LIBS@ HAVE_LIBPCREPOSIX = @HAVE_LIBPCREPOSIX@ INSTALL = @INSTALL@ INSTALL_DATA = @INSTALL_DATA@ INSTALL_PROGRAM = @INSTALL_PROGRAM@ INSTALL_SCRIPT = @INSTALL_SCRIPT@ INSTALL_STRIP_PROGRAM = @INSTALL_STRIP_PROGRAM@ LD = @LD@ LDFLAGS = @LDFLAGS@ LEX = @LEX@ LEXLIB = @LEXLIB@ LEX_OUTPUT_ROOT = @LEX_OUTPUT_ROOT@ LIBCAP = @LIBCAP@ LIBM = @LIBM@ LIBOBJS = @LIBOBJS@ LIBPAM = @LIBPAM@ LIBREADLINE = @LIBREADLINE@ LIBS = @LIBS@ LIBTOOL = @LIBTOOL@ LIBYANG_CFLAGS = @LIBYANG_CFLAGS@ LIBYANG_LIBS = @LIBYANG_LIBS@ LIPO = @LIPO@ LN_S = @LN_S@ LTLIBOBJS = @LTLIBOBJS@ LT_SYS_LIBRARY_PATH = @LT_SYS_LIBRARY_PATH@ LUA = @LUA@ LUA_EXEC_PREFIX = @LUA_EXEC_PREFIX@ LUA_INCLUDE = @LUA_INCLUDE@ LUA_LIB = @LUA_LIB@ LUA_PLATFORM = @LUA_PLATFORM@ LUA_PREFIX = @LUA_PREFIX@ LUA_SHORT_VERSION = @LUA_SHORT_VERSION@ LUA_VERSION = @LUA_VERSION@ MAKEINFO = @MAKEINFO@ MANIFEST_TOOL = @MANIFEST_TOOL@ MKDIR_P = @MKDIR_P@ NETSNMP_CONFIG = @NETSNMP_CONFIG@ NM = @NM@ NMEDIT = @NMEDIT@ OBJCOPY = @OBJCOPY@ OBJDUMP = @OBJDUMP@ OBJEXT = @OBJEXT@ OTOOL = @OTOOL@ OTOOL64 = @OTOOL64@ PACKAGE = @PACKAGE@ PACKAGE_BUGREPORT = @PACKAGE_BUGREPORT@ PACKAGE_EXTRAVERSION = @PACKAGE_EXTRAVERSION@ PACKAGE_FULLNAME = @PACKAGE_FULLNAME@ PACKAGE_NAME = @PACKAGE_NAME@ PACKAGE_STRING = @PACKAGE_STRING@ PACKAGE_TARNAME = @PACKAGE_TARNAME@ PACKAGE_URL = @PACKAGE_URL@ PACKAGE_VERSION = @PACKAGE_VERSION@ PATH_SEPARATOR = @PATH_SEPARATOR@ PERL = @PERL@ PKG_CONFIG = @PKG_CONFIG@ PKG_CONFIG_LIBDIR = @PKG_CONFIG_LIBDIR@ PKG_CONFIG_PATH = @PKG_CONFIG_PATH@ PROTOBUF_C_CFLAGS = @PROTOBUF_C_CFLAGS@ PROTOBUF_C_LIBS = @PROTOBUF_C_LIBS@ PROTOC = @PROTOC@ PROTOC_C = @PROTOC_C@ PTHREAD_CC = @PTHREAD_CC@ PTHREAD_CFLAGS = @PTHREAD_CFLAGS@ PTHREAD_LIBS = @PTHREAD_LIBS@ PYSPHINX = @PYSPHINX@ PYTHON = @PYTHON@ PYTHON_CFLAGS = @PYTHON_CFLAGS@ PYTHON_LIBS = @PYTHON_LIBS@ RANLIB = @RANLIB@ RTRLIB_CFLAGS = @RTRLIB_CFLAGS@ RTRLIB_LIBS = @RTRLIB_LIBS@ SAN_FLAGS = @SAN_FLAGS@ SED = @SED@ SET_MAKE = @SET_MAKE@ SHELL = @SHELL@ SNMP_CFLAGS = @SNMP_CFLAGS@ SNMP_LIBS = @SNMP_LIBS@ SOLARIS = @SOLARIS@ SQLITE3_CFLAGS = @SQLITE3_CFLAGS@ SQLITE3_LIBS = @SQLITE3_LIBS@ STRIP = @STRIP@ SYSREPO_CFLAGS = @SYSREPO_CFLAGS@ SYSREPO_LIBS = @SYSREPO_LIBS@ UNWIND_CFLAGS = @UNWIND_CFLAGS@ UNWIND_LIBS = @UNWIND_LIBS@ VERSION = @VERSION@ WERROR = @WERROR@ YACC = @YACC@ YFLAGS = @YFLAGS@ ZEROMQ_CFLAGS = @ZEROMQ_CFLAGS@ ZEROMQ_LIBS = @ZEROMQ_LIBS@ abs_builddir = @abs_builddir@ abs_srcdir = @abs_srcdir@ abs_top_builddir = @abs_top_builddir@ abs_top_srcdir = @abs_top_srcdir@ ac_ct_AR = @ac_ct_AR@ ac_ct_CC = @ac_ct_CC@ ac_ct_CXX = @ac_ct_CXX@ ac_ct_DUMPBIN = @ac_ct_DUMPBIN@ am__include = @am__include@ am__leading_dot = @am__leading_dot@ am__quote = @am__quote@ am__tar = @am__tar@ am__untar = @am__untar@ ax_pthread_config = @ax_pthread_config@ bindir = @bindir@ build = @build@ build_alias = @build_alias@ build_cpu = @build_cpu@ build_os = @build_os@ build_vendor = @build_vendor@ builddir = @builddir@ datadir = @datadir@ datarootdir = @datarootdir@ docdir = @docdir@ dvidir = @dvidir@ enable_group = @enable_group@ enable_user = @enable_user@ enable_vty_group = @enable_vty_group@ exampledir = @exampledir@ exec_prefix = @exec_prefix@ frr_statedir = @frr_statedir@ host = @host@ host_alias = @host_alias@ host_cpu = @host_cpu@ host_os = @host_os@ host_vendor = @host_vendor@ htmldir = @htmldir@ includedir = @includedir@ infodir = @infodir@ install_sh = @install_sh@ libdir = @libdir@ libexecdir = @libexecdir@ localedir = @localedir@ localstatedir = @localstatedir@ luadir = @luadir@ luaexecdir = @luaexecdir@ mandir = @mandir@ mkdir_p = @mkdir_p@ moduledir = @moduledir@ oldincludedir = @oldincludedir@ pdfdir = @pdfdir@ pkgluadir = @pkgluadir@ pkgluaexecdir = @pkgluaexecdir@ pkgsrcrcdir = @pkgsrcrcdir@ prefix = @prefix@ program_transform_name = @program_transform_name@ psdir = @psdir@ runstatedir = @runstatedir@ sbindir = @sbindir@ sharedstatedir = @sharedstatedir@ srcdir = @srcdir@ sysconfdir = @sysconfdir@ target_alias = @target_alias@ top_build_prefix = @top_build_prefix@ top_builddir = @top_builddir@ top_srcdir = @top_srcdir@ vtysh_bin = @vtysh_bin@ yangmodelsdir = @yangmodelsdir@ AUTOMAKE_OPTIONS = subdir-objects 1.12 ACLOCAL_AMFLAGS = -I m4 AM_CFLAGS = \ $(LIBYANG_CFLAGS) \ $(SQLITE3_CFLAGS) \ $(UNWIND_CFLAGS) \ $(SAN_FLAGS) \ $(WERROR) \ # end AM_CPPFLAGS = \ -I$(top_srcdir) -I$(top_srcdir)/include -I$(top_srcdir)/lib \ -I$(top_builddir) -I$(top_builddir)/include -I$(top_builddir)/lib \ $(LUA_INCLUDE) \ # end AM_LDFLAGS = \ -export-dynamic \ $(AC_LDFLAGS) \ $(SAN_FLAGS) \ # end @ONLY_CLIPPY_TRUE@.DEFAULT_GOAL := clippy-only # overwriting these vars breaks cross-compilation. let's be helpful and warn. # # note: "#AUTODERP# " will be removed from Makefile by configure. These are # GNU make directives & automake will f*ck them up by trying to process them # as automake directives. # #AUTODERP# null= #AUTODERP# SPACE=$(null) $(null) #AUTODERP# mkcheck_CC = $(findstring $(SPACE)CC=, $(SPACE)$(MAKEOVERRIDES)) #AUTODERP# mkcheck_CFLAGS = $(findstring $(SPACE)CFLAGS=, $(SPACE)$(MAKEOVERRIDES)) #AUTODERP# mkcheck_CPPFLAGS = $(findstring $(SPACE)CPPFLAGS=,$(SPACE)$(MAKEOVERRIDES)) #AUTODERP# mkcheck_CCLD = $(findstring $(SPACE)CCLD=, $(SPACE)$(MAKEOVERRIDES)) #AUTODERP# mkcheck_LD = $(findstring $(SPACE)LD=, $(SPACE)$(MAKEOVERRIDES)) #AUTODERP# mkcheck_LDFLAGS = $(findstring $(SPACE)LDFLAGS=, $(SPACE)$(MAKEOVERRIDES)) #AUTODERP# # #AUTODERP# ifneq ($(mkcheck_CC),) #AUTODERP# $(warning WARNING: you have overwritten the "CC" variable on the make command line.) #AUTODERP# endif #AUTODERP# ifneq ($(mkcheck_CFLAGS),) #AUTODERP# $(warning WARNING: you have overwritten the "CFLAGS" variable on the make command line.) #AUTODERP# endif #AUTODERP# ifneq ($(mkcheck_CPPFLAGS),) #AUTODERP# $(warning WARNING: you have overwritten the "CPPFLAGS" variable on the make command line.) #AUTODERP# endif #AUTODERP# ifneq ($(mkcheck_CCLD),) #AUTODERP# $(warning WARNING: you have overwritten the "CCLD" variable on the make command line.) #AUTODERP# endif #AUTODERP# ifneq ($(mkcheck_LD),) #AUTODERP# $(warning WARNING: you have overwritten the "LD" variable on the make command line.) #AUTODERP# endif #AUTODERP# ifneq ($(mkcheck_LDFLAGS),) #AUTODERP# $(warning WARNING: you have overwritten the "LDFLAGS" variable on the make command line.) #AUTODERP# endif #AUTODERP# # #AUTODERP# ifneq ($(mkcheck_CC)$(mkcheck_CFLAGS)$(mkcheck_CPPFLAGS)$(mkcheck_CCLD)$(mkcheck_LD)$(mkcheck_LDFLAGS),) #AUTODERP# $(warning ------) #AUTODERP# $(warning While overwriting these variables works most of the time, it is not recommended and can cause confusing build errors.) #AUTODERP# $(warning This is especially problematic when cross-compiling, since tools that run on the build system during the build process will not be compiled correctly.) #AUTODERP# $(warning All of these variables should be supplied to 'configure', and they will be remembered and correctly applied during 'make'.) #AUTODERP# $(warning ------) #AUTODERP# endif # If you want to build the developer's docs in other formats, try the # following: # # $ cd developer # $ make help # dist tarballs want doc sources # # generated sources & extra foo # EXTRA_DIST = doc/mpls/ChangeLog.opaque.txt doc/mpls/ospfd.conf \ doc/mpls/cli_summary.txt doc/mpls/opaque_lsa.txt \ doc/figures/cligraph.png doc/figures/cligraph.svg \ doc/figures/fig-normal-processing.dia \ doc/figures/fig-normal-processing.png \ doc/figures/fig-normal-processing.txt \ doc/figures/fig-rs-processing.dia \ doc/figures/fig-rs-processing.png \ doc/figures/fig-rs-processing.txt \ doc/figures/fig_topologies_full.dia \ doc/figures/fig_topologies_full.png \ doc/figures/fig_topologies_full.txt \ doc/figures/fig_topologies_rs.dia \ doc/figures/fig_topologies_rs.png \ doc/figures/fig_topologies_rs.txt \ doc/figures/fig-vnc-commercial-route-reflector.dia \ doc/figures/fig-vnc-commercial-route-reflector.png \ doc/figures/fig-vnc-commercial-route-reflector.txt \ doc/figures/fig-vnc-frr-route-reflector.dia \ doc/figures/fig-vnc-frr-route-reflector.png \ doc/figures/fig-vnc-frr-route-reflector.txt \ doc/figures/fig-vnc-gw.dia doc/figures/fig-vnc-gw.png \ doc/figures/fig-vnc-gw-rr.dia doc/figures/fig-vnc-gw-rr.png \ doc/figures/fig-vnc-gw-rr.txt doc/figures/fig-vnc-gw.txt \ doc/figures/fig-vnc-mesh.dia doc/figures/fig-vnc-mesh.png \ doc/figures/fig-vnc-mesh.txt \ doc/figures/fig-vnc-redundant-route-reflectors.dia \ doc/figures/fig-vnc-redundant-route-reflectors.png \ doc/figures/fig-vnc-redundant-route-reflectors.txt \ doc/figures/frr-icon.svg doc/figures/frr-logo-icon.png \ doc/figures/frr-logo-medium.png doc/figures/frr-logo.png \ doc/figures/frr-logo-small.png doc/figures/git_branches.png \ doc/figures/git_branches.svg \ doc/figures/ospf_api_architecture.png \ doc/figures/ospf_api_msghdr.png doc/figures/ospf_api_msgs1.png \ doc/figures/ospf_api_msgs2.png doc/extra/frrlexer.py \ $(user_RSTFILES) doc/user/Useful_Sysctl_Settings.md \ $(man_RSTFILES) $(dev_RSTFILES) \ doc/developer/draft-zebra-00.ms \ doc/developer/ldpd-basic-test-setup.md lib/command_lex.h \ lib/command_parse.h lib/gitversion.pl lib/route_types.pl \ lib/route_types.txt qpb/qpb.proto fpm/fpm.proto \ grpc/frr-northbound.proto tools/etc tools/frr-reload \ tools/frr-reload.py tools/frr.service tools/multiple-bgpd.sh \ tools/rrcheck.pl tools/rrlookup.pl tools/zc.pl tools/zebra.el \ tools/build-debian-package.sh solaris/frr.xml.in \ solaris/frr.init.in solaris/pkginfo.tmpl.in \ solaris/prototype.daemons.in solaris/prototype.dev.in \ solaris/prototype.doc.in solaris/prototype.libs.in \ solaris/prototype.smf.in solaris/pkginfo.daemons.tmpl.in \ solaris/pkginfo.dev.tmpl.in solaris/pkginfo.doc.tmpl.in \ solaris/pkginfo.libs.tmpl.in solaris/pkginfo.smf.tmpl.in \ solaris/depend.daemons.in solaris/depend.dev.in \ solaris/depend.doc.in solaris/depend.libs.in \ solaris/depend.smf.in solaris/README.txt \ ospfd/ChangeLog.opaque.txt yang/embedmodel.py \ tests/runtests.py tests/bgpd/test_aspath.py \ tests/bgpd/test_capability.py tests/bgpd/test_ecommunity.py \ tests/bgpd/test_mp_attr.py tests/bgpd/test_mpath.py \ tests/bgpd/test_peer_attr.py tests/helpers/python/frrsix.py \ tests/helpers/python/frrtest.py \ tests/isisd/test_fuzz_isis_tlv.py \ tests/isisd/test_fuzz_isis_tlv_tests.h.gz \ tests/isisd/test_isis_lspdb.py \ tests/isisd/test_isis_vertex_queue.py \ tests/lib/cli/test_commands.in tests/lib/cli/test_commands.py \ tests/lib/cli/test_commands.refout tests/lib/cli/test_cli.in \ tests/lib/cli/test_cli.py tests/lib/cli/test_cli.refout \ tests/lib/northbound/test_oper_data.in \ tests/lib/northbound/test_oper_data.py \ tests/lib/northbound/test_oper_data.refout \ tests/lib/test_atomlist.py tests/lib/test_nexthop_iter.py \ tests/lib/test_ntop.py tests/lib/test_prefix2str.py \ tests/lib/test_printfrr.py tests/lib/test_ringbuf.py \ tests/lib/test_srcdest_table.py tests/lib/test_stream.py \ tests/lib/test_stream.refout tests/lib/test_table.py \ tests/lib/test_timer_correctness.py tests/lib/test_ttable.py \ tests/lib/test_ttable.refout tests/lib/test_typelist.py \ tests/lib/test_zlog.py tests/lib/test_graph.py \ tests/lib/test_graph.refout tests/ospf6d/test_lsdb.py \ tests/ospf6d/test_lsdb.in tests/ospf6d/test_lsdb.refout \ aclocal.m4 README.md m4/README.txt \ m4/libtool-whole-archive.patch config.version changelog-auto \ changelog-auto.in python/clidef.py python/clippy/__init__.py \ redhat/frr.logrotate redhat/frr.pam redhat/frr.spec \ snapcraft/snapcraft.yaml snapcraft/README.snap_build.md \ snapcraft/README.usage.md snapcraft/extra_version_info.txt \ snapcraft/scripts snapcraft/defaults snapcraft/helpers \ snapcraft/snap babeld/Makefile bgpd/Makefile \ bgpd/rfp-example/librfp/Makefile \ bgpd/rfp-example/rfptest/Makefile doc/Makefile \ doc/developer/Makefile doc/manpages/Makefile doc/user/Makefile \ eigrpd/Makefile fpm/Makefile grpc/Makefile isisd/Makefile \ ldpd/Makefile lib/Makefile nhrpd/Makefile ospf6d/Makefile \ ospfclient/Makefile ospfd/Makefile pbrd/Makefile pimd/Makefile \ qpb/Makefile ripd/Makefile ripngd/Makefile staticd/Makefile \ tests/Makefile tools/Makefile vtysh/Makefile watchfrr/Makefile \ zebra/Makefile vrrpd/Makefile # end BUILT_SOURCES = lib/gitversion.h lib/route_types.h # end CLEANFILES = qpb/qpb.pb-c.c qpb/qpb.pb-c.h fpm/fpm.pb-c.c \ fpm/fpm.pb-c.h grpc/frr-northbound.pb.cc \ grpc/frr-northbound.pb.h grpc/frr-northbound.grpc.pb.cc \ grpc/frr-northbound.grpc.pb.h solaris/frr.xml solaris/frr.init \ solaris/pkginfo.tmpl solaris/prototype.daemons \ solaris/prototype.dev solaris/prototype.doc \ solaris/prototype.libs solaris/prototype.smf \ solaris/pkginfo.daemons.tmpl solaris/pkginfo.dev.tmpl \ solaris/pkginfo.doc.tmpl solaris/pkginfo.libs.tmpl \ solaris/pkginfo.smf.tmpl solaris/depend.daemons \ solaris/depend.dev solaris/depend.doc solaris/depend.libs \ solaris/depend.smf vtysh/vtysh_cmd.c \ tests/lib/cli/test_commands_defun.c \ tests/isisd/test_fuzz_isis_tlv_tests.h DISTCLEANFILES = lib/route_types.h examplesdir = $(exampledir) sbin_SCRIPTS = tools/frr-reload tools/frr-reload.py tools/frr \ tools/frrcommon.sh tools/frrinit.sh tools/watchfrr.sh # end noinst_HEADERS = include/linux/if_addr.h include/linux/if_bridge.h \ include/linux/if_link.h include/linux/lwtunnel.h \ include/linux/mpls_iptunnel.h include/linux/neighbour.h \ include/linux/netlink.h include/linux/rtnetlink.h \ include/linux/socket.h include/linux/net_namespace.h \ include/linux/fib_rules.h lib/clippy.h lib/log_int.h \ lib/plist_int.h lib/printf/printfcommon.h \ lib/printf/printflocal.h zebra/connected.h zebra/debug.h \ zebra/if_netlink.h zebra/interface.h zebra/ioctl.h \ zebra/ioctl_solaris.h zebra/ipforward.h zebra/irdp.h \ zebra/kernel_netlink.h zebra/kernel_socket.h \ zebra/label_manager.h zebra/redistribute.h zebra/rib.h \ zebra/router-id.h zebra/rt.h zebra/rt_netlink.h zebra/rtadv.h \ zebra/rule_netlink.h zebra/zebra_mlag.h \ zebra/zebra_fpm_private.h zebra/zebra_l2.h \ zebra/zebra_dplane.h zebra/zebra_memory.h zebra/zebra_mpls.h \ zebra/zebra_mroute.h zebra/zebra_nhg.h zebra/zebra_ns.h \ zebra/zebra_pbr.h zebra/zebra_ptm.h \ zebra/zebra_ptm_redistribute.h zebra/zebra_pw.h \ zebra/zebra_rnh.h zebra/zebra_routemap.h zebra/zebra_router.h \ zebra/zebra_vrf.h zebra/zebra_vxlan.h \ zebra/zebra_vxlan_private.h zebra/zserv.h \ zebra/zebra_netns_id.h zebra/zebra_netns_notify.h \ zebra/table_manager.h zebra/zapi_msg.h zebra/zebra_errors.h \ watchfrr/watchfrr.h watchfrr/watchfrr_errors.h \ qpb/linear_allocator.h qpb/qpb.h qpb/qpb_allocator.h \ bgpd/bgp_addpath.h bgpd/bgp_addpath_types.h \ bgpd/bgp_advertise.h bgpd/bgp_aspath.h bgpd/bgp_attr.h \ bgpd/bgp_attr_evpn.h bgpd/bgp_bfd.h bgpd/bgp_clist.h \ bgpd/bgp_community.h bgpd/bgp_damp.h bgpd/bgp_debug.h \ bgpd/bgp_dump.h bgpd/bgp_bmp.h bgpd/bgp_ecommunity.h \ bgpd/bgp_encap_tlv.h bgpd/bgp_encap_types.h bgpd/bgp_errors.h \ bgpd/bgp_evpn.h bgpd/bgp_evpn_private.h bgpd/bgp_evpn_vty.h \ bgpd/bgp_filter.h bgpd/bgp_flowspec.h \ bgpd/bgp_flowspec_private.h bgpd/bgp_flowspec_util.h \ bgpd/bgp_fsm.h bgpd/bgp_io.h bgpd/bgp_keepalives.h \ bgpd/bgp_label.h bgpd/bgp_labelpool.h bgpd/bgp_lcommunity.h \ bgpd/bgp_mac.h bgpd/bgp_memory.h bgpd/bgp_mpath.h \ bgpd/bgp_mplsvpn.h bgpd/bgp_network.h bgpd/bgp_nexthop.h \ bgpd/bgp_nht.h bgpd/bgp_open.h bgpd/bgp_packet.h \ bgpd/bgp_pbr.h bgpd/bgp_rd.h bgpd/bgp_regex.h bgpd/bgp_route.h \ bgpd/bgp_table.h bgpd/bgp_updgrp.h bgpd/bgp_vpn.h \ bgpd/bgp_vty.h bgpd/bgp_zebra.h bgpd/bgpd.h \ bgpd/rfapi/bgp_rfapi_cfg.h bgpd/rfapi/rfapi_import.h \ bgpd/rfapi/rfapi.h bgpd/rfapi/rfapi_ap.h \ bgpd/rfapi/rfapi_backend.h \ bgpd/rfapi/rfapi_descriptor_rfp_utils.h \ bgpd/rfapi/rfapi_encap_tlv.h bgpd/rfapi/rfapi_nve_addr.h \ bgpd/rfapi/rfapi_monitor.h bgpd/rfapi/rfapi_private.h \ bgpd/rfapi/rfapi_rib.h bgpd/rfapi/rfapi_vty.h \ bgpd/rfapi/vnc_debug.h bgpd/rfapi/vnc_export_bgp.h \ bgpd/rfapi/vnc_export_table.h bgpd/rfapi/vnc_import_bgp.h \ bgpd/rfapi/vnc_zebra.h bgpd/rfapi/vnc_export_bgp_p.h \ bgpd/rfapi/vnc_import_bgp_p.h bgpd/bgp_vnc_types.h \ bgpd/rfp-example/librfp/rfp.h \ bgpd/rfp-example/librfp/rfp_internal.h \ bgpd/rfp-example/rfptest/rfptest.h ripd/rip_cli.h \ ripd/rip_debug.h ripd/rip_errors.h ripd/rip_interface.h \ ripd/ripd.h ripngd/ripng_cli.h ripngd/ripng_debug.h \ ripngd/ripng_nexthop.h ripngd/ripng_route.h ripngd/ripngd.h \ ospfd/ospf_abr.h ospfd/ospf_apiserver.h ospfd/ospf_ase.h \ ospfd/ospf_bfd.h ospfd/ospf_errors.h ospfd/ospf_ext.h \ ospfd/ospf_flood.h ospfd/ospf_ia.h ospfd/ospf_interface.h \ ospfd/ospf_memory.h ospfd/ospf_neighbor.h ospfd/ospf_network.h \ ospfd/ospf_packet.h ospfd/ospf_ri.h ospfd/ospf_route.h \ ospfd/ospf_spf.h ospfd/ospf_sr.h ospfd/ospf_te.h \ ospfd/ospf_vty.h ospfd/ospf_zebra.h ospf6d/ospf6_abr.h \ ospf6d/ospf6_area.h ospf6d/ospf6_asbr.h ospf6d/ospf6_bfd.h \ ospf6d/ospf6_flood.h ospf6d/ospf6_interface.h \ ospf6d/ospf6_intra.h ospf6d/ospf6_lsa.h ospf6d/ospf6_lsdb.h \ ospf6d/ospf6_memory.h ospf6d/ospf6_message.h \ ospf6d/ospf6_neighbor.h ospf6d/ospf6_network.h \ ospf6d/ospf6_proto.h ospf6d/ospf6_route.h ospf6d/ospf6_spf.h \ ospf6d/ospf6_top.h ospf6d/ospf6_zebra.h ospf6d/ospf6d.h \ isisd/isis_adjacency.h isisd/isis_bfd.h isisd/isis_circuit.h \ isisd/isis_common.h isisd/isis_constants.h isisd/isis_csm.h \ isisd/isis_dr.h isisd/isis_dynhn.h isisd/isis_errors.h \ isisd/isis_events.h isisd/isis_flags.h isisd/isis_lsp.h \ isisd/isis_memory.h isisd/isis_misc.h isisd/isis_mt.h \ isisd/isis_network.h isisd/isis_pdu.h isisd/isis_pdu_counter.h \ isisd/isis_redist.h isisd/isis_route.h isisd/isis_routemap.h \ isisd/isis_spf.h isisd/isis_spf_private.h isisd/isis_te.h \ isisd/isis_tlvs.h isisd/isis_tx_queue.h isisd/isis_zebra.h \ isisd/isisd.h isisd/iso_checksum.h isisd/fabricd.h \ isisd/isis_cli.h nhrpd/debug.h nhrpd/list.h nhrpd/netlink.h \ nhrpd/nhrp_errors.h nhrpd/nhrp_protocol.h nhrpd/nhrpd.h \ nhrpd/os.h nhrpd/vici.h nhrpd/zbuf.h nhrpd/znl.h \ ldpd/control.h ldpd/lde.h ldpd/ldp.h ldpd/ldp_debug.h \ ldpd/ldp_vty.h ldpd/ldpd.h ldpd/ldpe.h ldpd/log.h \ babeld/babel_errors.h babeld/babel_filter.h \ babeld/babel_interface.h babeld/babel_main.h \ babeld/babel_zebra.h babeld/babeld.h babeld/kernel.h \ babeld/message.h babeld/neighbour.h babeld/net.h \ babeld/resend.h babeld/route.h babeld/source.h babeld/util.h \ babeld/xroute.h eigrpd/eigrp_const.h eigrpd/eigrp_errors.h \ eigrpd/eigrp_filter.h eigrpd/eigrp_fsm.h \ eigrpd/eigrp_interface.h eigrpd/eigrp_macros.h \ eigrpd/eigrp_memory.h eigrpd/eigrp_neighbor.h \ eigrpd/eigrp_network.h eigrpd/eigrp_packet.h \ eigrpd/eigrp_snmp.h eigrpd/eigrp_structs.h eigrpd/eigrp_vrf.h \ eigrpd/eigrp_vty.h eigrpd/eigrp_zebra.h sharpd/sharp_nht.h \ sharpd/sharp_vty.h sharpd/sharp_globals.h sharpd/sharp_zebra.h \ pimd/pim_assert.h pimd/pim_bfd.h pimd/pim_br.h pimd/pim_bsm.h \ pimd/pim_cmd.h pimd/pim_errors.h pimd/pim_hello.h \ pimd/pim_iface.h pimd/pim_ifchannel.h pimd/pim_igmp.h \ pimd/pim_igmp_join.h pimd/pim_igmp_mtrace.h \ pimd/pim_igmp_stats.h pimd/pim_igmpv2.h pimd/pim_igmpv3.h \ pimd/pim_instance.h pimd/pim_int.h pimd/pim_join.h \ pimd/pim_jp_agg.h pimd/pim_macro.h pimd/pim_memory.h \ pimd/pim_mroute.h pimd/pim_msdp.h pimd/pim_msdp_packet.h \ pimd/pim_msdp_socket.h pimd/pim_msg.h pimd/pim_neighbor.h \ pimd/pim_nht.h pimd/pim_oil.h pimd/pim_pim.h \ pimd/pim_register.h pimd/pim_rp.h pimd/pim_rpf.h \ pimd/pim_signals.h pimd/pim_sock.h pimd/pim_ssm.h \ pimd/pim_ssmpingd.h pimd/pim_static.h pimd/pim_str.h \ pimd/pim_time.h pimd/pim_tlv.h pimd/pim_upstream.h \ pimd/pim_util.h pimd/pim_version.h pimd/pim_vty.h \ pimd/pim_zebra.h pimd/pim_zlookup.h pimd/pim_vxlan.h \ pimd/pim_vxlan_instance.h pimd/pimd.h pimd/mtracebis_netlink.h \ pimd/mtracebis_routeget.h pbrd/pbr_map.h pbrd/pbr_memory.h \ pbrd/pbr_nht.h pbrd/pbr_vty.h pbrd/pbr_zebra.h \ pbrd/pbr_debug.h staticd/static_memory.h staticd/static_nht.h \ staticd/static_zebra.h staticd/static_routes.h \ staticd/static_vty.h staticd/static_vrf.h bfdd/bfdctl.h \ bfdd/bfd.h vrrpd/vrrp.h vrrpd/vrrp_arp.h vrrpd/vrrp_debug.h \ vrrpd/vrrp_ndisc.h vrrpd/vrrp_packet.h vrrpd/vrrp_vty.h \ vrrpd/vrrp_zebra.h vtysh/vtysh.h vtysh/vtysh_user.h \ tests/helpers/c/prng.h tests/helpers/c/tests.h \ tests/lib/cli/common_cli.h tests/lib/test_typelist.h \ defaults.h noinst_LIBRARIES = $(am__append_32) $(am__append_47) $(am__append_49) \ $(am__append_55) $(am__append_59) $(am__append_65) \ $(am__append_74) $(am__append_79) $(am__append_85) \ $(am__append_90) $(am__append_94) $(am__append_99) \ $(am__append_104) $(am__append_111) $(am__append_116) \ $(am__append_121) $(am__append_131) nodist_noinst_DATA = $(am__append_1) $(am__append_2) # # libfrr # lib_LTLIBRARIES = lib/libfrr.la $(am__append_7) $(am__append_8) \ $(am__append_10) $(am__append_29) $(am__append_30) \ $(am__append_31) $(am__append_71) module_LTLIBRARIES = $(am__append_12) $(am__append_13) \ $(am__append_14) $(am__append_19) $(am__append_20) \ $(am__append_21) $(am__append_38) $(am__append_39) \ $(am__append_40) $(am__append_53) $(am__append_63) \ $(am__append_69) pkginclude_HEADERS = lib/agg_table.h lib/atomlist.h lib/bfd.h \ lib/bitfield.h lib/buffer.h lib/checksum.h lib/mlag.h \ lib/command.h lib/command_graph.h lib/command_match.h \ lib/compiler.h lib/csv.h lib/db.h lib/debug.h lib/distribute.h \ lib/ferr.h lib/filter.h lib/freebsd-queue.h lib/frrlua.h \ lib/frr_pthread.h lib/frratomic.h lib/frrcu.h lib/frrstr.h \ lib/getopt.h lib/graph.h lib/hash.h lib/hook.h lib/iana_afi.h \ lib/id_alloc.h lib/if.h lib/if_rmap.h lib/imsg.h lib/ipaddr.h \ lib/jhash.h lib/json.h lib/keychain.h lib/lib_errors.h \ lib/libfrr.h lib/libospf.h lib/linklist.h lib/log.h \ lib/log_vty.h lib/md5.h lib/memory.h lib/memory_vty.h \ lib/module.h lib/monotime.h lib/mpls.h lib/network.h \ lib/nexthop.h lib/nexthop_group.h lib/nexthop_group_private.h \ lib/northbound.h lib/northbound_cli.h lib/northbound_db.h \ lib/ns.h lib/openbsd-queue.h lib/openbsd-tree.h lib/plist.h \ lib/prefix.h lib/printfrr.h lib/privs.h lib/ptm_lib.h \ lib/pullwr.h lib/pw.h lib/qobj.h lib/queue.h lib/ringbuf.h \ lib/routemap.h lib/sbuf.h lib/seqlock.h lib/sha256.h \ lib/sigevent.h lib/skiplist.h lib/smux.h lib/sockopt.h \ lib/sockunion.h lib/spf_backoff.h lib/srcdest_table.h \ lib/stream.h lib/systemd.h lib/table.h lib/termtable.h \ lib/thread.h lib/typerb.h lib/typesafe.h lib/vector.h \ lib/vlan.h lib/vrf.h lib/vrf_int.h lib/vty.h lib/vxlan.h \ lib/wheel.h lib/workqueue.h lib/yang.h lib/yang_translator.h \ lib/yang_wrappers.h lib/zassert.h lib/zclient.h lib/zebra.h \ lib/pbr.h $(am__append_9) $(am__append_11) nodist_pkginclude_HEADERS = lib/route_types.h lib/version.h # end dist_examples_DATA = $(am__append_17) $(am__append_35) \ $(am__append_51) ripngd/ripngd.conf.sample $(am__append_61) \ $(am__append_67) $(am__append_76) $(am__append_81) \ $(am__append_87) $(am__append_92) $(am__append_96) \ $(am__append_101) $(am__append_108) $(am__append_113) \ $(am__append_118) $(am__append_123) $(am__append_136) # use .yang.c files like this: # # ripd_ripd_SOURCES = \ # ... # nodist_ripd_ripd_SOURCES = \ # yang/frr-ripd.yang.c \ # # end # # Note that putting the .yang.c file into a static library.a will NOT work # because the entire file is "optimized out" since it does not contain any # global symbols :(. Just put it in the daemon. Dynamic libraries.so work # without problems, as seen in libfrr. dist_yangmodels_DATA = yang/frr-module-translator.yang \ yang/frr-test-module.yang yang/frr-interface.yang \ yang/frr-route-types.yang $(am__append_126) $(am__append_127) \ $(am__append_128) $(am__append_129) $(am__append_130) man_MANS = # can be loaded as DSO - always include for vtysh vtysh_scan = $(top_srcdir)/lib/distribute.c $(top_srcdir)/lib/filter.c \ $(top_srcdir)/lib/if.c $(top_srcdir)/lib/if_rmap.c \ $(top_srcdir)/lib/keychain.c $(top_srcdir)/lib/nexthop_group.c \ $(top_srcdir)/lib/plist.c $(top_srcdir)/lib/routemap.c \ $(top_srcdir)/lib/vrf.c $(top_srcdir)/lib/vty.c \ $(top_srcdir)/lib/agentx.c $(am__append_18) $(am__append_27) \ $(am__append_36) $(am__append_37) $(am__append_52) \ $(am__append_57) $(am__append_62) $(am__append_68) \ $(am__append_77) $(am__append_83) $(am__append_88) \ $(am__append_93) $(am__append_97) $(am__append_102) \ $(am__append_109) $(am__append_114) $(am__append_119) \ $(am__append_124) $(am__append_133) # Internal variables. PAPEROPT_a4 = -D latex_paper_size=a4 PAPEROPT_letter = -D latex_paper_size=letter ALLSPHINXOPTS = $(PAPEROPT_$(PAPER)) $(SPHINXOPTS) # the i18n builder cannot share the environment and doctrees with the others I18NSPHINXOPTS = $(PAPEROPT_$(PAPER)) $(SPHINXOPTS) ### AM_V_SPHINX = $(am__v_SPHINX_$(V)) am__v_SPHINX_ = $(am__v_SPHINX_$(AM_DEFAULT_VERBOSITY)) am__v_SPHINX_0 = @echo " SPHINX " $@; am__v_SPHINX_1 = AM_V_MAKEINFO = $(am__v_MAKEINFO_$(V)) am__v_MAKEINFO_ = $(am__v_MAKEINFO_$(AM_DEFAULT_VERBOSITY)) am__v_MAKEINFO_0 = @echo " MAKEINFO" $@; am__v_MAKEINFO_1 = # # auxiliary sphinx targets (output name = directory, # deps will not work very well) # SPHINXTARGETS = \ html dirhtml singlehtml pickle json \ htmlhelp qthelp applehelp devhelp \ epub latex text man texinfo gettext \ changes linkcheck doctest coverage \ xml pseudoxml \ # end M_SPHINXTARGETS = $(addprefix doc/%/_build/,$(SPHINXTARGETS)) user_RSTFILES = \ doc/user/babeld.rst \ doc/user/ldpd.rst \ doc/user/basic.rst \ doc/user/bgp.rst \ doc/user/bmp.rst \ doc/user/bugs.rst \ doc/user/conf.py \ doc/user/eigrpd.rst \ doc/user/fabricd.rst \ doc/user/filter.rst \ doc/user/glossary.rst \ doc/user/index.rst \ doc/user/installation.rst \ doc/user/ipv6.rst \ doc/user/isisd.rst \ doc/user/kernel.rst \ doc/user/nhrpd.rst \ doc/user/ospf6d.rst \ doc/user/ospfd.rst \ doc/user/ospf_fundamentals.rst \ doc/user/overview.rst \ doc/user/packet-dumps.rst \ doc/user/pim.rst \ doc/user/ripd.rst \ doc/user/pbr.rst \ doc/user/ripngd.rst \ doc/user/routemap.rst \ doc/user/routeserver.rst \ doc/user/rpki.rst \ doc/user/setup.rst \ doc/user/sharp.rst \ doc/user/snmp.rst \ doc/user/snmptrap.rst \ doc/user/static.rst \ doc/user/vnc.rst \ doc/user/vrrp.rst \ doc/user/vtysh.rst \ doc/user/zebra.rst \ doc/user/bfd.rst \ doc/user/flowspec.rst \ # end USERBUILD = doc/user/_build @DOC_TRUE@DOC_INFO = info @DOC_TRUE@TARGET_INSTALL_INFO = install-info @DOC_TRUE@TARGET_UNINSTALL_INFO = uninstall-info @DOC_HTML_TRUE@DOC_HTML = html @DOC_HTML_TRUE@TARGET_INSTALL_HTML = install-html @DOC_HTML_TRUE@TARGET_UNINSTALL_HTML = uninstall-html man_RSTFILES = \ doc/manpages/bfd-options.rst \ doc/manpages/common-options.rst \ doc/manpages/conf.py \ doc/manpages/defines.rst \ doc/manpages/epilogue.rst \ doc/manpages/frr-bfdd.rst \ doc/manpages/frr-bgpd.rst \ doc/manpages/frr-eigrpd.rst \ doc/manpages/frr-fabricd.rst \ doc/manpages/frr-isisd.rst \ doc/manpages/frr-ldpd.rst \ doc/manpages/frr-nhrpd.rst \ doc/manpages/frr-ospf6d.rst \ doc/manpages/frr-ospfclient.rst \ doc/manpages/frr-ospfd.rst \ doc/manpages/frr-pbrd.rst \ doc/manpages/frr-pimd.rst \ doc/manpages/frr-ripd.rst \ doc/manpages/frr-ripngd.rst \ doc/manpages/frr-sharpd.rst \ doc/manpages/frr-staticd.rst \ doc/manpages/frr-vrrpd.rst \ doc/manpages/frr-watchfrr.rst \ doc/manpages/frr-zebra.rst \ doc/manpages/frr.rst \ doc/manpages/index.rst \ doc/manpages/mtracebis.rst \ doc/manpages/vtysh.rst \ # end MANPARENT = doc/manpages/_build MANBUILD = $(MANPARENT)/man # # automake integration # rstman1dir = $(mandir)/man1 rstman8dir = $(mandir)/man8 rstman1_DATA = $(am__append_3) rstman8_DATA = $(am__append_4) man1 = $(MANBUILD)/frr.1 $(am__append_137) man8 = $(am__append_22) $(am__append_28) $(am__append_41) \ $(am__append_54) $(am__append_58) $(am__append_64) \ $(am__append_70) $(am__append_78) $(am__append_84) \ $(am__append_89) $(am__append_98) $(am__append_103) \ $(am__append_110) $(am__append_115) $(am__append_120) \ $(am__append_125) $(am__append_134) dev_RSTFILES = \ doc/developer/bgp-typecodes.rst \ doc/developer/bgpd.rst \ doc/developer/building-frr-for-alpine.rst \ doc/developer/building-frr-for-centos6.rst \ doc/developer/building-frr-for-centos7.rst \ doc/developer/building-frr-for-debian8.rst \ doc/developer/building-frr-for-debian9.rst \ doc/developer/building-frr-for-fedora.rst \ doc/developer/building-frr-for-freebsd10.rst \ doc/developer/building-frr-for-freebsd11.rst \ doc/developer/building-frr-for-freebsd9.rst \ doc/developer/building-frr-for-netbsd6.rst \ doc/developer/building-frr-for-netbsd7.rst \ doc/developer/building-frr-for-omnios.rst \ doc/developer/building-frr-for-openbsd6.rst \ doc/developer/building-frr-for-openwrt.rst \ doc/developer/building-frr-for-ubuntu1404.rst \ doc/developer/building-frr-for-ubuntu1604.rst \ doc/developer/building-frr-for-ubuntu1804.rst \ doc/developer/building-libyang.rst \ doc/developer/building.rst \ doc/developer/cli.rst \ doc/developer/conf.py \ doc/developer/hooks.rst \ doc/developer/include-compile.rst \ doc/developer/index.rst \ doc/developer/library.rst \ doc/developer/lists.rst \ doc/developer/locking.rst \ doc/developer/logging.rst \ doc/developer/maintainer-release-build.rst \ doc/developer/memtypes.rst \ doc/developer/modules.rst \ doc/developer/next-hop-tracking.rst \ doc/developer/ospf-api.rst \ doc/developer/ospf-sr.rst \ doc/developer/ospf.rst \ doc/developer/packaging-debian.rst \ doc/developer/packaging-redhat.rst \ doc/developer/packaging.rst \ doc/developer/rcu.rst \ doc/developer/testing.rst \ doc/developer/topotests-snippets.rst \ doc/developer/topotests.rst \ doc/developer/workflow.rst \ doc/developer/zebra.rst \ # end DEVBUILD = doc/developer/_build lib_libfrr_la_LDFLAGS = -version-info 0:0:0 -Xlinker -e_libfrr_version lib_libfrr_la_LIBADD = $(LIBCAP) $(UNWIND_LIBS) $(LIBYANG_LIBS) \ $(LUA_LIB) $(LIBM) $(am__append_5) lib_libfrr_la_SOURCES = lib/agg_table.c lib/atomlist.c lib/bfd.c \ lib/buffer.c lib/checksum.c lib/command.c lib/command_graph.c \ lib/command_lex.l lib/command_match.c lib/command_parse.y \ lib/csv.c lib/debug.c lib/distribute.c lib/ferr.c lib/filter.c \ lib/frrcu.c lib/frrlua.c lib/frr_pthread.c lib/frrstr.c \ lib/getopt.c lib/getopt1.c lib/grammar_sandbox.c lib/graph.c \ lib/hash.c lib/hook.c lib/id_alloc.c lib/if.c lib/if_rmap.c \ lib/imsg-buffer.c lib/imsg.c lib/jhash.c lib/json.c \ lib/keychain.c lib/lib_errors.c lib/libfrr.c lib/linklist.c \ lib/log.c lib/log_vty.c lib/md5.c lib/memory.c \ lib/memory_vty.c lib/mlag.c lib/module.c lib/mpls.c \ lib/network.c lib/nexthop.c lib/netns_linux.c \ lib/netns_other.c lib/nexthop_group.c lib/northbound.c \ lib/northbound_cli.c lib/northbound_db.c lib/ntop.c \ lib/openbsd-tree.c lib/pid_output.c lib/plist.c lib/prefix.c \ lib/privs.c lib/ptm_lib.c lib/pullwr.c lib/qobj.c \ lib/ringbuf.c lib/routemap.c lib/sbuf.c lib/seqlock.c \ lib/sha256.c lib/sigevent.c lib/skiplist.c lib/sockopt.c \ lib/sockunion.c lib/spf_backoff.c lib/srcdest_table.c \ lib/stream.c lib/strlcat.c lib/strlcpy.c lib/systemd.c \ lib/table.c lib/termtable.c lib/thread.c lib/typerb.c \ lib/typesafe.c lib/vector.c lib/vrf.c lib/vty.c lib/wheel.c \ lib/workqueue.c lib/yang.c lib/yang_translator.c \ lib/yang_wrappers.c lib/zclient.c lib/printf/printf-pos.c \ lib/printf/vfprintf.c lib/printf/glue.c $(am__append_6) nodist_lib_libfrr_la_SOURCES = \ yang/frr-interface.yang.c \ yang/frr-route-types.yang.c \ yang/frr-module-translator.yang.c \ # end lib_libfrrsnmp_la_CFLAGS = $(WERROR) $(SNMP_CFLAGS) -std=gnu99 lib_libfrrsnmp_la_LDFLAGS = -version-info 0:0:0 lib_libfrrsnmp_la_LIBADD = $(SNMP_LIBS) lib_libfrrsnmp_la_SOURCES = \ lib/agentx.c \ lib/snmp.c \ # end lib_libfrrcares_la_CFLAGS = $(WERROR) $(CARES_CFLAGS) lib_libfrrcares_la_LDFLAGS = -version-info 0:0:0 lib_libfrrcares_la_LIBADD = $(CARES_LIBS) lib_libfrrcares_la_SOURCES = \ lib/resolver.c \ #end lib_libfrrzmq_la_CFLAGS = $(WERROR) $(ZEROMQ_CFLAGS) lib_libfrrzmq_la_LDFLAGS = -version-info 0:0:0 lib_libfrrzmq_la_LIBADD = $(ZEROMQ_LIBS) lib_libfrrzmq_la_SOURCES = \ lib/frr_zmq.c \ #end lib_confd_la_CFLAGS = $(WERROR) $(CONFD_CFLAGS) lib_confd_la_LDFLAGS = -avoid-version -module -shared -export-dynamic lib_confd_la_LIBADD = lib/libfrr.la $(CONFD_LIBS) lib_confd_la_SOURCES = lib/northbound_confd.c lib_sysrepo_la_CFLAGS = $(WERROR) $(SYSREPO_CFLAGS) lib_sysrepo_la_LDFLAGS = -avoid-version -module -shared -export-dynamic lib_sysrepo_la_LIBADD = lib/libfrr.la $(SYSREPO_LIBS) lib_sysrepo_la_SOURCES = lib/northbound_sysrepo.c lib_grpc_la_CXXFLAGS = $(WERROR) $(GRPC_CFLAGS) lib_grpc_la_LDFLAGS = -avoid-version -module -shared -export-dynamic lib_grpc_la_LIBADD = lib/libfrr.la grpc/libfrrgrpc_pb.la $(GRPC_LIBS) lib_grpc_la_SOURCES = lib/northbound_grpc.cpp lib_grammar_sandbox_SOURCES = \ lib/grammar_sandbox_main.c lib_grammar_sandbox_LDADD = \ lib/libfrr.la lib_clippy_CPPFLAGS = $(AM_CPPFLAGS) -D_GNU_SOURCE -DBUILDING_CLIPPY lib_clippy_CFLAGS = $(PYTHON_CFLAGS) lib_clippy_LDADD = $(PYTHON_LIBS) lib_clippy_LDFLAGS = -export-dynamic lib_clippy_SOURCES = \ lib/clippy.c \ lib/command_graph.c \ lib/command_lex.l \ lib/command_parse.y \ lib/command_py.c \ lib/defun_lex.l \ lib/graph.c \ lib/memory.c \ lib/vector.c \ # end # (global) clippy rules for all directories AM_V_CLIPPY = $(am__v_CLIPPY_$(V)) am__v_CLIPPY_ = $(am__v_CLIPPY_$(AM_DEFAULT_VERBOSITY)) am__v_CLIPPY_0 = @echo " CLIPPY " $@; am__v_CLIPPY_1 = CLIPPY_DEPS = $(CLIPPY) $(top_srcdir)/python/clidef.py SUFFIXES = _clippy.c .proto .pb-c.c .pb-c.h .pb.h .pb.cc .grpc.pb.cc \ .yang .yang.c .yin .yin.c AM_YFLAGS = -d -Dapi.prefix=@BISON_OPENBRACE@cmd_yy@BISON_CLOSEBRACE@ @BISON_VERBOSE@ @GIT_VERSION_FALSE@PHONY_GITVERSION = lib/gitversion.h # bit of a trick here to always have up-to-date git stamps without triggering # unneccessary rebuilds. .PHONY causes the .tmp file to be rebuilt always, # but if we use that on gitversion.h it'll ripple through the .c file deps. # (even if gitversion.h's file timestamp doesn't change, make will think it # did, because of .PHONY...) @GIT_VERSION_TRUE@PHONY_GITVERSION = lib/gitversion.h.tmp @GIT_VERSION_TRUE@GITH = lib/gitversion.h zebra_zebra_LDADD = lib/libfrr.la $(LIBCAP) zebra_zebra_SOURCES = \ zebra/connected.c \ zebra/debug.c \ zebra/if_ioctl.c \ zebra/if_ioctl_solaris.c \ zebra/if_netlink.c \ zebra/if_sysctl.c \ zebra/interface.c \ zebra/ioctl.c \ zebra/ioctl_solaris.c \ zebra/ipforward_proc.c \ zebra/ipforward_solaris.c \ zebra/ipforward_sysctl.c \ zebra/kernel_netlink.c \ zebra/kernel_socket.c \ zebra/label_manager.c \ zebra/main.c \ zebra/redistribute.c \ zebra/router-id.c \ zebra/rt_netlink.c \ zebra/rt_socket.c \ zebra/rtadv.c \ zebra/rtread_getmsg.c \ zebra/rtread_netlink.c \ zebra/rtread_sysctl.c \ zebra/rule_netlink.c \ zebra/rule_socket.c \ zebra/zebra_mlag.c \ zebra/zebra_l2.c \ zebra/zebra_memory.c \ zebra/zebra_dplane.c \ zebra/zebra_mpls.c \ zebra/zebra_mpls_netlink.c \ zebra/zebra_mpls_openbsd.c \ zebra/zebra_mpls_null.c \ zebra/zebra_mpls_vty.c \ zebra/zebra_mroute.c \ zebra/zebra_nhg.c \ zebra/zebra_ns.c \ zebra/zebra_pbr.c \ zebra/zebra_ptm.c \ zebra/zebra_ptm_redistribute.c \ zebra/zebra_pw.c \ zebra/zebra_rib.c \ zebra/zebra_router.c \ zebra/zebra_rnh.c \ zebra/zebra_routemap.c \ zebra/zebra_vrf.c \ zebra/zebra_vty.c \ zebra/zebra_vxlan.c \ zebra/zserv.c \ zebra/zebra_netns_id.c \ zebra/zebra_netns_notify.c \ zebra/table_manager.c \ zebra/zapi_msg.c \ zebra/zebra_errors.c \ # end zebra_zebra_irdp_la_SOURCES = \ zebra/irdp_interface.c \ zebra/irdp_main.c \ zebra/irdp_packet.c \ # end zebra_zebra_irdp_la_LDFLAGS = -avoid-version -module -shared -export-dynamic zebra_zebra_snmp_la_SOURCES = zebra/zebra_snmp.c zebra_zebra_snmp_la_CFLAGS = $(WERROR) $(SNMP_CFLAGS) -std=gnu99 zebra_zebra_snmp_la_LDFLAGS = -avoid-version -module -shared -export-dynamic zebra_zebra_snmp_la_LIBADD = lib/libfrrsnmp.la zebra_zebra_fpm_la_LDFLAGS = -avoid-version -module -shared -export-dynamic zebra_zebra_fpm_la_LIBADD = $(am__append_23) zebra_zebra_fpm_la_SOURCES = zebra/zebra_fpm.c \ zebra/zebra_fpm_netlink.c $(am__append_24) $(am__append_25) watchfrr_watchfrr_LDADD = lib/libfrr.la $(LIBCAP) watchfrr_watchfrr_SOURCES = \ watchfrr/watchfrr.c \ watchfrr/watchfrr_errors.c \ watchfrr/watchfrr_vty.c \ # end qpb_libfrr_pb_la_CPPFLAGS = $(AM_CPPFLAGS) $(PROTOBUF_C_CFLAGS) qpb_libfrr_pb_la_LIBADD = $(PROTOBUF_C_LIBS) qpb_libfrr_pb_la_LDFLAGS = -version-info 0:0:0 qpb_libfrr_pb_la_SOURCES = \ qpb/qpb.c \ qpb/qpb_allocator.c \ # end nodist_qpb_libfrr_pb_la_SOURCES = \ qpb/qpb.pb-c.c \ # end @HAVE_PROTOBUF_TRUE@AM_V_PROTOC_C = $(am__v_PROTOC_C_$(V)) @HAVE_PROTOBUF_TRUE@am__v_PROTOC_C_ = $(am__v_PROTOC_C_$(AM_DEFAULT_VERBOSITY)) @HAVE_PROTOBUF_TRUE@am__v_PROTOC_C_0 = @echo " PROTOC_C" $@; @HAVE_PROTOBUF_TRUE@am__v_PROTOC_C_1 = fpm_libfrrfpm_pb_la_LDFLAGS = -version-info 0:0:0 fpm_libfrrfpm_pb_la_CPPFLAGS = $(AM_CPPFLAGS) $(PROTOBUF_C_CFLAGS) fpm_libfrrfpm_pb_la_SOURCES = \ fpm/fpm.h \ fpm/fpm_pb.h \ fpm/fpm_pb.c \ # end nodist_fpm_libfrrfpm_pb_la_SOURCES = \ fpm/fpm.pb-c.c \ # end grpc_libfrrgrpc_pb_la_LDFLAGS = -version-info 0:0:0 grpc_libfrrgrpc_pb_la_CPPFLAGS = $(AM_CPPFLAGS) $(GRPC_CXXFLAGS) nodist_grpc_libfrrgrpc_pb_la_SOURCES = \ grpc/frr-northbound.pb.cc \ grpc/frr-northbound.grpc.pb.cc \ # end AM_V_PROTOC = $(am__v_PROTOC_$(V)) am__v_PROTOC_ = $(am__v_PROTOC_$(AM_DEFAULT_VERBOSITY)) am__v_PROTOC_0 = @echo " PROTOC" $@; am__v_PROTOC_1 = tools_permutations_SOURCES = tools/permutations.c tools_permutations_LDADD = lib/libfrr.la tools_gen_northbound_callbacks_SOURCES = tools/gen_northbound_callbacks.c tools_gen_northbound_callbacks_LDADD = lib/libfrr.la $(LIBYANG_LIBS) tools_gen_yang_deviations_SOURCES = tools/gen_yang_deviations.c tools_gen_yang_deviations_LDADD = lib/libfrr.la $(LIBYANG_LIBS) tools_ssd_SOURCES = tools/start-stop-daemon.c bgpd_libbgp_a_SOURCES = bgpd/bgp_addpath.c bgpd/bgp_advertise.c \ bgpd/bgp_aspath.c bgpd/bgp_attr.c bgpd/bgp_attr_evpn.c \ bgpd/bgp_bfd.c bgpd/bgp_clist.c bgpd/bgp_community.c \ bgpd/bgp_damp.c bgpd/bgp_debug.c bgpd/bgp_dump.c \ bgpd/bgp_ecommunity.c bgpd/bgp_encap_tlv.c bgpd/bgp_errors.c \ bgpd/bgp_evpn.c bgpd/bgp_evpn_vty.c bgpd/bgp_filter.c \ bgpd/bgp_flowspec.c bgpd/bgp_flowspec_util.c \ bgpd/bgp_flowspec_vty.c bgpd/bgp_fsm.c bgpd/bgp_io.c \ bgpd/bgp_keepalives.c bgpd/bgp_label.c bgpd/bgp_labelpool.c \ bgpd/bgp_lcommunity.c bgpd/bgp_mac.c bgpd/bgp_memory.c \ bgpd/bgp_mpath.c bgpd/bgp_mplsvpn.c bgpd/bgp_network.c \ bgpd/bgp_nexthop.c bgpd/bgp_nht.c bgpd/bgp_open.c \ bgpd/bgp_packet.c bgpd/bgp_pbr.c bgpd/bgp_rd.c \ bgpd/bgp_regex.c bgpd/bgp_route.c bgpd/bgp_routemap.c \ bgpd/bgp_table.c bgpd/bgp_updgrp.c bgpd/bgp_updgrp_adv.c \ bgpd/bgp_updgrp_packet.c bgpd/bgp_vpn.c bgpd/bgp_vty.c \ bgpd/bgp_zebra.c bgpd/bgpd.c $(am__append_42) bgpd_bgpd_SOURCES = bgpd/bgp_main.c $(am__append_43) bgpd_bgp_btoa_SOURCES = bgpd/bgp_btoa.c $(am__append_45) bgpd_bgpd_CFLAGS = $(AM_CFLAGS) $(am__append_44) bgpd_bgp_btoa_CFLAGS = $(AM_CFLAGS) $(am__append_46) # RFPLDADD is set in bgpd/rfp-example/librfp/subdir.am bgpd_bgpd_LDADD = bgpd/libbgp.a $(RFPLDADD) lib/libfrr.la $(LIBCAP) $(LIBM) bgpd_bgp_btoa_LDADD = bgpd/libbgp.a $(RFPLDADD) lib/libfrr.la $(LIBCAP) $(LIBM) bgpd_bgpd_snmp_la_SOURCES = bgpd/bgp_snmp.c bgpd_bgpd_snmp_la_CFLAGS = $(WERROR) $(SNMP_CFLAGS) -std=gnu99 bgpd_bgpd_snmp_la_LDFLAGS = -avoid-version -module -shared -export-dynamic bgpd_bgpd_snmp_la_LIBADD = lib/libfrrsnmp.la bgpd_bgpd_rpki_la_SOURCES = bgpd/bgp_rpki.c bgpd_bgpd_rpki_la_CFLAGS = $(WERROR) $(RTRLIB_CFLAGS) bgpd_bgpd_rpki_la_LDFLAGS = -avoid-version -module -shared -export-dynamic bgpd_bgpd_rpki_la_LIBADD = $(RTRLIB_LIBS) bgpd_bgpd_bmp_la_SOURCES = bgpd/bgp_bmp.c bgpd_bgpd_bmp_la_LIBADD = lib/libfrrcares.la bgpd_bgpd_bmp_la_LDFLAGS = -avoid-version -module -shared -export-dynamic @ENABLE_BGP_VNC_TRUE@RFPLDADD = bgpd/rfp-example/librfp/librfp.a bgpd_rfp_example_librfp_librfp_a_SOURCES = \ bgpd/rfp-example/librfp/rfp_example.c \ # end bgpd_rfp_example_rfptest_rfptest_CFLAGS = -I$(top_srcdir)/bgpd/rfapi bgpd_rfp_example_rfptest_rfptest_SOURCES = \ bgpd/rfp-example/rfptest/rfptest.c \ # end bgpd_rfp_example_rfptest_rfptest_LDADD = \ lib/libfrr.la \ $(RFPLDADD) \ # end ripd_librip_a_SOURCES = \ ripd/rip_cli.c \ ripd/rip_debug.c \ ripd/rip_errors.c \ ripd/rip_interface.c \ ripd/rip_offset.c \ ripd/rip_northbound.c \ ripd/rip_peer.c \ ripd/rip_routemap.c \ ripd/rip_zebra.c \ ripd/ripd.c \ # end ripd_ripd_LDADD = ripd/librip.a lib/libfrr.la $(LIBCAP) ripd_ripd_SOURCES = \ ripd/rip_main.c \ # end nodist_ripd_ripd_SOURCES = \ yang/frr-ripd.yang.c \ # end ripd_ripd_snmp_la_SOURCES = ripd/rip_snmp.c ripd_ripd_snmp_la_CFLAGS = $(WERROR) $(SNMP_CFLAGS) -std=gnu99 ripd_ripd_snmp_la_LDFLAGS = -avoid-version -module -shared -export-dynamic ripd_ripd_snmp_la_LIBADD = lib/libfrrsnmp.la ripngd_libripng_a_SOURCES = \ ripngd/ripng_cli.c \ ripngd/ripng_debug.c \ ripngd/ripng_interface.c \ ripngd/ripng_nexthop.c \ ripngd/ripng_offset.c \ ripngd/ripng_northbound.c \ ripngd/ripng_peer.c \ ripngd/ripng_route.c \ ripngd/ripng_routemap.c \ ripngd/ripng_zebra.c \ ripngd/ripngd.c \ # end ripngd_ripngd_LDADD = ripngd/libripng.a lib/libfrr.la $(LIBCAP) ripngd_ripngd_SOURCES = \ ripngd/ripng_main.c \ # end nodist_ripngd_ripngd_SOURCES = \ yang/frr-ripngd.yang.c \ # end ospfd_libfrrospf_a_SOURCES = \ ospfd/ospf_abr.c \ ospfd/ospf_api.c \ ospfd/ospf_apiserver.c \ ospfd/ospf_asbr.c \ ospfd/ospf_ase.c \ ospfd/ospf_bfd.c \ ospfd/ospf_dump.c \ ospfd/ospf_dump_api.c \ ospfd/ospf_errors.c \ ospfd/ospf_ext.c \ ospfd/ospf_flood.c \ ospfd/ospf_ia.c \ ospfd/ospf_interface.c \ ospfd/ospf_ism.c \ ospfd/ospf_lsa.c \ ospfd/ospf_lsdb.c \ ospfd/ospf_memory.c \ ospfd/ospf_neighbor.c \ ospfd/ospf_network.c \ ospfd/ospf_nsm.c \ ospfd/ospf_opaque.c \ ospfd/ospf_packet.c \ ospfd/ospf_ri.c \ ospfd/ospf_route.c \ ospfd/ospf_routemap.c \ ospfd/ospf_spf.c \ ospfd/ospf_sr.c \ ospfd/ospf_te.c \ ospfd/ospf_vty.c \ ospfd/ospf_zebra.c \ ospfd/ospfd.c \ # end @OSPFD_TRUE@ospfdheaderdir = $(pkgincludedir)/ospfd @OSPFD_TRUE@ospfdheader_HEADERS = \ @OSPFD_TRUE@ ospfd/ospf_api.h \ @OSPFD_TRUE@ ospfd/ospf_asbr.h \ @OSPFD_TRUE@ ospfd/ospf_dump.h \ @OSPFD_TRUE@ ospfd/ospf_dump_api.h \ @OSPFD_TRUE@ ospfd/ospf_ism.h \ @OSPFD_TRUE@ ospfd/ospf_lsa.h \ @OSPFD_TRUE@ ospfd/ospf_lsdb.h \ @OSPFD_TRUE@ ospfd/ospf_nsm.h \ @OSPFD_TRUE@ ospfd/ospf_opaque.h \ @OSPFD_TRUE@ ospfd/ospfd.h \ @OSPFD_TRUE@ # end ospfd_ospfd_LDADD = ospfd/libfrrospf.a lib/libfrr.la $(LIBCAP) $(LIBM) ospfd_ospfd_SOURCES = ospfd/ospf_main.c ospfd_ospfd_snmp_la_SOURCES = ospfd/ospf_snmp.c ospfd_ospfd_snmp_la_CFLAGS = $(WERROR) $(SNMP_CFLAGS) -std=gnu99 ospfd_ospfd_snmp_la_LDFLAGS = -avoid-version -module -shared -export-dynamic ospfd_ospfd_snmp_la_LIBADD = lib/libfrrsnmp.la ospf6d_libospf6_a_SOURCES = \ ospf6d/ospf6_abr.c \ ospf6d/ospf6_area.c \ ospf6d/ospf6_asbr.c \ ospf6d/ospf6_bfd.c \ ospf6d/ospf6_flood.c \ ospf6d/ospf6_interface.c \ ospf6d/ospf6_intra.c \ ospf6d/ospf6_lsa.c \ ospf6d/ospf6_lsdb.c \ ospf6d/ospf6_memory.c \ ospf6d/ospf6_message.c \ ospf6d/ospf6_neighbor.c \ ospf6d/ospf6_network.c \ ospf6d/ospf6_proto.c \ ospf6d/ospf6_route.c \ ospf6d/ospf6_spf.c \ ospf6d/ospf6_top.c \ ospf6d/ospf6_zebra.c \ ospf6d/ospf6d.c \ # end ospf6d_ospf6d_LDADD = ospf6d/libospf6.a lib/libfrr.la $(LIBCAP) ospf6d_ospf6d_SOURCES = \ ospf6d/ospf6_main.c \ # end ospf6d_ospf6d_snmp_la_SOURCES = ospf6d/ospf6_snmp.c ospf6d_ospf6d_snmp_la_CFLAGS = $(WERROR) $(SNMP_CFLAGS) -std=gnu99 ospf6d_ospf6d_snmp_la_LDFLAGS = -avoid-version -module -shared -export-dynamic ospf6d_ospf6d_snmp_la_LIBADD = lib/libfrrsnmp.la #man8 += $(MANBUILD)/frr-ospfclient.8 ospfclient_libfrrospfapiclient_la_LDFLAGS = -version-info 0:0:0 ospfclient_libfrrospfapiclient_la_LIBADD = lib/libfrr.la ospfclient_libfrrospfapiclient_la_SOURCES = \ ospfclient/ospf_apiclient.c \ # end @OSPFCLIENT_TRUE@ospfapiheaderdir = $(pkgincludedir)/ospfapi @OSPFCLIENT_TRUE@ospfapiheader_HEADERS = \ @OSPFCLIENT_TRUE@ ospfclient/ospf_apiclient.h \ @OSPFCLIENT_TRUE@ # end ospfclient_ospfclient_LDADD = ospfclient/libfrrospfapiclient.la \ $(LIBCAP) $(am__append_73) ospfclient_ospfclient_SOURCES = \ ospfclient/ospfclient.c \ # end LIBISIS_SOURCES = \ isisd/isis_adjacency.c \ isisd/isis_bfd.c \ isisd/isis_circuit.c \ isisd/isis_csm.c \ isisd/isis_dr.c \ isisd/isis_dynhn.c \ isisd/isis_errors.c \ isisd/isis_events.c \ isisd/isis_flags.c \ isisd/isis_lsp.c \ isisd/isis_memory.c \ isisd/isis_misc.c \ isisd/isis_mt.c \ isisd/isis_pdu.c \ isisd/isis_pdu_counter.c \ isisd/isis_redist.c \ isisd/isis_route.c \ isisd/isis_routemap.c \ isisd/isis_spf.c \ isisd/isis_te.c \ isisd/isis_tlvs.c \ isisd/isis_tx_queue.c \ isisd/isis_zebra.c \ isisd/isisd.c \ isisd/iso_checksum.c \ isisd/fabricd.c \ # end ISIS_SOURCES = \ isisd/isis_bpf.c \ isisd/isis_dlpi.c \ isisd/isis_main.c \ isisd/isis_pfpacket.c \ # end ISIS_LDADD_COMMON = lib/libfrr.la $(LIBCAP) # Building isisd isisd_libisis_a_SOURCES = \ $(LIBISIS_SOURCES) \ isisd/isis_northbound.c \ isisd/isis_cli.c \ #end isisd_isisd_LDADD = isisd/libisis.a $(ISIS_LDADD_COMMON) isisd_isisd_SOURCES = $(ISIS_SOURCES) nodist_isisd_isisd_SOURCES = \ yang/frr-isisd.yang.c \ # end # Building fabricd FABRICD_CPPFLAGS = -DFABRICD=1 $(AM_CPPFLAGS) isisd_libfabric_a_SOURCES = \ $(LIBISIS_SOURCES) \ isisd/isis_vty_fabricd.c \ #end isisd_libfabric_a_CPPFLAGS = $(FABRICD_CPPFLAGS) isisd_fabricd_LDADD = isisd/libfabric.a $(ISIS_LDADD_COMMON) isisd_fabricd_SOURCES = $(ISIS_SOURCES) isisd_fabricd_CPPFLAGS = $(FABRICD_CPPFLAGS) nhrpd_nhrpd_LDADD = lib/libfrr.la lib/libfrrcares.la $(LIBCAP) nhrpd_nhrpd_SOURCES = \ nhrpd/linux.c \ nhrpd/netlink_arp.c \ nhrpd/netlink_gre.c \ nhrpd/nhrp_cache.c \ nhrpd/nhrp_errors.c \ nhrpd/nhrp_event.c \ nhrpd/nhrp_interface.c \ nhrpd/nhrp_main.c \ nhrpd/nhrp_nhs.c \ nhrpd/nhrp_packet.c \ nhrpd/nhrp_peer.c \ nhrpd/nhrp_route.c \ nhrpd/nhrp_shortcut.c \ nhrpd/nhrp_vc.c \ nhrpd/nhrp_vty.c \ nhrpd/reqid.c \ nhrpd/vici.c \ nhrpd/zbuf.c \ nhrpd/znl.c \ # end ldpd_libldp_a_SOURCES = \ ldpd/accept.c \ ldpd/address.c \ ldpd/adjacency.c \ ldpd/control.c \ ldpd/hello.c \ ldpd/init.c \ ldpd/interface.c \ ldpd/keepalive.c \ ldpd/l2vpn.c \ ldpd/labelmapping.c \ ldpd/lde.c \ ldpd/lde_lib.c \ ldpd/ldp_debug.c \ ldpd/ldp_vty_cmds.c \ ldpd/ldp_vty_conf.c \ ldpd/ldp_vty_exec.c \ ldpd/ldp_zebra.c \ ldpd/ldpd.c \ ldpd/ldpe.c \ ldpd/log.c \ ldpd/logmsg.c \ ldpd/neighbor.c \ ldpd/notification.c \ ldpd/packet.c \ ldpd/pfkey.c \ ldpd/socket.c \ ldpd/util.c \ # end ldpd_ldpd_SOURCES = ldpd/ldpd.c ldpd_ldpd_LDADD = ldpd/libldp.a lib/libfrr.la $(LIBCAP) babeld_libbabel_a_SOURCES = \ babeld/babel_errors.c \ babeld/babel_filter.c \ babeld/babel_interface.c \ babeld/babel_zebra.c \ babeld/babeld.c \ babeld/kernel.c \ babeld/message.c \ babeld/neighbour.c \ babeld/net.c \ babeld/resend.c \ babeld/route.c \ babeld/source.c \ babeld/util.c \ babeld/xroute.c \ # end babeld_babeld_SOURCES = babeld/babel_main.c babeld_babeld_LDADD = babeld/libbabel.a lib/libfrr.la $(LIBCAP) eigrpd_libeigrp_a_SOURCES = \ eigrpd/eigrp_cli.c \ eigrpd/eigrp_dump.c \ eigrpd/eigrp_errors.c \ eigrpd/eigrp_filter.c \ eigrpd/eigrp_fsm.c \ eigrpd/eigrp_hello.c \ eigrpd/eigrp_interface.c \ eigrpd/eigrp_memory.c \ eigrpd/eigrp_neighbor.c \ eigrpd/eigrp_network.c \ eigrpd/eigrp_northbound.c \ eigrpd/eigrp_packet.c \ eigrpd/eigrp_query.c \ eigrpd/eigrp_reply.c \ eigrpd/eigrp_siaquery.c \ eigrpd/eigrp_siareply.c \ eigrpd/eigrp_snmp.c \ eigrpd/eigrp_topology.c \ eigrpd/eigrp_update.c \ eigrpd/eigrp_vrf.c \ eigrpd/eigrp_vty.c \ eigrpd/eigrp_zebra.c \ eigrpd/eigrpd.c \ # end eigrpdheaderdir = $(pkgincludedir)/eigrpd eigrpdheader_HEADERS = \ eigrpd/eigrp_dump.h \ eigrpd/eigrp_topology.h \ eigrpd/eigrpd.h \ # end nodist_eigrpd_eigrpd_SOURCES = \ yang/frr-eigrpd.yang.c \ # end eigrpd_eigrpd_SOURCES = eigrpd/eigrp_main.c eigrpd_eigrpd_LDADD = eigrpd/libeigrp.a lib/libfrr.la $(LIBCAP) sharpd_libsharp_a_SOURCES = \ sharpd/sharp_nht.c \ sharpd/sharp_zebra.c \ sharpd/sharp_vty.c \ # end sharpd_sharpd_SOURCES = sharpd/sharp_main.c sharpd_sharpd_LDADD = sharpd/libsharp.a lib/libfrr.la $(LIBCAP) pimd_libpim_a_SOURCES = \ pimd/pim_assert.c \ pimd/pim_bfd.c \ pimd/pim_br.c \ pimd/pim_bsm.c \ pimd/pim_cmd.c \ pimd/pim_errors.c \ pimd/pim_hello.c \ pimd/pim_iface.c \ pimd/pim_ifchannel.c \ pimd/pim_igmp.c \ pimd/pim_igmp_mtrace.c \ pimd/pim_igmp_stats.c \ pimd/pim_igmpv2.c \ pimd/pim_igmpv3.c \ pimd/pim_instance.c \ pimd/pim_int.c \ pimd/pim_join.c \ pimd/pim_jp_agg.c \ pimd/pim_macro.c \ pimd/pim_memory.c \ pimd/pim_mroute.c \ pimd/pim_msdp.c \ pimd/pim_msdp_packet.c \ pimd/pim_msdp_socket.c \ pimd/pim_msg.c \ pimd/pim_neighbor.c \ pimd/pim_nht.c \ pimd/pim_oil.c \ pimd/pim_pim.c \ pimd/pim_register.c \ pimd/pim_routemap.c \ pimd/pim_rp.c \ pimd/pim_rpf.c \ pimd/pim_signals.c \ pimd/pim_sock.c \ pimd/pim_ssm.c \ pimd/pim_ssmpingd.c \ pimd/pim_static.c \ pimd/pim_str.c \ pimd/pim_time.c \ pimd/pim_tlv.c \ pimd/pim_upstream.c \ pimd/pim_util.c \ pimd/pim_version.c \ pimd/pim_vty.c \ pimd/pim_zebra.c \ pimd/pim_zlookup.c \ pimd/pim_vxlan.c \ pimd/pimd.c \ # end pimd_pimd_LDADD = pimd/libpim.a lib/libfrr.la $(LIBCAP) pimd_pimd_SOURCES = pimd/pim_main.c pimd_test_igmpv3_join_LDADD = lib/libfrr.la pimd_test_igmpv3_join_SOURCES = pimd/test_igmpv3_join.c pimd_mtracebis_LDADD = lib/libfrr.la pimd_mtracebis_SOURCES = pimd/mtracebis.c \ pimd/mtracebis_netlink.c \ pimd/mtracebis_routeget.c \ # end pbrd_libpbr_a_SOURCES = \ pbrd/pbr_zebra.c \ pbrd/pbr_vty.c \ pbrd/pbr_map.c \ pbrd/pbr_memory.c \ pbrd/pbr_nht.c \ pbrd/pbr_debug.c \ # end pbrd_pbrd_SOURCES = pbrd/pbr_main.c pbrd_pbrd_LDADD = pbrd/libpbr.a lib/libfrr.la $(LIBCAP) staticd_libstatic_a_SOURCES = \ staticd/static_memory.c \ staticd/static_nht.c \ staticd/static_routes.c \ staticd/static_zebra.c \ staticd/static_vrf.c \ staticd/static_vty.c \ # end staticd_staticd_SOURCES = staticd/static_main.c staticd_staticd_LDADD = staticd/libstatic.a lib/libfrr.la $(LIBCAP) bfdd_libbfd_a_SOURCES = \ bfdd/bfd.c \ bfdd/bfdd_northbound.c \ bfdd/bfdd_vty.c \ bfdd/bfdd_cli.c \ bfdd/bfd_packet.c \ bfdd/config.c \ bfdd/control.c \ bfdd/event.c \ bfdd/log.c \ bfdd/ptm_adapter.c \ # end nodist_bfdd_bfdd_SOURCES = \ yang/frr-bfdd.yang.c \ # end bfdd_bfdd_SOURCES = bfdd/bfdd.c bfdd_bfdd_LDADD = bfdd/libbfd.a lib/libfrr.la vrrpd_libvrrp_a_SOURCES = \ vrrpd/vrrp.c \ vrrpd/vrrp_arp.c \ vrrpd/vrrp_debug.c \ vrrpd/vrrp_ndisc.c \ vrrpd/vrrp_packet.c \ vrrpd/vrrp_vty.c \ vrrpd/vrrp_zebra.c \ # end vrrpd_vrrpd_SOURCES = vrrpd/vrrp_main.c vrrpd_vrrpd_LDADD = vrrpd/libvrrp.a lib/libfrr.la @LIBCAP@ vtysh_vtysh_SOURCES = \ vtysh/vtysh_main.c \ vtysh/vtysh.c \ vtysh/vtysh_user.c \ vtysh/vtysh_config.c \ # end nodist_vtysh_vtysh_SOURCES = \ vtysh/vtysh_cmd.c \ # end vtysh_vtysh_LDADD = lib/libfrr.la $(LIBCAP) $(LIBREADLINE) $(LIBS) $(LIBPAM) AM_V_EXTRACT = $(am__v_EXTRACT_$(V)) am__v_EXTRACT_ = $(am__v_EXTRACT_$(AM_DEFAULT_VERBOSITY)) am__v_EXTRACT_0 = @echo " EXTRACT " $@; am__v_EXTRACT_1 = @BGPD_FALSE@TESTS_BGPD = @BGPD_TRUE@TESTS_BGPD = \ @BGPD_TRUE@ tests/bgpd/test_aspath \ @BGPD_TRUE@ tests/bgpd/test_capability \ @BGPD_TRUE@ tests/bgpd/test_packet \ @BGPD_TRUE@ tests/bgpd/test_peer_attr \ @BGPD_TRUE@ tests/bgpd/test_ecommunity \ @BGPD_TRUE@ tests/bgpd/test_mp_attr \ @BGPD_TRUE@ tests/bgpd/test_mpath \ @BGPD_TRUE@ tests/bgpd/test_bgp_table @ISISD_FALSE@TESTS_ISISD = @ISISD_TRUE@@SOLARIS_FALSE@TESTS_ISISD = \ @ISISD_TRUE@@SOLARIS_FALSE@ tests/isisd/test_fuzz_isis_tlv \ @ISISD_TRUE@@SOLARIS_FALSE@ tests/isisd/test_isis_lspdb \ @ISISD_TRUE@@SOLARIS_FALSE@ tests/isisd/test_isis_vertex_queue \ @ISISD_TRUE@@SOLARIS_FALSE@ # end @ISISD_TRUE@@SOLARIS_TRUE@TESTS_ISISD = @OSPF6D_FALSE@TESTS_OSPF6D = @OSPF6D_TRUE@TESTS_OSPF6D = \ @OSPF6D_TRUE@ tests/ospf6d/test_lsdb \ @OSPF6D_TRUE@ # end # # *sigh* - there is no way to get CPPFLAGS or CFLAGS for a group of files :( # TESTS_CPPFLAGS = $(AM_CPPFLAGS) \ -I$(top_srcdir)/tests/helpers/c \ -I$(top_builddir)/tests/helpers/c \ # end TESTS_CFLAGS = \ $(LIBYANG_CFLAGS) \ $(SAN_FLAGS) \ # end # note no -Werror ALL_TESTS_LDADD = lib/libfrr.la $(LIBCAP) BGP_TEST_LDADD = bgpd/libbgp.a $(RFPLDADD) $(ALL_TESTS_LDADD) -lm ISISD_TEST_LDADD = isisd/libisis.a $(ALL_TESTS_LDADD) OSPF6_TEST_LDADD = ospf6d/libospf6.a $(ALL_TESTS_LDADD) tests_bgpd_test_aspath_CFLAGS = $(TESTS_CFLAGS) tests_bgpd_test_aspath_CPPFLAGS = $(TESTS_CPPFLAGS) tests_bgpd_test_aspath_LDADD = $(BGP_TEST_LDADD) tests_bgpd_test_aspath_SOURCES = tests/bgpd/test_aspath.c tests_bgpd_test_bgp_table_CFLAGS = $(TESTS_CFLAGS) tests_bgpd_test_bgp_table_CPPFLAGS = $(TESTS_CPPFLAGS) tests_bgpd_test_bgp_table_LDADD = $(BGP_TEST_LDADD) tests_bgpd_test_bgp_table_SOURCES = tests/bgpd/test_bgp_table.c tests_bgpd_test_capability_CFLAGS = $(TESTS_CFLAGS) tests_bgpd_test_capability_CPPFLAGS = $(TESTS_CPPFLAGS) tests_bgpd_test_capability_LDADD = $(BGP_TEST_LDADD) tests_bgpd_test_capability_SOURCES = tests/bgpd/test_capability.c tests_bgpd_test_ecommunity_CFLAGS = $(TESTS_CFLAGS) tests_bgpd_test_ecommunity_CPPFLAGS = $(TESTS_CPPFLAGS) tests_bgpd_test_ecommunity_LDADD = $(BGP_TEST_LDADD) tests_bgpd_test_ecommunity_SOURCES = tests/bgpd/test_ecommunity.c tests_bgpd_test_mp_attr_CFLAGS = $(TESTS_CFLAGS) tests_bgpd_test_mp_attr_CPPFLAGS = $(TESTS_CPPFLAGS) tests_bgpd_test_mp_attr_LDADD = $(BGP_TEST_LDADD) tests_bgpd_test_mp_attr_SOURCES = tests/bgpd/test_mp_attr.c tests_bgpd_test_mpath_CFLAGS = $(TESTS_CFLAGS) tests_bgpd_test_mpath_CPPFLAGS = $(TESTS_CPPFLAGS) tests_bgpd_test_mpath_LDADD = $(BGP_TEST_LDADD) tests_bgpd_test_mpath_SOURCES = tests/bgpd/test_mpath.c tests_bgpd_test_packet_CFLAGS = $(TESTS_CFLAGS) tests_bgpd_test_packet_CPPFLAGS = $(TESTS_CPPFLAGS) tests_bgpd_test_packet_LDADD = $(BGP_TEST_LDADD) tests_bgpd_test_packet_SOURCES = tests/bgpd/test_packet.c tests_bgpd_test_peer_attr_CFLAGS = $(TESTS_CFLAGS) tests_bgpd_test_peer_attr_CPPFLAGS = $(TESTS_CPPFLAGS) tests_bgpd_test_peer_attr_LDADD = $(BGP_TEST_LDADD) tests_bgpd_test_peer_attr_SOURCES = tests/bgpd/test_peer_attr.c tests_isisd_test_fuzz_isis_tlv_CFLAGS = $(TESTS_CFLAGS) -I$(top_builddir)/tests/isisd tests_isisd_test_fuzz_isis_tlv_CPPFLAGS = $(TESTS_CPPFLAGS) -I$(top_builddir)/tests/isisd tests_isisd_test_fuzz_isis_tlv_LDADD = $(ISISD_TEST_LDADD) tests_isisd_test_fuzz_isis_tlv_SOURCES = tests/isisd/test_fuzz_isis_tlv.c nodist_tests_isisd_test_fuzz_isis_tlv_SOURCES = tests/isisd/test_fuzz_isis_tlv_tests.h tests_isisd_test_isis_lspdb_CFLAGS = $(TESTS_CFLAGS) tests_isisd_test_isis_lspdb_CPPFLAGS = $(TESTS_CPPFLAGS) tests_isisd_test_isis_lspdb_LDADD = $(ISISD_TEST_LDADD) tests_isisd_test_isis_lspdb_SOURCES = tests/isisd/test_isis_lspdb.c tests_isisd_test_isis_vertex_queue_CFLAGS = $(TESTS_CFLAGS) tests_isisd_test_isis_vertex_queue_CPPFLAGS = $(TESTS_CPPFLAGS) tests_isisd_test_isis_vertex_queue_LDADD = $(ISISD_TEST_LDADD) tests_isisd_test_isis_vertex_queue_SOURCES = tests/isisd/test_isis_vertex_queue.c tests_lib_cxxcompat_CFLAGS = $(TESTS_CFLAGS) $(CXX_COMPAT_CFLAGS) $(WERROR) tests_lib_cxxcompat_CPPFLAGS = $(TESTS_CPPFLAGS) tests_lib_cxxcompat_SOURCES = tests/lib/cxxcompat.c tests_lib_cxxcompat_LDADD = $(ALL_TESTS_LDADD) tests_lib_cli_test_cli_CFLAGS = $(TESTS_CFLAGS) tests_lib_cli_test_cli_CPPFLAGS = $(TESTS_CPPFLAGS) tests_lib_cli_test_cli_LDADD = $(ALL_TESTS_LDADD) tests_lib_cli_test_cli_SOURCES = tests/lib/cli/test_cli.c tests/lib/cli/common_cli.c tests_lib_cli_test_commands_CFLAGS = $(TESTS_CFLAGS) tests_lib_cli_test_commands_CPPFLAGS = $(TESTS_CPPFLAGS) tests_lib_cli_test_commands_LDADD = $(ALL_TESTS_LDADD) nodist_tests_lib_cli_test_commands_SOURCES = tests/lib/cli/test_commands_defun.c tests_lib_cli_test_commands_SOURCES = tests/lib/cli/test_commands.c tests/helpers/c/prng.c tests_lib_northbound_test_oper_data_CFLAGS = $(TESTS_CFLAGS) tests_lib_northbound_test_oper_data_CPPFLAGS = $(TESTS_CPPFLAGS) tests_lib_northbound_test_oper_data_LDADD = $(ALL_TESTS_LDADD) tests_lib_northbound_test_oper_data_SOURCES = tests/lib/northbound/test_oper_data.c nodist_tests_lib_northbound_test_oper_data_SOURCES = yang/frr-test-module.yang.c tests_lib_test_atomlist_CFLAGS = $(TESTS_CFLAGS) tests_lib_test_atomlist_CPPFLAGS = $(TESTS_CPPFLAGS) tests_lib_test_atomlist_LDADD = $(ALL_TESTS_LDADD) tests_lib_test_atomlist_SOURCES = tests/lib/test_atomlist.c tests_lib_test_buffer_CFLAGS = $(TESTS_CFLAGS) tests_lib_test_buffer_CPPFLAGS = $(TESTS_CPPFLAGS) tests_lib_test_buffer_LDADD = $(ALL_TESTS_LDADD) tests_lib_test_buffer_SOURCES = tests/lib/test_buffer.c tests_lib_test_checksum_CFLAGS = $(TESTS_CFLAGS) tests_lib_test_checksum_CPPFLAGS = $(TESTS_CPPFLAGS) tests_lib_test_checksum_LDADD = $(ALL_TESTS_LDADD) tests_lib_test_checksum_SOURCES = tests/lib/test_checksum.c tests_lib_test_graph_CFLAGS = $(TESTS_CFLAGS) tests_lib_test_graph_CPPFLAGS = $(TESTS_CPPFLAGS) tests_lib_test_graph_LDADD = $(ALL_TESTS_LDADD) tests_lib_test_graph_SOURCES = tests/lib/test_graph.c tests_lib_test_heavy_CFLAGS = $(TESTS_CFLAGS) tests_lib_test_heavy_CPPFLAGS = $(TESTS_CPPFLAGS) tests_lib_test_heavy_LDADD = $(ALL_TESTS_LDADD) -lm tests_lib_test_heavy_SOURCES = tests/lib/test_heavy.c tests/helpers/c/main.c tests_lib_test_heavy_thread_CFLAGS = $(TESTS_CFLAGS) tests_lib_test_heavy_thread_CPPFLAGS = $(TESTS_CPPFLAGS) tests_lib_test_heavy_thread_LDADD = $(ALL_TESTS_LDADD) -lm tests_lib_test_heavy_thread_SOURCES = tests/lib/test_heavy_thread.c tests/helpers/c/main.c tests_lib_test_heavy_wq_CFLAGS = $(TESTS_CFLAGS) tests_lib_test_heavy_wq_CPPFLAGS = $(TESTS_CPPFLAGS) tests_lib_test_heavy_wq_LDADD = $(ALL_TESTS_LDADD) -lm tests_lib_test_heavy_wq_SOURCES = tests/lib/test_heavy_wq.c tests/helpers/c/main.c tests_lib_test_idalloc_CFLAGS = $(TESTS_CFLAGS) tests_lib_test_idalloc_LDADD = $(ALL_TESTS_LDADD) tests_lib_test_idalloc_SOURCES = tests/lib/test_idalloc.c tests_lib_test_memory_CFLAGS = $(TESTS_CFLAGS) tests_lib_test_memory_CPPFLAGS = $(TESTS_CPPFLAGS) tests_lib_test_memory_LDADD = $(ALL_TESTS_LDADD) tests_lib_test_memory_SOURCES = tests/lib/test_memory.c tests_lib_test_nexthop_iter_CFLAGS = $(TESTS_CFLAGS) tests_lib_test_nexthop_iter_CPPFLAGS = $(TESTS_CPPFLAGS) tests_lib_test_nexthop_iter_LDADD = $(ALL_TESTS_LDADD) tests_lib_test_nexthop_iter_SOURCES = tests/lib/test_nexthop_iter.c tests/helpers/c/prng.c tests_lib_test_ntop_CFLAGS = $(TESTS_CFLAGS) tests_lib_test_ntop_CPPFLAGS = $(TESTS_CPPFLAGS) tests_lib_test_ntop_LDADD = # none tests_lib_test_ntop_SOURCES = tests/lib/test_ntop.c tests/helpers/c/prng.c tests_lib_test_prefix2str_CFLAGS = $(TESTS_CFLAGS) tests_lib_test_prefix2str_CPPFLAGS = $(TESTS_CPPFLAGS) tests_lib_test_prefix2str_LDADD = $(ALL_TESTS_LDADD) tests_lib_test_prefix2str_SOURCES = tests/lib/test_prefix2str.c tests/helpers/c/prng.c tests_lib_test_printfrr_CFLAGS = $(TESTS_CFLAGS) tests_lib_test_printfrr_CPPFLAGS = $(TESTS_CPPFLAGS) tests_lib_test_printfrr_LDADD = $(ALL_TESTS_LDADD) tests_lib_test_printfrr_SOURCES = tests/lib/test_printfrr.c tests_lib_test_privs_CFLAGS = $(TESTS_CFLAGS) tests_lib_test_privs_CPPFLAGS = $(TESTS_CPPFLAGS) tests_lib_test_privs_LDADD = $(ALL_TESTS_LDADD) tests_lib_test_privs_SOURCES = tests/lib/test_privs.c tests_lib_test_ringbuf_CFLAGS = $(TESTS_CFLAGS) tests_lib_test_ringbuf_CPPFLAGS = $(TESTS_CPPFLAGS) tests_lib_test_ringbuf_LDADD = $(ALL_TESTS_LDADD) tests_lib_test_ringbuf_SOURCES = tests/lib/test_ringbuf.c tests_lib_test_segv_CFLAGS = $(TESTS_CFLAGS) tests_lib_test_segv_CPPFLAGS = $(TESTS_CPPFLAGS) tests_lib_test_segv_LDADD = $(ALL_TESTS_LDADD) tests_lib_test_segv_SOURCES = tests/lib/test_segv.c tests_lib_test_seqlock_CFLAGS = $(TESTS_CFLAGS) tests_lib_test_seqlock_CPPFLAGS = $(TESTS_CPPFLAGS) tests_lib_test_seqlock_LDADD = $(ALL_TESTS_LDADD) tests_lib_test_seqlock_SOURCES = tests/lib/test_seqlock.c tests_lib_test_sig_CFLAGS = $(TESTS_CFLAGS) tests_lib_test_sig_CPPFLAGS = $(TESTS_CPPFLAGS) tests_lib_test_sig_LDADD = $(ALL_TESTS_LDADD) tests_lib_test_sig_SOURCES = tests/lib/test_sig.c tests_lib_test_srcdest_table_CFLAGS = $(TESTS_CFLAGS) tests_lib_test_srcdest_table_CPPFLAGS = $(TESTS_CPPFLAGS) tests_lib_test_srcdest_table_LDADD = $(ALL_TESTS_LDADD) tests_lib_test_srcdest_table_SOURCES = tests/lib/test_srcdest_table.c tests/helpers/c/prng.c tests_lib_test_stream_CFLAGS = $(TESTS_CFLAGS) tests_lib_test_stream_CPPFLAGS = $(TESTS_CPPFLAGS) tests_lib_test_stream_LDADD = $(ALL_TESTS_LDADD) tests_lib_test_stream_SOURCES = tests/lib/test_stream.c tests_lib_test_table_CFLAGS = $(TESTS_CFLAGS) tests_lib_test_table_CPPFLAGS = $(TESTS_CPPFLAGS) tests_lib_test_table_LDADD = $(ALL_TESTS_LDADD) -lm tests_lib_test_table_SOURCES = tests/lib/test_table.c tests_lib_test_timer_correctness_CFLAGS = $(TESTS_CFLAGS) tests_lib_test_timer_correctness_CPPFLAGS = $(TESTS_CPPFLAGS) tests_lib_test_timer_correctness_LDADD = $(ALL_TESTS_LDADD) tests_lib_test_timer_correctness_SOURCES = tests/lib/test_timer_correctness.c tests/helpers/c/prng.c tests_lib_test_timer_performance_CFLAGS = $(TESTS_CFLAGS) tests_lib_test_timer_performance_CPPFLAGS = $(TESTS_CPPFLAGS) tests_lib_test_timer_performance_LDADD = $(ALL_TESTS_LDADD) tests_lib_test_timer_performance_SOURCES = tests/lib/test_timer_performance.c tests/helpers/c/prng.c tests_lib_test_ttable_CFLAGS = $(TESTS_CFLAGS) tests_lib_test_ttable_CPPFLAGS = $(TESTS_CPPFLAGS) tests_lib_test_ttable_LDADD = $(ALL_TESTS_LDADD) tests_lib_test_ttable_SOURCES = tests/lib/test_ttable.c tests_lib_test_typelist_CFLAGS = $(TESTS_CFLAGS) tests_lib_test_typelist_CPPFLAGS = $(TESTS_CPPFLAGS) tests_lib_test_typelist_LDADD = $(ALL_TESTS_LDADD) tests_lib_test_typelist_SOURCES = tests/lib/test_typelist.c tests/helpers/c/prng.c tests_lib_test_zlog_CFLAGS = $(TESTS_CFLAGS) tests_lib_test_zlog_CPPFLAGS = $(TESTS_CPPFLAGS) tests_lib_test_zlog_LDADD = $(ALL_TESTS_LDADD) tests_lib_test_zlog_SOURCES = tests/lib/test_zlog.c tests_lib_test_zmq_CFLAGS = $(TESTS_CFLAGS) $(ZEROMQ_CFLAGS) tests_lib_test_zmq_CPPFLAGS = $(TESTS_CPPFLAGS) tests_lib_test_zmq_LDADD = lib/libfrrzmq.la $(ALL_TESTS_LDADD) $(ZEROMQ_LIBS) tests_lib_test_zmq_SOURCES = tests/lib/test_zmq.c tests_ospf6d_test_lsdb_CFLAGS = $(TESTS_CFLAGS) tests_ospf6d_test_lsdb_CPPFLAGS = $(TESTS_CPPFLAGS) tests_ospf6d_test_lsdb_LDADD = $(OSPF6_TEST_LDADD) tests_ospf6d_test_lsdb_SOURCES = tests/ospf6d/test_lsdb.c tests/lib/cli/common_cli.c TOPOTESTS_DIR = tests/topotests @PKGSRC_TRUE@rcdir = @pkgsrcrcdir@ @PKGSRC_TRUE@rc_SCRIPTS = \ @PKGSRC_TRUE@ pkgsrc/bgpd.sh \ @PKGSRC_TRUE@ pkgsrc/ospf6d.sh \ @PKGSRC_TRUE@ pkgsrc/ospfd.sh \ @PKGSRC_TRUE@ pkgsrc/ripd.sh \ @PKGSRC_TRUE@ pkgsrc/ripngd.sh \ @PKGSRC_TRUE@ pkgsrc/zebra.sh \ @PKGSRC_TRUE@ # end all: $(BUILT_SOURCES) config.h $(MAKE) $(AM_MAKEFLAGS) all-am .SUFFIXES: .SUFFIXES: _clippy.c .proto .pb-c.c .pb-c.h .pb.h .pb.cc .grpc.pb.cc .yang .yang.c .yin .yin.c .c .cc .cpp .l .lo .o .obj .y am--refresh: Makefile @: $(srcdir)/Makefile.in: $(srcdir)/Makefile.am $(srcdir)/doc/subdir.am $(srcdir)/doc/user/subdir.am $(srcdir)/doc/manpages/subdir.am $(srcdir)/doc/developer/subdir.am $(srcdir)/include/subdir.am $(srcdir)/lib/subdir.am $(srcdir)/zebra/subdir.am $(srcdir)/watchfrr/subdir.am $(srcdir)/qpb/subdir.am $(srcdir)/fpm/subdir.am $(srcdir)/grpc/subdir.am $(srcdir)/tools/subdir.am $(srcdir)/solaris/subdir.am $(srcdir)/bgpd/subdir.am $(srcdir)/bgpd/rfp-example/librfp/subdir.am $(srcdir)/bgpd/rfp-example/rfptest/subdir.am $(srcdir)/ripd/subdir.am $(srcdir)/ripngd/subdir.am $(srcdir)/ospfd/subdir.am $(srcdir)/ospf6d/subdir.am $(srcdir)/ospfclient/subdir.am $(srcdir)/isisd/subdir.am $(srcdir)/nhrpd/subdir.am $(srcdir)/ldpd/subdir.am $(srcdir)/babeld/subdir.am $(srcdir)/eigrpd/subdir.am $(srcdir)/sharpd/subdir.am $(srcdir)/pimd/subdir.am $(srcdir)/pbrd/subdir.am $(srcdir)/staticd/subdir.am $(srcdir)/bfdd/subdir.am $(srcdir)/yang/subdir.am $(srcdir)/yang/libyang_plugins/subdir.am $(srcdir)/vrrpd/subdir.am $(srcdir)/vtysh/subdir.am $(srcdir)/tests/subdir.am $(srcdir)/tests/topotests/subdir.am $(am__configure_deps) @for dep in $?; do \ case '$(am__configure_deps)' in \ *$$dep*) \ echo ' cd $(srcdir) && $(AUTOMAKE) --foreign'; \ $(am__cd) $(srcdir) && $(AUTOMAKE) --foreign \ && exit 0; \ exit 1;; \ esac; \ done; \ echo ' cd $(top_srcdir) && $(AUTOMAKE) --foreign Makefile'; \ $(am__cd) $(top_srcdir) && \ $(AUTOMAKE) --foreign Makefile Makefile: $(srcdir)/Makefile.in $(top_builddir)/config.status @case '$?' in \ *config.status*) \ echo ' $(SHELL) ./config.status'; \ $(SHELL) ./config.status;; \ *) \ echo ' cd $(top_builddir) && $(SHELL) ./config.status $@ $(am__maybe_remake_depfiles)'; \ cd $(top_builddir) && $(SHELL) ./config.status $@ $(am__maybe_remake_depfiles);; \ esac; $(srcdir)/doc/subdir.am $(srcdir)/doc/user/subdir.am $(srcdir)/doc/manpages/subdir.am $(srcdir)/doc/developer/subdir.am $(srcdir)/include/subdir.am $(srcdir)/lib/subdir.am $(srcdir)/zebra/subdir.am $(srcdir)/watchfrr/subdir.am $(srcdir)/qpb/subdir.am $(srcdir)/fpm/subdir.am $(srcdir)/grpc/subdir.am $(srcdir)/tools/subdir.am $(srcdir)/solaris/subdir.am $(srcdir)/bgpd/subdir.am $(srcdir)/bgpd/rfp-example/librfp/subdir.am $(srcdir)/bgpd/rfp-example/rfptest/subdir.am $(srcdir)/ripd/subdir.am $(srcdir)/ripngd/subdir.am $(srcdir)/ospfd/subdir.am $(srcdir)/ospf6d/subdir.am $(srcdir)/ospfclient/subdir.am $(srcdir)/isisd/subdir.am $(srcdir)/nhrpd/subdir.am $(srcdir)/ldpd/subdir.am $(srcdir)/babeld/subdir.am $(srcdir)/eigrpd/subdir.am $(srcdir)/sharpd/subdir.am $(srcdir)/pimd/subdir.am $(srcdir)/pbrd/subdir.am $(srcdir)/staticd/subdir.am $(srcdir)/bfdd/subdir.am $(srcdir)/yang/subdir.am $(srcdir)/yang/libyang_plugins/subdir.am $(srcdir)/vrrpd/subdir.am $(srcdir)/vtysh/subdir.am $(srcdir)/tests/subdir.am $(srcdir)/tests/topotests/subdir.am $(am__empty): $(top_builddir)/config.status: $(top_srcdir)/configure $(CONFIG_STATUS_DEPENDENCIES) $(SHELL) ./config.status --recheck $(top_srcdir)/configure: $(am__configure_deps) $(am__cd) $(srcdir) && $(AUTOCONF) $(ACLOCAL_M4): $(am__aclocal_m4_deps) $(am__cd) $(srcdir) && $(ACLOCAL) $(ACLOCAL_AMFLAGS) $(am__aclocal_m4_deps): config.h: stamp-h1 @test -f $@ || rm -f stamp-h1 @test -f $@ || $(MAKE) $(AM_MAKEFLAGS) stamp-h1 stamp-h1: $(srcdir)/config.h.in $(top_builddir)/config.status @rm -f stamp-h1 cd $(top_builddir) && $(SHELL) ./config.status config.h $(srcdir)/config.h.in: $(am__configure_deps) ($(am__cd) $(top_srcdir) && $(AUTOHEADER)) rm -f stamp-h1 touch $@ distclean-hdr: -rm -f config.h stamp-h1 config.version: $(top_builddir)/config.status $(srcdir)/config.version.in cd $(top_builddir) && $(SHELL) ./config.status $@ changelog-auto: $(top_builddir)/config.status $(srcdir)/changelog-auto.in cd $(top_builddir) && $(SHELL) ./config.status $@ redhat/frr.spec: $(top_builddir)/config.status $(top_srcdir)/redhat/frr.spec.in cd $(top_builddir) && $(SHELL) ./config.status $@ solaris/Makefile: $(top_builddir)/config.status $(top_srcdir)/solaris/Makefile.in cd $(top_builddir) && $(SHELL) ./config.status $@ alpine/APKBUILD: $(top_builddir)/config.status $(top_srcdir)/alpine/APKBUILD.in cd $(top_builddir) && $(SHELL) ./config.status $@ snapcraft/snapcraft.yaml: $(top_builddir)/config.status $(top_srcdir)/snapcraft/snapcraft.yaml.in cd $(top_builddir) && $(SHELL) ./config.status $@ lib/version.h: $(top_builddir)/config.status $(top_srcdir)/lib/version.h.in cd $(top_builddir) && $(SHELL) ./config.status $@ tests/lib/cli/test_cli.refout: $(top_builddir)/config.status $(top_srcdir)/tests/lib/cli/test_cli.refout.in cd $(top_builddir) && $(SHELL) ./config.status $@ pkgsrc/bgpd.sh: $(top_builddir)/config.status $(top_srcdir)/pkgsrc/bgpd.sh.in cd $(top_builddir) && $(SHELL) ./config.status $@ pkgsrc/ospf6d.sh: $(top_builddir)/config.status $(top_srcdir)/pkgsrc/ospf6d.sh.in cd $(top_builddir) && $(SHELL) ./config.status $@ pkgsrc/ospfd.sh: $(top_builddir)/config.status $(top_srcdir)/pkgsrc/ospfd.sh.in cd $(top_builddir) && $(SHELL) ./config.status $@ pkgsrc/ripd.sh: $(top_builddir)/config.status $(top_srcdir)/pkgsrc/ripd.sh.in cd $(top_builddir) && $(SHELL) ./config.status $@ pkgsrc/ripngd.sh: $(top_builddir)/config.status $(top_srcdir)/pkgsrc/ripngd.sh.in cd $(top_builddir) && $(SHELL) ./config.status $@ pkgsrc/zebra.sh: $(top_builddir)/config.status $(top_srcdir)/pkgsrc/zebra.sh.in cd $(top_builddir) && $(SHELL) ./config.status $@ pkgsrc/eigrpd.sh: $(top_builddir)/config.status $(top_srcdir)/pkgsrc/eigrpd.sh.in cd $(top_builddir) && $(SHELL) ./config.status $@ vtysh/extract.pl: $(top_builddir)/config.status $(top_srcdir)/vtysh/extract.pl.in cd $(top_builddir) && $(SHELL) ./config.status $@ tools/frr: $(top_builddir)/config.status $(top_srcdir)/tools/frr.in cd $(top_builddir) && $(SHELL) ./config.status $@ tools/watchfrr.sh: $(top_builddir)/config.status $(top_srcdir)/tools/watchfrr.sh.in cd $(top_builddir) && $(SHELL) ./config.status $@ tools/frrinit.sh: $(top_builddir)/config.status $(top_srcdir)/tools/frrinit.sh.in cd $(top_builddir) && $(SHELL) ./config.status $@ tools/frrcommon.sh: $(top_builddir)/config.status $(top_srcdir)/tools/frrcommon.sh.in cd $(top_builddir) && $(SHELL) ./config.status $@ install-binPROGRAMS: $(bin_PROGRAMS) @$(NORMAL_INSTALL) @list='$(bin_PROGRAMS)'; test -n "$(bindir)" || list=; \ if test -n "$$list"; then \ echo " $(MKDIR_P) '$(DESTDIR)$(bindir)'"; \ $(MKDIR_P) "$(DESTDIR)$(bindir)" || exit 1; \ fi; \ for p in $$list; do echo "$$p $$p"; done | \ sed 's/$(EXEEXT)$$//' | \ while read p p1; do if test -f $$p \ || test -f $$p1 \ ; then echo "$$p"; echo "$$p"; else :; fi; \ done | \ sed -e 'p;s,.*/,,;n;h' \ -e 's|.*|.|' \ -e 'p;x;s,.*/,,;s/$(EXEEXT)$$//;$(transform);s/$$/$(EXEEXT)/' | \ sed 'N;N;N;s,\n, ,g' | \ $(AWK) 'BEGIN { files["."] = ""; dirs["."] = 1 } \ { d=$$3; if (dirs[d] != 1) { print "d", d; dirs[d] = 1 } \ if ($$2 == $$4) files[d] = files[d] " " $$1; \ else { print "f", $$3 "/" $$4, $$1; } } \ END { for (d in files) print "f", d, files[d] }' | \ while read type dir files; do \ if test "$$dir" = .; then dir=; else dir=/$$dir; fi; \ test -z "$$files" || { \ echo " $(INSTALL_PROGRAM_ENV) $(LIBTOOL) $(AM_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=install $(INSTALL_PROGRAM) $$files '$(DESTDIR)$(bindir)$$dir'"; \ $(INSTALL_PROGRAM_ENV) $(LIBTOOL) $(AM_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=install $(INSTALL_PROGRAM) $$files "$(DESTDIR)$(bindir)$$dir" || exit $$?; \ } \ ; done uninstall-binPROGRAMS: @$(NORMAL_UNINSTALL) @list='$(bin_PROGRAMS)'; test -n "$(bindir)" || list=; \ files=`for p in $$list; do echo "$$p"; done | \ sed -e 'h;s,^.*/,,;s/$(EXEEXT)$$//;$(transform)' \ -e 's/$$/$(EXEEXT)/' \ `; \ test -n "$$list" || exit 0; \ echo " ( cd '$(DESTDIR)$(bindir)' && rm -f" $$files ")"; \ cd "$(DESTDIR)$(bindir)" && rm -f $$files clean-binPROGRAMS: @list='$(bin_PROGRAMS)'; test -n "$$list" || exit 0; \ echo " rm -f" $$list; \ rm -f $$list || exit $$?; \ test -n "$(EXEEXT)" || exit 0; \ list=`for p in $$list; do echo "$$p"; done | sed 's/$(EXEEXT)$$//'`; \ echo " rm -f" $$list; \ rm -f $$list clean-checkPROGRAMS: @list='$(check_PROGRAMS)'; test -n "$$list" || exit 0; \ echo " rm -f" $$list; \ rm -f $$list || exit $$?; \ test -n "$(EXEEXT)" || exit 0; \ list=`for p in $$list; do echo "$$p"; done | sed 's/$(EXEEXT)$$//'`; \ echo " rm -f" $$list; \ rm -f $$list clean-noinstPROGRAMS: @list='$(noinst_PROGRAMS)'; test -n "$$list" || exit 0; \ echo " rm -f" $$list; \ rm -f $$list || exit $$?; \ test -n "$(EXEEXT)" || exit 0; \ list=`for p in $$list; do echo "$$p"; done | sed 's/$(EXEEXT)$$//'`; \ echo " rm -f" $$list; \ rm -f $$list install-sbinPROGRAMS: $(sbin_PROGRAMS) @$(NORMAL_INSTALL) @list='$(sbin_PROGRAMS)'; test -n "$(sbindir)" || list=; \ if test -n "$$list"; then \ echo " $(MKDIR_P) '$(DESTDIR)$(sbindir)'"; \ $(MKDIR_P) "$(DESTDIR)$(sbindir)" || exit 1; \ fi; \ for p in $$list; do echo "$$p $$p"; done | \ sed 's/$(EXEEXT)$$//' | \ while read p p1; do if test -f $$p \ || test -f $$p1 \ ; then echo "$$p"; echo "$$p"; else :; fi; \ done | \ sed -e 'p;s,.*/,,;n;h' \ -e 's|.*|.|' \ -e 'p;x;s,.*/,,;s/$(EXEEXT)$$//;$(transform);s/$$/$(EXEEXT)/' | \ sed 'N;N;N;s,\n, ,g' | \ $(AWK) 'BEGIN { files["."] = ""; dirs["."] = 1 } \ { d=$$3; if (dirs[d] != 1) { print "d", d; dirs[d] = 1 } \ if ($$2 == $$4) files[d] = files[d] " " $$1; \ else { print "f", $$3 "/" $$4, $$1; } } \ END { for (d in files) print "f", d, files[d] }' | \ while read type dir files; do \ if test "$$dir" = .; then dir=; else dir=/$$dir; fi; \ test -z "$$files" || { \ echo " $(INSTALL_PROGRAM_ENV) $(LIBTOOL) $(AM_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=install $(INSTALL_PROGRAM) $$files '$(DESTDIR)$(sbindir)$$dir'"; \ $(INSTALL_PROGRAM_ENV) $(LIBTOOL) $(AM_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=install $(INSTALL_PROGRAM) $$files "$(DESTDIR)$(sbindir)$$dir" || exit $$?; \ } \ ; done uninstall-sbinPROGRAMS: @$(NORMAL_UNINSTALL) @list='$(sbin_PROGRAMS)'; test -n "$(sbindir)" || list=; \ files=`for p in $$list; do echo "$$p"; done | \ sed -e 'h;s,^.*/,,;s/$(EXEEXT)$$//;$(transform)' \ -e 's/$$/$(EXEEXT)/' \ `; \ test -n "$$list" || exit 0; \ echo " ( cd '$(DESTDIR)$(sbindir)' && rm -f" $$files ")"; \ cd "$(DESTDIR)$(sbindir)" && rm -f $$files clean-sbinPROGRAMS: @list='$(sbin_PROGRAMS)'; test -n "$$list" || exit 0; \ echo " rm -f" $$list; \ rm -f $$list || exit $$?; \ test -n "$(EXEEXT)" || exit 0; \ list=`for p in $$list; do echo "$$p"; done | sed 's/$(EXEEXT)$$//'`; \ echo " rm -f" $$list; \ rm -f $$list clean-noinstLIBRARIES: -test -z "$(noinst_LIBRARIES)" || rm -f $(noinst_LIBRARIES) install-libLTLIBRARIES: $(lib_LTLIBRARIES) @$(NORMAL_INSTALL) @list='$(lib_LTLIBRARIES)'; test -n "$(libdir)" || list=; \ list2=; for p in $$list; do \ if test -f $$p; then \ list2="$$list2 $$p"; \ else :; fi; \ done; \ test -z "$$list2" || { \ echo " $(MKDIR_P) '$(DESTDIR)$(libdir)'"; \ $(MKDIR_P) "$(DESTDIR)$(libdir)" || exit 1; \ echo " $(LIBTOOL) $(AM_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=install $(INSTALL) $(INSTALL_STRIP_FLAG) $$list2 '$(DESTDIR)$(libdir)'"; \ $(LIBTOOL) $(AM_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=install $(INSTALL) $(INSTALL_STRIP_FLAG) $$list2 "$(DESTDIR)$(libdir)"; \ } uninstall-libLTLIBRARIES: @$(NORMAL_UNINSTALL) @list='$(lib_LTLIBRARIES)'; test -n "$(libdir)" || list=; \ for p in $$list; do \ $(am__strip_dir) \ echo " $(LIBTOOL) $(AM_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=uninstall rm -f '$(DESTDIR)$(libdir)/$$f'"; \ $(LIBTOOL) $(AM_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=uninstall rm -f "$(DESTDIR)$(libdir)/$$f"; \ done clean-libLTLIBRARIES: -test -z "$(lib_LTLIBRARIES)" || rm -f $(lib_LTLIBRARIES) @list='$(lib_LTLIBRARIES)'; \ locs=`for p in $$list; do echo $$p; done | \ sed 's|^[^/]*$$|.|; s|/[^/]*$$||; s|$$|/so_locations|' | \ sort -u`; \ test -z "$$locs" || { \ echo rm -f $${locs}; \ rm -f $${locs}; \ } install-moduleLTLIBRARIES: $(module_LTLIBRARIES) @$(NORMAL_INSTALL) @list='$(module_LTLIBRARIES)'; test -n "$(moduledir)" || list=; \ list2=; for p in $$list; do \ if test -f $$p; then \ list2="$$list2 $$p"; \ else :; fi; \ done; \ test -z "$$list2" || { \ echo " $(MKDIR_P) '$(DESTDIR)$(moduledir)'"; \ $(MKDIR_P) "$(DESTDIR)$(moduledir)" || exit 1; \ echo " $(LIBTOOL) $(AM_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=install $(INSTALL) $(INSTALL_STRIP_FLAG) $$list2 '$(DESTDIR)$(moduledir)'"; \ $(LIBTOOL) $(AM_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=install $(INSTALL) $(INSTALL_STRIP_FLAG) $$list2 "$(DESTDIR)$(moduledir)"; \ } uninstall-moduleLTLIBRARIES: @$(NORMAL_UNINSTALL) @list='$(module_LTLIBRARIES)'; test -n "$(moduledir)" || list=; \ for p in $$list; do \ $(am__strip_dir) \ echo " $(LIBTOOL) $(AM_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=uninstall rm -f '$(DESTDIR)$(moduledir)/$$f'"; \ $(LIBTOOL) $(AM_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=uninstall rm -f "$(DESTDIR)$(moduledir)/$$f"; \ done clean-moduleLTLIBRARIES: -test -z "$(module_LTLIBRARIES)" || rm -f $(module_LTLIBRARIES) @list='$(module_LTLIBRARIES)'; \ locs=`for p in $$list; do echo $$p; done | \ sed 's|^[^/]*$$|.|; s|/[^/]*$$||; s|$$|/so_locations|' | \ sort -u`; \ test -z "$$locs" || { \ echo rm -f $${locs}; \ rm -f $${locs}; \ } babeld/$(am__dirstamp): @$(MKDIR_P) babeld @: > babeld/$(am__dirstamp) babeld/$(DEPDIR)/$(am__dirstamp): @$(MKDIR_P) babeld/$(DEPDIR) @: > babeld/$(DEPDIR)/$(am__dirstamp) babeld/babel_errors.$(OBJEXT): babeld/$(am__dirstamp) \ babeld/$(DEPDIR)/$(am__dirstamp) babeld/babel_filter.$(OBJEXT): babeld/$(am__dirstamp) \ babeld/$(DEPDIR)/$(am__dirstamp) babeld/babel_interface.$(OBJEXT): babeld/$(am__dirstamp) \ babeld/$(DEPDIR)/$(am__dirstamp) babeld/babel_zebra.$(OBJEXT): babeld/$(am__dirstamp) \ babeld/$(DEPDIR)/$(am__dirstamp) babeld/babeld.$(OBJEXT): babeld/$(am__dirstamp) \ babeld/$(DEPDIR)/$(am__dirstamp) babeld/kernel.$(OBJEXT): babeld/$(am__dirstamp) \ babeld/$(DEPDIR)/$(am__dirstamp) babeld/message.$(OBJEXT): babeld/$(am__dirstamp) \ babeld/$(DEPDIR)/$(am__dirstamp) babeld/neighbour.$(OBJEXT): babeld/$(am__dirstamp) \ babeld/$(DEPDIR)/$(am__dirstamp) babeld/net.$(OBJEXT): babeld/$(am__dirstamp) \ babeld/$(DEPDIR)/$(am__dirstamp) babeld/resend.$(OBJEXT): babeld/$(am__dirstamp) \ babeld/$(DEPDIR)/$(am__dirstamp) babeld/route.$(OBJEXT): babeld/$(am__dirstamp) \ babeld/$(DEPDIR)/$(am__dirstamp) babeld/source.$(OBJEXT): babeld/$(am__dirstamp) \ babeld/$(DEPDIR)/$(am__dirstamp) babeld/util.$(OBJEXT): babeld/$(am__dirstamp) \ babeld/$(DEPDIR)/$(am__dirstamp) babeld/xroute.$(OBJEXT): babeld/$(am__dirstamp) \ babeld/$(DEPDIR)/$(am__dirstamp) babeld/libbabel.a: $(babeld_libbabel_a_OBJECTS) $(babeld_libbabel_a_DEPENDENCIES) $(EXTRA_babeld_libbabel_a_DEPENDENCIES) babeld/$(am__dirstamp) $(AM_V_at)-rm -f babeld/libbabel.a $(AM_V_AR)$(babeld_libbabel_a_AR) babeld/libbabel.a $(babeld_libbabel_a_OBJECTS) $(babeld_libbabel_a_LIBADD) $(AM_V_at)$(RANLIB) babeld/libbabel.a bfdd/$(am__dirstamp): @$(MKDIR_P) bfdd @: > bfdd/$(am__dirstamp) bfdd/$(DEPDIR)/$(am__dirstamp): @$(MKDIR_P) bfdd/$(DEPDIR) @: > bfdd/$(DEPDIR)/$(am__dirstamp) bfdd/bfd.$(OBJEXT): bfdd/$(am__dirstamp) \ bfdd/$(DEPDIR)/$(am__dirstamp) bfdd/bfdd_northbound.$(OBJEXT): bfdd/$(am__dirstamp) \ bfdd/$(DEPDIR)/$(am__dirstamp) bfdd/bfdd_vty.$(OBJEXT): bfdd/$(am__dirstamp) \ bfdd/$(DEPDIR)/$(am__dirstamp) bfdd/bfdd_cli.$(OBJEXT): bfdd/$(am__dirstamp) \ bfdd/$(DEPDIR)/$(am__dirstamp) bfdd/bfd_packet.$(OBJEXT): bfdd/$(am__dirstamp) \ bfdd/$(DEPDIR)/$(am__dirstamp) bfdd/config.$(OBJEXT): bfdd/$(am__dirstamp) \ bfdd/$(DEPDIR)/$(am__dirstamp) bfdd/control.$(OBJEXT): bfdd/$(am__dirstamp) \ bfdd/$(DEPDIR)/$(am__dirstamp) bfdd/event.$(OBJEXT): bfdd/$(am__dirstamp) \ bfdd/$(DEPDIR)/$(am__dirstamp) bfdd/log.$(OBJEXT): bfdd/$(am__dirstamp) \ bfdd/$(DEPDIR)/$(am__dirstamp) bfdd/ptm_adapter.$(OBJEXT): bfdd/$(am__dirstamp) \ bfdd/$(DEPDIR)/$(am__dirstamp) bfdd/libbfd.a: $(bfdd_libbfd_a_OBJECTS) $(bfdd_libbfd_a_DEPENDENCIES) $(EXTRA_bfdd_libbfd_a_DEPENDENCIES) bfdd/$(am__dirstamp) $(AM_V_at)-rm -f bfdd/libbfd.a $(AM_V_AR)$(bfdd_libbfd_a_AR) bfdd/libbfd.a $(bfdd_libbfd_a_OBJECTS) $(bfdd_libbfd_a_LIBADD) $(AM_V_at)$(RANLIB) bfdd/libbfd.a bgpd/$(am__dirstamp): @$(MKDIR_P) bgpd @: > bgpd/$(am__dirstamp) bgpd/$(DEPDIR)/$(am__dirstamp): @$(MKDIR_P) bgpd/$(DEPDIR) @: > bgpd/$(DEPDIR)/$(am__dirstamp) bgpd/bgp_addpath.$(OBJEXT): bgpd/$(am__dirstamp) \ bgpd/$(DEPDIR)/$(am__dirstamp) bgpd/bgp_advertise.$(OBJEXT): bgpd/$(am__dirstamp) \ bgpd/$(DEPDIR)/$(am__dirstamp) bgpd/bgp_aspath.$(OBJEXT): bgpd/$(am__dirstamp) \ bgpd/$(DEPDIR)/$(am__dirstamp) bgpd/bgp_attr.$(OBJEXT): bgpd/$(am__dirstamp) \ bgpd/$(DEPDIR)/$(am__dirstamp) bgpd/bgp_attr_evpn.$(OBJEXT): bgpd/$(am__dirstamp) \ bgpd/$(DEPDIR)/$(am__dirstamp) bgpd/bgp_bfd.$(OBJEXT): bgpd/$(am__dirstamp) \ bgpd/$(DEPDIR)/$(am__dirstamp) bgpd/bgp_clist.$(OBJEXT): bgpd/$(am__dirstamp) \ bgpd/$(DEPDIR)/$(am__dirstamp) bgpd/bgp_community.$(OBJEXT): bgpd/$(am__dirstamp) \ bgpd/$(DEPDIR)/$(am__dirstamp) bgpd/bgp_damp.$(OBJEXT): bgpd/$(am__dirstamp) \ bgpd/$(DEPDIR)/$(am__dirstamp) bgpd/bgp_debug.$(OBJEXT): bgpd/$(am__dirstamp) \ bgpd/$(DEPDIR)/$(am__dirstamp) bgpd/bgp_dump.$(OBJEXT): bgpd/$(am__dirstamp) \ bgpd/$(DEPDIR)/$(am__dirstamp) bgpd/bgp_ecommunity.$(OBJEXT): bgpd/$(am__dirstamp) \ bgpd/$(DEPDIR)/$(am__dirstamp) bgpd/bgp_encap_tlv.$(OBJEXT): bgpd/$(am__dirstamp) \ bgpd/$(DEPDIR)/$(am__dirstamp) bgpd/bgp_errors.$(OBJEXT): bgpd/$(am__dirstamp) \ bgpd/$(DEPDIR)/$(am__dirstamp) bgpd/bgp_evpn.$(OBJEXT): bgpd/$(am__dirstamp) \ bgpd/$(DEPDIR)/$(am__dirstamp) bgpd/bgp_evpn_vty.$(OBJEXT): bgpd/$(am__dirstamp) \ bgpd/$(DEPDIR)/$(am__dirstamp) bgpd/bgp_filter.$(OBJEXT): bgpd/$(am__dirstamp) \ bgpd/$(DEPDIR)/$(am__dirstamp) bgpd/bgp_flowspec.$(OBJEXT): bgpd/$(am__dirstamp) \ bgpd/$(DEPDIR)/$(am__dirstamp) bgpd/bgp_flowspec_util.$(OBJEXT): bgpd/$(am__dirstamp) \ bgpd/$(DEPDIR)/$(am__dirstamp) bgpd/bgp_flowspec_vty.$(OBJEXT): bgpd/$(am__dirstamp) \ bgpd/$(DEPDIR)/$(am__dirstamp) bgpd/bgp_fsm.$(OBJEXT): bgpd/$(am__dirstamp) \ bgpd/$(DEPDIR)/$(am__dirstamp) bgpd/bgp_io.$(OBJEXT): bgpd/$(am__dirstamp) \ bgpd/$(DEPDIR)/$(am__dirstamp) bgpd/bgp_keepalives.$(OBJEXT): bgpd/$(am__dirstamp) \ bgpd/$(DEPDIR)/$(am__dirstamp) bgpd/bgp_label.$(OBJEXT): bgpd/$(am__dirstamp) \ bgpd/$(DEPDIR)/$(am__dirstamp) bgpd/bgp_labelpool.$(OBJEXT): bgpd/$(am__dirstamp) \ bgpd/$(DEPDIR)/$(am__dirstamp) bgpd/bgp_lcommunity.$(OBJEXT): bgpd/$(am__dirstamp) \ bgpd/$(DEPDIR)/$(am__dirstamp) bgpd/bgp_mac.$(OBJEXT): bgpd/$(am__dirstamp) \ bgpd/$(DEPDIR)/$(am__dirstamp) bgpd/bgp_memory.$(OBJEXT): bgpd/$(am__dirstamp) \ bgpd/$(DEPDIR)/$(am__dirstamp) bgpd/bgp_mpath.$(OBJEXT): bgpd/$(am__dirstamp) \ bgpd/$(DEPDIR)/$(am__dirstamp) bgpd/bgp_mplsvpn.$(OBJEXT): bgpd/$(am__dirstamp) \ bgpd/$(DEPDIR)/$(am__dirstamp) bgpd/bgp_network.$(OBJEXT): bgpd/$(am__dirstamp) \ bgpd/$(DEPDIR)/$(am__dirstamp) bgpd/bgp_nexthop.$(OBJEXT): bgpd/$(am__dirstamp) \ bgpd/$(DEPDIR)/$(am__dirstamp) bgpd/bgp_nht.$(OBJEXT): bgpd/$(am__dirstamp) \ bgpd/$(DEPDIR)/$(am__dirstamp) bgpd/bgp_open.$(OBJEXT): bgpd/$(am__dirstamp) \ bgpd/$(DEPDIR)/$(am__dirstamp) bgpd/bgp_packet.$(OBJEXT): bgpd/$(am__dirstamp) \ bgpd/$(DEPDIR)/$(am__dirstamp) bgpd/bgp_pbr.$(OBJEXT): bgpd/$(am__dirstamp) \ bgpd/$(DEPDIR)/$(am__dirstamp) bgpd/bgp_rd.$(OBJEXT): bgpd/$(am__dirstamp) \ bgpd/$(DEPDIR)/$(am__dirstamp) bgpd/bgp_regex.$(OBJEXT): bgpd/$(am__dirstamp) \ bgpd/$(DEPDIR)/$(am__dirstamp) bgpd/bgp_route.$(OBJEXT): bgpd/$(am__dirstamp) \ bgpd/$(DEPDIR)/$(am__dirstamp) bgpd/bgp_routemap.$(OBJEXT): bgpd/$(am__dirstamp) \ bgpd/$(DEPDIR)/$(am__dirstamp) bgpd/bgp_table.$(OBJEXT): bgpd/$(am__dirstamp) \ bgpd/$(DEPDIR)/$(am__dirstamp) bgpd/bgp_updgrp.$(OBJEXT): bgpd/$(am__dirstamp) \ bgpd/$(DEPDIR)/$(am__dirstamp) bgpd/bgp_updgrp_adv.$(OBJEXT): bgpd/$(am__dirstamp) \ bgpd/$(DEPDIR)/$(am__dirstamp) bgpd/bgp_updgrp_packet.$(OBJEXT): bgpd/$(am__dirstamp) \ bgpd/$(DEPDIR)/$(am__dirstamp) bgpd/bgp_vpn.$(OBJEXT): bgpd/$(am__dirstamp) \ bgpd/$(DEPDIR)/$(am__dirstamp) bgpd/bgp_vty.$(OBJEXT): bgpd/$(am__dirstamp) \ bgpd/$(DEPDIR)/$(am__dirstamp) bgpd/bgp_zebra.$(OBJEXT): bgpd/$(am__dirstamp) \ bgpd/$(DEPDIR)/$(am__dirstamp) bgpd/bgpd.$(OBJEXT): bgpd/$(am__dirstamp) \ bgpd/$(DEPDIR)/$(am__dirstamp) bgpd/rfapi/$(am__dirstamp): @$(MKDIR_P) bgpd/rfapi @: > bgpd/rfapi/$(am__dirstamp) bgpd/rfapi/$(DEPDIR)/$(am__dirstamp): @$(MKDIR_P) bgpd/rfapi/$(DEPDIR) @: > bgpd/rfapi/$(DEPDIR)/$(am__dirstamp) bgpd/rfapi/bgp_rfapi_cfg.$(OBJEXT): bgpd/rfapi/$(am__dirstamp) \ bgpd/rfapi/$(DEPDIR)/$(am__dirstamp) bgpd/rfapi/rfapi_import.$(OBJEXT): bgpd/rfapi/$(am__dirstamp) \ bgpd/rfapi/$(DEPDIR)/$(am__dirstamp) bgpd/rfapi/rfapi.$(OBJEXT): bgpd/rfapi/$(am__dirstamp) \ bgpd/rfapi/$(DEPDIR)/$(am__dirstamp) bgpd/rfapi/rfapi_ap.$(OBJEXT): bgpd/rfapi/$(am__dirstamp) \ bgpd/rfapi/$(DEPDIR)/$(am__dirstamp) bgpd/rfapi/rfapi_descriptor_rfp_utils.$(OBJEXT): \ bgpd/rfapi/$(am__dirstamp) \ bgpd/rfapi/$(DEPDIR)/$(am__dirstamp) bgpd/rfapi/rfapi_encap_tlv.$(OBJEXT): bgpd/rfapi/$(am__dirstamp) \ bgpd/rfapi/$(DEPDIR)/$(am__dirstamp) bgpd/rfapi/rfapi_nve_addr.$(OBJEXT): bgpd/rfapi/$(am__dirstamp) \ bgpd/rfapi/$(DEPDIR)/$(am__dirstamp) bgpd/rfapi/rfapi_monitor.$(OBJEXT): bgpd/rfapi/$(am__dirstamp) \ bgpd/rfapi/$(DEPDIR)/$(am__dirstamp) bgpd/rfapi/rfapi_rib.$(OBJEXT): bgpd/rfapi/$(am__dirstamp) \ bgpd/rfapi/$(DEPDIR)/$(am__dirstamp) bgpd/rfapi/rfapi_vty.$(OBJEXT): bgpd/rfapi/$(am__dirstamp) \ bgpd/rfapi/$(DEPDIR)/$(am__dirstamp) bgpd/rfapi/vnc_debug.$(OBJEXT): bgpd/rfapi/$(am__dirstamp) \ bgpd/rfapi/$(DEPDIR)/$(am__dirstamp) bgpd/rfapi/vnc_export_bgp.$(OBJEXT): bgpd/rfapi/$(am__dirstamp) \ bgpd/rfapi/$(DEPDIR)/$(am__dirstamp) bgpd/rfapi/vnc_export_table.$(OBJEXT): bgpd/rfapi/$(am__dirstamp) \ bgpd/rfapi/$(DEPDIR)/$(am__dirstamp) bgpd/rfapi/vnc_import_bgp.$(OBJEXT): bgpd/rfapi/$(am__dirstamp) \ bgpd/rfapi/$(DEPDIR)/$(am__dirstamp) bgpd/rfapi/vnc_zebra.$(OBJEXT): bgpd/rfapi/$(am__dirstamp) \ bgpd/rfapi/$(DEPDIR)/$(am__dirstamp) bgpd/libbgp.a: $(bgpd_libbgp_a_OBJECTS) $(bgpd_libbgp_a_DEPENDENCIES) $(EXTRA_bgpd_libbgp_a_DEPENDENCIES) bgpd/$(am__dirstamp) $(AM_V_at)-rm -f bgpd/libbgp.a $(AM_V_AR)$(bgpd_libbgp_a_AR) bgpd/libbgp.a $(bgpd_libbgp_a_OBJECTS) $(bgpd_libbgp_a_LIBADD) $(AM_V_at)$(RANLIB) bgpd/libbgp.a bgpd/rfp-example/librfp/$(am__dirstamp): @$(MKDIR_P) bgpd/rfp-example/librfp @: > bgpd/rfp-example/librfp/$(am__dirstamp) bgpd/rfp-example/librfp/$(DEPDIR)/$(am__dirstamp): @$(MKDIR_P) bgpd/rfp-example/librfp/$(DEPDIR) @: > bgpd/rfp-example/librfp/$(DEPDIR)/$(am__dirstamp) bgpd/rfp-example/librfp/rfp_example.$(OBJEXT): \ bgpd/rfp-example/librfp/$(am__dirstamp) \ bgpd/rfp-example/librfp/$(DEPDIR)/$(am__dirstamp) bgpd/rfp-example/librfp/librfp.a: $(bgpd_rfp_example_librfp_librfp_a_OBJECTS) $(bgpd_rfp_example_librfp_librfp_a_DEPENDENCIES) $(EXTRA_bgpd_rfp_example_librfp_librfp_a_DEPENDENCIES) bgpd/rfp-example/librfp/$(am__dirstamp) $(AM_V_at)-rm -f bgpd/rfp-example/librfp/librfp.a $(AM_V_AR)$(bgpd_rfp_example_librfp_librfp_a_AR) bgpd/rfp-example/librfp/librfp.a $(bgpd_rfp_example_librfp_librfp_a_OBJECTS) $(bgpd_rfp_example_librfp_librfp_a_LIBADD) $(AM_V_at)$(RANLIB) bgpd/rfp-example/librfp/librfp.a eigrpd/$(am__dirstamp): @$(MKDIR_P) eigrpd @: > eigrpd/$(am__dirstamp) eigrpd/$(DEPDIR)/$(am__dirstamp): @$(MKDIR_P) eigrpd/$(DEPDIR) @: > eigrpd/$(DEPDIR)/$(am__dirstamp) eigrpd/eigrp_cli.$(OBJEXT): eigrpd/$(am__dirstamp) \ eigrpd/$(DEPDIR)/$(am__dirstamp) eigrpd/eigrp_dump.$(OBJEXT): eigrpd/$(am__dirstamp) \ eigrpd/$(DEPDIR)/$(am__dirstamp) eigrpd/eigrp_errors.$(OBJEXT): eigrpd/$(am__dirstamp) \ eigrpd/$(DEPDIR)/$(am__dirstamp) eigrpd/eigrp_filter.$(OBJEXT): eigrpd/$(am__dirstamp) \ eigrpd/$(DEPDIR)/$(am__dirstamp) eigrpd/eigrp_fsm.$(OBJEXT): eigrpd/$(am__dirstamp) \ eigrpd/$(DEPDIR)/$(am__dirstamp) eigrpd/eigrp_hello.$(OBJEXT): eigrpd/$(am__dirstamp) \ eigrpd/$(DEPDIR)/$(am__dirstamp) eigrpd/eigrp_interface.$(OBJEXT): eigrpd/$(am__dirstamp) \ eigrpd/$(DEPDIR)/$(am__dirstamp) eigrpd/eigrp_memory.$(OBJEXT): eigrpd/$(am__dirstamp) \ eigrpd/$(DEPDIR)/$(am__dirstamp) eigrpd/eigrp_neighbor.$(OBJEXT): eigrpd/$(am__dirstamp) \ eigrpd/$(DEPDIR)/$(am__dirstamp) eigrpd/eigrp_network.$(OBJEXT): eigrpd/$(am__dirstamp) \ eigrpd/$(DEPDIR)/$(am__dirstamp) eigrpd/eigrp_northbound.$(OBJEXT): eigrpd/$(am__dirstamp) \ eigrpd/$(DEPDIR)/$(am__dirstamp) eigrpd/eigrp_packet.$(OBJEXT): eigrpd/$(am__dirstamp) \ eigrpd/$(DEPDIR)/$(am__dirstamp) eigrpd/eigrp_query.$(OBJEXT): eigrpd/$(am__dirstamp) \ eigrpd/$(DEPDIR)/$(am__dirstamp) eigrpd/eigrp_reply.$(OBJEXT): eigrpd/$(am__dirstamp) \ eigrpd/$(DEPDIR)/$(am__dirstamp) eigrpd/eigrp_siaquery.$(OBJEXT): eigrpd/$(am__dirstamp) \ eigrpd/$(DEPDIR)/$(am__dirstamp) eigrpd/eigrp_siareply.$(OBJEXT): eigrpd/$(am__dirstamp) \ eigrpd/$(DEPDIR)/$(am__dirstamp) eigrpd/eigrp_snmp.$(OBJEXT): eigrpd/$(am__dirstamp) \ eigrpd/$(DEPDIR)/$(am__dirstamp) eigrpd/eigrp_topology.$(OBJEXT): eigrpd/$(am__dirstamp) \ eigrpd/$(DEPDIR)/$(am__dirstamp) eigrpd/eigrp_update.$(OBJEXT): eigrpd/$(am__dirstamp) \ eigrpd/$(DEPDIR)/$(am__dirstamp) eigrpd/eigrp_vrf.$(OBJEXT): eigrpd/$(am__dirstamp) \ eigrpd/$(DEPDIR)/$(am__dirstamp) eigrpd/eigrp_vty.$(OBJEXT): eigrpd/$(am__dirstamp) \ eigrpd/$(DEPDIR)/$(am__dirstamp) eigrpd/eigrp_zebra.$(OBJEXT): eigrpd/$(am__dirstamp) \ eigrpd/$(DEPDIR)/$(am__dirstamp) eigrpd/eigrpd.$(OBJEXT): eigrpd/$(am__dirstamp) \ eigrpd/$(DEPDIR)/$(am__dirstamp) eigrpd/libeigrp.a: $(eigrpd_libeigrp_a_OBJECTS) $(eigrpd_libeigrp_a_DEPENDENCIES) $(EXTRA_eigrpd_libeigrp_a_DEPENDENCIES) eigrpd/$(am__dirstamp) $(AM_V_at)-rm -f eigrpd/libeigrp.a $(AM_V_AR)$(eigrpd_libeigrp_a_AR) eigrpd/libeigrp.a $(eigrpd_libeigrp_a_OBJECTS) $(eigrpd_libeigrp_a_LIBADD) $(AM_V_at)$(RANLIB) eigrpd/libeigrp.a isisd/$(am__dirstamp): @$(MKDIR_P) isisd @: > isisd/$(am__dirstamp) isisd/$(DEPDIR)/$(am__dirstamp): @$(MKDIR_P) isisd/$(DEPDIR) @: > isisd/$(DEPDIR)/$(am__dirstamp) isisd/libfabric_a-isis_adjacency.$(OBJEXT): isisd/$(am__dirstamp) \ isisd/$(DEPDIR)/$(am__dirstamp) isisd/libfabric_a-isis_bfd.$(OBJEXT): isisd/$(am__dirstamp) \ isisd/$(DEPDIR)/$(am__dirstamp) isisd/libfabric_a-isis_circuit.$(OBJEXT): isisd/$(am__dirstamp) \ isisd/$(DEPDIR)/$(am__dirstamp) isisd/libfabric_a-isis_csm.$(OBJEXT): isisd/$(am__dirstamp) \ isisd/$(DEPDIR)/$(am__dirstamp) isisd/libfabric_a-isis_dr.$(OBJEXT): isisd/$(am__dirstamp) \ isisd/$(DEPDIR)/$(am__dirstamp) isisd/libfabric_a-isis_dynhn.$(OBJEXT): isisd/$(am__dirstamp) \ isisd/$(DEPDIR)/$(am__dirstamp) isisd/libfabric_a-isis_errors.$(OBJEXT): isisd/$(am__dirstamp) \ isisd/$(DEPDIR)/$(am__dirstamp) isisd/libfabric_a-isis_events.$(OBJEXT): isisd/$(am__dirstamp) \ isisd/$(DEPDIR)/$(am__dirstamp) isisd/libfabric_a-isis_flags.$(OBJEXT): isisd/$(am__dirstamp) \ isisd/$(DEPDIR)/$(am__dirstamp) isisd/libfabric_a-isis_lsp.$(OBJEXT): isisd/$(am__dirstamp) \ isisd/$(DEPDIR)/$(am__dirstamp) isisd/libfabric_a-isis_memory.$(OBJEXT): isisd/$(am__dirstamp) \ isisd/$(DEPDIR)/$(am__dirstamp) isisd/libfabric_a-isis_misc.$(OBJEXT): isisd/$(am__dirstamp) \ isisd/$(DEPDIR)/$(am__dirstamp) isisd/libfabric_a-isis_mt.$(OBJEXT): isisd/$(am__dirstamp) \ isisd/$(DEPDIR)/$(am__dirstamp) isisd/libfabric_a-isis_pdu.$(OBJEXT): isisd/$(am__dirstamp) \ isisd/$(DEPDIR)/$(am__dirstamp) isisd/libfabric_a-isis_pdu_counter.$(OBJEXT): isisd/$(am__dirstamp) \ isisd/$(DEPDIR)/$(am__dirstamp) isisd/libfabric_a-isis_redist.$(OBJEXT): isisd/$(am__dirstamp) \ isisd/$(DEPDIR)/$(am__dirstamp) isisd/libfabric_a-isis_route.$(OBJEXT): isisd/$(am__dirstamp) \ isisd/$(DEPDIR)/$(am__dirstamp) isisd/libfabric_a-isis_routemap.$(OBJEXT): isisd/$(am__dirstamp) \ isisd/$(DEPDIR)/$(am__dirstamp) isisd/libfabric_a-isis_spf.$(OBJEXT): isisd/$(am__dirstamp) \ isisd/$(DEPDIR)/$(am__dirstamp) isisd/libfabric_a-isis_te.$(OBJEXT): isisd/$(am__dirstamp) \ isisd/$(DEPDIR)/$(am__dirstamp) isisd/libfabric_a-isis_tlvs.$(OBJEXT): isisd/$(am__dirstamp) \ isisd/$(DEPDIR)/$(am__dirstamp) isisd/libfabric_a-isis_tx_queue.$(OBJEXT): isisd/$(am__dirstamp) \ isisd/$(DEPDIR)/$(am__dirstamp) isisd/libfabric_a-isis_zebra.$(OBJEXT): isisd/$(am__dirstamp) \ isisd/$(DEPDIR)/$(am__dirstamp) isisd/libfabric_a-isisd.$(OBJEXT): isisd/$(am__dirstamp) \ isisd/$(DEPDIR)/$(am__dirstamp) isisd/libfabric_a-iso_checksum.$(OBJEXT): isisd/$(am__dirstamp) \ isisd/$(DEPDIR)/$(am__dirstamp) isisd/libfabric_a-fabricd.$(OBJEXT): isisd/$(am__dirstamp) \ isisd/$(DEPDIR)/$(am__dirstamp) isisd/libfabric_a-isis_vty_fabricd.$(OBJEXT): isisd/$(am__dirstamp) \ isisd/$(DEPDIR)/$(am__dirstamp) isisd/libfabric.a: $(isisd_libfabric_a_OBJECTS) $(isisd_libfabric_a_DEPENDENCIES) $(EXTRA_isisd_libfabric_a_DEPENDENCIES) isisd/$(am__dirstamp) $(AM_V_at)-rm -f isisd/libfabric.a $(AM_V_AR)$(isisd_libfabric_a_AR) isisd/libfabric.a $(isisd_libfabric_a_OBJECTS) $(isisd_libfabric_a_LIBADD) $(AM_V_at)$(RANLIB) isisd/libfabric.a isisd/isis_adjacency.$(OBJEXT): isisd/$(am__dirstamp) \ isisd/$(DEPDIR)/$(am__dirstamp) isisd/isis_bfd.$(OBJEXT): isisd/$(am__dirstamp) \ isisd/$(DEPDIR)/$(am__dirstamp) isisd/isis_circuit.$(OBJEXT): isisd/$(am__dirstamp) \ isisd/$(DEPDIR)/$(am__dirstamp) isisd/isis_csm.$(OBJEXT): isisd/$(am__dirstamp) \ isisd/$(DEPDIR)/$(am__dirstamp) isisd/isis_dr.$(OBJEXT): isisd/$(am__dirstamp) \ isisd/$(DEPDIR)/$(am__dirstamp) isisd/isis_dynhn.$(OBJEXT): isisd/$(am__dirstamp) \ isisd/$(DEPDIR)/$(am__dirstamp) isisd/isis_errors.$(OBJEXT): isisd/$(am__dirstamp) \ isisd/$(DEPDIR)/$(am__dirstamp) isisd/isis_events.$(OBJEXT): isisd/$(am__dirstamp) \ isisd/$(DEPDIR)/$(am__dirstamp) isisd/isis_flags.$(OBJEXT): isisd/$(am__dirstamp) \ isisd/$(DEPDIR)/$(am__dirstamp) isisd/isis_lsp.$(OBJEXT): isisd/$(am__dirstamp) \ isisd/$(DEPDIR)/$(am__dirstamp) isisd/isis_memory.$(OBJEXT): isisd/$(am__dirstamp) \ isisd/$(DEPDIR)/$(am__dirstamp) isisd/isis_misc.$(OBJEXT): isisd/$(am__dirstamp) \ isisd/$(DEPDIR)/$(am__dirstamp) isisd/isis_mt.$(OBJEXT): isisd/$(am__dirstamp) \ isisd/$(DEPDIR)/$(am__dirstamp) isisd/isis_pdu.$(OBJEXT): isisd/$(am__dirstamp) \ isisd/$(DEPDIR)/$(am__dirstamp) isisd/isis_pdu_counter.$(OBJEXT): isisd/$(am__dirstamp) \ isisd/$(DEPDIR)/$(am__dirstamp) isisd/isis_redist.$(OBJEXT): isisd/$(am__dirstamp) \ isisd/$(DEPDIR)/$(am__dirstamp) isisd/isis_route.$(OBJEXT): isisd/$(am__dirstamp) \ isisd/$(DEPDIR)/$(am__dirstamp) isisd/isis_routemap.$(OBJEXT): isisd/$(am__dirstamp) \ isisd/$(DEPDIR)/$(am__dirstamp) isisd/isis_spf.$(OBJEXT): isisd/$(am__dirstamp) \ isisd/$(DEPDIR)/$(am__dirstamp) isisd/isis_te.$(OBJEXT): isisd/$(am__dirstamp) \ isisd/$(DEPDIR)/$(am__dirstamp) isisd/isis_tlvs.$(OBJEXT): isisd/$(am__dirstamp) \ isisd/$(DEPDIR)/$(am__dirstamp) isisd/isis_tx_queue.$(OBJEXT): isisd/$(am__dirstamp) \ isisd/$(DEPDIR)/$(am__dirstamp) isisd/isis_zebra.$(OBJEXT): isisd/$(am__dirstamp) \ isisd/$(DEPDIR)/$(am__dirstamp) isisd/isisd.$(OBJEXT): isisd/$(am__dirstamp) \ isisd/$(DEPDIR)/$(am__dirstamp) isisd/iso_checksum.$(OBJEXT): isisd/$(am__dirstamp) \ isisd/$(DEPDIR)/$(am__dirstamp) isisd/fabricd.$(OBJEXT): isisd/$(am__dirstamp) \ isisd/$(DEPDIR)/$(am__dirstamp) isisd/isis_northbound.$(OBJEXT): isisd/$(am__dirstamp) \ isisd/$(DEPDIR)/$(am__dirstamp) isisd/isis_cli.$(OBJEXT): isisd/$(am__dirstamp) \ isisd/$(DEPDIR)/$(am__dirstamp) isisd/libisis.a: $(isisd_libisis_a_OBJECTS) $(isisd_libisis_a_DEPENDENCIES) $(EXTRA_isisd_libisis_a_DEPENDENCIES) isisd/$(am__dirstamp) $(AM_V_at)-rm -f isisd/libisis.a $(AM_V_AR)$(isisd_libisis_a_AR) isisd/libisis.a $(isisd_libisis_a_OBJECTS) $(isisd_libisis_a_LIBADD) $(AM_V_at)$(RANLIB) isisd/libisis.a ldpd/$(am__dirstamp): @$(MKDIR_P) ldpd @: > ldpd/$(am__dirstamp) ldpd/$(DEPDIR)/$(am__dirstamp): @$(MKDIR_P) ldpd/$(DEPDIR) @: > ldpd/$(DEPDIR)/$(am__dirstamp) ldpd/accept.$(OBJEXT): ldpd/$(am__dirstamp) \ ldpd/$(DEPDIR)/$(am__dirstamp) ldpd/address.$(OBJEXT): ldpd/$(am__dirstamp) \ ldpd/$(DEPDIR)/$(am__dirstamp) ldpd/adjacency.$(OBJEXT): ldpd/$(am__dirstamp) \ ldpd/$(DEPDIR)/$(am__dirstamp) ldpd/control.$(OBJEXT): ldpd/$(am__dirstamp) \ ldpd/$(DEPDIR)/$(am__dirstamp) ldpd/hello.$(OBJEXT): ldpd/$(am__dirstamp) \ ldpd/$(DEPDIR)/$(am__dirstamp) ldpd/init.$(OBJEXT): ldpd/$(am__dirstamp) \ ldpd/$(DEPDIR)/$(am__dirstamp) ldpd/interface.$(OBJEXT): ldpd/$(am__dirstamp) \ ldpd/$(DEPDIR)/$(am__dirstamp) ldpd/keepalive.$(OBJEXT): ldpd/$(am__dirstamp) \ ldpd/$(DEPDIR)/$(am__dirstamp) ldpd/l2vpn.$(OBJEXT): ldpd/$(am__dirstamp) \ ldpd/$(DEPDIR)/$(am__dirstamp) ldpd/labelmapping.$(OBJEXT): ldpd/$(am__dirstamp) \ ldpd/$(DEPDIR)/$(am__dirstamp) ldpd/lde.$(OBJEXT): ldpd/$(am__dirstamp) \ ldpd/$(DEPDIR)/$(am__dirstamp) ldpd/lde_lib.$(OBJEXT): ldpd/$(am__dirstamp) \ ldpd/$(DEPDIR)/$(am__dirstamp) ldpd/ldp_debug.$(OBJEXT): ldpd/$(am__dirstamp) \ ldpd/$(DEPDIR)/$(am__dirstamp) ldpd/ldp_vty_cmds.$(OBJEXT): ldpd/$(am__dirstamp) \ ldpd/$(DEPDIR)/$(am__dirstamp) ldpd/ldp_vty_conf.$(OBJEXT): ldpd/$(am__dirstamp) \ ldpd/$(DEPDIR)/$(am__dirstamp) ldpd/ldp_vty_exec.$(OBJEXT): ldpd/$(am__dirstamp) \ ldpd/$(DEPDIR)/$(am__dirstamp) ldpd/ldp_zebra.$(OBJEXT): ldpd/$(am__dirstamp) \ ldpd/$(DEPDIR)/$(am__dirstamp) ldpd/ldpd.$(OBJEXT): ldpd/$(am__dirstamp) \ ldpd/$(DEPDIR)/$(am__dirstamp) ldpd/ldpe.$(OBJEXT): ldpd/$(am__dirstamp) \ ldpd/$(DEPDIR)/$(am__dirstamp) ldpd/log.$(OBJEXT): ldpd/$(am__dirstamp) \ ldpd/$(DEPDIR)/$(am__dirstamp) ldpd/logmsg.$(OBJEXT): ldpd/$(am__dirstamp) \ ldpd/$(DEPDIR)/$(am__dirstamp) ldpd/neighbor.$(OBJEXT): ldpd/$(am__dirstamp) \ ldpd/$(DEPDIR)/$(am__dirstamp) ldpd/notification.$(OBJEXT): ldpd/$(am__dirstamp) \ ldpd/$(DEPDIR)/$(am__dirstamp) ldpd/packet.$(OBJEXT): ldpd/$(am__dirstamp) \ ldpd/$(DEPDIR)/$(am__dirstamp) ldpd/pfkey.$(OBJEXT): ldpd/$(am__dirstamp) \ ldpd/$(DEPDIR)/$(am__dirstamp) ldpd/socket.$(OBJEXT): ldpd/$(am__dirstamp) \ ldpd/$(DEPDIR)/$(am__dirstamp) ldpd/util.$(OBJEXT): ldpd/$(am__dirstamp) \ ldpd/$(DEPDIR)/$(am__dirstamp) ldpd/libldp.a: $(ldpd_libldp_a_OBJECTS) $(ldpd_libldp_a_DEPENDENCIES) $(EXTRA_ldpd_libldp_a_DEPENDENCIES) ldpd/$(am__dirstamp) $(AM_V_at)-rm -f ldpd/libldp.a $(AM_V_AR)$(ldpd_libldp_a_AR) ldpd/libldp.a $(ldpd_libldp_a_OBJECTS) $(ldpd_libldp_a_LIBADD) $(AM_V_at)$(RANLIB) ldpd/libldp.a ospf6d/$(am__dirstamp): @$(MKDIR_P) ospf6d @: > ospf6d/$(am__dirstamp) ospf6d/$(DEPDIR)/$(am__dirstamp): @$(MKDIR_P) ospf6d/$(DEPDIR) @: > ospf6d/$(DEPDIR)/$(am__dirstamp) ospf6d/ospf6_abr.$(OBJEXT): ospf6d/$(am__dirstamp) \ ospf6d/$(DEPDIR)/$(am__dirstamp) ospf6d/ospf6_area.$(OBJEXT): ospf6d/$(am__dirstamp) \ ospf6d/$(DEPDIR)/$(am__dirstamp) ospf6d/ospf6_asbr.$(OBJEXT): ospf6d/$(am__dirstamp) \ ospf6d/$(DEPDIR)/$(am__dirstamp) ospf6d/ospf6_bfd.$(OBJEXT): ospf6d/$(am__dirstamp) \ ospf6d/$(DEPDIR)/$(am__dirstamp) ospf6d/ospf6_flood.$(OBJEXT): ospf6d/$(am__dirstamp) \ ospf6d/$(DEPDIR)/$(am__dirstamp) ospf6d/ospf6_interface.$(OBJEXT): ospf6d/$(am__dirstamp) \ ospf6d/$(DEPDIR)/$(am__dirstamp) ospf6d/ospf6_intra.$(OBJEXT): ospf6d/$(am__dirstamp) \ ospf6d/$(DEPDIR)/$(am__dirstamp) ospf6d/ospf6_lsa.$(OBJEXT): ospf6d/$(am__dirstamp) \ ospf6d/$(DEPDIR)/$(am__dirstamp) ospf6d/ospf6_lsdb.$(OBJEXT): ospf6d/$(am__dirstamp) \ ospf6d/$(DEPDIR)/$(am__dirstamp) ospf6d/ospf6_memory.$(OBJEXT): ospf6d/$(am__dirstamp) \ ospf6d/$(DEPDIR)/$(am__dirstamp) ospf6d/ospf6_message.$(OBJEXT): ospf6d/$(am__dirstamp) \ ospf6d/$(DEPDIR)/$(am__dirstamp) ospf6d/ospf6_neighbor.$(OBJEXT): ospf6d/$(am__dirstamp) \ ospf6d/$(DEPDIR)/$(am__dirstamp) ospf6d/ospf6_network.$(OBJEXT): ospf6d/$(am__dirstamp) \ ospf6d/$(DEPDIR)/$(am__dirstamp) ospf6d/ospf6_proto.$(OBJEXT): ospf6d/$(am__dirstamp) \ ospf6d/$(DEPDIR)/$(am__dirstamp) ospf6d/ospf6_route.$(OBJEXT): ospf6d/$(am__dirstamp) \ ospf6d/$(DEPDIR)/$(am__dirstamp) ospf6d/ospf6_spf.$(OBJEXT): ospf6d/$(am__dirstamp) \ ospf6d/$(DEPDIR)/$(am__dirstamp) ospf6d/ospf6_top.$(OBJEXT): ospf6d/$(am__dirstamp) \ ospf6d/$(DEPDIR)/$(am__dirstamp) ospf6d/ospf6_zebra.$(OBJEXT): ospf6d/$(am__dirstamp) \ ospf6d/$(DEPDIR)/$(am__dirstamp) ospf6d/ospf6d.$(OBJEXT): ospf6d/$(am__dirstamp) \ ospf6d/$(DEPDIR)/$(am__dirstamp) ospf6d/libospf6.a: $(ospf6d_libospf6_a_OBJECTS) $(ospf6d_libospf6_a_DEPENDENCIES) $(EXTRA_ospf6d_libospf6_a_DEPENDENCIES) ospf6d/$(am__dirstamp) $(AM_V_at)-rm -f ospf6d/libospf6.a $(AM_V_AR)$(ospf6d_libospf6_a_AR) ospf6d/libospf6.a $(ospf6d_libospf6_a_OBJECTS) $(ospf6d_libospf6_a_LIBADD) $(AM_V_at)$(RANLIB) ospf6d/libospf6.a ospfd/$(am__dirstamp): @$(MKDIR_P) ospfd @: > ospfd/$(am__dirstamp) ospfd/$(DEPDIR)/$(am__dirstamp): @$(MKDIR_P) ospfd/$(DEPDIR) @: > ospfd/$(DEPDIR)/$(am__dirstamp) ospfd/ospf_abr.$(OBJEXT): ospfd/$(am__dirstamp) \ ospfd/$(DEPDIR)/$(am__dirstamp) ospfd/ospf_api.$(OBJEXT): ospfd/$(am__dirstamp) \ ospfd/$(DEPDIR)/$(am__dirstamp) ospfd/ospf_apiserver.$(OBJEXT): ospfd/$(am__dirstamp) \ ospfd/$(DEPDIR)/$(am__dirstamp) ospfd/ospf_asbr.$(OBJEXT): ospfd/$(am__dirstamp) \ ospfd/$(DEPDIR)/$(am__dirstamp) ospfd/ospf_ase.$(OBJEXT): ospfd/$(am__dirstamp) \ ospfd/$(DEPDIR)/$(am__dirstamp) ospfd/ospf_bfd.$(OBJEXT): ospfd/$(am__dirstamp) \ ospfd/$(DEPDIR)/$(am__dirstamp) ospfd/ospf_dump.$(OBJEXT): ospfd/$(am__dirstamp) \ ospfd/$(DEPDIR)/$(am__dirstamp) ospfd/ospf_dump_api.$(OBJEXT): ospfd/$(am__dirstamp) \ ospfd/$(DEPDIR)/$(am__dirstamp) ospfd/ospf_errors.$(OBJEXT): ospfd/$(am__dirstamp) \ ospfd/$(DEPDIR)/$(am__dirstamp) ospfd/ospf_ext.$(OBJEXT): ospfd/$(am__dirstamp) \ ospfd/$(DEPDIR)/$(am__dirstamp) ospfd/ospf_flood.$(OBJEXT): ospfd/$(am__dirstamp) \ ospfd/$(DEPDIR)/$(am__dirstamp) ospfd/ospf_ia.$(OBJEXT): ospfd/$(am__dirstamp) \ ospfd/$(DEPDIR)/$(am__dirstamp) ospfd/ospf_interface.$(OBJEXT): ospfd/$(am__dirstamp) \ ospfd/$(DEPDIR)/$(am__dirstamp) ospfd/ospf_ism.$(OBJEXT): ospfd/$(am__dirstamp) \ ospfd/$(DEPDIR)/$(am__dirstamp) ospfd/ospf_lsa.$(OBJEXT): ospfd/$(am__dirstamp) \ ospfd/$(DEPDIR)/$(am__dirstamp) ospfd/ospf_lsdb.$(OBJEXT): ospfd/$(am__dirstamp) \ ospfd/$(DEPDIR)/$(am__dirstamp) ospfd/ospf_memory.$(OBJEXT): ospfd/$(am__dirstamp) \ ospfd/$(DEPDIR)/$(am__dirstamp) ospfd/ospf_neighbor.$(OBJEXT): ospfd/$(am__dirstamp) \ ospfd/$(DEPDIR)/$(am__dirstamp) ospfd/ospf_network.$(OBJEXT): ospfd/$(am__dirstamp) \ ospfd/$(DEPDIR)/$(am__dirstamp) ospfd/ospf_nsm.$(OBJEXT): ospfd/$(am__dirstamp) \ ospfd/$(DEPDIR)/$(am__dirstamp) ospfd/ospf_opaque.$(OBJEXT): ospfd/$(am__dirstamp) \ ospfd/$(DEPDIR)/$(am__dirstamp) ospfd/ospf_packet.$(OBJEXT): ospfd/$(am__dirstamp) \ ospfd/$(DEPDIR)/$(am__dirstamp) ospfd/ospf_ri.$(OBJEXT): ospfd/$(am__dirstamp) \ ospfd/$(DEPDIR)/$(am__dirstamp) ospfd/ospf_route.$(OBJEXT): ospfd/$(am__dirstamp) \ ospfd/$(DEPDIR)/$(am__dirstamp) ospfd/ospf_routemap.$(OBJEXT): ospfd/$(am__dirstamp) \ ospfd/$(DEPDIR)/$(am__dirstamp) ospfd/ospf_spf.$(OBJEXT): ospfd/$(am__dirstamp) \ ospfd/$(DEPDIR)/$(am__dirstamp) ospfd/ospf_sr.$(OBJEXT): ospfd/$(am__dirstamp) \ ospfd/$(DEPDIR)/$(am__dirstamp) ospfd/ospf_te.$(OBJEXT): ospfd/$(am__dirstamp) \ ospfd/$(DEPDIR)/$(am__dirstamp) ospfd/ospf_vty.$(OBJEXT): ospfd/$(am__dirstamp) \ ospfd/$(DEPDIR)/$(am__dirstamp) ospfd/ospf_zebra.$(OBJEXT): ospfd/$(am__dirstamp) \ ospfd/$(DEPDIR)/$(am__dirstamp) ospfd/ospfd.$(OBJEXT): ospfd/$(am__dirstamp) \ ospfd/$(DEPDIR)/$(am__dirstamp) ospfd/libfrrospf.a: $(ospfd_libfrrospf_a_OBJECTS) $(ospfd_libfrrospf_a_DEPENDENCIES) $(EXTRA_ospfd_libfrrospf_a_DEPENDENCIES) ospfd/$(am__dirstamp) $(AM_V_at)-rm -f ospfd/libfrrospf.a $(AM_V_AR)$(ospfd_libfrrospf_a_AR) ospfd/libfrrospf.a $(ospfd_libfrrospf_a_OBJECTS) $(ospfd_libfrrospf_a_LIBADD) $(AM_V_at)$(RANLIB) ospfd/libfrrospf.a pbrd/$(am__dirstamp): @$(MKDIR_P) pbrd @: > pbrd/$(am__dirstamp) pbrd/$(DEPDIR)/$(am__dirstamp): @$(MKDIR_P) pbrd/$(DEPDIR) @: > pbrd/$(DEPDIR)/$(am__dirstamp) pbrd/pbr_zebra.$(OBJEXT): pbrd/$(am__dirstamp) \ pbrd/$(DEPDIR)/$(am__dirstamp) pbrd/pbr_vty.$(OBJEXT): pbrd/$(am__dirstamp) \ pbrd/$(DEPDIR)/$(am__dirstamp) pbrd/pbr_map.$(OBJEXT): pbrd/$(am__dirstamp) \ pbrd/$(DEPDIR)/$(am__dirstamp) pbrd/pbr_memory.$(OBJEXT): pbrd/$(am__dirstamp) \ pbrd/$(DEPDIR)/$(am__dirstamp) pbrd/pbr_nht.$(OBJEXT): pbrd/$(am__dirstamp) \ pbrd/$(DEPDIR)/$(am__dirstamp) pbrd/pbr_debug.$(OBJEXT): pbrd/$(am__dirstamp) \ pbrd/$(DEPDIR)/$(am__dirstamp) pbrd/libpbr.a: $(pbrd_libpbr_a_OBJECTS) $(pbrd_libpbr_a_DEPENDENCIES) $(EXTRA_pbrd_libpbr_a_DEPENDENCIES) pbrd/$(am__dirstamp) $(AM_V_at)-rm -f pbrd/libpbr.a $(AM_V_AR)$(pbrd_libpbr_a_AR) pbrd/libpbr.a $(pbrd_libpbr_a_OBJECTS) $(pbrd_libpbr_a_LIBADD) $(AM_V_at)$(RANLIB) pbrd/libpbr.a pimd/$(am__dirstamp): @$(MKDIR_P) pimd @: > pimd/$(am__dirstamp) pimd/$(DEPDIR)/$(am__dirstamp): @$(MKDIR_P) pimd/$(DEPDIR) @: > pimd/$(DEPDIR)/$(am__dirstamp) pimd/pim_assert.$(OBJEXT): pimd/$(am__dirstamp) \ pimd/$(DEPDIR)/$(am__dirstamp) pimd/pim_bfd.$(OBJEXT): pimd/$(am__dirstamp) \ pimd/$(DEPDIR)/$(am__dirstamp) pimd/pim_br.$(OBJEXT): pimd/$(am__dirstamp) \ pimd/$(DEPDIR)/$(am__dirstamp) pimd/pim_bsm.$(OBJEXT): pimd/$(am__dirstamp) \ pimd/$(DEPDIR)/$(am__dirstamp) pimd/pim_cmd.$(OBJEXT): pimd/$(am__dirstamp) \ pimd/$(DEPDIR)/$(am__dirstamp) pimd/pim_errors.$(OBJEXT): pimd/$(am__dirstamp) \ pimd/$(DEPDIR)/$(am__dirstamp) pimd/pim_hello.$(OBJEXT): pimd/$(am__dirstamp) \ pimd/$(DEPDIR)/$(am__dirstamp) pimd/pim_iface.$(OBJEXT): pimd/$(am__dirstamp) \ pimd/$(DEPDIR)/$(am__dirstamp) pimd/pim_ifchannel.$(OBJEXT): pimd/$(am__dirstamp) \ pimd/$(DEPDIR)/$(am__dirstamp) pimd/pim_igmp.$(OBJEXT): pimd/$(am__dirstamp) \ pimd/$(DEPDIR)/$(am__dirstamp) pimd/pim_igmp_mtrace.$(OBJEXT): pimd/$(am__dirstamp) \ pimd/$(DEPDIR)/$(am__dirstamp) pimd/pim_igmp_stats.$(OBJEXT): pimd/$(am__dirstamp) \ pimd/$(DEPDIR)/$(am__dirstamp) pimd/pim_igmpv2.$(OBJEXT): pimd/$(am__dirstamp) \ pimd/$(DEPDIR)/$(am__dirstamp) pimd/pim_igmpv3.$(OBJEXT): pimd/$(am__dirstamp) \ pimd/$(DEPDIR)/$(am__dirstamp) pimd/pim_instance.$(OBJEXT): pimd/$(am__dirstamp) \ pimd/$(DEPDIR)/$(am__dirstamp) pimd/pim_int.$(OBJEXT): pimd/$(am__dirstamp) \ pimd/$(DEPDIR)/$(am__dirstamp) pimd/pim_join.$(OBJEXT): pimd/$(am__dirstamp) \ pimd/$(DEPDIR)/$(am__dirstamp) pimd/pim_jp_agg.$(OBJEXT): pimd/$(am__dirstamp) \ pimd/$(DEPDIR)/$(am__dirstamp) pimd/pim_macro.$(OBJEXT): pimd/$(am__dirstamp) \ pimd/$(DEPDIR)/$(am__dirstamp) pimd/pim_memory.$(OBJEXT): pimd/$(am__dirstamp) \ pimd/$(DEPDIR)/$(am__dirstamp) pimd/pim_mroute.$(OBJEXT): pimd/$(am__dirstamp) \ pimd/$(DEPDIR)/$(am__dirstamp) pimd/pim_msdp.$(OBJEXT): pimd/$(am__dirstamp) \ pimd/$(DEPDIR)/$(am__dirstamp) pimd/pim_msdp_packet.$(OBJEXT): pimd/$(am__dirstamp) \ pimd/$(DEPDIR)/$(am__dirstamp) pimd/pim_msdp_socket.$(OBJEXT): pimd/$(am__dirstamp) \ pimd/$(DEPDIR)/$(am__dirstamp) pimd/pim_msg.$(OBJEXT): pimd/$(am__dirstamp) \ pimd/$(DEPDIR)/$(am__dirstamp) pimd/pim_neighbor.$(OBJEXT): pimd/$(am__dirstamp) \ pimd/$(DEPDIR)/$(am__dirstamp) pimd/pim_nht.$(OBJEXT): pimd/$(am__dirstamp) \ pimd/$(DEPDIR)/$(am__dirstamp) pimd/pim_oil.$(OBJEXT): pimd/$(am__dirstamp) \ pimd/$(DEPDIR)/$(am__dirstamp) pimd/pim_pim.$(OBJEXT): pimd/$(am__dirstamp) \ pimd/$(DEPDIR)/$(am__dirstamp) pimd/pim_register.$(OBJEXT): pimd/$(am__dirstamp) \ pimd/$(DEPDIR)/$(am__dirstamp) pimd/pim_routemap.$(OBJEXT): pimd/$(am__dirstamp) \ pimd/$(DEPDIR)/$(am__dirstamp) pimd/pim_rp.$(OBJEXT): pimd/$(am__dirstamp) \ pimd/$(DEPDIR)/$(am__dirstamp) pimd/pim_rpf.$(OBJEXT): pimd/$(am__dirstamp) \ pimd/$(DEPDIR)/$(am__dirstamp) pimd/pim_signals.$(OBJEXT): pimd/$(am__dirstamp) \ pimd/$(DEPDIR)/$(am__dirstamp) pimd/pim_sock.$(OBJEXT): pimd/$(am__dirstamp) \ pimd/$(DEPDIR)/$(am__dirstamp) pimd/pim_ssm.$(OBJEXT): pimd/$(am__dirstamp) \ pimd/$(DEPDIR)/$(am__dirstamp) pimd/pim_ssmpingd.$(OBJEXT): pimd/$(am__dirstamp) \ pimd/$(DEPDIR)/$(am__dirstamp) pimd/pim_static.$(OBJEXT): pimd/$(am__dirstamp) \ pimd/$(DEPDIR)/$(am__dirstamp) pimd/pim_str.$(OBJEXT): pimd/$(am__dirstamp) \ pimd/$(DEPDIR)/$(am__dirstamp) pimd/pim_time.$(OBJEXT): pimd/$(am__dirstamp) \ pimd/$(DEPDIR)/$(am__dirstamp) pimd/pim_tlv.$(OBJEXT): pimd/$(am__dirstamp) \ pimd/$(DEPDIR)/$(am__dirstamp) pimd/pim_upstream.$(OBJEXT): pimd/$(am__dirstamp) \ pimd/$(DEPDIR)/$(am__dirstamp) pimd/pim_util.$(OBJEXT): pimd/$(am__dirstamp) \ pimd/$(DEPDIR)/$(am__dirstamp) pimd/pim_version.$(OBJEXT): pimd/$(am__dirstamp) \ pimd/$(DEPDIR)/$(am__dirstamp) pimd/pim_vty.$(OBJEXT): pimd/$(am__dirstamp) \ pimd/$(DEPDIR)/$(am__dirstamp) pimd/pim_zebra.$(OBJEXT): pimd/$(am__dirstamp) \ pimd/$(DEPDIR)/$(am__dirstamp) pimd/pim_zlookup.$(OBJEXT): pimd/$(am__dirstamp) \ pimd/$(DEPDIR)/$(am__dirstamp) pimd/pim_vxlan.$(OBJEXT): pimd/$(am__dirstamp) \ pimd/$(DEPDIR)/$(am__dirstamp) pimd/pimd.$(OBJEXT): pimd/$(am__dirstamp) \ pimd/$(DEPDIR)/$(am__dirstamp) pimd/libpim.a: $(pimd_libpim_a_OBJECTS) $(pimd_libpim_a_DEPENDENCIES) $(EXTRA_pimd_libpim_a_DEPENDENCIES) pimd/$(am__dirstamp) $(AM_V_at)-rm -f pimd/libpim.a $(AM_V_AR)$(pimd_libpim_a_AR) pimd/libpim.a $(pimd_libpim_a_OBJECTS) $(pimd_libpim_a_LIBADD) $(AM_V_at)$(RANLIB) pimd/libpim.a ripd/$(am__dirstamp): @$(MKDIR_P) ripd @: > ripd/$(am__dirstamp) ripd/$(DEPDIR)/$(am__dirstamp): @$(MKDIR_P) ripd/$(DEPDIR) @: > ripd/$(DEPDIR)/$(am__dirstamp) ripd/rip_cli.$(OBJEXT): ripd/$(am__dirstamp) \ ripd/$(DEPDIR)/$(am__dirstamp) ripd/rip_debug.$(OBJEXT): ripd/$(am__dirstamp) \ ripd/$(DEPDIR)/$(am__dirstamp) ripd/rip_errors.$(OBJEXT): ripd/$(am__dirstamp) \ ripd/$(DEPDIR)/$(am__dirstamp) ripd/rip_interface.$(OBJEXT): ripd/$(am__dirstamp) \ ripd/$(DEPDIR)/$(am__dirstamp) ripd/rip_offset.$(OBJEXT): ripd/$(am__dirstamp) \ ripd/$(DEPDIR)/$(am__dirstamp) ripd/rip_northbound.$(OBJEXT): ripd/$(am__dirstamp) \ ripd/$(DEPDIR)/$(am__dirstamp) ripd/rip_peer.$(OBJEXT): ripd/$(am__dirstamp) \ ripd/$(DEPDIR)/$(am__dirstamp) ripd/rip_routemap.$(OBJEXT): ripd/$(am__dirstamp) \ ripd/$(DEPDIR)/$(am__dirstamp) ripd/rip_zebra.$(OBJEXT): ripd/$(am__dirstamp) \ ripd/$(DEPDIR)/$(am__dirstamp) ripd/ripd.$(OBJEXT): ripd/$(am__dirstamp) \ ripd/$(DEPDIR)/$(am__dirstamp) ripd/librip.a: $(ripd_librip_a_OBJECTS) $(ripd_librip_a_DEPENDENCIES) $(EXTRA_ripd_librip_a_DEPENDENCIES) ripd/$(am__dirstamp) $(AM_V_at)-rm -f ripd/librip.a $(AM_V_AR)$(ripd_librip_a_AR) ripd/librip.a $(ripd_librip_a_OBJECTS) $(ripd_librip_a_LIBADD) $(AM_V_at)$(RANLIB) ripd/librip.a ripngd/$(am__dirstamp): @$(MKDIR_P) ripngd @: > ripngd/$(am__dirstamp) ripngd/$(DEPDIR)/$(am__dirstamp): @$(MKDIR_P) ripngd/$(DEPDIR) @: > ripngd/$(DEPDIR)/$(am__dirstamp) ripngd/ripng_cli.$(OBJEXT): ripngd/$(am__dirstamp) \ ripngd/$(DEPDIR)/$(am__dirstamp) ripngd/ripng_debug.$(OBJEXT): ripngd/$(am__dirstamp) \ ripngd/$(DEPDIR)/$(am__dirstamp) ripngd/ripng_interface.$(OBJEXT): ripngd/$(am__dirstamp) \ ripngd/$(DEPDIR)/$(am__dirstamp) ripngd/ripng_nexthop.$(OBJEXT): ripngd/$(am__dirstamp) \ ripngd/$(DEPDIR)/$(am__dirstamp) ripngd/ripng_offset.$(OBJEXT): ripngd/$(am__dirstamp) \ ripngd/$(DEPDIR)/$(am__dirstamp) ripngd/ripng_northbound.$(OBJEXT): ripngd/$(am__dirstamp) \ ripngd/$(DEPDIR)/$(am__dirstamp) ripngd/ripng_peer.$(OBJEXT): ripngd/$(am__dirstamp) \ ripngd/$(DEPDIR)/$(am__dirstamp) ripngd/ripng_route.$(OBJEXT): ripngd/$(am__dirstamp) \ ripngd/$(DEPDIR)/$(am__dirstamp) ripngd/ripng_routemap.$(OBJEXT): ripngd/$(am__dirstamp) \ ripngd/$(DEPDIR)/$(am__dirstamp) ripngd/ripng_zebra.$(OBJEXT): ripngd/$(am__dirstamp) \ ripngd/$(DEPDIR)/$(am__dirstamp) ripngd/ripngd.$(OBJEXT): ripngd/$(am__dirstamp) \ ripngd/$(DEPDIR)/$(am__dirstamp) ripngd/libripng.a: $(ripngd_libripng_a_OBJECTS) $(ripngd_libripng_a_DEPENDENCIES) $(EXTRA_ripngd_libripng_a_DEPENDENCIES) ripngd/$(am__dirstamp) $(AM_V_at)-rm -f ripngd/libripng.a $(AM_V_AR)$(ripngd_libripng_a_AR) ripngd/libripng.a $(ripngd_libripng_a_OBJECTS) $(ripngd_libripng_a_LIBADD) $(AM_V_at)$(RANLIB) ripngd/libripng.a sharpd/$(am__dirstamp): @$(MKDIR_P) sharpd @: > sharpd/$(am__dirstamp) sharpd/$(DEPDIR)/$(am__dirstamp): @$(MKDIR_P) sharpd/$(DEPDIR) @: > sharpd/$(DEPDIR)/$(am__dirstamp) sharpd/sharp_nht.$(OBJEXT): sharpd/$(am__dirstamp) \ sharpd/$(DEPDIR)/$(am__dirstamp) sharpd/sharp_zebra.$(OBJEXT): sharpd/$(am__dirstamp) \ sharpd/$(DEPDIR)/$(am__dirstamp) sharpd/sharp_vty.$(OBJEXT): sharpd/$(am__dirstamp) \ sharpd/$(DEPDIR)/$(am__dirstamp) sharpd/libsharp.a: $(sharpd_libsharp_a_OBJECTS) $(sharpd_libsharp_a_DEPENDENCIES) $(EXTRA_sharpd_libsharp_a_DEPENDENCIES) sharpd/$(am__dirstamp) $(AM_V_at)-rm -f sharpd/libsharp.a $(AM_V_AR)$(sharpd_libsharp_a_AR) sharpd/libsharp.a $(sharpd_libsharp_a_OBJECTS) $(sharpd_libsharp_a_LIBADD) $(AM_V_at)$(RANLIB) sharpd/libsharp.a staticd/$(am__dirstamp): @$(MKDIR_P) staticd @: > staticd/$(am__dirstamp) staticd/$(DEPDIR)/$(am__dirstamp): @$(MKDIR_P) staticd/$(DEPDIR) @: > staticd/$(DEPDIR)/$(am__dirstamp) staticd/static_memory.$(OBJEXT): staticd/$(am__dirstamp) \ staticd/$(DEPDIR)/$(am__dirstamp) staticd/static_nht.$(OBJEXT): staticd/$(am__dirstamp) \ staticd/$(DEPDIR)/$(am__dirstamp) staticd/static_routes.$(OBJEXT): staticd/$(am__dirstamp) \ staticd/$(DEPDIR)/$(am__dirstamp) staticd/static_zebra.$(OBJEXT): staticd/$(am__dirstamp) \ staticd/$(DEPDIR)/$(am__dirstamp) staticd/static_vrf.$(OBJEXT): staticd/$(am__dirstamp) \ staticd/$(DEPDIR)/$(am__dirstamp) staticd/static_vty.$(OBJEXT): staticd/$(am__dirstamp) \ staticd/$(DEPDIR)/$(am__dirstamp) staticd/libstatic.a: $(staticd_libstatic_a_OBJECTS) $(staticd_libstatic_a_DEPENDENCIES) $(EXTRA_staticd_libstatic_a_DEPENDENCIES) staticd/$(am__dirstamp) $(AM_V_at)-rm -f staticd/libstatic.a $(AM_V_AR)$(staticd_libstatic_a_AR) staticd/libstatic.a $(staticd_libstatic_a_OBJECTS) $(staticd_libstatic_a_LIBADD) $(AM_V_at)$(RANLIB) staticd/libstatic.a vrrpd/$(am__dirstamp): @$(MKDIR_P) vrrpd @: > vrrpd/$(am__dirstamp) vrrpd/$(DEPDIR)/$(am__dirstamp): @$(MKDIR_P) vrrpd/$(DEPDIR) @: > vrrpd/$(DEPDIR)/$(am__dirstamp) vrrpd/vrrp.$(OBJEXT): vrrpd/$(am__dirstamp) \ vrrpd/$(DEPDIR)/$(am__dirstamp) vrrpd/vrrp_arp.$(OBJEXT): vrrpd/$(am__dirstamp) \ vrrpd/$(DEPDIR)/$(am__dirstamp) vrrpd/vrrp_debug.$(OBJEXT): vrrpd/$(am__dirstamp) \ vrrpd/$(DEPDIR)/$(am__dirstamp) vrrpd/vrrp_ndisc.$(OBJEXT): vrrpd/$(am__dirstamp) \ vrrpd/$(DEPDIR)/$(am__dirstamp) vrrpd/vrrp_packet.$(OBJEXT): vrrpd/$(am__dirstamp) \ vrrpd/$(DEPDIR)/$(am__dirstamp) vrrpd/vrrp_vty.$(OBJEXT): vrrpd/$(am__dirstamp) \ vrrpd/$(DEPDIR)/$(am__dirstamp) vrrpd/vrrp_zebra.$(OBJEXT): vrrpd/$(am__dirstamp) \ vrrpd/$(DEPDIR)/$(am__dirstamp) vrrpd/libvrrp.a: $(vrrpd_libvrrp_a_OBJECTS) $(vrrpd_libvrrp_a_DEPENDENCIES) $(EXTRA_vrrpd_libvrrp_a_DEPENDENCIES) vrrpd/$(am__dirstamp) $(AM_V_at)-rm -f vrrpd/libvrrp.a $(AM_V_AR)$(vrrpd_libvrrp_a_AR) vrrpd/libvrrp.a $(vrrpd_libvrrp_a_OBJECTS) $(vrrpd_libvrrp_a_LIBADD) $(AM_V_at)$(RANLIB) vrrpd/libvrrp.a bgpd/bgp_bmp.lo: bgpd/$(am__dirstamp) bgpd/$(DEPDIR)/$(am__dirstamp) bgpd/bgpd_bmp.la: $(bgpd_bgpd_bmp_la_OBJECTS) $(bgpd_bgpd_bmp_la_DEPENDENCIES) $(EXTRA_bgpd_bgpd_bmp_la_DEPENDENCIES) bgpd/$(am__dirstamp) $(AM_V_CCLD)$(bgpd_bgpd_bmp_la_LINK) $(am_bgpd_bgpd_bmp_la_rpath) $(bgpd_bgpd_bmp_la_OBJECTS) $(bgpd_bgpd_bmp_la_LIBADD) $(LIBS) bgpd/bgpd_rpki_la-bgp_rpki.lo: bgpd/$(am__dirstamp) \ bgpd/$(DEPDIR)/$(am__dirstamp) bgpd/bgpd_rpki.la: $(bgpd_bgpd_rpki_la_OBJECTS) $(bgpd_bgpd_rpki_la_DEPENDENCIES) $(EXTRA_bgpd_bgpd_rpki_la_DEPENDENCIES) bgpd/$(am__dirstamp) $(AM_V_CCLD)$(bgpd_bgpd_rpki_la_LINK) $(am_bgpd_bgpd_rpki_la_rpath) $(bgpd_bgpd_rpki_la_OBJECTS) $(bgpd_bgpd_rpki_la_LIBADD) $(LIBS) bgpd/bgpd_snmp_la-bgp_snmp.lo: bgpd/$(am__dirstamp) \ bgpd/$(DEPDIR)/$(am__dirstamp) bgpd/bgpd_snmp.la: $(bgpd_bgpd_snmp_la_OBJECTS) $(bgpd_bgpd_snmp_la_DEPENDENCIES) $(EXTRA_bgpd_bgpd_snmp_la_DEPENDENCIES) bgpd/$(am__dirstamp) $(AM_V_CCLD)$(bgpd_bgpd_snmp_la_LINK) $(am_bgpd_bgpd_snmp_la_rpath) $(bgpd_bgpd_snmp_la_OBJECTS) $(bgpd_bgpd_snmp_la_LIBADD) $(LIBS) fpm/$(am__dirstamp): @$(MKDIR_P) fpm @: > fpm/$(am__dirstamp) fpm/$(DEPDIR)/$(am__dirstamp): @$(MKDIR_P) fpm/$(DEPDIR) @: > fpm/$(DEPDIR)/$(am__dirstamp) fpm/libfrrfpm_pb_la-fpm_pb.lo: fpm/$(am__dirstamp) \ fpm/$(DEPDIR)/$(am__dirstamp) fpm/libfrrfpm_pb_la-fpm.pb-c.lo: fpm/$(am__dirstamp) \ fpm/$(DEPDIR)/$(am__dirstamp) fpm/libfrrfpm_pb.la: $(fpm_libfrrfpm_pb_la_OBJECTS) $(fpm_libfrrfpm_pb_la_DEPENDENCIES) $(EXTRA_fpm_libfrrfpm_pb_la_DEPENDENCIES) fpm/$(am__dirstamp) $(AM_V_CCLD)$(fpm_libfrrfpm_pb_la_LINK) $(am_fpm_libfrrfpm_pb_la_rpath) $(fpm_libfrrfpm_pb_la_OBJECTS) $(fpm_libfrrfpm_pb_la_LIBADD) $(LIBS) grpc/$(am__dirstamp): @$(MKDIR_P) grpc @: > grpc/$(am__dirstamp) grpc/$(DEPDIR)/$(am__dirstamp): @$(MKDIR_P) grpc/$(DEPDIR) @: > grpc/$(DEPDIR)/$(am__dirstamp) grpc/libfrrgrpc_pb_la-frr-northbound.pb.lo: grpc/$(am__dirstamp) \ grpc/$(DEPDIR)/$(am__dirstamp) grpc/libfrrgrpc_pb_la-frr-northbound.grpc.pb.lo: grpc/$(am__dirstamp) \ grpc/$(DEPDIR)/$(am__dirstamp) grpc/libfrrgrpc_pb.la: $(grpc_libfrrgrpc_pb_la_OBJECTS) $(grpc_libfrrgrpc_pb_la_DEPENDENCIES) $(EXTRA_grpc_libfrrgrpc_pb_la_DEPENDENCIES) grpc/$(am__dirstamp) $(AM_V_CXXLD)$(grpc_libfrrgrpc_pb_la_LINK) $(am_grpc_libfrrgrpc_pb_la_rpath) $(grpc_libfrrgrpc_pb_la_OBJECTS) $(grpc_libfrrgrpc_pb_la_LIBADD) $(LIBS) lib/$(am__dirstamp): @$(MKDIR_P) lib @: > lib/$(am__dirstamp) lib/$(DEPDIR)/$(am__dirstamp): @$(MKDIR_P) lib/$(DEPDIR) @: > lib/$(DEPDIR)/$(am__dirstamp) lib/confd_la-northbound_confd.lo: lib/$(am__dirstamp) \ lib/$(DEPDIR)/$(am__dirstamp) lib/confd.la: $(lib_confd_la_OBJECTS) $(lib_confd_la_DEPENDENCIES) $(EXTRA_lib_confd_la_DEPENDENCIES) lib/$(am__dirstamp) $(AM_V_CCLD)$(lib_confd_la_LINK) $(am_lib_confd_la_rpath) $(lib_confd_la_OBJECTS) $(lib_confd_la_LIBADD) $(LIBS) lib/grpc_la-northbound_grpc.lo: lib/$(am__dirstamp) \ lib/$(DEPDIR)/$(am__dirstamp) lib/grpc.la: $(lib_grpc_la_OBJECTS) $(lib_grpc_la_DEPENDENCIES) $(EXTRA_lib_grpc_la_DEPENDENCIES) lib/$(am__dirstamp) $(AM_V_CXXLD)$(lib_grpc_la_LINK) $(am_lib_grpc_la_rpath) $(lib_grpc_la_OBJECTS) $(lib_grpc_la_LIBADD) $(LIBS) lib/agg_table.lo: lib/$(am__dirstamp) lib/$(DEPDIR)/$(am__dirstamp) lib/atomlist.lo: lib/$(am__dirstamp) lib/$(DEPDIR)/$(am__dirstamp) lib/bfd.lo: lib/$(am__dirstamp) lib/$(DEPDIR)/$(am__dirstamp) lib/buffer.lo: lib/$(am__dirstamp) lib/$(DEPDIR)/$(am__dirstamp) lib/checksum.lo: lib/$(am__dirstamp) lib/$(DEPDIR)/$(am__dirstamp) lib/command.lo: lib/$(am__dirstamp) lib/$(DEPDIR)/$(am__dirstamp) lib/command_graph.lo: lib/$(am__dirstamp) \ lib/$(DEPDIR)/$(am__dirstamp) lib/command_lex.lo: lib/$(am__dirstamp) lib/$(DEPDIR)/$(am__dirstamp) lib/command_match.lo: lib/$(am__dirstamp) \ lib/$(DEPDIR)/$(am__dirstamp) lib/command_parse.h: lib/command_parse.c @if test ! -f $@; then rm -f lib/command_parse.c; else :; fi @if test ! -f $@; then $(MAKE) $(AM_MAKEFLAGS) lib/command_parse.c; else :; fi lib/command_parse.lo: lib/$(am__dirstamp) \ lib/$(DEPDIR)/$(am__dirstamp) lib/csv.lo: lib/$(am__dirstamp) lib/$(DEPDIR)/$(am__dirstamp) lib/debug.lo: lib/$(am__dirstamp) lib/$(DEPDIR)/$(am__dirstamp) lib/distribute.lo: lib/$(am__dirstamp) lib/$(DEPDIR)/$(am__dirstamp) lib/ferr.lo: lib/$(am__dirstamp) lib/$(DEPDIR)/$(am__dirstamp) lib/filter.lo: lib/$(am__dirstamp) lib/$(DEPDIR)/$(am__dirstamp) lib/frrcu.lo: lib/$(am__dirstamp) lib/$(DEPDIR)/$(am__dirstamp) lib/frrlua.lo: lib/$(am__dirstamp) lib/$(DEPDIR)/$(am__dirstamp) lib/frr_pthread.lo: lib/$(am__dirstamp) lib/$(DEPDIR)/$(am__dirstamp) lib/frrstr.lo: lib/$(am__dirstamp) lib/$(DEPDIR)/$(am__dirstamp) lib/getopt.lo: lib/$(am__dirstamp) lib/$(DEPDIR)/$(am__dirstamp) lib/getopt1.lo: lib/$(am__dirstamp) lib/$(DEPDIR)/$(am__dirstamp) lib/grammar_sandbox.lo: lib/$(am__dirstamp) \ lib/$(DEPDIR)/$(am__dirstamp) lib/graph.lo: lib/$(am__dirstamp) lib/$(DEPDIR)/$(am__dirstamp) lib/hash.lo: lib/$(am__dirstamp) lib/$(DEPDIR)/$(am__dirstamp) lib/hook.lo: lib/$(am__dirstamp) lib/$(DEPDIR)/$(am__dirstamp) lib/id_alloc.lo: lib/$(am__dirstamp) lib/$(DEPDIR)/$(am__dirstamp) lib/if.lo: lib/$(am__dirstamp) lib/$(DEPDIR)/$(am__dirstamp) lib/if_rmap.lo: lib/$(am__dirstamp) lib/$(DEPDIR)/$(am__dirstamp) lib/imsg-buffer.lo: lib/$(am__dirstamp) lib/$(DEPDIR)/$(am__dirstamp) lib/imsg.lo: lib/$(am__dirstamp) lib/$(DEPDIR)/$(am__dirstamp) lib/jhash.lo: lib/$(am__dirstamp) lib/$(DEPDIR)/$(am__dirstamp) lib/json.lo: lib/$(am__dirstamp) lib/$(DEPDIR)/$(am__dirstamp) lib/keychain.lo: lib/$(am__dirstamp) lib/$(DEPDIR)/$(am__dirstamp) lib/lib_errors.lo: lib/$(am__dirstamp) lib/$(DEPDIR)/$(am__dirstamp) lib/libfrr.lo: lib/$(am__dirstamp) lib/$(DEPDIR)/$(am__dirstamp) lib/linklist.lo: lib/$(am__dirstamp) lib/$(DEPDIR)/$(am__dirstamp) lib/log.lo: lib/$(am__dirstamp) lib/$(DEPDIR)/$(am__dirstamp) lib/log_vty.lo: lib/$(am__dirstamp) lib/$(DEPDIR)/$(am__dirstamp) lib/md5.lo: lib/$(am__dirstamp) lib/$(DEPDIR)/$(am__dirstamp) lib/memory.lo: lib/$(am__dirstamp) lib/$(DEPDIR)/$(am__dirstamp) lib/memory_vty.lo: lib/$(am__dirstamp) lib/$(DEPDIR)/$(am__dirstamp) lib/mlag.lo: lib/$(am__dirstamp) lib/$(DEPDIR)/$(am__dirstamp) lib/module.lo: lib/$(am__dirstamp) lib/$(DEPDIR)/$(am__dirstamp) lib/mpls.lo: lib/$(am__dirstamp) lib/$(DEPDIR)/$(am__dirstamp) lib/network.lo: lib/$(am__dirstamp) lib/$(DEPDIR)/$(am__dirstamp) lib/nexthop.lo: lib/$(am__dirstamp) lib/$(DEPDIR)/$(am__dirstamp) lib/netns_linux.lo: lib/$(am__dirstamp) lib/$(DEPDIR)/$(am__dirstamp) lib/netns_other.lo: lib/$(am__dirstamp) lib/$(DEPDIR)/$(am__dirstamp) lib/nexthop_group.lo: lib/$(am__dirstamp) \ lib/$(DEPDIR)/$(am__dirstamp) lib/northbound.lo: lib/$(am__dirstamp) lib/$(DEPDIR)/$(am__dirstamp) lib/northbound_cli.lo: lib/$(am__dirstamp) \ lib/$(DEPDIR)/$(am__dirstamp) lib/northbound_db.lo: lib/$(am__dirstamp) \ lib/$(DEPDIR)/$(am__dirstamp) lib/ntop.lo: lib/$(am__dirstamp) lib/$(DEPDIR)/$(am__dirstamp) lib/openbsd-tree.lo: lib/$(am__dirstamp) lib/$(DEPDIR)/$(am__dirstamp) lib/pid_output.lo: lib/$(am__dirstamp) lib/$(DEPDIR)/$(am__dirstamp) lib/plist.lo: lib/$(am__dirstamp) lib/$(DEPDIR)/$(am__dirstamp) lib/prefix.lo: lib/$(am__dirstamp) lib/$(DEPDIR)/$(am__dirstamp) lib/privs.lo: lib/$(am__dirstamp) lib/$(DEPDIR)/$(am__dirstamp) lib/ptm_lib.lo: lib/$(am__dirstamp) lib/$(DEPDIR)/$(am__dirstamp) lib/pullwr.lo: lib/$(am__dirstamp) lib/$(DEPDIR)/$(am__dirstamp) lib/qobj.lo: lib/$(am__dirstamp) lib/$(DEPDIR)/$(am__dirstamp) lib/ringbuf.lo: lib/$(am__dirstamp) lib/$(DEPDIR)/$(am__dirstamp) lib/routemap.lo: lib/$(am__dirstamp) lib/$(DEPDIR)/$(am__dirstamp) lib/sbuf.lo: lib/$(am__dirstamp) lib/$(DEPDIR)/$(am__dirstamp) lib/seqlock.lo: lib/$(am__dirstamp) lib/$(DEPDIR)/$(am__dirstamp) lib/sha256.lo: lib/$(am__dirstamp) lib/$(DEPDIR)/$(am__dirstamp) lib/sigevent.lo: lib/$(am__dirstamp) lib/$(DEPDIR)/$(am__dirstamp) lib/skiplist.lo: lib/$(am__dirstamp) lib/$(DEPDIR)/$(am__dirstamp) lib/sockopt.lo: lib/$(am__dirstamp) lib/$(DEPDIR)/$(am__dirstamp) lib/sockunion.lo: lib/$(am__dirstamp) lib/$(DEPDIR)/$(am__dirstamp) lib/spf_backoff.lo: lib/$(am__dirstamp) lib/$(DEPDIR)/$(am__dirstamp) lib/srcdest_table.lo: lib/$(am__dirstamp) \ lib/$(DEPDIR)/$(am__dirstamp) lib/stream.lo: lib/$(am__dirstamp) lib/$(DEPDIR)/$(am__dirstamp) lib/strlcat.lo: lib/$(am__dirstamp) lib/$(DEPDIR)/$(am__dirstamp) lib/strlcpy.lo: lib/$(am__dirstamp) lib/$(DEPDIR)/$(am__dirstamp) lib/systemd.lo: lib/$(am__dirstamp) lib/$(DEPDIR)/$(am__dirstamp) lib/table.lo: lib/$(am__dirstamp) lib/$(DEPDIR)/$(am__dirstamp) lib/termtable.lo: lib/$(am__dirstamp) lib/$(DEPDIR)/$(am__dirstamp) lib/thread.lo: lib/$(am__dirstamp) lib/$(DEPDIR)/$(am__dirstamp) lib/typerb.lo: lib/$(am__dirstamp) lib/$(DEPDIR)/$(am__dirstamp) lib/typesafe.lo: lib/$(am__dirstamp) lib/$(DEPDIR)/$(am__dirstamp) lib/vector.lo: lib/$(am__dirstamp) lib/$(DEPDIR)/$(am__dirstamp) lib/vrf.lo: lib/$(am__dirstamp) lib/$(DEPDIR)/$(am__dirstamp) lib/vty.lo: lib/$(am__dirstamp) lib/$(DEPDIR)/$(am__dirstamp) lib/wheel.lo: lib/$(am__dirstamp) lib/$(DEPDIR)/$(am__dirstamp) lib/workqueue.lo: lib/$(am__dirstamp) lib/$(DEPDIR)/$(am__dirstamp) lib/yang.lo: lib/$(am__dirstamp) lib/$(DEPDIR)/$(am__dirstamp) lib/yang_translator.lo: lib/$(am__dirstamp) \ lib/$(DEPDIR)/$(am__dirstamp) lib/yang_wrappers.lo: lib/$(am__dirstamp) \ lib/$(DEPDIR)/$(am__dirstamp) lib/zclient.lo: lib/$(am__dirstamp) lib/$(DEPDIR)/$(am__dirstamp) lib/printf/$(am__dirstamp): @$(MKDIR_P) lib/printf @: > lib/printf/$(am__dirstamp) lib/printf/$(DEPDIR)/$(am__dirstamp): @$(MKDIR_P) lib/printf/$(DEPDIR) @: > lib/printf/$(DEPDIR)/$(am__dirstamp) lib/printf/printf-pos.lo: lib/printf/$(am__dirstamp) \ lib/printf/$(DEPDIR)/$(am__dirstamp) lib/printf/vfprintf.lo: lib/printf/$(am__dirstamp) \ lib/printf/$(DEPDIR)/$(am__dirstamp) lib/printf/glue.lo: lib/printf/$(am__dirstamp) \ lib/printf/$(DEPDIR)/$(am__dirstamp) lib/db.lo: lib/$(am__dirstamp) lib/$(DEPDIR)/$(am__dirstamp) yang/$(am__dirstamp): @$(MKDIR_P) yang @: > yang/$(am__dirstamp) yang/$(DEPDIR)/$(am__dirstamp): @$(MKDIR_P) yang/$(DEPDIR) @: > yang/$(DEPDIR)/$(am__dirstamp) yang/frr-interface.yang.lo: yang/$(am__dirstamp) \ yang/$(DEPDIR)/$(am__dirstamp) yang/frr-route-types.yang.lo: yang/$(am__dirstamp) \ yang/$(DEPDIR)/$(am__dirstamp) yang/frr-module-translator.yang.lo: yang/$(am__dirstamp) \ yang/$(DEPDIR)/$(am__dirstamp) lib/libfrr.la: $(lib_libfrr_la_OBJECTS) $(lib_libfrr_la_DEPENDENCIES) $(EXTRA_lib_libfrr_la_DEPENDENCIES) lib/$(am__dirstamp) $(AM_V_CCLD)$(lib_libfrr_la_LINK) -rpath $(libdir) $(lib_libfrr_la_OBJECTS) $(lib_libfrr_la_LIBADD) $(LIBS) lib/libfrrcares_la-resolver.lo: lib/$(am__dirstamp) \ lib/$(DEPDIR)/$(am__dirstamp) lib/libfrrcares.la: $(lib_libfrrcares_la_OBJECTS) $(lib_libfrrcares_la_DEPENDENCIES) $(EXTRA_lib_libfrrcares_la_DEPENDENCIES) lib/$(am__dirstamp) $(AM_V_CCLD)$(lib_libfrrcares_la_LINK) $(am_lib_libfrrcares_la_rpath) $(lib_libfrrcares_la_OBJECTS) $(lib_libfrrcares_la_LIBADD) $(LIBS) lib/libfrrsnmp_la-agentx.lo: lib/$(am__dirstamp) \ lib/$(DEPDIR)/$(am__dirstamp) lib/libfrrsnmp_la-snmp.lo: lib/$(am__dirstamp) \ lib/$(DEPDIR)/$(am__dirstamp) lib/libfrrsnmp.la: $(lib_libfrrsnmp_la_OBJECTS) $(lib_libfrrsnmp_la_DEPENDENCIES) $(EXTRA_lib_libfrrsnmp_la_DEPENDENCIES) lib/$(am__dirstamp) $(AM_V_CCLD)$(lib_libfrrsnmp_la_LINK) $(am_lib_libfrrsnmp_la_rpath) $(lib_libfrrsnmp_la_OBJECTS) $(lib_libfrrsnmp_la_LIBADD) $(LIBS) lib/libfrrzmq_la-frr_zmq.lo: lib/$(am__dirstamp) \ lib/$(DEPDIR)/$(am__dirstamp) lib/libfrrzmq.la: $(lib_libfrrzmq_la_OBJECTS) $(lib_libfrrzmq_la_DEPENDENCIES) $(EXTRA_lib_libfrrzmq_la_DEPENDENCIES) lib/$(am__dirstamp) $(AM_V_CCLD)$(lib_libfrrzmq_la_LINK) $(am_lib_libfrrzmq_la_rpath) $(lib_libfrrzmq_la_OBJECTS) $(lib_libfrrzmq_la_LIBADD) $(LIBS) lib/sysrepo_la-northbound_sysrepo.lo: lib/$(am__dirstamp) \ lib/$(DEPDIR)/$(am__dirstamp) lib/sysrepo.la: $(lib_sysrepo_la_OBJECTS) $(lib_sysrepo_la_DEPENDENCIES) $(EXTRA_lib_sysrepo_la_DEPENDENCIES) lib/$(am__dirstamp) $(AM_V_CCLD)$(lib_sysrepo_la_LINK) $(am_lib_sysrepo_la_rpath) $(lib_sysrepo_la_OBJECTS) $(lib_sysrepo_la_LIBADD) $(LIBS) ospf6d/ospf6d_snmp_la-ospf6_snmp.lo: ospf6d/$(am__dirstamp) \ ospf6d/$(DEPDIR)/$(am__dirstamp) ospf6d/ospf6d_snmp.la: $(ospf6d_ospf6d_snmp_la_OBJECTS) $(ospf6d_ospf6d_snmp_la_DEPENDENCIES) $(EXTRA_ospf6d_ospf6d_snmp_la_DEPENDENCIES) ospf6d/$(am__dirstamp) $(AM_V_CCLD)$(ospf6d_ospf6d_snmp_la_LINK) $(am_ospf6d_ospf6d_snmp_la_rpath) $(ospf6d_ospf6d_snmp_la_OBJECTS) $(ospf6d_ospf6d_snmp_la_LIBADD) $(LIBS) ospfclient/$(am__dirstamp): @$(MKDIR_P) ospfclient @: > ospfclient/$(am__dirstamp) ospfclient/$(DEPDIR)/$(am__dirstamp): @$(MKDIR_P) ospfclient/$(DEPDIR) @: > ospfclient/$(DEPDIR)/$(am__dirstamp) ospfclient/ospf_apiclient.lo: ospfclient/$(am__dirstamp) \ ospfclient/$(DEPDIR)/$(am__dirstamp) ospfclient/libfrrospfapiclient.la: $(ospfclient_libfrrospfapiclient_la_OBJECTS) $(ospfclient_libfrrospfapiclient_la_DEPENDENCIES) $(EXTRA_ospfclient_libfrrospfapiclient_la_DEPENDENCIES) ospfclient/$(am__dirstamp) $(AM_V_CCLD)$(ospfclient_libfrrospfapiclient_la_LINK) $(am_ospfclient_libfrrospfapiclient_la_rpath) $(ospfclient_libfrrospfapiclient_la_OBJECTS) $(ospfclient_libfrrospfapiclient_la_LIBADD) $(LIBS) ospfd/ospfd_snmp_la-ospf_snmp.lo: ospfd/$(am__dirstamp) \ ospfd/$(DEPDIR)/$(am__dirstamp) ospfd/ospfd_snmp.la: $(ospfd_ospfd_snmp_la_OBJECTS) $(ospfd_ospfd_snmp_la_DEPENDENCIES) $(EXTRA_ospfd_ospfd_snmp_la_DEPENDENCIES) ospfd/$(am__dirstamp) $(AM_V_CCLD)$(ospfd_ospfd_snmp_la_LINK) $(am_ospfd_ospfd_snmp_la_rpath) $(ospfd_ospfd_snmp_la_OBJECTS) $(ospfd_ospfd_snmp_la_LIBADD) $(LIBS) qpb/$(am__dirstamp): @$(MKDIR_P) qpb @: > qpb/$(am__dirstamp) qpb/$(DEPDIR)/$(am__dirstamp): @$(MKDIR_P) qpb/$(DEPDIR) @: > qpb/$(DEPDIR)/$(am__dirstamp) qpb/libfrr_pb_la-qpb.lo: qpb/$(am__dirstamp) \ qpb/$(DEPDIR)/$(am__dirstamp) qpb/libfrr_pb_la-qpb_allocator.lo: qpb/$(am__dirstamp) \ qpb/$(DEPDIR)/$(am__dirstamp) qpb/libfrr_pb_la-qpb.pb-c.lo: qpb/$(am__dirstamp) \ qpb/$(DEPDIR)/$(am__dirstamp) qpb/libfrr_pb.la: $(qpb_libfrr_pb_la_OBJECTS) $(qpb_libfrr_pb_la_DEPENDENCIES) $(EXTRA_qpb_libfrr_pb_la_DEPENDENCIES) qpb/$(am__dirstamp) $(AM_V_CCLD)$(qpb_libfrr_pb_la_LINK) $(am_qpb_libfrr_pb_la_rpath) $(qpb_libfrr_pb_la_OBJECTS) $(qpb_libfrr_pb_la_LIBADD) $(LIBS) ripd/ripd_snmp_la-rip_snmp.lo: ripd/$(am__dirstamp) \ ripd/$(DEPDIR)/$(am__dirstamp) ripd/ripd_snmp.la: $(ripd_ripd_snmp_la_OBJECTS) $(ripd_ripd_snmp_la_DEPENDENCIES) $(EXTRA_ripd_ripd_snmp_la_DEPENDENCIES) ripd/$(am__dirstamp) $(AM_V_CCLD)$(ripd_ripd_snmp_la_LINK) $(am_ripd_ripd_snmp_la_rpath) $(ripd_ripd_snmp_la_OBJECTS) $(ripd_ripd_snmp_la_LIBADD) $(LIBS) zebra/$(am__dirstamp): @$(MKDIR_P) zebra @: > zebra/$(am__dirstamp) zebra/$(DEPDIR)/$(am__dirstamp): @$(MKDIR_P) zebra/$(DEPDIR) @: > zebra/$(DEPDIR)/$(am__dirstamp) zebra/zebra_fpm.lo: zebra/$(am__dirstamp) \ zebra/$(DEPDIR)/$(am__dirstamp) zebra/zebra_fpm_netlink.lo: zebra/$(am__dirstamp) \ zebra/$(DEPDIR)/$(am__dirstamp) zebra/zebra_fpm_protobuf.lo: zebra/$(am__dirstamp) \ zebra/$(DEPDIR)/$(am__dirstamp) zebra/zebra_fpm_dt.lo: zebra/$(am__dirstamp) \ zebra/$(DEPDIR)/$(am__dirstamp) zebra/zebra_fpm.la: $(zebra_zebra_fpm_la_OBJECTS) $(zebra_zebra_fpm_la_DEPENDENCIES) $(EXTRA_zebra_zebra_fpm_la_DEPENDENCIES) zebra/$(am__dirstamp) $(AM_V_CCLD)$(zebra_zebra_fpm_la_LINK) $(am_zebra_zebra_fpm_la_rpath) $(zebra_zebra_fpm_la_OBJECTS) $(zebra_zebra_fpm_la_LIBADD) $(LIBS) zebra/irdp_interface.lo: zebra/$(am__dirstamp) \ zebra/$(DEPDIR)/$(am__dirstamp) zebra/irdp_main.lo: zebra/$(am__dirstamp) \ zebra/$(DEPDIR)/$(am__dirstamp) zebra/irdp_packet.lo: zebra/$(am__dirstamp) \ zebra/$(DEPDIR)/$(am__dirstamp) zebra/zebra_irdp.la: $(zebra_zebra_irdp_la_OBJECTS) $(zebra_zebra_irdp_la_DEPENDENCIES) $(EXTRA_zebra_zebra_irdp_la_DEPENDENCIES) zebra/$(am__dirstamp) $(AM_V_CCLD)$(zebra_zebra_irdp_la_LINK) $(am_zebra_zebra_irdp_la_rpath) $(zebra_zebra_irdp_la_OBJECTS) $(zebra_zebra_irdp_la_LIBADD) $(LIBS) zebra/zebra_snmp_la-zebra_snmp.lo: zebra/$(am__dirstamp) \ zebra/$(DEPDIR)/$(am__dirstamp) zebra/zebra_snmp.la: $(zebra_zebra_snmp_la_OBJECTS) $(zebra_zebra_snmp_la_DEPENDENCIES) $(EXTRA_zebra_zebra_snmp_la_DEPENDENCIES) zebra/$(am__dirstamp) $(AM_V_CCLD)$(zebra_zebra_snmp_la_LINK) $(am_zebra_zebra_snmp_la_rpath) $(zebra_zebra_snmp_la_OBJECTS) $(zebra_zebra_snmp_la_LIBADD) $(LIBS) babeld/babel_main.$(OBJEXT): babeld/$(am__dirstamp) \ babeld/$(DEPDIR)/$(am__dirstamp) babeld/babeld$(EXEEXT): $(babeld_babeld_OBJECTS) $(babeld_babeld_DEPENDENCIES) $(EXTRA_babeld_babeld_DEPENDENCIES) babeld/$(am__dirstamp) @rm -f babeld/babeld$(EXEEXT) $(AM_V_CCLD)$(LINK) $(babeld_babeld_OBJECTS) $(babeld_babeld_LDADD) $(LIBS) bfdd/bfdd.$(OBJEXT): bfdd/$(am__dirstamp) \ bfdd/$(DEPDIR)/$(am__dirstamp) yang/frr-bfdd.yang.$(OBJEXT): yang/$(am__dirstamp) \ yang/$(DEPDIR)/$(am__dirstamp) bfdd/bfdd$(EXEEXT): $(bfdd_bfdd_OBJECTS) $(bfdd_bfdd_DEPENDENCIES) $(EXTRA_bfdd_bfdd_DEPENDENCIES) bfdd/$(am__dirstamp) @rm -f bfdd/bfdd$(EXEEXT) $(AM_V_CCLD)$(LINK) $(bfdd_bfdd_OBJECTS) $(bfdd_bfdd_LDADD) $(LIBS) bgpd/bgp_btoa-bgp_btoa.$(OBJEXT): bgpd/$(am__dirstamp) \ bgpd/$(DEPDIR)/$(am__dirstamp) bgpd/rfapi/bgp_btoa-rfapi_descriptor_rfp_utils.$(OBJEXT): \ bgpd/rfapi/$(am__dirstamp) \ bgpd/rfapi/$(DEPDIR)/$(am__dirstamp) bgpd/bgp_btoa$(EXEEXT): $(bgpd_bgp_btoa_OBJECTS) $(bgpd_bgp_btoa_DEPENDENCIES) $(EXTRA_bgpd_bgp_btoa_DEPENDENCIES) bgpd/$(am__dirstamp) @rm -f bgpd/bgp_btoa$(EXEEXT) $(AM_V_CCLD)$(bgpd_bgp_btoa_LINK) $(bgpd_bgp_btoa_OBJECTS) $(bgpd_bgp_btoa_LDADD) $(LIBS) bgpd/bgpd-bgp_main.$(OBJEXT): bgpd/$(am__dirstamp) \ bgpd/$(DEPDIR)/$(am__dirstamp) bgpd/rfapi/bgpd-rfapi_descriptor_rfp_utils.$(OBJEXT): \ bgpd/rfapi/$(am__dirstamp) \ bgpd/rfapi/$(DEPDIR)/$(am__dirstamp) bgpd/bgpd$(EXEEXT): $(bgpd_bgpd_OBJECTS) $(bgpd_bgpd_DEPENDENCIES) $(EXTRA_bgpd_bgpd_DEPENDENCIES) bgpd/$(am__dirstamp) @rm -f bgpd/bgpd$(EXEEXT) $(AM_V_CCLD)$(bgpd_bgpd_LINK) $(bgpd_bgpd_OBJECTS) $(bgpd_bgpd_LDADD) $(LIBS) bgpd/rfp-example/rfptest/$(am__dirstamp): @$(MKDIR_P) bgpd/rfp-example/rfptest @: > bgpd/rfp-example/rfptest/$(am__dirstamp) bgpd/rfp-example/rfptest/$(DEPDIR)/$(am__dirstamp): @$(MKDIR_P) bgpd/rfp-example/rfptest/$(DEPDIR) @: > bgpd/rfp-example/rfptest/$(DEPDIR)/$(am__dirstamp) bgpd/rfp-example/rfptest/rfptest-rfptest.$(OBJEXT): \ bgpd/rfp-example/rfptest/$(am__dirstamp) \ bgpd/rfp-example/rfptest/$(DEPDIR)/$(am__dirstamp) bgpd/rfp-example/rfptest/rfptest$(EXEEXT): $(bgpd_rfp_example_rfptest_rfptest_OBJECTS) $(bgpd_rfp_example_rfptest_rfptest_DEPENDENCIES) $(EXTRA_bgpd_rfp_example_rfptest_rfptest_DEPENDENCIES) bgpd/rfp-example/rfptest/$(am__dirstamp) @rm -f bgpd/rfp-example/rfptest/rfptest$(EXEEXT) $(AM_V_CCLD)$(bgpd_rfp_example_rfptest_rfptest_LINK) $(bgpd_rfp_example_rfptest_rfptest_OBJECTS) $(bgpd_rfp_example_rfptest_rfptest_LDADD) $(LIBS) eigrpd/eigrp_main.$(OBJEXT): eigrpd/$(am__dirstamp) \ eigrpd/$(DEPDIR)/$(am__dirstamp) yang/frr-eigrpd.yang.$(OBJEXT): yang/$(am__dirstamp) \ yang/$(DEPDIR)/$(am__dirstamp) eigrpd/eigrpd$(EXEEXT): $(eigrpd_eigrpd_OBJECTS) $(eigrpd_eigrpd_DEPENDENCIES) $(EXTRA_eigrpd_eigrpd_DEPENDENCIES) eigrpd/$(am__dirstamp) @rm -f eigrpd/eigrpd$(EXEEXT) $(AM_V_CCLD)$(LINK) $(eigrpd_eigrpd_OBJECTS) $(eigrpd_eigrpd_LDADD) $(LIBS) isisd/fabricd-isis_bpf.$(OBJEXT): isisd/$(am__dirstamp) \ isisd/$(DEPDIR)/$(am__dirstamp) isisd/fabricd-isis_dlpi.$(OBJEXT): isisd/$(am__dirstamp) \ isisd/$(DEPDIR)/$(am__dirstamp) isisd/fabricd-isis_main.$(OBJEXT): isisd/$(am__dirstamp) \ isisd/$(DEPDIR)/$(am__dirstamp) isisd/fabricd-isis_pfpacket.$(OBJEXT): isisd/$(am__dirstamp) \ isisd/$(DEPDIR)/$(am__dirstamp) isisd/fabricd$(EXEEXT): $(isisd_fabricd_OBJECTS) $(isisd_fabricd_DEPENDENCIES) $(EXTRA_isisd_fabricd_DEPENDENCIES) isisd/$(am__dirstamp) @rm -f isisd/fabricd$(EXEEXT) $(AM_V_CCLD)$(LINK) $(isisd_fabricd_OBJECTS) $(isisd_fabricd_LDADD) $(LIBS) isisd/isis_bpf.$(OBJEXT): isisd/$(am__dirstamp) \ isisd/$(DEPDIR)/$(am__dirstamp) isisd/isis_dlpi.$(OBJEXT): isisd/$(am__dirstamp) \ isisd/$(DEPDIR)/$(am__dirstamp) isisd/isis_main.$(OBJEXT): isisd/$(am__dirstamp) \ isisd/$(DEPDIR)/$(am__dirstamp) isisd/isis_pfpacket.$(OBJEXT): isisd/$(am__dirstamp) \ isisd/$(DEPDIR)/$(am__dirstamp) yang/frr-isisd.yang.$(OBJEXT): yang/$(am__dirstamp) \ yang/$(DEPDIR)/$(am__dirstamp) isisd/isisd$(EXEEXT): $(isisd_isisd_OBJECTS) $(isisd_isisd_DEPENDENCIES) $(EXTRA_isisd_isisd_DEPENDENCIES) isisd/$(am__dirstamp) @rm -f isisd/isisd$(EXEEXT) $(AM_V_CCLD)$(LINK) $(isisd_isisd_OBJECTS) $(isisd_isisd_LDADD) $(LIBS) ldpd/ldpd$(EXEEXT): $(ldpd_ldpd_OBJECTS) $(ldpd_ldpd_DEPENDENCIES) $(EXTRA_ldpd_ldpd_DEPENDENCIES) ldpd/$(am__dirstamp) @rm -f ldpd/ldpd$(EXEEXT) $(AM_V_CCLD)$(LINK) $(ldpd_ldpd_OBJECTS) $(ldpd_ldpd_LDADD) $(LIBS) lib/clippy-clippy.$(OBJEXT): lib/$(am__dirstamp) \ lib/$(DEPDIR)/$(am__dirstamp) lib/clippy-command_graph.$(OBJEXT): lib/$(am__dirstamp) \ lib/$(DEPDIR)/$(am__dirstamp) lib/clippy-command_lex.$(OBJEXT): lib/$(am__dirstamp) \ lib/$(DEPDIR)/$(am__dirstamp) lib/clippy-command_parse.$(OBJEXT): lib/$(am__dirstamp) \ lib/$(DEPDIR)/$(am__dirstamp) lib/clippy-command_py.$(OBJEXT): lib/$(am__dirstamp) \ lib/$(DEPDIR)/$(am__dirstamp) lib/clippy-defun_lex.$(OBJEXT): lib/$(am__dirstamp) \ lib/$(DEPDIR)/$(am__dirstamp) lib/clippy-graph.$(OBJEXT): lib/$(am__dirstamp) \ lib/$(DEPDIR)/$(am__dirstamp) lib/clippy-memory.$(OBJEXT): lib/$(am__dirstamp) \ lib/$(DEPDIR)/$(am__dirstamp) lib/clippy-vector.$(OBJEXT): lib/$(am__dirstamp) \ lib/$(DEPDIR)/$(am__dirstamp) lib/clippy$(EXEEXT): $(lib_clippy_OBJECTS) $(lib_clippy_DEPENDENCIES) $(EXTRA_lib_clippy_DEPENDENCIES) lib/$(am__dirstamp) @rm -f lib/clippy$(EXEEXT) $(AM_V_CCLD)$(lib_clippy_LINK) $(lib_clippy_OBJECTS) $(lib_clippy_LDADD) $(LIBS) lib/grammar_sandbox_main.$(OBJEXT): lib/$(am__dirstamp) \ lib/$(DEPDIR)/$(am__dirstamp) lib/grammar_sandbox$(EXEEXT): $(lib_grammar_sandbox_OBJECTS) $(lib_grammar_sandbox_DEPENDENCIES) $(EXTRA_lib_grammar_sandbox_DEPENDENCIES) lib/$(am__dirstamp) @rm -f lib/grammar_sandbox$(EXEEXT) $(AM_V_CCLD)$(LINK) $(lib_grammar_sandbox_OBJECTS) $(lib_grammar_sandbox_LDADD) $(LIBS) nhrpd/$(am__dirstamp): @$(MKDIR_P) nhrpd @: > nhrpd/$(am__dirstamp) nhrpd/$(DEPDIR)/$(am__dirstamp): @$(MKDIR_P) nhrpd/$(DEPDIR) @: > nhrpd/$(DEPDIR)/$(am__dirstamp) nhrpd/linux.$(OBJEXT): nhrpd/$(am__dirstamp) \ nhrpd/$(DEPDIR)/$(am__dirstamp) nhrpd/netlink_arp.$(OBJEXT): nhrpd/$(am__dirstamp) \ nhrpd/$(DEPDIR)/$(am__dirstamp) nhrpd/netlink_gre.$(OBJEXT): nhrpd/$(am__dirstamp) \ nhrpd/$(DEPDIR)/$(am__dirstamp) nhrpd/nhrp_cache.$(OBJEXT): nhrpd/$(am__dirstamp) \ nhrpd/$(DEPDIR)/$(am__dirstamp) nhrpd/nhrp_errors.$(OBJEXT): nhrpd/$(am__dirstamp) \ nhrpd/$(DEPDIR)/$(am__dirstamp) nhrpd/nhrp_event.$(OBJEXT): nhrpd/$(am__dirstamp) \ nhrpd/$(DEPDIR)/$(am__dirstamp) nhrpd/nhrp_interface.$(OBJEXT): nhrpd/$(am__dirstamp) \ nhrpd/$(DEPDIR)/$(am__dirstamp) nhrpd/nhrp_main.$(OBJEXT): nhrpd/$(am__dirstamp) \ nhrpd/$(DEPDIR)/$(am__dirstamp) nhrpd/nhrp_nhs.$(OBJEXT): nhrpd/$(am__dirstamp) \ nhrpd/$(DEPDIR)/$(am__dirstamp) nhrpd/nhrp_packet.$(OBJEXT): nhrpd/$(am__dirstamp) \ nhrpd/$(DEPDIR)/$(am__dirstamp) nhrpd/nhrp_peer.$(OBJEXT): nhrpd/$(am__dirstamp) \ nhrpd/$(DEPDIR)/$(am__dirstamp) nhrpd/nhrp_route.$(OBJEXT): nhrpd/$(am__dirstamp) \ nhrpd/$(DEPDIR)/$(am__dirstamp) nhrpd/nhrp_shortcut.$(OBJEXT): nhrpd/$(am__dirstamp) \ nhrpd/$(DEPDIR)/$(am__dirstamp) nhrpd/nhrp_vc.$(OBJEXT): nhrpd/$(am__dirstamp) \ nhrpd/$(DEPDIR)/$(am__dirstamp) nhrpd/nhrp_vty.$(OBJEXT): nhrpd/$(am__dirstamp) \ nhrpd/$(DEPDIR)/$(am__dirstamp) nhrpd/reqid.$(OBJEXT): nhrpd/$(am__dirstamp) \ nhrpd/$(DEPDIR)/$(am__dirstamp) nhrpd/vici.$(OBJEXT): nhrpd/$(am__dirstamp) \ nhrpd/$(DEPDIR)/$(am__dirstamp) nhrpd/zbuf.$(OBJEXT): nhrpd/$(am__dirstamp) \ nhrpd/$(DEPDIR)/$(am__dirstamp) nhrpd/znl.$(OBJEXT): nhrpd/$(am__dirstamp) \ nhrpd/$(DEPDIR)/$(am__dirstamp) nhrpd/nhrpd$(EXEEXT): $(nhrpd_nhrpd_OBJECTS) $(nhrpd_nhrpd_DEPENDENCIES) $(EXTRA_nhrpd_nhrpd_DEPENDENCIES) nhrpd/$(am__dirstamp) @rm -f nhrpd/nhrpd$(EXEEXT) $(AM_V_CCLD)$(LINK) $(nhrpd_nhrpd_OBJECTS) $(nhrpd_nhrpd_LDADD) $(LIBS) ospf6d/ospf6_main.$(OBJEXT): ospf6d/$(am__dirstamp) \ ospf6d/$(DEPDIR)/$(am__dirstamp) ospf6d/ospf6d$(EXEEXT): $(ospf6d_ospf6d_OBJECTS) $(ospf6d_ospf6d_DEPENDENCIES) $(EXTRA_ospf6d_ospf6d_DEPENDENCIES) ospf6d/$(am__dirstamp) @rm -f ospf6d/ospf6d$(EXEEXT) $(AM_V_CCLD)$(LINK) $(ospf6d_ospf6d_OBJECTS) $(ospf6d_ospf6d_LDADD) $(LIBS) ospfclient/ospfclient.$(OBJEXT): ospfclient/$(am__dirstamp) \ ospfclient/$(DEPDIR)/$(am__dirstamp) ospfclient/ospfclient$(EXEEXT): $(ospfclient_ospfclient_OBJECTS) $(ospfclient_ospfclient_DEPENDENCIES) $(EXTRA_ospfclient_ospfclient_DEPENDENCIES) ospfclient/$(am__dirstamp) @rm -f ospfclient/ospfclient$(EXEEXT) $(AM_V_CCLD)$(LINK) $(ospfclient_ospfclient_OBJECTS) $(ospfclient_ospfclient_LDADD) $(LIBS) ospfd/ospf_main.$(OBJEXT): ospfd/$(am__dirstamp) \ ospfd/$(DEPDIR)/$(am__dirstamp) ospfd/ospfd$(EXEEXT): $(ospfd_ospfd_OBJECTS) $(ospfd_ospfd_DEPENDENCIES) $(EXTRA_ospfd_ospfd_DEPENDENCIES) ospfd/$(am__dirstamp) @rm -f ospfd/ospfd$(EXEEXT) $(AM_V_CCLD)$(LINK) $(ospfd_ospfd_OBJECTS) $(ospfd_ospfd_LDADD) $(LIBS) pbrd/pbr_main.$(OBJEXT): pbrd/$(am__dirstamp) \ pbrd/$(DEPDIR)/$(am__dirstamp) pbrd/pbrd$(EXEEXT): $(pbrd_pbrd_OBJECTS) $(pbrd_pbrd_DEPENDENCIES) $(EXTRA_pbrd_pbrd_DEPENDENCIES) pbrd/$(am__dirstamp) @rm -f pbrd/pbrd$(EXEEXT) $(AM_V_CCLD)$(LINK) $(pbrd_pbrd_OBJECTS) $(pbrd_pbrd_LDADD) $(LIBS) pimd/mtracebis.$(OBJEXT): pimd/$(am__dirstamp) \ pimd/$(DEPDIR)/$(am__dirstamp) pimd/mtracebis_netlink.$(OBJEXT): pimd/$(am__dirstamp) \ pimd/$(DEPDIR)/$(am__dirstamp) pimd/mtracebis_routeget.$(OBJEXT): pimd/$(am__dirstamp) \ pimd/$(DEPDIR)/$(am__dirstamp) pimd/mtracebis$(EXEEXT): $(pimd_mtracebis_OBJECTS) $(pimd_mtracebis_DEPENDENCIES) $(EXTRA_pimd_mtracebis_DEPENDENCIES) pimd/$(am__dirstamp) @rm -f pimd/mtracebis$(EXEEXT) $(AM_V_CCLD)$(LINK) $(pimd_mtracebis_OBJECTS) $(pimd_mtracebis_LDADD) $(LIBS) pimd/pim_main.$(OBJEXT): pimd/$(am__dirstamp) \ pimd/$(DEPDIR)/$(am__dirstamp) pimd/pimd$(EXEEXT): $(pimd_pimd_OBJECTS) $(pimd_pimd_DEPENDENCIES) $(EXTRA_pimd_pimd_DEPENDENCIES) pimd/$(am__dirstamp) @rm -f pimd/pimd$(EXEEXT) $(AM_V_CCLD)$(LINK) $(pimd_pimd_OBJECTS) $(pimd_pimd_LDADD) $(LIBS) pimd/test_igmpv3_join.$(OBJEXT): pimd/$(am__dirstamp) \ pimd/$(DEPDIR)/$(am__dirstamp) pimd/test_igmpv3_join$(EXEEXT): $(pimd_test_igmpv3_join_OBJECTS) $(pimd_test_igmpv3_join_DEPENDENCIES) $(EXTRA_pimd_test_igmpv3_join_DEPENDENCIES) pimd/$(am__dirstamp) @rm -f pimd/test_igmpv3_join$(EXEEXT) $(AM_V_CCLD)$(LINK) $(pimd_test_igmpv3_join_OBJECTS) $(pimd_test_igmpv3_join_LDADD) $(LIBS) ripd/rip_main.$(OBJEXT): ripd/$(am__dirstamp) \ ripd/$(DEPDIR)/$(am__dirstamp) yang/frr-ripd.yang.$(OBJEXT): yang/$(am__dirstamp) \ yang/$(DEPDIR)/$(am__dirstamp) ripd/ripd$(EXEEXT): $(ripd_ripd_OBJECTS) $(ripd_ripd_DEPENDENCIES) $(EXTRA_ripd_ripd_DEPENDENCIES) ripd/$(am__dirstamp) @rm -f ripd/ripd$(EXEEXT) $(AM_V_CCLD)$(LINK) $(ripd_ripd_OBJECTS) $(ripd_ripd_LDADD) $(LIBS) ripngd/ripng_main.$(OBJEXT): ripngd/$(am__dirstamp) \ ripngd/$(DEPDIR)/$(am__dirstamp) yang/frr-ripngd.yang.$(OBJEXT): yang/$(am__dirstamp) \ yang/$(DEPDIR)/$(am__dirstamp) ripngd/ripngd$(EXEEXT): $(ripngd_ripngd_OBJECTS) $(ripngd_ripngd_DEPENDENCIES) $(EXTRA_ripngd_ripngd_DEPENDENCIES) ripngd/$(am__dirstamp) @rm -f ripngd/ripngd$(EXEEXT) $(AM_V_CCLD)$(LINK) $(ripngd_ripngd_OBJECTS) $(ripngd_ripngd_LDADD) $(LIBS) sharpd/sharp_main.$(OBJEXT): sharpd/$(am__dirstamp) \ sharpd/$(DEPDIR)/$(am__dirstamp) sharpd/sharpd$(EXEEXT): $(sharpd_sharpd_OBJECTS) $(sharpd_sharpd_DEPENDENCIES) $(EXTRA_sharpd_sharpd_DEPENDENCIES) sharpd/$(am__dirstamp) @rm -f sharpd/sharpd$(EXEEXT) $(AM_V_CCLD)$(LINK) $(sharpd_sharpd_OBJECTS) $(sharpd_sharpd_LDADD) $(LIBS) staticd/static_main.$(OBJEXT): staticd/$(am__dirstamp) \ staticd/$(DEPDIR)/$(am__dirstamp) staticd/staticd$(EXEEXT): $(staticd_staticd_OBJECTS) $(staticd_staticd_DEPENDENCIES) $(EXTRA_staticd_staticd_DEPENDENCIES) staticd/$(am__dirstamp) @rm -f staticd/staticd$(EXEEXT) $(AM_V_CCLD)$(LINK) $(staticd_staticd_OBJECTS) $(staticd_staticd_LDADD) $(LIBS) tests/bgpd/$(am__dirstamp): @$(MKDIR_P) tests/bgpd @: > tests/bgpd/$(am__dirstamp) tests/bgpd/$(DEPDIR)/$(am__dirstamp): @$(MKDIR_P) tests/bgpd/$(DEPDIR) @: > tests/bgpd/$(DEPDIR)/$(am__dirstamp) tests/bgpd/test_aspath-test_aspath.$(OBJEXT): \ tests/bgpd/$(am__dirstamp) \ tests/bgpd/$(DEPDIR)/$(am__dirstamp) tests/bgpd/test_aspath$(EXEEXT): $(tests_bgpd_test_aspath_OBJECTS) $(tests_bgpd_test_aspath_DEPENDENCIES) $(EXTRA_tests_bgpd_test_aspath_DEPENDENCIES) tests/bgpd/$(am__dirstamp) @rm -f tests/bgpd/test_aspath$(EXEEXT) $(AM_V_CCLD)$(tests_bgpd_test_aspath_LINK) $(tests_bgpd_test_aspath_OBJECTS) $(tests_bgpd_test_aspath_LDADD) $(LIBS) tests/bgpd/test_bgp_table-test_bgp_table.$(OBJEXT): \ tests/bgpd/$(am__dirstamp) \ tests/bgpd/$(DEPDIR)/$(am__dirstamp) tests/bgpd/test_bgp_table$(EXEEXT): $(tests_bgpd_test_bgp_table_OBJECTS) $(tests_bgpd_test_bgp_table_DEPENDENCIES) $(EXTRA_tests_bgpd_test_bgp_table_DEPENDENCIES) tests/bgpd/$(am__dirstamp) @rm -f tests/bgpd/test_bgp_table$(EXEEXT) $(AM_V_CCLD)$(tests_bgpd_test_bgp_table_LINK) $(tests_bgpd_test_bgp_table_OBJECTS) $(tests_bgpd_test_bgp_table_LDADD) $(LIBS) tests/bgpd/test_capability-test_capability.$(OBJEXT): \ tests/bgpd/$(am__dirstamp) \ tests/bgpd/$(DEPDIR)/$(am__dirstamp) tests/bgpd/test_capability$(EXEEXT): $(tests_bgpd_test_capability_OBJECTS) $(tests_bgpd_test_capability_DEPENDENCIES) $(EXTRA_tests_bgpd_test_capability_DEPENDENCIES) tests/bgpd/$(am__dirstamp) @rm -f tests/bgpd/test_capability$(EXEEXT) $(AM_V_CCLD)$(tests_bgpd_test_capability_LINK) $(tests_bgpd_test_capability_OBJECTS) $(tests_bgpd_test_capability_LDADD) $(LIBS) tests/bgpd/test_ecommunity-test_ecommunity.$(OBJEXT): \ tests/bgpd/$(am__dirstamp) \ tests/bgpd/$(DEPDIR)/$(am__dirstamp) tests/bgpd/test_ecommunity$(EXEEXT): $(tests_bgpd_test_ecommunity_OBJECTS) $(tests_bgpd_test_ecommunity_DEPENDENCIES) $(EXTRA_tests_bgpd_test_ecommunity_DEPENDENCIES) tests/bgpd/$(am__dirstamp) @rm -f tests/bgpd/test_ecommunity$(EXEEXT) $(AM_V_CCLD)$(tests_bgpd_test_ecommunity_LINK) $(tests_bgpd_test_ecommunity_OBJECTS) $(tests_bgpd_test_ecommunity_LDADD) $(LIBS) tests/bgpd/test_mp_attr-test_mp_attr.$(OBJEXT): \ tests/bgpd/$(am__dirstamp) \ tests/bgpd/$(DEPDIR)/$(am__dirstamp) tests/bgpd/test_mp_attr$(EXEEXT): $(tests_bgpd_test_mp_attr_OBJECTS) $(tests_bgpd_test_mp_attr_DEPENDENCIES) $(EXTRA_tests_bgpd_test_mp_attr_DEPENDENCIES) tests/bgpd/$(am__dirstamp) @rm -f tests/bgpd/test_mp_attr$(EXEEXT) $(AM_V_CCLD)$(tests_bgpd_test_mp_attr_LINK) $(tests_bgpd_test_mp_attr_OBJECTS) $(tests_bgpd_test_mp_attr_LDADD) $(LIBS) tests/bgpd/test_mpath-test_mpath.$(OBJEXT): \ tests/bgpd/$(am__dirstamp) \ tests/bgpd/$(DEPDIR)/$(am__dirstamp) tests/bgpd/test_mpath$(EXEEXT): $(tests_bgpd_test_mpath_OBJECTS) $(tests_bgpd_test_mpath_DEPENDENCIES) $(EXTRA_tests_bgpd_test_mpath_DEPENDENCIES) tests/bgpd/$(am__dirstamp) @rm -f tests/bgpd/test_mpath$(EXEEXT) $(AM_V_CCLD)$(tests_bgpd_test_mpath_LINK) $(tests_bgpd_test_mpath_OBJECTS) $(tests_bgpd_test_mpath_LDADD) $(LIBS) tests/bgpd/test_packet-test_packet.$(OBJEXT): \ tests/bgpd/$(am__dirstamp) \ tests/bgpd/$(DEPDIR)/$(am__dirstamp) tests/bgpd/test_packet$(EXEEXT): $(tests_bgpd_test_packet_OBJECTS) $(tests_bgpd_test_packet_DEPENDENCIES) $(EXTRA_tests_bgpd_test_packet_DEPENDENCIES) tests/bgpd/$(am__dirstamp) @rm -f tests/bgpd/test_packet$(EXEEXT) $(AM_V_CCLD)$(tests_bgpd_test_packet_LINK) $(tests_bgpd_test_packet_OBJECTS) $(tests_bgpd_test_packet_LDADD) $(LIBS) tests/bgpd/test_peer_attr-test_peer_attr.$(OBJEXT): \ tests/bgpd/$(am__dirstamp) \ tests/bgpd/$(DEPDIR)/$(am__dirstamp) tests/bgpd/test_peer_attr$(EXEEXT): $(tests_bgpd_test_peer_attr_OBJECTS) $(tests_bgpd_test_peer_attr_DEPENDENCIES) $(EXTRA_tests_bgpd_test_peer_attr_DEPENDENCIES) tests/bgpd/$(am__dirstamp) @rm -f tests/bgpd/test_peer_attr$(EXEEXT) $(AM_V_CCLD)$(tests_bgpd_test_peer_attr_LINK) $(tests_bgpd_test_peer_attr_OBJECTS) $(tests_bgpd_test_peer_attr_LDADD) $(LIBS) tests/isisd/$(am__dirstamp): @$(MKDIR_P) tests/isisd @: > tests/isisd/$(am__dirstamp) tests/isisd/$(DEPDIR)/$(am__dirstamp): @$(MKDIR_P) tests/isisd/$(DEPDIR) @: > tests/isisd/$(DEPDIR)/$(am__dirstamp) tests/isisd/test_fuzz_isis_tlv-test_fuzz_isis_tlv.$(OBJEXT): \ tests/isisd/$(am__dirstamp) \ tests/isisd/$(DEPDIR)/$(am__dirstamp) tests/isisd/test_fuzz_isis_tlv$(EXEEXT): $(tests_isisd_test_fuzz_isis_tlv_OBJECTS) $(tests_isisd_test_fuzz_isis_tlv_DEPENDENCIES) $(EXTRA_tests_isisd_test_fuzz_isis_tlv_DEPENDENCIES) tests/isisd/$(am__dirstamp) @rm -f tests/isisd/test_fuzz_isis_tlv$(EXEEXT) $(AM_V_CCLD)$(tests_isisd_test_fuzz_isis_tlv_LINK) $(tests_isisd_test_fuzz_isis_tlv_OBJECTS) $(tests_isisd_test_fuzz_isis_tlv_LDADD) $(LIBS) tests/isisd/test_isis_lspdb-test_isis_lspdb.$(OBJEXT): \ tests/isisd/$(am__dirstamp) \ tests/isisd/$(DEPDIR)/$(am__dirstamp) tests/isisd/test_isis_lspdb$(EXEEXT): $(tests_isisd_test_isis_lspdb_OBJECTS) $(tests_isisd_test_isis_lspdb_DEPENDENCIES) $(EXTRA_tests_isisd_test_isis_lspdb_DEPENDENCIES) tests/isisd/$(am__dirstamp) @rm -f tests/isisd/test_isis_lspdb$(EXEEXT) $(AM_V_CCLD)$(tests_isisd_test_isis_lspdb_LINK) $(tests_isisd_test_isis_lspdb_OBJECTS) $(tests_isisd_test_isis_lspdb_LDADD) $(LIBS) tests/isisd/test_isis_vertex_queue-test_isis_vertex_queue.$(OBJEXT): \ tests/isisd/$(am__dirstamp) \ tests/isisd/$(DEPDIR)/$(am__dirstamp) tests/isisd/test_isis_vertex_queue$(EXEEXT): $(tests_isisd_test_isis_vertex_queue_OBJECTS) $(tests_isisd_test_isis_vertex_queue_DEPENDENCIES) $(EXTRA_tests_isisd_test_isis_vertex_queue_DEPENDENCIES) tests/isisd/$(am__dirstamp) @rm -f tests/isisd/test_isis_vertex_queue$(EXEEXT) $(AM_V_CCLD)$(tests_isisd_test_isis_vertex_queue_LINK) $(tests_isisd_test_isis_vertex_queue_OBJECTS) $(tests_isisd_test_isis_vertex_queue_LDADD) $(LIBS) tests/lib/cli/$(am__dirstamp): @$(MKDIR_P) tests/lib/cli @: > tests/lib/cli/$(am__dirstamp) tests/lib/cli/$(DEPDIR)/$(am__dirstamp): @$(MKDIR_P) tests/lib/cli/$(DEPDIR) @: > tests/lib/cli/$(DEPDIR)/$(am__dirstamp) tests/lib/cli/test_cli-test_cli.$(OBJEXT): \ tests/lib/cli/$(am__dirstamp) \ tests/lib/cli/$(DEPDIR)/$(am__dirstamp) tests/lib/cli/test_cli-common_cli.$(OBJEXT): \ tests/lib/cli/$(am__dirstamp) \ tests/lib/cli/$(DEPDIR)/$(am__dirstamp) tests/lib/cli/test_cli$(EXEEXT): $(tests_lib_cli_test_cli_OBJECTS) $(tests_lib_cli_test_cli_DEPENDENCIES) $(EXTRA_tests_lib_cli_test_cli_DEPENDENCIES) tests/lib/cli/$(am__dirstamp) @rm -f tests/lib/cli/test_cli$(EXEEXT) $(AM_V_CCLD)$(tests_lib_cli_test_cli_LINK) $(tests_lib_cli_test_cli_OBJECTS) $(tests_lib_cli_test_cli_LDADD) $(LIBS) tests/lib/cli/test_commands-test_commands.$(OBJEXT): \ tests/lib/cli/$(am__dirstamp) \ tests/lib/cli/$(DEPDIR)/$(am__dirstamp) tests/helpers/c/$(am__dirstamp): @$(MKDIR_P) tests/helpers/c @: > tests/helpers/c/$(am__dirstamp) tests/helpers/c/$(DEPDIR)/$(am__dirstamp): @$(MKDIR_P) tests/helpers/c/$(DEPDIR) @: > tests/helpers/c/$(DEPDIR)/$(am__dirstamp) tests/helpers/c/lib_cli_test_commands-prng.$(OBJEXT): \ tests/helpers/c/$(am__dirstamp) \ tests/helpers/c/$(DEPDIR)/$(am__dirstamp) tests/lib/cli/test_commands-test_commands_defun.$(OBJEXT): \ tests/lib/cli/$(am__dirstamp) \ tests/lib/cli/$(DEPDIR)/$(am__dirstamp) tests/lib/cli/test_commands$(EXEEXT): $(tests_lib_cli_test_commands_OBJECTS) $(tests_lib_cli_test_commands_DEPENDENCIES) $(EXTRA_tests_lib_cli_test_commands_DEPENDENCIES) tests/lib/cli/$(am__dirstamp) @rm -f tests/lib/cli/test_commands$(EXEEXT) $(AM_V_CCLD)$(tests_lib_cli_test_commands_LINK) $(tests_lib_cli_test_commands_OBJECTS) $(tests_lib_cli_test_commands_LDADD) $(LIBS) tests/lib/$(am__dirstamp): @$(MKDIR_P) tests/lib @: > tests/lib/$(am__dirstamp) tests/lib/$(DEPDIR)/$(am__dirstamp): @$(MKDIR_P) tests/lib/$(DEPDIR) @: > tests/lib/$(DEPDIR)/$(am__dirstamp) tests/lib/cxxcompat-cxxcompat.$(OBJEXT): tests/lib/$(am__dirstamp) \ tests/lib/$(DEPDIR)/$(am__dirstamp) tests/lib/cxxcompat$(EXEEXT): $(tests_lib_cxxcompat_OBJECTS) $(tests_lib_cxxcompat_DEPENDENCIES) $(EXTRA_tests_lib_cxxcompat_DEPENDENCIES) tests/lib/$(am__dirstamp) @rm -f tests/lib/cxxcompat$(EXEEXT) $(AM_V_CCLD)$(tests_lib_cxxcompat_LINK) $(tests_lib_cxxcompat_OBJECTS) $(tests_lib_cxxcompat_LDADD) $(LIBS) tests/lib/northbound/$(am__dirstamp): @$(MKDIR_P) tests/lib/northbound @: > tests/lib/northbound/$(am__dirstamp) tests/lib/northbound/$(DEPDIR)/$(am__dirstamp): @$(MKDIR_P) tests/lib/northbound/$(DEPDIR) @: > tests/lib/northbound/$(DEPDIR)/$(am__dirstamp) tests/lib/northbound/test_oper_data-test_oper_data.$(OBJEXT): \ tests/lib/northbound/$(am__dirstamp) \ tests/lib/northbound/$(DEPDIR)/$(am__dirstamp) yang/tests_lib_northbound_test_oper_data-frr-test-module.yang.$(OBJEXT): \ yang/$(am__dirstamp) yang/$(DEPDIR)/$(am__dirstamp) tests/lib/northbound/test_oper_data$(EXEEXT): $(tests_lib_northbound_test_oper_data_OBJECTS) $(tests_lib_northbound_test_oper_data_DEPENDENCIES) $(EXTRA_tests_lib_northbound_test_oper_data_DEPENDENCIES) tests/lib/northbound/$(am__dirstamp) @rm -f tests/lib/northbound/test_oper_data$(EXEEXT) $(AM_V_CCLD)$(tests_lib_northbound_test_oper_data_LINK) $(tests_lib_northbound_test_oper_data_OBJECTS) $(tests_lib_northbound_test_oper_data_LDADD) $(LIBS) tests/lib/test_atomlist-test_atomlist.$(OBJEXT): \ tests/lib/$(am__dirstamp) tests/lib/$(DEPDIR)/$(am__dirstamp) tests/lib/test_atomlist$(EXEEXT): $(tests_lib_test_atomlist_OBJECTS) $(tests_lib_test_atomlist_DEPENDENCIES) $(EXTRA_tests_lib_test_atomlist_DEPENDENCIES) tests/lib/$(am__dirstamp) @rm -f tests/lib/test_atomlist$(EXEEXT) $(AM_V_CCLD)$(tests_lib_test_atomlist_LINK) $(tests_lib_test_atomlist_OBJECTS) $(tests_lib_test_atomlist_LDADD) $(LIBS) tests/lib/test_buffer-test_buffer.$(OBJEXT): \ tests/lib/$(am__dirstamp) tests/lib/$(DEPDIR)/$(am__dirstamp) tests/lib/test_buffer$(EXEEXT): $(tests_lib_test_buffer_OBJECTS) $(tests_lib_test_buffer_DEPENDENCIES) $(EXTRA_tests_lib_test_buffer_DEPENDENCIES) tests/lib/$(am__dirstamp) @rm -f tests/lib/test_buffer$(EXEEXT) $(AM_V_CCLD)$(tests_lib_test_buffer_LINK) $(tests_lib_test_buffer_OBJECTS) $(tests_lib_test_buffer_LDADD) $(LIBS) tests/lib/test_checksum-test_checksum.$(OBJEXT): \ tests/lib/$(am__dirstamp) tests/lib/$(DEPDIR)/$(am__dirstamp) tests/lib/test_checksum$(EXEEXT): $(tests_lib_test_checksum_OBJECTS) $(tests_lib_test_checksum_DEPENDENCIES) $(EXTRA_tests_lib_test_checksum_DEPENDENCIES) tests/lib/$(am__dirstamp) @rm -f tests/lib/test_checksum$(EXEEXT) $(AM_V_CCLD)$(tests_lib_test_checksum_LINK) $(tests_lib_test_checksum_OBJECTS) $(tests_lib_test_checksum_LDADD) $(LIBS) tests/lib/test_graph-test_graph.$(OBJEXT): tests/lib/$(am__dirstamp) \ tests/lib/$(DEPDIR)/$(am__dirstamp) tests/lib/test_graph$(EXEEXT): $(tests_lib_test_graph_OBJECTS) $(tests_lib_test_graph_DEPENDENCIES) $(EXTRA_tests_lib_test_graph_DEPENDENCIES) tests/lib/$(am__dirstamp) @rm -f tests/lib/test_graph$(EXEEXT) $(AM_V_CCLD)$(tests_lib_test_graph_LINK) $(tests_lib_test_graph_OBJECTS) $(tests_lib_test_graph_LDADD) $(LIBS) tests/lib/test_heavy-test_heavy.$(OBJEXT): tests/lib/$(am__dirstamp) \ tests/lib/$(DEPDIR)/$(am__dirstamp) tests/helpers/c/lib_test_heavy-main.$(OBJEXT): \ tests/helpers/c/$(am__dirstamp) \ tests/helpers/c/$(DEPDIR)/$(am__dirstamp) tests/lib/test_heavy$(EXEEXT): $(tests_lib_test_heavy_OBJECTS) $(tests_lib_test_heavy_DEPENDENCIES) $(EXTRA_tests_lib_test_heavy_DEPENDENCIES) tests/lib/$(am__dirstamp) @rm -f tests/lib/test_heavy$(EXEEXT) $(AM_V_CCLD)$(tests_lib_test_heavy_LINK) $(tests_lib_test_heavy_OBJECTS) $(tests_lib_test_heavy_LDADD) $(LIBS) tests/lib/test_heavy_thread-test_heavy_thread.$(OBJEXT): \ tests/lib/$(am__dirstamp) tests/lib/$(DEPDIR)/$(am__dirstamp) tests/helpers/c/lib_test_heavy_thread-main.$(OBJEXT): \ tests/helpers/c/$(am__dirstamp) \ tests/helpers/c/$(DEPDIR)/$(am__dirstamp) tests/lib/test_heavy_thread$(EXEEXT): $(tests_lib_test_heavy_thread_OBJECTS) $(tests_lib_test_heavy_thread_DEPENDENCIES) $(EXTRA_tests_lib_test_heavy_thread_DEPENDENCIES) tests/lib/$(am__dirstamp) @rm -f tests/lib/test_heavy_thread$(EXEEXT) $(AM_V_CCLD)$(tests_lib_test_heavy_thread_LINK) $(tests_lib_test_heavy_thread_OBJECTS) $(tests_lib_test_heavy_thread_LDADD) $(LIBS) tests/lib/test_heavy_wq-test_heavy_wq.$(OBJEXT): \ tests/lib/$(am__dirstamp) tests/lib/$(DEPDIR)/$(am__dirstamp) tests/helpers/c/lib_test_heavy_wq-main.$(OBJEXT): \ tests/helpers/c/$(am__dirstamp) \ tests/helpers/c/$(DEPDIR)/$(am__dirstamp) tests/lib/test_heavy_wq$(EXEEXT): $(tests_lib_test_heavy_wq_OBJECTS) $(tests_lib_test_heavy_wq_DEPENDENCIES) $(EXTRA_tests_lib_test_heavy_wq_DEPENDENCIES) tests/lib/$(am__dirstamp) @rm -f tests/lib/test_heavy_wq$(EXEEXT) $(AM_V_CCLD)$(tests_lib_test_heavy_wq_LINK) $(tests_lib_test_heavy_wq_OBJECTS) $(tests_lib_test_heavy_wq_LDADD) $(LIBS) tests/lib/test_idalloc-test_idalloc.$(OBJEXT): \ tests/lib/$(am__dirstamp) tests/lib/$(DEPDIR)/$(am__dirstamp) tests/lib/test_idalloc$(EXEEXT): $(tests_lib_test_idalloc_OBJECTS) $(tests_lib_test_idalloc_DEPENDENCIES) $(EXTRA_tests_lib_test_idalloc_DEPENDENCIES) tests/lib/$(am__dirstamp) @rm -f tests/lib/test_idalloc$(EXEEXT) $(AM_V_CCLD)$(tests_lib_test_idalloc_LINK) $(tests_lib_test_idalloc_OBJECTS) $(tests_lib_test_idalloc_LDADD) $(LIBS) tests/lib/test_memory-test_memory.$(OBJEXT): \ tests/lib/$(am__dirstamp) tests/lib/$(DEPDIR)/$(am__dirstamp) tests/lib/test_memory$(EXEEXT): $(tests_lib_test_memory_OBJECTS) $(tests_lib_test_memory_DEPENDENCIES) $(EXTRA_tests_lib_test_memory_DEPENDENCIES) tests/lib/$(am__dirstamp) @rm -f tests/lib/test_memory$(EXEEXT) $(AM_V_CCLD)$(tests_lib_test_memory_LINK) $(tests_lib_test_memory_OBJECTS) $(tests_lib_test_memory_LDADD) $(LIBS) tests/lib/test_nexthop_iter-test_nexthop_iter.$(OBJEXT): \ tests/lib/$(am__dirstamp) tests/lib/$(DEPDIR)/$(am__dirstamp) tests/helpers/c/lib_test_nexthop_iter-prng.$(OBJEXT): \ tests/helpers/c/$(am__dirstamp) \ tests/helpers/c/$(DEPDIR)/$(am__dirstamp) tests/lib/test_nexthop_iter$(EXEEXT): $(tests_lib_test_nexthop_iter_OBJECTS) $(tests_lib_test_nexthop_iter_DEPENDENCIES) $(EXTRA_tests_lib_test_nexthop_iter_DEPENDENCIES) tests/lib/$(am__dirstamp) @rm -f tests/lib/test_nexthop_iter$(EXEEXT) $(AM_V_CCLD)$(tests_lib_test_nexthop_iter_LINK) $(tests_lib_test_nexthop_iter_OBJECTS) $(tests_lib_test_nexthop_iter_LDADD) $(LIBS) tests/lib/test_ntop-test_ntop.$(OBJEXT): tests/lib/$(am__dirstamp) \ tests/lib/$(DEPDIR)/$(am__dirstamp) tests/helpers/c/lib_test_ntop-prng.$(OBJEXT): \ tests/helpers/c/$(am__dirstamp) \ tests/helpers/c/$(DEPDIR)/$(am__dirstamp) tests/lib/test_ntop$(EXEEXT): $(tests_lib_test_ntop_OBJECTS) $(tests_lib_test_ntop_DEPENDENCIES) $(EXTRA_tests_lib_test_ntop_DEPENDENCIES) tests/lib/$(am__dirstamp) @rm -f tests/lib/test_ntop$(EXEEXT) $(AM_V_CCLD)$(tests_lib_test_ntop_LINK) $(tests_lib_test_ntop_OBJECTS) $(tests_lib_test_ntop_LDADD) $(LIBS) tests/lib/test_prefix2str-test_prefix2str.$(OBJEXT): \ tests/lib/$(am__dirstamp) tests/lib/$(DEPDIR)/$(am__dirstamp) tests/helpers/c/lib_test_prefix2str-prng.$(OBJEXT): \ tests/helpers/c/$(am__dirstamp) \ tests/helpers/c/$(DEPDIR)/$(am__dirstamp) tests/lib/test_prefix2str$(EXEEXT): $(tests_lib_test_prefix2str_OBJECTS) $(tests_lib_test_prefix2str_DEPENDENCIES) $(EXTRA_tests_lib_test_prefix2str_DEPENDENCIES) tests/lib/$(am__dirstamp) @rm -f tests/lib/test_prefix2str$(EXEEXT) $(AM_V_CCLD)$(tests_lib_test_prefix2str_LINK) $(tests_lib_test_prefix2str_OBJECTS) $(tests_lib_test_prefix2str_LDADD) $(LIBS) tests/lib/test_printfrr-test_printfrr.$(OBJEXT): \ tests/lib/$(am__dirstamp) tests/lib/$(DEPDIR)/$(am__dirstamp) tests/lib/test_printfrr$(EXEEXT): $(tests_lib_test_printfrr_OBJECTS) $(tests_lib_test_printfrr_DEPENDENCIES) $(EXTRA_tests_lib_test_printfrr_DEPENDENCIES) tests/lib/$(am__dirstamp) @rm -f tests/lib/test_printfrr$(EXEEXT) $(AM_V_CCLD)$(tests_lib_test_printfrr_LINK) $(tests_lib_test_printfrr_OBJECTS) $(tests_lib_test_printfrr_LDADD) $(LIBS) tests/lib/test_privs-test_privs.$(OBJEXT): tests/lib/$(am__dirstamp) \ tests/lib/$(DEPDIR)/$(am__dirstamp) tests/lib/test_privs$(EXEEXT): $(tests_lib_test_privs_OBJECTS) $(tests_lib_test_privs_DEPENDENCIES) $(EXTRA_tests_lib_test_privs_DEPENDENCIES) tests/lib/$(am__dirstamp) @rm -f tests/lib/test_privs$(EXEEXT) $(AM_V_CCLD)$(tests_lib_test_privs_LINK) $(tests_lib_test_privs_OBJECTS) $(tests_lib_test_privs_LDADD) $(LIBS) tests/lib/test_ringbuf-test_ringbuf.$(OBJEXT): \ tests/lib/$(am__dirstamp) tests/lib/$(DEPDIR)/$(am__dirstamp) tests/lib/test_ringbuf$(EXEEXT): $(tests_lib_test_ringbuf_OBJECTS) $(tests_lib_test_ringbuf_DEPENDENCIES) $(EXTRA_tests_lib_test_ringbuf_DEPENDENCIES) tests/lib/$(am__dirstamp) @rm -f tests/lib/test_ringbuf$(EXEEXT) $(AM_V_CCLD)$(tests_lib_test_ringbuf_LINK) $(tests_lib_test_ringbuf_OBJECTS) $(tests_lib_test_ringbuf_LDADD) $(LIBS) tests/lib/test_segv-test_segv.$(OBJEXT): tests/lib/$(am__dirstamp) \ tests/lib/$(DEPDIR)/$(am__dirstamp) tests/lib/test_segv$(EXEEXT): $(tests_lib_test_segv_OBJECTS) $(tests_lib_test_segv_DEPENDENCIES) $(EXTRA_tests_lib_test_segv_DEPENDENCIES) tests/lib/$(am__dirstamp) @rm -f tests/lib/test_segv$(EXEEXT) $(AM_V_CCLD)$(tests_lib_test_segv_LINK) $(tests_lib_test_segv_OBJECTS) $(tests_lib_test_segv_LDADD) $(LIBS) tests/lib/test_seqlock-test_seqlock.$(OBJEXT): \ tests/lib/$(am__dirstamp) tests/lib/$(DEPDIR)/$(am__dirstamp) tests/lib/test_seqlock$(EXEEXT): $(tests_lib_test_seqlock_OBJECTS) $(tests_lib_test_seqlock_DEPENDENCIES) $(EXTRA_tests_lib_test_seqlock_DEPENDENCIES) tests/lib/$(am__dirstamp) @rm -f tests/lib/test_seqlock$(EXEEXT) $(AM_V_CCLD)$(tests_lib_test_seqlock_LINK) $(tests_lib_test_seqlock_OBJECTS) $(tests_lib_test_seqlock_LDADD) $(LIBS) tests/lib/test_sig-test_sig.$(OBJEXT): tests/lib/$(am__dirstamp) \ tests/lib/$(DEPDIR)/$(am__dirstamp) tests/lib/test_sig$(EXEEXT): $(tests_lib_test_sig_OBJECTS) $(tests_lib_test_sig_DEPENDENCIES) $(EXTRA_tests_lib_test_sig_DEPENDENCIES) tests/lib/$(am__dirstamp) @rm -f tests/lib/test_sig$(EXEEXT) $(AM_V_CCLD)$(tests_lib_test_sig_LINK) $(tests_lib_test_sig_OBJECTS) $(tests_lib_test_sig_LDADD) $(LIBS) tests/lib/test_srcdest_table-test_srcdest_table.$(OBJEXT): \ tests/lib/$(am__dirstamp) tests/lib/$(DEPDIR)/$(am__dirstamp) tests/helpers/c/lib_test_srcdest_table-prng.$(OBJEXT): \ tests/helpers/c/$(am__dirstamp) \ tests/helpers/c/$(DEPDIR)/$(am__dirstamp) tests/lib/test_srcdest_table$(EXEEXT): $(tests_lib_test_srcdest_table_OBJECTS) $(tests_lib_test_srcdest_table_DEPENDENCIES) $(EXTRA_tests_lib_test_srcdest_table_DEPENDENCIES) tests/lib/$(am__dirstamp) @rm -f tests/lib/test_srcdest_table$(EXEEXT) $(AM_V_CCLD)$(tests_lib_test_srcdest_table_LINK) $(tests_lib_test_srcdest_table_OBJECTS) $(tests_lib_test_srcdest_table_LDADD) $(LIBS) tests/lib/test_stream-test_stream.$(OBJEXT): \ tests/lib/$(am__dirstamp) tests/lib/$(DEPDIR)/$(am__dirstamp) tests/lib/test_stream$(EXEEXT): $(tests_lib_test_stream_OBJECTS) $(tests_lib_test_stream_DEPENDENCIES) $(EXTRA_tests_lib_test_stream_DEPENDENCIES) tests/lib/$(am__dirstamp) @rm -f tests/lib/test_stream$(EXEEXT) $(AM_V_CCLD)$(tests_lib_test_stream_LINK) $(tests_lib_test_stream_OBJECTS) $(tests_lib_test_stream_LDADD) $(LIBS) tests/lib/test_table-test_table.$(OBJEXT): tests/lib/$(am__dirstamp) \ tests/lib/$(DEPDIR)/$(am__dirstamp) tests/lib/test_table$(EXEEXT): $(tests_lib_test_table_OBJECTS) $(tests_lib_test_table_DEPENDENCIES) $(EXTRA_tests_lib_test_table_DEPENDENCIES) tests/lib/$(am__dirstamp) @rm -f tests/lib/test_table$(EXEEXT) $(AM_V_CCLD)$(tests_lib_test_table_LINK) $(tests_lib_test_table_OBJECTS) $(tests_lib_test_table_LDADD) $(LIBS) tests/lib/test_timer_correctness-test_timer_correctness.$(OBJEXT): \ tests/lib/$(am__dirstamp) tests/lib/$(DEPDIR)/$(am__dirstamp) tests/helpers/c/lib_test_timer_correctness-prng.$(OBJEXT): \ tests/helpers/c/$(am__dirstamp) \ tests/helpers/c/$(DEPDIR)/$(am__dirstamp) tests/lib/test_timer_correctness$(EXEEXT): $(tests_lib_test_timer_correctness_OBJECTS) $(tests_lib_test_timer_correctness_DEPENDENCIES) $(EXTRA_tests_lib_test_timer_correctness_DEPENDENCIES) tests/lib/$(am__dirstamp) @rm -f tests/lib/test_timer_correctness$(EXEEXT) $(AM_V_CCLD)$(tests_lib_test_timer_correctness_LINK) $(tests_lib_test_timer_correctness_OBJECTS) $(tests_lib_test_timer_correctness_LDADD) $(LIBS) tests/lib/test_timer_performance-test_timer_performance.$(OBJEXT): \ tests/lib/$(am__dirstamp) tests/lib/$(DEPDIR)/$(am__dirstamp) tests/helpers/c/lib_test_timer_performance-prng.$(OBJEXT): \ tests/helpers/c/$(am__dirstamp) \ tests/helpers/c/$(DEPDIR)/$(am__dirstamp) tests/lib/test_timer_performance$(EXEEXT): $(tests_lib_test_timer_performance_OBJECTS) $(tests_lib_test_timer_performance_DEPENDENCIES) $(EXTRA_tests_lib_test_timer_performance_DEPENDENCIES) tests/lib/$(am__dirstamp) @rm -f tests/lib/test_timer_performance$(EXEEXT) $(AM_V_CCLD)$(tests_lib_test_timer_performance_LINK) $(tests_lib_test_timer_performance_OBJECTS) $(tests_lib_test_timer_performance_LDADD) $(LIBS) tests/lib/test_ttable-test_ttable.$(OBJEXT): \ tests/lib/$(am__dirstamp) tests/lib/$(DEPDIR)/$(am__dirstamp) tests/lib/test_ttable$(EXEEXT): $(tests_lib_test_ttable_OBJECTS) $(tests_lib_test_ttable_DEPENDENCIES) $(EXTRA_tests_lib_test_ttable_DEPENDENCIES) tests/lib/$(am__dirstamp) @rm -f tests/lib/test_ttable$(EXEEXT) $(AM_V_CCLD)$(tests_lib_test_ttable_LINK) $(tests_lib_test_ttable_OBJECTS) $(tests_lib_test_ttable_LDADD) $(LIBS) tests/lib/test_typelist-test_typelist.$(OBJEXT): \ tests/lib/$(am__dirstamp) tests/lib/$(DEPDIR)/$(am__dirstamp) tests/helpers/c/lib_test_typelist-prng.$(OBJEXT): \ tests/helpers/c/$(am__dirstamp) \ tests/helpers/c/$(DEPDIR)/$(am__dirstamp) tests/lib/test_typelist$(EXEEXT): $(tests_lib_test_typelist_OBJECTS) $(tests_lib_test_typelist_DEPENDENCIES) $(EXTRA_tests_lib_test_typelist_DEPENDENCIES) tests/lib/$(am__dirstamp) @rm -f tests/lib/test_typelist$(EXEEXT) $(AM_V_CCLD)$(tests_lib_test_typelist_LINK) $(tests_lib_test_typelist_OBJECTS) $(tests_lib_test_typelist_LDADD) $(LIBS) tests/lib/test_zlog-test_zlog.$(OBJEXT): tests/lib/$(am__dirstamp) \ tests/lib/$(DEPDIR)/$(am__dirstamp) tests/lib/test_zlog$(EXEEXT): $(tests_lib_test_zlog_OBJECTS) $(tests_lib_test_zlog_DEPENDENCIES) $(EXTRA_tests_lib_test_zlog_DEPENDENCIES) tests/lib/$(am__dirstamp) @rm -f tests/lib/test_zlog$(EXEEXT) $(AM_V_CCLD)$(tests_lib_test_zlog_LINK) $(tests_lib_test_zlog_OBJECTS) $(tests_lib_test_zlog_LDADD) $(LIBS) tests/lib/test_zmq-test_zmq.$(OBJEXT): tests/lib/$(am__dirstamp) \ tests/lib/$(DEPDIR)/$(am__dirstamp) tests/lib/test_zmq$(EXEEXT): $(tests_lib_test_zmq_OBJECTS) $(tests_lib_test_zmq_DEPENDENCIES) $(EXTRA_tests_lib_test_zmq_DEPENDENCIES) tests/lib/$(am__dirstamp) @rm -f tests/lib/test_zmq$(EXEEXT) $(AM_V_CCLD)$(tests_lib_test_zmq_LINK) $(tests_lib_test_zmq_OBJECTS) $(tests_lib_test_zmq_LDADD) $(LIBS) tests/ospf6d/$(am__dirstamp): @$(MKDIR_P) tests/ospf6d @: > tests/ospf6d/$(am__dirstamp) tests/ospf6d/$(DEPDIR)/$(am__dirstamp): @$(MKDIR_P) tests/ospf6d/$(DEPDIR) @: > tests/ospf6d/$(DEPDIR)/$(am__dirstamp) tests/ospf6d/test_lsdb-test_lsdb.$(OBJEXT): \ tests/ospf6d/$(am__dirstamp) \ tests/ospf6d/$(DEPDIR)/$(am__dirstamp) tests/lib/cli/ospf6d_test_lsdb-common_cli.$(OBJEXT): \ tests/lib/cli/$(am__dirstamp) \ tests/lib/cli/$(DEPDIR)/$(am__dirstamp) tests/ospf6d/test_lsdb$(EXEEXT): $(tests_ospf6d_test_lsdb_OBJECTS) $(tests_ospf6d_test_lsdb_DEPENDENCIES) $(EXTRA_tests_ospf6d_test_lsdb_DEPENDENCIES) tests/ospf6d/$(am__dirstamp) @rm -f tests/ospf6d/test_lsdb$(EXEEXT) $(AM_V_CCLD)$(tests_ospf6d_test_lsdb_LINK) $(tests_ospf6d_test_lsdb_OBJECTS) $(tests_ospf6d_test_lsdb_LDADD) $(LIBS) tools/$(am__dirstamp): @$(MKDIR_P) tools @: > tools/$(am__dirstamp) tools/$(DEPDIR)/$(am__dirstamp): @$(MKDIR_P) tools/$(DEPDIR) @: > tools/$(DEPDIR)/$(am__dirstamp) tools/gen_northbound_callbacks.$(OBJEXT): tools/$(am__dirstamp) \ tools/$(DEPDIR)/$(am__dirstamp) tools/gen_northbound_callbacks$(EXEEXT): $(tools_gen_northbound_callbacks_OBJECTS) $(tools_gen_northbound_callbacks_DEPENDENCIES) $(EXTRA_tools_gen_northbound_callbacks_DEPENDENCIES) tools/$(am__dirstamp) @rm -f tools/gen_northbound_callbacks$(EXEEXT) $(AM_V_CCLD)$(LINK) $(tools_gen_northbound_callbacks_OBJECTS) $(tools_gen_northbound_callbacks_LDADD) $(LIBS) tools/gen_yang_deviations.$(OBJEXT): tools/$(am__dirstamp) \ tools/$(DEPDIR)/$(am__dirstamp) tools/gen_yang_deviations$(EXEEXT): $(tools_gen_yang_deviations_OBJECTS) $(tools_gen_yang_deviations_DEPENDENCIES) $(EXTRA_tools_gen_yang_deviations_DEPENDENCIES) tools/$(am__dirstamp) @rm -f tools/gen_yang_deviations$(EXEEXT) $(AM_V_CCLD)$(LINK) $(tools_gen_yang_deviations_OBJECTS) $(tools_gen_yang_deviations_LDADD) $(LIBS) tools/permutations.$(OBJEXT): tools/$(am__dirstamp) \ tools/$(DEPDIR)/$(am__dirstamp) tools/permutations$(EXEEXT): $(tools_permutations_OBJECTS) $(tools_permutations_DEPENDENCIES) $(EXTRA_tools_permutations_DEPENDENCIES) tools/$(am__dirstamp) @rm -f tools/permutations$(EXEEXT) $(AM_V_CCLD)$(LINK) $(tools_permutations_OBJECTS) $(tools_permutations_LDADD) $(LIBS) tools/start-stop-daemon.$(OBJEXT): tools/$(am__dirstamp) \ tools/$(DEPDIR)/$(am__dirstamp) tools/ssd$(EXEEXT): $(tools_ssd_OBJECTS) $(tools_ssd_DEPENDENCIES) $(EXTRA_tools_ssd_DEPENDENCIES) tools/$(am__dirstamp) @rm -f tools/ssd$(EXEEXT) $(AM_V_CCLD)$(LINK) $(tools_ssd_OBJECTS) $(tools_ssd_LDADD) $(LIBS) vrrpd/vrrp_main.$(OBJEXT): vrrpd/$(am__dirstamp) \ vrrpd/$(DEPDIR)/$(am__dirstamp) vrrpd/vrrpd$(EXEEXT): $(vrrpd_vrrpd_OBJECTS) $(vrrpd_vrrpd_DEPENDENCIES) $(EXTRA_vrrpd_vrrpd_DEPENDENCIES) vrrpd/$(am__dirstamp) @rm -f vrrpd/vrrpd$(EXEEXT) $(AM_V_CCLD)$(LINK) $(vrrpd_vrrpd_OBJECTS) $(vrrpd_vrrpd_LDADD) $(LIBS) vtysh/$(am__dirstamp): @$(MKDIR_P) vtysh @: > vtysh/$(am__dirstamp) vtysh/$(DEPDIR)/$(am__dirstamp): @$(MKDIR_P) vtysh/$(DEPDIR) @: > vtysh/$(DEPDIR)/$(am__dirstamp) vtysh/vtysh_main.$(OBJEXT): vtysh/$(am__dirstamp) \ vtysh/$(DEPDIR)/$(am__dirstamp) vtysh/vtysh.$(OBJEXT): vtysh/$(am__dirstamp) \ vtysh/$(DEPDIR)/$(am__dirstamp) vtysh/vtysh_user.$(OBJEXT): vtysh/$(am__dirstamp) \ vtysh/$(DEPDIR)/$(am__dirstamp) vtysh/vtysh_config.$(OBJEXT): vtysh/$(am__dirstamp) \ vtysh/$(DEPDIR)/$(am__dirstamp) vtysh/vtysh_cmd.$(OBJEXT): vtysh/$(am__dirstamp) \ vtysh/$(DEPDIR)/$(am__dirstamp) vtysh/vtysh$(EXEEXT): $(vtysh_vtysh_OBJECTS) $(vtysh_vtysh_DEPENDENCIES) $(EXTRA_vtysh_vtysh_DEPENDENCIES) vtysh/$(am__dirstamp) @rm -f vtysh/vtysh$(EXEEXT) $(AM_V_CCLD)$(LINK) $(vtysh_vtysh_OBJECTS) $(vtysh_vtysh_LDADD) $(LIBS) watchfrr/$(am__dirstamp): @$(MKDIR_P) watchfrr @: > watchfrr/$(am__dirstamp) watchfrr/$(DEPDIR)/$(am__dirstamp): @$(MKDIR_P) watchfrr/$(DEPDIR) @: > watchfrr/$(DEPDIR)/$(am__dirstamp) watchfrr/watchfrr.$(OBJEXT): watchfrr/$(am__dirstamp) \ watchfrr/$(DEPDIR)/$(am__dirstamp) watchfrr/watchfrr_errors.$(OBJEXT): watchfrr/$(am__dirstamp) \ watchfrr/$(DEPDIR)/$(am__dirstamp) watchfrr/watchfrr_vty.$(OBJEXT): watchfrr/$(am__dirstamp) \ watchfrr/$(DEPDIR)/$(am__dirstamp) watchfrr/watchfrr$(EXEEXT): $(watchfrr_watchfrr_OBJECTS) $(watchfrr_watchfrr_DEPENDENCIES) $(EXTRA_watchfrr_watchfrr_DEPENDENCIES) watchfrr/$(am__dirstamp) @rm -f watchfrr/watchfrr$(EXEEXT) $(AM_V_CCLD)$(LINK) $(watchfrr_watchfrr_OBJECTS) $(watchfrr_watchfrr_LDADD) $(LIBS) zebra/connected.$(OBJEXT): zebra/$(am__dirstamp) \ zebra/$(DEPDIR)/$(am__dirstamp) zebra/debug.$(OBJEXT): zebra/$(am__dirstamp) \ zebra/$(DEPDIR)/$(am__dirstamp) zebra/if_ioctl.$(OBJEXT): zebra/$(am__dirstamp) \ zebra/$(DEPDIR)/$(am__dirstamp) zebra/if_ioctl_solaris.$(OBJEXT): zebra/$(am__dirstamp) \ zebra/$(DEPDIR)/$(am__dirstamp) zebra/if_netlink.$(OBJEXT): zebra/$(am__dirstamp) \ zebra/$(DEPDIR)/$(am__dirstamp) zebra/if_sysctl.$(OBJEXT): zebra/$(am__dirstamp) \ zebra/$(DEPDIR)/$(am__dirstamp) zebra/interface.$(OBJEXT): zebra/$(am__dirstamp) \ zebra/$(DEPDIR)/$(am__dirstamp) zebra/ioctl.$(OBJEXT): zebra/$(am__dirstamp) \ zebra/$(DEPDIR)/$(am__dirstamp) zebra/ioctl_solaris.$(OBJEXT): zebra/$(am__dirstamp) \ zebra/$(DEPDIR)/$(am__dirstamp) zebra/ipforward_proc.$(OBJEXT): zebra/$(am__dirstamp) \ zebra/$(DEPDIR)/$(am__dirstamp) zebra/ipforward_solaris.$(OBJEXT): zebra/$(am__dirstamp) \ zebra/$(DEPDIR)/$(am__dirstamp) zebra/ipforward_sysctl.$(OBJEXT): zebra/$(am__dirstamp) \ zebra/$(DEPDIR)/$(am__dirstamp) zebra/kernel_netlink.$(OBJEXT): zebra/$(am__dirstamp) \ zebra/$(DEPDIR)/$(am__dirstamp) zebra/kernel_socket.$(OBJEXT): zebra/$(am__dirstamp) \ zebra/$(DEPDIR)/$(am__dirstamp) zebra/label_manager.$(OBJEXT): zebra/$(am__dirstamp) \ zebra/$(DEPDIR)/$(am__dirstamp) zebra/main.$(OBJEXT): zebra/$(am__dirstamp) \ zebra/$(DEPDIR)/$(am__dirstamp) zebra/redistribute.$(OBJEXT): zebra/$(am__dirstamp) \ zebra/$(DEPDIR)/$(am__dirstamp) zebra/router-id.$(OBJEXT): zebra/$(am__dirstamp) \ zebra/$(DEPDIR)/$(am__dirstamp) zebra/rt_netlink.$(OBJEXT): zebra/$(am__dirstamp) \ zebra/$(DEPDIR)/$(am__dirstamp) zebra/rt_socket.$(OBJEXT): zebra/$(am__dirstamp) \ zebra/$(DEPDIR)/$(am__dirstamp) zebra/rtadv.$(OBJEXT): zebra/$(am__dirstamp) \ zebra/$(DEPDIR)/$(am__dirstamp) zebra/rtread_getmsg.$(OBJEXT): zebra/$(am__dirstamp) \ zebra/$(DEPDIR)/$(am__dirstamp) zebra/rtread_netlink.$(OBJEXT): zebra/$(am__dirstamp) \ zebra/$(DEPDIR)/$(am__dirstamp) zebra/rtread_sysctl.$(OBJEXT): zebra/$(am__dirstamp) \ zebra/$(DEPDIR)/$(am__dirstamp) zebra/rule_netlink.$(OBJEXT): zebra/$(am__dirstamp) \ zebra/$(DEPDIR)/$(am__dirstamp) zebra/rule_socket.$(OBJEXT): zebra/$(am__dirstamp) \ zebra/$(DEPDIR)/$(am__dirstamp) zebra/zebra_mlag.$(OBJEXT): zebra/$(am__dirstamp) \ zebra/$(DEPDIR)/$(am__dirstamp) zebra/zebra_l2.$(OBJEXT): zebra/$(am__dirstamp) \ zebra/$(DEPDIR)/$(am__dirstamp) zebra/zebra_memory.$(OBJEXT): zebra/$(am__dirstamp) \ zebra/$(DEPDIR)/$(am__dirstamp) zebra/zebra_dplane.$(OBJEXT): zebra/$(am__dirstamp) \ zebra/$(DEPDIR)/$(am__dirstamp) zebra/zebra_mpls.$(OBJEXT): zebra/$(am__dirstamp) \ zebra/$(DEPDIR)/$(am__dirstamp) zebra/zebra_mpls_netlink.$(OBJEXT): zebra/$(am__dirstamp) \ zebra/$(DEPDIR)/$(am__dirstamp) zebra/zebra_mpls_openbsd.$(OBJEXT): zebra/$(am__dirstamp) \ zebra/$(DEPDIR)/$(am__dirstamp) zebra/zebra_mpls_null.$(OBJEXT): zebra/$(am__dirstamp) \ zebra/$(DEPDIR)/$(am__dirstamp) zebra/zebra_mpls_vty.$(OBJEXT): zebra/$(am__dirstamp) \ zebra/$(DEPDIR)/$(am__dirstamp) zebra/zebra_mroute.$(OBJEXT): zebra/$(am__dirstamp) \ zebra/$(DEPDIR)/$(am__dirstamp) zebra/zebra_nhg.$(OBJEXT): zebra/$(am__dirstamp) \ zebra/$(DEPDIR)/$(am__dirstamp) zebra/zebra_ns.$(OBJEXT): zebra/$(am__dirstamp) \ zebra/$(DEPDIR)/$(am__dirstamp) zebra/zebra_pbr.$(OBJEXT): zebra/$(am__dirstamp) \ zebra/$(DEPDIR)/$(am__dirstamp) zebra/zebra_ptm.$(OBJEXT): zebra/$(am__dirstamp) \ zebra/$(DEPDIR)/$(am__dirstamp) zebra/zebra_ptm_redistribute.$(OBJEXT): zebra/$(am__dirstamp) \ zebra/$(DEPDIR)/$(am__dirstamp) zebra/zebra_pw.$(OBJEXT): zebra/$(am__dirstamp) \ zebra/$(DEPDIR)/$(am__dirstamp) zebra/zebra_rib.$(OBJEXT): zebra/$(am__dirstamp) \ zebra/$(DEPDIR)/$(am__dirstamp) zebra/zebra_router.$(OBJEXT): zebra/$(am__dirstamp) \ zebra/$(DEPDIR)/$(am__dirstamp) zebra/zebra_rnh.$(OBJEXT): zebra/$(am__dirstamp) \ zebra/$(DEPDIR)/$(am__dirstamp) zebra/zebra_routemap.$(OBJEXT): zebra/$(am__dirstamp) \ zebra/$(DEPDIR)/$(am__dirstamp) zebra/zebra_vrf.$(OBJEXT): zebra/$(am__dirstamp) \ zebra/$(DEPDIR)/$(am__dirstamp) zebra/zebra_vty.$(OBJEXT): zebra/$(am__dirstamp) \ zebra/$(DEPDIR)/$(am__dirstamp) zebra/zebra_vxlan.$(OBJEXT): zebra/$(am__dirstamp) \ zebra/$(DEPDIR)/$(am__dirstamp) zebra/zserv.$(OBJEXT): zebra/$(am__dirstamp) \ zebra/$(DEPDIR)/$(am__dirstamp) zebra/zebra_netns_id.$(OBJEXT): zebra/$(am__dirstamp) \ zebra/$(DEPDIR)/$(am__dirstamp) zebra/zebra_netns_notify.$(OBJEXT): zebra/$(am__dirstamp) \ zebra/$(DEPDIR)/$(am__dirstamp) zebra/table_manager.$(OBJEXT): zebra/$(am__dirstamp) \ zebra/$(DEPDIR)/$(am__dirstamp) zebra/zapi_msg.$(OBJEXT): zebra/$(am__dirstamp) \ zebra/$(DEPDIR)/$(am__dirstamp) zebra/zebra_errors.$(OBJEXT): zebra/$(am__dirstamp) \ zebra/$(DEPDIR)/$(am__dirstamp) zebra/zebra$(EXEEXT): $(zebra_zebra_OBJECTS) $(zebra_zebra_DEPENDENCIES) $(EXTRA_zebra_zebra_DEPENDENCIES) zebra/$(am__dirstamp) @rm -f zebra/zebra$(EXEEXT) $(AM_V_CCLD)$(LINK) $(zebra_zebra_OBJECTS) $(zebra_zebra_LDADD) $(LIBS) install-rcSCRIPTS: $(rc_SCRIPTS) @$(NORMAL_INSTALL) @list='$(rc_SCRIPTS)'; test -n "$(rcdir)" || list=; \ if test -n "$$list"; then \ echo " $(MKDIR_P) '$(DESTDIR)$(rcdir)'"; \ $(MKDIR_P) "$(DESTDIR)$(rcdir)" || exit 1; \ fi; \ for p in $$list; do \ if test -f "$$p"; then d=; else d="$(srcdir)/"; fi; \ if test -f "$$d$$p"; then echo "$$d$$p"; echo "$$p"; else :; fi; \ done | \ sed -e 'p;s,.*/,,;n' \ -e 'h;s|.*|.|' \ -e 'p;x;s,.*/,,;$(transform)' | sed 'N;N;N;s,\n, ,g' | \ $(AWK) 'BEGIN { files["."] = ""; dirs["."] = 1; } \ { d=$$3; if (dirs[d] != 1) { print "d", d; dirs[d] = 1 } \ if ($$2 == $$4) { files[d] = files[d] " " $$1; \ if (++n[d] == $(am__install_max)) { \ print "f", d, files[d]; n[d] = 0; files[d] = "" } } \ else { print "f", d "/" $$4, $$1 } } \ END { for (d in files) print "f", d, files[d] }' | \ while read type dir files; do \ if test "$$dir" = .; then dir=; else dir=/$$dir; fi; \ test -z "$$files" || { \ echo " $(INSTALL_SCRIPT) $$files '$(DESTDIR)$(rcdir)$$dir'"; \ $(INSTALL_SCRIPT) $$files "$(DESTDIR)$(rcdir)$$dir" || exit $$?; \ } \ ; done uninstall-rcSCRIPTS: @$(NORMAL_UNINSTALL) @list='$(rc_SCRIPTS)'; test -n "$(rcdir)" || exit 0; \ files=`for p in $$list; do echo "$$p"; done | \ sed -e 's,.*/,,;$(transform)'`; \ dir='$(DESTDIR)$(rcdir)'; $(am__uninstall_files_from_dir) install-sbinSCRIPTS: $(sbin_SCRIPTS) @$(NORMAL_INSTALL) @list='$(sbin_SCRIPTS)'; test -n "$(sbindir)" || list=; \ if test -n "$$list"; then \ echo " $(MKDIR_P) '$(DESTDIR)$(sbindir)'"; \ $(MKDIR_P) "$(DESTDIR)$(sbindir)" || exit 1; \ fi; \ for p in $$list; do \ if test -f "$$p"; then d=; else d="$(srcdir)/"; fi; \ if test -f "$$d$$p"; then echo "$$d$$p"; echo "$$p"; else :; fi; \ done | \ sed -e 'p;s,.*/,,;n' \ -e 'h;s|.*|.|' \ -e 'p;x;s,.*/,,;$(transform)' | sed 'N;N;N;s,\n, ,g' | \ $(AWK) 'BEGIN { files["."] = ""; dirs["."] = 1; } \ { d=$$3; if (dirs[d] != 1) { print "d", d; dirs[d] = 1 } \ if ($$2 == $$4) { files[d] = files[d] " " $$1; \ if (++n[d] == $(am__install_max)) { \ print "f", d, files[d]; n[d] = 0; files[d] = "" } } \ else { print "f", d "/" $$4, $$1 } } \ END { for (d in files) print "f", d, files[d] }' | \ while read type dir files; do \ if test "$$dir" = .; then dir=; else dir=/$$dir; fi; \ test -z "$$files" || { \ echo " $(INSTALL_SCRIPT) $$files '$(DESTDIR)$(sbindir)$$dir'"; \ $(INSTALL_SCRIPT) $$files "$(DESTDIR)$(sbindir)$$dir" || exit $$?; \ } \ ; done uninstall-sbinSCRIPTS: @$(NORMAL_UNINSTALL) @list='$(sbin_SCRIPTS)'; test -n "$(sbindir)" || exit 0; \ files=`for p in $$list; do echo "$$p"; done | \ sed -e 's,.*/,,;$(transform)'`; \ dir='$(DESTDIR)$(sbindir)'; $(am__uninstall_files_from_dir) mostlyclean-compile: -rm -f *.$(OBJEXT) -rm -f babeld/*.$(OBJEXT) -rm -f bfdd/*.$(OBJEXT) -rm -f bgpd/*.$(OBJEXT) -rm -f bgpd/*.lo -rm -f bgpd/rfapi/*.$(OBJEXT) -rm -f bgpd/rfp-example/librfp/*.$(OBJEXT) -rm -f bgpd/rfp-example/rfptest/*.$(OBJEXT) -rm -f eigrpd/*.$(OBJEXT) -rm -f fpm/*.$(OBJEXT) -rm -f fpm/*.lo -rm -f grpc/*.$(OBJEXT) -rm -f grpc/*.lo -rm -f isisd/*.$(OBJEXT) -rm -f ldpd/*.$(OBJEXT) -rm -f lib/*.$(OBJEXT) -rm -f lib/*.lo -rm -f lib/printf/*.$(OBJEXT) -rm -f lib/printf/*.lo -rm -f nhrpd/*.$(OBJEXT) -rm -f ospf6d/*.$(OBJEXT) -rm -f ospf6d/*.lo -rm -f ospfclient/*.$(OBJEXT) -rm -f ospfclient/*.lo -rm -f ospfd/*.$(OBJEXT) -rm -f ospfd/*.lo -rm -f pbrd/*.$(OBJEXT) -rm -f pimd/*.$(OBJEXT) -rm -f qpb/*.$(OBJEXT) -rm -f qpb/*.lo -rm -f ripd/*.$(OBJEXT) -rm -f ripd/*.lo -rm -f ripngd/*.$(OBJEXT) -rm -f sharpd/*.$(OBJEXT) -rm -f staticd/*.$(OBJEXT) -rm -f tests/bgpd/*.$(OBJEXT) -rm -f tests/helpers/c/*.$(OBJEXT) -rm -f tests/isisd/*.$(OBJEXT) -rm -f tests/lib/*.$(OBJEXT) -rm -f tests/lib/cli/*.$(OBJEXT) -rm -f tests/lib/northbound/*.$(OBJEXT) -rm -f tests/ospf6d/*.$(OBJEXT) -rm -f tools/*.$(OBJEXT) -rm -f vrrpd/*.$(OBJEXT) -rm -f vtysh/*.$(OBJEXT) -rm -f watchfrr/*.$(OBJEXT) -rm -f yang/*.$(OBJEXT) -rm -f yang/*.lo -rm -f zebra/*.$(OBJEXT) -rm -f zebra/*.lo distclean-compile: -rm -f *.tab.c @AMDEP_TRUE@@am__include@ @am__quote@babeld/$(DEPDIR)/babel_errors.Po@am__quote@ # am--include-marker @AMDEP_TRUE@@am__include@ @am__quote@babeld/$(DEPDIR)/babel_filter.Po@am__quote@ # am--include-marker @AMDEP_TRUE@@am__include@ @am__quote@babeld/$(DEPDIR)/babel_interface.Po@am__quote@ # am--include-marker @AMDEP_TRUE@@am__include@ @am__quote@babeld/$(DEPDIR)/babel_main.Po@am__quote@ # am--include-marker @AMDEP_TRUE@@am__include@ @am__quote@babeld/$(DEPDIR)/babel_zebra.Po@am__quote@ # am--include-marker @AMDEP_TRUE@@am__include@ @am__quote@babeld/$(DEPDIR)/babeld.Po@am__quote@ # am--include-marker @AMDEP_TRUE@@am__include@ @am__quote@babeld/$(DEPDIR)/kernel.Po@am__quote@ # am--include-marker @AMDEP_TRUE@@am__include@ @am__quote@babeld/$(DEPDIR)/message.Po@am__quote@ # am--include-marker @AMDEP_TRUE@@am__include@ @am__quote@babeld/$(DEPDIR)/neighbour.Po@am__quote@ # am--include-marker @AMDEP_TRUE@@am__include@ @am__quote@babeld/$(DEPDIR)/net.Po@am__quote@ # am--include-marker @AMDEP_TRUE@@am__include@ @am__quote@babeld/$(DEPDIR)/resend.Po@am__quote@ # am--include-marker @AMDEP_TRUE@@am__include@ @am__quote@babeld/$(DEPDIR)/route.Po@am__quote@ # am--include-marker @AMDEP_TRUE@@am__include@ @am__quote@babeld/$(DEPDIR)/source.Po@am__quote@ # am--include-marker @AMDEP_TRUE@@am__include@ @am__quote@babeld/$(DEPDIR)/util.Po@am__quote@ # am--include-marker @AMDEP_TRUE@@am__include@ @am__quote@babeld/$(DEPDIR)/xroute.Po@am__quote@ # am--include-marker @AMDEP_TRUE@@am__include@ @am__quote@bfdd/$(DEPDIR)/bfd.Po@am__quote@ # am--include-marker @AMDEP_TRUE@@am__include@ @am__quote@bfdd/$(DEPDIR)/bfd_packet.Po@am__quote@ # am--include-marker @AMDEP_TRUE@@am__include@ @am__quote@bfdd/$(DEPDIR)/bfdd.Po@am__quote@ # am--include-marker @AMDEP_TRUE@@am__include@ @am__quote@bfdd/$(DEPDIR)/bfdd_cli.Po@am__quote@ # am--include-marker @AMDEP_TRUE@@am__include@ @am__quote@bfdd/$(DEPDIR)/bfdd_northbound.Po@am__quote@ # am--include-marker @AMDEP_TRUE@@am__include@ @am__quote@bfdd/$(DEPDIR)/bfdd_vty.Po@am__quote@ # am--include-marker @AMDEP_TRUE@@am__include@ @am__quote@bfdd/$(DEPDIR)/config.Po@am__quote@ # am--include-marker @AMDEP_TRUE@@am__include@ @am__quote@bfdd/$(DEPDIR)/control.Po@am__quote@ # am--include-marker @AMDEP_TRUE@@am__include@ @am__quote@bfdd/$(DEPDIR)/event.Po@am__quote@ # am--include-marker @AMDEP_TRUE@@am__include@ @am__quote@bfdd/$(DEPDIR)/log.Po@am__quote@ # am--include-marker @AMDEP_TRUE@@am__include@ @am__quote@bfdd/$(DEPDIR)/ptm_adapter.Po@am__quote@ # am--include-marker @AMDEP_TRUE@@am__include@ @am__quote@bgpd/$(DEPDIR)/bgp_addpath.Po@am__quote@ # am--include-marker @AMDEP_TRUE@@am__include@ @am__quote@bgpd/$(DEPDIR)/bgp_advertise.Po@am__quote@ # am--include-marker @AMDEP_TRUE@@am__include@ @am__quote@bgpd/$(DEPDIR)/bgp_aspath.Po@am__quote@ # am--include-marker @AMDEP_TRUE@@am__include@ @am__quote@bgpd/$(DEPDIR)/bgp_attr.Po@am__quote@ # am--include-marker @AMDEP_TRUE@@am__include@ @am__quote@bgpd/$(DEPDIR)/bgp_attr_evpn.Po@am__quote@ # am--include-marker @AMDEP_TRUE@@am__include@ @am__quote@bgpd/$(DEPDIR)/bgp_bfd.Po@am__quote@ # am--include-marker @AMDEP_TRUE@@am__include@ @am__quote@bgpd/$(DEPDIR)/bgp_bmp.Plo@am__quote@ # am--include-marker @AMDEP_TRUE@@am__include@ @am__quote@bgpd/$(DEPDIR)/bgp_btoa-bgp_btoa.Po@am__quote@ # am--include-marker @AMDEP_TRUE@@am__include@ @am__quote@bgpd/$(DEPDIR)/bgp_clist.Po@am__quote@ # am--include-marker @AMDEP_TRUE@@am__include@ @am__quote@bgpd/$(DEPDIR)/bgp_community.Po@am__quote@ # am--include-marker @AMDEP_TRUE@@am__include@ @am__quote@bgpd/$(DEPDIR)/bgp_damp.Po@am__quote@ # am--include-marker @AMDEP_TRUE@@am__include@ @am__quote@bgpd/$(DEPDIR)/bgp_debug.Po@am__quote@ # am--include-marker @AMDEP_TRUE@@am__include@ @am__quote@bgpd/$(DEPDIR)/bgp_dump.Po@am__quote@ # am--include-marker @AMDEP_TRUE@@am__include@ @am__quote@bgpd/$(DEPDIR)/bgp_ecommunity.Po@am__quote@ # am--include-marker @AMDEP_TRUE@@am__include@ @am__quote@bgpd/$(DEPDIR)/bgp_encap_tlv.Po@am__quote@ # am--include-marker @AMDEP_TRUE@@am__include@ @am__quote@bgpd/$(DEPDIR)/bgp_errors.Po@am__quote@ # am--include-marker @AMDEP_TRUE@@am__include@ @am__quote@bgpd/$(DEPDIR)/bgp_evpn.Po@am__quote@ # am--include-marker @AMDEP_TRUE@@am__include@ @am__quote@bgpd/$(DEPDIR)/bgp_evpn_vty.Po@am__quote@ # am--include-marker @AMDEP_TRUE@@am__include@ @am__quote@bgpd/$(DEPDIR)/bgp_filter.Po@am__quote@ # am--include-marker @AMDEP_TRUE@@am__include@ @am__quote@bgpd/$(DEPDIR)/bgp_flowspec.Po@am__quote@ # am--include-marker @AMDEP_TRUE@@am__include@ @am__quote@bgpd/$(DEPDIR)/bgp_flowspec_util.Po@am__quote@ # am--include-marker @AMDEP_TRUE@@am__include@ @am__quote@bgpd/$(DEPDIR)/bgp_flowspec_vty.Po@am__quote@ # am--include-marker @AMDEP_TRUE@@am__include@ @am__quote@bgpd/$(DEPDIR)/bgp_fsm.Po@am__quote@ # am--include-marker @AMDEP_TRUE@@am__include@ @am__quote@bgpd/$(DEPDIR)/bgp_io.Po@am__quote@ # am--include-marker @AMDEP_TRUE@@am__include@ @am__quote@bgpd/$(DEPDIR)/bgp_keepalives.Po@am__quote@ # am--include-marker @AMDEP_TRUE@@am__include@ @am__quote@bgpd/$(DEPDIR)/bgp_label.Po@am__quote@ # am--include-marker @AMDEP_TRUE@@am__include@ @am__quote@bgpd/$(DEPDIR)/bgp_labelpool.Po@am__quote@ # am--include-marker @AMDEP_TRUE@@am__include@ @am__quote@bgpd/$(DEPDIR)/bgp_lcommunity.Po@am__quote@ # am--include-marker @AMDEP_TRUE@@am__include@ @am__quote@bgpd/$(DEPDIR)/bgp_mac.Po@am__quote@ # am--include-marker @AMDEP_TRUE@@am__include@ @am__quote@bgpd/$(DEPDIR)/bgp_memory.Po@am__quote@ # am--include-marker @AMDEP_TRUE@@am__include@ @am__quote@bgpd/$(DEPDIR)/bgp_mpath.Po@am__quote@ # am--include-marker @AMDEP_TRUE@@am__include@ @am__quote@bgpd/$(DEPDIR)/bgp_mplsvpn.Po@am__quote@ # am--include-marker @AMDEP_TRUE@@am__include@ @am__quote@bgpd/$(DEPDIR)/bgp_network.Po@am__quote@ # am--include-marker @AMDEP_TRUE@@am__include@ @am__quote@bgpd/$(DEPDIR)/bgp_nexthop.Po@am__quote@ # am--include-marker @AMDEP_TRUE@@am__include@ @am__quote@bgpd/$(DEPDIR)/bgp_nht.Po@am__quote@ # am--include-marker @AMDEP_TRUE@@am__include@ @am__quote@bgpd/$(DEPDIR)/bgp_open.Po@am__quote@ # am--include-marker @AMDEP_TRUE@@am__include@ @am__quote@bgpd/$(DEPDIR)/bgp_packet.Po@am__quote@ # am--include-marker @AMDEP_TRUE@@am__include@ @am__quote@bgpd/$(DEPDIR)/bgp_pbr.Po@am__quote@ # am--include-marker @AMDEP_TRUE@@am__include@ @am__quote@bgpd/$(DEPDIR)/bgp_rd.Po@am__quote@ # am--include-marker @AMDEP_TRUE@@am__include@ @am__quote@bgpd/$(DEPDIR)/bgp_regex.Po@am__quote@ # am--include-marker @AMDEP_TRUE@@am__include@ @am__quote@bgpd/$(DEPDIR)/bgp_route.Po@am__quote@ # am--include-marker @AMDEP_TRUE@@am__include@ @am__quote@bgpd/$(DEPDIR)/bgp_routemap.Po@am__quote@ # am--include-marker @AMDEP_TRUE@@am__include@ @am__quote@bgpd/$(DEPDIR)/bgp_table.Po@am__quote@ # am--include-marker @AMDEP_TRUE@@am__include@ @am__quote@bgpd/$(DEPDIR)/bgp_updgrp.Po@am__quote@ # am--include-marker @AMDEP_TRUE@@am__include@ @am__quote@bgpd/$(DEPDIR)/bgp_updgrp_adv.Po@am__quote@ # am--include-marker @AMDEP_TRUE@@am__include@ @am__quote@bgpd/$(DEPDIR)/bgp_updgrp_packet.Po@am__quote@ # am--include-marker @AMDEP_TRUE@@am__include@ @am__quote@bgpd/$(DEPDIR)/bgp_vpn.Po@am__quote@ # am--include-marker @AMDEP_TRUE@@am__include@ @am__quote@bgpd/$(DEPDIR)/bgp_vty.Po@am__quote@ # am--include-marker @AMDEP_TRUE@@am__include@ @am__quote@bgpd/$(DEPDIR)/bgp_zebra.Po@am__quote@ # am--include-marker @AMDEP_TRUE@@am__include@ @am__quote@bgpd/$(DEPDIR)/bgpd-bgp_main.Po@am__quote@ # am--include-marker @AMDEP_TRUE@@am__include@ @am__quote@bgpd/$(DEPDIR)/bgpd.Po@am__quote@ # am--include-marker @AMDEP_TRUE@@am__include@ @am__quote@bgpd/$(DEPDIR)/bgpd_rpki_la-bgp_rpki.Plo@am__quote@ # am--include-marker @AMDEP_TRUE@@am__include@ @am__quote@bgpd/$(DEPDIR)/bgpd_snmp_la-bgp_snmp.Plo@am__quote@ # am--include-marker @AMDEP_TRUE@@am__include@ @am__quote@bgpd/rfapi/$(DEPDIR)/bgp_btoa-rfapi_descriptor_rfp_utils.Po@am__quote@ # am--include-marker @AMDEP_TRUE@@am__include@ @am__quote@bgpd/rfapi/$(DEPDIR)/bgp_rfapi_cfg.Po@am__quote@ # am--include-marker @AMDEP_TRUE@@am__include@ @am__quote@bgpd/rfapi/$(DEPDIR)/bgpd-rfapi_descriptor_rfp_utils.Po@am__quote@ # am--include-marker @AMDEP_TRUE@@am__include@ @am__quote@bgpd/rfapi/$(DEPDIR)/rfapi.Po@am__quote@ # am--include-marker @AMDEP_TRUE@@am__include@ @am__quote@bgpd/rfapi/$(DEPDIR)/rfapi_ap.Po@am__quote@ # am--include-marker @AMDEP_TRUE@@am__include@ @am__quote@bgpd/rfapi/$(DEPDIR)/rfapi_descriptor_rfp_utils.Po@am__quote@ # am--include-marker @AMDEP_TRUE@@am__include@ @am__quote@bgpd/rfapi/$(DEPDIR)/rfapi_encap_tlv.Po@am__quote@ # am--include-marker @AMDEP_TRUE@@am__include@ @am__quote@bgpd/rfapi/$(DEPDIR)/rfapi_import.Po@am__quote@ # am--include-marker @AMDEP_TRUE@@am__include@ @am__quote@bgpd/rfapi/$(DEPDIR)/rfapi_monitor.Po@am__quote@ # am--include-marker @AMDEP_TRUE@@am__include@ @am__quote@bgpd/rfapi/$(DEPDIR)/rfapi_nve_addr.Po@am__quote@ # am--include-marker @AMDEP_TRUE@@am__include@ @am__quote@bgpd/rfapi/$(DEPDIR)/rfapi_rib.Po@am__quote@ # am--include-marker @AMDEP_TRUE@@am__include@ @am__quote@bgpd/rfapi/$(DEPDIR)/rfapi_vty.Po@am__quote@ # am--include-marker @AMDEP_TRUE@@am__include@ @am__quote@bgpd/rfapi/$(DEPDIR)/vnc_debug.Po@am__quote@ # am--include-marker @AMDEP_TRUE@@am__include@ @am__quote@bgpd/rfapi/$(DEPDIR)/vnc_export_bgp.Po@am__quote@ # am--include-marker @AMDEP_TRUE@@am__include@ @am__quote@bgpd/rfapi/$(DEPDIR)/vnc_export_table.Po@am__quote@ # am--include-marker @AMDEP_TRUE@@am__include@ @am__quote@bgpd/rfapi/$(DEPDIR)/vnc_import_bgp.Po@am__quote@ # am--include-marker @AMDEP_TRUE@@am__include@ @am__quote@bgpd/rfapi/$(DEPDIR)/vnc_zebra.Po@am__quote@ # am--include-marker @AMDEP_TRUE@@am__include@ @am__quote@bgpd/rfp-example/librfp/$(DEPDIR)/rfp_example.Po@am__quote@ # am--include-marker @AMDEP_TRUE@@am__include@ @am__quote@bgpd/rfp-example/rfptest/$(DEPDIR)/rfptest-rfptest.Po@am__quote@ # am--include-marker @AMDEP_TRUE@@am__include@ @am__quote@eigrpd/$(DEPDIR)/eigrp_cli.Po@am__quote@ # am--include-marker @AMDEP_TRUE@@am__include@ @am__quote@eigrpd/$(DEPDIR)/eigrp_dump.Po@am__quote@ # am--include-marker @AMDEP_TRUE@@am__include@ @am__quote@eigrpd/$(DEPDIR)/eigrp_errors.Po@am__quote@ # am--include-marker @AMDEP_TRUE@@am__include@ @am__quote@eigrpd/$(DEPDIR)/eigrp_filter.Po@am__quote@ # am--include-marker @AMDEP_TRUE@@am__include@ @am__quote@eigrpd/$(DEPDIR)/eigrp_fsm.Po@am__quote@ # am--include-marker @AMDEP_TRUE@@am__include@ @am__quote@eigrpd/$(DEPDIR)/eigrp_hello.Po@am__quote@ # am--include-marker @AMDEP_TRUE@@am__include@ @am__quote@eigrpd/$(DEPDIR)/eigrp_interface.Po@am__quote@ # am--include-marker @AMDEP_TRUE@@am__include@ @am__quote@eigrpd/$(DEPDIR)/eigrp_main.Po@am__quote@ # am--include-marker @AMDEP_TRUE@@am__include@ @am__quote@eigrpd/$(DEPDIR)/eigrp_memory.Po@am__quote@ # am--include-marker @AMDEP_TRUE@@am__include@ @am__quote@eigrpd/$(DEPDIR)/eigrp_neighbor.Po@am__quote@ # am--include-marker @AMDEP_TRUE@@am__include@ @am__quote@eigrpd/$(DEPDIR)/eigrp_network.Po@am__quote@ # am--include-marker @AMDEP_TRUE@@am__include@ @am__quote@eigrpd/$(DEPDIR)/eigrp_northbound.Po@am__quote@ # am--include-marker @AMDEP_TRUE@@am__include@ @am__quote@eigrpd/$(DEPDIR)/eigrp_packet.Po@am__quote@ # am--include-marker @AMDEP_TRUE@@am__include@ @am__quote@eigrpd/$(DEPDIR)/eigrp_query.Po@am__quote@ # am--include-marker @AMDEP_TRUE@@am__include@ @am__quote@eigrpd/$(DEPDIR)/eigrp_reply.Po@am__quote@ # am--include-marker @AMDEP_TRUE@@am__include@ @am__quote@eigrpd/$(DEPDIR)/eigrp_siaquery.Po@am__quote@ # am--include-marker @AMDEP_TRUE@@am__include@ @am__quote@eigrpd/$(DEPDIR)/eigrp_siareply.Po@am__quote@ # am--include-marker @AMDEP_TRUE@@am__include@ @am__quote@eigrpd/$(DEPDIR)/eigrp_snmp.Po@am__quote@ # am--include-marker @AMDEP_TRUE@@am__include@ @am__quote@eigrpd/$(DEPDIR)/eigrp_topology.Po@am__quote@ # am--include-marker @AMDEP_TRUE@@am__include@ @am__quote@eigrpd/$(DEPDIR)/eigrp_update.Po@am__quote@ # am--include-marker @AMDEP_TRUE@@am__include@ @am__quote@eigrpd/$(DEPDIR)/eigrp_vrf.Po@am__quote@ # am--include-marker @AMDEP_TRUE@@am__include@ @am__quote@eigrpd/$(DEPDIR)/eigrp_vty.Po@am__quote@ # am--include-marker @AMDEP_TRUE@@am__include@ @am__quote@eigrpd/$(DEPDIR)/eigrp_zebra.Po@am__quote@ # am--include-marker @AMDEP_TRUE@@am__include@ @am__quote@eigrpd/$(DEPDIR)/eigrpd.Po@am__quote@ # am--include-marker @AMDEP_TRUE@@am__include@ @am__quote@fpm/$(DEPDIR)/libfrrfpm_pb_la-fpm.pb-c.Plo@am__quote@ # am--include-marker @AMDEP_TRUE@@am__include@ @am__quote@fpm/$(DEPDIR)/libfrrfpm_pb_la-fpm_pb.Plo@am__quote@ # am--include-marker @AMDEP_TRUE@@am__include@ @am__quote@grpc/$(DEPDIR)/libfrrgrpc_pb_la-frr-northbound.grpc.pb.Plo@am__quote@ # am--include-marker @AMDEP_TRUE@@am__include@ @am__quote@grpc/$(DEPDIR)/libfrrgrpc_pb_la-frr-northbound.pb.Plo@am__quote@ # am--include-marker @AMDEP_TRUE@@am__include@ @am__quote@isisd/$(DEPDIR)/fabricd-isis_bpf.Po@am__quote@ # am--include-marker @AMDEP_TRUE@@am__include@ @am__quote@isisd/$(DEPDIR)/fabricd-isis_dlpi.Po@am__quote@ # am--include-marker @AMDEP_TRUE@@am__include@ @am__quote@isisd/$(DEPDIR)/fabricd-isis_main.Po@am__quote@ # am--include-marker @AMDEP_TRUE@@am__include@ @am__quote@isisd/$(DEPDIR)/fabricd-isis_pfpacket.Po@am__quote@ # am--include-marker @AMDEP_TRUE@@am__include@ @am__quote@isisd/$(DEPDIR)/fabricd.Po@am__quote@ # am--include-marker @AMDEP_TRUE@@am__include@ @am__quote@isisd/$(DEPDIR)/isis_adjacency.Po@am__quote@ # am--include-marker @AMDEP_TRUE@@am__include@ @am__quote@isisd/$(DEPDIR)/isis_bfd.Po@am__quote@ # am--include-marker @AMDEP_TRUE@@am__include@ @am__quote@isisd/$(DEPDIR)/isis_bpf.Po@am__quote@ # am--include-marker @AMDEP_TRUE@@am__include@ @am__quote@isisd/$(DEPDIR)/isis_circuit.Po@am__quote@ # am--include-marker @AMDEP_TRUE@@am__include@ @am__quote@isisd/$(DEPDIR)/isis_cli.Po@am__quote@ # am--include-marker @AMDEP_TRUE@@am__include@ @am__quote@isisd/$(DEPDIR)/isis_csm.Po@am__quote@ # am--include-marker @AMDEP_TRUE@@am__include@ @am__quote@isisd/$(DEPDIR)/isis_dlpi.Po@am__quote@ # am--include-marker @AMDEP_TRUE@@am__include@ @am__quote@isisd/$(DEPDIR)/isis_dr.Po@am__quote@ # am--include-marker @AMDEP_TRUE@@am__include@ @am__quote@isisd/$(DEPDIR)/isis_dynhn.Po@am__quote@ # am--include-marker @AMDEP_TRUE@@am__include@ @am__quote@isisd/$(DEPDIR)/isis_errors.Po@am__quote@ # am--include-marker @AMDEP_TRUE@@am__include@ @am__quote@isisd/$(DEPDIR)/isis_events.Po@am__quote@ # am--include-marker @AMDEP_TRUE@@am__include@ @am__quote@isisd/$(DEPDIR)/isis_flags.Po@am__quote@ # am--include-marker @AMDEP_TRUE@@am__include@ @am__quote@isisd/$(DEPDIR)/isis_lsp.Po@am__quote@ # am--include-marker @AMDEP_TRUE@@am__include@ @am__quote@isisd/$(DEPDIR)/isis_main.Po@am__quote@ # am--include-marker @AMDEP_TRUE@@am__include@ @am__quote@isisd/$(DEPDIR)/isis_memory.Po@am__quote@ # am--include-marker @AMDEP_TRUE@@am__include@ @am__quote@isisd/$(DEPDIR)/isis_misc.Po@am__quote@ # am--include-marker @AMDEP_TRUE@@am__include@ @am__quote@isisd/$(DEPDIR)/isis_mt.Po@am__quote@ # am--include-marker @AMDEP_TRUE@@am__include@ @am__quote@isisd/$(DEPDIR)/isis_northbound.Po@am__quote@ # am--include-marker @AMDEP_TRUE@@am__include@ @am__quote@isisd/$(DEPDIR)/isis_pdu.Po@am__quote@ # am--include-marker @AMDEP_TRUE@@am__include@ @am__quote@isisd/$(DEPDIR)/isis_pdu_counter.Po@am__quote@ # am--include-marker @AMDEP_TRUE@@am__include@ @am__quote@isisd/$(DEPDIR)/isis_pfpacket.Po@am__quote@ # am--include-marker @AMDEP_TRUE@@am__include@ @am__quote@isisd/$(DEPDIR)/isis_redist.Po@am__quote@ # am--include-marker @AMDEP_TRUE@@am__include@ @am__quote@isisd/$(DEPDIR)/isis_route.Po@am__quote@ # am--include-marker @AMDEP_TRUE@@am__include@ @am__quote@isisd/$(DEPDIR)/isis_routemap.Po@am__quote@ # am--include-marker @AMDEP_TRUE@@am__include@ @am__quote@isisd/$(DEPDIR)/isis_spf.Po@am__quote@ # am--include-marker @AMDEP_TRUE@@am__include@ @am__quote@isisd/$(DEPDIR)/isis_te.Po@am__quote@ # am--include-marker @AMDEP_TRUE@@am__include@ @am__quote@isisd/$(DEPDIR)/isis_tlvs.Po@am__quote@ # am--include-marker @AMDEP_TRUE@@am__include@ @am__quote@isisd/$(DEPDIR)/isis_tx_queue.Po@am__quote@ # am--include-marker @AMDEP_TRUE@@am__include@ @am__quote@isisd/$(DEPDIR)/isis_zebra.Po@am__quote@ # am--include-marker @AMDEP_TRUE@@am__include@ @am__quote@isisd/$(DEPDIR)/isisd.Po@am__quote@ # am--include-marker @AMDEP_TRUE@@am__include@ @am__quote@isisd/$(DEPDIR)/iso_checksum.Po@am__quote@ # am--include-marker @AMDEP_TRUE@@am__include@ @am__quote@isisd/$(DEPDIR)/libfabric_a-fabricd.Po@am__quote@ # am--include-marker @AMDEP_TRUE@@am__include@ @am__quote@isisd/$(DEPDIR)/libfabric_a-isis_adjacency.Po@am__quote@ # am--include-marker @AMDEP_TRUE@@am__include@ @am__quote@isisd/$(DEPDIR)/libfabric_a-isis_bfd.Po@am__quote@ # am--include-marker @AMDEP_TRUE@@am__include@ @am__quote@isisd/$(DEPDIR)/libfabric_a-isis_circuit.Po@am__quote@ # am--include-marker @AMDEP_TRUE@@am__include@ @am__quote@isisd/$(DEPDIR)/libfabric_a-isis_csm.Po@am__quote@ # am--include-marker @AMDEP_TRUE@@am__include@ @am__quote@isisd/$(DEPDIR)/libfabric_a-isis_dr.Po@am__quote@ # am--include-marker @AMDEP_TRUE@@am__include@ @am__quote@isisd/$(DEPDIR)/libfabric_a-isis_dynhn.Po@am__quote@ # am--include-marker @AMDEP_TRUE@@am__include@ @am__quote@isisd/$(DEPDIR)/libfabric_a-isis_errors.Po@am__quote@ # am--include-marker @AMDEP_TRUE@@am__include@ @am__quote@isisd/$(DEPDIR)/libfabric_a-isis_events.Po@am__quote@ # am--include-marker @AMDEP_TRUE@@am__include@ @am__quote@isisd/$(DEPDIR)/libfabric_a-isis_flags.Po@am__quote@ # am--include-marker @AMDEP_TRUE@@am__include@ @am__quote@isisd/$(DEPDIR)/libfabric_a-isis_lsp.Po@am__quote@ # am--include-marker @AMDEP_TRUE@@am__include@ @am__quote@isisd/$(DEPDIR)/libfabric_a-isis_memory.Po@am__quote@ # am--include-marker @AMDEP_TRUE@@am__include@ @am__quote@isisd/$(DEPDIR)/libfabric_a-isis_misc.Po@am__quote@ # am--include-marker @AMDEP_TRUE@@am__include@ @am__quote@isisd/$(DEPDIR)/libfabric_a-isis_mt.Po@am__quote@ # am--include-marker @AMDEP_TRUE@@am__include@ @am__quote@isisd/$(DEPDIR)/libfabric_a-isis_pdu.Po@am__quote@ # am--include-marker @AMDEP_TRUE@@am__include@ @am__quote@isisd/$(DEPDIR)/libfabric_a-isis_pdu_counter.Po@am__quote@ # am--include-marker @AMDEP_TRUE@@am__include@ @am__quote@isisd/$(DEPDIR)/libfabric_a-isis_redist.Po@am__quote@ # am--include-marker @AMDEP_TRUE@@am__include@ @am__quote@isisd/$(DEPDIR)/libfabric_a-isis_route.Po@am__quote@ # am--include-marker @AMDEP_TRUE@@am__include@ @am__quote@isisd/$(DEPDIR)/libfabric_a-isis_routemap.Po@am__quote@ # am--include-marker @AMDEP_TRUE@@am__include@ @am__quote@isisd/$(DEPDIR)/libfabric_a-isis_spf.Po@am__quote@ # am--include-marker @AMDEP_TRUE@@am__include@ @am__quote@isisd/$(DEPDIR)/libfabric_a-isis_te.Po@am__quote@ # am--include-marker @AMDEP_TRUE@@am__include@ @am__quote@isisd/$(DEPDIR)/libfabric_a-isis_tlvs.Po@am__quote@ # am--include-marker @AMDEP_TRUE@@am__include@ @am__quote@isisd/$(DEPDIR)/libfabric_a-isis_tx_queue.Po@am__quote@ # am--include-marker @AMDEP_TRUE@@am__include@ @am__quote@isisd/$(DEPDIR)/libfabric_a-isis_vty_fabricd.Po@am__quote@ # am--include-marker @AMDEP_TRUE@@am__include@ @am__quote@isisd/$(DEPDIR)/libfabric_a-isis_zebra.Po@am__quote@ # am--include-marker @AMDEP_TRUE@@am__include@ @am__quote@isisd/$(DEPDIR)/libfabric_a-isisd.Po@am__quote@ # am--include-marker @AMDEP_TRUE@@am__include@ @am__quote@isisd/$(DEPDIR)/libfabric_a-iso_checksum.Po@am__quote@ # am--include-marker @AMDEP_TRUE@@am__include@ @am__quote@ldpd/$(DEPDIR)/accept.Po@am__quote@ # am--include-marker @AMDEP_TRUE@@am__include@ @am__quote@ldpd/$(DEPDIR)/address.Po@am__quote@ # am--include-marker @AMDEP_TRUE@@am__include@ @am__quote@ldpd/$(DEPDIR)/adjacency.Po@am__quote@ # am--include-marker @AMDEP_TRUE@@am__include@ @am__quote@ldpd/$(DEPDIR)/control.Po@am__quote@ # am--include-marker @AMDEP_TRUE@@am__include@ @am__quote@ldpd/$(DEPDIR)/hello.Po@am__quote@ # am--include-marker @AMDEP_TRUE@@am__include@ @am__quote@ldpd/$(DEPDIR)/init.Po@am__quote@ # am--include-marker @AMDEP_TRUE@@am__include@ @am__quote@ldpd/$(DEPDIR)/interface.Po@am__quote@ # am--include-marker @AMDEP_TRUE@@am__include@ @am__quote@ldpd/$(DEPDIR)/keepalive.Po@am__quote@ # am--include-marker @AMDEP_TRUE@@am__include@ @am__quote@ldpd/$(DEPDIR)/l2vpn.Po@am__quote@ # am--include-marker @AMDEP_TRUE@@am__include@ @am__quote@ldpd/$(DEPDIR)/labelmapping.Po@am__quote@ # am--include-marker @AMDEP_TRUE@@am__include@ @am__quote@ldpd/$(DEPDIR)/lde.Po@am__quote@ # am--include-marker @AMDEP_TRUE@@am__include@ @am__quote@ldpd/$(DEPDIR)/lde_lib.Po@am__quote@ # am--include-marker @AMDEP_TRUE@@am__include@ @am__quote@ldpd/$(DEPDIR)/ldp_debug.Po@am__quote@ # am--include-marker @AMDEP_TRUE@@am__include@ @am__quote@ldpd/$(DEPDIR)/ldp_vty_cmds.Po@am__quote@ # am--include-marker @AMDEP_TRUE@@am__include@ @am__quote@ldpd/$(DEPDIR)/ldp_vty_conf.Po@am__quote@ # am--include-marker @AMDEP_TRUE@@am__include@ @am__quote@ldpd/$(DEPDIR)/ldp_vty_exec.Po@am__quote@ # am--include-marker @AMDEP_TRUE@@am__include@ @am__quote@ldpd/$(DEPDIR)/ldp_zebra.Po@am__quote@ # am--include-marker @AMDEP_TRUE@@am__include@ @am__quote@ldpd/$(DEPDIR)/ldpd.Po@am__quote@ # am--include-marker @AMDEP_TRUE@@am__include@ @am__quote@ldpd/$(DEPDIR)/ldpe.Po@am__quote@ # am--include-marker @AMDEP_TRUE@@am__include@ @am__quote@ldpd/$(DEPDIR)/log.Po@am__quote@ # am--include-marker @AMDEP_TRUE@@am__include@ @am__quote@ldpd/$(DEPDIR)/logmsg.Po@am__quote@ # am--include-marker @AMDEP_TRUE@@am__include@ @am__quote@ldpd/$(DEPDIR)/neighbor.Po@am__quote@ # am--include-marker @AMDEP_TRUE@@am__include@ @am__quote@ldpd/$(DEPDIR)/notification.Po@am__quote@ # am--include-marker @AMDEP_TRUE@@am__include@ @am__quote@ldpd/$(DEPDIR)/packet.Po@am__quote@ # am--include-marker @AMDEP_TRUE@@am__include@ @am__quote@ldpd/$(DEPDIR)/pfkey.Po@am__quote@ # am--include-marker @AMDEP_TRUE@@am__include@ @am__quote@ldpd/$(DEPDIR)/socket.Po@am__quote@ # am--include-marker @AMDEP_TRUE@@am__include@ @am__quote@ldpd/$(DEPDIR)/util.Po@am__quote@ # am--include-marker @AMDEP_TRUE@@am__include@ @am__quote@lib/$(DEPDIR)/agg_table.Plo@am__quote@ # am--include-marker @AMDEP_TRUE@@am__include@ @am__quote@lib/$(DEPDIR)/atomlist.Plo@am__quote@ # am--include-marker @AMDEP_TRUE@@am__include@ @am__quote@lib/$(DEPDIR)/bfd.Plo@am__quote@ # am--include-marker @AMDEP_TRUE@@am__include@ @am__quote@lib/$(DEPDIR)/buffer.Plo@am__quote@ # am--include-marker @AMDEP_TRUE@@am__include@ @am__quote@lib/$(DEPDIR)/checksum.Plo@am__quote@ # am--include-marker @AMDEP_TRUE@@am__include@ @am__quote@lib/$(DEPDIR)/clippy-clippy.Po@am__quote@ # am--include-marker @AMDEP_TRUE@@am__include@ @am__quote@lib/$(DEPDIR)/clippy-command_graph.Po@am__quote@ # am--include-marker @AMDEP_TRUE@@am__include@ @am__quote@lib/$(DEPDIR)/clippy-command_lex.Po@am__quote@ # am--include-marker @AMDEP_TRUE@@am__include@ @am__quote@lib/$(DEPDIR)/clippy-command_parse.Po@am__quote@ # am--include-marker @AMDEP_TRUE@@am__include@ @am__quote@lib/$(DEPDIR)/clippy-command_py.Po@am__quote@ # am--include-marker @AMDEP_TRUE@@am__include@ @am__quote@lib/$(DEPDIR)/clippy-defun_lex.Po@am__quote@ # am--include-marker @AMDEP_TRUE@@am__include@ @am__quote@lib/$(DEPDIR)/clippy-graph.Po@am__quote@ # am--include-marker @AMDEP_TRUE@@am__include@ @am__quote@lib/$(DEPDIR)/clippy-memory.Po@am__quote@ # am--include-marker @AMDEP_TRUE@@am__include@ @am__quote@lib/$(DEPDIR)/clippy-vector.Po@am__quote@ # am--include-marker @AMDEP_TRUE@@am__include@ @am__quote@lib/$(DEPDIR)/command.Plo@am__quote@ # am--include-marker @AMDEP_TRUE@@am__include@ @am__quote@lib/$(DEPDIR)/command_graph.Plo@am__quote@ # am--include-marker @AMDEP_TRUE@@am__include@ @am__quote@lib/$(DEPDIR)/command_lex.Plo@am__quote@ # am--include-marker @AMDEP_TRUE@@am__include@ @am__quote@lib/$(DEPDIR)/command_match.Plo@am__quote@ # am--include-marker @AMDEP_TRUE@@am__include@ @am__quote@lib/$(DEPDIR)/command_parse.Plo@am__quote@ # am--include-marker @AMDEP_TRUE@@am__include@ @am__quote@lib/$(DEPDIR)/confd_la-northbound_confd.Plo@am__quote@ # am--include-marker @AMDEP_TRUE@@am__include@ @am__quote@lib/$(DEPDIR)/csv.Plo@am__quote@ # am--include-marker @AMDEP_TRUE@@am__include@ @am__quote@lib/$(DEPDIR)/db.Plo@am__quote@ # am--include-marker @AMDEP_TRUE@@am__include@ @am__quote@lib/$(DEPDIR)/debug.Plo@am__quote@ # am--include-marker @AMDEP_TRUE@@am__include@ @am__quote@lib/$(DEPDIR)/distribute.Plo@am__quote@ # am--include-marker @AMDEP_TRUE@@am__include@ @am__quote@lib/$(DEPDIR)/ferr.Plo@am__quote@ # am--include-marker @AMDEP_TRUE@@am__include@ @am__quote@lib/$(DEPDIR)/filter.Plo@am__quote@ # am--include-marker @AMDEP_TRUE@@am__include@ @am__quote@lib/$(DEPDIR)/frr_pthread.Plo@am__quote@ # am--include-marker @AMDEP_TRUE@@am__include@ @am__quote@lib/$(DEPDIR)/frrcu.Plo@am__quote@ # am--include-marker @AMDEP_TRUE@@am__include@ @am__quote@lib/$(DEPDIR)/frrlua.Plo@am__quote@ # am--include-marker @AMDEP_TRUE@@am__include@ @am__quote@lib/$(DEPDIR)/frrstr.Plo@am__quote@ # am--include-marker @AMDEP_TRUE@@am__include@ @am__quote@lib/$(DEPDIR)/getopt.Plo@am__quote@ # am--include-marker @AMDEP_TRUE@@am__include@ @am__quote@lib/$(DEPDIR)/getopt1.Plo@am__quote@ # am--include-marker @AMDEP_TRUE@@am__include@ @am__quote@lib/$(DEPDIR)/grammar_sandbox.Plo@am__quote@ # am--include-marker @AMDEP_TRUE@@am__include@ @am__quote@lib/$(DEPDIR)/grammar_sandbox_main.Po@am__quote@ # am--include-marker @AMDEP_TRUE@@am__include@ @am__quote@lib/$(DEPDIR)/graph.Plo@am__quote@ # am--include-marker @AMDEP_TRUE@@am__include@ @am__quote@lib/$(DEPDIR)/grpc_la-northbound_grpc.Plo@am__quote@ # am--include-marker @AMDEP_TRUE@@am__include@ @am__quote@lib/$(DEPDIR)/hash.Plo@am__quote@ # am--include-marker @AMDEP_TRUE@@am__include@ @am__quote@lib/$(DEPDIR)/hook.Plo@am__quote@ # am--include-marker @AMDEP_TRUE@@am__include@ @am__quote@lib/$(DEPDIR)/id_alloc.Plo@am__quote@ # am--include-marker @AMDEP_TRUE@@am__include@ @am__quote@lib/$(DEPDIR)/if.Plo@am__quote@ # am--include-marker @AMDEP_TRUE@@am__include@ @am__quote@lib/$(DEPDIR)/if_rmap.Plo@am__quote@ # am--include-marker @AMDEP_TRUE@@am__include@ @am__quote@lib/$(DEPDIR)/imsg-buffer.Plo@am__quote@ # am--include-marker @AMDEP_TRUE@@am__include@ @am__quote@lib/$(DEPDIR)/imsg.Plo@am__quote@ # am--include-marker @AMDEP_TRUE@@am__include@ @am__quote@lib/$(DEPDIR)/jhash.Plo@am__quote@ # am--include-marker @AMDEP_TRUE@@am__include@ @am__quote@lib/$(DEPDIR)/json.Plo@am__quote@ # am--include-marker @AMDEP_TRUE@@am__include@ @am__quote@lib/$(DEPDIR)/keychain.Plo@am__quote@ # am--include-marker @AMDEP_TRUE@@am__include@ @am__quote@lib/$(DEPDIR)/lib_errors.Plo@am__quote@ # am--include-marker @AMDEP_TRUE@@am__include@ @am__quote@lib/$(DEPDIR)/libfrr.Plo@am__quote@ # am--include-marker @AMDEP_TRUE@@am__include@ @am__quote@lib/$(DEPDIR)/libfrrcares_la-resolver.Plo@am__quote@ # am--include-marker @AMDEP_TRUE@@am__include@ @am__quote@lib/$(DEPDIR)/libfrrsnmp_la-agentx.Plo@am__quote@ # am--include-marker @AMDEP_TRUE@@am__include@ @am__quote@lib/$(DEPDIR)/libfrrsnmp_la-snmp.Plo@am__quote@ # am--include-marker @AMDEP_TRUE@@am__include@ @am__quote@lib/$(DEPDIR)/libfrrzmq_la-frr_zmq.Plo@am__quote@ # am--include-marker @AMDEP_TRUE@@am__include@ @am__quote@lib/$(DEPDIR)/linklist.Plo@am__quote@ # am--include-marker @AMDEP_TRUE@@am__include@ @am__quote@lib/$(DEPDIR)/log.Plo@am__quote@ # am--include-marker @AMDEP_TRUE@@am__include@ @am__quote@lib/$(DEPDIR)/log_vty.Plo@am__quote@ # am--include-marker @AMDEP_TRUE@@am__include@ @am__quote@lib/$(DEPDIR)/md5.Plo@am__quote@ # am--include-marker @AMDEP_TRUE@@am__include@ @am__quote@lib/$(DEPDIR)/memory.Plo@am__quote@ # am--include-marker @AMDEP_TRUE@@am__include@ @am__quote@lib/$(DEPDIR)/memory_vty.Plo@am__quote@ # am--include-marker @AMDEP_TRUE@@am__include@ @am__quote@lib/$(DEPDIR)/mlag.Plo@am__quote@ # am--include-marker @AMDEP_TRUE@@am__include@ @am__quote@lib/$(DEPDIR)/module.Plo@am__quote@ # am--include-marker @AMDEP_TRUE@@am__include@ @am__quote@lib/$(DEPDIR)/mpls.Plo@am__quote@ # am--include-marker @AMDEP_TRUE@@am__include@ @am__quote@lib/$(DEPDIR)/netns_linux.Plo@am__quote@ # am--include-marker @AMDEP_TRUE@@am__include@ @am__quote@lib/$(DEPDIR)/netns_other.Plo@am__quote@ # am--include-marker @AMDEP_TRUE@@am__include@ @am__quote@lib/$(DEPDIR)/network.Plo@am__quote@ # am--include-marker @AMDEP_TRUE@@am__include@ @am__quote@lib/$(DEPDIR)/nexthop.Plo@am__quote@ # am--include-marker @AMDEP_TRUE@@am__include@ @am__quote@lib/$(DEPDIR)/nexthop_group.Plo@am__quote@ # am--include-marker @AMDEP_TRUE@@am__include@ @am__quote@lib/$(DEPDIR)/northbound.Plo@am__quote@ # am--include-marker @AMDEP_TRUE@@am__include@ @am__quote@lib/$(DEPDIR)/northbound_cli.Plo@am__quote@ # am--include-marker @AMDEP_TRUE@@am__include@ @am__quote@lib/$(DEPDIR)/northbound_db.Plo@am__quote@ # am--include-marker @AMDEP_TRUE@@am__include@ @am__quote@lib/$(DEPDIR)/ntop.Plo@am__quote@ # am--include-marker @AMDEP_TRUE@@am__include@ @am__quote@lib/$(DEPDIR)/openbsd-tree.Plo@am__quote@ # am--include-marker @AMDEP_TRUE@@am__include@ @am__quote@lib/$(DEPDIR)/pid_output.Plo@am__quote@ # am--include-marker @AMDEP_TRUE@@am__include@ @am__quote@lib/$(DEPDIR)/plist.Plo@am__quote@ # am--include-marker @AMDEP_TRUE@@am__include@ @am__quote@lib/$(DEPDIR)/prefix.Plo@am__quote@ # am--include-marker @AMDEP_TRUE@@am__include@ @am__quote@lib/$(DEPDIR)/privs.Plo@am__quote@ # am--include-marker @AMDEP_TRUE@@am__include@ @am__quote@lib/$(DEPDIR)/ptm_lib.Plo@am__quote@ # am--include-marker @AMDEP_TRUE@@am__include@ @am__quote@lib/$(DEPDIR)/pullwr.Plo@am__quote@ # am--include-marker @AMDEP_TRUE@@am__include@ @am__quote@lib/$(DEPDIR)/qobj.Plo@am__quote@ # am--include-marker @AMDEP_TRUE@@am__include@ @am__quote@lib/$(DEPDIR)/ringbuf.Plo@am__quote@ # am--include-marker @AMDEP_TRUE@@am__include@ @am__quote@lib/$(DEPDIR)/routemap.Plo@am__quote@ # am--include-marker @AMDEP_TRUE@@am__include@ @am__quote@lib/$(DEPDIR)/sbuf.Plo@am__quote@ # am--include-marker @AMDEP_TRUE@@am__include@ @am__quote@lib/$(DEPDIR)/seqlock.Plo@am__quote@ # am--include-marker @AMDEP_TRUE@@am__include@ @am__quote@lib/$(DEPDIR)/sha256.Plo@am__quote@ # am--include-marker @AMDEP_TRUE@@am__include@ @am__quote@lib/$(DEPDIR)/sigevent.Plo@am__quote@ # am--include-marker @AMDEP_TRUE@@am__include@ @am__quote@lib/$(DEPDIR)/skiplist.Plo@am__quote@ # am--include-marker @AMDEP_TRUE@@am__include@ @am__quote@lib/$(DEPDIR)/sockopt.Plo@am__quote@ # am--include-marker @AMDEP_TRUE@@am__include@ @am__quote@lib/$(DEPDIR)/sockunion.Plo@am__quote@ # am--include-marker @AMDEP_TRUE@@am__include@ @am__quote@lib/$(DEPDIR)/spf_backoff.Plo@am__quote@ # am--include-marker @AMDEP_TRUE@@am__include@ @am__quote@lib/$(DEPDIR)/srcdest_table.Plo@am__quote@ # am--include-marker @AMDEP_TRUE@@am__include@ @am__quote@lib/$(DEPDIR)/stream.Plo@am__quote@ # am--include-marker @AMDEP_TRUE@@am__include@ @am__quote@lib/$(DEPDIR)/strlcat.Plo@am__quote@ # am--include-marker @AMDEP_TRUE@@am__include@ @am__quote@lib/$(DEPDIR)/strlcpy.Plo@am__quote@ # am--include-marker @AMDEP_TRUE@@am__include@ @am__quote@lib/$(DEPDIR)/sysrepo_la-northbound_sysrepo.Plo@am__quote@ # am--include-marker @AMDEP_TRUE@@am__include@ @am__quote@lib/$(DEPDIR)/systemd.Plo@am__quote@ # am--include-marker @AMDEP_TRUE@@am__include@ @am__quote@lib/$(DEPDIR)/table.Plo@am__quote@ # am--include-marker @AMDEP_TRUE@@am__include@ @am__quote@lib/$(DEPDIR)/termtable.Plo@am__quote@ # am--include-marker @AMDEP_TRUE@@am__include@ @am__quote@lib/$(DEPDIR)/thread.Plo@am__quote@ # am--include-marker @AMDEP_TRUE@@am__include@ @am__quote@lib/$(DEPDIR)/typerb.Plo@am__quote@ # am--include-marker @AMDEP_TRUE@@am__include@ @am__quote@lib/$(DEPDIR)/typesafe.Plo@am__quote@ # am--include-marker @AMDEP_TRUE@@am__include@ @am__quote@lib/$(DEPDIR)/vector.Plo@am__quote@ # am--include-marker @AMDEP_TRUE@@am__include@ @am__quote@lib/$(DEPDIR)/vrf.Plo@am__quote@ # am--include-marker @AMDEP_TRUE@@am__include@ @am__quote@lib/$(DEPDIR)/vty.Plo@am__quote@ # am--include-marker @AMDEP_TRUE@@am__include@ @am__quote@lib/$(DEPDIR)/wheel.Plo@am__quote@ # am--include-marker @AMDEP_TRUE@@am__include@ @am__quote@lib/$(DEPDIR)/workqueue.Plo@am__quote@ # am--include-marker @AMDEP_TRUE@@am__include@ @am__quote@lib/$(DEPDIR)/yang.Plo@am__quote@ # am--include-marker @AMDEP_TRUE@@am__include@ @am__quote@lib/$(DEPDIR)/yang_translator.Plo@am__quote@ # am--include-marker @AMDEP_TRUE@@am__include@ @am__quote@lib/$(DEPDIR)/yang_wrappers.Plo@am__quote@ # am--include-marker @AMDEP_TRUE@@am__include@ @am__quote@lib/$(DEPDIR)/zclient.Plo@am__quote@ # am--include-marker @AMDEP_TRUE@@am__include@ @am__quote@lib/printf/$(DEPDIR)/glue.Plo@am__quote@ # am--include-marker @AMDEP_TRUE@@am__include@ @am__quote@lib/printf/$(DEPDIR)/printf-pos.Plo@am__quote@ # am--include-marker @AMDEP_TRUE@@am__include@ @am__quote@lib/printf/$(DEPDIR)/vfprintf.Plo@am__quote@ # am--include-marker @AMDEP_TRUE@@am__include@ @am__quote@nhrpd/$(DEPDIR)/linux.Po@am__quote@ # am--include-marker @AMDEP_TRUE@@am__include@ @am__quote@nhrpd/$(DEPDIR)/netlink_arp.Po@am__quote@ # am--include-marker @AMDEP_TRUE@@am__include@ @am__quote@nhrpd/$(DEPDIR)/netlink_gre.Po@am__quote@ # am--include-marker @AMDEP_TRUE@@am__include@ @am__quote@nhrpd/$(DEPDIR)/nhrp_cache.Po@am__quote@ # am--include-marker @AMDEP_TRUE@@am__include@ @am__quote@nhrpd/$(DEPDIR)/nhrp_errors.Po@am__quote@ # am--include-marker @AMDEP_TRUE@@am__include@ @am__quote@nhrpd/$(DEPDIR)/nhrp_event.Po@am__quote@ # am--include-marker @AMDEP_TRUE@@am__include@ @am__quote@nhrpd/$(DEPDIR)/nhrp_interface.Po@am__quote@ # am--include-marker @AMDEP_TRUE@@am__include@ @am__quote@nhrpd/$(DEPDIR)/nhrp_main.Po@am__quote@ # am--include-marker @AMDEP_TRUE@@am__include@ @am__quote@nhrpd/$(DEPDIR)/nhrp_nhs.Po@am__quote@ # am--include-marker @AMDEP_TRUE@@am__include@ @am__quote@nhrpd/$(DEPDIR)/nhrp_packet.Po@am__quote@ # am--include-marker @AMDEP_TRUE@@am__include@ @am__quote@nhrpd/$(DEPDIR)/nhrp_peer.Po@am__quote@ # am--include-marker @AMDEP_TRUE@@am__include@ @am__quote@nhrpd/$(DEPDIR)/nhrp_route.Po@am__quote@ # am--include-marker @AMDEP_TRUE@@am__include@ @am__quote@nhrpd/$(DEPDIR)/nhrp_shortcut.Po@am__quote@ # am--include-marker @AMDEP_TRUE@@am__include@ @am__quote@nhrpd/$(DEPDIR)/nhrp_vc.Po@am__quote@ # am--include-marker @AMDEP_TRUE@@am__include@ @am__quote@nhrpd/$(DEPDIR)/nhrp_vty.Po@am__quote@ # am--include-marker @AMDEP_TRUE@@am__include@ @am__quote@nhrpd/$(DEPDIR)/reqid.Po@am__quote@ # am--include-marker @AMDEP_TRUE@@am__include@ @am__quote@nhrpd/$(DEPDIR)/vici.Po@am__quote@ # am--include-marker @AMDEP_TRUE@@am__include@ @am__quote@nhrpd/$(DEPDIR)/zbuf.Po@am__quote@ # am--include-marker @AMDEP_TRUE@@am__include@ @am__quote@nhrpd/$(DEPDIR)/znl.Po@am__quote@ # am--include-marker @AMDEP_TRUE@@am__include@ @am__quote@ospf6d/$(DEPDIR)/ospf6_abr.Po@am__quote@ # am--include-marker @AMDEP_TRUE@@am__include@ @am__quote@ospf6d/$(DEPDIR)/ospf6_area.Po@am__quote@ # am--include-marker @AMDEP_TRUE@@am__include@ @am__quote@ospf6d/$(DEPDIR)/ospf6_asbr.Po@am__quote@ # am--include-marker @AMDEP_TRUE@@am__include@ @am__quote@ospf6d/$(DEPDIR)/ospf6_bfd.Po@am__quote@ # am--include-marker @AMDEP_TRUE@@am__include@ @am__quote@ospf6d/$(DEPDIR)/ospf6_flood.Po@am__quote@ # am--include-marker @AMDEP_TRUE@@am__include@ @am__quote@ospf6d/$(DEPDIR)/ospf6_interface.Po@am__quote@ # am--include-marker @AMDEP_TRUE@@am__include@ @am__quote@ospf6d/$(DEPDIR)/ospf6_intra.Po@am__quote@ # am--include-marker @AMDEP_TRUE@@am__include@ @am__quote@ospf6d/$(DEPDIR)/ospf6_lsa.Po@am__quote@ # am--include-marker @AMDEP_TRUE@@am__include@ @am__quote@ospf6d/$(DEPDIR)/ospf6_lsdb.Po@am__quote@ # am--include-marker @AMDEP_TRUE@@am__include@ @am__quote@ospf6d/$(DEPDIR)/ospf6_main.Po@am__quote@ # am--include-marker @AMDEP_TRUE@@am__include@ @am__quote@ospf6d/$(DEPDIR)/ospf6_memory.Po@am__quote@ # am--include-marker @AMDEP_TRUE@@am__include@ @am__quote@ospf6d/$(DEPDIR)/ospf6_message.Po@am__quote@ # am--include-marker @AMDEP_TRUE@@am__include@ @am__quote@ospf6d/$(DEPDIR)/ospf6_neighbor.Po@am__quote@ # am--include-marker @AMDEP_TRUE@@am__include@ @am__quote@ospf6d/$(DEPDIR)/ospf6_network.Po@am__quote@ # am--include-marker @AMDEP_TRUE@@am__include@ @am__quote@ospf6d/$(DEPDIR)/ospf6_proto.Po@am__quote@ # am--include-marker @AMDEP_TRUE@@am__include@ @am__quote@ospf6d/$(DEPDIR)/ospf6_route.Po@am__quote@ # am--include-marker @AMDEP_TRUE@@am__include@ @am__quote@ospf6d/$(DEPDIR)/ospf6_spf.Po@am__quote@ # am--include-marker @AMDEP_TRUE@@am__include@ @am__quote@ospf6d/$(DEPDIR)/ospf6_top.Po@am__quote@ # am--include-marker @AMDEP_TRUE@@am__include@ @am__quote@ospf6d/$(DEPDIR)/ospf6_zebra.Po@am__quote@ # am--include-marker @AMDEP_TRUE@@am__include@ @am__quote@ospf6d/$(DEPDIR)/ospf6d.Po@am__quote@ # am--include-marker @AMDEP_TRUE@@am__include@ @am__quote@ospf6d/$(DEPDIR)/ospf6d_snmp_la-ospf6_snmp.Plo@am__quote@ # am--include-marker @AMDEP_TRUE@@am__include@ @am__quote@ospfclient/$(DEPDIR)/ospf_apiclient.Plo@am__quote@ # am--include-marker @AMDEP_TRUE@@am__include@ @am__quote@ospfclient/$(DEPDIR)/ospfclient.Po@am__quote@ # am--include-marker @AMDEP_TRUE@@am__include@ @am__quote@ospfd/$(DEPDIR)/ospf_abr.Po@am__quote@ # am--include-marker @AMDEP_TRUE@@am__include@ @am__quote@ospfd/$(DEPDIR)/ospf_api.Po@am__quote@ # am--include-marker @AMDEP_TRUE@@am__include@ @am__quote@ospfd/$(DEPDIR)/ospf_apiserver.Po@am__quote@ # am--include-marker @AMDEP_TRUE@@am__include@ @am__quote@ospfd/$(DEPDIR)/ospf_asbr.Po@am__quote@ # am--include-marker @AMDEP_TRUE@@am__include@ @am__quote@ospfd/$(DEPDIR)/ospf_ase.Po@am__quote@ # am--include-marker @AMDEP_TRUE@@am__include@ @am__quote@ospfd/$(DEPDIR)/ospf_bfd.Po@am__quote@ # am--include-marker @AMDEP_TRUE@@am__include@ @am__quote@ospfd/$(DEPDIR)/ospf_dump.Po@am__quote@ # am--include-marker @AMDEP_TRUE@@am__include@ @am__quote@ospfd/$(DEPDIR)/ospf_dump_api.Po@am__quote@ # am--include-marker @AMDEP_TRUE@@am__include@ @am__quote@ospfd/$(DEPDIR)/ospf_errors.Po@am__quote@ # am--include-marker @AMDEP_TRUE@@am__include@ @am__quote@ospfd/$(DEPDIR)/ospf_ext.Po@am__quote@ # am--include-marker @AMDEP_TRUE@@am__include@ @am__quote@ospfd/$(DEPDIR)/ospf_flood.Po@am__quote@ # am--include-marker @AMDEP_TRUE@@am__include@ @am__quote@ospfd/$(DEPDIR)/ospf_ia.Po@am__quote@ # am--include-marker @AMDEP_TRUE@@am__include@ @am__quote@ospfd/$(DEPDIR)/ospf_interface.Po@am__quote@ # am--include-marker @AMDEP_TRUE@@am__include@ @am__quote@ospfd/$(DEPDIR)/ospf_ism.Po@am__quote@ # am--include-marker @AMDEP_TRUE@@am__include@ @am__quote@ospfd/$(DEPDIR)/ospf_lsa.Po@am__quote@ # am--include-marker @AMDEP_TRUE@@am__include@ @am__quote@ospfd/$(DEPDIR)/ospf_lsdb.Po@am__quote@ # am--include-marker @AMDEP_TRUE@@am__include@ @am__quote@ospfd/$(DEPDIR)/ospf_main.Po@am__quote@ # am--include-marker @AMDEP_TRUE@@am__include@ @am__quote@ospfd/$(DEPDIR)/ospf_memory.Po@am__quote@ # am--include-marker @AMDEP_TRUE@@am__include@ @am__quote@ospfd/$(DEPDIR)/ospf_neighbor.Po@am__quote@ # am--include-marker @AMDEP_TRUE@@am__include@ @am__quote@ospfd/$(DEPDIR)/ospf_network.Po@am__quote@ # am--include-marker @AMDEP_TRUE@@am__include@ @am__quote@ospfd/$(DEPDIR)/ospf_nsm.Po@am__quote@ # am--include-marker @AMDEP_TRUE@@am__include@ @am__quote@ospfd/$(DEPDIR)/ospf_opaque.Po@am__quote@ # am--include-marker @AMDEP_TRUE@@am__include@ @am__quote@ospfd/$(DEPDIR)/ospf_packet.Po@am__quote@ # am--include-marker @AMDEP_TRUE@@am__include@ @am__quote@ospfd/$(DEPDIR)/ospf_ri.Po@am__quote@ # am--include-marker @AMDEP_TRUE@@am__include@ @am__quote@ospfd/$(DEPDIR)/ospf_route.Po@am__quote@ # am--include-marker @AMDEP_TRUE@@am__include@ @am__quote@ospfd/$(DEPDIR)/ospf_routemap.Po@am__quote@ # am--include-marker @AMDEP_TRUE@@am__include@ @am__quote@ospfd/$(DEPDIR)/ospf_spf.Po@am__quote@ # am--include-marker @AMDEP_TRUE@@am__include@ @am__quote@ospfd/$(DEPDIR)/ospf_sr.Po@am__quote@ # am--include-marker @AMDEP_TRUE@@am__include@ @am__quote@ospfd/$(DEPDIR)/ospf_te.Po@am__quote@ # am--include-marker @AMDEP_TRUE@@am__include@ @am__quote@ospfd/$(DEPDIR)/ospf_vty.Po@am__quote@ # am--include-marker @AMDEP_TRUE@@am__include@ @am__quote@ospfd/$(DEPDIR)/ospf_zebra.Po@am__quote@ # am--include-marker @AMDEP_TRUE@@am__include@ @am__quote@ospfd/$(DEPDIR)/ospfd.Po@am__quote@ # am--include-marker @AMDEP_TRUE@@am__include@ @am__quote@ospfd/$(DEPDIR)/ospfd_snmp_la-ospf_snmp.Plo@am__quote@ # am--include-marker @AMDEP_TRUE@@am__include@ @am__quote@pbrd/$(DEPDIR)/pbr_debug.Po@am__quote@ # am--include-marker @AMDEP_TRUE@@am__include@ @am__quote@pbrd/$(DEPDIR)/pbr_main.Po@am__quote@ # am--include-marker @AMDEP_TRUE@@am__include@ @am__quote@pbrd/$(DEPDIR)/pbr_map.Po@am__quote@ # am--include-marker @AMDEP_TRUE@@am__include@ @am__quote@pbrd/$(DEPDIR)/pbr_memory.Po@am__quote@ # am--include-marker @AMDEP_TRUE@@am__include@ @am__quote@pbrd/$(DEPDIR)/pbr_nht.Po@am__quote@ # am--include-marker @AMDEP_TRUE@@am__include@ @am__quote@pbrd/$(DEPDIR)/pbr_vty.Po@am__quote@ # am--include-marker @AMDEP_TRUE@@am__include@ @am__quote@pbrd/$(DEPDIR)/pbr_zebra.Po@am__quote@ # am--include-marker @AMDEP_TRUE@@am__include@ @am__quote@pimd/$(DEPDIR)/mtracebis.Po@am__quote@ # am--include-marker @AMDEP_TRUE@@am__include@ @am__quote@pimd/$(DEPDIR)/mtracebis_netlink.Po@am__quote@ # am--include-marker @AMDEP_TRUE@@am__include@ @am__quote@pimd/$(DEPDIR)/mtracebis_routeget.Po@am__quote@ # am--include-marker @AMDEP_TRUE@@am__include@ @am__quote@pimd/$(DEPDIR)/pim_assert.Po@am__quote@ # am--include-marker @AMDEP_TRUE@@am__include@ @am__quote@pimd/$(DEPDIR)/pim_bfd.Po@am__quote@ # am--include-marker @AMDEP_TRUE@@am__include@ @am__quote@pimd/$(DEPDIR)/pim_br.Po@am__quote@ # am--include-marker @AMDEP_TRUE@@am__include@ @am__quote@pimd/$(DEPDIR)/pim_bsm.Po@am__quote@ # am--include-marker @AMDEP_TRUE@@am__include@ @am__quote@pimd/$(DEPDIR)/pim_cmd.Po@am__quote@ # am--include-marker @AMDEP_TRUE@@am__include@ @am__quote@pimd/$(DEPDIR)/pim_errors.Po@am__quote@ # am--include-marker @AMDEP_TRUE@@am__include@ @am__quote@pimd/$(DEPDIR)/pim_hello.Po@am__quote@ # am--include-marker @AMDEP_TRUE@@am__include@ @am__quote@pimd/$(DEPDIR)/pim_iface.Po@am__quote@ # am--include-marker @AMDEP_TRUE@@am__include@ @am__quote@pimd/$(DEPDIR)/pim_ifchannel.Po@am__quote@ # am--include-marker @AMDEP_TRUE@@am__include@ @am__quote@pimd/$(DEPDIR)/pim_igmp.Po@am__quote@ # am--include-marker @AMDEP_TRUE@@am__include@ @am__quote@pimd/$(DEPDIR)/pim_igmp_mtrace.Po@am__quote@ # am--include-marker @AMDEP_TRUE@@am__include@ @am__quote@pimd/$(DEPDIR)/pim_igmp_stats.Po@am__quote@ # am--include-marker @AMDEP_TRUE@@am__include@ @am__quote@pimd/$(DEPDIR)/pim_igmpv2.Po@am__quote@ # am--include-marker @AMDEP_TRUE@@am__include@ @am__quote@pimd/$(DEPDIR)/pim_igmpv3.Po@am__quote@ # am--include-marker @AMDEP_TRUE@@am__include@ @am__quote@pimd/$(DEPDIR)/pim_instance.Po@am__quote@ # am--include-marker @AMDEP_TRUE@@am__include@ @am__quote@pimd/$(DEPDIR)/pim_int.Po@am__quote@ # am--include-marker @AMDEP_TRUE@@am__include@ @am__quote@pimd/$(DEPDIR)/pim_join.Po@am__quote@ # am--include-marker @AMDEP_TRUE@@am__include@ @am__quote@pimd/$(DEPDIR)/pim_jp_agg.Po@am__quote@ # am--include-marker @AMDEP_TRUE@@am__include@ @am__quote@pimd/$(DEPDIR)/pim_macro.Po@am__quote@ # am--include-marker @AMDEP_TRUE@@am__include@ @am__quote@pimd/$(DEPDIR)/pim_main.Po@am__quote@ # am--include-marker @AMDEP_TRUE@@am__include@ @am__quote@pimd/$(DEPDIR)/pim_memory.Po@am__quote@ # am--include-marker @AMDEP_TRUE@@am__include@ @am__quote@pimd/$(DEPDIR)/pim_mroute.Po@am__quote@ # am--include-marker @AMDEP_TRUE@@am__include@ @am__quote@pimd/$(DEPDIR)/pim_msdp.Po@am__quote@ # am--include-marker @AMDEP_TRUE@@am__include@ @am__quote@pimd/$(DEPDIR)/pim_msdp_packet.Po@am__quote@ # am--include-marker @AMDEP_TRUE@@am__include@ @am__quote@pimd/$(DEPDIR)/pim_msdp_socket.Po@am__quote@ # am--include-marker @AMDEP_TRUE@@am__include@ @am__quote@pimd/$(DEPDIR)/pim_msg.Po@am__quote@ # am--include-marker @AMDEP_TRUE@@am__include@ @am__quote@pimd/$(DEPDIR)/pim_neighbor.Po@am__quote@ # am--include-marker @AMDEP_TRUE@@am__include@ @am__quote@pimd/$(DEPDIR)/pim_nht.Po@am__quote@ # am--include-marker @AMDEP_TRUE@@am__include@ @am__quote@pimd/$(DEPDIR)/pim_oil.Po@am__quote@ # am--include-marker @AMDEP_TRUE@@am__include@ @am__quote@pimd/$(DEPDIR)/pim_pim.Po@am__quote@ # am--include-marker @AMDEP_TRUE@@am__include@ @am__quote@pimd/$(DEPDIR)/pim_register.Po@am__quote@ # am--include-marker @AMDEP_TRUE@@am__include@ @am__quote@pimd/$(DEPDIR)/pim_routemap.Po@am__quote@ # am--include-marker @AMDEP_TRUE@@am__include@ @am__quote@pimd/$(DEPDIR)/pim_rp.Po@am__quote@ # am--include-marker @AMDEP_TRUE@@am__include@ @am__quote@pimd/$(DEPDIR)/pim_rpf.Po@am__quote@ # am--include-marker @AMDEP_TRUE@@am__include@ @am__quote@pimd/$(DEPDIR)/pim_signals.Po@am__quote@ # am--include-marker @AMDEP_TRUE@@am__include@ @am__quote@pimd/$(DEPDIR)/pim_sock.Po@am__quote@ # am--include-marker @AMDEP_TRUE@@am__include@ @am__quote@pimd/$(DEPDIR)/pim_ssm.Po@am__quote@ # am--include-marker @AMDEP_TRUE@@am__include@ @am__quote@pimd/$(DEPDIR)/pim_ssmpingd.Po@am__quote@ # am--include-marker @AMDEP_TRUE@@am__include@ @am__quote@pimd/$(DEPDIR)/pim_static.Po@am__quote@ # am--include-marker @AMDEP_TRUE@@am__include@ @am__quote@pimd/$(DEPDIR)/pim_str.Po@am__quote@ # am--include-marker @AMDEP_TRUE@@am__include@ @am__quote@pimd/$(DEPDIR)/pim_time.Po@am__quote@ # am--include-marker @AMDEP_TRUE@@am__include@ @am__quote@pimd/$(DEPDIR)/pim_tlv.Po@am__quote@ # am--include-marker @AMDEP_TRUE@@am__include@ @am__quote@pimd/$(DEPDIR)/pim_upstream.Po@am__quote@ # am--include-marker @AMDEP_TRUE@@am__include@ @am__quote@pimd/$(DEPDIR)/pim_util.Po@am__quote@ # am--include-marker @AMDEP_TRUE@@am__include@ @am__quote@pimd/$(DEPDIR)/pim_version.Po@am__quote@ # am--include-marker @AMDEP_TRUE@@am__include@ @am__quote@pimd/$(DEPDIR)/pim_vty.Po@am__quote@ # am--include-marker @AMDEP_TRUE@@am__include@ @am__quote@pimd/$(DEPDIR)/pim_vxlan.Po@am__quote@ # am--include-marker @AMDEP_TRUE@@am__include@ @am__quote@pimd/$(DEPDIR)/pim_zebra.Po@am__quote@ # am--include-marker @AMDEP_TRUE@@am__include@ @am__quote@pimd/$(DEPDIR)/pim_zlookup.Po@am__quote@ # am--include-marker @AMDEP_TRUE@@am__include@ @am__quote@pimd/$(DEPDIR)/pimd.Po@am__quote@ # am--include-marker @AMDEP_TRUE@@am__include@ @am__quote@pimd/$(DEPDIR)/test_igmpv3_join.Po@am__quote@ # am--include-marker @AMDEP_TRUE@@am__include@ @am__quote@qpb/$(DEPDIR)/libfrr_pb_la-qpb.Plo@am__quote@ # am--include-marker @AMDEP_TRUE@@am__include@ @am__quote@qpb/$(DEPDIR)/libfrr_pb_la-qpb.pb-c.Plo@am__quote@ # am--include-marker @AMDEP_TRUE@@am__include@ @am__quote@qpb/$(DEPDIR)/libfrr_pb_la-qpb_allocator.Plo@am__quote@ # am--include-marker @AMDEP_TRUE@@am__include@ @am__quote@ripd/$(DEPDIR)/rip_cli.Po@am__quote@ # am--include-marker @AMDEP_TRUE@@am__include@ @am__quote@ripd/$(DEPDIR)/rip_debug.Po@am__quote@ # am--include-marker @AMDEP_TRUE@@am__include@ @am__quote@ripd/$(DEPDIR)/rip_errors.Po@am__quote@ # am--include-marker @AMDEP_TRUE@@am__include@ @am__quote@ripd/$(DEPDIR)/rip_interface.Po@am__quote@ # am--include-marker @AMDEP_TRUE@@am__include@ @am__quote@ripd/$(DEPDIR)/rip_main.Po@am__quote@ # am--include-marker @AMDEP_TRUE@@am__include@ @am__quote@ripd/$(DEPDIR)/rip_northbound.Po@am__quote@ # am--include-marker @AMDEP_TRUE@@am__include@ @am__quote@ripd/$(DEPDIR)/rip_offset.Po@am__quote@ # am--include-marker @AMDEP_TRUE@@am__include@ @am__quote@ripd/$(DEPDIR)/rip_peer.Po@am__quote@ # am--include-marker @AMDEP_TRUE@@am__include@ @am__quote@ripd/$(DEPDIR)/rip_routemap.Po@am__quote@ # am--include-marker @AMDEP_TRUE@@am__include@ @am__quote@ripd/$(DEPDIR)/rip_zebra.Po@am__quote@ # am--include-marker @AMDEP_TRUE@@am__include@ @am__quote@ripd/$(DEPDIR)/ripd.Po@am__quote@ # am--include-marker @AMDEP_TRUE@@am__include@ @am__quote@ripd/$(DEPDIR)/ripd_snmp_la-rip_snmp.Plo@am__quote@ # am--include-marker @AMDEP_TRUE@@am__include@ @am__quote@ripngd/$(DEPDIR)/ripng_cli.Po@am__quote@ # am--include-marker @AMDEP_TRUE@@am__include@ @am__quote@ripngd/$(DEPDIR)/ripng_debug.Po@am__quote@ # am--include-marker @AMDEP_TRUE@@am__include@ @am__quote@ripngd/$(DEPDIR)/ripng_interface.Po@am__quote@ # am--include-marker @AMDEP_TRUE@@am__include@ @am__quote@ripngd/$(DEPDIR)/ripng_main.Po@am__quote@ # am--include-marker @AMDEP_TRUE@@am__include@ @am__quote@ripngd/$(DEPDIR)/ripng_nexthop.Po@am__quote@ # am--include-marker @AMDEP_TRUE@@am__include@ @am__quote@ripngd/$(DEPDIR)/ripng_northbound.Po@am__quote@ # am--include-marker @AMDEP_TRUE@@am__include@ @am__quote@ripngd/$(DEPDIR)/ripng_offset.Po@am__quote@ # am--include-marker @AMDEP_TRUE@@am__include@ @am__quote@ripngd/$(DEPDIR)/ripng_peer.Po@am__quote@ # am--include-marker @AMDEP_TRUE@@am__include@ @am__quote@ripngd/$(DEPDIR)/ripng_route.Po@am__quote@ # am--include-marker @AMDEP_TRUE@@am__include@ @am__quote@ripngd/$(DEPDIR)/ripng_routemap.Po@am__quote@ # am--include-marker @AMDEP_TRUE@@am__include@ @am__quote@ripngd/$(DEPDIR)/ripng_zebra.Po@am__quote@ # am--include-marker @AMDEP_TRUE@@am__include@ @am__quote@ripngd/$(DEPDIR)/ripngd.Po@am__quote@ # am--include-marker @AMDEP_TRUE@@am__include@ @am__quote@sharpd/$(DEPDIR)/sharp_main.Po@am__quote@ # am--include-marker @AMDEP_TRUE@@am__include@ @am__quote@sharpd/$(DEPDIR)/sharp_nht.Po@am__quote@ # am--include-marker @AMDEP_TRUE@@am__include@ @am__quote@sharpd/$(DEPDIR)/sharp_vty.Po@am__quote@ # am--include-marker @AMDEP_TRUE@@am__include@ @am__quote@sharpd/$(DEPDIR)/sharp_zebra.Po@am__quote@ # am--include-marker @AMDEP_TRUE@@am__include@ @am__quote@staticd/$(DEPDIR)/static_main.Po@am__quote@ # am--include-marker @AMDEP_TRUE@@am__include@ @am__quote@staticd/$(DEPDIR)/static_memory.Po@am__quote@ # am--include-marker @AMDEP_TRUE@@am__include@ @am__quote@staticd/$(DEPDIR)/static_nht.Po@am__quote@ # am--include-marker @AMDEP_TRUE@@am__include@ @am__quote@staticd/$(DEPDIR)/static_routes.Po@am__quote@ # am--include-marker @AMDEP_TRUE@@am__include@ @am__quote@staticd/$(DEPDIR)/static_vrf.Po@am__quote@ # am--include-marker @AMDEP_TRUE@@am__include@ @am__quote@staticd/$(DEPDIR)/static_vty.Po@am__quote@ # am--include-marker @AMDEP_TRUE@@am__include@ @am__quote@staticd/$(DEPDIR)/static_zebra.Po@am__quote@ # am--include-marker @AMDEP_TRUE@@am__include@ @am__quote@tests/bgpd/$(DEPDIR)/test_aspath-test_aspath.Po@am__quote@ # am--include-marker @AMDEP_TRUE@@am__include@ @am__quote@tests/bgpd/$(DEPDIR)/test_bgp_table-test_bgp_table.Po@am__quote@ # am--include-marker @AMDEP_TRUE@@am__include@ @am__quote@tests/bgpd/$(DEPDIR)/test_capability-test_capability.Po@am__quote@ # am--include-marker @AMDEP_TRUE@@am__include@ @am__quote@tests/bgpd/$(DEPDIR)/test_ecommunity-test_ecommunity.Po@am__quote@ # am--include-marker @AMDEP_TRUE@@am__include@ @am__quote@tests/bgpd/$(DEPDIR)/test_mp_attr-test_mp_attr.Po@am__quote@ # am--include-marker @AMDEP_TRUE@@am__include@ @am__quote@tests/bgpd/$(DEPDIR)/test_mpath-test_mpath.Po@am__quote@ # am--include-marker @AMDEP_TRUE@@am__include@ @am__quote@tests/bgpd/$(DEPDIR)/test_packet-test_packet.Po@am__quote@ # am--include-marker @AMDEP_TRUE@@am__include@ @am__quote@tests/bgpd/$(DEPDIR)/test_peer_attr-test_peer_attr.Po@am__quote@ # am--include-marker @AMDEP_TRUE@@am__include@ @am__quote@tests/helpers/c/$(DEPDIR)/lib_cli_test_commands-prng.Po@am__quote@ # am--include-marker @AMDEP_TRUE@@am__include@ @am__quote@tests/helpers/c/$(DEPDIR)/lib_test_heavy-main.Po@am__quote@ # am--include-marker @AMDEP_TRUE@@am__include@ @am__quote@tests/helpers/c/$(DEPDIR)/lib_test_heavy_thread-main.Po@am__quote@ # am--include-marker @AMDEP_TRUE@@am__include@ @am__quote@tests/helpers/c/$(DEPDIR)/lib_test_heavy_wq-main.Po@am__quote@ # am--include-marker @AMDEP_TRUE@@am__include@ @am__quote@tests/helpers/c/$(DEPDIR)/lib_test_nexthop_iter-prng.Po@am__quote@ # am--include-marker @AMDEP_TRUE@@am__include@ @am__quote@tests/helpers/c/$(DEPDIR)/lib_test_ntop-prng.Po@am__quote@ # am--include-marker @AMDEP_TRUE@@am__include@ @am__quote@tests/helpers/c/$(DEPDIR)/lib_test_prefix2str-prng.Po@am__quote@ # am--include-marker @AMDEP_TRUE@@am__include@ @am__quote@tests/helpers/c/$(DEPDIR)/lib_test_srcdest_table-prng.Po@am__quote@ # am--include-marker @AMDEP_TRUE@@am__include@ @am__quote@tests/helpers/c/$(DEPDIR)/lib_test_timer_correctness-prng.Po@am__quote@ # am--include-marker @AMDEP_TRUE@@am__include@ @am__quote@tests/helpers/c/$(DEPDIR)/lib_test_timer_performance-prng.Po@am__quote@ # am--include-marker @AMDEP_TRUE@@am__include@ @am__quote@tests/helpers/c/$(DEPDIR)/lib_test_typelist-prng.Po@am__quote@ # am--include-marker @AMDEP_TRUE@@am__include@ @am__quote@tests/isisd/$(DEPDIR)/test_fuzz_isis_tlv-test_fuzz_isis_tlv.Po@am__quote@ # am--include-marker @AMDEP_TRUE@@am__include@ @am__quote@tests/isisd/$(DEPDIR)/test_isis_lspdb-test_isis_lspdb.Po@am__quote@ # am--include-marker @AMDEP_TRUE@@am__include@ @am__quote@tests/isisd/$(DEPDIR)/test_isis_vertex_queue-test_isis_vertex_queue.Po@am__quote@ # am--include-marker @AMDEP_TRUE@@am__include@ @am__quote@tests/lib/$(DEPDIR)/cxxcompat-cxxcompat.Po@am__quote@ # am--include-marker @AMDEP_TRUE@@am__include@ @am__quote@tests/lib/$(DEPDIR)/test_atomlist-test_atomlist.Po@am__quote@ # am--include-marker @AMDEP_TRUE@@am__include@ @am__quote@tests/lib/$(DEPDIR)/test_buffer-test_buffer.Po@am__quote@ # am--include-marker @AMDEP_TRUE@@am__include@ @am__quote@tests/lib/$(DEPDIR)/test_checksum-test_checksum.Po@am__quote@ # am--include-marker @AMDEP_TRUE@@am__include@ @am__quote@tests/lib/$(DEPDIR)/test_graph-test_graph.Po@am__quote@ # am--include-marker @AMDEP_TRUE@@am__include@ @am__quote@tests/lib/$(DEPDIR)/test_heavy-test_heavy.Po@am__quote@ # am--include-marker @AMDEP_TRUE@@am__include@ @am__quote@tests/lib/$(DEPDIR)/test_heavy_thread-test_heavy_thread.Po@am__quote@ # am--include-marker @AMDEP_TRUE@@am__include@ @am__quote@tests/lib/$(DEPDIR)/test_heavy_wq-test_heavy_wq.Po@am__quote@ # am--include-marker @AMDEP_TRUE@@am__include@ @am__quote@tests/lib/$(DEPDIR)/test_idalloc-test_idalloc.Po@am__quote@ # am--include-marker @AMDEP_TRUE@@am__include@ @am__quote@tests/lib/$(DEPDIR)/test_memory-test_memory.Po@am__quote@ # am--include-marker @AMDEP_TRUE@@am__include@ @am__quote@tests/lib/$(DEPDIR)/test_nexthop_iter-test_nexthop_iter.Po@am__quote@ # am--include-marker @AMDEP_TRUE@@am__include@ @am__quote@tests/lib/$(DEPDIR)/test_ntop-test_ntop.Po@am__quote@ # am--include-marker @AMDEP_TRUE@@am__include@ @am__quote@tests/lib/$(DEPDIR)/test_prefix2str-test_prefix2str.Po@am__quote@ # am--include-marker @AMDEP_TRUE@@am__include@ @am__quote@tests/lib/$(DEPDIR)/test_printfrr-test_printfrr.Po@am__quote@ # am--include-marker @AMDEP_TRUE@@am__include@ @am__quote@tests/lib/$(DEPDIR)/test_privs-test_privs.Po@am__quote@ # am--include-marker @AMDEP_TRUE@@am__include@ @am__quote@tests/lib/$(DEPDIR)/test_ringbuf-test_ringbuf.Po@am__quote@ # am--include-marker @AMDEP_TRUE@@am__include@ @am__quote@tests/lib/$(DEPDIR)/test_segv-test_segv.Po@am__quote@ # am--include-marker @AMDEP_TRUE@@am__include@ @am__quote@tests/lib/$(DEPDIR)/test_seqlock-test_seqlock.Po@am__quote@ # am--include-marker @AMDEP_TRUE@@am__include@ @am__quote@tests/lib/$(DEPDIR)/test_sig-test_sig.Po@am__quote@ # am--include-marker @AMDEP_TRUE@@am__include@ @am__quote@tests/lib/$(DEPDIR)/test_srcdest_table-test_srcdest_table.Po@am__quote@ # am--include-marker @AMDEP_TRUE@@am__include@ @am__quote@tests/lib/$(DEPDIR)/test_stream-test_stream.Po@am__quote@ # am--include-marker @AMDEP_TRUE@@am__include@ @am__quote@tests/lib/$(DEPDIR)/test_table-test_table.Po@am__quote@ # am--include-marker @AMDEP_TRUE@@am__include@ @am__quote@tests/lib/$(DEPDIR)/test_timer_correctness-test_timer_correctness.Po@am__quote@ # am--include-marker @AMDEP_TRUE@@am__include@ @am__quote@tests/lib/$(DEPDIR)/test_timer_performance-test_timer_performance.Po@am__quote@ # am--include-marker @AMDEP_TRUE@@am__include@ @am__quote@tests/lib/$(DEPDIR)/test_ttable-test_ttable.Po@am__quote@ # am--include-marker @AMDEP_TRUE@@am__include@ @am__quote@tests/lib/$(DEPDIR)/test_typelist-test_typelist.Po@am__quote@ # am--include-marker @AMDEP_TRUE@@am__include@ @am__quote@tests/lib/$(DEPDIR)/test_zlog-test_zlog.Po@am__quote@ # am--include-marker @AMDEP_TRUE@@am__include@ @am__quote@tests/lib/$(DEPDIR)/test_zmq-test_zmq.Po@am__quote@ # am--include-marker @AMDEP_TRUE@@am__include@ @am__quote@tests/lib/cli/$(DEPDIR)/ospf6d_test_lsdb-common_cli.Po@am__quote@ # am--include-marker @AMDEP_TRUE@@am__include@ @am__quote@tests/lib/cli/$(DEPDIR)/test_cli-common_cli.Po@am__quote@ # am--include-marker @AMDEP_TRUE@@am__include@ @am__quote@tests/lib/cli/$(DEPDIR)/test_cli-test_cli.Po@am__quote@ # am--include-marker @AMDEP_TRUE@@am__include@ @am__quote@tests/lib/cli/$(DEPDIR)/test_commands-test_commands.Po@am__quote@ # am--include-marker @AMDEP_TRUE@@am__include@ @am__quote@tests/lib/cli/$(DEPDIR)/test_commands-test_commands_defun.Po@am__quote@ # am--include-marker @AMDEP_TRUE@@am__include@ @am__quote@tests/lib/northbound/$(DEPDIR)/test_oper_data-test_oper_data.Po@am__quote@ # am--include-marker @AMDEP_TRUE@@am__include@ @am__quote@tests/ospf6d/$(DEPDIR)/test_lsdb-test_lsdb.Po@am__quote@ # am--include-marker @AMDEP_TRUE@@am__include@ @am__quote@tools/$(DEPDIR)/gen_northbound_callbacks.Po@am__quote@ # am--include-marker @AMDEP_TRUE@@am__include@ @am__quote@tools/$(DEPDIR)/gen_yang_deviations.Po@am__quote@ # am--include-marker @AMDEP_TRUE@@am__include@ @am__quote@tools/$(DEPDIR)/permutations.Po@am__quote@ # am--include-marker @AMDEP_TRUE@@am__include@ @am__quote@tools/$(DEPDIR)/start-stop-daemon.Po@am__quote@ # am--include-marker @AMDEP_TRUE@@am__include@ @am__quote@vrrpd/$(DEPDIR)/vrrp.Po@am__quote@ # am--include-marker @AMDEP_TRUE@@am__include@ @am__quote@vrrpd/$(DEPDIR)/vrrp_arp.Po@am__quote@ # am--include-marker @AMDEP_TRUE@@am__include@ @am__quote@vrrpd/$(DEPDIR)/vrrp_debug.Po@am__quote@ # am--include-marker @AMDEP_TRUE@@am__include@ @am__quote@vrrpd/$(DEPDIR)/vrrp_main.Po@am__quote@ # am--include-marker @AMDEP_TRUE@@am__include@ @am__quote@vrrpd/$(DEPDIR)/vrrp_ndisc.Po@am__quote@ # am--include-marker @AMDEP_TRUE@@am__include@ @am__quote@vrrpd/$(DEPDIR)/vrrp_packet.Po@am__quote@ # am--include-marker @AMDEP_TRUE@@am__include@ @am__quote@vrrpd/$(DEPDIR)/vrrp_vty.Po@am__quote@ # am--include-marker @AMDEP_TRUE@@am__include@ @am__quote@vrrpd/$(DEPDIR)/vrrp_zebra.Po@am__quote@ # am--include-marker @AMDEP_TRUE@@am__include@ @am__quote@vtysh/$(DEPDIR)/vtysh.Po@am__quote@ # am--include-marker @AMDEP_TRUE@@am__include@ @am__quote@vtysh/$(DEPDIR)/vtysh_cmd.Po@am__quote@ # am--include-marker @AMDEP_TRUE@@am__include@ @am__quote@vtysh/$(DEPDIR)/vtysh_config.Po@am__quote@ # am--include-marker @AMDEP_TRUE@@am__include@ @am__quote@vtysh/$(DEPDIR)/vtysh_main.Po@am__quote@ # am--include-marker @AMDEP_TRUE@@am__include@ @am__quote@vtysh/$(DEPDIR)/vtysh_user.Po@am__quote@ # am--include-marker @AMDEP_TRUE@@am__include@ @am__quote@watchfrr/$(DEPDIR)/watchfrr.Po@am__quote@ # am--include-marker @AMDEP_TRUE@@am__include@ @am__quote@watchfrr/$(DEPDIR)/watchfrr_errors.Po@am__quote@ # am--include-marker @AMDEP_TRUE@@am__include@ @am__quote@watchfrr/$(DEPDIR)/watchfrr_vty.Po@am__quote@ # am--include-marker @AMDEP_TRUE@@am__include@ @am__quote@yang/$(DEPDIR)/frr-bfdd.yang.Po@am__quote@ # am--include-marker @AMDEP_TRUE@@am__include@ @am__quote@yang/$(DEPDIR)/frr-eigrpd.yang.Po@am__quote@ # am--include-marker @AMDEP_TRUE@@am__include@ @am__quote@yang/$(DEPDIR)/frr-interface.yang.Plo@am__quote@ # am--include-marker @AMDEP_TRUE@@am__include@ @am__quote@yang/$(DEPDIR)/frr-isisd.yang.Po@am__quote@ # am--include-marker @AMDEP_TRUE@@am__include@ @am__quote@yang/$(DEPDIR)/frr-module-translator.yang.Plo@am__quote@ # am--include-marker @AMDEP_TRUE@@am__include@ @am__quote@yang/$(DEPDIR)/frr-ripd.yang.Po@am__quote@ # am--include-marker @AMDEP_TRUE@@am__include@ @am__quote@yang/$(DEPDIR)/frr-ripngd.yang.Po@am__quote@ # am--include-marker @AMDEP_TRUE@@am__include@ @am__quote@yang/$(DEPDIR)/frr-route-types.yang.Plo@am__quote@ # am--include-marker @AMDEP_TRUE@@am__include@ @am__quote@yang/$(DEPDIR)/tests_lib_northbound_test_oper_data-frr-test-module.yang.Po@am__quote@ # am--include-marker @AMDEP_TRUE@@am__include@ @am__quote@zebra/$(DEPDIR)/connected.Po@am__quote@ # am--include-marker @AMDEP_TRUE@@am__include@ @am__quote@zebra/$(DEPDIR)/debug.Po@am__quote@ # am--include-marker @AMDEP_TRUE@@am__include@ @am__quote@zebra/$(DEPDIR)/if_ioctl.Po@am__quote@ # am--include-marker @AMDEP_TRUE@@am__include@ @am__quote@zebra/$(DEPDIR)/if_ioctl_solaris.Po@am__quote@ # am--include-marker @AMDEP_TRUE@@am__include@ @am__quote@zebra/$(DEPDIR)/if_netlink.Po@am__quote@ # am--include-marker @AMDEP_TRUE@@am__include@ @am__quote@zebra/$(DEPDIR)/if_sysctl.Po@am__quote@ # am--include-marker @AMDEP_TRUE@@am__include@ @am__quote@zebra/$(DEPDIR)/interface.Po@am__quote@ # am--include-marker @AMDEP_TRUE@@am__include@ @am__quote@zebra/$(DEPDIR)/ioctl.Po@am__quote@ # am--include-marker @AMDEP_TRUE@@am__include@ @am__quote@zebra/$(DEPDIR)/ioctl_solaris.Po@am__quote@ # am--include-marker @AMDEP_TRUE@@am__include@ @am__quote@zebra/$(DEPDIR)/ipforward_proc.Po@am__quote@ # am--include-marker @AMDEP_TRUE@@am__include@ @am__quote@zebra/$(DEPDIR)/ipforward_solaris.Po@am__quote@ # am--include-marker @AMDEP_TRUE@@am__include@ @am__quote@zebra/$(DEPDIR)/ipforward_sysctl.Po@am__quote@ # am--include-marker @AMDEP_TRUE@@am__include@ @am__quote@zebra/$(DEPDIR)/irdp_interface.Plo@am__quote@ # am--include-marker @AMDEP_TRUE@@am__include@ @am__quote@zebra/$(DEPDIR)/irdp_main.Plo@am__quote@ # am--include-marker @AMDEP_TRUE@@am__include@ @am__quote@zebra/$(DEPDIR)/irdp_packet.Plo@am__quote@ # am--include-marker @AMDEP_TRUE@@am__include@ @am__quote@zebra/$(DEPDIR)/kernel_netlink.Po@am__quote@ # am--include-marker @AMDEP_TRUE@@am__include@ @am__quote@zebra/$(DEPDIR)/kernel_socket.Po@am__quote@ # am--include-marker @AMDEP_TRUE@@am__include@ @am__quote@zebra/$(DEPDIR)/label_manager.Po@am__quote@ # am--include-marker @AMDEP_TRUE@@am__include@ @am__quote@zebra/$(DEPDIR)/main.Po@am__quote@ # am--include-marker @AMDEP_TRUE@@am__include@ @am__quote@zebra/$(DEPDIR)/redistribute.Po@am__quote@ # am--include-marker @AMDEP_TRUE@@am__include@ @am__quote@zebra/$(DEPDIR)/router-id.Po@am__quote@ # am--include-marker @AMDEP_TRUE@@am__include@ @am__quote@zebra/$(DEPDIR)/rt_netlink.Po@am__quote@ # am--include-marker @AMDEP_TRUE@@am__include@ @am__quote@zebra/$(DEPDIR)/rt_socket.Po@am__quote@ # am--include-marker @AMDEP_TRUE@@am__include@ @am__quote@zebra/$(DEPDIR)/rtadv.Po@am__quote@ # am--include-marker @AMDEP_TRUE@@am__include@ @am__quote@zebra/$(DEPDIR)/rtread_getmsg.Po@am__quote@ # am--include-marker @AMDEP_TRUE@@am__include@ @am__quote@zebra/$(DEPDIR)/rtread_netlink.Po@am__quote@ # am--include-marker @AMDEP_TRUE@@am__include@ @am__quote@zebra/$(DEPDIR)/rtread_sysctl.Po@am__quote@ # am--include-marker @AMDEP_TRUE@@am__include@ @am__quote@zebra/$(DEPDIR)/rule_netlink.Po@am__quote@ # am--include-marker @AMDEP_TRUE@@am__include@ @am__quote@zebra/$(DEPDIR)/rule_socket.Po@am__quote@ # am--include-marker @AMDEP_TRUE@@am__include@ @am__quote@zebra/$(DEPDIR)/table_manager.Po@am__quote@ # am--include-marker @AMDEP_TRUE@@am__include@ @am__quote@zebra/$(DEPDIR)/zapi_msg.Po@am__quote@ # am--include-marker @AMDEP_TRUE@@am__include@ @am__quote@zebra/$(DEPDIR)/zebra_dplane.Po@am__quote@ # am--include-marker @AMDEP_TRUE@@am__include@ @am__quote@zebra/$(DEPDIR)/zebra_errors.Po@am__quote@ # am--include-marker @AMDEP_TRUE@@am__include@ @am__quote@zebra/$(DEPDIR)/zebra_fpm.Plo@am__quote@ # am--include-marker @AMDEP_TRUE@@am__include@ @am__quote@zebra/$(DEPDIR)/zebra_fpm_dt.Plo@am__quote@ # am--include-marker @AMDEP_TRUE@@am__include@ @am__quote@zebra/$(DEPDIR)/zebra_fpm_netlink.Plo@am__quote@ # am--include-marker @AMDEP_TRUE@@am__include@ @am__quote@zebra/$(DEPDIR)/zebra_fpm_protobuf.Plo@am__quote@ # am--include-marker @AMDEP_TRUE@@am__include@ @am__quote@zebra/$(DEPDIR)/zebra_l2.Po@am__quote@ # am--include-marker @AMDEP_TRUE@@am__include@ @am__quote@zebra/$(DEPDIR)/zebra_memory.Po@am__quote@ # am--include-marker @AMDEP_TRUE@@am__include@ @am__quote@zebra/$(DEPDIR)/zebra_mlag.Po@am__quote@ # am--include-marker @AMDEP_TRUE@@am__include@ @am__quote@zebra/$(DEPDIR)/zebra_mpls.Po@am__quote@ # am--include-marker @AMDEP_TRUE@@am__include@ @am__quote@zebra/$(DEPDIR)/zebra_mpls_netlink.Po@am__quote@ # am--include-marker @AMDEP_TRUE@@am__include@ @am__quote@zebra/$(DEPDIR)/zebra_mpls_null.Po@am__quote@ # am--include-marker @AMDEP_TRUE@@am__include@ @am__quote@zebra/$(DEPDIR)/zebra_mpls_openbsd.Po@am__quote@ # am--include-marker @AMDEP_TRUE@@am__include@ @am__quote@zebra/$(DEPDIR)/zebra_mpls_vty.Po@am__quote@ # am--include-marker @AMDEP_TRUE@@am__include@ @am__quote@zebra/$(DEPDIR)/zebra_mroute.Po@am__quote@ # am--include-marker @AMDEP_TRUE@@am__include@ @am__quote@zebra/$(DEPDIR)/zebra_netns_id.Po@am__quote@ # am--include-marker @AMDEP_TRUE@@am__include@ @am__quote@zebra/$(DEPDIR)/zebra_netns_notify.Po@am__quote@ # am--include-marker @AMDEP_TRUE@@am__include@ @am__quote@zebra/$(DEPDIR)/zebra_nhg.Po@am__quote@ # am--include-marker @AMDEP_TRUE@@am__include@ @am__quote@zebra/$(DEPDIR)/zebra_ns.Po@am__quote@ # am--include-marker @AMDEP_TRUE@@am__include@ @am__quote@zebra/$(DEPDIR)/zebra_pbr.Po@am__quote@ # am--include-marker @AMDEP_TRUE@@am__include@ @am__quote@zebra/$(DEPDIR)/zebra_ptm.Po@am__quote@ # am--include-marker @AMDEP_TRUE@@am__include@ @am__quote@zebra/$(DEPDIR)/zebra_ptm_redistribute.Po@am__quote@ # am--include-marker @AMDEP_TRUE@@am__include@ @am__quote@zebra/$(DEPDIR)/zebra_pw.Po@am__quote@ # am--include-marker @AMDEP_TRUE@@am__include@ @am__quote@zebra/$(DEPDIR)/zebra_rib.Po@am__quote@ # am--include-marker @AMDEP_TRUE@@am__include@ @am__quote@zebra/$(DEPDIR)/zebra_rnh.Po@am__quote@ # am--include-marker @AMDEP_TRUE@@am__include@ @am__quote@zebra/$(DEPDIR)/zebra_routemap.Po@am__quote@ # am--include-marker @AMDEP_TRUE@@am__include@ @am__quote@zebra/$(DEPDIR)/zebra_router.Po@am__quote@ # am--include-marker @AMDEP_TRUE@@am__include@ @am__quote@zebra/$(DEPDIR)/zebra_snmp_la-zebra_snmp.Plo@am__quote@ # am--include-marker @AMDEP_TRUE@@am__include@ @am__quote@zebra/$(DEPDIR)/zebra_vrf.Po@am__quote@ # am--include-marker @AMDEP_TRUE@@am__include@ @am__quote@zebra/$(DEPDIR)/zebra_vty.Po@am__quote@ # am--include-marker @AMDEP_TRUE@@am__include@ @am__quote@zebra/$(DEPDIR)/zebra_vxlan.Po@am__quote@ # am--include-marker @AMDEP_TRUE@@am__include@ @am__quote@zebra/$(DEPDIR)/zserv.Po@am__quote@ # am--include-marker $(am__depfiles_remade): @$(MKDIR_P) $(@D) @echo '# dummy' >$@-t && $(am__mv) $@-t $@ am--depfiles: $(am__depfiles_remade) .c.o: @am__fastdepCC_TRUE@ $(AM_V_CC)depbase=`echo $@ | sed 's|[^/]*$$|$(DEPDIR)/&|;s|\.o$$||'`;\ @am__fastdepCC_TRUE@ $(COMPILE) -MT $@ -MD -MP -MF $$depbase.Tpo -c -o $@ $< &&\ @am__fastdepCC_TRUE@ $(am__mv) $$depbase.Tpo $$depbase.Po @AMDEP_TRUE@@am__fastdepCC_FALSE@ $(AM_V_CC)source='$<' object='$@' libtool=no @AMDEPBACKSLASH@ @AMDEP_TRUE@@am__fastdepCC_FALSE@ DEPDIR=$(DEPDIR) $(CCDEPMODE) $(depcomp) @AMDEPBACKSLASH@ @am__fastdepCC_FALSE@ $(AM_V_CC@am__nodep@)$(COMPILE) -c -o $@ $< .c.obj: @am__fastdepCC_TRUE@ $(AM_V_CC)depbase=`echo $@ | sed 's|[^/]*$$|$(DEPDIR)/&|;s|\.obj$$||'`;\ @am__fastdepCC_TRUE@ $(COMPILE) -MT $@ -MD -MP -MF $$depbase.Tpo -c -o $@ `$(CYGPATH_W) '$<'` &&\ @am__fastdepCC_TRUE@ $(am__mv) $$depbase.Tpo $$depbase.Po @AMDEP_TRUE@@am__fastdepCC_FALSE@ $(AM_V_CC)source='$<' object='$@' libtool=no @AMDEPBACKSLASH@ @AMDEP_TRUE@@am__fastdepCC_FALSE@ DEPDIR=$(DEPDIR) $(CCDEPMODE) $(depcomp) @AMDEPBACKSLASH@ @am__fastdepCC_FALSE@ $(AM_V_CC@am__nodep@)$(COMPILE) -c -o $@ `$(CYGPATH_W) '$<'` .c.lo: @am__fastdepCC_TRUE@ $(AM_V_CC)depbase=`echo $@ | sed 's|[^/]*$$|$(DEPDIR)/&|;s|\.lo$$||'`;\ @am__fastdepCC_TRUE@ $(LTCOMPILE) -MT $@ -MD -MP -MF $$depbase.Tpo -c -o $@ $< &&\ @am__fastdepCC_TRUE@ $(am__mv) $$depbase.Tpo $$depbase.Plo @AMDEP_TRUE@@am__fastdepCC_FALSE@ $(AM_V_CC)source='$<' object='$@' libtool=yes @AMDEPBACKSLASH@ @AMDEP_TRUE@@am__fastdepCC_FALSE@ DEPDIR=$(DEPDIR) $(CCDEPMODE) $(depcomp) @AMDEPBACKSLASH@ @am__fastdepCC_FALSE@ $(AM_V_CC@am__nodep@)$(LTCOMPILE) -c -o $@ $< isisd/libfabric_a-isis_adjacency.o: isisd/isis_adjacency.c @am__fastdepCC_TRUE@ $(AM_V_CC)$(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(isisd_libfabric_a_CPPFLAGS) $(CPPFLAGS) $(AM_CFLAGS) $(CFLAGS) -MT isisd/libfabric_a-isis_adjacency.o -MD -MP -MF isisd/$(DEPDIR)/libfabric_a-isis_adjacency.Tpo -c -o isisd/libfabric_a-isis_adjacency.o `test -f 'isisd/isis_adjacency.c' || echo '$(srcdir)/'`isisd/isis_adjacency.c @am__fastdepCC_TRUE@ $(AM_V_at)$(am__mv) isisd/$(DEPDIR)/libfabric_a-isis_adjacency.Tpo isisd/$(DEPDIR)/libfabric_a-isis_adjacency.Po @AMDEP_TRUE@@am__fastdepCC_FALSE@ $(AM_V_CC)source='isisd/isis_adjacency.c' object='isisd/libfabric_a-isis_adjacency.o' libtool=no @AMDEPBACKSLASH@ @AMDEP_TRUE@@am__fastdepCC_FALSE@ DEPDIR=$(DEPDIR) $(CCDEPMODE) $(depcomp) @AMDEPBACKSLASH@ @am__fastdepCC_FALSE@ $(AM_V_CC@am__nodep@)$(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(isisd_libfabric_a_CPPFLAGS) $(CPPFLAGS) $(AM_CFLAGS) $(CFLAGS) -c -o isisd/libfabric_a-isis_adjacency.o `test -f 'isisd/isis_adjacency.c' || echo '$(srcdir)/'`isisd/isis_adjacency.c isisd/libfabric_a-isis_adjacency.obj: isisd/isis_adjacency.c @am__fastdepCC_TRUE@ $(AM_V_CC)$(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(isisd_libfabric_a_CPPFLAGS) $(CPPFLAGS) $(AM_CFLAGS) $(CFLAGS) -MT isisd/libfabric_a-isis_adjacency.obj -MD -MP -MF isisd/$(DEPDIR)/libfabric_a-isis_adjacency.Tpo -c -o isisd/libfabric_a-isis_adjacency.obj `if test -f 'isisd/isis_adjacency.c'; then $(CYGPATH_W) 'isisd/isis_adjacency.c'; else $(CYGPATH_W) '$(srcdir)/isisd/isis_adjacency.c'; fi` @am__fastdepCC_TRUE@ $(AM_V_at)$(am__mv) isisd/$(DEPDIR)/libfabric_a-isis_adjacency.Tpo isisd/$(DEPDIR)/libfabric_a-isis_adjacency.Po @AMDEP_TRUE@@am__fastdepCC_FALSE@ $(AM_V_CC)source='isisd/isis_adjacency.c' object='isisd/libfabric_a-isis_adjacency.obj' libtool=no @AMDEPBACKSLASH@ @AMDEP_TRUE@@am__fastdepCC_FALSE@ DEPDIR=$(DEPDIR) $(CCDEPMODE) $(depcomp) @AMDEPBACKSLASH@ @am__fastdepCC_FALSE@ $(AM_V_CC@am__nodep@)$(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(isisd_libfabric_a_CPPFLAGS) $(CPPFLAGS) $(AM_CFLAGS) $(CFLAGS) -c -o isisd/libfabric_a-isis_adjacency.obj `if test -f 'isisd/isis_adjacency.c'; then $(CYGPATH_W) 'isisd/isis_adjacency.c'; else $(CYGPATH_W) '$(srcdir)/isisd/isis_adjacency.c'; fi` isisd/libfabric_a-isis_bfd.o: isisd/isis_bfd.c @am__fastdepCC_TRUE@ $(AM_V_CC)$(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(isisd_libfabric_a_CPPFLAGS) $(CPPFLAGS) $(AM_CFLAGS) $(CFLAGS) -MT isisd/libfabric_a-isis_bfd.o -MD -MP -MF isisd/$(DEPDIR)/libfabric_a-isis_bfd.Tpo -c -o isisd/libfabric_a-isis_bfd.o `test -f 'isisd/isis_bfd.c' || echo '$(srcdir)/'`isisd/isis_bfd.c @am__fastdepCC_TRUE@ $(AM_V_at)$(am__mv) isisd/$(DEPDIR)/libfabric_a-isis_bfd.Tpo isisd/$(DEPDIR)/libfabric_a-isis_bfd.Po @AMDEP_TRUE@@am__fastdepCC_FALSE@ $(AM_V_CC)source='isisd/isis_bfd.c' object='isisd/libfabric_a-isis_bfd.o' libtool=no @AMDEPBACKSLASH@ @AMDEP_TRUE@@am__fastdepCC_FALSE@ DEPDIR=$(DEPDIR) $(CCDEPMODE) $(depcomp) @AMDEPBACKSLASH@ @am__fastdepCC_FALSE@ $(AM_V_CC@am__nodep@)$(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(isisd_libfabric_a_CPPFLAGS) $(CPPFLAGS) $(AM_CFLAGS) $(CFLAGS) -c -o isisd/libfabric_a-isis_bfd.o `test -f 'isisd/isis_bfd.c' || echo '$(srcdir)/'`isisd/isis_bfd.c isisd/libfabric_a-isis_bfd.obj: isisd/isis_bfd.c @am__fastdepCC_TRUE@ $(AM_V_CC)$(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(isisd_libfabric_a_CPPFLAGS) $(CPPFLAGS) $(AM_CFLAGS) $(CFLAGS) -MT isisd/libfabric_a-isis_bfd.obj -MD -MP -MF isisd/$(DEPDIR)/libfabric_a-isis_bfd.Tpo -c -o isisd/libfabric_a-isis_bfd.obj `if test -f 'isisd/isis_bfd.c'; then $(CYGPATH_W) 'isisd/isis_bfd.c'; else $(CYGPATH_W) '$(srcdir)/isisd/isis_bfd.c'; fi` @am__fastdepCC_TRUE@ $(AM_V_at)$(am__mv) isisd/$(DEPDIR)/libfabric_a-isis_bfd.Tpo isisd/$(DEPDIR)/libfabric_a-isis_bfd.Po @AMDEP_TRUE@@am__fastdepCC_FALSE@ $(AM_V_CC)source='isisd/isis_bfd.c' object='isisd/libfabric_a-isis_bfd.obj' libtool=no @AMDEPBACKSLASH@ @AMDEP_TRUE@@am__fastdepCC_FALSE@ DEPDIR=$(DEPDIR) $(CCDEPMODE) $(depcomp) @AMDEPBACKSLASH@ @am__fastdepCC_FALSE@ $(AM_V_CC@am__nodep@)$(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(isisd_libfabric_a_CPPFLAGS) $(CPPFLAGS) $(AM_CFLAGS) $(CFLAGS) -c -o isisd/libfabric_a-isis_bfd.obj `if test -f 'isisd/isis_bfd.c'; then $(CYGPATH_W) 'isisd/isis_bfd.c'; else $(CYGPATH_W) '$(srcdir)/isisd/isis_bfd.c'; fi` isisd/libfabric_a-isis_circuit.o: isisd/isis_circuit.c @am__fastdepCC_TRUE@ $(AM_V_CC)$(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(isisd_libfabric_a_CPPFLAGS) $(CPPFLAGS) $(AM_CFLAGS) $(CFLAGS) -MT isisd/libfabric_a-isis_circuit.o -MD -MP -MF isisd/$(DEPDIR)/libfabric_a-isis_circuit.Tpo -c -o isisd/libfabric_a-isis_circuit.o `test -f 'isisd/isis_circuit.c' || echo '$(srcdir)/'`isisd/isis_circuit.c @am__fastdepCC_TRUE@ $(AM_V_at)$(am__mv) isisd/$(DEPDIR)/libfabric_a-isis_circuit.Tpo isisd/$(DEPDIR)/libfabric_a-isis_circuit.Po @AMDEP_TRUE@@am__fastdepCC_FALSE@ $(AM_V_CC)source='isisd/isis_circuit.c' object='isisd/libfabric_a-isis_circuit.o' libtool=no @AMDEPBACKSLASH@ @AMDEP_TRUE@@am__fastdepCC_FALSE@ DEPDIR=$(DEPDIR) $(CCDEPMODE) $(depcomp) @AMDEPBACKSLASH@ @am__fastdepCC_FALSE@ $(AM_V_CC@am__nodep@)$(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(isisd_libfabric_a_CPPFLAGS) $(CPPFLAGS) $(AM_CFLAGS) $(CFLAGS) -c -o isisd/libfabric_a-isis_circuit.o `test -f 'isisd/isis_circuit.c' || echo '$(srcdir)/'`isisd/isis_circuit.c isisd/libfabric_a-isis_circuit.obj: isisd/isis_circuit.c @am__fastdepCC_TRUE@ $(AM_V_CC)$(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(isisd_libfabric_a_CPPFLAGS) $(CPPFLAGS) $(AM_CFLAGS) $(CFLAGS) -MT isisd/libfabric_a-isis_circuit.obj -MD -MP -MF isisd/$(DEPDIR)/libfabric_a-isis_circuit.Tpo -c -o isisd/libfabric_a-isis_circuit.obj `if test -f 'isisd/isis_circuit.c'; then $(CYGPATH_W) 'isisd/isis_circuit.c'; else $(CYGPATH_W) '$(srcdir)/isisd/isis_circuit.c'; fi` @am__fastdepCC_TRUE@ $(AM_V_at)$(am__mv) isisd/$(DEPDIR)/libfabric_a-isis_circuit.Tpo isisd/$(DEPDIR)/libfabric_a-isis_circuit.Po @AMDEP_TRUE@@am__fastdepCC_FALSE@ $(AM_V_CC)source='isisd/isis_circuit.c' object='isisd/libfabric_a-isis_circuit.obj' libtool=no @AMDEPBACKSLASH@ @AMDEP_TRUE@@am__fastdepCC_FALSE@ DEPDIR=$(DEPDIR) $(CCDEPMODE) $(depcomp) @AMDEPBACKSLASH@ @am__fastdepCC_FALSE@ $(AM_V_CC@am__nodep@)$(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(isisd_libfabric_a_CPPFLAGS) $(CPPFLAGS) $(AM_CFLAGS) $(CFLAGS) -c -o isisd/libfabric_a-isis_circuit.obj `if test -f 'isisd/isis_circuit.c'; then $(CYGPATH_W) 'isisd/isis_circuit.c'; else $(CYGPATH_W) '$(srcdir)/isisd/isis_circuit.c'; fi` isisd/libfabric_a-isis_csm.o: isisd/isis_csm.c @am__fastdepCC_TRUE@ $(AM_V_CC)$(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(isisd_libfabric_a_CPPFLAGS) $(CPPFLAGS) $(AM_CFLAGS) $(CFLAGS) -MT isisd/libfabric_a-isis_csm.o -MD -MP -MF isisd/$(DEPDIR)/libfabric_a-isis_csm.Tpo -c -o isisd/libfabric_a-isis_csm.o `test -f 'isisd/isis_csm.c' || echo '$(srcdir)/'`isisd/isis_csm.c @am__fastdepCC_TRUE@ $(AM_V_at)$(am__mv) isisd/$(DEPDIR)/libfabric_a-isis_csm.Tpo isisd/$(DEPDIR)/libfabric_a-isis_csm.Po @AMDEP_TRUE@@am__fastdepCC_FALSE@ $(AM_V_CC)source='isisd/isis_csm.c' object='isisd/libfabric_a-isis_csm.o' libtool=no @AMDEPBACKSLASH@ @AMDEP_TRUE@@am__fastdepCC_FALSE@ DEPDIR=$(DEPDIR) $(CCDEPMODE) $(depcomp) @AMDEPBACKSLASH@ @am__fastdepCC_FALSE@ $(AM_V_CC@am__nodep@)$(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(isisd_libfabric_a_CPPFLAGS) $(CPPFLAGS) $(AM_CFLAGS) $(CFLAGS) -c -o isisd/libfabric_a-isis_csm.o `test -f 'isisd/isis_csm.c' || echo '$(srcdir)/'`isisd/isis_csm.c isisd/libfabric_a-isis_csm.obj: isisd/isis_csm.c @am__fastdepCC_TRUE@ $(AM_V_CC)$(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(isisd_libfabric_a_CPPFLAGS) $(CPPFLAGS) $(AM_CFLAGS) $(CFLAGS) -MT isisd/libfabric_a-isis_csm.obj -MD -MP -MF isisd/$(DEPDIR)/libfabric_a-isis_csm.Tpo -c -o isisd/libfabric_a-isis_csm.obj `if test -f 'isisd/isis_csm.c'; then $(CYGPATH_W) 'isisd/isis_csm.c'; else $(CYGPATH_W) '$(srcdir)/isisd/isis_csm.c'; fi` @am__fastdepCC_TRUE@ $(AM_V_at)$(am__mv) isisd/$(DEPDIR)/libfabric_a-isis_csm.Tpo isisd/$(DEPDIR)/libfabric_a-isis_csm.Po @AMDEP_TRUE@@am__fastdepCC_FALSE@ $(AM_V_CC)source='isisd/isis_csm.c' object='isisd/libfabric_a-isis_csm.obj' libtool=no @AMDEPBACKSLASH@ @AMDEP_TRUE@@am__fastdepCC_FALSE@ DEPDIR=$(DEPDIR) $(CCDEPMODE) $(depcomp) @AMDEPBACKSLASH@ @am__fastdepCC_FALSE@ $(AM_V_CC@am__nodep@)$(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(isisd_libfabric_a_CPPFLAGS) $(CPPFLAGS) $(AM_CFLAGS) $(CFLAGS) -c -o isisd/libfabric_a-isis_csm.obj `if test -f 'isisd/isis_csm.c'; then $(CYGPATH_W) 'isisd/isis_csm.c'; else $(CYGPATH_W) '$(srcdir)/isisd/isis_csm.c'; fi` isisd/libfabric_a-isis_dr.o: isisd/isis_dr.c @am__fastdepCC_TRUE@ $(AM_V_CC)$(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(isisd_libfabric_a_CPPFLAGS) $(CPPFLAGS) $(AM_CFLAGS) $(CFLAGS) -MT isisd/libfabric_a-isis_dr.o -MD -MP -MF isisd/$(DEPDIR)/libfabric_a-isis_dr.Tpo -c -o isisd/libfabric_a-isis_dr.o `test -f 'isisd/isis_dr.c' || echo '$(srcdir)/'`isisd/isis_dr.c @am__fastdepCC_TRUE@ $(AM_V_at)$(am__mv) isisd/$(DEPDIR)/libfabric_a-isis_dr.Tpo isisd/$(DEPDIR)/libfabric_a-isis_dr.Po @AMDEP_TRUE@@am__fastdepCC_FALSE@ $(AM_V_CC)source='isisd/isis_dr.c' object='isisd/libfabric_a-isis_dr.o' libtool=no @AMDEPBACKSLASH@ @AMDEP_TRUE@@am__fastdepCC_FALSE@ DEPDIR=$(DEPDIR) $(CCDEPMODE) $(depcomp) @AMDEPBACKSLASH@ @am__fastdepCC_FALSE@ $(AM_V_CC@am__nodep@)$(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(isisd_libfabric_a_CPPFLAGS) $(CPPFLAGS) $(AM_CFLAGS) $(CFLAGS) -c -o isisd/libfabric_a-isis_dr.o `test -f 'isisd/isis_dr.c' || echo '$(srcdir)/'`isisd/isis_dr.c isisd/libfabric_a-isis_dr.obj: isisd/isis_dr.c @am__fastdepCC_TRUE@ $(AM_V_CC)$(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(isisd_libfabric_a_CPPFLAGS) $(CPPFLAGS) $(AM_CFLAGS) $(CFLAGS) -MT isisd/libfabric_a-isis_dr.obj -MD -MP -MF isisd/$(DEPDIR)/libfabric_a-isis_dr.Tpo -c -o isisd/libfabric_a-isis_dr.obj `if test -f 'isisd/isis_dr.c'; then $(CYGPATH_W) 'isisd/isis_dr.c'; else $(CYGPATH_W) '$(srcdir)/isisd/isis_dr.c'; fi` @am__fastdepCC_TRUE@ $(AM_V_at)$(am__mv) isisd/$(DEPDIR)/libfabric_a-isis_dr.Tpo isisd/$(DEPDIR)/libfabric_a-isis_dr.Po @AMDEP_TRUE@@am__fastdepCC_FALSE@ $(AM_V_CC)source='isisd/isis_dr.c' object='isisd/libfabric_a-isis_dr.obj' libtool=no @AMDEPBACKSLASH@ @AMDEP_TRUE@@am__fastdepCC_FALSE@ DEPDIR=$(DEPDIR) $(CCDEPMODE) $(depcomp) @AMDEPBACKSLASH@ @am__fastdepCC_FALSE@ $(AM_V_CC@am__nodep@)$(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(isisd_libfabric_a_CPPFLAGS) $(CPPFLAGS) $(AM_CFLAGS) $(CFLAGS) -c -o isisd/libfabric_a-isis_dr.obj `if test -f 'isisd/isis_dr.c'; then $(CYGPATH_W) 'isisd/isis_dr.c'; else $(CYGPATH_W) '$(srcdir)/isisd/isis_dr.c'; fi` isisd/libfabric_a-isis_dynhn.o: isisd/isis_dynhn.c @am__fastdepCC_TRUE@ $(AM_V_CC)$(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(isisd_libfabric_a_CPPFLAGS) $(CPPFLAGS) $(AM_CFLAGS) $(CFLAGS) -MT isisd/libfabric_a-isis_dynhn.o -MD -MP -MF isisd/$(DEPDIR)/libfabric_a-isis_dynhn.Tpo -c -o isisd/libfabric_a-isis_dynhn.o `test -f 'isisd/isis_dynhn.c' || echo '$(srcdir)/'`isisd/isis_dynhn.c @am__fastdepCC_TRUE@ $(AM_V_at)$(am__mv) isisd/$(DEPDIR)/libfabric_a-isis_dynhn.Tpo isisd/$(DEPDIR)/libfabric_a-isis_dynhn.Po @AMDEP_TRUE@@am__fastdepCC_FALSE@ $(AM_V_CC)source='isisd/isis_dynhn.c' object='isisd/libfabric_a-isis_dynhn.o' libtool=no @AMDEPBACKSLASH@ @AMDEP_TRUE@@am__fastdepCC_FALSE@ DEPDIR=$(DEPDIR) $(CCDEPMODE) $(depcomp) @AMDEPBACKSLASH@ @am__fastdepCC_FALSE@ $(AM_V_CC@am__nodep@)$(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(isisd_libfabric_a_CPPFLAGS) $(CPPFLAGS) $(AM_CFLAGS) $(CFLAGS) -c -o isisd/libfabric_a-isis_dynhn.o `test -f 'isisd/isis_dynhn.c' || echo '$(srcdir)/'`isisd/isis_dynhn.c isisd/libfabric_a-isis_dynhn.obj: isisd/isis_dynhn.c @am__fastdepCC_TRUE@ $(AM_V_CC)$(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(isisd_libfabric_a_CPPFLAGS) $(CPPFLAGS) $(AM_CFLAGS) $(CFLAGS) -MT isisd/libfabric_a-isis_dynhn.obj -MD -MP -MF isisd/$(DEPDIR)/libfabric_a-isis_dynhn.Tpo -c -o isisd/libfabric_a-isis_dynhn.obj `if test -f 'isisd/isis_dynhn.c'; then $(CYGPATH_W) 'isisd/isis_dynhn.c'; else $(CYGPATH_W) '$(srcdir)/isisd/isis_dynhn.c'; fi` @am__fastdepCC_TRUE@ $(AM_V_at)$(am__mv) isisd/$(DEPDIR)/libfabric_a-isis_dynhn.Tpo isisd/$(DEPDIR)/libfabric_a-isis_dynhn.Po @AMDEP_TRUE@@am__fastdepCC_FALSE@ $(AM_V_CC)source='isisd/isis_dynhn.c' object='isisd/libfabric_a-isis_dynhn.obj' libtool=no @AMDEPBACKSLASH@ @AMDEP_TRUE@@am__fastdepCC_FALSE@ DEPDIR=$(DEPDIR) $(CCDEPMODE) $(depcomp) @AMDEPBACKSLASH@ @am__fastdepCC_FALSE@ $(AM_V_CC@am__nodep@)$(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(isisd_libfabric_a_CPPFLAGS) $(CPPFLAGS) $(AM_CFLAGS) $(CFLAGS) -c -o isisd/libfabric_a-isis_dynhn.obj `if test -f 'isisd/isis_dynhn.c'; then $(CYGPATH_W) 'isisd/isis_dynhn.c'; else $(CYGPATH_W) '$(srcdir)/isisd/isis_dynhn.c'; fi` isisd/libfabric_a-isis_errors.o: isisd/isis_errors.c @am__fastdepCC_TRUE@ $(AM_V_CC)$(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(isisd_libfabric_a_CPPFLAGS) $(CPPFLAGS) $(AM_CFLAGS) $(CFLAGS) -MT isisd/libfabric_a-isis_errors.o -MD -MP -MF isisd/$(DEPDIR)/libfabric_a-isis_errors.Tpo -c -o isisd/libfabric_a-isis_errors.o `test -f 'isisd/isis_errors.c' || echo '$(srcdir)/'`isisd/isis_errors.c @am__fastdepCC_TRUE@ $(AM_V_at)$(am__mv) isisd/$(DEPDIR)/libfabric_a-isis_errors.Tpo isisd/$(DEPDIR)/libfabric_a-isis_errors.Po @AMDEP_TRUE@@am__fastdepCC_FALSE@ $(AM_V_CC)source='isisd/isis_errors.c' object='isisd/libfabric_a-isis_errors.o' libtool=no @AMDEPBACKSLASH@ @AMDEP_TRUE@@am__fastdepCC_FALSE@ DEPDIR=$(DEPDIR) $(CCDEPMODE) $(depcomp) @AMDEPBACKSLASH@ @am__fastdepCC_FALSE@ $(AM_V_CC@am__nodep@)$(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(isisd_libfabric_a_CPPFLAGS) $(CPPFLAGS) $(AM_CFLAGS) $(CFLAGS) -c -o isisd/libfabric_a-isis_errors.o `test -f 'isisd/isis_errors.c' || echo '$(srcdir)/'`isisd/isis_errors.c isisd/libfabric_a-isis_errors.obj: isisd/isis_errors.c @am__fastdepCC_TRUE@ $(AM_V_CC)$(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(isisd_libfabric_a_CPPFLAGS) $(CPPFLAGS) $(AM_CFLAGS) $(CFLAGS) -MT isisd/libfabric_a-isis_errors.obj -MD -MP -MF isisd/$(DEPDIR)/libfabric_a-isis_errors.Tpo -c -o isisd/libfabric_a-isis_errors.obj `if test -f 'isisd/isis_errors.c'; then $(CYGPATH_W) 'isisd/isis_errors.c'; else $(CYGPATH_W) '$(srcdir)/isisd/isis_errors.c'; fi` @am__fastdepCC_TRUE@ $(AM_V_at)$(am__mv) isisd/$(DEPDIR)/libfabric_a-isis_errors.Tpo isisd/$(DEPDIR)/libfabric_a-isis_errors.Po @AMDEP_TRUE@@am__fastdepCC_FALSE@ $(AM_V_CC)source='isisd/isis_errors.c' object='isisd/libfabric_a-isis_errors.obj' libtool=no @AMDEPBACKSLASH@ @AMDEP_TRUE@@am__fastdepCC_FALSE@ DEPDIR=$(DEPDIR) $(CCDEPMODE) $(depcomp) @AMDEPBACKSLASH@ @am__fastdepCC_FALSE@ $(AM_V_CC@am__nodep@)$(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(isisd_libfabric_a_CPPFLAGS) $(CPPFLAGS) $(AM_CFLAGS) $(CFLAGS) -c -o isisd/libfabric_a-isis_errors.obj `if test -f 'isisd/isis_errors.c'; then $(CYGPATH_W) 'isisd/isis_errors.c'; else $(CYGPATH_W) '$(srcdir)/isisd/isis_errors.c'; fi` isisd/libfabric_a-isis_events.o: isisd/isis_events.c @am__fastdepCC_TRUE@ $(AM_V_CC)$(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(isisd_libfabric_a_CPPFLAGS) $(CPPFLAGS) $(AM_CFLAGS) $(CFLAGS) -MT isisd/libfabric_a-isis_events.o -MD -MP -MF isisd/$(DEPDIR)/libfabric_a-isis_events.Tpo -c -o isisd/libfabric_a-isis_events.o `test -f 'isisd/isis_events.c' || echo '$(srcdir)/'`isisd/isis_events.c @am__fastdepCC_TRUE@ $(AM_V_at)$(am__mv) isisd/$(DEPDIR)/libfabric_a-isis_events.Tpo isisd/$(DEPDIR)/libfabric_a-isis_events.Po @AMDEP_TRUE@@am__fastdepCC_FALSE@ $(AM_V_CC)source='isisd/isis_events.c' object='isisd/libfabric_a-isis_events.o' libtool=no @AMDEPBACKSLASH@ @AMDEP_TRUE@@am__fastdepCC_FALSE@ DEPDIR=$(DEPDIR) $(CCDEPMODE) $(depcomp) @AMDEPBACKSLASH@ @am__fastdepCC_FALSE@ $(AM_V_CC@am__nodep@)$(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(isisd_libfabric_a_CPPFLAGS) $(CPPFLAGS) $(AM_CFLAGS) $(CFLAGS) -c -o isisd/libfabric_a-isis_events.o `test -f 'isisd/isis_events.c' || echo '$(srcdir)/'`isisd/isis_events.c isisd/libfabric_a-isis_events.obj: isisd/isis_events.c @am__fastdepCC_TRUE@ $(AM_V_CC)$(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(isisd_libfabric_a_CPPFLAGS) $(CPPFLAGS) $(AM_CFLAGS) $(CFLAGS) -MT isisd/libfabric_a-isis_events.obj -MD -MP -MF isisd/$(DEPDIR)/libfabric_a-isis_events.Tpo -c -o isisd/libfabric_a-isis_events.obj `if test -f 'isisd/isis_events.c'; then $(CYGPATH_W) 'isisd/isis_events.c'; else $(CYGPATH_W) '$(srcdir)/isisd/isis_events.c'; fi` @am__fastdepCC_TRUE@ $(AM_V_at)$(am__mv) isisd/$(DEPDIR)/libfabric_a-isis_events.Tpo isisd/$(DEPDIR)/libfabric_a-isis_events.Po @AMDEP_TRUE@@am__fastdepCC_FALSE@ $(AM_V_CC)source='isisd/isis_events.c' object='isisd/libfabric_a-isis_events.obj' libtool=no @AMDEPBACKSLASH@ @AMDEP_TRUE@@am__fastdepCC_FALSE@ DEPDIR=$(DEPDIR) $(CCDEPMODE) $(depcomp) @AMDEPBACKSLASH@ @am__fastdepCC_FALSE@ $(AM_V_CC@am__nodep@)$(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(isisd_libfabric_a_CPPFLAGS) $(CPPFLAGS) $(AM_CFLAGS) $(CFLAGS) -c -o isisd/libfabric_a-isis_events.obj `if test -f 'isisd/isis_events.c'; then $(CYGPATH_W) 'isisd/isis_events.c'; else $(CYGPATH_W) '$(srcdir)/isisd/isis_events.c'; fi` isisd/libfabric_a-isis_flags.o: isisd/isis_flags.c @am__fastdepCC_TRUE@ $(AM_V_CC)$(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(isisd_libfabric_a_CPPFLAGS) $(CPPFLAGS) $(AM_CFLAGS) $(CFLAGS) -MT isisd/libfabric_a-isis_flags.o -MD -MP -MF isisd/$(DEPDIR)/libfabric_a-isis_flags.Tpo -c -o isisd/libfabric_a-isis_flags.o `test -f 'isisd/isis_flags.c' || echo '$(srcdir)/'`isisd/isis_flags.c @am__fastdepCC_TRUE@ $(AM_V_at)$(am__mv) isisd/$(DEPDIR)/libfabric_a-isis_flags.Tpo isisd/$(DEPDIR)/libfabric_a-isis_flags.Po @AMDEP_TRUE@@am__fastdepCC_FALSE@ $(AM_V_CC)source='isisd/isis_flags.c' object='isisd/libfabric_a-isis_flags.o' libtool=no @AMDEPBACKSLASH@ @AMDEP_TRUE@@am__fastdepCC_FALSE@ DEPDIR=$(DEPDIR) $(CCDEPMODE) $(depcomp) @AMDEPBACKSLASH@ @am__fastdepCC_FALSE@ $(AM_V_CC@am__nodep@)$(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(isisd_libfabric_a_CPPFLAGS) $(CPPFLAGS) $(AM_CFLAGS) $(CFLAGS) -c -o isisd/libfabric_a-isis_flags.o `test -f 'isisd/isis_flags.c' || echo '$(srcdir)/'`isisd/isis_flags.c isisd/libfabric_a-isis_flags.obj: isisd/isis_flags.c @am__fastdepCC_TRUE@ $(AM_V_CC)$(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(isisd_libfabric_a_CPPFLAGS) $(CPPFLAGS) $(AM_CFLAGS) $(CFLAGS) -MT isisd/libfabric_a-isis_flags.obj -MD -MP -MF isisd/$(DEPDIR)/libfabric_a-isis_flags.Tpo -c -o isisd/libfabric_a-isis_flags.obj `if test -f 'isisd/isis_flags.c'; then $(CYGPATH_W) 'isisd/isis_flags.c'; else $(CYGPATH_W) '$(srcdir)/isisd/isis_flags.c'; fi` @am__fastdepCC_TRUE@ $(AM_V_at)$(am__mv) isisd/$(DEPDIR)/libfabric_a-isis_flags.Tpo isisd/$(DEPDIR)/libfabric_a-isis_flags.Po @AMDEP_TRUE@@am__fastdepCC_FALSE@ $(AM_V_CC)source='isisd/isis_flags.c' object='isisd/libfabric_a-isis_flags.obj' libtool=no @AMDEPBACKSLASH@ @AMDEP_TRUE@@am__fastdepCC_FALSE@ DEPDIR=$(DEPDIR) $(CCDEPMODE) $(depcomp) @AMDEPBACKSLASH@ @am__fastdepCC_FALSE@ $(AM_V_CC@am__nodep@)$(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(isisd_libfabric_a_CPPFLAGS) $(CPPFLAGS) $(AM_CFLAGS) $(CFLAGS) -c -o isisd/libfabric_a-isis_flags.obj `if test -f 'isisd/isis_flags.c'; then $(CYGPATH_W) 'isisd/isis_flags.c'; else $(CYGPATH_W) '$(srcdir)/isisd/isis_flags.c'; fi` isisd/libfabric_a-isis_lsp.o: isisd/isis_lsp.c @am__fastdepCC_TRUE@ $(AM_V_CC)$(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(isisd_libfabric_a_CPPFLAGS) $(CPPFLAGS) $(AM_CFLAGS) $(CFLAGS) -MT isisd/libfabric_a-isis_lsp.o -MD -MP -MF isisd/$(DEPDIR)/libfabric_a-isis_lsp.Tpo -c -o isisd/libfabric_a-isis_lsp.o `test -f 'isisd/isis_lsp.c' || echo '$(srcdir)/'`isisd/isis_lsp.c @am__fastdepCC_TRUE@ $(AM_V_at)$(am__mv) isisd/$(DEPDIR)/libfabric_a-isis_lsp.Tpo isisd/$(DEPDIR)/libfabric_a-isis_lsp.Po @AMDEP_TRUE@@am__fastdepCC_FALSE@ $(AM_V_CC)source='isisd/isis_lsp.c' object='isisd/libfabric_a-isis_lsp.o' libtool=no @AMDEPBACKSLASH@ @AMDEP_TRUE@@am__fastdepCC_FALSE@ DEPDIR=$(DEPDIR) $(CCDEPMODE) $(depcomp) @AMDEPBACKSLASH@ @am__fastdepCC_FALSE@ $(AM_V_CC@am__nodep@)$(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(isisd_libfabric_a_CPPFLAGS) $(CPPFLAGS) $(AM_CFLAGS) $(CFLAGS) -c -o isisd/libfabric_a-isis_lsp.o `test -f 'isisd/isis_lsp.c' || echo '$(srcdir)/'`isisd/isis_lsp.c isisd/libfabric_a-isis_lsp.obj: isisd/isis_lsp.c @am__fastdepCC_TRUE@ $(AM_V_CC)$(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(isisd_libfabric_a_CPPFLAGS) $(CPPFLAGS) $(AM_CFLAGS) $(CFLAGS) -MT isisd/libfabric_a-isis_lsp.obj -MD -MP -MF isisd/$(DEPDIR)/libfabric_a-isis_lsp.Tpo -c -o isisd/libfabric_a-isis_lsp.obj `if test -f 'isisd/isis_lsp.c'; then $(CYGPATH_W) 'isisd/isis_lsp.c'; else $(CYGPATH_W) '$(srcdir)/isisd/isis_lsp.c'; fi` @am__fastdepCC_TRUE@ $(AM_V_at)$(am__mv) isisd/$(DEPDIR)/libfabric_a-isis_lsp.Tpo isisd/$(DEPDIR)/libfabric_a-isis_lsp.Po @AMDEP_TRUE@@am__fastdepCC_FALSE@ $(AM_V_CC)source='isisd/isis_lsp.c' object='isisd/libfabric_a-isis_lsp.obj' libtool=no @AMDEPBACKSLASH@ @AMDEP_TRUE@@am__fastdepCC_FALSE@ DEPDIR=$(DEPDIR) $(CCDEPMODE) $(depcomp) @AMDEPBACKSLASH@ @am__fastdepCC_FALSE@ $(AM_V_CC@am__nodep@)$(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(isisd_libfabric_a_CPPFLAGS) $(CPPFLAGS) $(AM_CFLAGS) $(CFLAGS) -c -o isisd/libfabric_a-isis_lsp.obj `if test -f 'isisd/isis_lsp.c'; then $(CYGPATH_W) 'isisd/isis_lsp.c'; else $(CYGPATH_W) '$(srcdir)/isisd/isis_lsp.c'; fi` isisd/libfabric_a-isis_memory.o: isisd/isis_memory.c @am__fastdepCC_TRUE@ $(AM_V_CC)$(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(isisd_libfabric_a_CPPFLAGS) $(CPPFLAGS) $(AM_CFLAGS) $(CFLAGS) -MT isisd/libfabric_a-isis_memory.o -MD -MP -MF isisd/$(DEPDIR)/libfabric_a-isis_memory.Tpo -c -o isisd/libfabric_a-isis_memory.o `test -f 'isisd/isis_memory.c' || echo '$(srcdir)/'`isisd/isis_memory.c @am__fastdepCC_TRUE@ $(AM_V_at)$(am__mv) isisd/$(DEPDIR)/libfabric_a-isis_memory.Tpo isisd/$(DEPDIR)/libfabric_a-isis_memory.Po @AMDEP_TRUE@@am__fastdepCC_FALSE@ $(AM_V_CC)source='isisd/isis_memory.c' object='isisd/libfabric_a-isis_memory.o' libtool=no @AMDEPBACKSLASH@ @AMDEP_TRUE@@am__fastdepCC_FALSE@ DEPDIR=$(DEPDIR) $(CCDEPMODE) $(depcomp) @AMDEPBACKSLASH@ @am__fastdepCC_FALSE@ $(AM_V_CC@am__nodep@)$(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(isisd_libfabric_a_CPPFLAGS) $(CPPFLAGS) $(AM_CFLAGS) $(CFLAGS) -c -o isisd/libfabric_a-isis_memory.o `test -f 'isisd/isis_memory.c' || echo '$(srcdir)/'`isisd/isis_memory.c isisd/libfabric_a-isis_memory.obj: isisd/isis_memory.c @am__fastdepCC_TRUE@ $(AM_V_CC)$(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(isisd_libfabric_a_CPPFLAGS) $(CPPFLAGS) $(AM_CFLAGS) $(CFLAGS) -MT isisd/libfabric_a-isis_memory.obj -MD -MP -MF isisd/$(DEPDIR)/libfabric_a-isis_memory.Tpo -c -o isisd/libfabric_a-isis_memory.obj `if test -f 'isisd/isis_memory.c'; then $(CYGPATH_W) 'isisd/isis_memory.c'; else $(CYGPATH_W) '$(srcdir)/isisd/isis_memory.c'; fi` @am__fastdepCC_TRUE@ $(AM_V_at)$(am__mv) isisd/$(DEPDIR)/libfabric_a-isis_memory.Tpo isisd/$(DEPDIR)/libfabric_a-isis_memory.Po @AMDEP_TRUE@@am__fastdepCC_FALSE@ $(AM_V_CC)source='isisd/isis_memory.c' object='isisd/libfabric_a-isis_memory.obj' libtool=no @AMDEPBACKSLASH@ @AMDEP_TRUE@@am__fastdepCC_FALSE@ DEPDIR=$(DEPDIR) $(CCDEPMODE) $(depcomp) @AMDEPBACKSLASH@ @am__fastdepCC_FALSE@ $(AM_V_CC@am__nodep@)$(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(isisd_libfabric_a_CPPFLAGS) $(CPPFLAGS) $(AM_CFLAGS) $(CFLAGS) -c -o isisd/libfabric_a-isis_memory.obj `if test -f 'isisd/isis_memory.c'; then $(CYGPATH_W) 'isisd/isis_memory.c'; else $(CYGPATH_W) '$(srcdir)/isisd/isis_memory.c'; fi` isisd/libfabric_a-isis_misc.o: isisd/isis_misc.c @am__fastdepCC_TRUE@ $(AM_V_CC)$(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(isisd_libfabric_a_CPPFLAGS) $(CPPFLAGS) $(AM_CFLAGS) $(CFLAGS) -MT isisd/libfabric_a-isis_misc.o -MD -MP -MF isisd/$(DEPDIR)/libfabric_a-isis_misc.Tpo -c -o isisd/libfabric_a-isis_misc.o `test -f 'isisd/isis_misc.c' || echo '$(srcdir)/'`isisd/isis_misc.c @am__fastdepCC_TRUE@ $(AM_V_at)$(am__mv) isisd/$(DEPDIR)/libfabric_a-isis_misc.Tpo isisd/$(DEPDIR)/libfabric_a-isis_misc.Po @AMDEP_TRUE@@am__fastdepCC_FALSE@ $(AM_V_CC)source='isisd/isis_misc.c' object='isisd/libfabric_a-isis_misc.o' libtool=no @AMDEPBACKSLASH@ @AMDEP_TRUE@@am__fastdepCC_FALSE@ DEPDIR=$(DEPDIR) $(CCDEPMODE) $(depcomp) @AMDEPBACKSLASH@ @am__fastdepCC_FALSE@ $(AM_V_CC@am__nodep@)$(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(isisd_libfabric_a_CPPFLAGS) $(CPPFLAGS) $(AM_CFLAGS) $(CFLAGS) -c -o isisd/libfabric_a-isis_misc.o `test -f 'isisd/isis_misc.c' || echo '$(srcdir)/'`isisd/isis_misc.c isisd/libfabric_a-isis_misc.obj: isisd/isis_misc.c @am__fastdepCC_TRUE@ $(AM_V_CC)$(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(isisd_libfabric_a_CPPFLAGS) $(CPPFLAGS) $(AM_CFLAGS) $(CFLAGS) -MT isisd/libfabric_a-isis_misc.obj -MD -MP -MF isisd/$(DEPDIR)/libfabric_a-isis_misc.Tpo -c -o isisd/libfabric_a-isis_misc.obj `if test -f 'isisd/isis_misc.c'; then $(CYGPATH_W) 'isisd/isis_misc.c'; else $(CYGPATH_W) '$(srcdir)/isisd/isis_misc.c'; fi` @am__fastdepCC_TRUE@ $(AM_V_at)$(am__mv) isisd/$(DEPDIR)/libfabric_a-isis_misc.Tpo isisd/$(DEPDIR)/libfabric_a-isis_misc.Po @AMDEP_TRUE@@am__fastdepCC_FALSE@ $(AM_V_CC)source='isisd/isis_misc.c' object='isisd/libfabric_a-isis_misc.obj' libtool=no @AMDEPBACKSLASH@ @AMDEP_TRUE@@am__fastdepCC_FALSE@ DEPDIR=$(DEPDIR) $(CCDEPMODE) $(depcomp) @AMDEPBACKSLASH@ @am__fastdepCC_FALSE@ $(AM_V_CC@am__nodep@)$(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(isisd_libfabric_a_CPPFLAGS) $(CPPFLAGS) $(AM_CFLAGS) $(CFLAGS) -c -o isisd/libfabric_a-isis_misc.obj `if test -f 'isisd/isis_misc.c'; then $(CYGPATH_W) 'isisd/isis_misc.c'; else $(CYGPATH_W) '$(srcdir)/isisd/isis_misc.c'; fi` isisd/libfabric_a-isis_mt.o: isisd/isis_mt.c @am__fastdepCC_TRUE@ $(AM_V_CC)$(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(isisd_libfabric_a_CPPFLAGS) $(CPPFLAGS) $(AM_CFLAGS) $(CFLAGS) -MT isisd/libfabric_a-isis_mt.o -MD -MP -MF isisd/$(DEPDIR)/libfabric_a-isis_mt.Tpo -c -o isisd/libfabric_a-isis_mt.o `test -f 'isisd/isis_mt.c' || echo '$(srcdir)/'`isisd/isis_mt.c @am__fastdepCC_TRUE@ $(AM_V_at)$(am__mv) isisd/$(DEPDIR)/libfabric_a-isis_mt.Tpo isisd/$(DEPDIR)/libfabric_a-isis_mt.Po @AMDEP_TRUE@@am__fastdepCC_FALSE@ $(AM_V_CC)source='isisd/isis_mt.c' object='isisd/libfabric_a-isis_mt.o' libtool=no @AMDEPBACKSLASH@ @AMDEP_TRUE@@am__fastdepCC_FALSE@ DEPDIR=$(DEPDIR) $(CCDEPMODE) $(depcomp) @AMDEPBACKSLASH@ @am__fastdepCC_FALSE@ $(AM_V_CC@am__nodep@)$(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(isisd_libfabric_a_CPPFLAGS) $(CPPFLAGS) $(AM_CFLAGS) $(CFLAGS) -c -o isisd/libfabric_a-isis_mt.o `test -f 'isisd/isis_mt.c' || echo '$(srcdir)/'`isisd/isis_mt.c isisd/libfabric_a-isis_mt.obj: isisd/isis_mt.c @am__fastdepCC_TRUE@ $(AM_V_CC)$(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(isisd_libfabric_a_CPPFLAGS) $(CPPFLAGS) $(AM_CFLAGS) $(CFLAGS) -MT isisd/libfabric_a-isis_mt.obj -MD -MP -MF isisd/$(DEPDIR)/libfabric_a-isis_mt.Tpo -c -o isisd/libfabric_a-isis_mt.obj `if test -f 'isisd/isis_mt.c'; then $(CYGPATH_W) 'isisd/isis_mt.c'; else $(CYGPATH_W) '$(srcdir)/isisd/isis_mt.c'; fi` @am__fastdepCC_TRUE@ $(AM_V_at)$(am__mv) isisd/$(DEPDIR)/libfabric_a-isis_mt.Tpo isisd/$(DEPDIR)/libfabric_a-isis_mt.Po @AMDEP_TRUE@@am__fastdepCC_FALSE@ $(AM_V_CC)source='isisd/isis_mt.c' object='isisd/libfabric_a-isis_mt.obj' libtool=no @AMDEPBACKSLASH@ @AMDEP_TRUE@@am__fastdepCC_FALSE@ DEPDIR=$(DEPDIR) $(CCDEPMODE) $(depcomp) @AMDEPBACKSLASH@ @am__fastdepCC_FALSE@ $(AM_V_CC@am__nodep@)$(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(isisd_libfabric_a_CPPFLAGS) $(CPPFLAGS) $(AM_CFLAGS) $(CFLAGS) -c -o isisd/libfabric_a-isis_mt.obj `if test -f 'isisd/isis_mt.c'; then $(CYGPATH_W) 'isisd/isis_mt.c'; else $(CYGPATH_W) '$(srcdir)/isisd/isis_mt.c'; fi` isisd/libfabric_a-isis_pdu.o: isisd/isis_pdu.c @am__fastdepCC_TRUE@ $(AM_V_CC)$(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(isisd_libfabric_a_CPPFLAGS) $(CPPFLAGS) $(AM_CFLAGS) $(CFLAGS) -MT isisd/libfabric_a-isis_pdu.o -MD -MP -MF isisd/$(DEPDIR)/libfabric_a-isis_pdu.Tpo -c -o isisd/libfabric_a-isis_pdu.o `test -f 'isisd/isis_pdu.c' || echo '$(srcdir)/'`isisd/isis_pdu.c @am__fastdepCC_TRUE@ $(AM_V_at)$(am__mv) isisd/$(DEPDIR)/libfabric_a-isis_pdu.Tpo isisd/$(DEPDIR)/libfabric_a-isis_pdu.Po @AMDEP_TRUE@@am__fastdepCC_FALSE@ $(AM_V_CC)source='isisd/isis_pdu.c' object='isisd/libfabric_a-isis_pdu.o' libtool=no @AMDEPBACKSLASH@ @AMDEP_TRUE@@am__fastdepCC_FALSE@ DEPDIR=$(DEPDIR) $(CCDEPMODE) $(depcomp) @AMDEPBACKSLASH@ @am__fastdepCC_FALSE@ $(AM_V_CC@am__nodep@)$(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(isisd_libfabric_a_CPPFLAGS) $(CPPFLAGS) $(AM_CFLAGS) $(CFLAGS) -c -o isisd/libfabric_a-isis_pdu.o `test -f 'isisd/isis_pdu.c' || echo '$(srcdir)/'`isisd/isis_pdu.c isisd/libfabric_a-isis_pdu.obj: isisd/isis_pdu.c @am__fastdepCC_TRUE@ $(AM_V_CC)$(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(isisd_libfabric_a_CPPFLAGS) $(CPPFLAGS) $(AM_CFLAGS) $(CFLAGS) -MT isisd/libfabric_a-isis_pdu.obj -MD -MP -MF isisd/$(DEPDIR)/libfabric_a-isis_pdu.Tpo -c -o isisd/libfabric_a-isis_pdu.obj `if test -f 'isisd/isis_pdu.c'; then $(CYGPATH_W) 'isisd/isis_pdu.c'; else $(CYGPATH_W) '$(srcdir)/isisd/isis_pdu.c'; fi` @am__fastdepCC_TRUE@ $(AM_V_at)$(am__mv) isisd/$(DEPDIR)/libfabric_a-isis_pdu.Tpo isisd/$(DEPDIR)/libfabric_a-isis_pdu.Po @AMDEP_TRUE@@am__fastdepCC_FALSE@ $(AM_V_CC)source='isisd/isis_pdu.c' object='isisd/libfabric_a-isis_pdu.obj' libtool=no @AMDEPBACKSLASH@ @AMDEP_TRUE@@am__fastdepCC_FALSE@ DEPDIR=$(DEPDIR) $(CCDEPMODE) $(depcomp) @AMDEPBACKSLASH@ @am__fastdepCC_FALSE@ $(AM_V_CC@am__nodep@)$(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(isisd_libfabric_a_CPPFLAGS) $(CPPFLAGS) $(AM_CFLAGS) $(CFLAGS) -c -o isisd/libfabric_a-isis_pdu.obj `if test -f 'isisd/isis_pdu.c'; then $(CYGPATH_W) 'isisd/isis_pdu.c'; else $(CYGPATH_W) '$(srcdir)/isisd/isis_pdu.c'; fi` isisd/libfabric_a-isis_pdu_counter.o: isisd/isis_pdu_counter.c @am__fastdepCC_TRUE@ $(AM_V_CC)$(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(isisd_libfabric_a_CPPFLAGS) $(CPPFLAGS) $(AM_CFLAGS) $(CFLAGS) -MT isisd/libfabric_a-isis_pdu_counter.o -MD -MP -MF isisd/$(DEPDIR)/libfabric_a-isis_pdu_counter.Tpo -c -o isisd/libfabric_a-isis_pdu_counter.o `test -f 'isisd/isis_pdu_counter.c' || echo '$(srcdir)/'`isisd/isis_pdu_counter.c @am__fastdepCC_TRUE@ $(AM_V_at)$(am__mv) isisd/$(DEPDIR)/libfabric_a-isis_pdu_counter.Tpo isisd/$(DEPDIR)/libfabric_a-isis_pdu_counter.Po @AMDEP_TRUE@@am__fastdepCC_FALSE@ $(AM_V_CC)source='isisd/isis_pdu_counter.c' object='isisd/libfabric_a-isis_pdu_counter.o' libtool=no @AMDEPBACKSLASH@ @AMDEP_TRUE@@am__fastdepCC_FALSE@ DEPDIR=$(DEPDIR) $(CCDEPMODE) $(depcomp) @AMDEPBACKSLASH@ @am__fastdepCC_FALSE@ $(AM_V_CC@am__nodep@)$(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(isisd_libfabric_a_CPPFLAGS) $(CPPFLAGS) $(AM_CFLAGS) $(CFLAGS) -c -o isisd/libfabric_a-isis_pdu_counter.o `test -f 'isisd/isis_pdu_counter.c' || echo '$(srcdir)/'`isisd/isis_pdu_counter.c isisd/libfabric_a-isis_pdu_counter.obj: isisd/isis_pdu_counter.c @am__fastdepCC_TRUE@ $(AM_V_CC)$(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(isisd_libfabric_a_CPPFLAGS) $(CPPFLAGS) $(AM_CFLAGS) $(CFLAGS) -MT isisd/libfabric_a-isis_pdu_counter.obj -MD -MP -MF isisd/$(DEPDIR)/libfabric_a-isis_pdu_counter.Tpo -c -o isisd/libfabric_a-isis_pdu_counter.obj `if test -f 'isisd/isis_pdu_counter.c'; then $(CYGPATH_W) 'isisd/isis_pdu_counter.c'; else $(CYGPATH_W) '$(srcdir)/isisd/isis_pdu_counter.c'; fi` @am__fastdepCC_TRUE@ $(AM_V_at)$(am__mv) isisd/$(DEPDIR)/libfabric_a-isis_pdu_counter.Tpo isisd/$(DEPDIR)/libfabric_a-isis_pdu_counter.Po @AMDEP_TRUE@@am__fastdepCC_FALSE@ $(AM_V_CC)source='isisd/isis_pdu_counter.c' object='isisd/libfabric_a-isis_pdu_counter.obj' libtool=no @AMDEPBACKSLASH@ @AMDEP_TRUE@@am__fastdepCC_FALSE@ DEPDIR=$(DEPDIR) $(CCDEPMODE) $(depcomp) @AMDEPBACKSLASH@ @am__fastdepCC_FALSE@ $(AM_V_CC@am__nodep@)$(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(isisd_libfabric_a_CPPFLAGS) $(CPPFLAGS) $(AM_CFLAGS) $(CFLAGS) -c -o isisd/libfabric_a-isis_pdu_counter.obj `if test -f 'isisd/isis_pdu_counter.c'; then $(CYGPATH_W) 'isisd/isis_pdu_counter.c'; else $(CYGPATH_W) '$(srcdir)/isisd/isis_pdu_counter.c'; fi` isisd/libfabric_a-isis_redist.o: isisd/isis_redist.c @am__fastdepCC_TRUE@ $(AM_V_CC)$(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(isisd_libfabric_a_CPPFLAGS) $(CPPFLAGS) $(AM_CFLAGS) $(CFLAGS) -MT isisd/libfabric_a-isis_redist.o -MD -MP -MF isisd/$(DEPDIR)/libfabric_a-isis_redist.Tpo -c -o isisd/libfabric_a-isis_redist.o `test -f 'isisd/isis_redist.c' || echo '$(srcdir)/'`isisd/isis_redist.c @am__fastdepCC_TRUE@ $(AM_V_at)$(am__mv) isisd/$(DEPDIR)/libfabric_a-isis_redist.Tpo isisd/$(DEPDIR)/libfabric_a-isis_redist.Po @AMDEP_TRUE@@am__fastdepCC_FALSE@ $(AM_V_CC)source='isisd/isis_redist.c' object='isisd/libfabric_a-isis_redist.o' libtool=no @AMDEPBACKSLASH@ @AMDEP_TRUE@@am__fastdepCC_FALSE@ DEPDIR=$(DEPDIR) $(CCDEPMODE) $(depcomp) @AMDEPBACKSLASH@ @am__fastdepCC_FALSE@ $(AM_V_CC@am__nodep@)$(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(isisd_libfabric_a_CPPFLAGS) $(CPPFLAGS) $(AM_CFLAGS) $(CFLAGS) -c -o isisd/libfabric_a-isis_redist.o `test -f 'isisd/isis_redist.c' || echo '$(srcdir)/'`isisd/isis_redist.c isisd/libfabric_a-isis_redist.obj: isisd/isis_redist.c @am__fastdepCC_TRUE@ $(AM_V_CC)$(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(isisd_libfabric_a_CPPFLAGS) $(CPPFLAGS) $(AM_CFLAGS) $(CFLAGS) -MT isisd/libfabric_a-isis_redist.obj -MD -MP -MF isisd/$(DEPDIR)/libfabric_a-isis_redist.Tpo -c -o isisd/libfabric_a-isis_redist.obj `if test -f 'isisd/isis_redist.c'; then $(CYGPATH_W) 'isisd/isis_redist.c'; else $(CYGPATH_W) '$(srcdir)/isisd/isis_redist.c'; fi` @am__fastdepCC_TRUE@ $(AM_V_at)$(am__mv) isisd/$(DEPDIR)/libfabric_a-isis_redist.Tpo isisd/$(DEPDIR)/libfabric_a-isis_redist.Po @AMDEP_TRUE@@am__fastdepCC_FALSE@ $(AM_V_CC)source='isisd/isis_redist.c' object='isisd/libfabric_a-isis_redist.obj' libtool=no @AMDEPBACKSLASH@ @AMDEP_TRUE@@am__fastdepCC_FALSE@ DEPDIR=$(DEPDIR) $(CCDEPMODE) $(depcomp) @AMDEPBACKSLASH@ @am__fastdepCC_FALSE@ $(AM_V_CC@am__nodep@)$(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(isisd_libfabric_a_CPPFLAGS) $(CPPFLAGS) $(AM_CFLAGS) $(CFLAGS) -c -o isisd/libfabric_a-isis_redist.obj `if test -f 'isisd/isis_redist.c'; then $(CYGPATH_W) 'isisd/isis_redist.c'; else $(CYGPATH_W) '$(srcdir)/isisd/isis_redist.c'; fi` isisd/libfabric_a-isis_route.o: isisd/isis_route.c @am__fastdepCC_TRUE@ $(AM_V_CC)$(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(isisd_libfabric_a_CPPFLAGS) $(CPPFLAGS) $(AM_CFLAGS) $(CFLAGS) -MT isisd/libfabric_a-isis_route.o -MD -MP -MF isisd/$(DEPDIR)/libfabric_a-isis_route.Tpo -c -o isisd/libfabric_a-isis_route.o `test -f 'isisd/isis_route.c' || echo '$(srcdir)/'`isisd/isis_route.c @am__fastdepCC_TRUE@ $(AM_V_at)$(am__mv) isisd/$(DEPDIR)/libfabric_a-isis_route.Tpo isisd/$(DEPDIR)/libfabric_a-isis_route.Po @AMDEP_TRUE@@am__fastdepCC_FALSE@ $(AM_V_CC)source='isisd/isis_route.c' object='isisd/libfabric_a-isis_route.o' libtool=no @AMDEPBACKSLASH@ @AMDEP_TRUE@@am__fastdepCC_FALSE@ DEPDIR=$(DEPDIR) $(CCDEPMODE) $(depcomp) @AMDEPBACKSLASH@ @am__fastdepCC_FALSE@ $(AM_V_CC@am__nodep@)$(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(isisd_libfabric_a_CPPFLAGS) $(CPPFLAGS) $(AM_CFLAGS) $(CFLAGS) -c -o isisd/libfabric_a-isis_route.o `test -f 'isisd/isis_route.c' || echo '$(srcdir)/'`isisd/isis_route.c isisd/libfabric_a-isis_route.obj: isisd/isis_route.c @am__fastdepCC_TRUE@ $(AM_V_CC)$(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(isisd_libfabric_a_CPPFLAGS) $(CPPFLAGS) $(AM_CFLAGS) $(CFLAGS) -MT isisd/libfabric_a-isis_route.obj -MD -MP -MF isisd/$(DEPDIR)/libfabric_a-isis_route.Tpo -c -o isisd/libfabric_a-isis_route.obj `if test -f 'isisd/isis_route.c'; then $(CYGPATH_W) 'isisd/isis_route.c'; else $(CYGPATH_W) '$(srcdir)/isisd/isis_route.c'; fi` @am__fastdepCC_TRUE@ $(AM_V_at)$(am__mv) isisd/$(DEPDIR)/libfabric_a-isis_route.Tpo isisd/$(DEPDIR)/libfabric_a-isis_route.Po @AMDEP_TRUE@@am__fastdepCC_FALSE@ $(AM_V_CC)source='isisd/isis_route.c' object='isisd/libfabric_a-isis_route.obj' libtool=no @AMDEPBACKSLASH@ @AMDEP_TRUE@@am__fastdepCC_FALSE@ DEPDIR=$(DEPDIR) $(CCDEPMODE) $(depcomp) @AMDEPBACKSLASH@ @am__fastdepCC_FALSE@ $(AM_V_CC@am__nodep@)$(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(isisd_libfabric_a_CPPFLAGS) $(CPPFLAGS) $(AM_CFLAGS) $(CFLAGS) -c -o isisd/libfabric_a-isis_route.obj `if test -f 'isisd/isis_route.c'; then $(CYGPATH_W) 'isisd/isis_route.c'; else $(CYGPATH_W) '$(srcdir)/isisd/isis_route.c'; fi` isisd/libfabric_a-isis_routemap.o: isisd/isis_routemap.c @am__fastdepCC_TRUE@ $(AM_V_CC)$(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(isisd_libfabric_a_CPPFLAGS) $(CPPFLAGS) $(AM_CFLAGS) $(CFLAGS) -MT isisd/libfabric_a-isis_routemap.o -MD -MP -MF isisd/$(DEPDIR)/libfabric_a-isis_routemap.Tpo -c -o isisd/libfabric_a-isis_routemap.o `test -f 'isisd/isis_routemap.c' || echo '$(srcdir)/'`isisd/isis_routemap.c @am__fastdepCC_TRUE@ $(AM_V_at)$(am__mv) isisd/$(DEPDIR)/libfabric_a-isis_routemap.Tpo isisd/$(DEPDIR)/libfabric_a-isis_routemap.Po @AMDEP_TRUE@@am__fastdepCC_FALSE@ $(AM_V_CC)source='isisd/isis_routemap.c' object='isisd/libfabric_a-isis_routemap.o' libtool=no @AMDEPBACKSLASH@ @AMDEP_TRUE@@am__fastdepCC_FALSE@ DEPDIR=$(DEPDIR) $(CCDEPMODE) $(depcomp) @AMDEPBACKSLASH@ @am__fastdepCC_FALSE@ $(AM_V_CC@am__nodep@)$(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(isisd_libfabric_a_CPPFLAGS) $(CPPFLAGS) $(AM_CFLAGS) $(CFLAGS) -c -o isisd/libfabric_a-isis_routemap.o `test -f 'isisd/isis_routemap.c' || echo '$(srcdir)/'`isisd/isis_routemap.c isisd/libfabric_a-isis_routemap.obj: isisd/isis_routemap.c @am__fastdepCC_TRUE@ $(AM_V_CC)$(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(isisd_libfabric_a_CPPFLAGS) $(CPPFLAGS) $(AM_CFLAGS) $(CFLAGS) -MT isisd/libfabric_a-isis_routemap.obj -MD -MP -MF isisd/$(DEPDIR)/libfabric_a-isis_routemap.Tpo -c -o isisd/libfabric_a-isis_routemap.obj `if test -f 'isisd/isis_routemap.c'; then $(CYGPATH_W) 'isisd/isis_routemap.c'; else $(CYGPATH_W) '$(srcdir)/isisd/isis_routemap.c'; fi` @am__fastdepCC_TRUE@ $(AM_V_at)$(am__mv) isisd/$(DEPDIR)/libfabric_a-isis_routemap.Tpo isisd/$(DEPDIR)/libfabric_a-isis_routemap.Po @AMDEP_TRUE@@am__fastdepCC_FALSE@ $(AM_V_CC)source='isisd/isis_routemap.c' object='isisd/libfabric_a-isis_routemap.obj' libtool=no @AMDEPBACKSLASH@ @AMDEP_TRUE@@am__fastdepCC_FALSE@ DEPDIR=$(DEPDIR) $(CCDEPMODE) $(depcomp) @AMDEPBACKSLASH@ @am__fastdepCC_FALSE@ $(AM_V_CC@am__nodep@)$(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(isisd_libfabric_a_CPPFLAGS) $(CPPFLAGS) $(AM_CFLAGS) $(CFLAGS) -c -o isisd/libfabric_a-isis_routemap.obj `if test -f 'isisd/isis_routemap.c'; then $(CYGPATH_W) 'isisd/isis_routemap.c'; else $(CYGPATH_W) '$(srcdir)/isisd/isis_routemap.c'; fi` isisd/libfabric_a-isis_spf.o: isisd/isis_spf.c @am__fastdepCC_TRUE@ $(AM_V_CC)$(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(isisd_libfabric_a_CPPFLAGS) $(CPPFLAGS) $(AM_CFLAGS) $(CFLAGS) -MT isisd/libfabric_a-isis_spf.o -MD -MP -MF isisd/$(DEPDIR)/libfabric_a-isis_spf.Tpo -c -o isisd/libfabric_a-isis_spf.o `test -f 'isisd/isis_spf.c' || echo '$(srcdir)/'`isisd/isis_spf.c @am__fastdepCC_TRUE@ $(AM_V_at)$(am__mv) isisd/$(DEPDIR)/libfabric_a-isis_spf.Tpo isisd/$(DEPDIR)/libfabric_a-isis_spf.Po @AMDEP_TRUE@@am__fastdepCC_FALSE@ $(AM_V_CC)source='isisd/isis_spf.c' object='isisd/libfabric_a-isis_spf.o' libtool=no @AMDEPBACKSLASH@ @AMDEP_TRUE@@am__fastdepCC_FALSE@ DEPDIR=$(DEPDIR) $(CCDEPMODE) $(depcomp) @AMDEPBACKSLASH@ @am__fastdepCC_FALSE@ $(AM_V_CC@am__nodep@)$(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(isisd_libfabric_a_CPPFLAGS) $(CPPFLAGS) $(AM_CFLAGS) $(CFLAGS) -c -o isisd/libfabric_a-isis_spf.o `test -f 'isisd/isis_spf.c' || echo '$(srcdir)/'`isisd/isis_spf.c isisd/libfabric_a-isis_spf.obj: isisd/isis_spf.c @am__fastdepCC_TRUE@ $(AM_V_CC)$(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(isisd_libfabric_a_CPPFLAGS) $(CPPFLAGS) $(AM_CFLAGS) $(CFLAGS) -MT isisd/libfabric_a-isis_spf.obj -MD -MP -MF isisd/$(DEPDIR)/libfabric_a-isis_spf.Tpo -c -o isisd/libfabric_a-isis_spf.obj `if test -f 'isisd/isis_spf.c'; then $(CYGPATH_W) 'isisd/isis_spf.c'; else $(CYGPATH_W) '$(srcdir)/isisd/isis_spf.c'; fi` @am__fastdepCC_TRUE@ $(AM_V_at)$(am__mv) isisd/$(DEPDIR)/libfabric_a-isis_spf.Tpo isisd/$(DEPDIR)/libfabric_a-isis_spf.Po @AMDEP_TRUE@@am__fastdepCC_FALSE@ $(AM_V_CC)source='isisd/isis_spf.c' object='isisd/libfabric_a-isis_spf.obj' libtool=no @AMDEPBACKSLASH@ @AMDEP_TRUE@@am__fastdepCC_FALSE@ DEPDIR=$(DEPDIR) $(CCDEPMODE) $(depcomp) @AMDEPBACKSLASH@ @am__fastdepCC_FALSE@ $(AM_V_CC@am__nodep@)$(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(isisd_libfabric_a_CPPFLAGS) $(CPPFLAGS) $(AM_CFLAGS) $(CFLAGS) -c -o isisd/libfabric_a-isis_spf.obj `if test -f 'isisd/isis_spf.c'; then $(CYGPATH_W) 'isisd/isis_spf.c'; else $(CYGPATH_W) '$(srcdir)/isisd/isis_spf.c'; fi` isisd/libfabric_a-isis_te.o: isisd/isis_te.c @am__fastdepCC_TRUE@ $(AM_V_CC)$(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(isisd_libfabric_a_CPPFLAGS) $(CPPFLAGS) $(AM_CFLAGS) $(CFLAGS) -MT isisd/libfabric_a-isis_te.o -MD -MP -MF isisd/$(DEPDIR)/libfabric_a-isis_te.Tpo -c -o isisd/libfabric_a-isis_te.o `test -f 'isisd/isis_te.c' || echo '$(srcdir)/'`isisd/isis_te.c @am__fastdepCC_TRUE@ $(AM_V_at)$(am__mv) isisd/$(DEPDIR)/libfabric_a-isis_te.Tpo isisd/$(DEPDIR)/libfabric_a-isis_te.Po @AMDEP_TRUE@@am__fastdepCC_FALSE@ $(AM_V_CC)source='isisd/isis_te.c' object='isisd/libfabric_a-isis_te.o' libtool=no @AMDEPBACKSLASH@ @AMDEP_TRUE@@am__fastdepCC_FALSE@ DEPDIR=$(DEPDIR) $(CCDEPMODE) $(depcomp) @AMDEPBACKSLASH@ @am__fastdepCC_FALSE@ $(AM_V_CC@am__nodep@)$(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(isisd_libfabric_a_CPPFLAGS) $(CPPFLAGS) $(AM_CFLAGS) $(CFLAGS) -c -o isisd/libfabric_a-isis_te.o `test -f 'isisd/isis_te.c' || echo '$(srcdir)/'`isisd/isis_te.c isisd/libfabric_a-isis_te.obj: isisd/isis_te.c @am__fastdepCC_TRUE@ $(AM_V_CC)$(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(isisd_libfabric_a_CPPFLAGS) $(CPPFLAGS) $(AM_CFLAGS) $(CFLAGS) -MT isisd/libfabric_a-isis_te.obj -MD -MP -MF isisd/$(DEPDIR)/libfabric_a-isis_te.Tpo -c -o isisd/libfabric_a-isis_te.obj `if test -f 'isisd/isis_te.c'; then $(CYGPATH_W) 'isisd/isis_te.c'; else $(CYGPATH_W) '$(srcdir)/isisd/isis_te.c'; fi` @am__fastdepCC_TRUE@ $(AM_V_at)$(am__mv) isisd/$(DEPDIR)/libfabric_a-isis_te.Tpo isisd/$(DEPDIR)/libfabric_a-isis_te.Po @AMDEP_TRUE@@am__fastdepCC_FALSE@ $(AM_V_CC)source='isisd/isis_te.c' object='isisd/libfabric_a-isis_te.obj' libtool=no @AMDEPBACKSLASH@ @AMDEP_TRUE@@am__fastdepCC_FALSE@ DEPDIR=$(DEPDIR) $(CCDEPMODE) $(depcomp) @AMDEPBACKSLASH@ @am__fastdepCC_FALSE@ $(AM_V_CC@am__nodep@)$(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(isisd_libfabric_a_CPPFLAGS) $(CPPFLAGS) $(AM_CFLAGS) $(CFLAGS) -c -o isisd/libfabric_a-isis_te.obj `if test -f 'isisd/isis_te.c'; then $(CYGPATH_W) 'isisd/isis_te.c'; else $(CYGPATH_W) '$(srcdir)/isisd/isis_te.c'; fi` isisd/libfabric_a-isis_tlvs.o: isisd/isis_tlvs.c @am__fastdepCC_TRUE@ $(AM_V_CC)$(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(isisd_libfabric_a_CPPFLAGS) $(CPPFLAGS) $(AM_CFLAGS) $(CFLAGS) -MT isisd/libfabric_a-isis_tlvs.o -MD -MP -MF isisd/$(DEPDIR)/libfabric_a-isis_tlvs.Tpo -c -o isisd/libfabric_a-isis_tlvs.o `test -f 'isisd/isis_tlvs.c' || echo '$(srcdir)/'`isisd/isis_tlvs.c @am__fastdepCC_TRUE@ $(AM_V_at)$(am__mv) isisd/$(DEPDIR)/libfabric_a-isis_tlvs.Tpo isisd/$(DEPDIR)/libfabric_a-isis_tlvs.Po @AMDEP_TRUE@@am__fastdepCC_FALSE@ $(AM_V_CC)source='isisd/isis_tlvs.c' object='isisd/libfabric_a-isis_tlvs.o' libtool=no @AMDEPBACKSLASH@ @AMDEP_TRUE@@am__fastdepCC_FALSE@ DEPDIR=$(DEPDIR) $(CCDEPMODE) $(depcomp) @AMDEPBACKSLASH@ @am__fastdepCC_FALSE@ $(AM_V_CC@am__nodep@)$(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(isisd_libfabric_a_CPPFLAGS) $(CPPFLAGS) $(AM_CFLAGS) $(CFLAGS) -c -o isisd/libfabric_a-isis_tlvs.o `test -f 'isisd/isis_tlvs.c' || echo '$(srcdir)/'`isisd/isis_tlvs.c isisd/libfabric_a-isis_tlvs.obj: isisd/isis_tlvs.c @am__fastdepCC_TRUE@ $(AM_V_CC)$(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(isisd_libfabric_a_CPPFLAGS) $(CPPFLAGS) $(AM_CFLAGS) $(CFLAGS) -MT isisd/libfabric_a-isis_tlvs.obj -MD -MP -MF isisd/$(DEPDIR)/libfabric_a-isis_tlvs.Tpo -c -o isisd/libfabric_a-isis_tlvs.obj `if test -f 'isisd/isis_tlvs.c'; then $(CYGPATH_W) 'isisd/isis_tlvs.c'; else $(CYGPATH_W) '$(srcdir)/isisd/isis_tlvs.c'; fi` @am__fastdepCC_TRUE@ $(AM_V_at)$(am__mv) isisd/$(DEPDIR)/libfabric_a-isis_tlvs.Tpo isisd/$(DEPDIR)/libfabric_a-isis_tlvs.Po @AMDEP_TRUE@@am__fastdepCC_FALSE@ $(AM_V_CC)source='isisd/isis_tlvs.c' object='isisd/libfabric_a-isis_tlvs.obj' libtool=no @AMDEPBACKSLASH@ @AMDEP_TRUE@@am__fastdepCC_FALSE@ DEPDIR=$(DEPDIR) $(CCDEPMODE) $(depcomp) @AMDEPBACKSLASH@ @am__fastdepCC_FALSE@ $(AM_V_CC@am__nodep@)$(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(isisd_libfabric_a_CPPFLAGS) $(CPPFLAGS) $(AM_CFLAGS) $(CFLAGS) -c -o isisd/libfabric_a-isis_tlvs.obj `if test -f 'isisd/isis_tlvs.c'; then $(CYGPATH_W) 'isisd/isis_tlvs.c'; else $(CYGPATH_W) '$(srcdir)/isisd/isis_tlvs.c'; fi` isisd/libfabric_a-isis_tx_queue.o: isisd/isis_tx_queue.c @am__fastdepCC_TRUE@ $(AM_V_CC)$(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(isisd_libfabric_a_CPPFLAGS) $(CPPFLAGS) $(AM_CFLAGS) $(CFLAGS) -MT isisd/libfabric_a-isis_tx_queue.o -MD -MP -MF isisd/$(DEPDIR)/libfabric_a-isis_tx_queue.Tpo -c -o isisd/libfabric_a-isis_tx_queue.o `test -f 'isisd/isis_tx_queue.c' || echo '$(srcdir)/'`isisd/isis_tx_queue.c @am__fastdepCC_TRUE@ $(AM_V_at)$(am__mv) isisd/$(DEPDIR)/libfabric_a-isis_tx_queue.Tpo isisd/$(DEPDIR)/libfabric_a-isis_tx_queue.Po @AMDEP_TRUE@@am__fastdepCC_FALSE@ $(AM_V_CC)source='isisd/isis_tx_queue.c' object='isisd/libfabric_a-isis_tx_queue.o' libtool=no @AMDEPBACKSLASH@ @AMDEP_TRUE@@am__fastdepCC_FALSE@ DEPDIR=$(DEPDIR) $(CCDEPMODE) $(depcomp) @AMDEPBACKSLASH@ @am__fastdepCC_FALSE@ $(AM_V_CC@am__nodep@)$(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(isisd_libfabric_a_CPPFLAGS) $(CPPFLAGS) $(AM_CFLAGS) $(CFLAGS) -c -o isisd/libfabric_a-isis_tx_queue.o `test -f 'isisd/isis_tx_queue.c' || echo '$(srcdir)/'`isisd/isis_tx_queue.c isisd/libfabric_a-isis_tx_queue.obj: isisd/isis_tx_queue.c @am__fastdepCC_TRUE@ $(AM_V_CC)$(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(isisd_libfabric_a_CPPFLAGS) $(CPPFLAGS) $(AM_CFLAGS) $(CFLAGS) -MT isisd/libfabric_a-isis_tx_queue.obj -MD -MP -MF isisd/$(DEPDIR)/libfabric_a-isis_tx_queue.Tpo -c -o isisd/libfabric_a-isis_tx_queue.obj `if test -f 'isisd/isis_tx_queue.c'; then $(CYGPATH_W) 'isisd/isis_tx_queue.c'; else $(CYGPATH_W) '$(srcdir)/isisd/isis_tx_queue.c'; fi` @am__fastdepCC_TRUE@ $(AM_V_at)$(am__mv) isisd/$(DEPDIR)/libfabric_a-isis_tx_queue.Tpo isisd/$(DEPDIR)/libfabric_a-isis_tx_queue.Po @AMDEP_TRUE@@am__fastdepCC_FALSE@ $(AM_V_CC)source='isisd/isis_tx_queue.c' object='isisd/libfabric_a-isis_tx_queue.obj' libtool=no @AMDEPBACKSLASH@ @AMDEP_TRUE@@am__fastdepCC_FALSE@ DEPDIR=$(DEPDIR) $(CCDEPMODE) $(depcomp) @AMDEPBACKSLASH@ @am__fastdepCC_FALSE@ $(AM_V_CC@am__nodep@)$(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(isisd_libfabric_a_CPPFLAGS) $(CPPFLAGS) $(AM_CFLAGS) $(CFLAGS) -c -o isisd/libfabric_a-isis_tx_queue.obj `if test -f 'isisd/isis_tx_queue.c'; then $(CYGPATH_W) 'isisd/isis_tx_queue.c'; else $(CYGPATH_W) '$(srcdir)/isisd/isis_tx_queue.c'; fi` isisd/libfabric_a-isis_zebra.o: isisd/isis_zebra.c @am__fastdepCC_TRUE@ $(AM_V_CC)$(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(isisd_libfabric_a_CPPFLAGS) $(CPPFLAGS) $(AM_CFLAGS) $(CFLAGS) -MT isisd/libfabric_a-isis_zebra.o -MD -MP -MF isisd/$(DEPDIR)/libfabric_a-isis_zebra.Tpo -c -o isisd/libfabric_a-isis_zebra.o `test -f 'isisd/isis_zebra.c' || echo '$(srcdir)/'`isisd/isis_zebra.c @am__fastdepCC_TRUE@ $(AM_V_at)$(am__mv) isisd/$(DEPDIR)/libfabric_a-isis_zebra.Tpo isisd/$(DEPDIR)/libfabric_a-isis_zebra.Po @AMDEP_TRUE@@am__fastdepCC_FALSE@ $(AM_V_CC)source='isisd/isis_zebra.c' object='isisd/libfabric_a-isis_zebra.o' libtool=no @AMDEPBACKSLASH@ @AMDEP_TRUE@@am__fastdepCC_FALSE@ DEPDIR=$(DEPDIR) $(CCDEPMODE) $(depcomp) @AMDEPBACKSLASH@ @am__fastdepCC_FALSE@ $(AM_V_CC@am__nodep@)$(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(isisd_libfabric_a_CPPFLAGS) $(CPPFLAGS) $(AM_CFLAGS) $(CFLAGS) -c -o isisd/libfabric_a-isis_zebra.o `test -f 'isisd/isis_zebra.c' || echo '$(srcdir)/'`isisd/isis_zebra.c isisd/libfabric_a-isis_zebra.obj: isisd/isis_zebra.c @am__fastdepCC_TRUE@ $(AM_V_CC)$(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(isisd_libfabric_a_CPPFLAGS) $(CPPFLAGS) $(AM_CFLAGS) $(CFLAGS) -MT isisd/libfabric_a-isis_zebra.obj -MD -MP -MF isisd/$(DEPDIR)/libfabric_a-isis_zebra.Tpo -c -o isisd/libfabric_a-isis_zebra.obj `if test -f 'isisd/isis_zebra.c'; then $(CYGPATH_W) 'isisd/isis_zebra.c'; else $(CYGPATH_W) '$(srcdir)/isisd/isis_zebra.c'; fi` @am__fastdepCC_TRUE@ $(AM_V_at)$(am__mv) isisd/$(DEPDIR)/libfabric_a-isis_zebra.Tpo isisd/$(DEPDIR)/libfabric_a-isis_zebra.Po @AMDEP_TRUE@@am__fastdepCC_FALSE@ $(AM_V_CC)source='isisd/isis_zebra.c' object='isisd/libfabric_a-isis_zebra.obj' libtool=no @AMDEPBACKSLASH@ @AMDEP_TRUE@@am__fastdepCC_FALSE@ DEPDIR=$(DEPDIR) $(CCDEPMODE) $(depcomp) @AMDEPBACKSLASH@ @am__fastdepCC_FALSE@ $(AM_V_CC@am__nodep@)$(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(isisd_libfabric_a_CPPFLAGS) $(CPPFLAGS) $(AM_CFLAGS) $(CFLAGS) -c -o isisd/libfabric_a-isis_zebra.obj `if test -f 'isisd/isis_zebra.c'; then $(CYGPATH_W) 'isisd/isis_zebra.c'; else $(CYGPATH_W) '$(srcdir)/isisd/isis_zebra.c'; fi` isisd/libfabric_a-isisd.o: isisd/isisd.c @am__fastdepCC_TRUE@ $(AM_V_CC)$(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(isisd_libfabric_a_CPPFLAGS) $(CPPFLAGS) $(AM_CFLAGS) $(CFLAGS) -MT isisd/libfabric_a-isisd.o -MD -MP -MF isisd/$(DEPDIR)/libfabric_a-isisd.Tpo -c -o isisd/libfabric_a-isisd.o `test -f 'isisd/isisd.c' || echo '$(srcdir)/'`isisd/isisd.c @am__fastdepCC_TRUE@ $(AM_V_at)$(am__mv) isisd/$(DEPDIR)/libfabric_a-isisd.Tpo isisd/$(DEPDIR)/libfabric_a-isisd.Po @AMDEP_TRUE@@am__fastdepCC_FALSE@ $(AM_V_CC)source='isisd/isisd.c' object='isisd/libfabric_a-isisd.o' libtool=no @AMDEPBACKSLASH@ @AMDEP_TRUE@@am__fastdepCC_FALSE@ DEPDIR=$(DEPDIR) $(CCDEPMODE) $(depcomp) @AMDEPBACKSLASH@ @am__fastdepCC_FALSE@ $(AM_V_CC@am__nodep@)$(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(isisd_libfabric_a_CPPFLAGS) $(CPPFLAGS) $(AM_CFLAGS) $(CFLAGS) -c -o isisd/libfabric_a-isisd.o `test -f 'isisd/isisd.c' || echo '$(srcdir)/'`isisd/isisd.c isisd/libfabric_a-isisd.obj: isisd/isisd.c @am__fastdepCC_TRUE@ $(AM_V_CC)$(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(isisd_libfabric_a_CPPFLAGS) $(CPPFLAGS) $(AM_CFLAGS) $(CFLAGS) -MT isisd/libfabric_a-isisd.obj -MD -MP -MF isisd/$(DEPDIR)/libfabric_a-isisd.Tpo -c -o isisd/libfabric_a-isisd.obj `if test -f 'isisd/isisd.c'; then $(CYGPATH_W) 'isisd/isisd.c'; else $(CYGPATH_W) '$(srcdir)/isisd/isisd.c'; fi` @am__fastdepCC_TRUE@ $(AM_V_at)$(am__mv) isisd/$(DEPDIR)/libfabric_a-isisd.Tpo isisd/$(DEPDIR)/libfabric_a-isisd.Po @AMDEP_TRUE@@am__fastdepCC_FALSE@ $(AM_V_CC)source='isisd/isisd.c' object='isisd/libfabric_a-isisd.obj' libtool=no @AMDEPBACKSLASH@ @AMDEP_TRUE@@am__fastdepCC_FALSE@ DEPDIR=$(DEPDIR) $(CCDEPMODE) $(depcomp) @AMDEPBACKSLASH@ @am__fastdepCC_FALSE@ $(AM_V_CC@am__nodep@)$(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(isisd_libfabric_a_CPPFLAGS) $(CPPFLAGS) $(AM_CFLAGS) $(CFLAGS) -c -o isisd/libfabric_a-isisd.obj `if test -f 'isisd/isisd.c'; then $(CYGPATH_W) 'isisd/isisd.c'; else $(CYGPATH_W) '$(srcdir)/isisd/isisd.c'; fi` isisd/libfabric_a-iso_checksum.o: isisd/iso_checksum.c @am__fastdepCC_TRUE@ $(AM_V_CC)$(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(isisd_libfabric_a_CPPFLAGS) $(CPPFLAGS) $(AM_CFLAGS) $(CFLAGS) -MT isisd/libfabric_a-iso_checksum.o -MD -MP -MF isisd/$(DEPDIR)/libfabric_a-iso_checksum.Tpo -c -o isisd/libfabric_a-iso_checksum.o `test -f 'isisd/iso_checksum.c' || echo '$(srcdir)/'`isisd/iso_checksum.c @am__fastdepCC_TRUE@ $(AM_V_at)$(am__mv) isisd/$(DEPDIR)/libfabric_a-iso_checksum.Tpo isisd/$(DEPDIR)/libfabric_a-iso_checksum.Po @AMDEP_TRUE@@am__fastdepCC_FALSE@ $(AM_V_CC)source='isisd/iso_checksum.c' object='isisd/libfabric_a-iso_checksum.o' libtool=no @AMDEPBACKSLASH@ @AMDEP_TRUE@@am__fastdepCC_FALSE@ DEPDIR=$(DEPDIR) $(CCDEPMODE) $(depcomp) @AMDEPBACKSLASH@ @am__fastdepCC_FALSE@ $(AM_V_CC@am__nodep@)$(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(isisd_libfabric_a_CPPFLAGS) $(CPPFLAGS) $(AM_CFLAGS) $(CFLAGS) -c -o isisd/libfabric_a-iso_checksum.o `test -f 'isisd/iso_checksum.c' || echo '$(srcdir)/'`isisd/iso_checksum.c isisd/libfabric_a-iso_checksum.obj: isisd/iso_checksum.c @am__fastdepCC_TRUE@ $(AM_V_CC)$(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(isisd_libfabric_a_CPPFLAGS) $(CPPFLAGS) $(AM_CFLAGS) $(CFLAGS) -MT isisd/libfabric_a-iso_checksum.obj -MD -MP -MF isisd/$(DEPDIR)/libfabric_a-iso_checksum.Tpo -c -o isisd/libfabric_a-iso_checksum.obj `if test -f 'isisd/iso_checksum.c'; then $(CYGPATH_W) 'isisd/iso_checksum.c'; else $(CYGPATH_W) '$(srcdir)/isisd/iso_checksum.c'; fi` @am__fastdepCC_TRUE@ $(AM_V_at)$(am__mv) isisd/$(DEPDIR)/libfabric_a-iso_checksum.Tpo isisd/$(DEPDIR)/libfabric_a-iso_checksum.Po @AMDEP_TRUE@@am__fastdepCC_FALSE@ $(AM_V_CC)source='isisd/iso_checksum.c' object='isisd/libfabric_a-iso_checksum.obj' libtool=no @AMDEPBACKSLASH@ @AMDEP_TRUE@@am__fastdepCC_FALSE@ DEPDIR=$(DEPDIR) $(CCDEPMODE) $(depcomp) @AMDEPBACKSLASH@ @am__fastdepCC_FALSE@ $(AM_V_CC@am__nodep@)$(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(isisd_libfabric_a_CPPFLAGS) $(CPPFLAGS) $(AM_CFLAGS) $(CFLAGS) -c -o isisd/libfabric_a-iso_checksum.obj `if test -f 'isisd/iso_checksum.c'; then $(CYGPATH_W) 'isisd/iso_checksum.c'; else $(CYGPATH_W) '$(srcdir)/isisd/iso_checksum.c'; fi` isisd/libfabric_a-fabricd.o: isisd/fabricd.c @am__fastdepCC_TRUE@ $(AM_V_CC)$(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(isisd_libfabric_a_CPPFLAGS) $(CPPFLAGS) $(AM_CFLAGS) $(CFLAGS) -MT isisd/libfabric_a-fabricd.o -MD -MP -MF isisd/$(DEPDIR)/libfabric_a-fabricd.Tpo -c -o isisd/libfabric_a-fabricd.o `test -f 'isisd/fabricd.c' || echo '$(srcdir)/'`isisd/fabricd.c @am__fastdepCC_TRUE@ $(AM_V_at)$(am__mv) isisd/$(DEPDIR)/libfabric_a-fabricd.Tpo isisd/$(DEPDIR)/libfabric_a-fabricd.Po @AMDEP_TRUE@@am__fastdepCC_FALSE@ $(AM_V_CC)source='isisd/fabricd.c' object='isisd/libfabric_a-fabricd.o' libtool=no @AMDEPBACKSLASH@ @AMDEP_TRUE@@am__fastdepCC_FALSE@ DEPDIR=$(DEPDIR) $(CCDEPMODE) $(depcomp) @AMDEPBACKSLASH@ @am__fastdepCC_FALSE@ $(AM_V_CC@am__nodep@)$(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(isisd_libfabric_a_CPPFLAGS) $(CPPFLAGS) $(AM_CFLAGS) $(CFLAGS) -c -o isisd/libfabric_a-fabricd.o `test -f 'isisd/fabricd.c' || echo '$(srcdir)/'`isisd/fabricd.c isisd/libfabric_a-fabricd.obj: isisd/fabricd.c @am__fastdepCC_TRUE@ $(AM_V_CC)$(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(isisd_libfabric_a_CPPFLAGS) $(CPPFLAGS) $(AM_CFLAGS) $(CFLAGS) -MT isisd/libfabric_a-fabricd.obj -MD -MP -MF isisd/$(DEPDIR)/libfabric_a-fabricd.Tpo -c -o isisd/libfabric_a-fabricd.obj `if test -f 'isisd/fabricd.c'; then $(CYGPATH_W) 'isisd/fabricd.c'; else $(CYGPATH_W) '$(srcdir)/isisd/fabricd.c'; fi` @am__fastdepCC_TRUE@ $(AM_V_at)$(am__mv) isisd/$(DEPDIR)/libfabric_a-fabricd.Tpo isisd/$(DEPDIR)/libfabric_a-fabricd.Po @AMDEP_TRUE@@am__fastdepCC_FALSE@ $(AM_V_CC)source='isisd/fabricd.c' object='isisd/libfabric_a-fabricd.obj' libtool=no @AMDEPBACKSLASH@ @AMDEP_TRUE@@am__fastdepCC_FALSE@ DEPDIR=$(DEPDIR) $(CCDEPMODE) $(depcomp) @AMDEPBACKSLASH@ @am__fastdepCC_FALSE@ $(AM_V_CC@am__nodep@)$(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(isisd_libfabric_a_CPPFLAGS) $(CPPFLAGS) $(AM_CFLAGS) $(CFLAGS) -c -o isisd/libfabric_a-fabricd.obj `if test -f 'isisd/fabricd.c'; then $(CYGPATH_W) 'isisd/fabricd.c'; else $(CYGPATH_W) '$(srcdir)/isisd/fabricd.c'; fi` isisd/libfabric_a-isis_vty_fabricd.o: isisd/isis_vty_fabricd.c @am__fastdepCC_TRUE@ $(AM_V_CC)$(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(isisd_libfabric_a_CPPFLAGS) $(CPPFLAGS) $(AM_CFLAGS) $(CFLAGS) -MT isisd/libfabric_a-isis_vty_fabricd.o -MD -MP -MF isisd/$(DEPDIR)/libfabric_a-isis_vty_fabricd.Tpo -c -o isisd/libfabric_a-isis_vty_fabricd.o `test -f 'isisd/isis_vty_fabricd.c' || echo '$(srcdir)/'`isisd/isis_vty_fabricd.c @am__fastdepCC_TRUE@ $(AM_V_at)$(am__mv) isisd/$(DEPDIR)/libfabric_a-isis_vty_fabricd.Tpo isisd/$(DEPDIR)/libfabric_a-isis_vty_fabricd.Po @AMDEP_TRUE@@am__fastdepCC_FALSE@ $(AM_V_CC)source='isisd/isis_vty_fabricd.c' object='isisd/libfabric_a-isis_vty_fabricd.o' libtool=no @AMDEPBACKSLASH@ @AMDEP_TRUE@@am__fastdepCC_FALSE@ DEPDIR=$(DEPDIR) $(CCDEPMODE) $(depcomp) @AMDEPBACKSLASH@ @am__fastdepCC_FALSE@ $(AM_V_CC@am__nodep@)$(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(isisd_libfabric_a_CPPFLAGS) $(CPPFLAGS) $(AM_CFLAGS) $(CFLAGS) -c -o isisd/libfabric_a-isis_vty_fabricd.o `test -f 'isisd/isis_vty_fabricd.c' || echo '$(srcdir)/'`isisd/isis_vty_fabricd.c isisd/libfabric_a-isis_vty_fabricd.obj: isisd/isis_vty_fabricd.c @am__fastdepCC_TRUE@ $(AM_V_CC)$(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(isisd_libfabric_a_CPPFLAGS) $(CPPFLAGS) $(AM_CFLAGS) $(CFLAGS) -MT isisd/libfabric_a-isis_vty_fabricd.obj -MD -MP -MF isisd/$(DEPDIR)/libfabric_a-isis_vty_fabricd.Tpo -c -o isisd/libfabric_a-isis_vty_fabricd.obj `if test -f 'isisd/isis_vty_fabricd.c'; then $(CYGPATH_W) 'isisd/isis_vty_fabricd.c'; else $(CYGPATH_W) '$(srcdir)/isisd/isis_vty_fabricd.c'; fi` @am__fastdepCC_TRUE@ $(AM_V_at)$(am__mv) isisd/$(DEPDIR)/libfabric_a-isis_vty_fabricd.Tpo isisd/$(DEPDIR)/libfabric_a-isis_vty_fabricd.Po @AMDEP_TRUE@@am__fastdepCC_FALSE@ $(AM_V_CC)source='isisd/isis_vty_fabricd.c' object='isisd/libfabric_a-isis_vty_fabricd.obj' libtool=no @AMDEPBACKSLASH@ @AMDEP_TRUE@@am__fastdepCC_FALSE@ DEPDIR=$(DEPDIR) $(CCDEPMODE) $(depcomp) @AMDEPBACKSLASH@ @am__fastdepCC_FALSE@ $(AM_V_CC@am__nodep@)$(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(isisd_libfabric_a_CPPFLAGS) $(CPPFLAGS) $(AM_CFLAGS) $(CFLAGS) -c -o isisd/libfabric_a-isis_vty_fabricd.obj `if test -f 'isisd/isis_vty_fabricd.c'; then $(CYGPATH_W) 'isisd/isis_vty_fabricd.c'; else $(CYGPATH_W) '$(srcdir)/isisd/isis_vty_fabricd.c'; fi` bgpd/bgpd_rpki_la-bgp_rpki.lo: bgpd/bgp_rpki.c @am__fastdepCC_TRUE@ $(AM_V_CC)$(LIBTOOL) $(AM_V_lt) --tag=CC $(AM_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=compile $(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(AM_CPPFLAGS) $(CPPFLAGS) $(bgpd_bgpd_rpki_la_CFLAGS) $(CFLAGS) -MT bgpd/bgpd_rpki_la-bgp_rpki.lo -MD -MP -MF bgpd/$(DEPDIR)/bgpd_rpki_la-bgp_rpki.Tpo -c -o bgpd/bgpd_rpki_la-bgp_rpki.lo `test -f 'bgpd/bgp_rpki.c' || echo '$(srcdir)/'`bgpd/bgp_rpki.c @am__fastdepCC_TRUE@ $(AM_V_at)$(am__mv) bgpd/$(DEPDIR)/bgpd_rpki_la-bgp_rpki.Tpo bgpd/$(DEPDIR)/bgpd_rpki_la-bgp_rpki.Plo @AMDEP_TRUE@@am__fastdepCC_FALSE@ $(AM_V_CC)source='bgpd/bgp_rpki.c' object='bgpd/bgpd_rpki_la-bgp_rpki.lo' libtool=yes @AMDEPBACKSLASH@ @AMDEP_TRUE@@am__fastdepCC_FALSE@ DEPDIR=$(DEPDIR) $(CCDEPMODE) $(depcomp) @AMDEPBACKSLASH@ @am__fastdepCC_FALSE@ $(AM_V_CC@am__nodep@)$(LIBTOOL) $(AM_V_lt) --tag=CC $(AM_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=compile $(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(AM_CPPFLAGS) $(CPPFLAGS) $(bgpd_bgpd_rpki_la_CFLAGS) $(CFLAGS) -c -o bgpd/bgpd_rpki_la-bgp_rpki.lo `test -f 'bgpd/bgp_rpki.c' || echo '$(srcdir)/'`bgpd/bgp_rpki.c bgpd/bgpd_snmp_la-bgp_snmp.lo: bgpd/bgp_snmp.c @am__fastdepCC_TRUE@ $(AM_V_CC)$(LIBTOOL) $(AM_V_lt) --tag=CC $(AM_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=compile $(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(AM_CPPFLAGS) $(CPPFLAGS) $(bgpd_bgpd_snmp_la_CFLAGS) $(CFLAGS) -MT bgpd/bgpd_snmp_la-bgp_snmp.lo -MD -MP -MF bgpd/$(DEPDIR)/bgpd_snmp_la-bgp_snmp.Tpo -c -o bgpd/bgpd_snmp_la-bgp_snmp.lo `test -f 'bgpd/bgp_snmp.c' || echo '$(srcdir)/'`bgpd/bgp_snmp.c @am__fastdepCC_TRUE@ $(AM_V_at)$(am__mv) bgpd/$(DEPDIR)/bgpd_snmp_la-bgp_snmp.Tpo bgpd/$(DEPDIR)/bgpd_snmp_la-bgp_snmp.Plo @AMDEP_TRUE@@am__fastdepCC_FALSE@ $(AM_V_CC)source='bgpd/bgp_snmp.c' object='bgpd/bgpd_snmp_la-bgp_snmp.lo' libtool=yes @AMDEPBACKSLASH@ @AMDEP_TRUE@@am__fastdepCC_FALSE@ DEPDIR=$(DEPDIR) $(CCDEPMODE) $(depcomp) @AMDEPBACKSLASH@ @am__fastdepCC_FALSE@ $(AM_V_CC@am__nodep@)$(LIBTOOL) $(AM_V_lt) --tag=CC $(AM_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=compile $(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(AM_CPPFLAGS) $(CPPFLAGS) $(bgpd_bgpd_snmp_la_CFLAGS) $(CFLAGS) -c -o bgpd/bgpd_snmp_la-bgp_snmp.lo `test -f 'bgpd/bgp_snmp.c' || echo '$(srcdir)/'`bgpd/bgp_snmp.c fpm/libfrrfpm_pb_la-fpm_pb.lo: fpm/fpm_pb.c @am__fastdepCC_TRUE@ $(AM_V_CC)$(LIBTOOL) $(AM_V_lt) --tag=CC $(AM_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=compile $(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(fpm_libfrrfpm_pb_la_CPPFLAGS) $(CPPFLAGS) $(AM_CFLAGS) $(CFLAGS) -MT fpm/libfrrfpm_pb_la-fpm_pb.lo -MD -MP -MF fpm/$(DEPDIR)/libfrrfpm_pb_la-fpm_pb.Tpo -c -o fpm/libfrrfpm_pb_la-fpm_pb.lo `test -f 'fpm/fpm_pb.c' || echo '$(srcdir)/'`fpm/fpm_pb.c @am__fastdepCC_TRUE@ $(AM_V_at)$(am__mv) fpm/$(DEPDIR)/libfrrfpm_pb_la-fpm_pb.Tpo fpm/$(DEPDIR)/libfrrfpm_pb_la-fpm_pb.Plo @AMDEP_TRUE@@am__fastdepCC_FALSE@ $(AM_V_CC)source='fpm/fpm_pb.c' object='fpm/libfrrfpm_pb_la-fpm_pb.lo' libtool=yes @AMDEPBACKSLASH@ @AMDEP_TRUE@@am__fastdepCC_FALSE@ DEPDIR=$(DEPDIR) $(CCDEPMODE) $(depcomp) @AMDEPBACKSLASH@ @am__fastdepCC_FALSE@ $(AM_V_CC@am__nodep@)$(LIBTOOL) $(AM_V_lt) --tag=CC $(AM_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=compile $(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(fpm_libfrrfpm_pb_la_CPPFLAGS) $(CPPFLAGS) $(AM_CFLAGS) $(CFLAGS) -c -o fpm/libfrrfpm_pb_la-fpm_pb.lo `test -f 'fpm/fpm_pb.c' || echo '$(srcdir)/'`fpm/fpm_pb.c fpm/libfrrfpm_pb_la-fpm.pb-c.lo: fpm/fpm.pb-c.c @am__fastdepCC_TRUE@ $(AM_V_CC)$(LIBTOOL) $(AM_V_lt) --tag=CC $(AM_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=compile $(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(fpm_libfrrfpm_pb_la_CPPFLAGS) $(CPPFLAGS) $(AM_CFLAGS) $(CFLAGS) -MT fpm/libfrrfpm_pb_la-fpm.pb-c.lo -MD -MP -MF fpm/$(DEPDIR)/libfrrfpm_pb_la-fpm.pb-c.Tpo -c -o fpm/libfrrfpm_pb_la-fpm.pb-c.lo `test -f 'fpm/fpm.pb-c.c' || echo '$(srcdir)/'`fpm/fpm.pb-c.c @am__fastdepCC_TRUE@ $(AM_V_at)$(am__mv) fpm/$(DEPDIR)/libfrrfpm_pb_la-fpm.pb-c.Tpo fpm/$(DEPDIR)/libfrrfpm_pb_la-fpm.pb-c.Plo @AMDEP_TRUE@@am__fastdepCC_FALSE@ $(AM_V_CC)source='fpm/fpm.pb-c.c' object='fpm/libfrrfpm_pb_la-fpm.pb-c.lo' libtool=yes @AMDEPBACKSLASH@ @AMDEP_TRUE@@am__fastdepCC_FALSE@ DEPDIR=$(DEPDIR) $(CCDEPMODE) $(depcomp) @AMDEPBACKSLASH@ @am__fastdepCC_FALSE@ $(AM_V_CC@am__nodep@)$(LIBTOOL) $(AM_V_lt) --tag=CC $(AM_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=compile $(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(fpm_libfrrfpm_pb_la_CPPFLAGS) $(CPPFLAGS) $(AM_CFLAGS) $(CFLAGS) -c -o fpm/libfrrfpm_pb_la-fpm.pb-c.lo `test -f 'fpm/fpm.pb-c.c' || echo '$(srcdir)/'`fpm/fpm.pb-c.c lib/confd_la-northbound_confd.lo: lib/northbound_confd.c @am__fastdepCC_TRUE@ $(AM_V_CC)$(LIBTOOL) $(AM_V_lt) --tag=CC $(AM_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=compile $(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(AM_CPPFLAGS) $(CPPFLAGS) $(lib_confd_la_CFLAGS) $(CFLAGS) -MT lib/confd_la-northbound_confd.lo -MD -MP -MF lib/$(DEPDIR)/confd_la-northbound_confd.Tpo -c -o lib/confd_la-northbound_confd.lo `test -f 'lib/northbound_confd.c' || echo '$(srcdir)/'`lib/northbound_confd.c @am__fastdepCC_TRUE@ $(AM_V_at)$(am__mv) lib/$(DEPDIR)/confd_la-northbound_confd.Tpo lib/$(DEPDIR)/confd_la-northbound_confd.Plo @AMDEP_TRUE@@am__fastdepCC_FALSE@ $(AM_V_CC)source='lib/northbound_confd.c' object='lib/confd_la-northbound_confd.lo' libtool=yes @AMDEPBACKSLASH@ @AMDEP_TRUE@@am__fastdepCC_FALSE@ DEPDIR=$(DEPDIR) $(CCDEPMODE) $(depcomp) @AMDEPBACKSLASH@ @am__fastdepCC_FALSE@ $(AM_V_CC@am__nodep@)$(LIBTOOL) $(AM_V_lt) --tag=CC $(AM_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=compile $(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(AM_CPPFLAGS) $(CPPFLAGS) $(lib_confd_la_CFLAGS) $(CFLAGS) -c -o lib/confd_la-northbound_confd.lo `test -f 'lib/northbound_confd.c' || echo '$(srcdir)/'`lib/northbound_confd.c lib/libfrrcares_la-resolver.lo: lib/resolver.c @am__fastdepCC_TRUE@ $(AM_V_CC)$(LIBTOOL) $(AM_V_lt) --tag=CC $(AM_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=compile $(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(AM_CPPFLAGS) $(CPPFLAGS) $(lib_libfrrcares_la_CFLAGS) $(CFLAGS) -MT lib/libfrrcares_la-resolver.lo -MD -MP -MF lib/$(DEPDIR)/libfrrcares_la-resolver.Tpo -c -o lib/libfrrcares_la-resolver.lo `test -f 'lib/resolver.c' || echo '$(srcdir)/'`lib/resolver.c @am__fastdepCC_TRUE@ $(AM_V_at)$(am__mv) lib/$(DEPDIR)/libfrrcares_la-resolver.Tpo lib/$(DEPDIR)/libfrrcares_la-resolver.Plo @AMDEP_TRUE@@am__fastdepCC_FALSE@ $(AM_V_CC)source='lib/resolver.c' object='lib/libfrrcares_la-resolver.lo' libtool=yes @AMDEPBACKSLASH@ @AMDEP_TRUE@@am__fastdepCC_FALSE@ DEPDIR=$(DEPDIR) $(CCDEPMODE) $(depcomp) @AMDEPBACKSLASH@ @am__fastdepCC_FALSE@ $(AM_V_CC@am__nodep@)$(LIBTOOL) $(AM_V_lt) --tag=CC $(AM_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=compile $(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(AM_CPPFLAGS) $(CPPFLAGS) $(lib_libfrrcares_la_CFLAGS) $(CFLAGS) -c -o lib/libfrrcares_la-resolver.lo `test -f 'lib/resolver.c' || echo '$(srcdir)/'`lib/resolver.c lib/libfrrsnmp_la-agentx.lo: lib/agentx.c @am__fastdepCC_TRUE@ $(AM_V_CC)$(LIBTOOL) $(AM_V_lt) --tag=CC $(AM_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=compile $(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(AM_CPPFLAGS) $(CPPFLAGS) $(lib_libfrrsnmp_la_CFLAGS) $(CFLAGS) -MT lib/libfrrsnmp_la-agentx.lo -MD -MP -MF lib/$(DEPDIR)/libfrrsnmp_la-agentx.Tpo -c -o lib/libfrrsnmp_la-agentx.lo `test -f 'lib/agentx.c' || echo '$(srcdir)/'`lib/agentx.c @am__fastdepCC_TRUE@ $(AM_V_at)$(am__mv) lib/$(DEPDIR)/libfrrsnmp_la-agentx.Tpo lib/$(DEPDIR)/libfrrsnmp_la-agentx.Plo @AMDEP_TRUE@@am__fastdepCC_FALSE@ $(AM_V_CC)source='lib/agentx.c' object='lib/libfrrsnmp_la-agentx.lo' libtool=yes @AMDEPBACKSLASH@ @AMDEP_TRUE@@am__fastdepCC_FALSE@ DEPDIR=$(DEPDIR) $(CCDEPMODE) $(depcomp) @AMDEPBACKSLASH@ @am__fastdepCC_FALSE@ $(AM_V_CC@am__nodep@)$(LIBTOOL) $(AM_V_lt) --tag=CC $(AM_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=compile $(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(AM_CPPFLAGS) $(CPPFLAGS) $(lib_libfrrsnmp_la_CFLAGS) $(CFLAGS) -c -o lib/libfrrsnmp_la-agentx.lo `test -f 'lib/agentx.c' || echo '$(srcdir)/'`lib/agentx.c lib/libfrrsnmp_la-snmp.lo: lib/snmp.c @am__fastdepCC_TRUE@ $(AM_V_CC)$(LIBTOOL) $(AM_V_lt) --tag=CC $(AM_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=compile $(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(AM_CPPFLAGS) $(CPPFLAGS) $(lib_libfrrsnmp_la_CFLAGS) $(CFLAGS) -MT lib/libfrrsnmp_la-snmp.lo -MD -MP -MF lib/$(DEPDIR)/libfrrsnmp_la-snmp.Tpo -c -o lib/libfrrsnmp_la-snmp.lo `test -f 'lib/snmp.c' || echo '$(srcdir)/'`lib/snmp.c @am__fastdepCC_TRUE@ $(AM_V_at)$(am__mv) lib/$(DEPDIR)/libfrrsnmp_la-snmp.Tpo lib/$(DEPDIR)/libfrrsnmp_la-snmp.Plo @AMDEP_TRUE@@am__fastdepCC_FALSE@ $(AM_V_CC)source='lib/snmp.c' object='lib/libfrrsnmp_la-snmp.lo' libtool=yes @AMDEPBACKSLASH@ @AMDEP_TRUE@@am__fastdepCC_FALSE@ DEPDIR=$(DEPDIR) $(CCDEPMODE) $(depcomp) @AMDEPBACKSLASH@ @am__fastdepCC_FALSE@ $(AM_V_CC@am__nodep@)$(LIBTOOL) $(AM_V_lt) --tag=CC $(AM_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=compile $(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(AM_CPPFLAGS) $(CPPFLAGS) $(lib_libfrrsnmp_la_CFLAGS) $(CFLAGS) -c -o lib/libfrrsnmp_la-snmp.lo `test -f 'lib/snmp.c' || echo '$(srcdir)/'`lib/snmp.c lib/libfrrzmq_la-frr_zmq.lo: lib/frr_zmq.c @am__fastdepCC_TRUE@ $(AM_V_CC)$(LIBTOOL) $(AM_V_lt) --tag=CC $(AM_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=compile $(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(AM_CPPFLAGS) $(CPPFLAGS) $(lib_libfrrzmq_la_CFLAGS) $(CFLAGS) -MT lib/libfrrzmq_la-frr_zmq.lo -MD -MP -MF lib/$(DEPDIR)/libfrrzmq_la-frr_zmq.Tpo -c -o lib/libfrrzmq_la-frr_zmq.lo `test -f 'lib/frr_zmq.c' || echo '$(srcdir)/'`lib/frr_zmq.c @am__fastdepCC_TRUE@ $(AM_V_at)$(am__mv) lib/$(DEPDIR)/libfrrzmq_la-frr_zmq.Tpo lib/$(DEPDIR)/libfrrzmq_la-frr_zmq.Plo @AMDEP_TRUE@@am__fastdepCC_FALSE@ $(AM_V_CC)source='lib/frr_zmq.c' object='lib/libfrrzmq_la-frr_zmq.lo' libtool=yes @AMDEPBACKSLASH@ @AMDEP_TRUE@@am__fastdepCC_FALSE@ DEPDIR=$(DEPDIR) $(CCDEPMODE) $(depcomp) @AMDEPBACKSLASH@ @am__fastdepCC_FALSE@ $(AM_V_CC@am__nodep@)$(LIBTOOL) $(AM_V_lt) --tag=CC $(AM_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=compile $(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(AM_CPPFLAGS) $(CPPFLAGS) $(lib_libfrrzmq_la_CFLAGS) $(CFLAGS) -c -o lib/libfrrzmq_la-frr_zmq.lo `test -f 'lib/frr_zmq.c' || echo '$(srcdir)/'`lib/frr_zmq.c lib/sysrepo_la-northbound_sysrepo.lo: lib/northbound_sysrepo.c @am__fastdepCC_TRUE@ $(AM_V_CC)$(LIBTOOL) $(AM_V_lt) --tag=CC $(AM_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=compile $(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(AM_CPPFLAGS) $(CPPFLAGS) $(lib_sysrepo_la_CFLAGS) $(CFLAGS) -MT lib/sysrepo_la-northbound_sysrepo.lo -MD -MP -MF lib/$(DEPDIR)/sysrepo_la-northbound_sysrepo.Tpo -c -o lib/sysrepo_la-northbound_sysrepo.lo `test -f 'lib/northbound_sysrepo.c' || echo '$(srcdir)/'`lib/northbound_sysrepo.c @am__fastdepCC_TRUE@ $(AM_V_at)$(am__mv) lib/$(DEPDIR)/sysrepo_la-northbound_sysrepo.Tpo lib/$(DEPDIR)/sysrepo_la-northbound_sysrepo.Plo @AMDEP_TRUE@@am__fastdepCC_FALSE@ $(AM_V_CC)source='lib/northbound_sysrepo.c' object='lib/sysrepo_la-northbound_sysrepo.lo' libtool=yes @AMDEPBACKSLASH@ @AMDEP_TRUE@@am__fastdepCC_FALSE@ DEPDIR=$(DEPDIR) $(CCDEPMODE) $(depcomp) @AMDEPBACKSLASH@ @am__fastdepCC_FALSE@ $(AM_V_CC@am__nodep@)$(LIBTOOL) $(AM_V_lt) --tag=CC $(AM_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=compile $(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(AM_CPPFLAGS) $(CPPFLAGS) $(lib_sysrepo_la_CFLAGS) $(CFLAGS) -c -o lib/sysrepo_la-northbound_sysrepo.lo `test -f 'lib/northbound_sysrepo.c' || echo '$(srcdir)/'`lib/northbound_sysrepo.c ospf6d/ospf6d_snmp_la-ospf6_snmp.lo: ospf6d/ospf6_snmp.c @am__fastdepCC_TRUE@ $(AM_V_CC)$(LIBTOOL) $(AM_V_lt) --tag=CC $(AM_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=compile $(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(AM_CPPFLAGS) $(CPPFLAGS) $(ospf6d_ospf6d_snmp_la_CFLAGS) $(CFLAGS) -MT ospf6d/ospf6d_snmp_la-ospf6_snmp.lo -MD -MP -MF ospf6d/$(DEPDIR)/ospf6d_snmp_la-ospf6_snmp.Tpo -c -o ospf6d/ospf6d_snmp_la-ospf6_snmp.lo `test -f 'ospf6d/ospf6_snmp.c' || echo '$(srcdir)/'`ospf6d/ospf6_snmp.c @am__fastdepCC_TRUE@ $(AM_V_at)$(am__mv) ospf6d/$(DEPDIR)/ospf6d_snmp_la-ospf6_snmp.Tpo ospf6d/$(DEPDIR)/ospf6d_snmp_la-ospf6_snmp.Plo @AMDEP_TRUE@@am__fastdepCC_FALSE@ $(AM_V_CC)source='ospf6d/ospf6_snmp.c' object='ospf6d/ospf6d_snmp_la-ospf6_snmp.lo' libtool=yes @AMDEPBACKSLASH@ @AMDEP_TRUE@@am__fastdepCC_FALSE@ DEPDIR=$(DEPDIR) $(CCDEPMODE) $(depcomp) @AMDEPBACKSLASH@ @am__fastdepCC_FALSE@ $(AM_V_CC@am__nodep@)$(LIBTOOL) $(AM_V_lt) --tag=CC $(AM_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=compile $(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(AM_CPPFLAGS) $(CPPFLAGS) $(ospf6d_ospf6d_snmp_la_CFLAGS) $(CFLAGS) -c -o ospf6d/ospf6d_snmp_la-ospf6_snmp.lo `test -f 'ospf6d/ospf6_snmp.c' || echo '$(srcdir)/'`ospf6d/ospf6_snmp.c ospfd/ospfd_snmp_la-ospf_snmp.lo: ospfd/ospf_snmp.c @am__fastdepCC_TRUE@ $(AM_V_CC)$(LIBTOOL) $(AM_V_lt) --tag=CC $(AM_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=compile $(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(AM_CPPFLAGS) $(CPPFLAGS) $(ospfd_ospfd_snmp_la_CFLAGS) $(CFLAGS) -MT ospfd/ospfd_snmp_la-ospf_snmp.lo -MD -MP -MF ospfd/$(DEPDIR)/ospfd_snmp_la-ospf_snmp.Tpo -c -o ospfd/ospfd_snmp_la-ospf_snmp.lo `test -f 'ospfd/ospf_snmp.c' || echo '$(srcdir)/'`ospfd/ospf_snmp.c @am__fastdepCC_TRUE@ $(AM_V_at)$(am__mv) ospfd/$(DEPDIR)/ospfd_snmp_la-ospf_snmp.Tpo ospfd/$(DEPDIR)/ospfd_snmp_la-ospf_snmp.Plo @AMDEP_TRUE@@am__fastdepCC_FALSE@ $(AM_V_CC)source='ospfd/ospf_snmp.c' object='ospfd/ospfd_snmp_la-ospf_snmp.lo' libtool=yes @AMDEPBACKSLASH@ @AMDEP_TRUE@@am__fastdepCC_FALSE@ DEPDIR=$(DEPDIR) $(CCDEPMODE) $(depcomp) @AMDEPBACKSLASH@ @am__fastdepCC_FALSE@ $(AM_V_CC@am__nodep@)$(LIBTOOL) $(AM_V_lt) --tag=CC $(AM_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=compile $(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(AM_CPPFLAGS) $(CPPFLAGS) $(ospfd_ospfd_snmp_la_CFLAGS) $(CFLAGS) -c -o ospfd/ospfd_snmp_la-ospf_snmp.lo `test -f 'ospfd/ospf_snmp.c' || echo '$(srcdir)/'`ospfd/ospf_snmp.c qpb/libfrr_pb_la-qpb.lo: qpb/qpb.c @am__fastdepCC_TRUE@ $(AM_V_CC)$(LIBTOOL) $(AM_V_lt) --tag=CC $(AM_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=compile $(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(qpb_libfrr_pb_la_CPPFLAGS) $(CPPFLAGS) $(AM_CFLAGS) $(CFLAGS) -MT qpb/libfrr_pb_la-qpb.lo -MD -MP -MF qpb/$(DEPDIR)/libfrr_pb_la-qpb.Tpo -c -o qpb/libfrr_pb_la-qpb.lo `test -f 'qpb/qpb.c' || echo '$(srcdir)/'`qpb/qpb.c @am__fastdepCC_TRUE@ $(AM_V_at)$(am__mv) qpb/$(DEPDIR)/libfrr_pb_la-qpb.Tpo qpb/$(DEPDIR)/libfrr_pb_la-qpb.Plo @AMDEP_TRUE@@am__fastdepCC_FALSE@ $(AM_V_CC)source='qpb/qpb.c' object='qpb/libfrr_pb_la-qpb.lo' libtool=yes @AMDEPBACKSLASH@ @AMDEP_TRUE@@am__fastdepCC_FALSE@ DEPDIR=$(DEPDIR) $(CCDEPMODE) $(depcomp) @AMDEPBACKSLASH@ @am__fastdepCC_FALSE@ $(AM_V_CC@am__nodep@)$(LIBTOOL) $(AM_V_lt) --tag=CC $(AM_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=compile $(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(qpb_libfrr_pb_la_CPPFLAGS) $(CPPFLAGS) $(AM_CFLAGS) $(CFLAGS) -c -o qpb/libfrr_pb_la-qpb.lo `test -f 'qpb/qpb.c' || echo '$(srcdir)/'`qpb/qpb.c qpb/libfrr_pb_la-qpb_allocator.lo: qpb/qpb_allocator.c @am__fastdepCC_TRUE@ $(AM_V_CC)$(LIBTOOL) $(AM_V_lt) --tag=CC $(AM_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=compile $(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(qpb_libfrr_pb_la_CPPFLAGS) $(CPPFLAGS) $(AM_CFLAGS) $(CFLAGS) -MT qpb/libfrr_pb_la-qpb_allocator.lo -MD -MP -MF qpb/$(DEPDIR)/libfrr_pb_la-qpb_allocator.Tpo -c -o qpb/libfrr_pb_la-qpb_allocator.lo `test -f 'qpb/qpb_allocator.c' || echo '$(srcdir)/'`qpb/qpb_allocator.c @am__fastdepCC_TRUE@ $(AM_V_at)$(am__mv) qpb/$(DEPDIR)/libfrr_pb_la-qpb_allocator.Tpo qpb/$(DEPDIR)/libfrr_pb_la-qpb_allocator.Plo @AMDEP_TRUE@@am__fastdepCC_FALSE@ $(AM_V_CC)source='qpb/qpb_allocator.c' object='qpb/libfrr_pb_la-qpb_allocator.lo' libtool=yes @AMDEPBACKSLASH@ @AMDEP_TRUE@@am__fastdepCC_FALSE@ DEPDIR=$(DEPDIR) $(CCDEPMODE) $(depcomp) @AMDEPBACKSLASH@ @am__fastdepCC_FALSE@ $(AM_V_CC@am__nodep@)$(LIBTOOL) $(AM_V_lt) --tag=CC $(AM_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=compile $(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(qpb_libfrr_pb_la_CPPFLAGS) $(CPPFLAGS) $(AM_CFLAGS) $(CFLAGS) -c -o qpb/libfrr_pb_la-qpb_allocator.lo `test -f 'qpb/qpb_allocator.c' || echo '$(srcdir)/'`qpb/qpb_allocator.c qpb/libfrr_pb_la-qpb.pb-c.lo: qpb/qpb.pb-c.c @am__fastdepCC_TRUE@ $(AM_V_CC)$(LIBTOOL) $(AM_V_lt) --tag=CC $(AM_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=compile $(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(qpb_libfrr_pb_la_CPPFLAGS) $(CPPFLAGS) $(AM_CFLAGS) $(CFLAGS) -MT qpb/libfrr_pb_la-qpb.pb-c.lo -MD -MP -MF qpb/$(DEPDIR)/libfrr_pb_la-qpb.pb-c.Tpo -c -o qpb/libfrr_pb_la-qpb.pb-c.lo `test -f 'qpb/qpb.pb-c.c' || echo '$(srcdir)/'`qpb/qpb.pb-c.c @am__fastdepCC_TRUE@ $(AM_V_at)$(am__mv) qpb/$(DEPDIR)/libfrr_pb_la-qpb.pb-c.Tpo qpb/$(DEPDIR)/libfrr_pb_la-qpb.pb-c.Plo @AMDEP_TRUE@@am__fastdepCC_FALSE@ $(AM_V_CC)source='qpb/qpb.pb-c.c' object='qpb/libfrr_pb_la-qpb.pb-c.lo' libtool=yes @AMDEPBACKSLASH@ @AMDEP_TRUE@@am__fastdepCC_FALSE@ DEPDIR=$(DEPDIR) $(CCDEPMODE) $(depcomp) @AMDEPBACKSLASH@ @am__fastdepCC_FALSE@ $(AM_V_CC@am__nodep@)$(LIBTOOL) $(AM_V_lt) --tag=CC $(AM_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=compile $(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(qpb_libfrr_pb_la_CPPFLAGS) $(CPPFLAGS) $(AM_CFLAGS) $(CFLAGS) -c -o qpb/libfrr_pb_la-qpb.pb-c.lo `test -f 'qpb/qpb.pb-c.c' || echo '$(srcdir)/'`qpb/qpb.pb-c.c ripd/ripd_snmp_la-rip_snmp.lo: ripd/rip_snmp.c @am__fastdepCC_TRUE@ $(AM_V_CC)$(LIBTOOL) $(AM_V_lt) --tag=CC $(AM_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=compile $(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(AM_CPPFLAGS) $(CPPFLAGS) $(ripd_ripd_snmp_la_CFLAGS) $(CFLAGS) -MT ripd/ripd_snmp_la-rip_snmp.lo -MD -MP -MF ripd/$(DEPDIR)/ripd_snmp_la-rip_snmp.Tpo -c -o ripd/ripd_snmp_la-rip_snmp.lo `test -f 'ripd/rip_snmp.c' || echo '$(srcdir)/'`ripd/rip_snmp.c @am__fastdepCC_TRUE@ $(AM_V_at)$(am__mv) ripd/$(DEPDIR)/ripd_snmp_la-rip_snmp.Tpo ripd/$(DEPDIR)/ripd_snmp_la-rip_snmp.Plo @AMDEP_TRUE@@am__fastdepCC_FALSE@ $(AM_V_CC)source='ripd/rip_snmp.c' object='ripd/ripd_snmp_la-rip_snmp.lo' libtool=yes @AMDEPBACKSLASH@ @AMDEP_TRUE@@am__fastdepCC_FALSE@ DEPDIR=$(DEPDIR) $(CCDEPMODE) $(depcomp) @AMDEPBACKSLASH@ @am__fastdepCC_FALSE@ $(AM_V_CC@am__nodep@)$(LIBTOOL) $(AM_V_lt) --tag=CC $(AM_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=compile $(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(AM_CPPFLAGS) $(CPPFLAGS) $(ripd_ripd_snmp_la_CFLAGS) $(CFLAGS) -c -o ripd/ripd_snmp_la-rip_snmp.lo `test -f 'ripd/rip_snmp.c' || echo '$(srcdir)/'`ripd/rip_snmp.c zebra/zebra_snmp_la-zebra_snmp.lo: zebra/zebra_snmp.c @am__fastdepCC_TRUE@ $(AM_V_CC)$(LIBTOOL) $(AM_V_lt) --tag=CC $(AM_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=compile $(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(AM_CPPFLAGS) $(CPPFLAGS) $(zebra_zebra_snmp_la_CFLAGS) $(CFLAGS) -MT zebra/zebra_snmp_la-zebra_snmp.lo -MD -MP -MF zebra/$(DEPDIR)/zebra_snmp_la-zebra_snmp.Tpo -c -o zebra/zebra_snmp_la-zebra_snmp.lo `test -f 'zebra/zebra_snmp.c' || echo '$(srcdir)/'`zebra/zebra_snmp.c @am__fastdepCC_TRUE@ $(AM_V_at)$(am__mv) zebra/$(DEPDIR)/zebra_snmp_la-zebra_snmp.Tpo zebra/$(DEPDIR)/zebra_snmp_la-zebra_snmp.Plo @AMDEP_TRUE@@am__fastdepCC_FALSE@ $(AM_V_CC)source='zebra/zebra_snmp.c' object='zebra/zebra_snmp_la-zebra_snmp.lo' libtool=yes @AMDEPBACKSLASH@ @AMDEP_TRUE@@am__fastdepCC_FALSE@ DEPDIR=$(DEPDIR) $(CCDEPMODE) $(depcomp) @AMDEPBACKSLASH@ @am__fastdepCC_FALSE@ $(AM_V_CC@am__nodep@)$(LIBTOOL) $(AM_V_lt) --tag=CC $(AM_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=compile $(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(AM_CPPFLAGS) $(CPPFLAGS) $(zebra_zebra_snmp_la_CFLAGS) $(CFLAGS) -c -o zebra/zebra_snmp_la-zebra_snmp.lo `test -f 'zebra/zebra_snmp.c' || echo '$(srcdir)/'`zebra/zebra_snmp.c bgpd/bgp_btoa-bgp_btoa.o: bgpd/bgp_btoa.c @am__fastdepCC_TRUE@ $(AM_V_CC)$(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(AM_CPPFLAGS) $(CPPFLAGS) $(bgpd_bgp_btoa_CFLAGS) $(CFLAGS) -MT bgpd/bgp_btoa-bgp_btoa.o -MD -MP -MF bgpd/$(DEPDIR)/bgp_btoa-bgp_btoa.Tpo -c -o bgpd/bgp_btoa-bgp_btoa.o `test -f 'bgpd/bgp_btoa.c' || echo '$(srcdir)/'`bgpd/bgp_btoa.c @am__fastdepCC_TRUE@ $(AM_V_at)$(am__mv) bgpd/$(DEPDIR)/bgp_btoa-bgp_btoa.Tpo bgpd/$(DEPDIR)/bgp_btoa-bgp_btoa.Po @AMDEP_TRUE@@am__fastdepCC_FALSE@ $(AM_V_CC)source='bgpd/bgp_btoa.c' object='bgpd/bgp_btoa-bgp_btoa.o' libtool=no @AMDEPBACKSLASH@ @AMDEP_TRUE@@am__fastdepCC_FALSE@ DEPDIR=$(DEPDIR) $(CCDEPMODE) $(depcomp) @AMDEPBACKSLASH@ @am__fastdepCC_FALSE@ $(AM_V_CC@am__nodep@)$(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(AM_CPPFLAGS) $(CPPFLAGS) $(bgpd_bgp_btoa_CFLAGS) $(CFLAGS) -c -o bgpd/bgp_btoa-bgp_btoa.o `test -f 'bgpd/bgp_btoa.c' || echo '$(srcdir)/'`bgpd/bgp_btoa.c bgpd/bgp_btoa-bgp_btoa.obj: bgpd/bgp_btoa.c @am__fastdepCC_TRUE@ $(AM_V_CC)$(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(AM_CPPFLAGS) $(CPPFLAGS) $(bgpd_bgp_btoa_CFLAGS) $(CFLAGS) -MT bgpd/bgp_btoa-bgp_btoa.obj -MD -MP -MF bgpd/$(DEPDIR)/bgp_btoa-bgp_btoa.Tpo -c -o bgpd/bgp_btoa-bgp_btoa.obj `if test -f 'bgpd/bgp_btoa.c'; then $(CYGPATH_W) 'bgpd/bgp_btoa.c'; else $(CYGPATH_W) '$(srcdir)/bgpd/bgp_btoa.c'; fi` @am__fastdepCC_TRUE@ $(AM_V_at)$(am__mv) bgpd/$(DEPDIR)/bgp_btoa-bgp_btoa.Tpo bgpd/$(DEPDIR)/bgp_btoa-bgp_btoa.Po @AMDEP_TRUE@@am__fastdepCC_FALSE@ $(AM_V_CC)source='bgpd/bgp_btoa.c' object='bgpd/bgp_btoa-bgp_btoa.obj' libtool=no @AMDEPBACKSLASH@ @AMDEP_TRUE@@am__fastdepCC_FALSE@ DEPDIR=$(DEPDIR) $(CCDEPMODE) $(depcomp) @AMDEPBACKSLASH@ @am__fastdepCC_FALSE@ $(AM_V_CC@am__nodep@)$(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(AM_CPPFLAGS) $(CPPFLAGS) $(bgpd_bgp_btoa_CFLAGS) $(CFLAGS) -c -o bgpd/bgp_btoa-bgp_btoa.obj `if test -f 'bgpd/bgp_btoa.c'; then $(CYGPATH_W) 'bgpd/bgp_btoa.c'; else $(CYGPATH_W) '$(srcdir)/bgpd/bgp_btoa.c'; fi` bgpd/rfapi/bgp_btoa-rfapi_descriptor_rfp_utils.o: bgpd/rfapi/rfapi_descriptor_rfp_utils.c @am__fastdepCC_TRUE@ $(AM_V_CC)$(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(AM_CPPFLAGS) $(CPPFLAGS) $(bgpd_bgp_btoa_CFLAGS) $(CFLAGS) -MT bgpd/rfapi/bgp_btoa-rfapi_descriptor_rfp_utils.o -MD -MP -MF bgpd/rfapi/$(DEPDIR)/bgp_btoa-rfapi_descriptor_rfp_utils.Tpo -c -o bgpd/rfapi/bgp_btoa-rfapi_descriptor_rfp_utils.o `test -f 'bgpd/rfapi/rfapi_descriptor_rfp_utils.c' || echo '$(srcdir)/'`bgpd/rfapi/rfapi_descriptor_rfp_utils.c @am__fastdepCC_TRUE@ $(AM_V_at)$(am__mv) bgpd/rfapi/$(DEPDIR)/bgp_btoa-rfapi_descriptor_rfp_utils.Tpo bgpd/rfapi/$(DEPDIR)/bgp_btoa-rfapi_descriptor_rfp_utils.Po @AMDEP_TRUE@@am__fastdepCC_FALSE@ $(AM_V_CC)source='bgpd/rfapi/rfapi_descriptor_rfp_utils.c' object='bgpd/rfapi/bgp_btoa-rfapi_descriptor_rfp_utils.o' libtool=no @AMDEPBACKSLASH@ @AMDEP_TRUE@@am__fastdepCC_FALSE@ DEPDIR=$(DEPDIR) $(CCDEPMODE) $(depcomp) @AMDEPBACKSLASH@ @am__fastdepCC_FALSE@ $(AM_V_CC@am__nodep@)$(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(AM_CPPFLAGS) $(CPPFLAGS) $(bgpd_bgp_btoa_CFLAGS) $(CFLAGS) -c -o bgpd/rfapi/bgp_btoa-rfapi_descriptor_rfp_utils.o `test -f 'bgpd/rfapi/rfapi_descriptor_rfp_utils.c' || echo '$(srcdir)/'`bgpd/rfapi/rfapi_descriptor_rfp_utils.c bgpd/rfapi/bgp_btoa-rfapi_descriptor_rfp_utils.obj: bgpd/rfapi/rfapi_descriptor_rfp_utils.c @am__fastdepCC_TRUE@ $(AM_V_CC)$(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(AM_CPPFLAGS) $(CPPFLAGS) $(bgpd_bgp_btoa_CFLAGS) $(CFLAGS) -MT bgpd/rfapi/bgp_btoa-rfapi_descriptor_rfp_utils.obj -MD -MP -MF bgpd/rfapi/$(DEPDIR)/bgp_btoa-rfapi_descriptor_rfp_utils.Tpo -c -o bgpd/rfapi/bgp_btoa-rfapi_descriptor_rfp_utils.obj `if test -f 'bgpd/rfapi/rfapi_descriptor_rfp_utils.c'; then $(CYGPATH_W) 'bgpd/rfapi/rfapi_descriptor_rfp_utils.c'; else $(CYGPATH_W) '$(srcdir)/bgpd/rfapi/rfapi_descriptor_rfp_utils.c'; fi` @am__fastdepCC_TRUE@ $(AM_V_at)$(am__mv) bgpd/rfapi/$(DEPDIR)/bgp_btoa-rfapi_descriptor_rfp_utils.Tpo bgpd/rfapi/$(DEPDIR)/bgp_btoa-rfapi_descriptor_rfp_utils.Po @AMDEP_TRUE@@am__fastdepCC_FALSE@ $(AM_V_CC)source='bgpd/rfapi/rfapi_descriptor_rfp_utils.c' object='bgpd/rfapi/bgp_btoa-rfapi_descriptor_rfp_utils.obj' libtool=no @AMDEPBACKSLASH@ @AMDEP_TRUE@@am__fastdepCC_FALSE@ DEPDIR=$(DEPDIR) $(CCDEPMODE) $(depcomp) @AMDEPBACKSLASH@ @am__fastdepCC_FALSE@ $(AM_V_CC@am__nodep@)$(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(AM_CPPFLAGS) $(CPPFLAGS) $(bgpd_bgp_btoa_CFLAGS) $(CFLAGS) -c -o bgpd/rfapi/bgp_btoa-rfapi_descriptor_rfp_utils.obj `if test -f 'bgpd/rfapi/rfapi_descriptor_rfp_utils.c'; then $(CYGPATH_W) 'bgpd/rfapi/rfapi_descriptor_rfp_utils.c'; else $(CYGPATH_W) '$(srcdir)/bgpd/rfapi/rfapi_descriptor_rfp_utils.c'; fi` bgpd/bgpd-bgp_main.o: bgpd/bgp_main.c @am__fastdepCC_TRUE@ $(AM_V_CC)$(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(AM_CPPFLAGS) $(CPPFLAGS) $(bgpd_bgpd_CFLAGS) $(CFLAGS) -MT bgpd/bgpd-bgp_main.o -MD -MP -MF bgpd/$(DEPDIR)/bgpd-bgp_main.Tpo -c -o bgpd/bgpd-bgp_main.o `test -f 'bgpd/bgp_main.c' || echo '$(srcdir)/'`bgpd/bgp_main.c @am__fastdepCC_TRUE@ $(AM_V_at)$(am__mv) bgpd/$(DEPDIR)/bgpd-bgp_main.Tpo bgpd/$(DEPDIR)/bgpd-bgp_main.Po @AMDEP_TRUE@@am__fastdepCC_FALSE@ $(AM_V_CC)source='bgpd/bgp_main.c' object='bgpd/bgpd-bgp_main.o' libtool=no @AMDEPBACKSLASH@ @AMDEP_TRUE@@am__fastdepCC_FALSE@ DEPDIR=$(DEPDIR) $(CCDEPMODE) $(depcomp) @AMDEPBACKSLASH@ @am__fastdepCC_FALSE@ $(AM_V_CC@am__nodep@)$(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(AM_CPPFLAGS) $(CPPFLAGS) $(bgpd_bgpd_CFLAGS) $(CFLAGS) -c -o bgpd/bgpd-bgp_main.o `test -f 'bgpd/bgp_main.c' || echo '$(srcdir)/'`bgpd/bgp_main.c bgpd/bgpd-bgp_main.obj: bgpd/bgp_main.c @am__fastdepCC_TRUE@ $(AM_V_CC)$(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(AM_CPPFLAGS) $(CPPFLAGS) $(bgpd_bgpd_CFLAGS) $(CFLAGS) -MT bgpd/bgpd-bgp_main.obj -MD -MP -MF bgpd/$(DEPDIR)/bgpd-bgp_main.Tpo -c -o bgpd/bgpd-bgp_main.obj `if test -f 'bgpd/bgp_main.c'; then $(CYGPATH_W) 'bgpd/bgp_main.c'; else $(CYGPATH_W) '$(srcdir)/bgpd/bgp_main.c'; fi` @am__fastdepCC_TRUE@ $(AM_V_at)$(am__mv) bgpd/$(DEPDIR)/bgpd-bgp_main.Tpo bgpd/$(DEPDIR)/bgpd-bgp_main.Po @AMDEP_TRUE@@am__fastdepCC_FALSE@ $(AM_V_CC)source='bgpd/bgp_main.c' object='bgpd/bgpd-bgp_main.obj' libtool=no @AMDEPBACKSLASH@ @AMDEP_TRUE@@am__fastdepCC_FALSE@ DEPDIR=$(DEPDIR) $(CCDEPMODE) $(depcomp) @AMDEPBACKSLASH@ @am__fastdepCC_FALSE@ $(AM_V_CC@am__nodep@)$(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(AM_CPPFLAGS) $(CPPFLAGS) $(bgpd_bgpd_CFLAGS) $(CFLAGS) -c -o bgpd/bgpd-bgp_main.obj `if test -f 'bgpd/bgp_main.c'; then $(CYGPATH_W) 'bgpd/bgp_main.c'; else $(CYGPATH_W) '$(srcdir)/bgpd/bgp_main.c'; fi` bgpd/rfapi/bgpd-rfapi_descriptor_rfp_utils.o: bgpd/rfapi/rfapi_descriptor_rfp_utils.c @am__fastdepCC_TRUE@ $(AM_V_CC)$(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(AM_CPPFLAGS) $(CPPFLAGS) $(bgpd_bgpd_CFLAGS) $(CFLAGS) -MT bgpd/rfapi/bgpd-rfapi_descriptor_rfp_utils.o -MD -MP -MF bgpd/rfapi/$(DEPDIR)/bgpd-rfapi_descriptor_rfp_utils.Tpo -c -o bgpd/rfapi/bgpd-rfapi_descriptor_rfp_utils.o `test -f 'bgpd/rfapi/rfapi_descriptor_rfp_utils.c' || echo '$(srcdir)/'`bgpd/rfapi/rfapi_descriptor_rfp_utils.c @am__fastdepCC_TRUE@ $(AM_V_at)$(am__mv) bgpd/rfapi/$(DEPDIR)/bgpd-rfapi_descriptor_rfp_utils.Tpo bgpd/rfapi/$(DEPDIR)/bgpd-rfapi_descriptor_rfp_utils.Po @AMDEP_TRUE@@am__fastdepCC_FALSE@ $(AM_V_CC)source='bgpd/rfapi/rfapi_descriptor_rfp_utils.c' object='bgpd/rfapi/bgpd-rfapi_descriptor_rfp_utils.o' libtool=no @AMDEPBACKSLASH@ @AMDEP_TRUE@@am__fastdepCC_FALSE@ DEPDIR=$(DEPDIR) $(CCDEPMODE) $(depcomp) @AMDEPBACKSLASH@ @am__fastdepCC_FALSE@ $(AM_V_CC@am__nodep@)$(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(AM_CPPFLAGS) $(CPPFLAGS) $(bgpd_bgpd_CFLAGS) $(CFLAGS) -c -o bgpd/rfapi/bgpd-rfapi_descriptor_rfp_utils.o `test -f 'bgpd/rfapi/rfapi_descriptor_rfp_utils.c' || echo '$(srcdir)/'`bgpd/rfapi/rfapi_descriptor_rfp_utils.c bgpd/rfapi/bgpd-rfapi_descriptor_rfp_utils.obj: bgpd/rfapi/rfapi_descriptor_rfp_utils.c @am__fastdepCC_TRUE@ $(AM_V_CC)$(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(AM_CPPFLAGS) $(CPPFLAGS) $(bgpd_bgpd_CFLAGS) $(CFLAGS) -MT bgpd/rfapi/bgpd-rfapi_descriptor_rfp_utils.obj -MD -MP -MF bgpd/rfapi/$(DEPDIR)/bgpd-rfapi_descriptor_rfp_utils.Tpo -c -o bgpd/rfapi/bgpd-rfapi_descriptor_rfp_utils.obj `if test -f 'bgpd/rfapi/rfapi_descriptor_rfp_utils.c'; then $(CYGPATH_W) 'bgpd/rfapi/rfapi_descriptor_rfp_utils.c'; else $(CYGPATH_W) '$(srcdir)/bgpd/rfapi/rfapi_descriptor_rfp_utils.c'; fi` @am__fastdepCC_TRUE@ $(AM_V_at)$(am__mv) bgpd/rfapi/$(DEPDIR)/bgpd-rfapi_descriptor_rfp_utils.Tpo bgpd/rfapi/$(DEPDIR)/bgpd-rfapi_descriptor_rfp_utils.Po @AMDEP_TRUE@@am__fastdepCC_FALSE@ $(AM_V_CC)source='bgpd/rfapi/rfapi_descriptor_rfp_utils.c' object='bgpd/rfapi/bgpd-rfapi_descriptor_rfp_utils.obj' libtool=no @AMDEPBACKSLASH@ @AMDEP_TRUE@@am__fastdepCC_FALSE@ DEPDIR=$(DEPDIR) $(CCDEPMODE) $(depcomp) @AMDEPBACKSLASH@ @am__fastdepCC_FALSE@ $(AM_V_CC@am__nodep@)$(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(AM_CPPFLAGS) $(CPPFLAGS) $(bgpd_bgpd_CFLAGS) $(CFLAGS) -c -o bgpd/rfapi/bgpd-rfapi_descriptor_rfp_utils.obj `if test -f 'bgpd/rfapi/rfapi_descriptor_rfp_utils.c'; then $(CYGPATH_W) 'bgpd/rfapi/rfapi_descriptor_rfp_utils.c'; else $(CYGPATH_W) '$(srcdir)/bgpd/rfapi/rfapi_descriptor_rfp_utils.c'; fi` bgpd/rfp-example/rfptest/rfptest-rfptest.o: bgpd/rfp-example/rfptest/rfptest.c @am__fastdepCC_TRUE@ $(AM_V_CC)$(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(AM_CPPFLAGS) $(CPPFLAGS) $(bgpd_rfp_example_rfptest_rfptest_CFLAGS) $(CFLAGS) -MT bgpd/rfp-example/rfptest/rfptest-rfptest.o -MD -MP -MF bgpd/rfp-example/rfptest/$(DEPDIR)/rfptest-rfptest.Tpo -c -o bgpd/rfp-example/rfptest/rfptest-rfptest.o `test -f 'bgpd/rfp-example/rfptest/rfptest.c' || echo '$(srcdir)/'`bgpd/rfp-example/rfptest/rfptest.c @am__fastdepCC_TRUE@ $(AM_V_at)$(am__mv) bgpd/rfp-example/rfptest/$(DEPDIR)/rfptest-rfptest.Tpo bgpd/rfp-example/rfptest/$(DEPDIR)/rfptest-rfptest.Po @AMDEP_TRUE@@am__fastdepCC_FALSE@ $(AM_V_CC)source='bgpd/rfp-example/rfptest/rfptest.c' object='bgpd/rfp-example/rfptest/rfptest-rfptest.o' libtool=no @AMDEPBACKSLASH@ @AMDEP_TRUE@@am__fastdepCC_FALSE@ DEPDIR=$(DEPDIR) $(CCDEPMODE) $(depcomp) @AMDEPBACKSLASH@ @am__fastdepCC_FALSE@ $(AM_V_CC@am__nodep@)$(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(AM_CPPFLAGS) $(CPPFLAGS) $(bgpd_rfp_example_rfptest_rfptest_CFLAGS) $(CFLAGS) -c -o bgpd/rfp-example/rfptest/rfptest-rfptest.o `test -f 'bgpd/rfp-example/rfptest/rfptest.c' || echo '$(srcdir)/'`bgpd/rfp-example/rfptest/rfptest.c bgpd/rfp-example/rfptest/rfptest-rfptest.obj: bgpd/rfp-example/rfptest/rfptest.c @am__fastdepCC_TRUE@ $(AM_V_CC)$(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(AM_CPPFLAGS) $(CPPFLAGS) $(bgpd_rfp_example_rfptest_rfptest_CFLAGS) $(CFLAGS) -MT bgpd/rfp-example/rfptest/rfptest-rfptest.obj -MD -MP -MF bgpd/rfp-example/rfptest/$(DEPDIR)/rfptest-rfptest.Tpo -c -o bgpd/rfp-example/rfptest/rfptest-rfptest.obj `if test -f 'bgpd/rfp-example/rfptest/rfptest.c'; then $(CYGPATH_W) 'bgpd/rfp-example/rfptest/rfptest.c'; else $(CYGPATH_W) '$(srcdir)/bgpd/rfp-example/rfptest/rfptest.c'; fi` @am__fastdepCC_TRUE@ $(AM_V_at)$(am__mv) bgpd/rfp-example/rfptest/$(DEPDIR)/rfptest-rfptest.Tpo bgpd/rfp-example/rfptest/$(DEPDIR)/rfptest-rfptest.Po @AMDEP_TRUE@@am__fastdepCC_FALSE@ $(AM_V_CC)source='bgpd/rfp-example/rfptest/rfptest.c' object='bgpd/rfp-example/rfptest/rfptest-rfptest.obj' libtool=no @AMDEPBACKSLASH@ @AMDEP_TRUE@@am__fastdepCC_FALSE@ DEPDIR=$(DEPDIR) $(CCDEPMODE) $(depcomp) @AMDEPBACKSLASH@ @am__fastdepCC_FALSE@ $(AM_V_CC@am__nodep@)$(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(AM_CPPFLAGS) $(CPPFLAGS) $(bgpd_rfp_example_rfptest_rfptest_CFLAGS) $(CFLAGS) -c -o bgpd/rfp-example/rfptest/rfptest-rfptest.obj `if test -f 'bgpd/rfp-example/rfptest/rfptest.c'; then $(CYGPATH_W) 'bgpd/rfp-example/rfptest/rfptest.c'; else $(CYGPATH_W) '$(srcdir)/bgpd/rfp-example/rfptest/rfptest.c'; fi` isisd/fabricd-isis_bpf.o: isisd/isis_bpf.c @am__fastdepCC_TRUE@ $(AM_V_CC)$(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(isisd_fabricd_CPPFLAGS) $(CPPFLAGS) $(AM_CFLAGS) $(CFLAGS) -MT isisd/fabricd-isis_bpf.o -MD -MP -MF isisd/$(DEPDIR)/fabricd-isis_bpf.Tpo -c -o isisd/fabricd-isis_bpf.o `test -f 'isisd/isis_bpf.c' || echo '$(srcdir)/'`isisd/isis_bpf.c @am__fastdepCC_TRUE@ $(AM_V_at)$(am__mv) isisd/$(DEPDIR)/fabricd-isis_bpf.Tpo isisd/$(DEPDIR)/fabricd-isis_bpf.Po @AMDEP_TRUE@@am__fastdepCC_FALSE@ $(AM_V_CC)source='isisd/isis_bpf.c' object='isisd/fabricd-isis_bpf.o' libtool=no @AMDEPBACKSLASH@ @AMDEP_TRUE@@am__fastdepCC_FALSE@ DEPDIR=$(DEPDIR) $(CCDEPMODE) $(depcomp) @AMDEPBACKSLASH@ @am__fastdepCC_FALSE@ $(AM_V_CC@am__nodep@)$(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(isisd_fabricd_CPPFLAGS) $(CPPFLAGS) $(AM_CFLAGS) $(CFLAGS) -c -o isisd/fabricd-isis_bpf.o `test -f 'isisd/isis_bpf.c' || echo '$(srcdir)/'`isisd/isis_bpf.c isisd/fabricd-isis_bpf.obj: isisd/isis_bpf.c @am__fastdepCC_TRUE@ $(AM_V_CC)$(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(isisd_fabricd_CPPFLAGS) $(CPPFLAGS) $(AM_CFLAGS) $(CFLAGS) -MT isisd/fabricd-isis_bpf.obj -MD -MP -MF isisd/$(DEPDIR)/fabricd-isis_bpf.Tpo -c -o isisd/fabricd-isis_bpf.obj `if test -f 'isisd/isis_bpf.c'; then $(CYGPATH_W) 'isisd/isis_bpf.c'; else $(CYGPATH_W) '$(srcdir)/isisd/isis_bpf.c'; fi` @am__fastdepCC_TRUE@ $(AM_V_at)$(am__mv) isisd/$(DEPDIR)/fabricd-isis_bpf.Tpo isisd/$(DEPDIR)/fabricd-isis_bpf.Po @AMDEP_TRUE@@am__fastdepCC_FALSE@ $(AM_V_CC)source='isisd/isis_bpf.c' object='isisd/fabricd-isis_bpf.obj' libtool=no @AMDEPBACKSLASH@ @AMDEP_TRUE@@am__fastdepCC_FALSE@ DEPDIR=$(DEPDIR) $(CCDEPMODE) $(depcomp) @AMDEPBACKSLASH@ @am__fastdepCC_FALSE@ $(AM_V_CC@am__nodep@)$(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(isisd_fabricd_CPPFLAGS) $(CPPFLAGS) $(AM_CFLAGS) $(CFLAGS) -c -o isisd/fabricd-isis_bpf.obj `if test -f 'isisd/isis_bpf.c'; then $(CYGPATH_W) 'isisd/isis_bpf.c'; else $(CYGPATH_W) '$(srcdir)/isisd/isis_bpf.c'; fi` isisd/fabricd-isis_dlpi.o: isisd/isis_dlpi.c @am__fastdepCC_TRUE@ $(AM_V_CC)$(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(isisd_fabricd_CPPFLAGS) $(CPPFLAGS) $(AM_CFLAGS) $(CFLAGS) -MT isisd/fabricd-isis_dlpi.o -MD -MP -MF isisd/$(DEPDIR)/fabricd-isis_dlpi.Tpo -c -o isisd/fabricd-isis_dlpi.o `test -f 'isisd/isis_dlpi.c' || echo '$(srcdir)/'`isisd/isis_dlpi.c @am__fastdepCC_TRUE@ $(AM_V_at)$(am__mv) isisd/$(DEPDIR)/fabricd-isis_dlpi.Tpo isisd/$(DEPDIR)/fabricd-isis_dlpi.Po @AMDEP_TRUE@@am__fastdepCC_FALSE@ $(AM_V_CC)source='isisd/isis_dlpi.c' object='isisd/fabricd-isis_dlpi.o' libtool=no @AMDEPBACKSLASH@ @AMDEP_TRUE@@am__fastdepCC_FALSE@ DEPDIR=$(DEPDIR) $(CCDEPMODE) $(depcomp) @AMDEPBACKSLASH@ @am__fastdepCC_FALSE@ $(AM_V_CC@am__nodep@)$(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(isisd_fabricd_CPPFLAGS) $(CPPFLAGS) $(AM_CFLAGS) $(CFLAGS) -c -o isisd/fabricd-isis_dlpi.o `test -f 'isisd/isis_dlpi.c' || echo '$(srcdir)/'`isisd/isis_dlpi.c isisd/fabricd-isis_dlpi.obj: isisd/isis_dlpi.c @am__fastdepCC_TRUE@ $(AM_V_CC)$(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(isisd_fabricd_CPPFLAGS) $(CPPFLAGS) $(AM_CFLAGS) $(CFLAGS) -MT isisd/fabricd-isis_dlpi.obj -MD -MP -MF isisd/$(DEPDIR)/fabricd-isis_dlpi.Tpo -c -o isisd/fabricd-isis_dlpi.obj `if test -f 'isisd/isis_dlpi.c'; then $(CYGPATH_W) 'isisd/isis_dlpi.c'; else $(CYGPATH_W) '$(srcdir)/isisd/isis_dlpi.c'; fi` @am__fastdepCC_TRUE@ $(AM_V_at)$(am__mv) isisd/$(DEPDIR)/fabricd-isis_dlpi.Tpo isisd/$(DEPDIR)/fabricd-isis_dlpi.Po @AMDEP_TRUE@@am__fastdepCC_FALSE@ $(AM_V_CC)source='isisd/isis_dlpi.c' object='isisd/fabricd-isis_dlpi.obj' libtool=no @AMDEPBACKSLASH@ @AMDEP_TRUE@@am__fastdepCC_FALSE@ DEPDIR=$(DEPDIR) $(CCDEPMODE) $(depcomp) @AMDEPBACKSLASH@ @am__fastdepCC_FALSE@ $(AM_V_CC@am__nodep@)$(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(isisd_fabricd_CPPFLAGS) $(CPPFLAGS) $(AM_CFLAGS) $(CFLAGS) -c -o isisd/fabricd-isis_dlpi.obj `if test -f 'isisd/isis_dlpi.c'; then $(CYGPATH_W) 'isisd/isis_dlpi.c'; else $(CYGPATH_W) '$(srcdir)/isisd/isis_dlpi.c'; fi` isisd/fabricd-isis_main.o: isisd/isis_main.c @am__fastdepCC_TRUE@ $(AM_V_CC)$(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(isisd_fabricd_CPPFLAGS) $(CPPFLAGS) $(AM_CFLAGS) $(CFLAGS) -MT isisd/fabricd-isis_main.o -MD -MP -MF isisd/$(DEPDIR)/fabricd-isis_main.Tpo -c -o isisd/fabricd-isis_main.o `test -f 'isisd/isis_main.c' || echo '$(srcdir)/'`isisd/isis_main.c @am__fastdepCC_TRUE@ $(AM_V_at)$(am__mv) isisd/$(DEPDIR)/fabricd-isis_main.Tpo isisd/$(DEPDIR)/fabricd-isis_main.Po @AMDEP_TRUE@@am__fastdepCC_FALSE@ $(AM_V_CC)source='isisd/isis_main.c' object='isisd/fabricd-isis_main.o' libtool=no @AMDEPBACKSLASH@ @AMDEP_TRUE@@am__fastdepCC_FALSE@ DEPDIR=$(DEPDIR) $(CCDEPMODE) $(depcomp) @AMDEPBACKSLASH@ @am__fastdepCC_FALSE@ $(AM_V_CC@am__nodep@)$(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(isisd_fabricd_CPPFLAGS) $(CPPFLAGS) $(AM_CFLAGS) $(CFLAGS) -c -o isisd/fabricd-isis_main.o `test -f 'isisd/isis_main.c' || echo '$(srcdir)/'`isisd/isis_main.c isisd/fabricd-isis_main.obj: isisd/isis_main.c @am__fastdepCC_TRUE@ $(AM_V_CC)$(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(isisd_fabricd_CPPFLAGS) $(CPPFLAGS) $(AM_CFLAGS) $(CFLAGS) -MT isisd/fabricd-isis_main.obj -MD -MP -MF isisd/$(DEPDIR)/fabricd-isis_main.Tpo -c -o isisd/fabricd-isis_main.obj `if test -f 'isisd/isis_main.c'; then $(CYGPATH_W) 'isisd/isis_main.c'; else $(CYGPATH_W) '$(srcdir)/isisd/isis_main.c'; fi` @am__fastdepCC_TRUE@ $(AM_V_at)$(am__mv) isisd/$(DEPDIR)/fabricd-isis_main.Tpo isisd/$(DEPDIR)/fabricd-isis_main.Po @AMDEP_TRUE@@am__fastdepCC_FALSE@ $(AM_V_CC)source='isisd/isis_main.c' object='isisd/fabricd-isis_main.obj' libtool=no @AMDEPBACKSLASH@ @AMDEP_TRUE@@am__fastdepCC_FALSE@ DEPDIR=$(DEPDIR) $(CCDEPMODE) $(depcomp) @AMDEPBACKSLASH@ @am__fastdepCC_FALSE@ $(AM_V_CC@am__nodep@)$(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(isisd_fabricd_CPPFLAGS) $(CPPFLAGS) $(AM_CFLAGS) $(CFLAGS) -c -o isisd/fabricd-isis_main.obj `if test -f 'isisd/isis_main.c'; then $(CYGPATH_W) 'isisd/isis_main.c'; else $(CYGPATH_W) '$(srcdir)/isisd/isis_main.c'; fi` isisd/fabricd-isis_pfpacket.o: isisd/isis_pfpacket.c @am__fastdepCC_TRUE@ $(AM_V_CC)$(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(isisd_fabricd_CPPFLAGS) $(CPPFLAGS) $(AM_CFLAGS) $(CFLAGS) -MT isisd/fabricd-isis_pfpacket.o -MD -MP -MF isisd/$(DEPDIR)/fabricd-isis_pfpacket.Tpo -c -o isisd/fabricd-isis_pfpacket.o `test -f 'isisd/isis_pfpacket.c' || echo '$(srcdir)/'`isisd/isis_pfpacket.c @am__fastdepCC_TRUE@ $(AM_V_at)$(am__mv) isisd/$(DEPDIR)/fabricd-isis_pfpacket.Tpo isisd/$(DEPDIR)/fabricd-isis_pfpacket.Po @AMDEP_TRUE@@am__fastdepCC_FALSE@ $(AM_V_CC)source='isisd/isis_pfpacket.c' object='isisd/fabricd-isis_pfpacket.o' libtool=no @AMDEPBACKSLASH@ @AMDEP_TRUE@@am__fastdepCC_FALSE@ DEPDIR=$(DEPDIR) $(CCDEPMODE) $(depcomp) @AMDEPBACKSLASH@ @am__fastdepCC_FALSE@ $(AM_V_CC@am__nodep@)$(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(isisd_fabricd_CPPFLAGS) $(CPPFLAGS) $(AM_CFLAGS) $(CFLAGS) -c -o isisd/fabricd-isis_pfpacket.o `test -f 'isisd/isis_pfpacket.c' || echo '$(srcdir)/'`isisd/isis_pfpacket.c isisd/fabricd-isis_pfpacket.obj: isisd/isis_pfpacket.c @am__fastdepCC_TRUE@ $(AM_V_CC)$(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(isisd_fabricd_CPPFLAGS) $(CPPFLAGS) $(AM_CFLAGS) $(CFLAGS) -MT isisd/fabricd-isis_pfpacket.obj -MD -MP -MF isisd/$(DEPDIR)/fabricd-isis_pfpacket.Tpo -c -o isisd/fabricd-isis_pfpacket.obj `if test -f 'isisd/isis_pfpacket.c'; then $(CYGPATH_W) 'isisd/isis_pfpacket.c'; else $(CYGPATH_W) '$(srcdir)/isisd/isis_pfpacket.c'; fi` @am__fastdepCC_TRUE@ $(AM_V_at)$(am__mv) isisd/$(DEPDIR)/fabricd-isis_pfpacket.Tpo isisd/$(DEPDIR)/fabricd-isis_pfpacket.Po @AMDEP_TRUE@@am__fastdepCC_FALSE@ $(AM_V_CC)source='isisd/isis_pfpacket.c' object='isisd/fabricd-isis_pfpacket.obj' libtool=no @AMDEPBACKSLASH@ @AMDEP_TRUE@@am__fastdepCC_FALSE@ DEPDIR=$(DEPDIR) $(CCDEPMODE) $(depcomp) @AMDEPBACKSLASH@ @am__fastdepCC_FALSE@ $(AM_V_CC@am__nodep@)$(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(isisd_fabricd_CPPFLAGS) $(CPPFLAGS) $(AM_CFLAGS) $(CFLAGS) -c -o isisd/fabricd-isis_pfpacket.obj `if test -f 'isisd/isis_pfpacket.c'; then $(CYGPATH_W) 'isisd/isis_pfpacket.c'; else $(CYGPATH_W) '$(srcdir)/isisd/isis_pfpacket.c'; fi` lib/clippy-clippy.o: lib/clippy.c @am__fastdepCC_TRUE@ $(AM_V_CC)$(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(lib_clippy_CPPFLAGS) $(CPPFLAGS) $(lib_clippy_CFLAGS) $(CFLAGS) -MT lib/clippy-clippy.o -MD -MP -MF lib/$(DEPDIR)/clippy-clippy.Tpo -c -o lib/clippy-clippy.o `test -f 'lib/clippy.c' || echo '$(srcdir)/'`lib/clippy.c @am__fastdepCC_TRUE@ $(AM_V_at)$(am__mv) lib/$(DEPDIR)/clippy-clippy.Tpo lib/$(DEPDIR)/clippy-clippy.Po @AMDEP_TRUE@@am__fastdepCC_FALSE@ $(AM_V_CC)source='lib/clippy.c' object='lib/clippy-clippy.o' libtool=no @AMDEPBACKSLASH@ @AMDEP_TRUE@@am__fastdepCC_FALSE@ DEPDIR=$(DEPDIR) $(CCDEPMODE) $(depcomp) @AMDEPBACKSLASH@ @am__fastdepCC_FALSE@ $(AM_V_CC@am__nodep@)$(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(lib_clippy_CPPFLAGS) $(CPPFLAGS) $(lib_clippy_CFLAGS) $(CFLAGS) -c -o lib/clippy-clippy.o `test -f 'lib/clippy.c' || echo '$(srcdir)/'`lib/clippy.c lib/clippy-clippy.obj: lib/clippy.c @am__fastdepCC_TRUE@ $(AM_V_CC)$(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(lib_clippy_CPPFLAGS) $(CPPFLAGS) $(lib_clippy_CFLAGS) $(CFLAGS) -MT lib/clippy-clippy.obj -MD -MP -MF lib/$(DEPDIR)/clippy-clippy.Tpo -c -o lib/clippy-clippy.obj `if test -f 'lib/clippy.c'; then $(CYGPATH_W) 'lib/clippy.c'; else $(CYGPATH_W) '$(srcdir)/lib/clippy.c'; fi` @am__fastdepCC_TRUE@ $(AM_V_at)$(am__mv) lib/$(DEPDIR)/clippy-clippy.Tpo lib/$(DEPDIR)/clippy-clippy.Po @AMDEP_TRUE@@am__fastdepCC_FALSE@ $(AM_V_CC)source='lib/clippy.c' object='lib/clippy-clippy.obj' libtool=no @AMDEPBACKSLASH@ @AMDEP_TRUE@@am__fastdepCC_FALSE@ DEPDIR=$(DEPDIR) $(CCDEPMODE) $(depcomp) @AMDEPBACKSLASH@ @am__fastdepCC_FALSE@ $(AM_V_CC@am__nodep@)$(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(lib_clippy_CPPFLAGS) $(CPPFLAGS) $(lib_clippy_CFLAGS) $(CFLAGS) -c -o lib/clippy-clippy.obj `if test -f 'lib/clippy.c'; then $(CYGPATH_W) 'lib/clippy.c'; else $(CYGPATH_W) '$(srcdir)/lib/clippy.c'; fi` lib/clippy-command_graph.o: lib/command_graph.c @am__fastdepCC_TRUE@ $(AM_V_CC)$(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(lib_clippy_CPPFLAGS) $(CPPFLAGS) $(lib_clippy_CFLAGS) $(CFLAGS) -MT lib/clippy-command_graph.o -MD -MP -MF lib/$(DEPDIR)/clippy-command_graph.Tpo -c -o lib/clippy-command_graph.o `test -f 'lib/command_graph.c' || echo '$(srcdir)/'`lib/command_graph.c @am__fastdepCC_TRUE@ $(AM_V_at)$(am__mv) lib/$(DEPDIR)/clippy-command_graph.Tpo lib/$(DEPDIR)/clippy-command_graph.Po @AMDEP_TRUE@@am__fastdepCC_FALSE@ $(AM_V_CC)source='lib/command_graph.c' object='lib/clippy-command_graph.o' libtool=no @AMDEPBACKSLASH@ @AMDEP_TRUE@@am__fastdepCC_FALSE@ DEPDIR=$(DEPDIR) $(CCDEPMODE) $(depcomp) @AMDEPBACKSLASH@ @am__fastdepCC_FALSE@ $(AM_V_CC@am__nodep@)$(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(lib_clippy_CPPFLAGS) $(CPPFLAGS) $(lib_clippy_CFLAGS) $(CFLAGS) -c -o lib/clippy-command_graph.o `test -f 'lib/command_graph.c' || echo '$(srcdir)/'`lib/command_graph.c lib/clippy-command_graph.obj: lib/command_graph.c @am__fastdepCC_TRUE@ $(AM_V_CC)$(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(lib_clippy_CPPFLAGS) $(CPPFLAGS) $(lib_clippy_CFLAGS) $(CFLAGS) -MT lib/clippy-command_graph.obj -MD -MP -MF lib/$(DEPDIR)/clippy-command_graph.Tpo -c -o lib/clippy-command_graph.obj `if test -f 'lib/command_graph.c'; then $(CYGPATH_W) 'lib/command_graph.c'; else $(CYGPATH_W) '$(srcdir)/lib/command_graph.c'; fi` @am__fastdepCC_TRUE@ $(AM_V_at)$(am__mv) lib/$(DEPDIR)/clippy-command_graph.Tpo lib/$(DEPDIR)/clippy-command_graph.Po @AMDEP_TRUE@@am__fastdepCC_FALSE@ $(AM_V_CC)source='lib/command_graph.c' object='lib/clippy-command_graph.obj' libtool=no @AMDEPBACKSLASH@ @AMDEP_TRUE@@am__fastdepCC_FALSE@ DEPDIR=$(DEPDIR) $(CCDEPMODE) $(depcomp) @AMDEPBACKSLASH@ @am__fastdepCC_FALSE@ $(AM_V_CC@am__nodep@)$(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(lib_clippy_CPPFLAGS) $(CPPFLAGS) $(lib_clippy_CFLAGS) $(CFLAGS) -c -o lib/clippy-command_graph.obj `if test -f 'lib/command_graph.c'; then $(CYGPATH_W) 'lib/command_graph.c'; else $(CYGPATH_W) '$(srcdir)/lib/command_graph.c'; fi` lib/clippy-command_lex.o: lib/command_lex.c @am__fastdepCC_TRUE@ $(AM_V_CC)$(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(lib_clippy_CPPFLAGS) $(CPPFLAGS) $(lib_clippy_CFLAGS) $(CFLAGS) -MT lib/clippy-command_lex.o -MD -MP -MF lib/$(DEPDIR)/clippy-command_lex.Tpo -c -o lib/clippy-command_lex.o `test -f 'lib/command_lex.c' || echo '$(srcdir)/'`lib/command_lex.c @am__fastdepCC_TRUE@ $(AM_V_at)$(am__mv) lib/$(DEPDIR)/clippy-command_lex.Tpo lib/$(DEPDIR)/clippy-command_lex.Po @AMDEP_TRUE@@am__fastdepCC_FALSE@ $(AM_V_CC)source='lib/command_lex.c' object='lib/clippy-command_lex.o' libtool=no @AMDEPBACKSLASH@ @AMDEP_TRUE@@am__fastdepCC_FALSE@ DEPDIR=$(DEPDIR) $(CCDEPMODE) $(depcomp) @AMDEPBACKSLASH@ @am__fastdepCC_FALSE@ $(AM_V_CC@am__nodep@)$(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(lib_clippy_CPPFLAGS) $(CPPFLAGS) $(lib_clippy_CFLAGS) $(CFLAGS) -c -o lib/clippy-command_lex.o `test -f 'lib/command_lex.c' || echo '$(srcdir)/'`lib/command_lex.c lib/clippy-command_lex.obj: lib/command_lex.c @am__fastdepCC_TRUE@ $(AM_V_CC)$(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(lib_clippy_CPPFLAGS) $(CPPFLAGS) $(lib_clippy_CFLAGS) $(CFLAGS) -MT lib/clippy-command_lex.obj -MD -MP -MF lib/$(DEPDIR)/clippy-command_lex.Tpo -c -o lib/clippy-command_lex.obj `if test -f 'lib/command_lex.c'; then $(CYGPATH_W) 'lib/command_lex.c'; else $(CYGPATH_W) '$(srcdir)/lib/command_lex.c'; fi` @am__fastdepCC_TRUE@ $(AM_V_at)$(am__mv) lib/$(DEPDIR)/clippy-command_lex.Tpo lib/$(DEPDIR)/clippy-command_lex.Po @AMDEP_TRUE@@am__fastdepCC_FALSE@ $(AM_V_CC)source='lib/command_lex.c' object='lib/clippy-command_lex.obj' libtool=no @AMDEPBACKSLASH@ @AMDEP_TRUE@@am__fastdepCC_FALSE@ DEPDIR=$(DEPDIR) $(CCDEPMODE) $(depcomp) @AMDEPBACKSLASH@ @am__fastdepCC_FALSE@ $(AM_V_CC@am__nodep@)$(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(lib_clippy_CPPFLAGS) $(CPPFLAGS) $(lib_clippy_CFLAGS) $(CFLAGS) -c -o lib/clippy-command_lex.obj `if test -f 'lib/command_lex.c'; then $(CYGPATH_W) 'lib/command_lex.c'; else $(CYGPATH_W) '$(srcdir)/lib/command_lex.c'; fi` lib/clippy-command_parse.o: lib/command_parse.c @am__fastdepCC_TRUE@ $(AM_V_CC)$(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(lib_clippy_CPPFLAGS) $(CPPFLAGS) $(lib_clippy_CFLAGS) $(CFLAGS) -MT lib/clippy-command_parse.o -MD -MP -MF lib/$(DEPDIR)/clippy-command_parse.Tpo -c -o lib/clippy-command_parse.o `test -f 'lib/command_parse.c' || echo '$(srcdir)/'`lib/command_parse.c @am__fastdepCC_TRUE@ $(AM_V_at)$(am__mv) lib/$(DEPDIR)/clippy-command_parse.Tpo lib/$(DEPDIR)/clippy-command_parse.Po @AMDEP_TRUE@@am__fastdepCC_FALSE@ $(AM_V_CC)source='lib/command_parse.c' object='lib/clippy-command_parse.o' libtool=no @AMDEPBACKSLASH@ @AMDEP_TRUE@@am__fastdepCC_FALSE@ DEPDIR=$(DEPDIR) $(CCDEPMODE) $(depcomp) @AMDEPBACKSLASH@ @am__fastdepCC_FALSE@ $(AM_V_CC@am__nodep@)$(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(lib_clippy_CPPFLAGS) $(CPPFLAGS) $(lib_clippy_CFLAGS) $(CFLAGS) -c -o lib/clippy-command_parse.o `test -f 'lib/command_parse.c' || echo '$(srcdir)/'`lib/command_parse.c lib/clippy-command_parse.obj: lib/command_parse.c @am__fastdepCC_TRUE@ $(AM_V_CC)$(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(lib_clippy_CPPFLAGS) $(CPPFLAGS) $(lib_clippy_CFLAGS) $(CFLAGS) -MT lib/clippy-command_parse.obj -MD -MP -MF lib/$(DEPDIR)/clippy-command_parse.Tpo -c -o lib/clippy-command_parse.obj `if test -f 'lib/command_parse.c'; then $(CYGPATH_W) 'lib/command_parse.c'; else $(CYGPATH_W) '$(srcdir)/lib/command_parse.c'; fi` @am__fastdepCC_TRUE@ $(AM_V_at)$(am__mv) lib/$(DEPDIR)/clippy-command_parse.Tpo lib/$(DEPDIR)/clippy-command_parse.Po @AMDEP_TRUE@@am__fastdepCC_FALSE@ $(AM_V_CC)source='lib/command_parse.c' object='lib/clippy-command_parse.obj' libtool=no @AMDEPBACKSLASH@ @AMDEP_TRUE@@am__fastdepCC_FALSE@ DEPDIR=$(DEPDIR) $(CCDEPMODE) $(depcomp) @AMDEPBACKSLASH@ @am__fastdepCC_FALSE@ $(AM_V_CC@am__nodep@)$(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(lib_clippy_CPPFLAGS) $(CPPFLAGS) $(lib_clippy_CFLAGS) $(CFLAGS) -c -o lib/clippy-command_parse.obj `if test -f 'lib/command_parse.c'; then $(CYGPATH_W) 'lib/command_parse.c'; else $(CYGPATH_W) '$(srcdir)/lib/command_parse.c'; fi` lib/clippy-command_py.o: lib/command_py.c @am__fastdepCC_TRUE@ $(AM_V_CC)$(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(lib_clippy_CPPFLAGS) $(CPPFLAGS) $(lib_clippy_CFLAGS) $(CFLAGS) -MT lib/clippy-command_py.o -MD -MP -MF lib/$(DEPDIR)/clippy-command_py.Tpo -c -o lib/clippy-command_py.o `test -f 'lib/command_py.c' || echo '$(srcdir)/'`lib/command_py.c @am__fastdepCC_TRUE@ $(AM_V_at)$(am__mv) lib/$(DEPDIR)/clippy-command_py.Tpo lib/$(DEPDIR)/clippy-command_py.Po @AMDEP_TRUE@@am__fastdepCC_FALSE@ $(AM_V_CC)source='lib/command_py.c' object='lib/clippy-command_py.o' libtool=no @AMDEPBACKSLASH@ @AMDEP_TRUE@@am__fastdepCC_FALSE@ DEPDIR=$(DEPDIR) $(CCDEPMODE) $(depcomp) @AMDEPBACKSLASH@ @am__fastdepCC_FALSE@ $(AM_V_CC@am__nodep@)$(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(lib_clippy_CPPFLAGS) $(CPPFLAGS) $(lib_clippy_CFLAGS) $(CFLAGS) -c -o lib/clippy-command_py.o `test -f 'lib/command_py.c' || echo '$(srcdir)/'`lib/command_py.c lib/clippy-command_py.obj: lib/command_py.c @am__fastdepCC_TRUE@ $(AM_V_CC)$(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(lib_clippy_CPPFLAGS) $(CPPFLAGS) $(lib_clippy_CFLAGS) $(CFLAGS) -MT lib/clippy-command_py.obj -MD -MP -MF lib/$(DEPDIR)/clippy-command_py.Tpo -c -o lib/clippy-command_py.obj `if test -f 'lib/command_py.c'; then $(CYGPATH_W) 'lib/command_py.c'; else $(CYGPATH_W) '$(srcdir)/lib/command_py.c'; fi` @am__fastdepCC_TRUE@ $(AM_V_at)$(am__mv) lib/$(DEPDIR)/clippy-command_py.Tpo lib/$(DEPDIR)/clippy-command_py.Po @AMDEP_TRUE@@am__fastdepCC_FALSE@ $(AM_V_CC)source='lib/command_py.c' object='lib/clippy-command_py.obj' libtool=no @AMDEPBACKSLASH@ @AMDEP_TRUE@@am__fastdepCC_FALSE@ DEPDIR=$(DEPDIR) $(CCDEPMODE) $(depcomp) @AMDEPBACKSLASH@ @am__fastdepCC_FALSE@ $(AM_V_CC@am__nodep@)$(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(lib_clippy_CPPFLAGS) $(CPPFLAGS) $(lib_clippy_CFLAGS) $(CFLAGS) -c -o lib/clippy-command_py.obj `if test -f 'lib/command_py.c'; then $(CYGPATH_W) 'lib/command_py.c'; else $(CYGPATH_W) '$(srcdir)/lib/command_py.c'; fi` lib/clippy-defun_lex.o: lib/defun_lex.c @am__fastdepCC_TRUE@ $(AM_V_CC)$(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(lib_clippy_CPPFLAGS) $(CPPFLAGS) $(lib_clippy_CFLAGS) $(CFLAGS) -MT lib/clippy-defun_lex.o -MD -MP -MF lib/$(DEPDIR)/clippy-defun_lex.Tpo -c -o lib/clippy-defun_lex.o `test -f 'lib/defun_lex.c' || echo '$(srcdir)/'`lib/defun_lex.c @am__fastdepCC_TRUE@ $(AM_V_at)$(am__mv) lib/$(DEPDIR)/clippy-defun_lex.Tpo lib/$(DEPDIR)/clippy-defun_lex.Po @AMDEP_TRUE@@am__fastdepCC_FALSE@ $(AM_V_CC)source='lib/defun_lex.c' object='lib/clippy-defun_lex.o' libtool=no @AMDEPBACKSLASH@ @AMDEP_TRUE@@am__fastdepCC_FALSE@ DEPDIR=$(DEPDIR) $(CCDEPMODE) $(depcomp) @AMDEPBACKSLASH@ @am__fastdepCC_FALSE@ $(AM_V_CC@am__nodep@)$(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(lib_clippy_CPPFLAGS) $(CPPFLAGS) $(lib_clippy_CFLAGS) $(CFLAGS) -c -o lib/clippy-defun_lex.o `test -f 'lib/defun_lex.c' || echo '$(srcdir)/'`lib/defun_lex.c lib/clippy-defun_lex.obj: lib/defun_lex.c @am__fastdepCC_TRUE@ $(AM_V_CC)$(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(lib_clippy_CPPFLAGS) $(CPPFLAGS) $(lib_clippy_CFLAGS) $(CFLAGS) -MT lib/clippy-defun_lex.obj -MD -MP -MF lib/$(DEPDIR)/clippy-defun_lex.Tpo -c -o lib/clippy-defun_lex.obj `if test -f 'lib/defun_lex.c'; then $(CYGPATH_W) 'lib/defun_lex.c'; else $(CYGPATH_W) '$(srcdir)/lib/defun_lex.c'; fi` @am__fastdepCC_TRUE@ $(AM_V_at)$(am__mv) lib/$(DEPDIR)/clippy-defun_lex.Tpo lib/$(DEPDIR)/clippy-defun_lex.Po @AMDEP_TRUE@@am__fastdepCC_FALSE@ $(AM_V_CC)source='lib/defun_lex.c' object='lib/clippy-defun_lex.obj' libtool=no @AMDEPBACKSLASH@ @AMDEP_TRUE@@am__fastdepCC_FALSE@ DEPDIR=$(DEPDIR) $(CCDEPMODE) $(depcomp) @AMDEPBACKSLASH@ @am__fastdepCC_FALSE@ $(AM_V_CC@am__nodep@)$(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(lib_clippy_CPPFLAGS) $(CPPFLAGS) $(lib_clippy_CFLAGS) $(CFLAGS) -c -o lib/clippy-defun_lex.obj `if test -f 'lib/defun_lex.c'; then $(CYGPATH_W) 'lib/defun_lex.c'; else $(CYGPATH_W) '$(srcdir)/lib/defun_lex.c'; fi` lib/clippy-graph.o: lib/graph.c @am__fastdepCC_TRUE@ $(AM_V_CC)$(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(lib_clippy_CPPFLAGS) $(CPPFLAGS) $(lib_clippy_CFLAGS) $(CFLAGS) -MT lib/clippy-graph.o -MD -MP -MF lib/$(DEPDIR)/clippy-graph.Tpo -c -o lib/clippy-graph.o `test -f 'lib/graph.c' || echo '$(srcdir)/'`lib/graph.c @am__fastdepCC_TRUE@ $(AM_V_at)$(am__mv) lib/$(DEPDIR)/clippy-graph.Tpo lib/$(DEPDIR)/clippy-graph.Po @AMDEP_TRUE@@am__fastdepCC_FALSE@ $(AM_V_CC)source='lib/graph.c' object='lib/clippy-graph.o' libtool=no @AMDEPBACKSLASH@ @AMDEP_TRUE@@am__fastdepCC_FALSE@ DEPDIR=$(DEPDIR) $(CCDEPMODE) $(depcomp) @AMDEPBACKSLASH@ @am__fastdepCC_FALSE@ $(AM_V_CC@am__nodep@)$(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(lib_clippy_CPPFLAGS) $(CPPFLAGS) $(lib_clippy_CFLAGS) $(CFLAGS) -c -o lib/clippy-graph.o `test -f 'lib/graph.c' || echo '$(srcdir)/'`lib/graph.c lib/clippy-graph.obj: lib/graph.c @am__fastdepCC_TRUE@ $(AM_V_CC)$(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(lib_clippy_CPPFLAGS) $(CPPFLAGS) $(lib_clippy_CFLAGS) $(CFLAGS) -MT lib/clippy-graph.obj -MD -MP -MF lib/$(DEPDIR)/clippy-graph.Tpo -c -o lib/clippy-graph.obj `if test -f 'lib/graph.c'; then $(CYGPATH_W) 'lib/graph.c'; else $(CYGPATH_W) '$(srcdir)/lib/graph.c'; fi` @am__fastdepCC_TRUE@ $(AM_V_at)$(am__mv) lib/$(DEPDIR)/clippy-graph.Tpo lib/$(DEPDIR)/clippy-graph.Po @AMDEP_TRUE@@am__fastdepCC_FALSE@ $(AM_V_CC)source='lib/graph.c' object='lib/clippy-graph.obj' libtool=no @AMDEPBACKSLASH@ @AMDEP_TRUE@@am__fastdepCC_FALSE@ DEPDIR=$(DEPDIR) $(CCDEPMODE) $(depcomp) @AMDEPBACKSLASH@ @am__fastdepCC_FALSE@ $(AM_V_CC@am__nodep@)$(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(lib_clippy_CPPFLAGS) $(CPPFLAGS) $(lib_clippy_CFLAGS) $(CFLAGS) -c -o lib/clippy-graph.obj `if test -f 'lib/graph.c'; then $(CYGPATH_W) 'lib/graph.c'; else $(CYGPATH_W) '$(srcdir)/lib/graph.c'; fi` lib/clippy-memory.o: lib/memory.c @am__fastdepCC_TRUE@ $(AM_V_CC)$(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(lib_clippy_CPPFLAGS) $(CPPFLAGS) $(lib_clippy_CFLAGS) $(CFLAGS) -MT lib/clippy-memory.o -MD -MP -MF lib/$(DEPDIR)/clippy-memory.Tpo -c -o lib/clippy-memory.o `test -f 'lib/memory.c' || echo '$(srcdir)/'`lib/memory.c @am__fastdepCC_TRUE@ $(AM_V_at)$(am__mv) lib/$(DEPDIR)/clippy-memory.Tpo lib/$(DEPDIR)/clippy-memory.Po @AMDEP_TRUE@@am__fastdepCC_FALSE@ $(AM_V_CC)source='lib/memory.c' object='lib/clippy-memory.o' libtool=no @AMDEPBACKSLASH@ @AMDEP_TRUE@@am__fastdepCC_FALSE@ DEPDIR=$(DEPDIR) $(CCDEPMODE) $(depcomp) @AMDEPBACKSLASH@ @am__fastdepCC_FALSE@ $(AM_V_CC@am__nodep@)$(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(lib_clippy_CPPFLAGS) $(CPPFLAGS) $(lib_clippy_CFLAGS) $(CFLAGS) -c -o lib/clippy-memory.o `test -f 'lib/memory.c' || echo '$(srcdir)/'`lib/memory.c lib/clippy-memory.obj: lib/memory.c @am__fastdepCC_TRUE@ $(AM_V_CC)$(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(lib_clippy_CPPFLAGS) $(CPPFLAGS) $(lib_clippy_CFLAGS) $(CFLAGS) -MT lib/clippy-memory.obj -MD -MP -MF lib/$(DEPDIR)/clippy-memory.Tpo -c -o lib/clippy-memory.obj `if test -f 'lib/memory.c'; then $(CYGPATH_W) 'lib/memory.c'; else $(CYGPATH_W) '$(srcdir)/lib/memory.c'; fi` @am__fastdepCC_TRUE@ $(AM_V_at)$(am__mv) lib/$(DEPDIR)/clippy-memory.Tpo lib/$(DEPDIR)/clippy-memory.Po @AMDEP_TRUE@@am__fastdepCC_FALSE@ $(AM_V_CC)source='lib/memory.c' object='lib/clippy-memory.obj' libtool=no @AMDEPBACKSLASH@ @AMDEP_TRUE@@am__fastdepCC_FALSE@ DEPDIR=$(DEPDIR) $(CCDEPMODE) $(depcomp) @AMDEPBACKSLASH@ @am__fastdepCC_FALSE@ $(AM_V_CC@am__nodep@)$(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(lib_clippy_CPPFLAGS) $(CPPFLAGS) $(lib_clippy_CFLAGS) $(CFLAGS) -c -o lib/clippy-memory.obj `if test -f 'lib/memory.c'; then $(CYGPATH_W) 'lib/memory.c'; else $(CYGPATH_W) '$(srcdir)/lib/memory.c'; fi` lib/clippy-vector.o: lib/vector.c @am__fastdepCC_TRUE@ $(AM_V_CC)$(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(lib_clippy_CPPFLAGS) $(CPPFLAGS) $(lib_clippy_CFLAGS) $(CFLAGS) -MT lib/clippy-vector.o -MD -MP -MF lib/$(DEPDIR)/clippy-vector.Tpo -c -o lib/clippy-vector.o `test -f 'lib/vector.c' || echo '$(srcdir)/'`lib/vector.c @am__fastdepCC_TRUE@ $(AM_V_at)$(am__mv) lib/$(DEPDIR)/clippy-vector.Tpo lib/$(DEPDIR)/clippy-vector.Po @AMDEP_TRUE@@am__fastdepCC_FALSE@ $(AM_V_CC)source='lib/vector.c' object='lib/clippy-vector.o' libtool=no @AMDEPBACKSLASH@ @AMDEP_TRUE@@am__fastdepCC_FALSE@ DEPDIR=$(DEPDIR) $(CCDEPMODE) $(depcomp) @AMDEPBACKSLASH@ @am__fastdepCC_FALSE@ $(AM_V_CC@am__nodep@)$(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(lib_clippy_CPPFLAGS) $(CPPFLAGS) $(lib_clippy_CFLAGS) $(CFLAGS) -c -o lib/clippy-vector.o `test -f 'lib/vector.c' || echo '$(srcdir)/'`lib/vector.c lib/clippy-vector.obj: lib/vector.c @am__fastdepCC_TRUE@ $(AM_V_CC)$(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(lib_clippy_CPPFLAGS) $(CPPFLAGS) $(lib_clippy_CFLAGS) $(CFLAGS) -MT lib/clippy-vector.obj -MD -MP -MF lib/$(DEPDIR)/clippy-vector.Tpo -c -o lib/clippy-vector.obj `if test -f 'lib/vector.c'; then $(CYGPATH_W) 'lib/vector.c'; else $(CYGPATH_W) '$(srcdir)/lib/vector.c'; fi` @am__fastdepCC_TRUE@ $(AM_V_at)$(am__mv) lib/$(DEPDIR)/clippy-vector.Tpo lib/$(DEPDIR)/clippy-vector.Po @AMDEP_TRUE@@am__fastdepCC_FALSE@ $(AM_V_CC)source='lib/vector.c' object='lib/clippy-vector.obj' libtool=no @AMDEPBACKSLASH@ @AMDEP_TRUE@@am__fastdepCC_FALSE@ DEPDIR=$(DEPDIR) $(CCDEPMODE) $(depcomp) @AMDEPBACKSLASH@ @am__fastdepCC_FALSE@ $(AM_V_CC@am__nodep@)$(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(lib_clippy_CPPFLAGS) $(CPPFLAGS) $(lib_clippy_CFLAGS) $(CFLAGS) -c -o lib/clippy-vector.obj `if test -f 'lib/vector.c'; then $(CYGPATH_W) 'lib/vector.c'; else $(CYGPATH_W) '$(srcdir)/lib/vector.c'; fi` tests/bgpd/test_aspath-test_aspath.o: tests/bgpd/test_aspath.c @am__fastdepCC_TRUE@ $(AM_V_CC)$(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(tests_bgpd_test_aspath_CPPFLAGS) $(CPPFLAGS) $(tests_bgpd_test_aspath_CFLAGS) $(CFLAGS) -MT tests/bgpd/test_aspath-test_aspath.o -MD -MP -MF tests/bgpd/$(DEPDIR)/test_aspath-test_aspath.Tpo -c -o tests/bgpd/test_aspath-test_aspath.o `test -f 'tests/bgpd/test_aspath.c' || echo '$(srcdir)/'`tests/bgpd/test_aspath.c @am__fastdepCC_TRUE@ $(AM_V_at)$(am__mv) tests/bgpd/$(DEPDIR)/test_aspath-test_aspath.Tpo tests/bgpd/$(DEPDIR)/test_aspath-test_aspath.Po @AMDEP_TRUE@@am__fastdepCC_FALSE@ $(AM_V_CC)source='tests/bgpd/test_aspath.c' object='tests/bgpd/test_aspath-test_aspath.o' libtool=no @AMDEPBACKSLASH@ @AMDEP_TRUE@@am__fastdepCC_FALSE@ DEPDIR=$(DEPDIR) $(CCDEPMODE) $(depcomp) @AMDEPBACKSLASH@ @am__fastdepCC_FALSE@ $(AM_V_CC@am__nodep@)$(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(tests_bgpd_test_aspath_CPPFLAGS) $(CPPFLAGS) $(tests_bgpd_test_aspath_CFLAGS) $(CFLAGS) -c -o tests/bgpd/test_aspath-test_aspath.o `test -f 'tests/bgpd/test_aspath.c' || echo '$(srcdir)/'`tests/bgpd/test_aspath.c tests/bgpd/test_aspath-test_aspath.obj: tests/bgpd/test_aspath.c @am__fastdepCC_TRUE@ $(AM_V_CC)$(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(tests_bgpd_test_aspath_CPPFLAGS) $(CPPFLAGS) $(tests_bgpd_test_aspath_CFLAGS) $(CFLAGS) -MT tests/bgpd/test_aspath-test_aspath.obj -MD -MP -MF tests/bgpd/$(DEPDIR)/test_aspath-test_aspath.Tpo -c -o tests/bgpd/test_aspath-test_aspath.obj `if test -f 'tests/bgpd/test_aspath.c'; then $(CYGPATH_W) 'tests/bgpd/test_aspath.c'; else $(CYGPATH_W) '$(srcdir)/tests/bgpd/test_aspath.c'; fi` @am__fastdepCC_TRUE@ $(AM_V_at)$(am__mv) tests/bgpd/$(DEPDIR)/test_aspath-test_aspath.Tpo tests/bgpd/$(DEPDIR)/test_aspath-test_aspath.Po @AMDEP_TRUE@@am__fastdepCC_FALSE@ $(AM_V_CC)source='tests/bgpd/test_aspath.c' object='tests/bgpd/test_aspath-test_aspath.obj' libtool=no @AMDEPBACKSLASH@ @AMDEP_TRUE@@am__fastdepCC_FALSE@ DEPDIR=$(DEPDIR) $(CCDEPMODE) $(depcomp) @AMDEPBACKSLASH@ @am__fastdepCC_FALSE@ $(AM_V_CC@am__nodep@)$(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(tests_bgpd_test_aspath_CPPFLAGS) $(CPPFLAGS) $(tests_bgpd_test_aspath_CFLAGS) $(CFLAGS) -c -o tests/bgpd/test_aspath-test_aspath.obj `if test -f 'tests/bgpd/test_aspath.c'; then $(CYGPATH_W) 'tests/bgpd/test_aspath.c'; else $(CYGPATH_W) '$(srcdir)/tests/bgpd/test_aspath.c'; fi` tests/bgpd/test_bgp_table-test_bgp_table.o: tests/bgpd/test_bgp_table.c @am__fastdepCC_TRUE@ $(AM_V_CC)$(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(tests_bgpd_test_bgp_table_CPPFLAGS) $(CPPFLAGS) $(tests_bgpd_test_bgp_table_CFLAGS) $(CFLAGS) -MT tests/bgpd/test_bgp_table-test_bgp_table.o -MD -MP -MF tests/bgpd/$(DEPDIR)/test_bgp_table-test_bgp_table.Tpo -c -o tests/bgpd/test_bgp_table-test_bgp_table.o `test -f 'tests/bgpd/test_bgp_table.c' || echo '$(srcdir)/'`tests/bgpd/test_bgp_table.c @am__fastdepCC_TRUE@ $(AM_V_at)$(am__mv) tests/bgpd/$(DEPDIR)/test_bgp_table-test_bgp_table.Tpo tests/bgpd/$(DEPDIR)/test_bgp_table-test_bgp_table.Po @AMDEP_TRUE@@am__fastdepCC_FALSE@ $(AM_V_CC)source='tests/bgpd/test_bgp_table.c' object='tests/bgpd/test_bgp_table-test_bgp_table.o' libtool=no @AMDEPBACKSLASH@ @AMDEP_TRUE@@am__fastdepCC_FALSE@ DEPDIR=$(DEPDIR) $(CCDEPMODE) $(depcomp) @AMDEPBACKSLASH@ @am__fastdepCC_FALSE@ $(AM_V_CC@am__nodep@)$(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(tests_bgpd_test_bgp_table_CPPFLAGS) $(CPPFLAGS) $(tests_bgpd_test_bgp_table_CFLAGS) $(CFLAGS) -c -o tests/bgpd/test_bgp_table-test_bgp_table.o `test -f 'tests/bgpd/test_bgp_table.c' || echo '$(srcdir)/'`tests/bgpd/test_bgp_table.c tests/bgpd/test_bgp_table-test_bgp_table.obj: tests/bgpd/test_bgp_table.c @am__fastdepCC_TRUE@ $(AM_V_CC)$(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(tests_bgpd_test_bgp_table_CPPFLAGS) $(CPPFLAGS) $(tests_bgpd_test_bgp_table_CFLAGS) $(CFLAGS) -MT tests/bgpd/test_bgp_table-test_bgp_table.obj -MD -MP -MF tests/bgpd/$(DEPDIR)/test_bgp_table-test_bgp_table.Tpo -c -o tests/bgpd/test_bgp_table-test_bgp_table.obj `if test -f 'tests/bgpd/test_bgp_table.c'; then $(CYGPATH_W) 'tests/bgpd/test_bgp_table.c'; else $(CYGPATH_W) '$(srcdir)/tests/bgpd/test_bgp_table.c'; fi` @am__fastdepCC_TRUE@ $(AM_V_at)$(am__mv) tests/bgpd/$(DEPDIR)/test_bgp_table-test_bgp_table.Tpo tests/bgpd/$(DEPDIR)/test_bgp_table-test_bgp_table.Po @AMDEP_TRUE@@am__fastdepCC_FALSE@ $(AM_V_CC)source='tests/bgpd/test_bgp_table.c' object='tests/bgpd/test_bgp_table-test_bgp_table.obj' libtool=no @AMDEPBACKSLASH@ @AMDEP_TRUE@@am__fastdepCC_FALSE@ DEPDIR=$(DEPDIR) $(CCDEPMODE) $(depcomp) @AMDEPBACKSLASH@ @am__fastdepCC_FALSE@ $(AM_V_CC@am__nodep@)$(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(tests_bgpd_test_bgp_table_CPPFLAGS) $(CPPFLAGS) $(tests_bgpd_test_bgp_table_CFLAGS) $(CFLAGS) -c -o tests/bgpd/test_bgp_table-test_bgp_table.obj `if test -f 'tests/bgpd/test_bgp_table.c'; then $(CYGPATH_W) 'tests/bgpd/test_bgp_table.c'; else $(CYGPATH_W) '$(srcdir)/tests/bgpd/test_bgp_table.c'; fi` tests/bgpd/test_capability-test_capability.o: tests/bgpd/test_capability.c @am__fastdepCC_TRUE@ $(AM_V_CC)$(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(tests_bgpd_test_capability_CPPFLAGS) $(CPPFLAGS) $(tests_bgpd_test_capability_CFLAGS) $(CFLAGS) -MT tests/bgpd/test_capability-test_capability.o -MD -MP -MF tests/bgpd/$(DEPDIR)/test_capability-test_capability.Tpo -c -o tests/bgpd/test_capability-test_capability.o `test -f 'tests/bgpd/test_capability.c' || echo '$(srcdir)/'`tests/bgpd/test_capability.c @am__fastdepCC_TRUE@ $(AM_V_at)$(am__mv) tests/bgpd/$(DEPDIR)/test_capability-test_capability.Tpo tests/bgpd/$(DEPDIR)/test_capability-test_capability.Po @AMDEP_TRUE@@am__fastdepCC_FALSE@ $(AM_V_CC)source='tests/bgpd/test_capability.c' object='tests/bgpd/test_capability-test_capability.o' libtool=no @AMDEPBACKSLASH@ @AMDEP_TRUE@@am__fastdepCC_FALSE@ DEPDIR=$(DEPDIR) $(CCDEPMODE) $(depcomp) @AMDEPBACKSLASH@ @am__fastdepCC_FALSE@ $(AM_V_CC@am__nodep@)$(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(tests_bgpd_test_capability_CPPFLAGS) $(CPPFLAGS) $(tests_bgpd_test_capability_CFLAGS) $(CFLAGS) -c -o tests/bgpd/test_capability-test_capability.o `test -f 'tests/bgpd/test_capability.c' || echo '$(srcdir)/'`tests/bgpd/test_capability.c tests/bgpd/test_capability-test_capability.obj: tests/bgpd/test_capability.c @am__fastdepCC_TRUE@ $(AM_V_CC)$(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(tests_bgpd_test_capability_CPPFLAGS) $(CPPFLAGS) $(tests_bgpd_test_capability_CFLAGS) $(CFLAGS) -MT tests/bgpd/test_capability-test_capability.obj -MD -MP -MF tests/bgpd/$(DEPDIR)/test_capability-test_capability.Tpo -c -o tests/bgpd/test_capability-test_capability.obj `if test -f 'tests/bgpd/test_capability.c'; then $(CYGPATH_W) 'tests/bgpd/test_capability.c'; else $(CYGPATH_W) '$(srcdir)/tests/bgpd/test_capability.c'; fi` @am__fastdepCC_TRUE@ $(AM_V_at)$(am__mv) tests/bgpd/$(DEPDIR)/test_capability-test_capability.Tpo tests/bgpd/$(DEPDIR)/test_capability-test_capability.Po @AMDEP_TRUE@@am__fastdepCC_FALSE@ $(AM_V_CC)source='tests/bgpd/test_capability.c' object='tests/bgpd/test_capability-test_capability.obj' libtool=no @AMDEPBACKSLASH@ @AMDEP_TRUE@@am__fastdepCC_FALSE@ DEPDIR=$(DEPDIR) $(CCDEPMODE) $(depcomp) @AMDEPBACKSLASH@ @am__fastdepCC_FALSE@ $(AM_V_CC@am__nodep@)$(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(tests_bgpd_test_capability_CPPFLAGS) $(CPPFLAGS) $(tests_bgpd_test_capability_CFLAGS) $(CFLAGS) -c -o tests/bgpd/test_capability-test_capability.obj `if test -f 'tests/bgpd/test_capability.c'; then $(CYGPATH_W) 'tests/bgpd/test_capability.c'; else $(CYGPATH_W) '$(srcdir)/tests/bgpd/test_capability.c'; fi` tests/bgpd/test_ecommunity-test_ecommunity.o: tests/bgpd/test_ecommunity.c @am__fastdepCC_TRUE@ $(AM_V_CC)$(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(tests_bgpd_test_ecommunity_CPPFLAGS) $(CPPFLAGS) $(tests_bgpd_test_ecommunity_CFLAGS) $(CFLAGS) -MT tests/bgpd/test_ecommunity-test_ecommunity.o -MD -MP -MF tests/bgpd/$(DEPDIR)/test_ecommunity-test_ecommunity.Tpo -c -o tests/bgpd/test_ecommunity-test_ecommunity.o `test -f 'tests/bgpd/test_ecommunity.c' || echo '$(srcdir)/'`tests/bgpd/test_ecommunity.c @am__fastdepCC_TRUE@ $(AM_V_at)$(am__mv) tests/bgpd/$(DEPDIR)/test_ecommunity-test_ecommunity.Tpo tests/bgpd/$(DEPDIR)/test_ecommunity-test_ecommunity.Po @AMDEP_TRUE@@am__fastdepCC_FALSE@ $(AM_V_CC)source='tests/bgpd/test_ecommunity.c' object='tests/bgpd/test_ecommunity-test_ecommunity.o' libtool=no @AMDEPBACKSLASH@ @AMDEP_TRUE@@am__fastdepCC_FALSE@ DEPDIR=$(DEPDIR) $(CCDEPMODE) $(depcomp) @AMDEPBACKSLASH@ @am__fastdepCC_FALSE@ $(AM_V_CC@am__nodep@)$(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(tests_bgpd_test_ecommunity_CPPFLAGS) $(CPPFLAGS) $(tests_bgpd_test_ecommunity_CFLAGS) $(CFLAGS) -c -o tests/bgpd/test_ecommunity-test_ecommunity.o `test -f 'tests/bgpd/test_ecommunity.c' || echo '$(srcdir)/'`tests/bgpd/test_ecommunity.c tests/bgpd/test_ecommunity-test_ecommunity.obj: tests/bgpd/test_ecommunity.c @am__fastdepCC_TRUE@ $(AM_V_CC)$(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(tests_bgpd_test_ecommunity_CPPFLAGS) $(CPPFLAGS) $(tests_bgpd_test_ecommunity_CFLAGS) $(CFLAGS) -MT tests/bgpd/test_ecommunity-test_ecommunity.obj -MD -MP -MF tests/bgpd/$(DEPDIR)/test_ecommunity-test_ecommunity.Tpo -c -o tests/bgpd/test_ecommunity-test_ecommunity.obj `if test -f 'tests/bgpd/test_ecommunity.c'; then $(CYGPATH_W) 'tests/bgpd/test_ecommunity.c'; else $(CYGPATH_W) '$(srcdir)/tests/bgpd/test_ecommunity.c'; fi` @am__fastdepCC_TRUE@ $(AM_V_at)$(am__mv) tests/bgpd/$(DEPDIR)/test_ecommunity-test_ecommunity.Tpo tests/bgpd/$(DEPDIR)/test_ecommunity-test_ecommunity.Po @AMDEP_TRUE@@am__fastdepCC_FALSE@ $(AM_V_CC)source='tests/bgpd/test_ecommunity.c' object='tests/bgpd/test_ecommunity-test_ecommunity.obj' libtool=no @AMDEPBACKSLASH@ @AMDEP_TRUE@@am__fastdepCC_FALSE@ DEPDIR=$(DEPDIR) $(CCDEPMODE) $(depcomp) @AMDEPBACKSLASH@ @am__fastdepCC_FALSE@ $(AM_V_CC@am__nodep@)$(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(tests_bgpd_test_ecommunity_CPPFLAGS) $(CPPFLAGS) $(tests_bgpd_test_ecommunity_CFLAGS) $(CFLAGS) -c -o tests/bgpd/test_ecommunity-test_ecommunity.obj `if test -f 'tests/bgpd/test_ecommunity.c'; then $(CYGPATH_W) 'tests/bgpd/test_ecommunity.c'; else $(CYGPATH_W) '$(srcdir)/tests/bgpd/test_ecommunity.c'; fi` tests/bgpd/test_mp_attr-test_mp_attr.o: tests/bgpd/test_mp_attr.c @am__fastdepCC_TRUE@ $(AM_V_CC)$(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(tests_bgpd_test_mp_attr_CPPFLAGS) $(CPPFLAGS) $(tests_bgpd_test_mp_attr_CFLAGS) $(CFLAGS) -MT tests/bgpd/test_mp_attr-test_mp_attr.o -MD -MP -MF tests/bgpd/$(DEPDIR)/test_mp_attr-test_mp_attr.Tpo -c -o tests/bgpd/test_mp_attr-test_mp_attr.o `test -f 'tests/bgpd/test_mp_attr.c' || echo '$(srcdir)/'`tests/bgpd/test_mp_attr.c @am__fastdepCC_TRUE@ $(AM_V_at)$(am__mv) tests/bgpd/$(DEPDIR)/test_mp_attr-test_mp_attr.Tpo tests/bgpd/$(DEPDIR)/test_mp_attr-test_mp_attr.Po @AMDEP_TRUE@@am__fastdepCC_FALSE@ $(AM_V_CC)source='tests/bgpd/test_mp_attr.c' object='tests/bgpd/test_mp_attr-test_mp_attr.o' libtool=no @AMDEPBACKSLASH@ @AMDEP_TRUE@@am__fastdepCC_FALSE@ DEPDIR=$(DEPDIR) $(CCDEPMODE) $(depcomp) @AMDEPBACKSLASH@ @am__fastdepCC_FALSE@ $(AM_V_CC@am__nodep@)$(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(tests_bgpd_test_mp_attr_CPPFLAGS) $(CPPFLAGS) $(tests_bgpd_test_mp_attr_CFLAGS) $(CFLAGS) -c -o tests/bgpd/test_mp_attr-test_mp_attr.o `test -f 'tests/bgpd/test_mp_attr.c' || echo '$(srcdir)/'`tests/bgpd/test_mp_attr.c tests/bgpd/test_mp_attr-test_mp_attr.obj: tests/bgpd/test_mp_attr.c @am__fastdepCC_TRUE@ $(AM_V_CC)$(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(tests_bgpd_test_mp_attr_CPPFLAGS) $(CPPFLAGS) $(tests_bgpd_test_mp_attr_CFLAGS) $(CFLAGS) -MT tests/bgpd/test_mp_attr-test_mp_attr.obj -MD -MP -MF tests/bgpd/$(DEPDIR)/test_mp_attr-test_mp_attr.Tpo -c -o tests/bgpd/test_mp_attr-test_mp_attr.obj `if test -f 'tests/bgpd/test_mp_attr.c'; then $(CYGPATH_W) 'tests/bgpd/test_mp_attr.c'; else $(CYGPATH_W) '$(srcdir)/tests/bgpd/test_mp_attr.c'; fi` @am__fastdepCC_TRUE@ $(AM_V_at)$(am__mv) tests/bgpd/$(DEPDIR)/test_mp_attr-test_mp_attr.Tpo tests/bgpd/$(DEPDIR)/test_mp_attr-test_mp_attr.Po @AMDEP_TRUE@@am__fastdepCC_FALSE@ $(AM_V_CC)source='tests/bgpd/test_mp_attr.c' object='tests/bgpd/test_mp_attr-test_mp_attr.obj' libtool=no @AMDEPBACKSLASH@ @AMDEP_TRUE@@am__fastdepCC_FALSE@ DEPDIR=$(DEPDIR) $(CCDEPMODE) $(depcomp) @AMDEPBACKSLASH@ @am__fastdepCC_FALSE@ $(AM_V_CC@am__nodep@)$(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(tests_bgpd_test_mp_attr_CPPFLAGS) $(CPPFLAGS) $(tests_bgpd_test_mp_attr_CFLAGS) $(CFLAGS) -c -o tests/bgpd/test_mp_attr-test_mp_attr.obj `if test -f 'tests/bgpd/test_mp_attr.c'; then $(CYGPATH_W) 'tests/bgpd/test_mp_attr.c'; else $(CYGPATH_W) '$(srcdir)/tests/bgpd/test_mp_attr.c'; fi` tests/bgpd/test_mpath-test_mpath.o: tests/bgpd/test_mpath.c @am__fastdepCC_TRUE@ $(AM_V_CC)$(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(tests_bgpd_test_mpath_CPPFLAGS) $(CPPFLAGS) $(tests_bgpd_test_mpath_CFLAGS) $(CFLAGS) -MT tests/bgpd/test_mpath-test_mpath.o -MD -MP -MF tests/bgpd/$(DEPDIR)/test_mpath-test_mpath.Tpo -c -o tests/bgpd/test_mpath-test_mpath.o `test -f 'tests/bgpd/test_mpath.c' || echo '$(srcdir)/'`tests/bgpd/test_mpath.c @am__fastdepCC_TRUE@ $(AM_V_at)$(am__mv) tests/bgpd/$(DEPDIR)/test_mpath-test_mpath.Tpo tests/bgpd/$(DEPDIR)/test_mpath-test_mpath.Po @AMDEP_TRUE@@am__fastdepCC_FALSE@ $(AM_V_CC)source='tests/bgpd/test_mpath.c' object='tests/bgpd/test_mpath-test_mpath.o' libtool=no @AMDEPBACKSLASH@ @AMDEP_TRUE@@am__fastdepCC_FALSE@ DEPDIR=$(DEPDIR) $(CCDEPMODE) $(depcomp) @AMDEPBACKSLASH@ @am__fastdepCC_FALSE@ $(AM_V_CC@am__nodep@)$(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(tests_bgpd_test_mpath_CPPFLAGS) $(CPPFLAGS) $(tests_bgpd_test_mpath_CFLAGS) $(CFLAGS) -c -o tests/bgpd/test_mpath-test_mpath.o `test -f 'tests/bgpd/test_mpath.c' || echo '$(srcdir)/'`tests/bgpd/test_mpath.c tests/bgpd/test_mpath-test_mpath.obj: tests/bgpd/test_mpath.c @am__fastdepCC_TRUE@ $(AM_V_CC)$(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(tests_bgpd_test_mpath_CPPFLAGS) $(CPPFLAGS) $(tests_bgpd_test_mpath_CFLAGS) $(CFLAGS) -MT tests/bgpd/test_mpath-test_mpath.obj -MD -MP -MF tests/bgpd/$(DEPDIR)/test_mpath-test_mpath.Tpo -c -o tests/bgpd/test_mpath-test_mpath.obj `if test -f 'tests/bgpd/test_mpath.c'; then $(CYGPATH_W) 'tests/bgpd/test_mpath.c'; else $(CYGPATH_W) '$(srcdir)/tests/bgpd/test_mpath.c'; fi` @am__fastdepCC_TRUE@ $(AM_V_at)$(am__mv) tests/bgpd/$(DEPDIR)/test_mpath-test_mpath.Tpo tests/bgpd/$(DEPDIR)/test_mpath-test_mpath.Po @AMDEP_TRUE@@am__fastdepCC_FALSE@ $(AM_V_CC)source='tests/bgpd/test_mpath.c' object='tests/bgpd/test_mpath-test_mpath.obj' libtool=no @AMDEPBACKSLASH@ @AMDEP_TRUE@@am__fastdepCC_FALSE@ DEPDIR=$(DEPDIR) $(CCDEPMODE) $(depcomp) @AMDEPBACKSLASH@ @am__fastdepCC_FALSE@ $(AM_V_CC@am__nodep@)$(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(tests_bgpd_test_mpath_CPPFLAGS) $(CPPFLAGS) $(tests_bgpd_test_mpath_CFLAGS) $(CFLAGS) -c -o tests/bgpd/test_mpath-test_mpath.obj `if test -f 'tests/bgpd/test_mpath.c'; then $(CYGPATH_W) 'tests/bgpd/test_mpath.c'; else $(CYGPATH_W) '$(srcdir)/tests/bgpd/test_mpath.c'; fi` tests/bgpd/test_packet-test_packet.o: tests/bgpd/test_packet.c @am__fastdepCC_TRUE@ $(AM_V_CC)$(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(tests_bgpd_test_packet_CPPFLAGS) $(CPPFLAGS) $(tests_bgpd_test_packet_CFLAGS) $(CFLAGS) -MT tests/bgpd/test_packet-test_packet.o -MD -MP -MF tests/bgpd/$(DEPDIR)/test_packet-test_packet.Tpo -c -o tests/bgpd/test_packet-test_packet.o `test -f 'tests/bgpd/test_packet.c' || echo '$(srcdir)/'`tests/bgpd/test_packet.c @am__fastdepCC_TRUE@ $(AM_V_at)$(am__mv) tests/bgpd/$(DEPDIR)/test_packet-test_packet.Tpo tests/bgpd/$(DEPDIR)/test_packet-test_packet.Po @AMDEP_TRUE@@am__fastdepCC_FALSE@ $(AM_V_CC)source='tests/bgpd/test_packet.c' object='tests/bgpd/test_packet-test_packet.o' libtool=no @AMDEPBACKSLASH@ @AMDEP_TRUE@@am__fastdepCC_FALSE@ DEPDIR=$(DEPDIR) $(CCDEPMODE) $(depcomp) @AMDEPBACKSLASH@ @am__fastdepCC_FALSE@ $(AM_V_CC@am__nodep@)$(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(tests_bgpd_test_packet_CPPFLAGS) $(CPPFLAGS) $(tests_bgpd_test_packet_CFLAGS) $(CFLAGS) -c -o tests/bgpd/test_packet-test_packet.o `test -f 'tests/bgpd/test_packet.c' || echo '$(srcdir)/'`tests/bgpd/test_packet.c tests/bgpd/test_packet-test_packet.obj: tests/bgpd/test_packet.c @am__fastdepCC_TRUE@ $(AM_V_CC)$(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(tests_bgpd_test_packet_CPPFLAGS) $(CPPFLAGS) $(tests_bgpd_test_packet_CFLAGS) $(CFLAGS) -MT tests/bgpd/test_packet-test_packet.obj -MD -MP -MF tests/bgpd/$(DEPDIR)/test_packet-test_packet.Tpo -c -o tests/bgpd/test_packet-test_packet.obj `if test -f 'tests/bgpd/test_packet.c'; then $(CYGPATH_W) 'tests/bgpd/test_packet.c'; else $(CYGPATH_W) '$(srcdir)/tests/bgpd/test_packet.c'; fi` @am__fastdepCC_TRUE@ $(AM_V_at)$(am__mv) tests/bgpd/$(DEPDIR)/test_packet-test_packet.Tpo tests/bgpd/$(DEPDIR)/test_packet-test_packet.Po @AMDEP_TRUE@@am__fastdepCC_FALSE@ $(AM_V_CC)source='tests/bgpd/test_packet.c' object='tests/bgpd/test_packet-test_packet.obj' libtool=no @AMDEPBACKSLASH@ @AMDEP_TRUE@@am__fastdepCC_FALSE@ DEPDIR=$(DEPDIR) $(CCDEPMODE) $(depcomp) @AMDEPBACKSLASH@ @am__fastdepCC_FALSE@ $(AM_V_CC@am__nodep@)$(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(tests_bgpd_test_packet_CPPFLAGS) $(CPPFLAGS) $(tests_bgpd_test_packet_CFLAGS) $(CFLAGS) -c -o tests/bgpd/test_packet-test_packet.obj `if test -f 'tests/bgpd/test_packet.c'; then $(CYGPATH_W) 'tests/bgpd/test_packet.c'; else $(CYGPATH_W) '$(srcdir)/tests/bgpd/test_packet.c'; fi` tests/bgpd/test_peer_attr-test_peer_attr.o: tests/bgpd/test_peer_attr.c @am__fastdepCC_TRUE@ $(AM_V_CC)$(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(tests_bgpd_test_peer_attr_CPPFLAGS) $(CPPFLAGS) $(tests_bgpd_test_peer_attr_CFLAGS) $(CFLAGS) -MT tests/bgpd/test_peer_attr-test_peer_attr.o -MD -MP -MF tests/bgpd/$(DEPDIR)/test_peer_attr-test_peer_attr.Tpo -c -o tests/bgpd/test_peer_attr-test_peer_attr.o `test -f 'tests/bgpd/test_peer_attr.c' || echo '$(srcdir)/'`tests/bgpd/test_peer_attr.c @am__fastdepCC_TRUE@ $(AM_V_at)$(am__mv) tests/bgpd/$(DEPDIR)/test_peer_attr-test_peer_attr.Tpo tests/bgpd/$(DEPDIR)/test_peer_attr-test_peer_attr.Po @AMDEP_TRUE@@am__fastdepCC_FALSE@ $(AM_V_CC)source='tests/bgpd/test_peer_attr.c' object='tests/bgpd/test_peer_attr-test_peer_attr.o' libtool=no @AMDEPBACKSLASH@ @AMDEP_TRUE@@am__fastdepCC_FALSE@ DEPDIR=$(DEPDIR) $(CCDEPMODE) $(depcomp) @AMDEPBACKSLASH@ @am__fastdepCC_FALSE@ $(AM_V_CC@am__nodep@)$(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(tests_bgpd_test_peer_attr_CPPFLAGS) $(CPPFLAGS) $(tests_bgpd_test_peer_attr_CFLAGS) $(CFLAGS) -c -o tests/bgpd/test_peer_attr-test_peer_attr.o `test -f 'tests/bgpd/test_peer_attr.c' || echo '$(srcdir)/'`tests/bgpd/test_peer_attr.c tests/bgpd/test_peer_attr-test_peer_attr.obj: tests/bgpd/test_peer_attr.c @am__fastdepCC_TRUE@ $(AM_V_CC)$(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(tests_bgpd_test_peer_attr_CPPFLAGS) $(CPPFLAGS) $(tests_bgpd_test_peer_attr_CFLAGS) $(CFLAGS) -MT tests/bgpd/test_peer_attr-test_peer_attr.obj -MD -MP -MF tests/bgpd/$(DEPDIR)/test_peer_attr-test_peer_attr.Tpo -c -o tests/bgpd/test_peer_attr-test_peer_attr.obj `if test -f 'tests/bgpd/test_peer_attr.c'; then $(CYGPATH_W) 'tests/bgpd/test_peer_attr.c'; else $(CYGPATH_W) '$(srcdir)/tests/bgpd/test_peer_attr.c'; fi` @am__fastdepCC_TRUE@ $(AM_V_at)$(am__mv) tests/bgpd/$(DEPDIR)/test_peer_attr-test_peer_attr.Tpo tests/bgpd/$(DEPDIR)/test_peer_attr-test_peer_attr.Po @AMDEP_TRUE@@am__fastdepCC_FALSE@ $(AM_V_CC)source='tests/bgpd/test_peer_attr.c' object='tests/bgpd/test_peer_attr-test_peer_attr.obj' libtool=no @AMDEPBACKSLASH@ @AMDEP_TRUE@@am__fastdepCC_FALSE@ DEPDIR=$(DEPDIR) $(CCDEPMODE) $(depcomp) @AMDEPBACKSLASH@ @am__fastdepCC_FALSE@ $(AM_V_CC@am__nodep@)$(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(tests_bgpd_test_peer_attr_CPPFLAGS) $(CPPFLAGS) $(tests_bgpd_test_peer_attr_CFLAGS) $(CFLAGS) -c -o tests/bgpd/test_peer_attr-test_peer_attr.obj `if test -f 'tests/bgpd/test_peer_attr.c'; then $(CYGPATH_W) 'tests/bgpd/test_peer_attr.c'; else $(CYGPATH_W) '$(srcdir)/tests/bgpd/test_peer_attr.c'; fi` tests/isisd/test_fuzz_isis_tlv-test_fuzz_isis_tlv.o: tests/isisd/test_fuzz_isis_tlv.c @am__fastdepCC_TRUE@ $(AM_V_CC)$(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(tests_isisd_test_fuzz_isis_tlv_CPPFLAGS) $(CPPFLAGS) $(tests_isisd_test_fuzz_isis_tlv_CFLAGS) $(CFLAGS) -MT tests/isisd/test_fuzz_isis_tlv-test_fuzz_isis_tlv.o -MD -MP -MF tests/isisd/$(DEPDIR)/test_fuzz_isis_tlv-test_fuzz_isis_tlv.Tpo -c -o tests/isisd/test_fuzz_isis_tlv-test_fuzz_isis_tlv.o `test -f 'tests/isisd/test_fuzz_isis_tlv.c' || echo '$(srcdir)/'`tests/isisd/test_fuzz_isis_tlv.c @am__fastdepCC_TRUE@ $(AM_V_at)$(am__mv) tests/isisd/$(DEPDIR)/test_fuzz_isis_tlv-test_fuzz_isis_tlv.Tpo tests/isisd/$(DEPDIR)/test_fuzz_isis_tlv-test_fuzz_isis_tlv.Po @AMDEP_TRUE@@am__fastdepCC_FALSE@ $(AM_V_CC)source='tests/isisd/test_fuzz_isis_tlv.c' object='tests/isisd/test_fuzz_isis_tlv-test_fuzz_isis_tlv.o' libtool=no @AMDEPBACKSLASH@ @AMDEP_TRUE@@am__fastdepCC_FALSE@ DEPDIR=$(DEPDIR) $(CCDEPMODE) $(depcomp) @AMDEPBACKSLASH@ @am__fastdepCC_FALSE@ $(AM_V_CC@am__nodep@)$(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(tests_isisd_test_fuzz_isis_tlv_CPPFLAGS) $(CPPFLAGS) $(tests_isisd_test_fuzz_isis_tlv_CFLAGS) $(CFLAGS) -c -o tests/isisd/test_fuzz_isis_tlv-test_fuzz_isis_tlv.o `test -f 'tests/isisd/test_fuzz_isis_tlv.c' || echo '$(srcdir)/'`tests/isisd/test_fuzz_isis_tlv.c tests/isisd/test_fuzz_isis_tlv-test_fuzz_isis_tlv.obj: tests/isisd/test_fuzz_isis_tlv.c @am__fastdepCC_TRUE@ $(AM_V_CC)$(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(tests_isisd_test_fuzz_isis_tlv_CPPFLAGS) $(CPPFLAGS) $(tests_isisd_test_fuzz_isis_tlv_CFLAGS) $(CFLAGS) -MT tests/isisd/test_fuzz_isis_tlv-test_fuzz_isis_tlv.obj -MD -MP -MF tests/isisd/$(DEPDIR)/test_fuzz_isis_tlv-test_fuzz_isis_tlv.Tpo -c -o tests/isisd/test_fuzz_isis_tlv-test_fuzz_isis_tlv.obj `if test -f 'tests/isisd/test_fuzz_isis_tlv.c'; then $(CYGPATH_W) 'tests/isisd/test_fuzz_isis_tlv.c'; else $(CYGPATH_W) '$(srcdir)/tests/isisd/test_fuzz_isis_tlv.c'; fi` @am__fastdepCC_TRUE@ $(AM_V_at)$(am__mv) tests/isisd/$(DEPDIR)/test_fuzz_isis_tlv-test_fuzz_isis_tlv.Tpo tests/isisd/$(DEPDIR)/test_fuzz_isis_tlv-test_fuzz_isis_tlv.Po @AMDEP_TRUE@@am__fastdepCC_FALSE@ $(AM_V_CC)source='tests/isisd/test_fuzz_isis_tlv.c' object='tests/isisd/test_fuzz_isis_tlv-test_fuzz_isis_tlv.obj' libtool=no @AMDEPBACKSLASH@ @AMDEP_TRUE@@am__fastdepCC_FALSE@ DEPDIR=$(DEPDIR) $(CCDEPMODE) $(depcomp) @AMDEPBACKSLASH@ @am__fastdepCC_FALSE@ $(AM_V_CC@am__nodep@)$(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(tests_isisd_test_fuzz_isis_tlv_CPPFLAGS) $(CPPFLAGS) $(tests_isisd_test_fuzz_isis_tlv_CFLAGS) $(CFLAGS) -c -o tests/isisd/test_fuzz_isis_tlv-test_fuzz_isis_tlv.obj `if test -f 'tests/isisd/test_fuzz_isis_tlv.c'; then $(CYGPATH_W) 'tests/isisd/test_fuzz_isis_tlv.c'; else $(CYGPATH_W) '$(srcdir)/tests/isisd/test_fuzz_isis_tlv.c'; fi` tests/isisd/test_isis_lspdb-test_isis_lspdb.o: tests/isisd/test_isis_lspdb.c @am__fastdepCC_TRUE@ $(AM_V_CC)$(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(tests_isisd_test_isis_lspdb_CPPFLAGS) $(CPPFLAGS) $(tests_isisd_test_isis_lspdb_CFLAGS) $(CFLAGS) -MT tests/isisd/test_isis_lspdb-test_isis_lspdb.o -MD -MP -MF tests/isisd/$(DEPDIR)/test_isis_lspdb-test_isis_lspdb.Tpo -c -o tests/isisd/test_isis_lspdb-test_isis_lspdb.o `test -f 'tests/isisd/test_isis_lspdb.c' || echo '$(srcdir)/'`tests/isisd/test_isis_lspdb.c @am__fastdepCC_TRUE@ $(AM_V_at)$(am__mv) tests/isisd/$(DEPDIR)/test_isis_lspdb-test_isis_lspdb.Tpo tests/isisd/$(DEPDIR)/test_isis_lspdb-test_isis_lspdb.Po @AMDEP_TRUE@@am__fastdepCC_FALSE@ $(AM_V_CC)source='tests/isisd/test_isis_lspdb.c' object='tests/isisd/test_isis_lspdb-test_isis_lspdb.o' libtool=no @AMDEPBACKSLASH@ @AMDEP_TRUE@@am__fastdepCC_FALSE@ DEPDIR=$(DEPDIR) $(CCDEPMODE) $(depcomp) @AMDEPBACKSLASH@ @am__fastdepCC_FALSE@ $(AM_V_CC@am__nodep@)$(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(tests_isisd_test_isis_lspdb_CPPFLAGS) $(CPPFLAGS) $(tests_isisd_test_isis_lspdb_CFLAGS) $(CFLAGS) -c -o tests/isisd/test_isis_lspdb-test_isis_lspdb.o `test -f 'tests/isisd/test_isis_lspdb.c' || echo '$(srcdir)/'`tests/isisd/test_isis_lspdb.c tests/isisd/test_isis_lspdb-test_isis_lspdb.obj: tests/isisd/test_isis_lspdb.c @am__fastdepCC_TRUE@ $(AM_V_CC)$(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(tests_isisd_test_isis_lspdb_CPPFLAGS) $(CPPFLAGS) $(tests_isisd_test_isis_lspdb_CFLAGS) $(CFLAGS) -MT tests/isisd/test_isis_lspdb-test_isis_lspdb.obj -MD -MP -MF tests/isisd/$(DEPDIR)/test_isis_lspdb-test_isis_lspdb.Tpo -c -o tests/isisd/test_isis_lspdb-test_isis_lspdb.obj `if test -f 'tests/isisd/test_isis_lspdb.c'; then $(CYGPATH_W) 'tests/isisd/test_isis_lspdb.c'; else $(CYGPATH_W) '$(srcdir)/tests/isisd/test_isis_lspdb.c'; fi` @am__fastdepCC_TRUE@ $(AM_V_at)$(am__mv) tests/isisd/$(DEPDIR)/test_isis_lspdb-test_isis_lspdb.Tpo tests/isisd/$(DEPDIR)/test_isis_lspdb-test_isis_lspdb.Po @AMDEP_TRUE@@am__fastdepCC_FALSE@ $(AM_V_CC)source='tests/isisd/test_isis_lspdb.c' object='tests/isisd/test_isis_lspdb-test_isis_lspdb.obj' libtool=no @AMDEPBACKSLASH@ @AMDEP_TRUE@@am__fastdepCC_FALSE@ DEPDIR=$(DEPDIR) $(CCDEPMODE) $(depcomp) @AMDEPBACKSLASH@ @am__fastdepCC_FALSE@ $(AM_V_CC@am__nodep@)$(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(tests_isisd_test_isis_lspdb_CPPFLAGS) $(CPPFLAGS) $(tests_isisd_test_isis_lspdb_CFLAGS) $(CFLAGS) -c -o tests/isisd/test_isis_lspdb-test_isis_lspdb.obj `if test -f 'tests/isisd/test_isis_lspdb.c'; then $(CYGPATH_W) 'tests/isisd/test_isis_lspdb.c'; else $(CYGPATH_W) '$(srcdir)/tests/isisd/test_isis_lspdb.c'; fi` tests/isisd/test_isis_vertex_queue-test_isis_vertex_queue.o: tests/isisd/test_isis_vertex_queue.c @am__fastdepCC_TRUE@ $(AM_V_CC)$(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(tests_isisd_test_isis_vertex_queue_CPPFLAGS) $(CPPFLAGS) $(tests_isisd_test_isis_vertex_queue_CFLAGS) $(CFLAGS) -MT tests/isisd/test_isis_vertex_queue-test_isis_vertex_queue.o -MD -MP -MF tests/isisd/$(DEPDIR)/test_isis_vertex_queue-test_isis_vertex_queue.Tpo -c -o tests/isisd/test_isis_vertex_queue-test_isis_vertex_queue.o `test -f 'tests/isisd/test_isis_vertex_queue.c' || echo '$(srcdir)/'`tests/isisd/test_isis_vertex_queue.c @am__fastdepCC_TRUE@ $(AM_V_at)$(am__mv) tests/isisd/$(DEPDIR)/test_isis_vertex_queue-test_isis_vertex_queue.Tpo tests/isisd/$(DEPDIR)/test_isis_vertex_queue-test_isis_vertex_queue.Po @AMDEP_TRUE@@am__fastdepCC_FALSE@ $(AM_V_CC)source='tests/isisd/test_isis_vertex_queue.c' object='tests/isisd/test_isis_vertex_queue-test_isis_vertex_queue.o' libtool=no @AMDEPBACKSLASH@ @AMDEP_TRUE@@am__fastdepCC_FALSE@ DEPDIR=$(DEPDIR) $(CCDEPMODE) $(depcomp) @AMDEPBACKSLASH@ @am__fastdepCC_FALSE@ $(AM_V_CC@am__nodep@)$(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(tests_isisd_test_isis_vertex_queue_CPPFLAGS) $(CPPFLAGS) $(tests_isisd_test_isis_vertex_queue_CFLAGS) $(CFLAGS) -c -o tests/isisd/test_isis_vertex_queue-test_isis_vertex_queue.o `test -f 'tests/isisd/test_isis_vertex_queue.c' || echo '$(srcdir)/'`tests/isisd/test_isis_vertex_queue.c tests/isisd/test_isis_vertex_queue-test_isis_vertex_queue.obj: tests/isisd/test_isis_vertex_queue.c @am__fastdepCC_TRUE@ $(AM_V_CC)$(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(tests_isisd_test_isis_vertex_queue_CPPFLAGS) $(CPPFLAGS) $(tests_isisd_test_isis_vertex_queue_CFLAGS) $(CFLAGS) -MT tests/isisd/test_isis_vertex_queue-test_isis_vertex_queue.obj -MD -MP -MF tests/isisd/$(DEPDIR)/test_isis_vertex_queue-test_isis_vertex_queue.Tpo -c -o tests/isisd/test_isis_vertex_queue-test_isis_vertex_queue.obj `if test -f 'tests/isisd/test_isis_vertex_queue.c'; then $(CYGPATH_W) 'tests/isisd/test_isis_vertex_queue.c'; else $(CYGPATH_W) '$(srcdir)/tests/isisd/test_isis_vertex_queue.c'; fi` @am__fastdepCC_TRUE@ $(AM_V_at)$(am__mv) tests/isisd/$(DEPDIR)/test_isis_vertex_queue-test_isis_vertex_queue.Tpo tests/isisd/$(DEPDIR)/test_isis_vertex_queue-test_isis_vertex_queue.Po @AMDEP_TRUE@@am__fastdepCC_FALSE@ $(AM_V_CC)source='tests/isisd/test_isis_vertex_queue.c' object='tests/isisd/test_isis_vertex_queue-test_isis_vertex_queue.obj' libtool=no @AMDEPBACKSLASH@ @AMDEP_TRUE@@am__fastdepCC_FALSE@ DEPDIR=$(DEPDIR) $(CCDEPMODE) $(depcomp) @AMDEPBACKSLASH@ @am__fastdepCC_FALSE@ $(AM_V_CC@am__nodep@)$(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(tests_isisd_test_isis_vertex_queue_CPPFLAGS) $(CPPFLAGS) $(tests_isisd_test_isis_vertex_queue_CFLAGS) $(CFLAGS) -c -o tests/isisd/test_isis_vertex_queue-test_isis_vertex_queue.obj `if test -f 'tests/isisd/test_isis_vertex_queue.c'; then $(CYGPATH_W) 'tests/isisd/test_isis_vertex_queue.c'; else $(CYGPATH_W) '$(srcdir)/tests/isisd/test_isis_vertex_queue.c'; fi` tests/lib/cli/test_cli-test_cli.o: tests/lib/cli/test_cli.c @am__fastdepCC_TRUE@ $(AM_V_CC)$(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(tests_lib_cli_test_cli_CPPFLAGS) $(CPPFLAGS) $(tests_lib_cli_test_cli_CFLAGS) $(CFLAGS) -MT tests/lib/cli/test_cli-test_cli.o -MD -MP -MF tests/lib/cli/$(DEPDIR)/test_cli-test_cli.Tpo -c -o tests/lib/cli/test_cli-test_cli.o `test -f 'tests/lib/cli/test_cli.c' || echo '$(srcdir)/'`tests/lib/cli/test_cli.c @am__fastdepCC_TRUE@ $(AM_V_at)$(am__mv) tests/lib/cli/$(DEPDIR)/test_cli-test_cli.Tpo tests/lib/cli/$(DEPDIR)/test_cli-test_cli.Po @AMDEP_TRUE@@am__fastdepCC_FALSE@ $(AM_V_CC)source='tests/lib/cli/test_cli.c' object='tests/lib/cli/test_cli-test_cli.o' libtool=no @AMDEPBACKSLASH@ @AMDEP_TRUE@@am__fastdepCC_FALSE@ DEPDIR=$(DEPDIR) $(CCDEPMODE) $(depcomp) @AMDEPBACKSLASH@ @am__fastdepCC_FALSE@ $(AM_V_CC@am__nodep@)$(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(tests_lib_cli_test_cli_CPPFLAGS) $(CPPFLAGS) $(tests_lib_cli_test_cli_CFLAGS) $(CFLAGS) -c -o tests/lib/cli/test_cli-test_cli.o `test -f 'tests/lib/cli/test_cli.c' || echo '$(srcdir)/'`tests/lib/cli/test_cli.c tests/lib/cli/test_cli-test_cli.obj: tests/lib/cli/test_cli.c @am__fastdepCC_TRUE@ $(AM_V_CC)$(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(tests_lib_cli_test_cli_CPPFLAGS) $(CPPFLAGS) $(tests_lib_cli_test_cli_CFLAGS) $(CFLAGS) -MT tests/lib/cli/test_cli-test_cli.obj -MD -MP -MF tests/lib/cli/$(DEPDIR)/test_cli-test_cli.Tpo -c -o tests/lib/cli/test_cli-test_cli.obj `if test -f 'tests/lib/cli/test_cli.c'; then $(CYGPATH_W) 'tests/lib/cli/test_cli.c'; else $(CYGPATH_W) '$(srcdir)/tests/lib/cli/test_cli.c'; fi` @am__fastdepCC_TRUE@ $(AM_V_at)$(am__mv) tests/lib/cli/$(DEPDIR)/test_cli-test_cli.Tpo tests/lib/cli/$(DEPDIR)/test_cli-test_cli.Po @AMDEP_TRUE@@am__fastdepCC_FALSE@ $(AM_V_CC)source='tests/lib/cli/test_cli.c' object='tests/lib/cli/test_cli-test_cli.obj' libtool=no @AMDEPBACKSLASH@ @AMDEP_TRUE@@am__fastdepCC_FALSE@ DEPDIR=$(DEPDIR) $(CCDEPMODE) $(depcomp) @AMDEPBACKSLASH@ @am__fastdepCC_FALSE@ $(AM_V_CC@am__nodep@)$(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(tests_lib_cli_test_cli_CPPFLAGS) $(CPPFLAGS) $(tests_lib_cli_test_cli_CFLAGS) $(CFLAGS) -c -o tests/lib/cli/test_cli-test_cli.obj `if test -f 'tests/lib/cli/test_cli.c'; then $(CYGPATH_W) 'tests/lib/cli/test_cli.c'; else $(CYGPATH_W) '$(srcdir)/tests/lib/cli/test_cli.c'; fi` tests/lib/cli/test_cli-common_cli.o: tests/lib/cli/common_cli.c @am__fastdepCC_TRUE@ $(AM_V_CC)$(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(tests_lib_cli_test_cli_CPPFLAGS) $(CPPFLAGS) $(tests_lib_cli_test_cli_CFLAGS) $(CFLAGS) -MT tests/lib/cli/test_cli-common_cli.o -MD -MP -MF tests/lib/cli/$(DEPDIR)/test_cli-common_cli.Tpo -c -o tests/lib/cli/test_cli-common_cli.o `test -f 'tests/lib/cli/common_cli.c' || echo '$(srcdir)/'`tests/lib/cli/common_cli.c @am__fastdepCC_TRUE@ $(AM_V_at)$(am__mv) tests/lib/cli/$(DEPDIR)/test_cli-common_cli.Tpo tests/lib/cli/$(DEPDIR)/test_cli-common_cli.Po @AMDEP_TRUE@@am__fastdepCC_FALSE@ $(AM_V_CC)source='tests/lib/cli/common_cli.c' object='tests/lib/cli/test_cli-common_cli.o' libtool=no @AMDEPBACKSLASH@ @AMDEP_TRUE@@am__fastdepCC_FALSE@ DEPDIR=$(DEPDIR) $(CCDEPMODE) $(depcomp) @AMDEPBACKSLASH@ @am__fastdepCC_FALSE@ $(AM_V_CC@am__nodep@)$(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(tests_lib_cli_test_cli_CPPFLAGS) $(CPPFLAGS) $(tests_lib_cli_test_cli_CFLAGS) $(CFLAGS) -c -o tests/lib/cli/test_cli-common_cli.o `test -f 'tests/lib/cli/common_cli.c' || echo '$(srcdir)/'`tests/lib/cli/common_cli.c tests/lib/cli/test_cli-common_cli.obj: tests/lib/cli/common_cli.c @am__fastdepCC_TRUE@ $(AM_V_CC)$(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(tests_lib_cli_test_cli_CPPFLAGS) $(CPPFLAGS) $(tests_lib_cli_test_cli_CFLAGS) $(CFLAGS) -MT tests/lib/cli/test_cli-common_cli.obj -MD -MP -MF tests/lib/cli/$(DEPDIR)/test_cli-common_cli.Tpo -c -o tests/lib/cli/test_cli-common_cli.obj `if test -f 'tests/lib/cli/common_cli.c'; then $(CYGPATH_W) 'tests/lib/cli/common_cli.c'; else $(CYGPATH_W) '$(srcdir)/tests/lib/cli/common_cli.c'; fi` @am__fastdepCC_TRUE@ $(AM_V_at)$(am__mv) tests/lib/cli/$(DEPDIR)/test_cli-common_cli.Tpo tests/lib/cli/$(DEPDIR)/test_cli-common_cli.Po @AMDEP_TRUE@@am__fastdepCC_FALSE@ $(AM_V_CC)source='tests/lib/cli/common_cli.c' object='tests/lib/cli/test_cli-common_cli.obj' libtool=no @AMDEPBACKSLASH@ @AMDEP_TRUE@@am__fastdepCC_FALSE@ DEPDIR=$(DEPDIR) $(CCDEPMODE) $(depcomp) @AMDEPBACKSLASH@ @am__fastdepCC_FALSE@ $(AM_V_CC@am__nodep@)$(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(tests_lib_cli_test_cli_CPPFLAGS) $(CPPFLAGS) $(tests_lib_cli_test_cli_CFLAGS) $(CFLAGS) -c -o tests/lib/cli/test_cli-common_cli.obj `if test -f 'tests/lib/cli/common_cli.c'; then $(CYGPATH_W) 'tests/lib/cli/common_cli.c'; else $(CYGPATH_W) '$(srcdir)/tests/lib/cli/common_cli.c'; fi` tests/lib/cli/test_commands-test_commands.o: tests/lib/cli/test_commands.c @am__fastdepCC_TRUE@ $(AM_V_CC)$(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(tests_lib_cli_test_commands_CPPFLAGS) $(CPPFLAGS) $(tests_lib_cli_test_commands_CFLAGS) $(CFLAGS) -MT tests/lib/cli/test_commands-test_commands.o -MD -MP -MF tests/lib/cli/$(DEPDIR)/test_commands-test_commands.Tpo -c -o tests/lib/cli/test_commands-test_commands.o `test -f 'tests/lib/cli/test_commands.c' || echo '$(srcdir)/'`tests/lib/cli/test_commands.c @am__fastdepCC_TRUE@ $(AM_V_at)$(am__mv) tests/lib/cli/$(DEPDIR)/test_commands-test_commands.Tpo tests/lib/cli/$(DEPDIR)/test_commands-test_commands.Po @AMDEP_TRUE@@am__fastdepCC_FALSE@ $(AM_V_CC)source='tests/lib/cli/test_commands.c' object='tests/lib/cli/test_commands-test_commands.o' libtool=no @AMDEPBACKSLASH@ @AMDEP_TRUE@@am__fastdepCC_FALSE@ DEPDIR=$(DEPDIR) $(CCDEPMODE) $(depcomp) @AMDEPBACKSLASH@ @am__fastdepCC_FALSE@ $(AM_V_CC@am__nodep@)$(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(tests_lib_cli_test_commands_CPPFLAGS) $(CPPFLAGS) $(tests_lib_cli_test_commands_CFLAGS) $(CFLAGS) -c -o tests/lib/cli/test_commands-test_commands.o `test -f 'tests/lib/cli/test_commands.c' || echo '$(srcdir)/'`tests/lib/cli/test_commands.c tests/lib/cli/test_commands-test_commands.obj: tests/lib/cli/test_commands.c @am__fastdepCC_TRUE@ $(AM_V_CC)$(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(tests_lib_cli_test_commands_CPPFLAGS) $(CPPFLAGS) $(tests_lib_cli_test_commands_CFLAGS) $(CFLAGS) -MT tests/lib/cli/test_commands-test_commands.obj -MD -MP -MF tests/lib/cli/$(DEPDIR)/test_commands-test_commands.Tpo -c -o tests/lib/cli/test_commands-test_commands.obj `if test -f 'tests/lib/cli/test_commands.c'; then $(CYGPATH_W) 'tests/lib/cli/test_commands.c'; else $(CYGPATH_W) '$(srcdir)/tests/lib/cli/test_commands.c'; fi` @am__fastdepCC_TRUE@ $(AM_V_at)$(am__mv) tests/lib/cli/$(DEPDIR)/test_commands-test_commands.Tpo tests/lib/cli/$(DEPDIR)/test_commands-test_commands.Po @AMDEP_TRUE@@am__fastdepCC_FALSE@ $(AM_V_CC)source='tests/lib/cli/test_commands.c' object='tests/lib/cli/test_commands-test_commands.obj' libtool=no @AMDEPBACKSLASH@ @AMDEP_TRUE@@am__fastdepCC_FALSE@ DEPDIR=$(DEPDIR) $(CCDEPMODE) $(depcomp) @AMDEPBACKSLASH@ @am__fastdepCC_FALSE@ $(AM_V_CC@am__nodep@)$(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(tests_lib_cli_test_commands_CPPFLAGS) $(CPPFLAGS) $(tests_lib_cli_test_commands_CFLAGS) $(CFLAGS) -c -o tests/lib/cli/test_commands-test_commands.obj `if test -f 'tests/lib/cli/test_commands.c'; then $(CYGPATH_W) 'tests/lib/cli/test_commands.c'; else $(CYGPATH_W) '$(srcdir)/tests/lib/cli/test_commands.c'; fi` tests/helpers/c/lib_cli_test_commands-prng.o: tests/helpers/c/prng.c @am__fastdepCC_TRUE@ $(AM_V_CC)$(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(tests_lib_cli_test_commands_CPPFLAGS) $(CPPFLAGS) $(tests_lib_cli_test_commands_CFLAGS) $(CFLAGS) -MT tests/helpers/c/lib_cli_test_commands-prng.o -MD -MP -MF tests/helpers/c/$(DEPDIR)/lib_cli_test_commands-prng.Tpo -c -o tests/helpers/c/lib_cli_test_commands-prng.o `test -f 'tests/helpers/c/prng.c' || echo '$(srcdir)/'`tests/helpers/c/prng.c @am__fastdepCC_TRUE@ $(AM_V_at)$(am__mv) tests/helpers/c/$(DEPDIR)/lib_cli_test_commands-prng.Tpo tests/helpers/c/$(DEPDIR)/lib_cli_test_commands-prng.Po @AMDEP_TRUE@@am__fastdepCC_FALSE@ $(AM_V_CC)source='tests/helpers/c/prng.c' object='tests/helpers/c/lib_cli_test_commands-prng.o' libtool=no @AMDEPBACKSLASH@ @AMDEP_TRUE@@am__fastdepCC_FALSE@ DEPDIR=$(DEPDIR) $(CCDEPMODE) $(depcomp) @AMDEPBACKSLASH@ @am__fastdepCC_FALSE@ $(AM_V_CC@am__nodep@)$(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(tests_lib_cli_test_commands_CPPFLAGS) $(CPPFLAGS) $(tests_lib_cli_test_commands_CFLAGS) $(CFLAGS) -c -o tests/helpers/c/lib_cli_test_commands-prng.o `test -f 'tests/helpers/c/prng.c' || echo '$(srcdir)/'`tests/helpers/c/prng.c tests/helpers/c/lib_cli_test_commands-prng.obj: tests/helpers/c/prng.c @am__fastdepCC_TRUE@ $(AM_V_CC)$(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(tests_lib_cli_test_commands_CPPFLAGS) $(CPPFLAGS) $(tests_lib_cli_test_commands_CFLAGS) $(CFLAGS) -MT tests/helpers/c/lib_cli_test_commands-prng.obj -MD -MP -MF tests/helpers/c/$(DEPDIR)/lib_cli_test_commands-prng.Tpo -c -o tests/helpers/c/lib_cli_test_commands-prng.obj `if test -f 'tests/helpers/c/prng.c'; then $(CYGPATH_W) 'tests/helpers/c/prng.c'; else $(CYGPATH_W) '$(srcdir)/tests/helpers/c/prng.c'; fi` @am__fastdepCC_TRUE@ $(AM_V_at)$(am__mv) tests/helpers/c/$(DEPDIR)/lib_cli_test_commands-prng.Tpo tests/helpers/c/$(DEPDIR)/lib_cli_test_commands-prng.Po @AMDEP_TRUE@@am__fastdepCC_FALSE@ $(AM_V_CC)source='tests/helpers/c/prng.c' object='tests/helpers/c/lib_cli_test_commands-prng.obj' libtool=no @AMDEPBACKSLASH@ @AMDEP_TRUE@@am__fastdepCC_FALSE@ DEPDIR=$(DEPDIR) $(CCDEPMODE) $(depcomp) @AMDEPBACKSLASH@ @am__fastdepCC_FALSE@ $(AM_V_CC@am__nodep@)$(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(tests_lib_cli_test_commands_CPPFLAGS) $(CPPFLAGS) $(tests_lib_cli_test_commands_CFLAGS) $(CFLAGS) -c -o tests/helpers/c/lib_cli_test_commands-prng.obj `if test -f 'tests/helpers/c/prng.c'; then $(CYGPATH_W) 'tests/helpers/c/prng.c'; else $(CYGPATH_W) '$(srcdir)/tests/helpers/c/prng.c'; fi` tests/lib/cli/test_commands-test_commands_defun.o: tests/lib/cli/test_commands_defun.c @am__fastdepCC_TRUE@ $(AM_V_CC)$(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(tests_lib_cli_test_commands_CPPFLAGS) $(CPPFLAGS) $(tests_lib_cli_test_commands_CFLAGS) $(CFLAGS) -MT tests/lib/cli/test_commands-test_commands_defun.o -MD -MP -MF tests/lib/cli/$(DEPDIR)/test_commands-test_commands_defun.Tpo -c -o tests/lib/cli/test_commands-test_commands_defun.o `test -f 'tests/lib/cli/test_commands_defun.c' || echo '$(srcdir)/'`tests/lib/cli/test_commands_defun.c @am__fastdepCC_TRUE@ $(AM_V_at)$(am__mv) tests/lib/cli/$(DEPDIR)/test_commands-test_commands_defun.Tpo tests/lib/cli/$(DEPDIR)/test_commands-test_commands_defun.Po @AMDEP_TRUE@@am__fastdepCC_FALSE@ $(AM_V_CC)source='tests/lib/cli/test_commands_defun.c' object='tests/lib/cli/test_commands-test_commands_defun.o' libtool=no @AMDEPBACKSLASH@ @AMDEP_TRUE@@am__fastdepCC_FALSE@ DEPDIR=$(DEPDIR) $(CCDEPMODE) $(depcomp) @AMDEPBACKSLASH@ @am__fastdepCC_FALSE@ $(AM_V_CC@am__nodep@)$(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(tests_lib_cli_test_commands_CPPFLAGS) $(CPPFLAGS) $(tests_lib_cli_test_commands_CFLAGS) $(CFLAGS) -c -o tests/lib/cli/test_commands-test_commands_defun.o `test -f 'tests/lib/cli/test_commands_defun.c' || echo '$(srcdir)/'`tests/lib/cli/test_commands_defun.c tests/lib/cli/test_commands-test_commands_defun.obj: tests/lib/cli/test_commands_defun.c @am__fastdepCC_TRUE@ $(AM_V_CC)$(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(tests_lib_cli_test_commands_CPPFLAGS) $(CPPFLAGS) $(tests_lib_cli_test_commands_CFLAGS) $(CFLAGS) -MT tests/lib/cli/test_commands-test_commands_defun.obj -MD -MP -MF tests/lib/cli/$(DEPDIR)/test_commands-test_commands_defun.Tpo -c -o tests/lib/cli/test_commands-test_commands_defun.obj `if test -f 'tests/lib/cli/test_commands_defun.c'; then $(CYGPATH_W) 'tests/lib/cli/test_commands_defun.c'; else $(CYGPATH_W) '$(srcdir)/tests/lib/cli/test_commands_defun.c'; fi` @am__fastdepCC_TRUE@ $(AM_V_at)$(am__mv) tests/lib/cli/$(DEPDIR)/test_commands-test_commands_defun.Tpo tests/lib/cli/$(DEPDIR)/test_commands-test_commands_defun.Po @AMDEP_TRUE@@am__fastdepCC_FALSE@ $(AM_V_CC)source='tests/lib/cli/test_commands_defun.c' object='tests/lib/cli/test_commands-test_commands_defun.obj' libtool=no @AMDEPBACKSLASH@ @AMDEP_TRUE@@am__fastdepCC_FALSE@ DEPDIR=$(DEPDIR) $(CCDEPMODE) $(depcomp) @AMDEPBACKSLASH@ @am__fastdepCC_FALSE@ $(AM_V_CC@am__nodep@)$(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(tests_lib_cli_test_commands_CPPFLAGS) $(CPPFLAGS) $(tests_lib_cli_test_commands_CFLAGS) $(CFLAGS) -c -o tests/lib/cli/test_commands-test_commands_defun.obj `if test -f 'tests/lib/cli/test_commands_defun.c'; then $(CYGPATH_W) 'tests/lib/cli/test_commands_defun.c'; else $(CYGPATH_W) '$(srcdir)/tests/lib/cli/test_commands_defun.c'; fi` tests/lib/cxxcompat-cxxcompat.o: tests/lib/cxxcompat.c @am__fastdepCC_TRUE@ $(AM_V_CC)$(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(tests_lib_cxxcompat_CPPFLAGS) $(CPPFLAGS) $(tests_lib_cxxcompat_CFLAGS) $(CFLAGS) -MT tests/lib/cxxcompat-cxxcompat.o -MD -MP -MF tests/lib/$(DEPDIR)/cxxcompat-cxxcompat.Tpo -c -o tests/lib/cxxcompat-cxxcompat.o `test -f 'tests/lib/cxxcompat.c' || echo '$(srcdir)/'`tests/lib/cxxcompat.c @am__fastdepCC_TRUE@ $(AM_V_at)$(am__mv) tests/lib/$(DEPDIR)/cxxcompat-cxxcompat.Tpo tests/lib/$(DEPDIR)/cxxcompat-cxxcompat.Po @AMDEP_TRUE@@am__fastdepCC_FALSE@ $(AM_V_CC)source='tests/lib/cxxcompat.c' object='tests/lib/cxxcompat-cxxcompat.o' libtool=no @AMDEPBACKSLASH@ @AMDEP_TRUE@@am__fastdepCC_FALSE@ DEPDIR=$(DEPDIR) $(CCDEPMODE) $(depcomp) @AMDEPBACKSLASH@ @am__fastdepCC_FALSE@ $(AM_V_CC@am__nodep@)$(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(tests_lib_cxxcompat_CPPFLAGS) $(CPPFLAGS) $(tests_lib_cxxcompat_CFLAGS) $(CFLAGS) -c -o tests/lib/cxxcompat-cxxcompat.o `test -f 'tests/lib/cxxcompat.c' || echo '$(srcdir)/'`tests/lib/cxxcompat.c tests/lib/cxxcompat-cxxcompat.obj: tests/lib/cxxcompat.c @am__fastdepCC_TRUE@ $(AM_V_CC)$(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(tests_lib_cxxcompat_CPPFLAGS) $(CPPFLAGS) $(tests_lib_cxxcompat_CFLAGS) $(CFLAGS) -MT tests/lib/cxxcompat-cxxcompat.obj -MD -MP -MF tests/lib/$(DEPDIR)/cxxcompat-cxxcompat.Tpo -c -o tests/lib/cxxcompat-cxxcompat.obj `if test -f 'tests/lib/cxxcompat.c'; then $(CYGPATH_W) 'tests/lib/cxxcompat.c'; else $(CYGPATH_W) '$(srcdir)/tests/lib/cxxcompat.c'; fi` @am__fastdepCC_TRUE@ $(AM_V_at)$(am__mv) tests/lib/$(DEPDIR)/cxxcompat-cxxcompat.Tpo tests/lib/$(DEPDIR)/cxxcompat-cxxcompat.Po @AMDEP_TRUE@@am__fastdepCC_FALSE@ $(AM_V_CC)source='tests/lib/cxxcompat.c' object='tests/lib/cxxcompat-cxxcompat.obj' libtool=no @AMDEPBACKSLASH@ @AMDEP_TRUE@@am__fastdepCC_FALSE@ DEPDIR=$(DEPDIR) $(CCDEPMODE) $(depcomp) @AMDEPBACKSLASH@ @am__fastdepCC_FALSE@ $(AM_V_CC@am__nodep@)$(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(tests_lib_cxxcompat_CPPFLAGS) $(CPPFLAGS) $(tests_lib_cxxcompat_CFLAGS) $(CFLAGS) -c -o tests/lib/cxxcompat-cxxcompat.obj `if test -f 'tests/lib/cxxcompat.c'; then $(CYGPATH_W) 'tests/lib/cxxcompat.c'; else $(CYGPATH_W) '$(srcdir)/tests/lib/cxxcompat.c'; fi` tests/lib/northbound/test_oper_data-test_oper_data.o: tests/lib/northbound/test_oper_data.c @am__fastdepCC_TRUE@ $(AM_V_CC)$(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(tests_lib_northbound_test_oper_data_CPPFLAGS) $(CPPFLAGS) $(tests_lib_northbound_test_oper_data_CFLAGS) $(CFLAGS) -MT tests/lib/northbound/test_oper_data-test_oper_data.o -MD -MP -MF tests/lib/northbound/$(DEPDIR)/test_oper_data-test_oper_data.Tpo -c -o tests/lib/northbound/test_oper_data-test_oper_data.o `test -f 'tests/lib/northbound/test_oper_data.c' || echo '$(srcdir)/'`tests/lib/northbound/test_oper_data.c @am__fastdepCC_TRUE@ $(AM_V_at)$(am__mv) tests/lib/northbound/$(DEPDIR)/test_oper_data-test_oper_data.Tpo tests/lib/northbound/$(DEPDIR)/test_oper_data-test_oper_data.Po @AMDEP_TRUE@@am__fastdepCC_FALSE@ $(AM_V_CC)source='tests/lib/northbound/test_oper_data.c' object='tests/lib/northbound/test_oper_data-test_oper_data.o' libtool=no @AMDEPBACKSLASH@ @AMDEP_TRUE@@am__fastdepCC_FALSE@ DEPDIR=$(DEPDIR) $(CCDEPMODE) $(depcomp) @AMDEPBACKSLASH@ @am__fastdepCC_FALSE@ $(AM_V_CC@am__nodep@)$(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(tests_lib_northbound_test_oper_data_CPPFLAGS) $(CPPFLAGS) $(tests_lib_northbound_test_oper_data_CFLAGS) $(CFLAGS) -c -o tests/lib/northbound/test_oper_data-test_oper_data.o `test -f 'tests/lib/northbound/test_oper_data.c' || echo '$(srcdir)/'`tests/lib/northbound/test_oper_data.c tests/lib/northbound/test_oper_data-test_oper_data.obj: tests/lib/northbound/test_oper_data.c @am__fastdepCC_TRUE@ $(AM_V_CC)$(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(tests_lib_northbound_test_oper_data_CPPFLAGS) $(CPPFLAGS) $(tests_lib_northbound_test_oper_data_CFLAGS) $(CFLAGS) -MT tests/lib/northbound/test_oper_data-test_oper_data.obj -MD -MP -MF tests/lib/northbound/$(DEPDIR)/test_oper_data-test_oper_data.Tpo -c -o tests/lib/northbound/test_oper_data-test_oper_data.obj `if test -f 'tests/lib/northbound/test_oper_data.c'; then $(CYGPATH_W) 'tests/lib/northbound/test_oper_data.c'; else $(CYGPATH_W) '$(srcdir)/tests/lib/northbound/test_oper_data.c'; fi` @am__fastdepCC_TRUE@ $(AM_V_at)$(am__mv) tests/lib/northbound/$(DEPDIR)/test_oper_data-test_oper_data.Tpo tests/lib/northbound/$(DEPDIR)/test_oper_data-test_oper_data.Po @AMDEP_TRUE@@am__fastdepCC_FALSE@ $(AM_V_CC)source='tests/lib/northbound/test_oper_data.c' object='tests/lib/northbound/test_oper_data-test_oper_data.obj' libtool=no @AMDEPBACKSLASH@ @AMDEP_TRUE@@am__fastdepCC_FALSE@ DEPDIR=$(DEPDIR) $(CCDEPMODE) $(depcomp) @AMDEPBACKSLASH@ @am__fastdepCC_FALSE@ $(AM_V_CC@am__nodep@)$(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(tests_lib_northbound_test_oper_data_CPPFLAGS) $(CPPFLAGS) $(tests_lib_northbound_test_oper_data_CFLAGS) $(CFLAGS) -c -o tests/lib/northbound/test_oper_data-test_oper_data.obj `if test -f 'tests/lib/northbound/test_oper_data.c'; then $(CYGPATH_W) 'tests/lib/northbound/test_oper_data.c'; else $(CYGPATH_W) '$(srcdir)/tests/lib/northbound/test_oper_data.c'; fi` yang/tests_lib_northbound_test_oper_data-frr-test-module.yang.o: yang/frr-test-module.yang.c @am__fastdepCC_TRUE@ $(AM_V_CC)$(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(tests_lib_northbound_test_oper_data_CPPFLAGS) $(CPPFLAGS) $(tests_lib_northbound_test_oper_data_CFLAGS) $(CFLAGS) -MT yang/tests_lib_northbound_test_oper_data-frr-test-module.yang.o -MD -MP -MF yang/$(DEPDIR)/tests_lib_northbound_test_oper_data-frr-test-module.yang.Tpo -c -o yang/tests_lib_northbound_test_oper_data-frr-test-module.yang.o `test -f 'yang/frr-test-module.yang.c' || echo '$(srcdir)/'`yang/frr-test-module.yang.c @am__fastdepCC_TRUE@ $(AM_V_at)$(am__mv) yang/$(DEPDIR)/tests_lib_northbound_test_oper_data-frr-test-module.yang.Tpo yang/$(DEPDIR)/tests_lib_northbound_test_oper_data-frr-test-module.yang.Po @AMDEP_TRUE@@am__fastdepCC_FALSE@ $(AM_V_CC)source='yang/frr-test-module.yang.c' object='yang/tests_lib_northbound_test_oper_data-frr-test-module.yang.o' libtool=no @AMDEPBACKSLASH@ @AMDEP_TRUE@@am__fastdepCC_FALSE@ DEPDIR=$(DEPDIR) $(CCDEPMODE) $(depcomp) @AMDEPBACKSLASH@ @am__fastdepCC_FALSE@ $(AM_V_CC@am__nodep@)$(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(tests_lib_northbound_test_oper_data_CPPFLAGS) $(CPPFLAGS) $(tests_lib_northbound_test_oper_data_CFLAGS) $(CFLAGS) -c -o yang/tests_lib_northbound_test_oper_data-frr-test-module.yang.o `test -f 'yang/frr-test-module.yang.c' || echo '$(srcdir)/'`yang/frr-test-module.yang.c yang/tests_lib_northbound_test_oper_data-frr-test-module.yang.obj: yang/frr-test-module.yang.c @am__fastdepCC_TRUE@ $(AM_V_CC)$(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(tests_lib_northbound_test_oper_data_CPPFLAGS) $(CPPFLAGS) $(tests_lib_northbound_test_oper_data_CFLAGS) $(CFLAGS) -MT yang/tests_lib_northbound_test_oper_data-frr-test-module.yang.obj -MD -MP -MF yang/$(DEPDIR)/tests_lib_northbound_test_oper_data-frr-test-module.yang.Tpo -c -o yang/tests_lib_northbound_test_oper_data-frr-test-module.yang.obj `if test -f 'yang/frr-test-module.yang.c'; then $(CYGPATH_W) 'yang/frr-test-module.yang.c'; else $(CYGPATH_W) '$(srcdir)/yang/frr-test-module.yang.c'; fi` @am__fastdepCC_TRUE@ $(AM_V_at)$(am__mv) yang/$(DEPDIR)/tests_lib_northbound_test_oper_data-frr-test-module.yang.Tpo yang/$(DEPDIR)/tests_lib_northbound_test_oper_data-frr-test-module.yang.Po @AMDEP_TRUE@@am__fastdepCC_FALSE@ $(AM_V_CC)source='yang/frr-test-module.yang.c' object='yang/tests_lib_northbound_test_oper_data-frr-test-module.yang.obj' libtool=no @AMDEPBACKSLASH@ @AMDEP_TRUE@@am__fastdepCC_FALSE@ DEPDIR=$(DEPDIR) $(CCDEPMODE) $(depcomp) @AMDEPBACKSLASH@ @am__fastdepCC_FALSE@ $(AM_V_CC@am__nodep@)$(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(tests_lib_northbound_test_oper_data_CPPFLAGS) $(CPPFLAGS) $(tests_lib_northbound_test_oper_data_CFLAGS) $(CFLAGS) -c -o yang/tests_lib_northbound_test_oper_data-frr-test-module.yang.obj `if test -f 'yang/frr-test-module.yang.c'; then $(CYGPATH_W) 'yang/frr-test-module.yang.c'; else $(CYGPATH_W) '$(srcdir)/yang/frr-test-module.yang.c'; fi` tests/lib/test_atomlist-test_atomlist.o: tests/lib/test_atomlist.c @am__fastdepCC_TRUE@ $(AM_V_CC)$(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(tests_lib_test_atomlist_CPPFLAGS) $(CPPFLAGS) $(tests_lib_test_atomlist_CFLAGS) $(CFLAGS) -MT tests/lib/test_atomlist-test_atomlist.o -MD -MP -MF tests/lib/$(DEPDIR)/test_atomlist-test_atomlist.Tpo -c -o tests/lib/test_atomlist-test_atomlist.o `test -f 'tests/lib/test_atomlist.c' || echo '$(srcdir)/'`tests/lib/test_atomlist.c @am__fastdepCC_TRUE@ $(AM_V_at)$(am__mv) tests/lib/$(DEPDIR)/test_atomlist-test_atomlist.Tpo tests/lib/$(DEPDIR)/test_atomlist-test_atomlist.Po @AMDEP_TRUE@@am__fastdepCC_FALSE@ $(AM_V_CC)source='tests/lib/test_atomlist.c' object='tests/lib/test_atomlist-test_atomlist.o' libtool=no @AMDEPBACKSLASH@ @AMDEP_TRUE@@am__fastdepCC_FALSE@ DEPDIR=$(DEPDIR) $(CCDEPMODE) $(depcomp) @AMDEPBACKSLASH@ @am__fastdepCC_FALSE@ $(AM_V_CC@am__nodep@)$(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(tests_lib_test_atomlist_CPPFLAGS) $(CPPFLAGS) $(tests_lib_test_atomlist_CFLAGS) $(CFLAGS) -c -o tests/lib/test_atomlist-test_atomlist.o `test -f 'tests/lib/test_atomlist.c' || echo '$(srcdir)/'`tests/lib/test_atomlist.c tests/lib/test_atomlist-test_atomlist.obj: tests/lib/test_atomlist.c @am__fastdepCC_TRUE@ $(AM_V_CC)$(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(tests_lib_test_atomlist_CPPFLAGS) $(CPPFLAGS) $(tests_lib_test_atomlist_CFLAGS) $(CFLAGS) -MT tests/lib/test_atomlist-test_atomlist.obj -MD -MP -MF tests/lib/$(DEPDIR)/test_atomlist-test_atomlist.Tpo -c -o tests/lib/test_atomlist-test_atomlist.obj `if test -f 'tests/lib/test_atomlist.c'; then $(CYGPATH_W) 'tests/lib/test_atomlist.c'; else $(CYGPATH_W) '$(srcdir)/tests/lib/test_atomlist.c'; fi` @am__fastdepCC_TRUE@ $(AM_V_at)$(am__mv) tests/lib/$(DEPDIR)/test_atomlist-test_atomlist.Tpo tests/lib/$(DEPDIR)/test_atomlist-test_atomlist.Po @AMDEP_TRUE@@am__fastdepCC_FALSE@ $(AM_V_CC)source='tests/lib/test_atomlist.c' object='tests/lib/test_atomlist-test_atomlist.obj' libtool=no @AMDEPBACKSLASH@ @AMDEP_TRUE@@am__fastdepCC_FALSE@ DEPDIR=$(DEPDIR) $(CCDEPMODE) $(depcomp) @AMDEPBACKSLASH@ @am__fastdepCC_FALSE@ $(AM_V_CC@am__nodep@)$(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(tests_lib_test_atomlist_CPPFLAGS) $(CPPFLAGS) $(tests_lib_test_atomlist_CFLAGS) $(CFLAGS) -c -o tests/lib/test_atomlist-test_atomlist.obj `if test -f 'tests/lib/test_atomlist.c'; then $(CYGPATH_W) 'tests/lib/test_atomlist.c'; else $(CYGPATH_W) '$(srcdir)/tests/lib/test_atomlist.c'; fi` tests/lib/test_buffer-test_buffer.o: tests/lib/test_buffer.c @am__fastdepCC_TRUE@ $(AM_V_CC)$(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(tests_lib_test_buffer_CPPFLAGS) $(CPPFLAGS) $(tests_lib_test_buffer_CFLAGS) $(CFLAGS) -MT tests/lib/test_buffer-test_buffer.o -MD -MP -MF tests/lib/$(DEPDIR)/test_buffer-test_buffer.Tpo -c -o tests/lib/test_buffer-test_buffer.o `test -f 'tests/lib/test_buffer.c' || echo '$(srcdir)/'`tests/lib/test_buffer.c @am__fastdepCC_TRUE@ $(AM_V_at)$(am__mv) tests/lib/$(DEPDIR)/test_buffer-test_buffer.Tpo tests/lib/$(DEPDIR)/test_buffer-test_buffer.Po @AMDEP_TRUE@@am__fastdepCC_FALSE@ $(AM_V_CC)source='tests/lib/test_buffer.c' object='tests/lib/test_buffer-test_buffer.o' libtool=no @AMDEPBACKSLASH@ @AMDEP_TRUE@@am__fastdepCC_FALSE@ DEPDIR=$(DEPDIR) $(CCDEPMODE) $(depcomp) @AMDEPBACKSLASH@ @am__fastdepCC_FALSE@ $(AM_V_CC@am__nodep@)$(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(tests_lib_test_buffer_CPPFLAGS) $(CPPFLAGS) $(tests_lib_test_buffer_CFLAGS) $(CFLAGS) -c -o tests/lib/test_buffer-test_buffer.o `test -f 'tests/lib/test_buffer.c' || echo '$(srcdir)/'`tests/lib/test_buffer.c tests/lib/test_buffer-test_buffer.obj: tests/lib/test_buffer.c @am__fastdepCC_TRUE@ $(AM_V_CC)$(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(tests_lib_test_buffer_CPPFLAGS) $(CPPFLAGS) $(tests_lib_test_buffer_CFLAGS) $(CFLAGS) -MT tests/lib/test_buffer-test_buffer.obj -MD -MP -MF tests/lib/$(DEPDIR)/test_buffer-test_buffer.Tpo -c -o tests/lib/test_buffer-test_buffer.obj `if test -f 'tests/lib/test_buffer.c'; then $(CYGPATH_W) 'tests/lib/test_buffer.c'; else $(CYGPATH_W) '$(srcdir)/tests/lib/test_buffer.c'; fi` @am__fastdepCC_TRUE@ $(AM_V_at)$(am__mv) tests/lib/$(DEPDIR)/test_buffer-test_buffer.Tpo tests/lib/$(DEPDIR)/test_buffer-test_buffer.Po @AMDEP_TRUE@@am__fastdepCC_FALSE@ $(AM_V_CC)source='tests/lib/test_buffer.c' object='tests/lib/test_buffer-test_buffer.obj' libtool=no @AMDEPBACKSLASH@ @AMDEP_TRUE@@am__fastdepCC_FALSE@ DEPDIR=$(DEPDIR) $(CCDEPMODE) $(depcomp) @AMDEPBACKSLASH@ @am__fastdepCC_FALSE@ $(AM_V_CC@am__nodep@)$(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(tests_lib_test_buffer_CPPFLAGS) $(CPPFLAGS) $(tests_lib_test_buffer_CFLAGS) $(CFLAGS) -c -o tests/lib/test_buffer-test_buffer.obj `if test -f 'tests/lib/test_buffer.c'; then $(CYGPATH_W) 'tests/lib/test_buffer.c'; else $(CYGPATH_W) '$(srcdir)/tests/lib/test_buffer.c'; fi` tests/lib/test_checksum-test_checksum.o: tests/lib/test_checksum.c @am__fastdepCC_TRUE@ $(AM_V_CC)$(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(tests_lib_test_checksum_CPPFLAGS) $(CPPFLAGS) $(tests_lib_test_checksum_CFLAGS) $(CFLAGS) -MT tests/lib/test_checksum-test_checksum.o -MD -MP -MF tests/lib/$(DEPDIR)/test_checksum-test_checksum.Tpo -c -o tests/lib/test_checksum-test_checksum.o `test -f 'tests/lib/test_checksum.c' || echo '$(srcdir)/'`tests/lib/test_checksum.c @am__fastdepCC_TRUE@ $(AM_V_at)$(am__mv) tests/lib/$(DEPDIR)/test_checksum-test_checksum.Tpo tests/lib/$(DEPDIR)/test_checksum-test_checksum.Po @AMDEP_TRUE@@am__fastdepCC_FALSE@ $(AM_V_CC)source='tests/lib/test_checksum.c' object='tests/lib/test_checksum-test_checksum.o' libtool=no @AMDEPBACKSLASH@ @AMDEP_TRUE@@am__fastdepCC_FALSE@ DEPDIR=$(DEPDIR) $(CCDEPMODE) $(depcomp) @AMDEPBACKSLASH@ @am__fastdepCC_FALSE@ $(AM_V_CC@am__nodep@)$(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(tests_lib_test_checksum_CPPFLAGS) $(CPPFLAGS) $(tests_lib_test_checksum_CFLAGS) $(CFLAGS) -c -o tests/lib/test_checksum-test_checksum.o `test -f 'tests/lib/test_checksum.c' || echo '$(srcdir)/'`tests/lib/test_checksum.c tests/lib/test_checksum-test_checksum.obj: tests/lib/test_checksum.c @am__fastdepCC_TRUE@ $(AM_V_CC)$(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(tests_lib_test_checksum_CPPFLAGS) $(CPPFLAGS) $(tests_lib_test_checksum_CFLAGS) $(CFLAGS) -MT tests/lib/test_checksum-test_checksum.obj -MD -MP -MF tests/lib/$(DEPDIR)/test_checksum-test_checksum.Tpo -c -o tests/lib/test_checksum-test_checksum.obj `if test -f 'tests/lib/test_checksum.c'; then $(CYGPATH_W) 'tests/lib/test_checksum.c'; else $(CYGPATH_W) '$(srcdir)/tests/lib/test_checksum.c'; fi` @am__fastdepCC_TRUE@ $(AM_V_at)$(am__mv) tests/lib/$(DEPDIR)/test_checksum-test_checksum.Tpo tests/lib/$(DEPDIR)/test_checksum-test_checksum.Po @AMDEP_TRUE@@am__fastdepCC_FALSE@ $(AM_V_CC)source='tests/lib/test_checksum.c' object='tests/lib/test_checksum-test_checksum.obj' libtool=no @AMDEPBACKSLASH@ @AMDEP_TRUE@@am__fastdepCC_FALSE@ DEPDIR=$(DEPDIR) $(CCDEPMODE) $(depcomp) @AMDEPBACKSLASH@ @am__fastdepCC_FALSE@ $(AM_V_CC@am__nodep@)$(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(tests_lib_test_checksum_CPPFLAGS) $(CPPFLAGS) $(tests_lib_test_checksum_CFLAGS) $(CFLAGS) -c -o tests/lib/test_checksum-test_checksum.obj `if test -f 'tests/lib/test_checksum.c'; then $(CYGPATH_W) 'tests/lib/test_checksum.c'; else $(CYGPATH_W) '$(srcdir)/tests/lib/test_checksum.c'; fi` tests/lib/test_graph-test_graph.o: tests/lib/test_graph.c @am__fastdepCC_TRUE@ $(AM_V_CC)$(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(tests_lib_test_graph_CPPFLAGS) $(CPPFLAGS) $(tests_lib_test_graph_CFLAGS) $(CFLAGS) -MT tests/lib/test_graph-test_graph.o -MD -MP -MF tests/lib/$(DEPDIR)/test_graph-test_graph.Tpo -c -o tests/lib/test_graph-test_graph.o `test -f 'tests/lib/test_graph.c' || echo '$(srcdir)/'`tests/lib/test_graph.c @am__fastdepCC_TRUE@ $(AM_V_at)$(am__mv) tests/lib/$(DEPDIR)/test_graph-test_graph.Tpo tests/lib/$(DEPDIR)/test_graph-test_graph.Po @AMDEP_TRUE@@am__fastdepCC_FALSE@ $(AM_V_CC)source='tests/lib/test_graph.c' object='tests/lib/test_graph-test_graph.o' libtool=no @AMDEPBACKSLASH@ @AMDEP_TRUE@@am__fastdepCC_FALSE@ DEPDIR=$(DEPDIR) $(CCDEPMODE) $(depcomp) @AMDEPBACKSLASH@ @am__fastdepCC_FALSE@ $(AM_V_CC@am__nodep@)$(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(tests_lib_test_graph_CPPFLAGS) $(CPPFLAGS) $(tests_lib_test_graph_CFLAGS) $(CFLAGS) -c -o tests/lib/test_graph-test_graph.o `test -f 'tests/lib/test_graph.c' || echo '$(srcdir)/'`tests/lib/test_graph.c tests/lib/test_graph-test_graph.obj: tests/lib/test_graph.c @am__fastdepCC_TRUE@ $(AM_V_CC)$(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(tests_lib_test_graph_CPPFLAGS) $(CPPFLAGS) $(tests_lib_test_graph_CFLAGS) $(CFLAGS) -MT tests/lib/test_graph-test_graph.obj -MD -MP -MF tests/lib/$(DEPDIR)/test_graph-test_graph.Tpo -c -o tests/lib/test_graph-test_graph.obj `if test -f 'tests/lib/test_graph.c'; then $(CYGPATH_W) 'tests/lib/test_graph.c'; else $(CYGPATH_W) '$(srcdir)/tests/lib/test_graph.c'; fi` @am__fastdepCC_TRUE@ $(AM_V_at)$(am__mv) tests/lib/$(DEPDIR)/test_graph-test_graph.Tpo tests/lib/$(DEPDIR)/test_graph-test_graph.Po @AMDEP_TRUE@@am__fastdepCC_FALSE@ $(AM_V_CC)source='tests/lib/test_graph.c' object='tests/lib/test_graph-test_graph.obj' libtool=no @AMDEPBACKSLASH@ @AMDEP_TRUE@@am__fastdepCC_FALSE@ DEPDIR=$(DEPDIR) $(CCDEPMODE) $(depcomp) @AMDEPBACKSLASH@ @am__fastdepCC_FALSE@ $(AM_V_CC@am__nodep@)$(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(tests_lib_test_graph_CPPFLAGS) $(CPPFLAGS) $(tests_lib_test_graph_CFLAGS) $(CFLAGS) -c -o tests/lib/test_graph-test_graph.obj `if test -f 'tests/lib/test_graph.c'; then $(CYGPATH_W) 'tests/lib/test_graph.c'; else $(CYGPATH_W) '$(srcdir)/tests/lib/test_graph.c'; fi` tests/lib/test_heavy-test_heavy.o: tests/lib/test_heavy.c @am__fastdepCC_TRUE@ $(AM_V_CC)$(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(tests_lib_test_heavy_CPPFLAGS) $(CPPFLAGS) $(tests_lib_test_heavy_CFLAGS) $(CFLAGS) -MT tests/lib/test_heavy-test_heavy.o -MD -MP -MF tests/lib/$(DEPDIR)/test_heavy-test_heavy.Tpo -c -o tests/lib/test_heavy-test_heavy.o `test -f 'tests/lib/test_heavy.c' || echo '$(srcdir)/'`tests/lib/test_heavy.c @am__fastdepCC_TRUE@ $(AM_V_at)$(am__mv) tests/lib/$(DEPDIR)/test_heavy-test_heavy.Tpo tests/lib/$(DEPDIR)/test_heavy-test_heavy.Po @AMDEP_TRUE@@am__fastdepCC_FALSE@ $(AM_V_CC)source='tests/lib/test_heavy.c' object='tests/lib/test_heavy-test_heavy.o' libtool=no @AMDEPBACKSLASH@ @AMDEP_TRUE@@am__fastdepCC_FALSE@ DEPDIR=$(DEPDIR) $(CCDEPMODE) $(depcomp) @AMDEPBACKSLASH@ @am__fastdepCC_FALSE@ $(AM_V_CC@am__nodep@)$(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(tests_lib_test_heavy_CPPFLAGS) $(CPPFLAGS) $(tests_lib_test_heavy_CFLAGS) $(CFLAGS) -c -o tests/lib/test_heavy-test_heavy.o `test -f 'tests/lib/test_heavy.c' || echo '$(srcdir)/'`tests/lib/test_heavy.c tests/lib/test_heavy-test_heavy.obj: tests/lib/test_heavy.c @am__fastdepCC_TRUE@ $(AM_V_CC)$(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(tests_lib_test_heavy_CPPFLAGS) $(CPPFLAGS) $(tests_lib_test_heavy_CFLAGS) $(CFLAGS) -MT tests/lib/test_heavy-test_heavy.obj -MD -MP -MF tests/lib/$(DEPDIR)/test_heavy-test_heavy.Tpo -c -o tests/lib/test_heavy-test_heavy.obj `if test -f 'tests/lib/test_heavy.c'; then $(CYGPATH_W) 'tests/lib/test_heavy.c'; else $(CYGPATH_W) '$(srcdir)/tests/lib/test_heavy.c'; fi` @am__fastdepCC_TRUE@ $(AM_V_at)$(am__mv) tests/lib/$(DEPDIR)/test_heavy-test_heavy.Tpo tests/lib/$(DEPDIR)/test_heavy-test_heavy.Po @AMDEP_TRUE@@am__fastdepCC_FALSE@ $(AM_V_CC)source='tests/lib/test_heavy.c' object='tests/lib/test_heavy-test_heavy.obj' libtool=no @AMDEPBACKSLASH@ @AMDEP_TRUE@@am__fastdepCC_FALSE@ DEPDIR=$(DEPDIR) $(CCDEPMODE) $(depcomp) @AMDEPBACKSLASH@ @am__fastdepCC_FALSE@ $(AM_V_CC@am__nodep@)$(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(tests_lib_test_heavy_CPPFLAGS) $(CPPFLAGS) $(tests_lib_test_heavy_CFLAGS) $(CFLAGS) -c -o tests/lib/test_heavy-test_heavy.obj `if test -f 'tests/lib/test_heavy.c'; then $(CYGPATH_W) 'tests/lib/test_heavy.c'; else $(CYGPATH_W) '$(srcdir)/tests/lib/test_heavy.c'; fi` tests/helpers/c/lib_test_heavy-main.o: tests/helpers/c/main.c @am__fastdepCC_TRUE@ $(AM_V_CC)$(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(tests_lib_test_heavy_CPPFLAGS) $(CPPFLAGS) $(tests_lib_test_heavy_CFLAGS) $(CFLAGS) -MT tests/helpers/c/lib_test_heavy-main.o -MD -MP -MF tests/helpers/c/$(DEPDIR)/lib_test_heavy-main.Tpo -c -o tests/helpers/c/lib_test_heavy-main.o `test -f 'tests/helpers/c/main.c' || echo '$(srcdir)/'`tests/helpers/c/main.c @am__fastdepCC_TRUE@ $(AM_V_at)$(am__mv) tests/helpers/c/$(DEPDIR)/lib_test_heavy-main.Tpo tests/helpers/c/$(DEPDIR)/lib_test_heavy-main.Po @AMDEP_TRUE@@am__fastdepCC_FALSE@ $(AM_V_CC)source='tests/helpers/c/main.c' object='tests/helpers/c/lib_test_heavy-main.o' libtool=no @AMDEPBACKSLASH@ @AMDEP_TRUE@@am__fastdepCC_FALSE@ DEPDIR=$(DEPDIR) $(CCDEPMODE) $(depcomp) @AMDEPBACKSLASH@ @am__fastdepCC_FALSE@ $(AM_V_CC@am__nodep@)$(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(tests_lib_test_heavy_CPPFLAGS) $(CPPFLAGS) $(tests_lib_test_heavy_CFLAGS) $(CFLAGS) -c -o tests/helpers/c/lib_test_heavy-main.o `test -f 'tests/helpers/c/main.c' || echo '$(srcdir)/'`tests/helpers/c/main.c tests/helpers/c/lib_test_heavy-main.obj: tests/helpers/c/main.c @am__fastdepCC_TRUE@ $(AM_V_CC)$(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(tests_lib_test_heavy_CPPFLAGS) $(CPPFLAGS) $(tests_lib_test_heavy_CFLAGS) $(CFLAGS) -MT tests/helpers/c/lib_test_heavy-main.obj -MD -MP -MF tests/helpers/c/$(DEPDIR)/lib_test_heavy-main.Tpo -c -o tests/helpers/c/lib_test_heavy-main.obj `if test -f 'tests/helpers/c/main.c'; then $(CYGPATH_W) 'tests/helpers/c/main.c'; else $(CYGPATH_W) '$(srcdir)/tests/helpers/c/main.c'; fi` @am__fastdepCC_TRUE@ $(AM_V_at)$(am__mv) tests/helpers/c/$(DEPDIR)/lib_test_heavy-main.Tpo tests/helpers/c/$(DEPDIR)/lib_test_heavy-main.Po @AMDEP_TRUE@@am__fastdepCC_FALSE@ $(AM_V_CC)source='tests/helpers/c/main.c' object='tests/helpers/c/lib_test_heavy-main.obj' libtool=no @AMDEPBACKSLASH@ @AMDEP_TRUE@@am__fastdepCC_FALSE@ DEPDIR=$(DEPDIR) $(CCDEPMODE) $(depcomp) @AMDEPBACKSLASH@ @am__fastdepCC_FALSE@ $(AM_V_CC@am__nodep@)$(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(tests_lib_test_heavy_CPPFLAGS) $(CPPFLAGS) $(tests_lib_test_heavy_CFLAGS) $(CFLAGS) -c -o tests/helpers/c/lib_test_heavy-main.obj `if test -f 'tests/helpers/c/main.c'; then $(CYGPATH_W) 'tests/helpers/c/main.c'; else $(CYGPATH_W) '$(srcdir)/tests/helpers/c/main.c'; fi` tests/lib/test_heavy_thread-test_heavy_thread.o: tests/lib/test_heavy_thread.c @am__fastdepCC_TRUE@ $(AM_V_CC)$(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(tests_lib_test_heavy_thread_CPPFLAGS) $(CPPFLAGS) $(tests_lib_test_heavy_thread_CFLAGS) $(CFLAGS) -MT tests/lib/test_heavy_thread-test_heavy_thread.o -MD -MP -MF tests/lib/$(DEPDIR)/test_heavy_thread-test_heavy_thread.Tpo -c -o tests/lib/test_heavy_thread-test_heavy_thread.o `test -f 'tests/lib/test_heavy_thread.c' || echo '$(srcdir)/'`tests/lib/test_heavy_thread.c @am__fastdepCC_TRUE@ $(AM_V_at)$(am__mv) tests/lib/$(DEPDIR)/test_heavy_thread-test_heavy_thread.Tpo tests/lib/$(DEPDIR)/test_heavy_thread-test_heavy_thread.Po @AMDEP_TRUE@@am__fastdepCC_FALSE@ $(AM_V_CC)source='tests/lib/test_heavy_thread.c' object='tests/lib/test_heavy_thread-test_heavy_thread.o' libtool=no @AMDEPBACKSLASH@ @AMDEP_TRUE@@am__fastdepCC_FALSE@ DEPDIR=$(DEPDIR) $(CCDEPMODE) $(depcomp) @AMDEPBACKSLASH@ @am__fastdepCC_FALSE@ $(AM_V_CC@am__nodep@)$(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(tests_lib_test_heavy_thread_CPPFLAGS) $(CPPFLAGS) $(tests_lib_test_heavy_thread_CFLAGS) $(CFLAGS) -c -o tests/lib/test_heavy_thread-test_heavy_thread.o `test -f 'tests/lib/test_heavy_thread.c' || echo '$(srcdir)/'`tests/lib/test_heavy_thread.c tests/lib/test_heavy_thread-test_heavy_thread.obj: tests/lib/test_heavy_thread.c @am__fastdepCC_TRUE@ $(AM_V_CC)$(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(tests_lib_test_heavy_thread_CPPFLAGS) $(CPPFLAGS) $(tests_lib_test_heavy_thread_CFLAGS) $(CFLAGS) -MT tests/lib/test_heavy_thread-test_heavy_thread.obj -MD -MP -MF tests/lib/$(DEPDIR)/test_heavy_thread-test_heavy_thread.Tpo -c -o tests/lib/test_heavy_thread-test_heavy_thread.obj `if test -f 'tests/lib/test_heavy_thread.c'; then $(CYGPATH_W) 'tests/lib/test_heavy_thread.c'; else $(CYGPATH_W) '$(srcdir)/tests/lib/test_heavy_thread.c'; fi` @am__fastdepCC_TRUE@ $(AM_V_at)$(am__mv) tests/lib/$(DEPDIR)/test_heavy_thread-test_heavy_thread.Tpo tests/lib/$(DEPDIR)/test_heavy_thread-test_heavy_thread.Po @AMDEP_TRUE@@am__fastdepCC_FALSE@ $(AM_V_CC)source='tests/lib/test_heavy_thread.c' object='tests/lib/test_heavy_thread-test_heavy_thread.obj' libtool=no @AMDEPBACKSLASH@ @AMDEP_TRUE@@am__fastdepCC_FALSE@ DEPDIR=$(DEPDIR) $(CCDEPMODE) $(depcomp) @AMDEPBACKSLASH@ @am__fastdepCC_FALSE@ $(AM_V_CC@am__nodep@)$(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(tests_lib_test_heavy_thread_CPPFLAGS) $(CPPFLAGS) $(tests_lib_test_heavy_thread_CFLAGS) $(CFLAGS) -c -o tests/lib/test_heavy_thread-test_heavy_thread.obj `if test -f 'tests/lib/test_heavy_thread.c'; then $(CYGPATH_W) 'tests/lib/test_heavy_thread.c'; else $(CYGPATH_W) '$(srcdir)/tests/lib/test_heavy_thread.c'; fi` tests/helpers/c/lib_test_heavy_thread-main.o: tests/helpers/c/main.c @am__fastdepCC_TRUE@ $(AM_V_CC)$(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(tests_lib_test_heavy_thread_CPPFLAGS) $(CPPFLAGS) $(tests_lib_test_heavy_thread_CFLAGS) $(CFLAGS) -MT tests/helpers/c/lib_test_heavy_thread-main.o -MD -MP -MF tests/helpers/c/$(DEPDIR)/lib_test_heavy_thread-main.Tpo -c -o tests/helpers/c/lib_test_heavy_thread-main.o `test -f 'tests/helpers/c/main.c' || echo '$(srcdir)/'`tests/helpers/c/main.c @am__fastdepCC_TRUE@ $(AM_V_at)$(am__mv) tests/helpers/c/$(DEPDIR)/lib_test_heavy_thread-main.Tpo tests/helpers/c/$(DEPDIR)/lib_test_heavy_thread-main.Po @AMDEP_TRUE@@am__fastdepCC_FALSE@ $(AM_V_CC)source='tests/helpers/c/main.c' object='tests/helpers/c/lib_test_heavy_thread-main.o' libtool=no @AMDEPBACKSLASH@ @AMDEP_TRUE@@am__fastdepCC_FALSE@ DEPDIR=$(DEPDIR) $(CCDEPMODE) $(depcomp) @AMDEPBACKSLASH@ @am__fastdepCC_FALSE@ $(AM_V_CC@am__nodep@)$(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(tests_lib_test_heavy_thread_CPPFLAGS) $(CPPFLAGS) $(tests_lib_test_heavy_thread_CFLAGS) $(CFLAGS) -c -o tests/helpers/c/lib_test_heavy_thread-main.o `test -f 'tests/helpers/c/main.c' || echo '$(srcdir)/'`tests/helpers/c/main.c tests/helpers/c/lib_test_heavy_thread-main.obj: tests/helpers/c/main.c @am__fastdepCC_TRUE@ $(AM_V_CC)$(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(tests_lib_test_heavy_thread_CPPFLAGS) $(CPPFLAGS) $(tests_lib_test_heavy_thread_CFLAGS) $(CFLAGS) -MT tests/helpers/c/lib_test_heavy_thread-main.obj -MD -MP -MF tests/helpers/c/$(DEPDIR)/lib_test_heavy_thread-main.Tpo -c -o tests/helpers/c/lib_test_heavy_thread-main.obj `if test -f 'tests/helpers/c/main.c'; then $(CYGPATH_W) 'tests/helpers/c/main.c'; else $(CYGPATH_W) '$(srcdir)/tests/helpers/c/main.c'; fi` @am__fastdepCC_TRUE@ $(AM_V_at)$(am__mv) tests/helpers/c/$(DEPDIR)/lib_test_heavy_thread-main.Tpo tests/helpers/c/$(DEPDIR)/lib_test_heavy_thread-main.Po @AMDEP_TRUE@@am__fastdepCC_FALSE@ $(AM_V_CC)source='tests/helpers/c/main.c' object='tests/helpers/c/lib_test_heavy_thread-main.obj' libtool=no @AMDEPBACKSLASH@ @AMDEP_TRUE@@am__fastdepCC_FALSE@ DEPDIR=$(DEPDIR) $(CCDEPMODE) $(depcomp) @AMDEPBACKSLASH@ @am__fastdepCC_FALSE@ $(AM_V_CC@am__nodep@)$(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(tests_lib_test_heavy_thread_CPPFLAGS) $(CPPFLAGS) $(tests_lib_test_heavy_thread_CFLAGS) $(CFLAGS) -c -o tests/helpers/c/lib_test_heavy_thread-main.obj `if test -f 'tests/helpers/c/main.c'; then $(CYGPATH_W) 'tests/helpers/c/main.c'; else $(CYGPATH_W) '$(srcdir)/tests/helpers/c/main.c'; fi` tests/lib/test_heavy_wq-test_heavy_wq.o: tests/lib/test_heavy_wq.c @am__fastdepCC_TRUE@ $(AM_V_CC)$(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(tests_lib_test_heavy_wq_CPPFLAGS) $(CPPFLAGS) $(tests_lib_test_heavy_wq_CFLAGS) $(CFLAGS) -MT tests/lib/test_heavy_wq-test_heavy_wq.o -MD -MP -MF tests/lib/$(DEPDIR)/test_heavy_wq-test_heavy_wq.Tpo -c -o tests/lib/test_heavy_wq-test_heavy_wq.o `test -f 'tests/lib/test_heavy_wq.c' || echo '$(srcdir)/'`tests/lib/test_heavy_wq.c @am__fastdepCC_TRUE@ $(AM_V_at)$(am__mv) tests/lib/$(DEPDIR)/test_heavy_wq-test_heavy_wq.Tpo tests/lib/$(DEPDIR)/test_heavy_wq-test_heavy_wq.Po @AMDEP_TRUE@@am__fastdepCC_FALSE@ $(AM_V_CC)source='tests/lib/test_heavy_wq.c' object='tests/lib/test_heavy_wq-test_heavy_wq.o' libtool=no @AMDEPBACKSLASH@ @AMDEP_TRUE@@am__fastdepCC_FALSE@ DEPDIR=$(DEPDIR) $(CCDEPMODE) $(depcomp) @AMDEPBACKSLASH@ @am__fastdepCC_FALSE@ $(AM_V_CC@am__nodep@)$(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(tests_lib_test_heavy_wq_CPPFLAGS) $(CPPFLAGS) $(tests_lib_test_heavy_wq_CFLAGS) $(CFLAGS) -c -o tests/lib/test_heavy_wq-test_heavy_wq.o `test -f 'tests/lib/test_heavy_wq.c' || echo '$(srcdir)/'`tests/lib/test_heavy_wq.c tests/lib/test_heavy_wq-test_heavy_wq.obj: tests/lib/test_heavy_wq.c @am__fastdepCC_TRUE@ $(AM_V_CC)$(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(tests_lib_test_heavy_wq_CPPFLAGS) $(CPPFLAGS) $(tests_lib_test_heavy_wq_CFLAGS) $(CFLAGS) -MT tests/lib/test_heavy_wq-test_heavy_wq.obj -MD -MP -MF tests/lib/$(DEPDIR)/test_heavy_wq-test_heavy_wq.Tpo -c -o tests/lib/test_heavy_wq-test_heavy_wq.obj `if test -f 'tests/lib/test_heavy_wq.c'; then $(CYGPATH_W) 'tests/lib/test_heavy_wq.c'; else $(CYGPATH_W) '$(srcdir)/tests/lib/test_heavy_wq.c'; fi` @am__fastdepCC_TRUE@ $(AM_V_at)$(am__mv) tests/lib/$(DEPDIR)/test_heavy_wq-test_heavy_wq.Tpo tests/lib/$(DEPDIR)/test_heavy_wq-test_heavy_wq.Po @AMDEP_TRUE@@am__fastdepCC_FALSE@ $(AM_V_CC)source='tests/lib/test_heavy_wq.c' object='tests/lib/test_heavy_wq-test_heavy_wq.obj' libtool=no @AMDEPBACKSLASH@ @AMDEP_TRUE@@am__fastdepCC_FALSE@ DEPDIR=$(DEPDIR) $(CCDEPMODE) $(depcomp) @AMDEPBACKSLASH@ @am__fastdepCC_FALSE@ $(AM_V_CC@am__nodep@)$(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(tests_lib_test_heavy_wq_CPPFLAGS) $(CPPFLAGS) $(tests_lib_test_heavy_wq_CFLAGS) $(CFLAGS) -c -o tests/lib/test_heavy_wq-test_heavy_wq.obj `if test -f 'tests/lib/test_heavy_wq.c'; then $(CYGPATH_W) 'tests/lib/test_heavy_wq.c'; else $(CYGPATH_W) '$(srcdir)/tests/lib/test_heavy_wq.c'; fi` tests/helpers/c/lib_test_heavy_wq-main.o: tests/helpers/c/main.c @am__fastdepCC_TRUE@ $(AM_V_CC)$(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(tests_lib_test_heavy_wq_CPPFLAGS) $(CPPFLAGS) $(tests_lib_test_heavy_wq_CFLAGS) $(CFLAGS) -MT tests/helpers/c/lib_test_heavy_wq-main.o -MD -MP -MF tests/helpers/c/$(DEPDIR)/lib_test_heavy_wq-main.Tpo -c -o tests/helpers/c/lib_test_heavy_wq-main.o `test -f 'tests/helpers/c/main.c' || echo '$(srcdir)/'`tests/helpers/c/main.c @am__fastdepCC_TRUE@ $(AM_V_at)$(am__mv) tests/helpers/c/$(DEPDIR)/lib_test_heavy_wq-main.Tpo tests/helpers/c/$(DEPDIR)/lib_test_heavy_wq-main.Po @AMDEP_TRUE@@am__fastdepCC_FALSE@ $(AM_V_CC)source='tests/helpers/c/main.c' object='tests/helpers/c/lib_test_heavy_wq-main.o' libtool=no @AMDEPBACKSLASH@ @AMDEP_TRUE@@am__fastdepCC_FALSE@ DEPDIR=$(DEPDIR) $(CCDEPMODE) $(depcomp) @AMDEPBACKSLASH@ @am__fastdepCC_FALSE@ $(AM_V_CC@am__nodep@)$(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(tests_lib_test_heavy_wq_CPPFLAGS) $(CPPFLAGS) $(tests_lib_test_heavy_wq_CFLAGS) $(CFLAGS) -c -o tests/helpers/c/lib_test_heavy_wq-main.o `test -f 'tests/helpers/c/main.c' || echo '$(srcdir)/'`tests/helpers/c/main.c tests/helpers/c/lib_test_heavy_wq-main.obj: tests/helpers/c/main.c @am__fastdepCC_TRUE@ $(AM_V_CC)$(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(tests_lib_test_heavy_wq_CPPFLAGS) $(CPPFLAGS) $(tests_lib_test_heavy_wq_CFLAGS) $(CFLAGS) -MT tests/helpers/c/lib_test_heavy_wq-main.obj -MD -MP -MF tests/helpers/c/$(DEPDIR)/lib_test_heavy_wq-main.Tpo -c -o tests/helpers/c/lib_test_heavy_wq-main.obj `if test -f 'tests/helpers/c/main.c'; then $(CYGPATH_W) 'tests/helpers/c/main.c'; else $(CYGPATH_W) '$(srcdir)/tests/helpers/c/main.c'; fi` @am__fastdepCC_TRUE@ $(AM_V_at)$(am__mv) tests/helpers/c/$(DEPDIR)/lib_test_heavy_wq-main.Tpo tests/helpers/c/$(DEPDIR)/lib_test_heavy_wq-main.Po @AMDEP_TRUE@@am__fastdepCC_FALSE@ $(AM_V_CC)source='tests/helpers/c/main.c' object='tests/helpers/c/lib_test_heavy_wq-main.obj' libtool=no @AMDEPBACKSLASH@ @AMDEP_TRUE@@am__fastdepCC_FALSE@ DEPDIR=$(DEPDIR) $(CCDEPMODE) $(depcomp) @AMDEPBACKSLASH@ @am__fastdepCC_FALSE@ $(AM_V_CC@am__nodep@)$(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(tests_lib_test_heavy_wq_CPPFLAGS) $(CPPFLAGS) $(tests_lib_test_heavy_wq_CFLAGS) $(CFLAGS) -c -o tests/helpers/c/lib_test_heavy_wq-main.obj `if test -f 'tests/helpers/c/main.c'; then $(CYGPATH_W) 'tests/helpers/c/main.c'; else $(CYGPATH_W) '$(srcdir)/tests/helpers/c/main.c'; fi` tests/lib/test_idalloc-test_idalloc.o: tests/lib/test_idalloc.c @am__fastdepCC_TRUE@ $(AM_V_CC)$(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(AM_CPPFLAGS) $(CPPFLAGS) $(tests_lib_test_idalloc_CFLAGS) $(CFLAGS) -MT tests/lib/test_idalloc-test_idalloc.o -MD -MP -MF tests/lib/$(DEPDIR)/test_idalloc-test_idalloc.Tpo -c -o tests/lib/test_idalloc-test_idalloc.o `test -f 'tests/lib/test_idalloc.c' || echo '$(srcdir)/'`tests/lib/test_idalloc.c @am__fastdepCC_TRUE@ $(AM_V_at)$(am__mv) tests/lib/$(DEPDIR)/test_idalloc-test_idalloc.Tpo tests/lib/$(DEPDIR)/test_idalloc-test_idalloc.Po @AMDEP_TRUE@@am__fastdepCC_FALSE@ $(AM_V_CC)source='tests/lib/test_idalloc.c' object='tests/lib/test_idalloc-test_idalloc.o' libtool=no @AMDEPBACKSLASH@ @AMDEP_TRUE@@am__fastdepCC_FALSE@ DEPDIR=$(DEPDIR) $(CCDEPMODE) $(depcomp) @AMDEPBACKSLASH@ @am__fastdepCC_FALSE@ $(AM_V_CC@am__nodep@)$(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(AM_CPPFLAGS) $(CPPFLAGS) $(tests_lib_test_idalloc_CFLAGS) $(CFLAGS) -c -o tests/lib/test_idalloc-test_idalloc.o `test -f 'tests/lib/test_idalloc.c' || echo '$(srcdir)/'`tests/lib/test_idalloc.c tests/lib/test_idalloc-test_idalloc.obj: tests/lib/test_idalloc.c @am__fastdepCC_TRUE@ $(AM_V_CC)$(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(AM_CPPFLAGS) $(CPPFLAGS) $(tests_lib_test_idalloc_CFLAGS) $(CFLAGS) -MT tests/lib/test_idalloc-test_idalloc.obj -MD -MP -MF tests/lib/$(DEPDIR)/test_idalloc-test_idalloc.Tpo -c -o tests/lib/test_idalloc-test_idalloc.obj `if test -f 'tests/lib/test_idalloc.c'; then $(CYGPATH_W) 'tests/lib/test_idalloc.c'; else $(CYGPATH_W) '$(srcdir)/tests/lib/test_idalloc.c'; fi` @am__fastdepCC_TRUE@ $(AM_V_at)$(am__mv) tests/lib/$(DEPDIR)/test_idalloc-test_idalloc.Tpo tests/lib/$(DEPDIR)/test_idalloc-test_idalloc.Po @AMDEP_TRUE@@am__fastdepCC_FALSE@ $(AM_V_CC)source='tests/lib/test_idalloc.c' object='tests/lib/test_idalloc-test_idalloc.obj' libtool=no @AMDEPBACKSLASH@ @AMDEP_TRUE@@am__fastdepCC_FALSE@ DEPDIR=$(DEPDIR) $(CCDEPMODE) $(depcomp) @AMDEPBACKSLASH@ @am__fastdepCC_FALSE@ $(AM_V_CC@am__nodep@)$(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(AM_CPPFLAGS) $(CPPFLAGS) $(tests_lib_test_idalloc_CFLAGS) $(CFLAGS) -c -o tests/lib/test_idalloc-test_idalloc.obj `if test -f 'tests/lib/test_idalloc.c'; then $(CYGPATH_W) 'tests/lib/test_idalloc.c'; else $(CYGPATH_W) '$(srcdir)/tests/lib/test_idalloc.c'; fi` tests/lib/test_memory-test_memory.o: tests/lib/test_memory.c @am__fastdepCC_TRUE@ $(AM_V_CC)$(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(tests_lib_test_memory_CPPFLAGS) $(CPPFLAGS) $(tests_lib_test_memory_CFLAGS) $(CFLAGS) -MT tests/lib/test_memory-test_memory.o -MD -MP -MF tests/lib/$(DEPDIR)/test_memory-test_memory.Tpo -c -o tests/lib/test_memory-test_memory.o `test -f 'tests/lib/test_memory.c' || echo '$(srcdir)/'`tests/lib/test_memory.c @am__fastdepCC_TRUE@ $(AM_V_at)$(am__mv) tests/lib/$(DEPDIR)/test_memory-test_memory.Tpo tests/lib/$(DEPDIR)/test_memory-test_memory.Po @AMDEP_TRUE@@am__fastdepCC_FALSE@ $(AM_V_CC)source='tests/lib/test_memory.c' object='tests/lib/test_memory-test_memory.o' libtool=no @AMDEPBACKSLASH@ @AMDEP_TRUE@@am__fastdepCC_FALSE@ DEPDIR=$(DEPDIR) $(CCDEPMODE) $(depcomp) @AMDEPBACKSLASH@ @am__fastdepCC_FALSE@ $(AM_V_CC@am__nodep@)$(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(tests_lib_test_memory_CPPFLAGS) $(CPPFLAGS) $(tests_lib_test_memory_CFLAGS) $(CFLAGS) -c -o tests/lib/test_memory-test_memory.o `test -f 'tests/lib/test_memory.c' || echo '$(srcdir)/'`tests/lib/test_memory.c tests/lib/test_memory-test_memory.obj: tests/lib/test_memory.c @am__fastdepCC_TRUE@ $(AM_V_CC)$(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(tests_lib_test_memory_CPPFLAGS) $(CPPFLAGS) $(tests_lib_test_memory_CFLAGS) $(CFLAGS) -MT tests/lib/test_memory-test_memory.obj -MD -MP -MF tests/lib/$(DEPDIR)/test_memory-test_memory.Tpo -c -o tests/lib/test_memory-test_memory.obj `if test -f 'tests/lib/test_memory.c'; then $(CYGPATH_W) 'tests/lib/test_memory.c'; else $(CYGPATH_W) '$(srcdir)/tests/lib/test_memory.c'; fi` @am__fastdepCC_TRUE@ $(AM_V_at)$(am__mv) tests/lib/$(DEPDIR)/test_memory-test_memory.Tpo tests/lib/$(DEPDIR)/test_memory-test_memory.Po @AMDEP_TRUE@@am__fastdepCC_FALSE@ $(AM_V_CC)source='tests/lib/test_memory.c' object='tests/lib/test_memory-test_memory.obj' libtool=no @AMDEPBACKSLASH@ @AMDEP_TRUE@@am__fastdepCC_FALSE@ DEPDIR=$(DEPDIR) $(CCDEPMODE) $(depcomp) @AMDEPBACKSLASH@ @am__fastdepCC_FALSE@ $(AM_V_CC@am__nodep@)$(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(tests_lib_test_memory_CPPFLAGS) $(CPPFLAGS) $(tests_lib_test_memory_CFLAGS) $(CFLAGS) -c -o tests/lib/test_memory-test_memory.obj `if test -f 'tests/lib/test_memory.c'; then $(CYGPATH_W) 'tests/lib/test_memory.c'; else $(CYGPATH_W) '$(srcdir)/tests/lib/test_memory.c'; fi` tests/lib/test_nexthop_iter-test_nexthop_iter.o: tests/lib/test_nexthop_iter.c @am__fastdepCC_TRUE@ $(AM_V_CC)$(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(tests_lib_test_nexthop_iter_CPPFLAGS) $(CPPFLAGS) $(tests_lib_test_nexthop_iter_CFLAGS) $(CFLAGS) -MT tests/lib/test_nexthop_iter-test_nexthop_iter.o -MD -MP -MF tests/lib/$(DEPDIR)/test_nexthop_iter-test_nexthop_iter.Tpo -c -o tests/lib/test_nexthop_iter-test_nexthop_iter.o `test -f 'tests/lib/test_nexthop_iter.c' || echo '$(srcdir)/'`tests/lib/test_nexthop_iter.c @am__fastdepCC_TRUE@ $(AM_V_at)$(am__mv) tests/lib/$(DEPDIR)/test_nexthop_iter-test_nexthop_iter.Tpo tests/lib/$(DEPDIR)/test_nexthop_iter-test_nexthop_iter.Po @AMDEP_TRUE@@am__fastdepCC_FALSE@ $(AM_V_CC)source='tests/lib/test_nexthop_iter.c' object='tests/lib/test_nexthop_iter-test_nexthop_iter.o' libtool=no @AMDEPBACKSLASH@ @AMDEP_TRUE@@am__fastdepCC_FALSE@ DEPDIR=$(DEPDIR) $(CCDEPMODE) $(depcomp) @AMDEPBACKSLASH@ @am__fastdepCC_FALSE@ $(AM_V_CC@am__nodep@)$(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(tests_lib_test_nexthop_iter_CPPFLAGS) $(CPPFLAGS) $(tests_lib_test_nexthop_iter_CFLAGS) $(CFLAGS) -c -o tests/lib/test_nexthop_iter-test_nexthop_iter.o `test -f 'tests/lib/test_nexthop_iter.c' || echo '$(srcdir)/'`tests/lib/test_nexthop_iter.c tests/lib/test_nexthop_iter-test_nexthop_iter.obj: tests/lib/test_nexthop_iter.c @am__fastdepCC_TRUE@ $(AM_V_CC)$(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(tests_lib_test_nexthop_iter_CPPFLAGS) $(CPPFLAGS) $(tests_lib_test_nexthop_iter_CFLAGS) $(CFLAGS) -MT tests/lib/test_nexthop_iter-test_nexthop_iter.obj -MD -MP -MF tests/lib/$(DEPDIR)/test_nexthop_iter-test_nexthop_iter.Tpo -c -o tests/lib/test_nexthop_iter-test_nexthop_iter.obj `if test -f 'tests/lib/test_nexthop_iter.c'; then $(CYGPATH_W) 'tests/lib/test_nexthop_iter.c'; else $(CYGPATH_W) '$(srcdir)/tests/lib/test_nexthop_iter.c'; fi` @am__fastdepCC_TRUE@ $(AM_V_at)$(am__mv) tests/lib/$(DEPDIR)/test_nexthop_iter-test_nexthop_iter.Tpo tests/lib/$(DEPDIR)/test_nexthop_iter-test_nexthop_iter.Po @AMDEP_TRUE@@am__fastdepCC_FALSE@ $(AM_V_CC)source='tests/lib/test_nexthop_iter.c' object='tests/lib/test_nexthop_iter-test_nexthop_iter.obj' libtool=no @AMDEPBACKSLASH@ @AMDEP_TRUE@@am__fastdepCC_FALSE@ DEPDIR=$(DEPDIR) $(CCDEPMODE) $(depcomp) @AMDEPBACKSLASH@ @am__fastdepCC_FALSE@ $(AM_V_CC@am__nodep@)$(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(tests_lib_test_nexthop_iter_CPPFLAGS) $(CPPFLAGS) $(tests_lib_test_nexthop_iter_CFLAGS) $(CFLAGS) -c -o tests/lib/test_nexthop_iter-test_nexthop_iter.obj `if test -f 'tests/lib/test_nexthop_iter.c'; then $(CYGPATH_W) 'tests/lib/test_nexthop_iter.c'; else $(CYGPATH_W) '$(srcdir)/tests/lib/test_nexthop_iter.c'; fi` tests/helpers/c/lib_test_nexthop_iter-prng.o: tests/helpers/c/prng.c @am__fastdepCC_TRUE@ $(AM_V_CC)$(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(tests_lib_test_nexthop_iter_CPPFLAGS) $(CPPFLAGS) $(tests_lib_test_nexthop_iter_CFLAGS) $(CFLAGS) -MT tests/helpers/c/lib_test_nexthop_iter-prng.o -MD -MP -MF tests/helpers/c/$(DEPDIR)/lib_test_nexthop_iter-prng.Tpo -c -o tests/helpers/c/lib_test_nexthop_iter-prng.o `test -f 'tests/helpers/c/prng.c' || echo '$(srcdir)/'`tests/helpers/c/prng.c @am__fastdepCC_TRUE@ $(AM_V_at)$(am__mv) tests/helpers/c/$(DEPDIR)/lib_test_nexthop_iter-prng.Tpo tests/helpers/c/$(DEPDIR)/lib_test_nexthop_iter-prng.Po @AMDEP_TRUE@@am__fastdepCC_FALSE@ $(AM_V_CC)source='tests/helpers/c/prng.c' object='tests/helpers/c/lib_test_nexthop_iter-prng.o' libtool=no @AMDEPBACKSLASH@ @AMDEP_TRUE@@am__fastdepCC_FALSE@ DEPDIR=$(DEPDIR) $(CCDEPMODE) $(depcomp) @AMDEPBACKSLASH@ @am__fastdepCC_FALSE@ $(AM_V_CC@am__nodep@)$(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(tests_lib_test_nexthop_iter_CPPFLAGS) $(CPPFLAGS) $(tests_lib_test_nexthop_iter_CFLAGS) $(CFLAGS) -c -o tests/helpers/c/lib_test_nexthop_iter-prng.o `test -f 'tests/helpers/c/prng.c' || echo '$(srcdir)/'`tests/helpers/c/prng.c tests/helpers/c/lib_test_nexthop_iter-prng.obj: tests/helpers/c/prng.c @am__fastdepCC_TRUE@ $(AM_V_CC)$(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(tests_lib_test_nexthop_iter_CPPFLAGS) $(CPPFLAGS) $(tests_lib_test_nexthop_iter_CFLAGS) $(CFLAGS) -MT tests/helpers/c/lib_test_nexthop_iter-prng.obj -MD -MP -MF tests/helpers/c/$(DEPDIR)/lib_test_nexthop_iter-prng.Tpo -c -o tests/helpers/c/lib_test_nexthop_iter-prng.obj `if test -f 'tests/helpers/c/prng.c'; then $(CYGPATH_W) 'tests/helpers/c/prng.c'; else $(CYGPATH_W) '$(srcdir)/tests/helpers/c/prng.c'; fi` @am__fastdepCC_TRUE@ $(AM_V_at)$(am__mv) tests/helpers/c/$(DEPDIR)/lib_test_nexthop_iter-prng.Tpo tests/helpers/c/$(DEPDIR)/lib_test_nexthop_iter-prng.Po @AMDEP_TRUE@@am__fastdepCC_FALSE@ $(AM_V_CC)source='tests/helpers/c/prng.c' object='tests/helpers/c/lib_test_nexthop_iter-prng.obj' libtool=no @AMDEPBACKSLASH@ @AMDEP_TRUE@@am__fastdepCC_FALSE@ DEPDIR=$(DEPDIR) $(CCDEPMODE) $(depcomp) @AMDEPBACKSLASH@ @am__fastdepCC_FALSE@ $(AM_V_CC@am__nodep@)$(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(tests_lib_test_nexthop_iter_CPPFLAGS) $(CPPFLAGS) $(tests_lib_test_nexthop_iter_CFLAGS) $(CFLAGS) -c -o tests/helpers/c/lib_test_nexthop_iter-prng.obj `if test -f 'tests/helpers/c/prng.c'; then $(CYGPATH_W) 'tests/helpers/c/prng.c'; else $(CYGPATH_W) '$(srcdir)/tests/helpers/c/prng.c'; fi` tests/lib/test_ntop-test_ntop.o: tests/lib/test_ntop.c @am__fastdepCC_TRUE@ $(AM_V_CC)$(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(tests_lib_test_ntop_CPPFLAGS) $(CPPFLAGS) $(tests_lib_test_ntop_CFLAGS) $(CFLAGS) -MT tests/lib/test_ntop-test_ntop.o -MD -MP -MF tests/lib/$(DEPDIR)/test_ntop-test_ntop.Tpo -c -o tests/lib/test_ntop-test_ntop.o `test -f 'tests/lib/test_ntop.c' || echo '$(srcdir)/'`tests/lib/test_ntop.c @am__fastdepCC_TRUE@ $(AM_V_at)$(am__mv) tests/lib/$(DEPDIR)/test_ntop-test_ntop.Tpo tests/lib/$(DEPDIR)/test_ntop-test_ntop.Po @AMDEP_TRUE@@am__fastdepCC_FALSE@ $(AM_V_CC)source='tests/lib/test_ntop.c' object='tests/lib/test_ntop-test_ntop.o' libtool=no @AMDEPBACKSLASH@ @AMDEP_TRUE@@am__fastdepCC_FALSE@ DEPDIR=$(DEPDIR) $(CCDEPMODE) $(depcomp) @AMDEPBACKSLASH@ @am__fastdepCC_FALSE@ $(AM_V_CC@am__nodep@)$(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(tests_lib_test_ntop_CPPFLAGS) $(CPPFLAGS) $(tests_lib_test_ntop_CFLAGS) $(CFLAGS) -c -o tests/lib/test_ntop-test_ntop.o `test -f 'tests/lib/test_ntop.c' || echo '$(srcdir)/'`tests/lib/test_ntop.c tests/lib/test_ntop-test_ntop.obj: tests/lib/test_ntop.c @am__fastdepCC_TRUE@ $(AM_V_CC)$(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(tests_lib_test_ntop_CPPFLAGS) $(CPPFLAGS) $(tests_lib_test_ntop_CFLAGS) $(CFLAGS) -MT tests/lib/test_ntop-test_ntop.obj -MD -MP -MF tests/lib/$(DEPDIR)/test_ntop-test_ntop.Tpo -c -o tests/lib/test_ntop-test_ntop.obj `if test -f 'tests/lib/test_ntop.c'; then $(CYGPATH_W) 'tests/lib/test_ntop.c'; else $(CYGPATH_W) '$(srcdir)/tests/lib/test_ntop.c'; fi` @am__fastdepCC_TRUE@ $(AM_V_at)$(am__mv) tests/lib/$(DEPDIR)/test_ntop-test_ntop.Tpo tests/lib/$(DEPDIR)/test_ntop-test_ntop.Po @AMDEP_TRUE@@am__fastdepCC_FALSE@ $(AM_V_CC)source='tests/lib/test_ntop.c' object='tests/lib/test_ntop-test_ntop.obj' libtool=no @AMDEPBACKSLASH@ @AMDEP_TRUE@@am__fastdepCC_FALSE@ DEPDIR=$(DEPDIR) $(CCDEPMODE) $(depcomp) @AMDEPBACKSLASH@ @am__fastdepCC_FALSE@ $(AM_V_CC@am__nodep@)$(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(tests_lib_test_ntop_CPPFLAGS) $(CPPFLAGS) $(tests_lib_test_ntop_CFLAGS) $(CFLAGS) -c -o tests/lib/test_ntop-test_ntop.obj `if test -f 'tests/lib/test_ntop.c'; then $(CYGPATH_W) 'tests/lib/test_ntop.c'; else $(CYGPATH_W) '$(srcdir)/tests/lib/test_ntop.c'; fi` tests/helpers/c/lib_test_ntop-prng.o: tests/helpers/c/prng.c @am__fastdepCC_TRUE@ $(AM_V_CC)$(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(tests_lib_test_ntop_CPPFLAGS) $(CPPFLAGS) $(tests_lib_test_ntop_CFLAGS) $(CFLAGS) -MT tests/helpers/c/lib_test_ntop-prng.o -MD -MP -MF tests/helpers/c/$(DEPDIR)/lib_test_ntop-prng.Tpo -c -o tests/helpers/c/lib_test_ntop-prng.o `test -f 'tests/helpers/c/prng.c' || echo '$(srcdir)/'`tests/helpers/c/prng.c @am__fastdepCC_TRUE@ $(AM_V_at)$(am__mv) tests/helpers/c/$(DEPDIR)/lib_test_ntop-prng.Tpo tests/helpers/c/$(DEPDIR)/lib_test_ntop-prng.Po @AMDEP_TRUE@@am__fastdepCC_FALSE@ $(AM_V_CC)source='tests/helpers/c/prng.c' object='tests/helpers/c/lib_test_ntop-prng.o' libtool=no @AMDEPBACKSLASH@ @AMDEP_TRUE@@am__fastdepCC_FALSE@ DEPDIR=$(DEPDIR) $(CCDEPMODE) $(depcomp) @AMDEPBACKSLASH@ @am__fastdepCC_FALSE@ $(AM_V_CC@am__nodep@)$(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(tests_lib_test_ntop_CPPFLAGS) $(CPPFLAGS) $(tests_lib_test_ntop_CFLAGS) $(CFLAGS) -c -o tests/helpers/c/lib_test_ntop-prng.o `test -f 'tests/helpers/c/prng.c' || echo '$(srcdir)/'`tests/helpers/c/prng.c tests/helpers/c/lib_test_ntop-prng.obj: tests/helpers/c/prng.c @am__fastdepCC_TRUE@ $(AM_V_CC)$(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(tests_lib_test_ntop_CPPFLAGS) $(CPPFLAGS) $(tests_lib_test_ntop_CFLAGS) $(CFLAGS) -MT tests/helpers/c/lib_test_ntop-prng.obj -MD -MP -MF tests/helpers/c/$(DEPDIR)/lib_test_ntop-prng.Tpo -c -o tests/helpers/c/lib_test_ntop-prng.obj `if test -f 'tests/helpers/c/prng.c'; then $(CYGPATH_W) 'tests/helpers/c/prng.c'; else $(CYGPATH_W) '$(srcdir)/tests/helpers/c/prng.c'; fi` @am__fastdepCC_TRUE@ $(AM_V_at)$(am__mv) tests/helpers/c/$(DEPDIR)/lib_test_ntop-prng.Tpo tests/helpers/c/$(DEPDIR)/lib_test_ntop-prng.Po @AMDEP_TRUE@@am__fastdepCC_FALSE@ $(AM_V_CC)source='tests/helpers/c/prng.c' object='tests/helpers/c/lib_test_ntop-prng.obj' libtool=no @AMDEPBACKSLASH@ @AMDEP_TRUE@@am__fastdepCC_FALSE@ DEPDIR=$(DEPDIR) $(CCDEPMODE) $(depcomp) @AMDEPBACKSLASH@ @am__fastdepCC_FALSE@ $(AM_V_CC@am__nodep@)$(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(tests_lib_test_ntop_CPPFLAGS) $(CPPFLAGS) $(tests_lib_test_ntop_CFLAGS) $(CFLAGS) -c -o tests/helpers/c/lib_test_ntop-prng.obj `if test -f 'tests/helpers/c/prng.c'; then $(CYGPATH_W) 'tests/helpers/c/prng.c'; else $(CYGPATH_W) '$(srcdir)/tests/helpers/c/prng.c'; fi` tests/lib/test_prefix2str-test_prefix2str.o: tests/lib/test_prefix2str.c @am__fastdepCC_TRUE@ $(AM_V_CC)$(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(tests_lib_test_prefix2str_CPPFLAGS) $(CPPFLAGS) $(tests_lib_test_prefix2str_CFLAGS) $(CFLAGS) -MT tests/lib/test_prefix2str-test_prefix2str.o -MD -MP -MF tests/lib/$(DEPDIR)/test_prefix2str-test_prefix2str.Tpo -c -o tests/lib/test_prefix2str-test_prefix2str.o `test -f 'tests/lib/test_prefix2str.c' || echo '$(srcdir)/'`tests/lib/test_prefix2str.c @am__fastdepCC_TRUE@ $(AM_V_at)$(am__mv) tests/lib/$(DEPDIR)/test_prefix2str-test_prefix2str.Tpo tests/lib/$(DEPDIR)/test_prefix2str-test_prefix2str.Po @AMDEP_TRUE@@am__fastdepCC_FALSE@ $(AM_V_CC)source='tests/lib/test_prefix2str.c' object='tests/lib/test_prefix2str-test_prefix2str.o' libtool=no @AMDEPBACKSLASH@ @AMDEP_TRUE@@am__fastdepCC_FALSE@ DEPDIR=$(DEPDIR) $(CCDEPMODE) $(depcomp) @AMDEPBACKSLASH@ @am__fastdepCC_FALSE@ $(AM_V_CC@am__nodep@)$(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(tests_lib_test_prefix2str_CPPFLAGS) $(CPPFLAGS) $(tests_lib_test_prefix2str_CFLAGS) $(CFLAGS) -c -o tests/lib/test_prefix2str-test_prefix2str.o `test -f 'tests/lib/test_prefix2str.c' || echo '$(srcdir)/'`tests/lib/test_prefix2str.c tests/lib/test_prefix2str-test_prefix2str.obj: tests/lib/test_prefix2str.c @am__fastdepCC_TRUE@ $(AM_V_CC)$(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(tests_lib_test_prefix2str_CPPFLAGS) $(CPPFLAGS) $(tests_lib_test_prefix2str_CFLAGS) $(CFLAGS) -MT tests/lib/test_prefix2str-test_prefix2str.obj -MD -MP -MF tests/lib/$(DEPDIR)/test_prefix2str-test_prefix2str.Tpo -c -o tests/lib/test_prefix2str-test_prefix2str.obj `if test -f 'tests/lib/test_prefix2str.c'; then $(CYGPATH_W) 'tests/lib/test_prefix2str.c'; else $(CYGPATH_W) '$(srcdir)/tests/lib/test_prefix2str.c'; fi` @am__fastdepCC_TRUE@ $(AM_V_at)$(am__mv) tests/lib/$(DEPDIR)/test_prefix2str-test_prefix2str.Tpo tests/lib/$(DEPDIR)/test_prefix2str-test_prefix2str.Po @AMDEP_TRUE@@am__fastdepCC_FALSE@ $(AM_V_CC)source='tests/lib/test_prefix2str.c' object='tests/lib/test_prefix2str-test_prefix2str.obj' libtool=no @AMDEPBACKSLASH@ @AMDEP_TRUE@@am__fastdepCC_FALSE@ DEPDIR=$(DEPDIR) $(CCDEPMODE) $(depcomp) @AMDEPBACKSLASH@ @am__fastdepCC_FALSE@ $(AM_V_CC@am__nodep@)$(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(tests_lib_test_prefix2str_CPPFLAGS) $(CPPFLAGS) $(tests_lib_test_prefix2str_CFLAGS) $(CFLAGS) -c -o tests/lib/test_prefix2str-test_prefix2str.obj `if test -f 'tests/lib/test_prefix2str.c'; then $(CYGPATH_W) 'tests/lib/test_prefix2str.c'; else $(CYGPATH_W) '$(srcdir)/tests/lib/test_prefix2str.c'; fi` tests/helpers/c/lib_test_prefix2str-prng.o: tests/helpers/c/prng.c @am__fastdepCC_TRUE@ $(AM_V_CC)$(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(tests_lib_test_prefix2str_CPPFLAGS) $(CPPFLAGS) $(tests_lib_test_prefix2str_CFLAGS) $(CFLAGS) -MT tests/helpers/c/lib_test_prefix2str-prng.o -MD -MP -MF tests/helpers/c/$(DEPDIR)/lib_test_prefix2str-prng.Tpo -c -o tests/helpers/c/lib_test_prefix2str-prng.o `test -f 'tests/helpers/c/prng.c' || echo '$(srcdir)/'`tests/helpers/c/prng.c @am__fastdepCC_TRUE@ $(AM_V_at)$(am__mv) tests/helpers/c/$(DEPDIR)/lib_test_prefix2str-prng.Tpo tests/helpers/c/$(DEPDIR)/lib_test_prefix2str-prng.Po @AMDEP_TRUE@@am__fastdepCC_FALSE@ $(AM_V_CC)source='tests/helpers/c/prng.c' object='tests/helpers/c/lib_test_prefix2str-prng.o' libtool=no @AMDEPBACKSLASH@ @AMDEP_TRUE@@am__fastdepCC_FALSE@ DEPDIR=$(DEPDIR) $(CCDEPMODE) $(depcomp) @AMDEPBACKSLASH@ @am__fastdepCC_FALSE@ $(AM_V_CC@am__nodep@)$(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(tests_lib_test_prefix2str_CPPFLAGS) $(CPPFLAGS) $(tests_lib_test_prefix2str_CFLAGS) $(CFLAGS) -c -o tests/helpers/c/lib_test_prefix2str-prng.o `test -f 'tests/helpers/c/prng.c' || echo '$(srcdir)/'`tests/helpers/c/prng.c tests/helpers/c/lib_test_prefix2str-prng.obj: tests/helpers/c/prng.c @am__fastdepCC_TRUE@ $(AM_V_CC)$(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(tests_lib_test_prefix2str_CPPFLAGS) $(CPPFLAGS) $(tests_lib_test_prefix2str_CFLAGS) $(CFLAGS) -MT tests/helpers/c/lib_test_prefix2str-prng.obj -MD -MP -MF tests/helpers/c/$(DEPDIR)/lib_test_prefix2str-prng.Tpo -c -o tests/helpers/c/lib_test_prefix2str-prng.obj `if test -f 'tests/helpers/c/prng.c'; then $(CYGPATH_W) 'tests/helpers/c/prng.c'; else $(CYGPATH_W) '$(srcdir)/tests/helpers/c/prng.c'; fi` @am__fastdepCC_TRUE@ $(AM_V_at)$(am__mv) tests/helpers/c/$(DEPDIR)/lib_test_prefix2str-prng.Tpo tests/helpers/c/$(DEPDIR)/lib_test_prefix2str-prng.Po @AMDEP_TRUE@@am__fastdepCC_FALSE@ $(AM_V_CC)source='tests/helpers/c/prng.c' object='tests/helpers/c/lib_test_prefix2str-prng.obj' libtool=no @AMDEPBACKSLASH@ @AMDEP_TRUE@@am__fastdepCC_FALSE@ DEPDIR=$(DEPDIR) $(CCDEPMODE) $(depcomp) @AMDEPBACKSLASH@ @am__fastdepCC_FALSE@ $(AM_V_CC@am__nodep@)$(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(tests_lib_test_prefix2str_CPPFLAGS) $(CPPFLAGS) $(tests_lib_test_prefix2str_CFLAGS) $(CFLAGS) -c -o tests/helpers/c/lib_test_prefix2str-prng.obj `if test -f 'tests/helpers/c/prng.c'; then $(CYGPATH_W) 'tests/helpers/c/prng.c'; else $(CYGPATH_W) '$(srcdir)/tests/helpers/c/prng.c'; fi` tests/lib/test_printfrr-test_printfrr.o: tests/lib/test_printfrr.c @am__fastdepCC_TRUE@ $(AM_V_CC)$(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(tests_lib_test_printfrr_CPPFLAGS) $(CPPFLAGS) $(tests_lib_test_printfrr_CFLAGS) $(CFLAGS) -MT tests/lib/test_printfrr-test_printfrr.o -MD -MP -MF tests/lib/$(DEPDIR)/test_printfrr-test_printfrr.Tpo -c -o tests/lib/test_printfrr-test_printfrr.o `test -f 'tests/lib/test_printfrr.c' || echo '$(srcdir)/'`tests/lib/test_printfrr.c @am__fastdepCC_TRUE@ $(AM_V_at)$(am__mv) tests/lib/$(DEPDIR)/test_printfrr-test_printfrr.Tpo tests/lib/$(DEPDIR)/test_printfrr-test_printfrr.Po @AMDEP_TRUE@@am__fastdepCC_FALSE@ $(AM_V_CC)source='tests/lib/test_printfrr.c' object='tests/lib/test_printfrr-test_printfrr.o' libtool=no @AMDEPBACKSLASH@ @AMDEP_TRUE@@am__fastdepCC_FALSE@ DEPDIR=$(DEPDIR) $(CCDEPMODE) $(depcomp) @AMDEPBACKSLASH@ @am__fastdepCC_FALSE@ $(AM_V_CC@am__nodep@)$(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(tests_lib_test_printfrr_CPPFLAGS) $(CPPFLAGS) $(tests_lib_test_printfrr_CFLAGS) $(CFLAGS) -c -o tests/lib/test_printfrr-test_printfrr.o `test -f 'tests/lib/test_printfrr.c' || echo '$(srcdir)/'`tests/lib/test_printfrr.c tests/lib/test_printfrr-test_printfrr.obj: tests/lib/test_printfrr.c @am__fastdepCC_TRUE@ $(AM_V_CC)$(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(tests_lib_test_printfrr_CPPFLAGS) $(CPPFLAGS) $(tests_lib_test_printfrr_CFLAGS) $(CFLAGS) -MT tests/lib/test_printfrr-test_printfrr.obj -MD -MP -MF tests/lib/$(DEPDIR)/test_printfrr-test_printfrr.Tpo -c -o tests/lib/test_printfrr-test_printfrr.obj `if test -f 'tests/lib/test_printfrr.c'; then $(CYGPATH_W) 'tests/lib/test_printfrr.c'; else $(CYGPATH_W) '$(srcdir)/tests/lib/test_printfrr.c'; fi` @am__fastdepCC_TRUE@ $(AM_V_at)$(am__mv) tests/lib/$(DEPDIR)/test_printfrr-test_printfrr.Tpo tests/lib/$(DEPDIR)/test_printfrr-test_printfrr.Po @AMDEP_TRUE@@am__fastdepCC_FALSE@ $(AM_V_CC)source='tests/lib/test_printfrr.c' object='tests/lib/test_printfrr-test_printfrr.obj' libtool=no @AMDEPBACKSLASH@ @AMDEP_TRUE@@am__fastdepCC_FALSE@ DEPDIR=$(DEPDIR) $(CCDEPMODE) $(depcomp) @AMDEPBACKSLASH@ @am__fastdepCC_FALSE@ $(AM_V_CC@am__nodep@)$(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(tests_lib_test_printfrr_CPPFLAGS) $(CPPFLAGS) $(tests_lib_test_printfrr_CFLAGS) $(CFLAGS) -c -o tests/lib/test_printfrr-test_printfrr.obj `if test -f 'tests/lib/test_printfrr.c'; then $(CYGPATH_W) 'tests/lib/test_printfrr.c'; else $(CYGPATH_W) '$(srcdir)/tests/lib/test_printfrr.c'; fi` tests/lib/test_privs-test_privs.o: tests/lib/test_privs.c @am__fastdepCC_TRUE@ $(AM_V_CC)$(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(tests_lib_test_privs_CPPFLAGS) $(CPPFLAGS) $(tests_lib_test_privs_CFLAGS) $(CFLAGS) -MT tests/lib/test_privs-test_privs.o -MD -MP -MF tests/lib/$(DEPDIR)/test_privs-test_privs.Tpo -c -o tests/lib/test_privs-test_privs.o `test -f 'tests/lib/test_privs.c' || echo '$(srcdir)/'`tests/lib/test_privs.c @am__fastdepCC_TRUE@ $(AM_V_at)$(am__mv) tests/lib/$(DEPDIR)/test_privs-test_privs.Tpo tests/lib/$(DEPDIR)/test_privs-test_privs.Po @AMDEP_TRUE@@am__fastdepCC_FALSE@ $(AM_V_CC)source='tests/lib/test_privs.c' object='tests/lib/test_privs-test_privs.o' libtool=no @AMDEPBACKSLASH@ @AMDEP_TRUE@@am__fastdepCC_FALSE@ DEPDIR=$(DEPDIR) $(CCDEPMODE) $(depcomp) @AMDEPBACKSLASH@ @am__fastdepCC_FALSE@ $(AM_V_CC@am__nodep@)$(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(tests_lib_test_privs_CPPFLAGS) $(CPPFLAGS) $(tests_lib_test_privs_CFLAGS) $(CFLAGS) -c -o tests/lib/test_privs-test_privs.o `test -f 'tests/lib/test_privs.c' || echo '$(srcdir)/'`tests/lib/test_privs.c tests/lib/test_privs-test_privs.obj: tests/lib/test_privs.c @am__fastdepCC_TRUE@ $(AM_V_CC)$(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(tests_lib_test_privs_CPPFLAGS) $(CPPFLAGS) $(tests_lib_test_privs_CFLAGS) $(CFLAGS) -MT tests/lib/test_privs-test_privs.obj -MD -MP -MF tests/lib/$(DEPDIR)/test_privs-test_privs.Tpo -c -o tests/lib/test_privs-test_privs.obj `if test -f 'tests/lib/test_privs.c'; then $(CYGPATH_W) 'tests/lib/test_privs.c'; else $(CYGPATH_W) '$(srcdir)/tests/lib/test_privs.c'; fi` @am__fastdepCC_TRUE@ $(AM_V_at)$(am__mv) tests/lib/$(DEPDIR)/test_privs-test_privs.Tpo tests/lib/$(DEPDIR)/test_privs-test_privs.Po @AMDEP_TRUE@@am__fastdepCC_FALSE@ $(AM_V_CC)source='tests/lib/test_privs.c' object='tests/lib/test_privs-test_privs.obj' libtool=no @AMDEPBACKSLASH@ @AMDEP_TRUE@@am__fastdepCC_FALSE@ DEPDIR=$(DEPDIR) $(CCDEPMODE) $(depcomp) @AMDEPBACKSLASH@ @am__fastdepCC_FALSE@ $(AM_V_CC@am__nodep@)$(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(tests_lib_test_privs_CPPFLAGS) $(CPPFLAGS) $(tests_lib_test_privs_CFLAGS) $(CFLAGS) -c -o tests/lib/test_privs-test_privs.obj `if test -f 'tests/lib/test_privs.c'; then $(CYGPATH_W) 'tests/lib/test_privs.c'; else $(CYGPATH_W) '$(srcdir)/tests/lib/test_privs.c'; fi` tests/lib/test_ringbuf-test_ringbuf.o: tests/lib/test_ringbuf.c @am__fastdepCC_TRUE@ $(AM_V_CC)$(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(tests_lib_test_ringbuf_CPPFLAGS) $(CPPFLAGS) $(tests_lib_test_ringbuf_CFLAGS) $(CFLAGS) -MT tests/lib/test_ringbuf-test_ringbuf.o -MD -MP -MF tests/lib/$(DEPDIR)/test_ringbuf-test_ringbuf.Tpo -c -o tests/lib/test_ringbuf-test_ringbuf.o `test -f 'tests/lib/test_ringbuf.c' || echo '$(srcdir)/'`tests/lib/test_ringbuf.c @am__fastdepCC_TRUE@ $(AM_V_at)$(am__mv) tests/lib/$(DEPDIR)/test_ringbuf-test_ringbuf.Tpo tests/lib/$(DEPDIR)/test_ringbuf-test_ringbuf.Po @AMDEP_TRUE@@am__fastdepCC_FALSE@ $(AM_V_CC)source='tests/lib/test_ringbuf.c' object='tests/lib/test_ringbuf-test_ringbuf.o' libtool=no @AMDEPBACKSLASH@ @AMDEP_TRUE@@am__fastdepCC_FALSE@ DEPDIR=$(DEPDIR) $(CCDEPMODE) $(depcomp) @AMDEPBACKSLASH@ @am__fastdepCC_FALSE@ $(AM_V_CC@am__nodep@)$(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(tests_lib_test_ringbuf_CPPFLAGS) $(CPPFLAGS) $(tests_lib_test_ringbuf_CFLAGS) $(CFLAGS) -c -o tests/lib/test_ringbuf-test_ringbuf.o `test -f 'tests/lib/test_ringbuf.c' || echo '$(srcdir)/'`tests/lib/test_ringbuf.c tests/lib/test_ringbuf-test_ringbuf.obj: tests/lib/test_ringbuf.c @am__fastdepCC_TRUE@ $(AM_V_CC)$(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(tests_lib_test_ringbuf_CPPFLAGS) $(CPPFLAGS) $(tests_lib_test_ringbuf_CFLAGS) $(CFLAGS) -MT tests/lib/test_ringbuf-test_ringbuf.obj -MD -MP -MF tests/lib/$(DEPDIR)/test_ringbuf-test_ringbuf.Tpo -c -o tests/lib/test_ringbuf-test_ringbuf.obj `if test -f 'tests/lib/test_ringbuf.c'; then $(CYGPATH_W) 'tests/lib/test_ringbuf.c'; else $(CYGPATH_W) '$(srcdir)/tests/lib/test_ringbuf.c'; fi` @am__fastdepCC_TRUE@ $(AM_V_at)$(am__mv) tests/lib/$(DEPDIR)/test_ringbuf-test_ringbuf.Tpo tests/lib/$(DEPDIR)/test_ringbuf-test_ringbuf.Po @AMDEP_TRUE@@am__fastdepCC_FALSE@ $(AM_V_CC)source='tests/lib/test_ringbuf.c' object='tests/lib/test_ringbuf-test_ringbuf.obj' libtool=no @AMDEPBACKSLASH@ @AMDEP_TRUE@@am__fastdepCC_FALSE@ DEPDIR=$(DEPDIR) $(CCDEPMODE) $(depcomp) @AMDEPBACKSLASH@ @am__fastdepCC_FALSE@ $(AM_V_CC@am__nodep@)$(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(tests_lib_test_ringbuf_CPPFLAGS) $(CPPFLAGS) $(tests_lib_test_ringbuf_CFLAGS) $(CFLAGS) -c -o tests/lib/test_ringbuf-test_ringbuf.obj `if test -f 'tests/lib/test_ringbuf.c'; then $(CYGPATH_W) 'tests/lib/test_ringbuf.c'; else $(CYGPATH_W) '$(srcdir)/tests/lib/test_ringbuf.c'; fi` tests/lib/test_segv-test_segv.o: tests/lib/test_segv.c @am__fastdepCC_TRUE@ $(AM_V_CC)$(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(tests_lib_test_segv_CPPFLAGS) $(CPPFLAGS) $(tests_lib_test_segv_CFLAGS) $(CFLAGS) -MT tests/lib/test_segv-test_segv.o -MD -MP -MF tests/lib/$(DEPDIR)/test_segv-test_segv.Tpo -c -o tests/lib/test_segv-test_segv.o `test -f 'tests/lib/test_segv.c' || echo '$(srcdir)/'`tests/lib/test_segv.c @am__fastdepCC_TRUE@ $(AM_V_at)$(am__mv) tests/lib/$(DEPDIR)/test_segv-test_segv.Tpo tests/lib/$(DEPDIR)/test_segv-test_segv.Po @AMDEP_TRUE@@am__fastdepCC_FALSE@ $(AM_V_CC)source='tests/lib/test_segv.c' object='tests/lib/test_segv-test_segv.o' libtool=no @AMDEPBACKSLASH@ @AMDEP_TRUE@@am__fastdepCC_FALSE@ DEPDIR=$(DEPDIR) $(CCDEPMODE) $(depcomp) @AMDEPBACKSLASH@ @am__fastdepCC_FALSE@ $(AM_V_CC@am__nodep@)$(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(tests_lib_test_segv_CPPFLAGS) $(CPPFLAGS) $(tests_lib_test_segv_CFLAGS) $(CFLAGS) -c -o tests/lib/test_segv-test_segv.o `test -f 'tests/lib/test_segv.c' || echo '$(srcdir)/'`tests/lib/test_segv.c tests/lib/test_segv-test_segv.obj: tests/lib/test_segv.c @am__fastdepCC_TRUE@ $(AM_V_CC)$(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(tests_lib_test_segv_CPPFLAGS) $(CPPFLAGS) $(tests_lib_test_segv_CFLAGS) $(CFLAGS) -MT tests/lib/test_segv-test_segv.obj -MD -MP -MF tests/lib/$(DEPDIR)/test_segv-test_segv.Tpo -c -o tests/lib/test_segv-test_segv.obj `if test -f 'tests/lib/test_segv.c'; then $(CYGPATH_W) 'tests/lib/test_segv.c'; else $(CYGPATH_W) '$(srcdir)/tests/lib/test_segv.c'; fi` @am__fastdepCC_TRUE@ $(AM_V_at)$(am__mv) tests/lib/$(DEPDIR)/test_segv-test_segv.Tpo tests/lib/$(DEPDIR)/test_segv-test_segv.Po @AMDEP_TRUE@@am__fastdepCC_FALSE@ $(AM_V_CC)source='tests/lib/test_segv.c' object='tests/lib/test_segv-test_segv.obj' libtool=no @AMDEPBACKSLASH@ @AMDEP_TRUE@@am__fastdepCC_FALSE@ DEPDIR=$(DEPDIR) $(CCDEPMODE) $(depcomp) @AMDEPBACKSLASH@ @am__fastdepCC_FALSE@ $(AM_V_CC@am__nodep@)$(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(tests_lib_test_segv_CPPFLAGS) $(CPPFLAGS) $(tests_lib_test_segv_CFLAGS) $(CFLAGS) -c -o tests/lib/test_segv-test_segv.obj `if test -f 'tests/lib/test_segv.c'; then $(CYGPATH_W) 'tests/lib/test_segv.c'; else $(CYGPATH_W) '$(srcdir)/tests/lib/test_segv.c'; fi` tests/lib/test_seqlock-test_seqlock.o: tests/lib/test_seqlock.c @am__fastdepCC_TRUE@ $(AM_V_CC)$(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(tests_lib_test_seqlock_CPPFLAGS) $(CPPFLAGS) $(tests_lib_test_seqlock_CFLAGS) $(CFLAGS) -MT tests/lib/test_seqlock-test_seqlock.o -MD -MP -MF tests/lib/$(DEPDIR)/test_seqlock-test_seqlock.Tpo -c -o tests/lib/test_seqlock-test_seqlock.o `test -f 'tests/lib/test_seqlock.c' || echo '$(srcdir)/'`tests/lib/test_seqlock.c @am__fastdepCC_TRUE@ $(AM_V_at)$(am__mv) tests/lib/$(DEPDIR)/test_seqlock-test_seqlock.Tpo tests/lib/$(DEPDIR)/test_seqlock-test_seqlock.Po @AMDEP_TRUE@@am__fastdepCC_FALSE@ $(AM_V_CC)source='tests/lib/test_seqlock.c' object='tests/lib/test_seqlock-test_seqlock.o' libtool=no @AMDEPBACKSLASH@ @AMDEP_TRUE@@am__fastdepCC_FALSE@ DEPDIR=$(DEPDIR) $(CCDEPMODE) $(depcomp) @AMDEPBACKSLASH@ @am__fastdepCC_FALSE@ $(AM_V_CC@am__nodep@)$(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(tests_lib_test_seqlock_CPPFLAGS) $(CPPFLAGS) $(tests_lib_test_seqlock_CFLAGS) $(CFLAGS) -c -o tests/lib/test_seqlock-test_seqlock.o `test -f 'tests/lib/test_seqlock.c' || echo '$(srcdir)/'`tests/lib/test_seqlock.c tests/lib/test_seqlock-test_seqlock.obj: tests/lib/test_seqlock.c @am__fastdepCC_TRUE@ $(AM_V_CC)$(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(tests_lib_test_seqlock_CPPFLAGS) $(CPPFLAGS) $(tests_lib_test_seqlock_CFLAGS) $(CFLAGS) -MT tests/lib/test_seqlock-test_seqlock.obj -MD -MP -MF tests/lib/$(DEPDIR)/test_seqlock-test_seqlock.Tpo -c -o tests/lib/test_seqlock-test_seqlock.obj `if test -f 'tests/lib/test_seqlock.c'; then $(CYGPATH_W) 'tests/lib/test_seqlock.c'; else $(CYGPATH_W) '$(srcdir)/tests/lib/test_seqlock.c'; fi` @am__fastdepCC_TRUE@ $(AM_V_at)$(am__mv) tests/lib/$(DEPDIR)/test_seqlock-test_seqlock.Tpo tests/lib/$(DEPDIR)/test_seqlock-test_seqlock.Po @AMDEP_TRUE@@am__fastdepCC_FALSE@ $(AM_V_CC)source='tests/lib/test_seqlock.c' object='tests/lib/test_seqlock-test_seqlock.obj' libtool=no @AMDEPBACKSLASH@ @AMDEP_TRUE@@am__fastdepCC_FALSE@ DEPDIR=$(DEPDIR) $(CCDEPMODE) $(depcomp) @AMDEPBACKSLASH@ @am__fastdepCC_FALSE@ $(AM_V_CC@am__nodep@)$(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(tests_lib_test_seqlock_CPPFLAGS) $(CPPFLAGS) $(tests_lib_test_seqlock_CFLAGS) $(CFLAGS) -c -o tests/lib/test_seqlock-test_seqlock.obj `if test -f 'tests/lib/test_seqlock.c'; then $(CYGPATH_W) 'tests/lib/test_seqlock.c'; else $(CYGPATH_W) '$(srcdir)/tests/lib/test_seqlock.c'; fi` tests/lib/test_sig-test_sig.o: tests/lib/test_sig.c @am__fastdepCC_TRUE@ $(AM_V_CC)$(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(tests_lib_test_sig_CPPFLAGS) $(CPPFLAGS) $(tests_lib_test_sig_CFLAGS) $(CFLAGS) -MT tests/lib/test_sig-test_sig.o -MD -MP -MF tests/lib/$(DEPDIR)/test_sig-test_sig.Tpo -c -o tests/lib/test_sig-test_sig.o `test -f 'tests/lib/test_sig.c' || echo '$(srcdir)/'`tests/lib/test_sig.c @am__fastdepCC_TRUE@ $(AM_V_at)$(am__mv) tests/lib/$(DEPDIR)/test_sig-test_sig.Tpo tests/lib/$(DEPDIR)/test_sig-test_sig.Po @AMDEP_TRUE@@am__fastdepCC_FALSE@ $(AM_V_CC)source='tests/lib/test_sig.c' object='tests/lib/test_sig-test_sig.o' libtool=no @AMDEPBACKSLASH@ @AMDEP_TRUE@@am__fastdepCC_FALSE@ DEPDIR=$(DEPDIR) $(CCDEPMODE) $(depcomp) @AMDEPBACKSLASH@ @am__fastdepCC_FALSE@ $(AM_V_CC@am__nodep@)$(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(tests_lib_test_sig_CPPFLAGS) $(CPPFLAGS) $(tests_lib_test_sig_CFLAGS) $(CFLAGS) -c -o tests/lib/test_sig-test_sig.o `test -f 'tests/lib/test_sig.c' || echo '$(srcdir)/'`tests/lib/test_sig.c tests/lib/test_sig-test_sig.obj: tests/lib/test_sig.c @am__fastdepCC_TRUE@ $(AM_V_CC)$(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(tests_lib_test_sig_CPPFLAGS) $(CPPFLAGS) $(tests_lib_test_sig_CFLAGS) $(CFLAGS) -MT tests/lib/test_sig-test_sig.obj -MD -MP -MF tests/lib/$(DEPDIR)/test_sig-test_sig.Tpo -c -o tests/lib/test_sig-test_sig.obj `if test -f 'tests/lib/test_sig.c'; then $(CYGPATH_W) 'tests/lib/test_sig.c'; else $(CYGPATH_W) '$(srcdir)/tests/lib/test_sig.c'; fi` @am__fastdepCC_TRUE@ $(AM_V_at)$(am__mv) tests/lib/$(DEPDIR)/test_sig-test_sig.Tpo tests/lib/$(DEPDIR)/test_sig-test_sig.Po @AMDEP_TRUE@@am__fastdepCC_FALSE@ $(AM_V_CC)source='tests/lib/test_sig.c' object='tests/lib/test_sig-test_sig.obj' libtool=no @AMDEPBACKSLASH@ @AMDEP_TRUE@@am__fastdepCC_FALSE@ DEPDIR=$(DEPDIR) $(CCDEPMODE) $(depcomp) @AMDEPBACKSLASH@ @am__fastdepCC_FALSE@ $(AM_V_CC@am__nodep@)$(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(tests_lib_test_sig_CPPFLAGS) $(CPPFLAGS) $(tests_lib_test_sig_CFLAGS) $(CFLAGS) -c -o tests/lib/test_sig-test_sig.obj `if test -f 'tests/lib/test_sig.c'; then $(CYGPATH_W) 'tests/lib/test_sig.c'; else $(CYGPATH_W) '$(srcdir)/tests/lib/test_sig.c'; fi` tests/lib/test_srcdest_table-test_srcdest_table.o: tests/lib/test_srcdest_table.c @am__fastdepCC_TRUE@ $(AM_V_CC)$(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(tests_lib_test_srcdest_table_CPPFLAGS) $(CPPFLAGS) $(tests_lib_test_srcdest_table_CFLAGS) $(CFLAGS) -MT tests/lib/test_srcdest_table-test_srcdest_table.o -MD -MP -MF tests/lib/$(DEPDIR)/test_srcdest_table-test_srcdest_table.Tpo -c -o tests/lib/test_srcdest_table-test_srcdest_table.o `test -f 'tests/lib/test_srcdest_table.c' || echo '$(srcdir)/'`tests/lib/test_srcdest_table.c @am__fastdepCC_TRUE@ $(AM_V_at)$(am__mv) tests/lib/$(DEPDIR)/test_srcdest_table-test_srcdest_table.Tpo tests/lib/$(DEPDIR)/test_srcdest_table-test_srcdest_table.Po @AMDEP_TRUE@@am__fastdepCC_FALSE@ $(AM_V_CC)source='tests/lib/test_srcdest_table.c' object='tests/lib/test_srcdest_table-test_srcdest_table.o' libtool=no @AMDEPBACKSLASH@ @AMDEP_TRUE@@am__fastdepCC_FALSE@ DEPDIR=$(DEPDIR) $(CCDEPMODE) $(depcomp) @AMDEPBACKSLASH@ @am__fastdepCC_FALSE@ $(AM_V_CC@am__nodep@)$(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(tests_lib_test_srcdest_table_CPPFLAGS) $(CPPFLAGS) $(tests_lib_test_srcdest_table_CFLAGS) $(CFLAGS) -c -o tests/lib/test_srcdest_table-test_srcdest_table.o `test -f 'tests/lib/test_srcdest_table.c' || echo '$(srcdir)/'`tests/lib/test_srcdest_table.c tests/lib/test_srcdest_table-test_srcdest_table.obj: tests/lib/test_srcdest_table.c @am__fastdepCC_TRUE@ $(AM_V_CC)$(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(tests_lib_test_srcdest_table_CPPFLAGS) $(CPPFLAGS) $(tests_lib_test_srcdest_table_CFLAGS) $(CFLAGS) -MT tests/lib/test_srcdest_table-test_srcdest_table.obj -MD -MP -MF tests/lib/$(DEPDIR)/test_srcdest_table-test_srcdest_table.Tpo -c -o tests/lib/test_srcdest_table-test_srcdest_table.obj `if test -f 'tests/lib/test_srcdest_table.c'; then $(CYGPATH_W) 'tests/lib/test_srcdest_table.c'; else $(CYGPATH_W) '$(srcdir)/tests/lib/test_srcdest_table.c'; fi` @am__fastdepCC_TRUE@ $(AM_V_at)$(am__mv) tests/lib/$(DEPDIR)/test_srcdest_table-test_srcdest_table.Tpo tests/lib/$(DEPDIR)/test_srcdest_table-test_srcdest_table.Po @AMDEP_TRUE@@am__fastdepCC_FALSE@ $(AM_V_CC)source='tests/lib/test_srcdest_table.c' object='tests/lib/test_srcdest_table-test_srcdest_table.obj' libtool=no @AMDEPBACKSLASH@ @AMDEP_TRUE@@am__fastdepCC_FALSE@ DEPDIR=$(DEPDIR) $(CCDEPMODE) $(depcomp) @AMDEPBACKSLASH@ @am__fastdepCC_FALSE@ $(AM_V_CC@am__nodep@)$(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(tests_lib_test_srcdest_table_CPPFLAGS) $(CPPFLAGS) $(tests_lib_test_srcdest_table_CFLAGS) $(CFLAGS) -c -o tests/lib/test_srcdest_table-test_srcdest_table.obj `if test -f 'tests/lib/test_srcdest_table.c'; then $(CYGPATH_W) 'tests/lib/test_srcdest_table.c'; else $(CYGPATH_W) '$(srcdir)/tests/lib/test_srcdest_table.c'; fi` tests/helpers/c/lib_test_srcdest_table-prng.o: tests/helpers/c/prng.c @am__fastdepCC_TRUE@ $(AM_V_CC)$(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(tests_lib_test_srcdest_table_CPPFLAGS) $(CPPFLAGS) $(tests_lib_test_srcdest_table_CFLAGS) $(CFLAGS) -MT tests/helpers/c/lib_test_srcdest_table-prng.o -MD -MP -MF tests/helpers/c/$(DEPDIR)/lib_test_srcdest_table-prng.Tpo -c -o tests/helpers/c/lib_test_srcdest_table-prng.o `test -f 'tests/helpers/c/prng.c' || echo '$(srcdir)/'`tests/helpers/c/prng.c @am__fastdepCC_TRUE@ $(AM_V_at)$(am__mv) tests/helpers/c/$(DEPDIR)/lib_test_srcdest_table-prng.Tpo tests/helpers/c/$(DEPDIR)/lib_test_srcdest_table-prng.Po @AMDEP_TRUE@@am__fastdepCC_FALSE@ $(AM_V_CC)source='tests/helpers/c/prng.c' object='tests/helpers/c/lib_test_srcdest_table-prng.o' libtool=no @AMDEPBACKSLASH@ @AMDEP_TRUE@@am__fastdepCC_FALSE@ DEPDIR=$(DEPDIR) $(CCDEPMODE) $(depcomp) @AMDEPBACKSLASH@ @am__fastdepCC_FALSE@ $(AM_V_CC@am__nodep@)$(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(tests_lib_test_srcdest_table_CPPFLAGS) $(CPPFLAGS) $(tests_lib_test_srcdest_table_CFLAGS) $(CFLAGS) -c -o tests/helpers/c/lib_test_srcdest_table-prng.o `test -f 'tests/helpers/c/prng.c' || echo '$(srcdir)/'`tests/helpers/c/prng.c tests/helpers/c/lib_test_srcdest_table-prng.obj: tests/helpers/c/prng.c @am__fastdepCC_TRUE@ $(AM_V_CC)$(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(tests_lib_test_srcdest_table_CPPFLAGS) $(CPPFLAGS) $(tests_lib_test_srcdest_table_CFLAGS) $(CFLAGS) -MT tests/helpers/c/lib_test_srcdest_table-prng.obj -MD -MP -MF tests/helpers/c/$(DEPDIR)/lib_test_srcdest_table-prng.Tpo -c -o tests/helpers/c/lib_test_srcdest_table-prng.obj `if test -f 'tests/helpers/c/prng.c'; then $(CYGPATH_W) 'tests/helpers/c/prng.c'; else $(CYGPATH_W) '$(srcdir)/tests/helpers/c/prng.c'; fi` @am__fastdepCC_TRUE@ $(AM_V_at)$(am__mv) tests/helpers/c/$(DEPDIR)/lib_test_srcdest_table-prng.Tpo tests/helpers/c/$(DEPDIR)/lib_test_srcdest_table-prng.Po @AMDEP_TRUE@@am__fastdepCC_FALSE@ $(AM_V_CC)source='tests/helpers/c/prng.c' object='tests/helpers/c/lib_test_srcdest_table-prng.obj' libtool=no @AMDEPBACKSLASH@ @AMDEP_TRUE@@am__fastdepCC_FALSE@ DEPDIR=$(DEPDIR) $(CCDEPMODE) $(depcomp) @AMDEPBACKSLASH@ @am__fastdepCC_FALSE@ $(AM_V_CC@am__nodep@)$(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(tests_lib_test_srcdest_table_CPPFLAGS) $(CPPFLAGS) $(tests_lib_test_srcdest_table_CFLAGS) $(CFLAGS) -c -o tests/helpers/c/lib_test_srcdest_table-prng.obj `if test -f 'tests/helpers/c/prng.c'; then $(CYGPATH_W) 'tests/helpers/c/prng.c'; else $(CYGPATH_W) '$(srcdir)/tests/helpers/c/prng.c'; fi` tests/lib/test_stream-test_stream.o: tests/lib/test_stream.c @am__fastdepCC_TRUE@ $(AM_V_CC)$(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(tests_lib_test_stream_CPPFLAGS) $(CPPFLAGS) $(tests_lib_test_stream_CFLAGS) $(CFLAGS) -MT tests/lib/test_stream-test_stream.o -MD -MP -MF tests/lib/$(DEPDIR)/test_stream-test_stream.Tpo -c -o tests/lib/test_stream-test_stream.o `test -f 'tests/lib/test_stream.c' || echo '$(srcdir)/'`tests/lib/test_stream.c @am__fastdepCC_TRUE@ $(AM_V_at)$(am__mv) tests/lib/$(DEPDIR)/test_stream-test_stream.Tpo tests/lib/$(DEPDIR)/test_stream-test_stream.Po @AMDEP_TRUE@@am__fastdepCC_FALSE@ $(AM_V_CC)source='tests/lib/test_stream.c' object='tests/lib/test_stream-test_stream.o' libtool=no @AMDEPBACKSLASH@ @AMDEP_TRUE@@am__fastdepCC_FALSE@ DEPDIR=$(DEPDIR) $(CCDEPMODE) $(depcomp) @AMDEPBACKSLASH@ @am__fastdepCC_FALSE@ $(AM_V_CC@am__nodep@)$(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(tests_lib_test_stream_CPPFLAGS) $(CPPFLAGS) $(tests_lib_test_stream_CFLAGS) $(CFLAGS) -c -o tests/lib/test_stream-test_stream.o `test -f 'tests/lib/test_stream.c' || echo '$(srcdir)/'`tests/lib/test_stream.c tests/lib/test_stream-test_stream.obj: tests/lib/test_stream.c @am__fastdepCC_TRUE@ $(AM_V_CC)$(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(tests_lib_test_stream_CPPFLAGS) $(CPPFLAGS) $(tests_lib_test_stream_CFLAGS) $(CFLAGS) -MT tests/lib/test_stream-test_stream.obj -MD -MP -MF tests/lib/$(DEPDIR)/test_stream-test_stream.Tpo -c -o tests/lib/test_stream-test_stream.obj `if test -f 'tests/lib/test_stream.c'; then $(CYGPATH_W) 'tests/lib/test_stream.c'; else $(CYGPATH_W) '$(srcdir)/tests/lib/test_stream.c'; fi` @am__fastdepCC_TRUE@ $(AM_V_at)$(am__mv) tests/lib/$(DEPDIR)/test_stream-test_stream.Tpo tests/lib/$(DEPDIR)/test_stream-test_stream.Po @AMDEP_TRUE@@am__fastdepCC_FALSE@ $(AM_V_CC)source='tests/lib/test_stream.c' object='tests/lib/test_stream-test_stream.obj' libtool=no @AMDEPBACKSLASH@ @AMDEP_TRUE@@am__fastdepCC_FALSE@ DEPDIR=$(DEPDIR) $(CCDEPMODE) $(depcomp) @AMDEPBACKSLASH@ @am__fastdepCC_FALSE@ $(AM_V_CC@am__nodep@)$(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(tests_lib_test_stream_CPPFLAGS) $(CPPFLAGS) $(tests_lib_test_stream_CFLAGS) $(CFLAGS) -c -o tests/lib/test_stream-test_stream.obj `if test -f 'tests/lib/test_stream.c'; then $(CYGPATH_W) 'tests/lib/test_stream.c'; else $(CYGPATH_W) '$(srcdir)/tests/lib/test_stream.c'; fi` tests/lib/test_table-test_table.o: tests/lib/test_table.c @am__fastdepCC_TRUE@ $(AM_V_CC)$(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(tests_lib_test_table_CPPFLAGS) $(CPPFLAGS) $(tests_lib_test_table_CFLAGS) $(CFLAGS) -MT tests/lib/test_table-test_table.o -MD -MP -MF tests/lib/$(DEPDIR)/test_table-test_table.Tpo -c -o tests/lib/test_table-test_table.o `test -f 'tests/lib/test_table.c' || echo '$(srcdir)/'`tests/lib/test_table.c @am__fastdepCC_TRUE@ $(AM_V_at)$(am__mv) tests/lib/$(DEPDIR)/test_table-test_table.Tpo tests/lib/$(DEPDIR)/test_table-test_table.Po @AMDEP_TRUE@@am__fastdepCC_FALSE@ $(AM_V_CC)source='tests/lib/test_table.c' object='tests/lib/test_table-test_table.o' libtool=no @AMDEPBACKSLASH@ @AMDEP_TRUE@@am__fastdepCC_FALSE@ DEPDIR=$(DEPDIR) $(CCDEPMODE) $(depcomp) @AMDEPBACKSLASH@ @am__fastdepCC_FALSE@ $(AM_V_CC@am__nodep@)$(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(tests_lib_test_table_CPPFLAGS) $(CPPFLAGS) $(tests_lib_test_table_CFLAGS) $(CFLAGS) -c -o tests/lib/test_table-test_table.o `test -f 'tests/lib/test_table.c' || echo '$(srcdir)/'`tests/lib/test_table.c tests/lib/test_table-test_table.obj: tests/lib/test_table.c @am__fastdepCC_TRUE@ $(AM_V_CC)$(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(tests_lib_test_table_CPPFLAGS) $(CPPFLAGS) $(tests_lib_test_table_CFLAGS) $(CFLAGS) -MT tests/lib/test_table-test_table.obj -MD -MP -MF tests/lib/$(DEPDIR)/test_table-test_table.Tpo -c -o tests/lib/test_table-test_table.obj `if test -f 'tests/lib/test_table.c'; then $(CYGPATH_W) 'tests/lib/test_table.c'; else $(CYGPATH_W) '$(srcdir)/tests/lib/test_table.c'; fi` @am__fastdepCC_TRUE@ $(AM_V_at)$(am__mv) tests/lib/$(DEPDIR)/test_table-test_table.Tpo tests/lib/$(DEPDIR)/test_table-test_table.Po @AMDEP_TRUE@@am__fastdepCC_FALSE@ $(AM_V_CC)source='tests/lib/test_table.c' object='tests/lib/test_table-test_table.obj' libtool=no @AMDEPBACKSLASH@ @AMDEP_TRUE@@am__fastdepCC_FALSE@ DEPDIR=$(DEPDIR) $(CCDEPMODE) $(depcomp) @AMDEPBACKSLASH@ @am__fastdepCC_FALSE@ $(AM_V_CC@am__nodep@)$(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(tests_lib_test_table_CPPFLAGS) $(CPPFLAGS) $(tests_lib_test_table_CFLAGS) $(CFLAGS) -c -o tests/lib/test_table-test_table.obj `if test -f 'tests/lib/test_table.c'; then $(CYGPATH_W) 'tests/lib/test_table.c'; else $(CYGPATH_W) '$(srcdir)/tests/lib/test_table.c'; fi` tests/lib/test_timer_correctness-test_timer_correctness.o: tests/lib/test_timer_correctness.c @am__fastdepCC_TRUE@ $(AM_V_CC)$(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(tests_lib_test_timer_correctness_CPPFLAGS) $(CPPFLAGS) $(tests_lib_test_timer_correctness_CFLAGS) $(CFLAGS) -MT tests/lib/test_timer_correctness-test_timer_correctness.o -MD -MP -MF tests/lib/$(DEPDIR)/test_timer_correctness-test_timer_correctness.Tpo -c -o tests/lib/test_timer_correctness-test_timer_correctness.o `test -f 'tests/lib/test_timer_correctness.c' || echo '$(srcdir)/'`tests/lib/test_timer_correctness.c @am__fastdepCC_TRUE@ $(AM_V_at)$(am__mv) tests/lib/$(DEPDIR)/test_timer_correctness-test_timer_correctness.Tpo tests/lib/$(DEPDIR)/test_timer_correctness-test_timer_correctness.Po @AMDEP_TRUE@@am__fastdepCC_FALSE@ $(AM_V_CC)source='tests/lib/test_timer_correctness.c' object='tests/lib/test_timer_correctness-test_timer_correctness.o' libtool=no @AMDEPBACKSLASH@ @AMDEP_TRUE@@am__fastdepCC_FALSE@ DEPDIR=$(DEPDIR) $(CCDEPMODE) $(depcomp) @AMDEPBACKSLASH@ @am__fastdepCC_FALSE@ $(AM_V_CC@am__nodep@)$(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(tests_lib_test_timer_correctness_CPPFLAGS) $(CPPFLAGS) $(tests_lib_test_timer_correctness_CFLAGS) $(CFLAGS) -c -o tests/lib/test_timer_correctness-test_timer_correctness.o `test -f 'tests/lib/test_timer_correctness.c' || echo '$(srcdir)/'`tests/lib/test_timer_correctness.c tests/lib/test_timer_correctness-test_timer_correctness.obj: tests/lib/test_timer_correctness.c @am__fastdepCC_TRUE@ $(AM_V_CC)$(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(tests_lib_test_timer_correctness_CPPFLAGS) $(CPPFLAGS) $(tests_lib_test_timer_correctness_CFLAGS) $(CFLAGS) -MT tests/lib/test_timer_correctness-test_timer_correctness.obj -MD -MP -MF tests/lib/$(DEPDIR)/test_timer_correctness-test_timer_correctness.Tpo -c -o tests/lib/test_timer_correctness-test_timer_correctness.obj `if test -f 'tests/lib/test_timer_correctness.c'; then $(CYGPATH_W) 'tests/lib/test_timer_correctness.c'; else $(CYGPATH_W) '$(srcdir)/tests/lib/test_timer_correctness.c'; fi` @am__fastdepCC_TRUE@ $(AM_V_at)$(am__mv) tests/lib/$(DEPDIR)/test_timer_correctness-test_timer_correctness.Tpo tests/lib/$(DEPDIR)/test_timer_correctness-test_timer_correctness.Po @AMDEP_TRUE@@am__fastdepCC_FALSE@ $(AM_V_CC)source='tests/lib/test_timer_correctness.c' object='tests/lib/test_timer_correctness-test_timer_correctness.obj' libtool=no @AMDEPBACKSLASH@ @AMDEP_TRUE@@am__fastdepCC_FALSE@ DEPDIR=$(DEPDIR) $(CCDEPMODE) $(depcomp) @AMDEPBACKSLASH@ @am__fastdepCC_FALSE@ $(AM_V_CC@am__nodep@)$(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(tests_lib_test_timer_correctness_CPPFLAGS) $(CPPFLAGS) $(tests_lib_test_timer_correctness_CFLAGS) $(CFLAGS) -c -o tests/lib/test_timer_correctness-test_timer_correctness.obj `if test -f 'tests/lib/test_timer_correctness.c'; then $(CYGPATH_W) 'tests/lib/test_timer_correctness.c'; else $(CYGPATH_W) '$(srcdir)/tests/lib/test_timer_correctness.c'; fi` tests/helpers/c/lib_test_timer_correctness-prng.o: tests/helpers/c/prng.c @am__fastdepCC_TRUE@ $(AM_V_CC)$(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(tests_lib_test_timer_correctness_CPPFLAGS) $(CPPFLAGS) $(tests_lib_test_timer_correctness_CFLAGS) $(CFLAGS) -MT tests/helpers/c/lib_test_timer_correctness-prng.o -MD -MP -MF tests/helpers/c/$(DEPDIR)/lib_test_timer_correctness-prng.Tpo -c -o tests/helpers/c/lib_test_timer_correctness-prng.o `test -f 'tests/helpers/c/prng.c' || echo '$(srcdir)/'`tests/helpers/c/prng.c @am__fastdepCC_TRUE@ $(AM_V_at)$(am__mv) tests/helpers/c/$(DEPDIR)/lib_test_timer_correctness-prng.Tpo tests/helpers/c/$(DEPDIR)/lib_test_timer_correctness-prng.Po @AMDEP_TRUE@@am__fastdepCC_FALSE@ $(AM_V_CC)source='tests/helpers/c/prng.c' object='tests/helpers/c/lib_test_timer_correctness-prng.o' libtool=no @AMDEPBACKSLASH@ @AMDEP_TRUE@@am__fastdepCC_FALSE@ DEPDIR=$(DEPDIR) $(CCDEPMODE) $(depcomp) @AMDEPBACKSLASH@ @am__fastdepCC_FALSE@ $(AM_V_CC@am__nodep@)$(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(tests_lib_test_timer_correctness_CPPFLAGS) $(CPPFLAGS) $(tests_lib_test_timer_correctness_CFLAGS) $(CFLAGS) -c -o tests/helpers/c/lib_test_timer_correctness-prng.o `test -f 'tests/helpers/c/prng.c' || echo '$(srcdir)/'`tests/helpers/c/prng.c tests/helpers/c/lib_test_timer_correctness-prng.obj: tests/helpers/c/prng.c @am__fastdepCC_TRUE@ $(AM_V_CC)$(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(tests_lib_test_timer_correctness_CPPFLAGS) $(CPPFLAGS) $(tests_lib_test_timer_correctness_CFLAGS) $(CFLAGS) -MT tests/helpers/c/lib_test_timer_correctness-prng.obj -MD -MP -MF tests/helpers/c/$(DEPDIR)/lib_test_timer_correctness-prng.Tpo -c -o tests/helpers/c/lib_test_timer_correctness-prng.obj `if test -f 'tests/helpers/c/prng.c'; then $(CYGPATH_W) 'tests/helpers/c/prng.c'; else $(CYGPATH_W) '$(srcdir)/tests/helpers/c/prng.c'; fi` @am__fastdepCC_TRUE@ $(AM_V_at)$(am__mv) tests/helpers/c/$(DEPDIR)/lib_test_timer_correctness-prng.Tpo tests/helpers/c/$(DEPDIR)/lib_test_timer_correctness-prng.Po @AMDEP_TRUE@@am__fastdepCC_FALSE@ $(AM_V_CC)source='tests/helpers/c/prng.c' object='tests/helpers/c/lib_test_timer_correctness-prng.obj' libtool=no @AMDEPBACKSLASH@ @AMDEP_TRUE@@am__fastdepCC_FALSE@ DEPDIR=$(DEPDIR) $(CCDEPMODE) $(depcomp) @AMDEPBACKSLASH@ @am__fastdepCC_FALSE@ $(AM_V_CC@am__nodep@)$(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(tests_lib_test_timer_correctness_CPPFLAGS) $(CPPFLAGS) $(tests_lib_test_timer_correctness_CFLAGS) $(CFLAGS) -c -o tests/helpers/c/lib_test_timer_correctness-prng.obj `if test -f 'tests/helpers/c/prng.c'; then $(CYGPATH_W) 'tests/helpers/c/prng.c'; else $(CYGPATH_W) '$(srcdir)/tests/helpers/c/prng.c'; fi` tests/lib/test_timer_performance-test_timer_performance.o: tests/lib/test_timer_performance.c @am__fastdepCC_TRUE@ $(AM_V_CC)$(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(tests_lib_test_timer_performance_CPPFLAGS) $(CPPFLAGS) $(tests_lib_test_timer_performance_CFLAGS) $(CFLAGS) -MT tests/lib/test_timer_performance-test_timer_performance.o -MD -MP -MF tests/lib/$(DEPDIR)/test_timer_performance-test_timer_performance.Tpo -c -o tests/lib/test_timer_performance-test_timer_performance.o `test -f 'tests/lib/test_timer_performance.c' || echo '$(srcdir)/'`tests/lib/test_timer_performance.c @am__fastdepCC_TRUE@ $(AM_V_at)$(am__mv) tests/lib/$(DEPDIR)/test_timer_performance-test_timer_performance.Tpo tests/lib/$(DEPDIR)/test_timer_performance-test_timer_performance.Po @AMDEP_TRUE@@am__fastdepCC_FALSE@ $(AM_V_CC)source='tests/lib/test_timer_performance.c' object='tests/lib/test_timer_performance-test_timer_performance.o' libtool=no @AMDEPBACKSLASH@ @AMDEP_TRUE@@am__fastdepCC_FALSE@ DEPDIR=$(DEPDIR) $(CCDEPMODE) $(depcomp) @AMDEPBACKSLASH@ @am__fastdepCC_FALSE@ $(AM_V_CC@am__nodep@)$(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(tests_lib_test_timer_performance_CPPFLAGS) $(CPPFLAGS) $(tests_lib_test_timer_performance_CFLAGS) $(CFLAGS) -c -o tests/lib/test_timer_performance-test_timer_performance.o `test -f 'tests/lib/test_timer_performance.c' || echo '$(srcdir)/'`tests/lib/test_timer_performance.c tests/lib/test_timer_performance-test_timer_performance.obj: tests/lib/test_timer_performance.c @am__fastdepCC_TRUE@ $(AM_V_CC)$(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(tests_lib_test_timer_performance_CPPFLAGS) $(CPPFLAGS) $(tests_lib_test_timer_performance_CFLAGS) $(CFLAGS) -MT tests/lib/test_timer_performance-test_timer_performance.obj -MD -MP -MF tests/lib/$(DEPDIR)/test_timer_performance-test_timer_performance.Tpo -c -o tests/lib/test_timer_performance-test_timer_performance.obj `if test -f 'tests/lib/test_timer_performance.c'; then $(CYGPATH_W) 'tests/lib/test_timer_performance.c'; else $(CYGPATH_W) '$(srcdir)/tests/lib/test_timer_performance.c'; fi` @am__fastdepCC_TRUE@ $(AM_V_at)$(am__mv) tests/lib/$(DEPDIR)/test_timer_performance-test_timer_performance.Tpo tests/lib/$(DEPDIR)/test_timer_performance-test_timer_performance.Po @AMDEP_TRUE@@am__fastdepCC_FALSE@ $(AM_V_CC)source='tests/lib/test_timer_performance.c' object='tests/lib/test_timer_performance-test_timer_performance.obj' libtool=no @AMDEPBACKSLASH@ @AMDEP_TRUE@@am__fastdepCC_FALSE@ DEPDIR=$(DEPDIR) $(CCDEPMODE) $(depcomp) @AMDEPBACKSLASH@ @am__fastdepCC_FALSE@ $(AM_V_CC@am__nodep@)$(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(tests_lib_test_timer_performance_CPPFLAGS) $(CPPFLAGS) $(tests_lib_test_timer_performance_CFLAGS) $(CFLAGS) -c -o tests/lib/test_timer_performance-test_timer_performance.obj `if test -f 'tests/lib/test_timer_performance.c'; then $(CYGPATH_W) 'tests/lib/test_timer_performance.c'; else $(CYGPATH_W) '$(srcdir)/tests/lib/test_timer_performance.c'; fi` tests/helpers/c/lib_test_timer_performance-prng.o: tests/helpers/c/prng.c @am__fastdepCC_TRUE@ $(AM_V_CC)$(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(tests_lib_test_timer_performance_CPPFLAGS) $(CPPFLAGS) $(tests_lib_test_timer_performance_CFLAGS) $(CFLAGS) -MT tests/helpers/c/lib_test_timer_performance-prng.o -MD -MP -MF tests/helpers/c/$(DEPDIR)/lib_test_timer_performance-prng.Tpo -c -o tests/helpers/c/lib_test_timer_performance-prng.o `test -f 'tests/helpers/c/prng.c' || echo '$(srcdir)/'`tests/helpers/c/prng.c @am__fastdepCC_TRUE@ $(AM_V_at)$(am__mv) tests/helpers/c/$(DEPDIR)/lib_test_timer_performance-prng.Tpo tests/helpers/c/$(DEPDIR)/lib_test_timer_performance-prng.Po @AMDEP_TRUE@@am__fastdepCC_FALSE@ $(AM_V_CC)source='tests/helpers/c/prng.c' object='tests/helpers/c/lib_test_timer_performance-prng.o' libtool=no @AMDEPBACKSLASH@ @AMDEP_TRUE@@am__fastdepCC_FALSE@ DEPDIR=$(DEPDIR) $(CCDEPMODE) $(depcomp) @AMDEPBACKSLASH@ @am__fastdepCC_FALSE@ $(AM_V_CC@am__nodep@)$(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(tests_lib_test_timer_performance_CPPFLAGS) $(CPPFLAGS) $(tests_lib_test_timer_performance_CFLAGS) $(CFLAGS) -c -o tests/helpers/c/lib_test_timer_performance-prng.o `test -f 'tests/helpers/c/prng.c' || echo '$(srcdir)/'`tests/helpers/c/prng.c tests/helpers/c/lib_test_timer_performance-prng.obj: tests/helpers/c/prng.c @am__fastdepCC_TRUE@ $(AM_V_CC)$(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(tests_lib_test_timer_performance_CPPFLAGS) $(CPPFLAGS) $(tests_lib_test_timer_performance_CFLAGS) $(CFLAGS) -MT tests/helpers/c/lib_test_timer_performance-prng.obj -MD -MP -MF tests/helpers/c/$(DEPDIR)/lib_test_timer_performance-prng.Tpo -c -o tests/helpers/c/lib_test_timer_performance-prng.obj `if test -f 'tests/helpers/c/prng.c'; then $(CYGPATH_W) 'tests/helpers/c/prng.c'; else $(CYGPATH_W) '$(srcdir)/tests/helpers/c/prng.c'; fi` @am__fastdepCC_TRUE@ $(AM_V_at)$(am__mv) tests/helpers/c/$(DEPDIR)/lib_test_timer_performance-prng.Tpo tests/helpers/c/$(DEPDIR)/lib_test_timer_performance-prng.Po @AMDEP_TRUE@@am__fastdepCC_FALSE@ $(AM_V_CC)source='tests/helpers/c/prng.c' object='tests/helpers/c/lib_test_timer_performance-prng.obj' libtool=no @AMDEPBACKSLASH@ @AMDEP_TRUE@@am__fastdepCC_FALSE@ DEPDIR=$(DEPDIR) $(CCDEPMODE) $(depcomp) @AMDEPBACKSLASH@ @am__fastdepCC_FALSE@ $(AM_V_CC@am__nodep@)$(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(tests_lib_test_timer_performance_CPPFLAGS) $(CPPFLAGS) $(tests_lib_test_timer_performance_CFLAGS) $(CFLAGS) -c -o tests/helpers/c/lib_test_timer_performance-prng.obj `if test -f 'tests/helpers/c/prng.c'; then $(CYGPATH_W) 'tests/helpers/c/prng.c'; else $(CYGPATH_W) '$(srcdir)/tests/helpers/c/prng.c'; fi` tests/lib/test_ttable-test_ttable.o: tests/lib/test_ttable.c @am__fastdepCC_TRUE@ $(AM_V_CC)$(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(tests_lib_test_ttable_CPPFLAGS) $(CPPFLAGS) $(tests_lib_test_ttable_CFLAGS) $(CFLAGS) -MT tests/lib/test_ttable-test_ttable.o -MD -MP -MF tests/lib/$(DEPDIR)/test_ttable-test_ttable.Tpo -c -o tests/lib/test_ttable-test_ttable.o `test -f 'tests/lib/test_ttable.c' || echo '$(srcdir)/'`tests/lib/test_ttable.c @am__fastdepCC_TRUE@ $(AM_V_at)$(am__mv) tests/lib/$(DEPDIR)/test_ttable-test_ttable.Tpo tests/lib/$(DEPDIR)/test_ttable-test_ttable.Po @AMDEP_TRUE@@am__fastdepCC_FALSE@ $(AM_V_CC)source='tests/lib/test_ttable.c' object='tests/lib/test_ttable-test_ttable.o' libtool=no @AMDEPBACKSLASH@ @AMDEP_TRUE@@am__fastdepCC_FALSE@ DEPDIR=$(DEPDIR) $(CCDEPMODE) $(depcomp) @AMDEPBACKSLASH@ @am__fastdepCC_FALSE@ $(AM_V_CC@am__nodep@)$(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(tests_lib_test_ttable_CPPFLAGS) $(CPPFLAGS) $(tests_lib_test_ttable_CFLAGS) $(CFLAGS) -c -o tests/lib/test_ttable-test_ttable.o `test -f 'tests/lib/test_ttable.c' || echo '$(srcdir)/'`tests/lib/test_ttable.c tests/lib/test_ttable-test_ttable.obj: tests/lib/test_ttable.c @am__fastdepCC_TRUE@ $(AM_V_CC)$(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(tests_lib_test_ttable_CPPFLAGS) $(CPPFLAGS) $(tests_lib_test_ttable_CFLAGS) $(CFLAGS) -MT tests/lib/test_ttable-test_ttable.obj -MD -MP -MF tests/lib/$(DEPDIR)/test_ttable-test_ttable.Tpo -c -o tests/lib/test_ttable-test_ttable.obj `if test -f 'tests/lib/test_ttable.c'; then $(CYGPATH_W) 'tests/lib/test_ttable.c'; else $(CYGPATH_W) '$(srcdir)/tests/lib/test_ttable.c'; fi` @am__fastdepCC_TRUE@ $(AM_V_at)$(am__mv) tests/lib/$(DEPDIR)/test_ttable-test_ttable.Tpo tests/lib/$(DEPDIR)/test_ttable-test_ttable.Po @AMDEP_TRUE@@am__fastdepCC_FALSE@ $(AM_V_CC)source='tests/lib/test_ttable.c' object='tests/lib/test_ttable-test_ttable.obj' libtool=no @AMDEPBACKSLASH@ @AMDEP_TRUE@@am__fastdepCC_FALSE@ DEPDIR=$(DEPDIR) $(CCDEPMODE) $(depcomp) @AMDEPBACKSLASH@ @am__fastdepCC_FALSE@ $(AM_V_CC@am__nodep@)$(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(tests_lib_test_ttable_CPPFLAGS) $(CPPFLAGS) $(tests_lib_test_ttable_CFLAGS) $(CFLAGS) -c -o tests/lib/test_ttable-test_ttable.obj `if test -f 'tests/lib/test_ttable.c'; then $(CYGPATH_W) 'tests/lib/test_ttable.c'; else $(CYGPATH_W) '$(srcdir)/tests/lib/test_ttable.c'; fi` tests/lib/test_typelist-test_typelist.o: tests/lib/test_typelist.c @am__fastdepCC_TRUE@ $(AM_V_CC)$(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(tests_lib_test_typelist_CPPFLAGS) $(CPPFLAGS) $(tests_lib_test_typelist_CFLAGS) $(CFLAGS) -MT tests/lib/test_typelist-test_typelist.o -MD -MP -MF tests/lib/$(DEPDIR)/test_typelist-test_typelist.Tpo -c -o tests/lib/test_typelist-test_typelist.o `test -f 'tests/lib/test_typelist.c' || echo '$(srcdir)/'`tests/lib/test_typelist.c @am__fastdepCC_TRUE@ $(AM_V_at)$(am__mv) tests/lib/$(DEPDIR)/test_typelist-test_typelist.Tpo tests/lib/$(DEPDIR)/test_typelist-test_typelist.Po @AMDEP_TRUE@@am__fastdepCC_FALSE@ $(AM_V_CC)source='tests/lib/test_typelist.c' object='tests/lib/test_typelist-test_typelist.o' libtool=no @AMDEPBACKSLASH@ @AMDEP_TRUE@@am__fastdepCC_FALSE@ DEPDIR=$(DEPDIR) $(CCDEPMODE) $(depcomp) @AMDEPBACKSLASH@ @am__fastdepCC_FALSE@ $(AM_V_CC@am__nodep@)$(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(tests_lib_test_typelist_CPPFLAGS) $(CPPFLAGS) $(tests_lib_test_typelist_CFLAGS) $(CFLAGS) -c -o tests/lib/test_typelist-test_typelist.o `test -f 'tests/lib/test_typelist.c' || echo '$(srcdir)/'`tests/lib/test_typelist.c tests/lib/test_typelist-test_typelist.obj: tests/lib/test_typelist.c @am__fastdepCC_TRUE@ $(AM_V_CC)$(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(tests_lib_test_typelist_CPPFLAGS) $(CPPFLAGS) $(tests_lib_test_typelist_CFLAGS) $(CFLAGS) -MT tests/lib/test_typelist-test_typelist.obj -MD -MP -MF tests/lib/$(DEPDIR)/test_typelist-test_typelist.Tpo -c -o tests/lib/test_typelist-test_typelist.obj `if test -f 'tests/lib/test_typelist.c'; then $(CYGPATH_W) 'tests/lib/test_typelist.c'; else $(CYGPATH_W) '$(srcdir)/tests/lib/test_typelist.c'; fi` @am__fastdepCC_TRUE@ $(AM_V_at)$(am__mv) tests/lib/$(DEPDIR)/test_typelist-test_typelist.Tpo tests/lib/$(DEPDIR)/test_typelist-test_typelist.Po @AMDEP_TRUE@@am__fastdepCC_FALSE@ $(AM_V_CC)source='tests/lib/test_typelist.c' object='tests/lib/test_typelist-test_typelist.obj' libtool=no @AMDEPBACKSLASH@ @AMDEP_TRUE@@am__fastdepCC_FALSE@ DEPDIR=$(DEPDIR) $(CCDEPMODE) $(depcomp) @AMDEPBACKSLASH@ @am__fastdepCC_FALSE@ $(AM_V_CC@am__nodep@)$(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(tests_lib_test_typelist_CPPFLAGS) $(CPPFLAGS) $(tests_lib_test_typelist_CFLAGS) $(CFLAGS) -c -o tests/lib/test_typelist-test_typelist.obj `if test -f 'tests/lib/test_typelist.c'; then $(CYGPATH_W) 'tests/lib/test_typelist.c'; else $(CYGPATH_W) '$(srcdir)/tests/lib/test_typelist.c'; fi` tests/helpers/c/lib_test_typelist-prng.o: tests/helpers/c/prng.c @am__fastdepCC_TRUE@ $(AM_V_CC)$(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(tests_lib_test_typelist_CPPFLAGS) $(CPPFLAGS) $(tests_lib_test_typelist_CFLAGS) $(CFLAGS) -MT tests/helpers/c/lib_test_typelist-prng.o -MD -MP -MF tests/helpers/c/$(DEPDIR)/lib_test_typelist-prng.Tpo -c -o tests/helpers/c/lib_test_typelist-prng.o `test -f 'tests/helpers/c/prng.c' || echo '$(srcdir)/'`tests/helpers/c/prng.c @am__fastdepCC_TRUE@ $(AM_V_at)$(am__mv) tests/helpers/c/$(DEPDIR)/lib_test_typelist-prng.Tpo tests/helpers/c/$(DEPDIR)/lib_test_typelist-prng.Po @AMDEP_TRUE@@am__fastdepCC_FALSE@ $(AM_V_CC)source='tests/helpers/c/prng.c' object='tests/helpers/c/lib_test_typelist-prng.o' libtool=no @AMDEPBACKSLASH@ @AMDEP_TRUE@@am__fastdepCC_FALSE@ DEPDIR=$(DEPDIR) $(CCDEPMODE) $(depcomp) @AMDEPBACKSLASH@ @am__fastdepCC_FALSE@ $(AM_V_CC@am__nodep@)$(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(tests_lib_test_typelist_CPPFLAGS) $(CPPFLAGS) $(tests_lib_test_typelist_CFLAGS) $(CFLAGS) -c -o tests/helpers/c/lib_test_typelist-prng.o `test -f 'tests/helpers/c/prng.c' || echo '$(srcdir)/'`tests/helpers/c/prng.c tests/helpers/c/lib_test_typelist-prng.obj: tests/helpers/c/prng.c @am__fastdepCC_TRUE@ $(AM_V_CC)$(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(tests_lib_test_typelist_CPPFLAGS) $(CPPFLAGS) $(tests_lib_test_typelist_CFLAGS) $(CFLAGS) -MT tests/helpers/c/lib_test_typelist-prng.obj -MD -MP -MF tests/helpers/c/$(DEPDIR)/lib_test_typelist-prng.Tpo -c -o tests/helpers/c/lib_test_typelist-prng.obj `if test -f 'tests/helpers/c/prng.c'; then $(CYGPATH_W) 'tests/helpers/c/prng.c'; else $(CYGPATH_W) '$(srcdir)/tests/helpers/c/prng.c'; fi` @am__fastdepCC_TRUE@ $(AM_V_at)$(am__mv) tests/helpers/c/$(DEPDIR)/lib_test_typelist-prng.Tpo tests/helpers/c/$(DEPDIR)/lib_test_typelist-prng.Po @AMDEP_TRUE@@am__fastdepCC_FALSE@ $(AM_V_CC)source='tests/helpers/c/prng.c' object='tests/helpers/c/lib_test_typelist-prng.obj' libtool=no @AMDEPBACKSLASH@ @AMDEP_TRUE@@am__fastdepCC_FALSE@ DEPDIR=$(DEPDIR) $(CCDEPMODE) $(depcomp) @AMDEPBACKSLASH@ @am__fastdepCC_FALSE@ $(AM_V_CC@am__nodep@)$(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(tests_lib_test_typelist_CPPFLAGS) $(CPPFLAGS) $(tests_lib_test_typelist_CFLAGS) $(CFLAGS) -c -o tests/helpers/c/lib_test_typelist-prng.obj `if test -f 'tests/helpers/c/prng.c'; then $(CYGPATH_W) 'tests/helpers/c/prng.c'; else $(CYGPATH_W) '$(srcdir)/tests/helpers/c/prng.c'; fi` tests/lib/test_zlog-test_zlog.o: tests/lib/test_zlog.c @am__fastdepCC_TRUE@ $(AM_V_CC)$(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(tests_lib_test_zlog_CPPFLAGS) $(CPPFLAGS) $(tests_lib_test_zlog_CFLAGS) $(CFLAGS) -MT tests/lib/test_zlog-test_zlog.o -MD -MP -MF tests/lib/$(DEPDIR)/test_zlog-test_zlog.Tpo -c -o tests/lib/test_zlog-test_zlog.o `test -f 'tests/lib/test_zlog.c' || echo '$(srcdir)/'`tests/lib/test_zlog.c @am__fastdepCC_TRUE@ $(AM_V_at)$(am__mv) tests/lib/$(DEPDIR)/test_zlog-test_zlog.Tpo tests/lib/$(DEPDIR)/test_zlog-test_zlog.Po @AMDEP_TRUE@@am__fastdepCC_FALSE@ $(AM_V_CC)source='tests/lib/test_zlog.c' object='tests/lib/test_zlog-test_zlog.o' libtool=no @AMDEPBACKSLASH@ @AMDEP_TRUE@@am__fastdepCC_FALSE@ DEPDIR=$(DEPDIR) $(CCDEPMODE) $(depcomp) @AMDEPBACKSLASH@ @am__fastdepCC_FALSE@ $(AM_V_CC@am__nodep@)$(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(tests_lib_test_zlog_CPPFLAGS) $(CPPFLAGS) $(tests_lib_test_zlog_CFLAGS) $(CFLAGS) -c -o tests/lib/test_zlog-test_zlog.o `test -f 'tests/lib/test_zlog.c' || echo '$(srcdir)/'`tests/lib/test_zlog.c tests/lib/test_zlog-test_zlog.obj: tests/lib/test_zlog.c @am__fastdepCC_TRUE@ $(AM_V_CC)$(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(tests_lib_test_zlog_CPPFLAGS) $(CPPFLAGS) $(tests_lib_test_zlog_CFLAGS) $(CFLAGS) -MT tests/lib/test_zlog-test_zlog.obj -MD -MP -MF tests/lib/$(DEPDIR)/test_zlog-test_zlog.Tpo -c -o tests/lib/test_zlog-test_zlog.obj `if test -f 'tests/lib/test_zlog.c'; then $(CYGPATH_W) 'tests/lib/test_zlog.c'; else $(CYGPATH_W) '$(srcdir)/tests/lib/test_zlog.c'; fi` @am__fastdepCC_TRUE@ $(AM_V_at)$(am__mv) tests/lib/$(DEPDIR)/test_zlog-test_zlog.Tpo tests/lib/$(DEPDIR)/test_zlog-test_zlog.Po @AMDEP_TRUE@@am__fastdepCC_FALSE@ $(AM_V_CC)source='tests/lib/test_zlog.c' object='tests/lib/test_zlog-test_zlog.obj' libtool=no @AMDEPBACKSLASH@ @AMDEP_TRUE@@am__fastdepCC_FALSE@ DEPDIR=$(DEPDIR) $(CCDEPMODE) $(depcomp) @AMDEPBACKSLASH@ @am__fastdepCC_FALSE@ $(AM_V_CC@am__nodep@)$(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(tests_lib_test_zlog_CPPFLAGS) $(CPPFLAGS) $(tests_lib_test_zlog_CFLAGS) $(CFLAGS) -c -o tests/lib/test_zlog-test_zlog.obj `if test -f 'tests/lib/test_zlog.c'; then $(CYGPATH_W) 'tests/lib/test_zlog.c'; else $(CYGPATH_W) '$(srcdir)/tests/lib/test_zlog.c'; fi` tests/lib/test_zmq-test_zmq.o: tests/lib/test_zmq.c @am__fastdepCC_TRUE@ $(AM_V_CC)$(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(tests_lib_test_zmq_CPPFLAGS) $(CPPFLAGS) $(tests_lib_test_zmq_CFLAGS) $(CFLAGS) -MT tests/lib/test_zmq-test_zmq.o -MD -MP -MF tests/lib/$(DEPDIR)/test_zmq-test_zmq.Tpo -c -o tests/lib/test_zmq-test_zmq.o `test -f 'tests/lib/test_zmq.c' || echo '$(srcdir)/'`tests/lib/test_zmq.c @am__fastdepCC_TRUE@ $(AM_V_at)$(am__mv) tests/lib/$(DEPDIR)/test_zmq-test_zmq.Tpo tests/lib/$(DEPDIR)/test_zmq-test_zmq.Po @AMDEP_TRUE@@am__fastdepCC_FALSE@ $(AM_V_CC)source='tests/lib/test_zmq.c' object='tests/lib/test_zmq-test_zmq.o' libtool=no @AMDEPBACKSLASH@ @AMDEP_TRUE@@am__fastdepCC_FALSE@ DEPDIR=$(DEPDIR) $(CCDEPMODE) $(depcomp) @AMDEPBACKSLASH@ @am__fastdepCC_FALSE@ $(AM_V_CC@am__nodep@)$(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(tests_lib_test_zmq_CPPFLAGS) $(CPPFLAGS) $(tests_lib_test_zmq_CFLAGS) $(CFLAGS) -c -o tests/lib/test_zmq-test_zmq.o `test -f 'tests/lib/test_zmq.c' || echo '$(srcdir)/'`tests/lib/test_zmq.c tests/lib/test_zmq-test_zmq.obj: tests/lib/test_zmq.c @am__fastdepCC_TRUE@ $(AM_V_CC)$(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(tests_lib_test_zmq_CPPFLAGS) $(CPPFLAGS) $(tests_lib_test_zmq_CFLAGS) $(CFLAGS) -MT tests/lib/test_zmq-test_zmq.obj -MD -MP -MF tests/lib/$(DEPDIR)/test_zmq-test_zmq.Tpo -c -o tests/lib/test_zmq-test_zmq.obj `if test -f 'tests/lib/test_zmq.c'; then $(CYGPATH_W) 'tests/lib/test_zmq.c'; else $(CYGPATH_W) '$(srcdir)/tests/lib/test_zmq.c'; fi` @am__fastdepCC_TRUE@ $(AM_V_at)$(am__mv) tests/lib/$(DEPDIR)/test_zmq-test_zmq.Tpo tests/lib/$(DEPDIR)/test_zmq-test_zmq.Po @AMDEP_TRUE@@am__fastdepCC_FALSE@ $(AM_V_CC)source='tests/lib/test_zmq.c' object='tests/lib/test_zmq-test_zmq.obj' libtool=no @AMDEPBACKSLASH@ @AMDEP_TRUE@@am__fastdepCC_FALSE@ DEPDIR=$(DEPDIR) $(CCDEPMODE) $(depcomp) @AMDEPBACKSLASH@ @am__fastdepCC_FALSE@ $(AM_V_CC@am__nodep@)$(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(tests_lib_test_zmq_CPPFLAGS) $(CPPFLAGS) $(tests_lib_test_zmq_CFLAGS) $(CFLAGS) -c -o tests/lib/test_zmq-test_zmq.obj `if test -f 'tests/lib/test_zmq.c'; then $(CYGPATH_W) 'tests/lib/test_zmq.c'; else $(CYGPATH_W) '$(srcdir)/tests/lib/test_zmq.c'; fi` tests/ospf6d/test_lsdb-test_lsdb.o: tests/ospf6d/test_lsdb.c @am__fastdepCC_TRUE@ $(AM_V_CC)$(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(tests_ospf6d_test_lsdb_CPPFLAGS) $(CPPFLAGS) $(tests_ospf6d_test_lsdb_CFLAGS) $(CFLAGS) -MT tests/ospf6d/test_lsdb-test_lsdb.o -MD -MP -MF tests/ospf6d/$(DEPDIR)/test_lsdb-test_lsdb.Tpo -c -o tests/ospf6d/test_lsdb-test_lsdb.o `test -f 'tests/ospf6d/test_lsdb.c' || echo '$(srcdir)/'`tests/ospf6d/test_lsdb.c @am__fastdepCC_TRUE@ $(AM_V_at)$(am__mv) tests/ospf6d/$(DEPDIR)/test_lsdb-test_lsdb.Tpo tests/ospf6d/$(DEPDIR)/test_lsdb-test_lsdb.Po @AMDEP_TRUE@@am__fastdepCC_FALSE@ $(AM_V_CC)source='tests/ospf6d/test_lsdb.c' object='tests/ospf6d/test_lsdb-test_lsdb.o' libtool=no @AMDEPBACKSLASH@ @AMDEP_TRUE@@am__fastdepCC_FALSE@ DEPDIR=$(DEPDIR) $(CCDEPMODE) $(depcomp) @AMDEPBACKSLASH@ @am__fastdepCC_FALSE@ $(AM_V_CC@am__nodep@)$(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(tests_ospf6d_test_lsdb_CPPFLAGS) $(CPPFLAGS) $(tests_ospf6d_test_lsdb_CFLAGS) $(CFLAGS) -c -o tests/ospf6d/test_lsdb-test_lsdb.o `test -f 'tests/ospf6d/test_lsdb.c' || echo '$(srcdir)/'`tests/ospf6d/test_lsdb.c tests/ospf6d/test_lsdb-test_lsdb.obj: tests/ospf6d/test_lsdb.c @am__fastdepCC_TRUE@ $(AM_V_CC)$(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(tests_ospf6d_test_lsdb_CPPFLAGS) $(CPPFLAGS) $(tests_ospf6d_test_lsdb_CFLAGS) $(CFLAGS) -MT tests/ospf6d/test_lsdb-test_lsdb.obj -MD -MP -MF tests/ospf6d/$(DEPDIR)/test_lsdb-test_lsdb.Tpo -c -o tests/ospf6d/test_lsdb-test_lsdb.obj `if test -f 'tests/ospf6d/test_lsdb.c'; then $(CYGPATH_W) 'tests/ospf6d/test_lsdb.c'; else $(CYGPATH_W) '$(srcdir)/tests/ospf6d/test_lsdb.c'; fi` @am__fastdepCC_TRUE@ $(AM_V_at)$(am__mv) tests/ospf6d/$(DEPDIR)/test_lsdb-test_lsdb.Tpo tests/ospf6d/$(DEPDIR)/test_lsdb-test_lsdb.Po @AMDEP_TRUE@@am__fastdepCC_FALSE@ $(AM_V_CC)source='tests/ospf6d/test_lsdb.c' object='tests/ospf6d/test_lsdb-test_lsdb.obj' libtool=no @AMDEPBACKSLASH@ @AMDEP_TRUE@@am__fastdepCC_FALSE@ DEPDIR=$(DEPDIR) $(CCDEPMODE) $(depcomp) @AMDEPBACKSLASH@ @am__fastdepCC_FALSE@ $(AM_V_CC@am__nodep@)$(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(tests_ospf6d_test_lsdb_CPPFLAGS) $(CPPFLAGS) $(tests_ospf6d_test_lsdb_CFLAGS) $(CFLAGS) -c -o tests/ospf6d/test_lsdb-test_lsdb.obj `if test -f 'tests/ospf6d/test_lsdb.c'; then $(CYGPATH_W) 'tests/ospf6d/test_lsdb.c'; else $(CYGPATH_W) '$(srcdir)/tests/ospf6d/test_lsdb.c'; fi` tests/lib/cli/ospf6d_test_lsdb-common_cli.o: tests/lib/cli/common_cli.c @am__fastdepCC_TRUE@ $(AM_V_CC)$(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(tests_ospf6d_test_lsdb_CPPFLAGS) $(CPPFLAGS) $(tests_ospf6d_test_lsdb_CFLAGS) $(CFLAGS) -MT tests/lib/cli/ospf6d_test_lsdb-common_cli.o -MD -MP -MF tests/lib/cli/$(DEPDIR)/ospf6d_test_lsdb-common_cli.Tpo -c -o tests/lib/cli/ospf6d_test_lsdb-common_cli.o `test -f 'tests/lib/cli/common_cli.c' || echo '$(srcdir)/'`tests/lib/cli/common_cli.c @am__fastdepCC_TRUE@ $(AM_V_at)$(am__mv) tests/lib/cli/$(DEPDIR)/ospf6d_test_lsdb-common_cli.Tpo tests/lib/cli/$(DEPDIR)/ospf6d_test_lsdb-common_cli.Po @AMDEP_TRUE@@am__fastdepCC_FALSE@ $(AM_V_CC)source='tests/lib/cli/common_cli.c' object='tests/lib/cli/ospf6d_test_lsdb-common_cli.o' libtool=no @AMDEPBACKSLASH@ @AMDEP_TRUE@@am__fastdepCC_FALSE@ DEPDIR=$(DEPDIR) $(CCDEPMODE) $(depcomp) @AMDEPBACKSLASH@ @am__fastdepCC_FALSE@ $(AM_V_CC@am__nodep@)$(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(tests_ospf6d_test_lsdb_CPPFLAGS) $(CPPFLAGS) $(tests_ospf6d_test_lsdb_CFLAGS) $(CFLAGS) -c -o tests/lib/cli/ospf6d_test_lsdb-common_cli.o `test -f 'tests/lib/cli/common_cli.c' || echo '$(srcdir)/'`tests/lib/cli/common_cli.c tests/lib/cli/ospf6d_test_lsdb-common_cli.obj: tests/lib/cli/common_cli.c @am__fastdepCC_TRUE@ $(AM_V_CC)$(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(tests_ospf6d_test_lsdb_CPPFLAGS) $(CPPFLAGS) $(tests_ospf6d_test_lsdb_CFLAGS) $(CFLAGS) -MT tests/lib/cli/ospf6d_test_lsdb-common_cli.obj -MD -MP -MF tests/lib/cli/$(DEPDIR)/ospf6d_test_lsdb-common_cli.Tpo -c -o tests/lib/cli/ospf6d_test_lsdb-common_cli.obj `if test -f 'tests/lib/cli/common_cli.c'; then $(CYGPATH_W) 'tests/lib/cli/common_cli.c'; else $(CYGPATH_W) '$(srcdir)/tests/lib/cli/common_cli.c'; fi` @am__fastdepCC_TRUE@ $(AM_V_at)$(am__mv) tests/lib/cli/$(DEPDIR)/ospf6d_test_lsdb-common_cli.Tpo tests/lib/cli/$(DEPDIR)/ospf6d_test_lsdb-common_cli.Po @AMDEP_TRUE@@am__fastdepCC_FALSE@ $(AM_V_CC)source='tests/lib/cli/common_cli.c' object='tests/lib/cli/ospf6d_test_lsdb-common_cli.obj' libtool=no @AMDEPBACKSLASH@ @AMDEP_TRUE@@am__fastdepCC_FALSE@ DEPDIR=$(DEPDIR) $(CCDEPMODE) $(depcomp) @AMDEPBACKSLASH@ @am__fastdepCC_FALSE@ $(AM_V_CC@am__nodep@)$(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(tests_ospf6d_test_lsdb_CPPFLAGS) $(CPPFLAGS) $(tests_ospf6d_test_lsdb_CFLAGS) $(CFLAGS) -c -o tests/lib/cli/ospf6d_test_lsdb-common_cli.obj `if test -f 'tests/lib/cli/common_cli.c'; then $(CYGPATH_W) 'tests/lib/cli/common_cli.c'; else $(CYGPATH_W) '$(srcdir)/tests/lib/cli/common_cli.c'; fi` .cc.o: @am__fastdepCXX_TRUE@ $(AM_V_CXX)depbase=`echo $@ | sed 's|[^/]*$$|$(DEPDIR)/&|;s|\.o$$||'`;\ @am__fastdepCXX_TRUE@ $(CXXCOMPILE) -MT $@ -MD -MP -MF $$depbase.Tpo -c -o $@ $< &&\ @am__fastdepCXX_TRUE@ $(am__mv) $$depbase.Tpo $$depbase.Po @AMDEP_TRUE@@am__fastdepCXX_FALSE@ $(AM_V_CXX)source='$<' object='$@' libtool=no @AMDEPBACKSLASH@ @AMDEP_TRUE@@am__fastdepCXX_FALSE@ DEPDIR=$(DEPDIR) $(CXXDEPMODE) $(depcomp) @AMDEPBACKSLASH@ @am__fastdepCXX_FALSE@ $(AM_V_CXX@am__nodep@)$(CXXCOMPILE) -c -o $@ $< .cc.obj: @am__fastdepCXX_TRUE@ $(AM_V_CXX)depbase=`echo $@ | sed 's|[^/]*$$|$(DEPDIR)/&|;s|\.obj$$||'`;\ @am__fastdepCXX_TRUE@ $(CXXCOMPILE) -MT $@ -MD -MP -MF $$depbase.Tpo -c -o $@ `$(CYGPATH_W) '$<'` &&\ @am__fastdepCXX_TRUE@ $(am__mv) $$depbase.Tpo $$depbase.Po @AMDEP_TRUE@@am__fastdepCXX_FALSE@ $(AM_V_CXX)source='$<' object='$@' libtool=no @AMDEPBACKSLASH@ @AMDEP_TRUE@@am__fastdepCXX_FALSE@ DEPDIR=$(DEPDIR) $(CXXDEPMODE) $(depcomp) @AMDEPBACKSLASH@ @am__fastdepCXX_FALSE@ $(AM_V_CXX@am__nodep@)$(CXXCOMPILE) -c -o $@ `$(CYGPATH_W) '$<'` .cc.lo: @am__fastdepCXX_TRUE@ $(AM_V_CXX)depbase=`echo $@ | sed 's|[^/]*$$|$(DEPDIR)/&|;s|\.lo$$||'`;\ @am__fastdepCXX_TRUE@ $(LTCXXCOMPILE) -MT $@ -MD -MP -MF $$depbase.Tpo -c -o $@ $< &&\ @am__fastdepCXX_TRUE@ $(am__mv) $$depbase.Tpo $$depbase.Plo @AMDEP_TRUE@@am__fastdepCXX_FALSE@ $(AM_V_CXX)source='$<' object='$@' libtool=yes @AMDEPBACKSLASH@ @AMDEP_TRUE@@am__fastdepCXX_FALSE@ DEPDIR=$(DEPDIR) $(CXXDEPMODE) $(depcomp) @AMDEPBACKSLASH@ @am__fastdepCXX_FALSE@ $(AM_V_CXX@am__nodep@)$(LTCXXCOMPILE) -c -o $@ $< grpc/libfrrgrpc_pb_la-frr-northbound.pb.lo: grpc/frr-northbound.pb.cc @am__fastdepCXX_TRUE@ $(AM_V_CXX)$(LIBTOOL) $(AM_V_lt) --tag=CXX $(AM_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=compile $(CXX) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(grpc_libfrrgrpc_pb_la_CPPFLAGS) $(CPPFLAGS) $(AM_CXXFLAGS) $(CXXFLAGS) -MT grpc/libfrrgrpc_pb_la-frr-northbound.pb.lo -MD -MP -MF grpc/$(DEPDIR)/libfrrgrpc_pb_la-frr-northbound.pb.Tpo -c -o grpc/libfrrgrpc_pb_la-frr-northbound.pb.lo `test -f 'grpc/frr-northbound.pb.cc' || echo '$(srcdir)/'`grpc/frr-northbound.pb.cc @am__fastdepCXX_TRUE@ $(AM_V_at)$(am__mv) grpc/$(DEPDIR)/libfrrgrpc_pb_la-frr-northbound.pb.Tpo grpc/$(DEPDIR)/libfrrgrpc_pb_la-frr-northbound.pb.Plo @AMDEP_TRUE@@am__fastdepCXX_FALSE@ $(AM_V_CXX)source='grpc/frr-northbound.pb.cc' object='grpc/libfrrgrpc_pb_la-frr-northbound.pb.lo' libtool=yes @AMDEPBACKSLASH@ @AMDEP_TRUE@@am__fastdepCXX_FALSE@ DEPDIR=$(DEPDIR) $(CXXDEPMODE) $(depcomp) @AMDEPBACKSLASH@ @am__fastdepCXX_FALSE@ $(AM_V_CXX@am__nodep@)$(LIBTOOL) $(AM_V_lt) --tag=CXX $(AM_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=compile $(CXX) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(grpc_libfrrgrpc_pb_la_CPPFLAGS) $(CPPFLAGS) $(AM_CXXFLAGS) $(CXXFLAGS) -c -o grpc/libfrrgrpc_pb_la-frr-northbound.pb.lo `test -f 'grpc/frr-northbound.pb.cc' || echo '$(srcdir)/'`grpc/frr-northbound.pb.cc grpc/libfrrgrpc_pb_la-frr-northbound.grpc.pb.lo: grpc/frr-northbound.grpc.pb.cc @am__fastdepCXX_TRUE@ $(AM_V_CXX)$(LIBTOOL) $(AM_V_lt) --tag=CXX $(AM_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=compile $(CXX) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(grpc_libfrrgrpc_pb_la_CPPFLAGS) $(CPPFLAGS) $(AM_CXXFLAGS) $(CXXFLAGS) -MT grpc/libfrrgrpc_pb_la-frr-northbound.grpc.pb.lo -MD -MP -MF grpc/$(DEPDIR)/libfrrgrpc_pb_la-frr-northbound.grpc.pb.Tpo -c -o grpc/libfrrgrpc_pb_la-frr-northbound.grpc.pb.lo `test -f 'grpc/frr-northbound.grpc.pb.cc' || echo '$(srcdir)/'`grpc/frr-northbound.grpc.pb.cc @am__fastdepCXX_TRUE@ $(AM_V_at)$(am__mv) grpc/$(DEPDIR)/libfrrgrpc_pb_la-frr-northbound.grpc.pb.Tpo grpc/$(DEPDIR)/libfrrgrpc_pb_la-frr-northbound.grpc.pb.Plo @AMDEP_TRUE@@am__fastdepCXX_FALSE@ $(AM_V_CXX)source='grpc/frr-northbound.grpc.pb.cc' object='grpc/libfrrgrpc_pb_la-frr-northbound.grpc.pb.lo' libtool=yes @AMDEPBACKSLASH@ @AMDEP_TRUE@@am__fastdepCXX_FALSE@ DEPDIR=$(DEPDIR) $(CXXDEPMODE) $(depcomp) @AMDEPBACKSLASH@ @am__fastdepCXX_FALSE@ $(AM_V_CXX@am__nodep@)$(LIBTOOL) $(AM_V_lt) --tag=CXX $(AM_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=compile $(CXX) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(grpc_libfrrgrpc_pb_la_CPPFLAGS) $(CPPFLAGS) $(AM_CXXFLAGS) $(CXXFLAGS) -c -o grpc/libfrrgrpc_pb_la-frr-northbound.grpc.pb.lo `test -f 'grpc/frr-northbound.grpc.pb.cc' || echo '$(srcdir)/'`grpc/frr-northbound.grpc.pb.cc lib/grpc_la-northbound_grpc.lo: lib/northbound_grpc.cpp @am__fastdepCXX_TRUE@ $(AM_V_CXX)$(LIBTOOL) $(AM_V_lt) --tag=CXX $(AM_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=compile $(CXX) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(AM_CPPFLAGS) $(CPPFLAGS) $(lib_grpc_la_CXXFLAGS) $(CXXFLAGS) -MT lib/grpc_la-northbound_grpc.lo -MD -MP -MF lib/$(DEPDIR)/grpc_la-northbound_grpc.Tpo -c -o lib/grpc_la-northbound_grpc.lo `test -f 'lib/northbound_grpc.cpp' || echo '$(srcdir)/'`lib/northbound_grpc.cpp @am__fastdepCXX_TRUE@ $(AM_V_at)$(am__mv) lib/$(DEPDIR)/grpc_la-northbound_grpc.Tpo lib/$(DEPDIR)/grpc_la-northbound_grpc.Plo @AMDEP_TRUE@@am__fastdepCXX_FALSE@ $(AM_V_CXX)source='lib/northbound_grpc.cpp' object='lib/grpc_la-northbound_grpc.lo' libtool=yes @AMDEPBACKSLASH@ @AMDEP_TRUE@@am__fastdepCXX_FALSE@ DEPDIR=$(DEPDIR) $(CXXDEPMODE) $(depcomp) @AMDEPBACKSLASH@ @am__fastdepCXX_FALSE@ $(AM_V_CXX@am__nodep@)$(LIBTOOL) $(AM_V_lt) --tag=CXX $(AM_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=compile $(CXX) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(AM_CPPFLAGS) $(CPPFLAGS) $(lib_grpc_la_CXXFLAGS) $(CXXFLAGS) -c -o lib/grpc_la-northbound_grpc.lo `test -f 'lib/northbound_grpc.cpp' || echo '$(srcdir)/'`lib/northbound_grpc.cpp .cpp.o: @am__fastdepCXX_TRUE@ $(AM_V_CXX)depbase=`echo $@ | sed 's|[^/]*$$|$(DEPDIR)/&|;s|\.o$$||'`;\ @am__fastdepCXX_TRUE@ $(CXXCOMPILE) -MT $@ -MD -MP -MF $$depbase.Tpo -c -o $@ $< &&\ @am__fastdepCXX_TRUE@ $(am__mv) $$depbase.Tpo $$depbase.Po @AMDEP_TRUE@@am__fastdepCXX_FALSE@ $(AM_V_CXX)source='$<' object='$@' libtool=no @AMDEPBACKSLASH@ @AMDEP_TRUE@@am__fastdepCXX_FALSE@ DEPDIR=$(DEPDIR) $(CXXDEPMODE) $(depcomp) @AMDEPBACKSLASH@ @am__fastdepCXX_FALSE@ $(AM_V_CXX@am__nodep@)$(CXXCOMPILE) -c -o $@ $< .cpp.obj: @am__fastdepCXX_TRUE@ $(AM_V_CXX)depbase=`echo $@ | sed 's|[^/]*$$|$(DEPDIR)/&|;s|\.obj$$||'`;\ @am__fastdepCXX_TRUE@ $(CXXCOMPILE) -MT $@ -MD -MP -MF $$depbase.Tpo -c -o $@ `$(CYGPATH_W) '$<'` &&\ @am__fastdepCXX_TRUE@ $(am__mv) $$depbase.Tpo $$depbase.Po @AMDEP_TRUE@@am__fastdepCXX_FALSE@ $(AM_V_CXX)source='$<' object='$@' libtool=no @AMDEPBACKSLASH@ @AMDEP_TRUE@@am__fastdepCXX_FALSE@ DEPDIR=$(DEPDIR) $(CXXDEPMODE) $(depcomp) @AMDEPBACKSLASH@ @am__fastdepCXX_FALSE@ $(AM_V_CXX@am__nodep@)$(CXXCOMPILE) -c -o $@ `$(CYGPATH_W) '$<'` .cpp.lo: @am__fastdepCXX_TRUE@ $(AM_V_CXX)depbase=`echo $@ | sed 's|[^/]*$$|$(DEPDIR)/&|;s|\.lo$$||'`;\ @am__fastdepCXX_TRUE@ $(LTCXXCOMPILE) -MT $@ -MD -MP -MF $$depbase.Tpo -c -o $@ $< &&\ @am__fastdepCXX_TRUE@ $(am__mv) $$depbase.Tpo $$depbase.Plo @AMDEP_TRUE@@am__fastdepCXX_FALSE@ $(AM_V_CXX)source='$<' object='$@' libtool=yes @AMDEPBACKSLASH@ @AMDEP_TRUE@@am__fastdepCXX_FALSE@ DEPDIR=$(DEPDIR) $(CXXDEPMODE) $(depcomp) @AMDEPBACKSLASH@ @am__fastdepCXX_FALSE@ $(AM_V_CXX@am__nodep@)$(LTCXXCOMPILE) -c -o $@ $< mostlyclean-libtool: -rm -f *.lo clean-libtool: -rm -rf .libs _libs -rm -rf babeld/.libs babeld/_libs -rm -rf bfdd/.libs bfdd/_libs -rm -rf bgpd/.libs bgpd/_libs -rm -rf bgpd/rfp-example/rfptest/.libs bgpd/rfp-example/rfptest/_libs -rm -rf eigrpd/.libs eigrpd/_libs -rm -rf fpm/.libs fpm/_libs -rm -rf grpc/.libs grpc/_libs -rm -rf isisd/.libs isisd/_libs -rm -rf ldpd/.libs ldpd/_libs -rm -rf lib/.libs lib/_libs -rm -rf lib/printf/.libs lib/printf/_libs -rm -rf nhrpd/.libs nhrpd/_libs -rm -rf ospf6d/.libs ospf6d/_libs -rm -rf ospfclient/.libs ospfclient/_libs -rm -rf ospfd/.libs ospfd/_libs -rm -rf pbrd/.libs pbrd/_libs -rm -rf pimd/.libs pimd/_libs -rm -rf qpb/.libs qpb/_libs -rm -rf ripd/.libs ripd/_libs -rm -rf ripngd/.libs ripngd/_libs -rm -rf sharpd/.libs sharpd/_libs -rm -rf staticd/.libs staticd/_libs -rm -rf tests/bgpd/.libs tests/bgpd/_libs -rm -rf tests/isisd/.libs tests/isisd/_libs -rm -rf tests/lib/.libs tests/lib/_libs -rm -rf tests/lib/cli/.libs tests/lib/cli/_libs -rm -rf tests/lib/northbound/.libs tests/lib/northbound/_libs -rm -rf tests/ospf6d/.libs tests/ospf6d/_libs -rm -rf tools/.libs tools/_libs -rm -rf vrrpd/.libs vrrpd/_libs -rm -rf vtysh/.libs vtysh/_libs -rm -rf watchfrr/.libs watchfrr/_libs -rm -rf yang/.libs yang/_libs -rm -rf zebra/.libs zebra/_libs distclean-libtool: -rm -f libtool config.lt install-dist_examplesDATA: $(dist_examples_DATA) @$(NORMAL_INSTALL) @list='$(dist_examples_DATA)'; test -n "$(examplesdir)" || list=; \ if test -n "$$list"; then \ echo " $(MKDIR_P) '$(DESTDIR)$(examplesdir)'"; \ $(MKDIR_P) "$(DESTDIR)$(examplesdir)" || exit 1; \ fi; \ for p in $$list; do \ if test -f "$$p"; then d=; else d="$(srcdir)/"; fi; \ echo "$$d$$p"; \ done | $(am__base_list) | \ while read files; do \ echo " $(INSTALL_DATA) $$files '$(DESTDIR)$(examplesdir)'"; \ $(INSTALL_DATA) $$files "$(DESTDIR)$(examplesdir)" || exit $$?; \ done uninstall-dist_examplesDATA: @$(NORMAL_UNINSTALL) @list='$(dist_examples_DATA)'; test -n "$(examplesdir)" || list=; \ files=`for p in $$list; do echo $$p; done | sed -e 's|^.*/||'`; \ dir='$(DESTDIR)$(examplesdir)'; $(am__uninstall_files_from_dir) install-dist_yangmodelsDATA: $(dist_yangmodels_DATA) @$(NORMAL_INSTALL) @list='$(dist_yangmodels_DATA)'; test -n "$(yangmodelsdir)" || list=; \ if test -n "$$list"; then \ echo " $(MKDIR_P) '$(DESTDIR)$(yangmodelsdir)'"; \ $(MKDIR_P) "$(DESTDIR)$(yangmodelsdir)" || exit 1; \ fi; \ for p in $$list; do \ if test -f "$$p"; then d=; else d="$(srcdir)/"; fi; \ echo "$$d$$p"; \ done | $(am__base_list) | \ while read files; do \ echo " $(INSTALL_DATA) $$files '$(DESTDIR)$(yangmodelsdir)'"; \ $(INSTALL_DATA) $$files "$(DESTDIR)$(yangmodelsdir)" || exit $$?; \ done uninstall-dist_yangmodelsDATA: @$(NORMAL_UNINSTALL) @list='$(dist_yangmodels_DATA)'; test -n "$(yangmodelsdir)" || list=; \ files=`for p in $$list; do echo $$p; done | sed -e 's|^.*/||'`; \ dir='$(DESTDIR)$(yangmodelsdir)'; $(am__uninstall_files_from_dir) install-rstman1DATA: $(rstman1_DATA) @$(NORMAL_INSTALL) @list='$(rstman1_DATA)'; test -n "$(rstman1dir)" || list=; \ if test -n "$$list"; then \ echo " $(MKDIR_P) '$(DESTDIR)$(rstman1dir)'"; \ $(MKDIR_P) "$(DESTDIR)$(rstman1dir)" || exit 1; \ fi; \ for p in $$list; do \ if test -f "$$p"; then d=; else d="$(srcdir)/"; fi; \ echo "$$d$$p"; \ done | $(am__base_list) | \ while read files; do \ echo " $(INSTALL_DATA) $$files '$(DESTDIR)$(rstman1dir)'"; \ $(INSTALL_DATA) $$files "$(DESTDIR)$(rstman1dir)" || exit $$?; \ done uninstall-rstman1DATA: @$(NORMAL_UNINSTALL) @list='$(rstman1_DATA)'; test -n "$(rstman1dir)" || list=; \ files=`for p in $$list; do echo $$p; done | sed -e 's|^.*/||'`; \ dir='$(DESTDIR)$(rstman1dir)'; $(am__uninstall_files_from_dir) install-rstman8DATA: $(rstman8_DATA) @$(NORMAL_INSTALL) @list='$(rstman8_DATA)'; test -n "$(rstman8dir)" || list=; \ if test -n "$$list"; then \ echo " $(MKDIR_P) '$(DESTDIR)$(rstman8dir)'"; \ $(MKDIR_P) "$(DESTDIR)$(rstman8dir)" || exit 1; \ fi; \ for p in $$list; do \ if test -f "$$p"; then d=; else d="$(srcdir)/"; fi; \ echo "$$d$$p"; \ done | $(am__base_list) | \ while read files; do \ echo " $(INSTALL_DATA) $$files '$(DESTDIR)$(rstman8dir)'"; \ $(INSTALL_DATA) $$files "$(DESTDIR)$(rstman8dir)" || exit $$?; \ done uninstall-rstman8DATA: @$(NORMAL_UNINSTALL) @list='$(rstman8_DATA)'; test -n "$(rstman8dir)" || list=; \ files=`for p in $$list; do echo $$p; done | sed -e 's|^.*/||'`; \ dir='$(DESTDIR)$(rstman8dir)'; $(am__uninstall_files_from_dir) install-eigrpdheaderHEADERS: $(eigrpdheader_HEADERS) @$(NORMAL_INSTALL) @list='$(eigrpdheader_HEADERS)'; test -n "$(eigrpdheaderdir)" || list=; \ if test -n "$$list"; then \ echo " $(MKDIR_P) '$(DESTDIR)$(eigrpdheaderdir)'"; \ $(MKDIR_P) "$(DESTDIR)$(eigrpdheaderdir)" || exit 1; \ fi; \ for p in $$list; do \ if test -f "$$p"; then d=; else d="$(srcdir)/"; fi; \ echo "$$d$$p"; \ done | $(am__base_list) | \ while read files; do \ echo " $(INSTALL_HEADER) $$files '$(DESTDIR)$(eigrpdheaderdir)'"; \ $(INSTALL_HEADER) $$files "$(DESTDIR)$(eigrpdheaderdir)" || exit $$?; \ done uninstall-eigrpdheaderHEADERS: @$(NORMAL_UNINSTALL) @list='$(eigrpdheader_HEADERS)'; test -n "$(eigrpdheaderdir)" || list=; \ files=`for p in $$list; do echo $$p; done | sed -e 's|^.*/||'`; \ dir='$(DESTDIR)$(eigrpdheaderdir)'; $(am__uninstall_files_from_dir) install-nodist_pkgincludeHEADERS: $(nodist_pkginclude_HEADERS) @$(NORMAL_INSTALL) @list='$(nodist_pkginclude_HEADERS)'; test -n "$(pkgincludedir)" || list=; \ if test -n "$$list"; then \ echo " $(MKDIR_P) '$(DESTDIR)$(pkgincludedir)'"; \ $(MKDIR_P) "$(DESTDIR)$(pkgincludedir)" || exit 1; \ fi; \ for p in $$list; do \ if test -f "$$p"; then d=; else d="$(srcdir)/"; fi; \ echo "$$d$$p"; \ done | $(am__base_list) | \ while read files; do \ echo " $(INSTALL_HEADER) $$files '$(DESTDIR)$(pkgincludedir)'"; \ $(INSTALL_HEADER) $$files "$(DESTDIR)$(pkgincludedir)" || exit $$?; \ done uninstall-nodist_pkgincludeHEADERS: @$(NORMAL_UNINSTALL) @list='$(nodist_pkginclude_HEADERS)'; test -n "$(pkgincludedir)" || list=; \ files=`for p in $$list; do echo $$p; done | sed -e 's|^.*/||'`; \ dir='$(DESTDIR)$(pkgincludedir)'; $(am__uninstall_files_from_dir) install-ospfapiheaderHEADERS: $(ospfapiheader_HEADERS) @$(NORMAL_INSTALL) @list='$(ospfapiheader_HEADERS)'; test -n "$(ospfapiheaderdir)" || list=; \ if test -n "$$list"; then \ echo " $(MKDIR_P) '$(DESTDIR)$(ospfapiheaderdir)'"; \ $(MKDIR_P) "$(DESTDIR)$(ospfapiheaderdir)" || exit 1; \ fi; \ for p in $$list; do \ if test -f "$$p"; then d=; else d="$(srcdir)/"; fi; \ echo "$$d$$p"; \ done | $(am__base_list) | \ while read files; do \ echo " $(INSTALL_HEADER) $$files '$(DESTDIR)$(ospfapiheaderdir)'"; \ $(INSTALL_HEADER) $$files "$(DESTDIR)$(ospfapiheaderdir)" || exit $$?; \ done uninstall-ospfapiheaderHEADERS: @$(NORMAL_UNINSTALL) @list='$(ospfapiheader_HEADERS)'; test -n "$(ospfapiheaderdir)" || list=; \ files=`for p in $$list; do echo $$p; done | sed -e 's|^.*/||'`; \ dir='$(DESTDIR)$(ospfapiheaderdir)'; $(am__uninstall_files_from_dir) install-ospfdheaderHEADERS: $(ospfdheader_HEADERS) @$(NORMAL_INSTALL) @list='$(ospfdheader_HEADERS)'; test -n "$(ospfdheaderdir)" || list=; \ if test -n "$$list"; then \ echo " $(MKDIR_P) '$(DESTDIR)$(ospfdheaderdir)'"; \ $(MKDIR_P) "$(DESTDIR)$(ospfdheaderdir)" || exit 1; \ fi; \ for p in $$list; do \ if test -f "$$p"; then d=; else d="$(srcdir)/"; fi; \ echo "$$d$$p"; \ done | $(am__base_list) | \ while read files; do \ echo " $(INSTALL_HEADER) $$files '$(DESTDIR)$(ospfdheaderdir)'"; \ $(INSTALL_HEADER) $$files "$(DESTDIR)$(ospfdheaderdir)" || exit $$?; \ done uninstall-ospfdheaderHEADERS: @$(NORMAL_UNINSTALL) @list='$(ospfdheader_HEADERS)'; test -n "$(ospfdheaderdir)" || list=; \ files=`for p in $$list; do echo $$p; done | sed -e 's|^.*/||'`; \ dir='$(DESTDIR)$(ospfdheaderdir)'; $(am__uninstall_files_from_dir) install-pkgincludeHEADERS: $(pkginclude_HEADERS) @$(NORMAL_INSTALL) @list='$(pkginclude_HEADERS)'; test -n "$(pkgincludedir)" || list=; \ if test -n "$$list"; then \ echo " $(MKDIR_P) '$(DESTDIR)$(pkgincludedir)'"; \ $(MKDIR_P) "$(DESTDIR)$(pkgincludedir)" || exit 1; \ fi; \ for p in $$list; do \ if test -f "$$p"; then d=; else d="$(srcdir)/"; fi; \ echo "$$d$$p"; \ done | $(am__base_list) | \ while read files; do \ echo " $(INSTALL_HEADER) $$files '$(DESTDIR)$(pkgincludedir)'"; \ $(INSTALL_HEADER) $$files "$(DESTDIR)$(pkgincludedir)" || exit $$?; \ done uninstall-pkgincludeHEADERS: @$(NORMAL_UNINSTALL) @list='$(pkginclude_HEADERS)'; test -n "$(pkgincludedir)" || list=; \ files=`for p in $$list; do echo $$p; done | sed -e 's|^.*/||'`; \ dir='$(DESTDIR)$(pkgincludedir)'; $(am__uninstall_files_from_dir) ID: $(am__tagged_files) $(am__define_uniq_tagged_files); mkid -fID $$unique tags: tags-am TAGS: tags tags-am: $(TAGS_DEPENDENCIES) $(am__tagged_files) set x; \ here=`pwd`; \ $(am__define_uniq_tagged_files); \ shift; \ if test -z "$(ETAGS_ARGS)$$*$$unique"; then :; else \ test -n "$$unique" || unique=$$empty_fix; \ if test $$# -gt 0; then \ $(ETAGS) $(ETAGSFLAGS) $(AM_ETAGSFLAGS) $(ETAGS_ARGS) \ "$$@" $$unique; \ else \ $(ETAGS) $(ETAGSFLAGS) $(AM_ETAGSFLAGS) $(ETAGS_ARGS) \ $$unique; \ fi; \ fi ctags: ctags-am CTAGS: ctags ctags-am: $(TAGS_DEPENDENCIES) $(am__tagged_files) $(am__define_uniq_tagged_files); \ test -z "$(CTAGS_ARGS)$$unique" \ || $(CTAGS) $(CTAGSFLAGS) $(AM_CTAGSFLAGS) $(CTAGS_ARGS) \ $$unique GTAGS: here=`$(am__cd) $(top_builddir) && pwd` \ && $(am__cd) $(top_srcdir) \ && gtags -i $(GTAGS_ARGS) "$$here" cscope: cscope.files test ! -s cscope.files \ || $(CSCOPE) -b -q $(AM_CSCOPEFLAGS) $(CSCOPEFLAGS) -i cscope.files $(CSCOPE_ARGS) clean-cscope: -rm -f cscope.files cscope.files: clean-cscope cscopelist cscopelist: cscopelist-am cscopelist-am: $(am__tagged_files) list='$(am__tagged_files)'; \ case "$(srcdir)" in \ [\\/]* | ?:[\\/]*) sdir="$(srcdir)" ;; \ *) sdir=$(subdir)/$(srcdir) ;; \ esac; \ for i in $$list; do \ if test -f "$$i"; then \ echo "$(subdir)/$$i"; \ else \ echo "$$sdir/$$i"; \ fi; \ done >> $(top_builddir)/cscope.files distclean-tags: -rm -f TAGS ID GTAGS GRTAGS GSYMS GPATH tags -rm -f cscope.out cscope.in.out cscope.po.out cscope.files distdir: $(BUILT_SOURCES) $(MAKE) $(AM_MAKEFLAGS) distdir-am distdir-am: $(DISTFILES) $(am__remove_distdir) test -d "$(distdir)" || mkdir "$(distdir)" @srcdirstrip=`echo "$(srcdir)" | sed 's/[].[^$$\\*]/\\\\&/g'`; \ topsrcdirstrip=`echo "$(top_srcdir)" | sed 's/[].[^$$\\*]/\\\\&/g'`; \ list='$(DISTFILES)'; \ dist_files=`for file in $$list; do echo $$file; done | \ sed -e "s|^$$srcdirstrip/||;t" \ -e "s|^$$topsrcdirstrip/|$(top_builddir)/|;t"`; \ case $$dist_files in \ */*) $(MKDIR_P) `echo "$$dist_files" | \ sed '/\//!d;s|^|$(distdir)/|;s,/[^/]*$$,,' | \ sort -u` ;; \ esac; \ for file in $$dist_files; do \ if test -f $$file || test -d $$file; then d=.; else d=$(srcdir); fi; \ if test -d $$d/$$file; then \ dir=`echo "/$$file" | sed -e 's,/[^/]*$$,,'`; \ if test -d "$(distdir)/$$file"; then \ find "$(distdir)/$$file" -type d ! -perm -700 -exec chmod u+rwx {} \;; \ fi; \ if test -d $(srcdir)/$$file && test $$d != $(srcdir); then \ cp -fpR $(srcdir)/$$file "$(distdir)$$dir" || exit 1; \ find "$(distdir)/$$file" -type d ! -perm -700 -exec chmod u+rwx {} \;; \ fi; \ cp -fpR $$d/$$file "$(distdir)$$dir" || exit 1; \ else \ test -f "$(distdir)/$$file" \ || cp -p $$d/$$file "$(distdir)/$$file" \ || exit 1; \ fi; \ done -test -n "$(am__skip_mode_fix)" \ || find "$(distdir)" -type d ! -perm -755 \ -exec chmod u+rwx,go+rx {} \; -o \ ! -type d ! -perm -444 -links 1 -exec chmod a+r {} \; -o \ ! -type d ! -perm -400 -exec chmod a+r {} \; -o \ ! -type d ! -perm -444 -exec $(install_sh) -c -m a+r {} {} \; \ || chmod -R a+r "$(distdir)" dist-gzip: distdir tardir=$(distdir) && $(am__tar) | eval GZIP= gzip $(GZIP_ENV) -c >$(distdir).tar.gz $(am__post_remove_distdir) dist-bzip2: distdir tardir=$(distdir) && $(am__tar) | BZIP2=$${BZIP2--9} bzip2 -c >$(distdir).tar.bz2 $(am__post_remove_distdir) dist-lzip: distdir tardir=$(distdir) && $(am__tar) | lzip -c $${LZIP_OPT--9} >$(distdir).tar.lz $(am__post_remove_distdir) dist-xz: distdir tardir=$(distdir) && $(am__tar) | XZ_OPT=$${XZ_OPT--e} xz -c >$(distdir).tar.xz $(am__post_remove_distdir) dist-tarZ: distdir @echo WARNING: "Support for distribution archives compressed with" \ "legacy program 'compress' is deprecated." >&2 @echo WARNING: "It will be removed altogether in Automake 2.0" >&2 tardir=$(distdir) && $(am__tar) | compress -c >$(distdir).tar.Z $(am__post_remove_distdir) dist-shar: distdir @echo WARNING: "Support for shar distribution archives is" \ "deprecated." >&2 @echo WARNING: "It will be removed altogether in Automake 2.0" >&2 shar $(distdir) | eval GZIP= gzip $(GZIP_ENV) -c >$(distdir).shar.gz $(am__post_remove_distdir) dist-zip: distdir -rm -f $(distdir).zip zip -rq $(distdir).zip $(distdir) $(am__post_remove_distdir) dist dist-all: $(MAKE) $(AM_MAKEFLAGS) $(DIST_TARGETS) am__post_remove_distdir='@:' $(am__post_remove_distdir) # This target untars the dist file and tries a VPATH configuration. Then # it guarantees that the distribution is self-contained by making another # tarfile. distcheck: dist case '$(DIST_ARCHIVES)' in \ *.tar.gz*) \ eval GZIP= gzip $(GZIP_ENV) -dc $(distdir).tar.gz | $(am__untar) ;;\ *.tar.bz2*) \ bzip2 -dc $(distdir).tar.bz2 | $(am__untar) ;;\ *.tar.lz*) \ lzip -dc $(distdir).tar.lz | $(am__untar) ;;\ *.tar.xz*) \ xz -dc $(distdir).tar.xz | $(am__untar) ;;\ *.tar.Z*) \ uncompress -c $(distdir).tar.Z | $(am__untar) ;;\ *.shar.gz*) \ eval GZIP= gzip $(GZIP_ENV) -dc $(distdir).shar.gz | unshar ;;\ *.zip*) \ unzip $(distdir).zip ;;\ esac chmod -R a-w $(distdir) chmod u+w $(distdir) mkdir $(distdir)/_build $(distdir)/_build/sub $(distdir)/_inst chmod a-w $(distdir) test -d $(distdir)/_build || exit 0; \ dc_install_base=`$(am__cd) $(distdir)/_inst && pwd | sed -e 's,^[^:\\/]:[\\/],/,'` \ && dc_destdir="$${TMPDIR-/tmp}/am-dc-$$$$/" \ && am__cwd=`pwd` \ && $(am__cd) $(distdir)/_build/sub \ && ../../configure \ $(AM_DISTCHECK_CONFIGURE_FLAGS) \ $(DISTCHECK_CONFIGURE_FLAGS) \ --srcdir=../.. --prefix="$$dc_install_base" \ && $(MAKE) $(AM_MAKEFLAGS) \ && $(MAKE) $(AM_MAKEFLAGS) dvi \ && $(MAKE) $(AM_MAKEFLAGS) check \ && $(MAKE) $(AM_MAKEFLAGS) install \ && $(MAKE) $(AM_MAKEFLAGS) installcheck \ && $(MAKE) $(AM_MAKEFLAGS) uninstall \ && $(MAKE) $(AM_MAKEFLAGS) distuninstallcheck_dir="$$dc_install_base" \ distuninstallcheck \ && chmod -R a-w "$$dc_install_base" \ && ({ \ (cd ../.. && umask 077 && mkdir "$$dc_destdir") \ && $(MAKE) $(AM_MAKEFLAGS) DESTDIR="$$dc_destdir" install \ && $(MAKE) $(AM_MAKEFLAGS) DESTDIR="$$dc_destdir" uninstall \ && $(MAKE) $(AM_MAKEFLAGS) DESTDIR="$$dc_destdir" \ distuninstallcheck_dir="$$dc_destdir" distuninstallcheck; \ } || { rm -rf "$$dc_destdir"; exit 1; }) \ && rm -rf "$$dc_destdir" \ && $(MAKE) $(AM_MAKEFLAGS) dist \ && rm -rf $(DIST_ARCHIVES) \ && $(MAKE) $(AM_MAKEFLAGS) distcleancheck \ && cd "$$am__cwd" \ || exit 1 $(am__post_remove_distdir) @(echo "$(distdir) archives ready for distribution: "; \ list='$(DIST_ARCHIVES)'; for i in $$list; do echo $$i; done) | \ sed -e 1h -e 1s/./=/g -e 1p -e 1x -e '$$p' -e '$$x' distuninstallcheck: @test -n '$(distuninstallcheck_dir)' || { \ echo 'ERROR: trying to run $@ with an empty' \ '$$(distuninstallcheck_dir)' >&2; \ exit 1; \ }; \ $(am__cd) '$(distuninstallcheck_dir)' || { \ echo 'ERROR: cannot chdir into $(distuninstallcheck_dir)' >&2; \ exit 1; \ }; \ test `$(am__distuninstallcheck_listfiles) | wc -l` -eq 0 \ || { echo "ERROR: files left after uninstall:" ; \ if test -n "$(DESTDIR)"; then \ echo " (check DESTDIR support)"; \ fi ; \ $(distuninstallcheck_listfiles) ; \ exit 1; } >&2 distcleancheck: distclean @if test '$(srcdir)' = . ; then \ echo "ERROR: distcleancheck can only run from a VPATH build" ; \ exit 1 ; \ fi @test `$(distcleancheck_listfiles) | wc -l` -eq 0 \ || { echo "ERROR: files left in build directory after distclean:" ; \ $(distcleancheck_listfiles) ; \ exit 1; } >&2 check-am: all-am $(MAKE) $(AM_MAKEFLAGS) $(check_PROGRAMS) check: $(BUILT_SOURCES) $(MAKE) $(AM_MAKEFLAGS) check-am all-am: Makefile $(PROGRAMS) $(LIBRARIES) $(LTLIBRARIES) $(SCRIPTS) \ $(DATA) $(HEADERS) config.h install-binPROGRAMS: install-libLTLIBRARIES installdirs: for dir in "$(DESTDIR)$(bindir)" "$(DESTDIR)$(sbindir)" "$(DESTDIR)$(libdir)" "$(DESTDIR)$(moduledir)" "$(DESTDIR)$(rcdir)" "$(DESTDIR)$(sbindir)" "$(DESTDIR)$(examplesdir)" "$(DESTDIR)$(yangmodelsdir)" "$(DESTDIR)$(rstman1dir)" "$(DESTDIR)$(rstman8dir)" "$(DESTDIR)$(eigrpdheaderdir)" "$(DESTDIR)$(pkgincludedir)" "$(DESTDIR)$(ospfapiheaderdir)" "$(DESTDIR)$(ospfdheaderdir)" "$(DESTDIR)$(pkgincludedir)"; do \ test -z "$$dir" || $(MKDIR_P) "$$dir"; \ done install: $(BUILT_SOURCES) $(MAKE) $(AM_MAKEFLAGS) install-am install-exec: install-exec-am install-data: install-data-am uninstall: uninstall-am install-am: all-am @$(MAKE) $(AM_MAKEFLAGS) install-exec-am install-data-am installcheck: installcheck-am install-strip: if test -z '$(STRIP)'; then \ $(MAKE) $(AM_MAKEFLAGS) INSTALL_PROGRAM="$(INSTALL_STRIP_PROGRAM)" \ install_sh_PROGRAM="$(INSTALL_STRIP_PROGRAM)" INSTALL_STRIP_FLAG=-s \ install; \ else \ $(MAKE) $(AM_MAKEFLAGS) INSTALL_PROGRAM="$(INSTALL_STRIP_PROGRAM)" \ install_sh_PROGRAM="$(INSTALL_STRIP_PROGRAM)" INSTALL_STRIP_FLAG=-s \ "INSTALL_PROGRAM_ENV=STRIPPROG='$(STRIP)'" install; \ fi mostlyclean-generic: clean-generic: -test -z "$(CLEANFILES)" || rm -f $(CLEANFILES) distclean-generic: -test -z "$(CONFIG_CLEAN_FILES)" || rm -f $(CONFIG_CLEAN_FILES) -test . = "$(srcdir)" || test -z "$(CONFIG_CLEAN_VPATH_FILES)" || rm -f $(CONFIG_CLEAN_VPATH_FILES) -rm -f babeld/$(DEPDIR)/$(am__dirstamp) -rm -f babeld/$(am__dirstamp) -rm -f bfdd/$(DEPDIR)/$(am__dirstamp) -rm -f bfdd/$(am__dirstamp) -rm -f bgpd/$(DEPDIR)/$(am__dirstamp) -rm -f bgpd/$(am__dirstamp) -rm -f bgpd/rfapi/$(DEPDIR)/$(am__dirstamp) -rm -f bgpd/rfapi/$(am__dirstamp) -rm -f bgpd/rfp-example/librfp/$(DEPDIR)/$(am__dirstamp) -rm -f bgpd/rfp-example/librfp/$(am__dirstamp) -rm -f bgpd/rfp-example/rfptest/$(DEPDIR)/$(am__dirstamp) -rm -f bgpd/rfp-example/rfptest/$(am__dirstamp) -rm -f eigrpd/$(DEPDIR)/$(am__dirstamp) -rm -f eigrpd/$(am__dirstamp) -rm -f fpm/$(DEPDIR)/$(am__dirstamp) -rm -f fpm/$(am__dirstamp) -rm -f grpc/$(DEPDIR)/$(am__dirstamp) -rm -f grpc/$(am__dirstamp) -rm -f isisd/$(DEPDIR)/$(am__dirstamp) -rm -f isisd/$(am__dirstamp) -rm -f ldpd/$(DEPDIR)/$(am__dirstamp) -rm -f ldpd/$(am__dirstamp) -rm -f lib/$(DEPDIR)/$(am__dirstamp) -rm -f lib/$(am__dirstamp) -rm -f lib/printf/$(DEPDIR)/$(am__dirstamp) -rm -f lib/printf/$(am__dirstamp) -rm -f nhrpd/$(DEPDIR)/$(am__dirstamp) -rm -f nhrpd/$(am__dirstamp) -rm -f ospf6d/$(DEPDIR)/$(am__dirstamp) -rm -f ospf6d/$(am__dirstamp) -rm -f ospfclient/$(DEPDIR)/$(am__dirstamp) -rm -f ospfclient/$(am__dirstamp) -rm -f ospfd/$(DEPDIR)/$(am__dirstamp) -rm -f ospfd/$(am__dirstamp) -rm -f pbrd/$(DEPDIR)/$(am__dirstamp) -rm -f pbrd/$(am__dirstamp) -rm -f pimd/$(DEPDIR)/$(am__dirstamp) -rm -f pimd/$(am__dirstamp) -rm -f qpb/$(DEPDIR)/$(am__dirstamp) -rm -f qpb/$(am__dirstamp) -rm -f ripd/$(DEPDIR)/$(am__dirstamp) -rm -f ripd/$(am__dirstamp) -rm -f ripngd/$(DEPDIR)/$(am__dirstamp) -rm -f ripngd/$(am__dirstamp) -rm -f sharpd/$(DEPDIR)/$(am__dirstamp) -rm -f sharpd/$(am__dirstamp) -rm -f staticd/$(DEPDIR)/$(am__dirstamp) -rm -f staticd/$(am__dirstamp) -rm -f tests/bgpd/$(DEPDIR)/$(am__dirstamp) -rm -f tests/bgpd/$(am__dirstamp) -rm -f tests/helpers/c/$(DEPDIR)/$(am__dirstamp) -rm -f tests/helpers/c/$(am__dirstamp) -rm -f tests/isisd/$(DEPDIR)/$(am__dirstamp) -rm -f tests/isisd/$(am__dirstamp) -rm -f tests/lib/$(DEPDIR)/$(am__dirstamp) -rm -f tests/lib/$(am__dirstamp) -rm -f tests/lib/cli/$(DEPDIR)/$(am__dirstamp) -rm -f tests/lib/cli/$(am__dirstamp) -rm -f tests/lib/northbound/$(DEPDIR)/$(am__dirstamp) -rm -f tests/lib/northbound/$(am__dirstamp) -rm -f tests/ospf6d/$(DEPDIR)/$(am__dirstamp) -rm -f tests/ospf6d/$(am__dirstamp) -rm -f tools/$(DEPDIR)/$(am__dirstamp) -rm -f tools/$(am__dirstamp) -rm -f vrrpd/$(DEPDIR)/$(am__dirstamp) -rm -f vrrpd/$(am__dirstamp) -rm -f vtysh/$(DEPDIR)/$(am__dirstamp) -rm -f vtysh/$(am__dirstamp) -rm -f watchfrr/$(DEPDIR)/$(am__dirstamp) -rm -f watchfrr/$(am__dirstamp) -rm -f yang/$(DEPDIR)/$(am__dirstamp) -rm -f yang/$(am__dirstamp) -rm -f zebra/$(DEPDIR)/$(am__dirstamp) -rm -f zebra/$(am__dirstamp) -test -z "$(DISTCLEANFILES)" || rm -f $(DISTCLEANFILES) maintainer-clean-generic: @echo "This command is intended for maintainers to use" @echo "it deletes files that may require special tools to rebuild." -rm -f lib/command_lex.c -rm -f lib/command_parse.c -rm -f lib/command_parse.h -rm -f lib/defun_lex.c -test -z "$(BUILT_SOURCES)" || rm -f $(BUILT_SOURCES) clean: clean-am clean-am: clean-binPROGRAMS clean-checkPROGRAMS clean-generic \ clean-libLTLIBRARIES clean-libtool clean-local \ clean-moduleLTLIBRARIES clean-noinstLIBRARIES \ clean-noinstPROGRAMS clean-sbinPROGRAMS mostlyclean-am distclean: distclean-am -rm -f $(am__CONFIG_DISTCLEAN_FILES) -rm -f babeld/$(DEPDIR)/babel_errors.Po -rm -f babeld/$(DEPDIR)/babel_filter.Po -rm -f babeld/$(DEPDIR)/babel_interface.Po -rm -f babeld/$(DEPDIR)/babel_main.Po -rm -f babeld/$(DEPDIR)/babel_zebra.Po -rm -f babeld/$(DEPDIR)/babeld.Po -rm -f babeld/$(DEPDIR)/kernel.Po -rm -f babeld/$(DEPDIR)/message.Po -rm -f babeld/$(DEPDIR)/neighbour.Po -rm -f babeld/$(DEPDIR)/net.Po -rm -f babeld/$(DEPDIR)/resend.Po -rm -f babeld/$(DEPDIR)/route.Po -rm -f babeld/$(DEPDIR)/source.Po -rm -f babeld/$(DEPDIR)/util.Po -rm -f babeld/$(DEPDIR)/xroute.Po -rm -f bfdd/$(DEPDIR)/bfd.Po -rm -f bfdd/$(DEPDIR)/bfd_packet.Po -rm -f bfdd/$(DEPDIR)/bfdd.Po -rm -f bfdd/$(DEPDIR)/bfdd_cli.Po -rm -f bfdd/$(DEPDIR)/bfdd_northbound.Po -rm -f bfdd/$(DEPDIR)/bfdd_vty.Po -rm -f bfdd/$(DEPDIR)/config.Po -rm -f bfdd/$(DEPDIR)/control.Po -rm -f bfdd/$(DEPDIR)/event.Po -rm -f bfdd/$(DEPDIR)/log.Po -rm -f bfdd/$(DEPDIR)/ptm_adapter.Po -rm -f bgpd/$(DEPDIR)/bgp_addpath.Po -rm -f bgpd/$(DEPDIR)/bgp_advertise.Po -rm -f bgpd/$(DEPDIR)/bgp_aspath.Po -rm -f bgpd/$(DEPDIR)/bgp_attr.Po -rm -f bgpd/$(DEPDIR)/bgp_attr_evpn.Po -rm -f bgpd/$(DEPDIR)/bgp_bfd.Po -rm -f bgpd/$(DEPDIR)/bgp_bmp.Plo -rm -f bgpd/$(DEPDIR)/bgp_btoa-bgp_btoa.Po -rm -f bgpd/$(DEPDIR)/bgp_clist.Po -rm -f bgpd/$(DEPDIR)/bgp_community.Po -rm -f bgpd/$(DEPDIR)/bgp_damp.Po -rm -f bgpd/$(DEPDIR)/bgp_debug.Po -rm -f bgpd/$(DEPDIR)/bgp_dump.Po -rm -f bgpd/$(DEPDIR)/bgp_ecommunity.Po -rm -f bgpd/$(DEPDIR)/bgp_encap_tlv.Po -rm -f bgpd/$(DEPDIR)/bgp_errors.Po -rm -f bgpd/$(DEPDIR)/bgp_evpn.Po -rm -f bgpd/$(DEPDIR)/bgp_evpn_vty.Po -rm -f bgpd/$(DEPDIR)/bgp_filter.Po -rm -f bgpd/$(DEPDIR)/bgp_flowspec.Po -rm -f bgpd/$(DEPDIR)/bgp_flowspec_util.Po -rm -f bgpd/$(DEPDIR)/bgp_flowspec_vty.Po -rm -f bgpd/$(DEPDIR)/bgp_fsm.Po -rm -f bgpd/$(DEPDIR)/bgp_io.Po -rm -f bgpd/$(DEPDIR)/bgp_keepalives.Po -rm -f bgpd/$(DEPDIR)/bgp_label.Po -rm -f bgpd/$(DEPDIR)/bgp_labelpool.Po -rm -f bgpd/$(DEPDIR)/bgp_lcommunity.Po -rm -f bgpd/$(DEPDIR)/bgp_mac.Po -rm -f bgpd/$(DEPDIR)/bgp_memory.Po -rm -f bgpd/$(DEPDIR)/bgp_mpath.Po -rm -f bgpd/$(DEPDIR)/bgp_mplsvpn.Po -rm -f bgpd/$(DEPDIR)/bgp_network.Po -rm -f bgpd/$(DEPDIR)/bgp_nexthop.Po -rm -f bgpd/$(DEPDIR)/bgp_nht.Po -rm -f bgpd/$(DEPDIR)/bgp_open.Po -rm -f bgpd/$(DEPDIR)/bgp_packet.Po -rm -f bgpd/$(DEPDIR)/bgp_pbr.Po -rm -f bgpd/$(DEPDIR)/bgp_rd.Po -rm -f bgpd/$(DEPDIR)/bgp_regex.Po -rm -f bgpd/$(DEPDIR)/bgp_route.Po -rm -f bgpd/$(DEPDIR)/bgp_routemap.Po -rm -f bgpd/$(DEPDIR)/bgp_table.Po -rm -f bgpd/$(DEPDIR)/bgp_updgrp.Po -rm -f bgpd/$(DEPDIR)/bgp_updgrp_adv.Po -rm -f bgpd/$(DEPDIR)/bgp_updgrp_packet.Po -rm -f bgpd/$(DEPDIR)/bgp_vpn.Po -rm -f bgpd/$(DEPDIR)/bgp_vty.Po -rm -f bgpd/$(DEPDIR)/bgp_zebra.Po -rm -f bgpd/$(DEPDIR)/bgpd-bgp_main.Po -rm -f bgpd/$(DEPDIR)/bgpd.Po -rm -f bgpd/$(DEPDIR)/bgpd_rpki_la-bgp_rpki.Plo -rm -f bgpd/$(DEPDIR)/bgpd_snmp_la-bgp_snmp.Plo -rm -f bgpd/rfapi/$(DEPDIR)/bgp_btoa-rfapi_descriptor_rfp_utils.Po -rm -f bgpd/rfapi/$(DEPDIR)/bgp_rfapi_cfg.Po -rm -f bgpd/rfapi/$(DEPDIR)/bgpd-rfapi_descriptor_rfp_utils.Po -rm -f bgpd/rfapi/$(DEPDIR)/rfapi.Po -rm -f bgpd/rfapi/$(DEPDIR)/rfapi_ap.Po -rm -f bgpd/rfapi/$(DEPDIR)/rfapi_descriptor_rfp_utils.Po -rm -f bgpd/rfapi/$(DEPDIR)/rfapi_encap_tlv.Po -rm -f bgpd/rfapi/$(DEPDIR)/rfapi_import.Po -rm -f bgpd/rfapi/$(DEPDIR)/rfapi_monitor.Po -rm -f bgpd/rfapi/$(DEPDIR)/rfapi_nve_addr.Po -rm -f bgpd/rfapi/$(DEPDIR)/rfapi_rib.Po -rm -f bgpd/rfapi/$(DEPDIR)/rfapi_vty.Po -rm -f bgpd/rfapi/$(DEPDIR)/vnc_debug.Po -rm -f bgpd/rfapi/$(DEPDIR)/vnc_export_bgp.Po -rm -f bgpd/rfapi/$(DEPDIR)/vnc_export_table.Po -rm -f bgpd/rfapi/$(DEPDIR)/vnc_import_bgp.Po -rm -f bgpd/rfapi/$(DEPDIR)/vnc_zebra.Po -rm -f bgpd/rfp-example/librfp/$(DEPDIR)/rfp_example.Po -rm -f bgpd/rfp-example/rfptest/$(DEPDIR)/rfptest-rfptest.Po -rm -f eigrpd/$(DEPDIR)/eigrp_cli.Po -rm -f eigrpd/$(DEPDIR)/eigrp_dump.Po -rm -f eigrpd/$(DEPDIR)/eigrp_errors.Po -rm -f eigrpd/$(DEPDIR)/eigrp_filter.Po -rm -f eigrpd/$(DEPDIR)/eigrp_fsm.Po -rm -f eigrpd/$(DEPDIR)/eigrp_hello.Po -rm -f eigrpd/$(DEPDIR)/eigrp_interface.Po -rm -f eigrpd/$(DEPDIR)/eigrp_main.Po -rm -f eigrpd/$(DEPDIR)/eigrp_memory.Po -rm -f eigrpd/$(DEPDIR)/eigrp_neighbor.Po -rm -f eigrpd/$(DEPDIR)/eigrp_network.Po -rm -f eigrpd/$(DEPDIR)/eigrp_northbound.Po -rm -f eigrpd/$(DEPDIR)/eigrp_packet.Po -rm -f eigrpd/$(DEPDIR)/eigrp_query.Po -rm -f eigrpd/$(DEPDIR)/eigrp_reply.Po -rm -f eigrpd/$(DEPDIR)/eigrp_siaquery.Po -rm -f eigrpd/$(DEPDIR)/eigrp_siareply.Po -rm -f eigrpd/$(DEPDIR)/eigrp_snmp.Po -rm -f eigrpd/$(DEPDIR)/eigrp_topology.Po -rm -f eigrpd/$(DEPDIR)/eigrp_update.Po -rm -f eigrpd/$(DEPDIR)/eigrp_vrf.Po -rm -f eigrpd/$(DEPDIR)/eigrp_vty.Po -rm -f eigrpd/$(DEPDIR)/eigrp_zebra.Po -rm -f eigrpd/$(DEPDIR)/eigrpd.Po -rm -f fpm/$(DEPDIR)/libfrrfpm_pb_la-fpm.pb-c.Plo -rm -f fpm/$(DEPDIR)/libfrrfpm_pb_la-fpm_pb.Plo -rm -f grpc/$(DEPDIR)/libfrrgrpc_pb_la-frr-northbound.grpc.pb.Plo -rm -f grpc/$(DEPDIR)/libfrrgrpc_pb_la-frr-northbound.pb.Plo -rm -f isisd/$(DEPDIR)/fabricd-isis_bpf.Po -rm -f isisd/$(DEPDIR)/fabricd-isis_dlpi.Po -rm -f isisd/$(DEPDIR)/fabricd-isis_main.Po -rm -f isisd/$(DEPDIR)/fabricd-isis_pfpacket.Po -rm -f isisd/$(DEPDIR)/fabricd.Po -rm -f isisd/$(DEPDIR)/isis_adjacency.Po -rm -f isisd/$(DEPDIR)/isis_bfd.Po -rm -f isisd/$(DEPDIR)/isis_bpf.Po -rm -f isisd/$(DEPDIR)/isis_circuit.Po -rm -f isisd/$(DEPDIR)/isis_cli.Po -rm -f isisd/$(DEPDIR)/isis_csm.Po -rm -f isisd/$(DEPDIR)/isis_dlpi.Po -rm -f isisd/$(DEPDIR)/isis_dr.Po -rm -f isisd/$(DEPDIR)/isis_dynhn.Po -rm -f isisd/$(DEPDIR)/isis_errors.Po -rm -f isisd/$(DEPDIR)/isis_events.Po -rm -f isisd/$(DEPDIR)/isis_flags.Po -rm -f isisd/$(DEPDIR)/isis_lsp.Po -rm -f isisd/$(DEPDIR)/isis_main.Po -rm -f isisd/$(DEPDIR)/isis_memory.Po -rm -f isisd/$(DEPDIR)/isis_misc.Po -rm -f isisd/$(DEPDIR)/isis_mt.Po -rm -f isisd/$(DEPDIR)/isis_northbound.Po -rm -f isisd/$(DEPDIR)/isis_pdu.Po -rm -f isisd/$(DEPDIR)/isis_pdu_counter.Po -rm -f isisd/$(DEPDIR)/isis_pfpacket.Po -rm -f isisd/$(DEPDIR)/isis_redist.Po -rm -f isisd/$(DEPDIR)/isis_route.Po -rm -f isisd/$(DEPDIR)/isis_routemap.Po -rm -f isisd/$(DEPDIR)/isis_spf.Po -rm -f isisd/$(DEPDIR)/isis_te.Po -rm -f isisd/$(DEPDIR)/isis_tlvs.Po -rm -f isisd/$(DEPDIR)/isis_tx_queue.Po -rm -f isisd/$(DEPDIR)/isis_zebra.Po -rm -f isisd/$(DEPDIR)/isisd.Po -rm -f isisd/$(DEPDIR)/iso_checksum.Po -rm -f isisd/$(DEPDIR)/libfabric_a-fabricd.Po -rm -f isisd/$(DEPDIR)/libfabric_a-isis_adjacency.Po -rm -f isisd/$(DEPDIR)/libfabric_a-isis_bfd.Po -rm -f isisd/$(DEPDIR)/libfabric_a-isis_circuit.Po -rm -f isisd/$(DEPDIR)/libfabric_a-isis_csm.Po -rm -f isisd/$(DEPDIR)/libfabric_a-isis_dr.Po -rm -f isisd/$(DEPDIR)/libfabric_a-isis_dynhn.Po -rm -f isisd/$(DEPDIR)/libfabric_a-isis_errors.Po -rm -f isisd/$(DEPDIR)/libfabric_a-isis_events.Po -rm -f isisd/$(DEPDIR)/libfabric_a-isis_flags.Po -rm -f isisd/$(DEPDIR)/libfabric_a-isis_lsp.Po -rm -f isisd/$(DEPDIR)/libfabric_a-isis_memory.Po -rm -f isisd/$(DEPDIR)/libfabric_a-isis_misc.Po -rm -f isisd/$(DEPDIR)/libfabric_a-isis_mt.Po -rm -f isisd/$(DEPDIR)/libfabric_a-isis_pdu.Po -rm -f isisd/$(DEPDIR)/libfabric_a-isis_pdu_counter.Po -rm -f isisd/$(DEPDIR)/libfabric_a-isis_redist.Po -rm -f isisd/$(DEPDIR)/libfabric_a-isis_route.Po -rm -f isisd/$(DEPDIR)/libfabric_a-isis_routemap.Po -rm -f isisd/$(DEPDIR)/libfabric_a-isis_spf.Po -rm -f isisd/$(DEPDIR)/libfabric_a-isis_te.Po -rm -f isisd/$(DEPDIR)/libfabric_a-isis_tlvs.Po -rm -f isisd/$(DEPDIR)/libfabric_a-isis_tx_queue.Po -rm -f isisd/$(DEPDIR)/libfabric_a-isis_vty_fabricd.Po -rm -f isisd/$(DEPDIR)/libfabric_a-isis_zebra.Po -rm -f isisd/$(DEPDIR)/libfabric_a-isisd.Po -rm -f isisd/$(DEPDIR)/libfabric_a-iso_checksum.Po -rm -f ldpd/$(DEPDIR)/accept.Po -rm -f ldpd/$(DEPDIR)/address.Po -rm -f ldpd/$(DEPDIR)/adjacency.Po -rm -f ldpd/$(DEPDIR)/control.Po -rm -f ldpd/$(DEPDIR)/hello.Po -rm -f ldpd/$(DEPDIR)/init.Po -rm -f ldpd/$(DEPDIR)/interface.Po -rm -f ldpd/$(DEPDIR)/keepalive.Po -rm -f ldpd/$(DEPDIR)/l2vpn.Po -rm -f ldpd/$(DEPDIR)/labelmapping.Po -rm -f ldpd/$(DEPDIR)/lde.Po -rm -f ldpd/$(DEPDIR)/lde_lib.Po -rm -f ldpd/$(DEPDIR)/ldp_debug.Po -rm -f ldpd/$(DEPDIR)/ldp_vty_cmds.Po -rm -f ldpd/$(DEPDIR)/ldp_vty_conf.Po -rm -f ldpd/$(DEPDIR)/ldp_vty_exec.Po -rm -f ldpd/$(DEPDIR)/ldp_zebra.Po -rm -f ldpd/$(DEPDIR)/ldpd.Po -rm -f ldpd/$(DEPDIR)/ldpe.Po -rm -f ldpd/$(DEPDIR)/log.Po -rm -f ldpd/$(DEPDIR)/logmsg.Po -rm -f ldpd/$(DEPDIR)/neighbor.Po -rm -f ldpd/$(DEPDIR)/notification.Po -rm -f ldpd/$(DEPDIR)/packet.Po -rm -f ldpd/$(DEPDIR)/pfkey.Po -rm -f ldpd/$(DEPDIR)/socket.Po -rm -f ldpd/$(DEPDIR)/util.Po -rm -f lib/$(DEPDIR)/agg_table.Plo -rm -f lib/$(DEPDIR)/atomlist.Plo -rm -f lib/$(DEPDIR)/bfd.Plo -rm -f lib/$(DEPDIR)/buffer.Plo -rm -f lib/$(DEPDIR)/checksum.Plo -rm -f lib/$(DEPDIR)/clippy-clippy.Po -rm -f lib/$(DEPDIR)/clippy-command_graph.Po -rm -f lib/$(DEPDIR)/clippy-command_lex.Po -rm -f lib/$(DEPDIR)/clippy-command_parse.Po -rm -f lib/$(DEPDIR)/clippy-command_py.Po -rm -f lib/$(DEPDIR)/clippy-defun_lex.Po -rm -f lib/$(DEPDIR)/clippy-graph.Po -rm -f lib/$(DEPDIR)/clippy-memory.Po -rm -f lib/$(DEPDIR)/clippy-vector.Po -rm -f lib/$(DEPDIR)/command.Plo -rm -f lib/$(DEPDIR)/command_graph.Plo -rm -f lib/$(DEPDIR)/command_lex.Plo -rm -f lib/$(DEPDIR)/command_match.Plo -rm -f lib/$(DEPDIR)/command_parse.Plo -rm -f lib/$(DEPDIR)/confd_la-northbound_confd.Plo -rm -f lib/$(DEPDIR)/csv.Plo -rm -f lib/$(DEPDIR)/db.Plo -rm -f lib/$(DEPDIR)/debug.Plo -rm -f lib/$(DEPDIR)/distribute.Plo -rm -f lib/$(DEPDIR)/ferr.Plo -rm -f lib/$(DEPDIR)/filter.Plo -rm -f lib/$(DEPDIR)/frr_pthread.Plo -rm -f lib/$(DEPDIR)/frrcu.Plo -rm -f lib/$(DEPDIR)/frrlua.Plo -rm -f lib/$(DEPDIR)/frrstr.Plo -rm -f lib/$(DEPDIR)/getopt.Plo -rm -f lib/$(DEPDIR)/getopt1.Plo -rm -f lib/$(DEPDIR)/grammar_sandbox.Plo -rm -f lib/$(DEPDIR)/grammar_sandbox_main.Po -rm -f lib/$(DEPDIR)/graph.Plo -rm -f lib/$(DEPDIR)/grpc_la-northbound_grpc.Plo -rm -f lib/$(DEPDIR)/hash.Plo -rm -f lib/$(DEPDIR)/hook.Plo -rm -f lib/$(DEPDIR)/id_alloc.Plo -rm -f lib/$(DEPDIR)/if.Plo -rm -f lib/$(DEPDIR)/if_rmap.Plo -rm -f lib/$(DEPDIR)/imsg-buffer.Plo -rm -f lib/$(DEPDIR)/imsg.Plo -rm -f lib/$(DEPDIR)/jhash.Plo -rm -f lib/$(DEPDIR)/json.Plo -rm -f lib/$(DEPDIR)/keychain.Plo -rm -f lib/$(DEPDIR)/lib_errors.Plo -rm -f lib/$(DEPDIR)/libfrr.Plo -rm -f lib/$(DEPDIR)/libfrrcares_la-resolver.Plo -rm -f lib/$(DEPDIR)/libfrrsnmp_la-agentx.Plo -rm -f lib/$(DEPDIR)/libfrrsnmp_la-snmp.Plo -rm -f lib/$(DEPDIR)/libfrrzmq_la-frr_zmq.Plo -rm -f lib/$(DEPDIR)/linklist.Plo -rm -f lib/$(DEPDIR)/log.Plo -rm -f lib/$(DEPDIR)/log_vty.Plo -rm -f lib/$(DEPDIR)/md5.Plo -rm -f lib/$(DEPDIR)/memory.Plo -rm -f lib/$(DEPDIR)/memory_vty.Plo -rm -f lib/$(DEPDIR)/mlag.Plo -rm -f lib/$(DEPDIR)/module.Plo -rm -f lib/$(DEPDIR)/mpls.Plo -rm -f lib/$(DEPDIR)/netns_linux.Plo -rm -f lib/$(DEPDIR)/netns_other.Plo -rm -f lib/$(DEPDIR)/network.Plo -rm -f lib/$(DEPDIR)/nexthop.Plo -rm -f lib/$(DEPDIR)/nexthop_group.Plo -rm -f lib/$(DEPDIR)/northbound.Plo -rm -f lib/$(DEPDIR)/northbound_cli.Plo -rm -f lib/$(DEPDIR)/northbound_db.Plo -rm -f lib/$(DEPDIR)/ntop.Plo -rm -f lib/$(DEPDIR)/openbsd-tree.Plo -rm -f lib/$(DEPDIR)/pid_output.Plo -rm -f lib/$(DEPDIR)/plist.Plo -rm -f lib/$(DEPDIR)/prefix.Plo -rm -f lib/$(DEPDIR)/privs.Plo -rm -f lib/$(DEPDIR)/ptm_lib.Plo -rm -f lib/$(DEPDIR)/pullwr.Plo -rm -f lib/$(DEPDIR)/qobj.Plo -rm -f lib/$(DEPDIR)/ringbuf.Plo -rm -f lib/$(DEPDIR)/routemap.Plo -rm -f lib/$(DEPDIR)/sbuf.Plo -rm -f lib/$(DEPDIR)/seqlock.Plo -rm -f lib/$(DEPDIR)/sha256.Plo -rm -f lib/$(DEPDIR)/sigevent.Plo -rm -f lib/$(DEPDIR)/skiplist.Plo -rm -f lib/$(DEPDIR)/sockopt.Plo -rm -f lib/$(DEPDIR)/sockunion.Plo -rm -f lib/$(DEPDIR)/spf_backoff.Plo -rm -f lib/$(DEPDIR)/srcdest_table.Plo -rm -f lib/$(DEPDIR)/stream.Plo -rm -f lib/$(DEPDIR)/strlcat.Plo -rm -f lib/$(DEPDIR)/strlcpy.Plo -rm -f lib/$(DEPDIR)/sysrepo_la-northbound_sysrepo.Plo -rm -f lib/$(DEPDIR)/systemd.Plo -rm -f lib/$(DEPDIR)/table.Plo -rm -f lib/$(DEPDIR)/termtable.Plo -rm -f lib/$(DEPDIR)/thread.Plo -rm -f lib/$(DEPDIR)/typerb.Plo -rm -f lib/$(DEPDIR)/typesafe.Plo -rm -f lib/$(DEPDIR)/vector.Plo -rm -f lib/$(DEPDIR)/vrf.Plo -rm -f lib/$(DEPDIR)/vty.Plo -rm -f lib/$(DEPDIR)/wheel.Plo -rm -f lib/$(DEPDIR)/workqueue.Plo -rm -f lib/$(DEPDIR)/yang.Plo -rm -f lib/$(DEPDIR)/yang_translator.Plo -rm -f lib/$(DEPDIR)/yang_wrappers.Plo -rm -f lib/$(DEPDIR)/zclient.Plo -rm -f lib/printf/$(DEPDIR)/glue.Plo -rm -f lib/printf/$(DEPDIR)/printf-pos.Plo -rm -f lib/printf/$(DEPDIR)/vfprintf.Plo -rm -f nhrpd/$(DEPDIR)/linux.Po -rm -f nhrpd/$(DEPDIR)/netlink_arp.Po -rm -f nhrpd/$(DEPDIR)/netlink_gre.Po -rm -f nhrpd/$(DEPDIR)/nhrp_cache.Po -rm -f nhrpd/$(DEPDIR)/nhrp_errors.Po -rm -f nhrpd/$(DEPDIR)/nhrp_event.Po -rm -f nhrpd/$(DEPDIR)/nhrp_interface.Po -rm -f nhrpd/$(DEPDIR)/nhrp_main.Po -rm -f nhrpd/$(DEPDIR)/nhrp_nhs.Po -rm -f nhrpd/$(DEPDIR)/nhrp_packet.Po -rm -f nhrpd/$(DEPDIR)/nhrp_peer.Po -rm -f nhrpd/$(DEPDIR)/nhrp_route.Po -rm -f nhrpd/$(DEPDIR)/nhrp_shortcut.Po -rm -f nhrpd/$(DEPDIR)/nhrp_vc.Po -rm -f nhrpd/$(DEPDIR)/nhrp_vty.Po -rm -f nhrpd/$(DEPDIR)/reqid.Po -rm -f nhrpd/$(DEPDIR)/vici.Po -rm -f nhrpd/$(DEPDIR)/zbuf.Po -rm -f nhrpd/$(DEPDIR)/znl.Po -rm -f ospf6d/$(DEPDIR)/ospf6_abr.Po -rm -f ospf6d/$(DEPDIR)/ospf6_area.Po -rm -f ospf6d/$(DEPDIR)/ospf6_asbr.Po -rm -f ospf6d/$(DEPDIR)/ospf6_bfd.Po -rm -f ospf6d/$(DEPDIR)/ospf6_flood.Po -rm -f ospf6d/$(DEPDIR)/ospf6_interface.Po -rm -f ospf6d/$(DEPDIR)/ospf6_intra.Po -rm -f ospf6d/$(DEPDIR)/ospf6_lsa.Po -rm -f ospf6d/$(DEPDIR)/ospf6_lsdb.Po -rm -f ospf6d/$(DEPDIR)/ospf6_main.Po -rm -f ospf6d/$(DEPDIR)/ospf6_memory.Po -rm -f ospf6d/$(DEPDIR)/ospf6_message.Po -rm -f ospf6d/$(DEPDIR)/ospf6_neighbor.Po -rm -f ospf6d/$(DEPDIR)/ospf6_network.Po -rm -f ospf6d/$(DEPDIR)/ospf6_proto.Po -rm -f ospf6d/$(DEPDIR)/ospf6_route.Po -rm -f ospf6d/$(DEPDIR)/ospf6_spf.Po -rm -f ospf6d/$(DEPDIR)/ospf6_top.Po -rm -f ospf6d/$(DEPDIR)/ospf6_zebra.Po -rm -f ospf6d/$(DEPDIR)/ospf6d.Po -rm -f ospf6d/$(DEPDIR)/ospf6d_snmp_la-ospf6_snmp.Plo -rm -f ospfclient/$(DEPDIR)/ospf_apiclient.Plo -rm -f ospfclient/$(DEPDIR)/ospfclient.Po -rm -f ospfd/$(DEPDIR)/ospf_abr.Po -rm -f ospfd/$(DEPDIR)/ospf_api.Po -rm -f ospfd/$(DEPDIR)/ospf_apiserver.Po -rm -f ospfd/$(DEPDIR)/ospf_asbr.Po -rm -f ospfd/$(DEPDIR)/ospf_ase.Po -rm -f ospfd/$(DEPDIR)/ospf_bfd.Po -rm -f ospfd/$(DEPDIR)/ospf_dump.Po -rm -f ospfd/$(DEPDIR)/ospf_dump_api.Po -rm -f ospfd/$(DEPDIR)/ospf_errors.Po -rm -f ospfd/$(DEPDIR)/ospf_ext.Po -rm -f ospfd/$(DEPDIR)/ospf_flood.Po -rm -f ospfd/$(DEPDIR)/ospf_ia.Po -rm -f ospfd/$(DEPDIR)/ospf_interface.Po -rm -f ospfd/$(DEPDIR)/ospf_ism.Po -rm -f ospfd/$(DEPDIR)/ospf_lsa.Po -rm -f ospfd/$(DEPDIR)/ospf_lsdb.Po -rm -f ospfd/$(DEPDIR)/ospf_main.Po -rm -f ospfd/$(DEPDIR)/ospf_memory.Po -rm -f ospfd/$(DEPDIR)/ospf_neighbor.Po -rm -f ospfd/$(DEPDIR)/ospf_network.Po -rm -f ospfd/$(DEPDIR)/ospf_nsm.Po -rm -f ospfd/$(DEPDIR)/ospf_opaque.Po -rm -f ospfd/$(DEPDIR)/ospf_packet.Po -rm -f ospfd/$(DEPDIR)/ospf_ri.Po -rm -f ospfd/$(DEPDIR)/ospf_route.Po -rm -f ospfd/$(DEPDIR)/ospf_routemap.Po -rm -f ospfd/$(DEPDIR)/ospf_spf.Po -rm -f ospfd/$(DEPDIR)/ospf_sr.Po -rm -f ospfd/$(DEPDIR)/ospf_te.Po -rm -f ospfd/$(DEPDIR)/ospf_vty.Po -rm -f ospfd/$(DEPDIR)/ospf_zebra.Po -rm -f ospfd/$(DEPDIR)/ospfd.Po -rm -f ospfd/$(DEPDIR)/ospfd_snmp_la-ospf_snmp.Plo -rm -f pbrd/$(DEPDIR)/pbr_debug.Po -rm -f pbrd/$(DEPDIR)/pbr_main.Po -rm -f pbrd/$(DEPDIR)/pbr_map.Po -rm -f pbrd/$(DEPDIR)/pbr_memory.Po -rm -f pbrd/$(DEPDIR)/pbr_nht.Po -rm -f pbrd/$(DEPDIR)/pbr_vty.Po -rm -f pbrd/$(DEPDIR)/pbr_zebra.Po -rm -f pimd/$(DEPDIR)/mtracebis.Po -rm -f pimd/$(DEPDIR)/mtracebis_netlink.Po -rm -f pimd/$(DEPDIR)/mtracebis_routeget.Po -rm -f pimd/$(DEPDIR)/pim_assert.Po -rm -f pimd/$(DEPDIR)/pim_bfd.Po -rm -f pimd/$(DEPDIR)/pim_br.Po -rm -f pimd/$(DEPDIR)/pim_bsm.Po -rm -f pimd/$(DEPDIR)/pim_cmd.Po -rm -f pimd/$(DEPDIR)/pim_errors.Po -rm -f pimd/$(DEPDIR)/pim_hello.Po -rm -f pimd/$(DEPDIR)/pim_iface.Po -rm -f pimd/$(DEPDIR)/pim_ifchannel.Po -rm -f pimd/$(DEPDIR)/pim_igmp.Po -rm -f pimd/$(DEPDIR)/pim_igmp_mtrace.Po -rm -f pimd/$(DEPDIR)/pim_igmp_stats.Po -rm -f pimd/$(DEPDIR)/pim_igmpv2.Po -rm -f pimd/$(DEPDIR)/pim_igmpv3.Po -rm -f pimd/$(DEPDIR)/pim_instance.Po -rm -f pimd/$(DEPDIR)/pim_int.Po -rm -f pimd/$(DEPDIR)/pim_join.Po -rm -f pimd/$(DEPDIR)/pim_jp_agg.Po -rm -f pimd/$(DEPDIR)/pim_macro.Po -rm -f pimd/$(DEPDIR)/pim_main.Po -rm -f pimd/$(DEPDIR)/pim_memory.Po -rm -f pimd/$(DEPDIR)/pim_mroute.Po -rm -f pimd/$(DEPDIR)/pim_msdp.Po -rm -f pimd/$(DEPDIR)/pim_msdp_packet.Po -rm -f pimd/$(DEPDIR)/pim_msdp_socket.Po -rm -f pimd/$(DEPDIR)/pim_msg.Po -rm -f pimd/$(DEPDIR)/pim_neighbor.Po -rm -f pimd/$(DEPDIR)/pim_nht.Po -rm -f pimd/$(DEPDIR)/pim_oil.Po -rm -f pimd/$(DEPDIR)/pim_pim.Po -rm -f pimd/$(DEPDIR)/pim_register.Po -rm -f pimd/$(DEPDIR)/pim_routemap.Po -rm -f pimd/$(DEPDIR)/pim_rp.Po -rm -f pimd/$(DEPDIR)/pim_rpf.Po -rm -f pimd/$(DEPDIR)/pim_signals.Po -rm -f pimd/$(DEPDIR)/pim_sock.Po -rm -f pimd/$(DEPDIR)/pim_ssm.Po -rm -f pimd/$(DEPDIR)/pim_ssmpingd.Po -rm -f pimd/$(DEPDIR)/pim_static.Po -rm -f pimd/$(DEPDIR)/pim_str.Po -rm -f pimd/$(DEPDIR)/pim_time.Po -rm -f pimd/$(DEPDIR)/pim_tlv.Po -rm -f pimd/$(DEPDIR)/pim_upstream.Po -rm -f pimd/$(DEPDIR)/pim_util.Po -rm -f pimd/$(DEPDIR)/pim_version.Po -rm -f pimd/$(DEPDIR)/pim_vty.Po -rm -f pimd/$(DEPDIR)/pim_vxlan.Po -rm -f pimd/$(DEPDIR)/pim_zebra.Po -rm -f pimd/$(DEPDIR)/pim_zlookup.Po -rm -f pimd/$(DEPDIR)/pimd.Po -rm -f pimd/$(DEPDIR)/test_igmpv3_join.Po -rm -f qpb/$(DEPDIR)/libfrr_pb_la-qpb.Plo -rm -f qpb/$(DEPDIR)/libfrr_pb_la-qpb.pb-c.Plo -rm -f qpb/$(DEPDIR)/libfrr_pb_la-qpb_allocator.Plo -rm -f ripd/$(DEPDIR)/rip_cli.Po -rm -f ripd/$(DEPDIR)/rip_debug.Po -rm -f ripd/$(DEPDIR)/rip_errors.Po -rm -f ripd/$(DEPDIR)/rip_interface.Po -rm -f ripd/$(DEPDIR)/rip_main.Po -rm -f ripd/$(DEPDIR)/rip_northbound.Po -rm -f ripd/$(DEPDIR)/rip_offset.Po -rm -f ripd/$(DEPDIR)/rip_peer.Po -rm -f ripd/$(DEPDIR)/rip_routemap.Po -rm -f ripd/$(DEPDIR)/rip_zebra.Po -rm -f ripd/$(DEPDIR)/ripd.Po -rm -f ripd/$(DEPDIR)/ripd_snmp_la-rip_snmp.Plo -rm -f ripngd/$(DEPDIR)/ripng_cli.Po -rm -f ripngd/$(DEPDIR)/ripng_debug.Po -rm -f ripngd/$(DEPDIR)/ripng_interface.Po -rm -f ripngd/$(DEPDIR)/ripng_main.Po -rm -f ripngd/$(DEPDIR)/ripng_nexthop.Po -rm -f ripngd/$(DEPDIR)/ripng_northbound.Po -rm -f ripngd/$(DEPDIR)/ripng_offset.Po -rm -f ripngd/$(DEPDIR)/ripng_peer.Po -rm -f ripngd/$(DEPDIR)/ripng_route.Po -rm -f ripngd/$(DEPDIR)/ripng_routemap.Po -rm -f ripngd/$(DEPDIR)/ripng_zebra.Po -rm -f ripngd/$(DEPDIR)/ripngd.Po -rm -f sharpd/$(DEPDIR)/sharp_main.Po -rm -f sharpd/$(DEPDIR)/sharp_nht.Po -rm -f sharpd/$(DEPDIR)/sharp_vty.Po -rm -f sharpd/$(DEPDIR)/sharp_zebra.Po -rm -f staticd/$(DEPDIR)/static_main.Po -rm -f staticd/$(DEPDIR)/static_memory.Po -rm -f staticd/$(DEPDIR)/static_nht.Po -rm -f staticd/$(DEPDIR)/static_routes.Po -rm -f staticd/$(DEPDIR)/static_vrf.Po -rm -f staticd/$(DEPDIR)/static_vty.Po -rm -f staticd/$(DEPDIR)/static_zebra.Po -rm -f tests/bgpd/$(DEPDIR)/test_aspath-test_aspath.Po -rm -f tests/bgpd/$(DEPDIR)/test_bgp_table-test_bgp_table.Po -rm -f tests/bgpd/$(DEPDIR)/test_capability-test_capability.Po -rm -f tests/bgpd/$(DEPDIR)/test_ecommunity-test_ecommunity.Po -rm -f tests/bgpd/$(DEPDIR)/test_mp_attr-test_mp_attr.Po -rm -f tests/bgpd/$(DEPDIR)/test_mpath-test_mpath.Po -rm -f tests/bgpd/$(DEPDIR)/test_packet-test_packet.Po -rm -f tests/bgpd/$(DEPDIR)/test_peer_attr-test_peer_attr.Po -rm -f tests/helpers/c/$(DEPDIR)/lib_cli_test_commands-prng.Po -rm -f tests/helpers/c/$(DEPDIR)/lib_test_heavy-main.Po -rm -f tests/helpers/c/$(DEPDIR)/lib_test_heavy_thread-main.Po -rm -f tests/helpers/c/$(DEPDIR)/lib_test_heavy_wq-main.Po -rm -f tests/helpers/c/$(DEPDIR)/lib_test_nexthop_iter-prng.Po -rm -f tests/helpers/c/$(DEPDIR)/lib_test_ntop-prng.Po -rm -f tests/helpers/c/$(DEPDIR)/lib_test_prefix2str-prng.Po -rm -f tests/helpers/c/$(DEPDIR)/lib_test_srcdest_table-prng.Po -rm -f tests/helpers/c/$(DEPDIR)/lib_test_timer_correctness-prng.Po -rm -f tests/helpers/c/$(DEPDIR)/lib_test_timer_performance-prng.Po -rm -f tests/helpers/c/$(DEPDIR)/lib_test_typelist-prng.Po -rm -f tests/isisd/$(DEPDIR)/test_fuzz_isis_tlv-test_fuzz_isis_tlv.Po -rm -f tests/isisd/$(DEPDIR)/test_isis_lspdb-test_isis_lspdb.Po -rm -f tests/isisd/$(DEPDIR)/test_isis_vertex_queue-test_isis_vertex_queue.Po -rm -f tests/lib/$(DEPDIR)/cxxcompat-cxxcompat.Po -rm -f tests/lib/$(DEPDIR)/test_atomlist-test_atomlist.Po -rm -f tests/lib/$(DEPDIR)/test_buffer-test_buffer.Po -rm -f tests/lib/$(DEPDIR)/test_checksum-test_checksum.Po -rm -f tests/lib/$(DEPDIR)/test_graph-test_graph.Po -rm -f tests/lib/$(DEPDIR)/test_heavy-test_heavy.Po -rm -f tests/lib/$(DEPDIR)/test_heavy_thread-test_heavy_thread.Po -rm -f tests/lib/$(DEPDIR)/test_heavy_wq-test_heavy_wq.Po -rm -f tests/lib/$(DEPDIR)/test_idalloc-test_idalloc.Po -rm -f tests/lib/$(DEPDIR)/test_memory-test_memory.Po -rm -f tests/lib/$(DEPDIR)/test_nexthop_iter-test_nexthop_iter.Po -rm -f tests/lib/$(DEPDIR)/test_ntop-test_ntop.Po -rm -f tests/lib/$(DEPDIR)/test_prefix2str-test_prefix2str.Po -rm -f tests/lib/$(DEPDIR)/test_printfrr-test_printfrr.Po -rm -f tests/lib/$(DEPDIR)/test_privs-test_privs.Po -rm -f tests/lib/$(DEPDIR)/test_ringbuf-test_ringbuf.Po -rm -f tests/lib/$(DEPDIR)/test_segv-test_segv.Po -rm -f tests/lib/$(DEPDIR)/test_seqlock-test_seqlock.Po -rm -f tests/lib/$(DEPDIR)/test_sig-test_sig.Po -rm -f tests/lib/$(DEPDIR)/test_srcdest_table-test_srcdest_table.Po -rm -f tests/lib/$(DEPDIR)/test_stream-test_stream.Po -rm -f tests/lib/$(DEPDIR)/test_table-test_table.Po -rm -f tests/lib/$(DEPDIR)/test_timer_correctness-test_timer_correctness.Po -rm -f tests/lib/$(DEPDIR)/test_timer_performance-test_timer_performance.Po -rm -f tests/lib/$(DEPDIR)/test_ttable-test_ttable.Po -rm -f tests/lib/$(DEPDIR)/test_typelist-test_typelist.Po -rm -f tests/lib/$(DEPDIR)/test_zlog-test_zlog.Po -rm -f tests/lib/$(DEPDIR)/test_zmq-test_zmq.Po -rm -f tests/lib/cli/$(DEPDIR)/ospf6d_test_lsdb-common_cli.Po -rm -f tests/lib/cli/$(DEPDIR)/test_cli-common_cli.Po -rm -f tests/lib/cli/$(DEPDIR)/test_cli-test_cli.Po -rm -f tests/lib/cli/$(DEPDIR)/test_commands-test_commands.Po -rm -f tests/lib/cli/$(DEPDIR)/test_commands-test_commands_defun.Po -rm -f tests/lib/northbound/$(DEPDIR)/test_oper_data-test_oper_data.Po -rm -f tests/ospf6d/$(DEPDIR)/test_lsdb-test_lsdb.Po -rm -f tools/$(DEPDIR)/gen_northbound_callbacks.Po -rm -f tools/$(DEPDIR)/gen_yang_deviations.Po -rm -f tools/$(DEPDIR)/permutations.Po -rm -f tools/$(DEPDIR)/start-stop-daemon.Po -rm -f vrrpd/$(DEPDIR)/vrrp.Po -rm -f vrrpd/$(DEPDIR)/vrrp_arp.Po -rm -f vrrpd/$(DEPDIR)/vrrp_debug.Po -rm -f vrrpd/$(DEPDIR)/vrrp_main.Po -rm -f vrrpd/$(DEPDIR)/vrrp_ndisc.Po -rm -f vrrpd/$(DEPDIR)/vrrp_packet.Po -rm -f vrrpd/$(DEPDIR)/vrrp_vty.Po -rm -f vrrpd/$(DEPDIR)/vrrp_zebra.Po -rm -f vtysh/$(DEPDIR)/vtysh.Po -rm -f vtysh/$(DEPDIR)/vtysh_cmd.Po -rm -f vtysh/$(DEPDIR)/vtysh_config.Po -rm -f vtysh/$(DEPDIR)/vtysh_main.Po -rm -f vtysh/$(DEPDIR)/vtysh_user.Po -rm -f watchfrr/$(DEPDIR)/watchfrr.Po -rm -f watchfrr/$(DEPDIR)/watchfrr_errors.Po -rm -f watchfrr/$(DEPDIR)/watchfrr_vty.Po -rm -f yang/$(DEPDIR)/frr-bfdd.yang.Po -rm -f yang/$(DEPDIR)/frr-eigrpd.yang.Po -rm -f yang/$(DEPDIR)/frr-interface.yang.Plo -rm -f yang/$(DEPDIR)/frr-isisd.yang.Po -rm -f yang/$(DEPDIR)/frr-module-translator.yang.Plo -rm -f yang/$(DEPDIR)/frr-ripd.yang.Po -rm -f yang/$(DEPDIR)/frr-ripngd.yang.Po -rm -f yang/$(DEPDIR)/frr-route-types.yang.Plo -rm -f yang/$(DEPDIR)/tests_lib_northbound_test_oper_data-frr-test-module.yang.Po -rm -f zebra/$(DEPDIR)/connected.Po -rm -f zebra/$(DEPDIR)/debug.Po -rm -f zebra/$(DEPDIR)/if_ioctl.Po -rm -f zebra/$(DEPDIR)/if_ioctl_solaris.Po -rm -f zebra/$(DEPDIR)/if_netlink.Po -rm -f zebra/$(DEPDIR)/if_sysctl.Po -rm -f zebra/$(DEPDIR)/interface.Po -rm -f zebra/$(DEPDIR)/ioctl.Po -rm -f zebra/$(DEPDIR)/ioctl_solaris.Po -rm -f zebra/$(DEPDIR)/ipforward_proc.Po -rm -f zebra/$(DEPDIR)/ipforward_solaris.Po -rm -f zebra/$(DEPDIR)/ipforward_sysctl.Po -rm -f zebra/$(DEPDIR)/irdp_interface.Plo -rm -f zebra/$(DEPDIR)/irdp_main.Plo -rm -f zebra/$(DEPDIR)/irdp_packet.Plo -rm -f zebra/$(DEPDIR)/kernel_netlink.Po -rm -f zebra/$(DEPDIR)/kernel_socket.Po -rm -f zebra/$(DEPDIR)/label_manager.Po -rm -f zebra/$(DEPDIR)/main.Po -rm -f zebra/$(DEPDIR)/redistribute.Po -rm -f zebra/$(DEPDIR)/router-id.Po -rm -f zebra/$(DEPDIR)/rt_netlink.Po -rm -f zebra/$(DEPDIR)/rt_socket.Po -rm -f zebra/$(DEPDIR)/rtadv.Po -rm -f zebra/$(DEPDIR)/rtread_getmsg.Po -rm -f zebra/$(DEPDIR)/rtread_netlink.Po -rm -f zebra/$(DEPDIR)/rtread_sysctl.Po -rm -f zebra/$(DEPDIR)/rule_netlink.Po -rm -f zebra/$(DEPDIR)/rule_socket.Po -rm -f zebra/$(DEPDIR)/table_manager.Po -rm -f zebra/$(DEPDIR)/zapi_msg.Po -rm -f zebra/$(DEPDIR)/zebra_dplane.Po -rm -f zebra/$(DEPDIR)/zebra_errors.Po -rm -f zebra/$(DEPDIR)/zebra_fpm.Plo -rm -f zebra/$(DEPDIR)/zebra_fpm_dt.Plo -rm -f zebra/$(DEPDIR)/zebra_fpm_netlink.Plo -rm -f zebra/$(DEPDIR)/zebra_fpm_protobuf.Plo -rm -f zebra/$(DEPDIR)/zebra_l2.Po -rm -f zebra/$(DEPDIR)/zebra_memory.Po -rm -f zebra/$(DEPDIR)/zebra_mlag.Po -rm -f zebra/$(DEPDIR)/zebra_mpls.Po -rm -f zebra/$(DEPDIR)/zebra_mpls_netlink.Po -rm -f zebra/$(DEPDIR)/zebra_mpls_null.Po -rm -f zebra/$(DEPDIR)/zebra_mpls_openbsd.Po -rm -f zebra/$(DEPDIR)/zebra_mpls_vty.Po -rm -f zebra/$(DEPDIR)/zebra_mroute.Po -rm -f zebra/$(DEPDIR)/zebra_netns_id.Po -rm -f zebra/$(DEPDIR)/zebra_netns_notify.Po -rm -f zebra/$(DEPDIR)/zebra_nhg.Po -rm -f zebra/$(DEPDIR)/zebra_ns.Po -rm -f zebra/$(DEPDIR)/zebra_pbr.Po -rm -f zebra/$(DEPDIR)/zebra_ptm.Po -rm -f zebra/$(DEPDIR)/zebra_ptm_redistribute.Po -rm -f zebra/$(DEPDIR)/zebra_pw.Po -rm -f zebra/$(DEPDIR)/zebra_rib.Po -rm -f zebra/$(DEPDIR)/zebra_rnh.Po -rm -f zebra/$(DEPDIR)/zebra_routemap.Po -rm -f zebra/$(DEPDIR)/zebra_router.Po -rm -f zebra/$(DEPDIR)/zebra_snmp_la-zebra_snmp.Plo -rm -f zebra/$(DEPDIR)/zebra_vrf.Po -rm -f zebra/$(DEPDIR)/zebra_vty.Po -rm -f zebra/$(DEPDIR)/zebra_vxlan.Po -rm -f zebra/$(DEPDIR)/zserv.Po -rm -f Makefile distclean-am: clean-am distclean-compile distclean-generic \ distclean-hdr distclean-libtool distclean-tags dvi: dvi-am dvi-am: html-am: info-am: install-data-am: install-data-local install-dist_examplesDATA \ install-dist_yangmodelsDATA install-eigrpdheaderHEADERS \ install-moduleLTLIBRARIES install-nodist_pkgincludeHEADERS \ install-ospfapiheaderHEADERS install-ospfdheaderHEADERS \ install-pkgincludeHEADERS install-rcSCRIPTS \ install-rstman1DATA install-rstman8DATA install-dvi: install-dvi-am install-dvi-am: install-exec-am: install-binPROGRAMS install-libLTLIBRARIES \ install-sbinPROGRAMS install-sbinSCRIPTS install-html-am: install-info-am: install-man: install-pdf: install-pdf-am install-pdf-am: install-ps: install-ps-am install-ps-am: installcheck-am: maintainer-clean: maintainer-clean-am -rm -f $(am__CONFIG_DISTCLEAN_FILES) -rm -rf $(top_srcdir)/autom4te.cache -rm -f babeld/$(DEPDIR)/babel_errors.Po -rm -f babeld/$(DEPDIR)/babel_filter.Po -rm -f babeld/$(DEPDIR)/babel_interface.Po -rm -f babeld/$(DEPDIR)/babel_main.Po -rm -f babeld/$(DEPDIR)/babel_zebra.Po -rm -f babeld/$(DEPDIR)/babeld.Po -rm -f babeld/$(DEPDIR)/kernel.Po -rm -f babeld/$(DEPDIR)/message.Po -rm -f babeld/$(DEPDIR)/neighbour.Po -rm -f babeld/$(DEPDIR)/net.Po -rm -f babeld/$(DEPDIR)/resend.Po -rm -f babeld/$(DEPDIR)/route.Po -rm -f babeld/$(DEPDIR)/source.Po -rm -f babeld/$(DEPDIR)/util.Po -rm -f babeld/$(DEPDIR)/xroute.Po -rm -f bfdd/$(DEPDIR)/bfd.Po -rm -f bfdd/$(DEPDIR)/bfd_packet.Po -rm -f bfdd/$(DEPDIR)/bfdd.Po -rm -f bfdd/$(DEPDIR)/bfdd_cli.Po -rm -f bfdd/$(DEPDIR)/bfdd_northbound.Po -rm -f bfdd/$(DEPDIR)/bfdd_vty.Po -rm -f bfdd/$(DEPDIR)/config.Po -rm -f bfdd/$(DEPDIR)/control.Po -rm -f bfdd/$(DEPDIR)/event.Po -rm -f bfdd/$(DEPDIR)/log.Po -rm -f bfdd/$(DEPDIR)/ptm_adapter.Po -rm -f bgpd/$(DEPDIR)/bgp_addpath.Po -rm -f bgpd/$(DEPDIR)/bgp_advertise.Po -rm -f bgpd/$(DEPDIR)/bgp_aspath.Po -rm -f bgpd/$(DEPDIR)/bgp_attr.Po -rm -f bgpd/$(DEPDIR)/bgp_attr_evpn.Po -rm -f bgpd/$(DEPDIR)/bgp_bfd.Po -rm -f bgpd/$(DEPDIR)/bgp_bmp.Plo -rm -f bgpd/$(DEPDIR)/bgp_btoa-bgp_btoa.Po -rm -f bgpd/$(DEPDIR)/bgp_clist.Po -rm -f bgpd/$(DEPDIR)/bgp_community.Po -rm -f bgpd/$(DEPDIR)/bgp_damp.Po -rm -f bgpd/$(DEPDIR)/bgp_debug.Po -rm -f bgpd/$(DEPDIR)/bgp_dump.Po -rm -f bgpd/$(DEPDIR)/bgp_ecommunity.Po -rm -f bgpd/$(DEPDIR)/bgp_encap_tlv.Po -rm -f bgpd/$(DEPDIR)/bgp_errors.Po -rm -f bgpd/$(DEPDIR)/bgp_evpn.Po -rm -f bgpd/$(DEPDIR)/bgp_evpn_vty.Po -rm -f bgpd/$(DEPDIR)/bgp_filter.Po -rm -f bgpd/$(DEPDIR)/bgp_flowspec.Po -rm -f bgpd/$(DEPDIR)/bgp_flowspec_util.Po -rm -f bgpd/$(DEPDIR)/bgp_flowspec_vty.Po -rm -f bgpd/$(DEPDIR)/bgp_fsm.Po -rm -f bgpd/$(DEPDIR)/bgp_io.Po -rm -f bgpd/$(DEPDIR)/bgp_keepalives.Po -rm -f bgpd/$(DEPDIR)/bgp_label.Po -rm -f bgpd/$(DEPDIR)/bgp_labelpool.Po -rm -f bgpd/$(DEPDIR)/bgp_lcommunity.Po -rm -f bgpd/$(DEPDIR)/bgp_mac.Po -rm -f bgpd/$(DEPDIR)/bgp_memory.Po -rm -f bgpd/$(DEPDIR)/bgp_mpath.Po -rm -f bgpd/$(DEPDIR)/bgp_mplsvpn.Po -rm -f bgpd/$(DEPDIR)/bgp_network.Po -rm -f bgpd/$(DEPDIR)/bgp_nexthop.Po -rm -f bgpd/$(DEPDIR)/bgp_nht.Po -rm -f bgpd/$(DEPDIR)/bgp_open.Po -rm -f bgpd/$(DEPDIR)/bgp_packet.Po -rm -f bgpd/$(DEPDIR)/bgp_pbr.Po -rm -f bgpd/$(DEPDIR)/bgp_rd.Po -rm -f bgpd/$(DEPDIR)/bgp_regex.Po -rm -f bgpd/$(DEPDIR)/bgp_route.Po -rm -f bgpd/$(DEPDIR)/bgp_routemap.Po -rm -f bgpd/$(DEPDIR)/bgp_table.Po -rm -f bgpd/$(DEPDIR)/bgp_updgrp.Po -rm -f bgpd/$(DEPDIR)/bgp_updgrp_adv.Po -rm -f bgpd/$(DEPDIR)/bgp_updgrp_packet.Po -rm -f bgpd/$(DEPDIR)/bgp_vpn.Po -rm -f bgpd/$(DEPDIR)/bgp_vty.Po -rm -f bgpd/$(DEPDIR)/bgp_zebra.Po -rm -f bgpd/$(DEPDIR)/bgpd-bgp_main.Po -rm -f bgpd/$(DEPDIR)/bgpd.Po -rm -f bgpd/$(DEPDIR)/bgpd_rpki_la-bgp_rpki.Plo -rm -f bgpd/$(DEPDIR)/bgpd_snmp_la-bgp_snmp.Plo -rm -f bgpd/rfapi/$(DEPDIR)/bgp_btoa-rfapi_descriptor_rfp_utils.Po -rm -f bgpd/rfapi/$(DEPDIR)/bgp_rfapi_cfg.Po -rm -f bgpd/rfapi/$(DEPDIR)/bgpd-rfapi_descriptor_rfp_utils.Po -rm -f bgpd/rfapi/$(DEPDIR)/rfapi.Po -rm -f bgpd/rfapi/$(DEPDIR)/rfapi_ap.Po -rm -f bgpd/rfapi/$(DEPDIR)/rfapi_descriptor_rfp_utils.Po -rm -f bgpd/rfapi/$(DEPDIR)/rfapi_encap_tlv.Po -rm -f bgpd/rfapi/$(DEPDIR)/rfapi_import.Po -rm -f bgpd/rfapi/$(DEPDIR)/rfapi_monitor.Po -rm -f bgpd/rfapi/$(DEPDIR)/rfapi_nve_addr.Po -rm -f bgpd/rfapi/$(DEPDIR)/rfapi_rib.Po -rm -f bgpd/rfapi/$(DEPDIR)/rfapi_vty.Po -rm -f bgpd/rfapi/$(DEPDIR)/vnc_debug.Po -rm -f bgpd/rfapi/$(DEPDIR)/vnc_export_bgp.Po -rm -f bgpd/rfapi/$(DEPDIR)/vnc_export_table.Po -rm -f bgpd/rfapi/$(DEPDIR)/vnc_import_bgp.Po -rm -f bgpd/rfapi/$(DEPDIR)/vnc_zebra.Po -rm -f bgpd/rfp-example/librfp/$(DEPDIR)/rfp_example.Po -rm -f bgpd/rfp-example/rfptest/$(DEPDIR)/rfptest-rfptest.Po -rm -f eigrpd/$(DEPDIR)/eigrp_cli.Po -rm -f eigrpd/$(DEPDIR)/eigrp_dump.Po -rm -f eigrpd/$(DEPDIR)/eigrp_errors.Po -rm -f eigrpd/$(DEPDIR)/eigrp_filter.Po -rm -f eigrpd/$(DEPDIR)/eigrp_fsm.Po -rm -f eigrpd/$(DEPDIR)/eigrp_hello.Po -rm -f eigrpd/$(DEPDIR)/eigrp_interface.Po -rm -f eigrpd/$(DEPDIR)/eigrp_main.Po -rm -f eigrpd/$(DEPDIR)/eigrp_memory.Po -rm -f eigrpd/$(DEPDIR)/eigrp_neighbor.Po -rm -f eigrpd/$(DEPDIR)/eigrp_network.Po -rm -f eigrpd/$(DEPDIR)/eigrp_northbound.Po -rm -f eigrpd/$(DEPDIR)/eigrp_packet.Po -rm -f eigrpd/$(DEPDIR)/eigrp_query.Po -rm -f eigrpd/$(DEPDIR)/eigrp_reply.Po -rm -f eigrpd/$(DEPDIR)/eigrp_siaquery.Po -rm -f eigrpd/$(DEPDIR)/eigrp_siareply.Po -rm -f eigrpd/$(DEPDIR)/eigrp_snmp.Po -rm -f eigrpd/$(DEPDIR)/eigrp_topology.Po -rm -f eigrpd/$(DEPDIR)/eigrp_update.Po -rm -f eigrpd/$(DEPDIR)/eigrp_vrf.Po -rm -f eigrpd/$(DEPDIR)/eigrp_vty.Po -rm -f eigrpd/$(DEPDIR)/eigrp_zebra.Po -rm -f eigrpd/$(DEPDIR)/eigrpd.Po -rm -f fpm/$(DEPDIR)/libfrrfpm_pb_la-fpm.pb-c.Plo -rm -f fpm/$(DEPDIR)/libfrrfpm_pb_la-fpm_pb.Plo -rm -f grpc/$(DEPDIR)/libfrrgrpc_pb_la-frr-northbound.grpc.pb.Plo -rm -f grpc/$(DEPDIR)/libfrrgrpc_pb_la-frr-northbound.pb.Plo -rm -f isisd/$(DEPDIR)/fabricd-isis_bpf.Po -rm -f isisd/$(DEPDIR)/fabricd-isis_dlpi.Po -rm -f isisd/$(DEPDIR)/fabricd-isis_main.Po -rm -f isisd/$(DEPDIR)/fabricd-isis_pfpacket.Po -rm -f isisd/$(DEPDIR)/fabricd.Po -rm -f isisd/$(DEPDIR)/isis_adjacency.Po -rm -f isisd/$(DEPDIR)/isis_bfd.Po -rm -f isisd/$(DEPDIR)/isis_bpf.Po -rm -f isisd/$(DEPDIR)/isis_circuit.Po -rm -f isisd/$(DEPDIR)/isis_cli.Po -rm -f isisd/$(DEPDIR)/isis_csm.Po -rm -f isisd/$(DEPDIR)/isis_dlpi.Po -rm -f isisd/$(DEPDIR)/isis_dr.Po -rm -f isisd/$(DEPDIR)/isis_dynhn.Po -rm -f isisd/$(DEPDIR)/isis_errors.Po -rm -f isisd/$(DEPDIR)/isis_events.Po -rm -f isisd/$(DEPDIR)/isis_flags.Po -rm -f isisd/$(DEPDIR)/isis_lsp.Po -rm -f isisd/$(DEPDIR)/isis_main.Po -rm -f isisd/$(DEPDIR)/isis_memory.Po -rm -f isisd/$(DEPDIR)/isis_misc.Po -rm -f isisd/$(DEPDIR)/isis_mt.Po -rm -f isisd/$(DEPDIR)/isis_northbound.Po -rm -f isisd/$(DEPDIR)/isis_pdu.Po -rm -f isisd/$(DEPDIR)/isis_pdu_counter.Po -rm -f isisd/$(DEPDIR)/isis_pfpacket.Po -rm -f isisd/$(DEPDIR)/isis_redist.Po -rm -f isisd/$(DEPDIR)/isis_route.Po -rm -f isisd/$(DEPDIR)/isis_routemap.Po -rm -f isisd/$(DEPDIR)/isis_spf.Po -rm -f isisd/$(DEPDIR)/isis_te.Po -rm -f isisd/$(DEPDIR)/isis_tlvs.Po -rm -f isisd/$(DEPDIR)/isis_tx_queue.Po -rm -f isisd/$(DEPDIR)/isis_zebra.Po -rm -f isisd/$(DEPDIR)/isisd.Po -rm -f isisd/$(DEPDIR)/iso_checksum.Po -rm -f isisd/$(DEPDIR)/libfabric_a-fabricd.Po -rm -f isisd/$(DEPDIR)/libfabric_a-isis_adjacency.Po -rm -f isisd/$(DEPDIR)/libfabric_a-isis_bfd.Po -rm -f isisd/$(DEPDIR)/libfabric_a-isis_circuit.Po -rm -f isisd/$(DEPDIR)/libfabric_a-isis_csm.Po -rm -f isisd/$(DEPDIR)/libfabric_a-isis_dr.Po -rm -f isisd/$(DEPDIR)/libfabric_a-isis_dynhn.Po -rm -f isisd/$(DEPDIR)/libfabric_a-isis_errors.Po -rm -f isisd/$(DEPDIR)/libfabric_a-isis_events.Po -rm -f isisd/$(DEPDIR)/libfabric_a-isis_flags.Po -rm -f isisd/$(DEPDIR)/libfabric_a-isis_lsp.Po -rm -f isisd/$(DEPDIR)/libfabric_a-isis_memory.Po -rm -f isisd/$(DEPDIR)/libfabric_a-isis_misc.Po -rm -f isisd/$(DEPDIR)/libfabric_a-isis_mt.Po -rm -f isisd/$(DEPDIR)/libfabric_a-isis_pdu.Po -rm -f isisd/$(DEPDIR)/libfabric_a-isis_pdu_counter.Po -rm -f isisd/$(DEPDIR)/libfabric_a-isis_redist.Po -rm -f isisd/$(DEPDIR)/libfabric_a-isis_route.Po -rm -f isisd/$(DEPDIR)/libfabric_a-isis_routemap.Po -rm -f isisd/$(DEPDIR)/libfabric_a-isis_spf.Po -rm -f isisd/$(DEPDIR)/libfabric_a-isis_te.Po -rm -f isisd/$(DEPDIR)/libfabric_a-isis_tlvs.Po -rm -f isisd/$(DEPDIR)/libfabric_a-isis_tx_queue.Po -rm -f isisd/$(DEPDIR)/libfabric_a-isis_vty_fabricd.Po -rm -f isisd/$(DEPDIR)/libfabric_a-isis_zebra.Po -rm -f isisd/$(DEPDIR)/libfabric_a-isisd.Po -rm -f isisd/$(DEPDIR)/libfabric_a-iso_checksum.Po -rm -f ldpd/$(DEPDIR)/accept.Po -rm -f ldpd/$(DEPDIR)/address.Po -rm -f ldpd/$(DEPDIR)/adjacency.Po -rm -f ldpd/$(DEPDIR)/control.Po -rm -f ldpd/$(DEPDIR)/hello.Po -rm -f ldpd/$(DEPDIR)/init.Po -rm -f ldpd/$(DEPDIR)/interface.Po -rm -f ldpd/$(DEPDIR)/keepalive.Po -rm -f ldpd/$(DEPDIR)/l2vpn.Po -rm -f ldpd/$(DEPDIR)/labelmapping.Po -rm -f ldpd/$(DEPDIR)/lde.Po -rm -f ldpd/$(DEPDIR)/lde_lib.Po -rm -f ldpd/$(DEPDIR)/ldp_debug.Po -rm -f ldpd/$(DEPDIR)/ldp_vty_cmds.Po -rm -f ldpd/$(DEPDIR)/ldp_vty_conf.Po -rm -f ldpd/$(DEPDIR)/ldp_vty_exec.Po -rm -f ldpd/$(DEPDIR)/ldp_zebra.Po -rm -f ldpd/$(DEPDIR)/ldpd.Po -rm -f ldpd/$(DEPDIR)/ldpe.Po -rm -f ldpd/$(DEPDIR)/log.Po -rm -f ldpd/$(DEPDIR)/logmsg.Po -rm -f ldpd/$(DEPDIR)/neighbor.Po -rm -f ldpd/$(DEPDIR)/notification.Po -rm -f ldpd/$(DEPDIR)/packet.Po -rm -f ldpd/$(DEPDIR)/pfkey.Po -rm -f ldpd/$(DEPDIR)/socket.Po -rm -f ldpd/$(DEPDIR)/util.Po -rm -f lib/$(DEPDIR)/agg_table.Plo -rm -f lib/$(DEPDIR)/atomlist.Plo -rm -f lib/$(DEPDIR)/bfd.Plo -rm -f lib/$(DEPDIR)/buffer.Plo -rm -f lib/$(DEPDIR)/checksum.Plo -rm -f lib/$(DEPDIR)/clippy-clippy.Po -rm -f lib/$(DEPDIR)/clippy-command_graph.Po -rm -f lib/$(DEPDIR)/clippy-command_lex.Po -rm -f lib/$(DEPDIR)/clippy-command_parse.Po -rm -f lib/$(DEPDIR)/clippy-command_py.Po -rm -f lib/$(DEPDIR)/clippy-defun_lex.Po -rm -f lib/$(DEPDIR)/clippy-graph.Po -rm -f lib/$(DEPDIR)/clippy-memory.Po -rm -f lib/$(DEPDIR)/clippy-vector.Po -rm -f lib/$(DEPDIR)/command.Plo -rm -f lib/$(DEPDIR)/command_graph.Plo -rm -f lib/$(DEPDIR)/command_lex.Plo -rm -f lib/$(DEPDIR)/command_match.Plo -rm -f lib/$(DEPDIR)/command_parse.Plo -rm -f lib/$(DEPDIR)/confd_la-northbound_confd.Plo -rm -f lib/$(DEPDIR)/csv.Plo -rm -f lib/$(DEPDIR)/db.Plo -rm -f lib/$(DEPDIR)/debug.Plo -rm -f lib/$(DEPDIR)/distribute.Plo -rm -f lib/$(DEPDIR)/ferr.Plo -rm -f lib/$(DEPDIR)/filter.Plo -rm -f lib/$(DEPDIR)/frr_pthread.Plo -rm -f lib/$(DEPDIR)/frrcu.Plo -rm -f lib/$(DEPDIR)/frrlua.Plo -rm -f lib/$(DEPDIR)/frrstr.Plo -rm -f lib/$(DEPDIR)/getopt.Plo -rm -f lib/$(DEPDIR)/getopt1.Plo -rm -f lib/$(DEPDIR)/grammar_sandbox.Plo -rm -f lib/$(DEPDIR)/grammar_sandbox_main.Po -rm -f lib/$(DEPDIR)/graph.Plo -rm -f lib/$(DEPDIR)/grpc_la-northbound_grpc.Plo -rm -f lib/$(DEPDIR)/hash.Plo -rm -f lib/$(DEPDIR)/hook.Plo -rm -f lib/$(DEPDIR)/id_alloc.Plo -rm -f lib/$(DEPDIR)/if.Plo -rm -f lib/$(DEPDIR)/if_rmap.Plo -rm -f lib/$(DEPDIR)/imsg-buffer.Plo -rm -f lib/$(DEPDIR)/imsg.Plo -rm -f lib/$(DEPDIR)/jhash.Plo -rm -f lib/$(DEPDIR)/json.Plo -rm -f lib/$(DEPDIR)/keychain.Plo -rm -f lib/$(DEPDIR)/lib_errors.Plo -rm -f lib/$(DEPDIR)/libfrr.Plo -rm -f lib/$(DEPDIR)/libfrrcares_la-resolver.Plo -rm -f lib/$(DEPDIR)/libfrrsnmp_la-agentx.Plo -rm -f lib/$(DEPDIR)/libfrrsnmp_la-snmp.Plo -rm -f lib/$(DEPDIR)/libfrrzmq_la-frr_zmq.Plo -rm -f lib/$(DEPDIR)/linklist.Plo -rm -f lib/$(DEPDIR)/log.Plo -rm -f lib/$(DEPDIR)/log_vty.Plo -rm -f lib/$(DEPDIR)/md5.Plo -rm -f lib/$(DEPDIR)/memory.Plo -rm -f lib/$(DEPDIR)/memory_vty.Plo -rm -f lib/$(DEPDIR)/mlag.Plo -rm -f lib/$(DEPDIR)/module.Plo -rm -f lib/$(DEPDIR)/mpls.Plo -rm -f lib/$(DEPDIR)/netns_linux.Plo -rm -f lib/$(DEPDIR)/netns_other.Plo -rm -f lib/$(DEPDIR)/network.Plo -rm -f lib/$(DEPDIR)/nexthop.Plo -rm -f lib/$(DEPDIR)/nexthop_group.Plo -rm -f lib/$(DEPDIR)/northbound.Plo -rm -f lib/$(DEPDIR)/northbound_cli.Plo -rm -f lib/$(DEPDIR)/northbound_db.Plo -rm -f lib/$(DEPDIR)/ntop.Plo -rm -f lib/$(DEPDIR)/openbsd-tree.Plo -rm -f lib/$(DEPDIR)/pid_output.Plo -rm -f lib/$(DEPDIR)/plist.Plo -rm -f lib/$(DEPDIR)/prefix.Plo -rm -f lib/$(DEPDIR)/privs.Plo -rm -f lib/$(DEPDIR)/ptm_lib.Plo -rm -f lib/$(DEPDIR)/pullwr.Plo -rm -f lib/$(DEPDIR)/qobj.Plo -rm -f lib/$(DEPDIR)/ringbuf.Plo -rm -f lib/$(DEPDIR)/routemap.Plo -rm -f lib/$(DEPDIR)/sbuf.Plo -rm -f lib/$(DEPDIR)/seqlock.Plo -rm -f lib/$(DEPDIR)/sha256.Plo -rm -f lib/$(DEPDIR)/sigevent.Plo -rm -f lib/$(DEPDIR)/skiplist.Plo -rm -f lib/$(DEPDIR)/sockopt.Plo -rm -f lib/$(DEPDIR)/sockunion.Plo -rm -f lib/$(DEPDIR)/spf_backoff.Plo -rm -f lib/$(DEPDIR)/srcdest_table.Plo -rm -f lib/$(DEPDIR)/stream.Plo -rm -f lib/$(DEPDIR)/strlcat.Plo -rm -f lib/$(DEPDIR)/strlcpy.Plo -rm -f lib/$(DEPDIR)/sysrepo_la-northbound_sysrepo.Plo -rm -f lib/$(DEPDIR)/systemd.Plo -rm -f lib/$(DEPDIR)/table.Plo -rm -f lib/$(DEPDIR)/termtable.Plo -rm -f lib/$(DEPDIR)/thread.Plo -rm -f lib/$(DEPDIR)/typerb.Plo -rm -f lib/$(DEPDIR)/typesafe.Plo -rm -f lib/$(DEPDIR)/vector.Plo -rm -f lib/$(DEPDIR)/vrf.Plo -rm -f lib/$(DEPDIR)/vty.Plo -rm -f lib/$(DEPDIR)/wheel.Plo -rm -f lib/$(DEPDIR)/workqueue.Plo -rm -f lib/$(DEPDIR)/yang.Plo -rm -f lib/$(DEPDIR)/yang_translator.Plo -rm -f lib/$(DEPDIR)/yang_wrappers.Plo -rm -f lib/$(DEPDIR)/zclient.Plo -rm -f lib/printf/$(DEPDIR)/glue.Plo -rm -f lib/printf/$(DEPDIR)/printf-pos.Plo -rm -f lib/printf/$(DEPDIR)/vfprintf.Plo -rm -f nhrpd/$(DEPDIR)/linux.Po -rm -f nhrpd/$(DEPDIR)/netlink_arp.Po -rm -f nhrpd/$(DEPDIR)/netlink_gre.Po -rm -f nhrpd/$(DEPDIR)/nhrp_cache.Po -rm -f nhrpd/$(DEPDIR)/nhrp_errors.Po -rm -f nhrpd/$(DEPDIR)/nhrp_event.Po -rm -f nhrpd/$(DEPDIR)/nhrp_interface.Po -rm -f nhrpd/$(DEPDIR)/nhrp_main.Po -rm -f nhrpd/$(DEPDIR)/nhrp_nhs.Po -rm -f nhrpd/$(DEPDIR)/nhrp_packet.Po -rm -f nhrpd/$(DEPDIR)/nhrp_peer.Po -rm -f nhrpd/$(DEPDIR)/nhrp_route.Po -rm -f nhrpd/$(DEPDIR)/nhrp_shortcut.Po -rm -f nhrpd/$(DEPDIR)/nhrp_vc.Po -rm -f nhrpd/$(DEPDIR)/nhrp_vty.Po -rm -f nhrpd/$(DEPDIR)/reqid.Po -rm -f nhrpd/$(DEPDIR)/vici.Po -rm -f nhrpd/$(DEPDIR)/zbuf.Po -rm -f nhrpd/$(DEPDIR)/znl.Po -rm -f ospf6d/$(DEPDIR)/ospf6_abr.Po -rm -f ospf6d/$(DEPDIR)/ospf6_area.Po -rm -f ospf6d/$(DEPDIR)/ospf6_asbr.Po -rm -f ospf6d/$(DEPDIR)/ospf6_bfd.Po -rm -f ospf6d/$(DEPDIR)/ospf6_flood.Po -rm -f ospf6d/$(DEPDIR)/ospf6_interface.Po -rm -f ospf6d/$(DEPDIR)/ospf6_intra.Po -rm -f ospf6d/$(DEPDIR)/ospf6_lsa.Po -rm -f ospf6d/$(DEPDIR)/ospf6_lsdb.Po -rm -f ospf6d/$(DEPDIR)/ospf6_main.Po -rm -f ospf6d/$(DEPDIR)/ospf6_memory.Po -rm -f ospf6d/$(DEPDIR)/ospf6_message.Po -rm -f ospf6d/$(DEPDIR)/ospf6_neighbor.Po -rm -f ospf6d/$(DEPDIR)/ospf6_network.Po -rm -f ospf6d/$(DEPDIR)/ospf6_proto.Po -rm -f ospf6d/$(DEPDIR)/ospf6_route.Po -rm -f ospf6d/$(DEPDIR)/ospf6_spf.Po -rm -f ospf6d/$(DEPDIR)/ospf6_top.Po -rm -f ospf6d/$(DEPDIR)/ospf6_zebra.Po -rm -f ospf6d/$(DEPDIR)/ospf6d.Po -rm -f ospf6d/$(DEPDIR)/ospf6d_snmp_la-ospf6_snmp.Plo -rm -f ospfclient/$(DEPDIR)/ospf_apiclient.Plo -rm -f ospfclient/$(DEPDIR)/ospfclient.Po -rm -f ospfd/$(DEPDIR)/ospf_abr.Po -rm -f ospfd/$(DEPDIR)/ospf_api.Po -rm -f ospfd/$(DEPDIR)/ospf_apiserver.Po -rm -f ospfd/$(DEPDIR)/ospf_asbr.Po -rm -f ospfd/$(DEPDIR)/ospf_ase.Po -rm -f ospfd/$(DEPDIR)/ospf_bfd.Po -rm -f ospfd/$(DEPDIR)/ospf_dump.Po -rm -f ospfd/$(DEPDIR)/ospf_dump_api.Po -rm -f ospfd/$(DEPDIR)/ospf_errors.Po -rm -f ospfd/$(DEPDIR)/ospf_ext.Po -rm -f ospfd/$(DEPDIR)/ospf_flood.Po -rm -f ospfd/$(DEPDIR)/ospf_ia.Po -rm -f ospfd/$(DEPDIR)/ospf_interface.Po -rm -f ospfd/$(DEPDIR)/ospf_ism.Po -rm -f ospfd/$(DEPDIR)/ospf_lsa.Po -rm -f ospfd/$(DEPDIR)/ospf_lsdb.Po -rm -f ospfd/$(DEPDIR)/ospf_main.Po -rm -f ospfd/$(DEPDIR)/ospf_memory.Po -rm -f ospfd/$(DEPDIR)/ospf_neighbor.Po -rm -f ospfd/$(DEPDIR)/ospf_network.Po -rm -f ospfd/$(DEPDIR)/ospf_nsm.Po -rm -f ospfd/$(DEPDIR)/ospf_opaque.Po -rm -f ospfd/$(DEPDIR)/ospf_packet.Po -rm -f ospfd/$(DEPDIR)/ospf_ri.Po -rm -f ospfd/$(DEPDIR)/ospf_route.Po -rm -f ospfd/$(DEPDIR)/ospf_routemap.Po -rm -f ospfd/$(DEPDIR)/ospf_spf.Po -rm -f ospfd/$(DEPDIR)/ospf_sr.Po -rm -f ospfd/$(DEPDIR)/ospf_te.Po -rm -f ospfd/$(DEPDIR)/ospf_vty.Po -rm -f ospfd/$(DEPDIR)/ospf_zebra.Po -rm -f ospfd/$(DEPDIR)/ospfd.Po -rm -f ospfd/$(DEPDIR)/ospfd_snmp_la-ospf_snmp.Plo -rm -f pbrd/$(DEPDIR)/pbr_debug.Po -rm -f pbrd/$(DEPDIR)/pbr_main.Po -rm -f pbrd/$(DEPDIR)/pbr_map.Po -rm -f pbrd/$(DEPDIR)/pbr_memory.Po -rm -f pbrd/$(DEPDIR)/pbr_nht.Po -rm -f pbrd/$(DEPDIR)/pbr_vty.Po -rm -f pbrd/$(DEPDIR)/pbr_zebra.Po -rm -f pimd/$(DEPDIR)/mtracebis.Po -rm -f pimd/$(DEPDIR)/mtracebis_netlink.Po -rm -f pimd/$(DEPDIR)/mtracebis_routeget.Po -rm -f pimd/$(DEPDIR)/pim_assert.Po -rm -f pimd/$(DEPDIR)/pim_bfd.Po -rm -f pimd/$(DEPDIR)/pim_br.Po -rm -f pimd/$(DEPDIR)/pim_bsm.Po -rm -f pimd/$(DEPDIR)/pim_cmd.Po -rm -f pimd/$(DEPDIR)/pim_errors.Po -rm -f pimd/$(DEPDIR)/pim_hello.Po -rm -f pimd/$(DEPDIR)/pim_iface.Po -rm -f pimd/$(DEPDIR)/pim_ifchannel.Po -rm -f pimd/$(DEPDIR)/pim_igmp.Po -rm -f pimd/$(DEPDIR)/pim_igmp_mtrace.Po -rm -f pimd/$(DEPDIR)/pim_igmp_stats.Po -rm -f pimd/$(DEPDIR)/pim_igmpv2.Po -rm -f pimd/$(DEPDIR)/pim_igmpv3.Po -rm -f pimd/$(DEPDIR)/pim_instance.Po -rm -f pimd/$(DEPDIR)/pim_int.Po -rm -f pimd/$(DEPDIR)/pim_join.Po -rm -f pimd/$(DEPDIR)/pim_jp_agg.Po -rm -f pimd/$(DEPDIR)/pim_macro.Po -rm -f pimd/$(DEPDIR)/pim_main.Po -rm -f pimd/$(DEPDIR)/pim_memory.Po -rm -f pimd/$(DEPDIR)/pim_mroute.Po -rm -f pimd/$(DEPDIR)/pim_msdp.Po -rm -f pimd/$(DEPDIR)/pim_msdp_packet.Po -rm -f pimd/$(DEPDIR)/pim_msdp_socket.Po -rm -f pimd/$(DEPDIR)/pim_msg.Po -rm -f pimd/$(DEPDIR)/pim_neighbor.Po -rm -f pimd/$(DEPDIR)/pim_nht.Po -rm -f pimd/$(DEPDIR)/pim_oil.Po -rm -f pimd/$(DEPDIR)/pim_pim.Po -rm -f pimd/$(DEPDIR)/pim_register.Po -rm -f pimd/$(DEPDIR)/pim_routemap.Po -rm -f pimd/$(DEPDIR)/pim_rp.Po -rm -f pimd/$(DEPDIR)/pim_rpf.Po -rm -f pimd/$(DEPDIR)/pim_signals.Po -rm -f pimd/$(DEPDIR)/pim_sock.Po -rm -f pimd/$(DEPDIR)/pim_ssm.Po -rm -f pimd/$(DEPDIR)/pim_ssmpingd.Po -rm -f pimd/$(DEPDIR)/pim_static.Po -rm -f pimd/$(DEPDIR)/pim_str.Po -rm -f pimd/$(DEPDIR)/pim_time.Po -rm -f pimd/$(DEPDIR)/pim_tlv.Po -rm -f pimd/$(DEPDIR)/pim_upstream.Po -rm -f pimd/$(DEPDIR)/pim_util.Po -rm -f pimd/$(DEPDIR)/pim_version.Po -rm -f pimd/$(DEPDIR)/pim_vty.Po -rm -f pimd/$(DEPDIR)/pim_vxlan.Po -rm -f pimd/$(DEPDIR)/pim_zebra.Po -rm -f pimd/$(DEPDIR)/pim_zlookup.Po -rm -f pimd/$(DEPDIR)/pimd.Po -rm -f pimd/$(DEPDIR)/test_igmpv3_join.Po -rm -f qpb/$(DEPDIR)/libfrr_pb_la-qpb.Plo -rm -f qpb/$(DEPDIR)/libfrr_pb_la-qpb.pb-c.Plo -rm -f qpb/$(DEPDIR)/libfrr_pb_la-qpb_allocator.Plo -rm -f ripd/$(DEPDIR)/rip_cli.Po -rm -f ripd/$(DEPDIR)/rip_debug.Po -rm -f ripd/$(DEPDIR)/rip_errors.Po -rm -f ripd/$(DEPDIR)/rip_interface.Po -rm -f ripd/$(DEPDIR)/rip_main.Po -rm -f ripd/$(DEPDIR)/rip_northbound.Po -rm -f ripd/$(DEPDIR)/rip_offset.Po -rm -f ripd/$(DEPDIR)/rip_peer.Po -rm -f ripd/$(DEPDIR)/rip_routemap.Po -rm -f ripd/$(DEPDIR)/rip_zebra.Po -rm -f ripd/$(DEPDIR)/ripd.Po -rm -f ripd/$(DEPDIR)/ripd_snmp_la-rip_snmp.Plo -rm -f ripngd/$(DEPDIR)/ripng_cli.Po -rm -f ripngd/$(DEPDIR)/ripng_debug.Po -rm -f ripngd/$(DEPDIR)/ripng_interface.Po -rm -f ripngd/$(DEPDIR)/ripng_main.Po -rm -f ripngd/$(DEPDIR)/ripng_nexthop.Po -rm -f ripngd/$(DEPDIR)/ripng_northbound.Po -rm -f ripngd/$(DEPDIR)/ripng_offset.Po -rm -f ripngd/$(DEPDIR)/ripng_peer.Po -rm -f ripngd/$(DEPDIR)/ripng_route.Po -rm -f ripngd/$(DEPDIR)/ripng_routemap.Po -rm -f ripngd/$(DEPDIR)/ripng_zebra.Po -rm -f ripngd/$(DEPDIR)/ripngd.Po -rm -f sharpd/$(DEPDIR)/sharp_main.Po -rm -f sharpd/$(DEPDIR)/sharp_nht.Po -rm -f sharpd/$(DEPDIR)/sharp_vty.Po -rm -f sharpd/$(DEPDIR)/sharp_zebra.Po -rm -f staticd/$(DEPDIR)/static_main.Po -rm -f staticd/$(DEPDIR)/static_memory.Po -rm -f staticd/$(DEPDIR)/static_nht.Po -rm -f staticd/$(DEPDIR)/static_routes.Po -rm -f staticd/$(DEPDIR)/static_vrf.Po -rm -f staticd/$(DEPDIR)/static_vty.Po -rm -f staticd/$(DEPDIR)/static_zebra.Po -rm -f tests/bgpd/$(DEPDIR)/test_aspath-test_aspath.Po -rm -f tests/bgpd/$(DEPDIR)/test_bgp_table-test_bgp_table.Po -rm -f tests/bgpd/$(DEPDIR)/test_capability-test_capability.Po -rm -f tests/bgpd/$(DEPDIR)/test_ecommunity-test_ecommunity.Po -rm -f tests/bgpd/$(DEPDIR)/test_mp_attr-test_mp_attr.Po -rm -f tests/bgpd/$(DEPDIR)/test_mpath-test_mpath.Po -rm -f tests/bgpd/$(DEPDIR)/test_packet-test_packet.Po -rm -f tests/bgpd/$(DEPDIR)/test_peer_attr-test_peer_attr.Po -rm -f tests/helpers/c/$(DEPDIR)/lib_cli_test_commands-prng.Po -rm -f tests/helpers/c/$(DEPDIR)/lib_test_heavy-main.Po -rm -f tests/helpers/c/$(DEPDIR)/lib_test_heavy_thread-main.Po -rm -f tests/helpers/c/$(DEPDIR)/lib_test_heavy_wq-main.Po -rm -f tests/helpers/c/$(DEPDIR)/lib_test_nexthop_iter-prng.Po -rm -f tests/helpers/c/$(DEPDIR)/lib_test_ntop-prng.Po -rm -f tests/helpers/c/$(DEPDIR)/lib_test_prefix2str-prng.Po -rm -f tests/helpers/c/$(DEPDIR)/lib_test_srcdest_table-prng.Po -rm -f tests/helpers/c/$(DEPDIR)/lib_test_timer_correctness-prng.Po -rm -f tests/helpers/c/$(DEPDIR)/lib_test_timer_performance-prng.Po -rm -f tests/helpers/c/$(DEPDIR)/lib_test_typelist-prng.Po -rm -f tests/isisd/$(DEPDIR)/test_fuzz_isis_tlv-test_fuzz_isis_tlv.Po -rm -f tests/isisd/$(DEPDIR)/test_isis_lspdb-test_isis_lspdb.Po -rm -f tests/isisd/$(DEPDIR)/test_isis_vertex_queue-test_isis_vertex_queue.Po -rm -f tests/lib/$(DEPDIR)/cxxcompat-cxxcompat.Po -rm -f tests/lib/$(DEPDIR)/test_atomlist-test_atomlist.Po -rm -f tests/lib/$(DEPDIR)/test_buffer-test_buffer.Po -rm -f tests/lib/$(DEPDIR)/test_checksum-test_checksum.Po -rm -f tests/lib/$(DEPDIR)/test_graph-test_graph.Po -rm -f tests/lib/$(DEPDIR)/test_heavy-test_heavy.Po -rm -f tests/lib/$(DEPDIR)/test_heavy_thread-test_heavy_thread.Po -rm -f tests/lib/$(DEPDIR)/test_heavy_wq-test_heavy_wq.Po -rm -f tests/lib/$(DEPDIR)/test_idalloc-test_idalloc.Po -rm -f tests/lib/$(DEPDIR)/test_memory-test_memory.Po -rm -f tests/lib/$(DEPDIR)/test_nexthop_iter-test_nexthop_iter.Po -rm -f tests/lib/$(DEPDIR)/test_ntop-test_ntop.Po -rm -f tests/lib/$(DEPDIR)/test_prefix2str-test_prefix2str.Po -rm -f tests/lib/$(DEPDIR)/test_printfrr-test_printfrr.Po -rm -f tests/lib/$(DEPDIR)/test_privs-test_privs.Po -rm -f tests/lib/$(DEPDIR)/test_ringbuf-test_ringbuf.Po -rm -f tests/lib/$(DEPDIR)/test_segv-test_segv.Po -rm -f tests/lib/$(DEPDIR)/test_seqlock-test_seqlock.Po -rm -f tests/lib/$(DEPDIR)/test_sig-test_sig.Po -rm -f tests/lib/$(DEPDIR)/test_srcdest_table-test_srcdest_table.Po -rm -f tests/lib/$(DEPDIR)/test_stream-test_stream.Po -rm -f tests/lib/$(DEPDIR)/test_table-test_table.Po -rm -f tests/lib/$(DEPDIR)/test_timer_correctness-test_timer_correctness.Po -rm -f tests/lib/$(DEPDIR)/test_timer_performance-test_timer_performance.Po -rm -f tests/lib/$(DEPDIR)/test_ttable-test_ttable.Po -rm -f tests/lib/$(DEPDIR)/test_typelist-test_typelist.Po -rm -f tests/lib/$(DEPDIR)/test_zlog-test_zlog.Po -rm -f tests/lib/$(DEPDIR)/test_zmq-test_zmq.Po -rm -f tests/lib/cli/$(DEPDIR)/ospf6d_test_lsdb-common_cli.Po -rm -f tests/lib/cli/$(DEPDIR)/test_cli-common_cli.Po -rm -f tests/lib/cli/$(DEPDIR)/test_cli-test_cli.Po -rm -f tests/lib/cli/$(DEPDIR)/test_commands-test_commands.Po -rm -f tests/lib/cli/$(DEPDIR)/test_commands-test_commands_defun.Po -rm -f tests/lib/northbound/$(DEPDIR)/test_oper_data-test_oper_data.Po -rm -f tests/ospf6d/$(DEPDIR)/test_lsdb-test_lsdb.Po -rm -f tools/$(DEPDIR)/gen_northbound_callbacks.Po -rm -f tools/$(DEPDIR)/gen_yang_deviations.Po -rm -f tools/$(DEPDIR)/permutations.Po -rm -f tools/$(DEPDIR)/start-stop-daemon.Po -rm -f vrrpd/$(DEPDIR)/vrrp.Po -rm -f vrrpd/$(DEPDIR)/vrrp_arp.Po -rm -f vrrpd/$(DEPDIR)/vrrp_debug.Po -rm -f vrrpd/$(DEPDIR)/vrrp_main.Po -rm -f vrrpd/$(DEPDIR)/vrrp_ndisc.Po -rm -f vrrpd/$(DEPDIR)/vrrp_packet.Po -rm -f vrrpd/$(DEPDIR)/vrrp_vty.Po -rm -f vrrpd/$(DEPDIR)/vrrp_zebra.Po -rm -f vtysh/$(DEPDIR)/vtysh.Po -rm -f vtysh/$(DEPDIR)/vtysh_cmd.Po -rm -f vtysh/$(DEPDIR)/vtysh_config.Po -rm -f vtysh/$(DEPDIR)/vtysh_main.Po -rm -f vtysh/$(DEPDIR)/vtysh_user.Po -rm -f watchfrr/$(DEPDIR)/watchfrr.Po -rm -f watchfrr/$(DEPDIR)/watchfrr_errors.Po -rm -f watchfrr/$(DEPDIR)/watchfrr_vty.Po -rm -f yang/$(DEPDIR)/frr-bfdd.yang.Po -rm -f yang/$(DEPDIR)/frr-eigrpd.yang.Po -rm -f yang/$(DEPDIR)/frr-interface.yang.Plo -rm -f yang/$(DEPDIR)/frr-isisd.yang.Po -rm -f yang/$(DEPDIR)/frr-module-translator.yang.Plo -rm -f yang/$(DEPDIR)/frr-ripd.yang.Po -rm -f yang/$(DEPDIR)/frr-ripngd.yang.Po -rm -f yang/$(DEPDIR)/frr-route-types.yang.Plo -rm -f yang/$(DEPDIR)/tests_lib_northbound_test_oper_data-frr-test-module.yang.Po -rm -f zebra/$(DEPDIR)/connected.Po -rm -f zebra/$(DEPDIR)/debug.Po -rm -f zebra/$(DEPDIR)/if_ioctl.Po -rm -f zebra/$(DEPDIR)/if_ioctl_solaris.Po -rm -f zebra/$(DEPDIR)/if_netlink.Po -rm -f zebra/$(DEPDIR)/if_sysctl.Po -rm -f zebra/$(DEPDIR)/interface.Po -rm -f zebra/$(DEPDIR)/ioctl.Po -rm -f zebra/$(DEPDIR)/ioctl_solaris.Po -rm -f zebra/$(DEPDIR)/ipforward_proc.Po -rm -f zebra/$(DEPDIR)/ipforward_solaris.Po -rm -f zebra/$(DEPDIR)/ipforward_sysctl.Po -rm -f zebra/$(DEPDIR)/irdp_interface.Plo -rm -f zebra/$(DEPDIR)/irdp_main.Plo -rm -f zebra/$(DEPDIR)/irdp_packet.Plo -rm -f zebra/$(DEPDIR)/kernel_netlink.Po -rm -f zebra/$(DEPDIR)/kernel_socket.Po -rm -f zebra/$(DEPDIR)/label_manager.Po -rm -f zebra/$(DEPDIR)/main.Po -rm -f zebra/$(DEPDIR)/redistribute.Po -rm -f zebra/$(DEPDIR)/router-id.Po -rm -f zebra/$(DEPDIR)/rt_netlink.Po -rm -f zebra/$(DEPDIR)/rt_socket.Po -rm -f zebra/$(DEPDIR)/rtadv.Po -rm -f zebra/$(DEPDIR)/rtread_getmsg.Po -rm -f zebra/$(DEPDIR)/rtread_netlink.Po -rm -f zebra/$(DEPDIR)/rtread_sysctl.Po -rm -f zebra/$(DEPDIR)/rule_netlink.Po -rm -f zebra/$(DEPDIR)/rule_socket.Po -rm -f zebra/$(DEPDIR)/table_manager.Po -rm -f zebra/$(DEPDIR)/zapi_msg.Po -rm -f zebra/$(DEPDIR)/zebra_dplane.Po -rm -f zebra/$(DEPDIR)/zebra_errors.Po -rm -f zebra/$(DEPDIR)/zebra_fpm.Plo -rm -f zebra/$(DEPDIR)/zebra_fpm_dt.Plo -rm -f zebra/$(DEPDIR)/zebra_fpm_netlink.Plo -rm -f zebra/$(DEPDIR)/zebra_fpm_protobuf.Plo -rm -f zebra/$(DEPDIR)/zebra_l2.Po -rm -f zebra/$(DEPDIR)/zebra_memory.Po -rm -f zebra/$(DEPDIR)/zebra_mlag.Po -rm -f zebra/$(DEPDIR)/zebra_mpls.Po -rm -f zebra/$(DEPDIR)/zebra_mpls_netlink.Po -rm -f zebra/$(DEPDIR)/zebra_mpls_null.Po -rm -f zebra/$(DEPDIR)/zebra_mpls_openbsd.Po -rm -f zebra/$(DEPDIR)/zebra_mpls_vty.Po -rm -f zebra/$(DEPDIR)/zebra_mroute.Po -rm -f zebra/$(DEPDIR)/zebra_netns_id.Po -rm -f zebra/$(DEPDIR)/zebra_netns_notify.Po -rm -f zebra/$(DEPDIR)/zebra_nhg.Po -rm -f zebra/$(DEPDIR)/zebra_ns.Po -rm -f zebra/$(DEPDIR)/zebra_pbr.Po -rm -f zebra/$(DEPDIR)/zebra_ptm.Po -rm -f zebra/$(DEPDIR)/zebra_ptm_redistribute.Po -rm -f zebra/$(DEPDIR)/zebra_pw.Po -rm -f zebra/$(DEPDIR)/zebra_rib.Po -rm -f zebra/$(DEPDIR)/zebra_rnh.Po -rm -f zebra/$(DEPDIR)/zebra_routemap.Po -rm -f zebra/$(DEPDIR)/zebra_router.Po -rm -f zebra/$(DEPDIR)/zebra_snmp_la-zebra_snmp.Plo -rm -f zebra/$(DEPDIR)/zebra_vrf.Po -rm -f zebra/$(DEPDIR)/zebra_vty.Po -rm -f zebra/$(DEPDIR)/zebra_vxlan.Po -rm -f zebra/$(DEPDIR)/zserv.Po -rm -f Makefile maintainer-clean-am: distclean-am maintainer-clean-generic mostlyclean: mostlyclean-am mostlyclean-am: mostlyclean-compile mostlyclean-generic \ mostlyclean-libtool pdf-am: ps: ps-am ps-am: uninstall-am: uninstall-binPROGRAMS uninstall-dist_examplesDATA \ uninstall-dist_yangmodelsDATA uninstall-eigrpdheaderHEADERS \ uninstall-libLTLIBRARIES uninstall-local \ uninstall-moduleLTLIBRARIES uninstall-nodist_pkgincludeHEADERS \ uninstall-ospfapiheaderHEADERS uninstall-ospfdheaderHEADERS \ uninstall-pkgincludeHEADERS uninstall-rcSCRIPTS \ uninstall-rstman1DATA uninstall-rstman8DATA \ uninstall-sbinPROGRAMS uninstall-sbinSCRIPTS .MAKE: all check check-am install install-am install-strip .PHONY: CTAGS GTAGS TAGS all all-am am--depfiles am--refresh check \ check-am clean clean-binPROGRAMS clean-checkPROGRAMS \ clean-cscope clean-generic clean-libLTLIBRARIES clean-libtool \ clean-local clean-moduleLTLIBRARIES clean-noinstLIBRARIES \ clean-noinstPROGRAMS clean-sbinPROGRAMS cscope cscopelist-am \ ctags ctags-am dist dist-all dist-bzip2 dist-gzip dist-lzip \ dist-shar dist-tarZ dist-xz dist-zip distcheck distclean \ distclean-compile distclean-generic distclean-hdr \ distclean-libtool distclean-tags distcleancheck distdir \ distuninstallcheck dvi dvi-am html html-am info info-am \ install install-am install-binPROGRAMS install-data \ install-data-am install-data-local install-dist_examplesDATA \ install-dist_yangmodelsDATA install-dvi install-dvi-am \ install-eigrpdheaderHEADERS install-exec install-exec-am \ install-html install-html-am install-info install-info-am \ install-libLTLIBRARIES install-man install-moduleLTLIBRARIES \ install-nodist_pkgincludeHEADERS install-ospfapiheaderHEADERS \ install-ospfdheaderHEADERS install-pdf install-pdf-am \ install-pkgincludeHEADERS install-ps install-ps-am \ install-rcSCRIPTS install-rstman1DATA install-rstman8DATA \ install-sbinPROGRAMS install-sbinSCRIPTS install-strip \ installcheck installcheck-am installdirs maintainer-clean \ maintainer-clean-generic mostlyclean mostlyclean-compile \ mostlyclean-generic mostlyclean-libtool pdf pdf-am ps ps-am \ tags tags-am uninstall uninstall-am uninstall-binPROGRAMS \ uninstall-dist_examplesDATA uninstall-dist_yangmodelsDATA \ uninstall-eigrpdheaderHEADERS uninstall-libLTLIBRARIES \ uninstall-local uninstall-moduleLTLIBRARIES \ uninstall-nodist_pkgincludeHEADERS \ uninstall-ospfapiheaderHEADERS uninstall-ospfdheaderHEADERS \ uninstall-pkgincludeHEADERS uninstall-rcSCRIPTS \ uninstall-rstman1DATA uninstall-rstman8DATA \ uninstall-sbinPROGRAMS uninstall-sbinSCRIPTS .PRECIOUS: Makefile # these two targets are provided to easily grab autoconf/Makefile variables # you can use either: # eval `make VARFD=3 shvar-CFLAGS 3>&1 1>&2` # CFLAGS="`make VARFD=3 var-CFLAGS 3>&1 1>&2`" # where the former can be used to set several variables at once. Note the # fd redirections -- this is to prevent garbage from make rebuilding other # targets from causing issues. .PHONY: shvar-% var-% VARFD ?= 1 shvar-%: @echo "$*=\"$($*)\"" >&$(VARFD) var-%: @echo "$($*)" >&$(VARFD) clippy-only: Makefile lib/clippy config.h .PHONY: clippy-only $(AUTOMAKE_DUMMY)install-moduleLTLIBRARIES: install-libLTLIBRARIES $(AUTOMAKE_DUMMY)install-binPROGRAMS: install-libLTLIBRARIES $(AUTOMAKE_DUMMY)install-sbinPROGRAMS: install-libLTLIBRARIES # You can set these variables from the command line. SPHINXOPTS ?= PAPER ?= # # real-file sphinx targets that work for dependencies # doc/%/_build/.doctrees/environment.pickle: $(AM_V_SPHINX) ( \ subdoc="$@"; subdoc="$${subdoc#doc/}"; subdoc="doc/$${subdoc%%/*}"; \ $(PYTHON) $(PYSPHINX) -a -q -b text -d "$${subdoc}/_build/.doctrees" \ $(ALLSPHINXOPTS) "$(top_srcdir)/$${subdoc}" "$${subdoc}/_build/text" \ ) doc/%/_build/html/.buildinfo: doc/%/_build/.doctrees/environment.pickle $(AM_V_SPHINX) ( \ subdoc="$@"; subdoc="$${subdoc#doc/}"; subdoc="doc/$${subdoc%%/*}"; \ $(PYTHON) $(PYSPHINX) -q -b html -d "$${subdoc}/_build/.doctrees" \ $(ALLSPHINXOPTS) "$(top_srcdir)/$${subdoc}" "$${subdoc}/_build/html" \ ) .PRECIOUS: doc/%/_build/texinfo/frr.texi doc/%/_build/texinfo/frr.texi: doc/%/_build/.doctrees/environment.pickle $(AM_V_SPHINX) ( \ subdoc="$@"; subdoc="$${subdoc#doc/}"; subdoc="doc/$${subdoc%%/*}"; \ $(PYTHON) $(PYSPHINX) -q -b texinfo -d "$${subdoc}/_build/.doctrees" \ $(ALLSPHINXOPTS) "$(top_srcdir)/$${subdoc}" "$${subdoc}/_build/texinfo" \ ) doc/%/_build/texinfo/frr.info: doc/%/_build/texinfo/frr.texi $(AM_V_MAKEINFO)$(MAKEINFO) --no-split -o '$@' '$<' doc/%/_build/man/man.stamp: doc/%/_build/.doctrees/environment.pickle $(AM_V_SPHINX) ( \ subdoc="$@"; subdoc="$${subdoc#doc/}"; subdoc="doc/$${subdoc%%/*}"; \ $(MKDIR_P) "$${subdoc}/_build/man"; touch $@.tmp; \ $(PYTHON) $(PYSPHINX) -a -q -b man -d "$${subdoc}/_build/.doctrees" \ $(ALLSPHINXOPTS) "$(top_srcdir)/$${subdoc}" "$${subdoc}/_build/man" && \ mv -f $@.tmp $@ \ ) .PRECIOUS: $(M_SPHINXTARGETS) $(M_SPHINXTARGETS): doc/%/_build/.doctrees/environment.pickle $(AM_V_SPHINX) ( \ target="$@"; \ builder="$${target##*/}"; \ subdoc="$${target#doc/}"; subdoc="doc/$${subdoc%%/*}"; \ rm -rf "$@"; \ $(PYTHON) $(PYSPHINX) -q -b $${builder} -d $${subdoc}/_build/.doctrees \ $(ALLSPHINXOPTS) $(top_srcdir)/$${subdoc} $@ \ ) .PHONY: doc/%/_build/latexpdf doc/%/_build/latexpdf: doc/%/_build/latex @make -C $< all-pdf .PHONY: doc/help doc/help: @echo "Please use \`make doc/{user,manpages,developer}/' where is one of" @echo " html to make standalone HTML files" @echo " dirhtml to make HTML files named index.html in directories" @echo " singlehtml to make a single large HTML file" @echo " pickle to make pickle files" @echo " json to make JSON files" @echo " htmlhelp to make HTML files and a HTML help project" @echo " qthelp to make HTML files and a qthelp project" @echo " applehelp to make an Apple Help Book" @echo " devhelp to make HTML files and a Devhelp project" @echo " epub to make an epub" @echo " latex to make LaTeX files, you can set PAPER=a4 or PAPER=letter" @echo " latexpdf to make LaTeX files and run them through pdflatex" @echo " latexpdfja to make LaTeX files and run them through platex/dvipdfmx" @echo " text to make text files" @echo " man to make manual pages" @echo " texinfo to make Texinfo files" @echo " info to make Texinfo files and run them through makeinfo" @echo " gettext to make PO message catalogs" @echo " changes to make an overview of all changed/added/deprecated items" @echo " xml to make Docutils-native XML files" @echo " pseudoxml to make pseudoxml-XML files for display purposes" @echo " linkcheck to check all external links for integrity" @echo " doctest to run all doctests embedded in the documentation (if enabled)" @echo " coverage to run coverage check of the documentation (if enabled)" $(USERBUILD)/.doctrees/environment.pickle: $(user_RSTFILES) # # standard targets # .PHONY: info html pdf info: $(USERBUILD)/texinfo/frr.info html: $(USERBUILD)/html/.buildinfo pdf: $(USERBUILD)/latexpdf # # hook-ins for clean / install / doc # .PHONY: clean-userdocs clean-local: clean-userdocs clean-userdocs: -rm -rf "$(USERBUILD)" # INSTALL_INFO=install-info .PHONY: install-info uninstall-info install-html uninstall-html install-info: $(USERBUILD)/texinfo/frr.info $(MKDIR_P) "$(DESTDIR)$(infodir)" $(INSTALL_DATA) "$<" "$(DESTDIR)$(infodir)" [ -z "${DESTDIR}" ] && $(INSTALL_INFO) --info-dir="$(DESTDIR)$(infodir)" "$<" || true uninstall-info: $(USERBUILD)/texinfo/frr.info -rm -f "$(DESTDIR)$(infodir)/$<" [ -z "${DESTDIR}" ] && $(INSTALL_INFO) --delete --info-dir="$(DESTDIR)$(infodir)" "$<" || true install-html: $(USERBUILD)/html/.buildinfo $(MKDIR_P) "$(DESTDIR)$(htmldir)" cp -r "$(USERBUILD)/html" "$(DESTDIR)$(htmldir)" uninstall-html: -rm -rf "$(DESTDIR)$(htmldir)/html" .PHONY: install-data-local uninstall-local # leave the comments in, this was causing weird reordering issues in automake install-data-local: $(TARGET_INSTALL_INFO) $(TARGET_INSTALL_HTML) # uninstall-local: $(TARGET_UNINSTALL_INFO) $(TARGET_UNINSTALL_HTML) # doc: $(DOC_INFO) $(DOC_HTML) doc/manpages/_build/.doctrees/environment.pickle: $(man_RSTFILES) # dependency $(man8) $(man1): $(MANBUILD)/man.stamp # # hook-ins for clean / doc # (install is handled by automake _DATA) # clean-local: clean-manpages .PHONY: clean-manpages clean-manpages: -rm -rf $(MANPARENT) doc: doc-man .PHONY: doc-man doc-man: $(rstman8_DATA) $(rstman1_DATA) $(DEVBUILD)/.doctrees/environment.pickle: $(dev_RSTFILES) # # nothing built automatically for "all" target. # # # standard targets # developer-info: $(DEVBUILD)/texinfo/frr.info developer-html: $(DEVBUILD)/html/.buildinfo developer-pdf: $(DEVBUILD)/latexpdf # # hook-in for clean # .PHONY: clean-devdocs clean-local: clean-devdocs clean-devdocs: -rm -rf "$(DEVBUILD)" lib/if_clippy.c: $(CLIPPY_DEPS) lib/if.lo: lib/if_clippy.c lib/plist_clippy.c: $(CLIPPY_DEPS) lib/plist.lo: lib/plist_clippy.c lib/nexthop_group_clippy.c: $(CLIPPY_DEPS) lib/nexthop_group.lo: lib/nexthop_group_clippy.c lib/northbound_cli_clippy.c: $(CLIPPY_DEPS) lib/northbound_cli.lo: lib/northbound_cli_clippy.c lib/vty_clippy.c: $(CLIPPY_DEPS) lib/vty.lo: lib/vty_clippy.c lib/log_vty_clippy.c: $(CLIPPY_DEPS) lib/log_vty.lo: lib/log_vty_clippy.c @BUILD_CLIPPY_FALSE@@HOSTTOOLS_CLIPPY_TRUE@$(CLIPPY): @BUILD_CLIPPY_FALSE@@HOSTTOOLS_CLIPPY_TRUE@ @$(MAKE) -C $(top_builddir)/hosttools lib/route_types.h lib/clippy .c_clippy.c: $(AM_V_CLIPPY) $(CLIPPY) $(top_srcdir)/python/clidef.py -o $@ $< .l.c: $(AM_V_LEX)$(am__skiplex) $(LEXCOMPILE) $< .y.c: $(AM_V_YACC)$(am__skipyacc) $(YACCCOMPILE) $< $(lib_clippy_OBJECTS): lib/route_types.h $(lib_libfrr_la_OBJECTS): lib/route_types.h lib/command_lex.h: lib/command_lex.c @if test ! -f $@; then rm -f "lib/command_lex.c"; else :; fi @if test ! -f $@; then $(MAKE) $(AM_MAKEFLAGS) "lib/command_lex.c"; else :; fi lib/command_lex.lo: lib/command_parse.h lib/command_parse.lo: lib/command_lex.h lib/clippy-command_lex.$(OBJEXT): lib/command_parse.h lib/clippy-command_parse.$(OBJEXT): lib/command_lex.h lib/lib_clippy-command_lex.$(OBJEXT): lib/command_parse.h lib/lib_clippy-command_parse.$(OBJEXT): lib/command_lex.h lib/route_types.h: $(top_srcdir)/lib/route_types.txt $(top_srcdir)/lib/route_types.pl $(PERL) $(top_srcdir)/lib/route_types.pl < $(top_srcdir)/lib/route_types.txt > $@ @GIT_VERSION_TRUE@.SILENT: lib/gitversion.h lib/gitversion.h.tmp @GIT_VERSION_TRUE@lib/gitversion.h.tmp: $(top_srcdir)/.git @GIT_VERSION_TRUE@ $(PERL) $(top_srcdir)/lib/gitversion.pl $(top_srcdir) > ${GITH}.tmp @GIT_VERSION_TRUE@lib/gitversion.h: lib/gitversion.h.tmp @GIT_VERSION_TRUE@ { test -f ${GITH} && diff -s -q ${GITH}.tmp ${GITH}; } || cp ${GITH}.tmp ${GITH} @GIT_VERSION_FALSE@lib/gitversion.h: @GIT_VERSION_FALSE@ true .PHONY: $(PHONY_GITVERSION) zebra/debug_clippy.c: $(CLIPPY_DEPS) zebra/debug.$(OBJEXT): zebra/debug_clippy.c zebra/zebra_mlag_clippy.c: $(CLIPPY_DEPS) zebra/zebra_mlag.$(OBJEXT): zebra/zebra_mlag_clippy.c zebra/zebra_vty_clippy.c: $(CLIPPY_DEPS) zebra/interface_clippy.c: $(CLIPPY_DEPS) zebra/interface.$(OBJEXT): zebra/interface_clippy.c zebra/zebra_vty.$(OBJEXT): zebra/zebra_vty_clippy.c zebra/zebra_routemap_clippy.c: $(CLIPPY_DEPS) zebra/zebra_routemap.$(OBJEXT): zebra/zebra_routemap_clippy.c # Rules @HAVE_PROTOBUF_TRUE@.proto.pb.h: @HAVE_PROTOBUF_TRUE@ $(PROTOC) -I$(top_srcdir) --cpp_out=$(top_srcdir) $(top_srcdir)/$^ @HAVE_PROTOBUF_TRUE@.proto.pb-c.c: @HAVE_PROTOBUF_TRUE@ $(AM_V_PROTOC_C)$(PROTOC_C) -I$(top_srcdir) --c_out=$(top_srcdir) $(top_srcdir)/$^ @HAVE_PROTOBUF_TRUE@.pb-c.c.pb-c.h: @HAVE_PROTOBUF_TRUE@ @/bin/true .proto.pb.cc: $(AM_V_PROTOC)$(PROTOC) -I$(top_srcdir) --cpp_out=$(top_srcdir) $(top_srcdir)/$^ .proto.grpc.pb.cc: $(AM_V_PROTOC)$(PROTOC) -I$(top_srcdir) --grpc_out=$(top_srcdir) --plugin=protoc-gen-grpc=`which grpc_cpp_plugin` $(top_srcdir)/$^ .PHONY: solaris/all @SOLARIS_TRUE@all: solaris/all @SOLARIS_TRUE@solaris/all: @SOLARIS_TRUE@ @make -s -C solaris all bgpd/bgp_evpn_vty_clippy.c: $(CLIPPY_DEPS) bgpd/bgp_evpn_vty.$(OBJEXT): bgpd/bgp_evpn_vty_clippy.c bgpd/bgp_vty_clippy.c: $(CLIPPY_DEPS) bgpd/bgp_vty.$(OBJEXT): bgpd/bgp_vty_clippy.c bgpd/bgp_route_clippy.c: $(CLIPPY_DEPS) bgpd/bgp_route.$(OBJEXT): bgpd/bgp_route_clippy.c bgpd/bgp_debug_clippy.c: $(CLIPPY_DEPS) bgpd/bgp_debug.$(OBJEXT): bgpd/bgp_debug_clippy.c bgpd/bgp_routemap_clippy.c: $(CLIPPY_DEPS) bgpd/bgp_routemap.$(OBJEXT): bgpd/bgp_routemap_clippy.c bgpd/bgp_rpki_clippy.c: $(CLIPPY_DEPS) $(AUTOMAKE_DUMMY)bgpd/bgpd_bgpd_rpki_la-bgp_rpki.lo: bgpd/bgp_rpki_clippy.c $(AUTOMAKE_DUMMY)bgpd/bgpd_rpki_la-bgp_rpki.lo: bgpd/bgp_rpki_clippy.c bgpd/bgp_bmp_clippy.c: $(CLIPPY_DEPS) bgpd/bgp_bmp.lo: bgpd/bgp_bmp_clippy.c ripd/rip_cli_clippy.c: $(CLIPPY_DEPS) ripd/rip_cli.$(OBJEXT): ripd/rip_cli_clippy.c ripngd/ripng_cli_clippy.c: $(CLIPPY_DEPS) ripngd/ripng_cli.$(OBJEXT): ripngd/ripng_cli_clippy.c ospfd/ospf_vty_clippy.c: $(CLIPPY_DEPS) ospfd/ospf_vty.$(OBJEXT): ospfd/ospf_vty_clippy.c isisd/isis_cli_clippy.c: $(CLIPPY_DEPS) isisd/isis_cli.$(OBJEXT): isisd/isis_cli_clippy.c ldpd/ldp_vty_cmds_clippy.c: $(CLIPPY_DEPS) ldpd/ldp_vty_cmds.$(OBJEXT): ldpd/ldp_vty_cmds_clippy.c eigrpd/eigrp_vty_clippy.c: $(CLIPPY_DEPS) eigrpd/eigrp_vty.$(OBJEXT): eigrpd/eigrp_vty_clippy.c eigrpd/eigrp_cli_clippy.c: $(CLIPPY_DEPS) eigrpd/eigrp_cli.$(OBJEXT): eigrpd/eigrp_cli_clippy.c sharpd/sharp_vty_clippy.c: $(CLIPPY_DEPS) sharpd/sharp_vty.$(OBJEXT): sharpd/sharp_vty_clippy.c pimd/pim_cmd_clippy.c: $(CLIPPY_DEPS) pimd/pim_cmd.$(OBJEXT): pimd/pim_cmd_clippy.c pbrd/pbr_vty_clippy.c: $(CLIPPY_DEPS) pbrd/pbr_vty.$(OBJEXT): pbrd/pbr_vty_clippy.c pbrd/pbr_debug_clippy.c: $(CLIPPY_DEPS) pbrd/pbr_debug.$(OBJEXT): pbrd/pbr_debug_clippy.c staticd/static_vty_clippy.c: $(CLIPPY_DEPS) staticd/static_vty.$(OBJEXT): staticd/static_vty_clippy.c bfdd/bfdd_vty_clippy.c: $(CLIPPY_DEPS) bfdd/bfdd_vty.$(OBJEXT): bfdd/bfdd_vty_clippy.c bfdd/bfdd_cli_clippy.c: $(CLIPPY_DEPS) bfdd/bfdd_cli.$(OBJEXT): bfdd/bfdd_cli_clippy.c .yang.yang.c: $(AM_V_GEN)$(PYTHON) $(top_srcdir)/yang/embedmodel.py $^ $@ .yin.yin.c: $(AM_V_GEN)$(PYTHON) $(top_srcdir)/yang/embedmodel.py $^ $@ # XXX: disable support for libyang custom user types temporarily to facilitate # the transition from libyang 0.x to libyang 1.x. #lib_libfrr_la_SOURCES += yang/libyang_plugins/frr_user_types.c vrrpd/vrrp_vty_clippy.c: $(CLIPPY_DEPS) vrrpd/vrrp_vty.$(OBJEXT): vrrpd/vrrp_vty_clippy.c vtysh/vtysh_cmd.c: $(vtysh_scan) vtysh/extract.pl $(AM_V_EXTRACT) vtysh/extract.pl $(vtysh_scan) > vtysh/vtysh_cmd.c tests/lib/cli/tests_lib_cli_test_cli-test_cli.$(OBJEXT): tests/lib/cli/test_cli_clippy.c tests/lib/cli/test_cli-test_cli.$(OBJEXT): tests/lib/cli/test_cli_clippy.c tests/ospf6d/tests_ospf6d_test_lsdb-test_lsdb.$(OBJEXT): tests/ospf6d/test_lsdb_clippy.c tests/ospf6d/test_lsdb-test_lsdb.$(OBJEXT): tests/ospf6d/test_lsdb_clippy.c tests/lib/cli/test_commands_defun.c: vtysh/vtysh_cmd.c sed \ -e 's%"vtysh/vtysh\.h"%"tests/helpers/c/tests.h"%' \ -e 's/vtysh_init_cmd/test_init_cmd/' \ -e 's/VTYSH_[A-Z][A-Z_0-9]*/0/g' \ < vtysh/vtysh_cmd.c \ > "$@" tests/isisd/test_fuzz_isis_tlv_tests.h: $(top_srcdir)/tests/isisd/test_fuzz_isis_tlv_tests.h.gz gzip -d < $(top_srcdir)/tests/isisd/test_fuzz_isis_tlv_tests.h.gz > "$@" tests/isisd/tests_isisd_test_fuzz_isis_tlv-test_fuzz_isis_tlv.$(OBJEXT): \ tests/isisd/test_fuzz_isis_tlv_tests.h tests/isisd/test_fuzz_isis_tlv-test_fuzz_isis_tlv.$(OBJEXT): \ tests/isisd/test_fuzz_isis_tlv_tests.h .PHONY: tests/tests.xml tests/tests.xml: $(check_PROGRAMS) ( cd tests; $(PYTHON) ../$(srcdir)/tests/runtests.py --junitxml=tests.xml -v ../$(srcdir)/tests; ) check: tests/tests.xml clean-local: clean-tests .PHONY: clean-tests clean-tests: -rm -f tests/tests.xml topotests-build: ## Builds docker images for topotests $(TOPOTESTS_DIR)/docker/build.sh topotests: ## Runs topotests $(TOPOTESTS_DIR)/docker/frr-topotests.sh clean-local: clean-python .PHONY: clean-python clean-python: find . -name __pycache__ -o -name .pytest_cache | xargs rm -rf find . -name "*.pyc" -o -name "*_clippy.c" | xargs rm -f redistclean: $(MAKE) distclean CONFIG_CLEAN_FILES="$(filter-out $(EXTRA_DIST), $(CONFIG_CLEAN_FILES))" indent: tools/indent.py `find sharpd bgpd eigrpd include isisd lib nhrpd ospf6d ospfd pimd qpb ripd vtysh zebra -name '*.[ch]' | grep -v include/linux` @HAVE_GCOV_TRUE@coverage: check @HAVE_GCOV_TRUE@ @ find . -name '*.o' -exec gcov {} \; @HAVE_GCOV_TRUE@yorn: @HAVE_GCOV_TRUE@ @ echo "OK to upload coverage to https://coverage.io [y/N]:" @HAVE_GCOV_TRUE@ @ read yn; test "$$yn" = "y" @HAVE_GCOV_TRUE@upload-check-coverage: @HAVE_GCOV_TRUE@ @ if [ "x${COMMIT}" = "x" ]; then echo "COMMIT required"; exit 1; fi @HAVE_GCOV_TRUE@ @ if [ "x${TOKEN}" = "x" ]; then echo "TOKEN required"; exit 1; fi @HAVE_GCOV_TRUE@ curl -s https://codecov.io/bash | bash -s - -C ${COMMIT} -t ${TOKEN} @HAVE_GCOV_TRUE@force-check-coverage: coverage upload-check-coverage @HAVE_GCOV_TRUE@check-coverage: coverage yorn upload-check-coverage # Tell versions [3.59,3.63) of GNU make to not export all variables. # Otherwise a system limit (for SysV at least) may be exceeded. .NOEXPORT: frr-7.2.1/README.md0000644000000000000000000000401713610377563010475 00000000000000FRRouting ========= FRR is free software that implements and manages various IPv4 and IPv6 routing protocols. It runs on nearly all distributions of Linux and BSD as well as Solaris and supports all modern CPU architectures. FRR currently supports the following protocols: * BGP * OSPFv2 * OSPFv3 * RIPv1 * RIPv2 * RIPng * IS-IS * PIM-SM/MSDP * LDP * BFD * Babel * PBR * OpenFabric * VRRP * EIGRP (alpha) * NHRP (alpha) Installation & Use ------------------ Packages are available for various distributions on our [releases page](https://github.com/FRRouting/frr/releases). Snaps are also available [here](https://snapcraft.io/frr). Instructions on building and installing from source for supported platforms may be found [here](http://docs.frrouting.org/projects/dev-guide/en/latest/building.html). Once installed, please refer to the [user guide](http://docs.frrouting.org/) for instructions on use. Community --------- The FRRouting email list server is located [here](https://lists.frrouting.org/listinfo) and offers the following public lists: | Topic | List | |-------------------|------------------------------| | Development | dev@lists.frrouting.org | | Users & Operators | frog@lists.frrouting.org | | Announcements | announce@lists.frrouting.org | For chat, we currently use [Slack](https://frrouting.slack.com). You can join by clicking the "Slack" link under the [Participate](https://frrouting.org/#participate) section of our website. Contributing ------------ FRR maintains [developer's documentation](http://docs.frrouting.org/projects/dev-guide/en/latest/index.html) which contains the [project workflow](http://docs.frrouting.org/projects/dev-guide/en/latest/workflow.html) and expectations for contributors. Some technical documentation on project internals is also available. We welcome and appreciate all contributions, no matter how small! Security -------- To report security issues, please use our security mailing list: ``` security [at] lists.frrouting.org ``` frr-7.2.1/aclocal.m40000644000000000000000000012340313610377563011057 00000000000000# generated automatically by aclocal 1.16.1 -*- Autoconf -*- # Copyright (C) 1996-2018 Free Software Foundation, Inc. # This file is free software; the Free Software Foundation # gives unlimited permission to copy and/or distribute it, # with or without modifications, as long as this notice is preserved. # This program is distributed in the hope that it will be useful, # but WITHOUT ANY WARRANTY, to the extent permitted by law; without # even the implied warranty of MERCHANTABILITY or FITNESS FOR A # PARTICULAR PURPOSE. m4_ifndef([AC_CONFIG_MACRO_DIRS], [m4_defun([_AM_CONFIG_MACRO_DIRS], [])m4_defun([AC_CONFIG_MACRO_DIRS], [_AM_CONFIG_MACRO_DIRS($@)])]) m4_ifndef([AC_AUTOCONF_VERSION], [m4_copy([m4_PACKAGE_VERSION], [AC_AUTOCONF_VERSION])])dnl m4_if(m4_defn([AC_AUTOCONF_VERSION]), [2.69],, [m4_warning([this file was generated for autoconf 2.69. You have another version of autoconf. It may work, but is not guaranteed to. If you have problems, you may need to regenerate the build system entirely. To do so, use the procedure documented by the package, typically 'autoreconf'.])]) # Copyright (C) 2002-2018 Free Software Foundation, Inc. # # This file is free software; the Free Software Foundation # gives unlimited permission to copy and/or distribute it, # with or without modifications, as long as this notice is preserved. # AM_AUTOMAKE_VERSION(VERSION) # ---------------------------- # Automake X.Y traces this macro to ensure aclocal.m4 has been # generated from the m4 files accompanying Automake X.Y. # (This private macro should not be called outside this file.) AC_DEFUN([AM_AUTOMAKE_VERSION], [am__api_version='1.16' dnl Some users find AM_AUTOMAKE_VERSION and mistake it for a way to dnl require some minimum version. Point them to the right macro. m4_if([$1], [1.16.1], [], [AC_FATAL([Do not call $0, use AM_INIT_AUTOMAKE([$1]).])])dnl ]) # _AM_AUTOCONF_VERSION(VERSION) # ----------------------------- # aclocal traces this macro to find the Autoconf version. # This is a private macro too. Using m4_define simplifies # the logic in aclocal, which can simply ignore this definition. m4_define([_AM_AUTOCONF_VERSION], []) # AM_SET_CURRENT_AUTOMAKE_VERSION # ------------------------------- # Call AM_AUTOMAKE_VERSION and AM_AUTOMAKE_VERSION so they can be traced. # This function is AC_REQUIREd by AM_INIT_AUTOMAKE. AC_DEFUN([AM_SET_CURRENT_AUTOMAKE_VERSION], [AM_AUTOMAKE_VERSION([1.16.1])dnl m4_ifndef([AC_AUTOCONF_VERSION], [m4_copy([m4_PACKAGE_VERSION], [AC_AUTOCONF_VERSION])])dnl _AM_AUTOCONF_VERSION(m4_defn([AC_AUTOCONF_VERSION]))]) # AM_AUX_DIR_EXPAND -*- Autoconf -*- # Copyright (C) 2001-2018 Free Software Foundation, Inc. # # This file is free software; the Free Software Foundation # gives unlimited permission to copy and/or distribute it, # with or without modifications, as long as this notice is preserved. # For projects using AC_CONFIG_AUX_DIR([foo]), Autoconf sets # $ac_aux_dir to '$srcdir/foo'. In other projects, it is set to # '$srcdir', '$srcdir/..', or '$srcdir/../..'. # # Of course, Automake must honor this variable whenever it calls a # tool from the auxiliary directory. The problem is that $srcdir (and # therefore $ac_aux_dir as well) can be either absolute or relative, # depending on how configure is run. This is pretty annoying, since # it makes $ac_aux_dir quite unusable in subdirectories: in the top # source directory, any form will work fine, but in subdirectories a # relative path needs to be adjusted first. # # $ac_aux_dir/missing # fails when called from a subdirectory if $ac_aux_dir is relative # $top_srcdir/$ac_aux_dir/missing # fails if $ac_aux_dir is absolute, # fails when called from a subdirectory in a VPATH build with # a relative $ac_aux_dir # # The reason of the latter failure is that $top_srcdir and $ac_aux_dir # are both prefixed by $srcdir. In an in-source build this is usually # harmless because $srcdir is '.', but things will broke when you # start a VPATH build or use an absolute $srcdir. # # So we could use something similar to $top_srcdir/$ac_aux_dir/missing, # iff we strip the leading $srcdir from $ac_aux_dir. That would be: # am_aux_dir='\$(top_srcdir)/'`expr "$ac_aux_dir" : "$srcdir//*\(.*\)"` # and then we would define $MISSING as # MISSING="\${SHELL} $am_aux_dir/missing" # This will work as long as MISSING is not called from configure, because # unfortunately $(top_srcdir) has no meaning in configure. # However there are other variables, like CC, which are often used in # configure, and could therefore not use this "fixed" $ac_aux_dir. # # Another solution, used here, is to always expand $ac_aux_dir to an # absolute PATH. The drawback is that using absolute paths prevent a # configured tree to be moved without reconfiguration. AC_DEFUN([AM_AUX_DIR_EXPAND], [AC_REQUIRE([AC_CONFIG_AUX_DIR_DEFAULT])dnl # Expand $ac_aux_dir to an absolute path. am_aux_dir=`cd "$ac_aux_dir" && pwd` ]) # AM_CONDITIONAL -*- Autoconf -*- # Copyright (C) 1997-2018 Free Software Foundation, Inc. # # This file is free software; the Free Software Foundation # gives unlimited permission to copy and/or distribute it, # with or without modifications, as long as this notice is preserved. # AM_CONDITIONAL(NAME, SHELL-CONDITION) # ------------------------------------- # Define a conditional. AC_DEFUN([AM_CONDITIONAL], [AC_PREREQ([2.52])dnl m4_if([$1], [TRUE], [AC_FATAL([$0: invalid condition: $1])], [$1], [FALSE], [AC_FATAL([$0: invalid condition: $1])])dnl AC_SUBST([$1_TRUE])dnl AC_SUBST([$1_FALSE])dnl _AM_SUBST_NOTMAKE([$1_TRUE])dnl _AM_SUBST_NOTMAKE([$1_FALSE])dnl m4_define([_AM_COND_VALUE_$1], [$2])dnl if $2; then $1_TRUE= $1_FALSE='#' else $1_TRUE='#' $1_FALSE= fi AC_CONFIG_COMMANDS_PRE( [if test -z "${$1_TRUE}" && test -z "${$1_FALSE}"; then AC_MSG_ERROR([[conditional "$1" was never defined. Usually this means the macro was only invoked conditionally.]]) fi])]) # Copyright (C) 1999-2018 Free Software Foundation, Inc. # # This file is free software; the Free Software Foundation # gives unlimited permission to copy and/or distribute it, # with or without modifications, as long as this notice is preserved. # There are a few dirty hacks below to avoid letting 'AC_PROG_CC' be # written in clear, in which case automake, when reading aclocal.m4, # will think it sees a *use*, and therefore will trigger all it's # C support machinery. Also note that it means that autoscan, seeing # CC etc. in the Makefile, will ask for an AC_PROG_CC use... # _AM_DEPENDENCIES(NAME) # ---------------------- # See how the compiler implements dependency checking. # NAME is "CC", "CXX", "OBJC", "OBJCXX", "UPC", or "GJC". # We try a few techniques and use that to set a single cache variable. # # We don't AC_REQUIRE the corresponding AC_PROG_CC since the latter was # modified to invoke _AM_DEPENDENCIES(CC); we would have a circular # dependency, and given that the user is not expected to run this macro, # just rely on AC_PROG_CC. AC_DEFUN([_AM_DEPENDENCIES], [AC_REQUIRE([AM_SET_DEPDIR])dnl AC_REQUIRE([AM_OUTPUT_DEPENDENCY_COMMANDS])dnl AC_REQUIRE([AM_MAKE_INCLUDE])dnl AC_REQUIRE([AM_DEP_TRACK])dnl m4_if([$1], [CC], [depcc="$CC" am_compiler_list=], [$1], [CXX], [depcc="$CXX" am_compiler_list=], [$1], [OBJC], [depcc="$OBJC" am_compiler_list='gcc3 gcc'], [$1], [OBJCXX], [depcc="$OBJCXX" am_compiler_list='gcc3 gcc'], [$1], [UPC], [depcc="$UPC" am_compiler_list=], [$1], [GCJ], [depcc="$GCJ" am_compiler_list='gcc3 gcc'], [depcc="$$1" am_compiler_list=]) AC_CACHE_CHECK([dependency style of $depcc], [am_cv_$1_dependencies_compiler_type], [if test -z "$AMDEP_TRUE" && test -f "$am_depcomp"; then # We make a subdir and do the tests there. Otherwise we can end up # making bogus files that we don't know about and never remove. For # instance it was reported that on HP-UX the gcc test will end up # making a dummy file named 'D' -- because '-MD' means "put the output # in D". rm -rf conftest.dir mkdir conftest.dir # Copy depcomp to subdir because otherwise we won't find it if we're # using a relative directory. cp "$am_depcomp" conftest.dir cd conftest.dir # We will build objects and dependencies in a subdirectory because # it helps to detect inapplicable dependency modes. For instance # both Tru64's cc and ICC support -MD to output dependencies as a # side effect of compilation, but ICC will put the dependencies in # the current directory while Tru64 will put them in the object # directory. mkdir sub am_cv_$1_dependencies_compiler_type=none if test "$am_compiler_list" = ""; then am_compiler_list=`sed -n ['s/^#*\([a-zA-Z0-9]*\))$/\1/p'] < ./depcomp` fi am__universal=false m4_case([$1], [CC], [case " $depcc " in #( *\ -arch\ *\ -arch\ *) am__universal=true ;; esac], [CXX], [case " $depcc " in #( *\ -arch\ *\ -arch\ *) am__universal=true ;; esac]) for depmode in $am_compiler_list; do # Setup a source with many dependencies, because some compilers # like to wrap large dependency lists on column 80 (with \), and # we should not choose a depcomp mode which is confused by this. # # We need to recreate these files for each test, as the compiler may # overwrite some of them when testing with obscure command lines. # This happens at least with the AIX C compiler. : > sub/conftest.c for i in 1 2 3 4 5 6; do echo '#include "conftst'$i'.h"' >> sub/conftest.c # Using ": > sub/conftst$i.h" creates only sub/conftst1.h with # Solaris 10 /bin/sh. echo '/* dummy */' > sub/conftst$i.h done echo "${am__include} ${am__quote}sub/conftest.Po${am__quote}" > confmf # We check with '-c' and '-o' for the sake of the "dashmstdout" # mode. It turns out that the SunPro C++ compiler does not properly # handle '-M -o', and we need to detect this. Also, some Intel # versions had trouble with output in subdirs. am__obj=sub/conftest.${OBJEXT-o} am__minus_obj="-o $am__obj" case $depmode in gcc) # This depmode causes a compiler race in universal mode. test "$am__universal" = false || continue ;; nosideeffect) # After this tag, mechanisms are not by side-effect, so they'll # only be used when explicitly requested. if test "x$enable_dependency_tracking" = xyes; then continue else break fi ;; msvc7 | msvc7msys | msvisualcpp | msvcmsys) # This compiler won't grok '-c -o', but also, the minuso test has # not run yet. These depmodes are late enough in the game, and # so weak that their functioning should not be impacted. am__obj=conftest.${OBJEXT-o} am__minus_obj= ;; none) break ;; esac if depmode=$depmode \ source=sub/conftest.c object=$am__obj \ depfile=sub/conftest.Po tmpdepfile=sub/conftest.TPo \ $SHELL ./depcomp $depcc -c $am__minus_obj sub/conftest.c \ >/dev/null 2>conftest.err && grep sub/conftst1.h sub/conftest.Po > /dev/null 2>&1 && grep sub/conftst6.h sub/conftest.Po > /dev/null 2>&1 && grep $am__obj sub/conftest.Po > /dev/null 2>&1 && ${MAKE-make} -s -f confmf > /dev/null 2>&1; then # icc doesn't choke on unknown options, it will just issue warnings # or remarks (even with -Werror). So we grep stderr for any message # that says an option was ignored or not supported. # When given -MP, icc 7.0 and 7.1 complain thusly: # icc: Command line warning: ignoring option '-M'; no argument required # The diagnosis changed in icc 8.0: # icc: Command line remark: option '-MP' not supported if (grep 'ignoring option' conftest.err || grep 'not supported' conftest.err) >/dev/null 2>&1; then :; else am_cv_$1_dependencies_compiler_type=$depmode break fi fi done cd .. rm -rf conftest.dir else am_cv_$1_dependencies_compiler_type=none fi ]) AC_SUBST([$1DEPMODE], [depmode=$am_cv_$1_dependencies_compiler_type]) AM_CONDITIONAL([am__fastdep$1], [ test "x$enable_dependency_tracking" != xno \ && test "$am_cv_$1_dependencies_compiler_type" = gcc3]) ]) # AM_SET_DEPDIR # ------------- # Choose a directory name for dependency files. # This macro is AC_REQUIREd in _AM_DEPENDENCIES. AC_DEFUN([AM_SET_DEPDIR], [AC_REQUIRE([AM_SET_LEADING_DOT])dnl AC_SUBST([DEPDIR], ["${am__leading_dot}deps"])dnl ]) # AM_DEP_TRACK # ------------ AC_DEFUN([AM_DEP_TRACK], [AC_ARG_ENABLE([dependency-tracking], [dnl AS_HELP_STRING( [--enable-dependency-tracking], [do not reject slow dependency extractors]) AS_HELP_STRING( [--disable-dependency-tracking], [speeds up one-time build])]) if test "x$enable_dependency_tracking" != xno; then am_depcomp="$ac_aux_dir/depcomp" AMDEPBACKSLASH='\' am__nodep='_no' fi AM_CONDITIONAL([AMDEP], [test "x$enable_dependency_tracking" != xno]) AC_SUBST([AMDEPBACKSLASH])dnl _AM_SUBST_NOTMAKE([AMDEPBACKSLASH])dnl AC_SUBST([am__nodep])dnl _AM_SUBST_NOTMAKE([am__nodep])dnl ]) # Generate code to set up dependency tracking. -*- Autoconf -*- # Copyright (C) 1999-2018 Free Software Foundation, Inc. # # This file is free software; the Free Software Foundation # gives unlimited permission to copy and/or distribute it, # with or without modifications, as long as this notice is preserved. # _AM_OUTPUT_DEPENDENCY_COMMANDS # ------------------------------ AC_DEFUN([_AM_OUTPUT_DEPENDENCY_COMMANDS], [{ # Older Autoconf quotes --file arguments for eval, but not when files # are listed without --file. Let's play safe and only enable the eval # if we detect the quoting. # TODO: see whether this extra hack can be removed once we start # requiring Autoconf 2.70 or later. AS_CASE([$CONFIG_FILES], [*\'*], [eval set x "$CONFIG_FILES"], [*], [set x $CONFIG_FILES]) shift # Used to flag and report bootstrapping failures. am_rc=0 for am_mf do # Strip MF so we end up with the name of the file. am_mf=`AS_ECHO(["$am_mf"]) | sed -e 's/:.*$//'` # Check whether this is an Automake generated Makefile which includes # dependency-tracking related rules and includes. # Grep'ing the whole file directly is not great: AIX grep has a line # limit of 2048, but all sed's we know have understand at least 4000. sed -n 's,^am--depfiles:.*,X,p' "$am_mf" | grep X >/dev/null 2>&1 \ || continue am_dirpart=`AS_DIRNAME(["$am_mf"])` am_filepart=`AS_BASENAME(["$am_mf"])` AM_RUN_LOG([cd "$am_dirpart" \ && sed -e '/# am--include-marker/d' "$am_filepart" \ | $MAKE -f - am--depfiles]) || am_rc=$? done if test $am_rc -ne 0; then AC_MSG_FAILURE([Something went wrong bootstrapping makefile fragments for automatic dependency tracking. Try re-running configure with the '--disable-dependency-tracking' option to at least be able to build the package (albeit without support for automatic dependency tracking).]) fi AS_UNSET([am_dirpart]) AS_UNSET([am_filepart]) AS_UNSET([am_mf]) AS_UNSET([am_rc]) rm -f conftest-deps.mk } ])# _AM_OUTPUT_DEPENDENCY_COMMANDS # AM_OUTPUT_DEPENDENCY_COMMANDS # ----------------------------- # This macro should only be invoked once -- use via AC_REQUIRE. # # This code is only required when automatic dependency tracking is enabled. # This creates each '.Po' and '.Plo' makefile fragment that we'll need in # order to bootstrap the dependency handling code. AC_DEFUN([AM_OUTPUT_DEPENDENCY_COMMANDS], [AC_CONFIG_COMMANDS([depfiles], [test x"$AMDEP_TRUE" != x"" || _AM_OUTPUT_DEPENDENCY_COMMANDS], [AMDEP_TRUE="$AMDEP_TRUE" MAKE="${MAKE-make}"])]) # Do all the work for Automake. -*- Autoconf -*- # Copyright (C) 1996-2018 Free Software Foundation, Inc. # # This file is free software; the Free Software Foundation # gives unlimited permission to copy and/or distribute it, # with or without modifications, as long as this notice is preserved. # This macro actually does too much. Some checks are only needed if # your package does certain things. But this isn't really a big deal. dnl Redefine AC_PROG_CC to automatically invoke _AM_PROG_CC_C_O. m4_define([AC_PROG_CC], m4_defn([AC_PROG_CC]) [_AM_PROG_CC_C_O ]) # AM_INIT_AUTOMAKE(PACKAGE, VERSION, [NO-DEFINE]) # AM_INIT_AUTOMAKE([OPTIONS]) # ----------------------------------------------- # The call with PACKAGE and VERSION arguments is the old style # call (pre autoconf-2.50), which is being phased out. PACKAGE # and VERSION should now be passed to AC_INIT and removed from # the call to AM_INIT_AUTOMAKE. # We support both call styles for the transition. After # the next Automake release, Autoconf can make the AC_INIT # arguments mandatory, and then we can depend on a new Autoconf # release and drop the old call support. AC_DEFUN([AM_INIT_AUTOMAKE], [AC_PREREQ([2.65])dnl dnl Autoconf wants to disallow AM_ names. We explicitly allow dnl the ones we care about. m4_pattern_allow([^AM_[A-Z]+FLAGS$])dnl AC_REQUIRE([AM_SET_CURRENT_AUTOMAKE_VERSION])dnl AC_REQUIRE([AC_PROG_INSTALL])dnl if test "`cd $srcdir && pwd`" != "`pwd`"; then # Use -I$(srcdir) only when $(srcdir) != ., so that make's output # is not polluted with repeated "-I." AC_SUBST([am__isrc], [' -I$(srcdir)'])_AM_SUBST_NOTMAKE([am__isrc])dnl # test to see if srcdir already configured if test -f $srcdir/config.status; then AC_MSG_ERROR([source directory already configured; run "make distclean" there first]) fi fi # test whether we have cygpath if test -z "$CYGPATH_W"; then if (cygpath --version) >/dev/null 2>/dev/null; then CYGPATH_W='cygpath -w' else CYGPATH_W=echo fi fi AC_SUBST([CYGPATH_W]) # Define the identity of the package. dnl Distinguish between old-style and new-style calls. m4_ifval([$2], [AC_DIAGNOSE([obsolete], [$0: two- and three-arguments forms are deprecated.]) m4_ifval([$3], [_AM_SET_OPTION([no-define])])dnl AC_SUBST([PACKAGE], [$1])dnl AC_SUBST([VERSION], [$2])], [_AM_SET_OPTIONS([$1])dnl dnl Diagnose old-style AC_INIT with new-style AM_AUTOMAKE_INIT. m4_if( m4_ifdef([AC_PACKAGE_NAME], [ok]):m4_ifdef([AC_PACKAGE_VERSION], [ok]), [ok:ok],, [m4_fatal([AC_INIT should be called with package and version arguments])])dnl AC_SUBST([PACKAGE], ['AC_PACKAGE_TARNAME'])dnl AC_SUBST([VERSION], ['AC_PACKAGE_VERSION'])])dnl _AM_IF_OPTION([no-define],, [AC_DEFINE_UNQUOTED([PACKAGE], ["$PACKAGE"], [Name of package]) AC_DEFINE_UNQUOTED([VERSION], ["$VERSION"], [Version number of package])])dnl # Some tools Automake needs. AC_REQUIRE([AM_SANITY_CHECK])dnl AC_REQUIRE([AC_ARG_PROGRAM])dnl AM_MISSING_PROG([ACLOCAL], [aclocal-${am__api_version}]) AM_MISSING_PROG([AUTOCONF], [autoconf]) AM_MISSING_PROG([AUTOMAKE], [automake-${am__api_version}]) AM_MISSING_PROG([AUTOHEADER], [autoheader]) AM_MISSING_PROG([MAKEINFO], [makeinfo]) AC_REQUIRE([AM_PROG_INSTALL_SH])dnl AC_REQUIRE([AM_PROG_INSTALL_STRIP])dnl AC_REQUIRE([AC_PROG_MKDIR_P])dnl # For better backward compatibility. To be removed once Automake 1.9.x # dies out for good. For more background, see: # # AC_SUBST([mkdir_p], ['$(MKDIR_P)']) # We need awk for the "check" target (and possibly the TAP driver). The # system "awk" is bad on some platforms. AC_REQUIRE([AC_PROG_AWK])dnl AC_REQUIRE([AC_PROG_MAKE_SET])dnl AC_REQUIRE([AM_SET_LEADING_DOT])dnl _AM_IF_OPTION([tar-ustar], [_AM_PROG_TAR([ustar])], [_AM_IF_OPTION([tar-pax], [_AM_PROG_TAR([pax])], [_AM_PROG_TAR([v7])])]) _AM_IF_OPTION([no-dependencies],, [AC_PROVIDE_IFELSE([AC_PROG_CC], [_AM_DEPENDENCIES([CC])], [m4_define([AC_PROG_CC], m4_defn([AC_PROG_CC])[_AM_DEPENDENCIES([CC])])])dnl AC_PROVIDE_IFELSE([AC_PROG_CXX], [_AM_DEPENDENCIES([CXX])], [m4_define([AC_PROG_CXX], m4_defn([AC_PROG_CXX])[_AM_DEPENDENCIES([CXX])])])dnl AC_PROVIDE_IFELSE([AC_PROG_OBJC], [_AM_DEPENDENCIES([OBJC])], [m4_define([AC_PROG_OBJC], m4_defn([AC_PROG_OBJC])[_AM_DEPENDENCIES([OBJC])])])dnl AC_PROVIDE_IFELSE([AC_PROG_OBJCXX], [_AM_DEPENDENCIES([OBJCXX])], [m4_define([AC_PROG_OBJCXX], m4_defn([AC_PROG_OBJCXX])[_AM_DEPENDENCIES([OBJCXX])])])dnl ]) AC_REQUIRE([AM_SILENT_RULES])dnl dnl The testsuite driver may need to know about EXEEXT, so add the dnl 'am__EXEEXT' conditional if _AM_COMPILER_EXEEXT was seen. This dnl macro is hooked onto _AC_COMPILER_EXEEXT early, see below. AC_CONFIG_COMMANDS_PRE(dnl [m4_provide_if([_AM_COMPILER_EXEEXT], [AM_CONDITIONAL([am__EXEEXT], [test -n "$EXEEXT"])])])dnl # POSIX will say in a future version that running "rm -f" with no argument # is OK; and we want to be able to make that assumption in our Makefile # recipes. So use an aggressive probe to check that the usage we want is # actually supported "in the wild" to an acceptable degree. # See automake bug#10828. # To make any issue more visible, cause the running configure to be aborted # by default if the 'rm' program in use doesn't match our expectations; the # user can still override this though. if rm -f && rm -fr && rm -rf; then : OK; else cat >&2 <<'END' Oops! Your 'rm' program seems unable to run without file operands specified on the command line, even when the '-f' option is present. This is contrary to the behaviour of most rm programs out there, and not conforming with the upcoming POSIX standard: Please tell bug-automake@gnu.org about your system, including the value of your $PATH and any error possibly output before this message. This can help us improve future automake versions. END if test x"$ACCEPT_INFERIOR_RM_PROGRAM" = x"yes"; then echo 'Configuration will proceed anyway, since you have set the' >&2 echo 'ACCEPT_INFERIOR_RM_PROGRAM variable to "yes"' >&2 echo >&2 else cat >&2 <<'END' Aborting the configuration process, to ensure you take notice of the issue. You can download and install GNU coreutils to get an 'rm' implementation that behaves properly: . If you want to complete the configuration process using your problematic 'rm' anyway, export the environment variable ACCEPT_INFERIOR_RM_PROGRAM to "yes", and re-run configure. END AC_MSG_ERROR([Your 'rm' program is bad, sorry.]) fi fi dnl The trailing newline in this macro's definition is deliberate, for dnl backward compatibility and to allow trailing 'dnl'-style comments dnl after the AM_INIT_AUTOMAKE invocation. See automake bug#16841. ]) dnl Hook into '_AC_COMPILER_EXEEXT' early to learn its expansion. Do not dnl add the conditional right here, as _AC_COMPILER_EXEEXT may be further dnl mangled by Autoconf and run in a shell conditional statement. m4_define([_AC_COMPILER_EXEEXT], m4_defn([_AC_COMPILER_EXEEXT])[m4_provide([_AM_COMPILER_EXEEXT])]) # When config.status generates a header, we must update the stamp-h file. # This file resides in the same directory as the config header # that is generated. The stamp files are numbered to have different names. # Autoconf calls _AC_AM_CONFIG_HEADER_HOOK (when defined) in the # loop where config.status creates the headers, so we can generate # our stamp files there. AC_DEFUN([_AC_AM_CONFIG_HEADER_HOOK], [# Compute $1's index in $config_headers. _am_arg=$1 _am_stamp_count=1 for _am_header in $config_headers :; do case $_am_header in $_am_arg | $_am_arg:* ) break ;; * ) _am_stamp_count=`expr $_am_stamp_count + 1` ;; esac done echo "timestamp for $_am_arg" >`AS_DIRNAME(["$_am_arg"])`/stamp-h[]$_am_stamp_count]) # Copyright (C) 2001-2018 Free Software Foundation, Inc. # # This file is free software; the Free Software Foundation # gives unlimited permission to copy and/or distribute it, # with or without modifications, as long as this notice is preserved. # AM_PROG_INSTALL_SH # ------------------ # Define $install_sh. AC_DEFUN([AM_PROG_INSTALL_SH], [AC_REQUIRE([AM_AUX_DIR_EXPAND])dnl if test x"${install_sh+set}" != xset; then case $am_aux_dir in *\ * | *\ *) install_sh="\${SHELL} '$am_aux_dir/install-sh'" ;; *) install_sh="\${SHELL} $am_aux_dir/install-sh" esac fi AC_SUBST([install_sh])]) # Copyright (C) 2003-2018 Free Software Foundation, Inc. # # This file is free software; the Free Software Foundation # gives unlimited permission to copy and/or distribute it, # with or without modifications, as long as this notice is preserved. # Check whether the underlying file-system supports filenames # with a leading dot. For instance MS-DOS doesn't. AC_DEFUN([AM_SET_LEADING_DOT], [rm -rf .tst 2>/dev/null mkdir .tst 2>/dev/null if test -d .tst; then am__leading_dot=. else am__leading_dot=_ fi rmdir .tst 2>/dev/null AC_SUBST([am__leading_dot])]) # Copyright (C) 1998-2018 Free Software Foundation, Inc. # # This file is free software; the Free Software Foundation # gives unlimited permission to copy and/or distribute it, # with or without modifications, as long as this notice is preserved. # AM_PROG_LEX # ----------- # Autoconf leaves LEX=: if lex or flex can't be found. Change that to a # "missing" invocation, for better error output. AC_DEFUN([AM_PROG_LEX], [AC_PREREQ([2.50])dnl AC_REQUIRE([AM_MISSING_HAS_RUN])dnl AC_REQUIRE([AC_PROG_LEX])dnl if test "$LEX" = :; then LEX=${am_missing_run}flex fi]) # Check to see how 'make' treats includes. -*- Autoconf -*- # Copyright (C) 2001-2018 Free Software Foundation, Inc. # # This file is free software; the Free Software Foundation # gives unlimited permission to copy and/or distribute it, # with or without modifications, as long as this notice is preserved. # AM_MAKE_INCLUDE() # ----------------- # Check whether make has an 'include' directive that can support all # the idioms we need for our automatic dependency tracking code. AC_DEFUN([AM_MAKE_INCLUDE], [AC_MSG_CHECKING([whether ${MAKE-make} supports the include directive]) cat > confinc.mk << 'END' am__doit: @echo this is the am__doit target >confinc.out .PHONY: am__doit END am__include="#" am__quote= # BSD make does it like this. echo '.include "confinc.mk" # ignored' > confmf.BSD # Other make implementations (GNU, Solaris 10, AIX) do it like this. echo 'include confinc.mk # ignored' > confmf.GNU _am_result=no for s in GNU BSD; do AM_RUN_LOG([${MAKE-make} -f confmf.$s && cat confinc.out]) AS_CASE([$?:`cat confinc.out 2>/dev/null`], ['0:this is the am__doit target'], [AS_CASE([$s], [BSD], [am__include='.include' am__quote='"'], [am__include='include' am__quote=''])]) if test "$am__include" != "#"; then _am_result="yes ($s style)" break fi done rm -f confinc.* confmf.* AC_MSG_RESULT([${_am_result}]) AC_SUBST([am__include])]) AC_SUBST([am__quote])]) # Fake the existence of programs that GNU maintainers use. -*- Autoconf -*- # Copyright (C) 1997-2018 Free Software Foundation, Inc. # # This file is free software; the Free Software Foundation # gives unlimited permission to copy and/or distribute it, # with or without modifications, as long as this notice is preserved. # AM_MISSING_PROG(NAME, PROGRAM) # ------------------------------ AC_DEFUN([AM_MISSING_PROG], [AC_REQUIRE([AM_MISSING_HAS_RUN]) $1=${$1-"${am_missing_run}$2"} AC_SUBST($1)]) # AM_MISSING_HAS_RUN # ------------------ # Define MISSING if not defined so far and test if it is modern enough. # If it is, set am_missing_run to use it, otherwise, to nothing. AC_DEFUN([AM_MISSING_HAS_RUN], [AC_REQUIRE([AM_AUX_DIR_EXPAND])dnl AC_REQUIRE_AUX_FILE([missing])dnl if test x"${MISSING+set}" != xset; then case $am_aux_dir in *\ * | *\ *) MISSING="\${SHELL} \"$am_aux_dir/missing\"" ;; *) MISSING="\${SHELL} $am_aux_dir/missing" ;; esac fi # Use eval to expand $SHELL if eval "$MISSING --is-lightweight"; then am_missing_run="$MISSING " else am_missing_run= AC_MSG_WARN(['missing' script is too old or missing]) fi ]) # Helper functions for option handling. -*- Autoconf -*- # Copyright (C) 2001-2018 Free Software Foundation, Inc. # # This file is free software; the Free Software Foundation # gives unlimited permission to copy and/or distribute it, # with or without modifications, as long as this notice is preserved. # _AM_MANGLE_OPTION(NAME) # ----------------------- AC_DEFUN([_AM_MANGLE_OPTION], [[_AM_OPTION_]m4_bpatsubst($1, [[^a-zA-Z0-9_]], [_])]) # _AM_SET_OPTION(NAME) # -------------------- # Set option NAME. Presently that only means defining a flag for this option. AC_DEFUN([_AM_SET_OPTION], [m4_define(_AM_MANGLE_OPTION([$1]), [1])]) # _AM_SET_OPTIONS(OPTIONS) # ------------------------ # OPTIONS is a space-separated list of Automake options. AC_DEFUN([_AM_SET_OPTIONS], [m4_foreach_w([_AM_Option], [$1], [_AM_SET_OPTION(_AM_Option)])]) # _AM_IF_OPTION(OPTION, IF-SET, [IF-NOT-SET]) # ------------------------------------------- # Execute IF-SET if OPTION is set, IF-NOT-SET otherwise. AC_DEFUN([_AM_IF_OPTION], [m4_ifset(_AM_MANGLE_OPTION([$1]), [$2], [$3])]) # Copyright (C) 1999-2018 Free Software Foundation, Inc. # # This file is free software; the Free Software Foundation # gives unlimited permission to copy and/or distribute it, # with or without modifications, as long as this notice is preserved. # _AM_PROG_CC_C_O # --------------- # Like AC_PROG_CC_C_O, but changed for automake. We rewrite AC_PROG_CC # to automatically call this. AC_DEFUN([_AM_PROG_CC_C_O], [AC_REQUIRE([AM_AUX_DIR_EXPAND])dnl AC_REQUIRE_AUX_FILE([compile])dnl AC_LANG_PUSH([C])dnl AC_CACHE_CHECK( [whether $CC understands -c and -o together], [am_cv_prog_cc_c_o], [AC_LANG_CONFTEST([AC_LANG_PROGRAM([])]) # Make sure it works both with $CC and with simple cc. # Following AC_PROG_CC_C_O, we do the test twice because some # compilers refuse to overwrite an existing .o file with -o, # though they will create one. am_cv_prog_cc_c_o=yes for am_i in 1 2; do if AM_RUN_LOG([$CC -c conftest.$ac_ext -o conftest2.$ac_objext]) \ && test -f conftest2.$ac_objext; then : OK else am_cv_prog_cc_c_o=no break fi done rm -f core conftest* unset am_i]) if test "$am_cv_prog_cc_c_o" != yes; then # Losing compiler, so override with the script. # FIXME: It is wrong to rewrite CC. # But if we don't then we get into trouble of one sort or another. # A longer-term fix would be to have automake use am__CC in this case, # and then we could set am__CC="\$(top_srcdir)/compile \$(CC)" CC="$am_aux_dir/compile $CC" fi AC_LANG_POP([C])]) # For backward compatibility. AC_DEFUN_ONCE([AM_PROG_CC_C_O], [AC_REQUIRE([AC_PROG_CC])]) # Copyright (C) 2001-2018 Free Software Foundation, Inc. # # This file is free software; the Free Software Foundation # gives unlimited permission to copy and/or distribute it, # with or without modifications, as long as this notice is preserved. # AM_RUN_LOG(COMMAND) # ------------------- # Run COMMAND, save the exit status in ac_status, and log it. # (This has been adapted from Autoconf's _AC_RUN_LOG macro.) AC_DEFUN([AM_RUN_LOG], [{ echo "$as_me:$LINENO: $1" >&AS_MESSAGE_LOG_FD ($1) >&AS_MESSAGE_LOG_FD 2>&AS_MESSAGE_LOG_FD ac_status=$? echo "$as_me:$LINENO: \$? = $ac_status" >&AS_MESSAGE_LOG_FD (exit $ac_status); }]) # Check to make sure that the build environment is sane. -*- Autoconf -*- # Copyright (C) 1996-2018 Free Software Foundation, Inc. # # This file is free software; the Free Software Foundation # gives unlimited permission to copy and/or distribute it, # with or without modifications, as long as this notice is preserved. # AM_SANITY_CHECK # --------------- AC_DEFUN([AM_SANITY_CHECK], [AC_MSG_CHECKING([whether build environment is sane]) # Reject unsafe characters in $srcdir or the absolute working directory # name. Accept space and tab only in the latter. am_lf=' ' case `pwd` in *[[\\\"\#\$\&\'\`$am_lf]]*) AC_MSG_ERROR([unsafe absolute working directory name]);; esac case $srcdir in *[[\\\"\#\$\&\'\`$am_lf\ \ ]]*) AC_MSG_ERROR([unsafe srcdir value: '$srcdir']);; esac # Do 'set' in a subshell so we don't clobber the current shell's # arguments. Must try -L first in case configure is actually a # symlink; some systems play weird games with the mod time of symlinks # (eg FreeBSD returns the mod time of the symlink's containing # directory). if ( am_has_slept=no for am_try in 1 2; do echo "timestamp, slept: $am_has_slept" > conftest.file set X `ls -Lt "$srcdir/configure" conftest.file 2> /dev/null` if test "$[*]" = "X"; then # -L didn't work. set X `ls -t "$srcdir/configure" conftest.file` fi if test "$[*]" != "X $srcdir/configure conftest.file" \ && test "$[*]" != "X conftest.file $srcdir/configure"; then # If neither matched, then we have a broken ls. This can happen # if, for instance, CONFIG_SHELL is bash and it inherits a # broken ls alias from the environment. This has actually # happened. Such a system could not be considered "sane". AC_MSG_ERROR([ls -t appears to fail. Make sure there is not a broken alias in your environment]) fi if test "$[2]" = conftest.file || test $am_try -eq 2; then break fi # Just in case. sleep 1 am_has_slept=yes done test "$[2]" = conftest.file ) then # Ok. : else AC_MSG_ERROR([newly created file is older than distributed files! Check your system clock]) fi AC_MSG_RESULT([yes]) # If we didn't sleep, we still need to ensure time stamps of config.status and # generated files are strictly newer. am_sleep_pid= if grep 'slept: no' conftest.file >/dev/null 2>&1; then ( sleep 1 ) & am_sleep_pid=$! fi AC_CONFIG_COMMANDS_PRE( [AC_MSG_CHECKING([that generated files are newer than configure]) if test -n "$am_sleep_pid"; then # Hide warnings about reused PIDs. wait $am_sleep_pid 2>/dev/null fi AC_MSG_RESULT([done])]) rm -f conftest.file ]) # Copyright (C) 2009-2018 Free Software Foundation, Inc. # # This file is free software; the Free Software Foundation # gives unlimited permission to copy and/or distribute it, # with or without modifications, as long as this notice is preserved. # AM_SILENT_RULES([DEFAULT]) # -------------------------- # Enable less verbose build rules; with the default set to DEFAULT # ("yes" being less verbose, "no" or empty being verbose). AC_DEFUN([AM_SILENT_RULES], [AC_ARG_ENABLE([silent-rules], [dnl AS_HELP_STRING( [--enable-silent-rules], [less verbose build output (undo: "make V=1")]) AS_HELP_STRING( [--disable-silent-rules], [verbose build output (undo: "make V=0")])dnl ]) case $enable_silent_rules in @%:@ ((( yes) AM_DEFAULT_VERBOSITY=0;; no) AM_DEFAULT_VERBOSITY=1;; *) AM_DEFAULT_VERBOSITY=m4_if([$1], [yes], [0], [1]);; esac dnl dnl A few 'make' implementations (e.g., NonStop OS and NextStep) dnl do not support nested variable expansions. dnl See automake bug#9928 and bug#10237. am_make=${MAKE-make} AC_CACHE_CHECK([whether $am_make supports nested variables], [am_cv_make_support_nested_variables], [if AS_ECHO([['TRUE=$(BAR$(V)) BAR0=false BAR1=true V=1 am__doit: @$(TRUE) .PHONY: am__doit']]) | $am_make -f - >/dev/null 2>&1; then am_cv_make_support_nested_variables=yes else am_cv_make_support_nested_variables=no fi]) if test $am_cv_make_support_nested_variables = yes; then dnl Using '$V' instead of '$(V)' breaks IRIX make. AM_V='$(V)' AM_DEFAULT_V='$(AM_DEFAULT_VERBOSITY)' else AM_V=$AM_DEFAULT_VERBOSITY AM_DEFAULT_V=$AM_DEFAULT_VERBOSITY fi AC_SUBST([AM_V])dnl AM_SUBST_NOTMAKE([AM_V])dnl AC_SUBST([AM_DEFAULT_V])dnl AM_SUBST_NOTMAKE([AM_DEFAULT_V])dnl AC_SUBST([AM_DEFAULT_VERBOSITY])dnl AM_BACKSLASH='\' AC_SUBST([AM_BACKSLASH])dnl _AM_SUBST_NOTMAKE([AM_BACKSLASH])dnl ]) # Copyright (C) 2001-2018 Free Software Foundation, Inc. # # This file is free software; the Free Software Foundation # gives unlimited permission to copy and/or distribute it, # with or without modifications, as long as this notice is preserved. # AM_PROG_INSTALL_STRIP # --------------------- # One issue with vendor 'install' (even GNU) is that you can't # specify the program used to strip binaries. This is especially # annoying in cross-compiling environments, where the build's strip # is unlikely to handle the host's binaries. # Fortunately install-sh will honor a STRIPPROG variable, so we # always use install-sh in "make install-strip", and initialize # STRIPPROG with the value of the STRIP variable (set by the user). AC_DEFUN([AM_PROG_INSTALL_STRIP], [AC_REQUIRE([AM_PROG_INSTALL_SH])dnl # Installed binaries are usually stripped using 'strip' when the user # run "make install-strip". However 'strip' might not be the right # tool to use in cross-compilation environments, therefore Automake # will honor the 'STRIP' environment variable to overrule this program. dnl Don't test for $cross_compiling = yes, because it might be 'maybe'. if test "$cross_compiling" != no; then AC_CHECK_TOOL([STRIP], [strip], :) fi INSTALL_STRIP_PROGRAM="\$(install_sh) -c -s" AC_SUBST([INSTALL_STRIP_PROGRAM])]) # Copyright (C) 2006-2018 Free Software Foundation, Inc. # # This file is free software; the Free Software Foundation # gives unlimited permission to copy and/or distribute it, # with or without modifications, as long as this notice is preserved. # _AM_SUBST_NOTMAKE(VARIABLE) # --------------------------- # Prevent Automake from outputting VARIABLE = @VARIABLE@ in Makefile.in. # This macro is traced by Automake. AC_DEFUN([_AM_SUBST_NOTMAKE]) # AM_SUBST_NOTMAKE(VARIABLE) # -------------------------- # Public sister of _AM_SUBST_NOTMAKE. AC_DEFUN([AM_SUBST_NOTMAKE], [_AM_SUBST_NOTMAKE($@)]) # Check how to create a tarball. -*- Autoconf -*- # Copyright (C) 2004-2018 Free Software Foundation, Inc. # # This file is free software; the Free Software Foundation # gives unlimited permission to copy and/or distribute it, # with or without modifications, as long as this notice is preserved. # _AM_PROG_TAR(FORMAT) # -------------------- # Check how to create a tarball in format FORMAT. # FORMAT should be one of 'v7', 'ustar', or 'pax'. # # Substitute a variable $(am__tar) that is a command # writing to stdout a FORMAT-tarball containing the directory # $tardir. # tardir=directory && $(am__tar) > result.tar # # Substitute a variable $(am__untar) that extract such # a tarball read from stdin. # $(am__untar) < result.tar # AC_DEFUN([_AM_PROG_TAR], [# Always define AMTAR for backward compatibility. Yes, it's still used # in the wild :-( We should find a proper way to deprecate it ... AC_SUBST([AMTAR], ['$${TAR-tar}']) # We'll loop over all known methods to create a tar archive until one works. _am_tools='gnutar m4_if([$1], [ustar], [plaintar]) pax cpio none' m4_if([$1], [v7], [am__tar='$${TAR-tar} chof - "$$tardir"' am__untar='$${TAR-tar} xf -'], [m4_case([$1], [ustar], [# The POSIX 1988 'ustar' format is defined with fixed-size fields. # There is notably a 21 bits limit for the UID and the GID. In fact, # the 'pax' utility can hang on bigger UID/GID (see automake bug#8343 # and bug#13588). am_max_uid=2097151 # 2^21 - 1 am_max_gid=$am_max_uid # The $UID and $GID variables are not portable, so we need to resort # to the POSIX-mandated id(1) utility. Errors in the 'id' calls # below are definitely unexpected, so allow the users to see them # (that is, avoid stderr redirection). am_uid=`id -u || echo unknown` am_gid=`id -g || echo unknown` AC_MSG_CHECKING([whether UID '$am_uid' is supported by ustar format]) if test $am_uid -le $am_max_uid; then AC_MSG_RESULT([yes]) else AC_MSG_RESULT([no]) _am_tools=none fi AC_MSG_CHECKING([whether GID '$am_gid' is supported by ustar format]) if test $am_gid -le $am_max_gid; then AC_MSG_RESULT([yes]) else AC_MSG_RESULT([no]) _am_tools=none fi], [pax], [], [m4_fatal([Unknown tar format])]) AC_MSG_CHECKING([how to create a $1 tar archive]) # Go ahead even if we have the value already cached. We do so because we # need to set the values for the 'am__tar' and 'am__untar' variables. _am_tools=${am_cv_prog_tar_$1-$_am_tools} for _am_tool in $_am_tools; do case $_am_tool in gnutar) for _am_tar in tar gnutar gtar; do AM_RUN_LOG([$_am_tar --version]) && break done am__tar="$_am_tar --format=m4_if([$1], [pax], [posix], [$1]) -chf - "'"$$tardir"' am__tar_="$_am_tar --format=m4_if([$1], [pax], [posix], [$1]) -chf - "'"$tardir"' am__untar="$_am_tar -xf -" ;; plaintar) # Must skip GNU tar: if it does not support --format= it doesn't create # ustar tarball either. (tar --version) >/dev/null 2>&1 && continue am__tar='tar chf - "$$tardir"' am__tar_='tar chf - "$tardir"' am__untar='tar xf -' ;; pax) am__tar='pax -L -x $1 -w "$$tardir"' am__tar_='pax -L -x $1 -w "$tardir"' am__untar='pax -r' ;; cpio) am__tar='find "$$tardir" -print | cpio -o -H $1 -L' am__tar_='find "$tardir" -print | cpio -o -H $1 -L' am__untar='cpio -i -H $1 -d' ;; none) am__tar=false am__tar_=false am__untar=false ;; esac # If the value was cached, stop now. We just wanted to have am__tar # and am__untar set. test -n "${am_cv_prog_tar_$1}" && break # tar/untar a dummy directory, and stop if the command works. rm -rf conftest.dir mkdir conftest.dir echo GrepMe > conftest.dir/file AM_RUN_LOG([tardir=conftest.dir && eval $am__tar_ >conftest.tar]) rm -rf conftest.dir if test -s conftest.tar; then AM_RUN_LOG([$am__untar /dev/null 2>&1 && break fi done rm -rf conftest.dir AC_CACHE_VAL([am_cv_prog_tar_$1], [am_cv_prog_tar_$1=$_am_tool]) AC_MSG_RESULT([$am_cv_prog_tar_$1])]) AC_SUBST([am__tar]) AC_SUBST([am__untar]) ]) # _AM_PROG_TAR m4_include([m4/ax_compare_version.m4]) m4_include([m4/ax_lua.m4]) m4_include([m4/ax_pthread.m4]) m4_include([m4/ax_python.m4]) m4_include([m4/libtool.m4]) m4_include([m4/ltoptions.m4]) m4_include([m4/ltsugar.m4]) m4_include([m4/ltversion.m4]) m4_include([m4/lt~obsolete.m4]) m4_include([m4/pkg.m4]) frr-7.2.1/alpine/0000755000000000000000000000000013610377563010544 500000000000000frr-7.2.1/alpine/APKBUILD.in0000644000000000000000000000373613610377563012220 00000000000000# Maintainer: Arthur Jones pkgname=frr pkgver=@VERSION@ pkgrel=0 pkgdesc="Free Range Routing is a fork of quagga" url="https://frrouting.org/" arch="x86_64" license="GPL-2.0" depends="json-c c-ares ipsec-tools iproute2 python py-ipaddr bash" makedepends="ncurses-dev net-snmp-dev gawk texinfo perl acct autoconf automake bash binutils bison bsd-compat-headers build-base c-ares c-ares-dev ca-certificates cryptsetup-libs curl device-mapper-libs expat fakeroot flex fortify-headers gdbm git gmp isl json-c-dev kmod lddtree libacl libatomic libattr libblkid libburn libbz2 libc-dev libcap libcurl libedit libffi libgcc libgomp libisoburn libisofs libltdl libressl libssh2 libstdc++ libtool libuuid libyang-dev linux-headers lzip lzo m4 make mkinitfs mpc1 mpfr3 mtools musl-dev ncurses-libs ncurses-terminfo ncurses-terminfo-base patch pax-utils pcre perl pkgconf python2 python2-dev readline readline-dev sqlite-libs squashfs-tools sudo tar texinfo xorriso xz-libs py-sphinx rtrlib rtrlib-dev" checkdepends="pytest py-setuptools" install="$pkgname.pre-install $pkgname.pre-deinstall $pkgname.post-deinstall" subpackages="$pkgname-dev $pkgname-doc $pkgname-dbg" source="$pkgname-$pkgver.tar.gz" builddir="$srcdir"/$pkgname-$pkgver _sbindir=/usr/lib/frr _sysconfdir=/etc/frr _libdir=/usr/lib _localstatedir=/var/run/frr _user=frr build() { cd "$builddir" ./configure \ --prefix=/usr \ --sbindir=$_sbindir \ --sysconfdir=$_sysconfdir \ --libdir=$_libdir \ --localstatedir=$_localstatedir \ --enable-systemd=no \ --enable-rpki \ --enable-vtysh \ --enable-multipath=64 \ --enable-vty-group=frrvty \ --enable-user=$_user \ --enable-group=$_user make } check() { cd "$builddir" make -j 1 check } package() { cd "$builddir" make DESTDIR="$pkgdir" install install -Dm644 "$builddir"/tools/etc/frr/daemons "$pkgdir"$_sysconfdir install -d "$pkgdir"/etc/init.d ln -s ${_sbindir}/frr "$pkgdir"/etc/init.d/frr } frr-7.2.1/babeld/0000755000000000000000000000000013610377563010505 500000000000000frr-7.2.1/babeld/Makefile0000644000000000000000000000022513610377563012064 00000000000000all: ALWAYS @$(MAKE) -s -C .. babeld/babeld %: ALWAYS @$(MAKE) -s -C .. babeld/$@ Makefile: #nothing ALWAYS: .PHONY: ALWAYS makefiles .SUFFIXES: frr-7.2.1/babeld/babel_errors.c0000644000000000000000000000372513610377563013241 00000000000000/* * Babel-specific error messages. * Copyright (C) 2018 Cumulus Networks, Inc. * Donald Sharp * * This program is free software; you can redistribute it and/or modify it * under the terms of the 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; see the file COPYING; if not, write to the Free Software * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA */ #include #include "lib/ferr.h" #include "babel_errors.h" /* clang-format off */ static struct log_ref ferr_babel_err[] = { { .code = EC_BABEL_MEMORY, .title = "BABEL Memory Errors", .description = "Babel has failed to allocate memory, the system is about to run out of memory", .suggestion = "Find the process that is causing memory shortages, remediate that process and restart FRR" }, { .code = EC_BABEL_PACKET, .title = "BABEL Packet Error", .description = "Babel has detected a packet encode/decode problem", .suggestion = "Collect relevant log files and file an Issue" }, { .code = EC_BABEL_CONFIG, .title = "BABEL Configuration Error", .description = "Babel has detected a configuration error of some sort", .suggestion = "Ensure that the configuration is correct" }, { .code = EC_BABEL_ROUTE, .title = "BABEL Route Error", .description = "Babel has detected a routing error and has an inconsistent state", .suggestion = "Gather data for filing an Issue and then restart FRR" }, { .code = END_FERR, } }; /* clang-format on */ void babel_error_init(void) { log_ref_add(ferr_babel_err); } frr-7.2.1/babeld/babel_errors.h0000644000000000000000000000210413610377563013234 00000000000000/* * Babel-specific error messages. * Copyright (C) 2018 Cumulus Networks, Inc. * Donald Sharp * * This program is free software; you can redistribute it and/or modify it * under the terms of the 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; see the file COPYING; if not, write to the Free Software * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA */ #ifndef __BABEL_ERRORS_H__ #define __BABEL_ERRORS_H__ #include "lib/ferr.h" enum babel_log_refs { EC_BABEL_MEMORY = BABEL_FERR_START, EC_BABEL_PACKET, EC_BABEL_CONFIG, EC_BABEL_ROUTE, }; extern void babel_error_init(void); #endif frr-7.2.1/babeld/babel_filter.c0000644000000000000000000001050013610377563013177 00000000000000/* Copyright 2011 by Matthieu Boutier and Juliusz Chroboczek Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions: The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. */ #ifdef HAVE_CONFIG_H #include "config.h" #endif #include "babel_filter.h" #include "vty.h" #include "filter.h" #include "log.h" #include "plist.h" #include "distribute.h" #include "util.h" int babel_filter(int output, const unsigned char *prefix, unsigned short plen, unsigned int ifindex) { struct interface *ifp = if_lookup_by_index(ifindex, VRF_DEFAULT); babel_interface_nfo *babel_ifp = ifp ? babel_get_if_nfo(ifp) : NULL; struct prefix p; struct distribute *dist = NULL; struct access_list *alist; struct prefix_list *plist; int distribute; struct babel *babel; p.family = v4mapped(prefix) ? AF_INET : AF_INET6; p.prefixlen = v4mapped(prefix) ? plen - 96 : plen; if (p.family == AF_INET) { uchar_to_inaddr(&p.u.prefix4, prefix); distribute = output ? DISTRIBUTE_V4_OUT : DISTRIBUTE_V4_IN; } else { uchar_to_in6addr(&p.u.prefix6, prefix); distribute = output ? DISTRIBUTE_V6_OUT : DISTRIBUTE_V6_IN; } if (babel_ifp != NULL && babel_ifp->list[distribute]) { if (access_list_apply (babel_ifp->list[distribute], &p) == FILTER_DENY) { debugf(BABEL_DEBUG_FILTER, "%s/%d filtered by distribute %s", p.family == AF_INET ? inet_ntoa(p.u.prefix4) : inet6_ntoa (p.u.prefix6), p.prefixlen, output ? "out" : "in"); return INFINITY; } } if (babel_ifp != NULL && babel_ifp->prefix[distribute]) { if (prefix_list_apply (babel_ifp->prefix[distribute], &p) == PREFIX_DENY) { debugf(BABEL_DEBUG_FILTER, "%s/%d filtered by distribute %s", p.family == AF_INET ? inet_ntoa(p.u.prefix4) : inet6_ntoa (p.u.prefix6), p.prefixlen, output ? "out" : "in"); return INFINITY; } } /* All interface filter check. */ babel = babel_lookup(); if (babel) dist = distribute_lookup (babel->distribute_ctx, NULL); if (dist) { if (dist->list[distribute]) { alist = access_list_lookup (p.family, dist->list[distribute]); if (alist) { if (access_list_apply (alist, &p) == FILTER_DENY) { debugf(BABEL_DEBUG_FILTER,"%s/%d filtered by distribute %s", p.family == AF_INET ? inet_ntoa(p.u.prefix4) : inet6_ntoa (p.u.prefix6), p.prefixlen, output ? "out" : "in"); return INFINITY; } } } if (dist->prefix[distribute]) { plist = prefix_list_lookup (p.family, dist->prefix[distribute]); if (plist) { if (prefix_list_apply (plist, &p) == PREFIX_DENY) { debugf(BABEL_DEBUG_FILTER,"%s/%d filtered by distribute %s", p.family == AF_INET ? inet_ntoa(p.u.prefix4) : inet6_ntoa (p.u.prefix6), p.prefixlen, output ? "out" : "in"); return INFINITY; } } } } return 0; } frr-7.2.1/babeld/babel_filter.h0000644000000000000000000000253513610377563013215 00000000000000/* Copyright 2011 by Matthieu Boutier and Juliusz Chroboczek Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions: The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. */ #ifndef BABELD_BABEL_FILTER_H #define BABELD_BABEL_FILTER_H #include #include "prefix.h" #include "babel_interface.h" int babel_filter(int output, const unsigned char *prefix, unsigned short plen, unsigned int index); #endif /* BABELD_BABEL_FILTER_H */ frr-7.2.1/babeld/babel_interface.c0000644000000000000000000012056013610377563013662 00000000000000/* Copyright 2011 by Matthieu Boutier and Juliusz Chroboczek Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions: The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. */ #include #include "memory.h" #include "log.h" #include "command.h" #include "prefix.h" #include "vector.h" #include "distribute.h" #include "lib_errors.h" #include "babel_main.h" #include "util.h" #include "kernel.h" #include "babel_interface.h" #include "message.h" #include "route.h" #include "babel_zebra.h" #include "neighbour.h" #include "route.h" #include "xroute.h" #include "babel_errors.h" DEFINE_MTYPE_STATIC(BABELD, BABEL_IF, "Babel Interface") #define IS_ENABLE(ifp) (babel_enable_if_lookup(ifp->name) >= 0) static int babel_enable_if_lookup (const char *ifname); static int babel_enable_if_add (const char *ifname); static int babel_enable_if_delete (const char *ifname); static int interface_recalculate(struct interface *ifp); static int interface_reset(struct interface *ifp); static int babel_if_new_hook (struct interface *ifp); static int babel_if_delete_hook (struct interface *ifp); static int interface_config_write (struct vty *vty); static babel_interface_nfo * babel_interface_allocate (void); static void babel_interface_free (babel_interface_nfo *bi); static vector babel_enable_if; /* enable interfaces (by cmd). */ static struct cmd_node babel_interface_node = /* babeld's interface node. */ { INTERFACE_NODE, "%s(config-if)# ", 1 /* VTYSH */ }; int babel_interface_up (ZAPI_CALLBACK_ARGS) { struct stream *s = NULL; struct interface *ifp = NULL; debugf(BABEL_DEBUG_IF, "receive a 'interface up'"); s = zclient->ibuf; ifp = zebra_interface_state_read(s, vrf_id); /* it updates iflist */ if (ifp == NULL) { return 0; } interface_recalculate(ifp); return 0; } int babel_interface_down (ZAPI_CALLBACK_ARGS) { struct stream *s = NULL; struct interface *ifp = NULL; debugf(BABEL_DEBUG_IF, "receive a 'interface down'"); s = zclient->ibuf; ifp = zebra_interface_state_read(s, vrf_id); /* it updates iflist */ if (ifp == NULL) { return 0; } interface_reset(ifp); return 0; } int babel_interface_add (ZAPI_CALLBACK_ARGS) { struct interface *ifp = NULL; debugf(BABEL_DEBUG_IF, "receive a 'interface add'"); /* read and add the interface in the iflist. */ ifp = zebra_interface_add_read (zclient->ibuf, vrf_id); if (ifp == NULL) { return 0; } interface_recalculate(ifp); return 0; } int babel_interface_delete (ZAPI_CALLBACK_ARGS) { struct interface *ifp; struct stream *s; debugf(BABEL_DEBUG_IF, "receive a 'interface delete'"); s = zclient->ibuf; ifp = zebra_interface_state_read(s, vrf_id); /* it updates iflist */ if (ifp == NULL) return 0; if (IS_ENABLE(ifp)) interface_reset(ifp); /* To support pseudo interface do not free interface structure. */ /* if_delete(ifp); */ if_set_index(ifp, IFINDEX_INTERNAL); return 0; } int babel_interface_address_add (ZAPI_CALLBACK_ARGS) { babel_interface_nfo *babel_ifp; struct connected *ifc; struct prefix *prefix; debugf(BABEL_DEBUG_IF, "receive a 'interface address add'"); ifc = zebra_interface_address_read (ZEBRA_INTERFACE_ADDRESS_ADD, zclient->ibuf, vrf_id); if (ifc == NULL) return 0; prefix = ifc->address; if (prefix->family == AF_INET) { flush_interface_routes(ifc->ifp, 0); babel_ifp = babel_get_if_nfo(ifc->ifp); if (babel_ifp->ipv4 == NULL) { babel_ifp->ipv4 = malloc(4); if (babel_ifp->ipv4 == NULL) { flog_err(EC_BABEL_MEMORY, "not enough memory"); } else { memcpy(babel_ifp->ipv4, &prefix->u.prefix4, 4); } } } send_request(ifc->ifp, NULL, 0); send_update(ifc->ifp, 0, NULL, 0); return 0; } int babel_interface_address_delete (ZAPI_CALLBACK_ARGS) { babel_interface_nfo *babel_ifp; struct connected *ifc; struct prefix *prefix; debugf(BABEL_DEBUG_IF, "receive a 'interface address delete'"); ifc = zebra_interface_address_read (ZEBRA_INTERFACE_ADDRESS_DELETE, zclient->ibuf, vrf_id); if (ifc == NULL) return 0; prefix = ifc->address; if (prefix->family == AF_INET) { flush_interface_routes(ifc->ifp, 0); babel_ifp = babel_get_if_nfo(ifc->ifp); if (babel_ifp->ipv4 != NULL && memcmp(babel_ifp->ipv4, &prefix->u.prefix4, 4) == 0) { free(babel_ifp->ipv4); babel_ifp->ipv4 = NULL; } } send_request(ifc->ifp, NULL, 0); send_update(ifc->ifp, 0, NULL, 0); return 0; } /* Lookup function. */ static int babel_enable_if_lookup (const char *ifname) { unsigned int i; char *str; for (i = 0; i < vector_active (babel_enable_if); i++) if ((str = vector_slot (babel_enable_if, i)) != NULL) if (strcmp (str, ifname) == 0) return i; return -1; } /* Add interface to babel_enable_if. */ static int babel_enable_if_add (const char *ifname) { int ret; struct interface *ifp = NULL; ret = babel_enable_if_lookup (ifname); if (ret >= 0) return -1; vector_set (babel_enable_if, strdup (ifname)); ifp = if_lookup_by_name(ifname, VRF_DEFAULT); if (ifp != NULL) interface_recalculate(ifp); return 1; } /* Delete interface from babel_enable_if. */ static int babel_enable_if_delete (const char *ifname) { int babel_enable_if_index; char *str; struct interface *ifp = NULL; babel_enable_if_index = babel_enable_if_lookup (ifname); if (babel_enable_if_index < 0) return -1; str = vector_slot (babel_enable_if, babel_enable_if_index); free (str); vector_unset (babel_enable_if, babel_enable_if_index); ifp = if_lookup_by_name(ifname, VRF_DEFAULT); if (ifp != NULL) interface_reset(ifp); return 1; } /* [Babel Command] Babel enable on specified interface or matched network. */ DEFUN (babel_network, babel_network_cmd, "network IF_OR_ADDR", "Enable Babel protocol on specified interface or network.\n" "Interface or address\n") { int ret; struct prefix p; ret = str2prefix (argv[1]->arg, &p); /* Given string is: */ if (ret) /* an IPv4 or v6 network */ return CMD_ERR_NO_MATCH; /* not implemented yet */ else /* an interface name */ ret = babel_enable_if_add (argv[1]->arg); if (ret < 0) { vty_out (vty, "There is same network configuration %s\n", argv[1]->arg); return CMD_WARNING; } return CMD_SUCCESS; } /* [Babel Command] Babel enable on specified interface or matched network. */ DEFUN (no_babel_network, no_babel_network_cmd, "no network IF_OR_ADDR", NO_STR "Disable Babel protocol on specified interface or network.\n" "Interface or address\n") { int ret; struct prefix p; ret = str2prefix (argv[2]->arg, &p); /* Given string is: */ if (ret) /* an IPv4 or v6 network */ return CMD_ERR_NO_MATCH; /* not implemented yet */ else /* an interface name */ ret = babel_enable_if_delete (argv[2]->arg); if (ret < 0) { vty_out (vty, "can't find network %s\n",argv[2]->arg); return CMD_WARNING_CONFIG_FAILED; } return CMD_SUCCESS; } /* There are a number of interface parameters that must be changed when an interface becomes wired/wireless. In Quagga, they cannot be configured separately. */ static void babel_set_wired_internal(babel_interface_nfo *babel_ifp, int wired) { if(wired) { babel_ifp->flags |= BABEL_IF_WIRED; babel_ifp->flags |= BABEL_IF_SPLIT_HORIZON; babel_ifp->cost = BABEL_DEFAULT_RXCOST_WIRED; babel_ifp->channel = BABEL_IF_CHANNEL_NONINTERFERING; babel_ifp->flags &= ~BABEL_IF_LQ; } else { babel_ifp->flags &= ~BABEL_IF_WIRED; babel_ifp->flags &= ~BABEL_IF_SPLIT_HORIZON; babel_ifp->cost = BABEL_DEFAULT_RXCOST_WIRELESS; babel_ifp->channel = BABEL_IF_CHANNEL_INTERFERING; babel_ifp->flags |= BABEL_IF_LQ; } } /* [Interface Command] Tell the interface is wire. */ DEFUN (babel_set_wired, babel_set_wired_cmd, "babel wired", "Babel interface commands\n" "Enable wired optimizations\n") { VTY_DECLVAR_CONTEXT(interface, ifp); babel_interface_nfo *babel_ifp; babel_ifp = babel_get_if_nfo(ifp); assert (babel_ifp != NULL); babel_set_wired_internal(babel_ifp, 1); return CMD_SUCCESS; } /* [Interface Command] Tell the interface is wireless (default). */ DEFUN (babel_set_wireless, babel_set_wireless_cmd, "babel wireless", "Babel interface commands\n" "Disable wired optimizations (assume wireless)\n") { VTY_DECLVAR_CONTEXT(interface, ifp); babel_interface_nfo *babel_ifp; babel_ifp = babel_get_if_nfo(ifp); assert (babel_ifp != NULL); babel_set_wired_internal(babel_ifp, 0); return CMD_SUCCESS; } /* [Interface Command] Enable split horizon. */ DEFUN (babel_split_horizon, babel_split_horizon_cmd, "babel split-horizon", "Babel interface commands\n" "Enable split horizon processing\n") { VTY_DECLVAR_CONTEXT(interface, ifp); babel_interface_nfo *babel_ifp; babel_ifp = babel_get_if_nfo(ifp); assert (babel_ifp != NULL); babel_ifp->flags |= BABEL_IF_SPLIT_HORIZON; return CMD_SUCCESS; } /* [Interface Command] Disable split horizon (default). */ DEFUN (no_babel_split_horizon, no_babel_split_horizon_cmd, "no babel split-horizon", NO_STR "Babel interface commands\n" "Disable split horizon processing\n") { VTY_DECLVAR_CONTEXT(interface, ifp); babel_interface_nfo *babel_ifp; babel_ifp = babel_get_if_nfo(ifp); assert (babel_ifp != NULL); babel_ifp->flags &= ~BABEL_IF_SPLIT_HORIZON; return CMD_SUCCESS; } /* [Interface Command]. */ DEFUN (babel_set_hello_interval, babel_set_hello_interval_cmd, "babel hello-interval (20-655340)", "Babel interface commands\n" "Time between scheduled hellos\n" "Milliseconds\n") { VTY_DECLVAR_CONTEXT(interface, ifp); babel_interface_nfo *babel_ifp; int interval; interval = strtoul(argv[2]->arg, NULL, 10); babel_ifp = babel_get_if_nfo(ifp); assert (babel_ifp != NULL); babel_ifp->hello_interval = interval; return CMD_SUCCESS; } /* [Interface Command]. */ DEFUN (babel_set_update_interval, babel_set_update_interval_cmd, "babel update-interval (20-655340)", "Babel interface commands\n" "Time between scheduled updates\n" "Milliseconds\n") { VTY_DECLVAR_CONTEXT(interface, ifp); babel_interface_nfo *babel_ifp; int interval; interval = strtoul(argv[2]->arg, NULL, 10); babel_ifp = babel_get_if_nfo(ifp); assert (babel_ifp != NULL); babel_ifp->update_interval = interval; return CMD_SUCCESS; } DEFUN (babel_set_rxcost, babel_set_rxcost_cmd, "babel rxcost (1-65534)", "Babel interface commands\n" "Rxcost multiplier\n" "Units\n") { VTY_DECLVAR_CONTEXT(interface, ifp); babel_interface_nfo *babel_ifp; int rxcost; rxcost = strtoul(argv[2]->arg, NULL, 10); babel_ifp = babel_get_if_nfo(ifp); assert (babel_ifp != NULL); babel_ifp->cost = rxcost; return CMD_SUCCESS; } DEFUN (babel_set_rtt_decay, babel_set_rtt_decay_cmd, "babel rtt-decay (1-256)", "Babel interface commands\n" "Decay factor for exponential moving average of RTT samples\n" "Units of 1/256\n") { VTY_DECLVAR_CONTEXT(interface, ifp); babel_interface_nfo *babel_ifp; int decay; decay = strtoul(argv[2]->arg, NULL, 10); babel_ifp = babel_get_if_nfo(ifp); assert (babel_ifp != NULL); babel_ifp->rtt_decay = decay; return CMD_SUCCESS; } DEFUN (babel_set_rtt_min, babel_set_rtt_min_cmd, "babel rtt-min (1-65535)", "Babel interface commands\n" "Minimum RTT starting for increasing cost\n" "Milliseconds\n") { VTY_DECLVAR_CONTEXT(interface, ifp); babel_interface_nfo *babel_ifp; int rtt; rtt = strtoul(argv[2]->arg, NULL, 10); babel_ifp = babel_get_if_nfo(ifp); assert (babel_ifp != NULL); babel_ifp->rtt_min = rtt; return CMD_SUCCESS; } DEFUN (babel_set_rtt_max, babel_set_rtt_max_cmd, "babel rtt-max (1-65535)", "Babel interface commands\n" "Maximum RTT\n" "Milliseconds\n") { VTY_DECLVAR_CONTEXT(interface, ifp); babel_interface_nfo *babel_ifp; int rtt; rtt = strtoul(argv[2]->arg, NULL, 10); babel_ifp = babel_get_if_nfo(ifp); assert (babel_ifp != NULL); babel_ifp->rtt_max = rtt; return CMD_SUCCESS; } DEFUN (babel_set_max_rtt_penalty, babel_set_max_rtt_penalty_cmd, "babel max-rtt-penalty (0-65535)", "Babel interface commands\n" "Maximum additional cost due to RTT\n" "Milliseconds\n") { VTY_DECLVAR_CONTEXT(interface, ifp); babel_interface_nfo *babel_ifp; int penalty; penalty = strtoul(argv[2]->arg, NULL, 10); babel_ifp = babel_get_if_nfo(ifp); assert (babel_ifp != NULL); babel_ifp->max_rtt_penalty = penalty; return CMD_SUCCESS; } DEFUN (babel_set_enable_timestamps, babel_set_enable_timestamps_cmd, "babel enable-timestamps", "Babel interface commands\n" "Enable timestamps\n") { VTY_DECLVAR_CONTEXT(interface, ifp); babel_interface_nfo *babel_ifp; babel_ifp = babel_get_if_nfo(ifp); assert (babel_ifp != NULL); babel_ifp->flags |= BABEL_IF_TIMESTAMPS; return CMD_SUCCESS; } DEFUN (no_babel_set_enable_timestamps, no_babel_set_enable_timestamps_cmd, "no babel enable-timestamps", NO_STR "Babel interface commands\n" "Disable timestamps\n") { VTY_DECLVAR_CONTEXT(interface, ifp); babel_interface_nfo *babel_ifp; babel_ifp = babel_get_if_nfo(ifp); assert (babel_ifp != NULL); babel_ifp->flags &= ~BABEL_IF_TIMESTAMPS; return CMD_SUCCESS; } DEFUN (babel_set_channel, babel_set_channel_cmd, "babel channel (1-254)", "Babel interface commands\n" "Channel number for diversity routing\n" "Number\n") { VTY_DECLVAR_CONTEXT(interface, ifp); babel_interface_nfo *babel_ifp; int channel; channel = strtoul(argv[2]->arg, NULL, 10); babel_ifp = babel_get_if_nfo(ifp); assert (babel_ifp != NULL); babel_ifp->channel = channel; return CMD_SUCCESS; } DEFUN (babel_set_channel_interfering, babel_set_channel_interfering_cmd, "babel channel interfering", "Babel interface commands\n" "Channel number for diversity routing\n" "Mark channel as interfering\n") { VTY_DECLVAR_CONTEXT(interface, ifp); babel_interface_nfo *babel_ifp; babel_ifp = babel_get_if_nfo(ifp); assert (babel_ifp != NULL); babel_ifp->channel = BABEL_IF_CHANNEL_INTERFERING; return CMD_SUCCESS; } DEFUN (babel_set_channel_noninterfering, babel_set_channel_noninterfering_cmd, "babel channel noninterfering", "Babel interface commands\n" "Channel number for diversity routing\n" "Mark channel as noninterfering\n") { VTY_DECLVAR_CONTEXT(interface, ifp); babel_interface_nfo *babel_ifp; babel_ifp = babel_get_if_nfo(ifp); assert (babel_ifp != NULL); babel_ifp->channel = BABEL_IF_CHANNEL_NONINTERFERING; return CMD_SUCCESS; } /* This should be no more than half the hello interval, so that hellos aren't sent late. The result is in milliseconds. */ unsigned jitter(babel_interface_nfo *babel_ifp, int urgent) { unsigned interval = babel_ifp->hello_interval; if(urgent) interval = MIN(interval, 100); else interval = MIN(interval, 4000); return roughly(interval) / 4; } unsigned update_jitter(babel_interface_nfo *babel_ifp, int urgent) { unsigned interval = babel_ifp->hello_interval; if(urgent) interval = MIN(interval, 100); else interval = MIN(interval, 4000); return roughly(interval); } /* calculate babeld's specific datas of an interface (change when the interface change) */ static int interface_recalculate(struct interface *ifp) { babel_interface_nfo *babel_ifp = babel_get_if_nfo(ifp); unsigned char *tmp = NULL; int mtu, rc; struct ipv6_mreq mreq; if (!IS_ENABLE(ifp)) return -1; if (!if_is_operative(ifp) || !CHECK_FLAG(ifp->flags, IFF_RUNNING)) { interface_reset(ifp); return -1; } babel_ifp->flags |= BABEL_IF_IS_UP; mtu = MIN(ifp->mtu, ifp->mtu6); /* We need to be able to fit at least two messages into a packet, so MTUs below 116 require lower layer fragmentation. */ /* In IPv6, the minimum MTU is 1280, and every host must be able to reassemble up to 1500 bytes, but I'd rather not rely on this. */ if(mtu < 128) { debugf(BABEL_DEBUG_IF, "Suspiciously low MTU %d on interface %s (%d).", mtu, ifp->name, ifp->ifindex); mtu = 128; } /* 4 for Babel header; 40 for IPv6 header, 8 for UDP header, 12 for good luck. */ babel_ifp->bufsize = mtu - 4 - 60; tmp = babel_ifp->sendbuf; babel_ifp->sendbuf = realloc(babel_ifp->sendbuf, babel_ifp->bufsize); if(babel_ifp->sendbuf == NULL) { flog_err(EC_BABEL_MEMORY, "Couldn't reallocate sendbuf."); free(tmp); babel_ifp->bufsize = 0; return -1; } tmp = NULL; rc = resize_receive_buffer(mtu); if(rc < 0) zlog_warn("couldn't resize " "receive buffer for interface %s (%d) (%d bytes).\n", ifp->name, ifp->ifindex, mtu); memset(&mreq, 0, sizeof(mreq)); memcpy(&mreq.ipv6mr_multiaddr, protocol_group, 16); mreq.ipv6mr_interface = ifp->ifindex; rc = setsockopt(protocol_socket, IPPROTO_IPV6, IPV6_JOIN_GROUP, (char*)&mreq, sizeof(mreq)); if(rc < 0) { flog_err_sys(EC_LIB_SOCKET, "setsockopt(IPV6_JOIN_GROUP) on interface '%s': %s", ifp->name, safe_strerror(errno)); /* This is probably due to a missing link-local address, so down this interface, and wait until the main loop tries to up it again. */ interface_reset(ifp); return -1; } set_timeout(&babel_ifp->hello_timeout, babel_ifp->hello_interval); set_timeout(&babel_ifp->update_timeout, babel_ifp->update_interval); send_hello(ifp); send_request(ifp, NULL, 0); update_interface_metric(ifp); debugf(BABEL_DEBUG_COMMON, "Upped interface %s (%s, cost=%d, channel=%d%s).", ifp->name, (babel_ifp->flags & BABEL_IF_WIRED) ? "wired" : "wireless", babel_ifp->cost, babel_ifp->channel, babel_ifp->ipv4 ? ", IPv4" : ""); if(rc > 0) send_update(ifp, 0, NULL, 0); return 1; } /* Reset the interface as it was new: it's not removed from the interface list, and may be considered as a upped interface. */ static int interface_reset(struct interface *ifp) { int rc; struct ipv6_mreq mreq; babel_interface_nfo *babel_ifp = babel_get_if_nfo(ifp); if (!(babel_ifp->flags & BABEL_IF_IS_UP)) return 0; debugf(BABEL_DEBUG_IF, "interface reset: %s", ifp->name); babel_ifp->flags &= ~BABEL_IF_IS_UP; flush_interface_routes(ifp, 0); babel_ifp->buffered = 0; babel_ifp->bufsize = 0; free(babel_ifp->sendbuf); babel_ifp->num_buffered_updates = 0; babel_ifp->update_bufsize = 0; if(babel_ifp->buffered_updates) free(babel_ifp->buffered_updates); babel_ifp->buffered_updates = NULL; babel_ifp->sendbuf = NULL; if(ifp->ifindex > 0) { memset(&mreq, 0, sizeof(mreq)); memcpy(&mreq.ipv6mr_multiaddr, protocol_group, 16); mreq.ipv6mr_interface = ifp->ifindex; rc = setsockopt(protocol_socket, IPPROTO_IPV6, IPV6_LEAVE_GROUP, (char*)&mreq, sizeof(mreq)); if(rc < 0) flog_err_sys(EC_LIB_SOCKET, "setsockopt(IPV6_LEAVE_GROUP) on interface '%s': %s", ifp->name, safe_strerror(errno)); } update_interface_metric(ifp); debugf(BABEL_DEBUG_COMMON,"Upped network %s (%s, cost=%d%s).", ifp->name, (babel_ifp->flags & BABEL_IF_WIRED) ? "wired" : "wireless", babel_ifp->cost, babel_ifp->ipv4 ? ", IPv4" : ""); return 1; } /* Send retraction to all, and reset all interfaces statistics. */ void babel_interface_close_all(void) { struct vrf *vrf = vrf_lookup_by_id(VRF_DEFAULT); struct interface *ifp = NULL; FOR_ALL_INTERFACES(vrf, ifp) { if(!if_up(ifp)) continue; send_wildcard_retraction(ifp); /* Make sure that we expire quickly from our neighbours' association caches. */ send_hello_noupdate(ifp, 10); flushbuf(ifp); usleep(roughly(1000)); gettime(&babel_now); } FOR_ALL_INTERFACES(vrf, ifp) { if(!if_up(ifp)) continue; /* Make sure they got it. */ send_wildcard_retraction(ifp); send_hello_noupdate(ifp, 1); flushbuf(ifp); usleep(roughly(10000)); gettime(&babel_now); interface_reset(ifp); } } /* return "true" if address is one of our ipv6 addresses */ int is_interface_ll_address(struct interface *ifp, const unsigned char *address) { struct connected *connected; struct listnode *node; if(!if_up(ifp)) return 0; FOR_ALL_INTERFACES_ADDRESSES(ifp, connected, node) { if(connected->address->family == AF_INET6 && memcmp(&connected->address->u.prefix6, address, 16) == 0) return 1; } return 0; } static void show_babel_interface_sub (struct vty *vty, struct interface *ifp) { int is_up; babel_interface_nfo *babel_ifp; vty_out (vty, "%s is %s\n", ifp->name, ((is_up = if_is_operative(ifp)) ? "up" : "down")); vty_out (vty, " ifindex %u, MTU %u bytes %s\n", ifp->ifindex, MIN(ifp->mtu, ifp->mtu6), if_flag_dump(ifp->flags)); if (!IS_ENABLE(ifp)) { vty_out (vty, " Babel protocol is not enabled on this interface\n"); return; } if (!is_up) { vty_out (vty, " Babel protocol is enabled, but not running on this interface\n"); return; } babel_ifp = babel_get_if_nfo (ifp); vty_out (vty, " Babel protocol is running on this interface\n"); vty_out (vty, " Operating mode is \"%s\"\n", CHECK_FLAG(babel_ifp->flags, BABEL_IF_WIRED) ? "wired" : "wireless"); vty_out (vty, " Split horizon mode is %s\n", CHECK_FLAG(babel_ifp->flags, BABEL_IF_SPLIT_HORIZON) ? "On" : "Off"); vty_out (vty, " Hello interval is %u ms\n", babel_ifp->hello_interval); vty_out (vty, " Update interval is %u ms\n", babel_ifp->update_interval); vty_out (vty, " Rxcost multiplier is %u\n", babel_ifp->cost); } DEFUN (show_babel_interface, show_babel_interface_cmd, "show babel interface [IFNAME]", SHOW_STR "Babel information\n" "Interface information\n" "Interface\n") { struct vrf *vrf = vrf_lookup_by_id(VRF_DEFAULT); struct interface *ifp; if (argc == 3) { FOR_ALL_INTERFACES (vrf, ifp) show_babel_interface_sub (vty, ifp); return CMD_SUCCESS; } if ((ifp = if_lookup_by_name (argv[3]->arg, VRF_DEFAULT)) == NULL) { vty_out (vty, "No such interface name\n"); return CMD_WARNING; } show_babel_interface_sub (vty, ifp); return CMD_SUCCESS; } static void show_babel_neighbour_sub (struct vty *vty, struct neighbour *neigh) { vty_out (vty, "Neighbour %s dev %s reach %04x rxcost %d txcost %d " "rtt %s rttcost %d%s.\n", format_address(neigh->address), neigh->ifp->name, neigh->reach, neighbour_rxcost(neigh), neigh->txcost, format_thousands(neigh->rtt), neighbour_rttcost(neigh), if_up(neigh->ifp) ? "" : " (down)"); } DEFUN (show_babel_neighbour, show_babel_neighbour_cmd, "show babel neighbor [IFNAME]", SHOW_STR "Babel information\n" "Print neighbors\n" "Interface\n") { struct neighbour *neigh; struct interface *ifp; if (argc == 3) { FOR_ALL_NEIGHBOURS(neigh) { show_babel_neighbour_sub(vty, neigh); } return CMD_SUCCESS; } if ((ifp = if_lookup_by_name (argv[3]->arg, VRF_DEFAULT)) == NULL) { vty_out (vty, "No such interface name\n"); return CMD_WARNING; } FOR_ALL_NEIGHBOURS(neigh) { if(ifp->ifindex == neigh->ifp->ifindex) { show_babel_neighbour_sub(vty, neigh); } } return CMD_SUCCESS; } static int babel_prefix_eq(struct prefix *prefix, unsigned char *p, int plen) { if(prefix->family == AF_INET6) { if(prefix->prefixlen != plen || memcmp(&prefix->u.prefix6, p, 16) != 0) return 0; } else if(prefix->family == AF_INET) { if(plen < 96 || !v4mapped(p) || prefix->prefixlen != plen - 96 || memcmp(&prefix->u.prefix4, p + 12, 4) != 0) return 0; } else { return 0; } return 1; } static void show_babel_routes_sub(struct babel_route *route, struct vty *vty, struct prefix *prefix) { const unsigned char *nexthop = memcmp(route->nexthop, route->neigh->address, 16) == 0 ? NULL : route->nexthop; char channels[100]; if(prefix && !babel_prefix_eq(prefix, route->src->prefix, route->src->plen)) return; if(route->channels[0] == 0) channels[0] = '\0'; else { int k, j = 0; snprintf(channels, 100, " chan ("); j = strlen(channels); for(k = 0; k < DIVERSITY_HOPS; k++) { if(route->channels[k] == 0) break; if(k > 0) channels[j++] = ','; snprintf(channels + j, 100 - j, "%u", route->channels[k]); j = strlen(channels); } snprintf(channels + j, 100 - j, ")"); if(k == 0) channels[0] = '\0'; } vty_out (vty, "%s metric %d refmetric %d id %s seqno %d%s age %d " "via %s neigh %s%s%s%s\n", format_prefix(route->src->prefix, route->src->plen), route_metric(route), route->refmetric, format_eui64(route->src->id), (int)route->seqno, channels, (int)(babel_now.tv_sec - route->time), route->neigh->ifp->name, format_address(route->neigh->address), nexthop ? " nexthop " : "", nexthop ? format_address(nexthop) : "", route->installed ? " (installed)" : route_feasible(route) ? " (feasible)" : ""); } static void show_babel_xroutes_sub (struct xroute *xroute, struct vty *vty, struct prefix *prefix) { if(prefix && !babel_prefix_eq(prefix, xroute->prefix, xroute->plen)) return; vty_out (vty, "%s metric %d (exported)\n", format_prefix(xroute->prefix, xroute->plen), xroute->metric); } DEFUN (show_babel_route, show_babel_route_cmd, "show babel route", SHOW_STR "Babel information\n" "Babel internal routing table\n") { struct route_stream *routes = NULL; struct xroute_stream *xroutes = NULL; routes = route_stream(0); if(routes) { while(1) { struct babel_route *route = route_stream_next(routes); if(route == NULL) break; show_babel_routes_sub(route, vty, NULL); } route_stream_done(routes); } else { flog_err(EC_BABEL_MEMORY, "Couldn't allocate route stream."); } xroutes = xroute_stream(); if(xroutes) { while(1) { struct xroute *xroute = xroute_stream_next(xroutes); if(xroute == NULL) break; show_babel_xroutes_sub(xroute, vty, NULL); } xroute_stream_done(xroutes); } else { flog_err(EC_BABEL_MEMORY, "Couldn't allocate route stream."); } return CMD_SUCCESS; } DEFUN (show_babel_route_prefix, show_babel_route_prefix_cmd, "show babel route ", SHOW_STR "Babel information\n" "Babel internal routing table\n" "IPv4 prefix /\n" "IPv6 prefix /\n") { struct route_stream *routes = NULL; struct xroute_stream *xroutes = NULL; struct prefix prefix; int ret; ret = str2prefix(argv[3]->arg, &prefix); if(ret == 0) { vty_out (vty, "%% Malformed address\n"); return CMD_WARNING; } routes = route_stream(0); if(routes) { while(1) { struct babel_route *route = route_stream_next(routes); if(route == NULL) break; show_babel_routes_sub(route, vty, &prefix); } route_stream_done(routes); } else { flog_err(EC_BABEL_MEMORY, "Couldn't allocate route stream."); } xroutes = xroute_stream(); if(xroutes) { while(1) { struct xroute *xroute = xroute_stream_next(xroutes); if(xroute == NULL) break; show_babel_xroutes_sub(xroute, vty, &prefix); } xroute_stream_done(xroutes); } else { flog_err(EC_BABEL_MEMORY, "Couldn't allocate route stream."); } return CMD_SUCCESS; } DEFUN (show_babel_route_addr, show_babel_route_addr_cmd, "show babel route A.B.C.D", SHOW_STR "Babel information\n" "Babel internal routing table\n" "IPv4 address /\n") { struct in_addr addr; char buf[INET_ADDRSTRLEN + 8]; struct route_stream *routes = NULL; struct xroute_stream *xroutes = NULL; struct prefix prefix; int ret; ret = inet_aton (argv[3]->arg, &addr); if (ret <= 0) { vty_out (vty, "%% Malformed address\n"); return CMD_WARNING; } /* Quagga has no convenient prefix constructors. */ snprintf(buf, sizeof(buf), "%s/%d", inet_ntoa(addr), 32); ret = str2prefix(buf, &prefix); if (ret == 0) { vty_out (vty, "%% Parse error -- this shouldn't happen\n"); return CMD_WARNING; } routes = route_stream(0); if(routes) { while(1) { struct babel_route *route = route_stream_next(routes); if(route == NULL) break; show_babel_routes_sub(route, vty, &prefix); } route_stream_done(routes); } else { flog_err(EC_BABEL_MEMORY, "Couldn't allocate route stream."); } xroutes = xroute_stream(); if(xroutes) { while(1) { struct xroute *xroute = xroute_stream_next(xroutes); if(xroute == NULL) break; show_babel_xroutes_sub(xroute, vty, &prefix); } xroute_stream_done(xroutes); } else { flog_err(EC_BABEL_MEMORY, "Couldn't allocate route stream."); } return CMD_SUCCESS; } DEFUN (show_babel_route_addr6, show_babel_route_addr6_cmd, "show babel route X:X::X:X", SHOW_STR "Babel information\n" "Babel internal routing table\n" "IPv6 address /\n") { struct in6_addr addr; char buf1[INET6_ADDRSTRLEN]; char buf[INET6_ADDRSTRLEN + 8]; struct route_stream *routes = NULL; struct xroute_stream *xroutes = NULL; struct prefix prefix; int ret; ret = inet_pton (AF_INET6, argv[3]->arg, &addr); if (ret <= 0) { vty_out (vty, "%% Malformed address\n"); return CMD_WARNING; } /* Quagga has no convenient prefix constructors. */ snprintf(buf, sizeof(buf), "%s/%d", inet_ntop(AF_INET6, &addr, buf1, sizeof(buf1)), 128); ret = str2prefix(buf, &prefix); if (ret == 0) { vty_out (vty, "%% Parse error -- this shouldn't happen\n"); return CMD_WARNING; } routes = route_stream(0); if(routes) { while(1) { struct babel_route *route = route_stream_next(routes); if(route == NULL) break; show_babel_routes_sub(route, vty, &prefix); } route_stream_done(routes); } else { flog_err(EC_BABEL_MEMORY, "Couldn't allocate route stream."); } xroutes = xroute_stream(); if(xroutes) { while(1) { struct xroute *xroute = xroute_stream_next(xroutes); if(xroute == NULL) break; show_babel_xroutes_sub(xroute, vty, &prefix); } xroute_stream_done(xroutes); } else { flog_err(EC_BABEL_MEMORY, "Couldn't allocate route stream."); } return CMD_SUCCESS; } DEFUN (show_babel_parameters, show_babel_parameters_cmd, "show babel parameters", SHOW_STR "Babel information\n" "Configuration information\n") { struct babel *babel_ctx; vty_out (vty, " -- Babel running configuration --\n"); show_babel_main_configuration(vty); babel_ctx = babel_lookup(); if (babel_ctx) { vty_out (vty, " -- distribution lists --\n"); config_show_distribute(vty, babel_ctx->distribute_ctx); } return CMD_SUCCESS; } void babel_if_init(void) { /* initialize interface list */ hook_register_prio(if_add, 0, babel_if_new_hook); hook_register_prio(if_del, 0, babel_if_delete_hook); babel_enable_if = vector_init (1); /* install interface node and commands */ install_node (&babel_interface_node, interface_config_write); if_cmd_init(); install_element(BABEL_NODE, &babel_network_cmd); install_element(BABEL_NODE, &no_babel_network_cmd); install_element(INTERFACE_NODE, &babel_split_horizon_cmd); install_element(INTERFACE_NODE, &no_babel_split_horizon_cmd); install_element(INTERFACE_NODE, &babel_set_wired_cmd); install_element(INTERFACE_NODE, &babel_set_wireless_cmd); install_element(INTERFACE_NODE, &babel_set_hello_interval_cmd); install_element(INTERFACE_NODE, &babel_set_update_interval_cmd); install_element(INTERFACE_NODE, &babel_set_rxcost_cmd); install_element(INTERFACE_NODE, &babel_set_channel_cmd); install_element(INTERFACE_NODE, &babel_set_rtt_decay_cmd); install_element(INTERFACE_NODE, &babel_set_rtt_min_cmd); install_element(INTERFACE_NODE, &babel_set_rtt_max_cmd); install_element(INTERFACE_NODE, &babel_set_max_rtt_penalty_cmd); install_element(INTERFACE_NODE, &babel_set_enable_timestamps_cmd); install_element(INTERFACE_NODE, &no_babel_set_enable_timestamps_cmd); install_element(INTERFACE_NODE, &babel_set_channel_interfering_cmd); install_element(INTERFACE_NODE, &babel_set_channel_noninterfering_cmd); /* "show babel ..." commands */ install_element(VIEW_NODE, &show_babel_interface_cmd); install_element(VIEW_NODE, &show_babel_neighbour_cmd); install_element(VIEW_NODE, &show_babel_route_cmd); install_element(VIEW_NODE, &show_babel_route_prefix_cmd); install_element(VIEW_NODE, &show_babel_route_addr_cmd); install_element(VIEW_NODE, &show_babel_route_addr6_cmd); install_element(VIEW_NODE, &show_babel_parameters_cmd); } /* hooks: functions called respectively when struct interface is created or deleted. */ static int babel_if_new_hook (struct interface *ifp) { ifp->info = babel_interface_allocate(); return 0; } static int babel_if_delete_hook (struct interface *ifp) { babel_interface_free(ifp->info); ifp->info = NULL; return 0; } /* Output an "interface" section for each of the known interfaces with babeld-specific statement lines where appropriate. */ static int interface_config_write (struct vty *vty) { struct vrf *vrf = vrf_lookup_by_id(VRF_DEFAULT); struct interface *ifp; int write = 0; FOR_ALL_INTERFACES (vrf, ifp) { vty_frame (vty, "interface %s\n",ifp->name); if (ifp->desc) vty_out (vty, " description %s\n",ifp->desc); babel_interface_nfo *babel_ifp = babel_get_if_nfo (ifp); /* wireless is the default*/ if (CHECK_FLAG (babel_ifp->flags, BABEL_IF_WIRED)) { vty_out (vty, " babel wired\n"); write++; } if (babel_ifp->hello_interval != BABEL_DEFAULT_HELLO_INTERVAL) { vty_out (vty, " babel hello-interval %u\n", babel_ifp->hello_interval); write++; } if (babel_ifp->update_interval != BABEL_DEFAULT_UPDATE_INTERVAL) { vty_out (vty, " babel update-interval %u\n", babel_ifp->update_interval); write++; } /* Some parameters have different defaults for wired/wireless. */ if (CHECK_FLAG (babel_ifp->flags, BABEL_IF_WIRED)) { if (!CHECK_FLAG (babel_ifp->flags, BABEL_IF_SPLIT_HORIZON)) { vty_out (vty, " no babel split-horizon\n"); write++; } if (babel_ifp->cost != BABEL_DEFAULT_RXCOST_WIRED) { vty_out (vty, " babel rxcost %u\n", babel_ifp->cost); write++; } if (babel_ifp->channel == BABEL_IF_CHANNEL_INTERFERING) { vty_out (vty, " babel channel interfering\n"); write++; } else if(babel_ifp->channel != BABEL_IF_CHANNEL_NONINTERFERING) { vty_out (vty, " babel channel %d\n",babel_ifp->channel); write++; } } else { if (CHECK_FLAG (babel_ifp->flags, BABEL_IF_SPLIT_HORIZON)) { vty_out (vty, " babel split-horizon\n"); write++; } if (babel_ifp->cost != BABEL_DEFAULT_RXCOST_WIRELESS) { vty_out (vty, " babel rxcost %u\n", babel_ifp->cost); write++; } if (babel_ifp->channel == BABEL_IF_CHANNEL_NONINTERFERING) { vty_out (vty, " babel channel noninterfering\n"); write++; } else if(babel_ifp->channel != BABEL_IF_CHANNEL_INTERFERING) { vty_out (vty, " babel channel %d\n",babel_ifp->channel); write++; } } vty_endframe (vty, "!\n"); write++; } return write; } /* Output a "network" statement line for each of the enabled interfaces. */ int babel_enable_if_config_write (struct vty * vty) { unsigned int i, lines = 0; char *str; for (i = 0; i < vector_active (babel_enable_if); i++) if ((str = vector_slot (babel_enable_if, i)) != NULL) { vty_out (vty, " network %s\n", str); lines++; } return lines; } /* functions to allocate or free memory for a babel_interface_nfo, filling needed fields */ static babel_interface_nfo * babel_interface_allocate (void) { babel_interface_nfo *babel_ifp; babel_ifp = XCALLOC(MTYPE_BABEL_IF, sizeof(babel_interface_nfo)); /* All flags are unset */ babel_ifp->bucket_time = babel_now.tv_sec; babel_ifp->bucket = BUCKET_TOKENS_MAX; babel_ifp->hello_seqno = (random() & 0xFFFF); babel_ifp->rtt_min = 10000; babel_ifp->rtt_max = 120000; babel_ifp->max_rtt_penalty = 150; babel_ifp->hello_interval = BABEL_DEFAULT_HELLO_INTERVAL; babel_ifp->update_interval = BABEL_DEFAULT_UPDATE_INTERVAL; babel_ifp->channel = BABEL_IF_CHANNEL_INTERFERING; babel_set_wired_internal(babel_ifp, 0); return babel_ifp; } static void babel_interface_free (babel_interface_nfo *babel_ifp) { XFREE(MTYPE_BABEL_IF, babel_ifp); } frr-7.2.1/babeld/babel_interface.h0000644000000000000000000001110713610377563013663 00000000000000/* Copyright 2011 by Matthieu Boutier and Juliusz Chroboczek Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions: The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. */ #ifndef BABEL_INTERFACE_H #define BABEL_INTERFACE_H #include #include "zclient.h" #include "vty.h" #include "distribute.h" #define CONFIG_DEFAULT 0 #define CONFIG_NO 1 #define CONFIG_YES 2 /* babeld interface information */ struct babel_interface { unsigned short flags; /* see below */ unsigned short cost; int channel; struct timeval hello_timeout; struct timeval update_timeout; struct timeval flush_timeout; struct timeval update_flush_timeout; unsigned char *ipv4; int buffered; int bufsize; /* Relative position of the Hello message in the send buffer, or (-1) if there is none. */ int buffered_hello; char have_buffered_id; char have_buffered_nh; char have_buffered_prefix; unsigned char buffered_id[8]; unsigned char buffered_nh[4]; unsigned char buffered_prefix[16]; unsigned char *sendbuf; struct buffered_update *buffered_updates; int num_buffered_updates; int update_bufsize; time_t bucket_time; unsigned int bucket; time_t last_update_time; unsigned short hello_seqno; unsigned hello_interval; unsigned update_interval; /* A higher value means we forget old RTT samples faster. Must be between 1 and 256, inclusive. */ unsigned int rtt_decay; /* Parameters for computing the cost associated to RTT. */ unsigned int rtt_min; unsigned int rtt_max; unsigned int max_rtt_penalty; /* For filter type slot. */ struct access_list *list[DISTRIBUTE_MAX]; /* Access-list. */ struct prefix_list *prefix[DISTRIBUTE_MAX]; /* Prefix-list. */ }; typedef struct babel_interface babel_interface_nfo; static inline babel_interface_nfo* babel_get_if_nfo(struct interface *ifp) { return ((babel_interface_nfo*) ifp->info); } /* babel_interface_nfo flags */ #define BABEL_IF_IS_UP (1 << 0) #define BABEL_IF_WIRED (1 << 1) #define BABEL_IF_SPLIT_HORIZON (1 << 2) #define BABEL_IF_LQ (1 << 3) #define BABEL_IF_FARAWAY (1 << 4) #define BABEL_IF_TIMESTAMPS (1 << 5) /* Only INTERFERING can appear on the wire. */ #define BABEL_IF_CHANNEL_UNKNOWN 0 #define BABEL_IF_CHANNEL_INTERFERING 255 #define BABEL_IF_CHANNEL_NONINTERFERING -2 static inline int if_up(struct interface *ifp) { return (if_is_operative(ifp) && ifp->connected != NULL && (babel_get_if_nfo(ifp)->flags & BABEL_IF_IS_UP)); } struct buffered_update { unsigned char id[8]; unsigned char prefix[16]; unsigned char plen; unsigned char pad[3]; }; /* init function */ void babel_if_init(void); /* Callback functions for zebra client */ int babel_interface_up (int, struct zclient *, zebra_size_t, vrf_id_t); int babel_interface_down (int, struct zclient *, zebra_size_t, vrf_id_t); int babel_interface_add (int, struct zclient *, zebra_size_t, vrf_id_t); int babel_interface_delete (int, struct zclient *, zebra_size_t, vrf_id_t); int babel_interface_address_add (int, struct zclient *, zebra_size_t, vrf_id_t); int babel_interface_address_delete (int, struct zclient *, zebra_size_t, vrf_id_t); unsigned jitter(babel_interface_nfo *, int); unsigned update_jitter(babel_interface_nfo *babel_ifp, int urgent); /* return "true" if "address" is one of our ipv6 addresses */ int is_interface_ll_address(struct interface *ifp, const unsigned char *address); /* Send retraction to all, and reset all interfaces statistics. */ void babel_interface_close_all(void); extern int babel_enable_if_config_write (struct vty *); #endif frr-7.2.1/babeld/babel_main.c0000644000000000000000000002442013610377563012644 00000000000000/* Copyright 2011 by Matthieu Boutier and Juliusz Chroboczek Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions: The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. */ /* include zebra library */ #include #include "getopt.h" #include "if.h" #include "log.h" #include "thread.h" #include "privs.h" #include "sigevent.h" #include "version.h" #include "command.h" #include "vty.h" #include "memory.h" #include "libfrr.h" #include "lib_errors.h" #include "babel_main.h" #include "babeld.h" #include "util.h" #include "kernel.h" #include "babel_interface.h" #include "neighbour.h" #include "route.h" #include "xroute.h" #include "message.h" #include "resend.h" #include "babel_zebra.h" #include "babel_errors.h" static void babel_fail(void); static void babel_init_random(void); static void babel_replace_by_null(int fd); static void babel_exit_properly(void); static void babel_save_state_file(void); struct thread_master *master; /* quagga's threads handler */ struct timeval babel_now; /* current time */ unsigned char myid[8]; /* unique id (mac address of an interface) */ int debug = 0; int resend_delay = -1; const unsigned char zeroes[16] = {0}; const unsigned char ones[16] = {0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF}; static char state_file[1024]; unsigned char protocol_group[16]; /* babel's link-local multicast address */ int protocol_port; /* babel's port */ int protocol_socket = -1; /* socket: communicate with others babeld */ static char babel_config_default[] = SYSCONFDIR BABEL_DEFAULT_CONFIG; static char *babel_vty_addr = NULL; static int babel_vty_port = BABEL_VTY_PORT; /* babeld privileges */ static zebra_capabilities_t _caps_p [] = { ZCAP_NET_RAW, ZCAP_BIND }; struct zebra_privs_t babeld_privs = { #if defined(FRR_USER) .user = FRR_USER, #endif #if defined FRR_GROUP .group = FRR_GROUP, #endif #ifdef VTY_GROUP .vty_group = VTY_GROUP, #endif .caps_p = _caps_p, .cap_num_p = array_size(_caps_p), .cap_num_i = 0 }; static void babel_sigexit(void) { zlog_notice("Terminating on signal"); babel_exit_properly(); } static void babel_sigusr1 (void) { zlog_rotate (); } static struct quagga_signal_t babel_signals[] = { { .signal = SIGUSR1, .handler = &babel_sigusr1, }, { .signal = SIGINT, .handler = &babel_sigexit, }, { .signal = SIGTERM, .handler = &babel_sigexit, }, }; struct option longopts[] = { { 0 } }; static const struct frr_yang_module_info *babeld_yang_modules[] = { &frr_interface_info, }; FRR_DAEMON_INFO(babeld, BABELD, .vty_port = BABEL_VTY_PORT, .proghelp = "Implementation of the BABEL routing protocol.", .signals = babel_signals, .n_signals = array_size(babel_signals), .privs = &babeld_privs, .yang_modules = babeld_yang_modules, .n_yang_modules = array_size(babeld_yang_modules), ) int main(int argc, char **argv) { int rc; frr_preinit (&babeld_di, argc, argv); frr_opt_add ("", longopts, ""); babel_init_random(); /* set the Babel's default link-local multicast address and Babel's port */ parse_address("ff02:0:0:0:0:0:1:6", protocol_group, NULL); protocol_port = 6696; /* get options */ while(1) { int opt; opt = frr_getopt (argc, argv, NULL); if (opt == EOF) break; switch (opt) { case 0: break; default: frr_help_exit (1); break; } } snprintf(state_file, sizeof(state_file), "%s/%s", frr_vtydir, "babel-state"); /* create the threads handler */ master = frr_init (); /* Library inits. */ babel_error_init(); resend_delay = BABEL_DEFAULT_RESEND_DELAY; change_smoothing_half_life(BABEL_DEFAULT_SMOOTHING_HALF_LIFE); babel_replace_by_null(STDIN_FILENO); /* init some quagga's dependencies, and babeld's commands */ babeld_quagga_init(); /* init zebra client's structure and it's commands */ /* this replace kernel_setup && kernel_setup_socket */ babelz_zebra_init (); /* init buffer */ rc = resize_receive_buffer(1500); if(rc < 0) babel_fail(); schedule_neighbours_check(5000, 1); frr_config_fork(); frr_run(master); return 0; } static void babel_fail(void) { exit(1); } /* initialize random value, and set 'babel_now' by the way. */ static void babel_init_random(void) { gettime(&babel_now); int rc; unsigned int seed; rc = read_random_bytes(&seed, sizeof(seed)); if(rc < 0) { flog_err_sys(EC_LIB_SYSTEM_CALL, "read(random): %s", safe_strerror(errno)); seed = 42; } seed ^= (babel_now.tv_sec ^ babel_now.tv_usec); srandom(seed); } /* close fd, and replace it by "/dev/null" exit if error */ static void babel_replace_by_null(int fd) { int fd_null; int rc; fd_null = open("/dev/null", O_RDONLY); if(fd_null < 0) { flog_err_sys(EC_LIB_SYSTEM_CALL, "open(null): %s", safe_strerror(errno)); exit(1); } rc = dup2(fd_null, fd); if(rc < 0) { flog_err_sys(EC_LIB_SYSTEM_CALL, "dup2(null, 0): %s", safe_strerror(errno)); exit(1); } close(fd_null); } /* Load the state file: check last babeld's running state, usefull in case of "/etc/init.d/babeld restart" */ void babel_load_state_file(void) { int fd; int rc; fd = open(state_file, O_RDONLY); if(fd < 0 && errno != ENOENT) flog_err_sys(EC_LIB_SYSTEM_CALL, "open(babel-state: %s)", safe_strerror(errno)); rc = unlink(state_file); if(fd >= 0 && rc < 0) { flog_err_sys(EC_LIB_SYSTEM_CALL, "unlink(babel-state): %s", safe_strerror(errno)); /* If we couldn't unlink it, it's probably stale. */ goto fini; } if(fd >= 0) { char buf[100]; char buf2[100]; int s; long t; rc = read(fd, buf, 99); if(rc < 0) { flog_err_sys(EC_LIB_SYSTEM_CALL, "read(babel-state): %s", safe_strerror(errno)); } else { buf[rc] = '\0'; rc = sscanf(buf, "%99s %d %ld\n", buf2, &s, &t); if(rc == 3 && s >= 0 && s <= 0xFFFF) { unsigned char sid[8]; rc = parse_eui64(buf2, sid); if(rc < 0) { flog_err(EC_BABEL_CONFIG, "Couldn't parse babel-state."); } else { struct timeval realnow; debugf(BABEL_DEBUG_COMMON, "Got %s %d %ld from babel-state.", format_eui64(sid), s, t); gettimeofday(&realnow, NULL); if(memcmp(sid, myid, 8) == 0) myseqno = seqno_plus(s, 1); else flog_err(EC_BABEL_CONFIG, "ID mismatch in babel-state. id=%s; old=%s", format_eui64(myid), format_eui64(sid)); } } else { flog_err(EC_BABEL_CONFIG, "Couldn't parse babel-state."); } } goto fini; } fini: if (fd >= 0) close(fd); return ; } static void babel_exit_properly(void) { debugf(BABEL_DEBUG_COMMON, "Exiting..."); usleep(roughly(10000)); gettime(&babel_now); /* Uninstall and flush all routes. */ debugf(BABEL_DEBUG_COMMON, "Uninstall routes."); flush_all_routes(); babel_interface_close_all(); babel_zebra_close_connexion(); babel_save_state_file(); debugf(BABEL_DEBUG_COMMON, "Remove pid file."); debugf(BABEL_DEBUG_COMMON, "Done."); frr_fini(); exit(0); } static void babel_save_state_file(void) { int fd; int rc; debugf(BABEL_DEBUG_COMMON, "Save state file."); fd = open(state_file, O_WRONLY | O_TRUNC | O_CREAT, 0644); if(fd < 0) { flog_err_sys(EC_LIB_SYSTEM_CALL, "creat(babel-state): %s", safe_strerror(errno)); unlink(state_file); } else { struct timeval realnow; char buf[100]; gettimeofday(&realnow, NULL); rc = snprintf(buf, 100, "%s %d %ld\n", format_eui64(myid), (int)myseqno, (long)realnow.tv_sec); if(rc < 0 || rc >= 100) { flog_err(EC_BABEL_CONFIG, "write(babel-state): overflow."); unlink(state_file); } else { rc = write(fd, buf, rc); if(rc < 0) { flog_err(EC_BABEL_CONFIG, "write(babel-state): %s", safe_strerror(errno)); unlink(state_file); } fsync(fd); } close(fd); } } void show_babel_main_configuration (struct vty *vty) { vty_out (vty, "state file = %s\n" "configuration file = %s\n" "protocol information:\n" " multicast address = %s\n" " port = %d\n" "vty address = %s\n" "vty port = %d\n" "id = %s\n" "kernel_metric = %d\n", state_file, babeld_di.config_file ? babeld_di.config_file : babel_config_default, format_address(protocol_group), protocol_port, babel_vty_addr ? babel_vty_addr : "None", babel_vty_port, format_eui64(myid), kernel_metric); } frr-7.2.1/babeld/babel_main.h0000644000000000000000000000323213610377563012647 00000000000000/* Copyright 2011 by Matthieu Boutier and Juliusz Chroboczek Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions: The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. */ #ifndef BABEL_MAIN_H #define BABEL_MAIN_H #include "vty.h" extern struct timeval babel_now; /* current time */ extern struct thread_master *master; /* quagga's threads handler */ extern int debug; extern int resend_delay; extern unsigned char myid[8]; extern const unsigned char zeroes[16], ones[16]; extern int protocol_port; extern unsigned char protocol_group[16]; extern int protocol_socket; extern int kernel_socket; extern int max_request_hopcount; void babel_load_state_file(void); void show_babel_main_configuration (struct vty *vty); #endif /* BABEL_MAIN_H */ frr-7.2.1/babeld/babel_zebra.c0000644000000000000000000001640113610377563013023 00000000000000/* Copyright 2011 by Matthieu Boutier and Juliusz Chroboczek Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions: The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. */ /* FRR's includes */ #include #include "command.h" #include "zclient.h" #include "stream.h" /* babel's includes*/ #include "babel_zebra.h" #include "babel_interface.h" #include "xroute.h" #include "util.h" void babelz_zebra_init(void); /* we must use a pointer because of zclient.c's functions (new, free). */ struct zclient *zclient; /* Debug types */ static struct { int type; int str_min_len; const char *str; } debug_type[] = { {BABEL_DEBUG_COMMON, 1, "common"}, {BABEL_DEBUG_KERNEL, 1, "kernel"}, {BABEL_DEBUG_FILTER, 1, "filter"}, {BABEL_DEBUG_TIMEOUT, 1, "timeout"}, {BABEL_DEBUG_IF, 1, "interface"}, {BABEL_DEBUG_ROUTE, 1, "route"}, {BABEL_DEBUG_ALL, 1, "all"}, {0, 0, NULL} }; /* Zebra route add and delete treatment. */ static int babel_zebra_read_route (ZAPI_CALLBACK_ARGS) { struct zapi_route api; if (zapi_route_decode(zclient->ibuf, &api) < 0) return -1; /* we completely ignore srcdest routes for now. */ if (CHECK_FLAG(api.message, ZAPI_MESSAGE_SRCPFX)) return 0; if (cmd == ZEBRA_REDISTRIBUTE_ROUTE_ADD) { babel_route_add(&api); } else { babel_route_delete(&api); } return 0; } /* [Babel Command] */ DEFUN (babel_redistribute_type, babel_redistribute_type_cmd, "[no] redistribute ", NO_STR "Redistribute\n" "Redistribute IPv4 routes\n" FRR_IP_REDIST_HELP_STR_BABELD "Redistribute IPv6 routes\n" FRR_IP6_REDIST_HELP_STR_BABELD) { int negate = 0; int family; int afi; int type; int idx = 0; if (argv_find(argv, argc, "no", &idx)) negate = 1; argv_find(argv, argc, "redistribute", &idx); family = str2family(argv[idx + 1]->text); if (family < 0) return CMD_WARNING_CONFIG_FAILED; afi = family2afi(family); if (!afi) return CMD_WARNING_CONFIG_FAILED; type = proto_redistnum(afi, argv[idx + 2]->text); if (type < 0) { vty_out (vty, "Invalid type %s\n", argv[idx + 2]->arg); return CMD_WARNING_CONFIG_FAILED; } if (!negate) zclient_redistribute (ZEBRA_REDISTRIBUTE_ADD, zclient, afi, type, 0, VRF_DEFAULT); else { zclient_redistribute (ZEBRA_REDISTRIBUTE_DELETE, zclient, afi, type, 0, VRF_DEFAULT); /* perhaps should we remove xroutes having the same type... */ } return CMD_SUCCESS; } #ifndef NO_DEBUG /* [Babel Command] */ DEFUN (debug_babel, debug_babel_cmd, "debug babel ", "Enable debug messages for specific or all part.\n" "Babel information\n" "Common messages (default)\n" "Kernel messages\n" "Filter messages\n" "Timeout messages\n" "Interface messages\n" "Route messages\n" "All messages\n") { int i; for(i = 0; debug_type[i].str != NULL; i++) { if (strncmp (debug_type[i].str, argv[2]->arg, debug_type[i].str_min_len) == 0) { debug |= debug_type[i].type; return CMD_SUCCESS; } } vty_out (vty, "Invalid type %s\n", argv[2]->arg); return CMD_WARNING_CONFIG_FAILED; } /* [Babel Command] */ DEFUN (no_debug_babel, no_debug_babel_cmd, "no debug babel ", NO_STR "Disable debug messages for specific or all part.\n" "Babel information\n" "Common messages (default)\n" "Kernel messages\n" "Filter messages\n" "Timeout messages\n" "Interface messages\n" "Route messages\n" "All messages\n") { int i; for (i = 0; debug_type[i].str; i++) { if (strncmp(debug_type[i].str, argv[3]->arg, debug_type[i].str_min_len) == 0) { debug &= ~debug_type[i].type; return CMD_SUCCESS; } } vty_out (vty, "Invalid type %s\n", argv[3]->arg); return CMD_WARNING_CONFIG_FAILED; } #endif /* NO_DEBUG */ /* Output "debug" statement lines, if necessary. */ int debug_babel_config_write (struct vty * vty) { #ifdef NO_DEBUG return 0; #else int i, lines = 0; if (debug == BABEL_DEBUG_ALL) { vty_out (vty, "debug babel all\n"); lines++; } else for (i = 0; debug_type[i].str != NULL; i++) if ( debug_type[i].type != BABEL_DEBUG_ALL && CHECK_FLAG (debug, debug_type[i].type) ) { vty_out (vty, "debug babel %s\n", debug_type[i].str); lines++; } if (lines) { vty_out (vty, "!\n"); lines++; } return lines; #endif /* NO_DEBUG */ } DEFUN_NOSH (show_debugging_babel, show_debugging_babel_cmd, "show debugging [babel]", SHOW_STR DEBUG_STR "Babel") { vty_out(vty, "BABEL debugging status\n"); debug_babel_config_write(vty); return CMD_SUCCESS; } static void babel_zebra_connected (struct zclient *zclient) { zclient_send_reg_requests (zclient, VRF_DEFAULT); } void babelz_zebra_init(void) { zclient = zclient_new(master, &zclient_options_default); zclient_init(zclient, ZEBRA_ROUTE_BABEL, 0, &babeld_privs); zclient->zebra_connected = babel_zebra_connected; zclient->interface_add = babel_interface_add; zclient->interface_delete = babel_interface_delete; zclient->interface_up = babel_interface_up; zclient->interface_down = babel_interface_down; zclient->interface_address_add = babel_interface_address_add; zclient->interface_address_delete = babel_interface_address_delete; zclient->redistribute_route_add = babel_zebra_read_route; zclient->redistribute_route_del = babel_zebra_read_route; install_element(BABEL_NODE, &babel_redistribute_type_cmd); install_element(ENABLE_NODE, &debug_babel_cmd); install_element(ENABLE_NODE, &no_debug_babel_cmd); install_element(CONFIG_NODE, &debug_babel_cmd); install_element(CONFIG_NODE, &no_debug_babel_cmd); install_element(VIEW_NODE, &show_debugging_babel_cmd); } void babel_zebra_close_connexion(void) { zclient_stop(zclient); zclient_free(zclient); } frr-7.2.1/babeld/babel_zebra.h0000644000000000000000000000244413610377563013032 00000000000000/* Copyright 2011 by Matthieu Boutier and Juliusz Chroboczek Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions: The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. */ #ifndef BABEL_ZEBRA_H #define BABEL_ZEBRA_H #include "vty.h" extern struct zclient *zclient; void babelz_zebra_init(void); void babel_zebra_close_connexion(void); extern int debug_babel_config_write (struct vty *); #endif frr-7.2.1/babeld/babeld.c0000644000000000000000000005511313610377563012007 00000000000000/* Copyright 2011 by Matthieu Boutier and Juliusz Chroboczek Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions: The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. */ #include #include "command.h" #include "prefix.h" #include "memory.h" #include "table.h" #include "distribute.h" #include "prefix.h" #include "filter.h" #include "plist.h" #include "lib_errors.h" #include "babel_main.h" #include "babeld.h" #include "util.h" #include "net.h" #include "kernel.h" #include "babel_interface.h" #include "neighbour.h" #include "route.h" #include "message.h" #include "resend.h" #include "babel_filter.h" #include "babel_zebra.h" #include "babel_errors.h" DEFINE_MGROUP(BABELD, "babeld") DEFINE_MTYPE_STATIC(BABELD, BABEL, "Babel Structure") static int babel_init_routing_process(struct thread *thread); static void babel_get_myid(void); static void babel_initial_noise(void); static int babel_read_protocol (struct thread *thread); static int babel_main_loop(struct thread *thread); static void babel_set_timer(struct timeval *timeout); static void babel_fill_with_next_timeout(struct timeval *tv); static void babel_distribute_update (struct distribute_ctx *ctx, struct distribute *dist); /* Informations relative to the babel running daemon. */ static struct babel *babel_routing_process = NULL; static unsigned char *receive_buffer = NULL; static int receive_buffer_size = 0; /* timeouts */ struct timeval check_neighbours_timeout; static time_t expiry_time; static time_t source_expiry_time; /* Babel node structure. */ static struct cmd_node cmd_babel_node = { .node = BABEL_NODE, .prompt = "%s(config-router)# ", .vtysh = 1, }; /* print current babel configuration on vty */ static int babel_config_write (struct vty *vty) { int lines = 0; int afi; int i; /* list enabled debug modes */ lines += debug_babel_config_write (vty); if (!babel_routing_process) return lines; vty_out (vty, "router babel\n"); if (diversity_kind != DIVERSITY_NONE) { vty_out (vty, " babel diversity\n"); lines++; } if (diversity_factor != BABEL_DEFAULT_DIVERSITY_FACTOR) { vty_out (vty, " babel diversity-factor %d\n",diversity_factor); lines++; } if (resend_delay != BABEL_DEFAULT_RESEND_DELAY) { vty_out (vty, " babel resend-delay %u\n", resend_delay); lines++; } if (smoothing_half_life != BABEL_DEFAULT_SMOOTHING_HALF_LIFE) { vty_out (vty, " babel smoothing-half-life %u\n", smoothing_half_life); lines++; } /* list enabled interfaces */ lines = 1 + babel_enable_if_config_write (vty); /* list redistributed protocols */ for (afi = AFI_IP; afi <= AFI_IP6; afi++) { for (i = 0; i < ZEBRA_ROUTE_MAX; i++) { if (i != zclient->redist_default && vrf_bitmap_check (zclient->redist[afi][i], VRF_DEFAULT)) { vty_out (vty, " redistribute %s %s\n", (afi == AFI_IP) ? "ipv4" : "ipv6", zebra_route_string(i)); lines++; } } } lines += config_write_distribute (vty, babel_routing_process->distribute_ctx); return lines; } static int babel_create_routing_process (void) { assert (babel_routing_process == NULL); /* Allocaste Babel instance. */ babel_routing_process = XCALLOC (MTYPE_BABEL, sizeof (struct babel)); /* Initialize timeouts */ gettime(&babel_now); expiry_time = babel_now.tv_sec + roughly(30); source_expiry_time = babel_now.tv_sec + roughly(300); /* Make socket for Babel protocol. */ protocol_socket = babel_socket(protocol_port); if (protocol_socket < 0) { flog_err_sys(EC_LIB_SOCKET, "Couldn't create link local socket: %s", safe_strerror(errno)); goto fail; } /* Threads. */ thread_add_read(master, &babel_read_protocol, NULL, protocol_socket, &babel_routing_process->t_read); /* wait a little: zebra will announce interfaces, addresses, routes... */ thread_add_timer_msec(master, babel_init_routing_process, NULL, 200L, &babel_routing_process->t_update); /* Distribute list install. */ babel_routing_process->distribute_ctx = distribute_list_ctx_create (vrf_lookup_by_id(VRF_DEFAULT)); distribute_list_add_hook (babel_routing_process->distribute_ctx, babel_distribute_update); distribute_list_delete_hook (babel_routing_process->distribute_ctx, babel_distribute_update); return 0; fail: XFREE(MTYPE_BABEL, babel_routing_process); babel_routing_process = NULL; return -1; } /* thread reading entries form others babel daemons */ static int babel_read_protocol (struct thread *thread) { int rc; struct vrf *vrf = vrf_lookup_by_id(VRF_DEFAULT); struct interface *ifp = NULL; struct sockaddr_in6 sin6; assert(babel_routing_process != NULL); assert(protocol_socket >= 0); rc = babel_recv(protocol_socket, receive_buffer, receive_buffer_size, (struct sockaddr*)&sin6, sizeof(sin6)); if(rc < 0) { if(errno != EAGAIN && errno != EINTR) { flog_err_sys(EC_LIB_SOCKET, "recv: %s", safe_strerror(errno)); } } else { FOR_ALL_INTERFACES(vrf, ifp) { if(!if_up(ifp)) continue; if(ifp->ifindex == (ifindex_t)sin6.sin6_scope_id) { parse_packet((unsigned char*)&sin6.sin6_addr, ifp, receive_buffer, rc); break; } } } /* re-add thread */ thread_add_read(master, &babel_read_protocol, NULL, protocol_socket, &babel_routing_process->t_read); return 0; } /* Zebra will give some information, especially about interfaces. This function must be call with a litte timeout wich may give zebra the time to do his job, making these inits have sense. */ static int babel_init_routing_process(struct thread *thread) { myseqno = (random() & 0xFFFF); babel_get_myid(); babel_load_state_file(); debugf(BABEL_DEBUG_COMMON, "My ID is : %s.", format_eui64(myid)); babel_initial_noise(); babel_main_loop(thread);/* this function self-add to the t_update thread */ return 0; } /* fill "myid" with an unique id (only if myid != {0}). */ static void babel_get_myid(void) { struct vrf *vrf = vrf_lookup_by_id(VRF_DEFAULT); struct interface *ifp = NULL; int rc; int i; /* if we already have an id (from state file), we return. */ if (memcmp(myid, zeroes, 8) != 0) { return; } FOR_ALL_INTERFACES(vrf, ifp) { /* ifp->ifindex is not necessarily valid at this point */ int ifindex = if_nametoindex(ifp->name); if(ifindex > 0) { unsigned char eui[8]; rc = if_eui64(ifindex, eui); if(rc < 0) continue; memcpy(myid, eui, 8); return; } } /* We failed to get a global EUI64 from the interfaces we were given. Let's try to find an interface with a MAC address. */ for(i = 1; i < 256; i++) { char buf[IF_NAMESIZE], *ifname; unsigned char eui[8]; ifname = if_indextoname(i, buf); if(ifname == NULL) continue; rc = if_eui64(i, eui); if(rc < 0) continue; memcpy(myid, eui, 8); return; } flog_err(EC_BABEL_CONFIG, "Warning: couldn't find router id -- using random value."); rc = read_random_bytes(myid, 8); if(rc < 0) { flog_err(EC_BABEL_CONFIG, "read(random): %s (cannot assign an ID)", safe_strerror(errno)); exit(1); } /* Clear group and global bits */ myid[0] &= ~3; } /* Make some noise so that others notice us, and send retractions in case we were restarted recently */ static void babel_initial_noise(void) { struct vrf *vrf = vrf_lookup_by_id(VRF_DEFAULT); struct interface *ifp = NULL; FOR_ALL_INTERFACES(vrf, ifp) { if(!if_up(ifp)) continue; /* Apply jitter before we send the first message. */ usleep(roughly(10000)); gettime(&babel_now); send_hello(ifp); send_wildcard_retraction(ifp); } FOR_ALL_INTERFACES(vrf, ifp) { if(!if_up(ifp)) continue; usleep(roughly(10000)); gettime(&babel_now); send_hello(ifp); send_wildcard_retraction(ifp); send_self_update(ifp); send_request(ifp, NULL, 0); flushupdates(ifp); flushbuf(ifp); } } /* Delete all the added babel routes, make babeld only speak to zebra. */ static void babel_clean_routing_process(void) { flush_all_routes(); babel_interface_close_all(); /* cancel threads */ if (babel_routing_process->t_read != NULL) { thread_cancel(babel_routing_process->t_read); } if (babel_routing_process->t_update != NULL) { thread_cancel(babel_routing_process->t_update); } distribute_list_delete(&babel_routing_process->distribute_ctx); XFREE(MTYPE_BABEL, babel_routing_process); babel_routing_process = NULL; } /* Function used with timeout. */ static int babel_main_loop(struct thread *thread) { struct timeval tv; struct vrf *vrf = vrf_lookup_by_id(VRF_DEFAULT); struct interface *ifp = NULL; while(1) { gettime(&babel_now); /* timeouts --------------------------------------------------------- */ /* get the next timeout */ babel_fill_with_next_timeout(&tv); /* if there is no timeout, we must wait. */ if(timeval_compare(&tv, &babel_now) > 0) { timeval_minus(&tv, &tv, &babel_now); debugf(BABEL_DEBUG_TIMEOUT, "babel main loop : timeout: %lld msecs", (long long)tv.tv_sec * 1000 + tv.tv_usec / 1000); /* it happens often to have less than 1 ms, it's bad. */ timeval_add_msec(&tv, &tv, 300); babel_set_timer(&tv); return 0; } gettime(&babel_now); /* update database -------------------------------------------------- */ if(timeval_compare(&check_neighbours_timeout, &babel_now) < 0) { int msecs; msecs = check_neighbours(); /* Multiply by 3/2 to allow neighbours to expire. */ msecs = MAX(3 * msecs / 2, 10); schedule_neighbours_check(msecs, 1); } if(babel_now.tv_sec >= expiry_time) { expire_routes(); expire_resend(); expiry_time = babel_now.tv_sec + roughly(30); } if(babel_now.tv_sec >= source_expiry_time) { expire_sources(); source_expiry_time = babel_now.tv_sec + roughly(300); } FOR_ALL_INTERFACES(vrf, ifp) { babel_interface_nfo *babel_ifp = NULL; if(!if_up(ifp)) continue; babel_ifp = babel_get_if_nfo(ifp); if(timeval_compare(&babel_now, &babel_ifp->hello_timeout) >= 0) send_hello(ifp); if(timeval_compare(&babel_now, &babel_ifp->update_timeout) >= 0) send_update(ifp, 0, NULL, 0); if(timeval_compare(&babel_now, &babel_ifp->update_flush_timeout) >= 0) flushupdates(ifp); } if(resend_time.tv_sec != 0) { if(timeval_compare(&babel_now, &resend_time) >= 0) do_resend(); } if(unicast_flush_timeout.tv_sec != 0) { if(timeval_compare(&babel_now, &unicast_flush_timeout) >= 0) flush_unicast(1); } FOR_ALL_INTERFACES(vrf, ifp) { babel_interface_nfo *babel_ifp = NULL; if(!if_up(ifp)) continue; babel_ifp = babel_get_if_nfo(ifp); if(babel_ifp->flush_timeout.tv_sec != 0) { if(timeval_compare(&babel_now, &babel_ifp->flush_timeout) >= 0) flushbuf(ifp); } } } assert(0); /* this line should never be reach */ } static void printIfMin(struct timeval *tv, int cmd, const char *tag, const char *ifname) { static struct timeval curr_tv; static char buffer[200]; static const char *curr_tag = NULL; switch (cmd) { case 0: /* reset timeval */ curr_tv = *tv; if(ifname != NULL) { snprintf(buffer, 200L, "interface: %s; %s", ifname, tag); curr_tag = buffer; } else { curr_tag = tag; } break; case 1: /* take the min */ if (tv->tv_sec == 0 && tv->tv_usec == 0) { /* if (tv == ∞) */ break; } if (tv->tv_sec < curr_tv.tv_sec ||(tv->tv_sec == curr_tv.tv_sec && tv->tv_usec < curr_tv.tv_usec)) { curr_tv = *tv; if(ifname != NULL) { snprintf(buffer, 200L, "interface: %s; %s", ifname, tag); curr_tag = buffer; } else { curr_tag = tag; } } break; case 2: /* print message */ debugf(BABEL_DEBUG_TIMEOUT, "next timeout due to: %s", curr_tag); break; default: break; } } static void babel_fill_with_next_timeout(struct timeval *tv) { #if (defined NO_DEBUG) #define printIfMin(a,b,c,d) #else #define printIfMin(a,b,c,d) \ if (UNLIKELY(debug & BABEL_DEBUG_TIMEOUT)) {printIfMin(a,b,c,d);} struct vrf *vrf = vrf_lookup_by_id(VRF_DEFAULT); struct interface *ifp = NULL; *tv = check_neighbours_timeout; printIfMin(tv, 0, "check_neighbours_timeout", NULL); timeval_min_sec(tv, expiry_time); printIfMin(tv, 1, "expiry_time", NULL); timeval_min_sec(tv, source_expiry_time); printIfMin(tv, 1, "source_expiry_time", NULL); timeval_min(tv, &resend_time); printIfMin(tv, 1, "resend_time", NULL); FOR_ALL_INTERFACES(vrf, ifp) { babel_interface_nfo *babel_ifp = NULL; if(!if_up(ifp)) continue; babel_ifp = babel_get_if_nfo(ifp); timeval_min(tv, &babel_ifp->flush_timeout); printIfMin(tv, 1, "flush_timeout", ifp->name); timeval_min(tv, &babel_ifp->hello_timeout); printIfMin(tv, 1, "hello_timeout", ifp->name); timeval_min(tv, &babel_ifp->update_timeout); printIfMin(tv, 1, "update_timeout", ifp->name); timeval_min(tv, &babel_ifp->update_flush_timeout); printIfMin(tv, 1, "update_flush_timeout",ifp->name); } timeval_min(tv, &unicast_flush_timeout); printIfMin(tv, 1, "unicast_flush_timeout", NULL); printIfMin(tv, 2, NULL, NULL); #undef printIfMin #endif } /* set the t_update thread of the babel routing process to be launch in 'timeout' (approximate at the milisecond) */ static void babel_set_timer(struct timeval *timeout) { long msecs = timeout->tv_sec * 1000 + timeout->tv_usec / 1000; if (babel_routing_process->t_update != NULL) { thread_cancel(babel_routing_process->t_update); } thread_add_timer_msec(master, babel_main_loop, NULL, msecs, &babel_routing_process->t_update); } void schedule_neighbours_check(int msecs, int override) { struct timeval timeout; timeval_add_msec(&timeout, &babel_now, msecs); if(override) check_neighbours_timeout = timeout; else timeval_min(&check_neighbours_timeout, &timeout); } int resize_receive_buffer(int size) { if(size <= receive_buffer_size) return 0; if(receive_buffer == NULL) { receive_buffer = malloc(size); if(receive_buffer == NULL) { flog_err(EC_BABEL_MEMORY, "malloc(receive_buffer): %s", safe_strerror(errno)); return -1; } receive_buffer_size = size; } else { unsigned char *new; new = realloc(receive_buffer, size); if(new == NULL) { flog_err(EC_BABEL_MEMORY, "realloc(receive_buffer): %s", safe_strerror(errno)); return -1; } receive_buffer = new; receive_buffer_size = size; } return 1; } static void babel_distribute_update (struct distribute_ctx *ctx, struct distribute *dist) { struct interface *ifp; babel_interface_nfo *babel_ifp; int type; int family; if (! dist->ifname) return; ifp = if_lookup_by_name (dist->ifname, VRF_DEFAULT); if (ifp == NULL) return; babel_ifp = babel_get_if_nfo(ifp); for (type = 0; type < DISTRIBUTE_MAX; type++) { family = type == DISTRIBUTE_V4_IN || type == DISTRIBUTE_V4_OUT ? AFI_IP : AFI_IP6; if (dist->list[type]) babel_ifp->list[type] = access_list_lookup (family, dist->list[type]); else babel_ifp->list[type] = NULL; if (dist->prefix[type]) babel_ifp->prefix[type] = prefix_list_lookup (family, dist->prefix[type]); else babel_ifp->prefix[type] = NULL; } } static void babel_distribute_update_interface (struct interface *ifp) { struct distribute *dist = NULL; if (babel_routing_process) dist = distribute_lookup(babel_routing_process->distribute_ctx, ifp->name); if (dist) babel_distribute_update (babel_routing_process->distribute_ctx, dist); } /* Update all interface's distribute list. */ static void babel_distribute_update_all (struct prefix_list *notused) { struct vrf *vrf = vrf_lookup_by_id(VRF_DEFAULT); struct interface *ifp; FOR_ALL_INTERFACES (vrf, ifp) babel_distribute_update_interface (ifp); } static void babel_distribute_update_all_wrapper (struct access_list *notused) { babel_distribute_update_all(NULL); } /* [Command] */ DEFUN_NOSH (router_babel, router_babel_cmd, "router babel", "Enable a routing process\n" "Make Babel instance command\n") { int ret; vty->node = BABEL_NODE; if (!babel_routing_process) { ret = babel_create_routing_process (); /* Notice to user we couldn't create Babel. */ if (ret < 0) { zlog_warn ("can't create Babel"); return CMD_WARNING; } } return CMD_SUCCESS; } /* [Command] */ DEFUN (no_router_babel, no_router_babel_cmd, "no router babel", NO_STR "Disable a routing process\n" "Remove Babel instance command\n") { if(babel_routing_process) babel_clean_routing_process(); return CMD_SUCCESS; } /* [Babel Command] */ DEFUN (babel_diversity, babel_diversity_cmd, "babel diversity", "Babel commands\n" "Enable diversity-aware routing.\n") { diversity_kind = DIVERSITY_CHANNEL; return CMD_SUCCESS; } /* [Babel Command] */ DEFUN (no_babel_diversity, no_babel_diversity_cmd, "no babel diversity", NO_STR "Babel commands\n" "Disable diversity-aware routing.\n") { diversity_kind = DIVERSITY_NONE; return CMD_SUCCESS; } /* [Babel Command] */ DEFUN (babel_diversity_factor, babel_diversity_factor_cmd, "babel diversity-factor (1-256)", "Babel commands\n" "Set the diversity factor.\n" "Factor in units of 1/256.\n") { int factor; factor = strtoul(argv[2]->arg, NULL, 10); diversity_factor = factor; return CMD_SUCCESS; } /* [Babel Command] */ DEFUN (babel_set_resend_delay, babel_set_resend_delay_cmd, "babel resend-delay (20-655340)", "Babel commands\n" "Time before resending a message\n" "Milliseconds\n") { int interval; interval = strtoul(argv[2]->arg, NULL, 10); resend_delay = interval; return CMD_SUCCESS; } /* [Babel Command] */ DEFUN (babel_set_smoothing_half_life, babel_set_smoothing_half_life_cmd, "babel smoothing-half-life (0-65534)", "Babel commands\n" "Smoothing half-life\n" "Seconds (0 to disable)\n") { int seconds; seconds = strtoul(argv[2]->arg, NULL, 10); change_smoothing_half_life(seconds); return CMD_SUCCESS; } void babeld_quagga_init(void) { install_node(&cmd_babel_node, &babel_config_write); install_element(CONFIG_NODE, &router_babel_cmd); install_element(CONFIG_NODE, &no_router_babel_cmd); install_default(BABEL_NODE); install_element(BABEL_NODE, &babel_diversity_cmd); install_element(BABEL_NODE, &no_babel_diversity_cmd); install_element(BABEL_NODE, &babel_diversity_factor_cmd); install_element(BABEL_NODE, &babel_set_resend_delay_cmd); install_element(BABEL_NODE, &babel_set_smoothing_half_life_cmd); babel_if_init(); /* Access list install. */ access_list_init (); access_list_add_hook (babel_distribute_update_all_wrapper); access_list_delete_hook (babel_distribute_update_all_wrapper); /* Prefix list initialize.*/ prefix_list_init (); prefix_list_add_hook (babel_distribute_update_all); prefix_list_delete_hook (babel_distribute_update_all); /* Distribute list install. */ distribute_list_init(BABEL_NODE); } /* Stubs to adapt Babel's filtering calls to Quagga's infrastructure. */ int input_filter(const unsigned char *id, const unsigned char *prefix, unsigned short plen, const unsigned char *neigh, unsigned int ifindex) { return babel_filter(0, prefix, plen, ifindex); } int output_filter(const unsigned char *id, const unsigned char *prefix, unsigned short plen, unsigned int ifindex) { return babel_filter(1, prefix, plen, ifindex); } /* There's no redistribute filter in Quagga -- the zebra daemon does its own filtering. */ int redistribute_filter(const unsigned char *prefix, unsigned short plen, unsigned int ifindex, int proto) { return 0; } struct babel *babel_lookup(void) { return babel_routing_process; } frr-7.2.1/babeld/babeld.conf.sample0000644000000000000000000000123113610377563013762 00000000000000debug babel common !debug babel kernel !debug babel filter !debug babel timeout !debug babel interface !debug babel route !debug babel all router babel ! network wlan0 ! network eth0 ! redistribute ipv4 kernel ! no redistribute ipv6 static ! The defaults are fine for a wireless interface !interface wlan0 ! A few optimisation tweaks are optional but recommended on a wired interface ! Disable link quality estimation, enable split horizon processing, and ! increase the hello and update intervals. !interface eth0 ! babel wired ! babel split-horizon ! babel hello-interval 12000 ! babel update-interval 36000 ! log file /var/log/quagga/babeld.log log stdout frr-7.2.1/babeld/babeld.h0000644000000000000000000000746513610377563012023 00000000000000/* Copyright (c) 2007, 2008 by Juliusz Chroboczek Copyright 2011 by Matthieu Boutier and Juliusz Chroboczek Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions: The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. */ #ifndef BABEL_BABELD_H #define BABEL_BABELD_H #include #include "vty.h" #define INFINITY ((unsigned short)(~0)) #ifndef RTPROT_BABEL #define RTPROT_BABEL 42 #endif #define RTPROT_BABEL_LOCAL -2 #undef MAX #undef MIN #define MAX(x,y) ((x)<=(y)?(y):(x)) #define MIN(x,y) ((x)<=(y)?(x):(y)) #if defined(__STDC_VERSION__) && __STDC_VERSION__ >= 199901L /* nothing */ #elif defined(__GNUC__) #define inline __inline #if (__GNUC__ >= 3) #define restrict __restrict #else #define restrict /**/ #endif #else #define inline /**/ #define restrict /**/ #endif #if defined(__GNUC__) && (__GNUC__ >= 3) #define ATTRIBUTE(x) __attribute__ (x) #define LIKELY(_x) __builtin_expect(!!(_x), 1) #define UNLIKELY(_x) __builtin_expect(!!(_x), 0) #else #define ATTRIBUTE(x) /**/ #define LIKELY(_x) !!(_x) #define UNLIKELY(_x) !!(_x) #endif #if defined(__GNUC__) && (__GNUC__ >= 4) && (__GNUC_MINOR__ >= 3) #define COLD __attribute__ ((cold)) #else #define COLD /**/ #endif #ifndef IF_NAMESIZE #include #include #endif #ifdef HAVE_VALGRIND #include #else #ifndef VALGRIND_MAKE_MEM_UNDEFINED #define VALGRIND_MAKE_MEM_UNDEFINED(a, b) do {} while(0) #endif #ifndef VALGRIND_CHECK_MEM_IS_DEFINED #define VALGRIND_CHECK_MEM_IS_DEFINED(a, b) do {} while(0) #endif #endif #define BABEL_VTY_PORT 2609 #define BABEL_DEFAULT_CONFIG "babeld.conf" /* Values in milliseconds */ #define BABEL_DEFAULT_HELLO_INTERVAL 4000 #define BABEL_DEFAULT_UPDATE_INTERVAL 16000 #define BABEL_DEFAULT_RESEND_DELAY 2000 /* In units of seconds */ #define BABEL_DEFAULT_SMOOTHING_HALF_LIFE 4 /* In units of 1/256. */ #define BABEL_DEFAULT_DIVERSITY_FACTOR 256 #define BABEL_DEFAULT_RXCOST_WIRED 96 #define BABEL_DEFAULT_RXCOST_WIRELESS 256 /* Babel structure. */ struct babel { /* Babel threads. */ struct thread *t_read; /* on Babel protocol's socket */ struct thread *t_update; /* timers */ /* distribute_ctx */ struct distribute_ctx *distribute_ctx; }; extern struct zebra_privs_t babeld_privs; extern void babeld_quagga_init(void); extern int input_filter(const unsigned char *id, const unsigned char *prefix, unsigned short plen, const unsigned char *neigh, unsigned int ifindex); extern int output_filter(const unsigned char *id, const unsigned char *prefix, unsigned short plen, unsigned int ifindex); extern int redistribute_filter(const unsigned char *prefix, unsigned short plen, unsigned int ifindex, int proto); extern int resize_receive_buffer(int size); extern void schedule_neighbours_check(int msecs, int override); extern struct babel *babel_lookup(void); #endif /* BABEL_BABELD_H */ frr-7.2.1/babeld/kernel.c0000644000000000000000000001643613610377563012063 00000000000000/* Copyright 2007, 2008 by Grégoire Henry, Julien Cristau and Juliusz Chroboczek Copyright 2011, 2012 by Matthieu Boutier and Juliusz Chroboczek Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions: The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. */ #ifdef HAVE_CONFIG_H #include "config.h" #endif #include #include #include #include "babeld.h" #include #include #include #include #include #include #include "prefix.h" #include "zclient.h" #include "kernel.h" #include "privs.h" #include "command.h" #include "vty.h" #include "memory.h" #include "thread.h" #include "nexthop.h" #include "util.h" #include "babel_interface.h" #include "babel_zebra.h" static int zebra_route(int add, int familt, const unsigned char *pref, unsigned short plen, const unsigned char *gate, int ifindex, unsigned int metric); int kernel_interface_operational(struct interface *interface) { return if_is_operative(interface); } int kernel_interface_mtu(struct interface *interface) { return MIN(interface->mtu, interface->mtu6); } int kernel_interface_wireless(struct interface *interface) { return 0; } int kernel_route(enum babel_kernel_routes operation, const unsigned char *pref, unsigned short plen, const unsigned char *gate, int ifindex, unsigned int metric, const unsigned char *newgate, int newifindex, unsigned int newmetric) { int rc; int family; /* Check that the protocol family is consistent. */ if(plen >= 96 && v4mapped(pref)) { if(!v4mapped(gate)) { errno = EINVAL; return -1; } family = AF_INET; } else { if(v4mapped(gate)) { errno = EINVAL; return -1; } family = AF_INET6; } switch (operation) { case ROUTE_ADD: return zebra_route(1, family, pref, plen, gate, ifindex, metric); break; case ROUTE_FLUSH: return zebra_route(0, family, pref, plen, gate, ifindex, metric); break; case ROUTE_MODIFY: if(newmetric == metric && memcmp(newgate, gate, 16) == 0 && newifindex == ifindex) return 0; debugf(BABEL_DEBUG_ROUTE, "Modify route: delete old; add new."); rc = zebra_route(0, family, pref, plen, gate, ifindex, metric); if (rc < 0) return -1; rc = zebra_route(1, family, pref, plen, newgate, newifindex, newmetric); return rc; break; } return 0; } static int zebra_route(int add, int family, const unsigned char *pref, unsigned short plen, const unsigned char *gate, int ifindex, unsigned int metric) { struct zapi_route api; /* quagga's communication system */ struct prefix quagga_prefix; /* quagga's prefix */ union g_addr babel_prefix_addr; /* babeld's prefix addr */ struct zapi_nexthop *api_nh; /* next router to go - no ECMP */ api_nh = &api.nexthops[0]; /* convert to be understandable by quagga */ /* convert given addresses */ switch (family) { case AF_INET: uchar_to_inaddr(&babel_prefix_addr.ipv4, pref); break; case AF_INET6: uchar_to_in6addr(&babel_prefix_addr.ipv6, pref); break; } /* make prefix structure */ memset (&quagga_prefix, 0, sizeof(quagga_prefix)); quagga_prefix.family = family; switch (family) { case AF_INET: IPV4_ADDR_COPY (&quagga_prefix.u.prefix4, &babel_prefix_addr.ipv4); /* our plen is for v4mapped's addr */ quagga_prefix.prefixlen = plen - 96; break; case AF_INET6: IPV6_ADDR_COPY (&quagga_prefix.u.prefix6, &babel_prefix_addr.ipv6); quagga_prefix.prefixlen = plen; break; } apply_mask(&quagga_prefix); memset(&api, 0, sizeof(api)); api.type = ZEBRA_ROUTE_BABEL; api.safi = SAFI_UNICAST; api.vrf_id = VRF_DEFAULT; api.prefix = quagga_prefix; if(metric >= KERNEL_INFINITY) { zapi_route_set_blackhole(&api, BLACKHOLE_REJECT); } else { SET_FLAG(api.message, ZAPI_MESSAGE_NEXTHOP); api.nexthop_num = 1; api_nh->ifindex = ifindex; api_nh->vrf_id = VRF_DEFAULT; switch (family) { case AF_INET: uchar_to_inaddr(&api_nh->gate.ipv4, gate); if (IPV4_ADDR_SAME (&api_nh->gate.ipv4, &quagga_prefix.u.prefix4) && quagga_prefix.prefixlen == 32) { api_nh->type = NEXTHOP_TYPE_IFINDEX; } else { api_nh->type = NEXTHOP_TYPE_IPV4_IFINDEX; } break; case AF_INET6: uchar_to_in6addr(&api_nh->gate.ipv6, gate); /* difference to IPv4: always leave the linklocal as nexthop */ api_nh->type = NEXTHOP_TYPE_IPV6_IFINDEX; break; } SET_FLAG(api.message, ZAPI_MESSAGE_METRIC); api.metric = metric; } debugf(BABEL_DEBUG_ROUTE, "%s route (%s) to zebra", add ? "adding" : "removing", (family == AF_INET) ? "ipv4" : "ipv6"); return zclient_route_send (add ? ZEBRA_ROUTE_ADD : ZEBRA_ROUTE_DELETE, zclient, &api); } int if_eui64(int ifindex, unsigned char *eui) { struct interface *ifp = if_lookup_by_index(ifindex, VRF_DEFAULT); if (ifp == NULL) { return -1; } uint8_t len = (uint8_t)ifp->hw_addr_len; char *tmp = (void*) ifp->hw_addr; if (len == 8) { memcpy(eui, tmp, 8); eui[0] ^= 2; } else if (len == 6) { memcpy(eui, tmp, 3); eui[3] = 0xFF; eui[4] = 0xFE; memcpy(eui+5, tmp+3, 3); } else { return -1; } return 0; } /* Like gettimeofday, but returns monotonic time. If POSIX clocks are not available, falls back to gettimeofday but enforces monotonicity. */ int gettime(struct timeval *tv) { return monotime(tv); } /* If /dev/urandom doesn't exist, this will fail with ENOENT, which the caller will deal with gracefully. */ int read_random_bytes(void *buf, size_t len) { int fd; int rc; fd = open("/dev/urandom", O_RDONLY); if(fd < 0) { rc = -1; } else { rc = read(fd, buf, len); if(rc < 0 || (unsigned) rc < len) rc = -1; close(fd); } return rc; } frr-7.2.1/babeld/kernel.h0000644000000000000000000000352713610377563012065 00000000000000/* Copyright (c) 2007, 2008 by Juliusz Chroboczek Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions: The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. */ #ifndef BABEL_KERNEL_H #define BABEL_KERNEL_H #include #include "babel_main.h" #include "if.h" #define KERNEL_INFINITY 0xFFFF enum babel_kernel_routes { ROUTE_FLUSH, ROUTE_ADD, ROUTE_MODIFY, }; int kernel_interface_operational(struct interface *interface); int kernel_interface_mtu(struct interface *interface); int kernel_interface_wireless(struct interface *interface); int kernel_route(enum babel_kernel_routes operation, const unsigned char *dest, unsigned short plen, const unsigned char *gate, int ifindex, unsigned int metric, const unsigned char *newgate, int newifindex, unsigned int newmetric); int if_eui64(int ifindex, unsigned char *eui); int gettime(struct timeval *tv); int read_random_bytes(void *buf, size_t len); #endif /* BABEL_KERNEL_H */ frr-7.2.1/babeld/message.c0000644000000000000000000016671113610377563012231 00000000000000/* Copyright (c) 2007, 2008 by Juliusz Chroboczek Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions: The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. */ #include #include "if.h" #include "babeld.h" #include "util.h" #include "net.h" #include "babel_interface.h" #include "source.h" #include "neighbour.h" #include "route.h" #include "xroute.h" #include "resend.h" #include "message.h" #include "kernel.h" #include "babel_main.h" #include "babel_errors.h" static unsigned char packet_header[4] = {42, 2}; int split_horizon = 1; unsigned short myseqno = 0; #define UNICAST_BUFSIZE 1024 static int unicast_buffered = 0; static unsigned char *unicast_buffer = NULL; struct neighbour *unicast_neighbour = NULL; struct timeval unicast_flush_timeout = {0, 0}; /* Minimum TLV _body_ length for TLVs of particular types (0 = no limit). */ static const unsigned char tlv_min_length[MESSAGE_MAX + 1] = { [ MESSAGE_PAD1 ] = 0, [ MESSAGE_PADN ] = 0, [ MESSAGE_ACK_REQ ] = 6, [ MESSAGE_ACK ] = 2, [ MESSAGE_HELLO ] = 6, [ MESSAGE_IHU ] = 6, [ MESSAGE_ROUTER_ID ] = 10, [ MESSAGE_NH ] = 2, [ MESSAGE_UPDATE ] = 10, [ MESSAGE_REQUEST ] = 2, [ MESSAGE_MH_REQUEST ] = 14, }; /* Parse a network prefix, encoded in the somewhat baroque compressed representation used by Babel. Return the number of bytes parsed. */ static int network_prefix(int ae, int plen, unsigned int omitted, const unsigned char *p, const unsigned char *dp, unsigned int len, unsigned char *p_r) { unsigned pb; unsigned char prefix[16]; int ret = -1; if(plen >= 0) pb = (plen + 7) / 8; else if(ae == 1) pb = 4; else pb = 16; if(pb > 16) return -1; memset(prefix, 0, 16); switch(ae) { case 0: ret = 0; break; case 1: if(omitted > 4 || pb > 4 || (pb > omitted && len < pb - omitted)) return -1; memcpy(prefix, v4prefix, 12); if(omitted) { if (dp == NULL || !v4mapped(dp)) return -1; memcpy(prefix, dp, 12 + omitted); } if(pb > omitted) memcpy(prefix + 12 + omitted, p, pb - omitted); ret = pb - omitted; break; case 2: if(omitted > 16 || (pb > omitted && len < pb - omitted)) return -1; if(omitted) { if (dp == NULL || v4mapped(dp)) return -1; memcpy(prefix, dp, omitted); } if(pb > omitted) memcpy(prefix + omitted, p, pb - omitted); ret = pb - omitted; break; case 3: if(pb > 8 && len < pb - 8) return -1; prefix[0] = 0xfe; prefix[1] = 0x80; if(pb > 8) memcpy(prefix + 8, p, pb - 8); ret = pb - 8; break; default: return -1; } mask_prefix(p_r, prefix, plen < 0 ? 128 : ae == 1 ? plen + 96 : plen); return ret; } static void parse_update_subtlv(const unsigned char *a, int alen, unsigned char *channels) { int type, len, i = 0; while(i < alen) { type = a[i]; if(type == SUBTLV_PAD1) { i++; continue; } if(i + 1 > alen) { flog_err(EC_BABEL_PACKET, "Received truncated attributes."); return; } len = a[i + 1]; if(i + len > alen) { flog_err(EC_BABEL_PACKET, "Received truncated attributes."); return; } if(type == SUBTLV_PADN) { /* Nothing. */ } else if(type == SUBTLV_DIVERSITY) { if(len > DIVERSITY_HOPS) { flog_err(EC_BABEL_PACKET, "Received overlong channel information (%d > %d).n", len, DIVERSITY_HOPS); len = DIVERSITY_HOPS; } if(memchr(a + i + 2, 0, len) != NULL) { /* 0 is reserved. */ flog_err(EC_BABEL_PACKET, "Channel information contains 0!"); return; } memset(channels, 0, DIVERSITY_HOPS); memcpy(channels, a + i + 2, len); } else { debugf(BABEL_DEBUG_COMMON, "Received unknown route attribute %d.", type); } i += len + 2; } } static int parse_hello_subtlv(const unsigned char *a, int alen, unsigned int *hello_send_us) { int type, len, i = 0, ret = 0; while(i < alen) { type = a[0]; if(type == SUBTLV_PAD1) { i++; continue; } if(i + 1 > alen) { flog_err(EC_BABEL_PACKET, "Received truncated sub-TLV on Hello message."); return -1; } len = a[i + 1]; if(i + len > alen) { flog_err(EC_BABEL_PACKET, "Received truncated sub-TLV on Hello message."); return -1; } if(type == SUBTLV_PADN) { /* Nothing to do. */ } else if(type == SUBTLV_TIMESTAMP) { if(len >= 4) { DO_NTOHL(*hello_send_us, a + i + 2); ret = 1; } else { flog_err(EC_BABEL_PACKET, "Received incorrect RTT sub-TLV on Hello message."); } } else { debugf(BABEL_DEBUG_COMMON, "Received unknown Hello sub-TLV type %d.", type); } i += len + 2; } return ret; } static int parse_ihu_subtlv(const unsigned char *a, int alen, unsigned int *hello_send_us, unsigned int *hello_rtt_receive_time) { int type, len, i = 0, ret = 0; while(i < alen) { type = a[0]; if(type == SUBTLV_PAD1) { i++; continue; } if(i + 1 > alen) { flog_err(EC_BABEL_PACKET, "Received truncated sub-TLV on IHU message."); return -1; } len = a[i + 1]; if(i + len > alen) { flog_err(EC_BABEL_PACKET, "Received truncated sub-TLV on IHU message."); return -1; } if(type == SUBTLV_PADN) { /* Nothing to do. */ } else if(type == SUBTLV_TIMESTAMP) { if(len >= 8) { DO_NTOHL(*hello_send_us, a + i + 2); DO_NTOHL(*hello_rtt_receive_time, a + i + 6); ret = 1; } else { flog_err(EC_BABEL_PACKET, "Received incorrect RTT sub-TLV on IHU message."); } } else { debugf(BABEL_DEBUG_COMMON, "Received unknown IHU sub-TLV type %d.", type); } i += len + 2; } return ret; } static int network_address(int ae, const unsigned char *a, unsigned int len, unsigned char *a_r) { return network_prefix(ae, -1, 0, a, NULL, len, a_r); } static int channels_len(unsigned char *channels) { unsigned char *p = memchr(channels, 0, DIVERSITY_HOPS); return p ? (p - channels) : DIVERSITY_HOPS; } /* Check, that the provided frame consists of a valid Babel packet header followed by a sequence of TLVs. TLVs of known types are also checked to meet minimum length constraints defined for each. Return 0 for no errors. */ static int babel_packet_examin(const unsigned char *packet, int packetlen) { unsigned i = 0, bodylen; const unsigned char *message; unsigned char type, len; if(packetlen < 4 || packet[0] != 42 || packet[1] != 2) return 1; DO_NTOHS(bodylen, packet + 2); while (i < bodylen){ message = packet + 4 + i; type = message[0]; if(type == MESSAGE_PAD1) { i++; continue; } if(i + 1 > bodylen) { debugf(BABEL_DEBUG_COMMON,"Received truncated message."); return 1; } len = message[1]; if(i + len > bodylen) { debugf(BABEL_DEBUG_COMMON,"Received truncated message."); return 1; } /* not Pad1 */ if(type <= MESSAGE_MAX && tlv_min_length[type] && len < tlv_min_length[type]) { debugf(BABEL_DEBUG_COMMON,"Undersized %u TLV", type); return 1; } i += len + 2; } return 0; } void parse_packet(const unsigned char *from, struct interface *ifp, const unsigned char *packet, int packetlen) { int i; const unsigned char *message; unsigned char type, len; int bodylen; struct neighbour *neigh; int have_router_id = 0, have_v4_prefix = 0, have_v6_prefix = 0, have_v4_nh = 0, have_v6_nh = 0; unsigned char router_id[8], v4_prefix[16], v6_prefix[16], v4_nh[16], v6_nh[16]; int have_hello_rtt = 0; /* Content of the RTT sub-TLV on IHU messages. */ unsigned int hello_send_us = 0, hello_rtt_receive_time = 0; babel_interface_nfo *babel_ifp = babel_get_if_nfo(ifp); if(babel_ifp->flags & BABEL_IF_TIMESTAMPS) { /* We want to track exactly when we received this packet. */ gettime(&babel_now); } if(!linklocal(from)) { flog_err(EC_BABEL_PACKET, "Received packet from non-local address %s.", format_address(from)); return; } if (babel_packet_examin (packet, packetlen)) { flog_err(EC_BABEL_PACKET, "Received malformed packet on %s from %s.", ifp->name, format_address(from)); return; } neigh = find_neighbour(from, ifp); if(neigh == NULL) { flog_err(EC_BABEL_PACKET, "Couldn't allocate neighbour."); return; } DO_NTOHS(bodylen, packet + 2); if(bodylen + 4 > packetlen) { flog_err(EC_BABEL_PACKET, "Received truncated packet (%d + 4 > %d).", bodylen, packetlen); bodylen = packetlen - 4; } i = 0; while(i < bodylen) { message = packet + 4 + i; type = message[0]; if(type == MESSAGE_PAD1) { debugf(BABEL_DEBUG_COMMON,"Received pad1 from %s on %s.", format_address(from), ifp->name); i++; continue; } len = message[1]; if(type == MESSAGE_PADN) { debugf(BABEL_DEBUG_COMMON,"Received pad%d from %s on %s.", len, format_address(from), ifp->name); } else if(type == MESSAGE_ACK_REQ) { unsigned short nonce, interval; DO_NTOHS(nonce, message + 4); DO_NTOHS(interval, message + 6); debugf(BABEL_DEBUG_COMMON,"Received ack-req (%04X %d) from %s on %s.", nonce, interval, format_address(from), ifp->name); send_ack(neigh, nonce, interval); } else if(type == MESSAGE_ACK) { debugf(BABEL_DEBUG_COMMON,"Received ack from %s on %s.", format_address(from), ifp->name); /* Nothing right now */ } else if(type == MESSAGE_HELLO) { unsigned short seqno, interval; int changed; unsigned int timestamp = 0; DO_NTOHS(seqno, message + 4); DO_NTOHS(interval, message + 6); debugf(BABEL_DEBUG_COMMON,"Received hello %d (%d) from %s on %s.", seqno, interval, format_address(from), ifp->name); changed = update_neighbour(neigh, seqno, interval); update_neighbour_metric(neigh, changed); if(interval > 0) /* Multiply by 3/2 to allow hellos to expire. */ schedule_neighbours_check(interval * 15, 0); /* Sub-TLV handling. */ if(len > 8) { if(parse_hello_subtlv(message + 8, len - 6, ×tamp) > 0) { neigh->hello_send_us = timestamp; neigh->hello_rtt_receive_time = babel_now; have_hello_rtt = 1; } } } else if(type == MESSAGE_IHU) { unsigned short txcost, interval; unsigned char address[16]; int rc; DO_NTOHS(txcost, message + 4); DO_NTOHS(interval, message + 6); rc = network_address(message[2], message + 8, len - 6, address); if(rc < 0) goto fail; debugf(BABEL_DEBUG_COMMON,"Received ihu %d (%d) from %s on %s for %s.", txcost, interval, format_address(from), ifp->name, format_address(address)); if(message[2] == 0 || is_interface_ll_address(ifp, address)) { int changed = txcost != neigh->txcost; neigh->txcost = txcost; neigh->ihu_time = babel_now; neigh->ihu_interval = interval; update_neighbour_metric(neigh, changed); if(interval > 0) /* Multiply by 3/2 to allow neighbours to expire. */ schedule_neighbours_check(interval * 45, 0); /* RTT sub-TLV. */ if(len > 10 + rc) parse_ihu_subtlv(message + 8 + rc, len - 6 - rc, &hello_send_us, &hello_rtt_receive_time); } } else if(type == MESSAGE_ROUTER_ID) { memcpy(router_id, message + 4, 8); have_router_id = 1; debugf(BABEL_DEBUG_COMMON,"Received router-id %s from %s on %s.", format_eui64(router_id), format_address(from), ifp->name); } else if(type == MESSAGE_NH) { unsigned char nh[16]; int rc; rc = network_address(message[2], message + 4, len - 2, nh); if(rc < 0) { have_v4_nh = 0; have_v6_nh = 0; goto fail; } debugf(BABEL_DEBUG_COMMON,"Received nh %s (%d) from %s on %s.", format_address(nh), message[2], format_address(from), ifp->name); if(message[2] == 1) { memcpy(v4_nh, nh, 16); have_v4_nh = 1; } else { memcpy(v6_nh, nh, 16); have_v6_nh = 1; } } else if(type == MESSAGE_UPDATE) { unsigned char prefix[16], *nh; unsigned char plen; unsigned char channels[DIVERSITY_HOPS]; unsigned short interval, seqno, metric; int rc, parsed_len; DO_NTOHS(interval, message + 6); DO_NTOHS(seqno, message + 8); DO_NTOHS(metric, message + 10); if(message[5] == 0 || (message[2] == 1 ? have_v4_prefix : have_v6_prefix)) rc = network_prefix(message[2], message[4], message[5], message + 12, message[2] == 1 ? v4_prefix : v6_prefix, len - 10, prefix); else rc = -1; if(rc < 0) { if(message[3] & 0x80) have_v4_prefix = have_v6_prefix = 0; goto fail; } parsed_len = 10 + rc; plen = message[4] + (message[2] == 1 ? 96 : 0); if(message[3] & 0x80) { if(message[2] == 1) { memcpy(v4_prefix, prefix, 16); have_v4_prefix = 1; } else { memcpy(v6_prefix, prefix, 16); have_v6_prefix = 1; } } if(message[3] & 0x40) { if(message[2] == 1) { memset(router_id, 0, 4); memcpy(router_id + 4, prefix + 12, 4); } else { memcpy(router_id, prefix + 8, 8); } have_router_id = 1; } if(!have_router_id && message[2] != 0) { flog_err(EC_BABEL_PACKET, "Received prefix with no router id."); goto fail; } debugf(BABEL_DEBUG_COMMON,"Received update%s%s for %s from %s on %s.", (message[3] & 0x80) ? "/prefix" : "", (message[3] & 0x40) ? "/id" : "", format_prefix(prefix, plen), format_address(from), ifp->name); if(message[2] == 0) { if(metric < 0xFFFF) { flog_err(EC_BABEL_PACKET, "Received wildcard update with finite metric."); goto done; } retract_neighbour_routes(neigh); goto done; } else if(message[2] == 1) { if(!have_v4_nh) goto fail; nh = v4_nh; } else if(have_v6_nh) { nh = v6_nh; } else { nh = neigh->address; } if(message[2] == 1) { if(!babel_get_if_nfo(ifp)->ipv4) goto done; } if((babel_get_if_nfo(ifp)->flags & BABEL_IF_FARAWAY)) { channels[0] = 0; } else { /* This will be overwritten by parse_update_subtlv below. */ if(metric < 256) { /* Assume non-interfering (wired) link. */ channels[0] = 0; } else { /* Assume interfering. */ channels[0] = BABEL_IF_CHANNEL_INTERFERING; channels[1] = 0; } if(parsed_len < len) parse_update_subtlv(message + 2 + parsed_len, len - parsed_len, channels); } update_route(router_id, prefix, plen, seqno, metric, interval, neigh, nh, channels, channels_len(channels)); } else if(type == MESSAGE_REQUEST) { unsigned char prefix[16], plen; int rc; rc = network_prefix(message[2], message[3], 0, message + 4, NULL, len - 2, prefix); if(rc < 0) goto fail; plen = message[3] + (message[2] == 1 ? 96 : 0); debugf(BABEL_DEBUG_COMMON,"Received request for %s from %s on %s.", message[2] == 0 ? "any" : format_prefix(prefix, plen), format_address(from), ifp->name); if(message[2] == 0) { struct babel_interface *neigh_ifp =babel_get_if_nfo(neigh->ifp); /* If a neighbour is requesting a full route dump from us, we might as well send it an IHU. */ send_ihu(neigh, NULL); /* Since nodes send wildcard requests on boot, booting a large number of nodes at the same time may cause an update storm. Ignore a wildcard request that happens shortly after we sent a full update. */ if(neigh_ifp->last_update_time < (time_t)(babel_now.tv_sec - MAX(neigh_ifp->hello_interval / 100, 1))) send_update(neigh->ifp, 0, NULL, 0); } else { send_update(neigh->ifp, 0, prefix, plen); } } else if(type == MESSAGE_MH_REQUEST) { unsigned char prefix[16], plen; unsigned short seqno; int rc; DO_NTOHS(seqno, message + 4); rc = network_prefix(message[2], message[3], 0, message + 16, NULL, len - 14, prefix); if(rc < 0) goto fail; plen = message[3] + (message[2] == 1 ? 96 : 0); debugf(BABEL_DEBUG_COMMON,"Received request (%d) for %s from %s on %s (%s, %d).", message[6], format_prefix(prefix, plen), format_address(from), ifp->name, format_eui64(message + 8), seqno); handle_request(neigh, prefix, plen, message[6], seqno, message + 8); } else { debugf(BABEL_DEBUG_COMMON,"Received unknown packet type %d from %s on %s.", type, format_address(from), ifp->name); } done: i += len + 2; continue; fail: flog_err(EC_BABEL_PACKET, "Couldn't parse packet (%d, %d) from %s on %s.", message[0], message[1], format_address(from), ifp->name); goto done; } /* We can calculate the RTT to this neighbour. */ if(have_hello_rtt && hello_send_us && hello_rtt_receive_time) { int remote_waiting_us, local_waiting_us; unsigned int rtt, smoothed_rtt; unsigned int old_rttcost; int changed = 0; remote_waiting_us = neigh->hello_send_us - hello_rtt_receive_time; local_waiting_us = time_us(neigh->hello_rtt_receive_time) - hello_send_us; /* Sanity checks (validity window of 10 minutes). */ if(remote_waiting_us < 0 || local_waiting_us < 0 || remote_waiting_us > 600000000 || local_waiting_us > 600000000) return; rtt = MAX(0, local_waiting_us - remote_waiting_us); debugf(BABEL_DEBUG_COMMON, "RTT to %s on %s sample result: %d us.\n", format_address(from), ifp->name, rtt); old_rttcost = neighbour_rttcost(neigh); if (valid_rtt(neigh)) { /* Running exponential average. */ smoothed_rtt = (babel_ifp->rtt_decay * rtt + (256 - babel_ifp->rtt_decay) * neigh->rtt); /* Rounding (up or down) to get closer to the sample. */ neigh->rtt = (neigh->rtt >= rtt) ? smoothed_rtt / 256 : (smoothed_rtt + 255) / 256; } else { /* We prefer to be conservative with new neighbours (higher RTT) */ assert(rtt <= 0x7FFFFFFF); neigh->rtt = 2*rtt; } changed = (neighbour_rttcost(neigh) == old_rttcost ? 0 : 1); update_neighbour_metric(neigh, changed); neigh->rtt_time = babel_now; } return; } /* Under normal circumstances, there are enough moderation mechanisms elsewhere in the protocol to make sure that this last-ditch check should never trigger. But I'm superstitious. */ static int check_bucket(struct interface *ifp) { babel_interface_nfo *babel_ifp = babel_get_if_nfo(ifp); if(babel_ifp->bucket == 0) { int seconds = babel_now.tv_sec - babel_ifp->bucket_time; if(seconds > 0) { babel_ifp->bucket = MIN(BUCKET_TOKENS_MAX, seconds * BUCKET_TOKENS_PER_SEC); } /* Reset bucket time unconditionally, in case clock is stepped. */ babel_ifp->bucket_time = babel_now.tv_sec; } if(babel_ifp->bucket > 0) { babel_ifp->bucket--; return 1; } else { return 0; } } static int fill_rtt_message(struct interface *ifp) { babel_interface_nfo *babel_ifp = babel_get_if_nfo(ifp); if((babel_ifp->flags & BABEL_IF_TIMESTAMPS) && (babel_ifp->buffered_hello >= 0)) { if(babel_ifp->sendbuf[babel_ifp->buffered_hello + 8] == SUBTLV_PADN && babel_ifp->sendbuf[babel_ifp->buffered_hello + 9] == 4) { unsigned int time; /* Change the type of sub-TLV. */ babel_ifp->sendbuf[babel_ifp->buffered_hello + 8] = SUBTLV_TIMESTAMP; gettime(&babel_now); time = time_us(babel_now); DO_HTONL(babel_ifp->sendbuf + babel_ifp->buffered_hello + 10, time); return 1; } else { flog_err(EC_BABEL_PACKET, "No space left for timestamp sub-TLV " "(this shouldn't happen)"); return -1; } } return 0; } void flushbuf(struct interface *ifp) { int rc; struct sockaddr_in6 sin6; babel_interface_nfo *babel_ifp = babel_get_if_nfo(ifp); assert(babel_ifp->buffered <= babel_ifp->bufsize); flushupdates(ifp); if(babel_ifp->buffered > 0) { debugf(BABEL_DEBUG_COMMON," (flushing %d buffered bytes on %s)", babel_ifp->buffered, ifp->name); if(check_bucket(ifp)) { memset(&sin6, 0, sizeof(sin6)); sin6.sin6_family = AF_INET6; memcpy(&sin6.sin6_addr, protocol_group, 16); sin6.sin6_port = htons(protocol_port); sin6.sin6_scope_id = ifp->ifindex; DO_HTONS(packet_header + 2, babel_ifp->buffered); fill_rtt_message(ifp); rc = babel_send(protocol_socket, packet_header, sizeof(packet_header), babel_ifp->sendbuf, babel_ifp->buffered, (struct sockaddr*)&sin6, sizeof(sin6)); if(rc < 0) flog_err(EC_BABEL_PACKET, "send: %s", safe_strerror(errno)); } else { flog_err(EC_BABEL_PACKET, "Warning: bucket full, dropping packet to %s.", ifp->name); } } VALGRIND_MAKE_MEM_UNDEFINED(babel_ifp->sendbuf, babel_ifp->bufsize); babel_ifp->buffered = 0; babel_ifp->buffered_hello = -1; babel_ifp->have_buffered_id = 0; babel_ifp->have_buffered_nh = 0; babel_ifp->have_buffered_prefix = 0; babel_ifp->flush_timeout.tv_sec = 0; babel_ifp->flush_timeout.tv_usec = 0; } static void schedule_flush(struct interface *ifp) { babel_interface_nfo *babel_ifp = babel_get_if_nfo(ifp); unsigned msecs = jitter(babel_ifp, 0); if(babel_ifp->flush_timeout.tv_sec != 0 && timeval_minus_msec(&babel_ifp->flush_timeout, &babel_now) < msecs) return; set_timeout(&babel_ifp->flush_timeout, msecs); } static void schedule_flush_now(struct interface *ifp) { babel_interface_nfo *babel_ifp = babel_get_if_nfo(ifp); /* Almost now */ unsigned msecs = roughly(10); if(babel_ifp->flush_timeout.tv_sec != 0 && timeval_minus_msec(&babel_ifp->flush_timeout, &babel_now) < msecs) return; set_timeout(&babel_ifp->flush_timeout, msecs); } static void schedule_unicast_flush(unsigned msecs) { if(!unicast_neighbour) return; if(unicast_flush_timeout.tv_sec != 0 && timeval_minus_msec(&unicast_flush_timeout, &babel_now) < msecs) return; unicast_flush_timeout.tv_usec = (babel_now.tv_usec + msecs * 1000) %1000000; unicast_flush_timeout.tv_sec = babel_now.tv_sec + (babel_now.tv_usec / 1000 + msecs) / 1000; } static void ensure_space(struct interface *ifp, int space) { babel_interface_nfo *babel_ifp = babel_get_if_nfo(ifp); if(babel_ifp->bufsize - babel_ifp->buffered < space) flushbuf(ifp); } static void start_message(struct interface *ifp, int type, int len) { babel_interface_nfo *babel_ifp = babel_get_if_nfo(ifp); if(babel_ifp->bufsize - babel_ifp->buffered < len + 2) flushbuf(ifp); babel_ifp->sendbuf[babel_ifp->buffered++] = type; babel_ifp->sendbuf[babel_ifp->buffered++] = len; } static void end_message(struct interface *ifp, int type, int bytes) { babel_interface_nfo *babel_ifp = babel_get_if_nfo(ifp); assert(babel_ifp->buffered >= bytes + 2 && babel_ifp->sendbuf[babel_ifp->buffered - bytes - 2] == type && babel_ifp->sendbuf[babel_ifp->buffered - bytes - 1] == bytes); schedule_flush(ifp); } static void accumulate_byte(struct interface *ifp, unsigned char value) { babel_interface_nfo *babel_ifp = babel_get_if_nfo(ifp); babel_ifp->sendbuf[babel_ifp->buffered++] = value; } static void accumulate_short(struct interface *ifp, unsigned short value) { babel_interface_nfo *babel_ifp = babel_get_if_nfo(ifp); DO_HTONS(babel_ifp->sendbuf + babel_ifp->buffered, value); babel_ifp->buffered += 2; } static void accumulate_int(struct interface *ifp, unsigned int value) { babel_interface_nfo *babel_ifp = babel_get_if_nfo(ifp); DO_HTONL(babel_ifp->sendbuf + babel_ifp->buffered, value); babel_ifp->buffered += 4; } static void accumulate_bytes(struct interface *ifp, const unsigned char *value, unsigned len) { babel_interface_nfo *babel_ifp = babel_get_if_nfo(ifp); memcpy(babel_ifp->sendbuf + babel_ifp->buffered, value, len); babel_ifp->buffered += len; } static int start_unicast_message(struct neighbour *neigh, int type, int len) { if(unicast_neighbour) { if(neigh != unicast_neighbour || unicast_buffered + len + 2 >= MIN(UNICAST_BUFSIZE, babel_get_if_nfo(neigh->ifp)->bufsize)) flush_unicast(0); } if(!unicast_buffer) unicast_buffer = malloc(UNICAST_BUFSIZE); if(!unicast_buffer) { flog_err(EC_BABEL_MEMORY, "malloc(unicast_buffer): %s", safe_strerror(errno)); return -1; } unicast_neighbour = neigh; unicast_buffer[unicast_buffered++] = type; unicast_buffer[unicast_buffered++] = len; return 1; } static void end_unicast_message(struct neighbour *neigh, int type, int bytes) { assert(unicast_neighbour == neigh && unicast_buffered >= bytes + 2 && unicast_buffer[unicast_buffered - bytes - 2] == type && unicast_buffer[unicast_buffered - bytes - 1] == bytes); schedule_unicast_flush(jitter(babel_get_if_nfo(neigh->ifp), 0)); } static void accumulate_unicast_byte(struct neighbour *neigh, unsigned char value) { unicast_buffer[unicast_buffered++] = value; } static void accumulate_unicast_short(struct neighbour *neigh, unsigned short value) { DO_HTONS(unicast_buffer + unicast_buffered, value); unicast_buffered += 2; } static void accumulate_unicast_int(struct neighbour *neigh, unsigned int value) { DO_HTONL(unicast_buffer + unicast_buffered, value); unicast_buffered += 4; } static void accumulate_unicast_bytes(struct neighbour *neigh, const unsigned char *value, unsigned len) { memcpy(unicast_buffer + unicast_buffered, value, len); unicast_buffered += len; } void send_ack(struct neighbour *neigh, unsigned short nonce, unsigned short interval) { int rc; debugf(BABEL_DEBUG_COMMON,"Sending ack (%04x) to %s on %s.", nonce, format_address(neigh->address), neigh->ifp->name); rc = start_unicast_message(neigh, MESSAGE_ACK, 2); if(rc < 0) return; accumulate_unicast_short(neigh, nonce); end_unicast_message(neigh, MESSAGE_ACK, 2); /* Roughly yields a value no larger than 3/2, so this meets the deadline */ schedule_unicast_flush(roughly(interval * 6)); } void send_hello_noupdate(struct interface *ifp, unsigned interval) { babel_interface_nfo *babel_ifp = babel_get_if_nfo(ifp); /* This avoids sending multiple hellos in a single packet, which breaks link quality estimation. */ if(babel_ifp->buffered_hello >= 0) flushbuf(ifp); babel_ifp->hello_seqno = seqno_plus(babel_ifp->hello_seqno, 1); set_timeout(&babel_ifp->hello_timeout, babel_ifp->hello_interval); if(!if_up(ifp)) return; debugf(BABEL_DEBUG_COMMON,"Sending hello %d (%d) to %s.", babel_ifp->hello_seqno, interval, ifp->name); start_message(ifp, MESSAGE_HELLO, (babel_ifp->flags & BABEL_IF_TIMESTAMPS) ? 12 : 6); babel_ifp->buffered_hello = babel_ifp->buffered - 2; accumulate_short(ifp, 0); accumulate_short(ifp, babel_ifp->hello_seqno); accumulate_short(ifp, interval > 0xFFFF ? 0xFFFF : interval); if(babel_ifp->flags & BABEL_IF_TIMESTAMPS) { /* Sub-TLV containing the local time of emission. We use a Pad4 sub-TLV, which we'll fill just before sending. */ accumulate_byte(ifp, SUBTLV_PADN); accumulate_byte(ifp, 4); accumulate_int(ifp, 0); } end_message(ifp, MESSAGE_HELLO, (babel_ifp->flags & BABEL_IF_TIMESTAMPS) ? 12 : 6); } void send_hello(struct interface *ifp) { babel_interface_nfo *babel_ifp = babel_get_if_nfo(ifp); send_hello_noupdate(ifp, (babel_ifp->hello_interval + 9) / 10); /* Send full IHU every 3 hellos, and marginal IHU each time */ if(babel_ifp->hello_seqno % 3 == 0) send_ihu(NULL, ifp); else send_marginal_ihu(ifp); } void flush_unicast(int dofree) { struct sockaddr_in6 sin6; int rc; if(unicast_buffered == 0) goto done; if(!if_up(unicast_neighbour->ifp)) goto done; /* Preserve ordering of messages */ flushbuf(unicast_neighbour->ifp); if(check_bucket(unicast_neighbour->ifp)) { memset(&sin6, 0, sizeof(sin6)); sin6.sin6_family = AF_INET6; memcpy(&sin6.sin6_addr, unicast_neighbour->address, 16); sin6.sin6_port = htons(protocol_port); sin6.sin6_scope_id = unicast_neighbour->ifp->ifindex; DO_HTONS(packet_header + 2, unicast_buffered); fill_rtt_message(unicast_neighbour->ifp); rc = babel_send(protocol_socket, packet_header, sizeof(packet_header), unicast_buffer, unicast_buffered, (struct sockaddr*)&sin6, sizeof(sin6)); if(rc < 0) flog_err(EC_BABEL_PACKET, "send(unicast): %s", safe_strerror(errno)); } else { flog_err(EC_BABEL_PACKET, "Warning: bucket full, dropping unicast packet to %s if %s.", format_address(unicast_neighbour->address), unicast_neighbour->ifp->name); } done: VALGRIND_MAKE_MEM_UNDEFINED(unicast_buffer, UNICAST_BUFSIZE); unicast_buffered = 0; if(dofree && unicast_buffer) { free(unicast_buffer); unicast_buffer = NULL; } unicast_neighbour = NULL; unicast_flush_timeout.tv_sec = 0; unicast_flush_timeout.tv_usec = 0; } static void really_send_update(struct interface *ifp, const unsigned char *id, const unsigned char *prefix, unsigned char plen, unsigned short seqno, unsigned short metric, unsigned char *channels, int channels_len) { babel_interface_nfo *babel_ifp = babel_get_if_nfo(ifp); int add_metric, v4, real_plen, omit = 0; const unsigned char *real_prefix; unsigned short flags = 0; int channels_size; if(diversity_kind != DIVERSITY_CHANNEL) channels_len = -1; channels_size = channels_len >= 0 ? channels_len + 2 : 0; if(!if_up(ifp)) return; add_metric = output_filter(id, prefix, plen, ifp->ifindex); if(add_metric >= INFINITY) return; metric = MIN(metric + add_metric, INFINITY); /* Worst case */ ensure_space(ifp, 20 + 12 + 28); v4 = plen >= 96 && v4mapped(prefix); if(v4) { if(!babel_ifp->ipv4) return; if(!babel_ifp->have_buffered_nh || memcmp(babel_ifp->buffered_nh, babel_ifp->ipv4, 4) != 0) { start_message(ifp, MESSAGE_NH, 6); accumulate_byte(ifp, 1); accumulate_byte(ifp, 0); accumulate_bytes(ifp, babel_ifp->ipv4, 4); end_message(ifp, MESSAGE_NH, 6); memcpy(babel_ifp->buffered_nh, babel_ifp->ipv4, 4); babel_ifp->have_buffered_nh = 1; } real_prefix = prefix + 12; real_plen = plen - 96; } else { if(babel_ifp->have_buffered_prefix) { while(omit < plen / 8 && babel_ifp->buffered_prefix[omit] == prefix[omit]) omit++; } if(!babel_ifp->have_buffered_prefix || plen >= 48) flags |= 0x80; real_prefix = prefix; real_plen = plen; } if(!babel_ifp->have_buffered_id || memcmp(id, babel_ifp->buffered_id, 8) != 0) { if(real_plen == 128 && memcmp(real_prefix + 8, id, 8) == 0) { flags |= 0x40; } else { start_message(ifp, MESSAGE_ROUTER_ID, 10); accumulate_short(ifp, 0); accumulate_bytes(ifp, id, 8); end_message(ifp, MESSAGE_ROUTER_ID, 10); } memcpy(babel_ifp->buffered_id, id, sizeof(babel_ifp->buffered_id)); babel_ifp->have_buffered_id = 1; } start_message(ifp, MESSAGE_UPDATE, 10 + (real_plen + 7) / 8 - omit + channels_size); accumulate_byte(ifp, v4 ? 1 : 2); accumulate_byte(ifp, flags); accumulate_byte(ifp, real_plen); accumulate_byte(ifp, omit); accumulate_short(ifp, (babel_ifp->update_interval + 5) / 10); accumulate_short(ifp, seqno); accumulate_short(ifp, metric); accumulate_bytes(ifp, real_prefix + omit, (real_plen + 7) / 8 - omit); /* Note that an empty channels TLV is different from no such TLV. */ if(channels_len >= 0) { accumulate_byte(ifp, 2); accumulate_byte(ifp, channels_len); if (channels && channels_len > 0) accumulate_bytes(ifp, channels, channels_len); } end_message(ifp, MESSAGE_UPDATE, 10 + (real_plen + 7) / 8 - omit + channels_size); if(flags & 0x80) { memcpy(babel_ifp->buffered_prefix, prefix, 16); babel_ifp->have_buffered_prefix = 1; } } static int compare_buffered_updates(const void *av, const void *bv) { const struct buffered_update *a = av, *b = bv; int rc, v4a, v4b, ma, mb; rc = memcmp(a->id, b->id, 8); if(rc != 0) return rc; v4a = (a->plen >= 96 && v4mapped(a->prefix)); v4b = (b->plen >= 96 && v4mapped(b->prefix)); if(v4a > v4b) return 1; else if(v4a < v4b) return -1; ma = (!v4a && a->plen == 128 && memcmp(a->prefix + 8, a->id, 8) == 0); mb = (!v4b && b->plen == 128 && memcmp(b->prefix + 8, b->id, 8) == 0); if(ma > mb) return -1; else if(mb > ma) return 1; if(a->plen < b->plen) return 1; else if(a->plen > b->plen) return -1; return memcmp(a->prefix, b->prefix, 16); } void flushupdates(struct interface *ifp) { babel_interface_nfo *babel_ifp = NULL; struct xroute *xroute; struct babel_route *route; const unsigned char *last_prefix = NULL; unsigned char last_plen = 0xFF; int i; if(ifp == NULL) { struct vrf *vrf = vrf_lookup_by_id(VRF_DEFAULT); struct interface *ifp_aux; FOR_ALL_INTERFACES(vrf, ifp_aux) flushupdates(ifp_aux); return; } babel_ifp = babel_get_if_nfo(ifp); if(babel_ifp->num_buffered_updates > 0) { struct buffered_update *b = babel_ifp->buffered_updates; int n = babel_ifp->num_buffered_updates; babel_ifp->buffered_updates = NULL; babel_ifp->update_bufsize = 0; babel_ifp->num_buffered_updates = 0; if(!if_up(ifp)) goto done; debugf(BABEL_DEBUG_COMMON," (flushing %d buffered updates on %s (%d))", n, ifp->name, ifp->ifindex); /* In order to send fewer update messages, we want to send updates with the same router-id together, with IPv6 going out before IPv4. */ for(i = 0; i < n; i++) { route = find_installed_route(b[i].prefix, b[i].plen); if(route) memcpy(b[i].id, route->src->id, 8); else memcpy(b[i].id, myid, 8); } qsort(b, n, sizeof(struct buffered_update), compare_buffered_updates); for(i = 0; i < n; i++) { /* The same update may be scheduled multiple times before it is sent out. Since our buffer is now sorted, it is enough to compare with the previous update. */ if(last_prefix) { if(b[i].plen == last_plen && memcmp(b[i].prefix, last_prefix, 16) == 0) continue; } xroute = find_xroute(b[i].prefix, b[i].plen); route = find_installed_route(b[i].prefix, b[i].plen); if(xroute && (!route || xroute->metric <= kernel_metric)) { really_send_update(ifp, myid, xroute->prefix, xroute->plen, myseqno, xroute->metric, NULL, 0); last_prefix = xroute->prefix; last_plen = xroute->plen; } else if(route) { unsigned char channels[DIVERSITY_HOPS]; int chlen; struct interface *route_ifp = route->neigh->ifp; struct babel_interface *babel_route_ifp = NULL; unsigned short metric; unsigned short seqno; seqno = route->seqno; metric = route_interferes(route, ifp) ? route_metric(route) : route_metric_noninterfering(route); if(metric < INFINITY) satisfy_request(route->src->prefix, route->src->plen, seqno, route->src->id, ifp); if((babel_ifp->flags & BABEL_IF_SPLIT_HORIZON) && route->neigh->ifp == ifp) continue; babel_route_ifp = babel_get_if_nfo(route_ifp); if(babel_route_ifp->channel ==BABEL_IF_CHANNEL_NONINTERFERING) { memcpy(channels, route->channels, DIVERSITY_HOPS); } else { if(babel_route_ifp->channel == BABEL_IF_CHANNEL_UNKNOWN) channels[0] = BABEL_IF_CHANNEL_INTERFERING; else { assert(babel_route_ifp->channel > 0 && babel_route_ifp->channel <= 255); channels[0] = babel_route_ifp->channel; } memcpy(channels + 1, route->channels, DIVERSITY_HOPS - 1); } chlen = channels_len(channels); really_send_update(ifp, route->src->id, route->src->prefix, route->src->plen, seqno, metric, channels, chlen); update_source(route->src, seqno, metric); last_prefix = route->src->prefix; last_plen = route->src->plen; } else { /* There's no route for this prefix. This can happen shortly after an xroute has been retracted, so send a retraction. */ really_send_update(ifp, myid, b[i].prefix, b[i].plen, myseqno, INFINITY, NULL, -1); } } schedule_flush_now(ifp); done: free(b); } babel_ifp->update_flush_timeout.tv_sec = 0; babel_ifp->update_flush_timeout.tv_usec = 0; } static void schedule_update_flush(struct interface *ifp, int urgent) { babel_interface_nfo *babel_ifp = babel_get_if_nfo(ifp); unsigned msecs; msecs = update_jitter(babel_ifp, urgent); if(babel_ifp->update_flush_timeout.tv_sec != 0 && timeval_minus_msec(&babel_ifp->update_flush_timeout, &babel_now) < msecs) return; set_timeout(&babel_ifp->update_flush_timeout, msecs); } static void buffer_update(struct interface *ifp, const unsigned char *prefix, unsigned char plen) { babel_interface_nfo *babel_ifp = babel_get_if_nfo(ifp); if(babel_ifp->num_buffered_updates > 0 && babel_ifp->num_buffered_updates >= babel_ifp->update_bufsize) flushupdates(ifp); if(babel_ifp->update_bufsize == 0) { int n; assert(babel_ifp->buffered_updates == NULL); /* Allocate enough space to hold a full update. Since the number of installed routes will grow over time, make sure we have enough space to send a full-ish frame. */ n = installed_routes_estimate() + xroutes_estimate() + 4; n = MAX(n, babel_ifp->bufsize / 16); again: babel_ifp->buffered_updates = malloc(n *sizeof(struct buffered_update)); if(babel_ifp->buffered_updates == NULL) { flog_err(EC_BABEL_MEMORY, "malloc(buffered_updates): %s", safe_strerror(errno)); if(n > 4) { /* Try again with a tiny buffer. */ n = 4; goto again; } return; } babel_ifp->update_bufsize = n; babel_ifp->num_buffered_updates = 0; } memcpy(babel_ifp->buffered_updates[babel_ifp->num_buffered_updates].prefix, prefix, 16); babel_ifp->buffered_updates[babel_ifp->num_buffered_updates].plen = plen; babel_ifp->num_buffered_updates++; } void send_update(struct interface *ifp, int urgent, const unsigned char *prefix, unsigned char plen) { babel_interface_nfo *babel_ifp = NULL; if(ifp == NULL) { struct vrf *vrf = vrf_lookup_by_id(VRF_DEFAULT); struct interface *ifp_aux; struct babel_route *route; FOR_ALL_INTERFACES(vrf, ifp_aux) send_update(ifp_aux, urgent, prefix, plen); if(prefix) { /* Since flushupdates only deals with non-wildcard interfaces, we need to do this now. */ route = find_installed_route(prefix, plen); if(route && route_metric(route) < INFINITY) satisfy_request(prefix, plen, route->src->seqno, route->src->id, NULL); } return; } if(!if_up(ifp)) return; babel_ifp = babel_get_if_nfo(ifp); if(prefix) { debugf(BABEL_DEBUG_COMMON,"Sending update to %s for %s.", ifp->name, format_prefix(prefix, plen)); buffer_update(ifp, prefix, plen); } else { struct route_stream *routes = NULL; send_self_update(ifp); debugf(BABEL_DEBUG_COMMON,"Sending update to %s for any.", ifp->name); routes = route_stream(1); if(routes) { while(1) { struct babel_route *route = route_stream_next(routes); if(route == NULL) break; buffer_update(ifp, route->src->prefix, route->src->plen); } route_stream_done(routes); } else { flog_err(EC_BABEL_MEMORY, "Couldn't allocate route stream."); } set_timeout(&babel_ifp->update_timeout, babel_ifp->update_interval); babel_ifp->last_update_time = babel_now.tv_sec; } schedule_update_flush(ifp, urgent); } void send_update_resend(struct interface *ifp, const unsigned char *prefix, unsigned char plen) { assert(prefix != NULL); send_update(ifp, 1, prefix, plen); record_resend(RESEND_UPDATE, prefix, plen, 0, NULL, NULL, resend_delay); } void send_wildcard_retraction(struct interface *ifp) { babel_interface_nfo *babel_ifp = NULL; if(ifp == NULL) { struct vrf *vrf = vrf_lookup_by_id(VRF_DEFAULT); struct interface *ifp_aux; FOR_ALL_INTERFACES(vrf, ifp_aux) send_wildcard_retraction(ifp_aux); return; } if(!if_up(ifp)) return; babel_ifp = babel_get_if_nfo(ifp); start_message(ifp, MESSAGE_UPDATE, 10); accumulate_byte(ifp, 0); accumulate_byte(ifp, 0x40); accumulate_byte(ifp, 0); accumulate_byte(ifp, 0); accumulate_short(ifp, 0xFFFF); accumulate_short(ifp, myseqno); accumulate_short(ifp, 0xFFFF); end_message(ifp, MESSAGE_UPDATE, 10); babel_ifp->have_buffered_id = 0; } void update_myseqno(void) { myseqno = seqno_plus(myseqno, 1); } void send_self_update(struct interface *ifp) { struct xroute_stream *xroutes; if(ifp == NULL) { struct vrf *vrf = vrf_lookup_by_id(VRF_DEFAULT); struct interface *ifp_aux; FOR_ALL_INTERFACES(vrf, ifp_aux) { if(!if_up(ifp_aux)) continue; send_self_update(ifp_aux); } return; } debugf(BABEL_DEBUG_COMMON,"Sending self update to %s.", ifp->name); xroutes = xroute_stream(); if(xroutes) { while(1) { struct xroute *xroute = xroute_stream_next(xroutes); if(xroute == NULL) break; send_update(ifp, 0, xroute->prefix, xroute->plen); } xroute_stream_done(xroutes); } else { flog_err(EC_BABEL_MEMORY, "Couldn't allocate xroute stream."); } } void send_ihu(struct neighbour *neigh, struct interface *ifp) { babel_interface_nfo *babel_ifp = NULL; int rxcost, interval; int ll; int send_rtt_data; int msglen; if(neigh == NULL && ifp == NULL) { struct vrf *vrf = vrf_lookup_by_id(VRF_DEFAULT); struct interface *ifp_aux; FOR_ALL_INTERFACES(vrf, ifp_aux) { if(if_up(ifp_aux)) continue; send_ihu(NULL, ifp_aux); } return; } if(neigh == NULL) { struct neighbour *ngh; FOR_ALL_NEIGHBOURS(ngh) { if(ngh->ifp == ifp) send_ihu(ngh, ifp); } return; } if(ifp && neigh->ifp != ifp) return; ifp = neigh->ifp; babel_ifp = babel_get_if_nfo(ifp); if(!if_up(ifp)) return; rxcost = neighbour_rxcost(neigh); interval = (babel_ifp->hello_interval * 3 + 9) / 10; /* Conceptually, an IHU is a unicast message. We usually send them as multicast, since this allows aggregation into a single packet and avoids an ARP exchange. If we already have a unicast message queued for this neighbour, however, we might as well piggyback the IHU. */ debugf(BABEL_DEBUG_COMMON,"Sending %sihu %d on %s to %s.", unicast_neighbour == neigh ? "unicast " : "", rxcost, neigh->ifp->name, format_address(neigh->address)); ll = linklocal(neigh->address); if((babel_ifp->flags & BABEL_IF_TIMESTAMPS) && neigh->hello_send_us /* Checks whether the RTT data is not too old to be sent. */ && timeval_minus_msec(&babel_now, &neigh->hello_rtt_receive_time) < 1000000) { send_rtt_data = 1; } else { neigh->hello_send_us = 0; send_rtt_data = 0; } /* The length depends on the format of the address, and then an optional 10-bytes sub-TLV for timestamps (used to compute a RTT). */ msglen = (ll ? 14 : 22) + (send_rtt_data ? 10 : 0); if(unicast_neighbour != neigh) { start_message(ifp, MESSAGE_IHU, msglen); accumulate_byte(ifp, ll ? 3 : 2); accumulate_byte(ifp, 0); accumulate_short(ifp, rxcost); accumulate_short(ifp, interval); if(ll) accumulate_bytes(ifp, neigh->address + 8, 8); else accumulate_bytes(ifp, neigh->address, 16); if (send_rtt_data) { accumulate_byte(ifp, SUBTLV_TIMESTAMP); accumulate_byte(ifp, 8); accumulate_int(ifp, neigh->hello_send_us); accumulate_int(ifp, time_us(neigh->hello_rtt_receive_time)); } end_message(ifp, MESSAGE_IHU, msglen); } else { int rc; rc = start_unicast_message(neigh, MESSAGE_IHU, msglen); if(rc < 0) return; accumulate_unicast_byte(neigh, ll ? 3 : 2); accumulate_unicast_byte(neigh, 0); accumulate_unicast_short(neigh, rxcost); accumulate_unicast_short(neigh, interval); if(ll) accumulate_unicast_bytes(neigh, neigh->address + 8, 8); else accumulate_unicast_bytes(neigh, neigh->address, 16); if (send_rtt_data) { accumulate_unicast_byte(neigh, SUBTLV_TIMESTAMP); accumulate_unicast_byte(neigh, 8); accumulate_unicast_int(neigh, neigh->hello_send_us); accumulate_unicast_int(neigh, time_us(neigh->hello_rtt_receive_time)); } end_unicast_message(neigh, MESSAGE_IHU, msglen); } } /* Send IHUs to all marginal neighbours */ void send_marginal_ihu(struct interface *ifp) { struct neighbour *neigh; FOR_ALL_NEIGHBOURS(neigh) { if(ifp && neigh->ifp != ifp) continue; if(neigh->txcost >= 384 || (neigh->reach & 0xF000) != 0xF000) send_ihu(neigh, ifp); } } void send_request(struct interface *ifp, const unsigned char *prefix, unsigned char plen) { int v4, pb, len; if(ifp == NULL) { struct vrf *vrf = vrf_lookup_by_id(VRF_DEFAULT); struct interface *ifp_aux; FOR_ALL_INTERFACES(vrf, ifp_aux) { if(if_up(ifp_aux)) continue; send_request(ifp_aux, prefix, plen); } return; } /* make sure any buffered updates go out before this request. */ flushupdates(ifp); if(!if_up(ifp)) return; debugf(BABEL_DEBUG_COMMON,"sending request to %s for %s.", ifp->name, prefix ? format_prefix(prefix, plen) : "any"); v4 = plen >= 96 && v4mapped(prefix); pb = v4 ? ((plen - 96) + 7) / 8 : (plen + 7) / 8; len = !prefix ? 2 : 2 + pb; start_message(ifp, MESSAGE_REQUEST, len); accumulate_byte(ifp, !prefix ? 0 : v4 ? 1 : 2); accumulate_byte(ifp, !prefix ? 0 : v4 ? plen - 96 : plen); if(prefix) { if(v4) accumulate_bytes(ifp, prefix + 12, pb); else accumulate_bytes(ifp, prefix, pb); } end_message(ifp, MESSAGE_REQUEST, len); } void send_unicast_request(struct neighbour *neigh, const unsigned char *prefix, unsigned char plen) { int rc, v4, pb, len; /* make sure any buffered updates go out before this request. */ flushupdates(neigh->ifp); debugf(BABEL_DEBUG_COMMON,"sending unicast request to %s for %s.", format_address(neigh->address), prefix ? format_prefix(prefix, plen) : "any"); v4 = plen >= 96 && v4mapped(prefix); pb = v4 ? ((plen - 96) + 7) / 8 : (plen + 7) / 8; len = !prefix ? 2 : 2 + pb; rc = start_unicast_message(neigh, MESSAGE_REQUEST, len); if(rc < 0) return; accumulate_unicast_byte(neigh, !prefix ? 0 : v4 ? 1 : 2); accumulate_unicast_byte(neigh, !prefix ? 0 : v4 ? plen - 96 : plen); if(prefix) { if(v4) accumulate_unicast_bytes(neigh, prefix + 12, pb); else accumulate_unicast_bytes(neigh, prefix, pb); } end_unicast_message(neigh, MESSAGE_REQUEST, len); } void send_multihop_request(struct interface *ifp, const unsigned char *prefix, unsigned char plen, unsigned short seqno, const unsigned char *id, unsigned short hop_count) { int v4, pb, len; /* Make sure any buffered updates go out before this request. */ flushupdates(ifp); if(ifp == NULL) { struct vrf *vrf = vrf_lookup_by_id(VRF_DEFAULT); struct interface *ifp_aux; FOR_ALL_INTERFACES(vrf, ifp_aux) { if(!if_up(ifp_aux)) continue; send_multihop_request(ifp_aux, prefix, plen, seqno, id, hop_count); } return; } if(!if_up(ifp)) return; debugf(BABEL_DEBUG_COMMON,"Sending request (%d) on %s for %s.", hop_count, ifp->name, format_prefix(prefix, plen)); v4 = plen >= 96 && v4mapped(prefix); pb = v4 ? ((plen - 96) + 7) / 8 : (plen + 7) / 8; len = 6 + 8 + pb; start_message(ifp, MESSAGE_MH_REQUEST, len); accumulate_byte(ifp, v4 ? 1 : 2); accumulate_byte(ifp, v4 ? plen - 96 : plen); accumulate_short(ifp, seqno); accumulate_byte(ifp, hop_count); accumulate_byte(ifp, 0); accumulate_bytes(ifp, id, 8); if(prefix) { if(v4) accumulate_bytes(ifp, prefix + 12, pb); else accumulate_bytes(ifp, prefix, pb); } end_message(ifp, MESSAGE_MH_REQUEST, len); } void send_unicast_multihop_request(struct neighbour *neigh, const unsigned char *prefix, unsigned char plen, unsigned short seqno, const unsigned char *id, unsigned short hop_count) { int rc, v4, pb, len; /* Make sure any buffered updates go out before this request. */ flushupdates(neigh->ifp); debugf(BABEL_DEBUG_COMMON,"Sending multi-hop request to %s for %s (%d hops).", format_address(neigh->address), format_prefix(prefix, plen), hop_count); v4 = plen >= 96 && v4mapped(prefix); pb = v4 ? ((plen - 96) + 7) / 8 : (plen + 7) / 8; len = 6 + 8 + pb; rc = start_unicast_message(neigh, MESSAGE_MH_REQUEST, len); if(rc < 0) return; accumulate_unicast_byte(neigh, v4 ? 1 : 2); accumulate_unicast_byte(neigh, v4 ? plen - 96 : plen); accumulate_unicast_short(neigh, seqno); accumulate_unicast_byte(neigh, hop_count); accumulate_unicast_byte(neigh, 0); accumulate_unicast_bytes(neigh, id, 8); if(prefix) { if(v4) accumulate_unicast_bytes(neigh, prefix + 12, pb); else accumulate_unicast_bytes(neigh, prefix, pb); } end_unicast_message(neigh, MESSAGE_MH_REQUEST, len); } void send_request_resend(struct neighbour *neigh, const unsigned char *prefix, unsigned char plen, unsigned short seqno, unsigned char *id) { if(neigh) send_unicast_multihop_request(neigh, prefix, plen, seqno, id, 127); else send_multihop_request(NULL, prefix, plen, seqno, id, 127); record_resend(RESEND_REQUEST, prefix, plen, seqno, id, neigh ? neigh->ifp : NULL, resend_delay); } void handle_request(struct neighbour *neigh, const unsigned char *prefix, unsigned char plen, unsigned char hop_count, unsigned short seqno, const unsigned char *id) { struct xroute *xroute; struct babel_route *route; struct neighbour *successor = NULL; xroute = find_xroute(prefix, plen); route = find_installed_route(prefix, plen); if(xroute && (!route || xroute->metric <= kernel_metric)) { if(hop_count > 0 && memcmp(id, myid, 8) == 0) { if(seqno_compare(seqno, myseqno) > 0) { if(seqno_minus(seqno, myseqno) > 100) { /* Hopelessly out-of-date request */ return; } update_myseqno(); } } send_update(neigh->ifp, 1, prefix, plen); return; } if(route && (memcmp(id, route->src->id, 8) != 0 || seqno_compare(seqno, route->seqno) <= 0)) { send_update(neigh->ifp, 1, prefix, plen); return; } if(hop_count <= 1) return; if(route && memcmp(id, route->src->id, 8) == 0 && seqno_minus(seqno, route->seqno) > 100) { /* Hopelessly out-of-date */ return; } if(request_redundant(neigh->ifp, prefix, plen, seqno, id)) return; /* Let's try to forward this request. */ if(route && route_metric(route) < INFINITY) successor = route->neigh; if(!successor || successor == neigh) { /* We were about to forward a request to its requestor. Try to find a different neighbour to forward the request to. */ struct babel_route *other_route; other_route = find_best_route(prefix, plen, 0, neigh); if(other_route && route_metric(other_route) < INFINITY) successor = other_route->neigh; } if(!successor || successor == neigh) /* Give up */ return; send_unicast_multihop_request(successor, prefix, plen, seqno, id, hop_count - 1); record_resend(RESEND_REQUEST, prefix, plen, seqno, id, neigh->ifp, 0); } frr-7.2.1/babeld/message.h0000644000000000000000000001011313610377563012216 00000000000000/* Copyright (c) 2007, 2008 by Juliusz Chroboczek Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions: The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. */ #ifndef BABEL_MESSAGE_H #define BABEL_MESSAGE_H #include "babel_interface.h" #define MAX_BUFFERED_UPDATES 200 #define BUCKET_TOKENS_MAX 200 #define BUCKET_TOKENS_PER_SEC 40 /* A registry of assigned TLV and sub-TLV types is available at http://www.pps.univ-paris-diderot.fr/~jch/software/babel/babel-tlv-registry.text */ #define MESSAGE_PAD1 0 #define MESSAGE_PADN 1 #define MESSAGE_ACK_REQ 2 #define MESSAGE_ACK 3 #define MESSAGE_HELLO 4 #define MESSAGE_IHU 5 #define MESSAGE_ROUTER_ID 6 #define MESSAGE_NH 7 #define MESSAGE_UPDATE 8 #define MESSAGE_REQUEST 9 #define MESSAGE_MH_REQUEST 10 #define MESSAGE_MAX 10 /* Protocol extension through sub-TLVs. */ #define SUBTLV_PAD1 0 #define SUBTLV_PADN 1 #define SUBTLV_DIVERSITY 2 /* Also known as babelz. */ #define SUBTLV_TIMESTAMP 3 /* Used to compute RTT. */ extern unsigned short myseqno; extern int broadcast_ihu; extern int split_horizon; extern struct neighbour *unicast_neighbour; extern struct timeval unicast_flush_timeout; void parse_packet(const unsigned char *from, struct interface *ifp, const unsigned char *packet, int packetlen); void flushbuf(struct interface *ifp); void flushupdates(struct interface *ifp); void send_ack(struct neighbour *neigh, unsigned short nonce, unsigned short interval); void send_hello_noupdate(struct interface *ifp, unsigned interval); void send_hello(struct interface *ifp); void flush_unicast(int dofree); void send_update(struct interface *ifp, int urgent, const unsigned char *prefix, unsigned char plen); void send_update_resend(struct interface *ifp, const unsigned char *prefix, unsigned char plen); void send_wildcard_retraction(struct interface *ifp); void update_myseqno(void); void send_self_update(struct interface *ifp); void send_ihu(struct neighbour *neigh, struct interface *ifp); void send_marginal_ihu(struct interface *ifp); void send_request(struct interface *ifp, const unsigned char *prefix, unsigned char plen); void send_unicast_request(struct neighbour *neigh, const unsigned char *prefix, unsigned char plen); void send_multihop_request(struct interface *ifp, const unsigned char *prefix, unsigned char plen, unsigned short seqno, const unsigned char *id, unsigned short hop_count); void send_unicast_multihop_request(struct neighbour *neigh, const unsigned char *prefix, unsigned char plen, unsigned short seqno, const unsigned char *id, unsigned short hop_count); void send_request_resend(struct neighbour *neigh, const unsigned char *prefix, unsigned char plen, unsigned short seqno, unsigned char *id); void handle_request(struct neighbour *neigh, const unsigned char *prefix, unsigned char plen, unsigned char hop_count, unsigned short seqno, const unsigned char *id); #endif frr-7.2.1/babeld/neighbour.c0000644000000000000000000002526713610377563012567 00000000000000/* Copyright (c) 2007, 2008 by Juliusz Chroboczek Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions: The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. */ #ifdef HAVE_CONFIG_H #include "config.h" #endif #include #include #include #include #include #include #include "if.h" #include "babel_main.h" #include "babeld.h" #include "util.h" #include "babel_interface.h" #include "neighbour.h" #include "source.h" #include "route.h" #include "message.h" #include "resend.h" #include "babel_errors.h" struct neighbour *neighs = NULL; static struct neighbour * find_neighbour_nocreate(const unsigned char *address, struct interface *ifp) { struct neighbour *neigh; FOR_ALL_NEIGHBOURS(neigh) { if(memcmp(address, neigh->address, 16) == 0 && neigh->ifp == ifp) return neigh; } return NULL; } void flush_neighbour(struct neighbour *neigh) { debugf(BABEL_DEBUG_COMMON,"Flushing neighbour %s (reach 0x%04x)", format_address(neigh->address), neigh->reach); flush_neighbour_routes(neigh); if(unicast_neighbour == neigh) flush_unicast(1); flush_resends(neigh); if(neighs == neigh) { neighs = neigh->next; } else { struct neighbour *previous = neighs; while(previous->next != neigh) previous = previous->next; previous->next = neigh->next; } free(neigh); } struct neighbour * find_neighbour(const unsigned char *address, struct interface *ifp) { struct neighbour *neigh; const struct timeval zero = {0, 0}; neigh = find_neighbour_nocreate(address, ifp); if(neigh) return neigh; debugf(BABEL_DEBUG_COMMON,"Creating neighbour %s on %s.", format_address(address), ifp->name); neigh = malloc(sizeof(struct neighbour)); if(neigh == NULL) { flog_err(EC_BABEL_MEMORY, "malloc(neighbour): %s", safe_strerror(errno)); return NULL; } neigh->hello_seqno = -1; memcpy(neigh->address, address, 16); neigh->reach = 0; neigh->txcost = INFINITY; neigh->ihu_time = babel_now; neigh->hello_time = zero; neigh->hello_interval = 0; neigh->ihu_interval = 0; neigh->hello_send_us = 0; neigh->hello_rtt_receive_time = zero; neigh->rtt = 0; neigh->rtt_time = zero; neigh->ifp = ifp; neigh->next = neighs; neighs = neigh; send_hello(ifp); return neigh; } /* Recompute a neighbour's rxcost. Return true if anything changed. */ int update_neighbour(struct neighbour *neigh, int hello, int hello_interval) { int missed_hellos; int rc = 0; if(hello < 0) { if(neigh->hello_interval == 0) return rc; missed_hellos = ((int)timeval_minus_msec(&babel_now, &neigh->hello_time) - neigh->hello_interval * 7) / (neigh->hello_interval * 10); if(missed_hellos <= 0) return rc; timeval_add_msec(&neigh->hello_time, &neigh->hello_time, missed_hellos * neigh->hello_interval * 10); } else { if(neigh->hello_seqno >= 0 && neigh->reach > 0) { missed_hellos = seqno_minus(hello, neigh->hello_seqno) - 1; if(missed_hellos < -8) { /* Probably a neighbour that rebooted and lost its seqno. Reboot the universe. */ neigh->reach = 0; missed_hellos = 0; rc = 1; } else if(missed_hellos < 0) { if(hello_interval > neigh->hello_interval) { /* This neighbour has increased its hello interval, and we didn't notice. */ neigh->reach <<= -missed_hellos; missed_hellos = 0; } else { /* Late hello. Probably due to the link layer buffering packets during a link outage. Ignore it, but reset the expected seqno. */ neigh->hello_seqno = hello; hello = -1; missed_hellos = 0; } rc = 1; } } else { missed_hellos = 0; } neigh->hello_time = babel_now; neigh->hello_interval = hello_interval; } if(missed_hellos > 0) { neigh->reach >>= missed_hellos; neigh->hello_seqno = seqno_plus(neigh->hello_seqno, missed_hellos); rc = 1; } if(hello >= 0) { neigh->hello_seqno = hello; neigh->reach >>= 1; neigh->reach |= 0x8000; if((neigh->reach & 0xFC00) != 0xFC00) rc = 1; } /* Make sure to give neighbours some feedback early after association */ if((neigh->reach & 0xBF00) == 0x8000) { /* A new neighbour */ send_hello(neigh->ifp); } else { /* Don't send hellos, in order to avoid a positive feedback loop. */ int a = (neigh->reach & 0xC000); int b = (neigh->reach & 0x3000); if((a == 0xC000 && b == 0) || (a == 0 && b == 0x3000)) { /* Reachability is either 1100 or 0011 */ send_self_update(neigh->ifp); } } if((neigh->reach & 0xFC00) == 0xC000) { /* This is a newish neighbour, let's request a full route dump. We ought to avoid this when the network is dense */ send_unicast_request(neigh, NULL, 0); send_ihu(neigh, NULL); } return rc; } static int reset_txcost(struct neighbour *neigh) { unsigned delay; delay = timeval_minus_msec(&babel_now, &neigh->ihu_time); if(neigh->ihu_interval > 0 && delay < neigh->ihu_interval * 10U * 3U) return 0; /* If we're losing a lot of packets, we probably lost an IHU too */ if(delay >= 180000 || (neigh->reach & 0xFFF0) == 0 || (neigh->ihu_interval > 0 && delay >= neigh->ihu_interval * 10U * 10U)) { neigh->txcost = INFINITY; neigh->ihu_time = babel_now; return 1; } return 0; } unsigned neighbour_txcost(struct neighbour *neigh) { return neigh->txcost; } unsigned check_neighbours(void) { struct neighbour *neigh; int changed, rc; unsigned msecs = 50000; debugf(BABEL_DEBUG_COMMON,"Checking neighbours."); neigh = neighs; while(neigh) { changed = update_neighbour(neigh, -1, 0); if(neigh->reach == 0 || neigh->hello_time.tv_sec > babel_now.tv_sec || /* clock stepped */ timeval_minus_msec(&babel_now, &neigh->hello_time) > 300000) { struct neighbour *old = neigh; neigh = neigh->next; flush_neighbour(old); continue; } rc = reset_txcost(neigh); changed = changed || rc; update_neighbour_metric(neigh, changed); if(neigh->hello_interval > 0) msecs = MIN(msecs, neigh->hello_interval * 10U); if(neigh->ihu_interval > 0) msecs = MIN(msecs, neigh->ihu_interval * 10U); neigh = neigh->next; } return msecs; } unsigned neighbour_rxcost(struct neighbour *neigh) { unsigned delay; unsigned short reach = neigh->reach; delay = timeval_minus_msec(&babel_now, &neigh->hello_time); if((reach & 0xFFF0) == 0 || delay >= 180000) { return INFINITY; } else if(babel_get_if_nfo(neigh->ifp)->flags & BABEL_IF_LQ) { int sreach = ((reach & 0x8000) >> 2) + ((reach & 0x4000) >> 1) + (reach & 0x3FFF); /* 0 <= sreach <= 0x7FFF */ int cost = (0x8000 * babel_get_if_nfo(neigh->ifp)->cost) / (sreach + 1); /* cost >= interface->cost */ if(delay >= 40000) cost = (cost * (delay - 20000) + 10000) / 20000; return MIN(cost, INFINITY); } else { /* To lose one hello is a misfortune, to lose two is carelessness. */ if((reach & 0xC000) == 0xC000) return babel_get_if_nfo(neigh->ifp)->cost; else if((reach & 0xC000) == 0) return INFINITY; else if((reach & 0x2000)) return babel_get_if_nfo(neigh->ifp)->cost; else return INFINITY; } } unsigned neighbour_rttcost(struct neighbour *neigh) { struct interface *ifp = neigh->ifp; babel_interface_nfo *babel_ifp = babel_get_if_nfo(ifp); if(!babel_ifp->max_rtt_penalty || !valid_rtt(neigh)) return 0; /* Function: linear behaviour between rtt_min and rtt_max. */ if(neigh->rtt <= babel_ifp->rtt_min) { return 0; } else if(neigh->rtt <= babel_ifp->rtt_max) { unsigned long long tmp = (unsigned long long)babel_ifp->max_rtt_penalty * (neigh->rtt - babel_ifp->rtt_min) / (babel_ifp->rtt_max - babel_ifp->rtt_min); assert((tmp & 0x7FFFFFFF) == tmp); return tmp; } else { return babel_ifp->max_rtt_penalty; } } unsigned neighbour_cost(struct neighbour *neigh) { unsigned a, b, cost; if(!if_up(neigh->ifp)) return INFINITY; a = neighbour_txcost(neigh); if(a >= INFINITY) return INFINITY; b = neighbour_rxcost(neigh); if(b >= INFINITY) return INFINITY; if(!(babel_get_if_nfo(neigh->ifp)->flags & BABEL_IF_LQ) || (a < 256 && b < 256)) { cost = a; } else { /* a = 256/alpha, b = 256/beta, where alpha and beta are the expected probabilities of a packet getting through in the direct and reverse directions. */ a = MAX(a, 256); b = MAX(b, 256); /* 1/(alpha * beta), which is just plain ETX. */ /* Since a and b are capped to 16 bits, overflow is impossible. */ cost = (a * b + 128) >> 8; } cost += neighbour_rttcost(neigh); return MIN(cost, INFINITY); } int valid_rtt(struct neighbour *neigh) { return (timeval_minus_msec(&babel_now, &neigh->rtt_time) < 180000) ? 1 : 0; } frr-7.2.1/babeld/neighbour.h0000644000000000000000000000475713610377563012575 00000000000000/* Copyright (c) 2007, 2008 by Juliusz Chroboczek Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions: The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. */ #ifndef BABEL_NEIGHBOUR_H #define BABEL_NEIGHBOUR_H struct neighbour { struct neighbour *next; /* This is -1 when unknown, so don't make it unsigned */ int hello_seqno; unsigned char address[16]; unsigned short reach; unsigned short txcost; struct timeval hello_time; struct timeval ihu_time; unsigned short hello_interval; /* in centiseconds */ unsigned short ihu_interval; /* in centiseconds */ /* Used for RTT estimation. */ /* Absolute time (modulo 2^32) at which the Hello was sent, according to remote clock. */ unsigned int hello_send_us; struct timeval hello_rtt_receive_time; unsigned int rtt; struct timeval rtt_time; struct interface *ifp; }; extern struct neighbour *neighs; #define FOR_ALL_NEIGHBOURS(_neigh) \ for(_neigh = neighs; _neigh; _neigh = _neigh->next) int neighbour_valid(struct neighbour *neigh); void flush_neighbour(struct neighbour *neigh); struct neighbour *find_neighbour(const unsigned char *address, struct interface *ifp); int update_neighbour(struct neighbour *neigh, int hello, int hello_interval); unsigned check_neighbours(void); unsigned neighbour_txcost(struct neighbour *neigh); unsigned neighbour_rxcost(struct neighbour *neigh); unsigned neighbour_rttcost(struct neighbour *neigh); unsigned neighbour_cost(struct neighbour *neigh); int valid_rtt(struct neighbour *neigh); #endif /* BABEL_NEIGHBOUR_H */ frr-7.2.1/babeld/net.c0000644000000000000000000001206413610377563011362 00000000000000/* Copyright (c) 2007, 2008 by Juliusz Chroboczek Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions: The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. */ #ifdef HAVE_CONFIG_H #include "config.h" #endif #include #include #include #include #include #include #include #include #include #include #include #include #include "babeld.h" #include "util.h" #include "net.h" #include "sockopt.h" int babel_socket(int port) { struct sockaddr_in6 sin6; int s, rc; int saved_errno; int one = 1, zero = 0; s = socket(PF_INET6, SOCK_DGRAM, 0); if(s < 0) return -1; rc = setsockopt(s, IPPROTO_IPV6, IPV6_V6ONLY, &one, sizeof(one)); if(rc < 0) goto fail; rc = setsockopt(s, SOL_SOCKET, SO_REUSEADDR, &one, sizeof(one)); if(rc < 0) goto fail; rc = setsockopt(s, IPPROTO_IPV6, IPV6_MULTICAST_LOOP, &zero, sizeof(zero)); if(rc < 0) goto fail; rc = setsockopt(s, IPPROTO_IPV6, IPV6_UNICAST_HOPS, &one, sizeof(one)); if(rc < 0) goto fail; rc = setsockopt(s, IPPROTO_IPV6, IPV6_MULTICAST_HOPS, &one, sizeof(one)); if(rc < 0) goto fail; setsockopt_ipv6_tclass (s, IPTOS_PREC_INTERNETCONTROL); rc = fcntl(s, F_GETFL, 0); if(rc < 0) goto fail; rc = fcntl(s, F_SETFL, (rc | O_NONBLOCK)); if(rc < 0) goto fail; rc = fcntl(s, F_GETFD, 0); if(rc < 0) goto fail; rc = fcntl(s, F_SETFD, rc | FD_CLOEXEC); if(rc < 0) goto fail; memset(&sin6, 0, sizeof(sin6)); sin6.sin6_family = AF_INET6; sin6.sin6_port = htons(port); rc = bind(s, (struct sockaddr*)&sin6, sizeof(sin6)); if(rc < 0) goto fail; return s; fail: saved_errno = errno; close(s); errno = saved_errno; return -1; } int babel_recv(int s, void *buf, int buflen, struct sockaddr *sin, int slen) { struct iovec iovec; struct msghdr msg; int rc; memset(&msg, 0, sizeof(msg)); iovec.iov_base = buf; iovec.iov_len = buflen; msg.msg_name = sin; msg.msg_namelen = slen; msg.msg_iov = &iovec; msg.msg_iovlen = 1; rc = recvmsg(s, &msg, 0); return rc; } int babel_send(int s, void *buf1, int buflen1, void *buf2, int buflen2, struct sockaddr *sin, int slen) { struct iovec iovec[2]; struct msghdr msg; int rc; iovec[0].iov_base = buf1; iovec[0].iov_len = buflen1; iovec[1].iov_base = buf2; iovec[1].iov_len = buflen2; memset(&msg, 0, sizeof(msg)); msg.msg_name = (struct sockaddr*)sin; msg.msg_namelen = slen; msg.msg_iov = iovec; msg.msg_iovlen = 2; again: rc = sendmsg(s, &msg, 0); if(rc < 0) { if(errno == EINTR) goto again; else if(errno == EAGAIN) { int rc2; rc2 = wait_for_fd(1, s, 5); if(rc2 > 0) goto again; errno = EAGAIN; } } return rc; } int tcp_server_socket(int port, int local) { struct sockaddr_in6 sin6; int s, rc, saved_errno; int one = 1; s = socket(PF_INET6, SOCK_STREAM, 0); if(s < 0) return -1; rc = setsockopt(s, SOL_SOCKET, SO_REUSEADDR, &one, sizeof(one)); if(rc < 0) goto fail; rc = fcntl(s, F_GETFL, 0); if(rc < 0) goto fail; rc = fcntl(s, F_SETFL, (rc | O_NONBLOCK)); if(rc < 0) goto fail; rc = fcntl(s, F_GETFD, 0); if(rc < 0) goto fail; rc = fcntl(s, F_SETFD, rc | FD_CLOEXEC); if(rc < 0) goto fail; memset(&sin6, 0, sizeof(sin6)); sin6.sin6_family = AF_INET6; sin6.sin6_port = htons(port); if(local) { rc = inet_pton(AF_INET6, "::1", &sin6.sin6_addr); if(rc < 0) goto fail; } rc = bind(s, (struct sockaddr*)&sin6, sizeof(sin6)); if(rc < 0) goto fail; rc = listen(s, 2); if(rc < 0) goto fail; return s; fail: saved_errno = errno; close(s); errno = saved_errno; return -1; } frr-7.2.1/babeld/net.h0000644000000000000000000000262713610377563011373 00000000000000/* Copyright (c) 2007, 2008 by Juliusz Chroboczek Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions: The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. */ #ifndef BABEL_NET_H #define BABEL_NET_H int babel_socket(int port); int babel_recv(int s, void *buf, int buflen, struct sockaddr *sin, int slen); int babel_send(int s, void *buf1, int buflen1, void *buf2, int buflen2, struct sockaddr *sin, int slen); int tcp_server_socket(int port, int local); #endif /* BABEL_NET_H */ frr-7.2.1/babeld/resend.c0000644000000000000000000002127213610377563012055 00000000000000/* Copyright (c) 2007, 2008 by Juliusz Chroboczek Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions: The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. */ #ifdef HAVE_CONFIG_H #include "config.h" #endif #include #include #include #include #include #include "if.h" #include "babel_main.h" #include "babeld.h" #include "util.h" #include "neighbour.h" #include "resend.h" #include "message.h" #include "babel_interface.h" struct timeval resend_time = {0, 0}; struct resend *to_resend = NULL; static int resend_match(struct resend *resend, int kind, const unsigned char *prefix, unsigned char plen) { return (resend->kind == kind && resend->plen == plen && memcmp(resend->prefix, prefix, 16) == 0); } /* This is called by neigh.c when a neighbour is flushed */ void flush_resends(struct neighbour *neigh) { /* Nothing for now */ } static struct resend * find_resend(int kind, const unsigned char *prefix, unsigned char plen, struct resend **previous_return) { struct resend *current, *previous; previous = NULL; current = to_resend; while(current) { if(resend_match(current, kind, prefix, plen)) { if(previous_return) *previous_return = previous; return current; } previous = current; current = current->next; } return NULL; } struct resend * find_request(const unsigned char *prefix, unsigned char plen, struct resend **previous_return) { return find_resend(RESEND_REQUEST, prefix, plen, previous_return); } int record_resend(int kind, const unsigned char *prefix, unsigned char plen, unsigned short seqno, const unsigned char *id, struct interface *ifp, int delay) { struct resend *resend; unsigned int ifindex = ifp ? ifp->ifindex : 0; if((kind == RESEND_REQUEST && input_filter(NULL, prefix, plen, NULL, ifindex) >= INFINITY) || (kind == RESEND_UPDATE && output_filter(NULL, prefix, plen, ifindex) >= INFINITY)) return 0; if(delay >= 0xFFFF) delay = 0xFFFF; resend = find_resend(kind, prefix, plen, NULL); if(resend) { if(resend->delay && delay) resend->delay = MIN(resend->delay, delay); else if(delay) resend->delay = delay; resend->time = babel_now; resend->max = RESEND_MAX; if(id && memcmp(resend->id, id, 8) == 0 && seqno_compare(resend->seqno, seqno) > 0) { return 0; } if(id) memcpy(resend->id, id, 8); else memset(resend->id, 0, 8); resend->seqno = seqno; if(resend->ifp != ifp) resend->ifp = NULL; } else { resend = malloc(sizeof(struct resend)); if(resend == NULL) return -1; resend->kind = kind; resend->max = RESEND_MAX; resend->delay = delay; memcpy(resend->prefix, prefix, 16); resend->plen = plen; resend->seqno = seqno; if(id) memcpy(resend->id, id, 8); else memset(resend->id, 0, 8); resend->ifp = ifp; resend->time = babel_now; resend->next = to_resend; to_resend = resend; } if(resend->delay) { struct timeval timeout; timeval_add_msec(&timeout, &resend->time, resend->delay); timeval_min(&resend_time, &timeout); } return 1; } static int resend_expired(struct resend *resend) { switch(resend->kind) { case RESEND_REQUEST: return timeval_minus_msec(&babel_now, &resend->time) >= REQUEST_TIMEOUT; default: return resend->max <= 0; } } int unsatisfied_request(const unsigned char *prefix, unsigned char plen, unsigned short seqno, const unsigned char *id) { struct resend *request; request = find_request(prefix, plen, NULL); if(request == NULL || resend_expired(request)) return 0; if(memcmp(request->id, id, 8) != 0 || seqno_compare(request->seqno, seqno) <= 0) return 1; return 0; } /* Determine whether a given request should be forwarded. */ int request_redundant(struct interface *ifp, const unsigned char *prefix, unsigned char plen, unsigned short seqno, const unsigned char *id) { struct resend *request; request = find_request(prefix, plen, NULL); if(request == NULL || resend_expired(request)) return 0; if(memcmp(request->id, id, 8) == 0 && seqno_compare(request->seqno, seqno) > 0) return 0; if(request->ifp != NULL && request->ifp != ifp) return 0; if(request->max > 0) /* Will be resent. */ return 1; if(timeval_minus_msec(&babel_now, &request->time) < (ifp ? MIN(babel_get_if_nfo(ifp)->hello_interval, 1000) : 1000)) /* Fairly recent. */ return 1; return 0; } int satisfy_request(const unsigned char *prefix, unsigned char plen, unsigned short seqno, const unsigned char *id, struct interface *ifp) { struct resend *request, *previous; request = find_request(prefix, plen, &previous); if(request == NULL) return 0; if(ifp != NULL && request->ifp != ifp) return 0; if(memcmp(request->id, id, 8) != 0 || seqno_compare(request->seqno, seqno) <= 0) { /* We cannot remove the request, as we may be walking the list right now. Mark it as expired, so that expire_resend will remove it. */ request->max = 0; request->time.tv_sec = 0; recompute_resend_time(); return 1; } return 0; } void expire_resend(void) { struct resend *current, *previous; int recompute = 0; previous = NULL; current = to_resend; while(current) { if(resend_expired(current)) { if(previous == NULL) { to_resend = current->next; free(current); current = to_resend; } else { previous->next = current->next; free(current); current = previous->next; } recompute = 1; } else { previous = current; current = current->next; } } if(recompute) recompute_resend_time(); } void recompute_resend_time(void) { struct resend *request; struct timeval resend = {0, 0}; request = to_resend; while(request) { if(!resend_expired(request) && request->delay > 0 && request->max > 0) { struct timeval timeout; timeval_add_msec(&timeout, &request->time, request->delay); timeval_min(&resend, &timeout); } request = request->next; } resend_time = resend; } void do_resend(void) { struct resend *resend; resend = to_resend; while(resend) { if(!resend_expired(resend) && resend->delay > 0 && resend->max > 0) { struct timeval timeout; timeval_add_msec(&timeout, &resend->time, resend->delay); if(timeval_compare(&babel_now, &timeout) >= 0) { switch(resend->kind) { case RESEND_REQUEST: send_multihop_request(resend->ifp, resend->prefix, resend->plen, resend->seqno, resend->id, 127); break; case RESEND_UPDATE: send_update(resend->ifp, 1, resend->prefix, resend->plen); break; default: abort(); } resend->delay = MIN(0xFFFF, resend->delay * 2); resend->max--; } } resend = resend->next; } recompute_resend_time(); } frr-7.2.1/babeld/resend.h0000644000000000000000000000475613610377563012072 00000000000000/* Copyright (c) 2007, 2008 by Juliusz Chroboczek Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions: The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. */ #ifndef BABEL_RESEND_H #define BABEL_RESEND_H #define REQUEST_TIMEOUT 65000 #define RESEND_MAX 3 #define RESEND_REQUEST 1 #define RESEND_UPDATE 2 struct resend { unsigned char kind; unsigned char max; unsigned short delay; struct timeval time; unsigned char prefix[16]; unsigned char plen; unsigned short seqno; unsigned char id[8]; struct interface *ifp; struct resend *next; }; extern struct timeval resend_time; struct resend *find_request(const unsigned char *prefix, unsigned char plen, struct resend **previous_return); void flush_resends(struct neighbour *neigh); int record_resend(int kind, const unsigned char *prefix, unsigned char plen, unsigned short seqno, const unsigned char *id, struct interface *ifp, int delay); int unsatisfied_request(const unsigned char *prefix, unsigned char plen, unsigned short seqno, const unsigned char *id); int request_redundant(struct interface *ifp, const unsigned char *prefix, unsigned char plen, unsigned short seqno, const unsigned char *id); int satisfy_request(const unsigned char *prefix, unsigned char plen, unsigned short seqno, const unsigned char *id, struct interface *ifp); void expire_resend(void); void recompute_resend_time(void); void do_resend(void); #endif /* BABEL_RESEND_H */ frr-7.2.1/babeld/route.c0000644000000000000000000007776613610377563011756 00000000000000/* Copyright (c) 2007, 2008 by Juliusz Chroboczek Copyright 2011 by Matthieu Boutier and Juliusz Chroboczek Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions: The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. */ #include #include "if.h" #include "babeld.h" #include "util.h" #include "kernel.h" #include "babel_interface.h" #include "source.h" #include "neighbour.h" #include "route.h" #include "xroute.h" #include "message.h" #include "resend.h" #include "babel_errors.h" static void consider_route(struct babel_route *route); struct babel_route **routes = NULL; static int route_slots = 0, max_route_slots = 0; int kernel_metric = 0; enum babel_diversity diversity_kind = DIVERSITY_NONE; int diversity_factor = BABEL_DEFAULT_DIVERSITY_FACTOR; int keep_unfeasible = 0; int smoothing_half_life = 0; static int two_to_the_one_over_hl = 0; /* 2^(1/hl) * 0x10000 */ /* We maintain a list of "slots", ordered by prefix. Every slot contains a linked list of the routes to this prefix, with the installed route, if any, at the head of the list. */ static int route_compare(const unsigned char *prefix, unsigned char plen, struct babel_route *route) { int i = memcmp(prefix, route->src->prefix, 16); if(i != 0) return i; if(plen < route->src->plen) return -1; else if(plen > route->src->plen) return 1; else return 0; } /* Performs binary search, returns -1 in case of failure. In the latter case, new_return is the place where to insert the new element. */ static int find_route_slot(const unsigned char *prefix, unsigned char plen, int *new_return) { int p, m, g, c; if(route_slots < 1) { if(new_return) *new_return = 0; return -1; } p = 0; g = route_slots - 1; do { m = (p + g) / 2; c = route_compare(prefix, plen, routes[m]); if(c == 0) return m; else if(c < 0) g = m - 1; else p = m + 1; } while(p <= g); if(new_return) *new_return = p; return -1; } struct babel_route * find_route(const unsigned char *prefix, unsigned char plen, struct neighbour *neigh, const unsigned char *nexthop) { struct babel_route *route; int i = find_route_slot(prefix, plen, NULL); if(i < 0) return NULL; route = routes[i]; while(route) { if(route->neigh == neigh && memcmp(route->nexthop, nexthop, 16) == 0) return route; route = route->next; } return NULL; } struct babel_route * find_installed_route(const unsigned char *prefix, unsigned char plen) { int i = find_route_slot(prefix, plen, NULL); if(i >= 0 && routes[i]->installed) return routes[i]; return NULL; } /* Returns an overestimate of the number of installed routes. */ int installed_routes_estimate(void) { return route_slots; } static int resize_route_table(int new_slots) { struct babel_route **new_routes; assert(new_slots >= route_slots); if(new_slots == 0) { new_routes = NULL; free(routes); } else { new_routes = realloc(routes, new_slots * sizeof(struct babel_route*)); if(new_routes == NULL) return -1; } max_route_slots = new_slots; routes = new_routes; return 1; } /* Insert a route into the table. If successful, retains the route. On failure, caller must free the route. */ static struct babel_route * insert_route(struct babel_route *route) { int i, n; assert(!route->installed); i = find_route_slot(route->src->prefix, route->src->plen, &n); if(i < 0) { if(route_slots >= max_route_slots) resize_route_table(max_route_slots < 1 ? 8 : 2 * max_route_slots); if(route_slots >= max_route_slots) return NULL; assert(routes); route->next = NULL; if(n < route_slots) memmove(routes + n + 1, routes + n, (route_slots - n) * sizeof(struct babel_route*)); route_slots++; routes[n] = route; } else { struct babel_route *r; r = routes[i]; while(r->next) r = r->next; r->next = route; route->next = NULL; } return route; } void flush_route(struct babel_route *route) { int i; struct source *src; unsigned oldmetric; int lost = 0; oldmetric = route_metric(route); src = route->src; if(route->installed) { uninstall_route(route); lost = 1; } i = find_route_slot(route->src->prefix, route->src->plen, NULL); assert(i >= 0 && i < route_slots); if(route == routes[i]) { routes[i] = route->next; route->next = NULL; free(route); if(routes[i] == NULL) { if(i < route_slots - 1) memmove(routes + i, routes + i + 1, (route_slots - i - 1) * sizeof(struct babel_route*)); routes[route_slots - 1] = NULL; route_slots--; } if(route_slots == 0) resize_route_table(0); else if(max_route_slots > 8 && route_slots < max_route_slots / 4) resize_route_table(max_route_slots / 2); } else { struct babel_route *r = routes[i]; while(r->next != route) r = r->next; r->next = route->next; route->next = NULL; free(route); } if(lost) route_lost(src, oldmetric); release_source(src); } void flush_all_routes(void) { int i; /* Start from the end, to avoid shifting the table. */ i = route_slots - 1; while(i >= 0) { while(i < route_slots) { /* Uninstall first, to avoid calling route_lost. */ if(routes[i]->installed) uninstall_route(routes[i]); flush_route(routes[i]); } i--; } check_sources_released(); } void flush_neighbour_routes(struct neighbour *neigh) { int i; i = 0; while(i < route_slots) { struct babel_route *r; r = routes[i]; while(r) { if(r->neigh == neigh) { flush_route(r); goto again; } r = r->next; } i++; again: ; } } void flush_interface_routes(struct interface *ifp, int v4only) { int i; i = 0; while(i < route_slots) { struct babel_route *r; r = routes[i]; while(r) { if(r->neigh->ifp == ifp && (!v4only || v4mapped(r->nexthop))) { flush_route(r); goto again; } r = r->next; } i++; again: ; } } struct route_stream { int installed; int index; struct babel_route *next; }; struct route_stream * route_stream(int installed) { struct route_stream *stream; stream = malloc(sizeof(struct route_stream)); if(stream == NULL) return NULL; stream->installed = installed; stream->index = installed ? 0 : -1; stream->next = NULL; return stream; } struct babel_route * route_stream_next(struct route_stream *stream) { if(stream->installed) { while(stream->index < route_slots && !routes[stream->index]->installed) stream->index++; if(stream->index < route_slots) return routes[stream->index++]; else return NULL; } else { struct babel_route *next; if(!stream->next) { stream->index++; if(stream->index >= route_slots) return NULL; stream->next = routes[stream->index]; } next = stream->next; stream->next = next->next; return next; } } void route_stream_done(struct route_stream *stream) { free(stream); } static int metric_to_kernel(int metric) { return metric < INFINITY ? kernel_metric : KERNEL_INFINITY; } /* This is used to maintain the invariant that the installed route is at the head of the list. */ static void move_installed_route(struct babel_route *route, int i) { assert(i >= 0 && i < route_slots); assert(route->installed); if(route != routes[i]) { struct babel_route *r = routes[i]; while(r->next != route) r = r->next; r->next = route->next; route->next = routes[i]; routes[i] = route; } } void install_route(struct babel_route *route) { int i, rc; if(route->installed) return; if(!route_feasible(route)) flog_err(EC_BABEL_ROUTE, "WARNING: installing unfeasible route " "(this shouldn't happen)."); i = find_route_slot(route->src->prefix, route->src->plen, NULL); assert(i >= 0 && i < route_slots); if(routes[i] != route && routes[i]->installed) { flog_err(EC_BABEL_ROUTE, "WARNING: attempting to install duplicate route " "(this shouldn't happen)."); return; } rc = kernel_route(ROUTE_ADD, route->src->prefix, route->src->plen, route->nexthop, route->neigh->ifp->ifindex, metric_to_kernel(route_metric(route)), NULL, 0, 0); if(rc < 0) { int save = errno; flog_err(EC_BABEL_ROUTE, "kernel_route(ADD): %s", safe_strerror(errno)); if(save != EEXIST) return; } route->installed = 1; move_installed_route(route, i); } void uninstall_route(struct babel_route *route) { int rc; if(!route->installed) return; rc = kernel_route(ROUTE_FLUSH, route->src->prefix, route->src->plen, route->nexthop, route->neigh->ifp->ifindex, metric_to_kernel(route_metric(route)), NULL, 0, 0); if(rc < 0) flog_err(EC_BABEL_ROUTE, "kernel_route(FLUSH): %s", safe_strerror(errno)); route->installed = 0; } /* This is equivalent to uninstall_route followed with install_route, but without the race condition. The destination of both routes must be the same. */ static void switch_routes(struct babel_route *old, struct babel_route *new) { int rc; if(!old) { install_route(new); return; } if(!old->installed) return; if(!route_feasible(new)) flog_err(EC_BABEL_ROUTE, "WARNING: switching to unfeasible route " "(this shouldn't happen)."); rc = kernel_route(ROUTE_MODIFY, old->src->prefix, old->src->plen, old->nexthop, old->neigh->ifp->ifindex, metric_to_kernel(route_metric(old)), new->nexthop, new->neigh->ifp->ifindex, metric_to_kernel(route_metric(new))); if(rc < 0) { flog_err(EC_BABEL_ROUTE, "kernel_route(MODIFY): %s", safe_strerror(errno)); return; } old->installed = 0; new->installed = 1; move_installed_route(new, find_route_slot(new->src->prefix, new->src->plen, NULL)); } static void change_route_metric(struct babel_route *route, unsigned refmetric, unsigned cost, unsigned add) { int old, new; int newmetric = MIN(refmetric + cost + add, INFINITY); old = metric_to_kernel(route_metric(route)); new = metric_to_kernel(newmetric); if(route->installed && old != new) { int rc; rc = kernel_route(ROUTE_MODIFY, route->src->prefix, route->src->plen, route->nexthop, route->neigh->ifp->ifindex, old, route->nexthop, route->neigh->ifp->ifindex, new); if(rc < 0) { flog_err(EC_BABEL_ROUTE, "kernel_route(MODIFY metric): %s", safe_strerror(errno)); return; } } /* Update route->smoothed_metric using the old metric. */ route_smoothed_metric(route); route->refmetric = refmetric; route->cost = cost; route->add_metric = add; if(smoothing_half_life == 0) { route->smoothed_metric = route_metric(route); route->smoothed_metric_time = babel_now.tv_sec; } } static void retract_route(struct babel_route *route) { /* We cannot simply remove the route from the kernel, as that might cause a routing loop -- see RFC 6126 Sections 2.8 and 3.5.5. */ change_route_metric(route, INFINITY, INFINITY, 0); } int route_feasible(struct babel_route *route) { return update_feasible(route->src, route->seqno, route->refmetric); } int route_old(struct babel_route *route) { return route->time < babel_now.tv_sec - route->hold_time * 7 / 8; } int route_expired(struct babel_route *route) { return route->time < babel_now.tv_sec - route->hold_time; } static int channels_interfere(int ch1, int ch2) { if(ch1 == BABEL_IF_CHANNEL_NONINTERFERING || ch2 == BABEL_IF_CHANNEL_NONINTERFERING) return 0; if(ch1 == BABEL_IF_CHANNEL_INTERFERING || ch2 == BABEL_IF_CHANNEL_INTERFERING) return 1; return ch1 == ch2; } int route_interferes(struct babel_route *route, struct interface *ifp) { struct babel_interface *babel_ifp = NULL; switch(diversity_kind) { case DIVERSITY_NONE: return 1; case DIVERSITY_INTERFACE_1: return route->neigh->ifp == ifp; case DIVERSITY_CHANNEL_1: case DIVERSITY_CHANNEL: if(route->neigh->ifp == ifp) return 1; babel_ifp = babel_get_if_nfo(ifp); if(channels_interfere(babel_ifp->channel, babel_get_if_nfo(route->neigh->ifp)->channel)) return 1; if(diversity_kind == DIVERSITY_CHANNEL) { int i; for(i = 0; i < DIVERSITY_HOPS; i++) { if(route->channels[i] == 0) break; if(channels_interfere(babel_ifp->channel, route->channels[i])) return 1; } } return 0; } return 1; } int update_feasible(struct source *src, unsigned short seqno, unsigned short refmetric) { if(src == NULL) return 1; if(src->time < babel_now.tv_sec - SOURCE_GC_TIME) /* Never mind what is probably stale data */ return 1; if(refmetric >= INFINITY) /* Retractions are always feasible */ return 1; return (seqno_compare(seqno, src->seqno) > 0 || (src->seqno == seqno && refmetric < src->metric)); } void change_smoothing_half_life(int half_life) { if(half_life <= 0) { smoothing_half_life = 0; two_to_the_one_over_hl = 0; return; } smoothing_half_life = half_life; switch(smoothing_half_life) { case 1: two_to_the_one_over_hl = 131072; break; case 2: two_to_the_one_over_hl = 92682; break; case 3: two_to_the_one_over_hl = 82570; break; case 4: two_to_the_one_over_hl = 77935; break; default: /* 2^(1/x) is 1 + log(2)/x + O(1/x^2) at infinity. */ two_to_the_one_over_hl = 0x10000 + 45426 / half_life; } } /* Update the smoothed metric, return the new value. */ int route_smoothed_metric(struct babel_route *route) { int metric = route_metric(route); if(smoothing_half_life <= 0 || /* no smoothing */ metric >= INFINITY || /* route retracted */ route->smoothed_metric_time > babel_now.tv_sec || /* clock stepped */ route->smoothed_metric == metric) { /* already converged */ route->smoothed_metric = metric; route->smoothed_metric_time = babel_now.tv_sec; } else { int diff; /* We randomise the computation, to minimise global synchronisation and hence oscillations. */ while(route->smoothed_metric_time <= babel_now.tv_sec - smoothing_half_life) { diff = metric - route->smoothed_metric; route->smoothed_metric += roughly(diff) / 2; route->smoothed_metric_time += smoothing_half_life; } while(route->smoothed_metric_time < babel_now.tv_sec) { diff = metric - route->smoothed_metric; route->smoothed_metric += roughly(diff) * (two_to_the_one_over_hl - 0x10000) / 0x10000; route->smoothed_metric_time++; } diff = metric - route->smoothed_metric; if(diff > -4 && diff < 4) route->smoothed_metric = metric; } /* change_route_metric relies on this */ assert(route->smoothed_metric_time == babel_now.tv_sec); return route->smoothed_metric; } static int route_acceptable(struct babel_route *route, int feasible, struct neighbour *exclude) { if(route_expired(route)) return 0; if(feasible && !route_feasible(route)) return 0; if(exclude && route->neigh == exclude) return 0; return 1; } /* Find the best route according to the weak ordering. Any linearisation of the strong ordering (see consider_route) will do, we use sm <= sm'. We could probably use a lexical ordering, but that's probably overkill. */ struct babel_route * find_best_route(const unsigned char *prefix, unsigned char plen, int feasible, struct neighbour *exclude) { struct babel_route *route = NULL, *r = NULL; int i = find_route_slot(prefix, plen, NULL); if(i < 0) return NULL; route = routes[i]; while(route && !route_acceptable(route, feasible, exclude)) route = route->next; if(!route) return NULL; r = route->next; while(r) { if(route_acceptable(r, feasible, exclude) && (route_smoothed_metric(r) < route_smoothed_metric(route))) route = r; r = r->next; } return route; } void update_route_metric(struct babel_route *route) { int oldmetric = route_metric(route); int old_smoothed_metric = route_smoothed_metric(route); if(route_expired(route)) { if(route->refmetric < INFINITY) { route->seqno = seqno_plus(route->src->seqno, 1); retract_route(route); if(oldmetric < INFINITY) route_changed(route, route->src, oldmetric); } } else { struct neighbour *neigh = route->neigh; int add_metric = input_filter(route->src->id, route->src->prefix, route->src->plen, neigh->address, neigh->ifp->ifindex); change_route_metric(route, route->refmetric, neighbour_cost(route->neigh), add_metric); if(route_metric(route) != oldmetric || route_smoothed_metric(route) != old_smoothed_metric) route_changed(route, route->src, oldmetric); } } /* Called whenever a neighbour's cost changes, to update the metric of all routes through that neighbour. */ void update_neighbour_metric(struct neighbour *neigh, int changed) { if(changed) { int i; for(i = 0; i < route_slots; i++) { struct babel_route *r = routes[i]; while(r) { if(r->neigh == neigh) update_route_metric(r); r = r->next; } } } } void update_interface_metric(struct interface *ifp) { int i; for(i = 0; i < route_slots; i++) { struct babel_route *r = routes[i]; while(r) { if(r->neigh->ifp == ifp) update_route_metric(r); r = r->next; } } } /* This is called whenever we receive an update. */ struct babel_route * update_route(const unsigned char *router_id, const unsigned char *prefix, unsigned char plen, unsigned short seqno, unsigned short refmetric, unsigned short interval, struct neighbour *neigh, const unsigned char *nexthop, const unsigned char *channels, int channels_len) { struct babel_route *route; struct source *src; int metric, feasible; int add_metric; int hold_time = MAX((4 * interval) / 100 + interval / 50, 15); if(memcmp(router_id, myid, 8) == 0) return NULL; if(martian_prefix(prefix, plen)) { flog_err(EC_BABEL_ROUTE, "Rejecting martian route to %s through %s.", format_prefix(prefix, plen), format_address(nexthop)); return NULL; } add_metric = input_filter(router_id, prefix, plen, neigh->address, neigh->ifp->ifindex); if(add_metric >= INFINITY) return NULL; route = find_route(prefix, plen, neigh, nexthop); if(route && memcmp(route->src->id, router_id, 8) == 0) /* Avoid scanning the source table. */ src = route->src; else src = find_source(router_id, prefix, plen, 1, seqno); if(src == NULL) return NULL; feasible = update_feasible(src, seqno, refmetric); metric = MIN((int)refmetric + neighbour_cost(neigh) + add_metric, INFINITY); if(route) { struct source *oldsrc; unsigned short oldmetric; int lost = 0; oldsrc = route->src; oldmetric = route_metric(route); /* If a successor switches sources, we must accept his update even if it makes a route unfeasible in order to break any routing loops in a timely manner. If the source remains the same, we ignore the update. */ if(!feasible && route->installed) { debugf(BABEL_DEBUG_COMMON,"Unfeasible update for installed route to %s " "(%s %d %d -> %s %d %d).", format_prefix(src->prefix, src->plen), format_eui64(route->src->id), route->seqno, route->refmetric, format_eui64(src->id), seqno, refmetric); if(src != route->src) { uninstall_route(route); lost = 1; } } route->src = retain_source(src); if((feasible || keep_unfeasible) && refmetric < INFINITY) route->time = babel_now.tv_sec; route->seqno = seqno; memset(&route->channels, 0, sizeof(route->channels)); if(channels_len > 0) memcpy(&route->channels, channels, MIN(channels_len, DIVERSITY_HOPS)); change_route_metric(route, refmetric, neighbour_cost(neigh), add_metric); route->hold_time = hold_time; route_changed(route, oldsrc, oldmetric); if(lost) route_lost(oldsrc, oldmetric); if(!feasible) send_unfeasible_request(neigh, route->installed && route_old(route), seqno, metric, src); release_source(oldsrc); } else { struct babel_route *new_route; if(refmetric >= INFINITY) /* Somebody's retracting a route we never saw. */ return NULL; if(!feasible) { send_unfeasible_request(neigh, 0, seqno, metric, src); if(!keep_unfeasible) return NULL; } route = malloc(sizeof(struct babel_route)); if(route == NULL) { perror("malloc(route)"); return NULL; } route->src = retain_source(src); route->refmetric = refmetric; route->cost = neighbour_cost(neigh); route->add_metric = add_metric; route->seqno = seqno; route->neigh = neigh; memcpy(route->nexthop, nexthop, 16); route->time = babel_now.tv_sec; route->hold_time = hold_time; route->smoothed_metric = MAX(route_metric(route), INFINITY / 2); route->smoothed_metric_time = babel_now.tv_sec; route->installed = 0; memset(&route->channels, 0, sizeof(route->channels)); if(channels_len > 0) memcpy(&route->channels, channels, MIN(channels_len, DIVERSITY_HOPS)); route->next = NULL; new_route = insert_route(route); if(new_route == NULL) { flog_err(EC_BABEL_ROUTE, "Couldn't insert route."); free(route); return NULL; } consider_route(route); } return route; } /* We just received an unfeasible update. If it's any good, send a request for a new seqno. */ void send_unfeasible_request(struct neighbour *neigh, int force, unsigned short seqno, unsigned short metric, struct source *src) { struct babel_route *route = find_installed_route(src->prefix, src->plen); if(seqno_minus(src->seqno, seqno) > 100) { /* Probably a source that lost its seqno. Let it time-out. */ return; } if(force || !route || route_metric(route) >= metric + 512) { send_unicast_multihop_request(neigh, src->prefix, src->plen, src->metric >= INFINITY ? src->seqno : seqno_plus(src->seqno, 1), src->id, 127); } } /* This takes a feasible route and decides whether to install it. This uses the strong ordering, which is defined by sm <= sm' AND m <= m'. This ordering is not total, which is what causes hysteresis. */ static void consider_route(struct babel_route *route) { struct babel_route *installed; struct xroute *xroute; if(route->installed) return; if(!route_feasible(route)) return; xroute = find_xroute(route->src->prefix, route->src->plen); if(xroute) return; installed = find_installed_route(route->src->prefix, route->src->plen); if(installed == NULL) goto install; if(route_metric(route) >= INFINITY) return; if(route_metric(installed) >= INFINITY) goto install; if(route_metric(installed) >= route_metric(route) && route_smoothed_metric(installed) > route_smoothed_metric(route)) goto install; return; install: switch_routes(installed, route); if(installed && route->installed) send_triggered_update(route, installed->src, route_metric(installed)); else send_update(NULL, 1, route->src->prefix, route->src->plen); return; } void retract_neighbour_routes(struct neighbour *neigh) { int i; for(i = 0; i < route_slots; i++) { struct babel_route *r = routes[i]; while(r) { if(r->neigh == neigh) { if(r->refmetric != INFINITY) { unsigned short oldmetric = route_metric(r); retract_route(r); if(oldmetric != INFINITY) route_changed(r, r->src, oldmetric); } } r = r->next; } } } void send_triggered_update(struct babel_route *route, struct source *oldsrc, unsigned oldmetric) { unsigned newmetric, diff; /* 1 means send speedily, 2 means resend */ int urgent; if(!route->installed) return; newmetric = route_metric(route); diff = newmetric >= oldmetric ? newmetric - oldmetric : oldmetric - newmetric; if(route->src != oldsrc || (oldmetric < INFINITY && newmetric >= INFINITY)) /* Switching sources can cause transient routing loops. Retractions can cause blackholes. */ urgent = 2; else if(newmetric > oldmetric && oldmetric < 6 * 256 && diff >= 512) /* Route getting significantly worse */ urgent = 1; else if(unsatisfied_request(route->src->prefix, route->src->plen, route->seqno, route->src->id)) /* Make sure that requests are satisfied speedily */ urgent = 1; else if(oldmetric >= INFINITY && newmetric < INFINITY) /* New route */ urgent = 0; else if(newmetric < oldmetric && diff < 1024) /* Route getting better. This may be a transient fluctuation, so don't advertise it to avoid making routes unfeasible later on. */ return; else if(diff < 384) /* Don't fret about trivialities */ return; else urgent = 0; if(urgent >= 2) send_update_resend(NULL, route->src->prefix, route->src->plen); else send_update(NULL, urgent, route->src->prefix, route->src->plen); if(oldmetric < INFINITY) { if(newmetric >= oldmetric + 512) { send_request_resend(NULL, route->src->prefix, route->src->plen, route->src->metric >= INFINITY ? route->src->seqno : seqno_plus(route->src->seqno, 1), route->src->id); } else if(newmetric >= oldmetric + 288) { send_request(NULL, route->src->prefix, route->src->plen); } } } /* A route has just changed. Decide whether to switch to a different route or send an update. */ void route_changed(struct babel_route *route, struct source *oldsrc, unsigned short oldmetric) { if(route->installed) { struct babel_route *better_route; /* Do this unconditionally -- microoptimisation is not worth it. */ better_route = find_best_route(route->src->prefix, route->src->plen, 1, NULL); if(better_route && route_metric(better_route) < route_metric(route)) consider_route(better_route); } if(route->installed) { /* We didn't change routes after all. */ send_triggered_update(route, oldsrc, oldmetric); } else { /* Reconsider routes even when their metric didn't decrease, they may not have been feasible before. */ consider_route(route); } } /* We just lost the installed route to a given destination. */ void route_lost(struct source *src, unsigned oldmetric) { struct babel_route *new_route; new_route = find_best_route(src->prefix, src->plen, 1, NULL); if(new_route) { consider_route(new_route); } else if(oldmetric < INFINITY) { /* Avoid creating a blackhole. */ send_update_resend(NULL, src->prefix, src->plen); /* If the route was usable enough, try to get an alternate one. If it was not, we could be dealing with oscillations around the value of INFINITY. */ if(oldmetric <= INFINITY / 2) send_request_resend(NULL, src->prefix, src->plen, src->metric >= INFINITY ? src->seqno : seqno_plus(src->seqno, 1), src->id); } } /* This is called periodically to flush old routes. It will also send requests for routes that are about to expire. */ void expire_routes(void) { struct babel_route *r; int i; debugf(BABEL_DEBUG_COMMON,"Expiring old routes."); i = 0; while(i < route_slots) { r = routes[i]; while(r) { /* Protect against clock being stepped. */ if(r->time > babel_now.tv_sec || route_old(r)) { flush_route(r); goto again; } update_route_metric(r); if(r->installed && r->refmetric < INFINITY) { if(route_old(r)) /* Route about to expire, send a request. */ send_unicast_request(r->neigh, r->src->prefix, r->src->plen); } r = r->next; } i++; again: ; } } frr-7.2.1/babeld/route.h0000644000000000000000000001206413610377563011737 00000000000000/* Copyright (c) 2007, 2008 by Juliusz Chroboczek Copyright 2011 by Matthieu Boutier and Juliusz Chroboczek Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions: The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. */ #ifndef BABEL_ROUTE_H #define BABEL_ROUTE_H #include "babel_interface.h" #include "source.h" enum babel_diversity { DIVERSITY_NONE, DIVERSITY_INTERFACE_1, DIVERSITY_CHANNEL_1, DIVERSITY_CHANNEL, }; #define DIVERSITY_HOPS 8 struct babel_route { struct source *src; unsigned short refmetric; unsigned short cost; unsigned short add_metric; unsigned short seqno; struct neighbour *neigh; unsigned char nexthop[16]; time_t time; unsigned short hold_time; /* in seconds */ unsigned short smoothed_metric; /* for route selection */ time_t smoothed_metric_time; short installed; unsigned char channels[DIVERSITY_HOPS]; struct babel_route *next; }; struct route_stream; extern struct babel_route **routes; extern int kernel_metric; extern enum babel_diversity diversity_kind; extern int diversity_factor; extern int keep_unfeasible; extern int smoothing_half_life; static inline int route_metric(const struct babel_route *route) { int m = (int)route->refmetric + route->cost + route->add_metric; return MIN(m, INFINITY); } static inline int route_metric_noninterfering(const struct babel_route *route) { int m = (int)route->refmetric + (diversity_factor * route->cost + 128) / 256 + route->add_metric; m = MAX(m, route->refmetric + 1); return MIN(m, INFINITY); } struct babel_route *find_route(const unsigned char *prefix, unsigned char plen, struct neighbour *neigh, const unsigned char *nexthop); struct babel_route *find_installed_route(const unsigned char *prefix, unsigned char plen); int installed_routes_estimate(void); void flush_route(struct babel_route *route); void flush_all_routes(void); void flush_neighbour_routes(struct neighbour *neigh); void flush_interface_routes(struct interface *ifp, int v4only); struct route_stream *route_stream(int installed); struct babel_route *route_stream_next(struct route_stream *stream); void route_stream_done(struct route_stream *stream); void install_route(struct babel_route *route); void uninstall_route(struct babel_route *route); int route_feasible(struct babel_route *route); int route_old(struct babel_route *route); int route_expired(struct babel_route *route); int route_interferes(struct babel_route *route, struct interface *ifp); int update_feasible(struct source *src, unsigned short seqno, unsigned short refmetric); void change_smoothing_half_life(int half_life); int route_smoothed_metric(struct babel_route *route); struct babel_route *find_best_route(const unsigned char *prefix, unsigned char plen, int feasible, struct neighbour *exclude); struct babel_route *install_best_route(const unsigned char prefix[16], unsigned char plen); void update_neighbour_metric(struct neighbour *neigh, int change); void update_interface_metric(struct interface *ifp); void update_route_metric(struct babel_route *route); struct babel_route *update_route(const unsigned char *id, const unsigned char *prefix, unsigned char plen, unsigned short seqno, unsigned short refmetric, unsigned short interval, struct neighbour *neigh, const unsigned char *nexthop, const unsigned char *channels, int channels_len); void retract_neighbour_routes(struct neighbour *neigh); void send_unfeasible_request(struct neighbour *neigh, int force, unsigned short seqno, unsigned short metric, struct source *src); void send_triggered_update(struct babel_route *route, struct source *oldsrc, unsigned oldmetric); void route_changed(struct babel_route *route, struct source *oldsrc, unsigned short oldmetric); void route_lost(struct source *src, unsigned oldmetric); void expire_routes(void); #endif frr-7.2.1/babeld/source.c0000644000000000000000000001060113610377563012067 00000000000000/* Copyright (c) 2007, 2008 by Juliusz Chroboczek Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions: The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. */ #ifdef HAVE_CONFIG_H #include "config.h" #endif #include #include #include #include #include "babel_main.h" #include "babeld.h" #include "util.h" #include "source.h" #include "babel_interface.h" #include "route.h" #include "babel_errors.h" struct source *srcs = NULL; struct source* find_source(const unsigned char *id, const unsigned char *p, unsigned char plen, int create, unsigned short seqno) { struct source *src; for(src = srcs; src; src = src->next) { /* This should really be a hash table. For now, check the last byte first. */ if(src->id[7] != id[7]) continue; if(memcmp(src->id, id, 8) != 0) continue; if(src->plen != plen) continue; if(memcmp(src->prefix, p, 16) == 0) return src; } if(!create) return NULL; src = malloc(sizeof(struct source)); if(src == NULL) { flog_err(EC_BABEL_MEMORY, "malloc(source): %s", safe_strerror(errno)); return NULL; } memcpy(src->id, id, 8); memcpy(src->prefix, p, 16); src->plen = plen; src->seqno = seqno; src->metric = INFINITY; src->time = babel_now.tv_sec; src->route_count = 0; src->next = srcs; srcs = src; return src; } struct source * retain_source(struct source *src) { assert(src->route_count < 0xffff); src->route_count++; return src; } void release_source(struct source *src) { assert(src->route_count > 0); src->route_count--; } int flush_source(struct source *src) { if(src->route_count > 0) /* The source is in use by a route. */ return 0; if(srcs == src) { srcs = src->next; } else { struct source *previous = srcs; while(previous->next != src) previous = previous->next; previous->next = src->next; } free(src); return 1; } void update_source(struct source *src, unsigned short seqno, unsigned short metric) { if(metric >= INFINITY) return; /* If a source is expired, pretend that it doesn't exist and update it unconditionally. This makes ensures that old data will eventually be overridden, and prevents us from getting stuck if a router loses its sequence number. */ if(src->time < babel_now.tv_sec - SOURCE_GC_TIME || seqno_compare(src->seqno, seqno) < 0 || (src->seqno == seqno && src->metric > metric)) { src->seqno = seqno; src->metric = metric; } src->time = babel_now.tv_sec; } void expire_sources(void) { struct source *src; src = srcs; while(src) { if(src->time > babel_now.tv_sec) /* clock stepped */ src->time = babel_now.tv_sec; if(src->time < babel_now.tv_sec - SOURCE_GC_TIME) { struct source *old = src; src = src->next; flush_source(old); continue; } src = src->next; } } void check_sources_released(void) { struct source *src; for(src = srcs; src; src = src->next) { if(src->route_count != 0) fprintf(stderr, "Warning: source %s %s has refcount %d.\n", format_eui64(src->id), format_prefix(src->prefix, src->plen), (int)src->route_count); } } frr-7.2.1/babeld/source.h0000644000000000000000000000354713610377563012107 00000000000000/* Copyright (c) 2007, 2008 by Juliusz Chroboczek Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions: The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. */ #ifndef BABEL_SOURCE_H #define BABEL_SOURCE_H #define SOURCE_GC_TIME 200 struct source { struct source *next; unsigned char id[8]; unsigned char prefix[16]; unsigned char plen; unsigned short seqno; unsigned short metric; unsigned short route_count; time_t time; }; struct source *find_source(const unsigned char *id, const unsigned char *p, unsigned char plen, int create, unsigned short seqno); struct source *retain_source(struct source *src); void release_source(struct source *src); int flush_source(struct source *src); void update_source(struct source *src, unsigned short seqno, unsigned short metric); void expire_sources(void); void check_sources_released(void); #endif frr-7.2.1/babeld/subdir.am0000644000000000000000000000204513610377563012235 00000000000000# # babeld # if BABELD noinst_LIBRARIES += babeld/libbabel.a sbin_PROGRAMS += babeld/babeld dist_examples_DATA += babeld/babeld.conf.sample vtysh_scan += \ $(top_srcdir)/babeld/babel_interface.c \ $(top_srcdir)/babeld/babel_zebra.c \ $(top_srcdir)/babeld/babeld.c \ # end endif babeld_libbabel_a_SOURCES = \ babeld/babel_errors.c \ babeld/babel_filter.c \ babeld/babel_interface.c \ babeld/babel_zebra.c \ babeld/babeld.c \ babeld/kernel.c \ babeld/message.c \ babeld/neighbour.c \ babeld/net.c \ babeld/resend.c \ babeld/route.c \ babeld/source.c \ babeld/util.c \ babeld/xroute.c \ # end noinst_HEADERS += \ babeld/babel_errors.h \ babeld/babel_filter.h \ babeld/babel_interface.h \ babeld/babel_main.h \ babeld/babel_zebra.h \ babeld/babeld.h \ babeld/kernel.h \ babeld/message.h \ babeld/neighbour.h \ babeld/net.h \ babeld/resend.h \ babeld/route.h \ babeld/source.h \ babeld/util.h \ babeld/xroute.h \ # end babeld_babeld_SOURCES = babeld/babel_main.c babeld_babeld_LDADD = babeld/libbabel.a lib/libfrr.la $(LIBCAP) frr-7.2.1/babeld/util.c0000644000000000000000000002465113610377563011556 00000000000000/* Copyright (c) 2007, 2008 by Juliusz Chroboczek Copyright 2011 by Matthieu Boutier and Juliusz Chroboczek Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions: The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. */ #ifdef HAVE_CONFIG_H #include "config.h" #endif #include #include #include #include #include #include #include #include #include #include #include #include #include "babel_main.h" #include "babeld.h" #include "util.h" int roughly(int value) { if(value < 0) return -roughly(-value); else if(value <= 1) return value; else return value * 3 / 4 + random() % (value / 2); } /* d = s1 - s2 */ void timeval_minus(struct timeval *d, const struct timeval *s1, const struct timeval *s2) { if(s1->tv_usec >= s2->tv_usec) { d->tv_usec = s1->tv_usec - s2->tv_usec; d->tv_sec = s1->tv_sec - s2->tv_sec; } else { d->tv_usec = s1->tv_usec + 1000000 - s2->tv_usec; d->tv_sec = s1->tv_sec - s2->tv_sec - 1; } } unsigned timeval_minus_msec(const struct timeval *s1, const struct timeval *s2) { if(s1->tv_sec < s2->tv_sec) return 0; /* Avoid overflow. */ if(s1->tv_sec - s2->tv_sec > 2000000) return 2000000000; if(s1->tv_sec > s2->tv_sec) return (unsigned)((unsigned)(s1->tv_sec - s2->tv_sec) * 1000 + ((int)s1->tv_usec - s2->tv_usec) / 1000); if(s1->tv_usec <= s2->tv_usec) return 0; return (unsigned)(s1->tv_usec - s2->tv_usec) / 1000u; } /* d = s + msecs */ void timeval_add_msec(struct timeval *d, const struct timeval *s, int msecs) { int usecs; d->tv_sec = s->tv_sec + msecs / 1000; usecs = s->tv_usec + (msecs % 1000) * 1000; if(usecs < 1000000) { d->tv_usec = usecs; } else { d->tv_usec = usecs - 1000000; d->tv_sec++; } } void set_timeout(struct timeval *timeout, int msecs) { timeval_add_msec(timeout, &babel_now, roughly(msecs)); } /* returns <0 if "s1" < "s2", etc. */ int timeval_compare(const struct timeval *s1, const struct timeval *s2) { if(s1->tv_sec < s2->tv_sec) return -1; else if(s1->tv_sec > s2->tv_sec) return 1; else if(s1->tv_usec < s2->tv_usec) return -1; else if(s1->tv_usec > s2->tv_usec) return 1; else return 0; } /* set d at min(d, s) */ /* {0, 0} represents infinity */ void timeval_min(struct timeval *d, const struct timeval *s) { if(s->tv_sec == 0) return; if(d->tv_sec == 0 || timeval_compare(d, s) > 0) { *d = *s; } } /* set d to min(d, x) with x in [secs, secs+1] */ void timeval_min_sec(struct timeval *d, time_t secs) { if(d->tv_sec == 0 || d->tv_sec > secs) { d->tv_sec = secs; d->tv_usec = random() % 1000000; } } /* parse a float value in second and return the corresponding mili-seconds. For example: parse_msec("12.342345") returns 12342 */ int parse_msec(const char *string) { unsigned int in, fl; int i, j; in = fl = 0; i = 0; while(string[i] == ' ' || string[i] == '\t') i++; while(string[i] >= '0' && string[i] <= '9') { in = in * 10 + string[i] - '0'; i++; } if(string[i] == '.') { i++; j = 0; while(string[i] >= '0' && string[i] <= '9') { fl = fl * 10 + string[i] - '0'; i++; j++; } while(j > 3) { fl /= 10; j--; } while(j < 3) { fl *= 10; j++; } } while(string[i] == ' ' || string[i] == '\t') i++; if(string[i] == '\0') return in * 1000 + fl; return -1; } /* There's no good name for a positive int in C, call it nat. */ int parse_nat(const char *string) { long l; char *end; l = strtol(string, &end, 0); while(*end == ' ' || *end == '\t') end++; if(*end != '\0') return -1; if(l < 0 || l > INT_MAX) return -1; return (int)l; } int in_prefix(const unsigned char *restrict address, const unsigned char *restrict prefix, unsigned char plen) { unsigned char m; if(plen > 128) plen = 128; if(memcmp(address, prefix, plen / 8) != 0) return 0; if(plen % 8 == 0) return 1; m = 0xFF << (8 - (plen % 8)); return ((address[plen / 8] & m) == (prefix[plen / 8] & m)); } unsigned char * mask_prefix(unsigned char *restrict ret, const unsigned char *restrict prefix, unsigned char plen) { if(plen >= 128) { memcpy(ret, prefix, 16); return ret; } memset(ret, 0, 16); memcpy(ret, prefix, plen / 8); if(plen % 8 != 0) ret[plen / 8] = (prefix[plen / 8] & ((0xFF << (8 - (plen % 8))) & 0xFF)); return ret; } const unsigned char v4prefix[16] = {0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0xFF, 0xFF, 0, 0, 0, 0 }; static const unsigned char llprefix[16] = {0xFE, 0x80}; const char * format_address(const unsigned char *address) { static char buf[4][INET6_ADDRSTRLEN]; static int i = 0; i = (i + 1) % 4; if(v4mapped(address)) inet_ntop(AF_INET, address + 12, buf[i], INET6_ADDRSTRLEN); else inet_ntop(AF_INET6, address, buf[i], INET6_ADDRSTRLEN); return buf[i]; } const char * format_prefix(const unsigned char *prefix, unsigned char plen) { static char buf[4][INET6_ADDRSTRLEN + 4]; static int i = 0; int n; i = (i + 1) % 4; if(plen >= 96 && v4mapped(prefix)) { inet_ntop(AF_INET, prefix + 12, buf[i], INET6_ADDRSTRLEN); n = strlen(buf[i]); snprintf(buf[i] + n, INET6_ADDRSTRLEN + 4 - n, "/%d", plen - 96); } else { inet_ntop(AF_INET6, prefix, buf[i], INET6_ADDRSTRLEN); n = strlen(buf[i]); snprintf(buf[i] + n, INET6_ADDRSTRLEN + 4 - n, "/%d", plen); } return buf[i]; } const char * format_eui64(const unsigned char *eui) { static char buf[4][28]; static int i = 0; i = (i + 1) % 4; snprintf(buf[i], 28, "%02x:%02x:%02x:%02x:%02x:%02x:%02x:%02x", eui[0], eui[1], eui[2], eui[3], eui[4], eui[5], eui[6], eui[7]); return buf[i]; } const char * format_thousands(unsigned int value) { static char buf[4][15]; static int i = 0; i = (i + 1) % 4; snprintf(buf[i], 15, "%u.%.3u", value / 1000, value % 1000); return buf[i]; } int parse_address(const char *address, unsigned char *addr_r, int *af_r) { struct in_addr ina; struct in6_addr ina6; int rc; rc = inet_pton(AF_INET, address, &ina); if(rc > 0) { v4tov6(addr_r, (const unsigned char *)&ina); if(af_r) *af_r = AF_INET; return 0; } rc = inet_pton(AF_INET6, address, &ina6); if(rc > 0) { memcpy(addr_r, &ina6, 16); if(af_r) *af_r = AF_INET6; return 0; } return -1; } int parse_eui64(const char *eui, unsigned char *eui_r) { int n; n = sscanf(eui, "%02hhx:%02hhx:%02hhx:%02hhx:%02hhx:%02hhx:%02hhx:%02hhx", &eui_r[0], &eui_r[1], &eui_r[2], &eui_r[3], &eui_r[4], &eui_r[5], &eui_r[6], &eui_r[7]); if(n == 8) return 0; n = sscanf(eui, "%02hhx-%02hhx-%02hhx-%02hhx-%02hhx-%02hhx-%02hhx-%02hhx", &eui_r[0], &eui_r[1], &eui_r[2], &eui_r[3], &eui_r[4], &eui_r[5], &eui_r[6], &eui_r[7]); if(n == 8) return 0; n = sscanf(eui, "%02hhx:%02hhx:%02hhx:%02hhx:%02hhx:%02hhx", &eui_r[0], &eui_r[1], &eui_r[2], &eui_r[5], &eui_r[6], &eui_r[7]); if(n == 6) { eui_r[3] = 0xFF; eui_r[4] = 0xFE; return 0; } return -1; } int wait_for_fd(int direction, int fd, int msecs) { fd_set fds; int rc; struct timeval tv; tv.tv_sec = msecs / 1000; tv.tv_usec = (msecs % 1000) * 1000; FD_ZERO(&fds); FD_SET(fd, &fds); if(direction) rc = select(fd + 1, NULL, &fds, NULL, &tv); else rc = select(fd + 1, &fds, NULL, NULL, &tv); return rc; } int martian_prefix(const unsigned char *prefix, int plen) { return (plen >= 8 && prefix[0] == 0xFF) || (plen >= 10 && prefix[0] == 0xFE && (prefix[1] & 0xC0) == 0x80) || (plen >= 128 && memcmp(prefix, zeroes, 15) == 0 && (prefix[15] == 0 || prefix[15] == 1)) || (plen >= 96 && v4mapped(prefix) && ((plen >= 104 && (prefix[12] == 127 || prefix[12] == 0)) || (plen >= 100 && (prefix[12] & 0xE0) == 0xE0))); } int linklocal(const unsigned char *address) { return memcmp(address, llprefix, 8) == 0; } int v4mapped(const unsigned char *address) { return memcmp(address, v4prefix, 12) == 0; } void v4tov6(unsigned char *dst, const unsigned char *src) { memcpy(dst, v4prefix, 12); memcpy(dst + 12, src, 4); } void inaddr_to_uchar(unsigned char *dest, const struct in_addr *src) { v4tov6(dest, (const unsigned char *)src); assert(v4mapped(dest)); } void uchar_to_inaddr(struct in_addr *dest, const unsigned char *src) { assert(v4mapped(src)); memcpy(dest, src + 12, 4); } void in6addr_to_uchar(unsigned char *dest, const struct in6_addr *src) { memcpy(dest, src, 16); } void uchar_to_in6addr(struct in6_addr *dest, const unsigned char *src) { memcpy(dest, src, 16); } int daemonise(void) { int rc; fflush(stdout); fflush(stderr); rc = fork(); if(rc < 0) return -1; if(rc > 0) exit(0); rc = setsid(); if(rc < 0) return -1; return 1; } frr-7.2.1/babeld/util.h0000644000000000000000000001402313610377563011553 00000000000000/* Copyright (c) 2007, 2008 by Juliusz Chroboczek Copyright 2011 by Matthieu Boutier and Juliusz Chroboczek Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions: The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. */ #ifndef BABEL_UTIL_H #define BABEL_UTIL_H #include "babeld.h" #include "babel_main.h" #include "log.h" #include "memory.h" DECLARE_MGROUP(BABELD) #if defined(i386) || defined(__mc68020__) || defined(__x86_64__) #define DO_NTOHS(_d, _s) do{ _d = ntohs(*(const unsigned short*)(_s)); }while(0) #define DO_NTOHL(_d, _s) do{ _d = ntohl(*(const unsigned*)(_s)); } while(0) #define DO_HTONS(_d, _s) do{ *(unsigned short*)(_d) = htons(_s); } while(0) #define DO_HTONL(_d, _s) do{ *(unsigned*)(_d) = htonl(_s); } while(0) /* Some versions of gcc seem to be buggy, and ignore the packed attribute. Disable this code until the issue is clarified. */ /* #elif defined __GNUC__*/ #else #define DO_NTOHS(_d, _s) \ do { short _dd; \ memcpy(&(_dd), (_s), 2); \ _d = ntohs(_dd); } while(0) #define DO_NTOHL(_d, _s) \ do { int _dd; \ memcpy(&(_dd), (_s), 4); \ _d = ntohl(_dd); } while(0) #define DO_HTONS(_d, _s) \ do { unsigned short _dd; \ _dd = htons(_s); \ memcpy((_d), &(_dd), 2); } while(0) #define DO_HTONL(_d, _s) \ do { unsigned _dd; \ _dd = htonl(_s); \ memcpy((_d), &(_dd), 4); } while(0) #endif static inline int seqno_compare(unsigned short s1, unsigned short s2) { if(s1 == s2) return 0; else return ((s2 - s1) & 0x8000) ? 1 : -1; } static inline short seqno_minus(unsigned short s1, unsigned short s2) { return (short)((s1 - s2) & 0xFFFF); } static inline unsigned short seqno_plus(unsigned short s, int plus) { return ((s + plus) & 0xFFFF); } /* Returns a time in microseconds on 32 bits (thus modulo 2^32, i.e. about 4295 seconds). */ static inline unsigned int time_us(const struct timeval t) { return (unsigned int) (t.tv_sec * 1000000 + t.tv_usec); } int roughly(int value); void timeval_minus(struct timeval *d, const struct timeval *s1, const struct timeval *s2); unsigned timeval_minus_msec(const struct timeval *s1, const struct timeval *s2) ATTRIBUTE ((pure)); void timeval_add_msec(struct timeval *d, const struct timeval *s, int msecs); void set_timeout (struct timeval *timeout, int msecs); int timeval_compare(const struct timeval *s1, const struct timeval *s2) ATTRIBUTE ((pure)); void timeval_min(struct timeval *d, const struct timeval *s); void timeval_min_sec(struct timeval *d, time_t secs); int parse_nat(const char *string) ATTRIBUTE ((pure)); int parse_msec(const char *string) ATTRIBUTE ((pure)); int in_prefix(const unsigned char *restrict address, const unsigned char *restrict prefix, unsigned char plen) ATTRIBUTE ((pure)); unsigned char *mask_prefix(unsigned char *restrict ret, const unsigned char *restrict prefix, unsigned char plen); const char *format_address(const unsigned char *address); const char *format_prefix(const unsigned char *address, unsigned char prefix); const char *format_eui64(const unsigned char *eui); const char *format_thousands(unsigned int value); int parse_address(const char *address, unsigned char *addr_r, int *af_r); int parse_eui64(const char *eui, unsigned char *eui_r); int wait_for_fd(int direction, int fd, int msecs); int martian_prefix(const unsigned char *prefix, int plen) ATTRIBUTE ((pure)); int linklocal(const unsigned char *address) ATTRIBUTE ((pure)); int v4mapped(const unsigned char *address) ATTRIBUTE ((pure)); void v4tov6(unsigned char *dst, const unsigned char *src); void inaddr_to_uchar(unsigned char *dest, const struct in_addr *src); void uchar_to_inaddr(struct in_addr *dest, const unsigned char *src); void in6addr_to_uchar(unsigned char *dest, const struct in6_addr *src); void uchar_to_in6addr(struct in6_addr *dest, const unsigned char *src); int daemonise(void); const unsigned char v4prefix[16]; /* If debugging is disabled, we want to avoid calling format_address for every omitted debugging message. So debug is a macro. But vararg macros are not portable. */ #if defined NO_DEBUG #if defined __STDC_VERSION__ && __STDC_VERSION__ >= 199901L #define debugf(...) do {} while(0) #elif defined __GNUC__ #define debugf(_args...) do {} while(0) #else static inline void debugf(int level, const char *format, ...) { return; } #endif #else /* NO_DEBUG */ /* some levels */ #define BABEL_DEBUG_COMMON (1 << 0) #define BABEL_DEBUG_KERNEL (1 << 1) #define BABEL_DEBUG_FILTER (1 << 2) #define BABEL_DEBUG_TIMEOUT (1 << 3) #define BABEL_DEBUG_IF (1 << 4) #define BABEL_DEBUG_ROUTE (1 << 5) #define BABEL_DEBUG_ALL (0xFFFF) #if defined __STDC_VERSION__ && __STDC_VERSION__ >= 199901L #define debugf(level, ...) \ do { \ if(UNLIKELY(debug & level)) zlog_debug(__VA_ARGS__); \ } while(0) #elif defined __GNUC__ #define debugf(level, _args...) \ do { \ if(UNLIKELY(debug & level)) zlog_debug(_args); \ } while(0) #else static inline void debugf(int level, const char *format, ...) { return; } #endif #endif /* NO_DEBUG */ #endif /* BABEL_UTIL_H */ frr-7.2.1/babeld/xroute.c0000644000000000000000000001512513610377563012123 00000000000000/* Copyright (c) 2007, 2008 by Juliusz Chroboczek Copyright 2011 by Matthieu Boutier and Juliusz Chroboczek Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions: The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. */ #include #include "if.h" #include "log.h" #include "babeld.h" #include "kernel.h" #include "neighbour.h" #include "message.h" #include "route.h" #include "xroute.h" #include "util.h" #include "babel_interface.h" static int xroute_add_new_route(unsigned char prefix[16], unsigned char plen, unsigned short metric, unsigned int ifindex, int proto, int send_updates); static struct xroute *xroutes; static int numxroutes = 0, maxxroutes = 0; /* Add redistributed route to Babel table. */ int babel_route_add (struct zapi_route *api) { unsigned char uchar_prefix[16]; switch (api->prefix.family) { case AF_INET: inaddr_to_uchar(uchar_prefix, &api->prefix.u.prefix4); debugf(BABEL_DEBUG_ROUTE, "Adding new ipv4 route coming from Zebra."); xroute_add_new_route(uchar_prefix, api->prefix.prefixlen + 96, api->metric, api->nexthops[0].ifindex, 0, 1); break; case AF_INET6: in6addr_to_uchar(uchar_prefix, &api->prefix.u.prefix6); debugf(BABEL_DEBUG_ROUTE, "Adding new ipv6 route coming from Zebra."); xroute_add_new_route(uchar_prefix, api->prefix.prefixlen, api->metric, api->nexthops[0].ifindex, 0, 1); break; } return 0; } /* Remove redistributed route from Babel table. */ int babel_route_delete (struct zapi_route *api) { unsigned char uchar_prefix[16]; struct xroute *xroute = NULL; switch (api->prefix.family) { case AF_INET: inaddr_to_uchar(uchar_prefix, &api->prefix.u.prefix4); xroute = find_xroute(uchar_prefix, api->prefix.prefixlen + 96); if (xroute != NULL) { debugf(BABEL_DEBUG_ROUTE, "Removing ipv4 route (from zebra)."); flush_xroute(xroute); } break; case AF_INET6: in6addr_to_uchar(uchar_prefix, &api->prefix.u.prefix6); xroute = find_xroute(uchar_prefix, api->prefix.prefixlen); if (xroute != NULL) { debugf(BABEL_DEBUG_ROUTE, "Removing ipv6 route (from zebra)."); flush_xroute(xroute); } break; } return 0; } struct xroute * find_xroute(const unsigned char *prefix, unsigned char plen) { int i; for(i = 0; i < numxroutes; i++) { if(xroutes[i].plen == plen && memcmp(xroutes[i].prefix, prefix, 16) == 0) return &xroutes[i]; } return NULL; } void flush_xroute(struct xroute *xroute) { int i; i = xroute - xroutes; assert(i >= 0 && i < numxroutes); if(i != numxroutes - 1) memcpy(xroutes + i, xroutes + numxroutes - 1, sizeof(struct xroute)); numxroutes--; VALGRIND_MAKE_MEM_UNDEFINED(xroutes + numxroutes, sizeof(struct xroute)); if(numxroutes == 0) { free(xroutes); xroutes = NULL; maxxroutes = 0; } else if(maxxroutes > 8 && numxroutes < maxxroutes / 4) { struct xroute *new_xroutes; int n = maxxroutes / 2; new_xroutes = realloc(xroutes, n * sizeof(struct xroute)); if(new_xroutes == NULL) return; xroutes = new_xroutes; maxxroutes = n; } } static int add_xroute(unsigned char prefix[16], unsigned char plen, unsigned short metric, unsigned int ifindex, int proto) { struct xroute *xroute = find_xroute(prefix, plen); if(xroute) { if(xroute->metric <= metric) return 0; xroute->metric = metric; return 1; } if(numxroutes >= maxxroutes) { struct xroute *new_xroutes; int n = maxxroutes < 1 ? 8 : 2 * maxxroutes; new_xroutes = xroutes == NULL ? malloc(n * sizeof(struct xroute)) : realloc(xroutes, n * sizeof(struct xroute)); if(new_xroutes == NULL) return -1; maxxroutes = n; xroutes = new_xroutes; } memcpy(xroutes[numxroutes].prefix, prefix, 16); xroutes[numxroutes].plen = plen; xroutes[numxroutes].metric = metric; xroutes[numxroutes].ifindex = ifindex; xroutes[numxroutes].proto = proto; numxroutes++; return 1; } /* Returns an overestimate of the number of xroutes. */ int xroutes_estimate(void) { return numxroutes; } struct xroute_stream { int index; }; struct xroute_stream * xroute_stream(void) { struct xroute_stream *stream = malloc(sizeof(struct xroute_stream)); if(stream == NULL) return NULL; stream->index = 0; return stream; } struct xroute * xroute_stream_next(struct xroute_stream *stream) { if(stream->index < numxroutes) return &xroutes[stream->index++]; else return NULL; } void xroute_stream_done(struct xroute_stream *stream) { free(stream); } /* add an xroute, verifying some conditions; return 0 if there is no changes */ static int xroute_add_new_route(unsigned char prefix[16], unsigned char plen, unsigned short metric, unsigned int ifindex, int proto, int send_updates) { int rc; if(martian_prefix(prefix, plen)) return 0; metric = redistribute_filter(prefix, plen, ifindex, proto); if(metric < INFINITY) { rc = add_xroute(prefix, plen, metric, ifindex, proto); if(rc > 0) { struct babel_route *route; route = find_installed_route(prefix, plen); if(route) uninstall_route(route); if(send_updates) send_update(NULL, 0, prefix, plen); return 1; } } return 0; } frr-7.2.1/babeld/xroute.h0000644000000000000000000000336713610377563012135 00000000000000/* Copyright (c) 2007, 2008 by Juliusz Chroboczek Copyright 2011 by Matthieu Boutier and Juliusz Chroboczek Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions: The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. */ #ifndef BABEL_XROUTE_H #define BABEL_XROUTE_H struct xroute { unsigned char prefix[16]; unsigned char plen; unsigned short metric; unsigned int ifindex; int proto; }; struct xroute_stream; struct xroute *find_xroute(const unsigned char *prefix, unsigned char plen); void flush_xroute(struct xroute *xroute); int babel_route_add (struct zapi_route *api); int babel_route_delete (struct zapi_route *api); int xroutes_estimate(void); struct xroute_stream *xroute_stream(void); struct xroute *xroute_stream_next(struct xroute_stream *stream); void xroute_stream_done(struct xroute_stream *stream); #endif /* BABEL_XROUTE_H */ frr-7.2.1/bfdd/0000755000000000000000000000000013610377563010173 500000000000000frr-7.2.1/bfdd/bfd.c0000644000000000000000000012310313610377563011012 00000000000000/********************************************************************* * Copyright 2013 Cumulus Networks, LLC. All rights reserved. * Copyright 2014,2015,2016,2017 Cumulus Networks, Inc. All rights reserved. * * This program is free software; you can redistribute it and/or modify it * under the terms of the GNU General Public License as published by the Free * Software Foundation; either version 2 of the License, or (at your option) * any later version. * * 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; see the file COPYING; if not, write to the Free Software * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA * * bfd.c: implements the BFD protocol. * * Authors * ------- * Shrijeet Mukherjee [shm@cumulusnetworks.com] * Kanna Rajagopal [kanna@cumulusnetworks.com] * Radhika Mahankali [Radhika@cumulusnetworks.com] */ #include #include "lib/jhash.h" #include "bfd.h" DEFINE_MTYPE_STATIC(BFDD, BFDD_CONFIG, "long-lived configuration memory") DEFINE_MTYPE_STATIC(BFDD, BFDD_SESSION_OBSERVER, "Session observer") DEFINE_MTYPE_STATIC(BFDD, BFDD_VRF, "BFD VRF") /* * Prototypes */ static uint32_t ptm_bfd_gen_ID(void); static void ptm_bfd_echo_xmt_TO(struct bfd_session *bfd); static struct bfd_session *bfd_find_disc(struct sockaddr_any *sa, uint32_t ldisc); static int bfd_session_update(struct bfd_session *bs, struct bfd_peer_cfg *bpc); static const char *get_diag_str(int diag); static void bs_admin_down_handler(struct bfd_session *bs, int nstate); static void bs_down_handler(struct bfd_session *bs, int nstate); static void bs_init_handler(struct bfd_session *bs, int nstate); static void bs_up_handler(struct bfd_session *bs, int nstate); /* Zeroed array with the size of an IPv6 address. */ struct in6_addr zero_addr; /* * Functions */ void gen_bfd_key(struct bfd_key *key, struct sockaddr_any *peer, struct sockaddr_any *local, bool mhop, const char *ifname, const char *vrfname) { memset(key, 0, sizeof(*key)); switch (peer->sa_sin.sin_family) { case AF_INET: key->family = AF_INET; memcpy(&key->peer, &peer->sa_sin.sin_addr, sizeof(peer->sa_sin.sin_addr)); memcpy(&key->local, &local->sa_sin.sin_addr, sizeof(local->sa_sin.sin_addr)); break; case AF_INET6: key->family = AF_INET6; memcpy(&key->peer, &peer->sa_sin6.sin6_addr, sizeof(peer->sa_sin6.sin6_addr)); memcpy(&key->local, &local->sa_sin6.sin6_addr, sizeof(local->sa_sin6.sin6_addr)); break; } key->mhop = mhop; if (ifname && ifname[0]) strlcpy(key->ifname, ifname, sizeof(key->ifname)); if (vrfname && vrfname[0]) strlcpy(key->vrfname, vrfname, sizeof(key->vrfname)); else strlcpy(key->vrfname, VRF_DEFAULT_NAME, sizeof(key->vrfname)); } struct bfd_session *bs_peer_find(struct bfd_peer_cfg *bpc) { struct bfd_session *bs; struct peer_label *pl; struct bfd_key key; /* Try to find label first. */ if (bpc->bpc_has_label) { pl = pl_find(bpc->bpc_label); if (pl != NULL) { bs = pl->pl_bs; return bs; } } /* Otherwise fallback to peer/local hash lookup. */ gen_bfd_key(&key, &bpc->bpc_peer, &bpc->bpc_local, bpc->bpc_mhop, bpc->bpc_localif, bpc->bpc_vrfname); return bfd_key_lookup(key); } /* * Starts a disabled BFD session. * * A session is disabled when the specified interface/VRF doesn't exist * yet. It might happen on FRR boot or with virtual interfaces. */ int bfd_session_enable(struct bfd_session *bs) { struct interface *ifp = NULL; struct vrf *vrf = NULL; int psock; /* * If the interface or VRF doesn't exist, then we must register * the session but delay its start. */ if (bs->key.vrfname[0]) { vrf = vrf_lookup_by_name(bs->key.vrfname); if (vrf == NULL) { log_error( "session-enable: specified VRF doesn't exists."); return 0; } } if (bs->key.ifname[0]) { if (vrf) ifp = if_lookup_by_name(bs->key.ifname, vrf->vrf_id); else ifp = if_lookup_by_name_all_vrf(bs->key.ifname); if (ifp == NULL) { log_error( "session-enable: specified interface doesn't exists."); return 0; } if (bs->key.ifname[0] && !vrf) { vrf = vrf_lookup_by_id(ifp->vrf_id); if (vrf == NULL) { log_error( "session-enable: specified VRF doesn't exists."); return 0; } } } /* Assign interface/VRF pointers. */ bs->vrf = vrf; if (bs->vrf == NULL) bs->vrf = vrf_lookup_by_id(VRF_DEFAULT); if (bs->key.ifname[0] && BFD_CHECK_FLAG(bs->flags, BFD_SESS_FLAG_MH) == 0) bs->ifp = ifp; /* Sanity check: don't leak open sockets. */ if (bs->sock != -1) { log_debug("session-enable: previous socket open"); close(bs->sock); bs->sock = -1; } /* * Get socket for transmitting control packets. Note that if we * could use the destination port (3784) for the source * port we wouldn't need a socket per session. */ if (BFD_CHECK_FLAG(bs->flags, BFD_SESS_FLAG_IPV6) == 0) { psock = bp_peer_socket(bs); if (psock == -1) return 0; } else { psock = bp_peer_socketv6(bs); if (psock == -1) return 0; } /* * We've got a valid socket, lets start the timers and the * protocol. */ bs->sock = psock; bfd_recvtimer_update(bs); ptm_bfd_start_xmt_timer(bs, false); return 0; } /* * Disabled a running BFD session. * * A session is disabled when the specified interface/VRF gets removed * (e.g. virtual interfaces). */ void bfd_session_disable(struct bfd_session *bs) { /* Free up socket resources. */ if (bs->sock != -1) { close(bs->sock); bs->sock = -1; } /* Disable all timers. */ bfd_recvtimer_delete(bs); bfd_xmttimer_delete(bs); ptm_bfd_echo_stop(bs); bs->vrf = NULL; bs->ifp = NULL; /* Set session down so it doesn't report UP and disabled. */ ptm_bfd_sess_dn(bs, BD_PATH_DOWN); } static uint32_t ptm_bfd_gen_ID(void) { uint32_t session_id; /* * RFC 5880, Section 6.8.1. recommends that we should generate * random session identification numbers. */ do { session_id = ((random() << 16) & 0xFFFF0000) | (random() & 0x0000FFFF); } while (session_id == 0 || bfd_id_lookup(session_id) != NULL); return session_id; } void ptm_bfd_start_xmt_timer(struct bfd_session *bfd, bool is_echo) { uint64_t jitter, xmt_TO; int maxpercent; xmt_TO = is_echo ? bfd->echo_xmt_TO : bfd->xmt_TO; /* * From section 6.5.2: trasmit interval should be randomly jittered * between * 75% and 100% of nominal value, unless detect_mult is 1, then should * be * between 75% and 90%. */ maxpercent = (bfd->detect_mult == 1) ? 16 : 26; jitter = (xmt_TO * (75 + (random() % maxpercent))) / 100; /* XXX remove that division above */ if (is_echo) bfd_echo_xmttimer_update(bfd, jitter); else bfd_xmttimer_update(bfd, jitter); } static void ptm_bfd_echo_xmt_TO(struct bfd_session *bfd) { /* Send the scheduled echo packet */ ptm_bfd_echo_snd(bfd); /* Restart the timer for next time */ ptm_bfd_start_xmt_timer(bfd, true); } void ptm_bfd_xmt_TO(struct bfd_session *bfd, int fbit) { /* Send the scheduled control packet */ ptm_bfd_snd(bfd, fbit); /* Restart the timer for next time */ ptm_bfd_start_xmt_timer(bfd, false); } void ptm_bfd_echo_stop(struct bfd_session *bfd) { bfd->echo_xmt_TO = 0; bfd->echo_detect_TO = 0; BFD_UNSET_FLAG(bfd->flags, BFD_SESS_FLAG_ECHO_ACTIVE); bfd_echo_xmttimer_delete(bfd); bfd_echo_recvtimer_delete(bfd); } void ptm_bfd_echo_start(struct bfd_session *bfd) { bfd->echo_detect_TO = (bfd->remote_detect_mult * bfd->echo_xmt_TO); if (bfd->echo_detect_TO > 0) ptm_bfd_echo_xmt_TO(bfd); } void ptm_bfd_sess_up(struct bfd_session *bfd) { int old_state = bfd->ses_state; bfd->local_diag = 0; bfd->ses_state = PTM_BFD_UP; monotime(&bfd->uptime); /* Connection is up, lets negotiate timers. */ bfd_set_polling(bfd); /* Start sending control packets with poll bit immediately. */ ptm_bfd_snd(bfd, 0); control_notify(bfd); if (old_state != bfd->ses_state) { bfd->stats.session_up++; log_info("state-change: [%s] %s -> %s", bs_to_string(bfd), state_list[old_state].str, state_list[bfd->ses_state].str); } } void ptm_bfd_sess_dn(struct bfd_session *bfd, uint8_t diag) { int old_state = bfd->ses_state; bfd->local_diag = diag; bfd->discrs.remote_discr = 0; bfd->ses_state = PTM_BFD_DOWN; bfd->polling = 0; bfd->demand_mode = 0; monotime(&bfd->downtime); /* * Only attempt to send if we have a valid socket: * this function might be called by session disablers and in * this case we won't have a valid socket (i.e. interface was * removed or VRF doesn't exist anymore). */ if (bfd->sock != -1) ptm_bfd_snd(bfd, 0); /* Slow down the control packets, the connection is down. */ bs_set_slow_timers(bfd); /* only signal clients when going from up->down state */ if (old_state == PTM_BFD_UP) control_notify(bfd); /* Stop echo packet transmission if they are active */ if (BFD_CHECK_FLAG(bfd->flags, BFD_SESS_FLAG_ECHO_ACTIVE)) ptm_bfd_echo_stop(bfd); if (old_state != bfd->ses_state) { bfd->stats.session_down++; log_info("state-change: [%s] %s -> %s reason:%s", bs_to_string(bfd), state_list[old_state].str, state_list[bfd->ses_state].str, get_diag_str(bfd->local_diag)); } } static struct bfd_session *bfd_find_disc(struct sockaddr_any *sa, uint32_t ldisc) { struct bfd_session *bs; bs = bfd_id_lookup(ldisc); if (bs == NULL) return NULL; switch (bs->key.family) { case AF_INET: if (memcmp(&sa->sa_sin.sin_addr, &bs->key.peer, sizeof(sa->sa_sin.sin_addr))) return NULL; break; case AF_INET6: if (memcmp(&sa->sa_sin6.sin6_addr, &bs->key.peer, sizeof(sa->sa_sin6.sin6_addr))) return NULL; break; } return bs; } struct bfd_session *ptm_bfd_sess_find(struct bfd_pkt *cp, struct sockaddr_any *peer, struct sockaddr_any *local, ifindex_t ifindex, vrf_id_t vrfid, bool is_mhop) { struct interface *ifp; struct vrf *vrf; struct bfd_key key; /* Find our session using the ID signaled by the remote end. */ if (cp->discrs.remote_discr) return bfd_find_disc(peer, ntohl(cp->discrs.remote_discr)); /* * Search for session without using discriminator. * * XXX: we can't trust `vrfid` because the VRF handling is not * properly implemented. Meanwhile we should use the interface * VRF to find out which one it belongs. */ ifp = if_lookup_by_index_all_vrf(ifindex); if (ifp == NULL) { if (vrfid != VRF_DEFAULT) vrf = vrf_lookup_by_id(vrfid); else vrf = NULL; } else vrf = vrf_lookup_by_id(ifp->vrf_id); gen_bfd_key(&key, peer, local, is_mhop, ifp ? ifp->name : NULL, vrf ? vrf->name : VRF_DEFAULT_NAME); /* XXX maybe remoteDiscr should be checked for remoteHeard cases. */ return bfd_key_lookup(key); } int bfd_xmt_cb(struct thread *t) { struct bfd_session *bs = THREAD_ARG(t); ptm_bfd_xmt_TO(bs, 0); return 0; } int bfd_echo_xmt_cb(struct thread *t) { struct bfd_session *bs = THREAD_ARG(t); if (bs->echo_xmt_TO > 0) ptm_bfd_echo_xmt_TO(bs); return 0; } /* Was ptm_bfd_detect_TO() */ int bfd_recvtimer_cb(struct thread *t) { struct bfd_session *bs = THREAD_ARG(t); switch (bs->ses_state) { case PTM_BFD_INIT: case PTM_BFD_UP: ptm_bfd_sess_dn(bs, BD_CONTROL_EXPIRED); bfd_recvtimer_update(bs); break; default: /* Second detect time expiration, zero remote discr (section * 6.5.1) */ bs->discrs.remote_discr = 0; break; } return 0; } /* Was ptm_bfd_echo_detect_TO() */ int bfd_echo_recvtimer_cb(struct thread *t) { struct bfd_session *bs = THREAD_ARG(t); switch (bs->ses_state) { case PTM_BFD_INIT: case PTM_BFD_UP: ptm_bfd_sess_dn(bs, BD_ECHO_FAILED); break; } return 0; } struct bfd_session *bfd_session_new(void) { struct bfd_session *bs; bs = XCALLOC(MTYPE_BFDD_CONFIG, sizeof(*bs)); bs->timers.desired_min_tx = BFD_DEFDESIREDMINTX; bs->timers.required_min_rx = BFD_DEFREQUIREDMINRX; bs->timers.required_min_echo = BFD_DEF_REQ_MIN_ECHO; bs->detect_mult = BFD_DEFDETECTMULT; bs->mh_ttl = BFD_DEF_MHOP_TTL; bs->ses_state = PTM_BFD_DOWN; /* Initiate connection with slow timers. */ bs_set_slow_timers(bs); /* Initiate remote settings as well. */ bs->remote_timers = bs->cur_timers; bs->remote_detect_mult = BFD_DEFDETECTMULT; bs->sock = -1; monotime(&bs->uptime); bs->downtime = bs->uptime; return bs; } int bfd_session_update_label(struct bfd_session *bs, const char *nlabel) { /* New label treatment: * - Check if the label is taken; * - Try to allocate the memory for it and register; */ if (bs->pl == NULL) { if (pl_find(nlabel) != NULL) { /* Someone is already using it. */ return -1; } if (pl_new(nlabel, bs) == NULL) return -1; return 0; } /* * Test label change consistency: * - Do nothing if it's the same label; * - Check if the future label is already taken; * - Change label; */ if (strcmp(nlabel, bs->pl->pl_label) == 0) return -1; if (pl_find(nlabel) != NULL) return -1; strlcpy(bs->pl->pl_label, nlabel, sizeof(bs->pl->pl_label)); return 0; } static void _bfd_session_update(struct bfd_session *bs, struct bfd_peer_cfg *bpc) { if (bpc->bpc_echo) { /* Check if echo mode is already active. */ if (BFD_CHECK_FLAG(bs->flags, BFD_SESS_FLAG_ECHO)) goto skip_echo; BFD_SET_FLAG(bs->flags, BFD_SESS_FLAG_ECHO); /* Activate/update echo receive timeout timer. */ bs_echo_timer_handler(bs); } else { /* Check if echo mode is already disabled. */ if (!BFD_CHECK_FLAG(bs->flags, BFD_SESS_FLAG_ECHO)) goto skip_echo; BFD_UNSET_FLAG(bs->flags, BFD_SESS_FLAG_ECHO); ptm_bfd_echo_stop(bs); } skip_echo: if (bpc->bpc_has_txinterval) bs->timers.desired_min_tx = bpc->bpc_txinterval * 1000; if (bpc->bpc_has_recvinterval) bs->timers.required_min_rx = bpc->bpc_recvinterval * 1000; if (bpc->bpc_has_detectmultiplier) bs->detect_mult = bpc->bpc_detectmultiplier; if (bpc->bpc_has_echointerval) bs->timers.required_min_echo = bpc->bpc_echointerval * 1000; if (bpc->bpc_has_label) bfd_session_update_label(bs, bpc->bpc_label); if (bpc->bpc_shutdown) { /* Check if already shutdown. */ if (BFD_CHECK_FLAG(bs->flags, BFD_SESS_FLAG_SHUTDOWN)) return; BFD_SET_FLAG(bs->flags, BFD_SESS_FLAG_SHUTDOWN); /* Disable all events. */ bfd_recvtimer_delete(bs); bfd_echo_recvtimer_delete(bs); bfd_xmttimer_delete(bs); bfd_echo_xmttimer_delete(bs); /* Change and notify state change. */ bs->ses_state = PTM_BFD_ADM_DOWN; control_notify(bs); /* Don't try to send packets with a disabled session. */ if (bs->sock != -1) ptm_bfd_snd(bs, 0); } else { /* Check if already working. */ if (!BFD_CHECK_FLAG(bs->flags, BFD_SESS_FLAG_SHUTDOWN)) return; BFD_UNSET_FLAG(bs->flags, BFD_SESS_FLAG_SHUTDOWN); /* Change and notify state change. */ bs->ses_state = PTM_BFD_DOWN; control_notify(bs); /* Enable all timers. */ bfd_recvtimer_update(bs); bfd_xmttimer_update(bs, bs->xmt_TO); } if (bpc->bpc_cbit) { if (BFD_CHECK_FLAG(bs->flags, BFD_SESS_FLAG_CBIT)) return; BFD_SET_FLAG(bs->flags, BFD_SESS_FLAG_CBIT); } else { if (!BFD_CHECK_FLAG(bs->flags, BFD_SESS_FLAG_CBIT)) return; BFD_UNSET_FLAG(bs->flags, BFD_SESS_FLAG_CBIT); } } static int bfd_session_update(struct bfd_session *bs, struct bfd_peer_cfg *bpc) { /* User didn't want to update, return failure. */ if (bpc->bpc_createonly) return -1; _bfd_session_update(bs, bpc); control_notify_config(BCM_NOTIFY_CONFIG_UPDATE, bs); return 0; } void bfd_session_free(struct bfd_session *bs) { struct bfd_session_observer *bso; bfd_session_disable(bs); bfd_key_delete(bs->key); bfd_id_delete(bs->discrs.my_discr); /* Remove observer if any. */ TAILQ_FOREACH(bso, &bglobal.bg_obslist, bso_entry) { if (bso->bso_bs != bs) continue; break; } if (bso != NULL) bs_observer_del(bso); pl_free(bs->pl); XFREE(MTYPE_BFDD_CONFIG, bs); } struct bfd_session *ptm_bfd_sess_new(struct bfd_peer_cfg *bpc) { struct bfd_session *bfd, *l_bfd; /* check to see if this needs a new session */ l_bfd = bs_peer_find(bpc); if (l_bfd) { /* Requesting a duplicated peer means update configuration. */ if (bfd_session_update(l_bfd, bpc) == 0) return l_bfd; else return NULL; } /* Get BFD session storage with its defaults. */ bfd = bfd_session_new(); if (bfd == NULL) { log_error("session-new: allocation failed"); return NULL; } /* * Store interface/VRF name in case we need to delay session * start. See `bfd_session_enable` for more information. */ if (bpc->bpc_has_localif) strlcpy(bfd->key.ifname, bpc->bpc_localif, sizeof(bfd->key.ifname)); if (bpc->bpc_has_vrfname) strlcpy(bfd->key.vrfname, bpc->bpc_vrfname, sizeof(bfd->key.vrfname)); else strlcpy(bfd->key.vrfname, VRF_DEFAULT_NAME, sizeof(bfd->key.vrfname)); /* Copy remaining data. */ if (bpc->bpc_ipv4 == false) BFD_SET_FLAG(bfd->flags, BFD_SESS_FLAG_IPV6); bfd->key.family = (bpc->bpc_ipv4) ? AF_INET : AF_INET6; switch (bfd->key.family) { case AF_INET: memcpy(&bfd->key.peer, &bpc->bpc_peer.sa_sin.sin_addr, sizeof(bpc->bpc_peer.sa_sin.sin_addr)); memcpy(&bfd->key.local, &bpc->bpc_local.sa_sin.sin_addr, sizeof(bpc->bpc_local.sa_sin.sin_addr)); break; case AF_INET6: memcpy(&bfd->key.peer, &bpc->bpc_peer.sa_sin6.sin6_addr, sizeof(bpc->bpc_peer.sa_sin6.sin6_addr)); memcpy(&bfd->key.local, &bpc->bpc_local.sa_sin6.sin6_addr, sizeof(bpc->bpc_local.sa_sin6.sin6_addr)); break; default: assert(1); break; } if (bpc->bpc_mhop) BFD_SET_FLAG(bfd->flags, BFD_SESS_FLAG_MH); bfd->key.mhop = bpc->bpc_mhop; if (bs_registrate(bfd) == NULL) return NULL; /* Apply other configurations. */ _bfd_session_update(bfd, bpc); return bfd; } struct bfd_session *bs_registrate(struct bfd_session *bfd) { /* Registrate session into data structures. */ bfd_key_insert(bfd); bfd->discrs.my_discr = ptm_bfd_gen_ID(); bfd_id_insert(bfd); /* Try to enable session and schedule for packet receive/send. */ if (bfd_session_enable(bfd) == -1) { /* Unrecoverable failure, remove the session/peer. */ bfd_session_free(bfd); return NULL; } /* Add observer if we have moving parts. */ if (bfd->key.ifname[0] || bfd->key.vrfname[0] || bfd->sock == -1) bs_observer_add(bfd); log_info("session-new: %s", bs_to_string(bfd)); control_notify_config(BCM_NOTIFY_CONFIG_ADD, bfd); return bfd; } int ptm_bfd_sess_del(struct bfd_peer_cfg *bpc) { struct bfd_session *bs; /* Find session and call free(). */ bs = bs_peer_find(bpc); if (bs == NULL) return -1; /* This pointer is being referenced, don't let it be deleted. */ if (bs->refcount > 0) { log_error("session-delete: refcount failure: %" PRIu64 " references", bs->refcount); return -1; } log_info("session-delete: %s", bs_to_string(bs)); control_notify_config(BCM_NOTIFY_CONFIG_DELETE, bs); bfd_session_free(bs); return 0; } void bfd_set_polling(struct bfd_session *bs) { /* * Start polling procedure: the only timers that require polling * to change value without losing connection are: * * - Desired minimum transmission interval; * - Required minimum receive interval; * * RFC 5880, Section 6.8.3. */ bs->polling = 1; } /* * bs__handler() functions implement the BFD state machine * transition mechanism. `` is the current session state and * the parameter `nstate` is the peer new state. */ static void bs_admin_down_handler(struct bfd_session *bs __attribute__((__unused__)), int nstate __attribute__((__unused__))) { /* * We are administratively down, there is no state machine * handling. */ } static void bs_down_handler(struct bfd_session *bs, int nstate) { switch (nstate) { case PTM_BFD_ADM_DOWN: /* * Remote peer doesn't want to talk, so lets keep the * connection down. */ case PTM_BFD_UP: /* Peer can't be up yet, wait it go to 'init' or 'down'. */ break; case PTM_BFD_DOWN: /* * Remote peer agreed that the path is down, lets try to * bring it up. */ bs->ses_state = PTM_BFD_INIT; break; case PTM_BFD_INIT: /* * Remote peer told us his path is up, lets turn * activate the session. */ ptm_bfd_sess_up(bs); break; default: log_debug("state-change: unhandled neighbor state: %d", nstate); break; } } static void bs_init_handler(struct bfd_session *bs, int nstate) { switch (nstate) { case PTM_BFD_ADM_DOWN: /* * Remote peer doesn't want to talk, so lets make the * connection down. */ bs->ses_state = PTM_BFD_DOWN; break; case PTM_BFD_DOWN: /* Remote peer hasn't moved to first stage yet. */ break; case PTM_BFD_INIT: case PTM_BFD_UP: /* We agreed on the settings and the path is up. */ ptm_bfd_sess_up(bs); break; default: log_debug("state-change: unhandled neighbor state: %d", nstate); break; } } static void bs_up_handler(struct bfd_session *bs, int nstate) { switch (nstate) { case PTM_BFD_ADM_DOWN: case PTM_BFD_DOWN: /* Peer lost or asked to shutdown connection. */ ptm_bfd_sess_dn(bs, BD_NEIGHBOR_DOWN); break; case PTM_BFD_INIT: case PTM_BFD_UP: /* Path is up and working. */ break; default: log_debug("state-change: unhandled neighbor state: %d", nstate); break; } } void bs_state_handler(struct bfd_session *bs, int nstate) { switch (bs->ses_state) { case PTM_BFD_ADM_DOWN: bs_admin_down_handler(bs, nstate); break; case PTM_BFD_DOWN: bs_down_handler(bs, nstate); break; case PTM_BFD_INIT: bs_init_handler(bs, nstate); break; case PTM_BFD_UP: bs_up_handler(bs, nstate); break; default: log_debug("state-change: [%s] is in invalid state: %d", bs_to_string(bs), nstate); break; } } /* * Handles echo timer manipulation after updating timer. */ void bs_echo_timer_handler(struct bfd_session *bs) { uint32_t old_timer; /* * Before doing any echo handling, check if it is possible to * use it. * * - Check for `echo-mode` configuration. * - Check that we are not using multi hop (RFC 5883, * Section 3). * - Check that we are already at the up state. */ if (BFD_CHECK_FLAG(bs->flags, BFD_SESS_FLAG_ECHO) == 0 || BFD_CHECK_FLAG(bs->flags, BFD_SESS_FLAG_MH) || bs->ses_state != PTM_BFD_UP) return; /* Remote peer asked to stop echo. */ if (bs->remote_timers.required_min_echo == 0) { if (BFD_CHECK_FLAG(bs->flags, BFD_SESS_FLAG_ECHO_ACTIVE)) ptm_bfd_echo_stop(bs); return; } /* * Calculate the echo transmission timer: we must not send * echo packets faster than the minimum required time * announced by the remote system. * * RFC 5880, Section 6.8.9. */ old_timer = bs->echo_xmt_TO; if (bs->remote_timers.required_min_echo > bs->timers.required_min_echo) bs->echo_xmt_TO = bs->remote_timers.required_min_echo; else bs->echo_xmt_TO = bs->timers.required_min_echo; if (BFD_CHECK_FLAG(bs->flags, BFD_SESS_FLAG_ECHO_ACTIVE) == 0 || old_timer != bs->echo_xmt_TO) ptm_bfd_echo_start(bs); } /* * RFC 5880 Section 6.5. * * When a BFD control packet with the final bit is received, we must * update the session parameters. */ void bs_final_handler(struct bfd_session *bs) { /* Start using our new timers. */ bs->cur_timers.desired_min_tx = bs->timers.desired_min_tx; bs->cur_timers.required_min_rx = bs->timers.required_min_rx; /* * TODO: demand mode. See RFC 5880 Section 6.1. * * When using demand mode we must disable the detection timer * for lost control packets. */ if (bs->demand_mode) { /* Notify watchers about changed timers. */ control_notify_config(BCM_NOTIFY_CONFIG_UPDATE, bs); return; } /* * Calculate detection time based on new timers. * * Transmission calculation: * We must respect the RequiredMinRxInterval from the remote * system: if our desired transmission timer is more than the * minimum receive rate, then we must lower it to at least the * minimum receive interval. * * RFC 5880, Section 6.8.3. */ if (bs->timers.desired_min_tx > bs->remote_timers.required_min_rx) bs->xmt_TO = bs->remote_timers.required_min_rx; else bs->xmt_TO = bs->timers.desired_min_tx; /* Apply new transmission timer immediately. */ ptm_bfd_start_xmt_timer(bs, false); /* * Detection timeout calculation: * The minimum detection timeout is the remote detection * multipler (number of packets to be missed) times the agreed * transmission interval. * * RFC 5880, Section 6.8.4. * * TODO: support sending/counting more packets inside detection * timeout. */ if (bs->remote_timers.required_min_rx > bs->timers.desired_min_tx) bs->detect_TO = bs->remote_detect_mult * bs->remote_timers.required_min_rx; else bs->detect_TO = bs->remote_detect_mult * bs->timers.desired_min_tx; /* Apply new receive timer immediately. */ bfd_recvtimer_update(bs); /* Notify watchers about changed timers. */ control_notify_config(BCM_NOTIFY_CONFIG_UPDATE, bs); } void bs_set_slow_timers(struct bfd_session *bs) { /* * BFD connection must use slow timers before going up or after * losing connectivity to avoid wasting bandwidth. * * RFC 5880, Section 6.8.3. */ bs->cur_timers.desired_min_tx = BFD_DEF_SLOWTX; bs->cur_timers.required_min_rx = BFD_DEF_SLOWTX; bs->cur_timers.required_min_echo = 0; /* Set the appropriated timeouts for slow connection. */ bs->detect_TO = (BFD_DEFDETECTMULT * BFD_DEF_SLOWTX); bs->xmt_TO = BFD_DEF_SLOWTX; } /* * Helper functions. */ static const char *get_diag_str(int diag) { for (int i = 0; diag_list[i].str; i++) { if (diag_list[i].type == diag) return diag_list[i].str; } return "N/A"; } const char *satostr(struct sockaddr_any *sa) { #define INETSTR_BUFCOUNT 8 static char buf[INETSTR_BUFCOUNT][INET6_ADDRSTRLEN]; static int bufidx; struct sockaddr_in *sin = &sa->sa_sin; struct sockaddr_in6 *sin6 = &sa->sa_sin6; bufidx += (bufidx + 1) % INETSTR_BUFCOUNT; buf[bufidx][0] = 0; switch (sin->sin_family) { case AF_INET: inet_ntop(AF_INET, &sin->sin_addr, buf[bufidx], sizeof(buf[bufidx])); break; case AF_INET6: inet_ntop(AF_INET6, &sin6->sin6_addr, buf[bufidx], sizeof(buf[bufidx])); break; default: strlcpy(buf[bufidx], "unknown", sizeof(buf[bufidx])); break; } return buf[bufidx]; } const char *diag2str(uint8_t diag) { switch (diag) { case 0: return "ok"; case 1: return "control detection time expired"; case 2: return "echo function failed"; case 3: return "neighbor signaled session down"; case 4: return "forwarding plane reset"; case 5: return "path down"; case 6: return "concatenated path down"; case 7: return "administratively down"; case 8: return "reverse concatenated path down"; default: return "unknown"; } } int strtosa(const char *addr, struct sockaddr_any *sa) { memset(sa, 0, sizeof(*sa)); if (inet_pton(AF_INET, addr, &sa->sa_sin.sin_addr) == 1) { sa->sa_sin.sin_family = AF_INET; #ifdef HAVE_STRUCT_SOCKADDR_SA_LEN sa->sa_sin.sin_len = sizeof(sa->sa_sin); #endif /* HAVE_STRUCT_SOCKADDR_SA_LEN */ return 0; } if (inet_pton(AF_INET6, addr, &sa->sa_sin6.sin6_addr) == 1) { sa->sa_sin6.sin6_family = AF_INET6; #ifdef HAVE_STRUCT_SOCKADDR_SA_LEN sa->sa_sin6.sin6_len = sizeof(sa->sa_sin6); #endif /* HAVE_STRUCT_SOCKADDR_SA_LEN */ return 0; } return -1; } void integer2timestr(uint64_t time, char *buf, size_t buflen) { unsigned int year, month, day, hour, minute, second; int rv; #define MINUTES (60) #define HOURS (60 * MINUTES) #define DAYS (24 * HOURS) #define MONTHS (30 * DAYS) #define YEARS (12 * MONTHS) if (time >= YEARS) { year = time / YEARS; time -= year * YEARS; rv = snprintf(buf, buflen, "%u year(s), ", year); buf += rv; buflen -= rv; } if (time >= MONTHS) { month = time / MONTHS; time -= month * MONTHS; rv = snprintf(buf, buflen, "%u month(s), ", month); buf += rv; buflen -= rv; } if (time >= DAYS) { day = time / DAYS; time -= day * DAYS; rv = snprintf(buf, buflen, "%u day(s), ", day); buf += rv; buflen -= rv; } if (time >= HOURS) { hour = time / HOURS; time -= hour * HOURS; rv = snprintf(buf, buflen, "%u hour(s), ", hour); buf += rv; buflen -= rv; } if (time >= MINUTES) { minute = time / MINUTES; time -= minute * MINUTES; rv = snprintf(buf, buflen, "%u minute(s), ", minute); buf += rv; buflen -= rv; } second = time % MINUTES; snprintf(buf, buflen, "%u second(s)", second); } const char *bs_to_string(const struct bfd_session *bs) { static char buf[256]; char addr_buf[INET6_ADDRSTRLEN]; int pos; bool is_mhop = BFD_CHECK_FLAG(bs->flags, BFD_SESS_FLAG_MH); pos = snprintf(buf, sizeof(buf), "mhop:%s", is_mhop ? "yes" : "no"); pos += snprintf(buf + pos, sizeof(buf) - pos, " peer:%s", inet_ntop(bs->key.family, &bs->key.peer, addr_buf, sizeof(addr_buf))); pos += snprintf(buf + pos, sizeof(buf) - pos, " local:%s", inet_ntop(bs->key.family, &bs->key.local, addr_buf, sizeof(addr_buf))); if (bs->key.vrfname[0]) pos += snprintf(buf + pos, sizeof(buf) - pos, " vrf:%s", bs->key.vrfname); if (bs->key.ifname[0]) pos += snprintf(buf + pos, sizeof(buf) - pos, " ifname:%s", bs->key.ifname); (void)pos; return buf; } int bs_observer_add(struct bfd_session *bs) { struct bfd_session_observer *bso; bso = XCALLOC(MTYPE_BFDD_SESSION_OBSERVER, sizeof(*bso)); bso->bso_bs = bs; bso->bso_addr.family = bs->key.family; memcpy(&bso->bso_addr.u.prefix, &bs->key.local, sizeof(bs->key.local)); TAILQ_INSERT_TAIL(&bglobal.bg_obslist, bso, bso_entry); return 0; } void bs_observer_del(struct bfd_session_observer *bso) { TAILQ_REMOVE(&bglobal.bg_obslist, bso, bso_entry); XFREE(MTYPE_BFDD_SESSION_OBSERVER, bso); } void bs_to_bpc(struct bfd_session *bs, struct bfd_peer_cfg *bpc) { memset(bpc, 0, sizeof(*bpc)); bpc->bpc_ipv4 = (bs->key.family == AF_INET); bpc->bpc_mhop = bs->key.mhop; switch (bs->key.family) { case AF_INET: bpc->bpc_peer.sa_sin.sin_family = AF_INET; memcpy(&bpc->bpc_peer.sa_sin.sin_addr, &bs->key.peer, sizeof(bpc->bpc_peer.sa_sin.sin_addr)); if (memcmp(&bs->key.local, &zero_addr, sizeof(bs->key.local))) { bpc->bpc_local.sa_sin.sin_family = AF_INET6; memcpy(&bpc->bpc_local.sa_sin.sin_addr, &bs->key.local, sizeof(bpc->bpc_local.sa_sin.sin_addr)); } break; case AF_INET6: bpc->bpc_peer.sa_sin.sin_family = AF_INET6; memcpy(&bpc->bpc_peer.sa_sin6.sin6_addr, &bs->key.peer, sizeof(bpc->bpc_peer.sa_sin6.sin6_addr)); bpc->bpc_local.sa_sin6.sin6_family = AF_INET6; memcpy(&bpc->bpc_local.sa_sin6.sin6_addr, &bs->key.local, sizeof(bpc->bpc_local.sa_sin6.sin6_addr)); break; } if (bs->key.ifname[0]) { bpc->bpc_has_localif = true; strlcpy(bpc->bpc_localif, bs->key.ifname, sizeof(bpc->bpc_localif)); } if (bs->key.vrfname[0]) { bpc->bpc_has_vrfname = true; strlcpy(bpc->bpc_vrfname, bs->key.vrfname, sizeof(bpc->bpc_vrfname)); } } /* * BFD hash data structures to find sessions. */ static struct hash *bfd_id_hash; static struct hash *bfd_key_hash; static unsigned int bfd_id_hash_do(const void *p); static unsigned int bfd_key_hash_do(const void *p); static void _bfd_free(struct hash_bucket *hb, void *arg __attribute__((__unused__))); /* BFD hash for our discriminator. */ static unsigned int bfd_id_hash_do(const void *p) { const struct bfd_session *bs = p; return jhash_1word(bs->discrs.my_discr, 0); } static bool bfd_id_hash_cmp(const void *n1, const void *n2) { const struct bfd_session *bs1 = n1, *bs2 = n2; return bs1->discrs.my_discr == bs2->discrs.my_discr; } /* BFD hash for single hop. */ static unsigned int bfd_key_hash_do(const void *p) { const struct bfd_session *bs = p; return jhash(&bs->key, sizeof(bs->key), 0); } static bool bfd_key_hash_cmp(const void *n1, const void *n2) { const struct bfd_session *bs1 = n1, *bs2 = n2; return memcmp(&bs1->key, &bs2->key, sizeof(bs1->key)) == 0; } /* * Hash public interface / exported functions. */ /* Lookup functions. */ struct bfd_session *bfd_id_lookup(uint32_t id) { struct bfd_session bs; bs.discrs.my_discr = id; return hash_lookup(bfd_id_hash, &bs); } struct bfd_key_walk_partial_lookup { struct bfd_session *given; struct bfd_session *result; }; /* ignore some parameters */ static int bfd_key_lookup_ignore_partial_walker(struct hash_bucket *b, void *data) { struct bfd_key_walk_partial_lookup *ctx = (struct bfd_key_walk_partial_lookup *)data; struct bfd_session *given = ctx->given; struct bfd_session *parsed = b->data; if (given->key.family != parsed->key.family) return HASHWALK_CONTINUE; if (given->key.mhop != parsed->key.mhop) return HASHWALK_CONTINUE; if (memcmp(&given->key.peer, &parsed->key.peer, sizeof(struct in6_addr))) return HASHWALK_CONTINUE; if (memcmp(given->key.vrfname, parsed->key.vrfname, MAXNAMELEN)) return HASHWALK_CONTINUE; ctx->result = parsed; /* ignore localaddr or interface */ return HASHWALK_ABORT; } struct bfd_session *bfd_key_lookup(struct bfd_key key) { struct bfd_session bs, *bsp; struct bfd_key_walk_partial_lookup ctx; char peer_buf[INET6_ADDRSTRLEN]; bs.key = key; bsp = hash_lookup(bfd_key_hash, &bs); if (bsp) return bsp; inet_ntop(bs.key.family, &bs.key.peer, peer_buf, sizeof(peer_buf)); /* Handle cases where local-address is optional. */ if (bs.key.family == AF_INET) { memset(&bs.key.local, 0, sizeof(bs.key.local)); bsp = hash_lookup(bfd_key_hash, &bs); if (bsp) { char addr_buf[INET6_ADDRSTRLEN]; inet_ntop(bs.key.family, &key.local, addr_buf, sizeof(addr_buf)); log_debug(" peer %s found, but loc-addr %s ignored", peer_buf, addr_buf); return bsp; } } bs.key = key; /* Handle cases where ifname is optional. */ if (bs.key.ifname[0]) { memset(bs.key.ifname, 0, sizeof(bs.key.ifname)); bsp = hash_lookup(bfd_key_hash, &bs); if (bsp) { log_debug(" peer %s found, but ifp %s ignored", peer_buf, key.ifname); return bsp; } } /* Handle cases where local-address and ifname are optional. */ if (bs.key.family == AF_INET) { memset(&bs.key.local, 0, sizeof(bs.key.local)); bsp = hash_lookup(bfd_key_hash, &bs); if (bsp) { char addr_buf[INET6_ADDRSTRLEN]; inet_ntop(bs.key.family, &bs.key.local, addr_buf, sizeof(addr_buf)); log_debug(" peer %s found, but ifp %s" " and loc-addr %s ignored", peer_buf, key.ifname, addr_buf); return bsp; } } bs.key = key; /* Handle case where a context more complex ctx is present. * input has no iface nor local-address, but a context may * exist */ ctx.result = NULL; ctx.given = &bs; hash_walk(bfd_key_hash, &bfd_key_lookup_ignore_partial_walker, &ctx); /* change key */ if (ctx.result) { bsp = ctx.result; log_debug(" peer %s found, but ifp" " and/or loc-addr params ignored", peer_buf); } return bsp; } /* * Delete functions. * * Delete functions searches and remove the item from the hash and * returns a pointer to the removed item data. If the item was not found * then it returns NULL. * * The data stored inside the hash is not free()ed, so you must do it * manually after getting the pointer back. */ struct bfd_session *bfd_id_delete(uint32_t id) { struct bfd_session bs; bs.discrs.my_discr = id; return hash_release(bfd_id_hash, &bs); } struct bfd_session *bfd_key_delete(struct bfd_key key) { struct bfd_session bs, *bsp; bs.key = key; bsp = hash_lookup(bfd_key_hash, &bs); if (bsp == NULL && key.ifname[0]) { memset(bs.key.ifname, 0, sizeof(bs.key.ifname)); bsp = hash_lookup(bfd_key_hash, &bs); } return hash_release(bfd_key_hash, bsp); } /* Iteration functions. */ void bfd_id_iterate(hash_iter_func hif, void *arg) { hash_iterate(bfd_id_hash, hif, arg); } void bfd_key_iterate(hash_iter_func hif, void *arg) { hash_iterate(bfd_key_hash, hif, arg); } /* * Insert functions. * * Inserts session into hash and returns `true` on success, otherwise * `false`. */ bool bfd_id_insert(struct bfd_session *bs) { return (hash_get(bfd_id_hash, bs, hash_alloc_intern) == bs); } bool bfd_key_insert(struct bfd_session *bs) { return (hash_get(bfd_key_hash, bs, hash_alloc_intern) == bs); } void bfd_initialize(void) { bfd_id_hash = hash_create(bfd_id_hash_do, bfd_id_hash_cmp, "BFD session discriminator hash"); bfd_key_hash = hash_create(bfd_key_hash_do, bfd_key_hash_cmp, "BFD session hash"); } static void _bfd_free(struct hash_bucket *hb, void *arg __attribute__((__unused__))) { struct bfd_session *bs = hb->data; bfd_session_free(bs); } void bfd_shutdown(void) { /* * Close and free all BFD sessions. * * _bfd_free() will call bfd_session_free() which will take care * of removing the session from all hashes, so we just run an * assert() here to make sure it really happened. */ bfd_id_iterate(_bfd_free, NULL); assert(bfd_key_hash->count == 0); /* Now free the hashes themselves. */ hash_free(bfd_id_hash); hash_free(bfd_key_hash); } struct bfd_session_iterator { int bsi_stop; bool bsi_mhop; const struct bfd_session *bsi_bs; }; static int _bfd_session_next(struct hash_bucket *hb, void *arg) { struct bfd_session_iterator *bsi = arg; struct bfd_session *bs = hb->data; /* Previous entry signaled stop. */ if (bsi->bsi_stop == 1) { /* Match the single/multi hop sessions. */ if (bs->key.mhop != bsi->bsi_mhop) return HASHWALK_CONTINUE; bsi->bsi_bs = bs; return HASHWALK_ABORT; } /* We found the current item, stop in the next one. */ if (bsi->bsi_bs == hb->data) { bsi->bsi_stop = 1; /* Set entry to NULL to signal end of list. */ bsi->bsi_bs = NULL; } else if (bsi->bsi_bs == NULL && bsi->bsi_mhop == bs->key.mhop) { /* We want the first list item. */ bsi->bsi_stop = 1; bsi->bsi_bs = hb->data; return HASHWALK_ABORT; } return HASHWALK_CONTINUE; } /* * bfd_session_next: uses the current session to find the next. * * `bs` might point to NULL to get the first item of the data structure. */ const struct bfd_session *bfd_session_next(const struct bfd_session *bs, bool mhop) { struct bfd_session_iterator bsi; bsi.bsi_stop = 0; bsi.bsi_bs = bs; bsi.bsi_mhop = mhop; hash_walk(bfd_key_hash, _bfd_session_next, &bsi); if (bsi.bsi_stop == 0) return NULL; return bsi.bsi_bs; } static void _bfd_session_remove_manual(struct hash_bucket *hb, void *arg __attribute__((__unused__))) { struct bfd_session *bs = hb->data; /* Delete only manually configured sessions. */ if (BFD_CHECK_FLAG(bs->flags, BFD_SESS_FLAG_CONFIG) == 0) return; bs->refcount--; BFD_UNSET_FLAG(bs->flags, BFD_SESS_FLAG_CONFIG); /* Don't delete sessions still in use. */ if (bs->refcount != 0) return; bfd_session_free(bs); } /* * bfd_sessions_remove_manual: remove all manually configured sessions. * * NOTE: this function doesn't remove automatically created sessions. */ void bfd_sessions_remove_manual(void) { hash_iterate(bfd_key_hash, _bfd_session_remove_manual, NULL); } /* * VRF related functions. */ static int bfd_vrf_new(struct vrf *vrf) { log_debug("VRF Created: %s(%u)", vrf->name, vrf->vrf_id); return 0; } static int bfd_vrf_delete(struct vrf *vrf) { log_debug("VRF Deletion: %s(%u)", vrf->name, vrf->vrf_id); return 0; } static int bfd_vrf_enable(struct vrf *vrf) { struct bfd_vrf_global *bvrf; /* a different name */ if (!vrf->info) { bvrf = XCALLOC(MTYPE_BFDD_VRF, sizeof(struct bfd_vrf_global)); bvrf->vrf = vrf; vrf->info = (void *)bvrf; } else bvrf = vrf->info; log_debug("VRF enable add %s id %u", vrf->name, vrf->vrf_id); if (vrf->vrf_id == VRF_DEFAULT || vrf_get_backend() == VRF_BACKEND_NETNS) { if (!bvrf->bg_shop) bvrf->bg_shop = bp_udp_shop(vrf); if (!bvrf->bg_mhop) bvrf->bg_mhop = bp_udp_mhop(vrf); if (!bvrf->bg_shop6) bvrf->bg_shop6 = bp_udp6_shop(vrf); if (!bvrf->bg_mhop6) bvrf->bg_mhop6 = bp_udp6_mhop(vrf); if (!bvrf->bg_echo) bvrf->bg_echo = bp_echo_socket(vrf); if (!bvrf->bg_echov6) bvrf->bg_echov6 = bp_echov6_socket(vrf); /* Add descriptors to the event loop. */ if (!bvrf->bg_ev[0]) thread_add_read(master, bfd_recv_cb, bvrf, bvrf->bg_shop, &bvrf->bg_ev[0]); if (!bvrf->bg_ev[1]) thread_add_read(master, bfd_recv_cb, bvrf, bvrf->bg_mhop, &bvrf->bg_ev[1]); if (!bvrf->bg_ev[2]) thread_add_read(master, bfd_recv_cb, bvrf, bvrf->bg_shop6, &bvrf->bg_ev[2]); if (!bvrf->bg_ev[3]) thread_add_read(master, bfd_recv_cb, bvrf, bvrf->bg_mhop6, &bvrf->bg_ev[3]); if (!bvrf->bg_ev[4]) thread_add_read(master, bfd_recv_cb, bvrf, bvrf->bg_echo, &bvrf->bg_ev[4]); if (!bvrf->bg_ev[5]) thread_add_read(master, bfd_recv_cb, bvrf, bvrf->bg_echov6, &bvrf->bg_ev[5]); } if (vrf->vrf_id != VRF_DEFAULT) { bfdd_zclient_register(vrf->vrf_id); bfdd_sessions_enable_vrf(vrf); } return 0; } static int bfd_vrf_disable(struct vrf *vrf) { struct bfd_vrf_global *bvrf; if (!vrf->info) return 0; bvrf = vrf->info; if (vrf->vrf_id != VRF_DEFAULT) { bfdd_sessions_disable_vrf(vrf); bfdd_zclient_unregister(vrf->vrf_id); } log_debug("VRF disable %s id %d", vrf->name, vrf->vrf_id); /* Disable read/write poll triggering. */ THREAD_OFF(bvrf->bg_ev[0]); THREAD_OFF(bvrf->bg_ev[1]); THREAD_OFF(bvrf->bg_ev[2]); THREAD_OFF(bvrf->bg_ev[3]); THREAD_OFF(bvrf->bg_ev[4]); THREAD_OFF(bvrf->bg_ev[5]); /* Close all descriptors. */ socket_close(&bvrf->bg_echo); socket_close(&bvrf->bg_shop); socket_close(&bvrf->bg_mhop); socket_close(&bvrf->bg_shop6); socket_close(&bvrf->bg_mhop6); socket_close(&bvrf->bg_echo); socket_close(&bvrf->bg_echov6); /* free context */ XFREE(MTYPE_BFDD_VRF, bvrf); vrf->info = NULL; return 0; } void bfd_vrf_init(void) { vrf_init(bfd_vrf_new, bfd_vrf_enable, bfd_vrf_disable, bfd_vrf_delete, NULL); } void bfd_vrf_terminate(void) { vrf_terminate(); } struct bfd_vrf_global *bfd_vrf_look_by_session(struct bfd_session *bfd) { struct vrf *vrf; if (!vrf_is_backend_netns()) { vrf = vrf_lookup_by_id(VRF_DEFAULT); if (vrf) return (struct bfd_vrf_global *)vrf->info; return NULL; } if (!bfd) return NULL; if (!bfd->vrf) return NULL; return bfd->vrf->info; } frr-7.2.1/bfdd/bfd.h0000644000000000000000000004277413610377563011035 00000000000000/********************************************************************* * Copyright 2014,2015,2016,2017 Cumulus Networks, Inc. All rights reserved. * * This program is free software; you can redistribute it and/or modify it * under the terms of the GNU General Public License as published by the Free * Software Foundation; either version 2 of the License, or (at your option) * any later version. * * 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; see the file COPYING; if not, write to the Free Software * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA * * bfd.h: implements the BFD protocol. */ #ifndef _BFD_H_ #define _BFD_H_ #include #include #include #include #include "lib/hash.h" #include "lib/libfrr.h" #include "lib/qobj.h" #include "lib/queue.h" #include "lib/vrf.h" #include "bfdctl.h" #ifdef BFD_DEBUG #define BFDD_JSON_CONV_OPTIONS (JSON_C_TO_STRING_PRETTY) #else #define BFDD_JSON_CONV_OPTIONS (0) #endif DECLARE_MGROUP(BFDD) DECLARE_MTYPE(BFDD_CONTROL) DECLARE_MTYPE(BFDD_NOTIFICATION) struct bfd_timers { uint32_t desired_min_tx; uint32_t required_min_rx; uint32_t required_min_echo; }; struct bfd_discrs { uint32_t my_discr; uint32_t remote_discr; }; /* * Format of control packet. From section 4) */ struct bfd_pkt { union { uint32_t byteFields; struct { uint8_t diag; uint8_t flags; uint8_t detect_mult; uint8_t len; }; }; struct bfd_discrs discrs; struct bfd_timers timers; }; /* * Format of Echo packet. */ struct bfd_echo_pkt { union { uint32_t byteFields; struct { uint8_t ver; uint8_t len; uint16_t reserved; }; }; uint32_t my_discr; uint8_t pad[16]; }; /* Macros for manipulating control packets */ #define BFD_VERMASK 0x03 #define BFD_DIAGMASK 0x1F #define BFD_GETVER(diag) ((diag >> 5) & BFD_VERMASK) #define BFD_SETVER(diag, val) ((diag) |= (val & BFD_VERMASK) << 5) #define BFD_VERSION 1 #define BFD_PBIT 0x20 #define BFD_FBIT 0x10 #define BFD_CBIT 0x08 #define BFD_ABIT 0x04 #define BFD_DEMANDBIT 0x02 #define BFD_SETDEMANDBIT(flags, val) \ { \ if ((val)) \ flags |= BFD_DEMANDBIT; \ } #define BFD_SETPBIT(flags, val) \ { \ if ((val)) \ flags |= BFD_PBIT; \ } #define BFD_GETPBIT(flags) (flags & BFD_PBIT) #define BFD_SETFBIT(flags, val) \ { \ if ((val)) \ flags |= BFD_FBIT; \ } #define BFD_GETFBIT(flags) (flags & BFD_FBIT) #define BFD_SETSTATE(flags, val) \ { \ if ((val)) \ flags |= (val & 0x3) << 6; \ } #define BFD_GETSTATE(flags) ((flags >> 6) & 0x3) #define BFD_SETCBIT(flags, val) \ { \ if ((val)) \ flags |= val; \ } #define BFD_GETCBIT(flags) (flags & BFD_FBIT) #define BFD_ECHO_VERSION 1 #define BFD_ECHO_PKT_LEN sizeof(struct bfd_echo_pkt) enum bfd_diagnosticis { BD_OK = 0, /* Control Detection Time Expired. */ BD_CONTROL_EXPIRED = 1, /* Echo Function Failed. */ BD_ECHO_FAILED = 2, /* Neighbor Signaled Session Down. */ BD_NEIGHBOR_DOWN = 3, /* Forwarding Plane Reset. */ BD_FORWARDING_RESET = 4, /* Path Down. */ BD_PATH_DOWN = 5, /* Concatenated Path Down. */ BD_CONCATPATH_DOWN = 6, /* Administratively Down. */ BD_ADMIN_DOWN = 7, /* Reverse Concatenated Path Down. */ BD_REVCONCATPATH_DOWN = 8, /* 9..31: reserved. */ }; /* BFD session flags */ enum bfd_session_flags { BFD_SESS_FLAG_NONE = 0, BFD_SESS_FLAG_ECHO = 1 << 0, /* BFD Echo functionality */ BFD_SESS_FLAG_ECHO_ACTIVE = 1 << 1, /* BFD Echo Packets are being sent * actively */ BFD_SESS_FLAG_MH = 1 << 2, /* BFD Multi-hop session */ BFD_SESS_FLAG_IPV6 = 1 << 4, /* BFD IPv6 session */ BFD_SESS_FLAG_SEND_EVT_ACTIVE = 1 << 5, /* send event timer active */ BFD_SESS_FLAG_SEND_EVT_IGNORE = 1 << 6, /* ignore send event when timer * expires */ BFD_SESS_FLAG_SHUTDOWN = 1 << 7, /* disable BGP peer function */ BFD_SESS_FLAG_CONFIG = 1 << 8, /* Session configured with bfd NB API */ BFD_SESS_FLAG_CBIT = 1 << 9, /* CBIT is set */ }; #define BFD_SET_FLAG(field, flag) (field |= flag) #define BFD_UNSET_FLAG(field, flag) (field &= ~flag) #define BFD_CHECK_FLAG(field, flag) (field & flag) /* BFD session hash keys */ struct bfd_key { uint16_t family; uint8_t mhop; struct in6_addr peer; struct in6_addr local; char ifname[MAXNAMELEN]; char vrfname[MAXNAMELEN]; }; struct bfd_session_stats { uint64_t rx_ctrl_pkt; uint64_t tx_ctrl_pkt; uint64_t rx_echo_pkt; uint64_t tx_echo_pkt; uint64_t session_up; uint64_t session_down; uint64_t znotification; }; /* bfd_session shortcut label forwarding. */ struct peer_label; /* * Session state information */ struct bfd_session { /* protocol state per RFC 5880*/ uint8_t ses_state; struct bfd_discrs discrs; uint8_t local_diag; uint8_t demand_mode; uint8_t detect_mult; uint8_t remote_detect_mult; uint8_t mh_ttl; uint8_t remote_cbit; /* Timers */ struct bfd_timers timers; struct bfd_timers cur_timers; uint64_t detect_TO; struct thread *echo_recvtimer_ev; struct thread *recvtimer_ev; uint64_t xmt_TO; uint64_t echo_xmt_TO; struct thread *xmttimer_ev; struct thread *echo_xmttimer_ev; uint64_t echo_detect_TO; /* software object state */ uint8_t polling; /* This and the localDiscr are the keys to state info */ struct bfd_key key; struct peer_label *pl; struct sockaddr_any local_address; struct interface *ifp; struct vrf *vrf; int sock; /* BFD session flags */ enum bfd_session_flags flags; struct bfd_session_stats stats; struct timeval uptime; /* last up time */ struct timeval downtime; /* last down time */ /* Remote peer data (for debugging mostly) */ uint8_t remote_diag; struct bfd_timers remote_timers; uint64_t refcount; /* number of pointers referencing this. */ }; struct peer_label { TAILQ_ENTRY(peer_label) pl_entry; struct bfd_session *pl_bs; char pl_label[MAXNAMELEN]; }; TAILQ_HEAD(pllist, peer_label); struct bfd_diag_str_list { const char *str; int type; }; struct bfd_state_str_list { const char *str; int type; }; struct bfd_session_observer { struct bfd_session *bso_bs; char bso_entryname[MAXNAMELEN]; struct prefix bso_addr; TAILQ_ENTRY(bfd_session_observer) bso_entry; }; TAILQ_HEAD(obslist, bfd_session_observer); /* States defined per 4.1 */ #define PTM_BFD_ADM_DOWN 0 #define PTM_BFD_DOWN 1 #define PTM_BFD_INIT 2 #define PTM_BFD_UP 3 /* Various constants */ /* Retrieved from ptm_timer.h from Cumulus PTM sources. */ #define BFD_DEF_DEMAND 0 #define BFD_DEFDETECTMULT 3 #define BFD_DEFDESIREDMINTX (300 * 1000) /* microseconds. */ #define BFD_DEFREQUIREDMINRX (300 * 1000) /* microseconds. */ #define BFD_DEF_REQ_MIN_ECHO (50 * 1000) /* microseconds. */ #define BFD_DEF_SLOWTX (1000 * 1000) /* microseconds. */ #define BFD_DEF_MHOP_TTL 5 #define BFD_PKT_LEN 24 /* Length of control packet */ #define BFD_TTL_VAL 255 #define BFD_RCV_TTL_VAL 1 #define BFD_TOS_VAL 0xC0 #define BFD_PKT_INFO_VAL 1 #define BFD_IPV6_PKT_INFO_VAL 1 #define BFD_IPV6_ONLY_VAL 1 #define BFD_SRCPORTINIT 49152 #define BFD_SRCPORTMAX 65535 #define BFD_DEFDESTPORT 3784 #define BFD_DEF_ECHO_PORT 3785 #define BFD_DEF_MHOP_DEST_PORT 4784 /* * control.c * * Daemon control code to speak with local consumers. */ /* See 'bfdctrl.h' for client protocol definitions. */ struct bfd_control_buffer { size_t bcb_left; size_t bcb_pos; union { struct bfd_control_msg *bcb_bcm; uint8_t *bcb_buf; }; }; struct bfd_control_queue { TAILQ_ENTRY(bfd_control_queue) bcq_entry; struct bfd_control_buffer bcq_bcb; }; TAILQ_HEAD(bcqueue, bfd_control_queue); struct bfd_notify_peer { TAILQ_ENTRY(bfd_notify_peer) bnp_entry; struct bfd_session *bnp_bs; }; TAILQ_HEAD(bnplist, bfd_notify_peer); struct bfd_control_socket { TAILQ_ENTRY(bfd_control_socket) bcs_entry; int bcs_sd; struct thread *bcs_ev; struct thread *bcs_outev; struct bcqueue bcs_bcqueue; /* Notification data */ uint64_t bcs_notify; struct bnplist bcs_bnplist; enum bc_msg_version bcs_version; enum bc_msg_type bcs_type; /* Message buffering */ struct bfd_control_buffer bcs_bin; struct bfd_control_buffer *bcs_bout; }; TAILQ_HEAD(bcslist, bfd_control_socket); int control_init(const char *path); void control_shutdown(void); int control_notify(struct bfd_session *bs); int control_notify_config(const char *op, struct bfd_session *bs); int control_accept(struct thread *t); /* * bfdd.c * * Daemon specific code. */ struct bfd_vrf_global { int bg_shop; int bg_mhop; int bg_shop6; int bg_mhop6; int bg_echo; int bg_echov6; struct vrf *vrf; struct thread *bg_ev[6]; }; struct bfd_global { int bg_csock; struct thread *bg_csockev; struct bcslist bg_bcslist; struct pllist bg_pllist; struct obslist bg_obslist; struct zebra_privs_t bfdd_privs; }; extern struct bfd_global bglobal; extern struct bfd_diag_str_list diag_list[]; extern struct bfd_state_str_list state_list[]; void socket_close(int *s); /* * config.c * * Contains the code related with loading/reloading configuration. */ int parse_config(const char *fname); int config_request_add(const char *jsonstr); int config_request_del(const char *jsonstr); char *config_response(const char *status, const char *error); char *config_notify(struct bfd_session *bs); char *config_notify_config(const char *op, struct bfd_session *bs); typedef int (*bpc_handle)(struct bfd_peer_cfg *, void *arg); int config_notify_request(struct bfd_control_socket *bcs, const char *jsonstr, bpc_handle bh); struct peer_label *pl_new(const char *label, struct bfd_session *bs); struct peer_label *pl_find(const char *label); void pl_free(struct peer_label *pl); /* * log.c * * Contains code that does the logging procedures. Might implement multiple * backends (e.g. zebra log, syslog or other logging lib). */ enum blog_level { /* level vs syslog equivalent */ BLOG_DEBUG = 0, /* LOG_DEBUG */ BLOG_INFO = 1, /* LOG_INFO */ BLOG_WARNING = 2, /* LOG_WARNING */ BLOG_ERROR = 3, /* LOG_ERR */ BLOG_FATAL = 4, /* LOG_CRIT */ }; void log_init(int foreground, enum blog_level level, struct frr_daemon_info *fdi); void log_info(const char *fmt, ...); void log_debug(const char *fmt, ...); void log_warning(const char *fmt, ...); void log_error(const char *fmt, ...); void log_fatal(const char *fmt, ...); /* * bfd_packet.c * * Contains the code related with receiving/seding, packing/unpacking BFD data. */ int bp_set_ttlv6(int sd, uint8_t value); int bp_set_ttl(int sd, uint8_t value); int bp_set_tosv6(int sd, uint8_t value); int bp_set_tos(int sd, uint8_t value); int bp_bind_dev(int sd, const char *dev); int bp_udp_shop(const struct vrf *vrf); int bp_udp_mhop(const struct vrf *vrf); int bp_udp6_shop(const struct vrf *vrf); int bp_udp6_mhop(const struct vrf *vrf); int bp_peer_socket(const struct bfd_session *bs); int bp_peer_socketv6(const struct bfd_session *bs); int bp_echo_socket(const struct vrf *vrf); int bp_echov6_socket(const struct vrf *vrf); void ptm_bfd_snd(struct bfd_session *bfd, int fbit); void ptm_bfd_echo_snd(struct bfd_session *bfd); int bfd_recv_cb(struct thread *t); /* * event.c * * Contains the code related with event loop. */ typedef void (*bfd_ev_cb)(struct thread *t); void bfd_recvtimer_update(struct bfd_session *bs); void bfd_echo_recvtimer_update(struct bfd_session *bs); void bfd_xmttimer_update(struct bfd_session *bs, uint64_t jitter); void bfd_echo_xmttimer_update(struct bfd_session *bs, uint64_t jitter); void bfd_xmttimer_delete(struct bfd_session *bs); void bfd_echo_xmttimer_delete(struct bfd_session *bs); void bfd_recvtimer_delete(struct bfd_session *bs); void bfd_echo_recvtimer_delete(struct bfd_session *bs); void bfd_recvtimer_assign(struct bfd_session *bs, bfd_ev_cb cb, int sd); void bfd_echo_recvtimer_assign(struct bfd_session *bs, bfd_ev_cb cb, int sd); void bfd_xmttimer_assign(struct bfd_session *bs, bfd_ev_cb cb); void bfd_echo_xmttimer_assign(struct bfd_session *bs, bfd_ev_cb cb); /* * bfd.c * * BFD protocol specific code. */ int bfd_session_enable(struct bfd_session *bs); void bfd_session_disable(struct bfd_session *bs); struct bfd_session *ptm_bfd_sess_new(struct bfd_peer_cfg *bpc); int ptm_bfd_sess_del(struct bfd_peer_cfg *bpc); void ptm_bfd_sess_dn(struct bfd_session *bfd, uint8_t diag); void ptm_bfd_sess_up(struct bfd_session *bfd); void ptm_bfd_echo_stop(struct bfd_session *bfd); void ptm_bfd_echo_start(struct bfd_session *bfd); void ptm_bfd_xmt_TO(struct bfd_session *bfd, int fbit); void ptm_bfd_start_xmt_timer(struct bfd_session *bfd, bool is_echo); struct bfd_session *ptm_bfd_sess_find(struct bfd_pkt *cp, struct sockaddr_any *peer, struct sockaddr_any *local, ifindex_t ifindex, vrf_id_t vrfid, bool is_mhop); struct bfd_session *bs_peer_find(struct bfd_peer_cfg *bpc); int bfd_session_update_label(struct bfd_session *bs, const char *nlabel); void bfd_set_polling(struct bfd_session *bs); void bs_state_handler(struct bfd_session *bs, int nstate); void bs_echo_timer_handler(struct bfd_session *bs); void bs_final_handler(struct bfd_session *bs); void bs_set_slow_timers(struct bfd_session *bs); const char *satostr(struct sockaddr_any *sa); const char *diag2str(uint8_t diag); int strtosa(const char *addr, struct sockaddr_any *sa); void integer2timestr(uint64_t time, char *buf, size_t buflen); const char *bs_to_string(const struct bfd_session *bs); int bs_observer_add(struct bfd_session *bs); void bs_observer_del(struct bfd_session_observer *bso); void bs_to_bpc(struct bfd_session *bs, struct bfd_peer_cfg *bpc); void gen_bfd_key(struct bfd_key *key, struct sockaddr_any *peer, struct sockaddr_any *local, bool mhop, const char *ifname, const char *vrfname); struct bfd_session *bfd_session_new(void); struct bfd_session *bs_registrate(struct bfd_session *bs); void bfd_session_free(struct bfd_session *bs); const struct bfd_session *bfd_session_next(const struct bfd_session *bs, bool mhop); void bfd_sessions_remove_manual(void); /* BFD hash data structures interface */ void bfd_initialize(void); void bfd_shutdown(void); void bfd_vrf_init(void); void bfd_vrf_terminate(void); struct bfd_vrf_global *bfd_vrf_look_by_session(struct bfd_session *bfd); struct bfd_session *bfd_id_lookup(uint32_t id); struct bfd_session *bfd_key_lookup(struct bfd_key key); struct bfd_session *bfd_id_delete(uint32_t id); struct bfd_session *bfd_key_delete(struct bfd_key key); bool bfd_id_insert(struct bfd_session *bs); bool bfd_key_insert(struct bfd_session *bs); typedef void (*hash_iter_func)(struct hash_bucket *hb, void *arg); void bfd_id_iterate(hash_iter_func hif, void *arg); void bfd_key_iterate(hash_iter_func hif, void *arg); /* Export callback functions for `event.c`. */ extern struct thread_master *master; int bfd_recvtimer_cb(struct thread *t); int bfd_echo_recvtimer_cb(struct thread *t); int bfd_xmt_cb(struct thread *t); int bfd_echo_xmt_cb(struct thread *t); extern struct in6_addr zero_addr; /* * bfdd_vty.c * * BFD daemon vty shell commands. */ void bfdd_vty_init(void); /* * bfdd_cli.c * * BFD daemon CLI implementation. */ void bfdd_cli_init(void); void bfd_cli_show_header(struct vty *vty, struct lyd_node *dnode, bool show_defaults); void bfd_cli_show_header_end(struct vty *vty, struct lyd_node *dnode); void bfd_cli_show_single_hop_peer(struct vty *vty, struct lyd_node *dnode, bool show_defaults); void bfd_cli_show_multi_hop_peer(struct vty *vty, struct lyd_node *dnode, bool show_defaults); void bfd_cli_show_peer_end(struct vty *vty, struct lyd_node *dnode); void bfd_cli_show_mult(struct vty *vty, struct lyd_node *dnode, bool show_defaults); void bfd_cli_show_tx(struct vty *vty, struct lyd_node *dnode, bool show_defaults); void bfd_cli_show_rx(struct vty *vty, struct lyd_node *dnode, bool show_defaults); void bfd_cli_show_shutdown(struct vty *vty, struct lyd_node *dnode, bool show_defaults); void bfd_cli_show_echo(struct vty *vty, struct lyd_node *dnode, bool show_defaults); void bfd_cli_show_echo_interval(struct vty *vty, struct lyd_node *dnode, bool show_defaults); /* * ptm_adapter.c */ void bfdd_zclient_init(struct zebra_privs_t *bfdd_priv); void bfdd_zclient_stop(void); void bfdd_zclient_unregister(vrf_id_t vrf_id); void bfdd_zclient_register(vrf_id_t vrf_id); void bfdd_sessions_enable_vrf(struct vrf *vrf); void bfdd_sessions_disable_vrf(struct vrf *vrf); int ptm_bfd_notify(struct bfd_session *bs); /* * bfdd_northbound.c * * BFD northbound callbacks. */ extern const struct frr_yang_module_info frr_bfdd_info; #endif /* _BFD_H_ */ frr-7.2.1/bfdd/bfd_packet.c0000644000000000000000000007331213610377563012347 00000000000000/********************************************************************* * Copyright 2017 Cumulus Networks, Inc. All rights reserved. * * This program is free software; you can redistribute it and/or modify it * under the terms of the GNU General Public License as published by the Free * Software Foundation; either version 2 of the License, or (at your option) * any later version. * * 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; see the file COPYING; if not, write to the Free Software * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA * * bfd_packet.c: implements the BFD protocol packet handling. * * Authors * ------- * Shrijeet Mukherjee [shm@cumulusnetworks.com] * Kanna Rajagopal [kanna@cumulusnetworks.com] * Radhika Mahankali [Radhika@cumulusnetworks.com] */ #include #ifdef BFD_LINUX #include #endif /* BFD_LINUX */ #include #include #include "lib/sockopt.h" #include "bfd.h" /* * Prototypes */ static int ptm_bfd_process_echo_pkt(struct bfd_vrf_global *bvrf, int s); int _ptm_bfd_send(struct bfd_session *bs, uint16_t *port, const void *data, size_t datalen); static void bfd_sd_reschedule(struct bfd_vrf_global *bvrf, int sd); ssize_t bfd_recv_ipv4(int sd, uint8_t *msgbuf, size_t msgbuflen, uint8_t *ttl, ifindex_t *ifindex, struct sockaddr_any *local, struct sockaddr_any *peer); ssize_t bfd_recv_ipv6(int sd, uint8_t *msgbuf, size_t msgbuflen, uint8_t *ttl, ifindex_t *ifindex, struct sockaddr_any *local, struct sockaddr_any *peer); int bp_udp_send(int sd, uint8_t ttl, uint8_t *data, size_t datalen, struct sockaddr *to, socklen_t tolen); int bp_bfd_echo_in(struct bfd_vrf_global *bvrf, int sd, uint8_t *ttl, uint32_t *my_discr); /* socket related prototypes */ static void bp_set_ipopts(int sd); static void bp_bind_ip(int sd, uint16_t port); static void bp_set_ipv6opts(int sd); static void bp_bind_ipv6(int sd, uint16_t port); /* * Functions */ int _ptm_bfd_send(struct bfd_session *bs, uint16_t *port, const void *data, size_t datalen) { struct sockaddr *sa; struct sockaddr_in sin; struct sockaddr_in6 sin6; socklen_t slen; ssize_t rv; int sd = -1; if (BFD_CHECK_FLAG(bs->flags, BFD_SESS_FLAG_IPV6)) { memset(&sin6, 0, sizeof(sin6)); sin6.sin6_family = AF_INET6; memcpy(&sin6.sin6_addr, &bs->key.peer, sizeof(sin6.sin6_addr)); if (IN6_IS_ADDR_LINKLOCAL(&sin6.sin6_addr)) sin6.sin6_scope_id = bs->ifp->ifindex; sin6.sin6_port = (port) ? *port : (BFD_CHECK_FLAG(bs->flags, BFD_SESS_FLAG_MH)) ? htons(BFD_DEF_MHOP_DEST_PORT) : htons(BFD_DEFDESTPORT); sd = bs->sock; sa = (struct sockaddr *)&sin6; slen = sizeof(sin6); } else { memset(&sin, 0, sizeof(sin)); sin.sin_family = AF_INET; memcpy(&sin.sin_addr, &bs->key.peer, sizeof(sin.sin_addr)); sin.sin_port = (port) ? *port : (BFD_CHECK_FLAG(bs->flags, BFD_SESS_FLAG_MH)) ? htons(BFD_DEF_MHOP_DEST_PORT) : htons(BFD_DEFDESTPORT); sd = bs->sock; sa = (struct sockaddr *)&sin; slen = sizeof(sin); } #ifdef HAVE_STRUCT_SOCKADDR_SA_LEN sa->sa_len = slen; #endif /* HAVE_STRUCT_SOCKADDR_SA_LEN */ rv = sendto(sd, data, datalen, 0, sa, slen); if (rv <= 0) { log_debug("packet-send: send failure: %s", strerror(errno)); return -1; } if (rv < (ssize_t)datalen) log_debug("packet-send: send partial", strerror(errno)); return 0; } void ptm_bfd_echo_snd(struct bfd_session *bfd) { struct sockaddr *sa; socklen_t salen; int sd; struct bfd_echo_pkt bep; struct sockaddr_in sin; struct sockaddr_in6 sin6; struct bfd_vrf_global *bvrf = bfd_vrf_look_by_session(bfd); if (!bvrf) return; if (!BFD_CHECK_FLAG(bfd->flags, BFD_SESS_FLAG_ECHO_ACTIVE)) BFD_SET_FLAG(bfd->flags, BFD_SESS_FLAG_ECHO_ACTIVE); memset(&bep, 0, sizeof(bep)); bep.ver = BFD_ECHO_VERSION; bep.len = BFD_ECHO_PKT_LEN; bep.my_discr = htonl(bfd->discrs.my_discr); if (BFD_CHECK_FLAG(bfd->flags, BFD_SESS_FLAG_IPV6)) { sd = bvrf->bg_echov6; memset(&sin6, 0, sizeof(sin6)); sin6.sin6_family = AF_INET6; memcpy(&sin6.sin6_addr, &bfd->key.peer, sizeof(sin6.sin6_addr)); if (bfd->ifp && IN6_IS_ADDR_LINKLOCAL(&sin6.sin6_addr)) sin6.sin6_scope_id = bfd->ifp->ifindex; sin6.sin6_port = htons(BFD_DEF_ECHO_PORT); #ifdef HAVE_STRUCT_SOCKADDR_SA_LEN sin6.sin6_len = sizeof(sin6); #endif /* HAVE_STRUCT_SOCKADDR_SA_LEN */ sa = (struct sockaddr *)&sin6; salen = sizeof(sin6); } else { sd = bvrf->bg_echo; memset(&sin6, 0, sizeof(sin6)); sin.sin_family = AF_INET; memcpy(&sin.sin_addr, &bfd->key.peer, sizeof(sin.sin_addr)); sin.sin_port = htons(BFD_DEF_ECHO_PORT); #ifdef HAVE_STRUCT_SOCKADDR_SA_LEN sin.sin_len = sizeof(sin); #endif /* HAVE_STRUCT_SOCKADDR_SA_LEN */ sa = (struct sockaddr *)&sin; salen = sizeof(sin); } if (bp_udp_send(sd, BFD_TTL_VAL, (uint8_t *)&bep, sizeof(bep), sa, salen) == -1) return; bfd->stats.tx_echo_pkt++; } static int ptm_bfd_process_echo_pkt(struct bfd_vrf_global *bvrf, int s) { struct bfd_session *bfd; uint32_t my_discr = 0; uint8_t ttl = 0; /* Receive and parse echo packet. */ if (bp_bfd_echo_in(bvrf, s, &ttl, &my_discr) == -1) return 0; /* Your discriminator not zero - use it to find session */ bfd = bfd_id_lookup(my_discr); if (bfd == NULL) { log_debug("echo-packet: no matching session (id:%u)", my_discr); return -1; } if (!BFD_CHECK_FLAG(bfd->flags, BFD_SESS_FLAG_ECHO_ACTIVE)) { log_debug("echo-packet: echo disabled [%s] (id:%u)", bs_to_string(bfd), my_discr); return -1; } bfd->stats.rx_echo_pkt++; /* Compute detect time */ bfd->echo_detect_TO = bfd->remote_detect_mult * bfd->echo_xmt_TO; /* Update echo receive timeout. */ if (bfd->echo_detect_TO > 0) bfd_echo_recvtimer_update(bfd); return 0; } void ptm_bfd_snd(struct bfd_session *bfd, int fbit) { struct bfd_pkt cp; /* Set fields according to section 6.5.7 */ cp.diag = bfd->local_diag; BFD_SETVER(cp.diag, BFD_VERSION); cp.flags = 0; BFD_SETSTATE(cp.flags, bfd->ses_state); if (BFD_CHECK_FLAG(bfd->flags, BFD_SESS_FLAG_CBIT)) BFD_SETCBIT(cp.flags, BFD_CBIT); BFD_SETDEMANDBIT(cp.flags, BFD_DEF_DEMAND); /* * Polling and Final can't be set at the same time. * * RFC 5880, Section 6.5. */ BFD_SETFBIT(cp.flags, fbit); if (fbit == 0) BFD_SETPBIT(cp.flags, bfd->polling); cp.detect_mult = bfd->detect_mult; cp.len = BFD_PKT_LEN; cp.discrs.my_discr = htonl(bfd->discrs.my_discr); cp.discrs.remote_discr = htonl(bfd->discrs.remote_discr); if (bfd->polling) { cp.timers.desired_min_tx = htonl(bfd->timers.desired_min_tx); cp.timers.required_min_rx = htonl(bfd->timers.required_min_rx); } else { /* * We can only announce current setting on poll, this * avoids timing mismatch with our peer and give it * the oportunity to learn. See `bs_final_handler` for * more information. */ cp.timers.desired_min_tx = htonl(bfd->cur_timers.desired_min_tx); cp.timers.required_min_rx = htonl(bfd->cur_timers.required_min_rx); } cp.timers.required_min_echo = htonl(bfd->timers.required_min_echo); if (_ptm_bfd_send(bfd, NULL, &cp, BFD_PKT_LEN) != 0) return; bfd->stats.tx_ctrl_pkt++; } ssize_t bfd_recv_ipv4(int sd, uint8_t *msgbuf, size_t msgbuflen, uint8_t *ttl, ifindex_t *ifindex, struct sockaddr_any *local, struct sockaddr_any *peer) { struct cmsghdr *cm; ssize_t mlen; struct sockaddr_in msgaddr; struct msghdr msghdr; struct iovec iov[1]; uint8_t cmsgbuf[255]; /* Prepare the recvmsg params. */ iov[0].iov_base = msgbuf; iov[0].iov_len = msgbuflen; memset(&msghdr, 0, sizeof(msghdr)); msghdr.msg_name = &msgaddr; msghdr.msg_namelen = sizeof(msgaddr); msghdr.msg_iov = iov; msghdr.msg_iovlen = 1; msghdr.msg_control = cmsgbuf; msghdr.msg_controllen = sizeof(cmsgbuf); mlen = recvmsg(sd, &msghdr, MSG_DONTWAIT); if (mlen == -1) { if (errno != EAGAIN) log_error("ipv4-recv: recv failed: %s", strerror(errno)); return -1; } /* Get source address */ peer->sa_sin = *((struct sockaddr_in *)(msghdr.msg_name)); /* Get and check TTL */ for (cm = CMSG_FIRSTHDR(&msghdr); cm != NULL; cm = CMSG_NXTHDR(&msghdr, cm)) { if (cm->cmsg_level != IPPROTO_IP) continue; switch (cm->cmsg_type) { #ifdef BFD_LINUX case IP_TTL: { uint32_t ttlval; memcpy(&ttlval, CMSG_DATA(cm), sizeof(ttlval)); if (ttlval > 255) { log_debug("ipv4-recv: invalid TTL: %u", ttlval); return -1; } *ttl = ttlval; break; } case IP_PKTINFO: { struct in_pktinfo *pi = (struct in_pktinfo *)CMSG_DATA(cm); if (pi == NULL) break; local->sa_sin.sin_family = AF_INET; local->sa_sin.sin_addr = pi->ipi_addr; #ifdef HAVE_STRUCT_SOCKADDR_SA_LEN local->sa_sin.sin_len = sizeof(local->sa_sin); #endif /* HAVE_STRUCT_SOCKADDR_SA_LEN */ *ifindex = pi->ipi_ifindex; break; } #endif /* BFD_LINUX */ #ifdef BFD_BSD case IP_RECVTTL: { memcpy(ttl, CMSG_DATA(cm), sizeof(*ttl)); break; } case IP_RECVDSTADDR: { struct in_addr ia; memcpy(&ia, CMSG_DATA(cm), sizeof(ia)); local->sa_sin.sin_family = AF_INET; local->sa_sin.sin_addr = ia; #ifdef HAVE_STRUCT_SOCKADDR_SA_LEN local->sa_sin.sin_len = sizeof(local->sa_sin); #endif /* HAVE_STRUCT_SOCKADDR_SA_LEN */ break; } #endif /* BFD_BSD */ default: /* * On *BSDs we expect to land here when skipping * the IP_RECVIF header. It will be handled by * getsockopt_ifindex() below. */ /* NOTHING */ break; } } /* OS agnostic way of getting interface name. */ if (*ifindex == IFINDEX_INTERNAL) *ifindex = getsockopt_ifindex(AF_INET, &msghdr); return mlen; } ssize_t bfd_recv_ipv6(int sd, uint8_t *msgbuf, size_t msgbuflen, uint8_t *ttl, ifindex_t *ifindex, struct sockaddr_any *local, struct sockaddr_any *peer) { struct cmsghdr *cm; struct in6_pktinfo *pi6 = NULL; ssize_t mlen; uint32_t ttlval; struct sockaddr_in6 msgaddr6; struct msghdr msghdr6; struct iovec iov[1]; uint8_t cmsgbuf6[255]; /* Prepare the recvmsg params. */ iov[0].iov_base = msgbuf; iov[0].iov_len = msgbuflen; memset(&msghdr6, 0, sizeof(msghdr6)); msghdr6.msg_name = &msgaddr6; msghdr6.msg_namelen = sizeof(msgaddr6); msghdr6.msg_iov = iov; msghdr6.msg_iovlen = 1; msghdr6.msg_control = cmsgbuf6; msghdr6.msg_controllen = sizeof(cmsgbuf6); mlen = recvmsg(sd, &msghdr6, MSG_DONTWAIT); if (mlen == -1) { if (errno != EAGAIN) log_error("ipv6-recv: recv failed: %s", strerror(errno)); return -1; } /* Get source address */ peer->sa_sin6 = *((struct sockaddr_in6 *)(msghdr6.msg_name)); /* Get and check TTL */ for (cm = CMSG_FIRSTHDR(&msghdr6); cm != NULL; cm = CMSG_NXTHDR(&msghdr6, cm)) { if (cm->cmsg_level != IPPROTO_IPV6) continue; if (cm->cmsg_type == IPV6_HOPLIMIT) { memcpy(&ttlval, CMSG_DATA(cm), sizeof(ttlval)); if (ttlval > 255) { log_debug("ipv6-recv: invalid TTL: %u", ttlval); return -1; } *ttl = ttlval; } else if (cm->cmsg_type == IPV6_PKTINFO) { pi6 = (struct in6_pktinfo *)CMSG_DATA(cm); if (pi6) { local->sa_sin6.sin6_family = AF_INET6; local->sa_sin6.sin6_addr = pi6->ipi6_addr; #ifdef HAVE_STRUCT_SOCKADDR_SA_LEN local->sa_sin6.sin6_len = sizeof(local->sa_sin6); #endif /* HAVE_STRUCT_SOCKADDR_SA_LEN */ *ifindex = pi6->ipi6_ifindex; /* Set scope ID for link local addresses. */ if (IN6_IS_ADDR_LINKLOCAL( &peer->sa_sin6.sin6_addr)) peer->sa_sin6.sin6_scope_id = *ifindex; if (IN6_IS_ADDR_LINKLOCAL( &local->sa_sin6.sin6_addr)) local->sa_sin6.sin6_scope_id = *ifindex; } } } return mlen; } static void bfd_sd_reschedule(struct bfd_vrf_global *bvrf, int sd) { if (sd == bvrf->bg_shop) { THREAD_OFF(bvrf->bg_ev[0]); thread_add_read(master, bfd_recv_cb, bvrf, bvrf->bg_shop, &bvrf->bg_ev[0]); } else if (sd == bvrf->bg_mhop) { THREAD_OFF(bvrf->bg_ev[1]); thread_add_read(master, bfd_recv_cb, bvrf, bvrf->bg_mhop, &bvrf->bg_ev[1]); } else if (sd == bvrf->bg_shop6) { THREAD_OFF(bvrf->bg_ev[2]); thread_add_read(master, bfd_recv_cb, bvrf, bvrf->bg_shop6, &bvrf->bg_ev[2]); } else if (sd == bvrf->bg_mhop6) { THREAD_OFF(bvrf->bg_ev[3]); thread_add_read(master, bfd_recv_cb, bvrf, bvrf->bg_mhop6, &bvrf->bg_ev[3]); } else if (sd == bvrf->bg_echo) { THREAD_OFF(bvrf->bg_ev[4]); thread_add_read(master, bfd_recv_cb, bvrf, bvrf->bg_echo, &bvrf->bg_ev[4]); } else if (sd == bvrf->bg_echov6) { THREAD_OFF(bvrf->bg_ev[5]); thread_add_read(master, bfd_recv_cb, bvrf, bvrf->bg_echov6, &bvrf->bg_ev[5]); } } static void cp_debug(bool mhop, struct sockaddr_any *peer, struct sockaddr_any *local, ifindex_t ifindex, vrf_id_t vrfid, const char *fmt, ...) { char buf[512], peerstr[128], localstr[128], portstr[64], vrfstr[64]; va_list vl; if (peer->sa_sin.sin_family) snprintf(peerstr, sizeof(peerstr), " peer:%s", satostr(peer)); else peerstr[0] = 0; if (local->sa_sin.sin_family) snprintf(localstr, sizeof(localstr), " local:%s", satostr(local)); else localstr[0] = 0; if (ifindex != IFINDEX_INTERNAL) snprintf(portstr, sizeof(portstr), " port:%u", ifindex); else portstr[0] = 0; if (vrfid != VRF_DEFAULT) snprintf(vrfstr, sizeof(vrfstr), " vrf:%u", vrfid); else vrfstr[0] = 0; va_start(vl, fmt); vsnprintf(buf, sizeof(buf), fmt, vl); va_end(vl); log_debug("control-packet: %s [mhop:%s%s%s%s%s]", buf, mhop ? "yes" : "no", peerstr, localstr, portstr, vrfstr); } int bfd_recv_cb(struct thread *t) { int sd = THREAD_FD(t); struct bfd_session *bfd; struct bfd_pkt *cp; bool is_mhop; ssize_t mlen = 0; uint8_t ttl = 0; vrf_id_t vrfid = VRF_DEFAULT; ifindex_t ifindex = IFINDEX_INTERNAL; struct sockaddr_any local, peer; uint8_t msgbuf[1516]; struct bfd_vrf_global *bvrf = THREAD_ARG(t); if (bvrf) vrfid = bvrf->vrf->vrf_id; /* Schedule next read. */ bfd_sd_reschedule(bvrf, sd); /* Handle echo packets. */ if (sd == bvrf->bg_echo || sd == bvrf->bg_echov6) { ptm_bfd_process_echo_pkt(bvrf, sd); return 0; } /* Sanitize input/output. */ memset(&local, 0, sizeof(local)); memset(&peer, 0, sizeof(peer)); /* Handle control packets. */ is_mhop = false; if (sd == bvrf->bg_shop || sd == bvrf->bg_mhop) { is_mhop = sd == bvrf->bg_mhop; mlen = bfd_recv_ipv4(sd, msgbuf, sizeof(msgbuf), &ttl, &ifindex, &local, &peer); } else if (sd == bvrf->bg_shop6 || sd == bvrf->bg_mhop6) { is_mhop = sd == bvrf->bg_mhop6; mlen = bfd_recv_ipv6(sd, msgbuf, sizeof(msgbuf), &ttl, &ifindex, &local, &peer); } /* Implement RFC 5880 6.8.6 */ if (mlen < BFD_PKT_LEN) { cp_debug(is_mhop, &peer, &local, ifindex, vrfid, "too small (%ld bytes)", mlen); return 0; } /* Validate packet TTL. */ if ((!is_mhop) && (ttl != BFD_TTL_VAL)) { cp_debug(is_mhop, &peer, &local, ifindex, vrfid, "invalid TTL: %d expected %d", ttl, BFD_TTL_VAL); return 0; } /* * Parse the control header for inconsistencies: * - Invalid version; * - Bad multiplier configuration; * - Short packets; * - Invalid discriminator; */ cp = (struct bfd_pkt *)(msgbuf); if (BFD_GETVER(cp->diag) != BFD_VERSION) { cp_debug(is_mhop, &peer, &local, ifindex, vrfid, "bad version %d", BFD_GETVER(cp->diag)); return 0; } if (cp->detect_mult == 0) { cp_debug(is_mhop, &peer, &local, ifindex, vrfid, "detect multiplier set to zero"); return 0; } if ((cp->len < BFD_PKT_LEN) || (cp->len > mlen)) { cp_debug(is_mhop, &peer, &local, ifindex, vrfid, "too small"); return 0; } if (cp->discrs.my_discr == 0) { cp_debug(is_mhop, &peer, &local, ifindex, vrfid, "'my discriminator' is zero"); return 0; } /* Find the session that this packet belongs. */ bfd = ptm_bfd_sess_find(cp, &peer, &local, ifindex, vrfid, is_mhop); if (bfd == NULL) { cp_debug(is_mhop, &peer, &local, ifindex, vrfid, "no session found"); return 0; } bfd->stats.rx_ctrl_pkt++; /* * Multi hop: validate packet TTL. * Single hop: set local address that received the packet. */ if (is_mhop) { if ((BFD_TTL_VAL - bfd->mh_ttl) > BFD_TTL_VAL) { cp_debug(is_mhop, &peer, &local, ifindex, vrfid, "exceeded max hop count (expected %d, got %d)", bfd->mh_ttl, BFD_TTL_VAL); return 0; } } else if (bfd->local_address.sa_sin.sin_family == AF_UNSPEC) { bfd->local_address = local; } /* * If no interface was detected, save the interface where the * packet came in. */ if (bfd->ifp == NULL) bfd->ifp = if_lookup_by_index(ifindex, vrfid); /* Log remote discriminator changes. */ if ((bfd->discrs.remote_discr != 0) && (bfd->discrs.remote_discr != ntohl(cp->discrs.my_discr))) cp_debug(is_mhop, &peer, &local, ifindex, vrfid, "remote discriminator mismatch (expected %u, got %u)", bfd->discrs.remote_discr, ntohl(cp->discrs.my_discr)); bfd->discrs.remote_discr = ntohl(cp->discrs.my_discr); /* Save remote diagnostics before state switch. */ bfd->remote_diag = cp->diag & BFD_DIAGMASK; /* Update remote timers settings. */ bfd->remote_timers.desired_min_tx = ntohl(cp->timers.desired_min_tx); bfd->remote_timers.required_min_rx = ntohl(cp->timers.required_min_rx); bfd->remote_timers.required_min_echo = ntohl(cp->timers.required_min_echo); bfd->remote_detect_mult = cp->detect_mult; if (BFD_GETCBIT(cp->flags)) bfd->remote_cbit = 1; else bfd->remote_cbit = 0; /* State switch from section 6.2. */ bs_state_handler(bfd, BFD_GETSTATE(cp->flags)); /* RFC 5880, Section 6.5: handle POLL/FINAL negotiation sequence. */ if (bfd->polling && BFD_GETFBIT(cp->flags)) { /* Disable pooling. */ bfd->polling = 0; /* Handle poll finalization. */ bs_final_handler(bfd); } else { /* Received a packet, lets update the receive timer. */ bfd_recvtimer_update(bfd); } /* Handle echo timers changes. */ bs_echo_timer_handler(bfd); /* * We've received a packet with the POLL bit set, we must send * a control packet back with the FINAL bit set. * * RFC 5880, Section 6.5. */ if (BFD_GETPBIT(cp->flags)) { /* We are finalizing a poll negotiation. */ bs_final_handler(bfd); /* Send the control packet with the final bit immediately. */ ptm_bfd_snd(bfd, 1); } return 0; } /* * bp_bfd_echo_in: proccesses an BFD echo packet. On TTL == BFD_TTL_VAL * the packet is looped back or returns the my discriminator ID along * with the TTL. * * Returns -1 on error or loopback or 0 on success. */ int bp_bfd_echo_in(struct bfd_vrf_global *bvrf, int sd, uint8_t *ttl, uint32_t *my_discr) { struct bfd_echo_pkt *bep; ssize_t rlen; struct sockaddr_any local, peer; ifindex_t ifindex = IFINDEX_INTERNAL; vrf_id_t vrfid = VRF_DEFAULT; uint8_t msgbuf[1516]; if (sd == bvrf->bg_echo) rlen = bfd_recv_ipv4(sd, msgbuf, sizeof(msgbuf), ttl, &ifindex, &local, &peer); else rlen = bfd_recv_ipv6(sd, msgbuf, sizeof(msgbuf), ttl, &ifindex, &local, &peer); /* Short packet, better not risk reading it. */ if (rlen < (ssize_t)sizeof(*bep)) { cp_debug(false, &peer, &local, ifindex, vrfid, "small echo packet"); return -1; } /* Test for loopback. */ if (*ttl == BFD_TTL_VAL) { bp_udp_send(sd, *ttl - 1, msgbuf, rlen, (struct sockaddr *)&peer, (sd == bvrf->bg_echo) ? sizeof(peer.sa_sin) : sizeof(peer.sa_sin6)); return -1; } /* Read my discriminator from BFD Echo packet. */ bep = (struct bfd_echo_pkt *)msgbuf; *my_discr = ntohl(bep->my_discr); if (*my_discr == 0) { cp_debug(false, &peer, &local, ifindex, vrfid, "invalid echo packet discriminator (zero)"); return -1; } return 0; } int bp_udp_send(int sd, uint8_t ttl, uint8_t *data, size_t datalen, struct sockaddr *to, socklen_t tolen) { struct cmsghdr *cmsg; ssize_t wlen; int ttlval = ttl; bool is_ipv6 = to->sa_family == AF_INET6; struct msghdr msg; struct iovec iov[1]; uint8_t msgctl[255]; /* Prepare message data. */ iov[0].iov_base = data; iov[0].iov_len = datalen; memset(&msg, 0, sizeof(msg)); memset(msgctl, 0, sizeof(msgctl)); msg.msg_name = to; msg.msg_namelen = tolen; msg.msg_iov = iov; msg.msg_iovlen = 1; /* Prepare the packet TTL information. */ if (ttl > 0) { /* Use ancillary data. */ msg.msg_control = msgctl; msg.msg_controllen = CMSG_LEN(sizeof(ttlval)); /* Configure the ancillary data. */ cmsg = CMSG_FIRSTHDR(&msg); cmsg->cmsg_len = CMSG_LEN(sizeof(ttlval)); if (is_ipv6) { cmsg->cmsg_level = IPPROTO_IPV6; cmsg->cmsg_type = IPV6_HOPLIMIT; } else { #if BFD_LINUX cmsg->cmsg_level = IPPROTO_IP; cmsg->cmsg_type = IP_TTL; #else /* FreeBSD does not support TTL in ancillary data. */ msg.msg_control = NULL; msg.msg_controllen = 0; bp_set_ttl(sd, ttl); #endif /* BFD_BSD */ } memcpy(CMSG_DATA(cmsg), &ttlval, sizeof(ttlval)); } /* Send echo back. */ wlen = sendmsg(sd, &msg, 0); if (wlen <= 0) { log_debug("udp-send: loopback failure: (%d) %s", errno, strerror(errno)); return -1; } else if (wlen < (ssize_t)datalen) { log_debug("udp-send: partial send: %ld expected %ld", wlen, datalen); return -1; } return 0; } /* * Sockets creation. */ /* * IPv4 sockets */ int bp_set_ttl(int sd, uint8_t value) { int ttl = value; if (setsockopt(sd, IPPROTO_IP, IP_TTL, &ttl, sizeof(ttl)) == -1) { log_warning("set-ttl: setsockopt(IP_TTL, %d): %s", value, strerror(errno)); return -1; } return 0; } int bp_set_tos(int sd, uint8_t value) { int tos = value; if (setsockopt(sd, IPPROTO_IP, IP_TOS, &tos, sizeof(tos)) == -1) { log_warning("set-tos: setsockopt(IP_TOS, %d): %s", value, strerror(errno)); return -1; } return 0; } static void bp_set_ipopts(int sd) { int rcvttl = BFD_RCV_TTL_VAL; if (bp_set_ttl(sd, BFD_TTL_VAL) != 0) log_fatal("set-ipopts: TTL configuration failed"); if (setsockopt(sd, IPPROTO_IP, IP_RECVTTL, &rcvttl, sizeof(rcvttl)) == -1) log_fatal("set-ipopts: setsockopt(IP_RECVTTL, %d): %s", rcvttl, strerror(errno)); #ifdef BFD_LINUX int pktinfo = BFD_PKT_INFO_VAL; /* Figure out address and interface to do the peer matching. */ if (setsockopt(sd, IPPROTO_IP, IP_PKTINFO, &pktinfo, sizeof(pktinfo)) == -1) log_fatal("set-ipopts: setsockopt(IP_PKTINFO, %d): %s", pktinfo, strerror(errno)); #endif /* BFD_LINUX */ #ifdef BFD_BSD int yes = 1; /* Find out our address for peer matching. */ if (setsockopt(sd, IPPROTO_IP, IP_RECVDSTADDR, &yes, sizeof(yes)) == -1) log_fatal("set-ipopts: setsockopt(IP_RECVDSTADDR, %d): %s", yes, strerror(errno)); /* Find out interface where the packet came in. */ if (setsockopt_ifindex(AF_INET, sd, yes) == -1) log_fatal("set-ipopts: setsockopt_ipv4_ifindex(%d): %s", yes, strerror(errno)); #endif /* BFD_BSD */ } static void bp_bind_ip(int sd, uint16_t port) { struct sockaddr_in sin; memset(&sin, 0, sizeof(sin)); sin.sin_family = AF_INET; sin.sin_addr.s_addr = htonl(INADDR_ANY); sin.sin_port = htons(port); if (bind(sd, (struct sockaddr *)&sin, sizeof(sin)) == -1) log_fatal("bind-ip: bind: %s", strerror(errno)); } int bp_udp_shop(const struct vrf *vrf) { int sd; frr_with_privs(&bglobal.bfdd_privs) { sd = vrf_socket(AF_INET, SOCK_DGRAM, PF_UNSPEC, vrf->vrf_id, vrf->name); } if (sd == -1) log_fatal("udp-shop: socket: %s", strerror(errno)); bp_set_ipopts(sd); bp_bind_ip(sd, BFD_DEFDESTPORT); return sd; } int bp_udp_mhop(const struct vrf *vrf) { int sd; frr_with_privs(&bglobal.bfdd_privs) { sd = vrf_socket(AF_INET, SOCK_DGRAM, PF_UNSPEC, vrf->vrf_id, vrf->name); } if (sd == -1) log_fatal("udp-mhop: socket: %s", strerror(errno)); bp_set_ipopts(sd); bp_bind_ip(sd, BFD_DEF_MHOP_DEST_PORT); return sd; } int bp_peer_socket(const struct bfd_session *bs) { int sd, pcount; struct sockaddr_in sin; static int srcPort = BFD_SRCPORTINIT; const char *device_to_bind = NULL; if (bs->key.ifname[0]) device_to_bind = (const char *)bs->key.ifname; else if (BFD_CHECK_FLAG(bs->flags, BFD_SESS_FLAG_MH) && bs->key.vrfname[0]) device_to_bind = (const char *)bs->key.vrfname; frr_with_privs(&bglobal.bfdd_privs) { sd = vrf_socket(AF_INET, SOCK_DGRAM, PF_UNSPEC, bs->vrf->vrf_id, device_to_bind); } if (sd == -1) { log_error("ipv4-new: failed to create socket: %s", strerror(errno)); return -1; } /* Set TTL to 255 for all transmitted packets */ if (bp_set_ttl(sd, BFD_TTL_VAL) != 0) { close(sd); return -1; } /* Set TOS to CS6 for all transmitted packets */ if (bp_set_tos(sd, BFD_TOS_VAL) != 0) { close(sd); return -1; } /* Find an available source port in the proper range */ memset(&sin, 0, sizeof(sin)); sin.sin_family = AF_INET; #ifdef HAVE_STRUCT_SOCKADDR_SA_LEN sin.sin_len = sizeof(sin); #endif /* HAVE_STRUCT_SOCKADDR_SA_LEN */ memcpy(&sin.sin_addr, &bs->key.local, sizeof(sin.sin_addr)); if (BFD_CHECK_FLAG(bs->flags, BFD_SESS_FLAG_MH) == 0) sin.sin_addr.s_addr = INADDR_ANY; pcount = 0; do { if ((++pcount) > (BFD_SRCPORTMAX - BFD_SRCPORTINIT)) { /* Searched all ports, none available */ log_error("ipv4-new: failed to bind port: %s", strerror(errno)); close(sd); return -1; } if (srcPort >= BFD_SRCPORTMAX) srcPort = BFD_SRCPORTINIT; sin.sin_port = htons(srcPort++); } while (bind(sd, (struct sockaddr *)&sin, sizeof(sin)) < 0); return sd; } /* * IPv6 sockets */ int bp_peer_socketv6(const struct bfd_session *bs) { int sd, pcount; struct sockaddr_in6 sin6; static int srcPort = BFD_SRCPORTINIT; const char *device_to_bind = NULL; if (bs->key.ifname[0]) device_to_bind = (const char *)bs->key.ifname; else if (BFD_CHECK_FLAG(bs->flags, BFD_SESS_FLAG_MH) && bs->key.vrfname[0]) device_to_bind = (const char *)bs->key.vrfname; frr_with_privs(&bglobal.bfdd_privs) { sd = vrf_socket(AF_INET6, SOCK_DGRAM, PF_UNSPEC, bs->vrf->vrf_id, device_to_bind); } if (sd == -1) { log_error("ipv6-new: failed to create socket: %s", strerror(errno)); return -1; } /* Set TTL to 255 for all transmitted packets */ if (bp_set_ttlv6(sd, BFD_TTL_VAL) != 0) { close(sd); return -1; } /* Set TOS to CS6 for all transmitted packets */ if (bp_set_tosv6(sd, BFD_TOS_VAL) != 0) { close(sd); return -1; } /* Find an available source port in the proper range */ memset(&sin6, 0, sizeof(sin6)); sin6.sin6_family = AF_INET6; #ifdef HAVE_STRUCT_SOCKADDR_SA_LEN sin6.sin6_len = sizeof(sin6); #endif /* HAVE_STRUCT_SOCKADDR_SA_LEN */ memcpy(&sin6.sin6_addr, &bs->key.local, sizeof(sin6.sin6_addr)); if (IN6_IS_ADDR_LINKLOCAL(&sin6.sin6_addr)) sin6.sin6_scope_id = bs->ifp->ifindex; pcount = 0; do { if ((++pcount) > (BFD_SRCPORTMAX - BFD_SRCPORTINIT)) { /* Searched all ports, none available */ log_error("ipv6-new: failed to bind port: %s", strerror(errno)); close(sd); return -1; } if (srcPort >= BFD_SRCPORTMAX) srcPort = BFD_SRCPORTINIT; sin6.sin6_port = htons(srcPort++); } while (bind(sd, (struct sockaddr *)&sin6, sizeof(sin6)) < 0); return sd; } int bp_set_ttlv6(int sd, uint8_t value) { int ttl = value; if (setsockopt(sd, IPPROTO_IPV6, IPV6_UNICAST_HOPS, &ttl, sizeof(ttl)) == -1) { log_warning("set-ttlv6: setsockopt(IPV6_UNICAST_HOPS, %d): %s", value, strerror(errno)); return -1; } return 0; } int bp_set_tosv6(int sd, uint8_t value) { int tos = value; if (setsockopt(sd, IPPROTO_IPV6, IPV6_TCLASS, &tos, sizeof(tos)) == -1) { log_warning("set-tosv6: setsockopt(IPV6_TCLASS, %d): %s", value, strerror(errno)); return -1; } return 0; } static void bp_set_ipv6opts(int sd) { int ipv6_pktinfo = BFD_IPV6_PKT_INFO_VAL; int ipv6_only = BFD_IPV6_ONLY_VAL; if (bp_set_ttlv6(sd, BFD_TTL_VAL) == -1) log_fatal("set-ipv6opts: setsockopt(IPV6_UNICAST_HOPS, %d): %s", BFD_TTL_VAL, strerror(errno)); if (setsockopt_ipv6_hoplimit(sd, BFD_RCV_TTL_VAL) == -1) log_fatal("set-ipv6opts: setsockopt(IPV6_HOPLIMIT, %d): %s", BFD_RCV_TTL_VAL, strerror(errno)); if (setsockopt_ipv6_pktinfo(sd, ipv6_pktinfo) == -1) log_fatal("set-ipv6opts: setsockopt(IPV6_PKTINFO, %d): %s", ipv6_pktinfo, strerror(errno)); if (setsockopt(sd, IPPROTO_IPV6, IPV6_V6ONLY, &ipv6_only, sizeof(ipv6_only)) == -1) log_fatal("set-ipv6opts: setsockopt(IPV6_V6ONLY, %d): %s", ipv6_only, strerror(errno)); } static void bp_bind_ipv6(int sd, uint16_t port) { struct sockaddr_in6 sin6; memset(&sin6, 0, sizeof(sin6)); sin6.sin6_family = AF_INET6; sin6.sin6_addr = in6addr_any; sin6.sin6_port = htons(port); #ifdef HAVE_STRUCT_SOCKADDR_SA_LEN sin6.sin6_len = sizeof(sin6); #endif /* HAVE_STRUCT_SOCKADDR_SA_LEN */ if (bind(sd, (struct sockaddr *)&sin6, sizeof(sin6)) == -1) log_fatal("bind-ipv6: bind: %s", strerror(errno)); } int bp_udp6_shop(const struct vrf *vrf) { int sd; frr_with_privs(&bglobal.bfdd_privs) { sd = vrf_socket(AF_INET6, SOCK_DGRAM, PF_UNSPEC, vrf->vrf_id, vrf->name); } if (sd == -1) log_fatal("udp6-shop: socket: %s", strerror(errno)); bp_set_ipv6opts(sd); bp_bind_ipv6(sd, BFD_DEFDESTPORT); return sd; } int bp_udp6_mhop(const struct vrf *vrf) { int sd; frr_with_privs(&bglobal.bfdd_privs) { sd = vrf_socket(AF_INET6, SOCK_DGRAM, PF_UNSPEC, vrf->vrf_id, vrf->name); } if (sd == -1) log_fatal("udp6-mhop: socket: %s", strerror(errno)); bp_set_ipv6opts(sd); bp_bind_ipv6(sd, BFD_DEF_MHOP_DEST_PORT); return sd; } int bp_echo_socket(const struct vrf *vrf) { int s; frr_with_privs(&bglobal.bfdd_privs) { s = vrf_socket(AF_INET, SOCK_DGRAM, 0, vrf->vrf_id, vrf->name); } if (s == -1) log_fatal("echo-socket: socket: %s", strerror(errno)); bp_set_ipopts(s); bp_bind_ip(s, BFD_DEF_ECHO_PORT); return s; } int bp_echov6_socket(const struct vrf *vrf) { int s; frr_with_privs(&bglobal.bfdd_privs) { s = vrf_socket(AF_INET6, SOCK_DGRAM, 0, vrf->vrf_id, vrf->name); } if (s == -1) log_fatal("echov6-socket: socket: %s", strerror(errno)); bp_set_ipv6opts(s); bp_bind_ipv6(s, BFD_DEF_ECHO_PORT); return s; } frr-7.2.1/bfdd/bfdctl.h0000644000000000000000000000731313610377563011526 00000000000000/********************************************************************* * Copyright 2017-2018 Network Device Education Foundation, Inc. ("NetDEF") * * This program is free software; you can redistribute it and/or modify it * under the terms of the 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; see the file COPYING; if not, write to the Free Software * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA * * bfdctl.h: all BFDd control socket protocol definitions. * * Authors * ------- * Rafael Zalamena */ #ifndef _BFDCTRL_H_ #define _BFDCTRL_H_ #include #include #include /* * Auxiliary definitions */ struct sockaddr_any { union { struct sockaddr_in sa_sin; struct sockaddr_in6 sa_sin6; }; }; #ifndef MAXNAMELEN #define MAXNAMELEN 32 #endif #define BPC_DEF_DETECTMULTIPLIER 3 #define BPC_DEF_RECEIVEINTERVAL 300 /* milliseconds */ #define BPC_DEF_TRANSMITINTERVAL 300 /* milliseconds */ #define BPC_DEF_ECHOINTERVAL 50 /* milliseconds */ /* Peer status */ enum bfd_peer_status { BPS_SHUTDOWN = 0, /* == PTM_BFD_ADM_DOWN, "adm-down" */ BPS_DOWN = 1, /* == PTM_BFD_DOWN, "down" */ BPS_INIT = 2, /* == PTM_BFD_INIT, "init" */ BPS_UP = 3, /* == PTM_BFD_UP, "up" */ }; struct bfd_peer_cfg { bool bpc_mhop; bool bpc_ipv4; struct sockaddr_any bpc_peer; struct sockaddr_any bpc_local; bool bpc_has_label; char bpc_label[MAXNAMELEN]; bool bpc_has_localif; char bpc_localif[MAXNAMELEN + 1]; bool bpc_has_vrfname; char bpc_vrfname[MAXNAMELEN + 1]; bool bpc_has_detectmultiplier; uint8_t bpc_detectmultiplier; bool bpc_has_recvinterval; uint64_t bpc_recvinterval; bool bpc_has_txinterval; uint64_t bpc_txinterval; bool bpc_has_echointerval; uint64_t bpc_echointerval; bool bpc_echo; bool bpc_createonly; bool bpc_shutdown; bool bpc_cbit; /* Status information */ enum bfd_peer_status bpc_bps; uint32_t bpc_id; uint32_t bpc_remoteid; uint8_t bpc_diag; uint8_t bpc_remotediag; uint8_t bpc_remote_detectmultiplier; uint64_t bpc_remote_recvinterval; uint64_t bpc_remote_txinterval; uint64_t bpc_remote_echointerval; uint64_t bpc_lastevent; }; /* * Protocol definitions */ enum bc_msg_version { BMV_VERSION_1 = 1, }; enum bc_msg_type { BMT_RESPONSE = 1, BMT_REQUEST_ADD = 2, BMT_REQUEST_DEL = 3, BMT_NOTIFY = 4, BMT_NOTIFY_ADD = 5, BMT_NOTIFY_DEL = 6, }; /* Notify flags to use with bcm_notify. */ #define BCM_NOTIFY_ALL ((uint64_t)-1) #define BCM_NOTIFY_PEER_STATE (1ULL << 0) #define BCM_NOTIFY_CONFIG (1ULL << 1) #define BCM_NOTIFY_NONE 0 /* Response 'status' definitions. */ #define BCM_RESPONSE_OK "ok" #define BCM_RESPONSE_ERROR "error" /* Notify operation. */ #define BCM_NOTIFY_PEER_STATUS "status" #define BCM_NOTIFY_CONFIG_ADD "add" #define BCM_NOTIFY_CONFIG_DELETE "delete" #define BCM_NOTIFY_CONFIG_UPDATE "update" /* Notification special ID. */ #define BCM_NOTIFY_ID 0 struct bfd_control_msg { /* Total length without the header. */ uint32_t bcm_length; /* * Message request/response id. * All requests will have a correspondent response with the * same id. */ uint16_t bcm_id; /* Message type. */ uint8_t bcm_type; /* Message version. */ uint8_t bcm_ver; /* Message payload. */ uint8_t bcm_data[0]; }; #endif frr-7.2.1/bfdd/bfdd.c0000644000000000000000000001277313610377563011170 00000000000000/* * BFD daemon code * Copyright (C) 2018 Network Device Education Foundation, Inc. ("NetDEF") * * FRR is free software; you can redistribute it and/or modify it * under the terms of the GNU General Public License as published by the * Free Software Foundation; either version 2, or (at your option) any * later version. * * FRR 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 FRR; see the file COPYING. If not, write to the Free * Software Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA * 02111-1307, USA. */ #include #include "filter.h" #include "bfd.h" #include "lib/version.h" /* * FRR related code. */ DEFINE_MGROUP(BFDD, "Bidirectional Forwarding Detection Daemon") DEFINE_MTYPE(BFDD, BFDD_CONTROL, "long-lived control socket memory") DEFINE_MTYPE(BFDD, BFDD_NOTIFICATION, "short-lived control notification data") /* Master of threads. */ struct thread_master *master; /* BFDd privileges */ static zebra_capabilities_t _caps_p[] = {ZCAP_BIND, ZCAP_SYS_ADMIN, ZCAP_NET_RAW}; /* BFD daemon information. */ static struct frr_daemon_info bfdd_di; void socket_close(int *s) { if (*s <= 0) return; if (close(*s) != 0) log_error("%s: close(%d): (%d) %s", __func__, *s, errno, strerror(errno)); *s = -1; } static void sigusr1_handler(void) { zlog_rotate(); } static void sigterm_handler(void) { /* Signalize shutdown. */ frr_early_fini(); /* Stop receiving message from zebra. */ bfdd_zclient_stop(); /* Shutdown controller to avoid receiving anymore commands. */ control_shutdown(); /* Shutdown and free all protocol related memory. */ bfd_shutdown(); bfd_vrf_terminate(); /* Terminate and free() FRR related memory. */ frr_fini(); exit(0); } static void sighup_handler(void) { zlog_info("SIGHUP received"); /* Reload config file. */ vty_read_config(NULL, bfdd_di.config_file, config_default); } static struct quagga_signal_t bfd_signals[] = { { .signal = SIGUSR1, .handler = &sigusr1_handler, }, { .signal = SIGTERM, .handler = &sigterm_handler, }, { .signal = SIGINT, .handler = &sigterm_handler, }, { .signal = SIGHUP, .handler = &sighup_handler, }, }; static const struct frr_yang_module_info *bfdd_yang_modules[] = { &frr_interface_info, &frr_bfdd_info, }; FRR_DAEMON_INFO(bfdd, BFD, .vty_port = 2617, .proghelp = "Implementation of the BFD protocol.", .signals = bfd_signals, .n_signals = array_size(bfd_signals), .privs = &bglobal.bfdd_privs, .yang_modules = bfdd_yang_modules, .n_yang_modules = array_size(bfdd_yang_modules)) #define OPTION_CTLSOCK 1001 static struct option longopts[] = { {"bfdctl", required_argument, NULL, OPTION_CTLSOCK}, {0} }; /* * BFD daemon related code. */ struct bfd_global bglobal; struct bfd_diag_str_list diag_list[] = { {.str = "control-expired", .type = BD_CONTROL_EXPIRED}, {.str = "echo-failed", .type = BD_ECHO_FAILED}, {.str = "neighbor-down", .type = BD_NEIGHBOR_DOWN}, {.str = "forwarding-reset", .type = BD_FORWARDING_RESET}, {.str = "path-down", .type = BD_PATH_DOWN}, {.str = "concatenated-path-down", .type = BD_CONCATPATH_DOWN}, {.str = "administratively-down", .type = BD_ADMIN_DOWN}, {.str = "reverse-concat-path-down", .type = BD_REVCONCATPATH_DOWN}, {.str = NULL}, }; struct bfd_state_str_list state_list[] = { {.str = "admin-down", .type = PTM_BFD_ADM_DOWN}, {.str = "down", .type = PTM_BFD_DOWN}, {.str = "init", .type = PTM_BFD_INIT}, {.str = "up", .type = PTM_BFD_UP}, {.str = NULL}, }; static void bg_init(void) { struct zebra_privs_t bfdd_privs = { #if defined(FRR_USER) && defined(FRR_GROUP) .user = FRR_USER, .group = FRR_GROUP, #endif #if defined(VTY_GROUP) .vty_group = VTY_GROUP, #endif .caps_p = _caps_p, .cap_num_p = array_size(_caps_p), .cap_num_i = 0, }; TAILQ_INIT(&bglobal.bg_bcslist); TAILQ_INIT(&bglobal.bg_obslist); memcpy(&bglobal.bfdd_privs, &bfdd_privs, sizeof(bfdd_privs)); } int main(int argc, char *argv[]) { char ctl_path[512]; bool ctlsockused = false; int opt; /* Initialize system sockets. */ bg_init(); frr_preinit(&bfdd_di, argc, argv); frr_opt_add("", longopts, " --bfdctl Specify bfdd control socket\n"); snprintf(ctl_path, sizeof(ctl_path), BFDD_CONTROL_SOCKET, "", ""); while (true) { opt = frr_getopt(argc, argv, NULL); if (opt == EOF) break; switch (opt) { case OPTION_CTLSOCK: strlcpy(ctl_path, optarg, sizeof(ctl_path)); ctlsockused = true; break; default: frr_help_exit(1); break; } } if (bfdd_di.pathspace && !ctlsockused) snprintf(ctl_path, sizeof(ctl_path), BFDD_CONTROL_SOCKET, "/", bfdd_di.pathspace); #if 0 /* TODO add support for JSON configuration files. */ parse_config(conf); #endif /* Initialize logging API. */ log_init(1, BLOG_DEBUG, &bfdd_di); /* Initialize control socket. */ control_init(ctl_path); /* Initialize FRR infrastructure. */ master = frr_init(); /* Initialize BFD data structures. */ bfd_initialize(); bfd_vrf_init(); access_list_init(); /* Initialize zebra connection. */ bfdd_zclient_init(&bglobal.bfdd_privs); thread_add_read(master, control_accept, NULL, bglobal.bg_csock, &bglobal.bg_csockev); /* Install commands. */ bfdd_vty_init(); /* read configuration file and daemonize */ frr_config_fork(); frr_run(master); /* NOTREACHED */ return 0; } frr-7.2.1/bfdd/bfdd.conf.sample0000644000000000000000000000004713610377563013142 00000000000000password zebra ! log stdout ! line vty frr-7.2.1/bfdd/bfdd_cli.c0000644000000000000000000002452113610377563012011 00000000000000/* * BFD daemon CLI implementation. * * Copyright (C) 2019 Network Device Education Foundation, Inc. ("NetDEF") * Rafael Zalamena * * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation; either version 2 of the License, or * (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program; if not, write to the Free Software * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA * 02110-1301 USA. */ #include #include "lib/command.h" #include "lib/log.h" #include "lib/northbound_cli.h" #ifndef VTYSH_EXTRACT_PL #include "bfdd/bfdd_cli_clippy.c" #endif /* VTYSH_EXTRACT_PL */ #include "bfd.h" /* * Definitions. */ #define PEER_STR "Configure peer\n" #define INTERFACE_NAME_STR "Configure interface name to use\n" #define PEER_IPV4_STR "IPv4 peer address\n" #define PEER_IPV6_STR "IPv6 peer address\n" #define MHOP_STR "Configure multihop\n" #define LOCAL_STR "Configure local address\n" #define LOCAL_IPV4_STR "IPv4 local address\n" #define LOCAL_IPV6_STR "IPv6 local address\n" #define LOCAL_INTF_STR "Configure local interface name to use\n" #define VRF_STR "Configure VRF\n" #define VRF_NAME_STR "Configure VRF name\n" /* * Prototypes. */ /* * Functions. */ DEFPY_NOSH( bfd_enter, bfd_enter_cmd, "bfd", "Configure BFD peers\n") { int ret; nb_cli_enqueue_change(vty, "/frr-bfdd:bfdd/bfd", NB_OP_CREATE, NULL); ret = nb_cli_apply_changes(vty, NULL); if (ret == CMD_SUCCESS) VTY_PUSH_XPATH(BFD_NODE, "/frr-bfdd:bfdd/bfd"); return ret; } DEFUN( bfd_config_reset, bfd_config_reset_cmd, "no bfd", NO_STR "Configure BFD peers\n") { nb_cli_enqueue_change(vty, "/frr-bfdd:bfdd/bfd", NB_OP_DESTROY, NULL); return nb_cli_apply_changes(vty, NULL); } void bfd_cli_show_header(struct vty *vty, struct lyd_node *dnode __attribute__((__unused__)), bool show_defaults __attribute__((__unused__))) { vty_out(vty, "!\nbfd\n"); } void bfd_cli_show_header_end(struct vty *vty, struct lyd_node *dnode __attribute__((__unused__))) { vty_out(vty, "!\n"); } DEFPY_NOSH( bfd_peer_enter, bfd_peer_enter_cmd, "peer [{multihop$multihop|local-address |interface IFNAME$ifname|vrf NAME}]", PEER_STR PEER_IPV4_STR PEER_IPV6_STR MHOP_STR LOCAL_STR LOCAL_IPV4_STR LOCAL_IPV6_STR INTERFACE_STR LOCAL_INTF_STR VRF_STR VRF_NAME_STR) { int ret, slen; char source_str[INET6_ADDRSTRLEN]; char xpath[XPATH_MAXLEN], xpath_srcaddr[XPATH_MAXLEN + 32]; if (multihop) snprintf(source_str, sizeof(source_str), "[source-addr='%s']", local_address_str); else source_str[0] = 0; slen = snprintf(xpath, sizeof(xpath), "/frr-bfdd:bfdd/bfd/sessions/%s%s[dest-addr='%s']", multihop ? "multi-hop" : "single-hop", source_str, peer_str); if (ifname) slen += snprintf(xpath + slen, sizeof(xpath) - slen, "[interface='%s']", ifname); else slen += snprintf(xpath + slen, sizeof(xpath) - slen, "[interface='']"); if (vrf) snprintf(xpath + slen, sizeof(xpath) - slen, "[vrf='%s']", vrf); else snprintf(xpath + slen, sizeof(xpath) - slen, "[vrf='%s']", VRF_DEFAULT_NAME); nb_cli_enqueue_change(vty, xpath, NB_OP_CREATE, NULL); if (multihop == NULL && local_address_str != NULL) { snprintf(xpath_srcaddr, sizeof(xpath_srcaddr), "%s/source-addr", xpath); nb_cli_enqueue_change(vty, xpath_srcaddr, NB_OP_MODIFY, local_address_str); } /* Apply settings immediately. */ ret = nb_cli_apply_changes(vty, NULL); if (ret == CMD_SUCCESS) VTY_PUSH_XPATH(BFD_PEER_NODE, xpath); return ret; } DEFPY( bfd_no_peer, bfd_no_peer_cmd, "no peer [{multihop$multihop|local-address |interface IFNAME$ifname|vrf NAME}]", NO_STR PEER_STR PEER_IPV4_STR PEER_IPV6_STR MHOP_STR LOCAL_STR LOCAL_IPV4_STR LOCAL_IPV6_STR INTERFACE_STR LOCAL_INTF_STR VRF_STR VRF_NAME_STR) { int slen; char xpath[XPATH_MAXLEN]; char source_str[INET6_ADDRSTRLEN]; if (multihop) snprintf(source_str, sizeof(source_str), "[source-addr='%s']", local_address_str); else source_str[0] = 0; slen = snprintf(xpath, sizeof(xpath), "/frr-bfdd:bfdd/bfd/sessions/%s%s[dest-addr='%s']", multihop ? "multi-hop" : "single-hop", source_str, peer_str); if (ifname) slen += snprintf(xpath + slen, sizeof(xpath) - slen, "[interface='%s']", ifname); else slen += snprintf(xpath + slen, sizeof(xpath) - slen, "[interface='']"); if (vrf) snprintf(xpath + slen, sizeof(xpath) - slen, "[vrf='%s']", vrf); else snprintf(xpath + slen, sizeof(xpath) - slen, "[vrf='%s']", VRF_DEFAULT_NAME); nb_cli_enqueue_change(vty, xpath, NB_OP_DESTROY, NULL); /* Apply settings immediatly. */ return nb_cli_apply_changes(vty, NULL); } static void _bfd_cli_show_peer(struct vty *vty, struct lyd_node *dnode, bool show_defaults __attribute__((__unused__)), bool mhop) { const char *vrf = yang_dnode_get_string(dnode, "./vrf"); const char *ifname = yang_dnode_get_string(dnode, "./interface"); vty_out(vty, " peer %s", yang_dnode_get_string(dnode, "./dest-addr")); if (mhop) vty_out(vty, " multihop"); if (yang_dnode_exists(dnode, "./source-addr")) vty_out(vty, " local-address %s", yang_dnode_get_string(dnode, "./source-addr")); if (strcmp(vrf, VRF_DEFAULT_NAME)) vty_out(vty, " vrf %s", vrf); if (ifname[0]) vty_out(vty, " interface %s", ifname); vty_out(vty, "\n"); } void bfd_cli_show_single_hop_peer(struct vty *vty, struct lyd_node *dnode, bool show_defaults) { _bfd_cli_show_peer(vty, dnode, show_defaults, false); } void bfd_cli_show_multi_hop_peer(struct vty *vty, struct lyd_node *dnode, bool show_defaults) { _bfd_cli_show_peer(vty, dnode, show_defaults, true); } void bfd_cli_show_peer_end(struct vty *vty, struct lyd_node *dnode __attribute__((__unused__))) { vty_out(vty, " !\n"); } DEFPY( bfd_peer_shutdown, bfd_peer_shutdown_cmd, "[no] shutdown", NO_STR "Disable BFD peer\n") { nb_cli_enqueue_change(vty, "./administrative-down", NB_OP_MODIFY, no ? "false" : "true"); return nb_cli_apply_changes(vty, NULL); } void bfd_cli_show_shutdown(struct vty *vty, struct lyd_node *dnode, bool show_defaults) { if (show_defaults) vty_out(vty, " shutdown\n"); else vty_out(vty, " %sshutdown\n", yang_dnode_get_bool(dnode, NULL) ? "" : "no "); } DEFPY( bfd_peer_mult, bfd_peer_mult_cmd, "detect-multiplier (2-255)$multiplier", "Configure peer detection multiplier\n" "Configure peer detection multiplier value\n") { nb_cli_enqueue_change(vty, "./detection-multiplier", NB_OP_MODIFY, multiplier_str); return nb_cli_apply_changes(vty, NULL); } void bfd_cli_show_mult(struct vty *vty, struct lyd_node *dnode, bool show_defaults) { if (show_defaults) vty_out(vty, " detect-multiplier %d\n", BFD_DEFDETECTMULT); else vty_out(vty, " detect-multiplier %s\n", yang_dnode_get_string(dnode, NULL)); } DEFPY( bfd_peer_rx, bfd_peer_rx_cmd, "receive-interval (10-60000)$interval", "Configure peer receive interval\n" "Configure peer receive interval value in milliseconds\n") { char value[32]; snprintf(value, sizeof(value), "%ld", interval * 1000); nb_cli_enqueue_change(vty, "./required-receive-interval", NB_OP_MODIFY, value); return nb_cli_apply_changes(vty, NULL); } void bfd_cli_show_rx(struct vty *vty, struct lyd_node *dnode, bool show_defaults) { uint32_t value; if (show_defaults) vty_out(vty, " receive-interval %d\n", BFD_DEFREQUIREDMINRX); else { value = yang_dnode_get_uint32(dnode, NULL); vty_out(vty, " receive-interval %" PRIu32 "\n", value / 1000); } } DEFPY( bfd_peer_tx, bfd_peer_tx_cmd, "transmit-interval (10-60000)$interval", "Configure peer transmit interval\n" "Configure peer transmit interval value in milliseconds\n") { char value[32]; snprintf(value, sizeof(value), "%ld", interval * 1000); nb_cli_enqueue_change(vty, "./desired-transmission-interval", NB_OP_MODIFY, value); return nb_cli_apply_changes(vty, NULL); } void bfd_cli_show_tx(struct vty *vty, struct lyd_node *dnode, bool show_defaults) { uint32_t value; if (show_defaults) vty_out(vty, " transmit-interval %d\n", BFD_DEFDESIREDMINTX); else { value = yang_dnode_get_uint32(dnode, NULL); vty_out(vty, " transmit-interval %" PRIu32 "\n", value / 1000); } } DEFPY( bfd_peer_echo, bfd_peer_echo_cmd, "[no] echo-mode", NO_STR "Configure echo mode\n") { nb_cli_enqueue_change(vty, "./echo-mode", NB_OP_MODIFY, no ? "false" : "true"); return nb_cli_apply_changes(vty, NULL); } void bfd_cli_show_echo(struct vty *vty, struct lyd_node *dnode, bool show_defaults) { if (show_defaults) vty_out(vty, " no echo-mode\n"); else vty_out(vty, " %secho-mode\n", yang_dnode_get_bool(dnode, NULL) ? "" : "no "); } DEFPY( bfd_peer_echo_interval, bfd_peer_echo_interval_cmd, "echo-interval (10-60000)$interval", "Configure peer echo interval\n" "Configure peer echo interval value in milliseconds\n") { char value[32]; snprintf(value, sizeof(value), "%ld", interval * 1000); nb_cli_enqueue_change(vty, "./desired-echo-transmission-interval", NB_OP_MODIFY, value); return nb_cli_apply_changes(vty, NULL); } void bfd_cli_show_echo_interval(struct vty *vty, struct lyd_node *dnode, bool show_defaults) { uint32_t value; if (show_defaults) vty_out(vty, " echo-interval %d\n", BFD_DEF_REQ_MIN_ECHO); else { value = yang_dnode_get_uint32(dnode, NULL); vty_out(vty, " echo-interval %" PRIu32 "\n", value / 1000); } } void bfdd_cli_init(void) { install_element(CONFIG_NODE, &bfd_enter_cmd); install_element(CONFIG_NODE, &bfd_config_reset_cmd); install_element(BFD_NODE, &bfd_peer_enter_cmd); install_element(BFD_NODE, &bfd_no_peer_cmd); install_element(BFD_PEER_NODE, &bfd_peer_shutdown_cmd); install_element(BFD_PEER_NODE, &bfd_peer_mult_cmd); install_element(BFD_PEER_NODE, &bfd_peer_rx_cmd); install_element(BFD_PEER_NODE, &bfd_peer_tx_cmd); install_element(BFD_PEER_NODE, &bfd_peer_echo_cmd); install_element(BFD_PEER_NODE, &bfd_peer_echo_interval_cmd); } frr-7.2.1/bfdd/bfdd_northbound.c0000644000000000000000000010015713610377563013424 00000000000000/* * BFD daemon northbound implementation. * * Copyright (C) 2019 Network Device Education Foundation, Inc. ("NetDEF") * Rafael Zalamena * * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation; either version 2 of the License, or * (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program; if not, write to the Free Software * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA * 02110-1301 USA. */ #include #include "lib/log.h" #include "lib/northbound.h" #include "bfd.h" /* * Helpers. */ static void bfd_session_get_key(bool mhop, const struct lyd_node *dnode, struct bfd_key *bk) { const char *ifname = NULL, *vrfname = NULL; struct sockaddr_any psa, lsa; /* Required destination parameter. */ strtosa(yang_dnode_get_string(dnode, "./dest-addr"), &psa); /* Get optional source address. */ memset(&lsa, 0, sizeof(lsa)); if (yang_dnode_exists(dnode, "./source-addr")) strtosa(yang_dnode_get_string(dnode, "./source-addr"), &lsa); /* Get optional interface and vrf names. */ if (yang_dnode_exists(dnode, "./interface")) ifname = yang_dnode_get_string(dnode, "./interface"); if (yang_dnode_exists(dnode, "./vrf")) vrfname = yang_dnode_get_string(dnode, "./vrf"); /* Generate the corresponding key. */ gen_bfd_key(bk, &psa, &lsa, mhop, ifname, vrfname); } static int bfd_session_create(enum nb_event event, const struct lyd_node *dnode, union nb_resource *resource, bool mhop) { struct bfd_session *bs; const char *ifname; struct bfd_key bk; struct in6_addr i6a; switch (event) { case NB_EV_VALIDATE: /* * When `dest-addr` is IPv6 and link-local we must * require interface name, otherwise we can't figure * which interface to use to send the packets. * * `memset` `i6a` in case address is IPv4 or non * link-local IPv6, it should also avoid static * analyzer warning about unset memory read. */ memset(&i6a, 0, sizeof(i6a)); yang_dnode_get_ipv6(&i6a, dnode, "./dest-addr"); /* * To support old FRR versions we must allow empty * interface to be specified, however that should * change in the future. */ if (yang_dnode_exists(dnode, "./interface")) ifname = yang_dnode_get_string(dnode, "./interface"); else ifname = ""; if (IN6_IS_ADDR_LINKLOCAL(&i6a) && strlen(ifname) == 0) { zlog_warn("%s: when using link-local you must specify " "an interface.", __func__); return NB_ERR_VALIDATION; } break; case NB_EV_PREPARE: bfd_session_get_key(mhop, dnode, &bk); bs = bfd_key_lookup(bk); /* This session was already configured by another daemon. */ if (bs != NULL) { /* Now it is configured also by CLI. */ BFD_SET_FLAG(bs->flags, BFD_SESS_FLAG_CONFIG); bs->refcount++; resource->ptr = bs; break; } bs = bfd_session_new(); if (bs == NULL) return NB_ERR_RESOURCE; /* Fill the session key. */ bfd_session_get_key(mhop, dnode, &bs->key); /* Set configuration flags. */ bs->refcount = 1; BFD_SET_FLAG(bs->flags, BFD_SESS_FLAG_CONFIG); if (mhop) BFD_SET_FLAG(bs->flags, BFD_SESS_FLAG_MH); if (bs->key.family == AF_INET6) BFD_SET_FLAG(bs->flags, BFD_SESS_FLAG_IPV6); resource->ptr = bs; break; case NB_EV_APPLY: bs = resource->ptr; /* Only attempt to registrate if freshly allocated. */ if (bs->discrs.my_discr == 0 && bs_registrate(bs) == NULL) return NB_ERR_RESOURCE; nb_running_set_entry(dnode, bs); break; case NB_EV_ABORT: bs = resource->ptr; if (bs->refcount <= 1) bfd_session_free(resource->ptr); break; } return NB_OK; } static int bfd_session_destroy(enum nb_event event, const struct lyd_node *dnode, bool mhop) { struct bfd_session *bs; struct bfd_key bk; switch (event) { case NB_EV_VALIDATE: bfd_session_get_key(mhop, dnode, &bk); if (bfd_key_lookup(bk) == NULL) return NB_ERR_INCONSISTENCY; break; case NB_EV_PREPARE: /* NOTHING */ break; case NB_EV_APPLY: bs = nb_running_unset_entry(dnode); /* CLI is not using this session anymore. */ if (BFD_CHECK_FLAG(bs->flags, BFD_SESS_FLAG_CONFIG) == 0) break; BFD_UNSET_FLAG(bs->flags, BFD_SESS_FLAG_CONFIG); bs->refcount--; /* There are still daemons using it. */ if (bs->refcount > 0) break; bfd_session_free(bs); break; case NB_EV_ABORT: /* NOTHING */ break; } return NB_OK; } /* * XPath: /frr-bfdd:bfdd/bfd */ static int bfdd_bfd_create(enum nb_event event, const struct lyd_node *dnode __attribute__((__unused__)), union nb_resource *resource __attribute__((__unused__))) { /* NOTHING */ return NB_OK; } static int bfdd_bfd_destroy(enum nb_event event, const struct lyd_node *dnode) { switch (event) { case NB_EV_VALIDATE: /* NOTHING */ return NB_OK; case NB_EV_PREPARE: /* NOTHING */ return NB_OK; case NB_EV_APPLY: bfd_sessions_remove_manual(); break; case NB_EV_ABORT: /* NOTHING */ return NB_OK; } return NB_OK; } /* * XPath: /frr-bfdd:bfdd/bfd/sessions/single-hop */ static int bfdd_bfd_sessions_single_hop_create(enum nb_event event, const struct lyd_node *dnode, union nb_resource *resource) { return bfd_session_create(event, dnode, resource, false); } static int bfdd_bfd_sessions_single_hop_destroy(enum nb_event event, const struct lyd_node *dnode) { return bfd_session_destroy(event, dnode, false); } static const void * bfdd_bfd_sessions_single_hop_get_next(const void *parent_list_entry __attribute__((__unused__)), const void *list_entry) { return bfd_session_next(list_entry, false); } static int bfdd_bfd_sessions_single_hop_get_keys(const void *list_entry, struct yang_list_keys *keys) { const struct bfd_session *bs = list_entry; char dstbuf[INET6_ADDRSTRLEN]; inet_ntop(bs->key.family, &bs->key.peer, dstbuf, sizeof(dstbuf)); keys->num = 3; strlcpy(keys->key[0], dstbuf, sizeof(keys->key[0])); strlcpy(keys->key[1], bs->key.ifname, sizeof(keys->key[1])); strlcpy(keys->key[2], bs->key.vrfname, sizeof(keys->key[2])); return NB_OK; } static const void * bfdd_bfd_sessions_single_hop_lookup_entry(const void *parent_list_entry __attribute__((__unused__)), const struct yang_list_keys *keys) { const char *dest_addr = keys->key[0]; const char *ifname = keys->key[1]; const char *vrf = keys->key[2]; struct sockaddr_any psa, lsa; struct bfd_key bk; strtosa(dest_addr, &psa); memset(&lsa, 0, sizeof(lsa)); gen_bfd_key(&bk, &psa, &lsa, false, ifname, vrf); return bfd_key_lookup(bk); } /* * XPath: /frr-bfdd:bfdd/bfd/sessions/single-hop/source-addr */ static int bfdd_bfd_sessions_single_hop_source_addr_modify( enum nb_event event __attribute__((__unused__)), const struct lyd_node *dnode __attribute__((__unused__)), union nb_resource *resource __attribute__((__unused__))) { return NB_OK; } static int bfdd_bfd_sessions_single_hop_source_addr_destroy( enum nb_event event __attribute__((__unused__)), const struct lyd_node *dnode __attribute__((__unused__))) { return NB_OK; } /* * XPath: /frr-bfdd:bfdd/bfd/sessions/single-hop/detection-multiplier */ static int bfdd_bfd_sessions_single_hop_detection_multiplier_modify( enum nb_event event, const struct lyd_node *dnode, union nb_resource *resource __attribute__((__unused__))) { uint8_t detection_multiplier = yang_dnode_get_uint8(dnode, NULL); struct bfd_session *bs; switch (event) { case NB_EV_VALIDATE: break; case NB_EV_PREPARE: /* NOTHING */ break; case NB_EV_APPLY: bs = nb_running_get_entry(dnode, NULL, true); bs->detect_mult = detection_multiplier; break; case NB_EV_ABORT: /* NOTHING */ break; } return NB_OK; } /* * XPath: /frr-bfdd:bfdd/bfd/sessions/single-hop/desired-transmission-interval */ static int bfdd_bfd_sessions_single_hop_desired_transmission_interval_modify( enum nb_event event, const struct lyd_node *dnode, union nb_resource *resource __attribute__((__unused__))) { uint32_t tx_interval = yang_dnode_get_uint32(dnode, NULL); struct bfd_session *bs; switch (event) { case NB_EV_VALIDATE: if (tx_interval < 10000 || tx_interval > 60000000) return NB_ERR_VALIDATION; break; case NB_EV_PREPARE: /* NOTHING */ break; case NB_EV_APPLY: bs = nb_running_get_entry(dnode, NULL, true); if (tx_interval == bs->timers.desired_min_tx) return NB_OK; bs->timers.desired_min_tx = tx_interval; bfd_set_polling(bs); break; case NB_EV_ABORT: /* NOTHING */ break; } return NB_OK; } /* * XPath: /frr-bfdd:bfdd/bfd/sessions/single-hop/required-receive-interval */ static int bfdd_bfd_sessions_single_hop_required_receive_interval_modify( enum nb_event event, const struct lyd_node *dnode, union nb_resource *resource __attribute__((__unused__))) { uint32_t rx_interval = yang_dnode_get_uint32(dnode, NULL); struct bfd_session *bs; switch (event) { case NB_EV_VALIDATE: if (rx_interval < 10000 || rx_interval > 60000000) return NB_ERR_VALIDATION; break; case NB_EV_PREPARE: /* NOTHING */ break; case NB_EV_APPLY: bs = nb_running_get_entry(dnode, NULL, true); if (rx_interval == bs->timers.required_min_rx) return NB_OK; bs->timers.required_min_rx = rx_interval; bfd_set_polling(bs); break; case NB_EV_ABORT: /* NOTHING */ break; } return NB_OK; } /* * XPath: /frr-bfdd:bfdd/bfd/sessions/single-hop/administrative-down */ static int bfdd_bfd_sessions_single_hop_administrative_down_modify( enum nb_event event, const struct lyd_node *dnode, union nb_resource *resource __attribute__((__unused__))) { bool shutdown = yang_dnode_get_bool(dnode, NULL); struct bfd_session *bs; switch (event) { case NB_EV_VALIDATE: case NB_EV_PREPARE: return NB_OK; case NB_EV_APPLY: break; case NB_EV_ABORT: return NB_OK; } bs = nb_running_get_entry(dnode, NULL, true); if (shutdown == false) { if (!BFD_CHECK_FLAG(bs->flags, BFD_SESS_FLAG_SHUTDOWN)) return NB_OK; BFD_UNSET_FLAG(bs->flags, BFD_SESS_FLAG_SHUTDOWN); /* Change and notify state change. */ bs->ses_state = PTM_BFD_DOWN; control_notify(bs); /* Enable all timers. */ bfd_recvtimer_update(bs); bfd_xmttimer_update(bs, bs->xmt_TO); if (BFD_CHECK_FLAG(bs->flags, BFD_SESS_FLAG_ECHO)) { bfd_echo_recvtimer_update(bs); bfd_echo_xmttimer_update(bs, bs->echo_xmt_TO); } } else { if (BFD_CHECK_FLAG(bs->flags, BFD_SESS_FLAG_SHUTDOWN)) return NB_OK; BFD_SET_FLAG(bs->flags, BFD_SESS_FLAG_SHUTDOWN); /* Disable all events. */ bfd_recvtimer_delete(bs); bfd_echo_recvtimer_delete(bs); bfd_xmttimer_delete(bs); bfd_echo_xmttimer_delete(bs); /* Change and notify state change. */ bs->ses_state = PTM_BFD_ADM_DOWN; control_notify(bs); ptm_bfd_snd(bs, 0); } return NB_OK; } /* * XPath: /frr-bfdd:bfdd/bfd/sessions/single-hop/echo-mode */ static int bfdd_bfd_sessions_single_hop_echo_mode_modify( enum nb_event event, const struct lyd_node *dnode, union nb_resource *resource __attribute__((__unused__))) { bool echo = yang_dnode_get_bool(dnode, NULL); struct bfd_session *bs; switch (event) { case NB_EV_VALIDATE: case NB_EV_PREPARE: return NB_OK; case NB_EV_APPLY: break; case NB_EV_ABORT: return NB_OK; } bs = nb_running_get_entry(dnode, NULL, true); if (echo == false) { if (!BFD_CHECK_FLAG(bs->flags, BFD_SESS_FLAG_ECHO)) return NB_OK; BFD_UNSET_FLAG(bs->flags, BFD_SESS_FLAG_ECHO); ptm_bfd_echo_stop(bs); } else { if (BFD_CHECK_FLAG(bs->flags, BFD_SESS_FLAG_ECHO)) return NB_OK; BFD_SET_FLAG(bs->flags, BFD_SESS_FLAG_ECHO); /* Apply setting immediately. */ if (!BFD_CHECK_FLAG(bs->flags, BFD_SESS_FLAG_SHUTDOWN)) bs_echo_timer_handler(bs); } return NB_OK; } /* * XPath: * /frr-bfdd:bfdd/bfd/sessions/single-hop/desired-echo-transmission-interval */ static int bfdd_bfd_sessions_single_hop_desired_echo_transmission_interval_modify( enum nb_event event, const struct lyd_node *dnode, union nb_resource *resource __attribute__((__unused__))) { uint32_t echo_interval = yang_dnode_get_uint32(dnode, NULL); struct bfd_session *bs; switch (event) { case NB_EV_VALIDATE: if (echo_interval < 10000 || echo_interval > 60000000) return NB_ERR_VALIDATION; break; case NB_EV_PREPARE: /* NOTHING */ break; case NB_EV_APPLY: bs = nb_running_get_entry(dnode, NULL, true); if (echo_interval == bs->timers.required_min_echo) return NB_OK; bs->timers.required_min_echo = echo_interval; break; case NB_EV_ABORT: /* NOTHING */ break; } return NB_OK; } /* * XPath: /frr-bfdd:bfdd/bfd/sessions/single-hop/stats/local-discriminator */ static struct yang_data * bfdd_bfd_sessions_single_hop_stats_local_discriminator_get_elem( const char *xpath, const void *list_entry) { const struct bfd_session *bs = list_entry; return yang_data_new_uint32(xpath, bs->discrs.my_discr); } /* * XPath: /frr-bfdd:bfdd/bfd/sessions/single-hop/stats/local-state */ static struct yang_data * bfdd_bfd_sessions_single_hop_stats_local_state_get_elem(const char *xpath, const void *list_entry) { const struct bfd_session *bs = list_entry; return yang_data_new_enum(xpath, bs->ses_state); } /* * XPath: /frr-bfdd:bfdd/bfd/sessions/single-hop/stats/local-diagnostic */ static struct yang_data * bfdd_bfd_sessions_single_hop_stats_local_diagnostic_get_elem( const char *xpath, const void *list_entry) { const struct bfd_session *bs = list_entry; return yang_data_new_enum(xpath, bs->local_diag); } /* * XPath: /frr-bfdd:bfdd/bfd/sessions/single-hop/stats/local-multiplier */ static struct yang_data * bfdd_bfd_sessions_single_hop_stats_local_multiplier_get_elem( const char *xpath, const void *list_entry) { const struct bfd_session *bs = list_entry; return yang_data_new_int8(xpath, bs->detect_mult); } /* * XPath: /frr-bfdd:bfdd/bfd/sessions/single-hop/stats/remote-discriminator */ static struct yang_data * bfdd_bfd_sessions_single_hop_stats_remote_discriminator_get_elem( const char *xpath, const void *list_entry) { const struct bfd_session *bs = list_entry; if (bs->discrs.remote_discr == 0) return NULL; return yang_data_new_uint32(xpath, bs->discrs.remote_discr); } /* * XPath: /frr-bfdd:bfdd/bfd/sessions/single-hop/stats/remote-state */ static struct yang_data * bfdd_bfd_sessions_single_hop_stats_remote_state_get_elem(const char *xpath, const void *list_entry) { const struct bfd_session *bs = list_entry; return yang_data_new_enum(xpath, bs->ses_state); } /* * XPath: /frr-bfdd:bfdd/bfd/sessions/single-hop/stats/remote-diagnostic */ static struct yang_data * bfdd_bfd_sessions_single_hop_stats_remote_diagnostic_get_elem( const char *xpath, const void *list_entry) { const struct bfd_session *bs = list_entry; return yang_data_new_enum(xpath, bs->remote_diag); } /* * XPath: /frr-bfdd:bfdd/bfd/sessions/single-hop/stats/remote-multiplier */ static struct yang_data * bfdd_bfd_sessions_single_hop_stats_remote_multiplier_get_elem( const char *xpath, const void *list_entry) { const struct bfd_session *bs = list_entry; return yang_data_new_int8(xpath, bs->remote_detect_mult); } /* * XPath: * /frr-bfdd:bfdd/bfd/sessions/single-hop/stats/negotiated-transmission-interval */ static struct yang_data * bfdd_bfd_sessions_single_hop_stats_negotiated_transmission_interval_get_elem( const char *xpath, const void *list_entry) { const struct bfd_session *bs = list_entry; return yang_data_new_uint32(xpath, bs->remote_timers.desired_min_tx); } /* * XPath: * /frr-bfdd:bfdd/bfd/sessions/single-hop/stats/negotiated-receive-interval */ static struct yang_data * bfdd_bfd_sessions_single_hop_stats_negotiated_receive_interval_get_elem( const char *xpath, const void *list_entry) { const struct bfd_session *bs = list_entry; return yang_data_new_uint32(xpath, bs->remote_timers.required_min_rx); } /* * XPath: /frr-bfdd:bfdd/bfd/sessions/single-hop/stats/detection-mode */ static struct yang_data * bfdd_bfd_sessions_single_hop_stats_detection_mode_get_elem( const char *xpath, const void *list_entry) { const struct bfd_session *bs = list_entry; int detection_mode; /* * Detection mode: * 1. Async with echo * 2. Async without echo * 3. Demand with echo * 4. Demand without echo * * TODO: support demand mode. */ if (BFD_CHECK_FLAG(bs->flags, BFD_SESS_FLAG_ECHO)) detection_mode = 1; else detection_mode = 2; return yang_data_new_enum(xpath, detection_mode); } /* * XPath: /frr-bfdd:bfdd/bfd/sessions/single-hop/stats/last-down-time */ static struct yang_data * bfdd_bfd_sessions_single_hop_stats_last_down_time_get_elem( const char *xpath __attribute__((__unused__)), const void *list_entry __attribute__((__unused__))) { /* * TODO: implement me. * * No yang support for time elements yet. */ return NULL; } /* * XPath: /frr-bfdd:bfdd/bfd/sessions/single-hop/stats/last-up-time */ static struct yang_data * bfdd_bfd_sessions_single_hop_stats_last_up_time_get_elem( const char *xpath __attribute__((__unused__)), const void *list_entry __attribute__((__unused__))) { /* * TODO: implement me. * * No yang support for time elements yet. */ return NULL; } /* * XPath: /frr-bfdd:bfdd/bfd/sessions/single-hop/stats/session-down-count */ static struct yang_data * bfdd_bfd_sessions_single_hop_stats_session_down_count_get_elem( const char *xpath, const void *list_entry) { const struct bfd_session *bs = list_entry; return yang_data_new_uint64(xpath, bs->stats.session_down); } /* * XPath: /frr-bfdd:bfdd/bfd/sessions/single-hop/stats/session-up-count */ static struct yang_data * bfdd_bfd_sessions_single_hop_stats_session_up_count_get_elem( const char *xpath, const void *list_entry) { const struct bfd_session *bs = list_entry; return yang_data_new_uint64(xpath, bs->stats.session_up); } /* * XPath: * /frr-bfdd:bfdd/bfd/sessions/single-hop/stats/control-packet-input-count */ static struct yang_data * bfdd_bfd_sessions_single_hop_stats_control_packet_input_count_get_elem( const char *xpath, const void *list_entry) { const struct bfd_session *bs = list_entry; return yang_data_new_uint64(xpath, bs->stats.rx_ctrl_pkt); } /* * XPath: * /frr-bfdd:bfdd/bfd/sessions/single-hop/stats/control-packet-output-count */ static struct yang_data * bfdd_bfd_sessions_single_hop_stats_control_packet_output_count_get_elem( const char *xpath, const void *list_entry) { const struct bfd_session *bs = list_entry; return yang_data_new_uint64(xpath, bs->stats.tx_ctrl_pkt); } /* * XPath: * /frr-bfdd:bfdd/bfd/sessions/single-hop/stats/negotiated-echo-transmission-interval */ static struct yang_data * bfdd_bfd_sessions_single_hop_stats_negotiated_echo_transmission_interval_get_elem( const char *xpath, const void *list_entry) { const struct bfd_session *bs = list_entry; return yang_data_new_uint32(xpath, bs->remote_timers.required_min_echo); } /* * XPath: /frr-bfdd:bfdd/bfd/sessions/single-hop/stats/echo-packet-input-count */ static struct yang_data * bfdd_bfd_sessions_single_hop_stats_echo_packet_input_count_get_elem( const char *xpath, const void *list_entry) { const struct bfd_session *bs = list_entry; return yang_data_new_uint64(xpath, bs->stats.rx_echo_pkt); } /* * XPath: /frr-bfdd:bfdd/bfd/sessions/single-hop/stats/echo-packet-output-count */ static struct yang_data * bfdd_bfd_sessions_single_hop_stats_echo_packet_output_count_get_elem( const char *xpath, const void *list_entry) { const struct bfd_session *bs = list_entry; return yang_data_new_uint64(xpath, bs->stats.tx_echo_pkt); } /* * XPath: /frr-bfdd:bfdd/bfd/sessions/multi-hop */ static int bfdd_bfd_sessions_multi_hop_create(enum nb_event event, const struct lyd_node *dnode, union nb_resource *resource) { return bfd_session_create(event, dnode, resource, true); } static int bfdd_bfd_sessions_multi_hop_destroy(enum nb_event event, const struct lyd_node *dnode) { return bfd_session_destroy(event, dnode, true); } static const void * bfdd_bfd_sessions_multi_hop_get_next(const void *parent_list_entry __attribute__((__unused__)), const void *list_entry) { return bfd_session_next(list_entry, true); } static int bfdd_bfd_sessions_multi_hop_get_keys(const void *list_entry, struct yang_list_keys *keys) { const struct bfd_session *bs = list_entry; char dstbuf[INET6_ADDRSTRLEN], srcbuf[INET6_ADDRSTRLEN]; inet_ntop(bs->key.family, &bs->key.peer, dstbuf, sizeof(dstbuf)); inet_ntop(bs->key.family, &bs->key.local, srcbuf, sizeof(srcbuf)); keys->num = 4; strlcpy(keys->key[0], srcbuf, sizeof(keys->key[0])); strlcpy(keys->key[1], dstbuf, sizeof(keys->key[1])); strlcpy(keys->key[2], bs->key.ifname, sizeof(keys->key[2])); strlcpy(keys->key[3], bs->key.vrfname, sizeof(keys->key[3])); return NB_OK; } static const void * bfdd_bfd_sessions_multi_hop_lookup_entry(const void *parent_list_entry __attribute__((__unused__)), const struct yang_list_keys *keys) { const char *source_addr = keys->key[0]; const char *dest_addr = keys->key[1]; const char *ifname = keys->key[2]; const char *vrf = keys->key[3]; struct sockaddr_any psa, lsa; struct bfd_key bk; strtosa(dest_addr, &psa); strtosa(source_addr, &lsa); gen_bfd_key(&bk, &psa, &lsa, true, ifname, vrf); return bfd_key_lookup(bk); } /* clang-format off */ const struct frr_yang_module_info frr_bfdd_info = { .name = "frr-bfdd", .nodes = { { .xpath = "/frr-bfdd:bfdd/bfd", .cbs = { .create = bfdd_bfd_create, .destroy = bfdd_bfd_destroy, .cli_show = bfd_cli_show_header, .cli_show_end = bfd_cli_show_header_end, } }, { .xpath = "/frr-bfdd:bfdd/bfd/sessions/single-hop", .cbs = { .create = bfdd_bfd_sessions_single_hop_create, .destroy = bfdd_bfd_sessions_single_hop_destroy, .get_next = bfdd_bfd_sessions_single_hop_get_next, .get_keys = bfdd_bfd_sessions_single_hop_get_keys, .lookup_entry = bfdd_bfd_sessions_single_hop_lookup_entry, .cli_show = bfd_cli_show_single_hop_peer, .cli_show_end = bfd_cli_show_peer_end, } }, { .xpath = "/frr-bfdd:bfdd/bfd/sessions/single-hop/source-addr", .cbs = { .modify = bfdd_bfd_sessions_single_hop_source_addr_modify, .destroy = bfdd_bfd_sessions_single_hop_source_addr_destroy, } }, { .xpath = "/frr-bfdd:bfdd/bfd/sessions/single-hop/detection-multiplier", .cbs = { .modify = bfdd_bfd_sessions_single_hop_detection_multiplier_modify, .cli_show = bfd_cli_show_mult, } }, { .xpath = "/frr-bfdd:bfdd/bfd/sessions/single-hop/desired-transmission-interval", .cbs = { .modify = bfdd_bfd_sessions_single_hop_desired_transmission_interval_modify, .cli_show = bfd_cli_show_tx, } }, { .xpath = "/frr-bfdd:bfdd/bfd/sessions/single-hop/required-receive-interval", .cbs = { .modify = bfdd_bfd_sessions_single_hop_required_receive_interval_modify, .cli_show = bfd_cli_show_rx, } }, { .xpath = "/frr-bfdd:bfdd/bfd/sessions/single-hop/administrative-down", .cbs = { .modify = bfdd_bfd_sessions_single_hop_administrative_down_modify, .cli_show = bfd_cli_show_shutdown, } }, { .xpath = "/frr-bfdd:bfdd/bfd/sessions/single-hop/echo-mode", .cbs = { .modify = bfdd_bfd_sessions_single_hop_echo_mode_modify, .cli_show = bfd_cli_show_echo, } }, { .xpath = "/frr-bfdd:bfdd/bfd/sessions/single-hop/desired-echo-transmission-interval", .cbs = { .modify = bfdd_bfd_sessions_single_hop_desired_echo_transmission_interval_modify, .cli_show = bfd_cli_show_echo_interval, } }, { .xpath = "/frr-bfdd:bfdd/bfd/sessions/single-hop/stats/local-discriminator", .cbs = { .get_elem = bfdd_bfd_sessions_single_hop_stats_local_discriminator_get_elem, } }, { .xpath = "/frr-bfdd:bfdd/bfd/sessions/single-hop/stats/local-state", .cbs = { .get_elem = bfdd_bfd_sessions_single_hop_stats_local_state_get_elem, } }, { .xpath = "/frr-bfdd:bfdd/bfd/sessions/single-hop/stats/local-diagnostic", .cbs = { .get_elem = bfdd_bfd_sessions_single_hop_stats_local_diagnostic_get_elem, } }, { .xpath = "/frr-bfdd:bfdd/bfd/sessions/single-hop/stats/local-multiplier", .cbs = { .get_elem = bfdd_bfd_sessions_single_hop_stats_local_multiplier_get_elem, } }, { .xpath = "/frr-bfdd:bfdd/bfd/sessions/single-hop/stats/remote-discriminator", .cbs = { .get_elem = bfdd_bfd_sessions_single_hop_stats_remote_discriminator_get_elem, } }, { .xpath = "/frr-bfdd:bfdd/bfd/sessions/single-hop/stats/remote-state", .cbs = { .get_elem = bfdd_bfd_sessions_single_hop_stats_remote_state_get_elem, } }, { .xpath = "/frr-bfdd:bfdd/bfd/sessions/single-hop/stats/remote-diagnostic", .cbs = { .get_elem = bfdd_bfd_sessions_single_hop_stats_remote_diagnostic_get_elem, } }, { .xpath = "/frr-bfdd:bfdd/bfd/sessions/single-hop/stats/remote-multiplier", .cbs = { .get_elem = bfdd_bfd_sessions_single_hop_stats_remote_multiplier_get_elem, } }, { .xpath = "/frr-bfdd:bfdd/bfd/sessions/single-hop/stats/negotiated-transmission-interval", .cbs = { .get_elem = bfdd_bfd_sessions_single_hop_stats_negotiated_transmission_interval_get_elem, } }, { .xpath = "/frr-bfdd:bfdd/bfd/sessions/single-hop/stats/negotiated-receive-interval", .cbs = { .get_elem = bfdd_bfd_sessions_single_hop_stats_negotiated_receive_interval_get_elem, } }, { .xpath = "/frr-bfdd:bfdd/bfd/sessions/single-hop/stats/detection-mode", .cbs = { .get_elem = bfdd_bfd_sessions_single_hop_stats_detection_mode_get_elem, } }, { .xpath = "/frr-bfdd:bfdd/bfd/sessions/single-hop/stats/last-down-time", .cbs = { .get_elem = bfdd_bfd_sessions_single_hop_stats_last_down_time_get_elem, } }, { .xpath = "/frr-bfdd:bfdd/bfd/sessions/single-hop/stats/last-up-time", .cbs = { .get_elem = bfdd_bfd_sessions_single_hop_stats_last_up_time_get_elem, } }, { .xpath = "/frr-bfdd:bfdd/bfd/sessions/single-hop/stats/session-down-count", .cbs = { .get_elem = bfdd_bfd_sessions_single_hop_stats_session_down_count_get_elem, } }, { .xpath = "/frr-bfdd:bfdd/bfd/sessions/single-hop/stats/session-up-count", .cbs = { .get_elem = bfdd_bfd_sessions_single_hop_stats_session_up_count_get_elem, } }, { .xpath = "/frr-bfdd:bfdd/bfd/sessions/single-hop/stats/control-packet-input-count", .cbs = { .get_elem = bfdd_bfd_sessions_single_hop_stats_control_packet_input_count_get_elem, } }, { .xpath = "/frr-bfdd:bfdd/bfd/sessions/single-hop/stats/control-packet-output-count", .cbs = { .get_elem = bfdd_bfd_sessions_single_hop_stats_control_packet_output_count_get_elem, } }, { .xpath = "/frr-bfdd:bfdd/bfd/sessions/single-hop/stats/negotiated-echo-transmission-interval", .cbs = { .get_elem = bfdd_bfd_sessions_single_hop_stats_negotiated_echo_transmission_interval_get_elem, } }, { .xpath = "/frr-bfdd:bfdd/bfd/sessions/single-hop/stats/echo-packet-input-count", .cbs = { .get_elem = bfdd_bfd_sessions_single_hop_stats_echo_packet_input_count_get_elem, } }, { .xpath = "/frr-bfdd:bfdd/bfd/sessions/single-hop/stats/echo-packet-output-count", .cbs = { .get_elem = bfdd_bfd_sessions_single_hop_stats_echo_packet_output_count_get_elem, } }, { .xpath = "/frr-bfdd:bfdd/bfd/sessions/multi-hop", .cbs = { .create = bfdd_bfd_sessions_multi_hop_create, .destroy = bfdd_bfd_sessions_multi_hop_destroy, .get_next = bfdd_bfd_sessions_multi_hop_get_next, .get_keys = bfdd_bfd_sessions_multi_hop_get_keys, .lookup_entry = bfdd_bfd_sessions_multi_hop_lookup_entry, .cli_show = bfd_cli_show_multi_hop_peer, .cli_show_end = bfd_cli_show_peer_end, } }, { .xpath = "/frr-bfdd:bfdd/bfd/sessions/multi-hop/detection-multiplier", .cbs = { .modify = bfdd_bfd_sessions_single_hop_detection_multiplier_modify, .cli_show = bfd_cli_show_mult, } }, { .xpath = "/frr-bfdd:bfdd/bfd/sessions/multi-hop/desired-transmission-interval", .cbs = { .modify = bfdd_bfd_sessions_single_hop_desired_transmission_interval_modify, .cli_show = bfd_cli_show_tx, } }, { .xpath = "/frr-bfdd:bfdd/bfd/sessions/multi-hop/required-receive-interval", .cbs = { .modify = bfdd_bfd_sessions_single_hop_required_receive_interval_modify, .cli_show = bfd_cli_show_rx, } }, { .xpath = "/frr-bfdd:bfdd/bfd/sessions/multi-hop/administrative-down", .cbs = { .modify = bfdd_bfd_sessions_single_hop_administrative_down_modify, .cli_show = bfd_cli_show_shutdown, } }, { .xpath = "/frr-bfdd:bfdd/bfd/sessions/multi-hop/stats/local-discriminator", .cbs = { .get_elem = bfdd_bfd_sessions_single_hop_stats_local_discriminator_get_elem, } }, { .xpath = "/frr-bfdd:bfdd/bfd/sessions/multi-hop/stats/local-state", .cbs = { .get_elem = bfdd_bfd_sessions_single_hop_stats_local_state_get_elem, } }, { .xpath = "/frr-bfdd:bfdd/bfd/sessions/multi-hop/stats/local-diagnostic", .cbs = { .get_elem = bfdd_bfd_sessions_single_hop_stats_local_diagnostic_get_elem, } }, { .xpath = "/frr-bfdd:bfdd/bfd/sessions/multi-hop/stats/local-multiplier", .cbs = { .get_elem = bfdd_bfd_sessions_single_hop_stats_local_multiplier_get_elem, } }, { .xpath = "/frr-bfdd:bfdd/bfd/sessions/multi-hop/stats/remote-discriminator", .cbs = { .get_elem = bfdd_bfd_sessions_single_hop_stats_remote_discriminator_get_elem, } }, { .xpath = "/frr-bfdd:bfdd/bfd/sessions/multi-hop/stats/remote-state", .cbs = { .get_elem = bfdd_bfd_sessions_single_hop_stats_remote_state_get_elem, } }, { .xpath = "/frr-bfdd:bfdd/bfd/sessions/multi-hop/stats/remote-diagnostic", .cbs = { .get_elem = bfdd_bfd_sessions_single_hop_stats_remote_diagnostic_get_elem, } }, { .xpath = "/frr-bfdd:bfdd/bfd/sessions/multi-hop/stats/remote-multiplier", .cbs = { .get_elem = bfdd_bfd_sessions_single_hop_stats_remote_multiplier_get_elem, } }, { .xpath = "/frr-bfdd:bfdd/bfd/sessions/multi-hop/stats/negotiated-transmission-interval", .cbs = { .get_elem = bfdd_bfd_sessions_single_hop_stats_negotiated_transmission_interval_get_elem, } }, { .xpath = "/frr-bfdd:bfdd/bfd/sessions/multi-hop/stats/negotiated-receive-interval", .cbs = { .get_elem = bfdd_bfd_sessions_single_hop_stats_negotiated_receive_interval_get_elem, } }, { .xpath = "/frr-bfdd:bfdd/bfd/sessions/multi-hop/stats/detection-mode", .cbs = { .get_elem = bfdd_bfd_sessions_single_hop_stats_detection_mode_get_elem, } }, { .xpath = "/frr-bfdd:bfdd/bfd/sessions/multi-hop/stats/last-down-time", .cbs = { .get_elem = bfdd_bfd_sessions_single_hop_stats_last_down_time_get_elem, } }, { .xpath = "/frr-bfdd:bfdd/bfd/sessions/multi-hop/stats/last-up-time", .cbs = { .get_elem = bfdd_bfd_sessions_single_hop_stats_last_up_time_get_elem, } }, { .xpath = "/frr-bfdd:bfdd/bfd/sessions/multi-hop/stats/session-down-count", .cbs = { .get_elem = bfdd_bfd_sessions_single_hop_stats_session_down_count_get_elem, } }, { .xpath = "/frr-bfdd:bfdd/bfd/sessions/multi-hop/stats/session-up-count", .cbs = { .get_elem = bfdd_bfd_sessions_single_hop_stats_session_up_count_get_elem, } }, { .xpath = "/frr-bfdd:bfdd/bfd/sessions/multi-hop/stats/control-packet-input-count", .cbs = { .get_elem = bfdd_bfd_sessions_single_hop_stats_control_packet_input_count_get_elem, } }, { .xpath = "/frr-bfdd:bfdd/bfd/sessions/multi-hop/stats/control-packet-output-count", .cbs = { .get_elem = bfdd_bfd_sessions_single_hop_stats_control_packet_output_count_get_elem, } }, { .xpath = "/frr-bfdd:bfdd/bfd/sessions/multi-hop/stats/negotiated-echo-transmission-interval", .cbs = { .get_elem = bfdd_bfd_sessions_single_hop_stats_negotiated_echo_transmission_interval_get_elem, } }, { .xpath = "/frr-bfdd:bfdd/bfd/sessions/multi-hop/stats/echo-packet-input-count", .cbs = { .get_elem = bfdd_bfd_sessions_single_hop_stats_echo_packet_input_count_get_elem, } }, { .xpath = "/frr-bfdd:bfdd/bfd/sessions/multi-hop/stats/echo-packet-output-count", .cbs = { .get_elem = bfdd_bfd_sessions_single_hop_stats_echo_packet_output_count_get_elem, } }, { .xpath = NULL, }, } }; frr-7.2.1/bfdd/bfdd_vty.c0000644000000000000000000004663513610377563012076 00000000000000/* * BFD daemon code * Copyright (C) 2018 Network Device Education Foundation, Inc. ("NetDEF") * * FRR is free software; you can redistribute it and/or modify it * under the terms of the GNU General Public License as published by the * Free Software Foundation; either version 2, or (at your option) any * later version. * * FRR 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 FRR; see the file COPYING. If not, write to the Free * Software Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA * 02111-1307, USA. */ #include #include "lib/command.h" #include "lib/json.h" #include "lib/log.h" #include "lib/northbound_cli.h" #include "lib/vty.h" #include "bfd.h" #ifndef VTYSH_EXTRACT_PL #include "bfdd/bfdd_vty_clippy.c" #endif /* * Commands help string definitions. */ #define PEER_IPV4_STR "IPv4 peer address\n" #define PEER_IPV6_STR "IPv6 peer address\n" #define MHOP_STR "Configure multihop\n" #define LOCAL_STR "Configure local address\n" #define LOCAL_IPV4_STR "IPv4 local address\n" #define LOCAL_IPV6_STR "IPv6 local address\n" #define LOCAL_INTF_STR "Configure local interface name to use\n" /* * Prototypes */ static int bfd_configure_peer(struct bfd_peer_cfg *bpc, bool mhop, const struct sockaddr_any *peer, const struct sockaddr_any *local, const char *ifname, const char *vrfname, char *ebuf, size_t ebuflen); static void _display_peer_header(struct vty *vty, struct bfd_session *bs); static struct json_object *__display_peer_json(struct bfd_session *bs); static struct json_object *_peer_json_header(struct bfd_session *bs); static void _display_peer_json(struct vty *vty, struct bfd_session *bs); static void _display_peer(struct vty *vty, struct bfd_session *bs); static void _display_all_peers(struct vty *vty, char *vrfname, bool use_json); static void _display_peer_iter(struct hash_bucket *hb, void *arg); static void _display_peer_json_iter(struct hash_bucket *hb, void *arg); static void _display_peer_counter(struct vty *vty, struct bfd_session *bs); static struct json_object *__display_peer_counters_json(struct bfd_session *bs); static void _display_peer_counters_json(struct vty *vty, struct bfd_session *bs); static void _display_peer_counter_iter(struct hash_bucket *hb, void *arg); static void _display_peer_counter_json_iter(struct hash_bucket *hb, void *arg); static void _display_peers_counter(struct vty *vty, char *vrfname, bool use_json); static struct bfd_session * _find_peer_or_error(struct vty *vty, int argc, struct cmd_token **argv, const char *label, const char *peer_str, const char *local_str, const char *ifname, const char *vrfname); /* * Show commands helper functions */ static void _display_peer_header(struct vty *vty, struct bfd_session *bs) { char addr_buf[INET6_ADDRSTRLEN]; vty_out(vty, "\tpeer %s", inet_ntop(bs->key.family, &bs->key.peer, addr_buf, sizeof(addr_buf))); if (BFD_CHECK_FLAG(bs->flags, BFD_SESS_FLAG_MH)) vty_out(vty, " multihop"); if (memcmp(&bs->key.local, &zero_addr, sizeof(bs->key.local))) vty_out(vty, " local-address %s", inet_ntop(bs->key.family, &bs->key.local, addr_buf, sizeof(addr_buf))); if (bs->key.vrfname[0]) vty_out(vty, " vrf %s", bs->key.vrfname); if (bs->key.ifname[0]) vty_out(vty, " interface %s", bs->key.ifname); vty_out(vty, "\n"); if (bs->pl) vty_out(vty, "\t\tlabel: %s\n", bs->pl->pl_label); } static void _display_peer(struct vty *vty, struct bfd_session *bs) { char buf[256]; time_t now; _display_peer_header(vty, bs); vty_out(vty, "\t\tID: %u\n", bs->discrs.my_discr); vty_out(vty, "\t\tRemote ID: %u\n", bs->discrs.remote_discr); vty_out(vty, "\t\tStatus: "); switch (bs->ses_state) { case PTM_BFD_ADM_DOWN: vty_out(vty, "shutdown\n"); break; case PTM_BFD_DOWN: vty_out(vty, "down\n"); now = monotime(NULL); integer2timestr(now - bs->downtime.tv_sec, buf, sizeof(buf)); vty_out(vty, "\t\tDowntime: %s\n", buf); break; case PTM_BFD_INIT: vty_out(vty, "init\n"); break; case PTM_BFD_UP: vty_out(vty, "up\n"); now = monotime(NULL); integer2timestr(now - bs->uptime.tv_sec, buf, sizeof(buf)); vty_out(vty, "\t\tUptime: %s\n", buf); break; default: vty_out(vty, "unknown\n"); break; } vty_out(vty, "\t\tDiagnostics: %s\n", diag2str(bs->local_diag)); vty_out(vty, "\t\tRemote diagnostics: %s\n", diag2str(bs->remote_diag)); vty_out(vty, "\t\tLocal timers:\n"); vty_out(vty, "\t\t\tReceive interval: %" PRIu32 "ms\n", bs->timers.required_min_rx / 1000); vty_out(vty, "\t\t\tTransmission interval: %" PRIu32 "ms\n", bs->timers.desired_min_tx / 1000); vty_out(vty, "\t\t\tEcho transmission interval: %" PRIu32 "ms\n", bs->timers.required_min_echo / 1000); vty_out(vty, "\t\tRemote timers:\n"); vty_out(vty, "\t\t\tReceive interval: %" PRIu32 "ms\n", bs->remote_timers.required_min_rx / 1000); vty_out(vty, "\t\t\tTransmission interval: %" PRIu32 "ms\n", bs->remote_timers.desired_min_tx / 1000); vty_out(vty, "\t\t\tEcho transmission interval: %" PRIu32 "ms\n", bs->remote_timers.required_min_echo / 1000); vty_out(vty, "\n"); } static struct json_object *_peer_json_header(struct bfd_session *bs) { struct json_object *jo = json_object_new_object(); char addr_buf[INET6_ADDRSTRLEN]; if (bs->key.mhop) json_object_boolean_true_add(jo, "multihop"); else json_object_boolean_false_add(jo, "multihop"); json_object_string_add(jo, "peer", inet_ntop(bs->key.family, &bs->key.peer, addr_buf, sizeof(addr_buf))); if (memcmp(&bs->key.local, &zero_addr, sizeof(bs->key.local))) json_object_string_add(jo, "local", inet_ntop(bs->key.family, &bs->key.local, addr_buf, sizeof(addr_buf))); if (bs->key.vrfname[0]) json_object_string_add(jo, "vrf", bs->key.vrfname); if (bs->key.ifname[0]) json_object_string_add(jo, "interface", bs->key.ifname); if (bs->pl) json_object_string_add(jo, "label", bs->pl->pl_label); return jo; } static struct json_object *__display_peer_json(struct bfd_session *bs) { struct json_object *jo = _peer_json_header(bs); json_object_int_add(jo, "id", bs->discrs.my_discr); json_object_int_add(jo, "remote-id", bs->discrs.remote_discr); switch (bs->ses_state) { case PTM_BFD_ADM_DOWN: json_object_string_add(jo, "status", "shutdown"); break; case PTM_BFD_DOWN: json_object_string_add(jo, "status", "down"); json_object_int_add(jo, "downtime", monotime(NULL) - bs->downtime.tv_sec); break; case PTM_BFD_INIT: json_object_string_add(jo, "status", "init"); break; case PTM_BFD_UP: json_object_string_add(jo, "status", "up"); json_object_int_add(jo, "uptime", monotime(NULL) - bs->uptime.tv_sec); break; default: json_object_string_add(jo, "status", "unknown"); break; } json_object_string_add(jo, "diagnostic", diag2str(bs->local_diag)); json_object_string_add(jo, "remote-diagnostic", diag2str(bs->remote_diag)); json_object_int_add(jo, "receive-interval", bs->timers.required_min_rx / 1000); json_object_int_add(jo, "transmit-interval", bs->timers.desired_min_tx / 1000); if (BFD_CHECK_FLAG(bs->flags, BFD_SESS_FLAG_ECHO)) json_object_int_add(jo, "echo-interval", bs->timers.required_min_echo / 1000); else json_object_int_add(jo, "echo-interval", 0); json_object_int_add(jo, "remote-receive-interval", bs->remote_timers.required_min_rx / 1000); json_object_int_add(jo, "remote-transmit-interval", bs->remote_timers.desired_min_tx / 1000); json_object_int_add(jo, "remote-echo-interval", bs->remote_timers.required_min_echo / 1000); return jo; } static void _display_peer_json(struct vty *vty, struct bfd_session *bs) { struct json_object *jo = __display_peer_json(bs); vty_out(vty, "%s\n", json_object_to_json_string_ext(jo, 0)); json_object_free(jo); } struct bfd_vrf_tuple { char *vrfname; struct vty *vty; struct json_object *jo; }; static void _display_peer_iter(struct hash_bucket *hb, void *arg) { struct bfd_vrf_tuple *bvt = (struct bfd_vrf_tuple *)arg; struct vty *vty; struct bfd_session *bs = hb->data; if (!bvt) return; vty = bvt->vty; if (bvt->vrfname) { if (!bs->key.vrfname[0] || !strmatch(bs->key.vrfname, bvt->vrfname)) return; } _display_peer(vty, bs); } static void _display_peer_json_iter(struct hash_bucket *hb, void *arg) { struct bfd_vrf_tuple *bvt = (struct bfd_vrf_tuple *)arg; struct json_object *jo, *jon = NULL; struct bfd_session *bs = hb->data; if (!bvt) return; jo = bvt->jo; if (bvt->vrfname) { if (!bs->key.vrfname[0] || !strmatch(bs->key.vrfname, bvt->vrfname)) return; } jon = __display_peer_json(bs); if (jon == NULL) { log_warning("%s: not enough memory", __func__); return; } json_object_array_add(jo, jon); } static void _display_all_peers(struct vty *vty, char *vrfname, bool use_json) { struct json_object *jo; struct bfd_vrf_tuple bvt; memset(&bvt, 0, sizeof(bvt)); bvt.vrfname = vrfname; if (!use_json) { bvt.vty = vty; vty_out(vty, "BFD Peers:\n"); bfd_id_iterate(_display_peer_iter, &bvt); return; } jo = json_object_new_array(); bvt.jo = jo; bfd_id_iterate(_display_peer_json_iter, &bvt); vty_out(vty, "%s\n", json_object_to_json_string_ext(jo, 0)); json_object_free(jo); } static void _display_peer_counter(struct vty *vty, struct bfd_session *bs) { _display_peer_header(vty, bs); vty_out(vty, "\t\tControl packet input: %" PRIu64 " packets\n", bs->stats.rx_ctrl_pkt); vty_out(vty, "\t\tControl packet output: %" PRIu64 " packets\n", bs->stats.tx_ctrl_pkt); vty_out(vty, "\t\tEcho packet input: %" PRIu64 " packets\n", bs->stats.rx_echo_pkt); vty_out(vty, "\t\tEcho packet output: %" PRIu64 " packets\n", bs->stats.tx_echo_pkt); vty_out(vty, "\t\tSession up events: %" PRIu64 "\n", bs->stats.session_up); vty_out(vty, "\t\tSession down events: %" PRIu64 "\n", bs->stats.session_down); vty_out(vty, "\t\tZebra notifications: %" PRIu64 "\n", bs->stats.znotification); vty_out(vty, "\n"); } static struct json_object *__display_peer_counters_json(struct bfd_session *bs) { struct json_object *jo = _peer_json_header(bs); json_object_int_add(jo, "control-packet-input", bs->stats.rx_ctrl_pkt); json_object_int_add(jo, "control-packet-output", bs->stats.tx_ctrl_pkt); json_object_int_add(jo, "echo-packet-input", bs->stats.rx_echo_pkt); json_object_int_add(jo, "echo-packet-output", bs->stats.tx_echo_pkt); json_object_int_add(jo, "session-up", bs->stats.session_up); json_object_int_add(jo, "session-down", bs->stats.session_down); json_object_int_add(jo, "zebra-notifications", bs->stats.znotification); return jo; } static void _display_peer_counters_json(struct vty *vty, struct bfd_session *bs) { struct json_object *jo = __display_peer_counters_json(bs); vty_out(vty, "%s\n", json_object_to_json_string_ext(jo, 0)); json_object_free(jo); } static void _display_peer_counter_iter(struct hash_bucket *hb, void *arg) { struct bfd_vrf_tuple *bvt = arg; struct vty *vty; struct bfd_session *bs = hb->data; if (!bvt) return; vty = bvt->vty; if (bvt->vrfname) { if (!bs->key.vrfname[0] || !strmatch(bs->key.vrfname, bvt->vrfname)) return; } _display_peer_counter(vty, bs); } static void _display_peer_counter_json_iter(struct hash_bucket *hb, void *arg) { struct json_object *jo, *jon = NULL; struct bfd_session *bs = hb->data; struct bfd_vrf_tuple *bvt = arg; if (!bvt) return; jo = bvt->jo; if (bvt->vrfname) { if (!bs->key.vrfname[0] || !strmatch(bs->key.vrfname, bvt->vrfname)) return; } jon = __display_peer_counters_json(bs); if (jon == NULL) { log_warning("%s: not enough memory", __func__); return; } json_object_array_add(jo, jon); } static void _display_peers_counter(struct vty *vty, char *vrfname, bool use_json) { struct json_object *jo; struct bfd_vrf_tuple bvt; memset(&bvt, 0, sizeof(struct bfd_vrf_tuple)); bvt.vrfname = vrfname; if (!use_json) { bvt.vty = vty; vty_out(vty, "BFD Peers:\n"); bfd_id_iterate(_display_peer_counter_iter, &bvt); return; } jo = json_object_new_array(); bvt.jo = jo; bfd_id_iterate(_display_peer_counter_json_iter, jo); vty_out(vty, "%s\n", json_object_to_json_string_ext(jo, 0)); json_object_free(jo); } static struct bfd_session * _find_peer_or_error(struct vty *vty, int argc, struct cmd_token **argv, const char *label, const char *peer_str, const char *local_str, const char *ifname, const char *vrfname) { int idx; bool mhop; struct bfd_session *bs = NULL; struct peer_label *pl; struct bfd_peer_cfg bpc; struct sockaddr_any psa, lsa, *lsap; char errormsg[128]; /* Look up the BFD peer. */ if (label) { pl = pl_find(label); if (pl) bs = pl->pl_bs; } else { strtosa(peer_str, &psa); if (local_str) { strtosa(local_str, &lsa); lsap = &lsa; } else lsap = NULL; idx = 0; mhop = argv_find(argv, argc, "multihop", &idx); if (bfd_configure_peer(&bpc, mhop, &psa, lsap, ifname, vrfname, errormsg, sizeof(errormsg)) != 0) { vty_out(vty, "%% Invalid peer configuration: %s\n", errormsg); return NULL; } bs = bs_peer_find(&bpc); } /* Find peer data. */ if (bs == NULL) { vty_out(vty, "%% Unable to find 'peer %s", label ? label : peer_str); if (ifname) vty_out(vty, " interface %s", ifname); if (local_str) vty_out(vty, " local-address %s", local_str); if (vrfname) vty_out(vty, " vrf %s", vrfname); vty_out(vty, "'\n"); return NULL; } return bs; } /* * Show commands. */ DEFPY(bfd_show_peers, bfd_show_peers_cmd, "show bfd [vrf ] peers [json]", SHOW_STR "Bidirection Forwarding Detection\n" VRF_CMD_HELP_STR "BFD peers status\n" JSON_STR) { char *vrf_name = NULL; int idx_vrf = 0; if (argv_find(argv, argc, "vrf", &idx_vrf)) vrf_name = argv[idx_vrf + 1]->arg; _display_all_peers(vty, vrf_name, use_json(argc, argv)); return CMD_SUCCESS; } DEFPY(bfd_show_peer, bfd_show_peer_cmd, "show bfd [vrf ] peer $peer [{multihop|local-address $local|interface IFNAME$ifname}]> [json]", SHOW_STR "Bidirection Forwarding Detection\n" VRF_CMD_HELP_STR "BFD peers status\n" "Peer label\n" PEER_IPV4_STR PEER_IPV6_STR MHOP_STR LOCAL_STR LOCAL_IPV4_STR LOCAL_IPV6_STR INTERFACE_STR LOCAL_INTF_STR JSON_STR) { struct bfd_session *bs; /* Look up the BFD peer. */ bs = _find_peer_or_error(vty, argc, argv, label, peer_str, local_str, ifname, vrf_name); if (bs == NULL) return CMD_WARNING_CONFIG_FAILED; if (use_json(argc, argv)) { _display_peer_json(vty, bs); } else { vty_out(vty, "BFD Peer:\n"); _display_peer(vty, bs); } return CMD_SUCCESS; } DEFPY(bfd_show_peer_counters, bfd_show_peer_counters_cmd, "show bfd [vrf ] peer $peer [{multihop|local-address $local|interface IFNAME$ifname}]> counters [json]", SHOW_STR "Bidirection Forwarding Detection\n" VRF_CMD_HELP_STR "BFD peers status\n" "Peer label\n" PEER_IPV4_STR PEER_IPV6_STR MHOP_STR LOCAL_STR LOCAL_IPV4_STR LOCAL_IPV6_STR INTERFACE_STR LOCAL_INTF_STR "Show BFD peer counters information\n" JSON_STR) { struct bfd_session *bs; /* Look up the BFD peer. */ bs = _find_peer_or_error(vty, argc, argv, label, peer_str, local_str, ifname, vrf_name); if (bs == NULL) return CMD_WARNING_CONFIG_FAILED; if (use_json(argc, argv)) _display_peer_counters_json(vty, bs); else _display_peer_counter(vty, bs); return CMD_SUCCESS; } DEFPY(bfd_show_peers_counters, bfd_show_peers_counters_cmd, "show bfd [vrf ] peers counters [json]", SHOW_STR "Bidirection Forwarding Detection\n" VRF_CMD_HELP_STR "BFD peers status\n" "Show BFD peer counters information\n" JSON_STR) { char *vrf_name = NULL; int idx_vrf = 0; if (argv_find(argv, argc, "vrf", &idx_vrf)) vrf_name = argv[idx_vrf + 1]->arg; _display_peers_counter(vty, vrf_name, use_json(argc, argv)); return CMD_SUCCESS; } /* * Function definitions. */ /* * Configuration rules: * * Single hop: * peer + (interface name) * * Multi hop: * peer + local + (optional vrf) * * Anything else is misconfiguration. */ static int bfd_configure_peer(struct bfd_peer_cfg *bpc, bool mhop, const struct sockaddr_any *peer, const struct sockaddr_any *local, const char *ifname, const char *vrfname, char *ebuf, size_t ebuflen) { memset(bpc, 0, sizeof(*bpc)); /* Defaults */ bpc->bpc_shutdown = true; bpc->bpc_detectmultiplier = BPC_DEF_DETECTMULTIPLIER; bpc->bpc_recvinterval = BPC_DEF_RECEIVEINTERVAL; bpc->bpc_txinterval = BPC_DEF_TRANSMITINTERVAL; bpc->bpc_echointerval = BPC_DEF_ECHOINTERVAL; bpc->bpc_lastevent = monotime(NULL); /* Safety check: when no error buf is provided len must be zero. */ if (ebuf == NULL) ebuflen = 0; /* Peer is always mandatory. */ if (peer == NULL) { snprintf(ebuf, ebuflen, "peer must not be empty"); return -1; } /* Validate address families. */ if (peer->sa_sin.sin_family == AF_INET) { if (local && local->sa_sin.sin_family != AF_INET) { snprintf(ebuf, ebuflen, "local is IPv6, but peer is IPv4"); return -1; } bpc->bpc_ipv4 = true; } else if (peer->sa_sin.sin_family == AF_INET6) { if (local && local->sa_sin.sin_family != AF_INET6) { snprintf(ebuf, ebuflen, "local is IPv4, but peer is IPv6"); return -1; } bpc->bpc_ipv4 = false; } else { snprintf(ebuf, ebuflen, "invalid peer address family"); return -1; } /* Copy local and/or peer addresses. */ if (local) bpc->bpc_local = *local; bpc->bpc_peer = *peer; bpc->bpc_mhop = mhop; /* Handle interface specification configuration. */ if (ifname) { bpc->bpc_has_localif = true; if (strlcpy(bpc->bpc_localif, ifname, sizeof(bpc->bpc_localif)) > sizeof(bpc->bpc_localif)) { snprintf(ebuf, ebuflen, "interface name too long"); return -1; } } /* Handle VRF configuration. */ if (vrfname) { bpc->bpc_has_vrfname = true; if (strlcpy(bpc->bpc_vrfname, vrfname, sizeof(bpc->bpc_vrfname)) > sizeof(bpc->bpc_vrfname)) { snprintf(ebuf, ebuflen, "vrf name too long"); return -1; } } return 0; } DEFUN_NOSH(show_debugging_bfd, show_debugging_bfd_cmd, "show debugging [bfd]", SHOW_STR DEBUG_STR "BFD daemon\n") { vty_out(vty, "BFD debugging status:\n"); return CMD_SUCCESS; } struct cmd_node bfd_node = { BFD_NODE, "%s(config-bfd)# ", 1, }; struct cmd_node bfd_peer_node = { BFD_PEER_NODE, "%s(config-bfd-peer)# ", 1, }; static int bfdd_write_config(struct vty *vty) { struct lyd_node *dnode; int written = 0; dnode = yang_dnode_get(running_config->dnode, "/frr-bfdd:bfdd"); if (dnode) { nb_cli_show_dnode_cmds(vty, dnode, false); written = 1; } return written; } void bfdd_vty_init(void) { install_element(ENABLE_NODE, &bfd_show_peers_counters_cmd); install_element(ENABLE_NODE, &bfd_show_peer_counters_cmd); install_element(ENABLE_NODE, &bfd_show_peers_cmd); install_element(ENABLE_NODE, &bfd_show_peer_cmd); install_element(ENABLE_NODE, &show_debugging_bfd_cmd); /* Install BFD node and commands. */ install_node(&bfd_node, bfdd_write_config); install_default(BFD_NODE); /* Install BFD peer node. */ install_node(&bfd_peer_node, NULL); install_default(BFD_PEER_NODE); bfdd_cli_init(); } frr-7.2.1/bfdd/config.c0000644000000000000000000003632413610377563011534 00000000000000/********************************************************************* * Copyright 2017-2018 Network Device Education Foundation, Inc. ("NetDEF") * * This program is free software; you can redistribute it and/or modify it * under the terms of the 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; see the file COPYING; if not, write to the Free Software * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA * * config.c: implements the BFD daemon configuration handling. * * Authors * ------- * Rafael Zalamena */ #include #include #include "lib/json.h" #include "bfd.h" DEFINE_MTYPE_STATIC(BFDD, BFDD_LABEL, "long-lived label memory") /* * Definitions */ enum peer_list_type { PLT_IPV4, PLT_IPV6, PLT_LABEL, }; /* * Prototypes */ static int parse_config_json(struct json_object *jo, bpc_handle h, void *arg); static int parse_list(struct json_object *jo, enum peer_list_type plt, bpc_handle h, void *arg); static int parse_peer_config(struct json_object *jo, struct bfd_peer_cfg *bpc); static int parse_peer_label_config(struct json_object *jo, struct bfd_peer_cfg *bpc); static int config_add(struct bfd_peer_cfg *bpc, void *arg); static int config_del(struct bfd_peer_cfg *bpc, void *arg); static int json_object_add_peer(struct json_object *jo, struct bfd_session *bs); /* * Implementation */ static int config_add(struct bfd_peer_cfg *bpc, void *arg __attribute__((unused))) { return ptm_bfd_sess_new(bpc) == NULL; } static int config_del(struct bfd_peer_cfg *bpc, void *arg __attribute__((unused))) { return ptm_bfd_sess_del(bpc) != 0; } static int parse_config_json(struct json_object *jo, bpc_handle h, void *arg) { const char *key, *sval; struct json_object *jo_val; struct json_object_iterator joi, join; int error = 0; JSON_FOREACH (jo, joi, join) { key = json_object_iter_peek_name(&joi); jo_val = json_object_iter_peek_value(&joi); if (strcmp(key, "ipv4") == 0) { error += parse_list(jo_val, PLT_IPV4, h, arg); } else if (strcmp(key, "ipv6") == 0) { error += parse_list(jo_val, PLT_IPV6, h, arg); } else if (strcmp(key, "label") == 0) { error += parse_list(jo_val, PLT_LABEL, h, arg); } else { sval = json_object_get_string(jo_val); log_warning("%s:%d invalid configuration: %s", __func__, __LINE__, sval); error++; } } /* * Our callers never call free() on json_object and only expect * the return value, so lets free() it here. */ json_object_put(jo); return error; } int parse_config(const char *fname) { struct json_object *jo; jo = json_object_from_file(fname); if (jo == NULL) return -1; return parse_config_json(jo, config_add, NULL); } static int parse_list(struct json_object *jo, enum peer_list_type plt, bpc_handle h, void *arg) { struct json_object *jo_val; struct bfd_peer_cfg bpc; int allen, idx; int error = 0, result; allen = json_object_array_length(jo); for (idx = 0; idx < allen; idx++) { jo_val = json_object_array_get_idx(jo, idx); /* Set defaults. */ memset(&bpc, 0, sizeof(bpc)); bpc.bpc_detectmultiplier = BFD_DEFDETECTMULT; bpc.bpc_recvinterval = BFD_DEFREQUIREDMINRX; bpc.bpc_txinterval = BFD_DEFDESIREDMINTX; bpc.bpc_echointerval = BFD_DEF_REQ_MIN_ECHO; switch (plt) { case PLT_IPV4: log_debug("ipv4 peers %d:", allen); bpc.bpc_ipv4 = true; break; case PLT_IPV6: log_debug("ipv6 peers %d:", allen); bpc.bpc_ipv4 = false; break; case PLT_LABEL: log_debug("label peers %d:", allen); if (parse_peer_label_config(jo_val, &bpc) != 0) { error++; continue; } break; default: error++; log_error("%s:%d: unsupported peer type", __func__, __LINE__); break; } result = parse_peer_config(jo_val, &bpc); error += result; if (result == 0) error += (h(&bpc, arg) != 0); } return error; } static int parse_peer_config(struct json_object *jo, struct bfd_peer_cfg *bpc) { const char *key, *sval; struct json_object *jo_val; struct json_object_iterator joi, join; int family_type = (bpc->bpc_ipv4) ? AF_INET : AF_INET6; int error = 0; log_debug("\tpeer: %s", bpc->bpc_ipv4 ? "ipv4" : "ipv6"); JSON_FOREACH (jo, joi, join) { key = json_object_iter_peek_name(&joi); jo_val = json_object_iter_peek_value(&joi); if (strcmp(key, "multihop") == 0) { bpc->bpc_mhop = json_object_get_boolean(jo_val); log_debug("\tmultihop: %s", bpc->bpc_mhop ? "true" : "false"); } else if (strcmp(key, "peer-address") == 0) { sval = json_object_get_string(jo_val); if (strtosa(sval, &bpc->bpc_peer) != 0 || bpc->bpc_peer.sa_sin.sin_family != family_type) { log_info( "%s:%d failed to parse peer-address '%s'", __func__, __LINE__, sval); error++; } log_debug("\tpeer-address: %s", sval); } else if (strcmp(key, "local-address") == 0) { sval = json_object_get_string(jo_val); if (strtosa(sval, &bpc->bpc_local) != 0 || bpc->bpc_local.sa_sin.sin_family != family_type) { log_info( "%s:%d failed to parse local-address '%s'", __func__, __LINE__, sval); error++; } log_debug("\tlocal-address: %s", sval); } else if (strcmp(key, "local-interface") == 0) { bpc->bpc_has_localif = true; sval = json_object_get_string(jo_val); if (strlcpy(bpc->bpc_localif, sval, sizeof(bpc->bpc_localif)) > sizeof(bpc->bpc_localif)) { log_debug("\tlocal-interface: %s (truncated)"); error++; } else { log_debug("\tlocal-interface: %s", sval); } } else if (strcmp(key, "vrf-name") == 0) { bpc->bpc_has_vrfname = true; sval = json_object_get_string(jo_val); if (strlcpy(bpc->bpc_vrfname, sval, sizeof(bpc->bpc_vrfname)) > sizeof(bpc->bpc_vrfname)) { log_debug("\tvrf-name: %s (truncated)", sval); error++; } else { log_debug("\tvrf-name: %s", sval); } } else if (strcmp(key, "detect-multiplier") == 0) { bpc->bpc_detectmultiplier = json_object_get_int64(jo_val); bpc->bpc_has_detectmultiplier = true; log_debug("\tdetect-multiplier: %llu", bpc->bpc_detectmultiplier); } else if (strcmp(key, "receive-interval") == 0) { bpc->bpc_recvinterval = json_object_get_int64(jo_val); bpc->bpc_has_recvinterval = true; log_debug("\treceive-interval: %llu", bpc->bpc_recvinterval); } else if (strcmp(key, "transmit-interval") == 0) { bpc->bpc_txinterval = json_object_get_int64(jo_val); bpc->bpc_has_txinterval = true; log_debug("\ttransmit-interval: %llu", bpc->bpc_txinterval); } else if (strcmp(key, "echo-interval") == 0) { bpc->bpc_echointerval = json_object_get_int64(jo_val); bpc->bpc_has_echointerval = true; log_debug("\techo-interval: %llu", bpc->bpc_echointerval); } else if (strcmp(key, "create-only") == 0) { bpc->bpc_createonly = json_object_get_boolean(jo_val); log_debug("\tcreate-only: %s", bpc->bpc_createonly ? "true" : "false"); } else if (strcmp(key, "shutdown") == 0) { bpc->bpc_shutdown = json_object_get_boolean(jo_val); log_debug("\tshutdown: %s", bpc->bpc_shutdown ? "true" : "false"); } else if (strcmp(key, "echo-mode") == 0) { bpc->bpc_echo = json_object_get_boolean(jo_val); log_debug("\techo-mode: %s", bpc->bpc_echo ? "true" : "false"); } else if (strcmp(key, "label") == 0) { bpc->bpc_has_label = true; sval = json_object_get_string(jo_val); if (strlcpy(bpc->bpc_label, sval, sizeof(bpc->bpc_label)) > sizeof(bpc->bpc_label)) { log_debug("\tlabel: %s (truncated)", sval); error++; } else { log_debug("\tlabel: %s", sval); } } else { sval = json_object_get_string(jo_val); log_warning("%s:%d invalid configuration: '%s: %s'", __func__, __LINE__, key, sval); error++; } } if (bpc->bpc_peer.sa_sin.sin_family == 0) { log_debug("%s:%d no peer address provided", __func__, __LINE__); error++; } return error; } static int parse_peer_label_config(struct json_object *jo, struct bfd_peer_cfg *bpc) { struct peer_label *pl; struct json_object *label; const char *sval; /* Get label and translate it to BFD daemon key. */ if (!json_object_object_get_ex(jo, "label", &label)) return 1; sval = json_object_get_string(label); pl = pl_find(sval); if (pl == NULL) return 1; log_debug("\tpeer-label: %s", sval); /* Translate the label into BFD address keys. */ bs_to_bpc(pl->pl_bs, bpc); return 0; } /* * Control socket JSON parsing. */ int config_request_add(const char *jsonstr) { struct json_object *jo; jo = json_tokener_parse(jsonstr); if (jo == NULL) return -1; return parse_config_json(jo, config_add, NULL); } int config_request_del(const char *jsonstr) { struct json_object *jo; jo = json_tokener_parse(jsonstr); if (jo == NULL) return -1; return parse_config_json(jo, config_del, NULL); } char *config_response(const char *status, const char *error) { struct json_object *resp, *jo; char *jsonstr; resp = json_object_new_object(); if (resp == NULL) return NULL; /* Add 'status' response key. */ jo = json_object_new_string(status); if (jo == NULL) { json_object_put(resp); return NULL; } json_object_object_add(resp, "status", jo); /* Add 'error' response key. */ if (error != NULL) { jo = json_object_new_string(error); if (jo == NULL) { json_object_put(resp); return NULL; } json_object_object_add(resp, "error", jo); } /* Generate JSON response. */ jsonstr = XSTRDUP( MTYPE_BFDD_NOTIFICATION, json_object_to_json_string_ext(resp, BFDD_JSON_CONV_OPTIONS)); json_object_put(resp); return jsonstr; } char *config_notify(struct bfd_session *bs) { struct json_object *resp; char *jsonstr; time_t now; resp = json_object_new_object(); if (resp == NULL) return NULL; json_object_string_add(resp, "op", BCM_NOTIFY_PEER_STATUS); json_object_add_peer(resp, bs); /* Add status information */ json_object_int_add(resp, "id", bs->discrs.my_discr); json_object_int_add(resp, "remote-id", bs->discrs.my_discr); switch (bs->ses_state) { case PTM_BFD_UP: json_object_string_add(resp, "state", "up"); now = monotime(NULL); json_object_int_add(resp, "uptime", now - bs->uptime.tv_sec); break; case PTM_BFD_ADM_DOWN: json_object_string_add(resp, "state", "adm-down"); break; case PTM_BFD_DOWN: json_object_string_add(resp, "state", "down"); now = monotime(NULL); json_object_int_add(resp, "downtime", now - bs->downtime.tv_sec); break; case PTM_BFD_INIT: json_object_string_add(resp, "state", "init"); break; default: json_object_string_add(resp, "state", "unknown"); break; } json_object_int_add(resp, "diagnostics", bs->local_diag); json_object_int_add(resp, "remote-diagnostics", bs->remote_diag); /* Generate JSON response. */ jsonstr = XSTRDUP( MTYPE_BFDD_NOTIFICATION, json_object_to_json_string_ext(resp, BFDD_JSON_CONV_OPTIONS)); json_object_put(resp); return jsonstr; } char *config_notify_config(const char *op, struct bfd_session *bs) { struct json_object *resp; char *jsonstr; resp = json_object_new_object(); if (resp == NULL) return NULL; json_object_string_add(resp, "op", op); json_object_add_peer(resp, bs); /* On peer deletion we don't need to add any additional information. */ if (strcmp(op, BCM_NOTIFY_CONFIG_DELETE) == 0) goto skip_config; json_object_int_add(resp, "detect-multiplier", bs->detect_mult); json_object_int_add(resp, "receive-interval", bs->timers.required_min_rx / 1000); json_object_int_add(resp, "transmit-interval", bs->timers.desired_min_tx / 1000); json_object_int_add(resp, "echo-interval", bs->timers.required_min_echo / 1000); json_object_int_add(resp, "remote-detect-multiplier", bs->remote_detect_mult); json_object_int_add(resp, "remote-receive-interval", bs->remote_timers.required_min_rx / 1000); json_object_int_add(resp, "remote-transmit-interval", bs->remote_timers.desired_min_tx / 1000); json_object_int_add(resp, "remote-echo-interval", bs->remote_timers.required_min_echo / 1000); if (BFD_CHECK_FLAG(bs->flags, BFD_SESS_FLAG_ECHO)) json_object_boolean_true_add(resp, "echo-mode"); else json_object_boolean_false_add(resp, "echo-mode"); if (BFD_CHECK_FLAG(bs->flags, BFD_SESS_FLAG_SHUTDOWN)) json_object_boolean_true_add(resp, "shutdown"); else json_object_boolean_false_add(resp, "shutdown"); skip_config: /* Generate JSON response. */ jsonstr = XSTRDUP( MTYPE_BFDD_NOTIFICATION, json_object_to_json_string_ext(resp, BFDD_JSON_CONV_OPTIONS)); json_object_put(resp); return jsonstr; } int config_notify_request(struct bfd_control_socket *bcs, const char *jsonstr, bpc_handle bh) { struct json_object *jo; jo = json_tokener_parse(jsonstr); if (jo == NULL) return -1; return parse_config_json(jo, bh, bcs); } static int json_object_add_peer(struct json_object *jo, struct bfd_session *bs) { char addr_buf[INET6_ADDRSTRLEN]; /* Add peer 'key' information. */ if (BFD_CHECK_FLAG(bs->flags, BFD_SESS_FLAG_IPV6)) json_object_boolean_true_add(jo, "ipv6"); else json_object_boolean_false_add(jo, "ipv6"); if (BFD_CHECK_FLAG(bs->flags, BFD_SESS_FLAG_MH)) { json_object_boolean_true_add(jo, "multihop"); json_object_string_add(jo, "peer-address", inet_ntop(bs->key.family, &bs->key.peer, addr_buf, sizeof(addr_buf))); json_object_string_add(jo, "local-address", inet_ntop(bs->key.family, &bs->key.local, addr_buf, sizeof(addr_buf))); if (bs->key.vrfname[0]) json_object_string_add(jo, "vrf-name", bs->key.vrfname); } else { json_object_boolean_false_add(jo, "multihop"); json_object_string_add(jo, "peer-address", inet_ntop(bs->key.family, &bs->key.peer, addr_buf, sizeof(addr_buf))); if (memcmp(&bs->key.local, &zero_addr, sizeof(bs->key.local))) json_object_string_add( jo, "local-address", inet_ntop(bs->key.family, &bs->key.local, addr_buf, sizeof(addr_buf))); if (bs->key.ifname[0]) json_object_string_add(jo, "local-interface", bs->key.ifname); } if (bs->pl) json_object_string_add(jo, "label", bs->pl->pl_label); return 0; } /* * Label handling */ struct peer_label *pl_find(const char *label) { struct peer_label *pl; TAILQ_FOREACH (pl, &bglobal.bg_pllist, pl_entry) { if (strcmp(pl->pl_label, label) != 0) continue; return pl; } return NULL; } struct peer_label *pl_new(const char *label, struct bfd_session *bs) { struct peer_label *pl; pl = XCALLOC(MTYPE_BFDD_LABEL, sizeof(*pl)); if (strlcpy(pl->pl_label, label, sizeof(pl->pl_label)) > sizeof(pl->pl_label)) log_warning("%s:%d: label was truncated", __func__, __LINE__); pl->pl_bs = bs; bs->pl = pl; TAILQ_INSERT_HEAD(&bglobal.bg_pllist, pl, pl_entry); return pl; } void pl_free(struct peer_label *pl) { if (pl == NULL) return; /* Remove the pointer back. */ pl->pl_bs->pl = NULL; TAILQ_REMOVE(&bglobal.bg_pllist, pl, pl_entry); XFREE(MTYPE_BFDD_LABEL, pl); } frr-7.2.1/bfdd/control.c0000644000000000000000000005230013610377563011737 00000000000000/********************************************************************* * Copyright 2017-2018 Network Device Education Foundation, Inc. ("NetDEF") * * This program is free software; you can redistribute it and/or modify it * under the terms of the 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; see the file COPYING; if not, write to the Free Software * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA * * control.c: implements the BFD daemon control socket. It will be used * to talk with clients daemon/scripts/consumers. * * Authors * ------- * Rafael Zalamena */ #include #include #include "bfd.h" /* * Prototypes */ static int sock_set_nonblock(int fd); struct bfd_control_queue *control_queue_new(struct bfd_control_socket *bcs); static void control_queue_free(struct bfd_control_socket *bcs, struct bfd_control_queue *bcq); static int control_queue_dequeue(struct bfd_control_socket *bcs); static int control_queue_enqueue(struct bfd_control_socket *bcs, struct bfd_control_msg *bcm); static int control_queue_enqueue_first(struct bfd_control_socket *bcs, struct bfd_control_msg *bcm); struct bfd_notify_peer *control_notifypeer_new(struct bfd_control_socket *bcs, struct bfd_session *bs); static void control_notifypeer_free(struct bfd_control_socket *bcs, struct bfd_notify_peer *bnp); struct bfd_notify_peer *control_notifypeer_find(struct bfd_control_socket *bcs, struct bfd_session *bs); struct bfd_control_socket *control_new(int sd); static void control_free(struct bfd_control_socket *bcs); static void control_reset_buf(struct bfd_control_buffer *bcb); static int control_read(struct thread *t); static int control_write(struct thread *t); static void control_handle_request_add(struct bfd_control_socket *bcs, struct bfd_control_msg *bcm); static void control_handle_request_del(struct bfd_control_socket *bcs, struct bfd_control_msg *bcm); static int notify_add_cb(struct bfd_peer_cfg *bpc, void *arg); static int notify_del_cb(struct bfd_peer_cfg *bpc, void *arg); static void control_handle_notify_add(struct bfd_control_socket *bcs, struct bfd_control_msg *bcm); static void control_handle_notify_del(struct bfd_control_socket *bcs, struct bfd_control_msg *bcm); static void _control_handle_notify(struct hash_bucket *hb, void *arg); static void control_handle_notify(struct bfd_control_socket *bcs, struct bfd_control_msg *bcm); static void control_response(struct bfd_control_socket *bcs, uint16_t id, const char *status, const char *error); static void _control_notify_config(struct bfd_control_socket *bcs, const char *op, struct bfd_session *bs); static void _control_notify(struct bfd_control_socket *bcs, struct bfd_session *bs); /* * Functions */ static int sock_set_nonblock(int fd) { int flags; flags = fcntl(fd, F_GETFL, 0); if (flags == -1) { log_warning("%s: fcntl F_GETFL: %s", __func__, strerror(errno)); return -1; } flags |= O_NONBLOCK; if (fcntl(fd, F_SETFL, flags) == -1) { log_warning("%s: fcntl F_SETFL: %s", __func__, strerror(errno)); return -1; } return 0; } int control_init(const char *path) { int sd; mode_t umval; struct sockaddr_un sun_ = { .sun_family = AF_UNIX, .sun_path = BFDD_CONTROL_SOCKET, }; if (path) strlcpy(sun_.sun_path, path, sizeof(sun_.sun_path)); /* Remove previously created sockets. */ unlink(sun_.sun_path); sd = socket(AF_UNIX, SOCK_STREAM, PF_UNSPEC); if (sd == -1) { log_error("%s: socket: %s", __func__, strerror(errno)); return -1; } umval = umask(0); if (bind(sd, (struct sockaddr *)&sun_, sizeof(sun_)) == -1) { log_error("%s: bind: %s", __func__, strerror(errno)); close(sd); return -1; } umask(umval); if (listen(sd, SOMAXCONN) == -1) { log_error("%s: listen: %s", __func__, strerror(errno)); close(sd); return -1; } sock_set_nonblock(sd); bglobal.bg_csock = sd; return 0; } void control_shutdown(void) { struct bfd_control_socket *bcs; if (bglobal.bg_csockev) { thread_cancel(bglobal.bg_csockev); bglobal.bg_csockev = NULL; } socket_close(&bglobal.bg_csock); while (!TAILQ_EMPTY(&bglobal.bg_bcslist)) { bcs = TAILQ_FIRST(&bglobal.bg_bcslist); control_free(bcs); } } int control_accept(struct thread *t) { int csock, sd = THREAD_FD(t); csock = accept(sd, NULL, 0); if (csock == -1) { log_warning("%s: accept: %s", __func__, strerror(errno)); return 0; } if (control_new(csock) == NULL) close(csock); bglobal.bg_csockev = NULL; thread_add_read(master, control_accept, NULL, sd, &bglobal.bg_csockev); return 0; } /* * Client handling */ struct bfd_control_socket *control_new(int sd) { struct bfd_control_socket *bcs; bcs = XCALLOC(MTYPE_BFDD_CONTROL, sizeof(*bcs)); /* Disable notifications by default. */ bcs->bcs_notify = 0; bcs->bcs_sd = sd; thread_add_read(master, control_read, bcs, sd, &bcs->bcs_ev); TAILQ_INIT(&bcs->bcs_bcqueue); TAILQ_INIT(&bcs->bcs_bnplist); TAILQ_INSERT_TAIL(&bglobal.bg_bcslist, bcs, bcs_entry); return bcs; } static void control_free(struct bfd_control_socket *bcs) { struct bfd_control_queue *bcq; struct bfd_notify_peer *bnp; if (bcs->bcs_ev) { thread_cancel(bcs->bcs_ev); bcs->bcs_ev = NULL; } if (bcs->bcs_outev) { thread_cancel(bcs->bcs_outev); bcs->bcs_outev = NULL; } close(bcs->bcs_sd); TAILQ_REMOVE(&bglobal.bg_bcslist, bcs, bcs_entry); /* Empty output queue. */ while (!TAILQ_EMPTY(&bcs->bcs_bcqueue)) { bcq = TAILQ_FIRST(&bcs->bcs_bcqueue); control_queue_free(bcs, bcq); } /* Empty notification list. */ while (!TAILQ_EMPTY(&bcs->bcs_bnplist)) { bnp = TAILQ_FIRST(&bcs->bcs_bnplist); control_notifypeer_free(bcs, bnp); } control_reset_buf(&bcs->bcs_bin); XFREE(MTYPE_BFDD_CONTROL, bcs); } struct bfd_notify_peer *control_notifypeer_new(struct bfd_control_socket *bcs, struct bfd_session *bs) { struct bfd_notify_peer *bnp; bnp = control_notifypeer_find(bcs, bs); if (bnp) return bnp; bnp = XCALLOC(MTYPE_BFDD_CONTROL, sizeof(*bnp)); TAILQ_INSERT_TAIL(&bcs->bcs_bnplist, bnp, bnp_entry); bnp->bnp_bs = bs; bs->refcount++; return bnp; } static void control_notifypeer_free(struct bfd_control_socket *bcs, struct bfd_notify_peer *bnp) { TAILQ_REMOVE(&bcs->bcs_bnplist, bnp, bnp_entry); bnp->bnp_bs->refcount--; XFREE(MTYPE_BFDD_CONTROL, bnp); } struct bfd_notify_peer *control_notifypeer_find(struct bfd_control_socket *bcs, struct bfd_session *bs) { struct bfd_notify_peer *bnp; TAILQ_FOREACH (bnp, &bcs->bcs_bnplist, bnp_entry) { if (bnp->bnp_bs == bs) return bnp; } return NULL; } struct bfd_control_queue *control_queue_new(struct bfd_control_socket *bcs) { struct bfd_control_queue *bcq; bcq = XCALLOC(MTYPE_BFDD_NOTIFICATION, sizeof(*bcq)); control_reset_buf(&bcq->bcq_bcb); TAILQ_INSERT_TAIL(&bcs->bcs_bcqueue, bcq, bcq_entry); return bcq; } static void control_queue_free(struct bfd_control_socket *bcs, struct bfd_control_queue *bcq) { control_reset_buf(&bcq->bcq_bcb); TAILQ_REMOVE(&bcs->bcs_bcqueue, bcq, bcq_entry); XFREE(MTYPE_BFDD_NOTIFICATION, bcq); } static int control_queue_dequeue(struct bfd_control_socket *bcs) { struct bfd_control_queue *bcq; /* List is empty, nothing to do. */ if (TAILQ_EMPTY(&bcs->bcs_bcqueue)) goto empty_list; bcq = TAILQ_FIRST(&bcs->bcs_bcqueue); control_queue_free(bcs, bcq); /* Get the next buffer to send. */ if (TAILQ_EMPTY(&bcs->bcs_bcqueue)) goto empty_list; bcq = TAILQ_FIRST(&bcs->bcs_bcqueue); bcs->bcs_bout = &bcq->bcq_bcb; bcs->bcs_outev = NULL; thread_add_write(master, control_write, bcs, bcs->bcs_sd, &bcs->bcs_outev); return 1; empty_list: if (bcs->bcs_outev) { thread_cancel(bcs->bcs_outev); bcs->bcs_outev = NULL; } bcs->bcs_bout = NULL; return 0; } static int control_queue_enqueue(struct bfd_control_socket *bcs, struct bfd_control_msg *bcm) { struct bfd_control_queue *bcq; struct bfd_control_buffer *bcb; bcq = control_queue_new(bcs); if (bcq == NULL) return -1; bcb = &bcq->bcq_bcb; bcb->bcb_left = sizeof(struct bfd_control_msg) + ntohl(bcm->bcm_length); bcb->bcb_pos = 0; bcb->bcb_bcm = bcm; /* If this is the first item, then dequeue and start using it. */ if (bcs->bcs_bout == NULL) { bcs->bcs_bout = bcb; /* New messages, active write events. */ thread_add_write(master, control_write, bcs, bcs->bcs_sd, &bcs->bcs_outev); } return 0; } static int control_queue_enqueue_first(struct bfd_control_socket *bcs, struct bfd_control_msg *bcm) { struct bfd_control_queue *bcq, *bcqn; struct bfd_control_buffer *bcb; /* Enqueue it somewhere. */ if (control_queue_enqueue(bcs, bcm) == -1) return -1; /* * The item is either the first or the last. So we must first * check the best case where the item is already the first. */ bcq = TAILQ_FIRST(&bcs->bcs_bcqueue); bcb = &bcq->bcq_bcb; if (bcm == bcb->bcb_bcm) return 0; /* * The item was not the first, so it is the last. We'll try to * assign it to the head of the queue, however if there is a * transfer in progress, then we have to make the item as the * next one. * * Interrupting the transfer of in progress message will cause * the client to lose track of the message position/data. */ bcqn = TAILQ_LAST(&bcs->bcs_bcqueue, bcqueue); TAILQ_REMOVE(&bcs->bcs_bcqueue, bcqn, bcq_entry); if (bcb->bcb_pos != 0) { /* * First position is already being sent, insert into * second position. */ TAILQ_INSERT_AFTER(&bcs->bcs_bcqueue, bcq, bcqn, bcq_entry); } else { /* * Old message didn't start being sent, we still have * time to put this one in the head of the queue. */ TAILQ_INSERT_HEAD(&bcs->bcs_bcqueue, bcqn, bcq_entry); bcb = &bcqn->bcq_bcb; bcs->bcs_bout = bcb; } return 0; } static void control_reset_buf(struct bfd_control_buffer *bcb) { /* Get ride of old data. */ XFREE(MTYPE_BFDD_NOTIFICATION, bcb->bcb_buf); bcb->bcb_buf = NULL; bcb->bcb_pos = 0; bcb->bcb_left = 0; } static int control_read(struct thread *t) { struct bfd_control_socket *bcs = THREAD_ARG(t); struct bfd_control_buffer *bcb = &bcs->bcs_bin; int sd = bcs->bcs_sd; struct bfd_control_msg bcm; ssize_t bread; size_t plen; /* * Check if we have already downloaded message content, if so then skip * to * download the rest of it and process. * * Otherwise download a new message header and allocate the necessary * memory. */ if (bcb->bcb_buf != NULL) goto skip_header; bread = read(sd, &bcm, sizeof(bcm)); if (bread == 0) { control_free(bcs); return 0; } if (bread < 0) { if (errno == EAGAIN || errno == EWOULDBLOCK || errno == EINTR) goto schedule_next_read; log_warning("%s: read: %s", __func__, strerror(errno)); control_free(bcs); return 0; } /* Validate header fields. */ plen = ntohl(bcm.bcm_length); if (plen < 2) { log_debug("%s: client closed due small message length: %d", __func__, bcm.bcm_length); control_free(bcs); return 0; } if (bcm.bcm_ver != BMV_VERSION_1) { log_debug("%s: client closed due bad version: %d", __func__, bcm.bcm_ver); control_free(bcs); return 0; } /* Prepare the buffer to load the message. */ bcs->bcs_version = bcm.bcm_ver; bcs->bcs_type = bcm.bcm_type; bcb->bcb_pos = sizeof(bcm); bcb->bcb_left = plen; bcb->bcb_buf = XMALLOC(MTYPE_BFDD_NOTIFICATION, sizeof(bcm) + bcb->bcb_left + 1); if (bcb->bcb_buf == NULL) { log_warning("%s: not enough memory for message size: %u", __func__, bcb->bcb_left); control_free(bcs); return 0; } memcpy(bcb->bcb_buf, &bcm, sizeof(bcm)); /* Terminate data string with NULL for later processing. */ bcb->bcb_buf[sizeof(bcm) + bcb->bcb_left] = 0; skip_header: /* Download the remaining data of the message and process it. */ bread = read(sd, &bcb->bcb_buf[bcb->bcb_pos], bcb->bcb_left); if (bread == 0) { control_free(bcs); return 0; } if (bread < 0) { if (errno == EAGAIN || errno == EWOULDBLOCK || errno == EINTR) goto schedule_next_read; log_warning("%s: read: %s", __func__, strerror(errno)); control_free(bcs); return 0; } bcb->bcb_pos += bread; bcb->bcb_left -= bread; /* We need more data, return to wait more. */ if (bcb->bcb_left > 0) goto schedule_next_read; switch (bcb->bcb_bcm->bcm_type) { case BMT_REQUEST_ADD: control_handle_request_add(bcs, bcb->bcb_bcm); break; case BMT_REQUEST_DEL: control_handle_request_del(bcs, bcb->bcb_bcm); break; case BMT_NOTIFY: control_handle_notify(bcs, bcb->bcb_bcm); break; case BMT_NOTIFY_ADD: control_handle_notify_add(bcs, bcb->bcb_bcm); break; case BMT_NOTIFY_DEL: control_handle_notify_del(bcs, bcb->bcb_bcm); break; default: log_debug("%s: unhandled message type: %d", __func__, bcb->bcb_bcm->bcm_type); control_response(bcs, bcb->bcb_bcm->bcm_id, BCM_RESPONSE_ERROR, "invalid message type"); break; } bcs->bcs_version = 0; bcs->bcs_type = 0; control_reset_buf(bcb); schedule_next_read: bcs->bcs_ev = NULL; thread_add_read(master, control_read, bcs, sd, &bcs->bcs_ev); return 0; } static int control_write(struct thread *t) { struct bfd_control_socket *bcs = THREAD_ARG(t); struct bfd_control_buffer *bcb = bcs->bcs_bout; int sd = bcs->bcs_sd; ssize_t bwrite; bwrite = write(sd, &bcb->bcb_buf[bcb->bcb_pos], bcb->bcb_left); if (bwrite == 0) { control_free(bcs); return 0; } if (bwrite < 0) { if (errno == EAGAIN || errno == EWOULDBLOCK || errno == EINTR) { bcs->bcs_outev = NULL; thread_add_write(master, control_write, bcs, bcs->bcs_sd, &bcs->bcs_outev); return 0; } log_warning("%s: write: %s", __func__, strerror(errno)); control_free(bcs); return 0; } bcb->bcb_pos += bwrite; bcb->bcb_left -= bwrite; if (bcb->bcb_left > 0) { bcs->bcs_outev = NULL; thread_add_write(master, control_write, bcs, bcs->bcs_sd, &bcs->bcs_outev); return 0; } control_queue_dequeue(bcs); return 0; } /* * Message processing */ static void control_handle_request_add(struct bfd_control_socket *bcs, struct bfd_control_msg *bcm) { const char *json = (const char *)bcm->bcm_data; if (config_request_add(json) == 0) control_response(bcs, bcm->bcm_id, BCM_RESPONSE_OK, NULL); else control_response(bcs, bcm->bcm_id, BCM_RESPONSE_ERROR, "request add failed"); } static void control_handle_request_del(struct bfd_control_socket *bcs, struct bfd_control_msg *bcm) { const char *json = (const char *)bcm->bcm_data; if (config_request_del(json) == 0) control_response(bcs, bcm->bcm_id, BCM_RESPONSE_OK, NULL); else control_response(bcs, bcm->bcm_id, BCM_RESPONSE_ERROR, "request del failed"); } static struct bfd_session *_notify_find_peer(struct bfd_peer_cfg *bpc) { struct peer_label *pl; if (bpc->bpc_has_label) { pl = pl_find(bpc->bpc_label); if (pl) return pl->pl_bs; } return bs_peer_find(bpc); } static void _control_handle_notify(struct hash_bucket *hb, void *arg) { struct bfd_control_socket *bcs = arg; struct bfd_session *bs = hb->data; /* Notify peer configuration. */ if (bcs->bcs_notify & BCM_NOTIFY_CONFIG) _control_notify_config(bcs, BCM_NOTIFY_CONFIG_ADD, bs); /* Notify peer status. */ if (bcs->bcs_notify & BCM_NOTIFY_PEER_STATE) _control_notify(bcs, bs); } static void control_handle_notify(struct bfd_control_socket *bcs, struct bfd_control_msg *bcm) { memcpy(&bcs->bcs_notify, bcm->bcm_data, sizeof(bcs->bcs_notify)); control_response(bcs, bcm->bcm_id, BCM_RESPONSE_OK, NULL); /* * If peer asked for notification configuration, send everything that * was configured until the moment to sync up. */ if (bcs->bcs_notify & (BCM_NOTIFY_CONFIG | BCM_NOTIFY_PEER_STATE)) bfd_id_iterate(_control_handle_notify, bcs); } static int notify_add_cb(struct bfd_peer_cfg *bpc, void *arg) { struct bfd_control_socket *bcs = arg; struct bfd_session *bs = _notify_find_peer(bpc); if (bs == NULL) return -1; if (control_notifypeer_new(bcs, bs) == NULL) return -1; /* Notify peer status. */ _control_notify(bcs, bs); return 0; } static int notify_del_cb(struct bfd_peer_cfg *bpc, void *arg) { struct bfd_control_socket *bcs = arg; struct bfd_session *bs = _notify_find_peer(bpc); struct bfd_notify_peer *bnp; if (bs == NULL) return -1; bnp = control_notifypeer_find(bcs, bs); if (bnp) control_notifypeer_free(bcs, bnp); return 0; } static void control_handle_notify_add(struct bfd_control_socket *bcs, struct bfd_control_msg *bcm) { const char *json = (const char *)bcm->bcm_data; if (config_notify_request(bcs, json, notify_add_cb) == 0) { control_response(bcs, bcm->bcm_id, BCM_RESPONSE_OK, NULL); return; } control_response(bcs, bcm->bcm_id, BCM_RESPONSE_ERROR, "failed to parse notify data"); } static void control_handle_notify_del(struct bfd_control_socket *bcs, struct bfd_control_msg *bcm) { const char *json = (const char *)bcm->bcm_data; if (config_notify_request(bcs, json, notify_del_cb) == 0) { control_response(bcs, bcm->bcm_id, BCM_RESPONSE_OK, NULL); return; } control_response(bcs, bcm->bcm_id, BCM_RESPONSE_ERROR, "failed to parse notify data"); } /* * Internal functions used by the BFD daemon. */ static void control_response(struct bfd_control_socket *bcs, uint16_t id, const char *status, const char *error) { struct bfd_control_msg *bcm; char *jsonstr; size_t jsonstrlen; /* Generate JSON response. */ jsonstr = config_response(status, error); if (jsonstr == NULL) { log_warning("%s: config_response: failed to get JSON str", __func__); return; } /* Allocate data and answer. */ jsonstrlen = strlen(jsonstr); bcm = XMALLOC(MTYPE_BFDD_NOTIFICATION, sizeof(struct bfd_control_msg) + jsonstrlen); bcm->bcm_length = htonl(jsonstrlen); bcm->bcm_ver = BMV_VERSION_1; bcm->bcm_type = BMT_RESPONSE; bcm->bcm_id = id; memcpy(bcm->bcm_data, jsonstr, jsonstrlen); XFREE(MTYPE_BFDD_NOTIFICATION, jsonstr); control_queue_enqueue_first(bcs, bcm); } static void _control_notify(struct bfd_control_socket *bcs, struct bfd_session *bs) { struct bfd_control_msg *bcm; char *jsonstr; size_t jsonstrlen; /* Generate JSON response. */ jsonstr = config_notify(bs); if (jsonstr == NULL) { log_warning("%s: config_notify: failed to get JSON str", __func__); return; } /* Allocate data and answer. */ jsonstrlen = strlen(jsonstr); bcm = XMALLOC(MTYPE_BFDD_NOTIFICATION, sizeof(struct bfd_control_msg) + jsonstrlen); bcm->bcm_length = htonl(jsonstrlen); bcm->bcm_ver = BMV_VERSION_1; bcm->bcm_type = BMT_NOTIFY; bcm->bcm_id = htons(BCM_NOTIFY_ID); memcpy(bcm->bcm_data, jsonstr, jsonstrlen); XFREE(MTYPE_BFDD_NOTIFICATION, jsonstr); control_queue_enqueue(bcs, bcm); } int control_notify(struct bfd_session *bs) { struct bfd_control_socket *bcs; struct bfd_notify_peer *bnp; /* Notify zebra listeners as well. */ ptm_bfd_notify(bs); /* * PERFORMANCE: reuse the bfd_control_msg allocated data for * all control sockets to avoid wasting memory. */ TAILQ_FOREACH (bcs, &bglobal.bg_bcslist, bcs_entry) { /* * Test for all notifications first, then search for * specific peers. */ if ((bcs->bcs_notify & BCM_NOTIFY_PEER_STATE) == 0) { bnp = control_notifypeer_find(bcs, bs); /* * If the notification is not configured here, * don't send it. */ if (bnp == NULL) continue; } _control_notify(bcs, bs); } return 0; } static void _control_notify_config(struct bfd_control_socket *bcs, const char *op, struct bfd_session *bs) { struct bfd_control_msg *bcm; char *jsonstr; size_t jsonstrlen; /* Generate JSON response. */ jsonstr = config_notify_config(op, bs); if (jsonstr == NULL) { log_warning("%s: config_notify_config: failed to get JSON str", __func__); return; } /* Allocate data and answer. */ jsonstrlen = strlen(jsonstr); bcm = XMALLOC(MTYPE_BFDD_NOTIFICATION, sizeof(struct bfd_control_msg) + jsonstrlen); bcm->bcm_length = htonl(jsonstrlen); bcm->bcm_ver = BMV_VERSION_1; bcm->bcm_type = BMT_NOTIFY; bcm->bcm_id = htons(BCM_NOTIFY_ID); memcpy(bcm->bcm_data, jsonstr, jsonstrlen); XFREE(MTYPE_BFDD_NOTIFICATION, jsonstr); control_queue_enqueue(bcs, bcm); } int control_notify_config(const char *op, struct bfd_session *bs) { struct bfd_control_socket *bcs; struct bfd_notify_peer *bnp; /* Remove the control sockets notification for this peer. */ if (strcmp(op, BCM_NOTIFY_CONFIG_DELETE) == 0 && bs->refcount > 0) { TAILQ_FOREACH (bcs, &bglobal.bg_bcslist, bcs_entry) { bnp = control_notifypeer_find(bcs, bs); if (bnp) control_notifypeer_free(bcs, bnp); } } /* * PERFORMANCE: reuse the bfd_control_msg allocated data for * all control sockets to avoid wasting memory. */ TAILQ_FOREACH (bcs, &bglobal.bg_bcslist, bcs_entry) { /* * Test for all notifications first, then search for * specific peers. */ if ((bcs->bcs_notify & BCM_NOTIFY_CONFIG) == 0) continue; _control_notify_config(bcs, op, bs); } return 0; } frr-7.2.1/bfdd/event.c0000644000000000000000000000744313610377563011410 00000000000000/********************************************************************* * Copyright 2017-2018 Network Device Education Foundation, Inc. ("NetDEF") * * This program is free software; you can redistribute it and/or modify it * under the terms of the 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; see the file COPYING; if not, write to the Free Software * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA * * event.c: implements the BFD loop event handlers. * * Authors * ------- * Rafael Zalamena */ #include #include "bfd.h" void tv_normalize(struct timeval *tv); void tv_normalize(struct timeval *tv) { /* Remove seconds part from microseconds. */ tv->tv_sec = tv->tv_usec / 1000000; tv->tv_usec = tv->tv_usec % 1000000; } void bfd_recvtimer_update(struct bfd_session *bs) { struct timeval tv = {.tv_sec = 0, .tv_usec = bs->detect_TO}; /* Remove previous schedule if any. */ bfd_recvtimer_delete(bs); /* Don't add event if peer is deactivated. */ if (BFD_CHECK_FLAG(bs->flags, BFD_SESS_FLAG_SHUTDOWN) || bs->sock == -1) return; tv_normalize(&tv); #ifdef BFD_EVENT_DEBUG log_debug("%s: sec = %ld, usec = %ld", __func__, tv.tv_sec, tv.tv_usec); #endif /* BFD_EVENT_DEBUG */ thread_add_timer_tv(master, bfd_recvtimer_cb, bs, &tv, &bs->recvtimer_ev); } void bfd_echo_recvtimer_update(struct bfd_session *bs) { struct timeval tv = {.tv_sec = 0, .tv_usec = bs->echo_detect_TO}; /* Remove previous schedule if any. */ bfd_echo_recvtimer_delete(bs); /* Don't add event if peer is deactivated. */ if (BFD_CHECK_FLAG(bs->flags, BFD_SESS_FLAG_SHUTDOWN) || bs->sock == -1) return; tv_normalize(&tv); #ifdef BFD_EVENT_DEBUG log_debug("%s: sec = %ld, usec = %ld", __func__, tv.tv_sec, tv.tv_usec); #endif /* BFD_EVENT_DEBUG */ thread_add_timer_tv(master, bfd_echo_recvtimer_cb, bs, &tv, &bs->echo_recvtimer_ev); } void bfd_xmttimer_update(struct bfd_session *bs, uint64_t jitter) { struct timeval tv = {.tv_sec = 0, .tv_usec = jitter}; /* Remove previous schedule if any. */ bfd_xmttimer_delete(bs); /* Don't add event if peer is deactivated. */ if (BFD_CHECK_FLAG(bs->flags, BFD_SESS_FLAG_SHUTDOWN) || bs->sock == -1) return; tv_normalize(&tv); #ifdef BFD_EVENT_DEBUG log_debug("%s: sec = %ld, usec = %ld", __func__, tv.tv_sec, tv.tv_usec); #endif /* BFD_EVENT_DEBUG */ thread_add_timer_tv(master, bfd_xmt_cb, bs, &tv, &bs->xmttimer_ev); } void bfd_echo_xmttimer_update(struct bfd_session *bs, uint64_t jitter) { struct timeval tv = {.tv_sec = 0, .tv_usec = jitter}; /* Remove previous schedule if any. */ bfd_echo_xmttimer_delete(bs); /* Don't add event if peer is deactivated. */ if (BFD_CHECK_FLAG(bs->flags, BFD_SESS_FLAG_SHUTDOWN) || bs->sock == -1) return; tv_normalize(&tv); #ifdef BFD_EVENT_DEBUG log_debug("%s: sec = %ld, usec = %ld", __func__, tv.tv_sec, tv.tv_usec); #endif /* BFD_EVENT_DEBUG */ thread_add_timer_tv(master, bfd_echo_xmt_cb, bs, &tv, &bs->echo_xmttimer_ev); } void bfd_recvtimer_delete(struct bfd_session *bs) { THREAD_OFF(bs->recvtimer_ev); } void bfd_echo_recvtimer_delete(struct bfd_session *bs) { THREAD_OFF(bs->echo_recvtimer_ev); } void bfd_xmttimer_delete(struct bfd_session *bs) { THREAD_OFF(bs->xmttimer_ev); } void bfd_echo_xmttimer_delete(struct bfd_session *bs) { THREAD_OFF(bs->echo_xmttimer_ev); } frr-7.2.1/bfdd/log.c0000644000000000000000000000475313610377563011051 00000000000000/********************************************************************* * Copyright 2017-2018 Network Device Education Foundation, Inc. ("NetDEF") * * This program is free software; you can redistribute it and/or modify it * under the terms of the 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; see the file COPYING; if not, write to the Free Software * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA * * log.c: implements an abstraction between loggers interface. Implement all * log backends in this file. * * Authors * ------- * Rafael Zalamena */ #include #include "bfd.h" #include "lib/log_int.h" void log_msg(int level, const char *fmt, va_list vl); static int log_fg; static int log_level = BLOG_DEBUG; void log_init(int foreground, enum blog_level level, struct frr_daemon_info *fdi) { log_fg = foreground; log_level = level; openzlog(fdi->progname, fdi->logname, 0, LOG_CONS | LOG_NDELAY | LOG_PID, LOG_DAEMON); } void log_msg(int level, const char *fmt, va_list vl) { if (level < log_level) return; switch (level) { case BLOG_DEBUG: vzlog(LOG_DEBUG, fmt, vl); break; case BLOG_INFO: vzlog(LOG_INFO, fmt, vl); break; case BLOG_WARNING: vzlog(LOG_WARNING, fmt, vl); break; case BLOG_ERROR: vzlog(LOG_ERR, fmt, vl); break; case BLOG_FATAL: vzlog(LOG_EMERG, fmt, vl); break; default: vfprintf(stderr, fmt, vl); break; } } void log_info(const char *fmt, ...) { va_list vl; va_start(vl, fmt); log_msg(BLOG_INFO, fmt, vl); va_end(vl); } void log_debug(const char *fmt, ...) { va_list vl; va_start(vl, fmt); log_msg(BLOG_DEBUG, fmt, vl); va_end(vl); } void log_error(const char *fmt, ...) { va_list vl; va_start(vl, fmt); log_msg(BLOG_ERROR, fmt, vl); va_end(vl); } void log_warning(const char *fmt, ...) { va_list vl; va_start(vl, fmt); log_msg(BLOG_WARNING, fmt, vl); va_end(vl); } void log_fatal(const char *fmt, ...) { va_list vl; va_start(vl, fmt); log_msg(BLOG_FATAL, fmt, vl); va_end(vl); exit(1); } frr-7.2.1/bfdd/ptm_adapter.c0000644000000000000000000005045613610377563012571 00000000000000/* * BFD PTM adapter code * Copyright (C) 2018 Network Device Education Foundation, Inc. ("NetDEF") * * FRR is free software; you can redistribute it and/or modify it * under the terms of the GNU General Public License as published by the * Free Software Foundation; either version 2, or (at your option) any * later version. * * FRR 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 FRR; see the file COPYING. If not, write to the Free * Software Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA * 02111-1307, USA. */ #include #include "lib/libfrr.h" #include "lib/queue.h" #include "lib/stream.h" #include "lib/zclient.h" #include "lib/bfd.h" #include "bfd.h" /* * Data structures */ struct ptm_client_notification { struct bfd_session *pcn_bs; struct ptm_client *pcn_pc; TAILQ_ENTRY(ptm_client_notification) pcn_entry; }; TAILQ_HEAD(pcnqueue, ptm_client_notification); struct ptm_client { uint32_t pc_pid; struct pcnqueue pc_pcnqueue; TAILQ_ENTRY(ptm_client) pc_entry; }; TAILQ_HEAD(pcqueue, ptm_client); static struct pcqueue pcqueue; static struct zclient *zclient; /* * Prototypes */ static int _ptm_msg_address(struct stream *msg, int family, const void *addr); static void _ptm_msg_read_address(struct stream *msg, struct sockaddr_any *sa); static int _ptm_msg_read(struct stream *msg, int command, vrf_id_t vrf_id, struct bfd_peer_cfg *bpc, struct ptm_client **pc); static struct ptm_client *pc_lookup(uint32_t pid); static struct ptm_client *pc_new(uint32_t pid); static void pc_free(struct ptm_client *pc); static void pc_free_all(void); static struct ptm_client_notification *pcn_new(struct ptm_client *pc, struct bfd_session *bs); static struct ptm_client_notification *pcn_lookup(struct ptm_client *pc, struct bfd_session *bs); static void pcn_free(struct ptm_client_notification *pcn); static void bfdd_dest_register(struct stream *msg, vrf_id_t vrf_id); static void bfdd_dest_deregister(struct stream *msg, vrf_id_t vrf_id); static void bfdd_client_register(struct stream *msg); static void bfdd_client_deregister(struct stream *msg); /* * Functions */ #ifdef BFD_DEBUG static void debug_printbpc(const char *func, unsigned int line, struct bfd_peer_cfg *bpc); static void debug_printbpc(const char *func, unsigned int line, struct bfd_peer_cfg *bpc) { char addr[3][128]; char timers[3][128]; char cbit_str[10]; addr[0][0] = addr[1][0] = addr[2][0] = timers[0][0] = timers[1][0] = timers[2][0] = 0; snprintf(addr[0], sizeof(addr[0]), "peer:%s", satostr(&bpc->bpc_peer)); if (bpc->bpc_local.sa_sin.sin_family) snprintf(addr[1], sizeof(addr[1]), " local:%s", satostr(&bpc->bpc_local)); if (bpc->bpc_has_localif) snprintf(addr[2], sizeof(addr[2]), " ifname:%s", bpc->bpc_localif); if (bpc->bpc_has_vrfname) snprintf(addr[2], sizeof(addr[2]), " vrf:%s", bpc->bpc_vrfname); if (bpc->bpc_has_recvinterval) snprintf(timers[0], sizeof(timers[0]), " rx:%lu", bpc->bpc_recvinterval); if (bpc->bpc_has_txinterval) snprintf(timers[1], sizeof(timers[1]), " tx:%lu", bpc->bpc_recvinterval); if (bpc->bpc_has_detectmultiplier) snprintf(timers[2], sizeof(timers[2]), " detect-multiplier:%d", bpc->bpc_detectmultiplier); sprintf(cbit_str, "CB %x", bpc->bpc_cbit); log_debug("%s:%d: %s %s%s%s%s%s%s %s", func, line, bpc->bpc_mhop ? "multi-hop" : "single-hop", addr[0], addr[1], addr[2], timers[0], timers[1], timers[2], cbit_str); } #define DEBUG_PRINTBPC(bpc) debug_printbpc(__FILE__, __LINE__, (bpc)) #else #define DEBUG_PRINTBPC(bpc) #endif /* BFD_DEBUG */ static int _ptm_msg_address(struct stream *msg, int family, const void *addr) { stream_putc(msg, family); switch (family) { case AF_INET: stream_put(msg, addr, sizeof(struct in_addr)); stream_putc(msg, 32); break; case AF_INET6: stream_put(msg, addr, sizeof(struct in6_addr)); stream_putc(msg, 128); break; default: assert(0); break; } return 0; } int ptm_bfd_notify(struct bfd_session *bs) { struct stream *msg; bs->stats.znotification++; /* * Message format: * - header: command, vrf * - l: interface index * - c: family * - AF_INET: * - 4 bytes: ipv4 * - AF_INET6: * - 16 bytes: ipv6 * - c: prefix length * - l: bfd status * - c: family * - AF_INET: * - 4 bytes: ipv4 * - AF_INET6: * - 16 bytes: ipv6 * - c: prefix length * - c: cbit * * Commands: ZEBRA_BFD_DEST_REPLAY * * q(64), l(32), w(16), c(8) */ msg = zclient->obuf; stream_reset(msg); /* TODO: VRF handling */ if (bs->vrf) zclient_create_header(msg, ZEBRA_BFD_DEST_REPLAY, bs->vrf->vrf_id); else zclient_create_header(msg, ZEBRA_BFD_DEST_REPLAY, VRF_DEFAULT); /* This header will be handled by `zebra_ptm.c`. */ stream_putl(msg, ZEBRA_INTERFACE_BFD_DEST_UPDATE); /* NOTE: Interface is a shortcut to avoid comparing source address. */ if (bs->ifp != NULL) stream_putl(msg, bs->ifp->ifindex); else stream_putl(msg, IFINDEX_INTERNAL); /* BFD destination prefix information. */ _ptm_msg_address(msg, bs->key.family, &bs->key.peer); /* BFD status */ switch (bs->ses_state) { case PTM_BFD_UP: stream_putl(msg, BFD_STATUS_UP); break; case PTM_BFD_ADM_DOWN: case PTM_BFD_DOWN: case PTM_BFD_INIT: stream_putl(msg, BFD_STATUS_DOWN); break; default: stream_putl(msg, BFD_STATUS_UNKNOWN); break; } /* BFD source prefix information. */ _ptm_msg_address(msg, bs->key.family, &bs->key.local); stream_putc(msg, bs->remote_cbit); /* Write packet size. */ stream_putw_at(msg, 0, stream_get_endp(msg)); return zclient_send_message(zclient); } static void _ptm_msg_read_address(struct stream *msg, struct sockaddr_any *sa) { uint16_t family; STREAM_GETW(msg, family); switch (family) { case AF_INET: sa->sa_sin.sin_family = family; STREAM_GET(&sa->sa_sin.sin_addr, msg, sizeof(sa->sa_sin.sin_addr)); #ifdef HAVE_STRUCT_SOCKADDR_SA_LEN sa->sa_sin.sin_len = sizeof(sa->sa_sin); #endif /* HAVE_STRUCT_SOCKADDR_SA_LEN */ return; case AF_INET6: sa->sa_sin6.sin6_family = family; STREAM_GET(&sa->sa_sin6.sin6_addr, msg, sizeof(sa->sa_sin6.sin6_addr)); #ifdef HAVE_STRUCT_SOCKADDR_SA_LEN sa->sa_sin6.sin6_len = sizeof(sa->sa_sin6); #endif /* HAVE_STRUCT_SOCKADDR_SA_LEN */ return; default: log_warning("ptm-read-address: invalid family: %d", family); break; } stream_failure: memset(sa, 0, sizeof(*sa)); } static int _ptm_msg_read(struct stream *msg, int command, vrf_id_t vrf_id, struct bfd_peer_cfg *bpc, struct ptm_client **pc) { uint32_t pid; uint8_t ttl __attribute__((unused)); size_t ifnamelen; /* * Register/Deregister/Update Message format: * - header: Command, VRF * - l: pid * - w: family * - AF_INET: * - l: destination ipv4 * - AF_INET6: * - 16 bytes: destination IPv6 * - command != ZEBRA_BFD_DEST_DEREGISTER * - l: min_rx * - l: min_tx * - c: detect multiplier * - c: is_multihop? * - multihop: * - w: family * - AF_INET: * - l: destination ipv4 * - AF_INET6: * - 16 bytes: destination IPv6 * - c: ttl * - no multihop * - AF_INET6: * - w: family * - 16 bytes: ipv6 address * - c: ifname length * - X bytes: interface name * - c: bfd_cbit * * q(64), l(32), w(16), c(8) */ /* Initialize parameters return values. */ memset(bpc, 0, sizeof(*bpc)); *pc = NULL; /* Find or allocate process context data. */ STREAM_GETL(msg, pid); *pc = pc_new(pid); if (*pc == NULL) { log_debug("ptm-read: failed to allocate memory"); return -1; } /* Register/update peer information. */ _ptm_msg_read_address(msg, &bpc->bpc_peer); /* Determine IP type from peer destination. */ bpc->bpc_ipv4 = (bpc->bpc_peer.sa_sin.sin_family == AF_INET); /* Get peer configuration. */ if (command != ZEBRA_BFD_DEST_DEREGISTER) { STREAM_GETL(msg, bpc->bpc_recvinterval); bpc->bpc_has_recvinterval = (bpc->bpc_recvinterval != BPC_DEF_RECEIVEINTERVAL); STREAM_GETL(msg, bpc->bpc_txinterval); bpc->bpc_has_txinterval = (bpc->bpc_txinterval != BPC_DEF_TRANSMITINTERVAL); STREAM_GETC(msg, bpc->bpc_detectmultiplier); bpc->bpc_has_detectmultiplier = (bpc->bpc_detectmultiplier != BPC_DEF_DETECTMULTIPLIER); } /* Read (single|multi)hop and its options. */ STREAM_GETC(msg, bpc->bpc_mhop); if (bpc->bpc_mhop) { /* Read multihop source address and TTL. */ _ptm_msg_read_address(msg, &bpc->bpc_local); STREAM_GETC(msg, ttl); } else { /* If target is IPv6, then we must obtain local address. */ if (bpc->bpc_ipv4 == false) _ptm_msg_read_address(msg, &bpc->bpc_local); /* * Read interface name and make sure it fits our data * structure, otherwise fail. */ STREAM_GETC(msg, ifnamelen); if (ifnamelen >= sizeof(bpc->bpc_localif)) { log_error("ptm-read: interface name is too big"); return -1; } bpc->bpc_has_localif = ifnamelen > 0; if (bpc->bpc_has_localif) { STREAM_GET(bpc->bpc_localif, msg, ifnamelen); bpc->bpc_localif[ifnamelen] = 0; } } if (vrf_id != VRF_DEFAULT) { struct vrf *vrf; vrf = vrf_lookup_by_id(vrf_id); if (vrf) { bpc->bpc_has_vrfname = true; strlcpy(bpc->bpc_vrfname, vrf->name, sizeof(bpc->bpc_vrfname)); } else { log_error("ptm-read: vrf id %u could not be identified", vrf_id); return -1; } } STREAM_GETC(msg, bpc->bpc_cbit); /* Sanity check: peer and local address must match IP types. */ if (bpc->bpc_local.sa_sin.sin_family != 0 && (bpc->bpc_local.sa_sin.sin_family != bpc->bpc_peer.sa_sin.sin_family)) { log_warning("ptm-read: peer family doesn't match local type"); return -1; } return 0; stream_failure: return -1; } static void bfdd_dest_register(struct stream *msg, vrf_id_t vrf_id) { struct ptm_client *pc; struct ptm_client_notification *pcn; struct bfd_session *bs; struct bfd_peer_cfg bpc; /* Read the client context and peer data. */ if (_ptm_msg_read(msg, ZEBRA_BFD_DEST_REGISTER, vrf_id, &bpc, &pc) == -1) return; DEBUG_PRINTBPC(&bpc); /* Find or start new BFD session. */ bs = bs_peer_find(&bpc); if (bs == NULL) { bs = ptm_bfd_sess_new(&bpc); if (bs == NULL) { log_debug("ptm-add-dest: failed to create BFD session"); return; } } else { /* Don't try to change echo/shutdown state. */ bpc.bpc_echo = BFD_CHECK_FLAG(bs->flags, BFD_SESS_FLAG_ECHO); bpc.bpc_shutdown = BFD_CHECK_FLAG(bs->flags, BFD_SESS_FLAG_SHUTDOWN); } /* Create client peer notification register. */ pcn = pcn_new(pc, bs); if (pcn == NULL) { log_error("ptm-add-dest: failed to registrate notifications"); return; } ptm_bfd_notify(bs); } static void bfdd_dest_deregister(struct stream *msg, vrf_id_t vrf_id) { struct ptm_client *pc; struct ptm_client_notification *pcn; struct bfd_session *bs; struct bfd_peer_cfg bpc; /* Read the client context and peer data. */ if (_ptm_msg_read(msg, ZEBRA_BFD_DEST_DEREGISTER, vrf_id, &bpc, &pc) == -1) return; DEBUG_PRINTBPC(&bpc); /* Find or start new BFD session. */ bs = bs_peer_find(&bpc); if (bs == NULL) { log_debug("ptm-del-dest: failed to find BFD session"); return; } /* Unregister client peer notification. */ pcn = pcn_lookup(pc, bs); pcn_free(pcn); if (bs->refcount || BFD_CHECK_FLAG(bs->flags, BFD_SESS_FLAG_CONFIG)) return; ptm_bfd_sess_del(&bpc); } /* * header: command, VRF * l: pid */ static void bfdd_client_register(struct stream *msg) { struct ptm_client *pc; uint32_t pid; /* Find or allocate process context data. */ STREAM_GETL(msg, pid); pc = pc_new(pid); if (pc == NULL) { log_error("ptm-add-client: failed to register client: %u", pid); return; } return; stream_failure: log_error("ptm-add-client: failed to register client"); } /* * header: command, VRF * l: pid */ static void bfdd_client_deregister(struct stream *msg) { struct ptm_client *pc; uint32_t pid; /* Find or allocate process context data. */ STREAM_GETL(msg, pid); pc = pc_lookup(pid); if (pc == NULL) { log_debug("ptm-del-client: failed to find client: %u", pid); return; } pc_free(pc); return; stream_failure: log_error("ptm-del-client: failed to deregister client"); } static int bfdd_replay(ZAPI_CALLBACK_ARGS) { struct stream *msg = zclient->ibuf; uint32_t rcmd; STREAM_GETL(msg, rcmd); switch (rcmd) { case ZEBRA_BFD_DEST_REGISTER: case ZEBRA_BFD_DEST_UPDATE: bfdd_dest_register(msg, vrf_id); break; case ZEBRA_BFD_DEST_DEREGISTER: bfdd_dest_deregister(msg, vrf_id); break; case ZEBRA_BFD_CLIENT_REGISTER: bfdd_client_register(msg); break; case ZEBRA_BFD_CLIENT_DEREGISTER: bfdd_client_deregister(msg); break; default: log_debug("ptm-replay: invalid message type %u", rcmd); return -1; } return 0; stream_failure: log_error("ptm-replay: failed to find command"); return -1; } static void bfdd_zebra_connected(struct zclient *zc) { struct stream *msg = zc->obuf; /* Clean-up and free ptm clients data memory. */ pc_free_all(); /* * The replay is an empty message just to trigger client daemons * configuration replay. */ stream_reset(msg); zclient_create_header(msg, ZEBRA_BFD_DEST_REPLAY, VRF_DEFAULT); stream_putl(msg, ZEBRA_BFD_DEST_REPLAY); stream_putw_at(msg, 0, stream_get_endp(msg)); /* Ask for interfaces information. */ zclient_create_header(msg, ZEBRA_INTERFACE_ADD, VRF_DEFAULT); /* Send requests. */ zclient_send_message(zclient); } static void bfdd_sessions_enable_interface(struct interface *ifp) { struct bfd_session_observer *bso; struct bfd_session *bs; struct vrf *vrf; TAILQ_FOREACH(bso, &bglobal.bg_obslist, bso_entry) { bs = bso->bso_bs; /* Interface name mismatch. */ if (strcmp(ifp->name, bs->key.ifname)) continue; vrf = vrf_lookup_by_id(ifp->vrf_id); if (!vrf) continue; if (bs->key.vrfname[0] && strcmp(vrf->name, bs->key.vrfname)) continue; /* Skip enabled sessions. */ if (bs->sock != -1) continue; /* Try to enable it. */ bfd_session_enable(bs); } } static void bfdd_sessions_disable_interface(struct interface *ifp) { struct bfd_session_observer *bso; struct bfd_session *bs; TAILQ_FOREACH(bso, &bglobal.bg_obslist, bso_entry) { bs = bso->bso_bs; if (strcmp(ifp->name, bs->key.ifname)) continue; /* Skip disabled sessions. */ if (bs->sock == -1) continue; bfd_session_disable(bs); } } void bfdd_sessions_enable_vrf(struct vrf *vrf) { struct bfd_session_observer *bso; struct bfd_session *bs; /* it may affect configs without interfaces */ TAILQ_FOREACH(bso, &bglobal.bg_obslist, bso_entry) { bs = bso->bso_bs; if (bs->vrf) continue; if (bs->key.vrfname[0] && strcmp(vrf->name, bs->key.vrfname)) continue; /* need to update the vrf information on * bs so that callbacks are handled */ bs->vrf = vrf; /* Skip enabled sessions. */ if (bs->sock != -1) continue; /* Try to enable it. */ bfd_session_enable(bs); } } void bfdd_sessions_disable_vrf(struct vrf *vrf) { struct bfd_session_observer *bso; struct bfd_session *bs; TAILQ_FOREACH(bso, &bglobal.bg_obslist, bso_entry) { bs = bso->bso_bs; if (bs->key.vrfname[0] && strcmp(vrf->name, bs->key.vrfname)) continue; /* Skip disabled sessions. */ if (bs->sock == -1) continue; bfd_session_disable(bs); } } static int bfdd_interface_update(ZAPI_CALLBACK_ARGS) { struct interface *ifp; /* * `zebra_interface_add_read` will handle the interface creation * on `lib/if.c`. We'll use that data structure instead of * rolling our own. */ if (cmd == ZEBRA_INTERFACE_ADD) { ifp = zebra_interface_add_read(zclient->ibuf, vrf_id); if (ifp == NULL) return 0; bfdd_sessions_enable_interface(ifp); return 0; } /* Update interface information. */ ifp = zebra_interface_state_read(zclient->ibuf, vrf_id); if (ifp == NULL) return 0; bfdd_sessions_disable_interface(ifp); if_set_index(ifp, IFINDEX_INTERNAL); return 0; } static int bfdd_interface_vrf_update(ZAPI_CALLBACK_ARGS) { struct interface *ifp; vrf_id_t nvrfid; ifp = zebra_interface_vrf_update_read(zclient->ibuf, vrf_id, &nvrfid); if (ifp == NULL) return 0; if_update_to_new_vrf(ifp, nvrfid); return 0; } static void bfdd_sessions_enable_address(struct connected *ifc) { struct bfd_session_observer *bso; struct bfd_session *bs; struct prefix prefix; TAILQ_FOREACH(bso, &bglobal.bg_obslist, bso_entry) { /* Skip enabled sessions. */ bs = bso->bso_bs; if (bs->sock != -1) continue; /* Check address. */ prefix = bso->bso_addr; prefix.prefixlen = ifc->address->prefixlen; if (prefix_cmp(&prefix, ifc->address)) continue; /* Try to enable it. */ bfd_session_enable(bs); } } static int bfdd_interface_address_update(ZAPI_CALLBACK_ARGS) { struct connected *ifc; ifc = zebra_interface_address_read(cmd, zclient->ibuf, vrf_id); if (ifc == NULL) return 0; bfdd_sessions_enable_address(ifc); return 0; } void bfdd_zclient_init(struct zebra_privs_t *bfdd_priv) { zclient = zclient_new(master, &zclient_options_default); assert(zclient != NULL); zclient_init(zclient, ZEBRA_ROUTE_BFD, 0, bfdd_priv); /* * We'll receive all messages through replay, however it will * contain a special field with the real command inside so we * avoid having to create too many handlers. */ zclient->bfd_dest_replay = bfdd_replay; /* Send replay request on zebra connect. */ zclient->zebra_connected = bfdd_zebra_connected; /* Learn interfaces from zebra instead of the OS. */ zclient->interface_add = bfdd_interface_update; zclient->interface_delete = bfdd_interface_update; /* Learn about interface VRF. */ zclient->interface_vrf_update = bfdd_interface_vrf_update; /* Learn about new addresses being registered. */ zclient->interface_address_add = bfdd_interface_address_update; zclient->interface_address_delete = bfdd_interface_address_update; } void bfdd_zclient_register(vrf_id_t vrf_id) { if (!zclient || zclient->sock < 0) return; zclient_send_reg_requests(zclient, vrf_id); } void bfdd_zclient_unregister(vrf_id_t vrf_id) { if (!zclient || zclient->sock < 0) return; zclient_send_dereg_requests(zclient, vrf_id); } void bfdd_zclient_stop(void) { zclient_stop(zclient); /* Clean-up and free ptm clients data memory. */ pc_free_all(); } /* * Client handling. */ static struct ptm_client *pc_lookup(uint32_t pid) { struct ptm_client *pc; TAILQ_FOREACH (pc, &pcqueue, pc_entry) { if (pc->pc_pid != pid) continue; break; } return pc; } static struct ptm_client *pc_new(uint32_t pid) { struct ptm_client *pc; /* Look up first, if not found create the client. */ pc = pc_lookup(pid); if (pc != NULL) return pc; /* Allocate the client data and save it. */ pc = XCALLOC(MTYPE_BFDD_CONTROL, sizeof(*pc)); pc->pc_pid = pid; TAILQ_INSERT_HEAD(&pcqueue, pc, pc_entry); return pc; } static void pc_free(struct ptm_client *pc) { struct ptm_client_notification *pcn; if (pc == NULL) return; TAILQ_REMOVE(&pcqueue, pc, pc_entry); while (!TAILQ_EMPTY(&pc->pc_pcnqueue)) { pcn = TAILQ_FIRST(&pc->pc_pcnqueue); pcn_free(pcn); } XFREE(MTYPE_BFDD_CONTROL, pc); } static void pc_free_all(void) { struct ptm_client *pc; while (!TAILQ_EMPTY(&pcqueue)) { pc = TAILQ_FIRST(&pcqueue); pc_free(pc); } } static struct ptm_client_notification *pcn_new(struct ptm_client *pc, struct bfd_session *bs) { struct ptm_client_notification *pcn; /* Try to find an existing pcn fist. */ pcn = pcn_lookup(pc, bs); if (pcn != NULL) return pcn; /* Save the client notification data. */ pcn = XCALLOC(MTYPE_BFDD_NOTIFICATION, sizeof(*pcn)); TAILQ_INSERT_HEAD(&pc->pc_pcnqueue, pcn, pcn_entry); pcn->pcn_pc = pc; pcn->pcn_bs = bs; bs->refcount++; return pcn; } static struct ptm_client_notification *pcn_lookup(struct ptm_client *pc, struct bfd_session *bs) { struct ptm_client_notification *pcn; TAILQ_FOREACH (pcn, &pc->pc_pcnqueue, pcn_entry) { if (pcn->pcn_bs != bs) continue; break; } return pcn; } static void pcn_free(struct ptm_client_notification *pcn) { struct ptm_client *pc; struct bfd_session *bs; if (pcn == NULL) return; /* Handle session de-registration. */ bs = pcn->pcn_bs; pcn->pcn_bs = NULL; bs->refcount--; /* Handle ptm_client deregistration. */ pc = pcn->pcn_pc; pcn->pcn_pc = NULL; TAILQ_REMOVE(&pc->pc_pcnqueue, pcn, pcn_entry); XFREE(MTYPE_BFDD_NOTIFICATION, pcn); } frr-7.2.1/bfdd/subdir.am0000644000000000000000000000151513610377563011724 00000000000000# # bfdd # if BFDD noinst_LIBRARIES += bfdd/libbfd.a sbin_PROGRAMS += bfdd/bfdd dist_examples_DATA += bfdd/bfdd.conf.sample vtysh_scan += $(top_srcdir)/bfdd/bfdd_vty.c vtysh_scan += $(top_srcdir)/bfdd/bfdd_cli.c man8 += $(MANBUILD)/frr-bfdd.8 endif bfdd_libbfd_a_SOURCES = \ bfdd/bfd.c \ bfdd/bfdd_northbound.c \ bfdd/bfdd_vty.c \ bfdd/bfdd_cli.c \ bfdd/bfd_packet.c \ bfdd/config.c \ bfdd/control.c \ bfdd/event.c \ bfdd/log.c \ bfdd/ptm_adapter.c \ # end bfdd/bfdd_vty_clippy.c: $(CLIPPY_DEPS) bfdd/bfdd_vty.$(OBJEXT): bfdd/bfdd_vty_clippy.c bfdd/bfdd_cli_clippy.c: $(CLIPPY_DEPS) bfdd/bfdd_cli.$(OBJEXT): bfdd/bfdd_cli_clippy.c noinst_HEADERS += \ bfdd/bfdctl.h \ bfdd/bfd.h \ # end nodist_bfdd_bfdd_SOURCES = \ yang/frr-bfdd.yang.c \ # end bfdd_bfdd_SOURCES = bfdd/bfdd.c bfdd_bfdd_LDADD = bfdd/libbfd.a lib/libfrr.la frr-7.2.1/bgpd/0000755000000000000000000000000013610377563010210 500000000000000frr-7.2.1/bgpd/Makefile0000644000000000000000000000021713610377563011570 00000000000000all: ALWAYS @$(MAKE) -s -C .. bgpd/bgpd %: ALWAYS @$(MAKE) -s -C .. bgpd/$@ Makefile: #nothing ALWAYS: .PHONY: ALWAYS makefiles .SUFFIXES: frr-7.2.1/bgpd/bgp_addpath.c0000644000000000000000000003150713610377563012537 00000000000000/* * Addpath TX ID selection, and related utilities * Copyright (C) 2018 Amazon.com, Inc. or its affiliates * * This program is free software; you can redistribute it and/or modify it * under the terms of the 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; see the file COPYING; if not, write to the Free Software * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA */ #ifdef HAVE_CONFIG_H #include "config.h" #endif #include "bgp_addpath.h" #include "bgp_route.h" static struct bgp_addpath_strategy_names strat_names[BGP_ADDPATH_MAX] = { { .config_name = "addpath-tx-all-paths", .human_name = "All", .human_description = "Advertise all paths via addpath", .type_json_name = "addpathTxAllPaths", .id_json_name = "addpathTxIdAll" }, { .config_name = "addpath-tx-bestpath-per-AS", .human_name = "Best-Per-AS", .human_description = "Advertise bestpath per AS via addpath", .type_json_name = "addpathTxBestpathPerAS", .id_json_name = "addpathTxIdBestPerAS" } }; static struct bgp_addpath_strategy_names unknown_names = { .config_name = "addpath-tx-unknown", .human_name = "Unknown-Addpath-Strategy", .human_description = "Unknown Addpath Strategy", .type_json_name = "addpathTxUnknown", .id_json_name = "addpathTxIdUnknown" }; /* * Returns a structure full of strings associated with an addpath type. Will * never return null. */ struct bgp_addpath_strategy_names * bgp_addpath_names(enum bgp_addpath_strat strat) { if (strat < BGP_ADDPATH_MAX) return &(strat_names[strat]); else return &unknown_names; }; /* * Returns if any peer is transmitting addpaths for a given afi/safi. */ int bgp_addpath_is_addpath_used(struct bgp_addpath_bgp_data *d, afi_t afi, safi_t safi) { return d->total_peercount[afi][safi] > 0; } /* * Initialize the BGP instance level data for addpath. */ void bgp_addpath_init_bgp_data(struct bgp_addpath_bgp_data *d) { safi_t safi; afi_t afi; int i; for (afi = AFI_IP; afi < AFI_MAX; afi++) { for (safi = SAFI_UNICAST; safi < SAFI_MAX; safi++) { for (i = 0; i < BGP_ADDPATH_MAX; i++) { d->id_allocators[afi][safi][i] = NULL; d->peercount[afi][safi][i] = 0; } d->total_peercount[afi][safi] = 0; } } } /* * Free up resources associated with BGP route info structures. */ void bgp_addpath_free_info_data(struct bgp_addpath_info_data *d, struct bgp_addpath_node_data *nd) { int i; for (i = 0; i < BGP_ADDPATH_MAX; i++) { if (d->addpath_tx_id[i] != IDALLOC_INVALID) idalloc_free_to_pool(&nd->free_ids[i], d->addpath_tx_id[i]); } } /* * Return the addpath ID used to send a particular route, to a particular peer, * in a particular AFI/SAFI. */ uint32_t bgp_addpath_id_for_peer(struct peer *peer, afi_t afi, safi_t safi, struct bgp_addpath_info_data *d) { if (peer->addpath_type[afi][safi] < BGP_ADDPATH_MAX) return d->addpath_tx_id[peer->addpath_type[afi][safi]]; else return IDALLOC_INVALID; } /* * Returns true if the path has an assigned addpath ID for any of the addpath * strategies. */ int bgp_addpath_info_has_ids(struct bgp_addpath_info_data *d) { int i; for (i = 0; i < BGP_ADDPATH_MAX; i++) if (d->addpath_tx_id[i] != 0) return 1; return 0; } /* * Releases any ID's associated with the BGP prefix. */ void bgp_addpath_free_node_data(struct bgp_addpath_bgp_data *bd, struct bgp_addpath_node_data *nd, afi_t afi, safi_t safi) { int i; for (i = 0; i < BGP_ADDPATH_MAX; i++) { idalloc_drain_pool(bd->id_allocators[afi][safi][i], &(nd->free_ids[i])); } } /* * Check to see if the addpath strategy requires DMED to be configured to work. */ int bgp_addpath_dmed_required(int strategy) { return strategy == BGP_ADDPATH_BEST_PER_AS; } /* * Return true if this is a path we should advertise due to a * configured addpath-tx knob */ int bgp_addpath_tx_path(enum bgp_addpath_strat strat, struct bgp_path_info *pi) { switch (strat) { case BGP_ADDPATH_NONE: return 0; case BGP_ADDPATH_ALL: return 1; case BGP_ADDPATH_BEST_PER_AS: if (CHECK_FLAG(pi->flags, BGP_PATH_DMED_SELECTED)) return 1; else return 0; default: return 0; } } static void bgp_addpath_flush_type_rn(struct bgp *bgp, afi_t afi, safi_t safi, enum bgp_addpath_strat addpath_type, struct bgp_node *rn) { struct bgp_path_info *pi; idalloc_drain_pool( bgp->tx_addpath.id_allocators[afi][safi][addpath_type], &(rn->tx_addpath.free_ids[addpath_type])); for (pi = bgp_node_get_bgp_path_info(rn); pi; pi = pi->next) { if (pi->tx_addpath.addpath_tx_id[addpath_type] != IDALLOC_INVALID) { idalloc_free( bgp->tx_addpath .id_allocators[afi][safi][addpath_type], pi->tx_addpath.addpath_tx_id[addpath_type]); pi->tx_addpath.addpath_tx_id[addpath_type] = IDALLOC_INVALID; } } } /* * Purge all addpath ID's on a BGP instance associated with the addpath * strategy, and afi/safi combination. This lets us let go of all memory held to * track ID numbers associated with an addpath type not in use. Since * post-bestpath ID processing is skipped for types not used, this is the only * chance to free this data. */ static void bgp_addpath_flush_type(struct bgp *bgp, afi_t afi, safi_t safi, enum bgp_addpath_strat addpath_type) { struct bgp_node *rn, *nrn; for (rn = bgp_table_top(bgp->rib[afi][safi]); rn; rn = bgp_route_next(rn)) { if (safi == SAFI_MPLS_VPN) { struct bgp_table *table; table = bgp_node_get_bgp_table_info(rn); if (!table) continue; for (nrn = bgp_table_top(table); nrn; nrn = bgp_route_next(nrn)) bgp_addpath_flush_type_rn(bgp, afi, safi, addpath_type, nrn); } else { bgp_addpath_flush_type_rn(bgp, afi, safi, addpath_type, rn); } } idalloc_destroy(bgp->tx_addpath.id_allocators[afi][safi][addpath_type]); bgp->tx_addpath.id_allocators[afi][safi][addpath_type] = NULL; } /* * Allocate an Addpath ID for the given type on a path, if necessary. */ static void bgp_addpath_populate_path(struct id_alloc *allocator, struct bgp_path_info *path, enum bgp_addpath_strat addpath_type) { if (bgp_addpath_tx_path(addpath_type, path)) { path->tx_addpath.addpath_tx_id[addpath_type] = idalloc_allocate(allocator); } } /* * Compute addpath ID's on a BGP instance associated with the addpath strategy, * and afi/safi combination. Since we won't waste the time computing addpath IDs * for unused strategies, the first time a peer is configured to use a strategy, * we have to backfill the data. */ static void bgp_addpath_populate_type(struct bgp *bgp, afi_t afi, safi_t safi, enum bgp_addpath_strat addpath_type) { struct bgp_node *rn, *nrn; char buf[200]; struct id_alloc *allocator; snprintf(buf, sizeof(buf), "Addpath ID Allocator %s:%d/%d", bgp_addpath_names(addpath_type)->config_name, (int)afi, (int)safi); buf[sizeof(buf) - 1] = '\0'; zlog_info("Computing addpath IDs for addpath type %s", bgp_addpath_names(addpath_type)->human_name); bgp->tx_addpath.id_allocators[afi][safi][addpath_type] = idalloc_new(buf); idalloc_reserve(bgp->tx_addpath.id_allocators[afi][safi][addpath_type], BGP_ADDPATH_TX_ID_FOR_DEFAULT_ORIGINATE); allocator = bgp->tx_addpath.id_allocators[afi][safi][addpath_type]; for (rn = bgp_table_top(bgp->rib[afi][safi]); rn; rn = bgp_route_next(rn)) { struct bgp_path_info *bi; if (safi == SAFI_MPLS_VPN) { struct bgp_table *table; table = bgp_node_get_bgp_table_info(rn); if (!table) continue; for (nrn = bgp_table_top(table); nrn; nrn = bgp_route_next(nrn)) for (bi = bgp_node_get_bgp_path_info(nrn); bi; bi = bi->next) bgp_addpath_populate_path(allocator, bi, addpath_type); } else { for (bi = bgp_node_get_bgp_path_info(rn); bi; bi = bi->next) bgp_addpath_populate_path(allocator, bi, addpath_type); } } } /* * Handle updates to a peer or group's addpath strategy. If after adjusting * counts a addpath strategy is in use for the first time, or no longer in use, * the IDs for that strategy will be populated or flushed. */ void bgp_addpath_type_changed(struct bgp *bgp) { afi_t afi; safi_t safi; struct listnode *node, *nnode; struct peer *peer; int peer_count[AFI_MAX][SAFI_MAX][BGP_ADDPATH_MAX]; enum bgp_addpath_strat type; FOREACH_AFI_SAFI(afi, safi) { for (type=0; typetx_addpath.total_peercount[afi][safi] = 0; } for (ALL_LIST_ELEMENTS(bgp->peer, node, nnode, peer)) { FOREACH_AFI_SAFI(afi, safi) { type = peer->addpath_type[afi][safi]; if (type != BGP_ADDPATH_NONE) { peer_count[afi][safi][type] += 1; bgp->tx_addpath.total_peercount[afi][safi] += 1; } } } FOREACH_AFI_SAFI(afi, safi) { for (type=0; typetx_addpath.peercount[afi][safi][type]; int new = peer_count[afi][safi][type]; bgp->tx_addpath.peercount[afi][safi][type] = new; if (old == 0 && new != 0) { bgp_addpath_populate_type(bgp, afi, safi, type); } else if (old != 0 && new == 0) { bgp_addpath_flush_type(bgp, afi, safi, type); } } } } /* * Change the addpath type assigned to a peer, or peer group. In addition to * adjusting the counts, peer sessions will be reset as needed to make the * change take effect. */ void bgp_addpath_set_peer_type(struct peer *peer, afi_t afi, safi_t safi, enum bgp_addpath_strat addpath_type) { struct bgp *bgp = peer->bgp; enum bgp_addpath_strat old_type = peer->addpath_type[afi][safi]; struct listnode *node, *nnode; struct peer *tmp_peer; struct peer_group *group; if (addpath_type == old_type) return; if (addpath_type == BGP_ADDPATH_NONE && peer->group && !CHECK_FLAG(peer->sflags, PEER_STATUS_GROUP)) { /* A "no" config on a group member inherits group */ addpath_type = peer->group->conf->addpath_type[afi][safi]; } peer->addpath_type[afi][safi] = addpath_type; bgp_addpath_type_changed(bgp); if (addpath_type != BGP_ADDPATH_NONE) { if (bgp_addpath_dmed_required(addpath_type)) { if (!bgp_flag_check(bgp, BGP_FLAG_DETERMINISTIC_MED)) { zlog_warn( "%s: enabling bgp deterministic-med, this is required for addpath-tx-bestpath-per-AS", peer->host); bgp_flag_set(bgp, BGP_FLAG_DETERMINISTIC_MED); bgp_recalculate_all_bestpaths(bgp); } } } zlog_info("Resetting peer %s%s due to change in addpath config", CHECK_FLAG(peer->sflags, PEER_STATUS_GROUP) ? "group " : "", peer->host); if (CHECK_FLAG(peer->sflags, PEER_STATUS_GROUP)) { group = peer->group; /* group will be null as peer_group_delete calls peer_delete on * group->conf. That peer_delete will eventuallly end up here * if the group was configured to tx addpaths. */ if (group != NULL) { for (ALL_LIST_ELEMENTS(group->peer, node, nnode, tmp_peer)) { if (tmp_peer->addpath_type[afi][safi] == old_type) { bgp_addpath_set_peer_type(tmp_peer, afi, safi, addpath_type); } } } } else { peer_change_action(peer, afi, safi, peer_change_reset); } } /* * Intended to run after bestpath. This function will take TX IDs from paths * that no longer need them, and give them to paths that do. This prevents * best-per-as updates from needing to do a separate withdraw and update just to * swap out which path is sent. */ void bgp_addpath_update_ids(struct bgp *bgp, struct bgp_node *bn, afi_t afi, safi_t safi) { int i; struct bgp_path_info *pi; struct id_alloc_pool **pool_ptr; for (i = 0; i < BGP_ADDPATH_MAX; i++) { struct id_alloc *alloc = bgp->tx_addpath.id_allocators[afi][safi][i]; pool_ptr = &(bn->tx_addpath.free_ids[i]); if (bgp->tx_addpath.peercount[afi][safi][i] == 0) continue; /* Free Unused IDs back to the pool.*/ for (pi = bgp_node_get_bgp_path_info(bn); pi; pi = pi->next) { if (pi->tx_addpath.addpath_tx_id[i] != IDALLOC_INVALID && !bgp_addpath_tx_path(i, pi)) { idalloc_free_to_pool(pool_ptr, pi->tx_addpath.addpath_tx_id[i]); pi->tx_addpath.addpath_tx_id[i] = IDALLOC_INVALID; } } /* Give IDs to paths that need them (pulling from the pool) */ for (pi = bgp_node_get_bgp_path_info(bn); pi; pi = pi->next) { if (pi->tx_addpath.addpath_tx_id[i] == IDALLOC_INVALID && bgp_addpath_tx_path(i, pi)) { pi->tx_addpath.addpath_tx_id[i] = idalloc_allocate_prefer_pool( alloc, pool_ptr); } } /* Free any IDs left in the pool to the main allocator */ idalloc_drain_pool(alloc, pool_ptr); } } frr-7.2.1/bgpd/bgp_addpath.h0000644000000000000000000000450613610377563012543 00000000000000/* * Addpath TX ID selection, and related utilities * Copyright (C) 2018 Amazon.com, Inc. or its affiliates * * This program is free software; you can redistribute it and/or modify it * under the terms of the 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; see the file COPYING; if not, write to the Free Software * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA */ #ifndef _QUAGGA_BGPD_TX_ADDPATH_H #define _QUAGGA_BGPD_TX_ADDPATH_H #include #include #include "bgpd/bgp_addpath_types.h" #include "bgpd/bgp_route.h" #include "bgpd/bgp_table.h" #include "lib/json.h" #define BGP_ADDPATH_TX_ID_FOR_DEFAULT_ORIGINATE 1 void bgp_addpath_init_bgp_data(struct bgp_addpath_bgp_data *d); int bgp_addpath_is_addpath_used(struct bgp_addpath_bgp_data *d, afi_t afi, safi_t safi); void bgp_addpath_free_node_data(struct bgp_addpath_bgp_data *bd, struct bgp_addpath_node_data *nd, afi_t afi, safi_t safi); void bgp_addpath_free_info_data(struct bgp_addpath_info_data *d, struct bgp_addpath_node_data *nd); int bgp_addpath_info_has_ids(struct bgp_addpath_info_data *d); uint32_t bgp_addpath_id_for_peer(struct peer *peer, afi_t afi, safi_t safi, struct bgp_addpath_info_data *d); struct bgp_addpath_strategy_names * bgp_addpath_names(enum bgp_addpath_strat strat); int bgp_addpath_dmed_required(int strategy); /* * Return true if this is a path we should advertise due to a configured * addpath-tx knob */ int bgp_addpath_tx_path(enum bgp_addpath_strat strat, struct bgp_path_info *pi); /* * Change the type of addpath used for a peer. */ void bgp_addpath_set_peer_type(struct peer *peer, afi_t afi, safi_t safi, enum bgp_addpath_strat addpath_type); void bgp_addpath_update_ids(struct bgp *bgp, struct bgp_node *bn, afi_t afi, safi_t safi); void bgp_addpath_type_changed(struct bgp *bgp); #endif frr-7.2.1/bgpd/bgp_addpath_types.h0000644000000000000000000000343013610377563013762 00000000000000/* * Addpath TX ID selection, and related utilities * Copyright (C) 2018 Amazon.com, Inc. or its affiliates * * This program is free software; you can redistribute it and/or modify it * under the terms of the 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; see the file COPYING; if not, write to the Free Software * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA */ #ifndef _QUAGGA_BGPD_TX_ADDPATH_DATA_H #define _QUAGGA_BGPD_TX_ADDPATH_DATA_H #include "lib/id_alloc.h" #include enum bgp_addpath_strat { BGP_ADDPATH_ALL = 0, BGP_ADDPATH_BEST_PER_AS, BGP_ADDPATH_MAX, BGP_ADDPATH_NONE, }; /* TX Addpath structures */ struct bgp_addpath_bgp_data { unsigned int peercount[AFI_MAX][SAFI_MAX][BGP_ADDPATH_MAX]; unsigned int total_peercount[AFI_MAX][SAFI_MAX]; struct id_alloc *id_allocators[AFI_MAX][SAFI_MAX][BGP_ADDPATH_MAX]; }; struct bgp_addpath_node_data { struct id_alloc_pool *free_ids[BGP_ADDPATH_MAX]; }; struct bgp_addpath_info_data { uint32_t addpath_tx_id[BGP_ADDPATH_MAX]; }; struct bgp_addpath_strategy_names { const char *config_name; const char *human_name; /* path detail non-json */ const char *human_description; /* non-json peer descriptions */ const char *type_json_name; /* json peer listings */ const char *id_json_name; /* path json output for tx ID# */ }; #endif frr-7.2.1/bgpd/bgp_advertise.c0000644000000000000000000001417413610377563013121 00000000000000/* BGP advertisement and adjacency * Copyright (C) 1996, 97, 98, 99, 2000 Kunihiro Ishiguro * * This file is part of GNU Zebra. * * GNU Zebra is free software; you can redistribute it and/or modify it * under the terms of the GNU General Public License as published by the * Free Software Foundation; either version 2, or (at your option) any * later version. * * GNU Zebra 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; see the file COPYING; if not, write to the Free Software * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA */ #include #include "command.h" #include "memory.h" #include "prefix.h" #include "hash.h" #include "thread.h" #include "queue.h" #include "filter.h" #include "bgpd/bgpd.h" #include "bgpd/bgp_table.h" #include "bgpd/bgp_route.h" #include "bgpd/bgp_advertise.h" #include "bgpd/bgp_attr.h" #include "bgpd/bgp_debug.h" #include "bgpd/bgp_aspath.h" #include "bgpd/bgp_packet.h" #include "bgpd/bgp_fsm.h" #include "bgpd/bgp_mplsvpn.h" #include "bgpd/bgp_updgrp.h" /* BGP advertise attribute is used for pack same attribute update into one packet. To do that we maintain attribute hash in struct peer. */ struct bgp_advertise_attr *baa_new(void) { return XCALLOC(MTYPE_BGP_ADVERTISE_ATTR, sizeof(struct bgp_advertise_attr)); } static void baa_free(struct bgp_advertise_attr *baa) { XFREE(MTYPE_BGP_ADVERTISE_ATTR, baa); } static void *baa_hash_alloc(void *p) { struct bgp_advertise_attr *ref = (struct bgp_advertise_attr *)p; struct bgp_advertise_attr *baa; baa = baa_new(); baa->attr = ref->attr; return baa; } unsigned int baa_hash_key(const void *p) { const struct bgp_advertise_attr *baa = p; return attrhash_key_make(baa->attr); } bool baa_hash_cmp(const void *p1, const void *p2) { const struct bgp_advertise_attr *baa1 = p1; const struct bgp_advertise_attr *baa2 = p2; return attrhash_cmp(baa1->attr, baa2->attr); } /* BGP update and withdraw information is stored in BGP advertise structure. This structure is referred from BGP adjacency information. */ struct bgp_advertise *bgp_advertise_new(void) { return XCALLOC(MTYPE_BGP_ADVERTISE, sizeof(struct bgp_advertise)); } void bgp_advertise_free(struct bgp_advertise *adv) { if (adv->pathi) /* bgp_advertise bgp_path_info reference */ bgp_path_info_unlock(adv->pathi); XFREE(MTYPE_BGP_ADVERTISE, adv); } void bgp_advertise_add(struct bgp_advertise_attr *baa, struct bgp_advertise *adv) { adv->next = baa->adv; if (baa->adv) baa->adv->prev = adv; baa->adv = adv; } void bgp_advertise_delete(struct bgp_advertise_attr *baa, struct bgp_advertise *adv) { if (adv->next) adv->next->prev = adv->prev; if (adv->prev) adv->prev->next = adv->next; else baa->adv = adv->next; } struct bgp_advertise_attr *bgp_advertise_intern(struct hash *hash, struct attr *attr) { struct bgp_advertise_attr ref; struct bgp_advertise_attr *baa; ref.attr = bgp_attr_intern(attr); baa = (struct bgp_advertise_attr *)hash_get(hash, &ref, baa_hash_alloc); baa->refcnt++; return baa; } void bgp_advertise_unintern(struct hash *hash, struct bgp_advertise_attr *baa) { if (baa->refcnt) baa->refcnt--; if (baa->refcnt && baa->attr) bgp_attr_unintern(&baa->attr); else { if (baa->attr) { hash_release(hash, baa); bgp_attr_unintern(&baa->attr); } baa_free(baa); } } int bgp_adj_out_lookup(struct peer *peer, struct bgp_node *rn, uint32_t addpath_tx_id) { struct bgp_adj_out *adj; struct peer_af *paf; afi_t afi; safi_t safi; int addpath_capable; RB_FOREACH (adj, bgp_adj_out_rb, &rn->adj_out) SUBGRP_FOREACH_PEER (adj->subgroup, paf) if (paf->peer == peer) { afi = SUBGRP_AFI(adj->subgroup); safi = SUBGRP_SAFI(adj->subgroup); addpath_capable = bgp_addpath_encode_tx(peer, afi, safi); /* Match on a specific addpath_tx_id if we are * using addpath for * this * peer and if an addpath_tx_id was specified */ if (addpath_capable && addpath_tx_id && adj->addpath_tx_id != addpath_tx_id) continue; return (adj->adv ? (adj->adv->baa ? 1 : 0) : (adj->attr ? 1 : 0)); } return 0; } void bgp_adj_in_set(struct bgp_node *rn, struct peer *peer, struct attr *attr, uint32_t addpath_id) { struct bgp_adj_in *adj; for (adj = rn->adj_in; adj; adj = adj->next) { if (adj->peer == peer && adj->addpath_rx_id == addpath_id) { if (adj->attr != attr) { bgp_attr_unintern(&adj->attr); adj->attr = bgp_attr_intern(attr); } return; } } adj = XCALLOC(MTYPE_BGP_ADJ_IN, sizeof(struct bgp_adj_in)); adj->peer = peer_lock(peer); /* adj_in peer reference */ adj->attr = bgp_attr_intern(attr); adj->uptime = bgp_clock(); adj->addpath_rx_id = addpath_id; BGP_ADJ_IN_ADD(rn, adj); bgp_lock_node(rn); } void bgp_adj_in_remove(struct bgp_node *rn, struct bgp_adj_in *bai) { bgp_attr_unintern(&bai->attr); BGP_ADJ_IN_DEL(rn, bai); peer_unlock(bai->peer); /* adj_in peer reference */ XFREE(MTYPE_BGP_ADJ_IN, bai); } int bgp_adj_in_unset(struct bgp_node *rn, struct peer *peer, uint32_t addpath_id) { struct bgp_adj_in *adj; struct bgp_adj_in *adj_next; adj = rn->adj_in; if (!adj) return 0; while (adj) { adj_next = adj->next; if (adj->peer == peer && adj->addpath_rx_id == addpath_id) { bgp_adj_in_remove(rn, adj); bgp_unlock_node(rn); } adj = adj_next; } return 1; } void bgp_sync_init(struct peer *peer) { afi_t afi; safi_t safi; struct bgp_synchronize *sync; FOREACH_AFI_SAFI (afi, safi) { sync = XCALLOC(MTYPE_BGP_SYNCHRONISE, sizeof(struct bgp_synchronize)); bgp_adv_fifo_init(&sync->update); bgp_adv_fifo_init(&sync->withdraw); bgp_adv_fifo_init(&sync->withdraw_low); peer->sync[afi][safi] = sync; } } void bgp_sync_delete(struct peer *peer) { afi_t afi; safi_t safi; FOREACH_AFI_SAFI (afi, safi) { XFREE(MTYPE_BGP_SYNCHRONISE, peer->sync[afi][safi]); peer->sync[afi][safi] = NULL; } } frr-7.2.1/bgpd/bgp_advertise.h0000644000000000000000000001202713610377563013121 00000000000000/* BGP advertisement and adjacency * Copyright (C) 1996, 97, 98, 99, 2000 Kunihiro Ishiguro * * This file is part of GNU Zebra. * * GNU Zebra is free software; you can redistribute it and/or modify it * under the terms of the GNU General Public License as published by the * Free Software Foundation; either version 2, or (at your option) any * later version. * * GNU Zebra 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; see the file COPYING; if not, write to the Free Software * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA */ #ifndef _QUAGGA_BGP_ADVERTISE_H #define _QUAGGA_BGP_ADVERTISE_H #include "lib/typesafe.h" PREDECL_DLIST(bgp_adv_fifo) struct update_subgroup; /* BGP advertise attribute. */ struct bgp_advertise_attr { /* Head of advertisement pointer. */ struct bgp_advertise *adv; /* Reference counter. */ unsigned long refcnt; /* Attribute pointer to be announced. */ struct attr *attr; }; struct bgp_advertise { /* FIFO for advertisement. */ struct bgp_adv_fifo_item fifo; /* Link list for same attribute advertise. */ struct bgp_advertise *next; struct bgp_advertise *prev; /* Prefix information. */ struct bgp_node *rn; /* Reference pointer. */ struct bgp_adj_out *adj; /* Advertisement attribute. */ struct bgp_advertise_attr *baa; /* BGP info. */ struct bgp_path_info *pathi; }; DECLARE_DLIST(bgp_adv_fifo, struct bgp_advertise, fifo) /* BGP adjacency out. */ struct bgp_adj_out { /* RB Tree of adjacency entries */ RB_ENTRY(bgp_adj_out) adj_entry; /* Advertised subgroup. */ struct update_subgroup *subgroup; /* Threading that makes the adj part of subgroup's adj queue */ TAILQ_ENTRY(bgp_adj_out) subgrp_adj_train; /* Prefix information. */ struct bgp_node *rn; uint32_t addpath_tx_id; /* Advertised attribute. */ struct attr *attr; /* Advertisement information. */ struct bgp_advertise *adv; }; RB_HEAD(bgp_adj_out_rb, bgp_adj_out); RB_PROTOTYPE(bgp_adj_out_rb, bgp_adj_out, adj_entry, bgp_adj_out_compare); /* BGP adjacency in. */ struct bgp_adj_in { /* Linked list pointer. */ struct bgp_adj_in *next; struct bgp_adj_in *prev; /* Received peer. */ struct peer *peer; /* Received attribute. */ struct attr *attr; /* timestamp (monotime) */ time_t uptime; /* Addpath identifier */ uint32_t addpath_rx_id; }; /* BGP advertisement list. */ struct bgp_synchronize { struct bgp_adv_fifo_head update; struct bgp_adv_fifo_head withdraw; struct bgp_adv_fifo_head withdraw_low; }; /* BGP adjacency linked list. */ #define BGP_PATH_INFO_ADD(N, A, TYPE) \ do { \ (A)->prev = NULL; \ (A)->next = (N)->TYPE; \ if ((N)->TYPE) \ (N)->TYPE->prev = (A); \ (N)->TYPE = (A); \ } while (0) #define BGP_PATH_INFO_DEL(N, A, TYPE) \ do { \ if ((A)->next) \ (A)->next->prev = (A)->prev; \ if ((A)->prev) \ (A)->prev->next = (A)->next; \ else \ (N)->TYPE = (A)->next; \ } while (0) #define BGP_ADJ_IN_ADD(N, A) BGP_PATH_INFO_ADD(N, A, adj_in) #define BGP_ADJ_IN_DEL(N, A) BGP_PATH_INFO_DEL(N, A, adj_in) /* Prototypes. */ extern int bgp_adj_out_lookup(struct peer *, struct bgp_node *, uint32_t); extern void bgp_adj_in_set(struct bgp_node *, struct peer *, struct attr *, uint32_t); extern int bgp_adj_in_unset(struct bgp_node *, struct peer *, uint32_t); extern void bgp_adj_in_remove(struct bgp_node *, struct bgp_adj_in *); extern void bgp_sync_init(struct peer *); extern void bgp_sync_delete(struct peer *); extern unsigned int baa_hash_key(const void *p); extern bool baa_hash_cmp(const void *p1, const void *p2); extern void bgp_advertise_add(struct bgp_advertise_attr *baa, struct bgp_advertise *adv); extern struct bgp_advertise *bgp_advertise_new(void); extern void bgp_advertise_free(struct bgp_advertise *adv); extern struct bgp_advertise_attr *bgp_advertise_intern(struct hash *hash, struct attr *attr); extern struct bgp_advertise_attr *baa_new(void); extern void bgp_advertise_delete(struct bgp_advertise_attr *baa, struct bgp_advertise *adv); extern void bgp_advertise_unintern(struct hash *hash, struct bgp_advertise_attr *baa); #endif /* _QUAGGA_BGP_ADVERTISE_H */ frr-7.2.1/bgpd/bgp_aspath.c0000644000000000000000000014664313610377563012422 00000000000000/* AS path management routines. * Copyright (C) 1996, 97, 98, 99 Kunihiro Ishiguro * Copyright (C) 2005 Sun Microsystems, Inc. * * This file is part of GNU Zebra. * * GNU Zebra is free software; you can redistribute it and/or modify it * under the terms of the GNU General Public License as published by the * Free Software Foundation; either version 2, or (at your option) any * later version. * * GNU Zebra 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; see the file COPYING; if not, write to the Free Software * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA */ #include #include "hash.h" #include "memory.h" #include "vector.h" #include "log.h" #include "stream.h" #include "command.h" #include "jhash.h" #include "queue.h" #include "filter.h" #include "bgpd/bgpd.h" #include "bgpd/bgp_aspath.h" #include "bgpd/bgp_debug.h" #include "bgpd/bgp_attr.h" #include "bgpd/bgp_errors.h" /* Attr. Flags and Attr. Type Code. */ #define AS_HEADER_SIZE 2 /* Now FOUR octets are used for AS value. */ #define AS_VALUE_SIZE sizeof (as_t) /* This is the old one */ #define AS16_VALUE_SIZE sizeof (as16_t) /* Maximum protocol segment length value */ #define AS_SEGMENT_MAX 255 /* The following length and size macros relate specifically to Quagga's * internal representation of AS-Segments, not per se to the on-wire * sizes and lengths. At present (200508) they sort of match, however * the ONLY functions which should now about the on-wire syntax are * aspath_put, assegment_put and assegment_parse. * * aspath_put returns bytes written, the only definitive record of * size of wire-format attribute.. */ /* Calculated size in bytes of ASN segment data to hold N ASN's */ #define ASSEGMENT_DATA_SIZE(N, S) \ ((N) * ((S) ? AS_VALUE_SIZE : AS16_VALUE_SIZE)) /* Calculated size of segment struct to hold N ASN's */ #define ASSEGMENT_SIZE(N,S) (AS_HEADER_SIZE + ASSEGMENT_DATA_SIZE (N,S)) /* AS segment octet length. */ #define ASSEGMENT_LEN(X,S) ASSEGMENT_SIZE((X)->length,S) /* AS_SEQUENCE segments can be packed together */ /* Can the types of X and Y be considered for packing? */ #define ASSEGMENT_TYPES_PACKABLE(X, Y) \ (((X)->type == (Y)->type) && ((X)->type == AS_SEQUENCE)) /* Types and length of X,Y suitable for packing? */ #define ASSEGMENTS_PACKABLE(X, Y) \ (ASSEGMENT_TYPES_PACKABLE((X), (Y)) \ && (((X)->length + (Y)->length) <= AS_SEGMENT_MAX)) /* As segment header - the on-wire representation * NOT the internal representation! */ struct assegment_header { uint8_t type; uint8_t length; }; /* Hash for aspath. This is the top level structure of AS path. */ static struct hash *ashash; /* Stream for SNMP. See aspath_snmp_pathseg */ static struct stream *snmp_stream; /* Callers are required to initialize the memory */ static as_t *assegment_data_new(int num) { return (XMALLOC(MTYPE_AS_SEG_DATA, ASSEGMENT_DATA_SIZE(num, 1))); } static void assegment_data_free(as_t *asdata) { XFREE(MTYPE_AS_SEG_DATA, asdata); } const char *aspath_segment_type_str[] = {"as-invalid", "as-set", "as-sequence", "as-confed-sequence", "as-confed-set"}; /* Get a new segment. Note that 0 is an allowed length, * and will result in a segment with no allocated data segment. * the caller should immediately assign data to the segment, as the segment * otherwise is not generally valid */ static struct assegment *assegment_new(uint8_t type, unsigned short length) { struct assegment *new; new = XCALLOC(MTYPE_AS_SEG, sizeof(struct assegment)); if (length) new->as = assegment_data_new(length); new->length = length; new->type = type; return new; } static void assegment_free(struct assegment *seg) { if (!seg) return; if (seg->as) assegment_data_free(seg->as); memset(seg, 0xfe, sizeof(struct assegment)); XFREE(MTYPE_AS_SEG, seg); return; } /* free entire chain of segments */ static void assegment_free_all(struct assegment *seg) { struct assegment *prev; while (seg) { prev = seg; seg = seg->next; assegment_free(prev); } } /* Duplicate just the given assegment and its data */ static struct assegment *assegment_dup(struct assegment *seg) { struct assegment *new; new = assegment_new(seg->type, seg->length); memcpy(new->as, seg->as, ASSEGMENT_DATA_SIZE(new->length, 1)); return new; } /* Duplicate entire chain of assegments, return the head */ static struct assegment *assegment_dup_all(struct assegment *seg) { struct assegment *new = NULL; struct assegment *head = NULL; while (seg) { if (head) { new->next = assegment_dup(seg); new = new->next; } else head = new = assegment_dup(seg); seg = seg->next; } return head; } /* prepend the as number to given segment, given num of times */ static struct assegment *assegment_prepend_asns(struct assegment *seg, as_t asnum, int num) { as_t *newas; int i; if (!num) return seg; if (num >= AS_SEGMENT_MAX) return seg; /* we don't do huge prepends */ if ((newas = assegment_data_new(seg->length + num)) == NULL) return seg; for (i = 0; i < num; i++) newas[i] = asnum; memcpy(newas + num, seg->as, ASSEGMENT_DATA_SIZE(seg->length, 1)); assegment_data_free(seg->as); seg->as = newas; seg->length += num; return seg; } /* append given array of as numbers to the segment */ static struct assegment *assegment_append_asns(struct assegment *seg, as_t *asnos, int num) { as_t *newas; if (!seg) return seg; newas = XREALLOC(MTYPE_AS_SEG_DATA, seg->as, ASSEGMENT_DATA_SIZE(seg->length + num, 1)); seg->as = newas; memcpy(seg->as + seg->length, asnos, ASSEGMENT_DATA_SIZE(num, 1)); seg->length += num; return seg; } static int int_cmp(const void *p1, const void *p2) { const as_t *as1 = p1; const as_t *as2 = p2; return (*as1 == *as2) ? 0 : ((*as1 > *as2) ? 1 : -1); } /* normalise the segment. * In particular, merge runs of AS_SEQUENCEs into one segment * Internally, we do not care about the wire segment length limit, and * we want each distinct AS_PATHs to have the exact same internal * representation - eg, so that our hashing actually works.. */ static struct assegment *assegment_normalise(struct assegment *head) { struct assegment *seg = head, *pin; struct assegment *tmp; if (!head) return head; while (seg) { pin = seg; /* Sort values SET segments, for determinism in paths to aid * creation of hash values / path comparisons * and because it helps other lesser implementations ;) */ if (seg->type == AS_SET || seg->type == AS_CONFED_SET) { int tail = 0; int i; qsort(seg->as, seg->length, sizeof(as_t), int_cmp); /* weed out dupes */ for (i = 1; i < seg->length; i++) { if (seg->as[tail] == seg->as[i]) continue; tail++; if (tail < i) seg->as[tail] = seg->as[i]; } /* seg->length can be 0.. */ if (seg->length) seg->length = tail + 1; } /* read ahead from the current, pinned segment while the * segments * are packable/mergeable. Append all following packable * segments * to the segment we have pinned and remove these appended * segments. */ while (pin->next && ASSEGMENT_TYPES_PACKABLE(pin, pin->next)) { tmp = pin->next; seg = pin->next; /* append the next sequence to the pinned sequence */ pin = assegment_append_asns(pin, seg->as, seg->length); /* bypass the next sequence */ pin->next = seg->next; /* get rid of the now referenceless segment */ assegment_free(tmp); } seg = pin->next; } return head; } static struct aspath *aspath_new(void) { return XCALLOC(MTYPE_AS_PATH, sizeof(struct aspath)); } /* Free AS path structure. */ void aspath_free(struct aspath *aspath) { if (!aspath) return; if (aspath->segments) assegment_free_all(aspath->segments); XFREE(MTYPE_AS_STR, aspath->str); if (aspath->json) { json_object_free(aspath->json); aspath->json = NULL; } XFREE(MTYPE_AS_PATH, aspath); } /* Unintern aspath from AS path bucket. */ void aspath_unintern(struct aspath **aspath) { struct aspath *ret; struct aspath *asp = *aspath; if (asp->refcnt) asp->refcnt--; if (asp->refcnt == 0) { /* This aspath must exist in aspath hash table. */ ret = hash_release(ashash, asp); assert(ret != NULL); aspath_free(asp); *aspath = NULL; } } /* Return the start or end delimiters for a particular Segment type */ #define AS_SEG_START 0 #define AS_SEG_END 1 static char aspath_delimiter_char(uint8_t type, uint8_t which) { int i; struct { int type; char start; char end; } aspath_delim_char[] = {{AS_SET, '{', '}'}, {AS_CONFED_SET, '[', ']'}, {AS_CONFED_SEQUENCE, '(', ')'}, {0}}; for (i = 0; aspath_delim_char[i].type != 0; i++) { if (aspath_delim_char[i].type == type) { if (which == AS_SEG_START) return aspath_delim_char[i].start; else if (which == AS_SEG_END) return aspath_delim_char[i].end; } } return ' '; } /* countup asns from this segment and index onward */ static int assegment_count_asns(struct assegment *seg, int from) { int count = 0; while (seg) { if (!from) count += seg->length; else { count += (seg->length - from); from = 0; } seg = seg->next; } return count; } unsigned int aspath_count_confeds(struct aspath *aspath) { int count = 0; struct assegment *seg = aspath->segments; while (seg) { if (seg->type == AS_CONFED_SEQUENCE) count += seg->length; else if (seg->type == AS_CONFED_SET) count++; seg = seg->next; } return count; } unsigned int aspath_count_hops(const struct aspath *aspath) { int count = 0; struct assegment *seg = aspath->segments; while (seg) { if (seg->type == AS_SEQUENCE) count += seg->length; else if (seg->type == AS_SET) count++; seg = seg->next; } return count; } /* Estimate size aspath /might/ take if encoded into an * ASPATH attribute. * * This is a quick estimate, not definitive! aspath_put() * may return a different number!! */ unsigned int aspath_size(struct aspath *aspath) { int size = 0; struct assegment *seg = aspath->segments; while (seg) { size += ASSEGMENT_SIZE(seg->length, 1); seg = seg->next; } return size; } /* Return highest public ASN in path */ as_t aspath_highest(struct aspath *aspath) { struct assegment *seg = aspath->segments; as_t highest = 0; unsigned int i; while (seg) { for (i = 0; i < seg->length; i++) if (seg->as[i] > highest && !BGP_AS_IS_PRIVATE(seg->as[i])) highest = seg->as[i]; seg = seg->next; } return highest; } /* Return the left-most ASN in path */ as_t aspath_leftmost(struct aspath *aspath) { struct assegment *seg = aspath->segments; as_t leftmost = 0; if (seg && seg->length && seg->type == AS_SEQUENCE) leftmost = seg->as[0]; return leftmost; } /* Return 1 if there are any 4-byte ASes in the path */ unsigned int aspath_has_as4(struct aspath *aspath) { struct assegment *seg = aspath->segments; unsigned int i; while (seg) { for (i = 0; i < seg->length; i++) if (seg->as[i] > BGP_AS_MAX) return 1; seg = seg->next; } return 0; } /* Convert aspath structure to string expression. */ static void aspath_make_str_count(struct aspath *as, bool make_json) { struct assegment *seg; int str_size; int len = 0; char *str_buf; json_object *jaspath_segments = NULL; json_object *jseg = NULL; json_object *jseg_list = NULL; if (make_json) { as->json = json_object_new_object(); jaspath_segments = json_object_new_array(); } /* Empty aspath. */ if (!as->segments) { if (make_json) { json_object_string_add(as->json, "string", "Local"); json_object_object_add(as->json, "segments", jaspath_segments); json_object_int_add(as->json, "length", 0); } as->str = XMALLOC(MTYPE_AS_STR, 1); as->str[0] = '\0'; as->str_len = 0; return; } seg = as->segments; /* ASN takes 5 to 10 chars plus seperator, see below. * If there is one differing segment type, we need an additional * 2 chars for segment delimiters, and the final '\0'. * Hopefully this is large enough to avoid hitting the realloc * code below for most common sequences. * * This was changed to 10 after the well-known BGP assertion, which * had hit some parts of the Internet in May of 2009. */ #define ASN_STR_LEN (10 + 1) str_size = MAX(assegment_count_asns(seg, 0) * ASN_STR_LEN + 2 + 1, ASPATH_STR_DEFAULT_LEN); str_buf = XMALLOC(MTYPE_AS_STR, str_size); while (seg) { int i; char seperator; /* Check AS type validity. Set seperator for segment */ switch (seg->type) { case AS_SET: case AS_CONFED_SET: seperator = ','; break; case AS_SEQUENCE: case AS_CONFED_SEQUENCE: seperator = ' '; break; default: XFREE(MTYPE_AS_STR, str_buf); as->str = NULL; as->str_len = 0; json_object_free(as->json); as->json = NULL; return; } /* We might need to increase str_buf, particularly if path has * differing segments types, our initial guesstimate above will * have been wrong. Need 10 chars for ASN, a seperator each and * potentially two segment delimiters, plus a space between each * segment and trailing zero. * * This definitely didn't work with the value of 5 bytes and * 32-bit ASNs. */ #define SEGMENT_STR_LEN(X) (((X)->length * ASN_STR_LEN) + 2 + 1 + 1) if ((len + SEGMENT_STR_LEN(seg)) > str_size) { str_size = len + SEGMENT_STR_LEN(seg); str_buf = XREALLOC(MTYPE_AS_STR, str_buf, str_size); } #undef ASN_STR_LEN #undef SEGMENT_STR_LEN if (seg->type != AS_SEQUENCE) len += snprintf( str_buf + len, str_size - len, "%c", aspath_delimiter_char(seg->type, AS_SEG_START)); if (make_json) jseg_list = json_object_new_array(); /* write out the ASNs, with their seperators, bar the last one*/ for (i = 0; i < seg->length; i++) { if (make_json) json_object_array_add( jseg_list, json_object_new_int64(seg->as[i])); len += snprintf(str_buf + len, str_size - len, "%u", seg->as[i]); if (i < (seg->length - 1)) len += snprintf(str_buf + len, str_size - len, "%c", seperator); } if (make_json) { jseg = json_object_new_object(); json_object_string_add( jseg, "type", aspath_segment_type_str[seg->type]); json_object_object_add(jseg, "list", jseg_list); json_object_array_add(jaspath_segments, jseg); } if (seg->type != AS_SEQUENCE) len += snprintf( str_buf + len, str_size - len, "%c", aspath_delimiter_char(seg->type, AS_SEG_END)); if (seg->next) len += snprintf(str_buf + len, str_size - len, " "); seg = seg->next; } assert(len < str_size); str_buf[len] = '\0'; as->str = str_buf; as->str_len = len; if (make_json) { json_object_string_add(as->json, "string", str_buf); json_object_object_add(as->json, "segments", jaspath_segments); json_object_int_add(as->json, "length", aspath_count_hops(as)); } return; } void aspath_str_update(struct aspath *as, bool make_json) { XFREE(MTYPE_AS_STR, as->str); if (as->json) { json_object_free(as->json); as->json = NULL; } aspath_make_str_count(as, make_json); } /* Intern allocated AS path. */ struct aspath *aspath_intern(struct aspath *aspath) { struct aspath *find; /* Assert this AS path structure is not interned and has the string representation built. */ assert(aspath->refcnt == 0); assert(aspath->str); /* Check AS path hash. */ find = hash_get(ashash, aspath, hash_alloc_intern); if (find != aspath) aspath_free(aspath); find->refcnt++; return find; } /* Duplicate aspath structure. Created same aspath structure but reference count and AS path string is cleared. */ struct aspath *aspath_dup(struct aspath *aspath) { unsigned short buflen = aspath->str_len + 1; struct aspath *new; new = XCALLOC(MTYPE_AS_PATH, sizeof(struct aspath)); new->json = NULL; if (aspath->segments) new->segments = assegment_dup_all(aspath->segments); if (!aspath->str) return new; new->str = XMALLOC(MTYPE_AS_STR, buflen); new->str_len = aspath->str_len; /* copy the string data */ if (aspath->str_len > 0) memcpy(new->str, aspath->str, buflen); else new->str[0] = '\0'; return new; } static void *aspath_hash_alloc(void *arg) { const struct aspath *aspath = arg; struct aspath *new; /* Malformed AS path value. */ assert(aspath->str); /* New aspath structure is needed. */ new = XMALLOC(MTYPE_AS_PATH, sizeof(struct aspath)); /* Reuse segments and string representation */ new->refcnt = 0; new->segments = aspath->segments; new->str = aspath->str; new->str_len = aspath->str_len; new->json = aspath->json; return new; } /* parse as-segment byte stream in struct assegment */ static int assegments_parse(struct stream *s, size_t length, struct assegment **result, int use32bit) { struct assegment_header segh; struct assegment *seg, *prev = NULL, *head = NULL; size_t bytes = 0; /* empty aspath (ie iBGP or somesuch) */ if (length == 0) return 0; if (BGP_DEBUG(as4, AS4_SEGMENT)) zlog_debug( "[AS4SEG] Parse aspath segment: got total byte length %lu", (unsigned long)length); /* basic checks */ if ((STREAM_READABLE(s) < length) || (STREAM_READABLE(s) < AS_HEADER_SIZE) || (length % AS16_VALUE_SIZE)) return -1; while (bytes < length) { int i; size_t seg_size; if ((length - bytes) <= AS_HEADER_SIZE) { if (head) assegment_free_all(head); return -1; } /* softly softly, get the header first on its own */ segh.type = stream_getc(s); segh.length = stream_getc(s); seg_size = ASSEGMENT_SIZE(segh.length, use32bit); if (BGP_DEBUG(as4, AS4_SEGMENT)) zlog_debug( "[AS4SEG] Parse aspath segment: got type %d, length %d", segh.type, segh.length); /* check it.. */ if (((bytes + seg_size) > length) /* 1771bis 4.3b: seg length contains one or more */ || (segh.length == 0) /* Paranoia in case someone changes type of segment length. * Shift both values by 0x10 to make the comparison operate * on more, than 8 bits (otherwise it's a warning, bug * #564). */ || ((sizeof segh.length > 1) && (0x10 + segh.length > 0x10 + AS_SEGMENT_MAX))) { if (head) assegment_free_all(head); return -1; } switch (segh.type) { case AS_SEQUENCE: case AS_SET: case AS_CONFED_SEQUENCE: case AS_CONFED_SET: break; default: if (head) assegment_free_all(head); return -1; } /* now its safe to trust lengths */ seg = assegment_new(segh.type, segh.length); if (head) prev->next = seg; else /* it's the first segment */ head = prev = seg; for (i = 0; i < segh.length; i++) seg->as[i] = (use32bit) ? stream_getl(s) : stream_getw(s); bytes += seg_size; if (BGP_DEBUG(as4, AS4_SEGMENT)) zlog_debug( "[AS4SEG] Parse aspath segment: Bytes now: %lu", (unsigned long)bytes); prev = seg; } *result = assegment_normalise(head); return 0; } /* AS path parse function. pnt is a pointer to byte stream and length is length of byte stream. If there is same AS path in the the AS path hash then return it else make new AS path structure. On error NULL is returned. */ struct aspath *aspath_parse(struct stream *s, size_t length, int use32bit) { struct aspath as; struct aspath *find; /* If length is odd it's malformed AS path. */ /* Nit-picking: if (use32bit == 0) it is malformed if odd, * otherwise its malformed when length is larger than 2 and (length-2) * is not dividable by 4. * But... this time we're lazy */ if (length % AS16_VALUE_SIZE) return NULL; memset(&as, 0, sizeof(struct aspath)); if (assegments_parse(s, length, &as.segments, use32bit) < 0) return NULL; /* If already same aspath exist then return it. */ find = hash_get(ashash, &as, aspath_hash_alloc); /* bug! should not happen, let the daemon crash below */ assert(find); /* if the aspath was already hashed free temporary memory. */ if (find->refcnt) { assegment_free_all(as.segments); /* aspath_key_make() always updates the string */ XFREE(MTYPE_AS_STR, as.str); if (as.json) { json_object_free(as.json); as.json = NULL; } } find->refcnt++; return find; } static void assegment_data_put(struct stream *s, as_t *as, int num, int use32bit) { int i; assert(num <= AS_SEGMENT_MAX); for (i = 0; i < num; i++) if (use32bit) stream_putl(s, as[i]); else { if (as[i] <= BGP_AS_MAX) stream_putw(s, as[i]); else stream_putw(s, BGP_AS_TRANS); } } static size_t assegment_header_put(struct stream *s, uint8_t type, int length) { size_t lenp; assert(length <= AS_SEGMENT_MAX); stream_putc(s, type); lenp = stream_get_endp(s); stream_putc(s, length); return lenp; } /* write aspath data to stream */ size_t aspath_put(struct stream *s, struct aspath *as, int use32bit) { struct assegment *seg = as->segments; size_t bytes = 0; if (!seg || seg->length == 0) return 0; if (seg) { /* * Hey, what do we do when we have > STREAM_WRITABLE(s) here? * At the moment, we would write out a partial aspath, and our * peer * will complain and drop the session :-/ * * The general assumption here is that many things tested will * never happen. And, in real live, up to now, they have not. */ while (seg && (ASSEGMENT_LEN(seg, use32bit) <= STREAM_WRITEABLE(s))) { struct assegment *next = seg->next; int written = 0; int asns_packed = 0; size_t lenp; /* Overlength segments have to be split up */ while ((seg->length - written) > AS_SEGMENT_MAX) { assegment_header_put(s, seg->type, AS_SEGMENT_MAX); assegment_data_put(s, (seg->as + written), AS_SEGMENT_MAX, use32bit); written += AS_SEGMENT_MAX; bytes += ASSEGMENT_SIZE(AS_SEGMENT_MAX, use32bit); } /* write the final segment, probably is also the first */ lenp = assegment_header_put(s, seg->type, seg->length - written); assegment_data_put(s, (seg->as + written), seg->length - written, use32bit); /* Sequence-type segments can be 'packed' together * Case of a segment which was overlength and split up * will be missed here, but that doesn't matter. */ while (next && ASSEGMENTS_PACKABLE(seg, next)) { /* NB: We should never normally get here given * we * normalise aspath data when parse them. * However, better * safe than sorry. We potentially could call * assegment_normalise here instead, but it's * cheaper and * easier to do it on the fly here rather than * go through * the segment list twice every time we write * out * aspath's. */ /* Next segment's data can fit in this one */ assegment_data_put(s, next->as, next->length, use32bit); /* update the length of the segment header */ stream_putc_at(s, lenp, seg->length - written + next->length); asns_packed += next->length; next = next->next; } bytes += ASSEGMENT_SIZE( seg->length - written + asns_packed, use32bit); seg = next; } } return bytes; } /* This is for SNMP BGP4PATHATTRASPATHSEGMENT * We have no way to manage the storage, so we use a static stream * wrapper around aspath_put. */ uint8_t *aspath_snmp_pathseg(struct aspath *as, size_t *varlen) { #define SNMP_PATHSEG_MAX 1024 if (!snmp_stream) snmp_stream = stream_new(SNMP_PATHSEG_MAX); else stream_reset(snmp_stream); if (!as) { *varlen = 0; return NULL; } aspath_put(snmp_stream, as, 0); /* use 16 bit for now here */ *varlen = stream_get_endp(snmp_stream); return stream_pnt(snmp_stream); } #define min(A,B) ((A) < (B) ? (A) : (B)) static struct assegment *aspath_aggregate_as_set_add(struct aspath *aspath, struct assegment *asset, as_t as) { int i; /* If this is first AS set member, create new as-set segment. */ if (asset == NULL) { asset = assegment_new(AS_SET, 1); if (!aspath->segments) aspath->segments = asset; else { struct assegment *seg = aspath->segments; while (seg->next) seg = seg->next; seg->next = asset; } asset->type = AS_SET; asset->length = 1; asset->as[0] = as; } else { /* Check this AS value already exists or not. */ for (i = 0; i < asset->length; i++) if (asset->as[i] == as) return asset; asset->length++; asset->as = XREALLOC(MTYPE_AS_SEG_DATA, asset->as, asset->length * AS_VALUE_SIZE); asset->as[asset->length - 1] = as; } return asset; } /* Modify as1 using as2 for aggregation. */ struct aspath *aspath_aggregate(struct aspath *as1, struct aspath *as2) { int i; int minlen = 0; int match = 0; int from; struct assegment *seg1 = as1->segments; struct assegment *seg2 = as2->segments; struct aspath *aspath = NULL; struct assegment *asset = NULL; struct assegment *prevseg = NULL; /* First of all check common leading sequence. */ while (seg1 && seg2) { /* Check segment type. */ if (seg1->type != seg2->type) break; /* Minimum segment length. */ minlen = min(seg1->length, seg2->length); for (match = 0; match < minlen; match++) if (seg1->as[match] != seg2->as[match]) break; if (match) { struct assegment *seg = assegment_new(seg1->type, 0); seg = assegment_append_asns(seg, seg1->as, match); if (!aspath) { aspath = aspath_new(); aspath->segments = seg; } else prevseg->next = seg; prevseg = seg; } if (match != minlen || match != seg1->length || seg1->length != seg2->length) break; /* We are moving on to the next segment to reset match */ else match = 0; seg1 = seg1->next; seg2 = seg2->next; } if (!aspath) aspath = aspath_new(); /* Make as-set using rest of all information. */ from = match; while (seg1) { for (i = from; i < seg1->length; i++) asset = aspath_aggregate_as_set_add(aspath, asset, seg1->as[i]); from = 0; seg1 = seg1->next; } from = match; while (seg2) { for (i = from; i < seg2->length; i++) asset = aspath_aggregate_as_set_add(aspath, asset, seg2->as[i]); from = 0; seg2 = seg2->next; } assegment_normalise(aspath->segments); aspath_str_update(aspath, false); return aspath; } /* When a BGP router receives an UPDATE with an MP_REACH_NLRI attribute, check the leftmost AS number in the AS_PATH attribute is or not the peer's AS number. */ int aspath_firstas_check(struct aspath *aspath, as_t asno) { if ((aspath == NULL) || (aspath->segments == NULL)) return 0; if (aspath->segments && (aspath->segments->type == AS_SEQUENCE) && (aspath->segments->as[0] == asno)) return 1; return 0; } unsigned int aspath_get_first_as(struct aspath *aspath) { if (aspath == NULL || aspath->segments == NULL) return 0; return aspath->segments->as[0]; } unsigned int aspath_get_last_as(struct aspath *aspath) { int i; unsigned int last_as = 0; const struct assegment *seg; if (aspath == NULL || aspath->segments == NULL) return last_as; seg = aspath->segments; while (seg) { if (seg->type == AS_SEQUENCE || seg->type == AS_CONFED_SEQUENCE) for (i = 0; i < seg->length; i++) last_as = seg->as[i]; seg = seg->next; } return last_as; } /* AS path loop check. If aspath contains asno then return >= 1. */ int aspath_loop_check(struct aspath *aspath, as_t asno) { struct assegment *seg; int count = 0; if ((aspath == NULL) || (aspath->segments == NULL)) return 0; seg = aspath->segments; while (seg) { int i; for (i = 0; i < seg->length; i++) if (seg->as[i] == asno) count++; seg = seg->next; } return count; } /* When all of AS path is private AS return 1. */ int aspath_private_as_check(struct aspath *aspath) { struct assegment *seg; if (!(aspath && aspath->segments)) return 0; seg = aspath->segments; while (seg) { int i; for (i = 0; i < seg->length; i++) { if (!BGP_AS_IS_PRIVATE(seg->as[i])) return 0; } seg = seg->next; } return 1; } /* Return True if the entire ASPATH consist of the specified ASN */ int aspath_single_asn_check(struct aspath *aspath, as_t asn) { struct assegment *seg; if (!(aspath && aspath->segments)) return 0; seg = aspath->segments; while (seg) { int i; for (i = 0; i < seg->length; i++) { if (seg->as[i] != asn) return 0; } seg = seg->next; } return 1; } /* Replace all instances of the target ASN with our own ASN */ struct aspath *aspath_replace_specific_asn(struct aspath *aspath, as_t target_asn, as_t our_asn) { struct aspath *new; struct assegment *seg; new = aspath_dup(aspath); seg = new->segments; while (seg) { int i; for (i = 0; i < seg->length; i++) { if (seg->as[i] == target_asn) seg->as[i] = our_asn; } seg = seg->next; } aspath_str_update(new, false); return new; } /* Replace all private ASNs with our own ASN */ struct aspath *aspath_replace_private_asns(struct aspath *aspath, as_t asn, as_t peer_asn) { struct aspath *new; struct assegment *seg; new = aspath_dup(aspath); seg = new->segments; while (seg) { int i; for (i = 0; i < seg->length; i++) { /* Don't replace if public ASN or peer's ASN */ if (BGP_AS_IS_PRIVATE(seg->as[i]) && (seg->as[i] != peer_asn)) seg->as[i] = asn; } seg = seg->next; } aspath_str_update(new, false); return new; } /* Remove all private ASNs */ struct aspath *aspath_remove_private_asns(struct aspath *aspath, as_t peer_asn) { struct aspath *new; struct assegment *seg; struct assegment *new_seg; struct assegment *last_new_seg; int i; int j; int public = 0; new = XCALLOC(MTYPE_AS_PATH, sizeof(struct aspath)); new->json = NULL; new_seg = NULL; last_new_seg = NULL; seg = aspath->segments; while (seg) { public = 0; for (i = 0; i < seg->length; i++) { // ASN is public if (!BGP_AS_IS_PRIVATE(seg->as[i])) { public ++; } } // The entire segment is public so copy it if (public == seg->length) new_seg = assegment_dup(seg); // The segment is a mix of public and private ASNs. Copy as many // spots as // there are public ASNs then come back and fill in only the // public ASNs. else { new_seg = assegment_new(seg->type, public); j = 0; for (i = 0; i < seg->length; i++) { // keep ASN if public or matches peer's ASN if (!BGP_AS_IS_PRIVATE(seg->as[i]) || (seg->as[i] == peer_asn)) { new_seg->as[j] = seg->as[i]; j++; } } } // This is the first segment so set the aspath segments pointer // to this one if (!last_new_seg) new->segments = new_seg; else last_new_seg->next = new_seg; last_new_seg = new_seg; seg = seg->next; } aspath_str_update(new, false); return new; } /* AS path confed check. If aspath contains confed set or sequence then return * 1. */ int aspath_confed_check(struct aspath *aspath) { struct assegment *seg; if (!(aspath && aspath->segments)) return 0; seg = aspath->segments; while (seg) { if (seg->type == AS_CONFED_SET || seg->type == AS_CONFED_SEQUENCE) return 1; seg = seg->next; } return 0; } /* Leftmost AS path segment confed check. If leftmost AS segment is of type AS_CONFED_SEQUENCE or AS_CONFED_SET then return 1. */ int aspath_left_confed_check(struct aspath *aspath) { if (!(aspath && aspath->segments)) return 0; if ((aspath->segments->type == AS_CONFED_SEQUENCE) || (aspath->segments->type == AS_CONFED_SET)) return 1; return 0; } /* Merge as1 to as2. as2 should be uninterned aspath. */ static struct aspath *aspath_merge(struct aspath *as1, struct aspath *as2) { struct assegment *last, *new; if (!as1 || !as2) return NULL; last = new = assegment_dup_all(as1->segments); /* find the last valid segment */ while (last && last->next) last = last->next; if (last) last->next = as2->segments; as2->segments = new; aspath_str_update(as2, false); return as2; } /* Prepend as1 to as2. as2 should be uninterned aspath. */ struct aspath *aspath_prepend(struct aspath *as1, struct aspath *as2) { struct assegment *as1segtail; struct assegment *as2segtail; struct assegment *as2seghead; if (!as1 || !as2) return NULL; /* If as2 is empty, only need to dupe as1's chain onto as2 */ if (as2->segments == NULL) { as2->segments = assegment_dup_all(as1->segments); aspath_str_update(as2, false); return as2; } /* If as1 is empty AS, no prepending to do. */ if (as1->segments == NULL) return as2; /* find the tail as1's segment chain. */ as1segtail = as1->segments; while (as1segtail && as1segtail->next) as1segtail = as1segtail->next; /* Delete any AS_CONFED_SEQUENCE segment from as2. */ if (as1segtail->type == AS_SEQUENCE && as2->segments->type == AS_CONFED_SEQUENCE) as2 = aspath_delete_confed_seq(as2); if (!as2->segments) { as2->segments = assegment_dup_all(as1->segments); aspath_str_update(as2, false); return as2; } /* Compare last segment type of as1 and first segment type of as2. */ if (as1segtail->type != as2->segments->type) return aspath_merge(as1, as2); if (as1segtail->type == AS_SEQUENCE) { /* We have two chains of segments, as1->segments and seg2, * and we have to attach them together, merging the attaching * segments together into one. * * 1. dupe as1->segments onto head of as2 * 2. merge seg2's asns onto last segment of this new chain * 3. attach chain after seg2 */ /* save as2 head */ as2seghead = as2->segments; /* dupe as1 onto as2's head */ as2segtail = as2->segments = assegment_dup_all(as1->segments); /* refind the tail of as2 */ while (as2segtail && as2segtail->next) as2segtail = as2segtail->next; /* merge the old head, seg2, into tail, seg1 */ assegment_append_asns(as2segtail, as2seghead->as, as2seghead->length); /* * bypass the merged seg2, and attach any chain after it * to chain descending from as2's head */ if (as2segtail) as2segtail->next = as2seghead->next; /* as2->segments is now referenceless and useless */ assegment_free(as2seghead); /* we've now prepended as1's segment chain to as2, merging * the inbetween AS_SEQUENCE of seg2 in the process */ aspath_str_update(as2, false); return as2; } else { /* AS_SET merge code is needed at here. */ return aspath_merge(as1, as2); } /* XXX: Ermmm, what if as1 has multiple segments?? */ /* Not reached */ } /* Iterate over AS_PATH segments and wipe all occurrences of the * listed AS numbers. Hence some segments may lose some or even * all data on the way, the operation is implemented as a smarter * version of aspath_dup(), which allocates memory to hold the new * data, not the original. The new AS path is returned. */ struct aspath *aspath_filter_exclude(struct aspath *source, struct aspath *exclude_list) { struct assegment *srcseg, *exclseg, *lastseg; struct aspath *newpath; newpath = aspath_new(); lastseg = NULL; for (srcseg = source->segments; srcseg; srcseg = srcseg->next) { unsigned i, y, newlen = 0, done = 0, skip_as; struct assegment *newseg; /* Find out, how much ASns are we going to pick from this * segment. * We can't perform filtering right inline, because the size of * the new segment isn't known at the moment yet. */ for (i = 0; i < srcseg->length; i++) { skip_as = 0; for (exclseg = exclude_list->segments; exclseg && !skip_as; exclseg = exclseg->next) for (y = 0; y < exclseg->length; y++) if (srcseg->as[i] == exclseg->as[y]) { skip_as = 1; // There's no sense in testing // the rest of exclusion list, // bail out. break; } if (!skip_as) newlen++; } /* newlen is now the number of ASns to copy */ if (!newlen) continue; /* Actual copying. Allocate memory and iterate once more, * performing filtering. */ newseg = assegment_new(srcseg->type, newlen); for (i = 0; i < srcseg->length; i++) { skip_as = 0; for (exclseg = exclude_list->segments; exclseg && !skip_as; exclseg = exclseg->next) for (y = 0; y < exclseg->length; y++) if (srcseg->as[i] == exclseg->as[y]) { skip_as = 1; break; } if (skip_as) continue; newseg->as[done++] = srcseg->as[i]; } /* At his point newlen must be equal to done, and both must be * positive. Append * the filtered segment to the gross result. */ if (!lastseg) newpath->segments = newseg; else lastseg->next = newseg; lastseg = newseg; } aspath_str_update(newpath, false); /* We are happy returning even an empty AS_PATH, because the * administrator * might expect this very behaviour. There's a mean to avoid this, if * necessary, * by having a match rule against certain AS_PATH regexps in the * route-map index. */ aspath_free(source); return newpath; } /* Add specified AS to the leftmost of aspath. */ static struct aspath *aspath_add_asns(struct aspath *aspath, as_t asno, uint8_t type, unsigned num) { struct assegment *assegment = aspath->segments; unsigned i; if (assegment && assegment->type == type) { /* extend existing segment */ aspath->segments = assegment_prepend_asns(aspath->segments, asno, num); } else { /* prepend with new segment */ struct assegment *newsegment = assegment_new(type, num); for (i = 0; i < num; i++) newsegment->as[i] = asno; /* insert potentially replacing empty segment */ if (assegment && assegment->length == 0) { newsegment->next = assegment->next; assegment_free(assegment); } else newsegment->next = assegment; aspath->segments = newsegment; } aspath_str_update(aspath, false); return aspath; } /* Add specified AS to the leftmost of aspath num times. */ struct aspath *aspath_add_seq_n(struct aspath *aspath, as_t asno, unsigned num) { return aspath_add_asns(aspath, asno, AS_SEQUENCE, num); } /* Add specified AS to the leftmost of aspath. */ struct aspath *aspath_add_seq(struct aspath *aspath, as_t asno) { return aspath_add_asns(aspath, asno, AS_SEQUENCE, 1); } /* Compare leftmost AS value for MED check. If as1's leftmost AS and as2's leftmost AS is same return 1. */ int aspath_cmp_left(const struct aspath *aspath1, const struct aspath *aspath2) { const struct assegment *seg1; const struct assegment *seg2; if (!(aspath1 && aspath2)) return 0; seg1 = aspath1->segments; seg2 = aspath2->segments; /* If both paths are originated in this AS then we do want to compare * MED */ if (!seg1 && !seg2) return 1; /* find first non-confed segments for each */ while (seg1 && ((seg1->type == AS_CONFED_SEQUENCE) || (seg1->type == AS_CONFED_SET))) seg1 = seg1->next; while (seg2 && ((seg2->type == AS_CONFED_SEQUENCE) || (seg2->type == AS_CONFED_SET))) seg2 = seg2->next; /* Check as1's */ if (!(seg1 && seg2 && (seg1->type == AS_SEQUENCE) && (seg2->type == AS_SEQUENCE))) return 0; if (seg1->as[0] == seg2->as[0]) return 1; return 0; } /* Truncate an aspath after a number of hops, and put the hops remaining * at the front of another aspath. Needed for AS4 compat. * * Returned aspath is a /new/ aspath, which should either by free'd or * interned by the caller, as desired. */ struct aspath *aspath_reconcile_as4(struct aspath *aspath, struct aspath *as4path) { struct assegment *seg, *newseg, *prevseg = NULL; struct aspath *newpath = NULL, *mergedpath; int hops, cpasns = 0; if (!aspath || !as4path) return NULL; seg = aspath->segments; /* CONFEDs should get reconciled too.. */ hops = (aspath_count_hops(aspath) + aspath_count_confeds(aspath)) - aspath_count_hops(as4path); if (hops < 0) { if (BGP_DEBUG(as4, AS4)) flog_warn( EC_BGP_ASPATH_FEWER_HOPS, "[AS4] Fewer hops in AS_PATH than NEW_AS_PATH"); /* Something's gone wrong. The RFC says we should now ignore * AS4_PATH, * which is daft behaviour - it contains vital loop-detection * information which must have been removed from AS_PATH. */ hops = aspath_count_hops(aspath); } if (!hops) { newpath = aspath_dup(as4path); aspath_str_update(newpath, false); return newpath; } if (BGP_DEBUG(as4, AS4)) zlog_debug( "[AS4] got AS_PATH %s and AS4_PATH %s synthesizing now", aspath->str, as4path->str); while (seg && hops > 0) { switch (seg->type) { case AS_SET: case AS_CONFED_SET: hops--; cpasns = seg->length; break; case AS_CONFED_SEQUENCE: /* Should never split a confed-sequence, if hop-count * suggests we must then something's gone wrong * somewhere. * * Most important goal is to preserve AS_PATHs prime * function * as loop-detector, so we fudge the numbers so that the * entire * confed-sequence is merged in. */ if (hops < seg->length) { if (BGP_DEBUG(as4, AS4)) zlog_debug( "[AS4] AS4PATHmangle: AS_CONFED_SEQUENCE falls" " across 2/4 ASN boundary somewhere, broken.."); hops = seg->length; } /* fallthru */ case AS_SEQUENCE: cpasns = MIN(seg->length, hops); hops -= seg->length; } assert(cpasns <= seg->length); newseg = assegment_new(seg->type, 0); newseg = assegment_append_asns(newseg, seg->as, cpasns); if (!newpath) { newpath = aspath_new(); newpath->segments = newseg; } else prevseg->next = newseg; prevseg = newseg; seg = seg->next; } /* We may be able to join some segments here, and we must * do this because... we want normalised aspaths in out hash * and we do not want to stumble in aspath_put. */ mergedpath = aspath_merge(newpath, aspath_dup(as4path)); aspath_free(newpath); mergedpath->segments = assegment_normalise(mergedpath->segments); aspath_str_update(mergedpath, false); if (BGP_DEBUG(as4, AS4)) zlog_debug("[AS4] result of synthesizing is %s", mergedpath->str); return mergedpath; } /* Compare leftmost AS value for MED check. If as1's leftmost AS and as2's leftmost AS is same return 1. (confederation as-path only). */ bool aspath_cmp_left_confed(const struct aspath *aspath1, const struct aspath *aspath2) { if (!(aspath1 && aspath2)) return false; if (!(aspath1->segments && aspath2->segments)) return false; if ((aspath1->segments->type != AS_CONFED_SEQUENCE) || (aspath2->segments->type != AS_CONFED_SEQUENCE)) return false; if (aspath1->segments->as[0] == aspath2->segments->as[0]) return true; return false; } /* Delete all AS_CONFED_SEQUENCE/SET segments from aspath. * RFC 5065 section 4.1.c.1 * * 1) if any path segments of the AS_PATH are of the type * AS_CONFED_SEQUENCE or AS_CONFED_SET, those segments MUST be * removed from the AS_PATH attribute, leaving the sanitized * AS_PATH attribute to be operated on by steps 2, 3 or 4. */ struct aspath *aspath_delete_confed_seq(struct aspath *aspath) { struct assegment *seg, *prev, *next; char removed_confed_segment; if (!(aspath && aspath->segments)) return aspath; seg = aspath->segments; removed_confed_segment = 0; next = NULL; prev = NULL; while (seg) { next = seg->next; if (seg->type == AS_CONFED_SEQUENCE || seg->type == AS_CONFED_SET) { /* This is the first segment in the aspath */ if (aspath->segments == seg) aspath->segments = seg->next; else prev->next = seg->next; assegment_free(seg); removed_confed_segment = 1; } else prev = seg; seg = next; } if (removed_confed_segment) aspath_str_update(aspath, false); return aspath; } /* Add new AS number to the leftmost part of the aspath as AS_CONFED_SEQUENCE. */ struct aspath *aspath_add_confed_seq(struct aspath *aspath, as_t asno) { return aspath_add_asns(aspath, asno, AS_CONFED_SEQUENCE, 1); } /* Add new as value to as path structure. */ static void aspath_as_add(struct aspath *as, as_t asno) { struct assegment *seg = as->segments; if (!seg) return; /* Last segment search procedure. */ while (seg->next) seg = seg->next; assegment_append_asns(seg, &asno, 1); } /* Add new as segment to the as path. */ static void aspath_segment_add(struct aspath *as, int type) { struct assegment *seg = as->segments; struct assegment *new = assegment_new(type, 0); if (seg) { while (seg->next) seg = seg->next; seg->next = new; } else as->segments = new; } struct aspath *aspath_empty(void) { return aspath_parse(NULL, 0, 1); /* 32Bit ;-) */ } struct aspath *aspath_empty_get(void) { struct aspath *aspath; aspath = aspath_new(); aspath_make_str_count(aspath, false); return aspath; } unsigned long aspath_count(void) { return ashash->count; } /* Theoretically, one as path can have: One BGP packet size should be less than 4096. One BGP attribute size should be less than 4096 - BGP header size. One BGP aspath size should be less than 4096 - BGP header size - BGP mandantry attribute size. */ /* AS path string lexical token enum. */ enum as_token { as_token_asval, as_token_set_start, as_token_set_end, as_token_confed_seq_start, as_token_confed_seq_end, as_token_confed_set_start, as_token_confed_set_end, as_token_unknown }; /* Return next token and point for string parse. */ static const char *aspath_gettoken(const char *buf, enum as_token *token, unsigned long *asno) { const char *p = buf; /* Skip seperators (space for sequences, ',' for sets). */ while (isspace((unsigned char)*p) || *p == ',') p++; /* Check the end of the string and type specify characters (e.g. {}()). */ switch (*p) { case '\0': return NULL; case '{': *token = as_token_set_start; p++; return p; case '}': *token = as_token_set_end; p++; return p; case '(': *token = as_token_confed_seq_start; p++; return p; case ')': *token = as_token_confed_seq_end; p++; return p; case '[': *token = as_token_confed_set_start; p++; return p; case ']': *token = as_token_confed_set_end; p++; return p; } /* Check actual AS value. */ if (isdigit((unsigned char)*p)) { as_t asval; *token = as_token_asval; asval = (*p - '0'); p++; while (isdigit((unsigned char)*p)) { asval *= 10; asval += (*p - '0'); p++; } *asno = asval; return p; } /* There is no match then return unknown token. */ *token = as_token_unknown; p++; return p; } struct aspath *aspath_str2aspath(const char *str) { enum as_token token = as_token_unknown; unsigned short as_type; unsigned long asno = 0; struct aspath *aspath; int needtype; aspath = aspath_new(); /* We start default type as AS_SEQUENCE. */ as_type = AS_SEQUENCE; needtype = 1; while ((str = aspath_gettoken(str, &token, &asno)) != NULL) { switch (token) { case as_token_asval: if (needtype) { aspath_segment_add(aspath, as_type); needtype = 0; } aspath_as_add(aspath, asno); break; case as_token_set_start: as_type = AS_SET; aspath_segment_add(aspath, as_type); needtype = 0; break; case as_token_set_end: as_type = AS_SEQUENCE; needtype = 1; break; case as_token_confed_seq_start: as_type = AS_CONFED_SEQUENCE; aspath_segment_add(aspath, as_type); needtype = 0; break; case as_token_confed_seq_end: as_type = AS_SEQUENCE; needtype = 1; break; case as_token_confed_set_start: as_type = AS_CONFED_SET; aspath_segment_add(aspath, as_type); needtype = 0; break; case as_token_confed_set_end: as_type = AS_SEQUENCE; needtype = 1; break; case as_token_unknown: default: aspath_free(aspath); return NULL; } } aspath_make_str_count(aspath, false); return aspath; } /* Make hash value by raw aspath data. */ unsigned int aspath_key_make(const void *p) { const struct aspath *aspath = p; unsigned int key = 0; if (!aspath->str) aspath_str_update((struct aspath *)aspath, false); key = jhash(aspath->str, aspath->str_len, 2334325); return key; } /* If two aspath have same value then return 1 else return 0 */ bool aspath_cmp(const void *arg1, const void *arg2) { const struct assegment *seg1 = ((const struct aspath *)arg1)->segments; const struct assegment *seg2 = ((const struct aspath *)arg2)->segments; while (seg1 || seg2) { int i; if ((!seg1 && seg2) || (seg1 && !seg2)) return false; if (seg1->type != seg2->type) return false; if (seg1->length != seg2->length) return false; for (i = 0; i < seg1->length; i++) if (seg1->as[i] != seg2->as[i]) return false; seg1 = seg1->next; seg2 = seg2->next; } return true; } /* AS path hash initialize. */ void aspath_init(void) { ashash = hash_create_size(32768, aspath_key_make, aspath_cmp, "BGP AS Path"); } void aspath_finish(void) { hash_clean(ashash, (void (*)(void *))aspath_free); hash_free(ashash); ashash = NULL; if (snmp_stream) stream_free(snmp_stream); } /* return and as path value */ const char *aspath_print(struct aspath *as) { return (as ? as->str : NULL); } /* Printing functions */ /* Feed the AS_PATH to the vty; the suffix string follows it only in case * AS_PATH wasn't empty. */ void aspath_print_vty(struct vty *vty, const char *format, struct aspath *as, const char *suffix) { assert(format); vty_out(vty, format, as->str); if (as->str_len && strlen(suffix)) vty_out(vty, "%s", suffix); } static void aspath_show_all_iterator(struct hash_bucket *bucket, struct vty *vty) { struct aspath *as; as = (struct aspath *)bucket->data; vty_out(vty, "[%p:%u] (%ld) ", (void *)bucket, bucket->key, as->refcnt); vty_out(vty, "%s\n", as->str); } /* Print all aspath and hash information. This function is used from `show [ip] bgp paths' command. */ void aspath_print_all_vty(struct vty *vty) { hash_iterate(ashash, (void (*)(struct hash_bucket *, void *))aspath_show_all_iterator, vty); } static struct aspath *bgp_aggr_aspath_lookup(struct bgp_aggregate *aggregate, struct aspath *aspath) { return hash_lookup(aggregate->aspath_hash, aspath); } static void *bgp_aggr_aspath_hash_alloc(void *p) { struct aspath *ref = (struct aspath *)p; struct aspath *aspath = NULL; aspath = aspath_dup(ref); return aspath; } static void bgp_aggr_aspath_prepare(struct hash_backet *hb, void *arg) { struct aspath *asmerge = NULL; struct aspath *hb_aspath = hb->data; struct aspath **aggr_aspath = arg; if (*aggr_aspath) { asmerge = aspath_aggregate(*aggr_aspath, hb_aspath); aspath_free(*aggr_aspath); *aggr_aspath = asmerge; } else *aggr_aspath = aspath_dup(hb_aspath); } void bgp_aggr_aspath_remove(void *arg) { struct aspath *aspath = arg; aspath_free(aspath); } void bgp_compute_aggregate_aspath(struct bgp_aggregate *aggregate, struct aspath *aspath) { struct aspath *aggr_aspath = NULL; if ((aggregate == NULL) || (aspath == NULL)) return; /* Create hash if not already created. */ if (aggregate->aspath_hash == NULL) aggregate->aspath_hash = hash_create( aspath_key_make, aspath_cmp, "BGP Aggregator as-path hash"); aggr_aspath = bgp_aggr_aspath_lookup(aggregate, aspath); if (aggr_aspath == NULL) { /* Insert as-path into hash. */ aggr_aspath = hash_get(aggregate->aspath_hash, aspath, bgp_aggr_aspath_hash_alloc); /* Compute aggregate's as-path. */ hash_iterate(aggregate->aspath_hash, bgp_aggr_aspath_prepare, &aggregate->aspath); } /* Increment refernce counter. */ aggr_aspath->refcnt++; } void bgp_remove_aspath_from_aggregate(struct bgp_aggregate *aggregate, struct aspath *aspath) { struct aspath *aggr_aspath = NULL; struct aspath *ret_aspath = NULL; if ((aggregate == NULL) || (aspath == NULL)) return; if (aggregate->aspath_hash == NULL) return; /* Look-up the aspath in the hash. */ aggr_aspath = bgp_aggr_aspath_lookup(aggregate, aspath); if (aggr_aspath) { aggr_aspath->refcnt--; if (aggr_aspath->refcnt == 0) { ret_aspath = hash_release(aggregate->aspath_hash, aggr_aspath); aspath_free(ret_aspath); /* Remove aggregate's old as-path. */ aspath_free(aggregate->aspath); aggregate->aspath = NULL; /* Compute aggregate's as-path. */ hash_iterate(aggregate->aspath_hash, bgp_aggr_aspath_prepare, &aggregate->aspath); } } } frr-7.2.1/bgpd/bgp_aspath.h0000644000000000000000000001300013610377563012403 00000000000000/* AS path related definitions. * Copyright (C) 1997, 98, 99 Kunihiro Ishiguro * * This file is part of GNU Zebra. * * GNU Zebra is free software; you can redistribute it and/or modify it * under the terms of the GNU General Public License as published by the * Free Software Foundation; either version 2, or (at your option) any * later version. * * GNU Zebra 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; see the file COPYING; if not, write to the Free Software * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA */ #ifndef _QUAGGA_BGP_ASPATH_H #define _QUAGGA_BGP_ASPATH_H #include "lib/json.h" #include "bgpd/bgp_route.h" /* AS path segment type. */ #define AS_SET 1 #define AS_SEQUENCE 2 #define AS_CONFED_SEQUENCE 3 #define AS_CONFED_SET 4 /* Private AS range defined in RFC2270. */ #define BGP_PRIVATE_AS_MIN 64512U #define BGP_PRIVATE_AS_MAX 65535U /* Private 4 byte AS range defined in RFC6996. */ #define BGP_PRIVATE_AS4_MIN 4200000000U #define BGP_PRIVATE_AS4_MAX 4294967294U /* we leave BGP_AS_MAX as the 16bit AS MAX number. */ #define BGP_AS_MAX 65535U #define BGP_AS4_MAX 4294967295U /* Transition 16Bit AS as defined by IANA */ #define BGP_AS_TRANS 23456U #define BGP_AS_IS_PRIVATE(ASN) \ (((ASN) >= BGP_PRIVATE_AS_MIN && (ASN) <= BGP_PRIVATE_AS_MAX) \ || ((ASN) >= BGP_PRIVATE_AS4_MIN && (ASN) <= BGP_PRIVATE_AS4_MAX)) /* AS_PATH segment data in abstracted form, no limit is placed on length */ struct assegment { struct assegment *next; as_t *as; unsigned short length; uint8_t type; }; /* AS path may be include some AsSegments. */ struct aspath { /* Reference count to this aspath. */ unsigned long refcnt; /* segment data */ struct assegment *segments; /* AS path as a json object */ json_object *json; /* String expression of AS path. This string is used by vty output and AS path regular expression match. */ char *str; unsigned short str_len; }; #define ASPATH_STR_DEFAULT_LEN 32 /* Prototypes. */ extern void aspath_init(void); extern void aspath_finish(void); extern struct aspath *aspath_parse(struct stream *, size_t, int); extern struct aspath *aspath_dup(struct aspath *); extern struct aspath *aspath_aggregate(struct aspath *, struct aspath *); extern struct aspath *aspath_prepend(struct aspath *, struct aspath *); extern struct aspath *aspath_filter_exclude(struct aspath *, struct aspath *); extern struct aspath *aspath_add_seq_n(struct aspath *, as_t, unsigned); extern struct aspath *aspath_add_seq(struct aspath *, as_t); extern struct aspath *aspath_add_confed_seq(struct aspath *, as_t); extern bool aspath_cmp(const void *as1, const void *as2); extern int aspath_cmp_left(const struct aspath *, const struct aspath *); extern bool aspath_cmp_left_confed(const struct aspath *as1, const struct aspath *as2xs); extern struct aspath *aspath_delete_confed_seq(struct aspath *); extern struct aspath *aspath_empty(void); extern struct aspath *aspath_empty_get(void); extern struct aspath *aspath_str2aspath(const char *); extern void aspath_str_update(struct aspath *as, bool make_json); extern void aspath_free(struct aspath *); extern struct aspath *aspath_intern(struct aspath *); extern void aspath_unintern(struct aspath **); extern const char *aspath_print(struct aspath *); extern void aspath_print_vty(struct vty *, const char *, struct aspath *, const char *); extern void aspath_print_all_vty(struct vty *); extern unsigned int aspath_key_make(const void *); extern unsigned int aspath_get_first_as(struct aspath *); extern unsigned int aspath_get_last_as(struct aspath *); extern int aspath_loop_check(struct aspath *, as_t); extern int aspath_private_as_check(struct aspath *); extern int aspath_single_asn_check(struct aspath *, as_t asn); extern struct aspath *aspath_replace_specific_asn(struct aspath *aspath, as_t target_asn, as_t our_asn); extern struct aspath *aspath_replace_private_asns(struct aspath *aspath, as_t asn, as_t peer_asn); extern struct aspath *aspath_remove_private_asns(struct aspath *aspath, as_t peer_asn); extern int aspath_firstas_check(struct aspath *, as_t); extern int aspath_confed_check(struct aspath *); extern int aspath_left_confed_check(struct aspath *); extern unsigned long aspath_count(void); extern unsigned int aspath_count_hops(const struct aspath *); extern unsigned int aspath_count_confeds(struct aspath *); extern unsigned int aspath_size(struct aspath *); extern as_t aspath_highest(struct aspath *); extern as_t aspath_leftmost(struct aspath *); extern size_t aspath_put(struct stream *, struct aspath *, int); extern struct aspath *aspath_reconcile_as4(struct aspath *, struct aspath *); extern unsigned int aspath_has_as4(struct aspath *); /* For SNMP BGP4PATHATTRASPATHSEGMENT, might be useful for debug */ extern uint8_t *aspath_snmp_pathseg(struct aspath *, size_t *); extern void bgp_compute_aggregate_aspath(struct bgp_aggregate *aggregate, struct aspath *aspath); extern void bgp_remove_aspath_from_aggregate(struct bgp_aggregate *aggregate, struct aspath *aspath); extern void bgp_aggr_aspath_remove(void *arg); #endif /* _QUAGGA_BGP_ASPATH_H */ frr-7.2.1/bgpd/bgp_attr.c0000644000000000000000000032426313610377563012110 00000000000000/* BGP attributes management routines. * Copyright (C) 1996, 97, 98, 1999 Kunihiro Ishiguro * * This file is part of GNU Zebra. * * GNU Zebra is free software; you can redistribute it and/or modify it * under the terms of the GNU General Public License as published by the * Free Software Foundation; either version 2, or (at your option) any * later version. * * GNU Zebra 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; see the file COPYING; if not, write to the Free Software * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA */ #include #include "linklist.h" #include "prefix.h" #include "memory.h" #include "vector.h" #include "stream.h" #include "log.h" #include "hash.h" #include "jhash.h" #include "queue.h" #include "table.h" #include "filter.h" #include "command.h" #include "bgpd/bgpd.h" #include "bgpd/bgp_attr.h" #include "bgpd/bgp_route.h" #include "bgpd/bgp_aspath.h" #include "bgpd/bgp_community.h" #include "bgpd/bgp_debug.h" #include "bgpd/bgp_errors.h" #include "bgpd/bgp_label.h" #include "bgpd/bgp_packet.h" #include "bgpd/bgp_ecommunity.h" #include "bgpd/bgp_lcommunity.h" #include "bgpd/bgp_updgrp.h" #include "bgpd/bgp_encap_types.h" #if ENABLE_BGP_VNC #include "bgpd/rfapi/bgp_rfapi_cfg.h" #include "bgp_encap_types.h" #include "bgp_vnc_types.h" #endif #include "bgp_evpn.h" #include "bgp_flowspec_private.h" #include "bgp_mac.h" /* Attribute strings for logging. */ static const struct message attr_str[] = { {BGP_ATTR_ORIGIN, "ORIGIN"}, {BGP_ATTR_AS_PATH, "AS_PATH"}, {BGP_ATTR_NEXT_HOP, "NEXT_HOP"}, {BGP_ATTR_MULTI_EXIT_DISC, "MULTI_EXIT_DISC"}, {BGP_ATTR_LOCAL_PREF, "LOCAL_PREF"}, {BGP_ATTR_ATOMIC_AGGREGATE, "ATOMIC_AGGREGATE"}, {BGP_ATTR_AGGREGATOR, "AGGREGATOR"}, {BGP_ATTR_COMMUNITIES, "COMMUNITY"}, {BGP_ATTR_ORIGINATOR_ID, "ORIGINATOR_ID"}, {BGP_ATTR_CLUSTER_LIST, "CLUSTER_LIST"}, {BGP_ATTR_DPA, "DPA"}, {BGP_ATTR_ADVERTISER, "ADVERTISER"}, {BGP_ATTR_RCID_PATH, "RCID_PATH"}, {BGP_ATTR_MP_REACH_NLRI, "MP_REACH_NLRI"}, {BGP_ATTR_MP_UNREACH_NLRI, "MP_UNREACH_NLRI"}, {BGP_ATTR_EXT_COMMUNITIES, "EXT_COMMUNITIES"}, {BGP_ATTR_AS4_PATH, "AS4_PATH"}, {BGP_ATTR_AS4_AGGREGATOR, "AS4_AGGREGATOR"}, {BGP_ATTR_AS_PATHLIMIT, "AS_PATHLIMIT"}, {BGP_ATTR_PMSI_TUNNEL, "PMSI_TUNNEL_ATTRIBUTE"}, {BGP_ATTR_ENCAP, "ENCAP"}, #if ENABLE_BGP_VNC_ATTR {BGP_ATTR_VNC, "VNC"}, #endif {BGP_ATTR_LARGE_COMMUNITIES, "LARGE_COMMUNITY"}, {BGP_ATTR_PREFIX_SID, "PREFIX_SID"}, {0}}; static const struct message attr_flag_str[] = { {BGP_ATTR_FLAG_OPTIONAL, "Optional"}, {BGP_ATTR_FLAG_TRANS, "Transitive"}, {BGP_ATTR_FLAG_PARTIAL, "Partial"}, /* bgp_attr_flags_diagnose() relies on this bit being last in this list */ {BGP_ATTR_FLAG_EXTLEN, "Extended Length"}, {0}}; static struct hash *cluster_hash; static void *cluster_hash_alloc(void *p) { const struct cluster_list *val = (const struct cluster_list *)p; struct cluster_list *cluster; cluster = XMALLOC(MTYPE_CLUSTER, sizeof(struct cluster_list)); cluster->length = val->length; if (cluster->length) { cluster->list = XMALLOC(MTYPE_CLUSTER_VAL, val->length); memcpy(cluster->list, val->list, val->length); } else cluster->list = NULL; cluster->refcnt = 0; return cluster; } /* Cluster list related functions. */ static struct cluster_list *cluster_parse(struct in_addr *pnt, int length) { struct cluster_list tmp; struct cluster_list *cluster; tmp.length = length; tmp.list = pnt; cluster = hash_get(cluster_hash, &tmp, cluster_hash_alloc); cluster->refcnt++; return cluster; } int cluster_loop_check(struct cluster_list *cluster, struct in_addr originator) { int i; for (i = 0; i < cluster->length / 4; i++) if (cluster->list[i].s_addr == originator.s_addr) return 1; return 0; } static unsigned int cluster_hash_key_make(const void *p) { const struct cluster_list *cluster = p; return jhash(cluster->list, cluster->length, 0); } static bool cluster_hash_cmp(const void *p1, const void *p2) { const struct cluster_list *cluster1 = p1; const struct cluster_list *cluster2 = p2; return (cluster1->length == cluster2->length && (cluster1->list == cluster2->list || memcmp(cluster1->list, cluster2->list, cluster1->length) == 0)); } static void cluster_free(struct cluster_list *cluster) { XFREE(MTYPE_CLUSTER_VAL, cluster->list); XFREE(MTYPE_CLUSTER, cluster); } static struct cluster_list *cluster_intern(struct cluster_list *cluster) { struct cluster_list *find; find = hash_get(cluster_hash, cluster, cluster_hash_alloc); find->refcnt++; return find; } void cluster_unintern(struct cluster_list *cluster) { if (cluster->refcnt) cluster->refcnt--; if (cluster->refcnt == 0) { hash_release(cluster_hash, cluster); cluster_free(cluster); } } static void cluster_init(void) { cluster_hash = hash_create(cluster_hash_key_make, cluster_hash_cmp, "BGP Cluster"); } static void cluster_finish(void) { hash_clean(cluster_hash, (void (*)(void *))cluster_free); hash_free(cluster_hash); cluster_hash = NULL; } static struct hash *encap_hash = NULL; #if ENABLE_BGP_VNC static struct hash *vnc_hash = NULL; #endif struct bgp_attr_encap_subtlv *encap_tlv_dup(struct bgp_attr_encap_subtlv *orig) { struct bgp_attr_encap_subtlv *new; struct bgp_attr_encap_subtlv *tail; struct bgp_attr_encap_subtlv *p; for (p = orig, tail = new = NULL; p; p = p->next) { int size = sizeof(struct bgp_attr_encap_subtlv) + p->length; if (tail) { tail->next = XCALLOC(MTYPE_ENCAP_TLV, size); tail = tail->next; } else { tail = new = XCALLOC(MTYPE_ENCAP_TLV, size); } assert(tail); memcpy(tail, p, size); tail->next = NULL; } return new; } static void encap_free(struct bgp_attr_encap_subtlv *p) { struct bgp_attr_encap_subtlv *next; while (p) { next = p->next; p->next = NULL; XFREE(MTYPE_ENCAP_TLV, p); p = next; } } void bgp_attr_flush_encap(struct attr *attr) { if (!attr) return; if (attr->encap_subtlvs) { encap_free(attr->encap_subtlvs); attr->encap_subtlvs = NULL; } #if ENABLE_BGP_VNC if (attr->vnc_subtlvs) { encap_free(attr->vnc_subtlvs); attr->vnc_subtlvs = NULL; } #endif } /* * Compare encap sub-tlv chains * * 1 = equivalent * 0 = not equivalent * * This algorithm could be made faster if needed */ static int encap_same(const struct bgp_attr_encap_subtlv *h1, const struct bgp_attr_encap_subtlv *h2) { const struct bgp_attr_encap_subtlv *p; const struct bgp_attr_encap_subtlv *q; if (h1 == h2) return 1; if (h1 == NULL || h2 == NULL) return 0; for (p = h1; p; p = p->next) { for (q = h2; q; q = q->next) { if ((p->type == q->type) && (p->length == q->length) && !memcmp(p->value, q->value, p->length)) { break; } } if (!q) return 0; } for (p = h2; p; p = p->next) { for (q = h1; q; q = q->next) { if ((p->type == q->type) && (p->length == q->length) && !memcmp(p->value, q->value, p->length)) { break; } } if (!q) return 0; } return 1; } static void *encap_hash_alloc(void *p) { /* Encap structure is already allocated. */ return p; } typedef enum { ENCAP_SUBTLV_TYPE, #if ENABLE_BGP_VNC VNC_SUBTLV_TYPE #endif } encap_subtlv_type; static struct bgp_attr_encap_subtlv * encap_intern(struct bgp_attr_encap_subtlv *encap, encap_subtlv_type type) { struct bgp_attr_encap_subtlv *find; struct hash *hash = encap_hash; #if ENABLE_BGP_VNC if (type == VNC_SUBTLV_TYPE) hash = vnc_hash; #endif find = hash_get(hash, encap, encap_hash_alloc); if (find != encap) encap_free(encap); find->refcnt++; return find; } static void encap_unintern(struct bgp_attr_encap_subtlv **encapp, encap_subtlv_type type) { struct bgp_attr_encap_subtlv *encap = *encapp; if (encap->refcnt) encap->refcnt--; if (encap->refcnt == 0) { struct hash *hash = encap_hash; #if ENABLE_BGP_VNC if (type == VNC_SUBTLV_TYPE) hash = vnc_hash; #endif hash_release(hash, encap); encap_free(encap); *encapp = NULL; } } static unsigned int encap_hash_key_make(const void *p) { const struct bgp_attr_encap_subtlv *encap = p; return jhash(encap->value, encap->length, 0); } static bool encap_hash_cmp(const void *p1, const void *p2) { return encap_same((const struct bgp_attr_encap_subtlv *)p1, (const struct bgp_attr_encap_subtlv *)p2); } static void encap_init(void) { encap_hash = hash_create(encap_hash_key_make, encap_hash_cmp, "BGP Encap Hash"); #if ENABLE_BGP_VNC vnc_hash = hash_create(encap_hash_key_make, encap_hash_cmp, "BGP VNC Hash"); #endif } static void encap_finish(void) { hash_clean(encap_hash, (void (*)(void *))encap_free); hash_free(encap_hash); encap_hash = NULL; #if ENABLE_BGP_VNC hash_clean(vnc_hash, (void (*)(void *))encap_free); hash_free(vnc_hash); vnc_hash = NULL; #endif } static bool overlay_index_same(const struct attr *a1, const struct attr *a2) { if (!a1 && a2) return false; if (!a2 && a1) return false; if (!a1 && !a2) return true; return !memcmp(&(a1->evpn_overlay), &(a2->evpn_overlay), sizeof(struct bgp_route_evpn)); } /* Unknown transit attribute. */ static struct hash *transit_hash; static void transit_free(struct transit *transit) { XFREE(MTYPE_TRANSIT_VAL, transit->val); XFREE(MTYPE_TRANSIT, transit); } static void *transit_hash_alloc(void *p) { /* Transit structure is already allocated. */ return p; } static struct transit *transit_intern(struct transit *transit) { struct transit *find; find = hash_get(transit_hash, transit, transit_hash_alloc); if (find != transit) transit_free(transit); find->refcnt++; return find; } static void transit_unintern(struct transit **transit) { if ((*transit)->refcnt) (*transit)->refcnt--; if ((*transit)->refcnt == 0) { hash_release(transit_hash, *transit); transit_free(*transit); *transit = NULL; } } static unsigned int transit_hash_key_make(const void *p) { const struct transit *transit = p; return jhash(transit->val, transit->length, 0); } static bool transit_hash_cmp(const void *p1, const void *p2) { const struct transit *transit1 = p1; const struct transit *transit2 = p2; return (transit1->length == transit2->length && memcmp(transit1->val, transit2->val, transit1->length) == 0); } static void transit_init(void) { transit_hash = hash_create(transit_hash_key_make, transit_hash_cmp, "BGP Transit Hash"); } static void transit_finish(void) { hash_clean(transit_hash, (void (*)(void *))transit_free); hash_free(transit_hash); transit_hash = NULL; } /* Attribute hash routines. */ static struct hash *attrhash; unsigned long int attr_count(void) { return attrhash->count; } unsigned long int attr_unknown_count(void) { return transit_hash->count; } unsigned int attrhash_key_make(const void *p) { const struct attr *attr = (struct attr *)p; uint32_t key = 0; #define MIX(val) key = jhash_1word(val, key) #define MIX3(a, b, c) key = jhash_3words((a), (b), (c), key) MIX3(attr->origin, attr->nexthop.s_addr, attr->med); MIX3(attr->local_pref, attr->aggregator_as, attr->aggregator_addr.s_addr); MIX3(attr->weight, attr->mp_nexthop_global_in.s_addr, attr->originator_id.s_addr); MIX3(attr->tag, attr->label, attr->label_index); if (attr->aspath) MIX(aspath_key_make(attr->aspath)); if (attr->community) MIX(community_hash_make(attr->community)); if (attr->lcommunity) MIX(lcommunity_hash_make(attr->lcommunity)); if (attr->ecommunity) MIX(ecommunity_hash_make(attr->ecommunity)); if (attr->cluster) MIX(cluster_hash_key_make(attr->cluster)); if (attr->transit) MIX(transit_hash_key_make(attr->transit)); if (attr->encap_subtlvs) MIX(encap_hash_key_make(attr->encap_subtlvs)); #if ENABLE_BGP_VNC if (attr->vnc_subtlvs) MIX(encap_hash_key_make(attr->vnc_subtlvs)); #endif MIX(attr->mp_nexthop_len); key = jhash(attr->mp_nexthop_global.s6_addr, IPV6_MAX_BYTELEN, key); key = jhash(attr->mp_nexthop_local.s6_addr, IPV6_MAX_BYTELEN, key); MIX(attr->nh_ifindex); MIX(attr->nh_lla_ifindex); return key; } bool attrhash_cmp(const void *p1, const void *p2) { const struct attr *attr1 = p1; const struct attr *attr2 = p2; if (attr1->flag == attr2->flag && attr1->origin == attr2->origin && attr1->nexthop.s_addr == attr2->nexthop.s_addr && attr1->aspath == attr2->aspath && attr1->community == attr2->community && attr1->med == attr2->med && attr1->local_pref == attr2->local_pref && attr1->rmap_change_flags == attr2->rmap_change_flags) { if (attr1->aggregator_as == attr2->aggregator_as && attr1->aggregator_addr.s_addr == attr2->aggregator_addr.s_addr && attr1->weight == attr2->weight && attr1->tag == attr2->tag && attr1->label_index == attr2->label_index && attr1->mp_nexthop_len == attr2->mp_nexthop_len && attr1->ecommunity == attr2->ecommunity && attr1->lcommunity == attr2->lcommunity && attr1->cluster == attr2->cluster && attr1->transit == attr2->transit && (attr1->encap_tunneltype == attr2->encap_tunneltype) && encap_same(attr1->encap_subtlvs, attr2->encap_subtlvs) #if ENABLE_BGP_VNC && encap_same(attr1->vnc_subtlvs, attr2->vnc_subtlvs) #endif && IPV6_ADDR_SAME(&attr1->mp_nexthop_global, &attr2->mp_nexthop_global) && IPV6_ADDR_SAME(&attr1->mp_nexthop_local, &attr2->mp_nexthop_local) && IPV4_ADDR_SAME(&attr1->mp_nexthop_global_in, &attr2->mp_nexthop_global_in) && IPV4_ADDR_SAME(&attr1->originator_id, &attr2->originator_id) && overlay_index_same(attr1, attr2) && attr1->nh_ifindex == attr2->nh_ifindex && attr1->nh_lla_ifindex == attr2->nh_lla_ifindex) return true; } return false; } static void attrhash_init(void) { attrhash = hash_create(attrhash_key_make, attrhash_cmp, "BGP Attributes"); } /* * special for hash_clean below */ static void attr_vfree(void *attr) { XFREE(MTYPE_ATTR, attr); } static void attrhash_finish(void) { hash_clean(attrhash, attr_vfree); hash_free(attrhash); attrhash = NULL; } static void attr_show_all_iterator(struct hash_bucket *bucket, struct vty *vty) { struct attr *attr = bucket->data; vty_out(vty, "attr[%ld] nexthop %s\n", attr->refcnt, inet_ntoa(attr->nexthop)); vty_out(vty, "\tflags: %" PRIu64 " med: %u local_pref: %u origin: %u weight: %u label: %u\n", attr->flag, attr->med, attr->local_pref, attr->origin, attr->weight, attr->label); } void attr_show_all(struct vty *vty) { hash_iterate(attrhash, (void (*)(struct hash_bucket *, void *))attr_show_all_iterator, vty); } static void *bgp_attr_hash_alloc(void *p) { struct attr *val = (struct attr *)p; struct attr *attr; attr = XMALLOC(MTYPE_ATTR, sizeof(struct attr)); *attr = *val; if (val->encap_subtlvs) { val->encap_subtlvs = NULL; } #if ENABLE_BGP_VNC if (val->vnc_subtlvs) { val->vnc_subtlvs = NULL; } #endif attr->refcnt = 0; return attr; } /* Internet argument attribute. */ struct attr *bgp_attr_intern(struct attr *attr) { struct attr *find; /* Intern referenced strucutre. */ if (attr->aspath) { if (!attr->aspath->refcnt) attr->aspath = aspath_intern(attr->aspath); else attr->aspath->refcnt++; } if (attr->community) { if (!attr->community->refcnt) attr->community = community_intern(attr->community); else attr->community->refcnt++; } if (attr->ecommunity) { if (!attr->ecommunity->refcnt) attr->ecommunity = ecommunity_intern(attr->ecommunity); else attr->ecommunity->refcnt++; } if (attr->lcommunity) { if (!attr->lcommunity->refcnt) attr->lcommunity = lcommunity_intern(attr->lcommunity); else attr->lcommunity->refcnt++; } if (attr->cluster) { if (!attr->cluster->refcnt) attr->cluster = cluster_intern(attr->cluster); else attr->cluster->refcnt++; } if (attr->transit) { if (!attr->transit->refcnt) attr->transit = transit_intern(attr->transit); else attr->transit->refcnt++; } if (attr->encap_subtlvs) { if (!attr->encap_subtlvs->refcnt) attr->encap_subtlvs = encap_intern(attr->encap_subtlvs, ENCAP_SUBTLV_TYPE); else attr->encap_subtlvs->refcnt++; } #if ENABLE_BGP_VNC if (attr->vnc_subtlvs) { if (!attr->vnc_subtlvs->refcnt) attr->vnc_subtlvs = encap_intern(attr->vnc_subtlvs, VNC_SUBTLV_TYPE); else attr->vnc_subtlvs->refcnt++; } #endif /* At this point, attr only contains intern'd pointers. that means * if we find it in attrhash, it has all the same pointers and we * correctly updated the refcounts on these. * If we don't find it, we need to allocate a one because in all * cases this returns a new reference to a hashed attr, but the input * wasn't on hash. */ find = (struct attr *)hash_get(attrhash, attr, bgp_attr_hash_alloc); find->refcnt++; return find; } /* Make network statement's attribute. */ struct attr *bgp_attr_default_set(struct attr *attr, uint8_t origin) { memset(attr, 0, sizeof(struct attr)); attr->origin = origin; attr->flag |= ATTR_FLAG_BIT(BGP_ATTR_ORIGIN); attr->aspath = aspath_empty(); attr->flag |= ATTR_FLAG_BIT(BGP_ATTR_AS_PATH); attr->weight = BGP_ATTR_DEFAULT_WEIGHT; attr->tag = 0; attr->label_index = BGP_INVALID_LABEL_INDEX; attr->label = MPLS_INVALID_LABEL; attr->flag |= ATTR_FLAG_BIT(BGP_ATTR_NEXT_HOP); attr->mp_nexthop_len = IPV6_MAX_BYTELEN; return attr; } /* Create the attributes for an aggregate */ struct attr *bgp_attr_aggregate_intern(struct bgp *bgp, uint8_t origin, struct aspath *aspath, struct community *community, struct ecommunity *ecommunity, struct lcommunity *lcommunity, struct bgp_aggregate *aggregate, uint8_t atomic_aggregate, struct prefix *p) { struct attr attr; struct attr *new; int ret; memset(&attr, 0, sizeof(struct attr)); /* Origin attribute. */ attr.origin = origin; attr.flag |= ATTR_FLAG_BIT(BGP_ATTR_ORIGIN); /* AS path attribute. */ if (aspath) attr.aspath = aspath_intern(aspath); else attr.aspath = aspath_empty(); attr.flag |= ATTR_FLAG_BIT(BGP_ATTR_AS_PATH); /* Next hop attribute. */ attr.flag |= ATTR_FLAG_BIT(BGP_ATTR_NEXT_HOP); if (community) { uint32_t gshut = COMMUNITY_GSHUT; /* If we are not shutting down ourselves and we are * aggregating a route that contains the GSHUT community we * need to remove that community when creating the aggregate */ if (!bgp_flag_check(bgp, BGP_FLAG_GRACEFUL_SHUTDOWN) && community_include(community, gshut)) { community_del_val(community, &gshut); } attr.community = community; attr.flag |= ATTR_FLAG_BIT(BGP_ATTR_COMMUNITIES); } if (ecommunity) { attr.ecommunity = ecommunity; attr.flag |= ATTR_FLAG_BIT(BGP_ATTR_EXT_COMMUNITIES); } if (lcommunity) { attr.lcommunity = lcommunity; attr.flag |= ATTR_FLAG_BIT(BGP_ATTR_LARGE_COMMUNITIES); } if (bgp_flag_check(bgp, BGP_FLAG_GRACEFUL_SHUTDOWN)) { bgp_attr_add_gshut_community(&attr); } attr.label_index = BGP_INVALID_LABEL_INDEX; attr.label = MPLS_INVALID_LABEL; attr.weight = BGP_ATTR_DEFAULT_WEIGHT; attr.mp_nexthop_len = IPV6_MAX_BYTELEN; if (!aggregate->as_set || atomic_aggregate) attr.flag |= ATTR_FLAG_BIT(BGP_ATTR_ATOMIC_AGGREGATE); attr.flag |= ATTR_FLAG_BIT(BGP_ATTR_AGGREGATOR); if (CHECK_FLAG(bgp->config, BGP_CONFIG_CONFEDERATION)) attr.aggregator_as = bgp->confed_id; else attr.aggregator_as = bgp->as; attr.aggregator_addr = bgp->router_id; attr.label_index = BGP_INVALID_LABEL_INDEX; attr.label = MPLS_INVALID_LABEL; /* Apply route-map */ if (aggregate->rmap.name) { struct attr attr_tmp = attr; struct bgp_path_info rmap_path; memset(&rmap_path, 0, sizeof(struct bgp_path_info)); rmap_path.peer = bgp->peer_self; rmap_path.attr = &attr_tmp; SET_FLAG(bgp->peer_self->rmap_type, PEER_RMAP_TYPE_AGGREGATE); ret = route_map_apply(aggregate->rmap.map, p, RMAP_BGP, &rmap_path); bgp->peer_self->rmap_type = 0; if (ret == RMAP_DENYMATCH) { /* Free uninterned attribute. */ bgp_attr_flush(&attr_tmp); /* Unintern original. */ aspath_unintern(&attr.aspath); return NULL; } if (bgp_flag_check(bgp, BGP_FLAG_GRACEFUL_SHUTDOWN)) bgp_attr_add_gshut_community(&attr_tmp); new = bgp_attr_intern(&attr_tmp); } else { if (bgp_flag_check(bgp, BGP_FLAG_GRACEFUL_SHUTDOWN)) bgp_attr_add_gshut_community(&attr); new = bgp_attr_intern(&attr); } aspath_unintern(&new->aspath); return new; } /* Unintern just the sub-components of the attr, but not the attr */ void bgp_attr_unintern_sub(struct attr *attr) { /* aspath refcount shoud be decrement. */ if (attr->aspath) aspath_unintern(&attr->aspath); UNSET_FLAG(attr->flag, ATTR_FLAG_BIT(BGP_ATTR_AS_PATH)); if (attr->community) community_unintern(&attr->community); UNSET_FLAG(attr->flag, ATTR_FLAG_BIT(BGP_ATTR_COMMUNITIES)); if (attr->ecommunity) ecommunity_unintern(&attr->ecommunity); UNSET_FLAG(attr->flag, ATTR_FLAG_BIT(BGP_ATTR_EXT_COMMUNITIES)); if (attr->lcommunity) lcommunity_unintern(&attr->lcommunity); UNSET_FLAG(attr->flag, ATTR_FLAG_BIT(BGP_ATTR_LARGE_COMMUNITIES)); if (attr->cluster) cluster_unintern(attr->cluster); UNSET_FLAG(attr->flag, ATTR_FLAG_BIT(BGP_ATTR_CLUSTER_LIST)); if (attr->transit) transit_unintern(&attr->transit); if (attr->encap_subtlvs) encap_unintern(&attr->encap_subtlvs, ENCAP_SUBTLV_TYPE); #if ENABLE_BGP_VNC if (attr->vnc_subtlvs) encap_unintern(&attr->vnc_subtlvs, VNC_SUBTLV_TYPE); #endif } /* * We have some show commands that let you experimentally * apply a route-map. When we apply the route-map * we are reseting values but not saving them for * posterity via intern'ing( because route-maps don't * do that) but at this point in time we need * to compare the new attr to the old and if the * routemap has changed it we need to, as Snoop Dog says, * Drop it like it's hot */ void bgp_attr_undup(struct attr *new, struct attr *old) { if (new->aspath != old->aspath) aspath_free(new->aspath); if (new->community != old->community) community_free(&new->community); if (new->ecommunity != old->ecommunity) ecommunity_free(&new->ecommunity); if (new->lcommunity != old->lcommunity) lcommunity_free(&new->lcommunity); } /* Free bgp attribute and aspath. */ void bgp_attr_unintern(struct attr **pattr) { struct attr *attr = *pattr; struct attr *ret; struct attr tmp; /* Decrement attribute reference. */ attr->refcnt--; tmp = *attr; /* If reference becomes zero then free attribute object. */ if (attr->refcnt == 0) { ret = hash_release(attrhash, attr); assert(ret != NULL); XFREE(MTYPE_ATTR, attr); *pattr = NULL; } bgp_attr_unintern_sub(&tmp); } void bgp_attr_flush(struct attr *attr) { if (attr->aspath && !attr->aspath->refcnt) { aspath_free(attr->aspath); attr->aspath = NULL; } if (attr->community && !attr->community->refcnt) community_free(&attr->community); if (attr->ecommunity && !attr->ecommunity->refcnt) ecommunity_free(&attr->ecommunity); if (attr->lcommunity && !attr->lcommunity->refcnt) lcommunity_free(&attr->lcommunity); if (attr->cluster && !attr->cluster->refcnt) { cluster_free(attr->cluster); attr->cluster = NULL; } if (attr->transit && !attr->transit->refcnt) { transit_free(attr->transit); attr->transit = NULL; } if (attr->encap_subtlvs && !attr->encap_subtlvs->refcnt) { encap_free(attr->encap_subtlvs); attr->encap_subtlvs = NULL; } #if ENABLE_BGP_VNC if (attr->vnc_subtlvs && !attr->vnc_subtlvs->refcnt) { encap_free(attr->vnc_subtlvs); attr->vnc_subtlvs = NULL; } #endif } /* Implement draft-scudder-idr-optional-transitive behaviour and * avoid resetting sessions for malformed attributes which are * are partial/optional and hence where the error likely was not * introduced by the sending neighbour. */ static bgp_attr_parse_ret_t bgp_attr_malformed(struct bgp_attr_parser_args *args, uint8_t subcode, bgp_size_t length) { struct peer *const peer = args->peer; const uint8_t flags = args->flags; /* startp and length must be special-cased, as whether or not to * send the attribute data with the NOTIFY depends on the error, * the caller therefore signals this with the seperate length argument */ uint8_t *notify_datap = (length > 0 ? args->startp : NULL); /* Only relax error handling for eBGP peers */ if (peer->sort != BGP_PEER_EBGP) { bgp_notify_send_with_data(peer, BGP_NOTIFY_UPDATE_ERR, subcode, notify_datap, length); return BGP_ATTR_PARSE_ERROR; } /* Adjust the stream getp to the end of the attribute, in case we can * still proceed but the caller hasn't read all the attribute. */ stream_set_getp(BGP_INPUT(peer), (args->startp - STREAM_DATA(BGP_INPUT(peer))) + args->total); switch (args->type) { /* where an attribute is relatively inconsequential, e.g. it does not * affect route selection, and can be safely ignored, then any such * attributes which are malformed should just be ignored and the route * processed as normal. */ case BGP_ATTR_AS4_AGGREGATOR: case BGP_ATTR_AGGREGATOR: case BGP_ATTR_ATOMIC_AGGREGATE: return BGP_ATTR_PARSE_PROCEED; /* Core attributes, particularly ones which may influence route * selection, should always cause session resets */ case BGP_ATTR_ORIGIN: case BGP_ATTR_AS_PATH: case BGP_ATTR_NEXT_HOP: case BGP_ATTR_MULTI_EXIT_DISC: case BGP_ATTR_LOCAL_PREF: case BGP_ATTR_COMMUNITIES: case BGP_ATTR_ORIGINATOR_ID: case BGP_ATTR_CLUSTER_LIST: case BGP_ATTR_MP_REACH_NLRI: case BGP_ATTR_MP_UNREACH_NLRI: case BGP_ATTR_EXT_COMMUNITIES: bgp_notify_send_with_data(peer, BGP_NOTIFY_UPDATE_ERR, subcode, notify_datap, length); return BGP_ATTR_PARSE_ERROR; } /* Partial optional attributes that are malformed should not cause * the whole session to be reset. Instead treat it as a withdrawal * of the routes, if possible. */ if (CHECK_FLAG(flags, BGP_ATTR_FLAG_TRANS) && CHECK_FLAG(flags, BGP_ATTR_FLAG_OPTIONAL) && CHECK_FLAG(flags, BGP_ATTR_FLAG_PARTIAL)) return BGP_ATTR_PARSE_WITHDRAW; /* default to reset */ return BGP_ATTR_PARSE_ERROR_NOTIFYPLS; } /* Find out what is wrong with the path attribute flag bits and log the error. "Flag bits" here stand for Optional, Transitive and Partial, but not for Extended Length. Checking O/T/P bits at once implies, that the attribute being diagnosed is defined by RFC as either a "well-known" or an "optional, non-transitive" attribute. */ static void bgp_attr_flags_diagnose(struct bgp_attr_parser_args *args, uint8_t desired_flags /* how RFC says it must be */ ) { uint8_t seen = 0, i; uint8_t real_flags = args->flags; const uint8_t attr_code = args->type; desired_flags &= ~BGP_ATTR_FLAG_EXTLEN; real_flags &= ~BGP_ATTR_FLAG_EXTLEN; for (i = 0; i <= 2; i++) /* O,T,P, but not E */ if (CHECK_FLAG(desired_flags, attr_flag_str[i].key) != CHECK_FLAG(real_flags, attr_flag_str[i].key)) { flog_err(EC_BGP_ATTR_FLAG, "%s attribute must%s be flagged as \"%s\"", lookup_msg(attr_str, attr_code, NULL), CHECK_FLAG(desired_flags, attr_flag_str[i].key) ? "" : " not", attr_flag_str[i].str); seen = 1; } if (!seen) { zlog_debug( "Strange, %s called for attr %s, but no problem found with flags" " (real flags 0x%x, desired 0x%x)", __func__, lookup_msg(attr_str, attr_code, NULL), real_flags, desired_flags); } } /* Required flags for attributes. EXTLEN will be masked off when testing, * as will PARTIAL for optional+transitive attributes. */ const uint8_t attr_flags_values[] = { [BGP_ATTR_ORIGIN] = BGP_ATTR_FLAG_TRANS, [BGP_ATTR_AS_PATH] = BGP_ATTR_FLAG_TRANS, [BGP_ATTR_NEXT_HOP] = BGP_ATTR_FLAG_TRANS, [BGP_ATTR_MULTI_EXIT_DISC] = BGP_ATTR_FLAG_OPTIONAL, [BGP_ATTR_LOCAL_PREF] = BGP_ATTR_FLAG_TRANS, [BGP_ATTR_ATOMIC_AGGREGATE] = BGP_ATTR_FLAG_TRANS, [BGP_ATTR_AGGREGATOR] = BGP_ATTR_FLAG_TRANS | BGP_ATTR_FLAG_OPTIONAL, [BGP_ATTR_COMMUNITIES] = BGP_ATTR_FLAG_TRANS | BGP_ATTR_FLAG_OPTIONAL, [BGP_ATTR_ORIGINATOR_ID] = BGP_ATTR_FLAG_OPTIONAL, [BGP_ATTR_CLUSTER_LIST] = BGP_ATTR_FLAG_OPTIONAL, [BGP_ATTR_MP_REACH_NLRI] = BGP_ATTR_FLAG_OPTIONAL, [BGP_ATTR_MP_UNREACH_NLRI] = BGP_ATTR_FLAG_OPTIONAL, [BGP_ATTR_EXT_COMMUNITIES] = BGP_ATTR_FLAG_OPTIONAL | BGP_ATTR_FLAG_TRANS, [BGP_ATTR_AS4_PATH] = BGP_ATTR_FLAG_OPTIONAL | BGP_ATTR_FLAG_TRANS, [BGP_ATTR_AS4_AGGREGATOR] = BGP_ATTR_FLAG_OPTIONAL | BGP_ATTR_FLAG_TRANS, [BGP_ATTR_PMSI_TUNNEL] = BGP_ATTR_FLAG_OPTIONAL | BGP_ATTR_FLAG_TRANS, [BGP_ATTR_LARGE_COMMUNITIES] = BGP_ATTR_FLAG_OPTIONAL | BGP_ATTR_FLAG_TRANS, [BGP_ATTR_PREFIX_SID] = BGP_ATTR_FLAG_OPTIONAL | BGP_ATTR_FLAG_TRANS, }; static const size_t attr_flags_values_max = array_size(attr_flags_values) - 1; static int bgp_attr_flag_invalid(struct bgp_attr_parser_args *args) { uint8_t mask = BGP_ATTR_FLAG_EXTLEN; const uint8_t flags = args->flags; const uint8_t attr_code = args->type; /* there may be attributes we don't know about */ if (attr_code > attr_flags_values_max) return 0; if (attr_flags_values[attr_code] == 0) return 0; /* RFC4271, "For well-known attributes, the Transitive bit MUST be set * to * 1." */ if (!CHECK_FLAG(BGP_ATTR_FLAG_OPTIONAL, flags) && !CHECK_FLAG(BGP_ATTR_FLAG_TRANS, flags)) { flog_err( EC_BGP_ATTR_FLAG, "%s well-known attributes must have transitive flag set (%x)", lookup_msg(attr_str, attr_code, NULL), flags); return 1; } /* "For well-known attributes and for optional non-transitive * attributes, * the Partial bit MUST be set to 0." */ if (CHECK_FLAG(flags, BGP_ATTR_FLAG_PARTIAL)) { if (!CHECK_FLAG(flags, BGP_ATTR_FLAG_OPTIONAL)) { flog_err(EC_BGP_ATTR_FLAG, "%s well-known attribute " "must NOT have the partial flag set (%x)", lookup_msg(attr_str, attr_code, NULL), flags); return 1; } if (CHECK_FLAG(flags, BGP_ATTR_FLAG_OPTIONAL) && !CHECK_FLAG(flags, BGP_ATTR_FLAG_TRANS)) { flog_err(EC_BGP_ATTR_FLAG, "%s optional + transitive attribute " "must NOT have the partial flag set (%x)", lookup_msg(attr_str, attr_code, NULL), flags); return 1; } } /* Optional transitive attributes may go through speakers that don't * reocgnise them and set the Partial bit. */ if (CHECK_FLAG(flags, BGP_ATTR_FLAG_OPTIONAL) && CHECK_FLAG(flags, BGP_ATTR_FLAG_TRANS)) SET_FLAG(mask, BGP_ATTR_FLAG_PARTIAL); if ((flags & ~mask) == attr_flags_values[attr_code]) return 0; bgp_attr_flags_diagnose(args, attr_flags_values[attr_code]); return 1; } /* Get origin attribute of the update message. */ static bgp_attr_parse_ret_t bgp_attr_origin(struct bgp_attr_parser_args *args) { struct peer *const peer = args->peer; struct attr *const attr = args->attr; const bgp_size_t length = args->length; /* If any recognized attribute has Attribute Length that conflicts with the expected length (based on the attribute type code), then the Error Subcode is set to Attribute Length Error. The Data field contains the erroneous attribute (type, length and value). */ if (length != 1) { flog_err(EC_BGP_ATTR_LEN, "Origin attribute length is not one %d", length); return bgp_attr_malformed(args, BGP_NOTIFY_UPDATE_ATTR_LENG_ERR, args->total); } /* Fetch origin attribute. */ attr->origin = stream_getc(BGP_INPUT(peer)); /* If the ORIGIN attribute has an undefined value, then the Error Subcode is set to Invalid Origin Attribute. The Data field contains the unrecognized attribute (type, length and value). */ if ((attr->origin != BGP_ORIGIN_IGP) && (attr->origin != BGP_ORIGIN_EGP) && (attr->origin != BGP_ORIGIN_INCOMPLETE)) { flog_err(EC_BGP_ATTR_ORIGIN, "Origin attribute value is invalid %d", attr->origin); return bgp_attr_malformed(args, BGP_NOTIFY_UPDATE_INVAL_ORIGIN, args->total); } /* Set oring attribute flag. */ attr->flag |= ATTR_FLAG_BIT(BGP_ATTR_ORIGIN); return 0; } /* Parse AS path information. This function is wrapper of aspath_parse. */ static int bgp_attr_aspath(struct bgp_attr_parser_args *args) { struct attr *const attr = args->attr; struct peer *const peer = args->peer; const bgp_size_t length = args->length; /* * peer with AS4 => will get 4Byte ASnums * otherwise, will get 16 Bit */ attr->aspath = aspath_parse(peer->curr, length, CHECK_FLAG(peer->cap, PEER_CAP_AS4_RCV)); /* In case of IBGP, length will be zero. */ if (!attr->aspath) { flog_err(EC_BGP_ATTR_MAL_AS_PATH, "Malformed AS path from %s, length is %d", peer->host, length); return bgp_attr_malformed(args, BGP_NOTIFY_UPDATE_MAL_AS_PATH, 0); } /* Set aspath attribute flag. */ attr->flag |= ATTR_FLAG_BIT(BGP_ATTR_AS_PATH); return BGP_ATTR_PARSE_PROCEED; } static bgp_attr_parse_ret_t bgp_attr_aspath_check(struct peer *const peer, struct attr *const attr) { /* These checks were part of bgp_attr_aspath, but with * as4 we should to check aspath things when * aspath synthesizing with as4_path has already taken place. * Otherwise we check ASPATH and use the synthesized thing, and that is * not right. * So do the checks later, i.e. here */ struct aspath *aspath; /* Confederation sanity check. */ if ((peer->sort == BGP_PEER_CONFED && !aspath_left_confed_check(attr->aspath)) || (peer->sort == BGP_PEER_EBGP && aspath_confed_check(attr->aspath))) { flog_err(EC_BGP_ATTR_MAL_AS_PATH, "Malformed AS path from %s", peer->host); bgp_notify_send(peer, BGP_NOTIFY_UPDATE_ERR, BGP_NOTIFY_UPDATE_MAL_AS_PATH); return BGP_ATTR_PARSE_ERROR; } /* First AS check for EBGP. */ if (CHECK_FLAG(peer->flags, PEER_FLAG_ENFORCE_FIRST_AS)) { if (peer->sort == BGP_PEER_EBGP && !aspath_firstas_check(attr->aspath, peer->as)) { flog_err(EC_BGP_ATTR_FIRST_AS, "%s incorrect first AS (must be %u)", peer->host, peer->as); bgp_notify_send(peer, BGP_NOTIFY_UPDATE_ERR, BGP_NOTIFY_UPDATE_MAL_AS_PATH); return BGP_ATTR_PARSE_ERROR; } } /* local-as prepend */ if (peer->change_local_as && !CHECK_FLAG(peer->flags, PEER_FLAG_LOCAL_AS_NO_PREPEND)) { aspath = aspath_dup(attr->aspath); aspath = aspath_add_seq(aspath, peer->change_local_as); aspath_unintern(&attr->aspath); attr->aspath = aspath_intern(aspath); } return BGP_ATTR_PARSE_PROCEED; } /* Parse AS4 path information. This function is another wrapper of aspath_parse. */ static int bgp_attr_as4_path(struct bgp_attr_parser_args *args, struct aspath **as4_path) { struct peer *const peer = args->peer; struct attr *const attr = args->attr; const bgp_size_t length = args->length; *as4_path = aspath_parse(peer->curr, length, 1); /* In case of IBGP, length will be zero. */ if (!*as4_path) { flog_err(EC_BGP_ATTR_MAL_AS_PATH, "Malformed AS4 path from %s, length is %d", peer->host, length); return bgp_attr_malformed(args, BGP_NOTIFY_UPDATE_MAL_AS_PATH, 0); } /* Set aspath attribute flag. */ attr->flag |= ATTR_FLAG_BIT(BGP_ATTR_AS4_PATH); return BGP_ATTR_PARSE_PROCEED; } /* * Check that the nexthop attribute is valid. */ bgp_attr_parse_ret_t bgp_attr_nexthop_valid(struct peer *peer, struct attr *attr) { in_addr_t nexthop_h; nexthop_h = ntohl(attr->nexthop.s_addr); if ((IPV4_NET0(nexthop_h) || IPV4_NET127(nexthop_h) || IPV4_CLASS_DE(nexthop_h)) && !BGP_DEBUG(allow_martians, ALLOW_MARTIANS)) { uint8_t data[7]; /* type(2) + length(1) + nhop(4) */ char buf[INET_ADDRSTRLEN]; inet_ntop(AF_INET, &attr->nexthop.s_addr, buf, INET_ADDRSTRLEN); flog_err(EC_BGP_ATTR_MARTIAN_NH, "Martian nexthop %s", buf); data[0] = BGP_ATTR_FLAG_TRANS; data[1] = BGP_ATTR_NEXT_HOP; data[2] = BGP_ATTR_NHLEN_IPV4; memcpy(&data[3], &attr->nexthop.s_addr, BGP_ATTR_NHLEN_IPV4); bgp_notify_send_with_data(peer, BGP_NOTIFY_UPDATE_ERR, BGP_NOTIFY_UPDATE_INVAL_NEXT_HOP, data, 7); return BGP_ATTR_PARSE_ERROR; } return BGP_ATTR_PARSE_PROCEED; } /* Nexthop attribute. */ static bgp_attr_parse_ret_t bgp_attr_nexthop(struct bgp_attr_parser_args *args) { struct peer *const peer = args->peer; struct attr *const attr = args->attr; const bgp_size_t length = args->length; /* Check nexthop attribute length. */ if (length != 4) { flog_err(EC_BGP_ATTR_LEN, "Nexthop attribute length isn't four [%d]", length); return bgp_attr_malformed(args, BGP_NOTIFY_UPDATE_ATTR_LENG_ERR, args->total); } attr->nexthop.s_addr = stream_get_ipv4(peer->curr); attr->flag |= ATTR_FLAG_BIT(BGP_ATTR_NEXT_HOP); return BGP_ATTR_PARSE_PROCEED; } /* MED atrribute. */ static bgp_attr_parse_ret_t bgp_attr_med(struct bgp_attr_parser_args *args) { struct peer *const peer = args->peer; struct attr *const attr = args->attr; const bgp_size_t length = args->length; /* Length check. */ if (length != 4) { flog_err(EC_BGP_ATTR_LEN, "MED attribute length isn't four [%d]", length); return bgp_attr_malformed(args, BGP_NOTIFY_UPDATE_ATTR_LENG_ERR, args->total); } attr->med = stream_getl(peer->curr); attr->flag |= ATTR_FLAG_BIT(BGP_ATTR_MULTI_EXIT_DISC); return BGP_ATTR_PARSE_PROCEED; } /* Local preference attribute. */ static bgp_attr_parse_ret_t bgp_attr_local_pref(struct bgp_attr_parser_args *args) { struct peer *const peer = args->peer; struct attr *const attr = args->attr; const bgp_size_t length = args->length; /* Length check. */ if (length != 4) { flog_err(EC_BGP_ATTR_LEN, "LOCAL_PREF attribute length isn't 4 [%u]", length); return bgp_attr_malformed(args, BGP_NOTIFY_UPDATE_ATTR_LENG_ERR, args->total); } /* If it is contained in an UPDATE message that is received from an external peer, then this attribute MUST be ignored by the receiving speaker. */ if (peer->sort == BGP_PEER_EBGP) { stream_forward_getp(peer->curr, length); return BGP_ATTR_PARSE_PROCEED; } attr->local_pref = stream_getl(peer->curr); /* Set the local-pref flag. */ attr->flag |= ATTR_FLAG_BIT(BGP_ATTR_LOCAL_PREF); return BGP_ATTR_PARSE_PROCEED; } /* Atomic aggregate. */ static int bgp_attr_atomic(struct bgp_attr_parser_args *args) { struct attr *const attr = args->attr; const bgp_size_t length = args->length; /* Length check. */ if (length != 0) { flog_err(EC_BGP_ATTR_LEN, "ATOMIC_AGGREGATE attribute length isn't 0 [%u]", length); return bgp_attr_malformed(args, BGP_NOTIFY_UPDATE_ATTR_LENG_ERR, args->total); } /* Set atomic aggregate flag. */ attr->flag |= ATTR_FLAG_BIT(BGP_ATTR_ATOMIC_AGGREGATE); return BGP_ATTR_PARSE_PROCEED; } /* Aggregator attribute */ static int bgp_attr_aggregator(struct bgp_attr_parser_args *args) { struct peer *const peer = args->peer; struct attr *const attr = args->attr; const bgp_size_t length = args->length; int wantedlen = 6; /* peer with AS4 will send 4 Byte AS, peer without will send 2 Byte */ if (CHECK_FLAG(peer->cap, PEER_CAP_AS4_RCV)) wantedlen = 8; if (length != wantedlen) { flog_err(EC_BGP_ATTR_LEN, "AGGREGATOR attribute length isn't %u [%u]", wantedlen, length); return bgp_attr_malformed(args, BGP_NOTIFY_UPDATE_ATTR_LENG_ERR, args->total); } if (CHECK_FLAG(peer->cap, PEER_CAP_AS4_RCV)) attr->aggregator_as = stream_getl(peer->curr); else attr->aggregator_as = stream_getw(peer->curr); attr->aggregator_addr.s_addr = stream_get_ipv4(peer->curr); /* Set atomic aggregate flag. */ attr->flag |= ATTR_FLAG_BIT(BGP_ATTR_AGGREGATOR); return BGP_ATTR_PARSE_PROCEED; } /* New Aggregator attribute */ static bgp_attr_parse_ret_t bgp_attr_as4_aggregator(struct bgp_attr_parser_args *args, as_t *as4_aggregator_as, struct in_addr *as4_aggregator_addr) { struct peer *const peer = args->peer; struct attr *const attr = args->attr; const bgp_size_t length = args->length; if (length != 8) { flog_err(EC_BGP_ATTR_LEN, "New Aggregator length is not 8 [%d]", length); return bgp_attr_malformed(args, BGP_NOTIFY_UPDATE_ATTR_LENG_ERR, 0); } *as4_aggregator_as = stream_getl(peer->curr); as4_aggregator_addr->s_addr = stream_get_ipv4(peer->curr); attr->flag |= ATTR_FLAG_BIT(BGP_ATTR_AS4_AGGREGATOR); return BGP_ATTR_PARSE_PROCEED; } /* Munge Aggregator and New-Aggregator, AS_PATH and NEW_AS_PATH. */ static bgp_attr_parse_ret_t bgp_attr_munge_as4_attrs(struct peer *const peer, struct attr *const attr, struct aspath *as4_path, as_t as4_aggregator, struct in_addr *as4_aggregator_addr) { int ignore_as4_path = 0; struct aspath *newpath; if (!attr->aspath) { /* NULL aspath shouldn't be possible as bgp_attr_parse should * have * checked that all well-known, mandatory attributes were * present. * * Can only be a problem with peer itself - hard error */ return BGP_ATTR_PARSE_ERROR; } if (CHECK_FLAG(peer->cap, PEER_CAP_AS4_RCV)) { /* peer can do AS4, so we ignore AS4_PATH and AS4_AGGREGATOR * if given. * It is worth a warning though, because the peer really * should not send them */ if (BGP_DEBUG(as4, AS4)) { if (attr->flag & (ATTR_FLAG_BIT(BGP_ATTR_AS4_PATH))) zlog_debug("[AS4] %s %s AS4_PATH", peer->host, "AS4 capable peer, yet it sent"); if (attr->flag & (ATTR_FLAG_BIT(BGP_ATTR_AS4_AGGREGATOR))) zlog_debug("[AS4] %s %s AS4_AGGREGATOR", peer->host, "AS4 capable peer, yet it sent"); } return BGP_ATTR_PARSE_PROCEED; } /* We have a asn16 peer. First, look for AS4_AGGREGATOR * because that may override AS4_PATH */ if (attr->flag & (ATTR_FLAG_BIT(BGP_ATTR_AS4_AGGREGATOR))) { if (attr->flag & (ATTR_FLAG_BIT(BGP_ATTR_AGGREGATOR))) { /* received both. * if the as_number in aggregator is not AS_TRANS, * then AS4_AGGREGATOR and AS4_PATH shall be ignored * and the Aggregator shall be taken as * info on the aggregating node, and the AS_PATH * shall be taken as the AS_PATH * otherwise * the Aggregator shall be ignored and the * AS4_AGGREGATOR shall be taken as the * Aggregating node and the AS_PATH is to be * constructed "as in all other cases" */ if (attr->aggregator_as != BGP_AS_TRANS) { /* ignore */ if (BGP_DEBUG(as4, AS4)) zlog_debug( "[AS4] %s BGP not AS4 capable peer" " send AGGREGATOR != AS_TRANS and" " AS4_AGGREGATOR, so ignore" " AS4_AGGREGATOR and AS4_PATH", peer->host); ignore_as4_path = 1; } else { /* "New_aggregator shall be taken as aggregator" */ attr->aggregator_as = as4_aggregator; attr->aggregator_addr.s_addr = as4_aggregator_addr->s_addr; } } else { /* We received a AS4_AGGREGATOR but no AGGREGATOR. * That is bogus - but reading the conditions * we have to handle AS4_AGGREGATOR as if it were * AGGREGATOR in that case */ if (BGP_DEBUG(as4, AS4)) zlog_debug( "[AS4] %s BGP not AS4 capable peer send" " AS4_AGGREGATOR but no AGGREGATOR, will take" " it as if AGGREGATOR with AS_TRANS had been there", peer->host); attr->aggregator_as = as4_aggregator; /* sweep it under the carpet and simulate a "good" * AGGREGATOR */ attr->flag |= (ATTR_FLAG_BIT(BGP_ATTR_AGGREGATOR)); } } /* need to reconcile NEW_AS_PATH and AS_PATH */ if (!ignore_as4_path && (attr->flag & (ATTR_FLAG_BIT(BGP_ATTR_AS4_PATH)))) { newpath = aspath_reconcile_as4(attr->aspath, as4_path); if (!newpath) return BGP_ATTR_PARSE_ERROR; aspath_unintern(&attr->aspath); attr->aspath = aspath_intern(newpath); } return BGP_ATTR_PARSE_PROCEED; } /* Community attribute. */ static bgp_attr_parse_ret_t bgp_attr_community(struct bgp_attr_parser_args *args) { struct peer *const peer = args->peer; struct attr *const attr = args->attr; const bgp_size_t length = args->length; if (length == 0) { attr->community = NULL; return BGP_ATTR_PARSE_PROCEED; } attr->community = community_parse((uint32_t *)stream_pnt(peer->curr), length); /* XXX: fix community_parse to use stream API and remove this */ stream_forward_getp(peer->curr, length); if (!attr->community) return bgp_attr_malformed(args, BGP_NOTIFY_UPDATE_OPT_ATTR_ERR, args->total); attr->flag |= ATTR_FLAG_BIT(BGP_ATTR_COMMUNITIES); return BGP_ATTR_PARSE_PROCEED; } /* Originator ID attribute. */ static bgp_attr_parse_ret_t bgp_attr_originator_id(struct bgp_attr_parser_args *args) { struct peer *const peer = args->peer; struct attr *const attr = args->attr; const bgp_size_t length = args->length; /* Length check. */ if (length != 4) { flog_err(EC_BGP_ATTR_LEN, "Bad originator ID length %d", length); return bgp_attr_malformed(args, BGP_NOTIFY_UPDATE_ATTR_LENG_ERR, args->total); } attr->originator_id.s_addr = stream_get_ipv4(peer->curr); attr->flag |= ATTR_FLAG_BIT(BGP_ATTR_ORIGINATOR_ID); return BGP_ATTR_PARSE_PROCEED; } /* Cluster list attribute. */ static bgp_attr_parse_ret_t bgp_attr_cluster_list(struct bgp_attr_parser_args *args) { struct peer *const peer = args->peer; struct attr *const attr = args->attr; const bgp_size_t length = args->length; /* Check length. */ if (length % 4) { flog_err(EC_BGP_ATTR_LEN, "Bad cluster list length %d", length); return bgp_attr_malformed(args, BGP_NOTIFY_UPDATE_ATTR_LENG_ERR, args->total); } attr->cluster = cluster_parse((struct in_addr *)stream_pnt(peer->curr), length); /* XXX: Fix cluster_parse to use stream API and then remove this */ stream_forward_getp(peer->curr, length); attr->flag |= ATTR_FLAG_BIT(BGP_ATTR_CLUSTER_LIST); return BGP_ATTR_PARSE_PROCEED; } /* Multiprotocol reachability information parse. */ int bgp_mp_reach_parse(struct bgp_attr_parser_args *args, struct bgp_nlri *mp_update) { iana_afi_t pkt_afi; afi_t afi; iana_safi_t pkt_safi; safi_t safi; bgp_size_t nlri_len; size_t start; struct stream *s; struct peer *const peer = args->peer; struct attr *const attr = args->attr; const bgp_size_t length = args->length; /* Set end of packet. */ s = BGP_INPUT(peer); start = stream_get_getp(s); /* safe to read statically sized header? */ #define BGP_MP_REACH_MIN_SIZE 5 #define LEN_LEFT (length - (stream_get_getp(s) - start)) if ((length > STREAM_READABLE(s)) || (length < BGP_MP_REACH_MIN_SIZE)) { zlog_info("%s: %s sent invalid length, %lu", __func__, peer->host, (unsigned long)length); return BGP_ATTR_PARSE_ERROR_NOTIFYPLS; } /* Load AFI, SAFI. */ pkt_afi = stream_getw(s); pkt_safi = stream_getc(s); /* Convert AFI, SAFI to internal values, check. */ if (bgp_map_afi_safi_iana2int(pkt_afi, pkt_safi, &afi, &safi)) { /* Log if AFI or SAFI is unrecognized. This is not an error * unless * the attribute is otherwise malformed. */ if (bgp_debug_update(peer, NULL, NULL, 0)) zlog_debug( "%s: MP_REACH received AFI %s or SAFI %s is unrecognized", peer->host, iana_afi2str(pkt_afi), iana_safi2str(pkt_safi)); return BGP_ATTR_PARSE_ERROR; } /* Get nexthop length. */ attr->mp_nexthop_len = stream_getc(s); if (LEN_LEFT < attr->mp_nexthop_len) { zlog_info( "%s: %s, MP nexthop length, %u, goes past end of attribute", __func__, peer->host, attr->mp_nexthop_len); return BGP_ATTR_PARSE_ERROR_NOTIFYPLS; } /* Nexthop length check. */ switch (attr->mp_nexthop_len) { case 0: if (safi != SAFI_FLOWSPEC) { zlog_info("%s: (%s) Wrong multiprotocol next hop length: %d", __func__, peer->host, attr->mp_nexthop_len); return BGP_ATTR_PARSE_ERROR_NOTIFYPLS; } break; case BGP_ATTR_NHLEN_VPNV4: stream_getl(s); /* RD high */ stream_getl(s); /* RD low */ /* * NOTE: intentional fall through * - for consistency in rx processing * * The following comment is to signal GCC this intention * and suppress the warning */ /* FALLTHRU */ case BGP_ATTR_NHLEN_IPV4: stream_get(&attr->mp_nexthop_global_in, s, IPV4_MAX_BYTELEN); /* Probably needed for RFC 2283 */ if (attr->nexthop.s_addr == 0) memcpy(&attr->nexthop.s_addr, &attr->mp_nexthop_global_in, IPV4_MAX_BYTELEN); break; case BGP_ATTR_NHLEN_IPV6_GLOBAL: case BGP_ATTR_NHLEN_VPNV6_GLOBAL: if (attr->mp_nexthop_len == BGP_ATTR_NHLEN_VPNV6_GLOBAL) { stream_getl(s); /* RD high */ stream_getl(s); /* RD low */ } stream_get(&attr->mp_nexthop_global, s, IPV6_MAX_BYTELEN); if (IN6_IS_ADDR_LINKLOCAL(&attr->mp_nexthop_global)) { if (!peer->nexthop.ifp) { zlog_warn("%s: Received a V6/VPNV6 Global attribute but address is a V6 LL and we have no peer interface information, withdrawing", peer->host); return BGP_ATTR_PARSE_WITHDRAW; } attr->nh_ifindex = peer->nexthop.ifp->ifindex; } break; case BGP_ATTR_NHLEN_IPV6_GLOBAL_AND_LL: case BGP_ATTR_NHLEN_VPNV6_GLOBAL_AND_LL: if (attr->mp_nexthop_len == BGP_ATTR_NHLEN_VPNV6_GLOBAL_AND_LL) { stream_getl(s); /* RD high */ stream_getl(s); /* RD low */ } stream_get(&attr->mp_nexthop_global, s, IPV6_MAX_BYTELEN); if (IN6_IS_ADDR_LINKLOCAL(&attr->mp_nexthop_global)) { if (!peer->nexthop.ifp) { zlog_warn("%s: Received V6/VPNV6 Global and LL attribute but global address is a V6 LL and we have no peer interface information, withdrawing", peer->host); return BGP_ATTR_PARSE_WITHDRAW; } attr->nh_ifindex = peer->nexthop.ifp->ifindex; } if (attr->mp_nexthop_len == BGP_ATTR_NHLEN_VPNV6_GLOBAL_AND_LL) { stream_getl(s); /* RD high */ stream_getl(s); /* RD low */ } stream_get(&attr->mp_nexthop_local, s, IPV6_MAX_BYTELEN); if (!IN6_IS_ADDR_LINKLOCAL(&attr->mp_nexthop_local)) { char buf1[INET6_ADDRSTRLEN]; char buf2[INET6_ADDRSTRLEN]; if (bgp_debug_update(peer, NULL, NULL, 1)) zlog_debug( "%s rcvd nexthops %s, %s -- ignoring non-LL value", peer->host, inet_ntop(AF_INET6, &attr->mp_nexthop_global, buf1, INET6_ADDRSTRLEN), inet_ntop(AF_INET6, &attr->mp_nexthop_local, buf2, INET6_ADDRSTRLEN)); attr->mp_nexthop_len = IPV6_MAX_BYTELEN; } if (!peer->nexthop.ifp) { zlog_warn("%s: Received a V6 LL nexthop and we have no peer interface information, withdrawing", peer->host); return BGP_ATTR_PARSE_WITHDRAW; } attr->nh_lla_ifindex = peer->nexthop.ifp->ifindex; break; default: zlog_info("%s: (%s) Wrong multiprotocol next hop length: %d", __func__, peer->host, attr->mp_nexthop_len); return BGP_ATTR_PARSE_ERROR_NOTIFYPLS; } if (!LEN_LEFT) { zlog_info("%s: (%s) Failed to read SNPA and NLRI(s)", __func__, peer->host); return BGP_ATTR_PARSE_ERROR_NOTIFYPLS; } { uint8_t val; if ((val = stream_getc(s))) flog_warn( EC_BGP_DEFUNCT_SNPA_LEN, "%s sent non-zero value, %u, for defunct SNPA-length field", peer->host, val); } /* must have nrli_len, what is left of the attribute */ nlri_len = LEN_LEFT; if (nlri_len > STREAM_READABLE(s)) { zlog_info("%s: (%s) Failed to read NLRI", __func__, peer->host); return BGP_ATTR_PARSE_ERROR_NOTIFYPLS; } if (!nlri_len) { zlog_info("%s: (%s) No Reachability, Treating as a EOR marker", __func__, peer->host); mp_update->afi = afi; mp_update->safi = safi; return BGP_ATTR_PARSE_EOR; } mp_update->afi = afi; mp_update->safi = safi; mp_update->nlri = stream_pnt(s); mp_update->length = nlri_len; stream_forward_getp(s, nlri_len); attr->flag |= ATTR_FLAG_BIT(BGP_ATTR_MP_REACH_NLRI); return BGP_ATTR_PARSE_PROCEED; #undef LEN_LEFT } /* Multiprotocol unreachable parse */ int bgp_mp_unreach_parse(struct bgp_attr_parser_args *args, struct bgp_nlri *mp_withdraw) { struct stream *s; iana_afi_t pkt_afi; afi_t afi; iana_safi_t pkt_safi; safi_t safi; uint16_t withdraw_len; struct peer *const peer = args->peer; struct attr *const attr = args->attr; const bgp_size_t length = args->length; s = peer->curr; #define BGP_MP_UNREACH_MIN_SIZE 3 if ((length > STREAM_READABLE(s)) || (length < BGP_MP_UNREACH_MIN_SIZE)) return BGP_ATTR_PARSE_ERROR_NOTIFYPLS; pkt_afi = stream_getw(s); pkt_safi = stream_getc(s); /* Convert AFI, SAFI to internal values, check. */ if (bgp_map_afi_safi_iana2int(pkt_afi, pkt_safi, &afi, &safi)) { /* Log if AFI or SAFI is unrecognized. This is not an error * unless * the attribute is otherwise malformed. */ if (bgp_debug_update(peer, NULL, NULL, 0)) zlog_debug( "%s: MP_UNREACH received AFI %s or SAFI %s is unrecognized", peer->host, iana_afi2str(pkt_afi), iana_safi2str(pkt_safi)); return BGP_ATTR_PARSE_ERROR; } withdraw_len = length - BGP_MP_UNREACH_MIN_SIZE; mp_withdraw->afi = afi; mp_withdraw->safi = safi; mp_withdraw->nlri = stream_pnt(s); mp_withdraw->length = withdraw_len; stream_forward_getp(s, withdraw_len); attr->flag |= ATTR_FLAG_BIT(BGP_ATTR_MP_UNREACH_NLRI); return BGP_ATTR_PARSE_PROCEED; } /* Large Community attribute. */ static bgp_attr_parse_ret_t bgp_attr_large_community(struct bgp_attr_parser_args *args) { struct peer *const peer = args->peer; struct attr *const attr = args->attr; const bgp_size_t length = args->length; /* * Large community follows new attribute format. */ if (length == 0) { attr->lcommunity = NULL; /* Empty extcomm doesn't seem to be invalid per se */ return BGP_ATTR_PARSE_PROCEED; } attr->lcommunity = lcommunity_parse((uint8_t *)stream_pnt(peer->curr), length); /* XXX: fix ecommunity_parse to use stream API */ stream_forward_getp(peer->curr, length); if (!attr->lcommunity) return bgp_attr_malformed(args, BGP_NOTIFY_UPDATE_OPT_ATTR_ERR, args->total); attr->flag |= ATTR_FLAG_BIT(BGP_ATTR_LARGE_COMMUNITIES); return BGP_ATTR_PARSE_PROCEED; } /* Extended Community attribute. */ static bgp_attr_parse_ret_t bgp_attr_ext_communities(struct bgp_attr_parser_args *args) { struct peer *const peer = args->peer; struct attr *const attr = args->attr; const bgp_size_t length = args->length; uint8_t sticky = 0; if (length == 0) { attr->ecommunity = NULL; /* Empty extcomm doesn't seem to be invalid per se */ return BGP_ATTR_PARSE_PROCEED; } attr->ecommunity = ecommunity_parse((uint8_t *)stream_pnt(peer->curr), length); /* XXX: fix ecommunity_parse to use stream API */ stream_forward_getp(peer->curr, length); if (!attr->ecommunity) return bgp_attr_malformed(args, BGP_NOTIFY_UPDATE_OPT_ATTR_ERR, args->total); attr->flag |= ATTR_FLAG_BIT(BGP_ATTR_EXT_COMMUNITIES); /* Extract MAC mobility sequence number, if any. */ attr->mm_seqnum = bgp_attr_mac_mobility_seqnum(attr, &sticky); attr->sticky = sticky; /* Check if this is a Gateway MAC-IP advertisement */ attr->default_gw = bgp_attr_default_gw(attr); /* Handle scenario where router flag ecommunity is not * set but default gw ext community is present. * Use default gateway, set and propogate R-bit. */ if (attr->default_gw) attr->router_flag = 1; /* Check EVPN Neighbor advertisement flags, R-bit */ bgp_attr_evpn_na_flag(attr, &attr->router_flag); /* Extract the Rmac, if any */ if (bgp_attr_rmac(attr, &attr->rmac)) { if (bgp_debug_update(peer, NULL, NULL, 1) && bgp_mac_exist(&attr->rmac)) { char buf1[ETHER_ADDR_STRLEN]; zlog_debug("%s: router mac %s is self mac", __func__, prefix_mac2str(&attr->rmac, buf1, sizeof(buf1))); } } /* Get the tunnel type from encap extended community */ bgp_attr_extcom_tunnel_type(attr, (bgp_encap_types *)&attr->encap_tunneltype); return BGP_ATTR_PARSE_PROCEED; } /* Parse Tunnel Encap attribute in an UPDATE */ static int bgp_attr_encap(uint8_t type, struct peer *peer, /* IN */ bgp_size_t length, /* IN: attr's length field */ struct attr *attr, /* IN: caller already allocated */ uint8_t flag, /* IN: attr's flags field */ uint8_t *startp) { bgp_size_t total; uint16_t tunneltype = 0; total = length + (CHECK_FLAG(flag, BGP_ATTR_FLAG_EXTLEN) ? 4 : 3); if (!CHECK_FLAG(flag, BGP_ATTR_FLAG_TRANS) || !CHECK_FLAG(flag, BGP_ATTR_FLAG_OPTIONAL)) { zlog_info( "Tunnel Encap attribute flag isn't optional and transitive %d", flag); bgp_notify_send_with_data(peer, BGP_NOTIFY_UPDATE_ERR, BGP_NOTIFY_UPDATE_ATTR_FLAG_ERR, startp, total); return -1; } if (BGP_ATTR_ENCAP == type) { /* read outer TLV type and length */ uint16_t tlv_length; if (length < 4) { zlog_info( "Tunnel Encap attribute not long enough to contain outer T,L"); bgp_notify_send_with_data( peer, BGP_NOTIFY_UPDATE_ERR, BGP_NOTIFY_UPDATE_OPT_ATTR_ERR, startp, total); return -1; } tunneltype = stream_getw(BGP_INPUT(peer)); tlv_length = stream_getw(BGP_INPUT(peer)); length -= 4; if (tlv_length != length) { zlog_info("%s: tlv_length(%d) != length(%d)", __func__, tlv_length, length); } } while (length >= 4) { uint16_t subtype = 0; uint16_t sublength = 0; struct bgp_attr_encap_subtlv *tlv; if (BGP_ATTR_ENCAP == type) { subtype = stream_getc(BGP_INPUT(peer)); sublength = stream_getc(BGP_INPUT(peer)); length -= 2; #if ENABLE_BGP_VNC } else { subtype = stream_getw(BGP_INPUT(peer)); sublength = stream_getw(BGP_INPUT(peer)); length -= 4; #endif } if (sublength > length) { zlog_info( "Tunnel Encap attribute sub-tlv length %d exceeds remaining length %d", sublength, length); bgp_notify_send_with_data( peer, BGP_NOTIFY_UPDATE_ERR, BGP_NOTIFY_UPDATE_OPT_ATTR_ERR, startp, total); return -1; } /* alloc and copy sub-tlv */ /* TBD make sure these are freed when attributes are released */ tlv = XCALLOC(MTYPE_ENCAP_TLV, sizeof(struct bgp_attr_encap_subtlv) + sublength); tlv->type = subtype; tlv->length = sublength; stream_get(tlv->value, peer->curr, sublength); length -= sublength; /* attach tlv to encap chain */ if (BGP_ATTR_ENCAP == type) { struct bgp_attr_encap_subtlv *stlv_last; for (stlv_last = attr->encap_subtlvs; stlv_last && stlv_last->next; stlv_last = stlv_last->next) ; if (stlv_last) { stlv_last->next = tlv; } else { attr->encap_subtlvs = tlv; } #if ENABLE_BGP_VNC } else { struct bgp_attr_encap_subtlv *stlv_last; for (stlv_last = attr->vnc_subtlvs; stlv_last && stlv_last->next; stlv_last = stlv_last->next) ; if (stlv_last) { stlv_last->next = tlv; } else { attr->vnc_subtlvs = tlv; } #endif } } if (BGP_ATTR_ENCAP == type) { attr->encap_tunneltype = tunneltype; } if (length) { /* spurious leftover data */ zlog_info( "Tunnel Encap attribute length is bad: %d leftover octets", length); bgp_notify_send_with_data(peer, BGP_NOTIFY_UPDATE_ERR, BGP_NOTIFY_UPDATE_OPT_ATTR_ERR, startp, total); return -1; } return 0; } /* * Read an individual SID value returning how much data we have read * Returns 0 if there was an error that needs to be passed up the stack */ static bgp_attr_parse_ret_t bgp_attr_psid_sub(int32_t type, int32_t length, struct bgp_attr_parser_args *args, struct bgp_nlri *mp_update) { struct peer *const peer = args->peer; struct attr *const attr = args->attr; uint32_t label_index; struct in6_addr ipv6_sid; uint32_t srgb_base; uint32_t srgb_range; int srgb_count; if (type == BGP_PREFIX_SID_LABEL_INDEX) { if (length != BGP_PREFIX_SID_LABEL_INDEX_LENGTH) { flog_err( EC_BGP_ATTR_LEN, "Prefix SID label index length is %d instead of %d", length, BGP_PREFIX_SID_LABEL_INDEX_LENGTH); return bgp_attr_malformed(args, BGP_NOTIFY_UPDATE_ATTR_LENG_ERR, args->total); } /* Ignore flags and reserved */ stream_getc(peer->curr); stream_getw(peer->curr); /* Fetch the label index and see if it is valid. */ label_index = stream_getl(peer->curr); if (label_index == BGP_INVALID_LABEL_INDEX) return bgp_attr_malformed(args, BGP_NOTIFY_UPDATE_OPT_ATTR_ERR, args->total); /* Store label index; subsequently, we'll check on * address-family */ attr->label_index = label_index; /* * Ignore the Label index attribute unless received for * labeled-unicast * SAFI. */ if (!mp_update->length || mp_update->safi != SAFI_LABELED_UNICAST) attr->label_index = BGP_INVALID_LABEL_INDEX; } /* Placeholder code for the IPv6 SID type */ else if (type == BGP_PREFIX_SID_IPV6) { if (length != BGP_PREFIX_SID_IPV6_LENGTH) { flog_err(EC_BGP_ATTR_LEN, "Prefix SID IPv6 length is %d instead of %d", length, BGP_PREFIX_SID_IPV6_LENGTH); return bgp_attr_malformed(args, BGP_NOTIFY_UPDATE_ATTR_LENG_ERR, args->total); } /* Ignore reserved */ stream_getc(peer->curr); stream_getw(peer->curr); stream_get(&ipv6_sid, peer->curr, 16); } /* Placeholder code for the Originator SRGB type */ else if (type == BGP_PREFIX_SID_ORIGINATOR_SRGB) { /* Ignore flags */ stream_getw(peer->curr); length -= 2; if (length % BGP_PREFIX_SID_ORIGINATOR_SRGB_LENGTH) { flog_err( EC_BGP_ATTR_LEN, "Prefix SID Originator SRGB length is %d, it must be a multiple of %d ", length, BGP_PREFIX_SID_ORIGINATOR_SRGB_LENGTH); return bgp_attr_malformed( args, BGP_NOTIFY_UPDATE_ATTR_LENG_ERR, args->total); } srgb_count = length / BGP_PREFIX_SID_ORIGINATOR_SRGB_LENGTH; for (int i = 0; i < srgb_count; i++) { stream_get(&srgb_base, peer->curr, 3); stream_get(&srgb_range, peer->curr, 3); } } return BGP_ATTR_PARSE_PROCEED; } /* Prefix SID attribute * draft-ietf-idr-bgp-prefix-sid-05 */ bgp_attr_parse_ret_t bgp_attr_prefix_sid(int32_t tlength, struct bgp_attr_parser_args *args, struct bgp_nlri *mp_update) { struct peer *const peer = args->peer; struct attr *const attr = args->attr; bgp_attr_parse_ret_t ret; attr->flag |= ATTR_FLAG_BIT(BGP_ATTR_PREFIX_SID); while (tlength) { int32_t type, length; type = stream_getc(peer->curr); length = stream_getw(peer->curr); ret = bgp_attr_psid_sub(type, length, args, mp_update); if (ret != BGP_ATTR_PARSE_PROCEED) return ret; /* * Subtract length + the T and the L * since length is the Vector portion */ tlength -= length + 3; if (tlength < 0) { flog_err( EC_BGP_ATTR_LEN, "Prefix SID internal length %d causes us to read beyond the total Prefix SID length", length); return bgp_attr_malformed(args, BGP_NOTIFY_UPDATE_ATTR_LENG_ERR, args->total); } } return BGP_ATTR_PARSE_PROCEED; } /* PMSI tunnel attribute (RFC 6514) * Basic validation checks done here. */ static bgp_attr_parse_ret_t bgp_attr_pmsi_tunnel(struct bgp_attr_parser_args *args) { struct peer *const peer = args->peer; struct attr *const attr = args->attr; const bgp_size_t length = args->length; uint8_t tnl_type; int attr_parse_len = 2 + BGP_LABEL_BYTES; /* Verify that the receiver is expecting "ingress replication" as we * can only support that. */ if (length < attr_parse_len) { flog_err(EC_BGP_ATTR_LEN, "Bad PMSI tunnel attribute length %d", length); return bgp_attr_malformed(args, BGP_NOTIFY_UPDATE_ATTR_LENG_ERR, args->total); } stream_getc(peer->curr); /* Flags */ tnl_type = stream_getc(peer->curr); if (tnl_type > PMSI_TNLTYPE_MAX) { flog_err(EC_BGP_ATTR_PMSI_TYPE, "Invalid PMSI tunnel attribute type %d", tnl_type); return bgp_attr_malformed(args, BGP_NOTIFY_UPDATE_OPT_ATTR_ERR, args->total); } if (tnl_type == PMSI_TNLTYPE_INGR_REPL) { if (length != 9) { flog_err(EC_BGP_ATTR_PMSI_LEN, "Bad PMSI tunnel attribute length %d for IR", length); return bgp_attr_malformed( args, BGP_NOTIFY_UPDATE_ATTR_LENG_ERR, args->total); } } attr->flag |= ATTR_FLAG_BIT(BGP_ATTR_PMSI_TUNNEL); attr->pmsi_tnl_type = tnl_type; stream_get(&attr->label, peer->curr, BGP_LABEL_BYTES); /* Forward read pointer of input stream. */ stream_forward_getp(peer->curr, length - attr_parse_len); return BGP_ATTR_PARSE_PROCEED; } /* BGP unknown attribute treatment. */ static bgp_attr_parse_ret_t bgp_attr_unknown(struct bgp_attr_parser_args *args) { bgp_size_t total = args->total; struct transit *transit; struct peer *const peer = args->peer; struct attr *const attr = args->attr; uint8_t *const startp = args->startp; const uint8_t type = args->type; const uint8_t flag = args->flags; const bgp_size_t length = args->length; if (bgp_debug_update(peer, NULL, NULL, 1)) zlog_debug( "%s Unknown attribute is received (type %d, length %d)", peer->host, type, length); /* Forward read pointer of input stream. */ stream_forward_getp(peer->curr, length); /* If any of the mandatory well-known attributes are not recognized, then the Error Subcode is set to Unrecognized Well-known Attribute. The Data field contains the unrecognized attribute (type, length and value). */ if (!CHECK_FLAG(flag, BGP_ATTR_FLAG_OPTIONAL)) { return bgp_attr_malformed(args, BGP_NOTIFY_UPDATE_UNREC_ATTR, args->total); } /* Unrecognized non-transitive optional attributes must be quietly ignored and not passed along to other BGP peers. */ if (!CHECK_FLAG(flag, BGP_ATTR_FLAG_TRANS)) return BGP_ATTR_PARSE_PROCEED; /* If a path with recognized transitive optional attribute is accepted and passed along to other BGP peers and the Partial bit in the Attribute Flags octet is set to 1 by some previous AS, it is not set back to 0 by the current AS. */ SET_FLAG(*startp, BGP_ATTR_FLAG_PARTIAL); /* Store transitive attribute to the end of attr->transit. */ if (!attr->transit) attr->transit = XCALLOC(MTYPE_TRANSIT, sizeof(struct transit)); transit = attr->transit; if (transit->val) transit->val = XREALLOC(MTYPE_TRANSIT_VAL, transit->val, transit->length + total); else transit->val = XMALLOC(MTYPE_TRANSIT_VAL, total); memcpy(transit->val + transit->length, startp, total); transit->length += total; return BGP_ATTR_PARSE_PROCEED; } /* Well-known attribute check. */ static int bgp_attr_check(struct peer *peer, struct attr *attr) { uint8_t type = 0; /* BGP Graceful-Restart End-of-RIB for IPv4 unicast is signaled as an * empty UPDATE. */ if (CHECK_FLAG(peer->cap, PEER_CAP_RESTART_RCV) && !attr->flag) return BGP_ATTR_PARSE_PROCEED; /* "An UPDATE message that contains the MP_UNREACH_NLRI is not required to carry any other path attributes.", though if MP_REACH_NLRI or NLRI are present, it should. Check for any other attribute being present instead. */ if ((!CHECK_FLAG(attr->flag, ATTR_FLAG_BIT(BGP_ATTR_MP_REACH_NLRI)) && CHECK_FLAG(attr->flag, ATTR_FLAG_BIT(BGP_ATTR_MP_UNREACH_NLRI)))) return BGP_ATTR_PARSE_PROCEED; if (!CHECK_FLAG(attr->flag, ATTR_FLAG_BIT(BGP_ATTR_ORIGIN))) type = BGP_ATTR_ORIGIN; if (!CHECK_FLAG(attr->flag, ATTR_FLAG_BIT(BGP_ATTR_AS_PATH))) type = BGP_ATTR_AS_PATH; /* RFC 2858 makes Next-Hop optional/ignored, if MP_REACH_NLRI is present * and * NLRI is empty. We can't easily check NLRI empty here though. */ if (!CHECK_FLAG(attr->flag, ATTR_FLAG_BIT(BGP_ATTR_NEXT_HOP)) && !CHECK_FLAG(attr->flag, ATTR_FLAG_BIT(BGP_ATTR_MP_REACH_NLRI))) type = BGP_ATTR_NEXT_HOP; if (peer->sort == BGP_PEER_IBGP && !CHECK_FLAG(attr->flag, ATTR_FLAG_BIT(BGP_ATTR_LOCAL_PREF))) type = BGP_ATTR_LOCAL_PREF; if (type) { flog_warn(EC_BGP_MISSING_ATTRIBUTE, "%s Missing well-known attribute %s.", peer->host, lookup_msg(attr_str, type, NULL)); bgp_notify_send_with_data(peer, BGP_NOTIFY_UPDATE_ERR, BGP_NOTIFY_UPDATE_MISS_ATTR, &type, 1); return BGP_ATTR_PARSE_ERROR; } return BGP_ATTR_PARSE_PROCEED; } /* Read attribute of update packet. This function is called from bgp_update_receive() in bgp_packet.c. */ bgp_attr_parse_ret_t bgp_attr_parse(struct peer *peer, struct attr *attr, bgp_size_t size, struct bgp_nlri *mp_update, struct bgp_nlri *mp_withdraw) { bgp_attr_parse_ret_t ret; uint8_t flag = 0; uint8_t type = 0; bgp_size_t length; uint8_t *startp, *endp; uint8_t *attr_endp; uint8_t seen[BGP_ATTR_BITMAP_SIZE]; /* we need the as4_path only until we have synthesized the as_path with * it */ /* same goes for as4_aggregator */ struct aspath *as4_path = NULL; as_t as4_aggregator = 0; struct in_addr as4_aggregator_addr = {.s_addr = 0}; /* Initialize bitmap. */ memset(seen, 0, BGP_ATTR_BITMAP_SIZE); /* End pointer of BGP attribute. */ endp = BGP_INPUT_PNT(peer) + size; /* Get attributes to the end of attribute length. */ while (BGP_INPUT_PNT(peer) < endp) { /* Check remaining length check.*/ if (endp - BGP_INPUT_PNT(peer) < BGP_ATTR_MIN_LEN) { /* XXX warning: long int format, int arg (arg 5) */ flog_warn( EC_BGP_ATTRIBUTE_TOO_SMALL, "%s: error BGP attribute length %lu is smaller than min len", peer->host, (unsigned long)(endp - stream_pnt(BGP_INPUT(peer)))); bgp_notify_send(peer, BGP_NOTIFY_UPDATE_ERR, BGP_NOTIFY_UPDATE_ATTR_LENG_ERR); ret = BGP_ATTR_PARSE_ERROR; goto done; } /* Fetch attribute flag and type. */ startp = BGP_INPUT_PNT(peer); /* "The lower-order four bits of the Attribute Flags octet are unused. They MUST be zero when sent and MUST be ignored when received." */ flag = 0xF0 & stream_getc(BGP_INPUT(peer)); type = stream_getc(BGP_INPUT(peer)); /* Check whether Extended-Length applies and is in bounds */ if (CHECK_FLAG(flag, BGP_ATTR_FLAG_EXTLEN) && ((endp - startp) < (BGP_ATTR_MIN_LEN + 1))) { flog_warn( EC_BGP_EXT_ATTRIBUTE_TOO_SMALL, "%s: Extended length set, but just %lu bytes of attr header", peer->host, (unsigned long)(endp - stream_pnt(BGP_INPUT(peer)))); bgp_notify_send(peer, BGP_NOTIFY_UPDATE_ERR, BGP_NOTIFY_UPDATE_ATTR_LENG_ERR); ret = BGP_ATTR_PARSE_ERROR; goto done; } /* Check extended attribue length bit. */ if (CHECK_FLAG(flag, BGP_ATTR_FLAG_EXTLEN)) length = stream_getw(BGP_INPUT(peer)); else length = stream_getc(BGP_INPUT(peer)); /* If any attribute appears more than once in the UPDATE message, then the Error Subcode is set to Malformed Attribute List. */ if (CHECK_BITMAP(seen, type)) { flog_warn( EC_BGP_ATTRIBUTE_REPEATED, "%s: error BGP attribute type %d appears twice in a message", peer->host, type); bgp_notify_send(peer, BGP_NOTIFY_UPDATE_ERR, BGP_NOTIFY_UPDATE_MAL_ATTR); ret = BGP_ATTR_PARSE_ERROR; goto done; } /* Set type to bitmap to check duplicate attribute. `type' is unsigned char so it never overflow bitmap range. */ SET_BITMAP(seen, type); /* Overflow check. */ attr_endp = BGP_INPUT_PNT(peer) + length; if (attr_endp > endp) { flog_warn( EC_BGP_ATTRIBUTE_TOO_LARGE, "%s: BGP type %d length %d is too large, attribute total length is %d. attr_endp is %p. endp is %p", peer->host, type, length, size, attr_endp, endp); /* * RFC 4271 6.3 * If any recognized attribute has an Attribute * Length that conflicts with the expected length * (based on the attribute type code), then the * Error Subcode MUST be set to Attribute Length * Error. The Data field MUST contain the erroneous * attribute (type, length, and value). * ---------- * We do not currently have a good way to determine the * length of the attribute independent of the length * received in the message. Instead we send the * minimum between the amount of data we have and the * amount specified by the attribute length field. * * Instead of directly passing in the packet buffer and * offset we use the stream_get* functions to read into * a stack buffer, since they perform bounds checking * and we are working with untrusted data. */ unsigned char ndata[BGP_MAX_PACKET_SIZE]; memset(ndata, 0x00, sizeof(ndata)); size_t lfl = CHECK_FLAG(flag, BGP_ATTR_FLAG_EXTLEN) ? 2 : 1; /* Rewind to end of flag field */ stream_forward_getp(BGP_INPUT(peer), -(1 + lfl)); /* Type */ stream_get(&ndata[0], BGP_INPUT(peer), 1); /* Length */ stream_get(&ndata[1], BGP_INPUT(peer), lfl); /* Value */ size_t atl = attr_endp - startp; size_t ndl = MIN(atl, STREAM_READABLE(BGP_INPUT(peer))); stream_get(&ndata[lfl + 1], BGP_INPUT(peer), ndl); bgp_notify_send_with_data( peer, BGP_NOTIFY_UPDATE_ERR, BGP_NOTIFY_UPDATE_ATTR_LENG_ERR, ndata, ndl + lfl + 1); ret = BGP_ATTR_PARSE_ERROR; goto done; } struct bgp_attr_parser_args attr_args = { .peer = peer, .length = length, .attr = attr, .type = type, .flags = flag, .startp = startp, .total = attr_endp - startp, }; /* If any recognized attribute has Attribute Flags that conflict with the Attribute Type Code, then the Error Subcode is set to Attribute Flags Error. The Data field contains the erroneous attribute (type, length and value). */ if (bgp_attr_flag_invalid(&attr_args)) { ret = bgp_attr_malformed( &attr_args, BGP_NOTIFY_UPDATE_ATTR_FLAG_ERR, attr_args.total); if (ret == BGP_ATTR_PARSE_PROCEED) continue; goto done; } /* OK check attribute and store it's value. */ switch (type) { case BGP_ATTR_ORIGIN: ret = bgp_attr_origin(&attr_args); break; case BGP_ATTR_AS_PATH: ret = bgp_attr_aspath(&attr_args); break; case BGP_ATTR_AS4_PATH: ret = bgp_attr_as4_path(&attr_args, &as4_path); break; case BGP_ATTR_NEXT_HOP: ret = bgp_attr_nexthop(&attr_args); break; case BGP_ATTR_MULTI_EXIT_DISC: ret = bgp_attr_med(&attr_args); break; case BGP_ATTR_LOCAL_PREF: ret = bgp_attr_local_pref(&attr_args); break; case BGP_ATTR_ATOMIC_AGGREGATE: ret = bgp_attr_atomic(&attr_args); break; case BGP_ATTR_AGGREGATOR: ret = bgp_attr_aggregator(&attr_args); break; case BGP_ATTR_AS4_AGGREGATOR: ret = bgp_attr_as4_aggregator(&attr_args, &as4_aggregator, &as4_aggregator_addr); break; case BGP_ATTR_COMMUNITIES: ret = bgp_attr_community(&attr_args); break; case BGP_ATTR_LARGE_COMMUNITIES: ret = bgp_attr_large_community(&attr_args); break; case BGP_ATTR_ORIGINATOR_ID: ret = bgp_attr_originator_id(&attr_args); break; case BGP_ATTR_CLUSTER_LIST: ret = bgp_attr_cluster_list(&attr_args); break; case BGP_ATTR_MP_REACH_NLRI: ret = bgp_mp_reach_parse(&attr_args, mp_update); break; case BGP_ATTR_MP_UNREACH_NLRI: ret = bgp_mp_unreach_parse(&attr_args, mp_withdraw); break; case BGP_ATTR_EXT_COMMUNITIES: ret = bgp_attr_ext_communities(&attr_args); break; #if ENABLE_BGP_VNC_ATTR case BGP_ATTR_VNC: #endif case BGP_ATTR_ENCAP: ret = bgp_attr_encap(type, peer, length, attr, flag, startp); break; case BGP_ATTR_PREFIX_SID: ret = bgp_attr_prefix_sid(length, &attr_args, mp_update); break; case BGP_ATTR_PMSI_TUNNEL: ret = bgp_attr_pmsi_tunnel(&attr_args); break; default: ret = bgp_attr_unknown(&attr_args); break; } if (ret == BGP_ATTR_PARSE_ERROR_NOTIFYPLS) { bgp_notify_send(peer, BGP_NOTIFY_UPDATE_ERR, BGP_NOTIFY_UPDATE_MAL_ATTR); ret = BGP_ATTR_PARSE_ERROR; goto done; } if (ret == BGP_ATTR_PARSE_EOR) { goto done; } if (ret == BGP_ATTR_PARSE_ERROR) { flog_warn(EC_BGP_ATTRIBUTE_PARSE_ERROR, "%s: Attribute %s, parse error", peer->host, lookup_msg(attr_str, type, NULL)); goto done; } if (ret == BGP_ATTR_PARSE_WITHDRAW) { flog_warn( EC_BGP_ATTRIBUTE_PARSE_WITHDRAW, "%s: Attribute %s, parse error - treating as withdrawal", peer->host, lookup_msg(attr_str, type, NULL)); goto done; } /* Check the fetched length. */ if (BGP_INPUT_PNT(peer) != attr_endp) { flog_warn(EC_BGP_ATTRIBUTE_FETCH_ERROR, "%s: BGP attribute %s, fetch error", peer->host, lookup_msg(attr_str, type, NULL)); bgp_notify_send(peer, BGP_NOTIFY_UPDATE_ERR, BGP_NOTIFY_UPDATE_ATTR_LENG_ERR); ret = BGP_ATTR_PARSE_ERROR; goto done; } } /* Check final read pointer is same as end pointer. */ if (BGP_INPUT_PNT(peer) != endp) { flog_warn(EC_BGP_ATTRIBUTES_MISMATCH, "%s: BGP attribute %s, length mismatch", peer->host, lookup_msg(attr_str, type, NULL)); bgp_notify_send(peer, BGP_NOTIFY_UPDATE_ERR, BGP_NOTIFY_UPDATE_ATTR_LENG_ERR); ret = BGP_ATTR_PARSE_ERROR; goto done; } /* * RFC4271: If the NEXT_HOP attribute field is syntactically incorrect, * then the Error Subcode MUST be set to Invalid NEXT_HOP Attribute. * This is implemented below and will result in a NOTIFICATION. If the * NEXT_HOP attribute is semantically incorrect, the error SHOULD be * logged, and the route SHOULD be ignored. In this case, a NOTIFICATION * message SHOULD NOT be sent. This is implemented elsewhere. * * RFC4760: An UPDATE message that carries no NLRI, other than the one * encoded in the MP_REACH_NLRI attribute, SHOULD NOT carry the NEXT_HOP * attribute. If such a message contains the NEXT_HOP attribute, the BGP * speaker that receives the message SHOULD ignore this attribute. */ if (CHECK_FLAG(attr->flag, ATTR_FLAG_BIT(BGP_ATTR_NEXT_HOP)) && !CHECK_FLAG(attr->flag, ATTR_FLAG_BIT(BGP_ATTR_MP_REACH_NLRI))) { if (bgp_attr_nexthop_valid(peer, attr) < 0) { ret = BGP_ATTR_PARSE_ERROR; goto done; } } /* Check all mandatory well-known attributes are present */ if ((ret = bgp_attr_check(peer, attr)) < 0) goto done; /* * At this place we can see whether we got AS4_PATH and/or * AS4_AGGREGATOR from a 16Bit peer and act accordingly. * We can not do this before we've read all attributes because * the as4 handling does not say whether AS4_PATH has to be sent * after AS_PATH or not - and when AS4_AGGREGATOR will be send * in relationship to AGGREGATOR. * So, to be defensive, we are not relying on any order and read * all attributes first, including these 32bit ones, and now, * afterwards, we look what and if something is to be done for as4. * * It is possible to not have AS_PATH, e.g. GR EoR and sole * MP_UNREACH_NLRI. */ /* actually... this doesn't ever return failure currently, but * better safe than sorry */ if (CHECK_FLAG(attr->flag, ATTR_FLAG_BIT(BGP_ATTR_AS_PATH)) && bgp_attr_munge_as4_attrs(peer, attr, as4_path, as4_aggregator, &as4_aggregator_addr)) { bgp_notify_send(peer, BGP_NOTIFY_UPDATE_ERR, BGP_NOTIFY_UPDATE_MAL_ATTR); ret = BGP_ATTR_PARSE_ERROR; goto done; } /* * Finally do the checks on the aspath we did not do yet * because we waited for a potentially synthesized aspath. */ if (attr->flag & (ATTR_FLAG_BIT(BGP_ATTR_AS_PATH))) { ret = bgp_attr_aspath_check(peer, attr); if (ret != BGP_ATTR_PARSE_PROCEED) goto done; } ret = BGP_ATTR_PARSE_PROCEED; done: /* * At this stage, we have done all fiddling with as4, and the * resulting info is in attr->aggregator resp. attr->aspath so * we can chuck as4_aggregator and as4_path alltogether in order * to save memory */ if (as4_path) { /* * unintern - it is in the hash * The flag that we got this is still there, but that * does not do any trouble */ aspath_unintern(&as4_path); } if (ret != BGP_ATTR_PARSE_ERROR) { /* Finally intern unknown attribute. */ if (attr->transit) attr->transit = transit_intern(attr->transit); if (attr->encap_subtlvs) attr->encap_subtlvs = encap_intern(attr->encap_subtlvs, ENCAP_SUBTLV_TYPE); #if ENABLE_BGP_VNC if (attr->vnc_subtlvs) attr->vnc_subtlvs = encap_intern(attr->vnc_subtlvs, VNC_SUBTLV_TYPE); #endif } else { if (attr->transit) { transit_free(attr->transit); attr->transit = NULL; } bgp_attr_flush_encap(attr); }; /* Sanity checks */ if (attr->transit) assert(attr->transit->refcnt > 0); if (attr->encap_subtlvs) assert(attr->encap_subtlvs->refcnt > 0); #if ENABLE_BGP_VNC if (attr->vnc_subtlvs) assert(attr->vnc_subtlvs->refcnt > 0); #endif return ret; } /* * Extract the tunnel type from extended community */ void bgp_attr_extcom_tunnel_type(struct attr *attr, bgp_encap_types *tunnel_type) { struct ecommunity *ecom; int i; if (!attr) return; ecom = attr->ecommunity; if (!ecom || !ecom->size) return; for (i = 0; i < ecom->size; i++) { uint8_t *pnt; uint8_t type, sub_type; pnt = (ecom->val + (i * ECOMMUNITY_SIZE)); type = pnt[0]; sub_type = pnt[1]; if (!(type == ECOMMUNITY_ENCODE_OPAQUE && sub_type == ECOMMUNITY_OPAQUE_SUBTYPE_ENCAP)) continue; *tunnel_type = ((pnt[6] << 8) | pnt[7]); return; } return; } size_t bgp_packet_mpattr_start(struct stream *s, struct peer *peer, afi_t afi, safi_t safi, struct bpacket_attr_vec_arr *vecarr, struct attr *attr) { size_t sizep; iana_afi_t pkt_afi; iana_safi_t pkt_safi; afi_t nh_afi; /* Set extended bit always to encode the attribute length as 2 bytes */ stream_putc(s, BGP_ATTR_FLAG_OPTIONAL | BGP_ATTR_FLAG_EXTLEN); stream_putc(s, BGP_ATTR_MP_REACH_NLRI); sizep = stream_get_endp(s); stream_putw(s, 0); /* Marker: Attribute length. */ /* Convert AFI, SAFI to values for packet. */ bgp_map_afi_safi_int2iana(afi, safi, &pkt_afi, &pkt_safi); stream_putw(s, pkt_afi); /* AFI */ stream_putc(s, pkt_safi); /* SAFI */ /* Nexthop AFI */ if (afi == AFI_IP && (safi == SAFI_UNICAST || safi == SAFI_LABELED_UNICAST)) nh_afi = peer_cap_enhe(peer, afi, safi) ? AFI_IP6 : AFI_IP; else nh_afi = BGP_NEXTHOP_AFI_FROM_NHLEN(attr->mp_nexthop_len); /* Nexthop */ bpacket_attr_vec_arr_set_vec(vecarr, BGP_ATTR_VEC_NH, s, attr); switch (nh_afi) { case AFI_IP: switch (safi) { case SAFI_UNICAST: case SAFI_MULTICAST: case SAFI_LABELED_UNICAST: stream_putc(s, 4); stream_put_ipv4(s, attr->nexthop.s_addr); break; case SAFI_MPLS_VPN: stream_putc(s, 12); stream_putl(s, 0); /* RD = 0, per RFC */ stream_putl(s, 0); stream_put(s, &attr->mp_nexthop_global_in, 4); break; case SAFI_ENCAP: case SAFI_EVPN: stream_putc(s, 4); stream_put(s, &attr->mp_nexthop_global_in, 4); break; case SAFI_FLOWSPEC: stream_putc(s, 0); /* no nexthop for flowspec */ default: break; } break; case AFI_IP6: switch (safi) { case SAFI_UNICAST: case SAFI_MULTICAST: case SAFI_LABELED_UNICAST: case SAFI_EVPN: { if (attr->mp_nexthop_len == BGP_ATTR_NHLEN_IPV6_GLOBAL_AND_LL) { stream_putc(s, BGP_ATTR_NHLEN_IPV6_GLOBAL_AND_LL); stream_put(s, &attr->mp_nexthop_global, IPV6_MAX_BYTELEN); stream_put(s, &attr->mp_nexthop_local, IPV6_MAX_BYTELEN); } else { stream_putc(s, IPV6_MAX_BYTELEN); stream_put(s, &attr->mp_nexthop_global, IPV6_MAX_BYTELEN); } } break; case SAFI_MPLS_VPN: { if (attr->mp_nexthop_len == BGP_ATTR_NHLEN_IPV6_GLOBAL) { stream_putc(s, 24); stream_putl(s, 0); /* RD = 0, per RFC */ stream_putl(s, 0); stream_put(s, &attr->mp_nexthop_global, IPV6_MAX_BYTELEN); } else if (attr->mp_nexthop_len == BGP_ATTR_NHLEN_IPV6_GLOBAL_AND_LL) { stream_putc(s, 48); stream_putl(s, 0); /* RD = 0, per RFC */ stream_putl(s, 0); stream_put(s, &attr->mp_nexthop_global, IPV6_MAX_BYTELEN); stream_putl(s, 0); /* RD = 0, per RFC */ stream_putl(s, 0); stream_put(s, &attr->mp_nexthop_local, IPV6_MAX_BYTELEN); } } break; case SAFI_ENCAP: stream_putc(s, IPV6_MAX_BYTELEN); stream_put(s, &attr->mp_nexthop_global, IPV6_MAX_BYTELEN); break; case SAFI_FLOWSPEC: stream_putc(s, 0); /* no nexthop for flowspec */ default: break; } break; default: if (safi != SAFI_FLOWSPEC) flog_err( EC_BGP_ATTR_NH_SEND_LEN, "Bad nexthop when sending to %s, AFI %u SAFI %u nhlen %d", peer->host, afi, safi, attr->mp_nexthop_len); break; } /* SNPA */ stream_putc(s, 0); return sizep; } void bgp_packet_mpattr_prefix(struct stream *s, afi_t afi, safi_t safi, struct prefix *p, struct prefix_rd *prd, mpls_label_t *label, uint32_t num_labels, int addpath_encode, uint32_t addpath_tx_id, struct attr *attr) { if (safi == SAFI_MPLS_VPN) { if (addpath_encode) stream_putl(s, addpath_tx_id); /* Label, RD, Prefix write. */ stream_putc(s, p->prefixlen + 88); stream_put(s, label, BGP_LABEL_BYTES); stream_put(s, prd->val, 8); stream_put(s, &p->u.prefix, PSIZE(p->prefixlen)); } else if (afi == AFI_L2VPN && safi == SAFI_EVPN) { /* EVPN prefix - contents depend on type */ bgp_evpn_encode_prefix(s, p, prd, label, num_labels, attr, addpath_encode, addpath_tx_id); } else if (safi == SAFI_LABELED_UNICAST) { /* Prefix write with label. */ stream_put_labeled_prefix(s, p, label, addpath_encode, addpath_tx_id); } else if (safi == SAFI_FLOWSPEC) { if (PSIZE (p->prefixlen)+2 < FLOWSPEC_NLRI_SIZELIMIT) stream_putc(s, PSIZE (p->prefixlen)+2); else stream_putw(s, (PSIZE (p->prefixlen)+2)|(0xf<<12)); stream_putc(s, 2);/* Filter type */ stream_putc(s, p->prefixlen);/* Prefix length */ stream_put(s, &p->u.prefix, PSIZE (p->prefixlen)); } else stream_put_prefix_addpath(s, p, addpath_encode, addpath_tx_id); } size_t bgp_packet_mpattr_prefix_size(afi_t afi, safi_t safi, struct prefix *p) { int size = PSIZE(p->prefixlen); if (safi == SAFI_MPLS_VPN) size += 88; else if (safi == SAFI_LABELED_UNICAST) size += BGP_LABEL_BYTES; else if (afi == AFI_L2VPN && safi == SAFI_EVPN) size += 232; // TODO: Maximum possible for type-2, type-3 and // type-5 return size; } /* * Encodes the tunnel encapsulation attribute, * and with ENABLE_BGP_VNC the VNC attribute which uses * almost the same TLV format */ static void bgp_packet_mpattr_tea(struct bgp *bgp, struct peer *peer, struct stream *s, struct attr *attr, uint8_t attrtype) { unsigned int attrlenfield = 0; unsigned int attrhdrlen = 0; struct bgp_attr_encap_subtlv *subtlvs; struct bgp_attr_encap_subtlv *st; const char *attrname; if (!attr || (attrtype == BGP_ATTR_ENCAP && (!attr->encap_tunneltype || attr->encap_tunneltype == BGP_ENCAP_TYPE_MPLS))) return; switch (attrtype) { case BGP_ATTR_ENCAP: attrname = "Tunnel Encap"; subtlvs = attr->encap_subtlvs; if (subtlvs == NULL) /* nothing to do */ return; /* * The tunnel encap attr has an "outer" tlv. * T = tunneltype, * L = total length of subtlvs, * V = concatenated subtlvs. */ attrlenfield = 2 + 2; /* T + L */ attrhdrlen = 1 + 1; /* subTLV T + L */ break; #if ENABLE_BGP_VNC_ATTR case BGP_ATTR_VNC: attrname = "VNC"; subtlvs = attr->vnc_subtlvs; if (subtlvs == NULL) /* nothing to do */ return; attrlenfield = 0; /* no outer T + L */ attrhdrlen = 2 + 2; /* subTLV T + L */ break; #endif default: assert(0); } /* compute attr length */ for (st = subtlvs; st; st = st->next) { attrlenfield += (attrhdrlen + st->length); } if (attrlenfield > 0xffff) { zlog_info("%s attribute is too long (length=%d), can't send it", attrname, attrlenfield); return; } if (attrlenfield > 0xff) { /* 2-octet length field */ stream_putc(s, BGP_ATTR_FLAG_TRANS | BGP_ATTR_FLAG_OPTIONAL | BGP_ATTR_FLAG_EXTLEN); stream_putc(s, attrtype); stream_putw(s, attrlenfield & 0xffff); } else { /* 1-octet length field */ stream_putc(s, BGP_ATTR_FLAG_TRANS | BGP_ATTR_FLAG_OPTIONAL); stream_putc(s, attrtype); stream_putc(s, attrlenfield & 0xff); } if (attrtype == BGP_ATTR_ENCAP) { /* write outer T+L */ stream_putw(s, attr->encap_tunneltype); stream_putw(s, attrlenfield - 4); } /* write each sub-tlv */ for (st = subtlvs; st; st = st->next) { if (attrtype == BGP_ATTR_ENCAP) { stream_putc(s, st->type); stream_putc(s, st->length); #if ENABLE_BGP_VNC } else { stream_putw(s, st->type); stream_putw(s, st->length); #endif } stream_put(s, st->value, st->length); } } void bgp_packet_mpattr_end(struct stream *s, size_t sizep) { /* Set MP attribute length. Don't count the (2) bytes used to encode the attr length */ stream_putw_at(s, sizep, (stream_get_endp(s) - sizep) - 2); } static int bgp_append_local_as(struct peer *peer, afi_t afi, safi_t safi) { if (!BGP_AS_IS_PRIVATE(peer->local_as) || (BGP_AS_IS_PRIVATE(peer->local_as) && !CHECK_FLAG(peer->af_flags[afi][safi], PEER_FLAG_REMOVE_PRIVATE_AS) && !CHECK_FLAG(peer->af_flags[afi][safi], PEER_FLAG_REMOVE_PRIVATE_AS_ALL) && !CHECK_FLAG(peer->af_flags[afi][safi], PEER_FLAG_REMOVE_PRIVATE_AS_REPLACE) && !CHECK_FLAG(peer->af_flags[afi][safi], PEER_FLAG_REMOVE_PRIVATE_AS_ALL_REPLACE))) return 1; return 0; } /* Make attribute packet. */ bgp_size_t bgp_packet_attribute(struct bgp *bgp, struct peer *peer, struct stream *s, struct attr *attr, struct bpacket_attr_vec_arr *vecarr, struct prefix *p, afi_t afi, safi_t safi, struct peer *from, struct prefix_rd *prd, mpls_label_t *label, uint32_t num_labels, int addpath_encode, uint32_t addpath_tx_id) { size_t cp; size_t aspath_sizep; struct aspath *aspath; int send_as4_path = 0; int send_as4_aggregator = 0; int use32bit = (CHECK_FLAG(peer->cap, PEER_CAP_AS4_RCV)) ? 1 : 0; if (!bgp) bgp = peer->bgp; /* Remember current pointer. */ cp = stream_get_endp(s); if (p && !((afi == AFI_IP && safi == SAFI_UNICAST) && !peer_cap_enhe(peer, afi, safi))) { size_t mpattrlen_pos = 0; mpattrlen_pos = bgp_packet_mpattr_start(s, peer, afi, safi, vecarr, attr); bgp_packet_mpattr_prefix(s, afi, safi, p, prd, label, num_labels, addpath_encode, addpath_tx_id, attr); bgp_packet_mpattr_end(s, mpattrlen_pos); } /* Origin attribute. */ stream_putc(s, BGP_ATTR_FLAG_TRANS); stream_putc(s, BGP_ATTR_ORIGIN); stream_putc(s, 1); stream_putc(s, attr->origin); /* AS path attribute. */ /* If remote-peer is EBGP */ if (peer->sort == BGP_PEER_EBGP && (!CHECK_FLAG(peer->af_flags[afi][safi], PEER_FLAG_AS_PATH_UNCHANGED) || attr->aspath->segments == NULL) && (!CHECK_FLAG(peer->af_flags[afi][safi], PEER_FLAG_RSERVER_CLIENT))) { aspath = aspath_dup(attr->aspath); /* Even though we may not be configured for confederations we * may have * RXed an AS_PATH with AS_CONFED_SEQUENCE or AS_CONFED_SET */ aspath = aspath_delete_confed_seq(aspath); if (CHECK_FLAG(bgp->config, BGP_CONFIG_CONFEDERATION)) { /* Stuff our path CONFED_ID on the front */ aspath = aspath_add_seq(aspath, bgp->confed_id); } else { if (peer->change_local_as) { /* If replace-as is specified, we only use the change_local_as when advertising routes. */ if (!CHECK_FLAG(peer->flags, PEER_FLAG_LOCAL_AS_REPLACE_AS)) if (bgp_append_local_as(peer, afi, safi)) aspath = aspath_add_seq( aspath, peer->local_as); aspath = aspath_add_seq(aspath, peer->change_local_as); } else { aspath = aspath_add_seq(aspath, peer->local_as); } } } else if (peer->sort == BGP_PEER_CONFED) { /* A confed member, so we need to do the AS_CONFED_SEQUENCE * thing */ aspath = aspath_dup(attr->aspath); aspath = aspath_add_confed_seq(aspath, peer->local_as); } else aspath = attr->aspath; /* If peer is not AS4 capable, then: * - send the created AS_PATH out as AS4_PATH (optional, transitive), * but ensure that no AS_CONFED_SEQUENCE and AS_CONFED_SET path * segment * types are in it (i.e. exclude them if they are there) * AND do this only if there is at least one asnum > 65535 in the * path! * - send an AS_PATH out, but put 16Bit ASnums in it, not 32bit, and * change * all ASnums > 65535 to BGP_AS_TRANS */ stream_putc(s, BGP_ATTR_FLAG_TRANS | BGP_ATTR_FLAG_EXTLEN); stream_putc(s, BGP_ATTR_AS_PATH); aspath_sizep = stream_get_endp(s); stream_putw(s, 0); stream_putw_at(s, aspath_sizep, aspath_put(s, aspath, use32bit)); /* OLD session may need NEW_AS_PATH sent, if there are 4-byte ASNs * in the path */ if (!use32bit && aspath_has_as4(aspath)) send_as4_path = 1; /* we'll do this later, at the correct place */ /* Nexthop attribute. */ if (afi == AFI_IP && safi == SAFI_UNICAST && !peer_cap_enhe(peer, afi, safi)) { afi_t nh_afi = BGP_NEXTHOP_AFI_FROM_NHLEN(attr->mp_nexthop_len); if (attr->flag & ATTR_FLAG_BIT(BGP_ATTR_NEXT_HOP)) { stream_putc(s, BGP_ATTR_FLAG_TRANS); stream_putc(s, BGP_ATTR_NEXT_HOP); bpacket_attr_vec_arr_set_vec(vecarr, BGP_ATTR_VEC_NH, s, attr); stream_putc(s, 4); stream_put_ipv4(s, attr->nexthop.s_addr); } else if (peer_cap_enhe(from, afi, safi) || (nh_afi == AFI_IP6)) { /* * Likely this is the case when an IPv4 prefix was * received with Extended Next-hop capability in this * or another vrf and is now being advertised to * non-ENHE peers. Since peer_cap_enhe only checks * peers in this vrf, also check the nh_afi to catch * the case where the originator was in another vrf. * Setting the mandatory (ipv4) next-hop attribute here * to enable implicit next-hop self with correct A-F * (ipv4 address family). */ stream_putc(s, BGP_ATTR_FLAG_TRANS); stream_putc(s, BGP_ATTR_NEXT_HOP); bpacket_attr_vec_arr_set_vec(vecarr, BGP_ATTR_VEC_NH, s, NULL); stream_putc(s, 4); stream_put_ipv4(s, 0); } } /* MED attribute. */ if (attr->flag & ATTR_FLAG_BIT(BGP_ATTR_MULTI_EXIT_DISC) || bgp->maxmed_active) { stream_putc(s, BGP_ATTR_FLAG_OPTIONAL); stream_putc(s, BGP_ATTR_MULTI_EXIT_DISC); stream_putc(s, 4); stream_putl(s, (bgp->maxmed_active ? bgp->maxmed_value : attr->med)); } /* Local preference. */ if (peer->sort == BGP_PEER_IBGP || peer->sort == BGP_PEER_CONFED) { stream_putc(s, BGP_ATTR_FLAG_TRANS); stream_putc(s, BGP_ATTR_LOCAL_PREF); stream_putc(s, 4); stream_putl(s, attr->local_pref); } /* Atomic aggregate. */ if (attr->flag & ATTR_FLAG_BIT(BGP_ATTR_ATOMIC_AGGREGATE)) { stream_putc(s, BGP_ATTR_FLAG_TRANS); stream_putc(s, BGP_ATTR_ATOMIC_AGGREGATE); stream_putc(s, 0); } /* Aggregator. */ if (attr->flag & ATTR_FLAG_BIT(BGP_ATTR_AGGREGATOR)) { /* Common to BGP_ATTR_AGGREGATOR, regardless of ASN size */ stream_putc(s, BGP_ATTR_FLAG_OPTIONAL | BGP_ATTR_FLAG_TRANS); stream_putc(s, BGP_ATTR_AGGREGATOR); if (use32bit) { /* AS4 capable peer */ stream_putc(s, 8); stream_putl(s, attr->aggregator_as); } else { /* 2-byte AS peer */ stream_putc(s, 6); /* Is ASN representable in 2-bytes? Or must AS_TRANS be * used? */ if (attr->aggregator_as > 65535) { stream_putw(s, BGP_AS_TRANS); /* we have to send AS4_AGGREGATOR, too. * we'll do that later in order to send * attributes in ascending * order. */ send_as4_aggregator = 1; } else stream_putw(s, (uint16_t)attr->aggregator_as); } stream_put_ipv4(s, attr->aggregator_addr.s_addr); } /* Community attribute. */ if (CHECK_FLAG(peer->af_flags[afi][safi], PEER_FLAG_SEND_COMMUNITY) && (attr->flag & ATTR_FLAG_BIT(BGP_ATTR_COMMUNITIES))) { if (attr->community->size * 4 > 255) { stream_putc(s, BGP_ATTR_FLAG_OPTIONAL | BGP_ATTR_FLAG_TRANS | BGP_ATTR_FLAG_EXTLEN); stream_putc(s, BGP_ATTR_COMMUNITIES); stream_putw(s, attr->community->size * 4); } else { stream_putc(s, BGP_ATTR_FLAG_OPTIONAL | BGP_ATTR_FLAG_TRANS); stream_putc(s, BGP_ATTR_COMMUNITIES); stream_putc(s, attr->community->size * 4); } stream_put(s, attr->community->val, attr->community->size * 4); } /* * Large Community attribute. */ if (CHECK_FLAG(peer->af_flags[afi][safi], PEER_FLAG_SEND_LARGE_COMMUNITY) && (attr->flag & ATTR_FLAG_BIT(BGP_ATTR_LARGE_COMMUNITIES))) { if (lcom_length(attr->lcommunity) > 255) { stream_putc(s, BGP_ATTR_FLAG_OPTIONAL | BGP_ATTR_FLAG_TRANS | BGP_ATTR_FLAG_EXTLEN); stream_putc(s, BGP_ATTR_LARGE_COMMUNITIES); stream_putw(s, lcom_length(attr->lcommunity)); } else { stream_putc(s, BGP_ATTR_FLAG_OPTIONAL | BGP_ATTR_FLAG_TRANS); stream_putc(s, BGP_ATTR_LARGE_COMMUNITIES); stream_putc(s, lcom_length(attr->lcommunity)); } stream_put(s, attr->lcommunity->val, lcom_length(attr->lcommunity)); } /* Route Reflector. */ if (peer->sort == BGP_PEER_IBGP && from && from->sort == BGP_PEER_IBGP) { /* Originator ID. */ stream_putc(s, BGP_ATTR_FLAG_OPTIONAL); stream_putc(s, BGP_ATTR_ORIGINATOR_ID); stream_putc(s, 4); if (attr->flag & ATTR_FLAG_BIT(BGP_ATTR_ORIGINATOR_ID)) stream_put_in_addr(s, &attr->originator_id); else stream_put_in_addr(s, &from->remote_id); /* Cluster list. */ stream_putc(s, BGP_ATTR_FLAG_OPTIONAL); stream_putc(s, BGP_ATTR_CLUSTER_LIST); if (attr->cluster) { stream_putc(s, attr->cluster->length + 4); /* If this peer configuration's parent BGP has * cluster_id. */ if (bgp->config & BGP_CONFIG_CLUSTER_ID) stream_put_in_addr(s, &bgp->cluster_id); else stream_put_in_addr(s, &bgp->router_id); stream_put(s, attr->cluster->list, attr->cluster->length); } else { stream_putc(s, 4); /* If this peer configuration's parent BGP has * cluster_id. */ if (bgp->config & BGP_CONFIG_CLUSTER_ID) stream_put_in_addr(s, &bgp->cluster_id); else stream_put_in_addr(s, &bgp->router_id); } } /* Extended Communities attribute. */ if (CHECK_FLAG(peer->af_flags[afi][safi], PEER_FLAG_SEND_EXT_COMMUNITY) && (attr->flag & ATTR_FLAG_BIT(BGP_ATTR_EXT_COMMUNITIES))) { if (peer->sort == BGP_PEER_IBGP || peer->sort == BGP_PEER_CONFED) { if (attr->ecommunity->size * 8 > 255) { stream_putc(s, BGP_ATTR_FLAG_OPTIONAL | BGP_ATTR_FLAG_TRANS | BGP_ATTR_FLAG_EXTLEN); stream_putc(s, BGP_ATTR_EXT_COMMUNITIES); stream_putw(s, attr->ecommunity->size * 8); } else { stream_putc(s, BGP_ATTR_FLAG_OPTIONAL | BGP_ATTR_FLAG_TRANS); stream_putc(s, BGP_ATTR_EXT_COMMUNITIES); stream_putc(s, attr->ecommunity->size * 8); } stream_put(s, attr->ecommunity->val, attr->ecommunity->size * 8); } else { uint8_t *pnt; int tbit; int ecom_tr_size = 0; int i; for (i = 0; i < attr->ecommunity->size; i++) { pnt = attr->ecommunity->val + (i * 8); tbit = *pnt; if (CHECK_FLAG(tbit, ECOMMUNITY_FLAG_NON_TRANSITIVE)) continue; ecom_tr_size++; } if (ecom_tr_size) { if (ecom_tr_size * 8 > 255) { stream_putc( s, BGP_ATTR_FLAG_OPTIONAL | BGP_ATTR_FLAG_TRANS | BGP_ATTR_FLAG_EXTLEN); stream_putc(s, BGP_ATTR_EXT_COMMUNITIES); stream_putw(s, ecom_tr_size * 8); } else { stream_putc( s, BGP_ATTR_FLAG_OPTIONAL | BGP_ATTR_FLAG_TRANS); stream_putc(s, BGP_ATTR_EXT_COMMUNITIES); stream_putc(s, ecom_tr_size * 8); } for (i = 0; i < attr->ecommunity->size; i++) { pnt = attr->ecommunity->val + (i * 8); tbit = *pnt; if (CHECK_FLAG( tbit, ECOMMUNITY_FLAG_NON_TRANSITIVE)) continue; stream_put(s, pnt, 8); } } } } /* Label index attribute. */ if (safi == SAFI_LABELED_UNICAST) { if (attr->flag & ATTR_FLAG_BIT(BGP_ATTR_PREFIX_SID)) { uint32_t label_index; label_index = attr->label_index; if (label_index != BGP_INVALID_LABEL_INDEX) { stream_putc(s, BGP_ATTR_FLAG_OPTIONAL | BGP_ATTR_FLAG_TRANS); stream_putc(s, BGP_ATTR_PREFIX_SID); stream_putc(s, 10); stream_putc(s, BGP_PREFIX_SID_LABEL_INDEX); stream_putw(s, BGP_PREFIX_SID_LABEL_INDEX_LENGTH); stream_putc(s, 0); // reserved stream_putw(s, 0); // flags stream_putl(s, label_index); } } } if (send_as4_path) { /* If the peer is NOT As4 capable, AND */ /* there are ASnums > 65535 in path THEN * give out AS4_PATH */ /* Get rid of all AS_CONFED_SEQUENCE and AS_CONFED_SET * path segments! * Hm, I wonder... confederation things *should* only be at * the beginning of an aspath, right? Then we should use * aspath_delete_confed_seq for this, because it is already * there! (JK) * Folks, talk to me: what is reasonable here!? */ aspath = aspath_delete_confed_seq(aspath); stream_putc(s, BGP_ATTR_FLAG_TRANS | BGP_ATTR_FLAG_OPTIONAL | BGP_ATTR_FLAG_EXTLEN); stream_putc(s, BGP_ATTR_AS4_PATH); aspath_sizep = stream_get_endp(s); stream_putw(s, 0); stream_putw_at(s, aspath_sizep, aspath_put(s, aspath, 1)); } if (aspath != attr->aspath) aspath_free(aspath); if (send_as4_aggregator) { /* send AS4_AGGREGATOR, at this place */ /* this section of code moved here in order to ensure the * correct * *ascending* order of attributes */ stream_putc(s, BGP_ATTR_FLAG_OPTIONAL | BGP_ATTR_FLAG_TRANS); stream_putc(s, BGP_ATTR_AS4_AGGREGATOR); stream_putc(s, 8); stream_putl(s, attr->aggregator_as); stream_put_ipv4(s, attr->aggregator_addr.s_addr); } if (((afi == AFI_IP || afi == AFI_IP6) && (safi == SAFI_ENCAP || safi == SAFI_MPLS_VPN)) || (afi == AFI_L2VPN && safi == SAFI_EVPN)) { /* Tunnel Encap attribute */ bgp_packet_mpattr_tea(bgp, peer, s, attr, BGP_ATTR_ENCAP); #if ENABLE_BGP_VNC_ATTR /* VNC attribute */ bgp_packet_mpattr_tea(bgp, peer, s, attr, BGP_ATTR_VNC); #endif } /* PMSI Tunnel */ if (attr->flag & ATTR_FLAG_BIT(BGP_ATTR_PMSI_TUNNEL)) { stream_putc(s, BGP_ATTR_FLAG_OPTIONAL | BGP_ATTR_FLAG_TRANS); stream_putc(s, BGP_ATTR_PMSI_TUNNEL); stream_putc(s, 9); // Length stream_putc(s, 0); // Flags stream_putc(s, attr->pmsi_tnl_type); stream_put(s, &(attr->label), BGP_LABEL_BYTES); // MPLS Label / VXLAN VNI stream_put_ipv4(s, attr->nexthop.s_addr); // Unicast tunnel endpoint IP address } /* Unknown transit attribute. */ if (attr->transit) stream_put(s, attr->transit->val, attr->transit->length); /* Return total size of attribute. */ return stream_get_endp(s) - cp; } size_t bgp_packet_mpunreach_start(struct stream *s, afi_t afi, safi_t safi) { unsigned long attrlen_pnt; iana_afi_t pkt_afi; iana_safi_t pkt_safi; /* Set extended bit always to encode the attribute length as 2 bytes */ stream_putc(s, BGP_ATTR_FLAG_OPTIONAL | BGP_ATTR_FLAG_EXTLEN); stream_putc(s, BGP_ATTR_MP_UNREACH_NLRI); attrlen_pnt = stream_get_endp(s); stream_putw(s, 0); /* Length of this attribute. */ /* Convert AFI, SAFI to values for packet. */ bgp_map_afi_safi_int2iana(afi, safi, &pkt_afi, &pkt_safi); stream_putw(s, pkt_afi); stream_putc(s, pkt_safi); return attrlen_pnt; } void bgp_packet_mpunreach_prefix(struct stream *s, struct prefix *p, afi_t afi, safi_t safi, struct prefix_rd *prd, mpls_label_t *label, uint32_t num_labels, int addpath_encode, uint32_t addpath_tx_id, struct attr *attr) { uint8_t wlabel[3] = {0x80, 0x00, 0x00}; if (safi == SAFI_LABELED_UNICAST) { label = (mpls_label_t *)wlabel; num_labels = 1; } bgp_packet_mpattr_prefix(s, afi, safi, p, prd, label, num_labels, addpath_encode, addpath_tx_id, attr); } void bgp_packet_mpunreach_end(struct stream *s, size_t attrlen_pnt) { bgp_packet_mpattr_end(s, attrlen_pnt); } /* Initialization of attribute. */ void bgp_attr_init(void) { aspath_init(); attrhash_init(); community_init(); ecommunity_init(); lcommunity_init(); cluster_init(); transit_init(); encap_init(); } void bgp_attr_finish(void) { aspath_finish(); attrhash_finish(); community_finish(); ecommunity_finish(); lcommunity_finish(); cluster_finish(); transit_finish(); encap_finish(); } /* Make attribute packet. */ void bgp_dump_routes_attr(struct stream *s, struct attr *attr, struct prefix *prefix) { unsigned long cp; unsigned long len; size_t aspath_lenp; struct aspath *aspath; int addpath_encode = 0; uint32_t addpath_tx_id = 0; /* Remember current pointer. */ cp = stream_get_endp(s); /* Place holder of length. */ stream_putw(s, 0); /* Origin attribute. */ stream_putc(s, BGP_ATTR_FLAG_TRANS); stream_putc(s, BGP_ATTR_ORIGIN); stream_putc(s, 1); stream_putc(s, attr->origin); aspath = attr->aspath; stream_putc(s, BGP_ATTR_FLAG_TRANS | BGP_ATTR_FLAG_EXTLEN); stream_putc(s, BGP_ATTR_AS_PATH); aspath_lenp = stream_get_endp(s); stream_putw(s, 0); stream_putw_at(s, aspath_lenp, aspath_put(s, aspath, 1)); /* Nexthop attribute. */ /* If it's an IPv6 prefix, don't dump the IPv4 nexthop to save space */ if (prefix != NULL && prefix->family != AF_INET6) { stream_putc(s, BGP_ATTR_FLAG_TRANS); stream_putc(s, BGP_ATTR_NEXT_HOP); stream_putc(s, 4); stream_put_ipv4(s, attr->nexthop.s_addr); } /* MED attribute. */ if (attr->flag & ATTR_FLAG_BIT(BGP_ATTR_MULTI_EXIT_DISC)) { stream_putc(s, BGP_ATTR_FLAG_OPTIONAL); stream_putc(s, BGP_ATTR_MULTI_EXIT_DISC); stream_putc(s, 4); stream_putl(s, attr->med); } /* Local preference. */ if (attr->flag & ATTR_FLAG_BIT(BGP_ATTR_LOCAL_PREF)) { stream_putc(s, BGP_ATTR_FLAG_TRANS); stream_putc(s, BGP_ATTR_LOCAL_PREF); stream_putc(s, 4); stream_putl(s, attr->local_pref); } /* Atomic aggregate. */ if (attr->flag & ATTR_FLAG_BIT(BGP_ATTR_ATOMIC_AGGREGATE)) { stream_putc(s, BGP_ATTR_FLAG_TRANS); stream_putc(s, BGP_ATTR_ATOMIC_AGGREGATE); stream_putc(s, 0); } /* Aggregator. */ if (attr->flag & ATTR_FLAG_BIT(BGP_ATTR_AGGREGATOR)) { stream_putc(s, BGP_ATTR_FLAG_OPTIONAL | BGP_ATTR_FLAG_TRANS); stream_putc(s, BGP_ATTR_AGGREGATOR); stream_putc(s, 8); stream_putl(s, attr->aggregator_as); stream_put_ipv4(s, attr->aggregator_addr.s_addr); } /* Community attribute. */ if (attr->flag & ATTR_FLAG_BIT(BGP_ATTR_COMMUNITIES)) { if (attr->community->size * 4 > 255) { stream_putc(s, BGP_ATTR_FLAG_OPTIONAL | BGP_ATTR_FLAG_TRANS | BGP_ATTR_FLAG_EXTLEN); stream_putc(s, BGP_ATTR_COMMUNITIES); stream_putw(s, attr->community->size * 4); } else { stream_putc(s, BGP_ATTR_FLAG_OPTIONAL | BGP_ATTR_FLAG_TRANS); stream_putc(s, BGP_ATTR_COMMUNITIES); stream_putc(s, attr->community->size * 4); } stream_put(s, attr->community->val, attr->community->size * 4); } /* Large Community attribute. */ if (attr->flag & ATTR_FLAG_BIT(BGP_ATTR_LARGE_COMMUNITIES)) { if (lcom_length(attr->lcommunity) > 255) { stream_putc(s, BGP_ATTR_FLAG_OPTIONAL | BGP_ATTR_FLAG_TRANS | BGP_ATTR_FLAG_EXTLEN); stream_putc(s, BGP_ATTR_LARGE_COMMUNITIES); stream_putw(s, lcom_length(attr->lcommunity)); } else { stream_putc(s, BGP_ATTR_FLAG_OPTIONAL | BGP_ATTR_FLAG_TRANS); stream_putc(s, BGP_ATTR_LARGE_COMMUNITIES); stream_putc(s, lcom_length(attr->lcommunity)); } stream_put(s, attr->lcommunity->val, lcom_length(attr->lcommunity)); } /* Add a MP_NLRI attribute to dump the IPv6 next hop */ if (prefix != NULL && prefix->family == AF_INET6 && (attr->mp_nexthop_len == BGP_ATTR_NHLEN_IPV6_GLOBAL || attr->mp_nexthop_len == BGP_ATTR_NHLEN_IPV6_GLOBAL_AND_LL)) { int sizep; stream_putc(s, BGP_ATTR_FLAG_OPTIONAL); stream_putc(s, BGP_ATTR_MP_REACH_NLRI); sizep = stream_get_endp(s); /* MP header */ stream_putc(s, 0); /* Marker: Attribute length. */ stream_putw(s, AFI_IP6); /* AFI */ stream_putc(s, SAFI_UNICAST); /* SAFI */ /* Next hop */ stream_putc(s, attr->mp_nexthop_len); stream_put(s, &attr->mp_nexthop_global, IPV6_MAX_BYTELEN); if (attr->mp_nexthop_len == BGP_ATTR_NHLEN_IPV6_GLOBAL_AND_LL) stream_put(s, &attr->mp_nexthop_local, IPV6_MAX_BYTELEN); /* SNPA */ stream_putc(s, 0); /* Prefix */ stream_put_prefix_addpath(s, prefix, addpath_encode, addpath_tx_id); /* Set MP attribute length. */ stream_putc_at(s, sizep, (stream_get_endp(s) - sizep) - 1); } /* Prefix SID */ if (attr->flag & ATTR_FLAG_BIT(BGP_ATTR_PREFIX_SID)) { if (attr->label_index != BGP_INVALID_LABEL_INDEX) { stream_putc(s, BGP_ATTR_FLAG_OPTIONAL | BGP_ATTR_FLAG_TRANS); stream_putc(s, BGP_ATTR_PREFIX_SID); stream_putc(s, 10); stream_putc(s, BGP_PREFIX_SID_LABEL_INDEX); stream_putc(s, BGP_PREFIX_SID_LABEL_INDEX_LENGTH); stream_putc(s, 0); // reserved stream_putw(s, 0); // flags stream_putl(s, attr->label_index); } } /* Return total size of attribute. */ len = stream_get_endp(s) - cp - 2; stream_putw_at(s, cp, len); } frr-7.2.1/bgpd/bgp_attr.h0000644000000000000000000002671213610377563012113 00000000000000/* BGP attributes. * Copyright (C) 1996, 97, 98 Kunihiro Ishiguro * * This file is part of GNU Zebra. * * GNU Zebra is free software; you can redistribute it and/or modify it * under the terms of the GNU General Public License as published by the * Free Software Foundation; either version 2, or (at your option) any * later version. * * GNU Zebra 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; see the file COPYING; if not, write to the Free Software * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA */ #ifndef _QUAGGA_BGP_ATTR_H #define _QUAGGA_BGP_ATTR_H #include "mpls.h" #include "bgp_attr_evpn.h" #include "bgpd/bgp_encap_types.h" /* Simple bit mapping. */ #define BITMAP_NBBY 8 #define SET_BITMAP(MAP, NUM) \ SET_FLAG(MAP[(NUM) / BITMAP_NBBY], 1 << ((NUM) % BITMAP_NBBY)) #define CHECK_BITMAP(MAP, NUM) \ CHECK_FLAG(MAP[(NUM) / BITMAP_NBBY], 1 << ((NUM) % BITMAP_NBBY)) #define BGP_MED_MAX UINT32_MAX /* BGP Attribute type range. */ #define BGP_ATTR_TYPE_RANGE 256 #define BGP_ATTR_BITMAP_SIZE (BGP_ATTR_TYPE_RANGE / BITMAP_NBBY) /* BGP Attribute flags. */ #define BGP_ATTR_FLAG_OPTIONAL 0x80 /* Attribute is optional. */ #define BGP_ATTR_FLAG_TRANS 0x40 /* Attribute is transitive. */ #define BGP_ATTR_FLAG_PARTIAL 0x20 /* Attribute is partial. */ #define BGP_ATTR_FLAG_EXTLEN 0x10 /* Extended length flag. */ /* BGP attribute header must bigger than 2. */ #define BGP_ATTR_MIN_LEN 3 /* Attribute flag, type length. */ #define BGP_ATTR_DEFAULT_WEIGHT 32768 /* Valid lengths for mp_nexthop_len */ #define BGP_ATTR_NHLEN_IPV4 IPV4_MAX_BYTELEN #define BGP_ATTR_NHLEN_VPNV4 8+IPV4_MAX_BYTELEN #define BGP_ATTR_NHLEN_IPV6_GLOBAL IPV6_MAX_BYTELEN #define BGP_ATTR_NHLEN_IPV6_GLOBAL_AND_LL (IPV6_MAX_BYTELEN * 2) #define BGP_ATTR_NHLEN_VPNV6_GLOBAL 8+IPV6_MAX_BYTELEN #define BGP_ATTR_NHLEN_VPNV6_GLOBAL_AND_LL ((8+IPV6_MAX_BYTELEN) * 2) /* Prefix SID types */ #define BGP_PREFIX_SID_LABEL_INDEX 1 #define BGP_PREFIX_SID_IPV6 2 #define BGP_PREFIX_SID_ORIGINATOR_SRGB 3 #define BGP_PREFIX_SID_LABEL_INDEX_LENGTH 7 #define BGP_PREFIX_SID_IPV6_LENGTH 19 #define BGP_PREFIX_SID_ORIGINATOR_SRGB_LENGTH 6 /* PMSI tunnel types (RFC 6514) */ struct bgp_attr_encap_subtlv { struct bgp_attr_encap_subtlv *next; /* for chaining */ /* Reference count of this attribute. */ unsigned long refcnt; uint16_t type; uint16_t length; uint8_t value[0]; /* will be extended */ }; #if ENABLE_BGP_VNC /* * old rfp<->rfapi representation */ struct bgp_tea_options { struct bgp_tea_options *next; uint8_t options_count; uint16_t options_length; /* each TLV may be 256 in length */ uint8_t type; uint8_t length; void *value; /* pointer to data */ }; #endif enum pta_type { PMSI_TNLTYPE_NO_INFO = 0, PMSI_TNLTYPE_RSVP_TE_P2MP, PMSI_TNLTYPE_MLDP_P2MP, PMSI_TNLTYPE_PIM_SSM, PMSI_TNLTYPE_PIM_SM, PMSI_TNLTYPE_PIM_BIDIR, PMSI_TNLTYPE_INGR_REPL, PMSI_TNLTYPE_MLDP_MP2MP, PMSI_TNLTYPE_MAX = PMSI_TNLTYPE_MLDP_MP2MP }; /* BGP core attribute structure. */ struct attr { /* AS Path structure */ struct aspath *aspath; /* Community structure */ struct community *community; /* Reference count of this attribute. */ unsigned long refcnt; /* Flag of attribute is set or not. */ uint64_t flag; /* Apart from in6_addr, the remaining static attributes */ struct in_addr nexthop; uint32_t med; uint32_t local_pref; ifindex_t nh_ifindex; /* Path origin attribute */ uint8_t origin; /* PMSI tunnel type (RFC 6514). */ enum pta_type pmsi_tnl_type; /* has the route-map changed any attribute? Used on the peer outbound side. */ uint32_t rmap_change_flags; /* Multi-Protocol Nexthop, AFI IPv6 */ struct in6_addr mp_nexthop_global; struct in6_addr mp_nexthop_local; /* ifIndex corresponding to mp_nexthop_local. */ ifindex_t nh_lla_ifindex; /* Extended Communities attribute. */ struct ecommunity *ecommunity; /* Large Communities attribute. */ struct lcommunity *lcommunity; /* Route-Reflector Cluster attribute */ struct cluster_list *cluster; /* Unknown transitive attribute. */ struct transit *transit; struct in_addr mp_nexthop_global_in; /* Aggregator Router ID attribute */ struct in_addr aggregator_addr; /* Route Reflector Originator attribute */ struct in_addr originator_id; /* Local weight, not actually an attribute */ uint32_t weight; /* Aggregator ASN */ as_t aggregator_as; /* MP Nexthop length */ uint8_t mp_nexthop_len; /* MP Nexthop preference */ uint8_t mp_nexthop_prefer_global; /* Static MAC for EVPN */ uint8_t sticky; /* Flag for default gateway extended community in EVPN */ uint8_t default_gw; /* NA router flag (R-bit) support in EVPN */ uint8_t router_flag; /* route tag */ route_tag_t tag; /* Label index */ uint32_t label_index; /* MPLS label */ mpls_label_t label; uint16_t encap_tunneltype; /* grr */ struct bgp_attr_encap_subtlv *encap_subtlvs; /* rfc5512 */ #if ENABLE_BGP_VNC struct bgp_attr_encap_subtlv *vnc_subtlvs; /* VNC-specific */ #endif /* EVPN */ struct bgp_route_evpn evpn_overlay; /* EVPN MAC Mobility sequence number, if any. */ uint32_t mm_seqnum; /* EVPN local router-mac */ struct ethaddr rmac; }; /* rmap_change_flags definition */ #define BATTR_RMAP_IPV4_NHOP_CHANGED (1 << 0) #define BATTR_RMAP_NEXTHOP_PEER_ADDRESS (1 << 1) #define BATTR_REFLECTED (1 << 2) #define BATTR_RMAP_NEXTHOP_UNCHANGED (1 << 3) #define BATTR_RMAP_IPV6_GLOBAL_NHOP_CHANGED (1 << 4) #define BATTR_RMAP_IPV6_LL_NHOP_CHANGED (1 << 5) #define BATTR_RMAP_IPV6_PREFER_GLOBAL_CHANGED (1 << 6) /* Router Reflector related structure. */ struct cluster_list { unsigned long refcnt; int length; struct in_addr *list; }; /* Unknown transit attribute. */ struct transit { unsigned long refcnt; int length; uint8_t *val; }; /* "(void) 0" will generate a compiler error. this is a safety check to * ensure we're not using a value that exceeds the bit size of attr->flag. */ #define ATTR_FLAG_BIT(X) \ __builtin_choose_expr((X) >= 1 && (X) <= 64, 1ULL << ((X)-1), (void)0) #define BGP_CLUSTER_LIST_LENGTH(attr) \ (((attr)->flag & ATTR_FLAG_BIT(BGP_ATTR_CLUSTER_LIST)) \ ? (attr)->cluster->length \ : 0) typedef enum { BGP_ATTR_PARSE_PROCEED = 0, BGP_ATTR_PARSE_ERROR = -1, BGP_ATTR_PARSE_WITHDRAW = -2, /* only used internally, send notify + convert to BGP_ATTR_PARSE_ERROR */ BGP_ATTR_PARSE_ERROR_NOTIFYPLS = -3, BGP_ATTR_PARSE_EOR = -4, } bgp_attr_parse_ret_t; struct bpacket_attr_vec_arr; /* Prototypes. */ extern void bgp_attr_init(void); extern void bgp_attr_finish(void); extern bgp_attr_parse_ret_t bgp_attr_parse(struct peer *, struct attr *, bgp_size_t, struct bgp_nlri *, struct bgp_nlri *); extern void bgp_attr_undup(struct attr *new, struct attr *old); extern struct attr *bgp_attr_intern(struct attr *attr); extern void bgp_attr_unintern_sub(struct attr *); extern void bgp_attr_unintern(struct attr **); extern void bgp_attr_flush(struct attr *); extern struct attr *bgp_attr_default_set(struct attr *attr, uint8_t); extern struct attr *bgp_attr_aggregate_intern(struct bgp *bgp, uint8_t origin, struct aspath *aspath, struct community *community, struct ecommunity *ecommunity, struct lcommunity *lcommunity, struct bgp_aggregate *aggregate, uint8_t atomic_aggregate, struct prefix *p); extern bgp_size_t bgp_packet_attribute(struct bgp *bgp, struct peer *, struct stream *, struct attr *, struct bpacket_attr_vec_arr *vecarr, struct prefix *, afi_t, safi_t, struct peer *, struct prefix_rd *, mpls_label_t *, uint32_t, int, uint32_t); extern void bgp_dump_routes_attr(struct stream *, struct attr *, struct prefix *); extern bool attrhash_cmp(const void *arg1, const void *arg2); extern unsigned int attrhash_key_make(const void *); extern void attr_show_all(struct vty *); extern unsigned long int attr_count(void); extern unsigned long int attr_unknown_count(void); /* Cluster list prototypes. */ extern int cluster_loop_check(struct cluster_list *, struct in_addr); extern void cluster_unintern(struct cluster_list *); /* Below exported for unit-test purposes only */ struct bgp_attr_parser_args { struct peer *peer; bgp_size_t length; /* attribute data length; */ bgp_size_t total; /* total length, inc header */ struct attr *attr; uint8_t type; uint8_t flags; uint8_t *startp; }; extern int bgp_mp_reach_parse(struct bgp_attr_parser_args *args, struct bgp_nlri *); extern int bgp_mp_unreach_parse(struct bgp_attr_parser_args *args, struct bgp_nlri *); extern bgp_attr_parse_ret_t bgp_attr_prefix_sid(int32_t tlength, struct bgp_attr_parser_args *args, struct bgp_nlri *mp_update); extern struct bgp_attr_encap_subtlv * encap_tlv_dup(struct bgp_attr_encap_subtlv *orig); extern void bgp_attr_flush_encap(struct attr *attr); extern void bgp_attr_extcom_tunnel_type(struct attr *attr, bgp_encap_types *tunnel_type); /** * Set of functions to encode MP_REACH_NLRI and MP_UNREACH_NLRI attributes. * Typical call sequence is to call _start(), followed by multiple _prefix(), * one for each NLRI that needs to be encoded into the UPDATE message, and * finally the _end() function. */ extern size_t bgp_packet_mpattr_start(struct stream *s, struct peer *peer, afi_t afi, safi_t safi, struct bpacket_attr_vec_arr *vecarr, struct attr *attr); extern void bgp_packet_mpattr_prefix(struct stream *s, afi_t afi, safi_t safi, struct prefix *p, struct prefix_rd *prd, mpls_label_t *label, uint32_t num_labels, int addpath_encode, uint32_t addpath_tx_id, struct attr *); extern size_t bgp_packet_mpattr_prefix_size(afi_t afi, safi_t safi, struct prefix *p); extern void bgp_packet_mpattr_end(struct stream *s, size_t sizep); extern size_t bgp_packet_mpunreach_start(struct stream *s, afi_t afi, safi_t safi); extern void bgp_packet_mpunreach_prefix(struct stream *s, struct prefix *p, afi_t afi, safi_t safi, struct prefix_rd *prd, mpls_label_t *, uint32_t, int, uint32_t, struct attr *); extern void bgp_packet_mpunreach_end(struct stream *s, size_t attrlen_pnt); extern bgp_attr_parse_ret_t bgp_attr_nexthop_valid(struct peer *peer, struct attr *attr); static inline int bgp_rmap_nhop_changed(uint32_t out_rmap_flags, uint32_t in_rmap_flags) { return ((CHECK_FLAG(out_rmap_flags, BATTR_RMAP_NEXTHOP_PEER_ADDRESS) || CHECK_FLAG(out_rmap_flags, BATTR_RMAP_NEXTHOP_UNCHANGED) || CHECK_FLAG(out_rmap_flags, BATTR_RMAP_IPV4_NHOP_CHANGED) || CHECK_FLAG(out_rmap_flags, BATTR_RMAP_IPV6_GLOBAL_NHOP_CHANGED) || CHECK_FLAG(out_rmap_flags, BATTR_RMAP_IPV6_PREFER_GLOBAL_CHANGED) || CHECK_FLAG(out_rmap_flags, BATTR_RMAP_IPV6_LL_NHOP_CHANGED) || CHECK_FLAG(in_rmap_flags, BATTR_RMAP_NEXTHOP_UNCHANGED)) ? 1 : 0); } static inline uint32_t mac_mobility_seqnum(struct attr *attr) { return (attr) ? attr->mm_seqnum : 0; } #endif /* _QUAGGA_BGP_ATTR_H */ frr-7.2.1/bgpd/bgp_attr_evpn.c0000644000000000000000000001544413610377563013136 00000000000000/* Ethernet-VPN Attribute handling file * Copyright (C) 2016 6WIND * * This file is part of FRRouting. * * FRRouting is free software; you can redistribute it and/or modify it * under the terms of the GNU General Public License as published by the * Free Software Foundation; either version 2, or (at your option) any * later version. * * FRRouting 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; see the file COPYING; if not, write to the Free Software * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA */ #include #include "command.h" #include "filter.h" #include "prefix.h" #include "log.h" #include "memory.h" #include "stream.h" #include "bgpd/bgpd.h" #include "bgpd/bgp_attr.h" #include "bgpd/bgp_route.h" #include "bgpd/bgp_attr_evpn.h" #include "bgpd/bgp_ecommunity.h" #include "bgpd/bgp_evpn.h" #include "bgpd/bgp_evpn_private.h" void bgp_add_routermac_ecom(struct attr *attr, struct ethaddr *routermac) { struct ecommunity_val routermac_ecom; memset(&routermac_ecom, 0, sizeof(struct ecommunity_val)); routermac_ecom.val[0] = ECOMMUNITY_ENCODE_EVPN; routermac_ecom.val[1] = ECOMMUNITY_EVPN_SUBTYPE_ROUTERMAC; memcpy(&routermac_ecom.val[2], routermac->octet, ETH_ALEN); if (!attr->ecommunity) attr->ecommunity = ecommunity_new(); ecommunity_add_val(attr->ecommunity, &routermac_ecom); ecommunity_str(attr->ecommunity); } /* converts to an esi * returns 1 on success, 0 otherwise * format accepted: AA:BB:CC:DD:EE:FF:GG:HH:II:JJ * if id is null, check only is done */ int str2esi(const char *str, struct eth_segment_id *id) { unsigned int a[ESI_LEN]; int i; if (!str) return 0; if (sscanf(str, "%2x:%2x:%2x:%2x:%2x:%2x:%2x:%2x:%2x:%2x", a + 0, a + 1, a + 2, a + 3, a + 4, a + 5, a + 6, a + 7, a + 8, a + 9) != ESI_LEN) { /* error in incoming str length */ return 0; } /* valid mac address */ if (!id) return 1; for (i = 0; i < ESI_LEN; ++i) id->val[i] = a[i] & 0xff; return 1; } char *esi2str(struct eth_segment_id *id) { char *ptr; uint8_t *val; if (!id) return NULL; val = id->val; ptr = XMALLOC(MTYPE_TMP, (ESI_LEN * 2 + ESI_LEN - 1 + 1) * sizeof(char)); snprintf(ptr, (ESI_LEN * 2 + ESI_LEN - 1 + 1), "%02x:%02x:%02x:%02x:%02x:%02x:%02x:%02x:%02x:%02x", val[0], val[1], val[2], val[3], val[4], val[5], val[6], val[7], val[8], val[9]); return ptr; } char *ecom_mac2str(char *ecom_mac) { char *en; en = ecom_mac; en += 2; return prefix_mac2str((struct ethaddr *)en, NULL, 0); } /* Fetch router-mac from extended community */ bool bgp_attr_rmac(struct attr *attr, struct ethaddr *rmac) { int i = 0; struct ecommunity *ecom; ecom = attr->ecommunity; if (!ecom || !ecom->size) return false; /* If there is a router mac extended community, set RMAC in attr */ for (i = 0; i < ecom->size; i++) { uint8_t *pnt = NULL; uint8_t type = 0; uint8_t sub_type = 0; pnt = (ecom->val + (i * ECOMMUNITY_SIZE)); type = *pnt++; sub_type = *pnt++; if (!(type == ECOMMUNITY_ENCODE_EVPN && sub_type == ECOMMUNITY_EVPN_SUBTYPE_ROUTERMAC)) continue; memcpy(rmac, pnt, ETH_ALEN); return true; } return false; } /* * return true if attr contains default gw extended community */ uint8_t bgp_attr_default_gw(struct attr *attr) { struct ecommunity *ecom; int i; ecom = attr->ecommunity; if (!ecom || !ecom->size) return 0; /* If there is a default gw extendd community return true otherwise * return 0 */ for (i = 0; i < ecom->size; i++) { uint8_t *pnt; uint8_t type, sub_type; pnt = (ecom->val + (i * ECOMMUNITY_SIZE)); type = *pnt++; sub_type = *pnt++; if ((type == ECOMMUNITY_ENCODE_OPAQUE && sub_type == ECOMMUNITY_EVPN_SUBTYPE_DEF_GW)) return 1; } return 0; } /* * Fetch and return the sequence number from MAC Mobility extended * community, if present, else 0. */ uint32_t bgp_attr_mac_mobility_seqnum(struct attr *attr, uint8_t *sticky) { struct ecommunity *ecom; int i; uint8_t flags = 0; ecom = attr->ecommunity; if (!ecom || !ecom->size) return 0; /* If there is a MAC Mobility extended community, return its * sequence number. * TODO: RFC is silent on handling of multiple MAC mobility extended * communities for the same route. We will bail out upon the first * one. */ for (i = 0; i < ecom->size; i++) { uint8_t *pnt; uint8_t type, sub_type; uint32_t seq_num; pnt = (ecom->val + (i * ECOMMUNITY_SIZE)); type = *pnt++; sub_type = *pnt++; if (!(type == ECOMMUNITY_ENCODE_EVPN && sub_type == ECOMMUNITY_EVPN_SUBTYPE_MACMOBILITY)) continue; flags = *pnt++; if (flags & ECOMMUNITY_EVPN_SUBTYPE_MACMOBILITY_FLAG_STICKY) *sticky = 1; else *sticky = 0; pnt++; pnt = ptr_get_be32(pnt, &seq_num); (void)pnt; /* consume value */ return seq_num; } return 0; } /* * return true if attr contains router flag extended community */ void bgp_attr_evpn_na_flag(struct attr *attr, uint8_t *router_flag) { struct ecommunity *ecom; int i; uint8_t val; ecom = attr->ecommunity; if (!ecom || !ecom->size) return; /* If there is a evpn na extendd community set router_flag */ for (i = 0; i < ecom->size; i++) { uint8_t *pnt; uint8_t type, sub_type; pnt = (ecom->val + (i * ECOMMUNITY_SIZE)); type = *pnt++; sub_type = *pnt++; if (type == ECOMMUNITY_ENCODE_EVPN && sub_type == ECOMMUNITY_EVPN_SUBTYPE_ND) { val = *pnt++; if (val & ECOMMUNITY_EVPN_SUBTYPE_ND_ROUTER_FLAG) { *router_flag = 1; break; } } } } /* dst prefix must be AF_INET or AF_INET6 prefix, to forge EVPN prefix */ extern int bgp_build_evpn_prefix(int evpn_type, uint32_t eth_tag, struct prefix *dst) { struct evpn_addr *p_evpn_p; struct prefix p2; struct prefix *src = &p2; if (!dst || dst->family == 0) return -1; /* store initial prefix in src */ prefix_copy(src, dst); memset(dst, 0, sizeof(struct prefix)); p_evpn_p = &(dst->u.prefix_evpn); dst->family = AF_EVPN; p_evpn_p->route_type = evpn_type; if (evpn_type == BGP_EVPN_IP_PREFIX_ROUTE) { p_evpn_p->prefix_addr.eth_tag = eth_tag; p_evpn_p->prefix_addr.ip_prefix_length = p2.prefixlen; if (src->family == AF_INET) { SET_IPADDR_V4(&p_evpn_p->prefix_addr.ip); memcpy(&p_evpn_p->prefix_addr.ip.ipaddr_v4, &src->u.prefix4, sizeof(struct in_addr)); dst->prefixlen = (uint16_t)PREFIX_LEN_ROUTE_TYPE_5_IPV4; } else { SET_IPADDR_V6(&p_evpn_p->prefix_addr.ip); memcpy(&p_evpn_p->prefix_addr.ip.ipaddr_v6, &src->u.prefix6, sizeof(struct in6_addr)); dst->prefixlen = (uint16_t)PREFIX_LEN_ROUTE_TYPE_5_IPV6; } } else return -1; return 0; } frr-7.2.1/bgpd/bgp_attr_evpn.h0000644000000000000000000000446713610377563013146 00000000000000/* E-VPN attribute handling structure file * Copyright (C) 2016 6WIND * * This file is part of FRRouting. * * FRRouting is free software; you can redistribute it and/or modify it * under the terms of the GNU General Public License as published by the * Free Software Foundation; either version 2, or (at your option) any * later version. * * FRRouting 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; see the file COPYING; if not, write to the Free Software * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA */ #ifndef _QUAGGA_BGP_ATTR_EVPN_H #define _QUAGGA_BGP_ATTR_EVPN_H /* value of first byte of ESI */ #define ESI_TYPE_ARBITRARY 0 /* */ #define ESI_TYPE_LACP 1 /* <> */ #define ESI_TYPE_BRIDGE 2 /* ::00 */ #define ESI_TYPE_MAC 3 /* : */ #define ESI_TYPE_ROUTER 4 /* : */ #define ESI_TYPE_AS 5 /* : */ #define MAX_ESI {0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff} #define ESI_LEN 10 #define MAX_ET 0xffffffff unsigned long eth_tag_id; struct attr; /* EVPN ESI */ struct eth_segment_id { uint8_t val[ESI_LEN]; }; union gw_addr { struct in_addr ipv4; struct in6_addr ipv6; }; struct bgp_route_evpn { struct eth_segment_id eth_s_id; union gw_addr gw_ip; }; extern int str2esi(const char *str, struct eth_segment_id *id); extern char *esi2str(struct eth_segment_id *id); extern char *ecom_mac2str(char *ecom_mac); extern void bgp_add_routermac_ecom(struct attr *attr, struct ethaddr *routermac); extern int bgp_build_evpn_prefix(int type, uint32_t eth_tag, struct prefix *dst); extern bool bgp_attr_rmac(struct attr *attr, struct ethaddr *rmac); extern uint32_t bgp_attr_mac_mobility_seqnum(struct attr *attr, uint8_t *sticky); extern uint8_t bgp_attr_default_gw(struct attr *attr); extern void bgp_attr_evpn_na_flag(struct attr *attr, uint8_t *router_flag); #endif /* _QUAGGA_BGP_ATTR_EVPN_H */ frr-7.2.1/bgpd/bgp_bfd.c0000644000000000000000000005255213610377563011670 00000000000000/** * bgp_bfd.c: BGP BFD handling routines * * @copyright Copyright (C) 2015 Cumulus Networks, Inc. * * This file is part of GNU Zebra. * * GNU Zebra is free software; you can redistribute it and/or modify it * under the terms of the GNU General Public License as published by the * Free Software Foundation; either version 2, or (at your option) any * later version. * * GNU Zebra 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; see the file COPYING; if not, write to the Free Software * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA */ #include #include "command.h" #include "linklist.h" #include "memory.h" #include "prefix.h" #include "thread.h" #include "buffer.h" #include "stream.h" #include "zclient.h" #include "bfd.h" #include "lib/json.h" #include "filter.h" #include "bgpd/bgpd.h" #include "bgp_fsm.h" #include "bgpd/bgp_bfd.h" #include "bgpd/bgp_debug.h" #include "bgpd/bgp_vty.h" extern struct zclient *zclient; /* * bgp_bfd_peer_group2peer_copy - Copy the BFD information from peer group * template * to peer. */ void bgp_bfd_peer_group2peer_copy(struct peer *conf, struct peer *peer) { struct bfd_info *bfd_info; struct bfd_info *conf_bfd_info; if (!conf->bfd_info) return; conf_bfd_info = (struct bfd_info *)conf->bfd_info; if (!peer->bfd_info) peer->bfd_info = bfd_info_create(); bfd_info = (struct bfd_info *)peer->bfd_info; /* Copy BFD parameter values */ bfd_info->required_min_rx = conf_bfd_info->required_min_rx; bfd_info->desired_min_tx = conf_bfd_info->desired_min_tx; bfd_info->detect_mult = conf_bfd_info->detect_mult; bfd_info->type = conf_bfd_info->type; } /* * bgp_bfd_is_peer_multihop - returns whether BFD peer is multi-hop or single * hop. */ int bgp_bfd_is_peer_multihop(struct peer *peer) { struct bfd_info *bfd_info; bfd_info = (struct bfd_info *)peer->bfd_info; if (!bfd_info) return 0; if ((bfd_info->type == BFD_TYPE_MULTIHOP) || ((peer->sort == BGP_PEER_IBGP) && !peer->shared_network) || is_ebgp_multihop_configured(peer)) return 1; else return 0; } /* * bgp_bfd_peer_sendmsg - Format and send a Peer register/Unregister * command to Zebra to be forwarded to BFD */ static void bgp_bfd_peer_sendmsg(struct peer *peer, int command) { struct bfd_info *bfd_info; int multihop, cbit = 0; vrf_id_t vrf_id; bfd_info = (struct bfd_info *)peer->bfd_info; vrf_id = peer->bgp->vrf_id; if (command == ZEBRA_BFD_DEST_DEREGISTER) { multihop = CHECK_FLAG(bfd_info->flags, BFD_FLAG_BFD_TYPE_MULTIHOP); UNSET_FLAG(bfd_info->flags, BFD_FLAG_BFD_TYPE_MULTIHOP); } else { multihop = bgp_bfd_is_peer_multihop(peer); if ((command == ZEBRA_BFD_DEST_REGISTER) && multihop) SET_FLAG(bfd_info->flags, BFD_FLAG_BFD_TYPE_MULTIHOP); } /* while graceful restart with fwd path preserved * and bfd controlplane check not configured is not kept * keep bfd independent controlplane bit set to 1 */ if (!bgp_flag_check(peer->bgp, BGP_FLAG_GRACEFUL_RESTART) && !bgp_flag_check(peer->bgp, BGP_FLAG_GR_PRESERVE_FWD) && !CHECK_FLAG(bfd_info->flags, BFD_FLAG_BFD_CHECK_CONTROLPLANE)) SET_FLAG(bfd_info->flags, BFD_FLAG_BFD_CBIT_ON); cbit = CHECK_FLAG(bfd_info->flags, BFD_FLAG_BFD_CBIT_ON); if (peer->su.sa.sa_family == AF_INET) bfd_peer_sendmsg( zclient, bfd_info, AF_INET, &peer->su.sin.sin_addr, (peer->su_local) ? &peer->su_local->sin.sin_addr : NULL, (peer->nexthop.ifp) ? peer->nexthop.ifp->name : NULL, peer->ttl, multihop, cbit, command, 1, vrf_id); else if (peer->su.sa.sa_family == AF_INET6) bfd_peer_sendmsg( zclient, bfd_info, AF_INET6, &peer->su.sin6.sin6_addr, (peer->su_local) ? &peer->su_local->sin6.sin6_addr : NULL, (peer->nexthop.ifp) ? peer->nexthop.ifp->name : NULL, peer->ttl, multihop, cbit, command, 1, vrf_id); } /* * bgp_bfd_register_peer - register a peer with BFD through zebra * for monitoring the peer rechahability. */ void bgp_bfd_register_peer(struct peer *peer) { struct bfd_info *bfd_info; if (!peer->bfd_info) return; bfd_info = (struct bfd_info *)peer->bfd_info; /* Check if BFD is enabled and peer has already been registered with BFD */ if (CHECK_FLAG(bfd_info->flags, BFD_FLAG_BFD_REG)) return; bgp_bfd_peer_sendmsg(peer, ZEBRA_BFD_DEST_REGISTER); } /** * bgp_bfd_deregister_peer - deregister a peer with BFD through zebra * for stopping the monitoring of the peer * rechahability. */ void bgp_bfd_deregister_peer(struct peer *peer) { struct bfd_info *bfd_info; if (!peer->bfd_info) return; bfd_info = (struct bfd_info *)peer->bfd_info; /* Check if BFD is eanbled and peer has not been registered */ if (!CHECK_FLAG(bfd_info->flags, BFD_FLAG_BFD_REG)) return; bfd_info->status = BFD_STATUS_DOWN; bfd_info->last_update = bgp_clock(); bgp_bfd_peer_sendmsg(peer, ZEBRA_BFD_DEST_DEREGISTER); } /* * bgp_bfd_update_peer - update peer with BFD with new BFD paramters * through zebra. */ static void bgp_bfd_update_peer(struct peer *peer) { struct bfd_info *bfd_info; if (!peer->bfd_info) return; bfd_info = (struct bfd_info *)peer->bfd_info; /* Check if the peer has been registered with BFD*/ if (!CHECK_FLAG(bfd_info->flags, BFD_FLAG_BFD_REG)) return; bgp_bfd_peer_sendmsg(peer, ZEBRA_BFD_DEST_UPDATE); } /* * bgp_bfd_update_type - update session type with BFD through zebra. */ static void bgp_bfd_update_type(struct peer *peer) { struct bfd_info *bfd_info; int multihop; if (!peer->bfd_info) return; bfd_info = (struct bfd_info *)peer->bfd_info; /* Check if the peer has been registered with BFD*/ if (!CHECK_FLAG(bfd_info->flags, BFD_FLAG_BFD_REG)) return; if (bfd_info->type == BFD_TYPE_NOT_CONFIGURED) { multihop = bgp_bfd_is_peer_multihop(peer); if ((multihop && !CHECK_FLAG(bfd_info->flags, BFD_FLAG_BFD_TYPE_MULTIHOP)) || (!multihop && CHECK_FLAG(bfd_info->flags, BFD_FLAG_BFD_TYPE_MULTIHOP))) { bgp_bfd_peer_sendmsg(peer, ZEBRA_BFD_DEST_DEREGISTER); bgp_bfd_peer_sendmsg(peer, ZEBRA_BFD_DEST_REGISTER); } } else { if ((bfd_info->type == BFD_TYPE_MULTIHOP && !CHECK_FLAG(bfd_info->flags, BFD_FLAG_BFD_TYPE_MULTIHOP)) || (bfd_info->type == BFD_TYPE_SINGLEHOP && CHECK_FLAG(bfd_info->flags, BFD_FLAG_BFD_TYPE_MULTIHOP))) { bgp_bfd_peer_sendmsg(peer, ZEBRA_BFD_DEST_DEREGISTER); bgp_bfd_peer_sendmsg(peer, ZEBRA_BFD_DEST_REGISTER); } } } /* * bgp_bfd_dest_replay - Replay all the peers that have BFD enabled * to zebra */ static int bgp_bfd_dest_replay(ZAPI_CALLBACK_ARGS) { struct listnode *mnode, *node, *nnode; struct bgp *bgp; struct peer *peer; if (BGP_DEBUG(zebra, ZEBRA)) zlog_debug("Zebra: BFD Dest replay request"); /* Send the client registration */ bfd_client_sendmsg(zclient, ZEBRA_BFD_CLIENT_REGISTER, vrf_id); /* Replay the peer, if BFD is enabled in BGP */ for (ALL_LIST_ELEMENTS_RO(bm->bgp, mnode, bgp)) for (ALL_LIST_ELEMENTS(bgp->peer, node, nnode, peer)) { bgp_bfd_update_peer(peer); } return 0; } /* * bgp_bfd_peer_status_update - Update the BFD status if it has changed. Bring * down the peer if the BFD session went down from * * up. */ static void bgp_bfd_peer_status_update(struct peer *peer, int status, int remote_cbit) { struct bfd_info *bfd_info; int old_status; bfd_info = (struct bfd_info *)peer->bfd_info; if (bfd_info->status == status) return; old_status = bfd_info->status; bfd_info->status = status; bfd_info->last_update = bgp_clock(); if (status != old_status) { if (BGP_DEBUG(neighbor_events, NEIGHBOR_EVENTS)) zlog_debug("[%s]: BFD %s", peer->host, bfd_get_status_str(status)); } if ((status == BFD_STATUS_DOWN) && (old_status == BFD_STATUS_UP)) { if (CHECK_FLAG(peer->sflags, PEER_STATUS_NSF_MODE) && CHECK_FLAG(bfd_info->flags, BFD_FLAG_BFD_CHECK_CONTROLPLANE) && !remote_cbit) { zlog_info("%s BFD DOWN message ignored in the process" " of graceful restart when C bit is cleared", peer->host); return; } peer->last_reset = PEER_DOWN_BFD_DOWN; BGP_EVENT_ADD(peer, BGP_Stop); } if ((status == BFD_STATUS_UP) && (old_status == BFD_STATUS_DOWN) && peer->status != Established) { if (!BGP_PEER_START_SUPPRESSED(peer)) { bgp_fsm_event_update(peer, 1); BGP_EVENT_ADD(peer, BGP_Start); } } } /* * bgp_bfd_dest_update - Find the peer for which the BFD status * has changed and bring down the peer * connectivity if the BFD session went down. */ static int bgp_bfd_dest_update(ZAPI_CALLBACK_ARGS) { struct interface *ifp; struct prefix dp; struct prefix sp; int status; int remote_cbit; ifp = bfd_get_peer_info(zclient->ibuf, &dp, &sp, &status, &remote_cbit, vrf_id); if (BGP_DEBUG(zebra, ZEBRA)) { char buf[2][PREFIX2STR_BUFFER]; prefix2str(&dp, buf[0], sizeof(buf[0])); if (ifp) { zlog_debug( "Zebra: vrf %u interface %s bfd destination %s %s %s", vrf_id, ifp->name, buf[0], bfd_get_status_str(status), remote_cbit ? "(cbit on)" : ""); } else { prefix2str(&sp, buf[1], sizeof(buf[1])); zlog_debug( "Zebra: vrf %u source %s bfd destination %s %s %s", vrf_id, buf[1], buf[0], bfd_get_status_str(status), remote_cbit ? "(cbit on)" : ""); } } /* Bring the peer down if BFD is enabled in BGP */ { struct listnode *mnode, *node, *nnode; struct bgp *bgp; struct peer *peer; for (ALL_LIST_ELEMENTS_RO(bm->bgp, mnode, bgp)) for (ALL_LIST_ELEMENTS(bgp->peer, node, nnode, peer)) { if (!peer->bfd_info) continue; if ((dp.family == AF_INET) && (peer->su.sa.sa_family == AF_INET)) { if (dp.u.prefix4.s_addr != peer->su.sin.sin_addr.s_addr) continue; } else if ((dp.family == AF_INET6) && (peer->su.sa.sa_family == AF_INET6)) { if (memcmp(&dp.u.prefix6, &peer->su.sin6.sin6_addr, sizeof(struct in6_addr))) continue; } else continue; if (ifp && (ifp == peer->nexthop.ifp)) { bgp_bfd_peer_status_update(peer, status, remote_cbit); } else { if (!peer->su_local) continue; if ((sp.family == AF_INET) && (peer->su_local->sa.sa_family == AF_INET)) { if (sp.u.prefix4.s_addr != peer->su_local->sin .sin_addr.s_addr) continue; } else if ((sp.family == AF_INET6) && (peer->su_local->sa .sa_family == AF_INET6)) { if (memcmp(&sp.u.prefix6, &peer->su_local->sin6 .sin6_addr, sizeof(struct in6_addr))) continue; } else continue; if ((vrf_id != VRF_DEFAULT) && (peer->bgp->vrf_id != vrf_id)) continue; bgp_bfd_peer_status_update(peer, status, remote_cbit); } } } return 0; } /* * bgp_bfd_peer_param_set - Set the configured BFD paramter values for peer. */ static int bgp_bfd_peer_param_set(struct peer *peer, uint32_t min_rx, uint32_t min_tx, uint8_t detect_mult, int defaults) { struct peer_group *group; struct listnode *node, *nnode; int command = 0; bfd_set_param((struct bfd_info **)&(peer->bfd_info), min_rx, min_tx, detect_mult, defaults, &command); if (CHECK_FLAG(peer->sflags, PEER_STATUS_GROUP)) { group = peer->group; for (ALL_LIST_ELEMENTS(group->peer, node, nnode, peer)) { command = 0; bfd_set_param((struct bfd_info **)&(peer->bfd_info), min_rx, min_tx, detect_mult, defaults, &command); if ((peer->status == Established) && (command == ZEBRA_BFD_DEST_REGISTER)) bgp_bfd_register_peer(peer); else if (command == ZEBRA_BFD_DEST_UPDATE) bgp_bfd_update_peer(peer); } } else { if ((peer->status == Established) && (command == ZEBRA_BFD_DEST_REGISTER)) bgp_bfd_register_peer(peer); else if (command == ZEBRA_BFD_DEST_UPDATE) bgp_bfd_update_peer(peer); } return 0; } /* * bgp_bfd_peer_param_unset - Delete the configured BFD paramter values for * peer. */ static int bgp_bfd_peer_param_unset(struct peer *peer) { struct peer_group *group; struct listnode *node, *nnode; if (!peer->bfd_info) return 0; if (CHECK_FLAG(peer->sflags, PEER_STATUS_GROUP)) { bfd_info_free(&(peer->bfd_info)); group = peer->group; for (ALL_LIST_ELEMENTS(group->peer, node, nnode, peer)) { bgp_bfd_deregister_peer(peer); bfd_info_free(&(peer->bfd_info)); } } else { bgp_bfd_deregister_peer(peer); bfd_info_free(&(peer->bfd_info)); } return 0; } /* * bgp_bfd_peer_param_type_set - set the BFD session type (multihop or * singlehop) */ static int bgp_bfd_peer_param_type_set(struct peer *peer, enum bfd_sess_type type) { struct peer_group *group; struct listnode *node, *nnode; int command = 0; struct bfd_info *bfd_info; if (!peer->bfd_info) bfd_set_param((struct bfd_info **)&(peer->bfd_info), BFD_DEF_MIN_RX, BFD_DEF_MIN_TX, BFD_DEF_DETECT_MULT, 1, &command); bfd_info = (struct bfd_info *)peer->bfd_info; bfd_info->type = type; if (CHECK_FLAG(peer->sflags, PEER_STATUS_GROUP)) { group = peer->group; for (ALL_LIST_ELEMENTS(group->peer, node, nnode, peer)) { command = 0; if (!peer->bfd_info) bfd_set_param( (struct bfd_info **)&(peer->bfd_info), BFD_DEF_MIN_RX, BFD_DEF_MIN_TX, BFD_DEF_DETECT_MULT, 1, &command); bfd_info = (struct bfd_info *)peer->bfd_info; bfd_info->type = type; if (peer->status == Established) { if (command == ZEBRA_BFD_DEST_REGISTER) bgp_bfd_register_peer(peer); else bgp_bfd_update_type(peer); } } } else { if (peer->status == Established) { if (command == ZEBRA_BFD_DEST_REGISTER) bgp_bfd_register_peer(peer); else bgp_bfd_update_type(peer); } } return 0; } /* * bgp_bfd_peer_config_write - Write the peer BFD configuration. */ void bgp_bfd_peer_config_write(struct vty *vty, struct peer *peer, char *addr) { struct bfd_info *bfd_info; if (!peer->bfd_info) return; bfd_info = (struct bfd_info *)peer->bfd_info; if (CHECK_FLAG(bfd_info->flags, BFD_FLAG_PARAM_CFG)) #if HAVE_BFDD > 0 vty_out(vty, " neighbor %s bfd\n", addr); #else vty_out(vty, " neighbor %s bfd %d %d %d\n", addr, bfd_info->detect_mult, bfd_info->required_min_rx, bfd_info->desired_min_tx); #endif /* HAVE_BFDD */ if (bfd_info->type != BFD_TYPE_NOT_CONFIGURED) vty_out(vty, " neighbor %s bfd %s\n", addr, (bfd_info->type == BFD_TYPE_MULTIHOP) ? "multihop" : "singlehop"); if (!CHECK_FLAG(bfd_info->flags, BFD_FLAG_PARAM_CFG) && (bfd_info->type == BFD_TYPE_NOT_CONFIGURED)) vty_out(vty, " neighbor %s bfd\n", addr); if (CHECK_FLAG(bfd_info->flags, BFD_FLAG_BFD_CHECK_CONTROLPLANE)) vty_out(vty, " neighbor %s bfd check-control-plane-failure\n", addr); } /* * bgp_bfd_show_info - Show the peer BFD information. */ void bgp_bfd_show_info(struct vty *vty, struct peer *peer, bool use_json, json_object *json_neigh) { bfd_show_info(vty, (struct bfd_info *)peer->bfd_info, bgp_bfd_is_peer_multihop(peer), 0, use_json, json_neigh); } DEFUN (neighbor_bfd, neighbor_bfd_cmd, "neighbor bfd", NEIGHBOR_STR NEIGHBOR_ADDR_STR2 "Enables BFD support\n") { int idx_peer = 1; struct peer *peer; int ret; peer = peer_and_group_lookup_vty(vty, argv[idx_peer]->arg); if (!peer) return CMD_WARNING_CONFIG_FAILED; ret = bgp_bfd_peer_param_set(peer, BFD_DEF_MIN_RX, BFD_DEF_MIN_TX, BFD_DEF_DETECT_MULT, 1); if (ret != 0) return bgp_vty_return(vty, ret); return CMD_SUCCESS; } #if HAVE_BFDD > 0 DEFUN_HIDDEN( #else DEFUN( #endif /* HAVE_BFDD */ neighbor_bfd_param, neighbor_bfd_param_cmd, "neighbor bfd (2-255) (50-60000) (50-60000)", NEIGHBOR_STR NEIGHBOR_ADDR_STR2 "Enables BFD support\n" "Detect Multiplier\n" "Required min receive interval\n" "Desired min transmit interval\n") { int idx_peer = 1; int idx_number_1 = 3; int idx_number_2 = 4; int idx_number_3 = 5; struct peer *peer; uint32_t rx_val; uint32_t tx_val; uint8_t dm_val; int ret; peer = peer_and_group_lookup_vty(vty, argv[idx_peer]->arg); if (!peer) return CMD_WARNING_CONFIG_FAILED; if ((ret = bfd_validate_param( vty, argv[idx_number_1]->arg, argv[idx_number_2]->arg, argv[idx_number_3]->arg, &dm_val, &rx_val, &tx_val)) != CMD_SUCCESS) return ret; ret = bgp_bfd_peer_param_set(peer, rx_val, tx_val, dm_val, 0); if (ret != 0) return bgp_vty_return(vty, ret); return CMD_SUCCESS; } DEFUN_HIDDEN (neighbor_bfd_type, neighbor_bfd_type_cmd, "neighbor bfd ", NEIGHBOR_STR NEIGHBOR_ADDR_STR2 "Enables BFD support\n" "Multihop session\n" "Single hop session\n") { int idx_peer = 1; int idx_hop = 3; struct peer *peer; enum bfd_sess_type type; int ret; peer = peer_and_group_lookup_vty(vty, argv[idx_peer]->arg); if (!peer) return CMD_WARNING_CONFIG_FAILED; if (strmatch(argv[idx_hop]->text, "singlehop")) type = BFD_TYPE_SINGLEHOP; else if (strmatch(argv[idx_hop]->text, "multihop")) type = BFD_TYPE_MULTIHOP; else return CMD_WARNING_CONFIG_FAILED; ret = bgp_bfd_peer_param_type_set(peer, type); if (ret != 0) return bgp_vty_return(vty, ret); return CMD_SUCCESS; } static int bgp_bfd_set_check_controlplane_failure_peer(struct vty *vty, struct peer *peer, const char *no) { struct bfd_info *bfd_info; if (!peer->bfd_info) { if (no) return CMD_SUCCESS; vty_out(vty, "%% Specify bfd command first\n"); return CMD_WARNING_CONFIG_FAILED; } bfd_info = (struct bfd_info *)peer->bfd_info; if (!no) { if (!CHECK_FLAG(bfd_info->flags, BFD_FLAG_BFD_CHECK_CONTROLPLANE)) { SET_FLAG(bfd_info->flags, BFD_FLAG_BFD_CHECK_CONTROLPLANE); bgp_bfd_update_peer(peer); } } else { if (CHECK_FLAG(bfd_info->flags, BFD_FLAG_BFD_CHECK_CONTROLPLANE)) { UNSET_FLAG(bfd_info->flags, BFD_FLAG_BFD_CHECK_CONTROLPLANE); bgp_bfd_update_peer(peer); } } return CMD_SUCCESS; } DEFUN (neighbor_bfd_check_controlplane_failure, neighbor_bfd_check_controlplane_failure_cmd, "[no] neighbor bfd check-control-plane-failure", NO_STR NEIGHBOR_STR NEIGHBOR_ADDR_STR2 "BFD support\n" "Link dataplane status with BGP controlplane\n") { const char *no = strmatch(argv[0]->text, "no") ? "no" : NULL; int idx_peer = 0; struct peer *peer; struct peer_group *group; struct listnode *node, *nnode; int ret = CMD_SUCCESS; if (no) idx_peer = 2; else idx_peer = 1; peer = peer_and_group_lookup_vty(vty, argv[idx_peer]->arg); if (!peer) { vty_out(vty, "%% Specify remote-as or peer-group commands first\n"); return CMD_WARNING_CONFIG_FAILED; } if (!peer->bfd_info) { if (no) return CMD_SUCCESS; vty_out(vty, "%% Specify bfd command first\n"); return CMD_WARNING_CONFIG_FAILED; } if (CHECK_FLAG(peer->sflags, PEER_STATUS_GROUP)) { group = peer->group; for (ALL_LIST_ELEMENTS(group->peer, node, nnode, peer)) ret = bgp_bfd_set_check_controlplane_failure_peer(vty, peer, no); } else ret = bgp_bfd_set_check_controlplane_failure_peer(vty, peer, no); return ret; } DEFUN (no_neighbor_bfd, no_neighbor_bfd_cmd, #if HAVE_BFDD > 0 "no neighbor bfd", #else "no neighbor bfd [(2-255) (50-60000) (50-60000)]", #endif /* HAVE_BFDD */ NO_STR NEIGHBOR_STR NEIGHBOR_ADDR_STR2 "Disables BFD support\n" #if HAVE_BFDD == 0 "Detect Multiplier\n" "Required min receive interval\n" "Desired min transmit interval\n" #endif /* !HAVE_BFDD */ ) { int idx_peer = 2; struct peer *peer; int ret; peer = peer_and_group_lookup_vty(vty, argv[idx_peer]->arg); if (!peer) return CMD_WARNING_CONFIG_FAILED; ret = bgp_bfd_peer_param_unset(peer); if (ret != 0) return bgp_vty_return(vty, ret); return CMD_SUCCESS; } DEFUN_HIDDEN (no_neighbor_bfd_type, no_neighbor_bfd_type_cmd, "no neighbor bfd ", NO_STR NEIGHBOR_STR NEIGHBOR_ADDR_STR2 "Disables BFD support\n" "Multihop session\n" "Singlehop session\n") { int idx_peer = 2; struct peer *peer; int ret; peer = peer_and_group_lookup_vty(vty, argv[idx_peer]->arg); if (!peer) return CMD_WARNING_CONFIG_FAILED; if (!peer->bfd_info) return 0; ret = bgp_bfd_peer_param_type_set(peer, BFD_TYPE_NOT_CONFIGURED); if (ret != 0) return bgp_vty_return(vty, ret); return CMD_SUCCESS; } void bgp_bfd_init(void) { bfd_gbl_init(); /* Initialize BFD client functions */ zclient->interface_bfd_dest_update = bgp_bfd_dest_update; zclient->bfd_dest_replay = bgp_bfd_dest_replay; /* "neighbor bfd" commands. */ install_element(BGP_NODE, &neighbor_bfd_cmd); install_element(BGP_NODE, &neighbor_bfd_param_cmd); install_element(BGP_NODE, &neighbor_bfd_type_cmd); install_element(BGP_NODE, &neighbor_bfd_check_controlplane_failure_cmd); install_element(BGP_NODE, &no_neighbor_bfd_cmd); install_element(BGP_NODE, &no_neighbor_bfd_type_cmd); } frr-7.2.1/bgpd/bgp_bfd.h0000644000000000000000000000265513610377563011674 00000000000000/** * bgp_bfd.h: BGP BFD definitions and structures * * @copyright Copyright (C) 2015 Cumulus Networks, Inc. * * This file is part of GNU Zebra. * * GNU Zebra is free software; you can redistribute it and/or modify it * under the terms of the GNU General Public License as published by the * Free Software Foundation; either version 2, or (at your option) any * later version. * * GNU Zebra 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; see the file COPYING; if not, write to the Free Software * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA */ #ifndef _QUAGGA_BGP_BFD_H #define _QUAGGA_BGP_BFD_H extern void bgp_bfd_init(void); extern void bgp_bfd_peer_group2peer_copy(struct peer *conf, struct peer *peer); extern void bgp_bfd_register_peer(struct peer *peer); extern void bgp_bfd_deregister_peer(struct peer *peer); extern void bgp_bfd_peer_config_write(struct vty *vty, struct peer *peer, char *addr); extern void bgp_bfd_show_info(struct vty *vty, struct peer *peer, bool use_json, json_object *json_neigh); extern int bgp_bfd_is_peer_multihop(struct peer *peer); #endif /* _QUAGGA_BGP_BFD_H */ frr-7.2.1/bgpd/bgp_bmp.c0000644000000000000000000015154613610377563011716 00000000000000/* BMP support. * Copyright (C) 2018 Yasuhiro Ohara * Copyright (C) 2019 David Lamparter for NetDEF, Inc. * * This program is free software; you can redistribute it and/or modify it * under the terms of the 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; see the file COPYING; if not, write to the Free Software * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA */ #include #include "log.h" #include "stream.h" #include "sockunion.h" #include "command.h" #include "prefix.h" #include "thread.h" #include "linklist.h" #include "queue.h" #include "pullwr.h" #include "memory.h" #include "network.h" #include "filter.h" #include "lib_errors.h" #include "stream.h" #include "libfrr.h" #include "version.h" #include "jhash.h" #include "termtable.h" #include "bgpd/bgp_table.h" #include "bgpd/bgpd.h" #include "bgpd/bgp_route.h" #include "bgpd/bgp_attr.h" #include "bgpd/bgp_dump.h" #include "bgpd/bgp_errors.h" #include "bgpd/bgp_packet.h" #include "bgpd/bgp_bmp.h" #include "bgpd/bgp_fsm.h" #include "bgpd/bgp_updgrp.h" #include "bgpd/bgp_vty.h" static void bmp_close(struct bmp *bmp); static struct bmp_bgp *bmp_bgp_find(struct bgp *bgp); static void bmp_targets_put(struct bmp_targets *bt); static struct bmp_bgp_peer *bmp_bgp_peer_find(uint64_t peerid); static struct bmp_bgp_peer *bmp_bgp_peer_get(struct peer *peer); static void bmp_active_disconnected(struct bmp_active *ba); static void bmp_active_put(struct bmp_active *ba); DEFINE_MGROUP(BMP, "BMP (BGP Monitoring Protocol)") DEFINE_MTYPE_STATIC(BMP, BMP_CONN, "BMP connection state") DEFINE_MTYPE_STATIC(BMP, BMP_TARGETS, "BMP targets") DEFINE_MTYPE_STATIC(BMP, BMP_TARGETSNAME, "BMP targets name") DEFINE_MTYPE_STATIC(BMP, BMP_LISTENER, "BMP listener") DEFINE_MTYPE_STATIC(BMP, BMP_ACTIVE, "BMP active connection config") DEFINE_MTYPE_STATIC(BMP, BMP_ACLNAME, "BMP access-list name") DEFINE_MTYPE_STATIC(BMP, BMP_QUEUE, "BMP update queue item") DEFINE_MTYPE_STATIC(BMP, BMP, "BMP instance state") DEFINE_MTYPE_STATIC(BMP, BMP_MIRRORQ, "BMP route mirroring buffer") DEFINE_MTYPE_STATIC(BMP, BMP_PEER, "BMP per BGP peer data") DEFINE_MTYPE_STATIC(BMP, BMP_OPEN, "BMP stored BGP OPEN message") DEFINE_QOBJ_TYPE(bmp_targets) static int bmp_bgp_cmp(const struct bmp_bgp *a, const struct bmp_bgp *b) { if (a->bgp < b->bgp) return -1; if (a->bgp > b->bgp) return 1; return 0; } static uint32_t bmp_bgp_hash(const struct bmp_bgp *e) { return jhash(&e->bgp, sizeof(e->bgp), 0x55aa5a5a); } DECLARE_HASH(bmp_bgph, struct bmp_bgp, bbi, bmp_bgp_cmp, bmp_bgp_hash) struct bmp_bgph_head bmp_bgph; static int bmp_bgp_peer_cmp(const struct bmp_bgp_peer *a, const struct bmp_bgp_peer *b) { if (a->peerid < b->peerid) return -1; if (a->peerid > b->peerid) return 1; return 0; } static uint32_t bmp_bgp_peer_hash(const struct bmp_bgp_peer *e) { return e->peerid; } DECLARE_HASH(bmp_peerh, struct bmp_bgp_peer, bpi, bmp_bgp_peer_cmp, bmp_bgp_peer_hash) struct bmp_peerh_head bmp_peerh; DECLARE_LIST(bmp_mirrorq, struct bmp_mirrorq, bmi) /* listener management */ static int bmp_listener_cmp(const struct bmp_listener *a, const struct bmp_listener *b) { int c; c = sockunion_cmp(&a->addr, &b->addr); if (c) return c; if (a->port < b->port) return -1; if (a->port > b->port) return 1; return 0; } DECLARE_SORTLIST_UNIQ(bmp_listeners, struct bmp_listener, bli, bmp_listener_cmp) static int bmp_targets_cmp(const struct bmp_targets *a, const struct bmp_targets *b) { return strcmp(a->name, b->name); } DECLARE_SORTLIST_UNIQ(bmp_targets, struct bmp_targets, bti, bmp_targets_cmp) DECLARE_LIST(bmp_session, struct bmp, bsi) DECLARE_DLIST(bmp_qlist, struct bmp_queue_entry, bli) static int bmp_qhash_cmp(const struct bmp_queue_entry *a, const struct bmp_queue_entry *b) { int ret; ret = prefix_cmp(&a->p, &b->p); if (ret) return ret; ret = memcmp(&a->peerid, &b->peerid, offsetof(struct bmp_queue_entry, refcount) - offsetof(struct bmp_queue_entry, peerid)); return ret; } static uint32_t bmp_qhash_hkey(const struct bmp_queue_entry *e) { uint32_t key; key = prefix_hash_key((void *)&e->p); key = jhash(&e->peerid, offsetof(struct bmp_queue_entry, refcount) - offsetof(struct bmp_queue_entry, peerid), key); return key; } DECLARE_HASH(bmp_qhash, struct bmp_queue_entry, bhi, bmp_qhash_cmp, bmp_qhash_hkey) static int bmp_active_cmp(const struct bmp_active *a, const struct bmp_active *b) { int c; c = strcmp(a->hostname, b->hostname); if (c) return c; if (a->port < b->port) return -1; if (a->port > b->port) return 1; return 0; } DECLARE_SORTLIST_UNIQ(bmp_actives, struct bmp_active, bai, bmp_active_cmp) static struct bmp *bmp_new(struct bmp_targets *bt, int bmp_sock) { struct bmp *new = XCALLOC(MTYPE_BMP_CONN, sizeof(struct bmp)); afi_t afi; safi_t safi; monotime(&new->t_up); new->targets = bt; new->socket = bmp_sock; new->syncafi = AFI_MAX; FOREACH_AFI_SAFI (afi, safi) { new->afistate[afi][safi] = bt->afimon[afi][safi] ? BMP_AFI_NEEDSYNC : BMP_AFI_INACTIVE; } bmp_session_add_tail(&bt->sessions, new); return new; } static void bmp_free(struct bmp *bmp) { bmp_session_del(&bmp->targets->sessions, bmp); XFREE(MTYPE_BMP_CONN, bmp); } static void bmp_common_hdr(struct stream *s, uint8_t ver, uint8_t type) { stream_putc(s, ver); stream_putl(s, 0); //dummy message length. will be set later. stream_putc(s, type); } static void bmp_per_peer_hdr(struct stream *s, struct peer *peer, uint8_t flags, const struct timeval *tv) { char peer_distinguisher[8]; #define BMP_PEER_TYPE_GLOBAL_INSTANCE 0 #define BMP_PEER_TYPE_RD_INSTANCE 1 #define BMP_PEER_TYPE_LOCAL_INSTANCE 2 #define BMP_PEER_FLAG_V (1 << 7) #define BMP_PEER_FLAG_L (1 << 6) #define BMP_PEER_FLAG_A (1 << 5) /* Peer Type */ stream_putc(s, BMP_PEER_TYPE_GLOBAL_INSTANCE); /* Peer Flags */ if (peer->su.sa.sa_family == AF_INET6) SET_FLAG(flags, BMP_PEER_FLAG_V); else UNSET_FLAG(flags, BMP_PEER_FLAG_V); stream_putc(s, flags); /* Peer Distinguisher */ memset (&peer_distinguisher[0], 0, 8); stream_put(s, &peer_distinguisher[0], 8); /* Peer Address */ if (peer->su.sa.sa_family == AF_INET6) stream_put(s, &peer->su.sin6.sin6_addr, 16); else if (peer->su.sa.sa_family == AF_INET) { stream_putl(s, 0); stream_putl(s, 0); stream_putl(s, 0); stream_put_in_addr(s, &peer->su.sin.sin_addr); } else { stream_putl(s, 0); stream_putl(s, 0); stream_putl(s, 0); stream_putl(s, 0); } /* Peer AS */ stream_putl(s, peer->as); /* Peer BGP ID */ stream_put_in_addr(s, &peer->remote_id); /* Timestamp */ if (tv) { stream_putl(s, tv->tv_sec); stream_putl(s, tv->tv_usec); } else { stream_putl(s, 0); stream_putl(s, 0); } } static void bmp_put_info_tlv(struct stream *s, uint16_t type, const char *string) { int len = strlen (string); stream_putw(s, type); stream_putw(s, len); stream_put(s, string, len); } static int bmp_send_initiation(struct bmp *bmp) { int len; struct stream *s; s = stream_new(BGP_MAX_PACKET_SIZE); bmp_common_hdr(s, BMP_VERSION_3, BMP_TYPE_INITIATION); #define BMP_INFO_TYPE_SYSDESCR 1 #define BMP_INFO_TYPE_SYSNAME 2 bmp_put_info_tlv(s, BMP_INFO_TYPE_SYSDESCR, FRR_FULL_NAME " " FRR_VER_SHORT); bmp_put_info_tlv(s, BMP_INFO_TYPE_SYSNAME, cmd_hostname_get()); len = stream_get_endp(s); stream_putl_at(s, BMP_LENGTH_POS, len); //message length is set. pullwr_write_stream(bmp->pullwr, s); stream_free(s); return 0; } static void bmp_notify_put(struct stream *s, struct bgp_notify *nfy) { size_t len_pos; uint8_t marker[16] = { 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, }; stream_put(s, marker, sizeof(marker)); len_pos = stream_get_endp(s); stream_putw(s, 0); stream_putc(s, BGP_MSG_NOTIFY); stream_putc(s, nfy->code); stream_putc(s, nfy->subcode); stream_put(s, nfy->data, nfy->length); stream_putw_at(s, len_pos, stream_get_endp(s) - len_pos + sizeof(marker)); } static struct stream *bmp_peerstate(struct peer *peer, bool down) { struct stream *s; size_t len; struct timeval uptime, uptime_real; uptime.tv_sec = peer->uptime; uptime.tv_usec = 0; monotime_to_realtime(&uptime, &uptime_real); #define BGP_BMP_MAX_PACKET_SIZE 1024 s = stream_new(BGP_MAX_PACKET_SIZE); if (peer->status == Established && !down) { struct bmp_bgp_peer *bbpeer; bmp_common_hdr(s, BMP_VERSION_3, BMP_TYPE_PEER_UP_NOTIFICATION); bmp_per_peer_hdr(s, peer, 0, &uptime_real); /* Local Address (16 bytes) */ if (peer->su_local->sa.sa_family == AF_INET6) stream_put(s, &peer->su_local->sin6.sin6_addr, 16); else if (peer->su_local->sa.sa_family == AF_INET) { stream_putl(s, 0); stream_putl(s, 0); stream_putl(s, 0); stream_put_in_addr(s, &peer->su_local->sin.sin_addr); } /* Local Port, Remote Port */ if (peer->su_local->sa.sa_family == AF_INET6) stream_putw(s, peer->su_local->sin6.sin6_port); else if (peer->su_local->sa.sa_family == AF_INET) stream_putw(s, peer->su_local->sin.sin_port); if (peer->su_remote->sa.sa_family == AF_INET6) stream_putw(s, peer->su_remote->sin6.sin6_port); else if (peer->su_remote->sa.sa_family == AF_INET) stream_putw(s, peer->su_remote->sin.sin_port); static const uint8_t dummy_open[] = { 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0x00, 0x13, 0x01, }; bbpeer = bmp_bgp_peer_find(peer->qobj_node.nid); if (bbpeer && bbpeer->open_tx) stream_put(s, bbpeer->open_tx, bbpeer->open_tx_len); else { stream_put(s, dummy_open, sizeof(dummy_open)); zlog_warn("bmp: missing TX OPEN message for peer %s\n", peer->host); } if (bbpeer && bbpeer->open_rx) stream_put(s, bbpeer->open_rx, bbpeer->open_rx_len); else { stream_put(s, dummy_open, sizeof(dummy_open)); zlog_warn("bmp: missing RX OPEN message for peer %s\n", peer->host); } if (peer->desc) bmp_put_info_tlv(s, 0, peer->desc); } else { uint8_t type; size_t type_pos; bmp_common_hdr(s, BMP_VERSION_3, BMP_TYPE_PEER_DOWN_NOTIFICATION); bmp_per_peer_hdr(s, peer, 0, &uptime_real); type_pos = stream_get_endp(s); stream_putc(s, 0); /* placeholder for down reason */ switch (peer->last_reset) { case PEER_DOWN_NOTIFY_RECEIVED: type = BMP_PEERDOWN_REMOTE_NOTIFY; bmp_notify_put(s, &peer->notify); break; case PEER_DOWN_CLOSE_SESSION: type = BMP_PEERDOWN_REMOTE_CLOSE; break; default: type = BMP_PEERDOWN_LOCAL_NOTIFY; stream_put(s, peer->last_reset_cause, peer->last_reset_cause_size); break; } stream_putc_at(s, type_pos, type); } len = stream_get_endp(s); stream_putl_at(s, BMP_LENGTH_POS, len); //message length is set. return s; } static int bmp_send_peerup(struct bmp *bmp) { struct peer *peer; struct listnode *node; struct stream *s; /* Walk down all peers */ for (ALL_LIST_ELEMENTS_RO(bmp->targets->bgp->peer, node, peer)) { s = bmp_peerstate(peer, false); pullwr_write_stream(bmp->pullwr, s); stream_free(s); } return 0; } /* XXX: kludge - filling the pullwr's buffer */ static void bmp_send_all(struct bmp_bgp *bmpbgp, struct stream *s) { struct bmp_targets *bt; struct bmp *bmp; frr_each(bmp_targets, &bmpbgp->targets, bt) frr_each(bmp_session, &bt->sessions, bmp) pullwr_write_stream(bmp->pullwr, s); stream_free(s); } /* * Route Mirroring */ #define BMP_MIRROR_TLV_TYPE_BGP_MESSAGE 0 #define BMP_MIRROR_TLV_TYPE_INFO 1 #define BMP_MIRROR_INFO_CODE_ERRORPDU 0 #define BMP_MIRROR_INFO_CODE_LOSTMSGS 1 static struct bmp_mirrorq *bmp_pull_mirror(struct bmp *bmp) { struct bmp_mirrorq *bmq; bmq = bmp->mirrorpos; if (!bmq) return NULL; bmp->mirrorpos = bmp_mirrorq_next(&bmp->targets->bmpbgp->mirrorq, bmq); bmq->refcount--; if (!bmq->refcount) { bmp->targets->bmpbgp->mirror_qsize -= sizeof(*bmq) + bmq->len; bmp_mirrorq_del(&bmp->targets->bmpbgp->mirrorq, bmq); } return bmq; } static void bmp_mirror_cull(struct bmp_bgp *bmpbgp) { while (bmpbgp->mirror_qsize > bmpbgp->mirror_qsizelimit) { struct bmp_mirrorq *bmq, *inner; struct bmp_targets *bt; struct bmp *bmp; bmq = bmp_mirrorq_first(&bmpbgp->mirrorq); frr_each(bmp_targets, &bmpbgp->targets, bt) { if (!bt->mirror) continue; frr_each(bmp_session, &bt->sessions, bmp) { if (bmp->mirrorpos != bmq) continue; while ((inner = bmp_pull_mirror(bmp))) { if (!inner->refcount) XFREE(MTYPE_BMP_MIRRORQ, inner); } zlog_warn("bmp[%s] lost mirror messages due to buffer size limit", bmp->remote); bmp->mirror_lost = true; pullwr_bump(bmp->pullwr); } } } } static int bmp_mirror_packet(struct peer *peer, uint8_t type, bgp_size_t size, struct stream *packet) { struct bmp_bgp *bmpbgp = bmp_bgp_find(peer->bgp); struct timeval tv; struct bmp_mirrorq *qitem; struct bmp_targets *bt; struct bmp *bmp; gettimeofday(&tv, NULL); if (type == BGP_MSG_OPEN) { struct bmp_bgp_peer *bbpeer = bmp_bgp_peer_get(peer); XFREE(MTYPE_BMP_OPEN, bbpeer->open_rx); bbpeer->open_rx_len = size; bbpeer->open_rx = XMALLOC(MTYPE_BMP_OPEN, size); memcpy(bbpeer->open_rx, packet->data, size); } if (!bmpbgp) return 0; qitem = XCALLOC(MTYPE_BMP_MIRRORQ, sizeof(*qitem) + size); qitem->peerid = peer->qobj_node.nid; qitem->tv = tv; qitem->len = size; memcpy(qitem->data, packet->data, size); frr_each(bmp_targets, &bmpbgp->targets, bt) { if (!bt->mirror) continue; frr_each(bmp_session, &bt->sessions, bmp) { qitem->refcount++; if (!bmp->mirrorpos) bmp->mirrorpos = qitem; pullwr_bump(bmp->pullwr); } } if (qitem->refcount == 0) XFREE(MTYPE_BMP_MIRRORQ, qitem); else { bmpbgp->mirror_qsize += sizeof(*qitem) + size; bmp_mirrorq_add_tail(&bmpbgp->mirrorq, qitem); bmp_mirror_cull(bmpbgp); bmpbgp->mirror_qsizemax = MAX(bmpbgp->mirror_qsizemax, bmpbgp->mirror_qsize); } return 0; } static void bmp_wrmirror_lost(struct bmp *bmp, struct pullwr *pullwr) { struct stream *s; struct timeval tv; gettimeofday(&tv, NULL); s = stream_new(BGP_MAX_PACKET_SIZE); bmp_common_hdr(s, BMP_VERSION_3, BMP_TYPE_ROUTE_MIRRORING); bmp_per_peer_hdr(s, bmp->targets->bgp->peer_self, 0, &tv); stream_putw(s, BMP_MIRROR_TLV_TYPE_INFO); stream_putw(s, 2); stream_putw(s, BMP_MIRROR_INFO_CODE_LOSTMSGS); stream_putl_at(s, BMP_LENGTH_POS, stream_get_endp(s)); bmp->cnt_mirror_overruns++; pullwr_write_stream(bmp->pullwr, s); stream_free(s); } static bool bmp_wrmirror(struct bmp *bmp, struct pullwr *pullwr) { struct bmp_mirrorq *bmq; struct peer *peer; bool written = false; if (bmp->mirror_lost) { bmp_wrmirror_lost(bmp, pullwr); bmp->mirror_lost = false; return true; } bmq = bmp_pull_mirror(bmp); if (!bmq) return false; peer = QOBJ_GET_TYPESAFE(bmq->peerid, peer); if (!peer) { zlog_info("bmp: skipping mirror message for deleted peer"); goto out; } struct stream *s; s = stream_new(BGP_MAX_PACKET_SIZE); bmp_common_hdr(s, BMP_VERSION_3, BMP_TYPE_ROUTE_MIRRORING); bmp_per_peer_hdr(s, peer, 0, &bmq->tv); /* BMP Mirror TLV. */ stream_putw(s, BMP_MIRROR_TLV_TYPE_BGP_MESSAGE); stream_putw(s, bmq->len); stream_putl_at(s, BMP_LENGTH_POS, stream_get_endp(s) + bmq->len); bmp->cnt_mirror++; pullwr_write_stream(bmp->pullwr, s); pullwr_write(bmp->pullwr, bmq->data, bmq->len); stream_free(s); written = true; out: if (!bmq->refcount) XFREE(MTYPE_BMP_MIRRORQ, bmq); return written; } static int bmp_outgoing_packet(struct peer *peer, uint8_t type, bgp_size_t size, struct stream *packet) { if (type == BGP_MSG_OPEN) { struct bmp_bgp_peer *bbpeer = bmp_bgp_peer_get(peer); XFREE(MTYPE_BMP_OPEN, bbpeer->open_tx); bbpeer->open_tx_len = size; bbpeer->open_tx = XMALLOC(MTYPE_BMP_OPEN, size); memcpy(bbpeer->open_tx, packet->data, size); } return 0; } static int bmp_peer_established(struct peer *peer) { struct bmp_bgp *bmpbgp = bmp_bgp_find(peer->bgp); if (!bmpbgp) return 0; if (peer->doppelganger && (peer->doppelganger->status != Deleted)) { struct bmp_bgp_peer *bbpeer, *bbdopp; bbpeer = bmp_bgp_peer_get(peer); bbdopp = bmp_bgp_peer_find(peer->doppelganger->qobj_node.nid); if (bbdopp) { XFREE(MTYPE_BMP_OPEN, bbpeer->open_tx); XFREE(MTYPE_BMP_OPEN, bbpeer->open_rx); bbpeer->open_tx = bbdopp->open_tx; bbpeer->open_tx_len = bbdopp->open_tx_len; bbpeer->open_rx = bbdopp->open_rx; bbpeer->open_rx_len = bbdopp->open_rx_len; bmp_peerh_del(&bmp_peerh, bbdopp); XFREE(MTYPE_BMP_PEER, bbdopp); } } bmp_send_all(bmpbgp, bmp_peerstate(peer, false)); return 0; } static int bmp_peer_backward(struct peer *peer) { struct bmp_bgp *bmpbgp = bmp_bgp_find(peer->bgp); struct bmp_bgp_peer *bbpeer; if (!bmpbgp) return 0; bbpeer = bmp_bgp_peer_find(peer->qobj_node.nid); if (bbpeer) { XFREE(MTYPE_BMP_OPEN, bbpeer->open_tx); bbpeer->open_tx_len = 0; XFREE(MTYPE_BMP_OPEN, bbpeer->open_rx); bbpeer->open_rx_len = 0; } bmp_send_all(bmpbgp, bmp_peerstate(peer, true)); return 0; } static void bmp_eor(struct bmp *bmp, afi_t afi, safi_t safi, uint8_t flags) { struct peer *peer; struct listnode *node; struct stream *s, *s2; iana_afi_t pkt_afi; iana_safi_t pkt_safi; s = stream_new(BGP_MAX_PACKET_SIZE); /* Make BGP update packet. */ bgp_packet_set_marker(s, BGP_MSG_UPDATE); /* Unfeasible Routes Length */ stream_putw(s, 0); if (afi == AFI_IP && safi == SAFI_UNICAST) { /* Total Path Attribute Length */ stream_putw(s, 0); } else { /* Convert AFI, SAFI to values for packet. */ bgp_map_afi_safi_int2iana(afi, safi, &pkt_afi, &pkt_safi); /* Total Path Attribute Length */ stream_putw(s, 6); stream_putc(s, BGP_ATTR_FLAG_OPTIONAL); stream_putc(s, BGP_ATTR_MP_UNREACH_NLRI); stream_putc(s, 3); stream_putw(s, pkt_afi); stream_putc(s, pkt_safi); } bgp_packet_set_size(s); for (ALL_LIST_ELEMENTS_RO(bmp->targets->bgp->peer, node, peer)) { if (!peer->afc_nego[afi][safi]) continue; s2 = stream_new(BGP_MAX_PACKET_SIZE); bmp_common_hdr(s2, BMP_VERSION_3, BMP_TYPE_ROUTE_MONITORING); bmp_per_peer_hdr(s2, peer, flags, NULL); stream_putl_at(s2, BMP_LENGTH_POS, stream_get_endp(s) + stream_get_endp(s2)); bmp->cnt_update++; pullwr_write_stream(bmp->pullwr, s2); pullwr_write_stream(bmp->pullwr, s); stream_free(s2); } stream_free(s); } static struct stream *bmp_update(struct prefix *p, struct peer *peer, struct attr *attr, afi_t afi, safi_t safi) { struct bpacket_attr_vec_arr vecarr; struct stream *s; size_t attrlen_pos = 0, mpattrlen_pos = 0; bgp_size_t total_attr_len = 0; bpacket_attr_vec_arr_reset(&vecarr); s = stream_new(BGP_MAX_PACKET_SIZE); bgp_packet_set_marker(s, BGP_MSG_UPDATE); /* 2: withdrawn routes length */ stream_putw(s, 0); /* 3: total attributes length - attrlen_pos stores the position */ attrlen_pos = stream_get_endp(s); stream_putw(s, 0); /* 5: Encode all the attributes, except MP_REACH_NLRI attr. */ total_attr_len = bgp_packet_attribute(NULL, peer, s, attr, &vecarr, NULL, afi, safi, peer, NULL, NULL, 0, 0, 0); /* space check? */ /* peer_cap_enhe & add-path removed */ if (afi == AFI_IP && safi == SAFI_UNICAST) stream_put_prefix(s, p); else { size_t p1 = stream_get_endp(s); /* MPLS removed for now */ mpattrlen_pos = bgp_packet_mpattr_start(s, peer, afi, safi, &vecarr, attr); bgp_packet_mpattr_prefix(s, afi, safi, p, NULL, NULL, 0, 0, 0, attr); bgp_packet_mpattr_end(s, mpattrlen_pos); total_attr_len += stream_get_endp(s) - p1; } /* set the total attribute length correctly */ stream_putw_at(s, attrlen_pos, total_attr_len); bgp_packet_set_size(s); return s; } static struct stream *bmp_withdraw(struct prefix *p, afi_t afi, safi_t safi) { struct stream *s; size_t attrlen_pos = 0, mp_start, mplen_pos; bgp_size_t total_attr_len = 0; bgp_size_t unfeasible_len; s = stream_new(BGP_MAX_PACKET_SIZE); bgp_packet_set_marker(s, BGP_MSG_UPDATE); stream_putw(s, 0); if (afi == AFI_IP && safi == SAFI_UNICAST) { stream_put_prefix(s, p); unfeasible_len = stream_get_endp(s) - BGP_HEADER_SIZE - BGP_UNFEASIBLE_LEN; stream_putw_at(s, BGP_HEADER_SIZE, unfeasible_len); stream_putw(s, 0); } else { attrlen_pos = stream_get_endp(s); /* total attr length = 0 for now. reevaluate later */ stream_putw(s, 0); mp_start = stream_get_endp(s); mplen_pos = bgp_packet_mpunreach_start(s, afi, safi); bgp_packet_mpunreach_prefix(s, p, afi, safi, NULL, NULL, 0, 0, 0, NULL); /* Set the mp_unreach attr's length */ bgp_packet_mpunreach_end(s, mplen_pos); /* Set total path attribute length. */ total_attr_len = stream_get_endp(s) - mp_start; stream_putw_at(s, attrlen_pos, total_attr_len); } bgp_packet_set_size(s); return s; } static void bmp_monitor(struct bmp *bmp, struct peer *peer, uint8_t flags, struct prefix *p, struct attr *attr, afi_t afi, safi_t safi, time_t uptime) { struct stream *hdr, *msg; struct timeval tv = { .tv_sec = uptime, .tv_usec = 0 }; if (attr) msg = bmp_update(p, peer, attr, afi, safi); else msg = bmp_withdraw(p, afi, safi); hdr = stream_new(BGP_MAX_PACKET_SIZE); bmp_common_hdr(hdr, BMP_VERSION_3, BMP_TYPE_ROUTE_MONITORING); bmp_per_peer_hdr(hdr, peer, flags, &tv); stream_putl_at(hdr, BMP_LENGTH_POS, stream_get_endp(hdr) + stream_get_endp(msg)); bmp->cnt_update++; pullwr_write_stream(bmp->pullwr, hdr); pullwr_write_stream(bmp->pullwr, msg); stream_free(hdr); stream_free(msg); } static bool bmp_wrsync(struct bmp *bmp, struct pullwr *pullwr) { afi_t afi; safi_t safi; if (bmp->syncafi == AFI_MAX) { FOREACH_AFI_SAFI (afi, safi) { if (bmp->afistate[afi][safi] != BMP_AFI_NEEDSYNC) continue; bmp->afistate[afi][safi] = BMP_AFI_SYNC; bmp->syncafi = afi; bmp->syncsafi = safi; bmp->syncpeerid = 0; memset(&bmp->syncpos, 0, sizeof(bmp->syncpos)); bmp->syncpos.family = afi2family(afi); zlog_info("bmp[%s] %s %s sending table", bmp->remote, afi2str(bmp->syncafi), safi2str(bmp->syncsafi)); /* break does not work here, 2 loops... */ goto afibreak; } if (bmp->syncafi == AFI_MAX) return false; } afibreak: afi = bmp->syncafi; safi = bmp->syncsafi; if (!bmp->targets->afimon[afi][safi]) { /* shouldn't happen */ bmp->afistate[afi][safi] = BMP_AFI_INACTIVE; bmp->syncafi = AFI_MAX; bmp->syncsafi = SAFI_MAX; return true; } struct bgp_table *table = bmp->targets->bgp->rib[afi][safi]; struct bgp_node *bn; struct bgp_path_info *bpi = NULL, *bpiter; struct bgp_adj_in *adjin = NULL, *adjiter; bn = bgp_node_lookup(table, &bmp->syncpos); do { if (!bn) { bn = bgp_table_get_next(table, &bmp->syncpos); if (!bn) { zlog_info("bmp[%s] %s %s table completed (EoR)", bmp->remote, afi2str(afi), safi2str(safi)); bmp_eor(bmp, afi, safi, BMP_PEER_FLAG_L); bmp_eor(bmp, afi, safi, 0); bmp->afistate[afi][safi] = BMP_AFI_LIVE; bmp->syncafi = AFI_MAX; bmp->syncsafi = SAFI_MAX; return true; } bmp->syncpeerid = 0; prefix_copy(&bmp->syncpos, &bn->p); } if (bmp->targets->afimon[afi][safi] & BMP_MON_POSTPOLICY) { for (bpiter = bn->info; bpiter; bpiter = bpiter->next) { if (!CHECK_FLAG(bpiter->flags, BGP_PATH_VALID)) continue; if (bpiter->peer->qobj_node.nid <= bmp->syncpeerid) continue; if (bpi && bpiter->peer->qobj_node.nid > bpi->peer->qobj_node.nid) continue; bpi = bpiter; } } if (bmp->targets->afimon[afi][safi] & BMP_MON_PREPOLICY) { for (adjiter = bn->adj_in; adjiter; adjiter = adjiter->next) { if (adjiter->peer->qobj_node.nid <= bmp->syncpeerid) continue; if (adjin && adjiter->peer->qobj_node.nid > adjin->peer->qobj_node.nid) continue; adjin = adjiter; } } if (bpi || adjin) break; bn = NULL; } while (1); if (adjin && bpi && adjin->peer->qobj_node.nid < bpi->peer->qobj_node.nid) { bpi = NULL; bmp->syncpeerid = adjin->peer->qobj_node.nid; } else if (adjin && bpi && adjin->peer->qobj_node.nid > bpi->peer->qobj_node.nid) { adjin = NULL; bmp->syncpeerid = bpi->peer->qobj_node.nid; } else if (bpi) { bmp->syncpeerid = bpi->peer->qobj_node.nid; } else if (adjin) { bmp->syncpeerid = adjin->peer->qobj_node.nid; } if (bpi) bmp_monitor(bmp, bpi->peer, BMP_PEER_FLAG_L, &bn->p, bpi->attr, afi, safi, bpi->uptime); if (adjin) bmp_monitor(bmp, adjin->peer, 0, &bn->p, adjin->attr, afi, safi, adjin->uptime); return true; } static struct bmp_queue_entry *bmp_pull(struct bmp *bmp) { struct bmp_queue_entry *bqe; bqe = bmp->queuepos; if (!bqe) return NULL; bmp->queuepos = bmp_qlist_next(&bmp->targets->updlist, bqe); bqe->refcount--; if (!bqe->refcount) { bmp_qhash_del(&bmp->targets->updhash, bqe); bmp_qlist_del(&bmp->targets->updlist, bqe); } return bqe; } static bool bmp_wrqueue(struct bmp *bmp, struct pullwr *pullwr) { struct bmp_queue_entry *bqe; struct peer *peer; struct bgp_node *bn; bool written = false; bqe = bmp_pull(bmp); if (!bqe) return false; afi_t afi = bqe->afi; safi_t safi = bqe->safi; switch (bmp->afistate[afi][safi]) { case BMP_AFI_INACTIVE: case BMP_AFI_NEEDSYNC: goto out; case BMP_AFI_SYNC: if (prefix_cmp(&bqe->p, &bmp->syncpos) <= 0) /* currently syncing but have already passed this * prefix => send it. */ break; /* currently syncing & haven't reached this prefix yet * => it'll be sent as part of the table sync, no need here */ goto out; case BMP_AFI_LIVE: break; } peer = QOBJ_GET_TYPESAFE(bqe->peerid, peer); if (!peer) { zlog_info("bmp: skipping queued item for deleted peer"); goto out; } if (peer->status != Established) goto out; bn = bgp_node_lookup(bmp->targets->bgp->rib[afi][safi], &bqe->p); if (bmp->targets->afimon[afi][safi] & BMP_MON_POSTPOLICY) { struct bgp_path_info *bpi; for (bpi = bn ? bn->info : NULL; bpi; bpi = bpi->next) { if (!CHECK_FLAG(bpi->flags, BGP_PATH_VALID)) continue; if (bpi->peer == peer) break; } bmp_monitor(bmp, peer, BMP_PEER_FLAG_L, &bqe->p, bpi ? bpi->attr : NULL, afi, safi, bpi ? bpi->uptime : monotime(NULL)); written = true; } if (bmp->targets->afimon[afi][safi] & BMP_MON_PREPOLICY) { struct bgp_adj_in *adjin; for (adjin = bn ? bn->adj_in : NULL; adjin; adjin = adjin->next) { if (adjin->peer == peer) break; } bmp_monitor(bmp, peer, BMP_PEER_FLAG_L, &bqe->p, adjin ? adjin->attr : NULL, afi, safi, adjin ? adjin->uptime : monotime(NULL)); written = true; } out: if (!bqe->refcount) XFREE(MTYPE_BMP_QUEUE, bqe); return written; } static void bmp_wrfill(struct bmp *bmp, struct pullwr *pullwr) { switch(bmp->state) { case BMP_PeerUp: bmp_send_peerup(bmp); bmp->state = BMP_Run; break; case BMP_Run: if (bmp_wrmirror(bmp, pullwr)) break; if (bmp_wrqueue(bmp, pullwr)) break; if (bmp_wrsync(bmp, pullwr)) break; break; } } static void bmp_wrerr(struct bmp *bmp, struct pullwr *pullwr, bool eof) { if (eof) zlog_info("bmp[%s] disconnected", bmp->remote); else flog_warn(EC_LIB_SYSTEM_CALL, "bmp[%s] connection error: %s", bmp->remote, strerror(errno)); bmp_close(bmp); bmp_free(bmp); } static void bmp_process_one(struct bmp_targets *bt, struct bgp *bgp, afi_t afi, safi_t safi, struct bgp_node *bn, struct peer *peer) { struct bmp *bmp; struct bmp_queue_entry *bqe, bqeref; size_t refcount; char buf[256]; prefix2str(&bn->p, buf, sizeof(buf)); refcount = bmp_session_count(&bt->sessions); if (refcount == 0) return; memset(&bqeref, 0, sizeof(bqeref)); prefix_copy(&bqeref.p, &bn->p); bqeref.peerid = peer->qobj_node.nid; bqeref.afi = afi; bqeref.safi = safi; bqe = bmp_qhash_find(&bt->updhash, &bqeref); if (bqe) { if (bqe->refcount >= refcount) /* nothing to do here */ return; bmp_qlist_del(&bt->updlist, bqe); } else { bqe = XMALLOC(MTYPE_BMP_QUEUE, sizeof(*bqe)); memcpy(bqe, &bqeref, sizeof(*bqe)); bmp_qhash_add(&bt->updhash, bqe); } bqe->refcount = refcount; bmp_qlist_add_tail(&bt->updlist, bqe); frr_each (bmp_session, &bt->sessions, bmp) if (!bmp->queuepos) bmp->queuepos = bqe; } static int bmp_process(struct bgp *bgp, afi_t afi, safi_t safi, struct bgp_node *bn, struct peer *peer, bool withdraw) { struct bmp_bgp *bmpbgp = bmp_bgp_find(peer->bgp); struct bmp_targets *bt; struct bmp *bmp; if (!bmpbgp) return 0; frr_each(bmp_targets, &bmpbgp->targets, bt) { if (!bt->afimon[afi][safi]) continue; bmp_process_one(bt, bgp, afi, safi, bn, peer); frr_each(bmp_session, &bt->sessions, bmp) { pullwr_bump(bmp->pullwr); } } return 0; } static void bmp_stat_put_u32(struct stream *s, size_t *cnt, uint16_t type, uint32_t value) { stream_putw(s, type); stream_putw(s, 4); stream_putl(s, value); (*cnt)++; } static int bmp_stats(struct thread *thread) { struct bmp_targets *bt = THREAD_ARG(thread); struct stream *s; struct peer *peer; struct listnode *node; struct timeval tv; if (bt->stat_msec) thread_add_timer_msec(bm->master, bmp_stats, bt, bt->stat_msec, &bt->t_stats); gettimeofday(&tv, NULL); /* Walk down all peers */ for (ALL_LIST_ELEMENTS_RO(bt->bgp->peer, node, peer)) { size_t count = 0, count_pos, len; if (peer->status != Established) continue; s = stream_new(BGP_MAX_PACKET_SIZE); bmp_common_hdr(s, BMP_VERSION_3, BMP_TYPE_STATISTICS_REPORT); bmp_per_peer_hdr(s, peer, 0, &tv); count_pos = stream_get_endp(s); stream_putl(s, 0); bmp_stat_put_u32(s, &count, BMP_STATS_PFX_REJECTED, peer->stat_pfx_filter); bmp_stat_put_u32(s, &count, BMP_STATS_UPD_LOOP_ASPATH, peer->stat_pfx_aspath_loop); bmp_stat_put_u32(s, &count, BMP_STATS_UPD_LOOP_ORIGINATOR, peer->stat_pfx_originator_loop); bmp_stat_put_u32(s, &count, BMP_STATS_UPD_LOOP_CLUSTER, peer->stat_pfx_cluster_loop); bmp_stat_put_u32(s, &count, BMP_STATS_PFX_DUP_WITHDRAW, peer->stat_pfx_dup_withdraw); bmp_stat_put_u32(s, &count, BMP_STATS_UPD_7606_WITHDRAW, peer->stat_upd_7606); bmp_stat_put_u32(s, &count, BMP_STATS_FRR_NH_INVALID, peer->stat_pfx_nh_invalid); stream_putl_at(s, count_pos, count); len = stream_get_endp(s); stream_putl_at(s, BMP_LENGTH_POS, len); bmp_send_all(bt->bmpbgp, s); } return 0; } static struct bmp *bmp_open(struct bmp_targets *bt, int bmp_sock) { union sockunion su, *sumem; struct prefix p; int on = 1; struct access_list *acl = NULL; enum filter_type ret; char buf[SU_ADDRSTRLEN]; struct bmp *bmp; sumem = sockunion_getpeername(bmp_sock); if (!sumem) { close(bmp_sock); return NULL; } memcpy(&su, sumem, sizeof(su)); sockunion_free(sumem); set_nonblocking(bmp_sock); set_cloexec(bmp_sock); shutdown(bmp_sock, SHUT_RD); sockunion2hostprefix(&su, &p); acl = NULL; switch (p.family) { case AF_INET: acl = access_list_lookup(AFI_IP, bt->acl_name); break; case AF_INET6: acl = access_list_lookup(AFI_IP6, bt->acl6_name); break; default: break; } ret = FILTER_PERMIT; if (acl) { ret = access_list_apply(acl, &p); } sockunion2str(&su, buf, SU_ADDRSTRLEN); snprintf(buf + strlen(buf), sizeof(buf) - strlen(buf), ":%u", su.sa.sa_family == AF_INET ? ntohs(su.sin.sin_port) : ntohs(su.sin6.sin6_port)); if (ret == FILTER_DENY) { bt->cnt_aclrefused++; zlog_info("bmp[%s] connection refused by access-list", buf); close(bmp_sock); return NULL; } bt->cnt_accept++; setsockopt(bmp_sock, SOL_SOCKET, SO_KEEPALIVE, &on, sizeof(on)); setsockopt(bmp_sock, IPPROTO_TCP, TCP_NODELAY, &on, sizeof(on)); zlog_info("bmp[%s] connection established", buf); /* Allocate new BMP structure and set up default values. */ bmp = bmp_new(bt, bmp_sock); strlcpy(bmp->remote, buf, sizeof(bmp->remote)); bmp->state = BMP_PeerUp; bmp->pullwr = pullwr_new(bm->master, bmp_sock, bmp, bmp_wrfill, bmp_wrerr); bmp_send_initiation(bmp); return bmp; } /* Accept BMP connection. */ static int bmp_accept(struct thread *thread) { union sockunion su; struct bmp_listener *bl = THREAD_ARG(thread); int bmp_sock; /* We continue hearing BMP socket. */ thread_add_read(bm->master, bmp_accept, bl, bl->sock, &bl->t_accept); memset(&su, 0, sizeof(union sockunion)); /* We can handle IPv4 or IPv6 socket. */ bmp_sock = sockunion_accept(bl->sock, &su); if (bmp_sock < 0) { zlog_info("bmp: accept_sock failed: %s\n", safe_strerror (errno)); return -1; } bmp_open(bl->targets, bmp_sock); return 0; } static void bmp_close(struct bmp *bmp) { struct bmp_queue_entry *bqe; struct bmp_mirrorq *bmq; if (bmp->active) bmp_active_disconnected(bmp->active); while ((bmq = bmp_pull_mirror(bmp))) if (!bmq->refcount) XFREE(MTYPE_BMP_MIRRORQ, bmq); while ((bqe = bmp_pull(bmp))) if (!bqe->refcount) XFREE(MTYPE_BMP_QUEUE, bqe); THREAD_OFF(bmp->t_read); pullwr_del(bmp->pullwr); close(bmp->socket); } static struct bmp_bgp *bmp_bgp_find(struct bgp *bgp) { struct bmp_bgp dummy = { .bgp = bgp }; return bmp_bgph_find(&bmp_bgph, &dummy); } static struct bmp_bgp *bmp_bgp_get(struct bgp *bgp) { struct bmp_bgp *bmpbgp; bmpbgp = bmp_bgp_find(bgp); if (bmpbgp) return bmpbgp; bmpbgp = XCALLOC(MTYPE_BMP, sizeof(*bmpbgp)); bmpbgp->bgp = bgp; bmpbgp->mirror_qsizelimit = ~0UL; bmp_mirrorq_init(&bmpbgp->mirrorq); bmp_bgph_add(&bmp_bgph, bmpbgp); return bmpbgp; } static void bmp_bgp_put(struct bmp_bgp *bmpbgp) { struct bmp_targets *bt; bmp_bgph_del(&bmp_bgph, bmpbgp); frr_each_safe(bmp_targets, &bmpbgp->targets, bt) bmp_targets_put(bt); bmp_mirrorq_fini(&bmpbgp->mirrorq); XFREE(MTYPE_BMP, bmpbgp); } static int bmp_bgp_del(struct bgp *bgp) { struct bmp_bgp *bmpbgp = bmp_bgp_find(bgp); if (bmpbgp) bmp_bgp_put(bmpbgp); return 0; } static struct bmp_bgp_peer *bmp_bgp_peer_find(uint64_t peerid) { struct bmp_bgp_peer dummy = { .peerid = peerid }; return bmp_peerh_find(&bmp_peerh, &dummy); } static struct bmp_bgp_peer *bmp_bgp_peer_get(struct peer *peer) { struct bmp_bgp_peer *bbpeer; bbpeer = bmp_bgp_peer_find(peer->qobj_node.nid); if (bbpeer) return bbpeer; bbpeer = XCALLOC(MTYPE_BMP_PEER, sizeof(*bbpeer)); bbpeer->peerid = peer->qobj_node.nid; bmp_peerh_add(&bmp_peerh, bbpeer); return bbpeer; } static struct bmp_targets *bmp_targets_find1(struct bgp *bgp, const char *name) { struct bmp_bgp *bmpbgp = bmp_bgp_find(bgp); struct bmp_targets dummy; if (!bmpbgp) return NULL; dummy.name = (char *)name; return bmp_targets_find(&bmpbgp->targets, &dummy); } static struct bmp_targets *bmp_targets_get(struct bgp *bgp, const char *name) { struct bmp_targets *bt; bt = bmp_targets_find1(bgp, name); if (bt) return bt; bt = XCALLOC(MTYPE_BMP_TARGETS, sizeof(*bt)); bt->name = XSTRDUP(MTYPE_BMP_TARGETSNAME, name); bt->bgp = bgp; bt->bmpbgp = bmp_bgp_get(bgp); bmp_session_init(&bt->sessions); bmp_qhash_init(&bt->updhash); bmp_qlist_init(&bt->updlist); bmp_actives_init(&bt->actives); bmp_listeners_init(&bt->listeners); QOBJ_REG(bt, bmp_targets); bmp_targets_add(&bt->bmpbgp->targets, bt); return bt; } static void bmp_targets_put(struct bmp_targets *bt) { struct bmp *bmp; struct bmp_active *ba; frr_each_safe (bmp_actives, &bt->actives, ba) bmp_active_put(ba); frr_each_safe(bmp_session, &bt->sessions, bmp) { bmp_close(bmp); bmp_free(bmp); } bmp_targets_del(&bt->bmpbgp->targets, bt); QOBJ_UNREG(bt); bmp_listeners_fini(&bt->listeners); bmp_actives_fini(&bt->actives); bmp_qhash_fini(&bt->updhash); bmp_qlist_fini(&bt->updlist); XFREE(MTYPE_BMP_ACLNAME, bt->acl_name); XFREE(MTYPE_BMP_ACLNAME, bt->acl6_name); bmp_session_fini(&bt->sessions); XFREE(MTYPE_BMP_TARGETSNAME, bt->name); XFREE(MTYPE_BMP_TARGETS, bt); } static struct bmp_listener *bmp_listener_find(struct bmp_targets *bt, const union sockunion *su, int port) { struct bmp_listener dummy; dummy.addr = *su; dummy.port = port; return bmp_listeners_find(&bt->listeners, &dummy); } static struct bmp_listener *bmp_listener_get(struct bmp_targets *bt, const union sockunion *su, int port) { struct bmp_listener *bl = bmp_listener_find(bt, su, port); if (bl) return bl; bl = XCALLOC(MTYPE_BMP_LISTENER, sizeof(*bl)); bl->targets = bt; bl->addr = *su; bl->port = port; bl->sock = -1; bmp_listeners_add(&bt->listeners, bl); return bl; } static void bmp_listener_put(struct bmp_listener *bl) { bmp_listeners_del(&bl->targets->listeners, bl); XFREE(MTYPE_BMP_LISTENER, bl); } static void bmp_listener_start(struct bmp_listener *bl) { int sock, ret; sock = socket(bl->addr.sa.sa_family, SOCK_STREAM, 0); if (sock < 0) return; sockopt_reuseaddr(sock); sockopt_reuseport(sock); sockopt_v6only(bl->addr.sa.sa_family, sock); set_cloexec(sock); ret = sockunion_bind(sock, &bl->addr, bl->port, &bl->addr); if (ret < 0) goto out_sock; ret = listen(sock, 3); if (ret < 0) goto out_sock; bl->sock = sock; thread_add_read(bm->master, bmp_accept, bl, sock, &bl->t_accept); return; out_sock: close(sock); } static void bmp_listener_stop(struct bmp_listener *bl) { THREAD_OFF(bl->t_accept); if (bl->sock != -1) close(bl->sock); bl->sock = -1; } static struct bmp_active *bmp_active_find(struct bmp_targets *bt, const char *hostname, int port) { struct bmp_active dummy; dummy.hostname = (char *)hostname; dummy.port = port; return bmp_actives_find(&bt->actives, &dummy); } static struct bmp_active *bmp_active_get(struct bmp_targets *bt, const char *hostname, int port) { struct bmp_active *ba; ba = bmp_active_find(bt, hostname, port); if (ba) return ba; ba = XCALLOC(MTYPE_BMP_ACTIVE, sizeof(*ba)); ba->targets = bt; ba->hostname = XSTRDUP(MTYPE_TMP, hostname); ba->port = port; ba->minretry = BMP_DFLT_MINRETRY; ba->maxretry = BMP_DFLT_MAXRETRY; ba->socket = -1; bmp_actives_add(&bt->actives, ba); return ba; } static void bmp_active_put(struct bmp_active *ba) { THREAD_OFF(ba->t_timer); THREAD_OFF(ba->t_read); THREAD_OFF(ba->t_write); bmp_actives_del(&ba->targets->actives, ba); if (ba->bmp) { ba->bmp->active = NULL; bmp_close(ba->bmp); bmp_free(ba->bmp); } if (ba->socket != -1) close(ba->socket); XFREE(MTYPE_TMP, ba->hostname); XFREE(MTYPE_BMP_ACTIVE, ba); } static void bmp_active_setup(struct bmp_active *ba); static void bmp_active_connect(struct bmp_active *ba) { enum connect_result res; char buf[SU_ADDRSTRLEN]; for (; ba->addrpos < ba->addrtotal; ba->addrpos++) { ba->socket = sockunion_socket(&ba->addrs[ba->addrpos]); if (ba->socket < 0) { zlog_warn("bmp[%s]: failed to create socket", ba->hostname); continue; } set_nonblocking(ba->socket); res = sockunion_connect(ba->socket, &ba->addrs[ba->addrpos], htons(ba->port), 0); switch (res) { case connect_error: sockunion2str(&ba->addrs[ba->addrpos], buf, sizeof(buf)); zlog_warn("bmp[%s]: failed to connect to %s:%d", ba->hostname, buf, ba->port); close(ba->socket); ba->socket = -1; continue; case connect_success: break; case connect_in_progress: bmp_active_setup(ba); return; } } /* exhausted all addresses */ ba->curretry += ba->curretry / 2; bmp_active_setup(ba); } static void bmp_active_resolved(struct resolver_query *resq, int numaddrs, union sockunion *addr) { struct bmp_active *ba = container_of(resq, struct bmp_active, resq); unsigned i; if (numaddrs <= 0) { zlog_warn("bmp[%s]: hostname resolution failed", ba->hostname); ba->curretry += ba->curretry / 2; bmp_active_setup(ba); return; } if (numaddrs > (int)array_size(ba->addrs)) numaddrs = array_size(ba->addrs); ba->addrpos = 0; ba->addrtotal = numaddrs; for (i = 0; i < ba->addrtotal; i++) memcpy(&ba->addrs[i], &addr[i], sizeof(ba->addrs[0])); bmp_active_connect(ba); } static int bmp_active_thread(struct thread *t) { struct bmp_active *ba = THREAD_ARG(t); socklen_t slen; int status, ret; char buf[SU_ADDRSTRLEN]; /* all 3 end up here, though only timer or read+write are active * at a time */ THREAD_OFF(ba->t_timer); THREAD_OFF(ba->t_read); THREAD_OFF(ba->t_write); if (ba->socket == -1) { resolver_resolve(&ba->resq, AF_UNSPEC, ba->hostname, bmp_active_resolved); return 0; } slen = sizeof(status); ret = getsockopt(ba->socket, SOL_SOCKET, SO_ERROR, (void *)&status, &slen); sockunion2str(&ba->addrs[ba->addrpos], buf, sizeof(buf)); if (ret < 0 || status != 0) { zlog_warn("bmp[%s]: failed to connect to %s:%d", ba->hostname, buf, ba->port); goto out_next; } zlog_warn("bmp[%s]: outbound connection to %s:%d", ba->hostname, buf, ba->port); ba->bmp = bmp_open(ba->targets, ba->socket); if (!ba->bmp) goto out_next; ba->bmp->active = ba; ba->socket = -1; ba->curretry = ba->minretry; return 0; out_next: close(ba->socket); ba->socket = -1; ba->addrpos++; bmp_active_connect(ba); return 0; } static void bmp_active_disconnected(struct bmp_active *ba) { ba->bmp = NULL; bmp_active_setup(ba); } static void bmp_active_setup(struct bmp_active *ba) { THREAD_OFF(ba->t_timer); THREAD_OFF(ba->t_read); THREAD_OFF(ba->t_write); if (ba->bmp) return; if (ba->resq.callback) return; if (ba->curretry > ba->maxretry) ba->curretry = ba->maxretry; if (ba->socket == -1) thread_add_timer_msec(bm->master, bmp_active_thread, ba, ba->curretry, &ba->t_timer); else { thread_add_read(bm->master, bmp_active_thread, ba, ba->socket, &ba->t_read); thread_add_write(bm->master, bmp_active_thread, ba, ba->socket, &ba->t_write); } } static struct cmd_node bmp_node = {BMP_NODE, "%s(config-bgp-bmp)# "}; #define BMP_STR "BGP Monitoring Protocol\n" #ifndef VTYSH_EXTRACT_PL #include "bgpd/bgp_bmp_clippy.c" #endif DEFPY_NOSH(bmp_targets_main, bmp_targets_cmd, "bmp targets BMPTARGETS", BMP_STR "Create BMP target group\n" "Name of the BMP target group\n") { VTY_DECLVAR_CONTEXT(bgp, bgp); struct bmp_targets *bt; bt = bmp_targets_get(bgp, bmptargets); VTY_PUSH_CONTEXT_SUB(BMP_NODE, bt); return CMD_SUCCESS; } DEFPY(no_bmp_targets_main, no_bmp_targets_cmd, "no bmp targets BMPTARGETS", NO_STR BMP_STR "Delete BMP target group\n" "Name of the BMP target group\n") { VTY_DECLVAR_CONTEXT(bgp, bgp); struct bmp_targets *bt; bt = bmp_targets_find1(bgp, bmptargets); if (!bt) { vty_out(vty, "%% BMP target group not found\n"); return CMD_WARNING; } bmp_targets_put(bt); return CMD_SUCCESS; } DEFPY(bmp_listener_main, bmp_listener_cmd, "bmp listener port (1-65535)", BMP_STR "Listen for inbound BMP connections\n" "IPv6 address to listen on\n" "IPv4 address to listen on\n" "TCP Port number\n" "TCP Port number\n") { VTY_DECLVAR_CONTEXT_SUB(bmp_targets, bt); struct bmp_listener *bl; bl = bmp_listener_get(bt, listener, port); if (bl->sock == -1) bmp_listener_start(bl); return CMD_SUCCESS; } DEFPY(no_bmp_listener_main, no_bmp_listener_cmd, "no bmp listener port (1-65535)", NO_STR BMP_STR "Create BMP listener\n" "IPv6 address to listen on\n" "IPv4 address to listen on\n" "TCP Port number\n" "TCP Port number\n") { VTY_DECLVAR_CONTEXT_SUB(bmp_targets, bt); struct bmp_listener *bl; bl = bmp_listener_find(bt, listener, port); if (!bl) { vty_out(vty, "%% BMP listener not found\n"); return CMD_WARNING; } bmp_listener_stop(bl); bmp_listener_put(bl); return CMD_SUCCESS; } DEFPY(bmp_connect, bmp_connect_cmd, "[no] bmp connect HOSTNAME port (1-65535) " "{min-retry (100-86400000)" "|max-retry (100-86400000)}", NO_STR BMP_STR "Actively establish connection to monitoring station\n" "Monitoring station hostname or address\n" "TCP port\n" "TCP port\n" "Minimum connection retry interval\n" "Minimum connection retry interval (milliseconds)\n" "Maximum connection retry interval\n" "Maximum connection retry interval (milliseconds)\n") { VTY_DECLVAR_CONTEXT_SUB(bmp_targets, bt); struct bmp_active *ba; if (no) { ba = bmp_active_find(bt, hostname, port); if (!ba) { vty_out(vty, "%% No such active connection found\n"); return CMD_WARNING; } bmp_active_put(ba); return CMD_SUCCESS; } ba = bmp_active_get(bt, hostname, port); if (min_retry_str) ba->minretry = min_retry; if (max_retry_str) ba->maxretry = max_retry; ba->curretry = ba->minretry; bmp_active_setup(ba); return CMD_SUCCESS; } DEFPY(bmp_acl, bmp_acl_cmd, "[no] $af access-list WORD", NO_STR IP_STR IPV6_STR "Access list to restrict BMP sessions\n" "Access list name\n") { VTY_DECLVAR_CONTEXT_SUB(bmp_targets, bt); char **what; if (no) access_list = NULL; if (!strcmp(af, "ipv6")) what = &bt->acl6_name; else what = &bt->acl_name; XFREE(MTYPE_BMP_ACLNAME, *what); if (access_list) *what = XSTRDUP(MTYPE_BMP_ACLNAME, access_list); return CMD_SUCCESS; } DEFPY(bmp_stats_cfg, bmp_stats_cmd, "[no] bmp stats [interval (100-86400000)]", NO_STR BMP_STR "Send BMP statistics messages\n" "Specify BMP stats interval\n" "Interval (milliseconds) to send BMP Stats in\n") { VTY_DECLVAR_CONTEXT_SUB(bmp_targets, bt); THREAD_OFF(bt->t_stats); if (no) bt->stat_msec = 0; else if (interval_str) bt->stat_msec = interval; else bt->stat_msec = BMP_STAT_DEFAULT_TIMER; if (bt->stat_msec) thread_add_timer_msec(bm->master, bmp_stats, bt, bt->stat_msec, &bt->t_stats); return CMD_SUCCESS; } DEFPY(bmp_monitor_cfg, bmp_monitor_cmd, "[no] bmp monitor "BGP_AFI_CMD_STR" $policy", NO_STR BMP_STR "Send BMP route monitoring messages\n" BGP_AFI_HELP_STR "Address family modifier\n" "Address family modifier\n" "Send state before policy and filter processing\n" "Send state with policy and filters applied\n") { int index = 0; uint8_t flag, prev; afi_t afi; safi_t safi; VTY_DECLVAR_CONTEXT_SUB(bmp_targets, bt); struct bmp *bmp; argv_find_and_parse_afi(argv, argc, &index, &afi); argv_find_and_parse_safi(argv, argc, &index, &safi); if (policy[1] == 'r') flag = BMP_MON_PREPOLICY; else flag = BMP_MON_POSTPOLICY; prev = bt->afimon[afi][safi]; if (no) bt->afimon[afi][safi] &= ~flag; else bt->afimon[afi][safi] |= flag; if (prev == bt->afimon[afi][safi]) return CMD_SUCCESS; frr_each (bmp_session, &bt->sessions, bmp) { if (bmp->syncafi == afi && bmp->syncsafi == safi) { bmp->syncafi = AFI_MAX; bmp->syncsafi = SAFI_MAX; } if (!bt->afimon[afi][safi]) { bmp->afistate[afi][safi] = BMP_AFI_INACTIVE; continue; } bmp->afistate[afi][safi] = BMP_AFI_NEEDSYNC; } return CMD_SUCCESS; } DEFPY(bmp_mirror_cfg, bmp_mirror_cmd, "[no] bmp mirror", NO_STR BMP_STR "Send BMP route mirroring messages\n") { VTY_DECLVAR_CONTEXT_SUB(bmp_targets, bt); struct bmp *bmp; if (bt->mirror == !no) return CMD_SUCCESS; bt->mirror = !no; if (bt->mirror) return CMD_SUCCESS; frr_each (bmp_session, &bt->sessions, bmp) { struct bmp_mirrorq *bmq; while ((bmq = bmp_pull_mirror(bmp))) if (!bmq->refcount) XFREE(MTYPE_BMP_MIRRORQ, bmq); } return CMD_SUCCESS; } DEFPY(bmp_mirror_limit_cfg, bmp_mirror_limit_cmd, "bmp mirror buffer-limit (0-4294967294)", BMP_STR "Route Mirroring settings\n" "Configure maximum memory used for buffered mirroring messages\n" "Limit in bytes\n") { VTY_DECLVAR_CONTEXT(bgp, bgp); struct bmp_bgp *bmpbgp; bmpbgp = bmp_bgp_get(bgp); bmpbgp->mirror_qsizelimit = buffer_limit; return CMD_SUCCESS; } DEFPY(no_bmp_mirror_limit_cfg, no_bmp_mirror_limit_cmd, "no bmp mirror buffer-limit [(0-4294967294)]", NO_STR BMP_STR "Route Mirroring settings\n" "Configure maximum memory used for buffered mirroring messages\n" "Limit in bytes\n") { VTY_DECLVAR_CONTEXT(bgp, bgp); struct bmp_bgp *bmpbgp; bmpbgp = bmp_bgp_get(bgp); bmpbgp->mirror_qsizelimit = ~0UL; return CMD_SUCCESS; } DEFPY(show_bmp, show_bmp_cmd, "show bmp", SHOW_STR BMP_STR) { struct bmp_bgp *bmpbgp; struct bmp_targets *bt; struct bmp_listener *bl; struct bmp *bmp; struct ttable *tt; char buf[SU_ADDRSTRLEN]; frr_each(bmp_bgph, &bmp_bgph, bmpbgp) { vty_out(vty, "BMP state for BGP %s:\n\n", bmpbgp->bgp->name_pretty); vty_out(vty, " Route Mirroring %9zu bytes (%zu messages) pending\n", bmpbgp->mirror_qsize, bmp_mirrorq_count(&bmpbgp->mirrorq)); vty_out(vty, " %9zu bytes maximum buffer used\n", bmpbgp->mirror_qsizemax); if (bmpbgp->mirror_qsizelimit != ~0UL) vty_out(vty, " %9zu bytes buffer size limit\n", bmpbgp->mirror_qsizelimit); vty_out(vty, "\n"); frr_each(bmp_targets, &bmpbgp->targets, bt) { vty_out(vty, " Targets \"%s\":\n", bt->name); vty_out(vty, " Route Mirroring %sabled\n", bt->mirror ? "en" : "dis"); afi_t afi; safi_t safi; FOREACH_AFI_SAFI (afi, safi) { const char *str = NULL; switch (bt->afimon[afi][safi]) { case BMP_MON_PREPOLICY: str = "pre-policy"; break; case BMP_MON_POSTPOLICY: str = "post-policy"; break; case BMP_MON_PREPOLICY | BMP_MON_POSTPOLICY: str = "pre-policy and post-policy"; break; } if (!str) continue; vty_out(vty, " Route Monitoring %s %s %s\n", afi2str(afi), safi2str(safi), str); } vty_out(vty, " Listeners:\n"); frr_each (bmp_listeners, &bt->listeners, bl) vty_out(vty, " %s:%d\n", sockunion2str(&bl->addr, buf, SU_ADDRSTRLEN), bl->port); vty_out(vty, "\n %zu connected clients:\n", bmp_session_count(&bt->sessions)); tt = ttable_new(&ttable_styles[TTSTYLE_BLANK]); ttable_add_row(tt, "remote|uptime|MonSent|MirrSent|MirrLost|ByteSent|ByteQ|ByteQKernel"); ttable_rowseps(tt, 0, BOTTOM, true, '-'); frr_each (bmp_session, &bt->sessions, bmp) { uint64_t total; size_t q, kq; pullwr_stats(bmp->pullwr, &total, &q, &kq); ttable_add_row(tt, "%s|-|%Lu|%Lu|%Lu|%Lu|%zu|%zu", bmp->remote, bmp->cnt_update, bmp->cnt_mirror, bmp->cnt_mirror_overruns, total, q, kq); } char *out = ttable_dump(tt, "\n"); vty_out(vty, "%s", out); XFREE(MTYPE_TMP, out); ttable_del(tt); vty_out(vty, "\n"); } } return CMD_SUCCESS; } static int bmp_config_write(struct bgp *bgp, struct vty *vty) { struct bmp_bgp *bmpbgp = bmp_bgp_find(bgp); struct bmp_targets *bt; struct bmp_listener *bl; struct bmp_active *ba; char buf[SU_ADDRSTRLEN]; afi_t afi; safi_t safi; if (!bmpbgp) return 0; if (bmpbgp->mirror_qsizelimit != ~0UL) vty_out(vty, " !\n bmp mirror buffer-limit %zu\n", bmpbgp->mirror_qsizelimit); frr_each(bmp_targets, &bmpbgp->targets, bt) { vty_out(vty, " !\n bmp targets %s\n", bt->name); if (bt->acl6_name) vty_out(vty, " ipv6 access-list %s\n", bt->acl6_name); if (bt->acl_name) vty_out(vty, " ip access-list %s\n", bt->acl_name); if (bt->stat_msec) vty_out(vty, " bmp stats interval %d\n", bt->stat_msec); if (bt->mirror) vty_out(vty, " bmp mirror\n"); FOREACH_AFI_SAFI (afi, safi) { const char *afi_str = (afi == AFI_IP) ? "ipv4" : "ipv6"; if (bt->afimon[afi][safi] & BMP_MON_PREPOLICY) vty_out(vty, " bmp monitor %s %s pre-policy\n", afi_str, safi2str(safi)); if (bt->afimon[afi][safi] & BMP_MON_POSTPOLICY) vty_out(vty, " bmp monitor %s %s post-policy\n", afi_str, safi2str(safi)); } frr_each (bmp_listeners, &bt->listeners, bl) vty_out(vty, " \n bmp listener %s port %d\n", sockunion2str(&bl->addr, buf, SU_ADDRSTRLEN), bl->port); frr_each (bmp_actives, &bt->actives, ba) vty_out(vty, " bmp connect %s port %u min-retry %u max-retry %u\n", ba->hostname, ba->port, ba->minretry, ba->maxretry); } return 0; } static int bgp_bmp_init(struct thread_master *tm) { install_node(&bmp_node, NULL); install_default(BMP_NODE); install_element(BGP_NODE, &bmp_targets_cmd); install_element(BGP_NODE, &no_bmp_targets_cmd); install_element(BMP_NODE, &bmp_listener_cmd); install_element(BMP_NODE, &no_bmp_listener_cmd); install_element(BMP_NODE, &bmp_connect_cmd); install_element(BMP_NODE, &bmp_acl_cmd); install_element(BMP_NODE, &bmp_stats_cmd); install_element(BMP_NODE, &bmp_monitor_cmd); install_element(BMP_NODE, &bmp_mirror_cmd); install_element(BGP_NODE, &bmp_mirror_limit_cmd); install_element(BGP_NODE, &no_bmp_mirror_limit_cmd); install_element(VIEW_NODE, &show_bmp_cmd); resolver_init(tm); return 0; } static int bgp_bmp_module_init(void) { hook_register(bgp_packet_dump, bmp_mirror_packet); hook_register(bgp_packet_send, bmp_outgoing_packet); hook_register(peer_established, bmp_peer_established); hook_register(peer_backward_transition, bmp_peer_backward); hook_register(bgp_process, bmp_process); hook_register(bgp_inst_config_write, bmp_config_write); hook_register(bgp_inst_delete, bmp_bgp_del); hook_register(frr_late_init, bgp_bmp_init); return 0; } FRR_MODULE_SETUP(.name = "bgpd_bmp", .version = FRR_VERSION, .description = "bgpd BMP module", .init = bgp_bmp_module_init) frr-7.2.1/bgpd/bgp_bmp.h0000644000000000000000000001701613610377563011714 00000000000000/* BMP support. * Copyright (C) 2018 Yasuhiro Ohara * Copyright (C) 2019 David Lamparter for NetDEF, Inc. * * This program is free software; you can redistribute it and/or modify it * under the terms of the 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; see the file COPYING; if not, write to the Free Software * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA */ #ifndef _BGP_BMP_H_ #define _BGP_BMP_H_ #include "zebra.h" #include "typesafe.h" #include "pullwr.h" #include "qobj.h" #include "resolver.h" #define BMP_VERSION_3 3 #define BMP_LENGTH_POS 1 /* BMP message types */ #define BMP_TYPE_ROUTE_MONITORING 0 #define BMP_TYPE_STATISTICS_REPORT 1 #define BMP_TYPE_PEER_DOWN_NOTIFICATION 2 #define BMP_TYPE_PEER_UP_NOTIFICATION 3 #define BMP_TYPE_INITIATION 4 #define BMP_TYPE_TERMINATION 5 #define BMP_TYPE_ROUTE_MIRRORING 6 #define BMP_READ_BUFSIZ 1024 /* bmp->state */ #define BMP_None 0 #define BMP_PeerUp 2 #define BMP_Run 3 /* This one is for BMP Route Monitoring messages, i.e. delivering updates * in somewhat processed (as opposed to fully raw, see mirroring below) form. * RFC explicitly says that we can skip old updates if we haven't sent them out * yet and another newer update for the same prefix arrives. * * So, at most one of these can exist for each (bgp, afi, safi, prefix, peerid) * tuple; if some prefix is "re-added" to the queue, the existing entry is * instead moved to the end of the queue. This ensures that the queue size is * bounded by the BGP table size. * * bmp_qlist is the queue itself while bmp_qhash is used to efficiently check * whether a tuple is already on the list. The queue is maintained per * bmp_target. * * refcount = number of "struct bmp *" whose queue position is before this * entry, i.e. number of BMP sessions where we still want to send this out. * Decremented on send so we know when we're done with an entry (i.e. this * always happens from the front of the queue.) */ PREDECL_DLIST(bmp_qlist) PREDECL_HASH(bmp_qhash) struct bmp_queue_entry { struct bmp_qlist_item bli; struct bmp_qhash_item bhi; struct prefix p; uint64_t peerid; afi_t afi; safi_t safi; size_t refcount; }; /* This is for BMP Route Mirroring, which feeds fully raw BGP PDUs out to BMP * receivers. So, this goes directly off packet RX/TX handling instead of * grabbing bits from tables. * * There is *one* queue for each "struct bgp *" where we throw everything on, * with a size limit. Refcount works the same as for monitoring above. */ PREDECL_LIST(bmp_mirrorq) struct bmp_mirrorq { struct bmp_mirrorq_item bmi; size_t refcount; uint64_t peerid; struct timeval tv; size_t len; uint8_t data[0]; }; enum { BMP_AFI_INACTIVE = 0, BMP_AFI_NEEDSYNC, BMP_AFI_SYNC, BMP_AFI_LIVE, }; PREDECL_LIST(bmp_session) struct bmp_active; struct bmp_targets; /* an established BMP session to a peer */ struct bmp { struct bmp_session_item bsi; struct bmp_targets *targets; struct bmp_active *active; int socket; char remote[SU_ADDRSTRLEN + 6]; struct thread *t_read; struct pullwr *pullwr; int state; /* queue positions must remain synced with refcounts in the items. * Whenever appending a queue item, we need to know the correct number * of "struct bmp *" that want it, and when moving these positions * ahead we need to make sure that refcount is decremented. Also, on * disconnects we need to walk the queue and drop our reference. */ struct bmp_queue_entry *queuepos; struct bmp_mirrorq *mirrorpos; bool mirror_lost; /* enum BMP_AFI_* */ uint8_t afistate[AFI_MAX][SAFI_MAX]; /* counters for the various BMP packet types */ uint64_t cnt_update, cnt_mirror; /* number of times this peer wasn't fast enough in consuming the * mirror queue */ uint64_t cnt_mirror_overruns; struct timeval t_up; /* synchronization / startup works by repeatedly finding the next * table entry, the sync* fields note down what we sent last */ struct prefix syncpos; uint64_t syncpeerid; afi_t syncafi; safi_t syncsafi; }; /* config & state for an active outbound connection. When the connection * succeeds, "bmp" is set up. */ PREDECL_SORTLIST_UNIQ(bmp_actives) #define BMP_DFLT_MINRETRY 30000 #define BMP_DFLT_MAXRETRY 720000 struct bmp_active { struct bmp_actives_item bai; struct bmp_targets *targets; struct bmp *bmp; char *hostname; int port; unsigned minretry, maxretry; struct resolver_query resq; unsigned curretry; unsigned addrpos, addrtotal; union sockunion addrs[8]; int socket; struct thread *t_timer, *t_read, *t_write; }; /* config & state for passive / listening sockets */ PREDECL_SORTLIST_UNIQ(bmp_listeners) struct bmp_listener { struct bmp_listeners_item bli; struct bmp_targets *targets; union sockunion addr; int port; struct thread *t_accept; int sock; }; /* bmp_targets - plural since it may contain multiple bmp_listener & * bmp_active items. If they have the same config, BMP session should be * put in the same targets since that's a bit more effective. */ PREDECL_SORTLIST_UNIQ(bmp_targets) struct bmp_targets { struct bmp_targets_item bti; struct bmp_bgp *bmpbgp; struct bgp *bgp; char *name; struct bmp_listeners_head listeners; char *acl_name; char *acl6_name; #define BMP_STAT_DEFAULT_TIMER 60000 int stat_msec; /* only IPv4 & IPv6 / unicast & multicast supported for now */ #define BMP_MON_PREPOLICY (1 << 0) #define BMP_MON_POSTPOLICY (1 << 1) uint8_t afimon[AFI_MAX][SAFI_MAX]; bool mirror; struct bmp_actives_head actives; struct thread *t_stats; struct bmp_session_head sessions; struct bmp_qhash_head updhash; struct bmp_qlist_head updlist; uint64_t cnt_accept, cnt_aclrefused; QOBJ_FIELDS }; DECLARE_QOBJ_TYPE(bmp_targets) /* per struct peer * data. Lookup by peer->qobj_node.nid, created on demand, * deleted in peer_backward hook. */ PREDECL_HASH(bmp_peerh) struct bmp_bgp_peer { struct bmp_peerh_item bpi; uint64_t peerid; /* struct peer *peer; */ uint8_t *open_rx; size_t open_rx_len; uint8_t *open_tx; size_t open_tx_len; }; /* per struct bgp * data */ PREDECL_HASH(bmp_bgph) struct bmp_bgp { struct bmp_bgph_item bbi; struct bgp *bgp; struct bmp_targets_head targets; struct bmp_mirrorq_head mirrorq; size_t mirror_qsize, mirror_qsizemax; size_t mirror_qsizelimit; }; enum { BMP_PEERDOWN_LOCAL_NOTIFY = 1, BMP_PEERDOWN_LOCAL_FSM = 2, BMP_PEERDOWN_REMOTE_NOTIFY = 3, BMP_PEERDOWN_REMOTE_CLOSE = 4, BMP_PEERDOWN_ENDMONITOR = 5, }; enum { BMP_STATS_PFX_REJECTED = 0, BMP_STATS_PFX_DUP_ADV = 1, BMP_STATS_PFX_DUP_WITHDRAW = 2, BMP_STATS_UPD_LOOP_CLUSTER = 3, BMP_STATS_UPD_LOOP_ASPATH = 4, BMP_STATS_UPD_LOOP_ORIGINATOR = 5, BMP_STATS_UPD_LOOP_CONFED = 6, BMP_STATS_SIZE_ADJ_RIB_IN = 7, BMP_STATS_SIZE_LOC_RIB = 8, BMP_STATS_SIZE_ADJ_RIB_IN_SAFI = 9, BMP_STATS_SIZE_LOC_RIB_IN_SAFI = 10, BMP_STATS_UPD_7606_WITHDRAW = 11, BMP_STATS_PFX_7606_WITHDRAW = 12, BMP_STATS_UPD_DUP = 13, BMP_STATS_FRR_NH_INVALID = 65531, }; DECLARE_MGROUP(BMP) #endif /*_BGP_BMP_H_*/ frr-7.2.1/bgpd/bgp_btoa.c0000644000000000000000000001463113610377563012056 00000000000000/* BGP dump to ascii converter * Copyright (C) 1999 Kunihiro Ishiguro * * This file is part of GNU Zebra. * * GNU Zebra is free software; you can redistribute it and/or modify it * under the terms of the GNU General Public License as published by the * Free Software Foundation; either version 2, or (at your option) any * later version. * * GNU Zebra 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; see the file COPYING; if not, write to the Free Software * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA */ #include #include "zebra.h" #include "stream.h" #include "log.h" #include "prefix.h" #include "command.h" #include "memory.h" #include "privs.h" #include "filter.h" #include "bgpd/bgpd.h" #include "bgpd/bgp_dump.h" #include "bgpd/bgp_attr.h" #include "bgpd/bgp_aspath.h" /* privileges */ static zebra_capabilities_t _caps_p[] = { ZCAP_BIND, ZCAP_NET_RAW, ZCAP_NET_ADMIN, }; struct zebra_privs_t bgpd_privs = { #if defined(FRR_USER) && defined(FRR_GROUP) .user = FRR_USER, .group = FRR_GROUP, #endif #ifdef VTY_GROUP .vty_group = VTY_GROUP, #endif .caps_p = _caps_p, .cap_num_p = array_size(_caps_p), .cap_num_i = 0, }; enum MRT_MSG_TYPES { MSG_NULL, MSG_START, /* sender is starting up */ MSG_DIE, /* receiver should shut down */ MSG_I_AM_DEAD, /* sender is shutting down */ MSG_PEER_DOWN, /* sender's peer is down */ MSG_PROTOCOL_BGP, /* msg is a BGP packet */ MSG_PROTOCOL_RIP, /* msg is a RIP packet */ MSG_PROTOCOL_IDRP, /* msg is an IDRP packet */ MSG_PROTOCOL_RIPNG, /* msg is a RIPNG packet */ MSG_PROTOCOL_BGP4PLUS, /* msg is a BGP4+ packet */ MSG_PROTOCOL_BGP4PLUS_01, /* msg is a BGP4+ (draft 01) packet */ MSG_PROTOCOL_OSPF, /* msg is an OSPF packet */ MSG_TABLE_DUMP /* routing table dump */ }; static int attr_parse(struct stream *s, uint16_t len) { unsigned int flag; unsigned int type; uint16_t length; uint16_t lim; lim = s->getp + len; printf("attr_parse s->getp %zd, len %d, lim %d\n", s->getp, len, lim); while (s->getp < lim) { flag = stream_getc(s); type = stream_getc(s); if (flag & BGP_ATTR_FLAG_EXTLEN) length = stream_getw(s); else length = stream_getc(s); printf("FLAG: %d\n", flag); printf("TYPE: %d\n", type); printf("Len: %d\n", length); switch (type) { case BGP_ATTR_ORIGIN: { uint8_t origin; origin = stream_getc(s); printf("ORIGIN: %d\n", origin); } break; case BGP_ATTR_AS_PATH: { struct aspath *aspath; aspath = aspath_parse(s, length, 1); printf("ASPATH: %s\n", aspath->str); aspath_free(aspath); } break; case BGP_ATTR_NEXT_HOP: { struct in_addr nexthop; nexthop.s_addr = stream_get_ipv4(s); printf("NEXTHOP: %s\n", inet_ntoa(nexthop)); } break; default: stream_getw_from(s, length); break; } } return 0; } int main(int argc, char **argv) { int ret; int fd; struct stream *s; time_t now; int type; int subtype; size_t len; int source_as; int dest_as; ifindex_t ifindex; int family; struct in_addr sip; struct in_addr dip; uint16_t viewno, seq_num; struct prefix_ipv4 p; s = stream_new(10000); if (argc != 2) { fprintf(stderr, "Usage: %s FILENAME\n", argv[0]); exit(1); } fd = open(argv[1], O_RDONLY); if (fd < 0) { fprintf(stdout, "%% Can't open configuration file %s due to '%s'.\n", argv[1], safe_strerror(errno)); exit(1); } while (1) { stream_reset(s); ret = stream_read(s, fd, 12); if (ret != 12) { if (!ret) printf("END OF FILE\n"); else if (ret < 0) printf("ERROR OF READ\n"); else printf("UNDERFLOW\n"); break; } /* Extract header. */ now = stream_getl(s); type = stream_getw(s); subtype = stream_getw(s); len = stream_getl(s); printf("TIME: %s", ctime(&now)); /* printf ("TYPE: %d/%d\n", type, subtype); */ if (type == MSG_PROTOCOL_BGP4MP) printf("TYPE: BGP4MP"); else if (type == MSG_PROTOCOL_BGP4MP_ET) printf("TYPE: BGP4MP_ET"); else if (type == MSG_TABLE_DUMP) printf("TYPE: MSG_TABLE_DUMP"); else printf("TYPE: Unknown %d", type); if (type == MSG_TABLE_DUMP) switch (subtype) { case AFI_IP: printf("/AFI_IP\n"); break; case AFI_IP6: printf("/AFI_IP6\n"); break; default: printf("/UNKNOWN %d", subtype); break; } else { switch (subtype) { case BGP4MP_STATE_CHANGE: printf("/CHANGE\n"); break; case BGP4MP_MESSAGE: printf("/MESSAGE\n"); break; case BGP4MP_ENTRY: printf("/ENTRY\n"); break; case BGP4MP_SNAPSHOT: printf("/SNAPSHOT\n"); break; default: printf("/UNKNOWN %d", subtype); break; } } printf("len: %zd\n", len); ret = stream_read(s, fd, len); if (ret != (int)len) { if (!ret) printf("END OF FILE 2\n"); else if (ret < 0) printf("ERROR OF READ 2\n"); else printf("UNDERFLOW 2\n"); break; } /* printf ("now read %d\n", len); */ if (type == MSG_TABLE_DUMP) { uint8_t status; time_t originated; struct in_addr peer; uint16_t attrlen; viewno = stream_getw(s); seq_num = stream_getw(s); printf("VIEW: %d\n", viewno); printf("SEQUENCE: %d\n", seq_num); /* start */ while (s->getp < len - 16) { p.prefix.s_addr = stream_get_ipv4(s); p.prefixlen = stream_getc(s); printf("PREFIX: %s/%d\n", inet_ntoa(p.prefix), p.prefixlen); status = stream_getc(s); originated = stream_getl(s); peer.s_addr = stream_get_ipv4(s); source_as = stream_getw(s); printf("FROM: %s AS%d\n", inet_ntoa(peer), source_as); printf("ORIGINATED: %s", ctime(&originated)); attrlen = stream_getw(s); printf("ATTRLEN: %d\n", attrlen); attr_parse(s, attrlen); printf("STATUS: 0x%x\n", status); } } else { source_as = stream_getw(s); dest_as = stream_getw(s); printf("source_as: %d\n", source_as); printf("dest_as: %d\n", dest_as); ifindex = stream_getw(s); family = stream_getw(s); printf("ifindex: %d\n", ifindex); printf("family: %d\n", family); sip.s_addr = stream_get_ipv4(s); dip.s_addr = stream_get_ipv4(s); printf("saddr: %s\n", inet_ntoa(sip)); printf("daddr: %s\n", inet_ntoa(dip)); printf("\n"); } } close(fd); return 0; } frr-7.2.1/bgpd/bgp_clist.c0000644000000000000000000010203013610377563012236 00000000000000/* BGP community-list and extcommunity-list. * Copyright (C) 1999 Kunihiro Ishiguro * * This file is part of GNU Zebra. * * GNU Zebra is free software; you can redistribute it and/or modify it * under the terms of the GNU General Public License as published by the * Free Software Foundation; either version 2, or (at your option) any * later version. * * GNU Zebra 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; see the file COPYING; if not, write to the Free Software * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA */ #include #include "command.h" #include "prefix.h" #include "memory.h" #include "queue.h" #include "filter.h" #include "stream.h" #include "jhash.h" #include "frrstr.h" #include "bgpd/bgpd.h" #include "bgpd/bgp_community.h" #include "bgpd/bgp_ecommunity.h" #include "bgpd/bgp_lcommunity.h" #include "bgpd/bgp_aspath.h" #include "bgpd/bgp_regex.h" #include "bgpd/bgp_clist.h" static uint32_t bgp_clist_hash_key_community_list(const void *data) { struct community_list *cl = (struct community_list *) data; if (cl->name_hash) return cl->name_hash; cl->name_hash = bgp_clist_hash_key(cl->name); return cl->name_hash; } static bool bgp_clist_hash_cmp_community_list(const void *a1, const void *a2) { const struct community_list *cl1 = a1; const struct community_list *cl2 = a2; if (cl1->name_hash != cl2->name_hash) return false; if (strcmp(cl1->name, cl2->name) == 0) return true; return false; } /* Lookup master structure for community-list or extcommunity-list. */ struct community_list_master * community_list_master_lookup(struct community_list_handler *ch, int master) { if (ch) switch (master) { case COMMUNITY_LIST_MASTER: return &ch->community_list; case EXTCOMMUNITY_LIST_MASTER: return &ch->extcommunity_list; case LARGE_COMMUNITY_LIST_MASTER: return &ch->lcommunity_list; } return NULL; } /* Allocate a new community list entry. */ static struct community_entry *community_entry_new(void) { return XCALLOC(MTYPE_COMMUNITY_LIST_ENTRY, sizeof(struct community_entry)); } /* Free community list entry. */ static void community_entry_free(struct community_entry *entry) { switch (entry->style) { case COMMUNITY_LIST_STANDARD: if (entry->u.com) community_free(&entry->u.com); break; case LARGE_COMMUNITY_LIST_STANDARD: if (entry->u.lcom) lcommunity_free(&entry->u.lcom); break; case EXTCOMMUNITY_LIST_STANDARD: /* In case of standard extcommunity-list, configuration string is made by ecommunity_ecom2str(). */ XFREE(MTYPE_ECOMMUNITY_STR, entry->config); if (entry->u.ecom) ecommunity_free(&entry->u.ecom); break; case COMMUNITY_LIST_EXPANDED: case EXTCOMMUNITY_LIST_EXPANDED: case LARGE_COMMUNITY_LIST_EXPANDED: XFREE(MTYPE_COMMUNITY_LIST_CONFIG, entry->config); if (entry->reg) bgp_regex_free(entry->reg); default: break; } XFREE(MTYPE_COMMUNITY_LIST_ENTRY, entry); } /* Allocate a new community-list. */ static struct community_list *community_list_new(void) { return XCALLOC(MTYPE_COMMUNITY_LIST, sizeof(struct community_list)); } /* Free community-list. */ static void community_list_free(struct community_list *list) { XFREE(MTYPE_COMMUNITY_LIST_NAME, list->name); XFREE(MTYPE_COMMUNITY_LIST, list); } static struct community_list * community_list_insert(struct community_list_handler *ch, const char *name, int master) { size_t i; long number; struct community_list *new; struct community_list *point; struct community_list_list *list; struct community_list_master *cm; /* Lookup community-list master. */ cm = community_list_master_lookup(ch, master); if (!cm) return NULL; /* Allocate new community_list and copy given name. */ new = community_list_new(); new->name = XSTRDUP(MTYPE_COMMUNITY_LIST_NAME, name); new->name_hash = bgp_clist_hash_key_community_list(new); /* Save for later */ hash_get(cm->hash, new, hash_alloc_intern); /* If name is made by all digit character. We treat it as number. */ for (number = 0, i = 0; i < strlen(name); i++) { if (isdigit((unsigned char)name[i])) number = (number * 10) + (name[i] - '0'); else break; } /* In case of name is all digit character */ if (i == strlen(name)) { new->sort = COMMUNITY_LIST_NUMBER; /* Set access_list to number list. */ list = &cm->num; for (point = list->head; point; point = point->next) if (atol(point->name) >= number) break; } else { new->sort = COMMUNITY_LIST_STRING; /* Set access_list to string list. */ list = &cm->str; /* Set point to insertion point. */ for (point = list->head; point; point = point->next) if (strcmp(point->name, name) >= 0) break; } /* Link to upper list. */ new->parent = list; /* In case of this is the first element of master. */ if (list->head == NULL) { list->head = list->tail = new; return new; } /* In case of insertion is made at the tail of access_list. */ if (point == NULL) { new->prev = list->tail; list->tail->next = new; list->tail = new; return new; } /* In case of insertion is made at the head of access_list. */ if (point == list->head) { new->next = list->head; list->head->prev = new; list->head = new; return new; } /* Insertion is made at middle of the access_list. */ new->next = point; new->prev = point->prev; if (point->prev) point->prev->next = new; point->prev = new; return new; } struct community_list *community_list_lookup(struct community_list_handler *ch, const char *name, uint32_t name_hash, int master) { struct community_list lookup; struct community_list_master *cm; if (!name) return NULL; cm = community_list_master_lookup(ch, master); if (!cm) return NULL; lookup.name = (char *)name; lookup.name_hash = name_hash; return hash_get(cm->hash, &lookup, NULL); } static struct community_list * community_list_get(struct community_list_handler *ch, const char *name, int master) { struct community_list *list; list = community_list_lookup(ch, name, 0, master); if (!list) list = community_list_insert(ch, name, master); return list; } static void community_list_delete(struct community_list_master *cm, struct community_list *list) { struct community_list_list *clist; struct community_entry *entry, *next; for (entry = list->head; entry; entry = next) { next = entry->next; community_entry_free(entry); } clist = list->parent; if (list->next) list->next->prev = list->prev; else clist->tail = list->prev; if (list->prev) list->prev->next = list->next; else clist->head = list->next; hash_release(cm->hash, list); community_list_free(list); } static int community_list_empty_p(struct community_list *list) { return (list->head == NULL && list->tail == NULL) ? 1 : 0; } /* Add community-list entry to the list. */ static void community_list_entry_add(struct community_list *list, struct community_entry *entry) { entry->next = NULL; entry->prev = list->tail; if (list->tail) list->tail->next = entry; else list->head = entry; list->tail = entry; } /* Delete community-list entry from the list. */ static void community_list_entry_delete(struct community_list_master *cm, struct community_list *list, struct community_entry *entry) { if (entry->next) entry->next->prev = entry->prev; else list->tail = entry->prev; if (entry->prev) entry->prev->next = entry->next; else list->head = entry->next; community_entry_free(entry); if (community_list_empty_p(list)) community_list_delete(cm, list); } /* Lookup community-list entry from the list. */ static struct community_entry * community_list_entry_lookup(struct community_list *list, const void *arg, int direct) { struct community_entry *entry; for (entry = list->head; entry; entry = entry->next) { switch (entry->style) { case COMMUNITY_LIST_STANDARD: if (entry->direct == direct && community_cmp(entry->u.com, arg)) return entry; break; case EXTCOMMUNITY_LIST_STANDARD: if (entry->direct == direct && ecommunity_cmp(entry->u.ecom, arg)) return entry; break; case LARGE_COMMUNITY_LIST_STANDARD: if (entry->direct == direct && lcommunity_cmp(entry->u.lcom, arg)) return entry; break; case COMMUNITY_LIST_EXPANDED: case EXTCOMMUNITY_LIST_EXPANDED: case LARGE_COMMUNITY_LIST_EXPANDED: if (entry->direct == direct && strcmp(entry->config, arg) == 0) return entry; break; default: break; } } return NULL; } static char *community_str_get(struct community *com, int i) { uint32_t comval; uint16_t as; uint16_t val; char *str; memcpy(&comval, com_nthval(com, i), sizeof(uint32_t)); comval = ntohl(comval); switch (comval) { case COMMUNITY_INTERNET: str = XSTRDUP(MTYPE_COMMUNITY_STR, "internet"); break; case COMMUNITY_GSHUT: str = XSTRDUP(MTYPE_COMMUNITY_STR, "graceful-shutdown"); break; case COMMUNITY_ACCEPT_OWN: str = XSTRDUP(MTYPE_COMMUNITY_STR, "accept-own"); break; case COMMUNITY_ROUTE_FILTER_TRANSLATED_v4: str = XSTRDUP(MTYPE_COMMUNITY_STR, "route-filter-translated-v4"); break; case COMMUNITY_ROUTE_FILTER_v4: str = XSTRDUP(MTYPE_COMMUNITY_STR, "route-filter-v4"); break; case COMMUNITY_ROUTE_FILTER_TRANSLATED_v6: str = XSTRDUP(MTYPE_COMMUNITY_STR, "route-filter-translated-v6"); break; case COMMUNITY_ROUTE_FILTER_v6: str = XSTRDUP(MTYPE_COMMUNITY_STR, "route-filter-v6"); break; case COMMUNITY_LLGR_STALE: str = XSTRDUP(MTYPE_COMMUNITY_STR, "llgr-stale"); break; case COMMUNITY_NO_LLGR: str = XSTRDUP(MTYPE_COMMUNITY_STR, "no-llgr"); break; case COMMUNITY_ACCEPT_OWN_NEXTHOP: str = XSTRDUP(MTYPE_COMMUNITY_STR, "accept-own-nexthop"); break; case COMMUNITY_BLACKHOLE: str = XSTRDUP(MTYPE_COMMUNITY_STR, "blackhole"); break; case COMMUNITY_NO_EXPORT: str = XSTRDUP(MTYPE_COMMUNITY_STR, "no-export"); break; case COMMUNITY_NO_ADVERTISE: str = XSTRDUP(MTYPE_COMMUNITY_STR, "no-advertise"); break; case COMMUNITY_LOCAL_AS: str = XSTRDUP(MTYPE_COMMUNITY_STR, "local-AS"); break; case COMMUNITY_NO_PEER: str = XSTRDUP(MTYPE_COMMUNITY_STR, "no-peer"); break; default: str = XSTRDUP(MTYPE_COMMUNITY_STR, "65536:65535"); as = (comval >> 16) & 0xFFFF; val = comval & 0xFFFF; snprintf(str, strlen(str), "%u:%d", as, val); break; } return str; } /* Internal function to perform regular expression match for * a single community. */ static int community_regexp_include(regex_t *reg, struct community *com, int i) { char *str; int rv; /* When there is no communities attribute it is treated as empty string. */ if (com == NULL || com->size == 0) str = XSTRDUP(MTYPE_COMMUNITY_STR, ""); else str = community_str_get(com, i); /* Regular expression match. */ rv = regexec(reg, str, 0, NULL, 0); XFREE(MTYPE_COMMUNITY_STR, str); if (rv == 0) return 1; /* No match. */ return 0; } /* Internal function to perform regular expression match for community attribute. */ static int community_regexp_match(struct community *com, regex_t *reg) { const char *str; /* When there is no communities attribute it is treated as empty string. */ if (com == NULL || com->size == 0) str = ""; else str = community_str(com, false); /* Regular expression match. */ if (regexec(reg, str, 0, NULL, 0) == 0) return 1; /* No match. */ return 0; } static char *lcommunity_str_get(struct lcommunity *lcom, int i) { struct lcommunity_val lcomval; uint32_t globaladmin; uint32_t localdata1; uint32_t localdata2; char *str; uint8_t *ptr; char *pnt; ptr = lcom->val + (i * LCOMMUNITY_SIZE); memcpy(&lcomval, ptr, LCOMMUNITY_SIZE); /* Allocate memory. 48 bytes taken off bgp_lcommunity.c */ str = pnt = XMALLOC(MTYPE_LCOMMUNITY_STR, 48); ptr = (uint8_t *)lcomval.val; ptr = ptr_get_be32(ptr, &globaladmin); ptr = ptr_get_be32(ptr, &localdata1); ptr = ptr_get_be32(ptr, &localdata2); (void)ptr; /* consume value */ sprintf(pnt, "%u:%u:%u", globaladmin, localdata1, localdata2); pnt += strlen(pnt); *pnt = '\0'; return str; } /* Internal function to perform regular expression match for * a single community. */ static int lcommunity_regexp_include(regex_t *reg, struct lcommunity *lcom, int i) { char *str; /* When there is no communities attribute it is treated as empty string. */ if (lcom == NULL || lcom->size == 0) str = XSTRDUP(MTYPE_LCOMMUNITY_STR, ""); else str = lcommunity_str_get(lcom, i); /* Regular expression match. */ if (regexec(reg, str, 0, NULL, 0) == 0) { XFREE(MTYPE_LCOMMUNITY_STR, str); return 1; } XFREE(MTYPE_LCOMMUNITY_STR, str); /* No match. */ return 0; } static int lcommunity_regexp_match(struct lcommunity *com, regex_t *reg) { const char *str; /* When there is no communities attribute it is treated as empty string. */ if (com == NULL || com->size == 0) str = ""; else str = lcommunity_str(com, false); /* Regular expression match. */ if (regexec(reg, str, 0, NULL, 0) == 0) return 1; /* No match. */ return 0; } static int ecommunity_regexp_match(struct ecommunity *ecom, regex_t *reg) { const char *str; /* When there is no communities attribute it is treated as empty string. */ if (ecom == NULL || ecom->size == 0) str = ""; else str = ecommunity_str(ecom); /* Regular expression match. */ if (regexec(reg, str, 0, NULL, 0) == 0) return 1; /* No match. */ return 0; } #if 0 /* Delete community attribute using regular expression match. Return modified communites attribute. */ static struct community * community_regexp_delete (struct community *com, regex_t * reg) { int i; uint32_t comval; /* Maximum is "65535:65535" + '\0'. */ char c[12]; const char *str; if (!com) return NULL; i = 0; while (i < com->size) { memcpy (&comval, com_nthval (com, i), sizeof (uint32_t)); comval = ntohl (comval); switch (comval) { case COMMUNITY_INTERNET: str = "internet"; break; case COMMUNITY_ACCEPT_OWN: str = "accept-own"; break; case COMMUNITY_ROUTE_FILTER_TRANSLATED_v4: str = "route-filter-translated-v4"; break; case COMMUNITY_ROUTE_FILTER_v4: str = "route-filter-v4"; break; case COMMUNITY_ROUTE_FILTER_TRANSLATED_v6: str = "route-filter-translated-v6"; break; case COMMUNITY_ROUTE_FILTER_v6: str = "route-filter-v6"; break; case COMMUNITY_LLGR_STALE: str = "llgr-stale"; break; case COMMUNITY_NO_LLGR: str = "no-llgr"; break; case COMMUNITY_ACCEPT_OWN_NEXTHOP: str = "accept-own-nexthop"; break; case COMMUNITY_BLACKHOLE: str = "blackhole"; break; case COMMUNITY_NO_EXPORT: str = "no-export"; break; case COMMUNITY_NO_ADVERTISE: str = "no-advertise"; break; case COMMUNITY_LOCAL_AS: str = "local-AS"; break; case COMMUNITY_NO_PEER: str = "no-peer"; break; default: sprintf (c, "%d:%d", (comval >> 16) & 0xFFFF, comval & 0xFFFF); str = c; break; } if (regexec (reg, str, 0, NULL, 0) == 0) community_del_val (com, com_nthval (com, i)); else i++; } return com; } #endif /* When given community attribute matches to the community-list return 1 else return 0. */ int community_list_match(struct community *com, struct community_list *list) { struct community_entry *entry; for (entry = list->head; entry; entry = entry->next) { if (entry->any) return entry->direct == COMMUNITY_PERMIT ? 1 : 0; if (entry->style == COMMUNITY_LIST_STANDARD) { if (community_include(entry->u.com, COMMUNITY_INTERNET)) return entry->direct == COMMUNITY_PERMIT ? 1 : 0; if (community_match(com, entry->u.com)) return entry->direct == COMMUNITY_PERMIT ? 1 : 0; } else if (entry->style == COMMUNITY_LIST_EXPANDED) { if (community_regexp_match(com, entry->reg)) return entry->direct == COMMUNITY_PERMIT ? 1 : 0; } } return 0; } int lcommunity_list_match(struct lcommunity *lcom, struct community_list *list) { struct community_entry *entry; for (entry = list->head; entry; entry = entry->next) { if (entry->any) return entry->direct == COMMUNITY_PERMIT ? 1 : 0; if (entry->style == LARGE_COMMUNITY_LIST_STANDARD) { if (lcommunity_match(lcom, entry->u.lcom)) return entry->direct == COMMUNITY_PERMIT ? 1 : 0; } else if (entry->style == LARGE_COMMUNITY_LIST_EXPANDED) { if (lcommunity_regexp_match(lcom, entry->reg)) return entry->direct == COMMUNITY_PERMIT ? 1 : 0; } } return 0; } /* Perform exact matching. In case of expanded large-community-list, do * same thing as lcommunity_list_match(). */ int lcommunity_list_exact_match(struct lcommunity *lcom, struct community_list *list) { struct community_entry *entry; for (entry = list->head; entry; entry = entry->next) { if (entry->any) return entry->direct == COMMUNITY_PERMIT ? 1 : 0; if (entry->style == LARGE_COMMUNITY_LIST_STANDARD) { if (lcommunity_cmp(lcom, entry->u.com)) return entry->direct == COMMUNITY_PERMIT ? 1 : 0; } else if (entry->style == LARGE_COMMUNITY_LIST_EXPANDED) { if (lcommunity_regexp_match(lcom, entry->reg)) return entry->direct == COMMUNITY_PERMIT ? 1 : 0; } } return 0; } int ecommunity_list_match(struct ecommunity *ecom, struct community_list *list) { struct community_entry *entry; for (entry = list->head; entry; entry = entry->next) { if (entry->any) return entry->direct == COMMUNITY_PERMIT ? 1 : 0; if (entry->style == EXTCOMMUNITY_LIST_STANDARD) { if (ecommunity_match(ecom, entry->u.ecom)) return entry->direct == COMMUNITY_PERMIT ? 1 : 0; } else if (entry->style == EXTCOMMUNITY_LIST_EXPANDED) { if (ecommunity_regexp_match(ecom, entry->reg)) return entry->direct == COMMUNITY_PERMIT ? 1 : 0; } } return 0; } /* Perform exact matching. In case of expanded community-list, do same thing as community_list_match(). */ int community_list_exact_match(struct community *com, struct community_list *list) { struct community_entry *entry; for (entry = list->head; entry; entry = entry->next) { if (entry->any) return entry->direct == COMMUNITY_PERMIT ? 1 : 0; if (entry->style == COMMUNITY_LIST_STANDARD) { if (community_include(entry->u.com, COMMUNITY_INTERNET)) return entry->direct == COMMUNITY_PERMIT ? 1 : 0; if (community_cmp(com, entry->u.com)) return entry->direct == COMMUNITY_PERMIT ? 1 : 0; } else if (entry->style == COMMUNITY_LIST_EXPANDED) { if (community_regexp_match(com, entry->reg)) return entry->direct == COMMUNITY_PERMIT ? 1 : 0; } } return 0; } /* Delete all permitted communities in the list from com. */ struct community *community_list_match_delete(struct community *com, struct community_list *list) { struct community_entry *entry; uint32_t val; uint32_t com_index_to_delete[com->size]; int delete_index = 0; int i; /* Loop over each community value and evaluate each against the * community-list. If we need to delete a community value add its index * to com_index_to_delete. */ for (i = 0; i < com->size; i++) { val = community_val_get(com, i); for (entry = list->head; entry; entry = entry->next) { if (entry->any) { if (entry->direct == COMMUNITY_PERMIT) { com_index_to_delete[delete_index] = i; delete_index++; } break; } else if ((entry->style == COMMUNITY_LIST_STANDARD) && (community_include(entry->u.com, COMMUNITY_INTERNET) || community_include(entry->u.com, val))) { if (entry->direct == COMMUNITY_PERMIT) { com_index_to_delete[delete_index] = i; delete_index++; } break; } else if ((entry->style == COMMUNITY_LIST_EXPANDED) && community_regexp_include(entry->reg, com, i)) { if (entry->direct == COMMUNITY_PERMIT) { com_index_to_delete[delete_index] = i; delete_index++; } break; } } } /* Delete all of the communities we flagged for deletion */ for (i = delete_index - 1; i >= 0; i--) { val = community_val_get(com, com_index_to_delete[i]); val = htonl(val); community_del_val(com, &val); } return com; } /* To avoid duplicated entry in the community-list, this function compares specified entry to existing entry. */ static int community_list_dup_check(struct community_list *list, struct community_entry *new) { struct community_entry *entry; for (entry = list->head; entry; entry = entry->next) { if (entry->style != new->style) continue; if (entry->direct != new->direct) continue; if (entry->any != new->any) continue; if (entry->any) return 1; switch (entry->style) { case COMMUNITY_LIST_STANDARD: if (community_cmp(entry->u.com, new->u.com)) return 1; break; case LARGE_COMMUNITY_LIST_STANDARD: if (lcommunity_cmp(entry->u.lcom, new->u.lcom)) return 1; break; case EXTCOMMUNITY_LIST_STANDARD: if (ecommunity_cmp(entry->u.ecom, new->u.ecom)) return 1; break; case COMMUNITY_LIST_EXPANDED: case EXTCOMMUNITY_LIST_EXPANDED: case LARGE_COMMUNITY_LIST_EXPANDED: if (strcmp(entry->config, new->config) == 0) return 1; break; default: break; } } return 0; } /* Set community-list. */ int community_list_set(struct community_list_handler *ch, const char *name, const char *str, int direct, int style) { struct community_entry *entry = NULL; struct community_list *list; struct community *com = NULL; regex_t *regex = NULL; /* Get community list. */ list = community_list_get(ch, name, COMMUNITY_LIST_MASTER); /* When community-list already has entry, new entry should have same style. If you want to have mixed style community-list, you can comment out this check. */ if (!community_list_empty_p(list)) { struct community_entry *first; first = list->head; if (style != first->style) { return (first->style == COMMUNITY_LIST_STANDARD ? COMMUNITY_LIST_ERR_STANDARD_CONFLICT : COMMUNITY_LIST_ERR_EXPANDED_CONFLICT); } } if (str) { if (style == COMMUNITY_LIST_STANDARD) com = community_str2com(str); else regex = bgp_regcomp(str); if (!com && !regex) return COMMUNITY_LIST_ERR_MALFORMED_VAL; } entry = community_entry_new(); entry->direct = direct; entry->style = style; entry->any = (str ? 0 : 1); entry->u.com = com; entry->reg = regex; entry->config = (regex ? XSTRDUP(MTYPE_COMMUNITY_LIST_CONFIG, str) : NULL); /* Do not put duplicated community entry. */ if (community_list_dup_check(list, entry)) community_entry_free(entry); else { community_list_entry_add(list, entry); route_map_notify_dependencies(name, RMAP_EVENT_CLIST_ADDED); } return 0; } /* Unset community-list */ int community_list_unset(struct community_list_handler *ch, const char *name, const char *str, int direct, int style) { struct community_list_master *cm = NULL; struct community_entry *entry = NULL; struct community_list *list; struct community *com = NULL; /* Lookup community list. */ list = community_list_lookup(ch, name, 0, COMMUNITY_LIST_MASTER); if (list == NULL) return COMMUNITY_LIST_ERR_CANT_FIND_LIST; cm = community_list_master_lookup(ch, COMMUNITY_LIST_MASTER); /* Delete all of entry belongs to this community-list. */ if (!str) { community_list_delete(cm, list); route_map_notify_dependencies(name, RMAP_EVENT_CLIST_DELETED); return 0; } if (style == COMMUNITY_LIST_STANDARD) com = community_str2com(str); if (com) { entry = community_list_entry_lookup(list, com, direct); community_free(&com); } else entry = community_list_entry_lookup(list, str, direct); if (!entry) return COMMUNITY_LIST_ERR_CANT_FIND_LIST; community_list_entry_delete(cm, list, entry); route_map_notify_dependencies(name, RMAP_EVENT_CLIST_DELETED); return 0; } /* Delete all permitted large communities in the list from com. */ struct lcommunity *lcommunity_list_match_delete(struct lcommunity *lcom, struct community_list *list) { struct community_entry *entry; uint32_t com_index_to_delete[lcom->size]; uint8_t *ptr; int delete_index = 0; int i; /* Loop over each lcommunity value and evaluate each against the * community-list. If we need to delete a community value add its index * to com_index_to_delete. */ for (i = 0; i < lcom->size; i++) { ptr = lcom->val + (i * LCOMMUNITY_SIZE); for (entry = list->head; entry; entry = entry->next) { if (entry->any) { if (entry->direct == COMMUNITY_PERMIT) { com_index_to_delete[delete_index] = i; delete_index++; } break; } else if ((entry->style == LARGE_COMMUNITY_LIST_STANDARD) && lcommunity_include(entry->u.lcom, ptr)) { if (entry->direct == COMMUNITY_PERMIT) { com_index_to_delete[delete_index] = i; delete_index++; } break; } else if ((entry->style == LARGE_COMMUNITY_LIST_EXPANDED) && lcommunity_regexp_include(entry->reg, lcom, i)) { if (entry->direct == COMMUNITY_PERMIT) { com_index_to_delete[delete_index] = i; delete_index++; } break; } } } /* Delete all of the communities we flagged for deletion */ for (i = delete_index - 1; i >= 0; i--) { ptr = lcom->val + (com_index_to_delete[i] * LCOMMUNITY_SIZE); lcommunity_del_val(lcom, ptr); } return lcom; } /* Helper to check if every octet do not exceed UINT_MAX */ static int lcommunity_list_valid(const char *community) { int octets = 0; char **splits; int num; frrstr_split(community, ":", &splits, &num); for (int i = 0; i < num; i++) { if (strtoul(splits[i], NULL, 10) > UINT_MAX) return 0; if (strlen(splits[i]) == 0) return 0; octets++; XFREE(MTYPE_TMP, splits[i]); } XFREE(MTYPE_TMP, splits); if (octets < 3) return 0; return 1; } /* Set lcommunity-list. */ int lcommunity_list_set(struct community_list_handler *ch, const char *name, const char *str, int direct, int style) { struct community_entry *entry = NULL; struct community_list *list; struct lcommunity *lcom = NULL; regex_t *regex = NULL; /* Get community list. */ list = community_list_get(ch, name, LARGE_COMMUNITY_LIST_MASTER); /* When community-list already has entry, new entry should have same style. If you want to have mixed style community-list, you can comment out this check. */ if (!community_list_empty_p(list)) { struct community_entry *first; first = list->head; if (style != first->style) { return (first->style == COMMUNITY_LIST_STANDARD ? COMMUNITY_LIST_ERR_STANDARD_CONFLICT : COMMUNITY_LIST_ERR_EXPANDED_CONFLICT); } } if (str) { if (!lcommunity_list_valid(str)) return COMMUNITY_LIST_ERR_MALFORMED_VAL; if (style == LARGE_COMMUNITY_LIST_STANDARD) lcom = lcommunity_str2com(str); else regex = bgp_regcomp(str); if (!lcom && !regex) return COMMUNITY_LIST_ERR_MALFORMED_VAL; } entry = community_entry_new(); entry->direct = direct; entry->style = style; entry->any = (str ? 0 : 1); entry->u.lcom = lcom; entry->reg = regex; entry->config = (regex ? XSTRDUP(MTYPE_COMMUNITY_LIST_CONFIG, str) : NULL); /* Do not put duplicated community entry. */ if (community_list_dup_check(list, entry)) community_entry_free(entry); else { community_list_entry_add(list, entry); route_map_notify_dependencies(name, RMAP_EVENT_LLIST_ADDED); } return 0; } /* Unset community-list. When str is NULL, delete all of community-list entry belongs to the specified name. */ int lcommunity_list_unset(struct community_list_handler *ch, const char *name, const char *str, int direct, int style) { struct community_list_master *cm = NULL; struct community_entry *entry = NULL; struct community_list *list; struct lcommunity *lcom = NULL; regex_t *regex = NULL; /* Lookup community list. */ list = community_list_lookup(ch, name, 0, LARGE_COMMUNITY_LIST_MASTER); if (list == NULL) return COMMUNITY_LIST_ERR_CANT_FIND_LIST; cm = community_list_master_lookup(ch, LARGE_COMMUNITY_LIST_MASTER); /* Delete all of entry belongs to this community-list. */ if (!str) { community_list_delete(cm, list); route_map_notify_dependencies(name, RMAP_EVENT_LLIST_DELETED); return 0; } if (style == LARGE_COMMUNITY_LIST_STANDARD) lcom = lcommunity_str2com(str); else regex = bgp_regcomp(str); if (!lcom && !regex) return COMMUNITY_LIST_ERR_MALFORMED_VAL; if (lcom) entry = community_list_entry_lookup(list, lcom, direct); else entry = community_list_entry_lookup(list, str, direct); if (lcom) lcommunity_free(&lcom); if (regex) bgp_regex_free(regex); if (!entry) return COMMUNITY_LIST_ERR_CANT_FIND_LIST; community_list_entry_delete(cm, list, entry); route_map_notify_dependencies(name, RMAP_EVENT_LLIST_DELETED); return 0; } /* Set extcommunity-list. */ int extcommunity_list_set(struct community_list_handler *ch, const char *name, const char *str, int direct, int style) { struct community_entry *entry = NULL; struct community_list *list; struct ecommunity *ecom = NULL; regex_t *regex = NULL; if (str == NULL) return COMMUNITY_LIST_ERR_MALFORMED_VAL; /* Get community list. */ list = community_list_get(ch, name, EXTCOMMUNITY_LIST_MASTER); /* When community-list already has entry, new entry should have same style. If you want to have mixed style community-list, you can comment out this check. */ if (!community_list_empty_p(list)) { struct community_entry *first; first = list->head; if (style != first->style) { return (first->style == EXTCOMMUNITY_LIST_STANDARD ? COMMUNITY_LIST_ERR_STANDARD_CONFLICT : COMMUNITY_LIST_ERR_EXPANDED_CONFLICT); } } if (style == EXTCOMMUNITY_LIST_STANDARD) ecom = ecommunity_str2com(str, 0, 1); else regex = bgp_regcomp(str); if (!ecom && !regex) return COMMUNITY_LIST_ERR_MALFORMED_VAL; if (ecom) ecom->str = ecommunity_ecom2str(ecom, ECOMMUNITY_FORMAT_DISPLAY, 0); entry = community_entry_new(); entry->direct = direct; entry->style = style; entry->any = 0; if (ecom) entry->config = ecommunity_ecom2str( ecom, ECOMMUNITY_FORMAT_COMMUNITY_LIST, 0); else if (regex) entry->config = XSTRDUP(MTYPE_COMMUNITY_LIST_CONFIG, str); entry->u.ecom = ecom; entry->reg = regex; /* Do not put duplicated community entry. */ if (community_list_dup_check(list, entry)) community_entry_free(entry); else { community_list_entry_add(list, entry); route_map_notify_dependencies(name, RMAP_EVENT_ECLIST_ADDED); } return 0; } /* Unset extcommunity-list. * * When str is NULL, delete all extcommunity-list entries belonging to the * specified name. */ int extcommunity_list_unset(struct community_list_handler *ch, const char *name, const char *str, int direct, int style) { struct community_list_master *cm = NULL; struct community_entry *entry = NULL; struct community_list *list; struct ecommunity *ecom = NULL; /* Lookup extcommunity list. */ list = community_list_lookup(ch, name, 0, EXTCOMMUNITY_LIST_MASTER); if (list == NULL) return COMMUNITY_LIST_ERR_CANT_FIND_LIST; cm = community_list_master_lookup(ch, EXTCOMMUNITY_LIST_MASTER); /* Delete all of entry belongs to this extcommunity-list. */ if (!str) { community_list_delete(cm, list); route_map_notify_dependencies(name, RMAP_EVENT_ECLIST_DELETED); return 0; } if (style == EXTCOMMUNITY_LIST_STANDARD) ecom = ecommunity_str2com(str, 0, 1); if (ecom) { entry = community_list_entry_lookup(list, ecom, direct); ecommunity_free(&ecom); } else entry = community_list_entry_lookup(list, str, direct); if (!entry) return COMMUNITY_LIST_ERR_CANT_FIND_LIST; community_list_entry_delete(cm, list, entry); route_map_notify_dependencies(name, RMAP_EVENT_ECLIST_DELETED); return 0; } /* Initializa community-list. Return community-list handler. */ struct community_list_handler *community_list_init(void) { struct community_list_handler *ch; ch = XCALLOC(MTYPE_COMMUNITY_LIST_HANDLER, sizeof(struct community_list_handler)); ch->community_list.hash = hash_create_size(4, bgp_clist_hash_key_community_list, bgp_clist_hash_cmp_community_list, "Community List Number Quick Lookup"); ch->extcommunity_list.hash = hash_create_size(4, bgp_clist_hash_key_community_list, bgp_clist_hash_cmp_community_list, "Extended Community List Quick Lookup"); ch->lcommunity_list.hash = hash_create_size(4, bgp_clist_hash_key_community_list, bgp_clist_hash_cmp_community_list, "Large Community List Quick Lookup"); return ch; } /* Terminate community-list. */ void community_list_terminate(struct community_list_handler *ch) { struct community_list_master *cm; struct community_list *list; cm = &ch->community_list; while ((list = cm->num.head) != NULL) community_list_delete(cm, list); while ((list = cm->str.head) != NULL) community_list_delete(cm, list); hash_free(cm->hash); cm = &ch->lcommunity_list; while ((list = cm->num.head) != NULL) community_list_delete(cm, list); while ((list = cm->str.head) != NULL) community_list_delete(cm, list); hash_free(cm->hash); cm = &ch->extcommunity_list; while ((list = cm->num.head) != NULL) community_list_delete(cm, list); while ((list = cm->str.head) != NULL) community_list_delete(cm, list); hash_free(cm->hash); XFREE(MTYPE_COMMUNITY_LIST_HANDLER, ch); } frr-7.2.1/bgpd/bgp_clist.h0000644000000000000000000001333313610377563012252 00000000000000/* BGP Community list. * Copyright (C) 1999 Kunihiro Ishiguro * * This file is part of GNU Zebra. * * GNU Zebra is free software; you can redistribute it and/or modify it * under the terms of the GNU General Public License as published by the * Free Software Foundation; either version 2, or (at your option) any * later version. * * GNU Zebra 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; see the file COPYING; if not, write to the Free Software * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA */ #ifndef _QUAGGA_BGP_CLIST_H #define _QUAGGA_BGP_CLIST_H #include "jhash.h" /* Master Community-list. */ #define COMMUNITY_LIST_MASTER 0 #define EXTCOMMUNITY_LIST_MASTER 1 #define LARGE_COMMUNITY_LIST_MASTER 2 /* Community-list deny and permit. */ #define COMMUNITY_DENY 0 #define COMMUNITY_PERMIT 1 /* Number and string based community-list name. */ #define COMMUNITY_LIST_STRING 0 #define COMMUNITY_LIST_NUMBER 1 /* Community-list entry types. */ #define COMMUNITY_LIST_STANDARD 0 /* Standard community-list. */ #define COMMUNITY_LIST_EXPANDED 1 /* Expanded community-list. */ #define EXTCOMMUNITY_LIST_STANDARD 2 /* Standard extcommunity-list. */ #define EXTCOMMUNITY_LIST_EXPANDED 3 /* Expanded extcommunity-list. */ #define LARGE_COMMUNITY_LIST_STANDARD 4 /* Standard Large community-list. */ #define LARGE_COMMUNITY_LIST_EXPANDED 5 /* Expanded Large community-list. */ /* Community-list. */ struct community_list { /* Name of the community-list. */ char *name; /* Stored hash value of name, to further speed up hash operations */ uint32_t name_hash; /* String or number. */ int sort; /* Link to upper list. */ struct community_list_list *parent; /* Linked list for other community-list. */ struct community_list *next; struct community_list *prev; /* Community-list entry in this community-list. */ struct community_entry *head; struct community_entry *tail; }; /* Each entry in community-list. */ struct community_entry { struct community_entry *next; struct community_entry *prev; /* Permit or deny. */ uint8_t direct; /* Standard or expanded. */ uint8_t style; /* Any match. */ uint8_t any; /* Community structure. */ union { struct community *com; struct ecommunity *ecom; struct lcommunity *lcom; } u; /* Configuration string. */ char *config; /* Expanded community-list regular expression. */ regex_t *reg; }; /* Linked list of community-list. */ struct community_list_list { struct community_list *head; struct community_list *tail; }; /* Master structure of community-list and extcommunity-list. */ struct community_list_master { struct community_list_list num; struct community_list_list str; struct hash *hash; }; /* Community-list handler. community_list_init() returns this structure as handler. */ struct community_list_handler { /* Community-list. */ struct community_list_master community_list; /* Exteded community-list. */ struct community_list_master extcommunity_list; /* Large community-list. */ struct community_list_master lcommunity_list; }; /* Error code of community-list. */ #define COMMUNITY_LIST_ERR_CANT_FIND_LIST -1 #define COMMUNITY_LIST_ERR_MALFORMED_VAL -2 #define COMMUNITY_LIST_ERR_STANDARD_CONFLICT -3 #define COMMUNITY_LIST_ERR_EXPANDED_CONFLICT -4 /* Handler. */ extern struct community_list_handler *bgp_clist; /* Prototypes. */ extern struct community_list_handler *community_list_init(void); extern void community_list_terminate(struct community_list_handler *); extern int community_list_set(struct community_list_handler *ch, const char *name, const char *str, int direct, int style); extern int community_list_unset(struct community_list_handler *ch, const char *name, const char *str, int direct, int style); extern int extcommunity_list_set(struct community_list_handler *ch, const char *name, const char *str, int direct, int style); extern int extcommunity_list_unset(struct community_list_handler *ch, const char *name, const char *str, int direct, int style); extern int lcommunity_list_set(struct community_list_handler *ch, const char *name, const char *str, int direct, int style); extern int lcommunity_list_unset(struct community_list_handler *ch, const char *name, const char *str, int direct, int style); extern struct community_list_master * community_list_master_lookup(struct community_list_handler *, int); extern struct community_list * community_list_lookup(struct community_list_handler *c, const char *name, uint32_t name_hash, int master); extern int community_list_match(struct community *, struct community_list *); extern int ecommunity_list_match(struct ecommunity *, struct community_list *); extern int lcommunity_list_match(struct lcommunity *, struct community_list *); extern int community_list_exact_match(struct community *, struct community_list *); extern int lcommunity_list_exact_match(struct lcommunity *lcom, struct community_list *list); extern struct community *community_list_match_delete(struct community *, struct community_list *); extern struct lcommunity * lcommunity_list_match_delete(struct lcommunity *lcom, struct community_list *list); static inline uint32_t bgp_clist_hash_key(char *name) { return jhash(name, strlen(name), 0xdeadbeaf); } #endif /* _QUAGGA_BGP_CLIST_H */ frr-7.2.1/bgpd/bgp_community.c0000644000000000000000000005701313610377563013156 00000000000000/* Community attribute related functions. * Copyright (C) 1998, 2001 Kunihiro Ishiguro * * This file is part of GNU Zebra. * * GNU Zebra is free software; you can redistribute it and/or modify it * under the terms of the GNU General Public License as published by the * Free Software Foundation; either version 2, or (at your option) any * later version. * * GNU Zebra 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; see the file COPYING; if not, write to the Free Software * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA */ #include #include "command.h" #include "hash.h" #include "memory.h" #include "jhash.h" #include "bgpd/bgp_memory.h" #include "bgpd/bgp_community.h" /* Hash of community attribute. */ static struct hash *comhash; /* Allocate a new communities value. */ static struct community *community_new(void) { return XCALLOC(MTYPE_COMMUNITY, sizeof(struct community)); } /* Free communities value. */ void community_free(struct community **com) { XFREE(MTYPE_COMMUNITY_VAL, (*com)->val); XFREE(MTYPE_COMMUNITY_STR, (*com)->str); if ((*com)->json) { json_object_free((*com)->json); (*com)->json = NULL; } XFREE(MTYPE_COMMUNITY, (*com)); } /* Add one community value to the community. */ static void community_add_val(struct community *com, uint32_t val) { com->size++; if (com->val) com->val = XREALLOC(MTYPE_COMMUNITY_VAL, com->val, com_length(com)); else com->val = XMALLOC(MTYPE_COMMUNITY_VAL, com_length(com)); val = htonl(val); memcpy(com_lastval(com), &val, sizeof(uint32_t)); } /* Delete one community. */ void community_del_val(struct community *com, uint32_t *val) { int i = 0; int c = 0; if (!com->val) return; while (i < com->size) { if (memcmp(com->val + i, val, sizeof(uint32_t)) == 0) { c = com->size - i - 1; if (c > 0) memmove(com->val + i, com->val + (i + 1), c * sizeof(*val)); com->size--; if (com->size > 0) com->val = XREALLOC(MTYPE_COMMUNITY_VAL, com->val, com_length(com)); else { XFREE(MTYPE_COMMUNITY_VAL, com->val); com->val = NULL; } return; } i++; } } /* Delete all communities listed in com2 from com1 */ struct community *community_delete(struct community *com1, struct community *com2) { int i = 0; while (i < com2->size) { community_del_val(com1, com2->val + i); i++; } return com1; } /* Callback function from qsort(). */ static int community_compare(const void *a1, const void *a2) { uint32_t v1; uint32_t v2; memcpy(&v1, a1, sizeof(uint32_t)); memcpy(&v2, a2, sizeof(uint32_t)); v1 = ntohl(v1); v2 = ntohl(v2); if (v1 < v2) return -1; if (v1 > v2) return 1; return 0; } int community_include(struct community *com, uint32_t val) { int i; val = htonl(val); for (i = 0; i < com->size; i++) if (memcmp(&val, com_nthval(com, i), sizeof(uint32_t)) == 0) return 1; return 0; } uint32_t community_val_get(struct community *com, int i) { uint8_t *p; uint32_t val; p = (uint8_t *)com->val; p += (i * 4); memcpy(&val, p, sizeof(uint32_t)); return ntohl(val); } /* Sort and uniq given community. */ struct community *community_uniq_sort(struct community *com) { int i; struct community *new; uint32_t val; if (!com) return NULL; new = community_new(); new->json = NULL; for (i = 0; i < com->size; i++) { val = community_val_get(com, i); if (!community_include(new, val)) community_add_val(new, val); } qsort(new->val, new->size, sizeof(uint32_t), community_compare); return new; } /* Convert communities attribute to string. For Well-known communities value, below keyword is used. 0x0 "internet" 0xFFFF0000 "graceful-shutdown" 0xFFFF0001 "accept-own" 0xFFFF0002 "route-filter-translated-v4" 0xFFFF0003 "route-filter-v4" 0xFFFF0004 "route-filter-translated-v6" 0xFFFF0005 "route-filter-v6" 0xFFFF0006 "llgr-stale" 0xFFFF0007 "no-llgr" 0xFFFF0008 "accept-own-nexthop" 0xFFFF029A "blackhole" 0xFFFFFF01 "no-export" 0xFFFFFF02 "no-advertise" 0xFFFFFF03 "local-AS" 0xFFFFFF04 "no-peer" For other values, "AS:VAL" format is used. */ static void set_community_string(struct community *com, bool make_json) { int i; char *str; int len; int first; uint32_t comval; uint16_t as; uint16_t val; json_object *json_community_list = NULL; json_object *json_string = NULL; if (!com) return; if (make_json) { com->json = json_object_new_object(); json_community_list = json_object_new_array(); } /* When communities attribute is empty. */ if (com->size == 0) { str = XMALLOC(MTYPE_COMMUNITY_STR, 1); str[0] = '\0'; if (make_json) { json_object_string_add(com->json, "string", ""); json_object_object_add(com->json, "list", json_community_list); } com->str = str; return; } /* Memory allocation is time consuming work. So we calculate required string length first. */ len = 0; for (i = 0; i < com->size; i++) { memcpy(&comval, com_nthval(com, i), sizeof(uint32_t)); comval = ntohl(comval); switch (comval) { case COMMUNITY_INTERNET: len += strlen(" internet"); break; case COMMUNITY_GSHUT: len += strlen(" graceful-shutdown"); break; case COMMUNITY_ACCEPT_OWN: len += strlen(" accept-own"); break; case COMMUNITY_ROUTE_FILTER_TRANSLATED_v4: len += strlen(" route-filter-translated-v4"); break; case COMMUNITY_ROUTE_FILTER_v4: len += strlen(" route-filter-v4"); break; case COMMUNITY_ROUTE_FILTER_TRANSLATED_v6: len += strlen(" route-filter-translated-v6"); break; case COMMUNITY_ROUTE_FILTER_v6: len += strlen(" route-filter-v6"); break; case COMMUNITY_LLGR_STALE: len += strlen(" llgr-stale"); break; case COMMUNITY_NO_LLGR: len += strlen(" no-llgr"); break; case COMMUNITY_ACCEPT_OWN_NEXTHOP: len += strlen(" accept-own-nexthop"); break; case COMMUNITY_BLACKHOLE: len += strlen(" blackhole"); break; case COMMUNITY_NO_EXPORT: len += strlen(" no-export"); break; case COMMUNITY_NO_ADVERTISE: len += strlen(" no-advertise"); break; case COMMUNITY_LOCAL_AS: len += strlen(" local-AS"); break; case COMMUNITY_NO_PEER: len += strlen(" no-peer"); break; default: len += strlen(" 65536:65535"); break; } } /* Allocate memory. */ str = XCALLOC(MTYPE_COMMUNITY_STR, len); first = 1; /* Fill in string. */ for (i = 0; i < com->size; i++) { memcpy(&comval, com_nthval(com, i), sizeof(uint32_t)); comval = ntohl(comval); if (first) first = 0; else strlcat(str, " ", len); switch (comval) { case COMMUNITY_INTERNET: strlcat(str, "internet", len); if (make_json) { json_string = json_object_new_string("internet"); json_object_array_add(json_community_list, json_string); } break; case COMMUNITY_GSHUT: strlcat(str, "graceful-shutdown", len); if (make_json) { json_string = json_object_new_string( "gracefulShutdown"); json_object_array_add(json_community_list, json_string); } break; case COMMUNITY_ACCEPT_OWN: strlcat(str, "accept-own", len); if (make_json) { json_string = json_object_new_string( "acceptown"); json_object_array_add(json_community_list, json_string); } break; case COMMUNITY_ROUTE_FILTER_TRANSLATED_v4: strlcat(str, "route-filter-translated-v4", len); if (make_json) { json_string = json_object_new_string( "routeFilterTranslatedV4"); json_object_array_add(json_community_list, json_string); } break; case COMMUNITY_ROUTE_FILTER_v4: strlcat(str, "route-filter-v4", len); if (make_json) { json_string = json_object_new_string( "routeFilterV4"); json_object_array_add(json_community_list, json_string); } break; case COMMUNITY_ROUTE_FILTER_TRANSLATED_v6: strlcat(str, "route-filter-translated-v6", len); if (make_json) { json_string = json_object_new_string( "routeFilterTranslatedV6"); json_object_array_add(json_community_list, json_string); } break; case COMMUNITY_ROUTE_FILTER_v6: strlcat(str, "route-filter-v6", len); if (make_json) { json_string = json_object_new_string( "routeFilterV6"); json_object_array_add(json_community_list, json_string); } break; case COMMUNITY_LLGR_STALE: strlcat(str, "llgr-stale", len); if (make_json) { json_string = json_object_new_string( "llgrStale"); json_object_array_add(json_community_list, json_string); } break; case COMMUNITY_NO_LLGR: strlcat(str, "no-llgr", len); if (make_json) { json_string = json_object_new_string( "noLlgr"); json_object_array_add(json_community_list, json_string); } break; case COMMUNITY_ACCEPT_OWN_NEXTHOP: strlcat(str, "accept-own-nexthop", len); if (make_json) { json_string = json_object_new_string( "acceptownnexthop"); json_object_array_add(json_community_list, json_string); } break; case COMMUNITY_BLACKHOLE: strlcat(str, "blackhole", len); if (make_json) { json_string = json_object_new_string( "blackhole"); json_object_array_add(json_community_list, json_string); } break; case COMMUNITY_NO_EXPORT: strlcat(str, "no-export", len); if (make_json) { json_string = json_object_new_string("noExport"); json_object_array_add(json_community_list, json_string); } break; case COMMUNITY_NO_ADVERTISE: strlcat(str, "no-advertise", len); if (make_json) { json_string = json_object_new_string("noAdvertise"); json_object_array_add(json_community_list, json_string); } break; case COMMUNITY_LOCAL_AS: strlcat(str, "local-AS", len); if (make_json) { json_string = json_object_new_string("localAs"); json_object_array_add(json_community_list, json_string); } break; case COMMUNITY_NO_PEER: strlcat(str, "no-peer", len); if (make_json) { json_string = json_object_new_string("noPeer"); json_object_array_add(json_community_list, json_string); } break; default: as = (comval >> 16) & 0xFFFF; val = comval & 0xFFFF; char buf[32]; snprintf(buf, sizeof(buf), "%u:%d", as, val); strlcat(str, buf, len); if (make_json) { json_string = json_object_new_string(buf); json_object_array_add(json_community_list, json_string); } break; } } if (make_json) { json_object_string_add(com->json, "string", str); json_object_object_add(com->json, "list", json_community_list); } com->str = str; } /* Intern communities attribute. */ struct community *community_intern(struct community *com) { struct community *find; /* Assert this community structure is not interned. */ assert(com->refcnt == 0); /* Lookup community hash. */ find = (struct community *)hash_get(comhash, com, hash_alloc_intern); /* Arguemnt com is allocated temporary. So when it is not used in hash, it should be freed. */ if (find != com) community_free(&com); /* Increment refrence counter. */ find->refcnt++; /* Make string. */ if (!find->str) set_community_string(find, false); return find; } /* Free community attribute. */ void community_unintern(struct community **com) { struct community *ret; if ((*com)->refcnt) (*com)->refcnt--; /* Pull off from hash. */ if ((*com)->refcnt == 0) { /* Community value com must exist in hash. */ ret = (struct community *)hash_release(comhash, *com); assert(ret != NULL); community_free(com); } } /* Create new community attribute. */ struct community *community_parse(uint32_t *pnt, unsigned short length) { struct community tmp; struct community *new; /* If length is malformed return NULL. */ if (length % 4) return NULL; /* Make temporary community for hash look up. */ tmp.size = length / 4; tmp.val = pnt; new = community_uniq_sort(&tmp); return community_intern(new); } struct community *community_dup(struct community *com) { struct community *new; new = XCALLOC(MTYPE_COMMUNITY, sizeof(struct community)); new->size = com->size; if (new->size) { new->val = XMALLOC(MTYPE_COMMUNITY_VAL, com->size * 4); memcpy(new->val, com->val, com->size * 4); } else new->val = NULL; return new; } /* Retrun string representation of communities attribute. */ char *community_str(struct community *com, bool make_json) { if (!com) return NULL; if (make_json && !com->json && com->str) XFREE(MTYPE_COMMUNITY_STR, com->str); if (!com->str) set_community_string(com, make_json); return com->str; } /* Make hash value of community attribute. This function is used by hash package.*/ unsigned int community_hash_make(const struct community *com) { uint32_t *pnt = (uint32_t *)com->val; return jhash2(pnt, com->size, 0x43ea96c1); } int community_match(const struct community *com1, const struct community *com2) { int i = 0; int j = 0; if (com1 == NULL && com2 == NULL) return 1; if (com1 == NULL || com2 == NULL) return 0; if (com1->size < com2->size) return 0; /* Every community on com2 needs to be on com1 for this to match */ while (i < com1->size && j < com2->size) { if (memcmp(com1->val + i, com2->val + j, sizeof(uint32_t)) == 0) j++; i++; } if (j == com2->size) return 1; else return 0; } /* If two aspath have same value then return 1 else return 0. This function is used by hash package. */ bool community_cmp(const struct community *com1, const struct community *com2) { if (com1 == NULL && com2 == NULL) return true; if (com1 == NULL || com2 == NULL) return false; if (com1->size == com2->size) if (memcmp(com1->val, com2->val, com1->size * 4) == 0) return true; return false; } /* Add com2 to the end of com1. */ struct community *community_merge(struct community *com1, struct community *com2) { if (com1->val) com1->val = XREALLOC(MTYPE_COMMUNITY_VAL, com1->val, (com1->size + com2->size) * 4); else com1->val = XMALLOC(MTYPE_COMMUNITY_VAL, (com1->size + com2->size) * 4); memcpy(com1->val + com1->size, com2->val, com2->size * 4); com1->size += com2->size; return com1; } /* Community token enum. */ enum community_token { community_token_val, community_token_gshut, community_token_accept_own, community_token_route_filter_translated_v4, community_token_route_filter_v4, community_token_route_filter_translated_v6, community_token_route_filter_v6, community_token_llgr_stale, community_token_no_llgr, community_token_accept_own_nexthop, community_token_blackhole, community_token_no_export, community_token_no_advertise, community_token_local_as, community_token_no_peer, community_token_unknown }; /* Get next community token from string. */ static const char * community_gettoken(const char *buf, enum community_token *token, uint32_t *val) { const char *p = buf; /* Skip white space. */ while (isspace((unsigned char)*p)) p++; /* Check the end of the line. */ if (*p == '\0') return NULL; /* Well known community string check. */ if (isalpha((unsigned char)*p)) { if (strncmp(p, "internet", strlen("internet")) == 0) { *val = COMMUNITY_INTERNET; *token = community_token_no_export; p += strlen("internet"); return p; } if (strncmp(p, "graceful-shutdown", strlen("graceful-shutdown")) == 0) { *val = COMMUNITY_GSHUT; *token = community_token_gshut; p += strlen("graceful-shutdown"); return p; } if (strncmp(p, "accept-own", strlen("accept-own")) == 0) { *val = COMMUNITY_ACCEPT_OWN; *token = community_token_accept_own; p += strlen("accept-own"); return p; } if (strncmp(p, "route-filter-translated-v4", strlen("route-filter-translated-v4")) == 0) { *val = COMMUNITY_ROUTE_FILTER_TRANSLATED_v4; *token = community_token_route_filter_translated_v4; p += strlen("route-filter-translated-v4"); return p; } if (strncmp(p, "route-filter-v4", strlen("route-filter-v4")) == 0) { *val = COMMUNITY_ROUTE_FILTER_v4; *token = community_token_route_filter_v4; p += strlen("route-filter-v4"); return p; } if (strncmp(p, "route-filter-translated-v6", strlen("route-filter-translated-v6")) == 0) { *val = COMMUNITY_ROUTE_FILTER_TRANSLATED_v6; *token = community_token_route_filter_translated_v6; p += strlen("route-filter-translated-v6"); return p; } if (strncmp(p, "route-filter-v6", strlen("route-filter-v6")) == 0) { *val = COMMUNITY_ROUTE_FILTER_v6; *token = community_token_route_filter_v6; p += strlen("route-filter-v6"); return p; } if (strncmp(p, "llgr-stale", strlen("llgr-stale")) == 0) { *val = COMMUNITY_LLGR_STALE; *token = community_token_llgr_stale; p += strlen("llgr-stale"); return p; } if (strncmp(p, "no-llgr", strlen("no-llgr")) == 0) { *val = COMMUNITY_NO_LLGR; *token = community_token_no_llgr; p += strlen("no-llgr"); return p; } if (strncmp(p, "accept-own-nexthop", strlen("accept-own-nexthop")) == 0) { *val = COMMUNITY_ACCEPT_OWN_NEXTHOP; *token = community_token_accept_own_nexthop; p += strlen("accept-own-nexthop"); return p; } if (strncmp(p, "blackhole", strlen("blackhole")) == 0) { *val = COMMUNITY_BLACKHOLE; *token = community_token_blackhole; p += strlen("blackhole"); return p; } if (strncmp(p, "no-export", strlen("no-export")) == 0) { *val = COMMUNITY_NO_EXPORT; *token = community_token_no_export; p += strlen("no-export"); return p; } if (strncmp(p, "no-advertise", strlen("no-advertise")) == 0) { *val = COMMUNITY_NO_ADVERTISE; *token = community_token_no_advertise; p += strlen("no-advertise"); return p; } if (strncmp(p, "local-AS", strlen("local-AS")) == 0) { *val = COMMUNITY_LOCAL_AS; *token = community_token_local_as; p += strlen("local-AS"); return p; } if (strncmp(p, "no-peer", strlen("no-peer")) == 0) { *val = COMMUNITY_NO_PEER; *token = community_token_no_peer; p += strlen("no-peer"); return p; } /* Unknown string. */ *token = community_token_unknown; return NULL; } /* Community value. */ if (isdigit((unsigned char)*p)) { int separator = 0; int digit = 0; uint32_t community_low = 0; uint32_t community_high = 0; while (isdigit((unsigned char)*p) || *p == ':') { if (*p == ':') { if (separator) { *token = community_token_unknown; return NULL; } else { separator = 1; digit = 0; if (community_low > UINT16_MAX) { *token = community_token_unknown; return NULL; } community_high = community_low << 16; community_low = 0; } } else { digit = 1; community_low *= 10; community_low += (*p - '0'); } p++; } if (!digit) { *token = community_token_unknown; return NULL; } if (community_low > UINT16_MAX) { *token = community_token_unknown; return NULL; } *val = community_high + community_low; *token = community_token_val; return p; } *token = community_token_unknown; return NULL; } /* convert string to community structure */ struct community *community_str2com(const char *str) { struct community *com = NULL; struct community *com_sort = NULL; uint32_t val = 0; enum community_token token = community_token_unknown; do { str = community_gettoken(str, &token, &val); switch (token) { case community_token_val: case community_token_gshut: case community_token_accept_own: case community_token_route_filter_translated_v4: case community_token_route_filter_v4: case community_token_route_filter_translated_v6: case community_token_route_filter_v6: case community_token_llgr_stale: case community_token_no_llgr: case community_token_accept_own_nexthop: case community_token_blackhole: case community_token_no_export: case community_token_no_advertise: case community_token_local_as: case community_token_no_peer: if (com == NULL) { com = community_new(); com->json = NULL; } community_add_val(com, val); break; case community_token_unknown: if (com) community_free(&com); return NULL; } } while (str); com_sort = community_uniq_sort(com); community_free(&com); return com_sort; } /* Return communities hash entry count. */ unsigned long community_count(void) { return comhash->count; } /* Return communities hash. */ struct hash *community_hash(void) { return comhash; } /* Initialize comminity related hash. */ void community_init(void) { comhash = hash_create((unsigned int (*)(const void *))community_hash_make, (bool (*)(const void *, const void *))community_cmp, "BGP Community Hash"); } void community_finish(void) { hash_free(comhash); comhash = NULL; } static struct community *bgp_aggr_community_lookup( struct bgp_aggregate *aggregate, struct community *community) { return hash_lookup(aggregate->community_hash, community); } static void *bgp_aggr_communty_hash_alloc(void *p) { struct community *ref = (struct community *)p; struct community *community = NULL; community = community_dup(ref); return community; } static void bgp_aggr_community_prepare(struct hash_backet *hb, void *arg) { struct community *commerge = NULL; struct community *hb_community = hb->data; struct community **aggr_community = arg; if (*aggr_community) { commerge = community_merge(*aggr_community, hb_community); *aggr_community = community_uniq_sort(commerge); community_free(&commerge); } else *aggr_community = community_dup(hb_community); } void bgp_aggr_community_remove(void *arg) { struct community *community = arg; community_free(&community); } void bgp_compute_aggregate_community(struct bgp_aggregate *aggregate, struct community *community) { struct community *aggr_community = NULL; if ((aggregate == NULL) || (community == NULL)) return; /* Create hash if not already created. */ if (aggregate->community_hash == NULL) aggregate->community_hash = hash_create( (unsigned int (*)(const void *))community_hash_make, (bool (*)(const void *, const void *))community_cmp, "BGP Aggregator community hash"); aggr_community = bgp_aggr_community_lookup(aggregate, community); if (aggr_community == NULL) { /* Insert community into hash. */ aggr_community = hash_get(aggregate->community_hash, community, bgp_aggr_communty_hash_alloc); /* Re-compute aggregate's community. */ if (aggregate->community) community_free(&aggregate->community); hash_iterate(aggregate->community_hash, bgp_aggr_community_prepare, &aggregate->community); } /* Increment refernce counter. */ aggr_community->refcnt++; } void bgp_remove_community_from_aggregate(struct bgp_aggregate *aggregate, struct community *community) { struct community *aggr_community = NULL; struct community *ret_comm = NULL; if ((aggregate == NULL) || (community == NULL)) return; if (aggregate->community_hash == NULL) return; /* Look-up the community in the hash. */ aggr_community = bgp_aggr_community_lookup(aggregate, community); if (aggr_community) { aggr_community->refcnt--; if (aggr_community->refcnt == 0) { ret_comm = hash_release(aggregate->community_hash, aggr_community); community_free(&ret_comm); community_free(&aggregate->community); /* Compute aggregate's community. */ hash_iterate(aggregate->community_hash, bgp_aggr_community_prepare, &aggregate->community); } } } frr-7.2.1/bgpd/bgp_community.h0000644000000000000000000001005413610377563013155 00000000000000/* Community attribute related functions. * Copyright (C) 1998 Kunihiro Ishiguro * * This file is part of GNU Zebra. * * GNU Zebra is free software; you can redistribute it and/or modify it * under the terms of the GNU General Public License as published by the * Free Software Foundation; either version 2, or (at your option) any * later version. * * GNU Zebra 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; see the file COPYING; if not, write to the Free Software * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA */ #ifndef _QUAGGA_BGP_COMMUNITY_H #define _QUAGGA_BGP_COMMUNITY_H #include "lib/json.h" #include "bgpd/bgp_route.h" /* Communities attribute. */ struct community { /* Reference count of communities value. */ unsigned long refcnt; /* Communities value size. */ int size; /* Communities value. */ uint32_t *val; /* Communities as a json object */ json_object *json; /* String of community attribute. This sring is used by vty output and expanded community-list for regular expression match. */ char *str; }; /* Well-known communities value. */ #define COMMUNITY_INTERNET 0x0 #define COMMUNITY_GSHUT 0xFFFF0000 #define COMMUNITY_ACCEPT_OWN 0xFFFF0001 #define COMMUNITY_ROUTE_FILTER_TRANSLATED_v4 0xFFFF0002 #define COMMUNITY_ROUTE_FILTER_v4 0xFFFF0003 #define COMMUNITY_ROUTE_FILTER_TRANSLATED_v6 0xFFFF0004 #define COMMUNITY_ROUTE_FILTER_v6 0xFFFF0005 #define COMMUNITY_LLGR_STALE 0xFFFF0006 #define COMMUNITY_NO_LLGR 0xFFFF0007 #define COMMUNITY_ACCEPT_OWN_NEXTHOP 0xFFFF0008 #define COMMUNITY_BLACKHOLE 0xFFFF029A #define COMMUNITY_NO_EXPORT 0xFFFFFF01 #define COMMUNITY_NO_ADVERTISE 0xFFFFFF02 #define COMMUNITY_NO_EXPORT_SUBCONFED 0xFFFFFF03 #define COMMUNITY_LOCAL_AS 0xFFFFFF03 #define COMMUNITY_NO_PEER 0xFFFFFF04 /* Macros of community attribute. */ #define com_length(X) ((X)->size * 4) #define com_lastval(X) ((X)->val + (X)->size - 1) #define com_nthval(X,n) ((X)->val + (n)) /* Prototypes of communities attribute functions. */ extern void community_init(void); extern void community_finish(void); extern void community_free(struct community **comm); extern struct community *community_uniq_sort(struct community *); extern struct community *community_parse(uint32_t *, unsigned short); extern struct community *community_intern(struct community *); extern void community_unintern(struct community **); extern char *community_str(struct community *, bool make_json); extern unsigned int community_hash_make(const struct community *); extern struct community *community_str2com(const char *); extern int community_match(const struct community *, const struct community *); extern bool community_cmp(const struct community *c1, const struct community *c2); extern struct community *community_merge(struct community *, struct community *); extern struct community *community_delete(struct community *, struct community *); extern struct community *community_dup(struct community *); extern int community_include(struct community *, uint32_t); extern void community_del_val(struct community *, uint32_t *); extern unsigned long community_count(void); extern struct hash *community_hash(void); extern uint32_t community_val_get(struct community *com, int i); extern void bgp_compute_aggregate_community(struct bgp_aggregate *aggregate, struct community *community); extern void bgp_remove_community_from_aggregate(struct bgp_aggregate *aggregate, struct community *community); extern void bgp_aggr_community_remove(void *arg); #endif /* _QUAGGA_BGP_COMMUNITY_H */ frr-7.2.1/bgpd/bgp_damp.c0000644000000000000000000004462213610377563012055 00000000000000/* BGP flap dampening * Copyright (C) 2001 IP Infusion Inc. * * This file is part of GNU Zebra. * * GNU Zebra is free software; you can redistribute it and/or modify it * under the terms of the GNU General Public License as published by the * Free Software Foundation; either version 2, or (at your option) any * later version. * * GNU Zebra 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; see the file COPYING; if not, write to the Free Software * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA */ #include #include #include "prefix.h" #include "memory.h" #include "command.h" #include "log.h" #include "thread.h" #include "queue.h" #include "filter.h" #include "bgpd/bgpd.h" #include "bgpd/bgp_damp.h" #include "bgpd/bgp_table.h" #include "bgpd/bgp_route.h" #include "bgpd/bgp_attr.h" #include "bgpd/bgp_advertise.h" /* Global variable to access damping configuration */ static struct bgp_damp_config damp[AFI_MAX][SAFI_MAX]; /* Utility macro to add and delete BGP dampening information to no used list. */ #define BGP_DAMP_LIST_ADD(N, A) BGP_PATH_INFO_ADD(N, A, no_reuse_list) #define BGP_DAMP_LIST_DEL(N, A) BGP_PATH_INFO_DEL(N, A, no_reuse_list) /* Calculate reuse list index by penalty value. */ static int bgp_reuse_index(int penalty, struct bgp_damp_config *bdc) { unsigned int i; int index; i = (int)(((double)penalty / bdc->reuse_limit - 1.0) * bdc->scale_factor); if (i >= bdc->reuse_index_size) i = bdc->reuse_index_size - 1; index = bdc->reuse_index[i] - bdc->reuse_index[0]; return (bdc->reuse_offset + index) % bdc->reuse_list_size; } /* Add BGP dampening information to reuse list. */ static void bgp_reuse_list_add(struct bgp_damp_info *bdi, struct bgp_damp_config *bdc) { int index; index = bdi->index = bgp_reuse_index(bdi->penalty, bdc); bdi->prev = NULL; bdi->next = bdc->reuse_list[index]; if (bdc->reuse_list[index]) bdc->reuse_list[index]->prev = bdi; bdc->reuse_list[index] = bdi; } /* Delete BGP dampening information from reuse list. */ static void bgp_reuse_list_delete(struct bgp_damp_info *bdi, struct bgp_damp_config *bdc) { if (bdi->next) bdi->next->prev = bdi->prev; if (bdi->prev) bdi->prev->next = bdi->next; else bdc->reuse_list[bdi->index] = bdi->next; } /* Return decayed penalty value. */ int bgp_damp_decay(time_t tdiff, int penalty, struct bgp_damp_config *bdc) { unsigned int i; i = (int)((double)tdiff / DELTA_T); if (i == 0) return penalty; if (i >= bdc->decay_array_size) return 0; return (int)(penalty * bdc->decay_array[i]); } /* Handler of reuse timer event. Each route in the current reuse-list is evaluated. RFC2439 Section 4.8.7. */ static int bgp_reuse_timer(struct thread *t) { struct bgp_damp_info *bdi; struct bgp_damp_info *next; time_t t_now, t_diff; struct bgp_damp_config *bdc = THREAD_ARG(t); bdc->t_reuse = NULL; thread_add_timer(bm->master, bgp_reuse_timer, bdc, DELTA_REUSE, &bdc->t_reuse); t_now = bgp_clock(); /* 1. save a pointer to the current zeroth queue head and zero the list head entry. */ bdi = bdc->reuse_list[bdc->reuse_offset]; bdc->reuse_list[bdc->reuse_offset] = NULL; /* 2. set offset = modulo reuse-list-size ( offset + 1 ), thereby rotating the circular queue of list-heads. */ bdc->reuse_offset = (bdc->reuse_offset + 1) % bdc->reuse_list_size; /* 3. if ( the saved list head pointer is non-empty ) */ for (; bdi; bdi = next) { struct bgp *bgp = bdi->path->peer->bgp; next = bdi->next; /* Set t-diff = t-now - t-updated. */ t_diff = t_now - bdi->t_updated; /* Set figure-of-merit = figure-of-merit * decay-array-ok * [t-diff] */ bdi->penalty = bgp_damp_decay(t_diff, bdi->penalty, bdc); /* Set t-updated = t-now. */ bdi->t_updated = t_now; /* if (figure-of-merit < reuse). */ if (bdi->penalty < bdc->reuse_limit) { /* Reuse the route. */ bgp_path_info_unset_flag(bdi->rn, bdi->path, BGP_PATH_DAMPED); bdi->suppress_time = 0; if (bdi->lastrecord == BGP_RECORD_UPDATE) { bgp_path_info_unset_flag(bdi->rn, bdi->path, BGP_PATH_HISTORY); bgp_aggregate_increment(bgp, &bdi->rn->p, bdi->path, bdi->afi, bdi->safi); bgp_process(bgp, bdi->rn, bdi->afi, bdi->safi); } if (bdi->penalty <= bdc->reuse_limit / 2.0) bgp_damp_info_free(bdi, 1, bdc->afi, bdc->safi); else BGP_DAMP_LIST_ADD(bdc, bdi); } else /* Re-insert into another list (See RFC2439 Section * 4.8.6). */ bgp_reuse_list_add(bdi, bdc); } return 0; } /* A route becomes unreachable (RFC2439 Section 4.8.2). */ int bgp_damp_withdraw(struct bgp_path_info *path, struct bgp_node *rn, afi_t afi, safi_t safi, int attr_change) { time_t t_now; struct bgp_damp_info *bdi = NULL; unsigned int last_penalty = 0; struct bgp_damp_config *bdc = &damp[afi][safi]; t_now = bgp_clock(); /* Processing Unreachable Messages. */ if (path->extra) bdi = path->extra->damp_info; if (bdi == NULL) { /* If there is no previous stability history. */ /* RFC2439 said: 1. allocate a damping structure. 2. set figure-of-merit = 1. 3. withdraw the route. */ bdi = XCALLOC(MTYPE_BGP_DAMP_INFO, sizeof(struct bgp_damp_info)); bdi->path = path; bdi->rn = rn; bdi->penalty = (attr_change ? DEFAULT_PENALTY / 2 : DEFAULT_PENALTY); bdi->flap = 1; bdi->start_time = t_now; bdi->suppress_time = 0; bdi->index = -1; bdi->afi = afi; bdi->safi = safi; (bgp_path_info_extra_get(path))->damp_info = bdi; BGP_DAMP_LIST_ADD(bdc, bdi); } else { last_penalty = bdi->penalty; /* 1. Set t-diff = t-now - t-updated. */ bdi->penalty = (bgp_damp_decay(t_now - bdi->t_updated, bdi->penalty, bdc) + (attr_change ? DEFAULT_PENALTY / 2 : DEFAULT_PENALTY)); if (bdi->penalty > bdc->ceiling) bdi->penalty = bdc->ceiling; bdi->flap++; } assert((rn == bdi->rn) && (path == bdi->path)); bdi->lastrecord = BGP_RECORD_WITHDRAW; bdi->t_updated = t_now; /* Make this route as historical status. */ bgp_path_info_set_flag(rn, path, BGP_PATH_HISTORY); /* Remove the route from a reuse list if it is on one. */ if (CHECK_FLAG(bdi->path->flags, BGP_PATH_DAMPED)) { /* If decay rate isn't equal to 0, reinsert brn. */ if (bdi->penalty != last_penalty && bdi->index >= 0) { bgp_reuse_list_delete(bdi, bdc); bgp_reuse_list_add(bdi, bdc); } return BGP_DAMP_SUPPRESSED; } /* If not suppressed before, do annonunce this withdraw and insert into reuse_list. */ if (bdi->penalty >= bdc->suppress_value) { bgp_path_info_set_flag(rn, path, BGP_PATH_DAMPED); bdi->suppress_time = t_now; BGP_DAMP_LIST_DEL(bdc, bdi); bgp_reuse_list_add(bdi, bdc); } return BGP_DAMP_USED; } int bgp_damp_update(struct bgp_path_info *path, struct bgp_node *rn, afi_t afi, safi_t safi) { time_t t_now; struct bgp_damp_info *bdi; int status; struct bgp_damp_config *bdc = &damp[afi][safi]; if (!path->extra || !((bdi = path->extra->damp_info))) return BGP_DAMP_USED; t_now = bgp_clock(); bgp_path_info_unset_flag(rn, path, BGP_PATH_HISTORY); bdi->lastrecord = BGP_RECORD_UPDATE; bdi->penalty = bgp_damp_decay(t_now - bdi->t_updated, bdi->penalty, bdc); if (!CHECK_FLAG(bdi->path->flags, BGP_PATH_DAMPED) && (bdi->penalty < bdc->suppress_value)) status = BGP_DAMP_USED; else if (CHECK_FLAG(bdi->path->flags, BGP_PATH_DAMPED) && (bdi->penalty < bdc->reuse_limit)) { bgp_path_info_unset_flag(rn, path, BGP_PATH_DAMPED); bgp_reuse_list_delete(bdi, bdc); BGP_DAMP_LIST_ADD(bdc, bdi); bdi->suppress_time = 0; status = BGP_DAMP_USED; } else status = BGP_DAMP_SUPPRESSED; if (bdi->penalty > bdc->reuse_limit / 2.0) bdi->t_updated = t_now; else bgp_damp_info_free(bdi, 0, afi, safi); return status; } void bgp_damp_info_free(struct bgp_damp_info *bdi, int withdraw, afi_t afi, safi_t safi) { struct bgp_path_info *path; struct bgp_damp_config *bdc = &damp[afi][safi]; if (!bdi) return; path = bdi->path; path->extra->damp_info = NULL; if (CHECK_FLAG(path->flags, BGP_PATH_DAMPED)) bgp_reuse_list_delete(bdi, bdc); else BGP_DAMP_LIST_DEL(bdc, bdi); bgp_path_info_unset_flag(bdi->rn, path, BGP_PATH_HISTORY | BGP_PATH_DAMPED); if (bdi->lastrecord == BGP_RECORD_WITHDRAW && withdraw) bgp_path_info_delete(bdi->rn, path); XFREE(MTYPE_BGP_DAMP_INFO, bdi); } static void bgp_damp_parameter_set(int hlife, int reuse, int sup, int maxsup, struct bgp_damp_config *bdc) { double reuse_max_ratio; unsigned int i; double j; bdc->suppress_value = sup; bdc->half_life = hlife; bdc->reuse_limit = reuse; bdc->max_suppress_time = maxsup; /* Initialize params per bgp_damp_config. */ bdc->reuse_index_size = REUSE_ARRAY_SIZE; bdc->ceiling = (int)(bdc->reuse_limit * (pow(2, (double)bdc->max_suppress_time / bdc->half_life))); /* Decay-array computations */ bdc->decay_array_size = ceil((double)bdc->max_suppress_time / DELTA_T); bdc->decay_array = XMALLOC(MTYPE_BGP_DAMP_ARRAY, sizeof(double) * (bdc->decay_array_size)); bdc->decay_array[0] = 1.0; bdc->decay_array[1] = exp((1.0 / ((double)bdc->half_life / DELTA_T)) * log(0.5)); /* Calculate decay values for all possible times */ for (i = 2; i < bdc->decay_array_size; i++) bdc->decay_array[i] = bdc->decay_array[i - 1] * bdc->decay_array[1]; /* Reuse-list computations */ i = ceil((double)bdc->max_suppress_time / DELTA_REUSE) + 1; if (i > REUSE_LIST_SIZE || i == 0) i = REUSE_LIST_SIZE; bdc->reuse_list_size = i; bdc->reuse_list = XCALLOC(MTYPE_BGP_DAMP_ARRAY, bdc->reuse_list_size * sizeof(struct bgp_reuse_node *)); /* Reuse-array computations */ bdc->reuse_index = XCALLOC(MTYPE_BGP_DAMP_ARRAY, sizeof(int) * bdc->reuse_index_size); reuse_max_ratio = (double)bdc->ceiling / bdc->reuse_limit; j = (exp((double)bdc->max_suppress_time / bdc->half_life) * log10(2.0)); if (reuse_max_ratio > j && j != 0) reuse_max_ratio = j; bdc->scale_factor = (double)bdc->reuse_index_size / (reuse_max_ratio - 1); for (i = 0; i < bdc->reuse_index_size; i++) { bdc->reuse_index[i] = (int)(((double)bdc->half_life / DELTA_REUSE) * log10(1.0 / (bdc->reuse_limit * (1.0 + ((double)i / bdc->scale_factor)))) / log10(0.5)); } } int bgp_damp_enable(struct bgp *bgp, afi_t afi, safi_t safi, time_t half, unsigned int reuse, unsigned int suppress, time_t max) { struct bgp_damp_config *bdc = &damp[afi][safi]; if (CHECK_FLAG(bgp->af_flags[afi][safi], BGP_CONFIG_DAMPENING)) { if (bdc->half_life == half && bdc->reuse_limit == reuse && bdc->suppress_value == suppress && bdc->max_suppress_time == max) return 0; bgp_damp_disable(bgp, afi, safi); } SET_FLAG(bgp->af_flags[afi][safi], BGP_CONFIG_DAMPENING); bgp_damp_parameter_set(half, reuse, suppress, max, bdc); /* Register reuse timer. */ thread_add_timer(bm->master, bgp_reuse_timer, bdc, DELTA_REUSE, &bdc->t_reuse); return 0; } static void bgp_damp_config_clean(struct bgp_damp_config *bdc) { /* Free decay array */ XFREE(MTYPE_BGP_DAMP_ARRAY, bdc->decay_array); bdc->decay_array_size = 0; /* Free reuse index array */ XFREE(MTYPE_BGP_DAMP_ARRAY, bdc->reuse_index); bdc->reuse_index_size = 0; /* Free reuse list array. */ XFREE(MTYPE_BGP_DAMP_ARRAY, bdc->reuse_list); bdc->reuse_list_size = 0; } /* Clean all the bgp_damp_info stored in reuse_list. */ void bgp_damp_info_clean(afi_t afi, safi_t safi) { unsigned int i; struct bgp_damp_info *bdi, *next; struct bgp_damp_config *bdc = &damp[afi][safi]; bdc->reuse_offset = 0; for (i = 0; i < bdc->reuse_list_size; i++) { if (!bdc->reuse_list[i]) continue; for (bdi = bdc->reuse_list[i]; bdi; bdi = next) { next = bdi->next; bgp_damp_info_free(bdi, 1, afi, safi); } bdc->reuse_list[i] = NULL; } for (bdi = bdc->no_reuse_list; bdi; bdi = next) { next = bdi->next; bgp_damp_info_free(bdi, 1, afi, safi); } bdc->no_reuse_list = NULL; } int bgp_damp_disable(struct bgp *bgp, afi_t afi, safi_t safi) { struct bgp_damp_config *bdc = &damp[afi][safi]; /* If it wasn't enabled, there's nothing to do. */ if (!CHECK_FLAG(bgp->af_flags[afi][safi], BGP_CONFIG_DAMPENING)) return 0; /* Cancel reuse thread. */ if (bdc->t_reuse) thread_cancel(bdc->t_reuse); bdc->t_reuse = NULL; /* Clean BGP dampening information. */ bgp_damp_info_clean(afi, safi); /* Clear configuration */ bgp_damp_config_clean(bdc); UNSET_FLAG(bgp->af_flags[afi][safi], BGP_CONFIG_DAMPENING); return 0; } void bgp_config_write_damp(struct vty *vty, afi_t afi, safi_t safi) { if (damp[afi][safi].half_life == DEFAULT_HALF_LIFE * 60 && damp[afi][safi].reuse_limit == DEFAULT_REUSE && damp[afi][safi].suppress_value == DEFAULT_SUPPRESS && damp[afi][safi].max_suppress_time == damp[afi][safi].half_life * 4) vty_out(vty, " bgp dampening\n"); else if (damp[afi][safi].half_life != DEFAULT_HALF_LIFE * 60 && damp[afi][safi].reuse_limit == DEFAULT_REUSE && damp[afi][safi].suppress_value == DEFAULT_SUPPRESS && damp[afi][safi].max_suppress_time == damp[afi][safi].half_life * 4) vty_out(vty, " bgp dampening %lld\n", damp[afi][safi].half_life / 60LL); else vty_out(vty, " bgp dampening %lld %d %d %lld\n", damp[afi][safi].half_life / 60LL, damp[afi][safi].reuse_limit, damp[afi][safi].suppress_value, damp[afi][safi].max_suppress_time / 60LL); } static const char *bgp_get_reuse_time(unsigned int penalty, char *buf, size_t len, afi_t afi, safi_t safi, bool use_json, json_object *json) { time_t reuse_time = 0; struct tm *tm = NULL; int time_store = 0; if (penalty > damp[afi][safi].reuse_limit) { reuse_time = (int)(DELTA_T * ((log((double)damp[afi][safi].reuse_limit / penalty)) / (log(damp[afi][safi].decay_array[1])))); if (reuse_time > damp[afi][safi].max_suppress_time) reuse_time = damp[afi][safi].max_suppress_time; tm = gmtime(&reuse_time); } else reuse_time = 0; /* Making formatted timer strings. */ if (reuse_time == 0) { if (use_json) json_object_int_add(json, "reuseTimerMsecs", 0); else snprintf(buf, len, "00:00:00"); } else if (reuse_time < ONE_DAY_SECOND) { if (use_json) { time_store = (3600000 * tm->tm_hour) + (60000 * tm->tm_min) + (1000 * tm->tm_sec); json_object_int_add(json, "reuseTimerMsecs", time_store); } else snprintf(buf, len, "%02d:%02d:%02d", tm->tm_hour, tm->tm_min, tm->tm_sec); } else if (reuse_time < ONE_WEEK_SECOND) { if (use_json) { time_store = (86400000 * tm->tm_yday) + (3600000 * tm->tm_hour) + (60000 * tm->tm_min) + (1000 * tm->tm_sec); json_object_int_add(json, "reuseTimerMsecs", time_store); } else snprintf(buf, len, "%dd%02dh%02dm", tm->tm_yday, tm->tm_hour, tm->tm_min); } else { if (use_json) { time_store = (604800000 * tm->tm_yday / 7) + (86400000 * (tm->tm_yday - ((tm->tm_yday / 7) * 7))) + (3600000 * tm->tm_hour) + (60000 * tm->tm_min) + (1000 * tm->tm_sec); json_object_int_add(json, "reuseTimerMsecs", time_store); } else snprintf(buf, len, "%02dw%dd%02dh", tm->tm_yday / 7, tm->tm_yday - ((tm->tm_yday / 7) * 7), tm->tm_hour); } return buf; } void bgp_damp_info_vty(struct vty *vty, struct bgp_path_info *path, afi_t afi, safi_t safi, json_object *json_path) { struct bgp_damp_info *bdi; time_t t_now, t_diff; char timebuf[BGP_UPTIME_LEN]; int penalty; struct bgp_damp_config *bdc = &damp[afi][safi]; if (!path->extra) return; /* BGP dampening information. */ bdi = path->extra->damp_info; /* If dampening is not enabled or there is no dampening information, return immediately. */ if (!bdc || !bdi) return; /* Calculate new penalty. */ t_now = bgp_clock(); t_diff = t_now - bdi->t_updated; penalty = bgp_damp_decay(t_diff, bdi->penalty, bdc); if (json_path) { json_object_int_add(json_path, "dampeningPenalty", penalty); json_object_int_add(json_path, "dampeningFlapCount", bdi->flap); peer_uptime(bdi->start_time, timebuf, BGP_UPTIME_LEN, 1, json_path); if (CHECK_FLAG(path->flags, BGP_PATH_DAMPED) && !CHECK_FLAG(path->flags, BGP_PATH_HISTORY)) bgp_get_reuse_time(penalty, timebuf, BGP_UPTIME_LEN, afi, safi, 1, json_path); } else { vty_out(vty, " Dampinfo: penalty %d, flapped %d times in %s", penalty, bdi->flap, peer_uptime(bdi->start_time, timebuf, BGP_UPTIME_LEN, 0, json_path)); if (CHECK_FLAG(path->flags, BGP_PATH_DAMPED) && !CHECK_FLAG(path->flags, BGP_PATH_HISTORY)) vty_out(vty, ", reuse in %s", bgp_get_reuse_time(penalty, timebuf, BGP_UPTIME_LEN, afi, safi, 0, json_path)); vty_out(vty, "\n"); } } const char *bgp_damp_reuse_time_vty(struct vty *vty, struct bgp_path_info *path, char *timebuf, size_t len, afi_t afi, safi_t safi, bool use_json, json_object *json) { struct bgp_damp_info *bdi; time_t t_now, t_diff; int penalty; struct bgp_damp_config *bdc = &damp[afi][safi]; if (!path->extra) return NULL; /* BGP dampening information. */ bdi = path->extra->damp_info; /* If dampening is not enabled or there is no dampening information, return immediately. */ if (!bdc || !bdi) return NULL; /* Calculate new penalty. */ t_now = bgp_clock(); t_diff = t_now - bdi->t_updated; penalty = bgp_damp_decay(t_diff, bdi->penalty, bdc); return bgp_get_reuse_time(penalty, timebuf, len, afi, safi, use_json, json); } int bgp_show_dampening_parameters(struct vty *vty, afi_t afi, safi_t safi) { struct bgp *bgp; bgp = bgp_get_default(); if (bgp == NULL) { vty_out(vty, "No BGP process is configured\n"); return CMD_WARNING; } if (CHECK_FLAG(bgp->af_flags[afi][safi], BGP_CONFIG_DAMPENING)) { vty_out(vty, "Half-life time: %lld min\n", (long long)damp[afi][safi].half_life / 60); vty_out(vty, "Reuse penalty: %d\n", damp[afi][safi].reuse_limit); vty_out(vty, "Suppress penalty: %d\n", damp[afi][safi].suppress_value); vty_out(vty, "Max suppress time: %lld min\n", (long long)damp[afi][safi].max_suppress_time / 60); vty_out(vty, "Max suppress penalty: %u\n", damp[afi][safi].ceiling); vty_out(vty, "\n"); } else vty_out(vty, "dampening not enabled for %s\n", afi == AFI_IP ? "IPv4" : "IPv6"); return CMD_SUCCESS; } frr-7.2.1/bgpd/bgp_damp.h0000644000000000000000000001126013610377563012052 00000000000000/* BGP flap dampening * Copyright (C) 2001 IP Infusion Inc. * * This file is part of GNU Zebra. * * GNU Zebra is free software; you can redistribute it and/or modify it * under the terms of the GNU General Public License as published by the * Free Software Foundation; either version 2, or (at your option) any * later version. * * GNU Zebra 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; see the file COPYING; if not, write to the Free Software * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA */ #ifndef _QUAGGA_BGP_DAMP_H #define _QUAGGA_BGP_DAMP_H /* Structure maintained on a per-route basis. */ struct bgp_damp_info { /* Doubly linked list. This information must be linked to reuse_list or no_reuse_list. */ struct bgp_damp_info *next; struct bgp_damp_info *prev; /* Figure-of-merit. */ unsigned int penalty; /* Number of flapping. */ unsigned int flap; /* First flap time */ time_t start_time; /* Last time penalty was updated. */ time_t t_updated; /* Time of route start to be suppressed. */ time_t suppress_time; /* Back reference to bgp_path_info. */ struct bgp_path_info *path; /* Back reference to bgp_node. */ struct bgp_node *rn; /* Current index in the reuse_list. */ int index; /* Last time message type. */ uint8_t lastrecord; #define BGP_RECORD_UPDATE 1U #define BGP_RECORD_WITHDRAW 2U afi_t afi; safi_t safi; }; /* Specified parameter set configuration. */ struct bgp_damp_config { /* Value over which routes suppressed. */ unsigned int suppress_value; /* Value below which suppressed routes reused. */ unsigned int reuse_limit; /* Max time a route can be suppressed. */ time_t max_suppress_time; /* Time during which accumulated penalty reduces by half. */ time_t half_life; /* Non-configurable parameters but fixed at implementation time. * To change this values, init_bgp_damp() should be modified. */ time_t tmax; /* Max time previous instability retained */ unsigned int reuse_list_size; /* Number of reuse lists */ unsigned int reuse_index_size; /* Size of reuse index array */ /* Non-configurable parameters. Most of these are calculated from * the configurable parameters above. */ unsigned int ceiling; /* Max value a penalty can attain */ unsigned int decay_rate_per_tick; /* Calculated from half-life */ unsigned int decay_array_size; /* Calculated using config parameters */ double scale_factor; unsigned int reuse_scale_factor; /* Decay array per-set based. */ double *decay_array; /* Reuse index array per-set based. */ int *reuse_index; /* Reuse list array per-set based. */ struct bgp_damp_info **reuse_list; int reuse_offset; /* All dampening information which is not on reuse list. */ struct bgp_damp_info *no_reuse_list; /* Reuse timer thread per-set base. */ struct thread *t_reuse; afi_t afi; safi_t safi; }; #define BGP_DAMP_NONE 0 #define BGP_DAMP_USED 1 #define BGP_DAMP_SUPPRESSED 2 /* Time granularity for reuse lists */ #define DELTA_REUSE 10 /* Time granularity for decay arrays */ #define DELTA_T 5 #define DEFAULT_PENALTY 1000 #define DEFAULT_HALF_LIFE 15 #define DEFAULT_REUSE 750 #define DEFAULT_SUPPRESS 2000 #define REUSE_LIST_SIZE 256 #define REUSE_ARRAY_SIZE 1024 extern int bgp_damp_enable(struct bgp *, afi_t, safi_t, time_t, unsigned int, unsigned int, time_t); extern int bgp_damp_disable(struct bgp *, afi_t, safi_t); extern int bgp_damp_withdraw(struct bgp_path_info *path, struct bgp_node *rn, afi_t afi, safi_t safi, int attr_change); extern int bgp_damp_update(struct bgp_path_info *path, struct bgp_node *rn, afi_t afi, safi_t saff); extern void bgp_damp_info_free(struct bgp_damp_info *path, int withdraw, afi_t afi, safi_t safi); extern void bgp_damp_info_clean(afi_t afi, safi_t safi); extern int bgp_damp_decay(time_t, int, struct bgp_damp_config *damp); extern void bgp_config_write_damp(struct vty *, afi_t afi, safi_t safi); extern void bgp_damp_info_vty(struct vty *vty, struct bgp_path_info *path, afi_t afi, safi_t safi, json_object *json_path); extern const char *bgp_damp_reuse_time_vty(struct vty *vty, struct bgp_path_info *path, char *timebuf, size_t len, afi_t afi, safi_t safi, bool use_json, json_object *json); extern int bgp_show_dampening_parameters(struct vty *vty, afi_t, safi_t); #endif /* _QUAGGA_BGP_DAMP_H */ frr-7.2.1/bgpd/bgp_debug.c0000644000000000000000000020736013610377563012222 00000000000000/* BGP-4, BGP-4+ packet debug routine * Copyright (C) 1996, 97, 99 Kunihiro Ishiguro * * This file is part of GNU Zebra. * * GNU Zebra is free software; you can redistribute it and/or modify it * under the terms of the GNU General Public License as published by the * Free Software Foundation; either version 2, or (at your option) any * later version. * * GNU Zebra 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; see the file COPYING; if not, write to the Free Software * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA */ #include #include #include "prefix.h" #include "linklist.h" #include "stream.h" #include "command.h" #include "log.h" #include "sockunion.h" #include "memory.h" #include "queue.h" #include "filter.h" #include "bgpd/bgpd.h" #include "bgpd/bgp_aspath.h" #include "bgpd/bgp_route.h" #include "bgpd/bgp_attr.h" #include "bgpd/bgp_debug.h" #include "bgpd/bgp_community.h" #include "bgpd/bgp_updgrp.h" #include "bgpd/bgp_mplsvpn.h" #include "bgpd/bgp_ecommunity.h" #include "bgpd/bgp_label.h" #include "bgpd/bgp_evpn.h" #include "bgpd/bgp_evpn_private.h" #include "bgpd/bgp_evpn_vty.h" #include "bgpd/bgp_vty.h" #include "bgpd/bgp_flowspec.h" unsigned long conf_bgp_debug_as4; unsigned long conf_bgp_debug_neighbor_events; unsigned long conf_bgp_debug_events; unsigned long conf_bgp_debug_packet; unsigned long conf_bgp_debug_filter; unsigned long conf_bgp_debug_keepalive; unsigned long conf_bgp_debug_update; unsigned long conf_bgp_debug_bestpath; unsigned long conf_bgp_debug_zebra; unsigned long conf_bgp_debug_allow_martians; unsigned long conf_bgp_debug_nht; unsigned long conf_bgp_debug_update_groups; unsigned long conf_bgp_debug_vpn; unsigned long conf_bgp_debug_flowspec; unsigned long conf_bgp_debug_labelpool; unsigned long conf_bgp_debug_pbr; unsigned long term_bgp_debug_as4; unsigned long term_bgp_debug_neighbor_events; unsigned long term_bgp_debug_events; unsigned long term_bgp_debug_packet; unsigned long term_bgp_debug_filter; unsigned long term_bgp_debug_keepalive; unsigned long term_bgp_debug_update; unsigned long term_bgp_debug_bestpath; unsigned long term_bgp_debug_zebra; unsigned long term_bgp_debug_allow_martians; unsigned long term_bgp_debug_nht; unsigned long term_bgp_debug_update_groups; unsigned long term_bgp_debug_vpn; unsigned long term_bgp_debug_flowspec; unsigned long term_bgp_debug_labelpool; unsigned long term_bgp_debug_pbr; struct list *bgp_debug_neighbor_events_peers = NULL; struct list *bgp_debug_keepalive_peers = NULL; struct list *bgp_debug_update_out_peers = NULL; struct list *bgp_debug_update_in_peers = NULL; struct list *bgp_debug_update_prefixes = NULL; struct list *bgp_debug_bestpath_prefixes = NULL; struct list *bgp_debug_zebra_prefixes = NULL; /* messages for BGP-4 status */ const struct message bgp_status_msg[] = {{Idle, "Idle"}, {Connect, "Connect"}, {Active, "Active"}, {OpenSent, "OpenSent"}, {OpenConfirm, "OpenConfirm"}, {Established, "Established"}, {Clearing, "Clearing"}, {Deleted, "Deleted"}, {0}}; /* BGP message type string. */ const char *bgp_type_str[] = {NULL, "OPEN", "UPDATE", "NOTIFICATION", "KEEPALIVE", "ROUTE-REFRESH", "CAPABILITY"}; /* message for BGP-4 Notify */ static const struct message bgp_notify_msg[] = { {BGP_NOTIFY_HEADER_ERR, "Message Header Error"}, {BGP_NOTIFY_OPEN_ERR, "OPEN Message Error"}, {BGP_NOTIFY_UPDATE_ERR, "UPDATE Message Error"}, {BGP_NOTIFY_HOLD_ERR, "Hold Timer Expired"}, {BGP_NOTIFY_FSM_ERR, "Neighbor Events Error"}, {BGP_NOTIFY_CEASE, "Cease"}, {BGP_NOTIFY_CAPABILITY_ERR, "CAPABILITY Message Error"}, {0}}; static const struct message bgp_notify_head_msg[] = { {BGP_NOTIFY_HEADER_NOT_SYNC, "/Connection Not Synchronized"}, {BGP_NOTIFY_HEADER_BAD_MESLEN, "/Bad Message Length"}, {BGP_NOTIFY_HEADER_BAD_MESTYPE, "/Bad Message Type"}, {0}}; static const struct message bgp_notify_open_msg[] = { {BGP_NOTIFY_SUBCODE_UNSPECIFIC, "/Unspecific"}, {BGP_NOTIFY_OPEN_UNSUP_VERSION, "/Unsupported Version Number"}, {BGP_NOTIFY_OPEN_BAD_PEER_AS, "/Bad Peer AS"}, {BGP_NOTIFY_OPEN_BAD_BGP_IDENT, "/Bad BGP Identifier"}, {BGP_NOTIFY_OPEN_UNSUP_PARAM, "/Unsupported Optional Parameter"}, {BGP_NOTIFY_OPEN_AUTH_FAILURE, "/Authentication Failure"}, {BGP_NOTIFY_OPEN_UNACEP_HOLDTIME, "/Unacceptable Hold Time"}, {BGP_NOTIFY_OPEN_UNSUP_CAPBL, "/Unsupported Capability"}, {0}}; static const struct message bgp_notify_update_msg[] = { {BGP_NOTIFY_SUBCODE_UNSPECIFIC, "/Unspecific"}, {BGP_NOTIFY_UPDATE_MAL_ATTR, "/Malformed Attribute List"}, {BGP_NOTIFY_UPDATE_UNREC_ATTR, "/Unrecognized Well-known Attribute"}, {BGP_NOTIFY_UPDATE_MISS_ATTR, "/Missing Well-known Attribute"}, {BGP_NOTIFY_UPDATE_ATTR_FLAG_ERR, "/Attribute Flags Error"}, {BGP_NOTIFY_UPDATE_ATTR_LENG_ERR, "/Attribute Length Error"}, {BGP_NOTIFY_UPDATE_INVAL_ORIGIN, "/Invalid ORIGIN Attribute"}, {BGP_NOTIFY_UPDATE_AS_ROUTE_LOOP, "/AS Routing Loop"}, {BGP_NOTIFY_UPDATE_INVAL_NEXT_HOP, "/Invalid NEXT_HOP Attribute"}, {BGP_NOTIFY_UPDATE_OPT_ATTR_ERR, "/Optional Attribute Error"}, {BGP_NOTIFY_UPDATE_INVAL_NETWORK, "/Invalid Network Field"}, {BGP_NOTIFY_UPDATE_MAL_AS_PATH, "/Malformed AS_PATH"}, {0}}; static const struct message bgp_notify_cease_msg[] = { {BGP_NOTIFY_SUBCODE_UNSPECIFIC, "/Unspecific"}, {BGP_NOTIFY_CEASE_MAX_PREFIX, "/Maximum Number of Prefixes Reached"}, {BGP_NOTIFY_CEASE_ADMIN_SHUTDOWN, "/Administratively Shutdown"}, {BGP_NOTIFY_CEASE_PEER_UNCONFIG, "/Peer Unconfigured"}, {BGP_NOTIFY_CEASE_ADMIN_RESET, "/Administratively Reset"}, {BGP_NOTIFY_CEASE_CONNECT_REJECT, "/Connection Rejected"}, {BGP_NOTIFY_CEASE_CONFIG_CHANGE, "/Other Configuration Change"}, {BGP_NOTIFY_CEASE_COLLISION_RESOLUTION, "/Connection collision resolution"}, {BGP_NOTIFY_CEASE_OUT_OF_RESOURCE, "/Out of Resource"}, {0}}; static const struct message bgp_notify_capability_msg[] = { {BGP_NOTIFY_SUBCODE_UNSPECIFIC, "/Unspecific"}, {BGP_NOTIFY_CAPABILITY_INVALID_ACTION, "/Invalid Action Value"}, {BGP_NOTIFY_CAPABILITY_INVALID_LENGTH, "/Invalid Capability Length"}, {BGP_NOTIFY_CAPABILITY_MALFORMED_CODE, "/Malformed Capability Value"}, {0}}; /* Origin strings. */ const char *bgp_origin_str[] = {"i", "e", "?"}; const char *bgp_origin_long_str[] = {"IGP", "EGP", "incomplete"}; static int bgp_debug_print_evpn_prefix(struct vty *vty, const char *desc, struct prefix *p); /* Given a string return a pointer the corresponding peer structure */ static struct peer *bgp_find_peer(struct vty *vty, const char *peer_str) { struct bgp *bgp = VTY_GET_CONTEXT(bgp); int ret; union sockunion su; struct peer *peer; if (!bgp) { return NULL; } ret = str2sockunion(peer_str, &su); /* 'swpX' string */ if (ret < 0) { peer = peer_lookup_by_conf_if(bgp, peer_str); if (!peer) peer = peer_lookup_by_hostname(bgp, peer_str); return peer; } else return peer_lookup(bgp, &su); } static void bgp_debug_list_free(struct list *list) { struct bgp_debug_filter *filter; struct listnode *node, *nnode; if (list) for (ALL_LIST_ELEMENTS(list, node, nnode, filter)) { listnode_delete(list, filter); if (filter->p) prefix_free(filter->p); if (filter->host) XFREE(MTYPE_BGP_DEBUG_STR, filter->host); XFREE(MTYPE_BGP_DEBUG_FILTER, filter); } } /* * Print the desc along with a list of peers/prefixes this debug is * enabled for */ static void bgp_debug_list_print(struct vty *vty, const char *desc, struct list *list) { struct bgp_debug_filter *filter; struct listnode *node, *nnode; char buf[PREFIX2STR_BUFFER]; vty_out(vty, "%s", desc); if (list && !list_isempty(list)) { vty_out(vty, " for"); for (ALL_LIST_ELEMENTS(list, node, nnode, filter)) { if (filter->host) vty_out(vty, " %s", filter->host); if (filter->p && filter->p->family == AF_EVPN) bgp_debug_print_evpn_prefix(vty, "", filter->p); else if (filter->p) { prefix2str(filter->p, buf, sizeof(buf)); vty_out(vty, " %s", buf); } } } vty_out(vty, "\n"); } /* * Print the command to enable the debug for each peer/prefix this debug is * enabled for */ static int bgp_debug_list_conf_print(struct vty *vty, const char *desc, struct list *list) { struct bgp_debug_filter *filter; struct listnode *node, *nnode; char buf[PREFIX2STR_BUFFER]; int write = 0; if (list && !list_isempty(list)) { for (ALL_LIST_ELEMENTS(list, node, nnode, filter)) { if (filter->host) { vty_out(vty, "%s %s\n", desc, filter->host); write++; } if (filter->p && filter->p->family == AF_EVPN) { bgp_debug_print_evpn_prefix(vty, desc, filter->p); write++; } else if (filter->p) { prefix2str(filter->p, buf, sizeof(buf)); vty_out(vty, "%s %s\n", desc, buf); write++; } } } if (!write) { vty_out(vty, "%s\n", desc); write++; } return write; } static void bgp_debug_list_add_entry(struct list *list, const char *host, const struct prefix *p) { struct bgp_debug_filter *filter; filter = XCALLOC(MTYPE_BGP_DEBUG_FILTER, sizeof(struct bgp_debug_filter)); if (host) { filter->host = XSTRDUP(MTYPE_BGP_DEBUG_STR, host); filter->p = NULL; } else if (p) { filter->host = NULL; filter->p = prefix_new(); prefix_copy(filter->p, p); } listnode_add(list, filter); } static int bgp_debug_list_remove_entry(struct list *list, const char *host, struct prefix *p) { struct bgp_debug_filter *filter; struct listnode *node, *nnode; for (ALL_LIST_ELEMENTS(list, node, nnode, filter)) { if (host && strcmp(filter->host, host) == 0) { listnode_delete(list, filter); XFREE(MTYPE_BGP_DEBUG_STR, filter->host); XFREE(MTYPE_BGP_DEBUG_FILTER, filter); return 1; } else if (p && filter->p->prefixlen == p->prefixlen && prefix_match(filter->p, p)) { listnode_delete(list, filter); prefix_free(filter->p); XFREE(MTYPE_BGP_DEBUG_FILTER, filter); return 1; } } return 0; } static int bgp_debug_list_has_entry(struct list *list, const char *host, const struct prefix *p) { struct bgp_debug_filter *filter; struct listnode *node, *nnode; for (ALL_LIST_ELEMENTS(list, node, nnode, filter)) { if (host) { if (strcmp(filter->host, host) == 0) { return 1; } } else if (p) { if (filter->p->prefixlen == p->prefixlen && prefix_match(filter->p, p)) { return 1; } } } return 0; } int bgp_debug_peer_updout_enabled(char *host) { return (bgp_debug_list_has_entry(bgp_debug_update_out_peers, host, NULL)); } /* Dump attribute. */ int bgp_dump_attr(struct attr *attr, char *buf, size_t size) { char addrbuf[BUFSIZ]; if (!attr) return 0; buf[0] = '\0'; if (CHECK_FLAG(attr->flag, ATTR_FLAG_BIT(BGP_ATTR_NEXT_HOP))) snprintf(buf, size, "nexthop %s", inet_ntoa(attr->nexthop)); if (CHECK_FLAG(attr->flag, ATTR_FLAG_BIT(BGP_ATTR_ORIGIN))) snprintf(buf + strlen(buf), size - strlen(buf), ", origin %s", bgp_origin_str[attr->origin]); /* Add MP case. */ if (attr->mp_nexthop_len == BGP_ATTR_NHLEN_IPV6_GLOBAL || attr->mp_nexthop_len == BGP_ATTR_NHLEN_IPV6_GLOBAL_AND_LL) snprintf(buf + strlen(buf), size - strlen(buf), ", mp_nexthop %s", inet_ntop(AF_INET6, &attr->mp_nexthop_global, addrbuf, BUFSIZ)); if (attr->mp_nexthop_len == BGP_ATTR_NHLEN_IPV6_GLOBAL_AND_LL) snprintf(buf + strlen(buf), size - strlen(buf), "(%s)", inet_ntop(AF_INET6, &attr->mp_nexthop_local, addrbuf, BUFSIZ)); if (attr->mp_nexthop_len == BGP_ATTR_NHLEN_IPV4) snprintf(buf, size, "nexthop %s", inet_ntoa(attr->nexthop)); if (CHECK_FLAG(attr->flag, ATTR_FLAG_BIT(BGP_ATTR_LOCAL_PREF))) snprintf(buf + strlen(buf), size - strlen(buf), ", localpref %u", attr->local_pref); if (CHECK_FLAG(attr->flag, ATTR_FLAG_BIT(BGP_ATTR_MULTI_EXIT_DISC))) snprintf(buf + strlen(buf), size - strlen(buf), ", metric %u", attr->med); if (CHECK_FLAG(attr->flag, ATTR_FLAG_BIT(BGP_ATTR_COMMUNITIES))) snprintf(buf + strlen(buf), size - strlen(buf), ", community %s", community_str(attr->community, false)); if (CHECK_FLAG(attr->flag, ATTR_FLAG_BIT(BGP_ATTR_EXT_COMMUNITIES))) snprintf(buf + strlen(buf), size - strlen(buf), ", extcommunity %s", ecommunity_str(attr->ecommunity)); if (CHECK_FLAG(attr->flag, ATTR_FLAG_BIT(BGP_ATTR_ATOMIC_AGGREGATE))) snprintf(buf + strlen(buf), size - strlen(buf), ", atomic-aggregate"); if (CHECK_FLAG(attr->flag, ATTR_FLAG_BIT(BGP_ATTR_AGGREGATOR))) snprintf(buf + strlen(buf), size - strlen(buf), ", aggregated by %u %s", attr->aggregator_as, inet_ntoa(attr->aggregator_addr)); if (CHECK_FLAG(attr->flag, ATTR_FLAG_BIT(BGP_ATTR_ORIGINATOR_ID))) snprintf(buf + strlen(buf), size - strlen(buf), ", originator %s", inet_ntoa(attr->originator_id)); if (CHECK_FLAG(attr->flag, ATTR_FLAG_BIT(BGP_ATTR_CLUSTER_LIST))) { int i; snprintf(buf + strlen(buf), size - strlen(buf), ", clusterlist"); for (i = 0; i < attr->cluster->length / 4; i++) snprintf(buf + strlen(buf), size - strlen(buf), " %s", inet_ntoa(attr->cluster->list[i])); } if (CHECK_FLAG(attr->flag, ATTR_FLAG_BIT(BGP_ATTR_PMSI_TUNNEL))) snprintf(buf + strlen(buf), size - strlen(buf), ", pmsi tnltype %u", attr->pmsi_tnl_type); if (CHECK_FLAG(attr->flag, ATTR_FLAG_BIT(BGP_ATTR_AS_PATH))) snprintf(buf + strlen(buf), size - strlen(buf), ", path %s", aspath_print(attr->aspath)); if (CHECK_FLAG(attr->flag, ATTR_FLAG_BIT(BGP_ATTR_PREFIX_SID))) { if (attr->label_index != BGP_INVALID_LABEL_INDEX) snprintf(buf + strlen(buf), size - strlen(buf), ", label-index %u", attr->label_index); } if (strlen(buf) > 1) return 1; else return 0; } const char *bgp_notify_code_str(char code) { return lookup_msg(bgp_notify_msg, code, "Unrecognized Error Code"); } const char *bgp_notify_subcode_str(char code, char subcode) { switch (code) { case BGP_NOTIFY_HEADER_ERR: return lookup_msg(bgp_notify_head_msg, subcode, "Unrecognized Error Subcode"); case BGP_NOTIFY_OPEN_ERR: return lookup_msg(bgp_notify_open_msg, subcode, "Unrecognized Error Subcode"); case BGP_NOTIFY_UPDATE_ERR: return lookup_msg(bgp_notify_update_msg, subcode, "Unrecognized Error Subcode"); case BGP_NOTIFY_HOLD_ERR: break; case BGP_NOTIFY_FSM_ERR: break; case BGP_NOTIFY_CEASE: return lookup_msg(bgp_notify_cease_msg, subcode, "Unrecognized Error Subcode"); case BGP_NOTIFY_CAPABILITY_ERR: return lookup_msg(bgp_notify_capability_msg, subcode, "Unrecognized Error Subcode"); } return ""; } /* extract notify admin reason if correctly present */ const char *bgp_notify_admin_message(char *buf, size_t bufsz, uint8_t *data, size_t datalen) { if (!data || datalen < 1) return NULL; uint8_t len = data[0]; if (len > 128 || len > datalen - 1) return NULL; return zlog_sanitize(buf, bufsz, data + 1, len); } /* dump notify packet */ void bgp_notify_print(struct peer *peer, struct bgp_notify *bgp_notify, const char *direct) { const char *subcode_str; const char *code_str; const char *msg_str = NULL; char msg_buf[1024]; if (BGP_DEBUG(neighbor_events, NEIGHBOR_EVENTS) || bgp_flag_check(peer->bgp, BGP_FLAG_LOG_NEIGHBOR_CHANGES)) { code_str = bgp_notify_code_str(bgp_notify->code); subcode_str = bgp_notify_subcode_str(bgp_notify->code, bgp_notify->subcode); if (bgp_notify->code == BGP_NOTIFY_CEASE && (bgp_notify->subcode == BGP_NOTIFY_CEASE_ADMIN_SHUTDOWN || bgp_notify->subcode == BGP_NOTIFY_CEASE_ADMIN_RESET)) { msg_str = bgp_notify_admin_message( msg_buf, sizeof(msg_buf), bgp_notify->raw_data, bgp_notify->length); } if (msg_str) { zlog_info( "%%NOTIFICATION: %s neighbor %s %d/%d (%s%s) \"%s\"", strcmp(direct, "received") == 0 ? "received from" : "sent to", peer->host, bgp_notify->code, bgp_notify->subcode, code_str, subcode_str, msg_str); } else { msg_str = bgp_notify->data ? bgp_notify->data : ""; zlog_info( "%%NOTIFICATION: %s neighbor %s %d/%d (%s%s) %d bytes %s", strcmp(direct, "received") == 0 ? "received from" : "sent to", peer->host, bgp_notify->code, bgp_notify->subcode, code_str, subcode_str, bgp_notify->length, msg_str); } } } static void bgp_debug_clear_updgrp_update_dbg(struct bgp *bgp) { if (!bgp) bgp = bgp_get_default(); update_group_walk(bgp, update_group_clear_update_dbg, NULL); } static int bgp_debug_print_evpn_prefix(struct vty *vty, const char *desc, struct prefix *p) { char evpn_desc[PREFIX2STR_BUFFER + INET_ADDRSTRLEN]; char buf[PREFIX2STR_BUFFER]; char buf2[ETHER_ADDR_STRLEN]; if (p->u.prefix_evpn.route_type == BGP_EVPN_MAC_IP_ROUTE) { if (is_evpn_prefix_ipaddr_none((struct prefix_evpn *)p)) { sprintf(evpn_desc, "l2vpn evpn type macip mac %s", prefix_mac2str( &p->u.prefix_evpn.macip_addr.mac, buf2, sizeof(buf2))); } else { uint8_t family = is_evpn_prefix_ipaddr_v4( (struct prefix_evpn *)p) ? AF_INET : AF_INET6; sprintf(evpn_desc, "l2vpn evpn type macip mac %s ip %s", prefix_mac2str( &p->u.prefix_evpn.macip_addr.mac, buf2, sizeof(buf2)), inet_ntop(family, &p->u.prefix_evpn.macip_addr.ip.ip.addr, buf, PREFIX2STR_BUFFER)); } } else if (p->u.prefix_evpn.route_type == BGP_EVPN_IMET_ROUTE) { sprintf(evpn_desc, "l2vpn evpn type multicast ip %s", inet_ntoa(p->u.prefix_evpn.imet_addr.ip.ipaddr_v4)); } else if (p->u.prefix_evpn.route_type == BGP_EVPN_IP_PREFIX_ROUTE) { uint8_t family = is_evpn_prefix_ipaddr_v4( (struct prefix_evpn *)p) ? AF_INET : AF_INET6; sprintf(evpn_desc, "l2vpn evpn type prefix ip %s/%d", inet_ntop(family, &p->u.prefix_evpn.prefix_addr.ip.ip.addr, buf, PREFIX2STR_BUFFER), p->u.prefix_evpn.prefix_addr.ip_prefix_length); } vty_out(vty, "%s %s\n", desc, evpn_desc); return 0; } static int bgp_debug_parse_evpn_prefix(struct vty *vty, struct cmd_token **argv, int argc, struct prefix **argv_pp) { struct prefix *argv_p; struct ethaddr mac; struct ipaddr ip; int evpn_type; int type_idx = 0; int mac_idx = 0; int ip_idx = 0; argv_p = *argv_pp; if (argv_find(argv, argc, "macip", &type_idx)) evpn_type = BGP_EVPN_MAC_IP_ROUTE; else if (argv_find(argv, argc, "multicast", &type_idx)) evpn_type = BGP_EVPN_IMET_ROUTE; else if (argv_find(argv, argc, "prefix", &type_idx)) evpn_type = BGP_EVPN_IP_PREFIX_ROUTE; else evpn_type = 0; if (evpn_type == BGP_EVPN_MAC_IP_ROUTE) { memset(&ip, 0, sizeof(struct ipaddr)); argv_find(argv, argc, "mac", &mac_idx); (void)prefix_str2mac(argv[mac_idx + 1]->arg, &mac); argv_find(argv, argc, "ip", &ip_idx); str2ipaddr(argv[ip_idx + 1]->arg, &ip); build_evpn_type2_prefix((struct prefix_evpn *)argv_p, &mac, &ip); } else if (evpn_type == BGP_EVPN_IMET_ROUTE) { memset(&ip, 0, sizeof(struct ipaddr)); argv_find(argv, argc, "ip", &ip_idx); str2ipaddr(argv[ip_idx + 1]->arg, &ip); build_evpn_type3_prefix((struct prefix_evpn *)argv_p, ip.ipaddr_v4); } else if (evpn_type == BGP_EVPN_IP_PREFIX_ROUTE) { struct prefix ip_prefix; memset(&ip_prefix, 0, sizeof(struct prefix)); if (argv_find(argv, argc, "ip", &ip_idx)) { (void)str2prefix(argv[ip_idx + 1]->arg, &ip_prefix); apply_mask(&ip_prefix); } build_type5_prefix_from_ip_prefix( (struct prefix_evpn *)argv_p, &ip_prefix); } return CMD_SUCCESS; } /* Debug option setting interface. */ unsigned long bgp_debug_option = 0; int debug(unsigned int option) { return bgp_debug_option & option; } DEFUN (debug_bgp_as4, debug_bgp_as4_cmd, "debug bgp as4", DEBUG_STR BGP_STR "BGP AS4 actions\n") { if (vty->node == CONFIG_NODE) DEBUG_ON(as4, AS4); else { TERM_DEBUG_ON(as4, AS4); vty_out(vty, "BGP as4 debugging is on\n"); } return CMD_SUCCESS; } DEFUN (no_debug_bgp_as4, no_debug_bgp_as4_cmd, "no debug bgp as4", NO_STR DEBUG_STR BGP_STR "BGP AS4 actions\n") { if (vty->node == CONFIG_NODE) DEBUG_OFF(as4, AS4); else { TERM_DEBUG_OFF(as4, AS4); vty_out(vty, "BGP as4 debugging is off\n"); } return CMD_SUCCESS; } DEFUN (debug_bgp_as4_segment, debug_bgp_as4_segment_cmd, "debug bgp as4 segment", DEBUG_STR BGP_STR "BGP AS4 actions\n" "BGP AS4 aspath segment handling\n") { if (vty->node == CONFIG_NODE) DEBUG_ON(as4, AS4_SEGMENT); else { TERM_DEBUG_ON(as4, AS4_SEGMENT); vty_out(vty, "BGP as4 segment debugging is on\n"); } return CMD_SUCCESS; } DEFUN (no_debug_bgp_as4_segment, no_debug_bgp_as4_segment_cmd, "no debug bgp as4 segment", NO_STR DEBUG_STR BGP_STR "BGP AS4 actions\n" "BGP AS4 aspath segment handling\n") { if (vty->node == CONFIG_NODE) DEBUG_OFF(as4, AS4_SEGMENT); else { TERM_DEBUG_OFF(as4, AS4_SEGMENT); vty_out(vty, "BGP as4 segment debugging is off\n"); } return CMD_SUCCESS; } /* debug bgp neighbor_events */ DEFUN (debug_bgp_neighbor_events, debug_bgp_neighbor_events_cmd, "debug bgp neighbor-events", DEBUG_STR BGP_STR "BGP Neighbor Events\n") { bgp_debug_list_free(bgp_debug_neighbor_events_peers); if (vty->node == CONFIG_NODE) DEBUG_ON(neighbor_events, NEIGHBOR_EVENTS); else { TERM_DEBUG_ON(neighbor_events, NEIGHBOR_EVENTS); vty_out(vty, "BGP neighbor-events debugging is on\n"); } return CMD_SUCCESS; } DEFUN (debug_bgp_neighbor_events_peer, debug_bgp_neighbor_events_peer_cmd, "debug bgp neighbor-events ", DEBUG_STR BGP_STR "BGP Neighbor Events\n" "BGP neighbor IP address to debug\n" "BGP IPv6 neighbor to debug\n" "BGP neighbor on interface to debug\n") { int idx_peer = 3; const char *host = argv[idx_peer]->arg; if (!bgp_debug_neighbor_events_peers) bgp_debug_neighbor_events_peers = list_new(); if (bgp_debug_list_has_entry(bgp_debug_neighbor_events_peers, host, NULL)) { vty_out(vty, "BGP neighbor-events debugging is already enabled for %s\n", host); return CMD_SUCCESS; } bgp_debug_list_add_entry(bgp_debug_neighbor_events_peers, host, NULL); if (vty->node == CONFIG_NODE) DEBUG_ON(neighbor_events, NEIGHBOR_EVENTS); else { TERM_DEBUG_ON(neighbor_events, NEIGHBOR_EVENTS); vty_out(vty, "BGP neighbor-events debugging is on for %s\n", host); } return CMD_SUCCESS; } DEFUN (no_debug_bgp_neighbor_events, no_debug_bgp_neighbor_events_cmd, "no debug bgp neighbor-events", NO_STR DEBUG_STR BGP_STR "Neighbor Events\n") { bgp_debug_list_free(bgp_debug_neighbor_events_peers); if (vty->node == CONFIG_NODE) DEBUG_OFF(neighbor_events, NEIGHBOR_EVENTS); else { TERM_DEBUG_OFF(neighbor_events, NEIGHBOR_EVENTS); vty_out(vty, "BGP neighbor-events debugging is off\n"); } return CMD_SUCCESS; } DEFUN (no_debug_bgp_neighbor_events_peer, no_debug_bgp_neighbor_events_peer_cmd, "no debug bgp neighbor-events ", NO_STR DEBUG_STR BGP_STR "Neighbor Events\n" "BGP neighbor IP address to debug\n" "BGP IPv6 neighbor to debug\n" "BGP neighbor on interface to debug\n") { int idx_peer = 4; int found_peer = 0; const char *host = argv[idx_peer]->arg; if (bgp_debug_neighbor_events_peers && !list_isempty(bgp_debug_neighbor_events_peers)) { found_peer = bgp_debug_list_remove_entry( bgp_debug_neighbor_events_peers, host, NULL); if (list_isempty(bgp_debug_neighbor_events_peers)) { if (vty->node == CONFIG_NODE) DEBUG_OFF(neighbor_events, NEIGHBOR_EVENTS); else TERM_DEBUG_OFF(neighbor_events, NEIGHBOR_EVENTS); } } if (found_peer) vty_out(vty, "BGP neighbor-events debugging is off for %s\n", host); else vty_out(vty, "BGP neighbor-events debugging was not enabled for %s\n", host); return CMD_SUCCESS; } /* debug bgp nht */ DEFUN (debug_bgp_nht, debug_bgp_nht_cmd, "debug bgp nht", DEBUG_STR BGP_STR "BGP nexthop tracking events\n") { if (vty->node == CONFIG_NODE) DEBUG_ON(nht, NHT); else { TERM_DEBUG_ON(nht, NHT); vty_out(vty, "BGP nexthop tracking debugging is on\n"); } return CMD_SUCCESS; } DEFUN (no_debug_bgp_nht, no_debug_bgp_nht_cmd, "no debug bgp nht", NO_STR DEBUG_STR BGP_STR "BGP nexthop tracking events\n") { if (vty->node == CONFIG_NODE) DEBUG_OFF(nht, NHT); else { TERM_DEBUG_OFF(nht, NHT); vty_out(vty, "BGP nexthop tracking debugging is off\n"); } return CMD_SUCCESS; } /* debug bgp keepalives */ DEFUN (debug_bgp_keepalive, debug_bgp_keepalive_cmd, "debug bgp keepalives", DEBUG_STR BGP_STR "BGP keepalives\n") { bgp_debug_list_free(bgp_debug_keepalive_peers); if (vty->node == CONFIG_NODE) DEBUG_ON(keepalive, KEEPALIVE); else { TERM_DEBUG_ON(keepalive, KEEPALIVE); vty_out(vty, "BGP keepalives debugging is on\n"); } return CMD_SUCCESS; } DEFUN (debug_bgp_keepalive_peer, debug_bgp_keepalive_peer_cmd, "debug bgp keepalives ", DEBUG_STR BGP_STR "BGP Neighbor Events\n" "BGP neighbor IP address to debug\n" "BGP IPv6 neighbor to debug\n" "BGP neighbor on interface to debug\n") { int idx_peer = 3; const char *host = argv[idx_peer]->arg; if (!bgp_debug_keepalive_peers) bgp_debug_keepalive_peers = list_new(); if (bgp_debug_list_has_entry(bgp_debug_keepalive_peers, host, NULL)) { vty_out(vty, "BGP keepalive debugging is already enabled for %s\n", host); return CMD_SUCCESS; } bgp_debug_list_add_entry(bgp_debug_keepalive_peers, host, NULL); if (vty->node == CONFIG_NODE) DEBUG_ON(keepalive, KEEPALIVE); else { TERM_DEBUG_ON(keepalive, KEEPALIVE); vty_out(vty, "BGP keepalives debugging is on for %s\n", host); } return CMD_SUCCESS; } DEFUN (no_debug_bgp_keepalive, no_debug_bgp_keepalive_cmd, "no debug bgp keepalives", NO_STR DEBUG_STR BGP_STR "BGP keepalives\n") { bgp_debug_list_free(bgp_debug_keepalive_peers); if (vty->node == CONFIG_NODE) DEBUG_OFF(keepalive, KEEPALIVE); else { TERM_DEBUG_OFF(keepalive, KEEPALIVE); vty_out(vty, "BGP keepalives debugging is off\n"); } return CMD_SUCCESS; } DEFUN (no_debug_bgp_keepalive_peer, no_debug_bgp_keepalive_peer_cmd, "no debug bgp keepalives ", NO_STR DEBUG_STR BGP_STR "BGP keepalives\n" "BGP neighbor IP address to debug\n" "BGP IPv6 neighbor to debug\n" "BGP neighbor on interface to debug\n") { int idx_peer = 4; int found_peer = 0; const char *host = argv[idx_peer]->arg; if (bgp_debug_keepalive_peers && !list_isempty(bgp_debug_keepalive_peers)) { found_peer = bgp_debug_list_remove_entry( bgp_debug_keepalive_peers, host, NULL); if (list_isempty(bgp_debug_keepalive_peers)) { if (vty->node == CONFIG_NODE) DEBUG_OFF(keepalive, KEEPALIVE); else TERM_DEBUG_OFF(keepalive, KEEPALIVE); } } if (found_peer) vty_out(vty, "BGP keepalives debugging is off for %s\n", host); else vty_out(vty, "BGP keepalives debugging was not enabled for %s\n", host); return CMD_SUCCESS; } /* debug bgp bestpath */ DEFUN (debug_bgp_bestpath_prefix, debug_bgp_bestpath_prefix_cmd, "debug bgp bestpath ", DEBUG_STR BGP_STR "BGP bestpath\n" "IPv4 prefix\n" "IPv6 prefix\n") { struct prefix *argv_p; int idx_ipv4_ipv6_prefixlen = 3; argv_p = prefix_new(); (void)str2prefix(argv[idx_ipv4_ipv6_prefixlen]->arg, argv_p); apply_mask(argv_p); if (!bgp_debug_bestpath_prefixes) bgp_debug_bestpath_prefixes = list_new(); if (bgp_debug_list_has_entry(bgp_debug_bestpath_prefixes, NULL, argv_p)) { vty_out(vty, "BGP bestpath debugging is already enabled for %s\n", argv[idx_ipv4_ipv6_prefixlen]->arg); return CMD_SUCCESS; } bgp_debug_list_add_entry(bgp_debug_bestpath_prefixes, NULL, argv_p); if (vty->node == CONFIG_NODE) { DEBUG_ON(bestpath, BESTPATH); } else { TERM_DEBUG_ON(bestpath, BESTPATH); vty_out(vty, "BGP bestpath debugging is on for %s\n", argv[idx_ipv4_ipv6_prefixlen]->arg); } return CMD_SUCCESS; } DEFUN (no_debug_bgp_bestpath_prefix, no_debug_bgp_bestpath_prefix_cmd, "no debug bgp bestpath ", NO_STR DEBUG_STR BGP_STR "BGP bestpath\n" "IPv4 prefix\n" "IPv6 prefix\n") { int idx_ipv4_ipv6_prefixlen = 4; struct prefix *argv_p; int found_prefix = 0; argv_p = prefix_new(); (void)str2prefix(argv[idx_ipv4_ipv6_prefixlen]->arg, argv_p); apply_mask(argv_p); if (bgp_debug_bestpath_prefixes && !list_isempty(bgp_debug_bestpath_prefixes)) { found_prefix = bgp_debug_list_remove_entry( bgp_debug_bestpath_prefixes, NULL, argv_p); if (list_isempty(bgp_debug_bestpath_prefixes)) { if (vty->node == CONFIG_NODE) { DEBUG_OFF(bestpath, BESTPATH); } else { TERM_DEBUG_OFF(bestpath, BESTPATH); vty_out(vty, "BGP bestpath debugging (per prefix) is off\n"); } } } if (found_prefix) vty_out(vty, "BGP bestpath debugging is off for %s\n", argv[idx_ipv4_ipv6_prefixlen]->arg); else vty_out(vty, "BGP bestpath debugging was not enabled for %s\n", argv[idx_ipv4_ipv6_prefixlen]->arg); return CMD_SUCCESS; } DEFUN (no_debug_bgp_bestpath, no_debug_bgp_bestpath_cmd, "no debug bgp bestpath", NO_STR DEBUG_STR BGP_STR "BGP bestpath\n") { bgp_debug_list_free(bgp_debug_bestpath_prefixes); if (vty->node == CONFIG_NODE) DEBUG_OFF(bestpath, BESTPATH); else { TERM_DEBUG_OFF(bestpath, BESTPATH); vty_out(vty, "BGP bestpath debugging is off\n"); } return CMD_SUCCESS; } /* debug bgp updates */ DEFUN (debug_bgp_update, debug_bgp_update_cmd, "debug bgp updates", DEBUG_STR BGP_STR "BGP updates\n") { bgp_debug_list_free(bgp_debug_update_in_peers); bgp_debug_list_free(bgp_debug_update_out_peers); bgp_debug_list_free(bgp_debug_update_prefixes); if (vty->node == CONFIG_NODE) { DEBUG_ON(update, UPDATE_IN); DEBUG_ON(update, UPDATE_OUT); } else { TERM_DEBUG_ON(update, UPDATE_IN); TERM_DEBUG_ON(update, UPDATE_OUT); vty_out(vty, "BGP updates debugging is on\n"); } return CMD_SUCCESS; } DEFUN (debug_bgp_update_direct, debug_bgp_update_direct_cmd, "debug bgp updates ", DEBUG_STR BGP_STR "BGP updates\n" "Inbound updates\n" "Outbound updates\n") { int idx_in_out = 3; if (strncmp("i", argv[idx_in_out]->arg, 1) == 0) bgp_debug_list_free(bgp_debug_update_in_peers); else bgp_debug_list_free(bgp_debug_update_out_peers); if (vty->node == CONFIG_NODE) { if (strncmp("i", argv[idx_in_out]->arg, 1) == 0) DEBUG_ON(update, UPDATE_IN); else DEBUG_ON(update, UPDATE_OUT); } else { if (strncmp("i", argv[idx_in_out]->arg, 1) == 0) { TERM_DEBUG_ON(update, UPDATE_IN); vty_out(vty, "BGP updates debugging is on (inbound)\n"); } else { TERM_DEBUG_ON(update, UPDATE_OUT); vty_out(vty, "BGP updates debugging is on (outbound)\n"); } } return CMD_SUCCESS; } DEFUN (debug_bgp_update_direct_peer, debug_bgp_update_direct_peer_cmd, "debug bgp updates ", DEBUG_STR BGP_STR "BGP updates\n" "Inbound updates\n" "Outbound updates\n" "BGP neighbor IP address to debug\n" "BGP IPv6 neighbor to debug\n" "BGP neighbor on interface to debug\n") { int idx_in_out = 3; int idx_peer = 4; const char *host = argv[idx_peer]->arg; int inbound; if (!bgp_debug_update_in_peers) bgp_debug_update_in_peers = list_new(); if (!bgp_debug_update_out_peers) bgp_debug_update_out_peers = list_new(); if (strncmp("i", argv[idx_in_out]->arg, 1) == 0) inbound = 1; else inbound = 0; if (inbound) { if (bgp_debug_list_has_entry(bgp_debug_update_in_peers, host, NULL)) { vty_out(vty, "BGP inbound update debugging is already enabled for %s\n", host); return CMD_SUCCESS; } } else { if (bgp_debug_list_has_entry(bgp_debug_update_out_peers, host, NULL)) { vty_out(vty, "BGP outbound update debugging is already enabled for %s\n", host); return CMD_SUCCESS; } } if (inbound) bgp_debug_list_add_entry(bgp_debug_update_in_peers, host, NULL); else { struct peer *peer; struct peer_af *paf; int afidx; bgp_debug_list_add_entry(bgp_debug_update_out_peers, host, NULL); peer = bgp_find_peer(vty, host); if (peer) { for (afidx = BGP_AF_START; afidx < BGP_AF_MAX; afidx++) { paf = peer->peer_af_array[afidx]; if (paf != NULL) { if (PAF_SUBGRP(paf)) { UPDGRP_PEER_DBG_EN( PAF_SUBGRP(paf) ->update_group); } } } } } if (vty->node == CONFIG_NODE) { if (inbound) DEBUG_ON(update, UPDATE_IN); else DEBUG_ON(update, UPDATE_OUT); } else { if (inbound) { TERM_DEBUG_ON(update, UPDATE_IN); vty_out(vty, "BGP updates debugging is on (inbound) for %s\n", argv[idx_peer]->arg); } else { TERM_DEBUG_ON(update, UPDATE_OUT); vty_out(vty, "BGP updates debugging is on (outbound) for %s\n", argv[idx_peer]->arg); } } return CMD_SUCCESS; } DEFUN (no_debug_bgp_update_direct, no_debug_bgp_update_direct_cmd, "no debug bgp updates ", NO_STR DEBUG_STR BGP_STR "BGP updates\n" "Inbound updates\n" "Outbound updates\n") { int idx_in_out = 4; if (strncmp("i", argv[idx_in_out]->arg, 1) == 0) { bgp_debug_list_free(bgp_debug_update_in_peers); if (vty->node == CONFIG_NODE) { DEBUG_OFF(update, UPDATE_IN); } else { TERM_DEBUG_OFF(update, UPDATE_IN); vty_out(vty, "BGP updates debugging is off (inbound)\n"); } } else { bgp_debug_list_free(bgp_debug_update_out_peers); if (vty->node == CONFIG_NODE) { DEBUG_OFF(update, UPDATE_OUT); } else { TERM_DEBUG_OFF(update, UPDATE_OUT); vty_out(vty, "BGP updates debugging is off (outbound)\n"); } } return CMD_SUCCESS; } DEFUN (no_debug_bgp_update_direct_peer, no_debug_bgp_update_direct_peer_cmd, "no debug bgp updates ", NO_STR DEBUG_STR BGP_STR "BGP updates\n" "Inbound updates\n" "Outbound updates\n" "BGP neighbor IP address to debug\n" "BGP IPv6 neighbor to debug\n" "BGP neighbor on interface to debug\n") { int idx_in_out = 4; int idx_peer = 5; int inbound; int found_peer = 0; const char *host = argv[idx_peer]->arg; if (strncmp("i", argv[idx_in_out]->arg, 1) == 0) inbound = 1; else inbound = 0; if (inbound && bgp_debug_update_in_peers && !list_isempty(bgp_debug_update_in_peers)) { found_peer = bgp_debug_list_remove_entry( bgp_debug_update_in_peers, host, NULL); if (list_isempty(bgp_debug_update_in_peers)) { if (vty->node == CONFIG_NODE) DEBUG_OFF(update, UPDATE_IN); else { TERM_DEBUG_OFF(update, UPDATE_IN); vty_out(vty, "BGP updates debugging (inbound) is off\n"); } } } if (!inbound && bgp_debug_update_out_peers && !list_isempty(bgp_debug_update_out_peers)) { found_peer = bgp_debug_list_remove_entry( bgp_debug_update_out_peers, host, NULL); if (list_isempty(bgp_debug_update_out_peers)) { if (vty->node == CONFIG_NODE) DEBUG_OFF(update, UPDATE_OUT); else { TERM_DEBUG_OFF(update, UPDATE_OUT); vty_out(vty, "BGP updates debugging (outbound) is off\n"); } } struct peer *peer; struct peer_af *paf; int afidx; peer = bgp_find_peer(vty, host); if (peer) { for (afidx = BGP_AF_START; afidx < BGP_AF_MAX; afidx++) { paf = peer->peer_af_array[afidx]; if (paf != NULL) { if (PAF_SUBGRP(paf)) { UPDGRP_PEER_DBG_DIS( PAF_SUBGRP(paf) ->update_group); } } } } } if (found_peer) if (inbound) vty_out(vty, "BGP updates debugging (inbound) is off for %s\n", host); else vty_out(vty, "BGP updates debugging (outbound) is off for %s\n", host); else if (inbound) vty_out(vty, "BGP updates debugging (inbound) was not enabled for %s\n", host); else vty_out(vty, "BGP updates debugging (outbound) was not enabled for %s\n", host); return CMD_SUCCESS; } #ifndef VTYSH_EXTRACT_PL #include "bgpd/bgp_debug_clippy.c" #endif DEFPY (debug_bgp_update_prefix_afi_safi, debug_bgp_update_prefix_afi_safi_cmd, "debug bgp updates prefix l2vpn$afi evpn$safi type [ip ]|multicast ip |prefix ip >", DEBUG_STR BGP_STR "BGP updates\n" "Specify a prefix to debug\n" L2VPN_HELP_STR EVPN_HELP_STR "Specify EVPN Route type\n" "MAC-IP (Type-2) route\n" MAC_STR MAC_STR MAC_STR IP_STR "IPv4 address\n" "IPv6 address\n" "Multicast (Type-3) route\n" IP_STR "IPv4 address\n" "IPv6 address\n" "Prefix (Type-5) route\n" IP_STR "IPv4 prefix\n" "IPv6 prefix\n") { struct prefix *argv_p; int ret = CMD_SUCCESS; char buf[PREFIX2STR_BUFFER]; argv_p = prefix_new(); ret = bgp_debug_parse_evpn_prefix(vty, argv, argc, &argv_p); if (ret != CMD_SUCCESS) { prefix_free(argv_p); return ret; } if (!bgp_debug_update_prefixes) bgp_debug_update_prefixes = list_new(); prefix2str(argv_p, buf, sizeof(buf)); if (bgp_debug_list_has_entry(bgp_debug_update_prefixes, NULL, argv_p)) { vty_out(vty, "BGP updates debugging is already enabled for %s\n", buf); prefix_free(argv_p); return CMD_SUCCESS; } bgp_debug_list_add_entry(bgp_debug_update_prefixes, NULL, argv_p); if (vty->node == CONFIG_NODE) { DEBUG_ON(update, UPDATE_PREFIX); } else { TERM_DEBUG_ON(update, UPDATE_PREFIX); vty_out(vty, "BGP updates debugging is on for %s\n", buf); } prefix_free(argv_p); return CMD_SUCCESS; } DEFPY (no_debug_bgp_update_prefix_afi_safi, no_debug_bgp_update_prefix_afi_safi_cmd, "no debug bgp updates prefix l2vpn$afi evpn$safi type [ip ]|multicast ip |prefix ip >", NO_STR DEBUG_STR BGP_STR "BGP updates\n" "Specify a prefix to debug\n" L2VPN_HELP_STR EVPN_HELP_STR "Specify EVPN Route type\n" "MAC-IP (Type-2) route\n" MAC_STR MAC_STR MAC_STR IP_STR "IPv4 address\n" "IPv6 address\n" "Multicast (Type-3) route\n" IP_STR "IPv4 address\n" "IPv6 address\n" "Prefix (Type-5) route\n" IP_STR "IPv4 prefix\n" "IPv6 prefix\n") { struct prefix *argv_p; bool found_prefix = false; int ret = CMD_SUCCESS; char buf[PREFIX2STR_BUFFER]; argv_p = prefix_new(); ret = bgp_debug_parse_evpn_prefix(vty, argv, argc, &argv_p); if (ret != CMD_SUCCESS) { prefix_free(argv_p); return ret; } if (bgp_debug_update_prefixes && !list_isempty(bgp_debug_update_prefixes)) { found_prefix = bgp_debug_list_remove_entry( bgp_debug_update_prefixes, NULL, argv_p); if (list_isempty(bgp_debug_update_prefixes)) { if (vty->node == CONFIG_NODE) { DEBUG_OFF(update, UPDATE_PREFIX); } else { TERM_DEBUG_OFF(update, UPDATE_PREFIX); vty_out(vty, "BGP updates debugging (per prefix) is off\n"); } } } prefix2str(argv_p, buf, sizeof(buf)); if (found_prefix) vty_out(vty, "BGP updates debugging is off for %s\n", buf); else vty_out(vty, "BGP updates debugging was not enabled for %s\n", buf); prefix_free(argv_p); return ret; } DEFUN (debug_bgp_update_prefix, debug_bgp_update_prefix_cmd, "debug bgp updates prefix ", DEBUG_STR BGP_STR "BGP updates\n" "Specify a prefix to debug\n" "IPv4 prefix\n" "IPv6 prefix\n") { int idx_ipv4_ipv6_prefixlen = 4; struct prefix *argv_p; argv_p = prefix_new(); (void)str2prefix(argv[idx_ipv4_ipv6_prefixlen]->arg, argv_p); apply_mask(argv_p); if (!bgp_debug_update_prefixes) bgp_debug_update_prefixes = list_new(); if (bgp_debug_list_has_entry(bgp_debug_update_prefixes, NULL, argv_p)) { vty_out(vty, "BGP updates debugging is already enabled for %s\n", argv[idx_ipv4_ipv6_prefixlen]->arg); return CMD_SUCCESS; } bgp_debug_list_add_entry(bgp_debug_update_prefixes, NULL, argv_p); if (vty->node == CONFIG_NODE) { DEBUG_ON(update, UPDATE_PREFIX); } else { TERM_DEBUG_ON(update, UPDATE_PREFIX); vty_out(vty, "BGP updates debugging is on for %s\n", argv[idx_ipv4_ipv6_prefixlen]->arg); } return CMD_SUCCESS; } DEFUN (no_debug_bgp_update_prefix, no_debug_bgp_update_prefix_cmd, "no debug bgp updates prefix ", NO_STR DEBUG_STR BGP_STR "BGP updates\n" "Specify a prefix to debug\n" "IPv4 prefix\n" "IPv6 prefix\n") { int idx_ipv4_ipv6_prefixlen = 5; struct prefix *argv_p; int found_prefix = 0; argv_p = prefix_new(); (void)str2prefix(argv[idx_ipv4_ipv6_prefixlen]->arg, argv_p); apply_mask(argv_p); if (bgp_debug_update_prefixes && !list_isempty(bgp_debug_update_prefixes)) { found_prefix = bgp_debug_list_remove_entry( bgp_debug_update_prefixes, NULL, argv_p); if (list_isempty(bgp_debug_update_prefixes)) { if (vty->node == CONFIG_NODE) { DEBUG_OFF(update, UPDATE_PREFIX); } else { TERM_DEBUG_OFF(update, UPDATE_PREFIX); vty_out(vty, "BGP updates debugging (per prefix) is off\n"); } } } if (found_prefix) vty_out(vty, "BGP updates debugging is off for %s\n", argv[idx_ipv4_ipv6_prefixlen]->arg); else vty_out(vty, "BGP updates debugging was not enabled for %s\n", argv[idx_ipv4_ipv6_prefixlen]->arg); return CMD_SUCCESS; } DEFUN (no_debug_bgp_update, no_debug_bgp_update_cmd, "no debug bgp updates", NO_STR DEBUG_STR BGP_STR "BGP updates\n") { struct listnode *ln; struct bgp *bgp; bgp_debug_list_free(bgp_debug_update_in_peers); bgp_debug_list_free(bgp_debug_update_out_peers); bgp_debug_list_free(bgp_debug_update_prefixes); for (ALL_LIST_ELEMENTS_RO(bm->bgp, ln, bgp)) bgp_debug_clear_updgrp_update_dbg(bgp); if (vty->node == CONFIG_NODE) { DEBUG_OFF(update, UPDATE_IN); DEBUG_OFF(update, UPDATE_OUT); DEBUG_OFF(update, UPDATE_PREFIX); } else { TERM_DEBUG_OFF(update, UPDATE_IN); TERM_DEBUG_OFF(update, UPDATE_OUT); TERM_DEBUG_OFF(update, UPDATE_PREFIX); vty_out(vty, "BGP updates debugging is off\n"); } return CMD_SUCCESS; } /* debug bgp zebra */ DEFUN (debug_bgp_zebra, debug_bgp_zebra_cmd, "debug bgp zebra", DEBUG_STR BGP_STR "BGP Zebra messages\n") { if (vty->node == CONFIG_NODE) DEBUG_ON(zebra, ZEBRA); else { TERM_DEBUG_ON(zebra, ZEBRA); vty_out(vty, "BGP zebra debugging is on\n"); } return CMD_SUCCESS; } DEFUN (debug_bgp_zebra_prefix, debug_bgp_zebra_prefix_cmd, "debug bgp zebra prefix ", DEBUG_STR BGP_STR "BGP Zebra messages\n" "Specify a prefix to debug\n" "IPv4 prefix\n" "IPv6 prefix\n") { int idx_ipv4_ipv6_prefixlen = 4; struct prefix *argv_p; argv_p = prefix_new(); (void)str2prefix(argv[idx_ipv4_ipv6_prefixlen]->arg, argv_p); apply_mask(argv_p); if (!bgp_debug_zebra_prefixes) bgp_debug_zebra_prefixes = list_new(); if (bgp_debug_list_has_entry(bgp_debug_zebra_prefixes, NULL, argv_p)) { vty_out(vty, "BGP zebra debugging is already enabled for %s\n", argv[idx_ipv4_ipv6_prefixlen]->arg); return CMD_SUCCESS; } bgp_debug_list_add_entry(bgp_debug_zebra_prefixes, NULL, argv_p); if (vty->node == CONFIG_NODE) DEBUG_ON(zebra, ZEBRA); else { TERM_DEBUG_ON(zebra, ZEBRA); vty_out(vty, "BGP zebra debugging is on for %s\n", argv[idx_ipv4_ipv6_prefixlen]->arg); } return CMD_SUCCESS; } DEFUN (no_debug_bgp_zebra, no_debug_bgp_zebra_cmd, "no debug bgp zebra", NO_STR DEBUG_STR BGP_STR "BGP Zebra messages\n") { bgp_debug_list_free(bgp_debug_zebra_prefixes); if (vty->node == CONFIG_NODE) DEBUG_OFF(zebra, ZEBRA); else { TERM_DEBUG_OFF(zebra, ZEBRA); vty_out(vty, "BGP zebra debugging is off\n"); } return CMD_SUCCESS; } DEFUN (no_debug_bgp_zebra_prefix, no_debug_bgp_zebra_prefix_cmd, "no debug bgp zebra prefix ", NO_STR DEBUG_STR BGP_STR "BGP Zebra messages\n" "Specify a prefix to debug\n" "IPv4 prefix\n" "IPv6 prefix\n") { int idx_ipv4_ipv6_prefixlen = 5; struct prefix *argv_p; int found_prefix = 0; argv_p = prefix_new(); (void)str2prefix(argv[idx_ipv4_ipv6_prefixlen]->arg, argv_p); apply_mask(argv_p); if (bgp_debug_zebra_prefixes && !list_isempty(bgp_debug_zebra_prefixes)) { found_prefix = bgp_debug_list_remove_entry( bgp_debug_zebra_prefixes, NULL, argv_p); if (list_isempty(bgp_debug_zebra_prefixes)) { if (vty->node == CONFIG_NODE) DEBUG_OFF(zebra, ZEBRA); else { TERM_DEBUG_OFF(zebra, ZEBRA); vty_out(vty, "BGP zebra debugging is off\n"); } } } if (found_prefix) vty_out(vty, "BGP zebra debugging is off for %s\n", argv[idx_ipv4_ipv6_prefixlen]->arg); else vty_out(vty, "BGP zebra debugging was not enabled for %s\n", argv[idx_ipv4_ipv6_prefixlen]->arg); return CMD_SUCCESS; } DEFUN (debug_bgp_allow_martians, debug_bgp_allow_martians_cmd, "debug bgp allow-martians", DEBUG_STR BGP_STR "BGP allow martian next hops\n") { if (vty->node == CONFIG_NODE) DEBUG_ON(allow_martians, ALLOW_MARTIANS); else { TERM_DEBUG_ON(allow_martians, ALLOW_MARTIANS); vty_out(vty, "BGP allow_martian next hop debugging is on\n"); } return CMD_SUCCESS; } DEFUN (no_debug_bgp_allow_martians, no_debug_bgp_allow_martians_cmd, "no debug bgp allow-martians", NO_STR DEBUG_STR BGP_STR "BGP allow martian next hops\n") { if (vty->node == CONFIG_NODE) DEBUG_OFF(allow_martians, ALLOW_MARTIANS); else { TERM_DEBUG_OFF(allow_martians, ALLOW_MARTIANS); vty_out(vty, "BGP allow martian next hop debugging is off\n"); } return CMD_SUCCESS; } /* debug bgp update-groups */ DEFUN (debug_bgp_update_groups, debug_bgp_update_groups_cmd, "debug bgp update-groups", DEBUG_STR BGP_STR "BGP update-groups\n") { if (vty->node == CONFIG_NODE) DEBUG_ON(update_groups, UPDATE_GROUPS); else { TERM_DEBUG_ON(update_groups, UPDATE_GROUPS); vty_out(vty, "BGP update-groups debugging is on\n"); } return CMD_SUCCESS; } DEFUN (no_debug_bgp_update_groups, no_debug_bgp_update_groups_cmd, "no debug bgp update-groups", NO_STR DEBUG_STR BGP_STR "BGP update-groups\n") { if (vty->node == CONFIG_NODE) DEBUG_OFF(update_groups, UPDATE_GROUPS); else { TERM_DEBUG_OFF(update_groups, UPDATE_GROUPS); vty_out(vty, "BGP update-groups debugging is off\n"); } return CMD_SUCCESS; } DEFUN (debug_bgp_vpn, debug_bgp_vpn_cmd, "debug bgp vpn ", DEBUG_STR BGP_STR "VPN routes\n" "leaked from vrf to vpn\n" "leaked to vrf from vpn\n" "route-map updates\n" "labels\n") { int idx = 3; if (argv_find(argv, argc, "leak-from-vrf", &idx)) { if (vty->node == CONFIG_NODE) DEBUG_ON(vpn, VPN_LEAK_FROM_VRF); else TERM_DEBUG_ON(vpn, VPN_LEAK_FROM_VRF); } else if (argv_find(argv, argc, "leak-to-vrf", &idx)) { if (vty->node == CONFIG_NODE) DEBUG_ON(vpn, VPN_LEAK_TO_VRF); else TERM_DEBUG_ON(vpn, VPN_LEAK_TO_VRF); } else if (argv_find(argv, argc, "rmap-event", &idx)) { if (vty->node == CONFIG_NODE) DEBUG_ON(vpn, VPN_LEAK_RMAP_EVENT); else TERM_DEBUG_ON(vpn, VPN_LEAK_RMAP_EVENT); } else if (argv_find(argv, argc, "label", &idx)) { if (vty->node == CONFIG_NODE) DEBUG_ON(vpn, VPN_LEAK_LABEL); else TERM_DEBUG_ON(vpn, VPN_LEAK_LABEL); } else { vty_out(vty, "%% unknown debug bgp vpn keyword\n"); return CMD_WARNING_CONFIG_FAILED; } if (vty->node != CONFIG_NODE) vty_out(vty, "enabled debug bgp vpn %s\n", argv[idx]->text); return CMD_SUCCESS; } DEFUN (no_debug_bgp_vpn, no_debug_bgp_vpn_cmd, "no debug bgp vpn ", NO_STR DEBUG_STR BGP_STR "VPN routes\n" "leaked from vrf to vpn\n" "leaked to vrf from vpn\n" "route-map updates\n" "labels\n") { int idx = 4; if (argv_find(argv, argc, "leak-from-vrf", &idx)) { if (vty->node == CONFIG_NODE) DEBUG_OFF(vpn, VPN_LEAK_FROM_VRF); else TERM_DEBUG_OFF(vpn, VPN_LEAK_FROM_VRF); } else if (argv_find(argv, argc, "leak-to-vrf", &idx)) { if (vty->node == CONFIG_NODE) DEBUG_OFF(vpn, VPN_LEAK_TO_VRF); else TERM_DEBUG_OFF(vpn, VPN_LEAK_TO_VRF); } else if (argv_find(argv, argc, "rmap-event", &idx)) { if (vty->node == CONFIG_NODE) DEBUG_OFF(vpn, VPN_LEAK_RMAP_EVENT); else TERM_DEBUG_OFF(vpn, VPN_LEAK_RMAP_EVENT); } else if (argv_find(argv, argc, "label", &idx)) { if (vty->node == CONFIG_NODE) DEBUG_OFF(vpn, VPN_LEAK_LABEL); else TERM_DEBUG_OFF(vpn, VPN_LEAK_LABEL); } else { vty_out(vty, "%% unknown debug bgp vpn keyword\n"); return CMD_WARNING_CONFIG_FAILED; } if (vty->node != CONFIG_NODE) vty_out(vty, "disabled debug bgp vpn %s\n", argv[idx]->text); return CMD_SUCCESS; } /* debug bgp pbr */ DEFUN (debug_bgp_pbr, debug_bgp_pbr_cmd, "debug bgp pbr [error]", DEBUG_STR BGP_STR "BGP policy based routing\n" "BGP PBR error\n") { int idx = 3; if (argv_find(argv, argc, "error", &idx)) { if (vty->node == CONFIG_NODE) DEBUG_ON(pbr, PBR_ERROR); else { TERM_DEBUG_ON(pbr, PBR_ERROR); vty_out(vty, "BGP policy based routing error is on\n"); } return CMD_SUCCESS; } if (vty->node == CONFIG_NODE) DEBUG_ON(pbr, PBR); else { TERM_DEBUG_ON(pbr, PBR); vty_out(vty, "BGP policy based routing is on\n"); } return CMD_SUCCESS; } DEFUN (no_debug_bgp_pbr, no_debug_bgp_pbr_cmd, "no debug bgp pbr [error]", NO_STR DEBUG_STR BGP_STR "BGP policy based routing\n" "BGP PBR Error\n") { int idx = 3; if (argv_find(argv, argc, "error", &idx)) { if (vty->node == CONFIG_NODE) DEBUG_OFF(pbr, PBR_ERROR); else { TERM_DEBUG_OFF(pbr, PBR_ERROR); vty_out(vty, "BGP policy based routing error is off\n"); } return CMD_SUCCESS; } if (vty->node == CONFIG_NODE) DEBUG_OFF(pbr, PBR); else { TERM_DEBUG_OFF(pbr, PBR); vty_out(vty, "BGP policy based routing is off\n"); } return CMD_SUCCESS; } DEFUN (debug_bgp_labelpool, debug_bgp_labelpool_cmd, "debug bgp labelpool", DEBUG_STR BGP_STR "label pool\n") { if (vty->node == CONFIG_NODE) DEBUG_ON(labelpool, LABELPOOL); else TERM_DEBUG_ON(labelpool, LABELPOOL); if (vty->node != CONFIG_NODE) vty_out(vty, "enabled debug bgp labelpool\n"); return CMD_SUCCESS; } DEFUN (no_debug_bgp_labelpool, no_debug_bgp_labelpool_cmd, "no debug bgp labelpool", NO_STR DEBUG_STR BGP_STR "label pool\n") { if (vty->node == CONFIG_NODE) DEBUG_OFF(labelpool, LABELPOOL); else TERM_DEBUG_OFF(labelpool, LABELPOOL); if (vty->node != CONFIG_NODE) vty_out(vty, "disabled debug bgp labelpool\n"); return CMD_SUCCESS; } DEFUN (no_debug_bgp, no_debug_bgp_cmd, "no debug bgp", NO_STR DEBUG_STR BGP_STR) { struct bgp *bgp; struct listnode *ln; bgp_debug_list_free(bgp_debug_neighbor_events_peers); bgp_debug_list_free(bgp_debug_keepalive_peers); bgp_debug_list_free(bgp_debug_update_in_peers); bgp_debug_list_free(bgp_debug_update_out_peers); bgp_debug_list_free(bgp_debug_update_prefixes); bgp_debug_list_free(bgp_debug_bestpath_prefixes); bgp_debug_list_free(bgp_debug_zebra_prefixes); for (ALL_LIST_ELEMENTS_RO(bm->bgp, ln, bgp)) bgp_debug_clear_updgrp_update_dbg(bgp); TERM_DEBUG_OFF(keepalive, KEEPALIVE); TERM_DEBUG_OFF(update, UPDATE_IN); TERM_DEBUG_OFF(update, UPDATE_OUT); TERM_DEBUG_OFF(update, UPDATE_PREFIX); TERM_DEBUG_OFF(bestpath, BESTPATH); TERM_DEBUG_OFF(as4, AS4); TERM_DEBUG_OFF(as4, AS4_SEGMENT); TERM_DEBUG_OFF(neighbor_events, NEIGHBOR_EVENTS); TERM_DEBUG_OFF(zebra, ZEBRA); TERM_DEBUG_OFF(allow_martians, ALLOW_MARTIANS); TERM_DEBUG_OFF(nht, NHT); TERM_DEBUG_OFF(vpn, VPN_LEAK_FROM_VRF); TERM_DEBUG_OFF(vpn, VPN_LEAK_TO_VRF); TERM_DEBUG_OFF(vpn, VPN_LEAK_RMAP_EVENT); TERM_DEBUG_OFF(vpn, VPN_LEAK_LABEL); TERM_DEBUG_OFF(flowspec, FLOWSPEC); TERM_DEBUG_OFF(labelpool, LABELPOOL); TERM_DEBUG_OFF(pbr, PBR); TERM_DEBUG_OFF(pbr, PBR_ERROR); vty_out(vty, "All possible debugging has been turned off\n"); return CMD_SUCCESS; } DEFUN_NOSH (show_debugging_bgp, show_debugging_bgp_cmd, "show debugging [bgp]", SHOW_STR DEBUG_STR BGP_STR) { vty_out(vty, "BGP debugging status:\n"); if (BGP_DEBUG(as4, AS4)) vty_out(vty, " BGP as4 debugging is on\n"); if (BGP_DEBUG(as4, AS4_SEGMENT)) vty_out(vty, " BGP as4 aspath segment debugging is on\n"); if (BGP_DEBUG(bestpath, BESTPATH)) bgp_debug_list_print(vty, " BGP bestpath debugging is on", bgp_debug_bestpath_prefixes); if (BGP_DEBUG(keepalive, KEEPALIVE)) bgp_debug_list_print(vty, " BGP keepalives debugging is on", bgp_debug_keepalive_peers); if (BGP_DEBUG(neighbor_events, NEIGHBOR_EVENTS)) bgp_debug_list_print(vty, " BGP neighbor-events debugging is on", bgp_debug_neighbor_events_peers); if (BGP_DEBUG(nht, NHT)) vty_out(vty, " BGP next-hop tracking debugging is on\n"); if (BGP_DEBUG(update_groups, UPDATE_GROUPS)) vty_out(vty, " BGP update-groups debugging is on\n"); if (BGP_DEBUG(update, UPDATE_PREFIX)) bgp_debug_list_print(vty, " BGP updates debugging is on", bgp_debug_update_prefixes); if (BGP_DEBUG(update, UPDATE_IN)) bgp_debug_list_print(vty, " BGP updates debugging is on (inbound)", bgp_debug_update_in_peers); if (BGP_DEBUG(update, UPDATE_OUT)) bgp_debug_list_print(vty, " BGP updates debugging is on (outbound)", bgp_debug_update_out_peers); if (BGP_DEBUG(zebra, ZEBRA)) bgp_debug_list_print(vty, " BGP zebra debugging is on", bgp_debug_zebra_prefixes); if (BGP_DEBUG(allow_martians, ALLOW_MARTIANS)) vty_out(vty, " BGP allow martian next hop debugging is on\n"); if (BGP_DEBUG(vpn, VPN_LEAK_FROM_VRF)) vty_out(vty, " BGP route leak from vrf to vpn debugging is on\n"); if (BGP_DEBUG(vpn, VPN_LEAK_TO_VRF)) vty_out(vty, " BGP route leak to vrf from vpn debugging is on\n"); if (BGP_DEBUG(vpn, VPN_LEAK_RMAP_EVENT)) vty_out(vty, " BGP vpn route-map event debugging is on\n"); if (BGP_DEBUG(vpn, VPN_LEAK_LABEL)) vty_out(vty, " BGP vpn label event debugging is on\n"); if (BGP_DEBUG(flowspec, FLOWSPEC)) vty_out(vty, " BGP flowspec debugging is on\n"); if (BGP_DEBUG(labelpool, LABELPOOL)) vty_out(vty, " BGP labelpool debugging is on\n"); if (BGP_DEBUG(pbr, PBR)) vty_out(vty, " BGP policy based routing debugging is on\n"); if (BGP_DEBUG(pbr, PBR_ERROR)) vty_out(vty, " BGP policy based routing error debugging is on\n"); vty_out(vty, "\n"); return CMD_SUCCESS; } static int bgp_config_write_debug(struct vty *vty) { int write = 0; if (CONF_BGP_DEBUG(as4, AS4)) { vty_out(vty, "debug bgp as4\n"); write++; } if (CONF_BGP_DEBUG(as4, AS4_SEGMENT)) { vty_out(vty, "debug bgp as4 segment\n"); write++; } if (CONF_BGP_DEBUG(bestpath, BESTPATH)) { write += bgp_debug_list_conf_print(vty, "debug bgp bestpath", bgp_debug_bestpath_prefixes); } if (CONF_BGP_DEBUG(keepalive, KEEPALIVE)) { write += bgp_debug_list_conf_print(vty, "debug bgp keepalives", bgp_debug_keepalive_peers); } if (CONF_BGP_DEBUG(neighbor_events, NEIGHBOR_EVENTS)) { write += bgp_debug_list_conf_print( vty, "debug bgp neighbor-events", bgp_debug_neighbor_events_peers); } if (CONF_BGP_DEBUG(nht, NHT)) { vty_out(vty, "debug bgp nht\n"); write++; } if (CONF_BGP_DEBUG(update_groups, UPDATE_GROUPS)) { vty_out(vty, "debug bgp update-groups\n"); write++; } if (CONF_BGP_DEBUG(update, UPDATE_PREFIX)) { write += bgp_debug_list_conf_print(vty, "debug bgp updates prefix", bgp_debug_update_prefixes); } if (CONF_BGP_DEBUG(update, UPDATE_IN)) { write += bgp_debug_list_conf_print(vty, "debug bgp updates in", bgp_debug_update_in_peers); } if (CONF_BGP_DEBUG(update, UPDATE_OUT)) { write += bgp_debug_list_conf_print(vty, "debug bgp updates out", bgp_debug_update_out_peers); } if (CONF_BGP_DEBUG(zebra, ZEBRA)) { if (!bgp_debug_zebra_prefixes || list_isempty(bgp_debug_zebra_prefixes)) { vty_out(vty, "debug bgp zebra\n"); write++; } else { write += bgp_debug_list_conf_print( vty, "debug bgp zebra prefix", bgp_debug_zebra_prefixes); } } if (CONF_BGP_DEBUG(allow_martians, ALLOW_MARTIANS)) { vty_out(vty, "debug bgp allow-martians\n"); write++; } if (CONF_BGP_DEBUG(vpn, VPN_LEAK_FROM_VRF)) { vty_out(vty, "debug bgp vpn leak-from-vrf\n"); write++; } if (CONF_BGP_DEBUG(vpn, VPN_LEAK_TO_VRF)) { vty_out(vty, "debug bgp vpn leak-to-vrf\n"); write++; } if (CONF_BGP_DEBUG(vpn, VPN_LEAK_RMAP_EVENT)) { vty_out(vty, "debug bgp vpn rmap-event\n"); write++; } if (CONF_BGP_DEBUG(vpn, VPN_LEAK_LABEL)) { vty_out(vty, "debug bgp vpn label\n"); write++; } if (CONF_BGP_DEBUG(flowspec, FLOWSPEC)) { vty_out(vty, "debug bgp flowspec\n"); write++; } if (CONF_BGP_DEBUG(labelpool, LABELPOOL)) { vty_out(vty, "debug bgp labelpool\n"); write++; } if (CONF_BGP_DEBUG(pbr, PBR)) { vty_out(vty, "debug bgp pbr\n"); write++; } if (CONF_BGP_DEBUG(pbr, PBR_ERROR)) { vty_out(vty, "debug bgp pbr error\n"); write++; } return write; } static struct cmd_node debug_node = {DEBUG_NODE, "", 1}; void bgp_debug_init(void) { install_node(&debug_node, bgp_config_write_debug); install_element(ENABLE_NODE, &show_debugging_bgp_cmd); install_element(ENABLE_NODE, &debug_bgp_as4_cmd); install_element(CONFIG_NODE, &debug_bgp_as4_cmd); install_element(ENABLE_NODE, &debug_bgp_as4_segment_cmd); install_element(CONFIG_NODE, &debug_bgp_as4_segment_cmd); install_element(ENABLE_NODE, &debug_bgp_neighbor_events_cmd); install_element(CONFIG_NODE, &debug_bgp_neighbor_events_cmd); install_element(ENABLE_NODE, &debug_bgp_nht_cmd); install_element(CONFIG_NODE, &debug_bgp_nht_cmd); install_element(ENABLE_NODE, &debug_bgp_keepalive_cmd); install_element(CONFIG_NODE, &debug_bgp_keepalive_cmd); install_element(ENABLE_NODE, &debug_bgp_update_cmd); install_element(CONFIG_NODE, &debug_bgp_update_cmd); install_element(ENABLE_NODE, &debug_bgp_zebra_cmd); install_element(CONFIG_NODE, &debug_bgp_zebra_cmd); install_element(ENABLE_NODE, &debug_bgp_allow_martians_cmd); install_element(CONFIG_NODE, &debug_bgp_allow_martians_cmd); install_element(ENABLE_NODE, &debug_bgp_update_groups_cmd); install_element(CONFIG_NODE, &debug_bgp_update_groups_cmd); install_element(ENABLE_NODE, &debug_bgp_bestpath_prefix_cmd); install_element(CONFIG_NODE, &debug_bgp_bestpath_prefix_cmd); /* debug bgp updates (in|out) */ install_element(ENABLE_NODE, &debug_bgp_update_direct_cmd); install_element(CONFIG_NODE, &debug_bgp_update_direct_cmd); install_element(ENABLE_NODE, &no_debug_bgp_update_direct_cmd); install_element(CONFIG_NODE, &no_debug_bgp_update_direct_cmd); /* debug bgp updates (in|out) A.B.C.D */ install_element(ENABLE_NODE, &debug_bgp_update_direct_peer_cmd); install_element(CONFIG_NODE, &debug_bgp_update_direct_peer_cmd); install_element(ENABLE_NODE, &no_debug_bgp_update_direct_peer_cmd); install_element(CONFIG_NODE, &no_debug_bgp_update_direct_peer_cmd); /* debug bgp updates prefix A.B.C.D/M */ install_element(ENABLE_NODE, &debug_bgp_update_prefix_cmd); install_element(CONFIG_NODE, &debug_bgp_update_prefix_cmd); install_element(ENABLE_NODE, &no_debug_bgp_update_prefix_cmd); install_element(CONFIG_NODE, &no_debug_bgp_update_prefix_cmd); install_element(ENABLE_NODE, &debug_bgp_update_prefix_afi_safi_cmd); install_element(CONFIG_NODE, &debug_bgp_update_prefix_afi_safi_cmd); install_element(ENABLE_NODE, &no_debug_bgp_update_prefix_afi_safi_cmd); install_element(CONFIG_NODE, &no_debug_bgp_update_prefix_afi_safi_cmd); /* debug bgp zebra prefix A.B.C.D/M */ install_element(ENABLE_NODE, &debug_bgp_zebra_prefix_cmd); install_element(CONFIG_NODE, &debug_bgp_zebra_prefix_cmd); install_element(ENABLE_NODE, &no_debug_bgp_zebra_prefix_cmd); install_element(CONFIG_NODE, &no_debug_bgp_zebra_prefix_cmd); install_element(ENABLE_NODE, &no_debug_bgp_as4_cmd); install_element(CONFIG_NODE, &no_debug_bgp_as4_cmd); install_element(ENABLE_NODE, &no_debug_bgp_as4_segment_cmd); install_element(CONFIG_NODE, &no_debug_bgp_as4_segment_cmd); /* debug bgp neighbor-events A.B.C.D */ install_element(ENABLE_NODE, &debug_bgp_neighbor_events_peer_cmd); install_element(CONFIG_NODE, &debug_bgp_neighbor_events_peer_cmd); install_element(ENABLE_NODE, &no_debug_bgp_neighbor_events_peer_cmd); install_element(CONFIG_NODE, &no_debug_bgp_neighbor_events_peer_cmd); /* debug bgp keepalive A.B.C.D */ install_element(ENABLE_NODE, &debug_bgp_keepalive_peer_cmd); install_element(CONFIG_NODE, &debug_bgp_keepalive_peer_cmd); install_element(ENABLE_NODE, &no_debug_bgp_keepalive_peer_cmd); install_element(CONFIG_NODE, &no_debug_bgp_keepalive_peer_cmd); install_element(ENABLE_NODE, &no_debug_bgp_neighbor_events_cmd); install_element(CONFIG_NODE, &no_debug_bgp_neighbor_events_cmd); install_element(ENABLE_NODE, &no_debug_bgp_nht_cmd); install_element(CONFIG_NODE, &no_debug_bgp_nht_cmd); install_element(ENABLE_NODE, &no_debug_bgp_keepalive_cmd); install_element(CONFIG_NODE, &no_debug_bgp_keepalive_cmd); install_element(ENABLE_NODE, &no_debug_bgp_update_cmd); install_element(CONFIG_NODE, &no_debug_bgp_update_cmd); install_element(ENABLE_NODE, &no_debug_bgp_zebra_cmd); install_element(CONFIG_NODE, &no_debug_bgp_zebra_cmd); install_element(ENABLE_NODE, &no_debug_bgp_allow_martians_cmd); install_element(CONFIG_NODE, &no_debug_bgp_allow_martians_cmd); install_element(ENABLE_NODE, &no_debug_bgp_update_groups_cmd); install_element(CONFIG_NODE, &no_debug_bgp_update_groups_cmd); install_element(ENABLE_NODE, &no_debug_bgp_cmd); install_element(ENABLE_NODE, &no_debug_bgp_bestpath_cmd); install_element(CONFIG_NODE, &no_debug_bgp_bestpath_cmd); install_element(ENABLE_NODE, &no_debug_bgp_bestpath_prefix_cmd); install_element(CONFIG_NODE, &no_debug_bgp_bestpath_prefix_cmd); install_element(ENABLE_NODE, &debug_bgp_vpn_cmd); install_element(CONFIG_NODE, &debug_bgp_vpn_cmd); install_element(ENABLE_NODE, &no_debug_bgp_vpn_cmd); install_element(CONFIG_NODE, &no_debug_bgp_vpn_cmd); install_element(ENABLE_NODE, &debug_bgp_labelpool_cmd); install_element(CONFIG_NODE, &debug_bgp_labelpool_cmd); install_element(ENABLE_NODE, &no_debug_bgp_labelpool_cmd); install_element(CONFIG_NODE, &no_debug_bgp_labelpool_cmd); /* debug bgp pbr */ install_element(ENABLE_NODE, &debug_bgp_pbr_cmd); install_element(CONFIG_NODE, &debug_bgp_pbr_cmd); install_element(ENABLE_NODE, &no_debug_bgp_pbr_cmd); install_element(CONFIG_NODE, &no_debug_bgp_pbr_cmd); } /* Return true if this prefix is on the per_prefix_list of prefixes to debug * for BGP_DEBUG_TYPE */ static int bgp_debug_per_prefix(struct prefix *p, unsigned long term_bgp_debug_type, unsigned int BGP_DEBUG_TYPE, struct list *per_prefix_list) { struct bgp_debug_filter *filter; struct listnode *node, *nnode; if (term_bgp_debug_type & BGP_DEBUG_TYPE) { /* We are debugging all prefixes so return true */ if (!per_prefix_list || list_isempty(per_prefix_list)) return 1; else { if (!p) return 0; for (ALL_LIST_ELEMENTS(per_prefix_list, node, nnode, filter)) if (filter->p->prefixlen == p->prefixlen && prefix_match(filter->p, p)) return 1; return 0; } } return 0; } /* Return true if this peer is on the per_peer_list of peers to debug * for BGP_DEBUG_TYPE */ static int bgp_debug_per_peer(char *host, unsigned long term_bgp_debug_type, unsigned int BGP_DEBUG_TYPE, struct list *per_peer_list) { struct bgp_debug_filter *filter; struct listnode *node, *nnode; if (term_bgp_debug_type & BGP_DEBUG_TYPE) { /* We are debugging all peers so return true */ if (!per_peer_list || list_isempty(per_peer_list)) return 1; else { if (!host) return 0; for (ALL_LIST_ELEMENTS(per_peer_list, node, nnode, filter)) if (strcmp(filter->host, host) == 0) return 1; return 0; } } return 0; } int bgp_debug_neighbor_events(struct peer *peer) { char *host = NULL; if (peer) host = peer->host; return bgp_debug_per_peer(host, term_bgp_debug_neighbor_events, BGP_DEBUG_NEIGHBOR_EVENTS, bgp_debug_neighbor_events_peers); } int bgp_debug_keepalive(struct peer *peer) { char *host = NULL; if (peer) host = peer->host; return bgp_debug_per_peer(host, term_bgp_debug_keepalive, BGP_DEBUG_KEEPALIVE, bgp_debug_keepalive_peers); } int bgp_debug_update(struct peer *peer, struct prefix *p, struct update_group *updgrp, unsigned int inbound) { char *host = NULL; if (peer) host = peer->host; if (inbound) { if (bgp_debug_per_peer(host, term_bgp_debug_update, BGP_DEBUG_UPDATE_IN, bgp_debug_update_in_peers)) return 1; } /* outbound */ else { if (bgp_debug_per_peer(host, term_bgp_debug_update, BGP_DEBUG_UPDATE_OUT, bgp_debug_update_out_peers)) return 1; /* Check if update debugging implicitly enabled for the group. */ if (updgrp && UPDGRP_DBG_ON(updgrp)) return 1; } if (BGP_DEBUG(update, UPDATE_PREFIX)) { if (bgp_debug_per_prefix(p, term_bgp_debug_update, BGP_DEBUG_UPDATE_PREFIX, bgp_debug_update_prefixes)) return 1; } return 0; } int bgp_debug_bestpath(struct prefix *p) { if (BGP_DEBUG(bestpath, BESTPATH)) { if (bgp_debug_per_prefix(p, term_bgp_debug_bestpath, BGP_DEBUG_BESTPATH, bgp_debug_bestpath_prefixes)) return 1; } return 0; } int bgp_debug_zebra(struct prefix *p) { if (BGP_DEBUG(zebra, ZEBRA)) { if (bgp_debug_per_prefix(p, term_bgp_debug_zebra, BGP_DEBUG_ZEBRA, bgp_debug_zebra_prefixes)) return 1; } return 0; } const char *bgp_debug_rdpfxpath2str(afi_t afi, safi_t safi, struct prefix_rd *prd, union prefixconstptr pu, mpls_label_t *label, uint32_t num_labels, int addpath_valid, uint32_t addpath_id, char *str, int size) { char rd_buf[RD_ADDRSTRLEN]; char pfx_buf[PREFIX_STRLEN]; char tag_buf[30]; /* ' with addpath ID ' 17 * max strlen of uint32 + 10 * +/- (just in case) + 1 * null terminator + 1 * ============================ 29 */ char pathid_buf[30]; if (size < BGP_PRD_PATH_STRLEN) return NULL; /* Note: Path-id is created by default, but only included in update * sometimes. */ pathid_buf[0] = '\0'; if (addpath_valid) snprintf(pathid_buf, sizeof(pathid_buf), " with addpath ID %u", addpath_id); tag_buf[0] = '\0'; if (bgp_labeled_safi(safi) && num_labels) { if (safi == SAFI_EVPN) { char tag_buf2[20]; bgp_evpn_label2str(label, num_labels, tag_buf2, 20); sprintf(tag_buf, " label %s", tag_buf2); } else { uint32_t label_value; label_value = decode_label(label); sprintf(tag_buf, " label %u", label_value); } } if (prd) snprintf(str, size, "RD %s %s%s%s %s %s", prefix_rd2str(prd, rd_buf, sizeof(rd_buf)), prefix2str(pu, pfx_buf, sizeof(pfx_buf)), tag_buf, pathid_buf, afi2str(afi), safi2str(safi)); else if (safi == SAFI_FLOWSPEC) { char return_string[BGP_FLOWSPEC_NLRI_STRING_MAX]; const struct prefix_fs *fs = pu.fs; bgp_fs_nlri_get_string((unsigned char *)fs->prefix.ptr, fs->prefix.prefixlen, return_string, NLRI_STRING_FORMAT_DEBUG, NULL); snprintf(str, size, "FS %s Match{%s}", afi2str(afi), return_string); } else snprintf(str, size, "%s%s%s %s %s", prefix2str(pu, pfx_buf, sizeof(pfx_buf)), tag_buf, pathid_buf, afi2str(afi), safi2str(safi)); return str; } frr-7.2.1/bgpd/bgp_debug.h0000644000000000000000000001450213610377563012221 00000000000000/* BGP message debug header. * Copyright (C) 1996, 97, 98 Kunihiro Ishiguro * * This file is part of GNU Zebra. * * GNU Zebra is free software; you can redistribute it and/or modify it * under the terms of the GNU General Public License as published by the * Free Software Foundation; either version 2, or (at your option) any * later version. * * GNU Zebra 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; see the file COPYING; if not, write to the Free Software * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA */ #ifndef _QUAGGA_BGP_DEBUG_H #define _QUAGGA_BGP_DEBUG_H #include "bgp_attr.h" #include "bgp_updgrp.h" /* sort of packet direction */ #define DUMP_ON 1 #define DUMP_SEND 2 #define DUMP_RECV 4 /* for dump_update */ #define DUMP_WITHDRAW 8 #define DUMP_NLRI 16 /* dump detail */ #define DUMP_DETAIL 32 /* RD + Prefix + Path-Id */ #define BGP_PRD_PATH_STRLEN (PREFIX_STRLEN + RD_ADDRSTRLEN + 20) extern int dump_open; extern int dump_update; extern int dump_keepalive; extern int dump_notify; extern int Debug_Event; extern int Debug_Keepalive; extern int Debug_Update; extern int Debug_Radix; #define NLRI 1 #define WITHDRAW 2 #define NO_OPT 3 #define SEND 4 #define RECV 5 #define DETAIL 6 /* Prototypes. */ extern void bgp_debug_init(void); extern void bgp_packet_dump(struct stream *); extern int debug(unsigned int option); extern unsigned long conf_bgp_debug_as4; extern unsigned long conf_bgp_debug_neighbor_events; extern unsigned long conf_bgp_debug_packet; extern unsigned long conf_bgp_debug_keepalive; extern unsigned long conf_bgp_debug_update; extern unsigned long conf_bgp_debug_bestpath; extern unsigned long conf_bgp_debug_zebra; extern unsigned long conf_bgp_debug_allow_martians; extern unsigned long conf_bgp_debug_nht; extern unsigned long conf_bgp_debug_update_groups; extern unsigned long conf_bgp_debug_vpn; extern unsigned long conf_bgp_debug_flowspec; extern unsigned long conf_bgp_debug_labelpool; extern unsigned long conf_bgp_debug_pbr; extern unsigned long term_bgp_debug_as4; extern unsigned long term_bgp_debug_neighbor_events; extern unsigned long term_bgp_debug_packet; extern unsigned long term_bgp_debug_keepalive; extern unsigned long term_bgp_debug_update; extern unsigned long term_bgp_debug_bestpath; extern unsigned long term_bgp_debug_zebra; extern unsigned long term_bgp_debug_allow_martians; extern unsigned long term_bgp_debug_nht; extern unsigned long term_bgp_debug_update_groups; extern unsigned long term_bgp_debug_vpn; extern unsigned long term_bgp_debug_flowspec; extern unsigned long term_bgp_debug_labelpool; extern unsigned long term_bgp_debug_pbr; extern struct list *bgp_debug_neighbor_events_peers; extern struct list *bgp_debug_keepalive_peers; extern struct list *bgp_debug_update_in_peers; extern struct list *bgp_debug_update_out_peers; extern struct list *bgp_debug_update_prefixes; extern struct list *bgp_debug_bestpath_prefixes; extern struct list *bgp_debug_zebra_prefixes; struct bgp_debug_filter { char *host; struct prefix *p; }; #define BGP_DEBUG_AS4 0x01 #define BGP_DEBUG_AS4_SEGMENT 0x02 #define BGP_DEBUG_BESTPATH 0x01 #define BGP_DEBUG_NEIGHBOR_EVENTS 0x01 #define BGP_DEBUG_PACKET 0x01 #define BGP_DEBUG_KEEPALIVE 0x01 #define BGP_DEBUG_UPDATE_IN 0x01 #define BGP_DEBUG_UPDATE_OUT 0x02 #define BGP_DEBUG_UPDATE_PREFIX 0x04 #define BGP_DEBUG_ZEBRA 0x01 #define BGP_DEBUG_ALLOW_MARTIANS 0x01 #define BGP_DEBUG_NHT 0x01 #define BGP_DEBUG_UPDATE_GROUPS 0x01 #define BGP_DEBUG_VPN_LEAK_FROM_VRF 0x01 #define BGP_DEBUG_VPN_LEAK_TO_VRF 0x02 #define BGP_DEBUG_VPN_LEAK_RMAP_EVENT 0x04 #define BGP_DEBUG_VPN_LEAK_LABEL 0x08 #define BGP_DEBUG_FLOWSPEC 0x01 #define BGP_DEBUG_LABELPOOL 0x01 #define BGP_DEBUG_PBR 0x01 #define BGP_DEBUG_PBR_ERROR 0x02 #define BGP_DEBUG_PACKET_SEND 0x01 #define BGP_DEBUG_PACKET_SEND_DETAIL 0x02 #define CONF_DEBUG_ON(a, b) (conf_bgp_debug_ ## a |= (BGP_DEBUG_ ## b)) #define CONF_DEBUG_OFF(a, b) (conf_bgp_debug_ ## a &= ~(BGP_DEBUG_ ## b)) #define TERM_DEBUG_ON(a, b) (term_bgp_debug_ ## a |= (BGP_DEBUG_ ## b)) #define TERM_DEBUG_OFF(a, b) (term_bgp_debug_ ## a &= ~(BGP_DEBUG_ ## b)) #define DEBUG_ON(a, b) \ do { \ CONF_DEBUG_ON(a, b); \ TERM_DEBUG_ON(a, b); \ } while (0) #define DEBUG_OFF(a, b) \ do { \ CONF_DEBUG_OFF(a, b); \ TERM_DEBUG_OFF(a, b); \ } while (0) #define BGP_DEBUG(a, b) (term_bgp_debug_ ## a & BGP_DEBUG_ ## b) #define CONF_BGP_DEBUG(a, b) (conf_bgp_debug_ ## a & BGP_DEBUG_ ## b) extern const char *bgp_type_str[]; extern const char *pmsi_tnltype_str[]; extern int bgp_dump_attr(struct attr *, char *, size_t); extern int bgp_debug_peer_updout_enabled(char *host); extern const char *bgp_notify_code_str(char); extern const char *bgp_notify_subcode_str(char, char); extern void bgp_notify_print(struct peer *, struct bgp_notify *, const char *); extern const struct message bgp_status_msg[]; extern int bgp_debug_neighbor_events(struct peer *peer); extern int bgp_debug_keepalive(struct peer *peer); extern int bgp_debug_update(struct peer *peer, struct prefix *p, struct update_group *updgrp, unsigned int inbound); extern int bgp_debug_bestpath(struct prefix *p); extern int bgp_debug_zebra(struct prefix *p); extern const char *bgp_debug_rdpfxpath2str(afi_t, safi_t, struct prefix_rd *, union prefixconstptr, mpls_label_t *, uint32_t, int, uint32_t, char *, int); const char *bgp_notify_admin_message(char *buf, size_t bufsz, uint8_t *data, size_t datalen); #endif /* _QUAGGA_BGP_DEBUG_H */ frr-7.2.1/bgpd/bgp_dump.c0000644000000000000000000005327413610377563012104 00000000000000/* BGP-4 dump routine * Copyright (C) 1999 Kunihiro Ishiguro * * This file is part of GNU Zebra. * * GNU Zebra is free software; you can redistribute it and/or modify it * under the terms of the GNU General Public License as published by the * Free Software Foundation; either version 2, or (at your option) any * later version. * * GNU Zebra 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; see the file COPYING; if not, write to the Free Software * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA */ #include #include "log.h" #include "stream.h" #include "sockunion.h" #include "command.h" #include "prefix.h" #include "thread.h" #include "linklist.h" #include "queue.h" #include "memory.h" #include "filter.h" #include "bgpd/bgp_table.h" #include "bgpd/bgpd.h" #include "bgpd/bgp_route.h" #include "bgpd/bgp_attr.h" #include "bgpd/bgp_dump.h" #include "bgpd/bgp_errors.h" #include "bgpd/bgp_packet.h" enum bgp_dump_type { BGP_DUMP_ALL, BGP_DUMP_ALL_ET, BGP_DUMP_UPDATES, BGP_DUMP_UPDATES_ET, BGP_DUMP_ROUTES }; static const struct bgp_dump_type_map { enum bgp_dump_type type; const char *str; } bgp_dump_type_map[] = { {BGP_DUMP_ALL, "all"}, {BGP_DUMP_ALL_ET, "all-et"}, {BGP_DUMP_UPDATES, "updates"}, {BGP_DUMP_UPDATES_ET, "updates-et"}, {BGP_DUMP_ROUTES, "routes-mrt"}, {0, NULL}, }; enum MRT_MSG_TYPES { MSG_NULL, MSG_START, /* sender is starting up */ MSG_DIE, /* receiver should shut down */ MSG_I_AM_DEAD, /* sender is shutting down */ MSG_PEER_DOWN, /* sender's peer is down */ MSG_PROTOCOL_BGP, /* msg is a BGP packet */ MSG_PROTOCOL_RIP, /* msg is a RIP packet */ MSG_PROTOCOL_IDRP, /* msg is an IDRP packet */ MSG_PROTOCOL_RIPNG, /* msg is a RIPNG packet */ MSG_PROTOCOL_BGP4PLUS, /* msg is a BGP4+ packet */ MSG_PROTOCOL_BGP4PLUS_01, /* msg is a BGP4+ (draft 01) packet */ MSG_PROTOCOL_OSPF, /* msg is an OSPF packet */ MSG_TABLE_DUMP, /* routing table dump */ MSG_TABLE_DUMP_V2 /* routing table dump, version 2 */ }; struct bgp_dump { enum bgp_dump_type type; char *filename; FILE *fp; unsigned int interval; char *interval_str; struct thread *t_interval; }; static int bgp_dump_unset(struct bgp_dump *bgp_dump); static int bgp_dump_interval_func(struct thread *); /* BGP packet dump output buffer. */ struct stream *bgp_dump_obuf; /* BGP dump strucuture for 'dump bgp all' */ struct bgp_dump bgp_dump_all; /* BGP dump structure for 'dump bgp updates' */ struct bgp_dump bgp_dump_updates; /* BGP dump structure for 'dump bgp routes' */ struct bgp_dump bgp_dump_routes; static FILE *bgp_dump_open_file(struct bgp_dump *bgp_dump) { int ret; time_t clock; struct tm *tm; char fullpath[MAXPATHLEN]; char realpath[MAXPATHLEN]; mode_t oldumask; time(&clock); tm = localtime(&clock); if (bgp_dump->filename[0] != DIRECTORY_SEP) { sprintf(fullpath, "%s/%s", vty_get_cwd(), bgp_dump->filename); ret = strftime(realpath, MAXPATHLEN, fullpath, tm); } else ret = strftime(realpath, MAXPATHLEN, bgp_dump->filename, tm); if (ret == 0) { flog_warn(EC_BGP_DUMP, "bgp_dump_open_file: strftime error"); return NULL; } if (bgp_dump->fp) fclose(bgp_dump->fp); oldumask = umask(0777 & ~LOGFILE_MASK); bgp_dump->fp = fopen(realpath, "w"); if (bgp_dump->fp == NULL) { flog_warn(EC_BGP_DUMP, "bgp_dump_open_file: %s: %s", realpath, strerror(errno)); umask(oldumask); return NULL; } umask(oldumask); return bgp_dump->fp; } static int bgp_dump_interval_add(struct bgp_dump *bgp_dump, int interval) { int secs_into_day; time_t t; struct tm *tm; if (interval > 0) { /* Periodic dump every interval seconds */ if ((interval < 86400) && ((86400 % interval) == 0)) { /* Dump at predictable times: if a day has a whole * number of * intervals, dump every interval seconds starting from * midnight */ (void)time(&t); tm = localtime(&t); secs_into_day = tm->tm_sec + 60 * tm->tm_min + 60 * 60 * tm->tm_hour; interval = interval - secs_into_day % interval; /* always > 0 */ } bgp_dump->t_interval = NULL; thread_add_timer(bm->master, bgp_dump_interval_func, bgp_dump, interval, &bgp_dump->t_interval); } else { /* One-off dump: execute immediately, don't affect any scheduled * dumps */ bgp_dump->t_interval = NULL; thread_add_event(bm->master, bgp_dump_interval_func, bgp_dump, 0, &bgp_dump->t_interval); } return 0; } /* Dump common header. */ static void bgp_dump_header(struct stream *obuf, int type, int subtype, int dump_type) { struct timeval clock; long msecs; time_t secs; if ((dump_type == BGP_DUMP_ALL_ET || dump_type == BGP_DUMP_UPDATES_ET) && type == MSG_PROTOCOL_BGP4MP) type = MSG_PROTOCOL_BGP4MP_ET; gettimeofday(&clock, NULL); secs = clock.tv_sec; msecs = clock.tv_usec; /* Put dump packet header. */ stream_putl(obuf, secs); stream_putw(obuf, type); stream_putw(obuf, subtype); stream_putl(obuf, 0); /* len */ /* Adding microseconds for the MRT Extended Header */ if (type == MSG_PROTOCOL_BGP4MP_ET) stream_putl(obuf, msecs); } static void bgp_dump_set_size(struct stream *s, int type) { /* * The BGP_DUMP_HEADER_SIZE stay at 12 event when ET: * "The Microsecond Timestamp is included in the computation * of the Length field value." (RFC6396 2011) */ stream_putl_at(s, 8, stream_get_endp(s) - BGP_DUMP_HEADER_SIZE); } static void bgp_dump_routes_index_table(struct bgp *bgp) { struct peer *peer; struct listnode *node; uint16_t peerno = 1; struct stream *obuf; obuf = bgp_dump_obuf; stream_reset(obuf); /* MRT header */ bgp_dump_header(obuf, MSG_TABLE_DUMP_V2, TABLE_DUMP_V2_PEER_INDEX_TABLE, BGP_DUMP_ROUTES); /* Collector BGP ID */ stream_put_in_addr(obuf, &bgp->router_id); /* View name */ if (bgp->name) { stream_putw(obuf, strlen(bgp->name)); stream_put(obuf, bgp->name, strlen(bgp->name)); } else { stream_putw(obuf, 0); } /* Peer count ( plus one extra internal peer ) */ stream_putw(obuf, listcount(bgp->peer) + 1); /* Populate fake peer at index 0, for locally originated routes */ /* Peer type (IPv4) */ stream_putc(obuf, TABLE_DUMP_V2_PEER_INDEX_TABLE_AS4 + TABLE_DUMP_V2_PEER_INDEX_TABLE_IP); /* Peer BGP ID (0.0.0.0) */ stream_putl(obuf, 0); /* Peer IP address (0.0.0.0) */ stream_putl(obuf, 0); /* Peer ASN (0) */ stream_putl(obuf, 0); /* Walk down all peers */ for (ALL_LIST_ELEMENTS_RO(bgp->peer, node, peer)) { /* Peer's type */ if (sockunion_family(&peer->su) == AF_INET) { stream_putc( obuf, TABLE_DUMP_V2_PEER_INDEX_TABLE_AS4 + TABLE_DUMP_V2_PEER_INDEX_TABLE_IP); } else if (sockunion_family(&peer->su) == AF_INET6) { stream_putc( obuf, TABLE_DUMP_V2_PEER_INDEX_TABLE_AS4 + TABLE_DUMP_V2_PEER_INDEX_TABLE_IP6); } /* Peer's BGP ID */ stream_put_in_addr(obuf, &peer->remote_id); /* Peer's IP address */ if (sockunion_family(&peer->su) == AF_INET) { stream_put_in_addr(obuf, &peer->su.sin.sin_addr); } else if (sockunion_family(&peer->su) == AF_INET6) { stream_write(obuf, (uint8_t *)&peer->su.sin6.sin6_addr, IPV6_MAX_BYTELEN); } /* Peer's AS number. */ /* Note that, as this is an AS4 compliant quagga, the RIB is * always AS4 */ stream_putl(obuf, peer->as); /* Store the peer number for this peer */ peer->table_dump_index = peerno; peerno++; } bgp_dump_set_size(obuf, MSG_TABLE_DUMP_V2); fwrite(STREAM_DATA(obuf), stream_get_endp(obuf), 1, bgp_dump_routes.fp); fflush(bgp_dump_routes.fp); } static struct bgp_path_info * bgp_dump_route_node_record(int afi, struct bgp_node *rn, struct bgp_path_info *path, unsigned int seq) { struct stream *obuf; size_t sizep; size_t endp; obuf = bgp_dump_obuf; stream_reset(obuf); /* MRT header */ if (afi == AFI_IP) bgp_dump_header(obuf, MSG_TABLE_DUMP_V2, TABLE_DUMP_V2_RIB_IPV4_UNICAST, BGP_DUMP_ROUTES); else if (afi == AFI_IP6) bgp_dump_header(obuf, MSG_TABLE_DUMP_V2, TABLE_DUMP_V2_RIB_IPV6_UNICAST, BGP_DUMP_ROUTES); /* Sequence number */ stream_putl(obuf, seq); /* Prefix length */ stream_putc(obuf, rn->p.prefixlen); /* Prefix */ if (afi == AFI_IP) { /* We'll dump only the useful bits (those not 0), but have to * align on 8 bits */ stream_write(obuf, (uint8_t *)&rn->p.u.prefix4, (rn->p.prefixlen + 7) / 8); } else if (afi == AFI_IP6) { /* We'll dump only the useful bits (those not 0), but have to * align on 8 bits */ stream_write(obuf, (uint8_t *)&rn->p.u.prefix6, (rn->p.prefixlen + 7) / 8); } /* Save where we are now, so we can overwride the entry count later */ sizep = stream_get_endp(obuf); /* Entry count */ uint16_t entry_count = 0; /* Entry count, note that this is overwritten later */ stream_putw(obuf, 0); endp = stream_get_endp(obuf); for (; path; path = path->next) { size_t cur_endp; /* Peer index */ stream_putw(obuf, path->peer->table_dump_index); /* Originated */ stream_putl(obuf, time(NULL) - (bgp_clock() - path->uptime)); /* Dump attribute. */ /* Skip prefix & AFI/SAFI for MP_NLRI */ bgp_dump_routes_attr(obuf, path->attr, &rn->p); cur_endp = stream_get_endp(obuf); if (cur_endp > BGP_MAX_PACKET_SIZE + BGP_DUMP_MSG_HEADER + BGP_DUMP_HEADER_SIZE) { stream_set_endp(obuf, endp); break; } entry_count++; endp = cur_endp; } /* Overwrite the entry count, now that we know the right number */ stream_putw_at(obuf, sizep, entry_count); bgp_dump_set_size(obuf, MSG_TABLE_DUMP_V2); fwrite(STREAM_DATA(obuf), stream_get_endp(obuf), 1, bgp_dump_routes.fp); return path; } /* Runs under child process. */ static unsigned int bgp_dump_routes_func(int afi, int first_run, unsigned int seq) { struct bgp_path_info *path; struct bgp_node *rn; struct bgp *bgp; struct bgp_table *table; bgp = bgp_get_default(); if (!bgp) return seq; if (bgp_dump_routes.fp == NULL) return seq; /* Note that bgp_dump_routes_index_table will do ipv4 and ipv6 peers, so this should only be done on the first call to bgp_dump_routes_func. ( this function will be called once for ipv4 and once for ipv6 ) */ if (first_run) bgp_dump_routes_index_table(bgp); /* Walk down each BGP route. */ table = bgp->rib[afi][SAFI_UNICAST]; for (rn = bgp_table_top(table); rn; rn = bgp_route_next(rn)) { path = bgp_node_get_bgp_path_info(rn); while (path) { path = bgp_dump_route_node_record(afi, rn, path, seq); seq++; } } fflush(bgp_dump_routes.fp); return seq; } static int bgp_dump_interval_func(struct thread *t) { struct bgp_dump *bgp_dump; bgp_dump = THREAD_ARG(t); bgp_dump->t_interval = NULL; /* Reschedule dump even if file couldn't be opened this time... */ if (bgp_dump_open_file(bgp_dump) != NULL) { /* In case of bgp_dump_routes, we need special route dump * function. */ if (bgp_dump->type == BGP_DUMP_ROUTES) { unsigned int seq = bgp_dump_routes_func(AFI_IP, 1, 0); bgp_dump_routes_func(AFI_IP6, 0, seq); /* Close the file now. For a RIB dump there's no point * in leaving * it open until the next scheduled dump starts. */ fclose(bgp_dump->fp); bgp_dump->fp = NULL; } } /* if interval is set reschedule */ if (bgp_dump->interval > 0) bgp_dump_interval_add(bgp_dump, bgp_dump->interval); return 0; } /* Dump common information. */ static void bgp_dump_common(struct stream *obuf, struct peer *peer, int forceas4) { char empty[16] = {0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0}; /* Source AS number and Destination AS number. */ if (forceas4 || CHECK_FLAG(peer->cap, PEER_CAP_AS4_RCV)) { stream_putl(obuf, peer->as); stream_putl(obuf, peer->local_as); } else { stream_putw(obuf, peer->as); stream_putw(obuf, peer->local_as); } if (peer->su.sa.sa_family == AF_INET) { stream_putw(obuf, peer->ifp ? peer->ifp->ifindex : 0); stream_putw(obuf, AFI_IP); stream_put(obuf, &peer->su.sin.sin_addr, IPV4_MAX_BYTELEN); if (peer->su_local) stream_put(obuf, &peer->su_local->sin.sin_addr, IPV4_MAX_BYTELEN); else stream_put(obuf, empty, IPV4_MAX_BYTELEN); } else if (peer->su.sa.sa_family == AF_INET6) { /* Interface Index and Address family. */ stream_putw(obuf, peer->ifp ? peer->ifp->ifindex : 0); stream_putw(obuf, AFI_IP6); /* Source IP Address and Destination IP Address. */ stream_put(obuf, &peer->su.sin6.sin6_addr, IPV6_MAX_BYTELEN); if (peer->su_local) stream_put(obuf, &peer->su_local->sin6.sin6_addr, IPV6_MAX_BYTELEN); else stream_put(obuf, empty, IPV6_MAX_BYTELEN); } } /* Dump BGP status change. */ void bgp_dump_state(struct peer *peer, int status_old, int status_new) { struct stream *obuf; /* If dump file pointer is disabled return immediately. */ if (bgp_dump_all.fp == NULL) return; /* Make dump stream. */ obuf = bgp_dump_obuf; stream_reset(obuf); bgp_dump_header(obuf, MSG_PROTOCOL_BGP4MP, BGP4MP_STATE_CHANGE_AS4, bgp_dump_all.type); bgp_dump_common(obuf, peer, 1); /* force this in as4speak*/ stream_putw(obuf, status_old); stream_putw(obuf, status_new); /* Set length. */ bgp_dump_set_size(obuf, MSG_PROTOCOL_BGP4MP); /* Write to the stream. */ fwrite(STREAM_DATA(obuf), stream_get_endp(obuf), 1, bgp_dump_all.fp); fflush(bgp_dump_all.fp); } static void bgp_dump_packet_func(struct bgp_dump *bgp_dump, struct peer *peer, struct stream *packet) { struct stream *obuf; /* If dump file pointer is disabled return immediately. */ if (bgp_dump->fp == NULL) return; /* Make dump stream. */ obuf = bgp_dump_obuf; stream_reset(obuf); /* Dump header and common part. */ if (CHECK_FLAG(peer->cap, PEER_CAP_AS4_RCV)) { bgp_dump_header(obuf, MSG_PROTOCOL_BGP4MP, BGP4MP_MESSAGE_AS4, bgp_dump->type); } else { bgp_dump_header(obuf, MSG_PROTOCOL_BGP4MP, BGP4MP_MESSAGE, bgp_dump->type); } bgp_dump_common(obuf, peer, 0); /* Packet contents. */ stream_put(obuf, STREAM_DATA(packet), stream_get_endp(packet)); /* Set length. */ bgp_dump_set_size(obuf, MSG_PROTOCOL_BGP4MP); /* Write to the stream. */ fwrite(STREAM_DATA(obuf), stream_get_endp(obuf), 1, bgp_dump->fp); fflush(bgp_dump->fp); } /* Called from bgp_packet.c when BGP packet is received. */ static int bgp_dump_packet(struct peer *peer, uint8_t type, bgp_size_t size, struct stream *packet) { /* bgp_dump_all. */ bgp_dump_packet_func(&bgp_dump_all, peer, packet); /* bgp_dump_updates. */ if (type == BGP_MSG_UPDATE) bgp_dump_packet_func(&bgp_dump_updates, peer, packet); return 0; } static unsigned int bgp_dump_parse_time(const char *str) { int i; int len; int seen_h; int seen_m; int time; unsigned int total; time = 0; total = 0; seen_h = 0; seen_m = 0; len = strlen(str); for (i = 0; i < len; i++) { if (isdigit((unsigned char)str[i])) { time *= 10; time += str[i] - '0'; } else if (str[i] == 'H' || str[i] == 'h') { if (seen_h) return 0; if (seen_m) return 0; total += time * 60 * 60; time = 0; seen_h = 1; } else if (str[i] == 'M' || str[i] == 'm') { if (seen_m) return 0; total += time * 60; time = 0; seen_m = 1; } else return 0; } return total + time; } static int bgp_dump_set(struct vty *vty, struct bgp_dump *bgp_dump, enum bgp_dump_type type, const char *path, const char *interval_str) { unsigned int interval; /* Don't schedule duplicate dumps if the dump command is given twice */ if (bgp_dump->filename && strcmp(path, bgp_dump->filename) == 0 && type == bgp_dump->type) { if (interval_str) { if (bgp_dump->interval_str && strcmp(bgp_dump->interval_str, interval_str) == 0) return CMD_SUCCESS; } else { if (!bgp_dump->interval_str) return CMD_SUCCESS; } } /* Removing previous config */ bgp_dump_unset(bgp_dump); if (interval_str) { /* Check interval string. */ interval = bgp_dump_parse_time(interval_str); if (interval == 0) { vty_out(vty, "Malformed interval string\n"); return CMD_WARNING_CONFIG_FAILED; } /* Setting interval string */ bgp_dump->interval_str = XSTRDUP(MTYPE_BGP_DUMP_STR, interval_str); } else { interval = 0; } /* Set type. */ bgp_dump->type = type; /* Set interval */ bgp_dump->interval = interval; /* Set file name. */ bgp_dump->filename = XSTRDUP(MTYPE_BGP_DUMP_STR, path); /* Create interval thread. */ bgp_dump_interval_add(bgp_dump, interval); /* This should be called when interval is expired. */ bgp_dump_open_file(bgp_dump); return CMD_SUCCESS; } static int bgp_dump_unset(struct bgp_dump *bgp_dump) { /* Removing file name. */ if (bgp_dump->filename) { XFREE(MTYPE_BGP_DUMP_STR, bgp_dump->filename); bgp_dump->filename = NULL; } /* Closing file. */ if (bgp_dump->fp) { fclose(bgp_dump->fp); bgp_dump->fp = NULL; } /* Removing interval thread. */ if (bgp_dump->t_interval) { thread_cancel(bgp_dump->t_interval); bgp_dump->t_interval = NULL; } bgp_dump->interval = 0; /* Removing interval string. */ if (bgp_dump->interval_str) { XFREE(MTYPE_BGP_DUMP_STR, bgp_dump->interval_str); bgp_dump->interval_str = NULL; } return CMD_SUCCESS; } DEFUN (dump_bgp_all, dump_bgp_all_cmd, "dump bgp PATH [INTERVAL]", "Dump packet\n" "BGP packet dump\n" "Dump all BGP packets\nDump all BGP packets (Extended Timestamp Header)\n" "Dump BGP updates only\nDump BGP updates only (Extended Timestamp Header)\n" "Dump whole BGP routing table\n" "Output filename\n" "Interval of output\n") { int idx_dump_routes = 2; int idx_path = 3; int idx_interval = 4; int bgp_dump_type = 0; const char *interval = NULL; struct bgp_dump *bgp_dump_struct = NULL; const struct bgp_dump_type_map *map = NULL; for (map = bgp_dump_type_map; map->str; map++) if (strmatch(argv[idx_dump_routes]->text, map->str)) bgp_dump_type = map->type; switch (bgp_dump_type) { case BGP_DUMP_ALL: case BGP_DUMP_ALL_ET: bgp_dump_struct = &bgp_dump_all; break; case BGP_DUMP_UPDATES: case BGP_DUMP_UPDATES_ET: bgp_dump_struct = &bgp_dump_updates; break; case BGP_DUMP_ROUTES: default: bgp_dump_struct = &bgp_dump_routes; break; } /* When an interval is given */ if (argc == idx_interval + 1) interval = argv[idx_interval]->arg; return bgp_dump_set(vty, bgp_dump_struct, bgp_dump_type, argv[idx_path]->arg, interval); } DEFUN (no_dump_bgp_all, no_dump_bgp_all_cmd, "no dump bgp [PATH [INTERVAL]]", NO_STR "Stop dump packet\n" "Stop BGP packet dump\n" "Stop dump process all\n" "Stop dump process all-et\n" "Stop dump process updates\n" "Stop dump process updates-et\n" "Stop dump process route-mrt\n" "Output filename\n" "Interval of output\n") { int idx_dump_routes = 3; int bgp_dump_type = 0; const struct bgp_dump_type_map *map = NULL; struct bgp_dump *bgp_dump_struct = NULL; for (map = bgp_dump_type_map; map->str; map++) if (strmatch(argv[idx_dump_routes]->text, map->str)) bgp_dump_type = map->type; switch (bgp_dump_type) { case BGP_DUMP_ALL: case BGP_DUMP_ALL_ET: bgp_dump_struct = &bgp_dump_all; break; case BGP_DUMP_UPDATES: case BGP_DUMP_UPDATES_ET: bgp_dump_struct = &bgp_dump_updates; break; case BGP_DUMP_ROUTES: default: bgp_dump_struct = &bgp_dump_routes; break; } return bgp_dump_unset(bgp_dump_struct); } /* BGP node structure. */ static struct cmd_node bgp_dump_node = {DUMP_NODE, "", 1}; #if 0 char * config_time2str (unsigned int interval) { static char buf[BUFSIZ]; buf[0] = '\0'; if (interval / 3600) { sprintf (buf, "%dh", interval / 3600); interval %= 3600; } if (interval / 60) { sprintf (buf + strlen (buf), "%dm", interval /60); interval %= 60; } if (interval) { sprintf (buf + strlen (buf), "%d", interval); } return buf; } #endif static int config_write_bgp_dump(struct vty *vty) { if (bgp_dump_all.filename) { const char *type_str = "all"; if (bgp_dump_all.type == BGP_DUMP_ALL_ET) type_str = "all-et"; if (bgp_dump_all.interval_str) vty_out(vty, "dump bgp %s %s %s\n", type_str, bgp_dump_all.filename, bgp_dump_all.interval_str); else vty_out(vty, "dump bgp %s %s\n", type_str, bgp_dump_all.filename); } if (bgp_dump_updates.filename) { const char *type_str = "updates"; if (bgp_dump_updates.type == BGP_DUMP_UPDATES_ET) type_str = "updates-et"; if (bgp_dump_updates.interval_str) vty_out(vty, "dump bgp %s %s %s\n", type_str, bgp_dump_updates.filename, bgp_dump_updates.interval_str); else vty_out(vty, "dump bgp %s %s\n", type_str, bgp_dump_updates.filename); } if (bgp_dump_routes.filename) { if (bgp_dump_routes.interval_str) vty_out(vty, "dump bgp routes-mrt %s %s\n", bgp_dump_routes.filename, bgp_dump_routes.interval_str); else vty_out(vty, "dump bgp routes-mrt %s\n", bgp_dump_routes.filename); } return 0; } /* Initialize BGP packet dump functionality. */ void bgp_dump_init(void) { memset(&bgp_dump_all, 0, sizeof(struct bgp_dump)); memset(&bgp_dump_updates, 0, sizeof(struct bgp_dump)); memset(&bgp_dump_routes, 0, sizeof(struct bgp_dump)); bgp_dump_obuf = stream_new((BGP_MAX_PACKET_SIZE << 1) + BGP_DUMP_MSG_HEADER + BGP_DUMP_HEADER_SIZE); install_node(&bgp_dump_node, config_write_bgp_dump); install_element(CONFIG_NODE, &dump_bgp_all_cmd); install_element(CONFIG_NODE, &no_dump_bgp_all_cmd); hook_register(bgp_packet_dump, bgp_dump_packet); } void bgp_dump_finish(void) { bgp_dump_unset(&bgp_dump_all); bgp_dump_unset(&bgp_dump_updates); bgp_dump_unset(&bgp_dump_routes); stream_free(bgp_dump_obuf); bgp_dump_obuf = NULL; hook_unregister(bgp_packet_dump, bgp_dump_packet); } frr-7.2.1/bgpd/bgp_dump.h0000644000000000000000000000357513610377563012110 00000000000000/* BGP dump routine. * Copyright (C) 1999 Kunihiro Ishiguro * * This file is part of GNU Zebra. * * GNU Zebra is free software; you can redistribute it and/or modify it * under the terms of the GNU General Public License as published by the * Free Software Foundation; either version 2, or (at your option) any * later version. * * GNU Zebra 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; see the file COPYING; if not, write to the Free Software * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA */ #ifndef _QUAGGA_BGP_DUMP_H #define _QUAGGA_BGP_DUMP_H /* MRT compatible packet dump values. */ /* type value */ #define MSG_PROTOCOL_BGP4MP 16 #define MSG_PROTOCOL_BGP4MP_ET 17 /* subtype value */ #define BGP4MP_STATE_CHANGE 0 #define BGP4MP_MESSAGE 1 #define BGP4MP_ENTRY 2 #define BGP4MP_SNAPSHOT 3 #define BGP4MP_MESSAGE_AS4 4 #define BGP4MP_STATE_CHANGE_AS4 5 #define BGP_DUMP_HEADER_SIZE 12 #define BGP_DUMP_MSG_HEADER 40 #define TABLE_DUMP_V2_PEER_INDEX_TABLE 1 #define TABLE_DUMP_V2_RIB_IPV4_UNICAST 2 #define TABLE_DUMP_V2_RIB_IPV4_MULTICAST 3 #define TABLE_DUMP_V2_RIB_IPV6_UNICAST 4 #define TABLE_DUMP_V2_RIB_IPV6_MULTICAST 5 #define TABLE_DUMP_V2_RIB_GENERIC 6 #define TABLE_DUMP_V2_PEER_INDEX_TABLE_IP 0 #define TABLE_DUMP_V2_PEER_INDEX_TABLE_IP6 1 #define TABLE_DUMP_V2_PEER_INDEX_TABLE_AS2 0 #define TABLE_DUMP_V2_PEER_INDEX_TABLE_AS4 2 extern void bgp_dump_init(void); extern void bgp_dump_finish(void); extern void bgp_dump_state(struct peer *, int, int); #endif /* _QUAGGA_BGP_DUMP_H */ frr-7.2.1/bgpd/bgp_ecommunity.c0000644000000000000000000007014713610377563013326 00000000000000/* BGP Extended Communities Attribute * Copyright (C) 2000 Kunihiro Ishiguro * * This file is part of GNU Zebra. * * GNU Zebra is free software; you can redistribute it and/or modify it * under the terms of the GNU General Public License as published by the * Free Software Foundation; either version 2, or (at your option) any * later version. * * GNU Zebra 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; see the file COPYING; if not, write to the Free Software * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA */ #include #include "hash.h" #include "memory.h" #include "prefix.h" #include "command.h" #include "queue.h" #include "filter.h" #include "jhash.h" #include "stream.h" #include "bgpd/bgpd.h" #include "bgpd/bgp_ecommunity.h" #include "bgpd/bgp_lcommunity.h" #include "bgpd/bgp_aspath.h" #include "bgpd/bgp_flowspec_private.h" #include "bgpd/bgp_pbr.h" /* struct used to dump the rate contained in FS set traffic-rate EC */ union traffic_rate { float rate_float; uint8_t rate_byte[4]; }; /* Hash of community attribute. */ static struct hash *ecomhash; /* Allocate a new ecommunities. */ struct ecommunity *ecommunity_new(void) { return XCALLOC(MTYPE_ECOMMUNITY, sizeof(struct ecommunity)); } void ecommunity_strfree(char **s) { XFREE(MTYPE_ECOMMUNITY_STR, *s); } /* Allocate ecommunities. */ void ecommunity_free(struct ecommunity **ecom) { XFREE(MTYPE_ECOMMUNITY_VAL, (*ecom)->val); XFREE(MTYPE_ECOMMUNITY_STR, (*ecom)->str); XFREE(MTYPE_ECOMMUNITY, *ecom); } static void ecommunity_hash_free(struct ecommunity *ecom) { ecommunity_free(&ecom); } /* Add a new Extended Communities value to Extended Communities Attribute structure. When the value is already exists in the structure, we don't add the value. Newly added value is sorted by numerical order. When the value is added to the structure return 1 else return 0. */ int ecommunity_add_val(struct ecommunity *ecom, struct ecommunity_val *eval) { int c; /* When this is fist value, just add it. */ if (ecom->val == NULL) { ecom->size = 1; ecom->val = XCALLOC(MTYPE_ECOMMUNITY_VAL, ECOMMUNITY_SIZE); memcpy(ecom->val, eval->val, ECOMMUNITY_SIZE); return 1; } /* If the value already exists in the structure return 0. */ c = 0; for (uint8_t *p = ecom->val; c < ecom->size; p += ECOMMUNITY_SIZE, c++) { int ret = memcmp(p, eval->val, ECOMMUNITY_SIZE); if (ret == 0) return 0; else if (ret > 0) break; } /* Add the value to the structure with numerical sorting. */ ecom->size++; ecom->val = XREALLOC(MTYPE_ECOMMUNITY_VAL, ecom->val, ecom->size * ECOMMUNITY_SIZE); memmove(ecom->val + ((c + 1) * ECOMMUNITY_SIZE), ecom->val + (c * ECOMMUNITY_SIZE), (ecom->size - 1 - c) * ECOMMUNITY_SIZE); memcpy(ecom->val + (c * ECOMMUNITY_SIZE), eval->val, ECOMMUNITY_SIZE); return 1; } /* This function takes pointer to Extended Communites strucutre then create a new Extended Communities structure by uniq and sort each Extended Communities value. */ struct ecommunity *ecommunity_uniq_sort(struct ecommunity *ecom) { int i; struct ecommunity *new; struct ecommunity_val *eval; if (!ecom) return NULL; new = ecommunity_new(); for (i = 0; i < ecom->size; i++) { eval = (struct ecommunity_val *)(ecom->val + (i * ECOMMUNITY_SIZE)); ecommunity_add_val(new, eval); } return new; } /* Parse Extended Communites Attribute in BGP packet. */ struct ecommunity *ecommunity_parse(uint8_t *pnt, unsigned short length) { struct ecommunity tmp; struct ecommunity *new; /* Length check. */ if (length % ECOMMUNITY_SIZE) return NULL; /* Prepare tmporary structure for making a new Extended Communities Attribute. */ tmp.size = length / ECOMMUNITY_SIZE; tmp.val = pnt; /* Create a new Extended Communities Attribute by uniq and sort each Extended Communities value */ new = ecommunity_uniq_sort(&tmp); return ecommunity_intern(new); } /* Duplicate the Extended Communities Attribute structure. */ struct ecommunity *ecommunity_dup(struct ecommunity *ecom) { struct ecommunity *new; new = XCALLOC(MTYPE_ECOMMUNITY, sizeof(struct ecommunity)); new->size = ecom->size; if (new->size) { new->val = XMALLOC(MTYPE_ECOMMUNITY_VAL, ecom->size * ECOMMUNITY_SIZE); memcpy(new->val, ecom->val, ecom->size * ECOMMUNITY_SIZE); } else new->val = NULL; return new; } /* Retrun string representation of communities attribute. */ char *ecommunity_str(struct ecommunity *ecom) { if (!ecom->str) ecom->str = ecommunity_ecom2str(ecom, ECOMMUNITY_FORMAT_DISPLAY, 0); return ecom->str; } /* Merge two Extended Communities Attribute structure. */ struct ecommunity *ecommunity_merge(struct ecommunity *ecom1, struct ecommunity *ecom2) { if (ecom1->val) ecom1->val = XREALLOC(MTYPE_ECOMMUNITY_VAL, ecom1->val, (ecom1->size + ecom2->size) * ECOMMUNITY_SIZE); else ecom1->val = XMALLOC(MTYPE_ECOMMUNITY_VAL, (ecom1->size + ecom2->size) * ECOMMUNITY_SIZE); memcpy(ecom1->val + (ecom1->size * ECOMMUNITY_SIZE), ecom2->val, ecom2->size * ECOMMUNITY_SIZE); ecom1->size += ecom2->size; return ecom1; } /* Intern Extended Communities Attribute. */ struct ecommunity *ecommunity_intern(struct ecommunity *ecom) { struct ecommunity *find; assert(ecom->refcnt == 0); find = (struct ecommunity *)hash_get(ecomhash, ecom, hash_alloc_intern); if (find != ecom) ecommunity_free(&ecom); find->refcnt++; if (!find->str) find->str = ecommunity_ecom2str(find, ECOMMUNITY_FORMAT_DISPLAY, 0); return find; } /* Unintern Extended Communities Attribute. */ void ecommunity_unintern(struct ecommunity **ecom) { struct ecommunity *ret; if ((*ecom)->refcnt) (*ecom)->refcnt--; /* Pull off from hash. */ if ((*ecom)->refcnt == 0) { /* Extended community must be in the hash. */ ret = (struct ecommunity *)hash_release(ecomhash, *ecom); assert(ret != NULL); ecommunity_free(ecom); } } /* Utinity function to make hash key. */ unsigned int ecommunity_hash_make(const void *arg) { const struct ecommunity *ecom = arg; int size = ecom->size * ECOMMUNITY_SIZE; return jhash(ecom->val, size, 0x564321ab); } /* Compare two Extended Communities Attribute structure. */ bool ecommunity_cmp(const void *arg1, const void *arg2) { const struct ecommunity *ecom1 = arg1; const struct ecommunity *ecom2 = arg2; if (ecom1 == NULL && ecom2 == NULL) return true; if (ecom1 == NULL || ecom2 == NULL) return false; return (ecom1->size == ecom2->size && memcmp(ecom1->val, ecom2->val, ecom1->size * ECOMMUNITY_SIZE) == 0); } /* Initialize Extended Comminities related hash. */ void ecommunity_init(void) { ecomhash = hash_create(ecommunity_hash_make, ecommunity_cmp, "BGP ecommunity hash"); } void ecommunity_finish(void) { hash_clean(ecomhash, (void (*)(void *))ecommunity_hash_free); hash_free(ecomhash); ecomhash = NULL; } /* Extended Communities token enum. */ enum ecommunity_token { ecommunity_token_unknown = 0, ecommunity_token_rt, ecommunity_token_soo, ecommunity_token_val, }; /* * Encode BGP extended community from passed values. Supports types * defined in RFC 4360 and well-known sub-types. */ static int ecommunity_encode(uint8_t type, uint8_t sub_type, int trans, as_t as, struct in_addr ip, uint32_t val, struct ecommunity_val *eval) { assert(eval); if (type == ECOMMUNITY_ENCODE_AS) { if (as > BGP_AS_MAX) return -1; } else if (type == ECOMMUNITY_ENCODE_IP || type == ECOMMUNITY_ENCODE_AS4) { if (val > UINT16_MAX) return -1; } /* Fill in the values. */ eval->val[0] = type; if (!trans) eval->val[0] |= ECOMMUNITY_FLAG_NON_TRANSITIVE; eval->val[1] = sub_type; if (type == ECOMMUNITY_ENCODE_AS) { eval->val[2] = (as >> 8) & 0xff; eval->val[3] = as & 0xff; eval->val[4] = (val >> 24) & 0xff; eval->val[5] = (val >> 16) & 0xff; eval->val[6] = (val >> 8) & 0xff; eval->val[7] = val & 0xff; } else if (type == ECOMMUNITY_ENCODE_IP) { memcpy(&eval->val[2], &ip, sizeof(struct in_addr)); eval->val[6] = (val >> 8) & 0xff; eval->val[7] = val & 0xff; } else { eval->val[2] = (as >> 24) & 0xff; eval->val[3] = (as >> 16) & 0xff; eval->val[4] = (as >> 8) & 0xff; eval->val[5] = as & 0xff; eval->val[6] = (val >> 8) & 0xff; eval->val[7] = val & 0xff; } return 0; } /* Get next Extended Communities token from the string. */ static const char *ecommunity_gettoken(const char *str, struct ecommunity_val *eval, enum ecommunity_token *token) { int ret; int dot = 0; int digit = 0; int separator = 0; const char *p = str; char *endptr; struct in_addr ip; as_t as = 0; uint32_t val = 0; uint8_t ecomm_type; char buf[INET_ADDRSTRLEN + 1]; /* Skip white space. */ while (isspace((unsigned char)*p)) { p++; str++; } /* Check the end of the line. */ if (*p == '\0') return NULL; /* "rt" and "soo" keyword parse. */ if (!isdigit((unsigned char)*p)) { /* "rt" match check. */ if (tolower((unsigned char)*p) == 'r') { p++; if (tolower((unsigned char)*p) == 't') { p++; *token = ecommunity_token_rt; return p; } if (isspace((unsigned char)*p) || *p == '\0') { *token = ecommunity_token_rt; return p; } goto error; } /* "soo" match check. */ else if (tolower((unsigned char)*p) == 's') { p++; if (tolower((unsigned char)*p) == 'o') { p++; if (tolower((unsigned char)*p) == 'o') { p++; *token = ecommunity_token_soo; return p; } if (isspace((unsigned char)*p) || *p == '\0') { *token = ecommunity_token_soo; return p; } goto error; } if (isspace((unsigned char)*p) || *p == '\0') { *token = ecommunity_token_soo; return p; } goto error; } goto error; } /* What a mess, there are several possibilities: * * a) A.B.C.D:MN * b) EF:OPQR * c) GHJK:MN * * A.B.C.D: Four Byte IP * EF: Two byte ASN * GHJK: Four-byte ASN * MN: Two byte value * OPQR: Four byte value * */ while (isdigit((unsigned char)*p) || *p == ':' || *p == '.') { if (*p == ':') { if (separator) goto error; separator = 1; digit = 0; if ((p - str) > INET_ADDRSTRLEN) goto error; memset(buf, 0, INET_ADDRSTRLEN + 1); memcpy(buf, str, p - str); if (dot) { /* Parsing A.B.C.D in: * A.B.C.D:MN */ ret = inet_aton(buf, &ip); if (ret == 0) goto error; } else { /* ASN */ as = strtoul(buf, &endptr, 10); if (*endptr != '\0' || as == BGP_AS4_MAX) goto error; } } else if (*p == '.') { if (separator) goto error; dot++; if (dot > 4) goto error; } else { digit = 1; /* We're past the IP/ASN part */ if (separator) { val *= 10; val += (*p - '0'); } } p++; } /* Low digit part must be there. */ if (!digit || !separator) goto error; /* Encode result into extended community. */ if (dot) ecomm_type = ECOMMUNITY_ENCODE_IP; else if (as > BGP_AS_MAX) ecomm_type = ECOMMUNITY_ENCODE_AS4; else ecomm_type = ECOMMUNITY_ENCODE_AS; if (ecommunity_encode(ecomm_type, 0, 1, as, ip, val, eval)) goto error; *token = ecommunity_token_val; return p; error: *token = ecommunity_token_unknown; return p; } /* Convert string to extended community attribute. When type is already known, please specify both str and type. str should not include keyword such as "rt" and "soo". Type is ECOMMUNITY_ROUTE_TARGET or ECOMMUNITY_SITE_ORIGIN. keyword_included should be zero. For example route-map's "set extcommunity" command case: "rt 100:1 100:2 100:3" -> str = "100:1 100:2 100:3" type = ECOMMUNITY_ROUTE_TARGET keyword_included = 0 "soo 100:1" -> str = "100:1" type = ECOMMUNITY_SITE_ORIGIN keyword_included = 0 When string includes keyword for each extended community value. Please specify keyword_included as non-zero value. For example standard extcommunity-list case: "rt 100:1 rt 100:2 soo 100:1" -> str = "rt 100:1 rt 100:2 soo 100:1" type = 0 keyword_include = 1 */ struct ecommunity *ecommunity_str2com(const char *str, int type, int keyword_included) { struct ecommunity *ecom = NULL; enum ecommunity_token token = ecommunity_token_unknown; struct ecommunity_val eval; int keyword = 0; while ((str = ecommunity_gettoken(str, &eval, &token))) { switch (token) { case ecommunity_token_rt: case ecommunity_token_soo: if (!keyword_included || keyword) { if (ecom) ecommunity_free(&ecom); return NULL; } keyword = 1; if (token == ecommunity_token_rt) { type = ECOMMUNITY_ROUTE_TARGET; } if (token == ecommunity_token_soo) { type = ECOMMUNITY_SITE_ORIGIN; } break; case ecommunity_token_val: if (keyword_included) { if (!keyword) { if (ecom) ecommunity_free(&ecom); return NULL; } keyword = 0; } if (ecom == NULL) ecom = ecommunity_new(); eval.val[1] = type; ecommunity_add_val(ecom, &eval); break; case ecommunity_token_unknown: default: if (ecom) ecommunity_free(&ecom); return NULL; } } return ecom; } static int ecommunity_rt_soo_str(char *buf, size_t bufsz, uint8_t *pnt, int type, int sub_type, int format) { int len = 0; const char *prefix; /* For parse Extended Community attribute tupple. */ struct ecommunity_as eas; struct ecommunity_ip eip; /* Determine prefix for string, if any. */ switch (format) { case ECOMMUNITY_FORMAT_COMMUNITY_LIST: prefix = (sub_type == ECOMMUNITY_ROUTE_TARGET ? "rt " : "soo "); break; case ECOMMUNITY_FORMAT_DISPLAY: prefix = (sub_type == ECOMMUNITY_ROUTE_TARGET ? "RT:" : "SoO:"); break; case ECOMMUNITY_FORMAT_ROUTE_MAP: prefix = ""; break; default: prefix = ""; break; } /* Put string into buffer. */ if (type == ECOMMUNITY_ENCODE_AS4) { pnt = ptr_get_be32(pnt, &eas.as); eas.val = (*pnt++ << 8); eas.val |= (*pnt++); len = snprintf(buf, bufsz, "%s%u:%u", prefix, eas.as, eas.val); } else if (type == ECOMMUNITY_ENCODE_AS) { eas.as = (*pnt++ << 8); eas.as |= (*pnt++); pnt = ptr_get_be32(pnt, &eas.val); len = snprintf(buf, bufsz, "%s%u:%u", prefix, eas.as, eas.val); } else if (type == ECOMMUNITY_ENCODE_IP) { memcpy(&eip.ip, pnt, 4); pnt += 4; eip.val = (*pnt++ << 8); eip.val |= (*pnt++); len = snprintf(buf, bufsz, "%s%s:%u", prefix, inet_ntoa(eip.ip), eip.val); } /* consume value */ (void)pnt; return len; } /* Convert extended community attribute to string. Due to historical reason of industry standard implementation, there are three types of format. route-map set extcommunity format "rt 100:1 100:2" "soo 100:3" extcommunity-list "rt 100:1 rt 100:2 soo 100:3" "show [ip] bgp" and extcommunity-list regular expression matching "RT:100:1 RT:100:2 SoO:100:3" For each formath please use below definition for format: ECOMMUNITY_FORMAT_ROUTE_MAP ECOMMUNITY_FORMAT_COMMUNITY_LIST ECOMMUNITY_FORMAT_DISPLAY Filter is added to display only ECOMMUNITY_ROUTE_TARGET in some cases. 0 value displays all */ char *ecommunity_ecom2str(struct ecommunity *ecom, int format, int filter) { int i; uint8_t *pnt; uint8_t type = 0; uint8_t sub_type = 0; #define ECOMMUNITY_STRLEN 64 int str_size; char *str_buf; if (ecom->size == 0) return XCALLOC(MTYPE_ECOMMUNITY_STR, 1); /* ecom strlen + space + null term */ str_size = (ecom->size * (ECOMMUNITY_STRLEN + 1)) + 1; str_buf = XCALLOC(MTYPE_ECOMMUNITY_STR, str_size); char encbuf[128]; for (i = 0; i < ecom->size; i++) { int unk_ecom = 0; memset(encbuf, 0x00, sizeof(encbuf)); /* Space between each value. */ if (i > 0) strlcat(str_buf, " ", str_size); /* Retrieve value field */ pnt = ecom->val + (i * 8); /* High-order octet is the type */ type = *pnt++; if (type == ECOMMUNITY_ENCODE_AS || type == ECOMMUNITY_ENCODE_IP || type == ECOMMUNITY_ENCODE_AS4) { /* Low-order octet of type. */ sub_type = *pnt++; if (sub_type != ECOMMUNITY_ROUTE_TARGET && sub_type != ECOMMUNITY_SITE_ORIGIN) { if (sub_type == ECOMMUNITY_FLOWSPEC_REDIRECT_IPV4 && type == ECOMMUNITY_ENCODE_IP) { struct in_addr *ipv4 = (struct in_addr *)pnt; char ipv4str[INET_ADDRSTRLEN]; inet_ntop(AF_INET, ipv4, ipv4str, INET_ADDRSTRLEN); snprintf(encbuf, sizeof(encbuf), "NH:%s:%d", ipv4str, pnt[5]); } else unk_ecom = 1; } else { ecommunity_rt_soo_str(encbuf, sizeof(encbuf), pnt, type, sub_type, format); } } else if (type == ECOMMUNITY_ENCODE_OPAQUE) { if (filter == ECOMMUNITY_ROUTE_TARGET) continue; if (*pnt == ECOMMUNITY_OPAQUE_SUBTYPE_ENCAP) { uint16_t tunneltype; memcpy(&tunneltype, pnt + 5, 2); tunneltype = ntohs(tunneltype); snprintf(encbuf, sizeof(encbuf), "ET:%d", tunneltype); } else if (*pnt == ECOMMUNITY_EVPN_SUBTYPE_DEF_GW) { strlcpy(encbuf, "Default Gateway", sizeof(encbuf)); } else { unk_ecom = 1; } } else if (type == ECOMMUNITY_ENCODE_EVPN) { if (filter == ECOMMUNITY_ROUTE_TARGET) continue; if (*pnt == ECOMMUNITY_EVPN_SUBTYPE_ROUTERMAC) { struct ethaddr rmac; pnt++; memcpy(&rmac, pnt, ETH_ALEN); snprintf(encbuf, sizeof(encbuf), "Rmac:%02x:%02x:%02x:%02x:%02x:%02x", (uint8_t)rmac.octet[0], (uint8_t)rmac.octet[1], (uint8_t)rmac.octet[2], (uint8_t)rmac.octet[3], (uint8_t)rmac.octet[4], (uint8_t)rmac.octet[5]); } else if (*pnt == ECOMMUNITY_EVPN_SUBTYPE_MACMOBILITY) { uint32_t seqnum; uint8_t flags = *++pnt; memcpy(&seqnum, pnt + 2, 4); seqnum = ntohl(seqnum); snprintf(encbuf, sizeof(encbuf), "MM:%u", seqnum); if (CHECK_FLAG( flags, ECOMMUNITY_EVPN_SUBTYPE_MACMOBILITY_FLAG_STICKY)) strlcat(encbuf, ", sticky MAC", sizeof(encbuf)); } else if (*pnt == ECOMMUNITY_EVPN_SUBTYPE_ND) { uint8_t flags = *++pnt; if (CHECK_FLAG( flags, ECOMMUNITY_EVPN_SUBTYPE_ND_ROUTER_FLAG)) strlcpy(encbuf, "ND:Router Flag", sizeof(encbuf)); } else unk_ecom = 1; } else if (type == ECOMMUNITY_ENCODE_REDIRECT_IP_NH) { sub_type = *pnt++; if (sub_type == ECOMMUNITY_REDIRECT_IP_NH) { snprintf(encbuf, sizeof(encbuf), "FS:redirect IP 0x%x", *(pnt + 5)); } else unk_ecom = 1; } else if (type == ECOMMUNITY_ENCODE_TRANS_EXP || type == ECOMMUNITY_EXTENDED_COMMUNITY_PART_2 || type == ECOMMUNITY_EXTENDED_COMMUNITY_PART_3) { sub_type = *pnt++; if (sub_type == ECOMMUNITY_REDIRECT_VRF) { char buf[16] = {}; ecommunity_rt_soo_str( buf, sizeof(buf), (uint8_t *)pnt, type & ~ECOMMUNITY_ENCODE_TRANS_EXP, ECOMMUNITY_ROUTE_TARGET, ECOMMUNITY_FORMAT_DISPLAY); snprintf(encbuf, sizeof(encbuf), "FS:redirect VRF %s", buf); } else if (type != ECOMMUNITY_ENCODE_TRANS_EXP) unk_ecom = 1; else if (sub_type == ECOMMUNITY_TRAFFIC_ACTION) { char action[64]; if (*(pnt+3) == 1 << FLOWSPEC_TRAFFIC_ACTION_TERMINAL) strlcpy(action, "terminate (apply)", sizeof(action)); else strlcpy(action, "eval stops", sizeof(action)); if (*(pnt+3) == 1 << FLOWSPEC_TRAFFIC_ACTION_SAMPLE) strlcat(action, ", sample", sizeof(action)); snprintf(encbuf, sizeof(encbuf), "FS:action %s", action); } else if (sub_type == ECOMMUNITY_TRAFFIC_RATE) { union traffic_rate data; data.rate_byte[3] = *(pnt+2); data.rate_byte[2] = *(pnt+3); data.rate_byte[1] = *(pnt+4); data.rate_byte[0] = *(pnt+5); snprintf(encbuf, sizeof(encbuf), "FS:rate %f", data.rate_float); } else if (sub_type == ECOMMUNITY_TRAFFIC_MARKING) { snprintf(encbuf, sizeof(encbuf), "FS:marking %u", *(pnt + 5)); } else if (*pnt == ECOMMUNITY_EVPN_SUBTYPE_ES_IMPORT_RT) { struct ethaddr mac; memcpy(&mac, pnt, ETH_ALEN); snprintf( encbuf, sizeof(encbuf), "ES-Import-Rt:%02x:%02x:%02x:%02x:%02x:%02x", (uint8_t)mac.octet[0], (uint8_t)mac.octet[1], (uint8_t)mac.octet[2], (uint8_t)mac.octet[3], (uint8_t)mac.octet[4], (uint8_t)mac.octet[5]); } else unk_ecom = 1; } else { sub_type = *pnt++; unk_ecom = 1; } if (unk_ecom) snprintf(encbuf, sizeof(encbuf), "UNK:%d, %d", type, sub_type); int r = strlcat(str_buf, encbuf, str_size); assert(r < str_size); } return str_buf; } int ecommunity_match(const struct ecommunity *ecom1, const struct ecommunity *ecom2) { int i = 0; int j = 0; if (ecom1 == NULL && ecom2 == NULL) return 1; if (ecom1 == NULL || ecom2 == NULL) return 0; if (ecom1->size < ecom2->size) return 0; /* Every community on com2 needs to be on com1 for this to match */ while (i < ecom1->size && j < ecom2->size) { if (memcmp(ecom1->val + i * ECOMMUNITY_SIZE, ecom2->val + j * ECOMMUNITY_SIZE, ECOMMUNITY_SIZE) == 0) j++; i++; } if (j == ecom2->size) return 1; else return 0; } /* return first occurence of type */ extern struct ecommunity_val *ecommunity_lookup(const struct ecommunity *ecom, uint8_t type, uint8_t subtype) { uint8_t *p; int c; /* If the value already exists in the structure return 0. */ c = 0; for (p = ecom->val; c < ecom->size; p += ECOMMUNITY_SIZE, c++) { if (p == NULL) { continue; } if (p[0] == type && p[1] == subtype) return (struct ecommunity_val *)p; } return NULL; } /* remove ext. community matching type and subtype * return 1 on success ( removed ), 0 otherwise (not present) */ extern int ecommunity_strip(struct ecommunity *ecom, uint8_t type, uint8_t subtype) { uint8_t *p; int c, found = 0; /* When this is fist value, just add it. */ if (ecom == NULL || ecom->val == NULL) { return 0; } /* If the value already exists in the structure return 0. */ c = 0; for (p = ecom->val; c < ecom->size; p += ECOMMUNITY_SIZE, c++) { if (p[0] == type && p[1] == subtype) { found = 1; break; } } if (found == 0) return 0; /* Strip The selected value */ ecom->size--; /* size is reduced. no memmove to do */ p = XMALLOC(MTYPE_ECOMMUNITY_VAL, ecom->size * ECOMMUNITY_SIZE); if (c != 0) memcpy(p, ecom->val, c * ECOMMUNITY_SIZE); if ((ecom->size - c) != 0) memcpy(p + (c)*ECOMMUNITY_SIZE, ecom->val + (c + 1) * ECOMMUNITY_SIZE, (ecom->size - c) * ECOMMUNITY_SIZE); /* shift last ecommunities */ XFREE(MTYPE_ECOMMUNITY, ecom->val); ecom->val = p; return 1; } /* * Remove specified extended community value from extended community. * Returns 1 if value was present (and hence, removed), 0 otherwise. */ int ecommunity_del_val(struct ecommunity *ecom, struct ecommunity_val *eval) { uint8_t *p; int c, found = 0; /* Make sure specified value exists. */ if (ecom == NULL || ecom->val == NULL) return 0; c = 0; for (p = ecom->val; c < ecom->size; p += ECOMMUNITY_SIZE, c++) { if (!memcmp(p, eval->val, ECOMMUNITY_SIZE)) { found = 1; break; } } if (found == 0) return 0; /* Delete the selected value */ ecom->size--; p = XMALLOC(MTYPE_ECOMMUNITY_VAL, ecom->size * ECOMMUNITY_SIZE); if (c != 0) memcpy(p, ecom->val, c * ECOMMUNITY_SIZE); if ((ecom->size - c) != 0) memcpy(p + (c)*ECOMMUNITY_SIZE, ecom->val + (c + 1) * ECOMMUNITY_SIZE, (ecom->size - c) * ECOMMUNITY_SIZE); XFREE(MTYPE_ECOMMUNITY_VAL, ecom->val); ecom->val = p; return 1; } int ecommunity_fill_pbr_action(struct ecommunity_val *ecom_eval, struct bgp_pbr_entry_action *api) { if (ecom_eval->val[1] == ECOMMUNITY_TRAFFIC_RATE) { api->action = ACTION_TRAFFICRATE; api->u.r.rate_info[3] = ecom_eval->val[4]; api->u.r.rate_info[2] = ecom_eval->val[5]; api->u.r.rate_info[1] = ecom_eval->val[6]; api->u.r.rate_info[0] = ecom_eval->val[7]; } else if (ecom_eval->val[1] == ECOMMUNITY_TRAFFIC_ACTION) { api->action = ACTION_TRAFFIC_ACTION; /* else distribute code is set by default */ if (ecom_eval->val[5] & (1 << FLOWSPEC_TRAFFIC_ACTION_TERMINAL)) api->u.za.filter |= TRAFFIC_ACTION_TERMINATE; else api->u.za.filter |= TRAFFIC_ACTION_DISTRIBUTE; if (ecom_eval->val[5] == 1 << FLOWSPEC_TRAFFIC_ACTION_SAMPLE) api->u.za.filter |= TRAFFIC_ACTION_SAMPLE; } else if (ecom_eval->val[1] == ECOMMUNITY_TRAFFIC_MARKING) { api->action = ACTION_MARKING; api->u.marking_dscp = ecom_eval->val[7]; } else if (ecom_eval->val[1] == ECOMMUNITY_REDIRECT_VRF) { /* must use external function */ return 0; } else if (ecom_eval->val[1] == ECOMMUNITY_REDIRECT_IP_NH) { /* see draft-ietf-idr-flowspec-redirect-ip-02 * Q1: how come a ext. community can host ipv6 address * Q2 : from cisco documentation: * Announces the reachability of one or more flowspec NLRI. * When a BGP speaker receives an UPDATE message with the * redirect-to-IP extended community, it is expected to * create a traffic filtering rule for every flow-spec * NLRI in the message that has this path as its best * path. The filter entry matches the IP packets * described in the NLRI field and redirects them or * copies them towards the IPv4 or IPv6 address specified * in the 'Network Address of Next- Hop' * field of the associated MP_REACH_NLRI. */ struct ecommunity_ip *ip_ecom = (struct ecommunity_ip *) ecom_eval + 2; api->u.zr.redirect_ip_v4 = ip_ecom->ip; } else return -1; return 0; } static struct ecommunity *bgp_aggr_ecommunity_lookup( struct bgp_aggregate *aggregate, struct ecommunity *ecommunity) { return hash_lookup(aggregate->ecommunity_hash, ecommunity); } static void *bgp_aggr_ecommunty_hash_alloc(void *p) { struct ecommunity *ref = (struct ecommunity *)p; struct ecommunity *ecommunity = NULL; ecommunity = ecommunity_dup(ref); return ecommunity; } static void bgp_aggr_ecommunity_prepare(struct hash_backet *hb, void *arg) { struct ecommunity *ecommerge = NULL; struct ecommunity *hb_ecommunity = hb->data; struct ecommunity **aggr_ecommunity = arg; if (*aggr_ecommunity) { ecommerge = ecommunity_merge(*aggr_ecommunity, hb_ecommunity); *aggr_ecommunity = ecommunity_uniq_sort(ecommerge); ecommunity_free(&ecommerge); } else *aggr_ecommunity = ecommunity_dup(hb_ecommunity); } void bgp_aggr_ecommunity_remove(void *arg) { struct ecommunity *ecommunity = arg; ecommunity_free(&ecommunity); } void bgp_compute_aggregate_ecommunity(struct bgp_aggregate *aggregate, struct ecommunity *ecommunity) { struct ecommunity *aggr_ecommunity = NULL; if ((aggregate == NULL) || (ecommunity == NULL)) return; /* Create hash if not already created. */ if (aggregate->ecommunity_hash == NULL) aggregate->ecommunity_hash = hash_create( ecommunity_hash_make, ecommunity_cmp, "BGP Aggregator ecommunity hash"); aggr_ecommunity = bgp_aggr_ecommunity_lookup(aggregate, ecommunity); if (aggr_ecommunity == NULL) { /* Insert ecommunity into hash. */ aggr_ecommunity = hash_get(aggregate->ecommunity_hash, ecommunity, bgp_aggr_ecommunty_hash_alloc); /* Re-compute aggregate's ecommunity. */ if (aggregate->ecommunity) ecommunity_free(&aggregate->ecommunity); hash_iterate(aggregate->ecommunity_hash, bgp_aggr_ecommunity_prepare, &aggregate->ecommunity); } /* Increment refernce counter. */ aggr_ecommunity->refcnt++; } void bgp_remove_ecommunity_from_aggregate(struct bgp_aggregate *aggregate, struct ecommunity *ecommunity) { struct ecommunity *aggr_ecommunity = NULL; struct ecommunity *ret_ecomm = NULL; if ((aggregate == NULL) || (ecommunity == NULL)) return; if (aggregate->ecommunity_hash == NULL) return; /* Look-up the ecommunity in the hash. */ aggr_ecommunity = bgp_aggr_ecommunity_lookup(aggregate, ecommunity); if (aggr_ecommunity) { aggr_ecommunity->refcnt--; if (aggr_ecommunity->refcnt == 0) { ret_ecomm = hash_release(aggregate->ecommunity_hash, aggr_ecommunity); ecommunity_free(&ret_ecomm); ecommunity_free(&aggregate->ecommunity); /* Compute aggregate's ecommunity. */ hash_iterate(aggregate->ecommunity_hash, bgp_aggr_ecommunity_prepare, &aggregate->ecommunity); } } } frr-7.2.1/bgpd/bgp_ecommunity.h0000644000000000000000000001543213610377563013327 00000000000000/* BGP Extended Communities Attribute. * Copyright (C) 2000 Kunihiro Ishiguro * * This file is part of GNU Zebra. * * GNU Zebra is free software; you can redistribute it and/or modify it * under the terms of the GNU General Public License as published by the * Free Software Foundation; either version 2, or (at your option) any * later version. * * GNU Zebra 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; see the file COPYING; if not, write to the Free Software * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA */ #ifndef _QUAGGA_BGP_ECOMMUNITY_H #define _QUAGGA_BGP_ECOMMUNITY_H #include "bgpd/bgp_route.h" #include "bgpd/bgpd.h" /* High-order octet of the Extended Communities type field. */ #define ECOMMUNITY_ENCODE_AS 0x00 #define ECOMMUNITY_ENCODE_IP 0x01 #define ECOMMUNITY_ENCODE_AS4 0x02 #define ECOMMUNITY_ENCODE_OPAQUE 0x03 #define ECOMMUNITY_ENCODE_EVPN 0x06 #define ECOMMUNITY_ENCODE_TRANS_EXP 0x80 /* Flow Spec */ #define ECOMMUNITY_ENCODE_REDIRECT_IP_NH 0x08 /* Flow Spec */ /* RFC7674 */ #define ECOMMUNITY_EXTENDED_COMMUNITY_PART_2 0x81 #define ECOMMUNITY_EXTENDED_COMMUNITY_PART_3 0x82 /* Low-order octet of the Extended Communities type field. */ #define ECOMMUNITY_ROUTE_TARGET 0x02 #define ECOMMUNITY_SITE_ORIGIN 0x03 #define ECOMMUNITY_TRAFFIC_RATE 0x06 /* Flow Spec */ #define ECOMMUNITY_TRAFFIC_ACTION 0x07 #define ECOMMUNITY_REDIRECT_VRF 0x08 #define ECOMMUNITY_TRAFFIC_MARKING 0x09 #define ECOMMUNITY_REDIRECT_IP_NH 0x00 /* from IANA: bgp-extended-communities/bgp-extended-communities.xhtml * 0x0c Flow-spec Redirect to IPv4 - draft-ietf-idr-flowspec-redirect */ #define ECOMMUNITY_FLOWSPEC_REDIRECT_IPV4 0x0c /* Low-order octet of the Extended Communities type field for EVPN types */ #define ECOMMUNITY_EVPN_SUBTYPE_MACMOBILITY 0x00 #define ECOMMUNITY_EVPN_SUBTYPE_ESI_LABEL 0x01 #define ECOMMUNITY_EVPN_SUBTYPE_ES_IMPORT_RT 0x02 #define ECOMMUNITY_EVPN_SUBTYPE_ROUTERMAC 0x03 #define ECOMMUNITY_EVPN_SUBTYPE_DEF_GW 0x0d #define ECOMMUNITY_EVPN_SUBTYPE_ND 0x08 #define ECOMMUNITY_EVPN_SUBTYPE_MACMOBILITY_FLAG_STICKY 0x01 #define ECOMMUNITY_EVPN_SUBTYPE_ND_ROUTER_FLAG 0x01 #define ECOMMUNITY_EVPN_SUBTYPE_ND_OVERRIDE_FLAG 0x02 /* Low-order octet of the Extended Communities type field for OPAQUE types */ #define ECOMMUNITY_OPAQUE_SUBTYPE_ENCAP 0x0c /* Extended communities attribute string format. */ #define ECOMMUNITY_FORMAT_ROUTE_MAP 0 #define ECOMMUNITY_FORMAT_COMMUNITY_LIST 1 #define ECOMMUNITY_FORMAT_DISPLAY 2 /* Extended Communities value is eight octet long. */ #define ECOMMUNITY_SIZE 8 /* Extended Communities type flag. */ #define ECOMMUNITY_FLAG_NON_TRANSITIVE 0x40 /* Extended Communities attribute. */ struct ecommunity { /* Reference counter. */ unsigned long refcnt; /* Size of Extended Communities attribute. */ int size; /* Extended Communities value. */ uint8_t *val; /* Human readable format string. */ char *str; }; struct ecommunity_as { as_t as; uint32_t val; }; struct ecommunity_ip { struct in_addr ip; uint16_t val; }; /* Extended community value is eight octet. */ struct ecommunity_val { char val[ECOMMUNITY_SIZE]; }; /* * Encode BGP Route Target AS:nn. */ static inline void encode_route_target_as(as_t as, uint32_t val, struct ecommunity_val *eval) { eval->val[0] = ECOMMUNITY_ENCODE_AS; eval->val[1] = ECOMMUNITY_ROUTE_TARGET; eval->val[2] = (as >> 8) & 0xff; eval->val[3] = as & 0xff; eval->val[4] = (val >> 24) & 0xff; eval->val[5] = (val >> 16) & 0xff; eval->val[6] = (val >> 8) & 0xff; eval->val[7] = val & 0xff; } /* * Encode BGP Route Target IP:nn. */ static inline void encode_route_target_ip(struct in_addr ip, uint16_t val, struct ecommunity_val *eval) { eval->val[0] = ECOMMUNITY_ENCODE_IP; eval->val[1] = ECOMMUNITY_ROUTE_TARGET; memcpy(&eval->val[2], &ip, sizeof(struct in_addr)); eval->val[6] = (val >> 8) & 0xff; eval->val[7] = val & 0xff; } /* * Encode BGP Route Target AS4:nn. */ static inline void encode_route_target_as4(as_t as, uint16_t val, struct ecommunity_val *eval) { eval->val[0] = ECOMMUNITY_ENCODE_AS4; eval->val[1] = ECOMMUNITY_ROUTE_TARGET; eval->val[2] = (as >> 24) & 0xff; eval->val[3] = (as >> 16) & 0xff; eval->val[4] = (as >> 8) & 0xff; eval->val[5] = as & 0xff; eval->val[6] = (val >> 8) & 0xff; eval->val[7] = val & 0xff; } extern void ecommunity_init(void); extern void ecommunity_finish(void); extern void ecommunity_free(struct ecommunity **); extern struct ecommunity *ecommunity_parse(uint8_t *, unsigned short); extern struct ecommunity *ecommunity_dup(struct ecommunity *); extern struct ecommunity *ecommunity_merge(struct ecommunity *, struct ecommunity *); extern struct ecommunity *ecommunity_uniq_sort(struct ecommunity *); extern struct ecommunity *ecommunity_intern(struct ecommunity *); extern bool ecommunity_cmp(const void *arg1, const void *arg2); extern void ecommunity_unintern(struct ecommunity **); extern unsigned int ecommunity_hash_make(const void *); extern struct ecommunity *ecommunity_str2com(const char *, int, int); extern char *ecommunity_ecom2str(struct ecommunity *, int, int); extern void ecommunity_strfree(char **s); extern int ecommunity_match(const struct ecommunity *, const struct ecommunity *); extern char *ecommunity_str(struct ecommunity *); extern struct ecommunity_val *ecommunity_lookup(const struct ecommunity *, uint8_t, uint8_t); extern int ecommunity_add_val(struct ecommunity *ecom, struct ecommunity_val *eval); /* for vpn */ extern struct ecommunity *ecommunity_new(void); extern int ecommunity_add_val(struct ecommunity *, struct ecommunity_val *); extern int ecommunity_strip(struct ecommunity *ecom, uint8_t type, uint8_t subtype); extern struct ecommunity *ecommunity_new(void); extern int ecommunity_del_val(struct ecommunity *ecom, struct ecommunity_val *eval); struct bgp_pbr_entry_action; extern int ecommunity_fill_pbr_action(struct ecommunity_val *ecom_eval, struct bgp_pbr_entry_action *api); extern void bgp_compute_aggregate_ecommunity( struct bgp_aggregate *aggregate, struct ecommunity *ecommunity); extern void bgp_remove_ecommunity_from_aggregate( struct bgp_aggregate *aggregate, struct ecommunity *ecommunity); extern void bgp_aggr_ecommunity_remove(void *arg); #endif /* _QUAGGA_BGP_ECOMMUNITY_H */ frr-7.2.1/bgpd/bgp_encap_tlv.c0000644000000000000000000006332113610377563013104 00000000000000/* * Copyright 2015, LabN Consulting, L.L.C. * * This program is free software; you can redistribute it and/or * modify it under the terms of the GNU General Public License * as published by the Free Software Foundation; either version 2 * of the License, or (at your option) any later version. * * 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; see the file COPYING; if not, write to the Free Software * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA */ #include #include "command.h" #include "memory.h" #include "prefix.h" #include "filter.h" #include "stream.h" #include "bgpd.h" #include "bgp_attr.h" #include "bgp_encap_types.h" #include "bgp_encap_tlv.h" /*********************************************************************** * SUBTLV ENCODE ***********************************************************************/ /* rfc5512 4.1 */ static struct bgp_attr_encap_subtlv *subtlv_encode_encap_l2tpv3_over_ip( struct bgp_tea_subtlv_encap_l2tpv3_over_ip *st) { struct bgp_attr_encap_subtlv *new; uint8_t *p; int total = 4 + st->cookie_length; /* sanity check */ assert(st->cookie_length <= sizeof(st->cookie)); assert(total <= 0xff); new = XCALLOC(MTYPE_ENCAP_TLV, sizeof(struct bgp_attr_encap_subtlv) + total); assert(new); new->type = BGP_ENCAP_SUBTLV_TYPE_ENCAPSULATION; new->length = total; p = new->value; *p++ = (st->sessionid & 0xff000000) >> 24; *p++ = (st->sessionid & 0xff0000) >> 16; *p++ = (st->sessionid & 0xff00) >> 8; *p++ = (st->sessionid & 0xff); memcpy(p, st->cookie, st->cookie_length); return new; } /* rfc5512 4.1 */ static struct bgp_attr_encap_subtlv * subtlv_encode_encap_gre(struct bgp_tea_subtlv_encap_gre_key *st) { struct bgp_attr_encap_subtlv *new; uint8_t *p; int total = 4; assert(total <= 0xff); new = XCALLOC(MTYPE_ENCAP_TLV, sizeof(struct bgp_attr_encap_subtlv) + total); assert(new); new->type = BGP_ENCAP_SUBTLV_TYPE_ENCAPSULATION; new->length = total; p = new->value; *p++ = (st->gre_key & 0xff000000) >> 24; *p++ = (st->gre_key & 0xff0000) >> 16; *p++ = (st->gre_key & 0xff00) >> 8; *p++ = (st->gre_key & 0xff); return new; } static struct bgp_attr_encap_subtlv * subtlv_encode_encap_pbb(struct bgp_tea_subtlv_encap_pbb *st) { struct bgp_attr_encap_subtlv *new; uint8_t *p; int total = 1 + 3 + 6 + 2; /* flags + isid + madaddr + vid */ assert(total <= 0xff); new = XCALLOC(MTYPE_ENCAP_TLV, sizeof(struct bgp_attr_encap_subtlv) + total); assert(new); new->type = BGP_ENCAP_SUBTLV_TYPE_ENCAPSULATION; new->length = total; p = new->value; *p++ = (st->flag_isid ? 0x80 : 0) | (st->flag_vid ? 0x40 : 0) | 0; if (st->flag_isid) { *p = (st->isid & 0xff0000) >> 16; *(p + 1) = (st->isid & 0xff00) >> 8; *(p + 2) = (st->isid & 0xff); } p += 3; memcpy(p, st->macaddr, 6); p += 6; if (st->flag_vid) { *p++ = (st->vid & 0xf00) >> 8; *p++ = st->vid & 0xff; } return new; } /* rfc5512 4.2 */ static struct bgp_attr_encap_subtlv * subtlv_encode_proto_type(struct bgp_tea_subtlv_proto_type *st) { struct bgp_attr_encap_subtlv *new; uint8_t *p; int total = 2; assert(total <= 0xff); new = XCALLOC(MTYPE_ENCAP_TLV, sizeof(struct bgp_attr_encap_subtlv) + total); assert(new); new->type = BGP_ENCAP_SUBTLV_TYPE_PROTO_TYPE; new->length = total; p = new->value; *p++ = (st->proto & 0xff00) >> 8; *p++ = (st->proto & 0xff); return new; } /* rfc5512 4.3 */ static struct bgp_attr_encap_subtlv * subtlv_encode_color(struct bgp_tea_subtlv_color *st) { struct bgp_attr_encap_subtlv *new; uint8_t *p; int total = 8; assert(total <= 0xff); new = XCALLOC(MTYPE_ENCAP_TLV, sizeof(struct bgp_attr_encap_subtlv) + total); assert(new); new->type = BGP_ENCAP_SUBTLV_TYPE_COLOR; new->length = total; p = new->value; *p++ = 0x03; /* transitive*/ *p++ = 0x0b; *p++ = 0; /* reserved */ *p++ = 0; /* reserved */ *p++ = (st->color & 0xff000000) >> 24; *p++ = (st->color & 0xff0000) >> 16; *p++ = (st->color & 0xff00) >> 8; *p++ = (st->color & 0xff); return new; } /* rfc 5566 4. */ static struct bgp_attr_encap_subtlv * subtlv_encode_ipsec_ta(struct bgp_tea_subtlv_ipsec_ta *st) { struct bgp_attr_encap_subtlv *new; uint8_t *p; int total = 2 + st->authenticator_length; /* sanity check */ assert(st->authenticator_length <= sizeof(st->value)); assert(total <= 0xff); new = XCALLOC(MTYPE_ENCAP_TLV, sizeof(struct bgp_attr_encap_subtlv) + total); assert(new); new->type = BGP_ENCAP_SUBTLV_TYPE_IPSEC_TA; new->length = total; p = new->value; *p++ = (st->authenticator_type & 0xff00) >> 8; *p++ = st->authenticator_type & 0xff; memcpy(p, st->value, st->authenticator_length); return new; } /* draft-rosen-idr-tunnel-encaps 2.1 */ static struct bgp_attr_encap_subtlv * subtlv_encode_remote_endpoint(struct bgp_tea_subtlv_remote_endpoint *st) { struct bgp_attr_encap_subtlv *new; uint8_t *p; int total = (st->family == AF_INET ? 8 : 20); assert(total <= 0xff); new = XCALLOC(MTYPE_ENCAP_TLV, sizeof(struct bgp_attr_encap_subtlv) + total); assert(new); new->type = BGP_ENCAP_SUBTLV_TYPE_REMOTE_ENDPOINT; new->length = total; p = new->value; if (st->family == AF_INET) { memcpy(p, &(st->ip_address.v4.s_addr), 4); p += 4; } else { assert(st->family == AF_INET6); memcpy(p, &(st->ip_address.v6.s6_addr), 16); p += 16; } memcpy(p, &(st->as4), 4); return new; } /*********************************************************************** * TUNNEL TYPE-SPECIFIC TLV ENCODE ***********************************************************************/ /* * requires "extra" and "last" to be defined in caller */ #define ENC_SUBTLV(flag, function, field) \ do { \ struct bgp_attr_encap_subtlv *new; \ if (CHECK_FLAG(bet->valid_subtlvs, (flag))) { \ new = function(&bet->field); \ if (last) { \ last->next = new; \ } else { \ attr->encap_subtlvs = new; \ } \ last = new; \ } \ } while (0) void bgp_encap_type_l2tpv3overip_to_tlv( struct bgp_encap_type_l2tpv3_over_ip *bet, /* input structure */ struct attr *attr) { struct bgp_attr_encap_subtlv *last; /* advance to last subtlv */ for (last = attr->encap_subtlvs; last && last->next; last = last->next) ; attr->encap_tunneltype = BGP_ENCAP_TYPE_L2TPV3_OVER_IP; assert(CHECK_FLAG(bet->valid_subtlvs, BGP_TEA_SUBTLV_ENCAP)); ENC_SUBTLV(BGP_TEA_SUBTLV_ENCAP, subtlv_encode_encap_l2tpv3_over_ip, st_encap); ENC_SUBTLV(BGP_TEA_SUBTLV_PROTO_TYPE, subtlv_encode_proto_type, st_proto); ENC_SUBTLV(BGP_TEA_SUBTLV_COLOR, subtlv_encode_color, st_color); ENC_SUBTLV(BGP_TEA_SUBTLV_REMOTE_ENDPOINT, subtlv_encode_remote_endpoint, st_endpoint); } void bgp_encap_type_gre_to_tlv( struct bgp_encap_type_gre *bet, /* input structure */ struct attr *attr) { struct bgp_attr_encap_subtlv *last; /* advance to last subtlv */ for (last = attr->encap_subtlvs; last && last->next; last = last->next) ; attr->encap_tunneltype = BGP_ENCAP_TYPE_GRE; ENC_SUBTLV(BGP_TEA_SUBTLV_ENCAP, subtlv_encode_encap_gre, st_encap); ENC_SUBTLV(BGP_TEA_SUBTLV_PROTO_TYPE, subtlv_encode_proto_type, st_proto); ENC_SUBTLV(BGP_TEA_SUBTLV_COLOR, subtlv_encode_color, st_color); ENC_SUBTLV(BGP_TEA_SUBTLV_REMOTE_ENDPOINT, subtlv_encode_remote_endpoint, st_endpoint); } void bgp_encap_type_ip_in_ip_to_tlv( struct bgp_encap_type_ip_in_ip *bet, /* input structure */ struct attr *attr) { struct bgp_attr_encap_subtlv *last; /* advance to last subtlv */ for (last = attr->encap_subtlvs; last && last->next; last = last->next) ; attr->encap_tunneltype = BGP_ENCAP_TYPE_IP_IN_IP; ENC_SUBTLV(BGP_TEA_SUBTLV_PROTO_TYPE, subtlv_encode_proto_type, st_proto); ENC_SUBTLV(BGP_TEA_SUBTLV_COLOR, subtlv_encode_color, st_color); ENC_SUBTLV(BGP_TEA_SUBTLV_REMOTE_ENDPOINT, subtlv_encode_remote_endpoint, st_endpoint); } void bgp_encap_type_transmit_tunnel_endpoint( struct bgp_encap_type_transmit_tunnel_endpoint *bet, /* input structure */ struct attr *attr) { struct bgp_attr_encap_subtlv *last; /* advance to last subtlv */ for (last = attr->encap_subtlvs; last && last->next; last = last->next) ; attr->encap_tunneltype = BGP_ENCAP_TYPE_TRANSMIT_TUNNEL_ENDPOINT; /* no subtlvs for this type */ } void bgp_encap_type_ipsec_in_tunnel_mode_to_tlv( struct bgp_encap_type_ipsec_in_tunnel_mode *bet, /* input structure */ struct attr *attr) { struct bgp_attr_encap_subtlv *last; /* advance to last subtlv */ for (last = attr->encap_subtlvs; last && last->next; last = last->next) ; attr->encap_tunneltype = BGP_ENCAP_TYPE_IPSEC_IN_TUNNEL_MODE; ENC_SUBTLV(BGP_TEA_SUBTLV_IPSEC_TA, subtlv_encode_ipsec_ta, st_ipsec_ta); } void bgp_encap_type_ip_in_ip_tunnel_with_ipsec_transport_mode_to_tlv( struct bgp_encap_type_ip_in_ip_tunnel_with_ipsec_transport_mode *bet, /* input structure */ struct attr *attr) { struct bgp_attr_encap_subtlv *last; /* advance to last subtlv */ for (last = attr->encap_subtlvs; last && last->next; last = last->next) ; attr->encap_tunneltype = BGP_ENCAP_TYPE_IP_IN_IP_TUNNEL_WITH_IPSEC_TRANSPORT_MODE; ENC_SUBTLV(BGP_TEA_SUBTLV_IPSEC_TA, subtlv_encode_ipsec_ta, st_ipsec_ta); } void bgp_encap_type_mpls_in_ip_tunnel_with_ipsec_transport_mode_to_tlv( struct bgp_encap_type_mpls_in_ip_tunnel_with_ipsec_transport_mode *bet, /* input structure */ struct attr *attr) { struct bgp_attr_encap_subtlv *last; /* advance to last subtlv */ for (last = attr->encap_subtlvs; last && last->next; last = last->next) ; attr->encap_tunneltype = BGP_ENCAP_TYPE_MPLS_IN_IP_TUNNEL_WITH_IPSEC_TRANSPORT_MODE; ENC_SUBTLV(BGP_TEA_SUBTLV_IPSEC_TA, subtlv_encode_ipsec_ta, st_ipsec_ta); } void bgp_encap_type_pbb_to_tlv( struct bgp_encap_type_pbb *bet, /* input structure */ struct attr *attr) { struct bgp_attr_encap_subtlv *last; /* advance to last subtlv */ for (last = attr->encap_subtlvs; last && last->next; last = last->next) ; attr->encap_tunneltype = BGP_ENCAP_TYPE_PBB; assert(CHECK_FLAG(bet->valid_subtlvs, BGP_TEA_SUBTLV_ENCAP)); ENC_SUBTLV(BGP_TEA_SUBTLV_ENCAP, subtlv_encode_encap_pbb, st_encap); } void bgp_encap_type_vxlan_to_tlv( struct bgp_encap_type_vxlan *bet, /* input structure */ struct attr *attr) { struct bgp_attr_encap_subtlv *tlv; uint32_t vnid; attr->encap_tunneltype = BGP_ENCAP_TYPE_VXLAN; if (bet == NULL || !bet->vnid) return; XFREE(MTYPE_ENCAP_TLV, attr->encap_subtlvs); tlv = XCALLOC(MTYPE_ENCAP_TLV, sizeof(struct bgp_attr_encap_subtlv) + 12); tlv->type = 1; /* encapsulation type */ tlv->length = 12; if (bet->vnid) { vnid = htonl(bet->vnid | VXLAN_ENCAP_MASK_VNID_VALID); memcpy(&tlv->value, &vnid, 4); } if (bet->mac_address) { char *ptr = (char *)&tlv->value + 4; memcpy(ptr, bet->mac_address, 6); } attr->encap_subtlvs = tlv; return; } void bgp_encap_type_nvgre_to_tlv( struct bgp_encap_type_nvgre *bet, /* input structure */ struct attr *attr) { attr->encap_tunneltype = BGP_ENCAP_TYPE_NVGRE; } void bgp_encap_type_mpls_to_tlv( struct bgp_encap_type_mpls *bet, /* input structure */ struct attr *attr) { return; /* no encap attribute for MPLS */ } void bgp_encap_type_mpls_in_gre_to_tlv( struct bgp_encap_type_mpls_in_gre *bet, /* input structure */ struct attr *attr) { attr->encap_tunneltype = BGP_ENCAP_TYPE_MPLS_IN_GRE; } void bgp_encap_type_vxlan_gpe_to_tlv( struct bgp_encap_type_vxlan_gpe *bet, /* input structure */ struct attr *attr) { attr->encap_tunneltype = BGP_ENCAP_TYPE_VXLAN_GPE; } void bgp_encap_type_mpls_in_udp_to_tlv( struct bgp_encap_type_mpls_in_udp *bet, /* input structure */ struct attr *attr) { attr->encap_tunneltype = BGP_ENCAP_TYPE_MPLS_IN_UDP; } /*********************************************************************** * SUBTLV DECODE ***********************************************************************/ /* rfc5512 4.1 */ static int subtlv_decode_encap_l2tpv3_over_ip( struct bgp_attr_encap_subtlv *subtlv, struct bgp_tea_subtlv_encap_l2tpv3_over_ip *st) { if (subtlv->length < 4) { zlog_debug("%s, subtlv length %d is less than 4", __func__, subtlv->length); return -1; } ptr_get_be32(subtlv->value, &st->sessionid); st->cookie_length = subtlv->length - 4; if (st->cookie_length > sizeof(st->cookie)) { zlog_debug("%s, subtlv length %d is greater than %d", __func__, st->cookie_length, (int)sizeof(st->cookie)); return -1; } memcpy(st->cookie, subtlv->value + 4, st->cookie_length); return 0; } /* rfc5512 4.1 */ static int subtlv_decode_encap_gre(struct bgp_attr_encap_subtlv *subtlv, struct bgp_tea_subtlv_encap_gre_key *st) { if (subtlv->length != 4) { zlog_debug("%s, subtlv length %d does not equal 4", __func__, subtlv->length); return -1; } ptr_get_be32(subtlv->value, &st->gre_key); return 0; } static int subtlv_decode_encap_pbb(struct bgp_attr_encap_subtlv *subtlv, struct bgp_tea_subtlv_encap_pbb *st) { if (subtlv->length != 1 + 3 + 6 + 2) { zlog_debug("%s, subtlv length %d does not equal %d", __func__, subtlv->length, 1 + 3 + 6 + 2); return -1; } if (subtlv->value[0] & 0x80) { st->flag_isid = 1; st->isid = (subtlv->value[1] << 16) | (subtlv->value[2] << 8) | subtlv->value[3]; } if (subtlv->value[0] & 0x40) { st->flag_vid = 1; st->vid = ((subtlv->value[10] & 0x0f) << 8) | subtlv->value[11]; } memcpy(st->macaddr, subtlv->value + 4, 6); return 0; } /* rfc5512 4.2 */ static int subtlv_decode_proto_type(struct bgp_attr_encap_subtlv *subtlv, struct bgp_tea_subtlv_proto_type *st) { if (subtlv->length != 2) { zlog_debug("%s, subtlv length %d does not equal 2", __func__, subtlv->length); return -1; } st->proto = (subtlv->value[0] << 8) | subtlv->value[1]; return 0; } /* rfc5512 4.3 */ static int subtlv_decode_color(struct bgp_attr_encap_subtlv *subtlv, struct bgp_tea_subtlv_color *st) { if (subtlv->length != 8) { zlog_debug("%s, subtlv length %d does not equal 8", __func__, subtlv->length); return -1; } if ((subtlv->value[0] != 0x03) || (subtlv->value[1] != 0x0b) || (subtlv->value[2] != 0) || (subtlv->value[3] != 0)) { zlog_debug("%s, subtlv value 1st 4 bytes are not 0x030b0000", __func__); return -1; } ptr_get_be32(subtlv->value + 4, &st->color); return 0; } /* rfc 5566 4. */ static int subtlv_decode_ipsec_ta(struct bgp_attr_encap_subtlv *subtlv, struct bgp_tea_subtlv_ipsec_ta *st) { st->authenticator_length = subtlv->length - 2; if (st->authenticator_length > sizeof(st->value)) { zlog_debug( "%s, authenticator length %d exceeds storage maximum %d", __func__, st->authenticator_length, (int)sizeof(st->value)); return -1; } st->authenticator_type = (subtlv->value[0] << 8) | subtlv->value[1]; memcpy(st->value, subtlv->value + 2, st->authenticator_length); return 0; } /* draft-rosen-idr-tunnel-encaps 2.1 */ static int subtlv_decode_remote_endpoint(struct bgp_attr_encap_subtlv *subtlv, struct bgp_tea_subtlv_remote_endpoint *st) { int i; if (subtlv->length != 8 && subtlv->length != 20) { zlog_debug("%s, subtlv length %d does not equal 8 or 20", __func__, subtlv->length); return -1; } if (subtlv->length == 8) { st->family = AF_INET; memcpy(&st->ip_address.v4.s_addr, subtlv->value, 4); } else { st->family = AF_INET6; memcpy(&(st->ip_address.v6.s6_addr), subtlv->value, 16); } i = subtlv->length - 4; ptr_get_be32(subtlv->value + i, &st->as4); return 0; } /*********************************************************************** * TUNNEL TYPE-SPECIFIC TLV DECODE ***********************************************************************/ int tlv_to_bgp_encap_type_l2tpv3overip( struct bgp_attr_encap_subtlv *stlv, /* subtlv chain */ struct bgp_encap_type_l2tpv3_over_ip *bet) /* caller-allocated */ { struct bgp_attr_encap_subtlv *st; int rc = 0; for (st = stlv; st; st = st->next) { switch (st->type) { case BGP_ENCAP_SUBTLV_TYPE_ENCAPSULATION: rc |= subtlv_decode_encap_l2tpv3_over_ip( st, &bet->st_encap); SET_SUBTLV_FLAG(bet, BGP_TEA_SUBTLV_ENCAP); break; case BGP_ENCAP_SUBTLV_TYPE_PROTO_TYPE: rc |= subtlv_decode_proto_type(st, &bet->st_proto); SET_SUBTLV_FLAG(bet, BGP_TEA_SUBTLV_PROTO_TYPE); break; case BGP_ENCAP_SUBTLV_TYPE_COLOR: rc |= subtlv_decode_color(st, &bet->st_color); SET_SUBTLV_FLAG(bet, BGP_TEA_SUBTLV_COLOR); break; case BGP_ENCAP_SUBTLV_TYPE_REMOTE_ENDPOINT: rc |= subtlv_decode_remote_endpoint(st, &bet->st_endpoint); SET_SUBTLV_FLAG(bet, BGP_TEA_SUBTLV_REMOTE_ENDPOINT); break; default: zlog_debug("%s: unexpected subtlv type %d", __func__, st->type); rc |= -1; break; } } return rc; } int tlv_to_bgp_encap_type_gre( struct bgp_attr_encap_subtlv *stlv, /* subtlv chain */ struct bgp_encap_type_gre *bet) /* caller-allocated */ { struct bgp_attr_encap_subtlv *st; int rc = 0; for (st = stlv; st; st = st->next) { switch (st->type) { case BGP_ENCAP_SUBTLV_TYPE_ENCAPSULATION: rc |= subtlv_decode_encap_gre(st, &bet->st_encap); SET_SUBTLV_FLAG(bet, BGP_TEA_SUBTLV_ENCAP); break; case BGP_ENCAP_SUBTLV_TYPE_PROTO_TYPE: rc |= subtlv_decode_proto_type(st, &bet->st_proto); SET_SUBTLV_FLAG(bet, BGP_TEA_SUBTLV_PROTO_TYPE); break; case BGP_ENCAP_SUBTLV_TYPE_COLOR: rc |= subtlv_decode_color(st, &bet->st_color); SET_SUBTLV_FLAG(bet, BGP_TEA_SUBTLV_COLOR); break; case BGP_ENCAP_SUBTLV_TYPE_REMOTE_ENDPOINT: rc |= subtlv_decode_remote_endpoint(st, &bet->st_endpoint); SET_SUBTLV_FLAG(bet, BGP_TEA_SUBTLV_REMOTE_ENDPOINT); break; default: zlog_debug("%s: unexpected subtlv type %d", __func__, st->type); rc |= -1; break; } } return rc; } int tlv_to_bgp_encap_type_ip_in_ip( struct bgp_attr_encap_subtlv *stlv, /* subtlv chain */ struct bgp_encap_type_ip_in_ip *bet) /* caller-allocated */ { struct bgp_attr_encap_subtlv *st; int rc = 0; for (st = stlv; st; st = st->next) { switch (st->type) { case BGP_ENCAP_SUBTLV_TYPE_PROTO_TYPE: rc |= subtlv_decode_proto_type(st, &bet->st_proto); SET_SUBTLV_FLAG(bet, BGP_TEA_SUBTLV_PROTO_TYPE); break; case BGP_ENCAP_SUBTLV_TYPE_COLOR: rc |= subtlv_decode_color(st, &bet->st_color); SET_SUBTLV_FLAG(bet, BGP_TEA_SUBTLV_COLOR); break; case BGP_ENCAP_SUBTLV_TYPE_REMOTE_ENDPOINT: rc |= subtlv_decode_remote_endpoint(st, &bet->st_endpoint); SET_SUBTLV_FLAG(bet, BGP_TEA_SUBTLV_REMOTE_ENDPOINT); break; default: zlog_debug("%s: unexpected subtlv type %d", __func__, st->type); rc |= -1; break; } } return rc; } int tlv_to_bgp_encap_type_transmit_tunnel_endpoint( struct bgp_attr_encap_subtlv *stlv, struct bgp_encap_type_transmit_tunnel_endpoint *bet) { struct bgp_attr_encap_subtlv *st; int rc = 0; for (st = stlv; st; st = st->next) { switch (st->type) { case BGP_ENCAP_SUBTLV_TYPE_REMOTE_ENDPOINT: rc |= subtlv_decode_remote_endpoint(st, &bet->st_endpoint); SET_SUBTLV_FLAG(bet, BGP_TEA_SUBTLV_REMOTE_ENDPOINT); break; default: zlog_debug("%s: unexpected subtlv type %d", __func__, st->type); rc |= -1; break; } } return rc; } int tlv_to_bgp_encap_type_ipsec_in_tunnel_mode( struct bgp_attr_encap_subtlv *stlv, /* subtlv chain */ struct bgp_encap_type_ipsec_in_tunnel_mode *bet) /* caller-allocated */ { struct bgp_attr_encap_subtlv *st; int rc = 0; for (st = stlv; st; st = st->next) { switch (st->type) { case BGP_ENCAP_SUBTLV_TYPE_IPSEC_TA: rc |= subtlv_decode_ipsec_ta(st, &bet->st_ipsec_ta); SET_SUBTLV_FLAG(bet, BGP_TEA_SUBTLV_IPSEC_TA); break; case BGP_ENCAP_SUBTLV_TYPE_REMOTE_ENDPOINT: rc |= subtlv_decode_remote_endpoint(st, &bet->st_endpoint); SET_SUBTLV_FLAG(bet, BGP_TEA_SUBTLV_REMOTE_ENDPOINT); break; default: zlog_debug("%s: unexpected subtlv type %d", __func__, st->type); rc |= -1; break; } } return rc; } int tlv_to_bgp_encap_type_ip_in_ip_tunnel_with_ipsec_transport_mode( struct bgp_attr_encap_subtlv *stlv, struct bgp_encap_type_ip_in_ip_tunnel_with_ipsec_transport_mode *bet) { struct bgp_attr_encap_subtlv *st; int rc = 0; for (st = stlv; st; st = st->next) { switch (st->type) { case BGP_ENCAP_SUBTLV_TYPE_IPSEC_TA: rc |= subtlv_decode_ipsec_ta(st, &bet->st_ipsec_ta); SET_SUBTLV_FLAG(bet, BGP_TEA_SUBTLV_IPSEC_TA); break; case BGP_ENCAP_SUBTLV_TYPE_REMOTE_ENDPOINT: rc |= subtlv_decode_remote_endpoint(st, &bet->st_endpoint); SET_SUBTLV_FLAG(bet, BGP_TEA_SUBTLV_REMOTE_ENDPOINT); break; default: zlog_debug("%s: unexpected subtlv type %d", __func__, st->type); rc |= -1; break; } } return rc; } int tlv_to_bgp_encap_type_mpls_in_ip_tunnel_with_ipsec_transport_mode( struct bgp_attr_encap_subtlv *stlv, struct bgp_encap_type_mpls_in_ip_tunnel_with_ipsec_transport_mode *bet) { struct bgp_attr_encap_subtlv *st; int rc = 0; for (st = stlv; st; st = st->next) { switch (st->type) { case BGP_ENCAP_SUBTLV_TYPE_IPSEC_TA: rc |= subtlv_decode_ipsec_ta(st, &bet->st_ipsec_ta); SET_SUBTLV_FLAG(bet, BGP_TEA_SUBTLV_IPSEC_TA); break; case BGP_ENCAP_SUBTLV_TYPE_REMOTE_ENDPOINT: rc |= subtlv_decode_remote_endpoint(st, &bet->st_endpoint); SET_SUBTLV_FLAG(bet, BGP_TEA_SUBTLV_REMOTE_ENDPOINT); break; default: zlog_debug("%s: unexpected subtlv type %d", __func__, st->type); rc |= -1; break; } } return rc; } int tlv_to_bgp_encap_type_vxlan(struct bgp_attr_encap_subtlv *stlv, struct bgp_encap_type_vxlan *bet) { struct bgp_attr_encap_subtlv *st; int rc = 0; for (st = stlv; st; st = st->next) { switch (st->type) { case BGP_ENCAP_SUBTLV_TYPE_REMOTE_ENDPOINT: rc |= subtlv_decode_remote_endpoint(st, &bet->st_endpoint); SET_SUBTLV_FLAG(bet, BGP_TEA_SUBTLV_REMOTE_ENDPOINT); break; default: zlog_debug("%s: unexpected subtlv type %d", __func__, st->type); rc |= -1; break; } } return rc; } int tlv_to_bgp_encap_type_nvgre(struct bgp_attr_encap_subtlv *stlv, struct bgp_encap_type_nvgre *bet) { struct bgp_attr_encap_subtlv *st; int rc = 0; for (st = stlv; st; st = st->next) { switch (st->type) { case BGP_ENCAP_SUBTLV_TYPE_REMOTE_ENDPOINT: rc |= subtlv_decode_remote_endpoint(st, &bet->st_endpoint); SET_SUBTLV_FLAG(bet, BGP_TEA_SUBTLV_REMOTE_ENDPOINT); break; default: zlog_debug("%s: unexpected subtlv type %d", __func__, st->type); rc |= -1; break; } } return rc; } int tlv_to_bgp_encap_type_mpls(struct bgp_attr_encap_subtlv *stlv, struct bgp_encap_type_mpls *bet) { struct bgp_attr_encap_subtlv *st; int rc = 0; for (st = stlv; st; st = st->next) { switch (st->type) { case BGP_ENCAP_SUBTLV_TYPE_REMOTE_ENDPOINT: rc |= subtlv_decode_remote_endpoint(st, &bet->st_endpoint); SET_SUBTLV_FLAG(bet, BGP_TEA_SUBTLV_REMOTE_ENDPOINT); break; default: zlog_debug("%s: unexpected subtlv type %d", __func__, st->type); rc |= -1; break; } } return rc; } int tlv_to_bgp_encap_type_mpls_in_gre(struct bgp_attr_encap_subtlv *stlv, struct bgp_encap_type_mpls_in_gre *bet) { struct bgp_attr_encap_subtlv *st; int rc = 0; for (st = stlv; st; st = st->next) { switch (st->type) { case BGP_ENCAP_SUBTLV_TYPE_REMOTE_ENDPOINT: rc |= subtlv_decode_remote_endpoint(st, &bet->st_endpoint); SET_SUBTLV_FLAG(bet, BGP_TEA_SUBTLV_REMOTE_ENDPOINT); break; default: zlog_debug("%s: unexpected subtlv type %d", __func__, st->type); rc |= -1; break; } } return rc; } int tlv_to_bgp_encap_type_vxlan_gpe(struct bgp_attr_encap_subtlv *stlv, struct bgp_encap_type_vxlan_gpe *bet) { struct bgp_attr_encap_subtlv *st; int rc = 0; for (st = stlv; st; st = st->next) { switch (st->type) { case BGP_ENCAP_SUBTLV_TYPE_REMOTE_ENDPOINT: rc |= subtlv_decode_remote_endpoint(st, &bet->st_endpoint); SET_SUBTLV_FLAG(bet, BGP_TEA_SUBTLV_REMOTE_ENDPOINT); break; default: zlog_debug("%s: unexpected subtlv type %d", __func__, st->type); rc |= -1; break; } } return rc; } int tlv_to_bgp_encap_type_mpls_in_udp(struct bgp_attr_encap_subtlv *stlv, struct bgp_encap_type_mpls_in_udp *bet) { struct bgp_attr_encap_subtlv *st; int rc = 0; for (st = stlv; st; st = st->next) { switch (st->type) { case BGP_ENCAP_SUBTLV_TYPE_REMOTE_ENDPOINT: rc |= subtlv_decode_remote_endpoint(st, &bet->st_endpoint); SET_SUBTLV_FLAG(bet, BGP_TEA_SUBTLV_REMOTE_ENDPOINT); break; default: zlog_debug("%s: unexpected subtlv type %d", __func__, st->type); rc |= -1; break; } } return rc; } int tlv_to_bgp_encap_type_pbb( struct bgp_attr_encap_subtlv *stlv, /* subtlv chain */ struct bgp_encap_type_pbb *bet) /* caller-allocated */ { struct bgp_attr_encap_subtlv *st; int rc = 0; for (st = stlv; st; st = st->next) { switch (st->type) { case BGP_ENCAP_SUBTLV_TYPE_ENCAPSULATION: rc |= subtlv_decode_encap_pbb(st, &bet->st_encap); SET_SUBTLV_FLAG(bet, BGP_TEA_SUBTLV_ENCAP); break; case BGP_ENCAP_SUBTLV_TYPE_REMOTE_ENDPOINT: rc |= subtlv_decode_remote_endpoint(st, &bet->st_endpoint); SET_SUBTLV_FLAG(bet, BGP_TEA_SUBTLV_REMOTE_ENDPOINT); break; default: zlog_debug("%s: unexpected subtlv type %d", __func__, st->type); rc |= -1; break; } } return rc; } frr-7.2.1/bgpd/bgp_encap_tlv.h0000644000000000000000000001203613610377563013106 00000000000000/* * Copyright 2015, LabN Consulting, L.L.C. * * This program is free software; you can redistribute it and/or * modify it under the terms of the GNU General Public License * as published by the Free Software Foundation; either version 2 * of the License, or (at your option) any later version. * * 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; see the file COPYING; if not, write to the Free Software * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA */ #ifndef _QUAGGA_BGP_ENCAP_TLV_H #define _QUAGGA_BGP_ENCAP_TLV_H /*********************************************************************** * TUNNEL TYPE-SPECIFIC TLV ENCODE ***********************************************************************/ extern void bgp_encap_type_l2tpv3overip_to_tlv(struct bgp_encap_type_l2tpv3_over_ip *bet, struct attr *attr); extern void bgp_encap_type_gre_to_tlv(struct bgp_encap_type_gre *bet, struct attr *attr); extern void bgp_encap_type_ip_in_ip_to_tlv(struct bgp_encap_type_ip_in_ip *bet, struct attr *attr); extern void bgp_encap_type_transmit_tunnel_endpoint( struct bgp_encap_type_transmit_tunnel_endpoint *bet, struct attr *attr); extern void bgp_encap_type_ipsec_in_tunnel_mode_to_tlv( struct bgp_encap_type_ipsec_in_tunnel_mode *bet, struct attr *attr); extern void bgp_encap_type_ip_in_ip_tunnel_with_ipsec_transport_mode_to_tlv( struct bgp_encap_type_ip_in_ip_tunnel_with_ipsec_transport_mode *bet, struct attr *attr); extern void bgp_encap_type_mpls_in_ip_tunnel_with_ipsec_transport_mode_to_tlv( struct bgp_encap_type_mpls_in_ip_tunnel_with_ipsec_transport_mode *bet, struct attr *attr); extern void bgp_encap_type_pbb_to_tlv(struct bgp_encap_type_pbb *bet, struct attr *attr); extern void bgp_encap_type_vxlan_to_tlv(struct bgp_encap_type_vxlan *bet, struct attr *attr); extern void bgp_encap_type_nvgre_to_tlv(struct bgp_encap_type_nvgre *bet, struct attr *attr); extern void bgp_encap_type_mpls_to_tlv(struct bgp_encap_type_mpls *bet, struct attr *attr); extern void bgp_encap_type_mpls_in_gre_to_tlv(struct bgp_encap_type_mpls_in_gre *bet, struct attr *attr); extern void bgp_encap_type_vxlan_gpe_to_tlv(struct bgp_encap_type_vxlan_gpe *bet, struct attr *attr); extern void bgp_encap_type_mpls_in_udp_to_tlv(struct bgp_encap_type_mpls_in_udp *bet, struct attr *attr); /*********************************************************************** * TUNNEL TYPE-SPECIFIC TLV DECODE ***********************************************************************/ extern int tlv_to_bgp_encap_type_l2tpv3overip( struct bgp_attr_encap_subtlv *stlv, /* subtlv chain */ struct bgp_encap_type_l2tpv3_over_ip *bet); /* caller-allocated */ extern int tlv_to_bgp_encap_type_gre( struct bgp_attr_encap_subtlv *stlv, /* subtlv chain */ struct bgp_encap_type_gre *bet); /* caller-allocated */ extern int tlv_to_bgp_encap_type_ip_in_ip( struct bgp_attr_encap_subtlv *stlv, /* subtlv chain */ struct bgp_encap_type_ip_in_ip *bet); /* caller-allocated */ extern int tlv_to_bgp_encap_type_transmit_tunnel_endpoint( struct bgp_attr_encap_subtlv *stlv, struct bgp_encap_type_transmit_tunnel_endpoint *bet); extern int tlv_to_bgp_encap_type_ipsec_in_tunnel_mode( struct bgp_attr_encap_subtlv *stlv, /* subtlv chain */ struct bgp_encap_type_ipsec_in_tunnel_mode *bet); /* caller-allocated */ extern int tlv_to_bgp_encap_type_ip_in_ip_tunnel_with_ipsec_transport_mode( struct bgp_attr_encap_subtlv *stlv, struct bgp_encap_type_ip_in_ip_tunnel_with_ipsec_transport_mode *bet); extern int tlv_to_bgp_encap_type_mpls_in_ip_tunnel_with_ipsec_transport_mode( struct bgp_attr_encap_subtlv *stlv, struct bgp_encap_type_mpls_in_ip_tunnel_with_ipsec_transport_mode *bet); extern int tlv_to_bgp_encap_type_vxlan(struct bgp_attr_encap_subtlv *stlv, struct bgp_encap_type_vxlan *bet); extern int tlv_to_bgp_encap_type_nvgre(struct bgp_attr_encap_subtlv *stlv, struct bgp_encap_type_nvgre *bet); extern int tlv_to_bgp_encap_type_mpls(struct bgp_attr_encap_subtlv *stlv, struct bgp_encap_type_mpls *bet); extern int tlv_to_bgp_encap_type_mpls(struct bgp_attr_encap_subtlv *stlv, struct bgp_encap_type_mpls *bet); extern int tlv_to_bgp_encap_type_mpls_in_gre(struct bgp_attr_encap_subtlv *stlv, struct bgp_encap_type_mpls_in_gre *bet); extern int tlv_to_bgp_encap_type_vxlan_gpe(struct bgp_attr_encap_subtlv *stlv, struct bgp_encap_type_vxlan_gpe *bet); extern int tlv_to_bgp_encap_type_mpls_in_udp(struct bgp_attr_encap_subtlv *stlv, struct bgp_encap_type_mpls_in_udp *bet); extern int tlv_to_bgp_encap_type_pbb( struct bgp_attr_encap_subtlv *stlv, /* subtlv chain */ struct bgp_encap_type_pbb *bet); /* caller-allocated */ #endif /* _QUAGGA_BGP_ENCAP_TLV_H */ frr-7.2.1/bgpd/bgp_encap_types.h0000644000000000000000000001563113610377563013451 00000000000000/* * Copyright 2015, LabN Consulting, L.L.C. * * This program is free software; you can redistribute it and/or * modify it under the terms of the GNU General Public License * as published by the Free Software Foundation; either version 2 * of the License, or (at your option) any later version. * * 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; see the file COPYING; if not, write to the Free Software * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA */ #ifndef _QUAGGA_BGP_ENCAP_TYPES_H #define _QUAGGA_BGP_ENCAP_TYPES_H #include "bgpd/bgp_ecommunity.h" /* from * http://www.iana.org/assignments/bgp-parameters/bgp-parameters.xhtml#tunnel-types */ typedef enum { BGP_ENCAP_TYPE_RESERVED = 0, BGP_ENCAP_TYPE_L2TPV3_OVER_IP = 1, BGP_ENCAP_TYPE_GRE = 2, BGP_ENCAP_TYPE_TRANSMIT_TUNNEL_ENDPOINT = 3, BGP_ENCAP_TYPE_IPSEC_IN_TUNNEL_MODE = 4, BGP_ENCAP_TYPE_IP_IN_IP_TUNNEL_WITH_IPSEC_TRANSPORT_MODE = 5, BGP_ENCAP_TYPE_MPLS_IN_IP_TUNNEL_WITH_IPSEC_TRANSPORT_MODE = 6, BGP_ENCAP_TYPE_IP_IN_IP = 7, BGP_ENCAP_TYPE_VXLAN = 8, BGP_ENCAP_TYPE_NVGRE = 9, BGP_ENCAP_TYPE_MPLS = 10, /* NOTE: Encap SAFI&Attribute not used */ BGP_ENCAP_TYPE_MPLS_IN_GRE = 11, BGP_ENCAP_TYPE_VXLAN_GPE = 12, BGP_ENCAP_TYPE_MPLS_IN_UDP = 13, BGP_ENCAP_TYPE_PBB } bgp_encap_types; typedef enum { BGP_ENCAP_SUBTLV_TYPE_ENCAPSULATION = 1, BGP_ENCAP_SUBTLV_TYPE_PROTO_TYPE = 2, BGP_ENCAP_SUBTLV_TYPE_IPSEC_TA = 3, BGP_ENCAP_SUBTLV_TYPE_COLOR = 4, BGP_ENCAP_SUBTLV_TYPE_REMOTE_ENDPOINT = 6 /* speculative, IANA assignment TBD */ } bgp_encap_subtlv_types; /* * Tunnel Encapsulation Attribute subtlvs */ struct bgp_tea_subtlv_encap_l2tpv3_over_ip { uint32_t sessionid; uint8_t cookie_length; uint8_t cookie[8]; }; struct bgp_tea_subtlv_encap_gre_key { uint32_t gre_key; }; struct bgp_tea_subtlv_encap_pbb { uint32_t flag_isid : 1; uint32_t flag_vid : 1; uint32_t isid : 24; uint16_t vid : 12; uint8_t macaddr[6]; }; struct bgp_tea_subtlv_proto_type { uint16_t proto; /* ether-type */ }; struct bgp_tea_subtlv_color { uint32_t color; }; /* per draft-rosen-idr-tunnel-encaps */ struct bgp_tea_subtlv_remote_endpoint { uint8_t family; /* IPv4 or IPv6 */ union { struct in_addr v4; struct in6_addr v6; } ip_address; as_t as4; /* always 4 bytes */ }; /* * This is the length of the value part of the ipsec tunnel authenticator * subtlv. Currently we only support the length for authenticator type 1. */ #define BGP_ENCAP_SUBTLV_IPSEC_TA_SIZE 20 struct bgp_tea_subtlv_ipsec_ta { uint16_t authenticator_type; /* only type 1 is supported so far */ uint8_t authenticator_length; /* octets in value field */ uint8_t value[BGP_ENCAP_SUBTLV_IPSEC_TA_SIZE]; }; /* * Subtlv valid flags * TBD change names to add "VALID" */ #define BGP_TEA_SUBTLV_ENCAP 0x00000001 #define BGP_TEA_SUBTLV_PROTO_TYPE 0x00000002 #define BGP_TEA_SUBTLV_COLOR 0x00000004 #define BGP_TEA_SUBTLV_IPSEC_TA 0x00000008 #define BGP_TEA_SUBTLV_REMOTE_ENDPOINT 0x00000010 #define CHECK_SUBTLV_FLAG(ptr, flag) CHECK_FLAG((ptr)->valid_subtlvs, (flag)) #define SET_SUBTLV_FLAG(ptr, flag) SET_FLAG((ptr)->valid_subtlvs, (flag)) #define UNSET_SUBTLV_FLAG(ptr, flag) UNSET_FLAG((ptr)->valid_subtlvs, (flag)) /* * Tunnel Type-specific APIs */ struct bgp_encap_type_reserved { uint32_t valid_subtlvs; struct bgp_tea_subtlv_remote_endpoint st_endpoint; /* optional */ }; struct bgp_encap_type_l2tpv3_over_ip { uint32_t valid_subtlvs; struct bgp_tea_subtlv_encap_l2tpv3_over_ip st_encap; struct bgp_tea_subtlv_proto_type st_proto; /* optional */ struct bgp_tea_subtlv_color st_color; /* optional */ struct bgp_tea_subtlv_remote_endpoint st_endpoint; /* optional */ }; struct bgp_encap_type_gre { uint32_t valid_subtlvs; struct bgp_tea_subtlv_encap_gre_key st_encap; /* optional */ struct bgp_tea_subtlv_proto_type st_proto; /* optional */ struct bgp_tea_subtlv_color st_color; /* optional */ struct bgp_tea_subtlv_remote_endpoint st_endpoint; /* optional */ }; struct bgp_encap_type_ip_in_ip { uint32_t valid_subtlvs; struct bgp_tea_subtlv_proto_type st_proto; /* optional */ struct bgp_tea_subtlv_color st_color; /* optional */ struct bgp_tea_subtlv_remote_endpoint st_endpoint; /* optional */ }; struct bgp_encap_type_transmit_tunnel_endpoint { uint32_t valid_subtlvs; struct bgp_tea_subtlv_remote_endpoint st_endpoint; /* optional */ /* No subtlvs defined in spec? */ }; struct bgp_encap_type_ipsec_in_tunnel_mode { uint32_t valid_subtlvs; struct bgp_tea_subtlv_ipsec_ta st_ipsec_ta; /* optional */ struct bgp_tea_subtlv_remote_endpoint st_endpoint; /* optional */ }; struct bgp_encap_type_ip_in_ip_tunnel_with_ipsec_transport_mode { uint32_t valid_subtlvs; struct bgp_tea_subtlv_ipsec_ta st_ipsec_ta; /* optional */ struct bgp_tea_subtlv_remote_endpoint st_endpoint; /* optional */ }; struct bgp_encap_type_mpls_in_ip_tunnel_with_ipsec_transport_mode { uint32_t valid_subtlvs; struct bgp_tea_subtlv_ipsec_ta st_ipsec_ta; /* optional */ struct bgp_tea_subtlv_remote_endpoint st_endpoint; /* optional */ }; #define VXLAN_ENCAP_MASK_VNID_VALID 0x80000000 #define VXLAN_ENCAP_MASK_MAC_VALID 0x40000000 struct bgp_encap_type_vxlan { uint32_t valid_subtlvs; struct bgp_tea_subtlv_remote_endpoint st_endpoint; /* optional */ /* draft-ietf-idr-tunnel-encaps-02 */ uint32_t vnid; /* does not include V and M bit */ uint8_t *mac_address; /* optional */ }; struct bgp_encap_type_nvgre { uint32_t valid_subtlvs; struct bgp_tea_subtlv_remote_endpoint st_endpoint; /* optional */ /* No subtlvs defined in spec? */ }; struct bgp_encap_type_mpls { uint32_t valid_subtlvs; struct bgp_tea_subtlv_remote_endpoint st_endpoint; /* optional */ /* No subtlvs defined in spec? */ }; struct bgp_encap_type_mpls_in_gre { uint32_t valid_subtlvs; struct bgp_tea_subtlv_remote_endpoint st_endpoint; /* optional */ /* No subtlvs defined in spec? */ }; struct bgp_encap_type_vxlan_gpe { uint32_t valid_subtlvs; struct bgp_tea_subtlv_remote_endpoint st_endpoint; /* optional */ /* No subtlvs defined in spec? */ }; struct bgp_encap_type_mpls_in_udp { uint32_t valid_subtlvs; struct bgp_tea_subtlv_remote_endpoint st_endpoint; /* optional */ /* No subtlvs defined in spec? */ }; struct bgp_encap_type_pbb { uint32_t valid_subtlvs; struct bgp_tea_subtlv_remote_endpoint st_endpoint; /* optional */ struct bgp_tea_subtlv_encap_pbb st_encap; }; static inline void encode_encap_extcomm(bgp_encap_types tnl_type, struct ecommunity_val *eval) { memset(eval, 0, sizeof(*eval)); eval->val[0] = ECOMMUNITY_ENCODE_OPAQUE; eval->val[1] = ECOMMUNITY_OPAQUE_SUBTYPE_ENCAP; eval->val[6] = ((tnl_type) >> 8) & 0xff; eval->val[7] = (tnl_type)&0xff; } #endif /* _QUAGGA_BGP_ENCAP_TYPES_H */ frr-7.2.1/bgpd/bgp_errors.c0000644000000000000000000005313013610377563012442 00000000000000/* * BGP-specific error messages. * Copyright (C) 2018 Cumulus Networks, Inc. * Don Slice * * This program is free software; you can redistribute it and/or modify it * under the terms of the 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; see the file COPYING; if not, write to the Free Software * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA */ #include #include "lib/ferr.h" #include "bgp_errors.h" /* clang-format off */ static struct log_ref ferr_bgp_warn[] = { { .code = EC_BGP_ASPATH_FEWER_HOPS, .title = "BGP AS-path conversion has failed", .description = "BGP has attempted to convert a AS2 to AS4 path and has failed", .suggestion = "Open an Issue with all relevant log files and restart FRR" }, { .code = EC_BGP_DEFUNCT_SNPA_LEN, .title = "BGP has received a value in a reserved field", .description = "BGP has received a non-zero value in a reserved field that was used for SNPA-length at one point in time", .suggestion = "BGP has peered with either a router that is attempting to send SNPA data or it has received a corrupted packet. If we are peering with a SNPA aware router(unlikely) upgrade that router, else open an Issue after gathering relevant log files", }, { .code = EC_BGP_MISSING_ATTRIBUTE, .title = "BGP has received an update with missing a missing attribute", .description = "BGP received update packets must have some minimum attribute information within them", .suggestion = "Gather log data from this and remote peer and open an Issue with this data", }, { .code = EC_BGP_ATTRIBUTE_TOO_SMALL, .title = "BGP udate packet with attribute data that is too small", .description = "BGP has received an update packet that is too small to parse a given attribute. This typically means that something has gone wrong between us and the remote peer", .suggestion = "Gather log data from this and remote peer and open an Issue with this data", }, { .code = EC_BGP_EXT_ATTRIBUTE_TOO_SMALL, .title = "BGP udate packet with extended attribute data that is too small", .description = "BGP has received an update packet that is too small to parse a given extended attribute. This typically means that something has gone wrong between us and the remote peer", .suggestion = "Gather log data from this and remote peer and open an Issue with this data", }, { .code = EC_BGP_ATTRIBUTE_REPEATED, .title = "BGP update packet received with a repeated attribute", .description = "BGP has received an update packet with a attribute that is repeated more than one time for a particular route. This typically means that something has gone wrong between us and the remote peer", .suggestion = "Gather log data from this and remote peer and open an Issue with this data", }, { .code = EC_BGP_ATTRIBUTE_TOO_LARGE, .title = "BGP udate packet with attribute data that is too large", .description = "BGP has received an update packet that has too much data in a particular attribute. This typically means that something has gone wrong between us and the remote peer", .suggestion = "Gather log data from this and remote peer and open an Issue with this data", }, { .code = EC_BGP_ATTRIBUTE_PARSE_ERROR, .title = "BGP update packet with attribute data has a parse error, specific to the attribute", .description = "BGP has received an update packet with an attribute that when parsed does not make sense in some manner", .suggestion = "Gather log data from this and remote peer and open an Issue with this data", }, { .code = EC_BGP_ATTRIBUTE_PARSE_WITHDRAW, .title = "BGP update packet with a broken optional attribute has caused a withdraw of associated routes", .description = "BGP has received a update packet with optional attributes that did not parse correctly, instead of resetting the peer, withdraw associated routes and note that this has happened", .suggestion = "Gather log data from this and remote peer and open an Issue with this data", }, { .code = EC_BGP_ATTRIBUTE_FETCH_ERROR, .title = "BGP update packet with a broken length", .description = "BGP has received a update packet with an attribute that has an incorrect length", .suggestion = "Gather log data from this and remote peer and open an Issue with this data", }, { .code = EC_BGP_ATTRIBUTES_MISMATCH, .title = "BGP update packet with a length different than attribute data length", .description = "BGP has received a update packet with attributes that when parsed do not correctly add up to packet data length", .suggestion = "Gather log data from this and remote peer and open an Issue with this data", }, { .code = EC_BGP_DUMP, .title = "BGP MRT dump subsystem has encountered an issue", .description = "BGP has found that the attempted write of MRT data to a dump file has failed", .suggestion = "Ensure BGP has permissions to write the specified file", }, { .code = EC_BGP_UPDATE_PACKET_SHORT, .title = "BGP Update Packet is to Small", .description = "The update packet received from a peer is to small", .suggestion = "Determine the source of the update packet and examine that peer for what has gone wrong", }, { .code = EC_BGP_UPDATE_PACKET_LONG, .title = "BGP Update Packet is to large", .description = "The update packet received from a peer is to large", .suggestion = "Determine the source of the update packet and examine that peer for what has gone wrong", }, { .code = EC_BGP_UNRECOGNIZED_CAPABILITY, .title = "Unknown BGP Capability Received", .description = "The negotiation of capabilities has received a capability that we do not know what to do with", .suggestion = "Determine the source of the capability and remove the capability from what is sent", }, { .code = EC_BGP_NO_TCP_MD5, .title = "Unable to set TCP MD5 option on socket", .description = "BGP attempted to setup TCP MD5 configuration on the socket as per configuration but was unable to", .suggestion = "Please collect log files and open Issue", }, { .code = EC_BGP_EVPN_PMSI_PRESENT, .title = "BGP Received a EVPN NLRI with PMSI included", .description = "BGP has received a type-3 NLRI with PMSI information. At this time FRR is not capable of properly handling this NLRI type", .suggestion = "Setup peer to not send this type of data to FRR" }, { .code = EC_BGP_EVPN_VPN_VNI, .title = "BGP has received a local macip and cannot properly handle it", .description = "BGP has received a local macip from zebra and has no way to properly handle the macip because the vni is not setup properly", .suggestion = "Ensure proper setup of BGP EVPN", }, { .code = EC_BGP_EVPN_ESI, .title = "BGP has received a local ESI for deletion", .description = "BGP has received a local ESI for deletion but when attempting to find the stored data internally was unable to find the information for deletion", .suggestion = "Gather logging and open an Issue", }, { .code = EC_BGP_INVALID_LABEL_STACK, .title = "BGP has received a label stack in a NLRI that does not have the BOS marked", .description = "BGP when it receives a NLRI with a label stack should have the BOS marked, this received packet does not have this", .suggestion = "Gather log information from here and remote peer and open an Issue", }, { .code = EC_BGP_ZEBRA_SEND, .title = "BGP has attempted to send data to zebra and has failed to do so", .description = "BGP has attempted to send data to zebra but has been unable to do so", .suggestion = "Gather log data, open an Issue and restart FRR" }, { .code = EC_BGP_CAPABILITY_INVALID_LENGTH, .title = "BGP has received a capability with an invalid length", .description = "BGP has received a capability from it's peer who's size is wrong", .suggestion = "Gather log files from here and from peer and open an Issue", }, { .code = EC_BGP_CAPABILITY_INVALID_DATA, .title = "BGP has received capability data with invalid information", .description = "BGP has noticed that during processing of capability information that data was wrong", .suggestion = "Gather log files from here and from peer and open an Issue", }, { .code = EC_BGP_CAPABILITY_VENDOR, .title = "BGP has received capability data specific to a particular vendor", .description = "BGP has received a capability that is vendor specific and as such we have no knowledge of how to use this capability in FRR", .suggestion = "On peer turn off this feature" }, { .code = EC_BGP_CAPABILITY_UNKNOWN, .title = "BGP has received capability data for a unknown capability", .description = "BGP has received a capability that it does not know how to decode. This may be due to a new feature that has not been coded into FRR or it may be a bug in the remote peer", .suggestion = "Gather log files from here and from peer and open an Issue", }, { .code = EC_BGP_INVALID_NEXTHOP_LENGTH, .title = "BGP is attempting to write an invalid nexthop length value", .description = "BGP is in the process of building NLRI information for a peer and has discovered an inconsistent internal state", .suggestion = "Gather log files and open an Issue, restart FRR", }, { .code = END_FERR, } }; static struct log_ref ferr_bgp_err[] = { { .code = EC_BGP_ATTR_FLAG, .title = "BGP attribute flag is incorrect", .description = "BGP attribute flag is set to the wrong value (Optional/Transitive/Partial)", .suggestion = "Determine the source of the attribute and determine why the attribute flag has been set incorrectly" }, { .code = EC_BGP_ATTR_LEN, .title = "BGP attribute length is incorrect", .description = "BGP attribute length is incorrect", .suggestion = "Determine the source of the attribute and determine why the attribute length has been set incorrectly" }, { .code = EC_BGP_ATTR_ORIGIN, .title = "BGP attribute origin value invalid", .description = "BGP attribute origin value is invalid", .suggestion = "Determine the source of the attribute and determine why the origin attribute has been set incorrectly" }, { .code = EC_BGP_ATTR_MAL_AS_PATH, .title = "BGP as path is invalid", .description = "BGP as path has been malformed", .suggestion = "Determine the source of the update and determine why the as path has been set incorrectly" }, { .code = EC_BGP_ATTR_FIRST_AS, .title = "BGP as path first as is invalid", .description = "BGP update has invalid first as in as path", .suggestion = "Determine the source of the update and determine why the as path first as value has been set incorrectly" }, { .code = EC_BGP_ATTR_PMSI_TYPE, .title = "BGP PMSI tunnel attribute type is invalid", .description = "BGP update has invalid type for PMSI tunnel", .suggestion = "Determine the source of the update and determine why the PMSI tunnel attribute type has been set incorrectly" }, { .code = EC_BGP_ATTR_PMSI_LEN, .title = "BGP PMSI tunnel attribute length is invalid", .description = "BGP update has invalid length for PMSI tunnel", .suggestion = "Determine the source of the update and determine why the PMSI tunnel attribute length has been set incorrectly" }, { .code = EC_BGP_PEER_GROUP, .title = "BGP peergroup operated on in error", .description = "BGP operating on peer-group instead of peers included", .suggestion = "Ensure the config doesn't contain peergroups contained within peergroups" }, { .code = EC_BGP_PEER_DELETE, .title = "BGP failed to delete peer structure", .description = "BGP was unable to delete peer structure when address-family removed", .suggestion = "Determine if all expected peers are removed and restart FRR if not. Most likely a bug" }, { .code = EC_BGP_TABLE_CHUNK, .title = "BGP failed to get table chunk memory", .description = "BGP unable to get chunk memory for table manager", .suggestion = "Ensure there is adequate memory on the device to support the table requirements" }, { .code = EC_BGP_MACIP_LEN, .title = "BGP received MACIP with invalid IP addr len", .description = "BGP received MACIP with invalid IP addr len from Zebra", .suggestion = "Verify MACIP entries inserted in Zebra are correct. Most likely a bug" }, { .code = EC_BGP_LM_ERROR, .title = "BGP received invalid label manager message", .description = "BGP received invalid label manager message from label manager", .suggestion = "Label manager sent invalid essage to BGP for wrong protocol, instance, etc. Most likely a bug" }, { .code = EC_BGP_JSON_MEM_ERROR, .title = "BGP unable to allocate memory for JSON output", .description = "BGP attempted to generate JSON output and was unable to allocate the memory required", .suggestion = "Ensure that the device has adequate memory to support the required functions" }, { .code = EC_BGP_UPDGRP_ATTR_LEN, .title = "BGP update had attributes too long to send", .description = "BGP attempted to send an update but the attributes were too long to fit", .suggestion = "Most likely a bug. If the problem persists, report the problem for troubleshooting" }, { .code = EC_BGP_UPDGRP_CREATE, .title = "BGP update group creation failed", .description = "BGP attempted to create an update group but was unable to", .suggestion = "Most likely a bug. If the problem persists, report the problem for troubleshooting" }, { .code = EC_BGP_UPDATE_SND, .title = "BGP error creating update packet", .description = "BGP attempted to create an update packet but was unable to", .suggestion = "Most likely a bug. If the problem persists, report the problem for troubleshooting" }, { .code = EC_BGP_PKT_OPEN, .title = "BGP error receiving open packet", .description = "BGP received an open from a peer that was invalid", .suggestion = "Determine the sending peer and correct his invalid open packet" }, { .code = EC_BGP_SND_FAIL, .title = "BGP error sending to peer", .description = "BGP attempted to respond to open from a peer and failed", .suggestion = "BGP attempted to respond to an open and could not sene the packet. Check local IP address for source" }, { .code = EC_BGP_INVALID_STATUS, .title = "BGP error receiving from peer", .description = "BGP received an update from a peer but status was incorrect", .suggestion = "Most likely a bug. If the problem persists, report the problem for troubleshooting" }, { .code = EC_BGP_UPDATE_RCV, .title = "BGP error receiving update packet", .description = "BGP received an invalid update packet", .suggestion = "Determine the source of the update and resolve the invalid update being sent" }, { .code = EC_BGP_NO_CAP, .title = "BGP error due to capability not enabled", .description = "BGP attempted a function that did not have the capability enabled", .suggestion = "Enable the capability if this functionality is desired" }, { .code = EC_BGP_NOTIFY_RCV, .title = "BGP error receiving notify message", .description = "BGP unable to process notification message", .suggestion = "BGP notify received while in stopped state. If the problem persists, report for troubleshooting" }, { .code = EC_BGP_KEEP_RCV, .title = "BGP error receiving keepalive packet", .description = "BGP unable to process keepalive packet", .suggestion = "BGP keepalive received while in stopped state. If the problem persists, report for troubleshooting" }, { .code = EC_BGP_RFSH_RCV, .title = "BGP error receiving route refresh message", .description = "BGP unable to process route refresh message", .suggestion = "BGP route refresh received while in stopped state. If the problem persists, report for troubleshooting"}, { .code = EC_BGP_CAP_RCV, .title = "BGP error capability message", .description = "BGP unable to process received capability", .suggestion = "BGP capability message received while in stopped state. If the problem persists, report for troubleshooting" }, { .code = EC_BGP_NH_UPD, .title = "BGP error with nexthopo update", .description = "BGP unable to process nexthop update", .suggestion = "BGP received nexthop update but nexthop is not reachable in this bgp instance. Report for troubleshooting" }, { .code = EC_BGP_LABEL, .title = "Failure to apply label", .description = "BGP attempted to attempted to apply a label but could not", .suggestion = "Most likely a bug. If the problem persists, report the problem for troubleshooting" }, { .code = EC_BGP_MULTIPATH, .title = "Multipath specified is invalid", .description = "BGP was started with an invalid ecmp/multipath value", .suggestion = "Correct the ecmp/multipath value supplied when starting the BGP daemon" }, { .code = EC_BGP_PKT_PROCESS, .title = "Failure to process a packet", .description = "BGP attempted to process a received packet but could not", .suggestion = "Most likely a bug. If the problem persists, report the problem for troubleshooting" }, { .code = EC_BGP_CONNECT, .title = "Failure to connect to peer", .description = "BGP attempted to send open to peer but couldn't connect", .suggestion = "Most likely a bug. If the problem persists, report the problem for troubleshooting" }, { .code = EC_BGP_FSM, .title = "BGP FSM issue", .description = "BGP neighbor transition problem", .suggestion = "Most likely a bug. If the problem persists, report the problem for troubleshooting" }, { .code = EC_BGP_VNI, .title = "BGP VNI creation issue", .description = "BGP could not create a new VNI", .suggestion = "Most likely a bug. If the problem persists, report the problem for troubleshooting" }, { .code = EC_BGP_NO_DFLT, .title = "BGP default instance missing", .description = "BGP could not find default instance", .suggestion = "Define a default instance of BGP since some feature requires it's existence" }, { .code = EC_BGP_VTEP_INVALID, .title = "BGP remote VTEP invalid", .description = "BGP remote VTEP is invalid and cannot be used", .suggestion = "Correct remote VTEP configuration or resolve the source of the problem" }, { .code = EC_BGP_ES_INVALID, .title = "BGP ES route error", .description = "BGP ES route incorrect, learned both local and remote", .suggestion = "Correct configuration or addressing so that same not learned both local and remote" }, { .code = EC_BGP_EVPN_ROUTE_DELETE, .title = "BGP EVPN route delete error", .description = "BGP attempted to delete an EVPN route and failed", .suggestion = "Most likely a bug. If the problem persists, report the problem for troubleshooting" }, { .code = EC_BGP_EVPN_FAIL, .title = "BGP EVPN install/uninstall error", .description = "BGP attempted to install or uninstall an EVPN prefix and failed", .suggestion = "Most likely a bug. If the problem persists, report the problem for troubleshooting" }, { .code = EC_BGP_EVPN_ROUTE_INVALID, .title = "BGP EVPN route received with invalid contents", .description = "BGP received an EVPN route with invalid contents", .suggestion = "Determine the source of the EVPN route and resolve whatever is causing invalid contents" }, { .code = EC_BGP_EVPN_ROUTE_CREATE, .title = "BGP EVPN route create error", .description = "BGP attempted to create an EVPN route and failed", .suggestion = "Most likely a bug. If the problem persists, report the problem for troubleshooting" }, { .code = EC_BGP_ES_CREATE, .title = "BGP EVPN ES entry create error", .description = "BGP attempted to create an EVPN ES entry and failed", .suggestion = "Most likely a bug. If the problem persists, report the problem for troubleshooting" }, { .code = EC_BGP_EVPN_AS_MISMATCH, .title = "BGP AS configuration issue", .description = "BGP configuration attempted for a different AS than currently configured", .suggestion = "Correct the configuration so that the correct BGP AS number is used" }, { .code = EC_BGP_EVPN_INSTANCE_MISMATCH, .title = "BGP EVPN AS and process name mismatch", .description = "BGP configuration has AS and process name mismatch", .suggestion = "Correct the configuration so that the BGP AS number and instance name are consistent" }, { .code = EC_BGP_FLOWSPEC_PACKET, .title = "BGP Flowspec packet processing error", .description = "The BGP flowspec subsystem has detected a error in the send or receive of a packet", .suggestion = "Gather log files from both sides of the peering relationship and open an issue" }, { .code = EC_BGP_FLOWSPEC_INSTALLATION, .title = "BGP Flowspec Installation/removal Error", .description = "The BGP flowspec subsystem has detected that there was a failure for installation/removal/modification of Flowspec from the dataplane", .suggestion = "Gather log files from the router and open an issue, Restart FRR" }, { .code = EC_BGP_DOPPELGANGER_CONFIG, .title = "BGP has detected a configuration overwrite during peer collision resolution", .description = "As part of BGP startup, the peer and ourselves can start connections to each other at the same time. During this process BGP received additional configuration, but it was only applied to one of the two nascent connections. Depending on the result of collision detection and resolution this configuration might be lost. To remedy this, after performing collision detection and resolution the peer session has been reset in order to apply the new configuration.", .suggestion = "Gather data and open a Issue so that this developmental escape can be fixed, the peer should have been reset", }, { .code = END_FERR, } }; /* clang-format on */ void bgp_error_init(void) { log_ref_add(ferr_bgp_err); log_ref_add(ferr_bgp_warn); } frr-7.2.1/bgpd/bgp_errors.h0000644000000000000000000000524713610377563012455 00000000000000/* * BGP-specific error messages. * Copyright (C) 2018 Cumulus Networks, Inc. * Don Slice * * This program is free software; you can redistribute it and/or modify it * under the terms of the 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; see the file COPYING; if not, write to the Free Software * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA */ #ifndef __BGP_ERRORS_H__ #define __BGP_ERRORS_H__ #include "lib/ferr.h" enum bgp_log_refs { EC_BGP_ATTR_FLAG = BGP_FERR_START, EC_BGP_ATTR_LEN, EC_BGP_ATTR_ORIGIN, EC_BGP_ATTR_MAL_AS_PATH, EC_BGP_ATTR_FIRST_AS, EC_BGP_ATTR_MARTIAN_NH, EC_BGP_ATTR_PMSI_TYPE, EC_BGP_ATTR_PMSI_LEN, EC_BGP_ATTR_NH_SEND_LEN, EC_BGP_PEER_GROUP, EC_BGP_PEER_DELETE, EC_BGP_TABLE_CHUNK, EC_BGP_MACIP_LEN, EC_BGP_LM_ERROR, EC_BGP_JSON_MEM_ERROR, EC_BGP_UPDGRP_ATTR_LEN, EC_BGP_UPDGRP_CREATE, EC_BGP_UPDATE_SND, EC_BGP_PKT_OPEN, EC_BGP_SND_FAIL, EC_BGP_INVALID_STATUS, EC_BGP_UPDATE_RCV, EC_BGP_NO_CAP, EC_BGP_NOTIFY_RCV, EC_BGP_KEEP_RCV, EC_BGP_RFSH_RCV, EC_BGP_CAP_RCV, EC_BGP_NH_UPD, EC_BGP_LABEL, EC_BGP_MULTIPATH, EC_BGP_PKT_PROCESS, EC_BGP_CONNECT, EC_BGP_FSM, EC_BGP_VNI, EC_BGP_NO_DFLT, EC_BGP_VTEP_INVALID, EC_BGP_ES_INVALID, EC_BGP_EVPN_ROUTE_DELETE, EC_BGP_EVPN_FAIL, EC_BGP_EVPN_ROUTE_INVALID, EC_BGP_EVPN_ROUTE_CREATE, EC_BGP_ES_CREATE, EC_BGP_EVPN_AS_MISMATCH, EC_BGP_EVPN_INSTANCE_MISMATCH, EC_BGP_FLOWSPEC_PACKET, EC_BGP_FLOWSPEC_INSTALLATION, EC_BGP_ASPATH_FEWER_HOPS, EC_BGP_DEFUNCT_SNPA_LEN, EC_BGP_MISSING_ATTRIBUTE, EC_BGP_ATTRIBUTE_TOO_SMALL, EC_BGP_EXT_ATTRIBUTE_TOO_SMALL, EC_BGP_ATTRIBUTE_REPEATED, EC_BGP_ATTRIBUTE_TOO_LARGE, EC_BGP_ATTRIBUTE_PARSE_ERROR, EC_BGP_ATTRIBUTE_PARSE_WITHDRAW, EC_BGP_ATTRIBUTE_FETCH_ERROR, EC_BGP_ATTRIBUTES_MISMATCH, EC_BGP_DUMP, EC_BGP_UPDATE_PACKET_SHORT, EC_BGP_UPDATE_PACKET_LONG, EC_BGP_UNRECOGNIZED_CAPABILITY, EC_BGP_NO_TCP_MD5, EC_BGP_EVPN_PMSI_PRESENT, EC_BGP_EVPN_VPN_VNI, EC_BGP_EVPN_ESI, EC_BGP_INVALID_LABEL_STACK, EC_BGP_ZEBRA_SEND, EC_BGP_CAPABILITY_INVALID_LENGTH, EC_BGP_CAPABILITY_INVALID_DATA, EC_BGP_CAPABILITY_VENDOR, EC_BGP_CAPABILITY_UNKNOWN, EC_BGP_INVALID_NEXTHOP_LENGTH, EC_BGP_DOPPELGANGER_CONFIG, }; extern void bgp_error_init(void); #endif frr-7.2.1/bgpd/bgp_evpn.c0000644000000000000000000047622113610377563012110 00000000000000/* Ethernet-VPN Packet and vty Processing File * Copyright (C) 2016 6WIND * Copyright (C) 2017 Cumulus Networks, Inc. * * This file is part of FRR. * * FRRouting is free software; you can redistribute it and/or modify it * under the terms of the GNU General Public License as published by the * Free Software Foundation; either version 2, or (at your option) any * later version. * * FRRouting 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; see the file COPYING; if not, write to the Free Software * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA */ #include #include "command.h" #include "filter.h" #include "prefix.h" #include "log.h" #include "memory.h" #include "stream.h" #include "hash.h" #include "jhash.h" #include "zclient.h" #include "bgpd/bgp_attr_evpn.h" #include "bgpd/bgpd.h" #include "bgpd/bgp_table.h" #include "bgpd/bgp_route.h" #include "bgpd/bgp_attr.h" #include "bgpd/bgp_mplsvpn.h" #include "bgpd/bgp_label.h" #include "bgpd/bgp_evpn.h" #include "bgpd/bgp_evpn_private.h" #include "bgpd/bgp_ecommunity.h" #include "bgpd/bgp_encap_types.h" #include "bgpd/bgp_debug.h" #include "bgpd/bgp_errors.h" #include "bgpd/bgp_aspath.h" #include "bgpd/bgp_zebra.h" #include "bgpd/bgp_nexthop.h" #include "bgpd/bgp_addpath.h" #include "bgpd/bgp_mac.h" /* * Definitions and external declarations. */ extern struct zclient *zclient; DEFINE_QOBJ_TYPE(bgpevpn) DEFINE_QOBJ_TYPE(evpnes) /* * Static function declarations */ static void delete_evpn_route_entry(struct bgp *bgp, afi_t afi, safi_t safi, struct bgp_node *rn, struct bgp_path_info **pi); static int delete_all_vni_routes(struct bgp *bgp, struct bgpevpn *vpn); /* * Private functions. */ /* compare two IPV4 VTEP IPs */ static int evpn_vtep_ip_cmp(void *p1, void *p2) { const struct in_addr *ip1 = p1; const struct in_addr *ip2 = p2; return ip1->s_addr - ip2->s_addr; } /* * Make hash key for ESI. */ static unsigned int esi_hash_keymake(const void *p) { const struct evpnes *pes = p; const void *pnt = (void *)pes->esi.val; return jhash(pnt, ESI_BYTES, 0xa5a5a55a); } /* * Compare two ESIs. */ static bool esi_cmp(const void *p1, const void *p2) { const struct evpnes *pes1 = p1; const struct evpnes *pes2 = p2; if (pes1 == NULL && pes2 == NULL) return true; if (pes1 == NULL || pes2 == NULL) return false; return (memcmp(pes1->esi.val, pes2->esi.val, ESI_BYTES) == 0); } /* * Make vni hash key. */ static unsigned int vni_hash_key_make(const void *p) { const struct bgpevpn *vpn = p; return (jhash_1word(vpn->vni, 0)); } /* * Comparison function for vni hash */ static bool vni_hash_cmp(const void *p1, const void *p2) { const struct bgpevpn *vpn1 = p1; const struct bgpevpn *vpn2 = p2; if (!vpn1 && !vpn2) return true; if (!vpn1 || !vpn2) return false; return (vpn1->vni == vpn2->vni); } static int vni_list_cmp(void *p1, void *p2) { const struct bgpevpn *vpn1 = p1; const struct bgpevpn *vpn2 = p2; return vpn1->vni - vpn2->vni; } /* * Make vrf import route target hash key. */ static unsigned int vrf_import_rt_hash_key_make(const void *p) { const struct vrf_irt_node *irt = p; const char *pnt = irt->rt.val; return jhash(pnt, 8, 0x5abc1234); } /* * Comparison function for vrf import rt hash */ static bool vrf_import_rt_hash_cmp(const void *p1, const void *p2) { const struct vrf_irt_node *irt1 = p1; const struct vrf_irt_node *irt2 = p2; if (irt1 == NULL && irt2 == NULL) return true; if (irt1 == NULL || irt2 == NULL) return false; return (memcmp(irt1->rt.val, irt2->rt.val, ECOMMUNITY_SIZE) == 0); } /* * Create a new vrf import_rt in evpn instance */ static struct vrf_irt_node *vrf_import_rt_new(struct ecommunity_val *rt) { struct bgp *bgp_evpn = NULL; struct vrf_irt_node *irt; bgp_evpn = bgp_get_evpn(); if (!bgp_evpn) { flog_err(EC_BGP_NO_DFLT, "vrf import rt new - evpn instance not created yet"); return NULL; } irt = XCALLOC(MTYPE_BGP_EVPN_VRF_IMPORT_RT, sizeof(struct vrf_irt_node)); irt->rt = *rt; irt->vrfs = list_new(); /* Add to hash */ if (!hash_get(bgp_evpn->vrf_import_rt_hash, irt, hash_alloc_intern)) { XFREE(MTYPE_BGP_EVPN_VRF_IMPORT_RT, irt); return NULL; } return irt; } /* * Free the vrf import rt node */ static void vrf_import_rt_free(struct vrf_irt_node *irt) { struct bgp *bgp_evpn = NULL; bgp_evpn = bgp_get_evpn(); if (!bgp_evpn) { flog_err(EC_BGP_NO_DFLT, "vrf import rt free - evpn instance not created yet"); return; } hash_release(bgp_evpn->vrf_import_rt_hash, irt); list_delete(&irt->vrfs); XFREE(MTYPE_BGP_EVPN_VRF_IMPORT_RT, irt); } /* * Function to lookup Import RT node - used to map a RT to set of * VNIs importing routes with that RT. */ static struct vrf_irt_node *lookup_vrf_import_rt(struct ecommunity_val *rt) { struct bgp *bgp_evpn = NULL; struct vrf_irt_node *irt; struct vrf_irt_node tmp; bgp_evpn = bgp_get_evpn(); if (!bgp_evpn) { flog_err( EC_BGP_NO_DFLT, "vrf import rt lookup - evpn instance not created yet"); return NULL; } memset(&tmp, 0, sizeof(struct vrf_irt_node)); memcpy(&tmp.rt, rt, ECOMMUNITY_SIZE); irt = hash_lookup(bgp_evpn->vrf_import_rt_hash, &tmp); return irt; } /* * Is specified VRF present on the RT's list of "importing" VRFs? */ static int is_vrf_present_in_irt_vrfs(struct list *vrfs, struct bgp *bgp_vrf) { struct listnode *node = NULL, *nnode = NULL; struct bgp *tmp_bgp_vrf = NULL; for (ALL_LIST_ELEMENTS(vrfs, node, nnode, tmp_bgp_vrf)) { if (tmp_bgp_vrf == bgp_vrf) return 1; } return 0; } /* * Make import route target hash key. */ static unsigned int import_rt_hash_key_make(const void *p) { const struct irt_node *irt = p; const char *pnt = irt->rt.val; return jhash(pnt, 8, 0xdeadbeef); } /* * Comparison function for import rt hash */ static bool import_rt_hash_cmp(const void *p1, const void *p2) { const struct irt_node *irt1 = p1; const struct irt_node *irt2 = p2; if (irt1 == NULL && irt2 == NULL) return true; if (irt1 == NULL || irt2 == NULL) return false; return (memcmp(irt1->rt.val, irt2->rt.val, ECOMMUNITY_SIZE) == 0); } /* * Create a new import_rt */ static struct irt_node *import_rt_new(struct bgp *bgp, struct ecommunity_val *rt) { struct irt_node *irt; if (!bgp) return NULL; irt = XCALLOC(MTYPE_BGP_EVPN_IMPORT_RT, sizeof(struct irt_node)); irt->rt = *rt; irt->vnis = list_new(); /* Add to hash */ if (!hash_get(bgp->import_rt_hash, irt, hash_alloc_intern)) { XFREE(MTYPE_BGP_EVPN_IMPORT_RT, irt); return NULL; } return irt; } /* * Free the import rt node */ static void import_rt_free(struct bgp *bgp, struct irt_node *irt) { hash_release(bgp->import_rt_hash, irt); list_delete(&irt->vnis); XFREE(MTYPE_BGP_EVPN_IMPORT_RT, irt); } /* * Function to lookup Import RT node - used to map a RT to set of * VNIs importing routes with that RT. */ static struct irt_node *lookup_import_rt(struct bgp *bgp, struct ecommunity_val *rt) { struct irt_node *irt; struct irt_node tmp; memset(&tmp, 0, sizeof(struct irt_node)); memcpy(&tmp.rt, rt, ECOMMUNITY_SIZE); irt = hash_lookup(bgp->import_rt_hash, &tmp); return irt; } /* * Is specified VNI present on the RT's list of "importing" VNIs? */ static int is_vni_present_in_irt_vnis(struct list *vnis, struct bgpevpn *vpn) { struct listnode *node, *nnode; struct bgpevpn *tmp_vpn; for (ALL_LIST_ELEMENTS(vnis, node, nnode, tmp_vpn)) { if (tmp_vpn == vpn) return 1; } return 0; } /* * Compare Route Targets. */ static int evpn_route_target_cmp(struct ecommunity *ecom1, struct ecommunity *ecom2) { if (ecom1 && !ecom2) return -1; if (!ecom1 && ecom2) return 1; if (!ecom1 && !ecom2) return 0; if (ecom1->str && !ecom2->str) return -1; if (!ecom1->str && ecom2->str) return 1; if (!ecom1->str && !ecom2->str) return 0; return strcmp(ecom1->str, ecom2->str); } static void evpn_xxport_delete_ecomm(void *val) { struct ecommunity *ecomm = val; ecommunity_free(&ecomm); } /* * Mask off global-admin field of specified extended community (RT), * just retain the local-admin field. */ static inline void mask_ecom_global_admin(struct ecommunity_val *dst, struct ecommunity_val *src) { uint8_t type; type = src->val[0]; dst->val[0] = 0; if (type == ECOMMUNITY_ENCODE_AS) { dst->val[2] = dst->val[3] = 0; } else if (type == ECOMMUNITY_ENCODE_AS4 || type == ECOMMUNITY_ENCODE_IP) { dst->val[2] = dst->val[3] = 0; dst->val[4] = dst->val[5] = 0; } } /* * Map one RT to specified VRF. * bgp_vrf = BGP vrf instance */ static void map_vrf_to_rt(struct bgp *bgp_vrf, struct ecommunity_val *eval) { struct vrf_irt_node *irt = NULL; struct ecommunity_val eval_tmp; /* If using "automatic" RT, * we only care about the local-admin sub-field. * This is to facilitate using L3VNI(VRF-VNI) * as the RT for EBGP peering too. */ memcpy(&eval_tmp, eval, ECOMMUNITY_SIZE); if (!CHECK_FLAG(bgp_vrf->vrf_flags, BGP_VRF_IMPORT_RT_CFGD)) mask_ecom_global_admin(&eval_tmp, eval); irt = lookup_vrf_import_rt(&eval_tmp); if (irt && is_vrf_present_in_irt_vrfs(irt->vrfs, bgp_vrf)) /* Already mapped. */ return; if (!irt) irt = vrf_import_rt_new(&eval_tmp); /* Add VRF to the list for this RT. */ listnode_add(irt->vrfs, bgp_vrf); } /* * Unmap specified VRF from specified RT. If there are no other * VRFs for this RT, then the RT hash is deleted. * bgp_vrf: BGP VRF specific instance */ static void unmap_vrf_from_rt(struct bgp *bgp_vrf, struct vrf_irt_node *irt) { /* Delete VRF from list for this RT. */ listnode_delete(irt->vrfs, bgp_vrf); if (!listnode_head(irt->vrfs)) { vrf_import_rt_free(irt); } } /* * Map one RT to specified VNI. */ static void map_vni_to_rt(struct bgp *bgp, struct bgpevpn *vpn, struct ecommunity_val *eval) { struct irt_node *irt; struct ecommunity_val eval_tmp; /* If using "automatic" RT, we only care about the local-admin * sub-field. * This is to facilitate using VNI as the RT for EBGP peering too. */ memcpy(&eval_tmp, eval, ECOMMUNITY_SIZE); if (!is_import_rt_configured(vpn)) mask_ecom_global_admin(&eval_tmp, eval); irt = lookup_import_rt(bgp, &eval_tmp); if (irt) if (is_vni_present_in_irt_vnis(irt->vnis, vpn)) /* Already mapped. */ return; if (!irt) { irt = import_rt_new(bgp, &eval_tmp); assert(irt); } /* Add VNI to the hash list for this RT. */ listnode_add(irt->vnis, vpn); } /* * Unmap specified VNI from specified RT. If there are no other * VNIs for this RT, then the RT hash is deleted. */ static void unmap_vni_from_rt(struct bgp *bgp, struct bgpevpn *vpn, struct irt_node *irt) { /* Delete VNI from hash list for this RT. */ listnode_delete(irt->vnis, vpn); if (!listnode_head(irt->vnis)) { import_rt_free(bgp, irt); } } /* * Create RT extended community automatically from passed information: * of the form AS:VNI. * NOTE: We use only the lower 16 bits of the AS. This is sufficient as * the need is to get a RT value that will be unique across different * VNIs but the same across routers (in the same AS) for a particular * VNI. */ static void form_auto_rt(struct bgp *bgp, vni_t vni, struct list *rtl) { struct ecommunity_val eval; struct ecommunity *ecomadd; if (bgp->advertise_autort_rfc8365) vni |= EVPN_AUTORT_VXLAN; encode_route_target_as((bgp->as & 0xFFFF), vni, &eval); ecomadd = ecommunity_new(); ecommunity_add_val(ecomadd, &eval); listnode_add_sort(rtl, ecomadd); } /* * Derive RD and RT for a VNI automatically. Invoked at the time of * creation of a VNI. */ static void derive_rd_rt_for_vni(struct bgp *bgp, struct bgpevpn *vpn) { bgp_evpn_derive_auto_rd(bgp, vpn); bgp_evpn_derive_auto_rt_import(bgp, vpn); bgp_evpn_derive_auto_rt_export(bgp, vpn); } /* * Convert nexthop (remote VTEP IP) into an IPv6 address. */ static void evpn_convert_nexthop_to_ipv6(struct attr *attr) { if (BGP_ATTR_NEXTHOP_AFI_IP6(attr)) return; ipv4_to_ipv4_mapped_ipv6(&attr->mp_nexthop_global, attr->nexthop); attr->mp_nexthop_len = IPV6_MAX_BYTELEN; } /* * Add (update) or delete MACIP from zebra. */ static int bgp_zebra_send_remote_macip(struct bgp *bgp, struct bgpevpn *vpn, struct prefix_evpn *p, struct in_addr remote_vtep_ip, int add, uint8_t flags, uint32_t seq) { struct stream *s; int ipa_len; char buf1[ETHER_ADDR_STRLEN]; char buf2[INET6_ADDRSTRLEN]; char buf3[INET6_ADDRSTRLEN]; /* Check socket. */ if (!zclient || zclient->sock < 0) return 0; /* Don't try to register if Zebra doesn't know of this instance. */ if (!IS_BGP_INST_KNOWN_TO_ZEBRA(bgp)) { if (BGP_DEBUG(zebra, ZEBRA)) zlog_debug("%s: No zebra instance to talk to, not installing remote macip", __PRETTY_FUNCTION__); return 0; } s = zclient->obuf; stream_reset(s); zclient_create_header( s, add ? ZEBRA_REMOTE_MACIP_ADD : ZEBRA_REMOTE_MACIP_DEL, bgp->vrf_id); stream_putl(s, vpn->vni); stream_put(s, &p->prefix.macip_addr.mac.octet, ETH_ALEN); /* Mac Addr */ /* IP address length and IP address, if any. */ if (is_evpn_prefix_ipaddr_none(p)) stream_putl(s, 0); else { ipa_len = is_evpn_prefix_ipaddr_v4(p) ? IPV4_MAX_BYTELEN : IPV6_MAX_BYTELEN; stream_putl(s, ipa_len); stream_put(s, &p->prefix.macip_addr.ip.ip.addr, ipa_len); } stream_put_in_addr(s, &remote_vtep_ip); /* TX flags - MAC sticky status and/or gateway mac */ /* Also TX the sequence number of the best route. */ if (add) { stream_putc(s, flags); stream_putl(s, seq); } stream_putw_at(s, 0, stream_get_endp(s)); if (bgp_debug_zebra(NULL)) zlog_debug( "Tx %s MACIP, VNI %u MAC %s IP %s flags 0x%x seq %u remote VTEP %s", add ? "ADD" : "DEL", vpn->vni, prefix_mac2str(&p->prefix.macip_addr.mac, buf1, sizeof(buf1)), ipaddr2str(&p->prefix.macip_addr.ip, buf3, sizeof(buf3)), flags, seq, inet_ntop(AF_INET, &remote_vtep_ip, buf2, sizeof(buf2))); return zclient_send_message(zclient); } /* * Add (update) or delete remote VTEP from zebra. */ static int bgp_zebra_send_remote_vtep(struct bgp *bgp, struct bgpevpn *vpn, struct prefix_evpn *p, int flood_control, int add) { struct stream *s; /* Check socket. */ if (!zclient || zclient->sock < 0) return 0; /* Don't try to register if Zebra doesn't know of this instance. */ if (!IS_BGP_INST_KNOWN_TO_ZEBRA(bgp)) { if (BGP_DEBUG(zebra, ZEBRA)) zlog_debug("%s: No zebra instance to talk to, not installing remote vtep", __PRETTY_FUNCTION__); return 0; } s = zclient->obuf; stream_reset(s); zclient_create_header( s, add ? ZEBRA_REMOTE_VTEP_ADD : ZEBRA_REMOTE_VTEP_DEL, bgp->vrf_id); stream_putl(s, vpn->vni); if (is_evpn_prefix_ipaddr_v4(p)) stream_put_in_addr(s, &p->prefix.imet_addr.ip.ipaddr_v4); else if (is_evpn_prefix_ipaddr_v6(p)) { flog_err( EC_BGP_VTEP_INVALID, "Bad remote IP when trying to %s remote VTEP for VNI %u", add ? "ADD" : "DEL", vpn->vni); return -1; } stream_putl(s, flood_control); stream_putw_at(s, 0, stream_get_endp(s)); if (bgp_debug_zebra(NULL)) zlog_debug("Tx %s Remote VTEP, VNI %u remote VTEP %s", add ? "ADD" : "DEL", vpn->vni, inet_ntoa(p->prefix.imet_addr.ip.ipaddr_v4)); return zclient_send_message(zclient); } /* * Build extended community for EVPN ES (type-4) route */ static void build_evpn_type4_route_extcomm(struct evpnes *es, struct attr *attr) { struct ecommunity ecom_encap; struct ecommunity ecom_es_rt; struct ecommunity_val eval; struct ecommunity_val eval_es_rt; bgp_encap_types tnl_type; struct ethaddr mac; /* Encap */ tnl_type = BGP_ENCAP_TYPE_VXLAN; memset(&ecom_encap, 0, sizeof(ecom_encap)); encode_encap_extcomm(tnl_type, &eval); ecom_encap.size = 1; ecom_encap.val = (uint8_t *)eval.val; attr->ecommunity = ecommunity_dup(&ecom_encap); /* ES import RT */ memset(&mac, 0, sizeof(struct ethaddr)); memset(&ecom_es_rt, 0, sizeof(ecom_es_rt)); es_get_system_mac(&es->esi, &mac); encode_es_rt_extcomm(&eval_es_rt, &mac); ecom_es_rt.size = 1; ecom_es_rt.val = (uint8_t *)eval_es_rt.val; attr->ecommunity = ecommunity_merge(attr->ecommunity, &ecom_es_rt); attr->flag |= ATTR_FLAG_BIT(BGP_ATTR_EXT_COMMUNITIES); } /* * Build extended communities for EVPN prefix route. */ static void build_evpn_type5_route_extcomm(struct bgp *bgp_vrf, struct attr *attr) { struct ecommunity ecom_encap; struct ecommunity ecom_rmac; struct ecommunity_val eval; struct ecommunity_val eval_rmac; bgp_encap_types tnl_type; struct listnode *node, *nnode; struct ecommunity *ecom; struct list *vrf_export_rtl = NULL; /* Encap */ tnl_type = BGP_ENCAP_TYPE_VXLAN; memset(&ecom_encap, 0, sizeof(ecom_encap)); encode_encap_extcomm(tnl_type, &eval); ecom_encap.size = 1; ecom_encap.val = (uint8_t *)eval.val; /* Add Encap */ attr->ecommunity = ecommunity_dup(&ecom_encap); /* Add the export RTs for L3VNI/VRF */ vrf_export_rtl = bgp_vrf->vrf_export_rtl; for (ALL_LIST_ELEMENTS(vrf_export_rtl, node, nnode, ecom)) attr->ecommunity = ecommunity_merge(attr->ecommunity, ecom); /* add the router mac extended community */ if (!is_zero_mac(&attr->rmac)) { memset(&ecom_rmac, 0, sizeof(ecom_rmac)); encode_rmac_extcomm(&eval_rmac, &attr->rmac); ecom_rmac.size = 1; ecom_rmac.val = (uint8_t *)eval_rmac.val; attr->ecommunity = ecommunity_merge(attr->ecommunity, &ecom_rmac); } attr->flag |= ATTR_FLAG_BIT(BGP_ATTR_EXT_COMMUNITIES); } /* * Build extended communities for EVPN route. * This function is applicable for type-2 and type-3 routes. The layer-2 RT * and ENCAP extended communities are applicable for all routes. * The default gateway extended community and MAC mobility (sticky) extended * community are added as needed based on passed settings - only for type-2 * routes. Likewise, the layer-3 RT and Router MAC extended communities are * added, if present, based on passed settings - only for non-link-local * type-2 routes. */ static void build_evpn_route_extcomm(struct bgpevpn *vpn, struct attr *attr, int add_l3_ecomm) { struct ecommunity ecom_encap; struct ecommunity ecom_sticky; struct ecommunity ecom_default_gw; struct ecommunity ecom_rmac; struct ecommunity ecom_na; struct ecommunity_val eval; struct ecommunity_val eval_sticky; struct ecommunity_val eval_default_gw; struct ecommunity_val eval_rmac; struct ecommunity_val eval_na; bgp_encap_types tnl_type; struct listnode *node, *nnode; struct ecommunity *ecom; uint32_t seqnum; struct list *vrf_export_rtl = NULL; /* Encap */ tnl_type = BGP_ENCAP_TYPE_VXLAN; memset(&ecom_encap, 0, sizeof(ecom_encap)); encode_encap_extcomm(tnl_type, &eval); ecom_encap.size = 1; ecom_encap.val = (uint8_t *)eval.val; /* Add Encap */ attr->ecommunity = ecommunity_dup(&ecom_encap); /* Add the export RTs for L2VNI */ for (ALL_LIST_ELEMENTS(vpn->export_rtl, node, nnode, ecom)) attr->ecommunity = ecommunity_merge(attr->ecommunity, ecom); /* Add the export RTs for L3VNI if told to - caller determines * when this should be done. */ if (add_l3_ecomm) { vrf_export_rtl = bgpevpn_get_vrf_export_rtl(vpn); if (vrf_export_rtl && !list_isempty(vrf_export_rtl)) { for (ALL_LIST_ELEMENTS(vrf_export_rtl, node, nnode, ecom)) attr->ecommunity = ecommunity_merge( attr->ecommunity, ecom); } } /* Add MAC mobility (sticky) if needed. */ if (attr->sticky) { seqnum = 0; memset(&ecom_sticky, 0, sizeof(ecom_sticky)); encode_mac_mobility_extcomm(1, seqnum, &eval_sticky); ecom_sticky.size = 1; ecom_sticky.val = (uint8_t *)eval_sticky.val; attr->ecommunity = ecommunity_merge(attr->ecommunity, &ecom_sticky); } /* Add RMAC, if told to. */ if (add_l3_ecomm) { memset(&ecom_rmac, 0, sizeof(ecom_rmac)); encode_rmac_extcomm(&eval_rmac, &attr->rmac); ecom_rmac.size = 1; ecom_rmac.val = (uint8_t *)eval_rmac.val; attr->ecommunity = ecommunity_merge(attr->ecommunity, &ecom_rmac); } /* Add default gateway, if needed. */ if (attr->default_gw) { memset(&ecom_default_gw, 0, sizeof(ecom_default_gw)); encode_default_gw_extcomm(&eval_default_gw); ecom_default_gw.size = 1; ecom_default_gw.val = (uint8_t *)eval_default_gw.val; attr->ecommunity = ecommunity_merge(attr->ecommunity, &ecom_default_gw); } if (attr->router_flag) { memset(&ecom_na, 0, sizeof(ecom_na)); encode_na_flag_extcomm(&eval_na, attr->router_flag); ecom_na.size = 1; ecom_na.val = (uint8_t *)eval_na.val; attr->ecommunity = ecommunity_merge(attr->ecommunity, &ecom_na); } attr->flag |= ATTR_FLAG_BIT(BGP_ATTR_EXT_COMMUNITIES); } /* * Add MAC mobility extended community to attribute. */ static void add_mac_mobility_to_attr(uint32_t seq_num, struct attr *attr) { struct ecommunity ecom_tmp; struct ecommunity_val eval; uint8_t *ecom_val_ptr; int i; uint8_t *pnt; int type = 0; int sub_type = 0; /* Build MM */ encode_mac_mobility_extcomm(0, seq_num, &eval); /* Find current MM ecommunity */ ecom_val_ptr = NULL; if (attr->ecommunity) { for (i = 0; i < attr->ecommunity->size; i++) { pnt = attr->ecommunity->val + (i * 8); type = *pnt++; sub_type = *pnt++; if (type == ECOMMUNITY_ENCODE_EVPN && sub_type == ECOMMUNITY_EVPN_SUBTYPE_MACMOBILITY) { ecom_val_ptr = (uint8_t *)(attr->ecommunity->val + (i * 8)); break; } } } /* Update the existing MM ecommunity */ if (ecom_val_ptr) { memcpy(ecom_val_ptr, eval.val, sizeof(char) * ECOMMUNITY_SIZE); } /* Add MM to existing */ else { memset(&ecom_tmp, 0, sizeof(ecom_tmp)); ecom_tmp.size = 1; ecom_tmp.val = (uint8_t *)eval.val; if (attr->ecommunity) attr->ecommunity = ecommunity_merge(attr->ecommunity, &ecom_tmp); else attr->ecommunity = ecommunity_dup(&ecom_tmp); } } /* Install EVPN route into zebra. */ static int evpn_zebra_install(struct bgp *bgp, struct bgpevpn *vpn, struct prefix_evpn *p, struct bgp_path_info *pi) { int ret; uint8_t flags; int flood_control; if (p->prefix.route_type == BGP_EVPN_MAC_IP_ROUTE) { flags = 0; if (pi->attr->sticky) SET_FLAG(flags, ZEBRA_MACIP_TYPE_STICKY); if (pi->attr->default_gw) SET_FLAG(flags, ZEBRA_MACIP_TYPE_GW); if (is_evpn_prefix_ipaddr_v6(p) && pi->attr->router_flag) SET_FLAG(flags, ZEBRA_MACIP_TYPE_ROUTER_FLAG); ret = bgp_zebra_send_remote_macip( bgp, vpn, p, pi->attr->nexthop, 1, flags, mac_mobility_seqnum(pi->attr)); } else { switch (pi->attr->pmsi_tnl_type) { case PMSI_TNLTYPE_INGR_REPL: flood_control = VXLAN_FLOOD_HEAD_END_REPL; break; case PMSI_TNLTYPE_PIM_SM: flood_control = VXLAN_FLOOD_PIM_SM; break; default: flood_control = VXLAN_FLOOD_DISABLED; break; } ret = bgp_zebra_send_remote_vtep(bgp, vpn, p, flood_control, 1); } return ret; } /* Uninstall EVPN route from zebra. */ static int evpn_zebra_uninstall(struct bgp *bgp, struct bgpevpn *vpn, struct prefix_evpn *p, struct in_addr remote_vtep_ip) { int ret; if (p->prefix.route_type == BGP_EVPN_MAC_IP_ROUTE) ret = bgp_zebra_send_remote_macip(bgp, vpn, p, remote_vtep_ip, 0, 0, 0); else ret = bgp_zebra_send_remote_vtep(bgp, vpn, p, VXLAN_FLOOD_DISABLED, 0); return ret; } /* * Due to MAC mobility, the prior "local" best route has been supplanted * by a "remote" best route. The prior route has to be deleted and withdrawn * from peers. */ static void evpn_delete_old_local_route(struct bgp *bgp, struct bgpevpn *vpn, struct bgp_node *rn, struct bgp_path_info *old_local) { struct bgp_node *global_rn; struct bgp_path_info *pi; afi_t afi = AFI_L2VPN; safi_t safi = SAFI_EVPN; /* Locate route node in the global EVPN routing table. Note that * this table is a 2-level tree (RD-level + Prefix-level) similar to * L3VPN routes. */ global_rn = bgp_afi_node_lookup(bgp->rib[afi][safi], afi, safi, (struct prefix *)&rn->p, &vpn->prd); if (global_rn) { /* Delete route entry in the global EVPN table. */ delete_evpn_route_entry(bgp, afi, safi, global_rn, &pi); /* Schedule for processing - withdraws to peers happen from * this table. */ if (pi) bgp_process(bgp, global_rn, afi, safi); bgp_unlock_node(global_rn); } /* Delete route entry in the VNI route table, caller to remove. */ bgp_path_info_delete(rn, old_local); } static struct in_addr *es_vtep_new(struct in_addr vtep) { struct in_addr *ip; ip = XCALLOC(MTYPE_BGP_EVPN_ES_VTEP, sizeof(struct in_addr)); ip->s_addr = vtep.s_addr; return ip; } static void es_vtep_free(struct in_addr *ip) { XFREE(MTYPE_BGP_EVPN_ES_VTEP, ip); } /* check if VTEP is already part of the list */ static int is_vtep_present_in_list(struct list *list, struct in_addr vtep) { struct listnode *node = NULL; struct in_addr *tmp; for (ALL_LIST_ELEMENTS_RO(list, node, tmp)) { if (tmp->s_addr == vtep.s_addr) return 1; } return 0; } /* * Best path for ES route was changed, * update the list of VTEPs for this ES */ static int evpn_es_install_vtep(struct bgp *bgp, struct evpnes *es, struct prefix_evpn *p, struct in_addr rvtep) { struct in_addr *vtep_ip; if (is_vtep_present_in_list(es->vtep_list, rvtep)) return 0; vtep_ip = es_vtep_new(rvtep); if (vtep_ip) listnode_add_sort(es->vtep_list, vtep_ip); return 0; } /* * Best path for ES route was changed, * update the list of VTEPs for this ES */ static int evpn_es_uninstall_vtep(struct bgp *bgp, struct evpnes *es, struct prefix_evpn *p, struct in_addr rvtep) { struct listnode *node, *nnode, *node_to_del = NULL; struct in_addr *tmp; for (ALL_LIST_ELEMENTS(es->vtep_list, node, nnode, tmp)) { if (tmp->s_addr == rvtep.s_addr) { es_vtep_free(tmp); node_to_del = node; } } if (node_to_del) list_delete_node(es->vtep_list, node_to_del); return 0; } /* * Calculate the best path for a ES(type-4) route. */ static int evpn_es_route_select_install(struct bgp *bgp, struct evpnes *es, struct bgp_node *rn) { int ret = 0; afi_t afi = AFI_L2VPN; safi_t safi = SAFI_EVPN; struct bgp_path_info *old_select; /* old best */ struct bgp_path_info *new_select; /* new best */ struct bgp_path_info_pair old_and_new; /* Compute the best path. */ bgp_best_selection(bgp, rn, &bgp->maxpaths[afi][safi], &old_and_new, afi, safi); old_select = old_and_new.old; new_select = old_and_new.new; /* * If the best path hasn't changed - see if something needs to be * updated */ if (old_select && old_select == new_select && old_select->type == ZEBRA_ROUTE_BGP && old_select->sub_type == BGP_ROUTE_IMPORTED && !CHECK_FLAG(rn->flags, BGP_NODE_USER_CLEAR) && !CHECK_FLAG(old_select->flags, BGP_PATH_ATTR_CHANGED) && !bgp_addpath_is_addpath_used(&bgp->tx_addpath, afi, safi)) { if (bgp_zebra_has_route_changed(rn, old_select)) { ret = evpn_es_install_vtep(bgp, es, (struct prefix_evpn *)&rn->p, old_select->attr->nexthop); } UNSET_FLAG(old_select->flags, BGP_PATH_MULTIPATH_CHG); bgp_zebra_clear_route_change_flags(rn); return ret; } /* If the user did a "clear" this flag will be set */ UNSET_FLAG(rn->flags, BGP_NODE_USER_CLEAR); /* * bestpath has changed; update relevant fields and install or uninstall * into the zebra RIB. */ if (old_select || new_select) bgp_bump_version(rn); if (old_select) bgp_path_info_unset_flag(rn, old_select, BGP_PATH_SELECTED); if (new_select) { bgp_path_info_set_flag(rn, new_select, BGP_PATH_SELECTED); bgp_path_info_unset_flag(rn, new_select, BGP_PATH_ATTR_CHANGED); UNSET_FLAG(new_select->flags, BGP_PATH_MULTIPATH_CHG); } if (new_select && new_select->type == ZEBRA_ROUTE_BGP && new_select->sub_type == BGP_ROUTE_IMPORTED) { ret = evpn_es_install_vtep(bgp, es, (struct prefix_evpn *)&rn->p, new_select->attr->nexthop); } else { if (old_select && old_select->type == ZEBRA_ROUTE_BGP && old_select->sub_type == BGP_ROUTE_IMPORTED) ret = evpn_es_uninstall_vtep( bgp, es, (struct prefix_evpn *)&rn->p, old_select->attr->nexthop); } /* Clear any route change flags. */ bgp_zebra_clear_route_change_flags(rn); /* Reap old select bgp_path_info, if it has been removed */ if (old_select && CHECK_FLAG(old_select->flags, BGP_PATH_REMOVED)) bgp_path_info_reap(rn, old_select); return ret; } /* * Calculate the best path for an EVPN route. Install/update best path in zebra, * if appropriate. */ static int evpn_route_select_install(struct bgp *bgp, struct bgpevpn *vpn, struct bgp_node *rn) { struct bgp_path_info *old_select, *new_select; struct bgp_path_info_pair old_and_new; afi_t afi = AFI_L2VPN; safi_t safi = SAFI_EVPN; int ret = 0; /* Compute the best path. */ bgp_best_selection(bgp, rn, &bgp->maxpaths[afi][safi], &old_and_new, afi, safi); old_select = old_and_new.old; new_select = old_and_new.new; /* If the best path hasn't changed - see if there is still something to * update * to zebra RIB. */ if (old_select && old_select == new_select && old_select->type == ZEBRA_ROUTE_BGP && old_select->sub_type == BGP_ROUTE_IMPORTED && !CHECK_FLAG(rn->flags, BGP_NODE_USER_CLEAR) && !CHECK_FLAG(old_select->flags, BGP_PATH_ATTR_CHANGED) && !bgp_addpath_is_addpath_used(&bgp->tx_addpath, afi, safi)) { if (bgp_zebra_has_route_changed(rn, old_select)) ret = evpn_zebra_install( bgp, vpn, (struct prefix_evpn *)&rn->p, old_select); UNSET_FLAG(old_select->flags, BGP_PATH_MULTIPATH_CHG); bgp_zebra_clear_route_change_flags(rn); return ret; } /* If the user did a "clear" this flag will be set */ UNSET_FLAG(rn->flags, BGP_NODE_USER_CLEAR); /* bestpath has changed; update relevant fields and install or uninstall * into the zebra RIB. */ if (old_select || new_select) bgp_bump_version(rn); if (old_select) bgp_path_info_unset_flag(rn, old_select, BGP_PATH_SELECTED); if (new_select) { bgp_path_info_set_flag(rn, new_select, BGP_PATH_SELECTED); bgp_path_info_unset_flag(rn, new_select, BGP_PATH_ATTR_CHANGED); UNSET_FLAG(new_select->flags, BGP_PATH_MULTIPATH_CHG); } if (new_select && new_select->type == ZEBRA_ROUTE_BGP && new_select->sub_type == BGP_ROUTE_IMPORTED) { ret = evpn_zebra_install(bgp, vpn, (struct prefix_evpn *)&rn->p, new_select); /* If an old best existed and it was a "local" route, the only * reason * it would be supplanted is due to MAC mobility procedures. So, * we * need to do an implicit delete and withdraw that route from * peers. */ if (old_select && old_select->peer == bgp->peer_self && old_select->type == ZEBRA_ROUTE_BGP && old_select->sub_type == BGP_ROUTE_STATIC) evpn_delete_old_local_route(bgp, vpn, rn, old_select); } else { if (old_select && old_select->type == ZEBRA_ROUTE_BGP && old_select->sub_type == BGP_ROUTE_IMPORTED) ret = evpn_zebra_uninstall(bgp, vpn, (struct prefix_evpn *)&rn->p, old_select->attr->nexthop); } /* Clear any route change flags. */ bgp_zebra_clear_route_change_flags(rn); /* Reap old select bgp_path_info, if it has been removed */ if (old_select && CHECK_FLAG(old_select->flags, BGP_PATH_REMOVED)) bgp_path_info_reap(rn, old_select); return ret; } /* * Return true if the local ri for this rn is of type gateway mac */ static int evpn_route_is_def_gw(struct bgp *bgp, struct bgp_node *rn) { struct bgp_path_info *tmp_pi = NULL; struct bgp_path_info *local_pi = NULL; local_pi = NULL; for (tmp_pi = bgp_node_get_bgp_path_info(rn); tmp_pi; tmp_pi = tmp_pi->next) { if (tmp_pi->peer == bgp->peer_self && tmp_pi->type == ZEBRA_ROUTE_BGP && tmp_pi->sub_type == BGP_ROUTE_STATIC) local_pi = tmp_pi; } if (!local_pi) return 0; return local_pi->attr->default_gw; } /* * Return true if the local ri for this rn has sticky set */ static int evpn_route_is_sticky(struct bgp *bgp, struct bgp_node *rn) { struct bgp_path_info *tmp_pi; struct bgp_path_info *local_pi; local_pi = NULL; for (tmp_pi = bgp_node_get_bgp_path_info(rn); tmp_pi; tmp_pi = tmp_pi->next) { if (tmp_pi->peer == bgp->peer_self && tmp_pi->type == ZEBRA_ROUTE_BGP && tmp_pi->sub_type == BGP_ROUTE_STATIC) local_pi = tmp_pi; } if (!local_pi) return 0; return local_pi->attr->sticky; } /* * create or update EVPN type4 route entry. * This could be in the ES table or the global table. * TODO: handle remote ES (type4) routes as well */ static int update_evpn_type4_route_entry(struct bgp *bgp, struct evpnes *es, afi_t afi, safi_t safi, struct bgp_node *rn, struct attr *attr, int add, struct bgp_path_info **ri, int *route_changed) { char buf[ESI_STR_LEN]; char buf1[INET6_ADDRSTRLEN]; struct bgp_path_info *tmp_pi = NULL; struct bgp_path_info *local_pi = NULL; /* local route entry if any */ struct bgp_path_info *remote_pi = NULL; /* remote route entry if any */ struct attr *attr_new = NULL; struct prefix_evpn *evp = NULL; *ri = NULL; *route_changed = 1; evp = (struct prefix_evpn *)&rn->p; /* locate the local and remote entries if any */ for (tmp_pi = bgp_node_get_bgp_path_info(rn); tmp_pi; tmp_pi = tmp_pi->next) { if (tmp_pi->peer == bgp->peer_self && tmp_pi->type == ZEBRA_ROUTE_BGP && tmp_pi->sub_type == BGP_ROUTE_STATIC) local_pi = tmp_pi; if (tmp_pi->type == ZEBRA_ROUTE_BGP && tmp_pi->sub_type == BGP_ROUTE_IMPORTED && CHECK_FLAG(tmp_pi->flags, BGP_PATH_VALID)) remote_pi = tmp_pi; } /* we don't expect to see a remote_ri at this point. * An ES route has esi + vtep_ip as the key, * We shouldn't see the same route from any other vtep. */ if (remote_pi) { flog_err( EC_BGP_ES_INVALID, "%u ERROR: local es route for ESI: %s Vtep %s also learnt from remote", bgp->vrf_id, esi_to_str(&evp->prefix.es_addr.esi, buf, sizeof(buf)), ipaddr2str(&es->originator_ip, buf1, sizeof(buf1))); return -1; } if (!local_pi && !add) return 0; /* create or update the entry */ if (!local_pi) { /* Add or update attribute to hash */ attr_new = bgp_attr_intern(attr); /* Create new route with its attribute. */ tmp_pi = info_make(ZEBRA_ROUTE_BGP, BGP_ROUTE_STATIC, 0, bgp->peer_self, attr_new, rn); SET_FLAG(tmp_pi->flags, BGP_PATH_VALID); /* add the newly created path to the route-node */ bgp_path_info_add(rn, tmp_pi); } else { tmp_pi = local_pi; if (attrhash_cmp(tmp_pi->attr, attr) && !CHECK_FLAG(tmp_pi->flags, BGP_PATH_REMOVED)) *route_changed = 0; else { /* The attribute has changed. * Add (or update) attribute to hash. */ attr_new = bgp_attr_intern(attr); bgp_path_info_set_flag(rn, tmp_pi, BGP_PATH_ATTR_CHANGED); /* Restore route, if needed. */ if (CHECK_FLAG(tmp_pi->flags, BGP_PATH_REMOVED)) bgp_path_info_restore(rn, tmp_pi); /* Unintern existing, set to new. */ bgp_attr_unintern(&tmp_pi->attr); tmp_pi->attr = attr_new; tmp_pi->uptime = bgp_clock(); } } /* Return back the route entry. */ *ri = tmp_pi; return 0; } /* update evpn es (type-4) route */ static int update_evpn_type4_route(struct bgp *bgp, struct evpnes *es, struct prefix_evpn *p) { int ret = 0; int route_changed = 0; char buf[ESI_STR_LEN]; char buf1[INET6_ADDRSTRLEN]; afi_t afi = AFI_L2VPN; safi_t safi = SAFI_EVPN; struct attr attr; struct attr *attr_new = NULL; struct bgp_node *rn = NULL; struct bgp_path_info *pi = NULL; memset(&attr, 0, sizeof(struct attr)); /* Build path-attribute for this route. */ bgp_attr_default_set(&attr, BGP_ORIGIN_IGP); attr.nexthop = es->originator_ip.ipaddr_v4; attr.mp_nexthop_global_in = es->originator_ip.ipaddr_v4; attr.mp_nexthop_len = BGP_ATTR_NHLEN_IPV4; /* Set up extended community. */ build_evpn_type4_route_extcomm(es, &attr); /* First, create (or fetch) route node within the ESI. */ /* NOTE: There is no RD here. */ rn = bgp_node_get(es->route_table, (struct prefix *)p); /* Create or update route entry. */ ret = update_evpn_type4_route_entry(bgp, es, afi, safi, rn, &attr, 1, &pi, &route_changed); if (ret != 0) { flog_err(EC_BGP_ES_INVALID, "%u ERROR: Failed to updated ES route ESI: %s VTEP %s", bgp->vrf_id, esi_to_str(&p->prefix.es_addr.esi, buf, sizeof(buf)), ipaddr2str(&es->originator_ip, buf1, sizeof(buf1))); } assert(pi); attr_new = pi->attr; /* Perform route selection; * this is just to set the flags correctly * as local route in the ES always wins. */ evpn_es_route_select_install(bgp, es, rn); bgp_unlock_node(rn); /* If this is a new route or some attribute has changed, export the * route to the global table. The route will be advertised to peers * from there. Note that this table is a 2-level tree (RD-level + * Prefix-level) similar to L3VPN routes. */ if (route_changed) { struct bgp_path_info *global_pi; rn = bgp_afi_node_get(bgp->rib[afi][safi], afi, safi, (struct prefix *)p, &es->prd); update_evpn_type4_route_entry(bgp, es, afi, safi, rn, attr_new, 1, &global_pi, &route_changed); /* Schedule for processing and unlock node. */ bgp_process(bgp, rn, afi, safi); bgp_unlock_node(rn); } /* Unintern temporary. */ aspath_unintern(&attr.aspath); return 0; } static int update_evpn_type5_route_entry(struct bgp *bgp_evpn, struct bgp *bgp_vrf, afi_t afi, safi_t safi, struct bgp_node *rn, struct attr *attr, int *route_changed) { struct attr *attr_new = NULL; struct bgp_path_info *pi = NULL; mpls_label_t label = MPLS_INVALID_LABEL; struct bgp_path_info *local_pi = NULL; struct bgp_path_info *tmp_pi = NULL; *route_changed = 0; /* locate the local route entry if any */ for (tmp_pi = bgp_node_get_bgp_path_info(rn); tmp_pi; tmp_pi = tmp_pi->next) { if (tmp_pi->peer == bgp_evpn->peer_self && tmp_pi->type == ZEBRA_ROUTE_BGP && tmp_pi->sub_type == BGP_ROUTE_STATIC) local_pi = tmp_pi; } /* * create a new route entry if one doesn't exist. * Otherwise see if route attr has changed */ if (!local_pi) { /* route has changed as this is the first entry */ *route_changed = 1; /* Add (or update) attribute to hash. */ attr_new = bgp_attr_intern(attr); /* create the route info from attribute */ pi = info_make(ZEBRA_ROUTE_BGP, BGP_ROUTE_STATIC, 0, bgp_evpn->peer_self, attr_new, rn); SET_FLAG(pi->flags, BGP_PATH_VALID); /* Type-5 routes advertise the L3-VNI */ bgp_path_info_extra_get(pi); vni2label(bgp_vrf->l3vni, &label); memcpy(&pi->extra->label, &label, sizeof(label)); pi->extra->num_labels = 1; /* add the route entry to route node*/ bgp_path_info_add(rn, pi); } else { tmp_pi = local_pi; if (!attrhash_cmp(tmp_pi->attr, attr)) { /* attribute changed */ *route_changed = 1; /* The attribute has changed. */ /* Add (or update) attribute to hash. */ attr_new = bgp_attr_intern(attr); bgp_path_info_set_flag(rn, tmp_pi, BGP_PATH_ATTR_CHANGED); /* Restore route, if needed. */ if (CHECK_FLAG(tmp_pi->flags, BGP_PATH_REMOVED)) bgp_path_info_restore(rn, tmp_pi); /* Unintern existing, set to new. */ bgp_attr_unintern(&tmp_pi->attr); tmp_pi->attr = attr_new; tmp_pi->uptime = bgp_clock(); } } return 0; } /* update evpn type-5 route entry */ static int update_evpn_type5_route(struct bgp *bgp_vrf, struct prefix_evpn *evp, struct attr *src_attr) { afi_t afi = AFI_L2VPN; safi_t safi = SAFI_EVPN; struct attr attr; struct bgp_node *rn = NULL; struct bgp *bgp_evpn = NULL; int route_changed = 0; bgp_evpn = bgp_get_evpn(); if (!bgp_evpn) return 0; /* Build path attribute for this route - use the source attr, if * present, else treat as locally originated. */ if (src_attr) attr = *src_attr; else { memset(&attr, 0, sizeof(struct attr)); bgp_attr_default_set(&attr, BGP_ORIGIN_IGP); } /* Set nexthop to ourselves and fill in the Router MAC. */ attr.nexthop = bgp_vrf->originator_ip; attr.mp_nexthop_global_in = bgp_vrf->originator_ip; attr.mp_nexthop_len = BGP_ATTR_NHLEN_IPV4; memcpy(&attr.rmac, &bgp_vrf->rmac, sizeof(struct ethaddr)); /* Setup RT and encap extended community */ build_evpn_type5_route_extcomm(bgp_vrf, &attr); /* get the route node in global table */ rn = bgp_afi_node_get(bgp_evpn->rib[afi][safi], afi, safi, (struct prefix *)evp, &bgp_vrf->vrf_prd); assert(rn); /* create or update the route entry within the route node */ update_evpn_type5_route_entry(bgp_evpn, bgp_vrf, afi, safi, rn, &attr, &route_changed); /* schedule for processing and unlock node */ if (route_changed) { bgp_process(bgp_evpn, rn, afi, safi); bgp_unlock_node(rn); } /* uninten temporary */ if (!src_attr) aspath_unintern(&attr.aspath); return 0; } /* * Create or update EVPN route entry. This could be in the VNI route table * or the global route table. */ static int update_evpn_route_entry(struct bgp *bgp, struct bgpevpn *vpn, afi_t afi, safi_t safi, struct bgp_node *rn, struct attr *attr, int add, struct bgp_path_info **pi, uint8_t flags, uint32_t seq) { struct bgp_path_info *tmp_pi; struct bgp_path_info *local_pi; struct attr *attr_new; mpls_label_t label[BGP_MAX_LABELS]; uint32_t num_labels = 1; int route_change = 1; uint8_t sticky = 0; struct prefix_evpn *evp; *pi = NULL; evp = (struct prefix_evpn *)&rn->p; memset(&label, 0, sizeof(label)); /* See if this is an update of an existing route, or a new add. */ local_pi = NULL; for (tmp_pi = bgp_node_get_bgp_path_info(rn); tmp_pi; tmp_pi = tmp_pi->next) { if (tmp_pi->peer == bgp->peer_self && tmp_pi->type == ZEBRA_ROUTE_BGP && tmp_pi->sub_type == BGP_ROUTE_STATIC) local_pi = tmp_pi; } /* If route doesn't exist already, create a new one, if told to. * Otherwise act based on whether the attributes of the route have * changed or not. */ if (!local_pi && !add) return 0; /* For non-GW MACs, update MAC mobility seq number, if needed. */ if (seq && !CHECK_FLAG(flags, ZEBRA_MACIP_TYPE_GW)) add_mac_mobility_to_attr(seq, attr); if (!local_pi) { /* Add (or update) attribute to hash. */ attr_new = bgp_attr_intern(attr); /* Extract MAC mobility sequence number, if any. */ attr_new->mm_seqnum = bgp_attr_mac_mobility_seqnum(attr_new, &sticky); attr_new->sticky = sticky; /* Create new route with its attribute. */ tmp_pi = info_make(ZEBRA_ROUTE_BGP, BGP_ROUTE_STATIC, 0, bgp->peer_self, attr_new, rn); SET_FLAG(tmp_pi->flags, BGP_PATH_VALID); bgp_path_info_extra_get(tmp_pi); /* The VNI goes into the 'label' field of the route */ vni2label(vpn->vni, &label[0]); /* Type-2 routes may carry a second VNI - the L3-VNI. * Only attach second label if we are advertising two labels for * type-2 routes. */ if (evp->prefix.route_type == BGP_EVPN_MAC_IP_ROUTE && CHECK_FLAG(vpn->flags, VNI_FLAG_USE_TWO_LABELS)) { vni_t l3vni; l3vni = bgpevpn_get_l3vni(vpn); if (l3vni) { vni2label(l3vni, &label[1]); num_labels++; } } memcpy(&tmp_pi->extra->label, label, sizeof(label)); tmp_pi->extra->num_labels = num_labels; bgp_path_info_add(rn, tmp_pi); } else { tmp_pi = local_pi; if (attrhash_cmp(tmp_pi->attr, attr) && !CHECK_FLAG(tmp_pi->flags, BGP_PATH_REMOVED)) route_change = 0; else { /* * The attributes have changed, type-2 routes needs to * be advertised with right labels. */ vni2label(vpn->vni, &label[0]); if (evp->prefix.route_type == BGP_EVPN_MAC_IP_ROUTE && CHECK_FLAG(vpn->flags, VNI_FLAG_USE_TWO_LABELS)) { vni_t l3vni; l3vni = bgpevpn_get_l3vni(vpn); if (l3vni) { vni2label(l3vni, &label[1]); num_labels++; } } memcpy(&tmp_pi->extra->label, label, sizeof(label)); tmp_pi->extra->num_labels = num_labels; /* The attribute has changed. */ /* Add (or update) attribute to hash. */ attr_new = bgp_attr_intern(attr); bgp_path_info_set_flag(rn, tmp_pi, BGP_PATH_ATTR_CHANGED); /* Extract MAC mobility sequence number, if any. */ attr_new->mm_seqnum = bgp_attr_mac_mobility_seqnum(attr_new, &sticky); attr_new->sticky = sticky; /* Restore route, if needed. */ if (CHECK_FLAG(tmp_pi->flags, BGP_PATH_REMOVED)) bgp_path_info_restore(rn, tmp_pi); /* Unintern existing, set to new. */ bgp_attr_unintern(&tmp_pi->attr); tmp_pi->attr = attr_new; tmp_pi->uptime = bgp_clock(); } } /* Return back the route entry. */ *pi = tmp_pi; return route_change; } static void evpn_zebra_reinstall_best_route(struct bgp *bgp, struct bgpevpn *vpn, struct bgp_node *rn) { struct bgp_path_info *tmp_ri; struct bgp_path_info *curr_select = NULL; for (tmp_ri = bgp_node_get_bgp_path_info(rn); tmp_ri; tmp_ri = tmp_ri->next) { if (CHECK_FLAG(tmp_ri->flags, BGP_PATH_SELECTED)) { curr_select = tmp_ri; break; } } if (curr_select && curr_select->type == ZEBRA_ROUTE_BGP && curr_select->sub_type == BGP_ROUTE_IMPORTED) evpn_zebra_install(bgp, vpn, (struct prefix_evpn *)&rn->p, curr_select); } /* * If the local route was not selected evict it and tell zebra to re-add * the best remote dest. * * Typically a local path added by zebra is expected to be selected as * best. In which case when a remote path wins as best (later) * evpn_route_select_install itself evicts the older-local-best path. * * However if bgp's add and zebra's add cross paths (race condition) it * is possible that the local path is no longer the "older" best path. * It is a path that was never designated as best and hence requires * additional handling to prevent bgp from injecting and holding on to a * non-best local path. */ static void evpn_cleanup_local_non_best_route(struct bgp *bgp, struct bgpevpn *vpn, struct bgp_node *rn, struct bgp_path_info *local_pi) { char buf[PREFIX_STRLEN]; /* local path was not picked as the winner; kick it out */ if (bgp_debug_zebra(NULL)) { zlog_debug("evicting local evpn prefix %s as remote won", prefix2str(&rn->p, buf, sizeof(buf))); } evpn_delete_old_local_route(bgp, vpn, rn, local_pi); bgp_path_info_reap(rn, local_pi); /* tell zebra to re-add the best remote path */ evpn_zebra_reinstall_best_route(bgp, vpn, rn); } /* * Create or update EVPN route (of type based on prefix) for specified VNI * and schedule for processing. */ static int update_evpn_route(struct bgp *bgp, struct bgpevpn *vpn, struct prefix_evpn *p, uint8_t flags, uint32_t seq) { struct bgp_node *rn; struct attr attr; struct attr *attr_new; int add_l3_ecomm = 0; struct bgp_path_info *pi; afi_t afi = AFI_L2VPN; safi_t safi = SAFI_EVPN; int route_change; memset(&attr, 0, sizeof(struct attr)); /* Build path-attribute for this route. */ bgp_attr_default_set(&attr, BGP_ORIGIN_IGP); attr.nexthop = vpn->originator_ip; attr.mp_nexthop_global_in = vpn->originator_ip; attr.mp_nexthop_len = BGP_ATTR_NHLEN_IPV4; attr.sticky = CHECK_FLAG(flags, ZEBRA_MACIP_TYPE_STICKY) ? 1 : 0; attr.default_gw = CHECK_FLAG(flags, ZEBRA_MACIP_TYPE_GW) ? 1 : 0; attr.router_flag = CHECK_FLAG(flags, ZEBRA_MACIP_TYPE_ROUTER_FLAG) ? 1 : 0; /* PMSI is only needed for type-3 routes */ if (p->prefix.route_type == BGP_EVPN_IMET_ROUTE) { attr.flag |= ATTR_FLAG_BIT(BGP_ATTR_PMSI_TUNNEL); attr.pmsi_tnl_type = PMSI_TNLTYPE_INGR_REPL; } /* router mac is only needed for type-2 routes here. */ if (p->prefix.route_type == BGP_EVPN_MAC_IP_ROUTE) bgpevpn_get_rmac(vpn, &attr.rmac); vni2label(vpn->vni, &(attr.label)); /* Include L3 VNI related RTs and RMAC for type-2 routes, if they're * IPv4 or IPv6 global addresses and we're advertising L3VNI with * these routes. */ if (p->prefix.route_type == BGP_EVPN_MAC_IP_ROUTE && (is_evpn_prefix_ipaddr_v4(p) || !IN6_IS_ADDR_LINKLOCAL(&p->prefix.macip_addr.ip.ipaddr_v6)) && CHECK_FLAG(vpn->flags, VNI_FLAG_USE_TWO_LABELS) && bgpevpn_get_l3vni(vpn)) add_l3_ecomm = 1; /* Set up extended community. */ build_evpn_route_extcomm(vpn, &attr, add_l3_ecomm); /* First, create (or fetch) route node within the VNI. */ /* NOTE: There is no RD here. */ rn = bgp_node_get(vpn->route_table, (struct prefix *)p); /* Create or update route entry. */ route_change = update_evpn_route_entry(bgp, vpn, afi, safi, rn, &attr, 1, &pi, flags, seq); assert(pi); attr_new = pi->attr; /* lock ri to prevent freeing in evpn_route_select_install */ bgp_path_info_lock(pi); /* Perform route selection; this is just to set the flags correctly * as local route in the VNI always wins. */ evpn_route_select_install(bgp, vpn, rn); /* * If the new local route was not selected evict it and tell zebra * to re-add the best remote dest. BGP doesn't retain non-best local * routes. */ if (!CHECK_FLAG(pi->flags, BGP_PATH_SELECTED)) { route_change = 0; evpn_cleanup_local_non_best_route(bgp, vpn, rn, pi); } bgp_path_info_unlock(pi); bgp_unlock_node(rn); /* If this is a new route or some attribute has changed, export the * route to the global table. The route will be advertised to peers * from there. Note that this table is a 2-level tree (RD-level + * Prefix-level) similar to L3VPN routes. */ if (route_change) { struct bgp_path_info *global_pi; rn = bgp_afi_node_get(bgp->rib[afi][safi], afi, safi, (struct prefix *)p, &vpn->prd); update_evpn_route_entry(bgp, vpn, afi, safi, rn, attr_new, 1, &global_pi, flags, seq); /* Schedule for processing and unlock node. */ bgp_process(bgp, rn, afi, safi); bgp_unlock_node(rn); } /* Unintern temporary. */ aspath_unintern(&attr.aspath); return 0; } /* * Delete EVPN route entry. * The entry can be in ESI/VNI table or the global table. */ static void delete_evpn_route_entry(struct bgp *bgp, afi_t afi, safi_t safi, struct bgp_node *rn, struct bgp_path_info **pi) { struct bgp_path_info *tmp_pi; *pi = NULL; /* Now, find matching route. */ for (tmp_pi = bgp_node_get_bgp_path_info(rn); tmp_pi; tmp_pi = tmp_pi->next) if (tmp_pi->peer == bgp->peer_self && tmp_pi->type == ZEBRA_ROUTE_BGP && tmp_pi->sub_type == BGP_ROUTE_STATIC) break; *pi = tmp_pi; /* Mark route for delete. */ if (tmp_pi) bgp_path_info_delete(rn, tmp_pi); } /* Delete EVPN ES (type-4) route */ static int delete_evpn_type4_route(struct bgp *bgp, struct evpnes *es, struct prefix_evpn *p) { afi_t afi = AFI_L2VPN; safi_t safi = SAFI_EVPN; struct bgp_path_info *pi; struct bgp_node *rn = NULL; /* rn in esi table */ struct bgp_node *global_rn = NULL; /* rn in global table */ /* First, locate the route node within the ESI. * If it doesn't exist, ther is nothing to do. * Note: there is no RD here. */ rn = bgp_node_lookup(es->route_table, (struct prefix *)p); if (!rn) return 0; /* Next, locate route node in the global EVPN routing table. * Note that this table is a 2-level tree (RD-level + Prefix-level) */ global_rn = bgp_afi_node_lookup(bgp->rib[afi][safi], afi, safi, (struct prefix *)p, &es->prd); if (global_rn) { /* Delete route entry in the global EVPN table. */ delete_evpn_route_entry(bgp, afi, safi, global_rn, &pi); /* Schedule for processing - withdraws to peers happen from * this table. */ if (pi) bgp_process(bgp, global_rn, afi, safi); bgp_unlock_node(global_rn); } /* * Delete route entry in the ESI route table. * This can just be removed. */ delete_evpn_route_entry(bgp, afi, safi, rn, &pi); if (pi) bgp_path_info_reap(rn, pi); bgp_unlock_node(rn); return 0; } /* Delete EVPN type5 route */ static int delete_evpn_type5_route(struct bgp *bgp_vrf, struct prefix_evpn *evp) { afi_t afi = AFI_L2VPN; safi_t safi = SAFI_EVPN; struct bgp_node *rn = NULL; struct bgp_path_info *pi = NULL; struct bgp *bgp_evpn = NULL; /* evpn bgp instance */ bgp_evpn = bgp_get_evpn(); if (!bgp_evpn) return 0; /* locate the global route entry for this type-5 prefix */ rn = bgp_afi_node_lookup(bgp_evpn->rib[afi][safi], afi, safi, (struct prefix *)evp, &bgp_vrf->vrf_prd); if (!rn) return 0; delete_evpn_route_entry(bgp_evpn, afi, safi, rn, &pi); if (pi) bgp_process(bgp_evpn, rn, afi, safi); bgp_unlock_node(rn); return 0; } /* * Delete EVPN route (of type based on prefix) for specified VNI and * schedule for processing. */ static int delete_evpn_route(struct bgp *bgp, struct bgpevpn *vpn, struct prefix_evpn *p) { struct bgp_node *rn, *global_rn; struct bgp_path_info *pi; afi_t afi = AFI_L2VPN; safi_t safi = SAFI_EVPN; /* First, locate the route node within the VNI. If it doesn't exist, * there * is nothing further to do. */ /* NOTE: There is no RD here. */ rn = bgp_node_lookup(vpn->route_table, (struct prefix *)p); if (!rn) return 0; /* Next, locate route node in the global EVPN routing table. Note that * this table is a 2-level tree (RD-level + Prefix-level) similar to * L3VPN routes. */ global_rn = bgp_afi_node_lookup(bgp->rib[afi][safi], afi, safi, (struct prefix *)p, &vpn->prd); if (global_rn) { /* Delete route entry in the global EVPN table. */ delete_evpn_route_entry(bgp, afi, safi, global_rn, &pi); /* Schedule for processing - withdraws to peers happen from * this table. */ if (pi) bgp_process(bgp, global_rn, afi, safi); bgp_unlock_node(global_rn); } /* Delete route entry in the VNI route table. This can just be removed. */ delete_evpn_route_entry(bgp, afi, safi, rn, &pi); if (pi) { bgp_path_info_reap(rn, pi); evpn_route_select_install(bgp, vpn, rn); } bgp_unlock_node(rn); return 0; } /* * Update all type-2 (MACIP) local routes for this VNI - these should also * be scheduled for advertise to peers. */ static int update_all_type2_routes(struct bgp *bgp, struct bgpevpn *vpn) { afi_t afi; safi_t safi; struct bgp_node *rn; struct bgp_path_info *pi, *tmp_pi; struct attr attr; struct attr *attr_new; uint32_t seq; int add_l3_ecomm = 0; afi = AFI_L2VPN; safi = SAFI_EVPN; /* Walk this VNI's route table and update local type-2 routes. For any * routes updated, update corresponding entry in the global table too. */ for (rn = bgp_table_top(vpn->route_table); rn; rn = bgp_route_next(rn)) { struct prefix_evpn *evp = (struct prefix_evpn *)&rn->p; struct bgp_node *rd_rn; struct bgp_path_info *global_pi; if (evp->prefix.route_type != BGP_EVPN_MAC_IP_ROUTE) continue; /* Identify local route. */ for (tmp_pi = bgp_node_get_bgp_path_info(rn); tmp_pi; tmp_pi = tmp_pi->next) { if (tmp_pi->peer == bgp->peer_self && tmp_pi->type == ZEBRA_ROUTE_BGP && tmp_pi->sub_type == BGP_ROUTE_STATIC) break; } if (!tmp_pi) continue; /* * Build attribute per local route as the MAC mobility and * some other values could differ for different routes. The * attributes will be shared in the hash table. */ bgp_attr_default_set(&attr, BGP_ORIGIN_IGP); attr.nexthop = vpn->originator_ip; attr.mp_nexthop_global_in = vpn->originator_ip; attr.mp_nexthop_len = BGP_ATTR_NHLEN_IPV4; bgpevpn_get_rmac(vpn, &attr.rmac); if (evpn_route_is_sticky(bgp, rn)) attr.sticky = 1; else if (evpn_route_is_def_gw(bgp, rn)) { attr.default_gw = 1; if (is_evpn_prefix_ipaddr_v6(evp)) attr.router_flag = 1; } /* Add L3 VNI RTs and RMAC for non IPv6 link-local if * using L3 VNI for type-2 routes also. */ if ((is_evpn_prefix_ipaddr_v4(evp) || !IN6_IS_ADDR_LINKLOCAL( &evp->prefix.macip_addr.ip.ipaddr_v6)) && CHECK_FLAG(vpn->flags, VNI_FLAG_USE_TWO_LABELS) && bgpevpn_get_l3vni(vpn)) add_l3_ecomm = 1; /* Set up extended community. */ build_evpn_route_extcomm(vpn, &attr, add_l3_ecomm); seq = mac_mobility_seqnum(tmp_pi->attr); /* Update the route entry. */ update_evpn_route_entry(bgp, vpn, afi, safi, rn, &attr, 0, &pi, 0, seq); /* Perform route selection; this is just to set the flags * correctly as local route in the VNI always wins. */ evpn_route_select_install(bgp, vpn, rn); attr_new = pi->attr; /* Update route in global routing table. */ rd_rn = bgp_afi_node_get(bgp->rib[afi][safi], afi, safi, (struct prefix *)evp, &vpn->prd); assert(rd_rn); update_evpn_route_entry(bgp, vpn, afi, safi, rd_rn, attr_new, 0, &global_pi, 0, mac_mobility_seqnum(attr_new)); /* Schedule for processing and unlock node. */ bgp_process(bgp, rd_rn, afi, safi); bgp_unlock_node(rd_rn); /* Unintern temporary. */ aspath_unintern(&attr.aspath); } return 0; } /* * Delete all type-2 (MACIP) local routes for this VNI - only from the * global routing table. These are also scheduled for withdraw from peers. */ static int delete_global_type2_routes(struct bgp *bgp, struct bgpevpn *vpn) { afi_t afi; safi_t safi; struct bgp_node *rdrn, *rn; struct bgp_table *table; struct bgp_path_info *pi; afi = AFI_L2VPN; safi = SAFI_EVPN; rdrn = bgp_node_lookup(bgp->rib[afi][safi], (struct prefix *)&vpn->prd); if (rdrn && bgp_node_has_bgp_path_info_data(rdrn)) { table = bgp_node_get_bgp_table_info(rdrn); for (rn = bgp_table_top(table); rn; rn = bgp_route_next(rn)) { struct prefix_evpn *evp = (struct prefix_evpn *)&rn->p; if (evp->prefix.route_type != BGP_EVPN_MAC_IP_ROUTE) continue; delete_evpn_route_entry(bgp, afi, safi, rn, &pi); if (pi) bgp_process(bgp, rn, afi, safi); } } /* Unlock RD node. */ if (rdrn) bgp_unlock_node(rdrn); return 0; } /* * Delete all type-2 (MACIP) local routes for this VNI - from the global * table as well as the per-VNI route table. */ static int delete_all_type2_routes(struct bgp *bgp, struct bgpevpn *vpn) { afi_t afi; safi_t safi; struct bgp_node *rn; struct bgp_path_info *pi; afi = AFI_L2VPN; safi = SAFI_EVPN; /* First, walk the global route table for this VNI's type-2 local * routes. * EVPN routes are a 2-level table, first get the RD table. */ delete_global_type2_routes(bgp, vpn); /* Next, walk this VNI's route table and delete local type-2 routes. */ for (rn = bgp_table_top(vpn->route_table); rn; rn = bgp_route_next(rn)) { struct prefix_evpn *evp = (struct prefix_evpn *)&rn->p; if (evp->prefix.route_type != BGP_EVPN_MAC_IP_ROUTE) continue; delete_evpn_route_entry(bgp, afi, safi, rn, &pi); /* Route entry in local table gets deleted immediately. */ if (pi) bgp_path_info_reap(rn, pi); } return 0; } /* * Delete all routes in per ES route-table */ static int delete_all_es_routes(struct bgp *bgp, struct evpnes *es) { struct bgp_node *rn; struct bgp_path_info *pi, *nextpi; /* Walk this ES's route table and delete all routes. */ for (rn = bgp_table_top(es->route_table); rn; rn = bgp_route_next(rn)) { for (pi = bgp_node_get_bgp_path_info(rn); (pi != NULL) && (nextpi = pi->next, 1); pi = nextpi) { bgp_path_info_delete(rn, pi); bgp_path_info_reap(rn, pi); } } return 0; } /* * Delete all routes in the per-VNI route table. */ static int delete_all_vni_routes(struct bgp *bgp, struct bgpevpn *vpn) { struct bgp_node *rn; struct bgp_path_info *pi, *nextpi; /* Walk this VNI's route table and delete all routes. */ for (rn = bgp_table_top(vpn->route_table); rn; rn = bgp_route_next(rn)) { for (pi = bgp_node_get_bgp_path_info(rn); (pi != NULL) && (nextpi = pi->next, 1); pi = nextpi) { bgp_path_info_delete(rn, pi); bgp_path_info_reap(rn, pi); } } return 0; } /* BUM traffic flood mode per-l2-vni */ static int bgp_evpn_vni_flood_mode_get(struct bgp *bgp, struct bgpevpn *vpn) { /* if flooding has been globally disabled per-vni mode is * not relevant */ if (bgp->vxlan_flood_ctrl == VXLAN_FLOOD_DISABLED) return VXLAN_FLOOD_DISABLED; /* if mcast group ip has been specified we use a PIM-SM MDT */ if (vpn->mcast_grp.s_addr != INADDR_ANY) return VXLAN_FLOOD_PIM_SM; /* default is ingress replication */ return VXLAN_FLOOD_HEAD_END_REPL; } /* * Update (and advertise) local routes for a VNI. Invoked upon the VNI * export RT getting modified or change to tunnel IP. Note that these * situations need the route in the per-VNI table as well as the global * table to be updated (as attributes change). */ static int update_routes_for_vni(struct bgp *bgp, struct bgpevpn *vpn) { int ret; struct prefix_evpn p; /* Update and advertise the type-3 route (only one) followed by the * locally learnt type-2 routes (MACIP) - for this VNI. * * RT-3 only if doing head-end replication */ if (bgp_evpn_vni_flood_mode_get(bgp, vpn) == VXLAN_FLOOD_HEAD_END_REPL) { build_evpn_type3_prefix(&p, vpn->originator_ip); ret = update_evpn_route(bgp, vpn, &p, 0, 0); if (ret) return ret; } return update_all_type2_routes(bgp, vpn); } /* Delete (and withdraw) local routes for specified ES from global and ES table. * Also remove all other routes from the per ES table. * Invoked when ES is deleted. */ static int delete_routes_for_es(struct bgp *bgp, struct evpnes *es) { int ret; char buf[ESI_STR_LEN]; struct prefix_evpn p; /* Delete and withdraw locally learnt ES route */ build_evpn_type4_prefix(&p, &es->esi, es->originator_ip.ipaddr_v4); ret = delete_evpn_type4_route(bgp, es, &p); if (ret) { flog_err(EC_BGP_EVPN_ROUTE_DELETE, "%u failed to delete type-4 route for ESI %s", bgp->vrf_id, esi_to_str(&es->esi, buf, sizeof(buf))); } /* Delete all routes from per ES table */ return delete_all_es_routes(bgp, es); } /* * Delete (and withdraw) local routes for specified VNI from the global * table and per-VNI table. After this, remove all other routes from * the per-VNI table. Invoked upon the VNI being deleted or EVPN * (advertise-all-vni) being disabled. */ static int delete_routes_for_vni(struct bgp *bgp, struct bgpevpn *vpn) { int ret; struct prefix_evpn p; /* Delete and withdraw locally learnt type-2 routes (MACIP) * followed by type-3 routes (only one) - for this VNI. */ ret = delete_all_type2_routes(bgp, vpn); if (ret) return ret; build_evpn_type3_prefix(&p, vpn->originator_ip); ret = delete_evpn_route(bgp, vpn, &p); if (ret) return ret; /* Delete all routes from the per-VNI table. */ return delete_all_vni_routes(bgp, vpn); } /* * There is a flood mcast IP address change. Update the mcast-grp and * remove the type-3 route if any. A new type-3 route will be generated * post tunnel_ip update if the new flood mode is head-end-replication. */ static int bgp_evpn_mcast_grp_change(struct bgp *bgp, struct bgpevpn *vpn, struct in_addr mcast_grp) { struct prefix_evpn p; vpn->mcast_grp = mcast_grp; if (is_vni_live(vpn)) { build_evpn_type3_prefix(&p, vpn->originator_ip); delete_evpn_route(bgp, vpn, &p); } return 0; } /* * There is a tunnel endpoint IP address change for this VNI, delete * prior type-3 route (if needed) and update. * Note: Route re-advertisement happens elsewhere after other processing * other changes. */ static int handle_tunnel_ip_change(struct bgp *bgp, struct bgpevpn *vpn, struct in_addr originator_ip) { struct prefix_evpn p; /* If VNI is not live, we only need to update the originator ip */ if (!is_vni_live(vpn)) { vpn->originator_ip = originator_ip; return 0; } /* Update the tunnel-ip hash */ bgp_tip_del(bgp, &vpn->originator_ip); bgp_tip_add(bgp, &originator_ip); /* filter routes as martian nexthop db has changed */ bgp_filter_evpn_routes_upon_martian_nh_change(bgp); /* Need to withdraw type-3 route as the originator IP is part * of the key. */ build_evpn_type3_prefix(&p, vpn->originator_ip); delete_evpn_route(bgp, vpn, &p); /* Update the tunnel IP and re-advertise all routes for this VNI. */ vpn->originator_ip = originator_ip; return 0; } /* Install EVPN route entry in ES */ static int install_evpn_route_entry_in_es(struct bgp *bgp, struct evpnes *es, struct prefix_evpn *p, struct bgp_path_info *parent_pi) { int ret = 0; struct bgp_node *rn = NULL; struct bgp_path_info *pi = NULL; struct attr *attr_new = NULL; /* Create (or fetch) route within the VNI. * NOTE: There is no RD here. */ rn = bgp_node_get(es->route_table, (struct prefix *)p); /* Check if route entry is already present. */ for (pi = bgp_node_get_bgp_path_info(rn); pi; pi = pi->next) if (pi->extra && (struct bgp_path_info *)pi->extra->parent == parent_pi) break; if (!pi) { /* Add (or update) attribute to hash. */ attr_new = bgp_attr_intern(parent_pi->attr); /* Create new route with its attribute. */ pi = info_make(parent_pi->type, BGP_ROUTE_IMPORTED, 0, parent_pi->peer, attr_new, rn); SET_FLAG(pi->flags, BGP_PATH_VALID); bgp_path_info_extra_get(pi); pi->extra->parent = parent_pi; bgp_path_info_add(rn, pi); } else { if (attrhash_cmp(pi->attr, parent_pi->attr) && !CHECK_FLAG(pi->flags, BGP_PATH_REMOVED)) { bgp_unlock_node(rn); return 0; } /* The attribute has changed. */ /* Add (or update) attribute to hash. */ attr_new = bgp_attr_intern(parent_pi->attr); /* Restore route, if needed. */ if (CHECK_FLAG(pi->flags, BGP_PATH_REMOVED)) bgp_path_info_restore(rn, pi); /* Mark if nexthop has changed. */ if (!IPV4_ADDR_SAME(&pi->attr->nexthop, &attr_new->nexthop)) SET_FLAG(pi->flags, BGP_PATH_IGP_CHANGED); /* Unintern existing, set to new. */ bgp_attr_unintern(&pi->attr); pi->attr = attr_new; pi->uptime = bgp_clock(); } /* Perform route selection and update zebra, if required. */ ret = evpn_es_route_select_install(bgp, es, rn); return ret; } /* * Install route entry into the VRF routing table and invoke route selection. */ static int install_evpn_route_entry_in_vrf(struct bgp *bgp_vrf, struct prefix_evpn *evp, struct bgp_path_info *parent_pi) { struct bgp_node *rn; struct bgp_path_info *pi; struct attr attr; struct attr *attr_new; int ret = 0; struct prefix p; struct prefix *pp = &p; afi_t afi = 0; safi_t safi = 0; char buf[PREFIX_STRLEN]; char buf1[PREFIX_STRLEN]; memset(pp, 0, sizeof(struct prefix)); ip_prefix_from_evpn_prefix(evp, pp); if (bgp_debug_zebra(NULL)) { zlog_debug( "import evpn prefix %s as ip prefix %s in vrf %s", prefix2str(evp, buf, sizeof(buf)), prefix2str(pp, buf1, sizeof(buf)), vrf_id_to_name(bgp_vrf->vrf_id)); } /* Create (or fetch) route within the VRF. */ /* NOTE: There is no RD here. */ if (is_evpn_prefix_ipaddr_v4(evp)) { afi = AFI_IP; safi = SAFI_UNICAST; rn = bgp_node_get(bgp_vrf->rib[afi][safi], pp); } else if (is_evpn_prefix_ipaddr_v6(evp)) { afi = AFI_IP6; safi = SAFI_UNICAST; rn = bgp_node_get(bgp_vrf->rib[afi][safi], pp); } else return 0; /* EVPN routes currently only support a IPv4 next hop which corresponds * to the remote VTEP. When importing into a VRF, if it is IPv6 host * or prefix route, we have to convert the next hop to an IPv4-mapped * address for the rest of the code to flow through. In the case of IPv4, * make sure to set the flag for next hop attribute. */ attr = *parent_pi->attr; if (afi == AFI_IP6) evpn_convert_nexthop_to_ipv6(&attr); else attr.flag |= ATTR_FLAG_BIT(BGP_ATTR_NEXT_HOP); /* Check if route entry is already present. */ for (pi = bgp_node_get_bgp_path_info(rn); pi; pi = pi->next) if (pi->extra && (struct bgp_path_info *)pi->extra->parent == parent_pi) break; if (!pi) { /* Add (or update) attribute to hash. */ attr_new = bgp_attr_intern(&attr); /* Create new route with its attribute. */ pi = info_make(parent_pi->type, BGP_ROUTE_IMPORTED, 0, parent_pi->peer, attr_new, rn); SET_FLAG(pi->flags, BGP_PATH_VALID); bgp_path_info_extra_get(pi); pi->extra->parent = bgp_path_info_lock(parent_pi); bgp_lock_node((struct bgp_node *)parent_pi->net); if (parent_pi->extra) { memcpy(&pi->extra->label, &parent_pi->extra->label, sizeof(pi->extra->label)); pi->extra->num_labels = parent_pi->extra->num_labels; } bgp_path_info_add(rn, pi); } else { if (attrhash_cmp(pi->attr, &attr) && !CHECK_FLAG(pi->flags, BGP_PATH_REMOVED)) { bgp_unlock_node(rn); return 0; } /* The attribute has changed. */ /* Add (or update) attribute to hash. */ attr_new = bgp_attr_intern(&attr); /* Restore route, if needed. */ if (CHECK_FLAG(pi->flags, BGP_PATH_REMOVED)) bgp_path_info_restore(rn, pi); /* Mark if nexthop has changed. */ if ((afi == AFI_IP && !IPV4_ADDR_SAME(&pi->attr->nexthop, &attr_new->nexthop)) || (afi == AFI_IP6 && !IPV6_ADDR_SAME(&pi->attr->mp_nexthop_global, &attr_new->mp_nexthop_global))) SET_FLAG(pi->flags, BGP_PATH_IGP_CHANGED); bgp_path_info_set_flag(rn, pi, BGP_PATH_ATTR_CHANGED); /* Unintern existing, set to new. */ bgp_attr_unintern(&pi->attr); pi->attr = attr_new; pi->uptime = bgp_clock(); } bgp_aggregate_increment(bgp_vrf, &rn->p, pi, afi, safi); /* Perform route selection and update zebra, if required. */ bgp_process(bgp_vrf, rn, afi, safi); /* Process for route leaking. */ vpn_leak_from_vrf_update(bgp_get_default(), bgp_vrf, pi); return ret; } /* * Install route entry into the VNI routing table and invoke route selection. */ static int install_evpn_route_entry(struct bgp *bgp, struct bgpevpn *vpn, struct prefix_evpn *p, struct bgp_path_info *parent_pi) { struct bgp_node *rn; struct bgp_path_info *pi; struct attr *attr_new; int ret; /* Create (or fetch) route within the VNI. */ /* NOTE: There is no RD here. */ rn = bgp_node_get(vpn->route_table, (struct prefix *)p); /* Check if route entry is already present. */ for (pi = bgp_node_get_bgp_path_info(rn); pi; pi = pi->next) if (pi->extra && (struct bgp_path_info *)pi->extra->parent == parent_pi) break; if (!pi) { /* Add (or update) attribute to hash. */ attr_new = bgp_attr_intern(parent_pi->attr); /* Create new route with its attribute. */ pi = info_make(parent_pi->type, BGP_ROUTE_IMPORTED, 0, parent_pi->peer, attr_new, rn); SET_FLAG(pi->flags, BGP_PATH_VALID); bgp_path_info_extra_get(pi); pi->extra->parent = bgp_path_info_lock(parent_pi); bgp_lock_node((struct bgp_node *)parent_pi->net); if (parent_pi->extra) { memcpy(&pi->extra->label, &parent_pi->extra->label, sizeof(pi->extra->label)); pi->extra->num_labels = parent_pi->extra->num_labels; } bgp_path_info_add(rn, pi); } else { if (attrhash_cmp(pi->attr, parent_pi->attr) && !CHECK_FLAG(pi->flags, BGP_PATH_REMOVED)) { bgp_unlock_node(rn); return 0; } /* The attribute has changed. */ /* Add (or update) attribute to hash. */ attr_new = bgp_attr_intern(parent_pi->attr); /* Restore route, if needed. */ if (CHECK_FLAG(pi->flags, BGP_PATH_REMOVED)) bgp_path_info_restore(rn, pi); /* Mark if nexthop has changed. */ if (!IPV4_ADDR_SAME(&pi->attr->nexthop, &attr_new->nexthop)) SET_FLAG(pi->flags, BGP_PATH_IGP_CHANGED); /* Unintern existing, set to new. */ bgp_attr_unintern(&pi->attr); pi->attr = attr_new; pi->uptime = bgp_clock(); } /* Perform route selection and update zebra, if required. */ ret = evpn_route_select_install(bgp, vpn, rn); return ret; } /* Uninstall EVPN route entry from ES route table */ static int uninstall_evpn_route_entry_in_es(struct bgp *bgp, struct evpnes *es, struct prefix_evpn *p, struct bgp_path_info *parent_pi) { int ret; struct bgp_node *rn; struct bgp_path_info *pi; if (!es->route_table) return 0; /* Locate route within the ESI. * NOTE: There is no RD here. */ rn = bgp_node_lookup(es->route_table, (struct prefix *)p); if (!rn) return 0; /* Find matching route entry. */ for (pi = bgp_node_get_bgp_path_info(rn); pi; pi = pi->next) if (pi->extra && (struct bgp_path_info *)pi->extra->parent == parent_pi) break; if (!pi) return 0; /* Mark entry for deletion */ bgp_path_info_delete(rn, pi); /* Perform route selection and update zebra, if required. */ ret = evpn_es_route_select_install(bgp, es, rn); /* Unlock route node. */ bgp_unlock_node(rn); return ret; } /* * Uninstall route entry from the VRF routing table and send message * to zebra, if appropriate. */ static int uninstall_evpn_route_entry_in_vrf(struct bgp *bgp_vrf, struct prefix_evpn *evp, struct bgp_path_info *parent_pi) { struct bgp_node *rn; struct bgp_path_info *pi; int ret = 0; struct prefix p; struct prefix *pp = &p; afi_t afi = 0; safi_t safi = 0; char buf[PREFIX_STRLEN]; char buf1[PREFIX_STRLEN]; memset(pp, 0, sizeof(struct prefix)); ip_prefix_from_evpn_prefix(evp, pp); if (bgp_debug_zebra(NULL)) { zlog_debug( "uninstalling evpn prefix %s as ip prefix %s in vrf %s", prefix2str(evp, buf, sizeof(buf)), prefix2str(pp, buf1, sizeof(buf)), vrf_id_to_name(bgp_vrf->vrf_id)); } /* Locate route within the VRF. */ /* NOTE: There is no RD here. */ if (is_evpn_prefix_ipaddr_v4(evp)) { afi = AFI_IP; safi = SAFI_UNICAST; rn = bgp_node_lookup(bgp_vrf->rib[afi][safi], pp); } else { afi = AFI_IP6; safi = SAFI_UNICAST; rn = bgp_node_lookup(bgp_vrf->rib[afi][safi], pp); } if (!rn) return 0; /* Find matching route entry. */ for (pi = bgp_node_get_bgp_path_info(rn); pi; pi = pi->next) if (pi->extra && (struct bgp_path_info *)pi->extra->parent == parent_pi) break; if (!pi) return 0; /* Process for route leaking. */ vpn_leak_from_vrf_withdraw(bgp_get_default(), bgp_vrf, pi); bgp_aggregate_decrement(bgp_vrf, &rn->p, pi, afi, safi); /* Mark entry for deletion */ bgp_path_info_delete(rn, pi); /* Perform route selection and update zebra, if required. */ bgp_process(bgp_vrf, rn, afi, safi); /* Unlock route node. */ bgp_unlock_node(rn); return ret; } /* * Uninstall route entry from the VNI routing table and send message * to zebra, if appropriate. */ static int uninstall_evpn_route_entry(struct bgp *bgp, struct bgpevpn *vpn, struct prefix_evpn *p, struct bgp_path_info *parent_pi) { struct bgp_node *rn; struct bgp_path_info *pi; int ret; /* Locate route within the VNI. */ /* NOTE: There is no RD here. */ rn = bgp_node_lookup(vpn->route_table, (struct prefix *)p); if (!rn) return 0; /* Find matching route entry. */ for (pi = bgp_node_get_bgp_path_info(rn); pi; pi = pi->next) if (pi->extra && (struct bgp_path_info *)pi->extra->parent == parent_pi) break; if (!pi) return 0; /* Mark entry for deletion */ bgp_path_info_delete(rn, pi); /* Perform route selection and update zebra, if required. */ ret = evpn_route_select_install(bgp, vpn, rn); /* Unlock route node. */ bgp_unlock_node(rn); return ret; } /* * Given a prefix, see if it belongs to ES. */ static int is_prefix_matching_for_es(struct prefix_evpn *p, struct evpnes *es) { /* if not an ES route return false */ if (p->prefix.route_type != BGP_EVPN_ES_ROUTE) return 0; if (memcmp(&p->prefix.es_addr.esi, &es->esi, sizeof(esi_t)) == 0) return 1; return 0; } /* * Given a route entry and a VRF, see if this route entry should be * imported into the VRF i.e., RTs match. */ static int is_route_matching_for_vrf(struct bgp *bgp_vrf, struct bgp_path_info *pi) { struct attr *attr = pi->attr; struct ecommunity *ecom; int i; assert(attr); /* Route should have valid RT to be even considered. */ if (!(attr->flag & ATTR_FLAG_BIT(BGP_ATTR_EXT_COMMUNITIES))) return 0; ecom = attr->ecommunity; if (!ecom || !ecom->size) return 0; /* For each extended community RT, see if it matches this VNI. If any RT * matches, we're done. */ for (i = 0; i < ecom->size; i++) { uint8_t *pnt; uint8_t type, sub_type; struct ecommunity_val *eval; struct ecommunity_val eval_tmp; struct vrf_irt_node *irt; /* Only deal with RTs */ pnt = (ecom->val + (i * ECOMMUNITY_SIZE)); eval = (struct ecommunity_val *)(ecom->val + (i * ECOMMUNITY_SIZE)); type = *pnt++; sub_type = *pnt++; if (sub_type != ECOMMUNITY_ROUTE_TARGET) continue; /* See if this RT matches specified VNIs import RTs */ irt = lookup_vrf_import_rt(eval); if (irt) if (is_vrf_present_in_irt_vrfs(irt->vrfs, bgp_vrf)) return 1; /* Also check for non-exact match. In this, we mask out the AS * and * only check on the local-admin sub-field. This is to * facilitate using * VNI as the RT for EBGP peering too. */ irt = NULL; if (type == ECOMMUNITY_ENCODE_AS || type == ECOMMUNITY_ENCODE_AS4 || type == ECOMMUNITY_ENCODE_IP) { memcpy(&eval_tmp, eval, ECOMMUNITY_SIZE); mask_ecom_global_admin(&eval_tmp, eval); irt = lookup_vrf_import_rt(&eval_tmp); } if (irt) if (is_vrf_present_in_irt_vrfs(irt->vrfs, bgp_vrf)) return 1; } return 0; } /* * Given a route entry and a VNI, see if this route entry should be * imported into the VNI i.e., RTs match. */ static int is_route_matching_for_vni(struct bgp *bgp, struct bgpevpn *vpn, struct bgp_path_info *pi) { struct attr *attr = pi->attr; struct ecommunity *ecom; int i; assert(attr); /* Route should have valid RT to be even considered. */ if (!(attr->flag & ATTR_FLAG_BIT(BGP_ATTR_EXT_COMMUNITIES))) return 0; ecom = attr->ecommunity; if (!ecom || !ecom->size) return 0; /* For each extended community RT, see if it matches this VNI. If any RT * matches, we're done. */ for (i = 0; i < ecom->size; i++) { uint8_t *pnt; uint8_t type, sub_type; struct ecommunity_val *eval; struct ecommunity_val eval_tmp; struct irt_node *irt; /* Only deal with RTs */ pnt = (ecom->val + (i * ECOMMUNITY_SIZE)); eval = (struct ecommunity_val *)(ecom->val + (i * ECOMMUNITY_SIZE)); type = *pnt++; sub_type = *pnt++; if (sub_type != ECOMMUNITY_ROUTE_TARGET) continue; /* See if this RT matches specified VNIs import RTs */ irt = lookup_import_rt(bgp, eval); if (irt) if (is_vni_present_in_irt_vnis(irt->vnis, vpn)) return 1; /* Also check for non-exact match. In this, we mask out the AS * and * only check on the local-admin sub-field. This is to * facilitate using * VNI as the RT for EBGP peering too. */ irt = NULL; if (type == ECOMMUNITY_ENCODE_AS || type == ECOMMUNITY_ENCODE_AS4 || type == ECOMMUNITY_ENCODE_IP) { memcpy(&eval_tmp, eval, ECOMMUNITY_SIZE); mask_ecom_global_admin(&eval_tmp, eval); irt = lookup_import_rt(bgp, &eval_tmp); } if (irt) if (is_vni_present_in_irt_vnis(irt->vnis, vpn)) return 1; } return 0; } static int install_uninstall_routes_for_es(struct bgp *bgp, struct evpnes *es, int install) { int ret; afi_t afi; safi_t safi; char buf[PREFIX_STRLEN]; char buf1[ESI_STR_LEN]; struct bgp_node *rd_rn, *rn; struct bgp_table *table; struct bgp_path_info *pi; afi = AFI_L2VPN; safi = SAFI_EVPN; /* * Walk entire global routing table and evaluate routes which could be * imported into this VRF. Note that we need to loop through all global * routes to determine which route matches the import rt on vrf */ for (rd_rn = bgp_table_top(bgp->rib[afi][safi]); rd_rn; rd_rn = bgp_route_next(rd_rn)) { table = bgp_node_get_bgp_table_info(rd_rn); if (!table) continue; for (rn = bgp_table_top(table); rn; rn = bgp_route_next(rn)) { struct prefix_evpn *evp = (struct prefix_evpn *)&rn->p; for (pi = bgp_node_get_bgp_path_info(rn); pi; pi = pi->next) { /* * Consider "valid" remote routes applicable for * this ES. */ if (!(CHECK_FLAG(pi->flags, BGP_PATH_VALID) && pi->type == ZEBRA_ROUTE_BGP && pi->sub_type == BGP_ROUTE_NORMAL)) continue; if (!is_prefix_matching_for_es(evp, es)) continue; if (install) ret = install_evpn_route_entry_in_es( bgp, es, evp, pi); else ret = uninstall_evpn_route_entry_in_es( bgp, es, evp, pi); if (ret) { flog_err( EC_BGP_EVPN_FAIL, "Failed to %s EVPN %s route in ESI %s", install ? "install" : "uninstall", prefix2str(evp, buf, sizeof(buf)), esi_to_str(&es->esi, buf1, sizeof(buf1))); return ret; } } } } return 0; } /* This API will scan evpn routes for checking attribute's rmac * macthes with bgp instance router mac. It avoid installing * route into bgp vrf table and remote rmac in bridge table. */ static int bgp_evpn_route_rmac_self_check(struct bgp *bgp_vrf, struct prefix_evpn *evp, struct bgp_path_info *pi) { /* evpn route could have learnt prior to L3vni has come up, * perform rmac check before installing route and * remote router mac. * The route will be removed from global bgp table once * SVI comes up with MAC and stored in hash, triggers * bgp_mac_rescan_all_evpn_tables. */ if (pi->attr && memcmp(&bgp_vrf->rmac, &pi->attr->rmac, ETH_ALEN) == 0) { if (bgp_debug_update(pi->peer, NULL, NULL, 1)) { char buf1[PREFIX_STRLEN]; char attr_str[BUFSIZ] = {0}; bgp_dump_attr(pi->attr, attr_str, BUFSIZ); zlog_debug("%s: bgp %u prefix %s with attr %s - DENIED due to self mac", __func__, bgp_vrf->vrf_id, prefix2str(evp, buf1, sizeof(buf1)), attr_str); } return 1; } return 0; } /* * Install or uninstall mac-ip routes are appropriate for this * particular VRF. */ static int install_uninstall_routes_for_vrf(struct bgp *bgp_vrf, int install) { afi_t afi; safi_t safi; struct bgp_node *rd_rn, *rn; struct bgp_table *table; struct bgp_path_info *pi; int ret; char buf[PREFIX_STRLEN]; struct bgp *bgp_evpn = NULL; afi = AFI_L2VPN; safi = SAFI_EVPN; bgp_evpn = bgp_get_evpn(); if (!bgp_evpn) return -1; /* Walk entire global routing table and evaluate routes which could be * imported into this VRF. Note that we need to loop through all global * routes to determine which route matches the import rt on vrf */ for (rd_rn = bgp_table_top(bgp_evpn->rib[afi][safi]); rd_rn; rd_rn = bgp_route_next(rd_rn)) { table = bgp_node_get_bgp_table_info(rd_rn); if (!table) continue; for (rn = bgp_table_top(table); rn; rn = bgp_route_next(rn)) { struct prefix_evpn *evp = (struct prefix_evpn *)&rn->p; /* if not mac-ip route skip this route */ if (!(evp->prefix.route_type == BGP_EVPN_MAC_IP_ROUTE || evp->prefix.route_type == BGP_EVPN_IP_PREFIX_ROUTE)) continue; /* if not a mac+ip route skip this route */ if (!(is_evpn_prefix_ipaddr_v4(evp) || is_evpn_prefix_ipaddr_v6(evp))) continue; for (pi = bgp_node_get_bgp_path_info(rn); pi; pi = pi->next) { /* Consider "valid" remote routes applicable for * this VRF. */ if (!(CHECK_FLAG(pi->flags, BGP_PATH_VALID) && pi->type == ZEBRA_ROUTE_BGP && pi->sub_type == BGP_ROUTE_NORMAL)) continue; if (is_route_matching_for_vrf(bgp_vrf, pi)) { if (bgp_evpn_route_rmac_self_check( bgp_vrf, evp, pi)) continue; if (install) ret = install_evpn_route_entry_in_vrf( bgp_vrf, evp, pi); else ret = uninstall_evpn_route_entry_in_vrf( bgp_vrf, evp, pi); if (ret) { flog_err( EC_BGP_EVPN_FAIL, "Failed to %s EVPN %s route in VRF %s", install ? "install" : "uninstall", prefix2str(evp, buf, sizeof(buf)), vrf_id_to_name( bgp_vrf->vrf_id)); return ret; } } } } } return 0; } /* * Install or uninstall routes of specified type that are appropriate for this * particular VNI. */ static int install_uninstall_routes_for_vni(struct bgp *bgp, struct bgpevpn *vpn, bgp_evpn_route_type rtype, int install) { afi_t afi; safi_t safi; struct bgp_node *rd_rn, *rn; struct bgp_table *table; struct bgp_path_info *pi; int ret; afi = AFI_L2VPN; safi = SAFI_EVPN; /* Walk entire global routing table and evaluate routes which could be * imported into this VPN. Note that we cannot just look at the routes * for * the VNI's RD - remote routes applicable for this VNI could have any * RD. */ /* EVPN routes are a 2-level table. */ for (rd_rn = bgp_table_top(bgp->rib[afi][safi]); rd_rn; rd_rn = bgp_route_next(rd_rn)) { table = bgp_node_get_bgp_table_info(rd_rn); if (!table) continue; for (rn = bgp_table_top(table); rn; rn = bgp_route_next(rn)) { struct prefix_evpn *evp = (struct prefix_evpn *)&rn->p; if (evp->prefix.route_type != rtype) continue; for (pi = bgp_node_get_bgp_path_info(rn); pi; pi = pi->next) { /* Consider "valid" remote routes applicable for * this VNI. */ if (!(CHECK_FLAG(pi->flags, BGP_PATH_VALID) && pi->type == ZEBRA_ROUTE_BGP && pi->sub_type == BGP_ROUTE_NORMAL)) continue; if (is_route_matching_for_vni(bgp, vpn, pi)) { if (install) ret = install_evpn_route_entry( bgp, vpn, evp, pi); else ret = uninstall_evpn_route_entry( bgp, vpn, evp, pi); if (ret) { flog_err( EC_BGP_EVPN_FAIL, "%u: Failed to %s EVPN %s route in VNI %u", bgp->vrf_id, install ? "install" : "uninstall", rtype == BGP_EVPN_MAC_IP_ROUTE ? "MACIP" : "IMET", vpn->vni); return ret; } } } } } return 0; } /* Install any existing remote ES routes applicable for this ES into its routing * table. This is invoked when ES comes up. */ static int install_routes_for_es(struct bgp *bgp, struct evpnes *es) { return install_uninstall_routes_for_es(bgp, es, 1); } /* Install any existing remote routes applicable for this VRF into VRF RIB. This * is invoked upon l3vni-add or l3vni import rt change */ static int install_routes_for_vrf(struct bgp *bgp_vrf) { install_uninstall_routes_for_vrf(bgp_vrf, 1); return 0; } /* * Install any existing remote routes applicable for this VNI into its * routing table. This is invoked when a VNI becomes "live" or its Import * RT is changed. */ static int install_routes_for_vni(struct bgp *bgp, struct bgpevpn *vpn) { int ret; /* Install type-3 routes followed by type-2 routes - the ones applicable * for this VNI. */ ret = install_uninstall_routes_for_vni(bgp, vpn, BGP_EVPN_IMET_ROUTE, 1); if (ret) return ret; return install_uninstall_routes_for_vni(bgp, vpn, BGP_EVPN_MAC_IP_ROUTE, 1); } /* uninstall routes from l3vni vrf. */ static int uninstall_routes_for_vrf(struct bgp *bgp_vrf) { install_uninstall_routes_for_vrf(bgp_vrf, 0); return 0; } /* * Uninstall any existing remote routes for this VNI. One scenario in which * this is invoked is upon an import RT change. */ static int uninstall_routes_for_vni(struct bgp *bgp, struct bgpevpn *vpn) { int ret; /* Uninstall type-2 routes followed by type-3 routes - the ones * applicable * for this VNI. */ ret = install_uninstall_routes_for_vni(bgp, vpn, BGP_EVPN_MAC_IP_ROUTE, 0); if (ret) return ret; return install_uninstall_routes_for_vni(bgp, vpn, BGP_EVPN_IMET_ROUTE, 0); } /* Install or unistall route in ES */ static int install_uninstall_route_in_es(struct bgp *bgp, struct evpnes *es, afi_t afi, safi_t safi, struct prefix_evpn *evp, struct bgp_path_info *pi, int install) { int ret = 0; char buf[ESI_STR_LEN]; if (install) ret = install_evpn_route_entry_in_es(bgp, es, evp, pi); else ret = uninstall_evpn_route_entry_in_es(bgp, es, evp, pi); if (ret) { flog_err( EC_BGP_EVPN_FAIL, "%u: Failed to %s EVPN %s route in ESI %s", bgp->vrf_id, install ? "install" : "uninstall", "ES", esi_to_str(&evp->prefix.es_addr.esi, buf, sizeof(buf))); return ret; } return 0; } /* * Install or uninstall route in matching VRFs (list). */ static int install_uninstall_route_in_vrfs(struct bgp *bgp_def, afi_t afi, safi_t safi, struct prefix_evpn *evp, struct bgp_path_info *pi, struct list *vrfs, int install) { char buf[PREFIX2STR_BUFFER]; struct bgp *bgp_vrf; struct listnode *node, *nnode; /* Only type-2/type-5 routes go into a VRF */ if (!(evp->prefix.route_type == BGP_EVPN_MAC_IP_ROUTE || evp->prefix.route_type == BGP_EVPN_IP_PREFIX_ROUTE)) return 0; /* if it is type-2 route and not a mac+ip route skip this route */ if ((evp->prefix.route_type == BGP_EVPN_MAC_IP_ROUTE) && !(is_evpn_prefix_ipaddr_v4(evp) || is_evpn_prefix_ipaddr_v6(evp))) return 0; for (ALL_LIST_ELEMENTS(vrfs, node, nnode, bgp_vrf)) { int ret; if (install) ret = install_evpn_route_entry_in_vrf(bgp_vrf, evp, pi); else ret = uninstall_evpn_route_entry_in_vrf(bgp_vrf, evp, pi); if (ret) { flog_err(EC_BGP_EVPN_FAIL, "%u: Failed to %s prefix %s in VRF %s", bgp_def->vrf_id, install ? "install" : "uninstall", prefix2str(evp, buf, sizeof(buf)), vrf_id_to_name(bgp_vrf->vrf_id)); return ret; } } return 0; } /* * Install or uninstall route in matching VNIs (list). */ static int install_uninstall_route_in_vnis(struct bgp *bgp, afi_t afi, safi_t safi, struct prefix_evpn *evp, struct bgp_path_info *pi, struct list *vnis, int install) { struct bgpevpn *vpn; struct listnode *node, *nnode; for (ALL_LIST_ELEMENTS(vnis, node, nnode, vpn)) { int ret; if (!is_vni_live(vpn)) continue; if (install) ret = install_evpn_route_entry(bgp, vpn, evp, pi); else ret = uninstall_evpn_route_entry(bgp, vpn, evp, pi); if (ret) { flog_err(EC_BGP_EVPN_FAIL, "%u: Failed to %s EVPN %s route in VNI %u", bgp->vrf_id, install ? "install" : "uninstall", evp->prefix.route_type == BGP_EVPN_MAC_IP_ROUTE ? "MACIP" : "IMET", vpn->vni); return ret; } } return 0; } /* * Install or uninstall route for appropriate VNIs/ESIs. */ static int install_uninstall_evpn_route(struct bgp *bgp, afi_t afi, safi_t safi, struct prefix *p, struct bgp_path_info *pi, int import) { struct prefix_evpn *evp = (struct prefix_evpn *)p; struct attr *attr = pi->attr; struct ecommunity *ecom; int i; assert(attr); /* Only type-2, type-3, type-4 and type-5 are supported currently */ if (!(evp->prefix.route_type == BGP_EVPN_MAC_IP_ROUTE || evp->prefix.route_type == BGP_EVPN_IMET_ROUTE || evp->prefix.route_type == BGP_EVPN_ES_ROUTE || evp->prefix.route_type == BGP_EVPN_IP_PREFIX_ROUTE)) return 0; /* If we don't have Route Target, nothing much to do. */ if (!(attr->flag & ATTR_FLAG_BIT(BGP_ATTR_EXT_COMMUNITIES))) return 0; ecom = attr->ecommunity; if (!ecom || !ecom->size) return -1; /* An EVPN route belongs to a VNI or a VRF or an ESI based on the RTs * attached to the route */ for (i = 0; i < ecom->size; i++) { uint8_t *pnt; uint8_t type, sub_type; struct ecommunity_val *eval; struct ecommunity_val eval_tmp; struct irt_node *irt; /* import rt for l2vni */ struct vrf_irt_node *vrf_irt; /* import rt for l3vni */ struct evpnes *es; /* Only deal with RTs */ pnt = (ecom->val + (i * ECOMMUNITY_SIZE)); eval = (struct ecommunity_val *)(ecom->val + (i * ECOMMUNITY_SIZE)); type = *pnt++; sub_type = *pnt++; if (sub_type != ECOMMUNITY_ROUTE_TARGET) continue; /* * macip routes (type-2) are imported into VNI and VRF tables. * IMET route is imported into VNI table. * prefix routes are imported into VRF table. */ if (evp->prefix.route_type == BGP_EVPN_MAC_IP_ROUTE || evp->prefix.route_type == BGP_EVPN_IMET_ROUTE || evp->prefix.route_type == BGP_EVPN_IP_PREFIX_ROUTE) { irt = lookup_import_rt(bgp, eval); if (irt) install_uninstall_route_in_vnis( bgp, afi, safi, evp, pi, irt->vnis, import); vrf_irt = lookup_vrf_import_rt(eval); if (vrf_irt) install_uninstall_route_in_vrfs( bgp, afi, safi, evp, pi, vrf_irt->vrfs, import); /* Also check for non-exact match. * In this, we mask out the AS and * only check on the local-admin sub-field. * This is to facilitate using * VNI as the RT for EBGP peering too. */ irt = NULL; vrf_irt = NULL; if (type == ECOMMUNITY_ENCODE_AS || type == ECOMMUNITY_ENCODE_AS4 || type == ECOMMUNITY_ENCODE_IP) { memcpy(&eval_tmp, eval, ECOMMUNITY_SIZE); mask_ecom_global_admin(&eval_tmp, eval); irt = lookup_import_rt(bgp, &eval_tmp); vrf_irt = lookup_vrf_import_rt(&eval_tmp); } if (irt) install_uninstall_route_in_vnis( bgp, afi, safi, evp, pi, irt->vnis, import); if (vrf_irt) install_uninstall_route_in_vrfs( bgp, afi, safi, evp, pi, vrf_irt->vrfs, import); } /* es route is imported into the es table */ if (evp->prefix.route_type == BGP_EVPN_ES_ROUTE) { /* we will match based on the entire esi to avoid * imoort of an es route for esi2 into esi1 */ es = bgp_evpn_lookup_es(bgp, &evp->prefix.es_addr.esi); if (es && is_es_local(es)) install_uninstall_route_in_es( bgp, es, afi, safi, evp, pi, import); } } return 0; } /* * delete and withdraw all ipv4 and ipv6 routes in the vrf table as type-5 * routes */ static void delete_withdraw_vrf_routes(struct bgp *bgp_vrf) { /* Delete ipv4 default route and withdraw from peers */ if (evpn_default_originate_set(bgp_vrf, AFI_IP, SAFI_UNICAST)) bgp_evpn_install_uninstall_default_route(bgp_vrf, AFI_IP, SAFI_UNICAST, false); /* delete all ipv4 routes and withdraw from peers */ if (advertise_type5_routes(bgp_vrf, AFI_IP)) bgp_evpn_withdraw_type5_routes(bgp_vrf, AFI_IP, SAFI_UNICAST); /* Delete ipv6 default route and withdraw from peers */ if (evpn_default_originate_set(bgp_vrf, AFI_IP6, SAFI_UNICAST)) bgp_evpn_install_uninstall_default_route(bgp_vrf, AFI_IP6, SAFI_UNICAST, false); /* delete all ipv6 routes and withdraw from peers */ if (advertise_type5_routes(bgp_vrf, AFI_IP6)) bgp_evpn_withdraw_type5_routes(bgp_vrf, AFI_IP6, SAFI_UNICAST); } /* * update and advertise all ipv4 and ipv6 routes in thr vrf table as type-5 * routes */ static void update_advertise_vrf_routes(struct bgp *bgp_vrf) { /* update all ipv4 routes */ if (advertise_type5_routes(bgp_vrf, AFI_IP)) bgp_evpn_advertise_type5_routes(bgp_vrf, AFI_IP, SAFI_UNICAST); /* update ipv4 default route and withdraw from peers */ if (evpn_default_originate_set(bgp_vrf, AFI_IP, SAFI_UNICAST)) bgp_evpn_install_uninstall_default_route(bgp_vrf, AFI_IP, SAFI_UNICAST, true); /* update all ipv6 routes */ if (advertise_type5_routes(bgp_vrf, AFI_IP6)) bgp_evpn_advertise_type5_routes(bgp_vrf, AFI_IP6, SAFI_UNICAST); /* update ipv6 default route and withdraw from peers */ if (evpn_default_originate_set(bgp_vrf, AFI_IP6, SAFI_UNICAST)) bgp_evpn_install_uninstall_default_route(bgp_vrf, AFI_IP6, SAFI_UNICAST, true); } /* * update and advertise local routes for a VRF as type-5 routes. * This is invoked upon RD change for a VRF. Note taht the processing is only * done in the global route table using the routes which already exist in the * VRF routing table */ static void update_router_id_vrf(struct bgp *bgp_vrf) { /* skip if the RD is configured */ if (is_vrf_rd_configured(bgp_vrf)) return; /* derive the RD for the VRF based on new router-id */ bgp_evpn_derive_auto_rd_for_vrf(bgp_vrf); /* update advertise ipv4|ipv6 routes as type-5 routes */ update_advertise_vrf_routes(bgp_vrf); } /* * Delete and withdraw all type-5 routes for the RD corresponding to VRF. * This is invoked upon VRF RD change. The processing is done only from global * table. */ static void withdraw_router_id_vrf(struct bgp *bgp_vrf) { /* skip if the RD is configured */ if (is_vrf_rd_configured(bgp_vrf)) return; /* delete/withdraw ipv4|ipv6 routes as type-5 routes */ delete_withdraw_vrf_routes(bgp_vrf); } /* * Update and advertise local routes for a VNI. Invoked upon router-id * change. Note that the processing is done only on the global route table * using routes that already exist in the per-VNI table. */ static int update_advertise_vni_routes(struct bgp *bgp, struct bgpevpn *vpn) { struct prefix_evpn p; struct bgp_node *rn, *global_rn; struct bgp_path_info *pi, *global_pi; struct attr *attr; afi_t afi = AFI_L2VPN; safi_t safi = SAFI_EVPN; /* Locate type-3 route for VNI in the per-VNI table and use its * attributes to create and advertise the type-3 route for this VNI * in the global table. * * RT-3 only if doing head-end replication */ if (bgp_evpn_vni_flood_mode_get(bgp, vpn) == VXLAN_FLOOD_HEAD_END_REPL) { build_evpn_type3_prefix(&p, vpn->originator_ip); rn = bgp_node_lookup(vpn->route_table, (struct prefix *)&p); if (!rn) /* unexpected */ return 0; for (pi = bgp_node_get_bgp_path_info(rn); pi; pi = pi->next) if (pi->peer == bgp->peer_self && pi->type == ZEBRA_ROUTE_BGP && pi->sub_type == BGP_ROUTE_STATIC) break; if (!pi) /* unexpected */ return 0; attr = pi->attr; global_rn = bgp_afi_node_get(bgp->rib[afi][safi], afi, safi, (struct prefix *)&p, &vpn->prd); update_evpn_route_entry(bgp, vpn, afi, safi, global_rn, attr, 1, &pi, 0, mac_mobility_seqnum(attr)); /* Schedule for processing and unlock node. */ bgp_process(bgp, global_rn, afi, safi); bgp_unlock_node(global_rn); } /* Now, walk this VNI's route table and use the route and its attribute * to create and schedule route in global table. */ for (rn = bgp_table_top(vpn->route_table); rn; rn = bgp_route_next(rn)) { struct prefix_evpn *evp = (struct prefix_evpn *)&rn->p; /* Identify MAC-IP local routes. */ if (evp->prefix.route_type != BGP_EVPN_MAC_IP_ROUTE) continue; for (pi = bgp_node_get_bgp_path_info(rn); pi; pi = pi->next) if (pi->peer == bgp->peer_self && pi->type == ZEBRA_ROUTE_BGP && pi->sub_type == BGP_ROUTE_STATIC) break; if (!pi) continue; /* Create route in global routing table using this route entry's * attribute. */ attr = pi->attr; global_rn = bgp_afi_node_get(bgp->rib[afi][safi], afi, safi, (struct prefix *)evp, &vpn->prd); assert(global_rn); update_evpn_route_entry(bgp, vpn, afi, safi, global_rn, attr, 1, &global_pi, 0, mac_mobility_seqnum(attr)); /* Schedule for processing and unlock node. */ bgp_process(bgp, global_rn, afi, safi); bgp_unlock_node(global_rn); } return 0; } /* * Delete (and withdraw) local routes for a VNI - only from the global * table. Invoked upon router-id change. */ static int delete_withdraw_vni_routes(struct bgp *bgp, struct bgpevpn *vpn) { int ret; struct prefix_evpn p; struct bgp_node *global_rn; struct bgp_path_info *pi; afi_t afi = AFI_L2VPN; safi_t safi = SAFI_EVPN; /* Delete and withdraw locally learnt type-2 routes (MACIP) * for this VNI - from the global table. */ ret = delete_global_type2_routes(bgp, vpn); if (ret) return ret; /* Remove type-3 route for this VNI from global table. */ build_evpn_type3_prefix(&p, vpn->originator_ip); global_rn = bgp_afi_node_lookup(bgp->rib[afi][safi], afi, safi, (struct prefix *)&p, &vpn->prd); if (global_rn) { /* Delete route entry in the global EVPN table. */ delete_evpn_route_entry(bgp, afi, safi, global_rn, &pi); /* Schedule for processing - withdraws to peers happen from * this table. */ if (pi) bgp_process(bgp, global_rn, afi, safi); bgp_unlock_node(global_rn); } return 0; } /* * Handle router-id change. Update and advertise local routes corresponding * to this VNI from peers. Note that this is invoked after updating the * router-id. The routes in the per-VNI table are used to create routes in * the global table and schedule them. */ static void update_router_id_vni(struct hash_bucket *bucket, struct bgp *bgp) { struct bgpevpn *vpn = (struct bgpevpn *)bucket->data; /* Skip VNIs with configured RD. */ if (is_rd_configured(vpn)) return; bgp_evpn_derive_auto_rd(bgp, vpn); update_advertise_vni_routes(bgp, vpn); } /* * Handle router-id change. Delete and withdraw local routes corresponding * to this VNI from peers. Note that this is invoked prior to updating * the router-id and is done only on the global route table, the routes * are needed in the per-VNI table to re-advertise with new router id. */ static void withdraw_router_id_vni(struct hash_bucket *bucket, struct bgp *bgp) { struct bgpevpn *vpn = (struct bgpevpn *)bucket->data; /* Skip VNIs with configured RD. */ if (is_rd_configured(vpn)) return; delete_withdraw_vni_routes(bgp, vpn); } /* * Create RT-3 for a VNI and schedule for processing and advertisement. * This is invoked upon flooding mode changing to head-end replication. */ static void create_advertise_type3(struct hash_bucket *bucket, void *data) { struct bgpevpn *vpn = bucket->data; struct bgp *bgp = data; struct prefix_evpn p; if (!vpn || !is_vni_live(vpn) || bgp_evpn_vni_flood_mode_get(bgp, vpn) != VXLAN_FLOOD_HEAD_END_REPL) return; build_evpn_type3_prefix(&p, vpn->originator_ip); if (update_evpn_route(bgp, vpn, &p, 0, 0)) flog_err(EC_BGP_EVPN_ROUTE_CREATE, "Type3 route creation failure for VNI %u", vpn->vni); } /* * Delete RT-3 for a VNI and schedule for processing and withdrawal. * This is invoked upon flooding mode changing to drop BUM packets. */ static void delete_withdraw_type3(struct hash_bucket *bucket, void *data) { struct bgpevpn *vpn = bucket->data; struct bgp *bgp = data; struct prefix_evpn p; if (!vpn || !is_vni_live(vpn)) return; build_evpn_type3_prefix(&p, vpn->originator_ip); delete_evpn_route(bgp, vpn, &p); } /* * Process received EVPN type-2 route (advertise or withdraw). */ static int process_type2_route(struct peer *peer, afi_t afi, safi_t safi, struct attr *attr, uint8_t *pfx, int psize, uint32_t addpath_id) { struct prefix_rd prd; struct prefix_evpn p; struct bgp_route_evpn evpn; uint8_t ipaddr_len; uint8_t macaddr_len; mpls_label_t label[BGP_MAX_LABELS]; /* holds the VNI(s) as in packet */ uint32_t num_labels = 0; uint32_t eth_tag; int ret; /* Type-2 route should be either 33, 37 or 49 bytes or an * additional 3 bytes if there is a second label (VNI): * RD (8), ESI (10), Eth Tag (4), MAC Addr Len (1), * MAC Addr (6), IP len (1), IP (0, 4 or 16), * MPLS Lbl1 (3), MPLS Lbl2 (0 or 3) */ if (psize != 33 && psize != 37 && psize != 49 && psize != 36 && psize != 40 && psize != 52) { flog_err(EC_BGP_EVPN_ROUTE_INVALID, "%u:%s - Rx EVPN Type-2 NLRI with invalid length %d", peer->bgp->vrf_id, peer->host, psize); return -1; } memset(&evpn, 0, sizeof(evpn)); /* Make prefix_rd */ prd.family = AF_UNSPEC; prd.prefixlen = 64; memcpy(&prd.val, pfx, 8); pfx += 8; /* Make EVPN prefix. */ memset(&p, 0, sizeof(struct prefix_evpn)); p.family = AF_EVPN; p.prefixlen = EVPN_ROUTE_PREFIXLEN; p.prefix.route_type = BGP_EVPN_MAC_IP_ROUTE; /* Copy Ethernet Seg Identifier */ memcpy(&evpn.eth_s_id.val, pfx, ESI_LEN); pfx += ESI_LEN; /* Copy Ethernet Tag */ memcpy(ð_tag, pfx, 4); p.prefix.macip_addr.eth_tag = ntohl(eth_tag); pfx += 4; /* Get the MAC Addr len */ macaddr_len = *pfx++; /* Get the MAC Addr */ if (macaddr_len == (ETH_ALEN * 8)) { memcpy(&p.prefix.macip_addr.mac.octet, pfx, ETH_ALEN); pfx += ETH_ALEN; } else { flog_err( EC_BGP_EVPN_ROUTE_INVALID, "%u:%s - Rx EVPN Type-2 NLRI with unsupported MAC address length %d", peer->bgp->vrf_id, peer->host, macaddr_len); return -1; } /* Get the IP. */ ipaddr_len = *pfx++; if (ipaddr_len != 0 && ipaddr_len != IPV4_MAX_BITLEN && ipaddr_len != IPV6_MAX_BITLEN) { flog_err( EC_BGP_EVPN_ROUTE_INVALID, "%u:%s - Rx EVPN Type-2 NLRI with unsupported IP address length %d", peer->bgp->vrf_id, peer->host, ipaddr_len); return -1; } if (ipaddr_len) { ipaddr_len /= 8; /* Convert to bytes. */ p.prefix.macip_addr.ip.ipa_type = (ipaddr_len == IPV4_MAX_BYTELEN) ? IPADDR_V4 : IPADDR_V6; memcpy(&p.prefix.macip_addr.ip.ip.addr, pfx, ipaddr_len); } pfx += ipaddr_len; /* Get the VNI(s). Stored as bytes here. */ num_labels++; memset(label, 0, sizeof(label)); memcpy(&label[0], pfx, BGP_LABEL_BYTES); pfx += BGP_LABEL_BYTES; psize -= (33 + ipaddr_len); /* Do we have a second VNI? */ if (psize) { num_labels++; memcpy(&label[1], pfx, BGP_LABEL_BYTES); /* * If in future, we are required to access additional fields, * we MUST increment pfx by BGP_LABEL_BYTES in before reading * the next field */ } /* Process the route. */ if (attr) ret = bgp_update(peer, (struct prefix *)&p, addpath_id, attr, afi, safi, ZEBRA_ROUTE_BGP, BGP_ROUTE_NORMAL, &prd, &label[0], num_labels, 0, &evpn); else ret = bgp_withdraw(peer, (struct prefix *)&p, addpath_id, attr, afi, safi, ZEBRA_ROUTE_BGP, BGP_ROUTE_NORMAL, &prd, &label[0], num_labels, &evpn); return ret; } /* * Process received EVPN type-3 route (advertise or withdraw). */ static int process_type3_route(struct peer *peer, afi_t afi, safi_t safi, struct attr *attr, uint8_t *pfx, int psize, uint32_t addpath_id) { struct prefix_rd prd; struct prefix_evpn p; uint8_t ipaddr_len; uint32_t eth_tag; int ret; /* Type-3 route should be either 17 or 29 bytes: RD (8), Eth Tag (4), * IP len (1) and IP (4 or 16). */ if (psize != 17 && psize != 29) { flog_err(EC_BGP_EVPN_ROUTE_INVALID, "%u:%s - Rx EVPN Type-3 NLRI with invalid length %d", peer->bgp->vrf_id, peer->host, psize); return -1; } /* If PMSI is present, log if it is anything other than IR. * Note: We just simply ignore the values as it is not clear if * doing anything else is better. */ if (attr && (attr->flag & ATTR_FLAG_BIT(BGP_ATTR_PMSI_TUNNEL))) { if (attr->pmsi_tnl_type != PMSI_TNLTYPE_INGR_REPL && attr->pmsi_tnl_type != PMSI_TNLTYPE_PIM_SM) { flog_warn(EC_BGP_EVPN_PMSI_PRESENT, "%u:%s - Rx EVPN Type-3 NLRI with unsupported PTA %d", peer->bgp->vrf_id, peer->host, attr->pmsi_tnl_type); } } /* Make prefix_rd */ prd.family = AF_UNSPEC; prd.prefixlen = 64; memcpy(&prd.val, pfx, 8); pfx += 8; /* Make EVPN prefix. */ memset(&p, 0, sizeof(struct prefix_evpn)); p.family = AF_EVPN; p.prefixlen = EVPN_ROUTE_PREFIXLEN; p.prefix.route_type = BGP_EVPN_IMET_ROUTE; /* Copy Ethernet Tag */ memcpy(ð_tag, pfx, 4); p.prefix.imet_addr.eth_tag = ntohl(eth_tag); pfx += 4; /* Get the IP. */ ipaddr_len = *pfx++; if (ipaddr_len == IPV4_MAX_BITLEN) { p.prefix.imet_addr.ip.ipa_type = IPADDR_V4; memcpy(&p.prefix.imet_addr.ip.ip.addr, pfx, IPV4_MAX_BYTELEN); } else { flog_err( EC_BGP_EVPN_ROUTE_INVALID, "%u:%s - Rx EVPN Type-3 NLRI with unsupported IP address length %d", peer->bgp->vrf_id, peer->host, ipaddr_len); return -1; } /* Process the route. */ if (attr) ret = bgp_update(peer, (struct prefix *)&p, addpath_id, attr, afi, safi, ZEBRA_ROUTE_BGP, BGP_ROUTE_NORMAL, &prd, NULL, 0, 0, NULL); else ret = bgp_withdraw(peer, (struct prefix *)&p, addpath_id, attr, afi, safi, ZEBRA_ROUTE_BGP, BGP_ROUTE_NORMAL, &prd, NULL, 0, NULL); return ret; } /* * Process received EVPN type-4 route (advertise or withdraw). */ static int process_type4_route(struct peer *peer, afi_t afi, safi_t safi, struct attr *attr, uint8_t *pfx, int psize, uint32_t addpath_id) { int ret; esi_t esi; uint8_t ipaddr_len; struct in_addr vtep_ip; struct prefix_rd prd; struct prefix_evpn p; /* Type-4 route should be either 23 or 35 bytes * RD (8), ESI (10), ip-len (1), ip (4 or 16) */ if (psize != 23 && psize != 35) { flog_err(EC_BGP_EVPN_ROUTE_INVALID, "%u:%s - Rx EVPN Type-4 NLRI with invalid length %d", peer->bgp->vrf_id, peer->host, psize); return -1; } /* Make prefix_rd */ prd.family = AF_UNSPEC; prd.prefixlen = 64; memcpy(&prd.val, pfx, 8); pfx += 8; /* get the ESI */ memcpy(&esi, pfx, ESI_BYTES); pfx += ESI_BYTES; /* Get the IP. */ ipaddr_len = *pfx++; if (ipaddr_len == IPV4_MAX_BITLEN) { memcpy(&vtep_ip, pfx, IPV4_MAX_BYTELEN); } else { flog_err( EC_BGP_EVPN_ROUTE_INVALID, "%u:%s - Rx EVPN Type-4 NLRI with unsupported IP address length %d", peer->bgp->vrf_id, peer->host, ipaddr_len); return -1; } build_evpn_type4_prefix(&p, &esi, vtep_ip); /* Process the route. */ if (attr) { ret = bgp_update(peer, (struct prefix *)&p, addpath_id, attr, afi, safi, ZEBRA_ROUTE_BGP, BGP_ROUTE_NORMAL, &prd, NULL, 0, 0, NULL); } else { ret = bgp_withdraw(peer, (struct prefix *)&p, addpath_id, attr, afi, safi, ZEBRA_ROUTE_BGP, BGP_ROUTE_NORMAL, &prd, NULL, 0, NULL); } return ret; } /* * Process received EVPN type-5 route (advertise or withdraw). */ static int process_type5_route(struct peer *peer, afi_t afi, safi_t safi, struct attr *attr, uint8_t *pfx, int psize, uint32_t addpath_id) { struct prefix_rd prd; struct prefix_evpn p; struct bgp_route_evpn evpn; uint8_t ippfx_len; uint32_t eth_tag; mpls_label_t label; /* holds the VNI as in the packet */ int ret; /* Type-5 route should be 34 or 58 bytes: * RD (8), ESI (10), Eth Tag (4), IP len (1), IP (4 or 16), * GW (4 or 16) and VNI (3). * Note that the IP and GW should both be IPv4 or both IPv6. */ if (psize != 34 && psize != 58) { flog_err(EC_BGP_EVPN_ROUTE_INVALID, "%u:%s - Rx EVPN Type-5 NLRI with invalid length %d", peer->bgp->vrf_id, peer->host, psize); return -1; } /* Make prefix_rd */ prd.family = AF_UNSPEC; prd.prefixlen = 64; memcpy(&prd.val, pfx, 8); pfx += 8; /* Make EVPN prefix. */ memset(&p, 0, sizeof(struct prefix_evpn)); p.family = AF_EVPN; p.prefixlen = EVPN_ROUTE_PREFIXLEN; p.prefix.route_type = BGP_EVPN_IP_PREFIX_ROUTE; /* Additional information outside of prefix - ESI and GW IP */ memset(&evpn, 0, sizeof(evpn)); /* Fetch ESI */ memcpy(&evpn.eth_s_id.val, pfx, 10); pfx += 10; /* Fetch Ethernet Tag. */ memcpy(ð_tag, pfx, 4); p.prefix.prefix_addr.eth_tag = ntohl(eth_tag); pfx += 4; /* Fetch IP prefix length. */ ippfx_len = *pfx++; if (ippfx_len > IPV6_MAX_BITLEN) { flog_err( EC_BGP_EVPN_ROUTE_INVALID, "%u:%s - Rx EVPN Type-5 NLRI with invalid IP Prefix length %d", peer->bgp->vrf_id, peer->host, ippfx_len); return -1; } p.prefix.prefix_addr.ip_prefix_length = ippfx_len; /* Determine IPv4 or IPv6 prefix */ /* Since the address and GW are from the same family, this just becomes * a simple check on the total size. */ if (psize == 34) { SET_IPADDR_V4(&p.prefix.prefix_addr.ip); memcpy(&p.prefix.prefix_addr.ip.ipaddr_v4, pfx, 4); pfx += 4; memcpy(&evpn.gw_ip.ipv4, pfx, 4); pfx += 4; } else { SET_IPADDR_V6(&p.prefix.prefix_addr.ip); memcpy(&p.prefix.prefix_addr.ip.ipaddr_v6, pfx, 16); pfx += 16; memcpy(&evpn.gw_ip.ipv6, pfx, 16); pfx += 16; } /* Get the VNI (in MPLS label field). Stored as bytes here. */ memset(&label, 0, sizeof(label)); memcpy(&label, pfx, BGP_LABEL_BYTES); /* * If in future, we are required to access additional fields, * we MUST increment pfx by BGP_LABEL_BYTES in before reading the next * field */ /* Process the route. */ if (attr) ret = bgp_update(peer, (struct prefix *)&p, addpath_id, attr, afi, safi, ZEBRA_ROUTE_BGP, BGP_ROUTE_NORMAL, &prd, &label, 1, 0, &evpn); else ret = bgp_withdraw(peer, (struct prefix *)&p, addpath_id, attr, afi, safi, ZEBRA_ROUTE_BGP, BGP_ROUTE_NORMAL, &prd, &label, 1, &evpn); return ret; } static void evpn_mpattr_encode_type5(struct stream *s, struct prefix *p, struct prefix_rd *prd, mpls_label_t *label, uint32_t num_labels, struct attr *attr) { int len; char temp[16]; struct evpn_addr *p_evpn_p; memset(&temp, 0, 16); if (p->family != AF_EVPN) return; p_evpn_p = &(p->u.prefix_evpn); /* len denites the total len of IP and GW-IP in the route IP and GW-IP have to be both ipv4 or ipv6 */ if (IS_IPADDR_V4(&p_evpn_p->prefix_addr.ip)) len = 8; /* IP and GWIP are both ipv4 */ else len = 32; /* IP and GWIP are both ipv6 */ /* Prefix contains RD, ESI, EthTag, IP length, IP, GWIP and VNI */ stream_putc(s, 8 + 10 + 4 + 1 + len + 3); stream_put(s, prd->val, 8); if (attr) stream_put(s, &(attr->evpn_overlay.eth_s_id), 10); else stream_put(s, &temp, 10); stream_putl(s, p_evpn_p->prefix_addr.eth_tag); stream_putc(s, p_evpn_p->prefix_addr.ip_prefix_length); if (IS_IPADDR_V4(&p_evpn_p->prefix_addr.ip)) stream_put_ipv4(s, p_evpn_p->prefix_addr.ip.ipaddr_v4.s_addr); else stream_put(s, &p_evpn_p->prefix_addr.ip.ipaddr_v6, 16); if (attr) { if (IS_IPADDR_V4(&p_evpn_p->prefix_addr.ip)) stream_put_ipv4(s, attr->evpn_overlay.gw_ip.ipv4.s_addr); else stream_put(s, &(attr->evpn_overlay.gw_ip.ipv6), 16); } else { if (IS_IPADDR_V4(&p_evpn_p->prefix_addr.ip)) stream_put_ipv4(s, 0); else stream_put(s, &temp, 16); } if (num_labels) stream_put(s, label, 3); else stream_put3(s, 0); } /* * Cleanup specific VNI upon EVPN (advertise-all-vni) being disabled. */ static void cleanup_vni_on_disable(struct hash_bucket *bucket, struct bgp *bgp) { struct bgpevpn *vpn = (struct bgpevpn *)bucket->data; /* Remove EVPN routes and schedule for processing. */ delete_routes_for_vni(bgp, vpn); /* Clear "live" flag and see if hash needs to be freed. */ UNSET_FLAG(vpn->flags, VNI_FLAG_LIVE); if (!is_vni_configured(vpn)) bgp_evpn_free(bgp, vpn); } /* * Free a VNI entry; iterator function called during cleanup. */ static void free_vni_entry(struct hash_bucket *bucket, struct bgp *bgp) { struct bgpevpn *vpn = (struct bgpevpn *)bucket->data; delete_all_vni_routes(bgp, vpn); bgp_evpn_free(bgp, vpn); } /* * Derive AUTO import RT for BGP VRF - L3VNI */ static void evpn_auto_rt_import_add_for_vrf(struct bgp *bgp_vrf) { struct bgp *bgp_evpn = NULL; form_auto_rt(bgp_vrf, bgp_vrf->l3vni, bgp_vrf->vrf_import_rtl); UNSET_FLAG(bgp_vrf->vrf_flags, BGP_VRF_IMPORT_RT_CFGD); /* Map RT to VRF */ bgp_evpn = bgp_get_evpn(); if (!bgp_evpn) return; bgp_evpn_map_vrf_to_its_rts(bgp_vrf); } /* * Delete AUTO import RT from BGP VRF - L3VNI */ static void evpn_auto_rt_import_delete_for_vrf(struct bgp *bgp_vrf) { evpn_rt_delete_auto(bgp_vrf, bgp_vrf->l3vni, bgp_vrf->vrf_import_rtl); } /* * Derive AUTO export RT for BGP VRF - L3VNI */ static void evpn_auto_rt_export_add_for_vrf(struct bgp *bgp_vrf) { UNSET_FLAG(bgp_vrf->vrf_flags, BGP_VRF_EXPORT_RT_CFGD); form_auto_rt(bgp_vrf, bgp_vrf->l3vni, bgp_vrf->vrf_export_rtl); } /* * Delete AUTO export RT from BGP VRF - L3VNI */ static void evpn_auto_rt_export_delete_for_vrf(struct bgp *bgp_vrf) { evpn_rt_delete_auto(bgp_vrf, bgp_vrf->l3vni, bgp_vrf->vrf_export_rtl); } static void bgp_evpn_handle_export_rt_change_for_vrf(struct bgp *bgp_vrf) { struct bgp *bgp_evpn = NULL; struct listnode *node = NULL; struct bgpevpn *vpn = NULL; bgp_evpn = bgp_get_evpn(); if (!bgp_evpn) return; /* update all type-5 routes */ update_advertise_vrf_routes(bgp_vrf); /* update all type-2 routes */ for (ALL_LIST_ELEMENTS_RO(bgp_vrf->l2vnis, node, vpn)) update_routes_for_vni(bgp_evpn, vpn); } /* * Handle autort change for a given VNI. */ static void update_autort_vni(struct hash_bucket *bucket, struct bgp *bgp) { struct bgpevpn *vpn = bucket->data; if (!is_import_rt_configured(vpn)) { if (is_vni_live(vpn)) bgp_evpn_uninstall_routes(bgp, vpn); bgp_evpn_unmap_vni_from_its_rts(bgp, vpn); list_delete_all_node(vpn->import_rtl); bgp_evpn_derive_auto_rt_import(bgp, vpn); if (is_vni_live(vpn)) bgp_evpn_install_routes(bgp, vpn); } if (!is_export_rt_configured(vpn)) { list_delete_all_node(vpn->export_rtl); bgp_evpn_derive_auto_rt_export(bgp, vpn); if (is_vni_live(vpn)) bgp_evpn_handle_export_rt_change(bgp, vpn); } } /* * Public functions. */ /* withdraw type-5 route corresponding to ip prefix */ void bgp_evpn_withdraw_type5_route(struct bgp *bgp_vrf, struct prefix *p, afi_t afi, safi_t safi) { int ret = 0; struct prefix_evpn evp; char buf[PREFIX_STRLEN]; build_type5_prefix_from_ip_prefix(&evp, p); ret = delete_evpn_type5_route(bgp_vrf, &evp); if (ret) { flog_err( EC_BGP_EVPN_ROUTE_DELETE, "%u failed to delete type-5 route for prefix %s in vrf %s", bgp_vrf->vrf_id, prefix2str(p, buf, sizeof(buf)), vrf_id_to_name(bgp_vrf->vrf_id)); } } /* withdraw all type-5 routes for an address family */ void bgp_evpn_withdraw_type5_routes(struct bgp *bgp_vrf, afi_t afi, safi_t safi) { struct bgp_table *table = NULL; struct bgp_node *rn = NULL; struct bgp_path_info *pi; table = bgp_vrf->rib[afi][safi]; for (rn = bgp_table_top(table); rn; rn = bgp_route_next(rn)) { /* Only care about "selected" routes. Also ensure that * these are routes that are injectable into EVPN. */ /* TODO: Support for AddPath for EVPN. */ for (pi = bgp_node_get_bgp_path_info(rn); pi; pi = pi->next) { if (CHECK_FLAG(pi->flags, BGP_PATH_SELECTED) && is_route_injectable_into_evpn(pi)) { bgp_evpn_withdraw_type5_route(bgp_vrf, &rn->p, afi, safi); break; } } } } /* * evpn - enable advertisement of default g/w */ void bgp_evpn_install_uninstall_default_route(struct bgp *bgp_vrf, afi_t afi, safi_t safi, bool add) { struct prefix ip_prefix; /* form the default prefix 0.0.0.0/0 */ memset(&ip_prefix, 0, sizeof(struct prefix)); ip_prefix.family = afi2family(afi); if (add) { bgp_evpn_advertise_type5_route(bgp_vrf, &ip_prefix, NULL, afi, safi); } else { bgp_evpn_withdraw_type5_route(bgp_vrf, &ip_prefix, afi, safi); } } /* * Advertise IP prefix as type-5 route. The afi/safi and src_attr passed * to this function correspond to those of the source IP prefix (best * path in the case of the attr. In the case of a local prefix (when we * are advertising local subnets), the src_attr will be NULL. */ void bgp_evpn_advertise_type5_route(struct bgp *bgp_vrf, struct prefix *p, struct attr *src_attr, afi_t afi, safi_t safi) { int ret = 0; struct prefix_evpn evp; char buf[PREFIX_STRLEN]; build_type5_prefix_from_ip_prefix(&evp, p); ret = update_evpn_type5_route(bgp_vrf, &evp, src_attr); if (ret) flog_err(EC_BGP_EVPN_ROUTE_CREATE, "%u: Failed to create type-5 route for prefix %s", bgp_vrf->vrf_id, prefix2str(p, buf, sizeof(buf))); } /* Inject all prefixes of a particular address-family (currently, IPv4 or * IPv6 unicast) into EVPN as type-5 routes. This is invoked when the * advertisement is enabled. */ void bgp_evpn_advertise_type5_routes(struct bgp *bgp_vrf, afi_t afi, safi_t safi) { struct bgp_table *table = NULL; struct bgp_node *rn = NULL; struct bgp_path_info *pi; table = bgp_vrf->rib[afi][safi]; for (rn = bgp_table_top(table); rn; rn = bgp_route_next(rn)) { /* Need to identify the "selected" route entry to use its * attribute. Also, ensure that the route is injectable * into EVPN. * TODO: Support for AddPath for EVPN. */ for (pi = bgp_node_get_bgp_path_info(rn); pi; pi = pi->next) { if (CHECK_FLAG(pi->flags, BGP_PATH_SELECTED) && is_route_injectable_into_evpn(pi)) { /* apply the route-map */ if (bgp_vrf->adv_cmd_rmap[afi][safi].map) { route_map_result_t ret; ret = route_map_apply( bgp_vrf->adv_cmd_rmap[afi][safi] .map, &rn->p, RMAP_BGP, pi); if (ret == RMAP_DENYMATCH) continue; } bgp_evpn_advertise_type5_route( bgp_vrf, &rn->p, pi->attr, afi, safi); break; } } } } void evpn_rt_delete_auto(struct bgp *bgp, vni_t vni, struct list *rtl) { struct listnode *node, *nnode, *node_to_del; struct ecommunity *ecom, *ecom_auto; struct ecommunity_val eval; if (bgp->advertise_autort_rfc8365) vni |= EVPN_AUTORT_VXLAN; encode_route_target_as((bgp->as & 0xFFFF), vni, &eval); ecom_auto = ecommunity_new(); ecommunity_add_val(ecom_auto, &eval); node_to_del = NULL; for (ALL_LIST_ELEMENTS(rtl, node, nnode, ecom)) { if (ecommunity_match(ecom, ecom_auto)) { ecommunity_free(&ecom); node_to_del = node; } } if (node_to_del) list_delete_node(rtl, node_to_del); ecommunity_free(&ecom_auto); } void bgp_evpn_configure_import_rt_for_vrf(struct bgp *bgp_vrf, struct ecommunity *ecomadd) { /* uninstall routes from vrf */ if (is_l3vni_live(bgp_vrf)) uninstall_routes_for_vrf(bgp_vrf); /* Cleanup the RT to VRF mapping */ bgp_evpn_unmap_vrf_from_its_rts(bgp_vrf); /* Remove auto generated RT */ evpn_auto_rt_import_delete_for_vrf(bgp_vrf); /* Add the newly configured RT to RT list */ listnode_add_sort(bgp_vrf->vrf_import_rtl, ecomadd); SET_FLAG(bgp_vrf->vrf_flags, BGP_VRF_IMPORT_RT_CFGD); /* map VRF to its RTs and install routes matching the new RTs */ if (is_l3vni_live(bgp_vrf)) { bgp_evpn_map_vrf_to_its_rts(bgp_vrf); install_routes_for_vrf(bgp_vrf); } } void bgp_evpn_unconfigure_import_rt_for_vrf(struct bgp *bgp_vrf, struct ecommunity *ecomdel) { struct listnode *node = NULL, *nnode = NULL, *node_to_del = NULL; struct ecommunity *ecom = NULL; /* uninstall routes from vrf */ if (is_l3vni_live(bgp_vrf)) uninstall_routes_for_vrf(bgp_vrf); /* Cleanup the RT to VRF mapping */ bgp_evpn_unmap_vrf_from_its_rts(bgp_vrf); /* remove the RT from the RT list */ for (ALL_LIST_ELEMENTS(bgp_vrf->vrf_import_rtl, node, nnode, ecom)) { if (ecommunity_match(ecom, ecomdel)) { ecommunity_free(&ecom); node_to_del = node; break; } } if (node_to_del) list_delete_node(bgp_vrf->vrf_import_rtl, node_to_del); assert(bgp_vrf->vrf_import_rtl); /* fallback to auto import rt, if this was the last RT */ if (bgp_vrf->vrf_import_rtl && list_isempty(bgp_vrf->vrf_import_rtl)) { UNSET_FLAG(bgp_vrf->vrf_flags, BGP_VRF_IMPORT_RT_CFGD); evpn_auto_rt_import_add_for_vrf(bgp_vrf); } /* map VRFs to its RTs and install routes matching this new RT */ if (is_l3vni_live(bgp_vrf)) { bgp_evpn_map_vrf_to_its_rts(bgp_vrf); install_routes_for_vrf(bgp_vrf); } } void bgp_evpn_configure_export_rt_for_vrf(struct bgp *bgp_vrf, struct ecommunity *ecomadd) { /* remove auto-generated RT */ evpn_auto_rt_export_delete_for_vrf(bgp_vrf); /* Add the new RT to the RT list */ listnode_add_sort(bgp_vrf->vrf_export_rtl, ecomadd); SET_FLAG(bgp_vrf->vrf_flags, BGP_VRF_EXPORT_RT_CFGD); bgp_evpn_handle_export_rt_change_for_vrf(bgp_vrf); } void bgp_evpn_unconfigure_export_rt_for_vrf(struct bgp *bgp_vrf, struct ecommunity *ecomdel) { struct listnode *node = NULL, *nnode = NULL, *node_to_del = NULL; struct ecommunity *ecom = NULL; /* Remove the RT from the RT list */ for (ALL_LIST_ELEMENTS(bgp_vrf->vrf_export_rtl, node, nnode, ecom)) { if (ecommunity_match(ecom, ecomdel)) { ecommunity_free(&ecom); node_to_del = node; break; } } if (node_to_del) list_delete_node(bgp_vrf->vrf_export_rtl, node_to_del); /* * Temporary assert to make SA happy. * The ALL_LIST_ELEMENTS macro above has a NULL check * which means that SA is going to complain about * the list_isempty call, which doesn't NULL check. * So until we get this situation cleaned up, here * we are. */ assert(bgp_vrf->vrf_export_rtl); /* fall back to auto-generated RT if this was the last RT */ if (list_isempty(bgp_vrf->vrf_export_rtl)) { UNSET_FLAG(bgp_vrf->vrf_flags, BGP_VRF_EXPORT_RT_CFGD); evpn_auto_rt_export_add_for_vrf(bgp_vrf); } bgp_evpn_handle_export_rt_change_for_vrf(bgp_vrf); } /* * Handle change to BGP router id. This is invoked twice by the change * handler, first before the router id has been changed and then after * the router id has been changed. The first invocation will result in * local routes for all VNIs/VRF being deleted and withdrawn and the next * will result in the routes being re-advertised. */ void bgp_evpn_handle_router_id_update(struct bgp *bgp, int withdraw) { if (withdraw) { /* delete and withdraw all the type-5 routes stored in the global table for this vrf */ withdraw_router_id_vrf(bgp); /* delete all the VNI routes (type-2/type-3) routes for all the * L2-VNIs */ hash_iterate(bgp->vnihash, (void (*)(struct hash_bucket *, void *))withdraw_router_id_vni, bgp); } else { /* advertise all routes in the vrf as type-5 routes with the new * RD */ update_router_id_vrf(bgp); /* advertise all the VNI routes (type-2/type-3) routes with the * new RD */ hash_iterate(bgp->vnihash, (void (*)(struct hash_bucket *, void *))update_router_id_vni, bgp); } } /* * Handle change to auto-RT algorithm - update and advertise local routes. */ void bgp_evpn_handle_autort_change(struct bgp *bgp) { hash_iterate(bgp->vnihash, (void (*)(struct hash_bucket *, void*))update_autort_vni, bgp); } /* * Handle change to export RT - update and advertise local routes. */ int bgp_evpn_handle_export_rt_change(struct bgp *bgp, struct bgpevpn *vpn) { return update_routes_for_vni(bgp, vpn); } void bgp_evpn_handle_vrf_rd_change(struct bgp *bgp_vrf, int withdraw) { if (withdraw) delete_withdraw_vrf_routes(bgp_vrf); else update_advertise_vrf_routes(bgp_vrf); } /* * Handle change to RD. This is invoked twice by the change handler, * first before the RD has been changed and then after the RD has * been changed. The first invocation will result in local routes * of this VNI being deleted and withdrawn and the next will result * in the routes being re-advertised. */ void bgp_evpn_handle_rd_change(struct bgp *bgp, struct bgpevpn *vpn, int withdraw) { if (withdraw) delete_withdraw_vni_routes(bgp, vpn); else update_advertise_vni_routes(bgp, vpn); } /* * Install routes for this VNI. Invoked upon change to Import RT. */ int bgp_evpn_install_routes(struct bgp *bgp, struct bgpevpn *vpn) { return install_routes_for_vni(bgp, vpn); } /* * Uninstall all routes installed for this VNI. Invoked upon change * to Import RT. */ int bgp_evpn_uninstall_routes(struct bgp *bgp, struct bgpevpn *vpn) { return uninstall_routes_for_vni(bgp, vpn); } /* * TODO: Hardcoded for a maximum of 2 VNIs right now */ char *bgp_evpn_label2str(mpls_label_t *label, uint32_t num_labels, char *buf, int len) { vni_t vni1, vni2; vni1 = label2vni(label); if (num_labels == 2) { vni2 = label2vni(label + 1); snprintf(buf, len, "%u/%u", vni1, vni2); } else snprintf(buf, len, "%u", vni1); return buf; } /* * Function to convert evpn route to json format. * NOTE: We don't use prefix2str as the output here is a bit different. */ void bgp_evpn_route2json(struct prefix_evpn *p, json_object *json) { char buf1[ETHER_ADDR_STRLEN]; char buf2[PREFIX2STR_BUFFER]; if (!json) return; if (p->prefix.route_type == BGP_EVPN_IMET_ROUTE) { json_object_int_add(json, "routeType", p->prefix.route_type); json_object_int_add(json, "ethTag", p->prefix.imet_addr.eth_tag); json_object_int_add(json, "ipLen", is_evpn_prefix_ipaddr_v4(p) ? IPV4_MAX_BITLEN : IPV6_MAX_BITLEN); json_object_string_add(json, "ip", inet_ntoa(p->prefix.imet_addr.ip.ipaddr_v4)); } else if (p->prefix.route_type == BGP_EVPN_MAC_IP_ROUTE) { if (is_evpn_prefix_ipaddr_none(p)) { json_object_int_add(json, "routeType", p->prefix.route_type); json_object_int_add(json, "ethTag", p->prefix.macip_addr.eth_tag); json_object_int_add(json, "macLen", 8 * ETH_ALEN); json_object_string_add(json, "mac", prefix_mac2str(&p->prefix.macip_addr.mac, buf1, sizeof(buf1))); } else { uint8_t family; family = is_evpn_prefix_ipaddr_v4(p) ? AF_INET : AF_INET6; json_object_int_add(json, "routeType", p->prefix.route_type); json_object_int_add(json, "ethTag", p->prefix.macip_addr.eth_tag); json_object_int_add(json, "macLen", 8 * ETH_ALEN); json_object_string_add(json, "mac", prefix_mac2str(&p->prefix.macip_addr.mac, buf1, sizeof(buf1))); json_object_int_add(json, "ipLen", is_evpn_prefix_ipaddr_v4(p) ? IPV4_MAX_BITLEN : IPV6_MAX_BITLEN); json_object_string_add( json, "ip", inet_ntop(family, &p->prefix.macip_addr.ip.ip.addr, buf2, PREFIX2STR_BUFFER)); } } else { /* Currently, this is to cater to other AF_ETHERNET code. */ } } /* * Function to convert evpn route to string. * NOTE: We don't use prefix2str as the output here is a bit different. */ char *bgp_evpn_route2str(struct prefix_evpn *p, char *buf, int len) { char buf1[ETHER_ADDR_STRLEN]; char buf2[PREFIX2STR_BUFFER]; char buf3[ESI_STR_LEN]; if (p->prefix.route_type == BGP_EVPN_IMET_ROUTE) { snprintf(buf, len, "[%d]:[%d]:[%d]:[%s]", p->prefix.route_type, p->prefix.imet_addr.eth_tag, is_evpn_prefix_ipaddr_v4(p) ? IPV4_MAX_BITLEN : IPV6_MAX_BITLEN, inet_ntoa(p->prefix.imet_addr.ip.ipaddr_v4)); } else if (p->prefix.route_type == BGP_EVPN_MAC_IP_ROUTE) { if (is_evpn_prefix_ipaddr_none(p)) snprintf(buf, len, "[%d]:[%d]:[%d]:[%s]", p->prefix.route_type, p->prefix.macip_addr.eth_tag, 8 * ETH_ALEN, prefix_mac2str(&p->prefix.macip_addr.mac, buf1, sizeof(buf1))); else { uint8_t family; family = is_evpn_prefix_ipaddr_v4(p) ? AF_INET : AF_INET6; snprintf(buf, len, "[%d]:[%d]:[%d]:[%s]:[%d]:[%s]", p->prefix.route_type, p->prefix.macip_addr.eth_tag, 8 * ETH_ALEN, prefix_mac2str(&p->prefix.macip_addr.mac, buf1, sizeof(buf1)), family == AF_INET ? IPV4_MAX_BITLEN : IPV6_MAX_BITLEN, inet_ntop(family, &p->prefix.macip_addr.ip.ip.addr, buf2, PREFIX2STR_BUFFER)); } } else if (p->prefix.route_type == BGP_EVPN_IP_PREFIX_ROUTE) { snprintf(buf, len, "[%d]:[%d]:[%d]:[%s]", p->prefix.route_type, p->prefix.prefix_addr.eth_tag, p->prefix.prefix_addr.ip_prefix_length, is_evpn_prefix_ipaddr_v4(p) ? inet_ntoa(p->prefix.prefix_addr.ip.ipaddr_v4) : inet6_ntoa(p->prefix.prefix_addr.ip.ipaddr_v6)); } else if (p->prefix.route_type == BGP_EVPN_ES_ROUTE) { snprintf(buf, len, "[%d]:[%s]:[%d]:[%s]", p->prefix.route_type, esi_to_str(&p->prefix.es_addr.esi, buf3, sizeof(buf3)), is_evpn_prefix_ipaddr_v4(p) ? IPV4_MAX_BITLEN : IPV6_MAX_BITLEN, inet_ntoa(p->prefix.es_addr.ip.ipaddr_v4)); } else { /* For EVPN route types not supported yet. */ snprintf(buf, len, "(unsupported route type %d)", p->prefix.route_type); } return (buf); } /* * Encode EVPN prefix in Update (MP_REACH) */ void bgp_evpn_encode_prefix(struct stream *s, struct prefix *p, struct prefix_rd *prd, mpls_label_t *label, uint32_t num_labels, struct attr *attr, int addpath_encode, uint32_t addpath_tx_id) { struct prefix_evpn *evp = (struct prefix_evpn *)p; int len, ipa_len = 0; if (addpath_encode) stream_putl(s, addpath_tx_id); /* Route type */ stream_putc(s, evp->prefix.route_type); switch (evp->prefix.route_type) { case BGP_EVPN_MAC_IP_ROUTE: if (is_evpn_prefix_ipaddr_v4(evp)) ipa_len = IPV4_MAX_BYTELEN; else if (is_evpn_prefix_ipaddr_v6(evp)) ipa_len = IPV6_MAX_BYTELEN; /* RD, ESI, EthTag, MAC+len, IP len, [IP], 1 VNI */ len = 8 + 10 + 4 + 1 + 6 + 1 + ipa_len + 3; if (ipa_len && num_labels > 1) /* There are 2 VNIs */ len += 3; stream_putc(s, len); stream_put(s, prd->val, 8); /* RD */ if (attr) stream_put(s, &attr->evpn_overlay.eth_s_id, ESI_LEN); else stream_put(s, 0, 10); stream_putl(s, evp->prefix.macip_addr.eth_tag); /* Ethernet Tag ID */ stream_putc(s, 8 * ETH_ALEN); /* Mac Addr Len - bits */ stream_put(s, evp->prefix.macip_addr.mac.octet, 6); /* Mac Addr */ stream_putc(s, 8 * ipa_len); /* IP address Length */ if (ipa_len) /* IP */ stream_put(s, &evp->prefix.macip_addr.ip.ip.addr, ipa_len); /* 1st label is the L2 VNI */ stream_put(s, label, BGP_LABEL_BYTES); /* Include 2nd label (L3 VNI) if advertising MAC+IP */ if (ipa_len && num_labels > 1) stream_put(s, label + 1, BGP_LABEL_BYTES); break; case BGP_EVPN_IMET_ROUTE: stream_putc(s, 17); // TODO: length - assumes IPv4 address stream_put(s, prd->val, 8); /* RD */ stream_putl(s, evp->prefix.imet_addr.eth_tag); /* Ethernet Tag ID */ stream_putc(s, IPV4_MAX_BITLEN); /* IP address Length - bits */ /* Originating Router's IP Addr */ stream_put_in_addr(s, &evp->prefix.imet_addr.ip.ipaddr_v4); break; case BGP_EVPN_ES_ROUTE: stream_putc(s, 23); /* TODO: length: assumes ipv4 VTEP */ stream_put(s, prd->val, 8); /* RD */ stream_put(s, evp->prefix.es_addr.esi.val, 10); /* ESI */ stream_putc(s, IPV4_MAX_BITLEN); /* IP address Length - bits */ /* VTEP IP */ stream_put_in_addr(s, &evp->prefix.es_addr.ip.ipaddr_v4); break; case BGP_EVPN_IP_PREFIX_ROUTE: /* TODO: AddPath support. */ evpn_mpattr_encode_type5(s, p, prd, label, num_labels, attr); break; default: break; } } int bgp_nlri_parse_evpn(struct peer *peer, struct attr *attr, struct bgp_nlri *packet, int withdraw) { uint8_t *pnt; uint8_t *lim; afi_t afi; safi_t safi; uint32_t addpath_id; int addpath_encoded; int psize = 0; uint8_t rtype; struct prefix p; /* Start processing the NLRI - there may be multiple in the MP_REACH */ pnt = packet->nlri; lim = pnt + packet->length; afi = packet->afi; safi = packet->safi; addpath_id = 0; addpath_encoded = (CHECK_FLAG(peer->af_cap[afi][safi], PEER_CAP_ADDPATH_AF_RX_ADV) && CHECK_FLAG(peer->af_cap[afi][safi], PEER_CAP_ADDPATH_AF_TX_RCV)); for (; pnt < lim; pnt += psize) { /* Clear prefix structure. */ memset(&p, 0, sizeof(struct prefix)); /* Deal with path-id if AddPath is supported. */ if (addpath_encoded) { /* When packet overflow occurs return immediately. */ if (pnt + BGP_ADDPATH_ID_LEN > lim) return BGP_NLRI_PARSE_ERROR_PACKET_OVERFLOW; memcpy(&addpath_id, pnt, BGP_ADDPATH_ID_LEN); addpath_id = ntohl(addpath_id); pnt += BGP_ADDPATH_ID_LEN; } /* All EVPN NLRI types start with type and length. */ if (pnt + 2 > lim) return BGP_NLRI_PARSE_ERROR_EVPN_MISSING_TYPE; rtype = *pnt++; psize = *pnt++; /* When packet overflow occur return immediately. */ if (pnt + psize > lim) return BGP_NLRI_PARSE_ERROR_PACKET_OVERFLOW; switch (rtype) { case BGP_EVPN_MAC_IP_ROUTE: if (process_type2_route(peer, afi, safi, withdraw ? NULL : attr, pnt, psize, addpath_id)) { flog_err( EC_BGP_EVPN_FAIL, "%u:%s - Error in processing EVPN type-2 NLRI size %d", peer->bgp->vrf_id, peer->host, psize); return BGP_NLRI_PARSE_ERROR_EVPN_TYPE2_SIZE; } break; case BGP_EVPN_IMET_ROUTE: if (process_type3_route(peer, afi, safi, withdraw ? NULL : attr, pnt, psize, addpath_id)) { flog_err( EC_BGP_PKT_PROCESS, "%u:%s - Error in processing EVPN type-3 NLRI size %d", peer->bgp->vrf_id, peer->host, psize); return BGP_NLRI_PARSE_ERROR_EVPN_TYPE3_SIZE; } break; case BGP_EVPN_ES_ROUTE: if (process_type4_route(peer, afi, safi, withdraw ? NULL : attr, pnt, psize, addpath_id)) { flog_err( EC_BGP_PKT_PROCESS, "%u:%s - Error in processing EVPN type-4 NLRI size %d", peer->bgp->vrf_id, peer->host, psize); return BGP_NLRI_PARSE_ERROR_EVPN_TYPE4_SIZE; } break; case BGP_EVPN_IP_PREFIX_ROUTE: if (process_type5_route(peer, afi, safi, withdraw ? NULL : attr, pnt, psize, addpath_id)) { flog_err( EC_BGP_PKT_PROCESS, "%u:%s - Error in processing EVPN type-5 NLRI size %d", peer->bgp->vrf_id, peer->host, psize); return BGP_NLRI_PARSE_ERROR_EVPN_TYPE5_SIZE; } break; default: break; } } /* Packet length consistency check. */ if (pnt != lim) return BGP_NLRI_PARSE_ERROR_PACKET_LENGTH; return BGP_NLRI_PARSE_OK; } /* * Map the RTs (configured or automatically derived) of a VRF to the VRF. * The mapping will be used during route processing. * bgp_def: default bgp instance * bgp_vrf: specific bgp vrf instance on which RT is configured */ void bgp_evpn_map_vrf_to_its_rts(struct bgp *bgp_vrf) { int i = 0; struct ecommunity_val *eval = NULL; struct listnode *node = NULL, *nnode = NULL; struct ecommunity *ecom = NULL; for (ALL_LIST_ELEMENTS(bgp_vrf->vrf_import_rtl, node, nnode, ecom)) { for (i = 0; i < ecom->size; i++) { eval = (struct ecommunity_val *)(ecom->val + (i * ECOMMUNITY_SIZE)); map_vrf_to_rt(bgp_vrf, eval); } } } /* * Unmap the RTs (configured or automatically derived) of a VRF from the VRF. */ void bgp_evpn_unmap_vrf_from_its_rts(struct bgp *bgp_vrf) { int i; struct ecommunity_val *eval; struct listnode *node, *nnode; struct ecommunity *ecom; for (ALL_LIST_ELEMENTS(bgp_vrf->vrf_import_rtl, node, nnode, ecom)) { for (i = 0; i < ecom->size; i++) { struct vrf_irt_node *irt; struct ecommunity_val eval_tmp; eval = (struct ecommunity_val *)(ecom->val + (i * ECOMMUNITY_SIZE)); /* If using "automatic" RT, we only care about the * local-admin sub-field. * This is to facilitate using VNI as the RT for EBGP * peering too. */ memcpy(&eval_tmp, eval, ECOMMUNITY_SIZE); if (!CHECK_FLAG(bgp_vrf->vrf_flags, BGP_VRF_IMPORT_RT_CFGD)) mask_ecom_global_admin(&eval_tmp, eval); irt = lookup_vrf_import_rt(&eval_tmp); if (irt) unmap_vrf_from_rt(bgp_vrf, irt); } } } /* * Map the RTs (configured or automatically derived) of a VNI to the VNI. * The mapping will be used during route processing. */ void bgp_evpn_map_vni_to_its_rts(struct bgp *bgp, struct bgpevpn *vpn) { int i; struct ecommunity_val *eval; struct listnode *node, *nnode; struct ecommunity *ecom; for (ALL_LIST_ELEMENTS(vpn->import_rtl, node, nnode, ecom)) { for (i = 0; i < ecom->size; i++) { eval = (struct ecommunity_val *)(ecom->val + (i * ECOMMUNITY_SIZE)); map_vni_to_rt(bgp, vpn, eval); } } } /* * Unmap the RTs (configured or automatically derived) of a VNI from the VNI. */ void bgp_evpn_unmap_vni_from_its_rts(struct bgp *bgp, struct bgpevpn *vpn) { int i; struct ecommunity_val *eval; struct listnode *node, *nnode; struct ecommunity *ecom; for (ALL_LIST_ELEMENTS(vpn->import_rtl, node, nnode, ecom)) { for (i = 0; i < ecom->size; i++) { struct irt_node *irt; struct ecommunity_val eval_tmp; eval = (struct ecommunity_val *)(ecom->val + (i * ECOMMUNITY_SIZE)); /* If using "automatic" RT, we only care about the * local-admin sub-field. * This is to facilitate using VNI as the RT for EBGP * peering too. */ memcpy(&eval_tmp, eval, ECOMMUNITY_SIZE); if (!is_import_rt_configured(vpn)) mask_ecom_global_admin(&eval_tmp, eval); irt = lookup_import_rt(bgp, &eval_tmp); if (irt) unmap_vni_from_rt(bgp, vpn, irt); } } } /* * Derive Import RT automatically for VNI and map VNI to RT. * The mapping will be used during route processing. */ void bgp_evpn_derive_auto_rt_import(struct bgp *bgp, struct bgpevpn *vpn) { form_auto_rt(bgp, vpn->vni, vpn->import_rtl); UNSET_FLAG(vpn->flags, VNI_FLAG_IMPRT_CFGD); /* Map RT to VNI */ bgp_evpn_map_vni_to_its_rts(bgp, vpn); } /* * Derive Export RT automatically for VNI. */ void bgp_evpn_derive_auto_rt_export(struct bgp *bgp, struct bgpevpn *vpn) { form_auto_rt(bgp, vpn->vni, vpn->export_rtl); UNSET_FLAG(vpn->flags, VNI_FLAG_EXPRT_CFGD); } /* * Derive RD automatically for VNI using passed information - it * is of the form RouterId:unique-id-for-vni. */ void bgp_evpn_derive_auto_rd_for_vrf(struct bgp *bgp) { if (is_vrf_rd_configured(bgp)) return; form_auto_rd(bgp->router_id, bgp->vrf_rd_id, &bgp->vrf_prd); } /* * Derive RD automatically for VNI using passed information - it * is of the form RouterId:unique-id-for-vni. */ void bgp_evpn_derive_auto_rd(struct bgp *bgp, struct bgpevpn *vpn) { char buf[100]; vpn->prd.family = AF_UNSPEC; vpn->prd.prefixlen = 64; sprintf(buf, "%s:%hu", inet_ntoa(bgp->router_id), vpn->rd_id); (void)str2prefix_rd(buf, &vpn->prd); UNSET_FLAG(vpn->flags, VNI_FLAG_RD_CFGD); } /* * Lookup L3-VNI */ bool bgp_evpn_lookup_l3vni_l2vni_table(vni_t vni) { struct list *inst = bm->bgp; struct listnode *node; struct bgp *bgp_vrf; for (ALL_LIST_ELEMENTS_RO(inst, node, bgp_vrf)) { if (bgp_vrf->l3vni == vni) return true; } return false; } /* * Lookup VNI. */ struct bgpevpn *bgp_evpn_lookup_vni(struct bgp *bgp, vni_t vni) { struct bgpevpn *vpn; struct bgpevpn tmp; memset(&tmp, 0, sizeof(struct bgpevpn)); tmp.vni = vni; vpn = hash_lookup(bgp->vnihash, &tmp); return vpn; } /* * Create a new vpn - invoked upon configuration or zebra notification. */ struct bgpevpn *bgp_evpn_new(struct bgp *bgp, vni_t vni, struct in_addr originator_ip, vrf_id_t tenant_vrf_id, struct in_addr mcast_grp) { struct bgpevpn *vpn; if (!bgp) return NULL; vpn = XCALLOC(MTYPE_BGP_EVPN, sizeof(struct bgpevpn)); /* Set values - RD and RT set to defaults. */ vpn->vni = vni; vpn->originator_ip = originator_ip; vpn->tenant_vrf_id = tenant_vrf_id; vpn->mcast_grp = mcast_grp; /* Initialize route-target import and export lists */ vpn->import_rtl = list_new(); vpn->import_rtl->cmp = (int (*)(void *, void *))evpn_route_target_cmp; vpn->import_rtl->del = evpn_xxport_delete_ecomm; vpn->export_rtl = list_new(); vpn->export_rtl->cmp = (int (*)(void *, void *))evpn_route_target_cmp; vpn->export_rtl->del = evpn_xxport_delete_ecomm; bf_assign_index(bm->rd_idspace, vpn->rd_id); derive_rd_rt_for_vni(bgp, vpn); /* Initialize EVPN route table. */ vpn->route_table = bgp_table_init(bgp, AFI_L2VPN, SAFI_EVPN); /* Add to hash */ if (!hash_get(bgp->vnihash, vpn, hash_alloc_intern)) { XFREE(MTYPE_BGP_EVPN, vpn); return NULL; } /* add to l2vni list on corresponding vrf */ bgpevpn_link_to_l3vni(vpn); QOBJ_REG(vpn, bgpevpn); return vpn; } /* * Free a given VPN - called in multiple scenarios such as zebra * notification, configuration being deleted, advertise-all-vni disabled etc. * This just frees appropriate memory, caller should have taken other * needed actions. */ void bgp_evpn_free(struct bgp *bgp, struct bgpevpn *vpn) { bgpevpn_unlink_from_l3vni(vpn); bgp_table_unlock(vpn->route_table); bgp_evpn_unmap_vni_from_its_rts(bgp, vpn); list_delete(&vpn->import_rtl); list_delete(&vpn->export_rtl); bf_release_index(bm->rd_idspace, vpn->rd_id); hash_release(bgp->vnihash, vpn); QOBJ_UNREG(vpn); XFREE(MTYPE_BGP_EVPN, vpn); } /* * Lookup local ES. */ struct evpnes *bgp_evpn_lookup_es(struct bgp *bgp, esi_t *esi) { struct evpnes *es; struct evpnes tmp; memset(&tmp, 0, sizeof(struct evpnes)); memcpy(&tmp.esi, esi, sizeof(esi_t)); es = hash_lookup(bgp->esihash, &tmp); return es; } /* * Create a new local es - invoked upon zebra notification. */ struct evpnes *bgp_evpn_es_new(struct bgp *bgp, esi_t *esi, struct ipaddr *originator_ip) { char buf[100]; struct evpnes *es; if (!bgp) return NULL; es = XCALLOC(MTYPE_BGP_EVPN_ES, sizeof(struct evpnes)); /* set the ESI and originator_ip */ memcpy(&es->esi, esi, sizeof(esi_t)); memcpy(&es->originator_ip, originator_ip, sizeof(struct ipaddr)); /* Initialise the VTEP list */ es->vtep_list = list_new(); es->vtep_list->cmp = evpn_vtep_ip_cmp; /* auto derive RD for this es */ bf_assign_index(bm->rd_idspace, es->rd_id); es->prd.family = AF_UNSPEC; es->prd.prefixlen = 64; sprintf(buf, "%s:%hu", inet_ntoa(bgp->router_id), es->rd_id); (void)str2prefix_rd(buf, &es->prd); /* Initialize the ES route table */ es->route_table = bgp_table_init(bgp, AFI_L2VPN, SAFI_EVPN); /* Add to hash */ if (!hash_get(bgp->esihash, es, hash_alloc_intern)) { XFREE(MTYPE_BGP_EVPN_ES, es); return NULL; } QOBJ_REG(es, evpnes); return es; } /* * Free a given ES - * This just frees appropriate memory, caller should have taken other * needed actions. */ void bgp_evpn_es_free(struct bgp *bgp, struct evpnes *es) { list_delete(&es->vtep_list); bgp_table_unlock(es->route_table); bf_release_index(bm->rd_idspace, es->rd_id); hash_release(bgp->esihash, es); QOBJ_UNREG(es); XFREE(MTYPE_BGP_EVPN_ES, es); } /* * Import evpn route from global table to VNI/VRF/ESI. */ int bgp_evpn_import_route(struct bgp *bgp, afi_t afi, safi_t safi, struct prefix *p, struct bgp_path_info *pi) { return install_uninstall_evpn_route(bgp, afi, safi, p, pi, 1); } /* * Unimport evpn route from VNI/VRF/ESI. */ int bgp_evpn_unimport_route(struct bgp *bgp, afi_t afi, safi_t safi, struct prefix *p, struct bgp_path_info *pi) { return install_uninstall_evpn_route(bgp, afi, safi, p, pi, 0); } /* filter routes which have martian next hops */ int bgp_filter_evpn_routes_upon_martian_nh_change(struct bgp *bgp) { afi_t afi; safi_t safi; struct bgp_node *rd_rn, *rn; struct bgp_table *table; struct bgp_path_info *pi; afi = AFI_L2VPN; safi = SAFI_EVPN; /* Walk entire global routing table and evaluate routes which could be * imported into this VPN. Note that we cannot just look at the routes * for the VNI's RD - * remote routes applicable for this VNI could have any RD. */ /* EVPN routes are a 2-level table. */ for (rd_rn = bgp_table_top(bgp->rib[afi][safi]); rd_rn; rd_rn = bgp_route_next(rd_rn)) { table = bgp_node_get_bgp_table_info(rd_rn); if (!table) continue; for (rn = bgp_table_top(table); rn; rn = bgp_route_next(rn)) { for (pi = bgp_node_get_bgp_path_info(rn); pi; pi = pi->next) { /* Consider "valid" remote routes applicable for * this VNI. */ if (!(pi->type == ZEBRA_ROUTE_BGP && pi->sub_type == BGP_ROUTE_NORMAL)) continue; if (bgp_nexthop_self(bgp, pi->attr->nexthop)) { char attr_str[BUFSIZ] = {0}; char pbuf[PREFIX_STRLEN]; bgp_dump_attr(pi->attr, attr_str, BUFSIZ); if (bgp_debug_update(pi->peer, &rn->p, NULL, 1)) zlog_debug( "%u: prefix %s with attr %s - DENIED due to martian or self nexthop", bgp->vrf_id, prefix2str( &rn->p, pbuf, sizeof(pbuf)), attr_str); bgp_evpn_unimport_route(bgp, afi, safi, &rn->p, pi); bgp_rib_remove(rn, pi, pi->peer, afi, safi); } } } } return 0; } /* * Handle del of a local MACIP. */ int bgp_evpn_local_macip_del(struct bgp *bgp, vni_t vni, struct ethaddr *mac, struct ipaddr *ip, int state) { struct bgpevpn *vpn; struct prefix_evpn p; struct bgp_node *rn; /* Lookup VNI hash - should exist. */ vpn = bgp_evpn_lookup_vni(bgp, vni); if (!vpn || !is_vni_live(vpn)) { flog_warn(EC_BGP_EVPN_VPN_VNI, "%u: VNI hash entry for VNI %u %s at MACIP DEL", bgp->vrf_id, vni, vpn ? "not live" : "not found"); return -1; } build_evpn_type2_prefix(&p, mac, ip); if (state == ZEBRA_NEIGH_ACTIVE) { /* Remove EVPN type-2 route and schedule for processing. */ delete_evpn_route(bgp, vpn, &p); } else { /* Re-instate the current remote best path if any */ rn = bgp_node_lookup(vpn->route_table, (struct prefix *)&p); if (rn) evpn_zebra_reinstall_best_route(bgp, vpn, rn); } return 0; } /* * Handle add of a local MACIP. */ int bgp_evpn_local_macip_add(struct bgp *bgp, vni_t vni, struct ethaddr *mac, struct ipaddr *ip, uint8_t flags, uint32_t seq) { struct bgpevpn *vpn; struct prefix_evpn p; /* Lookup VNI hash - should exist. */ vpn = bgp_evpn_lookup_vni(bgp, vni); if (!vpn || !is_vni_live(vpn)) { flog_warn(EC_BGP_EVPN_VPN_VNI, "%u: VNI hash entry for VNI %u %s at MACIP ADD", bgp->vrf_id, vni, vpn ? "not live" : "not found"); return -1; } /* Create EVPN type-2 route and schedule for processing. */ build_evpn_type2_prefix(&p, mac, ip); if (update_evpn_route(bgp, vpn, &p, flags, seq)) { char buf[ETHER_ADDR_STRLEN]; char buf2[INET6_ADDRSTRLEN]; flog_err( EC_BGP_EVPN_ROUTE_CREATE, "%u:Failed to create Type-2 route, VNI %u %s MAC %s IP %s (flags: 0x%x)", bgp->vrf_id, vpn->vni, CHECK_FLAG(flags, ZEBRA_MACIP_TYPE_STICKY) ? "sticky gateway" : "", prefix_mac2str(mac, buf, sizeof(buf)), ipaddr2str(ip, buf2, sizeof(buf2)), flags); return -1; } return 0; } static void link_l2vni_hash_to_l3vni(struct hash_bucket *bucket, struct bgp *bgp_vrf) { struct bgpevpn *vpn = (struct bgpevpn *)bucket->data; struct bgp *bgp_evpn = NULL; bgp_evpn = bgp_get_evpn(); assert(bgp_evpn); if (vpn->tenant_vrf_id == bgp_vrf->vrf_id) bgpevpn_link_to_l3vni(vpn); } int bgp_evpn_local_l3vni_add(vni_t l3vni, vrf_id_t vrf_id, struct ethaddr *rmac, struct in_addr originator_ip, int filter, ifindex_t svi_ifindex) { struct bgp *bgp_vrf = NULL; /* bgp VRF instance */ struct bgp *bgp_evpn = NULL; /* EVPN bgp instance */ struct listnode *node = NULL; struct bgpevpn *vpn = NULL; as_t as = 0; /* get the EVPN instance - required to get the AS number for VRF * auto-creatio */ bgp_evpn = bgp_get_evpn(); if (!bgp_evpn) { flog_err( EC_BGP_NO_DFLT, "Cannot process L3VNI %u ADD - EVPN BGP instance not yet created", l3vni); return -1; } as = bgp_evpn->as; /* if the BGP vrf instance doesn't exist - create one */ bgp_vrf = bgp_lookup_by_vrf_id(vrf_id); if (!bgp_vrf) { int ret = 0; ret = bgp_get(&bgp_vrf, &as, vrf_id_to_name(vrf_id), vrf_id == VRF_DEFAULT ? BGP_INSTANCE_TYPE_DEFAULT : BGP_INSTANCE_TYPE_VRF); switch (ret) { case BGP_ERR_AS_MISMATCH: flog_err(EC_BGP_EVPN_AS_MISMATCH, "BGP is already running; AS is %u\n", as); return -1; case BGP_ERR_INSTANCE_MISMATCH: flog_err(EC_BGP_EVPN_INSTANCE_MISMATCH, "BGP instance name and AS number mismatch\n"); return -1; } /* mark as auto created */ SET_FLAG(bgp_vrf->vrf_flags, BGP_VRF_AUTO); } /* associate the vrf with l3vni and related parameters */ bgp_vrf->l3vni = l3vni; memcpy(&bgp_vrf->rmac, rmac, sizeof(struct ethaddr)); bgp_vrf->originator_ip = originator_ip; bgp_vrf->l3vni_svi_ifindex = svi_ifindex; /* set the right filter - are we using l3vni only for prefix routes? */ if (filter) SET_FLAG(bgp_vrf->vrf_flags, BGP_VRF_L3VNI_PREFIX_ROUTES_ONLY); /* Map auto derive or configured RTs */ if (!CHECK_FLAG(bgp_vrf->vrf_flags, BGP_VRF_IMPORT_RT_CFGD)) evpn_auto_rt_import_add_for_vrf(bgp_vrf); else bgp_evpn_map_vrf_to_its_rts(bgp_vrf); if (!CHECK_FLAG(bgp_vrf->vrf_flags, BGP_VRF_EXPORT_RT_CFGD)) evpn_auto_rt_export_add_for_vrf(bgp_vrf); /* auto derive RD */ bgp_evpn_derive_auto_rd_for_vrf(bgp_vrf); /* link all corresponding l2vnis */ hash_iterate(bgp_evpn->vnihash, (void (*)(struct hash_bucket *, void *))link_l2vni_hash_to_l3vni, bgp_vrf); /* Only update all corresponding type-2 routes if we are advertising two * labels along with type-2 routes */ if (!filter) for (ALL_LIST_ELEMENTS_RO(bgp_vrf->l2vnis, node, vpn)) update_routes_for_vni(bgp_evpn, vpn); /* advertise type-5 routes if needed */ update_advertise_vrf_routes(bgp_vrf); /* install all remote routes belonging to this l3vni into correspondng * vrf */ install_routes_for_vrf(bgp_vrf); return 0; } int bgp_evpn_local_l3vni_del(vni_t l3vni, vrf_id_t vrf_id) { struct bgp *bgp_vrf = NULL; /* bgp vrf instance */ struct bgp *bgp_evpn = NULL; /* EVPN bgp instance */ struct listnode *node = NULL; struct listnode *next = NULL; struct bgpevpn *vpn = NULL; bgp_vrf = bgp_lookup_by_vrf_id(vrf_id); if (!bgp_vrf) { flog_err( EC_BGP_NO_DFLT, "Cannot process L3VNI %u Del - Could not find BGP instance", l3vni); return -1; } bgp_evpn = bgp_get_evpn(); if (!bgp_evpn) { flog_err( EC_BGP_NO_DFLT, "Cannot process L3VNI %u Del - Could not find EVPN BGP instance", l3vni); return -1; } /* Remove remote routes from BGT VRF even if BGP_VRF_AUTO is configured, * bgp_delete would not remove/decrement bgp_path_info of the ip_prefix * routes. This will uninstalling the routes from zebra and decremnt the * bgp info count. */ uninstall_routes_for_vrf(bgp_vrf); /* delete/withdraw all type-5 routes */ delete_withdraw_vrf_routes(bgp_vrf); /* remove the l3vni from vrf instance */ bgp_vrf->l3vni = 0; /* remove the Rmac from the BGP vrf */ memset(&bgp_vrf->rmac, 0, sizeof(struct ethaddr)); /* remove default import RT or Unmap non-default import RT */ if (!list_isempty(bgp_vrf->vrf_import_rtl)) { bgp_evpn_unmap_vrf_from_its_rts(bgp_vrf); if (!CHECK_FLAG(bgp_vrf->vrf_flags, BGP_VRF_IMPORT_RT_CFGD)) list_delete_all_node(bgp_vrf->vrf_import_rtl); } /* remove default export RT */ if (!list_isempty(bgp_vrf->vrf_export_rtl) && !CHECK_FLAG(bgp_vrf->vrf_flags, BGP_VRF_EXPORT_RT_CFGD)) { list_delete_all_node(bgp_vrf->vrf_export_rtl); } /* update all corresponding local mac-ip routes */ if (!CHECK_FLAG(bgp_vrf->vrf_flags, BGP_VRF_L3VNI_PREFIX_ROUTES_ONLY)) { for (ALL_LIST_ELEMENTS_RO(bgp_vrf->l2vnis, node, vpn)) { UNSET_FLAG(vpn->flags, VNI_FLAG_USE_TWO_LABELS); update_routes_for_vni(bgp_evpn, vpn); } } /* If any L2VNIs point to this instance, unlink them. */ for (ALL_LIST_ELEMENTS(bgp_vrf->l2vnis, node, next, vpn)) bgpevpn_unlink_from_l3vni(vpn); /* Delete the instance if it was autocreated */ if (CHECK_FLAG(bgp_vrf->vrf_flags, BGP_VRF_AUTO)) bgp_delete(bgp_vrf); return 0; } /* * Handle del of a local VNI. */ int bgp_evpn_local_vni_del(struct bgp *bgp, vni_t vni) { struct bgpevpn *vpn; /* Locate VNI hash */ vpn = bgp_evpn_lookup_vni(bgp, vni); if (!vpn) { if (bgp_debug_zebra(NULL)) flog_warn( EC_BGP_EVPN_VPN_VNI, "%u: VNI hash entry for VNI %u not found at DEL", bgp->vrf_id, vni); return 0; } /* Remove all local EVPN routes and schedule for processing (to * withdraw from peers). */ delete_routes_for_vni(bgp, vpn); /* * tunnel is no longer active, del tunnel ip address from tip_hash */ bgp_tip_del(bgp, &vpn->originator_ip); /* Clear "live" flag and see if hash needs to be freed. */ UNSET_FLAG(vpn->flags, VNI_FLAG_LIVE); if (!is_vni_configured(vpn)) bgp_evpn_free(bgp, vpn); return 0; } /* * Handle add (or update) of a local VNI. The VNI changes we care * about are for the local-tunnel-ip and the (tenant) VRF. */ int bgp_evpn_local_vni_add(struct bgp *bgp, vni_t vni, struct in_addr originator_ip, vrf_id_t tenant_vrf_id, struct in_addr mcast_grp) { struct bgpevpn *vpn; struct prefix_evpn p; /* Lookup VNI. If present and no change, exit. */ vpn = bgp_evpn_lookup_vni(bgp, vni); if (vpn) { if (is_vni_live(vpn) && IPV4_ADDR_SAME(&vpn->originator_ip, &originator_ip) && IPV4_ADDR_SAME(&vpn->mcast_grp, &mcast_grp) && vpn->tenant_vrf_id == tenant_vrf_id) /* Probably some other param has changed that we don't * care about. */ return 0; bgp_evpn_mcast_grp_change(bgp, vpn, mcast_grp); /* Update tenant_vrf_id if it has changed. */ if (vpn->tenant_vrf_id != tenant_vrf_id) { bgpevpn_unlink_from_l3vni(vpn); vpn->tenant_vrf_id = tenant_vrf_id; bgpevpn_link_to_l3vni(vpn); } /* If tunnel endpoint IP has changed, update (and delete prior * type-3 route, if needed.) */ if (!IPV4_ADDR_SAME(&vpn->originator_ip, &originator_ip)) handle_tunnel_ip_change(bgp, vpn, originator_ip); /* Update all routes with new endpoint IP and/or export RT * for VRFs */ if (is_vni_live(vpn)) update_routes_for_vni(bgp, vpn); } /* Create or update as appropriate. */ if (!vpn) { vpn = bgp_evpn_new(bgp, vni, originator_ip, tenant_vrf_id, mcast_grp); if (!vpn) { flog_err( EC_BGP_VNI, "%u: Failed to allocate VNI entry for VNI %u - at Add", bgp->vrf_id, vni); return -1; } } /* if the VNI is live already, there is nothing more to do */ if (is_vni_live(vpn)) return 0; /* Mark as "live" */ SET_FLAG(vpn->flags, VNI_FLAG_LIVE); /* tunnel is now active, add tunnel-ip to db */ bgp_tip_add(bgp, &originator_ip); /* filter routes as nexthop database has changed */ bgp_filter_evpn_routes_upon_martian_nh_change(bgp); /* * Create EVPN type-3 route and schedule for processing. * * RT-3 only if doing head-end replication */ if (bgp_evpn_vni_flood_mode_get(bgp, vpn) == VXLAN_FLOOD_HEAD_END_REPL) { build_evpn_type3_prefix(&p, vpn->originator_ip); if (update_evpn_route(bgp, vpn, &p, 0, 0)) { flog_err(EC_BGP_EVPN_ROUTE_CREATE, "%u: Type3 route creation failure for VNI %u", bgp->vrf_id, vni); return -1; } } /* If we have learnt and retained remote routes (VTEPs, MACs) for this * VNI, * install them. */ install_routes_for_vni(bgp, vpn); /* If we are advertising gateway mac-ip It needs to be conveyed again to zebra */ bgp_zebra_advertise_gw_macip(bgp, vpn->advertise_gw_macip, vpn->vni); return 0; } /* * bgp_evpn_local_es_del */ int bgp_evpn_local_es_del(struct bgp *bgp, esi_t *esi, struct ipaddr *originator_ip) { char buf[ESI_STR_LEN]; struct evpnes *es = NULL; if (!bgp->esihash) { flog_err(EC_BGP_ES_CREATE, "%u: ESI hash not yet created", bgp->vrf_id); return -1; } /* Lookup ESI hash - should exist. */ es = bgp_evpn_lookup_es(bgp, esi); if (!es) { flog_warn(EC_BGP_EVPN_ESI, "%u: ESI hash entry for ESI %s at Local ES DEL", bgp->vrf_id, esi_to_str(esi, buf, sizeof(buf))); return -1; } /* Delete all local EVPN ES routes from ESI table * and schedule for processing (to withdraw from peers)) */ delete_routes_for_es(bgp, es); /* free the hash entry */ bgp_evpn_es_free(bgp, es); return 0; } /* * bgp_evpn_local_es_add */ int bgp_evpn_local_es_add(struct bgp *bgp, esi_t *esi, struct ipaddr *originator_ip) { char buf[ESI_STR_LEN]; struct evpnes *es = NULL; struct prefix_evpn p; if (!bgp->esihash) { flog_err(EC_BGP_ES_CREATE, "%u: ESI hash not yet created", bgp->vrf_id); return -1; } /* create the new es */ es = bgp_evpn_lookup_es(bgp, esi); if (!es) { es = bgp_evpn_es_new(bgp, esi, originator_ip); if (!es) { flog_err( EC_BGP_ES_CREATE, "%u: Failed to allocate ES entry for ESI %s - at Local ES Add", bgp->vrf_id, esi_to_str(esi, buf, sizeof(buf))); return -1; } } UNSET_FLAG(es->flags, EVPNES_REMOTE); SET_FLAG(es->flags, EVPNES_LOCAL); build_evpn_type4_prefix(&p, esi, originator_ip->ipaddr_v4); if (update_evpn_type4_route(bgp, es, &p)) { flog_err(EC_BGP_EVPN_ROUTE_CREATE, "%u: Type4 route creation failure for ESI %s", bgp->vrf_id, esi_to_str(esi, buf, sizeof(buf))); return -1; } /* import all remote ES routes in th ES table */ install_routes_for_es(bgp, es); return 0; } /* * Handle change in setting for BUM handling. The supported values * are head-end replication and dropping all BUM packets. Any change * should be registered with zebra. Also, if doing head-end replication, * need to advertise local VNIs as EVPN RT-3 wheras, if BUM packets are * to be dropped, the RT-3s must be withdrawn. */ void bgp_evpn_flood_control_change(struct bgp *bgp) { zlog_info("L2VPN EVPN BUM handling is %s", bgp->vxlan_flood_ctrl == VXLAN_FLOOD_HEAD_END_REPL ? "Flooding" : "Flooding Disabled"); bgp_zebra_vxlan_flood_control(bgp, bgp->vxlan_flood_ctrl); if (bgp->vxlan_flood_ctrl == VXLAN_FLOOD_HEAD_END_REPL) hash_iterate(bgp->vnihash, create_advertise_type3, bgp); else if (bgp->vxlan_flood_ctrl == VXLAN_FLOOD_DISABLED) hash_iterate(bgp->vnihash, delete_withdraw_type3, bgp); } /* * Cleanup EVPN information on disable - Need to delete and withdraw * EVPN routes from peers. */ void bgp_evpn_cleanup_on_disable(struct bgp *bgp) { hash_iterate(bgp->vnihash, (void (*)(struct hash_bucket *, void *))cleanup_vni_on_disable, bgp); } /* * Cleanup EVPN information - invoked at the time of bgpd exit or when the * BGP instance (default) is being freed. */ void bgp_evpn_cleanup(struct bgp *bgp) { hash_iterate(bgp->vnihash, (void (*)(struct hash_bucket *, void *))free_vni_entry, bgp); hash_free(bgp->import_rt_hash); bgp->import_rt_hash = NULL; hash_free(bgp->vrf_import_rt_hash); bgp->vrf_import_rt_hash = NULL; hash_free(bgp->vnihash); bgp->vnihash = NULL; if (bgp->esihash) hash_free(bgp->esihash); bgp->esihash = NULL; list_delete(&bgp->vrf_import_rtl); list_delete(&bgp->vrf_export_rtl); list_delete(&bgp->l2vnis); } /* * Initialization for EVPN * Create * VNI hash table * hash for RT to VNI */ void bgp_evpn_init(struct bgp *bgp) { bgp->vnihash = hash_create(vni_hash_key_make, vni_hash_cmp, "BGP VNI Hash"); bgp->esihash = hash_create(esi_hash_keymake, esi_cmp, "BGP EVPN Local ESI Hash"); bgp->import_rt_hash = hash_create(import_rt_hash_key_make, import_rt_hash_cmp, "BGP Import RT Hash"); bgp->vrf_import_rt_hash = hash_create(vrf_import_rt_hash_key_make, vrf_import_rt_hash_cmp, "BGP VRF Import RT Hash"); bgp->vrf_import_rtl = list_new(); bgp->vrf_import_rtl->cmp = (int (*)(void *, void *))evpn_route_target_cmp; bgp->vrf_import_rtl->del = evpn_xxport_delete_ecomm; bgp->vrf_export_rtl = list_new(); bgp->vrf_export_rtl->cmp = (int (*)(void *, void *))evpn_route_target_cmp; bgp->vrf_export_rtl->del = evpn_xxport_delete_ecomm; bgp->l2vnis = list_new(); bgp->l2vnis->cmp = vni_list_cmp; /* By default Duplicate Address Dection is enabled. * Max-moves (N) 5, detection time (M) 180 * default action is warning-only * freeze action permanently freezes address, * and freeze time (auto-recovery) is disabled. */ if (bgp->evpn_info) { bgp->evpn_info->dup_addr_detect = true; bgp->evpn_info->dad_time = EVPN_DAD_DEFAULT_TIME; bgp->evpn_info->dad_max_moves = EVPN_DAD_DEFAULT_MAX_MOVES; bgp->evpn_info->dad_freeze = false; bgp->evpn_info->dad_freeze_time = 0; /* Initialize zebra vxlan */ bgp_zebra_dup_addr_detection(bgp); } /* Default BUM handling is to do head-end replication. */ bgp->vxlan_flood_ctrl = VXLAN_FLOOD_HEAD_END_REPL; } void bgp_evpn_vrf_delete(struct bgp *bgp_vrf) { bgp_evpn_unmap_vrf_from_its_rts(bgp_vrf); } frr-7.2.1/bgpd/bgp_evpn.h0000644000000000000000000001415213610377563012104 00000000000000/* E-VPN header for packet handling * Copyright (C) 2016 6WIND * * This file is part of FRRouting. * * FRRouting is free software; you can redistribute it and/or modify it * under the terms of the GNU General Public License as published by the * Free Software Foundation; either version 2, or (at your option) any * later version. * * FRRouting 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; see the file COPYING; if not, write to the Free Software * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA */ #ifndef _QUAGGA_BGP_EVPN_H #define _QUAGGA_BGP_EVPN_H #include "vxlan.h" #include "bgpd.h" #define EVPN_ROUTE_STRLEN 200 /* Must be >> MAC + IPv6 strings. */ #define EVPN_AUTORT_VXLAN 0x10000000 #define EVPN_ENABLED(bgp) (bgp)->advertise_all_vni static inline int is_evpn_enabled(void) { struct bgp *bgp = NULL; bgp = bgp_get_evpn(); return bgp ? EVPN_ENABLED(bgp) : 0; } static inline void vni2label(vni_t vni, mpls_label_t *label) { uint8_t *tag = (uint8_t *)label; tag[0] = (vni >> 16) & 0xFF; tag[1] = (vni >> 8) & 0xFF; tag[2] = vni & 0xFF; } static inline vni_t label2vni(mpls_label_t *label) { uint8_t *tag = (uint8_t *)label; vni_t vni; vni = ((uint32_t)*tag++ << 16); vni |= (uint32_t)*tag++ << 8; vni |= (uint32_t)(*tag & 0xFF); return vni; } static inline int advertise_type5_routes(struct bgp *bgp_vrf, afi_t afi) { if (!bgp_vrf->l3vni) return 0; if (afi == AFI_IP && CHECK_FLAG(bgp_vrf->af_flags[AFI_L2VPN][SAFI_EVPN], BGP_L2VPN_EVPN_ADVERTISE_IPV4_UNICAST)) return 1; if (afi == AFI_IP6 && CHECK_FLAG(bgp_vrf->af_flags[AFI_L2VPN][SAFI_EVPN], BGP_L2VPN_EVPN_ADVERTISE_IPV6_UNICAST)) return 1; return 0; } /* Flag if the route's parent is a EVPN route. */ static inline int is_route_parent_evpn(struct bgp_path_info *ri) { struct bgp_path_info *parent_ri; struct bgp_table *table; struct bgp_node *rn; /* If not imported (or doesn't have a parent), bail. */ if (ri->sub_type != BGP_ROUTE_IMPORTED || !ri->extra || !ri->extra->parent) return 0; /* Determine parent recursively */ for (parent_ri = ri->extra->parent; parent_ri->extra && parent_ri->extra->parent; parent_ri = parent_ri->extra->parent) ; /* See if of family L2VPN/EVPN */ rn = parent_ri->net; if (!rn) return 0; table = bgp_node_table(rn); if (table && table->afi == AFI_L2VPN && table->safi == SAFI_EVPN) return 1; return 0; } /* Flag if the route path's family is EVPN. */ static inline bool is_pi_family_evpn(struct bgp_path_info *pi) { return is_pi_family_matching(pi, AFI_L2VPN, SAFI_EVPN); } /* Flag if the route is injectable into EVPN. This would be either a * non-imported route or a non-EVPN imported route. */ static inline bool is_route_injectable_into_evpn(struct bgp_path_info *pi) { struct bgp_path_info *parent_pi; struct bgp_table *table; struct bgp_node *rn; if (pi->sub_type != BGP_ROUTE_IMPORTED || !pi->extra || !pi->extra->parent) return true; parent_pi = (struct bgp_path_info *)pi->extra->parent; rn = parent_pi->net; if (!rn) return true; table = bgp_node_table(rn); if (table && table->afi == AFI_L2VPN && table->safi == SAFI_EVPN) return false; return true; } extern void bgp_evpn_advertise_type5_route(struct bgp *bgp_vrf, struct prefix *p, struct attr *src_attr, afi_t afi, safi_t safi); extern void bgp_evpn_withdraw_type5_route(struct bgp *bgp_vrf, struct prefix *p, afi_t afi, safi_t safi); extern void bgp_evpn_withdraw_type5_routes(struct bgp *bgp_vrf, afi_t afi, safi_t safi); extern void bgp_evpn_advertise_type5_routes(struct bgp *bgp_vrf, afi_t afi, safi_t safi); extern void bgp_evpn_vrf_delete(struct bgp *bgp_vrf); extern void bgp_evpn_handle_router_id_update(struct bgp *bgp, int withdraw); extern char *bgp_evpn_label2str(mpls_label_t *label, uint32_t num_labels, char *buf, int len); extern char *bgp_evpn_route2str(struct prefix_evpn *p, char *buf, int len); extern void bgp_evpn_route2json(struct prefix_evpn *p, json_object *json); extern void bgp_evpn_encode_prefix(struct stream *s, struct prefix *p, struct prefix_rd *prd, mpls_label_t *label, uint32_t num_labels, struct attr *attr, int addpath_encode, uint32_t addpath_tx_id); extern int bgp_nlri_parse_evpn(struct peer *peer, struct attr *attr, struct bgp_nlri *packet, int withdraw); extern int bgp_evpn_import_route(struct bgp *bgp, afi_t afi, safi_t safi, struct prefix *p, struct bgp_path_info *ri); extern int bgp_evpn_unimport_route(struct bgp *bgp, afi_t afi, safi_t safi, struct prefix *p, struct bgp_path_info *ri); extern int bgp_filter_evpn_routes_upon_martian_nh_change(struct bgp *bgp); extern int bgp_evpn_local_macip_del(struct bgp *bgp, vni_t vni, struct ethaddr *mac, struct ipaddr *ip, int state); extern int bgp_evpn_local_macip_add(struct bgp *bgp, vni_t vni, struct ethaddr *mac, struct ipaddr *ip, uint8_t flags, uint32_t seq); extern int bgp_evpn_local_l3vni_add(vni_t vni, vrf_id_t vrf_id, struct ethaddr *rmac, struct in_addr originator_ip, int filter, ifindex_t svi_ifindex); extern int bgp_evpn_local_l3vni_del(vni_t vni, vrf_id_t vrf_id); extern int bgp_evpn_local_vni_del(struct bgp *bgp, vni_t vni); extern int bgp_evpn_local_vni_add(struct bgp *bgp, vni_t vni, struct in_addr originator_ip, vrf_id_t tenant_vrf_id, struct in_addr mcast_grp); extern int bgp_evpn_local_es_add(struct bgp *bgp, esi_t *esi, struct ipaddr *originator_ip); extern int bgp_evpn_local_es_del(struct bgp *bgp, esi_t *esi, struct ipaddr *originator_ip); extern void bgp_evpn_flood_control_change(struct bgp *bgp); extern void bgp_evpn_cleanup_on_disable(struct bgp *bgp); extern void bgp_evpn_cleanup(struct bgp *bgp); extern void bgp_evpn_init(struct bgp *bgp); #endif /* _QUAGGA_BGP_EVPN_H */ frr-7.2.1/bgpd/bgp_evpn_private.h0000644000000000000000000003753213610377563013645 00000000000000/* BGP EVPN internal definitions * Copyright (C) 2017 Cumulus Networks, Inc. * * This file is part of FRR. * * FRR is free software; you can redistribute it and/or modify it * under the terms of the GNU General Public License as published by the * Free Software Foundation; either version 2, or (at your option) any * later version. * * FRR 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 FRR; see the file COPYING. If not, write to the Free * Software Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA * 02111-1307, USA. */ #ifndef _BGP_EVPN_PRIVATE_H #define _BGP_EVPN_PRIVATE_H #include "vxlan.h" #include "zebra.h" #include "bgpd/bgpd.h" #include "bgpd/bgp_ecommunity.h" #define RT_ADDRSTRLEN 28 /* EVPN prefix lengths. This represents the sizeof struct evpn_addr * in bits */ #define EVPN_ROUTE_PREFIXLEN (sizeof(struct evpn_addr) * 8) /* EVPN route types. */ typedef enum { BGP_EVPN_AD_ROUTE = 1, /* Ethernet Auto-Discovery (A-D) route */ BGP_EVPN_MAC_IP_ROUTE, /* MAC/IP Advertisement route */ BGP_EVPN_IMET_ROUTE, /* Inclusive Multicast Ethernet Tag route */ BGP_EVPN_ES_ROUTE, /* Ethernet Segment route */ BGP_EVPN_IP_PREFIX_ROUTE, /* IP Prefix route */ } bgp_evpn_route_type; /* * Hash table of EVIs. Right now, the only type of EVI supported is with * VxLAN encapsulation, hence each EVI corresponds to a L2 VNI. * The VNIs are not "created" through BGP but through some other interface * on the system. This table stores VNIs that BGP comes to know as present * on the system (through interaction with zebra) as well as pre-configured * VNIs (which need to be defined in the system to become "live"). */ struct bgpevpn { vni_t vni; vrf_id_t tenant_vrf_id; uint32_t flags; #define VNI_FLAG_CFGD 0x1 /* VNI is user configured */ #define VNI_FLAG_LIVE 0x2 /* VNI is "live" */ #define VNI_FLAG_RD_CFGD 0x4 /* RD is user configured. */ #define VNI_FLAG_IMPRT_CFGD 0x8 /* Import RT is user configured */ #define VNI_FLAG_EXPRT_CFGD 0x10 /* Export RT is user configured */ #define VNI_FLAG_USE_TWO_LABELS 0x20 /* Attach both L2-VNI and L3-VNI if needed for this VPN */ struct bgp *bgp_vrf; /* back pointer to the vrf instance */ /* Flag to indicate if we are * advertising the g/w mac ip for * this VNI*/ uint8_t advertise_gw_macip; /* Flag to indicate if we are * advertising subnet for this VNI */ uint8_t advertise_subnet; /* Flag to indicate if we are advertising the svi mac ip for this VNI*/ uint8_t advertise_svi_macip; /* Id for deriving the RD * automatically for this VNI */ uint16_t rd_id; /* RD for this VNI. */ struct prefix_rd prd; /* Route type 3 field */ struct in_addr originator_ip; /* PIM-SM MDT group for BUM flooding */ struct in_addr mcast_grp; /* Import and Export RTs. */ struct list *import_rtl; struct list *export_rtl; /* Route table for EVPN routes for * this VNI. */ struct bgp_table *route_table; QOBJ_FIELDS }; DECLARE_QOBJ_TYPE(bgpevpn) struct evpnes { /* Ethernet Segment Identifier */ esi_t esi; /* es flags */ uint16_t flags; #define EVPNES_LOCAL 0x01 #define EVPNES_REMOTE 0x02 /* * Id for deriving the RD * automatically for this ESI */ uint16_t rd_id; /* RD for this VNI. */ struct prefix_rd prd; /* originator ip address */ struct ipaddr originator_ip; /* list of VTEPs in the same site */ struct list *vtep_list; /* * Route table for EVPN routes for * this ESI. - type4 routes */ struct bgp_table *route_table; QOBJ_FIELDS }; DECLARE_QOBJ_TYPE(evpnes) /* Mapping of Import RT to VNIs. * The Import RTs of all VNIs are maintained in a hash table with each * RT linking to all VNIs that will import routes matching this RT. */ struct irt_node { /* RT */ struct ecommunity_val rt; /* List of VNIs importing routes matching this RT. */ struct list *vnis; }; /* Mapping of Import RT to VRFs. * The Import RTs of all VRFss are maintained in a hash table with each * RT linking to all VRFs that will import routes matching this RT. */ struct vrf_irt_node { /* RT */ struct ecommunity_val rt; /* List of VNIs importing routes matching this RT. */ struct list *vrfs; }; #define RT_TYPE_IMPORT 1 #define RT_TYPE_EXPORT 2 #define RT_TYPE_BOTH 3 #define EVPN_DAD_DEFAULT_TIME 180 /* secs */ #define EVPN_DAD_DEFAULT_MAX_MOVES 5 /* default from RFC 7432 */ #define EVPN_DAD_DEFAULT_AUTO_RECOVERY_TIME 1800 /* secs */ struct bgp_evpn_info { /* enable disable dup detect */ bool dup_addr_detect; /* Detection time(M) */ int dad_time; /* Detection max moves(N) */ uint32_t dad_max_moves; /* Permanent freeze */ bool dad_freeze; /* Recovery time */ uint32_t dad_freeze_time; /* EVPN enable - advertise svi macip routes */ int advertise_svi_macip; }; static inline int is_vrf_rd_configured(struct bgp *bgp_vrf) { return (CHECK_FLAG(bgp_vrf->vrf_flags, BGP_VRF_RD_CFGD)); } static inline int bgp_evpn_vrf_rd_matches_existing(struct bgp *bgp_vrf, struct prefix_rd *prd) { return (memcmp(&bgp_vrf->vrf_prd.val, prd->val, ECOMMUNITY_SIZE) == 0); } static inline vni_t bgpevpn_get_l3vni(struct bgpevpn *vpn) { return vpn->bgp_vrf ? vpn->bgp_vrf->l3vni : 0; } static inline void bgpevpn_get_rmac(struct bgpevpn *vpn, struct ethaddr *rmac) { memset(rmac, 0, sizeof(struct ethaddr)); if (!vpn->bgp_vrf) return; memcpy(rmac, &vpn->bgp_vrf->rmac, sizeof(struct ethaddr)); } static inline struct list *bgpevpn_get_vrf_export_rtl(struct bgpevpn *vpn) { if (!vpn->bgp_vrf) return NULL; return vpn->bgp_vrf->vrf_export_rtl; } static inline struct list *bgpevpn_get_vrf_import_rtl(struct bgpevpn *vpn) { if (!vpn->bgp_vrf) return NULL; return vpn->bgp_vrf->vrf_import_rtl; } static inline void bgpevpn_unlink_from_l3vni(struct bgpevpn *vpn) { /* bail if vpn is not associated to bgp_vrf */ if (!vpn->bgp_vrf) return; UNSET_FLAG(vpn->flags, VNI_FLAG_USE_TWO_LABELS); listnode_delete(vpn->bgp_vrf->l2vnis, vpn); /* remove the backpointer to the vrf instance */ bgp_unlock(vpn->bgp_vrf); vpn->bgp_vrf = NULL; } static inline void bgpevpn_link_to_l3vni(struct bgpevpn *vpn) { struct bgp *bgp_vrf = NULL; /* bail if vpn is already associated to vrf */ if (vpn->bgp_vrf) return; bgp_vrf = bgp_lookup_by_vrf_id(vpn->tenant_vrf_id); if (!bgp_vrf) return; /* associate the vpn to the bgp_vrf instance */ vpn->bgp_vrf = bgp_lock(bgp_vrf); listnode_add_sort(bgp_vrf->l2vnis, vpn); /* check if we are advertising two labels for this vpn */ if (!CHECK_FLAG(bgp_vrf->vrf_flags, BGP_VRF_L3VNI_PREFIX_ROUTES_ONLY)) SET_FLAG(vpn->flags, VNI_FLAG_USE_TWO_LABELS); } static inline int is_vni_configured(struct bgpevpn *vpn) { return (CHECK_FLAG(vpn->flags, VNI_FLAG_CFGD)); } static inline int is_vni_live(struct bgpevpn *vpn) { return (CHECK_FLAG(vpn->flags, VNI_FLAG_LIVE)); } static inline int is_l3vni_live(struct bgp *bgp_vrf) { return (bgp_vrf->l3vni && bgp_vrf->l3vni_svi_ifindex); } static inline int is_rd_configured(struct bgpevpn *vpn) { return (CHECK_FLAG(vpn->flags, VNI_FLAG_RD_CFGD)); } static inline int bgp_evpn_rd_matches_existing(struct bgpevpn *vpn, struct prefix_rd *prd) { return (memcmp(&vpn->prd.val, prd->val, ECOMMUNITY_SIZE) == 0); } static inline int is_import_rt_configured(struct bgpevpn *vpn) { return (CHECK_FLAG(vpn->flags, VNI_FLAG_IMPRT_CFGD)); } static inline int is_export_rt_configured(struct bgpevpn *vpn) { return (CHECK_FLAG(vpn->flags, VNI_FLAG_EXPRT_CFGD)); } static inline int is_vni_param_configured(struct bgpevpn *vpn) { return (is_rd_configured(vpn) || is_import_rt_configured(vpn) || is_export_rt_configured(vpn)); } static inline void encode_es_rt_extcomm(struct ecommunity_val *eval, struct ethaddr *mac) { memset(eval, 0, sizeof(struct ecommunity_val)); eval->val[0] = ECOMMUNITY_ENCODE_EVPN; eval->val[1] = ECOMMUNITY_EVPN_SUBTYPE_ES_IMPORT_RT; memcpy(&eval->val[2], mac, ETH_ALEN); } static inline void encode_rmac_extcomm(struct ecommunity_val *eval, struct ethaddr *rmac) { memset(eval, 0, sizeof(*eval)); eval->val[0] = ECOMMUNITY_ENCODE_EVPN; eval->val[1] = ECOMMUNITY_EVPN_SUBTYPE_ROUTERMAC; memcpy(&eval->val[2], rmac, ETH_ALEN); } static inline void encode_default_gw_extcomm(struct ecommunity_val *eval) { memset(eval, 0, sizeof(*eval)); eval->val[0] = ECOMMUNITY_ENCODE_OPAQUE; eval->val[1] = ECOMMUNITY_EVPN_SUBTYPE_DEF_GW; } static inline void encode_mac_mobility_extcomm(int static_mac, uint32_t seq, struct ecommunity_val *eval) { memset(eval, 0, sizeof(*eval)); eval->val[0] = ECOMMUNITY_ENCODE_EVPN; eval->val[1] = ECOMMUNITY_EVPN_SUBTYPE_MACMOBILITY; if (static_mac) eval->val[2] = ECOMMUNITY_EVPN_SUBTYPE_MACMOBILITY_FLAG_STICKY; eval->val[4] = (seq >> 24) & 0xff; eval->val[5] = (seq >> 16) & 0xff; eval->val[6] = (seq >> 8) & 0xff; eval->val[7] = seq & 0xff; } static inline void encode_na_flag_extcomm(struct ecommunity_val *eval, uint8_t na_flag) { memset(eval, 0, sizeof(*eval)); eval->val[0] = ECOMMUNITY_ENCODE_EVPN; eval->val[1] = ECOMMUNITY_EVPN_SUBTYPE_ND; if (na_flag) eval->val[2] |= ECOMMUNITY_EVPN_SUBTYPE_ND_ROUTER_FLAG; } static inline void ip_prefix_from_type5_prefix(struct prefix_evpn *evp, struct prefix *ip) { memset(ip, 0, sizeof(struct prefix)); if (is_evpn_prefix_ipaddr_v4(evp)) { ip->family = AF_INET; ip->prefixlen = evp->prefix.prefix_addr.ip_prefix_length; memcpy(&(ip->u.prefix4), &(evp->prefix.prefix_addr.ip.ip), IPV4_MAX_BYTELEN); } else if (is_evpn_prefix_ipaddr_v6(evp)) { ip->family = AF_INET6; ip->prefixlen = evp->prefix.prefix_addr.ip_prefix_length; memcpy(&(ip->u.prefix6), &(evp->prefix.prefix_addr.ip.ip), IPV6_MAX_BYTELEN); } } static inline int is_evpn_prefix_default(const struct prefix *evp) { if (evp->family != AF_EVPN) return 0; return ((evp->u.prefix_evpn.prefix_addr.ip_prefix_length == 0) ? 1 : 0); } static inline void ip_prefix_from_type2_prefix(struct prefix_evpn *evp, struct prefix *ip) { memset(ip, 0, sizeof(struct prefix)); if (is_evpn_prefix_ipaddr_v4(evp)) { ip->family = AF_INET; ip->prefixlen = IPV4_MAX_BITLEN; memcpy(&(ip->u.prefix4), &(evp->prefix.macip_addr.ip.ip), IPV4_MAX_BYTELEN); } else if (is_evpn_prefix_ipaddr_v6(evp)) { ip->family = AF_INET6; ip->prefixlen = IPV6_MAX_BITLEN; memcpy(&(ip->u.prefix6), &(evp->prefix.macip_addr.ip.ip), IPV6_MAX_BYTELEN); } } static inline void ip_prefix_from_evpn_prefix(struct prefix_evpn *evp, struct prefix *ip) { if (evp->prefix.route_type == BGP_EVPN_MAC_IP_ROUTE) ip_prefix_from_type2_prefix(evp, ip); else if (evp->prefix.route_type == BGP_EVPN_IP_PREFIX_ROUTE) ip_prefix_from_type5_prefix(evp, ip); } static inline void build_evpn_type2_prefix(struct prefix_evpn *p, struct ethaddr *mac, struct ipaddr *ip) { memset(p, 0, sizeof(struct prefix_evpn)); p->family = AF_EVPN; p->prefixlen = EVPN_ROUTE_PREFIXLEN; p->prefix.route_type = BGP_EVPN_MAC_IP_ROUTE; memcpy(&p->prefix.macip_addr.mac.octet, mac->octet, ETH_ALEN); p->prefix.macip_addr.ip.ipa_type = IPADDR_NONE; if (ip) memcpy(&p->prefix.macip_addr.ip, ip, sizeof(*ip)); } static inline void build_type5_prefix_from_ip_prefix(struct prefix_evpn *evp, struct prefix *ip_prefix) { struct ipaddr ip; memset(&ip, 0, sizeof(struct ipaddr)); if (ip_prefix->family == AF_INET) { ip.ipa_type = IPADDR_V4; memcpy(&ip.ipaddr_v4, &ip_prefix->u.prefix4, sizeof(struct in_addr)); } else { ip.ipa_type = IPADDR_V6; memcpy(&ip.ipaddr_v6, &ip_prefix->u.prefix6, sizeof(struct in6_addr)); } memset(evp, 0, sizeof(struct prefix_evpn)); evp->family = AF_EVPN; evp->prefixlen = EVPN_ROUTE_PREFIXLEN; evp->prefix.route_type = BGP_EVPN_IP_PREFIX_ROUTE; evp->prefix.prefix_addr.ip_prefix_length = ip_prefix->prefixlen; evp->prefix.prefix_addr.ip.ipa_type = ip.ipa_type; memcpy(&evp->prefix.prefix_addr.ip, &ip, sizeof(struct ipaddr)); } static inline void build_evpn_type3_prefix(struct prefix_evpn *p, struct in_addr originator_ip) { memset(p, 0, sizeof(struct prefix_evpn)); p->family = AF_EVPN; p->prefixlen = EVPN_ROUTE_PREFIXLEN; p->prefix.route_type = BGP_EVPN_IMET_ROUTE; p->prefix.imet_addr.ip.ipa_type = IPADDR_V4; p->prefix.imet_addr.ip.ipaddr_v4 = originator_ip; } static inline void build_evpn_type4_prefix(struct prefix_evpn *p, esi_t *esi, struct in_addr originator_ip) { memset(p, 0, sizeof(struct prefix_evpn)); p->family = AF_EVPN; p->prefixlen = EVPN_ROUTE_PREFIXLEN; p->prefix.route_type = BGP_EVPN_ES_ROUTE; p->prefix.es_addr.ip_prefix_length = IPV4_MAX_BITLEN; p->prefix.es_addr.ip.ipa_type = IPADDR_V4; p->prefix.es_addr.ip.ipaddr_v4 = originator_ip; memcpy(&p->prefix.es_addr.esi, esi, sizeof(esi_t)); } static inline int evpn_default_originate_set(struct bgp *bgp, afi_t afi, safi_t safi) { if (afi == AFI_IP && CHECK_FLAG(bgp->af_flags[AFI_L2VPN][SAFI_EVPN], BGP_L2VPN_EVPN_DEFAULT_ORIGINATE_IPV4)) return 1; else if (afi == AFI_IP6 && CHECK_FLAG(bgp->af_flags[AFI_L2VPN][SAFI_EVPN], BGP_L2VPN_EVPN_DEFAULT_ORIGINATE_IPV6)) return 1; return 0; } static inline void es_get_system_mac(esi_t *esi, struct ethaddr *mac) { /* * for type-1 and type-3 ESIs, * the system mac starts at val[1] */ memcpy(mac, &esi->val[1], ETH_ALEN); } static inline int is_es_local(struct evpnes *es) { return CHECK_FLAG(es->flags, EVPNES_LOCAL) ? 1 : 0; } extern void bgp_evpn_install_uninstall_default_route(struct bgp *bgp_vrf, afi_t afi, safi_t safi, bool add); extern void evpn_rt_delete_auto(struct bgp *, vni_t, struct list *); extern void bgp_evpn_configure_export_rt_for_vrf(struct bgp *bgp_vrf, struct ecommunity *ecomadd); extern void bgp_evpn_unconfigure_export_rt_for_vrf(struct bgp *bgp_vrf, struct ecommunity *ecomdel); extern void bgp_evpn_configure_import_rt_for_vrf(struct bgp *bgp_vrf, struct ecommunity *ecomadd); extern void bgp_evpn_unconfigure_import_rt_for_vrf(struct bgp *bgp_vrf, struct ecommunity *ecomdel); extern int bgp_evpn_handle_export_rt_change(struct bgp *bgp, struct bgpevpn *vpn); extern void bgp_evpn_handle_autort_change(struct bgp *bgp); extern void bgp_evpn_handle_vrf_rd_change(struct bgp *bgp_vrf, int withdraw); extern void bgp_evpn_handle_rd_change(struct bgp *bgp, struct bgpevpn *vpn, int withdraw); extern int bgp_evpn_install_routes(struct bgp *bgp, struct bgpevpn *vpn); extern int bgp_evpn_uninstall_routes(struct bgp *bgp, struct bgpevpn *vpn); extern void bgp_evpn_map_vrf_to_its_rts(struct bgp *bgp_vrf); extern void bgp_evpn_unmap_vrf_from_its_rts(struct bgp *bgp_vrf); extern void bgp_evpn_map_vni_to_its_rts(struct bgp *bgp, struct bgpevpn *vpn); extern void bgp_evpn_unmap_vni_from_its_rts(struct bgp *bgp, struct bgpevpn *vpn); extern void bgp_evpn_derive_auto_rt_import(struct bgp *bgp, struct bgpevpn *vpn); extern void bgp_evpn_derive_auto_rt_export(struct bgp *bgp, struct bgpevpn *vpn); extern void bgp_evpn_derive_auto_rd(struct bgp *bgp, struct bgpevpn *vpn); extern void bgp_evpn_derive_auto_rd_for_vrf(struct bgp *bgp); extern struct bgpevpn *bgp_evpn_lookup_vni(struct bgp *bgp, vni_t vni); extern struct bgpevpn *bgp_evpn_new(struct bgp *bgp, vni_t vni, struct in_addr originator_ip, vrf_id_t tenant_vrf_id, struct in_addr mcast_grp); extern void bgp_evpn_free(struct bgp *bgp, struct bgpevpn *vpn); extern struct evpnes *bgp_evpn_lookup_es(struct bgp *bgp, esi_t *esi); extern struct evpnes *bgp_evpn_es_new(struct bgp *bgp, esi_t *esi, struct ipaddr *originator_ip); extern void bgp_evpn_es_free(struct bgp *bgp, struct evpnes *es); extern bool bgp_evpn_lookup_l3vni_l2vni_table(vni_t vni); #endif /* _BGP_EVPN_PRIVATE_H */ frr-7.2.1/bgpd/bgp_evpn_vty.c0000644000000000000000000042670613610377563013015 00000000000000/* Ethernet-VPN Packet and vty Processing File * Copyright (C) 2017 6WIND * * This file is part of FRRouting * * FRRouting is free software; you can redistribute it and/or modify it * under the terms of the GNU General Public License as published by the * Free Software Foundation; either version 2, or (at your option) any * later version. * * FRRouting 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; see the file COPYING; if not, write to the Free Software * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA */ #include #include "command.h" #include "prefix.h" #include "lib/json.h" #include "stream.h" #include "bgpd/bgpd.h" #include "bgpd/bgp_table.h" #include "bgpd/bgp_attr.h" #include "bgpd/bgp_route.h" #include "bgpd/bgp_mplsvpn.h" #include "bgpd/bgp_vpn.h" #include "bgpd/bgp_evpn_vty.h" #include "bgpd/bgp_evpn.h" #include "bgpd/bgp_evpn_private.h" #include "bgpd/bgp_zebra.h" #include "bgpd/bgp_vty.h" #include "bgpd/bgp_errors.h" #include "bgpd/bgp_ecommunity.h" #define SHOW_DISPLAY_STANDARD 0 #define SHOW_DISPLAY_TAGS 1 #define SHOW_DISPLAY_OVERLAY 2 #define VNI_STR_LEN 32 /* * Context for VNI hash walk - used by callbacks. */ struct vni_walk_ctx { struct bgp *bgp; struct vty *vty; struct in_addr vtep_ip; json_object *json; int detail; }; static void display_vrf_import_rt(struct vty *vty, struct vrf_irt_node *irt, json_object *json) { uint8_t *pnt; uint8_t type, sub_type; struct ecommunity_as eas; struct ecommunity_ip eip; struct listnode *node, *nnode; struct bgp *tmp_bgp_vrf = NULL; json_object *json_rt = NULL; json_object *json_vrfs = NULL; char rt_buf[RT_ADDRSTRLEN]; if (json) { json_rt = json_object_new_object(); json_vrfs = json_object_new_array(); } pnt = (uint8_t *)&irt->rt.val; type = *pnt++; sub_type = *pnt++; if (sub_type != ECOMMUNITY_ROUTE_TARGET) return; memset(&eas, 0, sizeof(eas)); switch (type) { case ECOMMUNITY_ENCODE_AS: eas.as = (*pnt++ << 8); eas.as |= (*pnt++); ptr_get_be32(pnt, &eas.val); snprintf(rt_buf, RT_ADDRSTRLEN, "%u:%u", eas.as, eas.val); if (json) json_object_string_add(json_rt, "rt", rt_buf); else vty_out(vty, "Route-target: %s", rt_buf); break; case ECOMMUNITY_ENCODE_IP: memcpy(&eip.ip, pnt, 4); pnt += 4; eip.val = (*pnt++ << 8); eip.val |= (*pnt++); snprintf(rt_buf, RT_ADDRSTRLEN, "%s:%u", inet_ntoa(eip.ip), eip.val); if (json) json_object_string_add(json_rt, "rt", rt_buf); else vty_out(vty, "Route-target: %s", rt_buf); break; case ECOMMUNITY_ENCODE_AS4: pnt = ptr_get_be32(pnt, &eas.val); eas.val = (*pnt++ << 8); eas.val |= (*pnt++); snprintf(rt_buf, RT_ADDRSTRLEN, "%u:%u", eas.as, eas.val); if (json) json_object_string_add(json_rt, "rt", rt_buf); else vty_out(vty, "Route-target: %s", rt_buf); break; default: return; } if (!json) { vty_out(vty, "\nList of VRFs importing routes with this route-target:\n"); } for (ALL_LIST_ELEMENTS(irt->vrfs, node, nnode, tmp_bgp_vrf)) { if (json) json_object_array_add( json_vrfs, json_object_new_string( vrf_id_to_name(tmp_bgp_vrf->vrf_id))); else vty_out(vty, " %s\n", vrf_id_to_name(tmp_bgp_vrf->vrf_id)); } if (json) { json_object_object_add(json_rt, "vrfs", json_vrfs); json_object_object_add(json, rt_buf, json_rt); } } static void show_vrf_import_rt_entry(struct hash_bucket *bucket, void *args[]) { json_object *json = NULL; struct vty *vty = NULL; struct vrf_irt_node *irt = (struct vrf_irt_node *)bucket->data; vty = (struct vty *)args[0]; json = (struct json_object *)args[1]; display_vrf_import_rt(vty, irt, json); } static void display_import_rt(struct vty *vty, struct irt_node *irt, json_object *json) { uint8_t *pnt; uint8_t type, sub_type; struct ecommunity_as eas; struct ecommunity_ip eip; struct listnode *node, *nnode; struct bgpevpn *tmp_vpn; json_object *json_rt = NULL; json_object *json_vnis = NULL; char rt_buf[RT_ADDRSTRLEN]; if (json) { json_rt = json_object_new_object(); json_vnis = json_object_new_array(); } /* TODO: This needs to go into a function */ pnt = (uint8_t *)&irt->rt.val; type = *pnt++; sub_type = *pnt++; if (sub_type != ECOMMUNITY_ROUTE_TARGET) return; memset(&eas, 0, sizeof(eas)); switch (type) { case ECOMMUNITY_ENCODE_AS: eas.as = (*pnt++ << 8); eas.as |= (*pnt++); ptr_get_be32(pnt, &eas.val); snprintf(rt_buf, RT_ADDRSTRLEN, "%u:%u", eas.as, eas.val); if (json) json_object_string_add(json_rt, "rt", rt_buf); else vty_out(vty, "Route-target: %s", rt_buf); break; case ECOMMUNITY_ENCODE_IP: memcpy(&eip.ip, pnt, 4); pnt += 4; eip.val = (*pnt++ << 8); eip.val |= (*pnt++); snprintf(rt_buf, RT_ADDRSTRLEN, "%s:%u", inet_ntoa(eip.ip), eip.val); if (json) json_object_string_add(json_rt, "rt", rt_buf); else vty_out(vty, "Route-target: %s", rt_buf); break; case ECOMMUNITY_ENCODE_AS4: pnt = ptr_get_be32(pnt, &eas.val); eas.val = (*pnt++ << 8); eas.val |= (*pnt++); snprintf(rt_buf, RT_ADDRSTRLEN, "%u:%u", eas.as, eas.val); if (json) json_object_string_add(json_rt, "rt", rt_buf); else vty_out(vty, "Route-target: %s", rt_buf); break; default: return; } if (!json) { vty_out(vty, "\nList of VNIs importing routes with this route-target:\n"); } for (ALL_LIST_ELEMENTS(irt->vnis, node, nnode, tmp_vpn)) { if (json) json_object_array_add( json_vnis, json_object_new_int(tmp_vpn->vni)); else vty_out(vty, " %u\n", tmp_vpn->vni); } if (json) { json_object_object_add(json_rt, "vnis", json_vnis); json_object_object_add(json, rt_buf, json_rt); } } static void show_import_rt_entry(struct hash_bucket *bucket, void *args[]) { json_object *json = NULL; struct vty *vty = NULL; struct irt_node *irt = (struct irt_node *)bucket->data; vty = args[0]; json = args[1]; display_import_rt(vty, irt, json); return; } static void bgp_evpn_show_route_rd_header(struct vty *vty, struct bgp_node *rd_rn, json_object *json) { uint16_t type; struct rd_as rd_as; struct rd_ip rd_ip; uint8_t *pnt; char rd_str[RD_ADDRSTRLEN]; pnt = rd_rn->p.u.val; /* Decode RD type. */ type = decode_rd_type(pnt); if (json) return; vty_out(vty, "Route Distinguisher: "); switch (type) { case RD_TYPE_AS: decode_rd_as(pnt + 2, &rd_as); snprintf(rd_str, RD_ADDRSTRLEN, "%u:%d", rd_as.as, rd_as.val); break; case RD_TYPE_IP: decode_rd_ip(pnt + 2, &rd_ip); snprintf(rd_str, RD_ADDRSTRLEN, "%s:%d", inet_ntoa(rd_ip.ip), rd_ip.val); break; default: snprintf(rd_str, RD_ADDRSTRLEN, "Unknown RD type"); break; } vty_out(vty, "%s\n", rd_str); } static void bgp_evpn_show_route_header(struct vty *vty, struct bgp *bgp, uint64_t tbl_ver, json_object *json) { char ri_header[] = " Network Next Hop Metric LocPrf Weight Path\n"; if (json) return; vty_out(vty, "BGP table version is %" PRIu64 ", local router ID is %s\n", tbl_ver, inet_ntoa(bgp->router_id)); vty_out(vty, "Status codes: s suppressed, d damped, h history, " "* valid, > best, i - internal\n"); vty_out(vty, "Origin codes: i - IGP, e - EGP, ? - incomplete\n"); vty_out(vty, "EVPN type-2 prefix: [2]:[EthTag]:[MAClen]:[MAC]:[IPlen]:[IP]\n"); vty_out(vty, "EVPN type-3 prefix: [3]:[EthTag]:[IPlen]:[OrigIP]\n"); vty_out(vty, "EVPN type-4 prefix: [4]:[ESI]:[IPlen]:[OrigIP]\n"); vty_out(vty, "EVPN type-5 prefix: [5]:[EthTag]:[IPlen]:[IP]\n\n"); vty_out(vty, "%s", ri_header); } static void display_l3vni(struct vty *vty, struct bgp *bgp_vrf, json_object *json) { char buf1[INET6_ADDRSTRLEN]; char *ecom_str; struct listnode *node, *nnode; struct ecommunity *ecom; json_object *json_import_rtl = NULL; json_object *json_export_rtl = NULL; json_import_rtl = json_export_rtl = 0; if (json) { json_import_rtl = json_object_new_array(); json_export_rtl = json_object_new_array(); json_object_int_add(json, "vni", bgp_vrf->l3vni); json_object_string_add(json, "type", "L3"); json_object_string_add(json, "kernelFlag", "Yes"); json_object_string_add( json, "rd", prefix_rd2str(&bgp_vrf->vrf_prd, buf1, RD_ADDRSTRLEN)); json_object_string_add(json, "originatorIp", inet_ntoa(bgp_vrf->originator_ip)); json_object_string_add(json, "advertiseGatewayMacip", "n/a"); } else { vty_out(vty, "VNI: %d", bgp_vrf->l3vni); vty_out(vty, " (known to the kernel)"); vty_out(vty, "\n"); vty_out(vty, " Type: %s\n", "L3"); vty_out(vty, " Tenant VRF: %s\n", vrf_id_to_name(bgp_vrf->vrf_id)); vty_out(vty, " RD: %s\n", prefix_rd2str(&bgp_vrf->vrf_prd, buf1, RD_ADDRSTRLEN)); vty_out(vty, " Originator IP: %s\n", inet_ntoa(bgp_vrf->originator_ip)); vty_out(vty, " Advertise-gw-macip : %s\n", "n/a"); } if (!json) vty_out(vty, " Import Route Target:\n"); for (ALL_LIST_ELEMENTS(bgp_vrf->vrf_import_rtl, node, nnode, ecom)) { ecom_str = ecommunity_ecom2str(ecom, ECOMMUNITY_FORMAT_ROUTE_MAP, 0); if (json) json_object_array_add(json_import_rtl, json_object_new_string(ecom_str)); else vty_out(vty, " %s\n", ecom_str); XFREE(MTYPE_ECOMMUNITY_STR, ecom_str); } if (json) json_object_object_add(json, "importRts", json_import_rtl); else vty_out(vty, " Export Route Target:\n"); for (ALL_LIST_ELEMENTS(bgp_vrf->vrf_export_rtl, node, nnode, ecom)) { ecom_str = ecommunity_ecom2str(ecom, ECOMMUNITY_FORMAT_ROUTE_MAP, 0); if (json) json_object_array_add(json_export_rtl, json_object_new_string(ecom_str)); else vty_out(vty, " %s\n", ecom_str); XFREE(MTYPE_ECOMMUNITY_STR, ecom_str); } if (json) json_object_object_add(json, "exportRts", json_export_rtl); } static void display_es(struct vty *vty, struct evpnes *es, json_object *json) { struct in_addr *vtep; char buf[ESI_STR_LEN]; char buf1[RD_ADDRSTRLEN]; char buf2[INET6_ADDRSTRLEN]; struct listnode *node = NULL; json_object *json_vteps = NULL; if (json) { json_vteps = json_object_new_array(); json_object_string_add(json, "esi", esi_to_str(&es->esi, buf, sizeof(buf))); json_object_string_add(json, "rd", prefix_rd2str(&es->prd, buf1, sizeof(buf1))); json_object_string_add( json, "originatorIp", ipaddr2str(&es->originator_ip, buf2, sizeof(buf2))); if (es->vtep_list) { for (ALL_LIST_ELEMENTS_RO(es->vtep_list, node, vtep)) json_object_array_add( json_vteps, json_object_new_string( inet_ntoa(*vtep))); } json_object_object_add(json, "vteps", json_vteps); } else { vty_out(vty, "ESI: %s\n", esi_to_str(&es->esi, buf, sizeof(buf))); vty_out(vty, " RD: %s\n", prefix_rd2str(&es->prd, buf1, sizeof(buf1))); vty_out(vty, " Originator-IP: %s\n", ipaddr2str(&es->originator_ip, buf2, sizeof(buf2))); if (es->vtep_list) { vty_out(vty, " VTEP List:\n"); for (ALL_LIST_ELEMENTS_RO(es->vtep_list, node, vtep)) vty_out(vty, " %s\n", inet_ntoa(*vtep)); } } } static void display_vni(struct vty *vty, struct bgpevpn *vpn, json_object *json) { char buf1[RD_ADDRSTRLEN]; char *ecom_str; struct listnode *node, *nnode; struct ecommunity *ecom; json_object *json_import_rtl = NULL; json_object *json_export_rtl = NULL; if (json) { json_import_rtl = json_object_new_array(); json_export_rtl = json_object_new_array(); json_object_int_add(json, "vni", vpn->vni); json_object_string_add(json, "type", "L2"); json_object_string_add(json, "kernelFlag", is_vni_live(vpn) ? "Yes" : "No"); json_object_string_add( json, "rd", prefix_rd2str(&vpn->prd, buf1, sizeof(buf1))); json_object_string_add(json, "originatorIp", inet_ntoa(vpn->originator_ip)); json_object_string_add(json, "mcastGroup", inet_ntoa(vpn->mcast_grp)); json_object_string_add(json, "advertiseGatewayMacip", vpn->advertise_gw_macip ? "Yes" : "No"); } else { vty_out(vty, "VNI: %d", vpn->vni); if (is_vni_live(vpn)) vty_out(vty, " (known to the kernel)"); vty_out(vty, "\n"); vty_out(vty, " Type: %s\n", "L2"); vty_out(vty, " Tenant-Vrf: %s\n", vrf_id_to_name(vpn->tenant_vrf_id)); vty_out(vty, " RD: %s\n", prefix_rd2str(&vpn->prd, buf1, sizeof(buf1))); vty_out(vty, " Originator IP: %s\n", inet_ntoa(vpn->originator_ip)); vty_out(vty, " Mcast group: %s\n", inet_ntoa(vpn->mcast_grp)); vty_out(vty, " Advertise-gw-macip : %s\n", vpn->advertise_gw_macip ? "Yes" : "No"); vty_out(vty, " Advertise-svi-macip : %s\n", vpn->advertise_svi_macip ? "Yes" : "No"); } if (!json) vty_out(vty, " Import Route Target:\n"); for (ALL_LIST_ELEMENTS(vpn->import_rtl, node, nnode, ecom)) { ecom_str = ecommunity_ecom2str(ecom, ECOMMUNITY_FORMAT_ROUTE_MAP, 0); if (json) json_object_array_add(json_import_rtl, json_object_new_string(ecom_str)); else vty_out(vty, " %s\n", ecom_str); XFREE(MTYPE_ECOMMUNITY_STR, ecom_str); } if (json) json_object_object_add(json, "importRts", json_import_rtl); else vty_out(vty, " Export Route Target:\n"); for (ALL_LIST_ELEMENTS(vpn->export_rtl, node, nnode, ecom)) { ecom_str = ecommunity_ecom2str(ecom, ECOMMUNITY_FORMAT_ROUTE_MAP, 0); if (json) json_object_array_add(json_export_rtl, json_object_new_string(ecom_str)); else vty_out(vty, " %s\n", ecom_str); XFREE(MTYPE_ECOMMUNITY_STR, ecom_str); } if (json) json_object_object_add(json, "exportRts", json_export_rtl); } static void show_esi_routes(struct bgp *bgp, struct evpnes *es, struct vty *vty, json_object *json) { int header = 1; struct bgp_node *rn; struct bgp_path_info *pi; uint32_t prefix_cnt, path_cnt; uint64_t tbl_ver; prefix_cnt = path_cnt = 0; tbl_ver = es->route_table->version; for (rn = bgp_table_top(es->route_table); rn; rn = bgp_route_next(rn)) { int add_prefix_to_json = 0; char prefix_str[BUFSIZ]; json_object *json_paths = NULL; json_object *json_prefix = NULL; bgp_evpn_route2str((struct prefix_evpn *)&rn->p, prefix_str, sizeof(prefix_str)); if (json) json_prefix = json_object_new_object(); pi = bgp_node_get_bgp_path_info(rn); if (pi) { /* Overall header/legend displayed once. */ if (header) { bgp_evpn_show_route_header(vty, bgp, tbl_ver, json); header = 0; } prefix_cnt++; } if (json) json_paths = json_object_new_array(); /* For EVPN, the prefix is displayed for each path (to fit in * with code that already exists). */ for (; pi; pi = pi->next) { json_object *json_path = NULL; if (json) json_path = json_object_new_array(); route_vty_out(vty, &rn->p, pi, 0, SAFI_EVPN, json_path); if (json) json_object_array_add(json_paths, json_path); path_cnt++; add_prefix_to_json = 1; } if (json && add_prefix_to_json) { json_object_string_add(json_prefix, "prefix", prefix_str); json_object_int_add(json_prefix, "prefixLen", rn->p.prefixlen); json_object_object_add(json_prefix, "paths", json_paths); json_object_object_add(json, prefix_str, json_prefix); } } if (json) { json_object_int_add(json, "numPrefix", prefix_cnt); json_object_int_add(json, "numPaths", path_cnt); } else { if (prefix_cnt == 0) vty_out(vty, "No EVPN prefixes exist for this ESI\n"); else vty_out(vty, "\nDisplayed %u prefixes (%u paths)\n", prefix_cnt, path_cnt); } } static void show_vni_routes(struct bgp *bgp, struct bgpevpn *vpn, int type, struct vty *vty, struct in_addr vtep_ip, json_object *json, int detail) { struct bgp_node *rn; struct bgp_path_info *pi; struct bgp_table *table; int header = detail ? 0 : 1; uint64_t tbl_ver; uint32_t prefix_cnt, path_cnt; prefix_cnt = path_cnt = 0; table = vpn->route_table; tbl_ver = table->version; for (rn = bgp_table_top(table); rn; rn = bgp_route_next(rn)) { struct prefix_evpn *evp = (struct prefix_evpn *)&rn->p; int add_prefix_to_json = 0; char prefix_str[BUFSIZ]; json_object *json_paths = NULL; json_object *json_prefix = NULL; bgp_evpn_route2str((struct prefix_evpn *)&rn->p, prefix_str, sizeof(prefix_str)); if (type && evp->prefix.route_type != type) continue; if (json) json_prefix = json_object_new_object(); pi = bgp_node_get_bgp_path_info(rn); if (pi) { /* Overall header/legend displayed once. */ if (header) { bgp_evpn_show_route_header(vty, bgp, tbl_ver, json); header = 0; } prefix_cnt++; } if (json) json_paths = json_object_new_array(); /* For EVPN, the prefix is displayed for each path (to fit in * with code that already exists). */ for (; pi; pi = pi->next) { json_object *json_path = NULL; if (vtep_ip.s_addr && !IPV4_ADDR_SAME(&(vtep_ip), &(pi->attr->nexthop))) continue; if (json) json_path = json_object_new_array(); if (detail) route_vty_out_detail(vty, bgp, rn, pi, AFI_L2VPN, SAFI_EVPN, json_path); else route_vty_out(vty, &rn->p, pi, 0, SAFI_EVPN, json_path); if (json) json_object_array_add(json_paths, json_path); path_cnt++; add_prefix_to_json = 1; } if (json && add_prefix_to_json) { json_object_string_add(json_prefix, "prefix", prefix_str); json_object_int_add(json_prefix, "prefixLen", rn->p.prefixlen); json_object_object_add(json_prefix, "paths", json_paths); json_object_object_add(json, prefix_str, json_prefix); } } if (json) { json_object_int_add(json, "numPrefix", prefix_cnt); json_object_int_add(json, "numPaths", path_cnt); } else { if (prefix_cnt == 0) vty_out(vty, "No EVPN prefixes %sexist for this VNI", type ? "(of requested type) " : ""); else vty_out(vty, "\nDisplayed %u prefixes (%u paths)%s\n", prefix_cnt, path_cnt, type ? " (of requested type)" : ""); vty_out(vty, "\n"); } } static void show_vni_routes_hash(struct hash_bucket *bucket, void *arg) { struct bgpevpn *vpn = (struct bgpevpn *)bucket->data; struct vni_walk_ctx *wctx = arg; struct vty *vty = wctx->vty; json_object *json = wctx->json; json_object *json_vni = NULL; char vni_str[VNI_STR_LEN]; snprintf(vni_str, VNI_STR_LEN, "%d", vpn->vni); if (json) { json_vni = json_object_new_object(); json_object_int_add(json_vni, "vni", vpn->vni); } else { vty_out(vty, "\nVNI: %d\n\n", vpn->vni); } show_vni_routes(wctx->bgp, vpn, 0, wctx->vty, wctx->vtep_ip, json_vni, wctx->detail); if (json) json_object_object_add(json, vni_str, json_vni); } static void show_l3vni_entry(struct vty *vty, struct bgp *bgp, json_object *json) { json_object *json_vni = NULL; json_object *json_import_rtl = NULL; json_object *json_export_rtl = NULL; char buf1[10]; char buf2[INET6_ADDRSTRLEN]; char rt_buf[25]; char *ecom_str; struct listnode *node, *nnode; struct ecommunity *ecom; if (!bgp->l3vni) return; if (json) { json_vni = json_object_new_object(); json_import_rtl = json_object_new_array(); json_export_rtl = json_object_new_array(); } /* if an l3vni is present in bgp it is live */ buf1[0] = '\0'; sprintf(buf1, "*"); if (json) { json_object_int_add(json_vni, "vni", bgp->l3vni); json_object_string_add(json_vni, "type", "L3"); json_object_string_add(json_vni, "inKernel", "True"); json_object_string_add(json_vni, "originatorIp", inet_ntoa(bgp->originator_ip)); json_object_string_add( json_vni, "rd", prefix_rd2str(&bgp->vrf_prd, buf2, RD_ADDRSTRLEN)); } else { vty_out(vty, "%-1s %-10u %-4s %-21s", buf1, bgp->l3vni, "L3", prefix_rd2str(&bgp->vrf_prd, buf2, RD_ADDRSTRLEN)); } for (ALL_LIST_ELEMENTS(bgp->vrf_import_rtl, node, nnode, ecom)) { ecom_str = ecommunity_ecom2str(ecom, ECOMMUNITY_FORMAT_ROUTE_MAP, 0); if (json) { json_object_array_add(json_import_rtl, json_object_new_string(ecom_str)); } else { if (listcount(bgp->vrf_import_rtl) > 1) sprintf(rt_buf, "%s, ...", ecom_str); else sprintf(rt_buf, "%s", ecom_str); vty_out(vty, " %-25s", rt_buf); } XFREE(MTYPE_ECOMMUNITY_STR, ecom_str); /* If there are multiple import RTs we break here and show only * one */ if (!json) break; } if (json) json_object_object_add(json_vni, "importRTs", json_import_rtl); for (ALL_LIST_ELEMENTS(bgp->vrf_export_rtl, node, nnode, ecom)) { ecom_str = ecommunity_ecom2str(ecom, ECOMMUNITY_FORMAT_ROUTE_MAP, 0); if (json) { json_object_array_add(json_export_rtl, json_object_new_string(ecom_str)); } else { if (listcount(bgp->vrf_export_rtl) > 1) sprintf(rt_buf, "%s, ...", ecom_str); else sprintf(rt_buf, "%s", ecom_str); vty_out(vty, " %-25s", rt_buf); } XFREE(MTYPE_ECOMMUNITY_STR, ecom_str); /* If there are multiple export RTs we break here and show only * one */ if (!json) break; } if (!json) vty_out(vty, "%-37s", vrf_id_to_name(bgp->vrf_id)); if (json) { char vni_str[VNI_STR_LEN]; json_object_object_add(json_vni, "exportRTs", json_export_rtl); snprintf(vni_str, VNI_STR_LEN, "%u", bgp->l3vni); json_object_object_add(json, vni_str, json_vni); } else { vty_out(vty, "\n"); } } static void show_es_entry(struct hash_bucket *bucket, void *args[]) { char buf[ESI_STR_LEN]; char buf1[RD_ADDRSTRLEN]; char buf2[INET6_ADDRSTRLEN]; struct in_addr *vtep = NULL; struct vty *vty = args[0]; json_object *json = args[1]; json_object *json_vteps = NULL; struct listnode *node = NULL; struct evpnes *es = (struct evpnes *)bucket->data; if (json) { json_vteps = json_object_new_array(); json_object_string_add(json, "esi", esi_to_str(&es->esi, buf, sizeof(buf))); json_object_string_add(json, "type", is_es_local(es) ? "Local" : "Remote"); json_object_string_add(json, "rd", prefix_rd2str(&es->prd, buf1, sizeof(buf1))); json_object_string_add( json, "originatorIp", ipaddr2str(&es->originator_ip, buf2, sizeof(buf2))); if (es->vtep_list) { for (ALL_LIST_ELEMENTS_RO(es->vtep_list, node, vtep)) json_object_array_add(json_vteps, json_object_new_string( inet_ntoa(*vtep))); } json_object_object_add(json, "vteps", json_vteps); } else { vty_out(vty, "%-30s %-6s %-21s %-15s %-6d\n", esi_to_str(&es->esi, buf, sizeof(buf)), is_es_local(es) ? "Local" : "Remote", prefix_rd2str(&es->prd, buf1, sizeof(buf1)), ipaddr2str(&es->originator_ip, buf2, sizeof(buf2)), es->vtep_list ? listcount(es->vtep_list) : 0); } } static void show_vni_entry(struct hash_bucket *bucket, void *args[]) { struct vty *vty; json_object *json; json_object *json_vni = NULL; json_object *json_import_rtl = NULL; json_object *json_export_rtl = NULL; struct bgpevpn *vpn = (struct bgpevpn *)bucket->data; char buf1[10]; char buf2[RD_ADDRSTRLEN]; char rt_buf[25]; char *ecom_str; struct listnode *node, *nnode; struct ecommunity *ecom; vty = args[0]; json = args[1]; if (json) { json_vni = json_object_new_object(); json_import_rtl = json_object_new_array(); json_export_rtl = json_object_new_array(); } buf1[0] = '\0'; if (is_vni_live(vpn)) sprintf(buf1, "*"); if (json) { json_object_int_add(json_vni, "vni", vpn->vni); json_object_string_add(json_vni, "type", "L2"); json_object_string_add(json_vni, "inKernel", is_vni_live(vpn) ? "True" : "False"); json_object_string_add(json_vni, "originatorIp", inet_ntoa(vpn->originator_ip)); json_object_string_add(json_vni, "originatorIp", inet_ntoa(vpn->originator_ip)); json_object_string_add( json_vni, "rd", prefix_rd2str(&vpn->prd, buf2, sizeof(buf2))); } else { vty_out(vty, "%-1s %-10u %-4s %-21s", buf1, vpn->vni, "L2", prefix_rd2str(&vpn->prd, buf2, RD_ADDRSTRLEN)); } for (ALL_LIST_ELEMENTS(vpn->import_rtl, node, nnode, ecom)) { ecom_str = ecommunity_ecom2str(ecom, ECOMMUNITY_FORMAT_ROUTE_MAP, 0); if (json) { json_object_array_add(json_import_rtl, json_object_new_string(ecom_str)); } else { if (listcount(vpn->import_rtl) > 1) sprintf(rt_buf, "%s, ...", ecom_str); else sprintf(rt_buf, "%s", ecom_str); vty_out(vty, " %-25s", rt_buf); } XFREE(MTYPE_ECOMMUNITY_STR, ecom_str); /* If there are multiple import RTs we break here and show only * one */ if (!json) break; } if (json) json_object_object_add(json_vni, "importRTs", json_import_rtl); for (ALL_LIST_ELEMENTS(vpn->export_rtl, node, nnode, ecom)) { ecom_str = ecommunity_ecom2str(ecom, ECOMMUNITY_FORMAT_ROUTE_MAP, 0); if (json) { json_object_array_add(json_export_rtl, json_object_new_string(ecom_str)); } else { if (listcount(vpn->export_rtl) > 1) sprintf(rt_buf, "%s, ...", ecom_str); else sprintf(rt_buf, "%s", ecom_str); vty_out(vty, " %-25s", rt_buf); } XFREE(MTYPE_ECOMMUNITY_STR, ecom_str); /* If there are multiple export RTs we break here and show only * one */ if (!json) break; } if (!json) vty_out(vty, "%-37s", vrf_id_to_name(vpn->tenant_vrf_id)); if (json) { char vni_str[VNI_STR_LEN]; json_object_object_add(json_vni, "exportRTs", json_export_rtl); snprintf(vni_str, VNI_STR_LEN, "%u", vpn->vni); json_object_object_add(json, vni_str, json_vni); } else { vty_out(vty, "\n"); } } static int bgp_show_ethernet_vpn(struct vty *vty, struct prefix_rd *prd, enum bgp_show_type type, void *output_arg, int option, bool use_json) { afi_t afi = AFI_L2VPN; struct bgp *bgp; struct bgp_table *table; struct bgp_node *rn; struct bgp_node *rm; struct bgp_path_info *pi; int rd_header; int header = 1; char rd_str[BUFSIZ]; char buf[BUFSIZ]; unsigned long output_count = 0; unsigned long total_count = 0; json_object *json = NULL; json_object *json_nroute = NULL; json_object *json_array = NULL; json_object *json_prefix_info = NULL; memset(rd_str, 0, BUFSIZ); bgp = bgp_get_evpn(); if (bgp == NULL) { if (!use_json) vty_out(vty, "No BGP process is configured\n"); else vty_out(vty, "{}\n"); return CMD_WARNING; } if (use_json) json = json_object_new_object(); for (rn = bgp_table_top(bgp->rib[afi][SAFI_EVPN]); rn; rn = bgp_route_next(rn)) { uint64_t tbl_ver; if (prd && memcmp(rn->p.u.val, prd->val, 8) != 0) continue; table = bgp_node_get_bgp_table_info(rn); if (!table) continue; rd_header = 1; tbl_ver = table->version; for (rm = bgp_table_top(table); rm; rm = bgp_route_next(rm)) { if (use_json) { json_array = json_object_new_array(); json_prefix_info = json_object_new_object(); json_object_string_add(json_prefix_info, "prefix", bgp_evpn_route2str( (struct prefix_evpn *)&rm->p, buf, BUFSIZ)); json_object_int_add(json_prefix_info, "prefixLen", rm->p.prefixlen); if (rd_header) json_nroute = json_object_new_object(); } for (pi = bgp_node_get_bgp_path_info(rm); pi; pi = pi->next) { total_count++; if (type == bgp_show_type_neighbor) { struct peer *peer = output_arg; if (peer_cmp(peer, pi->peer) != 0) continue; } if (header) { if (use_json) { json_object_int_add( json, "bgpTableVersion", tbl_ver); json_object_string_add( json, "bgpLocalRouterId", inet_ntoa( bgp->router_id)); json_object_int_add( json, "defaultLocPrf", bgp->default_local_pref); json_object_int_add( json, "localAS", bgp->as); } else { if (option == SHOW_DISPLAY_TAGS) vty_out(vty, V4_HEADER_TAG); else if ( option == SHOW_DISPLAY_OVERLAY) vty_out(vty, V4_HEADER_OVERLAY); else { vty_out(vty, "BGP table version is %" PRIu64 ", local router ID is %s\n", tbl_ver, inet_ntoa( bgp->router_id)); vty_out(vty, "Status codes: s suppressed, d damped, h history, * valid, > best, i - internal\n"); vty_out(vty, "Origin codes: i - IGP, e - EGP, ? - incomplete\n\n"); vty_out(vty, V4_HEADER); } } header = 0; } if (rd_header) { uint16_t type; struct rd_as rd_as; struct rd_ip rd_ip; uint8_t *pnt; pnt = rn->p.u.val; /* Decode RD type. */ type = decode_rd_type(pnt); /* Decode RD value. */ if (type == RD_TYPE_AS) decode_rd_as(pnt + 2, &rd_as); else if (type == RD_TYPE_AS4) decode_rd_as4(pnt + 2, &rd_as); else if (type == RD_TYPE_IP) decode_rd_ip(pnt + 2, &rd_ip); if (use_json) { if (type == RD_TYPE_AS || type == RD_TYPE_AS4) sprintf(rd_str, "%u:%d", rd_as.as, rd_as.val); else if (type == RD_TYPE_IP) sprintf(rd_str, "%s:%d", inet_ntoa( rd_ip.ip), rd_ip.val); json_object_string_add( json_nroute, "rd", rd_str); } else { vty_out(vty, "Route Distinguisher: "); if (type == RD_TYPE_AS) vty_out(vty, "as2 %u:%d", rd_as.as, rd_as.val); else if (type == RD_TYPE_AS4) vty_out(vty, "as4 %u:%d", rd_as.as, rd_as.val); else if (type == RD_TYPE_IP) vty_out(vty, "ip %s:%d", inet_ntoa( rd_ip.ip), rd_ip.val); vty_out(vty, "\n\n"); } rd_header = 0; } if (option == SHOW_DISPLAY_TAGS) route_vty_out_tag(vty, &rm->p, pi, 0, SAFI_EVPN, json_array); else if (option == SHOW_DISPLAY_OVERLAY) route_vty_out_overlay(vty, &rm->p, pi, 0, json_array); else route_vty_out(vty, &rm->p, pi, 0, SAFI_EVPN, json_array); output_count++; } rd_header = 0; if (use_json) { json_object_object_add(json_prefix_info, "paths", json_array); json_object_object_add(json_nroute, buf, json_prefix_info); } } if (use_json) json_object_object_add(json, rd_str, json_nroute); } if (use_json) { json_object_int_add(json, "numPrefix", output_count); json_object_int_add(json, "totalPrefix", total_count); vty_out(vty, "%s\n", json_object_to_json_string_ext( json, JSON_C_TO_STRING_PRETTY)); json_object_free(json); } else { if (output_count == 0) vty_out(vty, "No prefixes displayed, %ld exist\n", total_count); else vty_out(vty, "\nDisplayed %ld out of %ld total prefixes\n", output_count, total_count); } return CMD_SUCCESS; } DEFUN(show_ip_bgp_l2vpn_evpn, show_ip_bgp_l2vpn_evpn_cmd, "show [ip] bgp l2vpn evpn [json]", SHOW_STR IP_STR BGP_STR L2VPN_HELP_STR EVPN_HELP_STR JSON_STR) { return bgp_show_ethernet_vpn(vty, NULL, bgp_show_type_normal, NULL, 0, use_json(argc, argv)); } DEFUN(show_ip_bgp_l2vpn_evpn_rd, show_ip_bgp_l2vpn_evpn_rd_cmd, "show [ip] bgp l2vpn evpn rd ASN:NN_OR_IP-ADDRESS:NN [json]", SHOW_STR IP_STR BGP_STR L2VPN_HELP_STR EVPN_HELP_STR "Display information for a route distinguisher\n" "VPN Route Distinguisher\n" JSON_STR) { int idx_ext_community = 0; int ret; struct prefix_rd prd; argv_find(argv, argc, "ASN:NN_OR_IP-ADDRESS:NN", &idx_ext_community); ret = str2prefix_rd(argv[idx_ext_community]->arg, &prd); if (!ret) { vty_out(vty, "%% Malformed Route Distinguisher\n"); return CMD_WARNING; } return bgp_show_ethernet_vpn(vty, &prd, bgp_show_type_normal, NULL, 0, use_json(argc, argv)); } DEFUN(show_ip_bgp_l2vpn_evpn_all_tags, show_ip_bgp_l2vpn_evpn_all_tags_cmd, "show [ip] bgp l2vpn evpn all tags", SHOW_STR IP_STR BGP_STR L2VPN_HELP_STR EVPN_HELP_STR "Display information about all EVPN NLRIs\n" "Display BGP tags for prefixes\n") { return bgp_show_ethernet_vpn(vty, NULL, bgp_show_type_normal, NULL, 1, 0); } DEFUN(show_ip_bgp_l2vpn_evpn_rd_tags, show_ip_bgp_l2vpn_evpn_rd_tags_cmd, "show [ip] bgp l2vpn evpn rd ASN:NN_OR_IP-ADDRESS:NN tags", SHOW_STR IP_STR BGP_STR L2VPN_HELP_STR EVPN_HELP_STR "Display information for a route distinguisher\n" "VPN Route Distinguisher\n" "Display BGP tags for prefixes\n") { int idx_ext_community = 0; int ret; struct prefix_rd prd; argv_find(argv, argc, "ASN:NN_OR_IP-ADDRESS:NN", &idx_ext_community); ret = str2prefix_rd(argv[idx_ext_community]->arg, &prd); if (!ret) { vty_out(vty, "%% Malformed Route Distinguisher\n"); return CMD_WARNING; } return bgp_show_ethernet_vpn(vty, &prd, bgp_show_type_normal, NULL, 1, 0); } DEFUN(show_ip_bgp_l2vpn_evpn_neighbor_routes, show_ip_bgp_l2vpn_evpn_neighbor_routes_cmd, "show [ip] bgp l2vpn evpn neighbors routes [json]", SHOW_STR IP_STR BGP_STR L2VPN_HELP_STR EVPN_HELP_STR "Detailed information on TCP and BGP neighbor connections\n" "IPv4 Neighbor to display information about\n" "IPv6 Neighbor to display information about\n" "Neighbor on BGP configured interface\n" "Display routes learned from neighbor\n" JSON_STR) { int idx = 0; struct peer *peer; char *peerstr = NULL; bool uj = use_json(argc, argv); afi_t afi = AFI_L2VPN; safi_t safi = SAFI_EVPN; struct bgp *bgp = NULL; bgp_vty_find_and_parse_afi_safi_bgp(vty, argv, argc, &idx, &afi, &safi, &bgp, uj); if (!idx) { vty_out(vty, "No index\n"); return CMD_WARNING; } /* neighbors */ argv_find(argv, argc, "neighbors", &idx); peerstr = argv[++idx]->arg; peer = peer_lookup_in_view(vty, bgp, peerstr, uj); if (!peer) { if (uj) { json_object *json_no = NULL; json_no = json_object_new_object(); json_object_string_add(json_no, "warning", "Malformed address"); vty_out(vty, "%s\n", json_object_to_json_string(json_no)); json_object_free(json_no); } else vty_out(vty, "Malformed address: %s\n", argv[idx]->arg); return CMD_WARNING; } if (!peer || !peer->afc[AFI_L2VPN][SAFI_EVPN]) { if (uj) { json_object *json_no = NULL; json_no = json_object_new_object(); json_object_string_add( json_no, "warning", "No such neighbor or address family"); vty_out(vty, "%s\n", json_object_to_json_string(json_no)); json_object_free(json_no); } else vty_out(vty, "%% No such neighbor or address family\n"); return CMD_WARNING; } return bgp_show_ethernet_vpn(vty, NULL, bgp_show_type_neighbor, peer, 0, uj); } DEFUN(show_ip_bgp_l2vpn_evpn_rd_neighbor_routes, show_ip_bgp_l2vpn_evpn_rd_neighbor_routes_cmd, "show [ip] bgp l2vpn evpn rd ASN:NN_OR_IP-ADDRESS:NN neighbors routes [json]", SHOW_STR IP_STR BGP_STR L2VPN_HELP_STR EVPN_HELP_STR "Display information for a route distinguisher\n" "VPN Route Distinguisher\n" "Detailed information on TCP and BGP neighbor connections\n" "IPv4 Neighbor to display information about\n" "IPv6 Neighbor to display information about\n" "Neighbor on BGP configured interface\n" "Display routes learned from neighbor\n" JSON_STR) { int idx_ext_community = 0; int idx = 0; int ret; struct peer *peer; char *peerstr = NULL; struct prefix_rd prd; bool uj = use_json(argc, argv); afi_t afi = AFI_L2VPN; safi_t safi = SAFI_EVPN; struct bgp *bgp = NULL; bgp_vty_find_and_parse_afi_safi_bgp(vty, argv, argc, &idx, &afi, &safi, &bgp, uj); if (!idx) { vty_out(vty, "No index\n"); return CMD_WARNING; } argv_find(argv, argc, "ASN:NN_OR_IP-ADDRESS:NN", &idx_ext_community); ret = str2prefix_rd(argv[idx_ext_community]->arg, &prd); if (!ret) { if (uj) { json_object *json_no = NULL; json_no = json_object_new_object(); json_object_string_add(json_no, "warning", "Malformed Route Distinguisher"); vty_out(vty, "%s\n", json_object_to_json_string(json_no)); json_object_free(json_no); } else vty_out(vty, "%% Malformed Route Distinguisher\n"); return CMD_WARNING; } /* neighbors */ argv_find(argv, argc, "neighbors", &idx); peerstr = argv[++idx]->arg; peer = peer_lookup_in_view(vty, bgp, peerstr, uj); if (!peer) { if (uj) { json_object *json_no = NULL; json_no = json_object_new_object(); json_object_string_add(json_no, "warning", "Malformed address"); vty_out(vty, "%s\n", json_object_to_json_string(json_no)); json_object_free(json_no); } else vty_out(vty, "Malformed address: %s\n", argv[idx]->arg); return CMD_WARNING; } if (!peer || !peer->afc[AFI_L2VPN][SAFI_EVPN]) { if (uj) { json_object *json_no = NULL; json_no = json_object_new_object(); json_object_string_add( json_no, "warning", "No such neighbor or address family"); vty_out(vty, "%s\n", json_object_to_json_string(json_no)); json_object_free(json_no); } else vty_out(vty, "%% No such neighbor or address family\n"); return CMD_WARNING; } return bgp_show_ethernet_vpn(vty, &prd, bgp_show_type_neighbor, peer, 0, uj); } DEFUN(show_ip_bgp_l2vpn_evpn_neighbor_advertised_routes, show_ip_bgp_l2vpn_evpn_neighbor_advertised_routes_cmd, "show [ip] bgp l2vpn evpn neighbors advertised-routes [json]", SHOW_STR IP_STR BGP_STR L2VPN_HELP_STR EVPN_HELP_STR "Detailed information on TCP and BGP neighbor connections\n" "IPv4 Neighbor to display information about\n" "IPv6 Neighbor to display information about\n" "Neighbor on BGP configured interface\n" "Display the routes advertised to a BGP neighbor\n" JSON_STR) { int idx = 0; struct peer *peer; bool uj = use_json(argc, argv); struct bgp *bgp = NULL; afi_t afi = AFI_L2VPN; safi_t safi = SAFI_EVPN; char *peerstr = NULL; if (uj) argc--; bgp_vty_find_and_parse_afi_safi_bgp(vty, argv, argc, &idx, &afi, &safi, &bgp, uj); if (!idx) { vty_out(vty, "No index\n"); return CMD_WARNING; } /* neighbors */ argv_find(argv, argc, "neighbors", &idx); peerstr = argv[++idx]->arg; peer = peer_lookup_in_view(vty, bgp, peerstr, uj); if (!peer) { if (uj) { json_object *json_no = NULL; json_no = json_object_new_object(); json_object_string_add(json_no, "warning", "Malformed address"); vty_out(vty, "%s\n", json_object_to_json_string(json_no)); json_object_free(json_no); } else vty_out(vty, "Malformed address: %s\n", argv[idx]->arg); return CMD_WARNING; } if (!peer || !peer->afc[AFI_L2VPN][SAFI_EVPN]) { if (uj) { json_object *json_no = NULL; json_no = json_object_new_object(); json_object_string_add( json_no, "warning", "No such neighbor or address family"); vty_out(vty, "%s\n", json_object_to_json_string(json_no)); json_object_free(json_no); } else vty_out(vty, "%% No such neighbor or address family\n"); return CMD_WARNING; } return show_adj_route_vpn(vty, peer, NULL, AFI_L2VPN, SAFI_EVPN, uj); } DEFUN(show_ip_bgp_l2vpn_evpn_rd_neighbor_advertised_routes, show_ip_bgp_l2vpn_evpn_rd_neighbor_advertised_routes_cmd, "show [ip] bgp l2vpn evpn rd ASN:NN_OR_IP-ADDRESS:NN neighbors advertised-routes [json]", SHOW_STR IP_STR BGP_STR L2VPN_HELP_STR EVPN_HELP_STR "Display information for a route distinguisher\n" "VPN Route Distinguisher\n" "Detailed information on TCP and BGP neighbor connections\n" "IPv4 Neighbor to display information about\n" "IPv6 Neighbor to display information about\n" "Neighbor on BGP configured interface\n" "Display the routes advertised to a BGP neighbor\n" JSON_STR) { int idx_ext_community = 0; int idx = 0; int ret; struct peer *peer; struct prefix_rd prd; struct bgp *bgp = NULL; bool uj = use_json(argc, argv); char *peerstr = NULL; afi_t afi = AFI_L2VPN; safi_t safi = SAFI_EVPN; if (uj) argc--; if (uj) argc--; bgp_vty_find_and_parse_afi_safi_bgp(vty, argv, argc, &idx, &afi, &safi, &bgp, uj); if (!idx) { vty_out(vty, "No index\n"); return CMD_WARNING; } argv_find(argv, argc, "ASN:NN_OR_IP-ADDRESS:NN", &idx_ext_community); /* neighbors */ argv_find(argv, argc, "neighbors", &idx); peerstr = argv[++idx]->arg; peer = peer_lookup_in_view(vty, bgp, peerstr, uj); if (!peer) { if (uj) { json_object *json_no = NULL; json_no = json_object_new_object(); json_object_string_add(json_no, "warning", "Malformed address"); vty_out(vty, "%s\n", json_object_to_json_string(json_no)); json_object_free(json_no); } else vty_out(vty, "Malformed address: %s\n", argv[idx]->arg); return CMD_WARNING; } if (!peer || !peer->afc[AFI_L2VPN][SAFI_EVPN]) { if (uj) { json_object *json_no = NULL; json_no = json_object_new_object(); json_object_string_add( json_no, "warning", "No such neighbor or address family"); vty_out(vty, "%s\n", json_object_to_json_string(json_no)); json_object_free(json_no); } else vty_out(vty, "%% No such neighbor or address family\n"); return CMD_WARNING; } ret = str2prefix_rd(argv[idx_ext_community]->arg, &prd); if (!ret) { if (uj) { json_object *json_no = NULL; json_no = json_object_new_object(); json_object_string_add(json_no, "warning", "Malformed Route Distinguisher"); vty_out(vty, "%s\n", json_object_to_json_string(json_no)); json_object_free(json_no); } else vty_out(vty, "%% Malformed Route Distinguisher\n"); return CMD_WARNING; } return show_adj_route_vpn(vty, peer, &prd, AFI_L2VPN, SAFI_EVPN, uj); } DEFUN(show_ip_bgp_l2vpn_evpn_all_overlay, show_ip_bgp_l2vpn_evpn_all_overlay_cmd, "show [ip] bgp l2vpn evpn all overlay [json]", SHOW_STR IP_STR BGP_STR L2VPN_HELP_STR EVPN_HELP_STR "Display information about all EVPN NLRIs\n" "Display BGP Overlay Information for prefixes\n" JSON_STR) { return bgp_show_ethernet_vpn(vty, NULL, bgp_show_type_normal, NULL, SHOW_DISPLAY_OVERLAY, use_json(argc, argv)); } DEFUN(show_ip_bgp_evpn_rd_overlay, show_ip_bgp_evpn_rd_overlay_cmd, "show [ip] bgp l2vpn evpn rd ASN:NN_OR_IP-ADDRESS:NN overlay", SHOW_STR IP_STR BGP_STR L2VPN_HELP_STR EVPN_HELP_STR "Display information for a route distinguisher\n" "VPN Route Distinguisher\n" "Display BGP Overlay Information for prefixes\n") { int idx_ext_community = 0; int ret; struct prefix_rd prd; argv_find(argv, argc, "ASN:NN_OR_IP-ADDRESS:NN", &idx_ext_community); ret = str2prefix_rd(argv[idx_ext_community]->arg, &prd); if (!ret) { vty_out(vty, "%% Malformed Route Distinguisher\n"); return CMD_WARNING; } return bgp_show_ethernet_vpn(vty, &prd, bgp_show_type_normal, NULL, SHOW_DISPLAY_OVERLAY, use_json(argc, argv)); } /* For testing purpose, static route of EVPN RT-5. */ DEFUN(evpnrt5_network, evpnrt5_network_cmd, "network rd ASN:NN_OR_IP-ADDRESS:NN ethtag WORD label WORD esi WORD gwip routermac WORD [route-map WORD]", "Specify a network to announce via BGP\n" "IP prefix\n" "IPv6 prefix\n" "Specify Route Distinguisher\n" "VPN Route Distinguisher\n" "Ethernet Tag\n" "Ethernet Tag Value\n" "BGP label\n" "label value\n" "Ethernet Segment Identifier\n" "ESI value ( 00:11:22:33:44:55:66:77:88:99 format) \n" "Gateway IP\n" "Gateway IP ( A.B.C.D )\n" "Gateway IPv6 ( X:X::X:X )\n" "Router Mac Ext Comm\n" "Router Mac address Value ( aa:bb:cc:dd:ee:ff format)\n" "Route-map to modify the attributes\n" "Name of the route map\n") { int idx_ipv4_prefixlen = 1; int idx_route_distinguisher = 3; int idx_label = 7; int idx_esi = 9; int idx_gwip = 11; int idx_ethtag = 5; int idx_routermac = 13; return bgp_static_set_safi( AFI_L2VPN, SAFI_EVPN, vty, argv[idx_ipv4_prefixlen]->arg, argv[idx_route_distinguisher]->arg, argv[idx_label]->arg, NULL, BGP_EVPN_IP_PREFIX_ROUTE, argv[idx_esi]->arg, argv[idx_gwip]->arg, argv[idx_ethtag]->arg, argv[idx_routermac]->arg); } /* For testing purpose, static route of EVPN RT-5. */ DEFUN(no_evpnrt5_network, no_evpnrt5_network_cmd, "no network rd ASN:NN_OR_IP-ADDRESS:NN ethtag WORD label WORD esi WORD gwip ", NO_STR "Specify a network to announce via BGP\n" "IP prefix\n" "IPv6 prefix\n" "Specify Route Distinguisher\n" "VPN Route Distinguisher\n" "Ethernet Tag\n" "Ethernet Tag Value\n" "BGP label\n" "label value\n" "Ethernet Segment Identifier\n" "ESI value ( 00:11:22:33:44:55:66:77:88:99 format) \n" "Gateway IP\n" "Gateway IP ( A.B.C.D )\n" "Gateway IPv6 ( X:X::X:X )\n") { int idx_ipv4_prefixlen = 2; int idx_ext_community = 4; int idx_label = 8; int idx_ethtag = 6; int idx_esi = 10; int idx_gwip = 12; return bgp_static_unset_safi( AFI_L2VPN, SAFI_EVPN, vty, argv[idx_ipv4_prefixlen]->arg, argv[idx_ext_community]->arg, argv[idx_label]->arg, BGP_EVPN_IP_PREFIX_ROUTE, argv[idx_esi]->arg, argv[idx_gwip]->arg, argv[idx_ethtag]->arg); } static void evpn_import_rt_delete_auto(struct bgp *bgp, struct bgpevpn *vpn) { evpn_rt_delete_auto(bgp, vpn->vni, vpn->import_rtl); } static void evpn_export_rt_delete_auto(struct bgp *bgp, struct bgpevpn *vpn) { evpn_rt_delete_auto(bgp, vpn->vni, vpn->export_rtl); } /* * Configure the Import RTs for a VNI (vty handler). Caller expected to * check that this is a change. */ static void evpn_configure_import_rt(struct bgp *bgp, struct bgpevpn *vpn, struct ecommunity *ecomadd) { /* If the VNI is "live", we need to uninstall routes using the current * import RT(s) first before we update the import RT, and subsequently * install routes. */ if (is_vni_live(vpn)) bgp_evpn_uninstall_routes(bgp, vpn); /* Cleanup the RT to VNI mapping and get rid of existing import RT. */ bgp_evpn_unmap_vni_from_its_rts(bgp, vpn); /* If the auto route-target is in use we must remove it */ evpn_import_rt_delete_auto(bgp, vpn); /* Add new RT and rebuild the RT to VNI mapping */ listnode_add_sort(vpn->import_rtl, ecomadd); SET_FLAG(vpn->flags, VNI_FLAG_IMPRT_CFGD); bgp_evpn_map_vni_to_its_rts(bgp, vpn); /* Install routes that match new import RT */ if (is_vni_live(vpn)) bgp_evpn_install_routes(bgp, vpn); } /* * Unconfigure Import RT(s) for a VNI (vty handler). */ static void evpn_unconfigure_import_rt(struct bgp *bgp, struct bgpevpn *vpn, struct ecommunity *ecomdel) { struct listnode *node, *nnode, *node_to_del; struct ecommunity *ecom; /* Along the lines of "configure" except we have to reset to the * automatic value. */ if (is_vni_live(vpn)) bgp_evpn_uninstall_routes(bgp, vpn); /* Cleanup the RT to VNI mapping and get rid of existing import RT. */ bgp_evpn_unmap_vni_from_its_rts(bgp, vpn); /* Delete all import RTs */ if (ecomdel == NULL) { for (ALL_LIST_ELEMENTS(vpn->import_rtl, node, nnode, ecom)) { ecommunity_free(&ecom); list_delete_node(vpn->import_rtl, node); } } /* Delete a specific import RT */ else { node_to_del = NULL; for (ALL_LIST_ELEMENTS(vpn->import_rtl, node, nnode, ecom)) { if (ecommunity_match(ecom, ecomdel)) { ecommunity_free(&ecom); node_to_del = node; break; } } if (node_to_del) list_delete_node(vpn->import_rtl, node_to_del); } assert(vpn->import_rtl); /* Reset to auto RT - this also rebuilds the RT to VNI mapping */ if (list_isempty(vpn->import_rtl)) { UNSET_FLAG(vpn->flags, VNI_FLAG_IMPRT_CFGD); bgp_evpn_derive_auto_rt_import(bgp, vpn); } /* Rebuild the RT to VNI mapping */ else bgp_evpn_map_vni_to_its_rts(bgp, vpn); /* Install routes that match new import RT */ if (is_vni_live(vpn)) bgp_evpn_install_routes(bgp, vpn); } /* * Configure the Export RT for a VNI (vty handler). Caller expected to * check that this is a change. Note that only a single export RT is * allowed for a VNI and any change to configuration is implemented as * a "replace" (similar to other configuration). */ static void evpn_configure_export_rt(struct bgp *bgp, struct bgpevpn *vpn, struct ecommunity *ecomadd) { /* If the auto route-target is in use we must remove it */ evpn_export_rt_delete_auto(bgp, vpn); listnode_add_sort(vpn->export_rtl, ecomadd); SET_FLAG(vpn->flags, VNI_FLAG_EXPRT_CFGD); if (is_vni_live(vpn)) bgp_evpn_handle_export_rt_change(bgp, vpn); } /* * Unconfigure the Export RT for a VNI (vty handler) */ static void evpn_unconfigure_export_rt(struct bgp *bgp, struct bgpevpn *vpn, struct ecommunity *ecomdel) { struct listnode *node, *nnode, *node_to_del; struct ecommunity *ecom; /* Delete all export RTs */ if (ecomdel == NULL) { /* Reset to default and process all routes. */ for (ALL_LIST_ELEMENTS(vpn->export_rtl, node, nnode, ecom)) { ecommunity_free(&ecom); list_delete_node(vpn->export_rtl, node); } } /* Delete a specific export RT */ else { node_to_del = NULL; for (ALL_LIST_ELEMENTS(vpn->export_rtl, node, nnode, ecom)) { if (ecommunity_match(ecom, ecomdel)) { ecommunity_free(&ecom); node_to_del = node; break; } } if (node_to_del) list_delete_node(vpn->export_rtl, node_to_del); } assert(vpn->export_rtl); if (list_isempty(vpn->export_rtl)) { UNSET_FLAG(vpn->flags, VNI_FLAG_EXPRT_CFGD); bgp_evpn_derive_auto_rt_export(bgp, vpn); } if (is_vni_live(vpn)) bgp_evpn_handle_export_rt_change(bgp, vpn); } /* * Configure RD for VRF */ static void evpn_configure_vrf_rd(struct bgp *bgp_vrf, struct prefix_rd *rd) { /* If we have already advertise type-5 routes with a diffrent RD, we * have to delete and withdraw them firs */ bgp_evpn_handle_vrf_rd_change(bgp_vrf, 1); /* update RD */ memcpy(&bgp_vrf->vrf_prd, rd, sizeof(struct prefix_rd)); SET_FLAG(bgp_vrf->vrf_flags, BGP_VRF_RD_CFGD); /* We have a new RD for VRF. * Advertise all type-5 routes again with the new RD */ bgp_evpn_handle_vrf_rd_change(bgp_vrf, 0); } /* * Unconfigure RD for VRF */ static void evpn_unconfigure_vrf_rd(struct bgp *bgp_vrf) { /* If we have already advertise type-5 routes with a diffrent RD, we * have to delete and withdraw them firs */ bgp_evpn_handle_vrf_rd_change(bgp_vrf, 1); /* fall back to default RD */ bgp_evpn_derive_auto_rd_for_vrf(bgp_vrf); UNSET_FLAG(bgp_vrf->vrf_flags, BGP_VRF_RD_CFGD); /* We have a new RD for VRF. * Advertise all type-5 routes again with the new RD */ bgp_evpn_handle_vrf_rd_change(bgp_vrf, 0); } /* * Configure RD for a VNI (vty handler) */ static void evpn_configure_rd(struct bgp *bgp, struct bgpevpn *vpn, struct prefix_rd *rd) { /* If the VNI is "live", we need to delete and withdraw this VNI's * local routes with the prior RD first. Then, after updating RD, * need to re-advertise. */ if (is_vni_live(vpn)) bgp_evpn_handle_rd_change(bgp, vpn, 1); /* update RD */ memcpy(&vpn->prd, rd, sizeof(struct prefix_rd)); SET_FLAG(vpn->flags, VNI_FLAG_RD_CFGD); if (is_vni_live(vpn)) bgp_evpn_handle_rd_change(bgp, vpn, 0); } /* * Unconfigure RD for a VNI (vty handler) */ static void evpn_unconfigure_rd(struct bgp *bgp, struct bgpevpn *vpn) { /* If the VNI is "live", we need to delete and withdraw this VNI's * local routes with the prior RD first. Then, after resetting RD * to automatic value, need to re-advertise. */ if (is_vni_live(vpn)) bgp_evpn_handle_rd_change(bgp, vpn, 1); /* reset RD to default */ bgp_evpn_derive_auto_rd(bgp, vpn); if (is_vni_live(vpn)) bgp_evpn_handle_rd_change(bgp, vpn, 0); } /* * Create VNI, if not already present (VTY handler). Mark as configured. */ static struct bgpevpn *evpn_create_update_vni(struct bgp *bgp, vni_t vni) { struct bgpevpn *vpn; struct in_addr mcast_grp = {INADDR_ANY}; if (!bgp->vnihash) return NULL; vpn = bgp_evpn_lookup_vni(bgp, vni); if (!vpn) { /* Check if this L2VNI is already configured as L3VNI */ if (bgp_evpn_lookup_l3vni_l2vni_table(vni)) { flog_err( EC_BGP_VNI, "%u: Failed to create L2VNI %u, it is configured as L3VNI", bgp->vrf_id, vni); return NULL; } /* tenant vrf will be updated when we get local_vni_add from * zebra */ vpn = bgp_evpn_new(bgp, vni, bgp->router_id, 0, mcast_grp); if (!vpn) { flog_err( EC_BGP_VNI, "%u: Failed to allocate VNI entry for VNI %u - at Config", bgp->vrf_id, vni); return NULL; } } /* Mark as configured. */ SET_FLAG(vpn->flags, VNI_FLAG_CFGD); return vpn; } /* * Delete VNI. If VNI does not exist in the system (i.e., just * configuration), all that is needed is to free it. Otherwise, * any parameters configured for the VNI need to be reset (with * appropriate action) and the VNI marked as unconfigured; the * VNI will continue to exist, purely as a "learnt" entity. */ static int evpn_delete_vni(struct bgp *bgp, struct bgpevpn *vpn) { assert(bgp->vnihash); if (!is_vni_live(vpn)) { bgp_evpn_free(bgp, vpn); return 0; } /* We need to take the unconfigure action for each parameter of this VNI * that is configured. Some optimization is possible, but not worth the * additional code for an operation that should be pretty rare. */ UNSET_FLAG(vpn->flags, VNI_FLAG_CFGD); /* First, deal with the export side - RD and export RT changes. */ if (is_rd_configured(vpn)) evpn_unconfigure_rd(bgp, vpn); if (is_export_rt_configured(vpn)) evpn_unconfigure_export_rt(bgp, vpn, NULL); /* Next, deal with the import side. */ if (is_import_rt_configured(vpn)) evpn_unconfigure_import_rt(bgp, vpn, NULL); return 0; } /* * Display import RT mapping to VRFs (vty handler) * bgp_evpn: evpn bgp instance */ static void evpn_show_vrf_import_rts(struct vty *vty, struct bgp *bgp_evpn, json_object *json) { void *args[2]; args[0] = vty; args[1] = json; hash_iterate(bgp_evpn->vrf_import_rt_hash, (void (*)(struct hash_bucket *, void *))show_vrf_import_rt_entry, args); } /* * Display import RT mapping to VNIs (vty handler) */ static void evpn_show_import_rts(struct vty *vty, struct bgp *bgp, json_object *json) { void *args[2]; args[0] = vty; args[1] = json; hash_iterate( bgp->import_rt_hash, (void (*)(struct hash_bucket *, void *))show_import_rt_entry, args); } /* * Display EVPN routes for all VNIs - vty handler. */ static void evpn_show_routes_vni_all(struct vty *vty, struct bgp *bgp, struct in_addr vtep_ip, json_object *json, int detail) { uint32_t num_vnis; struct vni_walk_ctx wctx; num_vnis = hashcount(bgp->vnihash); if (!num_vnis) return; memset(&wctx, 0, sizeof(struct vni_walk_ctx)); wctx.bgp = bgp; wctx.vty = vty; wctx.vtep_ip = vtep_ip; wctx.json = json; wctx.detail = detail; hash_iterate(bgp->vnihash, (void (*)(struct hash_bucket *, void *))show_vni_routes_hash, &wctx); } /* * Display EVPN routes for a VNI -- for specific type-3 route (vty handler). */ static void evpn_show_route_vni_multicast(struct vty *vty, struct bgp *bgp, vni_t vni, struct in_addr orig_ip, json_object *json) { struct bgpevpn *vpn; struct prefix_evpn p; struct bgp_node *rn; struct bgp_path_info *pi; uint32_t path_cnt = 0; afi_t afi; safi_t safi; json_object *json_paths = NULL; afi = AFI_L2VPN; safi = SAFI_EVPN; /* Locate VNI. */ vpn = bgp_evpn_lookup_vni(bgp, vni); if (!vpn) { vty_out(vty, "VNI not found\n"); return; } /* See if route exists. */ build_evpn_type3_prefix(&p, orig_ip); rn = bgp_node_lookup(vpn->route_table, (struct prefix *)&p); if (!rn || !bgp_node_has_bgp_path_info_data(rn)) { if (!json) vty_out(vty, "%% Network not in table\n"); return; } if (json) json_paths = json_object_new_array(); /* Prefix and num paths displayed once per prefix. */ route_vty_out_detail_header(vty, bgp, rn, NULL, afi, safi, json); /* Display each path for this prefix. */ for (pi = bgp_node_get_bgp_path_info(rn); pi; pi = pi->next) { json_object *json_path = NULL; if (json) json_path = json_object_new_array(); route_vty_out_detail(vty, bgp, rn, pi, afi, safi, json_path); if (json) json_object_array_add(json_paths, json_path); path_cnt++; } if (json) { if (path_cnt) json_object_object_add(json, "paths", json_paths); json_object_int_add(json, "numPaths", path_cnt); } else { vty_out(vty, "\nDisplayed %u paths for requested prefix\n", path_cnt); } } /* * Display EVPN routes for a VNI -- for specific MAC and/or IP (vty handler). * By definition, only matching type-2 route will be displayed. */ static void evpn_show_route_vni_macip(struct vty *vty, struct bgp *bgp, vni_t vni, struct ethaddr *mac, struct ipaddr *ip, json_object *json) { struct bgpevpn *vpn; struct prefix_evpn p; struct bgp_node *rn; struct bgp_path_info *pi; uint32_t path_cnt = 0; afi_t afi; safi_t safi; json_object *json_paths = NULL; afi = AFI_L2VPN; safi = SAFI_EVPN; /* Locate VNI. */ vpn = bgp_evpn_lookup_vni(bgp, vni); if (!vpn) { if (!json) vty_out(vty, "VNI not found\n"); return; } /* See if route exists. Look for both non-sticky and sticky. */ build_evpn_type2_prefix(&p, mac, ip); rn = bgp_node_lookup(vpn->route_table, (struct prefix *)&p); if (!rn || !bgp_node_has_bgp_path_info_data(rn)) { if (!json) vty_out(vty, "%% Network not in table\n"); return; } if (json) json_paths = json_object_new_array(); /* Prefix and num paths displayed once per prefix. */ route_vty_out_detail_header(vty, bgp, rn, NULL, afi, safi, json); /* Display each path for this prefix. */ for (pi = bgp_node_get_bgp_path_info(rn); pi; pi = pi->next) { json_object *json_path = NULL; if (json) json_path = json_object_new_array(); route_vty_out_detail(vty, bgp, rn, pi, afi, safi, json_path); if (json) json_object_array_add(json_paths, json_path); path_cnt++; } if (json) { if (path_cnt) json_object_object_add(json, "paths", json_paths); json_object_int_add(json, "numPaths", path_cnt); } else { vty_out(vty, "\nDisplayed %u paths for requested prefix\n", path_cnt); } } /* Disaplay EVPN routes for a ESI - VTY handler */ static void evpn_show_routes_esi(struct vty *vty, struct bgp *bgp, esi_t *esi, json_object *json) { struct evpnes *es = NULL; /* locate the ES */ es = bgp_evpn_lookup_es(bgp, esi); if (!es) { if (!json) vty_out(vty, "ESI not found\n"); return; } show_esi_routes(bgp, es, vty, json); } /* * Display EVPN routes for a VNI - vty handler. * If 'type' is non-zero, only routes matching that type are shown. * If the vtep_ip is non zero, only routes behind that vtep are shown */ static void evpn_show_routes_vni(struct vty *vty, struct bgp *bgp, vni_t vni, int type, struct in_addr vtep_ip, json_object *json) { struct bgpevpn *vpn; /* Locate VNI. */ vpn = bgp_evpn_lookup_vni(bgp, vni); if (!vpn) { if (!json) vty_out(vty, "VNI not found\n"); return; } /* Walk this VNI's route table and display appropriate routes. */ show_vni_routes(bgp, vpn, type, vty, vtep_ip, json, 0); } /* * Display BGP EVPN routing table -- for specific RD and MAC and/or * IP (vty handler). By definition, only matching type-2 route will be * displayed. */ static void evpn_show_route_rd_macip(struct vty *vty, struct bgp *bgp, struct prefix_rd *prd, struct ethaddr *mac, struct ipaddr *ip, json_object *json) { struct prefix_evpn p; struct bgp_node *rn; struct bgp_path_info *pi; afi_t afi; safi_t safi; uint32_t path_cnt = 0; json_object *json_paths = NULL; char prefix_str[BUFSIZ]; afi = AFI_L2VPN; safi = SAFI_EVPN; /* See if route exists. Look for both non-sticky and sticky. */ build_evpn_type2_prefix(&p, mac, ip); rn = bgp_afi_node_lookup(bgp->rib[afi][safi], afi, safi, (struct prefix *)&p, prd); if (!rn || !bgp_node_has_bgp_path_info_data(rn)) { if (!json) vty_out(vty, "%% Network not in table\n"); return; } bgp_evpn_route2str((struct prefix_evpn *)&p, prefix_str, sizeof(prefix_str)); /* Prefix and num paths displayed once per prefix. */ route_vty_out_detail_header(vty, bgp, rn, prd, afi, safi, json); if (json) json_paths = json_object_new_array(); /* Display each path for this prefix. */ for (pi = bgp_node_get_bgp_path_info(rn); pi; pi = pi->next) { json_object *json_path = NULL; if (json) json_path = json_object_new_array(); route_vty_out_detail(vty, bgp, rn, pi, afi, safi, json_path); if (json) json_object_array_add(json_paths, json_path); path_cnt++; } if (json && path_cnt) { if (path_cnt) json_object_object_add(json, prefix_str, json_paths); json_object_int_add(json, "numPaths", path_cnt); } else { vty_out(vty, "\nDisplayed %u paths for requested prefix\n", path_cnt); } } /* * Display BGP EVPN routing table -- for specific RD (vty handler) * If 'type' is non-zero, only routes matching that type are shown. */ static void evpn_show_route_rd(struct vty *vty, struct bgp *bgp, struct prefix_rd *prd, int type, json_object *json) { struct bgp_node *rd_rn; struct bgp_table *table; struct bgp_node *rn; struct bgp_path_info *pi; int rd_header = 1; afi_t afi; safi_t safi; uint32_t prefix_cnt, path_cnt; char rd_str[RD_ADDRSTRLEN]; json_object *json_rd = NULL; int add_rd_to_json = 0; afi = AFI_L2VPN; safi = SAFI_EVPN; prefix_cnt = path_cnt = 0; prefix_rd2str((struct prefix_rd *)prd, rd_str, sizeof(rd_str)); rd_rn = bgp_node_lookup(bgp->rib[afi][safi], (struct prefix *)prd); if (!rd_rn) return; table = bgp_node_get_bgp_table_info(rd_rn); if (table == NULL) return; if (json) { json_rd = json_object_new_object(); json_object_string_add(json_rd, "rd", rd_str); } /* Display all prefixes with this RD. */ for (rn = bgp_table_top(table); rn; rn = bgp_route_next(rn)) { struct prefix_evpn *evp = (struct prefix_evpn *)&rn->p; json_object *json_prefix = NULL; json_object *json_paths = NULL; char prefix_str[BUFSIZ]; int add_prefix_to_json = 0; bgp_evpn_route2str((struct prefix_evpn *)&rn->p, prefix_str, sizeof(prefix_str)); if (type && evp->prefix.route_type != type) continue; if (json) json_prefix = json_object_new_object(); pi = bgp_node_get_bgp_path_info(rn); if (pi) { /* RD header and legend - once overall. */ if (rd_header && !json) { vty_out(vty, "EVPN type-2 prefix: [2]:[EthTag]:[MAClen]:[MAC]\n"); vty_out(vty, "EVPN type-3 prefix: [3]:[EthTag]:[IPlen]:[OrigIP]\n"); vty_out(vty, "EVPN type-5 prefix: [5]:[EthTag]:[IPlen]:[IP]\n\n"); rd_header = 0; } /* Prefix and num paths displayed once per prefix. */ route_vty_out_detail_header(vty, bgp, rn, prd, afi, safi, json_prefix); prefix_cnt++; } if (json) json_paths = json_object_new_array(); /* Display each path for this prefix. */ for (; pi; pi = pi->next) { json_object *json_path = NULL; if (json) json_path = json_object_new_array(); route_vty_out_detail(vty, bgp, rn, pi, afi, safi, json_path); if (json) json_object_array_add(json_paths, json_path); path_cnt++; add_prefix_to_json = 1; add_rd_to_json = 1; } if (json && add_prefix_to_json) { json_object_object_add(json_prefix, "paths", json_paths); json_object_object_add(json_rd, prefix_str, json_prefix); } } if (json && add_rd_to_json) json_object_object_add(json, rd_str, json_rd); if (json) { json_object_int_add(json, "numPrefix", prefix_cnt); json_object_int_add(json, "numPaths", path_cnt); } else { if (prefix_cnt == 0) vty_out(vty, "No prefixes exist with this RD%s\n", type ? " (of requested type)" : ""); else vty_out(vty, "\nDisplayed %u prefixes (%u paths) with this RD%s\n", prefix_cnt, path_cnt, type ? " (of requested type)" : ""); } } /* * Display BGP EVPN routing table - all routes (vty handler). * If 'type' is non-zero, only routes matching that type are shown. */ static void evpn_show_all_routes(struct vty *vty, struct bgp *bgp, int type, json_object *json, int detail) { struct bgp_node *rd_rn; struct bgp_table *table; struct bgp_node *rn; struct bgp_path_info *pi; int header = detail ? 0 : 1; int rd_header; afi_t afi; safi_t safi; uint32_t prefix_cnt, path_cnt; afi = AFI_L2VPN; safi = SAFI_EVPN; prefix_cnt = path_cnt = 0; /* EVPN routing table is a 2-level table with the first level being * the RD. */ for (rd_rn = bgp_table_top(bgp->rib[afi][safi]); rd_rn; rd_rn = bgp_route_next(rd_rn)) { char rd_str[RD_ADDRSTRLEN]; json_object *json_rd = NULL; /* contains routes for an RD */ int add_rd_to_json = 0; uint64_t tbl_ver; table = bgp_node_get_bgp_table_info(rd_rn); if (table == NULL) continue; tbl_ver = table->version; prefix_rd2str((struct prefix_rd *)&rd_rn->p, rd_str, sizeof(rd_str)); if (json) { json_rd = json_object_new_object(); json_object_string_add(json_rd, "rd", rd_str); } rd_header = 1; /* Display all prefixes for an RD */ for (rn = bgp_table_top(table); rn; rn = bgp_route_next(rn)) { json_object *json_prefix = NULL; /* contains prefix under a RD */ json_object *json_paths = NULL; /* array of paths under a prefix*/ struct prefix_evpn *evp = (struct prefix_evpn *)&rn->p; char prefix_str[BUFSIZ]; int add_prefix_to_json = 0; bgp_evpn_route2str((struct prefix_evpn *)&rn->p, prefix_str, sizeof(prefix_str)); if (type && evp->prefix.route_type != type) continue; pi = bgp_node_get_bgp_path_info(rn); if (pi) { /* Overall header/legend displayed once. */ if (header) { bgp_evpn_show_route_header(vty, bgp, tbl_ver, json); if (!json) vty_out(vty, "%19s Extended Community\n" , " "); header = 0; } /* RD header - per RD. */ if (rd_header) { bgp_evpn_show_route_rd_header( vty, rd_rn, json); rd_header = 0; } prefix_cnt++; } if (json) { json_prefix = json_object_new_object(); json_paths = json_object_new_array(); json_object_string_add(json_prefix, "prefix", prefix_str); json_object_int_add(json_prefix, "prefixLen", rn->p.prefixlen); } /* Prefix and num paths displayed once per prefix. */ if (detail) route_vty_out_detail_header( vty, bgp, rn, (struct prefix_rd *)&rd_rn->p, AFI_L2VPN, SAFI_EVPN, json_prefix); /* For EVPN, the prefix is displayed for each path (to * fit in * with code that already exists). */ for (; pi; pi = pi->next) { json_object *json_path = NULL; path_cnt++; add_prefix_to_json = 1; add_rd_to_json = 1; if (json) json_path = json_object_new_array(); if (detail) { route_vty_out_detail( vty, bgp, rn, pi, AFI_L2VPN, SAFI_EVPN, json_path); } else route_vty_out(vty, &rn->p, pi, 0, SAFI_EVPN, json_path); if (json) json_object_array_add(json_paths, json_path); } if (json && add_prefix_to_json) { json_object_object_add(json_prefix, "paths", json_paths); json_object_object_add(json_rd, prefix_str, json_prefix); } } if (json && add_rd_to_json) json_object_object_add(json, rd_str, json_rd); } if (json) { json_object_int_add(json, "numPrefix", prefix_cnt); json_object_int_add(json, "numPaths", path_cnt); } else { if (prefix_cnt == 0) { vty_out(vty, "No EVPN prefixes %sexist\n", type ? "(of requested type) " : ""); } else { vty_out(vty, "\nDisplayed %u prefixes (%u paths)%s\n", prefix_cnt, path_cnt, type ? " (of requested type)" : ""); } } } /* Display specific ES */ static void evpn_show_es(struct vty *vty, struct bgp *bgp, esi_t *esi, json_object *json) { struct evpnes *es = NULL; es = bgp_evpn_lookup_es(bgp, esi); if (es) { display_es(vty, es, json); } else { if (json) { vty_out(vty, "{}\n"); } else { vty_out(vty, "ESI not found\n"); return; } } } /* Display all ESs */ static void evpn_show_all_es(struct vty *vty, struct bgp *bgp, json_object *json) { void *args[2]; if (!json) vty_out(vty, "%-30s %-6s %-21s %-15s %-6s\n", "ESI", "Type", "RD", "Originator-IP", "#VTEPs"); /* print all ESs */ args[0] = vty; args[1] = json; hash_iterate(bgp->esihash, (void (*)(struct hash_bucket *, void *))show_es_entry, args); } /* * Display specified VNI (vty handler) */ static void evpn_show_vni(struct vty *vty, struct bgp *bgp, vni_t vni, json_object *json) { uint8_t found = 0; struct bgpevpn *vpn; vpn = bgp_evpn_lookup_vni(bgp, vni); if (vpn) { found = 1; display_vni(vty, vpn, json); } else { struct bgp *bgp_temp; struct listnode *node = NULL; for (ALL_LIST_ELEMENTS_RO(bm->bgp, node, bgp_temp)) { if (bgp_temp->l3vni == vni) { found = 1; display_l3vni(vty, bgp_temp, json); } } } if (!found) { if (json) { vty_out(vty, "{}\n"); } else { vty_out(vty, "VNI not found\n"); return; } } } /* * Display a VNI (upon user query). */ static void evpn_show_all_vnis(struct vty *vty, struct bgp *bgp, json_object *json) { void *args[2]; struct bgp *bgp_temp = NULL; struct listnode *node; if (!json) { vty_out(vty, "Flags: * - Kernel\n"); vty_out(vty, " %-10s %-4s %-21s %-25s %-25s %-37s\n", "VNI", "Type", "RD", "Import RT", "Export RT", "Tenant VRF"); } /* print all L2 VNIS */ args[0] = vty; args[1] = json; hash_iterate(bgp->vnihash, (void (*)(struct hash_bucket *, void *))show_vni_entry, args); /* print all L3 VNIs */ for (ALL_LIST_ELEMENTS_RO(bm->bgp, node, bgp_temp)) show_l3vni_entry(vty, bgp_temp, json); } /* * evpn - enable advertisement of svi MAC-IP */ static void evpn_set_advertise_svi_macip(struct bgp *bgp, struct bgpevpn *vpn, uint32_t set) { if (!vpn) { if (set && bgp->evpn_info->advertise_svi_macip) return; else if (!set && !bgp->evpn_info->advertise_svi_macip) return; bgp->evpn_info->advertise_svi_macip = set; bgp_zebra_advertise_svi_macip(bgp, bgp->evpn_info->advertise_svi_macip, 0); } else { if (set && vpn->advertise_svi_macip) return; else if (!set && !vpn->advertise_svi_macip) return; vpn->advertise_svi_macip = set; bgp_zebra_advertise_svi_macip(bgp, vpn->advertise_svi_macip, vpn->vni); } } /* * evpn - enable advertisement of default g/w */ static void evpn_set_advertise_default_gw(struct bgp *bgp, struct bgpevpn *vpn) { if (!vpn) { if (bgp->advertise_gw_macip) return; bgp->advertise_gw_macip = 1; bgp_zebra_advertise_gw_macip(bgp, bgp->advertise_gw_macip, 0); } else { if (vpn->advertise_gw_macip) return; vpn->advertise_gw_macip = 1; bgp_zebra_advertise_gw_macip(bgp, vpn->advertise_gw_macip, vpn->vni); } return; } /* * evpn - disable advertisement of default g/w */ static void evpn_unset_advertise_default_gw(struct bgp *bgp, struct bgpevpn *vpn) { if (!vpn) { if (!bgp->advertise_gw_macip) return; bgp->advertise_gw_macip = 0; bgp_zebra_advertise_gw_macip(bgp, bgp->advertise_gw_macip, 0); } else { if (!vpn->advertise_gw_macip) return; vpn->advertise_gw_macip = 0; bgp_zebra_advertise_gw_macip(bgp, vpn->advertise_gw_macip, vpn->vni); } return; } /* * evpn - enable advertisement of default g/w */ static void evpn_process_default_originate_cmd(struct bgp *bgp_vrf, afi_t afi, bool add) { safi_t safi = SAFI_UNICAST; /* ipv4/ipv6 unicast */ if (add) { /* bail if we are already advertising default route */ if (evpn_default_originate_set(bgp_vrf, afi, safi)) return; if (afi == AFI_IP) SET_FLAG(bgp_vrf->af_flags[AFI_L2VPN][SAFI_EVPN], BGP_L2VPN_EVPN_DEFAULT_ORIGINATE_IPV4); else if (afi == AFI_IP6) SET_FLAG(bgp_vrf->af_flags[AFI_L2VPN][SAFI_EVPN], BGP_L2VPN_EVPN_DEFAULT_ORIGINATE_IPV6); } else { /* bail out if we havent advertised the default route */ if (!evpn_default_originate_set(bgp_vrf, afi, safi)) return; if (afi == AFI_IP) UNSET_FLAG(bgp_vrf->af_flags[AFI_L2VPN][SAFI_EVPN], BGP_L2VPN_EVPN_DEFAULT_ORIGINATE_IPV4); else if (afi == AFI_IP6) UNSET_FLAG(bgp_vrf->af_flags[AFI_L2VPN][SAFI_EVPN], BGP_L2VPN_EVPN_DEFAULT_ORIGINATE_IPV6); } bgp_evpn_install_uninstall_default_route(bgp_vrf, afi, safi, add); } /* * evpn - enable advertisement of default g/w */ static void evpn_set_advertise_subnet(struct bgp *bgp, struct bgpevpn *vpn) { if (vpn->advertise_subnet) return; vpn->advertise_subnet = 1; bgp_zebra_advertise_subnet(bgp, vpn->advertise_subnet, vpn->vni); } /* * evpn - disable advertisement of default g/w */ static void evpn_unset_advertise_subnet(struct bgp *bgp, struct bgpevpn *vpn) { if (!vpn->advertise_subnet) return; vpn->advertise_subnet = 0; bgp_zebra_advertise_subnet(bgp, vpn->advertise_subnet, vpn->vni); } /* * EVPN (VNI advertisement) enabled. Register with zebra. */ static void evpn_set_advertise_all_vni(struct bgp *bgp) { bgp->advertise_all_vni = 1; bgp_set_evpn(bgp); bgp_zebra_advertise_all_vni(bgp, bgp->advertise_all_vni); } /* * EVPN (VNI advertisement) disabled. De-register with zebra. Cleanup VNI * cache, EVPN routes (delete and withdraw from peers). */ static void evpn_unset_advertise_all_vni(struct bgp *bgp) { bgp->advertise_all_vni = 0; bgp_set_evpn(bgp_get_default()); bgp_zebra_advertise_all_vni(bgp, bgp->advertise_all_vni); bgp_evpn_cleanup_on_disable(bgp); } /* * EVPN - use RFC8365 to auto-derive RT */ static void evpn_set_advertise_autort_rfc8365(struct bgp *bgp) { bgp->advertise_autort_rfc8365 = 1; bgp_evpn_handle_autort_change(bgp); } /* * EVPN - don't use RFC8365 to auto-derive RT */ static void evpn_unset_advertise_autort_rfc8365(struct bgp *bgp) { bgp->advertise_autort_rfc8365 = 0; bgp_evpn_handle_autort_change(bgp); } static void write_vni_config(struct vty *vty, struct bgpevpn *vpn) { char buf1[RD_ADDRSTRLEN]; char *ecom_str; struct listnode *node, *nnode; struct ecommunity *ecom; if (is_vni_configured(vpn)) { vty_out(vty, " vni %d\n", vpn->vni); if (is_rd_configured(vpn)) vty_out(vty, " rd %s\n", prefix_rd2str(&vpn->prd, buf1, sizeof(buf1))); if (is_import_rt_configured(vpn)) { for (ALL_LIST_ELEMENTS(vpn->import_rtl, node, nnode, ecom)) { ecom_str = ecommunity_ecom2str( ecom, ECOMMUNITY_FORMAT_ROUTE_MAP, 0); vty_out(vty, " route-target import %s\n", ecom_str); XFREE(MTYPE_ECOMMUNITY_STR, ecom_str); } } if (is_export_rt_configured(vpn)) { for (ALL_LIST_ELEMENTS(vpn->export_rtl, node, nnode, ecom)) { ecom_str = ecommunity_ecom2str( ecom, ECOMMUNITY_FORMAT_ROUTE_MAP, 0); vty_out(vty, " route-target export %s\n", ecom_str); XFREE(MTYPE_ECOMMUNITY_STR, ecom_str); } } if (vpn->advertise_gw_macip) vty_out(vty, " advertise-default-gw\n"); if (vpn->advertise_svi_macip) vty_out(vty, " advertise-svi-ip\n"); if (vpn->advertise_subnet) vty_out(vty, " advertise-subnet\n"); vty_out(vty, " exit-vni\n"); } } #ifndef VTYSH_EXTRACT_PL #include "bgpd/bgp_evpn_vty_clippy.c" #endif DEFPY(bgp_evpn_flood_control, bgp_evpn_flood_control_cmd, "[no$no] flooding ", NO_STR "Specify handling for BUM packets\n" "Do not flood any BUM packets\n" "Flood BUM packets using head-end replication\n") { struct bgp *bgp = VTY_GET_CONTEXT(bgp); enum vxlan_flood_control flood_ctrl; if (!bgp) return CMD_WARNING; if (disable && !no) flood_ctrl = VXLAN_FLOOD_DISABLED; else if (her || no) flood_ctrl = VXLAN_FLOOD_HEAD_END_REPL; else return CMD_WARNING; if (bgp->vxlan_flood_ctrl == flood_ctrl) return CMD_SUCCESS; bgp->vxlan_flood_ctrl = flood_ctrl; bgp_evpn_flood_control_change(bgp); return CMD_SUCCESS; } DEFUN (bgp_evpn_advertise_default_gw_vni, bgp_evpn_advertise_default_gw_vni_cmd, "advertise-default-gw", "Advertise default g/w mac-ip routes in EVPN for a VNI\n") { struct bgp *bgp = VTY_GET_CONTEXT(bgp); VTY_DECLVAR_CONTEXT_SUB(bgpevpn, vpn); if (!bgp) return CMD_WARNING; evpn_set_advertise_default_gw(bgp, vpn); return CMD_SUCCESS; } DEFUN (no_bgp_evpn_advertise_default_vni_gw, no_bgp_evpn_advertise_default_gw_vni_cmd, "no advertise-default-gw", NO_STR "Withdraw default g/w mac-ip routes from EVPN for a VNI\n") { struct bgp *bgp = VTY_GET_CONTEXT(bgp); VTY_DECLVAR_CONTEXT_SUB(bgpevpn, vpn); if (!bgp) return CMD_WARNING; evpn_unset_advertise_default_gw(bgp, vpn); return CMD_SUCCESS; } DEFUN (bgp_evpn_advertise_default_gw, bgp_evpn_advertise_default_gw_cmd, "advertise-default-gw", "Advertise All default g/w mac-ip routes in EVPN\n") { struct bgp *bgp = VTY_GET_CONTEXT(bgp); if (!bgp) return CMD_WARNING; if (!EVPN_ENABLED(bgp)) { vty_out(vty, "This command is only supported under the EVPN VRF\n"); return CMD_WARNING; } evpn_set_advertise_default_gw(bgp, NULL); return CMD_SUCCESS; } DEFUN (no_bgp_evpn_advertise_default_gw, no_bgp_evpn_advertise_default_gw_cmd, "no advertise-default-gw", NO_STR "Withdraw All default g/w mac-ip routes from EVPN\n") { struct bgp *bgp = VTY_GET_CONTEXT(bgp); if (!bgp) return CMD_WARNING; if (!EVPN_ENABLED(bgp)) { vty_out(vty, "This command is only supported under the EVPN VRF\n"); return CMD_WARNING; } evpn_unset_advertise_default_gw(bgp, NULL); return CMD_SUCCESS; } DEFUN (bgp_evpn_advertise_all_vni, bgp_evpn_advertise_all_vni_cmd, "advertise-all-vni", "Advertise All local VNIs\n") { struct bgp *bgp = VTY_GET_CONTEXT(bgp); struct bgp *bgp_evpn = NULL; if (!bgp) return CMD_WARNING; bgp_evpn = bgp_get_evpn(); if (bgp_evpn && bgp_evpn != bgp) { vty_out(vty, "%% Please unconfigure EVPN in VRF %s\n", bgp_evpn->name); return CMD_WARNING_CONFIG_FAILED; } evpn_set_advertise_all_vni(bgp); return CMD_SUCCESS; } DEFUN (no_bgp_evpn_advertise_all_vni, no_bgp_evpn_advertise_all_vni_cmd, "no advertise-all-vni", NO_STR "Advertise All local VNIs\n") { struct bgp *bgp = VTY_GET_CONTEXT(bgp); if (!bgp) return CMD_WARNING; evpn_unset_advertise_all_vni(bgp); return CMD_SUCCESS; } DEFUN (bgp_evpn_advertise_autort_rfc8365, bgp_evpn_advertise_autort_rfc8365_cmd, "autort rfc8365-compatible", "Auto-derivation of RT\n" "Auto-derivation of RT using RFC8365\n") { struct bgp *bgp = VTY_GET_CONTEXT(bgp); if (!bgp) return CMD_WARNING; evpn_set_advertise_autort_rfc8365(bgp); return CMD_SUCCESS; } DEFUN (no_bgp_evpn_advertise_autort_rfc8365, no_bgp_evpn_advertise_autort_rfc8365_cmd, "no autort rfc8365-compatible", NO_STR "Auto-derivation of RT\n" "Auto-derivation of RT using RFC8365\n") { struct bgp *bgp = VTY_GET_CONTEXT(bgp); if (!bgp) return CMD_WARNING; evpn_unset_advertise_autort_rfc8365(bgp); return CMD_SUCCESS; } DEFUN (bgp_evpn_default_originate, bgp_evpn_default_originate_cmd, "default-originate ", "originate a default route\n" "ipv4 address family\n" "ipv6 address family\n") { afi_t afi = 0; int idx_afi = 0; struct bgp *bgp_vrf = VTY_GET_CONTEXT(bgp); if (!bgp_vrf) return CMD_WARNING; argv_find_and_parse_afi(argv, argc, &idx_afi, &afi); evpn_process_default_originate_cmd(bgp_vrf, afi, true); return CMD_SUCCESS; } DEFUN (no_bgp_evpn_default_originate, no_bgp_evpn_default_originate_cmd, "no default-originate ", NO_STR "withdraw a default route\n" "ipv4 address family\n" "ipv6 address family\n") { afi_t afi = 0; int idx_afi = 0; struct bgp *bgp_vrf = VTY_GET_CONTEXT(bgp); if (!bgp_vrf) return CMD_WARNING; argv_find_and_parse_afi(argv, argc, &idx_afi, &afi); evpn_process_default_originate_cmd(bgp_vrf, afi, false); return CMD_SUCCESS; } DEFPY (dup_addr_detection, dup_addr_detection_cmd, "dup-addr-detection [max-moves (2-1000)$max_moves_val time (2-1800)$time_val]", "Duplicate address detection\n" "Max allowed moves before address detected as duplicate\n" "Num of max allowed moves (2-1000) default 5\n" "Duplicate address detection time\n" "Time in seconds (2-1800) default 180\n") { struct bgp *bgp_vrf = VTY_GET_CONTEXT(bgp); if (!bgp_vrf) return CMD_WARNING; if (!EVPN_ENABLED(bgp_vrf)) { vty_out(vty, "This command is only supported under the EVPN VRF\n"); return CMD_WARNING; } bgp_vrf->evpn_info->dup_addr_detect = true; if (time_val) bgp_vrf->evpn_info->dad_time = time_val; if (max_moves_val) bgp_vrf->evpn_info->dad_max_moves = max_moves_val; bgp_zebra_dup_addr_detection(bgp_vrf); return CMD_SUCCESS; } DEFPY (dup_addr_detection_auto_recovery, dup_addr_detection_auto_recovery_cmd, "dup-addr-detection freeze ", "Duplicate address detection\n" "Duplicate address detection freeze\n" "Duplicate address detection permanent freeze\n" "Duplicate address detection freeze time (30-3600)\n") { struct bgp *bgp_vrf = VTY_GET_CONTEXT(bgp); uint32_t freeze_time = freeze_time_val; if (!bgp_vrf) return CMD_WARNING; if (!EVPN_ENABLED(bgp_vrf)) { vty_out(vty, "This command is only supported under the EVPN VRF\n"); return CMD_WARNING; } bgp_vrf->evpn_info->dup_addr_detect = true; bgp_vrf->evpn_info->dad_freeze = true; bgp_vrf->evpn_info->dad_freeze_time = freeze_time; bgp_zebra_dup_addr_detection(bgp_vrf); return CMD_SUCCESS; } DEFPY (no_dup_addr_detection, no_dup_addr_detection_cmd, "no dup-addr-detection [max-moves (2-1000)$max_moves_val time (2-1800)$time_val | freeze ]", NO_STR "Duplicate address detection\n" "Max allowed moves before address detected as duplicate\n" "Num of max allowed moves (2-1000) default 5\n" "Duplicate address detection time\n" "Time in seconds (2-1800) default 180\n" "Duplicate address detection freeze\n" "Duplicate address detection permanent freeze\n" "Duplicate address detection freeze time (30-3600)\n") { struct bgp *bgp_vrf = VTY_GET_CONTEXT(bgp); uint32_t max_moves = (uint32_t)max_moves_val; uint32_t freeze_time = (uint32_t)freeze_time_val; if (!bgp_vrf) return CMD_WARNING; if (!EVPN_ENABLED(bgp_vrf)) { vty_out(vty, "This command is only supported under the EVPN VRF\n"); return CMD_WARNING; } if (argc == 2) { if (!bgp_vrf->evpn_info->dup_addr_detect) return CMD_SUCCESS; /* Reset all parameters to default. */ bgp_vrf->evpn_info->dup_addr_detect = false; bgp_vrf->evpn_info->dad_time = EVPN_DAD_DEFAULT_TIME; bgp_vrf->evpn_info->dad_max_moves = EVPN_DAD_DEFAULT_MAX_MOVES; bgp_vrf->evpn_info->dad_freeze = false; bgp_vrf->evpn_info->dad_freeze_time = 0; } else { if (max_moves) { if (bgp_vrf->evpn_info->dad_max_moves != max_moves) { vty_out(vty, "%% Value does not match with config\n"); return CMD_SUCCESS; } bgp_vrf->evpn_info->dad_max_moves = EVPN_DAD_DEFAULT_MAX_MOVES; } if (time_val) { if (bgp_vrf->evpn_info->dad_time != time_val) { vty_out(vty, "%% Value does not match with config\n"); return CMD_SUCCESS; } bgp_vrf->evpn_info->dad_time = EVPN_DAD_DEFAULT_TIME; } if (freeze_time) { if (bgp_vrf->evpn_info->dad_freeze_time != freeze_time) { vty_out(vty, "%% Value does not match with config\n"); return CMD_SUCCESS; } bgp_vrf->evpn_info->dad_freeze_time = 0; bgp_vrf->evpn_info->dad_freeze = false; } if (permanent_val) { if (bgp_vrf->evpn_info->dad_freeze_time) { vty_out(vty, "%% Value does not match with config\n"); return CMD_SUCCESS; } bgp_vrf->evpn_info->dad_freeze = false; } } bgp_zebra_dup_addr_detection(bgp_vrf); return CMD_SUCCESS; } DEFPY(bgp_evpn_advertise_svi_ip, bgp_evpn_advertise_svi_ip_cmd, "[no$no] advertise-svi-ip", NO_STR "Advertise svi mac-ip routes in EVPN\n") { struct bgp *bgp = VTY_GET_CONTEXT(bgp); if (!bgp) return CMD_WARNING; if (!EVPN_ENABLED(bgp)) { vty_out(vty, "This command is only supported under EVPN VRF\n"); return CMD_WARNING; } if (no) evpn_set_advertise_svi_macip(bgp, NULL, 0); else evpn_set_advertise_svi_macip(bgp, NULL, 1); return CMD_SUCCESS; } DEFPY(bgp_evpn_advertise_svi_ip_vni, bgp_evpn_advertise_svi_ip_vni_cmd, "[no$no] advertise-svi-ip", NO_STR "Advertise svi mac-ip routes in EVPN for a VNI\n") { struct bgp *bgp = VTY_GET_CONTEXT(bgp); VTY_DECLVAR_CONTEXT_SUB(bgpevpn, vpn); if (!bgp) return CMD_WARNING; if (no) evpn_set_advertise_svi_macip(bgp, vpn, 0); else evpn_set_advertise_svi_macip(bgp, vpn, 1); return CMD_SUCCESS; } DEFUN_HIDDEN (bgp_evpn_advertise_vni_subnet, bgp_evpn_advertise_vni_subnet_cmd, "advertise-subnet", "Advertise the subnet corresponding to VNI\n") { struct bgp *bgp_vrf = NULL; struct bgp *bgp = VTY_GET_CONTEXT(bgp); VTY_DECLVAR_CONTEXT_SUB(bgpevpn, vpn); if (!bgp) return CMD_WARNING; bgp_vrf = bgp_lookup_by_vrf_id(vpn->tenant_vrf_id); if (!bgp_vrf) return CMD_WARNING; evpn_set_advertise_subnet(bgp, vpn); return CMD_SUCCESS; } DEFUN_HIDDEN (no_bgp_evpn_advertise_vni_subnet, no_bgp_evpn_advertise_vni_subnet_cmd, "no advertise-subnet", NO_STR "Advertise All local VNIs\n") { struct bgp *bgp = VTY_GET_CONTEXT(bgp); VTY_DECLVAR_CONTEXT_SUB(bgpevpn, vpn); if (!bgp) return CMD_WARNING; evpn_unset_advertise_subnet(bgp, vpn); return CMD_SUCCESS; } DEFUN (bgp_evpn_advertise_type5, bgp_evpn_advertise_type5_cmd, "advertise " BGP_AFI_CMD_STR "" BGP_SAFI_CMD_STR " [route-map WORD]", "Advertise prefix routes\n" BGP_AFI_HELP_STR BGP_SAFI_HELP_STR "route-map for filtering specific routes\n" "Name of the route map\n") { struct bgp *bgp_vrf = VTY_GET_CONTEXT(bgp); /* bgp vrf instance */ int idx_afi = 0; int idx_safi = 0; int idx_rmap = 0; afi_t afi = 0; safi_t safi = 0; int ret = 0; int rmap_changed = 0; argv_find_and_parse_afi(argv, argc, &idx_afi, &afi); argv_find_and_parse_safi(argv, argc, &idx_safi, &safi); ret = argv_find(argv, argc, "route-map", &idx_rmap); if (ret) { if (!bgp_vrf->adv_cmd_rmap[afi][safi].name) rmap_changed = 1; else if (strcmp(argv[idx_rmap + 1]->arg, bgp_vrf->adv_cmd_rmap[afi][safi].name) != 0) rmap_changed = 1; } else if (bgp_vrf->adv_cmd_rmap[afi][safi].name) { rmap_changed = 1; } if (!(afi == AFI_IP || afi == AFI_IP6)) { vty_out(vty, "%%only ipv4 or ipv6 address families are supported"); return CMD_WARNING; } if (safi != SAFI_UNICAST) { vty_out(vty, "%%only ipv4 unicast or ipv6 unicast are supported"); return CMD_WARNING; } if (afi == AFI_IP) { /* if we are already advertising ipv4 prefix as type-5 * nothing to do */ if (!rmap_changed && CHECK_FLAG(bgp_vrf->af_flags[AFI_L2VPN][SAFI_EVPN], BGP_L2VPN_EVPN_ADVERTISE_IPV4_UNICAST)) return CMD_WARNING; SET_FLAG(bgp_vrf->af_flags[AFI_L2VPN][SAFI_EVPN], BGP_L2VPN_EVPN_ADVERTISE_IPV4_UNICAST); } else { /* if we are already advertising ipv6 prefix as type-5 * nothing to do */ if (!rmap_changed && CHECK_FLAG(bgp_vrf->af_flags[AFI_L2VPN][SAFI_EVPN], BGP_L2VPN_EVPN_ADVERTISE_IPV6_UNICAST)) return CMD_WARNING; SET_FLAG(bgp_vrf->af_flags[AFI_L2VPN][SAFI_EVPN], BGP_L2VPN_EVPN_ADVERTISE_IPV6_UNICAST); } if (rmap_changed) { bgp_evpn_withdraw_type5_routes(bgp_vrf, afi, safi); if (bgp_vrf->adv_cmd_rmap[afi][safi].name) { XFREE(MTYPE_ROUTE_MAP_NAME, bgp_vrf->adv_cmd_rmap[afi][safi].name); route_map_counter_decrement( bgp_vrf->adv_cmd_rmap[afi][safi].map); bgp_vrf->adv_cmd_rmap[afi][safi].name = NULL; bgp_vrf->adv_cmd_rmap[afi][safi].map = NULL; } } /* set the route-map for advertise command */ if (ret && argv[idx_rmap + 1]->arg) { bgp_vrf->adv_cmd_rmap[afi][safi].name = XSTRDUP(MTYPE_ROUTE_MAP_NAME, argv[idx_rmap + 1]->arg); bgp_vrf->adv_cmd_rmap[afi][safi].map = route_map_lookup_by_name(argv[idx_rmap + 1]->arg); route_map_counter_increment( bgp_vrf->adv_cmd_rmap[afi][safi].map); } /* advertise type-5 routes */ if (advertise_type5_routes(bgp_vrf, afi)) bgp_evpn_advertise_type5_routes(bgp_vrf, afi, safi); return CMD_SUCCESS; } DEFUN (no_bgp_evpn_advertise_type5, no_bgp_evpn_advertise_type5_cmd, "no advertise " BGP_AFI_CMD_STR "" BGP_SAFI_CMD_STR, NO_STR "Advertise prefix routes\n" BGP_AFI_HELP_STR BGP_SAFI_HELP_STR) { struct bgp *bgp_vrf = VTY_GET_CONTEXT(bgp); /* bgp vrf instance */ int idx_afi = 0; int idx_safi = 0; afi_t afi = 0; safi_t safi = 0; argv_find_and_parse_afi(argv, argc, &idx_afi, &afi); argv_find_and_parse_safi(argv, argc, &idx_safi, &safi); if (!(afi == AFI_IP || afi == AFI_IP6)) { vty_out(vty, "%%only ipv4 or ipv6 address families are supported"); return CMD_WARNING; } if (safi != SAFI_UNICAST) { vty_out(vty, "%%only ipv4 unicast or ipv6 unicast are supported"); return CMD_WARNING; } if (afi == AFI_IP) { /* if we are not advertising ipv4 prefix as type-5 * nothing to do */ if (CHECK_FLAG(bgp_vrf->af_flags[AFI_L2VPN][SAFI_EVPN], BGP_L2VPN_EVPN_ADVERTISE_IPV4_UNICAST)) { bgp_evpn_withdraw_type5_routes(bgp_vrf, afi, safi); UNSET_FLAG(bgp_vrf->af_flags[AFI_L2VPN][SAFI_EVPN], BGP_L2VPN_EVPN_ADVERTISE_IPV4_UNICAST); } } else { /* if we are not advertising ipv6 prefix as type-5 * nothing to do */ if (CHECK_FLAG(bgp_vrf->af_flags[AFI_L2VPN][SAFI_EVPN], BGP_L2VPN_EVPN_ADVERTISE_IPV6_UNICAST)) { bgp_evpn_withdraw_type5_routes(bgp_vrf, afi, safi); UNSET_FLAG(bgp_vrf->af_flags[AFI_L2VPN][SAFI_EVPN], BGP_L2VPN_EVPN_ADVERTISE_IPV6_UNICAST); } } /* clear the route-map information for advertise ipv4/ipv6 unicast */ if (bgp_vrf->adv_cmd_rmap[afi][safi].name) { XFREE(MTYPE_ROUTE_MAP_NAME, bgp_vrf->adv_cmd_rmap[afi][safi].name); bgp_vrf->adv_cmd_rmap[afi][safi].name = NULL; bgp_vrf->adv_cmd_rmap[afi][safi].map = NULL; } return CMD_SUCCESS; } /* * Display VNI information - for all or a specific VNI */ DEFUN(show_bgp_l2vpn_evpn_vni, show_bgp_l2vpn_evpn_vni_cmd, "show bgp l2vpn evpn vni [" CMD_VNI_RANGE "] [json]", SHOW_STR BGP_STR L2VPN_HELP_STR EVPN_HELP_STR "Show VNI\n" "VNI number\n" JSON_STR) { struct bgp *bgp_evpn; vni_t vni; int idx = 0; bool uj = false; json_object *json = NULL; uint32_t num_l2vnis = 0; uint32_t num_l3vnis = 0; uint32_t num_vnis = 0; struct listnode *node = NULL; struct bgp *bgp_temp = NULL; uj = use_json(argc, argv); bgp_evpn = bgp_get_evpn(); if (!bgp_evpn) return CMD_WARNING; if (!argv_find(argv, argc, "evpn", &idx)) return CMD_WARNING; if (uj) json = json_object_new_object(); if ((uj && argc == ((idx + 1) + 2)) || (!uj && argc == (idx + 1) + 1)) { num_l2vnis = hashcount(bgp_evpn->vnihash); for (ALL_LIST_ELEMENTS_RO(bm->bgp, node, bgp_temp)) { if (bgp_temp->l3vni) num_l3vnis++; } num_vnis = num_l2vnis + num_l3vnis; if (uj) { json_object_string_add(json, "advertiseGatewayMacip", bgp_evpn->advertise_gw_macip ? "Enabled" : "Disabled"); json_object_string_add(json, "advertiseAllVnis", is_evpn_enabled() ? "Enabled" : "Disabled"); json_object_string_add( json, "flooding", bgp_evpn->vxlan_flood_ctrl == VXLAN_FLOOD_HEAD_END_REPL ? "Head-end replication" : "Disabled"); json_object_int_add(json, "numVnis", num_vnis); json_object_int_add(json, "numL2Vnis", num_l2vnis); json_object_int_add(json, "numL3Vnis", num_l3vnis); } else { vty_out(vty, "Advertise Gateway Macip: %s\n", bgp_evpn->advertise_gw_macip ? "Enabled" : "Disabled"); vty_out(vty, "Advertise SVI Macip: %s\n", bgp_evpn->evpn_info->advertise_svi_macip ? "Enabled" : "Disabled"); vty_out(vty, "Advertise All VNI flag: %s\n", is_evpn_enabled() ? "Enabled" : "Disabled"); vty_out(vty, "BUM flooding: %s\n", bgp_evpn->vxlan_flood_ctrl == VXLAN_FLOOD_HEAD_END_REPL ? "Head-end replication" : "Disabled"); vty_out(vty, "Number of L2 VNIs: %u\n", num_l2vnis); vty_out(vty, "Number of L3 VNIs: %u\n", num_l3vnis); } evpn_show_all_vnis(vty, bgp_evpn, json); } else { int vni_idx = 0; if (!argv_find(argv, argc, "vni", &vni_idx)) return CMD_WARNING; /* Display specific VNI */ vni = strtoul(argv[vni_idx + 1]->arg, NULL, 10); evpn_show_vni(vty, bgp_evpn, vni, json); } if (uj) { vty_out(vty, "%s\n", json_object_to_json_string_ext( json, JSON_C_TO_STRING_PRETTY)); json_object_free(json); } return CMD_SUCCESS; } /* Disaply ES */ DEFUN(show_bgp_l2vpn_evpn_es, show_bgp_l2vpn_evpn_es_cmd, "show bgp l2vpn evpn es [ESI] [json]", SHOW_STR BGP_STR L2VPN_HELP_STR EVPN_HELP_STR "ethernet-Segment\n" "Ethernet-Segment Identifier\n" JSON_STR) { int idx = 0; bool uj = false; esi_t esi; json_object *json = NULL; struct bgp *bgp = NULL; memset(&esi, 0, sizeof(esi)); uj = use_json(argc, argv); bgp = bgp_get_evpn(); if (!bgp) return CMD_WARNING; if (!argv_find(argv, argc, "evpn", &idx)) return CMD_WARNING; if ((uj && argc == ((idx + 1) + 2)) || (!uj && argc == (idx + 1) + 1)) { /* show all ESs */ evpn_show_all_es(vty, bgp, json); } else { /* show a specific ES */ /* get the ESI - ESI-ID is at argv[5] */ if (!str_to_esi(argv[idx + 2]->arg, &esi)) { vty_out(vty, "%% Malformed ESI\n"); return CMD_WARNING; } evpn_show_es(vty, bgp, &esi, json); } if (uj) { vty_out(vty, "%s\n", json_object_to_json_string_ext( json, JSON_C_TO_STRING_PRETTY)); json_object_free(json); } return CMD_SUCCESS; } /* * Display EVPN neighbor summary. */ DEFUN(show_bgp_l2vpn_evpn_summary, show_bgp_l2vpn_evpn_summary_cmd, "show bgp [vrf VRFNAME] l2vpn evpn summary [failed] [json]", SHOW_STR BGP_STR "bgp vrf\n" "vrf name\n" L2VPN_HELP_STR EVPN_HELP_STR "Summary of BGP neighbor status\n" "Show only sessions not in Established state\n" JSON_STR) { int idx_vrf = 0; bool uj = use_json(argc, argv); char *vrf = NULL; bool show_failed = false; if (argv_find(argv, argc, "vrf", &idx_vrf)) vrf = argv[++idx_vrf]->arg; if (argv_find(argv, argc, "failed", &idx_vrf)) show_failed = true; return bgp_show_summary_vty(vty, vrf, AFI_L2VPN, SAFI_EVPN, show_failed, uj); } /* * Display global EVPN routing table. */ DEFUN(show_bgp_l2vpn_evpn_route, show_bgp_l2vpn_evpn_route_cmd, "show bgp l2vpn evpn route [detail] [type ] [json]", SHOW_STR BGP_STR L2VPN_HELP_STR EVPN_HELP_STR "EVPN route information\n" "Display Detailed Information\n" "Specify Route type\n" "MAC-IP (Type-2) route\n" "Multicast (Type-3) route\n" "Ethernet Segment (type-4) route \n" "Prefix (type-5 )route\n" JSON_STR) { struct bgp *bgp; int type_idx = 0; int detail = 0; int type = 0; bool uj = false; json_object *json = NULL; uj = use_json(argc, argv); bgp = bgp_get_evpn(); if (!bgp) return CMD_WARNING; if (uj) json = json_object_new_object(); /* get the type */ if (argv_find(argv, argc, "type", &type_idx)) { /* Specific type is requested */ if (strncmp(argv[type_idx + 1]->arg, "ma", 2) == 0) type = BGP_EVPN_MAC_IP_ROUTE; else if (strncmp(argv[type_idx + 1]->arg, "mu", 2) == 0) type = BGP_EVPN_IMET_ROUTE; else if (strncmp(argv[type_idx + 1]->arg, "e", 1) == 0) type = BGP_EVPN_ES_ROUTE; else if (strncmp(argv[type_idx + 1]->arg, "p", 1) == 0) type = BGP_EVPN_IP_PREFIX_ROUTE; else return CMD_WARNING; } if (argv_find(argv, argc, "detail", &detail)) detail = 1; evpn_show_all_routes(vty, bgp, type, json, detail); if (uj) { vty_out(vty, "%s\n", json_object_to_json_string_ext( json, JSON_C_TO_STRING_PRETTY)); json_object_free(json); } return CMD_SUCCESS; } /* * Display global EVPN routing table for specific RD. */ DEFUN(show_bgp_l2vpn_evpn_route_rd, show_bgp_l2vpn_evpn_route_rd_cmd, "show bgp l2vpn evpn route rd ASN:NN_OR_IP-ADDRESS:NN [type ] [json]", SHOW_STR BGP_STR L2VPN_HELP_STR EVPN_HELP_STR "EVPN route information\n" "Route Distinguisher\n" "ASN:XX or A.B.C.D:XX\n" "Specify Route type\n" "MAC-IP (Type-2) route\n" "Multicast (Type-3) route\n" "Ethernet Segment route\n" "Prefix route\n" JSON_STR) { struct bgp *bgp; int ret; struct prefix_rd prd; int type = 0; int rd_idx = 0; int type_idx = 0; bool uj = false; json_object *json = NULL; bgp = bgp_get_evpn(); if (!bgp) return CMD_WARNING; /* check if we need json output */ uj = use_json(argc, argv); if (uj) json = json_object_new_object(); /* get the RD */ if (argv_find(argv, argc, "rd", &rd_idx)) { ret = str2prefix_rd(argv[rd_idx + 1]->arg, &prd); if (!ret) { vty_out(vty, "%% Malformed Route Distinguisher\n"); return CMD_WARNING; } } /* get the type */ if (argv_find(argv, argc, "type", &type_idx)) { /* Specific type is requested */ if (strncmp(argv[type_idx + 1]->arg, "ma", 2) == 0) type = BGP_EVPN_MAC_IP_ROUTE; else if (strncmp(argv[type_idx + 1]->arg, "mu", 2) == 0) type = BGP_EVPN_IMET_ROUTE; else if (strncmp(argv[type_idx + 1]->arg, "pr", 2) == 0) type = BGP_EVPN_IP_PREFIX_ROUTE; else return CMD_WARNING; } evpn_show_route_rd(vty, bgp, &prd, type, json); if (uj) { vty_out(vty, "%s\n", json_object_to_json_string_ext( json, JSON_C_TO_STRING_PRETTY)); json_object_free(json); } return CMD_SUCCESS; } /* * Display global EVPN routing table for specific RD and MACIP. */ DEFUN(show_bgp_l2vpn_evpn_route_rd_macip, show_bgp_l2vpn_evpn_route_rd_macip_cmd, "show bgp l2vpn evpn route rd ASN:NN_OR_IP-ADDRESS:NN mac WORD [ip WORD] [json]", SHOW_STR BGP_STR L2VPN_HELP_STR EVPN_HELP_STR "EVPN route information\n" "Route Distinguisher\n" "ASN:XX or A.B.C.D:XX\n" "MAC\n" "MAC address (e.g., 00:e0:ec:20:12:62)\n" "IP\n" "IP address (IPv4 or IPv6)\n" JSON_STR) { struct bgp *bgp; int ret; struct prefix_rd prd; struct ethaddr mac; struct ipaddr ip; int rd_idx = 0; int mac_idx = 0; int ip_idx = 0; bool uj = false; json_object *json = NULL; memset(&mac, 0, sizeof(struct ethaddr)); memset(&ip, 0, sizeof(struct ipaddr)); bgp = bgp_get_evpn(); if (!bgp) return CMD_WARNING; /* check if we need json output */ uj = use_json(argc, argv); if (uj) json = json_object_new_object(); /* get the prd */ if (argv_find(argv, argc, "rd", &rd_idx)) { ret = str2prefix_rd(argv[rd_idx + 1]->arg, &prd); if (!ret) { vty_out(vty, "%% Malformed Route Distinguisher\n"); return CMD_WARNING; } } /* get the mac */ if (argv_find(argv, argc, "mac", &mac_idx)) { if (!prefix_str2mac(argv[mac_idx + 1]->arg, &mac)) { vty_out(vty, "%% Malformed MAC address\n"); return CMD_WARNING; } } /* get the ip if specified */ if (argv_find(argv, argc, "ip", &ip_idx)) { if (str2ipaddr(argv[ip_idx + 1]->arg, &ip) != 0) { vty_out(vty, "%% Malformed IP address\n"); return CMD_WARNING; } } evpn_show_route_rd_macip(vty, bgp, &prd, &mac, &ip, json); if (uj) { vty_out(vty, "%s\n", json_object_to_json_string_ext( json, JSON_C_TO_STRING_PRETTY)); json_object_free(json); } return CMD_SUCCESS; } /* Display per ESI routing table */ DEFUN(show_bgp_l2vpn_evpn_route_esi, show_bgp_l2vpn_evpn_route_esi_cmd, "show bgp l2vpn evpn route esi ESI [json]", SHOW_STR BGP_STR L2VPN_HELP_STR EVPN_HELP_STR "EVPN route information\n" "Ethernet Segment Identifier\n" "ESI ID\n" JSON_STR) { bool uj = false; esi_t esi; struct bgp *bgp = NULL; json_object *json = NULL; memset(&esi, 0, sizeof(esi)); bgp = bgp_get_evpn(); if (!bgp) return CMD_WARNING; uj = use_json(argc, argv); if (uj) json = json_object_new_object(); /* get the ESI - ESI-ID is at argv[6] */ if (!str_to_esi(argv[6]->arg, &esi)) { vty_out(vty, "%% Malformed ESI\n"); return CMD_WARNING; } evpn_show_routes_esi(vty, bgp, &esi, json); if (uj) { vty_out(vty, "%s\n", json_object_to_json_string_ext( json, JSON_C_TO_STRING_PRETTY)); json_object_free(json); } return CMD_SUCCESS; } /* * Display per-VNI EVPN routing table. */ DEFUN(show_bgp_l2vpn_evpn_route_vni, show_bgp_l2vpn_evpn_route_vni_cmd, "show bgp l2vpn evpn route vni " CMD_VNI_RANGE " [ | vtep A.B.C.D>] [json]", SHOW_STR BGP_STR L2VPN_HELP_STR EVPN_HELP_STR "EVPN route information\n" "VXLAN Network Identifier\n" "VNI number\n" "Specify Route type\n" "MAC-IP (Type-2) route\n" "Multicast (Type-3) route\n" "Remote VTEP\n" "Remote VTEP IP address\n" JSON_STR) { vni_t vni; struct bgp *bgp; struct in_addr vtep_ip; int type = 0; int idx = 0; bool uj = false; json_object *json = NULL; bgp = bgp_get_evpn(); if (!bgp) return CMD_WARNING; /* check if we need json output */ uj = use_json(argc, argv); if (uj) json = json_object_new_object(); if (!argv_find(argv, argc, "evpn", &idx)) return CMD_WARNING; vtep_ip.s_addr = 0; vni = strtoul(argv[idx + 3]->arg, NULL, 10); if ((!uj && ((argc == (idx + 1 + 5)) && argv[idx + 4]->arg)) || (uj && ((argc == (idx + 1 + 6)) && argv[idx + 4]->arg))) { if (strncmp(argv[idx + 4]->arg, "type", 4) == 0) { if (strncmp(argv[idx + 5]->arg, "ma", 2) == 0) type = BGP_EVPN_MAC_IP_ROUTE; else if (strncmp(argv[idx + 5]->arg, "mu", 2) == 0) type = BGP_EVPN_IMET_ROUTE; else return CMD_WARNING; } else if (strncmp(argv[idx + 4]->arg, "vtep", 4) == 0) { if (!inet_aton(argv[idx + 5]->arg, &vtep_ip)) { vty_out(vty, "%% Malformed VTEP IP address\n"); return CMD_WARNING; } } else return CMD_WARNING; } evpn_show_routes_vni(vty, bgp, vni, type, vtep_ip, json); if (uj) { vty_out(vty, "%s\n", json_object_to_json_string_ext( json, JSON_C_TO_STRING_PRETTY)); json_object_free(json); } return CMD_SUCCESS; } /* * Display per-VNI EVPN routing table for specific MACIP. */ DEFUN(show_bgp_l2vpn_evpn_route_vni_macip, show_bgp_l2vpn_evpn_route_vni_macip_cmd, "show bgp l2vpn evpn route vni " CMD_VNI_RANGE " mac WORD [ip WORD] [json]", SHOW_STR BGP_STR L2VPN_HELP_STR EVPN_HELP_STR "EVPN route information\n" "VXLAN Network Identifier\n" "VNI number\n" "MAC\n" "MAC address (e.g., 00:e0:ec:20:12:62)\n" "IP\n" "IP address (IPv4 or IPv6)\n" JSON_STR) { vni_t vni; struct bgp *bgp; struct ethaddr mac; struct ipaddr ip; int idx = 0; bool uj = false; json_object *json = NULL; bgp = bgp_get_evpn(); if (!bgp) return CMD_WARNING; /* check if we need json output */ uj = use_json(argc, argv); if (uj) json = json_object_new_object(); if (!argv_find(argv, argc, "evpn", &idx)) return CMD_WARNING; /* get the VNI */ vni = strtoul(argv[idx + 3]->arg, NULL, 10); /* get the mac */ if (!prefix_str2mac(argv[idx + 5]->arg, &mac)) { vty_out(vty, "%% Malformed MAC address\n"); return CMD_WARNING; } /* get the ip */ memset(&ip, 0, sizeof(ip)); if ((!uj && ((argc == (idx + 1 + 7)) && argv[idx + 7]->arg != NULL)) || (uj && ((argc == (idx + 1 + 8)) && argv[idx + 7]->arg != NULL))) { if (str2ipaddr(argv[idx + 7]->arg, &ip) != 0) { vty_out(vty, "%% Malformed IP address\n"); return CMD_WARNING; } } evpn_show_route_vni_macip(vty, bgp, vni, &mac, &ip, json); if (uj) { vty_out(vty, "%s\n", json_object_to_json_string_ext( json, JSON_C_TO_STRING_PRETTY)); json_object_free(json); } return CMD_SUCCESS; } /* * Display per-VNI EVPN routing table for specific multicast IP (remote VTEP). */ DEFUN(show_bgp_l2vpn_evpn_route_vni_multicast, show_bgp_l2vpn_evpn_route_vni_multicast_cmd, "show bgp l2vpn evpn route vni " CMD_VNI_RANGE " multicast A.B.C.D [json]", SHOW_STR BGP_STR L2VPN_HELP_STR EVPN_HELP_STR "EVPN route information\n" "VXLAN Network Identifier\n" "VNI number\n" "Multicast (Type-3) route\n" "Originating Router IP address\n" JSON_STR) { vni_t vni; struct bgp *bgp; int ret; struct in_addr orig_ip; int idx = 0; bool uj = false; json_object *json = NULL; bgp = bgp_get_evpn(); if (!bgp) return CMD_WARNING; /* check if we need json output */ uj = use_json(argc, argv); if (uj) json = json_object_new_object(); if (!argv_find(argv, argc, "evpn", &idx)) return CMD_WARNING; /* get the VNI */ vni = strtoul(argv[idx + 3]->arg, NULL, 10); /* get the ip */ ret = inet_aton(argv[idx + 5]->arg, &orig_ip); if (!ret) { vty_out(vty, "%% Malformed Originating Router IP address\n"); return CMD_WARNING; } evpn_show_route_vni_multicast(vty, bgp, vni, orig_ip, json); if (uj) { vty_out(vty, "%s\n", json_object_to_json_string_ext( json, JSON_C_TO_STRING_PRETTY)); json_object_free(json); } return CMD_SUCCESS; } /* * Display per-VNI EVPN routing table - for all VNIs. */ DEFUN(show_bgp_l2vpn_evpn_route_vni_all, show_bgp_l2vpn_evpn_route_vni_all_cmd, "show bgp l2vpn evpn route vni all [detail] [vtep A.B.C.D] [json]", SHOW_STR BGP_STR L2VPN_HELP_STR EVPN_HELP_STR "EVPN route information\n" "VXLAN Network Identifier\n" "All VNIs\n" "Print Detailed Output\n" "Remote VTEP\n" "Remote VTEP IP address\n" JSON_STR) { struct bgp *bgp; struct in_addr vtep_ip; int idx = 0; bool uj = false; json_object *json = NULL; /* Detail Adjust. Adjust indexes according to detail option */ int da = 0; bgp = bgp_get_evpn(); if (!bgp) return CMD_WARNING; /* check if we need json output */ uj = use_json(argc, argv); if (uj) json = json_object_new_object(); if (!argv_find(argv, argc, "evpn", &idx)) return CMD_WARNING; if (argv_find(argv, argc, "detail", &da)) da = 1; /* vtep-ip position depends on detail option */ vtep_ip.s_addr = 0; if ((!uj && (argc == (idx + 1 + 5 + da) && argv[idx + 5 + da]->arg)) || (uj && (argc == (idx + 1 + 6 + da) && argv[idx + 5 + da]->arg))) { if (!inet_aton(argv[idx + 5 + da]->arg, &vtep_ip)) { vty_out(vty, "%% Malformed VTEP IP address\n"); return CMD_WARNING; } } evpn_show_routes_vni_all(vty, bgp, vtep_ip, json, da); if (uj) { vty_out(vty, "%s\n", json_object_to_json_string_ext( json, JSON_C_TO_STRING_PRETTY)); json_object_free(json); } return CMD_SUCCESS; } /* * Display EVPN import route-target hash table */ DEFUN(show_bgp_l2vpn_evpn_vrf_import_rt, show_bgp_l2vpn_evpn_vrf_import_rt_cmd, "show bgp l2vpn evpn vrf-import-rt [json]", SHOW_STR BGP_STR L2VPN_HELP_STR EVPN_HELP_STR "Show vrf import route target\n" JSON_STR) { bool uj = false; struct bgp *bgp_evpn = NULL; json_object *json = NULL; bgp_evpn = bgp_get_evpn(); if (!bgp_evpn) return CMD_WARNING; uj = use_json(argc, argv); if (uj) json = json_object_new_object(); evpn_show_vrf_import_rts(vty, bgp_evpn, json); if (uj) { vty_out(vty, "%s\n", json_object_to_json_string_ext( json, JSON_C_TO_STRING_PRETTY)); json_object_free(json); } return CMD_SUCCESS; } /* * Display EVPN import route-target hash table */ DEFUN(show_bgp_l2vpn_evpn_import_rt, show_bgp_l2vpn_evpn_import_rt_cmd, "show bgp l2vpn evpn import-rt [json]", SHOW_STR BGP_STR L2VPN_HELP_STR EVPN_HELP_STR "Show import route target\n" JSON_STR) { struct bgp *bgp; bool uj = false; json_object *json = NULL; bgp = bgp_get_evpn(); if (!bgp) return CMD_WARNING; uj = use_json(argc, argv); if (uj) json = json_object_new_object(); evpn_show_import_rts(vty, bgp, json); if (uj) { vty_out(vty, "%s\n", json_object_to_json_string_ext( json, JSON_C_TO_STRING_PRETTY)); json_object_free(json); } return CMD_SUCCESS; } DEFUN(test_adv_evpn_type4_route, test_adv_evpn_type4_route_cmd, "advertise es ESI", "Advertise EVPN ES route\n" "Ethernet-segment\n" "Ethernet-Segment Identifier\n") { int ret = 0; esi_t esi; struct bgp *bgp; struct ipaddr vtep_ip; bgp = bgp_get_evpn(); if (!bgp) { vty_out(vty, "%%EVPN BGP instance not yet created\n"); return CMD_WARNING; } if (!str_to_esi(argv[2]->arg, &esi)) { vty_out(vty, "%%Malformed ESI\n"); return CMD_WARNING; } vtep_ip.ipa_type = IPADDR_V4; vtep_ip.ipaddr_v4 = bgp->router_id; ret = bgp_evpn_local_es_add(bgp, &esi, &vtep_ip); if (ret == -1) { vty_out(vty, "%%Failed to EVPN advertise type-4 route\n"); return CMD_WARNING; } return CMD_SUCCESS; } DEFUN(test_withdraw_evpn_type4_route, test_withdraw_evpn_type4_route_cmd, "withdraw es ESI", "Advertise EVPN ES route\n" "Ethernet-segment\n" "Ethernet-Segment Identifier\n") { int ret = 0; esi_t esi; struct bgp *bgp; struct ipaddr vtep_ip; bgp = bgp_get_evpn(); if (!bgp) { vty_out(vty, "%%EVPN BGP instance not yet created\n"); return CMD_WARNING; } if (!bgp->peer_self) { vty_out(vty, "%%BGP instance doesn't have self peer\n"); return CMD_WARNING; } if (!str_to_esi(argv[2]->arg, &esi)) { vty_out(vty, "%%Malformed ESI\n"); return CMD_WARNING; } vtep_ip.ipa_type = IPADDR_V4; vtep_ip.ipaddr_v4 = bgp->router_id; ret = bgp_evpn_local_es_del(bgp, &esi, &vtep_ip); if (ret == -1) { vty_out(vty, "%%Failed to withdraw EVPN type-4 route\n"); return CMD_WARNING; } return CMD_SUCCESS; } ALIAS_HIDDEN(show_bgp_l2vpn_evpn_vni, show_bgp_evpn_vni_cmd, "show bgp evpn vni [" CMD_VNI_RANGE "]", SHOW_STR BGP_STR EVPN_HELP_STR "Show VNI\n" "VNI number\n") ALIAS_HIDDEN(show_bgp_l2vpn_evpn_summary, show_bgp_evpn_summary_cmd, "show bgp evpn summary [json]", SHOW_STR BGP_STR EVPN_HELP_STR "Summary of BGP neighbor status\n" JSON_STR) ALIAS_HIDDEN(show_bgp_l2vpn_evpn_route, show_bgp_evpn_route_cmd, "show bgp evpn route [detail] [type ]", SHOW_STR BGP_STR EVPN_HELP_STR "EVPN route information\n" "Display Detailed Information\n" "Specify Route type\n" "MAC-IP (Type-2) route\n" "Multicast (Type-3) route\n") ALIAS_HIDDEN( show_bgp_l2vpn_evpn_route_rd, show_bgp_evpn_route_rd_cmd, "show bgp evpn route rd ASN:NN_OR_IP-ADDRESS:NN [type ]", SHOW_STR BGP_STR EVPN_HELP_STR "EVPN route information\n" "Route Distinguisher\n" "ASN:XX or A.B.C.D:XX\n" "Specify Route type\n" "MAC-IP (Type-2) route\n" "Multicast (Type-3) route\n") ALIAS_HIDDEN( show_bgp_l2vpn_evpn_route_rd_macip, show_bgp_evpn_route_rd_macip_cmd, "show bgp evpn route rd ASN:NN_OR_IP-ADDRESS:NN mac WORD [ip WORD]", SHOW_STR BGP_STR EVPN_HELP_STR "EVPN route information\n" "Route Distinguisher\n" "ASN:XX or A.B.C.D:XX\n" "MAC\n" "MAC address (e.g., 00:e0:ec:20:12:62)\n" "IP\n" "IP address (IPv4 or IPv6)\n") ALIAS_HIDDEN( show_bgp_l2vpn_evpn_route_vni, show_bgp_evpn_route_vni_cmd, "show bgp evpn route vni " CMD_VNI_RANGE " [ | vtep A.B.C.D>]", SHOW_STR BGP_STR EVPN_HELP_STR "EVPN route information\n" "VXLAN Network Identifier\n" "VNI number\n" "Specify Route type\n" "MAC-IP (Type-2) route\n" "Multicast (Type-3) route\n" "Remote VTEP\n" "Remote VTEP IP address\n") ALIAS_HIDDEN(show_bgp_l2vpn_evpn_route_vni_macip, show_bgp_evpn_route_vni_macip_cmd, "show bgp evpn route vni " CMD_VNI_RANGE " mac WORD [ip WORD]", SHOW_STR BGP_STR EVPN_HELP_STR "EVPN route information\n" "VXLAN Network Identifier\n" "VNI number\n" "MAC\n" "MAC address (e.g., 00:e0:ec:20:12:62)\n" "IP\n" "IP address (IPv4 or IPv6)\n") ALIAS_HIDDEN(show_bgp_l2vpn_evpn_route_vni_multicast, show_bgp_evpn_route_vni_multicast_cmd, "show bgp evpn route vni " CMD_VNI_RANGE " multicast A.B.C.D", SHOW_STR BGP_STR EVPN_HELP_STR "EVPN route information\n" "VXLAN Network Identifier\n" "VNI number\n" "Multicast (Type-3) route\n" "Originating Router IP address\n") ALIAS_HIDDEN(show_bgp_l2vpn_evpn_route_vni_all, show_bgp_evpn_route_vni_all_cmd, "show bgp evpn route vni all [detail] [vtep A.B.C.D]", SHOW_STR BGP_STR EVPN_HELP_STR "EVPN route information\n" "VXLAN Network Identifier\n" "All VNIs\n" "Print Detailed Output\n" "Remote VTEP\n" "Remote VTEP IP address\n") ALIAS_HIDDEN(show_bgp_l2vpn_evpn_import_rt, show_bgp_evpn_import_rt_cmd, "show bgp evpn import-rt", SHOW_STR BGP_STR EVPN_HELP_STR "Show import route target\n") DEFUN_NOSH (bgp_evpn_vni, bgp_evpn_vni_cmd, "vni " CMD_VNI_RANGE, "VXLAN Network Identifier\n" "VNI number\n") { vni_t vni; struct bgp *bgp = VTY_GET_CONTEXT(bgp); struct bgpevpn *vpn; if (!bgp) return CMD_WARNING; vni = strtoul(argv[1]->arg, NULL, 10); /* Create VNI, or mark as configured. */ vpn = evpn_create_update_vni(bgp, vni); if (!vpn) { vty_out(vty, "%% Failed to create VNI \n"); return CMD_WARNING; } VTY_PUSH_CONTEXT_SUB(BGP_EVPN_VNI_NODE, vpn); return CMD_SUCCESS; } DEFUN (no_bgp_evpn_vni, no_bgp_evpn_vni_cmd, "no vni " CMD_VNI_RANGE, NO_STR "VXLAN Network Identifier\n" "VNI number\n") { vni_t vni; struct bgp *bgp = VTY_GET_CONTEXT(bgp); struct bgpevpn *vpn; if (!bgp) return CMD_WARNING; vni = strtoul(argv[2]->arg, NULL, 10); /* Check if we should disallow. */ vpn = bgp_evpn_lookup_vni(bgp, vni); if (!vpn) { vty_out(vty, "%% Specified VNI does not exist\n"); return CMD_WARNING; } if (!is_vni_configured(vpn)) { vty_out(vty, "%% Specified VNI is not configured\n"); return CMD_WARNING; } evpn_delete_vni(bgp, vpn); return CMD_SUCCESS; } DEFUN_NOSH (exit_vni, exit_vni_cmd, "exit-vni", "Exit from VNI mode\n") { if (vty->node == BGP_EVPN_VNI_NODE) vty->node = BGP_EVPN_NODE; return CMD_SUCCESS; } DEFUN (bgp_evpn_vrf_rd, bgp_evpn_vrf_rd_cmd, "rd ASN:NN_OR_IP-ADDRESS:NN", "Route Distinguisher\n" "ASN:XX or A.B.C.D:XX\n") { int ret; struct prefix_rd prd; struct bgp *bgp_vrf = VTY_GET_CONTEXT(bgp); if (!bgp_vrf) return CMD_WARNING; ret = str2prefix_rd(argv[1]->arg, &prd); if (!ret) { vty_out(vty, "%% Malformed Route Distinguisher\n"); return CMD_WARNING; } /* If same as existing value, there is nothing more to do. */ if (bgp_evpn_vrf_rd_matches_existing(bgp_vrf, &prd)) return CMD_SUCCESS; /* Configure or update the RD. */ evpn_configure_vrf_rd(bgp_vrf, &prd); return CMD_SUCCESS; } DEFUN (no_bgp_evpn_vrf_rd, no_bgp_evpn_vrf_rd_cmd, "no rd ASN:NN_OR_IP-ADDRESS:NN", NO_STR "Route Distinguisher\n" "ASN:XX or A.B.C.D:XX\n") { int ret; struct prefix_rd prd; struct bgp *bgp_vrf = VTY_GET_CONTEXT(bgp); if (!bgp_vrf) return CMD_WARNING; ret = str2prefix_rd(argv[2]->arg, &prd); if (!ret) { vty_out(vty, "%% Malformed Route Distinguisher\n"); return CMD_WARNING; } /* Check if we should disallow. */ if (!is_vrf_rd_configured(bgp_vrf)) { vty_out(vty, "%% RD is not configured for this VRF\n"); return CMD_WARNING; } if (!bgp_evpn_vrf_rd_matches_existing(bgp_vrf, &prd)) { vty_out(vty, "%% RD specified does not match configuration for this VRF\n"); return CMD_WARNING; } evpn_unconfigure_vrf_rd(bgp_vrf); return CMD_SUCCESS; } DEFUN (no_bgp_evpn_vrf_rd_without_val, no_bgp_evpn_vrf_rd_without_val_cmd, "no rd", NO_STR "Route Distinguisher\n") { struct bgp *bgp_vrf = VTY_GET_CONTEXT(bgp); if (!bgp_vrf) return CMD_WARNING; /* Check if we should disallow. */ if (!is_vrf_rd_configured(bgp_vrf)) { vty_out(vty, "%% RD is not configured for this VRF\n"); return CMD_WARNING; } evpn_unconfigure_vrf_rd(bgp_vrf); return CMD_SUCCESS; } DEFUN (bgp_evpn_vni_rd, bgp_evpn_vni_rd_cmd, "rd ASN:NN_OR_IP-ADDRESS:NN", "Route Distinguisher\n" "ASN:XX or A.B.C.D:XX\n") { struct prefix_rd prd; struct bgp *bgp = VTY_GET_CONTEXT(bgp); VTY_DECLVAR_CONTEXT_SUB(bgpevpn, vpn); int ret; if (!bgp) return CMD_WARNING; if (!EVPN_ENABLED(bgp)) { vty_out(vty, "This command is only supported under EVPN VRF\n"); return CMD_WARNING; } ret = str2prefix_rd(argv[1]->arg, &prd); if (!ret) { vty_out(vty, "%% Malformed Route Distinguisher\n"); return CMD_WARNING; } /* If same as existing value, there is nothing more to do. */ if (bgp_evpn_rd_matches_existing(vpn, &prd)) return CMD_SUCCESS; /* Configure or update the RD. */ evpn_configure_rd(bgp, vpn, &prd); return CMD_SUCCESS; } DEFUN (no_bgp_evpn_vni_rd, no_bgp_evpn_vni_rd_cmd, "no rd ASN:NN_OR_IP-ADDRESS:NN", NO_STR "Route Distinguisher\n" "ASN:XX or A.B.C.D:XX\n") { struct prefix_rd prd; struct bgp *bgp = VTY_GET_CONTEXT(bgp); VTY_DECLVAR_CONTEXT_SUB(bgpevpn, vpn); int ret; if (!bgp) return CMD_WARNING; if (!EVPN_ENABLED(bgp)) { vty_out(vty, "This command is only supported under EVPN VRF\n"); return CMD_WARNING; } ret = str2prefix_rd(argv[2]->arg, &prd); if (!ret) { vty_out(vty, "%% Malformed Route Distinguisher\n"); return CMD_WARNING; } /* Check if we should disallow. */ if (!is_rd_configured(vpn)) { vty_out(vty, "%% RD is not configured for this VNI\n"); return CMD_WARNING; } if (!bgp_evpn_rd_matches_existing(vpn, &prd)) { vty_out(vty, "%% RD specified does not match configuration for this VNI\n"); return CMD_WARNING; } evpn_unconfigure_rd(bgp, vpn); return CMD_SUCCESS; } DEFUN (no_bgp_evpn_vni_rd_without_val, no_bgp_evpn_vni_rd_without_val_cmd, "no rd", NO_STR "Route Distinguisher\n") { struct bgp *bgp = VTY_GET_CONTEXT(bgp); VTY_DECLVAR_CONTEXT_SUB(bgpevpn, vpn); if (!bgp) return CMD_WARNING; if (!EVPN_ENABLED(bgp)) { vty_out(vty, "This command is only supported under EVPN VRF\n"); return CMD_WARNING; } /* Check if we should disallow. */ if (!is_rd_configured(vpn)) { vty_out(vty, "%% RD is not configured for this VNI\n"); return CMD_WARNING; } evpn_unconfigure_rd(bgp, vpn); return CMD_SUCCESS; } /* * Loop over all extended-communities in the route-target list rtl and * return 1 if we find ecomtarget */ static int bgp_evpn_rt_matches_existing(struct list *rtl, struct ecommunity *ecomtarget) { struct listnode *node, *nnode; struct ecommunity *ecom; for (ALL_LIST_ELEMENTS(rtl, node, nnode, ecom)) { if (ecommunity_match(ecom, ecomtarget)) return 1; } return 0; } /* display L3VNI related info for a VRF instance */ DEFUN (show_bgp_vrf_l3vni_info, show_bgp_vrf_l3vni_info_cmd, "show bgp vrf VRFNAME vni [json]", SHOW_STR BGP_STR "show bgp vrf\n" "VRF Name\n" "L3-VNI\n" JSON_STR) { char buf[ETHER_ADDR_STRLEN]; char buf1[INET6_ADDRSTRLEN]; int idx_vrf = 3; const char *name = NULL; struct bgp *bgp = NULL; struct listnode *node = NULL; struct bgpevpn *vpn = NULL; struct ecommunity *ecom = NULL; json_object *json = NULL; json_object *json_vnis = NULL; json_object *json_export_rts = NULL; json_object *json_import_rts = NULL; bool uj = use_json(argc, argv); if (uj) { json = json_object_new_object(); json_vnis = json_object_new_array(); json_export_rts = json_object_new_array(); json_import_rts = json_object_new_array(); } name = argv[idx_vrf]->arg; bgp = bgp_lookup_by_name(name); if (!bgp) { if (!uj) vty_out(vty, "BGP instance for VRF %s not found", name); else { json_object_string_add(json, "warning", "BGP instance not found"); vty_out(vty, "%s\n", json_object_to_json_string(json)); json_object_free(json); } return CMD_WARNING; } if (!json) { vty_out(vty, "BGP VRF: %s\n", name); vty_out(vty, " Local-Ip: %s\n", inet_ntoa(bgp->originator_ip)); vty_out(vty, " L3-VNI: %u\n", bgp->l3vni); vty_out(vty, " Rmac: %s\n", prefix_mac2str(&bgp->rmac, buf, sizeof(buf))); vty_out(vty, " VNI Filter: %s\n", CHECK_FLAG(bgp->vrf_flags, BGP_VRF_L3VNI_PREFIX_ROUTES_ONLY) ? "prefix-routes-only" : "none"); vty_out(vty, " L2-VNI List:\n"); vty_out(vty, " "); for (ALL_LIST_ELEMENTS_RO(bgp->l2vnis, node, vpn)) vty_out(vty, "%u ", vpn->vni); vty_out(vty, "\n"); vty_out(vty, " Export-RTs:\n"); vty_out(vty, " "); for (ALL_LIST_ELEMENTS_RO(bgp->vrf_export_rtl, node, ecom)) vty_out(vty, "%s ", ecommunity_str(ecom)); vty_out(vty, "\n"); vty_out(vty, " Import-RTs:\n"); vty_out(vty, " "); for (ALL_LIST_ELEMENTS_RO(bgp->vrf_import_rtl, node, ecom)) vty_out(vty, "%s ", ecommunity_str(ecom)); vty_out(vty, "\n"); vty_out(vty, " RD: %s\n", prefix_rd2str(&bgp->vrf_prd, buf1, RD_ADDRSTRLEN)); } else { json_object_string_add(json, "vrf", name); json_object_string_add(json, "local-ip", inet_ntoa(bgp->originator_ip)); json_object_int_add(json, "l3vni", bgp->l3vni); json_object_string_add( json, "rmac", prefix_mac2str(&bgp->rmac, buf, sizeof(buf))); json_object_string_add( json, "vniFilter", CHECK_FLAG(bgp->vrf_flags, BGP_VRF_L3VNI_PREFIX_ROUTES_ONLY) ? "prefix-routes-only" : "none"); /* list of l2vnis */ for (ALL_LIST_ELEMENTS_RO(bgp->l2vnis, node, vpn)) json_object_array_add(json_vnis, json_object_new_int(vpn->vni)); json_object_object_add(json, "l2vnis", json_vnis); /* export rts */ for (ALL_LIST_ELEMENTS_RO(bgp->vrf_export_rtl, node, ecom)) json_object_array_add( json_export_rts, json_object_new_string(ecommunity_str(ecom))); json_object_object_add(json, "export-rts", json_export_rts); /* import rts */ for (ALL_LIST_ELEMENTS_RO(bgp->vrf_import_rtl, node, ecom)) json_object_array_add( json_import_rts, json_object_new_string(ecommunity_str(ecom))); json_object_object_add(json, "import-rts", json_import_rts); json_object_string_add( json, "rd", prefix_rd2str(&bgp->vrf_prd, buf1, RD_ADDRSTRLEN)); } if (uj) { vty_out(vty, "%s\n", json_object_to_json_string_ext( json, JSON_C_TO_STRING_PRETTY)); json_object_free(json); } return CMD_SUCCESS; } /* import/export rt for l3vni-vrf */ DEFUN (bgp_evpn_vrf_rt, bgp_evpn_vrf_rt_cmd, "route-target RT", "Route Target\n" "import and export\n" "import\n" "export\n" "Route target (A.B.C.D:MN|EF:OPQR|GHJK:MN)\n") { int rt_type; struct bgp *bgp = VTY_GET_CONTEXT(bgp); struct ecommunity *ecomadd = NULL; if (!bgp) return CMD_WARNING; if (!strcmp(argv[1]->arg, "import")) rt_type = RT_TYPE_IMPORT; else if (!strcmp(argv[1]->arg, "export")) rt_type = RT_TYPE_EXPORT; else if (!strcmp(argv[1]->arg, "both")) rt_type = RT_TYPE_BOTH; else { vty_out(vty, "%% Invalid Route Target type\n"); return CMD_WARNING; } /* Add/update the import route-target */ if (rt_type == RT_TYPE_BOTH || rt_type == RT_TYPE_IMPORT) { ecomadd = ecommunity_str2com(argv[2]->arg, ECOMMUNITY_ROUTE_TARGET, 0); if (!ecomadd) { vty_out(vty, "%% Malformed Route Target list\n"); return CMD_WARNING; } ecommunity_str(ecomadd); /* Do nothing if we already have this import route-target */ if (!bgp_evpn_rt_matches_existing(bgp->vrf_import_rtl, ecomadd)) bgp_evpn_configure_import_rt_for_vrf(bgp, ecomadd); } /* Add/update the export route-target */ if (rt_type == RT_TYPE_BOTH || rt_type == RT_TYPE_EXPORT) { ecomadd = ecommunity_str2com(argv[2]->arg, ECOMMUNITY_ROUTE_TARGET, 0); if (!ecomadd) { vty_out(vty, "%% Malformed Route Target list\n"); return CMD_WARNING; } ecommunity_str(ecomadd); /* Do nothing if we already have this export route-target */ if (!bgp_evpn_rt_matches_existing(bgp->vrf_export_rtl, ecomadd)) bgp_evpn_configure_export_rt_for_vrf(bgp, ecomadd); } return CMD_SUCCESS; } DEFUN (no_bgp_evpn_vrf_rt, no_bgp_evpn_vrf_rt_cmd, "no route-target RT", NO_STR "Route Target\n" "import and export\n" "import\n" "export\n" "ASN:XX or A.B.C.D:XX\n") { struct bgp *bgp = VTY_GET_CONTEXT(bgp); int rt_type, found_ecomdel; struct ecommunity *ecomdel = NULL; if (!bgp) return CMD_WARNING; if (!strcmp(argv[2]->arg, "import")) rt_type = RT_TYPE_IMPORT; else if (!strcmp(argv[2]->arg, "export")) rt_type = RT_TYPE_EXPORT; else if (!strcmp(argv[2]->arg, "both")) rt_type = RT_TYPE_BOTH; else { vty_out(vty, "%% Invalid Route Target type\n"); return CMD_WARNING; } if (rt_type == RT_TYPE_IMPORT) { if (!CHECK_FLAG(bgp->vrf_flags, BGP_VRF_IMPORT_RT_CFGD)) { vty_out(vty, "%% Import RT is not configured for this VRF\n"); return CMD_WARNING; } } else if (rt_type == RT_TYPE_EXPORT) { if (!CHECK_FLAG(bgp->vrf_flags, BGP_VRF_EXPORT_RT_CFGD)) { vty_out(vty, "%% Export RT is not configured for this VRF\n"); return CMD_WARNING; } } else if (rt_type == RT_TYPE_BOTH) { if (!CHECK_FLAG(bgp->vrf_flags, BGP_VRF_IMPORT_RT_CFGD) && !CHECK_FLAG(bgp->vrf_flags, BGP_VRF_EXPORT_RT_CFGD)) { vty_out(vty, "%% Import/Export RT is not configured for this VRF\n"); return CMD_WARNING; } } ecomdel = ecommunity_str2com(argv[3]->arg, ECOMMUNITY_ROUTE_TARGET, 0); if (!ecomdel) { vty_out(vty, "%% Malformed Route Target list\n"); return CMD_WARNING; } ecommunity_str(ecomdel); if (rt_type == RT_TYPE_IMPORT) { if (!bgp_evpn_rt_matches_existing(bgp->vrf_import_rtl, ecomdel)) { vty_out(vty, "%% RT specified does not match configuration for this VRF\n"); return CMD_WARNING; } bgp_evpn_unconfigure_import_rt_for_vrf(bgp, ecomdel); } else if (rt_type == RT_TYPE_EXPORT) { if (!bgp_evpn_rt_matches_existing(bgp->vrf_export_rtl, ecomdel)) { vty_out(vty, "%% RT specified does not match configuration for this VRF\n"); return CMD_WARNING; } bgp_evpn_unconfigure_export_rt_for_vrf(bgp, ecomdel); } else if (rt_type == RT_TYPE_BOTH) { found_ecomdel = 0; if (bgp_evpn_rt_matches_existing(bgp->vrf_import_rtl, ecomdel)) { bgp_evpn_unconfigure_import_rt_for_vrf(bgp, ecomdel); found_ecomdel = 1; } if (bgp_evpn_rt_matches_existing(bgp->vrf_export_rtl, ecomdel)) { bgp_evpn_unconfigure_export_rt_for_vrf(bgp, ecomdel); found_ecomdel = 1; } if (!found_ecomdel) { vty_out(vty, "%% RT specified does not match configuration for this VRF\n"); return CMD_WARNING; } } return CMD_SUCCESS; } DEFUN (bgp_evpn_vni_rt, bgp_evpn_vni_rt_cmd, "route-target RT", "Route Target\n" "import and export\n" "import\n" "export\n" "Route target (A.B.C.D:MN|EF:OPQR|GHJK:MN)\n") { struct bgp *bgp = VTY_GET_CONTEXT(bgp); VTY_DECLVAR_CONTEXT_SUB(bgpevpn, vpn); int rt_type; struct ecommunity *ecomadd = NULL; if (!bgp) return CMD_WARNING; if (!EVPN_ENABLED(bgp)) { vty_out(vty, "This command is only supported under EVPN VRF\n"); return CMD_WARNING; } if (!strcmp(argv[1]->text, "import")) rt_type = RT_TYPE_IMPORT; else if (!strcmp(argv[1]->text, "export")) rt_type = RT_TYPE_EXPORT; else if (!strcmp(argv[1]->text, "both")) rt_type = RT_TYPE_BOTH; else { vty_out(vty, "%% Invalid Route Target type\n"); return CMD_WARNING; } /* Add/update the import route-target */ if (rt_type == RT_TYPE_BOTH || rt_type == RT_TYPE_IMPORT) { ecomadd = ecommunity_str2com(argv[2]->arg, ECOMMUNITY_ROUTE_TARGET, 0); if (!ecomadd) { vty_out(vty, "%% Malformed Route Target list\n"); return CMD_WARNING; } ecommunity_str(ecomadd); /* Do nothing if we already have this import route-target */ if (!bgp_evpn_rt_matches_existing(vpn->import_rtl, ecomadd)) evpn_configure_import_rt(bgp, vpn, ecomadd); } /* Add/update the export route-target */ if (rt_type == RT_TYPE_BOTH || rt_type == RT_TYPE_EXPORT) { ecomadd = ecommunity_str2com(argv[2]->arg, ECOMMUNITY_ROUTE_TARGET, 0); if (!ecomadd) { vty_out(vty, "%% Malformed Route Target list\n"); return CMD_WARNING; } ecommunity_str(ecomadd); /* Do nothing if we already have this export route-target */ if (!bgp_evpn_rt_matches_existing(vpn->export_rtl, ecomadd)) evpn_configure_export_rt(bgp, vpn, ecomadd); } return CMD_SUCCESS; } DEFUN (no_bgp_evpn_vni_rt, no_bgp_evpn_vni_rt_cmd, "no route-target RT", NO_STR "Route Target\n" "import and export\n" "import\n" "export\n" "ASN:XX or A.B.C.D:XX\n") { struct bgp *bgp = VTY_GET_CONTEXT(bgp); VTY_DECLVAR_CONTEXT_SUB(bgpevpn, vpn); int rt_type, found_ecomdel; struct ecommunity *ecomdel = NULL; if (!bgp) return CMD_WARNING; if (!EVPN_ENABLED(bgp)) { vty_out(vty, "This command is only supported under EVPN VRF\n"); return CMD_WARNING; } if (!strcmp(argv[2]->text, "import")) rt_type = RT_TYPE_IMPORT; else if (!strcmp(argv[2]->text, "export")) rt_type = RT_TYPE_EXPORT; else if (!strcmp(argv[2]->text, "both")) rt_type = RT_TYPE_BOTH; else { vty_out(vty, "%% Invalid Route Target type\n"); return CMD_WARNING; } /* The user did "no route-target import", check to see if there are any * import route-targets configured. */ if (rt_type == RT_TYPE_IMPORT) { if (!is_import_rt_configured(vpn)) { vty_out(vty, "%% Import RT is not configured for this VNI\n"); return CMD_WARNING; } } else if (rt_type == RT_TYPE_EXPORT) { if (!is_export_rt_configured(vpn)) { vty_out(vty, "%% Export RT is not configured for this VNI\n"); return CMD_WARNING; } } else if (rt_type == RT_TYPE_BOTH) { if (!is_import_rt_configured(vpn) && !is_export_rt_configured(vpn)) { vty_out(vty, "%% Import/Export RT is not configured for this VNI\n"); return CMD_WARNING; } } ecomdel = ecommunity_str2com(argv[3]->arg, ECOMMUNITY_ROUTE_TARGET, 0); if (!ecomdel) { vty_out(vty, "%% Malformed Route Target list\n"); return CMD_WARNING; } ecommunity_str(ecomdel); if (rt_type == RT_TYPE_IMPORT) { if (!bgp_evpn_rt_matches_existing(vpn->import_rtl, ecomdel)) { vty_out(vty, "%% RT specified does not match configuration for this VNI\n"); return CMD_WARNING; } evpn_unconfigure_import_rt(bgp, vpn, ecomdel); } else if (rt_type == RT_TYPE_EXPORT) { if (!bgp_evpn_rt_matches_existing(vpn->export_rtl, ecomdel)) { vty_out(vty, "%% RT specified does not match configuration for this VNI\n"); return CMD_WARNING; } evpn_unconfigure_export_rt(bgp, vpn, ecomdel); } else if (rt_type == RT_TYPE_BOTH) { found_ecomdel = 0; if (bgp_evpn_rt_matches_existing(vpn->import_rtl, ecomdel)) { evpn_unconfigure_import_rt(bgp, vpn, ecomdel); found_ecomdel = 1; } if (bgp_evpn_rt_matches_existing(vpn->export_rtl, ecomdel)) { evpn_unconfigure_export_rt(bgp, vpn, ecomdel); found_ecomdel = 1; } if (!found_ecomdel) { vty_out(vty, "%% RT specified does not match configuration for this VNI\n"); return CMD_WARNING; } } return CMD_SUCCESS; } DEFUN (no_bgp_evpn_vni_rt_without_val, no_bgp_evpn_vni_rt_without_val_cmd, "no route-target ", NO_STR "Route Target\n" "import\n" "export\n") { struct bgp *bgp = VTY_GET_CONTEXT(bgp); VTY_DECLVAR_CONTEXT_SUB(bgpevpn, vpn); int rt_type; if (!bgp) return CMD_WARNING; if (!EVPN_ENABLED(bgp)) { vty_out(vty, "This command is only supported under EVPN VRF\n"); return CMD_WARNING; } if (!strcmp(argv[2]->text, "import")) { rt_type = RT_TYPE_IMPORT; } else if (!strcmp(argv[2]->text, "export")) { rt_type = RT_TYPE_EXPORT; } else { vty_out(vty, "%% Invalid Route Target type\n"); return CMD_WARNING; } /* Check if we should disallow. */ if (rt_type == RT_TYPE_IMPORT) { if (!is_import_rt_configured(vpn)) { vty_out(vty, "%% Import RT is not configured for this VNI\n"); return CMD_WARNING; } } else { if (!is_export_rt_configured(vpn)) { vty_out(vty, "%% Export RT is not configured for this VNI\n"); return CMD_WARNING; } } /* Unconfigure the RT. */ if (rt_type == RT_TYPE_IMPORT) evpn_unconfigure_import_rt(bgp, vpn, NULL); else evpn_unconfigure_export_rt(bgp, vpn, NULL); return CMD_SUCCESS; } static int vni_cmp(const void **a, const void **b) { const struct bgpevpn *first = *a; const struct bgpevpn *secnd = *b; return secnd->vni - first->vni; } /* * Output EVPN configuration information. */ void bgp_config_write_evpn_info(struct vty *vty, struct bgp *bgp, afi_t afi, safi_t safi) { char buf1[RD_ADDRSTRLEN]; if (bgp->vnihash) { struct list *vnilist = hash_to_list(bgp->vnihash); struct listnode *ln; struct bgpevpn *data; list_sort(vnilist, vni_cmp); for (ALL_LIST_ELEMENTS_RO(vnilist, ln, data)) write_vni_config(vty, data); list_delete(&vnilist); } if (bgp->advertise_all_vni) vty_out(vty, " advertise-all-vni\n"); if (bgp->advertise_autort_rfc8365) vty_out(vty, " autort rfc8365-compatible\n"); if (bgp->advertise_gw_macip) vty_out(vty, " advertise-default-gw\n"); if (bgp->evpn_info->advertise_svi_macip) vty_out(vty, " advertise-svi-ip\n"); if (!bgp->evpn_info->dup_addr_detect) vty_out(vty, " no dup-addr-detection\n"); if (bgp->evpn_info->dad_max_moves != EVPN_DAD_DEFAULT_MAX_MOVES || bgp->evpn_info->dad_time != EVPN_DAD_DEFAULT_TIME) vty_out(vty, " dup-addr-detection max-moves %u time %u\n", bgp->evpn_info->dad_max_moves, bgp->evpn_info->dad_time); if (bgp->evpn_info->dad_freeze) { if (bgp->evpn_info->dad_freeze_time) vty_out(vty, " dup-addr-detection freeze %u\n", bgp->evpn_info->dad_freeze_time); else vty_out(vty, " dup-addr-detection freeze permanent\n"); } if (bgp->vxlan_flood_ctrl == VXLAN_FLOOD_DISABLED) vty_out(vty, " flooding disable\n"); if (CHECK_FLAG(bgp->af_flags[AFI_L2VPN][SAFI_EVPN], BGP_L2VPN_EVPN_ADVERTISE_IPV4_UNICAST)) { if (bgp->adv_cmd_rmap[AFI_IP][SAFI_UNICAST].name) vty_out(vty, " advertise ipv4 unicast route-map %s\n", bgp->adv_cmd_rmap[AFI_IP][SAFI_UNICAST].name); else vty_out(vty, " advertise ipv4 unicast\n"); } if (CHECK_FLAG(bgp->af_flags[AFI_L2VPN][SAFI_EVPN], BGP_L2VPN_EVPN_ADVERTISE_IPV6_UNICAST)) { if (bgp->adv_cmd_rmap[AFI_IP6][SAFI_UNICAST].name) vty_out(vty, " advertise ipv6 unicast route-map %s\n", bgp->adv_cmd_rmap[AFI_IP6][SAFI_UNICAST].name); else vty_out(vty, " advertise ipv6 unicast\n"); } if (CHECK_FLAG(bgp->af_flags[AFI_L2VPN][SAFI_EVPN], BGP_L2VPN_EVPN_DEFAULT_ORIGINATE_IPV4)) vty_out(vty, " default-originate ipv4\n"); if (CHECK_FLAG(bgp->af_flags[AFI_L2VPN][SAFI_EVPN], BGP_L2VPN_EVPN_DEFAULT_ORIGINATE_IPV6)) vty_out(vty, " default-originate ipv6\n"); if (CHECK_FLAG(bgp->vrf_flags, BGP_VRF_RD_CFGD)) vty_out(vty, " rd %s\n", prefix_rd2str(&bgp->vrf_prd, buf1, sizeof(buf1))); /* import route-target */ if (CHECK_FLAG(bgp->vrf_flags, BGP_VRF_IMPORT_RT_CFGD)) { char *ecom_str; struct listnode *node, *nnode; struct ecommunity *ecom; for (ALL_LIST_ELEMENTS(bgp->vrf_import_rtl, node, nnode, ecom)) { ecom_str = ecommunity_ecom2str( ecom, ECOMMUNITY_FORMAT_ROUTE_MAP, 0); vty_out(vty, " route-target import %s\n", ecom_str); XFREE(MTYPE_ECOMMUNITY_STR, ecom_str); } } /* export route-target */ if (CHECK_FLAG(bgp->vrf_flags, BGP_VRF_EXPORT_RT_CFGD)) { char *ecom_str; struct listnode *node, *nnode; struct ecommunity *ecom; for (ALL_LIST_ELEMENTS(bgp->vrf_export_rtl, node, nnode, ecom)) { ecom_str = ecommunity_ecom2str( ecom, ECOMMUNITY_FORMAT_ROUTE_MAP, 0); vty_out(vty, " route-target export %s\n", ecom_str); XFREE(MTYPE_ECOMMUNITY_STR, ecom_str); } } } void bgp_ethernetvpn_init(void) { install_element(VIEW_NODE, &show_ip_bgp_l2vpn_evpn_cmd); install_element(VIEW_NODE, &show_ip_bgp_l2vpn_evpn_rd_cmd); install_element(VIEW_NODE, &show_ip_bgp_l2vpn_evpn_all_tags_cmd); install_element(VIEW_NODE, &show_ip_bgp_l2vpn_evpn_rd_tags_cmd); install_element(VIEW_NODE, &show_ip_bgp_l2vpn_evpn_neighbor_routes_cmd); install_element(VIEW_NODE, &show_ip_bgp_l2vpn_evpn_rd_neighbor_routes_cmd); install_element( VIEW_NODE, &show_ip_bgp_l2vpn_evpn_neighbor_advertised_routes_cmd); install_element( VIEW_NODE, &show_ip_bgp_l2vpn_evpn_rd_neighbor_advertised_routes_cmd); install_element(VIEW_NODE, &show_ip_bgp_evpn_rd_overlay_cmd); install_element(VIEW_NODE, &show_ip_bgp_l2vpn_evpn_all_overlay_cmd); install_element(BGP_EVPN_NODE, &no_evpnrt5_network_cmd); install_element(BGP_EVPN_NODE, &evpnrt5_network_cmd); install_element(BGP_EVPN_NODE, &bgp_evpn_advertise_all_vni_cmd); install_element(BGP_EVPN_NODE, &no_bgp_evpn_advertise_all_vni_cmd); install_element(BGP_EVPN_NODE, &bgp_evpn_advertise_autort_rfc8365_cmd); install_element(BGP_EVPN_NODE, &no_bgp_evpn_advertise_autort_rfc8365_cmd); install_element(BGP_EVPN_NODE, &bgp_evpn_advertise_default_gw_cmd); install_element(BGP_EVPN_NODE, &no_bgp_evpn_advertise_default_gw_cmd); install_element(BGP_EVPN_NODE, &bgp_evpn_advertise_svi_ip_cmd); install_element(BGP_EVPN_NODE, &bgp_evpn_advertise_type5_cmd); install_element(BGP_EVPN_NODE, &no_bgp_evpn_advertise_type5_cmd); install_element(BGP_EVPN_NODE, &bgp_evpn_default_originate_cmd); install_element(BGP_EVPN_NODE, &no_bgp_evpn_default_originate_cmd); install_element(BGP_EVPN_NODE, &dup_addr_detection_cmd); install_element(BGP_EVPN_NODE, &dup_addr_detection_auto_recovery_cmd); install_element(BGP_EVPN_NODE, &no_dup_addr_detection_cmd); install_element(BGP_EVPN_NODE, &bgp_evpn_flood_control_cmd); /* test commands */ install_element(BGP_EVPN_NODE, &test_adv_evpn_type4_route_cmd); install_element(BGP_EVPN_NODE, &test_withdraw_evpn_type4_route_cmd); /* "show bgp l2vpn evpn" commands. */ install_element(VIEW_NODE, &show_bgp_l2vpn_evpn_es_cmd); install_element(VIEW_NODE, &show_bgp_l2vpn_evpn_vni_cmd); install_element(VIEW_NODE, &show_bgp_l2vpn_evpn_summary_cmd); install_element(VIEW_NODE, &show_bgp_l2vpn_evpn_route_cmd); install_element(VIEW_NODE, &show_bgp_l2vpn_evpn_route_rd_cmd); install_element(VIEW_NODE, &show_bgp_l2vpn_evpn_route_rd_macip_cmd); install_element(VIEW_NODE, &show_bgp_l2vpn_evpn_route_esi_cmd); install_element(VIEW_NODE, &show_bgp_l2vpn_evpn_route_vni_cmd); install_element(VIEW_NODE, &show_bgp_l2vpn_evpn_route_vni_multicast_cmd); install_element(VIEW_NODE, &show_bgp_l2vpn_evpn_route_vni_macip_cmd); install_element(VIEW_NODE, &show_bgp_l2vpn_evpn_route_vni_all_cmd); install_element(VIEW_NODE, &show_bgp_l2vpn_evpn_import_rt_cmd); install_element(VIEW_NODE, &show_bgp_l2vpn_evpn_vrf_import_rt_cmd); /* "show bgp evpn" commands. */ install_element(VIEW_NODE, &show_bgp_evpn_vni_cmd); install_element(VIEW_NODE, &show_bgp_evpn_summary_cmd); install_element(VIEW_NODE, &show_bgp_evpn_route_cmd); install_element(VIEW_NODE, &show_bgp_evpn_route_rd_cmd); install_element(VIEW_NODE, &show_bgp_evpn_route_rd_macip_cmd); install_element(VIEW_NODE, &show_bgp_evpn_route_vni_cmd); install_element(VIEW_NODE, &show_bgp_evpn_route_vni_multicast_cmd); install_element(VIEW_NODE, &show_bgp_evpn_route_vni_macip_cmd); install_element(VIEW_NODE, &show_bgp_evpn_route_vni_all_cmd); install_element(VIEW_NODE, &show_bgp_evpn_import_rt_cmd); install_element(VIEW_NODE, &show_bgp_vrf_l3vni_info_cmd); install_element(BGP_EVPN_NODE, &bgp_evpn_vni_cmd); install_element(BGP_EVPN_NODE, &no_bgp_evpn_vni_cmd); install_element(BGP_EVPN_VNI_NODE, &exit_vni_cmd); install_element(BGP_EVPN_VNI_NODE, &bgp_evpn_vni_rd_cmd); install_element(BGP_EVPN_VNI_NODE, &no_bgp_evpn_vni_rd_cmd); install_element(BGP_EVPN_VNI_NODE, &no_bgp_evpn_vni_rd_without_val_cmd); install_element(BGP_EVPN_VNI_NODE, &bgp_evpn_vni_rt_cmd); install_element(BGP_EVPN_VNI_NODE, &no_bgp_evpn_vni_rt_cmd); install_element(BGP_EVPN_VNI_NODE, &no_bgp_evpn_vni_rt_without_val_cmd); install_element(BGP_EVPN_NODE, &bgp_evpn_vrf_rd_cmd); install_element(BGP_EVPN_NODE, &no_bgp_evpn_vrf_rd_cmd); install_element(BGP_NODE, &no_bgp_evpn_vrf_rd_without_val_cmd); install_element(BGP_EVPN_NODE, &bgp_evpn_vrf_rt_cmd); install_element(BGP_EVPN_NODE, &no_bgp_evpn_vrf_rt_cmd); install_element(BGP_EVPN_VNI_NODE, &bgp_evpn_advertise_svi_ip_vni_cmd); install_element(BGP_EVPN_VNI_NODE, &bgp_evpn_advertise_default_gw_vni_cmd); install_element(BGP_EVPN_VNI_NODE, &no_bgp_evpn_advertise_default_gw_vni_cmd); install_element(BGP_EVPN_VNI_NODE, &bgp_evpn_advertise_vni_subnet_cmd); install_element(BGP_EVPN_VNI_NODE, &no_bgp_evpn_advertise_vni_subnet_cmd); } frr-7.2.1/bgpd/bgp_evpn_vty.h0000644000000000000000000000225113610377563013003 00000000000000/* EVPN VTY functions to EVPN * Copyright (C) 2017 6WIND * * This file is part of FRRouting. * * FRRouting is free software; you can redistribute it and/or modify it * under the terms of the GNU General Public License as published by the * Free Software Foundation; either version 2, or (at your option) any * later version. * * FRRouting 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; see the file COPYING; if not, write to the Free Software * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA */ #ifndef _FRR_BGP_EVPN_VTY_H #define _FRR_BGP_EVPN_VTY_H extern void bgp_config_write_evpn_info(struct vty *vty, struct bgp *bgp, afi_t afi, safi_t safi); extern void bgp_ethernetvpn_init(void); #define L2VPN_HELP_STR "Layer 2 Virtual Private Network\n" #define EVPN_HELP_STR "Ethernet Virtual Private Network\n" #endif /* _QUAGGA_BGP_EVPN_VTY_H */ frr-7.2.1/bgpd/bgp_filter.c0000644000000000000000000004722013610377563012416 00000000000000/* AS path filter list. * Copyright (C) 1999 Kunihiro Ishiguro * * This file is part of GNU Zebra. * * GNU Zebra is free software; you can redistribute it and/or modify it * under the terms of the GNU General Public License as published by the * Free Software Foundation; either version 2, or (at your option) any * later version. * * GNU Zebra 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; see the file COPYING; if not, write to the Free Software * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA */ #include #include "command.h" #include "log.h" #include "memory.h" #include "buffer.h" #include "queue.h" #include "filter.h" #include "bgpd/bgpd.h" #include "bgpd/bgp_aspath.h" #include "bgpd/bgp_regex.h" #include "bgpd/bgp_filter.h" /* List of AS filter list. */ struct as_list_list { struct as_list *head; struct as_list *tail; }; /* AS path filter master. */ struct as_list_master { /* List of access_list which name is number. */ struct as_list_list num; /* List of access_list which name is string. */ struct as_list_list str; /* Hook function which is executed when new access_list is added. */ void (*add_hook)(char *); /* Hook function which is executed when access_list is deleted. */ void (*delete_hook)(const char *); }; /* Element of AS path filter. */ struct as_filter { struct as_filter *next; struct as_filter *prev; enum as_filter_type type; regex_t *reg; char *reg_str; }; /* AS path filter list. */ struct as_list { char *name; enum access_type type; struct as_list *next; struct as_list *prev; struct as_filter *head; struct as_filter *tail; }; /* as-path access-list 10 permit AS1. */ static struct as_list_master as_list_master = {{NULL, NULL}, {NULL, NULL}, NULL, NULL}; /* Allocate new AS filter. */ static struct as_filter *as_filter_new(void) { return XCALLOC(MTYPE_AS_FILTER, sizeof(struct as_filter)); } /* Free allocated AS filter. */ static void as_filter_free(struct as_filter *asfilter) { if (asfilter->reg) bgp_regex_free(asfilter->reg); XFREE(MTYPE_AS_FILTER_STR, asfilter->reg_str); XFREE(MTYPE_AS_FILTER, asfilter); } /* Make new AS filter. */ static struct as_filter *as_filter_make(regex_t *reg, const char *reg_str, enum as_filter_type type) { struct as_filter *asfilter; asfilter = as_filter_new(); asfilter->reg = reg; asfilter->type = type; asfilter->reg_str = XSTRDUP(MTYPE_AS_FILTER_STR, reg_str); return asfilter; } static struct as_filter *as_filter_lookup(struct as_list *aslist, const char *reg_str, enum as_filter_type type) { struct as_filter *asfilter; for (asfilter = aslist->head; asfilter; asfilter = asfilter->next) if (strcmp(reg_str, asfilter->reg_str) == 0) return asfilter; return NULL; } static void as_list_filter_add(struct as_list *aslist, struct as_filter *asfilter) { asfilter->next = NULL; asfilter->prev = aslist->tail; if (aslist->tail) aslist->tail->next = asfilter; else aslist->head = asfilter; aslist->tail = asfilter; /* Run hook function. */ if (as_list_master.add_hook) (*as_list_master.add_hook)(aslist->name); } /* Lookup as_list from list of as_list by name. */ struct as_list *as_list_lookup(const char *name) { struct as_list *aslist; if (name == NULL) return NULL; for (aslist = as_list_master.num.head; aslist; aslist = aslist->next) if (strcmp(aslist->name, name) == 0) return aslist; for (aslist = as_list_master.str.head; aslist; aslist = aslist->next) if (strcmp(aslist->name, name) == 0) return aslist; return NULL; } static struct as_list *as_list_new(void) { return XCALLOC(MTYPE_AS_LIST, sizeof(struct as_list)); } static void as_list_free(struct as_list *aslist) { if (aslist->name) { XFREE(MTYPE_AS_STR, aslist->name); aslist->name = NULL; } XFREE(MTYPE_AS_LIST, aslist); } /* Insert new AS list to list of as_list. Each as_list is sorted by the name. */ static struct as_list *as_list_insert(const char *name) { size_t i; long number; struct as_list *aslist; struct as_list *point; struct as_list_list *list; /* Allocate new access_list and copy given name. */ aslist = as_list_new(); aslist->name = XSTRDUP(MTYPE_AS_STR, name); assert(aslist->name); /* If name is made by all digit character. We treat it as number. */ for (number = 0, i = 0; i < strlen(name); i++) { if (isdigit((unsigned char)name[i])) number = (number * 10) + (name[i] - '0'); else break; } /* In case of name is all digit character */ if (i == strlen(name)) { aslist->type = ACCESS_TYPE_NUMBER; /* Set access_list to number list. */ list = &as_list_master.num; for (point = list->head; point; point = point->next) if (atol(point->name) >= number) break; } else { aslist->type = ACCESS_TYPE_STRING; /* Set access_list to string list. */ list = &as_list_master.str; /* Set point to insertion point. */ for (point = list->head; point; point = point->next) if (strcmp(point->name, name) >= 0) break; } /* In case of this is the first element of master. */ if (list->head == NULL) { list->head = list->tail = aslist; return aslist; } /* In case of insertion is made at the tail of access_list. */ if (point == NULL) { aslist->prev = list->tail; list->tail->next = aslist; list->tail = aslist; return aslist; } /* In case of insertion is made at the head of access_list. */ if (point == list->head) { aslist->next = list->head; list->head->prev = aslist; list->head = aslist; return aslist; } /* Insertion is made at middle of the access_list. */ aslist->next = point; aslist->prev = point->prev; if (point->prev) point->prev->next = aslist; point->prev = aslist; return aslist; } static struct as_list *as_list_get(const char *name) { struct as_list *aslist; aslist = as_list_lookup(name); if (aslist == NULL) aslist = as_list_insert(name); return aslist; } static const char *filter_type_str(enum as_filter_type type) { switch (type) { case AS_FILTER_PERMIT: return "permit"; case AS_FILTER_DENY: return "deny"; default: return ""; } } static void as_list_delete(struct as_list *aslist) { struct as_list_list *list; struct as_filter *filter, *next; for (filter = aslist->head; filter; filter = next) { next = filter->next; as_filter_free(filter); } if (aslist->type == ACCESS_TYPE_NUMBER) list = &as_list_master.num; else list = &as_list_master.str; if (aslist->next) aslist->next->prev = aslist->prev; else list->tail = aslist->prev; if (aslist->prev) aslist->prev->next = aslist->next; else list->head = aslist->next; as_list_free(aslist); } static int as_list_empty(struct as_list *aslist) { if (aslist->head == NULL && aslist->tail == NULL) return 1; else return 0; } static void as_list_filter_delete(struct as_list *aslist, struct as_filter *asfilter) { char *name = XSTRDUP(MTYPE_AS_STR, aslist->name); if (asfilter->next) asfilter->next->prev = asfilter->prev; else aslist->tail = asfilter->prev; if (asfilter->prev) asfilter->prev->next = asfilter->next; else aslist->head = asfilter->next; as_filter_free(asfilter); /* If access_list becomes empty delete it from access_master. */ if (as_list_empty(aslist)) as_list_delete(aslist); /* Run hook function. */ if (as_list_master.delete_hook) (*as_list_master.delete_hook)(name); XFREE(MTYPE_AS_STR, name); } static int as_filter_match(struct as_filter *asfilter, struct aspath *aspath) { if (bgp_regexec(asfilter->reg, aspath) != REG_NOMATCH) return 1; return 0; } /* Apply AS path filter to AS. */ enum as_filter_type as_list_apply(struct as_list *aslist, void *object) { struct as_filter *asfilter; struct aspath *aspath; aspath = (struct aspath *)object; if (aslist == NULL) return AS_FILTER_DENY; for (asfilter = aslist->head; asfilter; asfilter = asfilter->next) { if (as_filter_match(asfilter, aspath)) return asfilter->type; } return AS_FILTER_DENY; } /* Add hook function. */ void as_list_add_hook(void (*func)(char *)) { as_list_master.add_hook = func; } /* Delete hook function. */ void as_list_delete_hook(void (*func)(const char *)) { as_list_master.delete_hook = func; } static int as_list_dup_check(struct as_list *aslist, struct as_filter *new) { struct as_filter *asfilter; for (asfilter = aslist->head; asfilter; asfilter = asfilter->next) { if (asfilter->type == new->type && strcmp(asfilter->reg_str, new->reg_str) == 0) return 1; } return 0; } int config_bgp_aspath_validate(const char *regstr) { char valid_chars[] = "1234567890_^|[,{}() ]$*+.?-\\"; if (strspn(regstr, valid_chars) == strlen(regstr)) return 1; return 0; } DEFUN(as_path, bgp_as_path_cmd, "bgp as-path access-list WORD LINE...", BGP_STR "BGP autonomous system path filter\n" "Specify an access list name\n" "Regular expression access list name\n" "Specify packets to reject\n" "Specify packets to forward\n" "A regular-expression (1234567890_^|[,{}() ]$*+.?-\\) to match the BGP AS paths\n") { int idx = 0; enum as_filter_type type; struct as_filter *asfilter; struct as_list *aslist; regex_t *regex; char *regstr; if (argv_find(argv, argc, "ip", &idx)) { vty_out(vty, "This config option is deprecated and is scheduled for removal.\n"); vty_out(vty, "if you are using this please migrate to the below command\n"); vty_out(vty, "'bgp as-path access-list WORD LINE'\n"); zlog_warn("Deprecated option: 'ip as-path access-list WORD LINE' being used"); } /* Retrieve access list name */ argv_find(argv, argc, "WORD", &idx); char *alname = argv[idx]->arg; /* Check the filter type. */ type = argv_find(argv, argc, "deny", &idx) ? AS_FILTER_DENY : AS_FILTER_PERMIT; /* Check AS path regex. */ argv_find(argv, argc, "LINE", &idx); regstr = argv_concat(argv, argc, idx); regex = bgp_regcomp(regstr); if (!regex) { vty_out(vty, "can't compile regexp %s\n", regstr); XFREE(MTYPE_TMP, regstr); return CMD_WARNING_CONFIG_FAILED; } if (!config_bgp_aspath_validate(regstr)) { vty_out(vty, "Invalid character in as-path access-list %s\n", regstr); return CMD_WARNING_CONFIG_FAILED; } asfilter = as_filter_make(regex, regstr, type); XFREE(MTYPE_TMP, regstr); /* Install new filter to the access_list. */ aslist = as_list_get(alname); /* Duplicate insertion check. */; if (as_list_dup_check(aslist, asfilter)) as_filter_free(asfilter); else as_list_filter_add(aslist, asfilter); return CMD_SUCCESS; } #if CONFDATE > 20191005 CPP_NOTICE("bgpd: remove deprecated 'ip as-path access-list WORD LINE' command") #endif ALIAS(as_path, ip_as_path_cmd, "ip as-path access-list WORD LINE...", IP_STR "BGP autonomous system path filter\n" "Specify an access list name\n" "Regular expression access list name\n" "Specify packets to reject\n" "Specify packets to forward\n" "A regular-expression (1234567890_^|[,{}() ]$*+.?-\\) to match the BGP AS paths\n") DEFUN(no_as_path, no_bgp_as_path_cmd, "no bgp as-path access-list WORD LINE...", NO_STR BGP_STR "BGP autonomous system path filter\n" "Specify an access list name\n" "Regular expression access list name\n" "Specify packets to reject\n" "Specify packets to forward\n" "A regular-expression (1234567890_^|[,{}() ]$*+.?-\\) to match the BGP AS paths\n") { int idx = 0; enum as_filter_type type; struct as_filter *asfilter; struct as_list *aslist; char *regstr; regex_t *regex; if (argv_find(argv, argc, "ip", &idx)) { vty_out(vty, "This config option is deprecated, and is scheduled for removal.\n"); vty_out(vty, "if you are using this please migrate to the below command\n"); vty_out(vty, "'no bgp as-path access-list WORD LINE'\n"); zlog_warn("Deprecated option: 'no ip as-path access-list WORD LINE' being used"); } char *aslistname = argv_find(argv, argc, "WORD", &idx) ? argv[idx]->arg : NULL; /* Lookup AS list from AS path list. */ aslist = as_list_lookup(aslistname); if (aslist == NULL) { vty_out(vty, "bgp as-path access-list %s doesn't exist\n", aslistname); return CMD_WARNING_CONFIG_FAILED; } /* Check the filter type. */ if (argv_find(argv, argc, "permit", &idx)) type = AS_FILTER_PERMIT; else if (argv_find(argv, argc, "deny", &idx)) type = AS_FILTER_DENY; else { vty_out(vty, "filter type must be [permit|deny]\n"); return CMD_WARNING_CONFIG_FAILED; } /* Compile AS path. */ argv_find(argv, argc, "LINE", &idx); regstr = argv_concat(argv, argc, idx); if (!config_bgp_aspath_validate(regstr)) { vty_out(vty, "Invalid character in as-path access-list %s\n", regstr); return CMD_WARNING_CONFIG_FAILED; } regex = bgp_regcomp(regstr); if (!regex) { vty_out(vty, "can't compile regexp %s\n", regstr); XFREE(MTYPE_TMP, regstr); return CMD_WARNING_CONFIG_FAILED; } /* Lookup asfilter. */ asfilter = as_filter_lookup(aslist, regstr, type); XFREE(MTYPE_TMP, regstr); bgp_regex_free(regex); if (asfilter == NULL) { vty_out(vty, "\n"); return CMD_WARNING_CONFIG_FAILED; } as_list_filter_delete(aslist, asfilter); return CMD_SUCCESS; } ALIAS(no_as_path, no_ip_as_path_cmd, "no ip as-path access-list WORD LINE...", NO_STR IP_STR "BGP autonomous system path filter\n" "Specify an access list name\n" "Regular expression access list name\n" "Specify packets to reject\n" "Specify packets to forward\n" "A regular-expression (1234567890_^|[,{}() ]$*+.?-\\) to match the BGP AS paths\n") DEFUN (no_as_path_all, no_bgp_as_path_all_cmd, "no bgp as-path access-list WORD", NO_STR BGP_STR "BGP autonomous system path filter\n" "Specify an access list name\n" "Regular expression access list name\n") { int idx_word = 4; struct as_list *aslist; int idx = 0; if (argv_find(argv, argc, "ip", &idx)) { vty_out(vty, "This config option is deprecated, and is scheduled for removal.\n"); vty_out(vty, "if you are using this please migrate to the below command\n"); vty_out(vty, "'no bgp as-path access-list WORD'\n"); zlog_warn("Deprecated option: `no ip as-path access-list WORD` being used"); } aslist = as_list_lookup(argv[idx_word]->arg); if (aslist == NULL) { vty_out(vty, "bgp as-path access-list %s doesn't exist\n", argv[idx_word]->arg); return CMD_WARNING_CONFIG_FAILED; } as_list_delete(aslist); /* Run hook function. */ if (as_list_master.delete_hook) (*as_list_master.delete_hook)(argv[idx_word]->arg); return CMD_SUCCESS; } ALIAS (no_as_path_all, no_ip_as_path_all_cmd, "no ip as-path access-list WORD", NO_STR IP_STR "BGP autonomous system path filter\n" "Specify an access list name\n" "Regular expression access list name\n") static void as_list_show(struct vty *vty, struct as_list *aslist) { struct as_filter *asfilter; vty_out(vty, "AS path access list %s\n", aslist->name); for (asfilter = aslist->head; asfilter; asfilter = asfilter->next) { vty_out(vty, " %s %s\n", filter_type_str(asfilter->type), asfilter->reg_str); } } static void as_list_show_all(struct vty *vty) { struct as_list *aslist; struct as_filter *asfilter; for (aslist = as_list_master.num.head; aslist; aslist = aslist->next) { vty_out(vty, "AS path access list %s\n", aslist->name); for (asfilter = aslist->head; asfilter; asfilter = asfilter->next) { vty_out(vty, " %s %s\n", filter_type_str(asfilter->type), asfilter->reg_str); } } for (aslist = as_list_master.str.head; aslist; aslist = aslist->next) { vty_out(vty, "AS path access list %s\n", aslist->name); for (asfilter = aslist->head; asfilter; asfilter = asfilter->next) { vty_out(vty, " %s %s\n", filter_type_str(asfilter->type), asfilter->reg_str); } } } DEFUN (show_as_path_access_list, show_bgp_as_path_access_list_cmd, "show bgp as-path-access-list WORD", SHOW_STR BGP_STR "List AS path access lists\n" "AS path access list name\n") { int idx_word = 3; struct as_list *aslist; int idx = 0; if (argv_find(argv, argc, "ip", &idx)) { vty_out(vty, "This config option is deprecated, and is scheduled for removal.\n"); vty_out(vty, "if you are using this please migrate to the below command\n"); vty_out(vty, "'show bgp as-path-access-list WORD'\n"); zlog_warn("Deprecated option: 'show ip as-path-access-list WORD' being used"); } aslist = as_list_lookup(argv[idx_word]->arg); if (aslist) as_list_show(vty, aslist); return CMD_SUCCESS; } ALIAS (show_as_path_access_list, show_ip_as_path_access_list_cmd, "show ip as-path-access-list WORD", SHOW_STR IP_STR "List AS path access lists\n" "AS path access list name\n") DEFUN (show_as_path_access_list_all, show_bgp_as_path_access_list_all_cmd, "show bgp as-path-access-list", SHOW_STR BGP_STR "List AS path access lists\n") { int idx = 0; if (argv_find(argv, argc, "ip", &idx)) { vty_out(vty, "This config option is deprecated, and is scheduled for removal.\n"); vty_out(vty, "if you are using this please migrate to the below command\n"); vty_out(vty, "'show bgp as-path-access-list'\n"); zlog_warn("Deprecated option: 'show ip as-path-access-list' being used"); } as_list_show_all(vty); return CMD_SUCCESS; } ALIAS (show_as_path_access_list_all, show_ip_as_path_access_list_all_cmd, "show ip as-path-access-list", SHOW_STR IP_STR "List AS path access lists\n") static int config_write_as_list(struct vty *vty) { struct as_list *aslist; struct as_filter *asfilter; int write = 0; for (aslist = as_list_master.num.head; aslist; aslist = aslist->next) for (asfilter = aslist->head; asfilter; asfilter = asfilter->next) { vty_out(vty, "bgp as-path access-list %s %s %s\n", aslist->name, filter_type_str(asfilter->type), asfilter->reg_str); write++; } for (aslist = as_list_master.str.head; aslist; aslist = aslist->next) for (asfilter = aslist->head; asfilter; asfilter = asfilter->next) { vty_out(vty, "bgp as-path access-list %s %s %s\n", aslist->name, filter_type_str(asfilter->type), asfilter->reg_str); write++; } return write; } static struct cmd_node as_list_node = {AS_LIST_NODE, "", 1}; /* Register functions. */ void bgp_filter_init(void) { install_node(&as_list_node, config_write_as_list); install_element(CONFIG_NODE, &bgp_as_path_cmd); install_element(CONFIG_NODE, &ip_as_path_cmd); install_element(CONFIG_NODE, &no_bgp_as_path_cmd); install_element(CONFIG_NODE, &no_ip_as_path_cmd); install_element(CONFIG_NODE, &no_bgp_as_path_all_cmd); install_element(CONFIG_NODE, &no_ip_as_path_all_cmd); install_element(VIEW_NODE, &show_bgp_as_path_access_list_cmd); install_element(VIEW_NODE, &show_ip_as_path_access_list_cmd); install_element(VIEW_NODE, &show_bgp_as_path_access_list_all_cmd); install_element(VIEW_NODE, &show_ip_as_path_access_list_all_cmd); } void bgp_filter_reset(void) { struct as_list *aslist; struct as_list *next; for (aslist = as_list_master.num.head; aslist; aslist = next) { next = aslist->next; as_list_delete(aslist); } for (aslist = as_list_master.str.head; aslist; aslist = next) { next = aslist->next; as_list_delete(aslist); } assert(as_list_master.num.head == NULL); assert(as_list_master.num.tail == NULL); assert(as_list_master.str.head == NULL); assert(as_list_master.str.tail == NULL); } frr-7.2.1/bgpd/bgp_filter.h0000644000000000000000000000247613610377563012427 00000000000000/* AS path filter list. * Copyright (C) 1999 Kunihiro Ishiguro * * This file is part of GNU Zebra. * * GNU Zebra is free software; you can redistribute it and/or modify it * under the terms of the GNU General Public License as published by the * Free Software Foundation; either version 2, or (at your option) any * later version. * * GNU Zebra 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; see the file COPYING; if not, write to the Free Software * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA */ #ifndef _QUAGGA_BGP_FILTER_H #define _QUAGGA_BGP_FILTER_H enum as_filter_type { AS_FILTER_DENY, AS_FILTER_PERMIT }; extern void bgp_filter_init(void); extern void bgp_filter_reset(void); extern enum as_filter_type as_list_apply(struct as_list *, void *); extern struct as_list *as_list_lookup(const char *); extern void as_list_add_hook(void (*func)(char *)); extern void as_list_delete_hook(void (*func)(const char *)); extern int config_bgp_aspath_validate(const char *regstr); #endif /* _QUAGGA_BGP_FILTER_H */ frr-7.2.1/bgpd/bgp_flowspec.c0000644000000000000000000001264513610377563012756 00000000000000/* BGP FlowSpec for packet handling * Portions: * Copyright (C) 2017 ChinaTelecom SDN Group * Copyright (C) 2018 6WIND * * FRRouting is free software; you can redistribute it and/or modify it * under the terms of the GNU General Public License as published by the * Free Software Foundation; either version 2, or (at your option) any * later version. * * FRRouting 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; see the file COPYING; if not, write to the Free Software * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA */ #include #include #include "prefix.h" #include "lib_errors.h" #include "bgpd/bgpd.h" #include "bgpd/bgp_route.h" #include "bgpd/bgp_flowspec.h" #include "bgpd/bgp_flowspec_util.h" #include "bgpd/bgp_flowspec_private.h" #include "bgpd/bgp_ecommunity.h" #include "bgpd/bgp_debug.h" #include "bgpd/bgp_errors.h" static int bgp_fs_nlri_validate(uint8_t *nlri_content, uint32_t len) { uint32_t offset = 0; int type; int ret = 0, error = 0; while (offset < len-1) { type = nlri_content[offset]; offset++; switch (type) { case FLOWSPEC_DEST_PREFIX: case FLOWSPEC_SRC_PREFIX: ret = bgp_flowspec_ip_address( BGP_FLOWSPEC_VALIDATE_ONLY, nlri_content + offset, len - offset, NULL, &error); break; case FLOWSPEC_IP_PROTOCOL: case FLOWSPEC_PORT: case FLOWSPEC_DEST_PORT: case FLOWSPEC_SRC_PORT: case FLOWSPEC_ICMP_TYPE: case FLOWSPEC_ICMP_CODE: ret = bgp_flowspec_op_decode(BGP_FLOWSPEC_VALIDATE_ONLY, nlri_content + offset, len - offset, NULL, &error); break; case FLOWSPEC_TCP_FLAGS: case FLOWSPEC_FRAGMENT: ret = bgp_flowspec_bitmask_decode( BGP_FLOWSPEC_VALIDATE_ONLY, nlri_content + offset, len - offset, NULL, &error); break; case FLOWSPEC_PKT_LEN: case FLOWSPEC_DSCP: ret = bgp_flowspec_op_decode( BGP_FLOWSPEC_VALIDATE_ONLY, nlri_content + offset, len - offset, NULL, &error); break; default: error = -1; break; } offset += ret; if (error < 0) break; } return error; } int bgp_nlri_parse_flowspec(struct peer *peer, struct attr *attr, struct bgp_nlri *packet, int withdraw) { uint8_t *pnt; uint8_t *lim; afi_t afi; safi_t safi; int psize = 0; struct prefix p; int ret; void *temp; /* Start processing the NLRI - there may be multiple in the MP_REACH */ pnt = packet->nlri; lim = pnt + packet->length; afi = packet->afi; safi = packet->safi; if (afi == AFI_IP6) { flog_err(EC_LIB_DEVELOPMENT, "BGP flowspec IPv6 not supported"); return BGP_NLRI_PARSE_ERROR_FLOWSPEC_IPV6_NOT_SUPPORTED; } if (packet->length >= FLOWSPEC_NLRI_SIZELIMIT) { flog_err(EC_BGP_FLOWSPEC_PACKET, "BGP flowspec nlri length maximum reached (%u)", packet->length); return BGP_NLRI_PARSE_ERROR_FLOWSPEC_NLRI_SIZELIMIT; } for (; pnt < lim; pnt += psize) { /* Clear prefix structure. */ memset(&p, 0, sizeof(struct prefix)); /* All FlowSpec NLRI begin with length. */ if (pnt + 1 > lim) return BGP_NLRI_PARSE_ERROR_PACKET_OVERFLOW; psize = *pnt++; /* When packet overflow occur return immediately. */ if (pnt + psize > lim) { flog_err( EC_BGP_FLOWSPEC_PACKET, "Flowspec NLRI length inconsistent ( size %u seen)", psize); return BGP_NLRI_PARSE_ERROR_PACKET_OVERFLOW; } if (bgp_fs_nlri_validate(pnt, psize) < 0) { flog_err( EC_BGP_FLOWSPEC_PACKET, "Bad flowspec format or NLRI options not supported"); return BGP_NLRI_PARSE_ERROR_FLOWSPEC_BAD_FORMAT; } p.family = AF_FLOWSPEC; p.prefixlen = 0; /* Flowspec encoding is in bytes */ p.u.prefix_flowspec.prefixlen = psize; temp = XCALLOC(MTYPE_TMP, psize); memcpy(temp, pnt, psize); p.u.prefix_flowspec.ptr = (uintptr_t) temp; if (BGP_DEBUG(flowspec, FLOWSPEC)) { char return_string[BGP_FLOWSPEC_NLRI_STRING_MAX]; char local_string[BGP_FLOWSPEC_NLRI_STRING_MAX*2+16]; char ec_string[BGP_FLOWSPEC_NLRI_STRING_MAX]; char *s = NULL; bgp_fs_nlri_get_string((unsigned char *) p.u.prefix_flowspec.ptr, p.u.prefix_flowspec.prefixlen, return_string, NLRI_STRING_FORMAT_MIN, NULL); snprintf(ec_string, sizeof(ec_string), "EC{none}"); if (attr && attr->ecommunity) { s = ecommunity_ecom2str(attr->ecommunity, ECOMMUNITY_FORMAT_ROUTE_MAP, 0); snprintf(ec_string, sizeof(ec_string), "EC{%s}", s == NULL ? "none" : s); if (s) ecommunity_strfree(&s); } snprintf(local_string, sizeof(local_string), "FS Rx %s %s %s %s", withdraw ? "Withdraw":"Update", afi2str(afi), return_string, attr != NULL ? ec_string : ""); zlog_info("%s", local_string); } /* Process the route. */ if (!withdraw) ret = bgp_update(peer, &p, 0, attr, afi, safi, ZEBRA_ROUTE_BGP, BGP_ROUTE_NORMAL, NULL, NULL, 0, 0, NULL); else ret = bgp_withdraw(peer, &p, 0, attr, afi, safi, ZEBRA_ROUTE_BGP, BGP_ROUTE_NORMAL, NULL, NULL, 0, NULL); if (ret) { flog_err(EC_BGP_FLOWSPEC_INSTALLATION, "Flowspec NLRI failed to be %s.", attr ? "added" : "withdrawn"); return BGP_NLRI_PARSE_ERROR; } } return BGP_NLRI_PARSE_OK; } frr-7.2.1/bgpd/bgp_flowspec.h0000644000000000000000000000421013610377563012750 00000000000000/* BGP Flowspec header for packet handling * Copyright (C) 2018 6WIND * * FRRouting is free software; you can redistribute it and/or modify it * under the terms of the GNU General Public License as published by the * Free Software Foundation; either version 2, or (at your option) any * later version. * * FRRouting 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; see the file COPYING; if not, write to the Free Software * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA */ #ifndef _FRR_BGP_FLOWSPEC_H #define _FRR_BGP_FLOWSPEC_H #define NLRI_STRING_FORMAT_LARGE 0 #define NLRI_STRING_FORMAT_DEBUG 1 #define NLRI_STRING_FORMAT_MIN 2 #define NLRI_STRING_FORMAT_JSON 3 #define NLRI_STRING_FORMAT_JSON_SIMPLE 4 #define BGP_FLOWSPEC_NLRI_STRING_MAX 512 extern int bgp_nlri_parse_flowspec(struct peer *peer, struct attr *attr, struct bgp_nlri *packet, int withdraw); extern void bgp_flowspec_vty_init(void); extern int bgp_show_table_flowspec(struct vty *vty, struct bgp *bgp, afi_t afi, struct bgp_table *table, enum bgp_show_type type, void *output_arg, bool use_json, int is_last, unsigned long *output_cum, unsigned long *total_cum); extern void bgp_fs_nlri_get_string(unsigned char *nlri_content, size_t len, char *return_string, int format, json_object *json_path); extern void route_vty_out_flowspec(struct vty *vty, struct prefix *p, struct bgp_path_info *path, int display, json_object *json_paths); extern int bgp_fs_config_write_pbr(struct vty *vty, struct bgp *bgp, afi_t afi, safi_t safi); extern int bgp_flowspec_display_match_per_ip(afi_t afi, struct bgp_table *rib, struct prefix *match, int prefix_check, struct vty *vty, bool use_json, json_object *json_paths); #endif /* _FRR_BGP_FLOWSPEC_H */ frr-7.2.1/bgpd/bgp_flowspec_private.h0000644000000000000000000000301313610377563014502 00000000000000/* BGP Flowspec header . private structs and defines * Copyright (C) 2018 6WIND * * FRRouting is free software; you can redistribute it and/or modify it * under the terms of the GNU General Public License as published by the * Free Software Foundation; either version 2, or (at your option) any * later version. * * FRRouting 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; see the file COPYING; if not, write to the Free Software * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA */ #ifndef _FRR_BGP_FLOWSPEC_PRIVATE_H #define _FRR_BGP_FLOWSPEC_PRIVATE_H #define FLOWSPEC_NLRI_SIZELIMIT 240 /* Flowspec raffic action bit*/ #define FLOWSPEC_TRAFFIC_ACTION_TERMINAL 1 #define FLOWSPEC_TRAFFIC_ACTION_SAMPLE 0 #define FLOWSPEC_TRAFFIC_ACTION_DISTRIBUTE 1 /* Flow Spec Component Types */ #define NUM_OF_FLOWSPEC_MATCH_TYPES 12 #define FLOWSPEC_DEST_PREFIX 1 #define FLOWSPEC_SRC_PREFIX 2 #define FLOWSPEC_IP_PROTOCOL 3 #define FLOWSPEC_PORT 4 #define FLOWSPEC_DEST_PORT 5 #define FLOWSPEC_SRC_PORT 6 #define FLOWSPEC_ICMP_TYPE 7 #define FLOWSPEC_ICMP_CODE 8 #define FLOWSPEC_TCP_FLAGS 9 #define FLOWSPEC_PKT_LEN 10 #define FLOWSPEC_DSCP 11 #define FLOWSPEC_FRAGMENT 12 #endif /* _FRR_BGP_FLOWSPEC_PRIVATE_H */ frr-7.2.1/bgpd/bgp_flowspec_util.c0000644000000000000000000004024313610377563014006 00000000000000/* BGP FlowSpec Utilities * Portions: * Copyright (C) 2017 ChinaTelecom SDN Group * Copyright (C) 2018 6WIND * * FRRouting is free software; you can redistribute it and/or modify it * under the terms of the GNU General Public License as published by the * Free Software Foundation; either version 2, or (at your option) any * later version. * * FRRouting 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; see the file COPYING; if not, write to the Free Software * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA */ #include "zebra.h" #include "prefix.h" #include "lib_errors.h" #include "bgp_route.h" #include "bgp_table.h" #include "bgp_flowspec_util.h" #include "bgp_flowspec_private.h" #include "bgp_pbr.h" #include "bgp_errors.h" static void hex2bin(uint8_t *hex, int *bin) { int remainder = *hex; int i = 0; while (remainder >= 1 && i < 8) { bin[7-i] = remainder % 2; remainder = remainder / 2; i++; } for (; i < 8; i++) bin[7-i] = 0; } static int hexstr2num(uint8_t *hexstr, int len) { int i = 0; int num = 0; for (i = 0; i < len; i++) num = hexstr[i] + 16*16*num; return num; } /* call bgp_flowspec_op_decode * returns offset */ static int bgp_flowspec_call_non_opaque_decode(uint8_t *nlri_content, int len, struct bgp_pbr_match_val *mval, uint8_t *match_num, int *error) { int ret; ret = bgp_flowspec_op_decode( BGP_FLOWSPEC_CONVERT_TO_NON_OPAQUE, nlri_content, len, mval, error); if (*error < 0) flog_err(EC_BGP_FLOWSPEC_PACKET, "%s: flowspec_op_decode error %d", __func__, *error); else *match_num = *error; return ret; } bool bgp_flowspec_contains_prefix(struct prefix *pfs, struct prefix *input, int prefix_check) { uint32_t offset = 0; int type; int ret = 0, error = 0; uint8_t *nlri_content = (uint8_t *)pfs->u.prefix_flowspec.ptr; size_t len = pfs->u.prefix_flowspec.prefixlen; struct prefix compare; error = 0; while (offset < len-1 && error >= 0) { type = nlri_content[offset]; offset++; switch (type) { case FLOWSPEC_DEST_PREFIX: case FLOWSPEC_SRC_PREFIX: memset(&compare, 0, sizeof(struct prefix)); ret = bgp_flowspec_ip_address( BGP_FLOWSPEC_CONVERT_TO_NON_OPAQUE, nlri_content+offset, len - offset, &compare, &error); if (ret <= 0) break; if (prefix_check && compare.prefixlen != input->prefixlen) break; if (compare.family != input->family) break; if ((input->family == AF_INET) && IPV4_ADDR_SAME(&input->u.prefix4, &compare.u.prefix4)) return true; if ((input->family == AF_INET6) && IPV6_ADDR_SAME(&input->u.prefix6.s6_addr, &compare.u.prefix6.s6_addr)) return true; break; case FLOWSPEC_IP_PROTOCOL: case FLOWSPEC_PORT: case FLOWSPEC_DEST_PORT: case FLOWSPEC_SRC_PORT: case FLOWSPEC_ICMP_TYPE: case FLOWSPEC_ICMP_CODE: ret = bgp_flowspec_op_decode(BGP_FLOWSPEC_VALIDATE_ONLY, nlri_content+offset, len - offset, NULL, &error); break; case FLOWSPEC_FRAGMENT: case FLOWSPEC_TCP_FLAGS: ret = bgp_flowspec_bitmask_decode( BGP_FLOWSPEC_VALIDATE_ONLY, nlri_content+offset, len - offset, NULL, &error); break; case FLOWSPEC_PKT_LEN: case FLOWSPEC_DSCP: ret = bgp_flowspec_op_decode( BGP_FLOWSPEC_VALIDATE_ONLY, nlri_content + offset, len - offset, NULL, &error); break; default: error = -1; break; } offset += ret; } return false; } /* * handle the flowspec address src/dst or generic address NLRI * return number of bytes analysed ( >= 0). */ int bgp_flowspec_ip_address(enum bgp_flowspec_util_nlri_t type, uint8_t *nlri_ptr, uint32_t max_len, void *result, int *error) { char *display = (char *)result; /* for return_string */ struct prefix *prefix = (struct prefix *)result; uint32_t offset = 0; struct prefix prefix_local; int psize; *error = 0; memset(&prefix_local, 0, sizeof(struct prefix)); /* read the prefix length */ prefix_local.prefixlen = nlri_ptr[offset]; psize = PSIZE(prefix_local.prefixlen); offset++; /* TODO Flowspec IPv6 Support */ prefix_local.family = AF_INET; /* Prefix length check. */ if (prefix_local.prefixlen > prefix_blen(&prefix_local) * 8) *error = -1; /* When packet overflow occur return immediately. */ if (psize + offset > max_len) *error = -1; /* Defensive coding, double-check * the psize fits in a struct prefix */ if (psize > (ssize_t)sizeof(prefix_local.u)) *error = -1; memcpy(&prefix_local.u.prefix, &nlri_ptr[offset], psize); offset += psize; switch (type) { case BGP_FLOWSPEC_RETURN_STRING: prefix2str(&prefix_local, display, BGP_FLOWSPEC_STRING_DISPLAY_MAX); break; case BGP_FLOWSPEC_CONVERT_TO_NON_OPAQUE: PREFIX_COPY_IPV4(prefix, &prefix_local) break; case BGP_FLOWSPEC_VALIDATE_ONLY: default: break; } return offset; } /* * handle the flowspec operator NLRI * return number of bytes analysed * if there is an error, the passed error param is used to give error: * -1 if decoding error, * if result is a string, its assumed length * is BGP_FLOWSPEC_STRING_DISPLAY_MAX */ int bgp_flowspec_op_decode(enum bgp_flowspec_util_nlri_t type, uint8_t *nlri_ptr, uint32_t max_len, void *result, int *error) { int op[8]; int len, value, value_size; int loop = 0; char *ptr = (char *)result; /* for return_string */ uint32_t offset = 0; int len_string = BGP_FLOWSPEC_STRING_DISPLAY_MAX; int len_written; struct bgp_pbr_match_val *mval = (struct bgp_pbr_match_val *)result; *error = 0; do { if (loop > BGP_PBR_MATCH_VAL_MAX) *error = -2; hex2bin(&nlri_ptr[offset], op); offset++; len = 2*op[2]+op[3]; value_size = 1 << len; value = hexstr2num(&nlri_ptr[offset], value_size); /* can not be < and > at the same time */ if (op[5] == 1 && op[6] == 1) *error = -1; /* if first element, AND bit can not be set */ if (op[1] == 1 && loop == 0) *error = -1; switch (type) { case BGP_FLOWSPEC_RETURN_STRING: if (loop) { len_written = snprintf(ptr, len_string, ", "); len_string -= len_written; ptr += len_written; } if (op[5] == 1) { len_written = snprintf(ptr, len_string, "<"); len_string -= len_written; ptr += len_written; } if (op[6] == 1) { len_written = snprintf(ptr, len_string, ">"); len_string -= len_written; ptr += len_written; } if (op[7] == 1) { len_written = snprintf(ptr, len_string, "="); len_string -= len_written; ptr += len_written; } len_written = snprintf(ptr, len_string, " %d ", value); len_string -= len_written; ptr += len_written; break; case BGP_FLOWSPEC_CONVERT_TO_NON_OPAQUE: /* limitation: stop converting */ if (*error == -2) break; mval->value = value; if (op[5] == 1) mval->compare_operator |= OPERATOR_COMPARE_LESS_THAN; if (op[6] == 1) mval->compare_operator |= OPERATOR_COMPARE_GREATER_THAN; if (op[7] == 1) mval->compare_operator |= OPERATOR_COMPARE_EQUAL_TO; if (op[1] == 1) mval->unary_operator = OPERATOR_UNARY_AND; else mval->unary_operator = OPERATOR_UNARY_OR; mval++; break; case BGP_FLOWSPEC_VALIDATE_ONLY: default: /* no action */ break; } offset += value_size; loop++; } while (op[0] == 0 && offset < max_len - 1); if (offset > max_len) *error = -1; /* use error parameter to count the number of entries */ if (*error == 0) *error = loop; return offset; } /* * handle the flowspec tcpflags or fragment field * return number of bytes analysed * if there is an error, the passed error param is used to give error: * -1 if decoding error, * if result is a string, its assumed length * is BGP_FLOWSPEC_STRING_DISPLAY_MAX */ int bgp_flowspec_bitmask_decode(enum bgp_flowspec_util_nlri_t type, uint8_t *nlri_ptr, uint32_t max_len, void *result, int *error) { int op[8]; int len, value_size, loop = 0, value; char *ptr = (char *)result; /* for return_string */ struct bgp_pbr_match_val *mval = (struct bgp_pbr_match_val *)result; uint32_t offset = 0; int len_string = BGP_FLOWSPEC_STRING_DISPLAY_MAX; int len_written; *error = 0; do { if (loop > BGP_PBR_MATCH_VAL_MAX) *error = -2; hex2bin(&nlri_ptr[offset], op); /* if first element, AND bit can not be set */ if (op[1] == 1 && loop == 0) *error = -1; offset++; len = 2 * op[2] + op[3]; value_size = 1 << len; value = hexstr2num(&nlri_ptr[offset], value_size); switch (type) { case BGP_FLOWSPEC_RETURN_STRING: if (op[1] == 1 && loop != 0) { len_written = snprintf(ptr, len_string, ",&"); len_string -= len_written; ptr += len_written; } else if (op[1] == 0 && loop != 0) { len_written = snprintf(ptr, len_string, ",|"); len_string -= len_written; ptr += len_written; } if (op[7] == 1) { len_written = snprintf(ptr, len_string, "= "); len_string -= len_written; ptr += len_written; } else { len_written = snprintf(ptr, len_string, "∋ "); len_string -= len_written; ptr += len_written; } if (op[6] == 1) { len_written = snprintf(ptr, len_string, "! "); len_string -= len_written; ptr += len_written; } len_written = snprintf(ptr, len_string, "%d", value); len_string -= len_written; ptr += len_written; break; case BGP_FLOWSPEC_CONVERT_TO_NON_OPAQUE: /* limitation: stop converting */ if (*error == -2) break; mval->value = value; if (op[6] == 1) { /* different from */ mval->compare_operator |= OPERATOR_COMPARE_LESS_THAN; mval->compare_operator |= OPERATOR_COMPARE_GREATER_THAN; } else mval->compare_operator |= OPERATOR_COMPARE_EQUAL_TO; if (op[7] == 1) mval->compare_operator |= OPERATOR_COMPARE_EXACT_MATCH; if (op[1] == 1) mval->unary_operator = OPERATOR_UNARY_AND; else mval->unary_operator = OPERATOR_UNARY_OR; mval++; break; case BGP_FLOWSPEC_VALIDATE_ONLY: default: /* no action */ break; } offset += value_size; loop++; } while (op[0] == 0 && offset < max_len - 1); if (offset > max_len) *error = -1; /* use error parameter to count the number of entries */ if (*error == 0) *error = loop; return offset; } int bgp_flowspec_match_rules_fill(uint8_t *nlri_content, int len, struct bgp_pbr_entry_main *bpem) { int offset = 0, error = 0; struct prefix *prefix; struct bgp_pbr_match_val *mval; uint8_t *match_num; uint8_t bitmask = 0; int ret = 0, type; while (offset < len - 1 && error >= 0) { type = nlri_content[offset]; offset++; switch (type) { case FLOWSPEC_DEST_PREFIX: case FLOWSPEC_SRC_PREFIX: bitmask = 0; if (type == FLOWSPEC_DEST_PREFIX) { bitmask |= PREFIX_DST_PRESENT; prefix = &bpem->dst_prefix; } else { bitmask |= PREFIX_SRC_PRESENT; prefix = &bpem->src_prefix; } ret = bgp_flowspec_ip_address( BGP_FLOWSPEC_CONVERT_TO_NON_OPAQUE, nlri_content + offset, len - offset, prefix, &error); if (error < 0) flog_err(EC_BGP_FLOWSPEC_PACKET, "%s: flowspec_ip_address error %d", __func__, error); else { /* if src or dst address is 0.0.0.0, * ignore that rule */ if (prefix->family == AF_INET && prefix->u.prefix4.s_addr == 0) bpem->match_bitmask_iprule |= bitmask; else bpem->match_bitmask |= bitmask; } offset += ret; break; case FLOWSPEC_IP_PROTOCOL: match_num = &(bpem->match_protocol_num); mval = (struct bgp_pbr_match_val *) &(bpem->protocol); offset += bgp_flowspec_call_non_opaque_decode( nlri_content + offset, len - offset, mval, match_num, &error); break; case FLOWSPEC_PORT: match_num = &(bpem->match_port_num); mval = (struct bgp_pbr_match_val *) &(bpem->port); offset += bgp_flowspec_call_non_opaque_decode( nlri_content + offset, len - offset, mval, match_num, &error); break; case FLOWSPEC_DEST_PORT: match_num = &(bpem->match_dst_port_num); mval = (struct bgp_pbr_match_val *) &(bpem->dst_port); offset += bgp_flowspec_call_non_opaque_decode( nlri_content + offset, len - offset, mval, match_num, &error); break; case FLOWSPEC_SRC_PORT: match_num = &(bpem->match_src_port_num); mval = (struct bgp_pbr_match_val *) &(bpem->src_port); offset += bgp_flowspec_call_non_opaque_decode( nlri_content + offset, len - offset, mval, match_num, &error); break; case FLOWSPEC_ICMP_TYPE: match_num = &(bpem->match_icmp_type_num); mval = (struct bgp_pbr_match_val *) &(bpem->icmp_type); offset += bgp_flowspec_call_non_opaque_decode( nlri_content + offset, len - offset, mval, match_num, &error); break; case FLOWSPEC_ICMP_CODE: match_num = &(bpem->match_icmp_code_num); mval = (struct bgp_pbr_match_val *) &(bpem->icmp_code); offset += bgp_flowspec_call_non_opaque_decode( nlri_content + offset, len - offset, mval, match_num, &error); break; case FLOWSPEC_PKT_LEN: match_num = &(bpem->match_packet_length_num); mval = (struct bgp_pbr_match_val *) &(bpem->packet_length); offset += bgp_flowspec_call_non_opaque_decode( nlri_content + offset, len - offset, mval, match_num, &error); break; case FLOWSPEC_DSCP: match_num = &(bpem->match_dscp_num); mval = (struct bgp_pbr_match_val *) &(bpem->dscp); offset += bgp_flowspec_call_non_opaque_decode( nlri_content + offset, len - offset, mval, match_num, &error); break; case FLOWSPEC_TCP_FLAGS: ret = bgp_flowspec_bitmask_decode( BGP_FLOWSPEC_CONVERT_TO_NON_OPAQUE, nlri_content + offset, len - offset, &bpem->tcpflags, &error); if (error < 0) flog_err( EC_BGP_FLOWSPEC_PACKET, "%s: flowspec_tcpflags_decode error %d", __func__, error); else bpem->match_tcpflags_num = error; /* contains the number of slots used */ offset += ret; break; case FLOWSPEC_FRAGMENT: ret = bgp_flowspec_bitmask_decode( BGP_FLOWSPEC_CONVERT_TO_NON_OPAQUE, nlri_content + offset, len - offset, &bpem->fragment, &error); if (error < 0) flog_err( EC_BGP_FLOWSPEC_PACKET, "%s: flowspec_fragment_type_decode error %d", __func__, error); else bpem->match_fragment_num = error; offset += ret; break; default: flog_err(EC_LIB_DEVELOPMENT, "%s: unknown type %d\n", __func__, type); } } if (bpem->match_packet_length_num || bpem->match_fragment_num || bpem->match_tcpflags_num || bpem->match_dscp_num || bpem->match_packet_length_num || bpem->match_icmp_code_num || bpem->match_icmp_type_num || bpem->match_port_num || bpem->match_src_port_num || bpem->match_dst_port_num || bpem->match_protocol_num || bpem->match_bitmask) bpem->type = BGP_PBR_IPSET; else if ((bpem->match_bitmask_iprule & PREFIX_SRC_PRESENT) || (bpem->match_bitmask_iprule & PREFIX_DST_PRESENT)) /* the extracted policy rule may not need an * iptables/ipset filtering. check this may not be * a standard ip rule : permit any to any ( eg) */ bpem->type = BGP_PBR_IPRULE; else bpem->type = BGP_PBR_UNDEFINED; return error; } /* return 1 if FS entry invalid or no NH IP */ int bgp_flowspec_get_first_nh(struct bgp *bgp, struct bgp_path_info *pi, struct prefix *p) { struct bgp_pbr_entry_main api; int i; struct bgp_node *rn = pi->net; struct bgp_pbr_entry_action *api_action; memset(&api, 0, sizeof(struct bgp_pbr_entry_main)); if (bgp_pbr_build_and_validate_entry(&rn->p, pi, &api) < 0) return 1; for (i = 0; i < api.action_num; i++) { api_action = &api.actions[i]; if (api_action->action != ACTION_REDIRECT_IP) continue; p->family = AF_INET; p->prefixlen = IPV4_MAX_BITLEN; p->u.prefix4 = api_action->u.zr.redirect_ip_v4; return 0; } return 1; } frr-7.2.1/bgpd/bgp_flowspec_util.h0000644000000000000000000000366513610377563014022 00000000000000/* BGP Flowspec header for utilities * Copyright (C) 2018 6WIND * * FRRouting is free software; you can redistribute it and/or modify it * under the terms of the GNU General Public License as published by the * Free Software Foundation; either version 2, or (at your option) any * later version. * * FRRouting 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; see the file COPYING; if not, write to the Free Software * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA */ #ifndef _FRR_BGP_FLOWSPEC_UTIL_H #define _FRR_BGP_FLOWSPEC_UTIL_H #include "zclient.h" #define BGP_FLOWSPEC_STRING_DISPLAY_MAX 512 enum bgp_flowspec_util_nlri_t { BGP_FLOWSPEC_VALIDATE_ONLY = 0, BGP_FLOWSPEC_RETURN_STRING = 1, BGP_FLOWSPEC_CONVERT_TO_NON_OPAQUE = 2, BGP_FLOWSPEC_RETURN_JSON = 3, }; extern int bgp_flowspec_op_decode(enum bgp_flowspec_util_nlri_t type, uint8_t *nlri_ptr, uint32_t max_len, void *result, int *error); extern int bgp_flowspec_ip_address(enum bgp_flowspec_util_nlri_t type, uint8_t *nlri_ptr, uint32_t max_len, void *result, int *error); extern int bgp_flowspec_bitmask_decode(enum bgp_flowspec_util_nlri_t type, uint8_t *nlri_ptr, uint32_t max_len, void *result, int *error); struct bgp_pbr_entry_main; extern int bgp_flowspec_match_rules_fill(uint8_t *nlri_content, int len, struct bgp_pbr_entry_main *bpem); extern bool bgp_flowspec_contains_prefix(struct prefix *pfs, struct prefix *input, int prefix_check); extern int bgp_flowspec_get_first_nh(struct bgp *bgp, struct bgp_path_info *pi, struct prefix *nh); #endif /* _FRR_BGP_FLOWSPEC_UTIL_H */ frr-7.2.1/bgpd/bgp_flowspec_vty.c0000644000000000000000000004020313610377563013647 00000000000000/* BGP FlowSpec VTY * Copyright (C) 2018 6WIND * * FRRouting is free software; you can redistribute it and/or modify it * under the terms of the GNU General Public License as published by the * Free Software Foundation; either version 2, or (at your option) any * later version. * * FRRouting 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; see the file COPYING; if not, write to the Free Software * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA */ #include #include "command.h" #include "bgpd/bgpd.h" #include "bgpd/bgp_table.h" #include "bgpd/bgp_attr.h" #include "bgpd/bgp_ecommunity.h" #include "bgpd/bgp_vty.h" #include "bgpd/bgp_route.h" #include "bgpd/bgp_aspath.h" #include "bgpd/bgp_flowspec.h" #include "bgpd/bgp_flowspec_util.h" #include "bgpd/bgp_flowspec_private.h" #include "bgpd/bgp_debug.h" #include "bgpd/bgp_pbr.h" /* Local Structures and variables declarations * This code block hosts the struct declared that host the flowspec rules * as well as some structure used to convert to stringx */ static const struct message bgp_flowspec_display_large[] = { {FLOWSPEC_DEST_PREFIX, "Destination Address"}, {FLOWSPEC_SRC_PREFIX, "Source Address"}, {FLOWSPEC_IP_PROTOCOL, "IP Protocol"}, {FLOWSPEC_PORT, "Port"}, {FLOWSPEC_DEST_PORT, "Destination Port"}, {FLOWSPEC_SRC_PORT, "Source Port"}, {FLOWSPEC_ICMP_TYPE, "ICMP Type"}, {FLOWSPEC_ICMP_CODE, "ICMP Code"}, {FLOWSPEC_TCP_FLAGS, "TCP Flags"}, {FLOWSPEC_PKT_LEN, "Packet Length"}, {FLOWSPEC_DSCP, "DSCP field"}, {FLOWSPEC_FRAGMENT, "Packet Fragment"}, {0} }; static const struct message bgp_flowspec_display_min[] = { {FLOWSPEC_DEST_PREFIX, "to"}, {FLOWSPEC_SRC_PREFIX, "from"}, {FLOWSPEC_IP_PROTOCOL, "proto"}, {FLOWSPEC_PORT, "port"}, {FLOWSPEC_DEST_PORT, "dstp"}, {FLOWSPEC_SRC_PORT, "srcp"}, {FLOWSPEC_ICMP_TYPE, "type"}, {FLOWSPEC_ICMP_CODE, "code"}, {FLOWSPEC_TCP_FLAGS, "tcp"}, {FLOWSPEC_PKT_LEN, "pktlen"}, {FLOWSPEC_DSCP, "dscp"}, {FLOWSPEC_FRAGMENT, "pktfrag"}, {0} }; #define FS_STRING_UPDATE(count, ptr, format, remaining_len) do { \ int _len_written; \ \ if (((format) == NLRI_STRING_FORMAT_DEBUG) && (count)) {\ _len_written = snprintf((ptr), (remaining_len), \ ", "); \ (remaining_len) -= _len_written; \ (ptr) += _len_written; \ } else if (((format) == NLRI_STRING_FORMAT_MIN) \ && (count)) { \ _len_written = snprintf((ptr), (remaining_len), \ " "); \ (remaining_len) -= _len_written; \ (ptr) += _len_written; \ } \ count++; \ } while (0) /* Parse FLOWSPEC NLRI * passed return_string string has assumed length * BGP_FLOWSPEC_STRING_DISPLAY_MAX */ void bgp_fs_nlri_get_string(unsigned char *nlri_content, size_t len, char *return_string, int format, json_object *json_path) { uint32_t offset = 0; int type; int ret = 0, error = 0; char *ptr = return_string; char local_string[BGP_FLOWSPEC_STRING_DISPLAY_MAX]; int count = 0; char extra[2] = ""; char pre_extra[2] = ""; const struct message *bgp_flowspec_display; enum bgp_flowspec_util_nlri_t type_util; int len_string = BGP_FLOWSPEC_STRING_DISPLAY_MAX; int len_written; if (format == NLRI_STRING_FORMAT_LARGE) { snprintf(pre_extra, sizeof(pre_extra), "\t"); snprintf(extra, sizeof(extra), "\n"); bgp_flowspec_display = bgp_flowspec_display_large; } else bgp_flowspec_display = bgp_flowspec_display_min; /* if needed. type_util can be set to other values */ type_util = BGP_FLOWSPEC_RETURN_STRING; error = 0; while (offset < len-1 && error >= 0) { type = nlri_content[offset]; offset++; switch (type) { case FLOWSPEC_DEST_PREFIX: case FLOWSPEC_SRC_PREFIX: ret = bgp_flowspec_ip_address( type_util, nlri_content+offset, len - offset, local_string, &error); if (ret <= 0) break; if (json_path) { json_object_string_add(json_path, lookup_msg(bgp_flowspec_display, type, ""), local_string); break; } FS_STRING_UPDATE(count, ptr, format, len_string); len_written = snprintf(ptr, len_string, "%s%s %s%s", pre_extra, lookup_msg(bgp_flowspec_display, type, ""), local_string, extra); len_string -= len_written; ptr += len_written; break; case FLOWSPEC_IP_PROTOCOL: case FLOWSPEC_PORT: case FLOWSPEC_DEST_PORT: case FLOWSPEC_SRC_PORT: case FLOWSPEC_ICMP_TYPE: case FLOWSPEC_ICMP_CODE: ret = bgp_flowspec_op_decode(type_util, nlri_content+offset, len - offset, local_string, &error); if (ret <= 0) break; if (json_path) { json_object_string_add(json_path, lookup_msg(bgp_flowspec_display, type, ""), local_string); break; } FS_STRING_UPDATE(count, ptr, format, len_string); len_written = snprintf(ptr, len_string, "%s%s %s%s", pre_extra, lookup_msg(bgp_flowspec_display, type, ""), local_string, extra); len_string -= len_written; ptr += len_written; break; case FLOWSPEC_TCP_FLAGS: ret = bgp_flowspec_bitmask_decode( type_util, nlri_content+offset, len - offset, local_string, &error); if (ret <= 0) break; if (json_path) { json_object_string_add(json_path, lookup_msg(bgp_flowspec_display, type, ""), local_string); break; } FS_STRING_UPDATE(count, ptr, format, len_string); len_written = snprintf(ptr, len_string, "%s%s %s%s", pre_extra, lookup_msg(bgp_flowspec_display, type, ""), local_string, extra); len_string -= len_written; ptr += len_written; break; case FLOWSPEC_PKT_LEN: case FLOWSPEC_DSCP: ret = bgp_flowspec_op_decode( type_util, nlri_content + offset, len - offset, local_string, &error); if (ret <= 0) break; if (json_path) { json_object_string_add(json_path, lookup_msg(bgp_flowspec_display, type, ""), local_string); break; } FS_STRING_UPDATE(count, ptr, format, len_string); len_written = snprintf(ptr, len_string, "%s%s %s%s", pre_extra, lookup_msg(bgp_flowspec_display, type, ""), local_string, extra); len_string -= len_written; ptr += len_written; break; case FLOWSPEC_FRAGMENT: ret = bgp_flowspec_bitmask_decode( type_util, nlri_content+offset, len - offset, local_string, &error); if (ret <= 0) break; if (json_path) { json_object_string_add(json_path, lookup_msg(bgp_flowspec_display, type, ""), local_string); break; } FS_STRING_UPDATE(count, ptr, format, len_string); len_written = snprintf(ptr, len_string, "%s%s %s%s", pre_extra, lookup_msg(bgp_flowspec_display, type, ""), local_string, extra); len_string -= len_written; ptr += len_written; break; default: error = -1; break; } offset += ret; } } void route_vty_out_flowspec(struct vty *vty, struct prefix *p, struct bgp_path_info *path, int display, json_object *json_paths) { struct attr *attr; char return_string[BGP_FLOWSPEC_STRING_DISPLAY_MAX]; char *s; json_object *json_nlri_path = NULL; json_object *json_ecom_path = NULL; json_object *json_time_path = NULL; char timebuf[BGP_UPTIME_LEN]; /* Print prefix */ if (p != NULL) { if (p->family != AF_FLOWSPEC) return; if (json_paths) { if (display == NLRI_STRING_FORMAT_JSON) json_nlri_path = json_object_new_object(); else json_nlri_path = json_paths; } if (display == NLRI_STRING_FORMAT_LARGE && path) vty_out(vty, "BGP flowspec entry: (flags 0x%x)\n", path->flags); bgp_fs_nlri_get_string((unsigned char *) p->u.prefix_flowspec.ptr, p->u.prefix_flowspec.prefixlen, return_string, display, json_nlri_path); if (display == NLRI_STRING_FORMAT_LARGE) vty_out(vty, "%s", return_string); else if (display == NLRI_STRING_FORMAT_DEBUG) vty_out(vty, "%s", return_string); else if (display == NLRI_STRING_FORMAT_MIN) vty_out(vty, " %-30s", return_string); else if (json_paths && display == NLRI_STRING_FORMAT_JSON) json_object_array_add(json_paths, json_nlri_path); } if (!path) return; if (path->attr && path->attr->ecommunity) { /* Print attribute */ attr = path->attr; s = ecommunity_ecom2str(attr->ecommunity, ECOMMUNITY_FORMAT_ROUTE_MAP, 0); if (!s) return; if (display == NLRI_STRING_FORMAT_LARGE) vty_out(vty, "\t%s\n", s); else if (display == NLRI_STRING_FORMAT_MIN) vty_out(vty, "%s", s); else if (json_paths) { json_ecom_path = json_object_new_object(); json_object_string_add(json_ecom_path, "ecomlist", s); if (display == NLRI_STRING_FORMAT_JSON) json_object_array_add(json_paths, json_ecom_path); } if (attr->nexthop.s_addr != 0 && display == NLRI_STRING_FORMAT_LARGE) vty_out(vty, "\tNLRI NH %-16s\n", inet_ntoa(attr->nexthop)); XFREE(MTYPE_ECOMMUNITY_STR, s); } peer_uptime(path->uptime, timebuf, BGP_UPTIME_LEN, 0, NULL); if (display == NLRI_STRING_FORMAT_LARGE) { vty_out(vty, "\treceived for %8s\n", timebuf); } else if (json_paths) { json_time_path = json_object_new_object(); json_object_string_add(json_time_path, "time", timebuf); if (display == NLRI_STRING_FORMAT_JSON) json_object_array_add(json_paths, json_time_path); } if (display == NLRI_STRING_FORMAT_LARGE) { struct bgp_path_info_extra *extra = bgp_path_info_extra_get(path); bool list_began = false; if (extra->bgp_fs_pbr && listcount(extra->bgp_fs_pbr)) { struct listnode *node; struct bgp_pbr_match_entry *bpme; struct bgp_pbr_match *bpm; struct list *list_bpm; list_bpm = list_new(); vty_out(vty, "\tinstalled in PBR"); for (ALL_LIST_ELEMENTS_RO(extra->bgp_fs_pbr, node, bpme)) { bpm = bpme->backpointer; if (listnode_lookup(list_bpm, bpm)) continue; listnode_add(list_bpm, bpm); if (!list_began) { vty_out(vty, " ("); list_began = true; } else vty_out(vty, ", "); vty_out(vty, "%s", bpm->ipset_name); } list_delete(&list_bpm); } if (extra->bgp_fs_iprule && listcount(extra->bgp_fs_iprule)) { struct listnode *node; struct bgp_pbr_rule *bpr; if (!list_began) vty_out(vty, "\tinstalled in PBR"); for (ALL_LIST_ELEMENTS_RO(extra->bgp_fs_iprule, node, bpr)) { if (!bpr->action) continue; if (!list_began) { vty_out(vty, " ("); list_began = true; } else vty_out(vty, ", "); vty_out(vty, "-ipv4-rule %d action lookup %u-", bpr->priority, bpr->action->table_id); } if (list_began) vty_out(vty, ")"); vty_out(vty, "\n"); } if (!list_began) vty_out(vty, "\tnot installed in PBR\n"); } } int bgp_show_table_flowspec(struct vty *vty, struct bgp *bgp, afi_t afi, struct bgp_table *table, enum bgp_show_type type, void *output_arg, bool use_json, int is_last, unsigned long *output_cum, unsigned long *total_cum) { struct bgp_path_info *pi; struct bgp_node *rn; unsigned long total_count = 0; json_object *json_paths = NULL; int display = NLRI_STRING_FORMAT_LARGE; if (type != bgp_show_type_detail) return CMD_SUCCESS; for (rn = bgp_table_top(table); rn; rn = bgp_route_next(rn)) { pi = bgp_node_get_bgp_path_info(rn); if (pi == NULL) continue; if (use_json) { json_paths = json_object_new_array(); display = NLRI_STRING_FORMAT_JSON; } for (; pi; pi = pi->next) { total_count++; route_vty_out_flowspec(vty, &rn->p, pi, display, json_paths); } if (use_json) { vty_out(vty, "%s\n", json_object_to_json_string_ext( json_paths, JSON_C_TO_STRING_PRETTY)); json_object_free(json_paths); json_paths = NULL; } } if (total_count && !use_json) vty_out(vty, "\nDisplayed %ld flowspec entries\n", total_count); return CMD_SUCCESS; } DEFUN (debug_bgp_flowspec, debug_bgp_flowspec_cmd, "debug bgp flowspec", DEBUG_STR BGP_STR "BGP allow flowspec debugging entries\n") { if (vty->node == CONFIG_NODE) DEBUG_ON(flowspec, FLOWSPEC); else { TERM_DEBUG_ON(flowspec, FLOWSPEC); vty_out(vty, "BGP flowspec debugging is on\n"); } return CMD_SUCCESS; } DEFUN (no_debug_bgp_flowspec, no_debug_bgp_flowspec_cmd, "no debug bgp flowspec", NO_STR DEBUG_STR BGP_STR "BGP allow flowspec debugging entries\n") { if (vty->node == CONFIG_NODE) DEBUG_OFF(flowspec, FLOWSPEC); else { TERM_DEBUG_OFF(flowspec, FLOWSPEC); vty_out(vty, "BGP flowspec debugging is off\n"); } return CMD_SUCCESS; } int bgp_fs_config_write_pbr(struct vty *vty, struct bgp *bgp, afi_t afi, safi_t safi) { struct bgp_pbr_interface *pbr_if; bool declare_node = false; struct bgp_pbr_config *bgp_pbr_cfg = bgp->bgp_pbr_cfg; struct bgp_pbr_interface_head *head; bool bgp_pbr_interface_any; if (!bgp_pbr_cfg || safi != SAFI_FLOWSPEC || afi != AFI_IP) return 0; head = &(bgp_pbr_cfg->ifaces_by_name_ipv4); bgp_pbr_interface_any = bgp_pbr_cfg->pbr_interface_any_ipv4; if (!RB_EMPTY(bgp_pbr_interface_head, head) || !bgp_pbr_interface_any) declare_node = true; RB_FOREACH (pbr_if, bgp_pbr_interface_head, head) { vty_out(vty, " local-install %s\n", pbr_if->name); } return declare_node ? 1 : 0; } static int bgp_fs_local_install_interface(struct bgp *bgp, const char *no, const char *ifname) { struct bgp_pbr_interface *pbr_if; struct bgp_pbr_config *bgp_pbr_cfg = bgp->bgp_pbr_cfg; struct bgp_pbr_interface_head *head; bool *bgp_pbr_interface_any; if (!bgp_pbr_cfg) return CMD_SUCCESS; head = &(bgp_pbr_cfg->ifaces_by_name_ipv4); bgp_pbr_interface_any = &(bgp_pbr_cfg->pbr_interface_any_ipv4); if (no) { if (!ifname) { if (*bgp_pbr_interface_any) { *bgp_pbr_interface_any = false; /* remove all other interface list */ bgp_pbr_reset(bgp, AFI_IP); } return CMD_SUCCESS; } pbr_if = bgp_pbr_interface_lookup(ifname, head); if (!pbr_if) return CMD_SUCCESS; RB_REMOVE(bgp_pbr_interface_head, head, pbr_if); return CMD_SUCCESS; } if (ifname) { pbr_if = bgp_pbr_interface_lookup(ifname, head); if (pbr_if) return CMD_SUCCESS; pbr_if = XCALLOC(MTYPE_TMP, sizeof(struct bgp_pbr_interface)); strlcpy(pbr_if->name, ifname, INTERFACE_NAMSIZ); RB_INSERT(bgp_pbr_interface_head, head, pbr_if); *bgp_pbr_interface_any = false; } else { /* set to default */ if (!*bgp_pbr_interface_any) { /* remove all other interface list */ bgp_pbr_reset(bgp, AFI_IP); *bgp_pbr_interface_any = true; } } return CMD_SUCCESS; } DEFUN (bgp_fs_local_install_ifname, bgp_fs_local_install_ifname_cmd, "[no] local-install INTERFACE", NO_STR "Apply local policy routing\n" "Interface name\n") { struct bgp *bgp = VTY_GET_CONTEXT(bgp); int idx = 0; const char *no = strmatch(argv[0]->text, "no") ? "no" : NULL; char *ifname = argv_find(argv, argc, "INTERFACE", &idx) ? argv[idx]->arg : NULL; return bgp_fs_local_install_interface(bgp, no, ifname); } extern int bgp_flowspec_display_match_per_ip(afi_t afi, struct bgp_table *rib, struct prefix *match, int prefix_check, struct vty *vty, bool use_json, json_object *json_paths) { struct bgp_node *rn; struct prefix *prefix; int display = 0; for (rn = bgp_table_top(rib); rn; rn = bgp_route_next(rn)) { prefix = &rn->p; if (prefix->family != AF_FLOWSPEC) continue; if (bgp_flowspec_contains_prefix(prefix, match, prefix_check)) { route_vty_out_flowspec( vty, &rn->p, bgp_node_get_bgp_path_info(rn), use_json ? NLRI_STRING_FORMAT_JSON : NLRI_STRING_FORMAT_LARGE, json_paths); display++; } } return display; } void bgp_flowspec_vty_init(void) { install_element(ENABLE_NODE, &debug_bgp_flowspec_cmd); install_element(CONFIG_NODE, &debug_bgp_flowspec_cmd); install_element(ENABLE_NODE, &no_debug_bgp_flowspec_cmd); install_element(CONFIG_NODE, &no_debug_bgp_flowspec_cmd); install_element(BGP_FLOWSPECV4_NODE, &bgp_fs_local_install_ifname_cmd); } frr-7.2.1/bgpd/bgp_fsm.c0000644000000000000000000016377413610377563011733 00000000000000/* BGP-4 Finite State Machine * From RFC1771 [A Border Gateway Protocol 4 (BGP-4)] * Copyright (C) 1996, 97, 98 Kunihiro Ishiguro * * This file is part of GNU Zebra. * * GNU Zebra is free software; you can redistribute it and/or modify it * under the terms of the GNU General Public License as published by the * Free Software Foundation; either version 2, or (at your option) any * later version. * * GNU Zebra 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; see the file COPYING; if not, write to the Free Software * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA */ #include #include "linklist.h" #include "prefix.h" #include "sockunion.h" #include "thread.h" #include "log.h" #include "stream.h" #include "ringbuf.h" #include "memory.h" #include "plist.h" #include "workqueue.h" #include "queue.h" #include "filter.h" #include "command.h" #include "lib_errors.h" #include "lib/json.h" #include "bgpd/bgpd.h" #include "bgpd/bgp_attr.h" #include "bgpd/bgp_debug.h" #include "bgpd/bgp_errors.h" #include "bgpd/bgp_fsm.h" #include "bgpd/bgp_packet.h" #include "bgpd/bgp_network.h" #include "bgpd/bgp_route.h" #include "bgpd/bgp_dump.h" #include "bgpd/bgp_open.h" #include "bgpd/bgp_advertise.h" #include "bgpd/bgp_updgrp.h" #include "bgpd/bgp_nht.h" #include "bgpd/bgp_bfd.h" #include "bgpd/bgp_memory.h" #include "bgpd/bgp_keepalives.h" #include "bgpd/bgp_io.h" #include "bgpd/bgp_zebra.h" DEFINE_HOOK(peer_backward_transition, (struct peer * peer), (peer)) DEFINE_HOOK(peer_established, (struct peer * peer), (peer)) /* Definition of display strings corresponding to FSM events. This should be * kept consistent with the events defined in bgpd.h */ static const char *bgp_event_str[] = { NULL, "BGP_Start", "BGP_Stop", "TCP_connection_open", "TCP_connection_closed", "TCP_connection_open_failed", "TCP_fatal_error", "ConnectRetry_timer_expired", "Hold_Timer_expired", "KeepAlive_timer_expired", "Receive_OPEN_message", "Receive_KEEPALIVE_message", "Receive_UPDATE_message", "Receive_NOTIFICATION_message", "Clearing_Completed", }; /* BGP FSM (finite state machine) has three types of functions. Type one is thread functions. Type two is event functions. Type three is FSM functions. Timer functions are set by bgp_timer_set function. */ /* BGP event function. */ int bgp_event(struct thread *); /* BGP thread functions. */ static int bgp_start_timer(struct thread *); static int bgp_connect_timer(struct thread *); static int bgp_holdtime_timer(struct thread *); /* BGP FSM functions. */ static int bgp_start(struct peer *); /* Register peer with NHT */ static int bgp_peer_reg_with_nht(struct peer *peer) { int connected = 0; if (peer->sort == BGP_PEER_EBGP && peer->ttl == 1 && !CHECK_FLAG(peer->flags, PEER_FLAG_DISABLE_CONNECTED_CHECK) && !bgp_flag_check(peer->bgp, BGP_FLAG_DISABLE_NH_CONNECTED_CHK)) connected = 1; return bgp_find_or_add_nexthop( peer->bgp, peer->bgp, family2afi(peer->su.sa.sa_family), NULL, peer, connected); } static void peer_xfer_stats(struct peer *peer_dst, struct peer *peer_src) { /* Copy stats over. These are only the pre-established state stats */ peer_dst->open_in += peer_src->open_in; peer_dst->open_out += peer_src->open_out; peer_dst->keepalive_in += peer_src->keepalive_in; peer_dst->keepalive_out += peer_src->keepalive_out; peer_dst->notify_in += peer_src->notify_in; peer_dst->notify_out += peer_src->notify_out; peer_dst->dynamic_cap_in += peer_src->dynamic_cap_in; peer_dst->dynamic_cap_out += peer_src->dynamic_cap_out; } static struct peer *peer_xfer_conn(struct peer *from_peer) { struct peer *peer; afi_t afi; safi_t safi; int fd; int status, pstatus; unsigned char last_evt, last_maj_evt; assert(from_peer != NULL); peer = from_peer->doppelganger; if (!peer || !CHECK_FLAG(peer->flags, PEER_FLAG_CONFIG_NODE)) return from_peer; /* * Let's check that we are not going to loose known configuration * state based upon doppelganger rules. */ FOREACH_AFI_SAFI (afi, safi) { if (from_peer->afc[afi][safi] != peer->afc[afi][safi]) { flog_err( EC_BGP_DOPPELGANGER_CONFIG, "from_peer->afc[%d][%d] is not the same as what we are overwriting", afi, safi); return NULL; } } if (bgp_debug_neighbor_events(peer)) zlog_debug("%s: peer transfer %p fd %d -> %p fd %d)", from_peer->host, from_peer, from_peer->fd, peer, peer->fd); bgp_writes_off(peer); bgp_reads_off(peer); bgp_writes_off(from_peer); bgp_reads_off(from_peer); BGP_TIMER_OFF(peer->t_routeadv); BGP_TIMER_OFF(peer->t_connect); BGP_TIMER_OFF(peer->t_connect_check_r); BGP_TIMER_OFF(peer->t_connect_check_w); BGP_TIMER_OFF(from_peer->t_routeadv); BGP_TIMER_OFF(from_peer->t_connect); BGP_TIMER_OFF(from_peer->t_connect_check_r); BGP_TIMER_OFF(from_peer->t_connect_check_w); BGP_TIMER_OFF(from_peer->t_process_packet); /* * At this point in time, it is possible that there are packets pending * on various buffers. Those need to be transferred or dropped, * otherwise we'll get spurious failures during session establishment. */ frr_with_mutex(&peer->io_mtx, &from_peer->io_mtx) { fd = peer->fd; peer->fd = from_peer->fd; from_peer->fd = fd; stream_fifo_clean(peer->ibuf); stream_fifo_clean(peer->obuf); /* * this should never happen, since bgp_process_packet() is the * only task that sets and unsets the current packet and it * runs in our pthread. */ if (peer->curr) { flog_err( EC_BGP_PKT_PROCESS, "[%s] Dropping pending packet on connection transfer:", peer->host); /* there used to be a bgp_packet_dump call here, but * that's extremely confusing since there's no way to * identify the packet in MRT dumps or BMP as dropped * due to connection transfer. */ stream_free(peer->curr); peer->curr = NULL; } // copy each packet from old peer's output queue to new peer while (from_peer->obuf->head) stream_fifo_push(peer->obuf, stream_fifo_pop(from_peer->obuf)); // copy each packet from old peer's input queue to new peer while (from_peer->ibuf->head) stream_fifo_push(peer->ibuf, stream_fifo_pop(from_peer->ibuf)); ringbuf_wipe(peer->ibuf_work); ringbuf_copy(peer->ibuf_work, from_peer->ibuf_work, ringbuf_remain(from_peer->ibuf_work)); } peer->as = from_peer->as; peer->v_holdtime = from_peer->v_holdtime; peer->v_keepalive = from_peer->v_keepalive; peer->v_routeadv = from_peer->v_routeadv; peer->v_gr_restart = from_peer->v_gr_restart; peer->cap = from_peer->cap; status = peer->status; pstatus = peer->ostatus; last_evt = peer->last_event; last_maj_evt = peer->last_major_event; peer->status = from_peer->status; peer->ostatus = from_peer->ostatus; peer->last_event = from_peer->last_event; peer->last_major_event = from_peer->last_major_event; from_peer->status = status; from_peer->ostatus = pstatus; from_peer->last_event = last_evt; from_peer->last_major_event = last_maj_evt; peer->remote_id = from_peer->remote_id; peer->last_reset = from_peer->last_reset; if (from_peer->hostname != NULL) { if (peer->hostname) { XFREE(MTYPE_BGP_PEER_HOST, peer->hostname); peer->hostname = NULL; } peer->hostname = from_peer->hostname; from_peer->hostname = NULL; } if (from_peer->domainname != NULL) { if (peer->domainname) { XFREE(MTYPE_BGP_PEER_HOST, peer->domainname); peer->domainname = NULL; } peer->domainname = from_peer->domainname; from_peer->domainname = NULL; } FOREACH_AFI_SAFI (afi, safi) { peer->af_flags[afi][safi] = from_peer->af_flags[afi][safi]; peer->af_sflags[afi][safi] = from_peer->af_sflags[afi][safi]; peer->af_cap[afi][safi] = from_peer->af_cap[afi][safi]; peer->afc_nego[afi][safi] = from_peer->afc_nego[afi][safi]; peer->afc_adv[afi][safi] = from_peer->afc_adv[afi][safi]; peer->afc_recv[afi][safi] = from_peer->afc_recv[afi][safi]; peer->orf_plist[afi][safi] = from_peer->orf_plist[afi][safi]; } if (bgp_getsockname(peer) < 0) { flog_err( EC_LIB_SOCKET, "%%bgp_getsockname() failed for %s peer %s fd %d (from_peer fd %d)", (CHECK_FLAG(peer->sflags, PEER_STATUS_ACCEPT_PEER) ? "accept" : ""), peer->host, peer->fd, from_peer->fd); bgp_stop(peer); bgp_stop(from_peer); return NULL; } if (from_peer->status > Active) { if (bgp_getsockname(from_peer) < 0) { flog_err( EC_LIB_SOCKET, "%%bgp_getsockname() failed for %s from_peer %s fd %d (peer fd %d)", (CHECK_FLAG(from_peer->sflags, PEER_STATUS_ACCEPT_PEER) ? "accept" : ""), from_peer->host, from_peer->fd, peer->fd); bgp_stop(from_peer); from_peer = NULL; } } // Note: peer_xfer_stats() must be called with I/O turned OFF if (from_peer) peer_xfer_stats(peer, from_peer); /* Register peer for NHT. This is to allow RAs to be enabled when * needed, even on a passive connection. */ bgp_peer_reg_with_nht(peer); bgp_reads_on(peer); bgp_writes_on(peer); thread_add_timer_msec(bm->master, bgp_process_packet, peer, 0, &peer->t_process_packet); return (peer); } /* Hook function called after bgp event is occered. And vty's neighbor command invoke this function after making neighbor structure. */ void bgp_timer_set(struct peer *peer) { switch (peer->status) { case Idle: /* First entry point of peer's finite state machine. In Idle status start timer is on unless peer is shutdown or peer is inactive. All other timer must be turned off */ if (BGP_PEER_START_SUPPRESSED(peer) || !peer_active(peer) || (peer->bgp->inst_type != BGP_INSTANCE_TYPE_VIEW && peer->bgp->vrf_id == VRF_UNKNOWN)) { BGP_TIMER_OFF(peer->t_start); } else { BGP_TIMER_ON(peer->t_start, bgp_start_timer, peer->v_start); } BGP_TIMER_OFF(peer->t_connect); BGP_TIMER_OFF(peer->t_holdtime); bgp_keepalives_off(peer); BGP_TIMER_OFF(peer->t_routeadv); break; case Connect: /* After start timer is expired, the peer moves to Connect status. Make sure start timer is off and connect timer is on. */ BGP_TIMER_OFF(peer->t_start); BGP_TIMER_ON(peer->t_connect, bgp_connect_timer, peer->v_connect); BGP_TIMER_OFF(peer->t_holdtime); bgp_keepalives_off(peer); BGP_TIMER_OFF(peer->t_routeadv); break; case Active: /* Active is waiting connection from remote peer. And if connect timer is expired, change status to Connect. */ BGP_TIMER_OFF(peer->t_start); /* If peer is passive mode, do not set connect timer. */ if (CHECK_FLAG(peer->flags, PEER_FLAG_PASSIVE) || CHECK_FLAG(peer->sflags, PEER_STATUS_NSF_WAIT)) { BGP_TIMER_OFF(peer->t_connect); } else { BGP_TIMER_ON(peer->t_connect, bgp_connect_timer, peer->v_connect); } BGP_TIMER_OFF(peer->t_holdtime); bgp_keepalives_off(peer); BGP_TIMER_OFF(peer->t_routeadv); break; case OpenSent: /* OpenSent status. */ BGP_TIMER_OFF(peer->t_start); BGP_TIMER_OFF(peer->t_connect); if (peer->v_holdtime != 0) { BGP_TIMER_ON(peer->t_holdtime, bgp_holdtime_timer, peer->v_holdtime); } else { BGP_TIMER_OFF(peer->t_holdtime); } bgp_keepalives_off(peer); BGP_TIMER_OFF(peer->t_routeadv); break; case OpenConfirm: /* OpenConfirm status. */ BGP_TIMER_OFF(peer->t_start); BGP_TIMER_OFF(peer->t_connect); /* If the negotiated Hold Time value is zero, then the Hold Time timer and KeepAlive timers are not started. */ if (peer->v_holdtime == 0) { BGP_TIMER_OFF(peer->t_holdtime); bgp_keepalives_off(peer); } else { BGP_TIMER_ON(peer->t_holdtime, bgp_holdtime_timer, peer->v_holdtime); bgp_keepalives_on(peer); } BGP_TIMER_OFF(peer->t_routeadv); break; case Established: /* In Established status start and connect timer is turned off. */ BGP_TIMER_OFF(peer->t_start); BGP_TIMER_OFF(peer->t_connect); /* Same as OpenConfirm, if holdtime is zero then both holdtime and keepalive must be turned off. */ if (peer->v_holdtime == 0) { BGP_TIMER_OFF(peer->t_holdtime); bgp_keepalives_off(peer); } else { BGP_TIMER_ON(peer->t_holdtime, bgp_holdtime_timer, peer->v_holdtime); bgp_keepalives_on(peer); } break; case Deleted: BGP_TIMER_OFF(peer->t_gr_restart); BGP_TIMER_OFF(peer->t_gr_stale); BGP_TIMER_OFF(peer->t_pmax_restart); /* fallthru */ case Clearing: BGP_TIMER_OFF(peer->t_start); BGP_TIMER_OFF(peer->t_connect); BGP_TIMER_OFF(peer->t_holdtime); bgp_keepalives_off(peer); BGP_TIMER_OFF(peer->t_routeadv); break; } } /* BGP start timer. This function set BGP_Start event to thread value and process event. */ static int bgp_start_timer(struct thread *thread) { struct peer *peer; peer = THREAD_ARG(thread); peer->t_start = NULL; if (bgp_debug_neighbor_events(peer)) zlog_debug("%s [FSM] Timer (start timer expire).", peer->host); THREAD_VAL(thread) = BGP_Start; bgp_event(thread); /* bgp_event unlocks peer */ return 0; } /* BGP connect retry timer. */ static int bgp_connect_timer(struct thread *thread) { struct peer *peer; int ret; peer = THREAD_ARG(thread); assert(!peer->t_write); assert(!peer->t_read); peer->t_connect = NULL; if (bgp_debug_neighbor_events(peer)) zlog_debug("%s [FSM] Timer (connect timer expire)", peer->host); if (CHECK_FLAG(peer->sflags, PEER_STATUS_ACCEPT_PEER)) { bgp_stop(peer); ret = -1; } else { THREAD_VAL(thread) = ConnectRetry_timer_expired; bgp_event(thread); /* bgp_event unlocks peer */ ret = 0; } return ret; } /* BGP holdtime timer. */ static int bgp_holdtime_timer(struct thread *thread) { struct peer *peer; peer = THREAD_ARG(thread); peer->t_holdtime = NULL; if (bgp_debug_neighbor_events(peer)) zlog_debug("%s [FSM] Timer (holdtime timer expire)", peer->host); THREAD_VAL(thread) = Hold_Timer_expired; bgp_event(thread); /* bgp_event unlocks peer */ return 0; } int bgp_routeadv_timer(struct thread *thread) { struct peer *peer; peer = THREAD_ARG(thread); peer->t_routeadv = NULL; if (bgp_debug_neighbor_events(peer)) zlog_debug("%s [FSM] Timer (routeadv timer expire)", peer->host); peer->synctime = bgp_clock(); thread_add_timer_msec(bm->master, bgp_generate_updgrp_packets, peer, 0, &peer->t_generate_updgrp_packets); /* MRAI timer will be started again when FIFO is built, no need to * do it here. */ return 0; } /* BGP Peer Down Cause */ const char *peer_down_str[] = {"", "Router ID changed", "Remote AS changed", "Local AS change", "Cluster ID changed", "Confederation identifier changed", "Confederation peer changed", "RR client config change", "RS client config change", "Update source change", "Address family activated", "Admin. shutdown", "User reset", "BGP Notification received", "BGP Notification send", "Peer closed the session", "Neighbor deleted", "Peer-group add member", "Peer-group delete member", "Capability changed", "Passive config change", "Multihop config change", "NSF peer closed the session", "Intf peering v6only config change", "BFD down received", "Interface down", "Neighbor address lost", "Waiting for NHT", "Waiting for Peer IPv6 LLA", "Waiting for VRF to be initialized", "No AFI/SAFI activated for peer"}; static int bgp_graceful_restart_timer_expire(struct thread *thread) { struct peer *peer; afi_t afi; safi_t safi; peer = THREAD_ARG(thread); peer->t_gr_restart = NULL; /* NSF delete stale route */ for (afi = AFI_IP; afi < AFI_MAX; afi++) for (safi = SAFI_UNICAST; safi <= SAFI_MPLS_VPN; safi++) if (peer->nsf[afi][safi]) bgp_clear_stale_route(peer, afi, safi); UNSET_FLAG(peer->sflags, PEER_STATUS_NSF_WAIT); BGP_TIMER_OFF(peer->t_gr_stale); if (bgp_debug_neighbor_events(peer)) { zlog_debug("%s graceful restart timer expired", peer->host); zlog_debug("%s graceful restart stalepath timer stopped", peer->host); } bgp_timer_set(peer); return 0; } static int bgp_graceful_stale_timer_expire(struct thread *thread) { struct peer *peer; afi_t afi; safi_t safi; peer = THREAD_ARG(thread); peer->t_gr_stale = NULL; if (bgp_debug_neighbor_events(peer)) zlog_debug("%s graceful restart stalepath timer expired", peer->host); /* NSF delete stale route */ for (afi = AFI_IP; afi < AFI_MAX; afi++) for (safi = SAFI_UNICAST; safi <= SAFI_MPLS_VPN; safi++) if (peer->nsf[afi][safi]) bgp_clear_stale_route(peer, afi, safi); return 0; } static int bgp_update_delay_applicable(struct bgp *bgp) { /* update_delay_over flag should be reset (set to 0) for any new applicability of the update-delay during BGP process lifetime. And it should be set after an occurence of the update-delay is over)*/ if (!bgp->update_delay_over) return 1; return 0; } int bgp_update_delay_active(struct bgp *bgp) { if (bgp->t_update_delay) return 1; return 0; } int bgp_update_delay_configured(struct bgp *bgp) { if (bgp->v_update_delay) return 1; return 0; } /* Do the post-processing needed when bgp comes out of the read-only mode on ending the update delay. */ void bgp_update_delay_end(struct bgp *bgp) { THREAD_TIMER_OFF(bgp->t_update_delay); THREAD_TIMER_OFF(bgp->t_establish_wait); /* Reset update-delay related state */ bgp->update_delay_over = 1; bgp->established = 0; bgp->restarted_peers = 0; bgp->implicit_eors = 0; bgp->explicit_eors = 0; quagga_timestamp(3, bgp->update_delay_end_time, sizeof(bgp->update_delay_end_time)); /* * Add an end-of-initial-update marker to the main process queues so * that * the route advertisement timer for the peers can be started. Also set * the zebra and peer update hold flags. These flags are used to achieve * three stages in the update-delay post processing: * 1. Finish best-path selection for all the prefixes held on the * queues. * (routes in BGP are updated, and peers sync queues are populated * too) * 2. As the eoiu mark is reached in the bgp process routine, ship all * the * routes to zebra. With that zebra should see updates from BGP * close * to each other. * 3. Unblock the peer update writes. With that peer update packing * with * the prefixes should be at its maximum. */ bgp_add_eoiu_mark(bgp); bgp->main_zebra_update_hold = 1; bgp->main_peers_update_hold = 1; /* Resume the queue processing. This should trigger the event that would take care of processing any work that was queued during the read-only mode. */ work_queue_unplug(bm->process_main_queue); } /** * see bgp_fsm.h */ void bgp_start_routeadv(struct bgp *bgp) { struct listnode *node, *nnode; struct peer *peer; zlog_info("bgp_start_routeadv(), update hold status %d", bgp->main_peers_update_hold); if (bgp->main_peers_update_hold) return; quagga_timestamp(3, bgp->update_delay_peers_resume_time, sizeof(bgp->update_delay_peers_resume_time)); for (ALL_LIST_ELEMENTS(bgp->peer, node, nnode, peer)) { if (peer->status != Established) continue; BGP_TIMER_OFF(peer->t_routeadv); BGP_TIMER_ON(peer->t_routeadv, bgp_routeadv_timer, 0); } } /** * see bgp_fsm.h */ void bgp_adjust_routeadv(struct peer *peer) { time_t nowtime = bgp_clock(); double diff; unsigned long remain; /* Bypass checks for special case of MRAI being 0 */ if (peer->v_routeadv == 0) { /* Stop existing timer, just in case it is running for a * different * duration and schedule write thread immediately. */ if (peer->t_routeadv) BGP_TIMER_OFF(peer->t_routeadv); peer->synctime = bgp_clock(); thread_add_timer_msec(bm->master, bgp_generate_updgrp_packets, peer, 0, &peer->t_generate_updgrp_packets); return; } /* * CASE I: * If the last update was written more than MRAI back, expire the timer * instantly so that we can send the update out sooner. * * <------- MRAI ---------> * |-----------------|-----------------------| * <------------- m ------------> * ^ ^ ^ * | | | * | | current time * | timer start * last write * * m > MRAI */ diff = difftime(nowtime, peer->last_update); if (diff > (double)peer->v_routeadv) { BGP_TIMER_OFF(peer->t_routeadv); BGP_TIMER_ON(peer->t_routeadv, bgp_routeadv_timer, 0); return; } /* * CASE II: * - Find when to expire the MRAI timer. * If MRAI timer is not active, assume we can start it now. * * <------- MRAI ---------> * |------------|-----------------------| * <-------- m ----------><----- r -----> * ^ ^ ^ * | | | * | | current time * | timer start * last write * * (MRAI - m) < r */ if (peer->t_routeadv) remain = thread_timer_remain_second(peer->t_routeadv); else remain = peer->v_routeadv; diff = peer->v_routeadv - diff; if (diff <= (double)remain) { BGP_TIMER_OFF(peer->t_routeadv); BGP_TIMER_ON(peer->t_routeadv, bgp_routeadv_timer, diff); } } static int bgp_maxmed_onstartup_applicable(struct bgp *bgp) { if (!bgp->maxmed_onstartup_over) return 1; return 0; } int bgp_maxmed_onstartup_configured(struct bgp *bgp) { if (bgp->v_maxmed_onstartup != BGP_MAXMED_ONSTARTUP_UNCONFIGURED) return 1; return 0; } int bgp_maxmed_onstartup_active(struct bgp *bgp) { if (bgp->t_maxmed_onstartup) return 1; return 0; } void bgp_maxmed_update(struct bgp *bgp) { uint8_t maxmed_active; uint32_t maxmed_value; if (bgp->v_maxmed_admin) { maxmed_active = 1; maxmed_value = bgp->maxmed_admin_value; } else if (bgp->t_maxmed_onstartup) { maxmed_active = 1; maxmed_value = bgp->maxmed_onstartup_value; } else { maxmed_active = 0; maxmed_value = BGP_MAXMED_VALUE_DEFAULT; } if (bgp->maxmed_active != maxmed_active || bgp->maxmed_value != maxmed_value) { bgp->maxmed_active = maxmed_active; bgp->maxmed_value = maxmed_value; update_group_announce(bgp); } } /* The maxmed onstartup timer expiry callback. */ static int bgp_maxmed_onstartup_timer(struct thread *thread) { struct bgp *bgp; zlog_info("Max med on startup ended - timer expired."); bgp = THREAD_ARG(thread); THREAD_TIMER_OFF(bgp->t_maxmed_onstartup); bgp->maxmed_onstartup_over = 1; bgp_maxmed_update(bgp); return 0; } static void bgp_maxmed_onstartup_begin(struct bgp *bgp) { /* Applicable only once in the process lifetime on the startup */ if (bgp->maxmed_onstartup_over) return; zlog_info("Begin maxmed onstartup mode - timer %d seconds", bgp->v_maxmed_onstartup); thread_add_timer(bm->master, bgp_maxmed_onstartup_timer, bgp, bgp->v_maxmed_onstartup, &bgp->t_maxmed_onstartup); if (!bgp->v_maxmed_admin) { bgp->maxmed_active = 1; bgp->maxmed_value = bgp->maxmed_onstartup_value; } /* Route announce to all peers should happen after this in * bgp_establish() */ } static void bgp_maxmed_onstartup_process_status_change(struct peer *peer) { if (peer->status == Established && !peer->bgp->established) { bgp_maxmed_onstartup_begin(peer->bgp); } } /* The update delay timer expiry callback. */ static int bgp_update_delay_timer(struct thread *thread) { struct bgp *bgp; zlog_info("Update delay ended - timer expired."); bgp = THREAD_ARG(thread); THREAD_TIMER_OFF(bgp->t_update_delay); bgp_update_delay_end(bgp); return 0; } /* The establish wait timer expiry callback. */ static int bgp_establish_wait_timer(struct thread *thread) { struct bgp *bgp; zlog_info("Establish wait - timer expired."); bgp = THREAD_ARG(thread); THREAD_TIMER_OFF(bgp->t_establish_wait); bgp_check_update_delay(bgp); return 0; } /* Steps to begin the update delay: - initialize queues if needed - stop the queue processing - start the timer */ static void bgp_update_delay_begin(struct bgp *bgp) { struct listnode *node, *nnode; struct peer *peer; /* Stop the processing of queued work. Enqueue shall continue */ work_queue_plug(bm->process_main_queue); for (ALL_LIST_ELEMENTS(bgp->peer, node, nnode, peer)) peer->update_delay_over = 0; /* Start the update-delay timer */ thread_add_timer(bm->master, bgp_update_delay_timer, bgp, bgp->v_update_delay, &bgp->t_update_delay); if (bgp->v_establish_wait != bgp->v_update_delay) thread_add_timer(bm->master, bgp_establish_wait_timer, bgp, bgp->v_establish_wait, &bgp->t_establish_wait); quagga_timestamp(3, bgp->update_delay_begin_time, sizeof(bgp->update_delay_begin_time)); } static void bgp_update_delay_process_status_change(struct peer *peer) { if (peer->status == Established) { if (!peer->bgp->established++) { bgp_update_delay_begin(peer->bgp); zlog_info( "Begin read-only mode - update-delay timer %d seconds", peer->bgp->v_update_delay); } if (CHECK_FLAG(peer->cap, PEER_CAP_RESTART_BIT_RCV)) bgp_update_restarted_peers(peer); } if (peer->ostatus == Established && bgp_update_delay_active(peer->bgp)) { /* Adjust the update-delay state to account for this flap. NOTE: Intentionally skipping adjusting implicit_eors or explicit_eors counters. Extra sanity check in bgp_check_update_delay() should be enough to take care of any additive discrepancy in bgp eor counters */ peer->bgp->established--; peer->update_delay_over = 0; } } /* Called after event occurred, this function change status and reset read/write and timer thread. */ void bgp_fsm_change_status(struct peer *peer, int status) { struct bgp *bgp; uint32_t peer_count; bgp_dump_state(peer, peer->status, status); bgp = peer->bgp; peer_count = bgp->established_peers; if (status == Established) bgp->established_peers++; else if ((peer->status == Established) && (status != Established)) bgp->established_peers--; if (bgp_debug_neighbor_events(peer)) { struct vrf *vrf = vrf_lookup_by_id(bgp->vrf_id); zlog_debug("%s : vrf %s(%u), Status: %s established_peers %u", __func__, vrf ? vrf->name : "Unknown", bgp->vrf_id, lookup_msg(bgp_status_msg, status, NULL), bgp->established_peers); } /* Set to router ID to the value provided by RIB if there are no peers * in the established state and peer count did not change */ if ((peer_count != bgp->established_peers) && (bgp->established_peers == 0)) bgp_router_id_zebra_bump(bgp->vrf_id, NULL); /* Transition into Clearing or Deleted must /always/ clear all routes.. * (and must do so before actually changing into Deleted.. */ if (status >= Clearing) { bgp_clear_route_all(peer); /* If no route was queued for the clear-node processing, * generate the * completion event here. This is needed because if there are no * routes * to trigger the background clear-node thread, the event won't * get * generated and the peer would be stuck in Clearing. Note that * this * event is for the peer and helps the peer transition out of * Clearing * state; it should not be generated per (AFI,SAFI). The event * is * directly posted here without calling clear_node_complete() as * we * shouldn't do an extra unlock. This event will get processed * after * the state change that happens below, so peer will be in * Clearing * (or Deleted). */ if (!work_queue_is_scheduled(peer->clear_node_queue)) BGP_EVENT_ADD(peer, Clearing_Completed); } /* Preserve old status and change into new status. */ peer->ostatus = peer->status; peer->status = status; /* Save event that caused status change. */ peer->last_major_event = peer->cur_event; if (status == Established) UNSET_FLAG(peer->sflags, PEER_STATUS_ACCEPT_PEER); /* If max-med processing is applicable, do the necessary. */ if (status == Established) { if (bgp_maxmed_onstartup_configured(peer->bgp) && bgp_maxmed_onstartup_applicable(peer->bgp)) bgp_maxmed_onstartup_process_status_change(peer); else peer->bgp->maxmed_onstartup_over = 1; } /* If update-delay processing is applicable, do the necessary. */ if (bgp_update_delay_configured(peer->bgp) && bgp_update_delay_applicable(peer->bgp)) bgp_update_delay_process_status_change(peer); if (bgp_debug_neighbor_events(peer)) zlog_debug("%s went from %s to %s", peer->host, lookup_msg(bgp_status_msg, peer->ostatus, NULL), lookup_msg(bgp_status_msg, peer->status, NULL)); } /* Flush the event queue and ensure the peer is shut down */ static int bgp_clearing_completed(struct peer *peer) { int rc = bgp_stop(peer); if (rc >= 0) BGP_EVENT_FLUSH(peer); return rc; } /* Administrative BGP peer stop event. */ /* May be called multiple times for the same peer */ int bgp_stop(struct peer *peer) { afi_t afi; safi_t safi; char orf_name[BUFSIZ]; int ret = 0; if (peer_dynamic_neighbor(peer) && !(CHECK_FLAG(peer->flags, PEER_FLAG_DELETE))) { if (bgp_debug_neighbor_events(peer)) zlog_debug("%s (dynamic neighbor) deleted", peer->host); peer_delete(peer); return -1; } /* Can't do this in Clearing; events are used for state transitions */ if (peer->status != Clearing) { /* Delete all existing events of the peer */ BGP_EVENT_FLUSH(peer); } /* Increment Dropped count. */ if (peer->status == Established) { peer->dropped++; /* bgp log-neighbor-changes of neighbor Down */ if (bgp_flag_check(peer->bgp, BGP_FLAG_LOG_NEIGHBOR_CHANGES)) { struct vrf *vrf = vrf_lookup_by_id(peer->bgp->vrf_id); zlog_info( "%%ADJCHANGE: neighbor %s(%s) in vrf %s Down %s", peer->host, (peer->hostname) ? peer->hostname : "Unknown", vrf ? ((vrf->vrf_id != VRF_DEFAULT) ? vrf->name : VRF_DEFAULT_NAME) : "", peer_down_str[(int)peer->last_reset]); } /* graceful restart */ if (peer->t_gr_stale) { BGP_TIMER_OFF(peer->t_gr_stale); if (bgp_debug_neighbor_events(peer)) zlog_debug( "%s graceful restart stalepath timer stopped", peer->host); } if (CHECK_FLAG(peer->sflags, PEER_STATUS_NSF_WAIT)) { if (bgp_debug_neighbor_events(peer)) { zlog_debug( "%s graceful restart timer started for %d sec", peer->host, peer->v_gr_restart); zlog_debug( "%s graceful restart stalepath timer started for %d sec", peer->host, peer->bgp->stalepath_time); } BGP_TIMER_ON(peer->t_gr_restart, bgp_graceful_restart_timer_expire, peer->v_gr_restart); BGP_TIMER_ON(peer->t_gr_stale, bgp_graceful_stale_timer_expire, peer->bgp->stalepath_time); } else { UNSET_FLAG(peer->sflags, PEER_STATUS_NSF_MODE); for (afi = AFI_IP; afi < AFI_MAX; afi++) for (safi = SAFI_UNICAST; safi <= SAFI_MPLS_VPN; safi++) peer->nsf[afi][safi] = 0; } /* set last reset time */ peer->resettime = peer->uptime = bgp_clock(); if (BGP_DEBUG(update_groups, UPDATE_GROUPS)) zlog_debug("%s remove from all update group", peer->host); update_group_remove_peer_afs(peer); hook_call(peer_backward_transition, peer); /* Reset peer synctime */ peer->synctime = 0; } /* stop keepalives */ bgp_keepalives_off(peer); /* Stop read and write threads. */ bgp_writes_off(peer); bgp_reads_off(peer); THREAD_OFF(peer->t_connect_check_r); THREAD_OFF(peer->t_connect_check_w); /* Stop all timers. */ BGP_TIMER_OFF(peer->t_start); BGP_TIMER_OFF(peer->t_connect); BGP_TIMER_OFF(peer->t_holdtime); BGP_TIMER_OFF(peer->t_routeadv); /* Clear input and output buffer. */ frr_with_mutex(&peer->io_mtx) { if (peer->ibuf) stream_fifo_clean(peer->ibuf); if (peer->obuf) stream_fifo_clean(peer->obuf); if (peer->ibuf_work) ringbuf_wipe(peer->ibuf_work); if (peer->obuf_work) stream_reset(peer->obuf_work); if (peer->curr) { stream_free(peer->curr); peer->curr = NULL; } } /* Close of file descriptor. */ if (peer->fd >= 0) { close(peer->fd); peer->fd = -1; } FOREACH_AFI_SAFI (afi, safi) { /* Reset all negotiated variables */ peer->afc_nego[afi][safi] = 0; peer->afc_adv[afi][safi] = 0; peer->afc_recv[afi][safi] = 0; /* peer address family capability flags*/ peer->af_cap[afi][safi] = 0; /* peer address family status flags*/ peer->af_sflags[afi][safi] = 0; /* Received ORF prefix-filter */ peer->orf_plist[afi][safi] = NULL; if ((peer->status == OpenConfirm) || (peer->status == Established)) { /* ORF received prefix-filter pnt */ sprintf(orf_name, "%s.%d.%d", peer->host, afi, safi); prefix_bgp_orf_remove_all(afi, orf_name); } } /* Reset keepalive and holdtime */ if (CHECK_FLAG(peer->flags, PEER_FLAG_TIMER)) { peer->v_keepalive = peer->keepalive; peer->v_holdtime = peer->holdtime; } else { peer->v_keepalive = peer->bgp->default_keepalive; peer->v_holdtime = peer->bgp->default_holdtime; } peer->update_time = 0; /* Until we are sure that there is no problem about prefix count this should be commented out.*/ #if 0 /* Reset prefix count */ peer->pcount[AFI_IP][SAFI_UNICAST] = 0; peer->pcount[AFI_IP][SAFI_MULTICAST] = 0; peer->pcount[AFI_IP][SAFI_LABELED_UNICAST] = 0; peer->pcount[AFI_IP][SAFI_MPLS_VPN] = 0; peer->pcount[AFI_IP6][SAFI_UNICAST] = 0; peer->pcount[AFI_IP6][SAFI_MULTICAST] = 0; peer->pcount[AFI_IP6][SAFI_LABELED_UNICAST] = 0; #endif /* 0 */ if (!CHECK_FLAG(peer->flags, PEER_FLAG_CONFIG_NODE) && !(CHECK_FLAG(peer->flags, PEER_FLAG_DELETE))) { peer_delete(peer); ret = -1; } else { bgp_peer_conf_if_to_su_update(peer); } return ret; } /* BGP peer is stoped by the error. */ static int bgp_stop_with_error(struct peer *peer) { /* Double start timer. */ peer->v_start *= 2; /* Overflow check. */ if (peer->v_start >= (60 * 2)) peer->v_start = (60 * 2); if (peer_dynamic_neighbor(peer)) { if (bgp_debug_neighbor_events(peer)) zlog_debug("%s (dynamic neighbor) deleted", peer->host); peer_delete(peer); return -1; } return (bgp_stop(peer)); } /* something went wrong, send notify and tear down */ static int bgp_stop_with_notify(struct peer *peer, uint8_t code, uint8_t sub_code) { /* Send notify to remote peer */ bgp_notify_send(peer, code, sub_code); if (peer_dynamic_neighbor(peer)) { if (bgp_debug_neighbor_events(peer)) zlog_debug("%s (dynamic neighbor) deleted", peer->host); peer_delete(peer); return -1; } /* Clear start timer value to default. */ peer->v_start = BGP_INIT_START_TIMER; return (bgp_stop(peer)); } /** * Determines whether a TCP session has successfully established for a peer and * events as appropriate. * * This function is called when setting up a new session. After connect() is * called on the peer's socket (in bgp_start()), the fd is passed to poll() * to wait for connection success or failure. When poll() returns, this * function is called to evaluate the result. * * Due to differences in behavior of poll() on Linux and BSD - specifically, * the value of .revents in the case of a closed connection - this function is * scheduled both for a read and a write event. The write event is triggered * when the connection is established. A read event is triggered when the * connection is closed. Thus we need to cancel whichever one did not occur. */ static int bgp_connect_check(struct thread *thread) { int status; socklen_t slen; int ret; struct peer *peer; peer = THREAD_ARG(thread); assert(!CHECK_FLAG(peer->thread_flags, PEER_THREAD_READS_ON)); assert(!CHECK_FLAG(peer->thread_flags, PEER_THREAD_WRITES_ON)); assert(!peer->t_read); assert(!peer->t_write); THREAD_OFF(peer->t_connect_check_r); THREAD_OFF(peer->t_connect_check_w); /* Check file descriptor. */ slen = sizeof(status); ret = getsockopt(peer->fd, SOL_SOCKET, SO_ERROR, (void *)&status, &slen); /* If getsockopt is fail, this is fatal error. */ if (ret < 0) { zlog_info("can't get sockopt for nonblocking connect: %d(%s)", errno, safe_strerror(errno)); BGP_EVENT_ADD(peer, TCP_fatal_error); return -1; } /* When status is 0 then TCP connection is established. */ if (status == 0) { BGP_EVENT_ADD(peer, TCP_connection_open); return 1; } else { if (bgp_debug_neighbor_events(peer)) zlog_debug("%s [Event] Connect failed %d(%s)", peer->host, status, safe_strerror(status)); BGP_EVENT_ADD(peer, TCP_connection_open_failed); return 0; } } /* TCP connection open. Next we send open message to remote peer. And add read thread for reading open message. */ static int bgp_connect_success(struct peer *peer) { if (peer->fd < 0) { flog_err(EC_BGP_CONNECT, "bgp_connect_success peer's fd is negative value %d", peer->fd); bgp_stop(peer); return -1; } if (bgp_getsockname(peer) < 0) { flog_err_sys(EC_LIB_SOCKET, "%s: bgp_getsockname(): failed for peer %s, fd %d", __FUNCTION__, peer->host, peer->fd); bgp_notify_send(peer, BGP_NOTIFY_FSM_ERR, 0); /* internal error */ bgp_writes_on(peer); return -1; } bgp_reads_on(peer); if (bgp_debug_neighbor_events(peer)) { char buf1[SU_ADDRSTRLEN]; if (!CHECK_FLAG(peer->sflags, PEER_STATUS_ACCEPT_PEER)) zlog_debug("%s open active, local address %s", peer->host, sockunion2str(peer->su_local, buf1, SU_ADDRSTRLEN)); else zlog_debug("%s passive open", peer->host); } bgp_open_send(peer); return 0; } /* TCP connect fail */ static int bgp_connect_fail(struct peer *peer) { if (peer_dynamic_neighbor(peer)) { if (bgp_debug_neighbor_events(peer)) zlog_debug("%s (dynamic neighbor) deleted", peer->host); peer_delete(peer); return -1; } return (bgp_stop(peer)); } /* This function is the first starting point of all BGP connection. It try to connect to remote peer with non-blocking IO. */ int bgp_start(struct peer *peer) { int status; bgp_peer_conf_if_to_su_update(peer); if (peer->su.sa.sa_family == AF_UNSPEC) { if (bgp_debug_neighbor_events(peer)) zlog_debug( "%s [FSM] Unable to get neighbor's IP address, waiting...", peer->host); peer->last_reset = PEER_DOWN_NBR_ADDR; return -1; } if (BGP_PEER_START_SUPPRESSED(peer)) { if (bgp_debug_neighbor_events(peer)) flog_err(EC_BGP_FSM, "%s [FSM] Trying to start suppressed peer" " - this is never supposed to happen!", peer->host); return -1; } /* Scrub some information that might be left over from a previous, * session */ /* Connection information. */ if (peer->su_local) { sockunion_free(peer->su_local); peer->su_local = NULL; } if (peer->su_remote) { sockunion_free(peer->su_remote); peer->su_remote = NULL; } /* Clear remote router-id. */ peer->remote_id.s_addr = 0; /* Clear peer capability flag. */ peer->cap = 0; /* If the peer is passive mode, force to move to Active mode. */ if (CHECK_FLAG(peer->flags, PEER_FLAG_PASSIVE)) { BGP_EVENT_ADD(peer, TCP_connection_open_failed); return 0; } if (peer->bgp->inst_type != BGP_INSTANCE_TYPE_VIEW && peer->bgp->vrf_id == VRF_UNKNOWN) { if (bgp_debug_neighbor_events(peer)) flog_err( EC_BGP_FSM, "%s [FSM] In a VRF that is not initialised yet", peer->host); peer->last_reset = PEER_DOWN_VRF_UNINIT; return -1; } /* Register peer for NHT. If next hop is already resolved, proceed * with connection setup, else wait. */ if (!bgp_peer_reg_with_nht(peer)) { if (bgp_zebra_num_connects()) { if (bgp_debug_neighbor_events(peer)) zlog_debug("%s [FSM] Waiting for NHT", peer->host); peer->last_reset = PEER_DOWN_WAITING_NHT; BGP_EVENT_ADD(peer, TCP_connection_open_failed); return 0; } } assert(!peer->t_write); assert(!peer->t_read); assert(!CHECK_FLAG(peer->thread_flags, PEER_THREAD_WRITES_ON)); assert(!CHECK_FLAG(peer->thread_flags, PEER_THREAD_READS_ON)); status = bgp_connect(peer); switch (status) { case connect_error: if (bgp_debug_neighbor_events(peer)) zlog_debug("%s [FSM] Connect error", peer->host); BGP_EVENT_ADD(peer, TCP_connection_open_failed); break; case connect_success: if (bgp_debug_neighbor_events(peer)) zlog_debug( "%s [FSM] Connect immediately success, fd %d", peer->host, peer->fd); BGP_EVENT_ADD(peer, TCP_connection_open); break; case connect_in_progress: /* To check nonblocking connect, we wait until socket is readable or writable. */ if (bgp_debug_neighbor_events(peer)) zlog_debug( "%s [FSM] Non blocking connect waiting result, fd %d", peer->host, peer->fd); if (peer->fd < 0) { flog_err(EC_BGP_FSM, "bgp_start peer's fd is negative value %d", peer->fd); return -1; } /* * - when the socket becomes ready, poll() will signify POLLOUT * - if it fails to connect, poll() will signify POLLHUP * - POLLHUP is handled as a 'read' event by thread.c * * therefore, we schedule both a read and a write event with * bgp_connect_check() as the handler for each and cancel the * unused event in that function. */ thread_add_read(bm->master, bgp_connect_check, peer, peer->fd, &peer->t_connect_check_r); thread_add_write(bm->master, bgp_connect_check, peer, peer->fd, &peer->t_connect_check_w); break; } return 0; } /* Connect retry timer is expired when the peer status is Connect. */ static int bgp_reconnect(struct peer *peer) { if (bgp_stop(peer) < 0) return -1; bgp_start(peer); return 0; } static int bgp_fsm_open(struct peer *peer) { /* Send keepalive and make keepalive timer */ bgp_keepalive_send(peer); /* Reset holdtimer value. */ BGP_TIMER_OFF(peer->t_holdtime); return 0; } /* FSM error, unexpected event. This is error of BGP connection. So cut the peer and change to Idle status. */ static int bgp_fsm_event_error(struct peer *peer) { flog_err(EC_BGP_FSM, "%s [FSM] unexpected packet received in state %s", peer->host, lookup_msg(bgp_status_msg, peer->status, NULL)); return bgp_stop_with_notify(peer, BGP_NOTIFY_FSM_ERR, 0); } /* Hold timer expire. This is error of BGP connection. So cut the peer and change to Idle status. */ static int bgp_fsm_holdtime_expire(struct peer *peer) { if (bgp_debug_neighbor_events(peer)) zlog_debug("%s [FSM] Hold timer expire", peer->host); return bgp_stop_with_notify(peer, BGP_NOTIFY_HOLD_ERR, 0); } /** * Transition to Established state. * * Convert peer from stub to full fledged peer, set some timers, and generate * initial updates. */ static int bgp_establish(struct peer *peer) { afi_t afi; safi_t safi; int nsf_af_count = 0; int ret = 0; struct peer *other; other = peer->doppelganger; peer = peer_xfer_conn(peer); if (!peer) { flog_err(EC_BGP_CONNECT, "%%Neighbor failed in xfer_conn"); return -1; } if (other == peer) ret = 1; /* bgp_establish specific code when xfer_conn happens. */ /* Reset capability open status flag. */ if (!CHECK_FLAG(peer->sflags, PEER_STATUS_CAPABILITY_OPEN)) SET_FLAG(peer->sflags, PEER_STATUS_CAPABILITY_OPEN); /* Clear start timer value to default. */ peer->v_start = BGP_INIT_START_TIMER; /* Increment established count. */ peer->established++; bgp_fsm_change_status(peer, Established); /* bgp log-neighbor-changes of neighbor Up */ if (bgp_flag_check(peer->bgp, BGP_FLAG_LOG_NEIGHBOR_CHANGES)) { struct vrf *vrf = vrf_lookup_by_id(peer->bgp->vrf_id); zlog_info("%%ADJCHANGE: neighbor %s(%s) in vrf %s Up", peer->host, (peer->hostname) ? peer->hostname : "Unknown", vrf ? ((vrf->vrf_id != VRF_DEFAULT) ? vrf->name : VRF_DEFAULT_NAME) : ""); } /* assign update-group/subgroup */ update_group_adjust_peer_afs(peer); /* graceful restart */ UNSET_FLAG(peer->sflags, PEER_STATUS_NSF_WAIT); for (afi = AFI_IP; afi < AFI_MAX; afi++) for (safi = SAFI_UNICAST; safi <= SAFI_MPLS_VPN; safi++) { if (peer->afc_nego[afi][safi] && CHECK_FLAG(peer->cap, PEER_CAP_RESTART_ADV) && CHECK_FLAG(peer->af_cap[afi][safi], PEER_CAP_RESTART_AF_RCV)) { if (peer->nsf[afi][safi] && !CHECK_FLAG( peer->af_cap[afi][safi], PEER_CAP_RESTART_AF_PRESERVE_RCV)) bgp_clear_stale_route(peer, afi, safi); peer->nsf[afi][safi] = 1; nsf_af_count++; } else { if (peer->nsf[afi][safi]) bgp_clear_stale_route(peer, afi, safi); peer->nsf[afi][safi] = 0; } } if (nsf_af_count) SET_FLAG(peer->sflags, PEER_STATUS_NSF_MODE); else { UNSET_FLAG(peer->sflags, PEER_STATUS_NSF_MODE); if (peer->t_gr_stale) { BGP_TIMER_OFF(peer->t_gr_stale); if (bgp_debug_neighbor_events(peer)) zlog_debug( "%s graceful restart stalepath timer stopped", peer->host); } } if (peer->t_gr_restart) { BGP_TIMER_OFF(peer->t_gr_restart); if (bgp_debug_neighbor_events(peer)) zlog_debug("%s graceful restart timer stopped", peer->host); } hook_call(peer_established, peer); /* Reset uptime, turn on keepalives, send current table. */ if (!peer->v_holdtime) bgp_keepalives_on(peer); peer->uptime = bgp_clock(); /* Send route-refresh when ORF is enabled */ FOREACH_AFI_SAFI (afi, safi) { if (CHECK_FLAG(peer->af_cap[afi][safi], PEER_CAP_ORF_PREFIX_SM_ADV)) { if (CHECK_FLAG(peer->af_cap[afi][safi], PEER_CAP_ORF_PREFIX_RM_RCV)) bgp_route_refresh_send(peer, afi, safi, ORF_TYPE_PREFIX, REFRESH_IMMEDIATE, 0); else if (CHECK_FLAG(peer->af_cap[afi][safi], PEER_CAP_ORF_PREFIX_RM_OLD_RCV)) bgp_route_refresh_send(peer, afi, safi, ORF_TYPE_PREFIX_OLD, REFRESH_IMMEDIATE, 0); } } /* First update is deferred until ORF or ROUTE-REFRESH is received */ FOREACH_AFI_SAFI (afi, safi) { if (CHECK_FLAG(peer->af_cap[afi][safi], PEER_CAP_ORF_PREFIX_RM_ADV)) if (CHECK_FLAG(peer->af_cap[afi][safi], PEER_CAP_ORF_PREFIX_SM_RCV) || CHECK_FLAG(peer->af_cap[afi][safi], PEER_CAP_ORF_PREFIX_SM_OLD_RCV)) SET_FLAG(peer->af_sflags[afi][safi], PEER_STATUS_ORF_WAIT_REFRESH); } bgp_announce_peer(peer); /* Start the route advertisement timer to send updates to the peer - if * BGP * is not in read-only mode. If it is, the timer will be started at the * end * of read-only mode. */ if (!bgp_update_delay_active(peer->bgp)) { BGP_TIMER_OFF(peer->t_routeadv); BGP_TIMER_ON(peer->t_routeadv, bgp_routeadv_timer, 0); } if (peer->doppelganger && (peer->doppelganger->status != Deleted)) { if (bgp_debug_neighbor_events(peer)) zlog_debug( "[Event] Deleting stub connection for peer %s", peer->host); if (peer->doppelganger->status > Active) bgp_notify_send(peer->doppelganger, BGP_NOTIFY_CEASE, BGP_NOTIFY_CEASE_COLLISION_RESOLUTION); else peer_delete(peer->doppelganger); } /* * If we are replacing the old peer for a doppelganger * then switch it around in the bgp->peerhash * the doppelgangers su and this peer's su are the same * so the hash_release is the same for either. */ hash_release(peer->bgp->peerhash, peer); hash_get(peer->bgp->peerhash, peer, hash_alloc_intern); bgp_bfd_register_peer(peer); return ret; } /* Keepalive packet is received. */ static int bgp_fsm_keepalive(struct peer *peer) { BGP_TIMER_OFF(peer->t_holdtime); return 0; } /* Update packet is received. */ static int bgp_fsm_update(struct peer *peer) { BGP_TIMER_OFF(peer->t_holdtime); return 0; } /* This is empty event. */ static int bgp_ignore(struct peer *peer) { flog_err( EC_BGP_FSM, "%s [FSM] Ignoring event %s in state %s, prior events %s, %s, fd %d", peer->host, bgp_event_str[peer->cur_event], lookup_msg(bgp_status_msg, peer->status, NULL), bgp_event_str[peer->last_event], bgp_event_str[peer->last_major_event], peer->fd); return 0; } /* This is to handle unexpected events.. */ static int bgp_fsm_exeption(struct peer *peer) { flog_err( EC_BGP_FSM, "%s [FSM] Unexpected event %s in state %s, prior events %s, %s, fd %d", peer->host, bgp_event_str[peer->cur_event], lookup_msg(bgp_status_msg, peer->status, NULL), bgp_event_str[peer->last_event], bgp_event_str[peer->last_major_event], peer->fd); return (bgp_stop(peer)); } void bgp_fsm_event_update(struct peer *peer, int valid) { if (!peer) return; switch (peer->status) { case Idle: if (valid) BGP_EVENT_ADD(peer, BGP_Start); break; case Connect: if (!valid) { BGP_TIMER_OFF(peer->t_connect); BGP_EVENT_ADD(peer, TCP_fatal_error); } break; case Active: if (valid) { BGP_TIMER_OFF(peer->t_connect); BGP_EVENT_ADD(peer, ConnectRetry_timer_expired); } break; case OpenSent: case OpenConfirm: case Established: if (!valid && (peer->gtsm_hops == 1)) BGP_EVENT_ADD(peer, TCP_fatal_error); case Clearing: case Deleted: default: break; } } /* Finite State Machine structure */ static const struct { int (*func)(struct peer *); int next_state; } FSM[BGP_STATUS_MAX - 1][BGP_EVENTS_MAX - 1] = { { /* Idle state: In Idle state, all events other than BGP_Start is ignored. With BGP_Start event, finite state machine calls bgp_start(). */ {bgp_start, Connect}, /* BGP_Start */ {bgp_stop, Idle}, /* BGP_Stop */ {bgp_stop, Idle}, /* TCP_connection_open */ {bgp_stop, Idle}, /* TCP_connection_closed */ {bgp_ignore, Idle}, /* TCP_connection_open_failed */ {bgp_stop, Idle}, /* TCP_fatal_error */ {bgp_ignore, Idle}, /* ConnectRetry_timer_expired */ {bgp_ignore, Idle}, /* Hold_Timer_expired */ {bgp_ignore, Idle}, /* KeepAlive_timer_expired */ {bgp_ignore, Idle}, /* Receive_OPEN_message */ {bgp_ignore, Idle}, /* Receive_KEEPALIVE_message */ {bgp_ignore, Idle}, /* Receive_UPDATE_message */ {bgp_ignore, Idle}, /* Receive_NOTIFICATION_message */ {bgp_ignore, Idle}, /* Clearing_Completed */ }, { /* Connect */ {bgp_ignore, Connect}, /* BGP_Start */ {bgp_stop, Idle}, /* BGP_Stop */ {bgp_connect_success, OpenSent}, /* TCP_connection_open */ {bgp_stop, Idle}, /* TCP_connection_closed */ {bgp_connect_fail, Active}, /* TCP_connection_open_failed */ {bgp_connect_fail, Idle}, /* TCP_fatal_error */ {bgp_reconnect, Connect}, /* ConnectRetry_timer_expired */ {bgp_fsm_exeption, Idle}, /* Hold_Timer_expired */ {bgp_fsm_exeption, Idle}, /* KeepAlive_timer_expired */ {bgp_fsm_exeption, Idle}, /* Receive_OPEN_message */ {bgp_fsm_exeption, Idle}, /* Receive_KEEPALIVE_message */ {bgp_fsm_exeption, Idle}, /* Receive_UPDATE_message */ {bgp_stop, Idle}, /* Receive_NOTIFICATION_message */ {bgp_fsm_exeption, Idle}, /* Clearing_Completed */ }, { /* Active, */ {bgp_ignore, Active}, /* BGP_Start */ {bgp_stop, Idle}, /* BGP_Stop */ {bgp_connect_success, OpenSent}, /* TCP_connection_open */ {bgp_stop, Idle}, /* TCP_connection_closed */ {bgp_ignore, Active}, /* TCP_connection_open_failed */ {bgp_fsm_exeption, Idle}, /* TCP_fatal_error */ {bgp_start, Connect}, /* ConnectRetry_timer_expired */ {bgp_fsm_exeption, Idle}, /* Hold_Timer_expired */ {bgp_fsm_exeption, Idle}, /* KeepAlive_timer_expired */ {bgp_fsm_exeption, Idle}, /* Receive_OPEN_message */ {bgp_fsm_exeption, Idle}, /* Receive_KEEPALIVE_message */ {bgp_fsm_exeption, Idle}, /* Receive_UPDATE_message */ {bgp_fsm_exeption, Idle}, /* Receive_NOTIFICATION_message */ {bgp_fsm_exeption, Idle}, /* Clearing_Completed */ }, { /* OpenSent, */ {bgp_ignore, OpenSent}, /* BGP_Start */ {bgp_stop, Idle}, /* BGP_Stop */ {bgp_stop, Active}, /* TCP_connection_open */ {bgp_stop, Active}, /* TCP_connection_closed */ {bgp_stop, Active}, /* TCP_connection_open_failed */ {bgp_stop, Active}, /* TCP_fatal_error */ {bgp_fsm_exeption, Idle}, /* ConnectRetry_timer_expired */ {bgp_fsm_holdtime_expire, Idle}, /* Hold_Timer_expired */ {bgp_fsm_exeption, Idle}, /* KeepAlive_timer_expired */ {bgp_fsm_open, OpenConfirm}, /* Receive_OPEN_message */ {bgp_fsm_event_error, Idle}, /* Receive_KEEPALIVE_message */ {bgp_fsm_event_error, Idle}, /* Receive_UPDATE_message */ {bgp_stop_with_error, Idle}, /* Receive_NOTIFICATION_message */ {bgp_fsm_exeption, Idle}, /* Clearing_Completed */ }, { /* OpenConfirm, */ {bgp_ignore, OpenConfirm}, /* BGP_Start */ {bgp_stop, Idle}, /* BGP_Stop */ {bgp_stop, Idle}, /* TCP_connection_open */ {bgp_stop, Idle}, /* TCP_connection_closed */ {bgp_stop, Idle}, /* TCP_connection_open_failed */ {bgp_stop, Idle}, /* TCP_fatal_error */ {bgp_fsm_exeption, Idle}, /* ConnectRetry_timer_expired */ {bgp_fsm_holdtime_expire, Idle}, /* Hold_Timer_expired */ {bgp_ignore, OpenConfirm}, /* KeepAlive_timer_expired */ {bgp_fsm_exeption, Idle}, /* Receive_OPEN_message */ {bgp_establish, Established}, /* Receive_KEEPALIVE_message */ {bgp_fsm_exeption, Idle}, /* Receive_UPDATE_message */ {bgp_stop_with_error, Idle}, /* Receive_NOTIFICATION_message */ {bgp_fsm_exeption, Idle}, /* Clearing_Completed */ }, { /* Established, */ {bgp_ignore, Established}, /* BGP_Start */ {bgp_stop, Clearing}, /* BGP_Stop */ {bgp_stop, Clearing}, /* TCP_connection_open */ {bgp_stop, Clearing}, /* TCP_connection_closed */ {bgp_stop, Clearing}, /* TCP_connection_open_failed */ {bgp_stop, Clearing}, /* TCP_fatal_error */ {bgp_stop, Clearing}, /* ConnectRetry_timer_expired */ {bgp_fsm_holdtime_expire, Clearing}, /* Hold_Timer_expired */ {bgp_ignore, Established}, /* KeepAlive_timer_expired */ {bgp_stop, Clearing}, /* Receive_OPEN_message */ {bgp_fsm_keepalive, Established}, /* Receive_KEEPALIVE_message */ {bgp_fsm_update, Established}, /* Receive_UPDATE_message */ {bgp_stop_with_error, Clearing}, /* Receive_NOTIFICATION_message */ {bgp_fsm_exeption, Idle}, /* Clearing_Completed */ }, { /* Clearing, */ {bgp_ignore, Clearing}, /* BGP_Start */ {bgp_stop, Clearing}, /* BGP_Stop */ {bgp_stop, Clearing}, /* TCP_connection_open */ {bgp_stop, Clearing}, /* TCP_connection_closed */ {bgp_stop, Clearing}, /* TCP_connection_open_failed */ {bgp_stop, Clearing}, /* TCP_fatal_error */ {bgp_stop, Clearing}, /* ConnectRetry_timer_expired */ {bgp_stop, Clearing}, /* Hold_Timer_expired */ {bgp_stop, Clearing}, /* KeepAlive_timer_expired */ {bgp_stop, Clearing}, /* Receive_OPEN_message */ {bgp_stop, Clearing}, /* Receive_KEEPALIVE_message */ {bgp_stop, Clearing}, /* Receive_UPDATE_message */ {bgp_stop, Clearing}, /* Receive_NOTIFICATION_message */ {bgp_clearing_completed, Idle}, /* Clearing_Completed */ }, { /* Deleted, */ {bgp_ignore, Deleted}, /* BGP_Start */ {bgp_ignore, Deleted}, /* BGP_Stop */ {bgp_ignore, Deleted}, /* TCP_connection_open */ {bgp_ignore, Deleted}, /* TCP_connection_closed */ {bgp_ignore, Deleted}, /* TCP_connection_open_failed */ {bgp_ignore, Deleted}, /* TCP_fatal_error */ {bgp_ignore, Deleted}, /* ConnectRetry_timer_expired */ {bgp_ignore, Deleted}, /* Hold_Timer_expired */ {bgp_ignore, Deleted}, /* KeepAlive_timer_expired */ {bgp_ignore, Deleted}, /* Receive_OPEN_message */ {bgp_ignore, Deleted}, /* Receive_KEEPALIVE_message */ {bgp_ignore, Deleted}, /* Receive_UPDATE_message */ {bgp_ignore, Deleted}, /* Receive_NOTIFICATION_message */ {bgp_ignore, Deleted}, /* Clearing_Completed */ }, }; /* Execute event process. */ int bgp_event(struct thread *thread) { int event; struct peer *peer; int ret; peer = THREAD_ARG(thread); event = THREAD_VAL(thread); ret = bgp_event_update(peer, event); return (ret); } int bgp_event_update(struct peer *peer, int event) { int next; int ret = 0; struct peer *other; int passive_conn = 0; int dyn_nbr; /* default return code */ ret = FSM_PEER_NOOP; other = peer->doppelganger; passive_conn = (CHECK_FLAG(peer->sflags, PEER_STATUS_ACCEPT_PEER)) ? 1 : 0; dyn_nbr = peer_dynamic_neighbor(peer); /* Logging this event. */ next = FSM[peer->status - 1][event - 1].next_state; if (bgp_debug_neighbor_events(peer) && peer->status != next) zlog_debug("%s [FSM] %s (%s->%s), fd %d", peer->host, bgp_event_str[event], lookup_msg(bgp_status_msg, peer->status, NULL), lookup_msg(bgp_status_msg, next, NULL), peer->fd); peer->last_event = peer->cur_event; peer->cur_event = event; /* Call function. */ if (FSM[peer->status - 1][event - 1].func) ret = (*(FSM[peer->status - 1][event - 1].func))(peer); if (ret >= 0) { if (ret == 1 && next == Established) { /* The case when doppelganger swap accurred in bgp_establish. Update the peer pointer accordingly */ ret = FSM_PEER_TRANSFERRED; peer = other; } /* If status is changed. */ if (next != peer->status) { bgp_fsm_change_status(peer, next); /* * If we're going to ESTABLISHED then we executed a * peer transfer. In this case we can either return * FSM_PEER_TRANSITIONED or FSM_PEER_TRANSFERRED. * Opting for TRANSFERRED since transfer implies * session establishment. */ if (ret != FSM_PEER_TRANSFERRED) ret = FSM_PEER_TRANSITIONED; } /* Make sure timer is set. */ bgp_timer_set(peer); } else { /* * If we got a return value of -1, that means there was an * error, restart the FSM. Since bgp_stop() was called on the * peer. only a few fields are safe to access here. In any case * we need to indicate that the peer was stopped in the return * code. */ if (!dyn_nbr && !passive_conn && peer->bgp) { flog_err( EC_BGP_FSM, "%s [FSM] Failure handling event %s in state %s, " "prior events %s, %s, fd %d", peer->host, bgp_event_str[peer->cur_event], lookup_msg(bgp_status_msg, peer->status, NULL), bgp_event_str[peer->last_event], bgp_event_str[peer->last_major_event], peer->fd); bgp_stop(peer); bgp_fsm_change_status(peer, Idle); bgp_timer_set(peer); } ret = FSM_PEER_STOPPED; } return ret; } frr-7.2.1/bgpd/bgp_fsm.h0000644000000000000000000000735713610377563011732 00000000000000/* BGP-4 Finite State Machine * From RFC1771 [A Border Gateway Protocol 4 (BGP-4)] * Copyright (C) 1998 Kunihiro Ishiguro * * This file is part of GNU Zebra. * * GNU Zebra is free software; you can redistribute it and/or modify it * under the terms of the GNU General Public License as published by the * Free Software Foundation; either version 2, or (at your option) any * later version. * * GNU Zebra 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; see the file COPYING; if not, write to the Free Software * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA */ #ifndef _QUAGGA_BGP_FSM_H #define _QUAGGA_BGP_FSM_H /* Macro for BGP read, write and timer thread. */ #define BGP_TIMER_ON(T, F, V) \ do { \ if ((peer->status != Deleted)) \ thread_add_timer(bm->master, (F), peer, (V), &(T)); \ } while (0) #define BGP_TIMER_OFF(T) \ do { \ if (T) \ THREAD_TIMER_OFF(T); \ } while (0) #define BGP_EVENT_ADD(P, E) \ do { \ if ((P)->status != Deleted) \ thread_add_event(bm->master, bgp_event, (P), (E), \ NULL); \ } while (0) #define BGP_EVENT_FLUSH(P) \ do { \ assert(peer); \ thread_cancel_event(bm->master, (P)); \ } while (0) #define BGP_MSEC_JITTER 10 /* Status codes for bgp_event_update() */ #define FSM_PEER_NOOP 0 #define FSM_PEER_STOPPED 1 #define FSM_PEER_TRANSFERRED 2 #define FSM_PEER_TRANSITIONED 3 /* Prototypes. */ extern void bgp_fsm_event_update(struct peer *peer, int valid); extern int bgp_event(struct thread *); extern int bgp_event_update(struct peer *, int event); extern int bgp_stop(struct peer *peer); extern void bgp_timer_set(struct peer *); extern int bgp_routeadv_timer(struct thread *); extern void bgp_fsm_change_status(struct peer *peer, int status); extern const char *peer_down_str[]; extern void bgp_update_delay_end(struct bgp *); extern void bgp_maxmed_update(struct bgp *); extern int bgp_maxmed_onstartup_configured(struct bgp *); extern int bgp_maxmed_onstartup_active(struct bgp *); /** * Start the route advertisement timer (that honors MRAI) for all the * peers. Typically called at the end of initial convergence, coming * out of read-only mode. */ extern void bgp_start_routeadv(struct bgp *); /** * See if the route advertisement timer needs to be adjusted for a * peer. For example, if the last update was written to the peer a * long while back, we don't need to wait for the periodic advertisement * timer to expire to send the new set of prefixes. It should fire * instantly and updates should go out sooner. */ extern void bgp_adjust_routeadv(struct peer *); #include "hook.h" DECLARE_HOOK(peer_backward_transition, (struct peer * peer), (peer)) DECLARE_HOOK(peer_established, (struct peer * peer), (peer)) #endif /* _QUAGGA_BGP_FSM_H */ frr-7.2.1/bgpd/bgp_io.c0000644000000000000000000003427713610377563011550 00000000000000/* BGP I/O. * Implements packet I/O in a pthread. * Copyright (C) 2017 Cumulus Networks * Quentin Young * * This program is free software; you can redistribute it and/or modify * it under the terms of the 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; see the file COPYING; if not, write to the * Free Software Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, * MA 02110-1301 USA */ /* clang-format off */ #include #include // for pthread_mutex_unlock, pthread_mutex_lock #include "frr_pthread.h" #include "linklist.h" // for list_delete, list_delete_all_node, lis... #include "log.h" // for zlog_debug, safe_strerror, zlog_err #include "memory.h" // for MTYPE_TMP, XCALLOC, XFREE #include "network.h" // for ERRNO_IO_RETRY #include "stream.h" // for stream_get_endp, stream_getw_from, str... #include "ringbuf.h" // for ringbuf_remain, ringbuf_peek, ringbuf_... #include "thread.h" // for THREAD_OFF, THREAD_ARG, thread, thread... #include "zassert.h" // for assert #include "bgpd/bgp_io.h" #include "bgpd/bgp_debug.h" // for bgp_debug_neighbor_events, bgp_type_str #include "bgpd/bgp_errors.h" // for expanded error reference information #include "bgpd/bgp_fsm.h" // for BGP_EVENT_ADD, bgp_event #include "bgpd/bgp_packet.h" // for bgp_notify_send_with_data, bgp_notify... #include "bgpd/bgpd.h" // for peer, BGP_MARKER_SIZE, bgp_master, bm /* clang-format on */ /* forward declarations */ static uint16_t bgp_write(struct peer *); static uint16_t bgp_read(struct peer *); static int bgp_process_writes(struct thread *); static int bgp_process_reads(struct thread *); static bool validate_header(struct peer *); /* generic i/o status codes */ #define BGP_IO_TRANS_ERR (1 << 0) // EAGAIN or similar occurred #define BGP_IO_FATAL_ERR (1 << 1) // some kind of fatal TCP error /* Thread external API ----------------------------------------------------- */ void bgp_writes_on(struct peer *peer) { struct frr_pthread *fpt = bgp_pth_io; assert(fpt->running); assert(peer->status != Deleted); assert(peer->obuf); assert(peer->ibuf); assert(peer->ibuf_work); assert(!peer->t_connect_check_r); assert(!peer->t_connect_check_w); assert(peer->fd); thread_add_write(fpt->master, bgp_process_writes, peer, peer->fd, &peer->t_write); SET_FLAG(peer->thread_flags, PEER_THREAD_WRITES_ON); } void bgp_writes_off(struct peer *peer) { struct frr_pthread *fpt = bgp_pth_io; assert(fpt->running); thread_cancel_async(fpt->master, &peer->t_write, NULL); THREAD_OFF(peer->t_generate_updgrp_packets); UNSET_FLAG(peer->thread_flags, PEER_THREAD_WRITES_ON); } void bgp_reads_on(struct peer *peer) { struct frr_pthread *fpt = bgp_pth_io; assert(fpt->running); assert(peer->status != Deleted); assert(peer->ibuf); assert(peer->fd); assert(peer->ibuf_work); assert(peer->obuf); assert(!peer->t_connect_check_r); assert(!peer->t_connect_check_w); assert(peer->fd); thread_add_read(fpt->master, bgp_process_reads, peer, peer->fd, &peer->t_read); SET_FLAG(peer->thread_flags, PEER_THREAD_READS_ON); } void bgp_reads_off(struct peer *peer) { struct frr_pthread *fpt = bgp_pth_io; assert(fpt->running); thread_cancel_async(fpt->master, &peer->t_read, NULL); THREAD_OFF(peer->t_process_packet); UNSET_FLAG(peer->thread_flags, PEER_THREAD_READS_ON); } /* Thread internal functions ----------------------------------------------- */ /* * Called from I/O pthread when a file descriptor has become ready for writing. */ static int bgp_process_writes(struct thread *thread) { static struct peer *peer; peer = THREAD_ARG(thread); uint16_t status; bool reschedule; bool fatal = false; if (peer->fd < 0) return -1; struct frr_pthread *fpt = bgp_pth_io; frr_with_mutex(&peer->io_mtx) { status = bgp_write(peer); reschedule = (stream_fifo_head(peer->obuf) != NULL); } /* no problem */ if (CHECK_FLAG(status, BGP_IO_TRANS_ERR)) { } /* problem */ if (CHECK_FLAG(status, BGP_IO_FATAL_ERR)) { reschedule = false; fatal = true; } if (reschedule) { thread_add_write(fpt->master, bgp_process_writes, peer, peer->fd, &peer->t_write); } else if (!fatal) { BGP_TIMER_ON(peer->t_generate_updgrp_packets, bgp_generate_updgrp_packets, 0); } return 0; } /* * Called from I/O pthread when a file descriptor has become ready for reading, * or has hung up. * * We read as much data as possible, process as many packets as we can and * place them on peer->ibuf for secondary processing by the main thread. */ static int bgp_process_reads(struct thread *thread) { /* clang-format off */ static struct peer *peer; // peer to read from uint16_t status; // bgp_read status code bool more = true; // whether we got more data bool fatal = false; // whether fatal error occurred bool added_pkt = false; // whether we pushed onto ->ibuf /* clang-format on */ peer = THREAD_ARG(thread); if (peer->fd < 0 || bm->terminating) return -1; struct frr_pthread *fpt = bgp_pth_io; frr_with_mutex(&peer->io_mtx) { status = bgp_read(peer); } /* error checking phase */ if (CHECK_FLAG(status, BGP_IO_TRANS_ERR)) { /* no problem; just don't process packets */ more = false; } if (CHECK_FLAG(status, BGP_IO_FATAL_ERR)) { /* problem; tear down session */ more = false; fatal = true; } while (more) { /* static buffer for transferring packets */ static unsigned char pktbuf[BGP_MAX_PACKET_SIZE]; /* shorter alias to peer's input buffer */ struct ringbuf *ibw = peer->ibuf_work; /* packet size as given by header */ uint16_t pktsize = 0; /* check that we have enough data for a header */ if (ringbuf_remain(ibw) < BGP_HEADER_SIZE) break; /* check that header is valid */ if (!validate_header(peer)) { fatal = true; break; } /* header is valid; retrieve packet size */ ringbuf_peek(ibw, BGP_MARKER_SIZE, &pktsize, sizeof(pktsize)); pktsize = ntohs(pktsize); /* if this fails we are seriously screwed */ assert(pktsize <= BGP_MAX_PACKET_SIZE); /* * If we have that much data, chuck it into its own * stream and append to input queue for processing. */ if (ringbuf_remain(ibw) >= pktsize) { struct stream *pkt = stream_new(pktsize); assert(ringbuf_get(ibw, pktbuf, pktsize) == pktsize); stream_put(pkt, pktbuf, pktsize); frr_with_mutex(&peer->io_mtx) { stream_fifo_push(peer->ibuf, pkt); } added_pkt = true; } else break; } assert(ringbuf_space(peer->ibuf_work) >= BGP_MAX_PACKET_SIZE); /* handle invalid header */ if (fatal) { /* wipe buffer just in case someone screwed up */ ringbuf_wipe(peer->ibuf_work); } else { thread_add_read(fpt->master, bgp_process_reads, peer, peer->fd, &peer->t_read); if (added_pkt) thread_add_timer_msec(bm->master, bgp_process_packet, peer, 0, &peer->t_process_packet); } return 0; } /* * Flush peer output buffer. * * This function pops packets off of peer->obuf and writes them to peer->fd. * The amount of packets written is equal to the minimum of peer->wpkt_quanta * and the number of packets on the output buffer, unless an error occurs. * * If write() returns an error, the appropriate FSM event is generated. * * The return value is equal to the number of packets written * (which may be zero). */ static uint16_t bgp_write(struct peer *peer) { uint8_t type; struct stream *s; int num; int update_last_write = 0; unsigned int count = 0; uint32_t uo = 0; uint16_t status = 0; uint32_t wpkt_quanta_old; wpkt_quanta_old = atomic_load_explicit(&peer->bgp->wpkt_quanta, memory_order_relaxed); while (count < wpkt_quanta_old && (s = stream_fifo_head(peer->obuf))) { int writenum; do { writenum = stream_get_endp(s) - stream_get_getp(s); num = write(peer->fd, stream_pnt(s), writenum); if (num < 0) { if (!ERRNO_IO_RETRY(errno)) { BGP_EVENT_ADD(peer, TCP_fatal_error); SET_FLAG(status, BGP_IO_FATAL_ERR); } else { SET_FLAG(status, BGP_IO_TRANS_ERR); } goto done; } else if (num != writenum) stream_forward_getp(s, num); } while (num != writenum); /* Retrieve BGP packet type. */ stream_set_getp(s, BGP_MARKER_SIZE + 2); type = stream_getc(s); switch (type) { case BGP_MSG_OPEN: atomic_fetch_add_explicit(&peer->open_out, 1, memory_order_relaxed); break; case BGP_MSG_UPDATE: atomic_fetch_add_explicit(&peer->update_out, 1, memory_order_relaxed); uo++; break; case BGP_MSG_NOTIFY: atomic_fetch_add_explicit(&peer->notify_out, 1, memory_order_relaxed); /* Double start timer. */ peer->v_start *= 2; /* Overflow check. */ if (peer->v_start >= (60 * 2)) peer->v_start = (60 * 2); /* * Handle Graceful Restart case where the state changes * to Connect instead of Idle. */ BGP_EVENT_ADD(peer, BGP_Stop); goto done; case BGP_MSG_KEEPALIVE: atomic_fetch_add_explicit(&peer->keepalive_out, 1, memory_order_relaxed); break; case BGP_MSG_ROUTE_REFRESH_NEW: case BGP_MSG_ROUTE_REFRESH_OLD: atomic_fetch_add_explicit(&peer->refresh_out, 1, memory_order_relaxed); break; case BGP_MSG_CAPABILITY: atomic_fetch_add_explicit(&peer->dynamic_cap_out, 1, memory_order_relaxed); break; } count++; stream_free(stream_fifo_pop(peer->obuf)); update_last_write = 1; } done : { /* * Update last_update if UPDATEs were written. * Note: that these are only updated at end, * not per message (i.e., per loop) */ if (uo) atomic_store_explicit(&peer->last_update, bgp_clock(), memory_order_relaxed); /* If we TXed any flavor of packet */ if (update_last_write) atomic_store_explicit(&peer->last_write, bgp_clock(), memory_order_relaxed); } return status; } /* * Reads a chunk of data from peer->fd into peer->ibuf_work. * * @return status flag (see top-of-file) */ static uint16_t bgp_read(struct peer *peer) { size_t readsize; // how many bytes we want to read ssize_t nbytes; // how many bytes we actually read uint16_t status = 0; static uint8_t ibw[BGP_MAX_PACKET_SIZE * BGP_READ_PACKET_MAX]; readsize = MIN(ringbuf_space(peer->ibuf_work), sizeof(ibw)); nbytes = read(peer->fd, ibw, readsize); /* EAGAIN or EWOULDBLOCK; come back later */ if (nbytes < 0 && ERRNO_IO_RETRY(errno)) { SET_FLAG(status, BGP_IO_TRANS_ERR); /* Fatal error; tear down session */ } else if (nbytes < 0) { flog_err(EC_BGP_UPDATE_RCV, "%s [Error] bgp_read_packet error: %s", peer->host, safe_strerror(errno)); if (peer->status == Established) { if (CHECK_FLAG(peer->sflags, PEER_STATUS_NSF_MODE)) { peer->last_reset = PEER_DOWN_NSF_CLOSE_SESSION; SET_FLAG(peer->sflags, PEER_STATUS_NSF_WAIT); } else peer->last_reset = PEER_DOWN_CLOSE_SESSION; } BGP_EVENT_ADD(peer, TCP_fatal_error); SET_FLAG(status, BGP_IO_FATAL_ERR); /* Received EOF / TCP session closed */ } else if (nbytes == 0) { if (bgp_debug_neighbor_events(peer)) zlog_debug("%s [Event] BGP connection closed fd %d", peer->host, peer->fd); if (peer->status == Established) { if (CHECK_FLAG(peer->sflags, PEER_STATUS_NSF_MODE)) { peer->last_reset = PEER_DOWN_NSF_CLOSE_SESSION; SET_FLAG(peer->sflags, PEER_STATUS_NSF_WAIT); } else peer->last_reset = PEER_DOWN_CLOSE_SESSION; } BGP_EVENT_ADD(peer, TCP_connection_closed); SET_FLAG(status, BGP_IO_FATAL_ERR); } else { assert(ringbuf_put(peer->ibuf_work, ibw, nbytes) == (size_t)nbytes); } return status; } /* * Called after we have read a BGP packet header. Validates marker, message * type and packet length. If any of these aren't correct, sends a notify. * * Assumes that there are at least BGP_HEADER_SIZE readable bytes in the input * buffer. */ static bool validate_header(struct peer *peer) { uint16_t size; uint8_t type; struct ringbuf *pkt = peer->ibuf_work; static uint8_t m_correct[BGP_MARKER_SIZE] = { 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff}; uint8_t m_rx[BGP_MARKER_SIZE] = {0x00}; if (ringbuf_peek(pkt, 0, m_rx, BGP_MARKER_SIZE) != BGP_MARKER_SIZE) return false; if (memcmp(m_correct, m_rx, BGP_MARKER_SIZE) != 0) { bgp_notify_send(peer, BGP_NOTIFY_HEADER_ERR, BGP_NOTIFY_HEADER_NOT_SYNC); return false; } /* Get size and type in network byte order. */ ringbuf_peek(pkt, BGP_MARKER_SIZE, &size, sizeof(size)); ringbuf_peek(pkt, BGP_MARKER_SIZE + 2, &type, sizeof(type)); size = ntohs(size); /* BGP type check. */ if (type != BGP_MSG_OPEN && type != BGP_MSG_UPDATE && type != BGP_MSG_NOTIFY && type != BGP_MSG_KEEPALIVE && type != BGP_MSG_ROUTE_REFRESH_NEW && type != BGP_MSG_ROUTE_REFRESH_OLD && type != BGP_MSG_CAPABILITY) { if (bgp_debug_neighbor_events(peer)) zlog_debug("%s unknown message type 0x%02x", peer->host, type); bgp_notify_send_with_data(peer, BGP_NOTIFY_HEADER_ERR, BGP_NOTIFY_HEADER_BAD_MESTYPE, &type, 1); return false; } /* Minimum packet length check. */ if ((size < BGP_HEADER_SIZE) || (size > BGP_MAX_PACKET_SIZE) || (type == BGP_MSG_OPEN && size < BGP_MSG_OPEN_MIN_SIZE) || (type == BGP_MSG_UPDATE && size < BGP_MSG_UPDATE_MIN_SIZE) || (type == BGP_MSG_NOTIFY && size < BGP_MSG_NOTIFY_MIN_SIZE) || (type == BGP_MSG_KEEPALIVE && size != BGP_MSG_KEEPALIVE_MIN_SIZE) || (type == BGP_MSG_ROUTE_REFRESH_NEW && size < BGP_MSG_ROUTE_REFRESH_MIN_SIZE) || (type == BGP_MSG_ROUTE_REFRESH_OLD && size < BGP_MSG_ROUTE_REFRESH_MIN_SIZE) || (type == BGP_MSG_CAPABILITY && size < BGP_MSG_CAPABILITY_MIN_SIZE)) { if (bgp_debug_neighbor_events(peer)) { zlog_debug("%s bad message length - %d for %s", peer->host, size, type == 128 ? "ROUTE-REFRESH" : bgp_type_str[(int)type]); } uint16_t nsize = htons(size); bgp_notify_send_with_data(peer, BGP_NOTIFY_HEADER_ERR, BGP_NOTIFY_HEADER_BAD_MESLEN, (unsigned char *)&nsize, 2); return false; } return true; } frr-7.2.1/bgpd/bgp_io.h0000644000000000000000000000560113610377563011542 00000000000000/* BGP I/O. * Implements packet I/O in a pthread. * Copyright (C) 2017 Cumulus Networks * Quentin Young * * This program is free software; you can redistribute it and/or modify * it under the terms of the 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; see the file COPYING; if not, write to the * Free Software Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, * MA 02110-1301 USA */ #ifndef _FRR_BGP_IO_H #define _FRR_BGP_IO_H #define BGP_WRITE_PACKET_MAX 10U #define BGP_READ_PACKET_MAX 10U #include "bgpd/bgpd.h" #include "frr_pthread.h" /** * Start function for write thread. * * @param arg - unused */ extern void *bgp_io_start(void *arg); /** * Start function for write thread. * * Uninitializes all resources and stops the thread. * * @param result - where to store data result, unused */ extern int bgp_io_stop(void **result, struct frr_pthread *fpt); /** * Turns on packet writing for a peer. * * After this function is called, any packets placed on peer->obuf will be * written to peer->fd until no more packets remain. * * Additionally, it becomes unsafe to perform socket actions on peer->fd. * * @param peer - peer to register */ extern void bgp_writes_on(struct peer *peer); /** * Turns off packet writing for a peer. * * After this function returns, packets placed on peer->obuf will not be * written to peer->fd by the I/O thread. * * After this function returns it becomes safe to perform socket actions on * peer->fd. * * @param peer - peer to deregister * @param flush - as described */ extern void bgp_writes_off(struct peer *peer); /** * Turns on packet reading for a peer. * * After this function is called, any packets received on peer->fd will be read * and copied into the FIFO queue peer->ibuf. * * Additionally, it becomes unsafe to perform socket actions on peer->fd. * * Whenever one or more packets are placed onto peer->ibuf, a task of type * THREAD_EVENT will be placed on the main thread whose handler is * * bgp_packet.c:bgp_process_packet() * * @param peer - peer to register */ extern void bgp_reads_on(struct peer *peer); /** * Turns off packet reading for a peer. * * After this function is called, any packets received on peer->fd will not be * read by the I/O thread. * * After this function returns it becomes safe to perform socket actions on * peer->fd. * * @param peer - peer to deregister */ extern void bgp_reads_off(struct peer *peer); #endif /* _FRR_BGP_IO_H */ frr-7.2.1/bgpd/bgp_keepalives.c0000644000000000000000000002112513610377563013255 00000000000000/* BGP Keepalives. * Implements a producer thread to generate BGP keepalives for peers. * Copyright (C) 2017 Cumulus Networks, Inc. * Quentin Young * * This file is part of FRRouting. * * FRRouting is free software; you can redistribute it and/or modify it under * the terms of the GNU General Public License as published by the Free * Software Foundation; either version 2, or (at your option) any later * version. * * FRRouting 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; see the file COPYING; if not, write to the Free Software * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA */ /* clang-format off */ #include #include // for pthread_mutex_lock, pthread_mutex_unlock #include "frr_pthread.h" // for frr_pthread #include "hash.h" // for hash, hash_clean, hash_create_size... #include "log.h" // for zlog_debug #include "memory.h" // for MTYPE_TMP, XFREE, XCALLOC, XMALLOC #include "monotime.h" // for monotime, monotime_since #include "bgpd/bgpd.h" // for peer, PEER_THREAD_KEEPALIVES_ON, peer... #include "bgpd/bgp_debug.h" // for bgp_debug_neighbor_events #include "bgpd/bgp_packet.h" // for bgp_keepalive_send #include "bgpd/bgp_keepalives.h" /* clang-format on */ /* * Peer KeepAlive Timer. * Associates a peer with the time of its last keepalive. */ struct pkat { /* the peer to send keepalives to */ struct peer *peer; /* absolute time of last keepalive sent */ struct timeval last; }; /* List of peers we are sending keepalives for, and associated mutex. */ static pthread_mutex_t *peerhash_mtx; static pthread_cond_t *peerhash_cond; static struct hash *peerhash; static struct pkat *pkat_new(struct peer *peer) { struct pkat *pkat = XMALLOC(MTYPE_TMP, sizeof(struct pkat)); pkat->peer = peer; monotime(&pkat->last); return pkat; } static void pkat_del(void *pkat) { XFREE(MTYPE_TMP, pkat); } /* * Callback for hash_iterate. Determines if a peer needs a keepalive and if so, * generates and sends it. * * For any given peer, if the elapsed time since its last keepalive exceeds its * configured keepalive timer, a keepalive is sent to the peer and its * last-sent time is reset. Additionally, If the elapsed time does not exceed * the configured keepalive timer, but the time until the next keepalive is due * is within a hardcoded tolerance, a keepalive is sent as if the configured * timer was exceeded. Doing this helps alleviate nanosecond sleeps between * ticks by grouping together peers who are due for keepalives at roughly the * same time. This tolerance value is arbitrarily chosen to be 100ms. * * In addition, this function calculates the maximum amount of time that the * keepalive thread can sleep before another tick needs to take place. This is * equivalent to shortest time until a keepalive is due for any one peer. * * @return maximum time to wait until next update (0 if infinity) */ static void peer_process(struct hash_bucket *hb, void *arg) { struct pkat *pkat = hb->data; struct timeval *next_update = arg; static struct timeval elapsed; // elapsed time since keepalive static struct timeval ka = {0}; // peer->v_keepalive as a timeval static struct timeval diff; // ka - elapsed static struct timeval tolerance = {0, 100000}; uint32_t v_ka = atomic_load_explicit(&pkat->peer->v_keepalive, memory_order_relaxed); /* 0 keepalive timer means no keepalives */ if (v_ka == 0) return; /* calculate elapsed time since last keepalive */ monotime_since(&pkat->last, &elapsed); /* calculate difference between elapsed time and configured time */ ka.tv_sec = v_ka; timersub(&ka, &elapsed, &diff); int send_keepalive = elapsed.tv_sec >= ka.tv_sec || timercmp(&diff, &tolerance, <); if (send_keepalive) { if (bgp_debug_neighbor_events(pkat->peer)) zlog_debug("%s [FSM] Timer (keepalive timer expire)", pkat->peer->host); bgp_keepalive_send(pkat->peer); monotime(&pkat->last); memset(&elapsed, 0x00, sizeof(struct timeval)); diff = ka; } /* if calculated next update for this peer < current delay, use it */ if (next_update->tv_sec < 0 || timercmp(&diff, next_update, <)) *next_update = diff; } static bool peer_hash_cmp(const void *f, const void *s) { const struct pkat *p1 = f; const struct pkat *p2 = s; return p1->peer == p2->peer; } static unsigned int peer_hash_key(const void *arg) { const struct pkat *pkat = arg; return (uintptr_t)pkat->peer; } /* Cleanup handler / deinitializer. */ static void bgp_keepalives_finish(void *arg) { if (peerhash) { hash_clean(peerhash, pkat_del); hash_free(peerhash); } peerhash = NULL; pthread_mutex_unlock(peerhash_mtx); pthread_mutex_destroy(peerhash_mtx); pthread_cond_destroy(peerhash_cond); XFREE(MTYPE_TMP, peerhash_mtx); XFREE(MTYPE_TMP, peerhash_cond); } /* * Entry function for peer keepalive generation pthread. */ void *bgp_keepalives_start(void *arg) { struct frr_pthread *fpt = arg; fpt->master->owner = pthread_self(); struct timeval currtime = {0, 0}; struct timeval aftertime = {0, 0}; struct timeval next_update = {0, 0}; struct timespec next_update_ts = {0, 0}; peerhash_mtx = XCALLOC(MTYPE_TMP, sizeof(pthread_mutex_t)); peerhash_cond = XCALLOC(MTYPE_TMP, sizeof(pthread_cond_t)); /* initialize mutex */ pthread_mutex_init(peerhash_mtx, NULL); /* use monotonic clock with condition variable */ pthread_condattr_t attrs; pthread_condattr_init(&attrs); pthread_condattr_setclock(&attrs, CLOCK_MONOTONIC); pthread_cond_init(peerhash_cond, &attrs); pthread_condattr_destroy(&attrs); /* * We are not using normal FRR pthread mechanics and are * not using fpt_run */ frr_pthread_set_name(fpt); /* initialize peer hashtable */ peerhash = hash_create_size(2048, peer_hash_key, peer_hash_cmp, NULL); pthread_mutex_lock(peerhash_mtx); /* register cleanup handler */ pthread_cleanup_push(&bgp_keepalives_finish, NULL); /* notify anybody waiting on us that we are done starting up */ frr_pthread_notify_running(fpt); while (atomic_load_explicit(&fpt->running, memory_order_relaxed)) { if (peerhash->count > 0) pthread_cond_timedwait(peerhash_cond, peerhash_mtx, &next_update_ts); else while (peerhash->count == 0 && atomic_load_explicit(&fpt->running, memory_order_relaxed)) pthread_cond_wait(peerhash_cond, peerhash_mtx); monotime(&currtime); next_update.tv_sec = -1; hash_iterate(peerhash, peer_process, &next_update); if (next_update.tv_sec == -1) memset(&next_update, 0x00, sizeof(next_update)); monotime_since(&currtime, &aftertime); timeradd(&currtime, &next_update, &next_update); TIMEVAL_TO_TIMESPEC(&next_update, &next_update_ts); } /* clean up */ pthread_cleanup_pop(1); return NULL; } /* --- thread external functions ------------------------------------------- */ void bgp_keepalives_on(struct peer *peer) { if (CHECK_FLAG(peer->thread_flags, PEER_THREAD_KEEPALIVES_ON)) return; struct frr_pthread *fpt = bgp_pth_ka; assert(fpt->running); /* placeholder bucket data to use for fast key lookups */ static struct pkat holder = {0}; /* * We need to ensure that bgp_keepalives_init was called first */ assert(peerhash_mtx); frr_with_mutex(peerhash_mtx) { holder.peer = peer; if (!hash_lookup(peerhash, &holder)) { struct pkat *pkat = pkat_new(peer); hash_get(peerhash, pkat, hash_alloc_intern); peer_lock(peer); } SET_FLAG(peer->thread_flags, PEER_THREAD_KEEPALIVES_ON); } bgp_keepalives_wake(); } void bgp_keepalives_off(struct peer *peer) { if (!CHECK_FLAG(peer->thread_flags, PEER_THREAD_KEEPALIVES_ON)) return; struct frr_pthread *fpt = bgp_pth_ka; assert(fpt->running); /* placeholder bucket data to use for fast key lookups */ static struct pkat holder = {0}; /* * We need to ensure that bgp_keepalives_init was called first */ assert(peerhash_mtx); frr_with_mutex(peerhash_mtx) { holder.peer = peer; struct pkat *res = hash_release(peerhash, &holder); if (res) { pkat_del(res); peer_unlock(peer); } UNSET_FLAG(peer->thread_flags, PEER_THREAD_KEEPALIVES_ON); } } void bgp_keepalives_wake(void) { frr_with_mutex(peerhash_mtx) { pthread_cond_signal(peerhash_cond); } } int bgp_keepalives_stop(struct frr_pthread *fpt, void **result) { assert(fpt->running); atomic_store_explicit(&fpt->running, false, memory_order_relaxed); bgp_keepalives_wake(); pthread_join(fpt->thread, result); return 0; } frr-7.2.1/bgpd/bgp_keepalives.h0000644000000000000000000000564013610377563013266 00000000000000/* BGP Keepalives. * Implements a producer thread to generate BGP keepalives for peers. * Copyright (C) 2017 Cumulus Networks, Inc. * Quentin Young * * This file is part of FRRouting. * * FRRouting is free software; you can redistribute it and/or modify it under * the terms of the GNU General Public License as published by the Free * Software Foundation; either version 2, or (at your option) any later * version. * * FRRouting 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; see the file COPYING; if not, write to the Free Software * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA */ #ifndef _FRR_BGP_KEEPALIVES_H #define _FRR_BGP_KEEPALIVES_H #include "frr_pthread.h" #include "bgpd.h" /** * Turns on keepalives for a peer. * * This function adds the peer to an internal list of peers to generate * keepalives for. * * At set intervals, a BGP KEEPALIVE packet is generated and placed on * peer->obuf. This operation is thread-safe with respect to peer->obuf. * * peer->v_keepalive determines the interval. Changing this value before * unregistering this peer with bgp_keepalives_off() results in undefined * behavior. * * If the peer is already registered for keepalives via this function, nothing * happens. */ extern void bgp_keepalives_on(struct peer *); /** * Turns off keepalives for a peer. * * Removes the peer from the internal list of peers to generate keepalives for. * * If the peer is already unregistered for keepalives, nothing happens. */ extern void bgp_keepalives_off(struct peer *); /** * Pre-run initialization function for keepalives pthread. * * Initializes synchronization primitives. This should be called before * anything else to avoid race conditions. */ extern void bgp_keepalives_init(void); /** * Entry function for keepalives pthread. * * This function loops over an internal list of peers, generating keepalives at * regular intervals as determined by each peer's keepalive timer. * * See bgp_keepalives_on() for additional details. * * @param arg pthread arg, not used */ extern void *bgp_keepalives_start(void *arg); /** * Poking function for keepalives pthread. * * Under normal circumstances the pthread will automatically wake itself * whenever it is necessary to do work. This function may be used to force the * thread to wake up and see if there is any work to do, or if it is time to * die. * * It is not necessary to call this after bgp_keepalives_on(). */ extern void bgp_keepalives_wake(void); /** * Stops the thread and blocks until it terminates. */ int bgp_keepalives_stop(struct frr_pthread *fpt, void **result); #endif /* _FRR_BGP_KEEPALIVES_H */ frr-7.2.1/bgpd/bgp_label.c0000644000000000000000000003104413610377563012205 00000000000000/* BGP carrying label information * Copyright (C) 2013 Cumulus Networks, Inc. * * This file is part of GNU Zebra. * * GNU Zebra is free software; you can redistribute it and/or modify it * under the terms of the GNU General Public License as published by the * Free Software Foundation; either version 2, or (at your option) any * later version. * * GNU Zebra 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; see the file COPYING; if not, write to the Free Software * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA */ #include #include "command.h" #include "thread.h" #include "prefix.h" #include "zclient.h" #include "stream.h" #include "network.h" #include "log.h" #include "memory.h" #include "nexthop.h" #include "mpls.h" #include "bgpd/bgpd.h" #include "bgpd/bgp_table.h" #include "bgpd/bgp_route.h" #include "bgpd/bgp_attr.h" #include "bgpd/bgp_label.h" #include "bgpd/bgp_packet.h" #include "bgpd/bgp_debug.h" #include "bgpd/bgp_errors.h" extern struct zclient *zclient; int bgp_parse_fec_update(void) { struct stream *s; struct bgp_node *rn; struct bgp *bgp; struct bgp_table *table; struct prefix p; uint32_t label; afi_t afi; safi_t safi; s = zclient->ibuf; memset(&p, 0, sizeof(struct prefix)); p.family = stream_getw(s); p.prefixlen = stream_getc(s); stream_get(p.u.val, s, PSIZE(p.prefixlen)); label = stream_getl(s); /* hack for the bgp instance & SAFI = have to send/receive it */ afi = family2afi(p.family); safi = SAFI_UNICAST; bgp = bgp_get_default(); if (!bgp) { zlog_debug("no default bgp instance"); return -1; } table = bgp->rib[afi][safi]; if (!table) { zlog_debug("no %u unicast table", p.family); return -1; } rn = bgp_node_lookup(table, &p); if (!rn) { zlog_debug("no node for the prefix"); return -1; } /* treat it as implicit withdraw - the label is invalid */ if (label == MPLS_INVALID_LABEL) bgp_unset_valid_label(&rn->local_label); else { label_ntop(label, 1, &rn->local_label); bgp_set_valid_label(&rn->local_label); } SET_FLAG(rn->flags, BGP_NODE_LABEL_CHANGED); bgp_unlock_node(rn); bgp_process(bgp, rn, afi, safi); return 1; } mpls_label_t bgp_adv_label(struct bgp_node *rn, struct bgp_path_info *pi, struct peer *to, afi_t afi, safi_t safi) { struct peer *from; mpls_label_t remote_label; int reflect; if (!rn || !pi || !to) return MPLS_INVALID_LABEL; remote_label = pi->extra ? pi->extra->label[0] : MPLS_INVALID_LABEL; from = pi->peer; reflect = ((from->sort == BGP_PEER_IBGP) && (to->sort == BGP_PEER_IBGP)); if (reflect && !CHECK_FLAG(to->af_flags[afi][safi], PEER_FLAG_FORCE_NEXTHOP_SELF)) return remote_label; if (CHECK_FLAG(to->af_flags[afi][safi], PEER_FLAG_NEXTHOP_UNCHANGED)) return remote_label; return rn->local_label; } /** * This is passed as the callback function to bgp_labelpool.c:bgp_lp_get() * by bgp_reg_dereg_for_label() when a label needs to be obtained from * label pool. * Note that it will reject the allocated label if a label index is found, * because the label index supposes predictable labels */ int bgp_reg_for_label_callback(mpls_label_t new_label, void *labelid, bool allocated) { struct bgp_path_info *pi; struct bgp_node *rn; char addr[PREFIX_STRLEN]; pi = labelid; /* Is this path still valid? */ if (!bgp_path_info_unlock(pi)) { if (BGP_DEBUG(labelpool, LABELPOOL)) zlog_debug( "%s: bgp_path_info is no longer valid, ignoring", __func__); return -1; } rn = pi->net; prefix2str(&rn->p, addr, PREFIX_STRLEN); if (BGP_DEBUG(labelpool, LABELPOOL)) zlog_debug("%s: FEC %s label=%u, allocated=%d", __func__, addr, new_label, allocated); if (!allocated) { /* * previously-allocated label is now invalid */ if (pi->attr->label_index == MPLS_INVALID_LABEL_INDEX && pi->attr->label != MPLS_LABEL_NONE && CHECK_FLAG(rn->flags, BGP_NODE_REGISTERED_FOR_LABEL)) { bgp_unregister_for_label(rn); label_ntop(MPLS_LABEL_IMPLICIT_NULL, 1, &rn->local_label); bgp_set_valid_label(&rn->local_label); } return 0; } /* * label index is assigned, this should be handled by SR-related code, * so retry FEC registration and then reject label allocation for * it to be released to label pool */ if (pi->attr->label_index != MPLS_INVALID_LABEL_INDEX) { flog_err( EC_BGP_LABEL, "%s: FEC %s Rejecting allocated label %u as Label Index is %u", __func__, addr, new_label, pi->attr->label_index); bgp_register_for_label(pi->net, pi); return -1; } if (pi->attr->label != MPLS_INVALID_LABEL) { if (new_label == pi->attr->label) { /* already have same label, accept but do nothing */ return 0; } /* Shouldn't happen: different label allocation */ flog_err(EC_BGP_LABEL, "%s: %s had label %u but got new assignment %u", __func__, addr, pi->attr->label, new_label); /* continue means use new one */ } label_ntop(new_label, 1, &rn->local_label); bgp_set_valid_label(&rn->local_label); /* * Get back to registering the FEC */ bgp_register_for_label(pi->net, pi); return 0; } void bgp_reg_dereg_for_label(struct bgp_node *rn, struct bgp_path_info *pi, bool reg) { bool with_label_index = false; struct stream *s; struct prefix *p; mpls_label_t *local_label; int command; uint16_t flags = 0; size_t flags_pos = 0; char addr[PREFIX_STRLEN]; p = &(rn->p); local_label = &(rn->local_label); /* this prevents the loop when we're called by * bgp_reg_for_label_callback() */ bool have_label_to_reg = bgp_is_valid_label(local_label) && label_pton(local_label) != MPLS_LABEL_IMPLICIT_NULL; if (reg) { assert(pi); /* * Determine if we will let zebra should derive label from * label index instead of bgpd requesting from label pool */ if (CHECK_FLAG(pi->attr->flag, ATTR_FLAG_BIT(BGP_ATTR_PREFIX_SID)) && pi->attr->label_index != BGP_INVALID_LABEL_INDEX) { with_label_index = true; } else { /* * If no label index was provided -- assume any label * from label pool will do. This means that label index * always takes precedence over auto-assigned labels. */ if (!have_label_to_reg) { if (BGP_DEBUG(labelpool, LABELPOOL)) { prefix2str(p, addr, PREFIX_STRLEN); zlog_debug("%s: Requesting label from LP for %s", __func__, addr); } /* bgp_reg_for_label_callback() will call back * __func__ when it gets a label from the pool. * This means we'll never register FECs without * valid labels. */ bgp_lp_get(LP_TYPE_BGP_LU, pi, bgp_reg_for_label_callback); return; } } } /* Check socket. */ if (!zclient || zclient->sock < 0) return; /* If the route node has a local_label assigned or the * path node has an MPLS SR label index allowing zebra to * derive the label, proceed with registration. */ s = zclient->obuf; stream_reset(s); command = (reg) ? ZEBRA_FEC_REGISTER : ZEBRA_FEC_UNREGISTER; zclient_create_header(s, command, VRF_DEFAULT); flags_pos = stream_get_endp(s); /* save position of 'flags' */ stream_putw(s, flags); /* initial flags */ stream_putw(s, PREFIX_FAMILY(p)); stream_put_prefix(s, p); if (reg) { if (have_label_to_reg) { flags |= ZEBRA_FEC_REGISTER_LABEL; stream_putl(s, label_pton(local_label)); } else if (with_label_index) { flags |= ZEBRA_FEC_REGISTER_LABEL_INDEX; stream_putl(s, pi->attr->label_index); } SET_FLAG(rn->flags, BGP_NODE_REGISTERED_FOR_LABEL); } else UNSET_FLAG(rn->flags, BGP_NODE_REGISTERED_FOR_LABEL); /* Set length and flags */ stream_putw_at(s, 0, stream_get_endp(s)); /* * We only need to write new flags if this is a register */ if (reg) stream_putw_at(s, flags_pos, flags); zclient_send_message(zclient); } static int bgp_nlri_get_labels(struct peer *peer, uint8_t *pnt, uint8_t plen, mpls_label_t *label) { uint8_t *data = pnt; uint8_t *lim = pnt + plen; uint8_t llen = 0; uint8_t label_depth = 0; for (; data < lim; data += BGP_LABEL_BYTES) { memcpy(label, data, BGP_LABEL_BYTES); llen += BGP_LABEL_BYTES; bgp_set_valid_label(label); label_depth += 1; if (bgp_is_withdraw_label(label) || label_bos(label)) break; } /* If we RX multiple labels we will end up keeping only the last * one. We do not yet support a label stack greater than 1. */ if (label_depth > 1) zlog_info("%s rcvd UPDATE with label stack %d deep", peer->host, label_depth); if (!(bgp_is_withdraw_label(label) || label_bos(label))) flog_warn( EC_BGP_INVALID_LABEL_STACK, "%s rcvd UPDATE with invalid label stack - no bottom of stack", peer->host); return llen; } int bgp_nlri_parse_label(struct peer *peer, struct attr *attr, struct bgp_nlri *packet) { uint8_t *pnt; uint8_t *lim; struct prefix p; int psize = 0; int prefixlen; afi_t afi; safi_t safi; int addpath_encoded; uint32_t addpath_id; mpls_label_t label = MPLS_INVALID_LABEL; uint8_t llen; pnt = packet->nlri; lim = pnt + packet->length; afi = packet->afi; safi = packet->safi; addpath_id = 0; addpath_encoded = (CHECK_FLAG(peer->af_cap[afi][safi], PEER_CAP_ADDPATH_AF_RX_ADV) && CHECK_FLAG(peer->af_cap[afi][safi], PEER_CAP_ADDPATH_AF_TX_RCV)); for (; pnt < lim; pnt += psize) { /* Clear prefix structure. */ memset(&p, 0, sizeof(struct prefix)); if (addpath_encoded) { /* When packet overflow occurs return immediately. */ if (pnt + BGP_ADDPATH_ID_LEN > lim) return BGP_NLRI_PARSE_ERROR_PACKET_OVERFLOW; memcpy(&addpath_id, pnt, BGP_ADDPATH_ID_LEN); addpath_id = ntohl(addpath_id); pnt += BGP_ADDPATH_ID_LEN; } /* Fetch prefix length. */ prefixlen = *pnt++; p.family = afi2family(packet->afi); psize = PSIZE(prefixlen); /* sanity check against packet data */ if ((pnt + psize) > lim) { flog_err( EC_BGP_UPDATE_RCV, "%s [Error] Update packet error / L-U (prefix length %d exceeds packet size %u)", peer->host, prefixlen, (uint)(lim - pnt)); return BGP_NLRI_PARSE_ERROR_PACKET_OVERFLOW; } /* Fill in the labels */ llen = bgp_nlri_get_labels(peer, pnt, psize, &label); p.prefixlen = prefixlen - BSIZE(llen); /* There needs to be at least one label */ if (prefixlen < 24) { flog_err(EC_BGP_UPDATE_RCV, "%s [Error] Update packet error" " (wrong label length %d)", peer->host, prefixlen); bgp_notify_send(peer, BGP_NOTIFY_UPDATE_ERR, BGP_NOTIFY_UPDATE_INVAL_NETWORK); return BGP_NLRI_PARSE_ERROR_LABEL_LENGTH; } if ((afi == AFI_IP && p.prefixlen > 32) || (afi == AFI_IP6 && p.prefixlen > 128)) return BGP_NLRI_PARSE_ERROR_PREFIX_LENGTH; /* Fetch prefix from NLRI packet */ memcpy(&p.u.prefix, pnt + llen, psize - llen); /* Check address. */ if (afi == AFI_IP && safi == SAFI_LABELED_UNICAST) { if (IN_CLASSD(ntohl(p.u.prefix4.s_addr))) { /* From RFC4271 Section 6.3: * * If a prefix in the NLRI field is semantically * incorrect * (e.g., an unexpected multicast IP address), * an error SHOULD * be logged locally, and the prefix SHOULD be * ignored. */ flog_err( EC_BGP_UPDATE_RCV, "%s: IPv4 labeled-unicast NLRI is multicast address %s, ignoring", peer->host, inet_ntoa(p.u.prefix4)); continue; } } /* Check address. */ if (afi == AFI_IP6 && safi == SAFI_LABELED_UNICAST) { if (IN6_IS_ADDR_LINKLOCAL(&p.u.prefix6)) { char buf[BUFSIZ]; flog_err( EC_BGP_UPDATE_RCV, "%s: IPv6 labeled-unicast NLRI is link-local address %s, ignoring", peer->host, inet_ntop(AF_INET6, &p.u.prefix6, buf, BUFSIZ)); continue; } if (IN6_IS_ADDR_MULTICAST(&p.u.prefix6)) { char buf[BUFSIZ]; flog_err( EC_BGP_UPDATE_RCV, "%s: IPv6 unicast NLRI is multicast address %s, ignoring", peer->host, inet_ntop(AF_INET6, &p.u.prefix6, buf, BUFSIZ)); continue; } } if (attr) { bgp_update(peer, &p, addpath_id, attr, packet->afi, SAFI_UNICAST, ZEBRA_ROUTE_BGP, BGP_ROUTE_NORMAL, NULL, &label, 1, 0, NULL); } else { bgp_withdraw(peer, &p, addpath_id, attr, packet->afi, SAFI_UNICAST, ZEBRA_ROUTE_BGP, BGP_ROUTE_NORMAL, NULL, &label, 1, NULL); } } /* Packet length consistency check. */ if (pnt != lim) { flog_err( EC_BGP_UPDATE_RCV, "%s [Error] Update packet error / L-U (%zu data remaining after parsing)", peer->host, lim - pnt); return BGP_NLRI_PARSE_ERROR_PACKET_LENGTH; } return BGP_NLRI_PARSE_OK; } frr-7.2.1/bgpd/bgp_label.h0000644000000000000000000000660113610377563012213 00000000000000/* BGP carrying Label information * Copyright (C) 2013 Cumulus Networks, Inc. * * This file is part of GNU Zebra. * * GNU Zebra is free software; you can redistribute it and/or modify it * under the terms of the GNU General Public License as published by the * Free Software Foundation; either version 2, or (at your option) any * later version. * * GNU Zebra 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; see the file COPYING; if not, write to the Free Software * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA */ #ifndef _BGP_LABEL_H #define _BGP_LABEL_H #define BGP_LABEL_BYTES 3 #define BGP_LABEL_BITS 24 #define BGP_WITHDRAW_LABEL 0x800000 #define BGP_PREVENT_VRF_2_VRF_LEAK 0xFFFFFFFE struct bgp_node; struct bgp_path_info; struct peer; extern int bgp_reg_for_label_callback(mpls_label_t new_label, void *labelid, bool allocated); extern void bgp_reg_dereg_for_label(struct bgp_node *rn, struct bgp_path_info *pi, bool reg); extern int bgp_parse_fec_update(void); extern mpls_label_t bgp_adv_label(struct bgp_node *rn, struct bgp_path_info *pi, struct peer *to, afi_t afi, safi_t safi); extern int bgp_nlri_parse_label(struct peer *peer, struct attr *attr, struct bgp_nlri *packet); static inline int bgp_labeled_safi(safi_t safi) { /* NOTE: This API really says a label (tag) MAY be present. Not all EVPN * routes will have a label. */ if ((safi == SAFI_LABELED_UNICAST) || (safi == SAFI_MPLS_VPN) || (safi == SAFI_EVPN)) return 1; return 0; } static inline int bgp_is_withdraw_label(mpls_label_t *label) { uint8_t *pkt = (uint8_t *)label; /* The check on pkt[2] for 0x00 or 0x02 is in case bgp_set_valid_label() * was called on the withdraw label */ if ((pkt[0] == 0x80) && (pkt[1] == 0x00) && ((pkt[2] == 0x00) || (pkt[2] == 0x02))) return 1; return 0; } static inline int bgp_is_valid_label(mpls_label_t *label) { uint8_t *t = (uint8_t *)label; if (!t) return 0; return (t[2] & 0x02); } static inline void bgp_set_valid_label(mpls_label_t *label) { uint8_t *t = (uint8_t *)label; if (t) t[2] |= 0x02; } static inline void bgp_unset_valid_label(mpls_label_t *label) { uint8_t *t = (uint8_t *)label; if (t) t[2] &= ~0x02; } static inline void bgp_register_for_label(struct bgp_node *rn, struct bgp_path_info *pi) { bgp_reg_dereg_for_label(rn, pi, true); } static inline void bgp_unregister_for_label(struct bgp_node *rn) { bgp_reg_dereg_for_label(rn, NULL, false); } /* Label stream to value */ static inline uint32_t label_pton(mpls_label_t *label) { uint8_t *t = (uint8_t *)label; return ((((unsigned int)t[0]) << 12) | (((unsigned int)t[1]) << 4) | ((unsigned int)((t[2] & 0xF0) >> 4))); } /* Encode label values */ static inline void label_ntop(uint32_t l, int bos, mpls_label_t *label) { uint8_t *t = (uint8_t *)label; t[0] = ((l & 0x000FF000) >> 12); t[1] = ((l & 0x00000FF0) >> 4); t[2] = ((l & 0x0000000F) << 4); if (bos) t[2] |= 0x01; } /* Return BOS value of label stream */ static inline uint8_t label_bos(mpls_label_t *label) { uint8_t *t = (uint8_t *)label; return (t[2] & 0x01); }; #endif /* _BGP_LABEL_H */ frr-7.2.1/bgpd/bgp_labelpool.c0000644000000000000000000003547413610377563013112 00000000000000/* * BGP Label Pool - Manage label chunk allocations from zebra asynchronously * * Copyright (C) 2018 LabN Consulting, L.L.C. * * This program is free software; you can redistribute it and/or modify it * under the terms of the GNU General Public License as published by the Free * Software Foundation; either version 2 of the License, or (at your option) * any later version. * * 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; see the file COPYING; if not, write to the Free Software * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA */ #include #include "log.h" #include "memory.h" #include "stream.h" #include "mpls.h" #include "vty.h" #include "linklist.h" #include "skiplist.h" #include "workqueue.h" #include "zclient.h" #include "mpls.h" #include "bgpd/bgpd.h" #include "bgpd/bgp_labelpool.h" #include "bgpd/bgp_debug.h" #include "bgpd/bgp_errors.h" #include "bgpd/bgp_route.h" /* * Definitions and external declarations. */ extern struct zclient *zclient; /* * Remember where pool data are kept */ static struct labelpool *lp; /* request this many labels at a time from zebra */ #define LP_CHUNK_SIZE 50 DEFINE_MTYPE_STATIC(BGPD, BGP_LABEL_CHUNK, "BGP Label Chunk") DEFINE_MTYPE_STATIC(BGPD, BGP_LABEL_FIFO, "BGP Label FIFO item") DEFINE_MTYPE_STATIC(BGPD, BGP_LABEL_CB, "BGP Dynamic Label Assignment") DEFINE_MTYPE_STATIC(BGPD, BGP_LABEL_CBQ, "BGP Dynamic Label Callback") struct lp_chunk { uint32_t first; uint32_t last; }; /* * label control block */ struct lp_lcb { mpls_label_t label; /* MPLS_LABEL_NONE = not allocated */ int type; void *labelid; /* unique ID */ /* * callback for label allocation and loss * * allocated: false = lost */ int (*cbfunc)(mpls_label_t label, void *lblid, bool alloc); }; struct lp_fifo { struct lp_fifo_item fifo; struct lp_lcb lcb; }; DECLARE_LIST(lp_fifo, struct lp_fifo, fifo) struct lp_cbq_item { int (*cbfunc)(mpls_label_t label, void *lblid, bool alloc); int type; mpls_label_t label; void *labelid; bool allocated; /* false = lost */ }; static wq_item_status lp_cbq_docallback(struct work_queue *wq, void *data) { struct lp_cbq_item *lcbq = data; int rc; int debug = BGP_DEBUG(labelpool, LABELPOOL); if (debug) zlog_debug("%s: calling callback with labelid=%p label=%u allocated=%d", __func__, lcbq->labelid, lcbq->label, lcbq->allocated); if (lcbq->label == MPLS_LABEL_NONE) { /* shouldn't happen */ flog_err(EC_BGP_LABEL, "%s: error: label==MPLS_LABEL_NONE", __func__); return WQ_SUCCESS; } rc = (*(lcbq->cbfunc))(lcbq->label, lcbq->labelid, lcbq->allocated); if (lcbq->allocated && rc) { /* * Callback rejected allocation. This situation could arise * if there was a label request followed by the requestor * deciding it didn't need the assignment (e.g., config * change) while the reply to the original request (with * label) was in the work queue. */ if (debug) zlog_debug("%s: callback rejected allocation, releasing labelid=%p label=%u", __func__, lcbq->labelid, lcbq->label); uintptr_t lbl = lcbq->label; void *labelid; struct lp_lcb *lcb; /* * If the rejected label was marked inuse by this labelid, * release the label back to the pool. * * Further, if the rejected label was still assigned to * this labelid in the LCB, delete the LCB. */ if (!skiplist_search(lp->inuse, (void *)lbl, &labelid)) { if (labelid == lcbq->labelid) { if (!skiplist_search(lp->ledger, labelid, (void **)&lcb)) { if (lcbq->label == lcb->label) skiplist_delete(lp->ledger, labelid, NULL); } skiplist_delete(lp->inuse, (void *)lbl, NULL); } } } return WQ_SUCCESS; } static void lp_cbq_item_free(struct work_queue *wq, void *data) { XFREE(MTYPE_BGP_LABEL_CBQ, data); } static void lp_lcb_free(void *goner) { XFREE(MTYPE_BGP_LABEL_CB, goner); } static void lp_chunk_free(void *goner) { XFREE(MTYPE_BGP_LABEL_CHUNK, goner); } void bgp_lp_init(struct thread_master *master, struct labelpool *pool) { if (BGP_DEBUG(labelpool, LABELPOOL)) zlog_debug("%s: entry", __func__); lp = pool; /* Set module pointer to pool data */ lp->ledger = skiplist_new(0, NULL, lp_lcb_free); lp->inuse = skiplist_new(0, NULL, NULL); lp->chunks = list_new(); lp->chunks->del = lp_chunk_free; lp_fifo_init(&lp->requests); lp->callback_q = work_queue_new(master, "label callbacks"); lp->callback_q->spec.workfunc = lp_cbq_docallback; lp->callback_q->spec.del_item_data = lp_cbq_item_free; lp->callback_q->spec.max_retries = 0; } /* check if a label callback was for a BGP LU path, and if so, unlock it */ static void check_bgp_lu_cb_unlock(struct lp_lcb *lcb) { if (lcb->type == LP_TYPE_BGP_LU) bgp_path_info_unlock(lcb->labelid); } /* check if a label callback was for a BGP LU path, and if so, lock it */ static void check_bgp_lu_cb_lock(struct lp_lcb *lcb) { if (lcb->type == LP_TYPE_BGP_LU) bgp_path_info_lock(lcb->labelid); } void bgp_lp_finish(void) { struct lp_fifo *lf; struct work_queue_item *item, *titem; if (!lp) return; skiplist_free(lp->ledger); lp->ledger = NULL; skiplist_free(lp->inuse); lp->inuse = NULL; list_delete(&lp->chunks); while ((lf = lp_fifo_pop(&lp->requests))) { check_bgp_lu_cb_unlock(&lf->lcb); XFREE(MTYPE_BGP_LABEL_FIFO, lf); } lp_fifo_fini(&lp->requests); /* we must unlock path infos for LU callbacks; but we cannot do that * in the deletion callback of the workqueue, as that is also called * to remove an element from the queue after it has been run, resulting * in a double unlock. Hence we need to iterate over our queues and * lists and manually perform the unlocking (ugh) */ STAILQ_FOREACH_SAFE (item, &lp->callback_q->items, wq, titem) check_bgp_lu_cb_unlock(item->data); work_queue_free_and_null(&lp->callback_q); lp = NULL; } static mpls_label_t get_label_from_pool(void *labelid) { struct listnode *node; struct lp_chunk *chunk; int debug = BGP_DEBUG(labelpool, LABELPOOL); /* * Find a free label * Linear search is not efficient but should be executed infrequently. */ for (ALL_LIST_ELEMENTS_RO(lp->chunks, node, chunk)) { uintptr_t lbl; if (debug) zlog_debug("%s: chunk first=%u last=%u", __func__, chunk->first, chunk->last); for (lbl = chunk->first; lbl <= chunk->last; ++lbl) { /* labelid is key to all-request "ledger" list */ if (!skiplist_insert(lp->inuse, (void *)lbl, labelid)) { /* * Success */ return lbl; } } } return MPLS_LABEL_NONE; } /* * Success indicated by value of "label" field in returned LCB */ static struct lp_lcb *lcb_alloc( int type, void *labelid, int (*cbfunc)(mpls_label_t label, void *labelid, bool allocated)) { /* * Set up label control block */ struct lp_lcb *new = XCALLOC(MTYPE_BGP_LABEL_CB, sizeof(struct lp_lcb)); new->label = get_label_from_pool(labelid); new->type = type; new->labelid = labelid; new->cbfunc = cbfunc; return new; } /* * Callers who need labels must supply a type, labelid, and callback. * The type is a value defined in bgp_labelpool.h (add types as needed). * The callback is for asynchronous notification of label allocation. * The labelid is passed as an argument to the callback. It should be unique * to the requested label instance. * * If zebra is not connected, callbacks with labels will be delayed * until connection is established. If zebra connection is lost after * labels have been assigned, existing assignments via this labelpool * module will continue until reconnection. * * When connection to zebra is reestablished, previous label assignments * will be invalidated (via callbacks having the "allocated" parameter unset) * and new labels will be automatically reassigned by this labelpool module * (that is, a requestor does not need to call lp_get() again if it is * notified via callback that its label has been lost: it will eventually * get another callback with a new label assignment). * * Prior requests for a given labelid are detected so that requests and * assignments are not duplicated. */ void bgp_lp_get( int type, void *labelid, int (*cbfunc)(mpls_label_t label, void *labelid, bool allocated)) { struct lp_lcb *lcb; int requested = 0; int debug = BGP_DEBUG(labelpool, LABELPOOL); if (debug) zlog_debug("%s: labelid=%p", __func__, labelid); /* * Have we seen this request before? */ if (!skiplist_search(lp->ledger, labelid, (void **)&lcb)) { requested = 1; } else { lcb = lcb_alloc(type, labelid, cbfunc); if (debug) zlog_debug("%s: inserting lcb=%p label=%u", __func__, lcb, lcb->label); int rc = skiplist_insert(lp->ledger, labelid, lcb); if (rc) { /* shouldn't happen */ flog_err(EC_BGP_LABEL, "%s: can't insert new LCB into ledger list", __func__); XFREE(MTYPE_BGP_LABEL_CB, lcb); return; } } if (lcb->label != MPLS_LABEL_NONE) { /* * Fast path: we filled the request from local pool (or * this is a duplicate request that we filled already). * Enqueue response work item with new label. */ struct lp_cbq_item *q; q = XCALLOC(MTYPE_BGP_LABEL_CBQ, sizeof(struct lp_cbq_item)); q->cbfunc = lcb->cbfunc; q->type = lcb->type; q->label = lcb->label; q->labelid = lcb->labelid; q->allocated = true; /* if this is a LU request, lock path info before queueing */ check_bgp_lu_cb_lock(lcb); work_queue_add(lp->callback_q, q); return; } if (requested) return; if (debug) zlog_debug("%s: slow path. lcb=%p label=%u", __func__, lcb, lcb->label); /* * Slow path: we are out of labels in the local pool, * so remember the request and also get another chunk from * the label manager. * * We track number of outstanding label requests: don't * need to get a chunk for each one. */ struct lp_fifo *lf = XCALLOC(MTYPE_BGP_LABEL_FIFO, sizeof(struct lp_fifo)); lf->lcb = *lcb; /* if this is a LU request, lock path info before queueing */ check_bgp_lu_cb_lock(lcb); lp_fifo_add_tail(&lp->requests, lf); if (lp_fifo_count(&lp->requests) > lp->pending_count) { if (!zclient || zclient->sock < 0) return; if (!zclient_send_get_label_chunk(zclient, 0, LP_CHUNK_SIZE, MPLS_LABEL_BASE_ANY)) lp->pending_count += LP_CHUNK_SIZE; } } void bgp_lp_release( int type, void *labelid, mpls_label_t label) { struct lp_lcb *lcb; if (!skiplist_search(lp->ledger, labelid, (void **)&lcb)) { if (label == lcb->label && type == lcb->type) { uintptr_t lbl = label; /* no longer in use */ skiplist_delete(lp->inuse, (void *)lbl, NULL); /* no longer requested */ skiplist_delete(lp->ledger, labelid, NULL); } } } /* * zebra response giving us a chunk of labels */ void bgp_lp_event_chunk(uint8_t keep, uint32_t first, uint32_t last) { struct lp_chunk *chunk; int debug = BGP_DEBUG(labelpool, LABELPOOL); struct lp_fifo *lf; if (last < first) { flog_err(EC_BGP_LABEL, "%s: zebra label chunk invalid: first=%u, last=%u", __func__, first, last); return; } chunk = XCALLOC(MTYPE_BGP_LABEL_CHUNK, sizeof(struct lp_chunk)); chunk->first = first; chunk->last = last; listnode_add(lp->chunks, chunk); lp->pending_count -= (last - first + 1); if (debug) { zlog_debug("%s: %zu pending requests", __func__, lp_fifo_count(&lp->requests)); } while ((lf = lp_fifo_first(&lp->requests))) { struct lp_lcb *lcb; void *labelid = lf->lcb.labelid; if (skiplist_search(lp->ledger, labelid, (void **)&lcb)) { /* request no longer in effect */ if (debug) { zlog_debug("%s: labelid %p: request no longer in effect", __func__, labelid); } goto finishedrequest; } /* have LCB */ if (lcb->label != MPLS_LABEL_NONE) { /* request already has a label */ if (debug) { zlog_debug("%s: labelid %p: request already has a label: %u=0x%x, lcb=%p", __func__, labelid, lcb->label, lcb->label, lcb); } /* if this was a BGP_LU request, unlock path info node */ check_bgp_lu_cb_unlock(lcb); goto finishedrequest; } lcb->label = get_label_from_pool(lcb->labelid); if (lcb->label == MPLS_LABEL_NONE) { /* * Out of labels in local pool, await next chunk */ if (debug) { zlog_debug("%s: out of labels, await more", __func__); } break; } /* * we filled the request from local pool. * Enqueue response work item with new label. */ struct lp_cbq_item *q = XCALLOC(MTYPE_BGP_LABEL_CBQ, sizeof(struct lp_cbq_item)); q->cbfunc = lcb->cbfunc; q->type = lcb->type; q->label = lcb->label; q->labelid = lcb->labelid; q->allocated = true; if (debug) zlog_debug("%s: assigning label %u to labelid %p", __func__, q->label, q->labelid); work_queue_add(lp->callback_q, q); finishedrequest: lp_fifo_del(&lp->requests, lf); XFREE(MTYPE_BGP_LABEL_FIFO, lf); } } /* * continue using allocated labels until zebra returns */ void bgp_lp_event_zebra_down(void) { /* rats. */ } /* * Inform owners of previously-allocated labels that their labels * are not valid. Request chunk from zebra large enough to satisfy * previously-allocated labels plus any outstanding requests. */ void bgp_lp_event_zebra_up(void) { int labels_needed; int chunks_needed; void *labelid; struct lp_lcb *lcb; int lm_init_ok; /* * Get label chunk allocation request dispatched to zebra */ labels_needed = lp_fifo_count(&lp->requests) + skiplist_count(lp->inuse); /* round up */ chunks_needed = (labels_needed / LP_CHUNK_SIZE) + 1; labels_needed = chunks_needed * LP_CHUNK_SIZE; lm_init_ok = lm_label_manager_connect(zclient, 1) == 0; if (!lm_init_ok) { zlog_err("%s: label manager connection error", __func__); return; } zclient_send_get_label_chunk(zclient, 0, labels_needed, MPLS_LABEL_BASE_ANY); lp->pending_count = labels_needed; /* * Invalidate current list of chunks */ list_delete_all_node(lp->chunks); /* * Invalidate any existing labels and requeue them as requests */ while (!skiplist_first(lp->inuse, NULL, &labelid)) { /* * Get LCB */ if (!skiplist_search(lp->ledger, labelid, (void **)&lcb)) { if (lcb->label != MPLS_LABEL_NONE) { /* * invalidate */ struct lp_cbq_item *q; q = XCALLOC(MTYPE_BGP_LABEL_CBQ, sizeof(struct lp_cbq_item)); q->cbfunc = lcb->cbfunc; q->type = lcb->type; q->label = lcb->label; q->labelid = lcb->labelid; q->allocated = false; check_bgp_lu_cb_lock(lcb); work_queue_add(lp->callback_q, q); lcb->label = MPLS_LABEL_NONE; } /* * request queue */ struct lp_fifo *lf = XCALLOC(MTYPE_BGP_LABEL_FIFO, sizeof(struct lp_fifo)); lf->lcb = *lcb; check_bgp_lu_cb_lock(lcb); lp_fifo_add_tail(&lp->requests, lf); } skiplist_delete_first(lp->inuse); } } frr-7.2.1/bgpd/bgp_labelpool.h0000644000000000000000000000361613610377563013110 00000000000000/* * BGP Label Pool - Manage label chunk allocations from zebra asynchronously * * Copyright (C) 2018 LabN Consulting, L.L.C. * * This program is free software; you can redistribute it and/or modify it * under the terms of the GNU General Public License as published by the Free * Software Foundation; either version 2 of the License, or (at your option) * any later version. * * 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; see the file COPYING; if not, write to the Free Software * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA */ #ifndef _FRR_BGP_LABELPOOL_H #define _FRR_BGP_LABELPOOL_H #include #include "mpls.h" /* * Types used in bgp_lp_get for debug tracking; add more as needed */ #define LP_TYPE_VRF 0x00000001 #define LP_TYPE_BGP_LU 0x00000002 PREDECL_LIST(lp_fifo) struct labelpool { struct skiplist *ledger; /* all requests */ struct skiplist *inuse; /* individual labels */ struct list *chunks; /* granted by zebra */ struct lp_fifo_head requests; /* blocked on zebra */ struct work_queue *callback_q; uint32_t pending_count; /* requested from zebra */ }; extern void bgp_lp_init(struct thread_master *master, struct labelpool *pool); extern void bgp_lp_finish(void); extern void bgp_lp_get(int type, void *labelid, int (*cbfunc)(mpls_label_t label, void *labelid, bool allocated)); extern void bgp_lp_release(int type, void *labelid, mpls_label_t label); extern void bgp_lp_event_chunk(uint8_t keep, uint32_t first, uint32_t last); extern void bgp_lp_event_zebra_down(void); extern void bgp_lp_event_zebra_up(void); #endif /* _FRR_BGP_LABELPOOL_H */ frr-7.2.1/bgpd/bgp_lcommunity.c0000644000000000000000000003612713610377563013335 00000000000000/* BGP Large Communities Attribute * * Copyright (C) 2016 Keyur Patel * * This file is part of FreeRangeRouting (FRR). * * FRR is free software; you can redistribute it and/or modify it under the * terms of the GNU General Public License as published by the Free Software * Foundation; either version 2, or (at your option) any later version. * * FRR 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; see the file COPYING; if not, write to the Free Software * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA */ #include #include "hash.h" #include "memory.h" #include "prefix.h" #include "command.h" #include "filter.h" #include "jhash.h" #include "stream.h" #include "bgpd/bgpd.h" #include "bgpd/bgp_lcommunity.h" #include "bgpd/bgp_aspath.h" /* Hash of community attribute. */ static struct hash *lcomhash; /* Allocate a new lcommunities. */ static struct lcommunity *lcommunity_new(void) { return XCALLOC(MTYPE_LCOMMUNITY, sizeof(struct lcommunity)); } /* Allocate lcommunities. */ void lcommunity_free(struct lcommunity **lcom) { XFREE(MTYPE_LCOMMUNITY_VAL, (*lcom)->val); XFREE(MTYPE_LCOMMUNITY_STR, (*lcom)->str); XFREE(MTYPE_LCOMMUNITY, *lcom); } static void lcommunity_hash_free(struct lcommunity *lcom) { lcommunity_free(&lcom); } /* Add a new Large Communities value to Large Communities Attribute structure. When the value is already exists in the structure, we don't add the value. Newly added value is sorted by numerical order. When the value is added to the structure return 1 else return 0. */ static int lcommunity_add_val(struct lcommunity *lcom, struct lcommunity_val *lval) { uint8_t *p; int ret; int c; /* When this is fist value, just add it. */ if (lcom->val == NULL) { lcom->size++; lcom->val = XMALLOC(MTYPE_LCOMMUNITY_VAL, lcom_length(lcom)); memcpy(lcom->val, lval->val, LCOMMUNITY_SIZE); return 1; } /* If the value already exists in the structure return 0. */ c = 0; for (p = lcom->val; c < lcom->size; p += LCOMMUNITY_SIZE, c++) { ret = memcmp(p, lval->val, LCOMMUNITY_SIZE); if (ret == 0) return 0; if (ret > 0) break; } /* Add the value to the structure with numerical sorting. */ lcom->size++; lcom->val = XREALLOC(MTYPE_LCOMMUNITY_VAL, lcom->val, lcom_length(lcom)); memmove(lcom->val + (c + 1) * LCOMMUNITY_SIZE, lcom->val + c * LCOMMUNITY_SIZE, (lcom->size - 1 - c) * LCOMMUNITY_SIZE); memcpy(lcom->val + c * LCOMMUNITY_SIZE, lval->val, LCOMMUNITY_SIZE); return 1; } /* This function takes pointer to Large Communites strucutre then create a new Large Communities structure by uniq and sort each Large Communities value. */ struct lcommunity *lcommunity_uniq_sort(struct lcommunity *lcom) { int i; struct lcommunity *new; struct lcommunity_val *lval; if (!lcom) return NULL; new = lcommunity_new(); for (i = 0; i < lcom->size; i++) { lval = (struct lcommunity_val *)(lcom->val + (i * LCOMMUNITY_SIZE)); lcommunity_add_val(new, lval); } return new; } /* Parse Large Communites Attribute in BGP packet. */ struct lcommunity *lcommunity_parse(uint8_t *pnt, unsigned short length) { struct lcommunity tmp; struct lcommunity *new; /* Length check. */ if (length % LCOMMUNITY_SIZE) return NULL; /* Prepare tmporary structure for making a new Large Communities Attribute. */ tmp.size = length / LCOMMUNITY_SIZE; tmp.val = pnt; /* Create a new Large Communities Attribute by uniq and sort each Large Communities value */ new = lcommunity_uniq_sort(&tmp); return lcommunity_intern(new); } /* Duplicate the Large Communities Attribute structure. */ struct lcommunity *lcommunity_dup(struct lcommunity *lcom) { struct lcommunity *new; new = lcommunity_new(); new->size = lcom->size; if (new->size) { new->val = XMALLOC(MTYPE_LCOMMUNITY_VAL, lcom_length(lcom)); memcpy(new->val, lcom->val, lcom_length(lcom)); } else new->val = NULL; return new; } /* Merge two Large Communities Attribute structure. */ struct lcommunity *lcommunity_merge(struct lcommunity *lcom1, struct lcommunity *lcom2) { if (lcom1->val) lcom1->val = XREALLOC(MTYPE_LCOMMUNITY_VAL, lcom1->val, lcom_length(lcom1) + lcom_length(lcom2)); else lcom1->val = XMALLOC(MTYPE_LCOMMUNITY_VAL, lcom_length(lcom1) + lcom_length(lcom2)); memcpy(lcom1->val + lcom_length(lcom1), lcom2->val, lcom_length(lcom2)); lcom1->size += lcom2->size; return lcom1; } static void set_lcommunity_string(struct lcommunity *lcom, bool make_json) { int i; int len; char *str_buf; uint8_t *pnt; uint32_t global, local1, local2; json_object *json_lcommunity_list = NULL; json_object *json_string = NULL; /* 3 32-bit integers, 2 colons, and a space */ #define LCOMMUNITY_STRLEN (10 * 3 + 2 + 1) if (!lcom) return; if (make_json) { lcom->json = json_object_new_object(); json_lcommunity_list = json_object_new_array(); } if (lcom->size == 0) { str_buf = XCALLOC(MTYPE_LCOMMUNITY_STR, 1); if (make_json) { json_object_string_add(lcom->json, "string", ""); json_object_object_add(lcom->json, "list", json_lcommunity_list); } lcom->str = str_buf; return; } /* 1 space + lcom->size lcom strings + null terminator */ size_t str_buf_sz = (LCOMMUNITY_STRLEN * lcom->size) + 2; str_buf = XCALLOC(MTYPE_LCOMMUNITY_STR, str_buf_sz); for (i = 0; i < lcom->size; i++) { if (i > 0) strlcat(str_buf, " ", str_buf_sz); pnt = lcom->val + (i * LCOMMUNITY_SIZE); pnt = ptr_get_be32(pnt, &global); pnt = ptr_get_be32(pnt, &local1); pnt = ptr_get_be32(pnt, &local2); (void)pnt; char lcsb[LCOMMUNITY_STRLEN + 1]; snprintf(lcsb, sizeof(lcsb), "%u:%u:%u", global, local1, local2); len = strlcat(str_buf, lcsb, str_buf_sz); assert((unsigned int)len < str_buf_sz); if (make_json) { json_string = json_object_new_string(lcsb); json_object_array_add(json_lcommunity_list, json_string); } } if (make_json) { json_object_string_add(lcom->json, "string", str_buf); json_object_object_add(lcom->json, "list", json_lcommunity_list); } lcom->str = str_buf; } /* Intern Large Communities Attribute. */ struct lcommunity *lcommunity_intern(struct lcommunity *lcom) { struct lcommunity *find; assert(lcom->refcnt == 0); find = (struct lcommunity *)hash_get(lcomhash, lcom, hash_alloc_intern); if (find != lcom) lcommunity_free(&lcom); find->refcnt++; if (!find->str) set_lcommunity_string(find, false); return find; } /* Unintern Large Communities Attribute. */ void lcommunity_unintern(struct lcommunity **lcom) { struct lcommunity *ret; if ((*lcom)->refcnt) (*lcom)->refcnt--; /* Pull off from hash. */ if ((*lcom)->refcnt == 0) { /* Large community must be in the hash. */ ret = (struct lcommunity *)hash_release(lcomhash, *lcom); assert(ret != NULL); lcommunity_free(lcom); } } /* Retrun string representation of communities attribute. */ char *lcommunity_str(struct lcommunity *lcom, bool make_json) { if (!lcom) return NULL; if (make_json && !lcom->json && lcom->str) XFREE(MTYPE_LCOMMUNITY_STR, lcom->str); if (!lcom->str) set_lcommunity_string(lcom, make_json); return lcom->str; } /* Utility function to make hash key. */ unsigned int lcommunity_hash_make(const void *arg) { const struct lcommunity *lcom = arg; int size = lcom_length(lcom); return jhash(lcom->val, size, 0xab125423); } /* Compare two Large Communities Attribute structure. */ bool lcommunity_cmp(const void *arg1, const void *arg2) { const struct lcommunity *lcom1 = arg1; const struct lcommunity *lcom2 = arg2; if (lcom1 == NULL && lcom2 == NULL) return true; if (lcom1 == NULL || lcom2 == NULL) return false; return (lcom1->size == lcom2->size && memcmp(lcom1->val, lcom2->val, lcom_length(lcom1)) == 0); } /* Return communities hash. */ struct hash *lcommunity_hash(void) { return lcomhash; } /* Initialize Large Comminities related hash. */ void lcommunity_init(void) { lcomhash = hash_create(lcommunity_hash_make, lcommunity_cmp, "BGP lcommunity hash"); } void lcommunity_finish(void) { hash_clean(lcomhash, (void (*)(void *))lcommunity_hash_free); hash_free(lcomhash); lcomhash = NULL; } /* Large Communities token enum. */ enum lcommunity_token { lcommunity_token_unknown = 0, lcommunity_token_val, }; /* Get next Large Communities token from the string. */ static const char *lcommunity_gettoken(const char *str, struct lcommunity_val *lval, enum lcommunity_token *token) { const char *p = str; /* Skip white space. */ while (isspace((unsigned char)*p)) { p++; str++; } /* Check the end of the line. */ if (*p == '\0') return NULL; /* Community value. */ if (isdigit((unsigned char)*p)) { int separator = 0; int digit = 0; uint32_t globaladmin = 0; uint32_t localdata1 = 0; uint32_t localdata2 = 0; while (isdigit((unsigned char)*p) || *p == ':') { if (*p == ':') { if (separator == 2) { *token = lcommunity_token_unknown; return NULL; } else { separator++; digit = 0; if (separator == 1) { globaladmin = localdata2; } else { localdata1 = localdata2; } localdata2 = 0; } } else { digit = 1; localdata2 *= 10; localdata2 += (*p - '0'); } p++; } if (!digit) { *token = lcommunity_token_unknown; return NULL; } /* * Copy the large comm. */ lval->val[0] = (globaladmin >> 24) & 0xff; lval->val[1] = (globaladmin >> 16) & 0xff; lval->val[2] = (globaladmin >> 8) & 0xff; lval->val[3] = globaladmin & 0xff; lval->val[4] = (localdata1 >> 24) & 0xff; lval->val[5] = (localdata1 >> 16) & 0xff; lval->val[6] = (localdata1 >> 8) & 0xff; lval->val[7] = localdata1 & 0xff; lval->val[8] = (localdata2 >> 24) & 0xff; lval->val[9] = (localdata2 >> 16) & 0xff; lval->val[10] = (localdata2 >> 8) & 0xff; lval->val[11] = localdata2 & 0xff; *token = lcommunity_token_val; return p; } *token = lcommunity_token_unknown; return p; } /* Convert string to large community attribute. When type is already known, please specify both str and type. When string includes keyword for each large community value. Please specify keyword_included as non-zero value. */ struct lcommunity *lcommunity_str2com(const char *str) { struct lcommunity *lcom = NULL; enum lcommunity_token token = lcommunity_token_unknown; struct lcommunity_val lval; do { str = lcommunity_gettoken(str, &lval, &token); switch (token) { case lcommunity_token_val: if (lcom == NULL) lcom = lcommunity_new(); lcommunity_add_val(lcom, &lval); break; case lcommunity_token_unknown: default: if (lcom) lcommunity_free(&lcom); return NULL; } } while (str); return lcom; } int lcommunity_include(struct lcommunity *lcom, uint8_t *ptr) { int i; uint8_t *lcom_ptr; for (i = 0; i < lcom->size; i++) { lcom_ptr = lcom->val + (i * LCOMMUNITY_SIZE); if (memcmp(ptr, lcom_ptr, LCOMMUNITY_SIZE) == 0) return 1; } return 0; } int lcommunity_match(const struct lcommunity *lcom1, const struct lcommunity *lcom2) { int i = 0; int j = 0; if (lcom1 == NULL && lcom2 == NULL) return 1; if (lcom1 == NULL || lcom2 == NULL) return 0; if (lcom1->size < lcom2->size) return 0; /* Every community on com2 needs to be on com1 for this to match */ while (i < lcom1->size && j < lcom2->size) { if (memcmp(lcom1->val + (i * LCOMMUNITY_SIZE), lcom2->val + (j * LCOMMUNITY_SIZE), LCOMMUNITY_SIZE) == 0) j++; i++; } if (j == lcom2->size) return 1; else return 0; } /* Delete one lcommunity. */ void lcommunity_del_val(struct lcommunity *lcom, uint8_t *ptr) { int i = 0; int c = 0; if (!lcom->val) return; while (i < lcom->size) { if (memcmp(lcom->val + i * LCOMMUNITY_SIZE, ptr, LCOMMUNITY_SIZE) == 0) { c = lcom->size - i - 1; if (c > 0) memmove(lcom->val + i * LCOMMUNITY_SIZE, lcom->val + (i + 1) * LCOMMUNITY_SIZE, c * LCOMMUNITY_SIZE); lcom->size--; if (lcom->size > 0) lcom->val = XREALLOC(MTYPE_LCOMMUNITY_VAL, lcom->val, lcom_length(lcom)); else { XFREE(MTYPE_LCOMMUNITY_VAL, lcom->val); lcom->val = NULL; } return; } i++; } } static struct lcommunity *bgp_aggr_lcommunity_lookup( struct bgp_aggregate *aggregate, struct lcommunity *lcommunity) { return hash_lookup(aggregate->lcommunity_hash, lcommunity); } static void *bgp_aggr_lcommunty_hash_alloc(void *p) { struct lcommunity *ref = (struct lcommunity *)p; struct lcommunity *lcommunity = NULL; lcommunity = lcommunity_dup(ref); return lcommunity; } static void bgp_aggr_lcommunity_prepare(struct hash_backet *hb, void *arg) { struct lcommunity *lcommerge = NULL; struct lcommunity *hb_lcommunity = hb->data; struct lcommunity **aggr_lcommunity = arg; if (*aggr_lcommunity) { lcommerge = lcommunity_merge(*aggr_lcommunity, hb_lcommunity); *aggr_lcommunity = lcommunity_uniq_sort(lcommerge); lcommunity_free(&lcommerge); } else *aggr_lcommunity = lcommunity_dup(hb_lcommunity); } void bgp_aggr_lcommunity_remove(void *arg) { struct lcommunity *lcommunity = arg; lcommunity_free(&lcommunity); } void bgp_compute_aggregate_lcommunity(struct bgp_aggregate *aggregate, struct lcommunity *lcommunity) { struct lcommunity *aggr_lcommunity = NULL; if ((aggregate == NULL) || (lcommunity == NULL)) return; /* Create hash if not already created. */ if (aggregate->lcommunity_hash == NULL) aggregate->lcommunity_hash = hash_create( lcommunity_hash_make, lcommunity_cmp, "BGP Aggregator lcommunity hash"); aggr_lcommunity = bgp_aggr_lcommunity_lookup(aggregate, lcommunity); if (aggr_lcommunity == NULL) { /* Insert lcommunity into hash. */ aggr_lcommunity = hash_get(aggregate->lcommunity_hash, lcommunity, bgp_aggr_lcommunty_hash_alloc); /* Re-compute aggregate's lcommunity. */ if (aggregate->lcommunity) lcommunity_free(&aggregate->lcommunity); hash_iterate(aggregate->lcommunity_hash, bgp_aggr_lcommunity_prepare, &aggregate->lcommunity); } /* Increment refernce counter. */ aggr_lcommunity->refcnt++; } void bgp_remove_lcommunity_from_aggregate(struct bgp_aggregate *aggregate, struct lcommunity *lcommunity) { struct lcommunity *aggr_lcommunity = NULL; struct lcommunity *ret_lcomm = NULL; if ((aggregate == NULL) || (lcommunity == NULL)) return; if (aggregate->lcommunity_hash == NULL) return; /* Look-up the lcommunity in the hash. */ aggr_lcommunity = bgp_aggr_lcommunity_lookup(aggregate, lcommunity); if (aggr_lcommunity) { aggr_lcommunity->refcnt--; if (aggr_lcommunity->refcnt == 0) { ret_lcomm = hash_release(aggregate->lcommunity_hash, aggr_lcommunity); lcommunity_free(&ret_lcomm); lcommunity_free(&aggregate->lcommunity); /* Compute aggregate's lcommunity. */ hash_iterate(aggregate->lcommunity_hash, bgp_aggr_lcommunity_prepare, &aggregate->lcommunity); } } } frr-7.2.1/bgpd/bgp_lcommunity.h0000644000000000000000000000561713610377563013342 00000000000000/* BGP Large Communities Attribute. * * Copyright (C) 2016 Keyur Patel * * This file is part of FreeRangeRouting (FRR). * * FRR is free software; you can redistribute it and/or modify it under the * terms of the GNU General Public License as published by the Free Software * Foundation; either version 2, or (at your option) any later version. * * FRR 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; see the file COPYING; if not, write to the Free Software * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA */ #ifndef _QUAGGA_BGP_LCOMMUNITY_H #define _QUAGGA_BGP_LCOMMUNITY_H #include "lib/json.h" #include "bgpd/bgp_route.h" /* Large Communities value is twelve octets long. */ #define LCOMMUNITY_SIZE 12 /* Large Communities attribute. */ struct lcommunity { /* Reference counter. */ unsigned long refcnt; /* Size of Extended Communities attribute. */ int size; /* Large Communities value. */ uint8_t *val; /* Large Communities as a json object */ json_object *json; /* Human readable format string. */ char *str; }; /* Large community value is 12 octets. */ struct lcommunity_val { char val[LCOMMUNITY_SIZE]; }; #define lcom_length(X) ((X)->size * LCOMMUNITY_SIZE) extern void lcommunity_init(void); extern void lcommunity_finish(void); extern void lcommunity_free(struct lcommunity **); extern struct lcommunity *lcommunity_parse(uint8_t *, unsigned short); extern struct lcommunity *lcommunity_dup(struct lcommunity *); extern struct lcommunity *lcommunity_merge(struct lcommunity *, struct lcommunity *); extern struct lcommunity *lcommunity_uniq_sort(struct lcommunity *); extern struct lcommunity *lcommunity_intern(struct lcommunity *); extern bool lcommunity_cmp(const void *arg1, const void *arg2); extern void lcommunity_unintern(struct lcommunity **); extern unsigned int lcommunity_hash_make(const void *); extern struct hash *lcommunity_hash(void); extern struct lcommunity *lcommunity_str2com(const char *); extern int lcommunity_match(const struct lcommunity *, const struct lcommunity *); extern char *lcommunity_str(struct lcommunity *, bool make_json); extern int lcommunity_include(struct lcommunity *lcom, uint8_t *ptr); extern void lcommunity_del_val(struct lcommunity *lcom, uint8_t *ptr); extern void bgp_compute_aggregate_lcommunity( struct bgp_aggregate *aggregate, struct lcommunity *lcommunity); extern void bgp_remove_lcommunity_from_aggregate( struct bgp_aggregate *aggregate, struct lcommunity *lcommunity); extern void bgp_aggr_lcommunity_remove(void *arg); #endif /* _QUAGGA_BGP_LCOMMUNITY_H */ frr-7.2.1/bgpd/bgp_mac.c0000644000000000000000000002332513610377563011671 00000000000000/* * BGPd - Mac hash code * Copyright (C) 2018 Cumulus Networks, Inc. * Donald Sharp * * This program is free software; you can redistribute it and/or modify it * under the terms of the 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; see the file COPYING; if not, write to the Free Software * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA */ #include #include #include #include #include #include "bgpd/bgpd.h" #include "bgpd/bgp_mac.h" #include "bgpd/bgp_memory.h" #include "bgpd/bgp_route.h" #include "bgpd/bgp_packet.h" #include "bgpd/bgp_rd.h" #include "bgpd/bgp_debug.h" #include "bgpd/bgp_evpn_private.h" DEFINE_MTYPE_STATIC(BGPD, BSM, "Mac Hash Entry"); DEFINE_MTYPE_STATIC(BGPD, BSM_STRING, "Mac Hash Entry Interface String"); struct bgp_self_mac { struct ethaddr macaddr; struct list *ifp_list; }; static unsigned int bgp_mac_hash_key_make(const void *data) { const struct bgp_self_mac *bsm = data; return jhash(&bsm->macaddr, ETH_ALEN, 0xa5a5dead); } static bool bgp_mac_hash_cmp(const void *d1, const void *d2) { const struct bgp_self_mac *bsm1 = d1; const struct bgp_self_mac *bsm2 = d2; if (memcmp(&bsm1->macaddr, &bsm2->macaddr, ETH_ALEN) == 0) return true; return false; } void bgp_mac_init(void) { bm->self_mac_hash = hash_create(bgp_mac_hash_key_make, bgp_mac_hash_cmp, "BGP MAC Hash"); } static void bgp_mac_hash_free(void *data) { struct bgp_self_mac *bsm = data; if (bsm->ifp_list) list_delete(&bsm->ifp_list); XFREE(MTYPE_BSM, bsm); } void bgp_mac_finish(void) { hash_clean(bm->self_mac_hash, bgp_mac_hash_free); hash_free(bm->self_mac_hash); } static void bgp_mac_hash_interface_string_del(void *val) { char *data = val; XFREE(MTYPE_BSM_STRING, data); } static void *bgp_mac_hash_alloc(void *p) { const struct bgp_self_mac *orig = p; struct bgp_self_mac *bsm; bsm = XCALLOC(MTYPE_BSM, sizeof(struct bgp_self_mac)); memcpy(&bsm->macaddr, &orig->macaddr, ETH_ALEN); bsm->ifp_list = list_new(); bsm->ifp_list->del = bgp_mac_hash_interface_string_del; return bsm; } struct bgp_mac_find_internal { struct bgp_self_mac *bsm; const char *ifname; }; static void bgp_mac_find_ifp_internal(struct hash_bucket *bucket, void *arg) { struct bgp_mac_find_internal *bmfi = arg; struct bgp_self_mac *bsm = bucket->data; struct listnode *node; char *name; for (ALL_LIST_ELEMENTS_RO(bsm->ifp_list, node, name)) { if (strcmp(name, bmfi->ifname) == 0) { bmfi->bsm = bsm; return; } } } static struct bgp_self_mac *bgp_mac_find_interface_name(const char *ifname) { struct bgp_mac_find_internal bmfi; bmfi.bsm = NULL; bmfi.ifname = ifname; hash_iterate(bm->self_mac_hash, bgp_mac_find_ifp_internal, &bmfi); return bmfi.bsm; } static void bgp_process_mac_rescan_table(struct bgp *bgp, struct peer *peer, struct bgp_table *table, struct ethaddr *macaddr) { struct bgp_node *prn, *rn; struct bgp_path_info *pi; for (prn = bgp_table_top(table); prn; prn = bgp_route_next(prn)) { struct bgp_table *sub = prn->info; if (!sub) continue; for (rn = bgp_table_top(sub); rn; rn = bgp_route_next(rn)) { bool rn_affected; struct prefix_evpn *pevpn = (struct prefix_evpn *)&rn->p; struct prefix_rd prd; uint32_t num_labels = 0; mpls_label_t *label_pnt = NULL; struct bgp_route_evpn evpn; if (pevpn->family == AF_EVPN && pevpn->prefix.route_type == BGP_EVPN_MAC_IP_ROUTE && memcmp(&rn->p.u.prefix_evpn.macip_addr.mac, macaddr, ETH_ALEN) == 0) rn_affected = true; else rn_affected = false; for (pi = rn->info; pi; pi = pi->next) { if (pi->peer == peer) break; } if (!pi) continue; /* * If the mac address is not the same then * we don't care and since we are looking */ if ((memcmp(&pi->attr->rmac, macaddr, ETH_ALEN) != 0) && !rn_affected) continue; if (pi->extra) num_labels = pi->extra->num_labels; if (num_labels) label_pnt = &pi->extra->label[0]; prd.family = AF_UNSPEC; prd.prefixlen = 64; memcpy(&prd.val, &prn->p.u.val, 8); if (CHECK_FLAG(pi->flags, BGP_PATH_REMOVED)) { if (bgp_debug_update(peer, &rn->p, NULL, 1)) { char pfx_buf[BGP_PRD_PATH_STRLEN]; bgp_debug_rdpfxpath2str( AFI_L2VPN, SAFI_EVPN, &prd, &rn->p, label_pnt, num_labels, pi->addpath_rx_id ? 1 : 0, pi->addpath_rx_id, pfx_buf, sizeof(pfx_buf)); zlog_debug( "%s skip update of %s marked as removed", peer->host, pfx_buf); } continue; } memcpy(&evpn, &pi->attr->evpn_overlay, sizeof(evpn)); int32_t ret = bgp_update(peer, &rn->p, pi->addpath_rx_id, pi->attr, AFI_L2VPN, SAFI_EVPN, ZEBRA_ROUTE_BGP, BGP_ROUTE_NORMAL, &prd, label_pnt, num_labels, 1, &evpn); if (ret < 0) bgp_unlock_node(rn); } } } static void bgp_mac_rescan_evpn_table(struct bgp *bgp, struct ethaddr *macaddr) { struct listnode *node; struct peer *peer; safi_t safi; afi_t afi; afi = AFI_L2VPN; safi = SAFI_EVPN; for (ALL_LIST_ELEMENTS_RO(bgp->peer, node, peer)) { if (CHECK_FLAG(peer->sflags, PEER_STATUS_GROUP)) continue; if (peer->status != Established) continue; if (CHECK_FLAG(peer->af_flags[afi][safi], PEER_FLAG_SOFT_RECONFIG)) { if (bgp_debug_update(peer, NULL, NULL, 1)) zlog_debug("Processing EVPN MAC interface change on peer %s (inbound, soft-reconfig)", peer->host); bgp_soft_reconfig_in(peer, afi, safi); } else { struct bgp_table *table = bgp->rib[afi][safi]; if (bgp_debug_update(peer, NULL, NULL, 1)) zlog_debug("Processing EVPN MAC interface change on peer %s", peer->host); bgp_process_mac_rescan_table(bgp, peer, table, macaddr); } } } static void bgp_mac_rescan_all_evpn_tables(struct ethaddr *macaddr) { struct listnode *node; struct bgp *bgp; for (ALL_LIST_ELEMENTS_RO(bm->bgp, node, bgp)) { struct bgp_table *table = bgp->rib[AFI_L2VPN][SAFI_EVPN]; if (table) bgp_mac_rescan_evpn_table(bgp, macaddr); } } static void bgp_mac_remove_ifp_internal(struct bgp_self_mac *bsm, char *ifname, struct ethaddr *macaddr) { struct listnode *node = NULL; char *name; for (ALL_LIST_ELEMENTS_RO(bsm->ifp_list, node, name)) { if (strcmp(name, ifname) == 0) break; } if (node) { list_delete_node(bsm->ifp_list, node); XFREE(MTYPE_BSM_STRING, name); } if (bsm->ifp_list->count == 0) { hash_release(bm->self_mac_hash, bsm); list_delete(&bsm->ifp_list); XFREE(MTYPE_BSM, bsm); bgp_mac_rescan_all_evpn_tables(macaddr); } } void bgp_mac_add_mac_entry(struct interface *ifp) { struct bgp_self_mac lookup; struct bgp_self_mac *bsm; struct bgp_self_mac *old_bsm; char *ifname; memcpy(&lookup.macaddr, &ifp->hw_addr, ETH_ALEN); bsm = hash_get(bm->self_mac_hash, &lookup, bgp_mac_hash_alloc); /* * Does this happen to be a move */ old_bsm = bgp_mac_find_interface_name(ifp->name); ifname = XSTRDUP(MTYPE_BSM_STRING, ifp->name); if (bsm->ifp_list->count == 0) { listnode_add(bsm->ifp_list, ifname); if (old_bsm) bgp_mac_remove_ifp_internal(old_bsm, ifname, &old_bsm->macaddr); } else { /* * If old mac address is the same as the new, * then there is nothing to do here */ if (old_bsm == bsm) { XFREE(MTYPE_BSM_STRING, ifname); return; } if (old_bsm) bgp_mac_remove_ifp_internal(old_bsm, ifp->name, &old_bsm->macaddr); listnode_add(bsm->ifp_list, ifname); } bgp_mac_rescan_all_evpn_tables(&bsm->macaddr); } void bgp_mac_del_mac_entry(struct interface *ifp) { struct bgp_self_mac lookup; struct bgp_self_mac *bsm; memcpy(&lookup.macaddr, &ifp->hw_addr, ETH_ALEN); bsm = hash_lookup(bm->self_mac_hash, &lookup); if (!bsm) return; /* * Write code to allow old mac address to no-longer * win if we happen to have received it from a peer. */ bgp_mac_remove_ifp_internal(bsm, ifp->name, &bsm->macaddr); } /* This API checks MAC address against any of local * assigned (SVIs) MAC address. * An example: router-mac attribute in any of evpn update * requires to compare against local mac. */ bool bgp_mac_exist(struct ethaddr *mac) { struct bgp_self_mac lookup; struct bgp_self_mac *bsm; static uint8_t tmp [ETHER_ADDR_STRLEN] = {0}; if (memcmp(mac, &tmp, ETH_ALEN) == 0) return false; memcpy(&lookup.macaddr, mac, ETH_ALEN); bsm = hash_lookup(bm->self_mac_hash, &lookup); if (!bsm) return false; return true; } /* This API checks EVPN type-2 prefix and comapares * mac against any of local assigned (SVIs) MAC * address. */ bool bgp_mac_entry_exists(struct prefix *p) { struct prefix_evpn *pevpn = (struct prefix_evpn *)p; if (pevpn->family != AF_EVPN) return false; if (pevpn->prefix.route_type != BGP_EVPN_MAC_IP_ROUTE) return false; return bgp_mac_exist(&p->u.prefix_evpn.macip_addr.mac); return true; } static void bgp_mac_show_mac_entry(struct hash_bucket *bucket, void *arg) { struct vty *vty = arg; struct bgp_self_mac *bsm = bucket->data; struct listnode *node; char *name; char buf_mac[ETHER_ADDR_STRLEN]; vty_out(vty, "Mac Address: %s ", prefix_mac2str(&bsm->macaddr, buf_mac, sizeof(buf_mac))); for (ALL_LIST_ELEMENTS_RO(bsm->ifp_list, node, name)) vty_out(vty, "%s ", name); vty_out(vty, "\n"); } void bgp_mac_dump_table(struct vty *vty) { hash_iterate(bm->self_mac_hash, bgp_mac_show_mac_entry, vty); } frr-7.2.1/bgpd/bgp_mac.h0000644000000000000000000000262613610377563011677 00000000000000/* * BGPd - Mac hash header * Copyright (C) 2018 Cumulus Networks, Inc. * Donald Sharp * * This program is free software; you can redistribute it and/or modify it * under the terms of the 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; see the file COPYING; if not, write to the Free Software * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA */ #ifndef __BGP_MAC_H__ #define __BGP_MAC_H__ void bgp_mac_init(void); void bgp_mac_finish(void); /* * Functions to add/delete the mac entry from the appropriate * bgp hash's. Additionally to do some additional processing * to allow the win/loss to be processed. */ void bgp_mac_add_mac_entry(struct interface *ifp); void bgp_mac_del_mac_entry(struct interface *ifp); void bgp_mac_dump_table(struct vty *vty); /* * Function to lookup the prefix and see if we have a matching mac */ bool bgp_mac_entry_exists(struct prefix *p); bool bgp_mac_exist(struct ethaddr *mac); #endif frr-7.2.1/bgpd/bgp_main.c0000644000000000000000000002646713610377563012067 00000000000000/* Main routine of bgpd. * Copyright (C) 1996, 97, 98, 1999 Kunihiro Ishiguro * * This file is part of GNU Zebra. * * GNU Zebra is free software; you can redistribute it and/or modify it * under the terms of the GNU General Public License as published by the * Free Software Foundation; either version 2, or (at your option) any * later version. * * GNU Zebra 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; see the file COPYING; if not, write to the Free Software * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA */ #include #include #include "vector.h" #include "command.h" #include "getopt.h" #include "thread.h" #include #include "memory.h" #include "memory_vty.h" #include "prefix.h" #include "log.h" #include "privs.h" #include "sigevent.h" #include "zclient.h" #include "routemap.h" #include "filter.h" #include "plist.h" #include "stream.h" #include "queue.h" #include "vrf.h" #include "bfd.h" #include "libfrr.h" #include "ns.h" #include "bgpd/bgpd.h" #include "bgpd/bgp_attr.h" #include "bgpd/bgp_route.h" #include "bgpd/bgp_mplsvpn.h" #include "bgpd/bgp_aspath.h" #include "bgpd/bgp_dump.h" #include "bgpd/bgp_route.h" #include "bgpd/bgp_nexthop.h" #include "bgpd/bgp_regex.h" #include "bgpd/bgp_clist.h" #include "bgpd/bgp_debug.h" #include "bgpd/bgp_errors.h" #include "bgpd/bgp_filter.h" #include "bgpd/bgp_zebra.h" #include "bgpd/bgp_packet.h" #include "bgpd/bgp_keepalives.h" #include "bgpd/bgp_network.h" #include "bgpd/bgp_errors.h" #ifdef ENABLE_BGP_VNC #include "bgpd/rfapi/rfapi_backend.h" #endif /* bgpd options, we use GNU getopt library. */ static const struct option longopts[] = { {"bgp_port", required_argument, NULL, 'p'}, {"listenon", required_argument, NULL, 'l'}, {"no_kernel", no_argument, NULL, 'n'}, {"skip_runas", no_argument, NULL, 'S'}, {"ecmp", required_argument, NULL, 'e'}, {"int_num", required_argument, NULL, 'I'}, {"no_zebra", no_argument, NULL, 'Z'}, {0}}; /* signal definitions */ void sighup(void); void sigint(void); void sigusr1(void); static void bgp_exit(int); static void bgp_vrf_terminate(void); static struct quagga_signal_t bgp_signals[] = { { .signal = SIGHUP, .handler = &sighup, }, { .signal = SIGUSR1, .handler = &sigusr1, }, { .signal = SIGINT, .handler = &sigint, }, { .signal = SIGTERM, .handler = &sigint, }, }; /* privileges */ static zebra_capabilities_t _caps_p[] = {ZCAP_BIND, ZCAP_NET_RAW, ZCAP_NET_ADMIN, ZCAP_SYS_ADMIN}; struct zebra_privs_t bgpd_privs = { #if defined(FRR_USER) && defined(FRR_GROUP) .user = FRR_USER, .group = FRR_GROUP, #endif #ifdef VTY_GROUP .vty_group = VTY_GROUP, #endif .caps_p = _caps_p, .cap_num_p = array_size(_caps_p), .cap_num_i = 0, }; static struct frr_daemon_info bgpd_di; /* SIGHUP handler. */ void sighup(void) { zlog_info("SIGHUP received"); /* Terminate all thread. */ bgp_terminate(); bgp_reset(); zlog_info("bgpd restarting!"); /* Reload config file. */ vty_read_config(NULL, bgpd_di.config_file, config_default); /* Try to return to normal operation. */ } /* SIGINT handler. */ __attribute__((__noreturn__)) void sigint(void) { zlog_notice("Terminating on signal"); assert(bm->terminating == false); bm->terminating = true; /* global flag that shutting down */ bgp_terminate(); bgp_exit(0); exit(0); } /* SIGUSR1 handler. */ void sigusr1(void) { zlog_rotate(); } /* Try to free up allocations we know about so that diagnostic tools such as valgrind are able to better illuminate leaks. Zebra route removal and protocol teardown are not meant to be done here. For example, "retain_mode" may be set. */ static __attribute__((__noreturn__)) void bgp_exit(int status) { struct bgp *bgp, *bgp_default, *bgp_evpn; struct listnode *node, *nnode; /* it only makes sense for this to be called on a clean exit */ assert(status == 0); frr_early_fini(); bfd_gbl_exit(); bgp_close(); bgp_default = bgp_get_default(); bgp_evpn = bgp_get_evpn(); /* reverse bgp_master_init */ for (ALL_LIST_ELEMENTS(bm->bgp, node, nnode, bgp)) { if (bgp_default == bgp || bgp_evpn == bgp) continue; bgp_delete(bgp); } if (bgp_evpn && bgp_evpn != bgp_default) bgp_delete(bgp_evpn); if (bgp_default) bgp_delete(bgp_default); /* reverse bgp_dump_init */ bgp_dump_finish(); /* reverse bgp_route_init */ bgp_route_finish(); /* cleanup route maps */ bgp_route_map_terminate(); /* reverse bgp_attr_init */ bgp_attr_finish(); /* stop pthreads */ bgp_pthreads_finish(); /* reverse access_list_init */ access_list_add_hook(NULL); access_list_delete_hook(NULL); access_list_reset(); /* reverse bgp_filter_init */ as_list_add_hook(NULL); as_list_delete_hook(NULL); bgp_filter_reset(); /* reverse prefix_list_init */ prefix_list_add_hook(NULL); prefix_list_delete_hook(NULL); prefix_list_reset(); /* reverse community_list_init */ community_list_terminate(bgp_clist); bgp_vrf_terminate(); #if ENABLE_BGP_VNC vnc_zebra_destroy(); #endif bgp_zebra_destroy(); bf_free(bm->rd_idspace); list_delete(&bm->bgp); bgp_lp_finish(); memset(bm, 0, sizeof(*bm)); frr_fini(); exit(status); } static int bgp_vrf_new(struct vrf *vrf) { if (BGP_DEBUG(zebra, ZEBRA)) zlog_debug("VRF Created: %s(%u)", vrf->name, vrf->vrf_id); return 0; } static int bgp_vrf_delete(struct vrf *vrf) { if (BGP_DEBUG(zebra, ZEBRA)) zlog_debug("VRF Deletion: %s(%u)", vrf->name, vrf->vrf_id); return 0; } static int bgp_vrf_enable(struct vrf *vrf) { struct bgp *bgp; vrf_id_t old_vrf_id; if (BGP_DEBUG(zebra, ZEBRA)) zlog_debug("VRF enable add %s id %u", vrf->name, vrf->vrf_id); bgp = bgp_lookup_by_name(vrf->name); if (bgp) { if (bgp->name && strmatch(vrf->name, VRF_DEFAULT_NAME)) { XFREE(MTYPE_BGP, bgp->name); bgp->name = NULL; XFREE(MTYPE_BGP, bgp->name_pretty); bgp->name_pretty = XSTRDUP(MTYPE_BGP, "VRF default"); } old_vrf_id = bgp->vrf_id; /* We have instance configured, link to VRF and make it "up". */ bgp_vrf_link(bgp, vrf); bgp_handle_socket(bgp, vrf, old_vrf_id, true); /* Update any redistribution if vrf_id changed */ if (old_vrf_id != bgp->vrf_id) bgp_redistribute_redo(bgp); bgp_instance_up(bgp); vpn_leak_zebra_vrf_label_update(bgp, AFI_IP); vpn_leak_zebra_vrf_label_update(bgp, AFI_IP6); vpn_leak_postchange(BGP_VPN_POLICY_DIR_TOVPN, AFI_IP, bgp_get_default(), bgp); vpn_leak_postchange(BGP_VPN_POLICY_DIR_FROMVPN, AFI_IP, bgp_get_default(), bgp); vpn_leak_postchange(BGP_VPN_POLICY_DIR_TOVPN, AFI_IP6, bgp_get_default(), bgp); vpn_leak_postchange(BGP_VPN_POLICY_DIR_FROMVPN, AFI_IP6, bgp_get_default(), bgp); } return 0; } static int bgp_vrf_disable(struct vrf *vrf) { struct bgp *bgp; vrf_id_t old_vrf_id; if (vrf->vrf_id == VRF_DEFAULT) return 0; if (BGP_DEBUG(zebra, ZEBRA)) zlog_debug("VRF disable %s id %d", vrf->name, vrf->vrf_id); bgp = bgp_lookup_by_name(vrf->name); if (bgp) { vpn_leak_zebra_vrf_label_withdraw(bgp, AFI_IP); vpn_leak_zebra_vrf_label_withdraw(bgp, AFI_IP6); vpn_leak_prechange(BGP_VPN_POLICY_DIR_TOVPN, AFI_IP, bgp_get_default(), bgp); vpn_leak_prechange(BGP_VPN_POLICY_DIR_FROMVPN, AFI_IP, bgp_get_default(), bgp); vpn_leak_prechange(BGP_VPN_POLICY_DIR_TOVPN, AFI_IP6, bgp_get_default(), bgp); vpn_leak_prechange(BGP_VPN_POLICY_DIR_FROMVPN, AFI_IP6, bgp_get_default(), bgp); old_vrf_id = bgp->vrf_id; bgp_handle_socket(bgp, vrf, VRF_UNKNOWN, false); /* We have instance configured, unlink from VRF and make it * "down". */ bgp_vrf_unlink(bgp, vrf); /* Delete any redistribute vrf bitmaps if the vrf_id changed */ if (old_vrf_id != bgp->vrf_id) bgp_unset_redist_vrf_bitmaps(bgp, old_vrf_id); bgp_instance_down(bgp); } /* Note: This is a callback, the VRF will be deleted by the caller. */ return 0; } static void bgp_vrf_init(void) { vrf_init(bgp_vrf_new, bgp_vrf_enable, bgp_vrf_disable, bgp_vrf_delete, NULL); } static void bgp_vrf_terminate(void) { vrf_terminate(); } static const struct frr_yang_module_info *bgpd_yang_modules[] = { }; FRR_DAEMON_INFO(bgpd, BGP, .vty_port = BGP_VTY_PORT, .proghelp = "Implementation of the BGP routing protocol.", .signals = bgp_signals, .n_signals = array_size(bgp_signals), .privs = &bgpd_privs, .yang_modules = bgpd_yang_modules, .n_yang_modules = array_size(bgpd_yang_modules), ) #define DEPRECATED_OPTIONS "" /* Main routine of bgpd. Treatment of argument and start bgp finite state machine is handled at here. */ int main(int argc, char **argv) { int opt; int tmp_port; int bgp_port = BGP_PORT_DEFAULT; char *bgp_address = NULL; int no_fib_flag = 0; int no_zebra_flag = 0; int skip_runas = 0; int instance = 0; frr_preinit(&bgpd_di, argc, argv); frr_opt_add( "p:l:SnZe:I:" DEPRECATED_OPTIONS, longopts, " -p, --bgp_port Set BGP listen port number (0 means do not listen).\n" " -l, --listenon Listen on specified address (implies -n)\n" " -n, --no_kernel Do not install route to kernel.\n" " -Z, --no_zebra Do not communicate with Zebra.\n" " -S, --skip_runas Skip capabilities checks, and changing user and group IDs.\n" " -e, --ecmp Specify ECMP to use.\n" " -I, --int_num Set instance number (label-manager)\n"); /* Command line argument treatment. */ while (1) { opt = frr_getopt(argc, argv, 0); if (opt && opt < 128 && strchr(DEPRECATED_OPTIONS, opt)) { fprintf(stderr, "The -%c option no longer exists.\nPlease refer to the manual.\n", opt); continue; } if (opt == EOF) break; switch (opt) { case 0: break; case 'p': tmp_port = atoi(optarg); if (tmp_port < 0 || tmp_port > 0xffff) bgp_port = BGP_PORT_DEFAULT; else bgp_port = tmp_port; break; case 'e': multipath_num = atoi(optarg); if (multipath_num > MULTIPATH_NUM || multipath_num <= 0) { flog_err( EC_BGP_MULTIPATH, "Multipath Number specified must be less than %d and greater than 0", MULTIPATH_NUM); return 1; } break; case 'l': bgp_address = optarg; /* listenon implies -n */ /* fallthru */ case 'n': no_fib_flag = 1; break; case 'Z': no_zebra_flag = 1; break; case 'S': skip_runas = 1; break; case 'I': instance = atoi(optarg); if (instance > (unsigned short)-1) zlog_err("Instance %i out of range (0..%u)", instance, (unsigned short)-1); break; default: frr_help_exit(1); break; } } if (skip_runas) memset(&bgpd_privs, 0, sizeof(bgpd_privs)); /* BGP master init. */ bgp_master_init(frr_init()); bm->port = bgp_port; if (bgp_port == 0) bgp_option_set(BGP_OPT_NO_LISTEN); bm->address = bgp_address; if (no_fib_flag || no_zebra_flag) bgp_option_set(BGP_OPT_NO_FIB); if (no_zebra_flag) bgp_option_set(BGP_OPT_NO_ZEBRA); bgp_error_init(); /* Initializations. */ bgp_vrf_init(); /* BGP related initialization. */ bgp_init((unsigned short)instance); snprintf(bgpd_di.startinfo, sizeof(bgpd_di.startinfo), ", bgp@%s:%d", (bm->address ? bm->address : ""), bm->port); frr_config_fork(); /* must be called after fork() */ bgp_pthreads_run(); frr_run(bm->master); /* Not reached. */ return (0); } frr-7.2.1/bgpd/bgp_memory.c0000644000000000000000000001270613610377563012442 00000000000000/* bgpd memory type definitions * * Copyright (C) 2015 David Lamparter * * This file is part of Quagga. * * Quagga is free software; you can redistribute it and/or modify it * under the terms of the GNU General Public License as published by the * Free Software Foundation; either version 2, or (at your option) any * later version. * * Quagga 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; see the file COPYING; if not, write to the Free Software * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA */ #ifdef HAVE_CONFIG_H #include "config.h" #endif #include "bgp_memory.h" /* this file is temporary in nature; definitions should be moved to the * files they're used in */ DEFINE_MGROUP(BGPD, "bgpd") DEFINE_MTYPE(BGPD, BGP, "BGP instance") DEFINE_MTYPE(BGPD, BGP_LISTENER, "BGP listen socket details") DEFINE_MTYPE(BGPD, BGP_PEER, "BGP peer") DEFINE_MTYPE(BGPD, BGP_PEER_HOST, "BGP peer hostname") DEFINE_MTYPE(BGPD, BGP_PEER_IFNAME, "BGP peer ifname") DEFINE_MTYPE(BGPD, PEER_GROUP, "Peer group") DEFINE_MTYPE(BGPD, PEER_GROUP_HOST, "BGP Peer group hostname") DEFINE_MTYPE(BGPD, PEER_DESC, "Peer description") DEFINE_MTYPE(BGPD, PEER_PASSWORD, "Peer password string") DEFINE_MTYPE(BGPD, BGP_PEER_AF, "BGP peer af") DEFINE_MTYPE(BGPD, BGP_UPDGRP, "BGP update group") DEFINE_MTYPE(BGPD, BGP_UPD_SUBGRP, "BGP update subgroup") DEFINE_MTYPE(BGPD, BGP_PACKET, "BGP packet") DEFINE_MTYPE(BGPD, ATTR, "BGP attribute") DEFINE_MTYPE(BGPD, AS_PATH, "BGP aspath") DEFINE_MTYPE(BGPD, AS_SEG, "BGP aspath seg") DEFINE_MTYPE(BGPD, AS_SEG_DATA, "BGP aspath segment data") DEFINE_MTYPE(BGPD, AS_STR, "BGP aspath str") DEFINE_MTYPE(BGPD, BGP_TABLE, "BGP table") DEFINE_MTYPE(BGPD, BGP_NODE, "BGP node") DEFINE_MTYPE(BGPD, BGP_ROUTE, "BGP route") DEFINE_MTYPE(BGPD, BGP_ROUTE_EXTRA, "BGP ancillary route info") DEFINE_MTYPE(BGPD, BGP_CONN, "BGP connected") DEFINE_MTYPE(BGPD, BGP_STATIC, "BGP static") DEFINE_MTYPE(BGPD, BGP_ADVERTISE_ATTR, "BGP adv attr") DEFINE_MTYPE(BGPD, BGP_ADVERTISE, "BGP adv") DEFINE_MTYPE(BGPD, BGP_SYNCHRONISE, "BGP synchronise") DEFINE_MTYPE(BGPD, BGP_ADJ_IN, "BGP adj in") DEFINE_MTYPE(BGPD, BGP_ADJ_OUT, "BGP adj out") DEFINE_MTYPE(BGPD, BGP_MPATH_INFO, "BGP multipath info") DEFINE_MTYPE(BGPD, AS_LIST, "BGP AS list") DEFINE_MTYPE(BGPD, AS_FILTER, "BGP AS filter") DEFINE_MTYPE(BGPD, AS_FILTER_STR, "BGP AS filter str") DEFINE_MTYPE(BGPD, COMMUNITY, "community") DEFINE_MTYPE(BGPD, COMMUNITY_VAL, "community val") DEFINE_MTYPE(BGPD, COMMUNITY_STR, "community str") DEFINE_MTYPE(BGPD, ECOMMUNITY, "extcommunity") DEFINE_MTYPE(BGPD, ECOMMUNITY_VAL, "extcommunity val") DEFINE_MTYPE(BGPD, ECOMMUNITY_STR, "extcommunity str") DEFINE_MTYPE(BGPD, COMMUNITY_LIST, "community-list") DEFINE_MTYPE(BGPD, COMMUNITY_LIST_NAME, "community-list name") DEFINE_MTYPE(BGPD, COMMUNITY_LIST_ENTRY, "community-list entry") DEFINE_MTYPE(BGPD, COMMUNITY_LIST_CONFIG, "community-list config") DEFINE_MTYPE(BGPD, COMMUNITY_LIST_HANDLER, "community-list handler") DEFINE_MTYPE(BGPD, CLUSTER, "Cluster list") DEFINE_MTYPE(BGPD, CLUSTER_VAL, "Cluster list val") DEFINE_MTYPE(BGPD, BGP_PROCESS_QUEUE, "BGP Process queue") DEFINE_MTYPE(BGPD, BGP_CLEAR_NODE_QUEUE, "BGP node clear queue") DEFINE_MTYPE(BGPD, TRANSIT, "BGP transit attr") DEFINE_MTYPE(BGPD, TRANSIT_VAL, "BGP transit val") DEFINE_MTYPE(BGPD, BGP_DEBUG_FILTER, "BGP debug filter") DEFINE_MTYPE(BGPD, BGP_DEBUG_STR, "BGP debug filter string") DEFINE_MTYPE(BGPD, BGP_DISTANCE, "BGP distance") DEFINE_MTYPE(BGPD, BGP_NEXTHOP_CACHE, "BGP nexthop") DEFINE_MTYPE(BGPD, BGP_CONFED_LIST, "BGP confed list") DEFINE_MTYPE(BGPD, PEER_UPDATE_SOURCE, "BGP peer update interface") DEFINE_MTYPE(BGPD, PEER_CONF_IF, "BGP peer config interface") DEFINE_MTYPE(BGPD, BGP_DAMP_INFO, "Dampening info") DEFINE_MTYPE(BGPD, BGP_DAMP_ARRAY, "BGP Dampening array") DEFINE_MTYPE(BGPD, BGP_REGEXP, "BGP regexp") DEFINE_MTYPE(BGPD, BGP_AGGREGATE, "BGP aggregate") DEFINE_MTYPE(BGPD, BGP_ADDR, "BGP own address") DEFINE_MTYPE(BGPD, TIP_ADDR, "BGP own tunnel-ip address") DEFINE_MTYPE(BGPD, BGP_REDIST, "BGP redistribution") DEFINE_MTYPE(BGPD, BGP_FILTER_NAME, "BGP Filter Information") DEFINE_MTYPE(BGPD, BGP_DUMP_STR, "BGP Dump String Information") DEFINE_MTYPE(BGPD, ENCAP_TLV, "ENCAP TLV") DEFINE_MTYPE(BGPD, BGP_TEA_OPTIONS, "BGP TEA Options") DEFINE_MTYPE(BGPD, BGP_TEA_OPTIONS_VALUE, "BGP TEA Options Value") DEFINE_MTYPE(BGPD, LCOMMUNITY, "Large Community") DEFINE_MTYPE(BGPD, LCOMMUNITY_STR, "Large Community display string") DEFINE_MTYPE(BGPD, LCOMMUNITY_VAL, "Large Community value") DEFINE_MTYPE(BGPD, BGP_EVPN, "BGP EVPN Information") DEFINE_MTYPE(BGPD, BGP_EVPN_ES_VTEP, "BGP EVPN ES VTEP Ip") DEFINE_MTYPE(BGPD, BGP_EVPN_ES, "BGP EVPN ESI Information") DEFINE_MTYPE(BGPD, BGP_EVPN_IMPORT_RT, "BGP EVPN Import RT") DEFINE_MTYPE(BGPD, BGP_EVPN_VRF_IMPORT_RT, "BGP EVPN VRF Import RT") DEFINE_MTYPE(BGPD, BGP_EVPN_MACIP, "BGP EVPN MAC IP") DEFINE_MTYPE(BGPD, BGP_FLOWSPEC, "BGP flowspec") DEFINE_MTYPE(BGPD, BGP_FLOWSPEC_RULE, "BGP flowspec rule") DEFINE_MTYPE(BGPD, BGP_FLOWSPEC_RULE_STR, "BGP flowspec rule str") DEFINE_MTYPE(BGPD, BGP_FLOWSPEC_COMPILED, "BGP flowspec compiled") DEFINE_MTYPE(BGPD, BGP_FLOWSPEC_NAME, "BGP flowspec name") DEFINE_MTYPE(BGPD, BGP_FLOWSPEC_INDEX, "BGP flowspec index") frr-7.2.1/bgpd/bgp_memory.h0000644000000000000000000000650113610377563012443 00000000000000/* bgpd memory type declarations * * Copyright (C) 2015 David Lamparter * * This file is part of Quagga. * * Quagga is free software; you can redistribute it and/or modify it * under the terms of the GNU General Public License as published by the * Free Software Foundation; either version 2, or (at your option) any * later version. * * Quagga 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; see the file COPYING; if not, write to the Free Software * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA */ #ifndef _QUAGGA_BGP_MEMORY_H #define _QUAGGA_BGP_MEMORY_H #include "memory.h" DECLARE_MGROUP(BGPD) DECLARE_MTYPE(BGP) DECLARE_MTYPE(BGP_LISTENER) DECLARE_MTYPE(BGP_PEER) DECLARE_MTYPE(BGP_PEER_HOST) DECLARE_MTYPE(BGP_PEER_IFNAME) DECLARE_MTYPE(PEER_GROUP) DECLARE_MTYPE(PEER_GROUP_HOST) DECLARE_MTYPE(PEER_DESC) DECLARE_MTYPE(PEER_PASSWORD) DECLARE_MTYPE(BGP_PEER_AF) DECLARE_MTYPE(BGP_UPDGRP) DECLARE_MTYPE(BGP_UPD_SUBGRP) DECLARE_MTYPE(BGP_PACKET) DECLARE_MTYPE(ATTR) DECLARE_MTYPE(AS_PATH) DECLARE_MTYPE(AS_SEG) DECLARE_MTYPE(AS_SEG_DATA) DECLARE_MTYPE(AS_STR) DECLARE_MTYPE(BGP_TABLE) DECLARE_MTYPE(BGP_NODE) DECLARE_MTYPE(BGP_ROUTE) DECLARE_MTYPE(BGP_ROUTE_EXTRA) DECLARE_MTYPE(BGP_CONN) DECLARE_MTYPE(BGP_STATIC) DECLARE_MTYPE(BGP_ADVERTISE_ATTR) DECLARE_MTYPE(BGP_ADVERTISE) DECLARE_MTYPE(BGP_SYNCHRONISE) DECLARE_MTYPE(BGP_ADJ_IN) DECLARE_MTYPE(BGP_ADJ_OUT) DECLARE_MTYPE(BGP_MPATH_INFO) DECLARE_MTYPE(AS_LIST) DECLARE_MTYPE(AS_FILTER) DECLARE_MTYPE(AS_FILTER_STR) DECLARE_MTYPE(COMMUNITY) DECLARE_MTYPE(COMMUNITY_VAL) DECLARE_MTYPE(COMMUNITY_STR) DECLARE_MTYPE(ECOMMUNITY) DECLARE_MTYPE(ECOMMUNITY_VAL) DECLARE_MTYPE(ECOMMUNITY_STR) DECLARE_MTYPE(COMMUNITY_LIST) DECLARE_MTYPE(COMMUNITY_LIST_NAME) DECLARE_MTYPE(COMMUNITY_LIST_ENTRY) DECLARE_MTYPE(COMMUNITY_LIST_CONFIG) DECLARE_MTYPE(COMMUNITY_LIST_HANDLER) DECLARE_MTYPE(CLUSTER) DECLARE_MTYPE(CLUSTER_VAL) DECLARE_MTYPE(BGP_PROCESS_QUEUE) DECLARE_MTYPE(BGP_CLEAR_NODE_QUEUE) DECLARE_MTYPE(TRANSIT) DECLARE_MTYPE(TRANSIT_VAL) DECLARE_MTYPE(BGP_DEBUG_FILTER) DECLARE_MTYPE(BGP_DEBUG_STR) DECLARE_MTYPE(BGP_DISTANCE) DECLARE_MTYPE(BGP_NEXTHOP_CACHE) DECLARE_MTYPE(BGP_CONFED_LIST) DECLARE_MTYPE(PEER_UPDATE_SOURCE) DECLARE_MTYPE(PEER_CONF_IF) DECLARE_MTYPE(BGP_DAMP_INFO) DECLARE_MTYPE(BGP_DAMP_ARRAY) DECLARE_MTYPE(BGP_REGEXP) DECLARE_MTYPE(BGP_AGGREGATE) DECLARE_MTYPE(BGP_ADDR) DECLARE_MTYPE(TIP_ADDR) DECLARE_MTYPE(BGP_REDIST) DECLARE_MTYPE(BGP_FILTER_NAME) DECLARE_MTYPE(BGP_DUMP_STR) DECLARE_MTYPE(ENCAP_TLV) DECLARE_MTYPE(BGP_TEA_OPTIONS) DECLARE_MTYPE(BGP_TEA_OPTIONS_VALUE) DECLARE_MTYPE(LCOMMUNITY) DECLARE_MTYPE(LCOMMUNITY_STR) DECLARE_MTYPE(LCOMMUNITY_VAL) DECLARE_MTYPE(BGP_EVPN_ES) DECLARE_MTYPE(BGP_EVPN_ES_VTEP) DECLARE_MTYPE(BGP_EVPN) DECLARE_MTYPE(BGP_EVPN_IMPORT_RT) DECLARE_MTYPE(BGP_EVPN_VRF_IMPORT_RT) DECLARE_MTYPE(BGP_EVPN_MACIP) DECLARE_MTYPE(BGP_FLOWSPEC) DECLARE_MTYPE(BGP_FLOWSPEC_RULE) DECLARE_MTYPE(BGP_FLOWSPEC_RULE_STR) DECLARE_MTYPE(BGP_FLOWSPEC_COMPILED) DECLARE_MTYPE(BGP_FLOWSPEC_NAME) DECLARE_MTYPE(BGP_FLOWSPEC_INDEX) #endif /* _QUAGGA_BGP_MEMORY_H */ frr-7.2.1/bgpd/bgp_mpath.c0000644000000000000000000005132313610377563012241 00000000000000/* * BGP Multipath * Copyright (C) 2010 Google Inc. * * This file is part of Quagga * * Quagga is free software; you can redistribute it and/or modify it * under the terms of the GNU General Public License as published by the * Free Software Foundation; either version 2, or (at your option) any * later version. * * Quagga 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; see the file COPYING; if not, write to the Free Software * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA */ #include #include "command.h" #include "prefix.h" #include "linklist.h" #include "sockunion.h" #include "memory.h" #include "queue.h" #include "filter.h" #include "bgpd/bgpd.h" #include "bgpd/bgp_table.h" #include "bgpd/bgp_route.h" #include "bgpd/bgp_attr.h" #include "bgpd/bgp_debug.h" #include "bgpd/bgp_aspath.h" #include "bgpd/bgp_community.h" #include "bgpd/bgp_ecommunity.h" #include "bgpd/bgp_lcommunity.h" #include "bgpd/bgp_mpath.h" /* * bgp_maximum_paths_set * * Record maximum-paths configuration for BGP instance */ int bgp_maximum_paths_set(struct bgp *bgp, afi_t afi, safi_t safi, int peertype, uint16_t maxpaths, uint16_t options) { if (!bgp || (afi >= AFI_MAX) || (safi >= SAFI_MAX)) return -1; switch (peertype) { case BGP_PEER_IBGP: bgp->maxpaths[afi][safi].maxpaths_ibgp = maxpaths; bgp->maxpaths[afi][safi].ibgp_flags |= options; break; case BGP_PEER_EBGP: bgp->maxpaths[afi][safi].maxpaths_ebgp = maxpaths; break; default: return -1; } return 0; } /* * bgp_maximum_paths_unset * * Remove maximum-paths configuration from BGP instance */ int bgp_maximum_paths_unset(struct bgp *bgp, afi_t afi, safi_t safi, int peertype) { if (!bgp || (afi >= AFI_MAX) || (safi >= SAFI_MAX)) return -1; switch (peertype) { case BGP_PEER_IBGP: bgp->maxpaths[afi][safi].maxpaths_ibgp = multipath_num; bgp->maxpaths[afi][safi].ibgp_flags = 0; break; case BGP_PEER_EBGP: bgp->maxpaths[afi][safi].maxpaths_ebgp = multipath_num; break; default: return -1; } return 0; } /* * bgp_interface_same * * Return true if ifindex for ifp1 and ifp2 are the same, else return false. */ static int bgp_interface_same(struct interface *ifp1, struct interface *ifp2) { if (!ifp1 && !ifp2) return 1; if (!ifp1 && ifp2) return 0; if (ifp1 && !ifp2) return 0; return (ifp1->ifindex == ifp2->ifindex); } /* * bgp_path_info_nexthop_cmp * * Compare the nexthops of two paths. Return value is less than, equal to, * or greater than zero if bpi1 is respectively less than, equal to, * or greater than bpi2. */ int bgp_path_info_nexthop_cmp(struct bgp_path_info *bpi1, struct bgp_path_info *bpi2) { int compare; struct in6_addr addr1, addr2; compare = IPV4_ADDR_CMP(&bpi1->attr->nexthop, &bpi2->attr->nexthop); if (!compare) { if (bpi1->attr->mp_nexthop_len == bpi2->attr->mp_nexthop_len) { switch (bpi1->attr->mp_nexthop_len) { case BGP_ATTR_NHLEN_IPV4: case BGP_ATTR_NHLEN_VPNV4: compare = IPV4_ADDR_CMP( &bpi1->attr->mp_nexthop_global_in, &bpi2->attr->mp_nexthop_global_in); break; case BGP_ATTR_NHLEN_IPV6_GLOBAL: case BGP_ATTR_NHLEN_VPNV6_GLOBAL: compare = IPV6_ADDR_CMP( &bpi1->attr->mp_nexthop_global, &bpi2->attr->mp_nexthop_global); break; case BGP_ATTR_NHLEN_IPV6_GLOBAL_AND_LL: addr1 = (bpi1->attr->mp_nexthop_prefer_global) ? bpi1->attr->mp_nexthop_global : bpi1->attr->mp_nexthop_local; addr2 = (bpi2->attr->mp_nexthop_prefer_global) ? bpi2->attr->mp_nexthop_global : bpi2->attr->mp_nexthop_local; if (!bpi1->attr->mp_nexthop_prefer_global && !bpi2->attr->mp_nexthop_prefer_global) compare = !bgp_interface_same( bpi1->peer->ifp, bpi2->peer->ifp); if (!compare) compare = IPV6_ADDR_CMP(&addr1, &addr2); break; } } /* This can happen if one IPv6 peer sends you global and * link-local * nexthops but another IPv6 peer only sends you global */ else if (bpi1->attr->mp_nexthop_len == BGP_ATTR_NHLEN_IPV6_GLOBAL || bpi1->attr->mp_nexthop_len == BGP_ATTR_NHLEN_IPV6_GLOBAL_AND_LL) { compare = IPV6_ADDR_CMP(&bpi1->attr->mp_nexthop_global, &bpi2->attr->mp_nexthop_global); if (!compare) { if (bpi1->attr->mp_nexthop_len < bpi2->attr->mp_nexthop_len) compare = -1; else compare = 1; } } } return compare; } /* * bgp_path_info_mpath_cmp * * This function determines our multipath list ordering. By ordering * the list we can deterministically select which paths are included * in the multipath set. The ordering also helps in detecting changes * in the multipath selection so we can detect whether to send an * update to zebra. * * The order of paths is determined first by received nexthop, and then * by peer address if the nexthops are the same. */ static int bgp_path_info_mpath_cmp(void *val1, void *val2) { struct bgp_path_info *bpi1, *bpi2; int compare; bpi1 = val1; bpi2 = val2; compare = bgp_path_info_nexthop_cmp(bpi1, bpi2); if (!compare) { if (!bpi1->peer->su_remote && !bpi2->peer->su_remote) compare = 0; else if (!bpi1->peer->su_remote) compare = 1; else if (!bpi2->peer->su_remote) compare = -1; else compare = sockunion_cmp(bpi1->peer->su_remote, bpi2->peer->su_remote); } return compare; } /* * bgp_mp_list_init * * Initialize the mp_list, which holds the list of multipaths * selected by bgp_best_selection */ void bgp_mp_list_init(struct list *mp_list) { assert(mp_list); memset(mp_list, 0, sizeof(struct list)); mp_list->cmp = bgp_path_info_mpath_cmp; } /* * bgp_mp_list_clear * * Clears all entries out of the mp_list */ void bgp_mp_list_clear(struct list *mp_list) { assert(mp_list); list_delete_all_node(mp_list); } /* * bgp_mp_list_add * * Adds a multipath entry to the mp_list */ void bgp_mp_list_add(struct list *mp_list, struct bgp_path_info *mpinfo) { assert(mp_list && mpinfo); listnode_add_sort(mp_list, mpinfo); } /* * bgp_path_info_mpath_new * * Allocate and zero memory for a new bgp_path_info_mpath element */ static struct bgp_path_info_mpath *bgp_path_info_mpath_new(void) { struct bgp_path_info_mpath *new_mpath; new_mpath = XCALLOC(MTYPE_BGP_MPATH_INFO, sizeof(struct bgp_path_info_mpath)); return new_mpath; } /* * bgp_path_info_mpath_free * * Release resources for a bgp_path_info_mpath element and zero out pointer */ void bgp_path_info_mpath_free(struct bgp_path_info_mpath **mpath) { if (mpath && *mpath) { if ((*mpath)->mp_attr) bgp_attr_unintern(&(*mpath)->mp_attr); XFREE(MTYPE_BGP_MPATH_INFO, *mpath); *mpath = NULL; } } /* * bgp_path_info_mpath_get * * Fetch the mpath element for the given bgp_path_info. Used for * doing lazy allocation. */ static struct bgp_path_info_mpath * bgp_path_info_mpath_get(struct bgp_path_info *path) { struct bgp_path_info_mpath *mpath; if (!path) return NULL; if (!path->mpath) { mpath = bgp_path_info_mpath_new(); if (!mpath) return NULL; path->mpath = mpath; mpath->mp_info = path; } return path->mpath; } /* * bgp_path_info_mpath_enqueue * * Enqueue a path onto the multipath list given the previous multipath * list entry */ static void bgp_path_info_mpath_enqueue(struct bgp_path_info *prev_info, struct bgp_path_info *path) { struct bgp_path_info_mpath *prev, *mpath; prev = bgp_path_info_mpath_get(prev_info); mpath = bgp_path_info_mpath_get(path); if (!prev || !mpath) return; mpath->mp_next = prev->mp_next; mpath->mp_prev = prev; if (prev->mp_next) prev->mp_next->mp_prev = mpath; prev->mp_next = mpath; SET_FLAG(path->flags, BGP_PATH_MULTIPATH); } /* * bgp_path_info_mpath_dequeue * * Remove a path from the multipath list */ void bgp_path_info_mpath_dequeue(struct bgp_path_info *path) { struct bgp_path_info_mpath *mpath = path->mpath; if (!mpath) return; if (mpath->mp_prev) mpath->mp_prev->mp_next = mpath->mp_next; if (mpath->mp_next) mpath->mp_next->mp_prev = mpath->mp_prev; mpath->mp_next = mpath->mp_prev = NULL; UNSET_FLAG(path->flags, BGP_PATH_MULTIPATH); } /* * bgp_path_info_mpath_next * * Given a bgp_path_info, return the next multipath entry */ struct bgp_path_info *bgp_path_info_mpath_next(struct bgp_path_info *path) { if (!path->mpath || !path->mpath->mp_next) return NULL; return path->mpath->mp_next->mp_info; } /* * bgp_path_info_mpath_first * * Given bestpath bgp_path_info, return the first multipath entry. */ struct bgp_path_info *bgp_path_info_mpath_first(struct bgp_path_info *path) { return bgp_path_info_mpath_next(path); } /* * bgp_path_info_mpath_count * * Given the bestpath bgp_path_info, return the number of multipath entries */ uint32_t bgp_path_info_mpath_count(struct bgp_path_info *path) { if (!path->mpath) return 0; return path->mpath->mp_count; } /* * bgp_path_info_mpath_count_set * * Sets the count of multipaths into bestpath's mpath element */ static void bgp_path_info_mpath_count_set(struct bgp_path_info *path, uint32_t count) { struct bgp_path_info_mpath *mpath; if (!count && !path->mpath) return; mpath = bgp_path_info_mpath_get(path); if (!mpath) return; mpath->mp_count = count; } /* * bgp_path_info_mpath_attr * * Given bestpath bgp_path_info, return aggregated attribute set used * for advertising the multipath route */ struct attr *bgp_path_info_mpath_attr(struct bgp_path_info *path) { if (!path->mpath) return NULL; return path->mpath->mp_attr; } /* * bgp_path_info_mpath_attr_set * * Sets the aggregated attribute into bestpath's mpath element */ static void bgp_path_info_mpath_attr_set(struct bgp_path_info *path, struct attr *attr) { struct bgp_path_info_mpath *mpath; if (!attr && !path->mpath) return; mpath = bgp_path_info_mpath_get(path); if (!mpath) return; mpath->mp_attr = attr; } /* * bgp_path_info_mpath_update * * Compare and sync up the multipath list with the mp_list generated by * bgp_best_selection */ void bgp_path_info_mpath_update(struct bgp_node *rn, struct bgp_path_info *new_best, struct bgp_path_info *old_best, struct list *mp_list, struct bgp_maxpaths_cfg *mpath_cfg) { uint16_t maxpaths, mpath_count, old_mpath_count; struct listnode *mp_node, *mp_next_node; struct bgp_path_info *cur_mpath, *new_mpath, *next_mpath, *prev_mpath; int mpath_changed, debug; char pfx_buf[PREFIX2STR_BUFFER], nh_buf[2][INET6_ADDRSTRLEN]; char path_buf[PATH_ADDPATH_STR_BUFFER]; mpath_changed = 0; maxpaths = multipath_num; mpath_count = 0; cur_mpath = NULL; old_mpath_count = 0; prev_mpath = new_best; mp_node = listhead(mp_list); debug = bgp_debug_bestpath(&rn->p); if (debug) prefix2str(&rn->p, pfx_buf, sizeof(pfx_buf)); if (new_best) { mpath_count++; if (new_best != old_best) bgp_path_info_mpath_dequeue(new_best); maxpaths = (new_best->peer->sort == BGP_PEER_IBGP) ? mpath_cfg->maxpaths_ibgp : mpath_cfg->maxpaths_ebgp; } if (old_best) { cur_mpath = bgp_path_info_mpath_first(old_best); old_mpath_count = bgp_path_info_mpath_count(old_best); bgp_path_info_mpath_count_set(old_best, 0); bgp_path_info_mpath_dequeue(old_best); } if (debug) zlog_debug( "%s: starting mpath update, newbest %s num candidates %d old-mpath-count %d", pfx_buf, new_best ? new_best->peer->host : "NONE", mp_list ? listcount(mp_list) : 0, old_mpath_count); /* * We perform an ordered walk through both lists in parallel. * The reason for the ordered walk is that if there are paths * that were previously multipaths and are still multipaths, the walk * should encounter them in both lists at the same time. Otherwise * there will be paths that are in one list or another, and we * will deal with these separately. * * Note that new_best might be somewhere in the mp_list, so we need * to skip over it */ while (mp_node || cur_mpath) { struct bgp_path_info *tmp_info; /* * We can bail out of this loop if all existing paths on the * multipath list have been visited (for cleanup purposes) and * the maxpath requirement is fulfulled */ if (!cur_mpath && (mpath_count >= maxpaths)) break; mp_next_node = mp_node ? listnextnode(mp_node) : NULL; next_mpath = cur_mpath ? bgp_path_info_mpath_next(cur_mpath) : NULL; tmp_info = mp_node ? listgetdata(mp_node) : NULL; if (debug) zlog_debug( "%s: comparing candidate %s with existing mpath %s", pfx_buf, tmp_info ? tmp_info->peer->host : "NONE", cur_mpath ? cur_mpath->peer->host : "NONE"); /* * If equal, the path was a multipath and is still a multipath. * Insert onto new multipath list if maxpaths allows. */ if (mp_node && (listgetdata(mp_node) == cur_mpath)) { list_delete_node(mp_list, mp_node); bgp_path_info_mpath_dequeue(cur_mpath); if ((mpath_count < maxpaths) && prev_mpath && bgp_path_info_nexthop_cmp(prev_mpath, cur_mpath)) { bgp_path_info_mpath_enqueue(prev_mpath, cur_mpath); prev_mpath = cur_mpath; mpath_count++; if (debug) { bgp_path_info_path_with_addpath_rx_str( cur_mpath, path_buf); zlog_debug( "%s: %s is still multipath, cur count %d", pfx_buf, path_buf, mpath_count); } } else { mpath_changed = 1; if (debug) { bgp_path_info_path_with_addpath_rx_str( cur_mpath, path_buf); zlog_debug( "%s: remove mpath %s nexthop %s, cur count %d", pfx_buf, path_buf, inet_ntop(AF_INET, &cur_mpath->attr ->nexthop, nh_buf[0], sizeof(nh_buf[0])), mpath_count); } } mp_node = mp_next_node; cur_mpath = next_mpath; continue; } if (cur_mpath && (!mp_node || (bgp_path_info_mpath_cmp(cur_mpath, listgetdata(mp_node)) < 0))) { /* * If here, we have an old multipath and either the * mp_list * is finished or the next mp_node points to a later * multipath, so we need to purge this path from the * multipath list */ bgp_path_info_mpath_dequeue(cur_mpath); mpath_changed = 1; if (debug) { bgp_path_info_path_with_addpath_rx_str( cur_mpath, path_buf); zlog_debug( "%s: remove mpath %s nexthop %s, cur count %d", pfx_buf, path_buf, inet_ntop(AF_INET, &cur_mpath->attr->nexthop, nh_buf[0], sizeof(nh_buf[0])), mpath_count); } cur_mpath = next_mpath; } else { /* * If here, we have a path on the mp_list that was not * previously * a multipath (due to non-equivalance or maxpaths * exceeded), * or the matching multipath is sorted later in the * multipath * list. Before we enqueue the path on the new multipath * list, * make sure its not on the old_best multipath list or * referenced * via next_mpath: * - If next_mpath points to this new path, update * next_mpath to * point to the multipath after this one * - Dequeue the path from the multipath list just to * make sure */ new_mpath = listgetdata(mp_node); list_delete_node(mp_list, mp_node); assert(new_mpath); assert(prev_mpath); if ((mpath_count < maxpaths) && (new_mpath != new_best) && bgp_path_info_nexthop_cmp(prev_mpath, new_mpath)) { if (new_mpath == next_mpath) bgp_path_info_mpath_next(new_mpath); bgp_path_info_mpath_dequeue(new_mpath); bgp_path_info_mpath_enqueue(prev_mpath, new_mpath); prev_mpath = new_mpath; mpath_changed = 1; mpath_count++; if (debug) { bgp_path_info_path_with_addpath_rx_str( new_mpath, path_buf); zlog_debug( "%s: add mpath %s nexthop %s, cur count %d", pfx_buf, path_buf, inet_ntop(AF_INET, &new_mpath->attr ->nexthop, nh_buf[0], sizeof(nh_buf[0])), mpath_count); } } mp_node = mp_next_node; } } if (new_best) { if (debug) zlog_debug( "%s: New mpath count (incl newbest) %d mpath-change %s", pfx_buf, mpath_count, mpath_changed ? "YES" : "NO"); bgp_path_info_mpath_count_set(new_best, mpath_count - 1); if (mpath_changed || (bgp_path_info_mpath_count(new_best) != old_mpath_count)) SET_FLAG(new_best->flags, BGP_PATH_MULTIPATH_CHG); } } /* * bgp_mp_dmed_deselect * * Clean up multipath information for BGP_PATH_DMED_SELECTED path that * is not selected as best path */ void bgp_mp_dmed_deselect(struct bgp_path_info *dmed_best) { struct bgp_path_info *mpinfo, *mpnext; if (!dmed_best) return; for (mpinfo = bgp_path_info_mpath_first(dmed_best); mpinfo; mpinfo = mpnext) { mpnext = bgp_path_info_mpath_next(mpinfo); bgp_path_info_mpath_dequeue(mpinfo); } bgp_path_info_mpath_count_set(dmed_best, 0); UNSET_FLAG(dmed_best->flags, BGP_PATH_MULTIPATH_CHG); assert(bgp_path_info_mpath_first(dmed_best) == NULL); } /* * bgp_path_info_mpath_aggregate_update * * Set the multipath aggregate attribute. We need to see if the * aggregate has changed and then set the ATTR_CHANGED flag on the * bestpath info so that a peer update will be generated. The * change is detected by generating the current attribute, * interning it, and then comparing the interned pointer with the * current value. We can skip this generate/compare step if there * is no change in multipath selection and no attribute change in * any multipath. */ void bgp_path_info_mpath_aggregate_update(struct bgp_path_info *new_best, struct bgp_path_info *old_best) { struct bgp_path_info *mpinfo; struct aspath *aspath; struct aspath *asmerge; struct attr *new_attr, *old_attr; uint8_t origin; struct community *community, *commerge; struct ecommunity *ecomm, *ecommerge; struct lcommunity *lcomm, *lcommerge; struct attr attr = {0}; if (old_best && (old_best != new_best) && (old_attr = bgp_path_info_mpath_attr(old_best))) { bgp_attr_unintern(&old_attr); bgp_path_info_mpath_attr_set(old_best, NULL); } if (!new_best) return; if (!bgp_path_info_mpath_count(new_best)) { if ((new_attr = bgp_path_info_mpath_attr(new_best))) { bgp_attr_unintern(&new_attr); bgp_path_info_mpath_attr_set(new_best, NULL); SET_FLAG(new_best->flags, BGP_PATH_ATTR_CHANGED); } return; } attr = *new_best->attr; if (new_best->peer && bgp_flag_check(new_best->peer->bgp, BGP_FLAG_MULTIPATH_RELAX_AS_SET)) { /* aggregate attribute from multipath constituents */ aspath = aspath_dup(attr.aspath); origin = attr.origin; community = attr.community ? community_dup(attr.community) : NULL; ecomm = (attr.ecommunity) ? ecommunity_dup(attr.ecommunity) : NULL; lcomm = (attr.lcommunity) ? lcommunity_dup(attr.lcommunity) : NULL; for (mpinfo = bgp_path_info_mpath_first(new_best); mpinfo; mpinfo = bgp_path_info_mpath_next(mpinfo)) { asmerge = aspath_aggregate(aspath, mpinfo->attr->aspath); aspath_free(aspath); aspath = asmerge; if (origin < mpinfo->attr->origin) origin = mpinfo->attr->origin; if (mpinfo->attr->community) { if (community) { commerge = community_merge( community, mpinfo->attr->community); community = community_uniq_sort(commerge); community_free(&commerge); } else community = community_dup( mpinfo->attr->community); } if (mpinfo->attr->ecommunity) { if (ecomm) { ecommerge = ecommunity_merge( ecomm, mpinfo->attr->ecommunity); ecomm = ecommunity_uniq_sort(ecommerge); ecommunity_free(&ecommerge); } else ecomm = ecommunity_dup( mpinfo->attr->ecommunity); } if (mpinfo->attr->lcommunity) { if (lcomm) { lcommerge = lcommunity_merge( lcomm, mpinfo->attr->lcommunity); lcomm = lcommunity_uniq_sort(lcommerge); lcommunity_free(&lcommerge); } else lcomm = lcommunity_dup( mpinfo->attr->lcommunity); } } attr.aspath = aspath; attr.origin = origin; if (community) { attr.community = community; attr.flag |= ATTR_FLAG_BIT(BGP_ATTR_COMMUNITIES); } if (ecomm) { attr.ecommunity = ecomm; attr.flag |= ATTR_FLAG_BIT(BGP_ATTR_EXT_COMMUNITIES); } if (lcomm) { attr.lcommunity = lcomm; attr.flag |= ATTR_FLAG_BIT(BGP_ATTR_LARGE_COMMUNITIES); } /* Zap multipath attr nexthop so we set nexthop to self */ attr.nexthop.s_addr = 0; memset(&attr.mp_nexthop_global, 0, sizeof(struct in6_addr)); /* TODO: should we set ATOMIC_AGGREGATE and AGGREGATOR? */ } new_attr = bgp_attr_intern(&attr); if (new_attr != bgp_path_info_mpath_attr(new_best)) { if ((old_attr = bgp_path_info_mpath_attr(new_best))) bgp_attr_unintern(&old_attr); bgp_path_info_mpath_attr_set(new_best, new_attr); SET_FLAG(new_best->flags, BGP_PATH_ATTR_CHANGED); } else bgp_attr_unintern(&new_attr); } frr-7.2.1/bgpd/bgp_mpath.h0000644000000000000000000000615213610377563012246 00000000000000/* * BGP Multipath * Copyright (C) 2010 Google Inc. * * This file is part of Quagga * * Quagga is free software; you can redistribute it and/or modify it * under the terms of the GNU General Public License as published by the * Free Software Foundation; either version 2, or (at your option) any * later version. * * Quagga 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; see the file COPYING; if not, write to the Free Software * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA */ #ifndef _QUAGGA_BGP_MPATH_H #define _QUAGGA_BGP_MPATH_H /* Supplemental information linked to bgp_path_info for keeping track of * multipath selections, lazily allocated to save memory */ struct bgp_path_info_mpath { /* Points to the first multipath (on bestpath) or the next multipath */ struct bgp_path_info_mpath *mp_next; /* Points to the previous multipath or NULL on bestpath */ struct bgp_path_info_mpath *mp_prev; /* Points to bgp_path_info associated with this multipath info */ struct bgp_path_info *mp_info; /* When attached to best path, the number of selected multipaths */ uint32_t mp_count; /* Aggregated attribute for advertising multipath route */ struct attr *mp_attr; }; /* Functions to support maximum-paths configuration */ extern int bgp_maximum_paths_set(struct bgp *, afi_t, safi_t, int, uint16_t, uint16_t); extern int bgp_maximum_paths_unset(struct bgp *, afi_t, safi_t, int); /* Functions used by bgp_best_selection to record current * multipath selections */ extern int bgp_path_info_nexthop_cmp(struct bgp_path_info *bpi1, struct bgp_path_info *bpi2); extern void bgp_mp_list_init(struct list *); extern void bgp_mp_list_clear(struct list *); extern void bgp_mp_list_add(struct list *mp_list, struct bgp_path_info *mpinfo); extern void bgp_mp_dmed_deselect(struct bgp_path_info *dmed_best); extern void bgp_path_info_mpath_update(struct bgp_node *rn, struct bgp_path_info *new_best, struct bgp_path_info *old_best, struct list *mp_list, struct bgp_maxpaths_cfg *mpath_cfg); extern void bgp_path_info_mpath_aggregate_update(struct bgp_path_info *new_best, struct bgp_path_info *old_best); /* Unlink and free multipath information associated with a bgp_path_info */ extern void bgp_path_info_mpath_dequeue(struct bgp_path_info *path); extern void bgp_path_info_mpath_free(struct bgp_path_info_mpath **mpath); /* Walk list of multipaths associated with a best path */ extern struct bgp_path_info * bgp_path_info_mpath_first(struct bgp_path_info *path); extern struct bgp_path_info * bgp_path_info_mpath_next(struct bgp_path_info *path); /* Accessors for multipath information */ extern uint32_t bgp_path_info_mpath_count(struct bgp_path_info *path); extern struct attr *bgp_path_info_mpath_attr(struct bgp_path_info *path); #endif /* _QUAGGA_BGP_MPATH_H */ frr-7.2.1/bgpd/bgp_mplsvpn.c0000644000000000000000000021525713610377563012637 00000000000000/* MPLS-VPN * Copyright (C) 2000 Kunihiro Ishiguro * * This file is part of GNU Zebra. * * GNU Zebra is free software; you can redistribute it and/or modify it * under the terms of the GNU General Public License as published by the * Free Software Foundation; either version 2, or (at your option) any * later version. * * GNU Zebra 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; see the file COPYING; if not, write to the Free Software * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA */ #include #include "command.h" #include "prefix.h" #include "log.h" #include "memory.h" #include "stream.h" #include "queue.h" #include "filter.h" #include "mpls.h" #include "json.h" #include "zclient.h" #include "bgpd/bgpd.h" #include "bgpd/bgp_debug.h" #include "bgpd/bgp_errors.h" #include "bgpd/bgp_table.h" #include "bgpd/bgp_route.h" #include "bgpd/bgp_attr.h" #include "bgpd/bgp_label.h" #include "bgpd/bgp_mplsvpn.h" #include "bgpd/bgp_packet.h" #include "bgpd/bgp_vty.h" #include "bgpd/bgp_vpn.h" #include "bgpd/bgp_ecommunity.h" #include "bgpd/bgp_zebra.h" #include "bgpd/bgp_nexthop.h" #include "bgpd/bgp_nht.h" #include "bgpd/bgp_evpn.h" #if ENABLE_BGP_VNC #include "bgpd/rfapi/rfapi_backend.h" #endif /* * Definitions and external declarations. */ extern struct zclient *zclient; extern int argv_find_and_parse_vpnvx(struct cmd_token **argv, int argc, int *index, afi_t *afi) { int ret = 0; if (argv_find(argv, argc, "vpnv4", index)) { ret = 1; if (afi) *afi = AFI_IP; } else if (argv_find(argv, argc, "vpnv6", index)) { ret = 1; if (afi) *afi = AFI_IP6; } return ret; } uint32_t decode_label(mpls_label_t *label_pnt) { uint32_t l; uint8_t *pnt = (uint8_t *)label_pnt; l = ((uint32_t)*pnt++ << 12); l |= (uint32_t)*pnt++ << 4; l |= (uint32_t)((*pnt & 0xf0) >> 4); return l; } void encode_label(mpls_label_t label, mpls_label_t *label_pnt) { uint8_t *pnt = (uint8_t *)label_pnt; if (pnt == NULL) return; if (label == BGP_PREVENT_VRF_2_VRF_LEAK) { *label_pnt = label; return; } *pnt++ = (label >> 12) & 0xff; *pnt++ = (label >> 4) & 0xff; *pnt++ = ((label << 4) + 1) & 0xff; /* S=1 */ } int bgp_nlri_parse_vpn(struct peer *peer, struct attr *attr, struct bgp_nlri *packet) { uint8_t *pnt; uint8_t *lim; struct prefix p; int psize = 0; int prefixlen; uint16_t type; struct rd_as rd_as; struct rd_ip rd_ip; struct prefix_rd prd = {0}; mpls_label_t label = {0}; afi_t afi; safi_t safi; int addpath_encoded; uint32_t addpath_id; /* Make prefix_rd */ prd.family = AF_UNSPEC; prd.prefixlen = 64; pnt = packet->nlri; lim = pnt + packet->length; afi = packet->afi; safi = packet->safi; addpath_id = 0; addpath_encoded = (CHECK_FLAG(peer->af_cap[afi][safi], PEER_CAP_ADDPATH_AF_RX_ADV) && CHECK_FLAG(peer->af_cap[afi][safi], PEER_CAP_ADDPATH_AF_TX_RCV)); #define VPN_PREFIXLEN_MIN_BYTES (3 + 8) /* label + RD */ for (; pnt < lim; pnt += psize) { /* Clear prefix structure. */ memset(&p, 0, sizeof(struct prefix)); if (addpath_encoded) { /* When packet overflow occurs return immediately. */ if (pnt + BGP_ADDPATH_ID_LEN > lim) return BGP_NLRI_PARSE_ERROR_PACKET_OVERFLOW; memcpy(&addpath_id, pnt, BGP_ADDPATH_ID_LEN); addpath_id = ntohl(addpath_id); pnt += BGP_ADDPATH_ID_LEN; } /* Fetch prefix length. */ prefixlen = *pnt++; p.family = afi2family(packet->afi); psize = PSIZE(prefixlen); if (prefixlen < VPN_PREFIXLEN_MIN_BYTES * 8) { flog_err( EC_BGP_UPDATE_RCV, "%s [Error] Update packet error / VPN (prefix length %d less than VPN min length)", peer->host, prefixlen); return BGP_NLRI_PARSE_ERROR_PREFIX_LENGTH; } /* sanity check against packet data */ if ((pnt + psize) > lim) { flog_err( EC_BGP_UPDATE_RCV, "%s [Error] Update packet error / VPN (prefix length %d exceeds packet size %u)", peer->host, prefixlen, (uint)(lim - pnt)); return BGP_NLRI_PARSE_ERROR_PACKET_OVERFLOW; } /* sanity check against storage for the IP address portion */ if ((psize - VPN_PREFIXLEN_MIN_BYTES) > (ssize_t)sizeof(p.u)) { flog_err( EC_BGP_UPDATE_RCV, "%s [Error] Update packet error / VPN (psize %d exceeds storage size %zu)", peer->host, prefixlen - VPN_PREFIXLEN_MIN_BYTES * 8, sizeof(p.u)); return BGP_NLRI_PARSE_ERROR_PACKET_LENGTH; } /* Sanity check against max bitlen of the address family */ if ((psize - VPN_PREFIXLEN_MIN_BYTES) > prefix_blen(&p)) { flog_err( EC_BGP_UPDATE_RCV, "%s [Error] Update packet error / VPN (psize %d exceeds family (%u) max byte len %u)", peer->host, prefixlen - VPN_PREFIXLEN_MIN_BYTES * 8, p.family, prefix_blen(&p)); return BGP_NLRI_PARSE_ERROR_PACKET_LENGTH; } /* Copy label to prefix. */ memcpy(&label, pnt, BGP_LABEL_BYTES); bgp_set_valid_label(&label); /* Copy routing distinguisher to rd. */ memcpy(&prd.val, pnt + BGP_LABEL_BYTES, 8); /* Decode RD type. */ type = decode_rd_type(pnt + BGP_LABEL_BYTES); switch (type) { case RD_TYPE_AS: decode_rd_as(pnt + 5, &rd_as); break; case RD_TYPE_AS4: decode_rd_as4(pnt + 5, &rd_as); break; case RD_TYPE_IP: decode_rd_ip(pnt + 5, &rd_ip); break; #if ENABLE_BGP_VNC case RD_TYPE_VNC_ETH: break; #endif default: flog_err(EC_BGP_UPDATE_RCV, "Unknown RD type %d", type); break; /* just report */ } p.prefixlen = prefixlen - VPN_PREFIXLEN_MIN_BYTES * 8; /* exclude label & RD */ memcpy(p.u.val, pnt + VPN_PREFIXLEN_MIN_BYTES, psize - VPN_PREFIXLEN_MIN_BYTES); if (attr) { bgp_update(peer, &p, addpath_id, attr, packet->afi, SAFI_MPLS_VPN, ZEBRA_ROUTE_BGP, BGP_ROUTE_NORMAL, &prd, &label, 1, 0, NULL); } else { bgp_withdraw(peer, &p, addpath_id, attr, packet->afi, SAFI_MPLS_VPN, ZEBRA_ROUTE_BGP, BGP_ROUTE_NORMAL, &prd, &label, 1, NULL); } } /* Packet length consistency check. */ if (pnt != lim) { flog_err( EC_BGP_UPDATE_RCV, "%s [Error] Update packet error / VPN (%zu data remaining after parsing)", peer->host, lim - pnt); return BGP_NLRI_PARSE_ERROR_PACKET_LENGTH; } return 0; #undef VPN_PREFIXLEN_MIN_BYTES } /* * This function informs zebra of the label this vrf sets on routes * leaked to VPN. Zebra should install this label in the kernel with * an action of "pop label and then use this vrf's IP FIB to route the PDU." * * Sending this vrf-label association is qualified by a) whether vrf->vpn * exporting is active ("export vpn" is enabled, vpn-policy RD and RT list * are set) and b) whether vpn-policy label is set. * * If any of these conditions do not hold, then we send MPLS_LABEL_NONE * for this vrf, which zebra interprets to mean "delete this vrf-label * association." */ void vpn_leak_zebra_vrf_label_update(struct bgp *bgp, afi_t afi) { mpls_label_t label = MPLS_LABEL_NONE; int debug = BGP_DEBUG(vpn, VPN_LEAK_LABEL); if (bgp->vrf_id == VRF_UNKNOWN) { if (debug) { zlog_debug( "%s: vrf %s: afi %s: vrf_id not set, " "can't set zebra vrf label", __func__, bgp->name_pretty, afi2str(afi)); } return; } if (vpn_leak_to_vpn_active(bgp, afi, NULL)) { label = bgp->vpn_policy[afi].tovpn_label; } if (debug) { zlog_debug("%s: vrf %s: afi %s: setting label %d for vrf id %d", __func__, bgp->name_pretty, afi2str(afi), label, bgp->vrf_id); } if (label == BGP_PREVENT_VRF_2_VRF_LEAK) label = MPLS_LABEL_NONE; zclient_send_vrf_label(zclient, bgp->vrf_id, afi, label, ZEBRA_LSP_BGP); bgp->vpn_policy[afi].tovpn_zebra_vrf_label_last_sent = label; } /* * If zebra tells us vrf has become unconfigured, tell zebra not to * use this label to forward to the vrf anymore */ void vpn_leak_zebra_vrf_label_withdraw(struct bgp *bgp, afi_t afi) { mpls_label_t label = MPLS_LABEL_NONE; int debug = BGP_DEBUG(vpn, VPN_LEAK_LABEL); if (bgp->vrf_id == VRF_UNKNOWN) { if (debug) { zlog_debug( "%s: vrf_id not set, can't delete zebra vrf label", __func__); } return; } if (debug) { zlog_debug("%s: deleting label for vrf %s (id=%d)", __func__, bgp->name_pretty, bgp->vrf_id); } zclient_send_vrf_label(zclient, bgp->vrf_id, afi, label, ZEBRA_LSP_BGP); bgp->vpn_policy[afi].tovpn_zebra_vrf_label_last_sent = label; } int vpn_leak_label_callback( mpls_label_t label, void *labelid, bool allocated) { struct vpn_policy *vp = (struct vpn_policy *)labelid; int debug = BGP_DEBUG(vpn, VPN_LEAK_LABEL); if (debug) zlog_debug("%s: label=%u, allocated=%d", __func__, label, allocated); if (!allocated) { /* * previously-allocated label is now invalid */ if (CHECK_FLAG(vp->flags, BGP_VPN_POLICY_TOVPN_LABEL_AUTO) && (vp->tovpn_label != MPLS_LABEL_NONE)) { vpn_leak_prechange(BGP_VPN_POLICY_DIR_TOVPN, vp->afi, bgp_get_default(), vp->bgp); vp->tovpn_label = MPLS_LABEL_NONE; vpn_leak_postchange(BGP_VPN_POLICY_DIR_TOVPN, vp->afi, bgp_get_default(), vp->bgp); } return 0; } /* * New label allocation */ if (!CHECK_FLAG(vp->flags, BGP_VPN_POLICY_TOVPN_LABEL_AUTO)) { /* * not currently configured for auto label, reject allocation */ return -1; } if (vp->tovpn_label != MPLS_LABEL_NONE) { if (label == vp->tovpn_label) { /* already have same label, accept but do nothing */ return 0; } /* Shouldn't happen: different label allocation */ flog_err(EC_BGP_LABEL, "%s: %s had label %u but got new assignment %u", __func__, vp->bgp->name_pretty, vp->tovpn_label, label); /* use new one */ } vpn_leak_prechange(BGP_VPN_POLICY_DIR_TOVPN, vp->afi, bgp_get_default(), vp->bgp); vp->tovpn_label = label; vpn_leak_postchange(BGP_VPN_POLICY_DIR_TOVPN, vp->afi, bgp_get_default(), vp->bgp); return 0; } static int ecom_intersect(struct ecommunity *e1, struct ecommunity *e2) { int i; int j; if (!e1 || !e2) return 0; for (i = 0; i < e1->size; ++i) { for (j = 0; j < e2->size; ++j) { if (!memcmp(e1->val + (i * ECOMMUNITY_SIZE), e2->val + (j * ECOMMUNITY_SIZE), ECOMMUNITY_SIZE)) { return 1; } } } return 0; } static bool labels_same(struct bgp_path_info *bpi, mpls_label_t *label, uint32_t n) { uint32_t i; if (!bpi->extra) { if (!n) return true; else return false; } if (n != bpi->extra->num_labels) return false; for (i = 0; i < n; ++i) { if (label[i] != bpi->extra->label[i]) return false; } return true; } /* * make encoded route labels match specified encoded label set */ static void setlabels(struct bgp_path_info *bpi, mpls_label_t *label, /* array of labels */ uint32_t num_labels) { if (num_labels) assert(label); assert(num_labels <= BGP_MAX_LABELS); if (!num_labels) { if (bpi->extra) bpi->extra->num_labels = 0; return; } struct bgp_path_info_extra *extra = bgp_path_info_extra_get(bpi); uint32_t i; for (i = 0; i < num_labels; ++i) { extra->label[i] = label[i]; if (!bgp_is_valid_label(&label[i])) { bgp_set_valid_label(&extra->label[i]); } } extra->num_labels = num_labels; } /* * returns pointer to new bgp_path_info upon success */ static struct bgp_path_info * leak_update(struct bgp *bgp, /* destination bgp instance */ struct bgp_node *bn, struct attr *new_attr, /* already interned */ afi_t afi, safi_t safi, struct bgp_path_info *source_bpi, mpls_label_t *label, uint32_t num_labels, void *parent, struct bgp *bgp_orig, struct prefix *nexthop_orig, int nexthop_self_flag, int debug) { struct prefix *p = &bn->p; struct bgp_path_info *bpi; struct bgp_path_info *bpi_ultimate; struct bgp_path_info *new; char buf_prefix[PREFIX_STRLEN]; if (debug) { prefix2str(&bn->p, buf_prefix, sizeof(buf_prefix)); zlog_debug("%s: entry: leak-to=%s, p=%s, type=%d, sub_type=%d", __func__, bgp->name_pretty, buf_prefix, source_bpi->type, source_bpi->sub_type); } /* * Routes that are redistributed into BGP from zebra do not get * nexthop tracking. However, if those routes are subsequently * imported to other RIBs within BGP, the leaked routes do not * carry the original BGP_ROUTE_REDISTRIBUTE sub_type. Therefore, * in order to determine if the route we are currently leaking * should have nexthop tracking, we must find the ultimate * parent so we can check its sub_type. * * As of now, source_bpi may at most be a second-generation route * (only one hop back to ultimate parent for vrf-vpn-vrf scheme). * Using a loop here supports more complex intra-bgp import-export * schemes that could be implemented in the future. * */ for (bpi_ultimate = source_bpi; bpi_ultimate->extra && bpi_ultimate->extra->parent; bpi_ultimate = bpi_ultimate->extra->parent) ; /* * match parent */ for (bpi = bgp_node_get_bgp_path_info(bn); bpi; bpi = bpi->next) { if (bpi->extra && bpi->extra->parent == parent) break; } if (bpi) { bool labelssame = labels_same(bpi, label, num_labels); if (attrhash_cmp(bpi->attr, new_attr) && labelssame && !CHECK_FLAG(bpi->flags, BGP_PATH_REMOVED)) { bgp_attr_unintern(&new_attr); if (debug) zlog_debug( "%s: ->%s: %s: Found route, no change", __func__, bgp->name_pretty, buf_prefix); return NULL; } /* attr is changed */ bgp_path_info_set_flag(bn, bpi, BGP_PATH_ATTR_CHANGED); /* Rewrite BGP route information. */ if (CHECK_FLAG(bpi->flags, BGP_PATH_REMOVED)) bgp_path_info_restore(bn, bpi); else bgp_aggregate_decrement(bgp, p, bpi, afi, safi); bgp_attr_unintern(&bpi->attr); bpi->attr = new_attr; bpi->uptime = bgp_clock(); /* * rewrite labels */ if (!labelssame) setlabels(bpi, label, num_labels); if (nexthop_self_flag) bgp_path_info_set_flag(bn, bpi, BGP_PATH_ANNC_NH_SELF); struct bgp *bgp_nexthop = bgp; int nh_valid; if (bpi->extra && bpi->extra->bgp_orig) bgp_nexthop = bpi->extra->bgp_orig; /* * No nexthop tracking for redistributed routes or for * EVPN-imported routes that get leaked. */ if (bpi_ultimate->sub_type == BGP_ROUTE_REDISTRIBUTE || is_pi_family_evpn(bpi_ultimate)) nh_valid = 1; else /* * TBD do we need to do anything about the * 'connected' parameter? */ nh_valid = bgp_find_or_add_nexthop(bgp, bgp_nexthop, afi, bpi, NULL, 0); if (debug) zlog_debug("%s: nexthop is %svalid (in vrf %s)", __func__, (nh_valid ? "" : "not "), bgp_nexthop->name_pretty); if (nh_valid) bgp_path_info_set_flag(bn, bpi, BGP_PATH_VALID); /* Process change. */ bgp_aggregate_increment(bgp, p, bpi, afi, safi); bgp_process(bgp, bn, afi, safi); bgp_unlock_node(bn); if (debug) zlog_debug("%s: ->%s: %s Found route, changed attr", __func__, bgp->name_pretty, buf_prefix); return bpi; } new = info_make(ZEBRA_ROUTE_BGP, BGP_ROUTE_IMPORTED, 0, bgp->peer_self, new_attr, bn); if (nexthop_self_flag) bgp_path_info_set_flag(bn, new, BGP_PATH_ANNC_NH_SELF); bgp_path_info_extra_get(new); if (num_labels) setlabels(new, label, num_labels); new->extra->parent = bgp_path_info_lock(parent); bgp_lock_node((struct bgp_node *)((struct bgp_path_info *)parent)->net); if (bgp_orig) new->extra->bgp_orig = bgp_lock(bgp_orig); if (nexthop_orig) new->extra->nexthop_orig = *nexthop_orig; /* * nexthop tracking for unicast routes */ struct bgp *bgp_nexthop = bgp; int nh_valid; if (new->extra->bgp_orig) bgp_nexthop = new->extra->bgp_orig; /* * No nexthop tracking for redistributed routes because * their originating protocols will do the tracking and * withdraw those routes if the nexthops become unreachable * This also holds good for EVPN-imported routes that get * leaked. */ if (bpi_ultimate->sub_type == BGP_ROUTE_REDISTRIBUTE || is_pi_family_evpn(bpi_ultimate)) nh_valid = 1; else /* * TBD do we need to do anything about the * 'connected' parameter? */ nh_valid = bgp_find_or_add_nexthop(bgp, bgp_nexthop, afi, new, NULL, 0); if (debug) zlog_debug("%s: nexthop is %svalid (in vrf %s)", __func__, (nh_valid ? "" : "not "), bgp_nexthop->name_pretty); if (nh_valid) bgp_path_info_set_flag(bn, new, BGP_PATH_VALID); bgp_aggregate_increment(bgp, p, new, afi, safi); bgp_path_info_add(bn, new); bgp_unlock_node(bn); bgp_process(bgp, bn, afi, safi); if (debug) zlog_debug("%s: ->%s: %s: Added new route", __func__, bgp->name_pretty, buf_prefix); return new; } /* cf vnc_import_bgp_add_route_mode_nvegroup() and add_vnc_route() */ void vpn_leak_from_vrf_update(struct bgp *bgp_vpn, /* to */ struct bgp *bgp_vrf, /* from */ struct bgp_path_info *path_vrf) /* route */ { int debug = BGP_DEBUG(vpn, VPN_LEAK_FROM_VRF); struct prefix *p = &path_vrf->net->p; afi_t afi = family2afi(p->family); struct attr static_attr = {0}; struct attr *new_attr = NULL; safi_t safi = SAFI_MPLS_VPN; mpls_label_t label_val; mpls_label_t label; struct bgp_node *bn; const char *debugmsg; int nexthop_self_flag = 0; if (debug) zlog_debug("%s: from vrf %s", __func__, bgp_vrf->name_pretty); if (debug && path_vrf->attr->ecommunity) { char *s = ecommunity_ecom2str(path_vrf->attr->ecommunity, ECOMMUNITY_FORMAT_ROUTE_MAP, 0); zlog_debug("%s: %s path_vrf->type=%d, EC{%s}", __func__, bgp_vrf->name, path_vrf->type, s); XFREE(MTYPE_ECOMMUNITY_STR, s); } if (!bgp_vpn) return; if (!afi) { if (debug) zlog_debug("%s: can't get afi of prefix", __func__); return; } /* Is this route exportable into the VPN table? */ if (!is_route_injectable_into_vpn(path_vrf)) return; if (!vpn_leak_to_vpn_active(bgp_vrf, afi, &debugmsg)) { if (debug) zlog_debug("%s: %s skipping: %s", __func__, bgp_vrf->name, debugmsg); return; } /* shallow copy */ static_attr = *path_vrf->attr; /* * route map handling */ if (bgp_vrf->vpn_policy[afi].rmap[BGP_VPN_POLICY_DIR_TOVPN]) { struct bgp_path_info info; route_map_result_t ret; memset(&info, 0, sizeof(info)); info.peer = bgp_vpn->peer_self; info.attr = &static_attr; ret = route_map_apply( bgp_vrf->vpn_policy[afi].rmap[BGP_VPN_POLICY_DIR_TOVPN], p, RMAP_BGP, &info); if (RMAP_DENYMATCH == ret) { bgp_attr_flush(&static_attr); /* free any added parts */ if (debug) zlog_debug( "%s: vrf %s route map \"%s\" says DENY, returning", __func__, bgp_vrf->name_pretty, bgp_vrf->vpn_policy[afi] .rmap[BGP_VPN_POLICY_DIR_TOVPN] ->name); return; } } if (debug && static_attr.ecommunity) { char *s = ecommunity_ecom2str(static_attr.ecommunity, ECOMMUNITY_FORMAT_ROUTE_MAP, 0); zlog_debug("%s: post route map static_attr.ecommunity{%s}", __func__, s); XFREE(MTYPE_ECOMMUNITY_STR, s); } /* * Add the vpn-policy rt-list */ struct ecommunity *old_ecom; struct ecommunity *new_ecom; old_ecom = static_attr.ecommunity; if (old_ecom) { new_ecom = ecommunity_merge( ecommunity_dup(old_ecom), bgp_vrf->vpn_policy[afi] .rtlist[BGP_VPN_POLICY_DIR_TOVPN]); if (!old_ecom->refcnt) ecommunity_free(&old_ecom); } else { new_ecom = ecommunity_dup( bgp_vrf->vpn_policy[afi] .rtlist[BGP_VPN_POLICY_DIR_TOVPN]); } static_attr.ecommunity = new_ecom; SET_FLAG(static_attr.flag, ATTR_FLAG_BIT(BGP_ATTR_EXT_COMMUNITIES)); if (debug && static_attr.ecommunity) { char *s = ecommunity_ecom2str(static_attr.ecommunity, ECOMMUNITY_FORMAT_ROUTE_MAP, 0); zlog_debug("%s: post merge static_attr.ecommunity{%s}", __func__, s); XFREE(MTYPE_ECOMMUNITY_STR, s); } /* Nexthop */ /* if policy nexthop not set, use 0 */ if (CHECK_FLAG(bgp_vrf->vpn_policy[afi].flags, BGP_VPN_POLICY_TOVPN_NEXTHOP_SET)) { struct prefix *nexthop = &bgp_vrf->vpn_policy[afi].tovpn_nexthop; switch (nexthop->family) { case AF_INET: /* prevent mp_nexthop_global_in <- self in bgp_route.c */ static_attr.nexthop.s_addr = nexthop->u.prefix4.s_addr; static_attr.mp_nexthop_global_in = nexthop->u.prefix4; static_attr.mp_nexthop_len = 4; break; case AF_INET6: static_attr.mp_nexthop_global = nexthop->u.prefix6; static_attr.mp_nexthop_len = 16; break; default: assert(0); } } else { if (!CHECK_FLAG(bgp_vrf->af_flags[afi][SAFI_UNICAST], BGP_CONFIG_VRF_TO_VRF_EXPORT)) { if (afi == AFI_IP) { /* * For ipv4, copy to multiprotocol * nexthop field */ static_attr.mp_nexthop_global_in = static_attr.nexthop; static_attr.mp_nexthop_len = 4; /* * XXX Leave static_attr.nexthop * intact for NHT */ static_attr.flag &= ~ATTR_FLAG_BIT(BGP_ATTR_NEXT_HOP); } } else { /* Update based on next-hop family to account for * RFC 5549 (BGP unnumbered) scenario. Note that * specific action is only needed for the case of * IPv4 nexthops as the attr has been copied * otherwise. */ if (afi == AFI_IP && !BGP_ATTR_NEXTHOP_AFI_IP6(path_vrf->attr)) { static_attr.mp_nexthop_global_in.s_addr = static_attr.nexthop.s_addr; static_attr.mp_nexthop_len = 4; static_attr.flag |= ATTR_FLAG_BIT(BGP_ATTR_NEXT_HOP); } } nexthop_self_flag = 1; } label_val = bgp_vrf->vpn_policy[afi].tovpn_label; if (label_val == MPLS_LABEL_NONE) { encode_label(MPLS_LABEL_IMPLICIT_NULL, &label); } else { encode_label(label_val, &label); } /* Set originator ID to "me" */ SET_FLAG(static_attr.flag, ATTR_FLAG_BIT(BGP_ATTR_ORIGINATOR_ID)); static_attr.originator_id = bgp_vpn->router_id; new_attr = bgp_attr_intern( &static_attr); /* hashed refcounted everything */ bgp_attr_flush(&static_attr); /* free locally-allocated parts */ if (debug && new_attr->ecommunity) { char *s = ecommunity_ecom2str(new_attr->ecommunity, ECOMMUNITY_FORMAT_ROUTE_MAP, 0); zlog_debug("%s: new_attr->ecommunity{%s}", __func__, s); XFREE(MTYPE_ECOMMUNITY_STR, s); } /* Now new_attr is an allocated interned attr */ bn = bgp_afi_node_get(bgp_vpn->rib[afi][safi], afi, safi, p, &(bgp_vrf->vpn_policy[afi].tovpn_rd)); struct bgp_path_info *new_info; new_info = leak_update(bgp_vpn, bn, new_attr, afi, safi, path_vrf, &label, 1, path_vrf, bgp_vrf, NULL, nexthop_self_flag, debug); /* * Routes actually installed in the vpn RIB must also be * offered to all vrfs (because now they originate from * the vpn RIB). * * Acceptance into other vrfs depends on rt-lists. * Originating vrf will not accept the looped back route * because of loop checking. */ if (new_info) vpn_leak_to_vrf_update(bgp_vrf, new_info); } void vpn_leak_from_vrf_withdraw(struct bgp *bgp_vpn, /* to */ struct bgp *bgp_vrf, /* from */ struct bgp_path_info *path_vrf) /* route */ { int debug = BGP_DEBUG(vpn, VPN_LEAK_FROM_VRF); struct prefix *p = &path_vrf->net->p; afi_t afi = family2afi(p->family); safi_t safi = SAFI_MPLS_VPN; struct bgp_path_info *bpi; struct bgp_node *bn; const char *debugmsg; char buf_prefix[PREFIX_STRLEN]; if (debug) { prefix2str(p, buf_prefix, sizeof(buf_prefix)); zlog_debug( "%s: entry: leak-from=%s, p=%s, type=%d, sub_type=%d", __func__, bgp_vrf->name_pretty, buf_prefix, path_vrf->type, path_vrf->sub_type); } if (!bgp_vpn) return; if (!afi) { if (debug) zlog_debug("%s: can't get afi of prefix", __func__); return; } /* Is this route exportable into the VPN table? */ if (!is_route_injectable_into_vpn(path_vrf)) return; if (!vpn_leak_to_vpn_active(bgp_vrf, afi, &debugmsg)) { if (debug) zlog_debug("%s: skipping: %s", __func__, debugmsg); return; } if (debug) zlog_debug("%s: withdrawing (path_vrf=%p)", __func__, path_vrf); bn = bgp_afi_node_get(bgp_vpn->rib[afi][safi], afi, safi, p, &(bgp_vrf->vpn_policy[afi].tovpn_rd)); if (!bn) return; /* * vrf -> vpn * match original bpi imported from */ for (bpi = bgp_node_get_bgp_path_info(bn); bpi; bpi = bpi->next) { if (bpi->extra && bpi->extra->parent == path_vrf) { break; } } if (bpi) { /* withdraw from looped vrfs as well */ vpn_leak_to_vrf_withdraw(bgp_vpn, bpi); bgp_aggregate_decrement(bgp_vpn, p, bpi, afi, safi); bgp_path_info_delete(bn, bpi); bgp_process(bgp_vpn, bn, afi, safi); } bgp_unlock_node(bn); } void vpn_leak_from_vrf_withdraw_all(struct bgp *bgp_vpn, /* to */ struct bgp *bgp_vrf, /* from */ afi_t afi) { int debug = BGP_DEBUG(vpn, VPN_LEAK_FROM_VRF); struct bgp_node *prn; safi_t safi = SAFI_MPLS_VPN; /* * Walk vpn table, delete bpi with bgp_orig == bgp_vrf */ for (prn = bgp_table_top(bgp_vpn->rib[afi][safi]); prn; prn = bgp_route_next(prn)) { struct bgp_table *table; struct bgp_node *bn; struct bgp_path_info *bpi; /* This is the per-RD table of prefixes */ table = bgp_node_get_bgp_table_info(prn); if (!table) continue; for (bn = bgp_table_top(table); bn; bn = bgp_route_next(bn)) { char buf[PREFIX2STR_BUFFER]; bpi = bgp_node_get_bgp_path_info(bn); if (debug && bpi) { zlog_debug( "%s: looking at prefix %s", __func__, prefix2str(&bn->p, buf, sizeof(buf))); } for (; bpi; bpi = bpi->next) { if (debug) zlog_debug("%s: type %d, sub_type %d", __func__, bpi->type, bpi->sub_type); if (bpi->sub_type != BGP_ROUTE_IMPORTED) continue; if (!bpi->extra) continue; if ((struct bgp *)bpi->extra->bgp_orig == bgp_vrf) { /* delete route */ if (debug) zlog_debug("%s: deleting it", __func__); bgp_aggregate_decrement(bgp_vpn, &bn->p, bpi, afi, safi); bgp_path_info_delete(bn, bpi); bgp_process(bgp_vpn, bn, afi, safi); } } } } } void vpn_leak_from_vrf_update_all(struct bgp *bgp_vpn, /* to */ struct bgp *bgp_vrf, /* from */ afi_t afi) { struct bgp_node *bn; struct bgp_path_info *bpi; int debug = BGP_DEBUG(vpn, VPN_LEAK_FROM_VRF); if (debug) zlog_debug("%s: entry, afi=%d, vrf=%s", __func__, afi, bgp_vrf->name_pretty); for (bn = bgp_table_top(bgp_vrf->rib[afi][SAFI_UNICAST]); bn; bn = bgp_route_next(bn)) { if (debug) zlog_debug("%s: node=%p", __func__, bn); for (bpi = bgp_node_get_bgp_path_info(bn); bpi; bpi = bpi->next) { if (debug) zlog_debug( "%s: calling vpn_leak_from_vrf_update", __func__); vpn_leak_from_vrf_update(bgp_vpn, bgp_vrf, bpi); } } } static void vpn_leak_to_vrf_update_onevrf(struct bgp *bgp_vrf, /* to */ struct bgp *bgp_vpn, /* from */ struct bgp_path_info *path_vpn) /* route */ { struct prefix *p = &path_vpn->net->p; afi_t afi = family2afi(p->family); struct attr static_attr = {0}; struct attr *new_attr = NULL; struct bgp_node *bn; safi_t safi = SAFI_UNICAST; const char *debugmsg; struct prefix nexthop_orig; mpls_label_t *pLabels = NULL; uint32_t num_labels = 0; int nexthop_self_flag = 1; struct bgp_path_info *bpi_ultimate = NULL; int origin_local = 0; struct bgp *src_vrf; int debug = BGP_DEBUG(vpn, VPN_LEAK_TO_VRF); if (!vpn_leak_from_vpn_active(bgp_vrf, afi, &debugmsg)) { if (debug) zlog_debug("%s: skipping: %s", __func__, debugmsg); return; } /* Check for intersection of route targets */ if (!ecom_intersect( bgp_vrf->vpn_policy[afi].rtlist[BGP_VPN_POLICY_DIR_FROMVPN], path_vpn->attr->ecommunity)) { return; } if (debug) { char buf_prefix[PREFIX_STRLEN]; prefix2str(p, buf_prefix, sizeof(buf_prefix)); zlog_debug("%s: updating %s to vrf %s", __func__, buf_prefix, bgp_vrf->name_pretty); } /* shallow copy */ static_attr = *path_vpn->attr; /* * Nexthop: stash and clear * * Nexthop is valid in context of VPN core, but not in destination vrf. * Stash it for later label resolution by vrf ingress path and then * overwrite with 0, i.e., "me", for the sake of vrf advertisement. */ uint8_t nhfamily = NEXTHOP_FAMILY(path_vpn->attr->mp_nexthop_len); memset(&nexthop_orig, 0, sizeof(nexthop_orig)); nexthop_orig.family = nhfamily; switch (nhfamily) { case AF_INET: /* save */ nexthop_orig.u.prefix4 = path_vpn->attr->mp_nexthop_global_in; nexthop_orig.prefixlen = 32; if (CHECK_FLAG(bgp_vrf->af_flags[afi][safi], BGP_CONFIG_VRF_TO_VRF_IMPORT)) { static_attr.nexthop.s_addr = nexthop_orig.u.prefix4.s_addr; static_attr.mp_nexthop_global_in = path_vpn->attr->mp_nexthop_global_in; static_attr.mp_nexthop_len = path_vpn->attr->mp_nexthop_len; } static_attr.flag |= ATTR_FLAG_BIT(BGP_ATTR_NEXT_HOP); break; case AF_INET6: /* save */ nexthop_orig.u.prefix6 = path_vpn->attr->mp_nexthop_global; nexthop_orig.prefixlen = 128; if (CHECK_FLAG(bgp_vrf->af_flags[afi][safi], BGP_CONFIG_VRF_TO_VRF_IMPORT)) { static_attr.mp_nexthop_global = nexthop_orig.u.prefix6; } break; } /* * route map handling */ if (bgp_vrf->vpn_policy[afi].rmap[BGP_VPN_POLICY_DIR_FROMVPN]) { struct bgp_path_info info; route_map_result_t ret; memset(&info, 0, sizeof(info)); info.peer = bgp_vrf->peer_self; info.attr = &static_attr; info.extra = path_vpn->extra; /* Used for source-vrf filter */ ret = route_map_apply(bgp_vrf->vpn_policy[afi] .rmap[BGP_VPN_POLICY_DIR_FROMVPN], p, RMAP_BGP, &info); if (RMAP_DENYMATCH == ret) { bgp_attr_flush(&static_attr); /* free any added parts */ if (debug) zlog_debug( "%s: vrf %s vpn-policy route map \"%s\" says DENY, returning", __func__, bgp_vrf->name_pretty, bgp_vrf->vpn_policy[afi] .rmap[BGP_VPN_POLICY_DIR_FROMVPN] ->name); return; } /* * if route-map changed nexthop, don't nexthop-self on output */ if (!CHECK_FLAG(static_attr.rmap_change_flags, BATTR_RMAP_NEXTHOP_UNCHANGED)) nexthop_self_flag = 0; } new_attr = bgp_attr_intern(&static_attr); bgp_attr_flush(&static_attr); bn = bgp_afi_node_get(bgp_vrf->rib[afi][safi], afi, safi, p, NULL); /* * ensure labels are copied * * However, there is a special case: if the route originated in * another local VRF (as opposed to arriving via VPN), then the * nexthop is reached by hairpinning through this router (me) * using IP forwarding only (no LSP). Therefore, the route * imported to the VRF should not have labels attached. Note * that nexthop tracking is also involved: eliminating the * labels for these routes enables the non-labeled nexthops * from the originating VRF to be considered valid for this route. */ if (!CHECK_FLAG(bgp_vrf->af_flags[afi][safi], BGP_CONFIG_VRF_TO_VRF_IMPORT)) { /* work back to original route */ for (bpi_ultimate = path_vpn; bpi_ultimate->extra && bpi_ultimate->extra->parent; bpi_ultimate = bpi_ultimate->extra->parent) ; /* * if original route was unicast, * then it did not arrive over vpn */ if (bpi_ultimate->net) { struct bgp_table *table; table = bgp_node_table(bpi_ultimate->net); if (table && (table->safi == SAFI_UNICAST)) origin_local = 1; } /* copy labels */ if (!origin_local && path_vpn->extra && path_vpn->extra->num_labels) { num_labels = path_vpn->extra->num_labels; if (num_labels > BGP_MAX_LABELS) num_labels = BGP_MAX_LABELS; pLabels = path_vpn->extra->label; } } if (debug) { char buf_prefix[PREFIX_STRLEN]; prefix2str(p, buf_prefix, sizeof(buf_prefix)); zlog_debug("%s: pfx %s: num_labels %d", __func__, buf_prefix, num_labels); } /* * For VRF-2-VRF route-leaking, * the source will be the originating VRF. */ if (path_vpn->extra && path_vpn->extra->bgp_orig) src_vrf = path_vpn->extra->bgp_orig; else src_vrf = bgp_vpn; leak_update(bgp_vrf, bn, new_attr, afi, safi, path_vpn, pLabels, num_labels, path_vpn, /* parent */ src_vrf, &nexthop_orig, nexthop_self_flag, debug); } void vpn_leak_to_vrf_update(struct bgp *bgp_vpn, /* from */ struct bgp_path_info *path_vpn) /* route */ { struct listnode *mnode, *mnnode; struct bgp *bgp; int debug = BGP_DEBUG(vpn, VPN_LEAK_TO_VRF); if (debug) zlog_debug("%s: start (path_vpn=%p)", __func__, path_vpn); /* Loop over VRFs */ for (ALL_LIST_ELEMENTS(bm->bgp, mnode, mnnode, bgp)) { if (!path_vpn->extra || path_vpn->extra->bgp_orig != bgp) { /* no loop */ vpn_leak_to_vrf_update_onevrf(bgp, bgp_vpn, path_vpn); } } } void vpn_leak_to_vrf_withdraw(struct bgp *bgp_vpn, /* from */ struct bgp_path_info *path_vpn) /* route */ { struct prefix *p; afi_t afi; safi_t safi = SAFI_UNICAST; struct bgp *bgp; struct listnode *mnode, *mnnode; struct bgp_node *bn; struct bgp_path_info *bpi; const char *debugmsg; char buf_prefix[PREFIX_STRLEN]; int debug = BGP_DEBUG(vpn, VPN_LEAK_TO_VRF); if (debug) { prefix2str(&path_vpn->net->p, buf_prefix, sizeof(buf_prefix)); zlog_debug("%s: entry: p=%s, type=%d, sub_type=%d", __func__, buf_prefix, path_vpn->type, path_vpn->sub_type); } if (debug) zlog_debug("%s: start (path_vpn=%p)", __func__, path_vpn); if (!path_vpn->net) { #if ENABLE_BGP_VNC /* BGP_ROUTE_RFP routes do not have path_vpn->net set (yet) */ if (path_vpn->type == ZEBRA_ROUTE_BGP && path_vpn->sub_type == BGP_ROUTE_RFP) { return; } #endif if (debug) zlog_debug( "%s: path_vpn->net unexpectedly NULL, no prefix, bailing", __func__); return; } p = &path_vpn->net->p; afi = family2afi(p->family); /* Loop over VRFs */ for (ALL_LIST_ELEMENTS(bm->bgp, mnode, mnnode, bgp)) { if (!vpn_leak_from_vpn_active(bgp, afi, &debugmsg)) { if (debug) zlog_debug("%s: skipping: %s", __func__, debugmsg); continue; } /* Check for intersection of route targets */ if (!ecom_intersect(bgp->vpn_policy[afi] .rtlist[BGP_VPN_POLICY_DIR_FROMVPN], path_vpn->attr->ecommunity)) { continue; } if (debug) zlog_debug("%s: withdrawing from vrf %s", __func__, bgp->name_pretty); bn = bgp_afi_node_get(bgp->rib[afi][safi], afi, safi, p, NULL); for (bpi = bgp_node_get_bgp_path_info(bn); bpi; bpi = bpi->next) { if (bpi->extra && (struct bgp_path_info *)bpi->extra->parent == path_vpn) { break; } } if (bpi) { if (debug) zlog_debug("%s: deleting bpi %p", __func__, bpi); bgp_aggregate_decrement(bgp, p, bpi, afi, safi); bgp_path_info_delete(bn, bpi); bgp_process(bgp, bn, afi, safi); } bgp_unlock_node(bn); } } void vpn_leak_to_vrf_withdraw_all(struct bgp *bgp_vrf, /* to */ afi_t afi) { struct bgp_node *bn; struct bgp_path_info *bpi; safi_t safi = SAFI_UNICAST; int debug = BGP_DEBUG(vpn, VPN_LEAK_TO_VRF); if (debug) zlog_debug("%s: entry", __func__); /* * Walk vrf table, delete bpi with bgp_orig in a different vrf */ for (bn = bgp_table_top(bgp_vrf->rib[afi][safi]); bn; bn = bgp_route_next(bn)) { for (bpi = bgp_node_get_bgp_path_info(bn); bpi; bpi = bpi->next) { if (bpi->extra && bpi->extra->bgp_orig != bgp_vrf && bpi->extra->parent && is_pi_family_vpn(bpi->extra->parent)) { /* delete route */ bgp_aggregate_decrement(bgp_vrf, &bn->p, bpi, afi, safi); bgp_path_info_delete(bn, bpi); bgp_process(bgp_vrf, bn, afi, safi); } } } } void vpn_leak_to_vrf_update_all(struct bgp *bgp_vrf, /* to */ struct bgp *bgp_vpn, /* from */ afi_t afi) { struct prefix_rd prd; struct bgp_node *prn; safi_t safi = SAFI_MPLS_VPN; assert(bgp_vpn); /* * Walk vpn table */ for (prn = bgp_table_top(bgp_vpn->rib[afi][safi]); prn; prn = bgp_route_next(prn)) { struct bgp_table *table; struct bgp_node *bn; struct bgp_path_info *bpi; memset(&prd, 0, sizeof(prd)); prd.family = AF_UNSPEC; prd.prefixlen = 64; memcpy(prd.val, prn->p.u.val, 8); /* This is the per-RD table of prefixes */ table = bgp_node_get_bgp_table_info(prn); if (!table) continue; for (bn = bgp_table_top(table); bn; bn = bgp_route_next(bn)) { for (bpi = bgp_node_get_bgp_path_info(bn); bpi; bpi = bpi->next) { if (bpi->extra && bpi->extra->bgp_orig == bgp_vrf) continue; vpn_leak_to_vrf_update_onevrf(bgp_vrf, bgp_vpn, bpi); } } } } /* * This function is called for definition/deletion/change to a route-map */ static void vpn_policy_routemap_update(struct bgp *bgp, const char *rmap_name) { int debug = BGP_DEBUG(vpn, VPN_LEAK_RMAP_EVENT); afi_t afi; struct route_map *rmap; if (bgp->inst_type != BGP_INSTANCE_TYPE_DEFAULT && bgp->inst_type != BGP_INSTANCE_TYPE_VRF) { return; } rmap = route_map_lookup_by_name(rmap_name); /* NULL if deleted */ for (afi = 0; afi < AFI_MAX; ++afi) { if (bgp->vpn_policy[afi].rmap_name[BGP_VPN_POLICY_DIR_TOVPN] && !strcmp(rmap_name, bgp->vpn_policy[afi] .rmap_name[BGP_VPN_POLICY_DIR_TOVPN])) { if (debug) zlog_debug( "%s: rmap \"%s\" matches vrf-policy tovpn for as %d afi %s", __func__, rmap_name, bgp->as, afi2str(afi)); vpn_leak_prechange(BGP_VPN_POLICY_DIR_TOVPN, afi, bgp_get_default(), bgp); if (debug) zlog_debug("%s: after vpn_leak_prechange", __func__); /* in case of definition/deletion */ bgp->vpn_policy[afi].rmap[BGP_VPN_POLICY_DIR_TOVPN] = rmap; vpn_leak_postchange(BGP_VPN_POLICY_DIR_TOVPN, afi, bgp_get_default(), bgp); if (debug) zlog_debug("%s: after vpn_leak_postchange", __func__); } if (bgp->vpn_policy[afi].rmap_name[BGP_VPN_POLICY_DIR_FROMVPN] && !strcmp(rmap_name, bgp->vpn_policy[afi] .rmap_name[BGP_VPN_POLICY_DIR_FROMVPN])) { if (debug) { zlog_debug("%s: rmap \"%s\" matches vrf-policy fromvpn for as %d afi %s", __func__, rmap_name, bgp->as, afi2str(afi)); } vpn_leak_prechange(BGP_VPN_POLICY_DIR_FROMVPN, afi, bgp_get_default(), bgp); /* in case of definition/deletion */ bgp->vpn_policy[afi].rmap[BGP_VPN_POLICY_DIR_FROMVPN] = rmap; vpn_leak_postchange(BGP_VPN_POLICY_DIR_FROMVPN, afi, bgp_get_default(), bgp); } } } /* This API is used during router-id change, reflect VPNs * auto RD and RT values and readvertise routes to VPN table. */ void vpn_handle_router_id_update(struct bgp *bgp, bool withdraw, bool is_config) { afi_t afi; int debug; char *vname; const char *export_name; char buf[RD_ADDRSTRLEN]; struct bgp *bgp_import; struct listnode *node; struct ecommunity *ecom; vpn_policy_direction_t idir, edir; if (bgp->inst_type != BGP_INSTANCE_TYPE_DEFAULT && bgp->inst_type != BGP_INSTANCE_TYPE_VRF) return; export_name = bgp->name ? bgp->name : VRF_DEFAULT_NAME; debug = (BGP_DEBUG(vpn, VPN_LEAK_TO_VRF) | BGP_DEBUG(vpn, VPN_LEAK_FROM_VRF)); idir = BGP_VPN_POLICY_DIR_FROMVPN; edir = BGP_VPN_POLICY_DIR_TOVPN; for (afi = 0; afi < AFI_MAX; ++afi) { if (!vpn_leak_to_vpn_active(bgp, afi, NULL)) continue; if (withdraw) { vpn_leak_prechange(BGP_VPN_POLICY_DIR_TOVPN, afi, bgp_get_default(), bgp); if (debug) zlog_debug("%s: %s after to_vpn vpn_leak_prechange", __func__, export_name); /* Remove import RT from VRFs */ ecom = bgp->vpn_policy[afi].rtlist[edir]; for (ALL_LIST_ELEMENTS_RO(bgp->vpn_policy[afi]. export_vrf, node, vname)) { bgp_import = bgp_lookup_by_name(vname); if (!bgp_import) continue; ecommunity_del_val(bgp_import->vpn_policy[afi]. rtlist[idir], (struct ecommunity_val *)ecom->val); } } else { /* * Router-id changes that are not explicit config * changes should not replace configured RD/RT. */ if (!is_config) { if (CHECK_FLAG(bgp->vpn_policy[afi].flags, BGP_VPN_POLICY_TOVPN_RD_SET)) { if (debug) zlog_debug("%s: auto router-id change skipped", __func__); goto postchange; } } /* New router-id derive auto RD and RT and export * to VPN */ form_auto_rd(bgp->router_id, bgp->vrf_rd_id, &bgp->vrf_prd_auto); bgp->vpn_policy[afi].tovpn_rd = bgp->vrf_prd_auto; prefix_rd2str(&bgp->vpn_policy[afi].tovpn_rd, buf, sizeof(buf)); bgp->vpn_policy[afi].rtlist[edir] = ecommunity_str2com(buf, ECOMMUNITY_ROUTE_TARGET, 0); /* Update import_vrf rt_list */ ecom = bgp->vpn_policy[afi].rtlist[edir]; for (ALL_LIST_ELEMENTS_RO(bgp->vpn_policy[afi]. export_vrf, node, vname)) { bgp_import = bgp_lookup_by_name(vname); if (!bgp_import) continue; if (bgp_import->vpn_policy[afi].rtlist[idir]) bgp_import->vpn_policy[afi].rtlist[idir] = ecommunity_merge( bgp_import->vpn_policy[afi] .rtlist[idir], ecom); else bgp_import->vpn_policy[afi].rtlist[idir] = ecommunity_dup(ecom); } postchange: /* Update routes to VPN */ vpn_leak_postchange(BGP_VPN_POLICY_DIR_TOVPN, afi, bgp_get_default(), bgp); if (debug) zlog_debug("%s: %s after to_vpn vpn_leak_postchange", __func__, export_name); } } } void vpn_policy_routemap_event(const char *rmap_name) { int debug = BGP_DEBUG(vpn, VPN_LEAK_RMAP_EVENT); struct listnode *mnode, *mnnode; struct bgp *bgp; if (debug) zlog_debug("%s: entry", __func__); if (bm->bgp == NULL) /* may be called during cleanup */ return; for (ALL_LIST_ELEMENTS(bm->bgp, mnode, mnnode, bgp)) vpn_policy_routemap_update(bgp, rmap_name); } void vrf_import_from_vrf(struct bgp *to_bgp, struct bgp *from_bgp, afi_t afi, safi_t safi) { const char *export_name; vpn_policy_direction_t idir, edir; char *vname, *tmp_name; char buf[RD_ADDRSTRLEN]; struct ecommunity *ecom; bool first_export = false; int debug; struct listnode *node; bool is_inst_match = false; export_name = to_bgp->name ? to_bgp->name : VRF_DEFAULT_NAME; idir = BGP_VPN_POLICY_DIR_FROMVPN; edir = BGP_VPN_POLICY_DIR_TOVPN; debug = (BGP_DEBUG(vpn, VPN_LEAK_TO_VRF) | BGP_DEBUG(vpn, VPN_LEAK_FROM_VRF)); /* * Cross-ref both VRFs. Also, note if this is the first time * any VRF is importing from "import_vrf". */ vname = (from_bgp->name ? XSTRDUP(MTYPE_TMP, from_bgp->name) : XSTRDUP(MTYPE_TMP, VRF_DEFAULT_NAME)); /* Check the import_vrf list of destination vrf for the source vrf name, * insert otherwise. */ for (ALL_LIST_ELEMENTS_RO(to_bgp->vpn_policy[afi].import_vrf, node, tmp_name)) { if (strcmp(vname, tmp_name) == 0) { is_inst_match = true; break; } } if (!is_inst_match) listnode_add(to_bgp->vpn_policy[afi].import_vrf, vname); /* Check if the source vrf already exports to any vrf, * first time export requires to setup auto derived RD/RT values. * Add the destination vrf name to export vrf list if it is * not present. */ is_inst_match = false; vname = XSTRDUP(MTYPE_TMP, export_name); if (!listcount(from_bgp->vpn_policy[afi].export_vrf)) { first_export = true; } else { for (ALL_LIST_ELEMENTS_RO(from_bgp->vpn_policy[afi].export_vrf, node, tmp_name)) { if (strcmp(vname, tmp_name) == 0) { is_inst_match = true; break; } } } if (!is_inst_match) listnode_add(from_bgp->vpn_policy[afi].export_vrf, vname); /* Update import RT for current VRF using export RT of the VRF we're * importing from. First though, make sure "import_vrf" has that * set. */ if (first_export) { form_auto_rd(from_bgp->router_id, from_bgp->vrf_rd_id, &from_bgp->vrf_prd_auto); from_bgp->vpn_policy[afi].tovpn_rd = from_bgp->vrf_prd_auto; SET_FLAG(from_bgp->vpn_policy[afi].flags, BGP_VPN_POLICY_TOVPN_RD_SET); prefix_rd2str(&from_bgp->vpn_policy[afi].tovpn_rd, buf, sizeof(buf)); from_bgp->vpn_policy[afi].rtlist[edir] = ecommunity_str2com(buf, ECOMMUNITY_ROUTE_TARGET, 0); SET_FLAG(from_bgp->af_flags[afi][safi], BGP_CONFIG_VRF_TO_VRF_EXPORT); from_bgp->vpn_policy[afi].tovpn_label = BGP_PREVENT_VRF_2_VRF_LEAK; } ecom = from_bgp->vpn_policy[afi].rtlist[edir]; if (to_bgp->vpn_policy[afi].rtlist[idir]) to_bgp->vpn_policy[afi].rtlist[idir] = ecommunity_merge(to_bgp->vpn_policy[afi] .rtlist[idir], ecom); else to_bgp->vpn_policy[afi].rtlist[idir] = ecommunity_dup(ecom); SET_FLAG(to_bgp->af_flags[afi][safi], BGP_CONFIG_VRF_TO_VRF_IMPORT); if (debug) { const char *from_name; from_name = from_bgp->name ? from_bgp->name : VRF_DEFAULT_NAME; zlog_debug("%s from %s to %s first_export %u import-rt %s export-rt %s", __func__, from_name, export_name, first_export, to_bgp->vpn_policy[afi].rtlist[idir] ? (ecommunity_ecom2str(to_bgp->vpn_policy[afi]. rtlist[idir], ECOMMUNITY_FORMAT_ROUTE_MAP, 0)) : " ", to_bgp->vpn_policy[afi].rtlist[edir] ? (ecommunity_ecom2str(to_bgp->vpn_policy[afi]. rtlist[edir], ECOMMUNITY_FORMAT_ROUTE_MAP, 0)) : " "); } /* Does "import_vrf" first need to export its routes or that * is already done and we just need to import those routes * from the global table? */ if (first_export) vpn_leak_postchange(edir, afi, bgp_get_default(), from_bgp); else vpn_leak_postchange(idir, afi, bgp_get_default(), to_bgp); } void vrf_unimport_from_vrf(struct bgp *to_bgp, struct bgp *from_bgp, afi_t afi, safi_t safi) { const char *export_name, *tmp_name; vpn_policy_direction_t idir, edir; char *vname; struct ecommunity *ecom = NULL; struct listnode *node; int debug; export_name = to_bgp->name ? to_bgp->name : VRF_DEFAULT_NAME; tmp_name = from_bgp->name ? from_bgp->name : VRF_DEFAULT_NAME; idir = BGP_VPN_POLICY_DIR_FROMVPN; edir = BGP_VPN_POLICY_DIR_TOVPN; debug = (BGP_DEBUG(vpn, VPN_LEAK_TO_VRF) | BGP_DEBUG(vpn, VPN_LEAK_FROM_VRF)); /* Were we importing from "import_vrf"? */ for (ALL_LIST_ELEMENTS_RO(to_bgp->vpn_policy[afi].import_vrf, node, vname)) { if (strcmp(vname, tmp_name) == 0) break; } /* * We do not check in the cli if the passed in bgp * instance is actually imported into us before * we call this function. As such if we do not * find this in the import_vrf list than * we just need to return safely. */ if (!vname) return; if (debug) zlog_debug("%s from %s to %s", __func__, tmp_name, export_name); /* Remove "import_vrf" from our import list. */ listnode_delete(to_bgp->vpn_policy[afi].import_vrf, vname); XFREE(MTYPE_TMP, vname); /* Remove routes imported from "import_vrf". */ /* TODO: In the current logic, we have to first remove all * imported routes and then (if needed) import back routes */ vpn_leak_prechange(idir, afi, bgp_get_default(), to_bgp); if (to_bgp->vpn_policy[afi].import_vrf->count == 0) { UNSET_FLAG(to_bgp->af_flags[afi][safi], BGP_CONFIG_VRF_TO_VRF_IMPORT); if (to_bgp->vpn_policy[afi].rtlist[idir]) ecommunity_free(&to_bgp->vpn_policy[afi].rtlist[idir]); } else { ecom = from_bgp->vpn_policy[afi].rtlist[edir]; if (ecom) ecommunity_del_val(to_bgp->vpn_policy[afi].rtlist[idir], (struct ecommunity_val *)ecom->val); vpn_leak_postchange(idir, afi, bgp_get_default(), to_bgp); } /* * What? * So SA is assuming that since the ALL_LIST_ELEMENTS_RO * below is checking for NULL that export_vrf can be * NULL, consequently it is complaining( like a cabbage ) * that we could dereference and crash in the listcount(..) * check below. * So make it happy, under protest, with liberty and justice * for all. */ assert(from_bgp->vpn_policy[afi].export_vrf); /* Remove us from "import_vrf's" export list. If no other VRF * is importing from "import_vrf", cleanup appropriately. */ for (ALL_LIST_ELEMENTS_RO(from_bgp->vpn_policy[afi].export_vrf, node, vname)) { if (strcmp(vname, export_name) == 0) break; } /* * If we have gotten to this point then the vname must * exist. If not, we are in a world of trouble and * have slag sitting around. * * import_vrf and export_vrf must match in having * the in/out names as appropriate. * export_vrf list could have been cleaned up * as part of no router bgp source instnace. */ if (!vname) return; listnode_delete(from_bgp->vpn_policy[afi].export_vrf, vname); XFREE(MTYPE_TMP, vname); if (!listcount(from_bgp->vpn_policy[afi].export_vrf)) { vpn_leak_prechange(edir, afi, bgp_get_default(), from_bgp); ecommunity_free(&from_bgp->vpn_policy[afi].rtlist[edir]); UNSET_FLAG(from_bgp->af_flags[afi][safi], BGP_CONFIG_VRF_TO_VRF_EXPORT); memset(&from_bgp->vpn_policy[afi].tovpn_rd, 0, sizeof(struct prefix_rd)); UNSET_FLAG(from_bgp->vpn_policy[afi].flags, BGP_VPN_POLICY_TOVPN_RD_SET); from_bgp->vpn_policy[afi].tovpn_label = MPLS_LABEL_NONE; } } /* For testing purpose, static route of MPLS-VPN. */ DEFUN (vpnv4_network, vpnv4_network_cmd, "network A.B.C.D/M rd ASN:NN_OR_IP-ADDRESS:NN (0-1048575)", "Specify a network to announce via BGP\n" "IPv4 prefix\n" "Specify Route Distinguisher\n" "VPN Route Distinguisher\n" "VPN NLRI label (tag)\n" "VPN NLRI label (tag)\n" "Label value\n") { int idx_ipv4_prefixlen = 1; int idx_ext_community = 3; int idx_label = 5; return bgp_static_set_safi( AFI_IP, SAFI_MPLS_VPN, vty, argv[idx_ipv4_prefixlen]->arg, argv[idx_ext_community]->arg, argv[idx_label]->arg, NULL, 0, NULL, NULL, NULL, NULL); } DEFUN (vpnv4_network_route_map, vpnv4_network_route_map_cmd, "network A.B.C.D/M rd ASN:NN_OR_IP-ADDRESS:NN (0-1048575) route-map WORD", "Specify a network to announce via BGP\n" "IPv4 prefix\n" "Specify Route Distinguisher\n" "VPN Route Distinguisher\n" "VPN NLRI label (tag)\n" "VPN NLRI label (tag)\n" "Label value\n" "route map\n" "route map name\n") { int idx_ipv4_prefixlen = 1; int idx_ext_community = 3; int idx_label = 5; int idx_word_2 = 7; return bgp_static_set_safi( AFI_IP, SAFI_MPLS_VPN, vty, argv[idx_ipv4_prefixlen]->arg, argv[idx_ext_community]->arg, argv[idx_label]->arg, argv[idx_word_2]->arg, 0, NULL, NULL, NULL, NULL); } /* For testing purpose, static route of MPLS-VPN. */ DEFUN (no_vpnv4_network, no_vpnv4_network_cmd, "no network A.B.C.D/M rd ASN:NN_OR_IP-ADDRESS:NN (0-1048575)", NO_STR "Specify a network to announce via BGP\n" "IPv4 prefix\n" "Specify Route Distinguisher\n" "VPN Route Distinguisher\n" "VPN NLRI label (tag)\n" "VPN NLRI label (tag)\n" "Label value\n") { int idx_ipv4_prefixlen = 2; int idx_ext_community = 4; int idx_label = 6; return bgp_static_unset_safi(AFI_IP, SAFI_MPLS_VPN, vty, argv[idx_ipv4_prefixlen]->arg, argv[idx_ext_community]->arg, argv[idx_label]->arg, 0, NULL, NULL, NULL); } DEFUN (vpnv6_network, vpnv6_network_cmd, "network X:X::X:X/M rd ASN:NN_OR_IP-ADDRESS:NN (0-1048575) [route-map WORD]", "Specify a network to announce via BGP\n" "IPv6 prefix /, e.g., 3ffe::/16\n" "Specify Route Distinguisher\n" "VPN Route Distinguisher\n" "VPN NLRI label (tag)\n" "VPN NLRI label (tag)\n" "Label value\n" "route map\n" "route map name\n") { int idx_ipv6_prefix = 1; int idx_ext_community = 3; int idx_label = 5; int idx_word_2 = 7; if (argc == 8) return bgp_static_set_safi( AFI_IP6, SAFI_MPLS_VPN, vty, argv[idx_ipv6_prefix]->arg, argv[idx_ext_community]->arg, argv[idx_label]->arg, argv[idx_word_2]->arg, 0, NULL, NULL, NULL, NULL); else return bgp_static_set_safi( AFI_IP6, SAFI_MPLS_VPN, vty, argv[idx_ipv6_prefix]->arg, argv[idx_ext_community]->arg, argv[idx_label]->arg, NULL, 0, NULL, NULL, NULL, NULL); } /* For testing purpose, static route of MPLS-VPN. */ DEFUN (no_vpnv6_network, no_vpnv6_network_cmd, "no network X:X::X:X/M rd ASN:NN_OR_IP-ADDRESS:NN (0-1048575)", NO_STR "Specify a network to announce via BGP\n" "IPv6 prefix /, e.g., 3ffe::/16\n" "Specify Route Distinguisher\n" "VPN Route Distinguisher\n" "VPN NLRI label (tag)\n" "VPN NLRI label (tag)\n" "Label value\n") { int idx_ipv6_prefix = 2; int idx_ext_community = 4; int idx_label = 6; return bgp_static_unset_safi(AFI_IP6, SAFI_MPLS_VPN, vty, argv[idx_ipv6_prefix]->arg, argv[idx_ext_community]->arg, argv[idx_label]->arg, 0, NULL, NULL, NULL); } int bgp_show_mpls_vpn(struct vty *vty, afi_t afi, struct prefix_rd *prd, enum bgp_show_type type, void *output_arg, int tags, bool use_json) { struct bgp *bgp; struct bgp_table *table; bgp = bgp_get_default(); if (bgp == NULL) { if (!use_json) vty_out(vty, "No BGP process is configured\n"); else vty_out(vty, "{}\n"); return CMD_WARNING; } table = bgp->rib[afi][SAFI_MPLS_VPN]; return bgp_show_table_rd(vty, bgp, SAFI_MPLS_VPN, table, prd, type, output_arg, use_json); } DEFUN (show_bgp_ip_vpn_all_rd, show_bgp_ip_vpn_all_rd_cmd, "show bgp "BGP_AFI_CMD_STR" vpn all [rd ASN:NN_OR_IP-ADDRESS:NN] [json]", SHOW_STR BGP_STR BGP_VPNVX_HELP_STR "Display VPN NLRI specific information\n" "Display VPN NLRI specific information\n" "Display information for a route distinguisher\n" "VPN Route Distinguisher\n" JSON_STR) { int ret; struct prefix_rd prd; afi_t afi; int idx = 0; if (argv_find_and_parse_afi(argv, argc, &idx, &afi)) { if (argv_find(argv, argc, "rd", &idx)) { ret = str2prefix_rd(argv[idx + 1]->arg, &prd); if (!ret) { vty_out(vty, "%% Malformed Route Distinguisher\n"); return CMD_WARNING; } return bgp_show_mpls_vpn(vty, afi, &prd, bgp_show_type_normal, NULL, 0, use_json(argc, argv)); } else { return bgp_show_mpls_vpn(vty, afi, NULL, bgp_show_type_normal, NULL, 0, use_json(argc, argv)); } } return CMD_SUCCESS; } ALIAS(show_bgp_ip_vpn_all_rd, show_bgp_ip_vpn_rd_cmd, "show bgp "BGP_AFI_CMD_STR" vpn rd ASN:NN_OR_IP-ADDRESS:NN [json]", SHOW_STR BGP_STR BGP_VPNVX_HELP_STR "Display VPN NLRI specific information\n" "Display information for a route distinguisher\n" "VPN Route Distinguisher\n" JSON_STR) #ifdef KEEP_OLD_VPN_COMMANDS DEFUN (show_ip_bgp_vpn_rd, show_ip_bgp_vpn_rd_cmd, "show ip bgp "BGP_AFI_CMD_STR" vpn rd ASN:NN_OR_IP-ADDRESS:NN", SHOW_STR IP_STR BGP_STR BGP_AFI_HELP_STR "Address Family modifier\n" "Display information for a route distinguisher\n" "VPN Route Distinguisher\n") { int idx_ext_community = argc - 1; int ret; struct prefix_rd prd; afi_t afi; int idx = 0; if (argv_find_and_parse_vpnvx(argv, argc, &idx, &afi)) { ret = str2prefix_rd(argv[idx_ext_community]->arg, &prd); if (!ret) { vty_out(vty, "%% Malformed Route Distinguisher\n"); return CMD_WARNING; } return bgp_show_mpls_vpn(vty, afi, &prd, bgp_show_type_normal, NULL, 0, 0); } return CMD_SUCCESS; } DEFUN (show_ip_bgp_vpn_all, show_ip_bgp_vpn_all_cmd, "show [ip] bgp ", SHOW_STR IP_STR BGP_STR BGP_VPNVX_HELP_STR) { afi_t afi; int idx = 0; if (argv_find_and_parse_vpnvx(argv, argc, &idx, &afi)) return bgp_show_mpls_vpn(vty, afi, NULL, bgp_show_type_normal, NULL, 0, 0); return CMD_SUCCESS; } DEFUN (show_ip_bgp_vpn_all_tags, show_ip_bgp_vpn_all_tags_cmd, "show [ip] bgp all tags", SHOW_STR IP_STR BGP_STR BGP_VPNVX_HELP_STR "Display information about all VPNv4/VPNV6 NLRIs\n" "Display BGP tags for prefixes\n") { afi_t afi; int idx = 0; if (argv_find_and_parse_vpnvx(argv, argc, &idx, &afi)) return bgp_show_mpls_vpn(vty, afi, NULL, bgp_show_type_normal, NULL, 1, 0); return CMD_SUCCESS; } DEFUN (show_ip_bgp_vpn_rd_tags, show_ip_bgp_vpn_rd_tags_cmd, "show [ip] bgp rd ASN:NN_OR_IP-ADDRESS:NN tags", SHOW_STR IP_STR BGP_STR BGP_VPNVX_HELP_STR "Display information for a route distinguisher\n" "VPN Route Distinguisher\n" "Display BGP tags for prefixes\n") { int idx_ext_community = 5; int ret; struct prefix_rd prd; afi_t afi; int idx = 0; if (argv_find_and_parse_vpnvx(argv, argc, &idx, &afi)) { ret = str2prefix_rd(argv[idx_ext_community]->arg, &prd); if (!ret) { vty_out(vty, "%% Malformed Route Distinguisher\n"); return CMD_WARNING; } return bgp_show_mpls_vpn(vty, afi, &prd, bgp_show_type_normal, NULL, 1, 0); } return CMD_SUCCESS; } DEFUN (show_ip_bgp_vpn_all_neighbor_routes, show_ip_bgp_vpn_all_neighbor_routes_cmd, "show [ip] bgp all neighbors A.B.C.D routes [json]", SHOW_STR IP_STR BGP_STR BGP_VPNVX_HELP_STR "Display information about all VPNv4/VPNv6 NLRIs\n" "Detailed information on TCP and BGP neighbor connections\n" "Neighbor to display information about\n" "Display routes learned from neighbor\n" JSON_STR) { int idx_ipv4 = 6; union sockunion su; struct peer *peer; int ret; bool uj = use_json(argc, argv); afi_t afi; int idx = 0; if (argv_find_and_parse_vpnvx(argv, argc, &idx, &afi)) { ret = str2sockunion(argv[idx_ipv4]->arg, &su); if (ret < 0) { if (uj) { json_object *json_no = NULL; json_no = json_object_new_object(); json_object_string_add(json_no, "warning", "Malformed address"); vty_out(vty, "%s\n", json_object_to_json_string(json_no)); json_object_free(json_no); } else vty_out(vty, "Malformed address: %s\n", argv[idx_ipv4]->arg); return CMD_WARNING; } peer = peer_lookup(NULL, &su); if (!peer || !peer->afc[afi][SAFI_MPLS_VPN]) { if (uj) { json_object *json_no = NULL; json_no = json_object_new_object(); json_object_string_add( json_no, "warning", "No such neighbor or address family"); vty_out(vty, "%s\n", json_object_to_json_string(json_no)); json_object_free(json_no); } else vty_out(vty, "%% No such neighbor or address family\n"); return CMD_WARNING; } return bgp_show_mpls_vpn(vty, afi, NULL, bgp_show_type_neighbor, &su, 0, uj); } return CMD_SUCCESS; } DEFUN (show_ip_bgp_vpn_rd_neighbor_routes, show_ip_bgp_vpn_rd_neighbor_routes_cmd, "show [ip] bgp rd ASN:NN_OR_IP-ADDRESS:NN neighbors A.B.C.D routes [json]", SHOW_STR IP_STR BGP_STR BGP_VPNVX_HELP_STR "Display information for a route distinguisher\n" "VPN Route Distinguisher\n" "Detailed information on TCP and BGP neighbor connections\n" "Neighbor to display information about\n" "Display routes learned from neighbor\n" JSON_STR) { int idx_ext_community = 5; int idx_ipv4 = 7; int ret; union sockunion su; struct peer *peer; struct prefix_rd prd; bool uj = use_json(argc, argv); afi_t afi; int idx = 0; if (argv_find_and_parse_vpnvx(argv, argc, &idx, &afi)) { ret = str2prefix_rd(argv[idx_ext_community]->arg, &prd); if (!ret) { if (uj) { json_object *json_no = NULL; json_no = json_object_new_object(); json_object_string_add( json_no, "warning", "Malformed Route Distinguisher"); vty_out(vty, "%s\n", json_object_to_json_string(json_no)); json_object_free(json_no); } else vty_out(vty, "%% Malformed Route Distinguisher\n"); return CMD_WARNING; } ret = str2sockunion(argv[idx_ipv4]->arg, &su); if (ret < 0) { if (uj) { json_object *json_no = NULL; json_no = json_object_new_object(); json_object_string_add(json_no, "warning", "Malformed address"); vty_out(vty, "%s\n", json_object_to_json_string(json_no)); json_object_free(json_no); } else vty_out(vty, "Malformed address: %s\n", argv[idx_ext_community]->arg); return CMD_WARNING; } peer = peer_lookup(NULL, &su); if (!peer || !peer->afc[afi][SAFI_MPLS_VPN]) { if (uj) { json_object *json_no = NULL; json_no = json_object_new_object(); json_object_string_add( json_no, "warning", "No such neighbor or address family"); vty_out(vty, "%s\n", json_object_to_json_string(json_no)); json_object_free(json_no); } else vty_out(vty, "%% No such neighbor or address family\n"); return CMD_WARNING; } return bgp_show_mpls_vpn(vty, afi, &prd, bgp_show_type_neighbor, &su, 0, uj); } return CMD_SUCCESS; } DEFUN (show_ip_bgp_vpn_all_neighbor_advertised_routes, show_ip_bgp_vpn_all_neighbor_advertised_routes_cmd, "show [ip] bgp all neighbors A.B.C.D advertised-routes [json]", SHOW_STR IP_STR BGP_STR BGP_VPNVX_HELP_STR "Display information about all VPNv4/VPNv6 NLRIs\n" "Detailed information on TCP and BGP neighbor connections\n" "Neighbor to display information about\n" "Display the routes advertised to a BGP neighbor\n" JSON_STR) { int idx_ipv4 = 6; int ret; struct peer *peer; union sockunion su; bool uj = use_json(argc, argv); afi_t afi; int idx = 0; if (argv_find_and_parse_vpnvx(argv, argc, &idx, &afi)) { ret = str2sockunion(argv[idx_ipv4]->arg, &su); if (ret < 0) { if (uj) { json_object *json_no = NULL; json_no = json_object_new_object(); json_object_string_add(json_no, "warning", "Malformed address"); vty_out(vty, "%s\n", json_object_to_json_string(json_no)); json_object_free(json_no); } else vty_out(vty, "Malformed address: %s\n", argv[idx_ipv4]->arg); return CMD_WARNING; } peer = peer_lookup(NULL, &su); if (!peer || !peer->afc[afi][SAFI_MPLS_VPN]) { if (uj) { json_object *json_no = NULL; json_no = json_object_new_object(); json_object_string_add( json_no, "warning", "No such neighbor or address family"); vty_out(vty, "%s\n", json_object_to_json_string(json_no)); json_object_free(json_no); } else vty_out(vty, "%% No such neighbor or address family\n"); return CMD_WARNING; } return show_adj_route_vpn(vty, peer, NULL, AFI_IP, SAFI_MPLS_VPN, uj); } return CMD_SUCCESS; } DEFUN (show_ip_bgp_vpn_rd_neighbor_advertised_routes, show_ip_bgp_vpn_rd_neighbor_advertised_routes_cmd, "show [ip] bgp rd ASN:NN_OR_IP-ADDRESS:NN neighbors A.B.C.D advertised-routes [json]", SHOW_STR IP_STR BGP_STR BGP_VPNVX_HELP_STR "Display information for a route distinguisher\n" "VPN Route Distinguisher\n" "Detailed information on TCP and BGP neighbor connections\n" "Neighbor to display information about\n" "Display the routes advertised to a BGP neighbor\n" JSON_STR) { int idx_ext_community = 5; int idx_ipv4 = 7; int ret; struct peer *peer; struct prefix_rd prd; union sockunion su; bool uj = use_json(argc, argv); afi_t afi; int idx = 0; if (argv_find_and_parse_vpnvx(argv, argc, &idx, &afi)) { ret = str2sockunion(argv[idx_ipv4]->arg, &su); if (ret < 0) { if (uj) { json_object *json_no = NULL; json_no = json_object_new_object(); json_object_string_add(json_no, "warning", "Malformed address"); vty_out(vty, "%s\n", json_object_to_json_string(json_no)); json_object_free(json_no); } else vty_out(vty, "Malformed address: %s\n", argv[idx_ext_community]->arg); return CMD_WARNING; } peer = peer_lookup(NULL, &su); if (!peer || !peer->afc[afi][SAFI_MPLS_VPN]) { if (uj) { json_object *json_no = NULL; json_no = json_object_new_object(); json_object_string_add( json_no, "warning", "No such neighbor or address family"); vty_out(vty, "%s\n", json_object_to_json_string(json_no)); json_object_free(json_no); } else vty_out(vty, "%% No such neighbor or address family\n"); return CMD_WARNING; } ret = str2prefix_rd(argv[idx_ext_community]->arg, &prd); if (!ret) { if (uj) { json_object *json_no = NULL; json_no = json_object_new_object(); json_object_string_add( json_no, "warning", "Malformed Route Distinguisher"); vty_out(vty, "%s\n", json_object_to_json_string(json_no)); json_object_free(json_no); } else vty_out(vty, "%% Malformed Route Distinguisher\n"); return CMD_WARNING; } return show_adj_route_vpn(vty, peer, &prd, AFI_IP, SAFI_MPLS_VPN, uj); } return CMD_SUCCESS; } #endif /* KEEP_OLD_VPN_COMMANDS */ void bgp_mplsvpn_init(void) { install_element(BGP_VPNV4_NODE, &vpnv4_network_cmd); install_element(BGP_VPNV4_NODE, &vpnv4_network_route_map_cmd); install_element(BGP_VPNV4_NODE, &no_vpnv4_network_cmd); install_element(BGP_VPNV6_NODE, &vpnv6_network_cmd); install_element(BGP_VPNV6_NODE, &no_vpnv6_network_cmd); install_element(VIEW_NODE, &show_bgp_ip_vpn_all_rd_cmd); install_element(VIEW_NODE, &show_bgp_ip_vpn_rd_cmd); #ifdef KEEP_OLD_VPN_COMMANDS install_element(VIEW_NODE, &show_ip_bgp_vpn_rd_cmd); install_element(VIEW_NODE, &show_ip_bgp_vpn_all_cmd); install_element(VIEW_NODE, &show_ip_bgp_vpn_all_tags_cmd); install_element(VIEW_NODE, &show_ip_bgp_vpn_rd_tags_cmd); install_element(VIEW_NODE, &show_ip_bgp_vpn_all_neighbor_routes_cmd); install_element(VIEW_NODE, &show_ip_bgp_vpn_rd_neighbor_routes_cmd); install_element(VIEW_NODE, &show_ip_bgp_vpn_all_neighbor_advertised_routes_cmd); install_element(VIEW_NODE, &show_ip_bgp_vpn_rd_neighbor_advertised_routes_cmd); #endif /* KEEP_OLD_VPN_COMMANDS */ } vrf_id_t get_first_vrf_for_redirect_with_rt(struct ecommunity *eckey) { struct listnode *mnode, *mnnode; struct bgp *bgp; for (ALL_LIST_ELEMENTS(bm->bgp, mnode, mnnode, bgp)) { struct ecommunity *ec; if (bgp->inst_type != BGP_INSTANCE_TYPE_VRF) continue; ec = bgp->vpn_policy[AFI_IP].import_redirect_rtlist; if (ecom_intersect(ec, eckey)) return bgp->vrf_id; } return VRF_UNKNOWN; } /* * The purpose of this function is to process leaks that were deferred * from earlier per-vrf configuration due to not-yet-existing default * vrf, in other words, configuration such as: * * router bgp MMM vrf FOO * address-family ipv4 unicast * rd vpn export 1:1 * exit-address-family * * router bgp NNN * ... * * This function gets called when the default instance ("router bgp NNN") * is created. */ void vpn_leak_postchange_all(void) { struct listnode *next; struct bgp *bgp; struct bgp *bgp_default = bgp_get_default(); assert(bgp_default); /* First, do any exporting from VRFs to the single VPN RIB */ for (ALL_LIST_ELEMENTS_RO(bm->bgp, next, bgp)) { if (bgp->inst_type != BGP_INSTANCE_TYPE_VRF) continue; vpn_leak_postchange( BGP_VPN_POLICY_DIR_TOVPN, AFI_IP, bgp_default, bgp); vpn_leak_postchange( BGP_VPN_POLICY_DIR_TOVPN, AFI_IP6, bgp_default, bgp); } /* Now, do any importing to VRFs from the single VPN RIB */ for (ALL_LIST_ELEMENTS_RO(bm->bgp, next, bgp)) { if (bgp->inst_type != BGP_INSTANCE_TYPE_VRF) continue; vpn_leak_postchange( BGP_VPN_POLICY_DIR_FROMVPN, AFI_IP, bgp_default, bgp); vpn_leak_postchange( BGP_VPN_POLICY_DIR_FROMVPN, AFI_IP6, bgp_default, bgp); } } /* When a bgp vrf instance is unconfigured, remove its routes * from the VPN table and this vrf could be importing routes from other * bgp vrf instnaces, unimport them. * VRF X and VRF Y are exporting routes to each other. * When VRF X is deleted, unimport its routes from all target vrfs, * also VRF Y should unimport its routes from VRF X table. * This will ensure VPN table is cleaned up appropriately. */ int bgp_vpn_leak_unimport(struct bgp *from_bgp, struct vty *vty) { struct bgp *to_bgp; const char *tmp_name; char *vname; struct listnode *node, *next; safi_t safi = SAFI_UNICAST; afi_t afi; bool is_vrf_leak_bind; int debug; if (from_bgp->inst_type != BGP_INSTANCE_TYPE_VRF) return 0; debug = (BGP_DEBUG(vpn, VPN_LEAK_TO_VRF) | BGP_DEBUG(vpn, VPN_LEAK_FROM_VRF)); tmp_name = from_bgp->name ? from_bgp->name : VRF_DEFAULT_NAME; for (afi = 0; afi < AFI_MAX; ++afi) { /* vrf leak is for IPv4 and IPv6 Unicast only */ if (afi != AFI_IP && afi != AFI_IP6) continue; for (ALL_LIST_ELEMENTS_RO(bm->bgp, next, to_bgp)) { if (from_bgp == to_bgp) continue; /* Unimport and remove source vrf from the * other vrfs import list. */ struct vpn_policy *to_vpolicy; is_vrf_leak_bind = false; to_vpolicy = &(to_bgp->vpn_policy[afi]); for (ALL_LIST_ELEMENTS_RO(to_vpolicy->import_vrf, node, vname)) { if (strcmp(vname, tmp_name) == 0) { is_vrf_leak_bind = true; break; } } /* skip this bgp instance as there is no leak to this * vrf instance. */ if (!is_vrf_leak_bind) continue; if (debug) zlog_debug("%s: unimport routes from %s to_bgp %s afi %s import vrfs count %u", __func__, from_bgp->name_pretty, to_bgp->name_pretty, afi2str(afi), to_vpolicy->import_vrf->count); vrf_unimport_from_vrf(to_bgp, from_bgp, afi, safi); /* readd vrf name as unimport removes import vrf name * from the destination vrf's import list where the * `import vrf` configuration still exist. */ vname = XSTRDUP(MTYPE_TMP, tmp_name); listnode_add(to_bgp->vpn_policy[afi].import_vrf, vname); SET_FLAG(to_bgp->af_flags[afi][safi], BGP_CONFIG_VRF_TO_VRF_IMPORT); /* If to_bgp exports its routes to the bgp vrf * which is being deleted, un-import the * to_bgp routes from VPN. */ for (ALL_LIST_ELEMENTS_RO(to_bgp->vpn_policy[afi] .export_vrf, node, vname)) { if (strcmp(vname, tmp_name) == 0) { vrf_unimport_from_vrf(from_bgp, to_bgp, afi, safi); break; } } } } return 0; } /* When a router bgp is configured, there could be a bgp vrf * instance importing routes from this newly configured * bgp vrf instance. Export routes from configured * bgp vrf to VPN. * VRF Y has import from bgp vrf x, * when a bgp vrf x instance is created, export its routes * to VRF Y instance. */ void bgp_vpn_leak_export(struct bgp *from_bgp) { afi_t afi; const char *export_name; char *vname; struct listnode *node, *next; struct ecommunity *ecom; vpn_policy_direction_t idir, edir; safi_t safi = SAFI_UNICAST; struct bgp *to_bgp; int debug; debug = (BGP_DEBUG(vpn, VPN_LEAK_TO_VRF) | BGP_DEBUG(vpn, VPN_LEAK_FROM_VRF)); idir = BGP_VPN_POLICY_DIR_FROMVPN; edir = BGP_VPN_POLICY_DIR_TOVPN; export_name = (from_bgp->name ? XSTRDUP(MTYPE_TMP, from_bgp->name) : XSTRDUP(MTYPE_TMP, VRF_DEFAULT_NAME)); for (afi = 0; afi < AFI_MAX; ++afi) { /* vrf leak is for IPv4 and IPv6 Unicast only */ if (afi != AFI_IP && afi != AFI_IP6) continue; for (ALL_LIST_ELEMENTS_RO(bm->bgp, next, to_bgp)) { if (from_bgp == to_bgp) continue; /* bgp instance has import list, check to see if newly * configured bgp instance is the list. */ struct vpn_policy *to_vpolicy; to_vpolicy = &(to_bgp->vpn_policy[afi]); for (ALL_LIST_ELEMENTS_RO(to_vpolicy->import_vrf, node, vname)) { if (strcmp(vname, export_name) != 0) continue; if (debug) zlog_debug("%s: found from_bgp %s in to_bgp %s import list, import routes.", __func__, export_name, to_bgp->name_pretty); ecom = from_bgp->vpn_policy[afi].rtlist[edir]; /* remove import rt, it will be readded * as part of import from vrf. */ if (ecom) ecommunity_del_val( to_vpolicy->rtlist[idir], (struct ecommunity_val *) ecom->val); vrf_import_from_vrf(to_bgp, from_bgp, afi, safi); break; } } } } frr-7.2.1/bgpd/bgp_mplsvpn.h0000644000000000000000000002112113610377563012625 00000000000000/* MPLS-VPN * Copyright (C) 2000 Kunihiro Ishiguro * * This file is part of GxNU Zebra. * * GNU Zebra is free software; you can redistribute it and/or modify it * under the terms of the GNU General Public License as published by the * Free Software Foundation; either version 2, or (at your option) any * later version. * * GNU Zebra 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; see the file COPYING; if not, write to the Free Software * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA */ #ifndef _QUAGGA_BGP_MPLSVPN_H #define _QUAGGA_BGP_MPLSVPN_H #include "bgpd/bgp_route.h" #include "bgpd/bgp_rd.h" #include "bgpd/bgp_zebra.h" #define MPLS_LABEL_IS_SPECIAL(label) ((label) <= MPLS_LABEL_EXTENSION) #define MPLS_LABEL_IS_NULL(label) \ ((label) == MPLS_LABEL_IPV4_EXPLICIT_NULL \ || (label) == MPLS_LABEL_IPV6_EXPLICIT_NULL \ || (label) == MPLS_LABEL_IMPLICIT_NULL) #define BGP_VPNVX_HELP_STR \ "Address Family\n" \ "Address Family\n" #define V4_HEADER \ " Network Next Hop Metric LocPrf Weight Path\n" #define V4_HEADER_TAG " Network Next Hop In tag/Out tag\n" #define V4_HEADER_OVERLAY \ " Network Next Hop EthTag Overlay Index RouterMac\n" extern void bgp_mplsvpn_init(void); extern int bgp_nlri_parse_vpn(struct peer *, struct attr *, struct bgp_nlri *); extern uint32_t decode_label(mpls_label_t *); extern void encode_label(mpls_label_t, mpls_label_t *); extern int argv_find_and_parse_vpnvx(struct cmd_token **argv, int argc, int *index, afi_t *afi); extern int bgp_show_mpls_vpn(struct vty *vty, afi_t afi, struct prefix_rd *prd, enum bgp_show_type type, void *output_arg, int tags, bool use_json); extern void vpn_leak_from_vrf_update(struct bgp *bgp_vpn, struct bgp *bgp_vrf, struct bgp_path_info *path_vrf); extern void vpn_leak_from_vrf_withdraw(struct bgp *bgp_vpn, struct bgp *bgp_vrf, struct bgp_path_info *path_vrf); extern void vpn_leak_from_vrf_withdraw_all(struct bgp *bgp_vpn, struct bgp *bgp_vrf, afi_t afi); extern void vpn_leak_from_vrf_update_all(struct bgp *bgp_vpn, struct bgp *bgp_vrf, afi_t afi); extern void vpn_leak_to_vrf_withdraw_all(struct bgp *bgp_vrf, afi_t afi); extern void vpn_leak_to_vrf_update_all(struct bgp *bgp_vrf, struct bgp *bgp_vpn, afi_t afi); extern void vpn_leak_to_vrf_update(struct bgp *bgp_vpn, struct bgp_path_info *path_vpn); extern void vpn_leak_to_vrf_withdraw(struct bgp *bgp_vpn, struct bgp_path_info *path_vpn); extern void vpn_leak_zebra_vrf_label_update(struct bgp *bgp, afi_t afi); extern void vpn_leak_zebra_vrf_label_withdraw(struct bgp *bgp, afi_t afi); extern int vpn_leak_label_callback(mpls_label_t label, void *lblid, bool alloc); extern void vrf_import_from_vrf(struct bgp *to_bgp, struct bgp *from_bgp, afi_t afi, safi_t safi); void vrf_unimport_from_vrf(struct bgp *to_bgp, struct bgp *from_bgp, afi_t afi, safi_t safi); static inline int vpn_leak_to_vpn_active(struct bgp *bgp_vrf, afi_t afi, const char **pmsg) { if (bgp_vrf->inst_type != BGP_INSTANCE_TYPE_VRF && bgp_vrf->inst_type != BGP_INSTANCE_TYPE_DEFAULT) { if (pmsg) *pmsg = "source bgp instance neither vrf nor default"; return 0; } /* Is vrf configured to export to vpn? */ if (!CHECK_FLAG(bgp_vrf->af_flags[afi][SAFI_UNICAST], BGP_CONFIG_VRF_TO_MPLSVPN_EXPORT) && !CHECK_FLAG(bgp_vrf->af_flags[afi][SAFI_UNICAST], BGP_CONFIG_VRF_TO_VRF_EXPORT)) { if (pmsg) *pmsg = "export not set"; return 0; } /* Is there an RT list set? */ if (!bgp_vrf->vpn_policy[afi].rtlist[BGP_VPN_POLICY_DIR_TOVPN]) { if (pmsg) *pmsg = "rtlist tovpn not defined"; return 0; } /* Is there an RD set? */ if (!CHECK_FLAG(bgp_vrf->vpn_policy[afi].flags, BGP_VPN_POLICY_TOVPN_RD_SET)) { if (pmsg) *pmsg = "rd not defined"; return 0; } /* Is a route-map specified, but not defined? */ if (bgp_vrf->vpn_policy[afi].rmap_name[BGP_VPN_POLICY_DIR_TOVPN] && !bgp_vrf->vpn_policy[afi].rmap[BGP_VPN_POLICY_DIR_TOVPN]) { if (pmsg) *pmsg = "route-map tovpn named but not defined"; return 0; } /* Is there an "auto" export label that isn't allocated yet? */ if (CHECK_FLAG(bgp_vrf->vpn_policy[afi].flags, BGP_VPN_POLICY_TOVPN_LABEL_AUTO) && (bgp_vrf->vpn_policy[afi].tovpn_label == MPLS_LABEL_NONE)) { if (pmsg) *pmsg = "auto label not allocated"; return 0; } return 1; } static inline int vpn_leak_from_vpn_active(struct bgp *bgp_vrf, afi_t afi, const char **pmsg) { if (bgp_vrf->inst_type != BGP_INSTANCE_TYPE_VRF && bgp_vrf->inst_type != BGP_INSTANCE_TYPE_DEFAULT) { if (pmsg) *pmsg = "destination bgp instance neither vrf nor default"; return 0; } if (bgp_vrf->vrf_id == VRF_UNKNOWN) { if (pmsg) *pmsg = "destination bgp instance vrf is VRF_UNKNOWN"; return 0; } /* Is vrf configured to import from vpn? */ if (!CHECK_FLAG(bgp_vrf->af_flags[afi][SAFI_UNICAST], BGP_CONFIG_MPLSVPN_TO_VRF_IMPORT) && !CHECK_FLAG(bgp_vrf->af_flags[afi][SAFI_UNICAST], BGP_CONFIG_VRF_TO_VRF_IMPORT)) { if (pmsg) *pmsg = "import not set"; return 0; } /* Is there an RT list set? */ if (!bgp_vrf->vpn_policy[afi].rtlist[BGP_VPN_POLICY_DIR_FROMVPN]) { if (pmsg) *pmsg = "rtlist fromvpn not defined"; return 0; } /* Is a route-map specified, but not defined? */ if (bgp_vrf->vpn_policy[afi].rmap_name[BGP_VPN_POLICY_DIR_FROMVPN] && !bgp_vrf->vpn_policy[afi].rmap[BGP_VPN_POLICY_DIR_FROMVPN]) { if (pmsg) *pmsg = "route-map fromvpn named but not defined"; return 0; } return 1; } static inline void vpn_leak_prechange(vpn_policy_direction_t direction, afi_t afi, struct bgp *bgp_vpn, struct bgp *bgp_vrf) { /* Detect when default bgp instance is not (yet) defined by config */ if (!bgp_vpn) return; if ((direction == BGP_VPN_POLICY_DIR_FROMVPN) && vpn_leak_from_vpn_active(bgp_vrf, afi, NULL)) { vpn_leak_to_vrf_withdraw_all(bgp_vrf, afi); } if ((direction == BGP_VPN_POLICY_DIR_TOVPN) && vpn_leak_to_vpn_active(bgp_vrf, afi, NULL)) { vpn_leak_from_vrf_withdraw_all(bgp_vpn, bgp_vrf, afi); } } static inline void vpn_leak_postchange(vpn_policy_direction_t direction, afi_t afi, struct bgp *bgp_vpn, struct bgp *bgp_vrf) { /* Detect when default bgp instance is not (yet) defined by config */ if (!bgp_vpn) return; if (direction == BGP_VPN_POLICY_DIR_FROMVPN) vpn_leak_to_vrf_update_all(bgp_vrf, bgp_vpn, afi); if (direction == BGP_VPN_POLICY_DIR_TOVPN) { if (bgp_vrf->vpn_policy[afi].tovpn_label != bgp_vrf->vpn_policy[afi] .tovpn_zebra_vrf_label_last_sent) { vpn_leak_zebra_vrf_label_update(bgp_vrf, afi); } vpn_leak_from_vrf_update_all(bgp_vpn, bgp_vrf, afi); } } /* Flag if the route is injectable into VPN. This would be either a * non-imported route or a non-VPN imported route. */ static inline bool is_route_injectable_into_vpn(struct bgp_path_info *pi) { struct bgp_path_info *parent_pi; struct bgp_table *table; struct bgp_node *rn; if (pi->sub_type != BGP_ROUTE_IMPORTED || !pi->extra || !pi->extra->parent) return true; parent_pi = (struct bgp_path_info *)pi->extra->parent; rn = parent_pi->net; if (!rn) return true; table = bgp_node_table(rn); if (table && (table->afi == AFI_IP || table->afi == AFI_IP6) && table->safi == SAFI_MPLS_VPN) return false; return true; } /* Flag if the route path's family is VPN. */ static inline bool is_pi_family_vpn(struct bgp_path_info *pi) { return (is_pi_family_matching(pi, AFI_IP, SAFI_MPLS_VPN) || is_pi_family_matching(pi, AFI_IP6, SAFI_MPLS_VPN)); } extern void vpn_policy_routemap_event(const char *rmap_name); extern vrf_id_t get_first_vrf_for_redirect_with_rt(struct ecommunity *eckey); extern void vpn_leak_postchange_all(void); extern void vpn_handle_router_id_update(struct bgp *bgp, bool withdraw, bool is_config); extern int bgp_vpn_leak_unimport(struct bgp *from_bgp, struct vty *vty); extern void bgp_vpn_leak_export(struct bgp *from_bgp); #endif /* _QUAGGA_BGP_MPLSVPN_H */ frr-7.2.1/bgpd/bgp_network.c0000644000000000000000000005337513610377563012632 00000000000000/* BGP network related fucntions * Copyright (C) 1999 Kunihiro Ishiguro * * This file is part of GNU Zebra. * * GNU Zebra is free software; you can redistribute it and/or modify it * under the terms of the GNU General Public License as published by the * Free Software Foundation; either version 2, or (at your option) any * later version. * * GNU Zebra 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; see the file COPYING; if not, write to the Free Software * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA */ #include #include "thread.h" #include "sockunion.h" #include "sockopt.h" #include "memory.h" #include "log.h" #include "if.h" #include "prefix.h" #include "command.h" #include "privs.h" #include "linklist.h" #include "network.h" #include "queue.h" #include "hash.h" #include "filter.h" #include "ns.h" #include "lib_errors.h" #include "nexthop.h" #include "bgpd/bgpd.h" #include "bgpd/bgp_open.h" #include "bgpd/bgp_fsm.h" #include "bgpd/bgp_attr.h" #include "bgpd/bgp_debug.h" #include "bgpd/bgp_errors.h" #include "bgpd/bgp_network.h" #include "bgpd/bgp_zebra.h" extern struct zebra_privs_t bgpd_privs; static char *bgp_get_bound_name(struct peer *peer); /* BGP listening socket. */ struct bgp_listener { int fd; union sockunion su; struct thread *thread; struct bgp *bgp; }; /* * Set MD5 key for the socket, for the given IPv4 peer address. * If the password is NULL or zero-length, the option will be disabled. */ static int bgp_md5_set_socket(int socket, union sockunion *su, uint16_t prefixlen, const char *password) { int ret = -1; int en = ENOSYS; #if HAVE_DECL_TCP_MD5SIG union sockunion su2; #endif /* HAVE_TCP_MD5SIG */ assert(socket >= 0); #if HAVE_DECL_TCP_MD5SIG /* Ensure there is no extraneous port information. */ memcpy(&su2, su, sizeof(union sockunion)); if (su2.sa.sa_family == AF_INET) su2.sin.sin_port = 0; else su2.sin6.sin6_port = 0; /* For addresses, use the non-extended signature functionality */ if ((su2.sa.sa_family == AF_INET && prefixlen == IPV4_MAX_PREFIXLEN) || (su2.sa.sa_family == AF_INET6 && prefixlen == IPV6_MAX_PREFIXLEN)) ret = sockopt_tcp_signature(socket, &su2, password); else ret = sockopt_tcp_signature_ext(socket, &su2, prefixlen, password); en = errno; #endif /* HAVE_TCP_MD5SIG */ if (ret < 0) { char sabuf[SU_ADDRSTRLEN]; sockunion2str(su, sabuf, sizeof(sabuf)); switch (ret) { case -2: flog_warn( EC_BGP_NO_TCP_MD5, "Unable to set TCP MD5 option on socket for peer %s (sock=%d): This platform does not support MD5 auth for prefixes", sabuf, socket); break; default: flog_warn( EC_BGP_NO_TCP_MD5, "Unable to set TCP MD5 option on socket for peer %s (sock=%d): %s", sabuf, socket, safe_strerror(en)); } } return ret; } /* Helper for bgp_connect */ static int bgp_md5_set_connect(int socket, union sockunion *su, uint16_t prefixlen, const char *password) { int ret = -1; #if HAVE_DECL_TCP_MD5SIG frr_with_privs(&bgpd_privs) { ret = bgp_md5_set_socket(socket, su, prefixlen, password); } #endif /* HAVE_TCP_MD5SIG */ return ret; } static int bgp_md5_set_password(struct peer *peer, const char *password) { struct listnode *node; int ret = 0; struct bgp_listener *listener; /* * Set or unset the password on the listen socket(s). Outbound * connections are taken care of in bgp_connect() below. */ frr_with_privs(&bgpd_privs) { for (ALL_LIST_ELEMENTS_RO(bm->listen_sockets, node, listener)) if (listener->su.sa.sa_family == peer->su.sa.sa_family) { uint16_t prefixlen = peer->su.sa.sa_family == AF_INET ? IPV4_MAX_PREFIXLEN : IPV6_MAX_PREFIXLEN; ret = bgp_md5_set_socket(listener->fd, &peer->su, prefixlen, password); break; } } return ret; } int bgp_md5_set_prefix(struct prefix *p, const char *password) { int ret = 0; union sockunion su; struct listnode *node; struct bgp_listener *listener; /* Set or unset the password on the listen socket(s). */ frr_with_privs(&bgpd_privs) { for (ALL_LIST_ELEMENTS_RO(bm->listen_sockets, node, listener)) if (listener->su.sa.sa_family == p->family) { prefix2sockunion(p, &su); ret = bgp_md5_set_socket(listener->fd, &su, p->prefixlen, password); break; } } return ret; } int bgp_md5_unset_prefix(struct prefix *p) { return bgp_md5_set_prefix(p, NULL); } int bgp_md5_set(struct peer *peer) { /* Set the password from listen socket. */ return bgp_md5_set_password(peer, peer->password); } int bgp_md5_unset(struct peer *peer) { /* Unset the password from listen socket. */ return bgp_md5_set_password(peer, NULL); } int bgp_set_socket_ttl(struct peer *peer, int bgp_sock) { char buf[INET_ADDRSTRLEN]; int ret = 0; /* In case of peer is EBGP, we should set TTL for this connection. */ if (!peer->gtsm_hops && (peer_sort(peer) == BGP_PEER_EBGP)) { ret = sockopt_ttl(peer->su.sa.sa_family, bgp_sock, peer->ttl); if (ret) { flog_err( EC_LIB_SOCKET, "%s: Can't set TxTTL on peer (rtrid %s) socket, err = %d", __func__, inet_ntop(AF_INET, &peer->remote_id, buf, sizeof(buf)), errno); return ret; } } else if (peer->gtsm_hops) { /* On Linux, setting minttl without setting ttl seems to mess with the outgoing ttl. Therefore setting both. */ ret = sockopt_ttl(peer->su.sa.sa_family, bgp_sock, MAXTTL); if (ret) { flog_err( EC_LIB_SOCKET, "%s: Can't set TxTTL on peer (rtrid %s) socket, err = %d", __func__, inet_ntop(AF_INET, &peer->remote_id, buf, sizeof(buf)), errno); return ret; } ret = sockopt_minttl(peer->su.sa.sa_family, bgp_sock, MAXTTL + 1 - peer->gtsm_hops); if (ret) { flog_err( EC_LIB_SOCKET, "%s: Can't set MinTTL on peer (rtrid %s) socket, err = %d", __func__, inet_ntop(AF_INET, &peer->remote_id, buf, sizeof(buf)), errno); return ret; } } return ret; } /* * Obtain the BGP instance that the incoming connection should be processed * against. This is important because more than one VRF could be using the * same IP address space. The instance is got by obtaining the device to * which the incoming connection is bound to. This could either be a VRF * or it could be an interface, which in turn determines the VRF. */ static int bgp_get_instance_for_inc_conn(int sock, struct bgp **bgp_inst) { #ifndef SO_BINDTODEVICE /* only Linux has SO_BINDTODEVICE, but we're in Linux-specific code here * anyway since the assumption is that the interface name returned by * getsockopt() is useful in identifying the VRF, particularly with * Linux's * VRF l3master device. The whole mechanism is specific to Linux, so... * when other platforms add VRF support, this will need handling here as * well. (or, some restructuring) */ *bgp_inst = bgp_get_default(); return !*bgp_inst; #else char name[VRF_NAMSIZ + 1]; socklen_t name_len = VRF_NAMSIZ; struct bgp *bgp; int rc; struct listnode *node, *nnode; *bgp_inst = NULL; name[0] = '\0'; rc = getsockopt(sock, SOL_SOCKET, SO_BINDTODEVICE, name, &name_len); if (rc != 0) { #if defined(HAVE_CUMULUS) flog_err(EC_LIB_SOCKET, "[Error] BGP SO_BINDTODEVICE get failed (%s), sock %d", safe_strerror(errno), sock); return -1; #endif } if (!strlen(name)) { *bgp_inst = bgp_get_default(); return 0; /* default instance. */ } /* First try match to instance; if that fails, check for interfaces. */ bgp = bgp_lookup_by_name(name); if (bgp) { if (!bgp->vrf_id) // unexpected return -1; *bgp_inst = bgp; return 0; } /* TODO - This will be optimized once interfaces move into the NS */ for (ALL_LIST_ELEMENTS(bm->bgp, node, nnode, bgp)) { struct interface *ifp; if (bgp->inst_type == BGP_INSTANCE_TYPE_VIEW) continue; ifp = if_lookup_by_name(name, bgp->vrf_id); if (ifp) { *bgp_inst = bgp; return 0; } } /* We didn't match to either an instance or an interface. */ return -1; #endif } /* Accept bgp connection. */ static int bgp_accept(struct thread *thread) { int bgp_sock; int accept_sock; union sockunion su; struct bgp_listener *listener = THREAD_ARG(thread); struct peer *peer; struct peer *peer1; char buf[SU_ADDRSTRLEN]; struct bgp *bgp = NULL; sockunion_init(&su); /* Register accept thread. */ accept_sock = THREAD_FD(thread); if (accept_sock < 0) { flog_err_sys(EC_LIB_SOCKET, "accept_sock is nevative value %d", accept_sock); return -1; } listener->thread = NULL; thread_add_read(bm->master, bgp_accept, listener, accept_sock, &listener->thread); /* Accept client connection. */ bgp_sock = sockunion_accept(accept_sock, &su); if (bgp_sock < 0) { flog_err_sys(EC_LIB_SOCKET, "[Error] BGP socket accept failed (%s)", safe_strerror(errno)); return -1; } set_nonblocking(bgp_sock); /* Obtain BGP instance this connection is meant for. * - if it is a VRF netns sock, then BGP is in listener structure * - otherwise, the bgp instance need to be demultiplexed */ if (listener->bgp) bgp = listener->bgp; else if (bgp_get_instance_for_inc_conn(bgp_sock, &bgp)) { if (bgp_debug_neighbor_events(NULL)) zlog_debug( "[Event] Could not get instance for incoming conn from %s", inet_sutop(&su, buf)); close(bgp_sock); return -1; } /* Set socket send buffer size */ setsockopt_so_sendbuf(bgp_sock, BGP_SOCKET_SNDBUF_SIZE); /* Check remote IP address */ peer1 = peer_lookup(bgp, &su); if (!peer1) { peer1 = peer_lookup_dynamic_neighbor(bgp, &su); if (peer1) { /* Dynamic neighbor has been created, let it proceed */ peer1->fd = bgp_sock; bgp_fsm_change_status(peer1, Active); BGP_TIMER_OFF( peer1->t_start); /* created in peer_create() */ if (peer_active(peer1)) BGP_EVENT_ADD(peer1, TCP_connection_open); return 0; } } if (!peer1) { if (bgp_debug_neighbor_events(NULL)) { zlog_debug( "[Event] %s connection rejected - not configured" " and not valid for dynamic", inet_sutop(&su, buf)); } close(bgp_sock); return -1; } if (CHECK_FLAG(peer1->flags, PEER_FLAG_SHUTDOWN)) { if (bgp_debug_neighbor_events(peer1)) zlog_debug( "[Event] connection from %s rejected due to admin shutdown", inet_sutop(&su, buf)); close(bgp_sock); return -1; } /* * Do not accept incoming connections in Clearing state. This can result * in incorect state transitions - e.g., the connection goes back to * Established and then the Clearing_Completed event is generated. Also, * block incoming connection in Deleted state. */ if (peer1->status == Clearing || peer1->status == Deleted) { if (bgp_debug_neighbor_events(peer1)) zlog_debug( "[Event] Closing incoming conn for %s (%p) state %d", peer1->host, peer1, peer1->status); close(bgp_sock); return -1; } /* Check that at least one AF is activated for the peer. */ if (!peer_active(peer1)) { if (bgp_debug_neighbor_events(peer1)) zlog_debug( "%s - incoming conn rejected - no AF activated for peer", peer1->host); close(bgp_sock); return -1; } /* Do not try to reconnect if the peer reached maximum * prefixes, restart timer is still running or the peer * is shutdown. */ if (BGP_PEER_START_SUPPRESSED(peer1)) { if (bgp_debug_neighbor_events(peer1)) zlog_debug( "[Event] Incoming BGP connection rejected from %s " "due to maximum-prefix or shutdown", peer1->host); close(bgp_sock); return -1; } if (bgp_debug_neighbor_events(peer1)) zlog_debug("[Event] BGP connection from host %s fd %d", inet_sutop(&su, buf), bgp_sock); if (peer1->doppelganger) { /* We have an existing connection. Kill the existing one and run with this one. */ if (bgp_debug_neighbor_events(peer1)) zlog_debug( "[Event] New active connection from peer %s, Killing" " previous active connection", peer1->host); peer_delete(peer1->doppelganger); } if (bgp_set_socket_ttl(peer1, bgp_sock) < 0) if (bgp_debug_neighbor_events(peer1)) zlog_debug( "[Event] Unable to set min/max TTL on peer %s, Continuing", peer1->host); peer = peer_create(&su, peer1->conf_if, peer1->bgp, peer1->local_as, peer1->as, peer1->as_type, 0, 0, NULL); hash_release(peer->bgp->peerhash, peer); hash_get(peer->bgp->peerhash, peer, hash_alloc_intern); peer_xfer_config(peer, peer1); UNSET_FLAG(peer->flags, PEER_FLAG_CONFIG_NODE); peer->doppelganger = peer1; peer1->doppelganger = peer; peer->fd = bgp_sock; vrf_bind(peer->bgp->vrf_id, bgp_sock, bgp_get_bound_name(peer)); bgp_fsm_change_status(peer, Active); BGP_TIMER_OFF(peer->t_start); /* created in peer_create() */ SET_FLAG(peer->sflags, PEER_STATUS_ACCEPT_PEER); /* Make dummy peer until read Open packet. */ if (peer1->status == Established && CHECK_FLAG(peer1->sflags, PEER_STATUS_NSF_MODE)) { /* If we have an existing established connection with graceful * restart * capability announced with one or more address families, then * drop * existing established connection and move state to connect. */ peer1->last_reset = PEER_DOWN_NSF_CLOSE_SESSION; SET_FLAG(peer1->sflags, PEER_STATUS_NSF_WAIT); bgp_event_update(peer1, TCP_connection_closed); } if (peer_active(peer)) { BGP_EVENT_ADD(peer, TCP_connection_open); } return 0; } /* BGP socket bind. */ static char *bgp_get_bound_name(struct peer *peer) { char *name = NULL; if (!peer) return NULL; if ((peer->bgp->vrf_id == VRF_DEFAULT) && !peer->ifname && !peer->conf_if) return NULL; if (peer->su.sa.sa_family != AF_INET && peer->su.sa.sa_family != AF_INET6) return NULL; // unexpected /* For IPv6 peering, interface (unnumbered or link-local with interface) * takes precedence over VRF. For IPv4 peering, explicit interface or * VRF are the situations to bind. */ if (peer->su.sa.sa_family == AF_INET6) name = (peer->conf_if ? peer->conf_if : (peer->ifname ? peer->ifname : peer->bgp->name)); else name = peer->ifname ? peer->ifname : peer->bgp->name; return name; } static int bgp_update_address(struct interface *ifp, const union sockunion *dst, union sockunion *addr) { struct prefix *p, *sel, d; struct connected *connected; struct listnode *node; int common; sockunion2hostprefix(dst, &d); sel = NULL; common = -1; for (ALL_LIST_ELEMENTS_RO(ifp->connected, node, connected)) { p = connected->address; if (p->family != d.family) continue; if (prefix_common_bits(p, &d) > common) { sel = p; common = prefix_common_bits(sel, &d); } } if (!sel) return 1; prefix2sockunion(sel, addr); return 0; } /* Update source selection. */ static int bgp_update_source(struct peer *peer) { struct interface *ifp; union sockunion addr; int ret = 0; sockunion_init(&addr); /* Source is specified with interface name. */ if (peer->update_if) { ifp = if_lookup_by_name(peer->update_if, peer->bgp->vrf_id); if (!ifp) return -1; if (bgp_update_address(ifp, &peer->su, &addr)) return -1; ret = sockunion_bind(peer->fd, &addr, 0, &addr); } /* Source is specified with IP address. */ if (peer->update_source) ret = sockunion_bind(peer->fd, peer->update_source, 0, peer->update_source); return ret; } /* BGP try to connect to the peer. */ int bgp_connect(struct peer *peer) { assert(!CHECK_FLAG(peer->thread_flags, PEER_THREAD_WRITES_ON)); assert(!CHECK_FLAG(peer->thread_flags, PEER_THREAD_READS_ON)); ifindex_t ifindex = 0; if (peer->conf_if && BGP_PEER_SU_UNSPEC(peer)) { zlog_debug("Peer address not learnt: Returning from connect"); return 0; } frr_with_privs(&bgpd_privs) { /* Make socket for the peer. */ peer->fd = vrf_sockunion_socket(&peer->su, peer->bgp->vrf_id, bgp_get_bound_name(peer)); } if (peer->fd < 0) return -1; set_nonblocking(peer->fd); /* Set socket send buffer size */ setsockopt_so_sendbuf(peer->fd, BGP_SOCKET_SNDBUF_SIZE); if (bgp_set_socket_ttl(peer, peer->fd) < 0) return -1; sockopt_reuseaddr(peer->fd); sockopt_reuseport(peer->fd); #ifdef IPTOS_PREC_INTERNETCONTROL frr_with_privs(&bgpd_privs) { if (sockunion_family(&peer->su) == AF_INET) setsockopt_ipv4_tos(peer->fd, IPTOS_PREC_INTERNETCONTROL); else if (sockunion_family(&peer->su) == AF_INET6) setsockopt_ipv6_tclass(peer->fd, IPTOS_PREC_INTERNETCONTROL); } #endif if (peer->password) { uint16_t prefixlen = peer->su.sa.sa_family == AF_INET ? IPV4_MAX_PREFIXLEN : IPV6_MAX_PREFIXLEN; bgp_md5_set_connect(peer->fd, &peer->su, prefixlen, peer->password); } /* Update source bind. */ if (bgp_update_source(peer) < 0) { return connect_error; } if (peer->conf_if || peer->ifname) ifindex = ifname2ifindex(peer->conf_if ? peer->conf_if : peer->ifname, peer->bgp->vrf_id); if (bgp_debug_neighbor_events(peer)) zlog_debug("%s [Event] Connect start to %s fd %d", peer->host, peer->host, peer->fd); /* Connect to the remote peer. */ return sockunion_connect(peer->fd, &peer->su, htons(peer->port), ifindex); } /* After TCP connection is established. Get local address and port. */ int bgp_getsockname(struct peer *peer) { if (peer->su_local) { sockunion_free(peer->su_local); peer->su_local = NULL; } if (peer->su_remote) { sockunion_free(peer->su_remote); peer->su_remote = NULL; } peer->su_local = sockunion_getsockname(peer->fd); if (!peer->su_local) return -1; peer->su_remote = sockunion_getpeername(peer->fd); if (!peer->su_remote) return -1; if (!bgp_zebra_nexthop_set(peer->su_local, peer->su_remote, &peer->nexthop, peer)) { flog_err(EC_BGP_NH_UPD, "%s: nexthop_set failed, resetting connection - intf %p", peer->host, peer->nexthop.ifp); return -1; } return 0; } static int bgp_listener(int sock, struct sockaddr *sa, socklen_t salen, struct bgp *bgp) { struct bgp_listener *listener; int ret, en; sockopt_reuseaddr(sock); sockopt_reuseport(sock); frr_with_privs(&bgpd_privs) { #ifdef IPTOS_PREC_INTERNETCONTROL if (sa->sa_family == AF_INET) setsockopt_ipv4_tos(sock, IPTOS_PREC_INTERNETCONTROL); else if (sa->sa_family == AF_INET6) setsockopt_ipv6_tclass(sock, IPTOS_PREC_INTERNETCONTROL); #endif sockopt_v6only(sa->sa_family, sock); ret = bind(sock, sa, salen); en = errno; } if (ret < 0) { flog_err_sys(EC_LIB_SOCKET, "bind: %s", safe_strerror(en)); return ret; } ret = listen(sock, SOMAXCONN); if (ret < 0) { flog_err_sys(EC_LIB_SOCKET, "listen: %s", safe_strerror(errno)); return ret; } listener = XCALLOC(MTYPE_BGP_LISTENER, sizeof(*listener)); listener->fd = sock; /* this socket needs a change of ns. record bgp back pointer */ if (bgp->vrf_id != VRF_DEFAULT && vrf_is_backend_netns()) listener->bgp = bgp; memcpy(&listener->su, sa, salen); listener->thread = NULL; thread_add_read(bm->master, bgp_accept, listener, sock, &listener->thread); listnode_add(bm->listen_sockets, listener); return 0; } /* IPv6 supported version of BGP server socket setup. */ int bgp_socket(struct bgp *bgp, unsigned short port, const char *address) { struct addrinfo *ainfo; struct addrinfo *ainfo_save; static const struct addrinfo req = { .ai_family = AF_UNSPEC, .ai_flags = AI_PASSIVE, .ai_socktype = SOCK_STREAM, }; int ret, count; char port_str[BUFSIZ]; snprintf(port_str, sizeof(port_str), "%d", port); port_str[sizeof(port_str) - 1] = '\0'; frr_with_privs(&bgpd_privs) { ret = vrf_getaddrinfo(address, port_str, &req, &ainfo_save, bgp->vrf_id); } if (ret != 0) { flog_err_sys(EC_LIB_SOCKET, "getaddrinfo: %s", gai_strerror(ret)); return -1; } if (bgp_option_check(BGP_OPT_NO_ZEBRA) && bgp->vrf_id != VRF_DEFAULT) { freeaddrinfo(ainfo_save); return -1; } count = 0; for (ainfo = ainfo_save; ainfo; ainfo = ainfo->ai_next) { int sock; if (ainfo->ai_family != AF_INET && ainfo->ai_family != AF_INET6) continue; frr_with_privs(&bgpd_privs) { sock = vrf_socket(ainfo->ai_family, ainfo->ai_socktype, ainfo->ai_protocol, bgp->vrf_id, (bgp->inst_type == BGP_INSTANCE_TYPE_VRF ? bgp->name : NULL)); } if (sock < 0) { flog_err_sys(EC_LIB_SOCKET, "socket: %s", safe_strerror(errno)); continue; } /* if we intend to implement ttl-security, this socket needs * ttl=255 */ sockopt_ttl(ainfo->ai_family, sock, MAXTTL); ret = bgp_listener(sock, ainfo->ai_addr, ainfo->ai_addrlen, bgp); if (ret == 0) ++count; else close(sock); } freeaddrinfo(ainfo_save); if (count == 0 && bgp->inst_type != BGP_INSTANCE_TYPE_VRF) { flog_err( EC_LIB_SOCKET, "%s: no usable addresses please check other programs usage of specified port %d", __func__, port); flog_err_sys(EC_LIB_SOCKET, "%s: Program cannot continue", __func__); exit(-1); } return 0; } /* this function closes vrf socket * this should be called only for vrf socket with netns backend */ void bgp_close_vrf_socket(struct bgp *bgp) { struct listnode *node, *next; struct bgp_listener *listener; if (!bgp) return; if (bm->listen_sockets == NULL) return; for (ALL_LIST_ELEMENTS(bm->listen_sockets, node, next, listener)) { if (listener->bgp == bgp) { thread_cancel(listener->thread); close(listener->fd); listnode_delete(bm->listen_sockets, listener); XFREE(MTYPE_BGP_LISTENER, listener); } } } /* this function closes main socket */ void bgp_close(void) { struct listnode *node, *next; struct bgp_listener *listener; if (bm->listen_sockets == NULL) return; for (ALL_LIST_ELEMENTS(bm->listen_sockets, node, next, listener)) { if (listener->bgp) continue; thread_cancel(listener->thread); close(listener->fd); listnode_delete(bm->listen_sockets, listener); XFREE(MTYPE_BGP_LISTENER, listener); } } frr-7.2.1/bgpd/bgp_network.h0000644000000000000000000000270113610377563012622 00000000000000/* BGP network related header * Copyright (C) 1999 Kunihiro Ishiguro * * This file is part of GNU Zebra. * * GNU Zebra is free software; you can redistribute it and/or modify it * under the terms of the GNU General Public License as published by the * Free Software Foundation; either version 2, or (at your option) any * later version. * * GNU Zebra 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; see the file COPYING; if not, write to the Free Software * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA */ #ifndef _QUAGGA_BGP_NETWORK_H #define _QUAGGA_BGP_NETWORK_H #define BGP_SOCKET_SNDBUF_SIZE 65536 extern int bgp_socket(struct bgp *bgp, unsigned short port, const char *address); extern void bgp_close_vrf_socket(struct bgp *bgp); extern void bgp_close(void); extern int bgp_connect(struct peer *); extern int bgp_getsockname(struct peer *); extern int bgp_md5_set_prefix(struct prefix *p, const char *password); extern int bgp_md5_unset_prefix(struct prefix *p); extern int bgp_md5_set(struct peer *); extern int bgp_md5_unset(struct peer *); extern int bgp_set_socket_ttl(struct peer *, int fd); #endif /* _QUAGGA_BGP_NETWORK_H */ frr-7.2.1/bgpd/bgp_nexthop.c0000644000000000000000000004607713610377563012627 00000000000000/* BGP nexthop scan * Copyright (C) 2000 Kunihiro Ishiguro * * This file is part of GNU Zebra. * * GNU Zebra is free software; you can redistribute it and/or modify it * under the terms of the GNU General Public License as published by the * Free Software Foundation; either version 2, or (at your option) any * later version. * * GNU Zebra 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; see the file COPYING; if not, write to the Free Software * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA */ #include #include "command.h" #include "thread.h" #include "prefix.h" #include "zclient.h" #include "stream.h" #include "network.h" #include "log.h" #include "memory.h" #include "hash.h" #include "jhash.h" #include "nexthop.h" #include "queue.h" #include "filter.h" #include "bgpd/bgpd.h" #include "bgpd/bgp_table.h" #include "bgpd/bgp_route.h" #include "bgpd/bgp_attr.h" #include "bgpd/bgp_nexthop.h" #include "bgpd/bgp_nht.h" #include "bgpd/bgp_debug.h" #include "bgpd/bgp_damp.h" #include "bgpd/bgp_fsm.h" #include "bgpd/bgp_vty.h" DEFINE_MTYPE_STATIC(BGPD, MARTIAN_STRING, "BGP Martian Address Intf String"); char *bnc_str(struct bgp_nexthop_cache *bnc, char *buf, int size) { prefix2str(&(bnc->node->p), buf, size); return buf; } void bnc_nexthop_free(struct bgp_nexthop_cache *bnc) { nexthops_free(bnc->nexthop); } struct bgp_nexthop_cache *bnc_new(void) { struct bgp_nexthop_cache *bnc; bnc = XCALLOC(MTYPE_BGP_NEXTHOP_CACHE, sizeof(struct bgp_nexthop_cache)); LIST_INIT(&(bnc->paths)); return bnc; } void bnc_free(struct bgp_nexthop_cache *bnc) { bnc_nexthop_free(bnc); XFREE(MTYPE_BGP_NEXTHOP_CACHE, bnc); } /* Reset and free all BGP nexthop cache. */ static void bgp_nexthop_cache_reset(struct bgp_table *table) { struct bgp_node *rn; struct bgp_nexthop_cache *bnc; for (rn = bgp_table_top(table); rn; rn = bgp_route_next(rn)) { bnc = bgp_node_get_bgp_nexthop_info(rn); if (!bnc) continue; while (!LIST_EMPTY(&(bnc->paths))) { struct bgp_path_info *path = LIST_FIRST(&(bnc->paths)); path_nh_map(path, bnc, false); } bnc_free(bnc); bgp_node_set_bgp_nexthop_info(rn, NULL); bgp_unlock_node(rn); } } static void *bgp_tip_hash_alloc(void *p) { const struct in_addr *val = (const struct in_addr *)p; struct tip_addr *addr; addr = XMALLOC(MTYPE_TIP_ADDR, sizeof(struct tip_addr)); addr->refcnt = 0; addr->addr.s_addr = val->s_addr; return addr; } static void bgp_tip_hash_free(void *addr) { XFREE(MTYPE_TIP_ADDR, addr); } static unsigned int bgp_tip_hash_key_make(const void *p) { const struct tip_addr *addr = p; return jhash_1word(addr->addr.s_addr, 0); } static bool bgp_tip_hash_cmp(const void *p1, const void *p2) { const struct tip_addr *addr1 = p1; const struct tip_addr *addr2 = p2; return addr1->addr.s_addr == addr2->addr.s_addr; } void bgp_tip_hash_init(struct bgp *bgp) { bgp->tip_hash = hash_create(bgp_tip_hash_key_make, bgp_tip_hash_cmp, "BGP TIP hash"); } void bgp_tip_hash_destroy(struct bgp *bgp) { if (bgp->tip_hash == NULL) return; hash_clean(bgp->tip_hash, bgp_tip_hash_free); hash_free(bgp->tip_hash); bgp->tip_hash = NULL; } void bgp_tip_add(struct bgp *bgp, struct in_addr *tip) { struct tip_addr tmp; struct tip_addr *addr; tmp.addr = *tip; addr = hash_get(bgp->tip_hash, &tmp, bgp_tip_hash_alloc); if (!addr) return; addr->refcnt++; } void bgp_tip_del(struct bgp *bgp, struct in_addr *tip) { struct tip_addr tmp; struct tip_addr *addr; tmp.addr = *tip; addr = hash_lookup(bgp->tip_hash, &tmp); /* may have been deleted earlier by bgp_interface_down() */ if (addr == NULL) return; addr->refcnt--; if (addr->refcnt == 0) { hash_release(bgp->tip_hash, addr); XFREE(MTYPE_TIP_ADDR, addr); } } /* BGP own address structure */ struct bgp_addr { struct in_addr addr; struct list *ifp_name_list; }; static void show_address_entry(struct hash_bucket *bucket, void *args) { struct vty *vty = (struct vty *)args; struct bgp_addr *addr = (struct bgp_addr *)bucket->data; char *name; struct listnode *node; vty_out(vty, "addr: %s, count: %d : ", inet_ntoa(addr->addr), addr->ifp_name_list->count); for (ALL_LIST_ELEMENTS_RO(addr->ifp_name_list, node, name)) { vty_out(vty, " %s,", name); } vty_out(vty, "\n"); } void bgp_nexthop_show_address_hash(struct vty *vty, struct bgp *bgp) { hash_iterate(bgp->address_hash, (void (*)(struct hash_bucket *, void *))show_address_entry, vty); } static void bgp_address_hash_string_del(void *val) { char *data = val; XFREE(MTYPE_MARTIAN_STRING, data); } static void *bgp_address_hash_alloc(void *p) { const struct in_addr *val = (const struct in_addr *)p; struct bgp_addr *addr; addr = XMALLOC(MTYPE_BGP_ADDR, sizeof(struct bgp_addr)); addr->addr.s_addr = val->s_addr; addr->ifp_name_list = list_new(); addr->ifp_name_list->del = bgp_address_hash_string_del; return addr; } static void bgp_address_hash_free(void *data) { struct bgp_addr *addr = data; list_delete(&addr->ifp_name_list); XFREE(MTYPE_BGP_ADDR, addr); } static unsigned int bgp_address_hash_key_make(const void *p) { const struct bgp_addr *addr = p; return jhash_1word(addr->addr.s_addr, 0); } static bool bgp_address_hash_cmp(const void *p1, const void *p2) { const struct bgp_addr *addr1 = p1; const struct bgp_addr *addr2 = p2; return addr1->addr.s_addr == addr2->addr.s_addr; } void bgp_address_init(struct bgp *bgp) { bgp->address_hash = hash_create(bgp_address_hash_key_make, bgp_address_hash_cmp, "BGP Address Hash"); } void bgp_address_destroy(struct bgp *bgp) { if (bgp->address_hash == NULL) return; hash_clean(bgp->address_hash, bgp_address_hash_free); hash_free(bgp->address_hash); bgp->address_hash = NULL; } static void bgp_address_add(struct bgp *bgp, struct connected *ifc, struct prefix *p) { struct bgp_addr tmp; struct bgp_addr *addr; struct listnode *node; char *name; tmp.addr = p->u.prefix4; addr = hash_get(bgp->address_hash, &tmp, bgp_address_hash_alloc); for (ALL_LIST_ELEMENTS_RO(addr->ifp_name_list, node, name)) { if (strcmp(ifc->ifp->name, name) == 0) break; } if (!node) { name = XSTRDUP(MTYPE_MARTIAN_STRING, ifc->ifp->name); listnode_add(addr->ifp_name_list, name); } } static void bgp_address_del(struct bgp *bgp, struct connected *ifc, struct prefix *p) { struct bgp_addr tmp; struct bgp_addr *addr; struct listnode *node; char *name; tmp.addr = p->u.prefix4; addr = hash_lookup(bgp->address_hash, &tmp); /* may have been deleted earlier by bgp_interface_down() */ if (addr == NULL) return; for (ALL_LIST_ELEMENTS_RO(addr->ifp_name_list, node, name)) { if (strcmp(ifc->ifp->name, name) == 0) break; } if (node) { list_delete_node(addr->ifp_name_list, node); XFREE(MTYPE_MARTIAN_STRING, name); } if (addr->ifp_name_list->count == 0) { hash_release(bgp->address_hash, addr); list_delete(&addr->ifp_name_list); XFREE(MTYPE_BGP_ADDR, addr); } } struct bgp_connected_ref { unsigned int refcnt; }; void bgp_connected_add(struct bgp *bgp, struct connected *ifc) { struct prefix p; struct prefix *addr; struct bgp_node *rn; struct bgp_connected_ref *bc; struct listnode *node, *nnode; struct peer *peer; addr = ifc->address; p = *(CONNECTED_PREFIX(ifc)); if (addr->family == AF_INET) { apply_mask_ipv4((struct prefix_ipv4 *)&p); if (prefix_ipv4_any((struct prefix_ipv4 *)&p)) return; bgp_address_add(bgp, ifc, addr); rn = bgp_node_get(bgp->connected_table[AFI_IP], (struct prefix *)&p); bc = bgp_node_get_bgp_connected_ref_info(rn); if (bc) bc->refcnt++; else { bc = XCALLOC(MTYPE_BGP_CONN, sizeof(struct bgp_connected_ref)); bc->refcnt = 1; bgp_node_set_bgp_connected_ref_info(rn, bc); } for (ALL_LIST_ELEMENTS(bgp->peer, node, nnode, peer)) { if (peer->conf_if && (strcmp(peer->conf_if, ifc->ifp->name) == 0) && peer->status != Established && !CHECK_FLAG(peer->flags, PEER_FLAG_IFPEER_V6ONLY)) { if (peer_active(peer)) BGP_EVENT_ADD(peer, BGP_Stop); BGP_EVENT_ADD(peer, BGP_Start); } } } else if (addr->family == AF_INET6) { apply_mask_ipv6((struct prefix_ipv6 *)&p); if (IN6_IS_ADDR_UNSPECIFIED(&p.u.prefix6)) return; if (IN6_IS_ADDR_LINKLOCAL(&p.u.prefix6)) return; rn = bgp_node_get(bgp->connected_table[AFI_IP6], (struct prefix *)&p); bc = bgp_node_get_bgp_connected_ref_info(rn); if (bc) bc->refcnt++; else { bc = XCALLOC(MTYPE_BGP_CONN, sizeof(struct bgp_connected_ref)); bc->refcnt = 1; bgp_node_set_bgp_connected_ref_info(rn, bc); } } } void bgp_connected_delete(struct bgp *bgp, struct connected *ifc) { struct prefix p; struct prefix *addr; struct bgp_node *rn = NULL; struct bgp_connected_ref *bc; addr = ifc->address; p = *(CONNECTED_PREFIX(ifc)); apply_mask(&p); if (addr->family == AF_INET) { if (prefix_ipv4_any((struct prefix_ipv4 *)&p)) return; bgp_address_del(bgp, ifc, addr); rn = bgp_node_lookup(bgp->connected_table[AFI_IP], &p); } else if (addr->family == AF_INET6) { if (IN6_IS_ADDR_UNSPECIFIED(&p.u.prefix6)) return; if (IN6_IS_ADDR_LINKLOCAL(&p.u.prefix6)) return; rn = bgp_node_lookup(bgp->connected_table[AFI_IP6], (struct prefix *)&p); } if (!rn) return; bc = bgp_node_get_bgp_connected_ref_info(rn); bc->refcnt--; if (bc->refcnt == 0) { XFREE(MTYPE_BGP_CONN, bc); bgp_node_set_bgp_connected_ref_info(rn, NULL); } bgp_unlock_node(rn); bgp_unlock_node(rn); } static void bgp_connected_cleanup(struct route_table *table, struct route_node *rn) { struct bgp_connected_ref *bc; struct bgp_node *bn = bgp_node_from_rnode(rn); bc = bgp_node_get_bgp_connected_ref_info(bn); if (!bc) return; bc->refcnt--; if (bc->refcnt == 0) { XFREE(MTYPE_BGP_CONN, bc); bgp_node_set_bgp_connected_ref_info(bn, NULL); } } int bgp_nexthop_self(struct bgp *bgp, struct in_addr nh_addr) { struct bgp_addr tmp, *addr; struct tip_addr tmp_tip, *tip; tmp.addr = nh_addr; addr = hash_lookup(bgp->address_hash, &tmp); if (addr) return 1; tmp_tip.addr = nh_addr; tip = hash_lookup(bgp->tip_hash, &tmp_tip); if (tip) return 1; return 0; } int bgp_multiaccess_check_v4(struct in_addr nexthop, struct peer *peer) { struct bgp_node *rn1; struct bgp_node *rn2; struct prefix p; int ret; p.family = AF_INET; p.prefixlen = IPV4_MAX_BITLEN; p.u.prefix4 = nexthop; rn1 = bgp_node_match(peer->bgp->connected_table[AFI_IP], &p); if (!rn1) return 0; p.family = AF_INET; p.prefixlen = IPV4_MAX_BITLEN; p.u.prefix4 = peer->su.sin.sin_addr; rn2 = bgp_node_match(peer->bgp->connected_table[AFI_IP], &p); if (!rn2) { bgp_unlock_node(rn1); return 0; } ret = (rn1 == rn2) ? 1 : 0; bgp_unlock_node(rn1); bgp_unlock_node(rn2); return (ret); } int bgp_multiaccess_check_v6(struct in6_addr nexthop, struct peer *peer) { struct bgp_node *rn1; struct bgp_node *rn2; struct prefix p; int ret; p.family = AF_INET6; p.prefixlen = IPV6_MAX_BITLEN; p.u.prefix6 = nexthop; rn1 = bgp_node_match(peer->bgp->connected_table[AFI_IP6], &p); if (!rn1) return 0; p.family = AF_INET6; p.prefixlen = IPV6_MAX_BITLEN; p.u.prefix6 = peer->su.sin6.sin6_addr; rn2 = bgp_node_match(peer->bgp->connected_table[AFI_IP6], &p); if (!rn2) { bgp_unlock_node(rn1); return 0; } ret = (rn1 == rn2) ? 1 : 0; bgp_unlock_node(rn1); bgp_unlock_node(rn2); return ret; } int bgp_subgrp_multiaccess_check_v6(struct in6_addr nexthop, struct update_subgroup *subgrp) { struct bgp_node *rn1 = NULL, *rn2 = NULL; struct peer_af *paf = NULL; struct prefix p = {0}, np = {0}; struct bgp *bgp = NULL; np.family = AF_INET6; np.prefixlen = IPV6_MAX_BITLEN; np.u.prefix6 = nexthop; p.family = AF_INET; p.prefixlen = IPV6_MAX_BITLEN; bgp = SUBGRP_INST(subgrp); rn1 = bgp_node_match(bgp->connected_table[AFI_IP6], &np); if (!rn1) return 0; SUBGRP_FOREACH_PEER (subgrp, paf) { p.u.prefix6 = paf->peer->su.sin6.sin6_addr; rn2 = bgp_node_match(bgp->connected_table[AFI_IP6], &p); if (rn1 == rn2) { bgp_unlock_node(rn1); bgp_unlock_node(rn2); return 1; } if (rn2) bgp_unlock_node(rn2); } bgp_unlock_node(rn1); return 0; } int bgp_subgrp_multiaccess_check_v4(struct in_addr nexthop, struct update_subgroup *subgrp) { struct bgp_node *rn1, *rn2; struct peer_af *paf; struct prefix p, np; struct bgp *bgp; np.family = AF_INET; np.prefixlen = IPV4_MAX_BITLEN; np.u.prefix4 = nexthop; p.family = AF_INET; p.prefixlen = IPV4_MAX_BITLEN; bgp = SUBGRP_INST(subgrp); rn1 = bgp_node_match(bgp->connected_table[AFI_IP], &np); if (!rn1) return 0; SUBGRP_FOREACH_PEER (subgrp, paf) { p.u.prefix4 = paf->peer->su.sin.sin_addr; rn2 = bgp_node_match(bgp->connected_table[AFI_IP], &p); if (rn1 == rn2) { bgp_unlock_node(rn1); bgp_unlock_node(rn2); return 1; } if (rn2) bgp_unlock_node(rn2); } bgp_unlock_node(rn1); return 0; } static void bgp_show_nexthops_detail(struct vty *vty, struct bgp *bgp, struct bgp_nexthop_cache *bnc) { char buf[PREFIX2STR_BUFFER]; struct nexthop *nexthop; for (nexthop = bnc->nexthop; nexthop; nexthop = nexthop->next) switch (nexthop->type) { case NEXTHOP_TYPE_IPV6: vty_out(vty, " gate %s\n", inet_ntop(AF_INET6, &nexthop->gate.ipv6, buf, sizeof(buf))); break; case NEXTHOP_TYPE_IPV6_IFINDEX: vty_out(vty, " gate %s, if %s\n", inet_ntop(AF_INET6, &nexthop->gate.ipv6, buf, sizeof(buf)), ifindex2ifname(nexthop->ifindex, bgp->vrf_id)); break; case NEXTHOP_TYPE_IPV4: vty_out(vty, " gate %s\n", inet_ntop(AF_INET, &nexthop->gate.ipv4, buf, sizeof(buf))); break; case NEXTHOP_TYPE_IFINDEX: vty_out(vty, " if %s\n", ifindex2ifname(nexthop->ifindex, bgp->vrf_id)); break; case NEXTHOP_TYPE_IPV4_IFINDEX: vty_out(vty, " gate %s, if %s\n", inet_ntop(AF_INET, &nexthop->gate.ipv4, buf, sizeof(buf)), ifindex2ifname(nexthop->ifindex, bgp->vrf_id)); break; case NEXTHOP_TYPE_BLACKHOLE: vty_out(vty, " blackhole\n"); break; default: vty_out(vty, " invalid nexthop type %u\n", nexthop->type); } } static void bgp_show_nexthops(struct vty *vty, struct bgp *bgp, int detail, bool import_table) { struct bgp_node *rn; struct bgp_nexthop_cache *bnc; char buf[PREFIX2STR_BUFFER]; time_t tbuf; afi_t afi; struct bgp_table **table; if (import_table) vty_out(vty, "Current BGP import check cache:\n"); else vty_out(vty, "Current BGP nexthop cache:\n"); if (import_table) table = bgp->import_check_table; else table = bgp->nexthop_cache_table; for (afi = AFI_IP; afi < AFI_MAX; afi++) { if (!table || !table[afi]) continue; for (rn = bgp_table_top(table[afi]); rn; rn = bgp_route_next(rn)) { struct peer *peer; bnc = bgp_node_get_bgp_nexthop_info(rn); if (!bnc) continue; peer = (struct peer *)bnc->nht_info; if (CHECK_FLAG(bnc->flags, BGP_NEXTHOP_VALID)) { vty_out(vty, " %s valid [IGP metric %d], #paths %d", inet_ntop(rn->p.family, &rn->p.u.prefix, buf, sizeof(buf)), bnc->metric, bnc->path_count); if (peer) vty_out(vty, ", peer %s", peer->host); vty_out(vty, "\n"); if (!detail) continue; bgp_show_nexthops_detail(vty, bgp, bnc); } else { vty_out(vty, " %s invalid", inet_ntop(rn->p.family, &rn->p.u.prefix, buf, sizeof(buf))); if (peer) vty_out(vty, ", peer %s", peer->host); vty_out(vty, "\n"); if (CHECK_FLAG(bnc->flags, BGP_NEXTHOP_CONNECTED)) vty_out(vty, " Must be Connected\n"); if (!CHECK_FLAG(bnc->flags, BGP_NEXTHOP_REGISTERED)) vty_out(vty, " Is not Registered\n"); } tbuf = time(NULL) - (bgp_clock() - bnc->last_update); vty_out(vty, " Last update: %s", ctime(&tbuf)); vty_out(vty, "\n"); } } } static int show_ip_bgp_nexthop_table(struct vty *vty, const char *name, int detail, bool import_table) { struct bgp *bgp; if (name) bgp = bgp_lookup_by_name(name); else bgp = bgp_get_default(); if (!bgp) { vty_out(vty, "%% No such BGP instance exist\n"); return CMD_WARNING; } bgp_show_nexthops(vty, bgp, detail, import_table); return CMD_SUCCESS; } static void bgp_show_all_instances_nexthops_vty(struct vty *vty) { struct listnode *node, *nnode; struct bgp *bgp; for (ALL_LIST_ELEMENTS(bm->bgp, node, nnode, bgp)) { vty_out(vty, "\nInstance %s:\n", (bgp->inst_type == BGP_INSTANCE_TYPE_DEFAULT) ? VRF_DEFAULT_NAME : bgp->name); bgp_show_nexthops(vty, bgp, 0, false); } } DEFUN (show_ip_bgp_nexthop, show_ip_bgp_nexthop_cmd, "show [ip] bgp [ VIEWVRFNAME] nexthop [detail]", SHOW_STR IP_STR BGP_STR BGP_INSTANCE_HELP_STR "BGP nexthop table\n" "Show detailed information\n") { int idx = 0; char *vrf = NULL; if (argv_find(argv, argc, "view", &idx) || argv_find(argv, argc, "vrf", &idx)) vrf = argv[++idx]->arg; int detail = argv_find(argv, argc, "detail", &idx) ? 1 : 0; return show_ip_bgp_nexthop_table(vty, vrf, detail, false); } DEFUN (show_ip_bgp_import_check, show_ip_bgp_import_check_cmd, "show [ip] bgp [ VIEWVRFNAME] import-check-table [detail]", SHOW_STR IP_STR BGP_STR BGP_INSTANCE_HELP_STR "BGP import check table\n" "Show detailed information\n") { int idx = 0; char *vrf = NULL; if (argv_find(argv, argc, "view", &idx) || argv_find(argv, argc, "vrf", &idx)) vrf = argv[++idx]->arg; int detail = argv_find(argv, argc, "detail", &idx) ? 1 : 0; return show_ip_bgp_nexthop_table(vty, vrf, detail, true); } DEFUN (show_ip_bgp_instance_all_nexthop, show_ip_bgp_instance_all_nexthop_cmd, "show [ip] bgp all nexthop", SHOW_STR IP_STR BGP_STR BGP_INSTANCE_ALL_HELP_STR "BGP nexthop table\n") { bgp_show_all_instances_nexthops_vty(vty); return CMD_SUCCESS; } void bgp_scan_init(struct bgp *bgp) { afi_t afi; for (afi = AFI_IP; afi < AFI_MAX; afi++) { bgp->nexthop_cache_table[afi] = bgp_table_init(bgp, afi, SAFI_UNICAST); bgp->connected_table[afi] = bgp_table_init(bgp, afi, SAFI_UNICAST); bgp->import_check_table[afi] = bgp_table_init(bgp, afi, SAFI_UNICAST); } } void bgp_scan_vty_init(void) { install_element(VIEW_NODE, &show_ip_bgp_nexthop_cmd); install_element(VIEW_NODE, &show_ip_bgp_import_check_cmd); install_element(VIEW_NODE, &show_ip_bgp_instance_all_nexthop_cmd); } void bgp_scan_finish(struct bgp *bgp) { afi_t afi; for (afi = AFI_IP; afi < AFI_MAX; afi++) { /* Only the current one needs to be reset. */ bgp_nexthop_cache_reset(bgp->nexthop_cache_table[afi]); bgp_table_unlock(bgp->nexthop_cache_table[afi]); bgp->nexthop_cache_table[afi] = NULL; bgp->connected_table[afi]->route_table->cleanup = bgp_connected_cleanup; bgp_table_unlock(bgp->connected_table[afi]); bgp->connected_table[afi] = NULL; bgp_table_unlock(bgp->import_check_table[afi]); bgp->import_check_table[afi] = NULL; } } frr-7.2.1/bgpd/bgp_nexthop.h0000644000000000000000000000757713610377563012636 00000000000000/* BGP nexthop scan * Copyright (C) 2000 Kunihiro Ishiguro * * This file is part of GNU Zebra. * * GNU Zebra is free software; you can redistribute it and/or modify it * under the terms of the GNU General Public License as published by the * Free Software Foundation; either version 2, or (at your option) any * later version. * * GNU Zebra 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; see the file COPYING; if not, write to the Free Software * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA */ #ifndef _QUAGGA_BGP_NEXTHOP_H #define _QUAGGA_BGP_NEXTHOP_H #include "if.h" #include "queue.h" #include "prefix.h" #define NEXTHOP_FAMILY(nexthop_len) \ (((nexthop_len) == 4 || (nexthop_len) == 12 \ ? AF_INET \ : ((nexthop_len) == 16 || (nexthop_len) == 24 \ || (nexthop_len) == 32 \ || (nexthop_len) == 48 \ ? AF_INET6 \ : AF_UNSPEC))) #define BGP_MP_NEXTHOP_FAMILY NEXTHOP_FAMILY /* BGP nexthop cache value structure. */ struct bgp_nexthop_cache { /* IGP route's metric. */ uint32_t metric; /* Nexthop number and nexthop linked list.*/ uint8_t nexthop_num; struct nexthop *nexthop; time_t last_update; uint16_t flags; #define BGP_NEXTHOP_VALID (1 << 0) #define BGP_NEXTHOP_REGISTERED (1 << 1) #define BGP_NEXTHOP_CONNECTED (1 << 2) #define BGP_NEXTHOP_PEER_NOTIFIED (1 << 3) #define BGP_STATIC_ROUTE (1 << 4) #define BGP_STATIC_ROUTE_EXACT_MATCH (1 << 5) #define BGP_NEXTHOP_LABELED_VALID (1 << 6) uint16_t change_flags; #define BGP_NEXTHOP_CHANGED (1 << 0) #define BGP_NEXTHOP_METRIC_CHANGED (1 << 1) #define BGP_NEXTHOP_CONNECTED_CHANGED (1 << 2) struct bgp_node *node; void *nht_info; /* In BGP, peer session */ LIST_HEAD(path_list, bgp_path_info) paths; unsigned int path_count; struct bgp *bgp; }; /* Own tunnel-ip address structure */ struct tip_addr { struct in_addr addr; int refcnt; }; struct bgp_addrv6 { struct in6_addr addrv6; struct list *ifp_name_list; }; extern void bgp_connected_add(struct bgp *bgp, struct connected *c); extern void bgp_connected_delete(struct bgp *bgp, struct connected *c); extern int bgp_subgrp_multiaccess_check_v4(struct in_addr nexthop, struct update_subgroup *subgrp); extern int bgp_subgrp_multiaccess_check_v6(struct in6_addr nexthop, struct update_subgroup *subgrp); extern int bgp_multiaccess_check_v4(struct in_addr nexthop, struct peer *peer); extern int bgp_multiaccess_check_v6(struct in6_addr nexthop, struct peer *peer); extern int bgp_config_write_scan_time(struct vty *); extern int bgp_nexthop_self(struct bgp *, struct in_addr); extern struct bgp_nexthop_cache *bnc_new(void); extern void bnc_free(struct bgp_nexthop_cache *bnc); extern void bnc_nexthop_free(struct bgp_nexthop_cache *bnc); extern char *bnc_str(struct bgp_nexthop_cache *bnc, char *buf, int size); extern void bgp_scan_init(struct bgp *bgp); extern void bgp_scan_finish(struct bgp *bgp); extern void bgp_scan_vty_init(void); extern void bgp_address_init(struct bgp *bgp); extern void bgp_address_destroy(struct bgp *bgp); extern void bgp_tip_add(struct bgp *bgp, struct in_addr *tip); extern void bgp_tip_del(struct bgp *bgp, struct in_addr *tip); extern void bgp_tip_hash_init(struct bgp *bgp); extern void bgp_tip_hash_destroy(struct bgp *bgp); extern void bgp_nexthop_show_address_hash(struct vty *vty, struct bgp *bgp); #endif /* _QUAGGA_BGP_NEXTHOP_H */ frr-7.2.1/bgpd/bgp_nht.c0000644000000000000000000005635713610377563011735 00000000000000/* BGP Nexthop tracking * Copyright (C) 2013 Cumulus Networks, Inc. * * This file is part of GNU Zebra. * * GNU Zebra is free software; you can redistribute it and/or modify it * under the terms of the GNU General Public License as published by the * Free Software Foundation; either version 2, or (at your option) any * later version. * * GNU Zebra 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; see the file COPYING; if not, write to the Free Software * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA */ #include #include "command.h" #include "thread.h" #include "prefix.h" #include "zclient.h" #include "stream.h" #include "network.h" #include "log.h" #include "memory.h" #include "nexthop.h" #include "vrf.h" #include "filter.h" #include "bgpd/bgpd.h" #include "bgpd/bgp_table.h" #include "bgpd/bgp_route.h" #include "bgpd/bgp_attr.h" #include "bgpd/bgp_nexthop.h" #include "bgpd/bgp_debug.h" #include "bgpd/bgp_errors.h" #include "bgpd/bgp_nht.h" #include "bgpd/bgp_fsm.h" #include "bgpd/bgp_zebra.h" #include "bgpd/bgp_flowspec_util.h" extern struct zclient *zclient; static void register_zebra_rnh(struct bgp_nexthop_cache *bnc, int is_bgp_static_route); static void unregister_zebra_rnh(struct bgp_nexthop_cache *bnc, int is_bgp_static_route); static void evaluate_paths(struct bgp_nexthop_cache *bnc); static int make_prefix(int afi, struct bgp_path_info *pi, struct prefix *p); static int bgp_isvalid_nexthop(struct bgp_nexthop_cache *bnc) { return (bgp_zebra_num_connects() == 0 || (bnc && CHECK_FLAG(bnc->flags, BGP_NEXTHOP_VALID))); } static int bgp_isvalid_labeled_nexthop(struct bgp_nexthop_cache *bnc) { return (bgp_zebra_num_connects() == 0 || (bnc && CHECK_FLAG(bnc->flags, BGP_NEXTHOP_LABELED_VALID))); } int bgp_find_nexthop(struct bgp_path_info *path, int connected) { struct bgp_nexthop_cache *bnc = path->nexthop; if (!bnc) return 0; /* * We are cheating here. Views have no associated underlying * ability to detect nexthops. So when we have a view * just tell everyone the nexthop is valid */ if (path->peer && path->peer->bgp->inst_type == BGP_INSTANCE_TYPE_VIEW) return 1; if (connected && !(CHECK_FLAG(bnc->flags, BGP_NEXTHOP_CONNECTED))) return 0; return (bgp_isvalid_nexthop(bnc)); } static void bgp_unlink_nexthop_check(struct bgp_nexthop_cache *bnc) { if (LIST_EMPTY(&(bnc->paths)) && !bnc->nht_info) { if (BGP_DEBUG(nht, NHT)) { char buf[PREFIX2STR_BUFFER]; zlog_debug("bgp_unlink_nexthop: freeing bnc %s", bnc_str(bnc, buf, PREFIX2STR_BUFFER)); } unregister_zebra_rnh(bnc, CHECK_FLAG(bnc->flags, BGP_STATIC_ROUTE)); bgp_node_set_bgp_nexthop_info(bnc->node, NULL); bgp_unlock_node(bnc->node); bnc->node = NULL; bnc_free(bnc); } } void bgp_unlink_nexthop(struct bgp_path_info *path) { struct bgp_nexthop_cache *bnc = path->nexthop; if (!bnc) return; path_nh_map(path, NULL, false); bgp_unlink_nexthop_check(bnc); } void bgp_unlink_nexthop_by_peer(struct peer *peer) { struct prefix p; struct bgp_node *rn; struct bgp_nexthop_cache *bnc; afi_t afi = family2afi(peer->su.sa.sa_family); if (!sockunion2hostprefix(&peer->su, &p)) return; rn = bgp_node_get(peer->bgp->nexthop_cache_table[afi], &p); bnc = bgp_node_get_bgp_nexthop_info(rn); if (!bnc) return; /* cleanup the peer reference */ bnc->nht_info = NULL; bgp_unlink_nexthop_check(bnc); } /* * A route and its nexthop might belong to different VRFs. Therefore, * we need both the bgp_route and bgp_nexthop pointers. */ int bgp_find_or_add_nexthop(struct bgp *bgp_route, struct bgp *bgp_nexthop, afi_t afi, struct bgp_path_info *pi, struct peer *peer, int connected) { struct bgp_node *rn; struct bgp_nexthop_cache *bnc; struct prefix p; int is_bgp_static_route = 0; if (pi) { is_bgp_static_route = ((pi->type == ZEBRA_ROUTE_BGP) && (pi->sub_type == BGP_ROUTE_STATIC)) ? 1 : 0; /* Since Extended Next-hop Encoding (RFC5549) support, we want to derive address-family from the next-hop. */ if (!is_bgp_static_route) afi = BGP_ATTR_NEXTHOP_AFI_IP6(pi->attr) ? AFI_IP6 : AFI_IP; /* This will return true if the global IPv6 NH is a link local * addr */ if (make_prefix(afi, pi, &p) < 0) return 1; } else if (peer) { if (!sockunion2hostprefix(&peer->su, &p)) { if (BGP_DEBUG(nht, NHT)) { zlog_debug( "%s: Attempting to register with unknown AFI %d (not %d or %d)", __FUNCTION__, afi, AFI_IP, AFI_IP6); } return 0; } } else return 0; if (is_bgp_static_route) rn = bgp_node_get(bgp_nexthop->import_check_table[afi], &p); else rn = bgp_node_get(bgp_nexthop->nexthop_cache_table[afi], &p); bnc = bgp_node_get_bgp_nexthop_info(rn); if (!bnc) { bnc = bnc_new(); bgp_node_set_bgp_nexthop_info(rn, bnc); bnc->node = rn; bnc->bgp = bgp_nexthop; bgp_lock_node(rn); if (BGP_DEBUG(nht, NHT)) { char buf[PREFIX2STR_BUFFER]; zlog_debug("Allocated bnc %s peer %p", bnc_str(bnc, buf, PREFIX2STR_BUFFER), peer); } } bgp_unlock_node(rn); if (is_bgp_static_route) { SET_FLAG(bnc->flags, BGP_STATIC_ROUTE); /* If we're toggling the type, re-register */ if ((bgp_flag_check(bgp_route, BGP_FLAG_IMPORT_CHECK)) && !CHECK_FLAG(bnc->flags, BGP_STATIC_ROUTE_EXACT_MATCH)) { SET_FLAG(bnc->flags, BGP_STATIC_ROUTE_EXACT_MATCH); UNSET_FLAG(bnc->flags, BGP_NEXTHOP_REGISTERED); UNSET_FLAG(bnc->flags, BGP_NEXTHOP_VALID); } else if ((!bgp_flag_check(bgp_route, BGP_FLAG_IMPORT_CHECK)) && CHECK_FLAG(bnc->flags, BGP_STATIC_ROUTE_EXACT_MATCH)) { UNSET_FLAG(bnc->flags, BGP_STATIC_ROUTE_EXACT_MATCH); UNSET_FLAG(bnc->flags, BGP_NEXTHOP_REGISTERED); UNSET_FLAG(bnc->flags, BGP_NEXTHOP_VALID); } } /* When nexthop is already known, but now requires 'connected' * resolution, * re-register it. The reverse scenario where the nexthop currently * requires * 'connected' resolution does not need a re-register (i.e., we treat * 'connected-required' as an override) except in the scenario where * this * is actually a case of tracking a peer for connectivity (e.g., after * disable connected-check). * NOTE: We don't track the number of paths separately for 'connected- * required' vs 'connected-not-required' as this change is not a common * scenario. */ else if (connected && !CHECK_FLAG(bnc->flags, BGP_NEXTHOP_CONNECTED)) { SET_FLAG(bnc->flags, BGP_NEXTHOP_CONNECTED); UNSET_FLAG(bnc->flags, BGP_NEXTHOP_REGISTERED); UNSET_FLAG(bnc->flags, BGP_NEXTHOP_VALID); } else if (peer && !connected && CHECK_FLAG(bnc->flags, BGP_NEXTHOP_CONNECTED)) { UNSET_FLAG(bnc->flags, BGP_NEXTHOP_CONNECTED); UNSET_FLAG(bnc->flags, BGP_NEXTHOP_REGISTERED); UNSET_FLAG(bnc->flags, BGP_NEXTHOP_VALID); } if (bgp_route->inst_type == BGP_INSTANCE_TYPE_VIEW) { SET_FLAG(bnc->flags, BGP_NEXTHOP_REGISTERED); SET_FLAG(bnc->flags, BGP_NEXTHOP_VALID); } else if (!CHECK_FLAG(bnc->flags, BGP_NEXTHOP_REGISTERED) && !is_default_host_route(&bnc->node->p)) register_zebra_rnh(bnc, is_bgp_static_route); if (pi && pi->nexthop != bnc) { /* Unlink from existing nexthop cache, if any. This will also * free * the nexthop cache entry, if appropriate. */ bgp_unlink_nexthop(pi); /* updates NHT pi list reference */ path_nh_map(pi, bnc, true); if (CHECK_FLAG(bnc->flags, BGP_NEXTHOP_VALID) && bnc->metric) (bgp_path_info_extra_get(pi))->igpmetric = bnc->metric; else if (pi->extra) pi->extra->igpmetric = 0; } else if (peer) bnc->nht_info = (void *)peer; /* NHT peer reference */ /* * We are cheating here. Views have no associated underlying * ability to detect nexthops. So when we have a view * just tell everyone the nexthop is valid */ if (bgp_route->inst_type == BGP_INSTANCE_TYPE_VIEW) return 1; else return (bgp_isvalid_nexthop(bnc)); } void bgp_delete_connected_nexthop(afi_t afi, struct peer *peer) { struct bgp_node *rn; struct bgp_nexthop_cache *bnc; struct prefix p; if (!peer) return; if (!sockunion2hostprefix(&peer->su, &p)) return; rn = bgp_node_lookup( peer->bgp->nexthop_cache_table[family2afi(p.family)], &p); if (!rn) { if (BGP_DEBUG(nht, NHT)) zlog_debug("Cannot find connected NHT node for peer %s", peer->host); return; } bnc = bgp_node_get_bgp_nexthop_info(rn); if (!bnc) { if (BGP_DEBUG(nht, NHT)) zlog_debug("Cannot find connected NHT node for peer %s on route_node as expected", peer->host); bgp_unlock_node(rn); return; } bgp_unlock_node(rn); if (bnc->nht_info != peer) { if (BGP_DEBUG(nht, NHT)) zlog_debug( "Connected NHT %p node for peer %s points to %p", bnc, peer->host, bnc->nht_info); return; } bnc->nht_info = NULL; if (LIST_EMPTY(&(bnc->paths))) { if (BGP_DEBUG(nht, NHT)) zlog_debug("Freeing connected NHT node %p for peer %s", bnc, peer->host); unregister_zebra_rnh(bnc, 0); bgp_node_set_bgp_nexthop_info(bnc->node, NULL); bgp_unlock_node(bnc->node); bnc_free(bnc); } } void bgp_parse_nexthop_update(int command, vrf_id_t vrf_id) { struct bgp_node *rn = NULL; struct bgp_nexthop_cache *bnc; struct nexthop *nexthop; struct nexthop *oldnh; struct nexthop *nhlist_head = NULL; struct nexthop *nhlist_tail = NULL; int i; struct bgp *bgp; struct zapi_route nhr; bgp = bgp_lookup_by_vrf_id(vrf_id); if (!bgp) { flog_err( EC_BGP_NH_UPD, "parse nexthop update: instance not found for vrf_id %u", vrf_id); return; } if (!zapi_nexthop_update_decode(zclient->ibuf, &nhr)) { if (BGP_DEBUG(nht, NHT)) zlog_debug("%s: Failure to decode nexthop update", __PRETTY_FUNCTION__); return; } if (command == ZEBRA_NEXTHOP_UPDATE) rn = bgp_node_lookup( bgp->nexthop_cache_table[family2afi(nhr.prefix.family)], &nhr.prefix); else if (command == ZEBRA_IMPORT_CHECK_UPDATE) rn = bgp_node_lookup( bgp->import_check_table[family2afi(nhr.prefix.family)], &nhr.prefix); if (!rn) { if (BGP_DEBUG(nht, NHT)) { char buf[PREFIX2STR_BUFFER]; prefix2str(&nhr.prefix, buf, sizeof(buf)); zlog_debug("parse nexthop update(%s): rn not found", buf); } return; } bnc = bgp_node_get_bgp_nexthop_info(rn); if (!bnc) { if (BGP_DEBUG(nht, NHT)) { char buf[PREFIX2STR_BUFFER]; prefix2str(&nhr.prefix, buf, sizeof(buf)); zlog_debug("parse nexthop update(%s): bnc node info not found", buf); } bgp_unlock_node(rn); return; } bgp_unlock_node(rn); bnc->last_update = bgp_clock(); bnc->change_flags = 0; /* debug print the input */ if (BGP_DEBUG(nht, NHT)) { char buf[PREFIX2STR_BUFFER]; prefix2str(&nhr.prefix, buf, sizeof(buf)); zlog_debug( "%u: Rcvd NH update %s - metric %d/%d #nhops %d/%d flags 0x%x", vrf_id, buf, nhr.metric, bnc->metric, nhr.nexthop_num, bnc->nexthop_num, bnc->flags); } if (nhr.metric != bnc->metric) bnc->change_flags |= BGP_NEXTHOP_METRIC_CHANGED; if (nhr.nexthop_num != bnc->nexthop_num) bnc->change_flags |= BGP_NEXTHOP_CHANGED; if (nhr.nexthop_num) { struct peer *peer = bnc->nht_info; /* notify bgp fsm if nbr ip goes from invalid->valid */ if (!bnc->nexthop_num) UNSET_FLAG(bnc->flags, BGP_NEXTHOP_PEER_NOTIFIED); bnc->flags |= BGP_NEXTHOP_VALID; bnc->metric = nhr.metric; bnc->nexthop_num = nhr.nexthop_num; bnc->flags &= ~BGP_NEXTHOP_LABELED_VALID; /* check below */ for (i = 0; i < nhr.nexthop_num; i++) { int num_labels = 0; nexthop = nexthop_from_zapi_nexthop(&nhr.nexthops[i]); /* * Turn on RA for the v6 nexthops * we receive from bgp. This is to allow us * to work with v4 routing over v6 nexthops */ if (peer && !peer->ifp && CHECK_FLAG(peer->flags, PEER_FLAG_CAPABILITY_ENHE) && nhr.prefix.family == AF_INET6) { struct interface *ifp; ifp = if_lookup_by_index(nexthop->ifindex, nexthop->vrf_id); zclient_send_interface_radv_req( zclient, nexthop->vrf_id, ifp, true, BGP_UNNUM_DEFAULT_RA_INTERVAL); } /* There is at least one label-switched path */ if (nexthop->nh_label && nexthop->nh_label->num_labels) { bnc->flags |= BGP_NEXTHOP_LABELED_VALID; num_labels = nexthop->nh_label->num_labels; } if (BGP_DEBUG(nht, NHT)) { char buf[NEXTHOP_STRLEN]; zlog_debug( " nhop via %s (%d labels)", nexthop2str(nexthop, buf, sizeof(buf)), num_labels); } if (nhlist_tail) { nhlist_tail->next = nexthop; nhlist_tail = nexthop; } else { nhlist_tail = nexthop; nhlist_head = nexthop; } /* No need to evaluate the nexthop if we have already * determined * that there has been a change. */ if (bnc->change_flags & BGP_NEXTHOP_CHANGED) continue; for (oldnh = bnc->nexthop; oldnh; oldnh = oldnh->next) if (nexthop_same(oldnh, nexthop)) break; if (!oldnh) bnc->change_flags |= BGP_NEXTHOP_CHANGED; } bnc_nexthop_free(bnc); bnc->nexthop = nhlist_head; } else { bnc->flags &= ~BGP_NEXTHOP_VALID; bnc->nexthop_num = nhr.nexthop_num; /* notify bgp fsm if nbr ip goes from valid->invalid */ UNSET_FLAG(bnc->flags, BGP_NEXTHOP_PEER_NOTIFIED); bnc_nexthop_free(bnc); bnc->nexthop = NULL; } evaluate_paths(bnc); } /* * Cleanup nexthop registration and status information for BGP nexthops * pertaining to this VRF. This is invoked upon VRF deletion. */ void bgp_cleanup_nexthops(struct bgp *bgp) { afi_t afi; struct bgp_node *rn; struct bgp_nexthop_cache *bnc; for (afi = AFI_IP; afi < AFI_MAX; afi++) { if (!bgp->nexthop_cache_table[afi]) continue; for (rn = bgp_table_top(bgp->nexthop_cache_table[afi]); rn; rn = bgp_route_next(rn)) { bnc = bgp_node_get_bgp_nexthop_info(rn); if (!bnc) continue; /* Clear relevant flags. */ UNSET_FLAG(bnc->flags, BGP_NEXTHOP_VALID); UNSET_FLAG(bnc->flags, BGP_NEXTHOP_REGISTERED); UNSET_FLAG(bnc->flags, BGP_NEXTHOP_PEER_NOTIFIED); } } } /** * make_prefix - make a prefix structure from the path (essentially * path's node. */ static int make_prefix(int afi, struct bgp_path_info *pi, struct prefix *p) { int is_bgp_static = ((pi->type == ZEBRA_ROUTE_BGP) && (pi->sub_type == BGP_ROUTE_STATIC)) ? 1 : 0; struct bgp_node *net = pi->net; struct prefix *p_orig = &net->p; if (p_orig->family == AF_FLOWSPEC) { if (!pi->peer) return -1; return bgp_flowspec_get_first_nh(pi->peer->bgp, pi, p); } memset(p, 0, sizeof(struct prefix)); switch (afi) { case AFI_IP: p->family = AF_INET; if (is_bgp_static) { p->u.prefix4 = pi->net->p.u.prefix4; p->prefixlen = pi->net->p.prefixlen; } else { p->u.prefix4 = pi->attr->nexthop; p->prefixlen = IPV4_MAX_BITLEN; } break; case AFI_IP6: p->family = AF_INET6; if (is_bgp_static) { p->u.prefix6 = pi->net->p.u.prefix6; p->prefixlen = pi->net->p.prefixlen; } else { p->u.prefix6 = pi->attr->mp_nexthop_global; p->prefixlen = IPV6_MAX_BITLEN; } break; default: if (BGP_DEBUG(nht, NHT)) { zlog_debug( "%s: Attempting to make prefix with unknown AFI %d (not %d or %d)", __FUNCTION__, afi, AFI_IP, AFI_IP6); } break; } return 0; } /** * sendmsg_zebra_rnh -- Format and send a nexthop register/Unregister * command to Zebra. * ARGUMENTS: * struct bgp_nexthop_cache *bnc -- the nexthop structure. * int command -- command to send to zebra * RETURNS: * void. */ static void sendmsg_zebra_rnh(struct bgp_nexthop_cache *bnc, int command) { struct prefix *p; bool exact_match = false; int ret; if (!zclient) return; /* Don't try to register if Zebra doesn't know of this instance. */ if (!IS_BGP_INST_KNOWN_TO_ZEBRA(bnc->bgp)) { if (BGP_DEBUG(zebra, ZEBRA)) zlog_debug("%s: No zebra instance to talk to, not installing NHT entry", __PRETTY_FUNCTION__); return; } if (!bgp_zebra_num_connects()) { if (BGP_DEBUG(zebra, ZEBRA)) zlog_debug("%s: We have not connected yet, cannot send nexthops", __PRETTY_FUNCTION__); } p = &(bnc->node->p); if ((command == ZEBRA_NEXTHOP_REGISTER || command == ZEBRA_IMPORT_ROUTE_REGISTER) && (CHECK_FLAG(bnc->flags, BGP_NEXTHOP_CONNECTED) || CHECK_FLAG(bnc->flags, BGP_STATIC_ROUTE_EXACT_MATCH))) exact_match = true; if (BGP_DEBUG(zebra, ZEBRA)) { char buf[PREFIX2STR_BUFFER]; prefix2str(p, buf, PREFIX2STR_BUFFER); zlog_debug("%s: sending cmd %s for %s (vrf %s)", __func__, zserv_command_string(command), buf, bnc->bgp->name); } ret = zclient_send_rnh(zclient, command, p, exact_match, bnc->bgp->vrf_id); /* TBD: handle the failure */ if (ret < 0) flog_warn(EC_BGP_ZEBRA_SEND, "sendmsg_nexthop: zclient_send_message() failed"); if ((command == ZEBRA_NEXTHOP_REGISTER) || (command == ZEBRA_IMPORT_ROUTE_REGISTER)) SET_FLAG(bnc->flags, BGP_NEXTHOP_REGISTERED); else if ((command == ZEBRA_NEXTHOP_UNREGISTER) || (command == ZEBRA_IMPORT_ROUTE_UNREGISTER)) UNSET_FLAG(bnc->flags, BGP_NEXTHOP_REGISTERED); return; } /** * register_zebra_rnh - register a NH/route with Zebra for notification * when the route or the route to the nexthop changes. * ARGUMENTS: * struct bgp_nexthop_cache *bnc * RETURNS: * void. */ static void register_zebra_rnh(struct bgp_nexthop_cache *bnc, int is_bgp_import_route) { /* Check if we have already registered */ if (bnc->flags & BGP_NEXTHOP_REGISTERED) return; if (is_bgp_import_route) sendmsg_zebra_rnh(bnc, ZEBRA_IMPORT_ROUTE_REGISTER); else sendmsg_zebra_rnh(bnc, ZEBRA_NEXTHOP_REGISTER); } /** * unregister_zebra_rnh -- Unregister the route/nexthop from Zebra. * ARGUMENTS: * struct bgp_nexthop_cache *bnc * RETURNS: * void. */ static void unregister_zebra_rnh(struct bgp_nexthop_cache *bnc, int is_bgp_import_route) { /* Check if we have already registered */ if (!CHECK_FLAG(bnc->flags, BGP_NEXTHOP_REGISTERED)) return; if (is_bgp_import_route) sendmsg_zebra_rnh(bnc, ZEBRA_IMPORT_ROUTE_UNREGISTER); else sendmsg_zebra_rnh(bnc, ZEBRA_NEXTHOP_UNREGISTER); } /** * evaluate_paths - Evaluate the paths/nets associated with a nexthop. * ARGUMENTS: * struct bgp_nexthop_cache *bnc -- the nexthop structure. * RETURNS: * void. */ static void evaluate_paths(struct bgp_nexthop_cache *bnc) { struct bgp_node *rn; struct bgp_path_info *path; int afi; struct peer *peer = (struct peer *)bnc->nht_info; struct bgp_table *table; safi_t safi; struct bgp *bgp_path; if (BGP_DEBUG(nht, NHT)) { char buf[PREFIX2STR_BUFFER]; bnc_str(bnc, buf, PREFIX2STR_BUFFER); zlog_debug( "NH update for %s - flags 0x%x chgflags 0x%x - evaluate paths", buf, bnc->flags, bnc->change_flags); } LIST_FOREACH (path, &(bnc->paths), nh_thread) { if (!(path->type == ZEBRA_ROUTE_BGP && ((path->sub_type == BGP_ROUTE_NORMAL) || (path->sub_type == BGP_ROUTE_STATIC) || (path->sub_type == BGP_ROUTE_IMPORTED)))) continue; rn = path->net; assert(rn && bgp_node_table(rn)); afi = family2afi(rn->p.family); table = bgp_node_table(rn); safi = table->safi; /* * handle routes from other VRFs (they can have a * nexthop in THIS VRF). bgp_path is the bgp instance * that owns the route referencing this nexthop. */ bgp_path = table->bgp; /* * Path becomes valid/invalid depending on whether the nexthop * reachable/unreachable. * * In case of unicast routes that were imported from vpn * and that have labels, they are valid only if there are * nexthops with labels */ int bnc_is_valid_nexthop = 0; if (safi == SAFI_UNICAST && path->sub_type == BGP_ROUTE_IMPORTED && path->extra && path->extra->num_labels) { bnc_is_valid_nexthop = bgp_isvalid_labeled_nexthop(bnc) ? 1 : 0; } else { bnc_is_valid_nexthop = bgp_isvalid_nexthop(bnc) ? 1 : 0; } if (BGP_DEBUG(nht, NHT)) { char buf[PREFIX_STRLEN]; prefix2str(&rn->p, buf, PREFIX_STRLEN); zlog_debug("%s: prefix %s (vrf %s) %svalid", __func__, buf, bgp_path->name, (bnc_is_valid_nexthop ? "" : "not ")); } if ((CHECK_FLAG(path->flags, BGP_PATH_VALID) ? 1 : 0) != bnc_is_valid_nexthop) { if (CHECK_FLAG(path->flags, BGP_PATH_VALID)) { bgp_aggregate_decrement(bgp_path, &rn->p, path, afi, safi); bgp_path_info_unset_flag(rn, path, BGP_PATH_VALID); } else { bgp_path_info_set_flag(rn, path, BGP_PATH_VALID); bgp_aggregate_increment(bgp_path, &rn->p, path, afi, safi); } } /* Copy the metric to the path. Will be used for bestpath * computation */ if (bgp_isvalid_nexthop(bnc) && bnc->metric) (bgp_path_info_extra_get(path))->igpmetric = bnc->metric; else if (path->extra) path->extra->igpmetric = 0; if (CHECK_FLAG(bnc->change_flags, BGP_NEXTHOP_METRIC_CHANGED) || CHECK_FLAG(bnc->change_flags, BGP_NEXTHOP_CHANGED)) SET_FLAG(path->flags, BGP_PATH_IGP_CHANGED); bgp_process(bgp_path, rn, afi, safi); } if (peer && !CHECK_FLAG(bnc->flags, BGP_NEXTHOP_PEER_NOTIFIED)) { if (BGP_DEBUG(nht, NHT)) zlog_debug("%s: Updating peer (%s) status with NHT", __FUNCTION__, peer->host); bgp_fsm_event_update(peer, bgp_isvalid_nexthop(bnc)); SET_FLAG(bnc->flags, BGP_NEXTHOP_PEER_NOTIFIED); } RESET_FLAG(bnc->change_flags); } /** * path_nh_map - make or break path-to-nexthop association. * ARGUMENTS: * path - pointer to the path structure * bnc - pointer to the nexthop structure * make - if set, make the association. if unset, just break the existing * association. */ void path_nh_map(struct bgp_path_info *path, struct bgp_nexthop_cache *bnc, bool make) { if (path->nexthop) { LIST_REMOVE(path, nh_thread); path->nexthop->path_count--; path->nexthop = NULL; } if (make) { LIST_INSERT_HEAD(&(bnc->paths), path, nh_thread); path->nexthop = bnc; path->nexthop->path_count++; } } /* * This function is called to register nexthops to zebra * as that we may have tried to install the nexthops * before we actually have a zebra connection */ void bgp_nht_register_nexthops(struct bgp *bgp) { struct bgp_node *rn; struct bgp_nexthop_cache *bnc; afi_t afi; for (afi = AFI_IP; afi < AFI_MAX; afi++) { if (!bgp->nexthop_cache_table[afi]) continue; for (rn = bgp_table_top(bgp->nexthop_cache_table[afi]); rn; rn = bgp_route_next(rn)) { bnc = bgp_node_get_bgp_nexthop_info(rn); if (!bnc) continue; register_zebra_rnh(bnc, 0); } } } void bgp_nht_register_enhe_capability_interfaces(struct peer *peer) { struct bgp *bgp; struct bgp_node *rn; struct bgp_nexthop_cache *bnc; struct nexthop *nhop; struct interface *ifp; struct prefix p; if (peer->ifp) return; bgp = peer->bgp; if (!bgp->nexthop_cache_table[AFI_IP6]) return; if (!sockunion2hostprefix(&peer->su, &p)) { if (BGP_DEBUG(nht, NHT)) zlog_debug("%s: Unable to convert prefix to sockunion", __PRETTY_FUNCTION__); return; } if (p.family != AF_INET6) return; rn = bgp_node_lookup(bgp->nexthop_cache_table[AFI_IP6], &p); if (!rn) return; bnc = bgp_node_get_bgp_nexthop_info(rn); if (!bnc) return; if (peer != bnc->nht_info) return; for (nhop = bnc->nexthop; nhop; nhop = nhop->next) { ifp = if_lookup_by_index(nhop->ifindex, nhop->vrf_id); zclient_send_interface_radv_req(zclient, nhop->vrf_id, ifp, true, BGP_UNNUM_DEFAULT_RA_INTERVAL); } } frr-7.2.1/bgpd/bgp_nht.h0000644000000000000000000000655613610377563011736 00000000000000/* BGP Nexthop tracking * Copyright (C) 2013 Cumulus Networks, Inc. * * This file is part of GNU Zebra. * * GNU Zebra is free software; you can redistribute it and/or modify it * under the terms of the GNU General Public License as published by the * Free Software Foundation; either version 2, or (at your option) any * later version. * * GNU Zebra 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; see the file COPYING; if not, write to the Free Software * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA */ #ifndef _BGP_NHT_H #define _BGP_NHT_H /** * bgp_parse_nexthop_update() - parse a nexthop update message from Zebra. */ extern void bgp_parse_nexthop_update(int command, vrf_id_t vrf_id); /** * bgp_find_nexthop() - lookup the nexthop cache table for the bnc object * ARGUMENTS: * p - path for which the nexthop object is being looked up * connected - True if NH MUST be a connected route */ extern int bgp_find_nexthop(struct bgp_path_info *p, int connected); /** * bgp_find_or_add_nexthop() - lookup the nexthop cache table for the bnc * object. If not found, create a new object and register with ZEBRA for * nexthop notification. * ARGUMENTS: * bgp_route - BGP instance of route * bgp_nexthop - BGP instance of nexthop * a - afi: AFI_IP or AF_IP6 * p - path for which the nexthop object is being looked up * peer - The BGP peer associated with this NHT * connected - True if NH MUST be a connected route */ extern int bgp_find_or_add_nexthop(struct bgp *bgp_route, struct bgp *bgp_nexthop, afi_t a, struct bgp_path_info *p, struct peer *peer, int connected); /** * bgp_unlink_nexthop() - Unlink the nexthop object from the path structure. * ARGUMENTS: * p - path structure. */ extern void bgp_unlink_nexthop(struct bgp_path_info *p); void bgp_unlink_nexthop_by_peer(struct peer *peer); /** * bgp_delete_connected_nexthop() - Reset the 'peer' pointer for a connected * nexthop entry. If no paths reference the nexthop, it will be unregistered * and freed. * ARGUMENTS: * afi - afi: AFI_IP or AF_IP6 * peer - Ptr to peer */ extern void bgp_delete_connected_nexthop(afi_t afi, struct peer *peer); /* * Cleanup nexthop registration and status information for BGP nexthops * pertaining to this VRF. This is invoked upon VRF deletion. */ extern void bgp_cleanup_nexthops(struct bgp *bgp); /* * Add or remove the tracking of the bgp_path_info that * uses this nexthop */ extern void path_nh_map(struct bgp_path_info *path, struct bgp_nexthop_cache *bnc, bool make); /* * When we actually have the connection to * the zebra daemon, we need to reregister * any nexthops we may have sitting around */ extern void bgp_nht_register_nexthops(struct bgp *bgp); /* * When we have the the PEER_FLAG_CAPABILITY_ENHE flag * set on a peer *after* it has been brought up we need * to notice and setup the interface based RA, * this code can walk the registered nexthops and * register the important ones with zebra for RA. */ extern void bgp_nht_register_enhe_capability_interfaces(struct peer *peer); #endif /* _BGP_NHT_H */ frr-7.2.1/bgpd/bgp_open.c0000644000000000000000000012406413610377563012074 00000000000000/* BGP open message handling * Copyright (C) 1998, 1999 Kunihiro Ishiguro * * This file is part of GNU Zebra. * * GNU Zebra is free software; you can redistribute it and/or modify it * under the terms of the GNU General Public License as published by the * Free Software Foundation; either version 2, or (at your option) any * later version. * * GNU Zebra 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; see the file COPYING; if not, write to the Free Software * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA */ #include #include "linklist.h" #include "prefix.h" #include "stream.h" #include "thread.h" #include "log.h" #include "command.h" #include "memory.h" #include "queue.h" #include "filter.h" #include "lib/json.h" #include "bgpd/bgpd.h" #include "bgpd/bgp_attr.h" #include "bgpd/bgp_debug.h" #include "bgpd/bgp_errors.h" #include "bgpd/bgp_fsm.h" #include "bgpd/bgp_packet.h" #include "bgpd/bgp_open.h" #include "bgpd/bgp_aspath.h" #include "bgpd/bgp_vty.h" #include "bgpd/bgp_memory.h" /* BGP-4 Multiprotocol Extentions lead us to the complex world. We can negotiate remote peer supports extentions or not. But if remote-peer doesn't supports negotiation process itself. We would like to do manual configuration. So there is many configurable point. First of all we want set each peer whether we send capability negotiation to the peer or not. Next, if we send capability to the peer we want to set my capability inforation at each peer. */ void bgp_capability_vty_out(struct vty *vty, struct peer *peer, bool use_json, json_object *json_neigh) { char *pnt; char *end; struct capability_mp_data mpc; struct capability_header *hdr; json_object *json_cap = NULL; if (use_json) json_cap = json_object_new_object(); pnt = peer->notify.data; end = pnt + peer->notify.length; while (pnt < end) { if (pnt + sizeof(struct capability_mp_data) + 2 > end) return; hdr = (struct capability_header *)pnt; if (pnt + hdr->length + 2 > end) return; memcpy(&mpc, pnt + 2, sizeof(struct capability_mp_data)); if (hdr->code == CAPABILITY_CODE_MP) { afi_t afi; safi_t safi; (void)bgp_map_afi_safi_iana2int(ntohs(mpc.afi), mpc.safi, &afi, &safi); if (use_json) { switch (afi) { case AFI_IP: json_object_string_add( json_cap, "capabilityErrorMultiProtocolAfi", "IPv4"); break; case AFI_IP6: json_object_string_add( json_cap, "capabilityErrorMultiProtocolAfi", "IPv6"); break; case AFI_L2VPN: json_object_string_add( json_cap, "capabilityErrorMultiProtocolAfi", "L2VPN"); break; default: json_object_int_add( json_cap, "capabilityErrorMultiProtocolAfiUnknown", ntohs(mpc.afi)); break; } switch (safi) { case SAFI_UNICAST: json_object_string_add( json_cap, "capabilityErrorMultiProtocolSafi", "unicast"); break; case SAFI_MULTICAST: json_object_string_add( json_cap, "capabilityErrorMultiProtocolSafi", "multicast"); break; case SAFI_LABELED_UNICAST: json_object_string_add( json_cap, "capabilityErrorMultiProtocolSafi", "labeled-unicast"); break; case SAFI_MPLS_VPN: json_object_string_add( json_cap, "capabilityErrorMultiProtocolSafi", "MPLS-labeled VPN"); break; case SAFI_ENCAP: json_object_string_add( json_cap, "capabilityErrorMultiProtocolSafi", "encap"); break; case SAFI_EVPN: json_object_string_add( json_cap, "capabilityErrorMultiProtocolSafi", "EVPN"); break; case SAFI_FLOWSPEC: json_object_string_add( json_cap, "capabilityErrorMultiProtocolSafi", "flowspec"); break; default: json_object_int_add( json_cap, "capabilityErrorMultiProtocolSafiUnknown", mpc.safi); break; } } else { vty_out(vty, " Capability error for: Multi protocol "); switch (afi) { case AFI_IP: vty_out(vty, "AFI IPv4, "); break; case AFI_IP6: vty_out(vty, "AFI IPv6, "); break; case AFI_L2VPN: vty_out(vty, "AFI L2VPN, "); break; default: vty_out(vty, "AFI Unknown %d, ", ntohs(mpc.afi)); break; } switch (safi) { case SAFI_UNICAST: vty_out(vty, "SAFI Unicast"); break; case SAFI_MULTICAST: vty_out(vty, "SAFI Multicast"); break; case SAFI_LABELED_UNICAST: vty_out(vty, "SAFI Labeled-unicast"); break; case SAFI_MPLS_VPN: vty_out(vty, "SAFI MPLS-labeled VPN"); break; case SAFI_ENCAP: vty_out(vty, "SAFI ENCAP"); break; case SAFI_FLOWSPEC: vty_out(vty, "SAFI FLOWSPEC"); break; case SAFI_EVPN: vty_out(vty, "SAFI EVPN"); break; default: vty_out(vty, "SAFI Unknown %d ", mpc.safi); break; } vty_out(vty, "\n"); } } else if (hdr->code >= 128) { if (use_json) json_object_int_add( json_cap, "capabilityErrorVendorSpecificCapabilityCode", hdr->code); else vty_out(vty, " Capability error: vendor specific capability code %d", hdr->code); } else { if (use_json) json_object_int_add( json_cap, "capabilityErrorUnknownCapabilityCode", hdr->code); else vty_out(vty, " Capability error: unknown capability code %d", hdr->code); } pnt += hdr->length + 2; } if (use_json) json_object_object_add(json_neigh, "capabilityErrors", json_cap); } static void bgp_capability_mp_data(struct stream *s, struct capability_mp_data *mpc) { mpc->afi = stream_getw(s); mpc->reserved = stream_getc(s); mpc->safi = stream_getc(s); } /* Set negotiated capability value. */ static int bgp_capability_mp(struct peer *peer, struct capability_header *hdr) { struct capability_mp_data mpc; struct stream *s = BGP_INPUT(peer); afi_t afi; safi_t safi; /* Verify length is 4 */ if (hdr->length != 4) { flog_warn( EC_BGP_CAPABILITY_INVALID_LENGTH, "MP Cap: Received invalid length %d, non-multiple of 4", hdr->length); return -1; } bgp_capability_mp_data(s, &mpc); if (bgp_debug_neighbor_events(peer)) zlog_debug("%s OPEN has MP_EXT CAP for afi/safi: %s/%s", peer->host, iana_afi2str(mpc.afi), iana_safi2str(mpc.safi)); /* Convert AFI, SAFI to internal values, check. */ if (bgp_map_afi_safi_iana2int(mpc.afi, mpc.safi, &afi, &safi)) return -1; /* Now safi remapped, and afi/safi are valid array indices */ peer->afc_recv[afi][safi] = 1; if (peer->afc[afi][safi]) peer->afc_nego[afi][safi] = 1; else return -1; return 0; } static void bgp_capability_orf_not_support(struct peer *peer, iana_afi_t afi, iana_safi_t safi, uint8_t type, uint8_t mode) { if (bgp_debug_neighbor_events(peer)) zlog_debug( "%s Addr-family %d/%d has ORF type/mode %d/%d not supported", peer->host, afi, safi, type, mode); } static const struct message orf_type_str[] = { {ORF_TYPE_PREFIX, "Prefixlist"}, {ORF_TYPE_PREFIX_OLD, "Prefixlist (old)"}, {0}}; static const struct message orf_mode_str[] = {{ORF_MODE_RECEIVE, "Receive"}, {ORF_MODE_SEND, "Send"}, {ORF_MODE_BOTH, "Both"}, {0}}; static int bgp_capability_orf_entry(struct peer *peer, struct capability_header *hdr) { struct stream *s = BGP_INPUT(peer); struct capability_mp_data mpc; uint8_t num; iana_afi_t pkt_afi; afi_t afi; iana_safi_t pkt_safi; safi_t safi; uint8_t type; uint8_t mode; uint16_t sm_cap = 0; /* capability send-mode receive */ uint16_t rm_cap = 0; /* capability receive-mode receive */ int i; /* ORF Entry header */ bgp_capability_mp_data(s, &mpc); num = stream_getc(s); pkt_afi = mpc.afi; pkt_safi = mpc.safi; if (bgp_debug_neighbor_events(peer)) zlog_debug("%s ORF Cap entry for afi/safi: %s/%s", peer->host, iana_afi2str(mpc.afi), iana_safi2str(mpc.safi)); /* Convert AFI, SAFI to internal values, check. */ if (bgp_map_afi_safi_iana2int(pkt_afi, pkt_safi, &afi, &safi)) { zlog_info( "%s Addr-family %d/%d not supported." " Ignoring the ORF capability", peer->host, pkt_afi, pkt_safi); return 0; } mpc.afi = pkt_afi; mpc.safi = safi; /* validate number field */ if (CAPABILITY_CODE_ORF_LEN + (num * 2) > hdr->length) { zlog_info( "%s ORF Capability entry length error," " Cap length %u, num %u", peer->host, hdr->length, num); bgp_notify_send(peer, BGP_NOTIFY_OPEN_ERR, BGP_NOTIFY_OPEN_MALFORMED_ATTR); return -1; } for (i = 0; i < num; i++) { type = stream_getc(s); mode = stream_getc(s); /* ORF Mode error check */ switch (mode) { case ORF_MODE_BOTH: case ORF_MODE_SEND: case ORF_MODE_RECEIVE: break; default: bgp_capability_orf_not_support(peer, pkt_afi, pkt_safi, type, mode); continue; } /* ORF Type and afi/safi error checks */ /* capcode versus type */ switch (hdr->code) { case CAPABILITY_CODE_ORF: switch (type) { case ORF_TYPE_PREFIX: break; default: bgp_capability_orf_not_support( peer, pkt_afi, pkt_safi, type, mode); continue; } break; case CAPABILITY_CODE_ORF_OLD: switch (type) { case ORF_TYPE_PREFIX_OLD: break; default: bgp_capability_orf_not_support( peer, pkt_afi, pkt_safi, type, mode); continue; } break; default: bgp_capability_orf_not_support(peer, pkt_afi, pkt_safi, type, mode); continue; } /* AFI vs SAFI */ if (!((afi == AFI_IP && safi == SAFI_UNICAST) || (afi == AFI_IP && safi == SAFI_MULTICAST) || (afi == AFI_IP6 && safi == SAFI_UNICAST))) { bgp_capability_orf_not_support(peer, pkt_afi, pkt_safi, type, mode); continue; } if (bgp_debug_neighbor_events(peer)) zlog_debug( "%s OPEN has %s ORF capability" " as %s for afi/safi: %s/%s", peer->host, lookup_msg(orf_type_str, type, NULL), lookup_msg(orf_mode_str, mode, NULL), iana_afi2str(pkt_afi), iana_safi2str(pkt_safi)); if (hdr->code == CAPABILITY_CODE_ORF) { sm_cap = PEER_CAP_ORF_PREFIX_SM_RCV; rm_cap = PEER_CAP_ORF_PREFIX_RM_RCV; } else if (hdr->code == CAPABILITY_CODE_ORF_OLD) { sm_cap = PEER_CAP_ORF_PREFIX_SM_OLD_RCV; rm_cap = PEER_CAP_ORF_PREFIX_RM_OLD_RCV; } else { bgp_capability_orf_not_support(peer, pkt_afi, pkt_safi, type, mode); continue; } switch (mode) { case ORF_MODE_BOTH: SET_FLAG(peer->af_cap[afi][safi], sm_cap); SET_FLAG(peer->af_cap[afi][safi], rm_cap); break; case ORF_MODE_SEND: SET_FLAG(peer->af_cap[afi][safi], sm_cap); break; case ORF_MODE_RECEIVE: SET_FLAG(peer->af_cap[afi][safi], rm_cap); break; } } return 0; } static int bgp_capability_restart(struct peer *peer, struct capability_header *caphdr) { struct stream *s = BGP_INPUT(peer); uint16_t restart_flag_time; size_t end = stream_get_getp(s) + caphdr->length; /* Verify length is a multiple of 4 */ if ((caphdr->length - 2) % 4) { flog_warn( EC_BGP_CAPABILITY_INVALID_LENGTH, "Restart Cap: Received invalid length %d, non-multiple of 4", caphdr->length); return -1; } SET_FLAG(peer->cap, PEER_CAP_RESTART_RCV); restart_flag_time = stream_getw(s); if (CHECK_FLAG(restart_flag_time, RESTART_R_BIT)) SET_FLAG(peer->cap, PEER_CAP_RESTART_BIT_RCV); UNSET_FLAG(restart_flag_time, 0xF000); peer->v_gr_restart = restart_flag_time; if (bgp_debug_neighbor_events(peer)) { zlog_debug("%s OPEN has Graceful Restart capability", peer->host); zlog_debug("%s Peer has%srestarted. Restart Time : %d", peer->host, CHECK_FLAG(peer->cap, PEER_CAP_RESTART_BIT_RCV) ? " " : " not ", peer->v_gr_restart); } while (stream_get_getp(s) + 4 <= end) { afi_t afi; safi_t safi; iana_afi_t pkt_afi = stream_getw(s); iana_safi_t pkt_safi = stream_getc(s); uint8_t flag = stream_getc(s); /* Convert AFI, SAFI to internal values, check. */ if (bgp_map_afi_safi_iana2int(pkt_afi, pkt_safi, &afi, &safi)) { if (bgp_debug_neighbor_events(peer)) zlog_debug( "%s Addr-family %s/%s(afi/safi) not supported." " Ignore the Graceful Restart capability for this AFI/SAFI", peer->host, iana_afi2str(pkt_afi), iana_safi2str(pkt_safi)); } else if (!peer->afc[afi][safi]) { if (bgp_debug_neighbor_events(peer)) zlog_debug( "%s Addr-family %s/%s(afi/safi) not enabled." " Ignore the Graceful Restart capability", peer->host, iana_afi2str(pkt_afi), iana_safi2str(pkt_safi)); } else { if (bgp_debug_neighbor_events(peer)) zlog_debug( "%s Address family %s is%spreserved", peer->host, get_afi_safi_str(afi, safi, false), CHECK_FLAG( peer->af_cap[afi][safi], PEER_CAP_RESTART_AF_PRESERVE_RCV) ? " " : " not "); SET_FLAG(peer->af_cap[afi][safi], PEER_CAP_RESTART_AF_RCV); if (CHECK_FLAG(flag, RESTART_F_BIT)) SET_FLAG(peer->af_cap[afi][safi], PEER_CAP_RESTART_AF_PRESERVE_RCV); } } return 0; } /* Unlike other capability parsing routines, this one returns 0 on error */ static as_t bgp_capability_as4(struct peer *peer, struct capability_header *hdr) { SET_FLAG(peer->cap, PEER_CAP_AS4_RCV); if (hdr->length != CAPABILITY_CODE_AS4_LEN) { flog_err(EC_BGP_PKT_OPEN, "%s AS4 capability has incorrect data length %d", peer->host, hdr->length); return 0; } as_t as4 = stream_getl(BGP_INPUT(peer)); if (BGP_DEBUG(as4, AS4)) zlog_debug( "%s [AS4] about to set cap PEER_CAP_AS4_RCV, got as4 %u", peer->host, as4); return as4; } static int bgp_capability_addpath(struct peer *peer, struct capability_header *hdr) { struct stream *s = BGP_INPUT(peer); size_t end = stream_get_getp(s) + hdr->length; SET_FLAG(peer->cap, PEER_CAP_ADDPATH_RCV); /* Verify length is a multiple of 4 */ if (hdr->length % 4) { flog_warn( EC_BGP_CAPABILITY_INVALID_LENGTH, "Add Path: Received invalid length %d, non-multiple of 4", hdr->length); return -1; } while (stream_get_getp(s) + 4 <= end) { afi_t afi; safi_t safi; iana_afi_t pkt_afi = stream_getw(s); iana_safi_t pkt_safi = stream_getc(s); uint8_t send_receive = stream_getc(s); if (bgp_debug_neighbor_events(peer)) zlog_debug( "%s OPEN has AddPath CAP for afi/safi: %s/%s%s%s", peer->host, iana_afi2str(pkt_afi), iana_safi2str(pkt_safi), (send_receive & BGP_ADDPATH_RX) ? ", receive" : "", (send_receive & BGP_ADDPATH_TX) ? ", transmit" : ""); /* Convert AFI, SAFI to internal values, check. */ if (bgp_map_afi_safi_iana2int(pkt_afi, pkt_safi, &afi, &safi)) { if (bgp_debug_neighbor_events(peer)) zlog_debug( "%s Addr-family %s/%s(afi/safi) not supported." " Ignore the Addpath Attribute for this AFI/SAFI", peer->host, iana_afi2str(pkt_afi), iana_safi2str(pkt_safi)); continue; } else if (!peer->afc[afi][safi]) { if (bgp_debug_neighbor_events(peer)) zlog_debug( "%s Addr-family %s/%s(afi/safi) not enabled." " Ignore the AddPath capability for this AFI/SAFI", peer->host, iana_afi2str(pkt_afi), iana_safi2str(pkt_safi)); continue; } if (send_receive & BGP_ADDPATH_RX) SET_FLAG(peer->af_cap[afi][safi], PEER_CAP_ADDPATH_AF_RX_RCV); if (send_receive & BGP_ADDPATH_TX) SET_FLAG(peer->af_cap[afi][safi], PEER_CAP_ADDPATH_AF_TX_RCV); } return 0; } static int bgp_capability_enhe(struct peer *peer, struct capability_header *hdr) { struct stream *s = BGP_INPUT(peer); size_t end = stream_get_getp(s) + hdr->length; /* Verify length is a multiple of 4 */ if (hdr->length % 6) { flog_warn( EC_BGP_CAPABILITY_INVALID_LENGTH, "Extended NH: Received invalid length %d, non-multiple of 6", hdr->length); return -1; } while (stream_get_getp(s) + 6 <= end) { iana_afi_t pkt_afi = stream_getw(s); afi_t afi; iana_safi_t pkt_safi = stream_getw(s); safi_t safi; iana_afi_t pkt_nh_afi = stream_getw(s); afi_t nh_afi; if (bgp_debug_neighbor_events(peer)) zlog_debug( "%s Received with afi/safi/next-hop afi: %s/%s/%u", peer->host, iana_afi2str(pkt_afi), iana_safi2str(pkt_safi), pkt_nh_afi); /* Convert AFI, SAFI to internal values, check. */ if (bgp_map_afi_safi_iana2int(pkt_afi, pkt_safi, &afi, &safi)) { if (bgp_debug_neighbor_events(peer)) zlog_debug( "%s Addr-family %s/%s(afi/safi) not supported." " Ignore the ENHE Attribute for this AFI/SAFI", peer->host, iana_afi2str(pkt_afi), iana_safi2str(pkt_safi)); continue; } /* RFC 5549 specifies use of this capability only for IPv4 AFI, * with * the Nexthop AFI being IPv6. A future spec may introduce other * possibilities, so we ignore other values with a log. Also, * only * SAFI_UNICAST and SAFI_LABELED_UNICAST are currently supported * (and expected). */ nh_afi = afi_iana2int(pkt_nh_afi); if (afi != AFI_IP || nh_afi != AFI_IP6 || !(safi == SAFI_UNICAST || safi == SAFI_LABELED_UNICAST)) { flog_warn( EC_BGP_CAPABILITY_INVALID_DATA, "%s Unexpected afi/safi/next-hop afi: %s/%s/%u " "in Extended Next-hop capability, ignoring", peer->host, iana_afi2str(pkt_afi), iana_safi2str(pkt_safi), pkt_nh_afi); continue; } SET_FLAG(peer->af_cap[afi][safi], PEER_CAP_ENHE_AF_RCV); if (CHECK_FLAG(peer->af_cap[afi][safi], PEER_CAP_ENHE_AF_ADV)) SET_FLAG(peer->af_cap[afi][safi], PEER_CAP_ENHE_AF_NEGO); } SET_FLAG(peer->cap, PEER_CAP_ENHE_RCV); return 0; } static int bgp_capability_hostname(struct peer *peer, struct capability_header *hdr) { struct stream *s = BGP_INPUT(peer); char str[BGP_MAX_HOSTNAME + 1]; size_t end = stream_get_getp(s) + hdr->length; uint8_t len; SET_FLAG(peer->cap, PEER_CAP_HOSTNAME_RCV); len = stream_getc(s); if (stream_get_getp(s) + len > end) { flog_warn( EC_BGP_CAPABILITY_INVALID_DATA, "%s: Received malformed hostname capability from peer %s", __FUNCTION__, peer->host); return -1; } if (len > BGP_MAX_HOSTNAME) { stream_get(str, s, BGP_MAX_HOSTNAME); stream_forward_getp(s, len - BGP_MAX_HOSTNAME); len = BGP_MAX_HOSTNAME; /* to set the '\0' below */ } else if (len) stream_get(str, s, len); if (len) { str[len] = '\0'; if (peer->hostname != NULL) { XFREE(MTYPE_BGP_PEER_HOST, peer->hostname); peer->hostname = NULL; } if (peer->domainname != NULL) { XFREE(MTYPE_BGP_PEER_HOST, peer->domainname); peer->domainname = NULL; } peer->hostname = XSTRDUP(MTYPE_BGP_PEER_HOST, str); } if (stream_get_getp(s) + 1 > end) { flog_warn( EC_BGP_CAPABILITY_INVALID_DATA, "%s: Received invalid domain name len (hostname capability) from peer %s", __FUNCTION__, peer->host); return -1; } len = stream_getc(s); if (stream_get_getp(s) + len > end) { flog_warn( EC_BGP_CAPABILITY_INVALID_DATA, "%s: Received runt domain name (hostname capability) from peer %s", __FUNCTION__, peer->host); return -1; } if (len > BGP_MAX_HOSTNAME) { stream_get(str, s, BGP_MAX_HOSTNAME); stream_forward_getp(s, len - BGP_MAX_HOSTNAME); len = BGP_MAX_HOSTNAME; /* to set the '\0' below */ } else if (len) stream_get(str, s, len); if (len) { str[len] = '\0'; if (peer->domainname != NULL) { XFREE(MTYPE_BGP_PEER_HOST, peer->domainname); peer->domainname = NULL; } peer->domainname = XSTRDUP(MTYPE_BGP_PEER_HOST, str); } if (bgp_debug_neighbor_events(peer)) { zlog_debug("%s received hostname %s, domainname %s", peer->host, peer->hostname, peer->domainname); } return 0; } static const struct message capcode_str[] = { {CAPABILITY_CODE_MP, "MultiProtocol Extensions"}, {CAPABILITY_CODE_REFRESH, "Route Refresh"}, {CAPABILITY_CODE_ORF, "Cooperative Route Filtering"}, {CAPABILITY_CODE_RESTART, "Graceful Restart"}, {CAPABILITY_CODE_AS4, "4-octet AS number"}, {CAPABILITY_CODE_ADDPATH, "AddPath"}, {CAPABILITY_CODE_DYNAMIC, "Dynamic"}, {CAPABILITY_CODE_ENHE, "Extended Next Hop Encoding"}, {CAPABILITY_CODE_DYNAMIC_OLD, "Dynamic (Old)"}, {CAPABILITY_CODE_REFRESH_OLD, "Route Refresh (Old)"}, {CAPABILITY_CODE_ORF_OLD, "ORF (Old)"}, {CAPABILITY_CODE_FQDN, "FQDN"}, {0}}; /* Minimum sizes for length field of each cap (so not inc. the header) */ static const size_t cap_minsizes[] = { [CAPABILITY_CODE_MP] = CAPABILITY_CODE_MP_LEN, [CAPABILITY_CODE_REFRESH] = CAPABILITY_CODE_REFRESH_LEN, [CAPABILITY_CODE_ORF] = CAPABILITY_CODE_ORF_LEN, [CAPABILITY_CODE_RESTART] = CAPABILITY_CODE_RESTART_LEN, [CAPABILITY_CODE_AS4] = CAPABILITY_CODE_AS4_LEN, [CAPABILITY_CODE_ADDPATH] = CAPABILITY_CODE_ADDPATH_LEN, [CAPABILITY_CODE_DYNAMIC] = CAPABILITY_CODE_DYNAMIC_LEN, [CAPABILITY_CODE_DYNAMIC_OLD] = CAPABILITY_CODE_DYNAMIC_LEN, [CAPABILITY_CODE_ENHE] = CAPABILITY_CODE_ENHE_LEN, [CAPABILITY_CODE_REFRESH_OLD] = CAPABILITY_CODE_REFRESH_LEN, [CAPABILITY_CODE_ORF_OLD] = CAPABILITY_CODE_ORF_LEN, [CAPABILITY_CODE_FQDN] = CAPABILITY_CODE_MIN_FQDN_LEN, }; /* value the capability must be a multiple of. * 0-data capabilities won't be checked against this. * Other capabilities whose data doesn't fall on convenient boundaries for this * table should be set to 1. */ static const size_t cap_modsizes[] = { [CAPABILITY_CODE_MP] = 4, [CAPABILITY_CODE_REFRESH] = 1, [CAPABILITY_CODE_ORF] = 1, [CAPABILITY_CODE_RESTART] = 1, [CAPABILITY_CODE_AS4] = 4, [CAPABILITY_CODE_ADDPATH] = 4, [CAPABILITY_CODE_DYNAMIC] = 1, [CAPABILITY_CODE_DYNAMIC_OLD] = 1, [CAPABILITY_CODE_ENHE] = 6, [CAPABILITY_CODE_REFRESH_OLD] = 1, [CAPABILITY_CODE_ORF_OLD] = 1, [CAPABILITY_CODE_FQDN] = 1, }; /** * Parse given capability. * XXX: This is reading into a stream, but not using stream API * * @param[out] mp_capability Set to 1 on return iff one or more Multiprotocol * capabilities were encountered. */ static int bgp_capability_parse(struct peer *peer, size_t length, int *mp_capability, uint8_t **error) { int ret; struct stream *s = BGP_INPUT(peer); size_t end = stream_get_getp(s) + length; assert(STREAM_READABLE(s) >= length); while (stream_get_getp(s) < end) { size_t start; uint8_t *sp = stream_pnt(s); struct capability_header caphdr; ret = 0; /* We need at least capability code and capability length. */ if (stream_get_getp(s) + 2 > end) { zlog_info("%s Capability length error (< header)", peer->host); bgp_notify_send(peer, BGP_NOTIFY_OPEN_ERR, BGP_NOTIFY_OPEN_MALFORMED_ATTR); return -1; } caphdr.code = stream_getc(s); caphdr.length = stream_getc(s); start = stream_get_getp(s); /* Capability length check sanity check. */ if (start + caphdr.length > end) { zlog_info("%s Capability length error (< length)", peer->host); bgp_notify_send(peer, BGP_NOTIFY_OPEN_ERR, BGP_NOTIFY_OPEN_MALFORMED_ATTR); return -1; } if (bgp_debug_neighbor_events(peer)) zlog_debug("%s OPEN has %s capability (%u), length %u", peer->host, lookup_msg(capcode_str, caphdr.code, NULL), caphdr.code, caphdr.length); /* Length sanity check, type-specific, for known capabilities */ switch (caphdr.code) { case CAPABILITY_CODE_MP: case CAPABILITY_CODE_REFRESH: case CAPABILITY_CODE_REFRESH_OLD: case CAPABILITY_CODE_ORF: case CAPABILITY_CODE_ORF_OLD: case CAPABILITY_CODE_RESTART: case CAPABILITY_CODE_AS4: case CAPABILITY_CODE_ADDPATH: case CAPABILITY_CODE_DYNAMIC: case CAPABILITY_CODE_DYNAMIC_OLD: case CAPABILITY_CODE_ENHE: case CAPABILITY_CODE_FQDN: /* Check length. */ if (caphdr.length < cap_minsizes[caphdr.code]) { zlog_info( "%s %s Capability length error: got %u," " expected at least %u", peer->host, lookup_msg(capcode_str, caphdr.code, NULL), caphdr.length, (unsigned)cap_minsizes[caphdr.code]); bgp_notify_send(peer, BGP_NOTIFY_OPEN_ERR, BGP_NOTIFY_OPEN_MALFORMED_ATTR); return -1; } if (caphdr.length && caphdr.length % cap_modsizes[caphdr.code] != 0) { zlog_info( "%s %s Capability length error: got %u," " expected a multiple of %u", peer->host, lookup_msg(capcode_str, caphdr.code, NULL), caphdr.length, (unsigned)cap_modsizes[caphdr.code]); bgp_notify_send(peer, BGP_NOTIFY_OPEN_ERR, BGP_NOTIFY_OPEN_MALFORMED_ATTR); return -1; } /* we deliberately ignore unknown codes, see below */ default: break; } switch (caphdr.code) { case CAPABILITY_CODE_MP: { *mp_capability = 1; /* Ignore capability when override-capability is set. */ if (!CHECK_FLAG(peer->flags, PEER_FLAG_OVERRIDE_CAPABILITY)) { /* Set negotiated value. */ ret = bgp_capability_mp(peer, &caphdr); /* Unsupported Capability. */ if (ret < 0) { /* Store return data. */ memcpy(*error, sp, caphdr.length + 2); *error += caphdr.length + 2; } ret = 0; /* Don't return error for this */ } } break; case CAPABILITY_CODE_REFRESH: case CAPABILITY_CODE_REFRESH_OLD: { /* BGP refresh capability */ if (caphdr.code == CAPABILITY_CODE_REFRESH_OLD) SET_FLAG(peer->cap, PEER_CAP_REFRESH_OLD_RCV); else SET_FLAG(peer->cap, PEER_CAP_REFRESH_NEW_RCV); } break; case CAPABILITY_CODE_ORF: case CAPABILITY_CODE_ORF_OLD: ret = bgp_capability_orf_entry(peer, &caphdr); break; case CAPABILITY_CODE_RESTART: ret = bgp_capability_restart(peer, &caphdr); break; case CAPABILITY_CODE_DYNAMIC: case CAPABILITY_CODE_DYNAMIC_OLD: SET_FLAG(peer->cap, PEER_CAP_DYNAMIC_RCV); break; case CAPABILITY_CODE_AS4: /* Already handled as a special-case parsing of the * capabilities * at the beginning of OPEN processing. So we care not a * jot * for the value really, only error case. */ if (!bgp_capability_as4(peer, &caphdr)) ret = -1; break; case CAPABILITY_CODE_ADDPATH: ret = bgp_capability_addpath(peer, &caphdr); break; case CAPABILITY_CODE_ENHE: ret = bgp_capability_enhe(peer, &caphdr); break; case CAPABILITY_CODE_FQDN: ret = bgp_capability_hostname(peer, &caphdr); break; default: if (caphdr.code > 128) { /* We don't send Notification for unknown vendor specific capabilities. It seems reasonable for now... */ flog_warn(EC_BGP_CAPABILITY_VENDOR, "%s Vendor specific capability %d", peer->host, caphdr.code); } else { flog_warn( EC_BGP_CAPABILITY_UNKNOWN, "%s unrecognized capability code: %d - ignored", peer->host, caphdr.code); memcpy(*error, sp, caphdr.length + 2); *error += caphdr.length + 2; } } if (ret < 0) { bgp_notify_send(peer, BGP_NOTIFY_OPEN_ERR, BGP_NOTIFY_OPEN_MALFORMED_ATTR); return -1; } if (stream_get_getp(s) != (start + caphdr.length)) { if (stream_get_getp(s) > (start + caphdr.length)) flog_warn( EC_BGP_CAPABILITY_INVALID_LENGTH, "%s Cap-parser for %s read past cap-length, %u!", peer->host, lookup_msg(capcode_str, caphdr.code, NULL), caphdr.length); stream_set_getp(s, start + caphdr.length); } } return 0; } static int bgp_auth_parse(struct peer *peer, size_t length) { bgp_notify_send(peer, BGP_NOTIFY_OPEN_ERR, BGP_NOTIFY_OPEN_AUTH_FAILURE); return -1; } static int strict_capability_same(struct peer *peer) { int i, j; for (i = AFI_IP; i < AFI_MAX; i++) for (j = SAFI_UNICAST; j < SAFI_MAX; j++) if (peer->afc[i][j] != peer->afc_nego[i][j]) return 0; return 1; } /* peek into option, stores ASN to *as4 if the AS4 capability was found. * Returns 0 if no as4 found, as4cap value otherwise. */ as_t peek_for_as4_capability(struct peer *peer, uint8_t length) { struct stream *s = BGP_INPUT(peer); size_t orig_getp = stream_get_getp(s); size_t end = orig_getp + length; as_t as4 = 0; if (BGP_DEBUG(as4, AS4)) zlog_info( "%s [AS4] rcv OPEN w/ OPTION parameter len: %u," " peeking for as4", peer->host, length); /* the error cases we DONT handle, we ONLY try to read as4 out of * correctly formatted options. */ while (stream_get_getp(s) < end) { uint8_t opt_type; uint8_t opt_length; /* Check the length. */ if (stream_get_getp(s) + 2 > end) goto end; /* Fetch option type and length. */ opt_type = stream_getc(s); opt_length = stream_getc(s); /* Option length check. */ if (stream_get_getp(s) + opt_length > end) goto end; if (opt_type == BGP_OPEN_OPT_CAP) { unsigned long capd_start = stream_get_getp(s); unsigned long capd_end = capd_start + opt_length; assert(capd_end <= end); while (stream_get_getp(s) < capd_end) { struct capability_header hdr; if (stream_get_getp(s) + 2 > capd_end) goto end; hdr.code = stream_getc(s); hdr.length = stream_getc(s); if ((stream_get_getp(s) + hdr.length) > capd_end) goto end; if (hdr.code == CAPABILITY_CODE_AS4) { if (BGP_DEBUG(as4, AS4)) zlog_info( "[AS4] found AS4 capability, about to parse"); as4 = bgp_capability_as4(peer, &hdr); goto end; } stream_forward_getp(s, hdr.length); } } } end: stream_set_getp(s, orig_getp); return as4; } /** * Parse open option. * * @param[out] mp_capability @see bgp_capability_parse() for semantics. */ int bgp_open_option_parse(struct peer *peer, uint8_t length, int *mp_capability) { int ret = 0; uint8_t *error; uint8_t error_data[BGP_MAX_PACKET_SIZE]; struct stream *s = BGP_INPUT(peer); size_t end = stream_get_getp(s) + length; error = error_data; if (bgp_debug_neighbor_events(peer)) zlog_debug("%s rcv OPEN w/ OPTION parameter len: %u", peer->host, length); while (stream_get_getp(s) < end) { uint8_t opt_type; uint8_t opt_length; /* Must have at least an OPEN option header */ if (STREAM_READABLE(s) < 2) { zlog_info("%s Option length error", peer->host); bgp_notify_send(peer, BGP_NOTIFY_OPEN_ERR, BGP_NOTIFY_OPEN_MALFORMED_ATTR); return -1; } /* Fetch option type and length. */ opt_type = stream_getc(s); opt_length = stream_getc(s); /* Option length check. */ if (STREAM_READABLE(s) < opt_length) { zlog_info("%s Option length error", peer->host); bgp_notify_send(peer, BGP_NOTIFY_OPEN_ERR, BGP_NOTIFY_OPEN_MALFORMED_ATTR); return -1; } if (bgp_debug_neighbor_events(peer)) zlog_debug( "%s rcvd OPEN w/ optional parameter type %u (%s) len %u", peer->host, opt_type, opt_type == BGP_OPEN_OPT_AUTH ? "Authentication" : opt_type == BGP_OPEN_OPT_CAP ? "Capability" : "Unknown", opt_length); switch (opt_type) { case BGP_OPEN_OPT_AUTH: ret = bgp_auth_parse(peer, opt_length); break; case BGP_OPEN_OPT_CAP: ret = bgp_capability_parse(peer, opt_length, mp_capability, &error); break; default: bgp_notify_send(peer, BGP_NOTIFY_OPEN_ERR, BGP_NOTIFY_OPEN_UNSUP_PARAM); ret = -1; break; } /* Parse error. To accumulate all unsupported capability codes, bgp_capability_parse does not return -1 when encounter unsupported capability code. To detect that, please check error and erro_data pointer, like below. */ if (ret < 0) return -1; } /* All OPEN option is parsed. Check capability when strict compare flag is enabled.*/ if (CHECK_FLAG(peer->flags, PEER_FLAG_STRICT_CAP_MATCH)) { /* If Unsupported Capability exists. */ if (error != error_data) { bgp_notify_send_with_data(peer, BGP_NOTIFY_OPEN_ERR, BGP_NOTIFY_OPEN_UNSUP_CAPBL, error_data, error - error_data); return -1; } /* Check local capability does not negotiated with remote peer. */ if (!strict_capability_same(peer)) { bgp_notify_send(peer, BGP_NOTIFY_OPEN_ERR, BGP_NOTIFY_OPEN_UNSUP_CAPBL); return -1; } } /* Check there are no common AFI/SAFIs and send Unsupported Capability error. */ if (*mp_capability && !CHECK_FLAG(peer->flags, PEER_FLAG_OVERRIDE_CAPABILITY)) { if (!peer->afc_nego[AFI_IP][SAFI_UNICAST] && !peer->afc_nego[AFI_IP][SAFI_MULTICAST] && !peer->afc_nego[AFI_IP][SAFI_LABELED_UNICAST] && !peer->afc_nego[AFI_IP][SAFI_MPLS_VPN] && !peer->afc_nego[AFI_IP][SAFI_ENCAP] && !peer->afc_nego[AFI_IP][SAFI_FLOWSPEC] && !peer->afc_nego[AFI_IP6][SAFI_UNICAST] && !peer->afc_nego[AFI_IP6][SAFI_MULTICAST] && !peer->afc_nego[AFI_IP6][SAFI_LABELED_UNICAST] && !peer->afc_nego[AFI_IP6][SAFI_MPLS_VPN] && !peer->afc_nego[AFI_IP6][SAFI_ENCAP] && !peer->afc_nego[AFI_IP6][SAFI_FLOWSPEC] && !peer->afc_nego[AFI_L2VPN][SAFI_EVPN]) { flog_err(EC_BGP_PKT_OPEN, "%s [Error] Configured AFI/SAFIs do not " "overlap with received MP capabilities", peer->host); if (error != error_data) bgp_notify_send_with_data( peer, BGP_NOTIFY_OPEN_ERR, BGP_NOTIFY_OPEN_UNSUP_CAPBL, error_data, error - error_data); else bgp_notify_send(peer, BGP_NOTIFY_OPEN_ERR, BGP_NOTIFY_OPEN_UNSUP_CAPBL); return -1; } } return 0; } static void bgp_open_capability_orf(struct stream *s, struct peer *peer, afi_t afi, safi_t safi, uint8_t code) { uint8_t cap_len; uint8_t orf_len; unsigned long capp; unsigned long orfp; unsigned long numberp; int number_of_orfs = 0; iana_afi_t pkt_afi; iana_safi_t pkt_safi; /* Convert AFI, SAFI to values for packet. */ bgp_map_afi_safi_int2iana(afi, safi, &pkt_afi, &pkt_safi); stream_putc(s, BGP_OPEN_OPT_CAP); capp = stream_get_endp(s); /* Set Capability Len Pointer */ stream_putc(s, 0); /* Capability Length */ stream_putc(s, code); /* Capability Code */ orfp = stream_get_endp(s); /* Set ORF Len Pointer */ stream_putc(s, 0); /* ORF Length */ stream_putw(s, pkt_afi); stream_putc(s, 0); stream_putc(s, pkt_safi); numberp = stream_get_endp(s); /* Set Number Pointer */ stream_putc(s, 0); /* Number of ORFs */ /* Address Prefix ORF */ if (CHECK_FLAG(peer->af_flags[afi][safi], PEER_FLAG_ORF_PREFIX_SM) || CHECK_FLAG(peer->af_flags[afi][safi], PEER_FLAG_ORF_PREFIX_RM)) { stream_putc(s, (code == CAPABILITY_CODE_ORF ? ORF_TYPE_PREFIX : ORF_TYPE_PREFIX_OLD)); if (CHECK_FLAG(peer->af_flags[afi][safi], PEER_FLAG_ORF_PREFIX_SM) && CHECK_FLAG(peer->af_flags[afi][safi], PEER_FLAG_ORF_PREFIX_RM)) { SET_FLAG(peer->af_cap[afi][safi], PEER_CAP_ORF_PREFIX_SM_ADV); SET_FLAG(peer->af_cap[afi][safi], PEER_CAP_ORF_PREFIX_RM_ADV); stream_putc(s, ORF_MODE_BOTH); } else if (CHECK_FLAG(peer->af_flags[afi][safi], PEER_FLAG_ORF_PREFIX_SM)) { SET_FLAG(peer->af_cap[afi][safi], PEER_CAP_ORF_PREFIX_SM_ADV); stream_putc(s, ORF_MODE_SEND); } else { SET_FLAG(peer->af_cap[afi][safi], PEER_CAP_ORF_PREFIX_RM_ADV); stream_putc(s, ORF_MODE_RECEIVE); } number_of_orfs++; } /* Total Number of ORFs. */ stream_putc_at(s, numberp, number_of_orfs); /* Total ORF Len. */ orf_len = stream_get_endp(s) - orfp - 1; stream_putc_at(s, orfp, orf_len); /* Total Capability Len. */ cap_len = stream_get_endp(s) - capp - 1; stream_putc_at(s, capp, cap_len); } /* Fill in capability open option to the packet. */ void bgp_open_capability(struct stream *s, struct peer *peer) { uint8_t len; unsigned long cp, capp, rcapp; iana_afi_t pkt_afi; afi_t afi; safi_t safi; iana_safi_t pkt_safi; as_t local_as; uint32_t restart_time; uint8_t afi_safi_count = 0; int adv_addpath_tx = 0; /* Remember current pointer for Opt Parm Len. */ cp = stream_get_endp(s); /* Opt Parm Len. */ stream_putc(s, 0); /* Do not send capability. */ if (!CHECK_FLAG(peer->sflags, PEER_STATUS_CAPABILITY_OPEN) || CHECK_FLAG(peer->flags, PEER_FLAG_DONT_CAPABILITY)) return; /* MP capability for configured AFI, SAFI */ FOREACH_AFI_SAFI (afi, safi) { if (peer->afc[afi][safi]) { /* Convert AFI, SAFI to values for packet. */ bgp_map_afi_safi_int2iana(afi, safi, &pkt_afi, &pkt_safi); peer->afc_adv[afi][safi] = 1; stream_putc(s, BGP_OPEN_OPT_CAP); stream_putc(s, CAPABILITY_CODE_MP_LEN + 2); stream_putc(s, CAPABILITY_CODE_MP); stream_putc(s, CAPABILITY_CODE_MP_LEN); stream_putw(s, pkt_afi); stream_putc(s, 0); stream_putc(s, pkt_safi); /* Extended nexthop capability - currently * supporting RFC-5549 for * Link-Local peering only */ if (CHECK_FLAG(peer->flags, PEER_FLAG_CAPABILITY_ENHE) && peer->su.sa.sa_family == AF_INET6 && afi == AFI_IP && (safi == SAFI_UNICAST || safi == SAFI_LABELED_UNICAST)) { /* RFC 5549 Extended Next Hop Encoding */ SET_FLAG(peer->cap, PEER_CAP_ENHE_ADV); stream_putc(s, BGP_OPEN_OPT_CAP); stream_putc(s, CAPABILITY_CODE_ENHE_LEN + 2); stream_putc(s, CAPABILITY_CODE_ENHE); stream_putc(s, CAPABILITY_CODE_ENHE_LEN); SET_FLAG(peer->af_cap[AFI_IP][safi], PEER_CAP_ENHE_AF_ADV); stream_putw(s, pkt_afi); stream_putw(s, pkt_safi); stream_putw(s, afi_int2iana(AFI_IP6)); if (CHECK_FLAG(peer->af_cap[afi][safi], PEER_CAP_ENHE_AF_RCV)) SET_FLAG(peer->af_cap[afi][safi], PEER_CAP_ENHE_AF_NEGO); } } } /* Route refresh. */ SET_FLAG(peer->cap, PEER_CAP_REFRESH_ADV); stream_putc(s, BGP_OPEN_OPT_CAP); stream_putc(s, CAPABILITY_CODE_REFRESH_LEN + 2); stream_putc(s, CAPABILITY_CODE_REFRESH_OLD); stream_putc(s, CAPABILITY_CODE_REFRESH_LEN); stream_putc(s, BGP_OPEN_OPT_CAP); stream_putc(s, CAPABILITY_CODE_REFRESH_LEN + 2); stream_putc(s, CAPABILITY_CODE_REFRESH); stream_putc(s, CAPABILITY_CODE_REFRESH_LEN); /* AS4 */ SET_FLAG(peer->cap, PEER_CAP_AS4_ADV); stream_putc(s, BGP_OPEN_OPT_CAP); stream_putc(s, CAPABILITY_CODE_AS4_LEN + 2); stream_putc(s, CAPABILITY_CODE_AS4); stream_putc(s, CAPABILITY_CODE_AS4_LEN); if (peer->change_local_as) local_as = peer->change_local_as; else local_as = peer->local_as; stream_putl(s, local_as); /* AddPath */ FOREACH_AFI_SAFI (afi, safi) { if (peer->afc[afi][safi]) { afi_safi_count++; /* Only advertise addpath TX if a feature that * will use it is * configured */ if (peer->addpath_type[afi][safi] != BGP_ADDPATH_NONE) adv_addpath_tx = 1; } } SET_FLAG(peer->cap, PEER_CAP_ADDPATH_ADV); stream_putc(s, BGP_OPEN_OPT_CAP); stream_putc(s, (CAPABILITY_CODE_ADDPATH_LEN * afi_safi_count) + 2); stream_putc(s, CAPABILITY_CODE_ADDPATH); stream_putc(s, CAPABILITY_CODE_ADDPATH_LEN * afi_safi_count); FOREACH_AFI_SAFI (afi, safi) { if (peer->afc[afi][safi]) { /* Convert AFI, SAFI to values for packet. */ bgp_map_afi_safi_int2iana(afi, safi, &pkt_afi, &pkt_safi); stream_putw(s, pkt_afi); stream_putc(s, pkt_safi); if (adv_addpath_tx) { stream_putc(s, BGP_ADDPATH_RX | BGP_ADDPATH_TX); SET_FLAG(peer->af_cap[afi][safi], PEER_CAP_ADDPATH_AF_RX_ADV); SET_FLAG(peer->af_cap[afi][safi], PEER_CAP_ADDPATH_AF_TX_ADV); } else { stream_putc(s, BGP_ADDPATH_RX); SET_FLAG(peer->af_cap[afi][safi], PEER_CAP_ADDPATH_AF_RX_ADV); UNSET_FLAG(peer->af_cap[afi][safi], PEER_CAP_ADDPATH_AF_TX_ADV); } } } /* ORF capability. */ FOREACH_AFI_SAFI (afi, safi) { if (CHECK_FLAG(peer->af_flags[afi][safi], PEER_FLAG_ORF_PREFIX_SM) || CHECK_FLAG(peer->af_flags[afi][safi], PEER_FLAG_ORF_PREFIX_RM)) { bgp_open_capability_orf(s, peer, afi, safi, CAPABILITY_CODE_ORF_OLD); bgp_open_capability_orf(s, peer, afi, safi, CAPABILITY_CODE_ORF); } } /* Dynamic capability. */ if (CHECK_FLAG(peer->flags, PEER_FLAG_DYNAMIC_CAPABILITY)) { SET_FLAG(peer->cap, PEER_CAP_DYNAMIC_ADV); stream_putc(s, BGP_OPEN_OPT_CAP); stream_putc(s, CAPABILITY_CODE_DYNAMIC_LEN + 2); stream_putc(s, CAPABILITY_CODE_DYNAMIC_OLD); stream_putc(s, CAPABILITY_CODE_DYNAMIC_LEN); stream_putc(s, BGP_OPEN_OPT_CAP); stream_putc(s, CAPABILITY_CODE_DYNAMIC_LEN + 2); stream_putc(s, CAPABILITY_CODE_DYNAMIC); stream_putc(s, CAPABILITY_CODE_DYNAMIC_LEN); } /* Hostname capability */ if (cmd_hostname_get()) { SET_FLAG(peer->cap, PEER_CAP_HOSTNAME_ADV); stream_putc(s, BGP_OPEN_OPT_CAP); rcapp = stream_get_endp(s); /* Ptr to length placeholder */ stream_putc(s, 0); /* dummy len for now */ stream_putc(s, CAPABILITY_CODE_FQDN); capp = stream_get_endp(s); stream_putc(s, 0); /* dummy len for now */ len = strlen(cmd_hostname_get()); if (len > BGP_MAX_HOSTNAME) len = BGP_MAX_HOSTNAME; stream_putc(s, len); stream_put(s, cmd_hostname_get(), len); if (cmd_domainname_get()) { len = strlen(cmd_domainname_get()); if (len > BGP_MAX_HOSTNAME) len = BGP_MAX_HOSTNAME; stream_putc(s, len); stream_put(s, cmd_domainname_get(), len); } else stream_putc(s, 0); /* 0 length */ /* Set the lengths straight */ len = stream_get_endp(s) - rcapp - 1; stream_putc_at(s, rcapp, len); len = stream_get_endp(s) - capp - 1; stream_putc_at(s, capp, len); if (bgp_debug_neighbor_events(peer)) zlog_debug( "%s Sending hostname cap with hn = %s, dn = %s", peer->host, cmd_hostname_get(), cmd_domainname_get()); } /* Sending base graceful-restart capability irrespective of the config */ SET_FLAG(peer->cap, PEER_CAP_RESTART_ADV); stream_putc(s, BGP_OPEN_OPT_CAP); capp = stream_get_endp(s); /* Set Capability Len Pointer */ stream_putc(s, 0); /* Capability Length */ stream_putc(s, CAPABILITY_CODE_RESTART); rcapp = stream_get_endp(s); /* Set Restart Capability Len Pointer */ stream_putc(s, 0); restart_time = peer->bgp->restart_time; if (peer->bgp->t_startup) { SET_FLAG(restart_time, RESTART_R_BIT); SET_FLAG(peer->cap, PEER_CAP_RESTART_BIT_ADV); } stream_putw(s, restart_time); /* Send address-family specific graceful-restart capability only when GR config is present */ if (bgp_flag_check(peer->bgp, BGP_FLAG_GRACEFUL_RESTART)) { FOREACH_AFI_SAFI (afi, safi) { if (peer->afc[afi][safi]) { /* Convert AFI, SAFI to values for * packet. */ bgp_map_afi_safi_int2iana(afi, safi, &pkt_afi, &pkt_safi); stream_putw(s, pkt_afi); stream_putc(s, pkt_safi); if (bgp_flag_check(peer->bgp, BGP_FLAG_GR_PRESERVE_FWD)) stream_putc(s, RESTART_F_BIT); else stream_putc(s, 0); } } } /* Total Graceful restart capability Len. */ len = stream_get_endp(s) - rcapp - 1; stream_putc_at(s, rcapp, len); /* Total Capability Len. */ len = stream_get_endp(s) - capp - 1; stream_putc_at(s, capp, len); /* Total Opt Parm Len. */ len = stream_get_endp(s) - cp - 1; stream_putc_at(s, cp, len); } frr-7.2.1/bgpd/bgp_open.h0000644000000000000000000000666313610377563012105 00000000000000/* BGP open message handling * Copyright (C) 1999 Kunihiro Ishiguro * * This file is part of GNU Zebra. * * GNU Zebra is free software; you can redistribute it and/or modify it * under the terms of the GNU General Public License as published by the * Free Software Foundation; either version 2, or (at your option) any * later version. * * GNU Zebra 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; see the file COPYING; if not, write to the Free Software * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA */ #ifndef _QUAGGA_BGP_OPEN_H #define _QUAGGA_BGP_OPEN_H /* Standard header for capability TLV */ struct capability_header { uint8_t code; uint8_t length; }; /* Generic MP capability data */ struct capability_mp_data { uint16_t afi; /* iana_afi_t */ uint8_t reserved; uint8_t safi; /* iana_safi_t */ }; struct graceful_restart_af { afi_t afi; safi_t safi; uint8_t flag; }; /* Capability Code */ #define CAPABILITY_CODE_MP 1 /* Multiprotocol Extensions */ #define CAPABILITY_CODE_REFRESH 2 /* Route Refresh Capability */ #define CAPABILITY_CODE_ORF 3 /* Cooperative Route Filtering Capability */ #define CAPABILITY_CODE_RESTART 64 /* Graceful Restart Capability */ #define CAPABILITY_CODE_AS4 65 /* 4-octet AS number Capability */ #define CAPABILITY_CODE_DYNAMIC_OLD 66 /* Dynamic Capability, deprecated since 2003 */ #define CAPABILITY_CODE_DYNAMIC 67 /* Dynamic Capability */ #define CAPABILITY_CODE_ADDPATH 69 /* Addpath Capability */ #define CAPABILITY_CODE_FQDN 73 /* Advertise hostname capability */ #define CAPABILITY_CODE_ENHE 5 /* Extended Next Hop Encoding */ #define CAPABILITY_CODE_REFRESH_OLD 128 /* Route Refresh Capability(cisco) */ #define CAPABILITY_CODE_ORF_OLD 130 /* Cooperative Route Filtering Capability(cisco) */ /* Capability Length */ #define CAPABILITY_CODE_MP_LEN 4 #define CAPABILITY_CODE_REFRESH_LEN 0 #define CAPABILITY_CODE_DYNAMIC_LEN 0 #define CAPABILITY_CODE_RESTART_LEN 2 /* Receiving only case */ #define CAPABILITY_CODE_AS4_LEN 4 #define CAPABILITY_CODE_ADDPATH_LEN 4 #define CAPABILITY_CODE_ENHE_LEN 6 /* NRLI AFI = 2, SAFI = 2, Nexthop AFI = 2 */ #define CAPABILITY_CODE_MIN_FQDN_LEN 2 #define CAPABILITY_CODE_ORF_LEN 5 /* Cooperative Route Filtering Capability. */ /* ORF Type */ #define ORF_TYPE_PREFIX 64 #define ORF_TYPE_PREFIX_OLD 128 /* ORF Mode */ #define ORF_MODE_RECEIVE 1 #define ORF_MODE_SEND 2 #define ORF_MODE_BOTH 3 /* Capability Message Action. */ #define CAPABILITY_ACTION_SET 0 #define CAPABILITY_ACTION_UNSET 1 /* Graceful Restart */ #define RESTART_R_BIT 0x8000 #define RESTART_F_BIT 0x80 extern int bgp_open_option_parse(struct peer *, uint8_t, int *); extern void bgp_open_capability(struct stream *, struct peer *); extern void bgp_capability_vty_out(struct vty *vty, struct peer *peer, bool use_json, json_object *json_neigh); extern as_t peek_for_as4_capability(struct peer *, uint8_t); #endif /* _QUAGGA_BGP_OPEN_H */ frr-7.2.1/bgpd/bgp_packet.c0000644000000000000000000020065613610377563012404 00000000000000/* BGP packet management routine. * Contains utility functions for constructing and consuming BGP messages. * Copyright (C) 2017 Cumulus Networks * Copyright (C) 1999 Kunihiro Ishiguro * * This file is part of GNU Zebra. * * GNU Zebra is free software; you can redistribute it and/or modify it * under the terms of the GNU General Public License as published by the * Free Software Foundation; either version 2, or (at your option) any * later version. * * GNU Zebra 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; see the file COPYING; if not, write to the Free Software * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA */ #include #include #include "thread.h" #include "stream.h" #include "network.h" #include "prefix.h" #include "command.h" #include "log.h" #include "memory.h" #include "sockunion.h" /* for inet_ntop () */ #include "sockopt.h" #include "linklist.h" #include "plist.h" #include "queue.h" #include "filter.h" #include "lib_errors.h" #include "bgpd/bgpd.h" #include "bgpd/bgp_table.h" #include "bgpd/bgp_dump.h" #include "bgpd/bgp_bmp.h" #include "bgpd/bgp_attr.h" #include "bgpd/bgp_debug.h" #include "bgpd/bgp_errors.h" #include "bgpd/bgp_fsm.h" #include "bgpd/bgp_route.h" #include "bgpd/bgp_packet.h" #include "bgpd/bgp_open.h" #include "bgpd/bgp_aspath.h" #include "bgpd/bgp_community.h" #include "bgpd/bgp_ecommunity.h" #include "bgpd/bgp_lcommunity.h" #include "bgpd/bgp_network.h" #include "bgpd/bgp_mplsvpn.h" #include "bgpd/bgp_evpn.h" #include "bgpd/bgp_advertise.h" #include "bgpd/bgp_vty.h" #include "bgpd/bgp_updgrp.h" #include "bgpd/bgp_label.h" #include "bgpd/bgp_io.h" #include "bgpd/bgp_keepalives.h" #include "bgpd/bgp_flowspec.h" DEFINE_HOOK(bgp_packet_dump, (struct peer *peer, uint8_t type, bgp_size_t size, struct stream *s), (peer, type, size, s)) DEFINE_HOOK(bgp_packet_send, (struct peer *peer, uint8_t type, bgp_size_t size, struct stream *s), (peer, type, size, s)) /** * Sets marker and type fields for a BGP message. * * @param s the stream containing the packet * @param type the packet type * @return the size of the stream */ int bgp_packet_set_marker(struct stream *s, uint8_t type) { int i; /* Fill in marker. */ for (i = 0; i < BGP_MARKER_SIZE; i++) stream_putc(s, 0xff); /* Dummy total length. This field is should be filled in later on. */ stream_putw(s, 0); /* BGP packet type. */ stream_putc(s, type); /* Return current stream size. */ return stream_get_endp(s); } /** * Sets size field for a BGP message. * * Size field is set to the size of the stream passed. * * @param s the stream containing the packet * @return the size of the stream */ int bgp_packet_set_size(struct stream *s) { int cp; /* Preserve current pointer. */ cp = stream_get_endp(s); stream_putw_at(s, BGP_MARKER_SIZE, cp); return cp; } /* * Push a packet onto the beginning of the peer's output queue. * This function acquires the peer's write mutex before proceeding. */ static void bgp_packet_add(struct peer *peer, struct stream *s) { frr_with_mutex(&peer->io_mtx) { stream_fifo_push(peer->obuf, s); } } static struct stream *bgp_update_packet_eor(struct peer *peer, afi_t afi, safi_t safi) { struct stream *s; iana_afi_t pkt_afi; iana_safi_t pkt_safi; if (DISABLE_BGP_ANNOUNCE) return NULL; if (bgp_debug_neighbor_events(peer)) zlog_debug("send End-of-RIB for %s to %s", get_afi_safi_str(afi, safi, false), peer->host); s = stream_new(BGP_MAX_PACKET_SIZE); /* Make BGP update packet. */ bgp_packet_set_marker(s, BGP_MSG_UPDATE); /* Unfeasible Routes Length */ stream_putw(s, 0); if (afi == AFI_IP && safi == SAFI_UNICAST) { /* Total Path Attribute Length */ stream_putw(s, 0); } else { /* Convert AFI, SAFI to values for packet. */ bgp_map_afi_safi_int2iana(afi, safi, &pkt_afi, &pkt_safi); /* Total Path Attribute Length */ stream_putw(s, 6); stream_putc(s, BGP_ATTR_FLAG_OPTIONAL); stream_putc(s, BGP_ATTR_MP_UNREACH_NLRI); stream_putc(s, 3); stream_putw(s, pkt_afi); stream_putc(s, pkt_safi); } bgp_packet_set_size(s); return s; } /* Called when there is a change in the EOR(implicit or explicit) status of a * peer. Ends the update-delay if all expected peers are done with EORs. */ void bgp_check_update_delay(struct bgp *bgp) { struct listnode *node, *nnode; struct peer *peer = NULL; if (bgp_debug_neighbor_events(peer)) zlog_debug("Checking update delay, T: %d R: %d I:%d E: %d", bgp->established, bgp->restarted_peers, bgp->implicit_eors, bgp->explicit_eors); if (bgp->established <= bgp->restarted_peers + bgp->implicit_eors + bgp->explicit_eors) { /* * This is an extra sanity check to make sure we wait for all * the eligible configured peers. This check is performed if * establish wait timer is on, or establish wait option is not * given with the update-delay command */ if (bgp->t_establish_wait || (bgp->v_establish_wait == bgp->v_update_delay)) for (ALL_LIST_ELEMENTS(bgp->peer, node, nnode, peer)) { if (CHECK_FLAG(peer->flags, PEER_FLAG_CONFIG_NODE) && !CHECK_FLAG(peer->flags, PEER_FLAG_SHUTDOWN) && !peer->update_delay_over) { if (bgp_debug_neighbor_events(peer)) zlog_debug( " Peer %s pending, continuing read-only mode", peer->host); return; } } zlog_info( "Update delay ended, restarted: %d, EORs implicit: %d, explicit: %d", bgp->restarted_peers, bgp->implicit_eors, bgp->explicit_eors); bgp_update_delay_end(bgp); } } /* * Called if peer is known to have restarted. The restart-state bit in * Graceful-Restart capability is used for that */ void bgp_update_restarted_peers(struct peer *peer) { if (!bgp_update_delay_active(peer->bgp)) return; /* BGP update delay has ended */ if (peer->update_delay_over) return; /* This peer has already been considered */ if (bgp_debug_neighbor_events(peer)) zlog_debug("Peer %s: Checking restarted", peer->host); if (peer->status == Established) { peer->update_delay_over = 1; peer->bgp->restarted_peers++; bgp_check_update_delay(peer->bgp); } } /* * Called as peer receives a keep-alive. Determines if this occurence can be * taken as an implicit EOR for this peer. * NOTE: The very first keep-alive after the Established state of a peer is * considered implicit EOR for the update-delay purposes */ void bgp_update_implicit_eors(struct peer *peer) { if (!bgp_update_delay_active(peer->bgp)) return; /* BGP update delay has ended */ if (peer->update_delay_over) return; /* This peer has already been considered */ if (bgp_debug_neighbor_events(peer)) zlog_debug("Peer %s: Checking implicit EORs", peer->host); if (peer->status == Established) { peer->update_delay_over = 1; peer->bgp->implicit_eors++; bgp_check_update_delay(peer->bgp); } } /* * Should be called only when there is a change in the EOR_RECEIVED status * for any afi/safi on a peer. */ static void bgp_update_explicit_eors(struct peer *peer) { afi_t afi; safi_t safi; if (!bgp_update_delay_active(peer->bgp)) return; /* BGP update delay has ended */ if (peer->update_delay_over) return; /* This peer has already been considered */ if (bgp_debug_neighbor_events(peer)) zlog_debug("Peer %s: Checking explicit EORs", peer->host); for (afi = AFI_IP; afi < AFI_MAX; afi++) for (safi = SAFI_UNICAST; safi < SAFI_MAX; safi++) { if (peer->afc_nego[afi][safi] && !CHECK_FLAG(peer->af_sflags[afi][safi], PEER_STATUS_EOR_RECEIVED)) { if (bgp_debug_neighbor_events(peer)) zlog_debug( " afi %d safi %d didn't receive EOR", afi, safi); return; } } peer->update_delay_over = 1; peer->bgp->explicit_eors++; bgp_check_update_delay(peer->bgp); } /** * Frontend for NLRI parsing, to fan-out to AFI/SAFI specific parsers. * * mp_withdraw, if set, is used to nullify attr structure on most of the * calling safi function and for evpn, passed as parameter */ int bgp_nlri_parse(struct peer *peer, struct attr *attr, struct bgp_nlri *packet, int mp_withdraw) { switch (packet->safi) { case SAFI_UNICAST: case SAFI_MULTICAST: return bgp_nlri_parse_ip(peer, mp_withdraw ? NULL : attr, packet); case SAFI_LABELED_UNICAST: return bgp_nlri_parse_label(peer, mp_withdraw ? NULL : attr, packet); case SAFI_MPLS_VPN: return bgp_nlri_parse_vpn(peer, mp_withdraw ? NULL : attr, packet); case SAFI_EVPN: return bgp_nlri_parse_evpn(peer, attr, packet, mp_withdraw); case SAFI_FLOWSPEC: return bgp_nlri_parse_flowspec(peer, attr, packet, mp_withdraw); } return BGP_NLRI_PARSE_ERROR; } /* * Checks a variety of conditions to determine whether the peer needs to be * rescheduled for packet generation again, and does so if necessary. * * @param peer to check for rescheduling */ static void bgp_write_proceed_actions(struct peer *peer) { afi_t afi; safi_t safi; struct peer_af *paf; struct bpacket *next_pkt; struct update_subgroup *subgrp; FOREACH_AFI_SAFI (afi, safi) { paf = peer_af_find(peer, afi, safi); if (!paf) continue; subgrp = paf->subgroup; if (!subgrp) continue; next_pkt = paf->next_pkt_to_send; if (next_pkt && next_pkt->buffer) { BGP_TIMER_ON(peer->t_generate_updgrp_packets, bgp_generate_updgrp_packets, 0); return; } /* No packets readily available for AFI/SAFI, are there * subgroup packets * that need to be generated? */ if (bpacket_queue_is_full(SUBGRP_INST(subgrp), SUBGRP_PKTQ(subgrp)) || subgroup_packets_to_build(subgrp)) { BGP_TIMER_ON(peer->t_generate_updgrp_packets, bgp_generate_updgrp_packets, 0); return; } /* No packets to send, see if EOR is pending */ if (CHECK_FLAG(peer->cap, PEER_CAP_RESTART_RCV)) { if (!subgrp->t_coalesce && peer->afc_nego[afi][safi] && peer->synctime && !CHECK_FLAG(peer->af_sflags[afi][safi], PEER_STATUS_EOR_SEND) && safi != SAFI_MPLS_VPN) { BGP_TIMER_ON(peer->t_generate_updgrp_packets, bgp_generate_updgrp_packets, 0); return; } } } } /* * Generate advertisement information (withdraws, updates, EOR) from each * update group a peer belongs to, encode this information into packets, and * enqueue the packets onto the peer's output buffer. */ int bgp_generate_updgrp_packets(struct thread *thread) { struct peer *peer = THREAD_ARG(thread); struct stream *s; struct peer_af *paf; struct bpacket *next_pkt; uint32_t wpq; uint32_t generated = 0; afi_t afi; safi_t safi; wpq = atomic_load_explicit(&peer->bgp->wpkt_quanta, memory_order_relaxed); /* * The code beyond this part deals with update packets, proceed only * if peer is Established and updates are not on hold (as part of * update-delay post processing). */ if (peer->status != Established) return 0; if (peer->bgp->main_peers_update_hold) return 0; do { s = NULL; FOREACH_AFI_SAFI (afi, safi) { paf = peer_af_find(peer, afi, safi); if (!paf || !PAF_SUBGRP(paf)) continue; next_pkt = paf->next_pkt_to_send; /* * Try to generate a packet for the peer if we are at * the end of the list. Always try to push out * WITHDRAWs first. */ if (!next_pkt || !next_pkt->buffer) { next_pkt = subgroup_withdraw_packet( PAF_SUBGRP(paf)); if (!next_pkt || !next_pkt->buffer) subgroup_update_packet(PAF_SUBGRP(paf)); next_pkt = paf->next_pkt_to_send; } /* * If we still don't have a packet to send to the peer, * then try to find out out if we have to send eor or * if not, skip to the next AFI, SAFI. Don't send the * EOR prematurely; if the subgroup's coalesce timer is * running, the adjacency-out structure is not created * yet. */ if (!next_pkt || !next_pkt->buffer) { if (CHECK_FLAG(peer->cap, PEER_CAP_RESTART_RCV)) { if (!(PAF_SUBGRP(paf))->t_coalesce && peer->afc_nego[afi][safi] && peer->synctime && !CHECK_FLAG( peer->af_sflags[afi] [safi], PEER_STATUS_EOR_SEND)) { SET_FLAG(peer->af_sflags[afi] [safi], PEER_STATUS_EOR_SEND); if ((s = bgp_update_packet_eor( peer, afi, safi))) { bgp_packet_add(peer, s); } } } continue; } /* Found a packet template to send, overwrite * packet with appropriate attributes from peer * and advance peer */ s = bpacket_reformat_for_peer(next_pkt, paf); bgp_packet_add(peer, s); bpacket_queue_advance_peer(paf); } } while (s && (++generated < wpq)); if (generated) bgp_writes_on(peer); bgp_write_proceed_actions(peer); return 0; } /* * Creates a BGP Keepalive packet and appends it to the peer's output queue. */ void bgp_keepalive_send(struct peer *peer) { struct stream *s; s = stream_new(BGP_MAX_PACKET_SIZE); /* Make keepalive packet. */ bgp_packet_set_marker(s, BGP_MSG_KEEPALIVE); /* Set packet size. */ (void)bgp_packet_set_size(s); /* Dump packet if debug option is set. */ /* bgp_packet_dump (s); */ if (bgp_debug_keepalive(peer)) zlog_debug("%s sending KEEPALIVE", peer->host); /* Add packet to the peer. */ bgp_packet_add(peer, s); bgp_writes_on(peer); } /* * Creates a BGP Open packet and appends it to the peer's output queue. * Sets capabilities as necessary. */ void bgp_open_send(struct peer *peer) { struct stream *s; uint16_t send_holdtime; as_t local_as; if (CHECK_FLAG(peer->flags, PEER_FLAG_TIMER)) send_holdtime = peer->holdtime; else send_holdtime = peer->bgp->default_holdtime; /* local-as Change */ if (peer->change_local_as) local_as = peer->change_local_as; else local_as = peer->local_as; s = stream_new(BGP_MAX_PACKET_SIZE); /* Make open packet. */ bgp_packet_set_marker(s, BGP_MSG_OPEN); /* Set open packet values. */ stream_putc(s, BGP_VERSION_4); /* BGP version */ stream_putw(s, (local_as <= BGP_AS_MAX) ? (uint16_t)local_as : BGP_AS_TRANS); stream_putw(s, send_holdtime); /* Hold Time */ stream_put_in_addr(s, &peer->local_id); /* BGP Identifier */ /* Set capability code. */ bgp_open_capability(s, peer); /* Set BGP packet length. */ (void)bgp_packet_set_size(s); if (bgp_debug_neighbor_events(peer)) zlog_debug( "%s sending OPEN, version %d, my as %u, holdtime %d, id %s", peer->host, BGP_VERSION_4, local_as, send_holdtime, inet_ntoa(peer->local_id)); /* Dump packet if debug option is set. */ /* bgp_packet_dump (s); */ hook_call(bgp_packet_send, peer, BGP_MSG_OPEN, stream_get_endp(s), s); /* Add packet to the peer. */ bgp_packet_add(peer, s); bgp_writes_on(peer); } /* * Writes NOTIFICATION message directly to a peer socket without waiting for * the I/O thread. * * There must be exactly one stream on the peer->obuf FIFO, and the data within * this stream must match the format of a BGP NOTIFICATION message. * Transmission is best-effort. * * @requires peer->io_mtx * @param peer * @return 0 */ static int bgp_write_notify(struct peer *peer) { int ret, val; uint8_t type; struct stream *s; /* There should be at least one packet. */ s = stream_fifo_pop(peer->obuf); if (!s) return 0; assert(stream_get_endp(s) >= BGP_HEADER_SIZE); /* Stop collecting data within the socket */ sockopt_cork(peer->fd, 0); /* * socket is in nonblocking mode, if we can't deliver the NOTIFY, well, * we only care about getting a clean shutdown at this point. */ ret = write(peer->fd, STREAM_DATA(s), stream_get_endp(s)); /* * only connection reset/close gets counted as TCP_fatal_error, failure * to write the entire NOTIFY doesn't get different FSM treatment */ if (ret <= 0) { stream_free(s); BGP_EVENT_ADD(peer, TCP_fatal_error); return 0; } /* Disable Nagle, make NOTIFY packet go out right away */ val = 1; (void)setsockopt(peer->fd, IPPROTO_TCP, TCP_NODELAY, (char *)&val, sizeof(val)); /* Retrieve BGP packet type. */ stream_set_getp(s, BGP_MARKER_SIZE + 2); type = stream_getc(s); assert(type == BGP_MSG_NOTIFY); /* Type should be notify. */ atomic_fetch_add_explicit(&peer->notify_out, 1, memory_order_relaxed); peer->notify_out++; /* Double start timer. */ peer->v_start *= 2; /* Overflow check. */ if (peer->v_start >= (60 * 2)) peer->v_start = (60 * 2); /* * Handle Graceful Restart case where the state changes to * Connect instead of Idle */ BGP_EVENT_ADD(peer, BGP_Stop); stream_free(s); return 0; } /* * Creates a BGP Notify and appends it to the peer's output queue. * * This function attempts to write the packet from the thread it is called * from, to ensure the packet gets out ASAP. * * This function may be called from multiple threads. Since the function * modifies I/O buffer(s) in the peer, these are locked for the duration of the * call to prevent tampering from other threads. * * Delivery of the NOTIFICATION is attempted once and is best-effort. After * return, the peer structure *must* be reset; no assumptions about session * state are valid. * * @param peer * @param code BGP error code * @param sub_code BGP error subcode * @param data Data portion * @param datalen length of data portion */ void bgp_notify_send_with_data(struct peer *peer, uint8_t code, uint8_t sub_code, uint8_t *data, size_t datalen) { struct stream *s; /* Lock I/O mutex to prevent other threads from pushing packets */ frr_mutex_lock_autounlock(&peer->io_mtx); /* ============================================== */ /* Allocate new stream. */ s = stream_new(BGP_MAX_PACKET_SIZE); /* Make notify packet. */ bgp_packet_set_marker(s, BGP_MSG_NOTIFY); /* Set notify packet values. */ stream_putc(s, code); /* BGP notify code */ stream_putc(s, sub_code); /* BGP notify sub_code */ /* If notify data is present. */ if (data) stream_write(s, data, datalen); /* Set BGP packet length. */ bgp_packet_set_size(s); /* wipe output buffer */ stream_fifo_clean(peer->obuf); /* * If possible, store last packet for debugging purposes. This check is * in place because we are sometimes called with a doppelganger peer, * who tends to have a plethora of fields nulled out. */ if (peer->curr) { size_t packetsize = stream_get_endp(peer->curr); assert(packetsize <= sizeof(peer->last_reset_cause)); memcpy(peer->last_reset_cause, peer->curr->data, packetsize); peer->last_reset_cause_size = packetsize; } /* For debug */ { struct bgp_notify bgp_notify; int first = 0; int i; char c[4]; bgp_notify.code = code; bgp_notify.subcode = sub_code; bgp_notify.data = NULL; bgp_notify.length = datalen; bgp_notify.raw_data = data; peer->notify.code = bgp_notify.code; peer->notify.subcode = bgp_notify.subcode; if (bgp_notify.length && data) { bgp_notify.data = XMALLOC(MTYPE_TMP, bgp_notify.length * 3); for (i = 0; i < bgp_notify.length; i++) if (first) { snprintf(c, sizeof(c), " %02x", data[i]); strlcat(bgp_notify.data, c, bgp_notify.length); } else { first = 1; snprintf(c, sizeof(c), "%02x", data[i]); strlcpy(bgp_notify.data, c, bgp_notify.length); } } bgp_notify_print(peer, &bgp_notify, "sending"); if (bgp_notify.data) { XFREE(MTYPE_TMP, bgp_notify.data); bgp_notify.data = NULL; bgp_notify.length = 0; } } /* peer reset cause */ if (code == BGP_NOTIFY_CEASE) { if (sub_code == BGP_NOTIFY_CEASE_ADMIN_RESET) peer->last_reset = PEER_DOWN_USER_RESET; else if (sub_code == BGP_NOTIFY_CEASE_ADMIN_SHUTDOWN) peer->last_reset = PEER_DOWN_USER_SHUTDOWN; else peer->last_reset = PEER_DOWN_NOTIFY_SEND; } else peer->last_reset = PEER_DOWN_NOTIFY_SEND; /* Add packet to peer's output queue */ stream_fifo_push(peer->obuf, s); bgp_write_notify(peer); } /* * Creates a BGP Notify and appends it to the peer's output queue. * * This function attempts to write the packet from the thread it is called * from, to ensure the packet gets out ASAP. * * @param peer * @param code BGP error code * @param sub_code BGP error subcode */ void bgp_notify_send(struct peer *peer, uint8_t code, uint8_t sub_code) { bgp_notify_send_with_data(peer, code, sub_code, NULL, 0); } /* * Creates BGP Route Refresh packet and appends it to the peer's output queue. * * @param peer * @param afi Address Family Identifier * @param safi Subsequent Address Family Identifier * @param orf_type Outbound Route Filtering type * @param when_to_refresh Whether to refresh immediately or defer * @param remove Whether to remove ORF for specified AFI/SAFI */ void bgp_route_refresh_send(struct peer *peer, afi_t afi, safi_t safi, uint8_t orf_type, uint8_t when_to_refresh, int remove) { struct stream *s; struct bgp_filter *filter; int orf_refresh = 0; iana_afi_t pkt_afi; iana_safi_t pkt_safi; if (DISABLE_BGP_ANNOUNCE) return; filter = &peer->filter[afi][safi]; /* Convert AFI, SAFI to values for packet. */ bgp_map_afi_safi_int2iana(afi, safi, &pkt_afi, &pkt_safi); s = stream_new(BGP_MAX_PACKET_SIZE); /* Make BGP update packet. */ if (CHECK_FLAG(peer->cap, PEER_CAP_REFRESH_NEW_RCV)) bgp_packet_set_marker(s, BGP_MSG_ROUTE_REFRESH_NEW); else bgp_packet_set_marker(s, BGP_MSG_ROUTE_REFRESH_OLD); /* Encode Route Refresh message. */ stream_putw(s, pkt_afi); stream_putc(s, 0); stream_putc(s, pkt_safi); if (orf_type == ORF_TYPE_PREFIX || orf_type == ORF_TYPE_PREFIX_OLD) if (remove || filter->plist[FILTER_IN].plist) { uint16_t orf_len; unsigned long orfp; orf_refresh = 1; stream_putc(s, when_to_refresh); stream_putc(s, orf_type); orfp = stream_get_endp(s); stream_putw(s, 0); if (remove) { UNSET_FLAG(peer->af_sflags[afi][safi], PEER_STATUS_ORF_PREFIX_SEND); stream_putc(s, ORF_COMMON_PART_REMOVE_ALL); if (bgp_debug_neighbor_events(peer)) zlog_debug( "%s sending REFRESH_REQ to remove ORF(%d) (%s) for afi/safi: %s/%s", peer->host, orf_type, (when_to_refresh == REFRESH_DEFER ? "defer" : "immediate"), iana_afi2str(pkt_afi), iana_safi2str(pkt_safi)); } else { SET_FLAG(peer->af_sflags[afi][safi], PEER_STATUS_ORF_PREFIX_SEND); prefix_bgp_orf_entry( s, filter->plist[FILTER_IN].plist, ORF_COMMON_PART_ADD, ORF_COMMON_PART_PERMIT, ORF_COMMON_PART_DENY); if (bgp_debug_neighbor_events(peer)) zlog_debug( "%s sending REFRESH_REQ with pfxlist ORF(%d) (%s) for afi/safi: %s/%s", peer->host, orf_type, (when_to_refresh == REFRESH_DEFER ? "defer" : "immediate"), iana_afi2str(pkt_afi), iana_safi2str(pkt_safi)); } /* Total ORF Entry Len. */ orf_len = stream_get_endp(s) - orfp - 2; stream_putw_at(s, orfp, orf_len); } /* Set packet size. */ (void)bgp_packet_set_size(s); if (bgp_debug_neighbor_events(peer)) { if (!orf_refresh) zlog_debug("%s sending REFRESH_REQ for afi/safi: %s/%s", peer->host, iana_afi2str(pkt_afi), iana_safi2str(pkt_safi)); } /* Add packet to the peer. */ bgp_packet_add(peer, s); bgp_writes_on(peer); } /* * Create a BGP Capability packet and append it to the peer's output queue. * * @param peer * @param afi Address Family Identifier * @param safi Subsequent Address Family Identifier * @param capability_code BGP Capability Code * @param action Set or Remove capability */ void bgp_capability_send(struct peer *peer, afi_t afi, safi_t safi, int capability_code, int action) { struct stream *s; iana_afi_t pkt_afi; iana_safi_t pkt_safi; /* Convert AFI, SAFI to values for packet. */ bgp_map_afi_safi_int2iana(afi, safi, &pkt_afi, &pkt_safi); s = stream_new(BGP_MAX_PACKET_SIZE); /* Make BGP update packet. */ bgp_packet_set_marker(s, BGP_MSG_CAPABILITY); /* Encode MP_EXT capability. */ if (capability_code == CAPABILITY_CODE_MP) { stream_putc(s, action); stream_putc(s, CAPABILITY_CODE_MP); stream_putc(s, CAPABILITY_CODE_MP_LEN); stream_putw(s, pkt_afi); stream_putc(s, 0); stream_putc(s, pkt_safi); if (bgp_debug_neighbor_events(peer)) zlog_debug( "%s sending CAPABILITY has %s MP_EXT CAP for afi/safi: %s/%s", peer->host, action == CAPABILITY_ACTION_SET ? "Advertising" : "Removing", iana_afi2str(pkt_afi), iana_safi2str(pkt_safi)); } /* Set packet size. */ (void)bgp_packet_set_size(s); /* Add packet to the peer. */ bgp_packet_add(peer, s); bgp_writes_on(peer); } /* RFC1771 6.8 Connection collision detection. */ static int bgp_collision_detect(struct peer *new, struct in_addr remote_id) { struct peer *peer; /* Upon receipt of an OPEN message, the local system must examine all of its connections that are in the OpenConfirm state. A BGP speaker may also examine connections in an OpenSent state if it knows the BGP Identifier of the peer by means outside of the protocol. If among these connections there is a connection to a remote BGP speaker whose BGP Identifier equals the one in the OPEN message, then the local system performs the following collision resolution procedure: */ if ((peer = new->doppelganger) != NULL) { /* Do not accept the new connection in Established or Clearing * states. * Note that a peer GR is handled by closing the existing * connection * upon receipt of new one. */ if (peer->status == Established || peer->status == Clearing) { bgp_notify_send(new, BGP_NOTIFY_CEASE, BGP_NOTIFY_CEASE_COLLISION_RESOLUTION); return (-1); } else if ((peer->status == OpenConfirm) || (peer->status == OpenSent)) { /* 1. The BGP Identifier of the local system is compared to the BGP Identifier of the remote system (as specified in the OPEN message). */ if (ntohl(peer->local_id.s_addr) < ntohl(remote_id.s_addr)) if (!CHECK_FLAG(peer->sflags, PEER_STATUS_ACCEPT_PEER)) { /* 2. If the value of the local BGP Identifier is less than the remote one, the local system closes BGP connection that already exists (the one that is already in the OpenConfirm state), and accepts BGP connection initiated by the remote system. */ bgp_notify_send( peer, BGP_NOTIFY_CEASE, BGP_NOTIFY_CEASE_COLLISION_RESOLUTION); return 1; } else { bgp_notify_send( new, BGP_NOTIFY_CEASE, BGP_NOTIFY_CEASE_COLLISION_RESOLUTION); return -1; } else { /* 3. Otherwise, the local system closes newly created BGP connection (the one associated with the newly received OPEN message), and continues to use the existing one (the one that is already in the OpenConfirm state). */ if (CHECK_FLAG(peer->sflags, PEER_STATUS_ACCEPT_PEER)) { bgp_notify_send( peer, BGP_NOTIFY_CEASE, BGP_NOTIFY_CEASE_COLLISION_RESOLUTION); return 1; } else { bgp_notify_send( new, BGP_NOTIFY_CEASE, BGP_NOTIFY_CEASE_COLLISION_RESOLUTION); return -1; } } } } return 0; } /* Packet processing routines ---------------------------------------------- */ /* * This is a family of functions designed to be called from * bgp_process_packet(). These functions all share similar behavior and should * adhere to the following invariants and restrictions: * * Return codes * ------------ * The return code of any one of those functions should be one of the FSM event * codes specified in bgpd.h. If a NOTIFY was sent, this event code MUST be * BGP_Stop. Otherwise, the code SHOULD correspond to the function's expected * packet type. For example, bgp_open_receive() should return BGP_Stop upon * error and Receive_OPEN_message otherwise. * * If no action is necessary, the correct return code is BGP_PACKET_NOOP as * defined below. * * Side effects * ------------ * - May send NOTIFY messages * - May not modify peer->status * - May not call bgp_event_update() */ #define BGP_PACKET_NOOP 0 /** * Process BGP OPEN message for peer. * * If any errors are encountered in the OPEN message, immediately sends NOTIFY * and returns BGP_Stop. * * @param peer * @param size size of the packet * @return as in summary */ static int bgp_open_receive(struct peer *peer, bgp_size_t size) { int ret; uint8_t version; uint8_t optlen; uint16_t holdtime; uint16_t send_holdtime; as_t remote_as; as_t as4 = 0, as4_be; struct in_addr remote_id; int mp_capability; uint8_t notify_data_remote_as[2]; uint8_t notify_data_remote_as4[4]; uint8_t notify_data_remote_id[4]; uint16_t *holdtime_ptr; /* Parse open packet. */ version = stream_getc(peer->curr); memcpy(notify_data_remote_as, stream_pnt(peer->curr), 2); remote_as = stream_getw(peer->curr); holdtime_ptr = (uint16_t *)stream_pnt(peer->curr); holdtime = stream_getw(peer->curr); memcpy(notify_data_remote_id, stream_pnt(peer->curr), 4); remote_id.s_addr = stream_get_ipv4(peer->curr); /* Receive OPEN message log */ if (bgp_debug_neighbor_events(peer)) zlog_debug( "%s rcv OPEN, version %d, remote-as (in open) %u," " holdtime %d, id %s", peer->host, version, remote_as, holdtime, inet_ntoa(remote_id)); /* BEGIN to read the capability here, but dont do it yet */ mp_capability = 0; optlen = stream_getc(peer->curr); if (optlen != 0) { /* If not enough bytes, it is an error. */ if (STREAM_READABLE(peer->curr) < optlen) { bgp_notify_send(peer, BGP_NOTIFY_OPEN_ERR, BGP_NOTIFY_OPEN_MALFORMED_ATTR); return BGP_Stop; } /* We need the as4 capability value *right now* because * if it is there, we have not got the remote_as yet, and * without * that we do not know which peer is connecting to us now. */ as4 = peek_for_as4_capability(peer, optlen); } as4_be = htonl(as4); memcpy(notify_data_remote_as4, &as4_be, 4); /* Just in case we have a silly peer who sends AS4 capability set to 0 */ if (CHECK_FLAG(peer->cap, PEER_CAP_AS4_RCV) && !as4) { flog_err(EC_BGP_PKT_OPEN, "%s bad OPEN, got AS4 capability, but AS4 set to 0", peer->host); bgp_notify_send_with_data(peer, BGP_NOTIFY_OPEN_ERR, BGP_NOTIFY_OPEN_BAD_PEER_AS, notify_data_remote_as4, 4); return BGP_Stop; } if (remote_as == BGP_AS_TRANS) { /* Take the AS4 from the capability. We must have received the * capability now! Otherwise we have a asn16 peer who uses * BGP_AS_TRANS, for some unknown reason. */ if (as4 == BGP_AS_TRANS) { flog_err( EC_BGP_PKT_OPEN, "%s [AS4] NEW speaker using AS_TRANS for AS4, not allowed", peer->host); bgp_notify_send_with_data(peer, BGP_NOTIFY_OPEN_ERR, BGP_NOTIFY_OPEN_BAD_PEER_AS, notify_data_remote_as4, 4); return BGP_Stop; } if (!as4 && BGP_DEBUG(as4, AS4)) zlog_debug( "%s [AS4] OPEN remote_as is AS_TRANS, but no AS4." " Odd, but proceeding.", peer->host); else if (as4 < BGP_AS_MAX && BGP_DEBUG(as4, AS4)) zlog_debug( "%s [AS4] OPEN remote_as is AS_TRANS, but AS4 (%u) fits " "in 2-bytes, very odd peer.", peer->host, as4); if (as4) remote_as = as4; } else { /* We may have a partner with AS4 who has an asno < BGP_AS_MAX */ /* If we have got the capability, peer->as4cap must match * remote_as */ if (CHECK_FLAG(peer->cap, PEER_CAP_AS4_RCV) && as4 != remote_as) { /* raise error, log this, close session */ flog_err( EC_BGP_PKT_OPEN, "%s bad OPEN, got AS4 capability, but remote_as %u" " mismatch with 16bit 'myasn' %u in open", peer->host, as4, remote_as); bgp_notify_send_with_data(peer, BGP_NOTIFY_OPEN_ERR, BGP_NOTIFY_OPEN_BAD_PEER_AS, notify_data_remote_as4, 4); return BGP_Stop; } } /* remote router-id check. */ if (remote_id.s_addr == 0 || IPV4_CLASS_DE(ntohl(remote_id.s_addr)) || ntohl(peer->local_id.s_addr) == ntohl(remote_id.s_addr)) { if (bgp_debug_neighbor_events(peer)) zlog_debug("%s bad OPEN, wrong router identifier %s", peer->host, inet_ntoa(remote_id)); bgp_notify_send_with_data(peer, BGP_NOTIFY_OPEN_ERR, BGP_NOTIFY_OPEN_BAD_BGP_IDENT, notify_data_remote_id, 4); return BGP_Stop; } /* Set remote router-id */ peer->remote_id = remote_id; /* Peer BGP version check. */ if (version != BGP_VERSION_4) { uint16_t maxver = htons(BGP_VERSION_4); /* XXX this reply may not be correct if version < 4 XXX */ if (bgp_debug_neighbor_events(peer)) zlog_debug( "%s bad protocol version, remote requested %d, local request %d", peer->host, version, BGP_VERSION_4); /* Data must be in network byte order here */ bgp_notify_send_with_data(peer, BGP_NOTIFY_OPEN_ERR, BGP_NOTIFY_OPEN_UNSUP_VERSION, (uint8_t *)&maxver, 2); return BGP_Stop; } /* Check neighbor as number. */ if (peer->as_type == AS_UNSPECIFIED) { if (bgp_debug_neighbor_events(peer)) zlog_debug( "%s bad OPEN, remote AS is unspecified currently", peer->host); bgp_notify_send_with_data(peer, BGP_NOTIFY_OPEN_ERR, BGP_NOTIFY_OPEN_BAD_PEER_AS, notify_data_remote_as, 2); return BGP_Stop; } else if (peer->as_type == AS_INTERNAL) { if (remote_as != peer->bgp->as) { if (bgp_debug_neighbor_events(peer)) zlog_debug( "%s bad OPEN, remote AS is %u, internal specified", peer->host, remote_as); bgp_notify_send_with_data(peer, BGP_NOTIFY_OPEN_ERR, BGP_NOTIFY_OPEN_BAD_PEER_AS, notify_data_remote_as, 2); return BGP_Stop; } peer->as = peer->local_as; } else if (peer->as_type == AS_EXTERNAL) { if (remote_as == peer->bgp->as) { if (bgp_debug_neighbor_events(peer)) zlog_debug( "%s bad OPEN, remote AS is %u, external specified", peer->host, remote_as); bgp_notify_send_with_data(peer, BGP_NOTIFY_OPEN_ERR, BGP_NOTIFY_OPEN_BAD_PEER_AS, notify_data_remote_as, 2); return BGP_Stop; } peer->as = remote_as; } else if ((peer->as_type == AS_SPECIFIED) && (remote_as != peer->as)) { if (bgp_debug_neighbor_events(peer)) zlog_debug("%s bad OPEN, remote AS is %u, expected %u", peer->host, remote_as, peer->as); bgp_notify_send_with_data(peer, BGP_NOTIFY_OPEN_ERR, BGP_NOTIFY_OPEN_BAD_PEER_AS, notify_data_remote_as, 2); return BGP_Stop; } /* From the rfc: Upon receipt of an OPEN message, a BGP speaker MUST calculate the value of the Hold Timer by using the smaller of its configured Hold Time and the Hold Time received in the OPEN message. The Hold Time MUST be either zero or at least three seconds. An implementation may reject connections on the basis of the Hold Time. */ if (holdtime < 3 && holdtime != 0) { bgp_notify_send_with_data(peer, BGP_NOTIFY_OPEN_ERR, BGP_NOTIFY_OPEN_UNACEP_HOLDTIME, (uint8_t *)holdtime_ptr, 2); return BGP_Stop; } /* From the rfc: A reasonable maximum time between KEEPALIVE messages would be one third of the Hold Time interval. KEEPALIVE messages MUST NOT be sent more frequently than one per second. An implementation MAY adjust the rate at which it sends KEEPALIVE messages as a function of the Hold Time interval. */ if (CHECK_FLAG(peer->flags, PEER_FLAG_TIMER)) send_holdtime = peer->holdtime; else send_holdtime = peer->bgp->default_holdtime; if (holdtime < send_holdtime) peer->v_holdtime = holdtime; else peer->v_holdtime = send_holdtime; if ((CHECK_FLAG(peer->flags, PEER_FLAG_TIMER)) && (peer->keepalive < peer->v_holdtime / 3)) peer->v_keepalive = peer->keepalive; else peer->v_keepalive = peer->v_holdtime / 3; /* Open option part parse. */ if (optlen != 0) { if ((ret = bgp_open_option_parse(peer, optlen, &mp_capability)) < 0) return BGP_Stop; } else { if (bgp_debug_neighbor_events(peer)) zlog_debug("%s rcvd OPEN w/ OPTION parameter len: 0", peer->host); } /* * Assume that the peer supports the locally configured set of * AFI/SAFIs if the peer did not send us any Mulitiprotocol * capabilities, or if 'override-capability' is configured. */ if (!mp_capability || CHECK_FLAG(peer->flags, PEER_FLAG_OVERRIDE_CAPABILITY)) { peer->afc_nego[AFI_IP][SAFI_UNICAST] = peer->afc[AFI_IP][SAFI_UNICAST]; peer->afc_nego[AFI_IP][SAFI_MULTICAST] = peer->afc[AFI_IP][SAFI_MULTICAST]; peer->afc_nego[AFI_IP][SAFI_LABELED_UNICAST] = peer->afc[AFI_IP][SAFI_LABELED_UNICAST]; peer->afc_nego[AFI_IP][SAFI_FLOWSPEC] = peer->afc[AFI_IP][SAFI_FLOWSPEC]; peer->afc_nego[AFI_IP6][SAFI_UNICAST] = peer->afc[AFI_IP6][SAFI_UNICAST]; peer->afc_nego[AFI_IP6][SAFI_MULTICAST] = peer->afc[AFI_IP6][SAFI_MULTICAST]; peer->afc_nego[AFI_IP6][SAFI_LABELED_UNICAST] = peer->afc[AFI_IP6][SAFI_LABELED_UNICAST]; peer->afc_nego[AFI_L2VPN][SAFI_EVPN] = peer->afc[AFI_L2VPN][SAFI_EVPN]; peer->afc_nego[AFI_IP6][SAFI_FLOWSPEC] = peer->afc[AFI_IP6][SAFI_FLOWSPEC]; } /* When collision is detected and this peer is closed. Retrun immidiately. */ ret = bgp_collision_detect(peer, remote_id); if (ret < 0) return BGP_Stop; /* Get sockname. */ if ((ret = bgp_getsockname(peer)) < 0) { flog_err_sys(EC_LIB_SOCKET, "%s: bgp_getsockname() failed for peer: %s", __FUNCTION__, peer->host); return BGP_Stop; } /* Verify valid local address present based on negotiated * address-families. */ if (peer->afc_nego[AFI_IP][SAFI_UNICAST] || peer->afc_nego[AFI_IP][SAFI_LABELED_UNICAST] || peer->afc_nego[AFI_IP][SAFI_MULTICAST] || peer->afc_nego[AFI_IP][SAFI_MPLS_VPN] || peer->afc_nego[AFI_IP][SAFI_ENCAP]) { if (!peer->nexthop.v4.s_addr) { #if defined(HAVE_CUMULUS) flog_err( EC_BGP_SND_FAIL, "%s: No local IPv4 addr resetting connection, fd %d", peer->host, peer->fd); bgp_notify_send(peer, BGP_NOTIFY_CEASE, BGP_NOTIFY_SUBCODE_UNSPECIFIC); return BGP_Stop; #endif } } if (peer->afc_nego[AFI_IP6][SAFI_UNICAST] || peer->afc_nego[AFI_IP6][SAFI_LABELED_UNICAST] || peer->afc_nego[AFI_IP6][SAFI_MULTICAST] || peer->afc_nego[AFI_IP6][SAFI_MPLS_VPN] || peer->afc_nego[AFI_IP6][SAFI_ENCAP]) { if (IN6_IS_ADDR_UNSPECIFIED(&peer->nexthop.v6_global)) { #if defined(HAVE_CUMULUS) flog_err( EC_BGP_SND_FAIL, "%s: No local IPv6 addr resetting connection, fd %d", peer->host, peer->fd); bgp_notify_send(peer, BGP_NOTIFY_CEASE, BGP_NOTIFY_SUBCODE_UNSPECIFIC); return BGP_Stop; #endif } } peer->rtt = sockopt_tcp_rtt(peer->fd); return Receive_OPEN_message; } /** * Process BGP KEEPALIVE message for peer. * * @param peer * @param size size of the packet * @return as in summary */ static int bgp_keepalive_receive(struct peer *peer, bgp_size_t size) { if (bgp_debug_keepalive(peer)) zlog_debug("%s KEEPALIVE rcvd", peer->host); bgp_update_implicit_eors(peer); return Receive_KEEPALIVE_message; } /** * Process BGP UPDATE message for peer. * * Parses UPDATE and creates attribute object. * * @param peer * @param size size of the packet * @return as in summary */ static int bgp_update_receive(struct peer *peer, bgp_size_t size) { int ret, nlri_ret; uint8_t *end; struct stream *s; struct attr attr; bgp_size_t attribute_len; bgp_size_t update_len; bgp_size_t withdraw_len; enum NLRI_TYPES { NLRI_UPDATE, NLRI_WITHDRAW, NLRI_MP_UPDATE, NLRI_MP_WITHDRAW, NLRI_TYPE_MAX }; struct bgp_nlri nlris[NLRI_TYPE_MAX]; /* Status must be Established. */ if (peer->status != Established) { flog_err(EC_BGP_INVALID_STATUS, "%s [FSM] Update packet received under status %s", peer->host, lookup_msg(bgp_status_msg, peer->status, NULL)); bgp_notify_send(peer, BGP_NOTIFY_FSM_ERR, 0); return BGP_Stop; } /* Set initial values. */ memset(&attr, 0, sizeof(struct attr)); attr.label_index = BGP_INVALID_LABEL_INDEX; attr.label = MPLS_INVALID_LABEL; memset(&nlris, 0, sizeof(nlris)); memset(peer->rcvd_attr_str, 0, BUFSIZ); peer->rcvd_attr_printed = 0; s = peer->curr; end = stream_pnt(s) + size; /* RFC1771 6.3 If the Unfeasible Routes Length or Total Attribute Length is too large (i.e., if Unfeasible Routes Length + Total Attribute Length + 23 exceeds the message Length), then the Error Subcode is set to Malformed Attribute List. */ if (stream_pnt(s) + 2 > end) { flog_err(EC_BGP_UPDATE_RCV, "%s [Error] Update packet error" " (packet length is short for unfeasible length)", peer->host); bgp_notify_send(peer, BGP_NOTIFY_UPDATE_ERR, BGP_NOTIFY_UPDATE_MAL_ATTR); return BGP_Stop; } /* Unfeasible Route Length. */ withdraw_len = stream_getw(s); /* Unfeasible Route Length check. */ if (stream_pnt(s) + withdraw_len > end) { flog_err(EC_BGP_UPDATE_RCV, "%s [Error] Update packet error" " (packet unfeasible length overflow %d)", peer->host, withdraw_len); bgp_notify_send(peer, BGP_NOTIFY_UPDATE_ERR, BGP_NOTIFY_UPDATE_MAL_ATTR); return BGP_Stop; } /* Unfeasible Route packet format check. */ if (withdraw_len > 0) { nlris[NLRI_WITHDRAW].afi = AFI_IP; nlris[NLRI_WITHDRAW].safi = SAFI_UNICAST; nlris[NLRI_WITHDRAW].nlri = stream_pnt(s); nlris[NLRI_WITHDRAW].length = withdraw_len; stream_forward_getp(s, withdraw_len); } /* Attribute total length check. */ if (stream_pnt(s) + 2 > end) { flog_warn( EC_BGP_UPDATE_PACKET_SHORT, "%s [Error] Packet Error (update packet is short for attribute length)", peer->host); bgp_notify_send(peer, BGP_NOTIFY_UPDATE_ERR, BGP_NOTIFY_UPDATE_MAL_ATTR); return BGP_Stop; } /* Fetch attribute total length. */ attribute_len = stream_getw(s); /* Attribute length check. */ if (stream_pnt(s) + attribute_len > end) { flog_warn( EC_BGP_UPDATE_PACKET_LONG, "%s [Error] Packet Error (update packet attribute length overflow %d)", peer->host, attribute_len); bgp_notify_send(peer, BGP_NOTIFY_UPDATE_ERR, BGP_NOTIFY_UPDATE_MAL_ATTR); return BGP_Stop; } /* Certain attribute parsing errors should not be considered bad enough * to reset the session for, most particularly any partial/optional * attributes that have 'tunneled' over speakers that don't understand * them. Instead we withdraw only the prefix concerned. * * Complicates the flow a little though.. */ bgp_attr_parse_ret_t attr_parse_ret = BGP_ATTR_PARSE_PROCEED; /* This define morphs the update case into a withdraw when lower levels * have signalled an error condition where this is best. */ #define NLRI_ATTR_ARG (attr_parse_ret != BGP_ATTR_PARSE_WITHDRAW ? &attr : NULL) /* Parse attribute when it exists. */ if (attribute_len) { attr_parse_ret = bgp_attr_parse(peer, &attr, attribute_len, &nlris[NLRI_MP_UPDATE], &nlris[NLRI_MP_WITHDRAW]); if (attr_parse_ret == BGP_ATTR_PARSE_ERROR) { bgp_attr_unintern_sub(&attr); return BGP_Stop; } } /* Logging the attribute. */ if (attr_parse_ret == BGP_ATTR_PARSE_WITHDRAW || BGP_DEBUG(update, UPDATE_IN) || BGP_DEBUG(update, UPDATE_PREFIX)) { ret = bgp_dump_attr(&attr, peer->rcvd_attr_str, BUFSIZ); peer->stat_upd_7606++; if (attr_parse_ret == BGP_ATTR_PARSE_WITHDRAW) flog_err( EC_BGP_UPDATE_RCV, "%s rcvd UPDATE with errors in attr(s)!! Withdrawing route.", peer->host); if (ret && bgp_debug_update(peer, NULL, NULL, 1)) { zlog_debug("%s rcvd UPDATE w/ attr: %s", peer->host, peer->rcvd_attr_str); peer->rcvd_attr_printed = 1; } } /* Network Layer Reachability Information. */ update_len = end - stream_pnt(s); if (update_len) { /* Set NLRI portion to structure. */ nlris[NLRI_UPDATE].afi = AFI_IP; nlris[NLRI_UPDATE].safi = SAFI_UNICAST; nlris[NLRI_UPDATE].nlri = stream_pnt(s); nlris[NLRI_UPDATE].length = update_len; stream_forward_getp(s, update_len); if (CHECK_FLAG(attr.flag, ATTR_FLAG_BIT(BGP_ATTR_MP_REACH_NLRI))) { /* * We skipped nexthop attribute validation earlier so * validate the nexthop now. */ if (bgp_attr_nexthop_valid(peer, &attr) < 0) { bgp_attr_unintern_sub(&attr); return BGP_Stop; } } } if (BGP_DEBUG(update, UPDATE_IN)) zlog_debug("%s rcvd UPDATE wlen %d attrlen %d alen %d", peer->host, withdraw_len, attribute_len, update_len); /* Parse any given NLRIs */ for (int i = NLRI_UPDATE; i < NLRI_TYPE_MAX; i++) { if (!nlris[i].nlri) continue; /* NLRI is processed iff the peer if configured for the specific * afi/safi */ if (!peer->afc[nlris[i].afi][nlris[i].safi]) { zlog_info( "%s [Info] UPDATE for non-enabled AFI/SAFI %u/%u", peer->host, nlris[i].afi, nlris[i].safi); continue; } /* EoR handled later */ if (nlris[i].length == 0) continue; switch (i) { case NLRI_UPDATE: case NLRI_MP_UPDATE: nlri_ret = bgp_nlri_parse(peer, NLRI_ATTR_ARG, &nlris[i], 0); break; case NLRI_WITHDRAW: case NLRI_MP_WITHDRAW: nlri_ret = bgp_nlri_parse(peer, &attr, &nlris[i], 1); break; default: nlri_ret = BGP_NLRI_PARSE_ERROR; } if (nlri_ret < BGP_NLRI_PARSE_OK && nlri_ret != BGP_NLRI_PARSE_ERROR_PREFIX_OVERFLOW) { flog_err(EC_BGP_UPDATE_RCV, "%s [Error] Error parsing NLRI", peer->host); if (peer->status == Established) bgp_notify_send( peer, BGP_NOTIFY_UPDATE_ERR, i <= NLRI_WITHDRAW ? BGP_NOTIFY_UPDATE_INVAL_NETWORK : BGP_NOTIFY_UPDATE_OPT_ATTR_ERR); bgp_attr_unintern_sub(&attr); return BGP_Stop; } } /* EoR checks * * Non-MP IPv4/Unicast EoR is a completely empty UPDATE * and MP EoR should have only an empty MP_UNREACH */ if ((!update_len && !withdraw_len && nlris[NLRI_MP_UPDATE].length == 0) || (attr_parse_ret == BGP_ATTR_PARSE_EOR)) { afi_t afi = 0; safi_t safi; /* Non-MP IPv4/Unicast is a completely emtpy UPDATE - already * checked * update and withdraw NLRI lengths are 0. */ if (!attribute_len) { afi = AFI_IP; safi = SAFI_UNICAST; } else if (attr.flag & ATTR_FLAG_BIT(BGP_ATTR_MP_UNREACH_NLRI) && nlris[NLRI_MP_WITHDRAW].length == 0) { afi = nlris[NLRI_MP_WITHDRAW].afi; safi = nlris[NLRI_MP_WITHDRAW].safi; } else if (attr_parse_ret == BGP_ATTR_PARSE_EOR) { afi = nlris[NLRI_MP_UPDATE].afi; safi = nlris[NLRI_MP_UPDATE].safi; } if (afi && peer->afc[afi][safi]) { struct vrf *vrf = vrf_lookup_by_id(peer->bgp->vrf_id); /* End-of-RIB received */ if (!CHECK_FLAG(peer->af_sflags[afi][safi], PEER_STATUS_EOR_RECEIVED)) { SET_FLAG(peer->af_sflags[afi][safi], PEER_STATUS_EOR_RECEIVED); bgp_update_explicit_eors(peer); } /* NSF delete stale route */ if (peer->nsf[afi][safi]) bgp_clear_stale_route(peer, afi, safi); zlog_info("%%NOTIFICATION: rcvd End-of-RIB for %s from %s in vrf %s", get_afi_safi_str(afi, safi, false), peer->host, vrf ? vrf->name : VRF_DEFAULT_NAME); } } /* Everything is done. We unintern temporary structures which interned in bgp_attr_parse(). */ bgp_attr_unintern_sub(&attr); peer->update_time = bgp_clock(); /* Rearm holdtime timer */ BGP_TIMER_OFF(peer->t_holdtime); bgp_timer_set(peer); return Receive_UPDATE_message; } /** * Process BGP NOTIFY message for peer. * * @param peer * @param size size of the packet * @return as in summary */ static int bgp_notify_receive(struct peer *peer, bgp_size_t size) { struct bgp_notify bgp_notify; if (peer->notify.data) { XFREE(MTYPE_TMP, peer->notify.data); peer->notify.data = NULL; peer->notify.length = 0; } bgp_notify.code = stream_getc(peer->curr); bgp_notify.subcode = stream_getc(peer->curr); bgp_notify.length = size - 2; bgp_notify.data = NULL; /* Preserv notify code and sub code. */ peer->notify.code = bgp_notify.code; peer->notify.subcode = bgp_notify.subcode; /* For further diagnostic record returned Data. */ if (bgp_notify.length) { peer->notify.length = size - 2; peer->notify.data = XMALLOC(MTYPE_TMP, size - 2); memcpy(peer->notify.data, stream_pnt(peer->curr), size - 2); } /* For debug */ { int i; int first = 0; char c[4]; if (bgp_notify.length) { bgp_notify.data = XMALLOC(MTYPE_TMP, bgp_notify.length * 3); for (i = 0; i < bgp_notify.length; i++) if (first) { snprintf(c, sizeof(c), " %02x", stream_getc(peer->curr)); strlcat(bgp_notify.data, c, bgp_notify.length); } else { first = 1; snprintf(c, sizeof(c), "%02x", stream_getc(peer->curr)); strlcpy(bgp_notify.data, c, bgp_notify.length); } bgp_notify.raw_data = (uint8_t *)peer->notify.data; } bgp_notify_print(peer, &bgp_notify, "received"); if (bgp_notify.data) { XFREE(MTYPE_TMP, bgp_notify.data); bgp_notify.data = NULL; bgp_notify.length = 0; } } /* peer count update */ atomic_fetch_add_explicit(&peer->notify_in, 1, memory_order_relaxed); peer->last_reset = PEER_DOWN_NOTIFY_RECEIVED; /* We have to check for Notify with Unsupported Optional Parameter. in that case we fallback to open without the capability option. But this done in bgp_stop. We just mark it here to avoid changing the fsm tables. */ if (bgp_notify.code == BGP_NOTIFY_OPEN_ERR && bgp_notify.subcode == BGP_NOTIFY_OPEN_UNSUP_PARAM) UNSET_FLAG(peer->sflags, PEER_STATUS_CAPABILITY_OPEN); return Receive_NOTIFICATION_message; } /** * Process BGP ROUTEREFRESH message for peer. * * @param peer * @param size size of the packet * @return as in summary */ static int bgp_route_refresh_receive(struct peer *peer, bgp_size_t size) { iana_afi_t pkt_afi; afi_t afi; iana_safi_t pkt_safi; safi_t safi; struct stream *s; struct peer_af *paf; struct update_group *updgrp; struct peer *updgrp_peer; /* If peer does not have the capability, send notification. */ if (!CHECK_FLAG(peer->cap, PEER_CAP_REFRESH_ADV)) { flog_err(EC_BGP_NO_CAP, "%s [Error] BGP route refresh is not enabled", peer->host); bgp_notify_send(peer, BGP_NOTIFY_HEADER_ERR, BGP_NOTIFY_HEADER_BAD_MESTYPE); return BGP_Stop; } /* Status must be Established. */ if (peer->status != Established) { flog_err( EC_BGP_INVALID_STATUS, "%s [Error] Route refresh packet received under status %s", peer->host, lookup_msg(bgp_status_msg, peer->status, NULL)); bgp_notify_send(peer, BGP_NOTIFY_FSM_ERR, 0); return BGP_Stop; } s = peer->curr; /* Parse packet. */ pkt_afi = stream_getw(s); (void)stream_getc(s); pkt_safi = stream_getc(s); if (bgp_debug_update(peer, NULL, NULL, 0)) zlog_debug("%s rcvd REFRESH_REQ for afi/safi: %s/%s", peer->host, iana_afi2str(pkt_afi), iana_safi2str(pkt_safi)); /* Convert AFI, SAFI to internal values and check. */ if (bgp_map_afi_safi_iana2int(pkt_afi, pkt_safi, &afi, &safi)) { zlog_info( "%s REFRESH_REQ for unrecognized afi/safi: %s/%s - ignored", peer->host, iana_afi2str(pkt_afi), iana_safi2str(pkt_safi)); return BGP_PACKET_NOOP; } if (size != BGP_MSG_ROUTE_REFRESH_MIN_SIZE - BGP_HEADER_SIZE) { uint8_t *end; uint8_t when_to_refresh; uint8_t orf_type; uint16_t orf_len; if (size - (BGP_MSG_ROUTE_REFRESH_MIN_SIZE - BGP_HEADER_SIZE) < 5) { zlog_info("%s ORF route refresh length error", peer->host); bgp_notify_send(peer, BGP_NOTIFY_CEASE, 0); return BGP_Stop; } when_to_refresh = stream_getc(s); end = stream_pnt(s) + (size - 5); while ((stream_pnt(s) + 2) < end) { orf_type = stream_getc(s); orf_len = stream_getw(s); /* orf_len in bounds? */ if ((stream_pnt(s) + orf_len) > end) break; /* XXX: Notify instead?? */ if (orf_type == ORF_TYPE_PREFIX || orf_type == ORF_TYPE_PREFIX_OLD) { uint8_t *p_pnt = stream_pnt(s); uint8_t *p_end = stream_pnt(s) + orf_len; struct orf_prefix orfp; uint8_t common = 0; uint32_t seq; int psize; char name[BUFSIZ]; int ret = CMD_SUCCESS; if (bgp_debug_neighbor_events(peer)) { zlog_debug( "%s rcvd Prefixlist ORF(%d) length %d", peer->host, orf_type, orf_len); } /* we're going to read at least 1 byte of common * ORF header, * and 7 bytes of ORF Address-filter entry from * the stream */ if (orf_len < 7) break; /* ORF prefix-list name */ sprintf(name, "%s.%d.%d", peer->host, afi, safi); while (p_pnt < p_end) { /* If the ORF entry is malformed, want * to read as much of it * as possible without going beyond the * bounds of the entry, * to maximise debug information. */ int ok; memset(&orfp, 0, sizeof(struct orf_prefix)); common = *p_pnt++; /* after ++: p_pnt <= p_end */ if (common & ORF_COMMON_PART_REMOVE_ALL) { if (bgp_debug_neighbor_events( peer)) zlog_debug( "%s rcvd Remove-All pfxlist ORF request", peer->host); prefix_bgp_orf_remove_all(afi, name); break; } ok = ((uint32_t)(p_end - p_pnt) >= sizeof(uint32_t)); if (ok) { memcpy(&seq, p_pnt, sizeof(uint32_t)); p_pnt += sizeof(uint32_t); orfp.seq = ntohl(seq); } else p_pnt = p_end; if ((ok = (p_pnt < p_end))) orfp.ge = *p_pnt++; /* value checked in prefix_bgp_orf_set() */ if ((ok = (p_pnt < p_end))) orfp.le = *p_pnt++; /* value checked in prefix_bgp_orf_set() */ if ((ok = (p_pnt < p_end))) orfp.p.prefixlen = *p_pnt++; orfp.p.family = afi2family( afi); /* afi checked already */ psize = PSIZE( orfp.p.prefixlen); /* 0 if not ok */ if (psize > prefix_blen( &orfp.p)) /* valid for family ? */ { ok = 0; psize = prefix_blen(&orfp.p); } if (psize > (p_end - p_pnt)) /* valid for packet ? */ { ok = 0; psize = p_end - p_pnt; } if (psize > 0) memcpy(&orfp.p.u.prefix, p_pnt, psize); p_pnt += psize; if (bgp_debug_neighbor_events(peer)) { char buf[INET6_BUFSIZ]; zlog_debug( "%s rcvd %s %s seq %u %s/%d ge %d le %d%s", peer->host, (common & ORF_COMMON_PART_REMOVE ? "Remove" : "Add"), (common & ORF_COMMON_PART_DENY ? "deny" : "permit"), orfp.seq, inet_ntop( orfp.p.family, &orfp.p.u.prefix, buf, INET6_BUFSIZ), orfp.p.prefixlen, orfp.ge, orfp.le, ok ? "" : " MALFORMED"); } if (ok) ret = prefix_bgp_orf_set( name, afi, &orfp, (common & ORF_COMMON_PART_DENY ? 0 : 1), (common & ORF_COMMON_PART_REMOVE ? 0 : 1)); if (!ok || (ok && ret != CMD_SUCCESS)) { zlog_info( "%s Received misformatted prefixlist ORF." " Remove All pfxlist", peer->host); prefix_bgp_orf_remove_all(afi, name); break; } } peer->orf_plist[afi][safi] = prefix_bgp_orf_lookup(afi, name); } stream_forward_getp(s, orf_len); } if (bgp_debug_neighbor_events(peer)) zlog_debug("%s rcvd Refresh %s ORF request", peer->host, when_to_refresh == REFRESH_DEFER ? "Defer" : "Immediate"); if (when_to_refresh == REFRESH_DEFER) return BGP_PACKET_NOOP; } /* First update is deferred until ORF or ROUTE-REFRESH is received */ if (CHECK_FLAG(peer->af_sflags[afi][safi], PEER_STATUS_ORF_WAIT_REFRESH)) UNSET_FLAG(peer->af_sflags[afi][safi], PEER_STATUS_ORF_WAIT_REFRESH); paf = peer_af_find(peer, afi, safi); if (paf && paf->subgroup) { if (peer->orf_plist[afi][safi]) { updgrp = PAF_UPDGRP(paf); updgrp_peer = UPDGRP_PEER(updgrp); updgrp_peer->orf_plist[afi][safi] = peer->orf_plist[afi][safi]; } /* If the peer is configured for default-originate clear the * SUBGRP_STATUS_DEFAULT_ORIGINATE flag so that we will * re-advertise the * default */ if (CHECK_FLAG(paf->subgroup->sflags, SUBGRP_STATUS_DEFAULT_ORIGINATE)) UNSET_FLAG(paf->subgroup->sflags, SUBGRP_STATUS_DEFAULT_ORIGINATE); } /* Perform route refreshment to the peer */ bgp_announce_route(peer, afi, safi); /* No FSM action necessary */ return BGP_PACKET_NOOP; } /** * Parse BGP CAPABILITY message for peer. * * @param peer * @param size size of the packet * @return as in summary */ static int bgp_capability_msg_parse(struct peer *peer, uint8_t *pnt, bgp_size_t length) { uint8_t *end; struct capability_mp_data mpc; struct capability_header *hdr; uint8_t action; iana_afi_t pkt_afi; afi_t afi; iana_safi_t pkt_safi; safi_t safi; end = pnt + length; while (pnt < end) { /* We need at least action, capability code and capability * length. */ if (pnt + 3 > end) { zlog_info("%s Capability length error", peer->host); bgp_notify_send(peer, BGP_NOTIFY_CEASE, 0); return BGP_Stop; } action = *pnt; hdr = (struct capability_header *)(pnt + 1); /* Action value check. */ if (action != CAPABILITY_ACTION_SET && action != CAPABILITY_ACTION_UNSET) { zlog_info("%s Capability Action Value error %d", peer->host, action); bgp_notify_send(peer, BGP_NOTIFY_CEASE, 0); return BGP_Stop; } if (bgp_debug_neighbor_events(peer)) zlog_debug( "%s CAPABILITY has action: %d, code: %u, length %u", peer->host, action, hdr->code, hdr->length); /* Capability length check. */ if ((pnt + hdr->length + 3) > end) { zlog_info("%s Capability length error", peer->host); bgp_notify_send(peer, BGP_NOTIFY_CEASE, 0); return BGP_Stop; } /* Fetch structure to the byte stream. */ memcpy(&mpc, pnt + 3, sizeof(struct capability_mp_data)); pnt += hdr->length + 3; /* We know MP Capability Code. */ if (hdr->code == CAPABILITY_CODE_MP) { pkt_afi = ntohs(mpc.afi); pkt_safi = mpc.safi; /* Ignore capability when override-capability is set. */ if (CHECK_FLAG(peer->flags, PEER_FLAG_OVERRIDE_CAPABILITY)) continue; /* Convert AFI, SAFI to internal values. */ if (bgp_map_afi_safi_iana2int(pkt_afi, pkt_safi, &afi, &safi)) { if (bgp_debug_neighbor_events(peer)) zlog_debug( "%s Dynamic Capability MP_EXT afi/safi invalid " "(%s/%s)", peer->host, iana_afi2str(pkt_afi), iana_safi2str(pkt_safi)); continue; } /* Address family check. */ if (bgp_debug_neighbor_events(peer)) zlog_debug( "%s CAPABILITY has %s MP_EXT CAP for afi/safi: %u/%u", peer->host, action == CAPABILITY_ACTION_SET ? "Advertising" : "Removing", pkt_afi, pkt_safi); if (action == CAPABILITY_ACTION_SET) { peer->afc_recv[afi][safi] = 1; if (peer->afc[afi][safi]) { peer->afc_nego[afi][safi] = 1; bgp_announce_route(peer, afi, safi); } } else { peer->afc_recv[afi][safi] = 0; peer->afc_nego[afi][safi] = 0; if (peer_active_nego(peer)) bgp_clear_route(peer, afi, safi); else return BGP_Stop; } } else { flog_warn( EC_BGP_UNRECOGNIZED_CAPABILITY, "%s unrecognized capability code: %d - ignored", peer->host, hdr->code); } } /* No FSM action necessary */ return BGP_PACKET_NOOP; } /** * Parse BGP CAPABILITY message for peer. * * Exported for unit testing. * * @param peer * @param size size of the packet * @return as in summary */ int bgp_capability_receive(struct peer *peer, bgp_size_t size) { uint8_t *pnt; /* Fetch pointer. */ pnt = stream_pnt(peer->curr); if (bgp_debug_neighbor_events(peer)) zlog_debug("%s rcv CAPABILITY", peer->host); /* If peer does not have the capability, send notification. */ if (!CHECK_FLAG(peer->cap, PEER_CAP_DYNAMIC_ADV)) { flog_err(EC_BGP_NO_CAP, "%s [Error] BGP dynamic capability is not enabled", peer->host); bgp_notify_send(peer, BGP_NOTIFY_HEADER_ERR, BGP_NOTIFY_HEADER_BAD_MESTYPE); return BGP_Stop; } /* Status must be Established. */ if (peer->status != Established) { flog_err( EC_BGP_NO_CAP, "%s [Error] Dynamic capability packet received under status %s", peer->host, lookup_msg(bgp_status_msg, peer->status, NULL)); bgp_notify_send(peer, BGP_NOTIFY_FSM_ERR, 0); return BGP_Stop; } /* Parse packet. */ return bgp_capability_msg_parse(peer, pnt, size); } /** * Processes a peer's input buffer. * * This function sidesteps the event loop and directly calls bgp_event_update() * after processing each BGP message. This is necessary to ensure proper * ordering of FSM events and unifies the behavior that was present previously, * whereby some of the packet handling functions would update the FSM and some * would not, making event flow difficult to understand. Please think twice * before hacking this. * * Thread type: THREAD_EVENT * @param thread * @return 0 */ int bgp_process_packet(struct thread *thread) { /* Yes first of all get peer pointer. */ struct peer *peer; // peer uint32_t rpkt_quanta_old; // how many packets to read int fsm_update_result; // return code of bgp_event_update() int mprc; // message processing return code peer = THREAD_ARG(thread); rpkt_quanta_old = atomic_load_explicit(&peer->bgp->rpkt_quanta, memory_order_relaxed); fsm_update_result = 0; /* Guard against scheduled events that occur after peer deletion. */ if (peer->status == Deleted || peer->status == Clearing) return 0; unsigned int processed = 0; while (processed < rpkt_quanta_old) { uint8_t type = 0; bgp_size_t size; char notify_data_length[2]; frr_with_mutex(&peer->io_mtx) { peer->curr = stream_fifo_pop(peer->ibuf); } if (peer->curr == NULL) // no packets to process, hmm... return 0; /* skip the marker and copy the packet length */ stream_forward_getp(peer->curr, BGP_MARKER_SIZE); memcpy(notify_data_length, stream_pnt(peer->curr), 2); /* read in the packet length and type */ size = stream_getw(peer->curr); type = stream_getc(peer->curr); hook_call(bgp_packet_dump, peer, type, size, peer->curr); /* adjust size to exclude the marker + length + type */ size -= BGP_HEADER_SIZE; /* Read rest of the packet and call each sort of packet routine */ switch (type) { case BGP_MSG_OPEN: atomic_fetch_add_explicit(&peer->open_in, 1, memory_order_relaxed); mprc = bgp_open_receive(peer, size); if (mprc == BGP_Stop) flog_err( EC_BGP_PKT_OPEN, "%s: BGP OPEN receipt failed for peer: %s", __FUNCTION__, peer->host); break; case BGP_MSG_UPDATE: atomic_fetch_add_explicit(&peer->update_in, 1, memory_order_relaxed); peer->readtime = monotime(NULL); mprc = bgp_update_receive(peer, size); if (mprc == BGP_Stop) flog_err( EC_BGP_UPDATE_RCV, "%s: BGP UPDATE receipt failed for peer: %s", __FUNCTION__, peer->host); break; case BGP_MSG_NOTIFY: atomic_fetch_add_explicit(&peer->notify_in, 1, memory_order_relaxed); mprc = bgp_notify_receive(peer, size); if (mprc == BGP_Stop) flog_err( EC_BGP_NOTIFY_RCV, "%s: BGP NOTIFY receipt failed for peer: %s", __FUNCTION__, peer->host); break; case BGP_MSG_KEEPALIVE: peer->readtime = monotime(NULL); atomic_fetch_add_explicit(&peer->keepalive_in, 1, memory_order_relaxed); mprc = bgp_keepalive_receive(peer, size); if (mprc == BGP_Stop) flog_err( EC_BGP_KEEP_RCV, "%s: BGP KEEPALIVE receipt failed for peer: %s", __FUNCTION__, peer->host); break; case BGP_MSG_ROUTE_REFRESH_NEW: case BGP_MSG_ROUTE_REFRESH_OLD: atomic_fetch_add_explicit(&peer->refresh_in, 1, memory_order_relaxed); mprc = bgp_route_refresh_receive(peer, size); if (mprc == BGP_Stop) flog_err( EC_BGP_RFSH_RCV, "%s: BGP ROUTEREFRESH receipt failed for peer: %s", __FUNCTION__, peer->host); break; case BGP_MSG_CAPABILITY: atomic_fetch_add_explicit(&peer->dynamic_cap_in, 1, memory_order_relaxed); mprc = bgp_capability_receive(peer, size); if (mprc == BGP_Stop) flog_err( EC_BGP_CAP_RCV, "%s: BGP CAPABILITY receipt failed for peer: %s", __FUNCTION__, peer->host); break; default: /* Suppress uninitialized variable warning */ mprc = 0; (void)mprc; /* * The message type should have been sanitized before * we ever got here. Receipt of a message with an * invalid header at this point is indicative of a * security issue. */ assert (!"Message of invalid type received during input processing"); } /* delete processed packet */ stream_free(peer->curr); peer->curr = NULL; processed++; /* Update FSM */ if (mprc != BGP_PACKET_NOOP) fsm_update_result = bgp_event_update(peer, mprc); else continue; /* * If peer was deleted, do not process any more packets. This * is usually due to executing BGP_Stop or a stub deletion. */ if (fsm_update_result == FSM_PEER_TRANSFERRED || fsm_update_result == FSM_PEER_STOPPED) break; } if (fsm_update_result != FSM_PEER_TRANSFERRED && fsm_update_result != FSM_PEER_STOPPED) { frr_with_mutex(&peer->io_mtx) { // more work to do, come back later if (peer->ibuf->count > 0) thread_add_timer_msec( bm->master, bgp_process_packet, peer, 0, &peer->t_process_packet); } } return 0; } frr-7.2.1/bgpd/bgp_packet.h0000644000000000000000000000511413610377563012401 00000000000000/* BGP packet management header. * Copyright (C) 1999 Kunihiro Ishiguro * * This file is part of GNU Zebra. * * GNU Zebra is free software; you can redistribute it and/or modify it * under the terms of the GNU General Public License as published by the * Free Software Foundation; either version 2, or (at your option) any * later version. * * GNU Zebra 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; see the file COPYING; if not, write to the Free Software * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA */ #ifndef _QUAGGA_BGP_PACKET_H #define _QUAGGA_BGP_PACKET_H #include "hook.h" DECLARE_HOOK(bgp_packet_dump, (struct peer *peer, uint8_t type, bgp_size_t size, struct stream *s), (peer, type, size, s)) DECLARE_HOOK(bgp_packet_send, (struct peer *peer, uint8_t type, bgp_size_t size, struct stream *s), (peer, type, size, s)) #define BGP_NLRI_LENGTH 1U #define BGP_TOTAL_ATTR_LEN 2U #define BGP_UNFEASIBLE_LEN 2U /* When to refresh */ #define REFRESH_IMMEDIATE 1 #define REFRESH_DEFER 2 /* ORF Common part flag */ #define ORF_COMMON_PART_ADD 0x00 #define ORF_COMMON_PART_REMOVE 0x80 #define ORF_COMMON_PART_REMOVE_ALL 0xC0 #define ORF_COMMON_PART_PERMIT 0x00 #define ORF_COMMON_PART_DENY 0x20 /* Packet send and receive function prototypes. */ extern void bgp_keepalive_send(struct peer *); extern void bgp_open_send(struct peer *); extern void bgp_notify_send(struct peer *, uint8_t, uint8_t); extern void bgp_notify_send_with_data(struct peer *, uint8_t, uint8_t, uint8_t *, size_t); extern void bgp_route_refresh_send(struct peer *, afi_t, safi_t, uint8_t, uint8_t, int); extern void bgp_capability_send(struct peer *, afi_t, safi_t, int, int); extern int bgp_capability_receive(struct peer *, bgp_size_t); extern int bgp_nlri_parse(struct peer *, struct attr *, struct bgp_nlri *, int mp_withdraw); extern void bgp_update_restarted_peers(struct peer *); extern void bgp_update_implicit_eors(struct peer *); extern void bgp_check_update_delay(struct bgp *); extern int bgp_packet_set_marker(struct stream *s, uint8_t type); extern int bgp_packet_set_size(struct stream *s); extern int bgp_generate_updgrp_packets(struct thread *); extern int bgp_process_packet(struct thread *); #endif /* _QUAGGA_BGP_PACKET_H */ frr-7.2.1/bgpd/bgp_pbr.c0000644000000000000000000022174213610377563011717 00000000000000/* * BGP pbr * Copyright (C) 6WIND * * FRR is free software; you can redistribute it and/or modify it * under the terms of the GNU General Public License as published by the * Free Software Foundation; either version 2, or (at your option) any * later version. * * FRR 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; see the file COPYING; if not, write to the Free Software * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA */ #include "zebra.h" #include "prefix.h" #include "zclient.h" #include "jhash.h" #include "pbr.h" #include "bgpd/bgpd.h" #include "bgpd/bgp_pbr.h" #include "bgpd/bgp_debug.h" #include "bgpd/bgp_flowspec_util.h" #include "bgpd/bgp_ecommunity.h" #include "bgpd/bgp_route.h" #include "bgpd/bgp_attr.h" #include "bgpd/bgp_zebra.h" #include "bgpd/bgp_mplsvpn.h" #include "bgpd/bgp_flowspec_private.h" #include "bgpd/bgp_errors.h" DEFINE_MTYPE_STATIC(BGPD, PBR_MATCH_ENTRY, "PBR match entry") DEFINE_MTYPE_STATIC(BGPD, PBR_MATCH, "PBR match") DEFINE_MTYPE_STATIC(BGPD, PBR_ACTION, "PBR action") DEFINE_MTYPE_STATIC(BGPD, PBR_RULE, "PBR rule") DEFINE_MTYPE_STATIC(BGPD, PBR, "BGP PBR Context") DEFINE_MTYPE_STATIC(BGPD, PBR_VALMASK, "BGP PBR Val Mask Value") RB_GENERATE(bgp_pbr_interface_head, bgp_pbr_interface, id_entry, bgp_pbr_interface_compare); struct bgp_pbr_interface_head ifaces_by_name_ipv4 = RB_INITIALIZER(&ifaces_by_name_ipv4); static int bgp_pbr_match_counter_unique; static int bgp_pbr_match_entry_counter_unique; static int bgp_pbr_action_counter_unique; static int bgp_pbr_match_iptable_counter_unique; struct bgp_pbr_match_iptable_unique { uint32_t unique; struct bgp_pbr_match *bpm_found; }; struct bgp_pbr_match_entry_unique { uint32_t unique; struct bgp_pbr_match_entry *bpme_found; }; struct bgp_pbr_action_unique { uint32_t unique; struct bgp_pbr_action *bpa_found; }; struct bgp_pbr_rule_unique { uint32_t unique; struct bgp_pbr_rule *bpr_found; }; static int bgp_pbr_rule_walkcb(struct hash_bucket *bucket, void *arg) { struct bgp_pbr_rule *bpr = (struct bgp_pbr_rule *)bucket->data; struct bgp_pbr_rule_unique *bpru = (struct bgp_pbr_rule_unique *) arg; uint32_t unique = bpru->unique; if (bpr->unique == unique) { bpru->bpr_found = bpr; return HASHWALK_ABORT; } return HASHWALK_CONTINUE; } static int bgp_pbr_action_walkcb(struct hash_bucket *bucket, void *arg) { struct bgp_pbr_action *bpa = (struct bgp_pbr_action *)bucket->data; struct bgp_pbr_action_unique *bpau = (struct bgp_pbr_action_unique *) arg; uint32_t unique = bpau->unique; if (bpa->unique == unique) { bpau->bpa_found = bpa; return HASHWALK_ABORT; } return HASHWALK_CONTINUE; } static int bgp_pbr_match_entry_walkcb(struct hash_bucket *bucket, void *arg) { struct bgp_pbr_match_entry *bpme = (struct bgp_pbr_match_entry *)bucket->data; struct bgp_pbr_match_entry_unique *bpmeu = (struct bgp_pbr_match_entry_unique *)arg; uint32_t unique = bpmeu->unique; if (bpme->unique == unique) { bpmeu->bpme_found = bpme; return HASHWALK_ABORT; } return HASHWALK_CONTINUE; } struct bgp_pbr_match_ipsetname { char *ipsetname; struct bgp_pbr_match *bpm_found; }; static int bgp_pbr_match_pername_walkcb(struct hash_bucket *bucket, void *arg) { struct bgp_pbr_match *bpm = (struct bgp_pbr_match *)bucket->data; struct bgp_pbr_match_ipsetname *bpmi = (struct bgp_pbr_match_ipsetname *)arg; char *ipset_name = bpmi->ipsetname; if (!strncmp(ipset_name, bpm->ipset_name, ZEBRA_IPSET_NAME_SIZE)) { bpmi->bpm_found = bpm; return HASHWALK_ABORT; } return HASHWALK_CONTINUE; } static int bgp_pbr_match_iptable_walkcb(struct hash_bucket *bucket, void *arg) { struct bgp_pbr_match *bpm = (struct bgp_pbr_match *)bucket->data; struct bgp_pbr_match_iptable_unique *bpmiu = (struct bgp_pbr_match_iptable_unique *)arg; uint32_t unique = bpmiu->unique; if (bpm->unique2 == unique) { bpmiu->bpm_found = bpm; return HASHWALK_ABORT; } return HASHWALK_CONTINUE; } struct bgp_pbr_match_unique { uint32_t unique; struct bgp_pbr_match *bpm_found; }; static int bgp_pbr_match_walkcb(struct hash_bucket *bucket, void *arg) { struct bgp_pbr_match *bpm = (struct bgp_pbr_match *)bucket->data; struct bgp_pbr_match_unique *bpmu = (struct bgp_pbr_match_unique *) arg; uint32_t unique = bpmu->unique; if (bpm->unique == unique) { bpmu->bpm_found = bpm; return HASHWALK_ABORT; } return HASHWALK_CONTINUE; } static int sprintf_bgp_pbr_match_val(char *str, struct bgp_pbr_match_val *mval, const char *prepend) { char *ptr = str; if (prepend) ptr += sprintf(ptr, "%s", prepend); else { if (mval->unary_operator & OPERATOR_UNARY_OR) ptr += sprintf(ptr, ", or "); if (mval->unary_operator & OPERATOR_UNARY_AND) ptr += sprintf(ptr, ", and "); } if (mval->compare_operator & OPERATOR_COMPARE_LESS_THAN) ptr += sprintf(ptr, "<"); if (mval->compare_operator & OPERATOR_COMPARE_GREATER_THAN) ptr += sprintf(ptr, ">"); if (mval->compare_operator & OPERATOR_COMPARE_EQUAL_TO) ptr += sprintf(ptr, "="); if (mval->compare_operator & OPERATOR_COMPARE_EXACT_MATCH) ptr += sprintf(ptr, "match"); ptr += sprintf(ptr, " %u", mval->value); return (int)(ptr - str); } #define INCREMENT_DISPLAY(_ptr, _cnt) do { \ if (_cnt) \ (_ptr) += sprintf((_ptr), "; "); \ _cnt++; \ } while (0) /* this structure can be used for port range, * but also for other values range like packet length range */ struct bgp_pbr_range_port { uint16_t min_port; uint16_t max_port; }; /* this structure can be used to filter with a mask * for instance it supports not instructions like for * tcpflags */ struct bgp_pbr_val_mask { uint16_t val; uint16_t mask; }; /* this structure is used to pass instructs * so that BGP can create pbr instructions to ZEBRA */ struct bgp_pbr_filter { uint8_t type; vrf_id_t vrf_id; struct prefix *src; struct prefix *dst; uint8_t bitmask_iprule; uint8_t protocol; struct bgp_pbr_range_port *pkt_len; struct bgp_pbr_range_port *src_port; struct bgp_pbr_range_port *dst_port; struct bgp_pbr_val_mask *tcp_flags; struct bgp_pbr_val_mask *dscp; struct bgp_pbr_val_mask *pkt_len_val; struct bgp_pbr_val_mask *fragment; }; /* this structure is used to contain OR instructions * so that BGP can create multiple pbr instructions * to ZEBRA */ struct bgp_pbr_or_filter { struct list *tcpflags; struct list *dscp; struct list *pkt_len; struct list *fragment; struct list *icmp_type; struct list *icmp_code; }; static void bgp_pbr_policyroute_add_to_zebra_unit(struct bgp *bgp, struct bgp_path_info *path, struct bgp_pbr_filter *bpf, struct nexthop *nh, float *rate); static void bgp_pbr_dump_entry(struct bgp_pbr_filter *bpf, bool add); static bool bgp_pbr_extract_enumerate_unary_opposite( uint8_t unary_operator, struct bgp_pbr_val_mask *and_valmask, struct list *or_valmask, uint32_t value, uint8_t type_entry) { if (unary_operator == OPERATOR_UNARY_AND && and_valmask) { if (type_entry == FLOWSPEC_TCP_FLAGS) { and_valmask->mask |= TCP_HEADER_ALL_FLAGS & ~(value); } else if (type_entry == FLOWSPEC_DSCP || type_entry == FLOWSPEC_PKT_LEN || type_entry == FLOWSPEC_FRAGMENT) { and_valmask->val = value; and_valmask->mask = 1; /* inverse */ } } else if (unary_operator == OPERATOR_UNARY_OR && or_valmask) { and_valmask = XCALLOC(MTYPE_PBR_VALMASK, sizeof(struct bgp_pbr_val_mask)); if (type_entry == FLOWSPEC_TCP_FLAGS) { and_valmask->val = TCP_HEADER_ALL_FLAGS; and_valmask->mask |= TCP_HEADER_ALL_FLAGS & ~(value); } else if (type_entry == FLOWSPEC_DSCP || type_entry == FLOWSPEC_FRAGMENT || type_entry == FLOWSPEC_PKT_LEN) { and_valmask->val = value; and_valmask->mask = 1; /* inverse */ } listnode_add(or_valmask, and_valmask); } else if (type_entry == FLOWSPEC_ICMP_CODE || type_entry == FLOWSPEC_ICMP_TYPE) return false; return true; } /* TCP : FIN and SYN -> val = ALL; mask = 3 * TCP : not (FIN and SYN) -> val = ALL; mask = ALL & ~(FIN|RST) * other variables type: dscp, pkt len, fragment * - value is copied in bgp_pbr_val_mask->val value * - if negate form is identifierd, bgp_pbr_val_mask->mask set to 1 */ static bool bgp_pbr_extract_enumerate_unary(struct bgp_pbr_match_val list[], int num, uint8_t unary_operator, void *valmask, uint8_t type_entry) { int i = 0; struct bgp_pbr_val_mask *and_valmask = NULL; struct list *or_valmask = NULL; bool ret; if (valmask) { if (unary_operator == OPERATOR_UNARY_AND) { and_valmask = (struct bgp_pbr_val_mask *)valmask; memset(and_valmask, 0, sizeof(struct bgp_pbr_val_mask)); } else if (unary_operator == OPERATOR_UNARY_OR) { or_valmask = (struct list *)valmask; } } for (i = 0; i < num; i++) { if (i != 0 && list[i].unary_operator != unary_operator) return false; if (!(list[i].compare_operator & OPERATOR_COMPARE_EQUAL_TO) && !(list[i].compare_operator & OPERATOR_COMPARE_EXACT_MATCH)) { if ((list[i].compare_operator & OPERATOR_COMPARE_LESS_THAN) && (list[i].compare_operator & OPERATOR_COMPARE_GREATER_THAN)) { ret = bgp_pbr_extract_enumerate_unary_opposite( unary_operator, and_valmask, or_valmask, list[i].value, type_entry); if (!ret) return ret; continue; } return false; } if (unary_operator == OPERATOR_UNARY_AND && and_valmask) { if (type_entry == FLOWSPEC_TCP_FLAGS) and_valmask->mask |= TCP_HEADER_ALL_FLAGS & list[i].value; } else if (unary_operator == OPERATOR_UNARY_OR && or_valmask) { and_valmask = XCALLOC(MTYPE_PBR_VALMASK, sizeof(struct bgp_pbr_val_mask)); if (type_entry == FLOWSPEC_TCP_FLAGS) { and_valmask->val = TCP_HEADER_ALL_FLAGS; and_valmask->mask |= TCP_HEADER_ALL_FLAGS & list[i].value; } else if (type_entry == FLOWSPEC_DSCP || type_entry == FLOWSPEC_ICMP_TYPE || type_entry == FLOWSPEC_ICMP_CODE || type_entry == FLOWSPEC_FRAGMENT || type_entry == FLOWSPEC_PKT_LEN) and_valmask->val = list[i].value; listnode_add(or_valmask, and_valmask); } } if (unary_operator == OPERATOR_UNARY_AND && and_valmask && type_entry == FLOWSPEC_TCP_FLAGS) and_valmask->val = TCP_HEADER_ALL_FLAGS; return true; } /* if unary operator can either be UNARY_OR/AND/OR-AND. * in the latter case, combinationf of both is not handled */ static bool bgp_pbr_extract_enumerate(struct bgp_pbr_match_val list[], int num, uint8_t unary_operator, void *valmask, uint8_t type_entry) { bool ret; uint8_t unary_operator_val; bool double_check = false; if ((unary_operator & OPERATOR_UNARY_OR) && (unary_operator & OPERATOR_UNARY_AND)) { unary_operator_val = OPERATOR_UNARY_AND; double_check = true; } else unary_operator_val = unary_operator; ret = bgp_pbr_extract_enumerate_unary(list, num, unary_operator_val, valmask, type_entry); if (!ret && double_check) ret = bgp_pbr_extract_enumerate_unary(list, num, OPERATOR_UNARY_OR, valmask, type_entry); return ret; } /* returns the unary operator that is in the list * return 0 if both operators are used */ static uint8_t bgp_pbr_match_val_get_operator(struct bgp_pbr_match_val list[], int num) { int i; uint8_t unary_operator = OPERATOR_UNARY_AND; for (i = 0; i < num; i++) { if (i == 0) continue; if (list[i].unary_operator & OPERATOR_UNARY_OR) unary_operator = OPERATOR_UNARY_OR; if ((list[i].unary_operator & OPERATOR_UNARY_AND && unary_operator == OPERATOR_UNARY_OR) || (list[i].unary_operator & OPERATOR_UNARY_OR && unary_operator == OPERATOR_UNARY_AND)) return 0; } return unary_operator; } /* return true if extraction ok */ static bool bgp_pbr_extract(struct bgp_pbr_match_val list[], int num, struct bgp_pbr_range_port *range) { int i = 0; bool exact_match = false; if (range) memset(range, 0, sizeof(struct bgp_pbr_range_port)); if (num > 2) return false; for (i = 0; i < num; i++) { if (i != 0 && (list[i].compare_operator == OPERATOR_COMPARE_EQUAL_TO)) return false; if (i == 0 && (list[i].compare_operator == OPERATOR_COMPARE_EQUAL_TO)) { if (range) range->min_port = list[i].value; exact_match = true; } if (exact_match && i > 0) return false; if (list[i].compare_operator == (OPERATOR_COMPARE_GREATER_THAN + OPERATOR_COMPARE_EQUAL_TO)) { if (range) range->min_port = list[i].value; } else if (list[i].compare_operator == (OPERATOR_COMPARE_LESS_THAN + OPERATOR_COMPARE_EQUAL_TO)) { if (range) range->max_port = list[i].value; } else if (list[i].compare_operator == OPERATOR_COMPARE_LESS_THAN) { if (range) range->max_port = list[i].value - 1; } else if (list[i].compare_operator == OPERATOR_COMPARE_GREATER_THAN) { if (range) range->min_port = list[i].value + 1; } } return true; } static int bgp_pbr_validate_policy_route(struct bgp_pbr_entry_main *api) { bool enumerate_icmp = false; if (api->type == BGP_PBR_UNDEFINED) { if (BGP_DEBUG(pbr, PBR)) zlog_debug("BGP: pbr entry undefined. cancel."); return 0; } /* because bgp pbr entry may contain unsupported * combinations, a message will be displayed here if * not supported. * for now, only match/set supported is * - combination src/dst => redirect nexthop [ + rate] * - combination src/dst => redirect VRF [ + rate] * - combination src/dst => drop * - combination srcport + @IP */ if (api->match_protocol_num > 1) { if (BGP_DEBUG(pbr, PBR)) zlog_debug("BGP: match protocol operations:" "multiple protocols ( %d). ignoring.", api->match_protocol_num); return 0; } if (api->match_protocol_num == 1 && api->protocol[0].value != PROTOCOL_UDP && api->protocol[0].value != PROTOCOL_ICMP && api->protocol[0].value != PROTOCOL_TCP) { if (BGP_DEBUG(pbr, PBR)) zlog_debug("BGP: match protocol operations:" "protocol (%d) not supported. ignoring", api->match_protocol_num); return 0; } if (!bgp_pbr_extract(api->src_port, api->match_src_port_num, NULL)) { if (BGP_DEBUG(pbr, PBR)) zlog_debug("BGP: match src port operations:" "too complex. ignoring."); return 0; } if (!bgp_pbr_extract(api->dst_port, api->match_dst_port_num, NULL)) { if (BGP_DEBUG(pbr, PBR)) zlog_debug("BGP: match dst port operations:" "too complex. ignoring."); return 0; } if (!bgp_pbr_extract_enumerate(api->tcpflags, api->match_tcpflags_num, OPERATOR_UNARY_AND | OPERATOR_UNARY_OR, NULL, FLOWSPEC_TCP_FLAGS)) { if (BGP_DEBUG(pbr, PBR)) zlog_debug("BGP: match tcp flags:" "too complex. ignoring."); return 0; } if (!bgp_pbr_extract(api->icmp_type, api->match_icmp_type_num, NULL)) { if (!bgp_pbr_extract_enumerate(api->icmp_type, api->match_icmp_type_num, OPERATOR_UNARY_OR, NULL, FLOWSPEC_ICMP_TYPE)) { if (BGP_DEBUG(pbr, PBR)) zlog_debug("BGP: match icmp type operations:" "too complex. ignoring."); return 0; } enumerate_icmp = true; } if (!bgp_pbr_extract(api->icmp_code, api->match_icmp_code_num, NULL)) { if (!bgp_pbr_extract_enumerate(api->icmp_code, api->match_icmp_code_num, OPERATOR_UNARY_OR, NULL, FLOWSPEC_ICMP_CODE)) { if (BGP_DEBUG(pbr, PBR)) zlog_debug("BGP: match icmp code operations:" "too complex. ignoring."); return 0; } else if (api->match_icmp_type_num > 1 && !enumerate_icmp) { if (BGP_DEBUG(pbr, PBR)) zlog_debug("BGP: match icmp code is enumerate" ", and icmp type is not." " too complex. ignoring."); return 0; } } if (!bgp_pbr_extract(api->port, api->match_port_num, NULL)) { if (BGP_DEBUG(pbr, PBR)) zlog_debug("BGP: match port operations:" "too complex. ignoring."); return 0; } if (api->match_packet_length_num) { bool ret; ret = bgp_pbr_extract(api->packet_length, api->match_packet_length_num, NULL); if (!ret) ret = bgp_pbr_extract_enumerate(api->packet_length, api->match_packet_length_num, OPERATOR_UNARY_OR | OPERATOR_UNARY_AND, NULL, FLOWSPEC_PKT_LEN); if (!ret) { if (BGP_DEBUG(pbr, PBR)) zlog_debug("BGP: match packet length operations:" "too complex. ignoring."); return 0; } } if (api->match_dscp_num) { if (!bgp_pbr_extract_enumerate(api->dscp, api->match_dscp_num, OPERATOR_UNARY_OR | OPERATOR_UNARY_AND, NULL, FLOWSPEC_DSCP)) { if (BGP_DEBUG(pbr, PBR)) zlog_debug("BGP: match DSCP operations:" "too complex. ignoring."); return 0; } } if (api->match_fragment_num) { char fail_str[64]; bool success; success = bgp_pbr_extract_enumerate(api->fragment, api->match_fragment_num, OPERATOR_UNARY_OR | OPERATOR_UNARY_AND, NULL, FLOWSPEC_FRAGMENT); if (success) { int i; for (i = 0; i < api->match_fragment_num; i++) { if (api->fragment[i].value != 1 && api->fragment[i].value != 2 && api->fragment[i].value != 4 && api->fragment[i].value != 8) { success = false; sprintf(fail_str, "Value not valid (%d) for this implementation", api->fragment[i].value); } } } else sprintf(fail_str, "too complex. ignoring"); if (!success) { if (BGP_DEBUG(pbr, PBR)) zlog_debug("BGP: match fragment operation (%d) %s", api->match_fragment_num, fail_str); return 0; } } /* no combinations with both src_port and dst_port * or port with src_port and dst_port */ if (api->match_src_port_num + api->match_dst_port_num + api->match_port_num > 3) { if (BGP_DEBUG(pbr, PBR)) zlog_debug("BGP: match multiple port operations:" " too complex. ignoring."); return 0; } if ((api->match_src_port_num || api->match_dst_port_num || api->match_port_num) && (api->match_icmp_type_num || api->match_icmp_code_num)) { if (BGP_DEBUG(pbr, PBR)) zlog_debug("BGP: match multiple port/imcp operations:" " too complex. ignoring."); return 0; } /* iprule only supports redirect IP */ if (api->type == BGP_PBR_IPRULE) { int i; for (i = 0; i < api->action_num; i++) { if (api->actions[i].action == ACTION_TRAFFICRATE && api->actions[i].u.r.rate == 0) { if (BGP_DEBUG(pbr, PBR)) { bgp_pbr_print_policy_route(api); zlog_debug("BGP: iprule match actions" " drop not supported"); } return 0; } if (api->actions[i].action == ACTION_MARKING) { if (BGP_DEBUG(pbr, PBR)) { bgp_pbr_print_policy_route(api); zlog_warn("PBR: iprule set DSCP %u" " not supported", api->actions[i].u.marking_dscp); } } if (api->actions[i].action == ACTION_REDIRECT) { if (BGP_DEBUG(pbr, PBR)) { bgp_pbr_print_policy_route(api); zlog_warn("PBR: iprule redirect VRF %u" " not supported", api->actions[i].u.redirect_vrf); } } } } else if (!(api->match_bitmask & PREFIX_SRC_PRESENT) && !(api->match_bitmask & PREFIX_DST_PRESENT)) { if (BGP_DEBUG(pbr, PBR)) { bgp_pbr_print_policy_route(api); zlog_debug("BGP: match actions without src" " or dst address can not operate." " ignoring."); } return 0; } return 1; } /* return -1 if build or validation failed */ int bgp_pbr_build_and_validate_entry(struct prefix *p, struct bgp_path_info *path, struct bgp_pbr_entry_main *api) { int ret; int i, action_count = 0; struct ecommunity *ecom; struct ecommunity_val *ecom_eval; struct bgp_pbr_entry_action *api_action; struct prefix *src = NULL, *dst = NULL; int valid_prefix = 0; afi_t afi = AFI_IP; struct bgp_pbr_entry_action *api_action_redirect_ip = NULL; bool discard_action_found = false; /* extract match from flowspec entries */ ret = bgp_flowspec_match_rules_fill((uint8_t *)p->u.prefix_flowspec.ptr, p->u.prefix_flowspec.prefixlen, api); if (ret < 0) return -1; /* extract actiosn from flowspec ecom list */ if (path && path->attr && path->attr->ecommunity) { ecom = path->attr->ecommunity; for (i = 0; i < ecom->size; i++) { ecom_eval = (struct ecommunity_val *) (ecom->val + (i * ECOMMUNITY_SIZE)); action_count++; if (action_count > ACTIONS_MAX_NUM) { if (BGP_DEBUG(pbr, PBR_ERROR)) flog_err( EC_BGP_FLOWSPEC_PACKET, "%s: flowspec actions exceeds limit (max %u)", __func__, action_count); break; } api_action = &api->actions[action_count - 1]; if ((ecom_eval->val[1] == (char)ECOMMUNITY_REDIRECT_VRF) && (ecom_eval->val[0] == (char)ECOMMUNITY_ENCODE_TRANS_EXP || ecom_eval->val[0] == (char)ECOMMUNITY_EXTENDED_COMMUNITY_PART_2 || ecom_eval->val[0] == (char)ECOMMUNITY_EXTENDED_COMMUNITY_PART_3)) { struct ecommunity *eckey = ecommunity_new(); struct ecommunity_val ecom_copy; memcpy(&ecom_copy, ecom_eval, sizeof(struct ecommunity_val)); ecom_copy.val[0] &= ~ECOMMUNITY_ENCODE_TRANS_EXP; ecom_copy.val[1] = ECOMMUNITY_ROUTE_TARGET; ecommunity_add_val(eckey, &ecom_copy); api_action->action = ACTION_REDIRECT; api_action->u.redirect_vrf = get_first_vrf_for_redirect_with_rt( eckey); ecommunity_free(&eckey); } else if ((ecom_eval->val[0] == (char)ECOMMUNITY_ENCODE_REDIRECT_IP_NH) && (ecom_eval->val[1] == (char)ECOMMUNITY_REDIRECT_IP_NH)) { /* in case the 2 ecom present, * do not overwrite * draft-ietf-idr-flowspec-redirect */ if (api_action_redirect_ip) { if (api_action_redirect_ip->u .zr.redirect_ip_v4.s_addr) continue; if (!path->attr->nexthop.s_addr) continue; api_action_redirect_ip->u .zr.redirect_ip_v4.s_addr = path->attr->nexthop.s_addr; api_action_redirect_ip->u.zr.duplicate = ecom_eval->val[7]; continue; } else { api_action->action = ACTION_REDIRECT_IP; api_action->u.zr.redirect_ip_v4.s_addr = path->attr->nexthop.s_addr; api_action->u.zr.duplicate = ecom_eval->val[7]; api_action_redirect_ip = api_action; } } else if ((ecom_eval->val[0] == (char)ECOMMUNITY_ENCODE_IP) && (ecom_eval->val[1] == (char)ECOMMUNITY_FLOWSPEC_REDIRECT_IPV4)) { /* in case the 2 ecom present, * overwrite simpson draft * update redirect ip fields */ if (api_action_redirect_ip) { memcpy(&(api_action_redirect_ip->u .zr.redirect_ip_v4.s_addr), (ecom_eval->val+2), 4); api_action_redirect_ip->u .zr.duplicate = ecom_eval->val[7]; continue; } else { api_action->action = ACTION_REDIRECT_IP; memcpy(&(api_action->u .zr.redirect_ip_v4.s_addr), (ecom_eval->val+2), 4); api_action->u.zr.duplicate = ecom_eval->val[7]; api_action_redirect_ip = api_action; } } else { if (ecom_eval->val[0] != (char)ECOMMUNITY_ENCODE_TRANS_EXP) continue; ret = ecommunity_fill_pbr_action(ecom_eval, api_action); if (ret != 0) continue; if ((api_action->action == ACTION_TRAFFICRATE) && api->actions[i].u.r.rate == 0) discard_action_found = true; } api->action_num++; } } /* if ECOMMUNITY_TRAFFIC_RATE = 0 as action * then reduce the API action list to that action */ if (api->action_num > 1 && discard_action_found) { api->action_num = 1; memset(&api->actions[0], 0, sizeof(struct bgp_pbr_entry_action)); api->actions[0].action = ACTION_TRAFFICRATE; } /* validate if incoming matc/action is compatible * with our policy routing engine */ if (!bgp_pbr_validate_policy_route(api)) return -1; /* check inconsistency in the match rule */ if (api->match_bitmask & PREFIX_SRC_PRESENT) { src = &api->src_prefix; afi = family2afi(src->family); valid_prefix = 1; } if (api->match_bitmask & PREFIX_DST_PRESENT) { dst = &api->dst_prefix; if (valid_prefix && afi != family2afi(dst->family)) { if (BGP_DEBUG(pbr, PBR)) { bgp_pbr_print_policy_route(api); zlog_debug("%s: inconsistency:" " no match for afi src and dst (%u/%u)", __func__, afi, family2afi(dst->family)); } return -1; } } return 0; } static void bgp_pbr_match_entry_free(void *arg) { struct bgp_pbr_match_entry *bpme; bpme = (struct bgp_pbr_match_entry *)arg; if (bpme->installed) { bgp_send_pbr_ipset_entry_match(bpme, false); bpme->installed = false; bpme->backpointer = NULL; } XFREE(MTYPE_PBR_MATCH_ENTRY, bpme); } static void bgp_pbr_match_free(void *arg) { struct bgp_pbr_match *bpm; bpm = (struct bgp_pbr_match *)arg; hash_clean(bpm->entry_hash, bgp_pbr_match_entry_free); if (hashcount(bpm->entry_hash) == 0) { /* delete iptable entry first */ /* then delete ipset match */ if (bpm->installed) { if (bpm->installed_in_iptable) { bgp_send_pbr_iptable(bpm->action, bpm, false); bpm->installed_in_iptable = false; bpm->action->refcnt--; } bgp_send_pbr_ipset_match(bpm, false); bpm->installed = false; bpm->action = NULL; } } hash_free(bpm->entry_hash); XFREE(MTYPE_PBR_MATCH, bpm); } static void *bgp_pbr_match_alloc_intern(void *arg) { struct bgp_pbr_match *bpm, *new; bpm = (struct bgp_pbr_match *)arg; new = XCALLOC(MTYPE_PBR_MATCH, sizeof(*new)); memcpy(new, bpm, sizeof(*bpm)); return new; } static void bgp_pbr_rule_free(void *arg) { struct bgp_pbr_rule *bpr; bpr = (struct bgp_pbr_rule *)arg; /* delete iprule */ if (bpr->installed) { bgp_send_pbr_rule_action(bpr->action, bpr, false); bpr->installed = false; bpr->action->refcnt--; bpr->action = NULL; } XFREE(MTYPE_PBR_RULE, bpr); } static void *bgp_pbr_rule_alloc_intern(void *arg) { struct bgp_pbr_rule *bpr, *new; bpr = (struct bgp_pbr_rule *)arg; new = XCALLOC(MTYPE_PBR_RULE, sizeof(*new)); memcpy(new, bpr, sizeof(*bpr)); return new; } static void bgp_pbr_action_free(void *arg) { struct bgp_pbr_action *bpa; bpa = (struct bgp_pbr_action *)arg; if (bpa->refcnt == 0) { if (bpa->installed && bpa->table_id != 0) { bgp_send_pbr_rule_action(bpa, NULL, false); bgp_zebra_announce_default(bpa->bgp, &(bpa->nh), AFI_IP, bpa->table_id, false); bpa->installed = false; } } XFREE(MTYPE_PBR_ACTION, bpa); } static void *bgp_pbr_action_alloc_intern(void *arg) { struct bgp_pbr_action *bpa, *new; bpa = (struct bgp_pbr_action *)arg; new = XCALLOC(MTYPE_PBR_ACTION, sizeof(*new)); memcpy(new, bpa, sizeof(*bpa)); return new; } static void *bgp_pbr_match_entry_alloc_intern(void *arg) { struct bgp_pbr_match_entry *bpme, *new; bpme = (struct bgp_pbr_match_entry *)arg; new = XCALLOC(MTYPE_PBR_MATCH_ENTRY, sizeof(*new)); memcpy(new, bpme, sizeof(*bpme)); return new; } uint32_t bgp_pbr_match_hash_key(const void *arg) { const struct bgp_pbr_match *pbm = arg; uint32_t key; key = jhash_1word(pbm->vrf_id, 0x4312abde); key = jhash_1word(pbm->flags, key); key = jhash(&pbm->pkt_len_min, 2, key); key = jhash(&pbm->pkt_len_max, 2, key); key = jhash(&pbm->tcp_flags, 2, key); key = jhash(&pbm->tcp_mask_flags, 2, key); key = jhash(&pbm->dscp_value, 1, key); key = jhash(&pbm->fragment, 1, key); key = jhash(&pbm->protocol, 1, key); return jhash_1word(pbm->type, key); } bool bgp_pbr_match_hash_equal(const void *arg1, const void *arg2) { const struct bgp_pbr_match *r1, *r2; r1 = (const struct bgp_pbr_match *)arg1; r2 = (const struct bgp_pbr_match *)arg2; if (r1->vrf_id != r2->vrf_id) return false; if (r1->type != r2->type) return false; if (r1->flags != r2->flags) return false; if (r1->action != r2->action) return false; if (r1->pkt_len_min != r2->pkt_len_min) return false; if (r1->pkt_len_max != r2->pkt_len_max) return false; if (r1->tcp_flags != r2->tcp_flags) return false; if (r1->tcp_mask_flags != r2->tcp_mask_flags) return false; if (r1->dscp_value != r2->dscp_value) return false; if (r1->fragment != r2->fragment) return false; if (r1->protocol != r2->protocol) return false; return true; } uint32_t bgp_pbr_rule_hash_key(const void *arg) { const struct bgp_pbr_rule *pbr = arg; uint32_t key; key = prefix_hash_key(&pbr->src); key = jhash_1word(pbr->vrf_id, key); key = jhash_1word(pbr->flags, key); return jhash_1word(prefix_hash_key(&pbr->dst), key); } bool bgp_pbr_rule_hash_equal(const void *arg1, const void *arg2) { const struct bgp_pbr_rule *r1, *r2; r1 = (const struct bgp_pbr_rule *)arg1; r2 = (const struct bgp_pbr_rule *)arg2; if (r1->vrf_id != r2->vrf_id) return false; if (r1->flags != r2->flags) return false; if (r1->action != r2->action) return false; if ((r1->flags & MATCH_IP_SRC_SET) && !prefix_same(&r1->src, &r2->src)) return false; if ((r1->flags & MATCH_IP_DST_SET) && !prefix_same(&r1->dst, &r2->dst)) return false; return true; } uint32_t bgp_pbr_match_entry_hash_key(const void *arg) { const struct bgp_pbr_match_entry *pbme; uint32_t key; pbme = arg; key = prefix_hash_key(&pbme->src); key = jhash_1word(prefix_hash_key(&pbme->dst), key); key = jhash(&pbme->dst_port_min, 2, key); key = jhash(&pbme->src_port_min, 2, key); key = jhash(&pbme->dst_port_max, 2, key); key = jhash(&pbme->src_port_max, 2, key); key = jhash(&pbme->proto, 1, key); return key; } bool bgp_pbr_match_entry_hash_equal(const void *arg1, const void *arg2) { const struct bgp_pbr_match_entry *r1, *r2; r1 = (const struct bgp_pbr_match_entry *)arg1; r2 = (const struct bgp_pbr_match_entry *)arg2; /* * on updates, comparing backpointer is not necessary * unique value is self calculated * rate is ignored for now */ if (!prefix_same(&r1->src, &r2->src)) return false; if (!prefix_same(&r1->dst, &r2->dst)) return false; if (r1->src_port_min != r2->src_port_min) return false; if (r1->dst_port_min != r2->dst_port_min) return false; if (r1->src_port_max != r2->src_port_max) return false; if (r1->dst_port_max != r2->dst_port_max) return false; if (r1->proto != r2->proto) return false; return true; } uint32_t bgp_pbr_action_hash_key(const void *arg) { const struct bgp_pbr_action *pbra; uint32_t key; pbra = arg; key = jhash_1word(pbra->table_id, 0x4312abde); key = jhash_1word(pbra->fwmark, key); return key; } bool bgp_pbr_action_hash_equal(const void *arg1, const void *arg2) { const struct bgp_pbr_action *r1, *r2; r1 = (const struct bgp_pbr_action *)arg1; r2 = (const struct bgp_pbr_action *)arg2; /* unique value is self calculated * table and fwmark is self calculated * rate is ignored */ if (r1->vrf_id != r2->vrf_id) return false; if (memcmp(&r1->nh, &r2->nh, sizeof(struct nexthop))) return false; return true; } struct bgp_pbr_rule *bgp_pbr_rule_lookup(vrf_id_t vrf_id, uint32_t unique) { struct bgp *bgp = bgp_lookup_by_vrf_id(vrf_id); struct bgp_pbr_rule_unique bpru; if (!bgp || unique == 0) return NULL; bpru.unique = unique; bpru.bpr_found = NULL; hash_walk(bgp->pbr_rule_hash, bgp_pbr_rule_walkcb, &bpru); return bpru.bpr_found; } struct bgp_pbr_action *bgp_pbr_action_rule_lookup(vrf_id_t vrf_id, uint32_t unique) { struct bgp *bgp = bgp_lookup_by_vrf_id(vrf_id); struct bgp_pbr_action_unique bpau; if (!bgp || unique == 0) return NULL; bpau.unique = unique; bpau.bpa_found = NULL; hash_walk(bgp->pbr_action_hash, bgp_pbr_action_walkcb, &bpau); return bpau.bpa_found; } struct bgp_pbr_match *bgp_pbr_match_ipset_lookup(vrf_id_t vrf_id, uint32_t unique) { struct bgp *bgp = bgp_lookup_by_vrf_id(vrf_id); struct bgp_pbr_match_unique bpmu; if (!bgp || unique == 0) return NULL; bpmu.unique = unique; bpmu.bpm_found = NULL; hash_walk(bgp->pbr_match_hash, bgp_pbr_match_walkcb, &bpmu); return bpmu.bpm_found; } struct bgp_pbr_match_entry *bgp_pbr_match_ipset_entry_lookup(vrf_id_t vrf_id, char *ipset_name, uint32_t unique) { struct bgp *bgp = bgp_lookup_by_vrf_id(vrf_id); struct bgp_pbr_match_entry_unique bpmeu; struct bgp_pbr_match_ipsetname bpmi; if (!bgp || unique == 0) return NULL; bpmi.ipsetname = XCALLOC(MTYPE_TMP, ZEBRA_IPSET_NAME_SIZE); snprintf(bpmi.ipsetname, ZEBRA_IPSET_NAME_SIZE, "%s", ipset_name); bpmi.bpm_found = NULL; hash_walk(bgp->pbr_match_hash, bgp_pbr_match_pername_walkcb, &bpmi); XFREE(MTYPE_TMP, bpmi.ipsetname); if (!bpmi.bpm_found) return NULL; bpmeu.bpme_found = NULL; bpmeu.unique = unique; hash_walk(bpmi.bpm_found->entry_hash, bgp_pbr_match_entry_walkcb, &bpmeu); return bpmeu.bpme_found; } struct bgp_pbr_match *bgp_pbr_match_iptable_lookup(vrf_id_t vrf_id, uint32_t unique) { struct bgp *bgp = bgp_lookup_by_vrf_id(vrf_id); struct bgp_pbr_match_iptable_unique bpmiu; if (!bgp || unique == 0) return NULL; bpmiu.unique = unique; bpmiu.bpm_found = NULL; hash_walk(bgp->pbr_match_hash, bgp_pbr_match_iptable_walkcb, &bpmiu); return bpmiu.bpm_found; } void bgp_pbr_cleanup(struct bgp *bgp) { if (bgp->pbr_match_hash) { hash_clean(bgp->pbr_match_hash, bgp_pbr_match_free); hash_free(bgp->pbr_match_hash); bgp->pbr_match_hash = NULL; } if (bgp->pbr_rule_hash) { hash_clean(bgp->pbr_rule_hash, bgp_pbr_rule_free); hash_free(bgp->pbr_rule_hash); bgp->pbr_rule_hash = NULL; } if (bgp->pbr_action_hash) { hash_clean(bgp->pbr_action_hash, bgp_pbr_action_free); hash_free(bgp->pbr_action_hash); bgp->pbr_action_hash = NULL; } if (bgp->bgp_pbr_cfg == NULL) return; bgp_pbr_reset(bgp, AFI_IP); XFREE(MTYPE_PBR, bgp->bgp_pbr_cfg); bgp->bgp_pbr_cfg = NULL; } void bgp_pbr_init(struct bgp *bgp) { bgp->pbr_match_hash = hash_create_size(8, bgp_pbr_match_hash_key, bgp_pbr_match_hash_equal, "Match Hash"); bgp->pbr_action_hash = hash_create_size(8, bgp_pbr_action_hash_key, bgp_pbr_action_hash_equal, "Match Hash Entry"); bgp->pbr_rule_hash = hash_create_size(8, bgp_pbr_rule_hash_key, bgp_pbr_rule_hash_equal, "Match Rule"); bgp->bgp_pbr_cfg = XCALLOC(MTYPE_PBR, sizeof(struct bgp_pbr_config)); bgp->bgp_pbr_cfg->pbr_interface_any_ipv4 = true; } void bgp_pbr_print_policy_route(struct bgp_pbr_entry_main *api) { int i = 0; char return_string[512]; char *ptr = return_string; char buff[64]; int nb_items = 0; ptr += sprintf(ptr, "MATCH : "); if (api->match_bitmask & PREFIX_SRC_PRESENT) { struct prefix *p = &(api->src_prefix); ptr += sprintf(ptr, "@src %s", prefix2str(p, buff, 64)); INCREMENT_DISPLAY(ptr, nb_items); } if (api->match_bitmask & PREFIX_DST_PRESENT) { struct prefix *p = &(api->dst_prefix); INCREMENT_DISPLAY(ptr, nb_items); ptr += sprintf(ptr, "@dst %s", prefix2str(p, buff, 64)); } if (api->match_protocol_num) INCREMENT_DISPLAY(ptr, nb_items); for (i = 0; i < api->match_protocol_num; i++) ptr += sprintf_bgp_pbr_match_val(ptr, &api->protocol[i], i > 0 ? NULL : "@proto "); if (api->match_src_port_num) INCREMENT_DISPLAY(ptr, nb_items); for (i = 0; i < api->match_src_port_num; i++) ptr += sprintf_bgp_pbr_match_val(ptr, &api->src_port[i], i > 0 ? NULL : "@srcport "); if (api->match_dst_port_num) INCREMENT_DISPLAY(ptr, nb_items); for (i = 0; i < api->match_dst_port_num; i++) ptr += sprintf_bgp_pbr_match_val(ptr, &api->dst_port[i], i > 0 ? NULL : "@dstport "); if (api->match_port_num) INCREMENT_DISPLAY(ptr, nb_items); for (i = 0; i < api->match_port_num; i++) ptr += sprintf_bgp_pbr_match_val(ptr, &api->port[i], i > 0 ? NULL : "@port "); if (api->match_icmp_type_num) INCREMENT_DISPLAY(ptr, nb_items); for (i = 0; i < api->match_icmp_type_num; i++) ptr += sprintf_bgp_pbr_match_val(ptr, &api->icmp_type[i], i > 0 ? NULL : "@icmptype "); if (api->match_icmp_code_num) INCREMENT_DISPLAY(ptr, nb_items); for (i = 0; i < api->match_icmp_code_num; i++) ptr += sprintf_bgp_pbr_match_val(ptr, &api->icmp_code[i], i > 0 ? NULL : "@icmpcode "); if (api->match_packet_length_num) INCREMENT_DISPLAY(ptr, nb_items); for (i = 0; i < api->match_packet_length_num; i++) ptr += sprintf_bgp_pbr_match_val(ptr, &api->packet_length[i], i > 0 ? NULL : "@plen "); if (api->match_dscp_num) INCREMENT_DISPLAY(ptr, nb_items); for (i = 0; i < api->match_dscp_num; i++) ptr += sprintf_bgp_pbr_match_val(ptr, &api->dscp[i], i > 0 ? NULL : "@dscp "); if (api->match_tcpflags_num) INCREMENT_DISPLAY(ptr, nb_items); for (i = 0; i < api->match_tcpflags_num; i++) ptr += sprintf_bgp_pbr_match_val(ptr, &api->tcpflags[i], i > 0 ? NULL : "@tcpflags "); if (api->match_fragment_num) INCREMENT_DISPLAY(ptr, nb_items); for (i = 0; i < api->match_fragment_num; i++) ptr += sprintf_bgp_pbr_match_val(ptr, &api->fragment[i], i > 0 ? NULL : "@fragment "); if (!nb_items) ptr = return_string; else ptr += sprintf(ptr, "; "); if (api->action_num) ptr += sprintf(ptr, "SET : "); nb_items = 0; for (i = 0; i < api->action_num; i++) { switch (api->actions[i].action) { case ACTION_TRAFFICRATE: INCREMENT_DISPLAY(ptr, nb_items); ptr += sprintf(ptr, "@set rate %f", api->actions[i].u.r.rate); break; case ACTION_TRAFFIC_ACTION: INCREMENT_DISPLAY(ptr, nb_items); ptr += sprintf(ptr, "@action "); if (api->actions[i].u.za.filter & TRAFFIC_ACTION_TERMINATE) ptr += sprintf(ptr, " terminate (apply filter(s))"); if (api->actions[i].u.za.filter & TRAFFIC_ACTION_DISTRIBUTE) ptr += sprintf(ptr, " distribute"); if (api->actions[i].u.za.filter & TRAFFIC_ACTION_SAMPLE) ptr += sprintf(ptr, " sample"); break; case ACTION_REDIRECT_IP: INCREMENT_DISPLAY(ptr, nb_items); char local_buff[INET_ADDRSTRLEN]; if (inet_ntop(AF_INET, &api->actions[i].u.zr.redirect_ip_v4, local_buff, INET_ADDRSTRLEN) != NULL) ptr += sprintf(ptr, "@redirect ip nh %s", local_buff); break; case ACTION_REDIRECT: INCREMENT_DISPLAY(ptr, nb_items); ptr += sprintf(ptr, "@redirect vrf %u", api->actions[i].u.redirect_vrf); break; case ACTION_MARKING: INCREMENT_DISPLAY(ptr, nb_items); ptr += sprintf(ptr, "@set dscp %u", api->actions[i].u.marking_dscp); break; default: break; } } zlog_info("%s", return_string); } static void bgp_pbr_flush_iprule(struct bgp *bgp, struct bgp_pbr_action *bpa, struct bgp_pbr_rule *bpr) { /* if bpr is null, do nothing */ if (bpr == NULL) return; if (bpr->installed) { bgp_send_pbr_rule_action(bpa, bpr, false); bpr->installed = false; bpr->action->refcnt--; bpr->action = NULL; if (bpr->path) { struct bgp_path_info *path; struct bgp_path_info_extra *extra; /* unlink path to bpme */ path = (struct bgp_path_info *)bpr->path; extra = bgp_path_info_extra_get(path); if (extra->bgp_fs_iprule) listnode_delete(extra->bgp_fs_iprule, bpr); bpr->path = NULL; } } hash_release(bgp->pbr_rule_hash, bpr); if (bpa->refcnt == 0) { if (bpa->installed && bpa->table_id != 0) { bgp_send_pbr_rule_action(bpa, NULL, false); bgp_zebra_announce_default(bpa->bgp, &(bpa->nh), AFI_IP, bpa->table_id, false); bpa->installed = false; } } } static void bgp_pbr_flush_entry(struct bgp *bgp, struct bgp_pbr_action *bpa, struct bgp_pbr_match *bpm, struct bgp_pbr_match_entry *bpme) { /* if bpme is null, bpm is also null */ if (bpme == NULL) return; /* ipset del entry */ if (bpme->installed) { bgp_send_pbr_ipset_entry_match(bpme, false); bpme->installed = false; bpme->backpointer = NULL; if (bpme->path) { struct bgp_path_info *path; struct bgp_path_info_extra *extra; /* unlink path to bpme */ path = (struct bgp_path_info *)bpme->path; extra = bgp_path_info_extra_get(path); if (extra->bgp_fs_pbr) listnode_delete(extra->bgp_fs_pbr, bpme); bpme->path = NULL; } } hash_release(bpm->entry_hash, bpme); if (hashcount(bpm->entry_hash) == 0) { /* delete iptable entry first */ /* then delete ipset match */ if (bpm->installed) { if (bpm->installed_in_iptable) { bgp_send_pbr_iptable(bpm->action, bpm, false); bpm->installed_in_iptable = false; bpm->action->refcnt--; } bgp_send_pbr_ipset_match(bpm, false); bpm->installed = false; bpm->action = NULL; } hash_release(bgp->pbr_match_hash, bpm); /* XXX release pbr_match_action if not used * note that drop does not need to call send_pbr_action */ } if (bpa->refcnt == 0) { if (bpa->installed && bpa->table_id != 0) { bgp_send_pbr_rule_action(bpa, NULL, false); bgp_zebra_announce_default(bpa->bgp, &(bpa->nh), AFI_IP, bpa->table_id, false); bpa->installed = false; } } } struct bgp_pbr_match_entry_remain { struct bgp_pbr_match_entry *bpme_to_match; struct bgp_pbr_match_entry *bpme_found; }; struct bgp_pbr_rule_remain { struct bgp_pbr_rule *bpr_to_match; struct bgp_pbr_rule *bpr_found; }; static int bgp_pbr_get_same_rule(struct hash_bucket *bucket, void *arg) { struct bgp_pbr_rule *r1 = (struct bgp_pbr_rule *)bucket->data; struct bgp_pbr_rule_remain *ctxt = (struct bgp_pbr_rule_remain *)arg; struct bgp_pbr_rule *r2; r2 = ctxt->bpr_to_match; if (r1->vrf_id != r2->vrf_id) return HASHWALK_CONTINUE; if (r1->flags != r2->flags) return HASHWALK_CONTINUE; if ((r1->flags & MATCH_IP_SRC_SET) && !prefix_same(&r1->src, &r2->src)) return HASHWALK_CONTINUE; if ((r1->flags & MATCH_IP_DST_SET) && !prefix_same(&r1->dst, &r2->dst)) return HASHWALK_CONTINUE; /* this function is used for two cases: * - remove an entry upon withdraw request * (case r2->action is null) * - replace an old iprule with different action * (case r2->action is != null) * the old one is removed after the new one * this is to avoid disruption in traffic */ if (r2->action == NULL || r1->action != r2->action) { ctxt->bpr_found = r1; return HASHWALK_ABORT; } return HASHWALK_CONTINUE; } static int bgp_pbr_get_remaining_entry(struct hash_bucket *bucket, void *arg) { struct bgp_pbr_match *bpm = (struct bgp_pbr_match *)bucket->data; struct bgp_pbr_match_entry_remain *bpmer = (struct bgp_pbr_match_entry_remain *)arg; struct bgp_pbr_match *bpm_temp; struct bgp_pbr_match_entry *bpme = bpmer->bpme_to_match; if (!bpme->backpointer || bpm == bpme->backpointer || bpme->backpointer->action == bpm->action) return HASHWALK_CONTINUE; /* ensure bpm other characteristics are equal */ bpm_temp = bpme->backpointer; if (bpm_temp->vrf_id != bpm->vrf_id || bpm_temp->type != bpm->type || bpm_temp->flags != bpm->flags || bpm_temp->tcp_flags != bpm->tcp_flags || bpm_temp->tcp_mask_flags != bpm->tcp_mask_flags || bpm_temp->pkt_len_min != bpm->pkt_len_min || bpm_temp->pkt_len_max != bpm->pkt_len_max || bpm_temp->dscp_value != bpm->dscp_value || bpm_temp->fragment != bpm->fragment) return HASHWALK_CONTINUE; /* look for remaining bpme */ bpmer->bpme_found = hash_lookup(bpm->entry_hash, bpme); if (!bpmer->bpme_found) return HASHWALK_CONTINUE; return HASHWALK_ABORT; } static void bgp_pbr_policyroute_remove_from_zebra_unit( struct bgp *bgp, struct bgp_path_info *path, struct bgp_pbr_filter *bpf) { struct bgp_pbr_match temp; struct bgp_pbr_match_entry temp2; struct bgp_pbr_rule pbr_rule; struct bgp_pbr_rule *bpr; struct bgp_pbr_match *bpm; struct bgp_pbr_match_entry *bpme; struct bgp_pbr_match_entry_remain bpmer; struct bgp_pbr_range_port *src_port; struct bgp_pbr_range_port *dst_port; struct bgp_pbr_range_port *pkt_len; struct bgp_pbr_rule_remain bprr; if (!bpf) return; src_port = bpf->src_port; dst_port = bpf->dst_port; pkt_len = bpf->pkt_len; if (BGP_DEBUG(zebra, ZEBRA)) bgp_pbr_dump_entry(bpf, false); /* as we don't know information from EC * look for bpm that have the bpm * with vrf_id characteristics */ memset(&temp2, 0, sizeof(temp2)); memset(&temp, 0, sizeof(temp)); if (bpf->type == BGP_PBR_IPRULE) { memset(&pbr_rule, 0, sizeof(pbr_rule)); pbr_rule.vrf_id = bpf->vrf_id; if (bpf->src) { prefix_copy(&pbr_rule.src, bpf->src); pbr_rule.flags |= MATCH_IP_SRC_SET; } if (bpf->dst) { prefix_copy(&pbr_rule.dst, bpf->dst); pbr_rule.flags |= MATCH_IP_DST_SET; } bpr = &pbr_rule; /* A previous entry may already exist * flush previous entry if necessary */ bprr.bpr_to_match = bpr; bprr.bpr_found = NULL; hash_walk(bgp->pbr_rule_hash, bgp_pbr_get_same_rule, &bprr); if (bprr.bpr_found) { static struct bgp_pbr_rule *local_bpr; static struct bgp_pbr_action *local_bpa; local_bpr = bprr.bpr_found; local_bpa = local_bpr->action; bgp_pbr_flush_iprule(bgp, local_bpa, local_bpr); } return; } if (bpf->src) { temp.flags |= MATCH_IP_SRC_SET; prefix_copy(&temp2.src, bpf->src); } else temp2.src.family = AF_INET; if (bpf->dst) { temp.flags |= MATCH_IP_DST_SET; prefix_copy(&temp2.dst, bpf->dst); } else temp2.dst.family = AF_INET; if (src_port && (src_port->min_port || bpf->protocol == IPPROTO_ICMP)) { if (bpf->protocol == IPPROTO_ICMP) temp.flags |= MATCH_ICMP_SET; temp.flags |= MATCH_PORT_SRC_SET; temp2.src_port_min = src_port->min_port; if (src_port->max_port) { temp.flags |= MATCH_PORT_SRC_RANGE_SET; temp2.src_port_max = src_port->max_port; } } if (dst_port && (dst_port->min_port || bpf->protocol == IPPROTO_ICMP)) { if (bpf->protocol == IPPROTO_ICMP) temp.flags |= MATCH_ICMP_SET; temp.flags |= MATCH_PORT_DST_SET; temp2.dst_port_min = dst_port->min_port; if (dst_port->max_port) { temp.flags |= MATCH_PORT_DST_RANGE_SET; temp2.dst_port_max = dst_port->max_port; } } temp2.proto = bpf->protocol; if (pkt_len) { temp.pkt_len_min = pkt_len->min_port; if (pkt_len->max_port) temp.pkt_len_max = pkt_len->max_port; } else if (bpf->pkt_len_val) { if (bpf->pkt_len_val->mask) temp.flags |= MATCH_PKT_LEN_INVERSE_SET; temp.pkt_len_min = bpf->pkt_len_val->val; } if (bpf->tcp_flags) { temp.tcp_flags = bpf->tcp_flags->val; temp.tcp_mask_flags = bpf->tcp_flags->mask; } if (bpf->dscp) { if (bpf->dscp->mask) temp.flags |= MATCH_DSCP_INVERSE_SET; else temp.flags |= MATCH_DSCP_SET; temp.dscp_value = bpf->dscp->val; } if (bpf->fragment) { if (bpf->fragment->mask) temp.flags |= MATCH_FRAGMENT_INVERSE_SET; temp.fragment = bpf->fragment->val; } if (bpf->src == NULL || bpf->dst == NULL) { if (temp.flags & (MATCH_PORT_DST_SET | MATCH_PORT_SRC_SET)) temp.type = IPSET_NET_PORT; else temp.type = IPSET_NET; } else { if (temp.flags & (MATCH_PORT_DST_SET | MATCH_PORT_SRC_SET)) temp.type = IPSET_NET_PORT_NET; else temp.type = IPSET_NET_NET; } if (bpf->vrf_id == VRF_UNKNOWN) /* XXX case BGP destroy */ temp.vrf_id = 0; else temp.vrf_id = bpf->vrf_id; bpme = &temp2; bpm = &temp; bpme->backpointer = bpm; /* right now, a previous entry may already exist * flush previous entry if necessary */ bpmer.bpme_to_match = bpme; bpmer.bpme_found = NULL; hash_walk(bgp->pbr_match_hash, bgp_pbr_get_remaining_entry, &bpmer); if (bpmer.bpme_found) { static struct bgp_pbr_match *local_bpm; static struct bgp_pbr_action *local_bpa; local_bpm = bpmer.bpme_found->backpointer; local_bpa = local_bpm->action; bgp_pbr_flush_entry(bgp, local_bpa, local_bpm, bpmer.bpme_found); } } static uint8_t bgp_pbr_next_type_entry(uint8_t type_entry) { if (type_entry == FLOWSPEC_TCP_FLAGS) return FLOWSPEC_DSCP; if (type_entry == FLOWSPEC_DSCP) return FLOWSPEC_PKT_LEN; if (type_entry == FLOWSPEC_PKT_LEN) return FLOWSPEC_FRAGMENT; if (type_entry == FLOWSPEC_FRAGMENT) return FLOWSPEC_ICMP_TYPE; return 0; } static void bgp_pbr_icmp_action(struct bgp *bgp, struct bgp_path_info *path, struct bgp_pbr_filter *bpf, struct bgp_pbr_or_filter *bpof, bool add, struct nexthop *nh, float *rate) { struct bgp_pbr_range_port srcp, dstp; struct bgp_pbr_val_mask *icmp_type, *icmp_code; struct listnode *tnode, *cnode; if (!bpf) return; if (bpf->protocol != IPPROTO_ICMP) return; bpf->src_port = &srcp; bpf->dst_port = &dstp; /* parse icmp type and lookup appropriate icmp code * if no icmp code found, create as many entryes as * there are listed icmp codes for that icmp type */ if (!bpof->icmp_type) { srcp.min_port = 0; srcp.max_port = 255; for (ALL_LIST_ELEMENTS_RO(bpof->icmp_code, cnode, icmp_code)) { dstp.min_port = icmp_code->val; if (add) bgp_pbr_policyroute_add_to_zebra_unit( bgp, path, bpf, nh, rate); else bgp_pbr_policyroute_remove_from_zebra_unit( bgp, path, bpf); } return; } for (ALL_LIST_ELEMENTS_RO(bpof->icmp_type, tnode, icmp_type)) { srcp.min_port = icmp_type->val; srcp.max_port = 0; dstp.max_port = 0; /* only icmp type. create an entry only with icmp type */ if (!bpof->icmp_code) { /* icmp type is not one of the above * forge an entry only based on the icmp type */ dstp.min_port = 0; dstp.max_port = 255; if (add) bgp_pbr_policyroute_add_to_zebra_unit( bgp, path, bpf, nh, rate); else bgp_pbr_policyroute_remove_from_zebra_unit( bgp, path, bpf); continue; } for (ALL_LIST_ELEMENTS_RO(bpof->icmp_code, cnode, icmp_code)) { dstp.min_port = icmp_code->val; if (add) bgp_pbr_policyroute_add_to_zebra_unit( bgp, path, bpf, nh, rate); else bgp_pbr_policyroute_remove_from_zebra_unit( bgp, path, bpf); } } } static void bgp_pbr_policyroute_remove_from_zebra_recursive( struct bgp *bgp, struct bgp_path_info *path, struct bgp_pbr_filter *bpf, struct bgp_pbr_or_filter *bpof, uint8_t type_entry) { struct listnode *node, *nnode; struct bgp_pbr_val_mask *valmask; uint8_t next_type_entry; struct list *orig_list; struct bgp_pbr_val_mask **target_val; if (type_entry == 0) { bgp_pbr_policyroute_remove_from_zebra_unit(bgp, path, bpf); return; } next_type_entry = bgp_pbr_next_type_entry(type_entry); if (type_entry == FLOWSPEC_TCP_FLAGS && bpof->tcpflags) { orig_list = bpof->tcpflags; target_val = &bpf->tcp_flags; } else if (type_entry == FLOWSPEC_DSCP && bpof->dscp) { orig_list = bpof->dscp; target_val = &bpf->dscp; } else if (type_entry == FLOWSPEC_PKT_LEN && bpof->pkt_len) { orig_list = bpof->pkt_len; target_val = &bpf->pkt_len_val; } else if (type_entry == FLOWSPEC_FRAGMENT && bpof->fragment) { orig_list = bpof->fragment; target_val = &bpf->fragment; } else if (type_entry == FLOWSPEC_ICMP_TYPE && (bpof->icmp_type || bpof->icmp_code)) { /* enumerate list for icmp - must be last one */ bgp_pbr_icmp_action(bgp, path, bpf, bpof, false, NULL, NULL); return; } else { bgp_pbr_policyroute_remove_from_zebra_recursive( bgp, path, bpf, bpof, next_type_entry); return; } for (ALL_LIST_ELEMENTS(orig_list, node, nnode, valmask)) { *target_val = valmask; bgp_pbr_policyroute_remove_from_zebra_recursive( bgp, path, bpf, bpof, next_type_entry); } } static void bgp_pbr_policyroute_remove_from_zebra( struct bgp *bgp, struct bgp_path_info *path, struct bgp_pbr_filter *bpf, struct bgp_pbr_or_filter *bpof) { if (!bpof) { bgp_pbr_policyroute_remove_from_zebra_unit(bgp, path, bpf); return; } if (bpof->tcpflags) bgp_pbr_policyroute_remove_from_zebra_recursive( bgp, path, bpf, bpof, FLOWSPEC_TCP_FLAGS); else if (bpof->dscp) bgp_pbr_policyroute_remove_from_zebra_recursive( bgp, path, bpf, bpof, FLOWSPEC_DSCP); else if (bpof->pkt_len) bgp_pbr_policyroute_remove_from_zebra_recursive( bgp, path, bpf, bpof, FLOWSPEC_PKT_LEN); else if (bpof->fragment) bgp_pbr_policyroute_remove_from_zebra_recursive( bgp, path, bpf, bpof, FLOWSPEC_FRAGMENT); else if (bpof->icmp_type || bpof->icmp_code) bgp_pbr_policyroute_remove_from_zebra_recursive( bgp, path, bpf, bpof, FLOWSPEC_ICMP_TYPE); else bgp_pbr_policyroute_remove_from_zebra_unit(bgp, path, bpf); /* flush bpof */ if (bpof->tcpflags) list_delete_all_node(bpof->tcpflags); if (bpof->dscp) list_delete_all_node(bpof->dscp); if (bpof->pkt_len) list_delete_all_node(bpof->pkt_len); if (bpof->fragment) list_delete_all_node(bpof->fragment); } static void bgp_pbr_dump_entry(struct bgp_pbr_filter *bpf, bool add) { struct bgp_pbr_range_port *src_port; struct bgp_pbr_range_port *dst_port; struct bgp_pbr_range_port *pkt_len; char bufsrc[64], bufdst[64]; char buffer[64]; int remaining_len = 0; char protocol_str[16]; if (!bpf) return; src_port = bpf->src_port; dst_port = bpf->dst_port; pkt_len = bpf->pkt_len; protocol_str[0] = '\0'; if (bpf->tcp_flags && bpf->tcp_flags->mask) bpf->protocol = IPPROTO_TCP; if (bpf->protocol) snprintf(protocol_str, sizeof(protocol_str), "proto %d", bpf->protocol); buffer[0] = '\0'; if (bpf->protocol == IPPROTO_ICMP && src_port && dst_port) remaining_len += snprintf(buffer, sizeof(buffer), "type %d, code %d", src_port->min_port, dst_port->min_port); else if (bpf->protocol == IPPROTO_UDP || bpf->protocol == IPPROTO_TCP) { if (src_port && src_port->min_port) remaining_len += snprintf(buffer, sizeof(buffer), "from [%u:%u]", src_port->min_port, src_port->max_port ? src_port->max_port : src_port->min_port); if (dst_port && dst_port->min_port) remaining_len += snprintf(buffer + remaining_len, sizeof(buffer) - remaining_len, "to [%u:%u]", dst_port->min_port, dst_port->max_port ? dst_port->max_port : dst_port->min_port); } if (pkt_len && (pkt_len->min_port || pkt_len->max_port)) { remaining_len += snprintf(buffer + remaining_len, sizeof(buffer) - remaining_len, " len [%u:%u]", pkt_len->min_port, pkt_len->max_port ? pkt_len->max_port : pkt_len->min_port); } else if (bpf->pkt_len_val) { remaining_len += snprintf(buffer + remaining_len, sizeof(buffer) - remaining_len, " %s len %u", bpf->pkt_len_val->mask ? "!" : "", bpf->pkt_len_val->val); } if (bpf->tcp_flags) { remaining_len += snprintf(buffer + remaining_len, sizeof(buffer) - remaining_len, "tcpflags %x/%x", bpf->tcp_flags->val, bpf->tcp_flags->mask); } if (bpf->dscp) { snprintf(buffer + remaining_len, sizeof(buffer) - remaining_len, "%s dscp %d", bpf->dscp->mask ? "!" : "", bpf->dscp->val); } zlog_debug("BGP: %s FS PBR from %s to %s, %s %s", add ? "adding" : "removing", bpf->src == NULL ? "" : prefix2str(bpf->src, bufsrc, sizeof(bufsrc)), bpf->dst == NULL ? "" : prefix2str(bpf->dst, bufdst, sizeof(bufdst)), protocol_str, buffer); } static void bgp_pbr_policyroute_add_to_zebra_unit(struct bgp *bgp, struct bgp_path_info *path, struct bgp_pbr_filter *bpf, struct nexthop *nh, float *rate) { struct bgp_pbr_match temp; struct bgp_pbr_match_entry temp2; struct bgp_pbr_match *bpm; struct bgp_pbr_match_entry *bpme = NULL; struct bgp_pbr_action temp3; struct bgp_pbr_action *bpa = NULL; struct bgp_pbr_match_entry_remain bpmer; struct bgp_pbr_rule_remain bprr; struct bgp_pbr_range_port *src_port; struct bgp_pbr_range_port *dst_port; struct bgp_pbr_range_port *pkt_len; struct bgp_pbr_rule pbr_rule; struct bgp_pbr_rule *bpr; bool bpr_found = false; bool bpme_found = false; if (!bpf) return; src_port = bpf->src_port; dst_port = bpf->dst_port; pkt_len = bpf->pkt_len; if (BGP_DEBUG(zebra, ZEBRA)) bgp_pbr_dump_entry(bpf, true); /* look for bpa first */ memset(&temp3, 0, sizeof(temp3)); if (rate) temp3.rate = *rate; if (nh) memcpy(&temp3.nh, nh, sizeof(struct nexthop)); temp3.vrf_id = bpf->vrf_id; bpa = hash_get(bgp->pbr_action_hash, &temp3, bgp_pbr_action_alloc_intern); if (bpa->fwmark == 0) { /* drop is handled by iptable */ if (nh && nh->type == NEXTHOP_TYPE_BLACKHOLE) { bpa->table_id = 0; bpa->installed = true; } else { bpa->fwmark = bgp_zebra_tm_get_id(); bpa->table_id = bpa->fwmark; bpa->installed = false; } bpa->bgp = bgp; bpa->unique = ++bgp_pbr_action_counter_unique; /* 0 value is forbidden */ bpa->install_in_progress = false; } if (bpf->type == BGP_PBR_IPRULE) { memset(&pbr_rule, 0, sizeof(pbr_rule)); pbr_rule.vrf_id = bpf->vrf_id; pbr_rule.priority = 20; if (bpf->src) { pbr_rule.flags |= MATCH_IP_SRC_SET; prefix_copy(&pbr_rule.src, bpf->src); } if (bpf->dst) { pbr_rule.flags |= MATCH_IP_DST_SET; prefix_copy(&pbr_rule.dst, bpf->dst); } pbr_rule.action = bpa; bpr = hash_get(bgp->pbr_rule_hash, &pbr_rule, bgp_pbr_rule_alloc_intern); if (bpr && bpr->unique == 0) { bpr->unique = ++bgp_pbr_action_counter_unique; bpr->installed = false; bpr->install_in_progress = false; /* link bgp info to bpr */ bpr->path = (void *)path; } else bpr_found = true; /* already installed */ if (bpr_found && bpr) { struct bgp_path_info_extra *extra = bgp_path_info_extra_get(path); if (extra && listnode_lookup_nocheck(extra->bgp_fs_iprule, bpr)) { if (BGP_DEBUG(pbr, PBR_ERROR)) zlog_err("%s: entry %p/%p already " "installed in bgp pbr iprule", __func__, path, bpr); return; } } if (!bpa->installed && !bpa->install_in_progress) { bgp_send_pbr_rule_action(bpa, NULL, true); bgp_zebra_announce_default(bgp, nh, AFI_IP, bpa->table_id, true); } /* ip rule add */ if (bpr && !bpr->installed) bgp_send_pbr_rule_action(bpa, bpr, true); /* A previous entry may already exist * flush previous entry if necessary */ bprr.bpr_to_match = bpr; bprr.bpr_found = NULL; hash_walk(bgp->pbr_rule_hash, bgp_pbr_get_same_rule, &bprr); if (bprr.bpr_found) { static struct bgp_pbr_rule *local_bpr; static struct bgp_pbr_action *local_bpa; local_bpr = bprr.bpr_found; local_bpa = local_bpr->action; bgp_pbr_flush_iprule(bgp, local_bpa, local_bpr); } return; } /* then look for bpm */ memset(&temp, 0, sizeof(temp)); temp.vrf_id = bpf->vrf_id; if (bpf->src) temp.flags |= MATCH_IP_SRC_SET; if (bpf->dst) temp.flags |= MATCH_IP_DST_SET; if (src_port && (src_port->min_port || bpf->protocol == IPPROTO_ICMP)) { if (bpf->protocol == IPPROTO_ICMP) temp.flags |= MATCH_ICMP_SET; temp.flags |= MATCH_PORT_SRC_SET; } if (dst_port && (dst_port->min_port || bpf->protocol == IPPROTO_ICMP)) { if (bpf->protocol == IPPROTO_ICMP) temp.flags |= MATCH_ICMP_SET; temp.flags |= MATCH_PORT_DST_SET; } if (src_port && src_port->max_port) temp.flags |= MATCH_PORT_SRC_RANGE_SET; if (dst_port && dst_port->max_port) temp.flags |= MATCH_PORT_DST_RANGE_SET; if (bpf->src == NULL || bpf->dst == NULL) { if (temp.flags & (MATCH_PORT_DST_SET | MATCH_PORT_SRC_SET)) temp.type = IPSET_NET_PORT; else temp.type = IPSET_NET; } else { if (temp.flags & (MATCH_PORT_DST_SET | MATCH_PORT_SRC_SET)) temp.type = IPSET_NET_PORT_NET; else temp.type = IPSET_NET_NET; } if (pkt_len) { temp.pkt_len_min = pkt_len->min_port; if (pkt_len->max_port) temp.pkt_len_max = pkt_len->max_port; } else if (bpf->pkt_len_val) { if (bpf->pkt_len_val->mask) temp.flags |= MATCH_PKT_LEN_INVERSE_SET; temp.pkt_len_min = bpf->pkt_len_val->val; } if (bpf->tcp_flags) { temp.tcp_flags = bpf->tcp_flags->val; temp.tcp_mask_flags = bpf->tcp_flags->mask; } if (bpf->dscp) { if (bpf->dscp->mask) temp.flags |= MATCH_DSCP_INVERSE_SET; else temp.flags |= MATCH_DSCP_SET; temp.dscp_value = bpf->dscp->val; } if (bpf->fragment) { if (bpf->fragment->mask) temp.flags |= MATCH_FRAGMENT_INVERSE_SET; temp.fragment = bpf->fragment->val; } if (bpf->protocol) { temp.protocol = bpf->protocol; temp.flags |= MATCH_PROTOCOL_SET; } temp.action = bpa; bpm = hash_get(bgp->pbr_match_hash, &temp, bgp_pbr_match_alloc_intern); /* new, then self allocate ipset_name and unique */ if (bpm->unique == 0) { bpm->unique = ++bgp_pbr_match_counter_unique; /* 0 value is forbidden */ sprintf(bpm->ipset_name, "match%p", bpm); bpm->entry_hash = hash_create_size(8, bgp_pbr_match_entry_hash_key, bgp_pbr_match_entry_hash_equal, "Match Entry Hash"); bpm->installed = false; /* unique2 should be updated too */ bpm->unique2 = ++bgp_pbr_match_iptable_counter_unique; bpm->installed_in_iptable = false; bpm->install_in_progress = false; bpm->install_iptable_in_progress = false; } memset(&temp2, 0, sizeof(temp2)); if (bpf->src) prefix_copy(&temp2.src, bpf->src); else temp2.src.family = AF_INET; if (bpf->dst) prefix_copy(&temp2.dst, bpf->dst); else temp2.dst.family = AF_INET; temp2.src_port_min = src_port ? src_port->min_port : 0; temp2.dst_port_min = dst_port ? dst_port->min_port : 0; temp2.src_port_max = src_port ? src_port->max_port : 0; temp2.dst_port_max = dst_port ? dst_port->max_port : 0; temp2.proto = bpf->protocol; bpme = hash_get(bpm->entry_hash, &temp2, bgp_pbr_match_entry_alloc_intern); if (bpme->unique == 0) { bpme->unique = ++bgp_pbr_match_entry_counter_unique; /* 0 value is forbidden */ bpme->backpointer = bpm; bpme->installed = false; bpme->install_in_progress = false; /* link bgp info to bpme */ bpme->path = (void *)path; } else bpme_found = true; /* already installed */ if (bpme_found) { struct bgp_path_info_extra *extra = bgp_path_info_extra_get(path); if (extra && listnode_lookup_nocheck(extra->bgp_fs_pbr, bpme)) { if (BGP_DEBUG(pbr, PBR_ERROR)) zlog_err( "%s: entry %p/%p already installed in bgp pbr", __func__, path, bpme); return; } } /* BGP FS: append entry to zebra * - policies are not routing entries and as such * route replace semantics don't necessarily follow * through to policy entries * - because of that, not all policing information will be stored * into zebra. and non selected policies will be suppressed from zebra * - as consequence, in order to bring consistency * a policy will be added, then ifan ecmp policy exists, * it will be suppressed subsequently */ /* ip rule add */ if (!bpa->installed && !bpa->install_in_progress) { bgp_send_pbr_rule_action(bpa, NULL, true); bgp_zebra_announce_default(bgp, nh, AFI_IP, bpa->table_id, true); } /* ipset create */ if (!bpm->installed) bgp_send_pbr_ipset_match(bpm, true); /* ipset add */ if (!bpme->installed) bgp_send_pbr_ipset_entry_match(bpme, true); /* iptables */ if (!bpm->installed_in_iptable) bgp_send_pbr_iptable(bpa, bpm, true); /* A previous entry may already exist * flush previous entry if necessary */ bpmer.bpme_to_match = bpme; bpmer.bpme_found = NULL; hash_walk(bgp->pbr_match_hash, bgp_pbr_get_remaining_entry, &bpmer); if (bpmer.bpme_found) { static struct bgp_pbr_match *local_bpm; static struct bgp_pbr_action *local_bpa; local_bpm = bpmer.bpme_found->backpointer; local_bpa = local_bpm->action; bgp_pbr_flush_entry(bgp, local_bpa, local_bpm, bpmer.bpme_found); } } static void bgp_pbr_policyroute_add_to_zebra_recursive( struct bgp *bgp, struct bgp_path_info *path, struct bgp_pbr_filter *bpf, struct bgp_pbr_or_filter *bpof, struct nexthop *nh, float *rate, uint8_t type_entry) { struct listnode *node, *nnode; struct bgp_pbr_val_mask *valmask; uint8_t next_type_entry; struct list *orig_list; struct bgp_pbr_val_mask **target_val; if (type_entry == 0) { bgp_pbr_policyroute_add_to_zebra_unit(bgp, path, bpf, nh, rate); return; } next_type_entry = bgp_pbr_next_type_entry(type_entry); if (type_entry == FLOWSPEC_TCP_FLAGS && bpof->tcpflags) { orig_list = bpof->tcpflags; target_val = &bpf->tcp_flags; } else if (type_entry == FLOWSPEC_DSCP && bpof->dscp) { orig_list = bpof->dscp; target_val = &bpf->dscp; } else if (type_entry == FLOWSPEC_PKT_LEN && bpof->pkt_len) { orig_list = bpof->pkt_len; target_val = &bpf->pkt_len_val; } else if (type_entry == FLOWSPEC_FRAGMENT && bpof->fragment) { orig_list = bpof->fragment; target_val = &bpf->fragment; } else if (type_entry == FLOWSPEC_ICMP_TYPE && (bpof->icmp_type || bpof->icmp_code)) { /* enumerate list for icmp - must be last one */ bgp_pbr_icmp_action(bgp, path, bpf, bpof, true, nh, rate); return; } else { bgp_pbr_policyroute_add_to_zebra_recursive( bgp, path, bpf, bpof, nh, rate, next_type_entry); return; } for (ALL_LIST_ELEMENTS(orig_list, node, nnode, valmask)) { *target_val = valmask; bgp_pbr_policyroute_add_to_zebra_recursive( bgp, path, bpf, bpof, nh, rate, next_type_entry); } } static void bgp_pbr_policyroute_add_to_zebra(struct bgp *bgp, struct bgp_path_info *path, struct bgp_pbr_filter *bpf, struct bgp_pbr_or_filter *bpof, struct nexthop *nh, float *rate) { if (!bpof) { bgp_pbr_policyroute_add_to_zebra_unit(bgp, path, bpf, nh, rate); return; } if (bpof->tcpflags) bgp_pbr_policyroute_add_to_zebra_recursive( bgp, path, bpf, bpof, nh, rate, FLOWSPEC_TCP_FLAGS); else if (bpof->dscp) bgp_pbr_policyroute_add_to_zebra_recursive( bgp, path, bpf, bpof, nh, rate, FLOWSPEC_DSCP); else if (bpof->pkt_len) bgp_pbr_policyroute_add_to_zebra_recursive( bgp, path, bpf, bpof, nh, rate, FLOWSPEC_PKT_LEN); else if (bpof->fragment) bgp_pbr_policyroute_add_to_zebra_recursive( bgp, path, bpf, bpof, nh, rate, FLOWSPEC_FRAGMENT); else if (bpof->icmp_type || bpof->icmp_code) bgp_pbr_policyroute_add_to_zebra_recursive( bgp, path, bpf, bpof, nh, rate, FLOWSPEC_ICMP_TYPE); else bgp_pbr_policyroute_add_to_zebra_unit(bgp, path, bpf, nh, rate); /* flush bpof */ if (bpof->tcpflags) list_delete_all_node(bpof->tcpflags); if (bpof->dscp) list_delete_all_node(bpof->dscp); if (bpof->pkt_len) list_delete_all_node(bpof->pkt_len); if (bpof->fragment) list_delete_all_node(bpof->fragment); if (bpof->icmp_type) list_delete_all_node(bpof->icmp_type); if (bpof->icmp_code) list_delete_all_node(bpof->icmp_code); } static void bgp_pbr_handle_entry(struct bgp *bgp, struct bgp_path_info *path, struct bgp_pbr_entry_main *api, bool add) { struct nexthop nh; int i = 0; int continue_loop = 1; float rate = 0; struct prefix *src = NULL, *dst = NULL; uint8_t proto = 0; struct bgp_pbr_range_port *srcp = NULL, *dstp = NULL; struct bgp_pbr_range_port range, range_icmp_code; struct bgp_pbr_range_port pkt_len; struct bgp_pbr_filter bpf; uint8_t kind_enum; struct bgp_pbr_or_filter bpof; struct bgp_pbr_val_mask bpvm; memset(&nh, 0, sizeof(struct nexthop)); memset(&bpf, 0, sizeof(struct bgp_pbr_filter)); memset(&bpof, 0, sizeof(struct bgp_pbr_or_filter)); if (api->match_bitmask & PREFIX_SRC_PRESENT || (api->type == BGP_PBR_IPRULE && api->match_bitmask_iprule & PREFIX_SRC_PRESENT)) src = &api->src_prefix; if (api->match_bitmask & PREFIX_DST_PRESENT || (api->type == BGP_PBR_IPRULE && api->match_bitmask_iprule & PREFIX_DST_PRESENT)) dst = &api->dst_prefix; if (api->type == BGP_PBR_IPRULE) bpf.type = api->type; memset(&nh, 0, sizeof(struct nexthop)); nh.vrf_id = VRF_UNKNOWN; if (api->match_protocol_num) proto = (uint8_t)api->protocol[0].value; /* if match_port is selected, then either src or dst port will be parsed * but not both at the same time */ if (api->match_port_num >= 1) { bgp_pbr_extract(api->port, api->match_port_num, &range); srcp = dstp = ⦥ } else if (api->match_src_port_num >= 1) { bgp_pbr_extract(api->src_port, api->match_src_port_num, &range); srcp = ⦥ dstp = NULL; } else if (api->match_dst_port_num >= 1) { bgp_pbr_extract(api->dst_port, api->match_dst_port_num, &range); dstp = ⦥ srcp = NULL; } if (api->match_icmp_type_num >= 1) { proto = IPPROTO_ICMP; if (bgp_pbr_extract(api->icmp_type, api->match_icmp_type_num, &range)) srcp = ⦥ else { bpof.icmp_type = list_new(); bgp_pbr_extract_enumerate(api->icmp_type, api->match_icmp_type_num, OPERATOR_UNARY_OR, bpof.icmp_type, FLOWSPEC_ICMP_TYPE); } } if (api->match_icmp_code_num >= 1) { proto = IPPROTO_ICMP; if (bgp_pbr_extract(api->icmp_code, api->match_icmp_code_num, &range_icmp_code)) dstp = &range_icmp_code; else { bpof.icmp_code = list_new(); bgp_pbr_extract_enumerate(api->icmp_code, api->match_icmp_code_num, OPERATOR_UNARY_OR, bpof.icmp_code, FLOWSPEC_ICMP_CODE); } } if (api->match_tcpflags_num) { kind_enum = bgp_pbr_match_val_get_operator(api->tcpflags, api->match_tcpflags_num); if (kind_enum == OPERATOR_UNARY_AND) { bpf.tcp_flags = &bpvm; bgp_pbr_extract_enumerate(api->tcpflags, api->match_tcpflags_num, OPERATOR_UNARY_AND, bpf.tcp_flags, FLOWSPEC_TCP_FLAGS); } else if (kind_enum == OPERATOR_UNARY_OR) { bpof.tcpflags = list_new(); bgp_pbr_extract_enumerate(api->tcpflags, api->match_tcpflags_num, OPERATOR_UNARY_OR, bpof.tcpflags, FLOWSPEC_TCP_FLAGS); } } if (api->match_packet_length_num) { bool ret; ret = bgp_pbr_extract(api->packet_length, api->match_packet_length_num, &pkt_len); if (ret) bpf.pkt_len = &pkt_len; else { bpof.pkt_len = list_new(); bgp_pbr_extract_enumerate(api->packet_length, api->match_packet_length_num, OPERATOR_UNARY_OR, bpof.pkt_len, FLOWSPEC_PKT_LEN); } } if (api->match_dscp_num >= 1) { bpof.dscp = list_new(); bgp_pbr_extract_enumerate(api->dscp, api->match_dscp_num, OPERATOR_UNARY_OR, bpof.dscp, FLOWSPEC_DSCP); } if (api->match_fragment_num) { bpof.fragment = list_new(); bgp_pbr_extract_enumerate(api->fragment, api->match_fragment_num, OPERATOR_UNARY_OR, bpof.fragment, FLOWSPEC_FRAGMENT); } bpf.vrf_id = api->vrf_id; bpf.src = src; bpf.dst = dst; bpf.protocol = proto; bpf.src_port = srcp; bpf.dst_port = dstp; if (!add) { bgp_pbr_policyroute_remove_from_zebra(bgp, path, &bpf, &bpof); return; } /* no action for add = true */ for (i = 0; i < api->action_num; i++) { switch (api->actions[i].action) { case ACTION_TRAFFICRATE: /* drop packet */ if (api->actions[i].u.r.rate == 0) { nh.vrf_id = api->vrf_id; nh.type = NEXTHOP_TYPE_BLACKHOLE; bgp_pbr_policyroute_add_to_zebra( bgp, path, &bpf, &bpof, &nh, &rate); } else { /* update rate. can be reentrant */ rate = api->actions[i].u.r.rate; if (BGP_DEBUG(pbr, PBR)) { bgp_pbr_print_policy_route(api); zlog_warn("PBR: ignoring Set action rate %f", api->actions[i].u.r.rate); } } break; case ACTION_TRAFFIC_ACTION: if (api->actions[i].u.za.filter & TRAFFIC_ACTION_SAMPLE) { if (BGP_DEBUG(pbr, PBR)) { bgp_pbr_print_policy_route(api); zlog_warn("PBR: Sample action Ignored"); } } #if 0 if (api->actions[i].u.za.filter & TRAFFIC_ACTION_DISTRIBUTE) { if (BGP_DEBUG(pbr, PBR)) { bgp_pbr_print_policy_route(api); zlog_warn("PBR: Distribute action Applies"); } continue_loop = 0; /* continue forwarding entry as before * no action */ } #endif /* XXX to confirm behaviour of traffic action. for now , ignore */ /* terminate action: run other filters */ break; case ACTION_REDIRECT_IP: nh.type = NEXTHOP_TYPE_IPV4; nh.gate.ipv4.s_addr = api->actions[i].u.zr.redirect_ip_v4.s_addr; nh.vrf_id = api->vrf_id; bgp_pbr_policyroute_add_to_zebra(bgp, path, &bpf, &bpof, &nh, &rate); /* XXX combination with REDIRECT_VRF * + REDIRECT_NH_IP not done */ continue_loop = 0; break; case ACTION_REDIRECT: nh.vrf_id = api->actions[i].u.redirect_vrf; nh.type = NEXTHOP_TYPE_IPV4; bgp_pbr_policyroute_add_to_zebra(bgp, path, &bpf, &bpof, &nh, &rate); continue_loop = 0; break; case ACTION_MARKING: if (BGP_DEBUG(pbr, PBR)) { bgp_pbr_print_policy_route(api); zlog_warn("PBR: Set DSCP %u Ignored", api->actions[i].u.marking_dscp); } break; default: break; } if (continue_loop == 0) break; } } void bgp_pbr_update_entry(struct bgp *bgp, struct prefix *p, struct bgp_path_info *info, afi_t afi, safi_t safi, bool nlri_update) { struct bgp_pbr_entry_main api; if (afi == AFI_IP6) return; /* IPv6 not supported */ if (safi != SAFI_FLOWSPEC) return; /* not supported */ /* Make Zebra API structure. */ memset(&api, 0, sizeof(api)); api.vrf_id = bgp->vrf_id; api.afi = afi; if (!bgp_zebra_tm_chunk_obtained()) { if (BGP_DEBUG(pbr, PBR_ERROR)) flog_err(EC_BGP_TABLE_CHUNK, "%s: table chunk not obtained yet", __func__); return; } if (bgp_pbr_build_and_validate_entry(p, info, &api) < 0) { if (BGP_DEBUG(pbr, PBR_ERROR)) flog_err(EC_BGP_FLOWSPEC_INSTALLATION, "%s: cancel updating entry %p in bgp pbr", __func__, info); return; } bgp_pbr_handle_entry(bgp, info, &api, nlri_update); } int bgp_pbr_interface_compare(const struct bgp_pbr_interface *a, const struct bgp_pbr_interface *b) { return strcmp(a->name, b->name); } struct bgp_pbr_interface *bgp_pbr_interface_lookup(const char *name, struct bgp_pbr_interface_head *head) { struct bgp_pbr_interface pbr_if; strlcpy(pbr_if.name, name, sizeof(pbr_if.name)); return (RB_FIND(bgp_pbr_interface_head, head, &pbr_if)); } /* this function resets to the default policy routing * go back to default status */ void bgp_pbr_reset(struct bgp *bgp, afi_t afi) { struct bgp_pbr_config *bgp_pbr_cfg = bgp->bgp_pbr_cfg; struct bgp_pbr_interface_head *head; struct bgp_pbr_interface *pbr_if; if (!bgp_pbr_cfg || afi != AFI_IP) return; head = &(bgp_pbr_cfg->ifaces_by_name_ipv4); while (!RB_EMPTY(bgp_pbr_interface_head, head)) { pbr_if = RB_ROOT(bgp_pbr_interface_head, head); RB_REMOVE(bgp_pbr_interface_head, head, pbr_if); XFREE(MTYPE_TMP, pbr_if); } } frr-7.2.1/bgpd/bgp_pbr.h0000644000000000000000000001713013610377563011716 00000000000000/* * BGP pbr * Copyright (C) 6WIND * * FRR is free software; you can redistribute it and/or modify it * under the terms of the GNU General Public License as published by the * Free Software Foundation; either version 2, or (at your option) any * later version. * * FRR 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; see the file COPYING; if not, write to the Free Software * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA */ #ifndef __BGP_PBR_H__ #define __BGP_PBR_H__ #include "nexthop.h" #include "zclient.h" /* flowspec case: 0 to 3 actions maximum: * 1 redirect * 1 set dscp * 1 set traffic rate */ #define ACTIONS_MAX_NUM 4 enum bgp_pbr_action_enum { ACTION_TRAFFICRATE = 1, ACTION_TRAFFIC_ACTION = 2, ACTION_REDIRECT = 3, ACTION_MARKING = 4, ACTION_REDIRECT_IP = 5 }; #define TRAFFIC_ACTION_SAMPLE (1 << 0) #define TRAFFIC_ACTION_TERMINATE (1 << 1) #define TRAFFIC_ACTION_DISTRIBUTE (1 << 2) #define OPERATOR_COMPARE_LESS_THAN (1<<1) #define OPERATOR_COMPARE_GREATER_THAN (1<<2) #define OPERATOR_COMPARE_EQUAL_TO (1<<3) #define OPERATOR_COMPARE_EXACT_MATCH (1<<4) #define OPERATOR_UNARY_OR (1<<1) #define OPERATOR_UNARY_AND (1<<2) /* struct used to store values [0;65535] * this can be used for port number of protocol */ #define BGP_PBR_MATCH_VAL_MAX 5 struct bgp_pbr_match_val { uint16_t value; uint8_t compare_operator; uint8_t unary_operator; }; #define FRAGMENT_DONT 1 #define FRAGMENT_IS 2 #define FRAGMENT_FIRST 4 #define FRAGMENT_LAST 8 struct bgp_pbr_entry_action { /* used to store enum bgp_pbr_action_enum enumerate */ uint8_t action; union { union { uint8_t rate_info[4]; /* IEEE.754.1985 */ float rate; } r __attribute__((aligned(8))); struct _pbr_action { uint8_t do_sample; uint8_t filter; } za; vrf_id_t redirect_vrf; struct _pbr_redirect_ip { struct in_addr redirect_ip_v4; uint8_t duplicate; } zr; uint8_t marking_dscp; } u __attribute__((aligned(8))); }; /* BGP Policy Route structure */ struct bgp_pbr_entry_main { #define BGP_PBR_UNDEFINED 0 #define BGP_PBR_IPSET 1 #define BGP_PBR_IPRULE 2 uint8_t type; /* * This is an enum but we are going to treat it as a uint8_t * for purpose of encoding/decoding */ afi_t afi; safi_t safi; #define PREFIX_SRC_PRESENT (1 << 0) #define PREFIX_DST_PRESENT (1 << 1) uint8_t match_bitmask_iprule; uint8_t match_bitmask; uint8_t match_src_port_num; uint8_t match_dst_port_num; uint8_t match_port_num; uint8_t match_protocol_num; uint8_t match_icmp_type_num; uint8_t match_icmp_code_num; uint8_t match_packet_length_num; uint8_t match_dscp_num; uint8_t match_tcpflags_num; uint8_t match_fragment_num; struct prefix src_prefix; struct prefix dst_prefix; #define PROTOCOL_UDP 17 #define PROTOCOL_TCP 6 #define PROTOCOL_ICMP 1 struct bgp_pbr_match_val protocol[BGP_PBR_MATCH_VAL_MAX]; struct bgp_pbr_match_val src_port[BGP_PBR_MATCH_VAL_MAX]; struct bgp_pbr_match_val dst_port[BGP_PBR_MATCH_VAL_MAX]; struct bgp_pbr_match_val port[BGP_PBR_MATCH_VAL_MAX]; struct bgp_pbr_match_val icmp_type[BGP_PBR_MATCH_VAL_MAX]; struct bgp_pbr_match_val icmp_code[BGP_PBR_MATCH_VAL_MAX]; struct bgp_pbr_match_val packet_length[BGP_PBR_MATCH_VAL_MAX]; struct bgp_pbr_match_val dscp[BGP_PBR_MATCH_VAL_MAX]; struct bgp_pbr_match_val tcpflags[BGP_PBR_MATCH_VAL_MAX]; struct bgp_pbr_match_val fragment[BGP_PBR_MATCH_VAL_MAX]; uint16_t action_num; struct bgp_pbr_entry_action actions[ACTIONS_MAX_NUM]; vrf_id_t vrf_id; }; struct bgp_pbr_interface { RB_ENTRY(bgp_pbr_interface) id_entry; char name[INTERFACE_NAMSIZ]; }; RB_HEAD(bgp_pbr_interface_head, bgp_pbr_interface); RB_PROTOTYPE(bgp_pbr_interface_head, bgp_pbr_interface, id_entry, bgp_pbr_interface_compare); extern int bgp_pbr_interface_compare(const struct bgp_pbr_interface *a, const struct bgp_pbr_interface *b); struct bgp_pbr_config { struct bgp_pbr_interface_head ifaces_by_name_ipv4; bool pbr_interface_any_ipv4; }; extern struct bgp_pbr_config *bgp_pbr_cfg; struct bgp_pbr_rule { uint32_t flags; struct prefix src; struct prefix dst; struct bgp_pbr_action *action; vrf_id_t vrf_id; uint32_t unique; uint32_t priority; bool installed; bool install_in_progress; void *path; }; struct bgp_pbr_match { char ipset_name[ZEBRA_IPSET_NAME_SIZE]; /* mapped on enum ipset_type */ uint32_t type; uint32_t flags; uint16_t pkt_len_min; uint16_t pkt_len_max; uint16_t tcp_flags; uint16_t tcp_mask_flags; uint8_t dscp_value; uint8_t fragment; uint8_t protocol; vrf_id_t vrf_id; /* unique identifier for ipset create transaction */ uint32_t unique; /* unique identifier for iptable add transaction */ uint32_t unique2; bool installed; bool install_in_progress; bool installed_in_iptable; bool install_iptable_in_progress; struct hash *entry_hash; struct bgp_pbr_action *action; }; struct bgp_pbr_match_entry { struct bgp_pbr_match *backpointer; uint32_t unique; struct prefix src; struct prefix dst; uint16_t src_port_min; uint16_t src_port_max; uint16_t dst_port_min; uint16_t dst_port_max; uint8_t proto; void *path; bool installed; bool install_in_progress; }; struct bgp_pbr_action { /* * The Unique identifier of this specific pbrms */ uint32_t unique; uint32_t fwmark; uint32_t table_id; float rate; /* * nexthop information, or drop information * contains src vrf_id and nh contains dest vrf_id */ vrf_id_t vrf_id; struct nexthop nh; bool installed; bool install_in_progress; uint32_t refcnt; struct bgp *bgp; }; extern struct bgp_pbr_rule *bgp_pbr_rule_lookup(vrf_id_t vrf_id, uint32_t unique); extern struct bgp_pbr_action *bgp_pbr_action_rule_lookup(vrf_id_t vrf_id, uint32_t unique); extern struct bgp_pbr_match *bgp_pbr_match_ipset_lookup(vrf_id_t vrf_id, uint32_t unique); extern struct bgp_pbr_match_entry *bgp_pbr_match_ipset_entry_lookup( vrf_id_t vrf_id, char *name, uint32_t unique); extern struct bgp_pbr_match *bgp_pbr_match_iptable_lookup(vrf_id_t vrf_id, uint32_t unique); extern void bgp_pbr_cleanup(struct bgp *bgp); extern void bgp_pbr_init(struct bgp *bgp); extern uint32_t bgp_pbr_rule_hash_key(const void *arg); extern bool bgp_pbr_rule_hash_equal(const void *arg1, const void *arg2); extern uint32_t bgp_pbr_action_hash_key(const void *arg); extern bool bgp_pbr_action_hash_equal(const void *arg1, const void *arg2); extern uint32_t bgp_pbr_match_entry_hash_key(const void *arg); extern bool bgp_pbr_match_entry_hash_equal(const void *arg1, const void *arg2); extern uint32_t bgp_pbr_match_hash_key(const void *arg); extern bool bgp_pbr_match_hash_equal(const void *arg1, const void *arg2); void bgp_pbr_print_policy_route(struct bgp_pbr_entry_main *api); struct bgp_node; struct bgp_path_info; extern void bgp_pbr_update_entry(struct bgp *bgp, struct prefix *p, struct bgp_path_info *new_select, afi_t afi, safi_t safi, bool nlri_update); /* bgp pbr utilities */ extern struct bgp_pbr_interface *pbr_interface_lookup(const char *name); extern void bgp_pbr_reset(struct bgp *bgp, afi_t afi); extern struct bgp_pbr_interface *bgp_pbr_interface_lookup(const char *name, struct bgp_pbr_interface_head *head); extern int bgp_pbr_build_and_validate_entry(struct prefix *p, struct bgp_path_info *path, struct bgp_pbr_entry_main *api); #endif /* __BGP_PBR_H__ */ frr-7.2.1/bgpd/bgp_rd.c0000644000000000000000000001121413610377563011530 00000000000000/* BGP RD definitions for BGP-based VPNs (IP/EVPN) * -- brought over from bgpd/bgp_mplsvpn.c * Copyright (C) 2000 Kunihiro Ishiguro * * This file is part of FRR. * * FRR is free software; you can redistribute it and/or modify it * under the terms of the GNU General Public License as published by the * Free Software Foundation; either version 2, or (at your option) any * later version. * * FRR 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 FRR; see the file COPYING. If not, write to the Free * Software Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA * 02111-1307, USA. */ #include #include "command.h" #include "log.h" #include "prefix.h" #include "memory.h" #include "stream.h" #include "filter.h" #include "frrstr.h" #include "bgpd/bgpd.h" #include "bgpd/bgp_rd.h" #include "bgpd/bgp_attr.h" #if ENABLE_BGP_VNC #include "bgpd/rfapi/rfapi_backend.h" #endif uint16_t decode_rd_type(uint8_t *pnt) { uint16_t v; v = ((uint16_t)*pnt++ << 8); #if ENABLE_BGP_VNC /* * VNC L2 stores LHI in lower byte, so omit it */ if (v != RD_TYPE_VNC_ETH) v |= (uint16_t)*pnt; #else /* duplicate code for clarity */ v |= (uint16_t)*pnt; #endif return v; } void encode_rd_type(uint16_t v, uint8_t *pnt) { *((uint16_t *)pnt) = htons(v); } /* type == RD_TYPE_AS */ void decode_rd_as(uint8_t *pnt, struct rd_as *rd_as) { rd_as->as = (uint16_t)*pnt++ << 8; rd_as->as |= (uint16_t)*pnt++; ptr_get_be32(pnt, &rd_as->val); } /* type == RD_TYPE_AS4 */ void decode_rd_as4(uint8_t *pnt, struct rd_as *rd_as) { pnt = ptr_get_be32(pnt, &rd_as->as); rd_as->val = ((uint16_t)*pnt++ << 8); rd_as->val |= (uint16_t)*pnt; } /* type == RD_TYPE_IP */ void decode_rd_ip(uint8_t *pnt, struct rd_ip *rd_ip) { memcpy(&rd_ip->ip, pnt, 4); pnt += 4; rd_ip->val = ((uint16_t)*pnt++ << 8); rd_ip->val |= (uint16_t)*pnt; } #if ENABLE_BGP_VNC /* type == RD_TYPE_VNC_ETH */ void decode_rd_vnc_eth(uint8_t *pnt, struct rd_vnc_eth *rd_vnc_eth) { rd_vnc_eth->type = RD_TYPE_VNC_ETH; rd_vnc_eth->local_nve_id = pnt[1]; memcpy(rd_vnc_eth->macaddr.octet, pnt + 2, ETH_ALEN); } #endif int str2prefix_rd(const char *str, struct prefix_rd *prd) { int ret; /* ret of called functions */ int lret; /* local ret, of this func */ char *p; char *p2; struct stream *s = NULL; char *half = NULL; struct in_addr addr; s = stream_new(8); prd->family = AF_UNSPEC; prd->prefixlen = 64; lret = 0; p = strchr(str, ':'); if (!p) goto out; if (!all_digit(p + 1)) goto out; half = XMALLOC(MTYPE_TMP, (p - str) + 1); memcpy(half, str, (p - str)); half[p - str] = '\0'; p2 = strchr(str, '.'); if (!p2) { unsigned long as_val; if (!all_digit(half)) goto out; as_val = atol(half); if (as_val > 0xffff) { stream_putw(s, RD_TYPE_AS4); stream_putl(s, as_val); stream_putw(s, atol(p + 1)); } else { stream_putw(s, RD_TYPE_AS); stream_putw(s, as_val); stream_putl(s, atol(p + 1)); } } else { ret = inet_aton(half, &addr); if (!ret) goto out; stream_putw(s, RD_TYPE_IP); stream_put_in_addr(s, &addr); stream_putw(s, atol(p + 1)); } memcpy(prd->val, s->data, 8); lret = 1; out: if (s) stream_free(s); XFREE(MTYPE_TMP, half); return lret; } char *prefix_rd2str(struct prefix_rd *prd, char *buf, size_t size) { uint8_t *pnt; uint16_t type; struct rd_as rd_as; struct rd_ip rd_ip; assert(size >= RD_ADDRSTRLEN); pnt = prd->val; type = decode_rd_type(pnt); if (type == RD_TYPE_AS) { decode_rd_as(pnt + 2, &rd_as); snprintf(buf, size, "%u:%" PRIu32, rd_as.as, rd_as.val); return buf; } else if (type == RD_TYPE_AS4) { decode_rd_as4(pnt + 2, &rd_as); snprintf(buf, size, "%u:%" PRIu32, rd_as.as, rd_as.val); return buf; } else if (type == RD_TYPE_IP) { decode_rd_ip(pnt + 2, &rd_ip); snprintf(buf, size, "%s:%" PRIu16, inet_ntoa(rd_ip.ip), rd_ip.val); return buf; } #if ENABLE_BGP_VNC else if (type == RD_TYPE_VNC_ETH) { snprintf(buf, size, "LHI:%d, %02x:%02x:%02x:%02x:%02x:%02x", *(pnt + 1), /* LHI */ *(pnt + 2), /* MAC[0] */ *(pnt + 3), *(pnt + 4), *(pnt + 5), *(pnt + 6), *(pnt + 7)); return buf; } #endif snprintf(buf, size, "Unknown Type: %d", type); return buf; } void form_auto_rd(struct in_addr router_id, uint16_t rd_id, struct prefix_rd *prd) { char buf[100]; prd->family = AF_UNSPEC; prd->prefixlen = 64; sprintf(buf, "%s:%hu", inet_ntoa(router_id), rd_id); (void)str2prefix_rd(buf, prd); } frr-7.2.1/bgpd/bgp_rd.h0000644000000000000000000000374113610377563011543 00000000000000/* BGP RD definitions for BGP-based VPNs (IP/EVPN) * -- brought over from bgpd/bgp_mplsvpn.h * Copyright (C) 2000 Kunihiro Ishiguro * * This file is part of FRR. * * FRR is free software; you can redistribute it and/or modify it * under the terms of the GNU General Public License as published by the * Free Software Foundation; either version 2, or (at your option) any * later version. * * FRR 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 FRR; see the file COPYING. If not, write to the Free * Software Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA * 02111-1307, USA. */ #ifndef _QUAGGA_BGP_RD_H #define _QUAGGA_BGP_RD_H /* RD types */ #define RD_TYPE_AS 0 #define RD_TYPE_IP 1 #define RD_TYPE_AS4 2 #if ENABLE_BGP_VNC #define RD_TYPE_VNC_ETH 0xff00 /* VNC L2VPN */ #endif #define RD_ADDRSTRLEN 28 struct rd_as { uint16_t type; as_t as; uint32_t val; }; struct rd_ip { uint16_t type; struct in_addr ip; uint16_t val; }; #if ENABLE_BGP_VNC struct rd_vnc_eth { uint16_t type; uint8_t local_nve_id; struct ethaddr macaddr; }; #endif extern uint16_t decode_rd_type(uint8_t *pnt); extern void encode_rd_type(uint16_t, uint8_t *); extern void decode_rd_as(uint8_t *pnt, struct rd_as *rd_as); extern void decode_rd_as4(uint8_t *pnt, struct rd_as *rd_as); extern void decode_rd_ip(uint8_t *pnt, struct rd_ip *rd_ip); #if ENABLE_BGP_VNC extern void decode_rd_vnc_eth(uint8_t *pnt, struct rd_vnc_eth *rd_vnc_eth); #endif extern int str2prefix_rd(const char *, struct prefix_rd *); extern char *prefix_rd2str(struct prefix_rd *, char *, size_t); extern void form_auto_rd(struct in_addr router_id, uint16_t rd_id, struct prefix_rd *prd); #endif /* _QUAGGA_BGP_RD_H */ frr-7.2.1/bgpd/bgp_regex.c0000644000000000000000000000422413610377563012240 00000000000000/* AS regular expression routine * Copyright (C) 1999 Kunihiro Ishiguro * * This file is part of GNU Zebra. * * GNU Zebra is free software; you can redistribute it and/or modify it * under the terms of the GNU General Public License as published by the * Free Software Foundation; either version 2, or (at your option) any * later version. * * GNU Zebra 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; see the file COPYING; if not, write to the Free Software * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA */ #include #include "log.h" #include "command.h" #include "memory.h" #include "queue.h" #include "filter.h" #include "bgpd.h" #include "bgp_aspath.h" #include "bgp_regex.h" /* Character `_' has special mean. It represents [,{}() ] and the beginning of the line(^) and the end of the line ($). (^|[,{}() ]|$) */ regex_t *bgp_regcomp(const char *regstr) { /* Convert _ character to generic regular expression. */ int i, j; int len; int magic = 0; char *magic_str; char magic_regexp[] = "(^|[,{}() ]|$)"; int ret; regex_t *regex; len = strlen(regstr); for (i = 0; i < len; i++) if (regstr[i] == '_') magic++; magic_str = XMALLOC(MTYPE_TMP, len + (14 * magic) + 1); for (i = 0, j = 0; i < len; i++) { if (regstr[i] == '_') { memcpy(magic_str + j, magic_regexp, strlen(magic_regexp)); j += strlen(magic_regexp); } else magic_str[j++] = regstr[i]; } magic_str[j] = '\0'; regex = XMALLOC(MTYPE_BGP_REGEXP, sizeof(regex_t)); ret = regcomp(regex, magic_str, REG_EXTENDED | REG_NOSUB); XFREE(MTYPE_TMP, magic_str); if (ret != 0) { XFREE(MTYPE_BGP_REGEXP, regex); return NULL; } return regex; } int bgp_regexec(regex_t *regex, struct aspath *aspath) { return regexec(regex, aspath->str, 0, NULL, 0); } void bgp_regex_free(regex_t *regex) { regfree(regex); XFREE(MTYPE_BGP_REGEXP, regex); } frr-7.2.1/bgpd/bgp_regex.h0000644000000000000000000000226113610377563012244 00000000000000/* AS regular expression routine * Copyright (C) 1999 Kunihiro Ishiguro * * This file is part of GNU Zebra. * * GNU Zebra is free software; you can redistribute it and/or modify it * under the terms of the GNU General Public License as published by the * Free Software Foundation; either version 2, or (at your option) any * later version. * * GNU Zebra 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; see the file COPYING; if not, write to the Free Software * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA */ #ifndef _QUAGGA_BGP_REGEX_H #define _QUAGGA_BGP_REGEX_H #include #ifdef HAVE_LIBPCREPOSIX #include #else #include #endif /* HAVE_LIBPCREPOSIX */ extern void bgp_regex_free(regex_t *regex); extern regex_t *bgp_regcomp(const char *str); extern int bgp_regexec(regex_t *regex, struct aspath *aspath); #endif /* _QUAGGA_BGP_REGEX_H */ frr-7.2.1/bgpd/bgp_route.c0000644000000000000000000125351513610377563012276 00000000000000/* BGP routing information * Copyright (C) 1996, 97, 98, 99 Kunihiro Ishiguro * Copyright (C) 2016 Job Snijders * * This file is part of GNU Zebra. * * GNU Zebra is free software; you can redistribute it and/or modify it * under the terms of the GNU General Public License as published by the * Free Software Foundation; either version 2, or (at your option) any * later version. * * GNU Zebra 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; see the file COPYING; if not, write to the Free Software * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA */ #include #include #include "printfrr.h" #include "prefix.h" #include "linklist.h" #include "memory.h" #include "command.h" #include "stream.h" #include "filter.h" #include "log.h" #include "routemap.h" #include "buffer.h" #include "sockunion.h" #include "plist.h" #include "thread.h" #include "workqueue.h" #include "queue.h" #include "memory.h" #include "lib/json.h" #include "lib_errors.h" #include "bgpd/bgpd.h" #include "bgpd/bgp_table.h" #include "bgpd/bgp_route.h" #include "bgpd/bgp_attr.h" #include "bgpd/bgp_debug.h" #include "bgpd/bgp_errors.h" #include "bgpd/bgp_aspath.h" #include "bgpd/bgp_regex.h" #include "bgpd/bgp_community.h" #include "bgpd/bgp_ecommunity.h" #include "bgpd/bgp_lcommunity.h" #include "bgpd/bgp_clist.h" #include "bgpd/bgp_packet.h" #include "bgpd/bgp_filter.h" #include "bgpd/bgp_fsm.h" #include "bgpd/bgp_mplsvpn.h" #include "bgpd/bgp_nexthop.h" #include "bgpd/bgp_damp.h" #include "bgpd/bgp_advertise.h" #include "bgpd/bgp_zebra.h" #include "bgpd/bgp_vty.h" #include "bgpd/bgp_mpath.h" #include "bgpd/bgp_nht.h" #include "bgpd/bgp_updgrp.h" #include "bgpd/bgp_label.h" #include "bgpd/bgp_addpath.h" #include "bgpd/bgp_mac.h" #if ENABLE_BGP_VNC #include "bgpd/rfapi/rfapi_backend.h" #include "bgpd/rfapi/vnc_import_bgp.h" #include "bgpd/rfapi/vnc_export_bgp.h" #endif #include "bgpd/bgp_encap_types.h" #include "bgpd/bgp_encap_tlv.h" #include "bgpd/bgp_evpn.h" #include "bgpd/bgp_evpn_vty.h" #include "bgpd/bgp_flowspec.h" #include "bgpd/bgp_flowspec_util.h" #include "bgpd/bgp_pbr.h" #ifndef VTYSH_EXTRACT_PL #include "bgpd/bgp_route_clippy.c" #endif /* Extern from bgp_dump.c */ extern const char *bgp_origin_str[]; extern const char *bgp_origin_long_str[]; /* PMSI strings. */ #define PMSI_TNLTYPE_STR_NO_INFO "No info" #define PMSI_TNLTYPE_STR_DEFAULT PMSI_TNLTYPE_STR_NO_INFO static const struct message bgp_pmsi_tnltype_str[] = { {PMSI_TNLTYPE_NO_INFO, PMSI_TNLTYPE_STR_NO_INFO}, {PMSI_TNLTYPE_RSVP_TE_P2MP, "RSVP-TE P2MP"}, {PMSI_TNLTYPE_MLDP_P2MP, "mLDP P2MP"}, {PMSI_TNLTYPE_PIM_SSM, "PIM-SSM"}, {PMSI_TNLTYPE_PIM_SM, "PIM-SM"}, {PMSI_TNLTYPE_PIM_BIDIR, "PIM-BIDIR"}, {PMSI_TNLTYPE_INGR_REPL, "Ingress Replication"}, {PMSI_TNLTYPE_MLDP_MP2MP, "mLDP MP2MP"}, {0} }; #define VRFID_NONE_STR "-" DEFINE_HOOK(bgp_process, (struct bgp *bgp, afi_t afi, safi_t safi, struct bgp_node *bn, struct peer *peer, bool withdraw), (bgp, afi, safi, bn, peer, withdraw)) struct bgp_node *bgp_afi_node_get(struct bgp_table *table, afi_t afi, safi_t safi, struct prefix *p, struct prefix_rd *prd) { struct bgp_node *rn; struct bgp_node *prn = NULL; assert(table); if (!table) return NULL; if ((safi == SAFI_MPLS_VPN) || (safi == SAFI_ENCAP) || (safi == SAFI_EVPN)) { prn = bgp_node_get(table, (struct prefix *)prd); if (!bgp_node_has_bgp_path_info_data(prn)) bgp_node_set_bgp_table_info( prn, bgp_table_init(table->bgp, afi, safi)); else bgp_unlock_node(prn); table = bgp_node_get_bgp_table_info(prn); } rn = bgp_node_get(table, p); if ((safi == SAFI_MPLS_VPN) || (safi == SAFI_ENCAP) || (safi == SAFI_EVPN)) rn->prn = prn; return rn; } struct bgp_node *bgp_afi_node_lookup(struct bgp_table *table, afi_t afi, safi_t safi, struct prefix *p, struct prefix_rd *prd) { struct bgp_node *rn; struct bgp_node *prn = NULL; if (!table) return NULL; if ((safi == SAFI_MPLS_VPN) || (safi == SAFI_ENCAP) || (safi == SAFI_EVPN)) { prn = bgp_node_lookup(table, (struct prefix *)prd); if (!prn) return NULL; if (!bgp_node_has_bgp_path_info_data(prn)) { bgp_unlock_node(prn); return NULL; } table = bgp_node_get_bgp_table_info(prn); } rn = bgp_node_lookup(table, p); return rn; } /* Allocate bgp_path_info_extra */ static struct bgp_path_info_extra *bgp_path_info_extra_new(void) { struct bgp_path_info_extra *new; new = XCALLOC(MTYPE_BGP_ROUTE_EXTRA, sizeof(struct bgp_path_info_extra)); new->label[0] = MPLS_INVALID_LABEL; new->num_labels = 0; new->bgp_fs_pbr = NULL; new->bgp_fs_iprule = NULL; return new; } void bgp_path_info_extra_free(struct bgp_path_info_extra **extra) { struct bgp_path_info_extra *e; if (!extra || !*extra) return; e = *extra; if (e->damp_info) bgp_damp_info_free(e->damp_info, 0, e->damp_info->afi, e->damp_info->safi); e->damp_info = NULL; if (e->parent) { struct bgp_path_info *bpi = (struct bgp_path_info *)e->parent; if (bpi->net) { /* FIXME: since multiple e may have the same e->parent * and e->parent->net is holding a refcount for each * of them, we need to do some fudging here. * * WARNING: if bpi->net->lock drops to 0, bpi may be * freed as well (because bpi->net was holding the * last reference to bpi) => write after free! */ unsigned refcount; bpi = bgp_path_info_lock(bpi); refcount = bpi->net->lock - 1; bgp_unlock_node((struct bgp_node *)bpi->net); if (!refcount) bpi->net = NULL; bgp_path_info_unlock(bpi); } bgp_path_info_unlock(e->parent); e->parent = NULL; } if (e->bgp_orig) bgp_unlock(e->bgp_orig); if ((*extra)->bgp_fs_iprule) list_delete(&((*extra)->bgp_fs_iprule)); if ((*extra)->bgp_fs_pbr) list_delete(&((*extra)->bgp_fs_pbr)); XFREE(MTYPE_BGP_ROUTE_EXTRA, *extra); *extra = NULL; } /* Get bgp_path_info extra information for the given bgp_path_info, lazy * allocated if required. */ struct bgp_path_info_extra *bgp_path_info_extra_get(struct bgp_path_info *pi) { if (!pi->extra) pi->extra = bgp_path_info_extra_new(); return pi->extra; } /* Free bgp route information. */ static void bgp_path_info_free(struct bgp_path_info *path) { if (path->attr) bgp_attr_unintern(&path->attr); bgp_unlink_nexthop(path); bgp_path_info_extra_free(&path->extra); bgp_path_info_mpath_free(&path->mpath); if (path->net) bgp_addpath_free_info_data(&path->tx_addpath, &path->net->tx_addpath); peer_unlock(path->peer); /* bgp_path_info peer reference */ XFREE(MTYPE_BGP_ROUTE, path); } struct bgp_path_info *bgp_path_info_lock(struct bgp_path_info *path) { path->lock++; return path; } struct bgp_path_info *bgp_path_info_unlock(struct bgp_path_info *path) { assert(path && path->lock > 0); path->lock--; if (path->lock == 0) { #if 0 zlog_debug ("%s: unlocked and freeing", __func__); zlog_backtrace (LOG_DEBUG); #endif bgp_path_info_free(path); return NULL; } #if 0 if (path->lock == 1) { zlog_debug ("%s: unlocked to 1", __func__); zlog_backtrace (LOG_DEBUG); } #endif return path; } void bgp_path_info_add(struct bgp_node *rn, struct bgp_path_info *pi) { struct bgp_path_info *top; top = bgp_node_get_bgp_path_info(rn); pi->next = top; pi->prev = NULL; if (top) top->prev = pi; bgp_node_set_bgp_path_info(rn, pi); bgp_path_info_lock(pi); bgp_lock_node(rn); peer_lock(pi->peer); /* bgp_path_info peer reference */ } /* Do the actual removal of info from RIB, for use by bgp_process completion callback *only* */ void bgp_path_info_reap(struct bgp_node *rn, struct bgp_path_info *pi) { if (pi->next) pi->next->prev = pi->prev; if (pi->prev) pi->prev->next = pi->next; else bgp_node_set_bgp_path_info(rn, pi->next); bgp_path_info_mpath_dequeue(pi); bgp_path_info_unlock(pi); bgp_unlock_node(rn); } void bgp_path_info_delete(struct bgp_node *rn, struct bgp_path_info *pi) { bgp_path_info_set_flag(rn, pi, BGP_PATH_REMOVED); /* set of previous already took care of pcount */ UNSET_FLAG(pi->flags, BGP_PATH_VALID); } /* undo the effects of a previous call to bgp_path_info_delete; typically called when a route is deleted and then quickly re-added before the deletion has been processed */ void bgp_path_info_restore(struct bgp_node *rn, struct bgp_path_info *pi) { bgp_path_info_unset_flag(rn, pi, BGP_PATH_REMOVED); /* unset of previous already took care of pcount */ SET_FLAG(pi->flags, BGP_PATH_VALID); } /* Adjust pcount as required */ static void bgp_pcount_adjust(struct bgp_node *rn, struct bgp_path_info *pi) { struct bgp_table *table; assert(rn && bgp_node_table(rn)); assert(pi && pi->peer && pi->peer->bgp); table = bgp_node_table(rn); if (pi->peer == pi->peer->bgp->peer_self) return; if (!BGP_PATH_COUNTABLE(pi) && CHECK_FLAG(pi->flags, BGP_PATH_COUNTED)) { UNSET_FLAG(pi->flags, BGP_PATH_COUNTED); /* slight hack, but more robust against errors. */ if (pi->peer->pcount[table->afi][table->safi]) pi->peer->pcount[table->afi][table->safi]--; else flog_err(EC_LIB_DEVELOPMENT, "Asked to decrement 0 prefix count for peer"); } else if (BGP_PATH_COUNTABLE(pi) && !CHECK_FLAG(pi->flags, BGP_PATH_COUNTED)) { SET_FLAG(pi->flags, BGP_PATH_COUNTED); pi->peer->pcount[table->afi][table->safi]++; } } static int bgp_label_index_differs(struct bgp_path_info *pi1, struct bgp_path_info *pi2) { return (!(pi1->attr->label_index == pi2->attr->label_index)); } /* Set/unset bgp_path_info flags, adjusting any other state as needed. * This is here primarily to keep prefix-count in check. */ void bgp_path_info_set_flag(struct bgp_node *rn, struct bgp_path_info *pi, uint32_t flag) { SET_FLAG(pi->flags, flag); /* early bath if we know it's not a flag that changes countability state */ if (!CHECK_FLAG(flag, BGP_PATH_VALID | BGP_PATH_HISTORY | BGP_PATH_REMOVED)) return; bgp_pcount_adjust(rn, pi); } void bgp_path_info_unset_flag(struct bgp_node *rn, struct bgp_path_info *pi, uint32_t flag) { UNSET_FLAG(pi->flags, flag); /* early bath if we know it's not a flag that changes countability state */ if (!CHECK_FLAG(flag, BGP_PATH_VALID | BGP_PATH_HISTORY | BGP_PATH_REMOVED)) return; bgp_pcount_adjust(rn, pi); } /* Get MED value. If MED value is missing and "bgp bestpath missing-as-worst" is specified, treat it as the worst value. */ static uint32_t bgp_med_value(struct attr *attr, struct bgp *bgp) { if (attr->flag & ATTR_FLAG_BIT(BGP_ATTR_MULTI_EXIT_DISC)) return attr->med; else { if (bgp_flag_check(bgp, BGP_FLAG_MED_MISSING_AS_WORST)) return BGP_MED_MAX; else return 0; } } void bgp_path_info_path_with_addpath_rx_str(struct bgp_path_info *pi, char *buf) { if (pi->addpath_rx_id) sprintf(buf, "path %s (addpath rxid %d)", pi->peer->host, pi->addpath_rx_id); else sprintf(buf, "path %s", pi->peer->host); } /* Compare two bgp route entity. If 'new' is preferable over 'exist' return 1. */ static int bgp_path_info_cmp(struct bgp *bgp, struct bgp_path_info *new, struct bgp_path_info *exist, int *paths_eq, struct bgp_maxpaths_cfg *mpath_cfg, int debug, char *pfx_buf, afi_t afi, safi_t safi, enum bgp_path_selection_reason *reason) { struct attr *newattr, *existattr; bgp_peer_sort_t new_sort; bgp_peer_sort_t exist_sort; uint32_t new_pref; uint32_t exist_pref; uint32_t new_med; uint32_t exist_med; uint32_t new_weight; uint32_t exist_weight; uint32_t newm, existm; struct in_addr new_id; struct in_addr exist_id; int new_cluster; int exist_cluster; int internal_as_route; int confed_as_route; int ret = 0; char new_buf[PATH_ADDPATH_STR_BUFFER]; char exist_buf[PATH_ADDPATH_STR_BUFFER]; uint32_t new_mm_seq; uint32_t exist_mm_seq; int nh_cmp; *paths_eq = 0; /* 0. Null check. */ if (new == NULL) { *reason = bgp_path_selection_none; if (debug) zlog_debug("%s: new is NULL", pfx_buf); return 0; } if (debug) bgp_path_info_path_with_addpath_rx_str(new, new_buf); if (exist == NULL) { *reason = bgp_path_selection_first; if (debug) zlog_debug("%s: %s is the initial bestpath", pfx_buf, new_buf); return 1; } if (debug) { bgp_path_info_path_with_addpath_rx_str(exist, exist_buf); zlog_debug("%s: Comparing %s flags 0x%x with %s flags 0x%x", pfx_buf, new_buf, new->flags, exist_buf, exist->flags); } newattr = new->attr; existattr = exist->attr; /* For EVPN routes, we cannot just go by local vs remote, we have to * look at the MAC mobility sequence number, if present. */ if (safi == SAFI_EVPN) { /* This is an error condition described in RFC 7432 Section * 15.2. The RFC * states that in this scenario "the PE MUST alert the operator" * but it * does not state what other action to take. In order to provide * some * consistency in this scenario we are going to prefer the path * with the * sticky flag. */ if (newattr->sticky != existattr->sticky) { if (!debug) { prefix2str(&new->net->p, pfx_buf, sizeof(*pfx_buf) * PREFIX2STR_BUFFER); bgp_path_info_path_with_addpath_rx_str(new, new_buf); bgp_path_info_path_with_addpath_rx_str( exist, exist_buf); } if (newattr->sticky && !existattr->sticky) { *reason = bgp_path_selection_evpn_sticky_mac; if (debug) zlog_debug( "%s: %s wins over %s due to sticky MAC flag", pfx_buf, new_buf, exist_buf); return 1; } if (!newattr->sticky && existattr->sticky) { *reason = bgp_path_selection_evpn_sticky_mac; if (debug) zlog_debug( "%s: %s loses to %s due to sticky MAC flag", pfx_buf, new_buf, exist_buf); return 0; } } new_mm_seq = mac_mobility_seqnum(newattr); exist_mm_seq = mac_mobility_seqnum(existattr); if (new_mm_seq > exist_mm_seq) { *reason = bgp_path_selection_evpn_seq; if (debug) zlog_debug( "%s: %s wins over %s due to MM seq %u > %u", pfx_buf, new_buf, exist_buf, new_mm_seq, exist_mm_seq); return 1; } if (new_mm_seq < exist_mm_seq) { *reason = bgp_path_selection_evpn_seq; if (debug) zlog_debug( "%s: %s loses to %s due to MM seq %u < %u", pfx_buf, new_buf, exist_buf, new_mm_seq, exist_mm_seq); return 0; } /* * if sequence numbers are the same path with the lowest IP * wins */ nh_cmp = bgp_path_info_nexthop_cmp(new, exist); if (nh_cmp < 0) { *reason = bgp_path_selection_evpn_lower_ip; if (debug) zlog_debug( "%s: %s wins over %s due to same MM seq %u and lower IP %s", pfx_buf, new_buf, exist_buf, new_mm_seq, inet_ntoa(new->attr->nexthop)); return 1; } if (nh_cmp > 0) { *reason = bgp_path_selection_evpn_lower_ip; if (debug) zlog_debug( "%s: %s loses to %s due to same MM seq %u and higher IP %s", pfx_buf, new_buf, exist_buf, new_mm_seq, inet_ntoa(new->attr->nexthop)); return 0; } } /* 1. Weight check. */ new_weight = newattr->weight; exist_weight = existattr->weight; if (new_weight > exist_weight) { *reason = bgp_path_selection_weight; if (debug) zlog_debug("%s: %s wins over %s due to weight %d > %d", pfx_buf, new_buf, exist_buf, new_weight, exist_weight); return 1; } if (new_weight < exist_weight) { *reason = bgp_path_selection_weight; if (debug) zlog_debug("%s: %s loses to %s due to weight %d < %d", pfx_buf, new_buf, exist_buf, new_weight, exist_weight); return 0; } /* 2. Local preference check. */ new_pref = exist_pref = bgp->default_local_pref; if (newattr->flag & ATTR_FLAG_BIT(BGP_ATTR_LOCAL_PREF)) new_pref = newattr->local_pref; if (existattr->flag & ATTR_FLAG_BIT(BGP_ATTR_LOCAL_PREF)) exist_pref = existattr->local_pref; if (new_pref > exist_pref) { *reason = bgp_path_selection_local_pref; if (debug) zlog_debug( "%s: %s wins over %s due to localpref %d > %d", pfx_buf, new_buf, exist_buf, new_pref, exist_pref); return 1; } if (new_pref < exist_pref) { *reason = bgp_path_selection_local_pref; if (debug) zlog_debug( "%s: %s loses to %s due to localpref %d < %d", pfx_buf, new_buf, exist_buf, new_pref, exist_pref); return 0; } /* 3. Local route check. We prefer: * - BGP_ROUTE_STATIC * - BGP_ROUTE_AGGREGATE * - BGP_ROUTE_REDISTRIBUTE */ if (!(new->sub_type == BGP_ROUTE_NORMAL || new->sub_type == BGP_ROUTE_IMPORTED)) { *reason = bgp_path_selection_local_route; if (debug) zlog_debug( "%s: %s wins over %s due to preferred BGP_ROUTE type", pfx_buf, new_buf, exist_buf); return 1; } if (!(exist->sub_type == BGP_ROUTE_NORMAL || exist->sub_type == BGP_ROUTE_IMPORTED)) { *reason = bgp_path_selection_local_route; if (debug) zlog_debug( "%s: %s loses to %s due to preferred BGP_ROUTE type", pfx_buf, new_buf, exist_buf); return 0; } /* 4. AS path length check. */ if (!bgp_flag_check(bgp, BGP_FLAG_ASPATH_IGNORE)) { int exist_hops = aspath_count_hops(existattr->aspath); int exist_confeds = aspath_count_confeds(existattr->aspath); if (bgp_flag_check(bgp, BGP_FLAG_ASPATH_CONFED)) { int aspath_hops; aspath_hops = aspath_count_hops(newattr->aspath); aspath_hops += aspath_count_confeds(newattr->aspath); if (aspath_hops < (exist_hops + exist_confeds)) { *reason = bgp_path_selection_confed_as_path; if (debug) zlog_debug( "%s: %s wins over %s due to aspath (with confeds) hopcount %d < %d", pfx_buf, new_buf, exist_buf, aspath_hops, (exist_hops + exist_confeds)); return 1; } if (aspath_hops > (exist_hops + exist_confeds)) { *reason = bgp_path_selection_confed_as_path; if (debug) zlog_debug( "%s: %s loses to %s due to aspath (with confeds) hopcount %d > %d", pfx_buf, new_buf, exist_buf, aspath_hops, (exist_hops + exist_confeds)); return 0; } } else { int newhops = aspath_count_hops(newattr->aspath); if (newhops < exist_hops) { *reason = bgp_path_selection_as_path; if (debug) zlog_debug( "%s: %s wins over %s due to aspath hopcount %d < %d", pfx_buf, new_buf, exist_buf, newhops, exist_hops); return 1; } if (newhops > exist_hops) { *reason = bgp_path_selection_as_path; if (debug) zlog_debug( "%s: %s loses to %s due to aspath hopcount %d > %d", pfx_buf, new_buf, exist_buf, newhops, exist_hops); return 0; } } } /* 5. Origin check. */ if (newattr->origin < existattr->origin) { *reason = bgp_path_selection_origin; if (debug) zlog_debug("%s: %s wins over %s due to ORIGIN %s < %s", pfx_buf, new_buf, exist_buf, bgp_origin_long_str[newattr->origin], bgp_origin_long_str[existattr->origin]); return 1; } if (newattr->origin > existattr->origin) { *reason = bgp_path_selection_origin; if (debug) zlog_debug("%s: %s loses to %s due to ORIGIN %s > %s", pfx_buf, new_buf, exist_buf, bgp_origin_long_str[newattr->origin], bgp_origin_long_str[existattr->origin]); return 0; } /* 6. MED check. */ internal_as_route = (aspath_count_hops(newattr->aspath) == 0 && aspath_count_hops(existattr->aspath) == 0); confed_as_route = (aspath_count_confeds(newattr->aspath) > 0 && aspath_count_confeds(existattr->aspath) > 0 && aspath_count_hops(newattr->aspath) == 0 && aspath_count_hops(existattr->aspath) == 0); if (bgp_flag_check(bgp, BGP_FLAG_ALWAYS_COMPARE_MED) || (bgp_flag_check(bgp, BGP_FLAG_MED_CONFED) && confed_as_route) || aspath_cmp_left(newattr->aspath, existattr->aspath) || aspath_cmp_left_confed(newattr->aspath, existattr->aspath) || internal_as_route) { new_med = bgp_med_value(new->attr, bgp); exist_med = bgp_med_value(exist->attr, bgp); if (new_med < exist_med) { *reason = bgp_path_selection_med; if (debug) zlog_debug( "%s: %s wins over %s due to MED %d < %d", pfx_buf, new_buf, exist_buf, new_med, exist_med); return 1; } if (new_med > exist_med) { *reason = bgp_path_selection_med; if (debug) zlog_debug( "%s: %s loses to %s due to MED %d > %d", pfx_buf, new_buf, exist_buf, new_med, exist_med); return 0; } } /* 7. Peer type check. */ new_sort = new->peer->sort; exist_sort = exist->peer->sort; if (new_sort == BGP_PEER_EBGP && (exist_sort == BGP_PEER_IBGP || exist_sort == BGP_PEER_CONFED)) { *reason = bgp_path_selection_peer; if (debug) zlog_debug( "%s: %s wins over %s due to eBGP peer > iBGP peer", pfx_buf, new_buf, exist_buf); return 1; } if (exist_sort == BGP_PEER_EBGP && (new_sort == BGP_PEER_IBGP || new_sort == BGP_PEER_CONFED)) { *reason = bgp_path_selection_peer; if (debug) zlog_debug( "%s: %s loses to %s due to iBGP peer < eBGP peer", pfx_buf, new_buf, exist_buf); return 0; } /* 8. IGP metric check. */ newm = existm = 0; if (new->extra) newm = new->extra->igpmetric; if (exist->extra) existm = exist->extra->igpmetric; if (newm < existm) { if (debug) zlog_debug( "%s: %s wins over %s due to IGP metric %d < %d", pfx_buf, new_buf, exist_buf, newm, existm); ret = 1; } if (newm > existm) { if (debug) zlog_debug( "%s: %s loses to %s due to IGP metric %d > %d", pfx_buf, new_buf, exist_buf, newm, existm); ret = 0; } /* 9. Same IGP metric. Compare the cluster list length as representative of IGP hops metric. Rewrite the metric value pair (newm, existm) with the cluster list length. Prefer the path with smaller cluster list length. */ if (newm == existm) { if (peer_sort(new->peer) == BGP_PEER_IBGP && peer_sort(exist->peer) == BGP_PEER_IBGP && (mpath_cfg == NULL || CHECK_FLAG( mpath_cfg->ibgp_flags, BGP_FLAG_IBGP_MULTIPATH_SAME_CLUSTERLEN))) { newm = BGP_CLUSTER_LIST_LENGTH(new->attr); existm = BGP_CLUSTER_LIST_LENGTH(exist->attr); if (newm < existm) { if (debug) zlog_debug( "%s: %s wins over %s due to CLUSTER_LIST length %d < %d", pfx_buf, new_buf, exist_buf, newm, existm); ret = 1; } if (newm > existm) { if (debug) zlog_debug( "%s: %s loses to %s due to CLUSTER_LIST length %d > %d", pfx_buf, new_buf, exist_buf, newm, existm); ret = 0; } } } /* 10. confed-external vs. confed-internal */ if (CHECK_FLAG(bgp->config, BGP_CONFIG_CONFEDERATION)) { if (new_sort == BGP_PEER_CONFED && exist_sort == BGP_PEER_IBGP) { *reason = bgp_path_selection_confed; if (debug) zlog_debug( "%s: %s wins over %s due to confed-external peer > confed-internal peer", pfx_buf, new_buf, exist_buf); return 1; } if (exist_sort == BGP_PEER_CONFED && new_sort == BGP_PEER_IBGP) { *reason = bgp_path_selection_confed; if (debug) zlog_debug( "%s: %s loses to %s due to confed-internal peer < confed-external peer", pfx_buf, new_buf, exist_buf); return 0; } } /* 11. Maximum path check. */ if (newm == existm) { /* If one path has a label but the other does not, do not treat * them as equals for multipath */ if ((new->extra &&bgp_is_valid_label(&new->extra->label[0])) != (exist->extra && bgp_is_valid_label(&exist->extra->label[0]))) { if (debug) zlog_debug( "%s: %s and %s cannot be multipath, one has a label while the other does not", pfx_buf, new_buf, exist_buf); } else if (bgp_flag_check(bgp, BGP_FLAG_ASPATH_MULTIPATH_RELAX)) { /* * For the two paths, all comparison steps till IGP * metric * have succeeded - including AS_PATH hop count. Since * 'bgp * bestpath as-path multipath-relax' knob is on, we * don't need * an exact match of AS_PATH. Thus, mark the paths are * equal. * That will trigger both these paths to get into the * multipath * array. */ *paths_eq = 1; if (debug) zlog_debug( "%s: %s and %s are equal via multipath-relax", pfx_buf, new_buf, exist_buf); } else if (new->peer->sort == BGP_PEER_IBGP) { if (aspath_cmp(new->attr->aspath, exist->attr->aspath)) { *paths_eq = 1; if (debug) zlog_debug( "%s: %s and %s are equal via matching aspaths", pfx_buf, new_buf, exist_buf); } } else if (new->peer->as == exist->peer->as) { *paths_eq = 1; if (debug) zlog_debug( "%s: %s and %s are equal via same remote-as", pfx_buf, new_buf, exist_buf); } } else { /* * TODO: If unequal cost ibgp multipath is enabled we can * mark the paths as equal here instead of returning */ if (debug) { if (ret == 1) zlog_debug( "%s: %s wins over %s after IGP metric comparison", pfx_buf, new_buf, exist_buf); else zlog_debug( "%s: %s loses to %s after IGP metric comparison", pfx_buf, new_buf, exist_buf); } *reason = bgp_path_selection_igp_metric; return ret; } /* 12. If both paths are external, prefer the path that was received first (the oldest one). This step minimizes route-flap, since a newer path won't displace an older one, even if it was the preferred route based on the additional decision criteria below. */ if (!bgp_flag_check(bgp, BGP_FLAG_COMPARE_ROUTER_ID) && new_sort == BGP_PEER_EBGP && exist_sort == BGP_PEER_EBGP) { if (CHECK_FLAG(new->flags, BGP_PATH_SELECTED)) { *reason = bgp_path_selection_older; if (debug) zlog_debug( "%s: %s wins over %s due to oldest external", pfx_buf, new_buf, exist_buf); return 1; } if (CHECK_FLAG(exist->flags, BGP_PATH_SELECTED)) { *reason = bgp_path_selection_older; if (debug) zlog_debug( "%s: %s loses to %s due to oldest external", pfx_buf, new_buf, exist_buf); return 0; } } /* 13. Router-ID comparision. */ /* If one of the paths is "stale", the corresponding peer router-id will * be 0 and would always win over the other path. If originator id is * used for the comparision, it will decide which path is better. */ if (newattr->flag & ATTR_FLAG_BIT(BGP_ATTR_ORIGINATOR_ID)) new_id.s_addr = newattr->originator_id.s_addr; else new_id.s_addr = new->peer->remote_id.s_addr; if (existattr->flag & ATTR_FLAG_BIT(BGP_ATTR_ORIGINATOR_ID)) exist_id.s_addr = existattr->originator_id.s_addr; else exist_id.s_addr = exist->peer->remote_id.s_addr; if (ntohl(new_id.s_addr) < ntohl(exist_id.s_addr)) { *reason = bgp_path_selection_router_id; if (debug) zlog_debug( "%s: %s wins over %s due to Router-ID comparison", pfx_buf, new_buf, exist_buf); return 1; } if (ntohl(new_id.s_addr) > ntohl(exist_id.s_addr)) { *reason = bgp_path_selection_router_id; if (debug) zlog_debug( "%s: %s loses to %s due to Router-ID comparison", pfx_buf, new_buf, exist_buf); return 0; } /* 14. Cluster length comparision. */ new_cluster = BGP_CLUSTER_LIST_LENGTH(new->attr); exist_cluster = BGP_CLUSTER_LIST_LENGTH(exist->attr); if (new_cluster < exist_cluster) { *reason = bgp_path_selection_cluster_length; if (debug) zlog_debug( "%s: %s wins over %s due to CLUSTER_LIST length %d < %d", pfx_buf, new_buf, exist_buf, new_cluster, exist_cluster); return 1; } if (new_cluster > exist_cluster) { *reason = bgp_path_selection_cluster_length; if (debug) zlog_debug( "%s: %s loses to %s due to CLUSTER_LIST length %d > %d", pfx_buf, new_buf, exist_buf, new_cluster, exist_cluster); return 0; } /* 15. Neighbor address comparision. */ /* Do this only if neither path is "stale" as stale paths do not have * valid peer information (as the connection may or may not be up). */ if (CHECK_FLAG(exist->flags, BGP_PATH_STALE)) { *reason = bgp_path_selection_stale; if (debug) zlog_debug( "%s: %s wins over %s due to latter path being STALE", pfx_buf, new_buf, exist_buf); return 1; } if (CHECK_FLAG(new->flags, BGP_PATH_STALE)) { *reason = bgp_path_selection_stale; if (debug) zlog_debug( "%s: %s loses to %s due to former path being STALE", pfx_buf, new_buf, exist_buf); return 0; } /* locally configured routes to advertise do not have su_remote */ if (new->peer->su_remote == NULL) { *reason = bgp_path_selection_local_configured; return 0; } if (exist->peer->su_remote == NULL) { *reason = bgp_path_selection_local_configured; return 1; } ret = sockunion_cmp(new->peer->su_remote, exist->peer->su_remote); if (ret == 1) { *reason = bgp_path_selection_neighbor_ip; if (debug) zlog_debug( "%s: %s loses to %s due to Neighor IP comparison", pfx_buf, new_buf, exist_buf); return 0; } if (ret == -1) { *reason = bgp_path_selection_neighbor_ip; if (debug) zlog_debug( "%s: %s wins over %s due to Neighor IP comparison", pfx_buf, new_buf, exist_buf); return 1; } *reason = bgp_path_selection_default; if (debug) zlog_debug("%s: %s wins over %s due to nothing left to compare", pfx_buf, new_buf, exist_buf); return 1; } /* Compare two bgp route entity. Return -1 if new is preferred, 1 if exist * is preferred, or 0 if they are the same (usually will only occur if * multipath is enabled * This version is compatible with */ int bgp_path_info_cmp_compatible(struct bgp *bgp, struct bgp_path_info *new, struct bgp_path_info *exist, char *pfx_buf, afi_t afi, safi_t safi, enum bgp_path_selection_reason *reason) { int paths_eq; int ret; ret = bgp_path_info_cmp(bgp, new, exist, &paths_eq, NULL, 0, pfx_buf, afi, safi, reason); if (paths_eq) ret = 0; else { if (ret == 1) ret = -1; else ret = 1; } return ret; } static enum filter_type bgp_input_filter(struct peer *peer, struct prefix *p, struct attr *attr, afi_t afi, safi_t safi) { struct bgp_filter *filter; filter = &peer->filter[afi][safi]; #define FILTER_EXIST_WARN(F, f, filter) \ if (BGP_DEBUG(update, UPDATE_IN) && !(F##_IN(filter))) \ zlog_debug("%s: Could not find configured input %s-list %s!", \ peer->host, #f, F##_IN_NAME(filter)); if (DISTRIBUTE_IN_NAME(filter)) { FILTER_EXIST_WARN(DISTRIBUTE, distribute, filter); if (access_list_apply(DISTRIBUTE_IN(filter), p) == FILTER_DENY) return FILTER_DENY; } if (PREFIX_LIST_IN_NAME(filter)) { FILTER_EXIST_WARN(PREFIX_LIST, prefix, filter); if (prefix_list_apply(PREFIX_LIST_IN(filter), p) == PREFIX_DENY) return FILTER_DENY; } if (FILTER_LIST_IN_NAME(filter)) { FILTER_EXIST_WARN(FILTER_LIST, as, filter); if (as_list_apply(FILTER_LIST_IN(filter), attr->aspath) == AS_FILTER_DENY) return FILTER_DENY; } return FILTER_PERMIT; #undef FILTER_EXIST_WARN } static enum filter_type bgp_output_filter(struct peer *peer, struct prefix *p, struct attr *attr, afi_t afi, safi_t safi) { struct bgp_filter *filter; filter = &peer->filter[afi][safi]; #define FILTER_EXIST_WARN(F, f, filter) \ if (BGP_DEBUG(update, UPDATE_OUT) && !(F##_OUT(filter))) \ zlog_debug("%s: Could not find configured output %s-list %s!", \ peer->host, #f, F##_OUT_NAME(filter)); if (DISTRIBUTE_OUT_NAME(filter)) { FILTER_EXIST_WARN(DISTRIBUTE, distribute, filter); if (access_list_apply(DISTRIBUTE_OUT(filter), p) == FILTER_DENY) return FILTER_DENY; } if (PREFIX_LIST_OUT_NAME(filter)) { FILTER_EXIST_WARN(PREFIX_LIST, prefix, filter); if (prefix_list_apply(PREFIX_LIST_OUT(filter), p) == PREFIX_DENY) return FILTER_DENY; } if (FILTER_LIST_OUT_NAME(filter)) { FILTER_EXIST_WARN(FILTER_LIST, as, filter); if (as_list_apply(FILTER_LIST_OUT(filter), attr->aspath) == AS_FILTER_DENY) return FILTER_DENY; } return FILTER_PERMIT; #undef FILTER_EXIST_WARN } /* If community attribute includes no_export then return 1. */ static int bgp_community_filter(struct peer *peer, struct attr *attr) { if (attr->community) { /* NO_ADVERTISE check. */ if (community_include(attr->community, COMMUNITY_NO_ADVERTISE)) return 1; /* NO_EXPORT check. */ if (peer->sort == BGP_PEER_EBGP && community_include(attr->community, COMMUNITY_NO_EXPORT)) return 1; /* NO_EXPORT_SUBCONFED check. */ if (peer->sort == BGP_PEER_EBGP || peer->sort == BGP_PEER_CONFED) if (community_include(attr->community, COMMUNITY_NO_EXPORT_SUBCONFED)) return 1; } return 0; } /* Route reflection loop check. */ static int bgp_cluster_filter(struct peer *peer, struct attr *attr) { struct in_addr cluster_id; if (attr->cluster) { if (peer->bgp->config & BGP_CONFIG_CLUSTER_ID) cluster_id = peer->bgp->cluster_id; else cluster_id = peer->bgp->router_id; if (cluster_loop_check(attr->cluster, cluster_id)) return 1; } return 0; } static int bgp_input_modifier(struct peer *peer, struct prefix *p, struct attr *attr, afi_t afi, safi_t safi, const char *rmap_name, mpls_label_t *label, uint32_t num_labels) { struct bgp_filter *filter; struct bgp_path_info rmap_path = { 0 }; struct bgp_path_info_extra extra = { 0 }; route_map_result_t ret; struct route_map *rmap = NULL; filter = &peer->filter[afi][safi]; /* Apply default weight value. */ if (peer->weight[afi][safi]) attr->weight = peer->weight[afi][safi]; if (rmap_name) { rmap = route_map_lookup_by_name(rmap_name); if (rmap == NULL) return RMAP_DENY; } else { if (ROUTE_MAP_IN_NAME(filter)) { rmap = ROUTE_MAP_IN(filter); if (rmap == NULL) return RMAP_DENY; } } /* Route map apply. */ if (rmap) { memset(&rmap_path, 0, sizeof(struct bgp_path_info)); /* Duplicate current value to new strucutre for modification. */ rmap_path.peer = peer; rmap_path.attr = attr; rmap_path.extra = &extra; extra.num_labels = num_labels; if (label && num_labels && num_labels <= BGP_MAX_LABELS) memcpy(extra.label, label, num_labels * sizeof(mpls_label_t)); SET_FLAG(peer->rmap_type, PEER_RMAP_TYPE_IN); /* Apply BGP route map to the attribute. */ ret = route_map_apply(rmap, p, RMAP_BGP, &rmap_path); peer->rmap_type = 0; if (ret == RMAP_DENYMATCH) return RMAP_DENY; } return RMAP_PERMIT; } static int bgp_output_modifier(struct peer *peer, struct prefix *p, struct attr *attr, afi_t afi, safi_t safi, const char *rmap_name) { struct bgp_path_info rmap_path; route_map_result_t ret; struct route_map *rmap = NULL; uint8_t rmap_type; /* * So if we get to this point and have no rmap_name * we want to just show the output as it currently * exists. */ if (!rmap_name) return RMAP_PERMIT; /* Apply default weight value. */ if (peer->weight[afi][safi]) attr->weight = peer->weight[afi][safi]; rmap = route_map_lookup_by_name(rmap_name); /* * If we have a route map name and we do not find * the routemap that means we have an implicit * deny. */ if (rmap == NULL) return RMAP_DENY; memset(&rmap_path, 0, sizeof(struct bgp_path_info)); /* Route map apply. */ /* Duplicate current value to new strucutre for modification. */ rmap_path.peer = peer; rmap_path.attr = attr; rmap_type = peer->rmap_type; SET_FLAG(peer->rmap_type, PEER_RMAP_TYPE_OUT); /* Apply BGP route map to the attribute. */ ret = route_map_apply(rmap, p, RMAP_BGP, &rmap_path); peer->rmap_type = rmap_type; if (ret == RMAP_DENYMATCH) /* * caller has multiple error paths with bgp_attr_flush() */ return RMAP_DENY; return RMAP_PERMIT; } /* If this is an EBGP peer with remove-private-AS */ static void bgp_peer_remove_private_as(struct bgp *bgp, afi_t afi, safi_t safi, struct peer *peer, struct attr *attr) { if (peer->sort == BGP_PEER_EBGP && (peer_af_flag_check(peer, afi, safi, PEER_FLAG_REMOVE_PRIVATE_AS_ALL_REPLACE) || peer_af_flag_check(peer, afi, safi, PEER_FLAG_REMOVE_PRIVATE_AS_REPLACE) || peer_af_flag_check(peer, afi, safi, PEER_FLAG_REMOVE_PRIVATE_AS_ALL) || peer_af_flag_check(peer, afi, safi, PEER_FLAG_REMOVE_PRIVATE_AS))) { // Take action on the entire aspath if (peer_af_flag_check(peer, afi, safi, PEER_FLAG_REMOVE_PRIVATE_AS_ALL_REPLACE) || peer_af_flag_check(peer, afi, safi, PEER_FLAG_REMOVE_PRIVATE_AS_ALL)) { if (peer_af_flag_check( peer, afi, safi, PEER_FLAG_REMOVE_PRIVATE_AS_ALL_REPLACE)) attr->aspath = aspath_replace_private_asns( attr->aspath, bgp->as, peer->as); // The entire aspath consists of private ASNs so create // an empty aspath else if (aspath_private_as_check(attr->aspath)) attr->aspath = aspath_empty_get(); // There are some public and some private ASNs, remove // the private ASNs else attr->aspath = aspath_remove_private_asns( attr->aspath, peer->as); } // 'all' was not specified so the entire aspath must be private // ASNs // for us to do anything else if (aspath_private_as_check(attr->aspath)) { if (peer_af_flag_check( peer, afi, safi, PEER_FLAG_REMOVE_PRIVATE_AS_REPLACE)) attr->aspath = aspath_replace_private_asns( attr->aspath, bgp->as, peer->as); else attr->aspath = aspath_empty_get(); } } } /* If this is an EBGP peer with as-override */ static void bgp_peer_as_override(struct bgp *bgp, afi_t afi, safi_t safi, struct peer *peer, struct attr *attr) { if (peer->sort == BGP_PEER_EBGP && peer_af_flag_check(peer, afi, safi, PEER_FLAG_AS_OVERRIDE)) { if (aspath_single_asn_check(attr->aspath, peer->as)) attr->aspath = aspath_replace_specific_asn( attr->aspath, peer->as, bgp->as); } } void bgp_attr_add_gshut_community(struct attr *attr) { struct community *old; struct community *new; struct community *merge; struct community *gshut; old = attr->community; gshut = community_str2com("graceful-shutdown"); assert(gshut); if (old) { merge = community_merge(community_dup(old), gshut); if (old->refcnt == 0) community_free(&old); new = community_uniq_sort(merge); community_free(&merge); } else { new = community_dup(gshut); } community_free(&gshut); attr->community = new; attr->flag |= ATTR_FLAG_BIT(BGP_ATTR_COMMUNITIES); /* When we add the graceful-shutdown community we must also * lower the local-preference */ attr->flag |= ATTR_FLAG_BIT(BGP_ATTR_LOCAL_PREF); attr->local_pref = BGP_GSHUT_LOCAL_PREF; } static void subgroup_announce_reset_nhop(uint8_t family, struct attr *attr) { if (family == AF_INET) { attr->nexthop.s_addr = 0; attr->mp_nexthop_global_in.s_addr = 0; } if (family == AF_INET6) memset(&attr->mp_nexthop_global, 0, IPV6_MAX_BYTELEN); if (family == AF_EVPN) memset(&attr->mp_nexthop_global_in, 0, BGP_ATTR_NHLEN_IPV4); } int subgroup_announce_check(struct bgp_node *rn, struct bgp_path_info *pi, struct update_subgroup *subgrp, struct prefix *p, struct attr *attr) { struct bgp_filter *filter; struct peer *from; struct peer *peer; struct peer *onlypeer; struct bgp *bgp; struct attr *piattr; char buf[PREFIX_STRLEN]; route_map_result_t ret; int transparent; int reflect; afi_t afi; safi_t safi; int samepeer_safe = 0; /* for synthetic mplsvpns routes */ if (DISABLE_BGP_ANNOUNCE) return 0; afi = SUBGRP_AFI(subgrp); safi = SUBGRP_SAFI(subgrp); peer = SUBGRP_PEER(subgrp); onlypeer = NULL; if (CHECK_FLAG(peer->flags, PEER_FLAG_LONESOUL)) onlypeer = SUBGRP_PFIRST(subgrp)->peer; from = pi->peer; filter = &peer->filter[afi][safi]; bgp = SUBGRP_INST(subgrp); piattr = bgp_path_info_mpath_count(pi) ? bgp_path_info_mpath_attr(pi) : pi->attr; #if ENABLE_BGP_VNC if (((afi == AFI_IP) || (afi == AFI_IP6)) && (safi == SAFI_MPLS_VPN) && ((pi->type == ZEBRA_ROUTE_BGP_DIRECT) || (pi->type == ZEBRA_ROUTE_BGP_DIRECT_EXT))) { /* * direct and direct_ext type routes originate internally even * though they can have peer pointers that reference other * systems */ prefix2str(p, buf, PREFIX_STRLEN); zlog_debug("%s: pfx %s bgp_direct->vpn route peer safe", __func__, buf); samepeer_safe = 1; } #endif if (((afi == AFI_IP) || (afi == AFI_IP6)) && ((safi == SAFI_MPLS_VPN) || (safi == SAFI_UNICAST)) && (pi->type == ZEBRA_ROUTE_BGP) && (pi->sub_type == BGP_ROUTE_IMPORTED)) { /* Applies to routes leaked vpn->vrf and vrf->vpn */ samepeer_safe = 1; } /* With addpath we may be asked to TX all kinds of paths so make sure * pi is valid */ if (!CHECK_FLAG(pi->flags, BGP_PATH_VALID) || CHECK_FLAG(pi->flags, BGP_PATH_HISTORY) || CHECK_FLAG(pi->flags, BGP_PATH_REMOVED)) { return 0; } /* If this is not the bestpath then check to see if there is an enabled * addpath * feature that requires us to advertise it */ if (!CHECK_FLAG(pi->flags, BGP_PATH_SELECTED)) { if (!bgp_addpath_tx_path(peer->addpath_type[afi][safi], pi)) { return 0; } } /* Aggregate-address suppress check. */ if (pi->extra && pi->extra->suppress) if (!UNSUPPRESS_MAP_NAME(filter)) { return 0; } /* * If we are doing VRF 2 VRF leaking via the import * statement, we want to prevent the route going * off box as that the RT and RD created are localy * significant and globaly useless. */ if (safi == SAFI_MPLS_VPN && pi->extra && pi->extra->num_labels && pi->extra->label[0] == BGP_PREVENT_VRF_2_VRF_LEAK) return 0; /* If it's labeled safi, make sure the route has a valid label. */ if (safi == SAFI_LABELED_UNICAST) { mpls_label_t label = bgp_adv_label(rn, pi, peer, afi, safi); if (!bgp_is_valid_label(&label)) { if (bgp_debug_update(NULL, p, subgrp->update_group, 0)) zlog_debug("u%" PRIu64 ":s%" PRIu64 " %s/%d is filtered - no label (%p)", subgrp->update_group->id, subgrp->id, inet_ntop(p->family, &p->u.prefix, buf, SU_ADDRSTRLEN), p->prefixlen, &label); return 0; } } /* Do not send back route to sender. */ if (onlypeer && from == onlypeer) { return 0; } /* Do not send the default route in the BGP table if the neighbor is * configured for default-originate */ if (CHECK_FLAG(peer->af_flags[afi][safi], PEER_FLAG_DEFAULT_ORIGINATE)) { if (p->family == AF_INET && p->u.prefix4.s_addr == INADDR_ANY) return 0; else if (p->family == AF_INET6 && p->prefixlen == 0) return 0; } /* Transparency check. */ if (CHECK_FLAG(peer->af_flags[afi][safi], PEER_FLAG_RSERVER_CLIENT) && CHECK_FLAG(from->af_flags[afi][safi], PEER_FLAG_RSERVER_CLIENT)) transparent = 1; else transparent = 0; /* If community is not disabled check the no-export and local. */ if (!transparent && bgp_community_filter(peer, piattr)) { if (bgp_debug_update(NULL, p, subgrp->update_group, 0)) zlog_debug( "subgrpannouncecheck: community filter check fail"); return 0; } /* If the attribute has originator-id and it is same as remote peer's id. */ if (onlypeer && piattr->flag & ATTR_FLAG_BIT(BGP_ATTR_ORIGINATOR_ID) && (IPV4_ADDR_SAME(&onlypeer->remote_id, &piattr->originator_id))) { if (bgp_debug_update(NULL, p, subgrp->update_group, 0)) zlog_debug( "%s [Update:SEND] %s originator-id is same as " "remote router-id", onlypeer->host, prefix2str(p, buf, sizeof(buf))); return 0; } /* ORF prefix-list filter check */ if (CHECK_FLAG(peer->af_cap[afi][safi], PEER_CAP_ORF_PREFIX_RM_ADV) && (CHECK_FLAG(peer->af_cap[afi][safi], PEER_CAP_ORF_PREFIX_SM_RCV) || CHECK_FLAG(peer->af_cap[afi][safi], PEER_CAP_ORF_PREFIX_SM_OLD_RCV))) if (peer->orf_plist[afi][safi]) { if (prefix_list_apply(peer->orf_plist[afi][safi], p) == PREFIX_DENY) { if (bgp_debug_update(NULL, p, subgrp->update_group, 0)) zlog_debug( "%s [Update:SEND] %s is filtered via ORF", peer->host, prefix2str(p, buf, sizeof(buf))); return 0; } } /* Output filter check. */ if (bgp_output_filter(peer, p, piattr, afi, safi) == FILTER_DENY) { if (bgp_debug_update(NULL, p, subgrp->update_group, 0)) zlog_debug("%s [Update:SEND] %s is filtered", peer->host, prefix2str(p, buf, sizeof(buf))); return 0; } #ifdef BGP_SEND_ASPATH_CHECK /* AS path loop check. */ if (onlypeer && aspath_loop_check(piattr->aspath, onlypeer->as)) { if (bgp_debug_update(NULL, p, subgrp->update_group, 0)) zlog_debug( "%s [Update:SEND] suppress announcement to peer AS %u " "that is part of AS path.", onlypeer->host, onlypeer->as); return 0; } #endif /* BGP_SEND_ASPATH_CHECK */ /* If we're a CONFED we need to loop check the CONFED ID too */ if (CHECK_FLAG(bgp->config, BGP_CONFIG_CONFEDERATION)) { if (aspath_loop_check(piattr->aspath, bgp->confed_id)) { if (bgp_debug_update(NULL, p, subgrp->update_group, 0)) zlog_debug( "%s [Update:SEND] suppress announcement to peer AS %u" " is AS path.", peer->host, bgp->confed_id); return 0; } } /* Route-Reflect check. */ if (from->sort == BGP_PEER_IBGP && peer->sort == BGP_PEER_IBGP) reflect = 1; else reflect = 0; /* IBGP reflection check. */ if (reflect && !samepeer_safe) { /* A route from a Client peer. */ if (CHECK_FLAG(from->af_flags[afi][safi], PEER_FLAG_REFLECTOR_CLIENT)) { /* Reflect to all the Non-Client peers and also to the Client peers other than the originator. Originator check is already done. So there is noting to do. */ /* no bgp client-to-client reflection check. */ if (bgp_flag_check(bgp, BGP_FLAG_NO_CLIENT_TO_CLIENT)) if (CHECK_FLAG(peer->af_flags[afi][safi], PEER_FLAG_REFLECTOR_CLIENT)) return 0; } else { /* A route from a Non-client peer. Reflect to all other clients. */ if (!CHECK_FLAG(peer->af_flags[afi][safi], PEER_FLAG_REFLECTOR_CLIENT)) return 0; } } /* For modify attribute, copy it to temporary structure. */ *attr = *piattr; /* If local-preference is not set. */ if ((peer->sort == BGP_PEER_IBGP || peer->sort == BGP_PEER_CONFED) && (!(attr->flag & ATTR_FLAG_BIT(BGP_ATTR_LOCAL_PREF)))) { attr->flag |= ATTR_FLAG_BIT(BGP_ATTR_LOCAL_PREF); attr->local_pref = bgp->default_local_pref; } /* If originator-id is not set and the route is to be reflected, set the originator id */ if (reflect && (!(attr->flag & ATTR_FLAG_BIT(BGP_ATTR_ORIGINATOR_ID)))) { IPV4_ADDR_COPY(&(attr->originator_id), &(from->remote_id)); SET_FLAG(attr->flag, BGP_ATTR_ORIGINATOR_ID); } /* Remove MED if its an EBGP peer - will get overwritten by route-maps */ if (peer->sort == BGP_PEER_EBGP && attr->flag & ATTR_FLAG_BIT(BGP_ATTR_MULTI_EXIT_DISC)) { if (from != bgp->peer_self && !transparent && !CHECK_FLAG(peer->af_flags[afi][safi], PEER_FLAG_MED_UNCHANGED)) attr->flag &= ~(ATTR_FLAG_BIT(BGP_ATTR_MULTI_EXIT_DISC)); } /* Since the nexthop attribute can vary per peer, it is not explicitly * set * in announce check, only certain flags and length (or number of * nexthops * -- for IPv6/MP_REACH) are set here in order to guide the update * formation * code in setting the nexthop(s) on a per peer basis in * reformat_peer(). * Typically, the source nexthop in the attribute is preserved but in * the * scenarios where we know it will always be overwritten, we reset the * nexthop to "0" in an attempt to achieve better Update packing. An * example of this is when a prefix from each of 2 IBGP peers needs to * be * announced to an EBGP peer (and they have the same attributes barring * their nexthop). */ if (reflect) SET_FLAG(attr->rmap_change_flags, BATTR_REFLECTED); #define NEXTHOP_IS_V6 \ ((safi != SAFI_ENCAP && safi != SAFI_MPLS_VPN \ && (p->family == AF_INET6 || peer_cap_enhe(peer, afi, safi))) \ || ((safi == SAFI_ENCAP || safi == SAFI_MPLS_VPN) \ && attr->mp_nexthop_len >= IPV6_MAX_BYTELEN)) /* IPv6/MP starts with 1 nexthop. The link-local address is passed only * if * the peer (group) is configured to receive link-local nexthop * unchanged * and it is available in the prefix OR we're not reflecting the route, * link-local nexthop address is valid and * the peer (group) to whom we're going to announce is on a shared * network * and this is either a self-originated route or the peer is EBGP. * By checking if nexthop LL address is valid we are sure that * we do not announce LL address as `::`. */ if (NEXTHOP_IS_V6) { attr->mp_nexthop_len = BGP_ATTR_NHLEN_IPV6_GLOBAL; if ((CHECK_FLAG(peer->af_flags[afi][safi], PEER_FLAG_NEXTHOP_LOCAL_UNCHANGED) && IN6_IS_ADDR_LINKLOCAL(&attr->mp_nexthop_local)) || (!reflect && IN6_IS_ADDR_LINKLOCAL(&peer->nexthop.v6_local) && peer->shared_network && (from == bgp->peer_self || peer->sort == BGP_PEER_EBGP))) { attr->mp_nexthop_len = BGP_ATTR_NHLEN_IPV6_GLOBAL_AND_LL; } /* Clear off link-local nexthop in source, whenever it is not * needed to * ensure more prefixes share the same attribute for * announcement. */ if (!(CHECK_FLAG(peer->af_flags[afi][safi], PEER_FLAG_NEXTHOP_LOCAL_UNCHANGED))) memset(&attr->mp_nexthop_local, 0, IPV6_MAX_BYTELEN); } bgp_peer_remove_private_as(bgp, afi, safi, peer, attr); bgp_peer_as_override(bgp, afi, safi, peer, attr); /* Route map & unsuppress-map apply. */ if (ROUTE_MAP_OUT_NAME(filter) || (pi->extra && pi->extra->suppress)) { struct bgp_path_info rmap_path = {0}; struct bgp_path_info_extra dummy_rmap_path_extra = {0}; struct attr dummy_attr = {0}; memset(&rmap_path, 0, sizeof(struct bgp_path_info)); rmap_path.peer = peer; rmap_path.attr = attr; if (pi->extra) { memcpy(&dummy_rmap_path_extra, pi->extra, sizeof(struct bgp_path_info_extra)); rmap_path.extra = &dummy_rmap_path_extra; } /* don't confuse inbound and outbound setting */ RESET_FLAG(attr->rmap_change_flags); /* * The route reflector is not allowed to modify the attributes * of the reflected IBGP routes unless explicitly allowed. */ if ((from->sort == BGP_PEER_IBGP && peer->sort == BGP_PEER_IBGP) && !bgp_flag_check(bgp, BGP_FLAG_RR_ALLOW_OUTBOUND_POLICY)) { dummy_attr = *attr; rmap_path.attr = &dummy_attr; } SET_FLAG(peer->rmap_type, PEER_RMAP_TYPE_OUT); if (pi->extra && pi->extra->suppress) ret = route_map_apply(UNSUPPRESS_MAP(filter), p, RMAP_BGP, &rmap_path); else ret = route_map_apply(ROUTE_MAP_OUT(filter), p, RMAP_BGP, &rmap_path); peer->rmap_type = 0; if (ret == RMAP_DENYMATCH) { if (bgp_debug_update(NULL, p, subgrp->update_group, 0)) zlog_debug("%s [Update:SEND] %s is filtered by route-map", peer->host, prefix2str(p, buf, sizeof(buf))); bgp_attr_flush(attr); return 0; } } /* RFC 8212 to prevent route leaks. * This specification intends to improve this situation by requiring the * explicit configuration of both BGP Import and Export Policies for any * External BGP (EBGP) session such as customers, peers, or * confederation boundaries for all enabled address families. Through * codification of the aforementioned requirement, operators will * benefit from consistent behavior across different BGP * implementations. */ if (peer->bgp->ebgp_requires_policy == DEFAULT_EBGP_POLICY_ENABLED) if (!bgp_outbound_policy_exists(peer, filter)) return 0; if (bgp_flag_check(bgp, BGP_FLAG_GRACEFUL_SHUTDOWN)) { if (peer->sort == BGP_PEER_IBGP || peer->sort == BGP_PEER_CONFED) { attr->flag |= ATTR_FLAG_BIT(BGP_ATTR_LOCAL_PREF); attr->local_pref = BGP_GSHUT_LOCAL_PREF; } else { bgp_attr_add_gshut_community(attr); } } /* After route-map has been applied, we check to see if the nexthop to * be carried in the attribute (that is used for the announcement) can * be cleared off or not. We do this in all cases where we would be * setting the nexthop to "ourselves". For IPv6, we only need to * consider * the global nexthop here; the link-local nexthop would have been * cleared * already, and if not, it is required by the update formation code. * Also see earlier comments in this function. */ /* * If route-map has performed some operation on the nexthop or the peer * configuration says to pass it unchanged, we cannot reset the nexthop * here, so only attempt to do it if these aren't true. Note that the * route-map handler itself might have cleared the nexthop, if for * example, * it is configured as 'peer-address'. */ if (!bgp_rmap_nhop_changed(attr->rmap_change_flags, piattr->rmap_change_flags) && !transparent && !CHECK_FLAG(peer->af_flags[afi][safi], PEER_FLAG_NEXTHOP_UNCHANGED)) { /* We can reset the nexthop, if setting (or forcing) it to * 'self' */ if (CHECK_FLAG(peer->af_flags[afi][safi], PEER_FLAG_NEXTHOP_SELF) || CHECK_FLAG(peer->af_flags[afi][safi], PEER_FLAG_FORCE_NEXTHOP_SELF)) { if (!reflect || CHECK_FLAG(peer->af_flags[afi][safi], PEER_FLAG_FORCE_NEXTHOP_SELF)) subgroup_announce_reset_nhop( (peer_cap_enhe(peer, afi, safi) ? AF_INET6 : p->family), attr); } else if (peer->sort == BGP_PEER_EBGP) { /* Can also reset the nexthop if announcing to EBGP, but * only if * no peer in the subgroup is on a shared subnet. * Note: 3rd party nexthop currently implemented for * IPv4 only. */ if ((p->family == AF_INET) && (!bgp_subgrp_multiaccess_check_v4( piattr->nexthop, subgrp))) subgroup_announce_reset_nhop( (peer_cap_enhe(peer, afi, safi) ? AF_INET6 : p->family), attr); if ((p->family == AF_INET6) && (!bgp_subgrp_multiaccess_check_v6( piattr->mp_nexthop_global, subgrp))) subgroup_announce_reset_nhop( (peer_cap_enhe(peer, afi, safi) ? AF_INET6 : p->family), attr); } else if (CHECK_FLAG(pi->flags, BGP_PATH_ANNC_NH_SELF)) { /* * This flag is used for leaked vpn-vrf routes */ int family = p->family; if (peer_cap_enhe(peer, afi, safi)) family = AF_INET6; if (bgp_debug_update(NULL, p, subgrp->update_group, 0)) zlog_debug( "%s: BGP_PATH_ANNC_NH_SELF, family=%s", __func__, family2str(family)); subgroup_announce_reset_nhop(family, attr); } /* If IPv6/MP and nexthop does not have any override and happens * to * be a link-local address, reset it so that we don't pass along * the * source's link-local IPv6 address to recipients who may not be * on * the same interface. */ if (p->family == AF_INET6 || peer_cap_enhe(peer, afi, safi)) { if (IN6_IS_ADDR_LINKLOCAL(&attr->mp_nexthop_global)) subgroup_announce_reset_nhop(AF_INET6, attr); } } return 1; } void bgp_best_selection(struct bgp *bgp, struct bgp_node *rn, struct bgp_maxpaths_cfg *mpath_cfg, struct bgp_path_info_pair *result, afi_t afi, safi_t safi) { struct bgp_path_info *new_select; struct bgp_path_info *old_select; struct bgp_path_info *pi; struct bgp_path_info *pi1; struct bgp_path_info *pi2; struct bgp_path_info *nextpi = NULL; int paths_eq, do_mpath, debug; struct list mp_list; char pfx_buf[PREFIX2STR_BUFFER]; char path_buf[PATH_ADDPATH_STR_BUFFER]; bgp_mp_list_init(&mp_list); do_mpath = (mpath_cfg->maxpaths_ebgp > 1 || mpath_cfg->maxpaths_ibgp > 1); debug = bgp_debug_bestpath(&rn->p); if (debug) prefix2str(&rn->p, pfx_buf, sizeof(pfx_buf)); /* bgp deterministic-med */ new_select = NULL; if (bgp_flag_check(bgp, BGP_FLAG_DETERMINISTIC_MED)) { /* Clear BGP_PATH_DMED_SELECTED for all paths */ for (pi1 = bgp_node_get_bgp_path_info(rn); pi1; pi1 = pi1->next) bgp_path_info_unset_flag(rn, pi1, BGP_PATH_DMED_SELECTED); for (pi1 = bgp_node_get_bgp_path_info(rn); pi1; pi1 = pi1->next) { if (CHECK_FLAG(pi1->flags, BGP_PATH_DMED_CHECK)) continue; if (BGP_PATH_HOLDDOWN(pi1)) continue; if (pi1->peer != bgp->peer_self) if (pi1->peer->status != Established) continue; new_select = pi1; if (pi1->next) { for (pi2 = pi1->next; pi2; pi2 = pi2->next) { if (CHECK_FLAG(pi2->flags, BGP_PATH_DMED_CHECK)) continue; if (BGP_PATH_HOLDDOWN(pi2)) continue; if (pi2->peer != bgp->peer_self && !CHECK_FLAG( pi2->peer->sflags, PEER_STATUS_NSF_WAIT)) if (pi2->peer->status != Established) continue; if (!aspath_cmp_left(pi1->attr->aspath, pi2->attr->aspath) && !aspath_cmp_left_confed( pi1->attr->aspath, pi2->attr->aspath)) continue; if (bgp_path_info_cmp( bgp, pi2, new_select, &paths_eq, mpath_cfg, debug, pfx_buf, afi, safi, &rn->reason)) { bgp_path_info_unset_flag( rn, new_select, BGP_PATH_DMED_SELECTED); new_select = pi2; } bgp_path_info_set_flag( rn, pi2, BGP_PATH_DMED_CHECK); } } bgp_path_info_set_flag(rn, new_select, BGP_PATH_DMED_CHECK); bgp_path_info_set_flag(rn, new_select, BGP_PATH_DMED_SELECTED); if (debug) { bgp_path_info_path_with_addpath_rx_str( new_select, path_buf); zlog_debug("%s: %s is the bestpath from AS %u", pfx_buf, path_buf, aspath_get_first_as( new_select->attr->aspath)); } } } /* Check old selected route and new selected route. */ old_select = NULL; new_select = NULL; for (pi = bgp_node_get_bgp_path_info(rn); (pi != NULL) && (nextpi = pi->next, 1); pi = nextpi) { if (CHECK_FLAG(pi->flags, BGP_PATH_SELECTED)) old_select = pi; if (BGP_PATH_HOLDDOWN(pi)) { /* reap REMOVED routes, if needs be * selected route must stay for a while longer though */ if (CHECK_FLAG(pi->flags, BGP_PATH_REMOVED) && (pi != old_select)) bgp_path_info_reap(rn, pi); if (debug) zlog_debug("%s: pi %p in holddown", __func__, pi); continue; } if (pi->peer && pi->peer != bgp->peer_self && !CHECK_FLAG(pi->peer->sflags, PEER_STATUS_NSF_WAIT)) if (pi->peer->status != Established) { if (debug) zlog_debug( "%s: pi %p non self peer %s not estab state", __func__, pi, pi->peer->host); continue; } if (bgp_flag_check(bgp, BGP_FLAG_DETERMINISTIC_MED) && (!CHECK_FLAG(pi->flags, BGP_PATH_DMED_SELECTED))) { bgp_path_info_unset_flag(rn, pi, BGP_PATH_DMED_CHECK); if (debug) zlog_debug("%s: pi %p dmed", __func__, pi); continue; } bgp_path_info_unset_flag(rn, pi, BGP_PATH_DMED_CHECK); if (bgp_path_info_cmp(bgp, pi, new_select, &paths_eq, mpath_cfg, debug, pfx_buf, afi, safi, &rn->reason)) { new_select = pi; } } /* Now that we know which path is the bestpath see if any of the other * paths * qualify as multipaths */ if (debug) { if (new_select) bgp_path_info_path_with_addpath_rx_str(new_select, path_buf); else sprintf(path_buf, "NONE"); zlog_debug( "%s: After path selection, newbest is %s oldbest was %s", pfx_buf, path_buf, old_select ? old_select->peer->host : "NONE"); } if (do_mpath && new_select) { for (pi = bgp_node_get_bgp_path_info(rn); (pi != NULL) && (nextpi = pi->next, 1); pi = nextpi) { if (debug) bgp_path_info_path_with_addpath_rx_str( pi, path_buf); if (pi == new_select) { if (debug) zlog_debug( "%s: %s is the bestpath, add to the multipath list", pfx_buf, path_buf); bgp_mp_list_add(&mp_list, pi); continue; } if (BGP_PATH_HOLDDOWN(pi)) continue; if (pi->peer && pi->peer != bgp->peer_self && !CHECK_FLAG(pi->peer->sflags, PEER_STATUS_NSF_WAIT)) if (pi->peer->status != Established) continue; if (!bgp_path_info_nexthop_cmp(pi, new_select)) { if (debug) zlog_debug( "%s: %s has the same nexthop as the bestpath, skip it", pfx_buf, path_buf); continue; } bgp_path_info_cmp(bgp, pi, new_select, &paths_eq, mpath_cfg, debug, pfx_buf, afi, safi, &rn->reason); if (paths_eq) { if (debug) zlog_debug( "%s: %s is equivalent to the bestpath, add to the multipath list", pfx_buf, path_buf); bgp_mp_list_add(&mp_list, pi); } } } bgp_path_info_mpath_update(rn, new_select, old_select, &mp_list, mpath_cfg); bgp_path_info_mpath_aggregate_update(new_select, old_select); bgp_mp_list_clear(&mp_list); bgp_addpath_update_ids(bgp, rn, afi, safi); result->old = old_select; result->new = new_select; return; } /* * A new route/change in bestpath of an existing route. Evaluate the path * for advertisement to the subgroup. */ int subgroup_process_announce_selected(struct update_subgroup *subgrp, struct bgp_path_info *selected, struct bgp_node *rn, uint32_t addpath_tx_id) { struct prefix *p; struct peer *onlypeer; struct attr attr; afi_t afi; safi_t safi; p = &rn->p; afi = SUBGRP_AFI(subgrp); safi = SUBGRP_SAFI(subgrp); onlypeer = ((SUBGRP_PCOUNT(subgrp) == 1) ? (SUBGRP_PFIRST(subgrp))->peer : NULL); if (BGP_DEBUG(update, UPDATE_OUT)) { char buf_prefix[PREFIX_STRLEN]; prefix2str(p, buf_prefix, sizeof(buf_prefix)); zlog_debug("%s: p=%s, selected=%p", __func__, buf_prefix, selected); } /* First update is deferred until ORF or ROUTE-REFRESH is received */ if (onlypeer && CHECK_FLAG(onlypeer->af_sflags[afi][safi], PEER_STATUS_ORF_WAIT_REFRESH)) return 0; memset(&attr, 0, sizeof(struct attr)); /* It's initialized in bgp_announce_check() */ /* Announcement to the subgroup. If the route is filtered withdraw it. */ if (selected) { if (subgroup_announce_check(rn, selected, subgrp, p, &attr)) bgp_adj_out_set_subgroup(rn, subgrp, &attr, selected); else bgp_adj_out_unset_subgroup(rn, subgrp, 1, addpath_tx_id); } /* If selected is NULL we must withdraw the path using addpath_tx_id */ else { bgp_adj_out_unset_subgroup(rn, subgrp, 1, addpath_tx_id); } return 0; } /* * Clear IGP changed flag and attribute changed flag for a route (all paths). * This is called at the end of route processing. */ void bgp_zebra_clear_route_change_flags(struct bgp_node *rn) { struct bgp_path_info *pi; for (pi = bgp_node_get_bgp_path_info(rn); pi; pi = pi->next) { if (BGP_PATH_HOLDDOWN(pi)) continue; UNSET_FLAG(pi->flags, BGP_PATH_IGP_CHANGED); UNSET_FLAG(pi->flags, BGP_PATH_ATTR_CHANGED); } } /* * Has the route changed from the RIB's perspective? This is invoked only * if the route selection returns the same best route as earlier - to * determine if we need to update zebra or not. */ int bgp_zebra_has_route_changed(struct bgp_node *rn, struct bgp_path_info *selected) { struct bgp_path_info *mpinfo; /* If this is multipath, check all selected paths for any nexthop * change or attribute change. Some attribute changes (e.g., community) * aren't of relevance to the RIB, but we'll update zebra to ensure * we handle the case of BGP nexthop change. This is the behavior * when the best path has an attribute change anyway. */ if (CHECK_FLAG(selected->flags, BGP_PATH_IGP_CHANGED) || CHECK_FLAG(selected->flags, BGP_PATH_MULTIPATH_CHG)) return 1; /* * If this is multipath, check all selected paths for any nexthop change */ for (mpinfo = bgp_path_info_mpath_first(selected); mpinfo; mpinfo = bgp_path_info_mpath_next(mpinfo)) { if (CHECK_FLAG(mpinfo->flags, BGP_PATH_IGP_CHANGED) || CHECK_FLAG(mpinfo->flags, BGP_PATH_ATTR_CHANGED)) return 1; } /* Nothing has changed from the RIB's perspective. */ return 0; } struct bgp_process_queue { struct bgp *bgp; STAILQ_HEAD(, bgp_node) pqueue; #define BGP_PROCESS_QUEUE_EOIU_MARKER (1 << 0) unsigned int flags; unsigned int queued; }; /* * old_select = The old best path * new_select = the new best path * * if (!old_select && new_select) * We are sending new information on. * * if (old_select && new_select) { * if (new_select != old_select) * We have a new best path send a change * else * We've received a update with new attributes that needs * to be passed on. * } * * if (old_select && !new_select) * We have no eligible route that we can announce or the rn * is being removed. */ static void bgp_process_main_one(struct bgp *bgp, struct bgp_node *rn, afi_t afi, safi_t safi) { struct bgp_path_info *new_select; struct bgp_path_info *old_select; struct bgp_path_info_pair old_and_new; char pfx_buf[PREFIX2STR_BUFFER]; int debug = 0; if (bgp_flag_check(bgp, BGP_FLAG_DELETE_IN_PROGRESS)) { if (rn) debug = bgp_debug_bestpath(&rn->p); if (debug) { prefix2str(&rn->p, pfx_buf, sizeof(pfx_buf)); zlog_debug( "%s: bgp delete in progress, ignoring event, p=%s", __func__, pfx_buf); } return; } /* Is it end of initial update? (after startup) */ if (!rn) { quagga_timestamp(3, bgp->update_delay_zebra_resume_time, sizeof(bgp->update_delay_zebra_resume_time)); bgp->main_zebra_update_hold = 0; FOREACH_AFI_SAFI (afi, safi) { if (bgp_fibupd_safi(safi)) bgp_zebra_announce_table(bgp, afi, safi); } bgp->main_peers_update_hold = 0; bgp_start_routeadv(bgp); return; } struct prefix *p = &rn->p; debug = bgp_debug_bestpath(&rn->p); if (debug) { prefix2str(&rn->p, pfx_buf, sizeof(pfx_buf)); zlog_debug("%s: p=%s afi=%s, safi=%s start", __func__, pfx_buf, afi2str(afi), safi2str(safi)); } /* Best path selection. */ bgp_best_selection(bgp, rn, &bgp->maxpaths[afi][safi], &old_and_new, afi, safi); old_select = old_and_new.old; new_select = old_and_new.new; /* Do we need to allocate or free labels? * Right now, since we only deal with per-prefix labels, it is not * necessary to do this upon changes to best path. Exceptions: * - label index has changed -> recalculate resulting label * - path_info sub_type changed -> switch to/from implicit-null * - no valid label (due to removed static label binding) -> get new one */ if (bgp->allocate_mpls_labels[afi][safi]) { if (new_select) { if (!old_select || bgp_label_index_differs(new_select, old_select) || new_select->sub_type != old_select->sub_type || !bgp_is_valid_label(&rn->local_label)) { /* Enforced penultimate hop popping: * implicit-null for local routes, aggregate * and redistributed routes */ if (new_select->sub_type == BGP_ROUTE_STATIC || new_select->sub_type == BGP_ROUTE_AGGREGATE || new_select->sub_type == BGP_ROUTE_REDISTRIBUTE) { if (CHECK_FLAG( rn->flags, BGP_NODE_REGISTERED_FOR_LABEL)) bgp_unregister_for_label(rn); label_ntop(MPLS_LABEL_IMPLICIT_NULL, 1, &rn->local_label); bgp_set_valid_label(&rn->local_label); } else bgp_register_for_label(rn, new_select); } } else if (CHECK_FLAG(rn->flags, BGP_NODE_REGISTERED_FOR_LABEL)) { bgp_unregister_for_label(rn); } } else if (CHECK_FLAG(rn->flags, BGP_NODE_REGISTERED_FOR_LABEL)) { bgp_unregister_for_label(rn); } if (debug) { prefix2str(&rn->p, pfx_buf, sizeof(pfx_buf)); zlog_debug( "%s: p=%s afi=%s, safi=%s, old_select=%p, new_select=%p", __func__, pfx_buf, afi2str(afi), safi2str(safi), old_select, new_select); } /* If best route remains the same and this is not due to user-initiated * clear, see exactly what needs to be done. */ if (old_select && old_select == new_select && !CHECK_FLAG(rn->flags, BGP_NODE_USER_CLEAR) && !CHECK_FLAG(old_select->flags, BGP_PATH_ATTR_CHANGED) && !bgp_addpath_is_addpath_used(&bgp->tx_addpath, afi, safi)) { if (bgp_zebra_has_route_changed(rn, old_select)) { #if ENABLE_BGP_VNC vnc_import_bgp_add_route(bgp, p, old_select); vnc_import_bgp_exterior_add_route(bgp, p, old_select); #endif if (bgp_fibupd_safi(safi) && !bgp_option_check(BGP_OPT_NO_FIB)) { if (new_select->type == ZEBRA_ROUTE_BGP && (new_select->sub_type == BGP_ROUTE_NORMAL || new_select->sub_type == BGP_ROUTE_IMPORTED)) bgp_zebra_announce(rn, p, old_select, bgp, afi, safi); } } UNSET_FLAG(old_select->flags, BGP_PATH_MULTIPATH_CHG); bgp_zebra_clear_route_change_flags(rn); /* If there is a change of interest to peers, reannounce the * route. */ if (CHECK_FLAG(old_select->flags, BGP_PATH_ATTR_CHANGED) || CHECK_FLAG(rn->flags, BGP_NODE_LABEL_CHANGED)) { group_announce_route(bgp, afi, safi, rn, new_select); /* unicast routes must also be annouced to * labeled-unicast update-groups */ if (safi == SAFI_UNICAST) group_announce_route(bgp, afi, SAFI_LABELED_UNICAST, rn, new_select); UNSET_FLAG(old_select->flags, BGP_PATH_ATTR_CHANGED); UNSET_FLAG(rn->flags, BGP_NODE_LABEL_CHANGED); } UNSET_FLAG(rn->flags, BGP_NODE_PROCESS_SCHEDULED); return; } /* If the user did "clear ip bgp prefix x.x.x.x" this flag will be set */ UNSET_FLAG(rn->flags, BGP_NODE_USER_CLEAR); /* bestpath has changed; bump version */ if (old_select || new_select) { bgp_bump_version(rn); if (!bgp->t_rmap_def_originate_eval) { bgp_lock(bgp); thread_add_timer( bm->master, update_group_refresh_default_originate_route_map, bgp, RMAP_DEFAULT_ORIGINATE_EVAL_TIMER, &bgp->t_rmap_def_originate_eval); } } if (old_select) bgp_path_info_unset_flag(rn, old_select, BGP_PATH_SELECTED); if (new_select) { if (debug) zlog_debug("%s: setting SELECTED flag", __func__); bgp_path_info_set_flag(rn, new_select, BGP_PATH_SELECTED); bgp_path_info_unset_flag(rn, new_select, BGP_PATH_ATTR_CHANGED); UNSET_FLAG(new_select->flags, BGP_PATH_MULTIPATH_CHG); } #if ENABLE_BGP_VNC if ((afi == AFI_IP || afi == AFI_IP6) && (safi == SAFI_UNICAST)) { if (old_select != new_select) { if (old_select) { vnc_import_bgp_exterior_del_route(bgp, p, old_select); vnc_import_bgp_del_route(bgp, p, old_select); } if (new_select) { vnc_import_bgp_exterior_add_route(bgp, p, new_select); vnc_import_bgp_add_route(bgp, p, new_select); } } } #endif group_announce_route(bgp, afi, safi, rn, new_select); /* unicast routes must also be annouced to labeled-unicast update-groups */ if (safi == SAFI_UNICAST) group_announce_route(bgp, afi, SAFI_LABELED_UNICAST, rn, new_select); /* FIB update. */ if (bgp_fibupd_safi(safi) && (bgp->inst_type != BGP_INSTANCE_TYPE_VIEW) && !bgp_option_check(BGP_OPT_NO_FIB)) { if (new_select && new_select->type == ZEBRA_ROUTE_BGP && (new_select->sub_type == BGP_ROUTE_NORMAL || new_select->sub_type == BGP_ROUTE_AGGREGATE || new_select->sub_type == BGP_ROUTE_IMPORTED)) { /* if this is an evpn imported type-5 prefix, * we need to withdraw the route first to clear * the nh neigh and the RMAC entry. */ if (old_select && is_route_parent_evpn(old_select)) bgp_zebra_withdraw(p, old_select, bgp, safi); bgp_zebra_announce(rn, p, new_select, bgp, afi, safi); } else { /* Withdraw the route from the kernel. */ if (old_select && old_select->type == ZEBRA_ROUTE_BGP && (old_select->sub_type == BGP_ROUTE_NORMAL || old_select->sub_type == BGP_ROUTE_AGGREGATE || old_select->sub_type == BGP_ROUTE_IMPORTED)) bgp_zebra_withdraw(p, old_select, bgp, safi); } } /* advertise/withdraw type-5 routes */ if ((afi == AFI_IP || afi == AFI_IP6) && (safi == SAFI_UNICAST)) { if (advertise_type5_routes(bgp, afi) && new_select && is_route_injectable_into_evpn(new_select)) { /* apply the route-map */ if (bgp->adv_cmd_rmap[afi][safi].map) { route_map_result_t ret; ret = route_map_apply( bgp->adv_cmd_rmap[afi][safi].map, &rn->p, RMAP_BGP, new_select); if (ret == RMAP_PERMITMATCH) bgp_evpn_advertise_type5_route( bgp, &rn->p, new_select->attr, afi, safi); else bgp_evpn_withdraw_type5_route( bgp, &rn->p, afi, safi); } else { bgp_evpn_advertise_type5_route(bgp, &rn->p, new_select->attr, afi, safi); } } else if (advertise_type5_routes(bgp, afi) && old_select && is_route_injectable_into_evpn(old_select)) bgp_evpn_withdraw_type5_route(bgp, &rn->p, afi, safi); } /* Clear any route change flags. */ bgp_zebra_clear_route_change_flags(rn); /* Reap old select bgp_path_info, if it has been removed */ if (old_select && CHECK_FLAG(old_select->flags, BGP_PATH_REMOVED)) bgp_path_info_reap(rn, old_select); UNSET_FLAG(rn->flags, BGP_NODE_PROCESS_SCHEDULED); return; } static wq_item_status bgp_process_wq(struct work_queue *wq, void *data) { struct bgp_process_queue *pqnode = data; struct bgp *bgp = pqnode->bgp; struct bgp_table *table; struct bgp_node *rn; /* eoiu marker */ if (CHECK_FLAG(pqnode->flags, BGP_PROCESS_QUEUE_EOIU_MARKER)) { bgp_process_main_one(bgp, NULL, 0, 0); /* should always have dedicated wq call */ assert(STAILQ_FIRST(&pqnode->pqueue) == NULL); return WQ_SUCCESS; } while (!STAILQ_EMPTY(&pqnode->pqueue)) { rn = STAILQ_FIRST(&pqnode->pqueue); STAILQ_REMOVE_HEAD(&pqnode->pqueue, pq); STAILQ_NEXT(rn, pq) = NULL; /* complete unlink */ table = bgp_node_table(rn); /* note, new RNs may be added as part of processing */ bgp_process_main_one(bgp, rn, table->afi, table->safi); bgp_unlock_node(rn); bgp_table_unlock(table); } return WQ_SUCCESS; } static void bgp_processq_del(struct work_queue *wq, void *data) { struct bgp_process_queue *pqnode = data; bgp_unlock(pqnode->bgp); XFREE(MTYPE_BGP_PROCESS_QUEUE, pqnode); } void bgp_process_queue_init(void) { if (!bm->process_main_queue) bm->process_main_queue = work_queue_new(bm->master, "process_main_queue"); bm->process_main_queue->spec.workfunc = &bgp_process_wq; bm->process_main_queue->spec.del_item_data = &bgp_processq_del; bm->process_main_queue->spec.max_retries = 0; bm->process_main_queue->spec.hold = 50; /* Use a higher yield value of 50ms for main queue processing */ bm->process_main_queue->spec.yield = 50 * 1000L; } static struct bgp_process_queue *bgp_processq_alloc(struct bgp *bgp) { struct bgp_process_queue *pqnode; pqnode = XCALLOC(MTYPE_BGP_PROCESS_QUEUE, sizeof(struct bgp_process_queue)); /* unlocked in bgp_processq_del */ pqnode->bgp = bgp_lock(bgp); STAILQ_INIT(&pqnode->pqueue); return pqnode; } void bgp_process(struct bgp *bgp, struct bgp_node *rn, afi_t afi, safi_t safi) { #define ARBITRARY_PROCESS_QLEN 10000 struct work_queue *wq = bm->process_main_queue; struct bgp_process_queue *pqnode; int pqnode_reuse = 0; /* already scheduled for processing? */ if (CHECK_FLAG(rn->flags, BGP_NODE_PROCESS_SCHEDULED)) return; if (wq == NULL) return; /* Add route nodes to an existing work queue item until reaching the limit only if is from the same BGP view and it's not an EOIU marker */ if (work_queue_item_count(wq)) { struct work_queue_item *item = work_queue_last_item(wq); pqnode = item->data; if (CHECK_FLAG(pqnode->flags, BGP_PROCESS_QUEUE_EOIU_MARKER) || pqnode->bgp != bgp || pqnode->queued >= ARBITRARY_PROCESS_QLEN) pqnode = bgp_processq_alloc(bgp); else pqnode_reuse = 1; } else pqnode = bgp_processq_alloc(bgp); /* all unlocked in bgp_process_wq */ bgp_table_lock(bgp_node_table(rn)); SET_FLAG(rn->flags, BGP_NODE_PROCESS_SCHEDULED); bgp_lock_node(rn); /* can't be enqueued twice */ assert(STAILQ_NEXT(rn, pq) == NULL); STAILQ_INSERT_TAIL(&pqnode->pqueue, rn, pq); pqnode->queued++; if (!pqnode_reuse) work_queue_add(wq, pqnode); return; } void bgp_add_eoiu_mark(struct bgp *bgp) { struct bgp_process_queue *pqnode; if (bm->process_main_queue == NULL) return; pqnode = bgp_processq_alloc(bgp); SET_FLAG(pqnode->flags, BGP_PROCESS_QUEUE_EOIU_MARKER); work_queue_add(bm->process_main_queue, pqnode); } static int bgp_maximum_prefix_restart_timer(struct thread *thread) { struct peer *peer; peer = THREAD_ARG(thread); peer->t_pmax_restart = NULL; if (bgp_debug_neighbor_events(peer)) zlog_debug( "%s Maximum-prefix restart timer expired, restore peering", peer->host); if ((peer_clear(peer, NULL) < 0) && bgp_debug_neighbor_events(peer)) zlog_debug("%s: %s peer_clear failed", __PRETTY_FUNCTION__, peer->host); return 0; } int bgp_maximum_prefix_overflow(struct peer *peer, afi_t afi, safi_t safi, int always) { iana_afi_t pkt_afi; iana_safi_t pkt_safi; if (!CHECK_FLAG(peer->af_flags[afi][safi], PEER_FLAG_MAX_PREFIX)) return 0; if (peer->pcount[afi][safi] > peer->pmax[afi][safi]) { if (CHECK_FLAG(peer->af_sflags[afi][safi], PEER_STATUS_PREFIX_LIMIT) && !always) return 0; zlog_info( "%%MAXPFXEXCEED: No. of %s prefix received from %s %" PRIu32 " exceed, limit %" PRIu32, get_afi_safi_str(afi, safi, false), peer->host, peer->pcount[afi][safi], peer->pmax[afi][safi]); SET_FLAG(peer->af_sflags[afi][safi], PEER_STATUS_PREFIX_LIMIT); if (CHECK_FLAG(peer->af_flags[afi][safi], PEER_FLAG_MAX_PREFIX_WARNING)) return 0; /* Convert AFI, SAFI to values for packet. */ pkt_afi = afi_int2iana(afi); pkt_safi = safi_int2iana(safi); { uint8_t ndata[7]; ndata[0] = (pkt_afi >> 8); ndata[1] = pkt_afi; ndata[2] = pkt_safi; ndata[3] = (peer->pmax[afi][safi] >> 24); ndata[4] = (peer->pmax[afi][safi] >> 16); ndata[5] = (peer->pmax[afi][safi] >> 8); ndata[6] = (peer->pmax[afi][safi]); SET_FLAG(peer->sflags, PEER_STATUS_PREFIX_OVERFLOW); bgp_notify_send_with_data(peer, BGP_NOTIFY_CEASE, BGP_NOTIFY_CEASE_MAX_PREFIX, ndata, 7); } /* Dynamic peers will just close their connection. */ if (peer_dynamic_neighbor(peer)) return 1; /* restart timer start */ if (peer->pmax_restart[afi][safi]) { peer->v_pmax_restart = peer->pmax_restart[afi][safi] * 60; if (bgp_debug_neighbor_events(peer)) zlog_debug( "%s Maximum-prefix restart timer started for %d secs", peer->host, peer->v_pmax_restart); BGP_TIMER_ON(peer->t_pmax_restart, bgp_maximum_prefix_restart_timer, peer->v_pmax_restart); } return 1; } else UNSET_FLAG(peer->af_sflags[afi][safi], PEER_STATUS_PREFIX_LIMIT); if (peer->pcount[afi][safi] > (peer->pmax[afi][safi] * peer->pmax_threshold[afi][safi] / 100)) { if (CHECK_FLAG(peer->af_sflags[afi][safi], PEER_STATUS_PREFIX_THRESHOLD) && !always) return 0; zlog_info( "%%MAXPFX: No. of %s prefix received from %s reaches %" PRIu32 ", max %" PRIu32, get_afi_safi_str(afi, safi, false), peer->host, peer->pcount[afi][safi], peer->pmax[afi][safi]); SET_FLAG(peer->af_sflags[afi][safi], PEER_STATUS_PREFIX_THRESHOLD); } else UNSET_FLAG(peer->af_sflags[afi][safi], PEER_STATUS_PREFIX_THRESHOLD); return 0; } /* Unconditionally remove the route from the RIB, without taking * damping into consideration (eg, because the session went down) */ void bgp_rib_remove(struct bgp_node *rn, struct bgp_path_info *pi, struct peer *peer, afi_t afi, safi_t safi) { bgp_aggregate_decrement(peer->bgp, &rn->p, pi, afi, safi); if (!CHECK_FLAG(pi->flags, BGP_PATH_HISTORY)) bgp_path_info_delete(rn, pi); /* keep historical info */ hook_call(bgp_process, peer->bgp, afi, safi, rn, peer, true); bgp_process(peer->bgp, rn, afi, safi); } static void bgp_rib_withdraw(struct bgp_node *rn, struct bgp_path_info *pi, struct peer *peer, afi_t afi, safi_t safi, struct prefix_rd *prd) { /* apply dampening, if result is suppressed, we'll be retaining * the bgp_path_info in the RIB for historical reference. */ if (CHECK_FLAG(peer->bgp->af_flags[afi][safi], BGP_CONFIG_DAMPENING) && peer->sort == BGP_PEER_EBGP) if ((bgp_damp_withdraw(pi, rn, afi, safi, 0)) == BGP_DAMP_SUPPRESSED) { bgp_aggregate_decrement(peer->bgp, &rn->p, pi, afi, safi); return; } #if ENABLE_BGP_VNC if (safi == SAFI_MPLS_VPN) { struct bgp_node *prn = NULL; struct bgp_table *table = NULL; prn = bgp_node_get(peer->bgp->rib[afi][safi], (struct prefix *)prd); if (bgp_node_has_bgp_path_info_data(prn)) { table = bgp_node_get_bgp_table_info(prn); vnc_import_bgp_del_vnc_host_route_mode_resolve_nve( peer->bgp, prd, table, &rn->p, pi); } bgp_unlock_node(prn); } if ((afi == AFI_IP || afi == AFI_IP6) && (safi == SAFI_UNICAST)) { if (CHECK_FLAG(pi->flags, BGP_PATH_SELECTED)) { vnc_import_bgp_del_route(peer->bgp, &rn->p, pi); vnc_import_bgp_exterior_del_route(peer->bgp, &rn->p, pi); } } #endif /* If this is an EVPN route, process for un-import. */ if (safi == SAFI_EVPN) bgp_evpn_unimport_route(peer->bgp, afi, safi, &rn->p, pi); bgp_rib_remove(rn, pi, peer, afi, safi); } struct bgp_path_info *info_make(int type, int sub_type, unsigned short instance, struct peer *peer, struct attr *attr, struct bgp_node *rn) { struct bgp_path_info *new; /* Make new BGP info. */ new = XCALLOC(MTYPE_BGP_ROUTE, sizeof(struct bgp_path_info)); new->type = type; new->instance = instance; new->sub_type = sub_type; new->peer = peer; new->attr = attr; new->uptime = bgp_clock(); new->net = rn; return new; } static void overlay_index_update(struct attr *attr, struct eth_segment_id *eth_s_id, union gw_addr *gw_ip) { if (!attr) return; if (eth_s_id == NULL) { memset(&(attr->evpn_overlay.eth_s_id), 0, sizeof(struct eth_segment_id)); } else { memcpy(&(attr->evpn_overlay.eth_s_id), eth_s_id, sizeof(struct eth_segment_id)); } if (gw_ip == NULL) { memset(&(attr->evpn_overlay.gw_ip), 0, sizeof(union gw_addr)); } else { memcpy(&(attr->evpn_overlay.gw_ip), gw_ip, sizeof(union gw_addr)); } } static bool overlay_index_equal(afi_t afi, struct bgp_path_info *path, struct eth_segment_id *eth_s_id, union gw_addr *gw_ip) { struct eth_segment_id *path_eth_s_id, *path_eth_s_id_remote; union gw_addr *path_gw_ip, *path_gw_ip_remote; union { struct eth_segment_id esi; union gw_addr ip; } temp; if (afi != AFI_L2VPN) return true; if (!path->attr) { memset(&temp, 0, sizeof(temp)); path_eth_s_id = &temp.esi; path_gw_ip = &temp.ip; if (eth_s_id == NULL && gw_ip == NULL) return true; } else { path_eth_s_id = &(path->attr->evpn_overlay.eth_s_id); path_gw_ip = &(path->attr->evpn_overlay.gw_ip); } if (gw_ip == NULL) { memset(&temp, 0, sizeof(temp)); path_gw_ip_remote = &temp.ip; } else path_gw_ip_remote = gw_ip; if (eth_s_id == NULL) { memset(&temp, 0, sizeof(temp)); path_eth_s_id_remote = &temp.esi; } else path_eth_s_id_remote = eth_s_id; if (!memcmp(path_gw_ip, path_gw_ip_remote, sizeof(union gw_addr))) return false; return !memcmp(path_eth_s_id, path_eth_s_id_remote, sizeof(struct eth_segment_id)); } /* Check if received nexthop is valid or not. */ static int bgp_update_martian_nexthop(struct bgp *bgp, afi_t afi, safi_t safi, struct attr *attr) { int ret = 0; /* Only validated for unicast and multicast currently. */ /* Also valid for EVPN where the nexthop is an IP address. */ if (safi != SAFI_UNICAST && safi != SAFI_MULTICAST && safi != SAFI_EVPN) return 0; /* If NEXT_HOP is present, validate it. */ if (attr->flag & ATTR_FLAG_BIT(BGP_ATTR_NEXT_HOP)) { if (attr->nexthop.s_addr == 0 || IPV4_CLASS_DE(ntohl(attr->nexthop.s_addr)) || bgp_nexthop_self(bgp, attr->nexthop)) return 1; } /* If MP_NEXTHOP is present, validate it. */ /* Note: For IPv6 nexthops, we only validate the global (1st) nexthop; * there is code in bgp_attr.c to ignore the link-local (2nd) nexthop if * it is not an IPv6 link-local address. */ if (attr->mp_nexthop_len) { switch (attr->mp_nexthop_len) { case BGP_ATTR_NHLEN_IPV4: case BGP_ATTR_NHLEN_VPNV4: ret = (attr->mp_nexthop_global_in.s_addr == 0 || IPV4_CLASS_DE(ntohl( attr->mp_nexthop_global_in.s_addr)) || bgp_nexthop_self(bgp, attr->mp_nexthop_global_in)); break; case BGP_ATTR_NHLEN_IPV6_GLOBAL: case BGP_ATTR_NHLEN_IPV6_GLOBAL_AND_LL: case BGP_ATTR_NHLEN_VPNV6_GLOBAL: ret = (IN6_IS_ADDR_UNSPECIFIED(&attr->mp_nexthop_global) || IN6_IS_ADDR_LOOPBACK(&attr->mp_nexthop_global) || IN6_IS_ADDR_MULTICAST( &attr->mp_nexthop_global)); break; default: ret = 1; break; } } return ret; } int bgp_update(struct peer *peer, struct prefix *p, uint32_t addpath_id, struct attr *attr, afi_t afi, safi_t safi, int type, int sub_type, struct prefix_rd *prd, mpls_label_t *label, uint32_t num_labels, int soft_reconfig, struct bgp_route_evpn *evpn) { int ret; int aspath_loop_count = 0; struct bgp_node *rn; struct bgp *bgp; struct attr new_attr; struct attr *attr_new; struct bgp_path_info *pi; struct bgp_path_info *new; struct bgp_path_info_extra *extra; const char *reason; char pfx_buf[BGP_PRD_PATH_STRLEN]; int connected = 0; int do_loop_check = 1; int has_valid_label = 0; #if ENABLE_BGP_VNC int vnc_implicit_withdraw = 0; #endif int same_attr = 0; memset(&new_attr, 0, sizeof(struct attr)); new_attr.label_index = BGP_INVALID_LABEL_INDEX; new_attr.label = MPLS_INVALID_LABEL; bgp = peer->bgp; rn = bgp_afi_node_get(bgp->rib[afi][safi], afi, safi, p, prd); /* TODO: Check to see if we can get rid of "is_valid_label" */ if (afi == AFI_L2VPN && safi == SAFI_EVPN) has_valid_label = (num_labels > 0) ? 1 : 0; else has_valid_label = bgp_is_valid_label(label); /* When peer's soft reconfiguration enabled. Record input packet in Adj-RIBs-In. */ if (!soft_reconfig && CHECK_FLAG(peer->af_flags[afi][safi], PEER_FLAG_SOFT_RECONFIG) && peer != bgp->peer_self) bgp_adj_in_set(rn, peer, attr, addpath_id); /* Check previously received route. */ for (pi = bgp_node_get_bgp_path_info(rn); pi; pi = pi->next) if (pi->peer == peer && pi->type == type && pi->sub_type == sub_type && pi->addpath_rx_id == addpath_id) break; /* AS path local-as loop check. */ if (peer->change_local_as) { if (peer->allowas_in[afi][safi]) aspath_loop_count = peer->allowas_in[afi][safi]; else if (!CHECK_FLAG(peer->flags, PEER_FLAG_LOCAL_AS_NO_PREPEND)) aspath_loop_count = 1; if (aspath_loop_check(attr->aspath, peer->change_local_as) > aspath_loop_count) { peer->stat_pfx_aspath_loop++; reason = "as-path contains our own AS;"; goto filtered; } } /* If the peer is configured for "allowas-in origin" and the last ASN in * the * as-path is our ASN then we do not need to call aspath_loop_check */ if (CHECK_FLAG(peer->af_flags[afi][safi], PEER_FLAG_ALLOWAS_IN_ORIGIN)) if (aspath_get_last_as(attr->aspath) == bgp->as) do_loop_check = 0; /* AS path loop check. */ if (do_loop_check) { if (aspath_loop_check(attr->aspath, bgp->as) > peer->allowas_in[afi][safi] || (CHECK_FLAG(bgp->config, BGP_CONFIG_CONFEDERATION) && aspath_loop_check(attr->aspath, bgp->confed_id) > peer->allowas_in[afi][safi])) { peer->stat_pfx_aspath_loop++; reason = "as-path contains our own AS;"; goto filtered; } } /* Route reflector originator ID check. */ if (attr->flag & ATTR_FLAG_BIT(BGP_ATTR_ORIGINATOR_ID) && IPV4_ADDR_SAME(&bgp->router_id, &attr->originator_id)) { peer->stat_pfx_originator_loop++; reason = "originator is us;"; goto filtered; } /* Route reflector cluster ID check. */ if (bgp_cluster_filter(peer, attr)) { peer->stat_pfx_cluster_loop++; reason = "reflected from the same cluster;"; goto filtered; } /* Apply incoming filter. */ if (bgp_input_filter(peer, p, attr, afi, safi) == FILTER_DENY) { peer->stat_pfx_filter++; reason = "filter;"; goto filtered; } /* RFC 8212 to prevent route leaks. * This specification intends to improve this situation by requiring the * explicit configuration of both BGP Import and Export Policies for any * External BGP (EBGP) session such as customers, peers, or * confederation boundaries for all enabled address families. Through * codification of the aforementioned requirement, operators will * benefit from consistent behavior across different BGP * implementations. */ if (peer->bgp->ebgp_requires_policy == DEFAULT_EBGP_POLICY_ENABLED) if (!bgp_inbound_policy_exists(peer, &peer->filter[afi][safi])) { reason = "inbound policy missing"; goto filtered; } new_attr = *attr; /* Apply incoming route-map. * NB: new_attr may now contain newly allocated values from route-map * "set" * commands, so we need bgp_attr_flush in the error paths, until we * intern * the attr (which takes over the memory references) */ if (bgp_input_modifier(peer, p, &new_attr, afi, safi, NULL, label, num_labels) == RMAP_DENY) { peer->stat_pfx_filter++; reason = "route-map;"; bgp_attr_flush(&new_attr); goto filtered; } if (peer->sort == BGP_PEER_EBGP) { /* If we receive the graceful-shutdown community from an eBGP * peer we must lower local-preference */ if (new_attr.community && community_include(new_attr.community, COMMUNITY_GSHUT)) { new_attr.flag |= ATTR_FLAG_BIT(BGP_ATTR_LOCAL_PREF); new_attr.local_pref = BGP_GSHUT_LOCAL_PREF; /* If graceful-shutdown is configured then add the GSHUT * community to all paths received from eBGP peers */ } else if (bgp_flag_check(peer->bgp, BGP_FLAG_GRACEFUL_SHUTDOWN)) { bgp_attr_add_gshut_community(&new_attr); } } /* next hop check. */ if (!CHECK_FLAG(peer->flags, PEER_FLAG_IS_RFAPI_HD) && bgp_update_martian_nexthop(bgp, afi, safi, &new_attr)) { peer->stat_pfx_nh_invalid++; reason = "martian or self next-hop;"; bgp_attr_flush(&new_attr); goto filtered; } if (bgp_mac_entry_exists(p) || bgp_mac_exist(&attr->rmac)) { peer->stat_pfx_nh_invalid++; reason = "self mac;"; goto filtered; } attr_new = bgp_attr_intern(&new_attr); /* If the update is implicit withdraw. */ if (pi) { pi->uptime = bgp_clock(); same_attr = attrhash_cmp(pi->attr, attr_new); hook_call(bgp_process, bgp, afi, safi, rn, peer, true); /* Same attribute comes in. */ if (!CHECK_FLAG(pi->flags, BGP_PATH_REMOVED) && attrhash_cmp(pi->attr, attr_new) && (!has_valid_label || memcmp(&(bgp_path_info_extra_get(pi))->label, label, num_labels * sizeof(mpls_label_t)) == 0) && (overlay_index_equal( afi, pi, evpn == NULL ? NULL : &evpn->eth_s_id, evpn == NULL ? NULL : &evpn->gw_ip))) { if (CHECK_FLAG(bgp->af_flags[afi][safi], BGP_CONFIG_DAMPENING) && peer->sort == BGP_PEER_EBGP && CHECK_FLAG(pi->flags, BGP_PATH_HISTORY)) { if (bgp_debug_update(peer, p, NULL, 1)) { bgp_debug_rdpfxpath2str( afi, safi, prd, p, label, num_labels, addpath_id ? 1 : 0, addpath_id, pfx_buf, sizeof(pfx_buf)); zlog_debug("%s rcvd %s", peer->host, pfx_buf); } if (bgp_damp_update(pi, rn, afi, safi) != BGP_DAMP_SUPPRESSED) { bgp_aggregate_increment(bgp, p, pi, afi, safi); bgp_process(bgp, rn, afi, safi); } } else /* Duplicate - odd */ { if (bgp_debug_update(peer, p, NULL, 1)) { if (!peer->rcvd_attr_printed) { zlog_debug( "%s rcvd UPDATE w/ attr: %s", peer->host, peer->rcvd_attr_str); peer->rcvd_attr_printed = 1; } bgp_debug_rdpfxpath2str( afi, safi, prd, p, label, num_labels, addpath_id ? 1 : 0, addpath_id, pfx_buf, sizeof(pfx_buf)); zlog_debug( "%s rcvd %s...duplicate ignored", peer->host, pfx_buf); } /* graceful restart STALE flag unset. */ if (CHECK_FLAG(pi->flags, BGP_PATH_STALE)) { bgp_path_info_unset_flag( rn, pi, BGP_PATH_STALE); bgp_process(bgp, rn, afi, safi); } } bgp_unlock_node(rn); bgp_attr_unintern(&attr_new); return 0; } /* Withdraw/Announce before we fully processed the withdraw */ if (CHECK_FLAG(pi->flags, BGP_PATH_REMOVED)) { if (bgp_debug_update(peer, p, NULL, 1)) { bgp_debug_rdpfxpath2str( afi, safi, prd, p, label, num_labels, addpath_id ? 1 : 0, addpath_id, pfx_buf, sizeof(pfx_buf)); zlog_debug( "%s rcvd %s, flapped quicker than processing", peer->host, pfx_buf); } bgp_path_info_restore(rn, pi); } /* Received Logging. */ if (bgp_debug_update(peer, p, NULL, 1)) { bgp_debug_rdpfxpath2str(afi, safi, prd, p, label, num_labels, addpath_id ? 1 : 0, addpath_id, pfx_buf, sizeof(pfx_buf)); zlog_debug("%s rcvd %s", peer->host, pfx_buf); } /* graceful restart STALE flag unset. */ if (CHECK_FLAG(pi->flags, BGP_PATH_STALE)) bgp_path_info_unset_flag(rn, pi, BGP_PATH_STALE); /* The attribute is changed. */ bgp_path_info_set_flag(rn, pi, BGP_PATH_ATTR_CHANGED); /* implicit withdraw, decrement aggregate and pcount here. * only if update is accepted, they'll increment below. */ bgp_aggregate_decrement(bgp, p, pi, afi, safi); /* Update bgp route dampening information. */ if (CHECK_FLAG(bgp->af_flags[afi][safi], BGP_CONFIG_DAMPENING) && peer->sort == BGP_PEER_EBGP) { /* This is implicit withdraw so we should update dampening information. */ if (!CHECK_FLAG(pi->flags, BGP_PATH_HISTORY)) bgp_damp_withdraw(pi, rn, afi, safi, 1); } #if ENABLE_BGP_VNC if (safi == SAFI_MPLS_VPN) { struct bgp_node *prn = NULL; struct bgp_table *table = NULL; prn = bgp_node_get(bgp->rib[afi][safi], (struct prefix *)prd); if (bgp_node_has_bgp_path_info_data(prn)) { table = bgp_node_get_bgp_table_info(prn); vnc_import_bgp_del_vnc_host_route_mode_resolve_nve( bgp, prd, table, p, pi); } bgp_unlock_node(prn); } if ((afi == AFI_IP || afi == AFI_IP6) && (safi == SAFI_UNICAST)) { if (CHECK_FLAG(pi->flags, BGP_PATH_SELECTED)) { /* * Implicit withdraw case. */ ++vnc_implicit_withdraw; vnc_import_bgp_del_route(bgp, p, pi); vnc_import_bgp_exterior_del_route(bgp, p, pi); } } #endif /* Special handling for EVPN update of an existing route. If the * extended community attribute has changed, we need to * un-import * the route using its existing extended community. It will be * subsequently processed for import with the new extended * community. */ if (safi == SAFI_EVPN && !same_attr) { if ((pi->attr->flag & ATTR_FLAG_BIT(BGP_ATTR_EXT_COMMUNITIES)) && (attr_new->flag & ATTR_FLAG_BIT(BGP_ATTR_EXT_COMMUNITIES))) { int cmp; cmp = ecommunity_cmp(pi->attr->ecommunity, attr_new->ecommunity); if (!cmp) { if (bgp_debug_update(peer, p, NULL, 1)) zlog_debug( "Change in EXT-COMM, existing %s new %s", ecommunity_str( pi->attr->ecommunity), ecommunity_str( attr_new->ecommunity)); bgp_evpn_unimport_route(bgp, afi, safi, p, pi); } } } /* Update to new attribute. */ bgp_attr_unintern(&pi->attr); pi->attr = attr_new; /* Update MPLS label */ if (has_valid_label) { extra = bgp_path_info_extra_get(pi); if (extra->label != label) { memcpy(&extra->label, label, num_labels * sizeof(mpls_label_t)); extra->num_labels = num_labels; } if (!(afi == AFI_L2VPN && safi == SAFI_EVPN)) bgp_set_valid_label(&extra->label[0]); } #if ENABLE_BGP_VNC if ((afi == AFI_IP || afi == AFI_IP6) && (safi == SAFI_UNICAST)) { if (vnc_implicit_withdraw) { /* * Add back the route with its new attributes * (e.g., nexthop). * The route is still selected, until the route * selection * queued by bgp_process actually runs. We have * to make this * update to the VNC side immediately to avoid * racing against * configuration changes (e.g., route-map * changes) which * trigger re-importation of the entire RIB. */ vnc_import_bgp_add_route(bgp, p, pi); vnc_import_bgp_exterior_add_route(bgp, p, pi); } } #endif /* Update Overlay Index */ if (afi == AFI_L2VPN) { overlay_index_update( pi->attr, evpn == NULL ? NULL : &evpn->eth_s_id, evpn == NULL ? NULL : &evpn->gw_ip); } /* Update bgp route dampening information. */ if (CHECK_FLAG(bgp->af_flags[afi][safi], BGP_CONFIG_DAMPENING) && peer->sort == BGP_PEER_EBGP) { /* Now we do normal update dampening. */ ret = bgp_damp_update(pi, rn, afi, safi); if (ret == BGP_DAMP_SUPPRESSED) { bgp_unlock_node(rn); return 0; } } /* Nexthop reachability check - for unicast and * labeled-unicast.. */ if ((afi == AFI_IP || afi == AFI_IP6) && (safi == SAFI_UNICAST || safi == SAFI_LABELED_UNICAST)) { if (peer->sort == BGP_PEER_EBGP && peer->ttl == 1 && !CHECK_FLAG(peer->flags, PEER_FLAG_DISABLE_CONNECTED_CHECK) && !bgp_flag_check( bgp, BGP_FLAG_DISABLE_NH_CONNECTED_CHK)) connected = 1; else connected = 0; struct bgp *bgp_nexthop = bgp; if (pi->extra && pi->extra->bgp_orig) bgp_nexthop = pi->extra->bgp_orig; if (bgp_find_or_add_nexthop(bgp, bgp_nexthop, afi, pi, NULL, connected) || CHECK_FLAG(peer->flags, PEER_FLAG_IS_RFAPI_HD)) bgp_path_info_set_flag(rn, pi, BGP_PATH_VALID); else { if (BGP_DEBUG(nht, NHT)) { char buf1[INET6_ADDRSTRLEN]; inet_ntop(AF_INET, (const void *)&attr_new ->nexthop, buf1, INET6_ADDRSTRLEN); zlog_debug("%s(%s): NH unresolved", __FUNCTION__, buf1); } bgp_path_info_unset_flag(rn, pi, BGP_PATH_VALID); } } else bgp_path_info_set_flag(rn, pi, BGP_PATH_VALID); #if ENABLE_BGP_VNC if (safi == SAFI_MPLS_VPN) { struct bgp_node *prn = NULL; struct bgp_table *table = NULL; prn = bgp_node_get(bgp->rib[afi][safi], (struct prefix *)prd); if (bgp_node_has_bgp_path_info_data(prn)) { table = bgp_node_get_bgp_table_info(prn); vnc_import_bgp_add_vnc_host_route_mode_resolve_nve( bgp, prd, table, p, pi); } bgp_unlock_node(prn); } #endif /* If this is an EVPN route and some attribute has changed, * process * route for import. If the extended community has changed, we * would * have done the un-import earlier and the import would result * in the * route getting injected into appropriate L2 VNIs. If it is * just * some other attribute change, the import will result in * updating * the attributes for the route in the VNI(s). */ if (safi == SAFI_EVPN && !same_attr) bgp_evpn_import_route(bgp, afi, safi, p, pi); /* Process change. */ bgp_aggregate_increment(bgp, p, pi, afi, safi); bgp_process(bgp, rn, afi, safi); bgp_unlock_node(rn); if (SAFI_UNICAST == safi && (bgp->inst_type == BGP_INSTANCE_TYPE_VRF || bgp->inst_type == BGP_INSTANCE_TYPE_DEFAULT)) { vpn_leak_from_vrf_update(bgp_get_default(), bgp, pi); } if ((SAFI_MPLS_VPN == safi) && (bgp->inst_type == BGP_INSTANCE_TYPE_DEFAULT)) { vpn_leak_to_vrf_update(bgp, pi); } #if ENABLE_BGP_VNC if (SAFI_MPLS_VPN == safi) { mpls_label_t label_decoded = decode_label(label); rfapiProcessUpdate(peer, NULL, p, prd, attr, afi, safi, type, sub_type, &label_decoded); } if (SAFI_ENCAP == safi) { rfapiProcessUpdate(peer, NULL, p, prd, attr, afi, safi, type, sub_type, NULL); } #endif return 0; } // End of implicit withdraw /* Received Logging. */ if (bgp_debug_update(peer, p, NULL, 1)) { if (!peer->rcvd_attr_printed) { zlog_debug("%s rcvd UPDATE w/ attr: %s", peer->host, peer->rcvd_attr_str); peer->rcvd_attr_printed = 1; } bgp_debug_rdpfxpath2str(afi, safi, prd, p, label, num_labels, addpath_id ? 1 : 0, addpath_id, pfx_buf, sizeof(pfx_buf)); zlog_debug("%s rcvd %s", peer->host, pfx_buf); } /* Make new BGP info. */ new = info_make(type, sub_type, 0, peer, attr_new, rn); /* Update MPLS label */ if (has_valid_label) { extra = bgp_path_info_extra_get(new); if (extra->label != label) { memcpy(&extra->label, label, num_labels * sizeof(mpls_label_t)); extra->num_labels = num_labels; } if (!(afi == AFI_L2VPN && safi == SAFI_EVPN)) bgp_set_valid_label(&extra->label[0]); } /* Update Overlay Index */ if (afi == AFI_L2VPN) { overlay_index_update(new->attr, evpn == NULL ? NULL : &evpn->eth_s_id, evpn == NULL ? NULL : &evpn->gw_ip); } /* Nexthop reachability check. */ if ((afi == AFI_IP || afi == AFI_IP6) && (safi == SAFI_UNICAST || safi == SAFI_LABELED_UNICAST)) { if (peer->sort == BGP_PEER_EBGP && peer->ttl == 1 && !CHECK_FLAG(peer->flags, PEER_FLAG_DISABLE_CONNECTED_CHECK) && !bgp_flag_check(bgp, BGP_FLAG_DISABLE_NH_CONNECTED_CHK)) connected = 1; else connected = 0; if (bgp_find_or_add_nexthop(bgp, bgp, afi, new, NULL, connected) || CHECK_FLAG(peer->flags, PEER_FLAG_IS_RFAPI_HD)) bgp_path_info_set_flag(rn, new, BGP_PATH_VALID); else { if (BGP_DEBUG(nht, NHT)) { char buf1[INET6_ADDRSTRLEN]; inet_ntop(AF_INET, (const void *)&attr_new->nexthop, buf1, INET6_ADDRSTRLEN); zlog_debug("%s(%s): NH unresolved", __FUNCTION__, buf1); } bgp_path_info_unset_flag(rn, new, BGP_PATH_VALID); } } else bgp_path_info_set_flag(rn, new, BGP_PATH_VALID); /* Addpath ID */ new->addpath_rx_id = addpath_id; /* Increment prefix */ bgp_aggregate_increment(bgp, p, new, afi, safi); /* Register new BGP information. */ bgp_path_info_add(rn, new); /* route_node_get lock */ bgp_unlock_node(rn); #if ENABLE_BGP_VNC if (safi == SAFI_MPLS_VPN) { struct bgp_node *prn = NULL; struct bgp_table *table = NULL; prn = bgp_node_get(bgp->rib[afi][safi], (struct prefix *)prd); if (bgp_node_has_bgp_path_info_data(prn)) { table = bgp_node_get_bgp_table_info(prn); vnc_import_bgp_add_vnc_host_route_mode_resolve_nve( bgp, prd, table, p, new); } bgp_unlock_node(prn); } #endif /* If maximum prefix count is configured and current prefix count exeed it. */ if (bgp_maximum_prefix_overflow(peer, afi, safi, 0)) return -1; /* If this is an EVPN route, process for import. */ if (safi == SAFI_EVPN) bgp_evpn_import_route(bgp, afi, safi, p, new); hook_call(bgp_process, bgp, afi, safi, rn, peer, false); /* Process change. */ bgp_process(bgp, rn, afi, safi); if (SAFI_UNICAST == safi && (bgp->inst_type == BGP_INSTANCE_TYPE_VRF || bgp->inst_type == BGP_INSTANCE_TYPE_DEFAULT)) { vpn_leak_from_vrf_update(bgp_get_default(), bgp, new); } if ((SAFI_MPLS_VPN == safi) && (bgp->inst_type == BGP_INSTANCE_TYPE_DEFAULT)) { vpn_leak_to_vrf_update(bgp, new); } #if ENABLE_BGP_VNC if (SAFI_MPLS_VPN == safi) { mpls_label_t label_decoded = decode_label(label); rfapiProcessUpdate(peer, NULL, p, prd, attr, afi, safi, type, sub_type, &label_decoded); } if (SAFI_ENCAP == safi) { rfapiProcessUpdate(peer, NULL, p, prd, attr, afi, safi, type, sub_type, NULL); } #endif return 0; /* This BGP update is filtered. Log the reason then update BGP entry. */ filtered: hook_call(bgp_process, bgp, afi, safi, rn, peer, true); if (bgp_debug_update(peer, p, NULL, 1)) { if (!peer->rcvd_attr_printed) { zlog_debug("%s rcvd UPDATE w/ attr: %s", peer->host, peer->rcvd_attr_str); peer->rcvd_attr_printed = 1; } bgp_debug_rdpfxpath2str(afi, safi, prd, p, label, num_labels, addpath_id ? 1 : 0, addpath_id, pfx_buf, sizeof(pfx_buf)); zlog_debug("%s rcvd UPDATE about %s -- DENIED due to: %s", peer->host, pfx_buf, reason); } if (pi) { /* If this is an EVPN route, un-import it as it is now filtered. */ if (safi == SAFI_EVPN) bgp_evpn_unimport_route(bgp, afi, safi, p, pi); if (SAFI_UNICAST == safi && (bgp->inst_type == BGP_INSTANCE_TYPE_VRF || bgp->inst_type == BGP_INSTANCE_TYPE_DEFAULT)) { vpn_leak_from_vrf_withdraw(bgp_get_default(), bgp, pi); } if ((SAFI_MPLS_VPN == safi) && (bgp->inst_type == BGP_INSTANCE_TYPE_DEFAULT)) { vpn_leak_to_vrf_withdraw(bgp, pi); } bgp_rib_remove(rn, pi, peer, afi, safi); } bgp_unlock_node(rn); #if ENABLE_BGP_VNC /* * Filtered update is treated as an implicit withdrawal (see * bgp_rib_remove() * a few lines above) */ if ((SAFI_MPLS_VPN == safi) || (SAFI_ENCAP == safi)) { rfapiProcessWithdraw(peer, NULL, p, prd, NULL, afi, safi, type, 0); } #endif return 0; } int bgp_withdraw(struct peer *peer, struct prefix *p, uint32_t addpath_id, struct attr *attr, afi_t afi, safi_t safi, int type, int sub_type, struct prefix_rd *prd, mpls_label_t *label, uint32_t num_labels, struct bgp_route_evpn *evpn) { struct bgp *bgp; char pfx_buf[BGP_PRD_PATH_STRLEN]; struct bgp_node *rn; struct bgp_path_info *pi; #if ENABLE_BGP_VNC if ((SAFI_MPLS_VPN == safi) || (SAFI_ENCAP == safi)) { rfapiProcessWithdraw(peer, NULL, p, prd, NULL, afi, safi, type, 0); } #endif bgp = peer->bgp; /* Lookup node. */ rn = bgp_afi_node_get(bgp->rib[afi][safi], afi, safi, p, prd); /* If peer is soft reconfiguration enabled. Record input packet for * further calculation. * * Cisco IOS 12.4(24)T4 on session establishment sends withdraws for all * routes that are filtered. This tanks out Quagga RS pretty badly due * to * the iteration over all RS clients. * Since we need to remove the entry from adj_in anyway, do that first * and * if there was no entry, we don't need to do anything more. */ if (CHECK_FLAG(peer->af_flags[afi][safi], PEER_FLAG_SOFT_RECONFIG) && peer != bgp->peer_self) if (!bgp_adj_in_unset(rn, peer, addpath_id)) { peer->stat_pfx_dup_withdraw++; if (bgp_debug_update(peer, p, NULL, 1)) { bgp_debug_rdpfxpath2str( afi, safi, prd, p, label, num_labels, addpath_id ? 1 : 0, addpath_id, pfx_buf, sizeof(pfx_buf)); zlog_debug( "%s withdrawing route %s not in adj-in", peer->host, pfx_buf); } bgp_unlock_node(rn); return 0; } /* Lookup withdrawn route. */ for (pi = bgp_node_get_bgp_path_info(rn); pi; pi = pi->next) if (pi->peer == peer && pi->type == type && pi->sub_type == sub_type && pi->addpath_rx_id == addpath_id) break; /* Logging. */ if (bgp_debug_update(peer, p, NULL, 1)) { bgp_debug_rdpfxpath2str(afi, safi, prd, p, label, num_labels, addpath_id ? 1 : 0, addpath_id, pfx_buf, sizeof(pfx_buf)); zlog_debug("%s rcvd UPDATE about %s -- withdrawn", peer->host, pfx_buf); } /* Withdraw specified route from routing table. */ if (pi && !CHECK_FLAG(pi->flags, BGP_PATH_HISTORY)) { bgp_rib_withdraw(rn, pi, peer, afi, safi, prd); if (SAFI_UNICAST == safi && (bgp->inst_type == BGP_INSTANCE_TYPE_VRF || bgp->inst_type == BGP_INSTANCE_TYPE_DEFAULT)) { vpn_leak_from_vrf_withdraw(bgp_get_default(), bgp, pi); } if ((SAFI_MPLS_VPN == safi) && (bgp->inst_type == BGP_INSTANCE_TYPE_DEFAULT)) { vpn_leak_to_vrf_withdraw(bgp, pi); } } else if (bgp_debug_update(peer, p, NULL, 1)) { bgp_debug_rdpfxpath2str(afi, safi, prd, p, label, num_labels, addpath_id ? 1 : 0, addpath_id, pfx_buf, sizeof(pfx_buf)); zlog_debug("%s Can't find the route %s", peer->host, pfx_buf); } /* Unlock bgp_node_get() lock. */ bgp_unlock_node(rn); return 0; } void bgp_default_originate(struct peer *peer, afi_t afi, safi_t safi, int withdraw) { struct update_subgroup *subgrp; subgrp = peer_subgroup(peer, afi, safi); subgroup_default_originate(subgrp, withdraw); } /* * bgp_stop_announce_route_timer */ void bgp_stop_announce_route_timer(struct peer_af *paf) { if (!paf->t_announce_route) return; THREAD_TIMER_OFF(paf->t_announce_route); } /* * bgp_announce_route_timer_expired * * Callback that is invoked when the route announcement timer for a * peer_af expires. */ static int bgp_announce_route_timer_expired(struct thread *t) { struct peer_af *paf; struct peer *peer; paf = THREAD_ARG(t); peer = paf->peer; if (peer->status != Established) return 0; if (!peer->afc_nego[paf->afi][paf->safi]) return 0; peer_af_announce_route(paf, 1); return 0; } /* * bgp_announce_route * * *Triggers* announcement of routes of a given AFI/SAFI to a peer. */ void bgp_announce_route(struct peer *peer, afi_t afi, safi_t safi) { struct peer_af *paf; struct update_subgroup *subgrp; paf = peer_af_find(peer, afi, safi); if (!paf) return; subgrp = PAF_SUBGRP(paf); /* * Ignore if subgroup doesn't exist (implies AF is not negotiated) * or a refresh has already been triggered. */ if (!subgrp || paf->t_announce_route) return; /* * Start a timer to stagger/delay the announce. This serves * two purposes - announcement can potentially be combined for * multiple peers and the announcement doesn't happen in the * vty context. */ thread_add_timer_msec(bm->master, bgp_announce_route_timer_expired, paf, (subgrp->peer_count == 1) ? BGP_ANNOUNCE_ROUTE_SHORT_DELAY_MS : BGP_ANNOUNCE_ROUTE_DELAY_MS, &paf->t_announce_route); } /* * Announce routes from all AF tables to a peer. * * This should ONLY be called when there is a need to refresh the * routes to the peer based on a policy change for this peer alone * or a route refresh request received from the peer. * The operation will result in splitting the peer from its existing * subgroups and putting it in new subgroups. */ void bgp_announce_route_all(struct peer *peer) { afi_t afi; safi_t safi; FOREACH_AFI_SAFI (afi, safi) bgp_announce_route(peer, afi, safi); } static void bgp_soft_reconfig_table(struct peer *peer, afi_t afi, safi_t safi, struct bgp_table *table, struct prefix_rd *prd) { int ret; struct bgp_node *rn; struct bgp_adj_in *ain; if (!table) table = peer->bgp->rib[afi][safi]; for (rn = bgp_table_top(table); rn; rn = bgp_route_next(rn)) for (ain = rn->adj_in; ain; ain = ain->next) { if (ain->peer != peer) continue; struct bgp_path_info *pi; uint32_t num_labels = 0; mpls_label_t *label_pnt = NULL; struct bgp_route_evpn evpn; for (pi = bgp_node_get_bgp_path_info(rn); pi; pi = pi->next) if (pi->peer == peer) break; if (pi && pi->extra) num_labels = pi->extra->num_labels; if (num_labels) label_pnt = &pi->extra->label[0]; if (pi) memcpy(&evpn, &pi->attr->evpn_overlay, sizeof(evpn)); else memset(&evpn, 0, sizeof(evpn)); ret = bgp_update(peer, &rn->p, ain->addpath_rx_id, ain->attr, afi, safi, ZEBRA_ROUTE_BGP, BGP_ROUTE_NORMAL, prd, label_pnt, num_labels, 1, &evpn); if (ret < 0) { bgp_unlock_node(rn); return; } } } void bgp_soft_reconfig_in(struct peer *peer, afi_t afi, safi_t safi) { struct bgp_node *rn; struct bgp_table *table; if (peer->status != Established) return; if ((safi != SAFI_MPLS_VPN) && (safi != SAFI_ENCAP) && (safi != SAFI_EVPN)) bgp_soft_reconfig_table(peer, afi, safi, NULL, NULL); else for (rn = bgp_table_top(peer->bgp->rib[afi][safi]); rn; rn = bgp_route_next(rn)) { table = bgp_node_get_bgp_table_info(rn); if (table != NULL) { struct prefix_rd prd; prd.family = AF_UNSPEC; prd.prefixlen = 64; memcpy(&prd.val, rn->p.u.val, 8); bgp_soft_reconfig_table(peer, afi, safi, table, &prd); } } } struct bgp_clear_node_queue { struct bgp_node *rn; }; static wq_item_status bgp_clear_route_node(struct work_queue *wq, void *data) { struct bgp_clear_node_queue *cnq = data; struct bgp_node *rn = cnq->rn; struct peer *peer = wq->spec.data; struct bgp_path_info *pi; struct bgp *bgp; afi_t afi = bgp_node_table(rn)->afi; safi_t safi = bgp_node_table(rn)->safi; assert(rn && peer); bgp = peer->bgp; /* It is possible that we have multiple paths for a prefix from a peer * if that peer is using AddPath. */ for (pi = bgp_node_get_bgp_path_info(rn); pi; pi = pi->next) { if (pi->peer != peer) continue; /* graceful restart STALE flag set. */ if (CHECK_FLAG(peer->sflags, PEER_STATUS_NSF_WAIT) && peer->nsf[afi][safi] && !CHECK_FLAG(pi->flags, BGP_PATH_STALE) && !CHECK_FLAG(pi->flags, BGP_PATH_UNUSEABLE)) bgp_path_info_set_flag(rn, pi, BGP_PATH_STALE); else { /* If this is an EVPN route, process for * un-import. */ if (safi == SAFI_EVPN) bgp_evpn_unimport_route(bgp, afi, safi, &rn->p, pi); /* Handle withdraw for VRF route-leaking and L3VPN */ if (SAFI_UNICAST == safi && (bgp->inst_type == BGP_INSTANCE_TYPE_VRF || bgp->inst_type == BGP_INSTANCE_TYPE_DEFAULT)) { vpn_leak_from_vrf_withdraw(bgp_get_default(), bgp, pi); } if (SAFI_MPLS_VPN == safi && bgp->inst_type == BGP_INSTANCE_TYPE_DEFAULT) { vpn_leak_to_vrf_withdraw(bgp, pi); } bgp_rib_remove(rn, pi, peer, afi, safi); } } return WQ_SUCCESS; } static void bgp_clear_node_queue_del(struct work_queue *wq, void *data) { struct bgp_clear_node_queue *cnq = data; struct bgp_node *rn = cnq->rn; struct bgp_table *table = bgp_node_table(rn); bgp_unlock_node(rn); bgp_table_unlock(table); XFREE(MTYPE_BGP_CLEAR_NODE_QUEUE, cnq); } static void bgp_clear_node_complete(struct work_queue *wq) { struct peer *peer = wq->spec.data; /* Tickle FSM to start moving again */ BGP_EVENT_ADD(peer, Clearing_Completed); peer_unlock(peer); /* bgp_clear_route */ } static void bgp_clear_node_queue_init(struct peer *peer) { char wname[sizeof("clear xxxx:xxxx:xxxx:xxxx:xxxx:xxxx:xxxx:xxxx")]; snprintf(wname, sizeof(wname), "clear %s", peer->host); #undef CLEAR_QUEUE_NAME_LEN peer->clear_node_queue = work_queue_new(bm->master, wname); peer->clear_node_queue->spec.hold = 10; peer->clear_node_queue->spec.workfunc = &bgp_clear_route_node; peer->clear_node_queue->spec.del_item_data = &bgp_clear_node_queue_del; peer->clear_node_queue->spec.completion_func = &bgp_clear_node_complete; peer->clear_node_queue->spec.max_retries = 0; /* we only 'lock' this peer reference when the queue is actually active */ peer->clear_node_queue->spec.data = peer; } static void bgp_clear_route_table(struct peer *peer, afi_t afi, safi_t safi, struct bgp_table *table) { struct bgp_node *rn; int force = bm->process_main_queue ? 0 : 1; if (!table) table = peer->bgp->rib[afi][safi]; /* If still no table => afi/safi isn't configured at all or smth. */ if (!table) return; for (rn = bgp_table_top(table); rn; rn = bgp_route_next(rn)) { struct bgp_path_info *pi, *next; struct bgp_adj_in *ain; struct bgp_adj_in *ain_next; /* XXX:TODO: This is suboptimal, every non-empty route_node is * queued for every clearing peer, regardless of whether it is * relevant to the peer at hand. * * Overview: There are 3 different indices which need to be * scrubbed, potentially, when a peer is removed: * * 1 peer's routes visible via the RIB (ie accepted routes) * 2 peer's routes visible by the (optional) peer's adj-in index * 3 other routes visible by the peer's adj-out index * * 3 there is no hurry in scrubbing, once the struct peer is * removed from bgp->peer, we could just GC such deleted peer's * adj-outs at our leisure. * * 1 and 2 must be 'scrubbed' in some way, at least made * invisible via RIB index before peer session is allowed to be * brought back up. So one needs to know when such a 'search' is * complete. * * Ideally: * * - there'd be a single global queue or a single RIB walker * - rather than tracking which route_nodes still need to be * examined on a peer basis, we'd track which peers still * aren't cleared * * Given that our per-peer prefix-counts now should be reliable, * this may actually be achievable. It doesn't seem to be a huge * problem at this time, * * It is possible that we have multiple paths for a prefix from * a peer * if that peer is using AddPath. */ ain = rn->adj_in; while (ain) { ain_next = ain->next; if (ain->peer == peer) { bgp_adj_in_remove(rn, ain); bgp_unlock_node(rn); } ain = ain_next; } for (pi = bgp_node_get_bgp_path_info(rn); pi; pi = next) { next = pi->next; if (pi->peer != peer) continue; if (force) bgp_path_info_reap(rn, pi); else { struct bgp_clear_node_queue *cnq; /* both unlocked in bgp_clear_node_queue_del */ bgp_table_lock(bgp_node_table(rn)); bgp_lock_node(rn); cnq = XCALLOC( MTYPE_BGP_CLEAR_NODE_QUEUE, sizeof(struct bgp_clear_node_queue)); cnq->rn = rn; work_queue_add(peer->clear_node_queue, cnq); break; } } } return; } void bgp_clear_route(struct peer *peer, afi_t afi, safi_t safi) { struct bgp_node *rn; struct bgp_table *table; if (peer->clear_node_queue == NULL) bgp_clear_node_queue_init(peer); /* bgp_fsm.c keeps sessions in state Clearing, not transitioning to * Idle until it receives a Clearing_Completed event. This protects * against peers which flap faster than we can we clear, which could * lead to: * * a) race with routes from the new session being installed before * clear_route_node visits the node (to delete the route of that * peer) * b) resource exhaustion, clear_route_node likely leads to an entry * on the process_main queue. Fast-flapping could cause that queue * to grow and grow. */ /* lock peer in assumption that clear-node-queue will get nodes; if so, * the unlock will happen upon work-queue completion; other wise, the * unlock happens at the end of this function. */ if (!peer->clear_node_queue->thread) peer_lock(peer); if (safi != SAFI_MPLS_VPN && safi != SAFI_ENCAP && safi != SAFI_EVPN) bgp_clear_route_table(peer, afi, safi, NULL); else for (rn = bgp_table_top(peer->bgp->rib[afi][safi]); rn; rn = bgp_route_next(rn)) { table = bgp_node_get_bgp_table_info(rn); if (!table) continue; bgp_clear_route_table(peer, afi, safi, table); } /* unlock if no nodes got added to the clear-node-queue. */ if (!peer->clear_node_queue->thread) peer_unlock(peer); } void bgp_clear_route_all(struct peer *peer) { afi_t afi; safi_t safi; FOREACH_AFI_SAFI (afi, safi) bgp_clear_route(peer, afi, safi); #if ENABLE_BGP_VNC rfapiProcessPeerDown(peer); #endif } void bgp_clear_adj_in(struct peer *peer, afi_t afi, safi_t safi) { struct bgp_table *table; struct bgp_node *rn; struct bgp_adj_in *ain; struct bgp_adj_in *ain_next; table = peer->bgp->rib[afi][safi]; /* It is possible that we have multiple paths for a prefix from a peer * if that peer is using AddPath. */ for (rn = bgp_table_top(table); rn; rn = bgp_route_next(rn)) { ain = rn->adj_in; while (ain) { ain_next = ain->next; if (ain->peer == peer) { bgp_adj_in_remove(rn, ain); bgp_unlock_node(rn); } ain = ain_next; } } } void bgp_clear_stale_route(struct peer *peer, afi_t afi, safi_t safi) { struct bgp_node *rn; struct bgp_path_info *pi; struct bgp_table *table; if (safi == SAFI_MPLS_VPN) { for (rn = bgp_table_top(peer->bgp->rib[afi][safi]); rn; rn = bgp_route_next(rn)) { struct bgp_node *rm; /* look for neighbor in tables */ table = bgp_node_get_bgp_table_info(rn); if (!table) continue; for (rm = bgp_table_top(table); rm; rm = bgp_route_next(rm)) for (pi = bgp_node_get_bgp_path_info(rm); pi; pi = pi->next) { if (pi->peer != peer) continue; if (!CHECK_FLAG(pi->flags, BGP_PATH_STALE)) break; bgp_rib_remove(rm, pi, peer, afi, safi); break; } } } else { for (rn = bgp_table_top(peer->bgp->rib[afi][safi]); rn; rn = bgp_route_next(rn)) for (pi = bgp_node_get_bgp_path_info(rn); pi; pi = pi->next) { if (pi->peer != peer) continue; if (!CHECK_FLAG(pi->flags, BGP_PATH_STALE)) break; bgp_rib_remove(rn, pi, peer, afi, safi); break; } } } int bgp_outbound_policy_exists(struct peer *peer, struct bgp_filter *filter) { if (peer->sort == BGP_PEER_IBGP) return 1; if (peer->sort == BGP_PEER_EBGP && (ROUTE_MAP_OUT_NAME(filter) || PREFIX_LIST_OUT_NAME(filter) || FILTER_LIST_OUT_NAME(filter) || DISTRIBUTE_OUT_NAME(filter))) return 1; return 0; } int bgp_inbound_policy_exists(struct peer *peer, struct bgp_filter *filter) { if (peer->sort == BGP_PEER_IBGP) return 1; if (peer->sort == BGP_PEER_EBGP && (ROUTE_MAP_IN_NAME(filter) || PREFIX_LIST_IN_NAME(filter) || FILTER_LIST_IN_NAME(filter) || DISTRIBUTE_IN_NAME(filter))) return 1; return 0; } static void bgp_cleanup_table(struct bgp *bgp, struct bgp_table *table, safi_t safi) { struct bgp_node *rn; struct bgp_path_info *pi; struct bgp_path_info *next; for (rn = bgp_table_top(table); rn; rn = bgp_route_next(rn)) for (pi = bgp_node_get_bgp_path_info(rn); pi; pi = next) { next = pi->next; /* Unimport EVPN routes from VRFs */ if (safi == SAFI_EVPN) bgp_evpn_unimport_route(bgp, AFI_L2VPN, SAFI_EVPN, &rn->p, pi); if (CHECK_FLAG(pi->flags, BGP_PATH_SELECTED) && pi->type == ZEBRA_ROUTE_BGP && (pi->sub_type == BGP_ROUTE_NORMAL || pi->sub_type == BGP_ROUTE_AGGREGATE || pi->sub_type == BGP_ROUTE_IMPORTED)) { if (bgp_fibupd_safi(safi)) bgp_zebra_withdraw(&rn->p, pi, bgp, safi); bgp_path_info_reap(rn, pi); } } } /* Delete all kernel routes. */ void bgp_cleanup_routes(struct bgp *bgp) { afi_t afi; struct bgp_node *rn; struct bgp_table *table; for (afi = AFI_IP; afi < AFI_MAX; ++afi) { if (afi == AFI_L2VPN) continue; bgp_cleanup_table(bgp, bgp->rib[afi][SAFI_UNICAST], SAFI_UNICAST); /* * VPN and ENCAP and EVPN tables are two-level (RD is top level) */ if (afi != AFI_L2VPN) { safi_t safi; safi = SAFI_MPLS_VPN; for (rn = bgp_table_top(bgp->rib[afi][safi]); rn; rn = bgp_route_next(rn)) { table = bgp_node_get_bgp_table_info(rn); if (table != NULL) { bgp_cleanup_table(bgp, table, safi); bgp_table_finish(&table); bgp_node_set_bgp_table_info(rn, NULL); bgp_unlock_node(rn); } } safi = SAFI_ENCAP; for (rn = bgp_table_top(bgp->rib[afi][safi]); rn; rn = bgp_route_next(rn)) { table = bgp_node_get_bgp_table_info(rn); if (table != NULL) { bgp_cleanup_table(bgp, table, safi); bgp_table_finish(&table); bgp_node_set_bgp_table_info(rn, NULL); bgp_unlock_node(rn); } } } } for (rn = bgp_table_top(bgp->rib[AFI_L2VPN][SAFI_EVPN]); rn; rn = bgp_route_next(rn)) { table = bgp_node_get_bgp_table_info(rn); if (table != NULL) { bgp_cleanup_table(bgp, table, SAFI_EVPN); bgp_table_finish(&table); bgp_node_set_bgp_table_info(rn, NULL); bgp_unlock_node(rn); } } } void bgp_reset(void) { vty_reset(); bgp_zclient_reset(); access_list_reset(); prefix_list_reset(); } static int bgp_addpath_encode_rx(struct peer *peer, afi_t afi, safi_t safi) { return (CHECK_FLAG(peer->af_cap[afi][safi], PEER_CAP_ADDPATH_AF_RX_ADV) && CHECK_FLAG(peer->af_cap[afi][safi], PEER_CAP_ADDPATH_AF_TX_RCV)); } /* Parse NLRI stream. Withdraw NLRI is recognized by NULL attr value. */ int bgp_nlri_parse_ip(struct peer *peer, struct attr *attr, struct bgp_nlri *packet) { uint8_t *pnt; uint8_t *lim; struct prefix p; int psize; int ret; afi_t afi; safi_t safi; int addpath_encoded; uint32_t addpath_id; pnt = packet->nlri; lim = pnt + packet->length; afi = packet->afi; safi = packet->safi; addpath_id = 0; addpath_encoded = bgp_addpath_encode_rx(peer, afi, safi); /* RFC4771 6.3 The NLRI field in the UPDATE message is checked for syntactic validity. If the field is syntactically incorrect, then the Error Subcode is set to Invalid Network Field. */ for (; pnt < lim; pnt += psize) { /* Clear prefix structure. */ memset(&p, 0, sizeof(struct prefix)); if (addpath_encoded) { /* When packet overflow occurs return immediately. */ if (pnt + BGP_ADDPATH_ID_LEN >= lim) return BGP_NLRI_PARSE_ERROR_PACKET_OVERFLOW; memcpy(&addpath_id, pnt, BGP_ADDPATH_ID_LEN); addpath_id = ntohl(addpath_id); pnt += BGP_ADDPATH_ID_LEN; } /* Fetch prefix length. */ p.prefixlen = *pnt++; /* afi/safi validity already verified by caller, * bgp_update_receive */ p.family = afi2family(afi); /* Prefix length check. */ if (p.prefixlen > prefix_blen(&p) * 8) { flog_err( EC_BGP_UPDATE_RCV, "%s [Error] Update packet error (wrong prefix length %d for afi %u)", peer->host, p.prefixlen, packet->afi); return BGP_NLRI_PARSE_ERROR_PREFIX_LENGTH; } /* Packet size overflow check. */ psize = PSIZE(p.prefixlen); /* When packet overflow occur return immediately. */ if (pnt + psize > lim) { flog_err( EC_BGP_UPDATE_RCV, "%s [Error] Update packet error (prefix length %d overflows packet)", peer->host, p.prefixlen); return BGP_NLRI_PARSE_ERROR_PACKET_OVERFLOW; } /* Defensive coding, double-check the psize fits in a struct * prefix */ if (psize > (ssize_t)sizeof(p.u)) { flog_err( EC_BGP_UPDATE_RCV, "%s [Error] Update packet error (prefix length %d too large for prefix storage %zu)", peer->host, p.prefixlen, sizeof(p.u)); return BGP_NLRI_PARSE_ERROR_PACKET_LENGTH; } /* Fetch prefix from NLRI packet. */ memcpy(p.u.val, pnt, psize); /* Check address. */ if (afi == AFI_IP && safi == SAFI_UNICAST) { if (IN_CLASSD(ntohl(p.u.prefix4.s_addr))) { /* From RFC4271 Section 6.3: * * If a prefix in the NLRI field is semantically * incorrect * (e.g., an unexpected multicast IP address), * an error SHOULD * be logged locally, and the prefix SHOULD be * ignored. */ flog_err( EC_BGP_UPDATE_RCV, "%s: IPv4 unicast NLRI is multicast address %s, ignoring", peer->host, inet_ntoa(p.u.prefix4)); continue; } } /* Check address. */ if (afi == AFI_IP6 && safi == SAFI_UNICAST) { if (IN6_IS_ADDR_LINKLOCAL(&p.u.prefix6)) { char buf[BUFSIZ]; flog_err( EC_BGP_UPDATE_RCV, "%s: IPv6 unicast NLRI is link-local address %s, ignoring", peer->host, inet_ntop(AF_INET6, &p.u.prefix6, buf, BUFSIZ)); continue; } if (IN6_IS_ADDR_MULTICAST(&p.u.prefix6)) { char buf[BUFSIZ]; flog_err( EC_BGP_UPDATE_RCV, "%s: IPv6 unicast NLRI is multicast address %s, ignoring", peer->host, inet_ntop(AF_INET6, &p.u.prefix6, buf, BUFSIZ)); continue; } } /* Normal process. */ if (attr) ret = bgp_update(peer, &p, addpath_id, attr, afi, safi, ZEBRA_ROUTE_BGP, BGP_ROUTE_NORMAL, NULL, NULL, 0, 0, NULL); else ret = bgp_withdraw(peer, &p, addpath_id, attr, afi, safi, ZEBRA_ROUTE_BGP, BGP_ROUTE_NORMAL, NULL, NULL, 0, NULL); /* Do not send BGP notification twice when maximum-prefix count * overflow. */ if (CHECK_FLAG(peer->sflags, PEER_STATUS_PREFIX_OVERFLOW)) return BGP_NLRI_PARSE_ERROR_PREFIX_OVERFLOW; /* Address family configuration mismatch. */ if (ret < 0) return BGP_NLRI_PARSE_ERROR_ADDRESS_FAMILY; } /* Packet length consistency check. */ if (pnt != lim) { flog_err( EC_BGP_UPDATE_RCV, "%s [Error] Update packet error (prefix length mismatch with total length)", peer->host); return BGP_NLRI_PARSE_ERROR_PACKET_LENGTH; } return BGP_NLRI_PARSE_OK; } static struct bgp_static *bgp_static_new(void) { return XCALLOC(MTYPE_BGP_STATIC, sizeof(struct bgp_static)); } static void bgp_static_free(struct bgp_static *bgp_static) { XFREE(MTYPE_ROUTE_MAP_NAME, bgp_static->rmap.name); route_map_counter_decrement(bgp_static->rmap.map); XFREE(MTYPE_ATTR, bgp_static->eth_s_id); XFREE(MTYPE_BGP_STATIC, bgp_static); } void bgp_static_update(struct bgp *bgp, struct prefix *p, struct bgp_static *bgp_static, afi_t afi, safi_t safi) { struct bgp_node *rn; struct bgp_path_info *pi; struct bgp_path_info *new; struct bgp_path_info rmap_path; struct attr attr; struct attr *attr_new; route_map_result_t ret; #if ENABLE_BGP_VNC int vnc_implicit_withdraw = 0; #endif assert(bgp_static); if (!bgp_static) return; rn = bgp_afi_node_get(bgp->rib[afi][safi], afi, safi, p, NULL); bgp_attr_default_set(&attr, BGP_ORIGIN_IGP); attr.nexthop = bgp_static->igpnexthop; attr.med = bgp_static->igpmetric; attr.flag |= ATTR_FLAG_BIT(BGP_ATTR_MULTI_EXIT_DISC); if (bgp_static->atomic) attr.flag |= ATTR_FLAG_BIT(BGP_ATTR_ATOMIC_AGGREGATE); /* Store label index, if required. */ if (bgp_static->label_index != BGP_INVALID_LABEL_INDEX) { attr.label_index = bgp_static->label_index; attr.flag |= ATTR_FLAG_BIT(BGP_ATTR_PREFIX_SID); } /* Apply route-map. */ if (bgp_static->rmap.name) { struct attr attr_tmp = attr; memset(&rmap_path, 0, sizeof(struct bgp_path_info)); rmap_path.peer = bgp->peer_self; rmap_path.attr = &attr_tmp; SET_FLAG(bgp->peer_self->rmap_type, PEER_RMAP_TYPE_NETWORK); ret = route_map_apply(bgp_static->rmap.map, p, RMAP_BGP, &rmap_path); bgp->peer_self->rmap_type = 0; if (ret == RMAP_DENYMATCH) { /* Free uninterned attribute. */ bgp_attr_flush(&attr_tmp); /* Unintern original. */ aspath_unintern(&attr.aspath); bgp_static_withdraw(bgp, p, afi, safi); return; } if (bgp_flag_check(bgp, BGP_FLAG_GRACEFUL_SHUTDOWN)) bgp_attr_add_gshut_community(&attr_tmp); attr_new = bgp_attr_intern(&attr_tmp); } else { if (bgp_flag_check(bgp, BGP_FLAG_GRACEFUL_SHUTDOWN)) bgp_attr_add_gshut_community(&attr); attr_new = bgp_attr_intern(&attr); } for (pi = bgp_node_get_bgp_path_info(rn); pi; pi = pi->next) if (pi->peer == bgp->peer_self && pi->type == ZEBRA_ROUTE_BGP && pi->sub_type == BGP_ROUTE_STATIC) break; if (pi) { if (attrhash_cmp(pi->attr, attr_new) && !CHECK_FLAG(pi->flags, BGP_PATH_REMOVED) && !bgp_flag_check(bgp, BGP_FLAG_FORCE_STATIC_PROCESS)) { bgp_unlock_node(rn); bgp_attr_unintern(&attr_new); aspath_unintern(&attr.aspath); return; } else { /* The attribute is changed. */ bgp_path_info_set_flag(rn, pi, BGP_PATH_ATTR_CHANGED); /* Rewrite BGP route information. */ if (CHECK_FLAG(pi->flags, BGP_PATH_REMOVED)) bgp_path_info_restore(rn, pi); else bgp_aggregate_decrement(bgp, p, pi, afi, safi); #if ENABLE_BGP_VNC if ((afi == AFI_IP || afi == AFI_IP6) && (safi == SAFI_UNICAST)) { if (CHECK_FLAG(pi->flags, BGP_PATH_SELECTED)) { /* * Implicit withdraw case. * We have to do this before pi is * changed */ ++vnc_implicit_withdraw; vnc_import_bgp_del_route(bgp, p, pi); vnc_import_bgp_exterior_del_route( bgp, p, pi); } } #endif bgp_attr_unintern(&pi->attr); pi->attr = attr_new; pi->uptime = bgp_clock(); #if ENABLE_BGP_VNC if ((afi == AFI_IP || afi == AFI_IP6) && (safi == SAFI_UNICAST)) { if (vnc_implicit_withdraw) { vnc_import_bgp_add_route(bgp, p, pi); vnc_import_bgp_exterior_add_route( bgp, p, pi); } } #endif /* Nexthop reachability check. */ if (bgp_flag_check(bgp, BGP_FLAG_IMPORT_CHECK) && (safi == SAFI_UNICAST || safi == SAFI_LABELED_UNICAST)) { struct bgp *bgp_nexthop = bgp; if (pi->extra && pi->extra->bgp_orig) bgp_nexthop = pi->extra->bgp_orig; if (bgp_find_or_add_nexthop(bgp, bgp_nexthop, afi, pi, NULL, 0)) bgp_path_info_set_flag(rn, pi, BGP_PATH_VALID); else { if (BGP_DEBUG(nht, NHT)) { char buf1[INET6_ADDRSTRLEN]; inet_ntop(p->family, &p->u.prefix, buf1, INET6_ADDRSTRLEN); zlog_debug( "%s(%s): Route not in table, not advertising", __FUNCTION__, buf1); } bgp_path_info_unset_flag( rn, pi, BGP_PATH_VALID); } } else { /* Delete the NHT structure if any, if we're * toggling between * enabling/disabling import check. We * deregister the route * from NHT to avoid overloading NHT and the * process interaction */ bgp_unlink_nexthop(pi); bgp_path_info_set_flag(rn, pi, BGP_PATH_VALID); } /* Process change. */ bgp_aggregate_increment(bgp, p, pi, afi, safi); bgp_process(bgp, rn, afi, safi); if (SAFI_UNICAST == safi && (bgp->inst_type == BGP_INSTANCE_TYPE_VRF || bgp->inst_type == BGP_INSTANCE_TYPE_DEFAULT)) { vpn_leak_from_vrf_update(bgp_get_default(), bgp, pi); } bgp_unlock_node(rn); aspath_unintern(&attr.aspath); return; } } /* Make new BGP info. */ new = info_make(ZEBRA_ROUTE_BGP, BGP_ROUTE_STATIC, 0, bgp->peer_self, attr_new, rn); /* Nexthop reachability check. */ if (bgp_flag_check(bgp, BGP_FLAG_IMPORT_CHECK) && (safi == SAFI_UNICAST || safi == SAFI_LABELED_UNICAST)) { if (bgp_find_or_add_nexthop(bgp, bgp, afi, new, NULL, 0)) bgp_path_info_set_flag(rn, new, BGP_PATH_VALID); else { if (BGP_DEBUG(nht, NHT)) { char buf1[INET6_ADDRSTRLEN]; inet_ntop(p->family, &p->u.prefix, buf1, INET6_ADDRSTRLEN); zlog_debug( "%s(%s): Route not in table, not advertising", __FUNCTION__, buf1); } bgp_path_info_unset_flag(rn, new, BGP_PATH_VALID); } } else { /* Delete the NHT structure if any, if we're toggling between * enabling/disabling import check. We deregister the route * from NHT to avoid overloading NHT and the process interaction */ bgp_unlink_nexthop(new); bgp_path_info_set_flag(rn, new, BGP_PATH_VALID); } /* Aggregate address increment. */ bgp_aggregate_increment(bgp, p, new, afi, safi); /* Register new BGP information. */ bgp_path_info_add(rn, new); /* route_node_get lock */ bgp_unlock_node(rn); /* Process change. */ bgp_process(bgp, rn, afi, safi); if (SAFI_UNICAST == safi && (bgp->inst_type == BGP_INSTANCE_TYPE_VRF || bgp->inst_type == BGP_INSTANCE_TYPE_DEFAULT)) { vpn_leak_from_vrf_update(bgp_get_default(), bgp, new); } /* Unintern original. */ aspath_unintern(&attr.aspath); } void bgp_static_withdraw(struct bgp *bgp, struct prefix *p, afi_t afi, safi_t safi) { struct bgp_node *rn; struct bgp_path_info *pi; rn = bgp_afi_node_get(bgp->rib[afi][safi], afi, safi, p, NULL); /* Check selected route and self inserted route. */ for (pi = bgp_node_get_bgp_path_info(rn); pi; pi = pi->next) if (pi->peer == bgp->peer_self && pi->type == ZEBRA_ROUTE_BGP && pi->sub_type == BGP_ROUTE_STATIC) break; /* Withdraw static BGP route from routing table. */ if (pi) { if (SAFI_UNICAST == safi && (bgp->inst_type == BGP_INSTANCE_TYPE_VRF || bgp->inst_type == BGP_INSTANCE_TYPE_DEFAULT)) { vpn_leak_from_vrf_withdraw(bgp_get_default(), bgp, pi); } bgp_aggregate_decrement(bgp, p, pi, afi, safi); bgp_unlink_nexthop(pi); bgp_path_info_delete(rn, pi); bgp_process(bgp, rn, afi, safi); } /* Unlock bgp_node_lookup. */ bgp_unlock_node(rn); } /* * Used for SAFI_MPLS_VPN and SAFI_ENCAP */ static void bgp_static_withdraw_safi(struct bgp *bgp, struct prefix *p, afi_t afi, safi_t safi, struct prefix_rd *prd) { struct bgp_node *rn; struct bgp_path_info *pi; rn = bgp_afi_node_get(bgp->rib[afi][safi], afi, safi, p, prd); /* Check selected route and self inserted route. */ for (pi = bgp_node_get_bgp_path_info(rn); pi; pi = pi->next) if (pi->peer == bgp->peer_self && pi->type == ZEBRA_ROUTE_BGP && pi->sub_type == BGP_ROUTE_STATIC) break; /* Withdraw static BGP route from routing table. */ if (pi) { #if ENABLE_BGP_VNC rfapiProcessWithdraw( pi->peer, NULL, p, prd, pi->attr, afi, safi, pi->type, 1); /* Kill, since it is an administrative change */ #endif if (SAFI_MPLS_VPN == safi && bgp->inst_type == BGP_INSTANCE_TYPE_DEFAULT) { vpn_leak_to_vrf_withdraw(bgp, pi); } bgp_aggregate_decrement(bgp, p, pi, afi, safi); bgp_path_info_delete(rn, pi); bgp_process(bgp, rn, afi, safi); } /* Unlock bgp_node_lookup. */ bgp_unlock_node(rn); } static void bgp_static_update_safi(struct bgp *bgp, struct prefix *p, struct bgp_static *bgp_static, afi_t afi, safi_t safi) { struct bgp_node *rn; struct bgp_path_info *new; struct attr *attr_new; struct attr attr = {0}; struct bgp_path_info *pi; #if ENABLE_BGP_VNC mpls_label_t label = 0; #endif uint32_t num_labels = 0; union gw_addr add; assert(bgp_static); if (bgp_static->label != MPLS_INVALID_LABEL) num_labels = 1; rn = bgp_afi_node_get(bgp->rib[afi][safi], afi, safi, p, &bgp_static->prd); bgp_attr_default_set(&attr, BGP_ORIGIN_IGP); attr.nexthop = bgp_static->igpnexthop; attr.med = bgp_static->igpmetric; attr.flag |= ATTR_FLAG_BIT(BGP_ATTR_MULTI_EXIT_DISC); if ((safi == SAFI_EVPN) || (safi == SAFI_MPLS_VPN) || (safi == SAFI_ENCAP)) { if (afi == AFI_IP) { attr.mp_nexthop_global_in = bgp_static->igpnexthop; attr.mp_nexthop_len = IPV4_MAX_BYTELEN; } } if (afi == AFI_L2VPN) { if (bgp_static->gatewayIp.family == AF_INET) add.ipv4.s_addr = bgp_static->gatewayIp.u.prefix4.s_addr; else if (bgp_static->gatewayIp.family == AF_INET6) memcpy(&(add.ipv6), &(bgp_static->gatewayIp.u.prefix6), sizeof(struct in6_addr)); overlay_index_update(&attr, bgp_static->eth_s_id, &add); if (bgp_static->encap_tunneltype == BGP_ENCAP_TYPE_VXLAN) { struct bgp_encap_type_vxlan bet; memset(&bet, 0, sizeof(struct bgp_encap_type_vxlan)); bet.vnid = p->u.prefix_evpn.prefix_addr.eth_tag; bgp_encap_type_vxlan_to_tlv(&bet, &attr); } if (bgp_static->router_mac) { bgp_add_routermac_ecom(&attr, bgp_static->router_mac); } } /* Apply route-map. */ if (bgp_static->rmap.name) { struct attr attr_tmp = attr; struct bgp_path_info rmap_path; route_map_result_t ret; rmap_path.peer = bgp->peer_self; rmap_path.attr = &attr_tmp; SET_FLAG(bgp->peer_self->rmap_type, PEER_RMAP_TYPE_NETWORK); ret = route_map_apply(bgp_static->rmap.map, p, RMAP_BGP, &rmap_path); bgp->peer_self->rmap_type = 0; if (ret == RMAP_DENYMATCH) { /* Free uninterned attribute. */ bgp_attr_flush(&attr_tmp); /* Unintern original. */ aspath_unintern(&attr.aspath); bgp_static_withdraw_safi(bgp, p, afi, safi, &bgp_static->prd); return; } attr_new = bgp_attr_intern(&attr_tmp); } else { attr_new = bgp_attr_intern(&attr); } for (pi = bgp_node_get_bgp_path_info(rn); pi; pi = pi->next) if (pi->peer == bgp->peer_self && pi->type == ZEBRA_ROUTE_BGP && pi->sub_type == BGP_ROUTE_STATIC) break; if (pi) { memset(&add, 0, sizeof(union gw_addr)); if (attrhash_cmp(pi->attr, attr_new) && overlay_index_equal(afi, pi, bgp_static->eth_s_id, &add) && !CHECK_FLAG(pi->flags, BGP_PATH_REMOVED)) { bgp_unlock_node(rn); bgp_attr_unintern(&attr_new); aspath_unintern(&attr.aspath); return; } else { /* The attribute is changed. */ bgp_path_info_set_flag(rn, pi, BGP_PATH_ATTR_CHANGED); /* Rewrite BGP route information. */ if (CHECK_FLAG(pi->flags, BGP_PATH_REMOVED)) bgp_path_info_restore(rn, pi); else bgp_aggregate_decrement(bgp, p, pi, afi, safi); bgp_attr_unintern(&pi->attr); pi->attr = attr_new; pi->uptime = bgp_clock(); #if ENABLE_BGP_VNC if (pi->extra) label = decode_label(&pi->extra->label[0]); #endif /* Process change. */ bgp_aggregate_increment(bgp, p, pi, afi, safi); bgp_process(bgp, rn, afi, safi); if (SAFI_MPLS_VPN == safi && bgp->inst_type == BGP_INSTANCE_TYPE_DEFAULT) { vpn_leak_to_vrf_update(bgp, pi); } #if ENABLE_BGP_VNC rfapiProcessUpdate(pi->peer, NULL, p, &bgp_static->prd, pi->attr, afi, safi, pi->type, pi->sub_type, &label); #endif bgp_unlock_node(rn); aspath_unintern(&attr.aspath); return; } } /* Make new BGP info. */ new = info_make(ZEBRA_ROUTE_BGP, BGP_ROUTE_STATIC, 0, bgp->peer_self, attr_new, rn); SET_FLAG(new->flags, BGP_PATH_VALID); new->extra = bgp_path_info_extra_new(); if (num_labels) { new->extra->label[0] = bgp_static->label; new->extra->num_labels = num_labels; } #if ENABLE_BGP_VNC label = decode_label(&bgp_static->label); #endif /* Aggregate address increment. */ bgp_aggregate_increment(bgp, p, new, afi, safi); /* Register new BGP information. */ bgp_path_info_add(rn, new); /* route_node_get lock */ bgp_unlock_node(rn); /* Process change. */ bgp_process(bgp, rn, afi, safi); if (SAFI_MPLS_VPN == safi && bgp->inst_type == BGP_INSTANCE_TYPE_DEFAULT) { vpn_leak_to_vrf_update(bgp, new); } #if ENABLE_BGP_VNC rfapiProcessUpdate(new->peer, NULL, p, &bgp_static->prd, new->attr, afi, safi, new->type, new->sub_type, &label); #endif /* Unintern original. */ aspath_unintern(&attr.aspath); } /* Configure static BGP network. When user don't run zebra, static route should be installed as valid. */ static int bgp_static_set(struct vty *vty, const char *negate, const char *ip_str, afi_t afi, safi_t safi, const char *rmap, int backdoor, uint32_t label_index) { VTY_DECLVAR_CONTEXT(bgp, bgp); int ret; struct prefix p; struct bgp_static *bgp_static; struct bgp_node *rn; uint8_t need_update = 0; /* Convert IP prefix string to struct prefix. */ ret = str2prefix(ip_str, &p); if (!ret) { vty_out(vty, "%% Malformed prefix\n"); return CMD_WARNING_CONFIG_FAILED; } if (afi == AFI_IP6 && IN6_IS_ADDR_LINKLOCAL(&p.u.prefix6)) { vty_out(vty, "%% Malformed prefix (link-local address)\n"); return CMD_WARNING_CONFIG_FAILED; } apply_mask(&p); if (negate) { /* Set BGP static route configuration. */ rn = bgp_node_lookup(bgp->route[afi][safi], &p); if (!rn) { vty_out(vty, "%% Can't find static route specified\n"); return CMD_WARNING_CONFIG_FAILED; } bgp_static = bgp_node_get_bgp_static_info(rn); if ((label_index != BGP_INVALID_LABEL_INDEX) && (label_index != bgp_static->label_index)) { vty_out(vty, "%% label-index doesn't match static route\n"); return CMD_WARNING_CONFIG_FAILED; } if ((rmap && bgp_static->rmap.name) && strcmp(rmap, bgp_static->rmap.name)) { vty_out(vty, "%% route-map name doesn't match static route\n"); return CMD_WARNING_CONFIG_FAILED; } /* Update BGP RIB. */ if (!bgp_static->backdoor) bgp_static_withdraw(bgp, &p, afi, safi); /* Clear configuration. */ bgp_static_free(bgp_static); bgp_node_set_bgp_static_info(rn, NULL); bgp_unlock_node(rn); bgp_unlock_node(rn); } else { /* Set BGP static route configuration. */ rn = bgp_node_get(bgp->route[afi][safi], &p); bgp_static = bgp_node_get_bgp_static_info(rn); if (bgp_static) { /* Configuration change. */ /* Label index cannot be changed. */ if (bgp_static->label_index != label_index) { vty_out(vty, "%% cannot change label-index\n"); return CMD_WARNING_CONFIG_FAILED; } /* Check previous routes are installed into BGP. */ if (bgp_static->valid && bgp_static->backdoor != backdoor) need_update = 1; bgp_static->backdoor = backdoor; if (rmap) { XFREE(MTYPE_ROUTE_MAP_NAME, bgp_static->rmap.name); route_map_counter_decrement( bgp_static->rmap.map); bgp_static->rmap.name = XSTRDUP(MTYPE_ROUTE_MAP_NAME, rmap); bgp_static->rmap.map = route_map_lookup_by_name(rmap); route_map_counter_increment( bgp_static->rmap.map); } else { XFREE(MTYPE_ROUTE_MAP_NAME, bgp_static->rmap.name); route_map_counter_decrement( bgp_static->rmap.map); bgp_static->rmap.name = NULL; bgp_static->rmap.map = NULL; bgp_static->valid = 0; } bgp_unlock_node(rn); } else { /* New configuration. */ bgp_static = bgp_static_new(); bgp_static->backdoor = backdoor; bgp_static->valid = 0; bgp_static->igpmetric = 0; bgp_static->igpnexthop.s_addr = 0; bgp_static->label_index = label_index; if (rmap) { XFREE(MTYPE_ROUTE_MAP_NAME, bgp_static->rmap.name); route_map_counter_decrement( bgp_static->rmap.map); bgp_static->rmap.name = XSTRDUP(MTYPE_ROUTE_MAP_NAME, rmap); bgp_static->rmap.map = route_map_lookup_by_name(rmap); route_map_counter_increment( bgp_static->rmap.map); } bgp_node_set_bgp_static_info(rn, bgp_static); } bgp_static->valid = 1; if (need_update) bgp_static_withdraw(bgp, &p, afi, safi); if (!bgp_static->backdoor) bgp_static_update(bgp, &p, bgp_static, afi, safi); } return CMD_SUCCESS; } void bgp_static_add(struct bgp *bgp) { afi_t afi; safi_t safi; struct bgp_node *rn; struct bgp_node *rm; struct bgp_table *table; struct bgp_static *bgp_static; FOREACH_AFI_SAFI (afi, safi) for (rn = bgp_table_top(bgp->route[afi][safi]); rn; rn = bgp_route_next(rn)) { if (!bgp_node_has_bgp_path_info_data(rn)) continue; if ((safi == SAFI_MPLS_VPN) || (safi == SAFI_ENCAP) || (safi == SAFI_EVPN)) { table = bgp_node_get_bgp_table_info(rn); for (rm = bgp_table_top(table); rm; rm = bgp_route_next(rm)) { bgp_static = bgp_node_get_bgp_static_info( rm); bgp_static_update_safi(bgp, &rm->p, bgp_static, afi, safi); } } else { bgp_static_update( bgp, &rn->p, bgp_node_get_bgp_static_info(rn), afi, safi); } } } /* Called from bgp_delete(). Delete all static routes from the BGP instance. */ void bgp_static_delete(struct bgp *bgp) { afi_t afi; safi_t safi; struct bgp_node *rn; struct bgp_node *rm; struct bgp_table *table; struct bgp_static *bgp_static; FOREACH_AFI_SAFI (afi, safi) for (rn = bgp_table_top(bgp->route[afi][safi]); rn; rn = bgp_route_next(rn)) { if (!bgp_node_has_bgp_path_info_data(rn)) continue; if ((safi == SAFI_MPLS_VPN) || (safi == SAFI_ENCAP) || (safi == SAFI_EVPN)) { table = bgp_node_get_bgp_table_info(rn); for (rm = bgp_table_top(table); rm; rm = bgp_route_next(rm)) { bgp_static = bgp_node_get_bgp_static_info( rm); if (!bgp_static) continue; bgp_static_withdraw_safi( bgp, &rm->p, AFI_IP, safi, (struct prefix_rd *)&rn->p); bgp_static_free(bgp_static); bgp_node_set_bgp_static_info(rn, NULL); bgp_unlock_node(rn); } } else { bgp_static = bgp_node_get_bgp_static_info(rn); bgp_static_withdraw(bgp, &rn->p, afi, safi); bgp_static_free(bgp_static); bgp_node_set_bgp_static_info(rn, NULL); bgp_unlock_node(rn); } } } void bgp_static_redo_import_check(struct bgp *bgp) { afi_t afi; safi_t safi; struct bgp_node *rn; struct bgp_node *rm; struct bgp_table *table; struct bgp_static *bgp_static; /* Use this flag to force reprocessing of the route */ bgp_flag_set(bgp, BGP_FLAG_FORCE_STATIC_PROCESS); FOREACH_AFI_SAFI (afi, safi) { for (rn = bgp_table_top(bgp->route[afi][safi]); rn; rn = bgp_route_next(rn)) { if (!bgp_node_has_bgp_path_info_data(rn)) continue; if ((safi == SAFI_MPLS_VPN) || (safi == SAFI_ENCAP) || (safi == SAFI_EVPN)) { table = bgp_node_get_bgp_table_info(rn); for (rm = bgp_table_top(table); rm; rm = bgp_route_next(rm)) { bgp_static = bgp_node_get_bgp_static_info( rm); bgp_static_update_safi(bgp, &rm->p, bgp_static, afi, safi); } } else { bgp_static = bgp_node_get_bgp_static_info(rn); bgp_static_update(bgp, &rn->p, bgp_static, afi, safi); } } } bgp_flag_unset(bgp, BGP_FLAG_FORCE_STATIC_PROCESS); } static void bgp_purge_af_static_redist_routes(struct bgp *bgp, afi_t afi, safi_t safi) { struct bgp_table *table; struct bgp_node *rn; struct bgp_path_info *pi; /* Do not install the aggregate route if BGP is in the * process of termination. */ if (bgp_flag_check(bgp, BGP_FLAG_DELETE_IN_PROGRESS) || (bgp->peer_self == NULL)) return; table = bgp->rib[afi][safi]; for (rn = bgp_table_top(table); rn; rn = bgp_route_next(rn)) { for (pi = bgp_node_get_bgp_path_info(rn); pi; pi = pi->next) { if (pi->peer == bgp->peer_self && ((pi->type == ZEBRA_ROUTE_BGP && pi->sub_type == BGP_ROUTE_STATIC) || (pi->type != ZEBRA_ROUTE_BGP && pi->sub_type == BGP_ROUTE_REDISTRIBUTE))) { bgp_aggregate_decrement(bgp, &rn->p, pi, afi, safi); bgp_unlink_nexthop(pi); bgp_path_info_delete(rn, pi); bgp_process(bgp, rn, afi, safi); } } } } /* * Purge all networks and redistributed routes from routing table. * Invoked upon the instance going down. */ void bgp_purge_static_redist_routes(struct bgp *bgp) { afi_t afi; safi_t safi; FOREACH_AFI_SAFI (afi, safi) bgp_purge_af_static_redist_routes(bgp, afi, safi); } /* * gpz 110624 * Currently this is used to set static routes for VPN and ENCAP. * I think it can probably be factored with bgp_static_set. */ int bgp_static_set_safi(afi_t afi, safi_t safi, struct vty *vty, const char *ip_str, const char *rd_str, const char *label_str, const char *rmap_str, int evpn_type, const char *esi, const char *gwip, const char *ethtag, const char *routermac) { VTY_DECLVAR_CONTEXT(bgp, bgp); int ret; struct prefix p; struct prefix_rd prd; struct bgp_node *prn; struct bgp_node *rn; struct bgp_table *table; struct bgp_static *bgp_static; mpls_label_t label = MPLS_INVALID_LABEL; struct prefix gw_ip; /* validate ip prefix */ ret = str2prefix(ip_str, &p); if (!ret) { vty_out(vty, "%% Malformed prefix\n"); return CMD_WARNING_CONFIG_FAILED; } apply_mask(&p); if ((afi == AFI_L2VPN) && (bgp_build_evpn_prefix(evpn_type, ethtag != NULL ? atol(ethtag) : 0, &p))) { vty_out(vty, "%% L2VPN prefix could not be forged\n"); return CMD_WARNING_CONFIG_FAILED; } ret = str2prefix_rd(rd_str, &prd); if (!ret) { vty_out(vty, "%% Malformed rd\n"); return CMD_WARNING_CONFIG_FAILED; } if (label_str) { unsigned long label_val; label_val = strtoul(label_str, NULL, 10); encode_label(label_val, &label); } if (safi == SAFI_EVPN) { if (esi && str2esi(esi, NULL) == 0) { vty_out(vty, "%% Malformed ESI\n"); return CMD_WARNING_CONFIG_FAILED; } if (routermac && prefix_str2mac(routermac, NULL) == 0) { vty_out(vty, "%% Malformed Router MAC\n"); return CMD_WARNING_CONFIG_FAILED; } if (gwip) { memset(&gw_ip, 0, sizeof(struct prefix)); ret = str2prefix(gwip, &gw_ip); if (!ret) { vty_out(vty, "%% Malformed GatewayIp\n"); return CMD_WARNING_CONFIG_FAILED; } if ((gw_ip.family == AF_INET && is_evpn_prefix_ipaddr_v6( (struct prefix_evpn *)&p)) || (gw_ip.family == AF_INET6 && is_evpn_prefix_ipaddr_v4( (struct prefix_evpn *)&p))) { vty_out(vty, "%% GatewayIp family differs with IP prefix\n"); return CMD_WARNING_CONFIG_FAILED; } } } prn = bgp_node_get(bgp->route[afi][safi], (struct prefix *)&prd); if (!bgp_node_has_bgp_path_info_data(prn)) bgp_node_set_bgp_table_info(prn, bgp_table_init(bgp, afi, safi)); table = bgp_node_get_bgp_table_info(prn); rn = bgp_node_get(table, &p); if (bgp_node_has_bgp_path_info_data(rn)) { vty_out(vty, "%% Same network configuration exists\n"); bgp_unlock_node(rn); } else { /* New configuration. */ bgp_static = bgp_static_new(); bgp_static->backdoor = 0; bgp_static->valid = 0; bgp_static->igpmetric = 0; bgp_static->igpnexthop.s_addr = 0; bgp_static->label = label; bgp_static->prd = prd; if (rmap_str) { XFREE(MTYPE_ROUTE_MAP_NAME, bgp_static->rmap.name); route_map_counter_decrement(bgp_static->rmap.map); bgp_static->rmap.name = XSTRDUP(MTYPE_ROUTE_MAP_NAME, rmap_str); bgp_static->rmap.map = route_map_lookup_by_name(rmap_str); route_map_counter_increment(bgp_static->rmap.map); } if (safi == SAFI_EVPN) { if (esi) { bgp_static->eth_s_id = XCALLOC(MTYPE_ATTR, sizeof(struct eth_segment_id)); str2esi(esi, bgp_static->eth_s_id); } if (routermac) { bgp_static->router_mac = XCALLOC(MTYPE_ATTR, ETH_ALEN + 1); (void)prefix_str2mac(routermac, bgp_static->router_mac); } if (gwip) prefix_copy(&bgp_static->gatewayIp, &gw_ip); } bgp_node_set_bgp_static_info(rn, bgp_static); bgp_static->valid = 1; bgp_static_update_safi(bgp, &p, bgp_static, afi, safi); } return CMD_SUCCESS; } /* Configure static BGP network. */ int bgp_static_unset_safi(afi_t afi, safi_t safi, struct vty *vty, const char *ip_str, const char *rd_str, const char *label_str, int evpn_type, const char *esi, const char *gwip, const char *ethtag) { VTY_DECLVAR_CONTEXT(bgp, bgp); int ret; struct prefix p; struct prefix_rd prd; struct bgp_node *prn; struct bgp_node *rn; struct bgp_table *table; struct bgp_static *bgp_static; mpls_label_t label = MPLS_INVALID_LABEL; /* Convert IP prefix string to struct prefix. */ ret = str2prefix(ip_str, &p); if (!ret) { vty_out(vty, "%% Malformed prefix\n"); return CMD_WARNING_CONFIG_FAILED; } apply_mask(&p); if ((afi == AFI_L2VPN) && (bgp_build_evpn_prefix(evpn_type, ethtag != NULL ? atol(ethtag) : 0, &p))) { vty_out(vty, "%% L2VPN prefix could not be forged\n"); return CMD_WARNING_CONFIG_FAILED; } ret = str2prefix_rd(rd_str, &prd); if (!ret) { vty_out(vty, "%% Malformed rd\n"); return CMD_WARNING_CONFIG_FAILED; } if (label_str) { unsigned long label_val; label_val = strtoul(label_str, NULL, 10); encode_label(label_val, &label); } prn = bgp_node_get(bgp->route[afi][safi], (struct prefix *)&prd); if (!bgp_node_has_bgp_path_info_data(prn)) bgp_node_set_bgp_table_info(prn, bgp_table_init(bgp, afi, safi)); else bgp_unlock_node(prn); table = bgp_node_get_bgp_table_info(prn); rn = bgp_node_lookup(table, &p); if (rn) { bgp_static_withdraw_safi(bgp, &p, afi, safi, &prd); bgp_static = bgp_node_get_bgp_static_info(rn); bgp_static_free(bgp_static); bgp_node_set_bgp_static_info(rn, NULL); bgp_unlock_node(rn); bgp_unlock_node(rn); } else vty_out(vty, "%% Can't find the route\n"); return CMD_SUCCESS; } static int bgp_table_map_set(struct vty *vty, afi_t afi, safi_t safi, const char *rmap_name) { VTY_DECLVAR_CONTEXT(bgp, bgp); struct bgp_rmap *rmap; rmap = &bgp->table_map[afi][safi]; if (rmap_name) { XFREE(MTYPE_ROUTE_MAP_NAME, rmap->name); route_map_counter_decrement(rmap->map); rmap->name = XSTRDUP(MTYPE_ROUTE_MAP_NAME, rmap_name); rmap->map = route_map_lookup_by_name(rmap_name); route_map_counter_increment(rmap->map); } else { XFREE(MTYPE_ROUTE_MAP_NAME, rmap->name); route_map_counter_decrement(rmap->map); rmap->name = NULL; rmap->map = NULL; } if (bgp_fibupd_safi(safi)) bgp_zebra_announce_table(bgp, afi, safi); return CMD_SUCCESS; } static int bgp_table_map_unset(struct vty *vty, afi_t afi, safi_t safi, const char *rmap_name) { VTY_DECLVAR_CONTEXT(bgp, bgp); struct bgp_rmap *rmap; rmap = &bgp->table_map[afi][safi]; XFREE(MTYPE_ROUTE_MAP_NAME, rmap->name); route_map_counter_decrement(rmap->map); rmap->name = NULL; rmap->map = NULL; if (bgp_fibupd_safi(safi)) bgp_zebra_announce_table(bgp, afi, safi); return CMD_SUCCESS; } void bgp_config_write_table_map(struct vty *vty, struct bgp *bgp, afi_t afi, safi_t safi) { if (bgp->table_map[afi][safi].name) { vty_out(vty, " table-map %s\n", bgp->table_map[afi][safi].name); } } DEFUN (bgp_table_map, bgp_table_map_cmd, "table-map WORD", "BGP table to RIB route download filter\n" "Name of the route map\n") { int idx_word = 1; return bgp_table_map_set(vty, bgp_node_afi(vty), bgp_node_safi(vty), argv[idx_word]->arg); } DEFUN (no_bgp_table_map, no_bgp_table_map_cmd, "no table-map WORD", NO_STR "BGP table to RIB route download filter\n" "Name of the route map\n") { int idx_word = 2; return bgp_table_map_unset(vty, bgp_node_afi(vty), bgp_node_safi(vty), argv[idx_word]->arg); } DEFPY(bgp_network, bgp_network_cmd, "[no] network \ \ [{route-map WORD$map_name|label-index (0-1048560)$label_index| \ backdoor$backdoor}]", NO_STR "Specify a network to announce via BGP\n" "IPv4 prefix\n" "Network number\n" "Network mask\n" "Network mask\n" "Route-map to modify the attributes\n" "Name of the route map\n" "Label index to associate with the prefix\n" "Label index value\n" "Specify a BGP backdoor route\n") { char addr_prefix_str[BUFSIZ]; if (address_str) { int ret; ret = netmask_str2prefix_str(address_str, netmask_str, addr_prefix_str); if (!ret) { vty_out(vty, "%% Inconsistent address and mask\n"); return CMD_WARNING_CONFIG_FAILED; } } return bgp_static_set( vty, no, address_str ? addr_prefix_str : prefix_str, AFI_IP, bgp_node_safi(vty), map_name, backdoor ? 1 : 0, label_index ? (uint32_t)label_index : BGP_INVALID_LABEL_INDEX); } DEFPY(ipv6_bgp_network, ipv6_bgp_network_cmd, "[no] network X:X::X:X/M$prefix \ [{route-map WORD$map_name|label-index (0-1048560)$label_index}]", NO_STR "Specify a network to announce via BGP\n" "IPv6 prefix\n" "Route-map to modify the attributes\n" "Name of the route map\n" "Label index to associate with the prefix\n" "Label index value\n") { return bgp_static_set( vty, no, prefix_str, AFI_IP6, bgp_node_safi(vty), map_name, 0, label_index ? (uint32_t)label_index : BGP_INVALID_LABEL_INDEX); } static struct bgp_aggregate *bgp_aggregate_new(void) { return XCALLOC(MTYPE_BGP_AGGREGATE, sizeof(struct bgp_aggregate)); } static void bgp_aggregate_free(struct bgp_aggregate *aggregate) { XFREE(MTYPE_ROUTE_MAP_NAME, aggregate->rmap.name); route_map_counter_decrement(aggregate->rmap.map); XFREE(MTYPE_BGP_AGGREGATE, aggregate); } static int bgp_aggregate_info_same(struct bgp_path_info *pi, uint8_t origin, struct aspath *aspath, struct community *comm, struct ecommunity *ecomm, struct lcommunity *lcomm) { static struct aspath *ae = NULL; if (!ae) ae = aspath_empty(); if (!pi) return 0; if (origin != pi->attr->origin) return 0; if (!aspath_cmp(pi->attr->aspath, (aspath) ? aspath : ae)) return 0; if (!community_cmp(pi->attr->community, comm)) return 0; if (!ecommunity_cmp(pi->attr->ecommunity, ecomm)) return 0; if (!lcommunity_cmp(pi->attr->lcommunity, lcomm)) return 0; if (!CHECK_FLAG(pi->flags, BGP_PATH_VALID)) return 0; return 1; } static void bgp_aggregate_install(struct bgp *bgp, afi_t afi, safi_t safi, struct prefix *p, uint8_t origin, struct aspath *aspath, struct community *community, struct ecommunity *ecommunity, struct lcommunity *lcommunity, uint8_t atomic_aggregate, struct bgp_aggregate *aggregate) { struct bgp_node *rn; struct bgp_table *table; struct bgp_path_info *pi, *orig, *new; struct attr *attr; table = bgp->rib[afi][safi]; rn = bgp_node_get(table, p); for (orig = pi = bgp_node_get_bgp_path_info(rn); pi; pi = pi->next) if (pi->peer == bgp->peer_self && pi->type == ZEBRA_ROUTE_BGP && pi->sub_type == BGP_ROUTE_AGGREGATE) break; if (aggregate->count > 0) { /* * If the aggregate information has not changed * no need to re-install it again. */ if (bgp_aggregate_info_same(orig, origin, aspath, community, ecommunity, lcommunity)) { bgp_unlock_node(rn); if (aspath) aspath_free(aspath); if (community) community_free(&community); if (ecommunity) ecommunity_free(&ecommunity); if (lcommunity) lcommunity_free(&lcommunity); return; } /* * Mark the old as unusable */ if (pi) bgp_path_info_delete(rn, pi); attr = bgp_attr_aggregate_intern( bgp, origin, aspath, community, ecommunity, lcommunity, aggregate, atomic_aggregate, p); if (!attr) { bgp_aggregate_delete(bgp, p, afi, safi, aggregate); return; } new = info_make(ZEBRA_ROUTE_BGP, BGP_ROUTE_AGGREGATE, 0, bgp->peer_self, attr, rn); SET_FLAG(new->flags, BGP_PATH_VALID); bgp_path_info_add(rn, new); bgp_process(bgp, rn, afi, safi); } else { for (pi = orig; pi; pi = pi->next) if (pi->peer == bgp->peer_self && pi->type == ZEBRA_ROUTE_BGP && pi->sub_type == BGP_ROUTE_AGGREGATE) break; /* Withdraw static BGP route from routing table. */ if (pi) { bgp_path_info_delete(rn, pi); bgp_process(bgp, rn, afi, safi); } } bgp_unlock_node(rn); } /* Update an aggregate as routes are added/removed from the BGP table */ void bgp_aggregate_route(struct bgp *bgp, struct prefix *p, afi_t afi, safi_t safi, struct bgp_aggregate *aggregate) { struct bgp_table *table; struct bgp_node *top; struct bgp_node *rn; uint8_t origin; struct aspath *aspath = NULL; struct community *community = NULL; struct ecommunity *ecommunity = NULL; struct lcommunity *lcommunity = NULL; struct bgp_path_info *pi; unsigned long match = 0; uint8_t atomic_aggregate = 0; /* If the bgp instance is being deleted or self peer is deleted * then do not create aggregate route */ if (bgp_flag_check(bgp, BGP_FLAG_DELETE_IN_PROGRESS) || (bgp->peer_self == NULL)) return; /* ORIGIN attribute: If at least one route among routes that are aggregated has ORIGIN with the value INCOMPLETE, then the aggregated route must have the ORIGIN attribute with the value INCOMPLETE. Otherwise, if at least one route among routes that are aggregated has ORIGIN with the value EGP, then the aggregated route must have the origin attribute with the value EGP. In all other case the value of the ORIGIN attribute of the aggregated route is INTERNAL. */ origin = BGP_ORIGIN_IGP; table = bgp->rib[afi][safi]; top = bgp_node_get(table, p); for (rn = bgp_node_get(table, p); rn; rn = bgp_route_next_until(rn, top)) { if (rn->p.prefixlen <= p->prefixlen) continue; match = 0; for (pi = bgp_node_get_bgp_path_info(rn); pi; pi = pi->next) { if (BGP_PATH_HOLDDOWN(pi)) continue; if (pi->attr->flag & ATTR_FLAG_BIT(BGP_ATTR_ATOMIC_AGGREGATE)) atomic_aggregate = 1; if (pi->sub_type == BGP_ROUTE_AGGREGATE) continue; /* * summary-only aggregate route suppress * aggregated route announcements. */ if (aggregate->summary_only) { (bgp_path_info_extra_get(pi))->suppress++; bgp_path_info_set_flag(rn, pi, BGP_PATH_ATTR_CHANGED); match++; } aggregate->count++; /* * If at least one route among routes that are * aggregated has ORIGIN with the value INCOMPLETE, * then the aggregated route MUST have the ORIGIN * attribute with the value INCOMPLETE. Otherwise, if * at least one route among routes that are aggregated * has ORIGIN with the value EGP, then the aggregated * route MUST have the ORIGIN attribute with the value * EGP. */ switch (pi->attr->origin) { case BGP_ORIGIN_INCOMPLETE: aggregate->incomplete_origin_count++; break; case BGP_ORIGIN_EGP: aggregate->egp_origin_count++; break; default: /*Do nothing. */ break; } if (!aggregate->as_set) continue; /* * as-set aggregate route generate origin, as path, * and community aggregation. */ /* Compute aggregate route's as-path. */ bgp_compute_aggregate_aspath(aggregate, pi->attr->aspath); /* Compute aggregate route's community. */ if (pi->attr->community) bgp_compute_aggregate_community( aggregate, pi->attr->community); /* Compute aggregate route's extended community. */ if (pi->attr->ecommunity) bgp_compute_aggregate_ecommunity( aggregate, pi->attr->ecommunity); /* Compute aggregate route's large community. */ if (pi->attr->lcommunity) bgp_compute_aggregate_lcommunity( aggregate, pi->attr->lcommunity); } if (match) bgp_process(bgp, rn, afi, safi); } bgp_unlock_node(top); if (aggregate->incomplete_origin_count > 0) origin = BGP_ORIGIN_INCOMPLETE; else if (aggregate->egp_origin_count > 0) origin = BGP_ORIGIN_EGP; if (aggregate->as_set) { if (aggregate->aspath) /* Retrieve aggregate route's as-path. */ aspath = aspath_dup(aggregate->aspath); if (aggregate->community) /* Retrieve aggregate route's community. */ community = community_dup(aggregate->community); if (aggregate->ecommunity) /* Retrieve aggregate route's ecommunity. */ ecommunity = ecommunity_dup(aggregate->ecommunity); if (aggregate->lcommunity) /* Retrieve aggregate route's lcommunity. */ lcommunity = lcommunity_dup(aggregate->lcommunity); } bgp_aggregate_install(bgp, afi, safi, p, origin, aspath, community, ecommunity, lcommunity, atomic_aggregate, aggregate); } void bgp_aggregate_delete(struct bgp *bgp, struct prefix *p, afi_t afi, safi_t safi, struct bgp_aggregate *aggregate) { struct bgp_table *table; struct bgp_node *top; struct bgp_node *rn; struct bgp_path_info *pi; unsigned long match; table = bgp->rib[afi][safi]; /* If routes exists below this node, generate aggregate routes. */ top = bgp_node_get(table, p); for (rn = bgp_node_get(table, p); rn; rn = bgp_route_next_until(rn, top)) { if (rn->p.prefixlen <= p->prefixlen) continue; match = 0; for (pi = bgp_node_get_bgp_path_info(rn); pi; pi = pi->next) { if (BGP_PATH_HOLDDOWN(pi)) continue; if (pi->sub_type == BGP_ROUTE_AGGREGATE) continue; if (aggregate->summary_only && pi->extra) { pi->extra->suppress--; if (pi->extra->suppress == 0) { bgp_path_info_set_flag( rn, pi, BGP_PATH_ATTR_CHANGED); match++; } } aggregate->count--; if (pi->attr->origin == BGP_ORIGIN_INCOMPLETE) aggregate->incomplete_origin_count--; else if (pi->attr->origin == BGP_ORIGIN_EGP) aggregate->egp_origin_count--; if (aggregate->as_set) { /* Remove as-path from aggregate. */ bgp_remove_aspath_from_aggregate( aggregate, pi->attr->aspath); if (pi->attr->community) /* Remove community from aggregate. */ bgp_remove_community_from_aggregate( aggregate, pi->attr->community); if (pi->attr->ecommunity) /* Remove ecommunity from aggregate. */ bgp_remove_ecommunity_from_aggregate( aggregate, pi->attr->ecommunity); if (pi->attr->lcommunity) /* Remove lcommunity from aggregate. */ bgp_remove_lcommunity_from_aggregate( aggregate, pi->attr->lcommunity); } } /* If this node was suppressed, process the change. */ if (match) bgp_process(bgp, rn, afi, safi); } bgp_unlock_node(top); } static void bgp_add_route_to_aggregate(struct bgp *bgp, struct prefix *aggr_p, struct bgp_path_info *pinew, afi_t afi, safi_t safi, struct bgp_aggregate *aggregate) { uint8_t origin; struct aspath *aspath = NULL; uint8_t atomic_aggregate = 0; struct community *community = NULL; struct ecommunity *ecommunity = NULL; struct lcommunity *lcommunity = NULL; /* ORIGIN attribute: If at least one route among routes that are * aggregated has ORIGIN with the value INCOMPLETE, then the * aggregated route must have the ORIGIN attribute with the value * INCOMPLETE. Otherwise, if at least one route among routes that * are aggregated has ORIGIN with the value EGP, then the aggregated * route must have the origin attribute with the value EGP. In all * other case the value of the ORIGIN attribute of the aggregated * route is INTERNAL. */ origin = BGP_ORIGIN_IGP; aggregate->count++; if (aggregate->summary_only) (bgp_path_info_extra_get(pinew))->suppress++; switch (pinew->attr->origin) { case BGP_ORIGIN_INCOMPLETE: aggregate->incomplete_origin_count++; break; case BGP_ORIGIN_EGP: aggregate->egp_origin_count++; break; default: /* Do nothing. */ break; } if (aggregate->incomplete_origin_count > 0) origin = BGP_ORIGIN_INCOMPLETE; else if (aggregate->egp_origin_count > 0) origin = BGP_ORIGIN_EGP; if (aggregate->as_set) { /* Compute aggregate route's as-path. */ bgp_compute_aggregate_aspath(aggregate, pinew->attr->aspath); /* Compute aggregate route's community. */ if (pinew->attr->community) bgp_compute_aggregate_community( aggregate, pinew->attr->community); /* Compute aggregate route's extended community. */ if (pinew->attr->ecommunity) bgp_compute_aggregate_ecommunity( aggregate, pinew->attr->ecommunity); /* Compute aggregate route's large community. */ if (pinew->attr->lcommunity) bgp_compute_aggregate_lcommunity( aggregate, pinew->attr->lcommunity); /* Retrieve aggregate route's as-path. */ if (aggregate->aspath) aspath = aspath_dup(aggregate->aspath); /* Retrieve aggregate route's community. */ if (aggregate->community) community = community_dup(aggregate->community); /* Retrieve aggregate route's ecommunity. */ if (aggregate->ecommunity) ecommunity = ecommunity_dup(aggregate->ecommunity); /* Retrieve aggregate route's lcommunity. */ if (aggregate->lcommunity) lcommunity = lcommunity_dup(aggregate->lcommunity); } bgp_aggregate_install(bgp, afi, safi, aggr_p, origin, aspath, community, ecommunity, lcommunity, atomic_aggregate, aggregate); } static void bgp_remove_route_from_aggregate(struct bgp *bgp, afi_t afi, safi_t safi, struct bgp_path_info *pi, struct bgp_aggregate *aggregate, struct prefix *aggr_p) { uint8_t origin; struct aspath *aspath = NULL; uint8_t atomic_aggregate = 0; struct community *community = NULL; struct ecommunity *ecommunity = NULL; struct lcommunity *lcommunity = NULL; unsigned long match = 0; if (BGP_PATH_HOLDDOWN(pi)) return; if (pi->sub_type == BGP_ROUTE_AGGREGATE) return; if (aggregate->summary_only && pi->extra && pi->extra->suppress > 0) { pi->extra->suppress--; if (pi->extra->suppress == 0) { bgp_path_info_set_flag(pi->net, pi, BGP_PATH_ATTR_CHANGED); match++; } } if (aggregate->count > 0) aggregate->count--; if (pi->attr->origin == BGP_ORIGIN_INCOMPLETE) aggregate->incomplete_origin_count--; else if (pi->attr->origin == BGP_ORIGIN_EGP) aggregate->egp_origin_count--; if (aggregate->as_set) { /* Remove as-path from aggregate. */ bgp_remove_aspath_from_aggregate(aggregate, pi->attr->aspath); if (pi->attr->community) /* Remove community from aggregate. */ bgp_remove_community_from_aggregate( aggregate, pi->attr->community); if (pi->attr->ecommunity) /* Remove ecommunity from aggregate. */ bgp_remove_ecommunity_from_aggregate( aggregate, pi->attr->ecommunity); if (pi->attr->lcommunity) /* Remove lcommunity from aggregate. */ bgp_remove_lcommunity_from_aggregate( aggregate, pi->attr->lcommunity); } /* If this node was suppressed, process the change. */ if (match) bgp_process(bgp, pi->net, afi, safi); origin = BGP_ORIGIN_IGP; if (aggregate->incomplete_origin_count > 0) origin = BGP_ORIGIN_INCOMPLETE; else if (aggregate->egp_origin_count > 0) origin = BGP_ORIGIN_EGP; if (aggregate->as_set) { /* Retrieve aggregate route's as-path. */ if (aggregate->aspath) aspath = aspath_dup(aggregate->aspath); /* Retrieve aggregate route's community. */ if (aggregate->community) community = community_dup(aggregate->community); /* Retrieve aggregate route's ecommunity. */ if (aggregate->ecommunity) ecommunity = ecommunity_dup(aggregate->ecommunity); /* Retrieve aggregate route's lcommunity. */ if (aggregate->lcommunity) lcommunity = lcommunity_dup(aggregate->lcommunity); } bgp_aggregate_install(bgp, afi, safi, aggr_p, origin, aspath, community, ecommunity, lcommunity, atomic_aggregate, aggregate); } void bgp_aggregate_increment(struct bgp *bgp, struct prefix *p, struct bgp_path_info *pi, afi_t afi, safi_t safi) { struct bgp_node *child; struct bgp_node *rn; struct bgp_aggregate *aggregate; struct bgp_table *table; table = bgp->aggregate[afi][safi]; /* No aggregates configured. */ if (bgp_table_top_nolock(table) == NULL) return; if (p->prefixlen == 0) return; if (BGP_PATH_HOLDDOWN(pi)) return; child = bgp_node_get(table, p); /* Aggregate address configuration check. */ for (rn = child; rn; rn = bgp_node_parent_nolock(rn)) { aggregate = bgp_node_get_bgp_aggregate_info(rn); if (aggregate != NULL && rn->p.prefixlen < p->prefixlen) { bgp_add_route_to_aggregate(bgp, &rn->p, pi, afi, safi, aggregate); } } bgp_unlock_node(child); } void bgp_aggregate_decrement(struct bgp *bgp, struct prefix *p, struct bgp_path_info *del, afi_t afi, safi_t safi) { struct bgp_node *child; struct bgp_node *rn; struct bgp_aggregate *aggregate; struct bgp_table *table; table = bgp->aggregate[afi][safi]; /* No aggregates configured. */ if (bgp_table_top_nolock(table) == NULL) return; if (p->prefixlen == 0) return; child = bgp_node_get(table, p); /* Aggregate address configuration check. */ for (rn = child; rn; rn = bgp_node_parent_nolock(rn)) { aggregate = bgp_node_get_bgp_aggregate_info(rn); if (aggregate != NULL && rn->p.prefixlen < p->prefixlen) { bgp_remove_route_from_aggregate(bgp, afi, safi, del, aggregate, &rn->p); } } bgp_unlock_node(child); } /* Aggregate route attribute. */ #define AGGREGATE_SUMMARY_ONLY 1 #define AGGREGATE_AS_SET 1 static int bgp_aggregate_unset(struct vty *vty, const char *prefix_str, afi_t afi, safi_t safi) { VTY_DECLVAR_CONTEXT(bgp, bgp); int ret; struct prefix p; struct bgp_node *rn; struct bgp_aggregate *aggregate; /* Convert string to prefix structure. */ ret = str2prefix(prefix_str, &p); if (!ret) { vty_out(vty, "Malformed prefix\n"); return CMD_WARNING_CONFIG_FAILED; } apply_mask(&p); /* Old configuration check. */ rn = bgp_node_lookup(bgp->aggregate[afi][safi], &p); if (!rn) { vty_out(vty, "%% There is no aggregate-address configuration.\n"); return CMD_WARNING_CONFIG_FAILED; } aggregate = bgp_node_get_bgp_aggregate_info(rn); bgp_aggregate_delete(bgp, &p, afi, safi, aggregate); bgp_aggregate_install(bgp, afi, safi, &p, 0, NULL, NULL, NULL, NULL, 0, aggregate); /* Unlock aggregate address configuration. */ bgp_node_set_bgp_aggregate_info(rn, NULL); if (aggregate->community) community_free(&aggregate->community); if (aggregate->community_hash) { /* Delete all communities in the hash. */ hash_clean(aggregate->community_hash, bgp_aggr_community_remove); /* Free up the community_hash. */ hash_free(aggregate->community_hash); } if (aggregate->ecommunity) ecommunity_free(&aggregate->ecommunity); if (aggregate->ecommunity_hash) { /* Delete all ecommunities in the hash. */ hash_clean(aggregate->ecommunity_hash, bgp_aggr_ecommunity_remove); /* Free up the ecommunity_hash. */ hash_free(aggregate->ecommunity_hash); } if (aggregate->lcommunity) lcommunity_free(&aggregate->lcommunity); if (aggregate->lcommunity_hash) { /* Delete all lcommunities in the hash. */ hash_clean(aggregate->lcommunity_hash, bgp_aggr_lcommunity_remove); /* Free up the lcommunity_hash. */ hash_free(aggregate->lcommunity_hash); } if (aggregate->aspath) aspath_free(aggregate->aspath); if (aggregate->aspath_hash) { /* Delete all as-paths in the hash. */ hash_clean(aggregate->aspath_hash, bgp_aggr_aspath_remove); /* Free up the aspath_hash. */ hash_free(aggregate->aspath_hash); } bgp_aggregate_free(aggregate); bgp_unlock_node(rn); bgp_unlock_node(rn); return CMD_SUCCESS; } static int bgp_aggregate_set(struct vty *vty, const char *prefix_str, afi_t afi, safi_t safi, const char *rmap, uint8_t summary_only, uint8_t as_set) { VTY_DECLVAR_CONTEXT(bgp, bgp); int ret; struct prefix p; struct bgp_node *rn; struct bgp_aggregate *aggregate; /* Convert string to prefix structure. */ ret = str2prefix(prefix_str, &p); if (!ret) { vty_out(vty, "Malformed prefix\n"); return CMD_WARNING_CONFIG_FAILED; } apply_mask(&p); if ((afi == AFI_IP && p.prefixlen == IPV4_MAX_BITLEN) || (afi == AFI_IP6 && p.prefixlen == IPV6_MAX_BITLEN)) { vty_out(vty, "Specified prefix: %s will not result in any useful aggregation, disallowing\n", prefix_str); return CMD_WARNING_CONFIG_FAILED; } /* Old configuration check. */ rn = bgp_node_get(bgp->aggregate[afi][safi], &p); aggregate = bgp_node_get_bgp_aggregate_info(rn); if (aggregate) { vty_out(vty, "There is already same aggregate network.\n"); /* try to remove the old entry */ ret = bgp_aggregate_unset(vty, prefix_str, afi, safi); if (ret) { vty_out(vty, "Error deleting aggregate.\n"); bgp_unlock_node(rn); return CMD_WARNING_CONFIG_FAILED; } } /* Make aggregate address structure. */ aggregate = bgp_aggregate_new(); aggregate->summary_only = summary_only; aggregate->as_set = as_set; aggregate->safi = safi; if (rmap) { XFREE(MTYPE_ROUTE_MAP_NAME, aggregate->rmap.name); route_map_counter_decrement(aggregate->rmap.map); aggregate->rmap.name = XSTRDUP(MTYPE_ROUTE_MAP_NAME, rmap); aggregate->rmap.map = route_map_lookup_by_name(rmap); route_map_counter_increment(aggregate->rmap.map); } bgp_node_set_bgp_aggregate_info(rn, aggregate); /* Aggregate address insert into BGP routing table. */ bgp_aggregate_route(bgp, &p, afi, safi, aggregate); return CMD_SUCCESS; } DEFUN (aggregate_address, aggregate_address_cmd, "aggregate-address A.B.C.D/M [] [route-map WORD]", "Configure BGP aggregate entries\n" "Aggregate prefix\n" "Generate AS set path information\n" "Filter more specific routes from updates\n" "Filter more specific routes from updates\n" "Generate AS set path information\n" "Apply route map to aggregate network\n" "Name of route map\n") { int idx = 0; argv_find(argv, argc, "A.B.C.D/M", &idx); char *prefix = argv[idx]->arg; char *rmap = NULL; int as_set = argv_find(argv, argc, "as-set", &idx) ? AGGREGATE_AS_SET : 0; idx = 0; int summary_only = argv_find(argv, argc, "summary-only", &idx) ? AGGREGATE_SUMMARY_ONLY : 0; idx = 0; argv_find(argv, argc, "WORD", &idx); if (idx) rmap = argv[idx]->arg; return bgp_aggregate_set(vty, prefix, AFI_IP, bgp_node_safi(vty), rmap, summary_only, as_set); } DEFUN (aggregate_address_mask, aggregate_address_mask_cmd, "aggregate-address A.B.C.D A.B.C.D [] [route-map WORD]", "Configure BGP aggregate entries\n" "Aggregate address\n" "Aggregate mask\n" "Generate AS set path information\n" "Filter more specific routes from updates\n" "Filter more specific routes from updates\n" "Generate AS set path information\n" "Apply route map to aggregate network\n" "Name of route map\n") { int idx = 0; argv_find(argv, argc, "A.B.C.D", &idx); char *prefix = argv[idx]->arg; char *mask = argv[idx + 1]->arg; bool rmap_found; char *rmap = NULL; int as_set = argv_find(argv, argc, "as-set", &idx) ? AGGREGATE_AS_SET : 0; idx = 0; int summary_only = argv_find(argv, argc, "summary-only", &idx) ? AGGREGATE_SUMMARY_ONLY : 0; rmap_found = argv_find(argv, argc, "WORD", &idx); if (rmap_found) rmap = argv[idx]->arg; char prefix_str[BUFSIZ]; int ret = netmask_str2prefix_str(prefix, mask, prefix_str); if (!ret) { vty_out(vty, "%% Inconsistent address and mask\n"); return CMD_WARNING_CONFIG_FAILED; } return bgp_aggregate_set(vty, prefix_str, AFI_IP, bgp_node_safi(vty), rmap, summary_only, as_set); } DEFUN (no_aggregate_address, no_aggregate_address_cmd, "no aggregate-address A.B.C.D/M [] [route-map WORD]", NO_STR "Configure BGP aggregate entries\n" "Aggregate prefix\n" "Generate AS set path information\n" "Filter more specific routes from updates\n" "Filter more specific routes from updates\n" "Generate AS set path information\n" "Apply route map to aggregate network\n" "Name of route map\n") { int idx = 0; argv_find(argv, argc, "A.B.C.D/M", &idx); char *prefix = argv[idx]->arg; return bgp_aggregate_unset(vty, prefix, AFI_IP, bgp_node_safi(vty)); } DEFUN (no_aggregate_address_mask, no_aggregate_address_mask_cmd, "no aggregate-address A.B.C.D A.B.C.D [] [route-map WORD]", NO_STR "Configure BGP aggregate entries\n" "Aggregate address\n" "Aggregate mask\n" "Generate AS set path information\n" "Filter more specific routes from updates\n" "Filter more specific routes from updates\n" "Generate AS set path information\n" "Apply route map to aggregate network\n" "Name of route map\n") { int idx = 0; argv_find(argv, argc, "A.B.C.D", &idx); char *prefix = argv[idx]->arg; char *mask = argv[idx + 1]->arg; char prefix_str[BUFSIZ]; int ret = netmask_str2prefix_str(prefix, mask, prefix_str); if (!ret) { vty_out(vty, "%% Inconsistent address and mask\n"); return CMD_WARNING_CONFIG_FAILED; } return bgp_aggregate_unset(vty, prefix_str, AFI_IP, bgp_node_safi(vty)); } DEFUN (ipv6_aggregate_address, ipv6_aggregate_address_cmd, "aggregate-address X:X::X:X/M [] [route-map WORD]", "Configure BGP aggregate entries\n" "Aggregate prefix\n" "Generate AS set path information\n" "Filter more specific routes from updates\n" "Filter more specific routes from updates\n" "Generate AS set path information\n" "Apply route map to aggregate network\n" "Name of route map\n") { int idx = 0; argv_find(argv, argc, "X:X::X:X/M", &idx); char *prefix = argv[idx]->arg; char *rmap = NULL; bool rmap_found; int as_set = argv_find(argv, argc, "as-set", &idx) ? AGGREGATE_AS_SET : 0; idx = 0; int sum_only = argv_find(argv, argc, "summary-only", &idx) ? AGGREGATE_SUMMARY_ONLY : 0; rmap_found = argv_find(argv, argc, "WORD", &idx); if (rmap_found) rmap = argv[idx]->arg; return bgp_aggregate_set(vty, prefix, AFI_IP6, SAFI_UNICAST, rmap, sum_only, as_set); } DEFUN (no_ipv6_aggregate_address, no_ipv6_aggregate_address_cmd, "no aggregate-address X:X::X:X/M [] [route-map WORD]", NO_STR "Configure BGP aggregate entries\n" "Aggregate prefix\n" "Generate AS set path information\n" "Filter more specific routes from updates\n" "Filter more specific routes from updates\n" "Generate AS set path information\n" "Apply route map to aggregate network\n" "Name of route map\n") { int idx = 0; argv_find(argv, argc, "X:X::X:X/M", &idx); char *prefix = argv[idx]->arg; return bgp_aggregate_unset(vty, prefix, AFI_IP6, SAFI_UNICAST); } /* Redistribute route treatment. */ void bgp_redistribute_add(struct bgp *bgp, struct prefix *p, const union g_addr *nexthop, ifindex_t ifindex, enum nexthop_types_t nhtype, uint32_t metric, uint8_t type, unsigned short instance, route_tag_t tag) { struct bgp_path_info *new; struct bgp_path_info *bpi; struct bgp_path_info rmap_path; struct bgp_node *bn; struct attr attr; struct attr *new_attr; afi_t afi; route_map_result_t ret; struct bgp_redist *red; /* Make default attribute. */ bgp_attr_default_set(&attr, BGP_ORIGIN_INCOMPLETE); switch (nhtype) { case NEXTHOP_TYPE_IFINDEX: break; case NEXTHOP_TYPE_IPV4: case NEXTHOP_TYPE_IPV4_IFINDEX: attr.nexthop = nexthop->ipv4; break; case NEXTHOP_TYPE_IPV6: case NEXTHOP_TYPE_IPV6_IFINDEX: attr.mp_nexthop_global = nexthop->ipv6; attr.mp_nexthop_len = BGP_ATTR_NHLEN_IPV6_GLOBAL; break; case NEXTHOP_TYPE_BLACKHOLE: switch (p->family) { case AF_INET: attr.nexthop.s_addr = INADDR_ANY; break; case AF_INET6: memset(&attr.mp_nexthop_global, 0, sizeof(attr.mp_nexthop_global)); attr.mp_nexthop_len = BGP_ATTR_NHLEN_IPV6_GLOBAL; break; } break; } attr.nh_ifindex = ifindex; attr.med = metric; attr.flag |= ATTR_FLAG_BIT(BGP_ATTR_MULTI_EXIT_DISC); attr.tag = tag; afi = family2afi(p->family); red = bgp_redist_lookup(bgp, afi, type, instance); if (red) { struct attr attr_new; /* Copy attribute for modification. */ attr_new = attr; if (red->redist_metric_flag) attr_new.med = red->redist_metric; /* Apply route-map. */ if (red->rmap.name) { memset(&rmap_path, 0, sizeof(struct bgp_path_info)); rmap_path.peer = bgp->peer_self; rmap_path.attr = &attr_new; SET_FLAG(bgp->peer_self->rmap_type, PEER_RMAP_TYPE_REDISTRIBUTE); ret = route_map_apply(red->rmap.map, p, RMAP_BGP, &rmap_path); bgp->peer_self->rmap_type = 0; if (ret == RMAP_DENYMATCH) { /* Free uninterned attribute. */ bgp_attr_flush(&attr_new); /* Unintern original. */ aspath_unintern(&attr.aspath); bgp_redistribute_delete(bgp, p, type, instance); return; } } if (bgp_flag_check(bgp, BGP_FLAG_GRACEFUL_SHUTDOWN)) bgp_attr_add_gshut_community(&attr_new); bn = bgp_afi_node_get(bgp->rib[afi][SAFI_UNICAST], afi, SAFI_UNICAST, p, NULL); new_attr = bgp_attr_intern(&attr_new); for (bpi = bgp_node_get_bgp_path_info(bn); bpi; bpi = bpi->next) if (bpi->peer == bgp->peer_self && bpi->sub_type == BGP_ROUTE_REDISTRIBUTE) break; if (bpi) { /* Ensure the (source route) type is updated. */ bpi->type = type; if (attrhash_cmp(bpi->attr, new_attr) && !CHECK_FLAG(bpi->flags, BGP_PATH_REMOVED)) { bgp_attr_unintern(&new_attr); aspath_unintern(&attr.aspath); bgp_unlock_node(bn); return; } else { /* The attribute is changed. */ bgp_path_info_set_flag(bn, bpi, BGP_PATH_ATTR_CHANGED); /* Rewrite BGP route information. */ if (CHECK_FLAG(bpi->flags, BGP_PATH_REMOVED)) bgp_path_info_restore(bn, bpi); else bgp_aggregate_decrement( bgp, p, bpi, afi, SAFI_UNICAST); bgp_attr_unintern(&bpi->attr); bpi->attr = new_attr; bpi->uptime = bgp_clock(); /* Process change. */ bgp_aggregate_increment(bgp, p, bpi, afi, SAFI_UNICAST); bgp_process(bgp, bn, afi, SAFI_UNICAST); bgp_unlock_node(bn); aspath_unintern(&attr.aspath); if ((bgp->inst_type == BGP_INSTANCE_TYPE_VRF) || (bgp->inst_type == BGP_INSTANCE_TYPE_DEFAULT)) { vpn_leak_from_vrf_update( bgp_get_default(), bgp, bpi); } return; } } new = info_make(type, BGP_ROUTE_REDISTRIBUTE, instance, bgp->peer_self, new_attr, bn); SET_FLAG(new->flags, BGP_PATH_VALID); bgp_aggregate_increment(bgp, p, new, afi, SAFI_UNICAST); bgp_path_info_add(bn, new); bgp_unlock_node(bn); bgp_process(bgp, bn, afi, SAFI_UNICAST); if ((bgp->inst_type == BGP_INSTANCE_TYPE_VRF) || (bgp->inst_type == BGP_INSTANCE_TYPE_DEFAULT)) { vpn_leak_from_vrf_update(bgp_get_default(), bgp, new); } } /* Unintern original. */ aspath_unintern(&attr.aspath); } void bgp_redistribute_delete(struct bgp *bgp, struct prefix *p, uint8_t type, unsigned short instance) { afi_t afi; struct bgp_node *rn; struct bgp_path_info *pi; struct bgp_redist *red; afi = family2afi(p->family); red = bgp_redist_lookup(bgp, afi, type, instance); if (red) { rn = bgp_afi_node_get(bgp->rib[afi][SAFI_UNICAST], afi, SAFI_UNICAST, p, NULL); for (pi = bgp_node_get_bgp_path_info(rn); pi; pi = pi->next) if (pi->peer == bgp->peer_self && pi->type == type) break; if (pi) { if ((bgp->inst_type == BGP_INSTANCE_TYPE_VRF) || (bgp->inst_type == BGP_INSTANCE_TYPE_DEFAULT)) { vpn_leak_from_vrf_withdraw(bgp_get_default(), bgp, pi); } bgp_aggregate_decrement(bgp, p, pi, afi, SAFI_UNICAST); bgp_path_info_delete(rn, pi); bgp_process(bgp, rn, afi, SAFI_UNICAST); } bgp_unlock_node(rn); } } /* Withdraw specified route type's route. */ void bgp_redistribute_withdraw(struct bgp *bgp, afi_t afi, int type, unsigned short instance) { struct bgp_node *rn; struct bgp_path_info *pi; struct bgp_table *table; table = bgp->rib[afi][SAFI_UNICAST]; for (rn = bgp_table_top(table); rn; rn = bgp_route_next(rn)) { for (pi = bgp_node_get_bgp_path_info(rn); pi; pi = pi->next) if (pi->peer == bgp->peer_self && pi->type == type && pi->instance == instance) break; if (pi) { if ((bgp->inst_type == BGP_INSTANCE_TYPE_VRF) || (bgp->inst_type == BGP_INSTANCE_TYPE_DEFAULT)) { vpn_leak_from_vrf_withdraw(bgp_get_default(), bgp, pi); } bgp_aggregate_decrement(bgp, &rn->p, pi, afi, SAFI_UNICAST); bgp_path_info_delete(rn, pi); bgp_process(bgp, rn, afi, SAFI_UNICAST); } } } /* Static function to display route. */ static void route_vty_out_route(struct prefix *p, struct vty *vty, json_object *json) { int len = 0; char buf[BUFSIZ]; char buf2[BUFSIZ]; if (p->family == AF_INET) { if (!json) { len = vty_out( vty, "%s/%d", inet_ntop(p->family, &p->u.prefix, buf, BUFSIZ), p->prefixlen); } else { json_object_string_add(json, "prefix", inet_ntop(p->family, &p->u.prefix, buf, BUFSIZ)); json_object_int_add(json, "prefixLen", p->prefixlen); prefix2str(p, buf2, PREFIX_STRLEN); json_object_string_add(json, "network", buf2); } } else if (p->family == AF_ETHERNET) { prefix2str(p, buf, PREFIX_STRLEN); len = vty_out(vty, "%s", buf); } else if (p->family == AF_EVPN) { if (!json) len = vty_out( vty, "%s", bgp_evpn_route2str((struct prefix_evpn *)p, buf, BUFSIZ)); else bgp_evpn_route2json((struct prefix_evpn *)p, json); } else if (p->family == AF_FLOWSPEC) { route_vty_out_flowspec(vty, p, NULL, json ? NLRI_STRING_FORMAT_JSON_SIMPLE : NLRI_STRING_FORMAT_MIN, json); } else { if (!json) len = vty_out( vty, "%s/%d", inet_ntop(p->family, &p->u.prefix, buf, BUFSIZ), p->prefixlen); else { json_object_string_add(json, "prefix", inet_ntop(p->family, &p->u.prefix, buf, BUFSIZ)); json_object_int_add(json, "prefixLen", p->prefixlen); prefix2str(p, buf2, PREFIX_STRLEN); json_object_string_add(json, "network", buf2); } } if (!json) { len = 17 - len; if (len < 1) vty_out(vty, "\n%*s", 20, " "); else vty_out(vty, "%*s", len, " "); } } enum bgp_display_type { normal_list, }; /* Print the short form route status for a bgp_path_info */ static void route_vty_short_status_out(struct vty *vty, struct bgp_path_info *path, json_object *json_path) { if (json_path) { /* Route status display. */ if (CHECK_FLAG(path->flags, BGP_PATH_REMOVED)) json_object_boolean_true_add(json_path, "removed"); if (CHECK_FLAG(path->flags, BGP_PATH_STALE)) json_object_boolean_true_add(json_path, "stale"); if (path->extra && path->extra->suppress) json_object_boolean_true_add(json_path, "suppressed"); if (CHECK_FLAG(path->flags, BGP_PATH_VALID) && !CHECK_FLAG(path->flags, BGP_PATH_HISTORY)) json_object_boolean_true_add(json_path, "valid"); /* Selected */ if (CHECK_FLAG(path->flags, BGP_PATH_HISTORY)) json_object_boolean_true_add(json_path, "history"); if (CHECK_FLAG(path->flags, BGP_PATH_DAMPED)) json_object_boolean_true_add(json_path, "damped"); if (CHECK_FLAG(path->flags, BGP_PATH_SELECTED)) json_object_boolean_true_add(json_path, "bestpath"); if (CHECK_FLAG(path->flags, BGP_PATH_MULTIPATH)) json_object_boolean_true_add(json_path, "multipath"); /* Internal route. */ if ((path->peer->as) && (path->peer->as == path->peer->local_as)) json_object_string_add(json_path, "pathFrom", "internal"); else json_object_string_add(json_path, "pathFrom", "external"); return; } /* Route status display. */ if (CHECK_FLAG(path->flags, BGP_PATH_REMOVED)) vty_out(vty, "R"); else if (CHECK_FLAG(path->flags, BGP_PATH_STALE)) vty_out(vty, "S"); else if (path->extra && path->extra->suppress) vty_out(vty, "s"); else if (CHECK_FLAG(path->flags, BGP_PATH_VALID) && !CHECK_FLAG(path->flags, BGP_PATH_HISTORY)) vty_out(vty, "*"); else vty_out(vty, " "); /* Selected */ if (CHECK_FLAG(path->flags, BGP_PATH_HISTORY)) vty_out(vty, "h"); else if (CHECK_FLAG(path->flags, BGP_PATH_DAMPED)) vty_out(vty, "d"); else if (CHECK_FLAG(path->flags, BGP_PATH_SELECTED)) vty_out(vty, ">"); else if (CHECK_FLAG(path->flags, BGP_PATH_MULTIPATH)) vty_out(vty, "="); else vty_out(vty, " "); /* Internal route. */ if (path->peer && (path->peer->as) && (path->peer->as == path->peer->local_as)) vty_out(vty, "i"); else vty_out(vty, " "); } static char *bgp_nexthop_hostname(struct peer *peer, struct attr *attr) { if (peer->hostname && bgp_flag_check(peer->bgp, BGP_FLAG_SHOW_HOSTNAME) && !(attr->flag & ATTR_FLAG_BIT(BGP_ATTR_ORIGINATOR_ID))) return peer->hostname; return NULL; } /* called from terminal list command */ void route_vty_out(struct vty *vty, struct prefix *p, struct bgp_path_info *path, int display, safi_t safi, json_object *json_paths) { struct attr *attr = path->attr; json_object *json_path = NULL; json_object *json_nexthops = NULL; json_object *json_nexthop_global = NULL; json_object *json_nexthop_ll = NULL; json_object *json_ext_community = NULL; char vrf_id_str[VRF_NAMSIZ] = {0}; bool nexthop_self = CHECK_FLAG(path->flags, BGP_PATH_ANNC_NH_SELF) ? true : false; bool nexthop_othervrf = false; vrf_id_t nexthop_vrfid = VRF_DEFAULT; const char *nexthop_vrfname = VRF_DEFAULT_NAME; char *nexthop_hostname = bgp_nexthop_hostname(path->peer, attr); if (json_paths) json_path = json_object_new_object(); /* short status lead text */ route_vty_short_status_out(vty, path, json_path); if (!json_paths) { /* print prefix and mask */ if (!display) route_vty_out_route(p, vty, json_path); else vty_out(vty, "%*s", 17, " "); } else { route_vty_out_route(p, vty, json_path); } /* Print attribute */ if (!attr) { if (json_paths) json_object_array_add(json_paths, json_path); else vty_out(vty, "\n"); return; } /* * If vrf id of nexthop is different from that of prefix, * set up printable string to append */ if (path->extra && path->extra->bgp_orig) { const char *self = ""; if (nexthop_self) self = "<"; nexthop_othervrf = true; nexthop_vrfid = path->extra->bgp_orig->vrf_id; if (path->extra->bgp_orig->vrf_id == VRF_UNKNOWN) snprintf(vrf_id_str, sizeof(vrf_id_str), "@%s%s", VRFID_NONE_STR, self); else snprintf(vrf_id_str, sizeof(vrf_id_str), "@%u%s", path->extra->bgp_orig->vrf_id, self); if (path->extra->bgp_orig->inst_type != BGP_INSTANCE_TYPE_DEFAULT) nexthop_vrfname = path->extra->bgp_orig->name; } else { const char *self = ""; if (nexthop_self) self = "<"; snprintf(vrf_id_str, sizeof(vrf_id_str), "%s", self); } /* * For ENCAP and EVPN routes, nexthop address family is not * neccessarily the same as the prefix address family. * Both SAFI_MPLS_VPN and SAFI_ENCAP use the MP nexthop field * EVPN routes are also exchanged with a MP nexthop. Currently, * this * is only IPv4, the value will be present in either * attr->nexthop or * attr->mp_nexthop_global_in */ if ((safi == SAFI_ENCAP) || (safi == SAFI_MPLS_VPN)) { char buf[BUFSIZ]; char nexthop[128]; int af = NEXTHOP_FAMILY(attr->mp_nexthop_len); switch (af) { case AF_INET: sprintf(nexthop, "%s", inet_ntop(af, &attr->mp_nexthop_global_in, buf, BUFSIZ)); break; case AF_INET6: sprintf(nexthop, "%s", inet_ntop(af, &attr->mp_nexthop_global, buf, BUFSIZ)); break; default: sprintf(nexthop, "?"); break; } if (json_paths) { json_nexthop_global = json_object_new_object(); json_object_string_add(json_nexthop_global, "ip", nexthop); if (nexthop_hostname) json_object_string_add(json_nexthop_global, "hostname", nexthop_hostname); json_object_string_add(json_nexthop_global, "afi", (af == AF_INET) ? "ipv4" : "ipv6"); json_object_boolean_true_add(json_nexthop_global, "used"); } else vty_out(vty, "%s%s", nexthop_hostname ? nexthop_hostname : nexthop, vrf_id_str); } else if (safi == SAFI_EVPN) { if (json_paths) { json_nexthop_global = json_object_new_object(); json_object_string_add(json_nexthop_global, "ip", inet_ntoa(attr->nexthop)); if (nexthop_hostname) json_object_string_add(json_nexthop_global, "hostname", nexthop_hostname); json_object_string_add(json_nexthop_global, "afi", "ipv4"); json_object_boolean_true_add(json_nexthop_global, "used"); } else vty_out(vty, "%-16s%s", nexthop_hostname ? nexthop_hostname : inet_ntoa(attr->nexthop), vrf_id_str); } else if (safi == SAFI_FLOWSPEC) { if (attr->nexthop.s_addr != 0) { if (json_paths) { json_nexthop_global = json_object_new_object(); json_object_string_add(json_nexthop_global, "afi", "ipv4"); json_object_string_add( json_nexthop_global, "ip", inet_ntoa(attr->nexthop)); if (nexthop_hostname) json_object_string_add( json_nexthop_global, "hostname", nexthop_hostname); json_object_boolean_true_add( json_nexthop_global, "used"); } else { vty_out(vty, "%-16s", nexthop_hostname ? nexthop_hostname : inet_ntoa(attr->nexthop)); } } } else if (p->family == AF_INET && !BGP_ATTR_NEXTHOP_AFI_IP6(attr)) { if (json_paths) { json_nexthop_global = json_object_new_object(); if ((safi == SAFI_MPLS_VPN) || (safi == SAFI_EVPN)) { json_object_string_add( json_nexthop_global, "ip", inet_ntoa(attr->mp_nexthop_global_in)); if (nexthop_hostname) json_object_string_add( json_nexthop_global, "hostname", nexthop_hostname); } else { json_object_string_add( json_nexthop_global, "ip", inet_ntoa(attr->nexthop)); if (nexthop_hostname) json_object_string_add( json_nexthop_global, "hostname", nexthop_hostname); json_object_string_add(json_nexthop_global, "afi", "ipv4"); json_object_boolean_true_add( json_nexthop_global, "used"); } } else { char buf[BUFSIZ]; snprintf(buf, sizeof(buf), "%s%s", nexthop_hostname ? nexthop_hostname : inet_ntoa(attr->nexthop), vrf_id_str); vty_out(vty, "%-16s", buf); } } /* IPv6 Next Hop */ else if (p->family == AF_INET6 || BGP_ATTR_NEXTHOP_AFI_IP6(attr)) { int len; char buf[BUFSIZ]; if (json_paths) { json_nexthop_global = json_object_new_object(); json_object_string_add( json_nexthop_global, "ip", inet_ntop(AF_INET6, &attr->mp_nexthop_global, buf, BUFSIZ)); if (nexthop_hostname) json_object_string_add(json_nexthop_global, "hostname", nexthop_hostname); json_object_string_add(json_nexthop_global, "afi", "ipv6"); json_object_string_add(json_nexthop_global, "scope", "global"); /* We display both LL & GL if both have been * received */ if ((attr->mp_nexthop_len == 32) || (path->peer->conf_if)) { json_nexthop_ll = json_object_new_object(); json_object_string_add( json_nexthop_ll, "ip", inet_ntop(AF_INET6, &attr->mp_nexthop_local, buf, BUFSIZ)); if (nexthop_hostname) json_object_string_add( json_nexthop_ll, "hostname", nexthop_hostname); json_object_string_add(json_nexthop_ll, "afi", "ipv6"); json_object_string_add(json_nexthop_ll, "scope", "link-local"); if ((IPV6_ADDR_CMP(&attr->mp_nexthop_global, &attr->mp_nexthop_local) != 0) && !attr->mp_nexthop_prefer_global) json_object_boolean_true_add( json_nexthop_ll, "used"); else json_object_boolean_true_add( json_nexthop_global, "used"); } else json_object_boolean_true_add( json_nexthop_global, "used"); } else { /* Display LL if LL/Global both in table unless * prefer-global is set */ if (((attr->mp_nexthop_len == 32) && !attr->mp_nexthop_prefer_global) || (path->peer->conf_if)) { if (path->peer->conf_if) { len = vty_out(vty, "%s", path->peer->conf_if); len = 16 - len; /* len of IPv6 addr + max len of def ifname */ if (len < 1) vty_out(vty, "\n%*s", 36, " "); else vty_out(vty, "%*s", len, " "); } else { len = vty_out( vty, "%s%s", nexthop_hostname ? nexthop_hostname : inet_ntop( AF_INET6, &attr->mp_nexthop_local, buf, BUFSIZ), vrf_id_str); len = 16 - len; if (len < 1) vty_out(vty, "\n%*s", 36, " "); else vty_out(vty, "%*s", len, " "); } } else { len = vty_out( vty, "%s%s", nexthop_hostname ? nexthop_hostname : inet_ntop( AF_INET6, &attr->mp_nexthop_global, buf, BUFSIZ), vrf_id_str); len = 16 - len; if (len < 1) vty_out(vty, "\n%*s", 36, " "); else vty_out(vty, "%*s", len, " "); } } } /* MED/Metric */ if (attr->flag & ATTR_FLAG_BIT(BGP_ATTR_MULTI_EXIT_DISC)) if (json_paths) { /* * Adding "metric" field to match with corresponding * CLI. "med" will be deprecated in future. */ json_object_int_add(json_path, "med", attr->med); json_object_int_add(json_path, "metric", attr->med); } else vty_out(vty, "%10u", attr->med); else if (!json_paths) vty_out(vty, " "); /* Local Pref */ if (attr->flag & ATTR_FLAG_BIT(BGP_ATTR_LOCAL_PREF)) if (json_paths) { /* * Adding "locPrf" field to match with corresponding * CLI. "localPref" will be deprecated in future. */ json_object_int_add(json_path, "localpref", attr->local_pref); json_object_int_add(json_path, "locPrf", attr->local_pref); } else vty_out(vty, "%7u", attr->local_pref); else if (!json_paths) vty_out(vty, " "); if (json_paths) json_object_int_add(json_path, "weight", attr->weight); else vty_out(vty, "%7u ", attr->weight); if (json_paths) { char buf[BUFSIZ]; json_object_string_add( json_path, "peerId", sockunion2str(&path->peer->su, buf, SU_ADDRSTRLEN)); } /* Print aspath */ if (attr->aspath) { if (json_paths) { /* * Adding "path" field to match with corresponding * CLI. "aspath" will be deprecated in future. */ json_object_string_add(json_path, "aspath", attr->aspath->str); json_object_string_add(json_path, "path", attr->aspath->str); } else aspath_print_vty(vty, "%s", attr->aspath, " "); } /* Print origin */ if (json_paths) json_object_string_add(json_path, "origin", bgp_origin_long_str[attr->origin]); else vty_out(vty, "%s", bgp_origin_str[attr->origin]); if (json_paths) { if (safi == SAFI_EVPN && attr->flag & ATTR_FLAG_BIT(BGP_ATTR_EXT_COMMUNITIES)) { json_ext_community = json_object_new_object(); json_object_string_add(json_ext_community, "string", attr->ecommunity->str); json_object_object_add(json_path, "extendedCommunity", json_ext_community); } if (nexthop_self) json_object_boolean_true_add(json_path, "announceNexthopSelf"); if (nexthop_othervrf) { json_object_string_add(json_path, "nhVrfName", nexthop_vrfname); json_object_int_add(json_path, "nhVrfId", ((nexthop_vrfid == VRF_UNKNOWN) ? -1 : (int)nexthop_vrfid)); } } if (json_paths) { if (json_nexthop_global || json_nexthop_ll) { json_nexthops = json_object_new_array(); if (json_nexthop_global) json_object_array_add(json_nexthops, json_nexthop_global); if (json_nexthop_ll) json_object_array_add(json_nexthops, json_nexthop_ll); json_object_object_add(json_path, "nexthops", json_nexthops); } json_object_array_add(json_paths, json_path); } else { vty_out(vty, "\n"); if (safi == SAFI_EVPN && attr->flag & ATTR_FLAG_BIT(BGP_ATTR_EXT_COMMUNITIES)) { vty_out(vty, "%*s", 20, " "); vty_out(vty, "%s\n", attr->ecommunity->str); } #if ENABLE_BGP_VNC /* prints an additional line, indented, with VNC info, if * present */ if ((safi == SAFI_MPLS_VPN) || (safi == SAFI_ENCAP)) rfapi_vty_out_vncinfo(vty, p, path, safi); #endif } } /* called from terminal list command */ void route_vty_out_tmp(struct vty *vty, struct prefix *p, struct attr *attr, safi_t safi, bool use_json, json_object *json_ar) { json_object *json_status = NULL; json_object *json_net = NULL; char buff[BUFSIZ]; char buf2[BUFSIZ]; /* Route status display. */ if (use_json) { json_status = json_object_new_object(); json_net = json_object_new_object(); } else { vty_out(vty, "*"); vty_out(vty, ">"); vty_out(vty, " "); } /* print prefix and mask */ if (use_json) { json_object_string_add( json_net, "addrPrefix", inet_ntop(p->family, &p->u.prefix, buff, BUFSIZ)); json_object_int_add(json_net, "prefixLen", p->prefixlen); prefix2str(p, buf2, PREFIX_STRLEN); json_object_string_add(json_net, "network", buf2); } else route_vty_out_route(p, vty, NULL); /* Print attribute */ if (attr) { if (use_json) { if (p->family == AF_INET && (safi == SAFI_MPLS_VPN || safi == SAFI_ENCAP || safi == SAFI_EVPN || !BGP_ATTR_NEXTHOP_AFI_IP6(attr))) { if (safi == SAFI_MPLS_VPN || safi == SAFI_ENCAP || safi == SAFI_EVPN) json_object_string_add( json_net, "nextHop", inet_ntoa( attr->mp_nexthop_global_in)); else json_object_string_add( json_net, "nextHop", inet_ntoa(attr->nexthop)); } else if (p->family == AF_INET6 || BGP_ATTR_NEXTHOP_AFI_IP6(attr)) { char buf[BUFSIZ]; json_object_string_add( json_net, "nextHopGlobal", inet_ntop(AF_INET6, &attr->mp_nexthop_global, buf, BUFSIZ)); } if (attr->flag & ATTR_FLAG_BIT(BGP_ATTR_MULTI_EXIT_DISC)) json_object_int_add(json_net, "metric", attr->med); if (attr->flag & ATTR_FLAG_BIT(BGP_ATTR_LOCAL_PREF)) { /* * Adding "locPrf" field to match with * corresponding CLI. "localPref" will be * deprecated in future. */ json_object_int_add(json_net, "localPref", attr->local_pref); json_object_int_add(json_net, "locPrf", attr->local_pref); } json_object_int_add(json_net, "weight", attr->weight); /* Print aspath */ if (attr->aspath) { /* * Adding "path" field to match with * corresponding CLI. "localPref" will be * deprecated in future. */ json_object_string_add(json_net, "asPath", attr->aspath->str); json_object_string_add(json_net, "path", attr->aspath->str); } /* Print origin */ json_object_string_add(json_net, "bgpOriginCode", bgp_origin_str[attr->origin]); } else { if (p->family == AF_INET && (safi == SAFI_MPLS_VPN || safi == SAFI_ENCAP || safi == SAFI_EVPN || !BGP_ATTR_NEXTHOP_AFI_IP6(attr))) { if (safi == SAFI_MPLS_VPN || safi == SAFI_ENCAP || safi == SAFI_EVPN) vty_out(vty, "%-16s", inet_ntoa( attr->mp_nexthop_global_in)); else vty_out(vty, "%-16s", inet_ntoa(attr->nexthop)); } else if (p->family == AF_INET6 || BGP_ATTR_NEXTHOP_AFI_IP6(attr)) { int len; char buf[BUFSIZ]; len = vty_out( vty, "%s", inet_ntop(AF_INET6, &attr->mp_nexthop_global, buf, BUFSIZ)); len = 16 - len; if (len < 1) vty_out(vty, "\n%*s", 36, " "); else vty_out(vty, "%*s", len, " "); } if (attr->flag & ATTR_FLAG_BIT(BGP_ATTR_MULTI_EXIT_DISC)) vty_out(vty, "%10u", attr->med); else vty_out(vty, " "); if (attr->flag & ATTR_FLAG_BIT(BGP_ATTR_LOCAL_PREF)) vty_out(vty, "%7u", attr->local_pref); else vty_out(vty, " "); vty_out(vty, "%7u ", attr->weight); /* Print aspath */ if (attr->aspath) aspath_print_vty(vty, "%s", attr->aspath, " "); /* Print origin */ vty_out(vty, "%s", bgp_origin_str[attr->origin]); } } if (use_json) { json_object_boolean_true_add(json_status, "*"); json_object_boolean_true_add(json_status, ">"); json_object_object_add(json_net, "appliedStatusSymbols", json_status); char buf_cut[BUFSIZ]; prefix2str(p, buf_cut, PREFIX_STRLEN); json_object_object_add(json_ar, buf_cut, json_net); } else vty_out(vty, "\n"); } void route_vty_out_tag(struct vty *vty, struct prefix *p, struct bgp_path_info *path, int display, safi_t safi, json_object *json) { json_object *json_out = NULL; struct attr *attr; mpls_label_t label = MPLS_INVALID_LABEL; if (!path->extra) return; if (json) json_out = json_object_new_object(); /* short status lead text */ route_vty_short_status_out(vty, path, json_out); /* print prefix and mask */ if (json == NULL) { if (!display) route_vty_out_route(p, vty, NULL); else vty_out(vty, "%*s", 17, " "); } /* Print attribute */ attr = path->attr; if (attr) { if (((p->family == AF_INET) && ((safi == SAFI_MPLS_VPN || safi == SAFI_ENCAP))) || (safi == SAFI_EVPN && !BGP_ATTR_NEXTHOP_AFI_IP6(attr)) || (!BGP_ATTR_NEXTHOP_AFI_IP6(attr))) { if (safi == SAFI_MPLS_VPN || safi == SAFI_ENCAP || safi == SAFI_EVPN) { if (json) json_object_string_add( json_out, "mpNexthopGlobalIn", inet_ntoa( attr->mp_nexthop_global_in)); else vty_out(vty, "%-16s", inet_ntoa( attr->mp_nexthop_global_in)); } else { if (json) json_object_string_add( json_out, "nexthop", inet_ntoa(attr->nexthop)); else vty_out(vty, "%-16s", inet_ntoa(attr->nexthop)); } } else if (((p->family == AF_INET6) && ((safi == SAFI_MPLS_VPN || safi == SAFI_ENCAP))) || (safi == SAFI_EVPN && BGP_ATTR_NEXTHOP_AFI_IP6(attr)) || (BGP_ATTR_NEXTHOP_AFI_IP6(attr))) { char buf_a[512]; if (attr->mp_nexthop_len == BGP_ATTR_NHLEN_IPV6_GLOBAL) { if (json) json_object_string_add( json_out, "mpNexthopGlobalIn", inet_ntop( AF_INET6, &attr->mp_nexthop_global, buf_a, sizeof(buf_a))); else vty_out(vty, "%s", inet_ntop( AF_INET6, &attr->mp_nexthop_global, buf_a, sizeof(buf_a))); } else if (attr->mp_nexthop_len == BGP_ATTR_NHLEN_IPV6_GLOBAL_AND_LL) { snprintfrr(buf_a, sizeof(buf_a), "%pI6(%pI6)", &attr->mp_nexthop_global, &attr->mp_nexthop_local); if (json) json_object_string_add( json_out, "mpNexthopGlobalLocal", buf_a); else vty_out(vty, "%s", buf_a); } } } label = decode_label(&path->extra->label[0]); if (bgp_is_valid_label(&label)) { if (json) { json_object_int_add(json_out, "notag", label); json_object_array_add(json, json_out); } else { vty_out(vty, "notag/%d", label); vty_out(vty, "\n"); } } } void route_vty_out_overlay(struct vty *vty, struct prefix *p, struct bgp_path_info *path, int display, json_object *json_paths) { struct attr *attr; char buf[BUFSIZ] = {0}; json_object *json_path = NULL; json_object *json_nexthop = NULL; json_object *json_overlay = NULL; if (!path->extra) return; if (json_paths) { json_path = json_object_new_object(); json_overlay = json_object_new_object(); json_nexthop = json_object_new_object(); } /* short status lead text */ route_vty_short_status_out(vty, path, json_path); /* print prefix and mask */ if (!display) route_vty_out_route(p, vty, json_path); else vty_out(vty, "%*s", 17, " "); /* Print attribute */ attr = path->attr; if (attr) { char buf1[BUFSIZ]; int af = NEXTHOP_FAMILY(attr->mp_nexthop_len); switch (af) { case AF_INET: inet_ntop(af, &attr->mp_nexthop_global_in, buf, BUFSIZ); if (!json_path) { vty_out(vty, "%-16s", buf); } else { json_object_string_add(json_nexthop, "ip", buf); json_object_string_add(json_nexthop, "afi", "ipv4"); json_object_object_add(json_path, "nexthop", json_nexthop); } break; case AF_INET6: inet_ntop(af, &attr->mp_nexthop_global, buf, BUFSIZ); inet_ntop(af, &attr->mp_nexthop_local, buf1, BUFSIZ); if (!json_path) { vty_out(vty, "%s(%s)", buf, buf1); } else { json_object_string_add(json_nexthop, "ipv6Global", buf); json_object_string_add(json_nexthop, "ipv6LinkLocal", buf1); json_object_string_add(json_nexthop, "afi", "ipv6"); json_object_object_add(json_path, "nexthop", json_nexthop); } break; default: if (!json_path) { vty_out(vty, "?"); } else { json_object_string_add(json_nexthop, "Error", "Unsupported address-family"); } } char *str = esi2str(&(attr->evpn_overlay.eth_s_id)); if (!json_path) vty_out(vty, "%s", str); else json_object_string_add(json_overlay, "esi", str); XFREE(MTYPE_TMP, str); if (is_evpn_prefix_ipaddr_v4((struct prefix_evpn *)p)) { inet_ntop(AF_INET, &(attr->evpn_overlay.gw_ip.ipv4), buf, BUFSIZ); } else if (is_evpn_prefix_ipaddr_v6((struct prefix_evpn *)p)) { inet_ntop(AF_INET6, &(attr->evpn_overlay.gw_ip.ipv6), buf, BUFSIZ); } if (!json_path) vty_out(vty, "/%s", buf); else json_object_string_add(json_overlay, "gw", buf); if (attr->ecommunity) { char *mac = NULL; struct ecommunity_val *routermac = ecommunity_lookup( attr->ecommunity, ECOMMUNITY_ENCODE_EVPN, ECOMMUNITY_EVPN_SUBTYPE_ROUTERMAC); if (routermac) mac = ecom_mac2str((char *)routermac->val); if (mac) { if (!json_path) { vty_out(vty, "/%s", (char *)mac); } else { json_object_string_add(json_overlay, "rmac", mac); } XFREE(MTYPE_TMP, mac); } } if (!json_path) { vty_out(vty, "\n"); } else { json_object_object_add(json_path, "overlay", json_overlay); json_object_array_add(json_paths, json_path); } } } /* dampening route */ static void damp_route_vty_out(struct vty *vty, struct prefix *p, struct bgp_path_info *path, int display, afi_t afi, safi_t safi, bool use_json, json_object *json) { struct attr *attr; int len; char timebuf[BGP_UPTIME_LEN]; /* short status lead text */ route_vty_short_status_out(vty, path, json); /* print prefix and mask */ if (!use_json) { if (!display) route_vty_out_route(p, vty, NULL); else vty_out(vty, "%*s", 17, " "); } len = vty_out(vty, "%s", path->peer->host); len = 17 - len; if (len < 1) { if (!use_json) vty_out(vty, "\n%*s", 34, " "); } else { if (use_json) json_object_int_add(json, "peerHost", len); else vty_out(vty, "%*s", len, " "); } if (use_json) bgp_damp_reuse_time_vty(vty, path, timebuf, BGP_UPTIME_LEN, afi, safi, use_json, json); else vty_out(vty, "%s ", bgp_damp_reuse_time_vty(vty, path, timebuf, BGP_UPTIME_LEN, afi, safi, use_json, json)); /* Print attribute */ attr = path->attr; if (attr) { /* Print aspath */ if (attr->aspath) { if (use_json) json_object_string_add(json, "asPath", attr->aspath->str); else aspath_print_vty(vty, "%s", attr->aspath, " "); } /* Print origin */ if (use_json) json_object_string_add(json, "origin", bgp_origin_str[attr->origin]); else vty_out(vty, "%s", bgp_origin_str[attr->origin]); } if (!use_json) vty_out(vty, "\n"); } /* flap route */ static void flap_route_vty_out(struct vty *vty, struct prefix *p, struct bgp_path_info *path, int display, afi_t afi, safi_t safi, bool use_json, json_object *json) { struct attr *attr; struct bgp_damp_info *bdi; char timebuf[BGP_UPTIME_LEN]; int len; if (!path->extra) return; bdi = path->extra->damp_info; /* short status lead text */ route_vty_short_status_out(vty, path, json); /* print prefix and mask */ if (!use_json) { if (!display) route_vty_out_route(p, vty, NULL); else vty_out(vty, "%*s", 17, " "); } len = vty_out(vty, "%s", path->peer->host); len = 16 - len; if (len < 1) { if (!use_json) vty_out(vty, "\n%*s", 33, " "); } else { if (use_json) json_object_int_add(json, "peerHost", len); else vty_out(vty, "%*s", len, " "); } len = vty_out(vty, "%d", bdi->flap); len = 5 - len; if (len < 1) { if (!use_json) vty_out(vty, " "); } else { if (use_json) json_object_int_add(json, "bdiFlap", len); else vty_out(vty, "%*s", len, " "); } if (use_json) peer_uptime(bdi->start_time, timebuf, BGP_UPTIME_LEN, use_json, json); else vty_out(vty, "%s ", peer_uptime(bdi->start_time, timebuf, BGP_UPTIME_LEN, 0, NULL)); if (CHECK_FLAG(path->flags, BGP_PATH_DAMPED) && !CHECK_FLAG(path->flags, BGP_PATH_HISTORY)) { if (use_json) bgp_damp_reuse_time_vty(vty, path, timebuf, BGP_UPTIME_LEN, afi, safi, use_json, json); else vty_out(vty, "%s ", bgp_damp_reuse_time_vty(vty, path, timebuf, BGP_UPTIME_LEN, afi, safi, use_json, json)); } else { if (!use_json) vty_out(vty, "%*s ", 8, " "); } /* Print attribute */ attr = path->attr; if (attr) { /* Print aspath */ if (attr->aspath) { if (use_json) json_object_string_add(json, "asPath", attr->aspath->str); else aspath_print_vty(vty, "%s", attr->aspath, " "); } /* Print origin */ if (use_json) json_object_string_add(json, "origin", bgp_origin_str[attr->origin]); else vty_out(vty, "%s", bgp_origin_str[attr->origin]); } if (!use_json) vty_out(vty, "\n"); } static void route_vty_out_advertised_to(struct vty *vty, struct peer *peer, int *first, const char *header, json_object *json_adv_to) { char buf1[INET6_ADDRSTRLEN]; json_object *json_peer = NULL; if (json_adv_to) { /* 'advertised-to' is a dictionary of peers we have advertised * this * prefix too. The key is the peer's IP or swpX, the value is * the * hostname if we know it and "" if not. */ json_peer = json_object_new_object(); if (peer->hostname) json_object_string_add(json_peer, "hostname", peer->hostname); if (peer->conf_if) json_object_object_add(json_adv_to, peer->conf_if, json_peer); else json_object_object_add( json_adv_to, sockunion2str(&peer->su, buf1, SU_ADDRSTRLEN), json_peer); } else { if (*first) { vty_out(vty, "%s", header); *first = 0; } if (peer->hostname && bgp_flag_check(peer->bgp, BGP_FLAG_SHOW_HOSTNAME)) { if (peer->conf_if) vty_out(vty, " %s(%s)", peer->hostname, peer->conf_if); else vty_out(vty, " %s(%s)", peer->hostname, sockunion2str(&peer->su, buf1, SU_ADDRSTRLEN)); } else { if (peer->conf_if) vty_out(vty, " %s", peer->conf_if); else vty_out(vty, " %s", sockunion2str(&peer->su, buf1, SU_ADDRSTRLEN)); } } } static void route_vty_out_tx_ids(struct vty *vty, struct bgp_addpath_info_data *d) { int i; for (i = 0; i < BGP_ADDPATH_MAX; i++) { vty_out(vty, "TX-%s %u%s", bgp_addpath_names(i)->human_name, d->addpath_tx_id[i], i < BGP_ADDPATH_MAX - 1 ? " " : "\n"); } } static const char *bgp_path_selection_reason2str( enum bgp_path_selection_reason reason) { switch (reason) { case bgp_path_selection_none: return "Nothing to Select"; break; case bgp_path_selection_first: return "First path received"; break; case bgp_path_selection_evpn_sticky_mac: return "EVPN Sticky Mac"; break; case bgp_path_selection_evpn_seq: return "EVPN sequence number"; break; case bgp_path_selection_evpn_lower_ip: return "EVPN lower IP"; break; case bgp_path_selection_weight: return "Weight"; break; case bgp_path_selection_local_pref: return "Local Pref"; break; case bgp_path_selection_local_route: return "Local Route"; break; case bgp_path_selection_confed_as_path: return "Confederation based AS Path"; break; case bgp_path_selection_as_path: return "AS Path"; break; case bgp_path_selection_origin: return "Origin"; break; case bgp_path_selection_med: return "MED"; break; case bgp_path_selection_peer: return "Peer Type"; break; case bgp_path_selection_confed: return "Confed Peer Type"; break; case bgp_path_selection_igp_metric: return "IGP Metric"; break; case bgp_path_selection_older: return "Older Path"; break; case bgp_path_selection_router_id: return "Router ID"; break; case bgp_path_selection_cluster_length: return "Cluser length"; break; case bgp_path_selection_stale: return "Path Staleness"; break; case bgp_path_selection_local_configured: return "Locally configured route"; break; case bgp_path_selection_neighbor_ip: return "Neighbor IP"; break; case bgp_path_selection_default: return "Nothing left to compare"; break; } return "Invalid (internal error)"; } void route_vty_out_detail(struct vty *vty, struct bgp *bgp, struct bgp_node *bn, struct bgp_path_info *path, afi_t afi, safi_t safi, json_object *json_paths) { char buf[INET6_ADDRSTRLEN]; char buf1[BUFSIZ]; char buf2[EVPN_ROUTE_STRLEN]; struct attr *attr = path->attr; int sockunion_vty_out(struct vty *, union sockunion *); time_t tbuf; json_object *json_bestpath = NULL; json_object *json_cluster_list = NULL; json_object *json_cluster_list_list = NULL; json_object *json_ext_community = NULL; json_object *json_last_update = NULL; json_object *json_pmsi = NULL; json_object *json_nexthop_global = NULL; json_object *json_nexthop_ll = NULL; json_object *json_nexthops = NULL; json_object *json_path = NULL; json_object *json_peer = NULL; json_object *json_string = NULL; json_object *json_adv_to = NULL; int first = 0; struct listnode *node, *nnode; struct peer *peer; int addpath_capable; int has_adj; unsigned int first_as; bool nexthop_self = CHECK_FLAG(path->flags, BGP_PATH_ANNC_NH_SELF) ? true : false; int i; char *nexthop_hostname = bgp_nexthop_hostname(path->peer, attr); if (json_paths) { json_path = json_object_new_object(); json_peer = json_object_new_object(); json_nexthop_global = json_object_new_object(); } if (!json_paths && path->extra) { char tag_buf[30]; buf2[0] = '\0'; tag_buf[0] = '\0'; if (path->extra && path->extra->num_labels) { bgp_evpn_label2str(path->extra->label, path->extra->num_labels, tag_buf, sizeof(tag_buf)); } if (safi == SAFI_EVPN) { bgp_evpn_route2str((struct prefix_evpn *)&bn->p, buf2, sizeof(buf2)); vty_out(vty, " Route %s", buf2); if (tag_buf[0] != '\0') vty_out(vty, " VNI %s", tag_buf); vty_out(vty, "\n"); } if (path->extra && path->extra->parent) { struct bgp_path_info *parent_ri; struct bgp_node *rn, *prn; parent_ri = (struct bgp_path_info *)path->extra->parent; rn = parent_ri->net; if (rn && rn->prn) { prn = rn->prn; prefix_rd2str((struct prefix_rd *)&prn->p, buf1, sizeof(buf1)); if (is_pi_family_evpn(parent_ri)) { bgp_evpn_route2str((struct prefix_evpn *)&rn->p, buf2, sizeof(buf2)); vty_out(vty, " Imported from %s:%s, VNI %s\n", buf1, buf2, tag_buf); } else vty_out(vty, " Imported from %s:%s\n", buf1, buf2); } } } attr = path->attr; if (attr) { /* Line1 display AS-path, Aggregator */ if (attr->aspath) { if (json_paths) { if (!attr->aspath->json) aspath_str_update(attr->aspath, true); json_object_lock(attr->aspath->json); json_object_object_add(json_path, "aspath", attr->aspath->json); } else { if (attr->aspath->segments) aspath_print_vty(vty, " %s", attr->aspath, ""); else vty_out(vty, " Local"); } } if (CHECK_FLAG(path->flags, BGP_PATH_REMOVED)) { if (json_paths) json_object_boolean_true_add(json_path, "removed"); else vty_out(vty, ", (removed)"); } if (CHECK_FLAG(path->flags, BGP_PATH_STALE)) { if (json_paths) json_object_boolean_true_add(json_path, "stale"); else vty_out(vty, ", (stale)"); } if (CHECK_FLAG(attr->flag, ATTR_FLAG_BIT(BGP_ATTR_AGGREGATOR))) { if (json_paths) { json_object_int_add(json_path, "aggregatorAs", attr->aggregator_as); json_object_string_add( json_path, "aggregatorId", inet_ntoa(attr->aggregator_addr)); } else { vty_out(vty, ", (aggregated by %u %s)", attr->aggregator_as, inet_ntoa(attr->aggregator_addr)); } } if (CHECK_FLAG(path->peer->af_flags[afi][safi], PEER_FLAG_REFLECTOR_CLIENT)) { if (json_paths) json_object_boolean_true_add( json_path, "rxedFromRrClient"); else vty_out(vty, ", (Received from a RR-client)"); } if (CHECK_FLAG(path->peer->af_flags[afi][safi], PEER_FLAG_RSERVER_CLIENT)) { if (json_paths) json_object_boolean_true_add( json_path, "rxedFromRsClient"); else vty_out(vty, ", (Received from a RS-client)"); } if (CHECK_FLAG(path->flags, BGP_PATH_HISTORY)) { if (json_paths) json_object_boolean_true_add( json_path, "dampeningHistoryEntry"); else vty_out(vty, ", (history entry)"); } else if (CHECK_FLAG(path->flags, BGP_PATH_DAMPED)) { if (json_paths) json_object_boolean_true_add( json_path, "dampeningSuppressed"); else vty_out(vty, ", (suppressed due to dampening)"); } if (!json_paths) vty_out(vty, "\n"); /* Line2 display Next-hop, Neighbor, Router-id */ /* Display the nexthop */ if ((bn->p.family == AF_INET || bn->p.family == AF_ETHERNET || bn->p.family == AF_EVPN) && (safi == SAFI_MPLS_VPN || safi == SAFI_ENCAP || safi == SAFI_EVPN || !BGP_ATTR_NEXTHOP_AFI_IP6(attr))) { if (safi == SAFI_MPLS_VPN || safi == SAFI_ENCAP || safi == SAFI_EVPN) { if (json_paths) { json_object_string_add( json_nexthop_global, "ip", inet_ntoa( attr->mp_nexthop_global_in)); if (nexthop_hostname) json_object_string_add( json_nexthop_global, "hostname", nexthop_hostname); } else { vty_out(vty, " %s", nexthop_hostname ? nexthop_hostname : inet_ntoa( attr->mp_nexthop_global_in)); } } else { if (json_paths) { json_object_string_add( json_nexthop_global, "ip", inet_ntoa(attr->nexthop)); if (nexthop_hostname) json_object_string_add( json_nexthop_global, "hostname", nexthop_hostname); } else vty_out(vty, " %s", nexthop_hostname ? nexthop_hostname : inet_ntoa( attr->nexthop)); } if (json_paths) json_object_string_add(json_nexthop_global, "afi", "ipv4"); } else { if (json_paths) { json_object_string_add( json_nexthop_global, "ip", inet_ntop(AF_INET6, &attr->mp_nexthop_global, buf, INET6_ADDRSTRLEN)); if (nexthop_hostname) json_object_string_add( json_nexthop_global, "hostname", nexthop_hostname); json_object_string_add(json_nexthop_global, "afi", "ipv6"); json_object_string_add(json_nexthop_global, "scope", "global"); } else { vty_out(vty, " %s", nexthop_hostname ? nexthop_hostname : inet_ntop( AF_INET6, &attr->mp_nexthop_global, buf, INET6_ADDRSTRLEN)); } } /* Display the IGP cost or 'inaccessible' */ if (!CHECK_FLAG(path->flags, BGP_PATH_VALID)) { if (json_paths) json_object_boolean_false_add( json_nexthop_global, "accessible"); else vty_out(vty, " (inaccessible)"); } else { if (path->extra && path->extra->igpmetric) { if (json_paths) json_object_int_add( json_nexthop_global, "metric", path->extra->igpmetric); else vty_out(vty, " (metric %u)", path->extra->igpmetric); } /* IGP cost is 0, display this only for json */ else { if (json_paths) json_object_int_add(json_nexthop_global, "metric", 0); } if (json_paths) json_object_boolean_true_add( json_nexthop_global, "accessible"); } /* Display peer "from" output */ /* This path was originated locally */ if (path->peer == bgp->peer_self) { if (safi == SAFI_EVPN || (bn->p.family == AF_INET && !BGP_ATTR_NEXTHOP_AFI_IP6(attr))) { if (json_paths) json_object_string_add( json_peer, "peerId", "0.0.0.0"); else vty_out(vty, " from 0.0.0.0 "); } else { if (json_paths) json_object_string_add(json_peer, "peerId", "::"); else vty_out(vty, " from :: "); } if (json_paths) json_object_string_add( json_peer, "routerId", inet_ntoa(bgp->router_id)); else vty_out(vty, "(%s)", inet_ntoa(bgp->router_id)); } /* We RXed this path from one of our peers */ else { if (json_paths) { json_object_string_add( json_peer, "peerId", sockunion2str(&path->peer->su, buf, SU_ADDRSTRLEN)); json_object_string_add( json_peer, "routerId", inet_ntop(AF_INET, &path->peer->remote_id, buf1, sizeof(buf1))); if (path->peer->hostname) json_object_string_add( json_peer, "hostname", path->peer->hostname); if (path->peer->domainname) json_object_string_add( json_peer, "domainname", path->peer->domainname); if (path->peer->conf_if) json_object_string_add( json_peer, "interface", path->peer->conf_if); } else { if (path->peer->conf_if) { if (path->peer->hostname && bgp_flag_check( path->peer->bgp, BGP_FLAG_SHOW_HOSTNAME)) vty_out(vty, " from %s(%s)", path->peer->hostname, path->peer->conf_if); else vty_out(vty, " from %s", path->peer->conf_if); } else { if (path->peer->hostname && bgp_flag_check( path->peer->bgp, BGP_FLAG_SHOW_HOSTNAME)) vty_out(vty, " from %s(%s)", path->peer->hostname, path->peer->host); else vty_out(vty, " from %s", sockunion2str( &path->peer->su, buf, SU_ADDRSTRLEN)); } if (attr->flag & ATTR_FLAG_BIT(BGP_ATTR_ORIGINATOR_ID)) vty_out(vty, " (%s)", inet_ntoa(attr->originator_id)); else vty_out(vty, " (%s)", inet_ntop( AF_INET, &path->peer->remote_id, buf1, sizeof(buf1))); } } /* * Note when vrfid of nexthop is different from that of prefix */ if (path->extra && path->extra->bgp_orig) { vrf_id_t nexthop_vrfid = path->extra->bgp_orig->vrf_id; if (json_paths) { const char *vn; if (path->extra->bgp_orig->inst_type == BGP_INSTANCE_TYPE_DEFAULT) vn = VRF_DEFAULT_NAME; else vn = path->extra->bgp_orig->name; json_object_string_add(json_path, "nhVrfName", vn); if (nexthop_vrfid == VRF_UNKNOWN) { json_object_int_add(json_path, "nhVrfId", -1); } else { json_object_int_add(json_path, "nhVrfId", (int)nexthop_vrfid); } } else { if (nexthop_vrfid == VRF_UNKNOWN) vty_out(vty, " vrf ?"); else vty_out(vty, " vrf %u", nexthop_vrfid); } } if (nexthop_self) { if (json_paths) { json_object_boolean_true_add(json_path, "announceNexthopSelf"); } else { vty_out(vty, " announce-nh-self"); } } if (!json_paths) vty_out(vty, "\n"); /* display the link-local nexthop */ if (attr->mp_nexthop_len == BGP_ATTR_NHLEN_IPV6_GLOBAL_AND_LL) { if (json_paths) { json_nexthop_ll = json_object_new_object(); json_object_string_add( json_nexthop_ll, "ip", inet_ntop(AF_INET6, &attr->mp_nexthop_local, buf, INET6_ADDRSTRLEN)); if (nexthop_hostname) json_object_string_add( json_nexthop_ll, "hostname", nexthop_hostname); json_object_string_add(json_nexthop_ll, "afi", "ipv6"); json_object_string_add(json_nexthop_ll, "scope", "link-local"); json_object_boolean_true_add(json_nexthop_ll, "accessible"); if (!attr->mp_nexthop_prefer_global) json_object_boolean_true_add( json_nexthop_ll, "used"); else json_object_boolean_true_add( json_nexthop_global, "used"); } else { vty_out(vty, " (%s) %s\n", inet_ntop(AF_INET6, &attr->mp_nexthop_local, buf, INET6_ADDRSTRLEN), attr->mp_nexthop_prefer_global ? "(prefer-global)" : "(used)"); } } /* If we do not have a link-local nexthop then we must flag the global as "used" */ else { if (json_paths) json_object_boolean_true_add( json_nexthop_global, "used"); } /* Line 3 display Origin, Med, Locpref, Weight, Tag, valid, * Int/Ext/Local, Atomic, best */ if (json_paths) json_object_string_add( json_path, "origin", bgp_origin_long_str[attr->origin]); else vty_out(vty, " Origin %s", bgp_origin_long_str[attr->origin]); if (attr->flag & ATTR_FLAG_BIT(BGP_ATTR_MULTI_EXIT_DISC)) { if (json_paths) { /* * Adding "metric" field to match with * corresponding CLI. "med" will be * deprecated in future. */ json_object_int_add(json_path, "med", attr->med); json_object_int_add(json_path, "metric", attr->med); } else vty_out(vty, ", metric %u", attr->med); } if (attr->flag & ATTR_FLAG_BIT(BGP_ATTR_LOCAL_PREF)) { if (json_paths) json_object_int_add(json_path, "localpref", attr->local_pref); else vty_out(vty, ", localpref %u", attr->local_pref); } if (attr->weight != 0) { if (json_paths) json_object_int_add(json_path, "weight", attr->weight); else vty_out(vty, ", weight %u", attr->weight); } if (attr->tag != 0) { if (json_paths) json_object_int_add(json_path, "tag", attr->tag); else vty_out(vty, ", tag %" ROUTE_TAG_PRI, attr->tag); } if (!CHECK_FLAG(path->flags, BGP_PATH_VALID)) { if (json_paths) json_object_boolean_false_add(json_path, "valid"); else vty_out(vty, ", invalid"); } else if (!CHECK_FLAG(path->flags, BGP_PATH_HISTORY)) { if (json_paths) json_object_boolean_true_add(json_path, "valid"); else vty_out(vty, ", valid"); } if (path->peer != bgp->peer_self) { if (path->peer->as == path->peer->local_as) { if (CHECK_FLAG(bgp->config, BGP_CONFIG_CONFEDERATION)) { if (json_paths) json_object_string_add( json_peer, "type", "confed-internal"); else vty_out(vty, ", confed-internal"); } else { if (json_paths) json_object_string_add( json_peer, "type", "internal"); else vty_out(vty, ", internal"); } } else { if (bgp_confederation_peers_check( bgp, path->peer->as)) { if (json_paths) json_object_string_add( json_peer, "type", "confed-external"); else vty_out(vty, ", confed-external"); } else { if (json_paths) json_object_string_add( json_peer, "type", "external"); else vty_out(vty, ", external"); } } } else if (path->sub_type == BGP_ROUTE_AGGREGATE) { if (json_paths) { json_object_boolean_true_add(json_path, "aggregated"); json_object_boolean_true_add(json_path, "local"); } else { vty_out(vty, ", aggregated, local"); } } else if (path->type != ZEBRA_ROUTE_BGP) { if (json_paths) json_object_boolean_true_add(json_path, "sourced"); else vty_out(vty, ", sourced"); } else { if (json_paths) { json_object_boolean_true_add(json_path, "sourced"); json_object_boolean_true_add(json_path, "local"); } else { vty_out(vty, ", sourced, local"); } } if (attr->flag & ATTR_FLAG_BIT(BGP_ATTR_ATOMIC_AGGREGATE)) { if (json_paths) json_object_boolean_true_add(json_path, "atomicAggregate"); else vty_out(vty, ", atomic-aggregate"); } if (CHECK_FLAG(path->flags, BGP_PATH_MULTIPATH) || (CHECK_FLAG(path->flags, BGP_PATH_SELECTED) && bgp_path_info_mpath_count(path))) { if (json_paths) json_object_boolean_true_add(json_path, "multipath"); else vty_out(vty, ", multipath"); } // Mark the bestpath(s) if (CHECK_FLAG(path->flags, BGP_PATH_DMED_SELECTED)) { first_as = aspath_get_first_as(attr->aspath); if (json_paths) { if (!json_bestpath) json_bestpath = json_object_new_object(); json_object_int_add(json_bestpath, "bestpathFromAs", first_as); } else { if (first_as) vty_out(vty, ", bestpath-from-AS %u", first_as); else vty_out(vty, ", bestpath-from-AS Local"); } } if (CHECK_FLAG(path->flags, BGP_PATH_SELECTED)) { if (json_paths) { if (!json_bestpath) json_bestpath = json_object_new_object(); json_object_boolean_true_add(json_bestpath, "overall"); json_object_string_add(json_bestpath, "selectionReason", bgp_path_selection_reason2str(bn->reason)); } else { vty_out(vty, ", best"); vty_out(vty, " (%s)", bgp_path_selection_reason2str(bn->reason)); } } if (json_bestpath) json_object_object_add(json_path, "bestpath", json_bestpath); if (!json_paths) vty_out(vty, "\n"); /* Line 4 display Community */ if (attr->community) { if (json_paths) { if (!attr->community->json) community_str(attr->community, true); json_object_lock(attr->community->json); json_object_object_add(json_path, "community", attr->community->json); } else { vty_out(vty, " Community: %s\n", attr->community->str); } } /* Line 5 display Extended-community */ if (attr->flag & ATTR_FLAG_BIT(BGP_ATTR_EXT_COMMUNITIES)) { if (json_paths) { json_ext_community = json_object_new_object(); json_object_string_add(json_ext_community, "string", attr->ecommunity->str); json_object_object_add(json_path, "extendedCommunity", json_ext_community); } else { vty_out(vty, " Extended Community: %s\n", attr->ecommunity->str); } } /* Line 6 display Large community */ if (attr->flag & ATTR_FLAG_BIT(BGP_ATTR_LARGE_COMMUNITIES)) { if (json_paths) { if (!attr->lcommunity->json) lcommunity_str(attr->lcommunity, true); json_object_lock(attr->lcommunity->json); json_object_object_add(json_path, "largeCommunity", attr->lcommunity->json); } else { vty_out(vty, " Large Community: %s\n", attr->lcommunity->str); } } /* Line 7 display Originator, Cluster-id */ if ((attr->flag & ATTR_FLAG_BIT(BGP_ATTR_ORIGINATOR_ID)) || (attr->flag & ATTR_FLAG_BIT(BGP_ATTR_CLUSTER_LIST))) { if (attr->flag & ATTR_FLAG_BIT(BGP_ATTR_ORIGINATOR_ID)) { if (json_paths) json_object_string_add( json_path, "originatorId", inet_ntoa(attr->originator_id)); else vty_out(vty, " Originator: %s", inet_ntoa(attr->originator_id)); } if (attr->flag & ATTR_FLAG_BIT(BGP_ATTR_CLUSTER_LIST)) { int i; if (json_paths) { json_cluster_list = json_object_new_object(); json_cluster_list_list = json_object_new_array(); for (i = 0; i < attr->cluster->length / 4; i++) { json_string = json_object_new_string( inet_ntoa( attr->cluster->list [i])); json_object_array_add( json_cluster_list_list, json_string); } /* struct cluster_list does not have "str" variable like * aspath and community do. Add this someday if someone * asks for it. json_object_string_add(json_cluster_list, "string", attr->cluster->str); */ json_object_object_add( json_cluster_list, "list", json_cluster_list_list); json_object_object_add( json_path, "clusterList", json_cluster_list); } else { vty_out(vty, ", Cluster list: "); for (i = 0; i < attr->cluster->length / 4; i++) { vty_out(vty, "%s ", inet_ntoa( attr->cluster->list [i])); } } } if (!json_paths) vty_out(vty, "\n"); } if (path->extra && path->extra->damp_info) bgp_damp_info_vty(vty, path, afi, safi, json_path); /* Remote Label */ if (path->extra && bgp_is_valid_label(&path->extra->label[0]) && safi != SAFI_EVPN) { mpls_label_t label = label_pton(&path->extra->label[0]); if (json_paths) json_object_int_add(json_path, "remoteLabel", label); else vty_out(vty, " Remote label: %d\n", label); } /* Label Index */ if (attr->label_index != BGP_INVALID_LABEL_INDEX) { if (json_paths) json_object_int_add(json_path, "labelIndex", attr->label_index); else vty_out(vty, " Label Index: %d\n", attr->label_index); } /* Line 8 display Addpath IDs */ if (path->addpath_rx_id || bgp_addpath_info_has_ids(&path->tx_addpath)) { if (json_paths) { json_object_int_add(json_path, "addpathRxId", path->addpath_rx_id); /* Keep backwards compatibility with the old API * by putting TX All's ID in the old field */ json_object_int_add( json_path, "addpathTxId", path->tx_addpath.addpath_tx_id [BGP_ADDPATH_ALL]); /* ... but create a specific field for each * strategy */ for (i = 0; i < BGP_ADDPATH_MAX; i++) { json_object_int_add( json_path, bgp_addpath_names(i) ->id_json_name, path->tx_addpath .addpath_tx_id[i]); } } else { vty_out(vty, " AddPath ID: RX %u, ", path->addpath_rx_id); route_vty_out_tx_ids(vty, &path->tx_addpath); } } /* If we used addpath to TX a non-bestpath we need to display * "Advertised to" on a path-by-path basis */ if (bgp_addpath_is_addpath_used(&bgp->tx_addpath, afi, safi)) { first = 1; for (ALL_LIST_ELEMENTS(bgp->peer, node, nnode, peer)) { addpath_capable = bgp_addpath_encode_tx(peer, afi, safi); has_adj = bgp_adj_out_lookup( peer, path->net, bgp_addpath_id_for_peer( peer, afi, safi, &path->tx_addpath)); if ((addpath_capable && has_adj) || (!addpath_capable && has_adj && CHECK_FLAG(path->flags, BGP_PATH_SELECTED))) { if (json_path && !json_adv_to) json_adv_to = json_object_new_object(); route_vty_out_advertised_to( vty, peer, &first, " Advertised to:", json_adv_to); } } if (json_path) { if (json_adv_to) { json_object_object_add(json_path, "advertisedTo", json_adv_to); } } else { if (!first) { vty_out(vty, "\n"); } } } /* Line 9 display Uptime */ tbuf = time(NULL) - (bgp_clock() - path->uptime); if (json_paths) { json_last_update = json_object_new_object(); json_object_int_add(json_last_update, "epoch", tbuf); json_object_string_add(json_last_update, "string", ctime(&tbuf)); json_object_object_add(json_path, "lastUpdate", json_last_update); } else vty_out(vty, " Last update: %s", ctime(&tbuf)); /* Line 10 display PMSI tunnel attribute, if present */ if (attr->flag & ATTR_FLAG_BIT(BGP_ATTR_PMSI_TUNNEL)) { const char *str = lookup_msg(bgp_pmsi_tnltype_str, attr->pmsi_tnl_type, PMSI_TNLTYPE_STR_DEFAULT); if (json_paths) { json_pmsi = json_object_new_object(); json_object_string_add(json_pmsi, "tunnelType", str); json_object_int_add(json_pmsi, "label", label2vni(&attr->label)); json_object_object_add(json_path, "pmsi", json_pmsi); } else vty_out(vty, " PMSI Tunnel Type: %s, label: %d\n", str, label2vni(&attr->label)); } } /* We've constructed the json object for this path, add it to the json * array of paths */ if (json_paths) { if (json_nexthop_global || json_nexthop_ll) { json_nexthops = json_object_new_array(); if (json_nexthop_global) json_object_array_add(json_nexthops, json_nexthop_global); if (json_nexthop_ll) json_object_array_add(json_nexthops, json_nexthop_ll); json_object_object_add(json_path, "nexthops", json_nexthops); } json_object_object_add(json_path, "peer", json_peer); json_object_array_add(json_paths, json_path); } else vty_out(vty, "\n"); } #define BGP_SHOW_HEADER_CSV "Flags, Network, Next Hop, Metric, LocPrf, Weight, Path" #define BGP_SHOW_DAMP_HEADER " Network From Reuse Path\n" #define BGP_SHOW_FLAP_HEADER " Network From Flaps Duration Reuse Path\n" static int bgp_show_prefix_list(struct vty *vty, struct bgp *bgp, const char *prefix_list_str, afi_t afi, safi_t safi, enum bgp_show_type type); static int bgp_show_filter_list(struct vty *vty, struct bgp *bgp, const char *filter, afi_t afi, safi_t safi, enum bgp_show_type type); static int bgp_show_route_map(struct vty *vty, struct bgp *bgp, const char *rmap_str, afi_t afi, safi_t safi, enum bgp_show_type type); static int bgp_show_community_list(struct vty *vty, struct bgp *bgp, const char *com, int exact, afi_t afi, safi_t safi); static int bgp_show_prefix_longer(struct vty *vty, struct bgp *bgp, const char *prefix, afi_t afi, safi_t safi, enum bgp_show_type type); static int bgp_show_regexp(struct vty *vty, struct bgp *bgp, const char *regstr, afi_t afi, safi_t safi, enum bgp_show_type type); static int bgp_show_community(struct vty *vty, struct bgp *bgp, const char *comstr, int exact, afi_t afi, safi_t safi, bool use_json); static int bgp_show_table(struct vty *vty, struct bgp *bgp, safi_t safi, struct bgp_table *table, enum bgp_show_type type, void *output_arg, bool use_json, char *rd, int is_last, unsigned long *output_cum, unsigned long *total_cum, unsigned long *json_header_depth) { struct bgp_path_info *pi; struct bgp_node *rn; int header = 1; int display; unsigned long output_count = 0; unsigned long total_count = 0; struct prefix *p; char buf2[BUFSIZ]; json_object *json_paths = NULL; int first = 1; if (output_cum && *output_cum != 0) header = 0; if (use_json && !*json_header_depth) { vty_out(vty, "{\n \"vrfId\": %d,\n \"vrfName\": \"%s\",\n \"tableVersion\": %" PRId64 ",\n \"routerId\": \"%s\",\n \"defaultLocPrf\": %u,\n" " \"localAS\": %u,\n \"routes\": { ", bgp->vrf_id == VRF_UNKNOWN ? -1 : (int)bgp->vrf_id, bgp->inst_type == BGP_INSTANCE_TYPE_DEFAULT ? VRF_DEFAULT_NAME : bgp->name, table->version, inet_ntoa(bgp->router_id), bgp->default_local_pref, bgp->as); *json_header_depth = 2; if (rd) { vty_out(vty, " \"routeDistinguishers\" : {"); ++*json_header_depth; } } if (use_json && rd) { vty_out(vty, " \"%s\" : { ", rd); } /* Start processing of routes. */ for (rn = bgp_table_top(table); rn; rn = bgp_route_next(rn)) { pi = bgp_node_get_bgp_path_info(rn); if (pi == NULL) continue; display = 0; if (use_json) json_paths = json_object_new_array(); else json_paths = NULL; for (; pi; pi = pi->next) { total_count++; if (type == bgp_show_type_flap_statistics || type == bgp_show_type_flap_neighbor || type == bgp_show_type_dampend_paths || type == bgp_show_type_damp_neighbor) { if (!(pi->extra && pi->extra->damp_info)) continue; } if (type == bgp_show_type_regexp) { regex_t *regex = output_arg; if (bgp_regexec(regex, pi->attr->aspath) == REG_NOMATCH) continue; } if (type == bgp_show_type_prefix_list) { struct prefix_list *plist = output_arg; if (prefix_list_apply(plist, &rn->p) != PREFIX_PERMIT) continue; } if (type == bgp_show_type_filter_list) { struct as_list *as_list = output_arg; if (as_list_apply(as_list, pi->attr->aspath) != AS_FILTER_PERMIT) continue; } if (type == bgp_show_type_route_map) { struct route_map *rmap = output_arg; struct bgp_path_info path; struct attr dummy_attr; route_map_result_t ret; dummy_attr = *pi->attr; path.peer = pi->peer; path.attr = &dummy_attr; ret = route_map_apply(rmap, &rn->p, RMAP_BGP, &path); if (ret == RMAP_DENYMATCH) continue; } if (type == bgp_show_type_neighbor || type == bgp_show_type_flap_neighbor || type == bgp_show_type_damp_neighbor) { union sockunion *su = output_arg; if (pi->peer == NULL || pi->peer->su_remote == NULL || !sockunion_same(pi->peer->su_remote, su)) continue; } if (type == bgp_show_type_cidr_only) { uint32_t destination; destination = ntohl(rn->p.u.prefix4.s_addr); if (IN_CLASSC(destination) && rn->p.prefixlen == 24) continue; if (IN_CLASSB(destination) && rn->p.prefixlen == 16) continue; if (IN_CLASSA(destination) && rn->p.prefixlen == 8) continue; } if (type == bgp_show_type_prefix_longer) { p = output_arg; if (!prefix_match(p, &rn->p)) continue; } if (type == bgp_show_type_community_all) { if (!pi->attr->community) continue; } if (type == bgp_show_type_community) { struct community *com = output_arg; if (!pi->attr->community || !community_match(pi->attr->community, com)) continue; } if (type == bgp_show_type_community_exact) { struct community *com = output_arg; if (!pi->attr->community || !community_cmp(pi->attr->community, com)) continue; } if (type == bgp_show_type_community_list) { struct community_list *list = output_arg; if (!community_list_match(pi->attr->community, list)) continue; } if (type == bgp_show_type_community_list_exact) { struct community_list *list = output_arg; if (!community_list_exact_match( pi->attr->community, list)) continue; } if (type == bgp_show_type_lcommunity) { struct lcommunity *lcom = output_arg; if (!pi->attr->lcommunity || !lcommunity_match(pi->attr->lcommunity, lcom)) continue; } if (type == bgp_show_type_lcommunity_exact) { struct lcommunity *lcom = output_arg; if (!pi->attr->lcommunity || !lcommunity_cmp(pi->attr->lcommunity, lcom)) continue; } if (type == bgp_show_type_lcommunity_list) { struct community_list *list = output_arg; if (!lcommunity_list_match(pi->attr->lcommunity, list)) continue; } if (type == bgp_show_type_lcommunity_list_exact) { struct community_list *list = output_arg; if (!lcommunity_list_exact_match( pi->attr->lcommunity, list)) continue; } if (type == bgp_show_type_lcommunity_all) { if (!pi->attr->lcommunity) continue; } if (type == bgp_show_type_dampend_paths || type == bgp_show_type_damp_neighbor) { if (!CHECK_FLAG(pi->flags, BGP_PATH_DAMPED) || CHECK_FLAG(pi->flags, BGP_PATH_HISTORY)) continue; } if (!use_json && header) { vty_out(vty, "BGP table version is %" PRIu64 ", local router ID is %s, vrf id ", table->version, inet_ntoa(bgp->router_id)); if (bgp->vrf_id == VRF_UNKNOWN) vty_out(vty, "%s", VRFID_NONE_STR); else vty_out(vty, "%u", bgp->vrf_id); vty_out(vty, "\n"); vty_out(vty, "Default local pref %u, ", bgp->default_local_pref); vty_out(vty, "local AS %u\n", bgp->as); vty_out(vty, BGP_SHOW_SCODE_HEADER); vty_out(vty, BGP_SHOW_NCODE_HEADER); vty_out(vty, BGP_SHOW_OCODE_HEADER); if (type == bgp_show_type_dampend_paths || type == bgp_show_type_damp_neighbor) vty_out(vty, BGP_SHOW_DAMP_HEADER); else if (type == bgp_show_type_flap_statistics || type == bgp_show_type_flap_neighbor) vty_out(vty, BGP_SHOW_FLAP_HEADER); else vty_out(vty, BGP_SHOW_HEADER); header = 0; } if (rd != NULL && !display && !output_count) { if (!use_json) vty_out(vty, "Route Distinguisher: %s\n", rd); } if (type == bgp_show_type_dampend_paths || type == bgp_show_type_damp_neighbor) damp_route_vty_out(vty, &rn->p, pi, display, AFI_IP, safi, use_json, json_paths); else if (type == bgp_show_type_flap_statistics || type == bgp_show_type_flap_neighbor) flap_route_vty_out(vty, &rn->p, pi, display, AFI_IP, safi, use_json, json_paths); else route_vty_out(vty, &rn->p, pi, display, safi, json_paths); display++; } if (display) { output_count++; if (!use_json) continue; p = &rn->p; /* encode prefix */ if (p->family == AF_FLOWSPEC) { char retstr[BGP_FLOWSPEC_STRING_DISPLAY_MAX]; bgp_fs_nlri_get_string((unsigned char *) p->u.prefix_flowspec.ptr, p->u.prefix_flowspec .prefixlen, retstr, NLRI_STRING_FORMAT_MIN, NULL); if (first) vty_out(vty, "\"%s/%d\": ", retstr, p->u.prefix_flowspec.prefixlen); else vty_out(vty, ",\"%s/%d\": ", retstr, p->u.prefix_flowspec.prefixlen); } else { prefix2str(p, buf2, sizeof(buf2)); if (first) vty_out(vty, "\"%s\": ", buf2); else vty_out(vty, ",\"%s\": ", buf2); } vty_out(vty, "%s", json_object_to_json_string(json_paths)); json_object_free(json_paths); json_paths = NULL; first = 0; } else json_object_free(json_paths); } if (output_cum) { output_count += *output_cum; *output_cum = output_count; } if (total_cum) { total_count += *total_cum; *total_cum = total_count; } if (use_json) { if (rd) { vty_out(vty, " }%s ", (is_last ? "" : ",")); } if (is_last) { unsigned long i; for (i = 0; i < *json_header_depth; ++i) vty_out(vty, " } "); vty_out(vty, "\n"); } } else { if (is_last) { /* No route is displayed */ if (output_count == 0) { if (type == bgp_show_type_normal) vty_out(vty, "No BGP prefixes displayed, %ld exist\n", total_count); } else vty_out(vty, "\nDisplayed %ld routes and %ld total paths\n", output_count, total_count); } } return CMD_SUCCESS; } int bgp_show_table_rd(struct vty *vty, struct bgp *bgp, safi_t safi, struct bgp_table *table, struct prefix_rd *prd_match, enum bgp_show_type type, void *output_arg, bool use_json) { struct bgp_node *rn, *next; unsigned long output_cum = 0; unsigned long total_cum = 0; unsigned long json_header_depth = 0; struct bgp_table *itable; bool show_msg; show_msg = (!use_json && type == bgp_show_type_normal); for (rn = bgp_table_top(table); rn; rn = next) { next = bgp_route_next(rn); if (prd_match && memcmp(rn->p.u.val, prd_match->val, 8) != 0) continue; itable = bgp_node_get_bgp_table_info(rn); if (itable != NULL) { struct prefix_rd prd; char rd[RD_ADDRSTRLEN]; memcpy(&prd, &(rn->p), sizeof(struct prefix_rd)); prefix_rd2str(&prd, rd, sizeof(rd)); bgp_show_table(vty, bgp, safi, itable, type, output_arg, use_json, rd, next == NULL, &output_cum, &total_cum, &json_header_depth); if (next == NULL) show_msg = false; } } if (show_msg) { if (output_cum == 0) vty_out(vty, "No BGP prefixes displayed, %ld exist\n", total_cum); else vty_out(vty, "\nDisplayed %ld routes and %ld total paths\n", output_cum, total_cum); } return CMD_SUCCESS; } static int bgp_show(struct vty *vty, struct bgp *bgp, afi_t afi, safi_t safi, enum bgp_show_type type, void *output_arg, bool use_json) { struct bgp_table *table; unsigned long json_header_depth = 0; if (bgp == NULL) { bgp = bgp_get_default(); } if (bgp == NULL) { if (!use_json) vty_out(vty, "No BGP process is configured\n"); else vty_out(vty, "{}\n"); return CMD_WARNING; } table = bgp->rib[afi][safi]; /* use MPLS and ENCAP specific shows until they are merged */ if (safi == SAFI_MPLS_VPN) { return bgp_show_table_rd(vty, bgp, safi, table, NULL, type, output_arg, use_json); } if (safi == SAFI_FLOWSPEC && type == bgp_show_type_detail) { return bgp_show_table_flowspec(vty, bgp, afi, table, type, output_arg, use_json, 1, NULL, NULL); } /* labeled-unicast routes live in the unicast table */ else if (safi == SAFI_LABELED_UNICAST) safi = SAFI_UNICAST; return bgp_show_table(vty, bgp, safi, table, type, output_arg, use_json, NULL, 1, NULL, NULL, &json_header_depth); } static void bgp_show_all_instances_routes_vty(struct vty *vty, afi_t afi, safi_t safi, bool use_json) { struct listnode *node, *nnode; struct bgp *bgp; int is_first = 1; bool route_output = false; if (use_json) vty_out(vty, "{\n"); for (ALL_LIST_ELEMENTS(bm->bgp, node, nnode, bgp)) { route_output = true; if (use_json) { if (!is_first) vty_out(vty, ",\n"); else is_first = 0; vty_out(vty, "\"%s\":", (bgp->inst_type == BGP_INSTANCE_TYPE_DEFAULT) ? VRF_DEFAULT_NAME : bgp->name); } else { vty_out(vty, "\nInstance %s:\n", (bgp->inst_type == BGP_INSTANCE_TYPE_DEFAULT) ? VRF_DEFAULT_NAME : bgp->name); } bgp_show(vty, bgp, afi, safi, bgp_show_type_normal, NULL, use_json); } if (use_json) vty_out(vty, "}\n"); else if (!route_output) vty_out(vty, "%% BGP instance not found\n"); } /* Header of detailed BGP route information */ void route_vty_out_detail_header(struct vty *vty, struct bgp *bgp, struct bgp_node *rn, struct prefix_rd *prd, afi_t afi, safi_t safi, json_object *json) { struct bgp_path_info *pi; struct prefix *p; struct peer *peer; struct listnode *node, *nnode; char buf1[RD_ADDRSTRLEN]; char buf2[INET6_ADDRSTRLEN]; char buf3[EVPN_ROUTE_STRLEN]; char prefix_str[BUFSIZ]; int count = 0; int best = 0; int suppress = 0; int accept_own = 0; int route_filter_translated_v4 = 0; int route_filter_v4 = 0; int route_filter_translated_v6 = 0; int route_filter_v6 = 0; int llgr_stale = 0; int no_llgr = 0; int accept_own_nexthop = 0; int blackhole = 0; int no_export = 0; int no_advertise = 0; int local_as = 0; int no_peer = 0; int first = 1; int has_valid_label = 0; mpls_label_t label = 0; json_object *json_adv_to = NULL; p = &rn->p; has_valid_label = bgp_is_valid_label(&rn->local_label); if (has_valid_label) label = label_pton(&rn->local_label); if (json) { if (has_valid_label) json_object_int_add(json, "localLabel", label); json_object_string_add( json, "prefix", prefix2str(p, prefix_str, sizeof(prefix_str))); } else { if (safi == SAFI_EVPN) vty_out(vty, "BGP routing table entry for %s%s%s\n", prd ? prefix_rd2str(prd, buf1, sizeof(buf1)) : "", prd ? ":" : "", bgp_evpn_route2str((struct prefix_evpn *)p, buf3, sizeof(buf3))); else vty_out(vty, "BGP routing table entry for %s%s%s/%d\n", ((safi == SAFI_MPLS_VPN || safi == SAFI_ENCAP) ? prefix_rd2str(prd, buf1, sizeof(buf1)) : ""), safi == SAFI_MPLS_VPN ? ":" : "", inet_ntop(p->family, &p->u.prefix, buf2, INET6_ADDRSTRLEN), p->prefixlen); if (has_valid_label) vty_out(vty, "Local label: %d\n", label); if (bgp_labeled_safi(safi) && safi != SAFI_EVPN) vty_out(vty, "not allocated\n"); } for (pi = bgp_node_get_bgp_path_info(rn); pi; pi = pi->next) { count++; if (CHECK_FLAG(pi->flags, BGP_PATH_SELECTED)) { best = count; if (pi->extra && pi->extra->suppress) suppress = 1; if (pi->attr->community == NULL) continue; no_advertise += community_include( pi->attr->community, COMMUNITY_NO_ADVERTISE); no_export += community_include(pi->attr->community, COMMUNITY_NO_EXPORT); local_as += community_include(pi->attr->community, COMMUNITY_LOCAL_AS); accept_own += community_include(pi->attr->community, COMMUNITY_ACCEPT_OWN); route_filter_translated_v4 += community_include( pi->attr->community, COMMUNITY_ROUTE_FILTER_TRANSLATED_v4); route_filter_translated_v6 += community_include( pi->attr->community, COMMUNITY_ROUTE_FILTER_TRANSLATED_v6); route_filter_v4 += community_include( pi->attr->community, COMMUNITY_ROUTE_FILTER_v4); route_filter_v6 += community_include( pi->attr->community, COMMUNITY_ROUTE_FILTER_v6); llgr_stale += community_include(pi->attr->community, COMMUNITY_LLGR_STALE); no_llgr += community_include(pi->attr->community, COMMUNITY_NO_LLGR); accept_own_nexthop += community_include(pi->attr->community, COMMUNITY_ACCEPT_OWN_NEXTHOP); blackhole += community_include(pi->attr->community, COMMUNITY_BLACKHOLE); no_peer += community_include(pi->attr->community, COMMUNITY_NO_PEER); } } if (!json) { vty_out(vty, "Paths: (%d available", count); if (best) { vty_out(vty, ", best #%d", best); if (safi == SAFI_UNICAST) { if (bgp->inst_type == BGP_INSTANCE_TYPE_DEFAULT) vty_out(vty, ", table %s", VRF_DEFAULT_NAME); else vty_out(vty, ", vrf %s", bgp->name); } } else vty_out(vty, ", no best path"); if (accept_own) vty_out(vty, ", accept own local route exported and imported in different VRF"); else if (route_filter_translated_v4) vty_out(vty, ", mark translated RTs for VPNv4 route filtering"); else if (route_filter_v4) vty_out(vty, ", attach RT as-is for VPNv4 route filtering"); else if (route_filter_translated_v6) vty_out(vty, ", mark translated RTs for VPNv6 route filtering"); else if (route_filter_v6) vty_out(vty, ", attach RT as-is for VPNv6 route filtering"); else if (llgr_stale) vty_out(vty, ", mark routes to be retained for a longer time. Requeres support for Long-lived BGP Graceful Restart"); else if (no_llgr) vty_out(vty, ", mark routes to not be treated according to Long-lived BGP Graceful Restart operations"); else if (accept_own_nexthop) vty_out(vty, ", accept local nexthop"); else if (blackhole) vty_out(vty, ", inform peer to blackhole prefix"); else if (no_export) vty_out(vty, ", not advertised to EBGP peer"); else if (no_advertise) vty_out(vty, ", not advertised to any peer"); else if (local_as) vty_out(vty, ", not advertised outside local AS"); else if (no_peer) vty_out(vty, ", inform EBGP peer not to advertise to their EBGP peers"); if (suppress) vty_out(vty, ", Advertisements suppressed by an aggregate."); vty_out(vty, ")\n"); } /* If we are not using addpath then we can display Advertised to and * that will * show what peers we advertised the bestpath to. If we are using * addpath * though then we must display Advertised to on a path-by-path basis. */ if (!bgp_addpath_is_addpath_used(&bgp->tx_addpath, afi, safi)) { for (ALL_LIST_ELEMENTS(bgp->peer, node, nnode, peer)) { if (bgp_adj_out_lookup(peer, rn, 0)) { if (json && !json_adv_to) json_adv_to = json_object_new_object(); route_vty_out_advertised_to( vty, peer, &first, " Advertised to non peer-group peers:\n ", json_adv_to); } } if (json) { if (json_adv_to) { json_object_object_add(json, "advertisedTo", json_adv_to); } } else { if (first) vty_out(vty, " Not advertised to any peer"); vty_out(vty, "\n"); } } } /* Display specified route of BGP table. */ static int bgp_show_route_in_table(struct vty *vty, struct bgp *bgp, struct bgp_table *rib, const char *ip_str, afi_t afi, safi_t safi, struct prefix_rd *prd, int prefix_check, enum bgp_path_type pathtype, bool use_json) { int ret; int header; int display = 0; struct prefix match; struct bgp_node *rn; struct bgp_node *rm; struct bgp_path_info *pi; struct bgp_table *table; json_object *json = NULL; json_object *json_paths = NULL; /* Check IP address argument. */ ret = str2prefix(ip_str, &match); if (!ret) { vty_out(vty, "address is malformed\n"); return CMD_WARNING; } match.family = afi2family(afi); if (use_json) { json = json_object_new_object(); json_paths = json_object_new_array(); } if (safi == SAFI_MPLS_VPN || safi == SAFI_ENCAP || safi == SAFI_EVPN) { for (rn = bgp_table_top(rib); rn; rn = bgp_route_next(rn)) { if (prd && memcmp(rn->p.u.val, prd->val, 8) != 0) continue; table = bgp_node_get_bgp_table_info(rn); if (!table) continue; header = 1; if ((rm = bgp_node_match(table, &match)) == NULL) continue; if (prefix_check && rm->p.prefixlen != match.prefixlen) { bgp_unlock_node(rm); continue; } for (pi = bgp_node_get_bgp_path_info(rm); pi; pi = pi->next) { if (header) { route_vty_out_detail_header( vty, bgp, rm, (struct prefix_rd *)&rn->p, AFI_IP, safi, json); header = 0; } display++; if (pathtype == BGP_PATH_SHOW_ALL || (pathtype == BGP_PATH_SHOW_BESTPATH && CHECK_FLAG(pi->flags, BGP_PATH_SELECTED)) || (pathtype == BGP_PATH_SHOW_MULTIPATH && (CHECK_FLAG(pi->flags, BGP_PATH_MULTIPATH) || CHECK_FLAG(pi->flags, BGP_PATH_SELECTED)))) route_vty_out_detail(vty, bgp, rm, pi, AFI_IP, safi, json_paths); } bgp_unlock_node(rm); } } else if (safi == SAFI_FLOWSPEC) { display = bgp_flowspec_display_match_per_ip(afi, rib, &match, prefix_check, vty, use_json, json_paths); } else { header = 1; if ((rn = bgp_node_match(rib, &match)) != NULL) { if (!prefix_check || rn->p.prefixlen == match.prefixlen) { for (pi = bgp_node_get_bgp_path_info(rn); pi; pi = pi->next) { if (header) { route_vty_out_detail_header( vty, bgp, rn, NULL, afi, safi, json); header = 0; } display++; if (pathtype == BGP_PATH_SHOW_ALL || (pathtype == BGP_PATH_SHOW_BESTPATH && CHECK_FLAG( pi->flags, BGP_PATH_SELECTED)) || (pathtype == BGP_PATH_SHOW_MULTIPATH && (CHECK_FLAG( pi->flags, BGP_PATH_MULTIPATH) || CHECK_FLAG( pi->flags, BGP_PATH_SELECTED)))) route_vty_out_detail( vty, bgp, rn, pi, afi, safi, json_paths); } } bgp_unlock_node(rn); } } if (use_json) { if (display) json_object_object_add(json, "paths", json_paths); vty_out(vty, "%s\n", json_object_to_json_string_ext( json, JSON_C_TO_STRING_PRETTY)); json_object_free(json); } else { if (!display) { vty_out(vty, "%% Network not in table\n"); return CMD_WARNING; } } return CMD_SUCCESS; } /* Display specified route of Main RIB */ static int bgp_show_route(struct vty *vty, struct bgp *bgp, const char *ip_str, afi_t afi, safi_t safi, struct prefix_rd *prd, int prefix_check, enum bgp_path_type pathtype, bool use_json) { if (!bgp) { bgp = bgp_get_default(); if (!bgp) { if (!use_json) vty_out(vty, "No BGP process is configured\n"); else vty_out(vty, "{}\n"); return CMD_WARNING; } } /* labeled-unicast routes live in the unicast table */ if (safi == SAFI_LABELED_UNICAST) safi = SAFI_UNICAST; return bgp_show_route_in_table(vty, bgp, bgp->rib[afi][safi], ip_str, afi, safi, prd, prefix_check, pathtype, use_json); } static int bgp_show_lcommunity(struct vty *vty, struct bgp *bgp, int argc, struct cmd_token **argv, bool exact, afi_t afi, safi_t safi, bool uj) { struct lcommunity *lcom; struct buffer *b; int i; char *str; int first = 0; b = buffer_new(1024); for (i = 0; i < argc; i++) { if (first) buffer_putc(b, ' '); else { if (strmatch(argv[i]->text, "AA:BB:CC")) { first = 1; buffer_putstr(b, argv[i]->arg); } } } buffer_putc(b, '\0'); str = buffer_getstr(b); buffer_free(b); lcom = lcommunity_str2com(str); XFREE(MTYPE_TMP, str); if (!lcom) { vty_out(vty, "%% Large-community malformed\n"); return CMD_WARNING; } return bgp_show(vty, bgp, afi, safi, (exact ? bgp_show_type_lcommunity_exact : bgp_show_type_lcommunity), lcom, uj); } static int bgp_show_lcommunity_list(struct vty *vty, struct bgp *bgp, const char *lcom, bool exact, afi_t afi, safi_t safi, bool uj) { struct community_list *list; list = community_list_lookup(bgp_clist, lcom, 0, LARGE_COMMUNITY_LIST_MASTER); if (list == NULL) { vty_out(vty, "%% %s is not a valid large-community-list name\n", lcom); return CMD_WARNING; } return bgp_show(vty, bgp, afi, safi, (exact ? bgp_show_type_lcommunity_list_exact : bgp_show_type_lcommunity_list), list, uj); } DEFUN (show_ip_bgp_large_community_list, show_ip_bgp_large_community_list_cmd, "show [ip] bgp [ VIEWVRFNAME] ["BGP_AFI_CMD_STR" ["BGP_SAFI_WITH_LABEL_CMD_STR"]] large-community-list <(1-500)|WORD> [exact-match] [json]", SHOW_STR IP_STR BGP_STR BGP_INSTANCE_HELP_STR BGP_AFI_HELP_STR BGP_SAFI_WITH_LABEL_HELP_STR "Display routes matching the large-community-list\n" "large-community-list number\n" "large-community-list name\n" "Exact match of the large-communities\n" JSON_STR) { char *vrf = NULL; afi_t afi = AFI_IP6; safi_t safi = SAFI_UNICAST; int idx = 0; bool exact_match = 0; if (argv_find(argv, argc, "ip", &idx)) afi = AFI_IP; if (argv_find(argv, argc, "view", &idx) || argv_find(argv, argc, "vrf", &idx)) vrf = argv[++idx]->arg; if (argv_find(argv, argc, "ipv4", &idx) || argv_find(argv, argc, "ipv6", &idx)) { afi = strmatch(argv[idx]->text, "ipv6") ? AFI_IP6 : AFI_IP; if (argv_find(argv, argc, "unicast", &idx) || argv_find(argv, argc, "multicast", &idx)) safi = bgp_vty_safi_from_str(argv[idx]->text); } bool uj = use_json(argc, argv); struct bgp *bgp = bgp_lookup_by_name(vrf); if (bgp == NULL) { vty_out(vty, "Can't find BGP instance %s\n", vrf); return CMD_WARNING; } argv_find(argv, argc, "large-community-list", &idx); const char *clist_number_or_name = argv[++idx]->arg; if (++idx < argc && strmatch(argv[idx]->text, "exact-match")) exact_match = 1; return bgp_show_lcommunity_list(vty, bgp, clist_number_or_name, exact_match, afi, safi, uj); } DEFUN (show_ip_bgp_large_community, show_ip_bgp_large_community_cmd, "show [ip] bgp [ VIEWVRFNAME] ["BGP_AFI_CMD_STR" ["BGP_SAFI_WITH_LABEL_CMD_STR"]] large-community [ [exact-match]] [json]", SHOW_STR IP_STR BGP_STR BGP_INSTANCE_HELP_STR BGP_AFI_HELP_STR BGP_SAFI_WITH_LABEL_HELP_STR "Display routes matching the large-communities\n" "List of large-community numbers\n" "Exact match of the large-communities\n" JSON_STR) { char *vrf = NULL; afi_t afi = AFI_IP6; safi_t safi = SAFI_UNICAST; int idx = 0; bool exact_match = 0; if (argv_find(argv, argc, "ip", &idx)) afi = AFI_IP; if (argv_find(argv, argc, "view", &idx) || argv_find(argv, argc, "vrf", &idx)) vrf = argv[++idx]->arg; if (argv_find(argv, argc, "ipv4", &idx) || argv_find(argv, argc, "ipv6", &idx)) { afi = strmatch(argv[idx]->text, "ipv6") ? AFI_IP6 : AFI_IP; if (argv_find(argv, argc, "unicast", &idx) || argv_find(argv, argc, "multicast", &idx)) safi = bgp_vty_safi_from_str(argv[idx]->text); } bool uj = use_json(argc, argv); struct bgp *bgp = bgp_lookup_by_name(vrf); if (bgp == NULL) { vty_out(vty, "Can't find BGP instance %s\n", vrf); return CMD_WARNING; } if (argv_find(argv, argc, "AA:BB:CC", &idx)) { if (argv_find(argv, argc, "exact-match", &idx)) exact_match = 1; return bgp_show_lcommunity(vty, bgp, argc, argv, exact_match, afi, safi, uj); } else return bgp_show(vty, bgp, afi, safi, bgp_show_type_lcommunity_all, NULL, uj); } static int bgp_table_stats(struct vty *vty, struct bgp *bgp, afi_t afi, safi_t safi); /* BGP route print out function without JSON */ DEFUN (show_ip_bgp, show_ip_bgp_cmd, "show [ip] bgp [ VIEWVRFNAME] ["BGP_AFI_CMD_STR" ["BGP_SAFI_WITH_LABEL_CMD_STR"]]\ \ |route-map WORD\ |prefix-list WORD\ |filter-list WORD\ |statistics\ |community-list <(1-500)|WORD> [exact-match]\ |A.B.C.D/M longer-prefixes\ |X:X::X:X/M longer-prefixes\ >", SHOW_STR IP_STR BGP_STR BGP_INSTANCE_HELP_STR BGP_AFI_HELP_STR BGP_SAFI_WITH_LABEL_HELP_STR "Display detailed information about dampening\n" "Display detail of configured dampening parameters\n" "Display routes matching the route-map\n" "A route-map to match on\n" "Display routes conforming to the prefix-list\n" "Prefix-list name\n" "Display routes conforming to the filter-list\n" "Regular expression access list name\n" "BGP RIB advertisement statistics\n" "Display routes matching the community-list\n" "community-list number\n" "community-list name\n" "Exact match of the communities\n" "IPv4 prefix\n" "Display route and more specific routes\n" "IPv6 prefix\n" "Display route and more specific routes\n") { afi_t afi = AFI_IP6; safi_t safi = SAFI_UNICAST; int exact_match = 0; struct bgp *bgp = NULL; int idx = 0; bgp_vty_find_and_parse_afi_safi_bgp(vty, argv, argc, &idx, &afi, &safi, &bgp, false); if (!idx) return CMD_WARNING; if (argv_find(argv, argc, "dampening", &idx)) { if (argv_find(argv, argc, "parameters", &idx)) return bgp_show_dampening_parameters(vty, afi, safi); } if (argv_find(argv, argc, "prefix-list", &idx)) return bgp_show_prefix_list(vty, bgp, argv[idx + 1]->arg, afi, safi, bgp_show_type_prefix_list); if (argv_find(argv, argc, "filter-list", &idx)) return bgp_show_filter_list(vty, bgp, argv[idx + 1]->arg, afi, safi, bgp_show_type_filter_list); if (argv_find(argv, argc, "statistics", &idx)) return bgp_table_stats(vty, bgp, afi, safi); if (argv_find(argv, argc, "route-map", &idx)) return bgp_show_route_map(vty, bgp, argv[idx + 1]->arg, afi, safi, bgp_show_type_route_map); if (argv_find(argv, argc, "community-list", &idx)) { const char *clist_number_or_name = argv[++idx]->arg; if (++idx < argc && strmatch(argv[idx]->text, "exact-match")) exact_match = 1; return bgp_show_community_list(vty, bgp, clist_number_or_name, exact_match, afi, safi); } /* prefix-longer */ if (argv_find(argv, argc, "A.B.C.D/M", &idx) || argv_find(argv, argc, "X:X::X:X/M", &idx)) return bgp_show_prefix_longer(vty, bgp, argv[idx]->arg, afi, safi, bgp_show_type_prefix_longer); return CMD_WARNING; } /* BGP route print out function with JSON */ DEFUN (show_ip_bgp_json, show_ip_bgp_json_cmd, "show [ip] bgp [ VIEWVRFNAME] ["BGP_AFI_CMD_STR" ["BGP_SAFI_WITH_LABEL_CMD_STR"]]\ [cidr-only\ |dampening \ |community [AA:NN|local-AS|no-advertise|no-export\ |graceful-shutdown|no-peer|blackhole|llgr-stale|no-llgr\ |accept-own|accept-own-nexthop|route-filter-v6\ |route-filter-v4|route-filter-translated-v6\ |route-filter-translated-v4] [exact-match]\ ] [json]", SHOW_STR IP_STR BGP_STR BGP_INSTANCE_HELP_STR BGP_AFI_HELP_STR BGP_SAFI_WITH_LABEL_HELP_STR "Display only routes with non-natural netmasks\n" "Display detailed information about dampening\n" "Display flap statistics of routes\n" "Display paths suppressed due to dampening\n" "Display routes matching the communities\n" COMMUNITY_AANN_STR "Do not send outside local AS (well-known community)\n" "Do not advertise to any peer (well-known community)\n" "Do not export to next AS (well-known community)\n" "Graceful shutdown (well-known community)\n" "Do not export to any peer (well-known community)\n" "Inform EBGP peers to blackhole traffic to prefix (well-known community)\n" "Staled Long-lived Graceful Restart VPN route (well-known community)\n" "Removed because Long-lived Graceful Restart was not enabled for VPN route (well-known community)\n" "Should accept local VPN route if exported and imported into different VRF (well-known community)\n" "Should accept VPN route with local nexthop (well-known community)\n" "RT VPNv6 route filtering (well-known community)\n" "RT VPNv4 route filtering (well-known community)\n" "RT translated VPNv6 route filtering (well-known community)\n" "RT translated VPNv4 route filtering (well-known community)\n" "Exact match of the communities\n" JSON_STR) { afi_t afi = AFI_IP6; safi_t safi = SAFI_UNICAST; enum bgp_show_type sh_type = bgp_show_type_normal; struct bgp *bgp = NULL; int idx = 0; int exact_match = 0; bool uj = use_json(argc, argv); if (uj) argc--; bgp_vty_find_and_parse_afi_safi_bgp(vty, argv, argc, &idx, &afi, &safi, &bgp, uj); if (!idx) return CMD_WARNING; if (argv_find(argv, argc, "cidr-only", &idx)) return bgp_show(vty, bgp, afi, safi, bgp_show_type_cidr_only, NULL, uj); if (argv_find(argv, argc, "dampening", &idx)) { if (argv_find(argv, argc, "dampened-paths", &idx)) return bgp_show(vty, bgp, afi, safi, bgp_show_type_dampend_paths, NULL, uj); else if (argv_find(argv, argc, "flap-statistics", &idx)) return bgp_show(vty, bgp, afi, safi, bgp_show_type_flap_statistics, NULL, uj); } if (argv_find(argv, argc, "community", &idx)) { char *maybecomm = NULL; char *community = NULL; if (idx + 1 < argc) { if (argv[idx + 1]->type == VARIABLE_TKN) maybecomm = argv[idx + 1]->arg; else maybecomm = argv[idx + 1]->text; } if (maybecomm && !strmatch(maybecomm, "json") && !strmatch(maybecomm, "exact-match")) community = maybecomm; if (argv_find(argv, argc, "exact-match", &idx)) exact_match = 1; if (community) return bgp_show_community(vty, bgp, community, exact_match, afi, safi, uj); else return (bgp_show(vty, bgp, afi, safi, bgp_show_type_community_all, NULL, uj)); } return bgp_show(vty, bgp, afi, safi, sh_type, NULL, uj); } DEFUN (show_ip_bgp_route, show_ip_bgp_route_cmd, "show [ip] bgp [ VIEWVRFNAME] ["BGP_AFI_CMD_STR" ["BGP_SAFI_WITH_LABEL_CMD_STR"]]" " [] [json]", SHOW_STR IP_STR BGP_STR BGP_INSTANCE_HELP_STR BGP_AFI_HELP_STR BGP_SAFI_WITH_LABEL_HELP_STR "Network in the BGP routing table to display\n" "IPv4 prefix\n" "Network in the BGP routing table to display\n" "IPv6 prefix\n" "Display only the bestpath\n" "Display only multipaths\n" JSON_STR) { int prefix_check = 0; afi_t afi = AFI_IP6; safi_t safi = SAFI_UNICAST; char *prefix = NULL; struct bgp *bgp = NULL; enum bgp_path_type path_type; bool uj = use_json(argc, argv); int idx = 0; bgp_vty_find_and_parse_afi_safi_bgp(vty, argv, argc, &idx, &afi, &safi, &bgp, uj); if (!idx) return CMD_WARNING; if (!bgp) { vty_out(vty, "Specified 'all' vrf's but this command currently only works per view/vrf\n"); return CMD_WARNING; } /* */ if (argv_find(argv, argc, "A.B.C.D", &idx) || argv_find(argv, argc, "X:X::X:X", &idx)) prefix_check = 0; else if (argv_find(argv, argc, "A.B.C.D/M", &idx) || argv_find(argv, argc, "X:X::X:X/M", &idx)) prefix_check = 1; if ((argv[idx]->type == IPV6_TKN || argv[idx]->type == IPV6_PREFIX_TKN) && afi != AFI_IP6) { vty_out(vty, "%% Cannot specify IPv6 address or prefix with IPv4 AFI\n"); return CMD_WARNING; } if ((argv[idx]->type == IPV4_TKN || argv[idx]->type == IPV4_PREFIX_TKN) && afi != AFI_IP) { vty_out(vty, "%% Cannot specify IPv4 address or prefix with IPv6 AFI\n"); return CMD_WARNING; } prefix = argv[idx]->arg; /* [] */ if (argv_find(argv, argc, "bestpath", &idx)) path_type = BGP_PATH_SHOW_BESTPATH; else if (argv_find(argv, argc, "multipath", &idx)) path_type = BGP_PATH_SHOW_MULTIPATH; else path_type = BGP_PATH_SHOW_ALL; return bgp_show_route(vty, bgp, prefix, afi, safi, NULL, prefix_check, path_type, uj); } DEFUN (show_ip_bgp_regexp, show_ip_bgp_regexp_cmd, "show [ip] bgp [ VIEWVRFNAME] ["BGP_AFI_CMD_STR" ["BGP_SAFI_WITH_LABEL_CMD_STR"]] regexp REGEX...", SHOW_STR IP_STR BGP_STR BGP_INSTANCE_HELP_STR BGP_AFI_HELP_STR BGP_SAFI_WITH_LABEL_HELP_STR "Display routes matching the AS path regular expression\n" "A regular-expression (1234567890_^|[,{}() ]$*+.?-\\) to match the BGP AS paths\n") { afi_t afi = AFI_IP6; safi_t safi = SAFI_UNICAST; struct bgp *bgp = NULL; int idx = 0; bgp_vty_find_and_parse_afi_safi_bgp(vty, argv, argc, &idx, &afi, &safi, &bgp, false); if (!idx) return CMD_WARNING; // get index of regex argv_find(argv, argc, "regexp", &idx); idx++; char *regstr = argv_concat(argv, argc, idx); int rc = bgp_show_regexp(vty, bgp, (const char *)regstr, afi, safi, bgp_show_type_regexp); XFREE(MTYPE_TMP, regstr); return rc; } DEFUN (show_ip_bgp_instance_all, show_ip_bgp_instance_all_cmd, "show [ip] bgp all ["BGP_AFI_CMD_STR" ["BGP_SAFI_WITH_LABEL_CMD_STR"]] [json]", SHOW_STR IP_STR BGP_STR BGP_INSTANCE_ALL_HELP_STR BGP_AFI_HELP_STR BGP_SAFI_WITH_LABEL_HELP_STR JSON_STR) { afi_t afi = AFI_IP; safi_t safi = SAFI_UNICAST; struct bgp *bgp = NULL; int idx = 0; bool uj = use_json(argc, argv); if (uj) argc--; bgp_vty_find_and_parse_afi_safi_bgp(vty, argv, argc, &idx, &afi, &safi, &bgp, uj); if (!idx) return CMD_WARNING; bgp_show_all_instances_routes_vty(vty, afi, safi, uj); return CMD_SUCCESS; } static int bgp_show_regexp(struct vty *vty, struct bgp *bgp, const char *regstr, afi_t afi, safi_t safi, enum bgp_show_type type) { regex_t *regex; int rc; if (!config_bgp_aspath_validate(regstr)) { vty_out(vty, "Invalid character in as-path access-list %s\n", regstr); return CMD_WARNING_CONFIG_FAILED; } regex = bgp_regcomp(regstr); if (!regex) { vty_out(vty, "Can't compile regexp %s\n", regstr); return CMD_WARNING; } rc = bgp_show(vty, bgp, afi, safi, type, regex, 0); bgp_regex_free(regex); return rc; } static int bgp_show_prefix_list(struct vty *vty, struct bgp *bgp, const char *prefix_list_str, afi_t afi, safi_t safi, enum bgp_show_type type) { struct prefix_list *plist; plist = prefix_list_lookup(afi, prefix_list_str); if (plist == NULL) { vty_out(vty, "%% %s is not a valid prefix-list name\n", prefix_list_str); return CMD_WARNING; } return bgp_show(vty, bgp, afi, safi, type, plist, 0); } static int bgp_show_filter_list(struct vty *vty, struct bgp *bgp, const char *filter, afi_t afi, safi_t safi, enum bgp_show_type type) { struct as_list *as_list; as_list = as_list_lookup(filter); if (as_list == NULL) { vty_out(vty, "%% %s is not a valid AS-path access-list name\n", filter); return CMD_WARNING; } return bgp_show(vty, bgp, afi, safi, type, as_list, 0); } static int bgp_show_route_map(struct vty *vty, struct bgp *bgp, const char *rmap_str, afi_t afi, safi_t safi, enum bgp_show_type type) { struct route_map *rmap; rmap = route_map_lookup_by_name(rmap_str); if (!rmap) { vty_out(vty, "%% %s is not a valid route-map name\n", rmap_str); return CMD_WARNING; } return bgp_show(vty, bgp, afi, safi, type, rmap, 0); } static int bgp_show_community(struct vty *vty, struct bgp *bgp, const char *comstr, int exact, afi_t afi, safi_t safi, bool use_json) { struct community *com; int ret = 0; com = community_str2com(comstr); if (!com) { vty_out(vty, "%% Community malformed: %s\n", comstr); return CMD_WARNING; } ret = bgp_show(vty, bgp, afi, safi, (exact ? bgp_show_type_community_exact : bgp_show_type_community), com, use_json); community_free(&com); return ret; } static int bgp_show_community_list(struct vty *vty, struct bgp *bgp, const char *com, int exact, afi_t afi, safi_t safi) { struct community_list *list; list = community_list_lookup(bgp_clist, com, 0, COMMUNITY_LIST_MASTER); if (list == NULL) { vty_out(vty, "%% %s is not a valid community-list name\n", com); return CMD_WARNING; } return bgp_show(vty, bgp, afi, safi, (exact ? bgp_show_type_community_list_exact : bgp_show_type_community_list), list, 0); } static int bgp_show_prefix_longer(struct vty *vty, struct bgp *bgp, const char *prefix, afi_t afi, safi_t safi, enum bgp_show_type type) { int ret; struct prefix *p; p = prefix_new(); ret = str2prefix(prefix, p); if (!ret) { vty_out(vty, "%% Malformed Prefix\n"); return CMD_WARNING; } ret = bgp_show(vty, bgp, afi, safi, type, p, 0); prefix_free(p); return ret; } enum bgp_stats { BGP_STATS_MAXBITLEN = 0, BGP_STATS_RIB, BGP_STATS_PREFIXES, BGP_STATS_TOTPLEN, BGP_STATS_UNAGGREGATEABLE, BGP_STATS_MAX_AGGREGATEABLE, BGP_STATS_AGGREGATES, BGP_STATS_SPACE, BGP_STATS_ASPATH_COUNT, BGP_STATS_ASPATH_MAXHOPS, BGP_STATS_ASPATH_TOTHOPS, BGP_STATS_ASPATH_MAXSIZE, BGP_STATS_ASPATH_TOTSIZE, BGP_STATS_ASN_HIGHEST, BGP_STATS_MAX, }; static const char *table_stats_strs[] = { [BGP_STATS_PREFIXES] = "Total Prefixes", [BGP_STATS_TOTPLEN] = "Average prefix length", [BGP_STATS_RIB] = "Total Advertisements", [BGP_STATS_UNAGGREGATEABLE] = "Unaggregateable prefixes", [BGP_STATS_MAX_AGGREGATEABLE] = "Maximum aggregateable prefixes", [BGP_STATS_AGGREGATES] = "BGP Aggregate advertisements", [BGP_STATS_SPACE] = "Address space advertised", [BGP_STATS_ASPATH_COUNT] = "Advertisements with paths", [BGP_STATS_ASPATH_MAXHOPS] = "Longest AS-Path (hops)", [BGP_STATS_ASPATH_MAXSIZE] = "Largest AS-Path (bytes)", [BGP_STATS_ASPATH_TOTHOPS] = "Average AS-Path length (hops)", [BGP_STATS_ASPATH_TOTSIZE] = "Average AS-Path size (bytes)", [BGP_STATS_ASN_HIGHEST] = "Highest public ASN", [BGP_STATS_MAX] = NULL, }; struct bgp_table_stats { struct bgp_table *table; unsigned long long counts[BGP_STATS_MAX]; double total_space; }; #if 0 #define TALLY_SIGFIG 100000 static unsigned long ravg_tally (unsigned long count, unsigned long oldavg, unsigned long newval) { unsigned long newtot = (count-1) * oldavg + (newval * TALLY_SIGFIG); unsigned long res = (newtot * TALLY_SIGFIG) / count; unsigned long ret = newtot / count; if ((res % TALLY_SIGFIG) > (TALLY_SIGFIG/2)) return ret + 1; else return ret; } #endif static void bgp_table_stats_rn(struct bgp_node *rn, struct bgp_node *top, struct bgp_table_stats *ts, unsigned int space) { struct bgp_node *prn = bgp_node_parent_nolock(rn); struct bgp_path_info *pi; if (rn == top) return; if (!bgp_node_has_bgp_path_info_data(rn)) return; ts->counts[BGP_STATS_PREFIXES]++; ts->counts[BGP_STATS_TOTPLEN] += rn->p.prefixlen; #if 0 ts->counts[BGP_STATS_AVGPLEN] = ravg_tally (ts->counts[BGP_STATS_PREFIXES], ts->counts[BGP_STATS_AVGPLEN], rn->p.prefixlen); #endif /* check if the prefix is included by any other announcements */ while (prn && !bgp_node_has_bgp_path_info_data(prn)) prn = bgp_node_parent_nolock(prn); if (prn == NULL || prn == top) { ts->counts[BGP_STATS_UNAGGREGATEABLE]++; /* announced address space */ if (space) ts->total_space += pow(2.0, space - rn->p.prefixlen); } else if (bgp_node_has_bgp_path_info_data(prn)) ts->counts[BGP_STATS_MAX_AGGREGATEABLE]++; for (pi = bgp_node_get_bgp_path_info(rn); pi; pi = pi->next) { ts->counts[BGP_STATS_RIB]++; if (pi->attr && (CHECK_FLAG(pi->attr->flag, ATTR_FLAG_BIT(BGP_ATTR_ATOMIC_AGGREGATE)))) ts->counts[BGP_STATS_AGGREGATES]++; /* as-path stats */ if (pi->attr && pi->attr->aspath) { unsigned int hops = aspath_count_hops(pi->attr->aspath); unsigned int size = aspath_size(pi->attr->aspath); as_t highest = aspath_highest(pi->attr->aspath); ts->counts[BGP_STATS_ASPATH_COUNT]++; if (hops > ts->counts[BGP_STATS_ASPATH_MAXHOPS]) ts->counts[BGP_STATS_ASPATH_MAXHOPS] = hops; if (size > ts->counts[BGP_STATS_ASPATH_MAXSIZE]) ts->counts[BGP_STATS_ASPATH_MAXSIZE] = size; ts->counts[BGP_STATS_ASPATH_TOTHOPS] += hops; ts->counts[BGP_STATS_ASPATH_TOTSIZE] += size; #if 0 ts->counts[BGP_STATS_ASPATH_AVGHOPS] = ravg_tally (ts->counts[BGP_STATS_ASPATH_COUNT], ts->counts[BGP_STATS_ASPATH_AVGHOPS], hops); ts->counts[BGP_STATS_ASPATH_AVGSIZE] = ravg_tally (ts->counts[BGP_STATS_ASPATH_COUNT], ts->counts[BGP_STATS_ASPATH_AVGSIZE], size); #endif if (highest > ts->counts[BGP_STATS_ASN_HIGHEST]) ts->counts[BGP_STATS_ASN_HIGHEST] = highest; } } } static int bgp_table_stats_walker(struct thread *t) { struct bgp_node *rn, *nrn; struct bgp_node *top; struct bgp_table_stats *ts = THREAD_ARG(t); unsigned int space = 0; if (!(top = bgp_table_top(ts->table))) return 0; switch (ts->table->afi) { case AFI_IP: space = IPV4_MAX_BITLEN; break; case AFI_IP6: space = IPV6_MAX_BITLEN; break; default: return 0; } ts->counts[BGP_STATS_MAXBITLEN] = space; for (rn = top; rn; rn = bgp_route_next(rn)) { if (ts->table->safi == SAFI_MPLS_VPN) { struct bgp_table *table; table = bgp_node_get_bgp_table_info(rn); if (!table) continue; top = bgp_table_top(table); for (nrn = bgp_table_top(table); nrn; nrn = bgp_route_next(nrn)) bgp_table_stats_rn(nrn, top, ts, space); } else { bgp_table_stats_rn(rn, top, ts, space); } } return 0; } static int bgp_table_stats(struct vty *vty, struct bgp *bgp, afi_t afi, safi_t safi) { struct bgp_table_stats ts; unsigned int i; if (!bgp->rib[afi][safi]) { vty_out(vty, "%% No RIB exist's for the AFI(%d)/SAFI(%d)\n", afi, safi); return CMD_WARNING; } vty_out(vty, "BGP %s RIB statistics\n", get_afi_safi_str(afi, safi, false)); /* labeled-unicast routes live in the unicast table */ if (safi == SAFI_LABELED_UNICAST) safi = SAFI_UNICAST; memset(&ts, 0, sizeof(ts)); ts.table = bgp->rib[afi][safi]; thread_execute(bm->master, bgp_table_stats_walker, &ts, 0); for (i = 0; i < BGP_STATS_MAX; i++) { if (!table_stats_strs[i]) continue; switch (i) { #if 0 case BGP_STATS_ASPATH_AVGHOPS: case BGP_STATS_ASPATH_AVGSIZE: case BGP_STATS_AVGPLEN: vty_out (vty, "%-30s: ", table_stats_strs[i]); vty_out (vty, "%12.2f", (float)ts.counts[i] / (float)TALLY_SIGFIG); break; #endif case BGP_STATS_ASPATH_TOTHOPS: case BGP_STATS_ASPATH_TOTSIZE: vty_out(vty, "%-30s: ", table_stats_strs[i]); vty_out(vty, "%12.2f", ts.counts[i] ? (float)ts.counts[i] / (float)ts.counts [BGP_STATS_ASPATH_COUNT] : 0); break; case BGP_STATS_TOTPLEN: vty_out(vty, "%-30s: ", table_stats_strs[i]); vty_out(vty, "%12.2f", ts.counts[i] ? (float)ts.counts[i] / (float)ts.counts [BGP_STATS_PREFIXES] : 0); break; case BGP_STATS_SPACE: vty_out(vty, "%-30s: ", table_stats_strs[i]); vty_out(vty, "%12g\n", ts.total_space); if (afi == AFI_IP6) { vty_out(vty, "%30s: ", "/32 equivalent "); vty_out(vty, "%12g\n", ts.total_space * pow(2.0, -128 + 32)); vty_out(vty, "%30s: ", "/48 equivalent "); vty_out(vty, "%12g\n", ts.total_space * pow(2.0, -128 + 48)); } else { vty_out(vty, "%30s: ", "% announced "); vty_out(vty, "%12.2f\n", ts.total_space * 100. * pow(2.0, -32)); vty_out(vty, "%30s: ", "/8 equivalent "); vty_out(vty, "%12.2f\n", ts.total_space * pow(2.0, -32 + 8)); vty_out(vty, "%30s: ", "/24 equivalent "); vty_out(vty, "%12.2f\n", ts.total_space * pow(2.0, -32 + 24)); } break; default: vty_out(vty, "%-30s: ", table_stats_strs[i]); vty_out(vty, "%12llu", ts.counts[i]); } vty_out(vty, "\n"); } return CMD_SUCCESS; } enum bgp_pcounts { PCOUNT_ADJ_IN = 0, PCOUNT_DAMPED, PCOUNT_REMOVED, PCOUNT_HISTORY, PCOUNT_STALE, PCOUNT_VALID, PCOUNT_ALL, PCOUNT_COUNTED, PCOUNT_PFCNT, /* the figure we display to users */ PCOUNT_MAX, }; static const char *pcount_strs[] = { [PCOUNT_ADJ_IN] = "Adj-in", [PCOUNT_DAMPED] = "Damped", [PCOUNT_REMOVED] = "Removed", [PCOUNT_HISTORY] = "History", [PCOUNT_STALE] = "Stale", [PCOUNT_VALID] = "Valid", [PCOUNT_ALL] = "All RIB", [PCOUNT_COUNTED] = "PfxCt counted", [PCOUNT_PFCNT] = "Useable", [PCOUNT_MAX] = NULL, }; struct peer_pcounts { unsigned int count[PCOUNT_MAX]; const struct peer *peer; const struct bgp_table *table; }; static int bgp_peer_count_walker(struct thread *t) { struct bgp_node *rn; struct peer_pcounts *pc = THREAD_ARG(t); const struct peer *peer = pc->peer; for (rn = bgp_table_top(pc->table); rn; rn = bgp_route_next(rn)) { struct bgp_adj_in *ain; struct bgp_path_info *pi; for (ain = rn->adj_in; ain; ain = ain->next) if (ain->peer == peer) pc->count[PCOUNT_ADJ_IN]++; for (pi = bgp_node_get_bgp_path_info(rn); pi; pi = pi->next) { if (pi->peer != peer) continue; pc->count[PCOUNT_ALL]++; if (CHECK_FLAG(pi->flags, BGP_PATH_DAMPED)) pc->count[PCOUNT_DAMPED]++; if (CHECK_FLAG(pi->flags, BGP_PATH_HISTORY)) pc->count[PCOUNT_HISTORY]++; if (CHECK_FLAG(pi->flags, BGP_PATH_REMOVED)) pc->count[PCOUNT_REMOVED]++; if (CHECK_FLAG(pi->flags, BGP_PATH_STALE)) pc->count[PCOUNT_STALE]++; if (CHECK_FLAG(pi->flags, BGP_PATH_VALID)) pc->count[PCOUNT_VALID]++; if (!CHECK_FLAG(pi->flags, BGP_PATH_UNUSEABLE)) pc->count[PCOUNT_PFCNT]++; if (CHECK_FLAG(pi->flags, BGP_PATH_COUNTED)) { pc->count[PCOUNT_COUNTED]++; if (CHECK_FLAG(pi->flags, BGP_PATH_UNUSEABLE)) flog_err( EC_LIB_DEVELOPMENT, "Attempting to count but flags say it is unusable"); } else { if (!CHECK_FLAG(pi->flags, BGP_PATH_UNUSEABLE)) flog_err( EC_LIB_DEVELOPMENT, "Not counted but flags say we should"); } } } return 0; } static int bgp_peer_counts(struct vty *vty, struct peer *peer, afi_t afi, safi_t safi, bool use_json) { struct peer_pcounts pcounts = {.peer = peer}; unsigned int i; json_object *json = NULL; json_object *json_loop = NULL; if (use_json) { json = json_object_new_object(); json_loop = json_object_new_object(); } if (!peer || !peer->bgp || !peer->afc[afi][safi] || !peer->bgp->rib[afi][safi]) { if (use_json) { json_object_string_add( json, "warning", "No such neighbor or address family"); vty_out(vty, "%s\n", json_object_to_json_string(json)); json_object_free(json); } else vty_out(vty, "%% No such neighbor or address family\n"); return CMD_WARNING; } memset(&pcounts, 0, sizeof(pcounts)); pcounts.peer = peer; pcounts.table = peer->bgp->rib[afi][safi]; /* in-place call via thread subsystem so as to record execution time * stats for the thread-walk (i.e. ensure this can't be blamed on * on just vty_read()). */ thread_execute(bm->master, bgp_peer_count_walker, &pcounts, 0); if (use_json) { json_object_string_add(json, "prefixCountsFor", peer->host); json_object_string_add(json, "multiProtocol", get_afi_safi_str(afi, safi, true)); json_object_int_add(json, "pfxCounter", peer->pcount[afi][safi]); for (i = 0; i < PCOUNT_MAX; i++) json_object_int_add(json_loop, pcount_strs[i], pcounts.count[i]); json_object_object_add(json, "ribTableWalkCounters", json_loop); if (pcounts.count[PCOUNT_PFCNT] != peer->pcount[afi][safi]) { json_object_string_add(json, "pfxctDriftFor", peer->host); json_object_string_add( json, "recommended", "Please report this bug, with the above command output"); } vty_out(vty, "%s\n", json_object_to_json_string_ext( json, JSON_C_TO_STRING_PRETTY)); json_object_free(json); } else { if (peer->hostname && bgp_flag_check(peer->bgp, BGP_FLAG_SHOW_HOSTNAME)) { vty_out(vty, "Prefix counts for %s/%s, %s\n", peer->hostname, peer->host, get_afi_safi_str(afi, safi, false)); } else { vty_out(vty, "Prefix counts for %s, %s\n", peer->host, get_afi_safi_str(afi, safi, false)); } vty_out(vty, "PfxCt: %" PRIu32 "\n", peer->pcount[afi][safi]); vty_out(vty, "\nCounts from RIB table walk:\n\n"); for (i = 0; i < PCOUNT_MAX; i++) vty_out(vty, "%20s: %-10d\n", pcount_strs[i], pcounts.count[i]); if (pcounts.count[PCOUNT_PFCNT] != peer->pcount[afi][safi]) { vty_out(vty, "%s [pcount] PfxCt drift!\n", peer->host); vty_out(vty, "Please report this bug, with the above command output\n"); } } return CMD_SUCCESS; } DEFUN (show_ip_bgp_instance_neighbor_prefix_counts, show_ip_bgp_instance_neighbor_prefix_counts_cmd, "show [ip] bgp [ VIEWVRFNAME] ["BGP_AFI_CMD_STR" ["BGP_SAFI_CMD_STR"]] " "neighbors prefix-counts [json]", SHOW_STR IP_STR BGP_STR BGP_INSTANCE_HELP_STR BGP_AFI_HELP_STR BGP_SAFI_HELP_STR "Detailed information on TCP and BGP neighbor connections\n" "Neighbor to display information about\n" "Neighbor to display information about\n" "Neighbor on BGP configured interface\n" "Display detailed prefix count information\n" JSON_STR) { afi_t afi = AFI_IP6; safi_t safi = SAFI_UNICAST; struct peer *peer; int idx = 0; struct bgp *bgp = NULL; bool uj = use_json(argc, argv); if (uj) argc--; bgp_vty_find_and_parse_afi_safi_bgp(vty, argv, argc, &idx, &afi, &safi, &bgp, uj); if (!idx) return CMD_WARNING; argv_find(argv, argc, "neighbors", &idx); peer = peer_lookup_in_view(vty, bgp, argv[idx + 1]->arg, uj); if (!peer) return CMD_WARNING; return bgp_peer_counts(vty, peer, afi, safi, uj); } #ifdef KEEP_OLD_VPN_COMMANDS DEFUN (show_ip_bgp_vpn_neighbor_prefix_counts, show_ip_bgp_vpn_neighbor_prefix_counts_cmd, "show [ip] bgp all neighbors prefix-counts [json]", SHOW_STR IP_STR BGP_STR BGP_VPNVX_HELP_STR "Display information about all VPNv4 NLRIs\n" "Detailed information on TCP and BGP neighbor connections\n" "Neighbor to display information about\n" "Neighbor to display information about\n" "Neighbor on BGP configured interface\n" "Display detailed prefix count information\n" JSON_STR) { int idx_peer = 6; struct peer *peer; bool uj = use_json(argc, argv); peer = peer_lookup_in_view(vty, NULL, argv[idx_peer]->arg, uj); if (!peer) return CMD_WARNING; return bgp_peer_counts(vty, peer, AFI_IP, SAFI_MPLS_VPN, uj); } DEFUN (show_ip_bgp_vpn_all_route_prefix, show_ip_bgp_vpn_all_route_prefix_cmd, "show [ip] bgp all [json]", SHOW_STR IP_STR BGP_STR BGP_VPNVX_HELP_STR "Display information about all VPNv4 NLRIs\n" "Network in the BGP routing table to display\n" "Network in the BGP routing table to display\n" JSON_STR) { int idx = 0; char *network = NULL; struct bgp *bgp = bgp_get_default(); if (!bgp) { vty_out(vty, "Can't find default instance\n"); return CMD_WARNING; } if (argv_find(argv, argc, "A.B.C.D", &idx)) network = argv[idx]->arg; else if (argv_find(argv, argc, "A.B.C.D/M", &idx)) network = argv[idx]->arg; else { vty_out(vty, "Unable to figure out Network\n"); return CMD_WARNING; } return bgp_show_route(vty, bgp, network, AFI_IP, SAFI_MPLS_VPN, NULL, 0, BGP_PATH_SHOW_ALL, use_json(argc, argv)); } #endif /* KEEP_OLD_VPN_COMMANDS */ DEFUN (show_ip_bgp_l2vpn_evpn_all_route_prefix, show_ip_bgp_l2vpn_evpn_all_route_prefix_cmd, "show [ip] bgp l2vpn evpn all [json]", SHOW_STR IP_STR BGP_STR L2VPN_HELP_STR EVPN_HELP_STR "Display information about all EVPN NLRIs\n" "Network in the BGP routing table to display\n" "Network in the BGP routing table to display\n" JSON_STR) { int idx = 0; char *network = NULL; if (argv_find(argv, argc, "A.B.C.D", &idx)) network = argv[idx]->arg; else if (argv_find(argv, argc, "A.B.C.D/M", &idx)) network = argv[idx]->arg; else { vty_out(vty, "Unable to figure out Network\n"); return CMD_WARNING; } return bgp_show_route(vty, NULL, network, AFI_L2VPN, SAFI_EVPN, NULL, 0, BGP_PATH_SHOW_ALL, use_json(argc, argv)); } static void show_adj_route(struct vty *vty, struct peer *peer, afi_t afi, safi_t safi, enum bgp_show_adj_route_type type, const char *rmap_name, bool use_json, json_object *json) { struct bgp_table *table; struct bgp_adj_in *ain; struct bgp_adj_out *adj; unsigned long output_count; unsigned long filtered_count; struct bgp_node *rn; int header1 = 1; struct bgp *bgp; int header2 = 1; struct attr attr; int ret; struct update_subgroup *subgrp; json_object *json_scode = NULL; json_object *json_ocode = NULL; json_object *json_ar = NULL; struct peer_af *paf; bool route_filtered; if (use_json) { json_scode = json_object_new_object(); json_ocode = json_object_new_object(); json_ar = json_object_new_object(); json_object_string_add(json_scode, "suppressed", "s"); json_object_string_add(json_scode, "damped", "d"); json_object_string_add(json_scode, "history", "h"); json_object_string_add(json_scode, "valid", "*"); json_object_string_add(json_scode, "best", ">"); json_object_string_add(json_scode, "multipath", "="); json_object_string_add(json_scode, "internal", "i"); json_object_string_add(json_scode, "ribFailure", "r"); json_object_string_add(json_scode, "stale", "S"); json_object_string_add(json_scode, "removed", "R"); json_object_string_add(json_ocode, "igp", "i"); json_object_string_add(json_ocode, "egp", "e"); json_object_string_add(json_ocode, "incomplete", "?"); } bgp = peer->bgp; if (!bgp) { if (use_json) { json_object_string_add(json, "alert", "no BGP"); vty_out(vty, "%s\n", json_object_to_json_string(json)); json_object_free(json); } else vty_out(vty, "%% No bgp\n"); return; } /* labeled-unicast routes live in the unicast table */ if (safi == SAFI_LABELED_UNICAST) table = bgp->rib[afi][SAFI_UNICAST]; else table = bgp->rib[afi][safi]; output_count = filtered_count = 0; subgrp = peer_subgroup(peer, afi, safi); if (type == bgp_show_adj_route_advertised && subgrp && CHECK_FLAG(subgrp->sflags, SUBGRP_STATUS_DEFAULT_ORIGINATE)) { if (use_json) { json_object_int_add(json, "bgpTableVersion", table->version); json_object_string_add(json, "bgpLocalRouterId", inet_ntoa(bgp->router_id)); json_object_int_add(json, "defaultLocPrf", bgp->default_local_pref); json_object_int_add(json, "localAS", bgp->as); json_object_object_add(json, "bgpStatusCodes", json_scode); json_object_object_add(json, "bgpOriginCodes", json_ocode); json_object_string_add( json, "bgpOriginatingDefaultNetwork", (afi == AFI_IP) ? "0.0.0.0/0" : "::/0"); } else { vty_out(vty, "BGP table version is %" PRIu64 ", local router ID is %s, vrf id ", table->version, inet_ntoa(bgp->router_id)); if (bgp->vrf_id == VRF_UNKNOWN) vty_out(vty, "%s", VRFID_NONE_STR); else vty_out(vty, "%u", bgp->vrf_id); vty_out(vty, "\n"); vty_out(vty, "Default local pref %u, ", bgp->default_local_pref); vty_out(vty, "local AS %u\n", bgp->as); vty_out(vty, BGP_SHOW_SCODE_HEADER); vty_out(vty, BGP_SHOW_NCODE_HEADER); vty_out(vty, BGP_SHOW_OCODE_HEADER); vty_out(vty, "Originating default network %s\n\n", (afi == AFI_IP) ? "0.0.0.0/0" : "::/0"); } header1 = 0; } for (rn = bgp_table_top(table); rn; rn = bgp_route_next(rn)) { if (type == bgp_show_adj_route_received || type == bgp_show_adj_route_filtered) { for (ain = rn->adj_in; ain; ain = ain->next) { if (ain->peer != peer || !ain->attr) continue; if (header1) { if (use_json) { json_object_int_add( json, "bgpTableVersion", 0); json_object_string_add( json, "bgpLocalRouterId", inet_ntoa( bgp->router_id)); json_object_int_add(json, "defaultLocPrf", bgp->default_local_pref); json_object_int_add(json, "localAS", bgp->as); json_object_object_add( json, "bgpStatusCodes", json_scode); json_object_object_add( json, "bgpOriginCodes", json_ocode); } else { vty_out(vty, "BGP table version is 0, local router ID is %s, vrf id ", inet_ntoa( bgp->router_id)); if (bgp->vrf_id == VRF_UNKNOWN) vty_out(vty, "%s", VRFID_NONE_STR); else vty_out(vty, "%u", bgp->vrf_id); vty_out(vty, "\n"); vty_out(vty, "Default local pref %u, ", bgp->default_local_pref); vty_out(vty, "local AS %u\n", bgp->as); vty_out(vty, BGP_SHOW_SCODE_HEADER); vty_out(vty, BGP_SHOW_NCODE_HEADER); vty_out(vty, BGP_SHOW_OCODE_HEADER); } header1 = 0; } if (header2) { if (!use_json) vty_out(vty, BGP_SHOW_HEADER); header2 = 0; } attr = *ain->attr; route_filtered = false; /* Filter prefix using distribute list, * filter list or prefix list */ if ((bgp_input_filter(peer, &rn->p, &attr, afi, safi)) == FILTER_DENY) route_filtered = true; /* Filter prefix using route-map */ ret = bgp_input_modifier(peer, &rn->p, &attr, afi, safi, rmap_name, NULL, 0); if (type == bgp_show_adj_route_filtered && !route_filtered && ret != RMAP_DENY) { bgp_attr_undup(&attr, ain->attr); continue; } if (type == bgp_show_adj_route_received && (route_filtered || ret == RMAP_DENY)) filtered_count++; route_vty_out_tmp(vty, &rn->p, &attr, safi, use_json, json_ar); bgp_attr_undup(&attr, ain->attr); output_count++; } } else if (type == bgp_show_adj_route_advertised) { RB_FOREACH (adj, bgp_adj_out_rb, &rn->adj_out) SUBGRP_FOREACH_PEER (adj->subgroup, paf) { if (paf->peer != peer || !adj->attr) continue; if (header1) { if (use_json) { json_object_int_add( json, "bgpTableVersion", table->version); json_object_string_add( json, "bgpLocalRouterId", inet_ntoa( bgp->router_id)); json_object_int_add( json, "defaultLocPrf", bgp->default_local_pref ); json_object_int_add( json, "localAS", bgp->as); json_object_object_add( json, "bgpStatusCodes", json_scode); json_object_object_add( json, "bgpOriginCodes", json_ocode); } else { vty_out(vty, "BGP table version is %" PRIu64 ", local router ID is %s, vrf id ", table->version, inet_ntoa( bgp->router_id)); if (bgp->vrf_id == VRF_UNKNOWN) vty_out(vty, "%s", VRFID_NONE_STR); else vty_out(vty, "%u", bgp->vrf_id); vty_out(vty, "\n"); vty_out(vty, "Default local pref %u, ", bgp->default_local_pref ); vty_out(vty, "local AS %u\n", bgp->as); vty_out(vty, BGP_SHOW_SCODE_HEADER); vty_out(vty, BGP_SHOW_NCODE_HEADER); vty_out(vty, BGP_SHOW_OCODE_HEADER); } header1 = 0; } if (header2) { if (!use_json) vty_out(vty, BGP_SHOW_HEADER); header2 = 0; } attr = *adj->attr; ret = bgp_output_modifier( peer, &rn->p, &attr, afi, safi, rmap_name); if (ret != RMAP_DENY) { route_vty_out_tmp(vty, &rn->p, &attr, safi, use_json, json_ar); output_count++; } else { filtered_count++; } bgp_attr_undup(&attr, adj->attr); } } } if (use_json) { json_object_object_add(json, "advertisedRoutes", json_ar); json_object_int_add(json, "totalPrefixCounter", output_count); json_object_int_add(json, "filteredPrefixCounter", filtered_count); vty_out(vty, "%s\n", json_object_to_json_string_ext( json, JSON_C_TO_STRING_PRETTY)); json_object_free(json); } else if (output_count > 0) { if (filtered_count > 0) vty_out(vty, "\nTotal number of prefixes %ld (%ld filtered)\n", output_count, filtered_count); else vty_out(vty, "\nTotal number of prefixes %ld\n", output_count); } } static int peer_adj_routes(struct vty *vty, struct peer *peer, afi_t afi, safi_t safi, enum bgp_show_adj_route_type type, const char *rmap_name, bool use_json) { json_object *json = NULL; if (use_json) json = json_object_new_object(); if (!peer || !peer->afc[afi][safi]) { if (use_json) { json_object_string_add( json, "warning", "No such neighbor or address family"); vty_out(vty, "%s\n", json_object_to_json_string(json)); json_object_free(json); } else vty_out(vty, "%% No such neighbor or address family\n"); return CMD_WARNING; } if ((type == bgp_show_adj_route_received || type == bgp_show_adj_route_filtered) && !CHECK_FLAG(peer->af_flags[afi][safi], PEER_FLAG_SOFT_RECONFIG)) { if (use_json) { json_object_string_add( json, "warning", "Inbound soft reconfiguration not enabled"); vty_out(vty, "%s\n", json_object_to_json_string(json)); json_object_free(json); } else vty_out(vty, "%% Inbound soft reconfiguration not enabled\n"); return CMD_WARNING; } show_adj_route(vty, peer, afi, safi, type, rmap_name, use_json, json); return CMD_SUCCESS; } DEFUN (show_ip_bgp_instance_neighbor_advertised_route, show_ip_bgp_instance_neighbor_advertised_route_cmd, "show [ip] bgp [ VIEWVRFNAME] ["BGP_AFI_CMD_STR" ["BGP_SAFI_WITH_LABEL_CMD_STR"]] " "neighbors [route-map WORD] [json]", SHOW_STR IP_STR BGP_STR BGP_INSTANCE_HELP_STR BGP_AFI_HELP_STR BGP_SAFI_WITH_LABEL_HELP_STR "Detailed information on TCP and BGP neighbor connections\n" "Neighbor to display information about\n" "Neighbor to display information about\n" "Neighbor on BGP configured interface\n" "Display the routes advertised to a BGP neighbor\n" "Display the received routes from neighbor\n" "Display the filtered routes received from neighbor\n" "Route-map to modify the attributes\n" "Name of the route map\n" JSON_STR) { afi_t afi = AFI_IP6; safi_t safi = SAFI_UNICAST; char *rmap_name = NULL; char *peerstr = NULL; struct bgp *bgp = NULL; struct peer *peer; enum bgp_show_adj_route_type type = bgp_show_adj_route_advertised; int idx = 0; bool uj = use_json(argc, argv); if (uj) argc--; bgp_vty_find_and_parse_afi_safi_bgp(vty, argv, argc, &idx, &afi, &safi, &bgp, uj); if (!idx) return CMD_WARNING; /* neighbors */ argv_find(argv, argc, "neighbors", &idx); peerstr = argv[++idx]->arg; peer = peer_lookup_in_view(vty, bgp, peerstr, uj); if (!peer) return CMD_WARNING; if (argv_find(argv, argc, "advertised-routes", &idx)) type = bgp_show_adj_route_advertised; else if (argv_find(argv, argc, "received-routes", &idx)) type = bgp_show_adj_route_received; else if (argv_find(argv, argc, "filtered-routes", &idx)) type = bgp_show_adj_route_filtered; if (argv_find(argv, argc, "route-map", &idx)) rmap_name = argv[++idx]->arg; return peer_adj_routes(vty, peer, afi, safi, type, rmap_name, uj); } DEFUN (show_ip_bgp_neighbor_received_prefix_filter, show_ip_bgp_neighbor_received_prefix_filter_cmd, "show [ip] bgp [ [unicast]] neighbors received prefix-filter [json]", SHOW_STR IP_STR BGP_STR "Address Family\n" "Address Family\n" "Address Family modifier\n" "Detailed information on TCP and BGP neighbor connections\n" "Neighbor to display information about\n" "Neighbor to display information about\n" "Neighbor on BGP configured interface\n" "Display information received from a BGP neighbor\n" "Display the prefixlist filter\n" JSON_STR) { afi_t afi = AFI_IP6; safi_t safi = SAFI_UNICAST; char *peerstr = NULL; char name[BUFSIZ]; union sockunion su; struct peer *peer; int count, ret; int idx = 0; /* show [ip] bgp */ if (argv_find(argv, argc, "ip", &idx)) afi = AFI_IP; /* [ [unicast]] */ if (argv_find(argv, argc, "ipv4", &idx)) afi = AFI_IP; if (argv_find(argv, argc, "ipv6", &idx)) afi = AFI_IP6; /* neighbors */ argv_find(argv, argc, "neighbors", &idx); peerstr = argv[++idx]->arg; bool uj = use_json(argc, argv); ret = str2sockunion(peerstr, &su); if (ret < 0) { peer = peer_lookup_by_conf_if(NULL, peerstr); if (!peer) { if (uj) vty_out(vty, "{}\n"); else vty_out(vty, "%% Malformed address or name: %s\n", peerstr); return CMD_WARNING; } } else { peer = peer_lookup(NULL, &su); if (!peer) { if (uj) vty_out(vty, "{}\n"); else vty_out(vty, "No peer\n"); return CMD_WARNING; } } sprintf(name, "%s.%d.%d", peer->host, afi, safi); count = prefix_bgp_show_prefix_list(NULL, afi, name, uj); if (count) { if (!uj) vty_out(vty, "Address Family: %s\n", get_afi_safi_str(afi, safi, false)); prefix_bgp_show_prefix_list(vty, afi, name, uj); } else { if (uj) vty_out(vty, "{}\n"); else vty_out(vty, "No functional output\n"); } return CMD_SUCCESS; } static int bgp_show_neighbor_route(struct vty *vty, struct peer *peer, afi_t afi, safi_t safi, enum bgp_show_type type, bool use_json) { /* labeled-unicast routes live in the unicast table */ if (safi == SAFI_LABELED_UNICAST) safi = SAFI_UNICAST; if (!peer || !peer->afc[afi][safi]) { if (use_json) { json_object *json_no = NULL; json_no = json_object_new_object(); json_object_string_add( json_no, "warning", "No such neighbor or address family"); vty_out(vty, "%s\n", json_object_to_json_string(json_no)); json_object_free(json_no); } else vty_out(vty, "%% No such neighbor or address family\n"); return CMD_WARNING; } return bgp_show(vty, peer->bgp, afi, safi, type, &peer->su, use_json); } DEFUN (show_ip_bgp_flowspec_routes_detailed, show_ip_bgp_flowspec_routes_detailed_cmd, "show [ip] bgp [ VIEWVRFNAME] ["BGP_AFI_CMD_STR" flowspec] detail [json]", SHOW_STR IP_STR BGP_STR BGP_INSTANCE_HELP_STR BGP_AFI_HELP_STR "SAFI Flowspec\n" "Detailed information on flowspec entries\n" JSON_STR) { afi_t afi = AFI_IP; safi_t safi = SAFI_UNICAST; struct bgp *bgp = NULL; int idx = 0; bool uj = use_json(argc, argv); if (uj) argc--; bgp_vty_find_and_parse_afi_safi_bgp(vty, argv, argc, &idx, &afi, &safi, &bgp, uj); if (!idx) return CMD_WARNING; return bgp_show(vty, bgp, afi, safi, bgp_show_type_detail, NULL, uj); } DEFUN (show_ip_bgp_neighbor_routes, show_ip_bgp_neighbor_routes_cmd, "show [ip] bgp [ VIEWVRFNAME] ["BGP_AFI_CMD_STR" ["BGP_SAFI_WITH_LABEL_CMD_STR"]] " "neighbors [json]", SHOW_STR IP_STR BGP_STR BGP_INSTANCE_HELP_STR BGP_AFI_HELP_STR BGP_SAFI_WITH_LABEL_HELP_STR "Detailed information on TCP and BGP neighbor connections\n" "Neighbor to display information about\n" "Neighbor to display information about\n" "Neighbor on BGP configured interface\n" "Display flap statistics of the routes learned from neighbor\n" "Display the dampened routes received from neighbor\n" "Display routes learned from neighbor\n" JSON_STR) { char *peerstr = NULL; struct bgp *bgp = NULL; afi_t afi = AFI_IP6; safi_t safi = SAFI_UNICAST; struct peer *peer; enum bgp_show_type sh_type = bgp_show_type_neighbor; int idx = 0; bool uj = use_json(argc, argv); if (uj) argc--; bgp_vty_find_and_parse_afi_safi_bgp(vty, argv, argc, &idx, &afi, &safi, &bgp, uj); if (!idx) return CMD_WARNING; /* neighbors */ argv_find(argv, argc, "neighbors", &idx); peerstr = argv[++idx]->arg; peer = peer_lookup_in_view(vty, bgp, peerstr, uj); if (!peer) return CMD_WARNING; if (argv_find(argv, argc, "flap-statistics", &idx)) sh_type = bgp_show_type_flap_neighbor; else if (argv_find(argv, argc, "dampened-routes", &idx)) sh_type = bgp_show_type_damp_neighbor; else if (argv_find(argv, argc, "routes", &idx)) sh_type = bgp_show_type_neighbor; return bgp_show_neighbor_route(vty, peer, afi, safi, sh_type, uj); } struct bgp_table *bgp_distance_table[AFI_MAX][SAFI_MAX]; struct bgp_distance { /* Distance value for the IP source prefix. */ uint8_t distance; /* Name of the access-list to be matched. */ char *access_list; }; DEFUN (show_bgp_afi_vpn_rd_route, show_bgp_afi_vpn_rd_route_cmd, "show bgp "BGP_AFI_CMD_STR" vpn rd ASN:NN_OR_IP-ADDRESS:NN [json]", SHOW_STR BGP_STR BGP_AFI_HELP_STR "Address Family modifier\n" "Display information for a route distinguisher\n" "Route Distinguisher\n" "Network in the BGP routing table to display\n" "Network in the BGP routing table to display\n" JSON_STR) { int ret; struct prefix_rd prd; afi_t afi = AFI_MAX; int idx = 0; if (!argv_find_and_parse_afi(argv, argc, &idx, &afi)) { vty_out(vty, "%% Malformed Address Family\n"); return CMD_WARNING; } ret = str2prefix_rd(argv[5]->arg, &prd); if (!ret) { vty_out(vty, "%% Malformed Route Distinguisher\n"); return CMD_WARNING; } return bgp_show_route(vty, NULL, argv[6]->arg, afi, SAFI_MPLS_VPN, &prd, 0, BGP_PATH_SHOW_ALL, use_json(argc, argv)); } static struct bgp_distance *bgp_distance_new(void) { return XCALLOC(MTYPE_BGP_DISTANCE, sizeof(struct bgp_distance)); } static void bgp_distance_free(struct bgp_distance *bdistance) { XFREE(MTYPE_BGP_DISTANCE, bdistance); } static int bgp_distance_set(struct vty *vty, const char *distance_str, const char *ip_str, const char *access_list_str) { int ret; afi_t afi; safi_t safi; struct prefix p; uint8_t distance; struct bgp_node *rn; struct bgp_distance *bdistance; afi = bgp_node_afi(vty); safi = bgp_node_safi(vty); ret = str2prefix(ip_str, &p); if (ret == 0) { vty_out(vty, "Malformed prefix\n"); return CMD_WARNING_CONFIG_FAILED; } distance = atoi(distance_str); /* Get BGP distance node. */ rn = bgp_node_get(bgp_distance_table[afi][safi], (struct prefix *)&p); bdistance = bgp_node_get_bgp_distance_info(rn); if (bdistance) bgp_unlock_node(rn); else { bdistance = bgp_distance_new(); bgp_node_set_bgp_distance_info(rn, bdistance); } /* Set distance value. */ bdistance->distance = distance; /* Reset access-list configuration. */ if (bdistance->access_list) { XFREE(MTYPE_AS_LIST, bdistance->access_list); bdistance->access_list = NULL; } if (access_list_str) bdistance->access_list = XSTRDUP(MTYPE_AS_LIST, access_list_str); return CMD_SUCCESS; } static int bgp_distance_unset(struct vty *vty, const char *distance_str, const char *ip_str, const char *access_list_str) { int ret; afi_t afi; safi_t safi; struct prefix p; int distance; struct bgp_node *rn; struct bgp_distance *bdistance; afi = bgp_node_afi(vty); safi = bgp_node_safi(vty); ret = str2prefix(ip_str, &p); if (ret == 0) { vty_out(vty, "Malformed prefix\n"); return CMD_WARNING_CONFIG_FAILED; } rn = bgp_node_lookup(bgp_distance_table[afi][safi], (struct prefix *)&p); if (!rn) { vty_out(vty, "Can't find specified prefix\n"); return CMD_WARNING_CONFIG_FAILED; } bdistance = bgp_node_get_bgp_distance_info(rn); distance = atoi(distance_str); if (bdistance->distance != distance) { vty_out(vty, "Distance does not match configured\n"); return CMD_WARNING_CONFIG_FAILED; } XFREE(MTYPE_AS_LIST, bdistance->access_list); bgp_distance_free(bdistance); bgp_node_set_bgp_path_info(rn, NULL); bgp_unlock_node(rn); bgp_unlock_node(rn); return CMD_SUCCESS; } /* Apply BGP information to distance method. */ uint8_t bgp_distance_apply(struct prefix *p, struct bgp_path_info *pinfo, afi_t afi, safi_t safi, struct bgp *bgp) { struct bgp_node *rn; struct prefix q; struct peer *peer; struct bgp_distance *bdistance; struct access_list *alist; struct bgp_static *bgp_static; if (!bgp) return 0; peer = pinfo->peer; /* Check source address. */ sockunion2hostprefix(&peer->su, &q); rn = bgp_node_match(bgp_distance_table[afi][safi], &q); if (rn) { bdistance = bgp_node_get_bgp_distance_info(rn); bgp_unlock_node(rn); if (bdistance->access_list) { alist = access_list_lookup(afi, bdistance->access_list); if (alist && access_list_apply(alist, p) == FILTER_PERMIT) return bdistance->distance; } else return bdistance->distance; } /* Backdoor check. */ rn = bgp_node_lookup(bgp->route[afi][safi], p); if (rn) { bgp_static = bgp_node_get_bgp_static_info(rn); bgp_unlock_node(rn); if (bgp_static->backdoor) { if (bgp->distance_local[afi][safi]) return bgp->distance_local[afi][safi]; else return ZEBRA_IBGP_DISTANCE_DEFAULT; } } if (peer->sort == BGP_PEER_EBGP) { if (bgp->distance_ebgp[afi][safi]) return bgp->distance_ebgp[afi][safi]; return ZEBRA_EBGP_DISTANCE_DEFAULT; } else { if (bgp->distance_ibgp[afi][safi]) return bgp->distance_ibgp[afi][safi]; return ZEBRA_IBGP_DISTANCE_DEFAULT; } } /* If we enter `distance bgp (1-255) (1-255) (1-255)`, * we should tell ZEBRA update the routes for a specific * AFI/SAFI to reflect changes in RIB. */ static void bgp_announce_routes_distance_update(struct bgp *bgp, afi_t update_afi, safi_t update_safi) { afi_t afi; safi_t safi; FOREACH_AFI_SAFI (afi, safi) { if (!bgp_fibupd_safi(safi)) continue; if (afi != update_afi && safi != update_safi) continue; if (BGP_DEBUG(zebra, ZEBRA)) zlog_debug( "%s: Announcing routes due to distance change afi/safi (%d/%d)", __func__, afi, safi); bgp_zebra_announce_table(bgp, afi, safi); } } DEFUN (bgp_distance, bgp_distance_cmd, "distance bgp (1-255) (1-255) (1-255)", "Define an administrative distance\n" "BGP distance\n" "Distance for routes external to the AS\n" "Distance for routes internal to the AS\n" "Distance for local routes\n") { VTY_DECLVAR_CONTEXT(bgp, bgp); int idx_number = 2; int idx_number_2 = 3; int idx_number_3 = 4; int distance_ebgp = atoi(argv[idx_number]->arg); int distance_ibgp = atoi(argv[idx_number_2]->arg); int distance_local = atoi(argv[idx_number_3]->arg); afi_t afi; safi_t safi; afi = bgp_node_afi(vty); safi = bgp_node_safi(vty); if (bgp->distance_ebgp[afi][safi] != distance_ebgp || bgp->distance_ibgp[afi][safi] != distance_ibgp || bgp->distance_local[afi][safi] != distance_local) { bgp->distance_ebgp[afi][safi] = distance_ebgp; bgp->distance_ibgp[afi][safi] = distance_ibgp; bgp->distance_local[afi][safi] = distance_local; bgp_announce_routes_distance_update(bgp, afi, safi); } return CMD_SUCCESS; } DEFUN (no_bgp_distance, no_bgp_distance_cmd, "no distance bgp [(1-255) (1-255) (1-255)]", NO_STR "Define an administrative distance\n" "BGP distance\n" "Distance for routes external to the AS\n" "Distance for routes internal to the AS\n" "Distance for local routes\n") { VTY_DECLVAR_CONTEXT(bgp, bgp); afi_t afi; safi_t safi; afi = bgp_node_afi(vty); safi = bgp_node_safi(vty); if (bgp->distance_ebgp[afi][safi] != 0 || bgp->distance_ibgp[afi][safi] != 0 || bgp->distance_local[afi][safi] != 0) { bgp->distance_ebgp[afi][safi] = 0; bgp->distance_ibgp[afi][safi] = 0; bgp->distance_local[afi][safi] = 0; bgp_announce_routes_distance_update(bgp, afi, safi); } return CMD_SUCCESS; } DEFUN (bgp_distance_source, bgp_distance_source_cmd, "distance (1-255) A.B.C.D/M", "Define an administrative distance\n" "Administrative distance\n" "IP source prefix\n") { int idx_number = 1; int idx_ipv4_prefixlen = 2; bgp_distance_set(vty, argv[idx_number]->arg, argv[idx_ipv4_prefixlen]->arg, NULL); return CMD_SUCCESS; } DEFUN (no_bgp_distance_source, no_bgp_distance_source_cmd, "no distance (1-255) A.B.C.D/M", NO_STR "Define an administrative distance\n" "Administrative distance\n" "IP source prefix\n") { int idx_number = 2; int idx_ipv4_prefixlen = 3; bgp_distance_unset(vty, argv[idx_number]->arg, argv[idx_ipv4_prefixlen]->arg, NULL); return CMD_SUCCESS; } DEFUN (bgp_distance_source_access_list, bgp_distance_source_access_list_cmd, "distance (1-255) A.B.C.D/M WORD", "Define an administrative distance\n" "Administrative distance\n" "IP source prefix\n" "Access list name\n") { int idx_number = 1; int idx_ipv4_prefixlen = 2; int idx_word = 3; bgp_distance_set(vty, argv[idx_number]->arg, argv[idx_ipv4_prefixlen]->arg, argv[idx_word]->arg); return CMD_SUCCESS; } DEFUN (no_bgp_distance_source_access_list, no_bgp_distance_source_access_list_cmd, "no distance (1-255) A.B.C.D/M WORD", NO_STR "Define an administrative distance\n" "Administrative distance\n" "IP source prefix\n" "Access list name\n") { int idx_number = 2; int idx_ipv4_prefixlen = 3; int idx_word = 4; bgp_distance_unset(vty, argv[idx_number]->arg, argv[idx_ipv4_prefixlen]->arg, argv[idx_word]->arg); return CMD_SUCCESS; } DEFUN (ipv6_bgp_distance_source, ipv6_bgp_distance_source_cmd, "distance (1-255) X:X::X:X/M", "Define an administrative distance\n" "Administrative distance\n" "IP source prefix\n") { bgp_distance_set(vty, argv[1]->arg, argv[2]->arg, NULL); return CMD_SUCCESS; } DEFUN (no_ipv6_bgp_distance_source, no_ipv6_bgp_distance_source_cmd, "no distance (1-255) X:X::X:X/M", NO_STR "Define an administrative distance\n" "Administrative distance\n" "IP source prefix\n") { bgp_distance_unset(vty, argv[2]->arg, argv[3]->arg, NULL); return CMD_SUCCESS; } DEFUN (ipv6_bgp_distance_source_access_list, ipv6_bgp_distance_source_access_list_cmd, "distance (1-255) X:X::X:X/M WORD", "Define an administrative distance\n" "Administrative distance\n" "IP source prefix\n" "Access list name\n") { bgp_distance_set(vty, argv[1]->arg, argv[2]->arg, argv[3]->arg); return CMD_SUCCESS; } DEFUN (no_ipv6_bgp_distance_source_access_list, no_ipv6_bgp_distance_source_access_list_cmd, "no distance (1-255) X:X::X:X/M WORD", NO_STR "Define an administrative distance\n" "Administrative distance\n" "IP source prefix\n" "Access list name\n") { bgp_distance_unset(vty, argv[2]->arg, argv[3]->arg, argv[4]->arg); return CMD_SUCCESS; } DEFUN (bgp_damp_set, bgp_damp_set_cmd, "bgp dampening [(1-45) [(1-20000) (1-20000) (1-255)]]", "BGP Specific commands\n" "Enable route-flap dampening\n" "Half-life time for the penalty\n" "Value to start reusing a route\n" "Value to start suppressing a route\n" "Maximum duration to suppress a stable route\n") { VTY_DECLVAR_CONTEXT(bgp, bgp); int idx_half_life = 2; int idx_reuse = 3; int idx_suppress = 4; int idx_max_suppress = 5; int half = DEFAULT_HALF_LIFE * 60; int reuse = DEFAULT_REUSE; int suppress = DEFAULT_SUPPRESS; int max = 4 * half; if (argc == 6) { half = atoi(argv[idx_half_life]->arg) * 60; reuse = atoi(argv[idx_reuse]->arg); suppress = atoi(argv[idx_suppress]->arg); max = atoi(argv[idx_max_suppress]->arg) * 60; } else if (argc == 3) { half = atoi(argv[idx_half_life]->arg) * 60; max = 4 * half; } if (suppress < reuse) { vty_out(vty, "Suppress value cannot be less than reuse value \n"); return 0; } return bgp_damp_enable(bgp, bgp_node_afi(vty), bgp_node_safi(vty), half, reuse, suppress, max); } DEFUN (bgp_damp_unset, bgp_damp_unset_cmd, "no bgp dampening [(1-45) [(1-20000) (1-20000) (1-255)]]", NO_STR "BGP Specific commands\n" "Enable route-flap dampening\n" "Half-life time for the penalty\n" "Value to start reusing a route\n" "Value to start suppressing a route\n" "Maximum duration to suppress a stable route\n") { VTY_DECLVAR_CONTEXT(bgp, bgp); return bgp_damp_disable(bgp, bgp_node_afi(vty), bgp_node_safi(vty)); } /* Display specified route of BGP table. */ static int bgp_clear_damp_route(struct vty *vty, const char *view_name, const char *ip_str, afi_t afi, safi_t safi, struct prefix_rd *prd, int prefix_check) { int ret; struct prefix match; struct bgp_node *rn; struct bgp_node *rm; struct bgp_path_info *pi; struct bgp_path_info *pi_temp; struct bgp *bgp; struct bgp_table *table; /* BGP structure lookup. */ if (view_name) { bgp = bgp_lookup_by_name(view_name); if (bgp == NULL) { vty_out(vty, "%% Can't find BGP instance %s\n", view_name); return CMD_WARNING; } } else { bgp = bgp_get_default(); if (bgp == NULL) { vty_out(vty, "%% No BGP process is configured\n"); return CMD_WARNING; } } /* Check IP address argument. */ ret = str2prefix(ip_str, &match); if (!ret) { vty_out(vty, "%% address is malformed\n"); return CMD_WARNING; } match.family = afi2family(afi); if ((safi == SAFI_MPLS_VPN) || (safi == SAFI_ENCAP) || (safi == SAFI_EVPN)) { for (rn = bgp_table_top(bgp->rib[AFI_IP][safi]); rn; rn = bgp_route_next(rn)) { if (prd && memcmp(rn->p.u.val, prd->val, 8) != 0) continue; table = bgp_node_get_bgp_table_info(rn); if (!table) continue; if ((rm = bgp_node_match(table, &match)) == NULL) continue; if (!prefix_check || rm->p.prefixlen == match.prefixlen) { pi = bgp_node_get_bgp_path_info(rm); while (pi) { if (pi->extra && pi->extra->damp_info) { pi_temp = pi->next; bgp_damp_info_free( pi->extra->damp_info, 1, afi, safi); pi = pi_temp; } else pi = pi->next; } } bgp_unlock_node(rm); } } else { if ((rn = bgp_node_match(bgp->rib[afi][safi], &match)) != NULL) { if (!prefix_check || rn->p.prefixlen == match.prefixlen) { pi = bgp_node_get_bgp_path_info(rn); while (pi) { if (pi->extra && pi->extra->damp_info) { pi_temp = pi->next; bgp_damp_info_free( pi->extra->damp_info, 1, afi, safi); pi = pi_temp; } else pi = pi->next; } } bgp_unlock_node(rn); } } return CMD_SUCCESS; } DEFUN (clear_ip_bgp_dampening, clear_ip_bgp_dampening_cmd, "clear ip bgp dampening", CLEAR_STR IP_STR BGP_STR "Clear route flap dampening information\n") { bgp_damp_info_clean(AFI_IP, SAFI_UNICAST); return CMD_SUCCESS; } DEFUN (clear_ip_bgp_dampening_prefix, clear_ip_bgp_dampening_prefix_cmd, "clear ip bgp dampening A.B.C.D/M", CLEAR_STR IP_STR BGP_STR "Clear route flap dampening information\n" "IPv4 prefix\n") { int idx_ipv4_prefixlen = 4; return bgp_clear_damp_route(vty, NULL, argv[idx_ipv4_prefixlen]->arg, AFI_IP, SAFI_UNICAST, NULL, 1); } DEFUN (clear_ip_bgp_dampening_address, clear_ip_bgp_dampening_address_cmd, "clear ip bgp dampening A.B.C.D", CLEAR_STR IP_STR BGP_STR "Clear route flap dampening information\n" "Network to clear damping information\n") { int idx_ipv4 = 4; return bgp_clear_damp_route(vty, NULL, argv[idx_ipv4]->arg, AFI_IP, SAFI_UNICAST, NULL, 0); } DEFUN (clear_ip_bgp_dampening_address_mask, clear_ip_bgp_dampening_address_mask_cmd, "clear ip bgp dampening A.B.C.D A.B.C.D", CLEAR_STR IP_STR BGP_STR "Clear route flap dampening information\n" "Network to clear damping information\n" "Network mask\n") { int idx_ipv4 = 4; int idx_ipv4_2 = 5; int ret; char prefix_str[BUFSIZ]; ret = netmask_str2prefix_str(argv[idx_ipv4]->arg, argv[idx_ipv4_2]->arg, prefix_str); if (!ret) { vty_out(vty, "%% Inconsistent address and mask\n"); return CMD_WARNING; } return bgp_clear_damp_route(vty, NULL, prefix_str, AFI_IP, SAFI_UNICAST, NULL, 0); } static void show_bgp_peerhash_entry(struct hash_bucket *bucket, void *arg) { struct vty *vty = arg; struct peer *peer = bucket->data; char buf[SU_ADDRSTRLEN]; vty_out(vty, "\tPeer: %s %s\n", peer->host, sockunion2str(&peer->su, buf, sizeof(buf))); } DEFUN (show_bgp_peerhash, show_bgp_peerhash_cmd, "show bgp peerhash", SHOW_STR BGP_STR "Display information about the BGP peerhash\n") { struct list *instances = bm->bgp; struct listnode *node; struct bgp *bgp; for (ALL_LIST_ELEMENTS_RO(instances, node, bgp)) { vty_out(vty, "BGP: %s\n", bgp->name); hash_iterate(bgp->peerhash, show_bgp_peerhash_entry, vty); } return CMD_SUCCESS; } /* also used for encap safi */ static void bgp_config_write_network_vpn(struct vty *vty, struct bgp *bgp, afi_t afi, safi_t safi) { struct bgp_node *prn; struct bgp_node *rn; struct bgp_table *table; struct prefix *p; struct prefix_rd *prd; struct bgp_static *bgp_static; mpls_label_t label; char buf[SU_ADDRSTRLEN]; char rdbuf[RD_ADDRSTRLEN]; /* Network configuration. */ for (prn = bgp_table_top(bgp->route[afi][safi]); prn; prn = bgp_route_next(prn)) { table = bgp_node_get_bgp_table_info(prn); if (!table) continue; for (rn = bgp_table_top(table); rn; rn = bgp_route_next(rn)) { bgp_static = bgp_node_get_bgp_static_info(rn); if (bgp_static == NULL) continue; p = &rn->p; prd = (struct prefix_rd *)&prn->p; /* "network" configuration display. */ prefix_rd2str(prd, rdbuf, sizeof(rdbuf)); label = decode_label(&bgp_static->label); vty_out(vty, " network %s/%d rd %s", inet_ntop(p->family, &p->u.prefix, buf, SU_ADDRSTRLEN), p->prefixlen, rdbuf); if (safi == SAFI_MPLS_VPN) vty_out(vty, " label %u", label); if (bgp_static->rmap.name) vty_out(vty, " route-map %s", bgp_static->rmap.name); if (bgp_static->backdoor) vty_out(vty, " backdoor"); vty_out(vty, "\n"); } } } static void bgp_config_write_network_evpn(struct vty *vty, struct bgp *bgp, afi_t afi, safi_t safi) { struct bgp_node *prn; struct bgp_node *rn; struct bgp_table *table; struct prefix *p; struct prefix_rd *prd; struct bgp_static *bgp_static; char buf[PREFIX_STRLEN * 2]; char buf2[SU_ADDRSTRLEN]; char rdbuf[RD_ADDRSTRLEN]; /* Network configuration. */ for (prn = bgp_table_top(bgp->route[afi][safi]); prn; prn = bgp_route_next(prn)) { table = bgp_node_get_bgp_table_info(prn); if (!table) continue; for (rn = bgp_table_top(table); rn; rn = bgp_route_next(rn)) { bgp_static = bgp_node_get_bgp_static_info(rn); if (bgp_static == NULL) continue; char *macrouter = NULL; char *esi = NULL; if (bgp_static->router_mac) macrouter = prefix_mac2str( bgp_static->router_mac, NULL, 0); if (bgp_static->eth_s_id) esi = esi2str(bgp_static->eth_s_id); p = &rn->p; prd = (struct prefix_rd *)&prn->p; /* "network" configuration display. */ prefix_rd2str(prd, rdbuf, sizeof(rdbuf)); if (p->u.prefix_evpn.route_type == 5) { char local_buf[PREFIX_STRLEN]; uint8_t family = is_evpn_prefix_ipaddr_v4(( struct prefix_evpn *)p) ? AF_INET : AF_INET6; inet_ntop(family, &p->u.prefix_evpn.prefix_addr.ip.ip.addr, local_buf, PREFIX_STRLEN); sprintf(buf, "%s/%u", local_buf, p->u.prefix_evpn.prefix_addr.ip_prefix_length); } else { prefix2str(p, buf, sizeof(buf)); } if (bgp_static->gatewayIp.family == AF_INET || bgp_static->gatewayIp.family == AF_INET6) inet_ntop(bgp_static->gatewayIp.family, &bgp_static->gatewayIp.u.prefix, buf2, sizeof(buf2)); vty_out(vty, " network %s rd %s ethtag %u label %u esi %s gwip %s routermac %s\n", buf, rdbuf, p->u.prefix_evpn.prefix_addr.eth_tag, decode_label(&bgp_static->label), esi, buf2, macrouter); XFREE(MTYPE_TMP, macrouter); XFREE(MTYPE_TMP, esi); } } } /* Configuration of static route announcement and aggregate information. */ void bgp_config_write_network(struct vty *vty, struct bgp *bgp, afi_t afi, safi_t safi) { struct bgp_node *rn; struct prefix *p; struct bgp_static *bgp_static; struct bgp_aggregate *bgp_aggregate; char buf[SU_ADDRSTRLEN]; if ((safi == SAFI_MPLS_VPN) || (safi == SAFI_ENCAP)) { bgp_config_write_network_vpn(vty, bgp, afi, safi); return; } if (afi == AFI_L2VPN && safi == SAFI_EVPN) { bgp_config_write_network_evpn(vty, bgp, afi, safi); return; } /* Network configuration. */ for (rn = bgp_table_top(bgp->route[afi][safi]); rn; rn = bgp_route_next(rn)) { bgp_static = bgp_node_get_bgp_static_info(rn); if (bgp_static == NULL) continue; p = &rn->p; vty_out(vty, " network %s/%d", inet_ntop(p->family, &p->u.prefix, buf, SU_ADDRSTRLEN), p->prefixlen); if (bgp_static->label_index != BGP_INVALID_LABEL_INDEX) vty_out(vty, " label-index %u", bgp_static->label_index); if (bgp_static->rmap.name) vty_out(vty, " route-map %s", bgp_static->rmap.name); if (bgp_static->backdoor) vty_out(vty, " backdoor"); vty_out(vty, "\n"); } /* Aggregate-address configuration. */ for (rn = bgp_table_top(bgp->aggregate[afi][safi]); rn; rn = bgp_route_next(rn)) { bgp_aggregate = bgp_node_get_bgp_aggregate_info(rn); if (bgp_aggregate == NULL) continue; p = &rn->p; vty_out(vty, " aggregate-address %s/%d", inet_ntop(p->family, &p->u.prefix, buf, SU_ADDRSTRLEN), p->prefixlen); if (bgp_aggregate->as_set) vty_out(vty, " as-set"); if (bgp_aggregate->summary_only) vty_out(vty, " summary-only"); if (bgp_aggregate->rmap.name) vty_out(vty, " route-map %s", bgp_aggregate->rmap.name); vty_out(vty, "\n"); } } void bgp_config_write_distance(struct vty *vty, struct bgp *bgp, afi_t afi, safi_t safi) { struct bgp_node *rn; struct bgp_distance *bdistance; /* Distance configuration. */ if (bgp->distance_ebgp[afi][safi] && bgp->distance_ibgp[afi][safi] && bgp->distance_local[afi][safi] && (bgp->distance_ebgp[afi][safi] != ZEBRA_EBGP_DISTANCE_DEFAULT || bgp->distance_ibgp[afi][safi] != ZEBRA_IBGP_DISTANCE_DEFAULT || bgp->distance_local[afi][safi] != ZEBRA_IBGP_DISTANCE_DEFAULT)) { vty_out(vty, " distance bgp %d %d %d\n", bgp->distance_ebgp[afi][safi], bgp->distance_ibgp[afi][safi], bgp->distance_local[afi][safi]); } for (rn = bgp_table_top(bgp_distance_table[afi][safi]); rn; rn = bgp_route_next(rn)) { bdistance = bgp_node_get_bgp_distance_info(rn); if (bdistance != NULL) { char buf[PREFIX_STRLEN]; vty_out(vty, " distance %d %s %s\n", bdistance->distance, prefix2str(&rn->p, buf, sizeof(buf)), bdistance->access_list ? bdistance->access_list : ""); } } } /* Allocate routing table structure and install commands. */ void bgp_route_init(void) { afi_t afi; safi_t safi; /* Init BGP distance table. */ FOREACH_AFI_SAFI (afi, safi) bgp_distance_table[afi][safi] = bgp_table_init(NULL, afi, safi); /* IPv4 BGP commands. */ install_element(BGP_NODE, &bgp_table_map_cmd); install_element(BGP_NODE, &bgp_network_cmd); install_element(BGP_NODE, &no_bgp_table_map_cmd); install_element(BGP_NODE, &aggregate_address_cmd); install_element(BGP_NODE, &aggregate_address_mask_cmd); install_element(BGP_NODE, &no_aggregate_address_cmd); install_element(BGP_NODE, &no_aggregate_address_mask_cmd); /* IPv4 unicast configuration. */ install_element(BGP_IPV4_NODE, &bgp_table_map_cmd); install_element(BGP_IPV4_NODE, &bgp_network_cmd); install_element(BGP_IPV4_NODE, &no_bgp_table_map_cmd); install_element(BGP_IPV4_NODE, &aggregate_address_cmd); install_element(BGP_IPV4_NODE, &aggregate_address_mask_cmd); install_element(BGP_IPV4_NODE, &no_aggregate_address_cmd); install_element(BGP_IPV4_NODE, &no_aggregate_address_mask_cmd); /* IPv4 multicast configuration. */ install_element(BGP_IPV4M_NODE, &bgp_table_map_cmd); install_element(BGP_IPV4M_NODE, &bgp_network_cmd); install_element(BGP_IPV4M_NODE, &no_bgp_table_map_cmd); install_element(BGP_IPV4M_NODE, &aggregate_address_cmd); install_element(BGP_IPV4M_NODE, &aggregate_address_mask_cmd); install_element(BGP_IPV4M_NODE, &no_aggregate_address_cmd); install_element(BGP_IPV4M_NODE, &no_aggregate_address_mask_cmd); /* IPv4 labeled-unicast configuration. */ install_element(VIEW_NODE, &show_ip_bgp_instance_all_cmd); install_element(VIEW_NODE, &show_ip_bgp_cmd); install_element(VIEW_NODE, &show_ip_bgp_json_cmd); install_element(VIEW_NODE, &show_ip_bgp_route_cmd); install_element(VIEW_NODE, &show_ip_bgp_regexp_cmd); install_element(VIEW_NODE, &show_ip_bgp_instance_neighbor_advertised_route_cmd); install_element(VIEW_NODE, &show_ip_bgp_neighbor_routes_cmd); install_element(VIEW_NODE, &show_ip_bgp_neighbor_received_prefix_filter_cmd); #ifdef KEEP_OLD_VPN_COMMANDS install_element(VIEW_NODE, &show_ip_bgp_vpn_all_route_prefix_cmd); #endif /* KEEP_OLD_VPN_COMMANDS */ install_element(VIEW_NODE, &show_bgp_afi_vpn_rd_route_cmd); install_element(VIEW_NODE, &show_ip_bgp_l2vpn_evpn_all_route_prefix_cmd); /* BGP dampening clear commands */ install_element(ENABLE_NODE, &clear_ip_bgp_dampening_cmd); install_element(ENABLE_NODE, &clear_ip_bgp_dampening_prefix_cmd); install_element(ENABLE_NODE, &clear_ip_bgp_dampening_address_cmd); install_element(ENABLE_NODE, &clear_ip_bgp_dampening_address_mask_cmd); /* prefix count */ install_element(ENABLE_NODE, &show_ip_bgp_instance_neighbor_prefix_counts_cmd); #ifdef KEEP_OLD_VPN_COMMANDS install_element(ENABLE_NODE, &show_ip_bgp_vpn_neighbor_prefix_counts_cmd); #endif /* KEEP_OLD_VPN_COMMANDS */ /* New config IPv6 BGP commands. */ install_element(BGP_IPV6_NODE, &bgp_table_map_cmd); install_element(BGP_IPV6_NODE, &ipv6_bgp_network_cmd); install_element(BGP_IPV6_NODE, &no_bgp_table_map_cmd); install_element(BGP_IPV6_NODE, &ipv6_aggregate_address_cmd); install_element(BGP_IPV6_NODE, &no_ipv6_aggregate_address_cmd); install_element(BGP_IPV6M_NODE, &ipv6_bgp_network_cmd); install_element(BGP_NODE, &bgp_distance_cmd); install_element(BGP_NODE, &no_bgp_distance_cmd); install_element(BGP_NODE, &bgp_distance_source_cmd); install_element(BGP_NODE, &no_bgp_distance_source_cmd); install_element(BGP_NODE, &bgp_distance_source_access_list_cmd); install_element(BGP_NODE, &no_bgp_distance_source_access_list_cmd); install_element(BGP_IPV4_NODE, &bgp_distance_cmd); install_element(BGP_IPV4_NODE, &no_bgp_distance_cmd); install_element(BGP_IPV4_NODE, &bgp_distance_source_cmd); install_element(BGP_IPV4_NODE, &no_bgp_distance_source_cmd); install_element(BGP_IPV4_NODE, &bgp_distance_source_access_list_cmd); install_element(BGP_IPV4_NODE, &no_bgp_distance_source_access_list_cmd); install_element(BGP_IPV4M_NODE, &bgp_distance_cmd); install_element(BGP_IPV4M_NODE, &no_bgp_distance_cmd); install_element(BGP_IPV4M_NODE, &bgp_distance_source_cmd); install_element(BGP_IPV4M_NODE, &no_bgp_distance_source_cmd); install_element(BGP_IPV4M_NODE, &bgp_distance_source_access_list_cmd); install_element(BGP_IPV4M_NODE, &no_bgp_distance_source_access_list_cmd); install_element(BGP_IPV6_NODE, &bgp_distance_cmd); install_element(BGP_IPV6_NODE, &no_bgp_distance_cmd); install_element(BGP_IPV6_NODE, &ipv6_bgp_distance_source_cmd); install_element(BGP_IPV6_NODE, &no_ipv6_bgp_distance_source_cmd); install_element(BGP_IPV6_NODE, &ipv6_bgp_distance_source_access_list_cmd); install_element(BGP_IPV6_NODE, &no_ipv6_bgp_distance_source_access_list_cmd); install_element(BGP_IPV6M_NODE, &bgp_distance_cmd); install_element(BGP_IPV6M_NODE, &no_bgp_distance_cmd); install_element(BGP_IPV6M_NODE, &ipv6_bgp_distance_source_cmd); install_element(BGP_IPV6M_NODE, &no_ipv6_bgp_distance_source_cmd); install_element(BGP_IPV6M_NODE, &ipv6_bgp_distance_source_access_list_cmd); install_element(BGP_IPV6M_NODE, &no_ipv6_bgp_distance_source_access_list_cmd); install_element(BGP_NODE, &bgp_damp_set_cmd); install_element(BGP_NODE, &bgp_damp_unset_cmd); install_element(BGP_IPV4_NODE, &bgp_damp_set_cmd); install_element(BGP_IPV4_NODE, &bgp_damp_unset_cmd); /* IPv4 Multicast Mode */ install_element(BGP_IPV4M_NODE, &bgp_damp_set_cmd); install_element(BGP_IPV4M_NODE, &bgp_damp_unset_cmd); /* Large Communities */ install_element(VIEW_NODE, &show_ip_bgp_large_community_list_cmd); install_element(VIEW_NODE, &show_ip_bgp_large_community_cmd); /* show bgp ipv4 flowspec detailed */ install_element(VIEW_NODE, &show_ip_bgp_flowspec_routes_detailed_cmd); install_element(VIEW_NODE, &show_bgp_peerhash_cmd); } void bgp_route_finish(void) { afi_t afi; safi_t safi; FOREACH_AFI_SAFI (afi, safi) { bgp_table_unlock(bgp_distance_table[afi][safi]); bgp_distance_table[afi][safi] = NULL; } } frr-7.2.1/bgpd/bgp_route.h0000644000000000000000000005166013610377563012277 00000000000000/* BGP routing information base * Copyright (C) 1996, 97, 98, 2000 Kunihiro Ishiguro * * This file is part of GNU Zebra. * * GNU Zebra is free software; you can redistribute it and/or modify it * under the terms of the GNU General Public License as published by the * Free Software Foundation; either version 2, or (at your option) any * later version. * * GNU Zebra 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; see the file COPYING; if not, write to the Free Software * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA */ #ifndef _QUAGGA_BGP_ROUTE_H #define _QUAGGA_BGP_ROUTE_H #include #include "hook.h" #include "queue.h" #include "nexthop.h" #include "bgp_table.h" #include "bgp_addpath_types.h" struct bgp_nexthop_cache; struct bgp_route_evpn; enum bgp_show_type { bgp_show_type_normal, bgp_show_type_regexp, bgp_show_type_prefix_list, bgp_show_type_filter_list, bgp_show_type_route_map, bgp_show_type_neighbor, bgp_show_type_cidr_only, bgp_show_type_prefix_longer, bgp_show_type_community_all, bgp_show_type_community, bgp_show_type_community_exact, bgp_show_type_community_list, bgp_show_type_community_list_exact, bgp_show_type_lcommunity_all, bgp_show_type_lcommunity, bgp_show_type_lcommunity_exact, bgp_show_type_lcommunity_list, bgp_show_type_lcommunity_list_exact, bgp_show_type_flap_statistics, bgp_show_type_flap_neighbor, bgp_show_type_dampend_paths, bgp_show_type_damp_neighbor, bgp_show_type_detail, }; enum bgp_show_adj_route_type { bgp_show_adj_route_advertised, bgp_show_adj_route_received, bgp_show_adj_route_filtered, }; #define BGP_SHOW_SCODE_HEADER \ "Status codes: s suppressed, d damped, " \ "h history, * valid, > best, = multipath,\n" \ " i internal, r RIB-failure, S Stale, R Removed\n" #define BGP_SHOW_OCODE_HEADER "Origin codes: i - IGP, e - EGP, ? - incomplete\n\n" #define BGP_SHOW_NCODE_HEADER "Nexthop codes: @NNN nexthop's vrf id, < announce-nh-self\n" #define BGP_SHOW_HEADER " Network Next Hop Metric LocPrf Weight Path\n" /* Maximum number of labels we can process or send with a prefix. We * really do only 1 for MPLS (BGP-LU) but we can do 2 for EVPN-VxLAN. */ #define BGP_MAX_LABELS 2 /* Error codes for handling NLRI */ #define BGP_NLRI_PARSE_OK 0 #define BGP_NLRI_PARSE_ERROR_PREFIX_OVERFLOW -1 #define BGP_NLRI_PARSE_ERROR_PACKET_OVERFLOW -2 #define BGP_NLRI_PARSE_ERROR_PREFIX_LENGTH -3 #define BGP_NLRI_PARSE_ERROR_PACKET_LENGTH -4 #define BGP_NLRI_PARSE_ERROR_LABEL_LENGTH -5 #define BGP_NLRI_PARSE_ERROR_EVPN_MISSING_TYPE -6 #define BGP_NLRI_PARSE_ERROR_EVPN_TYPE2_SIZE -7 #define BGP_NLRI_PARSE_ERROR_EVPN_TYPE3_SIZE -8 #define BGP_NLRI_PARSE_ERROR_EVPN_TYPE4_SIZE -9 #define BGP_NLRI_PARSE_ERROR_EVPN_TYPE5_SIZE -10 #define BGP_NLRI_PARSE_ERROR_FLOWSPEC_IPV6_NOT_SUPPORTED -11 #define BGP_NLRI_PARSE_ERROR_FLOWSPEC_NLRI_SIZELIMIT -12 #define BGP_NLRI_PARSE_ERROR_FLOWSPEC_BAD_FORMAT -13 #define BGP_NLRI_PARSE_ERROR_ADDRESS_FAMILY -14 #define BGP_NLRI_PARSE_ERROR -32 /* Ancillary information to struct bgp_path_info, * used for uncommonly used data (aggregation, MPLS, etc.) * and lazily allocated to save memory. */ struct bgp_path_info_extra { /* Pointer to dampening structure. */ struct bgp_damp_info *damp_info; /* This route is suppressed with aggregation. */ int suppress; /* Nexthop reachability check. */ uint32_t igpmetric; /* MPLS label(s) - VNI(s) for EVPN-VxLAN */ mpls_label_t label[BGP_MAX_LABELS]; uint32_t num_labels; #if ENABLE_BGP_VNC union { struct { void *rfapi_handle; /* export: NVE advertising this route */ struct list *local_nexthops; /* optional, for static routes */ } export; struct { struct thread *timer; void *hme; /* encap monitor, if this is a VPN route */ struct prefix_rd rd; /* import: route's route-distinguisher */ uint8_t un_family; /* family of cached un address, 0 if unset */ union { struct in_addr addr4; struct in6_addr addr6; } un; /* cached un address */ time_t create_time; struct prefix aux_prefix; /* AFI_L2VPN: the IP addr, if family set */ } import; } vnc; #endif /* For imported routes into a VNI (or VRF), this points to the parent. */ void *parent; /* * Some tunnelish parameters follow. Maybe consolidate into an * internal tunnel structure? */ /* * Original bgp instance for imported routes. Needed for: * 1. Find all routes from a specific vrf for deletion * 2. vrf context of original nexthop * * Store pointer to bgp instance rather than bgp->vrf_id because * bgp->vrf_id is not always valid (or may change?). * * Set to NULL if route is not imported from another bgp instance. */ struct bgp *bgp_orig; /* * Nexthop in context of original bgp instance. Needed * for label resolution of core mpls routes exported to a vrf. * Set nexthop_orig.family to 0 if not valid. */ struct prefix nexthop_orig; /* presence of FS pbr firewall based entry */ struct list *bgp_fs_pbr; /* presence of FS pbr iprule based entry */ struct list *bgp_fs_iprule; }; struct bgp_path_info { /* For linked list. */ struct bgp_path_info *next; struct bgp_path_info *prev; /* For nexthop linked list */ LIST_ENTRY(bgp_path_info) nh_thread; /* Back pointer to the prefix node */ struct bgp_node *net; /* Back pointer to the nexthop structure */ struct bgp_nexthop_cache *nexthop; /* Peer structure. */ struct peer *peer; /* Attribute structure. */ struct attr *attr; /* Extra information */ struct bgp_path_info_extra *extra; /* Multipath information */ struct bgp_path_info_mpath *mpath; /* Uptime. */ time_t uptime; /* reference count */ int lock; /* BGP information status. */ uint16_t flags; #define BGP_PATH_IGP_CHANGED (1 << 0) #define BGP_PATH_DAMPED (1 << 1) #define BGP_PATH_HISTORY (1 << 2) #define BGP_PATH_SELECTED (1 << 3) #define BGP_PATH_VALID (1 << 4) #define BGP_PATH_ATTR_CHANGED (1 << 5) #define BGP_PATH_DMED_CHECK (1 << 6) #define BGP_PATH_DMED_SELECTED (1 << 7) #define BGP_PATH_STALE (1 << 8) #define BGP_PATH_REMOVED (1 << 9) #define BGP_PATH_COUNTED (1 << 10) #define BGP_PATH_MULTIPATH (1 << 11) #define BGP_PATH_MULTIPATH_CHG (1 << 12) #define BGP_PATH_RIB_ATTR_CHG (1 << 13) #define BGP_PATH_ANNC_NH_SELF (1 << 14) /* BGP route type. This can be static, RIP, OSPF, BGP etc. */ uint8_t type; /* When above type is BGP. This sub type specify BGP sub type information. */ uint8_t sub_type; #define BGP_ROUTE_NORMAL 0 #define BGP_ROUTE_STATIC 1 #define BGP_ROUTE_AGGREGATE 2 #define BGP_ROUTE_REDISTRIBUTE 3 #ifdef ENABLE_BGP_VNC # define BGP_ROUTE_RFP 4 #endif #define BGP_ROUTE_IMPORTED 5 /* from another bgp instance/safi */ unsigned short instance; /* Addpath identifiers */ uint32_t addpath_rx_id; struct bgp_addpath_info_data tx_addpath; }; /* Structure used in BGP path selection */ struct bgp_path_info_pair { struct bgp_path_info *old; struct bgp_path_info *new; }; /* BGP static route configuration. */ struct bgp_static { /* Backdoor configuration. */ int backdoor; /* Label index configuration; applies to LU prefixes. */ uint32_t label_index; #define BGP_INVALID_LABEL_INDEX 0xFFFFFFFF /* Import check status. */ uint8_t valid; /* IGP metric. */ uint32_t igpmetric; /* IGP nexthop. */ struct in_addr igpnexthop; /* Atomic set reference count (ie cause of pathlimit) */ uint32_t atomic; /* BGP redistribute route-map. */ struct { char *name; struct route_map *map; } rmap; /* Route Distinguisher */ struct prefix_rd prd; /* MPLS label. */ mpls_label_t label; /* EVPN */ struct eth_segment_id *eth_s_id; struct ethaddr *router_mac; uint16_t encap_tunneltype; struct prefix gatewayIp; }; /* Aggreagete address: * * advertise-map Set condition to advertise attribute * as-set Generate AS set path information * attribute-map Set attributes of aggregate * route-map Set parameters of aggregate * summary-only Filter more specific routes from updates * suppress-map Conditionally filter more specific routes from updates * */ struct bgp_aggregate { /* Summary-only flag. */ uint8_t summary_only; /* AS set generation. */ uint8_t as_set; /* Route-map for aggregated route. */ struct { char *name; struct route_map *map; } rmap; /* Suppress-count. */ unsigned long count; /* Count of routes of origin type incomplete under this aggregate. */ unsigned long incomplete_origin_count; /* Count of routes of origin type egp under this aggregate. */ unsigned long egp_origin_count; /* Hash containing the communities of all the * routes under this aggregate. */ struct hash *community_hash; /* Hash containing the extended communities of all the * routes under this aggregate. */ struct hash *ecommunity_hash; /* Hash containing the large communities of all the * routes under this aggregate. */ struct hash *lcommunity_hash; /* Hash containing the AS-Path of all the * routes under this aggregate. */ struct hash *aspath_hash; /* Aggregate route's community. */ struct community *community; /* Aggregate route's extended community. */ struct ecommunity *ecommunity; /* Aggregate route's large community. */ struct lcommunity *lcommunity; /* Aggregate route's as-path. */ struct aspath *aspath; /* SAFI configuration. */ safi_t safi; }; #define BGP_NEXTHOP_AFI_FROM_NHLEN(nhlen) \ ((nhlen) < IPV4_MAX_BYTELEN \ ? 0 \ : ((nhlen) < IPV6_MAX_BYTELEN ? AFI_IP : AFI_IP6)) #define BGP_ATTR_NEXTHOP_AFI_IP6(attr) \ (!CHECK_FLAG(attr->flag, ATTR_FLAG_BIT(BGP_ATTR_NEXT_HOP)) \ && ((attr)->mp_nexthop_len == BGP_ATTR_NHLEN_IPV6_GLOBAL \ || (attr)->mp_nexthop_len == BGP_ATTR_NHLEN_IPV6_GLOBAL_AND_LL \ || (attr)->mp_nexthop_len == BGP_ATTR_NHLEN_VPNV6_GLOBAL \ || (attr)->mp_nexthop_len == BGP_ATTR_NHLEN_VPNV6_GLOBAL_AND_LL)) #define BGP_PATH_COUNTABLE(BI) \ (!CHECK_FLAG((BI)->flags, BGP_PATH_HISTORY) \ && !CHECK_FLAG((BI)->flags, BGP_PATH_REMOVED)) /* Flags which indicate a route is unuseable in some form */ #define BGP_PATH_UNUSEABLE \ (BGP_PATH_HISTORY | BGP_PATH_DAMPED | BGP_PATH_REMOVED) /* Macro to check BGP information is alive or not. Sadly, * not equivalent to just checking previous, because of the * sense of the additional VALID flag. */ #define BGP_PATH_HOLDDOWN(BI) \ (!CHECK_FLAG((BI)->flags, BGP_PATH_VALID) \ || CHECK_FLAG((BI)->flags, BGP_PATH_UNUSEABLE)) #define DISTRIBUTE_IN_NAME(F) ((F)->dlist[FILTER_IN].name) #define DISTRIBUTE_IN(F) ((F)->dlist[FILTER_IN].alist) #define DISTRIBUTE_OUT_NAME(F) ((F)->dlist[FILTER_OUT].name) #define DISTRIBUTE_OUT(F) ((F)->dlist[FILTER_OUT].alist) #define PREFIX_LIST_IN_NAME(F) ((F)->plist[FILTER_IN].name) #define PREFIX_LIST_IN(F) ((F)->plist[FILTER_IN].plist) #define PREFIX_LIST_OUT_NAME(F) ((F)->plist[FILTER_OUT].name) #define PREFIX_LIST_OUT(F) ((F)->plist[FILTER_OUT].plist) #define FILTER_LIST_IN_NAME(F) ((F)->aslist[FILTER_IN].name) #define FILTER_LIST_IN(F) ((F)->aslist[FILTER_IN].aslist) #define FILTER_LIST_OUT_NAME(F) ((F)->aslist[FILTER_OUT].name) #define FILTER_LIST_OUT(F) ((F)->aslist[FILTER_OUT].aslist) #define ROUTE_MAP_IN_NAME(F) ((F)->map[RMAP_IN].name) #define ROUTE_MAP_IN(F) ((F)->map[RMAP_IN].map) #define ROUTE_MAP_OUT_NAME(F) ((F)->map[RMAP_OUT].name) #define ROUTE_MAP_OUT(F) ((F)->map[RMAP_OUT].map) #define UNSUPPRESS_MAP_NAME(F) ((F)->usmap.name) #define UNSUPPRESS_MAP(F) ((F)->usmap.map) /* path PREFIX (addpath rxid NUMBER) */ #define PATH_ADDPATH_STR_BUFFER PREFIX2STR_BUFFER + 32 enum bgp_path_type { BGP_PATH_SHOW_ALL, BGP_PATH_SHOW_BESTPATH, BGP_PATH_SHOW_MULTIPATH }; static inline void bgp_bump_version(struct bgp_node *node) { node->version = bgp_table_next_version(bgp_node_table(node)); } static inline int bgp_fibupd_safi(safi_t safi) { if (safi == SAFI_UNICAST || safi == SAFI_MULTICAST || safi == SAFI_LABELED_UNICAST || safi == SAFI_FLOWSPEC) return 1; return 0; } /* Flag if the route path's family matches params. */ static inline bool is_pi_family_matching(struct bgp_path_info *pi, afi_t afi, safi_t safi) { struct bgp_table *table; struct bgp_node *rn; rn = pi->net; if (!rn) return false; table = bgp_node_table(rn); if (table && table->afi == afi && table->safi == safi) return true; return false; } /* called before bgp_process() */ DECLARE_HOOK(bgp_process, (struct bgp *bgp, afi_t afi, safi_t safi, struct bgp_node *bn, struct peer *peer, bool withdraw), (bgp, afi, safi, bn, peer, withdraw)) /* Prototypes. */ extern void bgp_rib_remove(struct bgp_node *rn, struct bgp_path_info *pi, struct peer *peer, afi_t afi, safi_t safi); extern void bgp_process_queue_init(void); extern void bgp_route_init(void); extern void bgp_route_finish(void); extern void bgp_cleanup_routes(struct bgp *); extern void bgp_announce_route(struct peer *, afi_t, safi_t); extern void bgp_stop_announce_route_timer(struct peer_af *paf); extern void bgp_announce_route_all(struct peer *); extern void bgp_default_originate(struct peer *, afi_t, safi_t, int); extern void bgp_soft_reconfig_in(struct peer *, afi_t, safi_t); extern void bgp_clear_route(struct peer *, afi_t, safi_t); extern void bgp_clear_route_all(struct peer *); extern void bgp_clear_adj_in(struct peer *, afi_t, safi_t); extern void bgp_clear_stale_route(struct peer *, afi_t, safi_t); extern int bgp_outbound_policy_exists(struct peer *, struct bgp_filter *); extern int bgp_inbound_policy_exists(struct peer *, struct bgp_filter *); extern struct bgp_node *bgp_afi_node_get(struct bgp_table *table, afi_t afi, safi_t safi, struct prefix *p, struct prefix_rd *prd); extern struct bgp_path_info *bgp_path_info_lock(struct bgp_path_info *path); extern struct bgp_path_info *bgp_path_info_unlock(struct bgp_path_info *path); extern void bgp_path_info_add(struct bgp_node *rn, struct bgp_path_info *pi); extern void bgp_path_info_extra_free(struct bgp_path_info_extra **extra); extern void bgp_path_info_reap(struct bgp_node *rn, struct bgp_path_info *pi); extern void bgp_path_info_delete(struct bgp_node *rn, struct bgp_path_info *pi); extern struct bgp_path_info_extra * bgp_path_info_extra_get(struct bgp_path_info *path); extern void bgp_path_info_set_flag(struct bgp_node *rn, struct bgp_path_info *path, uint32_t flag); extern void bgp_path_info_unset_flag(struct bgp_node *rn, struct bgp_path_info *path, uint32_t flag); extern void bgp_path_info_path_with_addpath_rx_str(struct bgp_path_info *pi, char *buf); extern int bgp_nlri_parse_ip(struct peer *, struct attr *, struct bgp_nlri *); extern int bgp_maximum_prefix_overflow(struct peer *, afi_t, safi_t, int); extern void bgp_redistribute_add(struct bgp *bgp, struct prefix *p, const union g_addr *nexthop, ifindex_t ifindex, enum nexthop_types_t nhtype, uint32_t metric, uint8_t type, unsigned short instance, route_tag_t tag); extern void bgp_redistribute_delete(struct bgp *, struct prefix *, uint8_t, unsigned short); extern void bgp_redistribute_withdraw(struct bgp *, afi_t, int, unsigned short); extern void bgp_static_add(struct bgp *); extern void bgp_static_delete(struct bgp *); extern void bgp_static_redo_import_check(struct bgp *); extern void bgp_purge_static_redist_routes(struct bgp *bgp); extern void bgp_static_update(struct bgp *, struct prefix *, struct bgp_static *, afi_t, safi_t); extern void bgp_static_withdraw(struct bgp *, struct prefix *, afi_t, safi_t); extern int bgp_static_set_safi(afi_t afi, safi_t safi, struct vty *vty, const char *, const char *, const char *, const char *, int, const char *, const char *, const char *, const char *); extern int bgp_static_unset_safi(afi_t afi, safi_t safi, struct vty *, const char *, const char *, const char *, int, const char *, const char *, const char *); /* this is primarily for MPLS-VPN */ extern int bgp_update(struct peer *, struct prefix *, uint32_t, struct attr *, afi_t, safi_t, int, int, struct prefix_rd *, mpls_label_t *, uint32_t, int, struct bgp_route_evpn *); extern int bgp_withdraw(struct peer *, struct prefix *, uint32_t, struct attr *, afi_t, safi_t, int, int, struct prefix_rd *, mpls_label_t *, uint32_t, struct bgp_route_evpn *); /* for bgp_nexthop and bgp_damp */ extern void bgp_process(struct bgp *, struct bgp_node *, afi_t, safi_t); /* * Add an end-of-initial-update marker to the process queue. This is just a * queue element with NULL bgp node. */ extern void bgp_add_eoiu_mark(struct bgp *); extern void bgp_config_write_table_map(struct vty *, struct bgp *, afi_t, safi_t); extern void bgp_config_write_network(struct vty *, struct bgp *, afi_t, safi_t); extern void bgp_config_write_distance(struct vty *, struct bgp *, afi_t, safi_t); extern void bgp_aggregate_delete(struct bgp *bgp, struct prefix *p, afi_t afi, safi_t safi, struct bgp_aggregate *aggregate); extern void bgp_aggregate_route(struct bgp *bgp, struct prefix *p, afi_t afi, safi_t safi, struct bgp_aggregate *aggregate); extern void bgp_aggregate_increment(struct bgp *bgp, struct prefix *p, struct bgp_path_info *path, afi_t afi, safi_t safi); extern void bgp_aggregate_decrement(struct bgp *bgp, struct prefix *p, struct bgp_path_info *path, afi_t afi, safi_t safi); extern uint8_t bgp_distance_apply(struct prefix *p, struct bgp_path_info *path, afi_t afi, safi_t safi, struct bgp *bgp); extern afi_t bgp_node_afi(struct vty *); extern safi_t bgp_node_safi(struct vty *); extern struct bgp_path_info *info_make(int type, int sub_type, unsigned short instance, struct peer *peer, struct attr *attr, struct bgp_node *rn); extern void route_vty_out(struct vty *vty, struct prefix *p, struct bgp_path_info *path, int display, safi_t safi, json_object *json_paths); extern void route_vty_out_tag(struct vty *vty, struct prefix *p, struct bgp_path_info *path, int display, safi_t safi, json_object *json); extern void route_vty_out_tmp(struct vty *vty, struct prefix *p, struct attr *attr, safi_t safi, bool use_json, json_object *json_ar); extern void route_vty_out_overlay(struct vty *vty, struct prefix *p, struct bgp_path_info *path, int display, json_object *json); extern int subgroup_process_announce_selected(struct update_subgroup *subgrp, struct bgp_path_info *selected, struct bgp_node *rn, uint32_t addpath_tx_id); extern int subgroup_announce_check(struct bgp_node *rn, struct bgp_path_info *pi, struct update_subgroup *subgrp, struct prefix *p, struct attr *attr); extern void bgp_peer_clear_node_queue_drain_immediate(struct peer *peer); extern void bgp_process_queues_drain_immediate(void); /* for encap/vpn */ extern struct bgp_node *bgp_afi_node_lookup(struct bgp_table *table, afi_t afi, safi_t safi, struct prefix *p, struct prefix_rd *prd); extern void bgp_path_info_restore(struct bgp_node *rn, struct bgp_path_info *path); extern int bgp_path_info_cmp_compatible(struct bgp *bgp, struct bgp_path_info *new, struct bgp_path_info *exist, char *pfx_buf, afi_t afi, safi_t safi, enum bgp_path_selection_reason *reason); extern void bgp_attr_add_gshut_community(struct attr *attr); extern void bgp_best_selection(struct bgp *bgp, struct bgp_node *rn, struct bgp_maxpaths_cfg *mpath_cfg, struct bgp_path_info_pair *result, afi_t afi, safi_t safi); extern void bgp_zebra_clear_route_change_flags(struct bgp_node *rn); extern int bgp_zebra_has_route_changed(struct bgp_node *rn, struct bgp_path_info *selected); extern void route_vty_out_detail_header(struct vty *vty, struct bgp *bgp, struct bgp_node *rn, struct prefix_rd *prd, afi_t afi, safi_t safi, json_object *json); extern void route_vty_out_detail(struct vty *vty, struct bgp *bgp, struct bgp_node *bn, struct bgp_path_info *path, afi_t afi, safi_t safi, json_object *json_paths); extern int bgp_show_table_rd(struct vty *vty, struct bgp *bgp, safi_t safi, struct bgp_table *table, struct prefix_rd *prd, enum bgp_show_type type, void *output_arg, bool use_json); #endif /* _QUAGGA_BGP_ROUTE_H */ frr-7.2.1/bgpd/bgp_routemap.c0000644000000000000000000041653713610377563013000 00000000000000/* Route map function of bgpd. * Copyright (C) 1998, 1999 Kunihiro Ishiguro * * This file is part of GNU Zebra. * * GNU Zebra is free software; you can redistribute it and/or modify it * under the terms of the GNU General Public License as published by the * Free Software Foundation; either version 2, or (at your option) any * later version. * * GNU Zebra 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; see the file COPYING; if not, write to the Free Software * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA */ #include #include "prefix.h" #include "filter.h" #include "routemap.h" #include "command.h" #include "linklist.h" #include "plist.h" #include "memory.h" #include "log.h" #include "frrlua.h" #ifdef HAVE_LIBPCREPOSIX #include #else #include #endif /* HAVE_LIBPCREPOSIX */ #include "buffer.h" #include "sockunion.h" #include "hash.h" #include "queue.h" #include "frrstr.h" #include "bgpd/bgpd.h" #include "bgpd/bgp_table.h" #include "bgpd/bgp_attr.h" #include "bgpd/bgp_aspath.h" #include "bgpd/bgp_packet.h" #include "bgpd/bgp_route.h" #include "bgpd/bgp_zebra.h" #include "bgpd/bgp_regex.h" #include "bgpd/bgp_community.h" #include "bgpd/bgp_clist.h" #include "bgpd/bgp_filter.h" #include "bgpd/bgp_mplsvpn.h" #include "bgpd/bgp_ecommunity.h" #include "bgpd/bgp_lcommunity.h" #include "bgpd/bgp_vty.h" #include "bgpd/bgp_debug.h" #include "bgpd/bgp_evpn.h" #include "bgpd/bgp_evpn_private.h" #include "bgpd/bgp_evpn_vty.h" #include "bgpd/bgp_mplsvpn.h" #include "bgpd/bgp_pbr.h" #include "bgpd/bgp_flowspec_util.h" #include "bgpd/bgp_encap_types.h" #if ENABLE_BGP_VNC #include "bgpd/rfapi/bgp_rfapi_cfg.h" #endif #ifndef VTYSH_EXTRACT_PL #include "bgpd/bgp_routemap_clippy.c" #endif /* Memo of route-map commands. o Cisco route-map match as-path : Done community : Done interface : Done ip address : Done ip next-hop : Done ip route-source : Done ip prefix-list : Done ipv6 address : Done ipv6 next-hop : Done ipv6 route-source: (This will not be implemented by bgpd) ipv6 prefix-list : Done length : (This will not be implemented by bgpd) metric : Done route-type : (This will not be implemented by bgpd) tag : Done local-preference : Done set as-path prepend : Done as-path tag : Not yet automatic-tag : (This will not be implemented by bgpd) community : Done large-community : Done large-comm-list : Done comm-list : Not yet dampning : Not yet default : (This will not be implemented by bgpd) interface : (This will not be implemented by bgpd) ip default : (This will not be implemented by bgpd) ip next-hop : Done ip precedence : (This will not be implemented by bgpd) ip tos : (This will not be implemented by bgpd) level : (This will not be implemented by bgpd) local-preference : Done metric : Done metric-type : Not yet origin : Done tag : Done weight : Done o Local extensions set ipv6 next-hop global: Done set ipv6 next-hop prefer-global: Done set ipv6 next-hop local : Done set as-path exclude : Done */ /* generic value manipulation to be shared in multiple rules */ #define RMAP_VALUE_SET 0 #define RMAP_VALUE_ADD 1 #define RMAP_VALUE_SUB 2 struct rmap_value { uint8_t action; uint8_t variable; uint32_t value; }; static int route_value_match(struct rmap_value *rv, uint32_t value) { if (rv->variable == 0 && value == rv->value) return RMAP_MATCH; return RMAP_NOMATCH; } static uint32_t route_value_adjust(struct rmap_value *rv, uint32_t current, struct peer *peer) { uint32_t value; switch (rv->variable) { case 1: value = peer->rtt; break; default: value = rv->value; break; } switch (rv->action) { case RMAP_VALUE_ADD: if (current > UINT32_MAX - value) return UINT32_MAX; return current + value; case RMAP_VALUE_SUB: if (current <= value) return 0; return current - value; default: return value; } } static void *route_value_compile(const char *arg) { uint8_t action = RMAP_VALUE_SET, var = 0; unsigned long larg = 0; char *endptr = NULL; struct rmap_value *rv; if (arg[0] == '+') { action = RMAP_VALUE_ADD; arg++; } else if (arg[0] == '-') { action = RMAP_VALUE_SUB; arg++; } if (all_digit(arg)) { errno = 0; larg = strtoul(arg, &endptr, 10); if (*arg == 0 || *endptr != 0 || errno || larg > UINT32_MAX) return NULL; } else { if (strcmp(arg, "rtt") == 0) var = 1; else return NULL; } rv = XMALLOC(MTYPE_ROUTE_MAP_COMPILED, sizeof(struct rmap_value)); rv->action = action; rv->variable = var; rv->value = larg; return rv; } static void route_value_free(void *rule) { XFREE(MTYPE_ROUTE_MAP_COMPILED, rule); } /* generic as path object to be shared in multiple rules */ static void *route_aspath_compile(const char *arg) { struct aspath *aspath; aspath = aspath_str2aspath(arg); if (!aspath) return NULL; return aspath; } static void route_aspath_free(void *rule) { struct aspath *aspath = rule; aspath_free(aspath); } struct bgp_match_peer_compiled { char *interface; union sockunion su; }; /* 'match peer (A.B.C.D|X:X::X:X|WORD)' */ /* Compares the peer specified in the 'match peer' clause with the peer received in bgp_path_info->peer. If it is the same, or if the peer structure received is a peer_group containing it, returns RMAP_MATCH. */ static enum route_map_cmd_result_t route_match_peer(void *rule, const struct prefix *prefix, route_map_object_t type, void *object) { struct bgp_match_peer_compiled *pc; union sockunion *su; union sockunion su_def = { .sin = {.sin_family = AF_INET, .sin_addr.s_addr = INADDR_ANY}}; struct peer_group *group; struct peer *peer; struct listnode *node, *nnode; if (type == RMAP_BGP) { pc = rule; su = &pc->su; peer = ((struct bgp_path_info *)object)->peer; if (pc->interface) { if (!peer->conf_if) return RMAP_NOMATCH; if (strcmp(peer->conf_if, pc->interface) == 0) return RMAP_MATCH; return RMAP_NOMATCH; } /* If su='0.0.0.0' (command 'match peer local'), and it's a NETWORK, REDISTRIBUTE, AGGREGATE-ADDRESS or DEFAULT_GENERATED route => return RMAP_MATCH */ if (sockunion_same(su, &su_def)) { int ret; if (CHECK_FLAG(peer->rmap_type, PEER_RMAP_TYPE_NETWORK) || CHECK_FLAG(peer->rmap_type, PEER_RMAP_TYPE_REDISTRIBUTE) || CHECK_FLAG(peer->rmap_type, PEER_RMAP_TYPE_AGGREGATE) || CHECK_FLAG(peer->rmap_type, PEER_RMAP_TYPE_DEFAULT)) ret = RMAP_MATCH; else ret = RMAP_NOMATCH; return ret; } if (!CHECK_FLAG(peer->sflags, PEER_STATUS_GROUP)) { if (sockunion_same(su, &peer->su)) return RMAP_MATCH; return RMAP_NOMATCH; } else { group = peer->group; for (ALL_LIST_ELEMENTS(group->peer, node, nnode, peer)) { if (sockunion_same(su, &peer->su)) return RMAP_MATCH; } return RMAP_NOMATCH; } } return RMAP_NOMATCH; } static void *route_match_peer_compile(const char *arg) { struct bgp_match_peer_compiled *pc; int ret; pc = XCALLOC(MTYPE_ROUTE_MAP_COMPILED, sizeof(struct bgp_match_peer_compiled)); ret = str2sockunion(strcmp(arg, "local") ? arg : "0.0.0.0", &pc->su); if (ret < 0) { pc->interface = XSTRDUP(MTYPE_ROUTE_MAP_COMPILED, arg); return pc; } return pc; } /* Free route map's compiled `ip address' value. */ static void route_match_peer_free(void *rule) { struct bgp_match_peer_compiled *pc = rule; XFREE(MTYPE_ROUTE_MAP_COMPILED, pc->interface); XFREE(MTYPE_ROUTE_MAP_COMPILED, rule); } /* Route map commands for ip address matching. */ struct route_map_rule_cmd route_match_peer_cmd = {"peer", route_match_peer, route_match_peer_compile, route_match_peer_free}; #if defined(HAVE_LUA) static enum route_map_cmd_result_t route_match_command(void *rule, const struct prefix *prefix, route_map_object_t type, void *object) { int status = RMAP_NOMATCH; u_int32_t locpref = 0; u_int32_t newlocpref = 0; enum lua_rm_status lrm_status; struct bgp_path_info *path = (struct bgp_path_info *)object; lua_State *L = lua_initialize("/etc/frr/lua.scr"); if (L == NULL) return status; /* * Setup the prefix information to pass in */ lua_setup_prefix_table(L, prefix); zlog_debug("Set up prefix table"); /* * Setup the bgp_path_info information */ lua_newtable(L); lua_pushinteger(L, path->attr->med); lua_setfield(L, -2, "metric"); lua_pushinteger(L, path->attr->nh_ifindex); lua_setfield(L, -2, "ifindex"); lua_pushstring(L, path->attr->aspath->str); lua_setfield(L, -2, "aspath"); lua_pushinteger(L, path->attr->local_pref); lua_setfield(L, -2, "localpref"); zlog_debug("%s %d", path->attr->aspath->str, path->attr->nh_ifindex); lua_setglobal(L, "nexthop"); zlog_debug("Set up nexthop information"); /* * Run the rule */ lrm_status = lua_run_rm_rule(L, rule); switch (lrm_status) { case LUA_RM_FAILURE: zlog_debug("RM_FAILURE"); break; case LUA_RM_NOMATCH: zlog_debug("RM_NOMATCH"); break; case LUA_RM_MATCH_AND_CHANGE: zlog_debug("MATCH AND CHANGE"); lua_getglobal(L, "nexthop"); path->attr->med = get_integer(L, "metric"); /* * This needs to be abstraced with the set function */ if (path->attr->flag & ATTR_FLAG_BIT(BGP_ATTR_LOCAL_PREF)) locpref = path->attr->local_pref; newlocpref = get_integer(L, "localpref"); if (newlocpref != locpref) { path->attr->flag |= ATTR_FLAG_BIT(BGP_ATTR_LOCAL_PREF); path->attr->local_pref = newlocpref; } status = RMAP_MATCH; break; case LUA_RM_MATCH: zlog_debug("MATCH ONLY"); status = RMAP_MATCH; break; } lua_close(L); return status; } static void *route_match_command_compile(const char *arg) { char *command; command = XSTRDUP(MTYPE_ROUTE_MAP_COMPILED, arg); return command; } static void route_match_command_free(void *rule) { XFREE(MTYPE_ROUTE_MAP_COMPILED, rule); } struct route_map_rule_cmd route_match_command_cmd = { "command", route_match_command, route_match_command_compile, route_match_command_free }; #endif /* `match ip address IP_ACCESS_LIST' */ /* Match function should return 1 if match is success else return zero. */ static enum route_map_cmd_result_t route_match_ip_address(void *rule, const struct prefix *prefix, route_map_object_t type, void *object) { struct access_list *alist; if (type == RMAP_BGP && prefix->family == AF_INET) { alist = access_list_lookup(AFI_IP, (char *)rule); if (alist == NULL) return RMAP_NOMATCH; return (access_list_apply(alist, prefix) == FILTER_DENY ? RMAP_NOMATCH : RMAP_MATCH); } return RMAP_NOMATCH; } /* Route map `ip address' match statement. `arg' should be access-list name. */ static void *route_match_ip_address_compile(const char *arg) { return XSTRDUP(MTYPE_ROUTE_MAP_COMPILED, arg); } /* Free route map's compiled `ip address' value. */ static void route_match_ip_address_free(void *rule) { XFREE(MTYPE_ROUTE_MAP_COMPILED, rule); } /* Route map commands for ip address matching. */ struct route_map_rule_cmd route_match_ip_address_cmd = { "ip address", route_match_ip_address, route_match_ip_address_compile, route_match_ip_address_free}; /* `match ip next-hop IP_ADDRESS' */ /* Match function return 1 if match is success else return zero. */ static enum route_map_cmd_result_t route_match_ip_next_hop(void *rule, const struct prefix *prefix, route_map_object_t type, void *object) { struct access_list *alist; struct bgp_path_info *path; struct prefix_ipv4 p; if (type == RMAP_BGP && prefix->family == AF_INET) { path = object; p.family = AF_INET; p.prefix = path->attr->nexthop; p.prefixlen = IPV4_MAX_BITLEN; alist = access_list_lookup(AFI_IP, (char *)rule); if (alist == NULL) return RMAP_NOMATCH; return (access_list_apply(alist, &p) == FILTER_DENY ? RMAP_NOMATCH : RMAP_MATCH); } return RMAP_NOMATCH; } /* Route map `ip next-hop' match statement. `arg' is access-list name. */ static void *route_match_ip_next_hop_compile(const char *arg) { return XSTRDUP(MTYPE_ROUTE_MAP_COMPILED, arg); } /* Free route map's compiled `ip address' value. */ static void route_match_ip_next_hop_free(void *rule) { XFREE(MTYPE_ROUTE_MAP_COMPILED, rule); } /* Route map commands for ip next-hop matching. */ struct route_map_rule_cmd route_match_ip_next_hop_cmd = { "ip next-hop", route_match_ip_next_hop, route_match_ip_next_hop_compile, route_match_ip_next_hop_free}; /* `match ip route-source ACCESS-LIST' */ /* Match function return 1 if match is success else return zero. */ static enum route_map_cmd_result_t route_match_ip_route_source(void *rule, const struct prefix *pfx, route_map_object_t type, void *object) { struct access_list *alist; struct bgp_path_info *path; struct peer *peer; struct prefix_ipv4 p; if (type == RMAP_BGP && pfx->family == AF_INET) { path = object; peer = path->peer; if (!peer || sockunion_family(&peer->su) != AF_INET) return RMAP_NOMATCH; p.family = AF_INET; p.prefix = peer->su.sin.sin_addr; p.prefixlen = IPV4_MAX_BITLEN; alist = access_list_lookup(AFI_IP, (char *)rule); if (alist == NULL) return RMAP_NOMATCH; return (access_list_apply(alist, &p) == FILTER_DENY ? RMAP_NOMATCH : RMAP_MATCH); } return RMAP_NOMATCH; } /* Route map `ip route-source' match statement. `arg' is access-list name. */ static void *route_match_ip_route_source_compile(const char *arg) { return XSTRDUP(MTYPE_ROUTE_MAP_COMPILED, arg); } /* Free route map's compiled `ip address' value. */ static void route_match_ip_route_source_free(void *rule) { XFREE(MTYPE_ROUTE_MAP_COMPILED, rule); } /* Route map commands for ip route-source matching. */ struct route_map_rule_cmd route_match_ip_route_source_cmd = { "ip route-source", route_match_ip_route_source, route_match_ip_route_source_compile, route_match_ip_route_source_free}; static enum route_map_cmd_result_t route_match_prefix_list_flowspec(afi_t afi, struct prefix_list *plist, const struct prefix *p) { int ret; struct bgp_pbr_entry_main api; memset(&api, 0, sizeof(api)); /* extract match from flowspec entries */ ret = bgp_flowspec_match_rules_fill( (uint8_t *)p->u.prefix_flowspec.ptr, p->u.prefix_flowspec.prefixlen, &api); if (ret < 0) return RMAP_NOMATCH; if (api.match_bitmask & PREFIX_DST_PRESENT || api.match_bitmask_iprule & PREFIX_DST_PRESENT) { if (family2afi((&api.dst_prefix)->family) != afi) return RMAP_NOMATCH; return prefix_list_apply(plist, &api.dst_prefix) == PREFIX_DENY ? RMAP_NOMATCH : RMAP_MATCH; } else if (api.match_bitmask & PREFIX_SRC_PRESENT || api.match_bitmask_iprule & PREFIX_SRC_PRESENT) { if (family2afi((&api.src_prefix)->family) != afi) return RMAP_NOMATCH; return (prefix_list_apply(plist, &api.src_prefix) == PREFIX_DENY ? RMAP_NOMATCH : RMAP_MATCH); } return RMAP_NOMATCH; } static enum route_map_cmd_result_t route_match_address_prefix_list(void *rule, afi_t afi, const struct prefix *prefix, route_map_object_t type, void *object) { struct prefix_list *plist; if (type != RMAP_BGP) return RMAP_NOMATCH; plist = prefix_list_lookup(afi, (char *)rule); if (plist == NULL) return RMAP_NOMATCH; if (prefix->family == AF_FLOWSPEC) return route_match_prefix_list_flowspec(afi, plist, prefix); return (prefix_list_apply(plist, prefix) == PREFIX_DENY ? RMAP_NOMATCH : RMAP_MATCH); } static enum route_map_cmd_result_t route_match_ip_address_prefix_list(void *rule, const struct prefix *prefix, route_map_object_t type, void *object) { return route_match_address_prefix_list(rule, AFI_IP, prefix, type, object); } static void *route_match_ip_address_prefix_list_compile(const char *arg) { return XSTRDUP(MTYPE_ROUTE_MAP_COMPILED, arg); } static void route_match_ip_address_prefix_list_free(void *rule) { XFREE(MTYPE_ROUTE_MAP_COMPILED, rule); } struct route_map_rule_cmd route_match_ip_address_prefix_list_cmd = { "ip address prefix-list", route_match_ip_address_prefix_list, route_match_ip_address_prefix_list_compile, route_match_ip_address_prefix_list_free}; /* `match ip next-hop prefix-list PREFIX_LIST' */ static enum route_map_cmd_result_t route_match_ip_next_hop_prefix_list(void *rule, const struct prefix *prefix, route_map_object_t type, void *object) { struct prefix_list *plist; struct bgp_path_info *path; struct prefix_ipv4 p; if (type == RMAP_BGP && prefix->family == AF_INET) { path = object; p.family = AF_INET; p.prefix = path->attr->nexthop; p.prefixlen = IPV4_MAX_BITLEN; plist = prefix_list_lookup(AFI_IP, (char *)rule); if (plist == NULL) return RMAP_NOMATCH; return (prefix_list_apply(plist, &p) == PREFIX_DENY ? RMAP_NOMATCH : RMAP_MATCH); } return RMAP_NOMATCH; } static void *route_match_ip_next_hop_prefix_list_compile(const char *arg) { return XSTRDUP(MTYPE_ROUTE_MAP_COMPILED, arg); } static void route_match_ip_next_hop_prefix_list_free(void *rule) { XFREE(MTYPE_ROUTE_MAP_COMPILED, rule); } struct route_map_rule_cmd route_match_ip_next_hop_prefix_list_cmd = { "ip next-hop prefix-list", route_match_ip_next_hop_prefix_list, route_match_ip_next_hop_prefix_list_compile, route_match_ip_next_hop_prefix_list_free}; /* `match ip next-hop type ' */ static enum route_map_cmd_result_t route_match_ip_next_hop_type(void *rule, const struct prefix *prefix, route_map_object_t type, void *object) { struct bgp_path_info *path; if (type == RMAP_BGP && prefix->family == AF_INET) { path = (struct bgp_path_info *)object; if (!path || !path->attr) return RMAP_NOMATCH; /* If nexthop interface's index can't be resolved and nexthop is set to any address then mark it as type `blackhole`. This logic works for matching kernel/static routes like: `ip route add blackhole 10.0.0.1`. */ if (path->attr->nexthop.s_addr == INADDR_ANY && !path->attr->nh_ifindex) return RMAP_MATCH; } return RMAP_NOMATCH; } static void *route_match_ip_next_hop_type_compile(const char *arg) { return XSTRDUP(MTYPE_ROUTE_MAP_COMPILED, arg); } static void route_match_ip_next_hop_type_free(void *rule) { XFREE(MTYPE_ROUTE_MAP_COMPILED, rule); } static struct route_map_rule_cmd route_match_ip_next_hop_type_cmd = { "ip next-hop type", route_match_ip_next_hop_type, route_match_ip_next_hop_type_compile, route_match_ip_next_hop_type_free}; /* `match ip route-source prefix-list PREFIX_LIST' */ static enum route_map_cmd_result_t route_match_ip_route_source_prefix_list(void *rule, const struct prefix *prefix, route_map_object_t type, void *object) { struct prefix_list *plist; struct bgp_path_info *path; struct peer *peer; struct prefix_ipv4 p; if (type == RMAP_BGP && prefix->family == AF_INET) { path = object; peer = path->peer; if (!peer || sockunion_family(&peer->su) != AF_INET) return RMAP_NOMATCH; p.family = AF_INET; p.prefix = peer->su.sin.sin_addr; p.prefixlen = IPV4_MAX_BITLEN; plist = prefix_list_lookup(AFI_IP, (char *)rule); if (plist == NULL) return RMAP_NOMATCH; return (prefix_list_apply(plist, &p) == PREFIX_DENY ? RMAP_NOMATCH : RMAP_MATCH); } return RMAP_NOMATCH; } static void *route_match_ip_route_source_prefix_list_compile(const char *arg) { return XSTRDUP(MTYPE_ROUTE_MAP_COMPILED, arg); } static void route_match_ip_route_source_prefix_list_free(void *rule) { XFREE(MTYPE_ROUTE_MAP_COMPILED, rule); } struct route_map_rule_cmd route_match_ip_route_source_prefix_list_cmd = { "ip route-source prefix-list", route_match_ip_route_source_prefix_list, route_match_ip_route_source_prefix_list_compile, route_match_ip_route_source_prefix_list_free}; /* `match evpn default-route' */ /* Match function should return 1 if match is success else 0 */ static enum route_map_cmd_result_t route_match_evpn_default_route(void *rule, const struct prefix *p, route_map_object_t type, void *object) { if (type == RMAP_BGP && is_evpn_prefix_default(p)) return RMAP_MATCH; return RMAP_NOMATCH; } /* Route map commands for default-route matching. */ struct route_map_rule_cmd route_match_evpn_default_route_cmd = { "evpn default-route", route_match_evpn_default_route, NULL, NULL}; /* `match mac address MAC_ACCESS_LIST' */ /* Match function should return 1 if match is success else return zero. */ static enum route_map_cmd_result_t route_match_mac_address(void *rule, const struct prefix *prefix, route_map_object_t type, void *object) { struct access_list *alist; struct prefix p; if (type == RMAP_BGP) { alist = access_list_lookup(AFI_L2VPN, (char *)rule); if (alist == NULL) return RMAP_NOMATCH; if (prefix->u.prefix_evpn.route_type != BGP_EVPN_MAC_IP_ROUTE) return RMAP_NOMATCH; p.family = AF_ETHERNET; p.prefixlen = ETH_ALEN * 8; p.u.prefix_eth = prefix->u.prefix_evpn.macip_addr.mac; return (access_list_apply(alist, &p) == FILTER_DENY ? RMAP_NOMATCH : RMAP_MATCH); } return RMAP_NOMATCH; } /* Route map `mac address' match statement. `arg' should be access-list name. */ static void *route_match_mac_address_compile(const char *arg) { return XSTRDUP(MTYPE_ROUTE_MAP_COMPILED, arg); } /* Free route map's compiled `ip address' value. */ static void route_match_mac_address_free(void *rule) { XFREE(MTYPE_ROUTE_MAP_COMPILED, rule); } /* Route map commands for mac address matching. */ struct route_map_rule_cmd route_match_mac_address_cmd = { "mac address", route_match_mac_address, route_match_mac_address_compile, route_match_mac_address_free}; /* * Match function returns: * ...RMAP_MATCH if match is found. * ...RMAP_NOMATCH if match is not found. * ...RMAP_NOOP to ignore this match check. */ static enum route_map_cmd_result_t route_match_vni(void *rule, const struct prefix *prefix, route_map_object_t type, void *object) { vni_t vni = 0; unsigned int label_cnt = 0; struct bgp_path_info *path = NULL; struct prefix_evpn *evp = (struct prefix_evpn *) prefix; if (type == RMAP_BGP) { vni = *((vni_t *)rule); path = (struct bgp_path_info *)object; /* * This rmap filter is valid for vxlan tunnel type only. * For any other tunnel type, return noop to ignore * this check. */ if (path->attr && path->attr->encap_tunneltype != BGP_ENCAP_TYPE_VXLAN) return RMAP_NOOP; /* * Apply filter to type 1, 2, 5 routes only. * Other route types do not have vni label. */ if (evp && (evp->prefix.route_type != BGP_EVPN_AD_ROUTE && evp->prefix.route_type != BGP_EVPN_MAC_IP_ROUTE && evp->prefix.route_type != BGP_EVPN_IP_PREFIX_ROUTE)) return RMAP_NOOP; if (path->extra == NULL) return RMAP_NOMATCH; for ( ; label_cnt < BGP_MAX_LABELS && label_cnt < path->extra->num_labels; label_cnt++) { if (vni == label2vni(&path->extra->label[label_cnt])) return RMAP_MATCH; } } return RMAP_NOMATCH; } /* Route map `vni' match statement. */ static void *route_match_vni_compile(const char *arg) { vni_t *vni = NULL; char *end = NULL; vni = XMALLOC(MTYPE_ROUTE_MAP_COMPILED, sizeof(vni_t)); *vni = strtoul(arg, &end, 10); if (*end != '\0') { XFREE(MTYPE_ROUTE_MAP_COMPILED, vni); return NULL; } return vni; } /* Free route map's compiled `vni' value. */ static void route_match_vni_free(void *rule) { XFREE(MTYPE_ROUTE_MAP_COMPILED, rule); } /* Route map commands for vni matching. */ struct route_map_rule_cmd route_match_evpn_vni_cmd = { "evpn vni", route_match_vni, route_match_vni_compile, route_match_vni_free}; /* `match evpn route-type' */ /* Match function should return 1 if match is success else return zero. */ static enum route_map_cmd_result_t route_match_evpn_route_type(void *rule, const struct prefix *pfx, route_map_object_t type, void *object) { uint8_t route_type = 0; if (type == RMAP_BGP) { route_type = *((uint8_t *)rule); if (route_type == pfx->u.prefix_evpn.route_type) return RMAP_MATCH; } return RMAP_NOMATCH; } /* Route map `route-type' match statement. */ static void *route_match_evpn_route_type_compile(const char *arg) { uint8_t *route_type = NULL; route_type = XMALLOC(MTYPE_ROUTE_MAP_COMPILED, sizeof(uint8_t)); if (strncmp(arg, "ma", 2) == 0) *route_type = BGP_EVPN_MAC_IP_ROUTE; else if (strncmp(arg, "mu", 2) == 0) *route_type = BGP_EVPN_IMET_ROUTE; else *route_type = BGP_EVPN_IP_PREFIX_ROUTE; return route_type; } /* Free route map's compiled `route-type' value. */ static void route_match_evpn_route_type_free(void *rule) { XFREE(MTYPE_ROUTE_MAP_COMPILED, rule); } /* Route map commands for evpn route-type matching. */ struct route_map_rule_cmd route_match_evpn_route_type_cmd = { "evpn route-type", route_match_evpn_route_type, route_match_evpn_route_type_compile, route_match_evpn_route_type_free}; /* Route map commands for VRF route leak with source vrf matching */ static enum route_map_cmd_result_t route_match_vrl_source_vrf(void *rule, const struct prefix *prefix, route_map_object_t type, void *object) { struct bgp_path_info *path; char *vrf_name; if (type == RMAP_BGP) { vrf_name = rule; path = (struct bgp_path_info *)object; if (strncmp(vrf_name, "n/a", VRF_NAMSIZ) == 0) return RMAP_NOMATCH; if (path->extra == NULL) return RMAP_NOMATCH; if (strncmp(vrf_name, vrf_id_to_name( path->extra->bgp_orig->vrf_id), VRF_NAMSIZ) == 0) return RMAP_MATCH; } return RMAP_NOMATCH; } static void *route_match_vrl_source_vrf_compile(const char *arg) { uint8_t *vrf_name = NULL; vrf_name = XSTRDUP(MTYPE_ROUTE_MAP_COMPILED, arg); return vrf_name; } /* Free route map's compiled `route-type' value. */ static void route_match_vrl_source_vrf_free(void *rule) { XFREE(MTYPE_ROUTE_MAP_COMPILED, rule); } struct route_map_rule_cmd route_match_vrl_source_vrf_cmd = { "source-vrf", route_match_vrl_source_vrf, route_match_vrl_source_vrf_compile, route_match_vrl_source_vrf_free}; /* `match local-preference LOCAL-PREF' */ /* Match function return 1 if match is success else return zero. */ static enum route_map_cmd_result_t route_match_local_pref(void *rule, const struct prefix *prefix, route_map_object_t type, void *object) { uint32_t *local_pref; struct bgp_path_info *path; if (type == RMAP_BGP) { local_pref = rule; path = object; if (path->attr->local_pref == *local_pref) return RMAP_MATCH; else return RMAP_NOMATCH; } return RMAP_NOMATCH; } /* Route map `match local-preference' match statement. `arg' is local-pref value */ static void *route_match_local_pref_compile(const char *arg) { uint32_t *local_pref; char *endptr = NULL; unsigned long tmpval; /* Locpref value shoud be integer. */ if (!all_digit(arg)) return NULL; errno = 0; tmpval = strtoul(arg, &endptr, 10); if (*endptr != '\0' || errno || tmpval > UINT32_MAX) return NULL; local_pref = XMALLOC(MTYPE_ROUTE_MAP_COMPILED, sizeof(uint32_t)); *local_pref = tmpval; return local_pref; } /* Free route map's compiled `match local-preference' value. */ static void route_match_local_pref_free(void *rule) { XFREE(MTYPE_ROUTE_MAP_COMPILED, rule); } /* Route map commands for metric matching. */ struct route_map_rule_cmd route_match_local_pref_cmd = { "local-preference", route_match_local_pref, route_match_local_pref_compile, route_match_local_pref_free}; /* `match metric METRIC' */ /* Match function return 1 if match is success else return zero. */ static enum route_map_cmd_result_t route_match_metric(void *rule, const struct prefix *prefix, route_map_object_t type, void *object) { struct rmap_value *rv; struct bgp_path_info *path; if (type == RMAP_BGP) { rv = rule; path = object; return route_value_match(rv, path->attr->med); } return RMAP_NOMATCH; } /* Route map commands for metric matching. */ struct route_map_rule_cmd route_match_metric_cmd = { "metric", route_match_metric, route_value_compile, route_value_free, }; /* `match as-path ASPATH' */ /* Match function for as-path match. I assume given object is */ static enum route_map_cmd_result_t route_match_aspath(void *rule, const struct prefix *prefix, route_map_object_t type, void *object) { struct as_list *as_list; struct bgp_path_info *path; if (type == RMAP_BGP) { as_list = as_list_lookup((char *)rule); if (as_list == NULL) return RMAP_NOMATCH; path = object; /* Perform match. */ return ((as_list_apply(as_list, path->attr->aspath) == AS_FILTER_DENY) ? RMAP_NOMATCH : RMAP_MATCH); } return RMAP_NOMATCH; } /* Compile function for as-path match. */ static void *route_match_aspath_compile(const char *arg) { return XSTRDUP(MTYPE_ROUTE_MAP_COMPILED, arg); } /* Compile function for as-path match. */ static void route_match_aspath_free(void *rule) { XFREE(MTYPE_ROUTE_MAP_COMPILED, rule); } /* Route map commands for aspath matching. */ struct route_map_rule_cmd route_match_aspath_cmd = { "as-path", route_match_aspath, route_match_aspath_compile, route_match_aspath_free}; /* `match community COMMUNIY' */ struct rmap_community { char *name; uint32_t name_hash; int exact; }; /* Match function for community match. */ static enum route_map_cmd_result_t route_match_community(void *rule, const struct prefix *prefix, route_map_object_t type, void *object) { struct community_list *list; struct bgp_path_info *path; struct rmap_community *rcom = rule; if (type == RMAP_BGP) { path = object; rcom = rule; list = community_list_lookup(bgp_clist, rcom->name, rcom->name_hash, COMMUNITY_LIST_MASTER); if (!list) return RMAP_NOMATCH; if (rcom->exact) { if (community_list_exact_match(path->attr->community, list)) return RMAP_MATCH; } else { if (community_list_match(path->attr->community, list)) return RMAP_MATCH; } } return RMAP_NOMATCH; } /* Compile function for community match. */ static void *route_match_community_compile(const char *arg) { struct rmap_community *rcom; int len; char *p; rcom = XCALLOC(MTYPE_ROUTE_MAP_COMPILED, sizeof(struct rmap_community)); p = strchr(arg, ' '); if (p) { len = p - arg; rcom->name = XCALLOC(MTYPE_ROUTE_MAP_COMPILED, len + 1); memcpy(rcom->name, arg, len); rcom->exact = 1; } else { rcom->name = XSTRDUP(MTYPE_ROUTE_MAP_COMPILED, arg); rcom->exact = 0; } rcom->name_hash = bgp_clist_hash_key(rcom->name); return rcom; } /* Compile function for community match. */ static void route_match_community_free(void *rule) { struct rmap_community *rcom = rule; XFREE(MTYPE_ROUTE_MAP_COMPILED, rcom->name); XFREE(MTYPE_ROUTE_MAP_COMPILED, rcom); } /* Route map commands for community matching. */ struct route_map_rule_cmd route_match_community_cmd = { "community", route_match_community, route_match_community_compile, route_match_community_free}; /* Match function for lcommunity match. */ static enum route_map_cmd_result_t route_match_lcommunity(void *rule, const struct prefix *prefix, route_map_object_t type, void *object) { struct community_list *list; struct bgp_path_info *path; struct rmap_community *rcom = rule; if (type == RMAP_BGP) { path = object; list = community_list_lookup(bgp_clist, rcom->name, rcom->name_hash, LARGE_COMMUNITY_LIST_MASTER); if (!list) return RMAP_NOMATCH; if (rcom->exact) { if (lcommunity_list_exact_match( path->attr->lcommunity, list)) return RMAP_MATCH; } else { if (lcommunity_list_match( path->attr->lcommunity, list)) return RMAP_MATCH; } } return RMAP_NOMATCH; } /* Compile function for community match. */ static void *route_match_lcommunity_compile(const char *arg) { struct rmap_community *rcom; int len; char *p; rcom = XCALLOC(MTYPE_ROUTE_MAP_COMPILED, sizeof(struct rmap_community)); p = strchr(arg, ' '); if (p) { len = p - arg; rcom->name = XCALLOC(MTYPE_ROUTE_MAP_COMPILED, len + 1); memcpy(rcom->name, arg, len); rcom->exact = 1; } else { rcom->name = XSTRDUP(MTYPE_ROUTE_MAP_COMPILED, arg); rcom->exact = 0; } rcom->name_hash = bgp_clist_hash_key(rcom->name); return rcom; } /* Compile function for community match. */ static void route_match_lcommunity_free(void *rule) { struct rmap_community *rcom = rule; XFREE(MTYPE_ROUTE_MAP_COMPILED, rcom->name); XFREE(MTYPE_ROUTE_MAP_COMPILED, rcom); } /* Route map commands for community matching. */ struct route_map_rule_cmd route_match_lcommunity_cmd = { "large-community", route_match_lcommunity, route_match_lcommunity_compile, route_match_lcommunity_free}; /* Match function for extcommunity match. */ static enum route_map_cmd_result_t route_match_ecommunity(void *rule, const struct prefix *prefix, route_map_object_t type, void *object) { struct community_list *list; struct bgp_path_info *path; struct rmap_community *rcom = rule; if (type == RMAP_BGP) { path = object; list = community_list_lookup(bgp_clist, rcom->name, rcom->name_hash, EXTCOMMUNITY_LIST_MASTER); if (!list) return RMAP_NOMATCH; if (ecommunity_list_match(path->attr->ecommunity, list)) return RMAP_MATCH; } return RMAP_NOMATCH; } /* Compile function for extcommunity match. */ static void *route_match_ecommunity_compile(const char *arg) { struct rmap_community *rcom; rcom = XCALLOC(MTYPE_ROUTE_MAP_COMPILED, sizeof(struct rmap_community)); rcom->name = XSTRDUP(MTYPE_ROUTE_MAP_COMPILED, arg); rcom->name_hash = bgp_clist_hash_key(rcom->name); return rcom; } /* Compile function for extcommunity match. */ static void route_match_ecommunity_free(void *rule) { struct rmap_community *rcom = rule; XFREE(MTYPE_ROUTE_MAP_COMPILED, rcom->name); XFREE(MTYPE_ROUTE_MAP_COMPILED, rcom); } /* Route map commands for community matching. */ struct route_map_rule_cmd route_match_ecommunity_cmd = { "extcommunity", route_match_ecommunity, route_match_ecommunity_compile, route_match_ecommunity_free}; /* `match nlri` and `set nlri` are replaced by `address-family ipv4` and `address-family vpnv4'. */ /* `match origin' */ static enum route_map_cmd_result_t route_match_origin(void *rule, const struct prefix *prefix, route_map_object_t type, void *object) { uint8_t *origin; struct bgp_path_info *path; if (type == RMAP_BGP) { origin = rule; path = object; if (path->attr->origin == *origin) return RMAP_MATCH; } return RMAP_NOMATCH; } static void *route_match_origin_compile(const char *arg) { uint8_t *origin; origin = XMALLOC(MTYPE_ROUTE_MAP_COMPILED, sizeof(uint8_t)); if (strcmp(arg, "igp") == 0) *origin = 0; else if (strcmp(arg, "egp") == 0) *origin = 1; else *origin = 2; return origin; } /* Free route map's compiled `ip address' value. */ static void route_match_origin_free(void *rule) { XFREE(MTYPE_ROUTE_MAP_COMPILED, rule); } /* Route map commands for origin matching. */ struct route_map_rule_cmd route_match_origin_cmd = { "origin", route_match_origin, route_match_origin_compile, route_match_origin_free}; /* match probability { */ static enum route_map_cmd_result_t route_match_probability(void *rule, const struct prefix *prefix, route_map_object_t type, void *object) { long r = random(); switch (*(long *)rule) { case 0: break; case RAND_MAX: return RMAP_MATCH; default: if (r < *(long *)rule) { return RMAP_MATCH; } } return RMAP_NOMATCH; } static void *route_match_probability_compile(const char *arg) { long *lobule; unsigned perc; perc = atoi(arg); lobule = XMALLOC(MTYPE_ROUTE_MAP_COMPILED, sizeof(long)); switch (perc) { case 0: *lobule = 0; break; case 100: *lobule = RAND_MAX; break; default: *lobule = RAND_MAX / 100 * perc; } return lobule; } static void route_match_probability_free(void *rule) { XFREE(MTYPE_ROUTE_MAP_COMPILED, rule); } struct route_map_rule_cmd route_match_probability_cmd = { "probability", route_match_probability, route_match_probability_compile, route_match_probability_free}; /* `match interface IFNAME' */ /* Match function should return 1 if match is success else return zero. */ static enum route_map_cmd_result_t route_match_interface(void *rule, const struct prefix *prefix, route_map_object_t type, void *object) { struct interface *ifp; struct bgp_path_info *path; if (type == RMAP_BGP) { path = object; if (!path || !path->attr) return RMAP_NOMATCH; ifp = if_lookup_by_name_all_vrf((char *)rule); if (ifp == NULL || ifp->ifindex != path->attr->nh_ifindex) return RMAP_NOMATCH; return RMAP_MATCH; } return RMAP_NOMATCH; } /* Route map `interface' match statement. `arg' should be interface name. */ static void *route_match_interface_compile(const char *arg) { return XSTRDUP(MTYPE_ROUTE_MAP_COMPILED, arg); } /* Free route map's compiled `interface' value. */ static void route_match_interface_free(void *rule) { XFREE(MTYPE_ROUTE_MAP_COMPILED, rule); } /* Route map commands for ip address matching. */ struct route_map_rule_cmd route_match_interface_cmd = { "interface", route_match_interface, route_match_interface_compile, route_match_interface_free}; /* } */ /* `set ip next-hop IP_ADDRESS' */ /* Match function return 1 if match is success else return zero. */ static enum route_map_cmd_result_t route_match_tag(void *rule, const struct prefix *prefix, route_map_object_t type, void *object) { route_tag_t *tag; struct bgp_path_info *path; if (type == RMAP_BGP) { tag = rule; path = object; return ((path->attr->tag == *tag) ? RMAP_MATCH : RMAP_NOMATCH); } return RMAP_NOMATCH; } /* Route map commands for tag matching. */ static struct route_map_rule_cmd route_match_tag_cmd = { "tag", route_match_tag, route_map_rule_tag_compile, route_map_rule_tag_free, }; /* Set nexthop to object. ojbect must be pointer to struct attr. */ struct rmap_ip_nexthop_set { struct in_addr *address; int peer_address; int unchanged; }; static enum route_map_cmd_result_t route_set_ip_nexthop(void *rule, const struct prefix *prefix, route_map_object_t type, void *object) { struct rmap_ip_nexthop_set *rins = rule; struct bgp_path_info *path; struct peer *peer; if (type != RMAP_BGP) return RMAP_OKAY; if (prefix->family == AF_INET6) return RMAP_OKAY; path = object; peer = path->peer; if (rins->unchanged) { SET_FLAG(path->attr->rmap_change_flags, BATTR_RMAP_NEXTHOP_UNCHANGED); } else if (rins->peer_address) { if ((CHECK_FLAG(peer->rmap_type, PEER_RMAP_TYPE_IN) || CHECK_FLAG(peer->rmap_type, PEER_RMAP_TYPE_IMPORT)) && peer->su_remote && sockunion_family(peer->su_remote) == AF_INET) { path->attr->nexthop.s_addr = sockunion2ip(peer->su_remote); path->attr->flag |= ATTR_FLAG_BIT(BGP_ATTR_NEXT_HOP); } else if (CHECK_FLAG(peer->rmap_type, PEER_RMAP_TYPE_OUT)) { /* The next hop value will be set as part of * packet rewrite. Set the flags here to indicate * that rewrite needs to be done. * Also, clear the value. */ SET_FLAG(path->attr->rmap_change_flags, BATTR_RMAP_NEXTHOP_PEER_ADDRESS); path->attr->nexthop.s_addr = 0; } } else { /* Set next hop value. */ path->attr->flag |= ATTR_FLAG_BIT(BGP_ATTR_NEXT_HOP); path->attr->nexthop = *rins->address; SET_FLAG(path->attr->rmap_change_flags, BATTR_RMAP_IPV4_NHOP_CHANGED); /* case for MP-BGP : MPLS VPN */ path->attr->mp_nexthop_global_in = *rins->address; path->attr->mp_nexthop_len = sizeof(*rins->address); } return RMAP_OKAY; } /* Route map `ip nexthop' compile function. Given string is converted to struct in_addr structure. */ static void *route_set_ip_nexthop_compile(const char *arg) { struct rmap_ip_nexthop_set *rins; struct in_addr *address = NULL; int peer_address = 0; int unchanged = 0; int ret; if (strcmp(arg, "peer-address") == 0) peer_address = 1; else if (strcmp(arg, "unchanged") == 0) unchanged = 1; else { address = XMALLOC(MTYPE_ROUTE_MAP_COMPILED, sizeof(struct in_addr)); ret = inet_aton(arg, address); if (ret == 0) { XFREE(MTYPE_ROUTE_MAP_COMPILED, address); return NULL; } } rins = XCALLOC(MTYPE_ROUTE_MAP_COMPILED, sizeof(struct rmap_ip_nexthop_set)); rins->address = address; rins->peer_address = peer_address; rins->unchanged = unchanged; return rins; } /* Free route map's compiled `ip nexthop' value. */ static void route_set_ip_nexthop_free(void *rule) { struct rmap_ip_nexthop_set *rins = rule; XFREE(MTYPE_ROUTE_MAP_COMPILED, rins->address); XFREE(MTYPE_ROUTE_MAP_COMPILED, rins); } /* Route map commands for ip nexthop set. */ struct route_map_rule_cmd route_set_ip_nexthop_cmd = { "ip next-hop", route_set_ip_nexthop, route_set_ip_nexthop_compile, route_set_ip_nexthop_free}; /* `set local-preference LOCAL_PREF' */ /* Set local preference. */ static enum route_map_cmd_result_t route_set_local_pref(void *rule, const struct prefix *prefix, route_map_object_t type, void *object) { struct rmap_value *rv; struct bgp_path_info *path; uint32_t locpref = 0; if (type == RMAP_BGP) { /* Fetch routemap's rule information. */ rv = rule; path = object; /* Set local preference value. */ if (path->attr->flag & ATTR_FLAG_BIT(BGP_ATTR_LOCAL_PREF)) locpref = path->attr->local_pref; path->attr->flag |= ATTR_FLAG_BIT(BGP_ATTR_LOCAL_PREF); path->attr->local_pref = route_value_adjust(rv, locpref, path->peer); } return RMAP_OKAY; } /* Set local preference rule structure. */ struct route_map_rule_cmd route_set_local_pref_cmd = { "local-preference", route_set_local_pref, route_value_compile, route_value_free, }; /* `set weight WEIGHT' */ /* Set weight. */ static enum route_map_cmd_result_t route_set_weight(void *rule, const struct prefix *prefix, route_map_object_t type, void *object) { struct rmap_value *rv; struct bgp_path_info *path; if (type == RMAP_BGP) { /* Fetch routemap's rule information. */ rv = rule; path = object; /* Set weight value. */ path->attr->weight = route_value_adjust(rv, 0, path->peer); } return RMAP_OKAY; } /* Set local preference rule structure. */ struct route_map_rule_cmd route_set_weight_cmd = { "weight", route_set_weight, route_value_compile, route_value_free, }; /* `set metric METRIC' */ /* Set metric to attribute. */ static enum route_map_cmd_result_t route_set_metric(void *rule, const struct prefix *prefix, route_map_object_t type, void *object) { struct rmap_value *rv; struct bgp_path_info *path; uint32_t med = 0; if (type == RMAP_BGP) { /* Fetch routemap's rule information. */ rv = rule; path = object; if (path->attr->flag & ATTR_FLAG_BIT(BGP_ATTR_MULTI_EXIT_DISC)) med = path->attr->med; path->attr->med = route_value_adjust(rv, med, path->peer); path->attr->flag |= ATTR_FLAG_BIT(BGP_ATTR_MULTI_EXIT_DISC); } return RMAP_OKAY; } /* Set metric rule structure. */ struct route_map_rule_cmd route_set_metric_cmd = { "metric", route_set_metric, route_value_compile, route_value_free, }; /* `set as-path prepend ASPATH' */ /* For AS path prepend mechanism. */ static enum route_map_cmd_result_t route_set_aspath_prepend(void *rule, const struct prefix *prefix, route_map_object_t type, void *object) { struct aspath *aspath; struct aspath *new; struct bgp_path_info *path; if (type == RMAP_BGP) { path = object; if (path->attr->aspath->refcnt) new = aspath_dup(path->attr->aspath); else new = path->attr->aspath; if ((uintptr_t)rule > 10) { aspath = rule; aspath_prepend(aspath, new); } else { as_t as = aspath_leftmost(new); if (!as) as = path->peer->as; new = aspath_add_seq_n(new, as, (uintptr_t)rule); } path->attr->aspath = new; } return RMAP_OKAY; } static void *route_set_aspath_prepend_compile(const char *arg) { unsigned int num; if (sscanf(arg, "last-as %u", &num) == 1 && num > 0 && num <= 10) return (void *)(uintptr_t)num; return route_aspath_compile(arg); } static void route_set_aspath_prepend_free(void *rule) { if ((uintptr_t)rule > 10) route_aspath_free(rule); } /* Set as-path prepend rule structure. */ struct route_map_rule_cmd route_set_aspath_prepend_cmd = { "as-path prepend", route_set_aspath_prepend, route_set_aspath_prepend_compile, route_set_aspath_prepend_free, }; /* `set as-path exclude ASn' */ /* For ASN exclude mechanism. * Iterate over ASns requested and filter them from the given AS_PATH one by * one. * Make a deep copy of existing AS_PATH, but for the first ASn only. */ static enum route_map_cmd_result_t route_set_aspath_exclude(void *rule, const struct prefix *dummy, route_map_object_t type, void *object) { struct aspath *new_path, *exclude_path; struct bgp_path_info *path; if (type == RMAP_BGP) { exclude_path = rule; path = object; if (path->attr->aspath->refcnt) new_path = aspath_dup(path->attr->aspath); else new_path = path->attr->aspath; path->attr->aspath = aspath_filter_exclude(new_path, exclude_path); } return RMAP_OKAY; } /* Set ASn exlude rule structure. */ struct route_map_rule_cmd route_set_aspath_exclude_cmd = { "as-path exclude", route_set_aspath_exclude, route_aspath_compile, route_aspath_free, }; /* `set community COMMUNITY' */ struct rmap_com_set { struct community *com; int additive; int none; }; /* For community set mechanism. */ static enum route_map_cmd_result_t route_set_community(void *rule, const struct prefix *prefix, route_map_object_t type, void *object) { struct rmap_com_set *rcs; struct bgp_path_info *path; struct attr *attr; struct community *new = NULL; struct community *old; struct community *merge; if (type == RMAP_BGP) { rcs = rule; path = object; attr = path->attr; old = attr->community; /* "none" case. */ if (rcs->none) { attr->flag &= ~(ATTR_FLAG_BIT(BGP_ATTR_COMMUNITIES)); attr->community = NULL; /* See the longer comment down below. */ if (old && old->refcnt == 0) community_free(&old); return RMAP_OKAY; } /* "additive" case. */ if (rcs->additive && old) { merge = community_merge(community_dup(old), rcs->com); new = community_uniq_sort(merge); community_free(&merge); } else new = community_dup(rcs->com); /* HACK: if the old community is not intern'd, * we should free it here, or all reference to it may be * lost. * Really need to cleanup attribute caching sometime. */ if (old && old->refcnt == 0) community_free(&old); /* will be interned by caller if required */ attr->community = new; attr->flag |= ATTR_FLAG_BIT(BGP_ATTR_COMMUNITIES); } return RMAP_OKAY; } /* Compile function for set community. */ static void *route_set_community_compile(const char *arg) { struct rmap_com_set *rcs; struct community *com = NULL; char *sp; int additive = 0; int none = 0; if (strcmp(arg, "none") == 0) none = 1; else { sp = strstr(arg, "additive"); if (sp && sp > arg) { /* "additive" keyword is included. */ additive = 1; *(sp - 1) = '\0'; } com = community_str2com(arg); if (additive) *(sp - 1) = ' '; if (!com) return NULL; } rcs = XCALLOC(MTYPE_ROUTE_MAP_COMPILED, sizeof(struct rmap_com_set)); rcs->com = com; rcs->additive = additive; rcs->none = none; return rcs; } /* Free function for set community. */ static void route_set_community_free(void *rule) { struct rmap_com_set *rcs = rule; if (rcs->com) community_free(&rcs->com); XFREE(MTYPE_ROUTE_MAP_COMPILED, rcs); } /* Set community rule structure. */ struct route_map_rule_cmd route_set_community_cmd = { "community", route_set_community, route_set_community_compile, route_set_community_free, }; /* `set community COMMUNITY' */ struct rmap_lcom_set { struct lcommunity *lcom; int additive; int none; }; /* For lcommunity set mechanism. */ static enum route_map_cmd_result_t route_set_lcommunity(void *rule, const struct prefix *prefix, route_map_object_t type, void *object) { struct rmap_lcom_set *rcs; struct bgp_path_info *path; struct attr *attr; struct lcommunity *new = NULL; struct lcommunity *old; struct lcommunity *merge; if (type == RMAP_BGP) { rcs = rule; path = object; attr = path->attr; old = attr->lcommunity; /* "none" case. */ if (rcs->none) { attr->flag &= ~(ATTR_FLAG_BIT(BGP_ATTR_LARGE_COMMUNITIES)); attr->lcommunity = NULL; /* See the longer comment down below. */ if (old && old->refcnt == 0) lcommunity_free(&old); return RMAP_OKAY; } if (rcs->additive && old) { merge = lcommunity_merge(lcommunity_dup(old), rcs->lcom); new = lcommunity_uniq_sort(merge); lcommunity_free(&merge); } else new = lcommunity_dup(rcs->lcom); /* HACK: if the old large-community is not intern'd, * we should free it here, or all reference to it may be * lost. * Really need to cleanup attribute caching sometime. */ if (old && old->refcnt == 0) lcommunity_free(&old); /* will be intern()'d or attr_flush()'d by bgp_update_main() */ attr->lcommunity = new; attr->flag |= ATTR_FLAG_BIT(BGP_ATTR_LARGE_COMMUNITIES); } return RMAP_OKAY; } /* Compile function for set community. */ static void *route_set_lcommunity_compile(const char *arg) { struct rmap_lcom_set *rcs; struct lcommunity *lcom = NULL; char *sp; int additive = 0; int none = 0; if (strcmp(arg, "none") == 0) none = 1; else { sp = strstr(arg, "additive"); if (sp && sp > arg) { /* "additive" keyworkd is included. */ additive = 1; *(sp - 1) = '\0'; } lcom = lcommunity_str2com(arg); if (additive) *(sp - 1) = ' '; if (!lcom) return NULL; } rcs = XCALLOC(MTYPE_ROUTE_MAP_COMPILED, sizeof(struct rmap_com_set)); rcs->lcom = lcom; rcs->additive = additive; rcs->none = none; return rcs; } /* Free function for set lcommunity. */ static void route_set_lcommunity_free(void *rule) { struct rmap_lcom_set *rcs = rule; if (rcs->lcom) { lcommunity_free(&rcs->lcom); } XFREE(MTYPE_ROUTE_MAP_COMPILED, rcs); } /* Set community rule structure. */ struct route_map_rule_cmd route_set_lcommunity_cmd = { "large-community", route_set_lcommunity, route_set_lcommunity_compile, route_set_lcommunity_free, }; /* `set large-comm-list (<1-99>|<100-500>|WORD) delete' */ /* For large community set mechanism. */ static enum route_map_cmd_result_t route_set_lcommunity_delete(void *rule, const struct prefix *pfx, route_map_object_t type, void *object) { struct community_list *list; struct lcommunity *merge; struct lcommunity *new; struct lcommunity *old; struct bgp_path_info *path; struct rmap_community *rcom = rule; if (type == RMAP_BGP) { if (!rcom) return RMAP_OKAY; path = object; list = community_list_lookup(bgp_clist, rcom->name, rcom->name_hash, LARGE_COMMUNITY_LIST_MASTER); old = path->attr->lcommunity; if (list && old) { merge = lcommunity_list_match_delete( lcommunity_dup(old), list); new = lcommunity_uniq_sort(merge); lcommunity_free(&merge); /* HACK: if the old community is not intern'd, * we should free it here, or all reference to it may be * lost. * Really need to cleanup attribute caching sometime. */ if (old->refcnt == 0) lcommunity_free(&old); if (new->size == 0) { path->attr->lcommunity = NULL; path->attr->flag &= ~ATTR_FLAG_BIT( BGP_ATTR_LARGE_COMMUNITIES); lcommunity_free(&new); } else { path->attr->lcommunity = new; path->attr->flag |= ATTR_FLAG_BIT( BGP_ATTR_LARGE_COMMUNITIES); } } } return RMAP_OKAY; } /* Compile function for set lcommunity. */ static void *route_set_lcommunity_delete_compile(const char *arg) { struct rmap_community *rcom; char **splits; int num; frrstr_split(arg, " ", &splits, &num); rcom = XCALLOC(MTYPE_ROUTE_MAP_COMPILED, sizeof(struct rmap_community)); rcom->name = XSTRDUP(MTYPE_ROUTE_MAP_COMPILED, splits[0]); rcom->name_hash = bgp_clist_hash_key(rcom->name); for (int i = 0; i < num; i++) XFREE(MTYPE_TMP, splits[i]); XFREE(MTYPE_TMP, splits); return rcom; } /* Free function for set lcommunity. */ static void route_set_lcommunity_delete_free(void *rule) { struct rmap_community *rcom = rule; XFREE(MTYPE_ROUTE_MAP_COMPILED, rcom->name); XFREE(MTYPE_ROUTE_MAP_COMPILED, rcom); } /* Set lcommunity rule structure. */ struct route_map_rule_cmd route_set_lcommunity_delete_cmd = { "large-comm-list", route_set_lcommunity_delete, route_set_lcommunity_delete_compile, route_set_lcommunity_delete_free, }; /* `set comm-list (<1-99>|<100-500>|WORD) delete' */ /* For community set mechanism. */ static enum route_map_cmd_result_t route_set_community_delete(void *rule, const struct prefix *prefix, route_map_object_t type, void *object) { struct community_list *list; struct community *merge; struct community *new; struct community *old; struct bgp_path_info *path; struct rmap_community *rcom = rule; if (type == RMAP_BGP) { if (!rcom) return RMAP_OKAY; path = object; list = community_list_lookup(bgp_clist, rcom->name, rcom->name_hash, COMMUNITY_LIST_MASTER); old = path->attr->community; if (list && old) { merge = community_list_match_delete(community_dup(old), list); new = community_uniq_sort(merge); community_free(&merge); /* HACK: if the old community is not intern'd, * we should free it here, or all reference to it may be * lost. * Really need to cleanup attribute caching sometime. */ if (old->refcnt == 0) community_free(&old); if (new->size == 0) { path->attr->community = NULL; path->attr->flag &= ~ATTR_FLAG_BIT(BGP_ATTR_COMMUNITIES); community_free(&new); } else { path->attr->community = new; path->attr->flag |= ATTR_FLAG_BIT(BGP_ATTR_COMMUNITIES); } } } return RMAP_OKAY; } /* Compile function for set community. */ static void *route_set_community_delete_compile(const char *arg) { struct rmap_community *rcom; char **splits; int num; frrstr_split(arg, " ", &splits, &num); rcom = XCALLOC(MTYPE_ROUTE_MAP_COMPILED, sizeof(struct rmap_community)); rcom->name = XSTRDUP(MTYPE_ROUTE_MAP_COMPILED, splits[0]); rcom->name_hash = bgp_clist_hash_key(rcom->name); for (int i = 0; i < num; i++) XFREE(MTYPE_TMP, splits[i]); XFREE(MTYPE_TMP, splits); return rcom; } /* Free function for set community. */ static void route_set_community_delete_free(void *rule) { struct rmap_community *rcom = rule; XFREE(MTYPE_ROUTE_MAP_COMPILED, rcom->name); XFREE(MTYPE_ROUTE_MAP_COMPILED, rcom); } /* Set community rule structure. */ struct route_map_rule_cmd route_set_community_delete_cmd = { "comm-list", route_set_community_delete, route_set_community_delete_compile, route_set_community_delete_free, }; /* `set extcommunity rt COMMUNITY' */ /* For community set mechanism. Used by _rt and _soo. */ static enum route_map_cmd_result_t route_set_ecommunity(void *rule, const struct prefix *prefix, route_map_object_t type, void *object) { struct ecommunity *ecom; struct ecommunity *new_ecom; struct ecommunity *old_ecom; struct bgp_path_info *path; if (type == RMAP_BGP) { ecom = rule; path = object; if (!ecom) return RMAP_OKAY; /* We assume additive for Extended Community. */ old_ecom = path->attr->ecommunity; if (old_ecom) { new_ecom = ecommunity_merge(ecommunity_dup(old_ecom), ecom); /* old_ecom->refcnt = 1 => owned elsewhere, e.g. * bgp_update_receive() * ->refcnt = 0 => set by a previous route-map * statement */ if (!old_ecom->refcnt) ecommunity_free(&old_ecom); } else new_ecom = ecommunity_dup(ecom); /* will be intern()'d or attr_flush()'d by bgp_update_main() */ path->attr->ecommunity = new_ecom; path->attr->flag |= ATTR_FLAG_BIT(BGP_ATTR_EXT_COMMUNITIES); } return RMAP_OKAY; } /* Compile function for set community. */ static void *route_set_ecommunity_rt_compile(const char *arg) { struct ecommunity *ecom; ecom = ecommunity_str2com(arg, ECOMMUNITY_ROUTE_TARGET, 0); if (!ecom) return NULL; return ecommunity_intern(ecom); } /* Free function for set community. Used by _rt and _soo */ static void route_set_ecommunity_free(void *rule) { struct ecommunity *ecom = rule; ecommunity_unintern(&ecom); } /* Set community rule structure. */ struct route_map_rule_cmd route_set_ecommunity_rt_cmd = { "extcommunity rt", route_set_ecommunity, route_set_ecommunity_rt_compile, route_set_ecommunity_free, }; /* `set extcommunity soo COMMUNITY' */ /* Compile function for set community. */ static void *route_set_ecommunity_soo_compile(const char *arg) { struct ecommunity *ecom; ecom = ecommunity_str2com(arg, ECOMMUNITY_SITE_ORIGIN, 0); if (!ecom) return NULL; return ecommunity_intern(ecom); } /* Set community rule structure. */ struct route_map_rule_cmd route_set_ecommunity_soo_cmd = { "extcommunity soo", route_set_ecommunity, route_set_ecommunity_soo_compile, route_set_ecommunity_free, }; /* `set origin ORIGIN' */ /* For origin set. */ static enum route_map_cmd_result_t route_set_origin(void *rule, const struct prefix *prefix, route_map_object_t type, void *object) { uint8_t *origin; struct bgp_path_info *path; if (type == RMAP_BGP) { origin = rule; path = object; path->attr->origin = *origin; } return RMAP_OKAY; } /* Compile function for origin set. */ static void *route_set_origin_compile(const char *arg) { uint8_t *origin; origin = XMALLOC(MTYPE_ROUTE_MAP_COMPILED, sizeof(uint8_t)); if (strcmp(arg, "igp") == 0) *origin = 0; else if (strcmp(arg, "egp") == 0) *origin = 1; else *origin = 2; return origin; } /* Compile function for origin set. */ static void route_set_origin_free(void *rule) { XFREE(MTYPE_ROUTE_MAP_COMPILED, rule); } /* Set origin rule structure. */ struct route_map_rule_cmd route_set_origin_cmd = { "origin", route_set_origin, route_set_origin_compile, route_set_origin_free, }; /* `set atomic-aggregate' */ /* For atomic aggregate set. */ static enum route_map_cmd_result_t route_set_atomic_aggregate(void *rule, const struct prefix *pfx, route_map_object_t type, void *object) { struct bgp_path_info *path; if (type == RMAP_BGP) { path = object; path->attr->flag |= ATTR_FLAG_BIT(BGP_ATTR_ATOMIC_AGGREGATE); } return RMAP_OKAY; } /* Compile function for atomic aggregate. */ static void *route_set_atomic_aggregate_compile(const char *arg) { return (void *)1; } /* Compile function for atomic aggregate. */ static void route_set_atomic_aggregate_free(void *rule) { return; } /* Set atomic aggregate rule structure. */ struct route_map_rule_cmd route_set_atomic_aggregate_cmd = { "atomic-aggregate", route_set_atomic_aggregate, route_set_atomic_aggregate_compile, route_set_atomic_aggregate_free, }; /* `set aggregator as AS A.B.C.D' */ struct aggregator { as_t as; struct in_addr address; }; static enum route_map_cmd_result_t route_set_aggregator_as(void *rule, const struct prefix *prefix, route_map_object_t type, void *object) { struct bgp_path_info *path; struct aggregator *aggregator; if (type == RMAP_BGP) { path = object; aggregator = rule; path->attr->aggregator_as = aggregator->as; path->attr->aggregator_addr = aggregator->address; path->attr->flag |= ATTR_FLAG_BIT(BGP_ATTR_AGGREGATOR); } return RMAP_OKAY; } static void *route_set_aggregator_as_compile(const char *arg) { struct aggregator *aggregator; char as[10]; char address[20]; int ret; aggregator = XCALLOC(MTYPE_ROUTE_MAP_COMPILED, sizeof(struct aggregator)); if (sscanf(arg, "%s %s", as, address) != 2) { XFREE(MTYPE_ROUTE_MAP_COMPILED, aggregator); return NULL; } aggregator->as = strtoul(as, NULL, 10); ret = inet_aton(address, &aggregator->address); if (ret == 0) { XFREE(MTYPE_ROUTE_MAP_COMPILED, aggregator); return NULL; } return aggregator; } static void route_set_aggregator_as_free(void *rule) { XFREE(MTYPE_ROUTE_MAP_COMPILED, rule); } struct route_map_rule_cmd route_set_aggregator_as_cmd = { "aggregator as", route_set_aggregator_as, route_set_aggregator_as_compile, route_set_aggregator_as_free, }; /* Set tag to object. object must be pointer to struct bgp_path_info */ static enum route_map_cmd_result_t route_set_tag(void *rule, const struct prefix *prefix, route_map_object_t type, void *object) { route_tag_t *tag; struct bgp_path_info *path; if (type == RMAP_BGP) { tag = rule; path = object; /* Set tag value */ path->attr->tag = *tag; } return RMAP_OKAY; } /* Route map commands for tag set. */ static struct route_map_rule_cmd route_set_tag_cmd = { "tag", route_set_tag, route_map_rule_tag_compile, route_map_rule_tag_free, }; /* Set label-index to object. object must be pointer to struct bgp_path_info */ static enum route_map_cmd_result_t route_set_label_index(void *rule, const struct prefix *prefix, route_map_object_t type, void *object) { struct rmap_value *rv; struct bgp_path_info *path; uint32_t label_index; if (type == RMAP_BGP) { /* Fetch routemap's rule information. */ rv = rule; path = object; /* Set label-index value. */ label_index = rv->value; if (label_index) { path->attr->label_index = label_index; path->attr->flag |= ATTR_FLAG_BIT(BGP_ATTR_PREFIX_SID); } } return RMAP_OKAY; } /* Route map commands for label-index set. */ static struct route_map_rule_cmd route_set_label_index_cmd = { "label-index", route_set_label_index, route_value_compile, route_value_free, }; /* `match ipv6 address IP_ACCESS_LIST' */ static enum route_map_cmd_result_t route_match_ipv6_address(void *rule, const struct prefix *prefix, route_map_object_t type, void *object) { struct access_list *alist; if (type == RMAP_BGP && prefix->family == AF_INET6) { alist = access_list_lookup(AFI_IP6, (char *)rule); if (alist == NULL) return RMAP_NOMATCH; return (access_list_apply(alist, prefix) == FILTER_DENY ? RMAP_NOMATCH : RMAP_MATCH); } return RMAP_NOMATCH; } static void *route_match_ipv6_address_compile(const char *arg) { return XSTRDUP(MTYPE_ROUTE_MAP_COMPILED, arg); } static void route_match_ipv6_address_free(void *rule) { XFREE(MTYPE_ROUTE_MAP_COMPILED, rule); } /* Route map commands for ip address matching. */ struct route_map_rule_cmd route_match_ipv6_address_cmd = { "ipv6 address", route_match_ipv6_address, route_match_ipv6_address_compile, route_match_ipv6_address_free}; /* `match ipv6 next-hop IP_ADDRESS' */ static enum route_map_cmd_result_t route_match_ipv6_next_hop(void *rule, const struct prefix *prefix, route_map_object_t type, void *object) { struct in6_addr *addr = rule; struct bgp_path_info *path; if (type == RMAP_BGP) { path = object; if (IPV6_ADDR_SAME(&path->attr->mp_nexthop_global, addr)) return RMAP_MATCH; if (path->attr->mp_nexthop_len == BGP_ATTR_NHLEN_IPV6_GLOBAL_AND_LL && IPV6_ADDR_SAME(&path->attr->mp_nexthop_local, rule)) return RMAP_MATCH; return RMAP_NOMATCH; } return RMAP_NOMATCH; } static void *route_match_ipv6_next_hop_compile(const char *arg) { struct in6_addr *address; int ret; address = XMALLOC(MTYPE_ROUTE_MAP_COMPILED, sizeof(struct in6_addr)); ret = inet_pton(AF_INET6, arg, address); if (!ret) { XFREE(MTYPE_ROUTE_MAP_COMPILED, address); return NULL; } return address; } static void route_match_ipv6_next_hop_free(void *rule) { XFREE(MTYPE_ROUTE_MAP_COMPILED, rule); } struct route_map_rule_cmd route_match_ipv6_next_hop_cmd = { "ipv6 next-hop", route_match_ipv6_next_hop, route_match_ipv6_next_hop_compile, route_match_ipv6_next_hop_free}; /* `match ipv6 address prefix-list PREFIX_LIST' */ static enum route_map_cmd_result_t route_match_ipv6_address_prefix_list(void *rule, const struct prefix *prefix, route_map_object_t type, void *object) { return route_match_address_prefix_list(rule, AFI_IP6, prefix, type, object); } static void *route_match_ipv6_address_prefix_list_compile(const char *arg) { return XSTRDUP(MTYPE_ROUTE_MAP_COMPILED, arg); } static void route_match_ipv6_address_prefix_list_free(void *rule) { XFREE(MTYPE_ROUTE_MAP_COMPILED, rule); } struct route_map_rule_cmd route_match_ipv6_address_prefix_list_cmd = { "ipv6 address prefix-list", route_match_ipv6_address_prefix_list, route_match_ipv6_address_prefix_list_compile, route_match_ipv6_address_prefix_list_free}; /* `match ipv6 next-hop type ' */ static enum route_map_cmd_result_t route_match_ipv6_next_hop_type(void *rule, const struct prefix *prefix, route_map_object_t type, void *object) { struct bgp_path_info *path; struct in6_addr *addr = rule; if (type == RMAP_BGP && prefix->family == AF_INET6) { path = (struct bgp_path_info *)object; if (!path || !path->attr) return RMAP_NOMATCH; if (IPV6_ADDR_SAME(&path->attr->mp_nexthop_global, addr) && !path->attr->nh_ifindex) return RMAP_MATCH; } return RMAP_NOMATCH; } static void *route_match_ipv6_next_hop_type_compile(const char *arg) { struct in6_addr *address; int ret; address = XMALLOC(MTYPE_ROUTE_MAP_COMPILED, sizeof(struct in6_addr)); ret = inet_pton(AF_INET6, "::0", address); if (!ret) { XFREE(MTYPE_ROUTE_MAP_COMPILED, address); return NULL; } return address; } static void route_match_ipv6_next_hop_type_free(void *rule) { XFREE(MTYPE_ROUTE_MAP_COMPILED, rule); } struct route_map_rule_cmd route_match_ipv6_next_hop_type_cmd = { "ipv6 next-hop type", route_match_ipv6_next_hop_type, route_match_ipv6_next_hop_type_compile, route_match_ipv6_next_hop_type_free}; /* `set ipv6 nexthop global IP_ADDRESS' */ /* Set nexthop to object. ojbect must be pointer to struct attr. */ static enum route_map_cmd_result_t route_set_ipv6_nexthop_global(void *rule, const struct prefix *p, route_map_object_t type, void *object) { struct in6_addr *address; struct bgp_path_info *path; if (type == RMAP_BGP) { /* Fetch routemap's rule information. */ address = rule; path = object; /* Set next hop value. */ path->attr->mp_nexthop_global = *address; /* Set nexthop length. */ if (path->attr->mp_nexthop_len == 0) path->attr->mp_nexthop_len = BGP_ATTR_NHLEN_IPV6_GLOBAL; SET_FLAG(path->attr->rmap_change_flags, BATTR_RMAP_IPV6_GLOBAL_NHOP_CHANGED); } return RMAP_OKAY; } /* Route map `ip next-hop' compile function. Given string is converted to struct in_addr structure. */ static void *route_set_ipv6_nexthop_global_compile(const char *arg) { int ret; struct in6_addr *address; address = XMALLOC(MTYPE_ROUTE_MAP_COMPILED, sizeof(struct in6_addr)); ret = inet_pton(AF_INET6, arg, address); if (ret == 0) { XFREE(MTYPE_ROUTE_MAP_COMPILED, address); return NULL; } return address; } /* Free route map's compiled `ip next-hop' value. */ static void route_set_ipv6_nexthop_global_free(void *rule) { XFREE(MTYPE_ROUTE_MAP_COMPILED, rule); } /* Route map commands for ip nexthop set. */ struct route_map_rule_cmd route_set_ipv6_nexthop_global_cmd = { "ipv6 next-hop global", route_set_ipv6_nexthop_global, route_set_ipv6_nexthop_global_compile, route_set_ipv6_nexthop_global_free}; /* Set next-hop preference value. */ static enum route_map_cmd_result_t route_set_ipv6_nexthop_prefer_global(void *rule, const struct prefix *prefix, route_map_object_t type, void *object) { struct bgp_path_info *path; struct peer *peer; if (type == RMAP_BGP) { /* Fetch routemap's rule information. */ path = object; peer = path->peer; if ((CHECK_FLAG(peer->rmap_type, PEER_RMAP_TYPE_IN) || CHECK_FLAG(peer->rmap_type, PEER_RMAP_TYPE_IMPORT)) && peer->su_remote && sockunion_family(peer->su_remote) == AF_INET6) { /* Set next hop preference to global */ path->attr->mp_nexthop_prefer_global = true; SET_FLAG(path->attr->rmap_change_flags, BATTR_RMAP_IPV6_PREFER_GLOBAL_CHANGED); } else { path->attr->mp_nexthop_prefer_global = false; SET_FLAG(path->attr->rmap_change_flags, BATTR_RMAP_IPV6_PREFER_GLOBAL_CHANGED); } } return RMAP_OKAY; } static void *route_set_ipv6_nexthop_prefer_global_compile(const char *arg) { int *rins = NULL; rins = XCALLOC(MTYPE_ROUTE_MAP_COMPILED, sizeof(int)); *rins = 1; return rins; } /* Free route map's compiled `ip next-hop' value. */ static void route_set_ipv6_nexthop_prefer_global_free(void *rule) { XFREE(MTYPE_ROUTE_MAP_COMPILED, rule); } /* Route map commands for ip nexthop set preferred. */ struct route_map_rule_cmd route_set_ipv6_nexthop_prefer_global_cmd = { "ipv6 next-hop prefer-global", route_set_ipv6_nexthop_prefer_global, route_set_ipv6_nexthop_prefer_global_compile, route_set_ipv6_nexthop_prefer_global_free}; /* `set ipv6 nexthop local IP_ADDRESS' */ /* Set nexthop to object. ojbect must be pointer to struct attr. */ static enum route_map_cmd_result_t route_set_ipv6_nexthop_local(void *rule, const struct prefix *p, route_map_object_t type, void *object) { struct in6_addr *address; struct bgp_path_info *path; if (type == RMAP_BGP) { /* Fetch routemap's rule information. */ address = rule; path = object; /* Set next hop value. */ path->attr->mp_nexthop_local = *address; /* Set nexthop length. */ if (path->attr->mp_nexthop_len != BGP_ATTR_NHLEN_IPV6_GLOBAL_AND_LL) path->attr->mp_nexthop_len = BGP_ATTR_NHLEN_IPV6_GLOBAL_AND_LL; SET_FLAG(path->attr->rmap_change_flags, BATTR_RMAP_IPV6_LL_NHOP_CHANGED); } return RMAP_OKAY; } /* Route map `ip nexthop' compile function. Given string is converted to struct in_addr structure. */ static void *route_set_ipv6_nexthop_local_compile(const char *arg) { int ret; struct in6_addr *address; address = XMALLOC(MTYPE_ROUTE_MAP_COMPILED, sizeof(struct in6_addr)); ret = inet_pton(AF_INET6, arg, address); if (ret == 0) { XFREE(MTYPE_ROUTE_MAP_COMPILED, address); return NULL; } return address; } /* Free route map's compiled `ip nexthop' value. */ static void route_set_ipv6_nexthop_local_free(void *rule) { XFREE(MTYPE_ROUTE_MAP_COMPILED, rule); } /* Route map commands for ip nexthop set. */ struct route_map_rule_cmd route_set_ipv6_nexthop_local_cmd = { "ipv6 next-hop local", route_set_ipv6_nexthop_local, route_set_ipv6_nexthop_local_compile, route_set_ipv6_nexthop_local_free}; /* `set ipv6 nexthop peer-address' */ /* Set nexthop to object. ojbect must be pointer to struct attr. */ static enum route_map_cmd_result_t route_set_ipv6_nexthop_peer(void *rule, const struct prefix *pfx, route_map_object_t type, void *object) { struct in6_addr peer_address; struct bgp_path_info *path; struct peer *peer; if (type == RMAP_BGP) { /* Fetch routemap's rule information. */ path = object; peer = path->peer; if ((CHECK_FLAG(peer->rmap_type, PEER_RMAP_TYPE_IN) || CHECK_FLAG(peer->rmap_type, PEER_RMAP_TYPE_IMPORT)) && peer->su_remote && sockunion_family(peer->su_remote) == AF_INET6) { peer_address = peer->su_remote->sin6.sin6_addr; /* Set next hop value and length in attribute. */ if (IN6_IS_ADDR_LINKLOCAL(&peer_address)) { path->attr->mp_nexthop_local = peer_address; if (path->attr->mp_nexthop_len != 32) path->attr->mp_nexthop_len = 32; } else { path->attr->mp_nexthop_global = peer_address; if (path->attr->mp_nexthop_len == 0) path->attr->mp_nexthop_len = 16; } } else if (CHECK_FLAG(peer->rmap_type, PEER_RMAP_TYPE_OUT)) { /* The next hop value will be set as part of packet * rewrite. * Set the flags here to indicate that rewrite needs to * be done. * Also, clear the value - we clear both global and * link-local * nexthops, whether we send one or both is determined * elsewhere. */ SET_FLAG(path->attr->rmap_change_flags, BATTR_RMAP_NEXTHOP_PEER_ADDRESS); /* clear next hop value. */ memset(&(path->attr->mp_nexthop_global), 0, sizeof(struct in6_addr)); memset(&(path->attr->mp_nexthop_local), 0, sizeof(struct in6_addr)); } } return RMAP_OKAY; } /* Route map `ip next-hop' compile function. Given string is converted to struct in_addr structure. */ static void *route_set_ipv6_nexthop_peer_compile(const char *arg) { int *rins = NULL; rins = XCALLOC(MTYPE_ROUTE_MAP_COMPILED, sizeof(int)); *rins = 1; return rins; } /* Free route map's compiled `ip next-hop' value. */ static void route_set_ipv6_nexthop_peer_free(void *rule) { XFREE(MTYPE_ROUTE_MAP_COMPILED, rule); } /* Route map commands for ip nexthop set. */ struct route_map_rule_cmd route_set_ipv6_nexthop_peer_cmd = { "ipv6 next-hop peer-address", route_set_ipv6_nexthop_peer, route_set_ipv6_nexthop_peer_compile, route_set_ipv6_nexthop_peer_free}; /* `set ipv4 vpn next-hop A.B.C.D' */ static enum route_map_cmd_result_t route_set_vpnv4_nexthop(void *rule, const struct prefix *prefix, route_map_object_t type, void *object) { struct in_addr *address; struct bgp_path_info *path; if (type == RMAP_BGP) { /* Fetch routemap's rule information. */ address = rule; path = object; /* Set next hop value. */ path->attr->mp_nexthop_global_in = *address; path->attr->mp_nexthop_len = 4; } return RMAP_OKAY; } static void *route_set_vpnv4_nexthop_compile(const char *arg) { int ret; struct in_addr *address; address = XMALLOC(MTYPE_ROUTE_MAP_COMPILED, sizeof(struct in_addr)); ret = inet_aton(arg, address); if (ret == 0) { XFREE(MTYPE_ROUTE_MAP_COMPILED, address); return NULL; } return address; } /* `set ipv6 vpn next-hop A.B.C.D' */ static enum route_map_cmd_result_t route_set_vpnv6_nexthop(void *rule, const struct prefix *prefix, route_map_object_t type, void *object) { struct in6_addr *address; struct bgp_path_info *path; if (type == RMAP_BGP) { /* Fetch routemap's rule information. */ address = rule; path = object; /* Set next hop value. */ memcpy(&path->attr->mp_nexthop_global, address, sizeof(struct in6_addr)); path->attr->mp_nexthop_len = BGP_ATTR_NHLEN_VPNV6_GLOBAL; } return RMAP_OKAY; } static void *route_set_vpnv6_nexthop_compile(const char *arg) { int ret; struct in6_addr *address; address = XMALLOC(MTYPE_ROUTE_MAP_COMPILED, sizeof(struct in6_addr)); ret = inet_pton(AF_INET6, arg, address); if (ret == 0) { XFREE(MTYPE_ROUTE_MAP_COMPILED, address); return NULL; } return address; } static void route_set_vpn_nexthop_free(void *rule) { XFREE(MTYPE_ROUTE_MAP_COMPILED, rule); } /* Route map commands for ipv4 next-hop set. */ struct route_map_rule_cmd route_set_vpnv4_nexthop_cmd = { "ipv4 vpn next-hop", route_set_vpnv4_nexthop, route_set_vpnv4_nexthop_compile, route_set_vpn_nexthop_free}; /* Route map commands for ipv6 next-hop set. */ struct route_map_rule_cmd route_set_vpnv6_nexthop_cmd = { "ipv6 vpn next-hop", route_set_vpnv6_nexthop, route_set_vpnv6_nexthop_compile, route_set_vpn_nexthop_free}; /* `set originator-id' */ /* For origin set. */ static enum route_map_cmd_result_t route_set_originator_id(void *rule, const struct prefix *prefix, route_map_object_t type, void *object) { struct in_addr *address; struct bgp_path_info *path; if (type == RMAP_BGP) { address = rule; path = object; path->attr->flag |= ATTR_FLAG_BIT(BGP_ATTR_ORIGINATOR_ID); path->attr->originator_id = *address; } return RMAP_OKAY; } /* Compile function for originator-id set. */ static void *route_set_originator_id_compile(const char *arg) { int ret; struct in_addr *address; address = XMALLOC(MTYPE_ROUTE_MAP_COMPILED, sizeof(struct in_addr)); ret = inet_aton(arg, address); if (ret == 0) { XFREE(MTYPE_ROUTE_MAP_COMPILED, address); return NULL; } return address; } /* Compile function for originator_id set. */ static void route_set_originator_id_free(void *rule) { XFREE(MTYPE_ROUTE_MAP_COMPILED, rule); } /* Set originator-id rule structure. */ struct route_map_rule_cmd route_set_originator_id_cmd = { "originator-id", route_set_originator_id, route_set_originator_id_compile, route_set_originator_id_free, }; /* Add bgp route map rule. */ static int bgp_route_match_add(struct vty *vty, const char *command, const char *arg, route_map_event_t type) { VTY_DECLVAR_CONTEXT(route_map_index, index); int retval = CMD_SUCCESS; enum rmap_compile_rets ret; ret = route_map_add_match(index, command, arg, type); switch (ret) { case RMAP_RULE_MISSING: vty_out(vty, "%% BGP Can't find rule.\n"); retval = CMD_WARNING_CONFIG_FAILED; break; case RMAP_COMPILE_ERROR: vty_out(vty, "%% BGP Argument is malformed.\n"); retval = CMD_WARNING_CONFIG_FAILED; break; case RMAP_COMPILE_SUCCESS: if (type != RMAP_EVENT_MATCH_ADDED) { route_map_upd8_dependency(type, arg, index->map->name); } break; case RMAP_DUPLICATE_RULE: /* * Intentionally doing nothing here. */ break; } return retval; } /* Delete bgp route map rule. */ static int bgp_route_match_delete(struct vty *vty, const char *command, const char *arg, route_map_event_t type) { VTY_DECLVAR_CONTEXT(route_map_index, index); enum rmap_compile_rets ret; int retval = CMD_SUCCESS; char *dep_name = NULL; const char *tmpstr; char *rmap_name = NULL; if (type != RMAP_EVENT_MATCH_DELETED) { /* ignore the mundane, the types without any dependency */ if (arg == NULL) { if ((tmpstr = route_map_get_match_arg(index, command)) != NULL) dep_name = XSTRDUP(MTYPE_ROUTE_MAP_RULE, tmpstr); } else { dep_name = XSTRDUP(MTYPE_ROUTE_MAP_RULE, arg); } rmap_name = XSTRDUP(MTYPE_ROUTE_MAP_NAME, index->map->name); } ret = route_map_delete_match(index, command, dep_name); switch (ret) { case RMAP_RULE_MISSING: vty_out(vty, "%% BGP Can't find rule.\n"); retval = CMD_WARNING_CONFIG_FAILED; break; case RMAP_COMPILE_ERROR: vty_out(vty, "%% BGP Argument is malformed.\n"); retval = CMD_WARNING_CONFIG_FAILED; break; case RMAP_COMPILE_SUCCESS: if (type != RMAP_EVENT_MATCH_DELETED && dep_name) route_map_upd8_dependency(type, dep_name, rmap_name); break; case RMAP_DUPLICATE_RULE: /* * Nothing to do here */ break; } XFREE(MTYPE_ROUTE_MAP_RULE, dep_name); XFREE(MTYPE_ROUTE_MAP_NAME, rmap_name); return retval; } /* * This is the workhorse routine for processing in/out routemap * modifications. */ static void bgp_route_map_process_peer(const char *rmap_name, struct route_map *map, struct peer *peer, int afi, int safi, int route_update) { struct bgp_filter *filter; if (!peer || !rmap_name) return; filter = &peer->filter[afi][safi]; /* * in is for non-route-server clients, * out is for all peers */ if (filter->map[RMAP_IN].name && (strcmp(rmap_name, filter->map[RMAP_IN].name) == 0)) { filter->map[RMAP_IN].map = map; if (route_update && peer->status == Established) { if (CHECK_FLAG(peer->af_flags[afi][safi], PEER_FLAG_SOFT_RECONFIG)) { if (bgp_debug_update(peer, NULL, NULL, 1)) zlog_debug( "Processing route_map %s update on peer %s (inbound, soft-reconfig)", rmap_name, peer->host); bgp_soft_reconfig_in(peer, afi, safi); } else if (CHECK_FLAG(peer->cap, PEER_CAP_REFRESH_OLD_RCV) || CHECK_FLAG(peer->cap, PEER_CAP_REFRESH_NEW_RCV)) { if (bgp_debug_update(peer, NULL, NULL, 1)) zlog_debug( "Processing route_map %s update on peer %s (inbound, route-refresh)", rmap_name, peer->host); bgp_route_refresh_send(peer, afi, safi, 0, 0, 0); } } } /* * For outbound, unsuppress and default-originate map change (content or * map created), merely update the "config" here, the actual route * announcement happens at the group level. */ if (filter->map[RMAP_OUT].name && (strcmp(rmap_name, filter->map[RMAP_OUT].name) == 0)) filter->map[RMAP_OUT].map = map; if (filter->usmap.name && (strcmp(rmap_name, filter->usmap.name) == 0)) filter->usmap.map = map; if (peer->default_rmap[afi][safi].name && (strcmp(rmap_name, peer->default_rmap[afi][safi].name) == 0)) peer->default_rmap[afi][safi].map = map; } static void bgp_route_map_update_peer_group(const char *rmap_name, struct route_map *map, struct bgp *bgp) { struct peer_group *group; struct listnode *node, *nnode; struct bgp_filter *filter; int afi, safi; int direct; if (!bgp) return; /* All the peers have been updated correctly already. This is * just updating the placeholder data. No real update required. */ for (ALL_LIST_ELEMENTS(bgp->group, node, nnode, group)) { FOREACH_AFI_SAFI (afi, safi) { filter = &group->conf->filter[afi][safi]; for (direct = RMAP_IN; direct < RMAP_MAX; direct++) { if ((filter->map[direct].name) && (strcmp(rmap_name, filter->map[direct].name) == 0)) filter->map[direct].map = map; } if (filter->usmap.name && (strcmp(rmap_name, filter->usmap.name) == 0)) filter->usmap.map = map; } } } /* * Note that if an extreme number (tens of thousands) of route-maps are in use * and if bgp has an extreme number of peers, network statements, etc then this * function can consume a lot of cycles. This is due to this function being * called for each route-map and within this function we walk the list of peers, * network statements, etc looking to see if they use this route-map. */ static void bgp_route_map_process_update(struct bgp *bgp, const char *rmap_name, int route_update) { int i; afi_t afi; safi_t safi; struct peer *peer; struct bgp_node *bn; struct bgp_static *bgp_static; struct bgp_aggregate *aggregate; struct listnode *node, *nnode; struct route_map *map; char buf[INET6_ADDRSTRLEN]; map = route_map_lookup_by_name(rmap_name); for (ALL_LIST_ELEMENTS(bgp->peer, node, nnode, peer)) { /* Ignore dummy peer-group structure */ if (CHECK_FLAG(peer->sflags, PEER_STATUS_GROUP)) continue; FOREACH_AFI_SAFI (afi, safi) { /* process in/out/import/export/default-orig * route-maps */ bgp_route_map_process_peer(rmap_name, map, peer, afi, safi, route_update); } } /* for outbound/default-orig route-maps, process for groups */ update_group_policy_update(bgp, BGP_POLICY_ROUTE_MAP, rmap_name, route_update, 0); /* update peer-group config (template) */ bgp_route_map_update_peer_group(rmap_name, map, bgp); FOREACH_AFI_SAFI (afi, safi) { /* For table route-map updates. */ if (!bgp_fibupd_safi(safi)) continue; if (bgp->table_map[afi][safi].name && (strcmp(rmap_name, bgp->table_map[afi][safi].name) == 0)) { /* bgp->table_map[afi][safi].map is NULL. * i.e Route map creation event. * So update applied_counter. * If it is not NULL, i.e It may be routemap updation or * deletion. so no need to update the counter. */ if (!bgp->table_map[afi][safi].map) route_map_counter_increment(map); bgp->table_map[afi][safi].map = map; if (BGP_DEBUG(zebra, ZEBRA)) zlog_debug( "Processing route_map %s update on " "table map", rmap_name); if (route_update) bgp_zebra_announce_table(bgp, afi, safi); } /* For network route-map updates. */ for (bn = bgp_table_top(bgp->route[afi][safi]); bn; bn = bgp_route_next(bn)) { bgp_static = bgp_node_get_bgp_static_info(bn); if (!bgp_static) continue; if (!bgp_static->rmap.name || (strcmp(rmap_name, bgp_static->rmap.name) != 0)) continue; if (!bgp_static->rmap.map) route_map_counter_increment(map); bgp_static->rmap.map = map; if (route_update && !bgp_static->backdoor) { if (bgp_debug_zebra(&bn->p)) zlog_debug( "Processing route_map %s update on static route %s", rmap_name, inet_ntop(bn->p.family, &bn->p.u.prefix, buf, INET6_ADDRSTRLEN)); bgp_static_update(bgp, &bn->p, bgp_static, afi, safi); } } /* For aggregate-address route-map updates. */ for (bn = bgp_table_top(bgp->aggregate[afi][safi]); bn; bn = bgp_route_next(bn)) { aggregate = bgp_node_get_bgp_aggregate_info(bn); if (!aggregate) continue; if (!aggregate->rmap.name || (strcmp(rmap_name, aggregate->rmap.name) != 0)) continue; if (!aggregate->rmap.map) route_map_counter_increment(map); aggregate->rmap.map = map; if (route_update) { if (bgp_debug_zebra(&bn->p)) zlog_debug( "Processing route_map %s update on aggregate-address route %s", rmap_name, inet_ntop(bn->p.family, &bn->p.u.prefix, buf, INET6_ADDRSTRLEN)); bgp_aggregate_route(bgp, &bn->p, afi, safi, aggregate); } } } /* For redistribute route-map updates. */ for (afi = AFI_IP; afi < AFI_MAX; afi++) for (i = 0; i < ZEBRA_ROUTE_MAX; i++) { struct list *red_list; struct bgp_redist *red; red_list = bgp->redist[afi][i]; if (!red_list) continue; for (ALL_LIST_ELEMENTS_RO(red_list, node, red)) { if (!red->rmap.name || (strcmp(rmap_name, red->rmap.name) != 0)) continue; if (!red->rmap.map) route_map_counter_increment(map); red->rmap.map = map; if (!route_update) continue; if (BGP_DEBUG(zebra, ZEBRA)) zlog_debug( "Processing route_map %s update on redistributed routes", rmap_name); bgp_redistribute_resend(bgp, afi, i, red->instance); } } /* for type5 command route-maps */ FOREACH_AFI_SAFI (afi, safi) { if (!bgp->adv_cmd_rmap[afi][safi].name || strcmp(rmap_name, bgp->adv_cmd_rmap[afi][safi].name) != 0) continue; /* Make sure the route-map is populated here if not already done */ bgp->adv_cmd_rmap[afi][safi].map = map; if (BGP_DEBUG(zebra, ZEBRA)) zlog_debug( "Processing route_map %s update on advertise type5 route command", rmap_name); if (route_update && advertise_type5_routes(bgp, afi)) { bgp_evpn_withdraw_type5_routes(bgp, afi, safi); bgp_evpn_advertise_type5_routes(bgp, afi, safi); } } } static void bgp_route_map_process_update_cb(char *rmap_name) { struct listnode *node, *nnode; struct bgp *bgp; for (ALL_LIST_ELEMENTS(bm->bgp, node, nnode, bgp)) { bgp_route_map_process_update(bgp, rmap_name, 1); #if ENABLE_BGP_VNC /* zlog_debug("%s: calling vnc_routemap_update", __func__); */ vnc_routemap_update(bgp, __func__); #endif } vpn_policy_routemap_event(rmap_name); } int bgp_route_map_update_timer(struct thread *thread) { bm->t_rmap_update = NULL; route_map_walk_update_list(bgp_route_map_process_update_cb); return (0); } static void bgp_route_map_mark_update(const char *rmap_name) { struct listnode *node, *nnode; struct bgp *bgp; /* If new update is received before the current timer timed out, * turn it off and start a new timer. */ if (bm->t_rmap_update != NULL) THREAD_OFF(bm->t_rmap_update); /* rmap_update_timer of 0 means don't do route updates */ if (bm->rmap_update_timer) { thread_add_timer(bm->master, bgp_route_map_update_timer, NULL, bm->rmap_update_timer, &bm->t_rmap_update); /* Signal the groups that a route-map update event has * started */ for (ALL_LIST_ELEMENTS(bm->bgp, node, nnode, bgp)) update_group_policy_update(bgp, BGP_POLICY_ROUTE_MAP, rmap_name, 1, 1); } else { for (ALL_LIST_ELEMENTS(bm->bgp, node, nnode, bgp)) bgp_route_map_process_update(bgp, rmap_name, 0); #if ENABLE_BGP_VNC zlog_debug("%s: calling vnc_routemap_update", __func__); vnc_routemap_update(bgp, __func__); #endif } } static void bgp_route_map_add(const char *rmap_name) { if (route_map_mark_updated(rmap_name) == 0) bgp_route_map_mark_update(rmap_name); route_map_notify_dependencies(rmap_name, RMAP_EVENT_MATCH_ADDED); } static void bgp_route_map_delete(const char *rmap_name) { if (route_map_mark_updated(rmap_name) == 0) bgp_route_map_mark_update(rmap_name); route_map_notify_dependencies(rmap_name, RMAP_EVENT_MATCH_DELETED); } static void bgp_route_map_event(const char *rmap_name) { if (route_map_mark_updated(rmap_name) == 0) bgp_route_map_mark_update(rmap_name); route_map_notify_dependencies(rmap_name, RMAP_EVENT_MATCH_ADDED); } DEFUN (match_mac_address, match_mac_address_cmd, "match mac address WORD", MATCH_STR "mac address\n" "Match address of route\n" "MAC Access-list name\n") { return bgp_route_match_add(vty, "mac address", argv[3]->arg, RMAP_EVENT_FILTER_ADDED); } DEFUN (no_match_mac_address, no_match_mac_address_cmd, "no match mac address WORD", NO_STR MATCH_STR "mac\n" "Match address of route\n" "MAC acess-list name\n") { return bgp_route_match_delete(vty, "mac address", argv[4]->arg, RMAP_EVENT_FILTER_DELETED); } DEFUN (match_evpn_route_type, match_evpn_route_type_cmd, "match evpn route-type ", MATCH_STR EVPN_HELP_STR "Match route-type\n" "mac-ip route\n" "IMET route\n" "prefix route\n") { return bgp_route_match_add(vty, "evpn route-type", argv[3]->arg, RMAP_EVENT_MATCH_ADDED); } DEFUN (no_match_evpn_route_type, no_match_evpn_route_type_cmd, "no match evpn route-type ", NO_STR MATCH_STR EVPN_HELP_STR "Match route-type\n" "mac-ip route\n" "IMET route\n" "prefix route\n") { return bgp_route_match_delete(vty, "evpn route-type", argv[4]->arg, RMAP_EVENT_MATCH_DELETED); } DEFUN (match_evpn_vni, match_evpn_vni_cmd, "match evpn vni " CMD_VNI_RANGE, MATCH_STR EVPN_HELP_STR "Match VNI\n" "VNI ID\n") { return bgp_route_match_add(vty, "evpn vni", argv[3]->arg, RMAP_EVENT_MATCH_ADDED); } DEFUN (no_match_evpn_vni, no_match_evpn_vni_cmd, "no match evpn vni " CMD_VNI_RANGE, NO_STR MATCH_STR EVPN_HELP_STR "Match VNI\n" "VNI ID\n") { return bgp_route_match_delete(vty, "evpn vni", argv[4]->arg, RMAP_EVENT_MATCH_DELETED); } DEFUN (match_evpn_default_route, match_evpn_default_route_cmd, "match evpn default-route", MATCH_STR EVPN_HELP_STR "default EVPN type-5 route\n") { return bgp_route_match_add(vty, "evpn default-route", NULL, RMAP_EVENT_MATCH_ADDED); } DEFUN (no_match_evpn_default_route, no_match_evpn_default_route_cmd, "no match evpn default-route", NO_STR MATCH_STR EVPN_HELP_STR "default EVPN type-5 route\n") { return bgp_route_match_delete(vty, "evpn default-route", NULL, RMAP_EVENT_MATCH_DELETED); } DEFPY(match_vrl_source_vrf, match_vrl_source_vrf_cmd, "match source-vrf NAME$vrf_name", MATCH_STR "source vrf\n" "The VRF name\n") { return bgp_route_match_add(vty, "source-vrf", vrf_name, RMAP_EVENT_MATCH_ADDED); } DEFPY(no_match_vrl_source_vrf, no_match_vrl_source_vrf_cmd, "no match source-vrf NAME$vrf_name", NO_STR MATCH_STR "source vrf\n" "The VRF name\n") { return bgp_route_match_delete(vty, "source-vrf", vrf_name, RMAP_EVENT_MATCH_DELETED); } DEFUN (match_peer, match_peer_cmd, "match peer ", MATCH_STR "Match peer address\n" "IP address of peer\n" "IPv6 address of peer\n" "Interface name of peer\n") { int idx_ip = 2; return bgp_route_match_add(vty, "peer", argv[idx_ip]->arg, RMAP_EVENT_MATCH_ADDED); } DEFUN (match_peer_local, match_peer_local_cmd, "match peer local", MATCH_STR "Match peer address\n" "Static or Redistributed routes\n") { return bgp_route_match_add(vty, "peer", "local", RMAP_EVENT_MATCH_DELETED); } DEFUN (no_match_peer, no_match_peer_cmd, "no match peer []", NO_STR MATCH_STR "Match peer address\n" "Static or Redistributed routes\n" "IP address of peer\n" "IPv6 address of peer\n" "Interface name of peer\n") { int idx_peer = 3; if (argc <= idx_peer) return bgp_route_match_delete(vty, "peer", NULL, RMAP_EVENT_MATCH_DELETED); return bgp_route_match_delete(vty, "peer", argv[idx_peer]->arg, RMAP_EVENT_MATCH_DELETED); } #if defined(HAVE_LUA) DEFUN (match_command, match_command_cmd, "match command WORD", MATCH_STR "Run a command to match\n" "The command to run\n") { return bgp_route_match_add(vty, "command", argv[2]->arg, RMAP_EVENT_FILTER_ADDED); } DEFUN (no_match_command, no_match_command_cmd, "no match command WORD", NO_STR MATCH_STR "Run a command to match\n" "The command to run\n") { return bgp_route_match_delete(vty, "command", argv[3]->arg, RMAP_EVENT_FILTER_DELETED); } #endif /* match probability */ DEFUN (match_probability, match_probability_cmd, "match probability (0-100)", MATCH_STR "Match portion of routes defined by percentage value\n" "Percentage of routes\n") { int idx_number = 2; return bgp_route_match_add(vty, "probability", argv[idx_number]->arg, RMAP_EVENT_MATCH_ADDED); } DEFUN (no_match_probability, no_match_probability_cmd, "no match probability [(1-99)]", NO_STR MATCH_STR "Match portion of routes defined by percentage value\n" "Percentage of routes\n") { int idx_number = 3; if (argc <= idx_number) return bgp_route_match_delete(vty, "probability", NULL, RMAP_EVENT_MATCH_DELETED); return bgp_route_match_delete(vty, "probability", argv[idx_number]->arg, RMAP_EVENT_MATCH_DELETED); } DEFUN (match_ip_route_source, match_ip_route_source_cmd, "match ip route-source <(1-199)|(1300-2699)|WORD>", MATCH_STR IP_STR "Match advertising source address of route\n" "IP access-list number\n" "IP access-list number (expanded range)\n" "IP standard access-list name\n") { int idx_acl = 3; return bgp_route_match_add(vty, "ip route-source", argv[idx_acl]->arg, RMAP_EVENT_FILTER_ADDED); } DEFUN (no_match_ip_route_source, no_match_ip_route_source_cmd, "no match ip route-source [<(1-199)|(1300-2699)|WORD>]", NO_STR MATCH_STR IP_STR "Match advertising source address of route\n" "IP access-list number\n" "IP access-list number (expanded range)\n" "IP standard access-list name\n") { int idx_number = 4; if (argc <= idx_number) return bgp_route_match_delete(vty, "ip route-source", NULL, RMAP_EVENT_FILTER_DELETED); return bgp_route_match_delete(vty, "ip route-source", argv[idx_number]->arg, RMAP_EVENT_FILTER_DELETED); } DEFUN (match_ip_route_source_prefix_list, match_ip_route_source_prefix_list_cmd, "match ip route-source prefix-list WORD", MATCH_STR IP_STR "Match advertising source address of route\n" "Match entries of prefix-lists\n" "IP prefix-list name\n") { int idx_word = 4; return bgp_route_match_add(vty, "ip route-source prefix-list", argv[idx_word]->arg, RMAP_EVENT_PLIST_ADDED); } DEFUN (no_match_ip_route_source_prefix_list, no_match_ip_route_source_prefix_list_cmd, "no match ip route-source prefix-list [WORD]", NO_STR MATCH_STR IP_STR "Match advertising source address of route\n" "Match entries of prefix-lists\n" "IP prefix-list name\n") { int idx_word = 5; if (argc <= idx_word) return bgp_route_match_delete(vty, "ip route-source prefix-list", NULL, RMAP_EVENT_PLIST_DELETED); return bgp_route_match_delete(vty, "ip route-source prefix-list", argv[idx_word]->arg, RMAP_EVENT_PLIST_DELETED); } DEFUN (match_local_pref, match_local_pref_cmd, "match local-preference (0-4294967295)", MATCH_STR "Match local-preference of route\n" "Metric value\n") { int idx_number = 2; return bgp_route_match_add(vty, "local-preference", argv[idx_number]->arg, RMAP_EVENT_MATCH_ADDED); } DEFUN (no_match_local_pref, no_match_local_pref_cmd, "no match local-preference [(0-4294967295)]", NO_STR MATCH_STR "Match local preference of route\n" "Local preference value\n") { int idx_localpref = 3; if (argc <= idx_localpref) return bgp_route_match_delete(vty, "local-preference", NULL, RMAP_EVENT_MATCH_DELETED); return bgp_route_match_delete(vty, "local-preference", argv[idx_localpref]->arg, RMAP_EVENT_MATCH_DELETED); } DEFUN (match_community, match_community_cmd, "match community <(1-99)|(100-500)|WORD> [exact-match]", MATCH_STR "Match BGP community list\n" "Community-list number (standard)\n" "Community-list number (expanded)\n" "Community-list name\n" "Do exact matching of communities\n") { int idx_comm_list = 2; int ret; char *argstr; if (argc == 4) { argstr = XMALLOC(MTYPE_ROUTE_MAP_COMPILED, strlen(argv[idx_comm_list]->arg) + strlen("exact-match") + 2); sprintf(argstr, "%s exact-match", argv[idx_comm_list]->arg); } else argstr = argv[idx_comm_list]->arg; ret = bgp_route_match_add(vty, "community", argstr, RMAP_EVENT_CLIST_ADDED); if (argstr != argv[idx_comm_list]->arg) XFREE(MTYPE_ROUTE_MAP_COMPILED, argstr); return ret; } DEFUN (no_match_community, no_match_community_cmd, "no match community [<(1-99)|(100-500)|WORD> [exact-match]]", NO_STR MATCH_STR "Match BGP community list\n" "Community-list number (standard)\n" "Community-list number (expanded)\n" "Community-list name\n" "Do exact matching of communities\n") { return bgp_route_match_delete(vty, "community", NULL, RMAP_EVENT_CLIST_DELETED); } DEFUN (match_lcommunity, match_lcommunity_cmd, "match large-community <(1-99)|(100-500)|WORD> [exact-match]", MATCH_STR "Match BGP large community list\n" "Large Community-list number (standard)\n" "Large Community-list number (expanded)\n" "Large Community-list name\n" "Do exact matching of communities\n") { int idx_lcomm_list = 2; int ret; char *argstr; if (argc == 4) { argstr = XMALLOC(MTYPE_ROUTE_MAP_COMPILED, strlen(argv[idx_lcomm_list]->arg) + strlen("exact-match") + 2); sprintf(argstr, "%s exact-match", argv[idx_lcomm_list]->arg); } else argstr = argv[idx_lcomm_list]->arg; ret = bgp_route_match_add(vty, "large-community", argstr, RMAP_EVENT_LLIST_ADDED); if (argstr != argv[idx_lcomm_list]->arg) XFREE(MTYPE_ROUTE_MAP_COMPILED, argstr); return ret; } DEFUN (no_match_lcommunity, no_match_lcommunity_cmd, "no match large-community [<(1-99)|(100-500)|WORD> [exact-match]]", NO_STR MATCH_STR "Match BGP large community list\n" "Large Community-list number (standard)\n" "Large Community-list number (expanded)\n" "Large Community-list name\n" "Do exact matching of communities\n") { return bgp_route_match_delete(vty, "large-community", NULL, RMAP_EVENT_LLIST_DELETED); } DEFUN (match_ecommunity, match_ecommunity_cmd, "match extcommunity <(1-99)|(100-500)|WORD>", MATCH_STR "Match BGP/VPN extended community list\n" "Extended community-list number (standard)\n" "Extended community-list number (expanded)\n" "Extended community-list name\n") { int idx_comm_list = 2; return bgp_route_match_add(vty, "extcommunity", argv[idx_comm_list]->arg, RMAP_EVENT_ECLIST_ADDED); } DEFUN (no_match_ecommunity, no_match_ecommunity_cmd, "no match extcommunity [<(1-99)|(100-500)|WORD>]", NO_STR MATCH_STR "Match BGP/VPN extended community list\n" "Extended community-list number (standard)\n" "Extended community-list number (expanded)\n" "Extended community-list name\n") { return bgp_route_match_delete(vty, "extcommunity", NULL, RMAP_EVENT_ECLIST_DELETED); } DEFUN (match_aspath, match_aspath_cmd, "match as-path WORD", MATCH_STR "Match BGP AS path list\n" "AS path access-list name\n") { int idx_word = 2; return bgp_route_match_add(vty, "as-path", argv[idx_word]->arg, RMAP_EVENT_ASLIST_ADDED); } DEFUN (no_match_aspath, no_match_aspath_cmd, "no match as-path [WORD]", NO_STR MATCH_STR "Match BGP AS path list\n" "AS path access-list name\n") { return bgp_route_match_delete(vty, "as-path", NULL, RMAP_EVENT_ASLIST_DELETED); } DEFUN (match_origin, match_origin_cmd, "match origin ", MATCH_STR "BGP origin code\n" "remote EGP\n" "local IGP\n" "unknown heritage\n") { int idx_origin = 2; if (strncmp(argv[idx_origin]->arg, "igp", 2) == 0) return bgp_route_match_add(vty, "origin", "igp", RMAP_EVENT_MATCH_ADDED); if (strncmp(argv[idx_origin]->arg, "egp", 1) == 0) return bgp_route_match_add(vty, "origin", "egp", RMAP_EVENT_MATCH_ADDED); if (strncmp(argv[idx_origin]->arg, "incomplete", 2) == 0) return bgp_route_match_add(vty, "origin", "incomplete", RMAP_EVENT_MATCH_ADDED); vty_out(vty, "%% Invalid match origin type\n"); return CMD_WARNING_CONFIG_FAILED; } DEFUN (no_match_origin, no_match_origin_cmd, "no match origin []", NO_STR MATCH_STR "BGP origin code\n" "remote EGP\n" "local IGP\n" "unknown heritage\n") { return bgp_route_match_delete(vty, "origin", NULL, RMAP_EVENT_MATCH_DELETED); } DEFUN (set_ip_nexthop_peer, set_ip_nexthop_peer_cmd, "[no] set ip next-hop peer-address", NO_STR SET_STR IP_STR "Next hop address\n" "Use peer address (for BGP only)\n") { int (*func)(struct vty *, struct route_map_index *, const char *, const char *) = strmatch(argv[0]->text, "no") ? generic_set_delete : generic_set_add; return func(vty, VTY_GET_CONTEXT(route_map_index), "ip next-hop", "peer-address"); } DEFUN (set_ip_nexthop_unchanged, set_ip_nexthop_unchanged_cmd, "[no] set ip next-hop unchanged", NO_STR SET_STR IP_STR "Next hop address\n" "Don't modify existing Next hop address\n") { int (*func)(struct vty *, struct route_map_index *, const char *, const char *) = strmatch(argv[0]->text, "no") ? generic_set_delete : generic_set_add; return func(vty, VTY_GET_CONTEXT(route_map_index), "ip next-hop", "unchanged"); } DEFUN (set_local_pref, set_local_pref_cmd, "set local-preference (0-4294967295)", SET_STR "BGP local preference path attribute\n" "Preference value\n") { int idx_number = 2; return generic_set_add(vty, VTY_GET_CONTEXT(route_map_index), "local-preference", argv[idx_number]->arg); } DEFUN (no_set_local_pref, no_set_local_pref_cmd, "no set local-preference [(0-4294967295)]", NO_STR SET_STR "BGP local preference path attribute\n" "Preference value\n") { int idx_localpref = 3; if (argc <= idx_localpref) return generic_set_delete(vty, VTY_GET_CONTEXT(route_map_index), "local-preference", NULL); return generic_set_delete(vty, VTY_GET_CONTEXT(route_map_index), "local-preference", argv[idx_localpref]->arg); } DEFUN (set_weight, set_weight_cmd, "set weight (0-4294967295)", SET_STR "BGP weight for routing table\n" "Weight value\n") { int idx_number = 2; return generic_set_add(vty, VTY_GET_CONTEXT(route_map_index), "weight", argv[idx_number]->arg); } DEFUN (no_set_weight, no_set_weight_cmd, "no set weight [(0-4294967295)]", NO_STR SET_STR "BGP weight for routing table\n" "Weight value\n") { int idx_weight = 3; if (argc <= idx_weight) return generic_set_delete(vty, VTY_GET_CONTEXT(route_map_index), "weight", NULL); return generic_set_delete(vty, VTY_GET_CONTEXT(route_map_index), "weight", argv[idx_weight]->arg); } DEFUN (set_label_index, set_label_index_cmd, "set label-index (0-1048560)", SET_STR "Label index to associate with the prefix\n" "Label index value\n") { int idx_number = 2; return generic_set_add(vty, VTY_GET_CONTEXT(route_map_index), "label-index", argv[idx_number]->arg); } DEFUN (no_set_label_index, no_set_label_index_cmd, "no set label-index [(0-1048560)]", NO_STR SET_STR "Label index to associate with the prefix\n" "Label index value\n") { int idx_label_index = 3; if (argc <= idx_label_index) return generic_set_delete(vty, VTY_GET_CONTEXT(route_map_index), "label-index", NULL); return generic_set_delete(vty, VTY_GET_CONTEXT(route_map_index), "label-index", argv[idx_label_index]->arg); } DEFUN (set_aspath_prepend_asn, set_aspath_prepend_asn_cmd, "set as-path prepend (1-4294967295)...", SET_STR "Transform BGP AS_PATH attribute\n" "Prepend to the as-path\n" "AS number\n") { int idx_asn = 3; int ret; char *str; str = argv_concat(argv, argc, idx_asn); ret = generic_set_add(vty, VTY_GET_CONTEXT(route_map_index), "as-path prepend", str); XFREE(MTYPE_TMP, str); return ret; } DEFUN (set_aspath_prepend_lastas, set_aspath_prepend_lastas_cmd, "set as-path prepend last-as (1-10)", SET_STR "Transform BGP AS_PATH attribute\n" "Prepend to the as-path\n" "Use the peer's AS-number\n" "Number of times to insert\n") { return set_aspath_prepend_asn(self, vty, argc, argv); } DEFUN (no_set_aspath_prepend, no_set_aspath_prepend_cmd, "no set as-path prepend [(1-4294967295)]", NO_STR SET_STR "Transform BGP AS_PATH attribute\n" "Prepend to the as-path\n" "AS number\n") { int idx_asn = 4; int ret; char *str; str = argv_concat(argv, argc, idx_asn); ret = generic_set_delete(vty, VTY_GET_CONTEXT(route_map_index), "as-path prepend", str); XFREE(MTYPE_TMP, str); return ret; } DEFUN (no_set_aspath_prepend_lastas, no_set_aspath_prepend_lastas_cmd, "no set as-path prepend last-as [(1-10)]", NO_STR SET_STR "Transform BGP AS_PATH attribute\n" "Prepend to the as-path\n" "Use the peers AS-number\n" "Number of times to insert\n") { return no_set_aspath_prepend(self, vty, argc, argv); } DEFUN (set_aspath_exclude, set_aspath_exclude_cmd, "set as-path exclude (1-4294967295)...", SET_STR "Transform BGP AS-path attribute\n" "Exclude from the as-path\n" "AS number\n") { int idx_asn = 3; int ret; char *str; str = argv_concat(argv, argc, idx_asn); ret = generic_set_add(vty, VTY_GET_CONTEXT(route_map_index), "as-path exclude", str); XFREE(MTYPE_TMP, str); return ret; } DEFUN (no_set_aspath_exclude, no_set_aspath_exclude_cmd, "no set as-path exclude (1-4294967295)...", NO_STR SET_STR "Transform BGP AS_PATH attribute\n" "Exclude from the as-path\n" "AS number\n") { int idx_asn = 4; int ret; char *str; str = argv_concat(argv, argc, idx_asn); ret = generic_set_delete(vty, VTY_GET_CONTEXT(route_map_index), "as-path exclude", str); XFREE(MTYPE_TMP, str); return ret; } ALIAS(no_set_aspath_exclude, no_set_aspath_exclude_all_cmd, "no set as-path exclude", NO_STR SET_STR "Transform BGP AS_PATH attribute\n" "Exclude from the as-path\n") DEFUN (set_community, set_community_cmd, "set community AA:NN...", SET_STR "BGP community attribute\n" COMMUNITY_VAL_STR) { int idx_aa_nn = 2; int i; int first = 0; int additive = 0; struct buffer *b; struct community *com = NULL; char *str; char *argstr; int ret; b = buffer_new(1024); for (i = idx_aa_nn; i < argc; i++) { if (strncmp(argv[i]->arg, "additive", strlen(argv[i]->arg)) == 0) { additive = 1; continue; } if (first) buffer_putc(b, ' '); else first = 1; if (strncmp(argv[i]->arg, "internet", strlen(argv[i]->arg)) == 0) { buffer_putstr(b, "internet"); continue; } if (strncmp(argv[i]->arg, "local-AS", strlen(argv[i]->arg)) == 0) { buffer_putstr(b, "local-AS"); continue; } if (strncmp(argv[i]->arg, "no-a", strlen("no-a")) == 0 && strncmp(argv[i]->arg, "no-advertise", strlen(argv[i]->arg)) == 0) { buffer_putstr(b, "no-advertise"); continue; } if (strncmp(argv[i]->arg, "no-e", strlen("no-e")) == 0 && strncmp(argv[i]->arg, "no-export", strlen(argv[i]->arg)) == 0) { buffer_putstr(b, "no-export"); continue; } if (strncmp(argv[i]->arg, "graceful-shutdown", strlen(argv[i]->arg)) == 0) { buffer_putstr(b, "graceful-shutdown"); continue; } buffer_putstr(b, argv[i]->arg); } buffer_putc(b, '\0'); /* Fetch result string then compile it to communities attribute. */ str = buffer_getstr(b); buffer_free(b); if (str) { com = community_str2com(str); XFREE(MTYPE_TMP, str); } /* Can't compile user input into communities attribute. */ if (!com) { vty_out(vty, "%% Malformed communities attribute\n"); return CMD_WARNING_CONFIG_FAILED; } /* Set communites attribute string. */ str = community_str(com, false); if (additive) { size_t argstr_sz = strlen(str) + strlen(" additive") + 1; argstr = XCALLOC(MTYPE_TMP, argstr_sz); strlcpy(argstr, str, argstr_sz); strlcat(argstr, " additive", argstr_sz); ret = generic_set_add(vty, VTY_GET_CONTEXT(route_map_index), "community", argstr); XFREE(MTYPE_TMP, argstr); } else ret = generic_set_add(vty, VTY_GET_CONTEXT(route_map_index), "community", str); community_free(&com); return ret; } DEFUN (set_community_none, set_community_none_cmd, "set community none", SET_STR "BGP community attribute\n" "No community attribute\n") { return generic_set_add(vty, VTY_GET_CONTEXT(route_map_index), "community", "none"); } DEFUN (no_set_community, no_set_community_cmd, "no set community AA:NN...", NO_STR SET_STR "BGP community attribute\n" COMMUNITY_VAL_STR) { return generic_set_delete(vty, VTY_GET_CONTEXT(route_map_index), "community", NULL); } ALIAS (no_set_community, no_set_community_short_cmd, "no set community", NO_STR SET_STR "BGP community attribute\n") DEFUN (set_community_delete, set_community_delete_cmd, "set comm-list <(1-99)|(100-500)|WORD> delete", SET_STR "set BGP community list (for deletion)\n" "Community-list number (standard)\n" "Community-list number (expanded)\n" "Community-list name\n" "Delete matching communities\n") { int idx_comm_list = 2; char *args; args = argv_concat(argv, argc, idx_comm_list); generic_set_add(vty, VTY_GET_CONTEXT(route_map_index), "comm-list", args); XFREE(MTYPE_TMP, args); return CMD_SUCCESS; } DEFUN (no_set_community_delete, no_set_community_delete_cmd, "no set comm-list [<(1-99)|(100-500)|WORD> delete]", NO_STR SET_STR "set BGP community list (for deletion)\n" "Community-list number (standard)\n" "Community-list number (expanded)\n" "Community-list name\n" "Delete matching communities\n") { return generic_set_delete(vty, VTY_GET_CONTEXT(route_map_index), "comm-list", NULL); } DEFUN (set_lcommunity, set_lcommunity_cmd, "set large-community AA:BB:CC...", SET_STR "BGP large community attribute\n" "Large Community number in aa:bb:cc format or additive\n") { int ret; char *str; str = argv_concat(argv, argc, 2); ret = generic_set_add(vty, VTY_GET_CONTEXT(route_map_index), "large-community", str); XFREE(MTYPE_TMP, str); return ret; } DEFUN (set_lcommunity_none, set_lcommunity_none_cmd, "set large-community none", SET_STR "BGP large community attribute\n" "No large community attribute\n") { return generic_set_add(vty, VTY_GET_CONTEXT(route_map_index), "large-community", "none"); } DEFUN (no_set_lcommunity, no_set_lcommunity_cmd, "no set large-community none", NO_STR SET_STR "BGP large community attribute\n" "No community attribute\n") { return generic_set_delete(vty, VTY_GET_CONTEXT(route_map_index), "large-community", NULL); } DEFUN (no_set_lcommunity1, no_set_lcommunity1_cmd, "no set large-community AA:BB:CC...", NO_STR SET_STR "BGP large community attribute\n" "Large community in AA:BB:CC... format or additive\n") { return generic_set_delete(vty, VTY_GET_CONTEXT(route_map_index), "large-community", NULL); } ALIAS (no_set_lcommunity1, no_set_lcommunity1_short_cmd, "no set large-community", NO_STR SET_STR "BGP large community attribute\n") DEFUN (set_lcommunity_delete, set_lcommunity_delete_cmd, "set large-comm-list <(1-99)|(100-500)|WORD> delete", SET_STR "set BGP large community list (for deletion)\n" "Large Community-list number (standard)\n" "Large Communitly-list number (expanded)\n" "Large Community-list name\n" "Delete matching large communities\n") { int idx_lcomm_list = 2; char *args; args = argv_concat(argv, argc, idx_lcomm_list); generic_set_add(vty, VTY_GET_CONTEXT(route_map_index), "large-comm-list", args); XFREE(MTYPE_TMP, args); return CMD_SUCCESS; } DEFUN (no_set_lcommunity_delete, no_set_lcommunity_delete_cmd, "no set large-comm-list <(1-99)|(100-500)|WORD> [delete]", NO_STR SET_STR "set BGP large community list (for deletion)\n" "Large Community-list number (standard)\n" "Large Communitly-list number (expanded)\n" "Large Community-list name\n" "Delete matching large communities\n") { return generic_set_delete(vty, VTY_GET_CONTEXT(route_map_index), "large-comm-list", NULL); } ALIAS (no_set_lcommunity_delete, no_set_lcommunity_delete_short_cmd, "no set large-comm-list", NO_STR SET_STR "set BGP large community list (for deletion)\n") DEFUN (set_ecommunity_rt, set_ecommunity_rt_cmd, "set extcommunity rt ASN:NN_OR_IP-ADDRESS:NN...", SET_STR "BGP extended community attribute\n" "Route Target extended community\n" "VPN extended community\n") { int idx_asn_nn = 3; int ret; char *str; str = argv_concat(argv, argc, idx_asn_nn); ret = generic_set_add(vty, VTY_GET_CONTEXT(route_map_index), "extcommunity rt", str); XFREE(MTYPE_TMP, str); return ret; } DEFUN (no_set_ecommunity_rt, no_set_ecommunity_rt_cmd, "no set extcommunity rt ASN:NN_OR_IP-ADDRESS:NN...", NO_STR SET_STR "BGP extended community attribute\n" "Route Target extended community\n" "VPN extended community\n") { return generic_set_delete(vty, VTY_GET_CONTEXT(route_map_index), "extcommunity rt", NULL); } ALIAS (no_set_ecommunity_rt, no_set_ecommunity_rt_short_cmd, "no set extcommunity rt", NO_STR SET_STR "BGP extended community attribute\n" "Route Target extended community\n") DEFUN (set_ecommunity_soo, set_ecommunity_soo_cmd, "set extcommunity soo ASN:NN_OR_IP-ADDRESS:NN...", SET_STR "BGP extended community attribute\n" "Site-of-Origin extended community\n" "VPN extended community\n") { int idx_asn_nn = 3; int ret; char *str; str = argv_concat(argv, argc, idx_asn_nn); ret = generic_set_add(vty, VTY_GET_CONTEXT(route_map_index), "extcommunity soo", str); XFREE(MTYPE_TMP, str); return ret; } DEFUN (no_set_ecommunity_soo, no_set_ecommunity_soo_cmd, "no set extcommunity soo ASN:NN_OR_IP-ADDRESS:NN...", NO_STR SET_STR "BGP extended community attribute\n" "Site-of-Origin extended community\n" "VPN extended community\n") { return generic_set_delete(vty, VTY_GET_CONTEXT(route_map_index), "extcommunity soo", NULL); } ALIAS (no_set_ecommunity_soo, no_set_ecommunity_soo_short_cmd, "no set extcommunity soo", NO_STR SET_STR "GP extended community attribute\n" "Site-of-Origin extended community\n") DEFUN (set_origin, set_origin_cmd, "set origin ", SET_STR "BGP origin code\n" "remote EGP\n" "local IGP\n" "unknown heritage\n") { int idx_origin = 2; if (strncmp(argv[idx_origin]->arg, "igp", 2) == 0) return generic_set_add(vty, VTY_GET_CONTEXT(route_map_index), "origin", "igp"); if (strncmp(argv[idx_origin]->arg, "egp", 1) == 0) return generic_set_add(vty, VTY_GET_CONTEXT(route_map_index), "origin", "egp"); if (strncmp(argv[idx_origin]->arg, "incomplete", 2) == 0) return generic_set_add(vty, VTY_GET_CONTEXT(route_map_index), "origin", "incomplete"); vty_out(vty, "%% Invalid set origin type\n"); return CMD_WARNING_CONFIG_FAILED; } DEFUN (no_set_origin, no_set_origin_cmd, "no set origin []", NO_STR SET_STR "BGP origin code\n" "remote EGP\n" "local IGP\n" "unknown heritage\n") { return generic_set_delete(vty, VTY_GET_CONTEXT(route_map_index), "origin", NULL); } DEFUN (set_atomic_aggregate, set_atomic_aggregate_cmd, "set atomic-aggregate", SET_STR "BGP atomic aggregate attribute\n" ) { return generic_set_add(vty, VTY_GET_CONTEXT(route_map_index), "atomic-aggregate", NULL); } DEFUN (no_set_atomic_aggregate, no_set_atomic_aggregate_cmd, "no set atomic-aggregate", NO_STR SET_STR "BGP atomic aggregate attribute\n" ) { return generic_set_delete(vty, VTY_GET_CONTEXT(route_map_index), "atomic-aggregate", NULL); } DEFUN (set_aggregator_as, set_aggregator_as_cmd, "set aggregator as (1-4294967295) A.B.C.D", SET_STR "BGP aggregator attribute\n" "AS number of aggregator\n" "AS number\n" "IP address of aggregator\n") { int idx_number = 3; int idx_ipv4 = 4; int ret; struct in_addr address; char *argstr; ret = inet_aton(argv[idx_ipv4]->arg, &address); if (ret == 0) { vty_out(vty, "Aggregator IP address is invalid\n"); return CMD_WARNING_CONFIG_FAILED; } argstr = XMALLOC(MTYPE_ROUTE_MAP_COMPILED, strlen(argv[idx_number]->arg) + strlen(argv[idx_ipv4]->arg) + 2); sprintf(argstr, "%s %s", argv[idx_number]->arg, argv[idx_ipv4]->arg); ret = generic_set_add(vty, VTY_GET_CONTEXT(route_map_index), "aggregator as", argstr); XFREE(MTYPE_ROUTE_MAP_COMPILED, argstr); return ret; } DEFUN (no_set_aggregator_as, no_set_aggregator_as_cmd, "no set aggregator as [(1-4294967295) A.B.C.D]", NO_STR SET_STR "BGP aggregator attribute\n" "AS number of aggregator\n" "AS number\n" "IP address of aggregator\n") { int idx_asn = 4; int idx_ip = 5; int ret; struct in_addr address; char *argstr; if (argc <= idx_asn) return generic_set_delete(vty, VTY_GET_CONTEXT(route_map_index), "aggregator as", NULL); ret = inet_aton(argv[idx_ip]->arg, &address); if (ret == 0) { vty_out(vty, "Aggregator IP address is invalid\n"); return CMD_WARNING_CONFIG_FAILED; } argstr = XMALLOC(MTYPE_ROUTE_MAP_COMPILED, strlen(argv[idx_asn]->arg) + strlen(argv[idx_ip]->arg) + 2); sprintf(argstr, "%s %s", argv[idx_asn]->arg, argv[idx_ip]->arg); ret = generic_set_delete(vty, VTY_GET_CONTEXT(route_map_index), "aggregator as", argstr); XFREE(MTYPE_ROUTE_MAP_COMPILED, argstr); return ret; } DEFUN (match_ipv6_next_hop, match_ipv6_next_hop_cmd, "match ipv6 next-hop X:X::X:X", MATCH_STR IPV6_STR "Match IPv6 next-hop address of route\n" "IPv6 address of next hop\n") { int idx_ipv6 = 3; return bgp_route_match_add(vty, "ipv6 next-hop", argv[idx_ipv6]->arg, RMAP_EVENT_MATCH_ADDED); } DEFUN (no_match_ipv6_next_hop, no_match_ipv6_next_hop_cmd, "no match ipv6 next-hop X:X::X:X", NO_STR MATCH_STR IPV6_STR "Match IPv6 next-hop address of route\n" "IPv6 address of next hop\n") { int idx_ipv6 = 4; return bgp_route_match_delete(vty, "ipv6 next-hop", argv[idx_ipv6]->arg, RMAP_EVENT_MATCH_DELETED); } DEFUN (set_ipv6_nexthop_peer, set_ipv6_nexthop_peer_cmd, "set ipv6 next-hop peer-address", SET_STR IPV6_STR "Next hop address\n" "Use peer address (for BGP only)\n") { return generic_set_add(vty, VTY_GET_CONTEXT(route_map_index), "ipv6 next-hop peer-address", NULL); } DEFUN (no_set_ipv6_nexthop_peer, no_set_ipv6_nexthop_peer_cmd, "no set ipv6 next-hop peer-address", NO_STR SET_STR IPV6_STR "IPv6 next-hop address\n" "Use peer address (for BGP only)\n") { return generic_set_delete(vty, VTY_GET_CONTEXT(route_map_index), "ipv6 next-hop peer-address", NULL); } DEFUN (set_ipv6_nexthop_prefer_global, set_ipv6_nexthop_prefer_global_cmd, "set ipv6 next-hop prefer-global", SET_STR IPV6_STR "IPv6 next-hop address\n" "Prefer global over link-local if both exist\n") { return generic_set_add(vty, VTY_GET_CONTEXT(route_map_index), "ipv6 next-hop prefer-global", NULL); ; } DEFUN (no_set_ipv6_nexthop_prefer_global, no_set_ipv6_nexthop_prefer_global_cmd, "no set ipv6 next-hop prefer-global", NO_STR SET_STR IPV6_STR "IPv6 next-hop address\n" "Prefer global over link-local if both exist\n") { return generic_set_delete(vty, VTY_GET_CONTEXT(route_map_index), "ipv6 next-hop prefer-global", NULL); } DEFUN (set_ipv6_nexthop_global, set_ipv6_nexthop_global_cmd, "set ipv6 next-hop global X:X::X:X", SET_STR IPV6_STR "IPv6 next-hop address\n" "IPv6 global address\n" "IPv6 address of next hop\n") { int idx_ipv6 = 4; struct in6_addr addr; int ret; ret = inet_pton(AF_INET6, argv[idx_ipv6]->arg, &addr); if (!ret) { vty_out(vty, "%% Malformed nexthop address\n"); return CMD_WARNING_CONFIG_FAILED; } if (IN6_IS_ADDR_UNSPECIFIED(&addr) || IN6_IS_ADDR_LOOPBACK(&addr) || IN6_IS_ADDR_MULTICAST(&addr) || IN6_IS_ADDR_LINKLOCAL(&addr)) { vty_out(vty, "%% Invalid global nexthop address\n"); return CMD_WARNING_CONFIG_FAILED; } return generic_set_add(vty, VTY_GET_CONTEXT(route_map_index), "ipv6 next-hop global", argv[idx_ipv6]->arg); } DEFUN (no_set_ipv6_nexthop_global, no_set_ipv6_nexthop_global_cmd, "no set ipv6 next-hop global X:X::X:X", NO_STR SET_STR IPV6_STR "IPv6 next-hop address\n" "IPv6 global address\n" "IPv6 address of next hop\n") { int idx_ipv6 = 5; if (argc <= idx_ipv6) return generic_set_delete(vty, VTY_GET_CONTEXT(route_map_index), "ipv6 next-hop global", NULL); return generic_set_delete(vty, VTY_GET_CONTEXT(route_map_index), "ipv6 next-hop global", argv[idx_ipv6]->arg); } #ifdef KEEP_OLD_VPN_COMMANDS DEFUN (set_vpn_nexthop, set_vpn_nexthop_cmd, "set ", SET_STR "VPNv4 information\n" "VPN next-hop address\n" "IP address of next hop\n" "VPNv6 information\n" "VPN next-hop address\n" "IPv6 address of next hop\n") { int idx_ip = 3; afi_t afi; int idx = 0; if (argv_find_and_parse_vpnvx(argv, argc, &idx, &afi)) { if (afi == AFI_IP) return generic_set_add( vty, VTY_GET_CONTEXT(route_map_index), "ipv4 vpn next-hop", argv[idx_ip]->arg); else return generic_set_add( vty, VTY_GET_CONTEXT(route_map_index), "ipv6 vpn next-hop", argv[idx_ip]->arg); } return CMD_SUCCESS; } DEFUN (no_set_vpn_nexthop, no_set_vpn_nexthop_cmd, "no set ", NO_STR SET_STR "VPNv4 information\n" "VPN next-hop address\n" "IP address of next hop\n" "VPNv6 information\n" "VPN next-hop address\n" "IPv6 address of next hop\n") { int idx_ip = 4; char *arg; afi_t afi; int idx = 0; if (argc <= idx_ip) arg = NULL; else arg = argv[idx_ip]->arg; if (argv_find_and_parse_vpnvx(argv, argc, &idx, &afi)) { if (afi == AFI_IP) return generic_set_delete( vty, VTY_GET_CONTEXT(route_map_index), "ipv4 vpn next-hop", arg); else return generic_set_delete( vty, VTY_GET_CONTEXT(route_map_index), "ipv6 vpn next-hop", argv[idx_ip]->arg); } return CMD_SUCCESS; } #endif /* KEEP_OLD_VPN_COMMANDS */ DEFUN (set_ipx_vpn_nexthop, set_ipx_vpn_nexthop_cmd, "set vpn next-hop ", SET_STR "IPv4 information\n" "IPv6 information\n" "VPN information\n" "VPN next-hop address\n" "IP address of next hop\n" "IPv6 address of next hop\n") { int idx_ip = 4; afi_t afi; int idx = 0; if (argv_find_and_parse_afi(argv, argc, &idx, &afi)) { if (afi == AFI_IP) return generic_set_add( vty, VTY_GET_CONTEXT(route_map_index), "ipv4 vpn next-hop", argv[idx_ip]->arg); else return generic_set_add( vty, VTY_GET_CONTEXT(route_map_index), "ipv6 vpn next-hop", argv[idx_ip]->arg); } return CMD_SUCCESS; } DEFUN (no_set_ipx_vpn_nexthop, no_set_ipx_vpn_nexthop_cmd, "no set vpn next-hop []", NO_STR SET_STR "IPv4 information\n" "IPv6 information\n" "VPN information\n" "VPN next-hop address\n" "IP address of next hop\n" "IPv6 address of next hop\n") { int idx_ip = 5; char *arg; afi_t afi; int idx = 0; if (argc <= idx_ip) arg = NULL; else arg = argv[idx_ip]->arg; if (argv_find_and_parse_afi(argv, argc, &idx, &afi)) { if (afi == AFI_IP) return generic_set_delete( vty, VTY_GET_CONTEXT(route_map_index), "ipv4 vpn next-hop", arg); else return generic_set_delete( vty, VTY_GET_CONTEXT(route_map_index), "ipv6 vpn next-hop", arg); } return CMD_SUCCESS; } DEFUN (set_originator_id, set_originator_id_cmd, "set originator-id A.B.C.D", SET_STR "BGP originator ID attribute\n" "IP address of originator\n") { int idx_ipv4 = 2; return generic_set_add(vty, VTY_GET_CONTEXT(route_map_index), "originator-id", argv[idx_ipv4]->arg); } DEFUN (no_set_originator_id, no_set_originator_id_cmd, "no set originator-id [A.B.C.D]", NO_STR SET_STR "BGP originator ID attribute\n" "IP address of originator\n") { int idx = 0; char *arg = argv_find(argv, argc, "A.B.C.D", &idx) ? argv[idx]->arg : NULL; return generic_set_delete(vty, VTY_GET_CONTEXT(route_map_index), "originator-id", arg); } /* Initialization of route map. */ void bgp_route_map_init(void) { route_map_init(); route_map_add_hook(bgp_route_map_add); route_map_delete_hook(bgp_route_map_delete); route_map_event_hook(bgp_route_map_event); route_map_match_interface_hook(generic_match_add); route_map_no_match_interface_hook(generic_match_delete); route_map_match_ip_address_hook(generic_match_add); route_map_no_match_ip_address_hook(generic_match_delete); route_map_match_ip_address_prefix_list_hook(generic_match_add); route_map_no_match_ip_address_prefix_list_hook(generic_match_delete); route_map_match_ip_next_hop_hook(generic_match_add); route_map_no_match_ip_next_hop_hook(generic_match_delete); route_map_match_ip_next_hop_prefix_list_hook(generic_match_add); route_map_no_match_ip_next_hop_prefix_list_hook(generic_match_delete); route_map_match_ip_next_hop_type_hook(generic_match_add); route_map_no_match_ip_next_hop_type_hook(generic_match_delete); route_map_match_ipv6_address_hook(generic_match_add); route_map_no_match_ipv6_address_hook(generic_match_delete); route_map_match_ipv6_address_prefix_list_hook(generic_match_add); route_map_no_match_ipv6_address_prefix_list_hook(generic_match_delete); route_map_match_ipv6_next_hop_type_hook(generic_match_add); route_map_no_match_ipv6_next_hop_type_hook(generic_match_delete); route_map_match_metric_hook(generic_match_add); route_map_no_match_metric_hook(generic_match_delete); route_map_match_tag_hook(generic_match_add); route_map_no_match_tag_hook(generic_match_delete); route_map_set_ip_nexthop_hook(generic_set_add); route_map_no_set_ip_nexthop_hook(generic_set_delete); route_map_set_ipv6_nexthop_local_hook(generic_set_add); route_map_no_set_ipv6_nexthop_local_hook(generic_set_delete); route_map_set_metric_hook(generic_set_add); route_map_no_set_metric_hook(generic_set_delete); route_map_set_tag_hook(generic_set_add); route_map_no_set_tag_hook(generic_set_delete); route_map_install_match(&route_match_peer_cmd); route_map_install_match(&route_match_local_pref_cmd); #if defined(HAVE_LUA) route_map_install_match(&route_match_command_cmd); #endif route_map_install_match(&route_match_ip_address_cmd); route_map_install_match(&route_match_ip_next_hop_cmd); route_map_install_match(&route_match_ip_route_source_cmd); route_map_install_match(&route_match_ip_address_prefix_list_cmd); route_map_install_match(&route_match_ip_next_hop_prefix_list_cmd); route_map_install_match(&route_match_ip_next_hop_type_cmd); route_map_install_match(&route_match_ip_route_source_prefix_list_cmd); route_map_install_match(&route_match_aspath_cmd); route_map_install_match(&route_match_community_cmd); route_map_install_match(&route_match_lcommunity_cmd); route_map_install_match(&route_match_ecommunity_cmd); route_map_install_match(&route_match_local_pref_cmd); route_map_install_match(&route_match_metric_cmd); route_map_install_match(&route_match_origin_cmd); route_map_install_match(&route_match_probability_cmd); route_map_install_match(&route_match_interface_cmd); route_map_install_match(&route_match_tag_cmd); route_map_install_match(&route_match_mac_address_cmd); route_map_install_match(&route_match_evpn_vni_cmd); route_map_install_match(&route_match_evpn_route_type_cmd); route_map_install_match(&route_match_evpn_default_route_cmd); route_map_install_match(&route_match_vrl_source_vrf_cmd); route_map_install_set(&route_set_ip_nexthop_cmd); route_map_install_set(&route_set_local_pref_cmd); route_map_install_set(&route_set_weight_cmd); route_map_install_set(&route_set_label_index_cmd); route_map_install_set(&route_set_metric_cmd); route_map_install_set(&route_set_aspath_prepend_cmd); route_map_install_set(&route_set_aspath_exclude_cmd); route_map_install_set(&route_set_origin_cmd); route_map_install_set(&route_set_atomic_aggregate_cmd); route_map_install_set(&route_set_aggregator_as_cmd); route_map_install_set(&route_set_community_cmd); route_map_install_set(&route_set_community_delete_cmd); route_map_install_set(&route_set_lcommunity_cmd); route_map_install_set(&route_set_lcommunity_delete_cmd); route_map_install_set(&route_set_vpnv4_nexthop_cmd); route_map_install_set(&route_set_vpnv6_nexthop_cmd); route_map_install_set(&route_set_originator_id_cmd); route_map_install_set(&route_set_ecommunity_rt_cmd); route_map_install_set(&route_set_ecommunity_soo_cmd); route_map_install_set(&route_set_tag_cmd); route_map_install_set(&route_set_label_index_cmd); install_element(RMAP_NODE, &match_peer_cmd); install_element(RMAP_NODE, &match_peer_local_cmd); install_element(RMAP_NODE, &no_match_peer_cmd); install_element(RMAP_NODE, &match_ip_route_source_cmd); install_element(RMAP_NODE, &no_match_ip_route_source_cmd); install_element(RMAP_NODE, &match_ip_route_source_prefix_list_cmd); install_element(RMAP_NODE, &no_match_ip_route_source_prefix_list_cmd); install_element(RMAP_NODE, &match_mac_address_cmd); install_element(RMAP_NODE, &no_match_mac_address_cmd); install_element(RMAP_NODE, &match_evpn_vni_cmd); install_element(RMAP_NODE, &no_match_evpn_vni_cmd); install_element(RMAP_NODE, &match_evpn_route_type_cmd); install_element(RMAP_NODE, &no_match_evpn_route_type_cmd); install_element(RMAP_NODE, &match_evpn_default_route_cmd); install_element(RMAP_NODE, &no_match_evpn_default_route_cmd); install_element(RMAP_NODE, &match_vrl_source_vrf_cmd); install_element(RMAP_NODE, &no_match_vrl_source_vrf_cmd); install_element(RMAP_NODE, &match_aspath_cmd); install_element(RMAP_NODE, &no_match_aspath_cmd); install_element(RMAP_NODE, &match_local_pref_cmd); install_element(RMAP_NODE, &no_match_local_pref_cmd); install_element(RMAP_NODE, &match_community_cmd); install_element(RMAP_NODE, &no_match_community_cmd); install_element(RMAP_NODE, &match_lcommunity_cmd); install_element(RMAP_NODE, &no_match_lcommunity_cmd); install_element(RMAP_NODE, &match_ecommunity_cmd); install_element(RMAP_NODE, &no_match_ecommunity_cmd); install_element(RMAP_NODE, &match_origin_cmd); install_element(RMAP_NODE, &no_match_origin_cmd); install_element(RMAP_NODE, &match_probability_cmd); install_element(RMAP_NODE, &no_match_probability_cmd); install_element(RMAP_NODE, &set_ip_nexthop_peer_cmd); install_element(RMAP_NODE, &set_ip_nexthop_unchanged_cmd); install_element(RMAP_NODE, &set_local_pref_cmd); install_element(RMAP_NODE, &no_set_local_pref_cmd); install_element(RMAP_NODE, &set_weight_cmd); install_element(RMAP_NODE, &set_label_index_cmd); install_element(RMAP_NODE, &no_set_weight_cmd); install_element(RMAP_NODE, &no_set_label_index_cmd); install_element(RMAP_NODE, &set_aspath_prepend_asn_cmd); install_element(RMAP_NODE, &set_aspath_prepend_lastas_cmd); install_element(RMAP_NODE, &set_aspath_exclude_cmd); install_element(RMAP_NODE, &no_set_aspath_prepend_cmd); install_element(RMAP_NODE, &no_set_aspath_prepend_lastas_cmd); install_element(RMAP_NODE, &no_set_aspath_exclude_cmd); install_element(RMAP_NODE, &no_set_aspath_exclude_all_cmd); install_element(RMAP_NODE, &set_origin_cmd); install_element(RMAP_NODE, &no_set_origin_cmd); install_element(RMAP_NODE, &set_atomic_aggregate_cmd); install_element(RMAP_NODE, &no_set_atomic_aggregate_cmd); install_element(RMAP_NODE, &set_aggregator_as_cmd); install_element(RMAP_NODE, &no_set_aggregator_as_cmd); install_element(RMAP_NODE, &set_community_cmd); install_element(RMAP_NODE, &set_community_none_cmd); install_element(RMAP_NODE, &no_set_community_cmd); install_element(RMAP_NODE, &no_set_community_short_cmd); install_element(RMAP_NODE, &set_community_delete_cmd); install_element(RMAP_NODE, &no_set_community_delete_cmd); install_element(RMAP_NODE, &set_lcommunity_cmd); install_element(RMAP_NODE, &set_lcommunity_none_cmd); install_element(RMAP_NODE, &no_set_lcommunity_cmd); install_element(RMAP_NODE, &no_set_lcommunity1_cmd); install_element(RMAP_NODE, &no_set_lcommunity1_short_cmd); install_element(RMAP_NODE, &set_lcommunity_delete_cmd); install_element(RMAP_NODE, &no_set_lcommunity_delete_cmd); install_element(RMAP_NODE, &no_set_lcommunity_delete_short_cmd); install_element(RMAP_NODE, &set_ecommunity_rt_cmd); install_element(RMAP_NODE, &no_set_ecommunity_rt_cmd); install_element(RMAP_NODE, &no_set_ecommunity_rt_short_cmd); install_element(RMAP_NODE, &set_ecommunity_soo_cmd); install_element(RMAP_NODE, &no_set_ecommunity_soo_cmd); install_element(RMAP_NODE, &no_set_ecommunity_soo_short_cmd); #ifdef KEEP_OLD_VPN_COMMANDS install_element(RMAP_NODE, &set_vpn_nexthop_cmd); install_element(RMAP_NODE, &no_set_vpn_nexthop_cmd); #endif /* KEEP_OLD_VPN_COMMANDS */ install_element(RMAP_NODE, &set_ipx_vpn_nexthop_cmd); install_element(RMAP_NODE, &no_set_ipx_vpn_nexthop_cmd); install_element(RMAP_NODE, &set_originator_id_cmd); install_element(RMAP_NODE, &no_set_originator_id_cmd); route_map_install_match(&route_match_ipv6_address_cmd); route_map_install_match(&route_match_ipv6_next_hop_cmd); route_map_install_match(&route_match_ipv6_address_prefix_list_cmd); route_map_install_match(&route_match_ipv6_next_hop_type_cmd); route_map_install_set(&route_set_ipv6_nexthop_global_cmd); route_map_install_set(&route_set_ipv6_nexthop_prefer_global_cmd); route_map_install_set(&route_set_ipv6_nexthop_local_cmd); route_map_install_set(&route_set_ipv6_nexthop_peer_cmd); install_element(RMAP_NODE, &match_ipv6_next_hop_cmd); install_element(RMAP_NODE, &no_match_ipv6_next_hop_cmd); install_element(RMAP_NODE, &set_ipv6_nexthop_global_cmd); install_element(RMAP_NODE, &no_set_ipv6_nexthop_global_cmd); install_element(RMAP_NODE, &set_ipv6_nexthop_prefer_global_cmd); install_element(RMAP_NODE, &no_set_ipv6_nexthop_prefer_global_cmd); install_element(RMAP_NODE, &set_ipv6_nexthop_peer_cmd); install_element(RMAP_NODE, &no_set_ipv6_nexthop_peer_cmd); #if defined(HAVE_LUA) install_element(RMAP_NODE, &match_command_cmd); install_element(RMAP_NODE, &no_match_command_cmd); #endif } void bgp_route_map_terminate(void) { /* ToDo: Cleanup all the used memory */ route_map_finish(); } frr-7.2.1/bgpd/bgp_rpki.c0000644000000000000000000011407213610377563012076 00000000000000/* * BGP RPKI * Copyright (C) 2013 Michael Mester (m.mester@fu-berlin.de), for FU Berlin * Copyright (C) 2014-2017 Andreas Reuter (andreas.reuter@fu-berlin.de), for FU * Berlin * Copyright (C) 2016-2017 Colin Sames (colin.sames@haw-hamburg.de), for HAW * Hamburg * Copyright (C) 2017-2018 Marcel Röthke (marcel.roethke@haw-hamburg.de), * for HAW Hamburg * * This file is part of FRRouting. * * This program is free software; you can redistribute it and/or modify it * under the terms of the 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; see the file COPYING; if not, write to the Free Software * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA */ /* If rtrlib compiled with ssh support, don`t fail build */ #define LIBSSH_LEGACY_0_4 #include #include #include #include #include #include "prefix.h" #include "log.h" #include "command.h" #include "linklist.h" #include "memory.h" #include "thread.h" #include "filter.h" #include "bgpd/bgpd.h" #include "bgpd/bgp_table.h" #include "bgp_advertise.h" #include "bgpd/bgp_debug.h" #include "bgpd/bgp_attr.h" #include "bgpd/bgp_aspath.h" #include "bgpd/bgp_route.h" #include "lib/network.h" #include "lib/thread.h" #ifndef VTYSH_EXTRACT_PL #include "rtrlib/rtrlib.h" #endif #include "hook.h" #include "libfrr.h" #include "version.h" #ifndef VTYSH_EXTRACT_PL #include "bgpd/bgp_rpki_clippy.c" #endif DEFINE_MTYPE_STATIC(BGPD, BGP_RPKI_CACHE, "BGP RPKI Cache server") DEFINE_MTYPE_STATIC(BGPD, BGP_RPKI_CACHE_GROUP, "BGP RPKI Cache server group") #define RPKI_VALID 1 #define RPKI_NOTFOUND 2 #define RPKI_INVALID 3 #define POLLING_PERIOD_DEFAULT 3600 #define EXPIRE_INTERVAL_DEFAULT 7200 #define RETRY_INTERVAL_DEFAULT 600 #define RPKI_DEBUG(...) \ if (rpki_debug) { \ zlog_debug("RPKI: " __VA_ARGS__); \ } #define RPKI_OUTPUT_STRING "Control rpki specific settings\n" struct cache { enum { TCP, SSH } type; struct tr_socket *tr_socket; union { struct tr_tcp_config *tcp_config; struct tr_ssh_config *ssh_config; } tr_config; struct rtr_socket *rtr_socket; uint8_t preference; }; enum return_values { SUCCESS = 0, ERROR = -1 }; struct rpki_for_each_record_arg { struct vty *vty; unsigned int *prefix_amount; }; static int start(void); static void stop(void); static int reset(bool force); static struct rtr_mgr_group *get_connected_group(void); static void print_prefix_table(struct vty *vty); static void install_cli_commands(void); static int config_write(struct vty *vty); static void overwrite_exit_commands(void); static void free_cache(struct cache *cache); static struct rtr_mgr_group *get_groups(void); #if defined(FOUND_SSH) static int add_ssh_cache(const char *host, const unsigned int port, const char *username, const char *client_privkey_path, const char *client_pubkey_path, const char *server_pubkey_path, const uint8_t preference); #endif static struct rtr_socket *create_rtr_socket(struct tr_socket *tr_socket); static struct cache *find_cache(const uint8_t preference); static int add_tcp_cache(const char *host, const char *port, const uint8_t preference); static void print_record(const struct pfx_record *record, struct vty *vty); static int is_synchronized(void); static int is_running(void); static void route_match_free(void *rule); static enum route_map_cmd_result_t route_match(void *rule, const struct prefix *prefix, route_map_object_t type, void *object); static void *route_match_compile(const char *arg); static void revalidate_bgp_node(struct bgp_node *bgp_node, afi_t afi, safi_t safi); static void revalidate_all_routes(void); static struct rtr_mgr_config *rtr_config; static struct list *cache_list; static int rtr_is_running; static int rtr_is_stopping; static _Atomic int rtr_update_overflow; static int rpki_debug; static unsigned int polling_period; static unsigned int expire_interval; static unsigned int retry_interval; static int rpki_sync_socket_rtr; static int rpki_sync_socket_bgpd; static struct cmd_node rpki_node = {RPKI_NODE, "%s(config-rpki)# ", 1}; static struct route_map_rule_cmd route_match_rpki_cmd = { "rpki", route_match, route_match_compile, route_match_free}; static void *malloc_wrapper(size_t size) { return XMALLOC(MTYPE_BGP_RPKI_CACHE, size); } static void *realloc_wrapper(void *ptr, size_t size) { return XREALLOC(MTYPE_BGP_RPKI_CACHE, ptr, size); } static void free_wrapper(void *ptr) { XFREE(MTYPE_BGP_RPKI_CACHE, ptr); } static void init_tr_socket(struct cache *cache) { if (cache->type == TCP) tr_tcp_init(cache->tr_config.tcp_config, cache->tr_socket); #if defined(FOUND_SSH) else tr_ssh_init(cache->tr_config.ssh_config, cache->tr_socket); #endif } static void free_tr_socket(struct cache *cache) { if (cache->type == TCP) tr_tcp_init(cache->tr_config.tcp_config, cache->tr_socket); #if defined(FOUND_SSH) else tr_ssh_init(cache->tr_config.ssh_config, cache->tr_socket); #endif } static int rpki_validate_prefix(struct peer *peer, struct attr *attr, const struct prefix *prefix); static void ipv6_addr_to_network_byte_order(const uint32_t *src, uint32_t *dest) { int i; for (i = 0; i < 4; i++) dest[i] = htonl(src[i]); } static void ipv6_addr_to_host_byte_order(const uint32_t *src, uint32_t *dest) { int i; for (i = 0; i < 4; i++) dest[i] = ntohl(src[i]); } static enum route_map_cmd_result_t route_match(void *rule, const struct prefix *prefix, route_map_object_t type, void *object) { int *rpki_status = rule; struct bgp_path_info *path; if (type == RMAP_BGP) { path = object; if (rpki_validate_prefix(path->peer, path->attr, prefix) == *rpki_status) { return RMAP_MATCH; } } return RMAP_NOMATCH; } static void *route_match_compile(const char *arg) { int *rpki_status; rpki_status = XMALLOC(MTYPE_ROUTE_MAP_COMPILED, sizeof(int)); if (strcmp(arg, "valid") == 0) *rpki_status = RPKI_VALID; else if (strcmp(arg, "invalid") == 0) *rpki_status = RPKI_INVALID; else *rpki_status = RPKI_NOTFOUND; return rpki_status; } static void route_match_free(void *rule) { XFREE(MTYPE_ROUTE_MAP_COMPILED, rule); } static struct rtr_socket *create_rtr_socket(struct tr_socket *tr_socket) { struct rtr_socket *rtr_socket = XMALLOC(MTYPE_BGP_RPKI_CACHE, sizeof(struct rtr_socket)); rtr_socket->tr_socket = tr_socket; return rtr_socket; } static struct cache *find_cache(const uint8_t preference) { struct listnode *cache_node; struct cache *cache; for (ALL_LIST_ELEMENTS_RO(cache_list, cache_node, cache)) { if (cache->preference == preference) return cache; } return NULL; } static void print_record(const struct pfx_record *record, struct vty *vty) { char ip[INET6_ADDRSTRLEN]; lrtr_ip_addr_to_str(&record->prefix, ip, sizeof(ip)); vty_out(vty, "%-40s %3u - %3u %10u\n", ip, record->min_len, record->max_len, record->asn); } static void print_record_cb(const struct pfx_record *record, void *data) { struct rpki_for_each_record_arg *arg = data; struct vty *vty = arg->vty; (*arg->prefix_amount)++; print_record(record, vty); } static struct rtr_mgr_group *get_groups(void) { struct listnode *cache_node; struct rtr_mgr_group *rtr_mgr_groups; struct cache *cache; int group_count = listcount(cache_list); if (group_count == 0) return NULL; rtr_mgr_groups = XMALLOC(MTYPE_BGP_RPKI_CACHE_GROUP, group_count * sizeof(struct rtr_mgr_group)); size_t i = 0; for (ALL_LIST_ELEMENTS_RO(cache_list, cache_node, cache)) { rtr_mgr_groups[i].sockets = &cache->rtr_socket; rtr_mgr_groups[i].sockets_len = 1; rtr_mgr_groups[i].preference = cache->preference; init_tr_socket(cache); i++; } return rtr_mgr_groups; } inline int is_synchronized(void) { return rtr_is_running && rtr_mgr_conf_in_sync(rtr_config); } inline int is_running(void) { return rtr_is_running; } static struct prefix *pfx_record_to_prefix(struct pfx_record *record) { struct prefix *prefix = prefix_new(); prefix->prefixlen = record->min_len; if (record->prefix.ver == LRTR_IPV4) { prefix->family = AF_INET; prefix->u.prefix4.s_addr = htonl(record->prefix.u.addr4.addr); } else { prefix->family = AF_INET6; ipv6_addr_to_network_byte_order(record->prefix.u.addr6.addr, prefix->u.prefix6.s6_addr32); } return prefix; } static int bgpd_sync_callback(struct thread *thread) { struct bgp *bgp; struct listnode *node; struct prefix *prefix; struct pfx_record rec; thread_add_read(bm->master, bgpd_sync_callback, NULL, rpki_sync_socket_bgpd, NULL); if (atomic_load_explicit(&rtr_update_overflow, memory_order_seq_cst)) { while (read(rpki_sync_socket_bgpd, &rec, sizeof(struct pfx_record)) != -1) ; atomic_store_explicit(&rtr_update_overflow, 0, memory_order_seq_cst); revalidate_all_routes(); return 0; } int retval = read(rpki_sync_socket_bgpd, &rec, sizeof(struct pfx_record)); if (retval != sizeof(struct pfx_record)) { RPKI_DEBUG("Could not read from rpki_sync_socket_bgpd"); return retval; } prefix = pfx_record_to_prefix(&rec); afi_t afi = (rec.prefix.ver == LRTR_IPV4) ? AFI_IP : AFI_IP6; for (ALL_LIST_ELEMENTS_RO(bm->bgp, node, bgp)) { struct peer *peer; struct listnode *peer_listnode; for (ALL_LIST_ELEMENTS_RO(bgp->peer, peer_listnode, peer)) { safi_t safi; for (safi = SAFI_UNICAST; safi < SAFI_MAX; safi++) { if (!peer->bgp->rib[afi][safi]) continue; struct list *matches = list_new(); matches->del = (void (*)(void *))bgp_unlock_node; bgp_table_range_lookup( peer->bgp->rib[afi][safi], prefix, rec.max_len, matches); struct bgp_node *bgp_node; struct listnode *bgp_listnode; for (ALL_LIST_ELEMENTS_RO(matches, bgp_listnode, bgp_node)) revalidate_bgp_node(bgp_node, afi, safi); list_delete(&matches); } } } prefix_free(prefix); return 0; } static void revalidate_bgp_node(struct bgp_node *bgp_node, afi_t afi, safi_t safi) { struct bgp_adj_in *ain; for (ain = bgp_node->adj_in; ain; ain = ain->next) { int ret; struct bgp_path_info *path = bgp_node_get_bgp_path_info(bgp_node); mpls_label_t *label = NULL; uint32_t num_labels = 0; if (path && path->extra) { label = path->extra->label; num_labels = path->extra->num_labels; } ret = bgp_update(ain->peer, &bgp_node->p, ain->addpath_rx_id, ain->attr, afi, safi, ZEBRA_ROUTE_BGP, BGP_ROUTE_NORMAL, NULL, label, num_labels, 1, NULL); if (ret < 0) return; } } static void revalidate_all_routes(void) { struct bgp *bgp; struct listnode *node; for (ALL_LIST_ELEMENTS_RO(bm->bgp, node, bgp)) { struct peer *peer; struct listnode *peer_listnode; for (ALL_LIST_ELEMENTS_RO(bgp->peer, peer_listnode, peer)) { for (size_t i = 0; i < 2; i++) { safi_t safi; afi_t afi = (i == 0) ? AFI_IP : AFI_IP6; for (safi = SAFI_UNICAST; safi < SAFI_MAX; safi++) { if (!peer->bgp->rib[afi][safi]) continue; bgp_soft_reconfig_in(peer, afi, safi); } } } } } static void rpki_update_cb_sync_rtr(struct pfx_table *p __attribute__((unused)), const struct pfx_record rec, const bool added __attribute__((unused))) { if (rtr_is_stopping || atomic_load_explicit(&rtr_update_overflow, memory_order_seq_cst)) return; int retval = write(rpki_sync_socket_rtr, &rec, sizeof(struct pfx_record)); if (retval == -1 && (errno == EAGAIN || errno == EWOULDBLOCK)) atomic_store_explicit(&rtr_update_overflow, 1, memory_order_seq_cst); else if (retval != sizeof(struct pfx_record)) RPKI_DEBUG("Could not write to rpki_sync_socket_rtr"); } static void rpki_init_sync_socket(void) { int fds[2]; const char *msg; RPKI_DEBUG("initializing sync socket"); if (socketpair(PF_LOCAL, SOCK_DGRAM, 0, fds) != 0) { msg = "could not open rpki sync socketpair"; goto err; } rpki_sync_socket_rtr = fds[0]; rpki_sync_socket_bgpd = fds[1]; if (set_nonblocking(rpki_sync_socket_rtr) != 0) { msg = "could not set rpki_sync_socket_rtr to non blocking"; goto err; } if (set_nonblocking(rpki_sync_socket_bgpd) != 0) { msg = "could not set rpki_sync_socket_bgpd to non blocking"; goto err; } thread_add_read(bm->master, bgpd_sync_callback, NULL, rpki_sync_socket_bgpd, NULL); return; err: zlog_err("RPKI: %s", msg); abort(); } static int bgp_rpki_init(struct thread_master *master) { rpki_debug = 0; rtr_is_running = 0; rtr_is_stopping = 0; cache_list = list_new(); cache_list->del = (void (*)(void *)) & free_cache; polling_period = POLLING_PERIOD_DEFAULT; expire_interval = EXPIRE_INTERVAL_DEFAULT; retry_interval = RETRY_INTERVAL_DEFAULT; install_cli_commands(); rpki_init_sync_socket(); return 0; } static int bgp_rpki_fini(void) { stop(); list_delete(&cache_list); close(rpki_sync_socket_rtr); close(rpki_sync_socket_bgpd); return 0; } static int bgp_rpki_module_init(void) { lrtr_set_alloc_functions(malloc_wrapper, realloc_wrapper, free_wrapper); hook_register(frr_late_init, bgp_rpki_init); hook_register(frr_early_fini, &bgp_rpki_fini); return 0; } static int start(void) { int ret; rtr_is_stopping = 0; rtr_update_overflow = 0; if (list_isempty(cache_list)) { RPKI_DEBUG( "No caches were found in config. Prefix validation is off."); return ERROR; } RPKI_DEBUG("Init rtr_mgr."); int groups_len = listcount(cache_list); struct rtr_mgr_group *groups = get_groups(); RPKI_DEBUG("Polling period: %d", polling_period); ret = rtr_mgr_init(&rtr_config, groups, groups_len, polling_period, expire_interval, retry_interval, rpki_update_cb_sync_rtr, NULL, NULL, NULL); if (ret == RTR_ERROR) { RPKI_DEBUG("Init rtr_mgr failed."); return ERROR; } RPKI_DEBUG("Starting rtr_mgr."); ret = rtr_mgr_start(rtr_config); if (ret == RTR_ERROR) { RPKI_DEBUG("Starting rtr_mgr failed."); rtr_mgr_free(rtr_config); return ERROR; } rtr_is_running = 1; XFREE(MTYPE_BGP_RPKI_CACHE_GROUP, groups); return SUCCESS; } static void stop(void) { rtr_is_stopping = 1; if (rtr_is_running) { rtr_mgr_stop(rtr_config); rtr_mgr_free(rtr_config); rtr_is_running = 0; } } static int reset(bool force) { if (rtr_is_running && !force) return SUCCESS; RPKI_DEBUG("Resetting RPKI Session"); stop(); return start(); } static struct rtr_mgr_group *get_connected_group(void) { if (!cache_list || list_isempty(cache_list)) return NULL; return rtr_mgr_get_first_group(rtr_config); } static void print_prefix_table(struct vty *vty) { struct rpki_for_each_record_arg arg; unsigned int number_of_ipv4_prefixes = 0; unsigned int number_of_ipv6_prefixes = 0; struct rtr_mgr_group *group = get_connected_group(); arg.vty = vty; if (!group) return; struct pfx_table *pfx_table = group->sockets[0]->pfx_table; vty_out(vty, "RPKI/RTR prefix table\n"); vty_out(vty, "%-40s %s %s\n", "Prefix", "Prefix Length", "Origin-AS"); arg.prefix_amount = &number_of_ipv4_prefixes; pfx_table_for_each_ipv4_record(pfx_table, print_record_cb, &arg); arg.prefix_amount = &number_of_ipv6_prefixes; pfx_table_for_each_ipv6_record(pfx_table, print_record_cb, &arg); vty_out(vty, "Number of IPv4 Prefixes: %u\n", number_of_ipv4_prefixes); vty_out(vty, "Number of IPv6 Prefixes: %u\n", number_of_ipv6_prefixes); } static int rpki_validate_prefix(struct peer *peer, struct attr *attr, const struct prefix *prefix) { struct assegment *as_segment; as_t as_number = 0; struct lrtr_ip_addr ip_addr_prefix; enum pfxv_state result; char buf[BUFSIZ]; const char *prefix_string; if (!is_synchronized()) return 0; // No aspath means route comes from iBGP if (!attr->aspath || !attr->aspath->segments) { // Set own as number as_number = peer->bgp->as; } else { as_segment = attr->aspath->segments; // Find last AsSegment while (as_segment->next) as_segment = as_segment->next; if (as_segment->type == AS_SEQUENCE) { // Get rightmost asn as_number = as_segment->as[as_segment->length - 1]; } else if (as_segment->type == AS_CONFED_SEQUENCE || as_segment->type == AS_CONFED_SET) { // Set own as number as_number = peer->bgp->as; } else { // RFC says: "Take distinguished value NONE as asn" // which means state is unknown return RPKI_NOTFOUND; } } // Get the prefix in requested format switch (prefix->family) { case AF_INET: ip_addr_prefix.ver = LRTR_IPV4; ip_addr_prefix.u.addr4.addr = ntohl(prefix->u.prefix4.s_addr); break; case AF_INET6: ip_addr_prefix.ver = LRTR_IPV6; ipv6_addr_to_host_byte_order(prefix->u.prefix6.s6_addr32, ip_addr_prefix.u.addr6.addr); break; default: return 0; } // Do the actual validation rtr_mgr_validate(rtr_config, as_number, &ip_addr_prefix, prefix->prefixlen, &result); // Print Debug output prefix_string = prefix2str(prefix, buf, sizeof(buf)); switch (result) { case BGP_PFXV_STATE_VALID: RPKI_DEBUG( "Validating Prefix %s from asn %u Result: VALID", prefix_string, as_number); return RPKI_VALID; case BGP_PFXV_STATE_NOT_FOUND: RPKI_DEBUG( "Validating Prefix %s from asn %u Result: NOT FOUND", prefix_string, as_number); return RPKI_NOTFOUND; case BGP_PFXV_STATE_INVALID: RPKI_DEBUG( "Validating Prefix %s from asn %u Result: INVALID", prefix_string, as_number); return RPKI_INVALID; default: RPKI_DEBUG( "Validating Prefix %s from asn %u Result: CANNOT VALIDATE", prefix_string, as_number); break; } return 0; } static int add_cache(struct cache *cache) { uint8_t preference = cache->preference; struct rtr_mgr_group group; group.preference = preference; group.sockets_len = 1; group.sockets = &cache->rtr_socket; if (rtr_is_running) { init_tr_socket(cache); if (rtr_mgr_add_group(rtr_config, &group) != RTR_SUCCESS) { free_tr_socket(cache); return ERROR; } } listnode_add(cache_list, cache); return SUCCESS; } static int add_tcp_cache(const char *host, const char *port, const uint8_t preference) { struct rtr_socket *rtr_socket; struct tr_tcp_config *tcp_config = XMALLOC(MTYPE_BGP_RPKI_CACHE, sizeof(struct tr_tcp_config)); struct tr_socket *tr_socket = XMALLOC(MTYPE_BGP_RPKI_CACHE, sizeof(struct tr_socket)); struct cache *cache = XMALLOC(MTYPE_BGP_RPKI_CACHE, sizeof(struct cache)); tcp_config->host = XSTRDUP(MTYPE_BGP_RPKI_CACHE, host); tcp_config->port = XSTRDUP(MTYPE_BGP_RPKI_CACHE, port); tcp_config->bindaddr = NULL; rtr_socket = create_rtr_socket(tr_socket); cache->type = TCP; cache->tr_socket = tr_socket; cache->tr_config.tcp_config = tcp_config; cache->rtr_socket = rtr_socket; cache->preference = preference; int ret = add_cache(cache); if (ret != SUCCESS) { free_cache(cache); } return ret; } #if defined(FOUND_SSH) static int add_ssh_cache(const char *host, const unsigned int port, const char *username, const char *client_privkey_path, const char *client_pubkey_path, const char *server_pubkey_path, const uint8_t preference) { struct tr_ssh_config *ssh_config = XMALLOC(MTYPE_BGP_RPKI_CACHE, sizeof(struct tr_ssh_config)); struct cache *cache = XMALLOC(MTYPE_BGP_RPKI_CACHE, sizeof(struct cache)); struct tr_socket *tr_socket = XMALLOC(MTYPE_BGP_RPKI_CACHE, sizeof(struct tr_socket)); struct rtr_socket *rtr_socket; ssh_config->port = port; ssh_config->host = XSTRDUP(MTYPE_BGP_RPKI_CACHE, host); ssh_config->bindaddr = NULL; ssh_config->username = XSTRDUP(MTYPE_BGP_RPKI_CACHE, username); ssh_config->client_privkey_path = XSTRDUP(MTYPE_BGP_RPKI_CACHE, client_privkey_path); ssh_config->server_hostkey_path = XSTRDUP(MTYPE_BGP_RPKI_CACHE, server_pubkey_path); rtr_socket = create_rtr_socket(tr_socket); cache->type = SSH; cache->tr_socket = tr_socket; cache->tr_config.ssh_config = ssh_config; cache->rtr_socket = rtr_socket; cache->preference = preference; int ret = add_cache(cache); if (ret != SUCCESS) { free_cache(cache); } return ret; } #endif static void free_cache(struct cache *cache) { if (cache->type == TCP) { XFREE(MTYPE_BGP_RPKI_CACHE, cache->tr_config.tcp_config->host); XFREE(MTYPE_BGP_RPKI_CACHE, cache->tr_config.tcp_config->port); XFREE(MTYPE_BGP_RPKI_CACHE, cache->tr_config.tcp_config); } #if defined(FOUND_SSH) else { XFREE(MTYPE_BGP_RPKI_CACHE, cache->tr_config.ssh_config->host); XFREE(MTYPE_BGP_RPKI_CACHE, cache->tr_config.ssh_config->username); XFREE(MTYPE_BGP_RPKI_CACHE, cache->tr_config.ssh_config->client_privkey_path); XFREE(MTYPE_BGP_RPKI_CACHE, cache->tr_config.ssh_config->server_hostkey_path); XFREE(MTYPE_BGP_RPKI_CACHE, cache->tr_config.ssh_config); } #endif XFREE(MTYPE_BGP_RPKI_CACHE, cache->tr_socket); XFREE(MTYPE_BGP_RPKI_CACHE, cache->rtr_socket); XFREE(MTYPE_BGP_RPKI_CACHE, cache); } static int config_write(struct vty *vty) { struct listnode *cache_node; struct cache *cache; if (listcount(cache_list)) { if (rpki_debug) vty_out(vty, "debug rpki\n"); vty_out(vty, "!\n"); vty_out(vty, "rpki\n"); vty_out(vty, " rpki polling_period %d\n", polling_period); for (ALL_LIST_ELEMENTS_RO(cache_list, cache_node, cache)) { switch (cache->type) { struct tr_tcp_config *tcp_config; #if defined(FOUND_SSH) struct tr_ssh_config *ssh_config; #endif case TCP: tcp_config = cache->tr_config.tcp_config; vty_out(vty, " rpki cache %s %s ", tcp_config->host, tcp_config->port); break; #if defined(FOUND_SSH) case SSH: ssh_config = cache->tr_config.ssh_config; vty_out(vty, " rpki cache %s %u %s %s %s ", ssh_config->host, ssh_config->port, ssh_config->username, ssh_config->client_privkey_path, ssh_config->server_hostkey_path != NULL ? ssh_config ->server_hostkey_path : " "); break; #endif default: break; } vty_out(vty, "preference %hhu\n", cache->preference); } vty_out(vty, " exit\n"); return 1; } else { return 0; } } DEFUN_NOSH (rpki, rpki_cmd, "rpki", "Enable rpki and enter rpki configuration mode\n") { vty->node = RPKI_NODE; return CMD_SUCCESS; } DEFUN (bgp_rpki_start, bgp_rpki_start_cmd, "rpki start", RPKI_OUTPUT_STRING "start rpki support\n") { if (listcount(cache_list) == 0) vty_out(vty, "Could not start rpki because no caches are configured\n"); if (!is_running()) { if (start() == ERROR) { RPKI_DEBUG("RPKI failed to start"); return CMD_WARNING; } } return CMD_SUCCESS; } DEFUN (bgp_rpki_stop, bgp_rpki_stop_cmd, "rpki stop", RPKI_OUTPUT_STRING "start rpki support\n") { if (is_running()) stop(); return CMD_SUCCESS; } DEFPY (rpki_polling_period, rpki_polling_period_cmd, "rpki polling_period (1-86400)$pp", RPKI_OUTPUT_STRING "Set polling period\n" "Polling period value\n") { polling_period = pp; return CMD_SUCCESS; } DEFUN (no_rpki_polling_period, no_rpki_polling_period_cmd, "no rpki polling_period", NO_STR RPKI_OUTPUT_STRING "Set polling period back to default\n") { polling_period = POLLING_PERIOD_DEFAULT; return CMD_SUCCESS; } DEFPY (rpki_expire_interval, rpki_expire_interval_cmd, "rpki expire_interval (600-172800)$tmp", RPKI_OUTPUT_STRING "Set expire interval\n" "Expire interval value\n") { if ((unsigned int)tmp >= polling_period) { expire_interval = tmp; return CMD_SUCCESS; } vty_out(vty, "%% Expiry interval must be polling period or larger\n"); return CMD_WARNING_CONFIG_FAILED; } DEFUN (no_rpki_expire_interval, no_rpki_expire_interval_cmd, "no rpki expire_interval", NO_STR RPKI_OUTPUT_STRING "Set expire interval back to default\n") { expire_interval = polling_period * 2; return CMD_SUCCESS; } DEFPY (rpki_retry_interval, rpki_retry_interval_cmd, "rpki retry_interval (1-7200)$tmp", RPKI_OUTPUT_STRING "Set retry interval\n" "retry interval value\n") { retry_interval = tmp; return CMD_SUCCESS; } DEFUN (no_rpki_retry_interval, no_rpki_retry_interval_cmd, "no rpki retry_interval", NO_STR RPKI_OUTPUT_STRING "Set retry interval back to default\n") { retry_interval = RETRY_INTERVAL_DEFAULT; return CMD_SUCCESS; } #if (CONFDATE > 20200901) CPP_NOTICE("bgpd: time to remove rpki timeout") CPP_NOTICE("bgpd: this includes rpki_timeout and rpki_synchronisation_timeout") #endif DEFPY_HIDDEN (rpki_timeout, rpki_timeout_cmd, "rpki timeout (1-4294967295)$to_arg", RPKI_OUTPUT_STRING "Set timeout\n" "Timeout value\n") { vty_out(vty, "This config option is deprecated, and is scheduled for removal.\n"); vty_out(vty, "This functionality has also already been removed because it caused bugs and was pointless\n"); return CMD_SUCCESS; } DEFUN_HIDDEN (no_rpki_timeout, no_rpki_timeout_cmd, "no rpki timeout", NO_STR RPKI_OUTPUT_STRING "Set timeout back to default\n") { vty_out(vty, "This config option is deprecated, and is scheduled for removal.\n"); vty_out(vty, "This functionality has also already been removed because it caused bugs and was pointless\n"); return CMD_SUCCESS; } DEFPY_HIDDEN (rpki_synchronisation_timeout, rpki_synchronisation_timeout_cmd, "rpki initial-synchronisation-timeout (1-4294967295)$ito_arg", RPKI_OUTPUT_STRING "Set a timeout for the initial synchronisation of prefix validation data\n" "Timeout value\n") { vty_out(vty, "This config option is deprecated, and is scheduled for removal.\n"); vty_out(vty, "This functionality has also already been removed because it caused bugs and was pointless\n"); return CMD_SUCCESS; } DEFUN_HIDDEN (no_rpki_synchronisation_timeout, no_rpki_synchronisation_timeout_cmd, "no rpki initial-synchronisation-timeout", NO_STR RPKI_OUTPUT_STRING "Set the initial synchronisation timeout back to default (30 sec.)\n") { vty_out(vty, "This config option is deprecated, and is scheduled for removal.\n"); vty_out(vty, "This functionality has also already been removed because it caused bugs and was pointless\n"); return CMD_SUCCESS; } DEFPY (rpki_cache, rpki_cache_cmd, "rpki cache " " " "preference (1-255)", RPKI_OUTPUT_STRING "Install a cache server to current group\n" "IP address of cache server\n Hostname of cache server\n" "TCP port number\n" "SSH port number\n" "SSH user name\n" "Path to own SSH private key\n" "Path to own SSH public key\n" "Path to Public key of cache server\n" "Preference of the cache server\n" "Preference value\n") { int return_value; struct listnode *cache_node; struct cache *current_cache; for (ALL_LIST_ELEMENTS_RO(cache_list, cache_node, current_cache)) { if (current_cache->preference == preference) { vty_out(vty, "Cache with preference %ld is already configured\n", preference); return CMD_WARNING; } } // use ssh connection if (ssh_uname) { #if defined(FOUND_SSH) return_value = add_ssh_cache(cache, sshport, ssh_uname, ssh_privkey, ssh_pubkey, server_pubkey, preference); #else return_value = SUCCESS; vty_out(vty, "ssh sockets are not supported. " "Please recompile rtrlib and frr with ssh support. " "If you want to use it\n"); #endif } else { // use tcp connection return_value = add_tcp_cache(cache, tcpport, preference); } if (return_value == ERROR) { vty_out(vty, "Could not create new rpki cache\n"); return CMD_WARNING; } return CMD_SUCCESS; } DEFPY (no_rpki_cache, no_rpki_cache_cmd, "no rpki cache preference (1-255)$preference", NO_STR RPKI_OUTPUT_STRING "Remove a cache server\n" "IP address of cache server\n Hostname of cache server\n" "TCP port number\n" "SSH port number\n" "Preference of the cache server\n" "Preference value\n") { struct cache *cache_p = find_cache(preference); if (!cache_p) { vty_out(vty, "Could not find cache %ld\n", preference); return CMD_WARNING; } if (rtr_is_running && listcount(cache_list) == 1) { stop(); } else if (rtr_is_running) { if (rtr_mgr_remove_group(rtr_config, preference) == RTR_ERROR) { vty_out(vty, "Could not remove cache %ld", preference); vty_out(vty, "\n"); return CMD_WARNING; } } listnode_delete(cache_list, cache_p); free_cache(cache_p); return CMD_SUCCESS; } DEFUN (show_rpki_prefix_table, show_rpki_prefix_table_cmd, "show rpki prefix-table", SHOW_STR RPKI_OUTPUT_STRING "Show validated prefixes which were received from RPKI Cache\n") { struct listnode *cache_node; struct cache *cache; for (ALL_LIST_ELEMENTS_RO(cache_list, cache_node, cache)) { vty_out(vty, "host: %s port: %s\n", cache->tr_config.tcp_config->host, cache->tr_config.tcp_config->port); } if (is_synchronized()) print_prefix_table(vty); else vty_out(vty, "No connection to RPKI cache server.\n"); return CMD_SUCCESS; } DEFPY (show_rpki_prefix, show_rpki_prefix_cmd, "show rpki prefix [(1-4294967295)$asn]", SHOW_STR RPKI_OUTPUT_STRING "Lookup IP prefix and optionally ASN in prefix table\n" "IPv4 prefix\n" "IPv6 prefix\n" "AS Number\n") { if (!is_synchronized()) { vty_out(vty, "No Connection to RPKI cache server.\n"); return CMD_WARNING; } struct lrtr_ip_addr addr; char addr_str[INET6_ADDRSTRLEN]; size_t addr_len = strchr(prefix_str, '/') - prefix_str; memset(addr_str, 0, sizeof(addr_str)); memcpy(addr_str, prefix_str, addr_len); if (lrtr_ip_str_to_addr(addr_str, &addr) != 0) { vty_out(vty, "Invalid IP prefix\n"); return CMD_WARNING; } struct pfx_record *matches = NULL; unsigned int match_count = 0; enum pfxv_state result; if (pfx_table_validate_r(rtr_config->pfx_table, &matches, &match_count, asn, &addr, prefix->prefixlen, &result) != PFX_SUCCESS) { vty_out(vty, "Prefix lookup failed"); return CMD_WARNING; } vty_out(vty, "%-40s %s %s\n", "Prefix", "Prefix Length", "Origin-AS"); for (size_t i = 0; i < match_count; ++i) { const struct pfx_record *record = &matches[i]; if (record->max_len >= prefix->prefixlen && ((asn != 0 && asn == record->asn) || asn == 0)) { print_record(&matches[i], vty); } } return CMD_SUCCESS; } DEFUN (show_rpki_cache_server, show_rpki_cache_server_cmd, "show rpki cache-server", SHOW_STR RPKI_OUTPUT_STRING "SHOW configured cache server\n") { struct listnode *cache_node; struct cache *cache; for (ALL_LIST_ELEMENTS_RO(cache_list, cache_node, cache)) { if (cache->type == TCP) { vty_out(vty, "host: %s port: %s\n", cache->tr_config.tcp_config->host, cache->tr_config.tcp_config->port); #if defined(FOUND_SSH) } else if (cache->type == SSH) { vty_out(vty, "host: %s port: %d username: %s " "server_hostkey_path: %s client_privkey_path: %s\n", cache->tr_config.ssh_config->host, cache->tr_config.ssh_config->port, cache->tr_config.ssh_config->username, cache->tr_config.ssh_config ->server_hostkey_path, cache->tr_config.ssh_config ->client_privkey_path); #endif } } return CMD_SUCCESS; } DEFUN (show_rpki_cache_connection, show_rpki_cache_connection_cmd, "show rpki cache-connection", SHOW_STR RPKI_OUTPUT_STRING "Show to which RPKI Cache Servers we have a connection\n") { if (is_synchronized()) { struct listnode *cache_node; struct cache *cache; struct rtr_mgr_group *group = get_connected_group(); if (!group) { vty_out(vty, "Cannot find a connected group.\n"); return CMD_SUCCESS; } vty_out(vty, "Connected to group %d\n", group->preference); for (ALL_LIST_ELEMENTS_RO(cache_list, cache_node, cache)) { if (cache->preference == group->preference) { struct tr_tcp_config *tcp_config; #if defined(FOUND_SSH) struct tr_ssh_config *ssh_config; #endif switch (cache->type) { case TCP: tcp_config = cache->tr_config.tcp_config; vty_out(vty, "rpki tcp cache %s %s pref %hhu\n", tcp_config->host, tcp_config->port, cache->preference); break; #if defined(FOUND_SSH) case SSH: ssh_config = cache->tr_config.ssh_config; vty_out(vty, "rpki ssh cache %s %u pref %hhu\n", ssh_config->host, ssh_config->port, cache->preference); break; #endif default: break; } } } } else { vty_out(vty, "No connection to RPKI cache server.\n"); } return CMD_SUCCESS; } DEFUN_NOSH (rpki_exit, rpki_exit_cmd, "exit", "Exit rpki configuration and restart rpki session\n") { reset(false); vty->node = CONFIG_NODE; return CMD_SUCCESS; } DEFUN_NOSH (rpki_quit, rpki_quit_cmd, "quit", "Exit rpki configuration mode\n") { return rpki_exit(self, vty, argc, argv); } DEFUN_NOSH (rpki_end, rpki_end_cmd, "end", "End rpki configuration, restart rpki session and change to enable mode.\n") { int ret = reset(false); vty_config_exit(vty); vty->node = ENABLE_NODE; return ret == SUCCESS ? CMD_SUCCESS : CMD_WARNING; } DEFUN (rpki_reset, rpki_reset_cmd, "rpki reset", RPKI_OUTPUT_STRING "reset rpki\n") { return reset(true) == SUCCESS ? CMD_SUCCESS : CMD_WARNING; } DEFUN (debug_rpki, debug_rpki_cmd, "debug rpki", DEBUG_STR "Enable debugging for rpki\n") { rpki_debug = 1; return CMD_SUCCESS; } DEFUN (no_debug_rpki, no_debug_rpki_cmd, "no debug rpki", NO_STR DEBUG_STR "Disable debugging for rpki\n") { rpki_debug = 0; return CMD_SUCCESS; } DEFUN (match_rpki, match_rpki_cmd, "match rpki ", MATCH_STR RPKI_OUTPUT_STRING "Valid prefix\n" "Invalid prefix\n" "Prefix not found\n") { VTY_DECLVAR_CONTEXT(route_map_index, index); enum rmap_compile_rets ret; ret = route_map_add_match(index, "rpki", argv[2]->arg, RMAP_EVENT_MATCH_ADDED); if (ret) { switch (ret) { case RMAP_RULE_MISSING: vty_out(vty, "%% BGP Can't find rule.\n"); return CMD_WARNING_CONFIG_FAILED; case RMAP_COMPILE_ERROR: vty_out(vty, "%% BGP Argument is malformed.\n"); return CMD_WARNING_CONFIG_FAILED; case RMAP_COMPILE_SUCCESS: case RMAP_DUPLICATE_RULE: /* * Intentionally doing nothing here */ break; } } return CMD_SUCCESS; } DEFUN (no_match_rpki, no_match_rpki_cmd, "no match rpki ", NO_STR MATCH_STR RPKI_OUTPUT_STRING "Valid prefix\n" "Invalid prefix\n" "Prefix not found\n") { VTY_DECLVAR_CONTEXT(route_map_index, index); enum rmap_compile_rets ret; ret = route_map_delete_match(index, "rpki", argv[3]->arg); if (ret) { switch (ret) { case RMAP_RULE_MISSING: vty_out(vty, "%% BGP Can't find rule.\n"); break; case RMAP_COMPILE_ERROR: vty_out(vty, "%% BGP Argument is malformed.\n"); break; case RMAP_COMPILE_SUCCESS: case RMAP_DUPLICATE_RULE: /* * Nothing to do here */ break; } return CMD_WARNING_CONFIG_FAILED; } return CMD_SUCCESS; } static void overwrite_exit_commands(void) { unsigned int i; vector cmd_vector = rpki_node.cmd_vector; for (i = 0; i < cmd_vector->active; ++i) { struct cmd_element *cmd = vector_lookup(cmd_vector, i); if (strcmp(cmd->string, "exit") == 0 || strcmp(cmd->string, "quit") == 0 || strcmp(cmd->string, "end") == 0) { uninstall_element(RPKI_NODE, cmd); } } install_element(RPKI_NODE, &rpki_exit_cmd); install_element(RPKI_NODE, &rpki_quit_cmd); install_element(RPKI_NODE, &rpki_end_cmd); } static void install_cli_commands(void) { // TODO: make config write work install_node(&rpki_node, &config_write); install_default(RPKI_NODE); overwrite_exit_commands(); install_element(CONFIG_NODE, &rpki_cmd); install_element(ENABLE_NODE, &rpki_cmd); install_element(ENABLE_NODE, &bgp_rpki_start_cmd); install_element(ENABLE_NODE, &bgp_rpki_stop_cmd); /* Install rpki reset command */ install_element(RPKI_NODE, &rpki_reset_cmd); /* Install rpki polling period commands */ install_element(RPKI_NODE, &rpki_polling_period_cmd); install_element(RPKI_NODE, &no_rpki_polling_period_cmd); /* Install rpki expire interval commands */ install_element(RPKI_NODE, &rpki_expire_interval_cmd); install_element(RPKI_NODE, &no_rpki_expire_interval_cmd); /* Install rpki retry interval commands */ install_element(RPKI_NODE, &rpki_retry_interval_cmd); install_element(RPKI_NODE, &no_rpki_retry_interval_cmd); /* Install rpki timeout commands */ install_element(RPKI_NODE, &rpki_timeout_cmd); install_element(RPKI_NODE, &no_rpki_timeout_cmd); /* Install rpki synchronisation timeout commands */ install_element(RPKI_NODE, &rpki_synchronisation_timeout_cmd); install_element(RPKI_NODE, &no_rpki_synchronisation_timeout_cmd); /* Install rpki cache commands */ install_element(RPKI_NODE, &rpki_cache_cmd); install_element(RPKI_NODE, &no_rpki_cache_cmd); /* Install show commands */ install_element(VIEW_NODE, &show_rpki_prefix_table_cmd); install_element(VIEW_NODE, &show_rpki_cache_connection_cmd); install_element(VIEW_NODE, &show_rpki_cache_server_cmd); install_element(VIEW_NODE, &show_rpki_prefix_cmd); /* Install debug commands */ install_element(CONFIG_NODE, &debug_rpki_cmd); install_element(ENABLE_NODE, &debug_rpki_cmd); install_element(CONFIG_NODE, &no_debug_rpki_cmd); install_element(ENABLE_NODE, &no_debug_rpki_cmd); /* Install route match */ route_map_install_match(&route_match_rpki_cmd); install_element(RMAP_NODE, &match_rpki_cmd); install_element(RMAP_NODE, &no_match_rpki_cmd); } FRR_MODULE_SETUP(.name = "bgpd_rpki", .version = "0.3.6", .description = "Enable RPKI support for FRR.", .init = bgp_rpki_module_init) frr-7.2.1/bgpd/bgp_snmp.c0000644000000000000000000005676613610377563012125 00000000000000/* BGP4 SNMP support * Copyright (C) 1999, 2000 Kunihiro Ishiguro * * This file is part of GNU Zebra. * * GNU Zebra is free software; you can redistribute it and/or modify it * under the terms of the GNU General Public License as published by the * Free Software Foundation; either version 2, or (at your option) any * later version. * * GNU Zebra 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; see the file COPYING; if not, write to the Free Software * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA */ #include #include #include #include "if.h" #include "log.h" #include "prefix.h" #include "command.h" #include "thread.h" #include "smux.h" #include "filter.h" #include "hook.h" #include "libfrr.h" #include "version.h" #include "bgpd/bgpd.h" #include "bgpd/bgp_table.h" #include "bgpd/bgp_aspath.h" #include "bgpd/bgp_attr.h" #include "bgpd/bgp_route.h" #include "bgpd/bgp_fsm.h" /* BGP4-MIB described in RFC1657. */ #define BGP4MIB 1,3,6,1,2,1,15 /* BGP TRAP. */ #define BGPESTABLISHED 1 #define BGPBACKWARDTRANSITION 2 /* BGP MIB bgpVersion. */ #define BGPVERSION 0 /* BGP MIB bgpLocalAs. */ #define BGPLOCALAS 0 /* BGP MIB bgpPeerTable. */ #define BGPPEERIDENTIFIER 1 #define BGPPEERSTATE 2 #define BGPPEERADMINSTATUS 3 #define BGPPEERNEGOTIATEDVERSION 4 #define BGPPEERLOCALADDR 5 #define BGPPEERLOCALPORT 6 #define BGPPEERREMOTEADDR 7 #define BGPPEERREMOTEPORT 8 #define BGPPEERREMOTEAS 9 #define BGPPEERINUPDATES 10 #define BGPPEEROUTUPDATES 11 #define BGPPEERINTOTALMESSAGES 12 #define BGPPEEROUTTOTALMESSAGES 13 #define BGPPEERLASTERROR 14 #define BGPPEERFSMESTABLISHEDTRANSITIONS 15 #define BGPPEERFSMESTABLISHEDTIME 16 #define BGPPEERCONNECTRETRYINTERVAL 17 #define BGPPEERHOLDTIME 18 #define BGPPEERKEEPALIVE 19 #define BGPPEERHOLDTIMECONFIGURED 20 #define BGPPEERKEEPALIVECONFIGURED 21 #define BGPPEERMINROUTEADVERTISEMENTINTERVAL 22 #define BGPPEERINUPDATEELAPSEDTIME 23 /* BGP MIB bgpIdentifier. */ #define BGPIDENTIFIER 0 /* BGP MIB bgpRcvdPathAttrTable */ #define BGPPATHATTRPEER 1 #define BGPPATHATTRDESTNETWORK 2 #define BGPPATHATTRORIGIN 3 #define BGPPATHATTRASPATH 4 #define BGPPATHATTRNEXTHOP 5 #define BGPPATHATTRINTERASMETRIC 6 /* BGP MIB bgp4PathAttrTable. */ #define BGP4PATHATTRPEER 1 #define BGP4PATHATTRIPADDRPREFIXLEN 2 #define BGP4PATHATTRIPADDRPREFIX 3 #define BGP4PATHATTRORIGIN 4 #define BGP4PATHATTRASPATHSEGMENT 5 #define BGP4PATHATTRNEXTHOP 6 #define BGP4PATHATTRMULTIEXITDISC 7 #define BGP4PATHATTRLOCALPREF 8 #define BGP4PATHATTRATOMICAGGREGATE 9 #define BGP4PATHATTRAGGREGATORAS 10 #define BGP4PATHATTRAGGREGATORADDR 11 #define BGP4PATHATTRCALCLOCALPREF 12 #define BGP4PATHATTRBEST 13 #define BGP4PATHATTRUNKNOWN 14 /* SNMP value hack. */ #define INTEGER ASN_INTEGER #define INTEGER32 ASN_INTEGER #define COUNTER32 ASN_COUNTER #define OCTET_STRING ASN_OCTET_STR #define IPADDRESS ASN_IPADDRESS #define GAUGE32 ASN_UNSIGNED /* Declare static local variables for convenience. */ SNMP_LOCAL_VARIABLES /* BGP-MIB instances. */ static oid bgp_oid[] = {BGP4MIB}; static oid bgp_trap_oid[] = {BGP4MIB, 0}; /* IP address 0.0.0.0. */ static struct in_addr bgp_empty_addr = {.s_addr = 0}; /* Hook functions. */ static uint8_t *bgpVersion(struct variable *, oid[], size_t *, int, size_t *, WriteMethod **); static uint8_t *bgpLocalAs(struct variable *, oid[], size_t *, int, size_t *, WriteMethod **); static uint8_t *bgpPeerTable(struct variable *, oid[], size_t *, int, size_t *, WriteMethod **); static uint8_t *bgpRcvdPathAttrTable(struct variable *, oid[], size_t *, int, size_t *, WriteMethod **); static uint8_t *bgpIdentifier(struct variable *, oid[], size_t *, int, size_t *, WriteMethod **); static uint8_t *bgp4PathAttrTable(struct variable *, oid[], size_t *, int, size_t *, WriteMethod **); /* static uint8_t *bgpTraps (); */ static struct variable bgp_variables[] = { /* BGP version. */ {BGPVERSION, OCTET_STRING, RONLY, bgpVersion, 1, {1}}, /* BGP local AS. */ {BGPLOCALAS, INTEGER, RONLY, bgpLocalAs, 1, {2}}, /* BGP peer table. */ {BGPPEERIDENTIFIER, IPADDRESS, RONLY, bgpPeerTable, 3, {3, 1, 1}}, {BGPPEERSTATE, INTEGER, RONLY, bgpPeerTable, 3, {3, 1, 2}}, {BGPPEERADMINSTATUS, INTEGER, RWRITE, bgpPeerTable, 3, {3, 1, 3}}, {BGPPEERNEGOTIATEDVERSION, INTEGER32, RONLY, bgpPeerTable, 3, {3, 1, 4}}, {BGPPEERLOCALADDR, IPADDRESS, RONLY, bgpPeerTable, 3, {3, 1, 5}}, {BGPPEERLOCALPORT, INTEGER, RONLY, bgpPeerTable, 3, {3, 1, 6}}, {BGPPEERREMOTEADDR, IPADDRESS, RONLY, bgpPeerTable, 3, {3, 1, 7}}, {BGPPEERREMOTEPORT, INTEGER, RONLY, bgpPeerTable, 3, {3, 1, 8}}, {BGPPEERREMOTEAS, INTEGER, RONLY, bgpPeerTable, 3, {3, 1, 9}}, {BGPPEERINUPDATES, COUNTER32, RONLY, bgpPeerTable, 3, {3, 1, 10}}, {BGPPEEROUTUPDATES, COUNTER32, RONLY, bgpPeerTable, 3, {3, 1, 11}}, {BGPPEERINTOTALMESSAGES, COUNTER32, RONLY, bgpPeerTable, 3, {3, 1, 12}}, {BGPPEEROUTTOTALMESSAGES, COUNTER32, RONLY, bgpPeerTable, 3, {3, 1, 13}}, {BGPPEERLASTERROR, OCTET_STRING, RONLY, bgpPeerTable, 3, {3, 1, 14}}, {BGPPEERFSMESTABLISHEDTRANSITIONS, COUNTER32, RONLY, bgpPeerTable, 3, {3, 1, 15}}, {BGPPEERFSMESTABLISHEDTIME, GAUGE32, RONLY, bgpPeerTable, 3, {3, 1, 16}}, {BGPPEERCONNECTRETRYINTERVAL, INTEGER, RWRITE, bgpPeerTable, 3, {3, 1, 17}}, {BGPPEERHOLDTIME, INTEGER, RONLY, bgpPeerTable, 3, {3, 1, 18}}, {BGPPEERKEEPALIVE, INTEGER, RONLY, bgpPeerTable, 3, {3, 1, 19}}, {BGPPEERHOLDTIMECONFIGURED, INTEGER, RWRITE, bgpPeerTable, 3, {3, 1, 20}}, {BGPPEERKEEPALIVECONFIGURED, INTEGER, RWRITE, bgpPeerTable, 3, {3, 1, 21}}, {BGPPEERMINROUTEADVERTISEMENTINTERVAL, INTEGER, RWRITE, bgpPeerTable, 3, {3, 1, 23}}, {BGPPEERINUPDATEELAPSEDTIME, GAUGE32, RONLY, bgpPeerTable, 3, {3, 1, 24}}, /* BGP identifier. */ {BGPIDENTIFIER, IPADDRESS, RONLY, bgpIdentifier, 1, {4}}, /* BGP received path attribute table. */ {BGPPATHATTRPEER, IPADDRESS, RONLY, bgpRcvdPathAttrTable, 3, {5, 1, 1}}, {BGPPATHATTRDESTNETWORK, IPADDRESS, RONLY, bgpRcvdPathAttrTable, 3, {5, 1, 2}}, {BGPPATHATTRORIGIN, INTEGER, RONLY, bgpRcvdPathAttrTable, 3, {5, 1, 3}}, {BGPPATHATTRASPATH, OCTET_STRING, RONLY, bgpRcvdPathAttrTable, 3, {5, 1, 4}}, {BGPPATHATTRNEXTHOP, IPADDRESS, RONLY, bgpRcvdPathAttrTable, 3, {5, 1, 5}}, {BGPPATHATTRINTERASMETRIC, INTEGER32, RONLY, bgpRcvdPathAttrTable, 3, {5, 1, 6}}, /* BGP-4 received path attribute table. */ {BGP4PATHATTRPEER, IPADDRESS, RONLY, bgp4PathAttrTable, 3, {6, 1, 1}}, {BGP4PATHATTRIPADDRPREFIXLEN, INTEGER, RONLY, bgp4PathAttrTable, 3, {6, 1, 2}}, {BGP4PATHATTRIPADDRPREFIX, IPADDRESS, RONLY, bgp4PathAttrTable, 3, {6, 1, 3}}, {BGP4PATHATTRORIGIN, INTEGER, RONLY, bgp4PathAttrTable, 3, {6, 1, 4}}, {BGP4PATHATTRASPATHSEGMENT, OCTET_STRING, RONLY, bgp4PathAttrTable, 3, {6, 1, 5}}, {BGP4PATHATTRNEXTHOP, IPADDRESS, RONLY, bgp4PathAttrTable, 3, {6, 1, 6}}, {BGP4PATHATTRMULTIEXITDISC, INTEGER, RONLY, bgp4PathAttrTable, 3, {6, 1, 7}}, {BGP4PATHATTRLOCALPREF, INTEGER, RONLY, bgp4PathAttrTable, 3, {6, 1, 8}}, {BGP4PATHATTRATOMICAGGREGATE, INTEGER, RONLY, bgp4PathAttrTable, 3, {6, 1, 9}}, {BGP4PATHATTRAGGREGATORAS, INTEGER, RONLY, bgp4PathAttrTable, 3, {6, 1, 10}}, {BGP4PATHATTRAGGREGATORADDR, IPADDRESS, RONLY, bgp4PathAttrTable, 3, {6, 1, 11}}, {BGP4PATHATTRCALCLOCALPREF, INTEGER, RONLY, bgp4PathAttrTable, 3, {6, 1, 12}}, {BGP4PATHATTRBEST, INTEGER, RONLY, bgp4PathAttrTable, 3, {6, 1, 13}}, {BGP4PATHATTRUNKNOWN, OCTET_STRING, RONLY, bgp4PathAttrTable, 3, {6, 1, 14}}, }; static uint8_t *bgpVersion(struct variable *v, oid name[], size_t *length, int exact, size_t *var_len, WriteMethod **write_method) { static uint8_t version; if (smux_header_generic(v, name, length, exact, var_len, write_method) == MATCH_FAILED) return NULL; /* Retrun BGP version. Zebra bgpd only support version 4. */ version = (0x80 >> (BGP_VERSION_4 - 1)); /* Return octet string length 1. */ *var_len = 1; return (uint8_t *)&version; } static uint8_t *bgpLocalAs(struct variable *v, oid name[], size_t *length, int exact, size_t *var_len, WriteMethod **write_method) { struct bgp *bgp; if (smux_header_generic(v, name, length, exact, var_len, write_method) == MATCH_FAILED) return NULL; /* Get BGP structure. */ bgp = bgp_get_default(); if (!bgp) return NULL; return SNMP_INTEGER(bgp->as); } static struct peer *peer_lookup_addr_ipv4(struct in_addr *src) { struct bgp *bgp; struct peer *peer; struct listnode *node; bgp = bgp_get_default(); if (!bgp) return NULL; for (ALL_LIST_ELEMENTS_RO(bgp->peer, node, peer)) { if (sockunion_family(&peer->su) != AF_INET) continue; if (sockunion2ip(&peer->su) == src->s_addr) return peer; } return NULL; } static struct peer *bgp_peer_lookup_next(struct in_addr *src) { struct bgp *bgp; struct peer *peer; struct peer *next_peer = NULL; struct listnode *node; bgp = bgp_get_default(); if (!bgp) return NULL; for (ALL_LIST_ELEMENTS_RO(bgp->peer, node, peer)) { if (sockunion_family(&peer->su) != AF_INET) continue; if (ntohl(sockunion2ip(&peer->su)) <= ntohl(src->s_addr)) continue; if (!next_peer || ntohl(sockunion2ip(&next_peer->su)) > ntohl(sockunion2ip(&peer->su))) { next_peer = peer; } } if (next_peer) { src->s_addr = sockunion2ip(&next_peer->su); return next_peer; } return NULL; } /* 1.3.6.1.2.1.15.3.1.x = 10 */ #define PEERTAB_NAMELEN 10 static struct peer *bgpPeerTable_lookup(struct variable *v, oid name[], size_t *length, struct in_addr *addr, int exact) { struct peer *peer = NULL; size_t namelen = v ? v->namelen : PEERTAB_NAMELEN; int len; if (exact) { /* Check the length. */ if (*length - namelen != sizeof(struct in_addr)) return NULL; oid2in_addr(name + namelen, IN_ADDR_SIZE, addr); peer = peer_lookup_addr_ipv4(addr); return peer; } else { len = *length - namelen; if (len > 4) len = 4; oid2in_addr(name + namelen, len, addr); peer = bgp_peer_lookup_next(addr); if (peer == NULL) return NULL; oid_copy_addr(name + namelen, addr, sizeof(struct in_addr)); *length = sizeof(struct in_addr) + namelen; return peer; } return NULL; } /* BGP write methods. */ static int write_bgpPeerTable(int action, uint8_t *var_val, uint8_t var_val_type, size_t var_val_len, uint8_t *statP, oid *name, size_t length) { struct in_addr addr; struct peer *peer; long intval; if (var_val_type != ASN_INTEGER) { return SNMP_ERR_WRONGTYPE; } if (var_val_len != sizeof(long)) { return SNMP_ERR_WRONGLENGTH; } intval = *(long *)var_val; memset(&addr, 0, sizeof(struct in_addr)); peer = bgpPeerTable_lookup(NULL, name, &length, &addr, 1); if (!peer) return SNMP_ERR_NOSUCHNAME; if (action != SNMP_MSG_INTERNAL_SET_COMMIT) return SNMP_ERR_NOERROR; zlog_info("%s: SNMP write .%ld = %ld", peer->host, (long)name[PEERTAB_NAMELEN - 1], intval); switch (name[PEERTAB_NAMELEN - 1]) { case BGPPEERADMINSTATUS: #define BGP_PeerAdmin_stop 1 #define BGP_PeerAdmin_start 2 /* When the peer is established, */ if (intval == BGP_PeerAdmin_stop) BGP_EVENT_ADD(peer, BGP_Stop); else if (intval == BGP_PeerAdmin_start) ; /* Do nothing. */ else return SNMP_ERR_NOSUCHNAME; break; case BGPPEERCONNECTRETRYINTERVAL: peer_flag_set(peer, PEER_FLAG_TIMER_CONNECT); peer->connect = intval; peer->v_connect = intval; break; case BGPPEERHOLDTIMECONFIGURED: peer_flag_set(peer, PEER_FLAG_TIMER); peer->holdtime = intval; peer->v_holdtime = intval; break; case BGPPEERKEEPALIVECONFIGURED: peer_flag_set(peer, PEER_FLAG_TIMER); peer->keepalive = intval; peer->v_keepalive = intval; break; case BGPPEERMINROUTEADVERTISEMENTINTERVAL: peer->v_routeadv = intval; break; } return SNMP_ERR_NOERROR; } static uint8_t *bgpPeerTable(struct variable *v, oid name[], size_t *length, int exact, size_t *var_len, WriteMethod **write_method) { static struct in_addr addr; struct peer *peer; uint32_t ui, uo; if (smux_header_table(v, name, length, exact, var_len, write_method) == MATCH_FAILED) return NULL; memset(&addr, 0, sizeof(struct in_addr)); peer = bgpPeerTable_lookup(v, name, length, &addr, exact); if (!peer) return NULL; switch (v->magic) { case BGPPEERIDENTIFIER: return SNMP_IPADDRESS(peer->remote_id); break; case BGPPEERSTATE: return SNMP_INTEGER(peer->status); break; case BGPPEERADMINSTATUS: *write_method = write_bgpPeerTable; #define BGP_PeerAdmin_stop 1 #define BGP_PeerAdmin_start 2 if (CHECK_FLAG(peer->flags, PEER_FLAG_SHUTDOWN)) return SNMP_INTEGER(BGP_PeerAdmin_stop); else return SNMP_INTEGER(BGP_PeerAdmin_start); break; case BGPPEERNEGOTIATEDVERSION: return SNMP_INTEGER(BGP_VERSION_4); break; case BGPPEERLOCALADDR: if (peer->su_local) return SNMP_IPADDRESS(peer->su_local->sin.sin_addr); else return SNMP_IPADDRESS(bgp_empty_addr); break; case BGPPEERLOCALPORT: if (peer->su_local) return SNMP_INTEGER( ntohs(peer->su_local->sin.sin_port)); else return SNMP_INTEGER(0); break; case BGPPEERREMOTEADDR: if (peer->su_remote) return SNMP_IPADDRESS(peer->su_remote->sin.sin_addr); else return SNMP_IPADDRESS(bgp_empty_addr); break; case BGPPEERREMOTEPORT: if (peer->su_remote) return SNMP_INTEGER( ntohs(peer->su_remote->sin.sin_port)); else return SNMP_INTEGER(0); break; case BGPPEERREMOTEAS: return SNMP_INTEGER(peer->as); break; case BGPPEERINUPDATES: ui = atomic_load_explicit(&peer->update_in, memory_order_relaxed); return SNMP_INTEGER(ui); break; case BGPPEEROUTUPDATES: uo = atomic_load_explicit(&peer->update_out, memory_order_relaxed); return SNMP_INTEGER(uo); break; case BGPPEERINTOTALMESSAGES: return SNMP_INTEGER(PEER_TOTAL_RX(peer)); break; case BGPPEEROUTTOTALMESSAGES: return SNMP_INTEGER(PEER_TOTAL_TX(peer)); break; case BGPPEERLASTERROR: { static uint8_t lasterror[2]; lasterror[0] = peer->notify.code; lasterror[1] = peer->notify.subcode; *var_len = 2; return (uint8_t *)&lasterror; } break; case BGPPEERFSMESTABLISHEDTRANSITIONS: return SNMP_INTEGER(peer->established); break; case BGPPEERFSMESTABLISHEDTIME: if (peer->uptime == 0) return SNMP_INTEGER(0); else return SNMP_INTEGER(bgp_clock() - peer->uptime); break; case BGPPEERCONNECTRETRYINTERVAL: *write_method = write_bgpPeerTable; return SNMP_INTEGER(peer->v_connect); break; case BGPPEERHOLDTIME: return SNMP_INTEGER(peer->v_holdtime); break; case BGPPEERKEEPALIVE: return SNMP_INTEGER(peer->v_keepalive); break; case BGPPEERHOLDTIMECONFIGURED: *write_method = write_bgpPeerTable; if (CHECK_FLAG(peer->flags, PEER_FLAG_TIMER)) return SNMP_INTEGER(peer->holdtime); else return SNMP_INTEGER(peer->v_holdtime); break; case BGPPEERKEEPALIVECONFIGURED: *write_method = write_bgpPeerTable; if (CHECK_FLAG(peer->flags, PEER_FLAG_TIMER)) return SNMP_INTEGER(peer->keepalive); else return SNMP_INTEGER(peer->v_keepalive); break; case BGPPEERMINROUTEADVERTISEMENTINTERVAL: *write_method = write_bgpPeerTable; return SNMP_INTEGER(peer->v_routeadv); break; case BGPPEERINUPDATEELAPSEDTIME: if (peer->update_time == 0) return SNMP_INTEGER(0); else return SNMP_INTEGER(bgp_clock() - peer->update_time); break; default: return NULL; break; } return NULL; } static uint8_t *bgpIdentifier(struct variable *v, oid name[], size_t *length, int exact, size_t *var_len, WriteMethod **write_method) { struct bgp *bgp; if (smux_header_generic(v, name, length, exact, var_len, write_method) == MATCH_FAILED) return NULL; bgp = bgp_get_default(); if (!bgp) return NULL; return SNMP_IPADDRESS(bgp->router_id); } static uint8_t *bgpRcvdPathAttrTable(struct variable *v, oid name[], size_t *length, int exact, size_t *var_len, WriteMethod **write_method) { /* Received Path Attribute Table. This table contains, one entry per path to a network, path attributes received from all peers running BGP version 3 or less. This table is obsolete, having been replaced in functionality with the bgp4PathAttrTable. */ return NULL; } static struct bgp_path_info *bgp4PathAttrLookup(struct variable *v, oid name[], size_t *length, struct bgp *bgp, struct prefix_ipv4 *addr, int exact) { oid *offset; int offsetlen; struct bgp_path_info *path; struct bgp_path_info *min; struct bgp_node *rn; union sockunion su; unsigned int len; struct in_addr paddr; sockunion_init(&su); #define BGP_PATHATTR_ENTRY_OFFSET (IN_ADDR_SIZE + 1 + IN_ADDR_SIZE) if (exact) { if (*length - v->namelen != BGP_PATHATTR_ENTRY_OFFSET) return NULL; /* Set OID offset for prefix. */ offset = name + v->namelen; oid2in_addr(offset, IN_ADDR_SIZE, &addr->prefix); offset += IN_ADDR_SIZE; /* Prefix length. */ addr->prefixlen = *offset; offset++; /* Peer address. */ su.sin.sin_family = AF_INET; oid2in_addr(offset, IN_ADDR_SIZE, &su.sin.sin_addr); /* Lookup node. */ rn = bgp_node_lookup(bgp->rib[AFI_IP][SAFI_UNICAST], (struct prefix *)addr); if (rn) { bgp_unlock_node(rn); for (path = bgp_node_get_bgp_path_info(rn); path; path = path->next) if (sockunion_same(&path->peer->su, &su)) return path; } } else { offset = name + v->namelen; offsetlen = *length - v->namelen; len = offsetlen; if (offsetlen == 0) rn = bgp_table_top(bgp->rib[AFI_IP][SAFI_UNICAST]); else { if (len > IN_ADDR_SIZE) len = IN_ADDR_SIZE; oid2in_addr(offset, len, &addr->prefix); offset += IN_ADDR_SIZE; offsetlen -= IN_ADDR_SIZE; if (offsetlen > 0) addr->prefixlen = *offset; else addr->prefixlen = len * 8; rn = bgp_node_get(bgp->rib[AFI_IP][SAFI_UNICAST], (struct prefix *)addr); offset++; offsetlen--; } if (offsetlen > 0) { len = offsetlen; if (len > IN_ADDR_SIZE) len = IN_ADDR_SIZE; oid2in_addr(offset, len, &paddr); } else paddr.s_addr = 0; if (!rn) return NULL; do { min = NULL; for (path = bgp_node_get_bgp_path_info(rn); path; path = path->next) { if (path->peer->su.sin.sin_family == AF_INET && ntohl(paddr.s_addr) < ntohl(path->peer->su.sin .sin_addr .s_addr)) { if (min) { if (ntohl(path->peer->su.sin .sin_addr .s_addr) < ntohl(min->peer->su.sin .sin_addr .s_addr)) min = path; } else min = path; } } if (min) { *length = v->namelen + BGP_PATHATTR_ENTRY_OFFSET; offset = name + v->namelen; oid_copy_addr(offset, &rn->p.u.prefix4, IN_ADDR_SIZE); offset += IN_ADDR_SIZE; *offset = rn->p.prefixlen; offset++; oid_copy_addr(offset, &min->peer->su.sin.sin_addr, IN_ADDR_SIZE); addr->prefix = rn->p.u.prefix4; addr->prefixlen = rn->p.prefixlen; bgp_unlock_node(rn); return min; } paddr.s_addr = 0; } while ((rn = bgp_route_next(rn)) != NULL); } return NULL; } static uint8_t *bgp4PathAttrTable(struct variable *v, oid name[], size_t *length, int exact, size_t *var_len, WriteMethod **write_method) { struct bgp *bgp; struct bgp_path_info *path; struct prefix_ipv4 addr; bgp = bgp_get_default(); if (!bgp) return NULL; if (smux_header_table(v, name, length, exact, var_len, write_method) == MATCH_FAILED) return NULL; memset(&addr, 0, sizeof(struct prefix_ipv4)); path = bgp4PathAttrLookup(v, name, length, bgp, &addr, exact); if (!path) return NULL; switch (v->magic) { case BGP4PATHATTRPEER: /* 1 */ return SNMP_IPADDRESS(path->peer->su.sin.sin_addr); break; case BGP4PATHATTRIPADDRPREFIXLEN: /* 2 */ return SNMP_INTEGER(addr.prefixlen); break; case BGP4PATHATTRIPADDRPREFIX: /* 3 */ return SNMP_IPADDRESS(addr.prefix); break; case BGP4PATHATTRORIGIN: /* 4 */ return SNMP_INTEGER(path->attr->origin); break; case BGP4PATHATTRASPATHSEGMENT: /* 5 */ return aspath_snmp_pathseg(path->attr->aspath, var_len); break; case BGP4PATHATTRNEXTHOP: /* 6 */ return SNMP_IPADDRESS(path->attr->nexthop); break; case BGP4PATHATTRMULTIEXITDISC: /* 7 */ return SNMP_INTEGER(path->attr->med); break; case BGP4PATHATTRLOCALPREF: /* 8 */ return SNMP_INTEGER(path->attr->local_pref); break; case BGP4PATHATTRATOMICAGGREGATE: /* 9 */ return SNMP_INTEGER(1); break; case BGP4PATHATTRAGGREGATORAS: /* 10 */ return SNMP_INTEGER(path->attr->aggregator_as); break; case BGP4PATHATTRAGGREGATORADDR: /* 11 */ return SNMP_IPADDRESS(path->attr->aggregator_addr); break; case BGP4PATHATTRCALCLOCALPREF: /* 12 */ return SNMP_INTEGER(-1); break; case BGP4PATHATTRBEST: /* 13 */ #define BGP4_PathAttrBest_false 1 #define BGP4_PathAttrBest_true 2 if (CHECK_FLAG(path->flags, BGP_PATH_SELECTED)) return SNMP_INTEGER(BGP4_PathAttrBest_true); else return SNMP_INTEGER(BGP4_PathAttrBest_false); break; case BGP4PATHATTRUNKNOWN: /* 14 */ *var_len = 0; return NULL; break; } return NULL; } /* BGP Traps. */ static struct trap_object bgpTrapList[] = {{3, {3, 1, BGPPEERLASTERROR}}, {3, {3, 1, BGPPEERSTATE}}}; static int bgpTrapEstablished(struct peer *peer) { int ret; struct in_addr addr; oid index[sizeof(oid) * IN_ADDR_SIZE]; ret = inet_aton(peer->host, &addr); if (ret == 0) return 0; oid_copy_addr(index, &addr, IN_ADDR_SIZE); smux_trap(bgp_variables, array_size(bgp_variables), bgp_trap_oid, array_size(bgp_trap_oid), bgp_oid, sizeof bgp_oid / sizeof(oid), index, IN_ADDR_SIZE, bgpTrapList, array_size(bgpTrapList), BGPESTABLISHED); return 0; } static int bgpTrapBackwardTransition(struct peer *peer) { int ret; struct in_addr addr; oid index[sizeof(oid) * IN_ADDR_SIZE]; ret = inet_aton(peer->host, &addr); if (ret == 0) return 0; oid_copy_addr(index, &addr, IN_ADDR_SIZE); smux_trap(bgp_variables, array_size(bgp_variables), bgp_trap_oid, array_size(bgp_trap_oid), bgp_oid, sizeof bgp_oid / sizeof(oid), index, IN_ADDR_SIZE, bgpTrapList, array_size(bgpTrapList), BGPBACKWARDTRANSITION); return 0; } static int bgp_snmp_init(struct thread_master *tm) { smux_init(tm); REGISTER_MIB("mibII/bgp", bgp_variables, variable, bgp_oid); return 0; } static int bgp_snmp_module_init(void) { hook_register(peer_established, bgpTrapEstablished); hook_register(peer_backward_transition, bgpTrapBackwardTransition); hook_register(frr_late_init, bgp_snmp_init); return 0; } FRR_MODULE_SETUP(.name = "bgpd_snmp", .version = FRR_VERSION, .description = "bgpd AgentX SNMP module", .init = bgp_snmp_module_init) frr-7.2.1/bgpd/bgp_table.c0000644000000000000000000001136413610377563012220 00000000000000/* BGP routing table * Copyright (C) 1998, 2001 Kunihiro Ishiguro * * This file is part of GNU Zebra. * * GNU Zebra is free software; you can redistribute it and/or modify it * under the terms of the GNU General Public License as published by the * Free Software Foundation; either version 2, or (at your option) any * later version. * * GNU Zebra 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; see the file COPYING; if not, write to the Free Software * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA */ #include #include "prefix.h" #include "memory.h" #include "sockunion.h" #include "queue.h" #include "filter.h" #include "command.h" #include "bgpd/bgpd.h" #include "bgpd/bgp_table.h" #include "bgp_addpath.h" void bgp_table_lock(struct bgp_table *rt) { rt->lock++; } void bgp_table_unlock(struct bgp_table *rt) { assert(rt->lock > 0); rt->lock--; if (rt->lock != 0) { return; } route_table_finish(rt->route_table); rt->route_table = NULL; XFREE(MTYPE_BGP_TABLE, rt); } void bgp_table_finish(struct bgp_table **rt) { if (*rt != NULL) { bgp_table_unlock(*rt); *rt = NULL; } } /* * bgp_node_create */ static struct route_node *bgp_node_create(route_table_delegate_t *delegate, struct route_table *table) { struct bgp_node *node; node = XCALLOC(MTYPE_BGP_NODE, sizeof(struct bgp_node)); RB_INIT(bgp_adj_out_rb, &node->adj_out); return bgp_node_to_rnode(node); } /* * bgp_node_destroy */ static void bgp_node_destroy(route_table_delegate_t *delegate, struct route_table *table, struct route_node *node) { struct bgp_node *bgp_node; struct bgp_table *rt; bgp_node = bgp_node_from_rnode(node); rt = table->info; if (rt->bgp) { bgp_addpath_free_node_data(&rt->bgp->tx_addpath, &bgp_node->tx_addpath, rt->afi, rt->safi); } XFREE(MTYPE_BGP_NODE, bgp_node); } /* * Function vector to customize the behavior of the route table * library for BGP route tables. */ route_table_delegate_t bgp_table_delegate = {.create_node = bgp_node_create, .destroy_node = bgp_node_destroy}; /* * bgp_table_init */ struct bgp_table *bgp_table_init(struct bgp *bgp, afi_t afi, safi_t safi) { struct bgp_table *rt; rt = XCALLOC(MTYPE_BGP_TABLE, sizeof(struct bgp_table)); rt->route_table = route_table_init_with_delegate(&bgp_table_delegate); /* * Set up back pointer to bgp_table. */ route_table_set_info(rt->route_table, rt); /* * pointer to bgp instance allows working back from bgp_path_info to bgp */ rt->bgp = bgp; bgp_table_lock(rt); rt->afi = afi; rt->safi = safi; return rt; } static struct bgp_node * bgp_route_next_until_maxlen(struct bgp_node *node, const struct bgp_node *limit, const uint8_t maxlen) { if (node->l_left && node->p.prefixlen < maxlen && node->l_left->p.prefixlen <= maxlen) { return bgp_node_from_rnode(node->l_left); } if (node->l_right && node->p.prefixlen < maxlen && node->l_right->p.prefixlen <= maxlen) { return bgp_node_from_rnode(node->l_right); } while (node->parent && node != limit) { if (bgp_node_from_rnode(node->parent->l_left) == node && node->parent->l_right) { return bgp_node_from_rnode(node->parent->l_right); } node = bgp_node_from_rnode(node->parent); } return NULL; } void bgp_table_range_lookup(const struct bgp_table *table, struct prefix *p, uint8_t maxlen, struct list *matches) { struct bgp_node *node = bgp_node_from_rnode(table->route_table->top); struct bgp_node *matched = NULL; if (node == NULL) return; while (node && node->p.prefixlen <= p->prefixlen && prefix_match(&node->p, p)) { if (bgp_node_has_bgp_path_info_data(node) && node->p.prefixlen == p->prefixlen) { matched = node; break; } node = bgp_node_from_rnode(node->link[prefix_bit( &p->u.prefix, node->p.prefixlen)]); } if (!node) return; if (matched == NULL && node->p.prefixlen <= maxlen && prefix_match(p, &node->p) && node->parent == NULL) matched = node; else if ((matched == NULL && node->p.prefixlen > maxlen) || !node->parent) return; else if (matched == NULL && node->parent) matched = node = bgp_node_from_rnode(node->parent); if (!matched) return; if (bgp_node_has_bgp_path_info_data(matched)) { bgp_lock_node(matched); listnode_add(matches, matched); } while ((node = bgp_route_next_until_maxlen(node, matched, maxlen))) { if (prefix_match(p, &node->p)) { if (bgp_node_has_bgp_path_info_data(node)) { bgp_lock_node(node); listnode_add(matches, node); } } } } frr-7.2.1/bgpd/bgp_table.h0000644000000000000000000002312213610377563012220 00000000000000/* BGP routing table * Copyright (C) 1998, 2001 Kunihiro Ishiguro * * This file is part of GNU Zebra. * * GNU Zebra is free software; you can redistribute it and/or modify it * under the terms of the GNU General Public License as published by the * Free Software Foundation; either version 2, or (at your option) any * later version. * * GNU Zebra 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; see the file COPYING; if not, write to the Free Software * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA */ #ifndef _QUAGGA_BGP_TABLE_H #define _QUAGGA_BGP_TABLE_H #include "mpls.h" #include "table.h" #include "queue.h" #include "linklist.h" #include "bgpd.h" #include "bgp_advertise.h" struct bgp_table { /* table belongs to this instance */ struct bgp *bgp; /* afi/safi of this table */ afi_t afi; safi_t safi; int lock; struct route_table *route_table; uint64_t version; }; enum bgp_path_selection_reason { bgp_path_selection_none, bgp_path_selection_first, bgp_path_selection_evpn_sticky_mac, bgp_path_selection_evpn_seq, bgp_path_selection_evpn_lower_ip, bgp_path_selection_weight, bgp_path_selection_local_pref, bgp_path_selection_local_route, bgp_path_selection_confed_as_path, bgp_path_selection_as_path, bgp_path_selection_origin, bgp_path_selection_med, bgp_path_selection_peer, bgp_path_selection_confed, bgp_path_selection_igp_metric, bgp_path_selection_older, bgp_path_selection_router_id, bgp_path_selection_cluster_length, bgp_path_selection_stale, bgp_path_selection_local_configured, bgp_path_selection_neighbor_ip, bgp_path_selection_default, }; struct bgp_node { /* * CAUTION * * These fields must be the very first fields in this structure. * * @see bgp_node_to_rnode * @see bgp_node_from_rnode */ ROUTE_NODE_FIELDS struct bgp_adj_out_rb adj_out; struct bgp_adj_in *adj_in; struct bgp_node *prn; STAILQ_ENTRY(bgp_node) pq; uint64_t version; mpls_label_t local_label; uint8_t flags; #define BGP_NODE_PROCESS_SCHEDULED (1 << 0) #define BGP_NODE_USER_CLEAR (1 << 1) #define BGP_NODE_LABEL_CHANGED (1 << 2) #define BGP_NODE_REGISTERED_FOR_LABEL (1 << 3) struct bgp_addpath_node_data tx_addpath; enum bgp_path_selection_reason reason; }; /* * bgp_table_iter_t * * Structure that holds state for iterating over a bgp table. */ typedef struct bgp_table_iter_t_ { struct bgp_table *table; route_table_iter_t rt_iter; } bgp_table_iter_t; extern struct bgp_table *bgp_table_init(struct bgp *bgp, afi_t, safi_t); extern void bgp_table_lock(struct bgp_table *); extern void bgp_table_unlock(struct bgp_table *); extern void bgp_table_finish(struct bgp_table **); /* * bgp_node_from_rnode * * Returns the bgp_node structure corresponding to a route_node. */ static inline struct bgp_node *bgp_node_from_rnode(struct route_node *rnode) { return (struct bgp_node *)rnode; } /* * bgp_node_to_rnode * * Returns the route_node structure corresponding to a bgp_node. */ static inline struct route_node *bgp_node_to_rnode(struct bgp_node *node) { return (struct route_node *)node; } /* * bgp_node_table * * Returns the bgp_table that the given node is in. */ static inline struct bgp_table *bgp_node_table(struct bgp_node *node) { return route_table_get_info(bgp_node_to_rnode(node)->table); } /* * bgp_node_parent_nolock * * Gets the parent node of the given node without locking it. */ static inline struct bgp_node *bgp_node_parent_nolock(struct bgp_node *node) { return bgp_node_from_rnode(node->parent); } /* * bgp_unlock_node */ static inline void bgp_unlock_node(struct bgp_node *node) { route_unlock_node(bgp_node_to_rnode(node)); } /* * bgp_table_top_nolock * * Gets the top node in the table without locking it. * * @see bgp_table_top */ static inline struct bgp_node * bgp_table_top_nolock(const struct bgp_table *const table) { return bgp_node_from_rnode(table->route_table->top); } /* * bgp_table_top */ static inline struct bgp_node * bgp_table_top(const struct bgp_table *const table) { return bgp_node_from_rnode(route_top(table->route_table)); } /* * bgp_route_next */ static inline struct bgp_node *bgp_route_next(struct bgp_node *node) { return bgp_node_from_rnode(route_next(bgp_node_to_rnode(node))); } /* * bgp_route_next_until */ static inline struct bgp_node *bgp_route_next_until(struct bgp_node *node, struct bgp_node *limit) { struct route_node *rnode; rnode = route_next_until(bgp_node_to_rnode(node), bgp_node_to_rnode(limit)); return bgp_node_from_rnode(rnode); } /* * bgp_node_get */ static inline struct bgp_node *bgp_node_get(struct bgp_table *const table, struct prefix *p) { return bgp_node_from_rnode(route_node_get(table->route_table, p)); } /* * bgp_node_lookup */ static inline struct bgp_node * bgp_node_lookup(const struct bgp_table *const table, struct prefix *p) { return bgp_node_from_rnode(route_node_lookup(table->route_table, p)); } /* * bgp_lock_node */ static inline struct bgp_node *bgp_lock_node(struct bgp_node *node) { return bgp_node_from_rnode(route_lock_node(bgp_node_to_rnode(node))); } /* * bgp_node_match */ static inline struct bgp_node *bgp_node_match(const struct bgp_table *table, struct prefix *p) { return bgp_node_from_rnode(route_node_match(table->route_table, p)); } /* * bgp_node_match_ipv4 */ static inline struct bgp_node * bgp_node_match_ipv4(const struct bgp_table *table, struct in_addr *addr) { return bgp_node_from_rnode( route_node_match_ipv4(table->route_table, addr)); } /* * bgp_node_match_ipv6 */ static inline struct bgp_node * bgp_node_match_ipv6(const struct bgp_table *table, struct in6_addr *addr) { return bgp_node_from_rnode( route_node_match_ipv6(table->route_table, addr)); } static inline unsigned long bgp_table_count(const struct bgp_table *const table) { return route_table_count(table->route_table); } /* * bgp_table_get_next */ static inline struct bgp_node *bgp_table_get_next(const struct bgp_table *table, struct prefix *p) { return bgp_node_from_rnode(route_table_get_next(table->route_table, p)); } /* * bgp_table_iter_init */ static inline void bgp_table_iter_init(bgp_table_iter_t *iter, struct bgp_table *table) { bgp_table_lock(table); iter->table = table; route_table_iter_init(&iter->rt_iter, table->route_table); } /* * bgp_table_iter_next */ static inline struct bgp_node *bgp_table_iter_next(bgp_table_iter_t *iter) { return bgp_node_from_rnode(route_table_iter_next(&iter->rt_iter)); } /* * bgp_table_iter_cleanup */ static inline void bgp_table_iter_cleanup(bgp_table_iter_t *iter) { route_table_iter_cleanup(&iter->rt_iter); bgp_table_unlock(iter->table); iter->table = NULL; } /* * bgp_table_iter_pause */ static inline void bgp_table_iter_pause(bgp_table_iter_t *iter) { route_table_iter_pause(&iter->rt_iter); } /* * bgp_table_iter_is_done */ static inline int bgp_table_iter_is_done(bgp_table_iter_t *iter) { return route_table_iter_is_done(&iter->rt_iter); } /* * bgp_table_iter_started */ static inline int bgp_table_iter_started(bgp_table_iter_t *iter) { return route_table_iter_started(&iter->rt_iter); } /* This would benefit from a real atomic operation... * until then. */ static inline uint64_t bgp_table_next_version(struct bgp_table *table) { return ++table->version; } static inline uint64_t bgp_table_version(struct bgp_table *table) { return table->version; } void bgp_table_range_lookup(const struct bgp_table *table, struct prefix *p, uint8_t maxlen, struct list *matches); static inline struct bgp_aggregate * bgp_node_get_bgp_aggregate_info(struct bgp_node *node) { return node->info; } static inline void bgp_node_set_bgp_aggregate_info(struct bgp_node *node, struct bgp_aggregate *aggregate) { node->info = aggregate; } static inline struct bgp_distance * bgp_node_get_bgp_distance_info(struct bgp_node *node) { return node->info; } static inline void bgp_node_set_bgp_distance_info(struct bgp_node *node, struct bgp_distance *distance) { node->info = distance; } static inline struct bgp_static * bgp_node_get_bgp_static_info(struct bgp_node *node) { return node->info; } static inline void bgp_node_set_bgp_static_info(struct bgp_node *node, struct bgp_static *bgp_static) { node->info = bgp_static; } static inline struct bgp_connected_ref * bgp_node_get_bgp_connected_ref_info(struct bgp_node *node) { return node->info; } static inline void bgp_node_set_bgp_connected_ref_info(struct bgp_node *node, struct bgp_connected_ref *bc) { node->info = bc; } static inline struct bgp_nexthop_cache * bgp_node_get_bgp_nexthop_info(struct bgp_node *node) { return node->info; } static inline void bgp_node_set_bgp_nexthop_info(struct bgp_node *node, struct bgp_nexthop_cache *bnc) { node->info = bnc; } static inline struct bgp_path_info * bgp_node_get_bgp_path_info(struct bgp_node *node) { return node->info; } static inline void bgp_node_set_bgp_path_info(struct bgp_node *node, struct bgp_path_info *bi) { node->info = bi; } static inline struct bgp_table * bgp_node_get_bgp_table_info(struct bgp_node *node) { return node->info; } static inline void bgp_node_set_bgp_table_info(struct bgp_node *node, struct bgp_table *table) { node->info = table; } static inline bool bgp_node_has_bgp_path_info_data(struct bgp_node *node) { return !!node->info; } #endif /* _QUAGGA_BGP_TABLE_H */ frr-7.2.1/bgpd/bgp_updgrp.c0000644000000000000000000014324113610377563012432 00000000000000/** * bgp_updgrp.c: BGP update group structures * * @copyright Copyright (C) 2014 Cumulus Networks, Inc. * * @author Avneesh Sachdev * @author Rajesh Varadarajan * @author Pradosh Mohapatra * * This file is part of GNU Zebra. * * GNU Zebra is free software; you can redistribute it and/or modify it * under the terms of the GNU General Public License as published by the * Free Software Foundation; either version 2, or (at your option) any * later version. * * GNU Zebra 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; see the file COPYING; if not, write to the Free Software * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA */ #include #include "prefix.h" #include "thread.h" #include "buffer.h" #include "stream.h" #include "command.h" #include "sockunion.h" #include "network.h" #include "memory.h" #include "filter.h" #include "routemap.h" #include "log.h" #include "plist.h" #include "linklist.h" #include "workqueue.h" #include "hash.h" #include "jhash.h" #include "queue.h" #include "bgpd/bgpd.h" #include "bgpd/bgp_table.h" #include "bgpd/bgp_debug.h" #include "bgpd/bgp_errors.h" #include "bgpd/bgp_fsm.h" #include "bgpd/bgp_advertise.h" #include "bgpd/bgp_packet.h" #include "bgpd/bgp_updgrp.h" #include "bgpd/bgp_route.h" #include "bgpd/bgp_filter.h" #include "bgpd/bgp_io.h" /******************** * PRIVATE FUNCTIONS ********************/ /** * assign a unique ID to update group and subgroup. Mostly for display/ * debugging purposes. It's a 64-bit space - used leisurely without a * worry about its wrapping and about filling gaps. While at it, timestamp * the creation. */ static void update_group_checkin(struct update_group *updgrp) { updgrp->id = ++bm->updgrp_idspace; updgrp->uptime = bgp_clock(); } static void update_subgroup_checkin(struct update_subgroup *subgrp, struct update_group *updgrp) { subgrp->id = ++bm->subgrp_idspace; subgrp->uptime = bgp_clock(); } static void sync_init(struct update_subgroup *subgrp) { subgrp->sync = XCALLOC(MTYPE_BGP_SYNCHRONISE, sizeof(struct bgp_synchronize)); bgp_adv_fifo_init(&subgrp->sync->update); bgp_adv_fifo_init(&subgrp->sync->withdraw); bgp_adv_fifo_init(&subgrp->sync->withdraw_low); subgrp->hash = hash_create(baa_hash_key, baa_hash_cmp, "BGP SubGroup Hash"); /* We use a larger buffer for subgrp->work in the event that: * - We RX a BGP_UPDATE where the attributes alone are just * under BGP_MAX_PACKET_SIZE * - The user configures an outbound route-map that does many as-path * prepends or adds many communities. At most they can have * CMD_ARGC_MAX * args in a route-map so there is a finite limit on how large they * can * make the attributes. * * Having a buffer with BGP_MAX_PACKET_SIZE_OVERFLOW allows us to avoid * bounds * checking for every single attribute as we construct an UPDATE. */ subgrp->work = stream_new(BGP_MAX_PACKET_SIZE + BGP_MAX_PACKET_SIZE_OVERFLOW); subgrp->scratch = stream_new(BGP_MAX_PACKET_SIZE); } static void sync_delete(struct update_subgroup *subgrp) { XFREE(MTYPE_BGP_SYNCHRONISE, subgrp->sync); subgrp->sync = NULL; if (subgrp->hash) hash_free(subgrp->hash); subgrp->hash = NULL; if (subgrp->work) stream_free(subgrp->work); subgrp->work = NULL; if (subgrp->scratch) stream_free(subgrp->scratch); subgrp->scratch = NULL; } /** * conf_copy * * copy only those fields that are relevant to update group match */ static void conf_copy(struct peer *dst, struct peer *src, afi_t afi, safi_t safi) { struct bgp_filter *srcfilter; struct bgp_filter *dstfilter; srcfilter = &src->filter[afi][safi]; dstfilter = &dst->filter[afi][safi]; dst->bgp = src->bgp; dst->sort = src->sort; dst->as = src->as; dst->v_routeadv = src->v_routeadv; dst->flags = src->flags; dst->af_flags[afi][safi] = src->af_flags[afi][safi]; XFREE(MTYPE_BGP_PEER_HOST, dst->host); dst->host = XSTRDUP(MTYPE_BGP_PEER_HOST, src->host); dst->cap = src->cap; dst->af_cap[afi][safi] = src->af_cap[afi][safi]; dst->afc_nego[afi][safi] = src->afc_nego[afi][safi]; dst->orf_plist[afi][safi] = src->orf_plist[afi][safi]; dst->addpath_type[afi][safi] = src->addpath_type[afi][safi]; dst->local_as = src->local_as; dst->change_local_as = src->change_local_as; dst->shared_network = src->shared_network; memcpy(&(dst->nexthop), &(src->nexthop), sizeof(struct bgp_nexthop)); dst->group = src->group; if (src->default_rmap[afi][safi].name) { dst->default_rmap[afi][safi].name = XSTRDUP(MTYPE_ROUTE_MAP_NAME, src->default_rmap[afi][safi].name); dst->default_rmap[afi][safi].map = src->default_rmap[afi][safi].map; } if (DISTRIBUTE_OUT_NAME(srcfilter)) { DISTRIBUTE_OUT_NAME(dstfilter) = XSTRDUP( MTYPE_BGP_FILTER_NAME, DISTRIBUTE_OUT_NAME(srcfilter)); DISTRIBUTE_OUT(dstfilter) = DISTRIBUTE_OUT(srcfilter); } if (PREFIX_LIST_OUT_NAME(srcfilter)) { PREFIX_LIST_OUT_NAME(dstfilter) = XSTRDUP( MTYPE_BGP_FILTER_NAME, PREFIX_LIST_OUT_NAME(srcfilter)); PREFIX_LIST_OUT(dstfilter) = PREFIX_LIST_OUT(srcfilter); } if (FILTER_LIST_OUT_NAME(srcfilter)) { FILTER_LIST_OUT_NAME(dstfilter) = XSTRDUP( MTYPE_BGP_FILTER_NAME, FILTER_LIST_OUT_NAME(srcfilter)); FILTER_LIST_OUT(dstfilter) = FILTER_LIST_OUT(srcfilter); } if (ROUTE_MAP_OUT_NAME(srcfilter)) { ROUTE_MAP_OUT_NAME(dstfilter) = XSTRDUP( MTYPE_BGP_FILTER_NAME, ROUTE_MAP_OUT_NAME(srcfilter)); ROUTE_MAP_OUT(dstfilter) = ROUTE_MAP_OUT(srcfilter); } if (UNSUPPRESS_MAP_NAME(srcfilter)) { UNSUPPRESS_MAP_NAME(dstfilter) = XSTRDUP( MTYPE_BGP_FILTER_NAME, UNSUPPRESS_MAP_NAME(srcfilter)); UNSUPPRESS_MAP(dstfilter) = UNSUPPRESS_MAP(srcfilter); } } /** * since we did a bunch of XSTRDUP's in conf_copy, time to free them up */ static void conf_release(struct peer *src, afi_t afi, safi_t safi) { struct bgp_filter *srcfilter; srcfilter = &src->filter[afi][safi]; XFREE(MTYPE_ROUTE_MAP_NAME, src->default_rmap[afi][safi].name); XFREE(MTYPE_BGP_FILTER_NAME, srcfilter->dlist[FILTER_OUT].name); XFREE(MTYPE_BGP_FILTER_NAME, srcfilter->plist[FILTER_OUT].name); XFREE(MTYPE_BGP_FILTER_NAME, srcfilter->aslist[FILTER_OUT].name); XFREE(MTYPE_BGP_FILTER_NAME, srcfilter->map[RMAP_OUT].name); XFREE(MTYPE_BGP_FILTER_NAME, srcfilter->usmap.name); XFREE(MTYPE_BGP_PEER_HOST, src->host); src->host = NULL; } static void peer2_updgrp_copy(struct update_group *updgrp, struct peer_af *paf) { struct peer *src; struct peer *dst; if (!updgrp || !paf) return; src = paf->peer; dst = updgrp->conf; if (!src || !dst) return; updgrp->afi = paf->afi; updgrp->safi = paf->safi; updgrp->afid = paf->afid; updgrp->bgp = src->bgp; conf_copy(dst, src, paf->afi, paf->safi); } /** * auxiliary functions to maintain the hash table. * - updgrp_hash_alloc - to create a new entry, passed to hash_get * - updgrp_hash_key_make - makes the key for update group search * - updgrp_hash_cmp - compare two update groups. */ static void *updgrp_hash_alloc(void *p) { struct update_group *updgrp; const struct update_group *in; in = (const struct update_group *)p; updgrp = XCALLOC(MTYPE_BGP_UPDGRP, sizeof(struct update_group)); memcpy(updgrp, in, sizeof(struct update_group)); updgrp->conf = XCALLOC(MTYPE_BGP_PEER, sizeof(struct peer)); conf_copy(updgrp->conf, in->conf, in->afi, in->safi); return updgrp; } /** * The hash value for a peer is computed from the following variables: * v = f( * 1. IBGP (1) or EBGP (2) * 2. FLAGS based on configuration: * LOCAL_AS_NO_PREPEND * LOCAL_AS_REPLACE_AS * 3. AF_FLAGS based on configuration: * Refer to definition in bgp_updgrp.h * 4. (AF-independent) Capability flags: * AS4_RCV capability * 5. (AF-dependent) Capability flags: * ORF_PREFIX_SM_RCV (peer can send prefix ORF) * 6. MRAI * 7. peer-group name * 8. Outbound route-map name (neighbor route-map <> out) * 9. Outbound distribute-list name (neighbor distribute-list <> out) * 10. Outbound prefix-list name (neighbor prefix-list <> out) * 11. Outbound as-list name (neighbor filter-list <> out) * 12. Unsuppress map name (neighbor unsuppress-map <>) * 13. default rmap name (neighbor default-originate route-map <>) * 14. encoding both global and link-local nexthop? * 15. If peer is configured to be a lonesoul, peer ip address * 16. Local-as should match, if configured. * ) */ static unsigned int updgrp_hash_key_make(const void *p) { const struct update_group *updgrp; const struct peer *peer; const struct bgp_filter *filter; uint32_t flags; uint32_t key; afi_t afi; safi_t safi; #define SEED1 999331 #define SEED2 2147483647 updgrp = p; peer = updgrp->conf; afi = updgrp->afi; safi = updgrp->safi; flags = peer->af_flags[afi][safi]; filter = &peer->filter[afi][safi]; key = 0; key = jhash_1word(peer->sort, key); /* EBGP or IBGP */ key = jhash_1word((peer->flags & PEER_UPDGRP_FLAGS), key); key = jhash_1word((flags & PEER_UPDGRP_AF_FLAGS), key); key = jhash_1word((uint32_t)peer->addpath_type[afi][safi], key); key = jhash_1word((peer->cap & PEER_UPDGRP_CAP_FLAGS), key); key = jhash_1word((peer->af_cap[afi][safi] & PEER_UPDGRP_AF_CAP_FLAGS), key); key = jhash_1word(peer->v_routeadv, key); key = jhash_1word(peer->change_local_as, key); if (peer->group) key = jhash_1word(jhash(peer->group->name, strlen(peer->group->name), SEED1), key); if (filter->map[RMAP_OUT].name) key = jhash_1word(jhash(filter->map[RMAP_OUT].name, strlen(filter->map[RMAP_OUT].name), SEED1), key); if (filter->dlist[FILTER_OUT].name) key = jhash_1word(jhash(filter->dlist[FILTER_OUT].name, strlen(filter->dlist[FILTER_OUT].name), SEED1), key); if (filter->plist[FILTER_OUT].name) key = jhash_1word(jhash(filter->plist[FILTER_OUT].name, strlen(filter->plist[FILTER_OUT].name), SEED1), key); if (filter->aslist[FILTER_OUT].name) key = jhash_1word(jhash(filter->aslist[FILTER_OUT].name, strlen(filter->aslist[FILTER_OUT].name), SEED1), key); if (filter->usmap.name) key = jhash_1word(jhash(filter->usmap.name, strlen(filter->usmap.name), SEED1), key); if (peer->default_rmap[afi][safi].name) key = jhash_1word( jhash(peer->default_rmap[afi][safi].name, strlen(peer->default_rmap[afi][safi].name), SEED1), key); /* If peer is on a shared network and is exchanging IPv6 prefixes, * it needs to include link-local address. That's different from * non-shared-network peers (nexthop encoded with 32 bytes vs 16 * bytes). We create different update groups to take care of that. */ key = jhash_1word( (peer->shared_network && peer_afi_active_nego(peer, AFI_IP6)), key); /* * There are certain peers that must get their own update-group: * - lonesoul peers * - peers that negotiated ORF */ if (CHECK_FLAG(peer->flags, PEER_FLAG_LONESOUL) || CHECK_FLAG(peer->af_cap[afi][safi], PEER_CAP_ORF_PREFIX_SM_RCV) || CHECK_FLAG(peer->af_cap[afi][safi], PEER_CAP_ORF_PREFIX_SM_OLD_RCV)) key = jhash_1word(jhash(peer->host, strlen(peer->host), SEED2), key); return key; } static bool updgrp_hash_cmp(const void *p1, const void *p2) { const struct update_group *grp1; const struct update_group *grp2; const struct peer *pe1; const struct peer *pe2; uint32_t flags1; uint32_t flags2; const struct bgp_filter *fl1; const struct bgp_filter *fl2; afi_t afi; safi_t safi; if (!p1 || !p2) return false; grp1 = p1; grp2 = p2; pe1 = grp1->conf; pe2 = grp2->conf; afi = grp1->afi; safi = grp1->safi; flags1 = pe1->af_flags[afi][safi]; flags2 = pe2->af_flags[afi][safi]; fl1 = &pe1->filter[afi][safi]; fl2 = &pe2->filter[afi][safi]; /* put EBGP and IBGP peers in different update groups */ if (pe1->sort != pe2->sort) return false; /* check peer flags */ if ((pe1->flags & PEER_UPDGRP_FLAGS) != (pe2->flags & PEER_UPDGRP_FLAGS)) return false; /* If there is 'local-as' configured, it should match. */ if (pe1->change_local_as != pe2->change_local_as) return false; /* flags like route reflector client */ if ((flags1 & PEER_UPDGRP_AF_FLAGS) != (flags2 & PEER_UPDGRP_AF_FLAGS)) return false; if (pe1->addpath_type[afi][safi] != pe2->addpath_type[afi][safi]) return false; if ((pe1->cap & PEER_UPDGRP_CAP_FLAGS) != (pe2->cap & PEER_UPDGRP_CAP_FLAGS)) return false; if ((pe1->af_cap[afi][safi] & PEER_UPDGRP_AF_CAP_FLAGS) != (pe2->af_cap[afi][safi] & PEER_UPDGRP_AF_CAP_FLAGS)) return false; if (pe1->v_routeadv != pe2->v_routeadv) return false; if (pe1->group != pe2->group) return false; /* route-map names should be the same */ if ((fl1->map[RMAP_OUT].name && !fl2->map[RMAP_OUT].name) || (!fl1->map[RMAP_OUT].name && fl2->map[RMAP_OUT].name) || (fl1->map[RMAP_OUT].name && fl2->map[RMAP_OUT].name && strcmp(fl1->map[RMAP_OUT].name, fl2->map[RMAP_OUT].name))) return false; if ((fl1->dlist[FILTER_OUT].name && !fl2->dlist[FILTER_OUT].name) || (!fl1->dlist[FILTER_OUT].name && fl2->dlist[FILTER_OUT].name) || (fl1->dlist[FILTER_OUT].name && fl2->dlist[FILTER_OUT].name && strcmp(fl1->dlist[FILTER_OUT].name, fl2->dlist[FILTER_OUT].name))) return false; if ((fl1->plist[FILTER_OUT].name && !fl2->plist[FILTER_OUT].name) || (!fl1->plist[FILTER_OUT].name && fl2->plist[FILTER_OUT].name) || (fl1->plist[FILTER_OUT].name && fl2->plist[FILTER_OUT].name && strcmp(fl1->plist[FILTER_OUT].name, fl2->plist[FILTER_OUT].name))) return false; if ((fl1->aslist[FILTER_OUT].name && !fl2->aslist[FILTER_OUT].name) || (!fl1->aslist[FILTER_OUT].name && fl2->aslist[FILTER_OUT].name) || (fl1->aslist[FILTER_OUT].name && fl2->aslist[FILTER_OUT].name && strcmp(fl1->aslist[FILTER_OUT].name, fl2->aslist[FILTER_OUT].name))) return false; if ((fl1->usmap.name && !fl2->usmap.name) || (!fl1->usmap.name && fl2->usmap.name) || (fl1->usmap.name && fl2->usmap.name && strcmp(fl1->usmap.name, fl2->usmap.name))) return false; if ((pe1->default_rmap[afi][safi].name && !pe2->default_rmap[afi][safi].name) || (!pe1->default_rmap[afi][safi].name && pe2->default_rmap[afi][safi].name) || (pe1->default_rmap[afi][safi].name && pe2->default_rmap[afi][safi].name && strcmp(pe1->default_rmap[afi][safi].name, pe2->default_rmap[afi][safi].name))) return false; if ((afi == AFI_IP6) && (pe1->shared_network != pe2->shared_network)) return false; if ((CHECK_FLAG(pe1->flags, PEER_FLAG_LONESOUL) || CHECK_FLAG(pe1->af_cap[afi][safi], PEER_CAP_ORF_PREFIX_SM_RCV) || CHECK_FLAG(pe1->af_cap[afi][safi], PEER_CAP_ORF_PREFIX_SM_OLD_RCV)) && !sockunion_same(&pe1->su, &pe2->su)) return false; return true; } static void peer_lonesoul_or_not(struct peer *peer, int set) { /* no change in status? */ if (set == (CHECK_FLAG(peer->flags, PEER_FLAG_LONESOUL) > 0)) return; if (set) SET_FLAG(peer->flags, PEER_FLAG_LONESOUL); else UNSET_FLAG(peer->flags, PEER_FLAG_LONESOUL); update_group_adjust_peer_afs(peer); } /* * subgroup_total_packets_enqueued * * Returns the total number of packets enqueued to a subgroup. */ static unsigned int subgroup_total_packets_enqueued(struct update_subgroup *subgrp) { struct bpacket *pkt; pkt = bpacket_queue_last(SUBGRP_PKTQ(subgrp)); return pkt->ver - 1; } static int update_group_show_walkcb(struct update_group *updgrp, void *arg) { struct updwalk_context *ctx = arg; struct vty *vty; struct update_subgroup *subgrp; struct peer_af *paf; struct bgp_filter *filter; int match = 0; if (!ctx) return CMD_SUCCESS; if (ctx->subgrp_id) { UPDGRP_FOREACH_SUBGRP (updgrp, subgrp) { if (ctx->subgrp_id && (ctx->subgrp_id != subgrp->id)) continue; else { match = 1; break; } } } else { match = 1; } if (!match) { /* Since this routine is invoked from a walk, we cannot signal * any */ /* error here, can only return. */ return CMD_SUCCESS; } vty = ctx->vty; vty_out(vty, "Update-group %" PRIu64 ":\n", updgrp->id); vty_out(vty, " Created: %s", timestamp_string(updgrp->uptime)); filter = &updgrp->conf->filter[updgrp->afi][updgrp->safi]; if (filter->map[RMAP_OUT].name) vty_out(vty, " Outgoing route map: %s%s\n", filter->map[RMAP_OUT].map ? "X" : "", filter->map[RMAP_OUT].name); vty_out(vty, " MRAI value (seconds): %d\n", updgrp->conf->v_routeadv); if (updgrp->conf->change_local_as) vty_out(vty, " Local AS %u%s%s\n", updgrp->conf->change_local_as, CHECK_FLAG(updgrp->conf->flags, PEER_FLAG_LOCAL_AS_NO_PREPEND) ? " no-prepend" : "", CHECK_FLAG(updgrp->conf->flags, PEER_FLAG_LOCAL_AS_REPLACE_AS) ? " replace-as" : ""); UPDGRP_FOREACH_SUBGRP (updgrp, subgrp) { if (ctx->subgrp_id && (ctx->subgrp_id != subgrp->id)) continue; vty_out(vty, "\n"); vty_out(vty, " Update-subgroup %" PRIu64 ":\n", subgrp->id); vty_out(vty, " Created: %s", timestamp_string(subgrp->uptime)); if (subgrp->split_from.update_group_id || subgrp->split_from.subgroup_id) { vty_out(vty, " Split from group id: %" PRIu64 "\n", subgrp->split_from.update_group_id); vty_out(vty, " Split from subgroup id: %" PRIu64 "\n", subgrp->split_from.subgroup_id); } vty_out(vty, " Join events: %u\n", subgrp->join_events); vty_out(vty, " Prune events: %u\n", subgrp->prune_events); vty_out(vty, " Merge events: %u\n", subgrp->merge_events); vty_out(vty, " Split events: %u\n", subgrp->split_events); vty_out(vty, " Update group switch events: %u\n", subgrp->updgrp_switch_events); vty_out(vty, " Peer refreshes combined: %u\n", subgrp->peer_refreshes_combined); vty_out(vty, " Merge checks triggered: %u\n", subgrp->merge_checks_triggered); vty_out(vty, " Version: %" PRIu64 "\n", subgrp->version); vty_out(vty, " Packet queue length: %d\n", bpacket_queue_length(SUBGRP_PKTQ(subgrp))); vty_out(vty, " Total packets enqueued: %u\n", subgroup_total_packets_enqueued(subgrp)); vty_out(vty, " Packet queue high watermark: %d\n", bpacket_queue_hwm_length(SUBGRP_PKTQ(subgrp))); vty_out(vty, " Adj-out list count: %u\n", subgrp->adj_count); vty_out(vty, " Advertise list: %s\n", advertise_list_is_empty(subgrp) ? "empty" : "not empty"); vty_out(vty, " Flags: %s\n", CHECK_FLAG(subgrp->flags, SUBGRP_FLAG_NEEDS_REFRESH) ? "R" : ""); if (subgrp->peer_count > 0) { vty_out(vty, " Peers:\n"); SUBGRP_FOREACH_PEER (subgrp, paf) vty_out(vty, " - %s\n", paf->peer->host); } } return UPDWALK_CONTINUE; } /* * Helper function to show the packet queue for each subgroup of update group. * Will be constrained to a particular subgroup id if id !=0 */ static int updgrp_show_packet_queue_walkcb(struct update_group *updgrp, void *arg) { struct updwalk_context *ctx = arg; struct update_subgroup *subgrp; struct vty *vty; vty = ctx->vty; UPDGRP_FOREACH_SUBGRP (updgrp, subgrp) { if (ctx->subgrp_id && (ctx->subgrp_id != subgrp->id)) continue; vty_out(vty, "update group %" PRIu64 ", subgroup %" PRIu64 "\n", updgrp->id, subgrp->id); bpacket_queue_show_vty(SUBGRP_PKTQ(subgrp), vty); } return UPDWALK_CONTINUE; } /* * Show the packet queue for each subgroup of update group. Will be * constrained to a particular subgroup id if id !=0 */ void update_group_show_packet_queue(struct bgp *bgp, afi_t afi, safi_t safi, struct vty *vty, uint64_t id) { struct updwalk_context ctx; memset(&ctx, 0, sizeof(ctx)); ctx.vty = vty; ctx.subgrp_id = id; ctx.flags = 0; update_group_af_walk(bgp, afi, safi, updgrp_show_packet_queue_walkcb, &ctx); } static struct update_group *update_group_find(struct peer_af *paf) { struct update_group *updgrp; struct update_group tmp; struct peer tmp_conf; if (!peer_established(PAF_PEER(paf))) return NULL; memset(&tmp, 0, sizeof(tmp)); memset(&tmp_conf, 0, sizeof(tmp_conf)); tmp.conf = &tmp_conf; peer2_updgrp_copy(&tmp, paf); updgrp = hash_lookup(paf->peer->bgp->update_groups[paf->afid], &tmp); conf_release(&tmp_conf, paf->afi, paf->safi); return updgrp; } static struct update_group *update_group_create(struct peer_af *paf) { struct update_group *updgrp; struct update_group tmp; struct peer tmp_conf; memset(&tmp, 0, sizeof(tmp)); memset(&tmp_conf, 0, sizeof(tmp_conf)); tmp.conf = &tmp_conf; peer2_updgrp_copy(&tmp, paf); updgrp = hash_get(paf->peer->bgp->update_groups[paf->afid], &tmp, updgrp_hash_alloc); if (!updgrp) return NULL; update_group_checkin(updgrp); if (BGP_DEBUG(update_groups, UPDATE_GROUPS)) zlog_debug("create update group %" PRIu64, updgrp->id); UPDGRP_GLOBAL_STAT(updgrp, updgrps_created) += 1; conf_release(&tmp_conf, paf->afi, paf->safi); return updgrp; } static void update_group_delete(struct update_group *updgrp) { if (BGP_DEBUG(update_groups, UPDATE_GROUPS)) zlog_debug("delete update group %" PRIu64, updgrp->id); UPDGRP_GLOBAL_STAT(updgrp, updgrps_deleted) += 1; hash_release(updgrp->bgp->update_groups[updgrp->afid], updgrp); conf_release(updgrp->conf, updgrp->afi, updgrp->safi); XFREE(MTYPE_BGP_PEER_HOST, updgrp->conf->host); updgrp->conf->host = NULL; XFREE(MTYPE_BGP_PEER_IFNAME, updgrp->conf->ifname); XFREE(MTYPE_BGP_PEER, updgrp->conf); XFREE(MTYPE_BGP_UPDGRP, updgrp); } static void update_group_add_subgroup(struct update_group *updgrp, struct update_subgroup *subgrp) { if (!updgrp || !subgrp) return; LIST_INSERT_HEAD(&(updgrp->subgrps), subgrp, updgrp_train); subgrp->update_group = updgrp; } static void update_group_remove_subgroup(struct update_group *updgrp, struct update_subgroup *subgrp) { if (!updgrp || !subgrp) return; LIST_REMOVE(subgrp, updgrp_train); subgrp->update_group = NULL; if (LIST_EMPTY(&(updgrp->subgrps))) update_group_delete(updgrp); } static struct update_subgroup * update_subgroup_create(struct update_group *updgrp) { struct update_subgroup *subgrp; subgrp = XCALLOC(MTYPE_BGP_UPD_SUBGRP, sizeof(struct update_subgroup)); update_subgroup_checkin(subgrp, updgrp); subgrp->v_coalesce = (UPDGRP_INST(updgrp))->coalesce_time; sync_init(subgrp); bpacket_queue_init(SUBGRP_PKTQ(subgrp)); bpacket_queue_add(SUBGRP_PKTQ(subgrp), NULL, NULL); TAILQ_INIT(&(subgrp->adjq)); if (BGP_DEBUG(update_groups, UPDATE_GROUPS)) zlog_debug("create subgroup u%" PRIu64 ":s%" PRIu64, updgrp->id, subgrp->id); update_group_add_subgroup(updgrp, subgrp); UPDGRP_INCR_STAT(updgrp, subgrps_created); return subgrp; } static void update_subgroup_delete(struct update_subgroup *subgrp) { if (!subgrp) return; if (subgrp->update_group) UPDGRP_INCR_STAT(subgrp->update_group, subgrps_deleted); if (subgrp->t_merge_check) THREAD_OFF(subgrp->t_merge_check); if (subgrp->t_coalesce) THREAD_TIMER_OFF(subgrp->t_coalesce); bpacket_queue_cleanup(SUBGRP_PKTQ(subgrp)); subgroup_clear_table(subgrp); if (subgrp->t_coalesce) THREAD_TIMER_OFF(subgrp->t_coalesce); sync_delete(subgrp); if (BGP_DEBUG(update_groups, UPDATE_GROUPS) && subgrp->update_group) zlog_debug("delete subgroup u%" PRIu64 ":s%" PRIu64, subgrp->update_group->id, subgrp->id); update_group_remove_subgroup(subgrp->update_group, subgrp); XFREE(MTYPE_BGP_UPD_SUBGRP, subgrp); } void update_subgroup_inherit_info(struct update_subgroup *to, struct update_subgroup *from) { if (!to || !from) return; to->sflags = from->sflags; } /* * update_subgroup_check_delete * * Delete a subgroup if it is ready to be deleted. * * Returns true if the subgroup was deleted. */ static int update_subgroup_check_delete(struct update_subgroup *subgrp) { if (!subgrp) return 0; if (!LIST_EMPTY(&(subgrp->peers))) return 0; update_subgroup_delete(subgrp); return 1; } /* * update_subgroup_add_peer * * @param send_enqueued_packets If true all currently enqueued packets will * also be sent to the peer. */ static void update_subgroup_add_peer(struct update_subgroup *subgrp, struct peer_af *paf, int send_enqueued_pkts) { struct bpacket *pkt; if (!subgrp || !paf) return; LIST_INSERT_HEAD(&(subgrp->peers), paf, subgrp_train); paf->subgroup = subgrp; subgrp->peer_count++; if (bgp_debug_peer_updout_enabled(paf->peer->host)) { UPDGRP_PEER_DBG_EN(subgrp->update_group); } SUBGRP_INCR_STAT(subgrp, join_events); if (send_enqueued_pkts) { pkt = bpacket_queue_first(SUBGRP_PKTQ(subgrp)); } else { /* * Hang the peer off of the last, placeholder, packet in the * queue. This means it won't see any of the packets that are * currently the queue. */ pkt = bpacket_queue_last(SUBGRP_PKTQ(subgrp)); assert(pkt->buffer == NULL); } bpacket_add_peer(pkt, paf); bpacket_queue_sanity_check(SUBGRP_PKTQ(subgrp)); if (BGP_DEBUG(update_groups, UPDATE_GROUPS)) zlog_debug("peer %s added to subgroup s%" PRIu64, paf->peer->host, subgrp->id); } /* * update_subgroup_remove_peer_internal * * Internal function that removes a peer from a subgroup, but does not * delete the subgroup. A call to this function must almost always be * followed by a call to update_subgroup_check_delete(). * * @see update_subgroup_remove_peer */ static void update_subgroup_remove_peer_internal(struct update_subgroup *subgrp, struct peer_af *paf) { assert(subgrp && paf && subgrp->update_group); if (bgp_debug_peer_updout_enabled(paf->peer->host)) { UPDGRP_PEER_DBG_DIS(subgrp->update_group); } bpacket_queue_remove_peer(paf); LIST_REMOVE(paf, subgrp_train); paf->subgroup = NULL; subgrp->peer_count--; if (BGP_DEBUG(update_groups, UPDATE_GROUPS)) zlog_debug("peer %s deleted from subgroup s%" PRIu64 "peer cnt %d", paf->peer->host, subgrp->id, subgrp->peer_count); SUBGRP_INCR_STAT(subgrp, prune_events); } /* * update_subgroup_remove_peer */ void update_subgroup_remove_peer(struct update_subgroup *subgrp, struct peer_af *paf) { if (!subgrp || !paf) return; update_subgroup_remove_peer_internal(subgrp, paf); if (update_subgroup_check_delete(subgrp)) return; /* * The deletion of the peer may have caused some packets to be * deleted from the subgroup packet queue. Check if the subgroup can * be merged now. */ update_subgroup_check_merge(subgrp, "removed peer from subgroup"); } static struct update_subgroup *update_subgroup_find(struct update_group *updgrp, struct peer_af *paf) { struct update_subgroup *subgrp = NULL; uint64_t version; if (paf->subgroup) { assert(0); return NULL; } else version = 0; if (!peer_established(PAF_PEER(paf))) return NULL; UPDGRP_FOREACH_SUBGRP (updgrp, subgrp) { if (subgrp->version != version || CHECK_FLAG(subgrp->sflags, SUBGRP_STATUS_DEFAULT_ORIGINATE)) continue; /* * The version number is not meaningful on a subgroup that needs * a refresh. */ if (update_subgroup_needs_refresh(subgrp)) continue; break; } return subgrp; } /* * update_subgroup_ready_for_merge * * Returns true if this subgroup is in a state that allows it to be * merged into another subgroup. */ static int update_subgroup_ready_for_merge(struct update_subgroup *subgrp) { /* * Not ready if there are any encoded packets waiting to be written * out to peers. */ if (!bpacket_queue_is_empty(SUBGRP_PKTQ(subgrp))) return 0; /* * Not ready if there enqueued updates waiting to be encoded. */ if (!advertise_list_is_empty(subgrp)) return 0; /* * Don't attempt to merge a subgroup that needs a refresh. For one, * we can't determine if the adj_out of such a group matches that of * another group. */ if (update_subgroup_needs_refresh(subgrp)) return 0; return 1; } /* * update_subgrp_can_merge_into * * Returns true if the first subgroup can merge into the second * subgroup. */ static int update_subgroup_can_merge_into(struct update_subgroup *subgrp, struct update_subgroup *target) { if (subgrp == target) return 0; /* * Both must have processed the BRIB to the same point in order to * be merged. */ if (subgrp->version != target->version) return 0; if (CHECK_FLAG(subgrp->sflags, SUBGRP_STATUS_DEFAULT_ORIGINATE) != CHECK_FLAG(target->sflags, SUBGRP_STATUS_DEFAULT_ORIGINATE)) return 0; if (subgrp->adj_count != target->adj_count) return 0; return update_subgroup_ready_for_merge(target); } /* * update_subgroup_merge * * Merge the first subgroup into the second one. */ static void update_subgroup_merge(struct update_subgroup *subgrp, struct update_subgroup *target, const char *reason) { struct peer_af *paf; int result; int peer_count; assert(subgrp->adj_count == target->adj_count); peer_count = subgrp->peer_count; while (1) { paf = LIST_FIRST(&subgrp->peers); if (!paf) break; update_subgroup_remove_peer_internal(subgrp, paf); /* * Add the peer to the target subgroup, while making sure that * any currently enqueued packets won't be sent to it. Enqueued * packets could, for example, result in an unnecessary withdraw * followed by an advertise. */ update_subgroup_add_peer(target, paf, 0); } SUBGRP_INCR_STAT(target, merge_events); if (BGP_DEBUG(update_groups, UPDATE_GROUPS)) zlog_debug("u%" PRIu64 ":s%" PRIu64 " (%d peers) merged into u%" PRIu64 ":s%" PRIu64 ", " "trigger: %s", subgrp->update_group->id, subgrp->id, peer_count, target->update_group->id, target->id, reason ? reason : "unknown"); result = update_subgroup_check_delete(subgrp); assert(result); } /* * update_subgroup_check_merge * * Merge this subgroup into another subgroup if possible. * * Returns true if the subgroup has been merged. The subgroup pointer * should not be accessed in this case. */ int update_subgroup_check_merge(struct update_subgroup *subgrp, const char *reason) { struct update_subgroup *target; if (!update_subgroup_ready_for_merge(subgrp)) return 0; /* * Look for a subgroup to merge into. */ UPDGRP_FOREACH_SUBGRP (subgrp->update_group, target) { if (update_subgroup_can_merge_into(subgrp, target)) break; } if (!target) return 0; update_subgroup_merge(subgrp, target, reason); return 1; } /* * update_subgroup_merge_check_thread_cb */ static int update_subgroup_merge_check_thread_cb(struct thread *thread) { struct update_subgroup *subgrp; subgrp = THREAD_ARG(thread); subgrp->t_merge_check = NULL; update_subgroup_check_merge(subgrp, "triggered merge check"); return 0; } /* * update_subgroup_trigger_merge_check * * Triggers a call to update_subgroup_check_merge() on a clean context. * * @param force If true, the merge check will be triggered even if the * subgroup doesn't currently look ready for a merge. * * Returns true if a merge check will be performed shortly. */ int update_subgroup_trigger_merge_check(struct update_subgroup *subgrp, int force) { if (subgrp->t_merge_check) return 1; if (!force && !update_subgroup_ready_for_merge(subgrp)) return 0; subgrp->t_merge_check = NULL; thread_add_timer_msec(bm->master, update_subgroup_merge_check_thread_cb, subgrp, 0, &subgrp->t_merge_check); SUBGRP_INCR_STAT(subgrp, merge_checks_triggered); return 1; } /* * update_subgroup_copy_adj_out * * Helper function that clones the adj out (state about advertised * routes) from one subgroup to another. It assumes that the adj out * of the target subgroup is empty. */ static void update_subgroup_copy_adj_out(struct update_subgroup *source, struct update_subgroup *dest) { struct bgp_adj_out *aout, *aout_copy; SUBGRP_FOREACH_ADJ (source, aout) { /* * Copy the adj out. */ aout_copy = bgp_adj_out_alloc(dest, aout->rn, aout->addpath_tx_id); aout_copy->attr = aout->attr ? bgp_attr_intern(aout->attr) : NULL; } dest->scount = source->scount; } /* * update_subgroup_copy_packets * * Copy packets after and including the given packet to the subgroup * 'dest'. * * Returns the number of packets copied. */ static int update_subgroup_copy_packets(struct update_subgroup *dest, struct bpacket *pkt) { int count; count = 0; while (pkt && pkt->buffer) { bpacket_queue_add(SUBGRP_PKTQ(dest), stream_dup(pkt->buffer), &pkt->arr); count++; pkt = bpacket_next(pkt); } bpacket_queue_sanity_check(SUBGRP_PKTQ(dest)); return count; } static int updgrp_prefix_list_update(struct update_group *updgrp, const char *name) { struct peer *peer; struct bgp_filter *filter; peer = UPDGRP_PEER(updgrp); filter = &peer->filter[UPDGRP_AFI(updgrp)][UPDGRP_SAFI(updgrp)]; if (PREFIX_LIST_OUT_NAME(filter) && (strcmp(name, PREFIX_LIST_OUT_NAME(filter)) == 0)) { PREFIX_LIST_OUT(filter) = prefix_list_lookup( UPDGRP_AFI(updgrp), PREFIX_LIST_OUT_NAME(filter)); return 1; } return 0; } static int updgrp_filter_list_update(struct update_group *updgrp, const char *name) { struct peer *peer; struct bgp_filter *filter; peer = UPDGRP_PEER(updgrp); filter = &peer->filter[UPDGRP_AFI(updgrp)][UPDGRP_SAFI(updgrp)]; if (FILTER_LIST_OUT_NAME(filter) && (strcmp(name, FILTER_LIST_OUT_NAME(filter)) == 0)) { FILTER_LIST_OUT(filter) = as_list_lookup(FILTER_LIST_OUT_NAME(filter)); return 1; } return 0; } static int updgrp_distribute_list_update(struct update_group *updgrp, const char *name) { struct peer *peer; struct bgp_filter *filter; peer = UPDGRP_PEER(updgrp); filter = &peer->filter[UPDGRP_AFI(updgrp)][UPDGRP_SAFI(updgrp)]; if (DISTRIBUTE_OUT_NAME(filter) && (strcmp(name, DISTRIBUTE_OUT_NAME(filter)) == 0)) { DISTRIBUTE_OUT(filter) = access_list_lookup( UPDGRP_AFI(updgrp), DISTRIBUTE_OUT_NAME(filter)); return 1; } return 0; } static int updgrp_route_map_update(struct update_group *updgrp, const char *name, int *def_rmap_changed) { struct peer *peer; struct bgp_filter *filter; int changed = 0; afi_t afi; safi_t safi; peer = UPDGRP_PEER(updgrp); afi = UPDGRP_AFI(updgrp); safi = UPDGRP_SAFI(updgrp); filter = &peer->filter[afi][safi]; if (ROUTE_MAP_OUT_NAME(filter) && (strcmp(name, ROUTE_MAP_OUT_NAME(filter)) == 0)) { ROUTE_MAP_OUT(filter) = route_map_lookup_by_name(name); changed = 1; } if (UNSUPPRESS_MAP_NAME(filter) && (strcmp(name, UNSUPPRESS_MAP_NAME(filter)) == 0)) { UNSUPPRESS_MAP(filter) = route_map_lookup_by_name(name); changed = 1; } /* process default-originate route-map */ if (peer->default_rmap[afi][safi].name && (strcmp(name, peer->default_rmap[afi][safi].name) == 0)) { peer->default_rmap[afi][safi].map = route_map_lookup_by_name(name); if (def_rmap_changed) *def_rmap_changed = 1; } return changed; } /* * hash iteration callback function to process a policy change for an * update group. Check if the changed policy matches the updgrp's * outbound route-map or unsuppress-map or default-originate map or * filter-list or prefix-list or distribute-list. * Trigger update generation accordingly. */ static int updgrp_policy_update_walkcb(struct update_group *updgrp, void *arg) { struct updwalk_context *ctx = arg; struct update_subgroup *subgrp; int changed = 0; int def_changed = 0; if (!updgrp || !ctx || !ctx->policy_name) return UPDWALK_CONTINUE; switch (ctx->policy_type) { case BGP_POLICY_ROUTE_MAP: changed = updgrp_route_map_update(updgrp, ctx->policy_name, &def_changed); break; case BGP_POLICY_FILTER_LIST: changed = updgrp_filter_list_update(updgrp, ctx->policy_name); break; case BGP_POLICY_PREFIX_LIST: changed = updgrp_prefix_list_update(updgrp, ctx->policy_name); break; case BGP_POLICY_DISTRIBUTE_LIST: changed = updgrp_distribute_list_update(updgrp, ctx->policy_name); break; default: break; } /* If not doing route update, return after updating "config" */ if (!ctx->policy_route_update) return UPDWALK_CONTINUE; /* If nothing has changed, return after updating "config" */ if (!changed && !def_changed) return UPDWALK_CONTINUE; /* * If something has changed, at the beginning of a route-map * modification * event, mark each subgroup's needs-refresh bit. For one, it signals to * whoever that the subgroup needs a refresh. Second, it prevents * premature * merge of this subgroup with another before a complete (outbound) * refresh. */ if (ctx->policy_event_start_flag) { UPDGRP_FOREACH_SUBGRP (updgrp, subgrp) { update_subgroup_set_needs_refresh(subgrp, 1); } return UPDWALK_CONTINUE; } UPDGRP_FOREACH_SUBGRP (updgrp, subgrp) { if (changed) { if (bgp_debug_update(NULL, NULL, updgrp, 0)) zlog_debug( "u%" PRIu64 ":s%" PRIu64 " announcing routes upon policy %s (type %d) change", updgrp->id, subgrp->id, ctx->policy_name, ctx->policy_type); subgroup_announce_route(subgrp); } if (def_changed) { if (bgp_debug_update(NULL, NULL, updgrp, 0)) zlog_debug( "u%" PRIu64 ":s%" PRIu64 " announcing default upon default routemap %s change", updgrp->id, subgrp->id, ctx->policy_name); subgroup_default_originate(subgrp, 0); } update_subgroup_set_needs_refresh(subgrp, 0); } return UPDWALK_CONTINUE; } static int update_group_walkcb(struct hash_bucket *bucket, void *arg) { struct update_group *updgrp = bucket->data; struct updwalk_context *wctx = arg; int ret = (*wctx->cb)(updgrp, wctx->context); return ret; } static int update_group_periodic_merge_walkcb(struct update_group *updgrp, void *arg) { struct update_subgroup *subgrp; struct update_subgroup *tmp_subgrp; const char *reason = arg; UPDGRP_FOREACH_SUBGRP_SAFE (updgrp, subgrp, tmp_subgrp) update_subgroup_check_merge(subgrp, reason); return UPDWALK_CONTINUE; } /******************** * PUBLIC FUNCTIONS ********************/ /* * trigger function when a policy (route-map/filter-list/prefix-list/ * distribute-list etc.) content changes. Go through all the * update groups and process the change. * * bgp: the bgp instance * ptype: the type of policy that got modified, see bgpd.h * pname: name of the policy * route_update: flag to control if an automatic update generation should * occur * start_event: flag that indicates if it's the beginning of the change. * Esp. when the user is changing the content interactively * over multiple statements. Useful to set dirty flag on * update groups. */ void update_group_policy_update(struct bgp *bgp, bgp_policy_type_e ptype, const char *pname, int route_update, int start_event) { struct updwalk_context ctx; memset(&ctx, 0, sizeof(ctx)); ctx.policy_type = ptype; ctx.policy_name = pname; ctx.policy_route_update = route_update; ctx.policy_event_start_flag = start_event; ctx.flags = 0; update_group_walk(bgp, updgrp_policy_update_walkcb, &ctx); } /* * update_subgroup_split_peer * * Ensure that the given peer is in a subgroup of its own in the * specified update group. */ void update_subgroup_split_peer(struct peer_af *paf, struct update_group *updgrp) { struct update_subgroup *old_subgrp, *subgrp; uint64_t old_id; old_subgrp = paf->subgroup; if (!updgrp) updgrp = old_subgrp->update_group; /* * If the peer is alone in its subgroup, reuse the existing * subgroup. */ if (old_subgrp->peer_count == 1) { if (updgrp == old_subgrp->update_group) return; subgrp = old_subgrp; old_id = old_subgrp->update_group->id; if (bgp_debug_peer_updout_enabled(paf->peer->host)) { UPDGRP_PEER_DBG_DIS(old_subgrp->update_group); } update_group_remove_subgroup(old_subgrp->update_group, old_subgrp); update_group_add_subgroup(updgrp, subgrp); if (bgp_debug_peer_updout_enabled(paf->peer->host)) { UPDGRP_PEER_DBG_EN(updgrp); } if (BGP_DEBUG(update_groups, UPDATE_GROUPS)) zlog_debug("u%" PRIu64 ":s%" PRIu64 " peer %s moved to u%" PRIu64 ":s%" PRIu64, old_id, subgrp->id, paf->peer->host, updgrp->id, subgrp->id); /* * The state of the subgroup (adj_out, advs, packet queue etc) * is consistent internally, but may not be identical to other * subgroups in the new update group even if the version number * matches up. Make sure a full refresh is done before the * subgroup is merged with another. */ update_subgroup_set_needs_refresh(subgrp, 1); SUBGRP_INCR_STAT(subgrp, updgrp_switch_events); return; } /* * Create a new subgroup under the specified update group, and copy * over relevant state to it. */ subgrp = update_subgroup_create(updgrp); update_subgroup_inherit_info(subgrp, old_subgrp); subgrp->split_from.update_group_id = old_subgrp->update_group->id; subgrp->split_from.subgroup_id = old_subgrp->id; /* * Copy out relevant state from the old subgroup. */ update_subgroup_copy_adj_out(paf->subgroup, subgrp); update_subgroup_copy_packets(subgrp, paf->next_pkt_to_send); if (BGP_DEBUG(update_groups, UPDATE_GROUPS)) zlog_debug("u%" PRIu64 ":s%" PRIu64 " peer %s split and moved into u%" PRIu64 ":s%" PRIu64, paf->subgroup->update_group->id, paf->subgroup->id, paf->peer->host, updgrp->id, subgrp->id); SUBGRP_INCR_STAT(paf->subgroup, split_events); /* * Since queued advs were left behind, this new subgroup needs a * refresh. */ update_subgroup_set_needs_refresh(subgrp, 1); /* * Remove peer from old subgroup, and add it to the new one. */ update_subgroup_remove_peer(paf->subgroup, paf); update_subgroup_add_peer(subgrp, paf, 1); } void update_bgp_group_init(struct bgp *bgp) { int afid; AF_FOREACH (afid) bgp->update_groups[afid] = hash_create(updgrp_hash_key_make, updgrp_hash_cmp, "BGP Update Group Hash"); } void update_bgp_group_free(struct bgp *bgp) { int afid; AF_FOREACH (afid) { if (bgp->update_groups[afid]) { hash_free(bgp->update_groups[afid]); bgp->update_groups[afid] = NULL; } } } void update_group_show(struct bgp *bgp, afi_t afi, safi_t safi, struct vty *vty, uint64_t subgrp_id) { struct updwalk_context ctx; memset(&ctx, 0, sizeof(ctx)); ctx.vty = vty; ctx.subgrp_id = subgrp_id; update_group_af_walk(bgp, afi, safi, update_group_show_walkcb, &ctx); } /* * update_group_show_stats * * Show global statistics about update groups. */ void update_group_show_stats(struct bgp *bgp, struct vty *vty) { vty_out(vty, "Update groups created: %u\n", bgp->update_group_stats.updgrps_created); vty_out(vty, "Update groups deleted: %u\n", bgp->update_group_stats.updgrps_deleted); vty_out(vty, "Update subgroups created: %u\n", bgp->update_group_stats.subgrps_created); vty_out(vty, "Update subgroups deleted: %u\n", bgp->update_group_stats.subgrps_deleted); vty_out(vty, "Join events: %u\n", bgp->update_group_stats.join_events); vty_out(vty, "Prune events: %u\n", bgp->update_group_stats.prune_events); vty_out(vty, "Merge events: %u\n", bgp->update_group_stats.merge_events); vty_out(vty, "Split events: %u\n", bgp->update_group_stats.split_events); vty_out(vty, "Update group switch events: %u\n", bgp->update_group_stats.updgrp_switch_events); vty_out(vty, "Peer route refreshes combined: %u\n", bgp->update_group_stats.peer_refreshes_combined); vty_out(vty, "Merge checks triggered: %u\n", bgp->update_group_stats.merge_checks_triggered); } /* * update_group_adjust_peer */ void update_group_adjust_peer(struct peer_af *paf) { struct update_group *updgrp; struct update_subgroup *subgrp, *old_subgrp; struct peer *peer; if (!paf) return; peer = PAF_PEER(paf); if (!peer_established(peer)) { return; } if (!CHECK_FLAG(peer->flags, PEER_FLAG_CONFIG_NODE)) { return; } if (!peer->afc_nego[paf->afi][paf->safi]) { return; } updgrp = update_group_find(paf); if (!updgrp) { updgrp = update_group_create(paf); if (!updgrp) { flog_err(EC_BGP_UPDGRP_CREATE, "couldn't create update group for peer %s", paf->peer->host); return; } } old_subgrp = paf->subgroup; if (old_subgrp) { /* * If the update group of the peer is unchanged, the peer can * stay * in its existing subgroup and we're done. */ if (old_subgrp->update_group == updgrp) return; /* * The peer is switching between update groups. Put it in its * own subgroup under the new update group. */ update_subgroup_split_peer(paf, updgrp); return; } subgrp = update_subgroup_find(updgrp, paf); if (!subgrp) { subgrp = update_subgroup_create(updgrp); if (!subgrp) return; } update_subgroup_add_peer(subgrp, paf, 1); if (BGP_DEBUG(update_groups, UPDATE_GROUPS)) zlog_debug("u%" PRIu64 ":s%" PRIu64 " add peer %s", updgrp->id, subgrp->id, paf->peer->host); return; } int update_group_adjust_soloness(struct peer *peer, int set) { struct peer_group *group; struct listnode *node, *nnode; if (!CHECK_FLAG(peer->sflags, PEER_STATUS_GROUP)) { peer_lonesoul_or_not(peer, set); if (peer->status == Established) bgp_announce_route_all(peer); } else { group = peer->group; for (ALL_LIST_ELEMENTS(group->peer, node, nnode, peer)) { peer_lonesoul_or_not(peer, set); if (peer->status == Established) bgp_announce_route_all(peer); } } return 0; } /* * update_subgroup_rib */ struct bgp_table *update_subgroup_rib(struct update_subgroup *subgrp) { struct bgp *bgp; bgp = SUBGRP_INST(subgrp); if (!bgp) return NULL; return bgp->rib[SUBGRP_AFI(subgrp)][SUBGRP_SAFI(subgrp)]; } void update_group_af_walk(struct bgp *bgp, afi_t afi, safi_t safi, updgrp_walkcb cb, void *ctx) { struct updwalk_context wctx; int afid; if (!bgp) return; afid = afindex(afi, safi); if (afid >= BGP_AF_MAX) return; memset(&wctx, 0, sizeof(wctx)); wctx.cb = cb; wctx.context = ctx; if (bgp->update_groups[afid]) hash_walk(bgp->update_groups[afid], update_group_walkcb, &wctx); } void update_group_walk(struct bgp *bgp, updgrp_walkcb cb, void *ctx) { afi_t afi; safi_t safi; FOREACH_AFI_SAFI (afi, safi) { update_group_af_walk(bgp, afi, safi, cb, ctx); } } void update_group_periodic_merge(struct bgp *bgp) { char reason[] = "periodic merge check"; update_group_walk(bgp, update_group_periodic_merge_walkcb, (void *)reason); } static int update_group_default_originate_route_map_walkcb(struct update_group *updgrp, void *arg) { struct update_subgroup *subgrp; struct peer *peer; afi_t afi; safi_t safi; UPDGRP_FOREACH_SUBGRP (updgrp, subgrp) { peer = SUBGRP_PEER(subgrp); afi = SUBGRP_AFI(subgrp); safi = SUBGRP_SAFI(subgrp); if (peer->default_rmap[afi][safi].name) { subgroup_default_originate(subgrp, 0); } } return UPDWALK_CONTINUE; } int update_group_refresh_default_originate_route_map(struct thread *thread) { struct bgp *bgp; char reason[] = "refresh default-originate route-map"; bgp = THREAD_ARG(thread); update_group_walk(bgp, update_group_default_originate_route_map_walkcb, reason); THREAD_TIMER_OFF(bgp->t_rmap_def_originate_eval); bgp_unlock(bgp); return (0); } /* * peer_af_announce_route * * Refreshes routes out to a peer_af immediately. * * If the combine parameter is true, then this function will try to * gather other peers in the subgroup for which a route announcement * is pending and efficently announce routes to all of them. * * For now, the 'combine' option has an effect only if all peers in * the subgroup have a route announcement pending. */ void peer_af_announce_route(struct peer_af *paf, int combine) { struct update_subgroup *subgrp; struct peer_af *cur_paf; int all_pending; subgrp = paf->subgroup; all_pending = 0; if (combine) { /* * If there are other peers in the old subgroup that also need * routes to be announced, pull them into the peer's new * subgroup. * Combine route announcement with other peers if possible. * * For now, we combine only if all peers in the subgroup have an * announcement pending. */ all_pending = 1; SUBGRP_FOREACH_PEER (subgrp, cur_paf) { if (cur_paf == paf) continue; if (cur_paf->t_announce_route) continue; all_pending = 0; break; } } /* * Announce to the peer alone if we were not asked to combine peers, * or if some peers don't have a route annoucement pending. */ if (!combine || !all_pending) { update_subgroup_split_peer(paf, NULL); subgrp = paf->subgroup; assert(subgrp && subgrp->update_group); if (bgp_debug_update(paf->peer, NULL, subgrp->update_group, 0)) zlog_debug("u%" PRIu64 ":s%" PRIu64 " %s announcing routes", subgrp->update_group->id, subgrp->id, paf->peer->host); subgroup_announce_route(paf->subgroup); return; } /* * We will announce routes the entire subgroup. * * First stop refresh timers on all the other peers. */ SUBGRP_FOREACH_PEER (subgrp, cur_paf) { if (cur_paf == paf) continue; bgp_stop_announce_route_timer(cur_paf); } if (bgp_debug_update(paf->peer, NULL, subgrp->update_group, 0)) zlog_debug("u%" PRIu64 ":s%" PRIu64 " announcing routes to %s, combined into %d peers", subgrp->update_group->id, subgrp->id, paf->peer->host, subgrp->peer_count); subgroup_announce_route(subgrp); SUBGRP_INCR_STAT_BY(subgrp, peer_refreshes_combined, subgrp->peer_count - 1); } void subgroup_trigger_write(struct update_subgroup *subgrp) { struct peer_af *paf; /* * For each peer in the subgroup, schedule a job to pull packets from * the subgroup output queue into their own output queue. This action * will trigger a write job on the I/O thread. */ SUBGRP_FOREACH_PEER (subgrp, paf) if (paf->peer->status == Established) thread_add_timer_msec( bm->master, bgp_generate_updgrp_packets, paf->peer, 0, &paf->peer->t_generate_updgrp_packets); } int update_group_clear_update_dbg(struct update_group *updgrp, void *arg) { UPDGRP_PEER_DBG_OFF(updgrp); return UPDWALK_CONTINUE; } /* Return true if we should addpath encode NLRI to this peer */ int bgp_addpath_encode_tx(struct peer *peer, afi_t afi, safi_t safi) { return (CHECK_FLAG(peer->af_cap[afi][safi], PEER_CAP_ADDPATH_AF_TX_ADV) && CHECK_FLAG(peer->af_cap[afi][safi], PEER_CAP_ADDPATH_AF_RX_RCV)); } frr-7.2.1/bgpd/bgp_updgrp.h0000644000000000000000000004650713610377563012446 00000000000000/** * bgp_updgrp.c: BGP update group structures * * @copyright Copyright (C) 2014 Cumulus Networks, Inc. * * @author Avneesh Sachdev * @author Rajesh Varadarajan * @author Pradosh Mohapatra * * This file is part of GNU Zebra. * * GNU Zebra is free software; you can redistribute it and/or modify it * under the terms of the GNU General Public License as published by the * Free Software Foundation; either version 2, or (at your option) any * later version. * * GNU Zebra 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; see the file COPYING; if not, write to the Free Software * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA */ #ifndef _QUAGGA_BGP_UPDGRP_H #define _QUAGGA_BGP_UPDGRP_H #include "bgp_advertise.h" /* * The following three heuristic constants determine how long advertisement to * a subgroup will be delayed after it is created. The intent is to allow * transient changes in peer state (primarily session establishment) to settle, * so that more peers can be grouped together and benefit from sharing * advertisement computations with the subgroup. * * These values have a very large impact on initial convergence time; any * changes should be accompanied by careful performance testing at all scales. * * The coalesce time 'C' for a new subgroup within a particular BGP instance * 'B' with total number of known peers 'P', established or not, is computed as * follows: * * C = MIN(BGP_MAX_SUBGROUP_COALESCE_TIME, * BGP_DEFAULT_SUBGROUP_COALESCE_TIME + * (P*BGP_PEER_ADJUST_SUBGROUP_COALESCE_TIME)) */ #define BGP_DEFAULT_SUBGROUP_COALESCE_TIME 1000 #define BGP_MAX_SUBGROUP_COALESCE_TIME 10000 #define BGP_PEER_ADJUST_SUBGROUP_COALESCE_TIME 50 #define PEER_UPDGRP_FLAGS \ (PEER_FLAG_LOCAL_AS_NO_PREPEND | PEER_FLAG_LOCAL_AS_REPLACE_AS) #define PEER_UPDGRP_AF_FLAGS \ (PEER_FLAG_SEND_COMMUNITY | PEER_FLAG_SEND_EXT_COMMUNITY \ | PEER_FLAG_SEND_LARGE_COMMUNITY \ | PEER_FLAG_DEFAULT_ORIGINATE | PEER_FLAG_REFLECTOR_CLIENT \ | PEER_FLAG_RSERVER_CLIENT | PEER_FLAG_NEXTHOP_SELF \ | PEER_FLAG_NEXTHOP_UNCHANGED | PEER_FLAG_FORCE_NEXTHOP_SELF \ | PEER_FLAG_AS_PATH_UNCHANGED | PEER_FLAG_MED_UNCHANGED \ | PEER_FLAG_NEXTHOP_LOCAL_UNCHANGED | PEER_FLAG_REMOVE_PRIVATE_AS \ | PEER_FLAG_REMOVE_PRIVATE_AS_ALL \ | PEER_FLAG_REMOVE_PRIVATE_AS_REPLACE \ | PEER_FLAG_REMOVE_PRIVATE_AS_ALL_REPLACE \ | PEER_FLAG_AS_OVERRIDE) #define PEER_UPDGRP_CAP_FLAGS (PEER_CAP_AS4_RCV) #define PEER_UPDGRP_AF_CAP_FLAGS \ (PEER_CAP_ORF_PREFIX_SM_RCV | PEER_CAP_ORF_PREFIX_SM_OLD_RCV \ | PEER_CAP_ADDPATH_AF_TX_ADV | PEER_CAP_ADDPATH_AF_RX_RCV \ | PEER_CAP_ENHE_AF_NEGO) typedef enum { BGP_ATTR_VEC_NH = 0, BGP_ATTR_VEC_MAX } bpacket_attr_vec_type; typedef struct { uint32_t flags; unsigned long offset; } bpacket_attr_vec; #define BPKT_ATTRVEC_FLAGS_UPDATED (1 << 0) #define BPKT_ATTRVEC_FLAGS_RMAP_NH_PEER_ADDRESS (1 << 1) #define BPKT_ATTRVEC_FLAGS_REFLECTED (1 << 2) #define BPKT_ATTRVEC_FLAGS_RMAP_NH_UNCHANGED (1 << 3) #define BPKT_ATTRVEC_FLAGS_RMAP_IPV4_NH_CHANGED (1 << 4) #define BPKT_ATTRVEC_FLAGS_RMAP_IPV6_GNH_CHANGED (1 << 5) #define BPKT_ATTRVEC_FLAGS_RMAP_IPV6_LNH_CHANGED (1 << 6) typedef struct bpacket_attr_vec_arr { bpacket_attr_vec entries[BGP_ATTR_VEC_MAX]; } bpacket_attr_vec_arr; struct bpacket { /* for being part of an update subgroup's message list */ TAILQ_ENTRY(bpacket) pkt_train; /* list of peers (well, peer_afs) that the packet needs to be sent to */ LIST_HEAD(pkt_peer_list, peer_af) peers; struct stream *buffer; bpacket_attr_vec_arr arr; unsigned int ver; }; struct bpacket_queue { TAILQ_HEAD(pkt_queue, bpacket) pkts; #if 0 /* A dummy packet that is used to thread all peers that have completed their work */ struct bpacket sentinel; #endif unsigned int conf_max_count; unsigned int curr_count; unsigned int hwm_count; unsigned int max_count_reached_count; }; struct update_group { /* back pointer to the BGP instance */ struct bgp *bgp; /* list of subgroups that belong to the update group */ LIST_HEAD(subgrp_list, update_subgroup) subgrps; /* lazy way to store configuration common to all peers hash function will compute from this data */ struct peer *conf; afi_t afi; safi_t safi; int afid; uint64_t id; time_t uptime; uint32_t join_events; uint32_t prune_events; uint32_t merge_events; uint32_t updgrp_switch_events; uint32_t peer_refreshes_combined; uint32_t adj_count; uint32_t split_events; uint32_t merge_checks_triggered; uint32_t subgrps_created; uint32_t subgrps_deleted; uint32_t num_dbg_en_peers; }; /* * Shorthand for a global statistics counter. */ #define UPDGRP_GLOBAL_STAT(updgrp, stat) \ ((updgrp)->bgp->update_group_stats.stat) /* * Add the given value to a counter on an update group and the bgp * instance. */ #define UPDGRP_INCR_STAT_BY(updgrp, stat, value) \ do { \ (updgrp)->stat += (value); \ UPDGRP_GLOBAL_STAT(updgrp, stat) += (value); \ } while (0) /* * Increment a counter on a update group and its parent structures. */ #define UPDGRP_INCR_STAT(subgrp, stat) UPDGRP_INCR_STAT_BY(subgrp, stat, 1) struct update_subgroup { /* back pointer to the parent update group */ struct update_group *update_group; /* list of peers that belong to the subgroup */ LIST_HEAD(peer_list, peer_af) peers; int peer_count; /* for being part of an update group's subgroup list */ LIST_ENTRY(update_subgroup) updgrp_train; struct bpacket_queue pkt_queue; /* * List of adj-out structures for this subgroup. * It essentially represents the snapshot of every prefix that * has been advertised to the members of the subgroup */ TAILQ_HEAD(adjout_queue, bgp_adj_out) adjq; /* packet buffer for update generation */ struct stream *work; /* We use a separate stream to encode MP_REACH_NLRI for efficient * NLRI packing. peer->obuf_work stores all the other attributes. The * actual packet is then constructed by concatenating the two. */ struct stream *scratch; /* synchronization list and time */ struct bgp_synchronize *sync; /* send prefix count */ unsigned long scount; /* announcement attribute hash */ struct hash *hash; struct thread *t_coalesce; uint32_t v_coalesce; struct thread *t_merge_check; /* table version that the subgroup has caught up to. */ uint64_t version; /* version maintained to record adj changes */ uint64_t adj_version; time_t uptime; /* * Identifying information about the subgroup that this subgroup was * split * from, if any. */ struct { uint64_t update_group_id; uint64_t subgroup_id; } split_from; uint32_t join_events; uint32_t prune_events; /* * This is bumped up when another subgroup merges into this one. */ uint32_t merge_events; uint32_t updgrp_switch_events; uint32_t peer_refreshes_combined; uint32_t adj_count; uint32_t split_events; uint32_t merge_checks_triggered; uint64_t id; uint16_t sflags; /* Subgroup flags, see below */ uint16_t flags; }; /* * We need to do an outbound refresh to get this subgroup into a * consistent state. */ #define SUBGRP_FLAG_NEEDS_REFRESH (1 << 0) #define SUBGRP_STATUS_DEFAULT_ORIGINATE (1 << 0) /* * Add the given value to the specified counter on a subgroup and its * parent structures. */ #define SUBGRP_INCR_STAT_BY(subgrp, stat, value) \ do { \ (subgrp)->stat += (value); \ if ((subgrp)->update_group) \ UPDGRP_INCR_STAT_BY((subgrp)->update_group, stat, \ value); \ } while (0) /* * Increment a counter on a subgroup and its parent structures. */ #define SUBGRP_INCR_STAT(subgrp, stat) SUBGRP_INCR_STAT_BY(subgrp, stat, 1) /* * Decrement a counter on a subgroup and its parent structures. */ #define SUBGRP_DECR_STAT(subgrp, stat) SUBGRP_INCR_STAT_BY(subgrp, stat, -1) typedef int (*updgrp_walkcb)(struct update_group *updgrp, void *ctx); /* really a private structure */ struct updwalk_context { struct vty *vty; struct bgp_node *rn; struct bgp_path_info *pi; uint64_t updgrp_id; uint64_t subgrp_id; bgp_policy_type_e policy_type; const char *policy_name; int policy_event_start_flag; int policy_route_update; updgrp_walkcb cb; void *context; uint8_t flags; #define UPDWALK_FLAGS_ADVQUEUE (1 << 0) #define UPDWALK_FLAGS_ADVERTISED (1 << 1) }; #define UPDWALK_CONTINUE HASHWALK_CONTINUE #define UPDWALK_ABORT HASHWALK_ABORT #define PAF_PEER(p) ((p)->peer) #define PAF_SUBGRP(p) ((p)->subgroup) #define PAF_UPDGRP(p) ((p)->subgroup->update_group) #define PAF_PKTQ(f) SUBGRP_PKTQ((f)->subgroup) #define UPDGRP_PEER(u) ((u)->conf) #define UPDGRP_AFI(u) ((u)->afi) #define UPDGRP_SAFI(u) ((u)->safi) #define UPDGRP_INST(u) ((u)->bgp) #define UPDGRP_AFFLAGS(u) ((u)->conf->af_flags[UPDGRP_AFI(u)][UPDGRP_SAFI(u)]) #define UPDGRP_DBG_ON(u) ((u)->num_dbg_en_peers) #define UPDGRP_PEER_DBG_EN(u) (((u)->num_dbg_en_peers)++) #define UPDGRP_PEER_DBG_DIS(u) (((u)->num_dbg_en_peers)--) #define UPDGRP_PEER_DBG_OFF(u) (u)->num_dbg_en_peers = 0 #define SUBGRP_AFI(s) UPDGRP_AFI((s)->update_group) #define SUBGRP_SAFI(s) UPDGRP_SAFI((s)->update_group) #define SUBGRP_PEER(s) UPDGRP_PEER((s)->update_group) #define SUBGRP_PCOUNT(s) ((s)->peer_count) #define SUBGRP_PFIRST(s) LIST_FIRST(&((s)->peers)) #define SUBGRP_PKTQ(s) &((s)->pkt_queue) #define SUBGRP_INST(s) UPDGRP_INST((s)->update_group) #define SUBGRP_AFFLAGS(s) UPDGRP_AFFLAGS((s)->update_group) #define SUBGRP_UPDGRP(s) ((s)->update_group) /* * Walk all subgroups in an update group. */ #define UPDGRP_FOREACH_SUBGRP(updgrp, subgrp) \ LIST_FOREACH (subgrp, &((updgrp)->subgrps), updgrp_train) #define UPDGRP_FOREACH_SUBGRP_SAFE(updgrp, subgrp, tmp_subgrp) \ LIST_FOREACH_SAFE (subgrp, &((updgrp)->subgrps), updgrp_train, \ tmp_subgrp) #define SUBGRP_FOREACH_PEER(subgrp, paf) \ LIST_FOREACH (paf, &(subgrp->peers), subgrp_train) #define SUBGRP_FOREACH_PEER_SAFE(subgrp, paf, temp_paf) \ LIST_FOREACH_SAFE (paf, &(subgrp->peers), subgrp_train, temp_paf) #define SUBGRP_FOREACH_ADJ(subgrp, adj) \ TAILQ_FOREACH (adj, &(subgrp->adjq), subgrp_adj_train) #define SUBGRP_FOREACH_ADJ_SAFE(subgrp, adj, adj_temp) \ TAILQ_FOREACH_SAFE (adj, &(subgrp->adjq), subgrp_adj_train, adj_temp) /* Prototypes. */ /* bgp_updgrp.c */ extern void update_bgp_group_init(struct bgp *); extern void udpate_bgp_group_free(struct bgp *); extern void update_group_show(struct bgp *bgp, afi_t afi, safi_t safi, struct vty *vty, uint64_t subgrp_id); extern void update_group_show_stats(struct bgp *bgp, struct vty *vty); extern void update_group_adjust_peer(struct peer_af *paf); extern int update_group_adjust_soloness(struct peer *peer, int set); extern void update_subgroup_remove_peer(struct update_subgroup *, struct peer_af *); extern struct bgp_table *update_subgroup_rib(struct update_subgroup *); extern void update_subgroup_split_peer(struct peer_af *, struct update_group *); extern int update_subgroup_check_merge(struct update_subgroup *, const char *); extern int update_subgroup_trigger_merge_check(struct update_subgroup *, int force); extern void update_group_policy_update(struct bgp *bgp, bgp_policy_type_e ptype, const char *pname, int route_update, int start_event); extern void update_group_af_walk(struct bgp *bgp, afi_t afi, safi_t safi, updgrp_walkcb cb, void *ctx); extern void update_group_walk(struct bgp *bgp, updgrp_walkcb cb, void *ctx); extern void update_group_periodic_merge(struct bgp *bgp); extern int update_group_refresh_default_originate_route_map(struct thread *thread); extern void update_group_start_advtimer(struct bgp *bgp); extern void update_subgroup_inherit_info(struct update_subgroup *to, struct update_subgroup *from); /* bgp_updgrp_packet.c */ extern struct bpacket *bpacket_alloc(void); extern void bpacket_free(struct bpacket *pkt); extern void bpacket_queue_init(struct bpacket_queue *q); extern void bpacket_queue_cleanup(struct bpacket_queue *q); extern void bpacket_queue_sanity_check(struct bpacket_queue *q); extern struct bpacket *bpacket_queue_add(struct bpacket_queue *q, struct stream *s, struct bpacket_attr_vec_arr *vecarr); struct bpacket *bpacket_queue_remove(struct bpacket_queue *q); extern struct bpacket *bpacket_queue_first(struct bpacket_queue *q); struct bpacket *bpacket_queue_last(struct bpacket_queue *q); unsigned int bpacket_queue_length(struct bpacket_queue *q); unsigned int bpacket_queue_hwm_length(struct bpacket_queue *q); int bpacket_queue_is_full(struct bgp *bgp, struct bpacket_queue *q); extern void bpacket_queue_advance_peer(struct peer_af *paf); extern void bpacket_queue_remove_peer(struct peer_af *paf); extern void bpacket_add_peer(struct bpacket *pkt, struct peer_af *paf); unsigned int bpacket_queue_virtual_length(struct peer_af *paf); extern void bpacket_queue_show_vty(struct bpacket_queue *q, struct vty *vty); int subgroup_packets_to_build(struct update_subgroup *subgrp); extern struct bpacket *subgroup_update_packet(struct update_subgroup *s); extern struct bpacket *subgroup_withdraw_packet(struct update_subgroup *s); extern struct stream *bpacket_reformat_for_peer(struct bpacket *pkt, struct peer_af *paf); extern void bpacket_attr_vec_arr_reset(struct bpacket_attr_vec_arr *vecarr); extern void bpacket_attr_vec_arr_set_vec(struct bpacket_attr_vec_arr *vecarr, bpacket_attr_vec_type type, struct stream *s, struct attr *attr); extern void subgroup_default_update_packet(struct update_subgroup *subgrp, struct attr *attr, struct peer *from); extern void subgroup_default_withdraw_packet(struct update_subgroup *subgrp); /* bgp_updgrp_adv.c */ extern struct bgp_advertise * bgp_advertise_clean_subgroup(struct update_subgroup *subgrp, struct bgp_adj_out *adj); extern void update_group_show_adj_queue(struct bgp *bgp, afi_t afi, safi_t safi, struct vty *vty, uint64_t id); extern void update_group_show_advertised(struct bgp *bgp, afi_t afi, safi_t safi, struct vty *vty, uint64_t id); extern void update_group_show_packet_queue(struct bgp *bgp, afi_t afi, safi_t safi, struct vty *vty, uint64_t id); extern void subgroup_announce_route(struct update_subgroup *subgrp); extern void subgroup_announce_all(struct update_subgroup *subgrp); extern void subgroup_default_originate(struct update_subgroup *subgrp, int withdraw); extern void group_announce_route(struct bgp *bgp, afi_t afi, safi_t safi, struct bgp_node *rn, struct bgp_path_info *pi); extern void subgroup_clear_table(struct update_subgroup *subgrp); extern void update_group_announce(struct bgp *bgp); extern void update_group_announce_rrclients(struct bgp *bgp); extern void peer_af_announce_route(struct peer_af *paf, int combine); extern struct bgp_adj_out *bgp_adj_out_alloc(struct update_subgroup *subgrp, struct bgp_node *rn, uint32_t addpath_tx_id); extern void bgp_adj_out_remove_subgroup(struct bgp_node *rn, struct bgp_adj_out *adj, struct update_subgroup *subgrp); extern void bgp_adj_out_set_subgroup(struct bgp_node *rn, struct update_subgroup *subgrp, struct attr *attr, struct bgp_path_info *path); extern void bgp_adj_out_unset_subgroup(struct bgp_node *rn, struct update_subgroup *subgrp, char withdraw, uint32_t addpath_tx_id); void subgroup_announce_table(struct update_subgroup *subgrp, struct bgp_table *table); extern void subgroup_trigger_write(struct update_subgroup *subgrp); extern int update_group_clear_update_dbg(struct update_group *updgrp, void *arg); extern void update_bgp_group_free(struct bgp *bgp); extern int bgp_addpath_encode_tx(struct peer *peer, afi_t afi, safi_t safi); /* * Inline functions */ /* * bpacket_queue_is_empty */ static inline int bpacket_queue_is_empty(struct bpacket_queue *queue) { /* * The packet queue is empty if it only contains a sentinel. */ if (queue->curr_count != 1) return 0; assert(bpacket_queue_first(queue)->buffer == NULL); return 1; } /* * bpacket_next * * Returns the packet after the given packet in a bpacket queue. */ static inline struct bpacket *bpacket_next(struct bpacket *pkt) { return TAILQ_NEXT(pkt, pkt_train); } /* * update_group_adjust_peer_afs * * Adjust all peer_af structures for the given peer. */ static inline void update_group_adjust_peer_afs(struct peer *peer) { struct peer_af *paf; int afidx; for (afidx = BGP_AF_START; afidx < BGP_AF_MAX; afidx++) { paf = peer->peer_af_array[afidx]; if (paf != NULL) update_group_adjust_peer(paf); } } /* * update_group_remove_peer_afs * * Remove all peer_af structures for the given peer from their subgroups. */ static inline void update_group_remove_peer_afs(struct peer *peer) { struct peer_af *paf; int afidx; for (afidx = BGP_AF_START; afidx < BGP_AF_MAX; afidx++) { paf = peer->peer_af_array[afidx]; if (paf != NULL) update_subgroup_remove_peer(PAF_SUBGRP(paf), paf); } } /* * update_subgroup_needs_refresh */ static inline int update_subgroup_needs_refresh(const struct update_subgroup *subgrp) { if (CHECK_FLAG(subgrp->flags, SUBGRP_FLAG_NEEDS_REFRESH)) return 1; else return 0; } /* * update_subgroup_set_needs_refresh */ static inline void update_subgroup_set_needs_refresh(struct update_subgroup *subgrp, int value) { if (value) SET_FLAG(subgrp->flags, SUBGRP_FLAG_NEEDS_REFRESH); else UNSET_FLAG(subgrp->flags, SUBGRP_FLAG_NEEDS_REFRESH); } static inline struct update_subgroup *peer_subgroup(struct peer *peer, afi_t afi, safi_t safi) { struct peer_af *paf; paf = peer_af_find(peer, afi, safi); if (paf) return PAF_SUBGRP(paf); return NULL; } /* * update_group_adjust_peer_afs * * Adjust all peer_af structures for the given peer. */ static inline void bgp_announce_peer(struct peer *peer) { struct peer_af *paf; int afidx; for (afidx = BGP_AF_START; afidx < BGP_AF_MAX; afidx++) { paf = peer->peer_af_array[afidx]; if (paf != NULL) subgroup_announce_all(PAF_SUBGRP(paf)); } } /** * advertise_list_is_empty */ static inline int advertise_list_is_empty(struct update_subgroup *subgrp) { if (bgp_adv_fifo_count(&subgrp->sync->update) || bgp_adv_fifo_count(&subgrp->sync->withdraw) || bgp_adv_fifo_count(&subgrp->sync->withdraw_low)) { return 0; } return 1; } #endif /* _QUAGGA_BGP_UPDGRP_H */ frr-7.2.1/bgpd/bgp_updgrp_adv.c0000644000000000000000000005417513610377563013273 00000000000000/** * bgp_updgrp_adv.c: BGP update group advertisement and adjacency * maintenance * * * @copyright Copyright (C) 2014 Cumulus Networks, Inc. * * @author Avneesh Sachdev * @author Rajesh Varadarajan * @author Pradosh Mohapatra * * This file is part of GNU Zebra. * * GNU Zebra is free software; you can redistribute it and/or modify it * under the terms of the GNU General Public License as published by the * Free Software Foundation; either version 2, or (at your option) any * later version. * * GNU Zebra 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; see the file COPYING; if not, write to the Free Software * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA */ #include #include "command.h" #include "memory.h" #include "prefix.h" #include "hash.h" #include "thread.h" #include "queue.h" #include "routemap.h" #include "filter.h" #include "bgpd/bgpd.h" #include "bgpd/bgp_table.h" #include "bgpd/bgp_debug.h" #include "bgpd/bgp_route.h" #include "bgpd/bgp_advertise.h" #include "bgpd/bgp_attr.h" #include "bgpd/bgp_aspath.h" #include "bgpd/bgp_packet.h" #include "bgpd/bgp_fsm.h" #include "bgpd/bgp_mplsvpn.h" #include "bgpd/bgp_updgrp.h" #include "bgpd/bgp_advertise.h" #include "bgpd/bgp_addpath.h" /******************** * PRIVATE FUNCTIONS ********************/ static int bgp_adj_out_compare(const struct bgp_adj_out *o1, const struct bgp_adj_out *o2) { if (o1->subgroup < o2->subgroup) return -1; if (o1->subgroup > o2->subgroup) return 1; if (o1->addpath_tx_id < o2->addpath_tx_id) return -1; if (o1->addpath_tx_id > o2->addpath_tx_id) return 1; return 0; } RB_GENERATE(bgp_adj_out_rb, bgp_adj_out, adj_entry, bgp_adj_out_compare); static inline struct bgp_adj_out *adj_lookup(struct bgp_node *rn, struct update_subgroup *subgrp, uint32_t addpath_tx_id) { struct bgp_adj_out lookup; if (!rn || !subgrp) return NULL; /* update-groups that do not support addpath will pass 0 for * addpath_tx_id. */ lookup.subgroup = subgrp; lookup.addpath_tx_id = addpath_tx_id; return RB_FIND(bgp_adj_out_rb, &rn->adj_out, &lookup); } static void adj_free(struct bgp_adj_out *adj) { TAILQ_REMOVE(&(adj->subgroup->adjq), adj, subgrp_adj_train); SUBGRP_DECR_STAT(adj->subgroup, adj_count); XFREE(MTYPE_BGP_ADJ_OUT, adj); } static void subgrp_withdraw_stale_addpath(struct updwalk_context *ctx, struct update_subgroup *subgrp) { struct bgp_adj_out *adj, *adj_next; uint32_t id; struct bgp_path_info *pi; afi_t afi = SUBGRP_AFI(subgrp); safi_t safi = SUBGRP_SAFI(subgrp); struct peer *peer = SUBGRP_PEER(subgrp); /* Look through all of the paths we have advertised for this rn and send * a withdraw for the ones that are no longer present */ RB_FOREACH_SAFE (adj, bgp_adj_out_rb, &ctx->rn->adj_out, adj_next) { if (adj->subgroup == subgrp) { for (pi = bgp_node_get_bgp_path_info(ctx->rn); pi; pi = pi->next) { id = bgp_addpath_id_for_peer(peer, afi, safi, &pi->tx_addpath); if (id == adj->addpath_tx_id) { break; } } if (!pi) { subgroup_process_announce_selected( subgrp, NULL, ctx->rn, adj->addpath_tx_id); } } } } static int group_announce_route_walkcb(struct update_group *updgrp, void *arg) { struct updwalk_context *ctx = arg; struct update_subgroup *subgrp; struct bgp_path_info *pi; afi_t afi; safi_t safi; struct peer *peer; struct bgp_adj_out *adj, *adj_next; int addpath_capable; afi = UPDGRP_AFI(updgrp); safi = UPDGRP_SAFI(updgrp); peer = UPDGRP_PEER(updgrp); addpath_capable = bgp_addpath_encode_tx(peer, afi, safi); if (BGP_DEBUG(update, UPDATE_OUT)) { char buf_prefix[PREFIX_STRLEN]; prefix2str(&ctx->rn->p, buf_prefix, sizeof(buf_prefix)); zlog_debug("%s: afi=%s, safi=%s, p=%s", __func__, afi2str(afi), safi2str(safi), buf_prefix); } UPDGRP_FOREACH_SUBGRP (updgrp, subgrp) { /* * Skip the subgroups that have coalesce timer running. We will * walk the entire prefix table for those subgroups when the * coalesce timer fires. */ if (!subgrp->t_coalesce) { /* An update-group that uses addpath */ if (addpath_capable) { subgrp_withdraw_stale_addpath(ctx, subgrp); for (pi = bgp_node_get_bgp_path_info(ctx->rn); pi; pi = pi->next) { /* Skip the bestpath for now */ if (pi == ctx->pi) continue; subgroup_process_announce_selected( subgrp, pi, ctx->rn, bgp_addpath_id_for_peer( peer, afi, safi, &pi->tx_addpath)); } /* Process the bestpath last so the "show [ip] * bgp neighbor x.x.x.x advertised" * output shows the attributes from the bestpath */ if (ctx->pi) subgroup_process_announce_selected( subgrp, ctx->pi, ctx->rn, bgp_addpath_id_for_peer( peer, afi, safi, &ctx->pi->tx_addpath)); } /* An update-group that does not use addpath */ else { if (ctx->pi) { subgroup_process_announce_selected( subgrp, ctx->pi, ctx->rn, bgp_addpath_id_for_peer( peer, afi, safi, &ctx->pi->tx_addpath)); } else { /* Find the addpath_tx_id of the path we * had advertised and * send a withdraw */ RB_FOREACH_SAFE (adj, bgp_adj_out_rb, &ctx->rn->adj_out, adj_next) { if (adj->subgroup == subgrp) { subgroup_process_announce_selected( subgrp, NULL, ctx->rn, adj->addpath_tx_id); } } } } } } return UPDWALK_CONTINUE; } static void subgrp_show_adjq_vty(struct update_subgroup *subgrp, struct vty *vty, uint8_t flags) { struct bgp_table *table; struct bgp_adj_out *adj; unsigned long output_count; struct bgp_node *rn; int header1 = 1; struct bgp *bgp; int header2 = 1; bgp = SUBGRP_INST(subgrp); if (!bgp) return; table = bgp->rib[SUBGRP_AFI(subgrp)][SUBGRP_SAFI(subgrp)]; output_count = 0; for (rn = bgp_table_top(table); rn; rn = bgp_route_next(rn)) RB_FOREACH (adj, bgp_adj_out_rb, &rn->adj_out) if (adj->subgroup == subgrp) { if (header1) { vty_out(vty, "BGP table version is %" PRIu64 ", local router ID is %s\n", table->version, inet_ntoa(bgp->router_id)); vty_out(vty, BGP_SHOW_SCODE_HEADER); vty_out(vty, BGP_SHOW_OCODE_HEADER); header1 = 0; } if (header2) { vty_out(vty, BGP_SHOW_HEADER); header2 = 0; } if ((flags & UPDWALK_FLAGS_ADVQUEUE) && adj->adv && adj->adv->baa) { route_vty_out_tmp(vty, &rn->p, adj->adv->baa->attr, SUBGRP_SAFI(subgrp), 0, NULL); output_count++; } if ((flags & UPDWALK_FLAGS_ADVERTISED) && adj->attr) { route_vty_out_tmp( vty, &rn->p, adj->attr, SUBGRP_SAFI(subgrp), 0, NULL); output_count++; } } if (output_count != 0) vty_out(vty, "\nTotal number of prefixes %ld\n", output_count); } static int updgrp_show_adj_walkcb(struct update_group *updgrp, void *arg) { struct updwalk_context *ctx = arg; struct update_subgroup *subgrp; struct vty *vty; vty = ctx->vty; UPDGRP_FOREACH_SUBGRP (updgrp, subgrp) { if (ctx->subgrp_id && (ctx->subgrp_id != subgrp->id)) continue; vty_out(vty, "update group %" PRIu64 ", subgroup %" PRIu64 "\n", updgrp->id, subgrp->id); subgrp_show_adjq_vty(subgrp, vty, ctx->flags); } return UPDWALK_CONTINUE; } static void updgrp_show_adj(struct bgp *bgp, afi_t afi, safi_t safi, struct vty *vty, uint64_t id, uint8_t flags) { struct updwalk_context ctx; memset(&ctx, 0, sizeof(ctx)); ctx.vty = vty; ctx.subgrp_id = id; ctx.flags = flags; update_group_af_walk(bgp, afi, safi, updgrp_show_adj_walkcb, &ctx); } static int subgroup_coalesce_timer(struct thread *thread) { struct update_subgroup *subgrp; subgrp = THREAD_ARG(thread); if (bgp_debug_update(NULL, NULL, subgrp->update_group, 0)) zlog_debug("u%" PRIu64 ":s%" PRIu64 " announcing routes upon coalesce timer expiry", (SUBGRP_UPDGRP(subgrp))->id, subgrp->id); subgrp->t_coalesce = NULL; subgrp->v_coalesce = 0; subgroup_announce_route(subgrp); /* While the announce_route() may kick off the route advertisement timer * for * the members of the subgroup, we'd like to send the initial updates * much * faster (i.e., without enforcing MRAI). Also, if there were no routes * to * announce, this is the method currently employed to trigger the EOR. */ if (!bgp_update_delay_active(SUBGRP_INST(subgrp))) { struct peer_af *paf; struct peer *peer; SUBGRP_FOREACH_PEER (subgrp, paf) { peer = PAF_PEER(paf); BGP_TIMER_OFF(peer->t_routeadv); BGP_TIMER_ON(peer->t_routeadv, bgp_routeadv_timer, 0); } } return 0; } static int update_group_announce_walkcb(struct update_group *updgrp, void *arg) { struct update_subgroup *subgrp; UPDGRP_FOREACH_SUBGRP (updgrp, subgrp) { subgroup_announce_all(subgrp); } return UPDWALK_CONTINUE; } static int update_group_announce_rrc_walkcb(struct update_group *updgrp, void *arg) { struct update_subgroup *subgrp; afi_t afi; safi_t safi; struct peer *peer; afi = UPDGRP_AFI(updgrp); safi = UPDGRP_SAFI(updgrp); peer = UPDGRP_PEER(updgrp); /* Only announce if this is a group of route-reflector-clients */ if (CHECK_FLAG(peer->af_flags[afi][safi], PEER_FLAG_REFLECTOR_CLIENT)) { UPDGRP_FOREACH_SUBGRP (updgrp, subgrp) { subgroup_announce_all(subgrp); } } return UPDWALK_CONTINUE; } /******************** * PUBLIC FUNCTIONS ********************/ /** * Allocate an adj-out object. Do proper initialization of its fields, * primarily its association with the subgroup and the prefix. */ struct bgp_adj_out *bgp_adj_out_alloc(struct update_subgroup *subgrp, struct bgp_node *rn, uint32_t addpath_tx_id) { struct bgp_adj_out *adj; adj = XCALLOC(MTYPE_BGP_ADJ_OUT, sizeof(struct bgp_adj_out)); adj->subgroup = subgrp; adj->addpath_tx_id = addpath_tx_id; if (rn) { RB_INSERT(bgp_adj_out_rb, &rn->adj_out, adj); bgp_lock_node(rn); adj->rn = rn; } TAILQ_INSERT_TAIL(&(subgrp->adjq), adj, subgrp_adj_train); SUBGRP_INCR_STAT(subgrp, adj_count); return adj; } struct bgp_advertise * bgp_advertise_clean_subgroup(struct update_subgroup *subgrp, struct bgp_adj_out *adj) { struct bgp_advertise *adv; struct bgp_advertise_attr *baa; struct bgp_advertise *next; struct bgp_adv_fifo_head *fhead; adv = adj->adv; baa = adv->baa; next = NULL; if (baa) { fhead = &subgrp->sync->update; /* Unlink myself from advertise attribute FIFO. */ bgp_advertise_delete(baa, adv); /* Fetch next advertise candidate. */ next = baa->adv; /* Unintern BGP advertise attribute. */ bgp_advertise_unintern(subgrp->hash, baa); } else fhead = &subgrp->sync->withdraw; /* Unlink myself from advertisement FIFO. */ bgp_adv_fifo_del(fhead, adv); /* Free memory. */ bgp_advertise_free(adj->adv); adj->adv = NULL; return next; } void bgp_adj_out_set_subgroup(struct bgp_node *rn, struct update_subgroup *subgrp, struct attr *attr, struct bgp_path_info *path) { struct bgp_adj_out *adj = NULL; struct bgp_advertise *adv; struct peer *peer; afi_t afi; safi_t safi; peer = SUBGRP_PEER(subgrp); afi = SUBGRP_AFI(subgrp); safi = SUBGRP_SAFI(subgrp); if (DISABLE_BGP_ANNOUNCE) return; /* Look for adjacency information. */ adj = adj_lookup( rn, subgrp, bgp_addpath_id_for_peer(peer, afi, safi, &path->tx_addpath)); if (!adj) { adj = bgp_adj_out_alloc( subgrp, rn, bgp_addpath_id_for_peer(peer, afi, safi, &path->tx_addpath)); if (!adj) return; } if (adj->adv) bgp_advertise_clean_subgroup(subgrp, adj); adj->adv = bgp_advertise_new(); adv = adj->adv; adv->rn = rn; assert(adv->pathi == NULL); /* bgp_path_info adj_out reference */ adv->pathi = bgp_path_info_lock(path); if (attr) adv->baa = bgp_advertise_intern(subgrp->hash, attr); else adv->baa = baa_new(); adv->adj = adj; /* Add new advertisement to advertisement attribute list. */ bgp_advertise_add(adv->baa, adv); /* * If the update adv list is empty, trigger the member peers' * mrai timers so the socket writes can happen. */ if (!bgp_adv_fifo_count(&subgrp->sync->update)) { struct peer_af *paf; SUBGRP_FOREACH_PEER (subgrp, paf) { bgp_adjust_routeadv(PAF_PEER(paf)); } } bgp_adv_fifo_add_tail(&subgrp->sync->update, adv); subgrp->version = max(subgrp->version, rn->version); } /* The only time 'withdraw' will be false is if we are sending * the "neighbor x.x.x.x default-originate" default and need to clear * bgp_adj_out for the 0.0.0.0/0 route in the BGP table. */ void bgp_adj_out_unset_subgroup(struct bgp_node *rn, struct update_subgroup *subgrp, char withdraw, uint32_t addpath_tx_id) { struct bgp_adj_out *adj; struct bgp_advertise *adv; bool trigger_write; if (DISABLE_BGP_ANNOUNCE) return; /* Lookup existing adjacency */ if ((adj = adj_lookup(rn, subgrp, addpath_tx_id)) != NULL) { /* Clean up previous advertisement. */ if (adj->adv) bgp_advertise_clean_subgroup(subgrp, adj); if (adj->attr && withdraw) { /* We need advertisement structure. */ adj->adv = bgp_advertise_new(); adv = adj->adv; adv->rn = rn; adv->adj = adj; /* Note if we need to trigger a packet write */ trigger_write = !bgp_adv_fifo_count(&subgrp->sync->withdraw); /* Add to synchronization entry for withdraw * announcement. */ bgp_adv_fifo_add_tail(&subgrp->sync->withdraw, adv); if (trigger_write) subgroup_trigger_write(subgrp); } else { /* Remove myself from adjacency. */ RB_REMOVE(bgp_adj_out_rb, &rn->adj_out, adj); /* Free allocated information. */ adj_free(adj); bgp_unlock_node(rn); } } subgrp->version = max(subgrp->version, rn->version); } void bgp_adj_out_remove_subgroup(struct bgp_node *rn, struct bgp_adj_out *adj, struct update_subgroup *subgrp) { if (adj->attr) bgp_attr_unintern(&adj->attr); if (adj->adv) bgp_advertise_clean_subgroup(subgrp, adj); RB_REMOVE(bgp_adj_out_rb, &rn->adj_out, adj); adj_free(adj); } /* * Go through all the routes and clean up the adj/adv structures corresponding * to the subgroup. */ void subgroup_clear_table(struct update_subgroup *subgrp) { struct bgp_adj_out *aout, *taout; SUBGRP_FOREACH_ADJ_SAFE (subgrp, aout, taout) { struct bgp_node *rn = aout->rn; bgp_adj_out_remove_subgroup(rn, aout, subgrp); bgp_unlock_node(rn); } } /* * subgroup_announce_table */ void subgroup_announce_table(struct update_subgroup *subgrp, struct bgp_table *table) { struct bgp_node *rn; struct bgp_path_info *ri; struct attr attr; struct peer *peer; afi_t afi; safi_t safi; int addpath_capable; peer = SUBGRP_PEER(subgrp); afi = SUBGRP_AFI(subgrp); safi = SUBGRP_SAFI(subgrp); addpath_capable = bgp_addpath_encode_tx(peer, afi, safi); if (safi == SAFI_LABELED_UNICAST) safi = SAFI_UNICAST; if (!table) table = peer->bgp->rib[afi][safi]; if (safi != SAFI_MPLS_VPN && safi != SAFI_ENCAP && safi != SAFI_EVPN && CHECK_FLAG(peer->af_flags[afi][safi], PEER_FLAG_DEFAULT_ORIGINATE)) subgroup_default_originate(subgrp, 0); for (rn = bgp_table_top(table); rn; rn = bgp_route_next(rn)) for (ri = bgp_node_get_bgp_path_info(rn); ri; ri = ri->next) if (CHECK_FLAG(ri->flags, BGP_PATH_SELECTED) || (addpath_capable && bgp_addpath_tx_path( peer->addpath_type[afi][safi], ri))) { if (subgroup_announce_check(rn, ri, subgrp, &rn->p, &attr)) bgp_adj_out_set_subgroup(rn, subgrp, &attr, ri); else bgp_adj_out_unset_subgroup( rn, subgrp, 1, bgp_addpath_id_for_peer( peer, afi, safi, &ri->tx_addpath)); } /* * We walked through the whole table -- make sure our version number * is consistent with the one on the table. This should allow * subgroups to merge sooner if a peer comes up when the route node * with the largest version is no longer in the table. This also * covers the pathological case where all routes in the table have * now been deleted. */ subgrp->version = max(subgrp->version, table->version); /* * Start a task to merge the subgroup if necessary. */ update_subgroup_trigger_merge_check(subgrp, 0); } /* * subgroup_announce_route * * Refresh all routes out to a subgroup. */ void subgroup_announce_route(struct update_subgroup *subgrp) { struct bgp_node *rn; struct bgp_table *table; struct peer *onlypeer; if (update_subgroup_needs_refresh(subgrp)) { update_subgroup_set_needs_refresh(subgrp, 0); } /* * First update is deferred until ORF or ROUTE-REFRESH is received */ onlypeer = ((SUBGRP_PCOUNT(subgrp) == 1) ? (SUBGRP_PFIRST(subgrp))->peer : NULL); if (onlypeer && CHECK_FLAG(onlypeer->af_sflags[SUBGRP_AFI(subgrp)] [SUBGRP_SAFI(subgrp)], PEER_STATUS_ORF_WAIT_REFRESH)) return; if (SUBGRP_SAFI(subgrp) != SAFI_MPLS_VPN && SUBGRP_SAFI(subgrp) != SAFI_ENCAP && SUBGRP_SAFI(subgrp) != SAFI_EVPN) subgroup_announce_table(subgrp, NULL); else for (rn = bgp_table_top(update_subgroup_rib(subgrp)); rn; rn = bgp_route_next(rn)) { table = bgp_node_get_bgp_table_info(rn); if (!table) continue; subgroup_announce_table(subgrp, table); } } void subgroup_default_originate(struct update_subgroup *subgrp, int withdraw) { struct bgp *bgp; struct attr attr; struct aspath *aspath; struct bgp_path_info tmp_info; struct prefix p; struct peer *from; struct bgp_node *rn; struct bgp_path_info *ri; struct peer *peer; route_map_result_t ret = RMAP_DENYMATCH; afi_t afi; safi_t safi; if (!subgrp) return; peer = SUBGRP_PEER(subgrp); afi = SUBGRP_AFI(subgrp); safi = SUBGRP_SAFI(subgrp); if (!(afi == AFI_IP || afi == AFI_IP6)) return; bgp = peer->bgp; from = bgp->peer_self; bgp_attr_default_set(&attr, BGP_ORIGIN_IGP); aspath = attr.aspath; attr.local_pref = bgp->default_local_pref; memset(&p, 0, sizeof(p)); p.family = afi2family(afi); p.prefixlen = 0; if ((afi == AFI_IP6) || peer_cap_enhe(peer, afi, safi)) { /* IPv6 global nexthop must be included. */ attr.mp_nexthop_len = BGP_ATTR_NHLEN_IPV6_GLOBAL; /* If the peer is on shared nextwork and we have link-local nexthop set it. */ if (peer->shared_network && !IN6_IS_ADDR_UNSPECIFIED(&peer->nexthop.v6_local)) attr.mp_nexthop_len = BGP_ATTR_NHLEN_IPV6_GLOBAL_AND_LL; } if (peer->default_rmap[afi][safi].name) { SET_FLAG(bgp->peer_self->rmap_type, PEER_RMAP_TYPE_DEFAULT); for (rn = bgp_table_top(bgp->rib[afi][safi]); rn; rn = bgp_route_next(rn)) { for (ri = bgp_node_get_bgp_path_info(rn); ri; ri = ri->next) { struct attr dummy_attr; /* Provide dummy so the route-map can't modify * the attributes */ dummy_attr = *ri->attr; tmp_info.peer = ri->peer; tmp_info.attr = &dummy_attr; ret = route_map_apply( peer->default_rmap[afi][safi].map, &rn->p, RMAP_BGP, &tmp_info); /* The route map might have set attributes. If * we don't flush them * here, they will be leaked. */ bgp_attr_flush(&dummy_attr); if (ret != RMAP_DENYMATCH) break; } if (ret != RMAP_DENYMATCH) break; } bgp->peer_self->rmap_type = 0; if (ret == RMAP_DENYMATCH) withdraw = 1; } if (withdraw) { if (CHECK_FLAG(subgrp->sflags, SUBGRP_STATUS_DEFAULT_ORIGINATE)) subgroup_default_withdraw_packet(subgrp); UNSET_FLAG(subgrp->sflags, SUBGRP_STATUS_DEFAULT_ORIGINATE); } else { if (!CHECK_FLAG(subgrp->sflags, SUBGRP_STATUS_DEFAULT_ORIGINATE)) { if (bgp_flag_check(bgp, BGP_FLAG_GRACEFUL_SHUTDOWN)) { bgp_attr_add_gshut_community(&attr); } SET_FLAG(subgrp->sflags, SUBGRP_STATUS_DEFAULT_ORIGINATE); subgroup_default_update_packet(subgrp, &attr, from); /* The 'neighbor x.x.x.x default-originate' default will * act as an * implicit withdraw for any previous UPDATEs sent for * 0.0.0.0/0 so * clear adj_out for the 0.0.0.0/0 prefix in the BGP * table. */ memset(&p, 0, sizeof(p)); p.family = afi2family(afi); p.prefixlen = 0; rn = bgp_afi_node_get(bgp->rib[afi][safi], afi, safi, &p, NULL); bgp_adj_out_unset_subgroup( rn, subgrp, 0, BGP_ADDPATH_TX_ID_FOR_DEFAULT_ORIGINATE); } } aspath_unintern(&aspath); } /* * Announce the BGP table to a subgroup. * * At startup, we try to optimize route announcement by coalescing the * peer-up events. This is done only the first time - from then on, * subgrp->v_coalesce will be set to zero and the normal logic * prevails. */ void subgroup_announce_all(struct update_subgroup *subgrp) { if (!subgrp) return; /* * If coalesce timer value is not set, announce routes immediately. */ if (!subgrp->v_coalesce) { if (bgp_debug_update(NULL, NULL, subgrp->update_group, 0)) zlog_debug("u%" PRIu64 ":s%" PRIu64 " announcing all routes", subgrp->update_group->id, subgrp->id); subgroup_announce_route(subgrp); return; } /* * We should wait for the coalesce timer. Arm the timer if not done. */ if (!subgrp->t_coalesce) { thread_add_timer_msec(bm->master, subgroup_coalesce_timer, subgrp, subgrp->v_coalesce, &subgrp->t_coalesce); } } /* * Go through all update subgroups and set up the adv queue for the * input route. */ void group_announce_route(struct bgp *bgp, afi_t afi, safi_t safi, struct bgp_node *rn, struct bgp_path_info *pi) { struct updwalk_context ctx; ctx.pi = pi; ctx.rn = rn; update_group_af_walk(bgp, afi, safi, group_announce_route_walkcb, &ctx); } void update_group_show_adj_queue(struct bgp *bgp, afi_t afi, safi_t safi, struct vty *vty, uint64_t id) { updgrp_show_adj(bgp, afi, safi, vty, id, UPDWALK_FLAGS_ADVQUEUE); } void update_group_show_advertised(struct bgp *bgp, afi_t afi, safi_t safi, struct vty *vty, uint64_t id) { updgrp_show_adj(bgp, afi, safi, vty, id, UPDWALK_FLAGS_ADVERTISED); } void update_group_announce(struct bgp *bgp) { update_group_walk(bgp, update_group_announce_walkcb, NULL); } void update_group_announce_rrclients(struct bgp *bgp) { update_group_walk(bgp, update_group_announce_rrc_walkcb, NULL); } frr-7.2.1/bgpd/bgp_updgrp_packet.c0000644000000000000000000010226013610377563013755 00000000000000/** * bgp_updgrp_packet.c: BGP update group packet handling routines * * @copyright Copyright (C) 2014 Cumulus Networks, Inc. * * @author Avneesh Sachdev * @author Rajesh Varadarajan * @author Pradosh Mohapatra * * This file is part of GNU Zebra. * * GNU Zebra is free software; you can redistribute it and/or modify it * under the terms of the GNU General Public License as published by the * Free Software Foundation; either version 2, or (at your option) any * later version. * * GNU Zebra 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; see the file COPYING; if not, write to the Free Software * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA */ #include #include "prefix.h" #include "thread.h" #include "buffer.h" #include "stream.h" #include "command.h" #include "sockunion.h" #include "network.h" #include "memory.h" #include "filter.h" #include "routemap.h" #include "log.h" #include "plist.h" #include "linklist.h" #include "workqueue.h" #include "hash.h" #include "queue.h" #include "mpls.h" #include "bgpd/bgpd.h" #include "bgpd/bgp_debug.h" #include "bgpd/bgp_errors.h" #include "bgpd/bgp_fsm.h" #include "bgpd/bgp_route.h" #include "bgpd/bgp_packet.h" #include "bgpd/bgp_advertise.h" #include "bgpd/bgp_updgrp.h" #include "bgpd/bgp_nexthop.h" #include "bgpd/bgp_nht.h" #include "bgpd/bgp_mplsvpn.h" #include "bgpd/bgp_label.h" #include "bgpd/bgp_addpath.h" /******************** * PRIVATE FUNCTIONS ********************/ /******************** * PUBLIC FUNCTIONS ********************/ struct bpacket *bpacket_alloc(void) { struct bpacket *pkt; pkt = XCALLOC(MTYPE_BGP_PACKET, sizeof(struct bpacket)); return pkt; } void bpacket_free(struct bpacket *pkt) { if (pkt->buffer) stream_free(pkt->buffer); pkt->buffer = NULL; XFREE(MTYPE_BGP_PACKET, pkt); } void bpacket_queue_init(struct bpacket_queue *q) { TAILQ_INIT(&(q->pkts)); } /* * bpacket_queue_sanity_check */ void bpacket_queue_sanity_check(struct bpacket_queue __attribute__((__unused__)) * q) { #if 0 struct bpacket *pkt; pkt = bpacket_queue_last (q); assert (pkt); assert (!pkt->buffer); /* * Make sure the count of packets is correct. */ int num_pkts = 0; pkt = bpacket_queue_first (q); while (pkt) { num_pkts++; if (num_pkts > q->curr_count) assert (0); pkt = TAILQ_NEXT (pkt, pkt_train); } assert (num_pkts == q->curr_count); #endif } /* * bpacket_queue_add_packet * * Internal function of bpacket_queue - and adds a * packet entry to the end of the list. * * Users of bpacket_queue should use bpacket_queue_add instead. */ static void bpacket_queue_add_packet(struct bpacket_queue *q, struct bpacket *pkt) { struct bpacket *last_pkt; if (TAILQ_EMPTY(&(q->pkts))) TAILQ_INSERT_TAIL(&(q->pkts), pkt, pkt_train); else { last_pkt = bpacket_queue_last(q); TAILQ_INSERT_AFTER(&(q->pkts), last_pkt, pkt, pkt_train); } q->curr_count++; if (q->hwm_count < q->curr_count) q->hwm_count = q->curr_count; } /* * Adds a packet to the bpacket_queue. * * The stream passed is consumed by this function. So, the caller should * not free or use the stream after * invoking this function. */ struct bpacket *bpacket_queue_add(struct bpacket_queue *q, struct stream *s, struct bpacket_attr_vec_arr *vecarrp) { struct bpacket *pkt; struct bpacket *last_pkt; pkt = bpacket_alloc(); if (TAILQ_EMPTY(&(q->pkts))) { pkt->ver = 1; pkt->buffer = s; if (vecarrp) memcpy(&pkt->arr, vecarrp, sizeof(struct bpacket_attr_vec_arr)); else bpacket_attr_vec_arr_reset(&pkt->arr); bpacket_queue_add_packet(q, pkt); bpacket_queue_sanity_check(q); return pkt; } /* * Fill in the new information into the current sentinel and create a * new sentinel. */ bpacket_queue_sanity_check(q); last_pkt = bpacket_queue_last(q); assert(last_pkt->buffer == NULL); last_pkt->buffer = s; if (vecarrp) memcpy(&last_pkt->arr, vecarrp, sizeof(struct bpacket_attr_vec_arr)); else bpacket_attr_vec_arr_reset(&last_pkt->arr); pkt->ver = last_pkt->ver; pkt->ver++; bpacket_queue_add_packet(q, pkt); bpacket_queue_sanity_check(q); return last_pkt; } struct bpacket *bpacket_queue_first(struct bpacket_queue *q) { return (TAILQ_FIRST(&(q->pkts))); } struct bpacket *bpacket_queue_last(struct bpacket_queue *q) { return TAILQ_LAST(&(q->pkts), pkt_queue); } struct bpacket *bpacket_queue_remove(struct bpacket_queue *q) { struct bpacket *first; first = bpacket_queue_first(q); if (first) { TAILQ_REMOVE(&(q->pkts), first, pkt_train); q->curr_count--; } return first; } unsigned int bpacket_queue_length(struct bpacket_queue *q) { return q->curr_count - 1; } unsigned int bpacket_queue_hwm_length(struct bpacket_queue *q) { return q->hwm_count - 1; } int bpacket_queue_is_full(struct bgp *bgp, struct bpacket_queue *q) { if (q->curr_count >= bgp->default_subgroup_pkt_queue_max) return 1; return 0; } void bpacket_add_peer(struct bpacket *pkt, struct peer_af *paf) { if (!pkt || !paf) return; LIST_INSERT_HEAD(&(pkt->peers), paf, pkt_train); paf->next_pkt_to_send = pkt; } /* * bpacket_queue_cleanup */ void bpacket_queue_cleanup(struct bpacket_queue *q) { struct bpacket *pkt; while ((pkt = bpacket_queue_remove(q))) { bpacket_free(pkt); } } /* * bpacket_queue_compact * * Delete packets that do not need to be transmitted to any peer from * the queue. * * @return the number of packets deleted. */ static int bpacket_queue_compact(struct bpacket_queue *q) { int num_deleted; struct bpacket *pkt, *removed_pkt; num_deleted = 0; while (1) { pkt = bpacket_queue_first(q); if (!pkt) break; /* * Don't delete the sentinel. */ if (!pkt->buffer) break; if (!LIST_EMPTY(&(pkt->peers))) break; removed_pkt = bpacket_queue_remove(q); assert(pkt == removed_pkt); bpacket_free(removed_pkt); num_deleted++; } bpacket_queue_sanity_check(q); return num_deleted; } void bpacket_queue_advance_peer(struct peer_af *paf) { struct bpacket *pkt; struct bpacket *old_pkt; old_pkt = paf->next_pkt_to_send; if (old_pkt->buffer == NULL) /* Already at end of list */ return; LIST_REMOVE(paf, pkt_train); pkt = TAILQ_NEXT(old_pkt, pkt_train); bpacket_add_peer(pkt, paf); if (!bpacket_queue_compact(PAF_PKTQ(paf))) return; /* * Deleted one or more packets. Check if we can now merge this * peer's subgroup into another subgroup. */ update_subgroup_check_merge(paf->subgroup, "advanced peer in queue"); } /* * bpacket_queue_remove_peer * * Remove the peer from the packet queue of the subgroup it belongs * to. */ void bpacket_queue_remove_peer(struct peer_af *paf) { struct bpacket_queue *q; q = PAF_PKTQ(paf); assert(q); if (!q) return; LIST_REMOVE(paf, pkt_train); paf->next_pkt_to_send = NULL; bpacket_queue_compact(q); } unsigned int bpacket_queue_virtual_length(struct peer_af *paf) { struct bpacket *pkt; struct bpacket *last; struct bpacket_queue *q; pkt = paf->next_pkt_to_send; if (!pkt || (pkt->buffer == NULL)) /* Already at end of list */ return 0; q = PAF_PKTQ(paf); if (TAILQ_EMPTY(&(q->pkts))) return 0; last = TAILQ_LAST(&(q->pkts), pkt_queue); if (last->ver >= pkt->ver) return last->ver - pkt->ver; /* sequence # rolled over */ return (UINT_MAX - pkt->ver + 1) + last->ver; } /* * Dump the bpacket queue */ void bpacket_queue_show_vty(struct bpacket_queue *q, struct vty *vty) { struct bpacket *pkt; struct peer_af *paf; pkt = bpacket_queue_first(q); while (pkt) { vty_out(vty, " Packet %p ver %u buffer %p\n", pkt, pkt->ver, pkt->buffer); LIST_FOREACH (paf, &(pkt->peers), pkt_train) { vty_out(vty, " - %s\n", paf->peer->host); } pkt = bpacket_next(pkt); } return; } struct stream *bpacket_reformat_for_peer(struct bpacket *pkt, struct peer_af *paf) { struct stream *s = NULL; bpacket_attr_vec *vec; struct peer *peer; char buf[BUFSIZ]; char buf2[BUFSIZ]; struct bgp_filter *filter; s = stream_dup(pkt->buffer); peer = PAF_PEER(paf); vec = &pkt->arr.entries[BGP_ATTR_VEC_NH]; if (CHECK_FLAG(vec->flags, BPKT_ATTRVEC_FLAGS_UPDATED)) { uint8_t nhlen; afi_t nhafi; int route_map_sets_nh; nhlen = stream_getc_from(s, vec->offset); filter = &peer->filter[paf->afi][paf->safi]; if (peer_cap_enhe(peer, paf->afi, paf->safi)) nhafi = AFI_IP6; else nhafi = BGP_NEXTHOP_AFI_FROM_NHLEN(nhlen); if (nhafi == AFI_IP) { struct in_addr v4nh, *mod_v4nh; int nh_modified = 0; size_t offset_nh = vec->offset + 1; route_map_sets_nh = (CHECK_FLAG( vec->flags, BPKT_ATTRVEC_FLAGS_RMAP_IPV4_NH_CHANGED) || CHECK_FLAG( vec->flags, BPKT_ATTRVEC_FLAGS_RMAP_NH_PEER_ADDRESS)); switch (nhlen) { case BGP_ATTR_NHLEN_IPV4: break; case BGP_ATTR_NHLEN_VPNV4: offset_nh += 8; break; default: /* TODO: handle IPv6 nexthops */ flog_warn( EC_BGP_INVALID_NEXTHOP_LENGTH, "%s: %s: invalid MP nexthop length (AFI IP): %u", __func__, peer->host, nhlen); stream_free(s); return NULL; } stream_get_from(&v4nh, s, offset_nh, IPV4_MAX_BYTELEN); mod_v4nh = &v4nh; /* * If route-map has set the nexthop, that is normally * used; if it is specified as peer-address, the peering * address is picked up. Otherwise, if NH is unavailable * from attribute, the peering addr is picked up; the * "NH unavailable" case also covers next-hop-self and * some other scenarios - see subgroup_announce_check(). * In all other cases, use the nexthop carried in the * attribute unless it is EBGP non-multiaccess and there * is no next-hop-unchanged setting or the peer is EBGP * and the route-map that changed the next-hop value * was applied inbound rather than outbound. Updates to * an EBGP peer should only modify the next-hop if it * was set in an outbound route-map to that peer. * Note: It is assumed route-map cannot set the nexthop * to an invalid value. */ if (route_map_sets_nh && ((peer->sort != BGP_PEER_EBGP) || ROUTE_MAP_OUT(filter))) { if (CHECK_FLAG( vec->flags, BPKT_ATTRVEC_FLAGS_RMAP_NH_PEER_ADDRESS)) { mod_v4nh = &peer->nexthop.v4; nh_modified = 1; } } else if (!v4nh.s_addr) { mod_v4nh = &peer->nexthop.v4; nh_modified = 1; } else if ( peer->sort == BGP_PEER_EBGP && (bgp_multiaccess_check_v4(v4nh, peer) == 0) && !CHECK_FLAG( vec->flags, BPKT_ATTRVEC_FLAGS_RMAP_NH_UNCHANGED) && !peer_af_flag_check( peer, paf->afi, paf->safi, PEER_FLAG_NEXTHOP_UNCHANGED)) { /* NOTE: not handling case where NH has new AFI */ mod_v4nh = &peer->nexthop.v4; nh_modified = 1; } if (nh_modified) /* allow for VPN RD */ stream_put_in_addr_at(s, offset_nh, mod_v4nh); if (bgp_debug_update(peer, NULL, NULL, 0)) zlog_debug("u%" PRIu64 ":s%" PRIu64 " %s send UPDATE w/ nexthop %s%s", PAF_SUBGRP(paf)->update_group->id, PAF_SUBGRP(paf)->id, peer->host, inet_ntoa(*mod_v4nh), (nhlen == 12 ? " and RD" : "")); } else if (nhafi == AFI_IP6) { struct in6_addr v6nhglobal, *mod_v6nhg; struct in6_addr v6nhlocal, *mod_v6nhl; int gnh_modified, lnh_modified; size_t offset_nhglobal = vec->offset + 1; size_t offset_nhlocal = vec->offset + 1; gnh_modified = lnh_modified = 0; mod_v6nhg = &v6nhglobal; mod_v6nhl = &v6nhlocal; route_map_sets_nh = (CHECK_FLAG( vec->flags, BPKT_ATTRVEC_FLAGS_RMAP_IPV6_GNH_CHANGED) || CHECK_FLAG( vec->flags, BPKT_ATTRVEC_FLAGS_RMAP_NH_PEER_ADDRESS)); /* * The logic here is rather similar to that for IPv4, * the * additional work being to handle 1 or 2 nexthops. * Also, 3rd * party nexthop is not propagated for EBGP right now. */ switch (nhlen) { case BGP_ATTR_NHLEN_IPV6_GLOBAL: break; case BGP_ATTR_NHLEN_IPV6_GLOBAL_AND_LL: offset_nhlocal += IPV6_MAX_BYTELEN; break; case BGP_ATTR_NHLEN_VPNV6_GLOBAL: offset_nhglobal += 8; break; case BGP_ATTR_NHLEN_VPNV6_GLOBAL_AND_LL: offset_nhglobal += 8; offset_nhlocal += 8 * 2 + IPV6_MAX_BYTELEN; break; default: /* TODO: handle IPv4 nexthops */ flog_warn( EC_BGP_INVALID_NEXTHOP_LENGTH, "%s: %s: invalid MP nexthop length (AFI IP6): %u", __func__, peer->host, nhlen); stream_free(s); return NULL; } stream_get_from(&v6nhglobal, s, offset_nhglobal, IPV6_MAX_BYTELEN); /* * Updates to an EBGP peer should only modify the * next-hop if it was set in an outbound route-map * to that peer. */ if (route_map_sets_nh && ((peer->sort != BGP_PEER_EBGP) || ROUTE_MAP_OUT(filter))) { if (CHECK_FLAG( vec->flags, BPKT_ATTRVEC_FLAGS_RMAP_NH_PEER_ADDRESS)) { mod_v6nhg = &peer->nexthop.v6_global; gnh_modified = 1; } } else if (IN6_IS_ADDR_UNSPECIFIED(&v6nhglobal)) { mod_v6nhg = &peer->nexthop.v6_global; gnh_modified = 1; } else if ( (peer->sort == BGP_PEER_EBGP) && (!bgp_multiaccess_check_v6(v6nhglobal, peer)) && !CHECK_FLAG(vec->flags, BPKT_ATTRVEC_FLAGS_RMAP_NH_UNCHANGED) && !peer_af_flag_check( peer, nhafi, paf->safi, PEER_FLAG_NEXTHOP_UNCHANGED)) { /* NOTE: not handling case where NH has new AFI */ mod_v6nhg = &peer->nexthop.v6_global; gnh_modified = 1; } if (nhlen == BGP_ATTR_NHLEN_IPV6_GLOBAL_AND_LL || nhlen == BGP_ATTR_NHLEN_VPNV6_GLOBAL_AND_LL) { stream_get_from(&v6nhlocal, s, offset_nhlocal, IPV6_MAX_BYTELEN); if (IN6_IS_ADDR_UNSPECIFIED(&v6nhlocal)) { mod_v6nhl = &peer->nexthop.v6_local; lnh_modified = 1; } } if (gnh_modified) stream_put_in6_addr_at(s, offset_nhglobal, mod_v6nhg); if (lnh_modified) stream_put_in6_addr_at(s, offset_nhlocal, mod_v6nhl); if (bgp_debug_update(peer, NULL, NULL, 0)) { if (nhlen == 32 || nhlen == 48) zlog_debug( "u%" PRIu64 ":s%" PRIu64 " %s send UPDATE w/ mp_nexthops %s, %s%s", PAF_SUBGRP(paf) ->update_group->id, PAF_SUBGRP(paf)->id, peer->host, inet_ntop(AF_INET6, mod_v6nhg, buf, BUFSIZ), inet_ntop(AF_INET6, mod_v6nhl, buf2, BUFSIZ), (nhlen == 48 ? " and RD" : "")); else zlog_debug( "u%" PRIu64 ":s%" PRIu64 " %s send UPDATE w/ mp_nexthop %s%s", PAF_SUBGRP(paf) ->update_group->id, PAF_SUBGRP(paf)->id, peer->host, inet_ntop(AF_INET6, mod_v6nhg, buf, BUFSIZ), (nhlen == 24 ? " and RD" : "")); } } else if (paf->afi == AFI_L2VPN) { struct in_addr v4nh, *mod_v4nh; int nh_modified = 0; stream_get_from(&v4nh, s, vec->offset + 1, 4); mod_v4nh = &v4nh; /* No route-map changes allowed for EVPN nexthops. */ if (!v4nh.s_addr) { mod_v4nh = &peer->nexthop.v4; nh_modified = 1; } if (nh_modified) stream_put_in_addr_at(s, vec->offset + 1, mod_v4nh); if (bgp_debug_update(peer, NULL, NULL, 0)) zlog_debug("u%" PRIu64 ":s%" PRIu64 " %s send UPDATE w/ nexthop %s", PAF_SUBGRP(paf)->update_group->id, PAF_SUBGRP(paf)->id, peer->host, inet_ntoa(*mod_v4nh)); } } return s; } /* * Update the vecarr offsets to go beyond 'pos' bytes, i.e. add 'pos' * to each offset. */ static void bpacket_attr_vec_arr_update(struct bpacket_attr_vec_arr *vecarr, size_t pos) { int i; if (!vecarr) return; for (i = 0; i < BGP_ATTR_VEC_MAX; i++) vecarr->entries[i].offset += pos; } /* * Return if there are packets to build for this subgroup. */ int subgroup_packets_to_build(struct update_subgroup *subgrp) { struct bgp_advertise *adv; if (!subgrp) return 0; adv = bgp_adv_fifo_first(&subgrp->sync->withdraw); if (adv) return 1; adv = bgp_adv_fifo_first(&subgrp->sync->update); if (adv) return 1; return 0; } /* Make BGP update packet. */ struct bpacket *subgroup_update_packet(struct update_subgroup *subgrp) { struct bpacket_attr_vec_arr vecarr; struct bpacket *pkt; struct peer *peer; struct stream *s; struct stream *snlri; struct stream *packet; struct bgp_adj_out *adj; struct bgp_advertise *adv; struct bgp_node *rn = NULL; struct bgp_path_info *path = NULL; bgp_size_t total_attr_len = 0; unsigned long attrlen_pos = 0; size_t mpattrlen_pos = 0; size_t mpattr_pos = 0; afi_t afi; safi_t safi; int space_remaining = 0; int space_needed = 0; char send_attr_str[BUFSIZ]; int send_attr_printed = 0; int num_pfx = 0; int addpath_encode = 0; int addpath_overhead = 0; uint32_t addpath_tx_id = 0; struct prefix_rd *prd = NULL; mpls_label_t label = MPLS_INVALID_LABEL, *label_pnt = NULL; uint32_t num_labels = 0; if (!subgrp) return NULL; if (bpacket_queue_is_full(SUBGRP_INST(subgrp), SUBGRP_PKTQ(subgrp))) return NULL; peer = SUBGRP_PEER(subgrp); afi = SUBGRP_AFI(subgrp); safi = SUBGRP_SAFI(subgrp); s = subgrp->work; stream_reset(s); snlri = subgrp->scratch; stream_reset(snlri); bpacket_attr_vec_arr_reset(&vecarr); addpath_encode = bgp_addpath_encode_tx(peer, afi, safi); addpath_overhead = addpath_encode ? BGP_ADDPATH_ID_LEN : 0; adv = bgp_adv_fifo_first(&subgrp->sync->update); while (adv) { assert(adv->rn); rn = adv->rn; adj = adv->adj; addpath_tx_id = adj->addpath_tx_id; path = adv->pathi; space_remaining = STREAM_CONCAT_REMAIN(s, snlri, STREAM_SIZE(s)) - BGP_MAX_PACKET_SIZE_OVERFLOW; space_needed = BGP_NLRI_LENGTH + addpath_overhead + bgp_packet_mpattr_prefix_size(afi, safi, &rn->p); /* When remaining space can't include NLRI and it's length. */ if (space_remaining < space_needed) break; /* If packet is empty, set attribute. */ if (stream_empty(s)) { struct peer *from = NULL; if (path) from = path->peer; /* 1: Write the BGP message header - 16 bytes marker, 2 * bytes length, * one byte message type. */ bgp_packet_set_marker(s, BGP_MSG_UPDATE); /* 2: withdrawn routes length */ stream_putw(s, 0); /* 3: total attributes length - attrlen_pos stores the * position */ attrlen_pos = stream_get_endp(s); stream_putw(s, 0); /* 4: if there is MP_REACH_NLRI attribute, that should * be the first * attribute, according to * draft-ietf-idr-error-handling. Save the * position. */ mpattr_pos = stream_get_endp(s); /* 5: Encode all the attributes, except MP_REACH_NLRI * attr. */ total_attr_len = bgp_packet_attribute( NULL, peer, s, adv->baa->attr, &vecarr, NULL, afi, safi, from, NULL, NULL, 0, 0, 0); space_remaining = STREAM_CONCAT_REMAIN(s, snlri, STREAM_SIZE(s)) - BGP_MAX_PACKET_SIZE_OVERFLOW; space_needed = BGP_NLRI_LENGTH + addpath_overhead + bgp_packet_mpattr_prefix_size( afi, safi, &rn->p); /* If the attributes alone do not leave any room for * NLRI then * return */ if (space_remaining < space_needed) { flog_err( EC_BGP_UPDGRP_ATTR_LEN, "u%" PRIu64 ":s%" PRIu64 " attributes too long, cannot send UPDATE", subgrp->update_group->id, subgrp->id); /* Flush the FIFO update queue */ while (adv) adv = bgp_advertise_clean_subgroup( subgrp, adj); return NULL; } if (BGP_DEBUG(update, UPDATE_OUT) || BGP_DEBUG(update, UPDATE_PREFIX)) { memset(send_attr_str, 0, BUFSIZ); send_attr_printed = 0; bgp_dump_attr(adv->baa->attr, send_attr_str, BUFSIZ); } } if ((afi == AFI_IP && safi == SAFI_UNICAST) && !peer_cap_enhe(peer, afi, safi)) stream_put_prefix_addpath(s, &rn->p, addpath_encode, addpath_tx_id); else { /* Encode the prefix in MP_REACH_NLRI attribute */ if (rn->prn) prd = (struct prefix_rd *)&rn->prn->p; if (safi == SAFI_LABELED_UNICAST) { label = bgp_adv_label(rn, path, peer, afi, safi); label_pnt = &label; num_labels = 1; } else if (path && path->extra) { label_pnt = &path->extra->label[0]; num_labels = path->extra->num_labels; } if (stream_empty(snlri)) mpattrlen_pos = bgp_packet_mpattr_start( snlri, peer, afi, safi, &vecarr, adv->baa->attr); bgp_packet_mpattr_prefix(snlri, afi, safi, &rn->p, prd, label_pnt, num_labels, addpath_encode, addpath_tx_id, adv->baa->attr); } num_pfx++; if (bgp_debug_update(NULL, &rn->p, subgrp->update_group, 0)) { char pfx_buf[BGP_PRD_PATH_STRLEN]; if (!send_attr_printed) { zlog_debug("u%" PRIu64 ":s%" PRIu64 " send UPDATE w/ attr: %s", subgrp->update_group->id, subgrp->id, send_attr_str); if (!stream_empty(snlri)) { iana_afi_t pkt_afi; iana_safi_t pkt_safi; pkt_afi = afi_int2iana(afi); pkt_safi = safi_int2iana(safi); zlog_debug( "u%" PRIu64 ":s%" PRIu64 " send MP_REACH for afi/safi %d/%d", subgrp->update_group->id, subgrp->id, pkt_afi, pkt_safi); } send_attr_printed = 1; } bgp_debug_rdpfxpath2str(afi, safi, prd, &rn->p, label_pnt, num_labels, addpath_encode, addpath_tx_id, pfx_buf, sizeof(pfx_buf)); zlog_debug("u%" PRIu64 ":s%" PRIu64 " send UPDATE %s", subgrp->update_group->id, subgrp->id, pfx_buf); } /* Synchnorize attribute. */ if (adj->attr) bgp_attr_unintern(&adj->attr); else subgrp->scount++; adj->attr = bgp_attr_intern(adv->baa->attr); adv = bgp_advertise_clean_subgroup(subgrp, adj); } if (!stream_empty(s)) { if (!stream_empty(snlri)) { bgp_packet_mpattr_end(snlri, mpattrlen_pos); total_attr_len += stream_get_endp(snlri); } /* set the total attribute length correctly */ stream_putw_at(s, attrlen_pos, total_attr_len); if (!stream_empty(snlri)) { packet = stream_dupcat(s, snlri, mpattr_pos); bpacket_attr_vec_arr_update(&vecarr, mpattr_pos); } else packet = stream_dup(s); bgp_packet_set_size(packet); if (bgp_debug_update(NULL, NULL, subgrp->update_group, 0)) zlog_debug("u%" PRIu64 ":s%" PRIu64 " send UPDATE len %zd numpfx %d", subgrp->update_group->id, subgrp->id, (stream_get_endp(packet) - stream_get_getp(packet)), num_pfx); pkt = bpacket_queue_add(SUBGRP_PKTQ(subgrp), packet, &vecarr); stream_reset(s); stream_reset(snlri); return pkt; } return NULL; } /* Make BGP withdraw packet. */ /* For ipv4 unicast: 16-octet marker | 2-octet length | 1-octet type | 2-octet withdrawn route length | withdrawn prefixes | 2-octet attrlen (=0) */ /* For other afi/safis: 16-octet marker | 2-octet length | 1-octet type | 2-octet withdrawn route length (=0) | 2-octet attrlen | mp_unreach attr type | attr len | afi | safi | withdrawn prefixes */ struct bpacket *subgroup_withdraw_packet(struct update_subgroup *subgrp) { struct bpacket *pkt; struct stream *s; struct bgp_adj_out *adj; struct bgp_advertise *adv; struct peer *peer; struct bgp_node *rn; bgp_size_t unfeasible_len; bgp_size_t total_attr_len; size_t mp_start = 0; size_t attrlen_pos = 0; size_t mplen_pos = 0; uint8_t first_time = 1; afi_t afi; safi_t safi; int space_remaining = 0; int space_needed = 0; int num_pfx = 0; int addpath_encode = 0; int addpath_overhead = 0; uint32_t addpath_tx_id = 0; struct prefix_rd *prd = NULL; if (!subgrp) return NULL; if (bpacket_queue_is_full(SUBGRP_INST(subgrp), SUBGRP_PKTQ(subgrp))) return NULL; peer = SUBGRP_PEER(subgrp); afi = SUBGRP_AFI(subgrp); safi = SUBGRP_SAFI(subgrp); s = subgrp->work; stream_reset(s); addpath_encode = bgp_addpath_encode_tx(peer, afi, safi); addpath_overhead = addpath_encode ? BGP_ADDPATH_ID_LEN : 0; while ((adv = bgp_adv_fifo_first(&subgrp->sync->withdraw)) != NULL) { assert(adv->rn); adj = adv->adj; rn = adv->rn; addpath_tx_id = adj->addpath_tx_id; space_remaining = STREAM_WRITEABLE(s) - BGP_MAX_PACKET_SIZE_OVERFLOW; space_needed = BGP_NLRI_LENGTH + addpath_overhead + BGP_TOTAL_ATTR_LEN + bgp_packet_mpattr_prefix_size(afi, safi, &rn->p); if (space_remaining < space_needed) break; if (stream_empty(s)) { bgp_packet_set_marker(s, BGP_MSG_UPDATE); stream_putw(s, 0); /* unfeasible routes length */ } else first_time = 0; if (afi == AFI_IP && safi == SAFI_UNICAST && !peer_cap_enhe(peer, afi, safi)) stream_put_prefix_addpath(s, &rn->p, addpath_encode, addpath_tx_id); else { if (rn->prn) prd = (struct prefix_rd *)&rn->prn->p; /* If first time, format the MP_UNREACH header */ if (first_time) { iana_afi_t pkt_afi; iana_safi_t pkt_safi; pkt_afi = afi_int2iana(afi); pkt_safi = safi_int2iana(safi); attrlen_pos = stream_get_endp(s); /* total attr length = 0 for now. reevaluate * later */ stream_putw(s, 0); mp_start = stream_get_endp(s); mplen_pos = bgp_packet_mpunreach_start(s, afi, safi); if (bgp_debug_update(NULL, NULL, subgrp->update_group, 0)) zlog_debug( "u%" PRIu64 ":s%" PRIu64 " send MP_UNREACH for afi/safi %d/%d", subgrp->update_group->id, subgrp->id, pkt_afi, pkt_safi); } bgp_packet_mpunreach_prefix(s, &rn->p, afi, safi, prd, NULL, 0, addpath_encode, addpath_tx_id, NULL); } num_pfx++; if (bgp_debug_update(NULL, &rn->p, subgrp->update_group, 0)) { char pfx_buf[BGP_PRD_PATH_STRLEN]; bgp_debug_rdpfxpath2str(afi, safi, prd, &rn->p, NULL, 0, addpath_encode, addpath_tx_id, pfx_buf, sizeof(pfx_buf)); zlog_debug("u%" PRIu64 ":s%" PRIu64 " send UPDATE %s -- unreachable", subgrp->update_group->id, subgrp->id, pfx_buf); } subgrp->scount--; bgp_adj_out_remove_subgroup(rn, adj, subgrp); bgp_unlock_node(rn); } if (!stream_empty(s)) { if (afi == AFI_IP && safi == SAFI_UNICAST && !peer_cap_enhe(peer, afi, safi)) { unfeasible_len = stream_get_endp(s) - BGP_HEADER_SIZE - BGP_UNFEASIBLE_LEN; stream_putw_at(s, BGP_HEADER_SIZE, unfeasible_len); stream_putw(s, 0); } else { /* Set the mp_unreach attr's length */ bgp_packet_mpunreach_end(s, mplen_pos); /* Set total path attribute length. */ total_attr_len = stream_get_endp(s) - mp_start; stream_putw_at(s, attrlen_pos, total_attr_len); } bgp_packet_set_size(s); if (bgp_debug_update(NULL, NULL, subgrp->update_group, 0)) zlog_debug("u%" PRIu64 ":s%" PRIu64 " send UPDATE (withdraw) len %zd numpfx %d", subgrp->update_group->id, subgrp->id, (stream_get_endp(s) - stream_get_getp(s)), num_pfx); pkt = bpacket_queue_add(SUBGRP_PKTQ(subgrp), stream_dup(s), NULL); stream_reset(s); return pkt; } return NULL; } void subgroup_default_update_packet(struct update_subgroup *subgrp, struct attr *attr, struct peer *from) { struct stream *s; struct peer *peer; struct prefix p; unsigned long pos; bgp_size_t total_attr_len; afi_t afi; safi_t safi; struct bpacket_attr_vec_arr vecarr; int addpath_encode = 0; if (DISABLE_BGP_ANNOUNCE) return; if (!subgrp) return; peer = SUBGRP_PEER(subgrp); afi = SUBGRP_AFI(subgrp); safi = SUBGRP_SAFI(subgrp); bpacket_attr_vec_arr_reset(&vecarr); addpath_encode = bgp_addpath_encode_tx(peer, afi, safi); memset(&p, 0, sizeof(p)); p.family = afi2family(afi); p.prefixlen = 0; /* Logging the attribute. */ if (bgp_debug_update(NULL, &p, subgrp->update_group, 0)) { char attrstr[BUFSIZ]; char buf[PREFIX_STRLEN]; /* ' with addpath ID ' 17 * max strlen of uint32 + 10 * +/- (just in case) + 1 * null terminator + 1 * ============================ 29 */ char tx_id_buf[30]; attrstr[0] = '\0'; bgp_dump_attr(attr, attrstr, BUFSIZ); if (addpath_encode) snprintf(tx_id_buf, sizeof(tx_id_buf), " with addpath ID %u", BGP_ADDPATH_TX_ID_FOR_DEFAULT_ORIGINATE); else tx_id_buf[0] = '\0'; zlog_debug("u%" PRIu64 ":s%" PRIu64 " send UPDATE %s%s %s", (SUBGRP_UPDGRP(subgrp))->id, subgrp->id, prefix2str(&p, buf, sizeof(buf)), tx_id_buf, attrstr); } s = stream_new(BGP_MAX_PACKET_SIZE); /* Make BGP update packet. */ bgp_packet_set_marker(s, BGP_MSG_UPDATE); /* Unfeasible Routes Length. */ stream_putw(s, 0); /* Make place for total attribute length. */ pos = stream_get_endp(s); stream_putw(s, 0); total_attr_len = bgp_packet_attribute( NULL, peer, s, attr, &vecarr, &p, afi, safi, from, NULL, NULL, 0, addpath_encode, BGP_ADDPATH_TX_ID_FOR_DEFAULT_ORIGINATE); /* Set Total Path Attribute Length. */ stream_putw_at(s, pos, total_attr_len); /* NLRI set. */ if (p.family == AF_INET && safi == SAFI_UNICAST && !peer_cap_enhe(peer, afi, safi)) stream_put_prefix_addpath( s, &p, addpath_encode, BGP_ADDPATH_TX_ID_FOR_DEFAULT_ORIGINATE); /* Set size. */ bgp_packet_set_size(s); (void)bpacket_queue_add(SUBGRP_PKTQ(subgrp), s, &vecarr); subgroup_trigger_write(subgrp); } void subgroup_default_withdraw_packet(struct update_subgroup *subgrp) { struct peer *peer; struct stream *s; struct prefix p; unsigned long attrlen_pos = 0; unsigned long cp; bgp_size_t unfeasible_len; bgp_size_t total_attr_len = 0; size_t mp_start = 0; size_t mplen_pos = 0; afi_t afi; safi_t safi; int addpath_encode = 0; if (DISABLE_BGP_ANNOUNCE) return; peer = SUBGRP_PEER(subgrp); afi = SUBGRP_AFI(subgrp); safi = SUBGRP_SAFI(subgrp); addpath_encode = bgp_addpath_encode_tx(peer, afi, safi); memset(&p, 0, sizeof(p)); p.family = afi2family(afi); p.prefixlen = 0; if (bgp_debug_update(NULL, &p, subgrp->update_group, 0)) { char buf[PREFIX_STRLEN]; /* ' with addpath ID ' 17 * max strlen of uint32 + 10 * +/- (just in case) + 1 * null terminator + 1 * ============================ 29 */ char tx_id_buf[30]; if (addpath_encode) snprintf(tx_id_buf, sizeof(tx_id_buf), " with addpath ID %u", BGP_ADDPATH_TX_ID_FOR_DEFAULT_ORIGINATE); zlog_debug("u%" PRIu64 ":s%" PRIu64 " send UPDATE %s%s -- unreachable", (SUBGRP_UPDGRP(subgrp))->id, subgrp->id, prefix2str(&p, buf, sizeof(buf)), tx_id_buf); } s = stream_new(BGP_MAX_PACKET_SIZE); /* Make BGP update packet. */ bgp_packet_set_marker(s, BGP_MSG_UPDATE); /* Unfeasible Routes Length. */; cp = stream_get_endp(s); stream_putw(s, 0); /* Withdrawn Routes. */ if (p.family == AF_INET && safi == SAFI_UNICAST && !peer_cap_enhe(peer, afi, safi)) { stream_put_prefix_addpath( s, &p, addpath_encode, BGP_ADDPATH_TX_ID_FOR_DEFAULT_ORIGINATE); unfeasible_len = stream_get_endp(s) - cp - 2; /* Set unfeasible len. */ stream_putw_at(s, cp, unfeasible_len); /* Set total path attribute length. */ stream_putw(s, 0); } else { attrlen_pos = stream_get_endp(s); stream_putw(s, 0); mp_start = stream_get_endp(s); mplen_pos = bgp_packet_mpunreach_start(s, afi, safi); bgp_packet_mpunreach_prefix( s, &p, afi, safi, NULL, NULL, 0, addpath_encode, BGP_ADDPATH_TX_ID_FOR_DEFAULT_ORIGINATE, NULL); /* Set the mp_unreach attr's length */ bgp_packet_mpunreach_end(s, mplen_pos); /* Set total path attribute length. */ total_attr_len = stream_get_endp(s) - mp_start; stream_putw_at(s, attrlen_pos, total_attr_len); } bgp_packet_set_size(s); (void)bpacket_queue_add(SUBGRP_PKTQ(subgrp), s, NULL); subgroup_trigger_write(subgrp); } static void bpacket_vec_arr_inherit_attr_flags(struct bpacket_attr_vec_arr *vecarr, bpacket_attr_vec_type type, struct attr *attr) { if (CHECK_FLAG(attr->rmap_change_flags, BATTR_RMAP_NEXTHOP_PEER_ADDRESS)) SET_FLAG(vecarr->entries[BGP_ATTR_VEC_NH].flags, BPKT_ATTRVEC_FLAGS_RMAP_NH_PEER_ADDRESS); if (CHECK_FLAG(attr->rmap_change_flags, BATTR_REFLECTED)) SET_FLAG(vecarr->entries[BGP_ATTR_VEC_NH].flags, BPKT_ATTRVEC_FLAGS_REFLECTED); if (CHECK_FLAG(attr->rmap_change_flags, BATTR_RMAP_NEXTHOP_UNCHANGED)) SET_FLAG(vecarr->entries[BGP_ATTR_VEC_NH].flags, BPKT_ATTRVEC_FLAGS_RMAP_NH_UNCHANGED); if (CHECK_FLAG(attr->rmap_change_flags, BATTR_RMAP_IPV4_NHOP_CHANGED)) SET_FLAG(vecarr->entries[BGP_ATTR_VEC_NH].flags, BPKT_ATTRVEC_FLAGS_RMAP_IPV4_NH_CHANGED); if (CHECK_FLAG(attr->rmap_change_flags, BATTR_RMAP_IPV6_GLOBAL_NHOP_CHANGED)) SET_FLAG(vecarr->entries[BGP_ATTR_VEC_NH].flags, BPKT_ATTRVEC_FLAGS_RMAP_IPV6_GNH_CHANGED); if (CHECK_FLAG(attr->rmap_change_flags, BATTR_RMAP_IPV6_LL_NHOP_CHANGED)) SET_FLAG(vecarr->entries[BGP_ATTR_VEC_NH].flags, BPKT_ATTRVEC_FLAGS_RMAP_IPV6_LNH_CHANGED); } /* Reset the Attributes vector array. The vector array is used to override * certain output parameters in the packet for a particular peer */ void bpacket_attr_vec_arr_reset(struct bpacket_attr_vec_arr *vecarr) { int i; if (!vecarr) return; i = 0; while (i < BGP_ATTR_VEC_MAX) { vecarr->entries[i].flags = 0; vecarr->entries[i].offset = 0; i++; } } /* Setup a particular node entry in the vecarr */ void bpacket_attr_vec_arr_set_vec(struct bpacket_attr_vec_arr *vecarr, bpacket_attr_vec_type type, struct stream *s, struct attr *attr) { if (!vecarr) return; assert(type < BGP_ATTR_VEC_MAX); SET_FLAG(vecarr->entries[type].flags, BPKT_ATTRVEC_FLAGS_UPDATED); vecarr->entries[type].offset = stream_get_endp(s); if (attr) bpacket_vec_arr_inherit_attr_flags(vecarr, type, attr); } frr-7.2.1/bgpd/bgp_vnc_types.h0000644000000000000000000000205613610377563013146 00000000000000/* * Copyright 2015-2016, LabN Consulting, L.L.C. * * This program is free software; you can redistribute it and/or * modify it under the terms of the GNU General Public License * as published by the Free Software Foundation; either version 2 * of the License, or (at your option) any later version. * * 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; see the file COPYING; if not, write to the Free Software * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA */ #ifndef _QUAGGA_BGP_VNC_TYPES_H #define _QUAGGA_BGP_VNC_TYPES_H #if ENABLE_BGP_VNC typedef enum { BGP_VNC_SUBTLV_TYPE_LIFETIME = 1, BGP_VNC_SUBTLV_TYPE_RFPOPTION = 2, /* deprecated */ } bgp_vnc_subtlv_types; #endif /* ENABLE_BGP_VNC */ #endif /* _QUAGGA_BGP_VNC_TYPES_H */ frr-7.2.1/bgpd/bgp_vpn.c0000644000000000000000000001324413610377563011733 00000000000000/* VPN Related functions * Copyright (C) 2017 6WIND * * This file is part of FRRouting * * FRRouting is free software; you can redistribute it and/or modify it * under the terms of the GNU General Public License as published by the * Free Software Foundation; either version 2, or (at your option) any * later version. * * FRRouting 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; see the file COPYING; if not, write to the Free Software * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA */ #include #include "command.h" #include "prefix.h" #include "lib/json.h" #include "bgpd/bgpd.h" #include "bgpd/bgp_route.h" #include "bgpd/bgp_table.h" #include "bgpd/bgp_attr.h" #include "bgpd/bgp_mplsvpn.h" #include "bgpd/bgp_vpn.h" int show_adj_route_vpn(struct vty *vty, struct peer *peer, struct prefix_rd *prd, afi_t afi, safi_t safi, bool use_json) { struct bgp *bgp; struct bgp_table *table; struct bgp_node *rn; struct bgp_node *rm; struct bgp_path_info *path; int rd_header; int header = 1; json_object *json = NULL; json_object *json_scode = NULL; json_object *json_ocode = NULL; json_object *json_routes = NULL; json_object *json_array = NULL; bgp = bgp_get_default(); if (bgp == NULL) { if (!use_json) vty_out(vty, "No BGP process is configured\n"); else vty_out(vty, "{}\n"); return CMD_WARNING; } if (use_json) { json_scode = json_object_new_object(); json_ocode = json_object_new_object(); json_routes = json_object_new_object(); json = json_object_new_object(); json_object_string_add(json_scode, "suppressed", "s"); json_object_string_add(json_scode, "damped", "d"); json_object_string_add(json_scode, "history", "h"); json_object_string_add(json_scode, "valid", "*"); json_object_string_add(json_scode, "best", ">"); json_object_string_add(json_scode, "internal", "i"); json_object_string_add(json_ocode, "igp", "i"); json_object_string_add(json_ocode, "egp", "e"); json_object_string_add(json_ocode, "incomplete", "?"); } for (rn = bgp_table_top(bgp->rib[afi][safi]); rn; rn = bgp_route_next(rn)) { if (prd && memcmp(rn->p.u.val, prd->val, 8) != 0) continue; table = bgp_node_get_bgp_table_info(rn); if (table == NULL) continue; if (use_json) json_array = json_object_new_array(); else json_array = NULL; rd_header = 1; for (rm = bgp_table_top(table); rm; rm = bgp_route_next(rm)) { path = bgp_node_get_bgp_path_info(rm); if (path == NULL) continue; if (header) { if (use_json) { json_object_int_add( json, "bgpTableVersion", 0); json_object_string_add( json, "bgpLocalRouterId", inet_ntoa(bgp->router_id)); json_object_object_add(json, "bgpStatusCodes", json_scode); json_object_object_add(json, "bgpOriginCodes", json_ocode); } else { vty_out(vty, "BGP table version is 0, local router ID is %s\n", inet_ntoa(bgp->router_id)); vty_out(vty, "Status codes: s suppressed, d damped, h history, * valid, > best, i - internal\n"); vty_out(vty, "Origin codes: i - IGP, e - EGP, ? - incomplete\n\n"); vty_out(vty, V4_HEADER); } header = 0; } if (rd_header) { uint16_t type; struct rd_as rd_as = {0}; struct rd_ip rd_ip = {0}; #if ENABLE_BGP_VNC struct rd_vnc_eth rd_vnc_eth = {0}; #endif uint8_t *pnt; pnt = rn->p.u.val; /* Decode RD type. */ type = decode_rd_type(pnt); /* Decode RD value. */ if (type == RD_TYPE_AS) decode_rd_as(pnt + 2, &rd_as); else if (type == RD_TYPE_AS4) decode_rd_as4(pnt + 2, &rd_as); else if (type == RD_TYPE_IP) decode_rd_ip(pnt + 2, &rd_ip); #if ENABLE_BGP_VNC else if (type == RD_TYPE_VNC_ETH) decode_rd_vnc_eth(pnt, &rd_vnc_eth); #endif if (use_json) { char buffer[BUFSIZ]; if (type == RD_TYPE_AS || type == RD_TYPE_AS4) sprintf(buffer, "%u:%d", rd_as.as, rd_as.val); else if (type == RD_TYPE_IP) sprintf(buffer, "%s:%d", inet_ntoa(rd_ip.ip), rd_ip.val); json_object_string_add( json_routes, "routeDistinguisher", buffer); } else { vty_out(vty, "Route Distinguisher: "); if (type == RD_TYPE_AS || type == RD_TYPE_AS4) vty_out(vty, "%u:%d", rd_as.as, rd_as.val); else if (type == RD_TYPE_IP) vty_out(vty, "%s:%d", inet_ntoa(rd_ip.ip), rd_ip.val); #if ENABLE_BGP_VNC else if (type == RD_TYPE_VNC_ETH) vty_out(vty, "%u:%02x:%02x:%02x:%02x:%02x:%02x", rd_vnc_eth.local_nve_id, rd_vnc_eth.macaddr .octet[0], rd_vnc_eth.macaddr .octet[1], rd_vnc_eth.macaddr .octet[2], rd_vnc_eth.macaddr .octet[3], rd_vnc_eth.macaddr .octet[4], rd_vnc_eth.macaddr .octet[5]); #endif vty_out(vty, "\n"); } rd_header = 0; } if (use_json) { char buf[BUFSIZ]; prefix2str(&rm->p, buf, sizeof(buf)); json_object_object_add(json_routes, buf, json_array); } else { route_vty_out_tmp(vty, &rm->p, path->attr, safi, use_json, json_array); } } } if (use_json) { json_object_object_add(json, "routes", json_routes); vty_out(vty, "%s\n", json_object_to_json_string_ext( json, JSON_C_TO_STRING_PRETTY)); json_object_free(json); } return CMD_SUCCESS; } frr-7.2.1/bgpd/bgp_vpn.h0000644000000000000000000000205313610377563011734 00000000000000/* VPN common functions to MP-BGP * Copyright (C) 2017 6WIND * * This file is part of FRRouting. * * FRRouting is free software; you can redistribute it and/or modify it * under the terms of the GNU General Public License as published by the * Free Software Foundation; either version 2, or (at your option) any * later version. * * FRRouting 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; see the file COPYING; if not, write to the Free Software * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA */ #ifndef _FRR_BGP_VPN_H #define _FRR_BGP_VPN_H #include extern int show_adj_route_vpn(struct vty *vty, struct peer *peer, struct prefix_rd *prd, afi_t afi, safi_t safi, bool use_json); #endif /* _QUAGGA_BGP_VPN_H */ frr-7.2.1/bgpd/bgp_vty.c0000644000000000000000000165432113610377563011762 00000000000000/* BGP VTY interface. * Copyright (C) 1996, 97, 98, 99, 2000 Kunihiro Ishiguro * * This file is part of GNU Zebra. * * GNU Zebra is free software; you can redistribute it and/or modify it * under the terms of the GNU General Public License as published by the * Free Software Foundation; either version 2, or (at your option) any * later version. * * GNU Zebra 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; see the file COPYING; if not, write to the Free Software * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA */ #include #include "command.h" #include "lib/json.h" #include "lib_errors.h" #include "lib/zclient.h" #include "prefix.h" #include "plist.h" #include "buffer.h" #include "linklist.h" #include "stream.h" #include "thread.h" #include "log.h" #include "memory.h" #include "memory_vty.h" #include "hash.h" #include "queue.h" #include "filter.h" #include "frrstr.h" #include "bgpd/bgpd.h" #include "bgpd/bgp_attr_evpn.h" #include "bgpd/bgp_advertise.h" #include "bgpd/bgp_attr.h" #include "bgpd/bgp_aspath.h" #include "bgpd/bgp_community.h" #include "bgpd/bgp_ecommunity.h" #include "bgpd/bgp_lcommunity.h" #include "bgpd/bgp_damp.h" #include "bgpd/bgp_debug.h" #include "bgpd/bgp_errors.h" #include "bgpd/bgp_fsm.h" #include "bgpd/bgp_nexthop.h" #include "bgpd/bgp_open.h" #include "bgpd/bgp_regex.h" #include "bgpd/bgp_route.h" #include "bgpd/bgp_mplsvpn.h" #include "bgpd/bgp_zebra.h" #include "bgpd/bgp_table.h" #include "bgpd/bgp_vty.h" #include "bgpd/bgp_mpath.h" #include "bgpd/bgp_packet.h" #include "bgpd/bgp_updgrp.h" #include "bgpd/bgp_bfd.h" #include "bgpd/bgp_io.h" #include "bgpd/bgp_evpn.h" #include "bgpd/bgp_addpath.h" #include "bgpd/bgp_mac.h" static struct peer_group *listen_range_exists(struct bgp *bgp, struct prefix *range, int exact); static enum node_type bgp_node_type(afi_t afi, safi_t safi) { switch (afi) { case AFI_IP: switch (safi) { case SAFI_UNICAST: return BGP_IPV4_NODE; break; case SAFI_MULTICAST: return BGP_IPV4M_NODE; break; case SAFI_LABELED_UNICAST: return BGP_IPV4L_NODE; break; case SAFI_MPLS_VPN: return BGP_VPNV4_NODE; break; case SAFI_FLOWSPEC: return BGP_FLOWSPECV4_NODE; default: /* not expected */ return BGP_IPV4_NODE; break; } break; case AFI_IP6: switch (safi) { case SAFI_UNICAST: return BGP_IPV6_NODE; break; case SAFI_MULTICAST: return BGP_IPV6M_NODE; break; case SAFI_LABELED_UNICAST: return BGP_IPV6L_NODE; break; case SAFI_MPLS_VPN: return BGP_VPNV6_NODE; break; case SAFI_FLOWSPEC: return BGP_FLOWSPECV6_NODE; default: /* not expected */ return BGP_IPV4_NODE; break; } break; case AFI_L2VPN: return BGP_EVPN_NODE; break; case AFI_UNSPEC: case AFI_MAX: // We should never be here but to clarify the switch statement.. return BGP_IPV4_NODE; break; } // Impossible to happen return BGP_IPV4_NODE; } static const char *get_afi_safi_vty_str(afi_t afi, safi_t safi) { if (afi == AFI_IP && safi == SAFI_UNICAST) return "IPv4 Unicast"; else if (afi == AFI_IP && safi == SAFI_MULTICAST) return "IPv4 Multicast"; else if (afi == AFI_IP && safi == SAFI_LABELED_UNICAST) return "IPv4 Labeled Unicast"; else if (afi == AFI_IP && safi == SAFI_MPLS_VPN) return "IPv4 VPN"; else if (afi == AFI_IP && safi == SAFI_ENCAP) return "IPv4 Encap"; else if (afi == AFI_IP && safi == SAFI_FLOWSPEC) return "IPv4 Flowspec"; else if (afi == AFI_IP6 && safi == SAFI_UNICAST) return "IPv6 Unicast"; else if (afi == AFI_IP6 && safi == SAFI_MULTICAST) return "IPv6 Multicast"; else if (afi == AFI_IP6 && safi == SAFI_LABELED_UNICAST) return "IPv6 Labeled Unicast"; else if (afi == AFI_IP6 && safi == SAFI_MPLS_VPN) return "IPv6 VPN"; else if (afi == AFI_IP6 && safi == SAFI_ENCAP) return "IPv6 Encap"; else if (afi == AFI_IP6 && safi == SAFI_FLOWSPEC) return "IPv6 Flowspec"; else if (afi == AFI_L2VPN && safi == SAFI_EVPN) return "L2VPN EVPN"; else return "Unknown"; } /* * Please note that we have intentionally camelCased * the return strings here. So if you want * to use this function, please ensure you * are doing this within json output */ static const char *get_afi_safi_json_str(afi_t afi, safi_t safi) { if (afi == AFI_IP && safi == SAFI_UNICAST) return "ipv4Unicast"; else if (afi == AFI_IP && safi == SAFI_MULTICAST) return "ipv4Multicast"; else if (afi == AFI_IP && safi == SAFI_LABELED_UNICAST) return "ipv4LabeledUnicast"; else if (afi == AFI_IP && safi == SAFI_MPLS_VPN) return "ipv4Vpn"; else if (afi == AFI_IP && safi == SAFI_ENCAP) return "ipv4Encap"; else if (afi == AFI_IP && safi == SAFI_FLOWSPEC) return "ipv4Flowspec"; else if (afi == AFI_IP6 && safi == SAFI_UNICAST) return "ipv6Unicast"; else if (afi == AFI_IP6 && safi == SAFI_MULTICAST) return "ipv6Multicast"; else if (afi == AFI_IP6 && safi == SAFI_LABELED_UNICAST) return "ipv6LabeledUnicast"; else if (afi == AFI_IP6 && safi == SAFI_MPLS_VPN) return "ipv6Vpn"; else if (afi == AFI_IP6 && safi == SAFI_ENCAP) return "ipv6Encap"; else if (afi == AFI_IP6 && safi == SAFI_FLOWSPEC) return "ipv6Flowspec"; else if (afi == AFI_L2VPN && safi == SAFI_EVPN) return "l2VpnEvpn"; else return "Unknown"; } /* Utility function to get address family from current node. */ afi_t bgp_node_afi(struct vty *vty) { afi_t afi; switch (vty->node) { case BGP_IPV6_NODE: case BGP_IPV6M_NODE: case BGP_IPV6L_NODE: case BGP_VPNV6_NODE: case BGP_FLOWSPECV6_NODE: afi = AFI_IP6; break; case BGP_EVPN_NODE: afi = AFI_L2VPN; break; default: afi = AFI_IP; break; } return afi; } /* Utility function to get subsequent address family from current node. */ safi_t bgp_node_safi(struct vty *vty) { safi_t safi; switch (vty->node) { case BGP_VPNV4_NODE: case BGP_VPNV6_NODE: safi = SAFI_MPLS_VPN; break; case BGP_IPV4M_NODE: case BGP_IPV6M_NODE: safi = SAFI_MULTICAST; break; case BGP_EVPN_NODE: safi = SAFI_EVPN; break; case BGP_IPV4L_NODE: case BGP_IPV6L_NODE: safi = SAFI_LABELED_UNICAST; break; case BGP_FLOWSPECV4_NODE: case BGP_FLOWSPECV6_NODE: safi = SAFI_FLOWSPEC; break; default: safi = SAFI_UNICAST; break; } return safi; } /** * Converts an AFI in string form to afi_t * * @param afi string, one of * - "ipv4" * - "ipv6" * - "l2vpn" * @return the corresponding afi_t */ afi_t bgp_vty_afi_from_str(const char *afi_str) { afi_t afi = AFI_MAX; /* unknown */ if (strmatch(afi_str, "ipv4")) afi = AFI_IP; else if (strmatch(afi_str, "ipv6")) afi = AFI_IP6; else if (strmatch(afi_str, "l2vpn")) afi = AFI_L2VPN; return afi; } int argv_find_and_parse_afi(struct cmd_token **argv, int argc, int *index, afi_t *afi) { int ret = 0; if (argv_find(argv, argc, "ipv4", index)) { ret = 1; if (afi) *afi = AFI_IP; } else if (argv_find(argv, argc, "ipv6", index)) { ret = 1; if (afi) *afi = AFI_IP6; } else if (argv_find(argv, argc, "l2vpn", index)) { ret = 1; if (afi) *afi = AFI_L2VPN; } return ret; } /* supports */ safi_t bgp_vty_safi_from_str(const char *safi_str) { safi_t safi = SAFI_MAX; /* unknown */ if (strmatch(safi_str, "multicast")) safi = SAFI_MULTICAST; else if (strmatch(safi_str, "unicast")) safi = SAFI_UNICAST; else if (strmatch(safi_str, "vpn")) safi = SAFI_MPLS_VPN; else if (strmatch(safi_str, "evpn")) safi = SAFI_EVPN; else if (strmatch(safi_str, "labeled-unicast")) safi = SAFI_LABELED_UNICAST; else if (strmatch(safi_str, "flowspec")) safi = SAFI_FLOWSPEC; return safi; } int argv_find_and_parse_safi(struct cmd_token **argv, int argc, int *index, safi_t *safi) { int ret = 0; if (argv_find(argv, argc, "unicast", index)) { ret = 1; if (safi) *safi = SAFI_UNICAST; } else if (argv_find(argv, argc, "multicast", index)) { ret = 1; if (safi) *safi = SAFI_MULTICAST; } else if (argv_find(argv, argc, "labeled-unicast", index)) { ret = 1; if (safi) *safi = SAFI_LABELED_UNICAST; } else if (argv_find(argv, argc, "vpn", index)) { ret = 1; if (safi) *safi = SAFI_MPLS_VPN; } else if (argv_find(argv, argc, "evpn", index)) { ret = 1; if (safi) *safi = SAFI_EVPN; } else if (argv_find(argv, argc, "flowspec", index)) { ret = 1; if (safi) *safi = SAFI_FLOWSPEC; } return ret; } /* * bgp_vty_find_and_parse_afi_safi_bgp * * For a given 'show ...' command, correctly parse the afi/safi/bgp out from it * This function *assumes* that the calling function pre-sets the afi/safi/bgp * to appropriate values for the calling function. This is to allow the * calling function to make decisions appropriate for the show command * that is being parsed. * * The show commands are generally of the form: * "show [ip] bgp [ VIEWVRFNAME] [ * []] ..." * * Since we use argv_find if the show command in particular doesn't have: * [ip] * [ VIEWVRFNAME] * [ []] * The command parsing should still be ok. * * vty -> The vty for the command so we can output some useful data in * the event of a parse error in the vrf. * argv -> The command tokens * argc -> How many command tokens we have * idx -> The current place in the command, generally should be 0 for this * function * afi -> The parsed afi if it was included in the show command, returned here * safi -> The parsed safi if it was included in the show command, returned here * bgp -> Pointer to the bgp data structure we need to fill in. * use_json -> json is configured or not * * The function returns the correct location in the parse tree for the * last token found. * * Returns 0 for failure to parse correctly, else the idx position of where * it found the last token. */ int bgp_vty_find_and_parse_afi_safi_bgp(struct vty *vty, struct cmd_token **argv, int argc, int *idx, afi_t *afi, safi_t *safi, struct bgp **bgp, bool use_json) { char *vrf_name = NULL; assert(afi); assert(safi); assert(bgp); if (argv_find(argv, argc, "ip", idx)) *afi = AFI_IP; if (argv_find(argv, argc, "view", idx)) vrf_name = argv[*idx + 1]->arg; else if (argv_find(argv, argc, "vrf", idx)) { vrf_name = argv[*idx + 1]->arg; if (strmatch(vrf_name, VRF_DEFAULT_NAME)) vrf_name = NULL; } if (vrf_name) { if (strmatch(vrf_name, "all")) *bgp = NULL; else { *bgp = bgp_lookup_by_name(vrf_name); if (!*bgp) { if (use_json) { json_object *json = NULL; json = json_object_new_object(); json_object_string_add( json, "warning", "View/Vrf is unknown"); vty_out(vty, "%s\n", json_object_to_json_string_ext(json, JSON_C_TO_STRING_PRETTY)); json_object_free(json); } else vty_out(vty, "View/Vrf %s is unknown\n", vrf_name); *idx = 0; return 0; } } } else { *bgp = bgp_get_default(); if (!*bgp) { if (use_json) { json_object *json = NULL; json = json_object_new_object(); json_object_string_add( json, "warning", "Default BGP instance not found"); vty_out(vty, "%s\n", json_object_to_json_string_ext(json, JSON_C_TO_STRING_PRETTY)); json_object_free(json); } else vty_out(vty, "Default BGP instance not found\n"); *idx = 0; return 0; } } if (argv_find_and_parse_afi(argv, argc, idx, afi)) argv_find_and_parse_safi(argv, argc, idx, safi); *idx += 1; return *idx; } static int peer_address_self_check(struct bgp *bgp, union sockunion *su) { struct interface *ifp = NULL; if (su->sa.sa_family == AF_INET) ifp = if_lookup_by_ipv4_exact(&su->sin.sin_addr, bgp->vrf_id); else if (su->sa.sa_family == AF_INET6) ifp = if_lookup_by_ipv6_exact(&su->sin6.sin6_addr, su->sin6.sin6_scope_id, bgp->vrf_id); if (ifp) return 1; return 0; } /* Utility function for looking up peer from VTY. */ /* This is used only for configuration, so disallow if attempted on * a dynamic neighbor. */ static struct peer *peer_lookup_vty(struct vty *vty, const char *ip_str) { struct bgp *bgp = VTY_GET_CONTEXT(bgp); int ret; union sockunion su; struct peer *peer; if (!bgp) { return NULL; } ret = str2sockunion(ip_str, &su); if (ret < 0) { peer = peer_lookup_by_conf_if(bgp, ip_str); if (!peer) { if ((peer = peer_lookup_by_hostname(bgp, ip_str)) == NULL) { vty_out(vty, "%% Malformed address or name: %s\n", ip_str); return NULL; } } } else { peer = peer_lookup(bgp, &su); if (!peer) { vty_out(vty, "%% Specify remote-as or peer-group commands first\n"); return NULL; } if (peer_dynamic_neighbor(peer)) { vty_out(vty, "%% Operation not allowed on a dynamic neighbor\n"); return NULL; } } return peer; } /* Utility function for looking up peer or peer group. */ /* This is used only for configuration, so disallow if attempted on * a dynamic neighbor. */ struct peer *peer_and_group_lookup_vty(struct vty *vty, const char *peer_str) { struct bgp *bgp = VTY_GET_CONTEXT(bgp); int ret; union sockunion su; struct peer *peer = NULL; struct peer_group *group = NULL; if (!bgp) { return NULL; } ret = str2sockunion(peer_str, &su); if (ret == 0) { /* IP address, locate peer. */ peer = peer_lookup(bgp, &su); } else { /* Not IP, could match either peer configured on interface or a * group. */ peer = peer_lookup_by_conf_if(bgp, peer_str); if (!peer) group = peer_group_lookup(bgp, peer_str); } if (peer) { if (peer_dynamic_neighbor(peer)) { vty_out(vty, "%% Operation not allowed on a dynamic neighbor\n"); return NULL; } return peer; } if (group) return group->conf; vty_out(vty, "%% Specify remote-as or peer-group commands first\n"); return NULL; } int bgp_vty_return(struct vty *vty, int ret) { const char *str = NULL; switch (ret) { case BGP_ERR_INVALID_VALUE: str = "Invalid value"; break; case BGP_ERR_INVALID_FLAG: str = "Invalid flag"; break; case BGP_ERR_PEER_GROUP_SHUTDOWN: str = "Peer-group has been shutdown. Activate the peer-group first"; break; case BGP_ERR_PEER_FLAG_CONFLICT: str = "Can't set override-capability and strict-capability-match at the same time"; break; case BGP_ERR_PEER_GROUP_NO_REMOTE_AS: str = "Specify remote-as or peer-group remote AS first"; break; case BGP_ERR_PEER_GROUP_CANT_CHANGE: str = "Cannot change the peer-group. Deconfigure first"; break; case BGP_ERR_PEER_GROUP_MISMATCH: str = "Peer is not a member of this peer-group"; break; case BGP_ERR_PEER_FILTER_CONFLICT: str = "Prefix/distribute list can not co-exist"; break; case BGP_ERR_NOT_INTERNAL_PEER: str = "Invalid command. Not an internal neighbor"; break; case BGP_ERR_REMOVE_PRIVATE_AS: str = "remove-private-AS cannot be configured for IBGP peers"; break; case BGP_ERR_LOCAL_AS_ALLOWED_ONLY_FOR_EBGP: str = "Local-AS allowed only for EBGP peers"; break; case BGP_ERR_CANNOT_HAVE_LOCAL_AS_SAME_AS: str = "Cannot have local-as same as BGP AS number"; break; case BGP_ERR_TCPSIG_FAILED: str = "Error while applying TCP-Sig to session(s)"; break; case BGP_ERR_NO_EBGP_MULTIHOP_WITH_TTLHACK: str = "ebgp-multihop and ttl-security cannot be configured together"; break; case BGP_ERR_NO_IBGP_WITH_TTLHACK: str = "ttl-security only allowed for EBGP peers"; break; case BGP_ERR_AS_OVERRIDE: str = "as-override cannot be configured for IBGP peers"; break; case BGP_ERR_INVALID_DYNAMIC_NEIGHBORS_LIMIT: str = "Invalid limit for number of dynamic neighbors"; break; case BGP_ERR_DYNAMIC_NEIGHBORS_RANGE_EXISTS: str = "Dynamic neighbor listen range already exists"; break; case BGP_ERR_INVALID_FOR_DYNAMIC_PEER: str = "Operation not allowed on a dynamic neighbor"; break; case BGP_ERR_INVALID_FOR_DIRECT_PEER: str = "Operation not allowed on a directly connected neighbor"; break; case BGP_ERR_PEER_SAFI_CONFLICT: str = "Cannot activate peer for both 'ipv4 unicast' and 'ipv4 labeled-unicast'"; break; } if (str) { vty_out(vty, "%% %s\n", str); return CMD_WARNING_CONFIG_FAILED; } return CMD_SUCCESS; } /* BGP clear sort. */ enum clear_sort { clear_all, clear_peer, clear_group, clear_external, clear_as }; static void bgp_clear_vty_error(struct vty *vty, struct peer *peer, afi_t afi, safi_t safi, int error) { switch (error) { case BGP_ERR_AF_UNCONFIGURED: vty_out(vty, "%%BGP: Enable %s address family for the neighbor %s\n", get_afi_safi_str(afi, safi, false), peer->host); break; case BGP_ERR_SOFT_RECONFIG_UNCONFIGURED: vty_out(vty, "%%BGP: Inbound soft reconfig for %s not possible as it\n has neither refresh capability, nor inbound soft reconfig\n", peer->host); break; default: break; } } static int bgp_peer_clear(struct peer *peer, afi_t afi, safi_t safi, struct listnode *nnode, enum bgp_clear_type stype) { int ret = 0; /* if afi/.safi not specified, spin thru all of them */ if ((afi == AFI_UNSPEC) && (safi == SAFI_UNSPEC)) { afi_t tmp_afi; safi_t tmp_safi; FOREACH_AFI_SAFI (tmp_afi, tmp_safi) { if (!peer->afc[tmp_afi][tmp_safi]) continue; if (stype == BGP_CLEAR_SOFT_NONE) ret = peer_clear(peer, &nnode); else ret = peer_clear_soft(peer, tmp_afi, tmp_safi, stype); } /* if afi specified and safi not, spin thru safis on this afi */ } else if (safi == SAFI_UNSPEC) { safi_t tmp_safi; for (tmp_safi = SAFI_UNICAST; tmp_safi < SAFI_MAX; tmp_safi++) { if (!peer->afc[afi][tmp_safi]) continue; if (stype == BGP_CLEAR_SOFT_NONE) ret = peer_clear(peer, &nnode); else ret = peer_clear_soft(peer, afi, tmp_safi, stype); } /* both afi/safi specified, let the caller know if not defined */ } else { if (!peer->afc[afi][safi]) return 1; if (stype == BGP_CLEAR_SOFT_NONE) ret = peer_clear(peer, &nnode); else ret = peer_clear_soft(peer, afi, safi, stype); } return ret; } /* `clear ip bgp' functions. */ static int bgp_clear(struct vty *vty, struct bgp *bgp, afi_t afi, safi_t safi, enum clear_sort sort, enum bgp_clear_type stype, const char *arg) { int ret = 0; bool found = false; struct peer *peer; struct listnode *node, *nnode; /* Clear all neighbors. */ /* * Pass along pointer to next node to peer_clear() when walking all * nodes on the BGP instance as that may get freed if it is a * doppelganger */ if (sort == clear_all) { for (ALL_LIST_ELEMENTS(bgp->peer, node, nnode, peer)) { ret = bgp_peer_clear(peer, afi, safi, nnode, stype); if (ret < 0) bgp_clear_vty_error(vty, peer, afi, safi, ret); } /* This is to apply read-only mode on this clear. */ if (stype == BGP_CLEAR_SOFT_NONE) bgp->update_delay_over = 0; return CMD_SUCCESS; } /* Clear specified neighbor. */ if (sort == clear_peer) { union sockunion su; /* Make sockunion for lookup. */ ret = str2sockunion(arg, &su); if (ret < 0) { peer = peer_lookup_by_conf_if(bgp, arg); if (!peer) { peer = peer_lookup_by_hostname(bgp, arg); if (!peer) { vty_out(vty, "Malformed address or name: %s\n", arg); return CMD_WARNING; } } } else { peer = peer_lookup(bgp, &su); if (!peer) { vty_out(vty, "%%BGP: Unknown neighbor - \"%s\"\n", arg); return CMD_WARNING; } } ret = bgp_peer_clear(peer, afi, safi, NULL, stype); /* if afi/safi not defined for this peer, let caller know */ if (ret == 1) ret = BGP_ERR_AF_UNCONFIGURED; if (ret < 0) bgp_clear_vty_error(vty, peer, afi, safi, ret); return CMD_SUCCESS; } /* Clear all neighbors belonging to a specific peer-group. */ if (sort == clear_group) { struct peer_group *group; group = peer_group_lookup(bgp, arg); if (!group) { vty_out(vty, "%%BGP: No such peer-group %s\n", arg); return CMD_WARNING; } for (ALL_LIST_ELEMENTS(group->peer, node, nnode, peer)) { ret = bgp_peer_clear(peer, afi, safi, nnode, stype); if (ret < 0) bgp_clear_vty_error(vty, peer, afi, safi, ret); else found = true; } if (!found) vty_out(vty, "%%BGP: No %s peer belonging to peer-group %s is configured\n", get_afi_safi_str(afi, safi, false), arg); return CMD_SUCCESS; } /* Clear all external (eBGP) neighbors. */ if (sort == clear_external) { for (ALL_LIST_ELEMENTS(bgp->peer, node, nnode, peer)) { if (peer->sort == BGP_PEER_IBGP) continue; ret = bgp_peer_clear(peer, afi, safi, nnode, stype); if (ret < 0) bgp_clear_vty_error(vty, peer, afi, safi, ret); else found = true; } if (!found) vty_out(vty, "%%BGP: No external %s peer is configured\n", get_afi_safi_str(afi, safi, false)); return CMD_SUCCESS; } /* Clear all neighbors belonging to a specific AS. */ if (sort == clear_as) { as_t as = strtoul(arg, NULL, 10); for (ALL_LIST_ELEMENTS(bgp->peer, node, nnode, peer)) { if (peer->as != as) continue; ret = bgp_peer_clear(peer, afi, safi, nnode, stype); if (ret < 0) bgp_clear_vty_error(vty, peer, afi, safi, ret); else found = true; } if (!found) vty_out(vty, "%%BGP: No %s peer is configured with AS %s\n", get_afi_safi_str(afi, safi, false), arg); return CMD_SUCCESS; } return CMD_SUCCESS; } static int bgp_clear_vty(struct vty *vty, const char *name, afi_t afi, safi_t safi, enum clear_sort sort, enum bgp_clear_type stype, const char *arg) { struct bgp *bgp; /* BGP structure lookup. */ if (name) { bgp = bgp_lookup_by_name(name); if (bgp == NULL) { vty_out(vty, "Can't find BGP instance %s\n", name); return CMD_WARNING; } } else { bgp = bgp_get_default(); if (bgp == NULL) { vty_out(vty, "No BGP process is configured\n"); return CMD_WARNING; } } return bgp_clear(vty, bgp, afi, safi, sort, stype, arg); } /* clear soft inbound */ static void bgp_clear_star_soft_in(struct vty *vty, const char *name) { afi_t afi; safi_t safi; FOREACH_AFI_SAFI (afi, safi) bgp_clear_vty(vty, name, afi, safi, clear_all, BGP_CLEAR_SOFT_IN, NULL); } /* clear soft outbound */ static void bgp_clear_star_soft_out(struct vty *vty, const char *name) { afi_t afi; safi_t safi; FOREACH_AFI_SAFI (afi, safi) bgp_clear_vty(vty, name, afi, safi, clear_all, BGP_CLEAR_SOFT_OUT, NULL); } #ifndef VTYSH_EXTRACT_PL #include "bgpd/bgp_vty_clippy.c" #endif DEFUN_HIDDEN (bgp_local_mac, bgp_local_mac_cmd, "bgp local-mac vni " CMD_VNI_RANGE " mac WORD seq (0-4294967295)", BGP_STR "Local MAC config\n" "VxLAN Network Identifier\n" "VNI number\n" "local mac\n" "mac address\n" "mac-mobility sequence\n" "seq number\n") { int rv; vni_t vni; struct ethaddr mac; struct ipaddr ip; uint32_t seq; struct bgp *bgp; vni = strtoul(argv[3]->arg, NULL, 10); if (!prefix_str2mac(argv[5]->arg, &mac)) { vty_out(vty, "%% Malformed MAC address\n"); return CMD_WARNING; } memset(&ip, 0, sizeof(ip)); seq = strtoul(argv[7]->arg, NULL, 10); bgp = bgp_get_default(); if (!bgp) { vty_out(vty, "Default BGP instance is not there\n"); return CMD_WARNING; } rv = bgp_evpn_local_macip_add(bgp, vni, &mac, &ip, 0 /* flags */, seq); if (rv < 0) { vty_out(vty, "Internal error\n"); return CMD_WARNING; } return CMD_SUCCESS; } DEFUN_HIDDEN (no_bgp_local_mac, no_bgp_local_mac_cmd, "no bgp local-mac vni " CMD_VNI_RANGE " mac WORD", NO_STR BGP_STR "Local MAC config\n" "VxLAN Network Identifier\n" "VNI number\n" "local mac\n" "mac address\n") { int rv; vni_t vni; struct ethaddr mac; struct ipaddr ip; struct bgp *bgp; vni = strtoul(argv[4]->arg, NULL, 10); if (!prefix_str2mac(argv[6]->arg, &mac)) { vty_out(vty, "%% Malformed MAC address\n"); return CMD_WARNING; } memset(&ip, 0, sizeof(ip)); bgp = bgp_get_default(); if (!bgp) { vty_out(vty, "Default BGP instance is not there\n"); return CMD_WARNING; } rv = bgp_evpn_local_macip_del(bgp, vni, &mac, &ip, ZEBRA_NEIGH_ACTIVE); if (rv < 0) { vty_out(vty, "Internal error\n"); return CMD_WARNING; } return CMD_SUCCESS; } DEFUN (no_synchronization, no_synchronization_cmd, "no synchronization", NO_STR "Perform IGP synchronization\n") { return CMD_SUCCESS; } DEFUN (no_auto_summary, no_auto_summary_cmd, "no auto-summary", NO_STR "Enable automatic network number summarization\n") { return CMD_SUCCESS; } /* "router bgp" commands. */ DEFUN_NOSH (router_bgp, router_bgp_cmd, "router bgp [(1-4294967295)$instasn [ VIEWVRFNAME]]", ROUTER_STR BGP_STR AS_STR BGP_INSTANCE_HELP_STR) { int idx_asn = 2; int idx_view_vrf = 3; int idx_vrf = 4; int is_new_bgp = 0; int ret; as_t as; struct bgp *bgp; const char *name = NULL; enum bgp_instance_type inst_type; // "router bgp" without an ASN if (argc == 2) { // Pending: Make VRF option available for ASN less config bgp = bgp_get_default(); if (bgp == NULL) { vty_out(vty, "%% No BGP process is configured\n"); return CMD_WARNING_CONFIG_FAILED; } if (listcount(bm->bgp) > 1) { vty_out(vty, "%% Please specify ASN and VRF\n"); return CMD_WARNING_CONFIG_FAILED; } } // "router bgp X" else { as = strtoul(argv[idx_asn]->arg, NULL, 10); inst_type = BGP_INSTANCE_TYPE_DEFAULT; if (argc > 3) { name = argv[idx_vrf]->arg; if (!strcmp(argv[idx_view_vrf]->text, "vrf")) { if (strmatch(name, VRF_DEFAULT_NAME)) name = NULL; else inst_type = BGP_INSTANCE_TYPE_VRF; } else if (!strcmp(argv[idx_view_vrf]->text, "view")) inst_type = BGP_INSTANCE_TYPE_VIEW; } if (inst_type == BGP_INSTANCE_TYPE_DEFAULT) is_new_bgp = (bgp_lookup(as, name) == NULL); ret = bgp_get(&bgp, &as, name, inst_type); switch (ret) { case BGP_ERR_AS_MISMATCH: vty_out(vty, "BGP is already running; AS is %u\n", as); return CMD_WARNING_CONFIG_FAILED; case BGP_ERR_INSTANCE_MISMATCH: vty_out(vty, "BGP instance name and AS number mismatch\n"); vty_out(vty, "BGP instance is already running; AS is %u\n", as); return CMD_WARNING_CONFIG_FAILED; } /* * If we just instantiated the default instance, complete * any pending VRF-VPN leaking that was configured via * earlier "router bgp X vrf FOO" blocks. */ if (is_new_bgp && inst_type == BGP_INSTANCE_TYPE_DEFAULT) vpn_leak_postchange_all(); if (inst_type == BGP_INSTANCE_TYPE_VRF) bgp_vpn_leak_export(bgp); /* Pending: handle when user tries to change a view to vrf n vv. */ } /* unset the auto created flag as the user config is now present */ UNSET_FLAG(bgp->vrf_flags, BGP_VRF_AUTO); VTY_PUSH_CONTEXT(BGP_NODE, bgp); return CMD_SUCCESS; } /* "no router bgp" commands. */ DEFUN (no_router_bgp, no_router_bgp_cmd, "no router bgp [(1-4294967295)$instasn [ VIEWVRFNAME]]", NO_STR ROUTER_STR BGP_STR AS_STR BGP_INSTANCE_HELP_STR) { int idx_asn = 3; int idx_vrf = 5; as_t as; struct bgp *bgp; const char *name = NULL; // "no router bgp" without an ASN if (argc == 3) { // Pending: Make VRF option available for ASN less config bgp = bgp_get_default(); if (bgp == NULL) { vty_out(vty, "%% No BGP process is configured\n"); return CMD_WARNING_CONFIG_FAILED; } if (listcount(bm->bgp) > 1) { vty_out(vty, "%% Please specify ASN and VRF\n"); return CMD_WARNING_CONFIG_FAILED; } if (bgp->l3vni) { vty_out(vty, "%% Please unconfigure l3vni %u", bgp->l3vni); return CMD_WARNING_CONFIG_FAILED; } } else { as = strtoul(argv[idx_asn]->arg, NULL, 10); if (argc > 4) name = argv[idx_vrf]->arg; /* Lookup bgp structure. */ bgp = bgp_lookup(as, name); if (!bgp) { vty_out(vty, "%% Can't find BGP instance\n"); return CMD_WARNING_CONFIG_FAILED; } if (bgp->l3vni) { vty_out(vty, "%% Please unconfigure l3vni %u\n", bgp->l3vni); return CMD_WARNING_CONFIG_FAILED; } /* Cannot delete default instance if vrf instances exist */ if (bgp->inst_type == BGP_INSTANCE_TYPE_DEFAULT) { struct listnode *node; struct bgp *tmp_bgp; for (ALL_LIST_ELEMENTS_RO(bm->bgp, node, tmp_bgp)) { if (tmp_bgp->inst_type == BGP_INSTANCE_TYPE_VRF) { vty_out(vty, "%% Cannot delete default BGP instance. Dependent VRF instances exist\n"); return CMD_WARNING_CONFIG_FAILED; } } } } if (bgp_vpn_leak_unimport(bgp, vty)) return CMD_WARNING_CONFIG_FAILED; bgp_delete(bgp); return CMD_SUCCESS; } /* BGP router-id. */ DEFPY (bgp_router_id, bgp_router_id_cmd, "bgp router-id A.B.C.D", BGP_STR "Override configured router identifier\n" "Manually configured router identifier\n") { VTY_DECLVAR_CONTEXT(bgp, bgp); bgp_router_id_static_set(bgp, router_id); return CMD_SUCCESS; } DEFPY (no_bgp_router_id, no_bgp_router_id_cmd, "no bgp router-id [A.B.C.D]", NO_STR BGP_STR "Override configured router identifier\n" "Manually configured router identifier\n") { VTY_DECLVAR_CONTEXT(bgp, bgp); if (router_id_str) { if (!IPV4_ADDR_SAME(&bgp->router_id_static, &router_id)) { vty_out(vty, "%% BGP router-id doesn't match\n"); return CMD_WARNING_CONFIG_FAILED; } } router_id.s_addr = 0; bgp_router_id_static_set(bgp, router_id); return CMD_SUCCESS; } /* BGP Cluster ID. */ DEFUN (bgp_cluster_id, bgp_cluster_id_cmd, "bgp cluster-id ", BGP_STR "Configure Route-Reflector Cluster-id\n" "Route-Reflector Cluster-id in IP address format\n" "Route-Reflector Cluster-id as 32 bit quantity\n") { VTY_DECLVAR_CONTEXT(bgp, bgp); int idx_ipv4 = 2; int ret; struct in_addr cluster; ret = inet_aton(argv[idx_ipv4]->arg, &cluster); if (!ret) { vty_out(vty, "%% Malformed bgp cluster identifier\n"); return CMD_WARNING_CONFIG_FAILED; } bgp_cluster_id_set(bgp, &cluster); bgp_clear_star_soft_out(vty, bgp->name); return CMD_SUCCESS; } DEFUN (no_bgp_cluster_id, no_bgp_cluster_id_cmd, "no bgp cluster-id []", NO_STR BGP_STR "Configure Route-Reflector Cluster-id\n" "Route-Reflector Cluster-id in IP address format\n" "Route-Reflector Cluster-id as 32 bit quantity\n") { VTY_DECLVAR_CONTEXT(bgp, bgp); bgp_cluster_id_unset(bgp); bgp_clear_star_soft_out(vty, bgp->name); return CMD_SUCCESS; } DEFUN (bgp_confederation_identifier, bgp_confederation_identifier_cmd, "bgp confederation identifier (1-4294967295)", "BGP specific commands\n" "AS confederation parameters\n" "AS number\n" "Set routing domain confederation AS\n") { VTY_DECLVAR_CONTEXT(bgp, bgp); int idx_number = 3; as_t as; as = strtoul(argv[idx_number]->arg, NULL, 10); bgp_confederation_id_set(bgp, as); return CMD_SUCCESS; } DEFUN (no_bgp_confederation_identifier, no_bgp_confederation_identifier_cmd, "no bgp confederation identifier [(1-4294967295)]", NO_STR "BGP specific commands\n" "AS confederation parameters\n" "AS number\n" "Set routing domain confederation AS\n") { VTY_DECLVAR_CONTEXT(bgp, bgp); bgp_confederation_id_unset(bgp); return CMD_SUCCESS; } DEFUN (bgp_confederation_peers, bgp_confederation_peers_cmd, "bgp confederation peers (1-4294967295)...", "BGP specific commands\n" "AS confederation parameters\n" "Peer ASs in BGP confederation\n" AS_STR) { VTY_DECLVAR_CONTEXT(bgp, bgp); int idx_asn = 3; as_t as; int i; for (i = idx_asn; i < argc; i++) { as = strtoul(argv[i]->arg, NULL, 10); if (bgp->as == as) { vty_out(vty, "%% Local member-AS not allowed in confed peer list\n"); continue; } bgp_confederation_peers_add(bgp, as); } return CMD_SUCCESS; } DEFUN (no_bgp_confederation_peers, no_bgp_confederation_peers_cmd, "no bgp confederation peers (1-4294967295)...", NO_STR "BGP specific commands\n" "AS confederation parameters\n" "Peer ASs in BGP confederation\n" AS_STR) { VTY_DECLVAR_CONTEXT(bgp, bgp); int idx_asn = 4; as_t as; int i; for (i = idx_asn; i < argc; i++) { as = strtoul(argv[i]->arg, NULL, 10); bgp_confederation_peers_remove(bgp, as); } return CMD_SUCCESS; } /** * Central routine for maximum-paths configuration. * @peer_type: BGP_PEER_EBGP or BGP_PEER_IBGP * @set: 1 for setting values, 0 for removing the max-paths config. */ static int bgp_maxpaths_config_vty(struct vty *vty, int peer_type, const char *mpaths, uint16_t options, int set) { VTY_DECLVAR_CONTEXT(bgp, bgp); uint16_t maxpaths = 0; int ret; afi_t afi; safi_t safi; afi = bgp_node_afi(vty); safi = bgp_node_safi(vty); if (set) { maxpaths = strtol(mpaths, NULL, 10); if (maxpaths > multipath_num) { vty_out(vty, "%% Maxpaths Specified: %d is > than multipath num specified on bgp command line %d", maxpaths, multipath_num); return CMD_WARNING_CONFIG_FAILED; } ret = bgp_maximum_paths_set(bgp, afi, safi, peer_type, maxpaths, options); } else ret = bgp_maximum_paths_unset(bgp, afi, safi, peer_type); if (ret < 0) { vty_out(vty, "%% Failed to %sset maximum-paths %s %u for afi %u, safi %u\n", (set == 1) ? "" : "un", (peer_type == BGP_PEER_EBGP) ? "ebgp" : "ibgp", maxpaths, afi, safi); return CMD_WARNING_CONFIG_FAILED; } bgp_recalculate_all_bestpaths(bgp); return CMD_SUCCESS; } DEFUN (bgp_maxmed_admin, bgp_maxmed_admin_cmd, "bgp max-med administrative ", BGP_STR "Advertise routes with max-med\n" "Administratively applied, for an indefinite period\n") { VTY_DECLVAR_CONTEXT(bgp, bgp); bgp->v_maxmed_admin = 1; bgp->maxmed_admin_value = BGP_MAXMED_VALUE_DEFAULT; bgp_maxmed_update(bgp); return CMD_SUCCESS; } DEFUN (bgp_maxmed_admin_medv, bgp_maxmed_admin_medv_cmd, "bgp max-med administrative (0-4294967295)", BGP_STR "Advertise routes with max-med\n" "Administratively applied, for an indefinite period\n" "Max MED value to be used\n") { VTY_DECLVAR_CONTEXT(bgp, bgp); int idx_number = 3; bgp->v_maxmed_admin = 1; bgp->maxmed_admin_value = strtoul(argv[idx_number]->arg, NULL, 10); bgp_maxmed_update(bgp); return CMD_SUCCESS; } DEFUN (no_bgp_maxmed_admin, no_bgp_maxmed_admin_cmd, "no bgp max-med administrative [(0-4294967295)]", NO_STR BGP_STR "Advertise routes with max-med\n" "Administratively applied, for an indefinite period\n" "Max MED value to be used\n") { VTY_DECLVAR_CONTEXT(bgp, bgp); bgp->v_maxmed_admin = BGP_MAXMED_ADMIN_UNCONFIGURED; bgp->maxmed_admin_value = BGP_MAXMED_VALUE_DEFAULT; bgp_maxmed_update(bgp); return CMD_SUCCESS; } DEFUN (bgp_maxmed_onstartup, bgp_maxmed_onstartup_cmd, "bgp max-med on-startup (5-86400) [(0-4294967295)]", BGP_STR "Advertise routes with max-med\n" "Effective on a startup\n" "Time (seconds) period for max-med\n" "Max MED value to be used\n") { VTY_DECLVAR_CONTEXT(bgp, bgp); int idx = 0; argv_find(argv, argc, "(5-86400)", &idx); bgp->v_maxmed_onstartup = strtoul(argv[idx]->arg, NULL, 10); if (argv_find(argv, argc, "(0-4294967295)", &idx)) bgp->maxmed_onstartup_value = strtoul(argv[idx]->arg, NULL, 10); else bgp->maxmed_onstartup_value = BGP_MAXMED_VALUE_DEFAULT; bgp_maxmed_update(bgp); return CMD_SUCCESS; } DEFUN (no_bgp_maxmed_onstartup, no_bgp_maxmed_onstartup_cmd, "no bgp max-med on-startup [(5-86400) [(0-4294967295)]]", NO_STR BGP_STR "Advertise routes with max-med\n" "Effective on a startup\n" "Time (seconds) period for max-med\n" "Max MED value to be used\n") { VTY_DECLVAR_CONTEXT(bgp, bgp); /* Cancel max-med onstartup if its on */ if (bgp->t_maxmed_onstartup) { THREAD_TIMER_OFF(bgp->t_maxmed_onstartup); bgp->maxmed_onstartup_over = 1; } bgp->v_maxmed_onstartup = BGP_MAXMED_ONSTARTUP_UNCONFIGURED; bgp->maxmed_onstartup_value = BGP_MAXMED_VALUE_DEFAULT; bgp_maxmed_update(bgp); return CMD_SUCCESS; } static int bgp_update_delay_config_vty(struct vty *vty, const char *delay, const char *wait) { VTY_DECLVAR_CONTEXT(bgp, bgp); uint16_t update_delay; uint16_t establish_wait; update_delay = strtoul(delay, NULL, 10); if (!wait) /* update-delay */ { bgp->v_update_delay = update_delay; bgp->v_establish_wait = bgp->v_update_delay; return CMD_SUCCESS; } /* update-delay */ establish_wait = atoi(wait); if (update_delay < establish_wait) { vty_out(vty, "%%Failed: update-delay less than the establish-wait!\n"); return CMD_WARNING_CONFIG_FAILED; } bgp->v_update_delay = update_delay; bgp->v_establish_wait = establish_wait; return CMD_SUCCESS; } static int bgp_update_delay_deconfig_vty(struct vty *vty) { VTY_DECLVAR_CONTEXT(bgp, bgp); bgp->v_update_delay = BGP_UPDATE_DELAY_DEF; bgp->v_establish_wait = bgp->v_update_delay; return CMD_SUCCESS; } void bgp_config_write_update_delay(struct vty *vty, struct bgp *bgp) { if (bgp->v_update_delay != BGP_UPDATE_DELAY_DEF) { vty_out(vty, " update-delay %d", bgp->v_update_delay); if (bgp->v_update_delay != bgp->v_establish_wait) vty_out(vty, " %d", bgp->v_establish_wait); vty_out(vty, "\n"); } } /* Update-delay configuration */ DEFUN (bgp_update_delay, bgp_update_delay_cmd, "update-delay (0-3600)", "Force initial delay for best-path and updates\n" "Seconds\n") { int idx_number = 1; return bgp_update_delay_config_vty(vty, argv[idx_number]->arg, NULL); } DEFUN (bgp_update_delay_establish_wait, bgp_update_delay_establish_wait_cmd, "update-delay (0-3600) (1-3600)", "Force initial delay for best-path and updates\n" "Seconds\n" "Seconds\n") { int idx_number = 1; int idx_number_2 = 2; return bgp_update_delay_config_vty(vty, argv[idx_number]->arg, argv[idx_number_2]->arg); } /* Update-delay deconfiguration */ DEFUN (no_bgp_update_delay, no_bgp_update_delay_cmd, "no update-delay [(0-3600) [(1-3600)]]", NO_STR "Force initial delay for best-path and updates\n" "Seconds\n" "Seconds\n") { return bgp_update_delay_deconfig_vty(vty); } static int bgp_wpkt_quanta_config_vty(struct vty *vty, const char *num, char set) { VTY_DECLVAR_CONTEXT(bgp, bgp); if (set) { uint32_t quanta = strtoul(num, NULL, 10); atomic_store_explicit(&bgp->wpkt_quanta, quanta, memory_order_relaxed); } else { atomic_store_explicit(&bgp->wpkt_quanta, BGP_WRITE_PACKET_MAX, memory_order_relaxed); } return CMD_SUCCESS; } static int bgp_rpkt_quanta_config_vty(struct vty *vty, const char *num, char set) { VTY_DECLVAR_CONTEXT(bgp, bgp); if (set) { uint32_t quanta = strtoul(num, NULL, 10); atomic_store_explicit(&bgp->rpkt_quanta, quanta, memory_order_relaxed); } else { atomic_store_explicit(&bgp->rpkt_quanta, BGP_READ_PACKET_MAX, memory_order_relaxed); } return CMD_SUCCESS; } void bgp_config_write_wpkt_quanta(struct vty *vty, struct bgp *bgp) { uint32_t quanta = atomic_load_explicit(&bgp->wpkt_quanta, memory_order_relaxed); if (quanta != BGP_WRITE_PACKET_MAX) vty_out(vty, " write-quanta %d\n", quanta); } void bgp_config_write_rpkt_quanta(struct vty *vty, struct bgp *bgp) { uint32_t quanta = atomic_load_explicit(&bgp->rpkt_quanta, memory_order_relaxed); if (quanta != BGP_READ_PACKET_MAX) vty_out(vty, " read-quanta %d\n", quanta); } /* Packet quanta configuration */ DEFUN (bgp_wpkt_quanta, bgp_wpkt_quanta_cmd, "write-quanta (1-10)", "How many packets to write to peer socket per run\n" "Number of packets\n") { int idx_number = 1; return bgp_wpkt_quanta_config_vty(vty, argv[idx_number]->arg, 1); } DEFUN (no_bgp_wpkt_quanta, no_bgp_wpkt_quanta_cmd, "no write-quanta (1-10)", NO_STR "How many packets to write to peer socket per I/O cycle\n" "Number of packets\n") { int idx_number = 2; return bgp_wpkt_quanta_config_vty(vty, argv[idx_number]->arg, 0); } DEFUN (bgp_rpkt_quanta, bgp_rpkt_quanta_cmd, "read-quanta (1-10)", "How many packets to read from peer socket per I/O cycle\n" "Number of packets\n") { int idx_number = 1; return bgp_rpkt_quanta_config_vty(vty, argv[idx_number]->arg, 1); } DEFUN (no_bgp_rpkt_quanta, no_bgp_rpkt_quanta_cmd, "no read-quanta (1-10)", NO_STR "How many packets to read from peer socket per I/O cycle\n" "Number of packets\n") { int idx_number = 2; return bgp_rpkt_quanta_config_vty(vty, argv[idx_number]->arg, 0); } void bgp_config_write_coalesce_time(struct vty *vty, struct bgp *bgp) { if (!bgp->heuristic_coalesce) vty_out(vty, " coalesce-time %u\n", bgp->coalesce_time); } DEFUN (bgp_coalesce_time, bgp_coalesce_time_cmd, "coalesce-time (0-4294967295)", "Subgroup coalesce timer\n" "Subgroup coalesce timer value (in ms)\n") { VTY_DECLVAR_CONTEXT(bgp, bgp); int idx = 0; argv_find(argv, argc, "(0-4294967295)", &idx); bgp->heuristic_coalesce = false; bgp->coalesce_time = strtoul(argv[idx]->arg, NULL, 10); return CMD_SUCCESS; } DEFUN (no_bgp_coalesce_time, no_bgp_coalesce_time_cmd, "no coalesce-time (0-4294967295)", NO_STR "Subgroup coalesce timer\n" "Subgroup coalesce timer value (in ms)\n") { VTY_DECLVAR_CONTEXT(bgp, bgp); bgp->heuristic_coalesce = true; bgp->coalesce_time = BGP_DEFAULT_SUBGROUP_COALESCE_TIME; return CMD_SUCCESS; } /* Maximum-paths configuration */ DEFUN (bgp_maxpaths, bgp_maxpaths_cmd, "maximum-paths " CMD_RANGE_STR(1, MULTIPATH_NUM), "Forward packets over multiple paths\n" "Number of paths\n") { int idx_number = 1; return bgp_maxpaths_config_vty(vty, BGP_PEER_EBGP, argv[idx_number]->arg, 0, 1); } ALIAS_HIDDEN(bgp_maxpaths, bgp_maxpaths_hidden_cmd, "maximum-paths " CMD_RANGE_STR(1, MULTIPATH_NUM), "Forward packets over multiple paths\n" "Number of paths\n") DEFUN (bgp_maxpaths_ibgp, bgp_maxpaths_ibgp_cmd, "maximum-paths ibgp " CMD_RANGE_STR(1, MULTIPATH_NUM), "Forward packets over multiple paths\n" "iBGP-multipath\n" "Number of paths\n") { int idx_number = 2; return bgp_maxpaths_config_vty(vty, BGP_PEER_IBGP, argv[idx_number]->arg, 0, 1); } ALIAS_HIDDEN(bgp_maxpaths_ibgp, bgp_maxpaths_ibgp_hidden_cmd, "maximum-paths ibgp " CMD_RANGE_STR(1, MULTIPATH_NUM), "Forward packets over multiple paths\n" "iBGP-multipath\n" "Number of paths\n") DEFUN (bgp_maxpaths_ibgp_cluster, bgp_maxpaths_ibgp_cluster_cmd, "maximum-paths ibgp " CMD_RANGE_STR(1, MULTIPATH_NUM) " equal-cluster-length", "Forward packets over multiple paths\n" "iBGP-multipath\n" "Number of paths\n" "Match the cluster length\n") { int idx_number = 2; return bgp_maxpaths_config_vty( vty, BGP_PEER_IBGP, argv[idx_number]->arg, BGP_FLAG_IBGP_MULTIPATH_SAME_CLUSTERLEN, 1); } ALIAS_HIDDEN(bgp_maxpaths_ibgp_cluster, bgp_maxpaths_ibgp_cluster_hidden_cmd, "maximum-paths ibgp " CMD_RANGE_STR( 1, MULTIPATH_NUM) " equal-cluster-length", "Forward packets over multiple paths\n" "iBGP-multipath\n" "Number of paths\n" "Match the cluster length\n") DEFUN (no_bgp_maxpaths, no_bgp_maxpaths_cmd, "no maximum-paths [" CMD_RANGE_STR(1, MULTIPATH_NUM) "]", NO_STR "Forward packets over multiple paths\n" "Number of paths\n") { return bgp_maxpaths_config_vty(vty, BGP_PEER_EBGP, NULL, 0, 0); } ALIAS_HIDDEN(no_bgp_maxpaths, no_bgp_maxpaths_hidden_cmd, "no maximum-paths [" CMD_RANGE_STR(1, MULTIPATH_NUM) "]", NO_STR "Forward packets over multiple paths\n" "Number of paths\n") DEFUN (no_bgp_maxpaths_ibgp, no_bgp_maxpaths_ibgp_cmd, "no maximum-paths ibgp [" CMD_RANGE_STR(1, MULTIPATH_NUM) " [equal-cluster-length]]", NO_STR "Forward packets over multiple paths\n" "iBGP-multipath\n" "Number of paths\n" "Match the cluster length\n") { return bgp_maxpaths_config_vty(vty, BGP_PEER_IBGP, NULL, 0, 0); } ALIAS_HIDDEN(no_bgp_maxpaths_ibgp, no_bgp_maxpaths_ibgp_hidden_cmd, "no maximum-paths ibgp [" CMD_RANGE_STR( 1, MULTIPATH_NUM) " [equal-cluster-length]]", NO_STR "Forward packets over multiple paths\n" "iBGP-multipath\n" "Number of paths\n" "Match the cluster length\n") void bgp_config_write_maxpaths(struct vty *vty, struct bgp *bgp, afi_t afi, safi_t safi) { if (bgp->maxpaths[afi][safi].maxpaths_ebgp != MULTIPATH_NUM) { vty_out(vty, " maximum-paths %d\n", bgp->maxpaths[afi][safi].maxpaths_ebgp); } if (bgp->maxpaths[afi][safi].maxpaths_ibgp != MULTIPATH_NUM) { vty_out(vty, " maximum-paths ibgp %d", bgp->maxpaths[afi][safi].maxpaths_ibgp); if (CHECK_FLAG(bgp->maxpaths[afi][safi].ibgp_flags, BGP_FLAG_IBGP_MULTIPATH_SAME_CLUSTERLEN)) vty_out(vty, " equal-cluster-length"); vty_out(vty, "\n"); } } /* BGP timers. */ DEFUN (bgp_timers, bgp_timers_cmd, "timers bgp (0-65535) (0-65535)", "Adjust routing timers\n" "BGP timers\n" "Keepalive interval\n" "Holdtime\n") { VTY_DECLVAR_CONTEXT(bgp, bgp); int idx_number = 2; int idx_number_2 = 3; unsigned long keepalive = 0; unsigned long holdtime = 0; keepalive = strtoul(argv[idx_number]->arg, NULL, 10); holdtime = strtoul(argv[idx_number_2]->arg, NULL, 10); /* Holdtime value check. */ if (holdtime < 3 && holdtime != 0) { vty_out(vty, "%% hold time value must be either 0 or greater than 3\n"); return CMD_WARNING_CONFIG_FAILED; } bgp_timers_set(bgp, keepalive, holdtime); return CMD_SUCCESS; } DEFUN (no_bgp_timers, no_bgp_timers_cmd, "no timers bgp [(0-65535) (0-65535)]", NO_STR "Adjust routing timers\n" "BGP timers\n" "Keepalive interval\n" "Holdtime\n") { VTY_DECLVAR_CONTEXT(bgp, bgp); bgp_timers_unset(bgp); return CMD_SUCCESS; } DEFUN (bgp_client_to_client_reflection, bgp_client_to_client_reflection_cmd, "bgp client-to-client reflection", "BGP specific commands\n" "Configure client to client route reflection\n" "reflection of routes allowed\n") { VTY_DECLVAR_CONTEXT(bgp, bgp); bgp_flag_unset(bgp, BGP_FLAG_NO_CLIENT_TO_CLIENT); bgp_clear_star_soft_out(vty, bgp->name); return CMD_SUCCESS; } DEFUN (no_bgp_client_to_client_reflection, no_bgp_client_to_client_reflection_cmd, "no bgp client-to-client reflection", NO_STR "BGP specific commands\n" "Configure client to client route reflection\n" "reflection of routes allowed\n") { VTY_DECLVAR_CONTEXT(bgp, bgp); bgp_flag_set(bgp, BGP_FLAG_NO_CLIENT_TO_CLIENT); bgp_clear_star_soft_out(vty, bgp->name); return CMD_SUCCESS; } /* "bgp always-compare-med" configuration. */ DEFUN (bgp_always_compare_med, bgp_always_compare_med_cmd, "bgp always-compare-med", "BGP specific commands\n" "Allow comparing MED from different neighbors\n") { VTY_DECLVAR_CONTEXT(bgp, bgp); bgp_flag_set(bgp, BGP_FLAG_ALWAYS_COMPARE_MED); bgp_recalculate_all_bestpaths(bgp); return CMD_SUCCESS; } DEFUN (no_bgp_always_compare_med, no_bgp_always_compare_med_cmd, "no bgp always-compare-med", NO_STR "BGP specific commands\n" "Allow comparing MED from different neighbors\n") { VTY_DECLVAR_CONTEXT(bgp, bgp); bgp_flag_unset(bgp, BGP_FLAG_ALWAYS_COMPARE_MED); bgp_recalculate_all_bestpaths(bgp); return CMD_SUCCESS; } DEFUN(bgp_ebgp_requires_policy, bgp_ebgp_requires_policy_cmd, "bgp ebgp-requires-policy", "BGP specific commands\n" "Require in and out policy for eBGP peers (RFC8212)\n") { VTY_DECLVAR_CONTEXT(bgp, bgp); bgp->ebgp_requires_policy = DEFAULT_EBGP_POLICY_ENABLED; return CMD_SUCCESS; } DEFUN(no_bgp_ebgp_requires_policy, no_bgp_ebgp_requires_policy_cmd, "no bgp ebgp-requires-policy", NO_STR "BGP specific commands\n" "Require in and out policy for eBGP peers (RFC8212)\n") { VTY_DECLVAR_CONTEXT(bgp, bgp); bgp->ebgp_requires_policy = DEFAULT_EBGP_POLICY_DISABLED; return CMD_SUCCESS; } /* "bgp deterministic-med" configuration. */ DEFUN (bgp_deterministic_med, bgp_deterministic_med_cmd, "bgp deterministic-med", "BGP specific commands\n" "Pick the best-MED path among paths advertised from the neighboring AS\n") { VTY_DECLVAR_CONTEXT(bgp, bgp); if (!bgp_flag_check(bgp, BGP_FLAG_DETERMINISTIC_MED)) { bgp_flag_set(bgp, BGP_FLAG_DETERMINISTIC_MED); bgp_recalculate_all_bestpaths(bgp); } return CMD_SUCCESS; } DEFUN (no_bgp_deterministic_med, no_bgp_deterministic_med_cmd, "no bgp deterministic-med", NO_STR "BGP specific commands\n" "Pick the best-MED path among paths advertised from the neighboring AS\n") { VTY_DECLVAR_CONTEXT(bgp, bgp); int bestpath_per_as_used; afi_t afi; safi_t safi; struct peer *peer; struct listnode *node, *nnode; if (bgp_flag_check(bgp, BGP_FLAG_DETERMINISTIC_MED)) { bestpath_per_as_used = 0; for (ALL_LIST_ELEMENTS(bgp->peer, node, nnode, peer)) { FOREACH_AFI_SAFI (afi, safi) if (bgp_addpath_dmed_required( peer->addpath_type[afi][safi])) { bestpath_per_as_used = 1; break; } if (bestpath_per_as_used) break; } if (bestpath_per_as_used) { vty_out(vty, "bgp deterministic-med cannot be disabled while addpath-tx-bestpath-per-AS is in use\n"); return CMD_WARNING_CONFIG_FAILED; } else { bgp_flag_unset(bgp, BGP_FLAG_DETERMINISTIC_MED); bgp_recalculate_all_bestpaths(bgp); } } return CMD_SUCCESS; } /* "bgp graceful-restart" configuration. */ DEFUN (bgp_graceful_restart, bgp_graceful_restart_cmd, "bgp graceful-restart", "BGP specific commands\n" "Graceful restart capability parameters\n") { VTY_DECLVAR_CONTEXT(bgp, bgp); bgp_flag_set(bgp, BGP_FLAG_GRACEFUL_RESTART); return CMD_SUCCESS; } DEFUN (no_bgp_graceful_restart, no_bgp_graceful_restart_cmd, "no bgp graceful-restart", NO_STR "BGP specific commands\n" "Graceful restart capability parameters\n") { VTY_DECLVAR_CONTEXT(bgp, bgp); bgp_flag_unset(bgp, BGP_FLAG_GRACEFUL_RESTART); return CMD_SUCCESS; } DEFUN (bgp_graceful_restart_stalepath_time, bgp_graceful_restart_stalepath_time_cmd, "bgp graceful-restart stalepath-time (1-4095)", "BGP specific commands\n" "Graceful restart capability parameters\n" "Set the max time to hold onto restarting peer's stale paths\n" "Delay value (seconds)\n") { VTY_DECLVAR_CONTEXT(bgp, bgp); int idx_number = 3; uint32_t stalepath; stalepath = strtoul(argv[idx_number]->arg, NULL, 10); bgp->stalepath_time = stalepath; return CMD_SUCCESS; } DEFUN (bgp_graceful_restart_restart_time, bgp_graceful_restart_restart_time_cmd, "bgp graceful-restart restart-time (1-4095)", "BGP specific commands\n" "Graceful restart capability parameters\n" "Set the time to wait to delete stale routes before a BGP open message is received\n" "Delay value (seconds)\n") { VTY_DECLVAR_CONTEXT(bgp, bgp); int idx_number = 3; uint32_t restart; restart = strtoul(argv[idx_number]->arg, NULL, 10); bgp->restart_time = restart; return CMD_SUCCESS; } DEFUN (no_bgp_graceful_restart_stalepath_time, no_bgp_graceful_restart_stalepath_time_cmd, "no bgp graceful-restart stalepath-time [(1-4095)]", NO_STR "BGP specific commands\n" "Graceful restart capability parameters\n" "Set the max time to hold onto restarting peer's stale paths\n" "Delay value (seconds)\n") { VTY_DECLVAR_CONTEXT(bgp, bgp); bgp->stalepath_time = BGP_DEFAULT_STALEPATH_TIME; return CMD_SUCCESS; } DEFUN (no_bgp_graceful_restart_restart_time, no_bgp_graceful_restart_restart_time_cmd, "no bgp graceful-restart restart-time [(1-4095)]", NO_STR "BGP specific commands\n" "Graceful restart capability parameters\n" "Set the time to wait to delete stale routes before a BGP open message is received\n" "Delay value (seconds)\n") { VTY_DECLVAR_CONTEXT(bgp, bgp); bgp->restart_time = BGP_DEFAULT_RESTART_TIME; return CMD_SUCCESS; } DEFUN (bgp_graceful_restart_preserve_fw, bgp_graceful_restart_preserve_fw_cmd, "bgp graceful-restart preserve-fw-state", "BGP specific commands\n" "Graceful restart capability parameters\n" "Sets F-bit indication that fib is preserved while doing Graceful Restart\n") { VTY_DECLVAR_CONTEXT(bgp, bgp); bgp_flag_set(bgp, BGP_FLAG_GR_PRESERVE_FWD); return CMD_SUCCESS; } DEFUN (no_bgp_graceful_restart_preserve_fw, no_bgp_graceful_restart_preserve_fw_cmd, "no bgp graceful-restart preserve-fw-state", NO_STR "BGP specific commands\n" "Graceful restart capability parameters\n" "Unsets F-bit indication that fib is preserved while doing Graceful Restart\n") { VTY_DECLVAR_CONTEXT(bgp, bgp); bgp_flag_unset(bgp, BGP_FLAG_GR_PRESERVE_FWD); return CMD_SUCCESS; } /* "bgp graceful-shutdown" configuration */ DEFUN (bgp_graceful_shutdown, bgp_graceful_shutdown_cmd, "bgp graceful-shutdown", BGP_STR "Graceful shutdown parameters\n") { VTY_DECLVAR_CONTEXT(bgp, bgp); if (!bgp_flag_check(bgp, BGP_FLAG_GRACEFUL_SHUTDOWN)) { bgp_flag_set(bgp, BGP_FLAG_GRACEFUL_SHUTDOWN); bgp_static_redo_import_check(bgp); bgp_redistribute_redo(bgp); bgp_clear_star_soft_out(vty, bgp->name); bgp_clear_star_soft_in(vty, bgp->name); } return CMD_SUCCESS; } DEFUN (no_bgp_graceful_shutdown, no_bgp_graceful_shutdown_cmd, "no bgp graceful-shutdown", NO_STR BGP_STR "Graceful shutdown parameters\n") { VTY_DECLVAR_CONTEXT(bgp, bgp); if (bgp_flag_check(bgp, BGP_FLAG_GRACEFUL_SHUTDOWN)) { bgp_flag_unset(bgp, BGP_FLAG_GRACEFUL_SHUTDOWN); bgp_static_redo_import_check(bgp); bgp_redistribute_redo(bgp); bgp_clear_star_soft_out(vty, bgp->name); bgp_clear_star_soft_in(vty, bgp->name); } return CMD_SUCCESS; } /* "bgp fast-external-failover" configuration. */ DEFUN (bgp_fast_external_failover, bgp_fast_external_failover_cmd, "bgp fast-external-failover", BGP_STR "Immediately reset session if a link to a directly connected external peer goes down\n") { VTY_DECLVAR_CONTEXT(bgp, bgp); bgp_flag_unset(bgp, BGP_FLAG_NO_FAST_EXT_FAILOVER); return CMD_SUCCESS; } DEFUN (no_bgp_fast_external_failover, no_bgp_fast_external_failover_cmd, "no bgp fast-external-failover", NO_STR BGP_STR "Immediately reset session if a link to a directly connected external peer goes down\n") { VTY_DECLVAR_CONTEXT(bgp, bgp); bgp_flag_set(bgp, BGP_FLAG_NO_FAST_EXT_FAILOVER); return CMD_SUCCESS; } /* "bgp bestpath compare-routerid" configuration. */ DEFUN (bgp_bestpath_compare_router_id, bgp_bestpath_compare_router_id_cmd, "bgp bestpath compare-routerid", "BGP specific commands\n" "Change the default bestpath selection\n" "Compare router-id for identical EBGP paths\n") { VTY_DECLVAR_CONTEXT(bgp, bgp); bgp_flag_set(bgp, BGP_FLAG_COMPARE_ROUTER_ID); bgp_recalculate_all_bestpaths(bgp); return CMD_SUCCESS; } DEFUN (no_bgp_bestpath_compare_router_id, no_bgp_bestpath_compare_router_id_cmd, "no bgp bestpath compare-routerid", NO_STR "BGP specific commands\n" "Change the default bestpath selection\n" "Compare router-id for identical EBGP paths\n") { VTY_DECLVAR_CONTEXT(bgp, bgp); bgp_flag_unset(bgp, BGP_FLAG_COMPARE_ROUTER_ID); bgp_recalculate_all_bestpaths(bgp); return CMD_SUCCESS; } /* "bgp bestpath as-path ignore" configuration. */ DEFUN (bgp_bestpath_aspath_ignore, bgp_bestpath_aspath_ignore_cmd, "bgp bestpath as-path ignore", "BGP specific commands\n" "Change the default bestpath selection\n" "AS-path attribute\n" "Ignore as-path length in selecting a route\n") { VTY_DECLVAR_CONTEXT(bgp, bgp); bgp_flag_set(bgp, BGP_FLAG_ASPATH_IGNORE); bgp_recalculate_all_bestpaths(bgp); return CMD_SUCCESS; } DEFUN (no_bgp_bestpath_aspath_ignore, no_bgp_bestpath_aspath_ignore_cmd, "no bgp bestpath as-path ignore", NO_STR "BGP specific commands\n" "Change the default bestpath selection\n" "AS-path attribute\n" "Ignore as-path length in selecting a route\n") { VTY_DECLVAR_CONTEXT(bgp, bgp); bgp_flag_unset(bgp, BGP_FLAG_ASPATH_IGNORE); bgp_recalculate_all_bestpaths(bgp); return CMD_SUCCESS; } /* "bgp bestpath as-path confed" configuration. */ DEFUN (bgp_bestpath_aspath_confed, bgp_bestpath_aspath_confed_cmd, "bgp bestpath as-path confed", "BGP specific commands\n" "Change the default bestpath selection\n" "AS-path attribute\n" "Compare path lengths including confederation sets & sequences in selecting a route\n") { VTY_DECLVAR_CONTEXT(bgp, bgp); bgp_flag_set(bgp, BGP_FLAG_ASPATH_CONFED); bgp_recalculate_all_bestpaths(bgp); return CMD_SUCCESS; } DEFUN (no_bgp_bestpath_aspath_confed, no_bgp_bestpath_aspath_confed_cmd, "no bgp bestpath as-path confed", NO_STR "BGP specific commands\n" "Change the default bestpath selection\n" "AS-path attribute\n" "Compare path lengths including confederation sets & sequences in selecting a route\n") { VTY_DECLVAR_CONTEXT(bgp, bgp); bgp_flag_unset(bgp, BGP_FLAG_ASPATH_CONFED); bgp_recalculate_all_bestpaths(bgp); return CMD_SUCCESS; } /* "bgp bestpath as-path multipath-relax" configuration. */ DEFUN (bgp_bestpath_aspath_multipath_relax, bgp_bestpath_aspath_multipath_relax_cmd, "bgp bestpath as-path multipath-relax []", "BGP specific commands\n" "Change the default bestpath selection\n" "AS-path attribute\n" "Allow load sharing across routes that have different AS paths (but same length)\n" "Generate an AS_SET\n" "Do not generate an AS_SET\n") { VTY_DECLVAR_CONTEXT(bgp, bgp); int idx = 0; bgp_flag_set(bgp, BGP_FLAG_ASPATH_MULTIPATH_RELAX); /* no-as-set is now the default behavior so we can silently * ignore it */ if (argv_find(argv, argc, "as-set", &idx)) bgp_flag_set(bgp, BGP_FLAG_MULTIPATH_RELAX_AS_SET); else bgp_flag_unset(bgp, BGP_FLAG_MULTIPATH_RELAX_AS_SET); bgp_recalculate_all_bestpaths(bgp); return CMD_SUCCESS; } DEFUN (no_bgp_bestpath_aspath_multipath_relax, no_bgp_bestpath_aspath_multipath_relax_cmd, "no bgp bestpath as-path multipath-relax []", NO_STR "BGP specific commands\n" "Change the default bestpath selection\n" "AS-path attribute\n" "Allow load sharing across routes that have different AS paths (but same length)\n" "Generate an AS_SET\n" "Do not generate an AS_SET\n") { VTY_DECLVAR_CONTEXT(bgp, bgp); bgp_flag_unset(bgp, BGP_FLAG_ASPATH_MULTIPATH_RELAX); bgp_flag_unset(bgp, BGP_FLAG_MULTIPATH_RELAX_AS_SET); bgp_recalculate_all_bestpaths(bgp); return CMD_SUCCESS; } /* "bgp log-neighbor-changes" configuration. */ DEFUN (bgp_log_neighbor_changes, bgp_log_neighbor_changes_cmd, "bgp log-neighbor-changes", "BGP specific commands\n" "Log neighbor up/down and reset reason\n") { VTY_DECLVAR_CONTEXT(bgp, bgp); bgp_flag_set(bgp, BGP_FLAG_LOG_NEIGHBOR_CHANGES); return CMD_SUCCESS; } DEFUN (no_bgp_log_neighbor_changes, no_bgp_log_neighbor_changes_cmd, "no bgp log-neighbor-changes", NO_STR "BGP specific commands\n" "Log neighbor up/down and reset reason\n") { VTY_DECLVAR_CONTEXT(bgp, bgp); bgp_flag_unset(bgp, BGP_FLAG_LOG_NEIGHBOR_CHANGES); return CMD_SUCCESS; } /* "bgp bestpath med" configuration. */ DEFUN (bgp_bestpath_med, bgp_bestpath_med_cmd, "bgp bestpath med ", "BGP specific commands\n" "Change the default bestpath selection\n" "MED attribute\n" "Compare MED among confederation paths\n" "Treat missing MED as the least preferred one\n" "Treat missing MED as the least preferred one\n" "Compare MED among confederation paths\n") { VTY_DECLVAR_CONTEXT(bgp, bgp); int idx = 0; if (argv_find(argv, argc, "confed", &idx)) bgp_flag_set(bgp, BGP_FLAG_MED_CONFED); idx = 0; if (argv_find(argv, argc, "missing-as-worst", &idx)) bgp_flag_set(bgp, BGP_FLAG_MED_MISSING_AS_WORST); bgp_recalculate_all_bestpaths(bgp); return CMD_SUCCESS; } DEFUN (no_bgp_bestpath_med, no_bgp_bestpath_med_cmd, "no bgp bestpath med ", NO_STR "BGP specific commands\n" "Change the default bestpath selection\n" "MED attribute\n" "Compare MED among confederation paths\n" "Treat missing MED as the least preferred one\n" "Treat missing MED as the least preferred one\n" "Compare MED among confederation paths\n") { VTY_DECLVAR_CONTEXT(bgp, bgp); int idx = 0; if (argv_find(argv, argc, "confed", &idx)) bgp_flag_unset(bgp, BGP_FLAG_MED_CONFED); idx = 0; if (argv_find(argv, argc, "missing-as-worst", &idx)) bgp_flag_unset(bgp, BGP_FLAG_MED_MISSING_AS_WORST); bgp_recalculate_all_bestpaths(bgp); return CMD_SUCCESS; } /* "no bgp default ipv4-unicast". */ DEFUN (no_bgp_default_ipv4_unicast, no_bgp_default_ipv4_unicast_cmd, "no bgp default ipv4-unicast", NO_STR "BGP specific commands\n" "Configure BGP defaults\n" "Activate ipv4-unicast for a peer by default\n") { VTY_DECLVAR_CONTEXT(bgp, bgp); bgp_flag_set(bgp, BGP_FLAG_NO_DEFAULT_IPV4); return CMD_SUCCESS; } DEFUN (bgp_default_ipv4_unicast, bgp_default_ipv4_unicast_cmd, "bgp default ipv4-unicast", "BGP specific commands\n" "Configure BGP defaults\n" "Activate ipv4-unicast for a peer by default\n") { VTY_DECLVAR_CONTEXT(bgp, bgp); bgp_flag_unset(bgp, BGP_FLAG_NO_DEFAULT_IPV4); return CMD_SUCCESS; } /* Display hostname in certain command outputs */ DEFUN (bgp_default_show_hostname, bgp_default_show_hostname_cmd, "bgp default show-hostname", "BGP specific commands\n" "Configure BGP defaults\n" "Show hostname in certain command outputs\n") { VTY_DECLVAR_CONTEXT(bgp, bgp); bgp_flag_set(bgp, BGP_FLAG_SHOW_HOSTNAME); return CMD_SUCCESS; } DEFUN (no_bgp_default_show_hostname, no_bgp_default_show_hostname_cmd, "no bgp default show-hostname", NO_STR "BGP specific commands\n" "Configure BGP defaults\n" "Show hostname in certain command outputs\n") { VTY_DECLVAR_CONTEXT(bgp, bgp); bgp_flag_unset(bgp, BGP_FLAG_SHOW_HOSTNAME); return CMD_SUCCESS; } /* "bgp network import-check" configuration. */ DEFUN (bgp_network_import_check, bgp_network_import_check_cmd, "bgp network import-check", "BGP specific commands\n" "BGP network command\n" "Check BGP network route exists in IGP\n") { VTY_DECLVAR_CONTEXT(bgp, bgp); if (!bgp_flag_check(bgp, BGP_FLAG_IMPORT_CHECK)) { bgp_flag_set(bgp, BGP_FLAG_IMPORT_CHECK); bgp_static_redo_import_check(bgp); } return CMD_SUCCESS; } ALIAS_HIDDEN(bgp_network_import_check, bgp_network_import_check_exact_cmd, "bgp network import-check exact", "BGP specific commands\n" "BGP network command\n" "Check BGP network route exists in IGP\n" "Match route precisely\n") DEFUN (no_bgp_network_import_check, no_bgp_network_import_check_cmd, "no bgp network import-check", NO_STR "BGP specific commands\n" "BGP network command\n" "Check BGP network route exists in IGP\n") { VTY_DECLVAR_CONTEXT(bgp, bgp); if (bgp_flag_check(bgp, BGP_FLAG_IMPORT_CHECK)) { bgp_flag_unset(bgp, BGP_FLAG_IMPORT_CHECK); bgp_static_redo_import_check(bgp); } return CMD_SUCCESS; } DEFUN (bgp_default_local_preference, bgp_default_local_preference_cmd, "bgp default local-preference (0-4294967295)", "BGP specific commands\n" "Configure BGP defaults\n" "local preference (higher=more preferred)\n" "Configure default local preference value\n") { VTY_DECLVAR_CONTEXT(bgp, bgp); int idx_number = 3; uint32_t local_pref; local_pref = strtoul(argv[idx_number]->arg, NULL, 10); bgp_default_local_preference_set(bgp, local_pref); bgp_clear_star_soft_in(vty, bgp->name); return CMD_SUCCESS; } DEFUN (no_bgp_default_local_preference, no_bgp_default_local_preference_cmd, "no bgp default local-preference [(0-4294967295)]", NO_STR "BGP specific commands\n" "Configure BGP defaults\n" "local preference (higher=more preferred)\n" "Configure default local preference value\n") { VTY_DECLVAR_CONTEXT(bgp, bgp); bgp_default_local_preference_unset(bgp); bgp_clear_star_soft_in(vty, bgp->name); return CMD_SUCCESS; } DEFUN (bgp_default_subgroup_pkt_queue_max, bgp_default_subgroup_pkt_queue_max_cmd, "bgp default subgroup-pkt-queue-max (20-100)", "BGP specific commands\n" "Configure BGP defaults\n" "subgroup-pkt-queue-max\n" "Configure subgroup packet queue max\n") { VTY_DECLVAR_CONTEXT(bgp, bgp); int idx_number = 3; uint32_t max_size; max_size = strtoul(argv[idx_number]->arg, NULL, 10); bgp_default_subgroup_pkt_queue_max_set(bgp, max_size); return CMD_SUCCESS; } DEFUN (no_bgp_default_subgroup_pkt_queue_max, no_bgp_default_subgroup_pkt_queue_max_cmd, "no bgp default subgroup-pkt-queue-max [(20-100)]", NO_STR "BGP specific commands\n" "Configure BGP defaults\n" "subgroup-pkt-queue-max\n" "Configure subgroup packet queue max\n") { VTY_DECLVAR_CONTEXT(bgp, bgp); bgp_default_subgroup_pkt_queue_max_unset(bgp); return CMD_SUCCESS; } DEFUN (bgp_rr_allow_outbound_policy, bgp_rr_allow_outbound_policy_cmd, "bgp route-reflector allow-outbound-policy", "BGP specific commands\n" "Allow modifications made by out route-map\n" "on ibgp neighbors\n") { VTY_DECLVAR_CONTEXT(bgp, bgp); if (!bgp_flag_check(bgp, BGP_FLAG_RR_ALLOW_OUTBOUND_POLICY)) { bgp_flag_set(bgp, BGP_FLAG_RR_ALLOW_OUTBOUND_POLICY); update_group_announce_rrclients(bgp); bgp_clear_star_soft_out(vty, bgp->name); } return CMD_SUCCESS; } DEFUN (no_bgp_rr_allow_outbound_policy, no_bgp_rr_allow_outbound_policy_cmd, "no bgp route-reflector allow-outbound-policy", NO_STR "BGP specific commands\n" "Allow modifications made by out route-map\n" "on ibgp neighbors\n") { VTY_DECLVAR_CONTEXT(bgp, bgp); if (bgp_flag_check(bgp, BGP_FLAG_RR_ALLOW_OUTBOUND_POLICY)) { bgp_flag_unset(bgp, BGP_FLAG_RR_ALLOW_OUTBOUND_POLICY); update_group_announce_rrclients(bgp); bgp_clear_star_soft_out(vty, bgp->name); } return CMD_SUCCESS; } DEFUN (bgp_listen_limit, bgp_listen_limit_cmd, "bgp listen limit (1-5000)", "BGP specific commands\n" "Configure BGP defaults\n" "maximum number of BGP Dynamic Neighbors that can be created\n" "Configure Dynamic Neighbors listen limit value\n") { VTY_DECLVAR_CONTEXT(bgp, bgp); int idx_number = 3; int listen_limit; listen_limit = strtoul(argv[idx_number]->arg, NULL, 10); bgp_listen_limit_set(bgp, listen_limit); return CMD_SUCCESS; } DEFUN (no_bgp_listen_limit, no_bgp_listen_limit_cmd, "no bgp listen limit [(1-5000)]", "BGP specific commands\n" "Configure BGP defaults\n" "unset maximum number of BGP Dynamic Neighbors that can be created\n" "Configure Dynamic Neighbors listen limit value to default\n" "Configure Dynamic Neighbors listen limit value\n") { VTY_DECLVAR_CONTEXT(bgp, bgp); bgp_listen_limit_unset(bgp); return CMD_SUCCESS; } /* * Check if this listen range is already configured. Check for exact * match or overlap based on input. */ static struct peer_group *listen_range_exists(struct bgp *bgp, struct prefix *range, int exact) { struct listnode *node, *nnode; struct listnode *node1, *nnode1; struct peer_group *group; struct prefix *lr; afi_t afi; int match; afi = family2afi(range->family); for (ALL_LIST_ELEMENTS(bgp->group, node, nnode, group)) { for (ALL_LIST_ELEMENTS(group->listen_range[afi], node1, nnode1, lr)) { if (exact) match = prefix_same(range, lr); else match = (prefix_match(range, lr) || prefix_match(lr, range)); if (match) return group; } } return NULL; } DEFUN (bgp_listen_range, bgp_listen_range_cmd, "bgp listen range peer-group PGNAME", "BGP specific commands\n" "Configure BGP dynamic neighbors listen range\n" "Configure BGP dynamic neighbors listen range\n" NEIGHBOR_ADDR_STR "Member of the peer-group\n" "Peer-group name\n") { VTY_DECLVAR_CONTEXT(bgp, bgp); struct prefix range; struct peer_group *group, *existing_group; afi_t afi; int ret; int idx = 0; argv_find(argv, argc, "A.B.C.D/M", &idx); argv_find(argv, argc, "X:X::X:X/M", &idx); char *prefix = argv[idx]->arg; argv_find(argv, argc, "PGNAME", &idx); char *peergroup = argv[idx]->arg; /* Convert IP prefix string to struct prefix. */ ret = str2prefix(prefix, &range); if (!ret) { vty_out(vty, "%% Malformed listen range\n"); return CMD_WARNING_CONFIG_FAILED; } afi = family2afi(range.family); if (afi == AFI_IP6 && IN6_IS_ADDR_LINKLOCAL(&range.u.prefix6)) { vty_out(vty, "%% Malformed listen range (link-local address)\n"); return CMD_WARNING_CONFIG_FAILED; } apply_mask(&range); /* Check if same listen range is already configured. */ existing_group = listen_range_exists(bgp, &range, 1); if (existing_group) { if (strcmp(existing_group->name, peergroup) == 0) return CMD_SUCCESS; else { vty_out(vty, "%% Same listen range is attached to peer-group %s\n", existing_group->name); return CMD_WARNING_CONFIG_FAILED; } } /* Check if an overlapping listen range exists. */ if (listen_range_exists(bgp, &range, 0)) { vty_out(vty, "%% Listen range overlaps with existing listen range\n"); return CMD_WARNING_CONFIG_FAILED; } group = peer_group_lookup(bgp, peergroup); if (!group) { vty_out(vty, "%% Configure the peer-group first\n"); return CMD_WARNING_CONFIG_FAILED; } ret = peer_group_listen_range_add(group, &range); return bgp_vty_return(vty, ret); } DEFUN (no_bgp_listen_range, no_bgp_listen_range_cmd, "no bgp listen range peer-group PGNAME", NO_STR "BGP specific commands\n" "Unconfigure BGP dynamic neighbors listen range\n" "Unconfigure BGP dynamic neighbors listen range\n" NEIGHBOR_ADDR_STR "Member of the peer-group\n" "Peer-group name\n") { VTY_DECLVAR_CONTEXT(bgp, bgp); struct prefix range; struct peer_group *group; afi_t afi; int ret; int idx = 0; argv_find(argv, argc, "A.B.C.D/M", &idx); argv_find(argv, argc, "X:X::X:X/M", &idx); char *prefix = argv[idx]->arg; argv_find(argv, argc, "PGNAME", &idx); char *peergroup = argv[idx]->arg; /* Convert IP prefix string to struct prefix. */ ret = str2prefix(prefix, &range); if (!ret) { vty_out(vty, "%% Malformed listen range\n"); return CMD_WARNING_CONFIG_FAILED; } afi = family2afi(range.family); if (afi == AFI_IP6 && IN6_IS_ADDR_LINKLOCAL(&range.u.prefix6)) { vty_out(vty, "%% Malformed listen range (link-local address)\n"); return CMD_WARNING_CONFIG_FAILED; } apply_mask(&range); group = peer_group_lookup(bgp, peergroup); if (!group) { vty_out(vty, "%% Peer-group does not exist\n"); return CMD_WARNING_CONFIG_FAILED; } ret = peer_group_listen_range_del(group, &range); return bgp_vty_return(vty, ret); } void bgp_config_write_listen(struct vty *vty, struct bgp *bgp) { struct peer_group *group; struct listnode *node, *nnode, *rnode, *nrnode; struct prefix *range; afi_t afi; char buf[PREFIX2STR_BUFFER]; if (bgp->dynamic_neighbors_limit != BGP_DYNAMIC_NEIGHBORS_LIMIT_DEFAULT) vty_out(vty, " bgp listen limit %d\n", bgp->dynamic_neighbors_limit); for (ALL_LIST_ELEMENTS(bgp->group, node, nnode, group)) { for (afi = AFI_IP; afi < AFI_MAX; afi++) { for (ALL_LIST_ELEMENTS(group->listen_range[afi], rnode, nrnode, range)) { prefix2str(range, buf, sizeof(buf)); vty_out(vty, " bgp listen range %s peer-group %s\n", buf, group->name); } } } } DEFUN (bgp_disable_connected_route_check, bgp_disable_connected_route_check_cmd, "bgp disable-ebgp-connected-route-check", "BGP specific commands\n" "Disable checking if nexthop is connected on ebgp sessions\n") { VTY_DECLVAR_CONTEXT(bgp, bgp); bgp_flag_set(bgp, BGP_FLAG_DISABLE_NH_CONNECTED_CHK); bgp_clear_star_soft_in(vty, bgp->name); return CMD_SUCCESS; } DEFUN (no_bgp_disable_connected_route_check, no_bgp_disable_connected_route_check_cmd, "no bgp disable-ebgp-connected-route-check", NO_STR "BGP specific commands\n" "Disable checking if nexthop is connected on ebgp sessions\n") { VTY_DECLVAR_CONTEXT(bgp, bgp); bgp_flag_unset(bgp, BGP_FLAG_DISABLE_NH_CONNECTED_CHK); bgp_clear_star_soft_in(vty, bgp->name); return CMD_SUCCESS; } static int peer_remote_as_vty(struct vty *vty, const char *peer_str, const char *as_str, afi_t afi, safi_t safi) { VTY_DECLVAR_CONTEXT(bgp, bgp); int ret; as_t as; int as_type = AS_SPECIFIED; union sockunion su; if (as_str[0] == 'i') { as = 0; as_type = AS_INTERNAL; } else if (as_str[0] == 'e') { as = 0; as_type = AS_EXTERNAL; } else { /* Get AS number. */ as = strtoul(as_str, NULL, 10); } /* If peer is peer group or interface peer, call proper function. */ ret = str2sockunion(peer_str, &su); if (ret < 0) { struct peer *peer; /* Check if existing interface peer */ peer = peer_lookup_by_conf_if(bgp, peer_str); ret = peer_remote_as(bgp, NULL, peer_str, &as, as_type, afi, safi); /* if not interface peer, check peer-group settings */ if (ret < 0 && !peer) { ret = peer_group_remote_as(bgp, peer_str, &as, as_type); if (ret < 0) { vty_out(vty, "%% Create the peer-group or interface first\n"); return CMD_WARNING_CONFIG_FAILED; } return CMD_SUCCESS; } } else { if (peer_address_self_check(bgp, &su)) { vty_out(vty, "%% Can not configure the local system as neighbor\n"); return CMD_WARNING_CONFIG_FAILED; } ret = peer_remote_as(bgp, &su, NULL, &as, as_type, afi, safi); } /* This peer belongs to peer group. */ switch (ret) { case BGP_ERR_PEER_GROUP_MEMBER: vty_out(vty, "%% Peer-group member cannot override remote-as of peer-group\n"); return CMD_WARNING_CONFIG_FAILED; case BGP_ERR_PEER_GROUP_PEER_TYPE_DIFFERENT: vty_out(vty, "%% Peer-group members must be all internal or all external\n"); return CMD_WARNING_CONFIG_FAILED; } return bgp_vty_return(vty, ret); } DEFUN (bgp_default_shutdown, bgp_default_shutdown_cmd, "[no] bgp default shutdown", NO_STR BGP_STR "Configure BGP defaults\n" "Apply administrative shutdown to newly configured peers\n") { VTY_DECLVAR_CONTEXT(bgp, bgp); bgp->autoshutdown = !strmatch(argv[0]->text, "no"); return CMD_SUCCESS; } DEFUN (neighbor_remote_as, neighbor_remote_as_cmd, "neighbor remote-as <(1-4294967295)|internal|external>", NEIGHBOR_STR NEIGHBOR_ADDR_STR2 "Specify a BGP neighbor\n" AS_STR "Internal BGP peer\n" "External BGP peer\n") { int idx_peer = 1; int idx_remote_as = 3; return peer_remote_as_vty(vty, argv[idx_peer]->arg, argv[idx_remote_as]->arg, AFI_IP, SAFI_UNICAST); } static int peer_conf_interface_get(struct vty *vty, const char *conf_if, afi_t afi, safi_t safi, int v6only, const char *peer_group_name, const char *as_str) { VTY_DECLVAR_CONTEXT(bgp, bgp); as_t as = 0; int as_type = AS_UNSPECIFIED; struct peer *peer; struct peer_group *group; int ret = 0; union sockunion su; group = peer_group_lookup(bgp, conf_if); if (group) { vty_out(vty, "%% Name conflict with peer-group \n"); return CMD_WARNING_CONFIG_FAILED; } if (as_str) { if (as_str[0] == 'i') { as_type = AS_INTERNAL; } else if (as_str[0] == 'e') { as_type = AS_EXTERNAL; } else { /* Get AS number. */ as = strtoul(as_str, NULL, 10); as_type = AS_SPECIFIED; } } peer = peer_lookup_by_conf_if(bgp, conf_if); if (peer) { if (as_str) ret = peer_remote_as(bgp, NULL, conf_if, &as, as_type, afi, safi); } else { if (bgp_flag_check(bgp, BGP_FLAG_NO_DEFAULT_IPV4) && afi == AFI_IP && safi == SAFI_UNICAST) peer = peer_create(NULL, conf_if, bgp, bgp->as, as, as_type, 0, 0, NULL); else peer = peer_create(NULL, conf_if, bgp, bgp->as, as, as_type, afi, safi, NULL); if (!peer) { vty_out(vty, "%% BGP failed to create peer\n"); return CMD_WARNING_CONFIG_FAILED; } if (v6only) peer_flag_set(peer, PEER_FLAG_IFPEER_V6ONLY); /* Request zebra to initiate IPv6 RAs on this interface. We do * this * any unnumbered peer in order to not worry about run-time * transitions * (e.g., peering is initially IPv4, but the IPv4 /30 or /31 * address * gets deleted later etc.) */ if (peer->ifp) bgp_zebra_initiate_radv(bgp, peer); } if ((v6only && !CHECK_FLAG(peer->flags, PEER_FLAG_IFPEER_V6ONLY)) || (!v6only && CHECK_FLAG(peer->flags, PEER_FLAG_IFPEER_V6ONLY))) { if (v6only) peer_flag_set(peer, PEER_FLAG_IFPEER_V6ONLY); else peer_flag_unset(peer, PEER_FLAG_IFPEER_V6ONLY); /* v6only flag changed. Reset bgp seesion */ if (BGP_IS_VALID_STATE_FOR_NOTIF(peer->status)) { peer->last_reset = PEER_DOWN_V6ONLY_CHANGE; bgp_notify_send(peer, BGP_NOTIFY_CEASE, BGP_NOTIFY_CEASE_CONFIG_CHANGE); } else bgp_session_reset(peer); } if (!CHECK_FLAG(peer->flags_invert, PEER_FLAG_CAPABILITY_ENHE)) { SET_FLAG(peer->flags, PEER_FLAG_CAPABILITY_ENHE); SET_FLAG(peer->flags_invert, PEER_FLAG_CAPABILITY_ENHE); SET_FLAG(peer->flags_override, PEER_FLAG_CAPABILITY_ENHE); } if (peer_group_name) { group = peer_group_lookup(bgp, peer_group_name); if (!group) { vty_out(vty, "%% Configure the peer-group first\n"); return CMD_WARNING_CONFIG_FAILED; } ret = peer_group_bind(bgp, &su, peer, group, &as); } return bgp_vty_return(vty, ret); } DEFUN (neighbor_interface_config, neighbor_interface_config_cmd, "neighbor WORD interface [peer-group PGNAME]", NEIGHBOR_STR "Interface name or neighbor tag\n" "Enable BGP on interface\n" "Member of the peer-group\n" "Peer-group name\n") { int idx_word = 1; int idx_peer_group_word = 4; if (argc > idx_peer_group_word) return peer_conf_interface_get( vty, argv[idx_word]->arg, AFI_IP, SAFI_UNICAST, 0, argv[idx_peer_group_word]->arg, NULL); else return peer_conf_interface_get(vty, argv[idx_word]->arg, AFI_IP, SAFI_UNICAST, 0, NULL, NULL); } DEFUN (neighbor_interface_config_v6only, neighbor_interface_config_v6only_cmd, "neighbor WORD interface v6only [peer-group PGNAME]", NEIGHBOR_STR "Interface name or neighbor tag\n" "Enable BGP on interface\n" "Enable BGP with v6 link-local only\n" "Member of the peer-group\n" "Peer-group name\n") { int idx_word = 1; int idx_peer_group_word = 5; if (argc > idx_peer_group_word) return peer_conf_interface_get( vty, argv[idx_word]->arg, AFI_IP, SAFI_UNICAST, 1, argv[idx_peer_group_word]->arg, NULL); return peer_conf_interface_get(vty, argv[idx_word]->arg, AFI_IP, SAFI_UNICAST, 1, NULL, NULL); } DEFUN (neighbor_interface_config_remote_as, neighbor_interface_config_remote_as_cmd, "neighbor WORD interface remote-as <(1-4294967295)|internal|external>", NEIGHBOR_STR "Interface name or neighbor tag\n" "Enable BGP on interface\n" "Specify a BGP neighbor\n" AS_STR "Internal BGP peer\n" "External BGP peer\n") { int idx_word = 1; int idx_remote_as = 4; return peer_conf_interface_get(vty, argv[idx_word]->arg, AFI_IP, SAFI_UNICAST, 0, NULL, argv[idx_remote_as]->arg); } DEFUN (neighbor_interface_v6only_config_remote_as, neighbor_interface_v6only_config_remote_as_cmd, "neighbor WORD interface v6only remote-as <(1-4294967295)|internal|external>", NEIGHBOR_STR "Interface name or neighbor tag\n" "Enable BGP with v6 link-local only\n" "Enable BGP on interface\n" "Specify a BGP neighbor\n" AS_STR "Internal BGP peer\n" "External BGP peer\n") { int idx_word = 1; int idx_remote_as = 5; return peer_conf_interface_get(vty, argv[idx_word]->arg, AFI_IP, SAFI_UNICAST, 1, NULL, argv[idx_remote_as]->arg); } DEFUN (neighbor_peer_group, neighbor_peer_group_cmd, "neighbor WORD peer-group", NEIGHBOR_STR "Interface name or neighbor tag\n" "Configure peer-group\n") { VTY_DECLVAR_CONTEXT(bgp, bgp); int idx_word = 1; struct peer *peer; struct peer_group *group; peer = peer_lookup_by_conf_if(bgp, argv[idx_word]->arg); if (peer) { vty_out(vty, "%% Name conflict with interface: \n"); return CMD_WARNING_CONFIG_FAILED; } group = peer_group_get(bgp, argv[idx_word]->arg); if (!group) { vty_out(vty, "%% BGP failed to find or create peer-group\n"); return CMD_WARNING_CONFIG_FAILED; } return CMD_SUCCESS; } DEFUN (no_neighbor, no_neighbor_cmd, "no neighbor [remote-as <(1-4294967295)|internal|external>]>", NO_STR NEIGHBOR_STR NEIGHBOR_ADDR_STR2 "Specify a BGP neighbor\n" AS_STR "Internal BGP peer\n" "External BGP peer\n") { VTY_DECLVAR_CONTEXT(bgp, bgp); int idx_peer = 2; int ret; union sockunion su; struct peer_group *group; struct peer *peer; struct peer *other; ret = str2sockunion(argv[idx_peer]->arg, &su); if (ret < 0) { /* look up for neighbor by interface name config. */ peer = peer_lookup_by_conf_if(bgp, argv[idx_peer]->arg); if (peer) { /* Request zebra to terminate IPv6 RAs on this * interface. */ if (peer->ifp) bgp_zebra_terminate_radv(peer->bgp, peer); peer_notify_unconfig(peer); peer_delete(peer); return CMD_SUCCESS; } group = peer_group_lookup(bgp, argv[idx_peer]->arg); if (group) { peer_group_notify_unconfig(group); peer_group_delete(group); } else { vty_out(vty, "%% Create the peer-group first\n"); return CMD_WARNING_CONFIG_FAILED; } } else { peer = peer_lookup(bgp, &su); if (peer) { if (peer_dynamic_neighbor(peer)) { vty_out(vty, "%% Operation not allowed on a dynamic neighbor\n"); return CMD_WARNING_CONFIG_FAILED; } other = peer->doppelganger; peer_notify_unconfig(peer); peer_delete(peer); if (other && other->status != Deleted) { peer_notify_unconfig(other); peer_delete(other); } } } return CMD_SUCCESS; } DEFUN (no_neighbor_interface_config, no_neighbor_interface_config_cmd, "no neighbor WORD interface [v6only] [peer-group PGNAME] [remote-as <(1-4294967295)|internal|external>]", NO_STR NEIGHBOR_STR "Interface name\n" "Configure BGP on interface\n" "Enable BGP with v6 link-local only\n" "Member of the peer-group\n" "Peer-group name\n" "Specify a BGP neighbor\n" AS_STR "Internal BGP peer\n" "External BGP peer\n") { VTY_DECLVAR_CONTEXT(bgp, bgp); int idx_word = 2; struct peer *peer; /* look up for neighbor by interface name config. */ peer = peer_lookup_by_conf_if(bgp, argv[idx_word]->arg); if (peer) { /* Request zebra to terminate IPv6 RAs on this interface. */ if (peer->ifp) bgp_zebra_terminate_radv(peer->bgp, peer); peer_notify_unconfig(peer); peer_delete(peer); } else { vty_out(vty, "%% Create the bgp interface first\n"); return CMD_WARNING_CONFIG_FAILED; } return CMD_SUCCESS; } DEFUN (no_neighbor_peer_group, no_neighbor_peer_group_cmd, "no neighbor WORD peer-group", NO_STR NEIGHBOR_STR "Neighbor tag\n" "Configure peer-group\n") { VTY_DECLVAR_CONTEXT(bgp, bgp); int idx_word = 2; struct peer_group *group; group = peer_group_lookup(bgp, argv[idx_word]->arg); if (group) { peer_group_notify_unconfig(group); peer_group_delete(group); } else { vty_out(vty, "%% Create the peer-group first\n"); return CMD_WARNING_CONFIG_FAILED; } return CMD_SUCCESS; } DEFUN (no_neighbor_interface_peer_group_remote_as, no_neighbor_interface_peer_group_remote_as_cmd, "no neighbor WORD remote-as <(1-4294967295)|internal|external>", NO_STR NEIGHBOR_STR "Interface name or neighbor tag\n" "Specify a BGP neighbor\n" AS_STR "Internal BGP peer\n" "External BGP peer\n") { VTY_DECLVAR_CONTEXT(bgp, bgp); int idx_word = 2; struct peer_group *group; struct peer *peer; /* look up for neighbor by interface name config. */ peer = peer_lookup_by_conf_if(bgp, argv[idx_word]->arg); if (peer) { peer_as_change(peer, 0, AS_UNSPECIFIED); return CMD_SUCCESS; } group = peer_group_lookup(bgp, argv[idx_word]->arg); if (group) peer_group_remote_as_delete(group); else { vty_out(vty, "%% Create the peer-group or interface first\n"); return CMD_WARNING_CONFIG_FAILED; } return CMD_SUCCESS; } DEFUN (neighbor_local_as, neighbor_local_as_cmd, "neighbor local-as (1-4294967295)", NEIGHBOR_STR NEIGHBOR_ADDR_STR2 "Specify a local-as number\n" "AS number used as local AS\n") { int idx_peer = 1; int idx_number = 3; struct peer *peer; int ret; as_t as; peer = peer_and_group_lookup_vty(vty, argv[idx_peer]->arg); if (!peer) return CMD_WARNING_CONFIG_FAILED; as = strtoul(argv[idx_number]->arg, NULL, 10); ret = peer_local_as_set(peer, as, 0, 0); return bgp_vty_return(vty, ret); } DEFUN (neighbor_local_as_no_prepend, neighbor_local_as_no_prepend_cmd, "neighbor local-as (1-4294967295) no-prepend", NEIGHBOR_STR NEIGHBOR_ADDR_STR2 "Specify a local-as number\n" "AS number used as local AS\n" "Do not prepend local-as to updates from ebgp peers\n") { int idx_peer = 1; int idx_number = 3; struct peer *peer; int ret; as_t as; peer = peer_and_group_lookup_vty(vty, argv[idx_peer]->arg); if (!peer) return CMD_WARNING_CONFIG_FAILED; as = strtoul(argv[idx_number]->arg, NULL, 10); ret = peer_local_as_set(peer, as, 1, 0); return bgp_vty_return(vty, ret); } DEFUN (neighbor_local_as_no_prepend_replace_as, neighbor_local_as_no_prepend_replace_as_cmd, "neighbor local-as (1-4294967295) no-prepend replace-as", NEIGHBOR_STR NEIGHBOR_ADDR_STR2 "Specify a local-as number\n" "AS number used as local AS\n" "Do not prepend local-as to updates from ebgp peers\n" "Do not prepend local-as to updates from ibgp peers\n") { int idx_peer = 1; int idx_number = 3; struct peer *peer; int ret; as_t as; peer = peer_and_group_lookup_vty(vty, argv[idx_peer]->arg); if (!peer) return CMD_WARNING_CONFIG_FAILED; as = strtoul(argv[idx_number]->arg, NULL, 10); ret = peer_local_as_set(peer, as, 1, 1); return bgp_vty_return(vty, ret); } DEFUN (no_neighbor_local_as, no_neighbor_local_as_cmd, "no neighbor local-as [(1-4294967295) [no-prepend [replace-as]]]", NO_STR NEIGHBOR_STR NEIGHBOR_ADDR_STR2 "Specify a local-as number\n" "AS number used as local AS\n" "Do not prepend local-as to updates from ebgp peers\n" "Do not prepend local-as to updates from ibgp peers\n") { int idx_peer = 2; struct peer *peer; int ret; peer = peer_and_group_lookup_vty(vty, argv[idx_peer]->arg); if (!peer) return CMD_WARNING_CONFIG_FAILED; ret = peer_local_as_unset(peer); return bgp_vty_return(vty, ret); } DEFUN (neighbor_solo, neighbor_solo_cmd, "neighbor solo", NEIGHBOR_STR NEIGHBOR_ADDR_STR2 "Solo peer - part of its own update group\n") { int idx_peer = 1; struct peer *peer; int ret; peer = peer_and_group_lookup_vty(vty, argv[idx_peer]->arg); if (!peer) return CMD_WARNING_CONFIG_FAILED; ret = update_group_adjust_soloness(peer, 1); return bgp_vty_return(vty, ret); } DEFUN (no_neighbor_solo, no_neighbor_solo_cmd, "no neighbor solo", NO_STR NEIGHBOR_STR NEIGHBOR_ADDR_STR2 "Solo peer - part of its own update group\n") { int idx_peer = 2; struct peer *peer; int ret; peer = peer_and_group_lookup_vty(vty, argv[idx_peer]->arg); if (!peer) return CMD_WARNING_CONFIG_FAILED; ret = update_group_adjust_soloness(peer, 0); return bgp_vty_return(vty, ret); } DEFUN (neighbor_password, neighbor_password_cmd, "neighbor password LINE", NEIGHBOR_STR NEIGHBOR_ADDR_STR2 "Set a password\n" "The password\n") { int idx_peer = 1; int idx_line = 3; struct peer *peer; int ret; peer = peer_and_group_lookup_vty(vty, argv[idx_peer]->arg); if (!peer) return CMD_WARNING_CONFIG_FAILED; ret = peer_password_set(peer, argv[idx_line]->arg); return bgp_vty_return(vty, ret); } DEFUN (no_neighbor_password, no_neighbor_password_cmd, "no neighbor password [LINE]", NO_STR NEIGHBOR_STR NEIGHBOR_ADDR_STR2 "Set a password\n" "The password\n") { int idx_peer = 2; struct peer *peer; int ret; peer = peer_and_group_lookup_vty(vty, argv[idx_peer]->arg); if (!peer) return CMD_WARNING_CONFIG_FAILED; ret = peer_password_unset(peer); return bgp_vty_return(vty, ret); } DEFUN (neighbor_activate, neighbor_activate_cmd, "neighbor activate", NEIGHBOR_STR NEIGHBOR_ADDR_STR2 "Enable the Address Family for this Neighbor\n") { int idx_peer = 1; int ret; struct peer *peer; peer = peer_and_group_lookup_vty(vty, argv[idx_peer]->arg); if (!peer) return CMD_WARNING_CONFIG_FAILED; ret = peer_activate(peer, bgp_node_afi(vty), bgp_node_safi(vty)); return bgp_vty_return(vty, ret); } ALIAS_HIDDEN(neighbor_activate, neighbor_activate_hidden_cmd, "neighbor activate", NEIGHBOR_STR NEIGHBOR_ADDR_STR2 "Enable the Address Family for this Neighbor\n") DEFUN (no_neighbor_activate, no_neighbor_activate_cmd, "no neighbor activate", NO_STR NEIGHBOR_STR NEIGHBOR_ADDR_STR2 "Enable the Address Family for this Neighbor\n") { int idx_peer = 2; int ret; struct peer *peer; /* Lookup peer. */ peer = peer_and_group_lookup_vty(vty, argv[idx_peer]->arg); if (!peer) return CMD_WARNING_CONFIG_FAILED; ret = peer_deactivate(peer, bgp_node_afi(vty), bgp_node_safi(vty)); return bgp_vty_return(vty, ret); } ALIAS_HIDDEN(no_neighbor_activate, no_neighbor_activate_hidden_cmd, "no neighbor activate", NO_STR NEIGHBOR_STR NEIGHBOR_ADDR_STR2 "Enable the Address Family for this Neighbor\n") DEFUN (neighbor_set_peer_group, neighbor_set_peer_group_cmd, "neighbor peer-group PGNAME", NEIGHBOR_STR NEIGHBOR_ADDR_STR2 "Member of the peer-group\n" "Peer-group name\n") { VTY_DECLVAR_CONTEXT(bgp, bgp); int idx_peer = 1; int idx_word = 3; int ret; as_t as; union sockunion su; struct peer *peer; struct peer_group *group; ret = str2sockunion(argv[idx_peer]->arg, &su); if (ret < 0) { peer = peer_lookup_by_conf_if(bgp, argv[idx_peer]->arg); if (!peer) { vty_out(vty, "%% Malformed address or name: %s\n", argv[idx_peer]->arg); return CMD_WARNING_CONFIG_FAILED; } } else { if (peer_address_self_check(bgp, &su)) { vty_out(vty, "%% Can not configure the local system as neighbor\n"); return CMD_WARNING_CONFIG_FAILED; } /* Disallow for dynamic neighbor. */ peer = peer_lookup(bgp, &su); if (peer && peer_dynamic_neighbor(peer)) { vty_out(vty, "%% Operation not allowed on a dynamic neighbor\n"); return CMD_WARNING_CONFIG_FAILED; } } group = peer_group_lookup(bgp, argv[idx_word]->arg); if (!group) { vty_out(vty, "%% Configure the peer-group first\n"); return CMD_WARNING_CONFIG_FAILED; } ret = peer_group_bind(bgp, &su, peer, group, &as); if (ret == BGP_ERR_PEER_GROUP_PEER_TYPE_DIFFERENT) { vty_out(vty, "%% Peer with AS %u cannot be in this peer-group, members must be all internal or all external\n", as); return CMD_WARNING_CONFIG_FAILED; } return bgp_vty_return(vty, ret); } ALIAS_HIDDEN(neighbor_set_peer_group, neighbor_set_peer_group_hidden_cmd, "neighbor peer-group PGNAME", NEIGHBOR_STR NEIGHBOR_ADDR_STR2 "Member of the peer-group\n" "Peer-group name\n") DEFUN (no_neighbor_set_peer_group, no_neighbor_set_peer_group_cmd, "no neighbor peer-group PGNAME", NO_STR NEIGHBOR_STR NEIGHBOR_ADDR_STR2 "Member of the peer-group\n" "Peer-group name\n") { VTY_DECLVAR_CONTEXT(bgp, bgp); int idx_peer = 2; int idx_word = 4; int ret; struct peer *peer; struct peer_group *group; peer = peer_lookup_vty(vty, argv[idx_peer]->arg); if (!peer) return CMD_WARNING_CONFIG_FAILED; group = peer_group_lookup(bgp, argv[idx_word]->arg); if (!group) { vty_out(vty, "%% Configure the peer-group first\n"); return CMD_WARNING_CONFIG_FAILED; } peer_notify_unconfig(peer); ret = peer_delete(peer); return bgp_vty_return(vty, ret); } ALIAS_HIDDEN(no_neighbor_set_peer_group, no_neighbor_set_peer_group_hidden_cmd, "no neighbor peer-group PGNAME", NO_STR NEIGHBOR_STR NEIGHBOR_ADDR_STR2 "Member of the peer-group\n" "Peer-group name\n") static int peer_flag_modify_vty(struct vty *vty, const char *ip_str, uint32_t flag, int set) { int ret; struct peer *peer; peer = peer_and_group_lookup_vty(vty, ip_str); if (!peer) return CMD_WARNING_CONFIG_FAILED; /* * If 'neighbor ', then this is for directly connected peers, * we should not accept disable-connected-check. */ if (peer->conf_if && (flag == PEER_FLAG_DISABLE_CONNECTED_CHECK)) { vty_out(vty, "%s is directly connected peer, cannot accept disable-" "connected-check\n", ip_str); return CMD_WARNING_CONFIG_FAILED; } if (!set && flag == PEER_FLAG_SHUTDOWN) peer_tx_shutdown_message_unset(peer); if (set) ret = peer_flag_set(peer, flag); else ret = peer_flag_unset(peer, flag); return bgp_vty_return(vty, ret); } static int peer_flag_set_vty(struct vty *vty, const char *ip_str, uint32_t flag) { return peer_flag_modify_vty(vty, ip_str, flag, 1); } static int peer_flag_unset_vty(struct vty *vty, const char *ip_str, uint32_t flag) { return peer_flag_modify_vty(vty, ip_str, flag, 0); } /* neighbor passive. */ DEFUN (neighbor_passive, neighbor_passive_cmd, "neighbor passive", NEIGHBOR_STR NEIGHBOR_ADDR_STR2 "Don't send open messages to this neighbor\n") { int idx_peer = 1; return peer_flag_set_vty(vty, argv[idx_peer]->arg, PEER_FLAG_PASSIVE); } DEFUN (no_neighbor_passive, no_neighbor_passive_cmd, "no neighbor passive", NO_STR NEIGHBOR_STR NEIGHBOR_ADDR_STR2 "Don't send open messages to this neighbor\n") { int idx_peer = 2; return peer_flag_unset_vty(vty, argv[idx_peer]->arg, PEER_FLAG_PASSIVE); } /* neighbor shutdown. */ DEFUN (neighbor_shutdown_msg, neighbor_shutdown_msg_cmd, "neighbor shutdown message MSG...", NEIGHBOR_STR NEIGHBOR_ADDR_STR2 "Administratively shut down this neighbor\n" "Add a shutdown message (draft-ietf-idr-shutdown-06)\n" "Shutdown message\n") { int idx_peer = 1; if (argc >= 5) { struct peer *peer = peer_and_group_lookup_vty(vty, argv[idx_peer]->arg); char *message; if (!peer) return CMD_WARNING_CONFIG_FAILED; message = argv_concat(argv, argc, 4); peer_tx_shutdown_message_set(peer, message); XFREE(MTYPE_TMP, message); } return peer_flag_set_vty(vty, argv[idx_peer]->arg, PEER_FLAG_SHUTDOWN); } ALIAS(neighbor_shutdown_msg, neighbor_shutdown_cmd, "neighbor shutdown", NEIGHBOR_STR NEIGHBOR_ADDR_STR2 "Administratively shut down this neighbor\n") DEFUN (no_neighbor_shutdown_msg, no_neighbor_shutdown_msg_cmd, "no neighbor shutdown message MSG...", NO_STR NEIGHBOR_STR NEIGHBOR_ADDR_STR2 "Administratively shut down this neighbor\n" "Remove a shutdown message (draft-ietf-idr-shutdown-06)\n" "Shutdown message\n") { int idx_peer = 2; return peer_flag_unset_vty(vty, argv[idx_peer]->arg, PEER_FLAG_SHUTDOWN); } ALIAS(no_neighbor_shutdown_msg, no_neighbor_shutdown_cmd, "no neighbor shutdown", NO_STR NEIGHBOR_STR NEIGHBOR_ADDR_STR2 "Administratively shut down this neighbor\n") /* neighbor capability dynamic. */ DEFUN (neighbor_capability_dynamic, neighbor_capability_dynamic_cmd, "neighbor capability dynamic", NEIGHBOR_STR NEIGHBOR_ADDR_STR2 "Advertise capability to the peer\n" "Advertise dynamic capability to this neighbor\n") { int idx_peer = 1; return peer_flag_set_vty(vty, argv[idx_peer]->arg, PEER_FLAG_DYNAMIC_CAPABILITY); } DEFUN (no_neighbor_capability_dynamic, no_neighbor_capability_dynamic_cmd, "no neighbor capability dynamic", NO_STR NEIGHBOR_STR NEIGHBOR_ADDR_STR2 "Advertise capability to the peer\n" "Advertise dynamic capability to this neighbor\n") { int idx_peer = 2; return peer_flag_unset_vty(vty, argv[idx_peer]->arg, PEER_FLAG_DYNAMIC_CAPABILITY); } /* neighbor dont-capability-negotiate */ DEFUN (neighbor_dont_capability_negotiate, neighbor_dont_capability_negotiate_cmd, "neighbor dont-capability-negotiate", NEIGHBOR_STR NEIGHBOR_ADDR_STR2 "Do not perform capability negotiation\n") { int idx_peer = 1; return peer_flag_set_vty(vty, argv[idx_peer]->arg, PEER_FLAG_DONT_CAPABILITY); } DEFUN (no_neighbor_dont_capability_negotiate, no_neighbor_dont_capability_negotiate_cmd, "no neighbor dont-capability-negotiate", NO_STR NEIGHBOR_STR NEIGHBOR_ADDR_STR2 "Do not perform capability negotiation\n") { int idx_peer = 2; return peer_flag_unset_vty(vty, argv[idx_peer]->arg, PEER_FLAG_DONT_CAPABILITY); } /* neighbor capability extended next hop encoding */ DEFUN (neighbor_capability_enhe, neighbor_capability_enhe_cmd, "neighbor capability extended-nexthop", NEIGHBOR_STR NEIGHBOR_ADDR_STR2 "Advertise capability to the peer\n" "Advertise extended next-hop capability to the peer\n") { int idx_peer = 1; return peer_flag_set_vty(vty, argv[idx_peer]->arg, PEER_FLAG_CAPABILITY_ENHE); } DEFUN (no_neighbor_capability_enhe, no_neighbor_capability_enhe_cmd, "no neighbor capability extended-nexthop", NO_STR NEIGHBOR_STR NEIGHBOR_ADDR_STR2 "Advertise capability to the peer\n" "Advertise extended next-hop capability to the peer\n") { int idx_peer = 2; return peer_flag_unset_vty(vty, argv[idx_peer]->arg, PEER_FLAG_CAPABILITY_ENHE); } static int peer_af_flag_modify_vty(struct vty *vty, const char *peer_str, afi_t afi, safi_t safi, uint32_t flag, int set) { int ret; struct peer *peer; peer = peer_and_group_lookup_vty(vty, peer_str); if (!peer) return CMD_WARNING_CONFIG_FAILED; if (set) ret = peer_af_flag_set(peer, afi, safi, flag); else ret = peer_af_flag_unset(peer, afi, safi, flag); return bgp_vty_return(vty, ret); } static int peer_af_flag_set_vty(struct vty *vty, const char *peer_str, afi_t afi, safi_t safi, uint32_t flag) { return peer_af_flag_modify_vty(vty, peer_str, afi, safi, flag, 1); } static int peer_af_flag_unset_vty(struct vty *vty, const char *peer_str, afi_t afi, safi_t safi, uint32_t flag) { return peer_af_flag_modify_vty(vty, peer_str, afi, safi, flag, 0); } /* neighbor capability orf prefix-list. */ DEFUN (neighbor_capability_orf_prefix, neighbor_capability_orf_prefix_cmd, "neighbor capability orf prefix-list ", NEIGHBOR_STR NEIGHBOR_ADDR_STR2 "Advertise capability to the peer\n" "Advertise ORF capability to the peer\n" "Advertise prefixlist ORF capability to this neighbor\n" "Capability to SEND and RECEIVE the ORF to/from this neighbor\n" "Capability to RECEIVE the ORF from this neighbor\n" "Capability to SEND the ORF to this neighbor\n") { int idx_peer = 1; int idx_send_recv = 5; uint16_t flag = 0; if (strmatch(argv[idx_send_recv]->text, "send")) flag = PEER_FLAG_ORF_PREFIX_SM; else if (strmatch(argv[idx_send_recv]->text, "receive")) flag = PEER_FLAG_ORF_PREFIX_RM; else if (strmatch(argv[idx_send_recv]->text, "both")) flag = PEER_FLAG_ORF_PREFIX_SM | PEER_FLAG_ORF_PREFIX_RM; else { vty_out(vty, "%% BGP invalid orf prefix-list option\n"); return CMD_WARNING_CONFIG_FAILED; } return peer_af_flag_set_vty(vty, argv[idx_peer]->arg, bgp_node_afi(vty), bgp_node_safi(vty), flag); } ALIAS_HIDDEN( neighbor_capability_orf_prefix, neighbor_capability_orf_prefix_hidden_cmd, "neighbor capability orf prefix-list ", NEIGHBOR_STR NEIGHBOR_ADDR_STR2 "Advertise capability to the peer\n" "Advertise ORF capability to the peer\n" "Advertise prefixlist ORF capability to this neighbor\n" "Capability to SEND and RECEIVE the ORF to/from this neighbor\n" "Capability to RECEIVE the ORF from this neighbor\n" "Capability to SEND the ORF to this neighbor\n") DEFUN (no_neighbor_capability_orf_prefix, no_neighbor_capability_orf_prefix_cmd, "no neighbor capability orf prefix-list ", NO_STR NEIGHBOR_STR NEIGHBOR_ADDR_STR2 "Advertise capability to the peer\n" "Advertise ORF capability to the peer\n" "Advertise prefixlist ORF capability to this neighbor\n" "Capability to SEND and RECEIVE the ORF to/from this neighbor\n" "Capability to RECEIVE the ORF from this neighbor\n" "Capability to SEND the ORF to this neighbor\n") { int idx_peer = 2; int idx_send_recv = 6; uint16_t flag = 0; if (strmatch(argv[idx_send_recv]->text, "send")) flag = PEER_FLAG_ORF_PREFIX_SM; else if (strmatch(argv[idx_send_recv]->text, "receive")) flag = PEER_FLAG_ORF_PREFIX_RM; else if (strmatch(argv[idx_send_recv]->text, "both")) flag = PEER_FLAG_ORF_PREFIX_SM | PEER_FLAG_ORF_PREFIX_RM; else { vty_out(vty, "%% BGP invalid orf prefix-list option\n"); return CMD_WARNING_CONFIG_FAILED; } return peer_af_flag_unset_vty(vty, argv[idx_peer]->arg, bgp_node_afi(vty), bgp_node_safi(vty), flag); } ALIAS_HIDDEN( no_neighbor_capability_orf_prefix, no_neighbor_capability_orf_prefix_hidden_cmd, "no neighbor capability orf prefix-list ", NO_STR NEIGHBOR_STR NEIGHBOR_ADDR_STR2 "Advertise capability to the peer\n" "Advertise ORF capability to the peer\n" "Advertise prefixlist ORF capability to this neighbor\n" "Capability to SEND and RECEIVE the ORF to/from this neighbor\n" "Capability to RECEIVE the ORF from this neighbor\n" "Capability to SEND the ORF to this neighbor\n") /* neighbor next-hop-self. */ DEFUN (neighbor_nexthop_self, neighbor_nexthop_self_cmd, "neighbor next-hop-self", NEIGHBOR_STR NEIGHBOR_ADDR_STR2 "Disable the next hop calculation for this neighbor\n") { int idx_peer = 1; return peer_af_flag_set_vty(vty, argv[idx_peer]->arg, bgp_node_afi(vty), bgp_node_safi(vty), PEER_FLAG_NEXTHOP_SELF); } ALIAS_HIDDEN(neighbor_nexthop_self, neighbor_nexthop_self_hidden_cmd, "neighbor next-hop-self", NEIGHBOR_STR NEIGHBOR_ADDR_STR2 "Disable the next hop calculation for this neighbor\n") /* neighbor next-hop-self. */ DEFUN (neighbor_nexthop_self_force, neighbor_nexthop_self_force_cmd, "neighbor next-hop-self force", NEIGHBOR_STR NEIGHBOR_ADDR_STR2 "Disable the next hop calculation for this neighbor\n" "Set the next hop to self for reflected routes\n") { int idx_peer = 1; return peer_af_flag_set_vty(vty, argv[idx_peer]->arg, bgp_node_afi(vty), bgp_node_safi(vty), PEER_FLAG_FORCE_NEXTHOP_SELF); } ALIAS_HIDDEN(neighbor_nexthop_self_force, neighbor_nexthop_self_force_hidden_cmd, "neighbor next-hop-self force", NEIGHBOR_STR NEIGHBOR_ADDR_STR2 "Disable the next hop calculation for this neighbor\n" "Set the next hop to self for reflected routes\n") ALIAS_HIDDEN(neighbor_nexthop_self_force, neighbor_nexthop_self_all_hidden_cmd, "neighbor next-hop-self all", NEIGHBOR_STR NEIGHBOR_ADDR_STR2 "Disable the next hop calculation for this neighbor\n" "Set the next hop to self for reflected routes\n") DEFUN (no_neighbor_nexthop_self, no_neighbor_nexthop_self_cmd, "no neighbor next-hop-self", NO_STR NEIGHBOR_STR NEIGHBOR_ADDR_STR2 "Disable the next hop calculation for this neighbor\n") { int idx_peer = 2; return peer_af_flag_unset_vty(vty, argv[idx_peer]->arg, bgp_node_afi(vty), bgp_node_safi(vty), PEER_FLAG_NEXTHOP_SELF); } ALIAS_HIDDEN(no_neighbor_nexthop_self, no_neighbor_nexthop_self_hidden_cmd, "no neighbor next-hop-self", NO_STR NEIGHBOR_STR NEIGHBOR_ADDR_STR2 "Disable the next hop calculation for this neighbor\n") DEFUN (no_neighbor_nexthop_self_force, no_neighbor_nexthop_self_force_cmd, "no neighbor next-hop-self force", NO_STR NEIGHBOR_STR NEIGHBOR_ADDR_STR2 "Disable the next hop calculation for this neighbor\n" "Set the next hop to self for reflected routes\n") { int idx_peer = 2; return peer_af_flag_unset_vty(vty, argv[idx_peer]->arg, bgp_node_afi(vty), bgp_node_safi(vty), PEER_FLAG_FORCE_NEXTHOP_SELF); } ALIAS_HIDDEN(no_neighbor_nexthop_self_force, no_neighbor_nexthop_self_force_hidden_cmd, "no neighbor next-hop-self force", NO_STR NEIGHBOR_STR NEIGHBOR_ADDR_STR2 "Disable the next hop calculation for this neighbor\n" "Set the next hop to self for reflected routes\n") ALIAS_HIDDEN(no_neighbor_nexthop_self_force, no_neighbor_nexthop_self_all_hidden_cmd, "no neighbor next-hop-self all", NO_STR NEIGHBOR_STR NEIGHBOR_ADDR_STR2 "Disable the next hop calculation for this neighbor\n" "Set the next hop to self for reflected routes\n") /* neighbor as-override */ DEFUN (neighbor_as_override, neighbor_as_override_cmd, "neighbor as-override", NEIGHBOR_STR NEIGHBOR_ADDR_STR2 "Override ASNs in outbound updates if aspath equals remote-as\n") { int idx_peer = 1; return peer_af_flag_set_vty(vty, argv[idx_peer]->arg, bgp_node_afi(vty), bgp_node_safi(vty), PEER_FLAG_AS_OVERRIDE); } ALIAS_HIDDEN(neighbor_as_override, neighbor_as_override_hidden_cmd, "neighbor as-override", NEIGHBOR_STR NEIGHBOR_ADDR_STR2 "Override ASNs in outbound updates if aspath equals remote-as\n") DEFUN (no_neighbor_as_override, no_neighbor_as_override_cmd, "no neighbor as-override", NO_STR NEIGHBOR_STR NEIGHBOR_ADDR_STR2 "Override ASNs in outbound updates if aspath equals remote-as\n") { int idx_peer = 2; return peer_af_flag_unset_vty(vty, argv[idx_peer]->arg, bgp_node_afi(vty), bgp_node_safi(vty), PEER_FLAG_AS_OVERRIDE); } ALIAS_HIDDEN(no_neighbor_as_override, no_neighbor_as_override_hidden_cmd, "no neighbor as-override", NO_STR NEIGHBOR_STR NEIGHBOR_ADDR_STR2 "Override ASNs in outbound updates if aspath equals remote-as\n") /* neighbor remove-private-AS. */ DEFUN (neighbor_remove_private_as, neighbor_remove_private_as_cmd, "neighbor remove-private-AS", NEIGHBOR_STR NEIGHBOR_ADDR_STR2 "Remove private ASNs in outbound updates\n") { int idx_peer = 1; return peer_af_flag_set_vty(vty, argv[idx_peer]->arg, bgp_node_afi(vty), bgp_node_safi(vty), PEER_FLAG_REMOVE_PRIVATE_AS); } ALIAS_HIDDEN(neighbor_remove_private_as, neighbor_remove_private_as_hidden_cmd, "neighbor remove-private-AS", NEIGHBOR_STR NEIGHBOR_ADDR_STR2 "Remove private ASNs in outbound updates\n") DEFUN (neighbor_remove_private_as_all, neighbor_remove_private_as_all_cmd, "neighbor remove-private-AS all", NEIGHBOR_STR NEIGHBOR_ADDR_STR2 "Remove private ASNs in outbound updates\n" "Apply to all AS numbers\n") { int idx_peer = 1; return peer_af_flag_set_vty(vty, argv[idx_peer]->arg, bgp_node_afi(vty), bgp_node_safi(vty), PEER_FLAG_REMOVE_PRIVATE_AS_ALL); } ALIAS_HIDDEN(neighbor_remove_private_as_all, neighbor_remove_private_as_all_hidden_cmd, "neighbor remove-private-AS all", NEIGHBOR_STR NEIGHBOR_ADDR_STR2 "Remove private ASNs in outbound updates\n" "Apply to all AS numbers") DEFUN (neighbor_remove_private_as_replace_as, neighbor_remove_private_as_replace_as_cmd, "neighbor remove-private-AS replace-AS", NEIGHBOR_STR NEIGHBOR_ADDR_STR2 "Remove private ASNs in outbound updates\n" "Replace private ASNs with our ASN in outbound updates\n") { int idx_peer = 1; return peer_af_flag_set_vty(vty, argv[idx_peer]->arg, bgp_node_afi(vty), bgp_node_safi(vty), PEER_FLAG_REMOVE_PRIVATE_AS_REPLACE); } ALIAS_HIDDEN(neighbor_remove_private_as_replace_as, neighbor_remove_private_as_replace_as_hidden_cmd, "neighbor remove-private-AS replace-AS", NEIGHBOR_STR NEIGHBOR_ADDR_STR2 "Remove private ASNs in outbound updates\n" "Replace private ASNs with our ASN in outbound updates\n") DEFUN (neighbor_remove_private_as_all_replace_as, neighbor_remove_private_as_all_replace_as_cmd, "neighbor remove-private-AS all replace-AS", NEIGHBOR_STR NEIGHBOR_ADDR_STR2 "Remove private ASNs in outbound updates\n" "Apply to all AS numbers\n" "Replace private ASNs with our ASN in outbound updates\n") { int idx_peer = 1; return peer_af_flag_set_vty(vty, argv[idx_peer]->arg, bgp_node_afi(vty), bgp_node_safi(vty), PEER_FLAG_REMOVE_PRIVATE_AS_ALL_REPLACE); } ALIAS_HIDDEN( neighbor_remove_private_as_all_replace_as, neighbor_remove_private_as_all_replace_as_hidden_cmd, "neighbor remove-private-AS all replace-AS", NEIGHBOR_STR NEIGHBOR_ADDR_STR2 "Remove private ASNs in outbound updates\n" "Apply to all AS numbers\n" "Replace private ASNs with our ASN in outbound updates\n") DEFUN (no_neighbor_remove_private_as, no_neighbor_remove_private_as_cmd, "no neighbor remove-private-AS", NO_STR NEIGHBOR_STR NEIGHBOR_ADDR_STR2 "Remove private ASNs in outbound updates\n") { int idx_peer = 2; return peer_af_flag_unset_vty(vty, argv[idx_peer]->arg, bgp_node_afi(vty), bgp_node_safi(vty), PEER_FLAG_REMOVE_PRIVATE_AS); } ALIAS_HIDDEN(no_neighbor_remove_private_as, no_neighbor_remove_private_as_hidden_cmd, "no neighbor remove-private-AS", NO_STR NEIGHBOR_STR NEIGHBOR_ADDR_STR2 "Remove private ASNs in outbound updates\n") DEFUN (no_neighbor_remove_private_as_all, no_neighbor_remove_private_as_all_cmd, "no neighbor remove-private-AS all", NO_STR NEIGHBOR_STR NEIGHBOR_ADDR_STR2 "Remove private ASNs in outbound updates\n" "Apply to all AS numbers\n") { int idx_peer = 2; return peer_af_flag_unset_vty(vty, argv[idx_peer]->arg, bgp_node_afi(vty), bgp_node_safi(vty), PEER_FLAG_REMOVE_PRIVATE_AS_ALL); } ALIAS_HIDDEN(no_neighbor_remove_private_as_all, no_neighbor_remove_private_as_all_hidden_cmd, "no neighbor remove-private-AS all", NO_STR NEIGHBOR_STR NEIGHBOR_ADDR_STR2 "Remove private ASNs in outbound updates\n" "Apply to all AS numbers\n") DEFUN (no_neighbor_remove_private_as_replace_as, no_neighbor_remove_private_as_replace_as_cmd, "no neighbor remove-private-AS replace-AS", NO_STR NEIGHBOR_STR NEIGHBOR_ADDR_STR2 "Remove private ASNs in outbound updates\n" "Replace private ASNs with our ASN in outbound updates\n") { int idx_peer = 2; return peer_af_flag_unset_vty(vty, argv[idx_peer]->arg, bgp_node_afi(vty), bgp_node_safi(vty), PEER_FLAG_REMOVE_PRIVATE_AS_REPLACE); } ALIAS_HIDDEN(no_neighbor_remove_private_as_replace_as, no_neighbor_remove_private_as_replace_as_hidden_cmd, "no neighbor remove-private-AS replace-AS", NO_STR NEIGHBOR_STR NEIGHBOR_ADDR_STR2 "Remove private ASNs in outbound updates\n" "Replace private ASNs with our ASN in outbound updates\n") DEFUN (no_neighbor_remove_private_as_all_replace_as, no_neighbor_remove_private_as_all_replace_as_cmd, "no neighbor remove-private-AS all replace-AS", NO_STR NEIGHBOR_STR NEIGHBOR_ADDR_STR2 "Remove private ASNs in outbound updates\n" "Apply to all AS numbers\n" "Replace private ASNs with our ASN in outbound updates\n") { int idx_peer = 2; return peer_af_flag_unset_vty(vty, argv[idx_peer]->arg, bgp_node_afi(vty), bgp_node_safi(vty), PEER_FLAG_REMOVE_PRIVATE_AS_ALL_REPLACE); } ALIAS_HIDDEN( no_neighbor_remove_private_as_all_replace_as, no_neighbor_remove_private_as_all_replace_as_hidden_cmd, "no neighbor remove-private-AS all replace-AS", NO_STR NEIGHBOR_STR NEIGHBOR_ADDR_STR2 "Remove private ASNs in outbound updates\n" "Apply to all AS numbers\n" "Replace private ASNs with our ASN in outbound updates\n") /* neighbor send-community. */ DEFUN (neighbor_send_community, neighbor_send_community_cmd, "neighbor send-community", NEIGHBOR_STR NEIGHBOR_ADDR_STR2 "Send Community attribute to this neighbor\n") { int idx_peer = 1; return peer_af_flag_set_vty(vty, argv[idx_peer]->arg, bgp_node_afi(vty), bgp_node_safi(vty), PEER_FLAG_SEND_COMMUNITY); } ALIAS_HIDDEN(neighbor_send_community, neighbor_send_community_hidden_cmd, "neighbor send-community", NEIGHBOR_STR NEIGHBOR_ADDR_STR2 "Send Community attribute to this neighbor\n") DEFUN (no_neighbor_send_community, no_neighbor_send_community_cmd, "no neighbor send-community", NO_STR NEIGHBOR_STR NEIGHBOR_ADDR_STR2 "Send Community attribute to this neighbor\n") { int idx_peer = 2; return peer_af_flag_unset_vty(vty, argv[idx_peer]->arg, bgp_node_afi(vty), bgp_node_safi(vty), PEER_FLAG_SEND_COMMUNITY); } ALIAS_HIDDEN(no_neighbor_send_community, no_neighbor_send_community_hidden_cmd, "no neighbor send-community", NO_STR NEIGHBOR_STR NEIGHBOR_ADDR_STR2 "Send Community attribute to this neighbor\n") /* neighbor send-community extended. */ DEFUN (neighbor_send_community_type, neighbor_send_community_type_cmd, "neighbor send-community ", NEIGHBOR_STR NEIGHBOR_ADDR_STR2 "Send Community attribute to this neighbor\n" "Send Standard and Extended Community attributes\n" "Send Standard, Large and Extended Community attributes\n" "Send Extended Community attributes\n" "Send Standard Community attributes\n" "Send Large Community attributes\n") { int idx_peer = 1; uint32_t flag = 0; const char *type = argv[argc - 1]->text; if (strmatch(type, "standard")) { SET_FLAG(flag, PEER_FLAG_SEND_COMMUNITY); } else if (strmatch(type, "extended")) { SET_FLAG(flag, PEER_FLAG_SEND_EXT_COMMUNITY); } else if (strmatch(type, "large")) { SET_FLAG(flag, PEER_FLAG_SEND_LARGE_COMMUNITY); } else if (strmatch(type, "both")) { SET_FLAG(flag, PEER_FLAG_SEND_COMMUNITY); SET_FLAG(flag, PEER_FLAG_SEND_EXT_COMMUNITY); } else { /* if (strmatch(type, "all")) */ SET_FLAG(flag, PEER_FLAG_SEND_COMMUNITY); SET_FLAG(flag, PEER_FLAG_SEND_EXT_COMMUNITY); SET_FLAG(flag, PEER_FLAG_SEND_LARGE_COMMUNITY); } return peer_af_flag_set_vty(vty, argv[idx_peer]->arg, bgp_node_afi(vty), bgp_node_safi(vty), flag); } ALIAS_HIDDEN( neighbor_send_community_type, neighbor_send_community_type_hidden_cmd, "neighbor send-community ", NEIGHBOR_STR NEIGHBOR_ADDR_STR2 "Send Community attribute to this neighbor\n" "Send Standard and Extended Community attributes\n" "Send Standard, Large and Extended Community attributes\n" "Send Extended Community attributes\n" "Send Standard Community attributes\n" "Send Large Community attributes\n") DEFUN (no_neighbor_send_community_type, no_neighbor_send_community_type_cmd, "no neighbor send-community ", NO_STR NEIGHBOR_STR NEIGHBOR_ADDR_STR2 "Send Community attribute to this neighbor\n" "Send Standard and Extended Community attributes\n" "Send Standard, Large and Extended Community attributes\n" "Send Extended Community attributes\n" "Send Standard Community attributes\n" "Send Large Community attributes\n") { int idx_peer = 2; uint32_t flag = 0; const char *type = argv[argc - 1]->text; if (strmatch(type, "standard")) { SET_FLAG(flag, PEER_FLAG_SEND_COMMUNITY); } else if (strmatch(type, "extended")) { SET_FLAG(flag, PEER_FLAG_SEND_EXT_COMMUNITY); } else if (strmatch(type, "large")) { SET_FLAG(flag, PEER_FLAG_SEND_LARGE_COMMUNITY); } else if (strmatch(type, "both")) { SET_FLAG(flag, PEER_FLAG_SEND_COMMUNITY); SET_FLAG(flag, PEER_FLAG_SEND_EXT_COMMUNITY); } else { /* if (strmatch(type, "all")) */ SET_FLAG(flag, PEER_FLAG_SEND_COMMUNITY); SET_FLAG(flag, PEER_FLAG_SEND_EXT_COMMUNITY); SET_FLAG(flag, PEER_FLAG_SEND_LARGE_COMMUNITY); } return peer_af_flag_unset_vty(vty, argv[idx_peer]->arg, bgp_node_afi(vty), bgp_node_safi(vty), flag); } ALIAS_HIDDEN( no_neighbor_send_community_type, no_neighbor_send_community_type_hidden_cmd, "no neighbor send-community ", NO_STR NEIGHBOR_STR NEIGHBOR_ADDR_STR2 "Send Community attribute to this neighbor\n" "Send Standard and Extended Community attributes\n" "Send Standard, Large and Extended Community attributes\n" "Send Extended Community attributes\n" "Send Standard Community attributes\n" "Send Large Community attributes\n") /* neighbor soft-reconfig. */ DEFUN (neighbor_soft_reconfiguration, neighbor_soft_reconfiguration_cmd, "neighbor soft-reconfiguration inbound", NEIGHBOR_STR NEIGHBOR_ADDR_STR2 "Per neighbor soft reconfiguration\n" "Allow inbound soft reconfiguration for this neighbor\n") { int idx_peer = 1; return peer_af_flag_set_vty(vty, argv[idx_peer]->arg, bgp_node_afi(vty), bgp_node_safi(vty), PEER_FLAG_SOFT_RECONFIG); } ALIAS_HIDDEN(neighbor_soft_reconfiguration, neighbor_soft_reconfiguration_hidden_cmd, "neighbor soft-reconfiguration inbound", NEIGHBOR_STR NEIGHBOR_ADDR_STR2 "Per neighbor soft reconfiguration\n" "Allow inbound soft reconfiguration for this neighbor\n") DEFUN (no_neighbor_soft_reconfiguration, no_neighbor_soft_reconfiguration_cmd, "no neighbor soft-reconfiguration inbound", NO_STR NEIGHBOR_STR NEIGHBOR_ADDR_STR2 "Per neighbor soft reconfiguration\n" "Allow inbound soft reconfiguration for this neighbor\n") { int idx_peer = 2; return peer_af_flag_unset_vty(vty, argv[idx_peer]->arg, bgp_node_afi(vty), bgp_node_safi(vty), PEER_FLAG_SOFT_RECONFIG); } ALIAS_HIDDEN(no_neighbor_soft_reconfiguration, no_neighbor_soft_reconfiguration_hidden_cmd, "no neighbor soft-reconfiguration inbound", NO_STR NEIGHBOR_STR NEIGHBOR_ADDR_STR2 "Per neighbor soft reconfiguration\n" "Allow inbound soft reconfiguration for this neighbor\n") DEFUN (neighbor_route_reflector_client, neighbor_route_reflector_client_cmd, "neighbor route-reflector-client", NEIGHBOR_STR NEIGHBOR_ADDR_STR2 "Configure a neighbor as Route Reflector client\n") { int idx_peer = 1; struct peer *peer; peer = peer_and_group_lookup_vty(vty, argv[idx_peer]->arg); if (!peer) return CMD_WARNING_CONFIG_FAILED; return peer_af_flag_set_vty(vty, argv[idx_peer]->arg, bgp_node_afi(vty), bgp_node_safi(vty), PEER_FLAG_REFLECTOR_CLIENT); } ALIAS_HIDDEN(neighbor_route_reflector_client, neighbor_route_reflector_client_hidden_cmd, "neighbor route-reflector-client", NEIGHBOR_STR NEIGHBOR_ADDR_STR2 "Configure a neighbor as Route Reflector client\n") DEFUN (no_neighbor_route_reflector_client, no_neighbor_route_reflector_client_cmd, "no neighbor route-reflector-client", NO_STR NEIGHBOR_STR NEIGHBOR_ADDR_STR2 "Configure a neighbor as Route Reflector client\n") { int idx_peer = 2; return peer_af_flag_unset_vty(vty, argv[idx_peer]->arg, bgp_node_afi(vty), bgp_node_safi(vty), PEER_FLAG_REFLECTOR_CLIENT); } ALIAS_HIDDEN(no_neighbor_route_reflector_client, no_neighbor_route_reflector_client_hidden_cmd, "no neighbor route-reflector-client", NO_STR NEIGHBOR_STR NEIGHBOR_ADDR_STR2 "Configure a neighbor as Route Reflector client\n") /* neighbor route-server-client. */ DEFUN (neighbor_route_server_client, neighbor_route_server_client_cmd, "neighbor route-server-client", NEIGHBOR_STR NEIGHBOR_ADDR_STR2 "Configure a neighbor as Route Server client\n") { int idx_peer = 1; struct peer *peer; peer = peer_and_group_lookup_vty(vty, argv[idx_peer]->arg); if (!peer) return CMD_WARNING_CONFIG_FAILED; return peer_af_flag_set_vty(vty, argv[idx_peer]->arg, bgp_node_afi(vty), bgp_node_safi(vty), PEER_FLAG_RSERVER_CLIENT); } ALIAS_HIDDEN(neighbor_route_server_client, neighbor_route_server_client_hidden_cmd, "neighbor route-server-client", NEIGHBOR_STR NEIGHBOR_ADDR_STR2 "Configure a neighbor as Route Server client\n") DEFUN (no_neighbor_route_server_client, no_neighbor_route_server_client_cmd, "no neighbor route-server-client", NO_STR NEIGHBOR_STR NEIGHBOR_ADDR_STR2 "Configure a neighbor as Route Server client\n") { int idx_peer = 2; return peer_af_flag_unset_vty(vty, argv[idx_peer]->arg, bgp_node_afi(vty), bgp_node_safi(vty), PEER_FLAG_RSERVER_CLIENT); } ALIAS_HIDDEN(no_neighbor_route_server_client, no_neighbor_route_server_client_hidden_cmd, "no neighbor route-server-client", NO_STR NEIGHBOR_STR NEIGHBOR_ADDR_STR2 "Configure a neighbor as Route Server client\n") DEFUN (neighbor_nexthop_local_unchanged, neighbor_nexthop_local_unchanged_cmd, "neighbor nexthop-local unchanged", NEIGHBOR_STR NEIGHBOR_ADDR_STR2 "Configure treatment of outgoing link-local nexthop attribute\n" "Leave link-local nexthop unchanged for this peer\n") { int idx_peer = 1; return peer_af_flag_set_vty(vty, argv[idx_peer]->arg, bgp_node_afi(vty), bgp_node_safi(vty), PEER_FLAG_NEXTHOP_LOCAL_UNCHANGED); } DEFUN (no_neighbor_nexthop_local_unchanged, no_neighbor_nexthop_local_unchanged_cmd, "no neighbor nexthop-local unchanged", NO_STR NEIGHBOR_STR NEIGHBOR_ADDR_STR2 "Configure treatment of outgoing link-local-nexthop attribute\n" "Leave link-local nexthop unchanged for this peer\n") { int idx_peer = 2; return peer_af_flag_unset_vty(vty, argv[idx_peer]->arg, bgp_node_afi(vty), bgp_node_safi(vty), PEER_FLAG_NEXTHOP_LOCAL_UNCHANGED); } DEFUN (neighbor_attr_unchanged, neighbor_attr_unchanged_cmd, "neighbor attribute-unchanged [{as-path|next-hop|med}]", NEIGHBOR_STR NEIGHBOR_ADDR_STR2 "BGP attribute is propagated unchanged to this neighbor\n" "As-path attribute\n" "Nexthop attribute\n" "Med attribute\n") { int idx = 0; char *peer_str = argv[1]->arg; struct peer *peer; uint16_t flags = 0; afi_t afi = bgp_node_afi(vty); safi_t safi = bgp_node_safi(vty); peer = peer_and_group_lookup_vty(vty, peer_str); if (!peer) return CMD_WARNING_CONFIG_FAILED; if (argv_find(argv, argc, "as-path", &idx)) SET_FLAG(flags, PEER_FLAG_AS_PATH_UNCHANGED); idx = 0; if (argv_find(argv, argc, "next-hop", &idx)) SET_FLAG(flags, PEER_FLAG_NEXTHOP_UNCHANGED); idx = 0; if (argv_find(argv, argc, "med", &idx)) SET_FLAG(flags, PEER_FLAG_MED_UNCHANGED); /* no flags means all of them! */ if (!flags) { SET_FLAG(flags, PEER_FLAG_AS_PATH_UNCHANGED); SET_FLAG(flags, PEER_FLAG_NEXTHOP_UNCHANGED); SET_FLAG(flags, PEER_FLAG_MED_UNCHANGED); } else { if (!CHECK_FLAG(flags, PEER_FLAG_AS_PATH_UNCHANGED) && peer_af_flag_check(peer, afi, safi, PEER_FLAG_AS_PATH_UNCHANGED)) { peer_af_flag_unset_vty(vty, peer_str, afi, safi, PEER_FLAG_AS_PATH_UNCHANGED); } if (!CHECK_FLAG(flags, PEER_FLAG_NEXTHOP_UNCHANGED) && peer_af_flag_check(peer, afi, safi, PEER_FLAG_NEXTHOP_UNCHANGED)) { peer_af_flag_unset_vty(vty, peer_str, afi, safi, PEER_FLAG_NEXTHOP_UNCHANGED); } if (!CHECK_FLAG(flags, PEER_FLAG_MED_UNCHANGED) && peer_af_flag_check(peer, afi, safi, PEER_FLAG_MED_UNCHANGED)) { peer_af_flag_unset_vty(vty, peer_str, afi, safi, PEER_FLAG_MED_UNCHANGED); } } return peer_af_flag_set_vty(vty, peer_str, afi, safi, flags); } ALIAS_HIDDEN( neighbor_attr_unchanged, neighbor_attr_unchanged_hidden_cmd, "neighbor attribute-unchanged [{as-path|next-hop|med}]", NEIGHBOR_STR NEIGHBOR_ADDR_STR2 "BGP attribute is propagated unchanged to this neighbor\n" "As-path attribute\n" "Nexthop attribute\n" "Med attribute\n") DEFUN (no_neighbor_attr_unchanged, no_neighbor_attr_unchanged_cmd, "no neighbor attribute-unchanged [{as-path|next-hop|med}]", NO_STR NEIGHBOR_STR NEIGHBOR_ADDR_STR2 "BGP attribute is propagated unchanged to this neighbor\n" "As-path attribute\n" "Nexthop attribute\n" "Med attribute\n") { int idx = 0; char *peer = argv[2]->arg; uint16_t flags = 0; if (argv_find(argv, argc, "as-path", &idx)) SET_FLAG(flags, PEER_FLAG_AS_PATH_UNCHANGED); idx = 0; if (argv_find(argv, argc, "next-hop", &idx)) SET_FLAG(flags, PEER_FLAG_NEXTHOP_UNCHANGED); idx = 0; if (argv_find(argv, argc, "med", &idx)) SET_FLAG(flags, PEER_FLAG_MED_UNCHANGED); if (!flags) // no flags means all of them! { SET_FLAG(flags, PEER_FLAG_AS_PATH_UNCHANGED); SET_FLAG(flags, PEER_FLAG_NEXTHOP_UNCHANGED); SET_FLAG(flags, PEER_FLAG_MED_UNCHANGED); } return peer_af_flag_unset_vty(vty, peer, bgp_node_afi(vty), bgp_node_safi(vty), flags); } ALIAS_HIDDEN( no_neighbor_attr_unchanged, no_neighbor_attr_unchanged_hidden_cmd, "no neighbor attribute-unchanged [{as-path|next-hop|med}]", NO_STR NEIGHBOR_STR NEIGHBOR_ADDR_STR2 "BGP attribute is propagated unchanged to this neighbor\n" "As-path attribute\n" "Nexthop attribute\n" "Med attribute\n") /* EBGP multihop configuration. */ static int peer_ebgp_multihop_set_vty(struct vty *vty, const char *ip_str, const char *ttl_str) { struct peer *peer; unsigned int ttl; peer = peer_and_group_lookup_vty(vty, ip_str); if (!peer) return CMD_WARNING_CONFIG_FAILED; if (peer->conf_if) return bgp_vty_return(vty, BGP_ERR_INVALID_FOR_DIRECT_PEER); if (!ttl_str) ttl = MAXTTL; else ttl = strtoul(ttl_str, NULL, 10); return bgp_vty_return(vty, peer_ebgp_multihop_set(peer, ttl)); } static int peer_ebgp_multihop_unset_vty(struct vty *vty, const char *ip_str) { struct peer *peer; peer = peer_and_group_lookup_vty(vty, ip_str); if (!peer) return CMD_WARNING_CONFIG_FAILED; return bgp_vty_return(vty, peer_ebgp_multihop_unset(peer)); } /* neighbor ebgp-multihop. */ DEFUN (neighbor_ebgp_multihop, neighbor_ebgp_multihop_cmd, "neighbor ebgp-multihop", NEIGHBOR_STR NEIGHBOR_ADDR_STR2 "Allow EBGP neighbors not on directly connected networks\n") { int idx_peer = 1; return peer_ebgp_multihop_set_vty(vty, argv[idx_peer]->arg, NULL); } DEFUN (neighbor_ebgp_multihop_ttl, neighbor_ebgp_multihop_ttl_cmd, "neighbor ebgp-multihop (1-255)", NEIGHBOR_STR NEIGHBOR_ADDR_STR2 "Allow EBGP neighbors not on directly connected networks\n" "maximum hop count\n") { int idx_peer = 1; int idx_number = 3; return peer_ebgp_multihop_set_vty(vty, argv[idx_peer]->arg, argv[idx_number]->arg); } DEFUN (no_neighbor_ebgp_multihop, no_neighbor_ebgp_multihop_cmd, "no neighbor ebgp-multihop [(1-255)]", NO_STR NEIGHBOR_STR NEIGHBOR_ADDR_STR2 "Allow EBGP neighbors not on directly connected networks\n" "maximum hop count\n") { int idx_peer = 2; return peer_ebgp_multihop_unset_vty(vty, argv[idx_peer]->arg); } /* disable-connected-check */ DEFUN (neighbor_disable_connected_check, neighbor_disable_connected_check_cmd, "neighbor ", NEIGHBOR_STR NEIGHBOR_ADDR_STR2 "one-hop away EBGP peer using loopback address\n" "Enforce EBGP neighbors perform multihop\n") { int idx_peer = 1; return peer_flag_set_vty(vty, argv[idx_peer]->arg, PEER_FLAG_DISABLE_CONNECTED_CHECK); } DEFUN (no_neighbor_disable_connected_check, no_neighbor_disable_connected_check_cmd, "no neighbor ", NO_STR NEIGHBOR_STR NEIGHBOR_ADDR_STR2 "one-hop away EBGP peer using loopback address\n" "Enforce EBGP neighbors perform multihop\n") { int idx_peer = 2; return peer_flag_unset_vty(vty, argv[idx_peer]->arg, PEER_FLAG_DISABLE_CONNECTED_CHECK); } /* enforce-first-as */ DEFUN (neighbor_enforce_first_as, neighbor_enforce_first_as_cmd, "neighbor enforce-first-as", NEIGHBOR_STR NEIGHBOR_ADDR_STR2 "Enforce the first AS for EBGP routes\n") { int idx_peer = 1; return peer_flag_set_vty(vty, argv[idx_peer]->arg, PEER_FLAG_ENFORCE_FIRST_AS); } DEFUN (no_neighbor_enforce_first_as, no_neighbor_enforce_first_as_cmd, "no neighbor enforce-first-as", NO_STR NEIGHBOR_STR NEIGHBOR_ADDR_STR2 "Enforce the first AS for EBGP routes\n") { int idx_peer = 2; return peer_flag_unset_vty(vty, argv[idx_peer]->arg, PEER_FLAG_ENFORCE_FIRST_AS); } DEFUN (neighbor_description, neighbor_description_cmd, "neighbor description LINE...", NEIGHBOR_STR NEIGHBOR_ADDR_STR2 "Neighbor specific description\n" "Up to 80 characters describing this neighbor\n") { int idx_peer = 1; int idx_line = 3; struct peer *peer; char *str; peer = peer_and_group_lookup_vty(vty, argv[idx_peer]->arg); if (!peer) return CMD_WARNING_CONFIG_FAILED; str = argv_concat(argv, argc, idx_line); peer_description_set(peer, str); XFREE(MTYPE_TMP, str); return CMD_SUCCESS; } DEFUN (no_neighbor_description, no_neighbor_description_cmd, "no neighbor description", NO_STR NEIGHBOR_STR NEIGHBOR_ADDR_STR2 "Neighbor specific description\n") { int idx_peer = 2; struct peer *peer; peer = peer_and_group_lookup_vty(vty, argv[idx_peer]->arg); if (!peer) return CMD_WARNING_CONFIG_FAILED; peer_description_unset(peer); return CMD_SUCCESS; } ALIAS(no_neighbor_description, no_neighbor_description_comment_cmd, "no neighbor description LINE...", NO_STR NEIGHBOR_STR NEIGHBOR_ADDR_STR2 "Neighbor specific description\n" "Up to 80 characters describing this neighbor\n") /* Neighbor update-source. */ static int peer_update_source_vty(struct vty *vty, const char *peer_str, const char *source_str) { struct peer *peer; struct prefix p; union sockunion su; peer = peer_and_group_lookup_vty(vty, peer_str); if (!peer) return CMD_WARNING_CONFIG_FAILED; if (peer->conf_if) return CMD_WARNING; if (source_str) { if (str2sockunion(source_str, &su) == 0) peer_update_source_addr_set(peer, &su); else { if (str2prefix(source_str, &p)) { vty_out(vty, "%% Invalid update-source, remove prefix length \n"); return CMD_WARNING_CONFIG_FAILED; } else peer_update_source_if_set(peer, source_str); } } else peer_update_source_unset(peer); return CMD_SUCCESS; } #define BGP_UPDATE_SOURCE_HELP_STR \ "IPv4 address\n" \ "IPv6 address\n" \ "Interface name (requires zebra to be running)\n" DEFUN (neighbor_update_source, neighbor_update_source_cmd, "neighbor update-source ", NEIGHBOR_STR NEIGHBOR_ADDR_STR2 "Source of routing updates\n" BGP_UPDATE_SOURCE_HELP_STR) { int idx_peer = 1; int idx_peer_2 = 3; return peer_update_source_vty(vty, argv[idx_peer]->arg, argv[idx_peer_2]->arg); } DEFUN (no_neighbor_update_source, no_neighbor_update_source_cmd, "no neighbor update-source []", NO_STR NEIGHBOR_STR NEIGHBOR_ADDR_STR2 "Source of routing updates\n" BGP_UPDATE_SOURCE_HELP_STR) { int idx_peer = 2; return peer_update_source_vty(vty, argv[idx_peer]->arg, NULL); } static int peer_default_originate_set_vty(struct vty *vty, const char *peer_str, afi_t afi, safi_t safi, const char *rmap, int set) { int ret; struct peer *peer; struct route_map *route_map = NULL; peer = peer_and_group_lookup_vty(vty, peer_str); if (!peer) return CMD_WARNING_CONFIG_FAILED; if (set) { if (rmap) route_map = route_map_lookup_warn_noexist(vty, rmap); ret = peer_default_originate_set(peer, afi, safi, rmap, route_map); } else ret = peer_default_originate_unset(peer, afi, safi); return bgp_vty_return(vty, ret); } /* neighbor default-originate. */ DEFUN (neighbor_default_originate, neighbor_default_originate_cmd, "neighbor default-originate", NEIGHBOR_STR NEIGHBOR_ADDR_STR2 "Originate default route to this neighbor\n") { int idx_peer = 1; return peer_default_originate_set_vty(vty, argv[idx_peer]->arg, bgp_node_afi(vty), bgp_node_safi(vty), NULL, 1); } ALIAS_HIDDEN(neighbor_default_originate, neighbor_default_originate_hidden_cmd, "neighbor default-originate", NEIGHBOR_STR NEIGHBOR_ADDR_STR2 "Originate default route to this neighbor\n") DEFUN (neighbor_default_originate_rmap, neighbor_default_originate_rmap_cmd, "neighbor default-originate route-map WORD", NEIGHBOR_STR NEIGHBOR_ADDR_STR2 "Originate default route to this neighbor\n" "Route-map to specify criteria to originate default\n" "route-map name\n") { int idx_peer = 1; int idx_word = 4; return peer_default_originate_set_vty( vty, argv[idx_peer]->arg, bgp_node_afi(vty), bgp_node_safi(vty), argv[idx_word]->arg, 1); } ALIAS_HIDDEN( neighbor_default_originate_rmap, neighbor_default_originate_rmap_hidden_cmd, "neighbor default-originate route-map WORD", NEIGHBOR_STR NEIGHBOR_ADDR_STR2 "Originate default route to this neighbor\n" "Route-map to specify criteria to originate default\n" "route-map name\n") DEFUN (no_neighbor_default_originate, no_neighbor_default_originate_cmd, "no neighbor default-originate [route-map WORD]", NO_STR NEIGHBOR_STR NEIGHBOR_ADDR_STR2 "Originate default route to this neighbor\n" "Route-map to specify criteria to originate default\n" "route-map name\n") { int idx_peer = 2; return peer_default_originate_set_vty(vty, argv[idx_peer]->arg, bgp_node_afi(vty), bgp_node_safi(vty), NULL, 0); } ALIAS_HIDDEN( no_neighbor_default_originate, no_neighbor_default_originate_hidden_cmd, "no neighbor default-originate [route-map WORD]", NO_STR NEIGHBOR_STR NEIGHBOR_ADDR_STR2 "Originate default route to this neighbor\n" "Route-map to specify criteria to originate default\n" "route-map name\n") /* Set neighbor's BGP port. */ static int peer_port_vty(struct vty *vty, const char *ip_str, int afi, const char *port_str) { struct peer *peer; uint16_t port; struct servent *sp; peer = peer_lookup_vty(vty, ip_str); if (!peer) return CMD_WARNING_CONFIG_FAILED; if (!port_str) { sp = getservbyname("bgp", "tcp"); port = (sp == NULL) ? BGP_PORT_DEFAULT : ntohs(sp->s_port); } else { port = strtoul(port_str, NULL, 10); } peer_port_set(peer, port); return CMD_SUCCESS; } /* Set specified peer's BGP port. */ DEFUN (neighbor_port, neighbor_port_cmd, "neighbor port (0-65535)", NEIGHBOR_STR NEIGHBOR_ADDR_STR "Neighbor's BGP port\n" "TCP port number\n") { int idx_ip = 1; int idx_number = 3; return peer_port_vty(vty, argv[idx_ip]->arg, AFI_IP, argv[idx_number]->arg); } DEFUN (no_neighbor_port, no_neighbor_port_cmd, "no neighbor port [(0-65535)]", NO_STR NEIGHBOR_STR NEIGHBOR_ADDR_STR "Neighbor's BGP port\n" "TCP port number\n") { int idx_ip = 2; return peer_port_vty(vty, argv[idx_ip]->arg, AFI_IP, NULL); } /* neighbor weight. */ static int peer_weight_set_vty(struct vty *vty, const char *ip_str, afi_t afi, safi_t safi, const char *weight_str) { int ret; struct peer *peer; unsigned long weight; peer = peer_and_group_lookup_vty(vty, ip_str); if (!peer) return CMD_WARNING_CONFIG_FAILED; weight = strtoul(weight_str, NULL, 10); ret = peer_weight_set(peer, afi, safi, weight); return bgp_vty_return(vty, ret); } static int peer_weight_unset_vty(struct vty *vty, const char *ip_str, afi_t afi, safi_t safi) { int ret; struct peer *peer; peer = peer_and_group_lookup_vty(vty, ip_str); if (!peer) return CMD_WARNING_CONFIG_FAILED; ret = peer_weight_unset(peer, afi, safi); return bgp_vty_return(vty, ret); } DEFUN (neighbor_weight, neighbor_weight_cmd, "neighbor weight (0-65535)", NEIGHBOR_STR NEIGHBOR_ADDR_STR2 "Set default weight for routes from this neighbor\n" "default weight\n") { int idx_peer = 1; int idx_number = 3; return peer_weight_set_vty(vty, argv[idx_peer]->arg, bgp_node_afi(vty), bgp_node_safi(vty), argv[idx_number]->arg); } ALIAS_HIDDEN(neighbor_weight, neighbor_weight_hidden_cmd, "neighbor weight (0-65535)", NEIGHBOR_STR NEIGHBOR_ADDR_STR2 "Set default weight for routes from this neighbor\n" "default weight\n") DEFUN (no_neighbor_weight, no_neighbor_weight_cmd, "no neighbor weight [(0-65535)]", NO_STR NEIGHBOR_STR NEIGHBOR_ADDR_STR2 "Set default weight for routes from this neighbor\n" "default weight\n") { int idx_peer = 2; return peer_weight_unset_vty(vty, argv[idx_peer]->arg, bgp_node_afi(vty), bgp_node_safi(vty)); } ALIAS_HIDDEN(no_neighbor_weight, no_neighbor_weight_hidden_cmd, "no neighbor weight [(0-65535)]", NO_STR NEIGHBOR_STR NEIGHBOR_ADDR_STR2 "Set default weight for routes from this neighbor\n" "default weight\n") /* Override capability negotiation. */ DEFUN (neighbor_override_capability, neighbor_override_capability_cmd, "neighbor override-capability", NEIGHBOR_STR NEIGHBOR_ADDR_STR2 "Override capability negotiation result\n") { int idx_peer = 1; return peer_flag_set_vty(vty, argv[idx_peer]->arg, PEER_FLAG_OVERRIDE_CAPABILITY); } DEFUN (no_neighbor_override_capability, no_neighbor_override_capability_cmd, "no neighbor override-capability", NO_STR NEIGHBOR_STR NEIGHBOR_ADDR_STR2 "Override capability negotiation result\n") { int idx_peer = 2; return peer_flag_unset_vty(vty, argv[idx_peer]->arg, PEER_FLAG_OVERRIDE_CAPABILITY); } DEFUN (neighbor_strict_capability, neighbor_strict_capability_cmd, "neighbor strict-capability-match", NEIGHBOR_STR NEIGHBOR_ADDR_STR2 "Strict capability negotiation match\n") { int idx_peer = 1; return peer_flag_set_vty(vty, argv[idx_peer]->arg, PEER_FLAG_STRICT_CAP_MATCH); } DEFUN (no_neighbor_strict_capability, no_neighbor_strict_capability_cmd, "no neighbor strict-capability-match", NO_STR NEIGHBOR_STR NEIGHBOR_ADDR_STR2 "Strict capability negotiation match\n") { int idx_peer = 2; return peer_flag_unset_vty(vty, argv[idx_peer]->arg, PEER_FLAG_STRICT_CAP_MATCH); } static int peer_timers_set_vty(struct vty *vty, const char *ip_str, const char *keep_str, const char *hold_str) { int ret; struct peer *peer; uint32_t keepalive; uint32_t holdtime; peer = peer_and_group_lookup_vty(vty, ip_str); if (!peer) return CMD_WARNING_CONFIG_FAILED; keepalive = strtoul(keep_str, NULL, 10); holdtime = strtoul(hold_str, NULL, 10); ret = peer_timers_set(peer, keepalive, holdtime); return bgp_vty_return(vty, ret); } static int peer_timers_unset_vty(struct vty *vty, const char *ip_str) { int ret; struct peer *peer; peer = peer_and_group_lookup_vty(vty, ip_str); if (!peer) return CMD_WARNING_CONFIG_FAILED; ret = peer_timers_unset(peer); return bgp_vty_return(vty, ret); } DEFUN (neighbor_timers, neighbor_timers_cmd, "neighbor timers (0-65535) (0-65535)", NEIGHBOR_STR NEIGHBOR_ADDR_STR2 "BGP per neighbor timers\n" "Keepalive interval\n" "Holdtime\n") { int idx_peer = 1; int idx_number = 3; int idx_number_2 = 4; return peer_timers_set_vty(vty, argv[idx_peer]->arg, argv[idx_number]->arg, argv[idx_number_2]->arg); } DEFUN (no_neighbor_timers, no_neighbor_timers_cmd, "no neighbor timers [(0-65535) (0-65535)]", NO_STR NEIGHBOR_STR NEIGHBOR_ADDR_STR2 "BGP per neighbor timers\n" "Keepalive interval\n" "Holdtime\n") { int idx_peer = 2; return peer_timers_unset_vty(vty, argv[idx_peer]->arg); } static int peer_timers_connect_set_vty(struct vty *vty, const char *ip_str, const char *time_str) { int ret; struct peer *peer; uint32_t connect; peer = peer_and_group_lookup_vty(vty, ip_str); if (!peer) return CMD_WARNING_CONFIG_FAILED; connect = strtoul(time_str, NULL, 10); ret = peer_timers_connect_set(peer, connect); return bgp_vty_return(vty, ret); } static int peer_timers_connect_unset_vty(struct vty *vty, const char *ip_str) { int ret; struct peer *peer; peer = peer_and_group_lookup_vty(vty, ip_str); if (!peer) return CMD_WARNING_CONFIG_FAILED; ret = peer_timers_connect_unset(peer); return bgp_vty_return(vty, ret); } DEFUN (neighbor_timers_connect, neighbor_timers_connect_cmd, "neighbor timers connect (1-65535)", NEIGHBOR_STR NEIGHBOR_ADDR_STR2 "BGP per neighbor timers\n" "BGP connect timer\n" "Connect timer\n") { int idx_peer = 1; int idx_number = 4; return peer_timers_connect_set_vty(vty, argv[idx_peer]->arg, argv[idx_number]->arg); } DEFUN (no_neighbor_timers_connect, no_neighbor_timers_connect_cmd, "no neighbor timers connect [(1-65535)]", NO_STR NEIGHBOR_STR NEIGHBOR_ADDR_STR2 "BGP per neighbor timers\n" "BGP connect timer\n" "Connect timer\n") { int idx_peer = 2; return peer_timers_connect_unset_vty(vty, argv[idx_peer]->arg); } static int peer_advertise_interval_vty(struct vty *vty, const char *ip_str, const char *time_str, int set) { int ret; struct peer *peer; uint32_t routeadv = 0; peer = peer_and_group_lookup_vty(vty, ip_str); if (!peer) return CMD_WARNING_CONFIG_FAILED; if (time_str) routeadv = strtoul(time_str, NULL, 10); if (set) ret = peer_advertise_interval_set(peer, routeadv); else ret = peer_advertise_interval_unset(peer); return bgp_vty_return(vty, ret); } DEFUN (neighbor_advertise_interval, neighbor_advertise_interval_cmd, "neighbor advertisement-interval (0-600)", NEIGHBOR_STR NEIGHBOR_ADDR_STR2 "Minimum interval between sending BGP routing updates\n" "time in seconds\n") { int idx_peer = 1; int idx_number = 3; return peer_advertise_interval_vty(vty, argv[idx_peer]->arg, argv[idx_number]->arg, 1); } DEFUN (no_neighbor_advertise_interval, no_neighbor_advertise_interval_cmd, "no neighbor advertisement-interval [(0-600)]", NO_STR NEIGHBOR_STR NEIGHBOR_ADDR_STR2 "Minimum interval between sending BGP routing updates\n" "time in seconds\n") { int idx_peer = 2; return peer_advertise_interval_vty(vty, argv[idx_peer]->arg, NULL, 0); } /* Time to wait before processing route-map updates */ DEFUN (bgp_set_route_map_delay_timer, bgp_set_route_map_delay_timer_cmd, "bgp route-map delay-timer (0-600)", SET_STR "BGP route-map delay timer\n" "Time in secs to wait before processing route-map changes\n" "0 disables the timer, no route updates happen when route-maps change\n") { int idx_number = 3; uint32_t rmap_delay_timer; if (argv[idx_number]->arg) { rmap_delay_timer = strtoul(argv[idx_number]->arg, NULL, 10); bm->rmap_update_timer = rmap_delay_timer; /* if the dynamic update handling is being disabled, and a timer * is * running, stop the timer and act as if the timer has already * fired. */ if (!rmap_delay_timer && bm->t_rmap_update) { BGP_TIMER_OFF(bm->t_rmap_update); thread_execute(bm->master, bgp_route_map_update_timer, NULL, 0); } return CMD_SUCCESS; } else { vty_out(vty, "%% BGP invalid route-map delay-timer\n"); return CMD_WARNING_CONFIG_FAILED; } } DEFUN (no_bgp_set_route_map_delay_timer, no_bgp_set_route_map_delay_timer_cmd, "no bgp route-map delay-timer [(0-600)]", NO_STR BGP_STR "Default BGP route-map delay timer\n" "Reset to default time to wait for processing route-map changes\n" "0 disables the timer, no route updates happen when route-maps change\n") { bm->rmap_update_timer = RMAP_DEFAULT_UPDATE_TIMER; return CMD_SUCCESS; } /* neighbor interface */ static int peer_interface_vty(struct vty *vty, const char *ip_str, const char *str) { struct peer *peer; peer = peer_lookup_vty(vty, ip_str); if (!peer || peer->conf_if) { vty_out(vty, "%% BGP invalid peer %s\n", ip_str); return CMD_WARNING_CONFIG_FAILED; } if (str) peer_interface_set(peer, str); else peer_interface_unset(peer); return CMD_SUCCESS; } DEFUN (neighbor_interface, neighbor_interface_cmd, "neighbor interface WORD", NEIGHBOR_STR NEIGHBOR_ADDR_STR "Interface\n" "Interface name\n") { int idx_ip = 1; int idx_word = 3; return peer_interface_vty(vty, argv[idx_ip]->arg, argv[idx_word]->arg); } DEFUN (no_neighbor_interface, no_neighbor_interface_cmd, "no neighbor interface WORD", NO_STR NEIGHBOR_STR NEIGHBOR_ADDR_STR2 "Interface\n" "Interface name\n") { int idx_peer = 2; return peer_interface_vty(vty, argv[idx_peer]->arg, NULL); } DEFUN (neighbor_distribute_list, neighbor_distribute_list_cmd, "neighbor distribute-list <(1-199)|(1300-2699)|WORD> ", NEIGHBOR_STR NEIGHBOR_ADDR_STR2 "Filter updates to/from this neighbor\n" "IP access-list number\n" "IP access-list number (expanded range)\n" "IP Access-list name\n" "Filter incoming updates\n" "Filter outgoing updates\n") { int idx_peer = 1; int idx_acl = 3; int direct, ret; struct peer *peer; const char *pstr = argv[idx_peer]->arg; const char *acl = argv[idx_acl]->arg; const char *inout = argv[argc - 1]->text; peer = peer_and_group_lookup_vty(vty, pstr); if (!peer) return CMD_WARNING_CONFIG_FAILED; /* Check filter direction. */ direct = strmatch(inout, "in") ? FILTER_IN : FILTER_OUT; ret = peer_distribute_set(peer, bgp_node_afi(vty), bgp_node_safi(vty), direct, acl); return bgp_vty_return(vty, ret); } ALIAS_HIDDEN( neighbor_distribute_list, neighbor_distribute_list_hidden_cmd, "neighbor distribute-list <(1-199)|(1300-2699)|WORD> ", NEIGHBOR_STR NEIGHBOR_ADDR_STR2 "Filter updates to/from this neighbor\n" "IP access-list number\n" "IP access-list number (expanded range)\n" "IP Access-list name\n" "Filter incoming updates\n" "Filter outgoing updates\n") DEFUN (no_neighbor_distribute_list, no_neighbor_distribute_list_cmd, "no neighbor distribute-list <(1-199)|(1300-2699)|WORD> ", NO_STR NEIGHBOR_STR NEIGHBOR_ADDR_STR2 "Filter updates to/from this neighbor\n" "IP access-list number\n" "IP access-list number (expanded range)\n" "IP Access-list name\n" "Filter incoming updates\n" "Filter outgoing updates\n") { int idx_peer = 2; int direct, ret; struct peer *peer; const char *pstr = argv[idx_peer]->arg; const char *inout = argv[argc - 1]->text; peer = peer_and_group_lookup_vty(vty, pstr); if (!peer) return CMD_WARNING_CONFIG_FAILED; /* Check filter direction. */ direct = strmatch(inout, "in") ? FILTER_IN : FILTER_OUT; ret = peer_distribute_unset(peer, bgp_node_afi(vty), bgp_node_safi(vty), direct); return bgp_vty_return(vty, ret); } ALIAS_HIDDEN( no_neighbor_distribute_list, no_neighbor_distribute_list_hidden_cmd, "no neighbor distribute-list <(1-199)|(1300-2699)|WORD> ", NO_STR NEIGHBOR_STR NEIGHBOR_ADDR_STR2 "Filter updates to/from this neighbor\n" "IP access-list number\n" "IP access-list number (expanded range)\n" "IP Access-list name\n" "Filter incoming updates\n" "Filter outgoing updates\n") /* Set prefix list to the peer. */ static int peer_prefix_list_set_vty(struct vty *vty, const char *ip_str, afi_t afi, safi_t safi, const char *name_str, const char *direct_str) { int ret; int direct = FILTER_IN; struct peer *peer; peer = peer_and_group_lookup_vty(vty, ip_str); if (!peer) return CMD_WARNING_CONFIG_FAILED; /* Check filter direction. */ if (strncmp(direct_str, "i", 1) == 0) direct = FILTER_IN; else if (strncmp(direct_str, "o", 1) == 0) direct = FILTER_OUT; ret = peer_prefix_list_set(peer, afi, safi, direct, name_str); return bgp_vty_return(vty, ret); } static int peer_prefix_list_unset_vty(struct vty *vty, const char *ip_str, afi_t afi, safi_t safi, const char *direct_str) { int ret; struct peer *peer; int direct = FILTER_IN; peer = peer_and_group_lookup_vty(vty, ip_str); if (!peer) return CMD_WARNING_CONFIG_FAILED; /* Check filter direction. */ if (strncmp(direct_str, "i", 1) == 0) direct = FILTER_IN; else if (strncmp(direct_str, "o", 1) == 0) direct = FILTER_OUT; ret = peer_prefix_list_unset(peer, afi, safi, direct); return bgp_vty_return(vty, ret); } DEFUN (neighbor_prefix_list, neighbor_prefix_list_cmd, "neighbor prefix-list WORD ", NEIGHBOR_STR NEIGHBOR_ADDR_STR2 "Filter updates to/from this neighbor\n" "Name of a prefix list\n" "Filter incoming updates\n" "Filter outgoing updates\n") { int idx_peer = 1; int idx_word = 3; int idx_in_out = 4; return peer_prefix_list_set_vty( vty, argv[idx_peer]->arg, bgp_node_afi(vty), bgp_node_safi(vty), argv[idx_word]->arg, argv[idx_in_out]->arg); } ALIAS_HIDDEN(neighbor_prefix_list, neighbor_prefix_list_hidden_cmd, "neighbor prefix-list WORD ", NEIGHBOR_STR NEIGHBOR_ADDR_STR2 "Filter updates to/from this neighbor\n" "Name of a prefix list\n" "Filter incoming updates\n" "Filter outgoing updates\n") DEFUN (no_neighbor_prefix_list, no_neighbor_prefix_list_cmd, "no neighbor prefix-list WORD ", NO_STR NEIGHBOR_STR NEIGHBOR_ADDR_STR2 "Filter updates to/from this neighbor\n" "Name of a prefix list\n" "Filter incoming updates\n" "Filter outgoing updates\n") { int idx_peer = 2; int idx_in_out = 5; return peer_prefix_list_unset_vty(vty, argv[idx_peer]->arg, bgp_node_afi(vty), bgp_node_safi(vty), argv[idx_in_out]->arg); } ALIAS_HIDDEN(no_neighbor_prefix_list, no_neighbor_prefix_list_hidden_cmd, "no neighbor prefix-list WORD ", NO_STR NEIGHBOR_STR NEIGHBOR_ADDR_STR2 "Filter updates to/from this neighbor\n" "Name of a prefix list\n" "Filter incoming updates\n" "Filter outgoing updates\n") static int peer_aslist_set_vty(struct vty *vty, const char *ip_str, afi_t afi, safi_t safi, const char *name_str, const char *direct_str) { int ret; struct peer *peer; int direct = FILTER_IN; peer = peer_and_group_lookup_vty(vty, ip_str); if (!peer) return CMD_WARNING_CONFIG_FAILED; /* Check filter direction. */ if (strncmp(direct_str, "i", 1) == 0) direct = FILTER_IN; else if (strncmp(direct_str, "o", 1) == 0) direct = FILTER_OUT; ret = peer_aslist_set(peer, afi, safi, direct, name_str); return bgp_vty_return(vty, ret); } static int peer_aslist_unset_vty(struct vty *vty, const char *ip_str, afi_t afi, safi_t safi, const char *direct_str) { int ret; struct peer *peer; int direct = FILTER_IN; peer = peer_and_group_lookup_vty(vty, ip_str); if (!peer) return CMD_WARNING_CONFIG_FAILED; /* Check filter direction. */ if (strncmp(direct_str, "i", 1) == 0) direct = FILTER_IN; else if (strncmp(direct_str, "o", 1) == 0) direct = FILTER_OUT; ret = peer_aslist_unset(peer, afi, safi, direct); return bgp_vty_return(vty, ret); } DEFUN (neighbor_filter_list, neighbor_filter_list_cmd, "neighbor filter-list WORD ", NEIGHBOR_STR NEIGHBOR_ADDR_STR2 "Establish BGP filters\n" "AS path access-list name\n" "Filter incoming routes\n" "Filter outgoing routes\n") { int idx_peer = 1; int idx_word = 3; int idx_in_out = 4; return peer_aslist_set_vty(vty, argv[idx_peer]->arg, bgp_node_afi(vty), bgp_node_safi(vty), argv[idx_word]->arg, argv[idx_in_out]->arg); } ALIAS_HIDDEN(neighbor_filter_list, neighbor_filter_list_hidden_cmd, "neighbor filter-list WORD ", NEIGHBOR_STR NEIGHBOR_ADDR_STR2 "Establish BGP filters\n" "AS path access-list name\n" "Filter incoming routes\n" "Filter outgoing routes\n") DEFUN (no_neighbor_filter_list, no_neighbor_filter_list_cmd, "no neighbor filter-list WORD ", NO_STR NEIGHBOR_STR NEIGHBOR_ADDR_STR2 "Establish BGP filters\n" "AS path access-list name\n" "Filter incoming routes\n" "Filter outgoing routes\n") { int idx_peer = 2; int idx_in_out = 5; return peer_aslist_unset_vty(vty, argv[idx_peer]->arg, bgp_node_afi(vty), bgp_node_safi(vty), argv[idx_in_out]->arg); } ALIAS_HIDDEN(no_neighbor_filter_list, no_neighbor_filter_list_hidden_cmd, "no neighbor filter-list WORD ", NO_STR NEIGHBOR_STR NEIGHBOR_ADDR_STR2 "Establish BGP filters\n" "AS path access-list name\n" "Filter incoming routes\n" "Filter outgoing routes\n") /* Set route-map to the peer. */ static int peer_route_map_set_vty(struct vty *vty, const char *ip_str, afi_t afi, safi_t safi, const char *name_str, const char *direct_str) { int ret; struct peer *peer; int direct = RMAP_IN; struct route_map *route_map; peer = peer_and_group_lookup_vty(vty, ip_str); if (!peer) return CMD_WARNING_CONFIG_FAILED; /* Check filter direction. */ if (strncmp(direct_str, "in", 2) == 0) direct = RMAP_IN; else if (strncmp(direct_str, "o", 1) == 0) direct = RMAP_OUT; route_map = route_map_lookup_warn_noexist(vty, name_str); ret = peer_route_map_set(peer, afi, safi, direct, name_str, route_map); return bgp_vty_return(vty, ret); } static int peer_route_map_unset_vty(struct vty *vty, const char *ip_str, afi_t afi, safi_t safi, const char *direct_str) { int ret; struct peer *peer; int direct = RMAP_IN; peer = peer_and_group_lookup_vty(vty, ip_str); if (!peer) return CMD_WARNING_CONFIG_FAILED; /* Check filter direction. */ if (strncmp(direct_str, "in", 2) == 0) direct = RMAP_IN; else if (strncmp(direct_str, "o", 1) == 0) direct = RMAP_OUT; ret = peer_route_map_unset(peer, afi, safi, direct); return bgp_vty_return(vty, ret); } DEFUN (neighbor_route_map, neighbor_route_map_cmd, "neighbor route-map WORD ", NEIGHBOR_STR NEIGHBOR_ADDR_STR2 "Apply route map to neighbor\n" "Name of route map\n" "Apply map to incoming routes\n" "Apply map to outbound routes\n") { int idx_peer = 1; int idx_word = 3; int idx_in_out = 4; return peer_route_map_set_vty( vty, argv[idx_peer]->arg, bgp_node_afi(vty), bgp_node_safi(vty), argv[idx_word]->arg, argv[idx_in_out]->arg); } ALIAS_HIDDEN(neighbor_route_map, neighbor_route_map_hidden_cmd, "neighbor route-map WORD ", NEIGHBOR_STR NEIGHBOR_ADDR_STR2 "Apply route map to neighbor\n" "Name of route map\n" "Apply map to incoming routes\n" "Apply map to outbound routes\n") DEFUN (no_neighbor_route_map, no_neighbor_route_map_cmd, "no neighbor route-map WORD ", NO_STR NEIGHBOR_STR NEIGHBOR_ADDR_STR2 "Apply route map to neighbor\n" "Name of route map\n" "Apply map to incoming routes\n" "Apply map to outbound routes\n") { int idx_peer = 2; int idx_in_out = 5; return peer_route_map_unset_vty(vty, argv[idx_peer]->arg, bgp_node_afi(vty), bgp_node_safi(vty), argv[idx_in_out]->arg); } ALIAS_HIDDEN(no_neighbor_route_map, no_neighbor_route_map_hidden_cmd, "no neighbor route-map WORD ", NO_STR NEIGHBOR_STR NEIGHBOR_ADDR_STR2 "Apply route map to neighbor\n" "Name of route map\n" "Apply map to incoming routes\n" "Apply map to outbound routes\n") /* Set unsuppress-map to the peer. */ static int peer_unsuppress_map_set_vty(struct vty *vty, const char *ip_str, afi_t afi, safi_t safi, const char *name_str) { int ret; struct peer *peer; struct route_map *route_map; peer = peer_and_group_lookup_vty(vty, ip_str); if (!peer) return CMD_WARNING_CONFIG_FAILED; route_map = route_map_lookup_warn_noexist(vty, name_str); ret = peer_unsuppress_map_set(peer, afi, safi, name_str, route_map); return bgp_vty_return(vty, ret); } /* Unset route-map from the peer. */ static int peer_unsuppress_map_unset_vty(struct vty *vty, const char *ip_str, afi_t afi, safi_t safi) { int ret; struct peer *peer; peer = peer_and_group_lookup_vty(vty, ip_str); if (!peer) return CMD_WARNING_CONFIG_FAILED; ret = peer_unsuppress_map_unset(peer, afi, safi); return bgp_vty_return(vty, ret); } DEFUN (neighbor_unsuppress_map, neighbor_unsuppress_map_cmd, "neighbor unsuppress-map WORD", NEIGHBOR_STR NEIGHBOR_ADDR_STR2 "Route-map to selectively unsuppress suppressed routes\n" "Name of route map\n") { int idx_peer = 1; int idx_word = 3; return peer_unsuppress_map_set_vty( vty, argv[idx_peer]->arg, bgp_node_afi(vty), bgp_node_safi(vty), argv[idx_word]->arg); } ALIAS_HIDDEN(neighbor_unsuppress_map, neighbor_unsuppress_map_hidden_cmd, "neighbor unsuppress-map WORD", NEIGHBOR_STR NEIGHBOR_ADDR_STR2 "Route-map to selectively unsuppress suppressed routes\n" "Name of route map\n") DEFUN (no_neighbor_unsuppress_map, no_neighbor_unsuppress_map_cmd, "no neighbor unsuppress-map WORD", NO_STR NEIGHBOR_STR NEIGHBOR_ADDR_STR2 "Route-map to selectively unsuppress suppressed routes\n" "Name of route map\n") { int idx_peer = 2; return peer_unsuppress_map_unset_vty(vty, argv[idx_peer]->arg, bgp_node_afi(vty), bgp_node_safi(vty)); } ALIAS_HIDDEN(no_neighbor_unsuppress_map, no_neighbor_unsuppress_map_hidden_cmd, "no neighbor unsuppress-map WORD", NO_STR NEIGHBOR_STR NEIGHBOR_ADDR_STR2 "Route-map to selectively unsuppress suppressed routes\n" "Name of route map\n") static int peer_maximum_prefix_set_vty(struct vty *vty, const char *ip_str, afi_t afi, safi_t safi, const char *num_str, const char *threshold_str, int warning, const char *restart_str) { int ret; struct peer *peer; uint32_t max; uint8_t threshold; uint16_t restart; peer = peer_and_group_lookup_vty(vty, ip_str); if (!peer) return CMD_WARNING_CONFIG_FAILED; max = strtoul(num_str, NULL, 10); if (threshold_str) threshold = atoi(threshold_str); else threshold = MAXIMUM_PREFIX_THRESHOLD_DEFAULT; if (restart_str) restart = atoi(restart_str); else restart = 0; ret = peer_maximum_prefix_set(peer, afi, safi, max, threshold, warning, restart); return bgp_vty_return(vty, ret); } static int peer_maximum_prefix_unset_vty(struct vty *vty, const char *ip_str, afi_t afi, safi_t safi) { int ret; struct peer *peer; peer = peer_and_group_lookup_vty(vty, ip_str); if (!peer) return CMD_WARNING_CONFIG_FAILED; ret = peer_maximum_prefix_unset(peer, afi, safi); return bgp_vty_return(vty, ret); } /* Maximum number of prefix configuration. prefix count is different for each peer configuration. So this configuration can be set for each peer configuration. */ DEFUN (neighbor_maximum_prefix, neighbor_maximum_prefix_cmd, "neighbor maximum-prefix (1-4294967295)", NEIGHBOR_STR NEIGHBOR_ADDR_STR2 "Maximum number of prefix accept from this peer\n" "maximum no. of prefix limit\n") { int idx_peer = 1; int idx_number = 3; return peer_maximum_prefix_set_vty( vty, argv[idx_peer]->arg, bgp_node_afi(vty), bgp_node_safi(vty), argv[idx_number]->arg, NULL, 0, NULL); } ALIAS_HIDDEN(neighbor_maximum_prefix, neighbor_maximum_prefix_hidden_cmd, "neighbor maximum-prefix (1-4294967295)", NEIGHBOR_STR NEIGHBOR_ADDR_STR2 "Maximum number of prefix accept from this peer\n" "maximum no. of prefix limit\n") DEFUN (neighbor_maximum_prefix_threshold, neighbor_maximum_prefix_threshold_cmd, "neighbor maximum-prefix (1-4294967295) (1-100)", NEIGHBOR_STR NEIGHBOR_ADDR_STR2 "Maximum number of prefix accept from this peer\n" "maximum no. of prefix limit\n" "Threshold value (%) at which to generate a warning msg\n") { int idx_peer = 1; int idx_number = 3; int idx_number_2 = 4; return peer_maximum_prefix_set_vty( vty, argv[idx_peer]->arg, bgp_node_afi(vty), bgp_node_safi(vty), argv[idx_number]->arg, argv[idx_number_2]->arg, 0, NULL); } ALIAS_HIDDEN( neighbor_maximum_prefix_threshold, neighbor_maximum_prefix_threshold_hidden_cmd, "neighbor maximum-prefix (1-4294967295) (1-100)", NEIGHBOR_STR NEIGHBOR_ADDR_STR2 "Maximum number of prefix accept from this peer\n" "maximum no. of prefix limit\n" "Threshold value (%) at which to generate a warning msg\n") DEFUN (neighbor_maximum_prefix_warning, neighbor_maximum_prefix_warning_cmd, "neighbor maximum-prefix (1-4294967295) warning-only", NEIGHBOR_STR NEIGHBOR_ADDR_STR2 "Maximum number of prefix accept from this peer\n" "maximum no. of prefix limit\n" "Only give warning message when limit is exceeded\n") { int idx_peer = 1; int idx_number = 3; return peer_maximum_prefix_set_vty( vty, argv[idx_peer]->arg, bgp_node_afi(vty), bgp_node_safi(vty), argv[idx_number]->arg, NULL, 1, NULL); } ALIAS_HIDDEN( neighbor_maximum_prefix_warning, neighbor_maximum_prefix_warning_hidden_cmd, "neighbor maximum-prefix (1-4294967295) warning-only", NEIGHBOR_STR NEIGHBOR_ADDR_STR2 "Maximum number of prefix accept from this peer\n" "maximum no. of prefix limit\n" "Only give warning message when limit is exceeded\n") DEFUN (neighbor_maximum_prefix_threshold_warning, neighbor_maximum_prefix_threshold_warning_cmd, "neighbor maximum-prefix (1-4294967295) (1-100) warning-only", NEIGHBOR_STR NEIGHBOR_ADDR_STR2 "Maximum number of prefix accept from this peer\n" "maximum no. of prefix limit\n" "Threshold value (%) at which to generate a warning msg\n" "Only give warning message when limit is exceeded\n") { int idx_peer = 1; int idx_number = 3; int idx_number_2 = 4; return peer_maximum_prefix_set_vty( vty, argv[idx_peer]->arg, bgp_node_afi(vty), bgp_node_safi(vty), argv[idx_number]->arg, argv[idx_number_2]->arg, 1, NULL); } ALIAS_HIDDEN( neighbor_maximum_prefix_threshold_warning, neighbor_maximum_prefix_threshold_warning_hidden_cmd, "neighbor maximum-prefix (1-4294967295) (1-100) warning-only", NEIGHBOR_STR NEIGHBOR_ADDR_STR2 "Maximum number of prefix accept from this peer\n" "maximum no. of prefix limit\n" "Threshold value (%) at which to generate a warning msg\n" "Only give warning message when limit is exceeded\n") DEFUN (neighbor_maximum_prefix_restart, neighbor_maximum_prefix_restart_cmd, "neighbor maximum-prefix (1-4294967295) restart (1-65535)", NEIGHBOR_STR NEIGHBOR_ADDR_STR2 "Maximum number of prefix accept from this peer\n" "maximum no. of prefix limit\n" "Restart bgp connection after limit is exceeded\n" "Restart interval in minutes\n") { int idx_peer = 1; int idx_number = 3; int idx_number_2 = 5; return peer_maximum_prefix_set_vty( vty, argv[idx_peer]->arg, bgp_node_afi(vty), bgp_node_safi(vty), argv[idx_number]->arg, NULL, 0, argv[idx_number_2]->arg); } ALIAS_HIDDEN( neighbor_maximum_prefix_restart, neighbor_maximum_prefix_restart_hidden_cmd, "neighbor maximum-prefix (1-4294967295) restart (1-65535)", NEIGHBOR_STR NEIGHBOR_ADDR_STR2 "Maximum number of prefix accept from this peer\n" "maximum no. of prefix limit\n" "Restart bgp connection after limit is exceeded\n" "Restart interval in minutes\n") DEFUN (neighbor_maximum_prefix_threshold_restart, neighbor_maximum_prefix_threshold_restart_cmd, "neighbor maximum-prefix (1-4294967295) (1-100) restart (1-65535)", NEIGHBOR_STR NEIGHBOR_ADDR_STR2 "Maximum number of prefixes to accept from this peer\n" "maximum no. of prefix limit\n" "Threshold value (%) at which to generate a warning msg\n" "Restart bgp connection after limit is exceeded\n" "Restart interval in minutes\n") { int idx_peer = 1; int idx_number = 3; int idx_number_2 = 4; int idx_number_3 = 6; return peer_maximum_prefix_set_vty( vty, argv[idx_peer]->arg, bgp_node_afi(vty), bgp_node_safi(vty), argv[idx_number]->arg, argv[idx_number_2]->arg, 0, argv[idx_number_3]->arg); } ALIAS_HIDDEN( neighbor_maximum_prefix_threshold_restart, neighbor_maximum_prefix_threshold_restart_hidden_cmd, "neighbor maximum-prefix (1-4294967295) (1-100) restart (1-65535)", NEIGHBOR_STR NEIGHBOR_ADDR_STR2 "Maximum number of prefixes to accept from this peer\n" "maximum no. of prefix limit\n" "Threshold value (%) at which to generate a warning msg\n" "Restart bgp connection after limit is exceeded\n" "Restart interval in minutes\n") DEFUN (no_neighbor_maximum_prefix, no_neighbor_maximum_prefix_cmd, "no neighbor maximum-prefix [(1-4294967295) [(1-100)] [restart (1-65535)] [warning-only]]", NO_STR NEIGHBOR_STR NEIGHBOR_ADDR_STR2 "Maximum number of prefixes to accept from this peer\n" "maximum no. of prefix limit\n" "Threshold value (%) at which to generate a warning msg\n" "Restart bgp connection after limit is exceeded\n" "Restart interval in minutes\n" "Only give warning message when limit is exceeded\n") { int idx_peer = 2; return peer_maximum_prefix_unset_vty(vty, argv[idx_peer]->arg, bgp_node_afi(vty), bgp_node_safi(vty)); } ALIAS_HIDDEN( no_neighbor_maximum_prefix, no_neighbor_maximum_prefix_hidden_cmd, "no neighbor maximum-prefix [(1-4294967295) [(1-100)] [restart (1-65535)] [warning-only]]", NO_STR NEIGHBOR_STR NEIGHBOR_ADDR_STR2 "Maximum number of prefixes to accept from this peer\n" "maximum no. of prefix limit\n" "Threshold value (%) at which to generate a warning msg\n" "Restart bgp connection after limit is exceeded\n" "Restart interval in minutes\n" "Only give warning message when limit is exceeded\n") /* "neighbor allowas-in" */ DEFUN (neighbor_allowas_in, neighbor_allowas_in_cmd, "neighbor allowas-in [<(1-10)|origin>]", NEIGHBOR_STR NEIGHBOR_ADDR_STR2 "Accept as-path with my AS present in it\n" "Number of occurrences of AS number\n" "Only accept my AS in the as-path if the route was originated in my AS\n") { int idx_peer = 1; int idx_number_origin = 3; int ret; int origin = 0; struct peer *peer; int allow_num = 0; peer = peer_and_group_lookup_vty(vty, argv[idx_peer]->arg); if (!peer) return CMD_WARNING_CONFIG_FAILED; if (argc <= idx_number_origin) allow_num = 3; else { if (argv[idx_number_origin]->type == WORD_TKN) origin = 1; else allow_num = atoi(argv[idx_number_origin]->arg); } ret = peer_allowas_in_set(peer, bgp_node_afi(vty), bgp_node_safi(vty), allow_num, origin); return bgp_vty_return(vty, ret); } ALIAS_HIDDEN( neighbor_allowas_in, neighbor_allowas_in_hidden_cmd, "neighbor allowas-in [<(1-10)|origin>]", NEIGHBOR_STR NEIGHBOR_ADDR_STR2 "Accept as-path with my AS present in it\n" "Number of occurrences of AS number\n" "Only accept my AS in the as-path if the route was originated in my AS\n") DEFUN (no_neighbor_allowas_in, no_neighbor_allowas_in_cmd, "no neighbor allowas-in [<(1-10)|origin>]", NO_STR NEIGHBOR_STR NEIGHBOR_ADDR_STR2 "allow local ASN appears in aspath attribute\n" "Number of occurrences of AS number\n" "Only accept my AS in the as-path if the route was originated in my AS\n") { int idx_peer = 2; int ret; struct peer *peer; peer = peer_and_group_lookup_vty(vty, argv[idx_peer]->arg); if (!peer) return CMD_WARNING_CONFIG_FAILED; ret = peer_allowas_in_unset(peer, bgp_node_afi(vty), bgp_node_safi(vty)); return bgp_vty_return(vty, ret); } ALIAS_HIDDEN( no_neighbor_allowas_in, no_neighbor_allowas_in_hidden_cmd, "no neighbor allowas-in [<(1-10)|origin>]", NO_STR NEIGHBOR_STR NEIGHBOR_ADDR_STR2 "allow local ASN appears in aspath attribute\n" "Number of occurrences of AS number\n" "Only accept my AS in the as-path if the route was originated in my AS\n") DEFUN (neighbor_ttl_security, neighbor_ttl_security_cmd, "neighbor ttl-security hops (1-254)", NEIGHBOR_STR NEIGHBOR_ADDR_STR2 "BGP ttl-security parameters\n" "Specify the maximum number of hops to the BGP peer\n" "Number of hops to BGP peer\n") { int idx_peer = 1; int idx_number = 4; struct peer *peer; int gtsm_hops; peer = peer_and_group_lookup_vty(vty, argv[idx_peer]->arg); if (!peer) return CMD_WARNING_CONFIG_FAILED; gtsm_hops = strtoul(argv[idx_number]->arg, NULL, 10); /* * If 'neighbor swpX', then this is for directly connected peers, * we should not accept a ttl-security hops value greater than 1. */ if (peer->conf_if && (gtsm_hops > 1)) { vty_out(vty, "%s is directly connected peer, hops cannot exceed 1\n", argv[idx_peer]->arg); return CMD_WARNING_CONFIG_FAILED; } return bgp_vty_return(vty, peer_ttl_security_hops_set(peer, gtsm_hops)); } DEFUN (no_neighbor_ttl_security, no_neighbor_ttl_security_cmd, "no neighbor ttl-security hops (1-254)", NO_STR NEIGHBOR_STR NEIGHBOR_ADDR_STR2 "BGP ttl-security parameters\n" "Specify the maximum number of hops to the BGP peer\n" "Number of hops to BGP peer\n") { int idx_peer = 2; struct peer *peer; peer = peer_and_group_lookup_vty(vty, argv[idx_peer]->arg); if (!peer) return CMD_WARNING_CONFIG_FAILED; return bgp_vty_return(vty, peer_ttl_security_hops_unset(peer)); } DEFUN (neighbor_addpath_tx_all_paths, neighbor_addpath_tx_all_paths_cmd, "neighbor addpath-tx-all-paths", NEIGHBOR_STR NEIGHBOR_ADDR_STR2 "Use addpath to advertise all paths to a neighbor\n") { int idx_peer = 1; struct peer *peer; peer = peer_and_group_lookup_vty(vty, argv[idx_peer]->arg); if (!peer) return CMD_WARNING_CONFIG_FAILED; bgp_addpath_set_peer_type(peer, bgp_node_afi(vty), bgp_node_safi(vty), BGP_ADDPATH_ALL); return CMD_SUCCESS; } ALIAS_HIDDEN(neighbor_addpath_tx_all_paths, neighbor_addpath_tx_all_paths_hidden_cmd, "neighbor addpath-tx-all-paths", NEIGHBOR_STR NEIGHBOR_ADDR_STR2 "Use addpath to advertise all paths to a neighbor\n") DEFUN (no_neighbor_addpath_tx_all_paths, no_neighbor_addpath_tx_all_paths_cmd, "no neighbor addpath-tx-all-paths", NO_STR NEIGHBOR_STR NEIGHBOR_ADDR_STR2 "Use addpath to advertise all paths to a neighbor\n") { int idx_peer = 2; struct peer *peer; peer = peer_and_group_lookup_vty(vty, argv[idx_peer]->arg); if (!peer) return CMD_WARNING_CONFIG_FAILED; if (peer->addpath_type[bgp_node_afi(vty)][bgp_node_safi(vty)] != BGP_ADDPATH_ALL) { vty_out(vty, "%% Peer not currently configured to transmit all paths."); return CMD_WARNING_CONFIG_FAILED; } bgp_addpath_set_peer_type(peer, bgp_node_afi(vty), bgp_node_safi(vty), BGP_ADDPATH_NONE); return CMD_SUCCESS; } ALIAS_HIDDEN(no_neighbor_addpath_tx_all_paths, no_neighbor_addpath_tx_all_paths_hidden_cmd, "no neighbor addpath-tx-all-paths", NO_STR NEIGHBOR_STR NEIGHBOR_ADDR_STR2 "Use addpath to advertise all paths to a neighbor\n") DEFUN (neighbor_addpath_tx_bestpath_per_as, neighbor_addpath_tx_bestpath_per_as_cmd, "neighbor addpath-tx-bestpath-per-AS", NEIGHBOR_STR NEIGHBOR_ADDR_STR2 "Use addpath to advertise the bestpath per each neighboring AS\n") { int idx_peer = 1; struct peer *peer; peer = peer_and_group_lookup_vty(vty, argv[idx_peer]->arg); if (!peer) return CMD_WARNING_CONFIG_FAILED; bgp_addpath_set_peer_type(peer, bgp_node_afi(vty), bgp_node_safi(vty), BGP_ADDPATH_BEST_PER_AS); return CMD_SUCCESS; } ALIAS_HIDDEN(neighbor_addpath_tx_bestpath_per_as, neighbor_addpath_tx_bestpath_per_as_hidden_cmd, "neighbor addpath-tx-bestpath-per-AS", NEIGHBOR_STR NEIGHBOR_ADDR_STR2 "Use addpath to advertise the bestpath per each neighboring AS\n") DEFUN (no_neighbor_addpath_tx_bestpath_per_as, no_neighbor_addpath_tx_bestpath_per_as_cmd, "no neighbor addpath-tx-bestpath-per-AS", NO_STR NEIGHBOR_STR NEIGHBOR_ADDR_STR2 "Use addpath to advertise the bestpath per each neighboring AS\n") { int idx_peer = 2; struct peer *peer; peer = peer_and_group_lookup_vty(vty, argv[idx_peer]->arg); if (!peer) return CMD_WARNING_CONFIG_FAILED; if (peer->addpath_type[bgp_node_afi(vty)][bgp_node_safi(vty)] != BGP_ADDPATH_BEST_PER_AS) { vty_out(vty, "%% Peer not currently configured to transmit all best path per as."); return CMD_WARNING_CONFIG_FAILED; } bgp_addpath_set_peer_type(peer, bgp_node_afi(vty), bgp_node_safi(vty), BGP_ADDPATH_NONE); return CMD_SUCCESS; } ALIAS_HIDDEN(no_neighbor_addpath_tx_bestpath_per_as, no_neighbor_addpath_tx_bestpath_per_as_hidden_cmd, "no neighbor addpath-tx-bestpath-per-AS", NO_STR NEIGHBOR_STR NEIGHBOR_ADDR_STR2 "Use addpath to advertise the bestpath per each neighboring AS\n") static int set_ecom_list(struct vty *vty, int argc, struct cmd_token **argv, struct ecommunity **list) { struct ecommunity *ecom = NULL; struct ecommunity *ecomadd; for (; argc; --argc, ++argv) { ecomadd = ecommunity_str2com(argv[0]->arg, ECOMMUNITY_ROUTE_TARGET, 0); if (!ecomadd) { vty_out(vty, "Malformed community-list value\n"); if (ecom) ecommunity_free(&ecom); return CMD_WARNING_CONFIG_FAILED; } if (ecom) { ecommunity_merge(ecom, ecomadd); ecommunity_free(&ecomadd); } else { ecom = ecomadd; } } if (*list) { ecommunity_free(&*list); } *list = ecom; return CMD_SUCCESS; } /* * v2vimport is true if we are handling a `import vrf ...` command */ static afi_t vpn_policy_getafi(struct vty *vty, struct bgp *bgp, bool v2vimport) { afi_t afi; switch (vty->node) { case BGP_IPV4_NODE: afi = AFI_IP; break; case BGP_IPV6_NODE: afi = AFI_IP6; break; default: vty_out(vty, "%% context error: valid only in address-family unicast block\n"); return AFI_MAX; } if (!v2vimport) { if (CHECK_FLAG(bgp->af_flags[afi][SAFI_UNICAST], BGP_CONFIG_VRF_TO_VRF_IMPORT) || CHECK_FLAG(bgp->af_flags[afi][SAFI_UNICAST], BGP_CONFIG_VRF_TO_VRF_EXPORT)) { vty_out(vty, "%% error: Please unconfigure import vrf commands before using vpn commands\n"); return AFI_MAX; } } else { if (CHECK_FLAG(bgp->af_flags[afi][SAFI_UNICAST], BGP_CONFIG_VRF_TO_MPLSVPN_EXPORT) || CHECK_FLAG(bgp->af_flags[afi][SAFI_UNICAST], BGP_CONFIG_MPLSVPN_TO_VRF_IMPORT)) { vty_out(vty, "%% error: Please unconfigure vpn to vrf commands before using import vrf commands\n"); return AFI_MAX; } } return afi; } DEFPY (af_rd_vpn_export, af_rd_vpn_export_cmd, "[no] rd vpn export ASN:NN_OR_IP-ADDRESS:NN$rd_str", NO_STR "Specify route distinguisher\n" "Between current address-family and vpn\n" "For routes leaked from current address-family to vpn\n" "Route Distinguisher (: | :)\n") { VTY_DECLVAR_CONTEXT(bgp, bgp); struct prefix_rd prd; int ret; afi_t afi; int idx = 0; int yes = 1; if (argv_find(argv, argc, "no", &idx)) yes = 0; if (yes) { ret = str2prefix_rd(rd_str, &prd); if (!ret) { vty_out(vty, "%% Malformed rd\n"); return CMD_WARNING_CONFIG_FAILED; } } afi = vpn_policy_getafi(vty, bgp, false); if (afi == AFI_MAX) return CMD_WARNING_CONFIG_FAILED; /* * pre-change: un-export vpn routes (vpn->vrf routes unaffected) */ vpn_leak_prechange(BGP_VPN_POLICY_DIR_TOVPN, afi, bgp_get_default(), bgp); if (yes) { bgp->vpn_policy[afi].tovpn_rd = prd; SET_FLAG(bgp->vpn_policy[afi].flags, BGP_VPN_POLICY_TOVPN_RD_SET); } else { UNSET_FLAG(bgp->vpn_policy[afi].flags, BGP_VPN_POLICY_TOVPN_RD_SET); } /* post-change: re-export vpn routes */ vpn_leak_postchange(BGP_VPN_POLICY_DIR_TOVPN, afi, bgp_get_default(), bgp); return CMD_SUCCESS; } ALIAS (af_rd_vpn_export, af_no_rd_vpn_export_cmd, "no rd vpn export", NO_STR "Specify route distinguisher\n" "Between current address-family and vpn\n" "For routes leaked from current address-family to vpn\n") DEFPY (af_label_vpn_export, af_label_vpn_export_cmd, "[no] label vpn export <(0-1048575)$label_val|auto$label_auto>", NO_STR "label value for VRF\n" "Between current address-family and vpn\n" "For routes leaked from current address-family to vpn\n" "Label Value <0-1048575>\n" "Automatically assign a label\n") { VTY_DECLVAR_CONTEXT(bgp, bgp); mpls_label_t label = MPLS_LABEL_NONE; afi_t afi; int idx = 0; int yes = 1; if (argv_find(argv, argc, "no", &idx)) yes = 0; /* If "no ...", squash trailing parameter */ if (!yes) label_auto = NULL; if (yes) { if (!label_auto) label = label_val; /* parser should force unsigned */ } afi = vpn_policy_getafi(vty, bgp, false); if (afi == AFI_MAX) return CMD_WARNING_CONFIG_FAILED; if (label_auto && CHECK_FLAG(bgp->vpn_policy[afi].flags, BGP_VPN_POLICY_TOVPN_LABEL_AUTO)) /* no change */ return CMD_SUCCESS; /* * pre-change: un-export vpn routes (vpn->vrf routes unaffected) */ vpn_leak_prechange(BGP_VPN_POLICY_DIR_TOVPN, afi, bgp_get_default(), bgp); if (!label_auto && CHECK_FLAG(bgp->vpn_policy[afi].flags, BGP_VPN_POLICY_TOVPN_LABEL_AUTO)) { if (bgp->vpn_policy[afi].tovpn_label != MPLS_LABEL_NONE) { /* * label has previously been automatically * assigned by labelpool: release it * * NB if tovpn_label == MPLS_LABEL_NONE it * means the automatic assignment is in flight * and therefore the labelpool callback must * detect that the auto label is not needed. */ bgp_lp_release(LP_TYPE_VRF, &bgp->vpn_policy[afi], bgp->vpn_policy[afi].tovpn_label); } UNSET_FLAG(bgp->vpn_policy[afi].flags, BGP_VPN_POLICY_TOVPN_LABEL_AUTO); } bgp->vpn_policy[afi].tovpn_label = label; if (label_auto) { SET_FLAG(bgp->vpn_policy[afi].flags, BGP_VPN_POLICY_TOVPN_LABEL_AUTO); bgp_lp_get(LP_TYPE_VRF, &bgp->vpn_policy[afi], vpn_leak_label_callback); } /* post-change: re-export vpn routes */ vpn_leak_postchange(BGP_VPN_POLICY_DIR_TOVPN, afi, bgp_get_default(), bgp); return CMD_SUCCESS; } ALIAS (af_label_vpn_export, af_no_label_vpn_export_cmd, "no label vpn export", NO_STR "label value for VRF\n" "Between current address-family and vpn\n" "For routes leaked from current address-family to vpn\n") DEFPY (af_nexthop_vpn_export, af_nexthop_vpn_export_cmd, "[no] nexthop vpn export $nexthop_str", NO_STR "Specify next hop to use for VRF advertised prefixes\n" "Between current address-family and vpn\n" "For routes leaked from current address-family to vpn\n" "IPv4 prefix\n" "IPv6 prefix\n") { VTY_DECLVAR_CONTEXT(bgp, bgp); afi_t afi; struct prefix p; int idx = 0; int yes = 1; if (argv_find(argv, argc, "no", &idx)) yes = 0; if (yes) { if (!sockunion2hostprefix(nexthop_str, &p)) return CMD_WARNING_CONFIG_FAILED; } afi = vpn_policy_getafi(vty, bgp, false); if (afi == AFI_MAX) return CMD_WARNING_CONFIG_FAILED; /* * pre-change: un-export vpn routes (vpn->vrf routes unaffected) */ vpn_leak_prechange(BGP_VPN_POLICY_DIR_TOVPN, afi, bgp_get_default(), bgp); if (yes) { bgp->vpn_policy[afi].tovpn_nexthop = p; SET_FLAG(bgp->vpn_policy[afi].flags, BGP_VPN_POLICY_TOVPN_NEXTHOP_SET); } else { UNSET_FLAG(bgp->vpn_policy[afi].flags, BGP_VPN_POLICY_TOVPN_NEXTHOP_SET); } /* post-change: re-export vpn routes */ vpn_leak_postchange(BGP_VPN_POLICY_DIR_TOVPN, afi, bgp_get_default(), bgp); return CMD_SUCCESS; } ALIAS (af_nexthop_vpn_export, af_no_nexthop_vpn_export_cmd, "no nexthop vpn export", NO_STR "Specify next hop to use for VRF advertised prefixes\n" "Between current address-family and vpn\n" "For routes leaked from current address-family to vpn\n") static int vpn_policy_getdirs(struct vty *vty, const char *dstr, int *dodir) { if (!strcmp(dstr, "import")) { dodir[BGP_VPN_POLICY_DIR_FROMVPN] = 1; } else if (!strcmp(dstr, "export")) { dodir[BGP_VPN_POLICY_DIR_TOVPN] = 1; } else if (!strcmp(dstr, "both")) { dodir[BGP_VPN_POLICY_DIR_FROMVPN] = 1; dodir[BGP_VPN_POLICY_DIR_TOVPN] = 1; } else { vty_out(vty, "%% direction parse error\n"); return CMD_WARNING_CONFIG_FAILED; } return CMD_SUCCESS; } DEFPY (af_rt_vpn_imexport, af_rt_vpn_imexport_cmd, "[no] vpn $direction_str RTLIST...", NO_STR "Specify route target list\n" "Specify route target list\n" "Between current address-family and vpn\n" "For routes leaked from vpn to current address-family: match any\n" "For routes leaked from current address-family to vpn: set\n" "both import: match any and export: set\n" "Space separated route target list (A.B.C.D:MN|EF:OPQR|GHJK:MN)\n") { VTY_DECLVAR_CONTEXT(bgp, bgp); int ret; struct ecommunity *ecom = NULL; int dodir[BGP_VPN_POLICY_DIR_MAX] = {0}; vpn_policy_direction_t dir; afi_t afi; int idx = 0; int yes = 1; if (argv_find(argv, argc, "no", &idx)) yes = 0; afi = vpn_policy_getafi(vty, bgp, false); if (afi == AFI_MAX) return CMD_WARNING_CONFIG_FAILED; ret = vpn_policy_getdirs(vty, direction_str, dodir); if (ret != CMD_SUCCESS) return ret; if (yes) { if (!argv_find(argv, argc, "RTLIST", &idx)) { vty_out(vty, "%% Missing RTLIST\n"); return CMD_WARNING_CONFIG_FAILED; } ret = set_ecom_list(vty, argc - idx, argv + idx, &ecom); if (ret != CMD_SUCCESS) { return ret; } } for (dir = 0; dir < BGP_VPN_POLICY_DIR_MAX; ++dir) { if (!dodir[dir]) continue; vpn_leak_prechange(dir, afi, bgp_get_default(), bgp); if (yes) { if (bgp->vpn_policy[afi].rtlist[dir]) ecommunity_free( &bgp->vpn_policy[afi].rtlist[dir]); bgp->vpn_policy[afi].rtlist[dir] = ecommunity_dup(ecom); } else { if (bgp->vpn_policy[afi].rtlist[dir]) ecommunity_free( &bgp->vpn_policy[afi].rtlist[dir]); bgp->vpn_policy[afi].rtlist[dir] = NULL; } vpn_leak_postchange(dir, afi, bgp_get_default(), bgp); } if (ecom) ecommunity_free(&ecom); return CMD_SUCCESS; } ALIAS (af_rt_vpn_imexport, af_no_rt_vpn_imexport_cmd, "no vpn $direction_str", NO_STR "Specify route target list\n" "Specify route target list\n" "Between current address-family and vpn\n" "For routes leaked from vpn to current address-family\n" "For routes leaked from current address-family to vpn\n" "both import and export\n") DEFPY (af_route_map_vpn_imexport, af_route_map_vpn_imexport_cmd, /* future: "route-map RMAP" */ "[no] route-map vpn $direction_str RMAP$rmap_str", NO_STR "Specify route map\n" "Between current address-family and vpn\n" "For routes leaked from vpn to current address-family\n" "For routes leaked from current address-family to vpn\n" "name of route-map\n") { VTY_DECLVAR_CONTEXT(bgp, bgp); int ret; int dodir[BGP_VPN_POLICY_DIR_MAX] = {0}; vpn_policy_direction_t dir; afi_t afi; int idx = 0; int yes = 1; if (argv_find(argv, argc, "no", &idx)) yes = 0; afi = vpn_policy_getafi(vty, bgp, false); if (afi == AFI_MAX) return CMD_WARNING_CONFIG_FAILED; ret = vpn_policy_getdirs(vty, direction_str, dodir); if (ret != CMD_SUCCESS) return ret; for (dir = 0; dir < BGP_VPN_POLICY_DIR_MAX; ++dir) { if (!dodir[dir]) continue; vpn_leak_prechange(dir, afi, bgp_get_default(), bgp); if (yes) { if (bgp->vpn_policy[afi].rmap_name[dir]) XFREE(MTYPE_ROUTE_MAP_NAME, bgp->vpn_policy[afi].rmap_name[dir]); bgp->vpn_policy[afi].rmap_name[dir] = XSTRDUP( MTYPE_ROUTE_MAP_NAME, rmap_str); bgp->vpn_policy[afi].rmap[dir] = route_map_lookup_warn_noexist(vty, rmap_str); if (!bgp->vpn_policy[afi].rmap[dir]) return CMD_SUCCESS; } else { if (bgp->vpn_policy[afi].rmap_name[dir]) XFREE(MTYPE_ROUTE_MAP_NAME, bgp->vpn_policy[afi].rmap_name[dir]); bgp->vpn_policy[afi].rmap_name[dir] = NULL; bgp->vpn_policy[afi].rmap[dir] = NULL; } vpn_leak_postchange(dir, afi, bgp_get_default(), bgp); } return CMD_SUCCESS; } ALIAS (af_route_map_vpn_imexport, af_no_route_map_vpn_imexport_cmd, "no route-map vpn $direction_str", NO_STR "Specify route map\n" "Between current address-family and vpn\n" "For routes leaked from vpn to current address-family\n" "For routes leaked from current address-family to vpn\n") DEFPY(af_import_vrf_route_map, af_import_vrf_route_map_cmd, "[no] import vrf route-map RMAP$rmap_str", NO_STR "Import routes from another VRF\n" "Vrf routes being filtered\n" "Specify route map\n" "name of route-map\n") { VTY_DECLVAR_CONTEXT(bgp, bgp); vpn_policy_direction_t dir = BGP_VPN_POLICY_DIR_FROMVPN; afi_t afi; int idx = 0; int yes = 1; struct bgp *bgp_default; if (argv_find(argv, argc, "no", &idx)) yes = 0; afi = vpn_policy_getafi(vty, bgp, true); if (afi == AFI_MAX) return CMD_WARNING_CONFIG_FAILED; bgp_default = bgp_get_default(); if (!bgp_default) { int32_t ret; as_t as = bgp->as; /* Auto-create assuming the same AS */ ret = bgp_get(&bgp_default, &as, NULL, BGP_INSTANCE_TYPE_DEFAULT); if (ret) { vty_out(vty, "VRF default is not configured as a bgp instance\n"); return CMD_WARNING; } } vpn_leak_prechange(dir, afi, bgp_get_default(), bgp); if (yes) { if (bgp->vpn_policy[afi].rmap_name[dir]) XFREE(MTYPE_ROUTE_MAP_NAME, bgp->vpn_policy[afi].rmap_name[dir]); bgp->vpn_policy[afi].rmap_name[dir] = XSTRDUP(MTYPE_ROUTE_MAP_NAME, rmap_str); bgp->vpn_policy[afi].rmap[dir] = route_map_lookup_warn_noexist(vty, rmap_str); if (!bgp->vpn_policy[afi].rmap[dir]) return CMD_SUCCESS; } else { if (bgp->vpn_policy[afi].rmap_name[dir]) XFREE(MTYPE_ROUTE_MAP_NAME, bgp->vpn_policy[afi].rmap_name[dir]); bgp->vpn_policy[afi].rmap_name[dir] = NULL; bgp->vpn_policy[afi].rmap[dir] = NULL; } vpn_leak_postchange(dir, afi, bgp_get_default(), bgp); return CMD_SUCCESS; } ALIAS(af_import_vrf_route_map, af_no_import_vrf_route_map_cmd, "no import vrf route-map", NO_STR "Import routes from another VRF\n" "Vrf routes being filtered\n" "Specify route map\n") DEFPY(bgp_imexport_vrf, bgp_imexport_vrf_cmd, "[no] import vrf VIEWVRFNAME$import_name", NO_STR "Import routes from another VRF\n" "VRF to import from\n" "The name of the VRF\n") { VTY_DECLVAR_CONTEXT(bgp, bgp); struct listnode *node; struct bgp *vrf_bgp, *bgp_default; int32_t ret = 0; as_t as = bgp->as; bool remove = false; int32_t idx = 0; char *vname; enum bgp_instance_type bgp_type = BGP_INSTANCE_TYPE_VRF; safi_t safi; afi_t afi; if (import_name == NULL) { vty_out(vty, "%% Missing import name\n"); return CMD_WARNING; } if (argv_find(argv, argc, "no", &idx)) remove = true; afi = vpn_policy_getafi(vty, bgp, true); if (afi == AFI_MAX) return CMD_WARNING_CONFIG_FAILED; safi = bgp_node_safi(vty); if (((BGP_INSTANCE_TYPE_DEFAULT == bgp->inst_type) && (strcmp(import_name, VRF_DEFAULT_NAME) == 0)) || (bgp->name && (strcmp(import_name, bgp->name) == 0))) { vty_out(vty, "%% Cannot %s vrf %s into itself\n", remove ? "unimport" : "import", import_name); return CMD_WARNING; } bgp_default = bgp_get_default(); if (!bgp_default) { /* Auto-create assuming the same AS */ ret = bgp_get(&bgp_default, &as, NULL, BGP_INSTANCE_TYPE_DEFAULT); if (ret) { vty_out(vty, "VRF default is not configured as a bgp instance\n"); return CMD_WARNING; } } vrf_bgp = bgp_lookup_by_name(import_name); if (!vrf_bgp) { if (strcmp(import_name, VRF_DEFAULT_NAME) == 0) vrf_bgp = bgp_default; else /* Auto-create assuming the same AS */ ret = bgp_get(&vrf_bgp, &as, import_name, bgp_type); if (ret) { vty_out(vty, "VRF %s is not configured as a bgp instance\n", import_name); return CMD_WARNING; } } if (remove) { vrf_unimport_from_vrf(bgp, vrf_bgp, afi, safi); } else { /* Already importing from "import_vrf"? */ for (ALL_LIST_ELEMENTS_RO(bgp->vpn_policy[afi].import_vrf, node, vname)) { if (strcmp(vname, import_name) == 0) return CMD_WARNING; } vrf_import_from_vrf(bgp, vrf_bgp, afi, safi); } return CMD_SUCCESS; } /* This command is valid only in a bgp vrf instance or the default instance */ DEFPY (bgp_imexport_vpn, bgp_imexport_vpn_cmd, "[no] $direction_str vpn", NO_STR "Import routes to this address-family\n" "Export routes from this address-family\n" "to/from default instance VPN RIB\n") { VTY_DECLVAR_CONTEXT(bgp, bgp); int previous_state; afi_t afi; safi_t safi; int idx = 0; int yes = 1; int flag; vpn_policy_direction_t dir; if (argv_find(argv, argc, "no", &idx)) yes = 0; if (BGP_INSTANCE_TYPE_VRF != bgp->inst_type && BGP_INSTANCE_TYPE_DEFAULT != bgp->inst_type) { vty_out(vty, "%% import|export vpn valid only for bgp vrf or default instance\n"); return CMD_WARNING_CONFIG_FAILED; } afi = bgp_node_afi(vty); safi = bgp_node_safi(vty); if ((SAFI_UNICAST != safi) || ((AFI_IP != afi) && (AFI_IP6 != afi))) { vty_out(vty, "%% import|export vpn valid only for unicast ipv4|ipv6\n"); return CMD_WARNING_CONFIG_FAILED; } if (!strcmp(direction_str, "import")) { flag = BGP_CONFIG_MPLSVPN_TO_VRF_IMPORT; dir = BGP_VPN_POLICY_DIR_FROMVPN; } else if (!strcmp(direction_str, "export")) { flag = BGP_CONFIG_VRF_TO_MPLSVPN_EXPORT; dir = BGP_VPN_POLICY_DIR_TOVPN; } else { vty_out(vty, "%% unknown direction %s\n", direction_str); return CMD_WARNING_CONFIG_FAILED; } previous_state = CHECK_FLAG(bgp->af_flags[afi][safi], flag); if (yes) { SET_FLAG(bgp->af_flags[afi][safi], flag); if (!previous_state) { /* trigger export current vrf */ vpn_leak_postchange(dir, afi, bgp_get_default(), bgp); } } else { if (previous_state) { /* trigger un-export current vrf */ vpn_leak_prechange(dir, afi, bgp_get_default(), bgp); } UNSET_FLAG(bgp->af_flags[afi][safi], flag); } return CMD_SUCCESS; } DEFPY (af_routetarget_import, af_routetarget_import_cmd, "[no] redirect import RTLIST...", NO_STR "Specify route target list\n" "Specify route target list\n" "Flow-spec redirect type route target\n" "Import routes to this address-family\n" "Space separated route target list (A.B.C.D:MN|EF:OPQR|GHJK:MN)\n") { VTY_DECLVAR_CONTEXT(bgp, bgp); int ret; struct ecommunity *ecom = NULL; afi_t afi; int idx = 0; int yes = 1; if (argv_find(argv, argc, "no", &idx)) yes = 0; afi = vpn_policy_getafi(vty, bgp, false); if (afi == AFI_MAX) return CMD_WARNING_CONFIG_FAILED; if (yes) { if (!argv_find(argv, argc, "RTLIST", &idx)) { vty_out(vty, "%% Missing RTLIST\n"); return CMD_WARNING_CONFIG_FAILED; } ret = set_ecom_list(vty, argc - idx, argv + idx, &ecom); if (ret != CMD_SUCCESS) return ret; } if (yes) { if (bgp->vpn_policy[afi].import_redirect_rtlist) ecommunity_free(&bgp->vpn_policy[afi] .import_redirect_rtlist); bgp->vpn_policy[afi].import_redirect_rtlist = ecommunity_dup(ecom); } else { if (bgp->vpn_policy[afi].import_redirect_rtlist) ecommunity_free(&bgp->vpn_policy[afi] .import_redirect_rtlist); bgp->vpn_policy[afi].import_redirect_rtlist = NULL; } if (ecom) ecommunity_free(&ecom); return CMD_SUCCESS; } DEFUN_NOSH (address_family_ipv4_safi, address_family_ipv4_safi_cmd, "address-family ipv4 []", "Enter Address Family command mode\n" "Address Family\n" BGP_SAFI_WITH_LABEL_HELP_STR) { if (argc == 3) { VTY_DECLVAR_CONTEXT(bgp, bgp); safi_t safi = bgp_vty_safi_from_str(argv[2]->text); if (bgp->inst_type != BGP_INSTANCE_TYPE_DEFAULT && safi != SAFI_UNICAST && safi != SAFI_MULTICAST && safi != SAFI_EVPN) { vty_out(vty, "Only Unicast/Multicast/EVPN SAFIs supported in non-core instances.\n"); return CMD_WARNING_CONFIG_FAILED; } vty->node = bgp_node_type(AFI_IP, safi); } else vty->node = BGP_IPV4_NODE; return CMD_SUCCESS; } DEFUN_NOSH (address_family_ipv6_safi, address_family_ipv6_safi_cmd, "address-family ipv6 []", "Enter Address Family command mode\n" "Address Family\n" BGP_SAFI_WITH_LABEL_HELP_STR) { if (argc == 3) { VTY_DECLVAR_CONTEXT(bgp, bgp); safi_t safi = bgp_vty_safi_from_str(argv[2]->text); if (bgp->inst_type != BGP_INSTANCE_TYPE_DEFAULT && safi != SAFI_UNICAST && safi != SAFI_MULTICAST && safi != SAFI_EVPN) { vty_out(vty, "Only Unicast/Multicast/EVPN SAFIs supported in non-core instances.\n"); return CMD_WARNING_CONFIG_FAILED; } vty->node = bgp_node_type(AFI_IP6, safi); } else vty->node = BGP_IPV6_NODE; return CMD_SUCCESS; } #ifdef KEEP_OLD_VPN_COMMANDS DEFUN_NOSH (address_family_vpnv4, address_family_vpnv4_cmd, "address-family vpnv4 [unicast]", "Enter Address Family command mode\n" "Address Family\n" "Address Family modifier\n") { vty->node = BGP_VPNV4_NODE; return CMD_SUCCESS; } DEFUN_NOSH (address_family_vpnv6, address_family_vpnv6_cmd, "address-family vpnv6 [unicast]", "Enter Address Family command mode\n" "Address Family\n" "Address Family modifier\n") { vty->node = BGP_VPNV6_NODE; return CMD_SUCCESS; } #endif /* KEEP_OLD_VPN_COMMANDS */ DEFUN_NOSH (address_family_evpn, address_family_evpn_cmd, "address-family l2vpn evpn", "Enter Address Family command mode\n" "Address Family\n" "Address Family modifier\n") { VTY_DECLVAR_CONTEXT(bgp, bgp); vty->node = BGP_EVPN_NODE; return CMD_SUCCESS; } DEFUN_NOSH (exit_address_family, exit_address_family_cmd, "exit-address-family", "Exit from Address Family configuration mode\n") { if (vty->node == BGP_IPV4_NODE || vty->node == BGP_IPV4M_NODE || vty->node == BGP_IPV4L_NODE || vty->node == BGP_VPNV4_NODE || vty->node == BGP_IPV6_NODE || vty->node == BGP_IPV6M_NODE || vty->node == BGP_IPV6L_NODE || vty->node == BGP_VPNV6_NODE || vty->node == BGP_EVPN_NODE || vty->node == BGP_FLOWSPECV4_NODE || vty->node == BGP_FLOWSPECV6_NODE) vty->node = BGP_NODE; return CMD_SUCCESS; } /* Recalculate bestpath and re-advertise a prefix */ static int bgp_clear_prefix(struct vty *vty, const char *view_name, const char *ip_str, afi_t afi, safi_t safi, struct prefix_rd *prd) { int ret; struct prefix match; struct bgp_node *rn; struct bgp_node *rm; struct bgp *bgp; struct bgp_table *table; struct bgp_table *rib; /* BGP structure lookup. */ if (view_name) { bgp = bgp_lookup_by_name(view_name); if (bgp == NULL) { vty_out(vty, "%% Can't find BGP instance %s\n", view_name); return CMD_WARNING; } } else { bgp = bgp_get_default(); if (bgp == NULL) { vty_out(vty, "%% No BGP process is configured\n"); return CMD_WARNING; } } /* Check IP address argument. */ ret = str2prefix(ip_str, &match); if (!ret) { vty_out(vty, "%% address is malformed\n"); return CMD_WARNING; } match.family = afi2family(afi); rib = bgp->rib[afi][safi]; if (safi == SAFI_MPLS_VPN) { for (rn = bgp_table_top(rib); rn; rn = bgp_route_next(rn)) { if (prd && memcmp(rn->p.u.val, prd->val, 8) != 0) continue; table = bgp_node_get_bgp_table_info(rn); if (table != NULL) { if ((rm = bgp_node_match(table, &match)) != NULL) { if (rm->p.prefixlen == match.prefixlen) { SET_FLAG(rm->flags, BGP_NODE_USER_CLEAR); bgp_process(bgp, rm, afi, safi); } bgp_unlock_node(rm); } } } } else { if ((rn = bgp_node_match(rib, &match)) != NULL) { if (rn->p.prefixlen == match.prefixlen) { SET_FLAG(rn->flags, BGP_NODE_USER_CLEAR); bgp_process(bgp, rn, afi, safi); } bgp_unlock_node(rn); } } return CMD_SUCCESS; } /* one clear bgp command to rule them all */ DEFUN (clear_ip_bgp_all, clear_ip_bgp_all_cmd, "clear [ip] bgp [ VIEWVRFNAME] [ []] <*|A.B.C.D$neighbor|X:X::X:X$neighbor|WORD$neighbor|(1-4294967295)|external|peer-group PGNAME> []|in [prefix-filter]|out>]", CLEAR_STR IP_STR BGP_STR BGP_INSTANCE_HELP_STR BGP_AFI_HELP_STR "Address Family\n" BGP_SAFI_WITH_LABEL_HELP_STR "Address Family modifier\n" "Clear all peers\n" "BGP IPv4 neighbor to clear\n" "BGP IPv6 neighbor to clear\n" "BGP neighbor on interface to clear\n" "Clear peers with the AS number\n" "Clear all external peers\n" "Clear all members of peer-group\n" "BGP peer-group name\n" BGP_SOFT_STR BGP_SOFT_IN_STR BGP_SOFT_OUT_STR BGP_SOFT_IN_STR "Push out prefix-list ORF and do inbound soft reconfig\n" BGP_SOFT_OUT_STR) { char *vrf = NULL; afi_t afi = AFI_UNSPEC; safi_t safi = SAFI_UNSPEC; enum clear_sort clr_sort = clear_peer; enum bgp_clear_type clr_type; char *clr_arg = NULL; int idx = 0; /* clear [ip] bgp */ if (argv_find(argv, argc, "ip", &idx)) afi = AFI_IP; /* [ VIEWVRFNAME] */ if (argv_find(argv, argc, "vrf", &idx)) { vrf = argv[idx + 1]->arg; idx += 2; if (vrf && strmatch(vrf, VRF_DEFAULT_NAME)) vrf = NULL; } else if (argv_find(argv, argc, "view", &idx)) { /* [ VIEWVRFNAME] */ vrf = argv[idx + 1]->arg; idx += 2; } /* ["BGP_AFI_CMD_STR" ["BGP_SAFI_CMD_STR"]] */ if (argv_find_and_parse_afi(argv, argc, &idx, &afi)) argv_find_and_parse_safi(argv, argc, &idx, &safi); /* <*|A.B.C.D|X:X::X:X|WORD|(1-4294967295)|external|peer-group PGNAME> */ if (argv_find(argv, argc, "*", &idx)) { clr_sort = clear_all; } else if (argv_find(argv, argc, "A.B.C.D", &idx)) { clr_sort = clear_peer; clr_arg = argv[idx]->arg; } else if (argv_find(argv, argc, "X:X::X:X", &idx)) { clr_sort = clear_peer; clr_arg = argv[idx]->arg; } else if (argv_find(argv, argc, "peer-group", &idx)) { clr_sort = clear_group; idx++; clr_arg = argv[idx]->arg; } else if (argv_find(argv, argc, "PGNAME", &idx)) { clr_sort = clear_peer; clr_arg = argv[idx]->arg; } else if (argv_find(argv, argc, "WORD", &idx)) { clr_sort = clear_peer; clr_arg = argv[idx]->arg; } else if (argv_find(argv, argc, "(1-4294967295)", &idx)) { clr_sort = clear_as; clr_arg = argv[idx]->arg; } else if (argv_find(argv, argc, "external", &idx)) { clr_sort = clear_external; } /* []|in [prefix-filter]|out>] */ if (argv_find(argv, argc, "soft", &idx)) { if (argv_find(argv, argc, "in", &idx) || argv_find(argv, argc, "out", &idx)) clr_type = strmatch(argv[idx]->text, "in") ? BGP_CLEAR_SOFT_IN : BGP_CLEAR_SOFT_OUT; else clr_type = BGP_CLEAR_SOFT_BOTH; } else if (argv_find(argv, argc, "in", &idx)) { clr_type = argv_find(argv, argc, "prefix-filter", &idx) ? BGP_CLEAR_SOFT_IN_ORF_PREFIX : BGP_CLEAR_SOFT_IN; } else if (argv_find(argv, argc, "out", &idx)) { clr_type = BGP_CLEAR_SOFT_OUT; } else clr_type = BGP_CLEAR_SOFT_NONE; return bgp_clear_vty(vty, vrf, afi, safi, clr_sort, clr_type, clr_arg); } DEFUN (clear_ip_bgp_prefix, clear_ip_bgp_prefix_cmd, "clear [ip] bgp [ VIEWVRFNAME] prefix A.B.C.D/M", CLEAR_STR IP_STR BGP_STR BGP_INSTANCE_HELP_STR "Clear bestpath and re-advertise\n" "IPv4 prefix\n") { char *vrf = NULL; char *prefix = NULL; int idx = 0; /* [ VIEWVRFNAME] */ if (argv_find(argv, argc, "vrf", &idx)) { vrf = argv[idx + 1]->arg; idx += 2; if (vrf && strmatch(vrf, VRF_DEFAULT_NAME)) vrf = NULL; } else if (argv_find(argv, argc, "view", &idx)) { /* [ VIEWVRFNAME] */ vrf = argv[idx + 1]->arg; idx += 2; } prefix = argv[argc - 1]->arg; return bgp_clear_prefix(vty, vrf, prefix, AFI_IP, SAFI_UNICAST, NULL); } DEFUN (clear_bgp_ipv6_safi_prefix, clear_bgp_ipv6_safi_prefix_cmd, "clear [ip] bgp ipv6 "BGP_SAFI_CMD_STR" prefix X:X::X:X/M", CLEAR_STR IP_STR BGP_STR "Address Family\n" BGP_SAFI_HELP_STR "Clear bestpath and re-advertise\n" "IPv6 prefix\n") { int idx_safi = 0; int idx_ipv6_prefix = 0; safi_t safi = SAFI_UNICAST; char *prefix = argv_find(argv, argc, "X:X::X:X/M", &idx_ipv6_prefix) ? argv[idx_ipv6_prefix]->arg : NULL; argv_find_and_parse_safi(argv, argc, &idx_safi, &safi); return bgp_clear_prefix( vty, NULL, prefix, AFI_IP6, safi, NULL); } DEFUN (clear_bgp_instance_ipv6_safi_prefix, clear_bgp_instance_ipv6_safi_prefix_cmd, "clear [ip] bgp VIEWVRFNAME ipv6 "BGP_SAFI_CMD_STR" prefix X:X::X:X/M", CLEAR_STR IP_STR BGP_STR BGP_INSTANCE_HELP_STR "Address Family\n" BGP_SAFI_HELP_STR "Clear bestpath and re-advertise\n" "IPv6 prefix\n") { int idx_safi = 0; int idx_vrfview = 0; int idx_ipv6_prefix = 0; safi_t safi = SAFI_UNICAST; char *prefix = argv_find(argv, argc, "X:X::X:X/M", &idx_ipv6_prefix) ? argv[idx_ipv6_prefix]->arg : NULL; char *vrfview = NULL; /* [ VIEWVRFNAME] */ if (argv_find(argv, argc, "vrf", &idx_vrfview)) { vrfview = argv[idx_vrfview + 1]->arg; if (vrfview && strmatch(vrfview, VRF_DEFAULT_NAME)) vrfview = NULL; } else if (argv_find(argv, argc, "view", &idx_vrfview)) { /* [ VIEWVRFNAME] */ vrfview = argv[idx_vrfview + 1]->arg; } argv_find_and_parse_safi(argv, argc, &idx_safi, &safi); return bgp_clear_prefix( vty, vrfview, prefix, AFI_IP6, safi, NULL); } DEFUN (show_bgp_views, show_bgp_views_cmd, "show [ip] bgp views", SHOW_STR IP_STR BGP_STR "Show the defined BGP views\n") { struct list *inst = bm->bgp; struct listnode *node; struct bgp *bgp; vty_out(vty, "Defined BGP views:\n"); for (ALL_LIST_ELEMENTS_RO(inst, node, bgp)) { /* Skip VRFs. */ if (bgp->inst_type == BGP_INSTANCE_TYPE_VRF) continue; vty_out(vty, "\t%s (AS%u)\n", bgp->name ? bgp->name : "(null)", bgp->as); } return CMD_SUCCESS; } DEFUN (show_bgp_vrfs, show_bgp_vrfs_cmd, "show [ip] bgp vrfs [json]", SHOW_STR IP_STR BGP_STR "Show BGP VRFs\n" JSON_STR) { char buf[ETHER_ADDR_STRLEN]; struct list *inst = bm->bgp; struct listnode *node; struct bgp *bgp; bool uj = use_json(argc, argv); json_object *json = NULL; json_object *json_vrfs = NULL; int count = 0; if (uj) { json = json_object_new_object(); json_vrfs = json_object_new_object(); } for (ALL_LIST_ELEMENTS_RO(inst, node, bgp)) { const char *name, *type; struct peer *peer; struct listnode *node2, *nnode2; int peers_cfg, peers_estb; json_object *json_vrf = NULL; /* Skip Views. */ if (bgp->inst_type == BGP_INSTANCE_TYPE_VIEW) continue; count++; if (!uj && count == 1) { vty_out(vty, "%4s %-5s %-16s %9s %10s %-37s\n", "Type", "Id", "routerId", "#PeersVfg", "#PeersEstb", "Name"); vty_out(vty, "%11s %-16s %-21s %-6s\n", " ", "L3-VNI", "RouterMAC", "Interface"); } peers_cfg = peers_estb = 0; if (uj) json_vrf = json_object_new_object(); for (ALL_LIST_ELEMENTS(bgp->peer, node2, nnode2, peer)) { if (!CHECK_FLAG(peer->flags, PEER_FLAG_CONFIG_NODE)) continue; peers_cfg++; if (peer->status == Established) peers_estb++; } if (bgp->inst_type == BGP_INSTANCE_TYPE_DEFAULT) { name = VRF_DEFAULT_NAME; type = "DFLT"; } else { name = bgp->name; type = "VRF"; } if (uj) { int64_t vrf_id_ui = (bgp->vrf_id == VRF_UNKNOWN) ? -1 : (int64_t)bgp->vrf_id; json_object_string_add(json_vrf, "type", type); json_object_int_add(json_vrf, "vrfId", vrf_id_ui); json_object_string_add(json_vrf, "routerId", inet_ntoa(bgp->router_id)); json_object_int_add(json_vrf, "numConfiguredPeers", peers_cfg); json_object_int_add(json_vrf, "numEstablishedPeers", peers_estb); json_object_int_add(json_vrf, "l3vni", bgp->l3vni); json_object_string_add( json_vrf, "rmac", prefix_mac2str(&bgp->rmac, buf, sizeof(buf))); json_object_string_add(json_vrf, "interface", ifindex2ifname(bgp->l3vni_svi_ifindex, bgp->vrf_id)); json_object_object_add(json_vrfs, name, json_vrf); } else { vty_out(vty, "%4s %-5d %-16s %-9u %-10u %-37s\n", type, bgp->vrf_id == VRF_UNKNOWN ? -1 : (int)bgp->vrf_id, inet_ntoa(bgp->router_id), peers_cfg, peers_estb, name); vty_out(vty,"%11s %-16u %-21s %-20s\n", " ", bgp->l3vni, prefix_mac2str(&bgp->rmac, buf, sizeof(buf)), ifindex2ifname(bgp->l3vni_svi_ifindex, bgp->vrf_id)); } } if (uj) { json_object_object_add(json, "vrfs", json_vrfs); json_object_int_add(json, "totalVrfs", count); vty_out(vty, "%s\n", json_object_to_json_string_ext( json, JSON_C_TO_STRING_PRETTY)); json_object_free(json); } else { if (count) vty_out(vty, "\nTotal number of VRFs (including default): %d\n", count); } return CMD_SUCCESS; } DEFUN (show_bgp_mac_hash, show_bgp_mac_hash_cmd, "show bgp mac hash", SHOW_STR BGP_STR "Mac Address\n" "Mac Address database\n") { bgp_mac_dump_table(vty); return CMD_SUCCESS; } static void show_tip_entry(struct hash_bucket *bucket, void *args) { struct vty *vty = (struct vty *)args; struct tip_addr *tip = (struct tip_addr *)bucket->data; vty_out(vty, "addr: %s, count: %d\n", inet_ntoa(tip->addr), tip->refcnt); } static void bgp_show_martian_nexthops(struct vty *vty, struct bgp *bgp) { vty_out(vty, "self nexthop database:\n"); bgp_nexthop_show_address_hash(vty, bgp); vty_out(vty, "Tunnel-ip database:\n"); hash_iterate(bgp->tip_hash, (void (*)(struct hash_bucket *, void *))show_tip_entry, vty); } DEFUN(show_bgp_martian_nexthop_db, show_bgp_martian_nexthop_db_cmd, "show bgp [ VIEWVRFNAME] martian next-hop", SHOW_STR BGP_STR BGP_INSTANCE_HELP_STR "martian next-hops\n" "martian next-hop database\n") { struct bgp *bgp = NULL; int idx = 0; char *name = NULL; /* [ VIEWVRFNAME] */ if (argv_find(argv, argc, "vrf", &idx)) { name = argv[idx + 1]->arg; if (name && strmatch(name, VRF_DEFAULT_NAME)) name = NULL; } else if (argv_find(argv, argc, "view", &idx)) /* [ VIEWVRFNAME] */ name = argv[idx + 1]->arg; if (name) bgp = bgp_lookup_by_name(name); else bgp = bgp_get_default(); if (!bgp) { vty_out(vty, "%% No BGP process is configured\n"); return CMD_WARNING; } bgp_show_martian_nexthops(vty, bgp); return CMD_SUCCESS; } DEFUN (show_bgp_memory, show_bgp_memory_cmd, "show [ip] bgp memory", SHOW_STR IP_STR BGP_STR "Global BGP memory statistics\n") { char memstrbuf[MTYPE_MEMSTR_LEN]; unsigned long count; /* RIB related usage stats */ count = mtype_stats_alloc(MTYPE_BGP_NODE); vty_out(vty, "%ld RIB nodes, using %s of memory\n", count, mtype_memstr(memstrbuf, sizeof(memstrbuf), count * sizeof(struct bgp_node))); count = mtype_stats_alloc(MTYPE_BGP_ROUTE); vty_out(vty, "%ld BGP routes, using %s of memory\n", count, mtype_memstr(memstrbuf, sizeof(memstrbuf), count * sizeof(struct bgp_path_info))); if ((count = mtype_stats_alloc(MTYPE_BGP_ROUTE_EXTRA))) vty_out(vty, "%ld BGP route ancillaries, using %s of memory\n", count, mtype_memstr( memstrbuf, sizeof(memstrbuf), count * sizeof(struct bgp_path_info_extra))); if ((count = mtype_stats_alloc(MTYPE_BGP_STATIC))) vty_out(vty, "%ld Static routes, using %s of memory\n", count, mtype_memstr(memstrbuf, sizeof(memstrbuf), count * sizeof(struct bgp_static))); if ((count = mtype_stats_alloc(MTYPE_BGP_PACKET))) vty_out(vty, "%ld Packets, using %s of memory\n", count, mtype_memstr(memstrbuf, sizeof(memstrbuf), count * sizeof(struct bpacket))); /* Adj-In/Out */ if ((count = mtype_stats_alloc(MTYPE_BGP_ADJ_IN))) vty_out(vty, "%ld Adj-In entries, using %s of memory\n", count, mtype_memstr(memstrbuf, sizeof(memstrbuf), count * sizeof(struct bgp_adj_in))); if ((count = mtype_stats_alloc(MTYPE_BGP_ADJ_OUT))) vty_out(vty, "%ld Adj-Out entries, using %s of memory\n", count, mtype_memstr(memstrbuf, sizeof(memstrbuf), count * sizeof(struct bgp_adj_out))); if ((count = mtype_stats_alloc(MTYPE_BGP_NEXTHOP_CACHE))) vty_out(vty, "%ld Nexthop cache entries, using %s of memory\n", count, mtype_memstr(memstrbuf, sizeof(memstrbuf), count * sizeof(struct bgp_nexthop_cache))); if ((count = mtype_stats_alloc(MTYPE_BGP_DAMP_INFO))) vty_out(vty, "%ld Dampening entries, using %s of memory\n", count, mtype_memstr(memstrbuf, sizeof(memstrbuf), count * sizeof(struct bgp_damp_info))); /* Attributes */ count = attr_count(); vty_out(vty, "%ld BGP attributes, using %s of memory\n", count, mtype_memstr(memstrbuf, sizeof(memstrbuf), count * sizeof(struct attr))); if ((count = attr_unknown_count())) vty_out(vty, "%ld unknown attributes\n", count); /* AS_PATH attributes */ count = aspath_count(); vty_out(vty, "%ld BGP AS-PATH entries, using %s of memory\n", count, mtype_memstr(memstrbuf, sizeof(memstrbuf), count * sizeof(struct aspath))); count = mtype_stats_alloc(MTYPE_AS_SEG); vty_out(vty, "%ld BGP AS-PATH segments, using %s of memory\n", count, mtype_memstr(memstrbuf, sizeof(memstrbuf), count * sizeof(struct assegment))); /* Other attributes */ if ((count = community_count())) vty_out(vty, "%ld BGP community entries, using %s of memory\n", count, mtype_memstr(memstrbuf, sizeof(memstrbuf), count * sizeof(struct community))); if ((count = mtype_stats_alloc(MTYPE_ECOMMUNITY))) vty_out(vty, "%ld BGP community entries, using %s of memory\n", count, mtype_memstr(memstrbuf, sizeof(memstrbuf), count * sizeof(struct ecommunity))); if ((count = mtype_stats_alloc(MTYPE_LCOMMUNITY))) vty_out(vty, "%ld BGP large-community entries, using %s of memory\n", count, mtype_memstr(memstrbuf, sizeof(memstrbuf), count * sizeof(struct lcommunity))); if ((count = mtype_stats_alloc(MTYPE_CLUSTER))) vty_out(vty, "%ld Cluster lists, using %s of memory\n", count, mtype_memstr(memstrbuf, sizeof(memstrbuf), count * sizeof(struct cluster_list))); /* Peer related usage */ count = mtype_stats_alloc(MTYPE_BGP_PEER); vty_out(vty, "%ld peers, using %s of memory\n", count, mtype_memstr(memstrbuf, sizeof(memstrbuf), count * sizeof(struct peer))); if ((count = mtype_stats_alloc(MTYPE_PEER_GROUP))) vty_out(vty, "%ld peer groups, using %s of memory\n", count, mtype_memstr(memstrbuf, sizeof(memstrbuf), count * sizeof(struct peer_group))); /* Other */ if ((count = mtype_stats_alloc(MTYPE_BGP_REGEXP))) vty_out(vty, "%ld compiled regexes, using %s of memory\n", count, mtype_memstr(memstrbuf, sizeof(memstrbuf), count * sizeof(regex_t))); return CMD_SUCCESS; } static void bgp_show_bestpath_json(struct bgp *bgp, json_object *json) { json_object *bestpath = json_object_new_object(); if (bgp_flag_check(bgp, BGP_FLAG_ASPATH_IGNORE)) json_object_string_add(bestpath, "asPath", "ignore"); if (bgp_flag_check(bgp, BGP_FLAG_ASPATH_CONFED)) json_object_string_add(bestpath, "asPath", "confed"); if (bgp_flag_check(bgp, BGP_FLAG_ASPATH_MULTIPATH_RELAX)) { if (bgp_flag_check(bgp, BGP_FLAG_MULTIPATH_RELAX_AS_SET)) json_object_string_add(bestpath, "multiPathRelax", "as-set"); else json_object_string_add(bestpath, "multiPathRelax", "true"); } else json_object_string_add(bestpath, "multiPathRelax", "false"); if (bgp_flag_check(bgp, BGP_FLAG_COMPARE_ROUTER_ID)) json_object_string_add(bestpath, "compareRouterId", "true"); if (bgp_flag_check(bgp, BGP_FLAG_MED_CONFED) || bgp_flag_check(bgp, BGP_FLAG_MED_MISSING_AS_WORST)) { if (bgp_flag_check(bgp, BGP_FLAG_MED_CONFED)) json_object_string_add(bestpath, "med", "confed"); if (bgp_flag_check(bgp, BGP_FLAG_MED_MISSING_AS_WORST)) json_object_string_add(bestpath, "med", "missing-as-worst"); else json_object_string_add(bestpath, "med", "true"); } json_object_object_add(json, "bestPath", bestpath); } /* Print the error code/subcode for why the peer is down */ static void bgp_show_peer_reset(struct vty * vty, struct peer *peer, json_object *json_peer, bool use_json) { const char *code_str; const char *subcode_str; if (use_json) { if (peer->last_reset == PEER_DOWN_NOTIFY_SEND || peer->last_reset == PEER_DOWN_NOTIFY_RECEIVED) { char errorcodesubcode_hexstr[5]; char errorcodesubcode_str[256]; code_str = bgp_notify_code_str(peer->notify.code); subcode_str = bgp_notify_subcode_str( peer->notify.code, peer->notify.subcode); sprintf(errorcodesubcode_hexstr, "%02X%02X", peer->notify.code, peer->notify.subcode); json_object_string_add(json_peer, "lastErrorCodeSubcode", errorcodesubcode_hexstr); snprintf(errorcodesubcode_str, 255, "%s%s", code_str, subcode_str); json_object_string_add(json_peer, "lastNotificationReason", errorcodesubcode_str); if (peer->last_reset == PEER_DOWN_NOTIFY_RECEIVED && peer->notify.code == BGP_NOTIFY_CEASE && (peer->notify.subcode == BGP_NOTIFY_CEASE_ADMIN_SHUTDOWN || peer->notify.subcode == BGP_NOTIFY_CEASE_ADMIN_RESET) && peer->notify.length) { char msgbuf[1024]; const char *msg_str; msg_str = bgp_notify_admin_message( msgbuf, sizeof(msgbuf), (uint8_t *)peer->notify.data, peer->notify.length); if (msg_str) json_object_string_add( json_peer, "lastShutdownDescription", msg_str); } } json_object_string_add(json_peer, "lastResetDueTo", peer_down_str[(int)peer->last_reset]); json_object_int_add(json_peer, "lastResetCode", peer->last_reset); } else { if (peer->last_reset == PEER_DOWN_NOTIFY_SEND || peer->last_reset == PEER_DOWN_NOTIFY_RECEIVED) { code_str = bgp_notify_code_str(peer->notify.code); subcode_str = bgp_notify_subcode_str(peer->notify.code, peer->notify.subcode); vty_out(vty, " Notification %s (%s%s)\n", peer->last_reset == PEER_DOWN_NOTIFY_SEND ? "sent" : "received", code_str, subcode_str); } else { vty_out(vty, " %s\n", peer_down_str[(int)peer->last_reset]); } } } static inline bool bgp_has_peer_failed(struct peer *peer, afi_t afi, safi_t safi) { return ((peer->status != Established) || !peer->afc_recv[afi][safi]); } static void bgp_show_failed_summary(struct vty *vty, struct bgp *bgp, struct peer *peer, json_object *json_peer, int max_neighbor_width, bool use_json) { char timebuf[BGP_UPTIME_LEN], dn_flag[2]; int len; if (use_json) { if (peer_dynamic_neighbor(peer)) json_object_boolean_true_add(json_peer, "dynamicPeer"); if (peer->hostname) json_object_string_add(json_peer, "hostname", peer->hostname); if (peer->domainname) json_object_string_add(json_peer, "domainname", peer->domainname); json_object_int_add(json_peer, "connectionsEstablished", peer->established); json_object_int_add(json_peer, "connectionsDropped", peer->dropped); peer_uptime(peer->uptime, timebuf, BGP_UPTIME_LEN, use_json, json_peer); if (peer->status == Established) json_object_string_add(json_peer, "lastResetDueTo", "AFI/SAFI Not Negotiated"); else bgp_show_peer_reset(NULL, peer, json_peer, true); } else { dn_flag[1] = '\0'; dn_flag[0] = peer_dynamic_neighbor(peer) ? '*' : '\0'; if (peer->hostname && bgp_flag_check(bgp, BGP_FLAG_SHOW_HOSTNAME)) len = vty_out(vty, "%s%s(%s)", dn_flag, peer->hostname, peer->host); else len = vty_out(vty, "%s%s", dn_flag, peer->host); /* pad the neighbor column with spaces */ if (len < max_neighbor_width) vty_out(vty, "%*s", max_neighbor_width - len, " "); vty_out(vty, "%7d %7d %8s", peer->established, peer->dropped, peer_uptime(peer->uptime, timebuf, BGP_UPTIME_LEN, 0, NULL)); if (peer->status == Established) vty_out(vty, " AFI/SAFI Not Negotiated\n"); else bgp_show_peer_reset(vty, peer, NULL, false); } } /* Show BGP peer's summary information. */ static int bgp_show_summary(struct vty *vty, struct bgp *bgp, int afi, int safi, bool show_failed, bool use_json) { struct peer *peer; struct listnode *node, *nnode; unsigned int count = 0, dn_count = 0; char timebuf[BGP_UPTIME_LEN], dn_flag[2]; char neighbor_buf[VTY_BUFSIZ]; int neighbor_col_default_width = 16; int len, failed_count = 0; int max_neighbor_width = 0; int pfx_rcd_safi; json_object *json = NULL; json_object *json_peer = NULL; json_object *json_peers = NULL; struct peer_af *paf; /* labeled-unicast routes are installed in the unicast table so in order * to * display the correct PfxRcd value we must look at SAFI_UNICAST */ if (safi == SAFI_LABELED_UNICAST) pfx_rcd_safi = SAFI_UNICAST; else pfx_rcd_safi = safi; if (use_json) { json = json_object_new_object(); json_peers = json_object_new_object(); for (ALL_LIST_ELEMENTS(bgp->peer, node, nnode, peer)) { if (!CHECK_FLAG(peer->flags, PEER_FLAG_CONFIG_NODE)) continue; if (peer->afc[afi][safi]) { /* See if we have at least a single failed peer */ if (bgp_has_peer_failed(peer, afi, safi)) failed_count++; count++; } if (peer_dynamic_neighbor(peer)) dn_count++; } } else { /* Loop over all neighbors that will be displayed to determine * how many * characters are needed for the Neighbor column */ for (ALL_LIST_ELEMENTS(bgp->peer, node, nnode, peer)) { if (!CHECK_FLAG(peer->flags, PEER_FLAG_CONFIG_NODE)) continue; if (peer->afc[afi][safi]) { memset(dn_flag, '\0', sizeof(dn_flag)); if (peer_dynamic_neighbor(peer)) dn_flag[0] = '*'; if (peer->hostname && bgp_flag_check(bgp, BGP_FLAG_SHOW_HOSTNAME)) sprintf(neighbor_buf, "%s%s(%s) ", dn_flag, peer->hostname, peer->host); else sprintf(neighbor_buf, "%s%s ", dn_flag, peer->host); len = strlen(neighbor_buf); if (len > max_neighbor_width) max_neighbor_width = len; /* See if we have at least a single failed peer */ if (bgp_has_peer_failed(peer, afi, safi)) failed_count++; count++; } } /* Originally we displayed the Neighbor column as 16 * characters wide so make that the default */ if (max_neighbor_width < neighbor_col_default_width) max_neighbor_width = neighbor_col_default_width; } if (show_failed && !failed_count) { if (use_json) { json_object_int_add(json, "failedPeersCount", 0); json_object_int_add(json, "dynamicPeers", dn_count); json_object_int_add(json, "totalPeers", count); vty_out(vty, "%s\n", json_object_to_json_string_ext( json, JSON_C_TO_STRING_PRETTY)); json_object_free(json); } else { vty_out(vty, "%% No failed BGP neighbors found\n"); vty_out(vty, "\nTotal number of neighbors %d\n", count); } return CMD_SUCCESS; } count = 0; /* Reset the value as its used again */ for (ALL_LIST_ELEMENTS(bgp->peer, node, nnode, peer)) { if (!CHECK_FLAG(peer->flags, PEER_FLAG_CONFIG_NODE)) continue; if (!peer->afc[afi][safi]) continue; if (!count) { unsigned long ents; char memstrbuf[MTYPE_MEMSTR_LEN]; int64_t vrf_id_ui; vrf_id_ui = (bgp->vrf_id == VRF_UNKNOWN) ? -1 : (int64_t)bgp->vrf_id; /* Usage summary and header */ if (use_json) { json_object_string_add( json, "routerId", inet_ntoa(bgp->router_id)); json_object_int_add(json, "as", bgp->as); json_object_int_add(json, "vrfId", vrf_id_ui); json_object_string_add( json, "vrfName", (bgp->inst_type == BGP_INSTANCE_TYPE_DEFAULT) ? VRF_DEFAULT_NAME : bgp->name); } else { vty_out(vty, "BGP router identifier %s, local AS number %u vrf-id %d", inet_ntoa(bgp->router_id), bgp->as, bgp->vrf_id == VRF_UNKNOWN ? -1 : (int)bgp->vrf_id); vty_out(vty, "\n"); } if (bgp_update_delay_configured(bgp)) { if (use_json) { json_object_int_add( json, "updateDelayLimit", bgp->v_update_delay); if (bgp->v_update_delay != bgp->v_establish_wait) json_object_int_add( json, "updateDelayEstablishWait", bgp->v_establish_wait); if (bgp_update_delay_active(bgp)) { json_object_string_add( json, "updateDelayFirstNeighbor", bgp->update_delay_begin_time); json_object_boolean_true_add( json, "updateDelayInProgress"); } else { if (bgp->update_delay_over) { json_object_string_add( json, "updateDelayFirstNeighbor", bgp->update_delay_begin_time); json_object_string_add( json, "updateDelayBestpathResumed", bgp->update_delay_end_time); json_object_string_add( json, "updateDelayZebraUpdateResume", bgp->update_delay_zebra_resume_time); json_object_string_add( json, "updateDelayPeerUpdateResume", bgp->update_delay_peers_resume_time); } } } else { vty_out(vty, "Read-only mode update-delay limit: %d seconds\n", bgp->v_update_delay); if (bgp->v_update_delay != bgp->v_establish_wait) vty_out(vty, " Establish wait: %d seconds\n", bgp->v_establish_wait); if (bgp_update_delay_active(bgp)) { vty_out(vty, " First neighbor established: %s\n", bgp->update_delay_begin_time); vty_out(vty, " Delay in progress\n"); } else { if (bgp->update_delay_over) { vty_out(vty, " First neighbor established: %s\n", bgp->update_delay_begin_time); vty_out(vty, " Best-paths resumed: %s\n", bgp->update_delay_end_time); vty_out(vty, " zebra update resumed: %s\n", bgp->update_delay_zebra_resume_time); vty_out(vty, " peers update resumed: %s\n", bgp->update_delay_peers_resume_time); } } } } if (use_json) { if (bgp_maxmed_onstartup_configured(bgp) && bgp->maxmed_active) json_object_boolean_true_add( json, "maxMedOnStartup"); if (bgp->v_maxmed_admin) json_object_boolean_true_add( json, "maxMedAdministrative"); json_object_int_add( json, "tableVersion", bgp_table_version(bgp->rib[afi][safi])); ents = bgp_table_count(bgp->rib[afi][safi]); json_object_int_add(json, "ribCount", ents); json_object_int_add( json, "ribMemory", ents * sizeof(struct bgp_node)); ents = bgp->af_peer_count[afi][safi]; json_object_int_add(json, "peerCount", ents); json_object_int_add(json, "peerMemory", ents * sizeof(struct peer)); if ((ents = listcount(bgp->group))) { json_object_int_add( json, "peerGroupCount", ents); json_object_int_add( json, "peerGroupMemory", ents * sizeof(struct peer_group)); } if (CHECK_FLAG(bgp->af_flags[afi][safi], BGP_CONFIG_DAMPENING)) json_object_boolean_true_add( json, "dampeningEnabled"); } else { if (bgp_maxmed_onstartup_configured(bgp) && bgp->maxmed_active) vty_out(vty, "Max-med on-startup active\n"); if (bgp->v_maxmed_admin) vty_out(vty, "Max-med administrative active\n"); vty_out(vty, "BGP table version %" PRIu64 "\n", bgp_table_version(bgp->rib[afi][safi])); ents = bgp_table_count(bgp->rib[afi][safi]); vty_out(vty, "RIB entries %ld, using %s of memory\n", ents, mtype_memstr(memstrbuf, sizeof(memstrbuf), ents * sizeof(struct bgp_node))); /* Peer related usage */ ents = bgp->af_peer_count[afi][safi]; vty_out(vty, "Peers %ld, using %s of memory\n", ents, mtype_memstr( memstrbuf, sizeof(memstrbuf), ents * sizeof(struct peer))); if ((ents = listcount(bgp->group))) vty_out(vty, "Peer groups %ld, using %s of memory\n", ents, mtype_memstr( memstrbuf, sizeof(memstrbuf), ents * sizeof(struct peer_group))); if (CHECK_FLAG(bgp->af_flags[afi][safi], BGP_CONFIG_DAMPENING)) vty_out(vty, "Dampening enabled.\n"); vty_out(vty, "\n"); /* Subtract 8 here because 'Neighbor' is * 8 characters */ vty_out(vty, "Neighbor"); vty_out(vty, "%*s", max_neighbor_width - 8, " "); if (show_failed) vty_out(vty, "EstdCnt DropCnt ResetTime Reason\n"); else vty_out(vty, "V AS MsgRcvd MsgSent TblVer InQ OutQ Up/Down State/PfxRcd\n"); } } count++; /* Works for both failed & successful cases */ if (peer_dynamic_neighbor(peer)) dn_count++; if (use_json) { json_peer = NULL; if (show_failed && bgp_has_peer_failed(peer, afi, safi)) { json_peer = json_object_new_object(); bgp_show_failed_summary(vty, bgp, peer, json_peer, 0, use_json); } else if (!show_failed) { json_peer = json_object_new_object(); if (peer_dynamic_neighbor(peer)) { json_object_boolean_true_add(json_peer, "dynamicPeer"); } if (peer->hostname) json_object_string_add(json_peer, "hostname", peer->hostname); if (peer->domainname) json_object_string_add(json_peer, "domainname", peer->domainname); json_object_int_add(json_peer, "remoteAs", peer->as); json_object_int_add(json_peer, "version", 4); json_object_int_add(json_peer, "msgRcvd", PEER_TOTAL_RX(peer)); json_object_int_add(json_peer, "msgSent", PEER_TOTAL_TX(peer)); json_object_int_add(json_peer, "tableVersion", peer->version[afi][safi]); json_object_int_add(json_peer, "outq", peer->obuf->count); json_object_int_add(json_peer, "inq", 0); peer_uptime(peer->uptime, timebuf, BGP_UPTIME_LEN, use_json, json_peer); /* * Adding "pfxRcd" field to match with the corresponding * CLI. "prefixReceivedCount" will be deprecated in * future. */ json_object_int_add(json_peer, "prefixReceivedCount", peer->pcount[afi][pfx_rcd_safi]); json_object_int_add(json_peer, "pfxRcd", peer->pcount[afi][pfx_rcd_safi]); paf = peer_af_find(peer, afi, pfx_rcd_safi); if (paf && PAF_SUBGRP(paf)) json_object_int_add(json_peer, "pfxSnt", (PAF_SUBGRP(paf))->scount); if (CHECK_FLAG(peer->flags, PEER_FLAG_SHUTDOWN)) json_object_string_add(json_peer, "state", "Idle (Admin)"); else if (peer->afc_recv[afi][safi]) json_object_string_add( json_peer, "state", lookup_msg(bgp_status_msg, peer->status, NULL)); else if (CHECK_FLAG(peer->sflags, PEER_STATUS_PREFIX_OVERFLOW)) json_object_string_add(json_peer, "state", "Idle (PfxCt)"); else json_object_string_add( json_peer, "state", lookup_msg(bgp_status_msg, peer->status, NULL)); json_object_int_add(json_peer, "connectionsEstablished", peer->established); json_object_int_add(json_peer, "connectionsDropped", peer->dropped); } /* Avoid creating empty peer dicts in JSON */ if (json_peer == NULL) continue; if (peer->conf_if) json_object_string_add(json_peer, "idType", "interface"); else if (peer->su.sa.sa_family == AF_INET) json_object_string_add(json_peer, "idType", "ipv4"); else if (peer->su.sa.sa_family == AF_INET6) json_object_string_add(json_peer, "idType", "ipv6"); json_object_object_add(json_peers, peer->host, json_peer); } else { if (show_failed && bgp_has_peer_failed(peer, afi, safi)) { bgp_show_failed_summary(vty, bgp, peer, NULL, max_neighbor_width, use_json); } else if (!show_failed) { memset(dn_flag, '\0', sizeof(dn_flag)); if (peer_dynamic_neighbor(peer)) { dn_flag[0] = '*'; } if (peer->hostname && bgp_flag_check(bgp, BGP_FLAG_SHOW_HOSTNAME)) len = vty_out(vty, "%s%s(%s)", dn_flag, peer->hostname, peer->host); else len = vty_out(vty, "%s%s", dn_flag, peer->host); /* pad the neighbor column with spaces */ if (len < max_neighbor_width) vty_out(vty, "%*s", max_neighbor_width - len, " "); vty_out(vty, "4 %10u %7u %7u %8" PRIu64 " %4d %4zd %8s", peer->as, PEER_TOTAL_RX(peer), PEER_TOTAL_TX(peer), peer->version[afi][safi], 0, peer->obuf->count, peer_uptime(peer->uptime, timebuf, BGP_UPTIME_LEN, 0, NULL)); if (peer->status == Established) if (peer->afc_recv[afi][safi]) vty_out(vty, " %12" PRIu32, peer->pcount [afi] [pfx_rcd_safi]); else vty_out(vty, " NoNeg"); else { if (CHECK_FLAG(peer->flags, PEER_FLAG_SHUTDOWN)) vty_out(vty, " Idle (Admin)"); else if (CHECK_FLAG( peer->sflags, PEER_STATUS_PREFIX_OVERFLOW)) vty_out(vty, " Idle (PfxCt)"); else vty_out(vty, " %12s", lookup_msg(bgp_status_msg, peer->status, NULL)); } vty_out(vty, "\n"); } } } if (use_json) { json_object_object_add(json, "peers", json_peers); json_object_int_add(json, "failedPeers", failed_count); json_object_int_add(json, "totalPeers", count); json_object_int_add(json, "dynamicPeers", dn_count); if (!show_failed) bgp_show_bestpath_json(bgp, json); vty_out(vty, "%s\n", json_object_to_json_string_ext( json, JSON_C_TO_STRING_PRETTY)); json_object_free(json); } else { if (count) vty_out(vty, "\nTotal number of neighbors %d\n", count); else { vty_out(vty, "No %s neighbor is configured\n", get_afi_safi_str(afi, safi, false)); } if (dn_count) { vty_out(vty, "* - dynamic neighbor\n"); vty_out(vty, "%d dynamic neighbor(s), limit %d\n", dn_count, bgp->dynamic_neighbors_limit); } } return CMD_SUCCESS; } static void bgp_show_summary_afi_safi(struct vty *vty, struct bgp *bgp, int afi, int safi, bool show_failed, bool use_json) { int is_first = 1; int afi_wildcard = (afi == AFI_MAX); int safi_wildcard = (safi == SAFI_MAX); int is_wildcard = (afi_wildcard || safi_wildcard); bool nbr_output = false; if (use_json && is_wildcard) vty_out(vty, "{\n"); if (afi_wildcard) afi = 1; /* AFI_IP */ while (afi < AFI_MAX) { if (safi_wildcard) safi = 1; /* SAFI_UNICAST */ while (safi < SAFI_MAX) { if (bgp_afi_safi_peer_exists(bgp, afi, safi)) { nbr_output = true; if (is_wildcard) { /* * So limit output to those afi/safi * pairs that * actualy have something interesting in * them */ if (use_json) { if (!is_first) vty_out(vty, ",\n"); else is_first = 0; vty_out(vty, "\"%s\":", get_afi_safi_str(afi, safi, true)); } else { vty_out(vty, "\n%s Summary:\n", get_afi_safi_str(afi, safi, false)); } } bgp_show_summary(vty, bgp, afi, safi, show_failed, use_json); } safi++; if (!safi_wildcard) safi = SAFI_MAX; } afi++; if (!afi_wildcard) afi = AFI_MAX; } if (use_json && is_wildcard) vty_out(vty, "}\n"); else if (!nbr_output) { if (use_json) vty_out(vty, "{}\n"); else vty_out(vty, "%% No BGP neighbors found\n"); } } static void bgp_show_all_instances_summary_vty(struct vty *vty, afi_t afi, safi_t safi, bool show_failed, bool use_json) { struct listnode *node, *nnode; struct bgp *bgp; int is_first = 1; bool nbr_output = false; if (use_json) vty_out(vty, "{\n"); for (ALL_LIST_ELEMENTS(bm->bgp, node, nnode, bgp)) { nbr_output = true; if (use_json) { if (!is_first) vty_out(vty, ",\n"); else is_first = 0; vty_out(vty, "\"%s\":", (bgp->inst_type == BGP_INSTANCE_TYPE_DEFAULT) ? VRF_DEFAULT_NAME : bgp->name); } else { vty_out(vty, "\nInstance %s:\n", (bgp->inst_type == BGP_INSTANCE_TYPE_DEFAULT) ? VRF_DEFAULT_NAME : bgp->name); } bgp_show_summary_afi_safi(vty, bgp, afi, safi, show_failed, use_json); } if (use_json) vty_out(vty, "}\n"); else if (!nbr_output) vty_out(vty, "%% BGP instance not found\n"); } int bgp_show_summary_vty(struct vty *vty, const char *name, afi_t afi, safi_t safi, bool show_failed, bool use_json) { struct bgp *bgp; if (name) { if (strmatch(name, "all")) { bgp_show_all_instances_summary_vty(vty, afi, safi, show_failed, use_json); return CMD_SUCCESS; } else { bgp = bgp_lookup_by_name(name); if (!bgp) { if (use_json) vty_out(vty, "{}\n"); else vty_out(vty, "%% BGP instance not found\n"); return CMD_WARNING; } bgp_show_summary_afi_safi(vty, bgp, afi, safi, show_failed, use_json); return CMD_SUCCESS; } } bgp = bgp_get_default(); if (bgp) bgp_show_summary_afi_safi(vty, bgp, afi, safi, show_failed, use_json); else { if (use_json) vty_out(vty, "{}\n"); else vty_out(vty, "%% BGP instance not found\n"); return CMD_WARNING; } return CMD_SUCCESS; } /* `show [ip] bgp summary' commands. */ DEFUN (show_ip_bgp_summary, show_ip_bgp_summary_cmd, "show [ip] bgp [ VIEWVRFNAME] ["BGP_AFI_CMD_STR" ["BGP_SAFI_WITH_LABEL_CMD_STR"]] summary [failed] [json]", SHOW_STR IP_STR BGP_STR BGP_INSTANCE_HELP_STR BGP_AFI_HELP_STR BGP_SAFI_WITH_LABEL_HELP_STR "Summary of BGP neighbor status\n" "Show only sessions not in Established state\n" JSON_STR) { char *vrf = NULL; afi_t afi = AFI_MAX; safi_t safi = SAFI_MAX; bool show_failed = false; int idx = 0; /* show [ip] bgp */ if (argv_find(argv, argc, "ip", &idx)) afi = AFI_IP; /* [ VIEWVRFNAME] */ if (argv_find(argv, argc, "vrf", &idx)) { vrf = argv[idx + 1]->arg; if (vrf && strmatch(vrf, VRF_DEFAULT_NAME)) vrf = NULL; } else if (argv_find(argv, argc, "view", &idx)) /* [ VIEWVRFNAME] */ vrf = argv[idx + 1]->arg; /* ["BGP_AFI_CMD_STR" ["BGP_SAFI_CMD_STR"]] */ if (argv_find_and_parse_afi(argv, argc, &idx, &afi)) { argv_find_and_parse_safi(argv, argc, &idx, &safi); } if (argv_find(argv, argc, "failed", &idx)) show_failed = true; bool uj = use_json(argc, argv); return bgp_show_summary_vty(vty, vrf, afi, safi, show_failed, uj); } const char *get_afi_safi_str(afi_t afi, safi_t safi, bool for_json) { if (for_json) return get_afi_safi_json_str(afi, safi); else return get_afi_safi_vty_str(afi, safi); } /* Show BGP peer's information. */ enum show_type { show_all, show_peer, show_ipv4_all, show_ipv6_all, show_ipv4_peer, show_ipv6_peer }; static void bgp_show_peer_afi_orf_cap(struct vty *vty, struct peer *p, afi_t afi, safi_t safi, uint16_t adv_smcap, uint16_t adv_rmcap, uint16_t rcv_smcap, uint16_t rcv_rmcap, bool use_json, json_object *json_pref) { /* Send-Mode */ if (CHECK_FLAG(p->af_cap[afi][safi], adv_smcap) || CHECK_FLAG(p->af_cap[afi][safi], rcv_smcap)) { if (use_json) { if (CHECK_FLAG(p->af_cap[afi][safi], adv_smcap) && CHECK_FLAG(p->af_cap[afi][safi], rcv_smcap)) json_object_string_add(json_pref, "sendMode", "advertisedAndReceived"); else if (CHECK_FLAG(p->af_cap[afi][safi], adv_smcap)) json_object_string_add(json_pref, "sendMode", "advertised"); else if (CHECK_FLAG(p->af_cap[afi][safi], rcv_smcap)) json_object_string_add(json_pref, "sendMode", "received"); } else { vty_out(vty, " Send-mode: "); if (CHECK_FLAG(p->af_cap[afi][safi], adv_smcap)) vty_out(vty, "advertised"); if (CHECK_FLAG(p->af_cap[afi][safi], rcv_smcap)) vty_out(vty, "%sreceived", CHECK_FLAG(p->af_cap[afi][safi], adv_smcap) ? ", " : ""); vty_out(vty, "\n"); } } /* Receive-Mode */ if (CHECK_FLAG(p->af_cap[afi][safi], adv_rmcap) || CHECK_FLAG(p->af_cap[afi][safi], rcv_rmcap)) { if (use_json) { if (CHECK_FLAG(p->af_cap[afi][safi], adv_rmcap) && CHECK_FLAG(p->af_cap[afi][safi], rcv_rmcap)) json_object_string_add(json_pref, "recvMode", "advertisedAndReceived"); else if (CHECK_FLAG(p->af_cap[afi][safi], adv_rmcap)) json_object_string_add(json_pref, "recvMode", "advertised"); else if (CHECK_FLAG(p->af_cap[afi][safi], rcv_rmcap)) json_object_string_add(json_pref, "recvMode", "received"); } else { vty_out(vty, " Receive-mode: "); if (CHECK_FLAG(p->af_cap[afi][safi], adv_rmcap)) vty_out(vty, "advertised"); if (CHECK_FLAG(p->af_cap[afi][safi], rcv_rmcap)) vty_out(vty, "%sreceived", CHECK_FLAG(p->af_cap[afi][safi], adv_rmcap) ? ", " : ""); vty_out(vty, "\n"); } } } static void bgp_show_peer_afi(struct vty *vty, struct peer *p, afi_t afi, safi_t safi, bool use_json, json_object *json_neigh) { struct bgp_filter *filter; struct peer_af *paf; char orf_pfx_name[BUFSIZ]; int orf_pfx_count; json_object *json_af = NULL; json_object *json_prefA = NULL; json_object *json_prefB = NULL; json_object *json_addr = NULL; if (use_json) { json_addr = json_object_new_object(); json_af = json_object_new_object(); filter = &p->filter[afi][safi]; if (peer_group_active(p)) json_object_string_add(json_addr, "peerGroupMember", p->group->name); paf = peer_af_find(p, afi, safi); if (paf && PAF_SUBGRP(paf)) { json_object_int_add(json_addr, "updateGroupId", PAF_UPDGRP(paf)->id); json_object_int_add(json_addr, "subGroupId", PAF_SUBGRP(paf)->id); json_object_int_add(json_addr, "packetQueueLength", bpacket_queue_virtual_length(paf)); } if (CHECK_FLAG(p->af_cap[afi][safi], PEER_CAP_ORF_PREFIX_SM_ADV) || CHECK_FLAG(p->af_cap[afi][safi], PEER_CAP_ORF_PREFIX_SM_RCV) || CHECK_FLAG(p->af_cap[afi][safi], PEER_CAP_ORF_PREFIX_RM_ADV) || CHECK_FLAG(p->af_cap[afi][safi], PEER_CAP_ORF_PREFIX_RM_RCV)) { json_object_int_add(json_af, "orfType", ORF_TYPE_PREFIX); json_prefA = json_object_new_object(); bgp_show_peer_afi_orf_cap(vty, p, afi, safi, PEER_CAP_ORF_PREFIX_SM_ADV, PEER_CAP_ORF_PREFIX_RM_ADV, PEER_CAP_ORF_PREFIX_SM_RCV, PEER_CAP_ORF_PREFIX_RM_RCV, use_json, json_prefA); json_object_object_add(json_af, "orfPrefixList", json_prefA); } if (CHECK_FLAG(p->af_cap[afi][safi], PEER_CAP_ORF_PREFIX_SM_ADV) || CHECK_FLAG(p->af_cap[afi][safi], PEER_CAP_ORF_PREFIX_SM_OLD_RCV) || CHECK_FLAG(p->af_cap[afi][safi], PEER_CAP_ORF_PREFIX_RM_ADV) || CHECK_FLAG(p->af_cap[afi][safi], PEER_CAP_ORF_PREFIX_RM_OLD_RCV)) { json_object_int_add(json_af, "orfOldType", ORF_TYPE_PREFIX_OLD); json_prefB = json_object_new_object(); bgp_show_peer_afi_orf_cap( vty, p, afi, safi, PEER_CAP_ORF_PREFIX_SM_ADV, PEER_CAP_ORF_PREFIX_RM_ADV, PEER_CAP_ORF_PREFIX_SM_OLD_RCV, PEER_CAP_ORF_PREFIX_RM_OLD_RCV, use_json, json_prefB); json_object_object_add(json_af, "orfOldPrefixList", json_prefB); } if (CHECK_FLAG(p->af_cap[afi][safi], PEER_CAP_ORF_PREFIX_SM_ADV) || CHECK_FLAG(p->af_cap[afi][safi], PEER_CAP_ORF_PREFIX_SM_RCV) || CHECK_FLAG(p->af_cap[afi][safi], PEER_CAP_ORF_PREFIX_SM_OLD_RCV) || CHECK_FLAG(p->af_cap[afi][safi], PEER_CAP_ORF_PREFIX_RM_ADV) || CHECK_FLAG(p->af_cap[afi][safi], PEER_CAP_ORF_PREFIX_RM_RCV) || CHECK_FLAG(p->af_cap[afi][safi], PEER_CAP_ORF_PREFIX_RM_OLD_RCV)) json_object_object_add(json_addr, "afDependentCap", json_af); else json_object_free(json_af); sprintf(orf_pfx_name, "%s.%d.%d", p->host, afi, safi); orf_pfx_count = prefix_bgp_show_prefix_list( NULL, afi, orf_pfx_name, use_json); if (CHECK_FLAG(p->af_sflags[afi][safi], PEER_STATUS_ORF_PREFIX_SEND) || orf_pfx_count) { if (CHECK_FLAG(p->af_sflags[afi][safi], PEER_STATUS_ORF_PREFIX_SEND)) json_object_boolean_true_add(json_neigh, "orfSent"); if (orf_pfx_count) json_object_int_add(json_addr, "orfRecvCounter", orf_pfx_count); } if (CHECK_FLAG(p->af_sflags[afi][safi], PEER_STATUS_ORF_WAIT_REFRESH)) json_object_string_add( json_addr, "orfFirstUpdate", "deferredUntilORFOrRouteRefreshRecvd"); if (CHECK_FLAG(p->af_flags[afi][safi], PEER_FLAG_REFLECTOR_CLIENT)) json_object_boolean_true_add(json_addr, "routeReflectorClient"); if (CHECK_FLAG(p->af_flags[afi][safi], PEER_FLAG_RSERVER_CLIENT)) json_object_boolean_true_add(json_addr, "routeServerClient"); if (CHECK_FLAG(p->af_flags[afi][safi], PEER_FLAG_SOFT_RECONFIG)) json_object_boolean_true_add(json_addr, "inboundSoftConfigPermit"); if (CHECK_FLAG(p->af_flags[afi][safi], PEER_FLAG_REMOVE_PRIVATE_AS_ALL_REPLACE)) json_object_boolean_true_add( json_addr, "privateAsNumsAllReplacedInUpdatesToNbr"); else if (CHECK_FLAG(p->af_flags[afi][safi], PEER_FLAG_REMOVE_PRIVATE_AS_REPLACE)) json_object_boolean_true_add( json_addr, "privateAsNumsReplacedInUpdatesToNbr"); else if (CHECK_FLAG(p->af_flags[afi][safi], PEER_FLAG_REMOVE_PRIVATE_AS_ALL)) json_object_boolean_true_add( json_addr, "privateAsNumsAllRemovedInUpdatesToNbr"); else if (CHECK_FLAG(p->af_flags[afi][safi], PEER_FLAG_REMOVE_PRIVATE_AS)) json_object_boolean_true_add( json_addr, "privateAsNumsRemovedInUpdatesToNbr"); if (p->addpath_type[afi][safi] != BGP_ADDPATH_NONE) json_object_boolean_true_add( json_addr, bgp_addpath_names(p->addpath_type[afi][safi]) ->type_json_name); if (CHECK_FLAG(p->af_flags[afi][safi], PEER_FLAG_AS_OVERRIDE)) json_object_string_add(json_addr, "overrideASNsInOutboundUpdates", "ifAspathEqualRemoteAs"); if (CHECK_FLAG(p->af_flags[afi][safi], PEER_FLAG_NEXTHOP_SELF) || CHECK_FLAG(p->af_flags[afi][safi], PEER_FLAG_FORCE_NEXTHOP_SELF)) json_object_boolean_true_add(json_addr, "routerAlwaysNextHop"); if (CHECK_FLAG(p->af_flags[afi][safi], PEER_FLAG_AS_PATH_UNCHANGED)) json_object_boolean_true_add( json_addr, "unchangedAsPathPropogatedToNbr"); if (CHECK_FLAG(p->af_flags[afi][safi], PEER_FLAG_NEXTHOP_UNCHANGED)) json_object_boolean_true_add( json_addr, "unchangedNextHopPropogatedToNbr"); if (CHECK_FLAG(p->af_flags[afi][safi], PEER_FLAG_MED_UNCHANGED)) json_object_boolean_true_add( json_addr, "unchangedMedPropogatedToNbr"); if (CHECK_FLAG(p->af_flags[afi][safi], PEER_FLAG_SEND_COMMUNITY) || CHECK_FLAG(p->af_flags[afi][safi], PEER_FLAG_SEND_EXT_COMMUNITY)) { if (CHECK_FLAG(p->af_flags[afi][safi], PEER_FLAG_SEND_COMMUNITY) && CHECK_FLAG(p->af_flags[afi][safi], PEER_FLAG_SEND_EXT_COMMUNITY)) json_object_string_add(json_addr, "commAttriSentToNbr", "extendedAndStandard"); else if (CHECK_FLAG(p->af_flags[afi][safi], PEER_FLAG_SEND_EXT_COMMUNITY)) json_object_string_add(json_addr, "commAttriSentToNbr", "extended"); else json_object_string_add(json_addr, "commAttriSentToNbr", "standard"); } if (CHECK_FLAG(p->af_flags[afi][safi], PEER_FLAG_DEFAULT_ORIGINATE)) { if (p->default_rmap[afi][safi].name) json_object_string_add( json_addr, "defaultRouteMap", p->default_rmap[afi][safi].name); if (paf && PAF_SUBGRP(paf) && CHECK_FLAG(PAF_SUBGRP(paf)->sflags, SUBGRP_STATUS_DEFAULT_ORIGINATE)) json_object_boolean_true_add(json_addr, "defaultSent"); else json_object_boolean_true_add(json_addr, "defaultNotSent"); } if (afi == AFI_L2VPN && safi == SAFI_EVPN) { if (is_evpn_enabled()) json_object_boolean_true_add( json_addr, "advertiseAllVnis"); } if (filter->plist[FILTER_IN].name || filter->dlist[FILTER_IN].name || filter->aslist[FILTER_IN].name || filter->map[RMAP_IN].name) json_object_boolean_true_add(json_addr, "inboundPathPolicyConfig"); if (filter->plist[FILTER_OUT].name || filter->dlist[FILTER_OUT].name || filter->aslist[FILTER_OUT].name || filter->map[RMAP_OUT].name || filter->usmap.name) json_object_boolean_true_add( json_addr, "outboundPathPolicyConfig"); /* prefix-list */ if (filter->plist[FILTER_IN].name) json_object_string_add(json_addr, "incomingUpdatePrefixFilterList", filter->plist[FILTER_IN].name); if (filter->plist[FILTER_OUT].name) json_object_string_add(json_addr, "outgoingUpdatePrefixFilterList", filter->plist[FILTER_OUT].name); /* distribute-list */ if (filter->dlist[FILTER_IN].name) json_object_string_add( json_addr, "incomingUpdateNetworkFilterList", filter->dlist[FILTER_IN].name); if (filter->dlist[FILTER_OUT].name) json_object_string_add( json_addr, "outgoingUpdateNetworkFilterList", filter->dlist[FILTER_OUT].name); /* filter-list. */ if (filter->aslist[FILTER_IN].name) json_object_string_add(json_addr, "incomingUpdateAsPathFilterList", filter->aslist[FILTER_IN].name); if (filter->aslist[FILTER_OUT].name) json_object_string_add(json_addr, "outgoingUpdateAsPathFilterList", filter->aslist[FILTER_OUT].name); /* route-map. */ if (filter->map[RMAP_IN].name) json_object_string_add( json_addr, "routeMapForIncomingAdvertisements", filter->map[RMAP_IN].name); if (filter->map[RMAP_OUT].name) json_object_string_add( json_addr, "routeMapForOutgoingAdvertisements", filter->map[RMAP_OUT].name); /* ebgp-requires-policy (inbound) */ if (p->bgp->ebgp_requires_policy == DEFAULT_EBGP_POLICY_ENABLED && !bgp_inbound_policy_exists(p, filter)) json_object_string_add( json_addr, "inboundEbgpRequiresPolicy", "Inbound updates discarded due to missing policy"); /* ebgp-requires-policy (outbound) */ if (p->bgp->ebgp_requires_policy == DEFAULT_EBGP_POLICY_ENABLED && (!bgp_outbound_policy_exists(p, filter))) json_object_string_add( json_addr, "outboundEbgpRequiresPolicy", "Outbound updates discarded due to missing policy"); /* unsuppress-map */ if (filter->usmap.name) json_object_string_add(json_addr, "selectiveUnsuppressRouteMap", filter->usmap.name); /* Receive prefix count */ json_object_int_add(json_addr, "acceptedPrefixCounter", p->pcount[afi][safi]); if (paf && PAF_SUBGRP(paf)) json_object_int_add(json_addr, "sentPrefixCounter", (PAF_SUBGRP(paf))->scount); /* Maximum prefix */ if (CHECK_FLAG(p->af_flags[afi][safi], PEER_FLAG_MAX_PREFIX)) { json_object_int_add(json_addr, "prefixAllowedMax", p->pmax[afi][safi]); if (CHECK_FLAG(p->af_flags[afi][safi], PEER_FLAG_MAX_PREFIX_WARNING)) json_object_boolean_true_add( json_addr, "prefixAllowedMaxWarning"); json_object_int_add(json_addr, "prefixAllowedWarningThresh", p->pmax_threshold[afi][safi]); if (p->pmax_restart[afi][safi]) json_object_int_add( json_addr, "prefixAllowedRestartIntervalMsecs", p->pmax_restart[afi][safi] * 60000); } json_object_object_add(json_neigh, get_afi_safi_str(afi, safi, true), json_addr); } else { filter = &p->filter[afi][safi]; vty_out(vty, " For address family: %s\n", get_afi_safi_str(afi, safi, false)); if (peer_group_active(p)) vty_out(vty, " %s peer-group member\n", p->group->name); paf = peer_af_find(p, afi, safi); if (paf && PAF_SUBGRP(paf)) { vty_out(vty, " Update group %" PRIu64 ", subgroup %" PRIu64 "\n", PAF_UPDGRP(paf)->id, PAF_SUBGRP(paf)->id); vty_out(vty, " Packet Queue length %d\n", bpacket_queue_virtual_length(paf)); } else { vty_out(vty, " Not part of any update group\n"); } if (CHECK_FLAG(p->af_cap[afi][safi], PEER_CAP_ORF_PREFIX_SM_ADV) || CHECK_FLAG(p->af_cap[afi][safi], PEER_CAP_ORF_PREFIX_SM_RCV) || CHECK_FLAG(p->af_cap[afi][safi], PEER_CAP_ORF_PREFIX_SM_OLD_RCV) || CHECK_FLAG(p->af_cap[afi][safi], PEER_CAP_ORF_PREFIX_RM_ADV) || CHECK_FLAG(p->af_cap[afi][safi], PEER_CAP_ORF_PREFIX_RM_RCV) || CHECK_FLAG(p->af_cap[afi][safi], PEER_CAP_ORF_PREFIX_RM_OLD_RCV)) vty_out(vty, " AF-dependant capabilities:\n"); if (CHECK_FLAG(p->af_cap[afi][safi], PEER_CAP_ORF_PREFIX_SM_ADV) || CHECK_FLAG(p->af_cap[afi][safi], PEER_CAP_ORF_PREFIX_SM_RCV) || CHECK_FLAG(p->af_cap[afi][safi], PEER_CAP_ORF_PREFIX_RM_ADV) || CHECK_FLAG(p->af_cap[afi][safi], PEER_CAP_ORF_PREFIX_RM_RCV)) { vty_out(vty, " Outbound Route Filter (ORF) type (%d) Prefix-list:\n", ORF_TYPE_PREFIX); bgp_show_peer_afi_orf_cap( vty, p, afi, safi, PEER_CAP_ORF_PREFIX_SM_ADV, PEER_CAP_ORF_PREFIX_RM_ADV, PEER_CAP_ORF_PREFIX_SM_RCV, PEER_CAP_ORF_PREFIX_RM_RCV, use_json, NULL); } if (CHECK_FLAG(p->af_cap[afi][safi], PEER_CAP_ORF_PREFIX_SM_ADV) || CHECK_FLAG(p->af_cap[afi][safi], PEER_CAP_ORF_PREFIX_SM_OLD_RCV) || CHECK_FLAG(p->af_cap[afi][safi], PEER_CAP_ORF_PREFIX_RM_ADV) || CHECK_FLAG(p->af_cap[afi][safi], PEER_CAP_ORF_PREFIX_RM_OLD_RCV)) { vty_out(vty, " Outbound Route Filter (ORF) type (%d) Prefix-list:\n", ORF_TYPE_PREFIX_OLD); bgp_show_peer_afi_orf_cap( vty, p, afi, safi, PEER_CAP_ORF_PREFIX_SM_ADV, PEER_CAP_ORF_PREFIX_RM_ADV, PEER_CAP_ORF_PREFIX_SM_OLD_RCV, PEER_CAP_ORF_PREFIX_RM_OLD_RCV, use_json, NULL); } sprintf(orf_pfx_name, "%s.%d.%d", p->host, afi, safi); orf_pfx_count = prefix_bgp_show_prefix_list( NULL, afi, orf_pfx_name, use_json); if (CHECK_FLAG(p->af_sflags[afi][safi], PEER_STATUS_ORF_PREFIX_SEND) || orf_pfx_count) { vty_out(vty, " Outbound Route Filter (ORF):"); if (CHECK_FLAG(p->af_sflags[afi][safi], PEER_STATUS_ORF_PREFIX_SEND)) vty_out(vty, " sent;"); if (orf_pfx_count) vty_out(vty, " received (%d entries)", orf_pfx_count); vty_out(vty, "\n"); } if (CHECK_FLAG(p->af_sflags[afi][safi], PEER_STATUS_ORF_WAIT_REFRESH)) vty_out(vty, " First update is deferred until ORF or ROUTE-REFRESH is received\n"); if (CHECK_FLAG(p->af_flags[afi][safi], PEER_FLAG_REFLECTOR_CLIENT)) vty_out(vty, " Route-Reflector Client\n"); if (CHECK_FLAG(p->af_flags[afi][safi], PEER_FLAG_RSERVER_CLIENT)) vty_out(vty, " Route-Server Client\n"); if (CHECK_FLAG(p->af_flags[afi][safi], PEER_FLAG_SOFT_RECONFIG)) vty_out(vty, " Inbound soft reconfiguration allowed\n"); if (CHECK_FLAG(p->af_flags[afi][safi], PEER_FLAG_REMOVE_PRIVATE_AS_ALL_REPLACE)) vty_out(vty, " Private AS numbers (all) replaced in updates to this neighbor\n"); else if (CHECK_FLAG(p->af_flags[afi][safi], PEER_FLAG_REMOVE_PRIVATE_AS_REPLACE)) vty_out(vty, " Private AS numbers replaced in updates to this neighbor\n"); else if (CHECK_FLAG(p->af_flags[afi][safi], PEER_FLAG_REMOVE_PRIVATE_AS_ALL)) vty_out(vty, " Private AS numbers (all) removed in updates to this neighbor\n"); else if (CHECK_FLAG(p->af_flags[afi][safi], PEER_FLAG_REMOVE_PRIVATE_AS)) vty_out(vty, " Private AS numbers removed in updates to this neighbor\n"); if (p->addpath_type[afi][safi] != BGP_ADDPATH_NONE) vty_out(vty, " %s\n", bgp_addpath_names(p->addpath_type[afi][safi]) ->human_description); if (CHECK_FLAG(p->af_flags[afi][safi], PEER_FLAG_AS_OVERRIDE)) vty_out(vty, " Override ASNs in outbound updates if aspath equals remote-as\n"); if (CHECK_FLAG(p->af_flags[afi][safi], PEER_FLAG_NEXTHOP_SELF) || CHECK_FLAG(p->af_flags[afi][safi], PEER_FLAG_FORCE_NEXTHOP_SELF)) vty_out(vty, " NEXT_HOP is always this router\n"); if (CHECK_FLAG(p->af_flags[afi][safi], PEER_FLAG_AS_PATH_UNCHANGED)) vty_out(vty, " AS_PATH is propagated unchanged to this neighbor\n"); if (CHECK_FLAG(p->af_flags[afi][safi], PEER_FLAG_NEXTHOP_UNCHANGED)) vty_out(vty, " NEXT_HOP is propagated unchanged to this neighbor\n"); if (CHECK_FLAG(p->af_flags[afi][safi], PEER_FLAG_MED_UNCHANGED)) vty_out(vty, " MED is propagated unchanged to this neighbor\n"); if (CHECK_FLAG(p->af_flags[afi][safi], PEER_FLAG_SEND_COMMUNITY) || CHECK_FLAG(p->af_flags[afi][safi], PEER_FLAG_SEND_EXT_COMMUNITY) || CHECK_FLAG(p->af_flags[afi][safi], PEER_FLAG_SEND_LARGE_COMMUNITY)) { vty_out(vty, " Community attribute sent to this neighbor"); if (CHECK_FLAG(p->af_flags[afi][safi], PEER_FLAG_SEND_COMMUNITY) && CHECK_FLAG(p->af_flags[afi][safi], PEER_FLAG_SEND_EXT_COMMUNITY) && CHECK_FLAG(p->af_flags[afi][safi], PEER_FLAG_SEND_LARGE_COMMUNITY)) vty_out(vty, "(all)\n"); else if (CHECK_FLAG(p->af_flags[afi][safi], PEER_FLAG_SEND_LARGE_COMMUNITY)) vty_out(vty, "(large)\n"); else if (CHECK_FLAG(p->af_flags[afi][safi], PEER_FLAG_SEND_EXT_COMMUNITY)) vty_out(vty, "(extended)\n"); else vty_out(vty, "(standard)\n"); } if (CHECK_FLAG(p->af_flags[afi][safi], PEER_FLAG_DEFAULT_ORIGINATE)) { vty_out(vty, " Default information originate,"); if (p->default_rmap[afi][safi].name) vty_out(vty, " default route-map %s%s,", p->default_rmap[afi][safi].map ? "*" : "", p->default_rmap[afi][safi].name); if (paf && PAF_SUBGRP(paf) && CHECK_FLAG(PAF_SUBGRP(paf)->sflags, SUBGRP_STATUS_DEFAULT_ORIGINATE)) vty_out(vty, " default sent\n"); else vty_out(vty, " default not sent\n"); } /* advertise-vni-all */ if (afi == AFI_L2VPN && safi == SAFI_EVPN) { if (is_evpn_enabled()) vty_out(vty, " advertise-all-vni\n"); } if (filter->plist[FILTER_IN].name || filter->dlist[FILTER_IN].name || filter->aslist[FILTER_IN].name || filter->map[RMAP_IN].name) vty_out(vty, " Inbound path policy configured\n"); if (filter->plist[FILTER_OUT].name || filter->dlist[FILTER_OUT].name || filter->aslist[FILTER_OUT].name || filter->map[RMAP_OUT].name || filter->usmap.name) vty_out(vty, " Outbound path policy configured\n"); /* prefix-list */ if (filter->plist[FILTER_IN].name) vty_out(vty, " Incoming update prefix filter list is %s%s\n", filter->plist[FILTER_IN].plist ? "*" : "", filter->plist[FILTER_IN].name); if (filter->plist[FILTER_OUT].name) vty_out(vty, " Outgoing update prefix filter list is %s%s\n", filter->plist[FILTER_OUT].plist ? "*" : "", filter->plist[FILTER_OUT].name); /* distribute-list */ if (filter->dlist[FILTER_IN].name) vty_out(vty, " Incoming update network filter list is %s%s\n", filter->dlist[FILTER_IN].alist ? "*" : "", filter->dlist[FILTER_IN].name); if (filter->dlist[FILTER_OUT].name) vty_out(vty, " Outgoing update network filter list is %s%s\n", filter->dlist[FILTER_OUT].alist ? "*" : "", filter->dlist[FILTER_OUT].name); /* filter-list. */ if (filter->aslist[FILTER_IN].name) vty_out(vty, " Incoming update AS path filter list is %s%s\n", filter->aslist[FILTER_IN].aslist ? "*" : "", filter->aslist[FILTER_IN].name); if (filter->aslist[FILTER_OUT].name) vty_out(vty, " Outgoing update AS path filter list is %s%s\n", filter->aslist[FILTER_OUT].aslist ? "*" : "", filter->aslist[FILTER_OUT].name); /* route-map. */ if (filter->map[RMAP_IN].name) vty_out(vty, " Route map for incoming advertisements is %s%s\n", filter->map[RMAP_IN].map ? "*" : "", filter->map[RMAP_IN].name); if (filter->map[RMAP_OUT].name) vty_out(vty, " Route map for outgoing advertisements is %s%s\n", filter->map[RMAP_OUT].map ? "*" : "", filter->map[RMAP_OUT].name); /* ebgp-requires-policy (inbound) */ if (p->bgp->ebgp_requires_policy == DEFAULT_EBGP_POLICY_ENABLED && !bgp_inbound_policy_exists(p, filter)) vty_out(vty, " Inbound updates discarded due to missing policy\n"); /* ebgp-requires-policy (outbound) */ if (p->bgp->ebgp_requires_policy == DEFAULT_EBGP_POLICY_ENABLED && !bgp_outbound_policy_exists(p, filter)) vty_out(vty, " Outbound updates discarded due to missing policy\n"); /* unsuppress-map */ if (filter->usmap.name) vty_out(vty, " Route map for selective unsuppress is %s%s\n", filter->usmap.map ? "*" : "", filter->usmap.name); /* Receive prefix count */ vty_out(vty, " %" PRIu32 " accepted prefixes\n", p->pcount[afi][safi]); /* Maximum prefix */ if (CHECK_FLAG(p->af_flags[afi][safi], PEER_FLAG_MAX_PREFIX)) { vty_out(vty, " Maximum prefixes allowed %" PRIu32 "%s\n", p->pmax[afi][safi], CHECK_FLAG(p->af_flags[afi][safi], PEER_FLAG_MAX_PREFIX_WARNING) ? " (warning-only)" : ""); vty_out(vty, " Threshold for warning message %d%%", p->pmax_threshold[afi][safi]); if (p->pmax_restart[afi][safi]) vty_out(vty, ", restart interval %d min", p->pmax_restart[afi][safi]); vty_out(vty, "\n"); } vty_out(vty, "\n"); } } static void bgp_show_peer(struct vty *vty, struct peer *p, bool use_json, json_object *json) { struct bgp *bgp; char buf1[PREFIX2STR_BUFFER], buf[SU_ADDRSTRLEN]; char timebuf[BGP_UPTIME_LEN]; char dn_flag[2]; afi_t afi; safi_t safi; uint16_t i; uint8_t *msg; json_object *json_neigh = NULL; time_t epoch_tbuf; bgp = p->bgp; if (use_json) json_neigh = json_object_new_object(); memset(dn_flag, '\0', sizeof(dn_flag)); if (!p->conf_if && peer_dynamic_neighbor(p)) dn_flag[0] = '*'; if (!use_json) { if (p->conf_if) /* Configured interface name. */ vty_out(vty, "BGP neighbor on %s: %s, ", p->conf_if, BGP_PEER_SU_UNSPEC(p) ? "None" : sockunion2str(&p->su, buf, SU_ADDRSTRLEN)); else /* Configured IP address. */ vty_out(vty, "BGP neighbor is %s%s, ", dn_flag, p->host); } if (use_json) { if (p->conf_if && BGP_PEER_SU_UNSPEC(p)) json_object_string_add(json_neigh, "bgpNeighborAddr", "none"); else if (p->conf_if && !BGP_PEER_SU_UNSPEC(p)) json_object_string_add( json_neigh, "bgpNeighborAddr", sockunion2str(&p->su, buf, SU_ADDRSTRLEN)); json_object_int_add(json_neigh, "remoteAs", p->as); if (p->change_local_as) json_object_int_add(json_neigh, "localAs", p->change_local_as); else json_object_int_add(json_neigh, "localAs", p->local_as); if (CHECK_FLAG(p->flags, PEER_FLAG_LOCAL_AS_NO_PREPEND)) json_object_boolean_true_add(json_neigh, "localAsNoPrepend"); if (CHECK_FLAG(p->flags, PEER_FLAG_LOCAL_AS_REPLACE_AS)) json_object_boolean_true_add(json_neigh, "localAsReplaceAs"); } else { if ((p->as_type == AS_SPECIFIED) || (p->as_type == AS_EXTERNAL) || (p->as_type == AS_INTERNAL)) vty_out(vty, "remote AS %u, ", p->as); else vty_out(vty, "remote AS Unspecified, "); vty_out(vty, "local AS %u%s%s, ", p->change_local_as ? p->change_local_as : p->local_as, CHECK_FLAG(p->flags, PEER_FLAG_LOCAL_AS_NO_PREPEND) ? " no-prepend" : "", CHECK_FLAG(p->flags, PEER_FLAG_LOCAL_AS_REPLACE_AS) ? " replace-as" : ""); } /* peer type internal or confed-internal */ if ((p->as == p->local_as) || (p->as_type == AS_INTERNAL)) { if (use_json) { if (CHECK_FLAG(bgp->config, BGP_CONFIG_CONFEDERATION)) json_object_boolean_true_add( json_neigh, "nbrConfedInternalLink"); else json_object_boolean_true_add(json_neigh, "nbrInternalLink"); } else { if (CHECK_FLAG(bgp->config, BGP_CONFIG_CONFEDERATION)) vty_out(vty, "confed-internal link\n"); else vty_out(vty, "internal link\n"); } /* peer type external or confed-external */ } else if (p->as || (p->as_type == AS_EXTERNAL)) { if (use_json) { if (CHECK_FLAG(bgp->config, BGP_CONFIG_CONFEDERATION)) json_object_boolean_true_add( json_neigh, "nbrConfedExternalLink"); else json_object_boolean_true_add(json_neigh, "nbrExternalLink"); } else { if (bgp_confederation_peers_check(bgp, p->as)) vty_out(vty, "confed-external link\n"); else vty_out(vty, "external link\n"); } } else { if (use_json) json_object_boolean_true_add(json_neigh, "nbrUnspecifiedLink"); else vty_out(vty, "unspecified link\n"); } /* Description. */ if (p->desc) { if (use_json) json_object_string_add(json_neigh, "nbrDesc", p->desc); else vty_out(vty, " Description: %s\n", p->desc); } if (p->hostname) { if (use_json) { if (p->hostname) json_object_string_add(json_neigh, "hostname", p->hostname); if (p->domainname) json_object_string_add(json_neigh, "domainname", p->domainname); } else { if (p->domainname && (p->domainname[0] != '\0')) vty_out(vty, "Hostname: %s.%s\n", p->hostname, p->domainname); else vty_out(vty, "Hostname: %s\n", p->hostname); } } /* Peer-group */ if (p->group) { if (use_json) { json_object_string_add(json_neigh, "peerGroup", p->group->name); if (dn_flag[0]) { struct prefix prefix, *range = NULL; sockunion2hostprefix(&(p->su), &prefix); range = peer_group_lookup_dynamic_neighbor_range( p->group, &prefix); if (range) { prefix2str(range, buf1, sizeof(buf1)); json_object_string_add( json_neigh, "peerSubnetRangeGroup", buf1); } } } else { vty_out(vty, " Member of peer-group %s for session parameters\n", p->group->name); if (dn_flag[0]) { struct prefix prefix, *range = NULL; sockunion2hostprefix(&(p->su), &prefix); range = peer_group_lookup_dynamic_neighbor_range( p->group, &prefix); if (range) { prefix2str(range, buf1, sizeof(buf1)); vty_out(vty, " Belongs to the subnet range group: %s\n", buf1); } } } } if (use_json) { /* Administrative shutdown. */ if (CHECK_FLAG(p->flags, PEER_FLAG_SHUTDOWN)) json_object_boolean_true_add(json_neigh, "adminShutDown"); /* BGP Version. */ json_object_int_add(json_neigh, "bgpVersion", 4); json_object_string_add( json_neigh, "remoteRouterId", inet_ntop(AF_INET, &p->remote_id, buf1, sizeof(buf1))); json_object_string_add( json_neigh, "localRouterId", inet_ntop(AF_INET, &bgp->router_id, buf1, sizeof(buf1))); /* Confederation */ if (CHECK_FLAG(bgp->config, BGP_CONFIG_CONFEDERATION) && bgp_confederation_peers_check(bgp, p->as)) json_object_boolean_true_add(json_neigh, "nbrCommonAdmin"); /* Status. */ json_object_string_add( json_neigh, "bgpState", lookup_msg(bgp_status_msg, p->status, NULL)); if (p->status == Established) { time_t uptime; uptime = bgp_clock(); uptime -= p->uptime; epoch_tbuf = time(NULL) - uptime; #if CONFDATE > 20200101 CPP_NOTICE( "bgpTimerUp should be deprecated and can be removed now"); #endif /* * bgpTimerUp was miliseconds that was accurate * up to 1 day, then the value returned * became garbage. So in order to provide * some level of backwards compatability, * we still provde the data, but now * we are returning the correct value * and also adding a new bgpTimerUpMsec * which will allow us to deprecate * this eventually */ json_object_int_add(json_neigh, "bgpTimerUp", uptime * 1000); json_object_int_add(json_neigh, "bgpTimerUpMsec", uptime * 1000); json_object_string_add(json_neigh, "bgpTimerUpString", peer_uptime(p->uptime, timebuf, BGP_UPTIME_LEN, 0, NULL)); json_object_int_add(json_neigh, "bgpTimerUpEstablishedEpoch", epoch_tbuf); } else if (p->status == Active) { if (CHECK_FLAG(p->flags, PEER_FLAG_PASSIVE)) json_object_string_add(json_neigh, "bgpStateIs", "passive"); else if (CHECK_FLAG(p->sflags, PEER_STATUS_NSF_WAIT)) json_object_string_add(json_neigh, "bgpStateIs", "passiveNSF"); } /* read timer */ time_t uptime; struct tm *tm; uptime = bgp_clock(); uptime -= p->readtime; tm = gmtime(&uptime); json_object_int_add(json_neigh, "bgpTimerLastRead", (tm->tm_sec * 1000) + (tm->tm_min * 60000) + (tm->tm_hour * 3600000)); uptime = bgp_clock(); uptime -= p->last_write; tm = gmtime(&uptime); json_object_int_add(json_neigh, "bgpTimerLastWrite", (tm->tm_sec * 1000) + (tm->tm_min * 60000) + (tm->tm_hour * 3600000)); uptime = bgp_clock(); uptime -= p->update_time; tm = gmtime(&uptime); json_object_int_add(json_neigh, "bgpInUpdateElapsedTimeMsecs", (tm->tm_sec * 1000) + (tm->tm_min * 60000) + (tm->tm_hour * 3600000)); /* Configured timer values. */ json_object_int_add(json_neigh, "bgpTimerHoldTimeMsecs", p->v_holdtime * 1000); json_object_int_add(json_neigh, "bgpTimerKeepAliveIntervalMsecs", p->v_keepalive * 1000); if (CHECK_FLAG(p->flags, PEER_FLAG_TIMER)) { json_object_int_add(json_neigh, "bgpTimerConfiguredHoldTimeMsecs", p->holdtime * 1000); json_object_int_add( json_neigh, "bgpTimerConfiguredKeepAliveIntervalMsecs", p->keepalive * 1000); } else if ((bgp->default_holdtime != BGP_DEFAULT_HOLDTIME) || (bgp->default_keepalive != BGP_DEFAULT_KEEPALIVE)) { json_object_int_add(json_neigh, "bgpTimerConfiguredHoldTimeMsecs", bgp->default_holdtime); json_object_int_add( json_neigh, "bgpTimerConfiguredKeepAliveIntervalMsecs", bgp->default_keepalive); } } else { /* Administrative shutdown. */ if (CHECK_FLAG(p->flags, PEER_FLAG_SHUTDOWN)) vty_out(vty, " Administratively shut down\n"); /* BGP Version. */ vty_out(vty, " BGP version 4"); vty_out(vty, ", remote router ID %s", inet_ntop(AF_INET, &p->remote_id, buf1, sizeof(buf1))); vty_out(vty, ", local router ID %s\n", inet_ntop(AF_INET, &bgp->router_id, buf1, sizeof(buf1))); /* Confederation */ if (CHECK_FLAG(bgp->config, BGP_CONFIG_CONFEDERATION) && bgp_confederation_peers_check(bgp, p->as)) vty_out(vty, " Neighbor under common administration\n"); /* Status. */ vty_out(vty, " BGP state = %s", lookup_msg(bgp_status_msg, p->status, NULL)); if (p->status == Established) vty_out(vty, ", up for %8s", peer_uptime(p->uptime, timebuf, BGP_UPTIME_LEN, 0, NULL)); else if (p->status == Active) { if (CHECK_FLAG(p->flags, PEER_FLAG_PASSIVE)) vty_out(vty, " (passive)"); else if (CHECK_FLAG(p->sflags, PEER_STATUS_NSF_WAIT)) vty_out(vty, " (NSF passive)"); } vty_out(vty, "\n"); /* read timer */ vty_out(vty, " Last read %s", peer_uptime(p->readtime, timebuf, BGP_UPTIME_LEN, 0, NULL)); vty_out(vty, ", Last write %s\n", peer_uptime(p->last_write, timebuf, BGP_UPTIME_LEN, 0, NULL)); /* Configured timer values. */ vty_out(vty, " Hold time is %d, keepalive interval is %d seconds\n", p->v_holdtime, p->v_keepalive); if (CHECK_FLAG(p->flags, PEER_FLAG_TIMER)) { vty_out(vty, " Configured hold time is %d", p->holdtime); vty_out(vty, ", keepalive interval is %d seconds\n", p->keepalive); } else if ((bgp->default_holdtime != BGP_DEFAULT_HOLDTIME) || (bgp->default_keepalive != BGP_DEFAULT_KEEPALIVE)) { vty_out(vty, " Configured hold time is %d", bgp->default_holdtime); vty_out(vty, ", keepalive interval is %d seconds\n", bgp->default_keepalive); } } /* Capability. */ if (p->status == Established) { if (p->cap || p->afc_adv[AFI_IP][SAFI_UNICAST] || p->afc_recv[AFI_IP][SAFI_UNICAST] || p->afc_adv[AFI_IP][SAFI_MULTICAST] || p->afc_recv[AFI_IP][SAFI_MULTICAST] || p->afc_adv[AFI_IP6][SAFI_UNICAST] || p->afc_recv[AFI_IP6][SAFI_UNICAST] || p->afc_adv[AFI_IP6][SAFI_MULTICAST] || p->afc_recv[AFI_IP6][SAFI_MULTICAST] || p->afc_adv[AFI_IP6][SAFI_MPLS_VPN] || p->afc_recv[AFI_IP6][SAFI_MPLS_VPN] || p->afc_adv[AFI_IP6][SAFI_ENCAP] || p->afc_recv[AFI_IP6][SAFI_ENCAP] || p->afc_adv[AFI_IP6][SAFI_FLOWSPEC] || p->afc_recv[AFI_IP6][SAFI_FLOWSPEC] || p->afc_adv[AFI_IP][SAFI_ENCAP] || p->afc_recv[AFI_IP][SAFI_ENCAP] || p->afc_adv[AFI_IP][SAFI_FLOWSPEC] || p->afc_recv[AFI_IP][SAFI_FLOWSPEC] || p->afc_adv[AFI_IP][SAFI_MPLS_VPN] || p->afc_recv[AFI_IP][SAFI_MPLS_VPN]) { if (use_json) { json_object *json_cap = NULL; json_cap = json_object_new_object(); /* AS4 */ if (CHECK_FLAG(p->cap, PEER_CAP_AS4_RCV) || CHECK_FLAG(p->cap, PEER_CAP_AS4_ADV)) { if (CHECK_FLAG(p->cap, PEER_CAP_AS4_ADV) && CHECK_FLAG(p->cap, PEER_CAP_AS4_RCV)) json_object_string_add( json_cap, "4byteAs", "advertisedAndReceived"); else if (CHECK_FLAG(p->cap, PEER_CAP_AS4_ADV)) json_object_string_add( json_cap, "4byteAs", "advertised"); else if (CHECK_FLAG(p->cap, PEER_CAP_AS4_RCV)) json_object_string_add( json_cap, "4byteAs", "received"); } /* AddPath */ if (CHECK_FLAG(p->cap, PEER_CAP_ADDPATH_RCV) || CHECK_FLAG(p->cap, PEER_CAP_ADDPATH_ADV)) { json_object *json_add = NULL; const char *print_store; json_add = json_object_new_object(); FOREACH_AFI_SAFI (afi, safi) { json_object *json_sub = NULL; json_sub = json_object_new_object(); print_store = get_afi_safi_str( afi, safi, true); if (CHECK_FLAG( p->af_cap[afi] [safi], PEER_CAP_ADDPATH_AF_TX_ADV) || CHECK_FLAG( p->af_cap[afi] [safi], PEER_CAP_ADDPATH_AF_TX_RCV)) { if (CHECK_FLAG( p->af_cap [afi] [safi], PEER_CAP_ADDPATH_AF_TX_ADV) && CHECK_FLAG( p->af_cap [afi] [safi], PEER_CAP_ADDPATH_AF_TX_RCV)) json_object_boolean_true_add( json_sub, "txAdvertisedAndReceived"); else if ( CHECK_FLAG( p->af_cap [afi] [safi], PEER_CAP_ADDPATH_AF_TX_ADV)) json_object_boolean_true_add( json_sub, "txAdvertised"); else if ( CHECK_FLAG( p->af_cap [afi] [safi], PEER_CAP_ADDPATH_AF_TX_RCV)) json_object_boolean_true_add( json_sub, "txReceived"); } if (CHECK_FLAG( p->af_cap[afi] [safi], PEER_CAP_ADDPATH_AF_RX_ADV) || CHECK_FLAG( p->af_cap[afi] [safi], PEER_CAP_ADDPATH_AF_RX_RCV)) { if (CHECK_FLAG( p->af_cap [afi] [safi], PEER_CAP_ADDPATH_AF_RX_ADV) && CHECK_FLAG( p->af_cap [afi] [safi], PEER_CAP_ADDPATH_AF_RX_RCV)) json_object_boolean_true_add( json_sub, "rxAdvertisedAndReceived"); else if ( CHECK_FLAG( p->af_cap [afi] [safi], PEER_CAP_ADDPATH_AF_RX_ADV)) json_object_boolean_true_add( json_sub, "rxAdvertised"); else if ( CHECK_FLAG( p->af_cap [afi] [safi], PEER_CAP_ADDPATH_AF_RX_RCV)) json_object_boolean_true_add( json_sub, "rxReceived"); } if (CHECK_FLAG( p->af_cap[afi] [safi], PEER_CAP_ADDPATH_AF_TX_ADV) || CHECK_FLAG( p->af_cap[afi] [safi], PEER_CAP_ADDPATH_AF_TX_RCV) || CHECK_FLAG( p->af_cap[afi] [safi], PEER_CAP_ADDPATH_AF_RX_ADV) || CHECK_FLAG( p->af_cap[afi] [safi], PEER_CAP_ADDPATH_AF_RX_RCV)) json_object_object_add( json_add, print_store, json_sub); else json_object_free( json_sub); } json_object_object_add( json_cap, "addPath", json_add); } /* Dynamic */ if (CHECK_FLAG(p->cap, PEER_CAP_DYNAMIC_RCV) || CHECK_FLAG(p->cap, PEER_CAP_DYNAMIC_ADV)) { if (CHECK_FLAG(p->cap, PEER_CAP_DYNAMIC_ADV) && CHECK_FLAG(p->cap, PEER_CAP_DYNAMIC_RCV)) json_object_string_add( json_cap, "dynamic", "advertisedAndReceived"); else if (CHECK_FLAG( p->cap, PEER_CAP_DYNAMIC_ADV)) json_object_string_add( json_cap, "dynamic", "advertised"); else if (CHECK_FLAG( p->cap, PEER_CAP_DYNAMIC_RCV)) json_object_string_add( json_cap, "dynamic", "received"); } /* Extended nexthop */ if (CHECK_FLAG(p->cap, PEER_CAP_ENHE_RCV) || CHECK_FLAG(p->cap, PEER_CAP_ENHE_ADV)) { json_object *json_nxt = NULL; const char *print_store; if (CHECK_FLAG(p->cap, PEER_CAP_ENHE_ADV) && CHECK_FLAG(p->cap, PEER_CAP_ENHE_RCV)) json_object_string_add( json_cap, "extendedNexthop", "advertisedAndReceived"); else if (CHECK_FLAG(p->cap, PEER_CAP_ENHE_ADV)) json_object_string_add( json_cap, "extendedNexthop", "advertised"); else if (CHECK_FLAG(p->cap, PEER_CAP_ENHE_RCV)) json_object_string_add( json_cap, "extendedNexthop", "received"); if (CHECK_FLAG(p->cap, PEER_CAP_ENHE_RCV)) { json_nxt = json_object_new_object(); for (safi = SAFI_UNICAST; safi < SAFI_MAX; safi++) { if (CHECK_FLAG( p->af_cap [AFI_IP] [safi], PEER_CAP_ENHE_AF_RCV)) { print_store = get_afi_safi_str( AFI_IP, safi, true); json_object_string_add( json_nxt, print_store, "recieved"); /* misspelled for compatibility */ } } json_object_object_add( json_cap, "extendedNexthopFamililesByPeer", json_nxt); } } /* Route Refresh */ if (CHECK_FLAG(p->cap, PEER_CAP_REFRESH_ADV) || CHECK_FLAG(p->cap, PEER_CAP_REFRESH_NEW_RCV) || CHECK_FLAG(p->cap, PEER_CAP_REFRESH_OLD_RCV)) { if (CHECK_FLAG(p->cap, PEER_CAP_REFRESH_ADV) && (CHECK_FLAG( p->cap, PEER_CAP_REFRESH_NEW_RCV) || CHECK_FLAG( p->cap, PEER_CAP_REFRESH_OLD_RCV))) { if (CHECK_FLAG( p->cap, PEER_CAP_REFRESH_OLD_RCV) && CHECK_FLAG( p->cap, PEER_CAP_REFRESH_NEW_RCV)) json_object_string_add( json_cap, "routeRefresh", "advertisedAndReceivedOldNew"); else { if (CHECK_FLAG( p->cap, PEER_CAP_REFRESH_OLD_RCV)) json_object_string_add( json_cap, "routeRefresh", "advertisedAndReceivedOld"); else json_object_string_add( json_cap, "routeRefresh", "advertisedAndReceivedNew"); } } else if ( CHECK_FLAG( p->cap, PEER_CAP_REFRESH_ADV)) json_object_string_add( json_cap, "routeRefresh", "advertised"); else if ( CHECK_FLAG( p->cap, PEER_CAP_REFRESH_NEW_RCV) || CHECK_FLAG( p->cap, PEER_CAP_REFRESH_OLD_RCV)) json_object_string_add( json_cap, "routeRefresh", "received"); } /* Multiprotocol Extensions */ json_object *json_multi = NULL; json_multi = json_object_new_object(); FOREACH_AFI_SAFI (afi, safi) { if (p->afc_adv[afi][safi] || p->afc_recv[afi][safi]) { json_object *json_exten = NULL; json_exten = json_object_new_object(); if (p->afc_adv[afi][safi] && p->afc_recv[afi][safi]) json_object_boolean_true_add( json_exten, "advertisedAndReceived"); else if (p->afc_adv[afi][safi]) json_object_boolean_true_add( json_exten, "advertised"); else if (p->afc_recv[afi][safi]) json_object_boolean_true_add( json_exten, "received"); json_object_object_add( json_multi, get_afi_safi_str(afi, safi, true), json_exten); } } json_object_object_add( json_cap, "multiprotocolExtensions", json_multi); /* Hostname capabilities */ json_object *json_hname = NULL; json_hname = json_object_new_object(); if (CHECK_FLAG(p->cap, PEER_CAP_HOSTNAME_ADV)) { json_object_string_add( json_hname, "advHostName", bgp->peer_self->hostname ? bgp->peer_self ->hostname : "n/a"); json_object_string_add( json_hname, "advDomainName", bgp->peer_self->domainname ? bgp->peer_self ->domainname : "n/a"); } if (CHECK_FLAG(p->cap, PEER_CAP_HOSTNAME_RCV)) { json_object_string_add( json_hname, "rcvHostName", p->hostname ? p->hostname : "n/a"); json_object_string_add( json_hname, "rcvDomainName", p->domainname ? p->domainname : "n/a"); } json_object_object_add(json_cap, "hostName", json_hname); /* Gracefull Restart */ if (CHECK_FLAG(p->cap, PEER_CAP_RESTART_RCV) || CHECK_FLAG(p->cap, PEER_CAP_RESTART_ADV)) { if (CHECK_FLAG(p->cap, PEER_CAP_RESTART_ADV) && CHECK_FLAG(p->cap, PEER_CAP_RESTART_RCV)) json_object_string_add( json_cap, "gracefulRestart", "advertisedAndReceived"); else if (CHECK_FLAG( p->cap, PEER_CAP_RESTART_ADV)) json_object_string_add( json_cap, "gracefulRestartCapability", "advertised"); else if (CHECK_FLAG( p->cap, PEER_CAP_RESTART_RCV)) json_object_string_add( json_cap, "gracefulRestartCapability", "received"); if (CHECK_FLAG(p->cap, PEER_CAP_RESTART_RCV)) { int restart_af_count = 0; json_object *json_restart = NULL; json_restart = json_object_new_object(); json_object_int_add( json_cap, "gracefulRestartRemoteTimerMsecs", p->v_gr_restart * 1000); FOREACH_AFI_SAFI (afi, safi) { if (CHECK_FLAG( p->af_cap [afi] [safi], PEER_CAP_RESTART_AF_RCV)) { json_object * json_sub = NULL; json_sub = json_object_new_object(); if (CHECK_FLAG( p->af_cap [afi] [safi], PEER_CAP_RESTART_AF_PRESERVE_RCV)) json_object_boolean_true_add( json_sub, "preserved"); restart_af_count++; json_object_object_add( json_restart, get_afi_safi_str( afi, safi, true), json_sub); } } if (!restart_af_count) { json_object_string_add( json_cap, "addressFamiliesByPeer", "none"); json_object_free( json_restart); } else json_object_object_add( json_cap, "addressFamiliesByPeer", json_restart); } } json_object_object_add(json_neigh, "neighborCapabilities", json_cap); } else { vty_out(vty, " Neighbor capabilities:\n"); /* AS4 */ if (CHECK_FLAG(p->cap, PEER_CAP_AS4_RCV) || CHECK_FLAG(p->cap, PEER_CAP_AS4_ADV)) { vty_out(vty, " 4 Byte AS:"); if (CHECK_FLAG(p->cap, PEER_CAP_AS4_ADV)) vty_out(vty, " advertised"); if (CHECK_FLAG(p->cap, PEER_CAP_AS4_RCV)) vty_out(vty, " %sreceived", CHECK_FLAG( p->cap, PEER_CAP_AS4_ADV) ? "and " : ""); vty_out(vty, "\n"); } /* AddPath */ if (CHECK_FLAG(p->cap, PEER_CAP_ADDPATH_RCV) || CHECK_FLAG(p->cap, PEER_CAP_ADDPATH_ADV)) { vty_out(vty, " AddPath:\n"); FOREACH_AFI_SAFI (afi, safi) { if (CHECK_FLAG( p->af_cap[afi] [safi], PEER_CAP_ADDPATH_AF_TX_ADV) || CHECK_FLAG( p->af_cap[afi] [safi], PEER_CAP_ADDPATH_AF_TX_RCV)) { vty_out(vty, " %s: TX ", get_afi_safi_str( afi, safi, false)); if (CHECK_FLAG( p->af_cap [afi] [safi], PEER_CAP_ADDPATH_AF_TX_ADV)) vty_out(vty, "advertised %s", get_afi_safi_str( afi, safi, false)); if (CHECK_FLAG( p->af_cap [afi] [safi], PEER_CAP_ADDPATH_AF_TX_RCV)) vty_out(vty, "%sreceived", CHECK_FLAG( p->af_cap [afi] [safi], PEER_CAP_ADDPATH_AF_TX_ADV) ? " and " : ""); vty_out(vty, "\n"); } if (CHECK_FLAG( p->af_cap[afi] [safi], PEER_CAP_ADDPATH_AF_RX_ADV) || CHECK_FLAG( p->af_cap[afi] [safi], PEER_CAP_ADDPATH_AF_RX_RCV)) { vty_out(vty, " %s: RX ", get_afi_safi_str( afi, safi, false)); if (CHECK_FLAG( p->af_cap [afi] [safi], PEER_CAP_ADDPATH_AF_RX_ADV)) vty_out(vty, "advertised %s", get_afi_safi_str( afi, safi, false)); if (CHECK_FLAG( p->af_cap [afi] [safi], PEER_CAP_ADDPATH_AF_RX_RCV)) vty_out(vty, "%sreceived", CHECK_FLAG( p->af_cap [afi] [safi], PEER_CAP_ADDPATH_AF_RX_ADV) ? " and " : ""); vty_out(vty, "\n"); } } } /* Dynamic */ if (CHECK_FLAG(p->cap, PEER_CAP_DYNAMIC_RCV) || CHECK_FLAG(p->cap, PEER_CAP_DYNAMIC_ADV)) { vty_out(vty, " Dynamic:"); if (CHECK_FLAG(p->cap, PEER_CAP_DYNAMIC_ADV)) vty_out(vty, " advertised"); if (CHECK_FLAG(p->cap, PEER_CAP_DYNAMIC_RCV)) vty_out(vty, " %sreceived", CHECK_FLAG( p->cap, PEER_CAP_DYNAMIC_ADV) ? "and " : ""); vty_out(vty, "\n"); } /* Extended nexthop */ if (CHECK_FLAG(p->cap, PEER_CAP_ENHE_RCV) || CHECK_FLAG(p->cap, PEER_CAP_ENHE_ADV)) { vty_out(vty, " Extended nexthop:"); if (CHECK_FLAG(p->cap, PEER_CAP_ENHE_ADV)) vty_out(vty, " advertised"); if (CHECK_FLAG(p->cap, PEER_CAP_ENHE_RCV)) vty_out(vty, " %sreceived", CHECK_FLAG( p->cap, PEER_CAP_ENHE_ADV) ? "and " : ""); vty_out(vty, "\n"); if (CHECK_FLAG(p->cap, PEER_CAP_ENHE_RCV)) { vty_out(vty, " Address families by peer:\n "); for (safi = SAFI_UNICAST; safi < SAFI_MAX; safi++) if (CHECK_FLAG( p->af_cap [AFI_IP] [safi], PEER_CAP_ENHE_AF_RCV)) vty_out(vty, " %s\n", get_afi_safi_str( AFI_IP, safi, false)); } } /* Route Refresh */ if (CHECK_FLAG(p->cap, PEER_CAP_REFRESH_ADV) || CHECK_FLAG(p->cap, PEER_CAP_REFRESH_NEW_RCV) || CHECK_FLAG(p->cap, PEER_CAP_REFRESH_OLD_RCV)) { vty_out(vty, " Route refresh:"); if (CHECK_FLAG(p->cap, PEER_CAP_REFRESH_ADV)) vty_out(vty, " advertised"); if (CHECK_FLAG(p->cap, PEER_CAP_REFRESH_NEW_RCV) || CHECK_FLAG( p->cap, PEER_CAP_REFRESH_OLD_RCV)) vty_out(vty, " %sreceived(%s)", CHECK_FLAG( p->cap, PEER_CAP_REFRESH_ADV) ? "and " : "", (CHECK_FLAG( p->cap, PEER_CAP_REFRESH_OLD_RCV) && CHECK_FLAG( p->cap, PEER_CAP_REFRESH_NEW_RCV)) ? "old & new" : CHECK_FLAG( p->cap, PEER_CAP_REFRESH_OLD_RCV) ? "old" : "new"); vty_out(vty, "\n"); } /* Multiprotocol Extensions */ FOREACH_AFI_SAFI (afi, safi) if (p->afc_adv[afi][safi] || p->afc_recv[afi][safi]) { vty_out(vty, " Address Family %s:", get_afi_safi_str( afi, safi, false)); if (p->afc_adv[afi][safi]) vty_out(vty, " advertised"); if (p->afc_recv[afi][safi]) vty_out(vty, " %sreceived", p->afc_adv[afi] [safi] ? "and " : ""); vty_out(vty, "\n"); } /* Hostname capability */ vty_out(vty, " Hostname Capability:"); if (CHECK_FLAG(p->cap, PEER_CAP_HOSTNAME_ADV)) { vty_out(vty, " advertised (name: %s,domain name: %s)", bgp->peer_self->hostname ? bgp->peer_self ->hostname : "n/a", bgp->peer_self->domainname ? bgp->peer_self ->domainname : "n/a"); } else { vty_out(vty, " not advertised"); } if (CHECK_FLAG(p->cap, PEER_CAP_HOSTNAME_RCV)) { vty_out(vty, " received (name: %s,domain name: %s)", p->hostname ? p->hostname : "n/a", p->domainname ? p->domainname : "n/a"); } else { vty_out(vty, " not received"); } vty_out(vty, "\n"); /* Gracefull Restart */ if (CHECK_FLAG(p->cap, PEER_CAP_RESTART_RCV) || CHECK_FLAG(p->cap, PEER_CAP_RESTART_ADV)) { vty_out(vty, " Graceful Restart Capabilty:"); if (CHECK_FLAG(p->cap, PEER_CAP_RESTART_ADV)) vty_out(vty, " advertised"); if (CHECK_FLAG(p->cap, PEER_CAP_RESTART_RCV)) vty_out(vty, " %sreceived", CHECK_FLAG( p->cap, PEER_CAP_RESTART_ADV) ? "and " : ""); vty_out(vty, "\n"); if (CHECK_FLAG(p->cap, PEER_CAP_RESTART_RCV)) { int restart_af_count = 0; vty_out(vty, " Remote Restart timer is %d seconds\n", p->v_gr_restart); vty_out(vty, " Address families by peer:\n "); FOREACH_AFI_SAFI (afi, safi) if (CHECK_FLAG( p->af_cap [afi] [safi], PEER_CAP_RESTART_AF_RCV)) { vty_out(vty, "%s%s(%s)", restart_af_count ? ", " : "", get_afi_safi_str( afi, safi, false), CHECK_FLAG( p->af_cap [afi] [safi], PEER_CAP_RESTART_AF_PRESERVE_RCV) ? "preserved" : "not preserved"); restart_af_count++; } if (!restart_af_count) vty_out(vty, "none"); vty_out(vty, "\n"); } } } } } /* graceful restart information */ if (CHECK_FLAG(p->cap, PEER_CAP_RESTART_RCV) || p->t_gr_restart || p->t_gr_stale) { json_object *json_grace = NULL; json_object *json_grace_send = NULL; json_object *json_grace_recv = NULL; int eor_send_af_count = 0; int eor_receive_af_count = 0; if (use_json) { json_grace = json_object_new_object(); json_grace_send = json_object_new_object(); json_grace_recv = json_object_new_object(); if (p->status == Established) { FOREACH_AFI_SAFI (afi, safi) { if (CHECK_FLAG(p->af_sflags[afi][safi], PEER_STATUS_EOR_SEND)) { json_object_boolean_true_add( json_grace_send, get_afi_safi_str(afi, safi, true)); eor_send_af_count++; } } FOREACH_AFI_SAFI (afi, safi) { if (CHECK_FLAG( p->af_sflags[afi][safi], PEER_STATUS_EOR_RECEIVED)) { json_object_boolean_true_add( json_grace_recv, get_afi_safi_str(afi, safi, true)); eor_receive_af_count++; } } } json_object_object_add(json_grace, "endOfRibSend", json_grace_send); json_object_object_add(json_grace, "endOfRibRecv", json_grace_recv); if (p->t_gr_restart) json_object_int_add(json_grace, "gracefulRestartTimerMsecs", thread_timer_remain_second( p->t_gr_restart) * 1000); if (p->t_gr_stale) json_object_int_add( json_grace, "gracefulStalepathTimerMsecs", thread_timer_remain_second( p->t_gr_stale) * 1000); json_object_object_add( json_neigh, "gracefulRestartInfo", json_grace); } else { vty_out(vty, " Graceful restart information:\n"); if (p->status == Established) { vty_out(vty, " End-of-RIB send: "); FOREACH_AFI_SAFI (afi, safi) { if (CHECK_FLAG(p->af_sflags[afi][safi], PEER_STATUS_EOR_SEND)) { vty_out(vty, "%s%s", eor_send_af_count ? ", " : "", get_afi_safi_str(afi, safi, false)); eor_send_af_count++; } } vty_out(vty, "\n"); vty_out(vty, " End-of-RIB received: "); FOREACH_AFI_SAFI (afi, safi) { if (CHECK_FLAG( p->af_sflags[afi][safi], PEER_STATUS_EOR_RECEIVED)) { vty_out(vty, "%s%s", eor_receive_af_count ? ", " : "", get_afi_safi_str(afi, safi, false)); eor_receive_af_count++; } } vty_out(vty, "\n"); } if (p->t_gr_restart) vty_out(vty, " The remaining time of restart timer is %ld\n", thread_timer_remain_second( p->t_gr_restart)); if (p->t_gr_stale) vty_out(vty, " The remaining time of stalepath timer is %ld\n", thread_timer_remain_second( p->t_gr_stale)); } } if (use_json) { json_object *json_stat = NULL; json_stat = json_object_new_object(); /* Packet counts. */ json_object_int_add(json_stat, "depthInq", 0); json_object_int_add(json_stat, "depthOutq", (unsigned long)p->obuf->count); json_object_int_add(json_stat, "opensSent", atomic_load_explicit(&p->open_out, memory_order_relaxed)); json_object_int_add(json_stat, "opensRecv", atomic_load_explicit(&p->open_in, memory_order_relaxed)); json_object_int_add(json_stat, "notificationsSent", atomic_load_explicit(&p->notify_out, memory_order_relaxed)); json_object_int_add(json_stat, "notificationsRecv", atomic_load_explicit(&p->notify_in, memory_order_relaxed)); json_object_int_add(json_stat, "updatesSent", atomic_load_explicit(&p->update_out, memory_order_relaxed)); json_object_int_add(json_stat, "updatesRecv", atomic_load_explicit(&p->update_in, memory_order_relaxed)); json_object_int_add(json_stat, "keepalivesSent", atomic_load_explicit(&p->keepalive_out, memory_order_relaxed)); json_object_int_add(json_stat, "keepalivesRecv", atomic_load_explicit(&p->keepalive_in, memory_order_relaxed)); json_object_int_add(json_stat, "routeRefreshSent", atomic_load_explicit(&p->refresh_out, memory_order_relaxed)); json_object_int_add(json_stat, "routeRefreshRecv", atomic_load_explicit(&p->refresh_in, memory_order_relaxed)); json_object_int_add(json_stat, "capabilitySent", atomic_load_explicit(&p->dynamic_cap_out, memory_order_relaxed)); json_object_int_add(json_stat, "capabilityRecv", atomic_load_explicit(&p->dynamic_cap_in, memory_order_relaxed)); json_object_int_add(json_stat, "totalSent", PEER_TOTAL_TX(p)); json_object_int_add(json_stat, "totalRecv", PEER_TOTAL_RX(p)); json_object_object_add(json_neigh, "messageStats", json_stat); } else { /* Packet counts. */ vty_out(vty, " Message statistics:\n"); vty_out(vty, " Inq depth is 0\n"); vty_out(vty, " Outq depth is %lu\n", (unsigned long)p->obuf->count); vty_out(vty, " Sent Rcvd\n"); vty_out(vty, " Opens: %10d %10d\n", atomic_load_explicit(&p->open_out, memory_order_relaxed), atomic_load_explicit(&p->open_in, memory_order_relaxed)); vty_out(vty, " Notifications: %10d %10d\n", atomic_load_explicit(&p->notify_out, memory_order_relaxed), atomic_load_explicit(&p->notify_in, memory_order_relaxed)); vty_out(vty, " Updates: %10d %10d\n", atomic_load_explicit(&p->update_out, memory_order_relaxed), atomic_load_explicit(&p->update_in, memory_order_relaxed)); vty_out(vty, " Keepalives: %10d %10d\n", atomic_load_explicit(&p->keepalive_out, memory_order_relaxed), atomic_load_explicit(&p->keepalive_in, memory_order_relaxed)); vty_out(vty, " Route Refresh: %10d %10d\n", atomic_load_explicit(&p->refresh_out, memory_order_relaxed), atomic_load_explicit(&p->refresh_in, memory_order_relaxed)); vty_out(vty, " Capability: %10d %10d\n", atomic_load_explicit(&p->dynamic_cap_out, memory_order_relaxed), atomic_load_explicit(&p->dynamic_cap_in, memory_order_relaxed)); vty_out(vty, " Total: %10d %10d\n", PEER_TOTAL_TX(p), PEER_TOTAL_RX(p)); } if (use_json) { /* advertisement-interval */ json_object_int_add(json_neigh, "minBtwnAdvertisementRunsTimerMsecs", p->v_routeadv * 1000); /* Update-source. */ if (p->update_if || p->update_source) { if (p->update_if) json_object_string_add(json_neigh, "updateSource", p->update_if); else if (p->update_source) json_object_string_add( json_neigh, "updateSource", sockunion2str(p->update_source, buf1, SU_ADDRSTRLEN)); } } else { /* advertisement-interval */ vty_out(vty, " Minimum time between advertisement runs is %d seconds\n", p->v_routeadv); /* Update-source. */ if (p->update_if || p->update_source) { vty_out(vty, " Update source is "); if (p->update_if) vty_out(vty, "%s", p->update_if); else if (p->update_source) vty_out(vty, "%s", sockunion2str(p->update_source, buf1, SU_ADDRSTRLEN)); vty_out(vty, "\n"); } vty_out(vty, "\n"); } /* Address Family Information */ json_object *json_hold = NULL; if (use_json) json_hold = json_object_new_object(); FOREACH_AFI_SAFI (afi, safi) if (p->afc[afi][safi]) bgp_show_peer_afi(vty, p, afi, safi, use_json, json_hold); if (use_json) { json_object_object_add(json_neigh, "addressFamilyInfo", json_hold); json_object_int_add(json_neigh, "connectionsEstablished", p->established); json_object_int_add(json_neigh, "connectionsDropped", p->dropped); } else vty_out(vty, " Connections established %d; dropped %d\n", p->established, p->dropped); if (!p->last_reset) { if (use_json) json_object_string_add(json_neigh, "lastReset", "never"); else vty_out(vty, " Last reset never\n"); } else { if (use_json) { time_t uptime; struct tm *tm; uptime = bgp_clock(); uptime -= p->resettime; tm = gmtime(&uptime); json_object_int_add(json_neigh, "lastResetTimerMsecs", (tm->tm_sec * 1000) + (tm->tm_min * 60000) + (tm->tm_hour * 3600000)); bgp_show_peer_reset(NULL, p, json_neigh, true); } else { vty_out(vty, " Last reset %s, ", peer_uptime(p->resettime, timebuf, BGP_UPTIME_LEN, 0, NULL)); bgp_show_peer_reset(vty, p, NULL, false); if (p->last_reset_cause_size) { msg = p->last_reset_cause; vty_out(vty, " Message received that caused BGP to send a NOTIFICATION:\n "); for (i = 1; i <= p->last_reset_cause_size; i++) { vty_out(vty, "%02X", *msg++); if (i != p->last_reset_cause_size) { if (i % 16 == 0) { vty_out(vty, "\n "); } else if (i % 4 == 0) { vty_out(vty, " "); } } } vty_out(vty, "\n"); } } } if (CHECK_FLAG(p->sflags, PEER_STATUS_PREFIX_OVERFLOW)) { if (use_json) json_object_boolean_true_add(json_neigh, "prefixesConfigExceedMax"); else vty_out(vty, " Peer had exceeded the max. no. of prefixes configured.\n"); if (p->t_pmax_restart) { if (use_json) { json_object_boolean_true_add( json_neigh, "reducePrefixNumFrom"); json_object_int_add(json_neigh, "restartInTimerMsec", thread_timer_remain_second( p->t_pmax_restart) * 1000); } else vty_out(vty, " Reduce the no. of prefix from %s, will restart in %ld seconds\n", p->host, thread_timer_remain_second( p->t_pmax_restart)); } else { if (use_json) json_object_boolean_true_add( json_neigh, "reducePrefixNumAndClearIpBgp"); else vty_out(vty, " Reduce the no. of prefix and clear ip bgp %s to restore peering\n", p->host); } } /* EBGP Multihop and GTSM */ if (p->sort != BGP_PEER_IBGP) { if (use_json) { if (p->gtsm_hops > 0) json_object_int_add(json_neigh, "externalBgpNbrMaxHopsAway", p->gtsm_hops); else if (p->ttl > 1) json_object_int_add(json_neigh, "externalBgpNbrMaxHopsAway", p->ttl); } else { if (p->gtsm_hops > 0) vty_out(vty, " External BGP neighbor may be up to %d hops away.\n", p->gtsm_hops); else if (p->ttl > 1) vty_out(vty, " External BGP neighbor may be up to %d hops away.\n", p->ttl); } } else { if (p->gtsm_hops > 0) { if (use_json) json_object_int_add(json_neigh, "internalBgpNbrMaxHopsAway", p->gtsm_hops); else vty_out(vty, " Internal BGP neighbor may be up to %d hops away.\n", p->gtsm_hops); } } /* Local address. */ if (p->su_local) { if (use_json) { json_object_string_add(json_neigh, "hostLocal", sockunion2str(p->su_local, buf1, SU_ADDRSTRLEN)); json_object_int_add(json_neigh, "portLocal", ntohs(p->su_local->sin.sin_port)); } else vty_out(vty, "Local host: %s, Local port: %d\n", sockunion2str(p->su_local, buf1, SU_ADDRSTRLEN), ntohs(p->su_local->sin.sin_port)); } /* Remote address. */ if (p->su_remote) { if (use_json) { json_object_string_add(json_neigh, "hostForeign", sockunion2str(p->su_remote, buf1, SU_ADDRSTRLEN)); json_object_int_add(json_neigh, "portForeign", ntohs(p->su_remote->sin.sin_port)); } else vty_out(vty, "Foreign host: %s, Foreign port: %d\n", sockunion2str(p->su_remote, buf1, SU_ADDRSTRLEN), ntohs(p->su_remote->sin.sin_port)); } /* Nexthop display. */ if (p->su_local) { if (use_json) { json_object_string_add(json_neigh, "nexthop", inet_ntop(AF_INET, &p->nexthop.v4, buf1, sizeof(buf1))); json_object_string_add(json_neigh, "nexthopGlobal", inet_ntop(AF_INET6, &p->nexthop.v6_global, buf1, sizeof(buf1))); json_object_string_add(json_neigh, "nexthopLocal", inet_ntop(AF_INET6, &p->nexthop.v6_local, buf1, sizeof(buf1))); if (p->shared_network) json_object_string_add(json_neigh, "bgpConnection", "sharedNetwork"); else json_object_string_add(json_neigh, "bgpConnection", "nonSharedNetwork"); } else { vty_out(vty, "Nexthop: %s\n", inet_ntop(AF_INET, &p->nexthop.v4, buf1, sizeof(buf1))); vty_out(vty, "Nexthop global: %s\n", inet_ntop(AF_INET6, &p->nexthop.v6_global, buf1, sizeof(buf1))); vty_out(vty, "Nexthop local: %s\n", inet_ntop(AF_INET6, &p->nexthop.v6_local, buf1, sizeof(buf1))); vty_out(vty, "BGP connection: %s\n", p->shared_network ? "shared network" : "non shared network"); } } /* Timer information. */ if (use_json) { json_object_int_add(json_neigh, "connectRetryTimer", p->v_connect); if (p->status == Established && p->rtt) json_object_int_add(json_neigh, "estimatedRttInMsecs", p->rtt); if (p->t_start) json_object_int_add( json_neigh, "nextStartTimerDueInMsecs", thread_timer_remain_second(p->t_start) * 1000); if (p->t_connect) json_object_int_add( json_neigh, "nextConnectTimerDueInMsecs", thread_timer_remain_second(p->t_connect) * 1000); if (p->t_routeadv) { json_object_int_add(json_neigh, "mraiInterval", p->v_routeadv); json_object_int_add( json_neigh, "mraiTimerExpireInMsecs", thread_timer_remain_second(p->t_routeadv) * 1000); } if (p->password) json_object_int_add(json_neigh, "authenticationEnabled", 1); if (p->t_read) json_object_string_add(json_neigh, "readThread", "on"); else json_object_string_add(json_neigh, "readThread", "off"); if (CHECK_FLAG(p->thread_flags, PEER_THREAD_WRITES_ON)) json_object_string_add(json_neigh, "writeThread", "on"); else json_object_string_add(json_neigh, "writeThread", "off"); } else { vty_out(vty, "BGP Connect Retry Timer in Seconds: %d\n", p->v_connect); if (p->status == Established && p->rtt) vty_out(vty, "Estimated round trip time: %d ms\n", p->rtt); if (p->t_start) vty_out(vty, "Next start timer due in %ld seconds\n", thread_timer_remain_second(p->t_start)); if (p->t_connect) vty_out(vty, "Next connect timer due in %ld seconds\n", thread_timer_remain_second(p->t_connect)); if (p->t_routeadv) vty_out(vty, "MRAI (interval %u) timer expires in %ld seconds\n", p->v_routeadv, thread_timer_remain_second(p->t_routeadv)); if (p->password) vty_out(vty, "Peer Authentication Enabled\n"); vty_out(vty, "Read thread: %s Write thread: %s FD used: %d\n", p->t_read ? "on" : "off", CHECK_FLAG(p->thread_flags, PEER_THREAD_WRITES_ON) ? "on" : "off", p->fd); } if (p->notify.code == BGP_NOTIFY_OPEN_ERR && p->notify.subcode == BGP_NOTIFY_OPEN_UNSUP_CAPBL) bgp_capability_vty_out(vty, p, use_json, json_neigh); if (!use_json) vty_out(vty, "\n"); /* BFD information. */ bgp_bfd_show_info(vty, p, use_json, json_neigh); if (use_json) { if (p->conf_if) /* Configured interface name. */ json_object_object_add(json, p->conf_if, json_neigh); else /* Configured IP address. */ json_object_object_add(json, p->host, json_neigh); } } static int bgp_show_neighbor(struct vty *vty, struct bgp *bgp, enum show_type type, union sockunion *su, const char *conf_if, bool use_json, json_object *json) { struct listnode *node, *nnode; struct peer *peer; int find = 0; bool nbr_output = false; afi_t afi = AFI_MAX; safi_t safi = SAFI_MAX; if (type == show_ipv4_peer || type == show_ipv4_all) { afi = AFI_IP; } else if (type == show_ipv6_peer || type == show_ipv6_all) { afi = AFI_IP6; } for (ALL_LIST_ELEMENTS(bgp->peer, node, nnode, peer)) { if (!CHECK_FLAG(peer->flags, PEER_FLAG_CONFIG_NODE)) continue; switch (type) { case show_all: bgp_show_peer(vty, peer, use_json, json); nbr_output = true; break; case show_peer: if (conf_if) { if ((peer->conf_if && !strcmp(peer->conf_if, conf_if)) || (peer->hostname && !strcmp(peer->hostname, conf_if))) { find = 1; bgp_show_peer(vty, peer, use_json, json); } } else { if (sockunion_same(&peer->su, su)) { find = 1; bgp_show_peer(vty, peer, use_json, json); } } break; case show_ipv4_peer: case show_ipv6_peer: FOREACH_SAFI (safi) { if (peer->afc[afi][safi]) { if (conf_if) { if ((peer->conf_if && !strcmp(peer->conf_if, conf_if)) || (peer->hostname && !strcmp(peer->hostname, conf_if))) { find = 1; bgp_show_peer(vty, peer, use_json, json); break; } } else { if (sockunion_same(&peer->su, su)) { find = 1; bgp_show_peer(vty, peer, use_json, json); break; } } } } break; case show_ipv4_all: case show_ipv6_all: FOREACH_SAFI (safi) { if (peer->afc[afi][safi]) { bgp_show_peer(vty, peer, use_json, json); nbr_output = true; break; } } break; } } if ((type == show_peer || type == show_ipv4_peer || type == show_ipv6_peer) && !find) { if (use_json) json_object_boolean_true_add(json, "bgpNoSuchNeighbor"); else vty_out(vty, "%% No such neighbor in this view/vrf\n"); } if (type != show_peer && type != show_ipv4_peer && type != show_ipv6_peer && !nbr_output && !use_json) vty_out(vty, "%% No BGP neighbors found\n"); if (use_json) { vty_out(vty, "%s\n", json_object_to_json_string_ext( json, JSON_C_TO_STRING_PRETTY)); } else { vty_out(vty, "\n"); } return CMD_SUCCESS; } static void bgp_show_all_instances_neighbors_vty(struct vty *vty, enum show_type type, const char *ip_str, bool use_json) { struct listnode *node, *nnode; struct bgp *bgp; union sockunion su; json_object *json = NULL; int ret, is_first = 1; bool nbr_output = false; if (use_json) vty_out(vty, "{\n"); for (ALL_LIST_ELEMENTS(bm->bgp, node, nnode, bgp)) { nbr_output = true; if (use_json) { if (!(json = json_object_new_object())) { flog_err( EC_BGP_JSON_MEM_ERROR, "Unable to allocate memory for JSON object"); vty_out(vty, "{\"error\": {\"message:\": \"Unable to allocate memory for JSON object\"}}}\n"); return; } json_object_int_add(json, "vrfId", (bgp->vrf_id == VRF_UNKNOWN) ? -1 : (int64_t)bgp->vrf_id); json_object_string_add( json, "vrfName", (bgp->inst_type == BGP_INSTANCE_TYPE_DEFAULT) ? VRF_DEFAULT_NAME : bgp->name); if (!is_first) vty_out(vty, ",\n"); else is_first = 0; vty_out(vty, "\"%s\":", (bgp->inst_type == BGP_INSTANCE_TYPE_DEFAULT) ? VRF_DEFAULT_NAME : bgp->name); } else { vty_out(vty, "\nInstance %s:\n", (bgp->inst_type == BGP_INSTANCE_TYPE_DEFAULT) ? VRF_DEFAULT_NAME : bgp->name); } if (type == show_peer || type == show_ipv4_peer || type == show_ipv6_peer) { ret = str2sockunion(ip_str, &su); if (ret < 0) bgp_show_neighbor(vty, bgp, type, NULL, ip_str, use_json, json); else bgp_show_neighbor(vty, bgp, type, &su, NULL, use_json, json); } else { bgp_show_neighbor(vty, bgp, type, NULL, NULL, use_json, json); } json_object_free(json); } if (use_json) { vty_out(vty, "}\n"); json_object_free(json); } else if (!nbr_output) vty_out(vty, "%% BGP instance not found\n"); } static int bgp_show_neighbor_vty(struct vty *vty, const char *name, enum show_type type, const char *ip_str, bool use_json) { int ret; struct bgp *bgp; union sockunion su; json_object *json = NULL; if (name) { if (strmatch(name, "all")) { bgp_show_all_instances_neighbors_vty(vty, type, ip_str, use_json); return CMD_SUCCESS; } else { bgp = bgp_lookup_by_name(name); if (!bgp) { if (use_json) { json = json_object_new_object(); vty_out(vty, "%s\n", json_object_to_json_string_ext( json, JSON_C_TO_STRING_PRETTY)); json_object_free(json); } else vty_out(vty, "%% BGP instance not found\n"); return CMD_WARNING; } } } else { bgp = bgp_get_default(); } if (bgp) { json = json_object_new_object(); if (ip_str) { ret = str2sockunion(ip_str, &su); if (ret < 0) bgp_show_neighbor(vty, bgp, type, NULL, ip_str, use_json, json); else bgp_show_neighbor(vty, bgp, type, &su, NULL, use_json, json); } else { bgp_show_neighbor(vty, bgp, type, NULL, NULL, use_json, json); } json_object_free(json); } else { if (use_json) vty_out(vty, "{}\n"); else vty_out(vty, "%% BGP instance not found\n"); } return CMD_SUCCESS; } /* "show [ip] bgp neighbors" commands. */ DEFUN (show_ip_bgp_neighbors, show_ip_bgp_neighbors_cmd, "show [ip] bgp [ VIEWVRFNAME] [] neighbors [] [json]", SHOW_STR IP_STR BGP_STR BGP_INSTANCE_HELP_STR "Address Family\n" "Address Family\n" "Detailed information on TCP and BGP neighbor connections\n" "Neighbor to display information about\n" "Neighbor to display information about\n" "Neighbor on BGP configured interface\n" JSON_STR) { char *vrf = NULL; char *sh_arg = NULL; enum show_type sh_type; afi_t afi = AFI_MAX; bool uj = use_json(argc, argv); int idx = 0; /* [ VIEWVRFNAME] */ if (argv_find(argv, argc, "vrf", &idx)) { vrf = argv[idx + 1]->arg; if (vrf && strmatch(vrf, VRF_DEFAULT_NAME)) vrf = NULL; } else if (argv_find(argv, argc, "view", &idx)) /* [ VIEWVRFNAME] */ vrf = argv[idx + 1]->arg; idx++; if (argv_find(argv, argc, "ipv4", &idx)) { sh_type = show_ipv4_all; afi = AFI_IP; } else if (argv_find(argv, argc, "ipv6", &idx)) { sh_type = show_ipv6_all; afi = AFI_IP6; } else { sh_type = show_all; } if (argv_find(argv, argc, "A.B.C.D", &idx) || argv_find(argv, argc, "X:X::X:X", &idx) || argv_find(argv, argc, "WORD", &idx)) { sh_type = show_peer; sh_arg = argv[idx]->arg; } if (sh_type == show_peer && afi == AFI_IP) { sh_type = show_ipv4_peer; } else if (sh_type == show_peer && afi == AFI_IP6) { sh_type = show_ipv6_peer; } return bgp_show_neighbor_vty(vty, vrf, sh_type, sh_arg, uj); } /* Show BGP's AS paths internal data. There are both `show [ip] bgp paths' and `show ip mbgp paths'. Those functions results are the same.*/ DEFUN (show_ip_bgp_paths, show_ip_bgp_paths_cmd, "show [ip] bgp ["BGP_SAFI_CMD_STR"] paths", SHOW_STR IP_STR BGP_STR BGP_SAFI_HELP_STR "Path information\n") { vty_out(vty, "Address Refcnt Path\n"); aspath_print_all_vty(vty); return CMD_SUCCESS; } #include "hash.h" static void community_show_all_iterator(struct hash_bucket *bucket, struct vty *vty) { struct community *com; com = (struct community *)bucket->data; vty_out(vty, "[%p] (%ld) %s\n", (void *)com, com->refcnt, community_str(com, false)); } /* Show BGP's community internal data. */ DEFUN (show_ip_bgp_community_info, show_ip_bgp_community_info_cmd, "show [ip] bgp community-info", SHOW_STR IP_STR BGP_STR "List all bgp community information\n") { vty_out(vty, "Address Refcnt Community\n"); hash_iterate(community_hash(), (void (*)(struct hash_bucket *, void *))community_show_all_iterator, vty); return CMD_SUCCESS; } static void lcommunity_show_all_iterator(struct hash_bucket *bucket, struct vty *vty) { struct lcommunity *lcom; lcom = (struct lcommunity *)bucket->data; vty_out(vty, "[%p] (%ld) %s\n", (void *)lcom, lcom->refcnt, lcommunity_str(lcom, false)); } /* Show BGP's community internal data. */ DEFUN (show_ip_bgp_lcommunity_info, show_ip_bgp_lcommunity_info_cmd, "show ip bgp large-community-info", SHOW_STR IP_STR BGP_STR "List all bgp large-community information\n") { vty_out(vty, "Address Refcnt Large-community\n"); hash_iterate(lcommunity_hash(), (void (*)(struct hash_bucket *, void *))lcommunity_show_all_iterator, vty); return CMD_SUCCESS; } DEFUN (show_ip_bgp_attr_info, show_ip_bgp_attr_info_cmd, "show [ip] bgp attribute-info", SHOW_STR IP_STR BGP_STR "List all bgp attribute information\n") { attr_show_all(vty); return CMD_SUCCESS; } static int bgp_show_route_leak_vty(struct vty *vty, const char *name, afi_t afi, safi_t safi, bool use_json, json_object *json) { struct bgp *bgp; struct listnode *node; char *vname; char buf1[INET6_ADDRSTRLEN]; char *ecom_str; vpn_policy_direction_t dir; if (json) { json_object *json_import_vrfs = NULL; json_object *json_export_vrfs = NULL; bgp = name ? bgp_lookup_by_name(name) : bgp_get_default(); if (!bgp) { vty_out(vty, "%s\n", json_object_to_json_string_ext( json, JSON_C_TO_STRING_PRETTY)); json_object_free(json); return CMD_WARNING; } /* Provide context for the block */ json_object_string_add(json, "vrf", name ? name : "default"); json_object_string_add(json, "afiSafi", get_afi_safi_str(afi, safi, true)); if (!CHECK_FLAG(bgp->af_flags[afi][safi], BGP_CONFIG_VRF_TO_VRF_IMPORT)) { json_object_string_add(json, "importFromVrfs", "none"); json_object_string_add(json, "importRts", "none"); } else { json_import_vrfs = json_object_new_array(); for (ALL_LIST_ELEMENTS_RO( bgp->vpn_policy[afi].import_vrf, node, vname)) json_object_array_add(json_import_vrfs, json_object_new_string(vname)); json_object_object_add(json, "importFromVrfs", json_import_vrfs); dir = BGP_VPN_POLICY_DIR_FROMVPN; if (bgp->vpn_policy[afi].rtlist[dir]) { ecom_str = ecommunity_ecom2str( bgp->vpn_policy[afi].rtlist[dir], ECOMMUNITY_FORMAT_ROUTE_MAP, 0); json_object_string_add(json, "importRts", ecom_str); XFREE(MTYPE_ECOMMUNITY_STR, ecom_str); } else json_object_string_add(json, "importRts", "none"); } if (!CHECK_FLAG(bgp->af_flags[afi][safi], BGP_CONFIG_VRF_TO_VRF_EXPORT)) { json_object_string_add(json, "exportToVrfs", "none"); json_object_string_add(json, "routeDistinguisher", "none"); json_object_string_add(json, "exportRts", "none"); } else { json_export_vrfs = json_object_new_array(); for (ALL_LIST_ELEMENTS_RO( bgp->vpn_policy[afi].export_vrf, node, vname)) json_object_array_add(json_export_vrfs, json_object_new_string(vname)); json_object_object_add(json, "exportToVrfs", json_export_vrfs); json_object_string_add(json, "routeDistinguisher", prefix_rd2str(&bgp->vpn_policy[afi].tovpn_rd, buf1, RD_ADDRSTRLEN)); dir = BGP_VPN_POLICY_DIR_TOVPN; if (bgp->vpn_policy[afi].rtlist[dir]) { ecom_str = ecommunity_ecom2str( bgp->vpn_policy[afi].rtlist[dir], ECOMMUNITY_FORMAT_ROUTE_MAP, 0); json_object_string_add(json, "exportRts", ecom_str); XFREE(MTYPE_ECOMMUNITY_STR, ecom_str); } else json_object_string_add(json, "exportRts", "none"); } if (use_json) { vty_out(vty, "%s\n", json_object_to_json_string_ext(json, JSON_C_TO_STRING_PRETTY)); json_object_free(json); } } else { bgp = name ? bgp_lookup_by_name(name) : bgp_get_default(); if (!bgp) { vty_out(vty, "%% No such BGP instance exist\n"); return CMD_WARNING; } if (!CHECK_FLAG(bgp->af_flags[afi][safi], BGP_CONFIG_VRF_TO_VRF_IMPORT)) vty_out(vty, "This VRF is not importing %s routes from any other VRF\n", get_afi_safi_str(afi, safi, false)); else { vty_out(vty, "This VRF is importing %s routes from the following VRFs:\n", get_afi_safi_str(afi, safi, false)); for (ALL_LIST_ELEMENTS_RO( bgp->vpn_policy[afi].import_vrf, node, vname)) vty_out(vty, " %s\n", vname); dir = BGP_VPN_POLICY_DIR_FROMVPN; ecom_str = NULL; if (bgp->vpn_policy[afi].rtlist[dir]) { ecom_str = ecommunity_ecom2str( bgp->vpn_policy[afi].rtlist[dir], ECOMMUNITY_FORMAT_ROUTE_MAP, 0); vty_out(vty, "Import RT(s): %s\n", ecom_str); XFREE(MTYPE_ECOMMUNITY_STR, ecom_str); } else vty_out(vty, "Import RT(s):\n"); } if (!CHECK_FLAG(bgp->af_flags[afi][safi], BGP_CONFIG_VRF_TO_VRF_EXPORT)) vty_out(vty, "This VRF is not exporting %s routes to any other VRF\n", get_afi_safi_str(afi, safi, false)); else { vty_out(vty, "This VRF is exporting %s routes to the following VRFs:\n", get_afi_safi_str(afi, safi, false)); for (ALL_LIST_ELEMENTS_RO( bgp->vpn_policy[afi].export_vrf, node, vname)) vty_out(vty, " %s\n", vname); vty_out(vty, "RD: %s\n", prefix_rd2str(&bgp->vpn_policy[afi].tovpn_rd, buf1, RD_ADDRSTRLEN)); dir = BGP_VPN_POLICY_DIR_TOVPN; if (bgp->vpn_policy[afi].rtlist[dir]) { ecom_str = ecommunity_ecom2str( bgp->vpn_policy[afi].rtlist[dir], ECOMMUNITY_FORMAT_ROUTE_MAP, 0); vty_out(vty, "Export RT: %s\n", ecom_str); XFREE(MTYPE_ECOMMUNITY_STR, ecom_str); } else vty_out(vty, "Import RT(s):\n"); } } return CMD_SUCCESS; } static int bgp_show_all_instance_route_leak_vty(struct vty *vty, afi_t afi, safi_t safi, bool use_json) { struct listnode *node, *nnode; struct bgp *bgp; char *vrf_name = NULL; json_object *json = NULL; json_object *json_vrf = NULL; json_object *json_vrfs = NULL; if (use_json) { json = json_object_new_object(); json_vrfs = json_object_new_object(); } for (ALL_LIST_ELEMENTS(bm->bgp, node, nnode, bgp)) { if (bgp->inst_type != BGP_INSTANCE_TYPE_DEFAULT) vrf_name = bgp->name; if (use_json) { json_vrf = json_object_new_object(); } else { vty_out(vty, "\nInstance %s:\n", (bgp->inst_type == BGP_INSTANCE_TYPE_DEFAULT) ? VRF_DEFAULT_NAME : bgp->name); } bgp_show_route_leak_vty(vty, vrf_name, afi, safi, 0, json_vrf); if (use_json) { if (bgp->inst_type == BGP_INSTANCE_TYPE_DEFAULT) json_object_object_add(json_vrfs, VRF_DEFAULT_NAME, json_vrf); else json_object_object_add(json_vrfs, vrf_name, json_vrf); } } if (use_json) { json_object_object_add(json, "vrfs", json_vrfs); vty_out(vty, "%s\n", json_object_to_json_string_ext(json, JSON_C_TO_STRING_PRETTY)); json_object_free(json); } return CMD_SUCCESS; } /* "show [ip] bgp route-leak" command. */ DEFUN (show_ip_bgp_route_leak, show_ip_bgp_route_leak_cmd, "show [ip] bgp [ VIEWVRFNAME] ["BGP_AFI_CMD_STR" ["BGP_SAFI_CMD_STR"]] route-leak [json]", SHOW_STR IP_STR BGP_STR BGP_INSTANCE_HELP_STR BGP_AFI_HELP_STR BGP_SAFI_HELP_STR "Route leaking information\n" JSON_STR) { char *vrf = NULL; afi_t afi = AFI_MAX; safi_t safi = SAFI_MAX; bool uj = use_json(argc, argv); int idx = 0; json_object *json = NULL; /* show [ip] bgp */ if (argv_find(argv, argc, "ip", &idx)) { afi = AFI_IP; safi = SAFI_UNICAST; } /* [vrf VIEWVRFNAME] */ if (argv_find(argv, argc, "view", &idx)) { vty_out(vty, "%% This command is not applicable to BGP views\n"); return CMD_WARNING; } if (argv_find(argv, argc, "vrf", &idx)) { vrf = argv[idx + 1]->arg; if (vrf && strmatch(vrf, VRF_DEFAULT_NAME)) vrf = NULL; } /* ["BGP_AFI_CMD_STR" ["BGP_SAFI_CMD_STR"]] */ if (argv_find_and_parse_afi(argv, argc, &idx, &afi)) { argv_find_and_parse_safi(argv, argc, &idx, &safi); } if (!((afi == AFI_IP || afi == AFI_IP6) && safi == SAFI_UNICAST)) { vty_out(vty, "%% This command is applicable only for unicast ipv4|ipv6\n"); return CMD_WARNING; } if (vrf && strmatch(vrf, "all")) return bgp_show_all_instance_route_leak_vty(vty, afi, safi, uj); if (uj) json = json_object_new_object(); return bgp_show_route_leak_vty(vty, vrf, afi, safi, uj, json); } static void bgp_show_all_instances_updgrps_vty(struct vty *vty, afi_t afi, safi_t safi) { struct listnode *node, *nnode; struct bgp *bgp; for (ALL_LIST_ELEMENTS(bm->bgp, node, nnode, bgp)) { vty_out(vty, "\nInstance %s:\n", (bgp->inst_type == BGP_INSTANCE_TYPE_DEFAULT) ? VRF_DEFAULT_NAME : bgp->name); update_group_show(bgp, afi, safi, vty, 0); } } static int bgp_show_update_groups(struct vty *vty, const char *name, int afi, int safi, uint64_t subgrp_id) { struct bgp *bgp; if (name) { if (strmatch(name, "all")) { bgp_show_all_instances_updgrps_vty(vty, afi, safi); return CMD_SUCCESS; } else { bgp = bgp_lookup_by_name(name); } } else { bgp = bgp_get_default(); } if (bgp) update_group_show(bgp, afi, safi, vty, subgrp_id); return CMD_SUCCESS; } DEFUN (show_ip_bgp_updgrps, show_ip_bgp_updgrps_cmd, "show [ip] bgp [ VIEWVRFNAME] ["BGP_AFI_CMD_STR" ["BGP_SAFI_WITH_LABEL_CMD_STR"]] update-groups [SUBGROUP-ID]", SHOW_STR IP_STR BGP_STR BGP_INSTANCE_HELP_STR BGP_AFI_HELP_STR BGP_SAFI_WITH_LABEL_HELP_STR "Detailed info about dynamic update groups\n" "Specific subgroup to display detailed info for\n") { char *vrf = NULL; afi_t afi = AFI_IP6; safi_t safi = SAFI_UNICAST; uint64_t subgrp_id = 0; int idx = 0; /* show [ip] bgp */ if (argv_find(argv, argc, "ip", &idx)) afi = AFI_IP; /* [ VIEWVRFNAME] */ if (argv_find(argv, argc, "vrf", &idx)) { vrf = argv[idx + 1]->arg; if (vrf && strmatch(vrf, VRF_DEFAULT_NAME)) vrf = NULL; } else if (argv_find(argv, argc, "view", &idx)) /* [ VIEWVRFNAME] */ vrf = argv[idx + 1]->arg; /* ["BGP_AFI_CMD_STR" ["BGP_SAFI_CMD_STR"]] */ if (argv_find_and_parse_afi(argv, argc, &idx, &afi)) { argv_find_and_parse_safi(argv, argc, &idx, &safi); } /* get subgroup id, if provided */ idx = argc - 1; if (argv[idx]->type == VARIABLE_TKN) subgrp_id = strtoull(argv[idx]->arg, NULL, 10); return (bgp_show_update_groups(vty, vrf, afi, safi, subgrp_id)); } DEFUN (show_bgp_instance_all_ipv6_updgrps, show_bgp_instance_all_ipv6_updgrps_cmd, "show [ip] bgp all update-groups", SHOW_STR IP_STR BGP_STR BGP_INSTANCE_ALL_HELP_STR "Detailed info about dynamic update groups\n") { bgp_show_all_instances_updgrps_vty(vty, AFI_IP6, SAFI_UNICAST); return CMD_SUCCESS; } DEFUN (show_bgp_l2vpn_evpn_updgrps, show_bgp_l2vpn_evpn_updgrps_cmd, "show [ip] bgp l2vpn evpn update-groups", SHOW_STR IP_STR BGP_STR "l2vpn address family\n" "evpn sub-address family\n" "Detailed info about dynamic update groups\n") { char *vrf = NULL; uint64_t subgrp_id = 0; bgp_show_update_groups(vty, vrf, AFI_L2VPN, SAFI_EVPN, subgrp_id); return CMD_SUCCESS; } DEFUN (show_bgp_updgrps_stats, show_bgp_updgrps_stats_cmd, "show [ip] bgp update-groups statistics", SHOW_STR IP_STR BGP_STR "Detailed info about dynamic update groups\n" "Statistics\n") { struct bgp *bgp; bgp = bgp_get_default(); if (bgp) update_group_show_stats(bgp, vty); return CMD_SUCCESS; } DEFUN (show_bgp_instance_updgrps_stats, show_bgp_instance_updgrps_stats_cmd, "show [ip] bgp VIEWVRFNAME update-groups statistics", SHOW_STR IP_STR BGP_STR BGP_INSTANCE_HELP_STR "Detailed info about dynamic update groups\n" "Statistics\n") { int idx_word = 3; struct bgp *bgp; bgp = bgp_lookup_by_name(argv[idx_word]->arg); if (bgp) update_group_show_stats(bgp, vty); return CMD_SUCCESS; } static void show_bgp_updgrps_adj_info_aux(struct vty *vty, const char *name, afi_t afi, safi_t safi, const char *what, uint64_t subgrp_id) { struct bgp *bgp; if (name) bgp = bgp_lookup_by_name(name); else bgp = bgp_get_default(); if (bgp) { if (!strcmp(what, "advertise-queue")) update_group_show_adj_queue(bgp, afi, safi, vty, subgrp_id); else if (!strcmp(what, "advertised-routes")) update_group_show_advertised(bgp, afi, safi, vty, subgrp_id); else if (!strcmp(what, "packet-queue")) update_group_show_packet_queue(bgp, afi, safi, vty, subgrp_id); } } DEFPY(show_ip_bgp_instance_updgrps_adj_s, show_ip_bgp_instance_updgrps_adj_s_cmd, "show [ip]$ip bgp [ VIEWVRFNAME$vrf] [$afi $safi] update-groups [SUBGROUP-ID]$sgid $rtq", SHOW_STR IP_STR BGP_STR BGP_INSTANCE_HELP_STR BGP_AFI_HELP_STR BGP_SAFI_HELP_STR "Detailed info about dynamic update groups\n" "Specific subgroup to display info for\n" "Advertisement queue\n" "Announced routes\n" "Packet queue\n") { uint64_t subgrp_id = 0; afi_t afiz; safi_t safiz; if (sgid) subgrp_id = strtoull(sgid, NULL, 10); if (!ip && !afi) afiz = AFI_IP6; if (!ip && afi) afiz = bgp_vty_afi_from_str(afi); if (ip && !afi) afiz = AFI_IP; if (ip && afi) { afiz = bgp_vty_afi_from_str(afi); if (afiz != AFI_IP) vty_out(vty, "%% Cannot specify both 'ip' and 'ipv6'\n"); return CMD_WARNING; } safiz = safi ? bgp_vty_safi_from_str(safi) : SAFI_UNICAST; show_bgp_updgrps_adj_info_aux(vty, vrf, afiz, safiz, rtq, subgrp_id); return CMD_SUCCESS; } static int bgp_show_one_peer_group(struct vty *vty, struct peer_group *group) { struct listnode *node, *nnode; struct prefix *range; struct peer *conf; struct peer *peer; char buf[PREFIX2STR_BUFFER]; afi_t afi; safi_t safi; const char *peer_status; const char *af_str; int lr_count; int dynamic; int af_cfgd; conf = group->conf; if (conf->as_type == AS_SPECIFIED || conf->as_type == AS_EXTERNAL) { vty_out(vty, "\nBGP peer-group %s, remote AS %" PRIu32 "\n", group->name, conf->as); } else if (conf->as_type == AS_INTERNAL) { vty_out(vty, "\nBGP peer-group %s, remote AS %" PRIu32 "\n", group->name, group->bgp->as); } else { vty_out(vty, "\nBGP peer-group %s\n", group->name); } if ((group->bgp->as == conf->as) || (conf->as_type == AS_INTERNAL)) vty_out(vty, " Peer-group type is internal\n"); else vty_out(vty, " Peer-group type is external\n"); /* Display AFs configured. */ vty_out(vty, " Configured address-families:"); FOREACH_AFI_SAFI (afi, safi) { if (conf->afc[afi][safi]) { af_cfgd = 1; vty_out(vty, " %s;", get_afi_safi_str(afi, safi, false)); } } if (!af_cfgd) vty_out(vty, " none\n"); else vty_out(vty, "\n"); /* Display listen ranges (for dynamic neighbors), if any */ for (afi = AFI_IP; afi < AFI_MAX; afi++) { if (afi == AFI_IP) af_str = "IPv4"; else if (afi == AFI_IP6) af_str = "IPv6"; else af_str = "???"; lr_count = listcount(group->listen_range[afi]); if (lr_count) { vty_out(vty, " %d %s listen range(s)\n", lr_count, af_str); for (ALL_LIST_ELEMENTS(group->listen_range[afi], node, nnode, range)) { prefix2str(range, buf, sizeof(buf)); vty_out(vty, " %s\n", buf); } } } /* Display group members and their status */ if (listcount(group->peer)) { vty_out(vty, " Peer-group members:\n"); for (ALL_LIST_ELEMENTS(group->peer, node, nnode, peer)) { if (CHECK_FLAG(peer->flags, PEER_FLAG_SHUTDOWN)) peer_status = "Idle (Admin)"; else if (CHECK_FLAG(peer->sflags, PEER_STATUS_PREFIX_OVERFLOW)) peer_status = "Idle (PfxCt)"; else peer_status = lookup_msg(bgp_status_msg, peer->status, NULL); dynamic = peer_dynamic_neighbor(peer); vty_out(vty, " %s %s %s \n", peer->host, dynamic ? "(dynamic)" : "", peer_status); } } return CMD_SUCCESS; } static int bgp_show_peer_group_vty(struct vty *vty, const char *name, const char *group_name) { struct bgp *bgp; struct listnode *node, *nnode; struct peer_group *group; bool found = false; bgp = name ? bgp_lookup_by_name(name) : bgp_get_default(); if (!bgp) { vty_out(vty, "%% BGP instance not found\n"); return CMD_WARNING; } for (ALL_LIST_ELEMENTS(bgp->group, node, nnode, group)) { if (group_name) { if (strmatch(group->name, group_name)) { bgp_show_one_peer_group(vty, group); found = true; break; } } else { bgp_show_one_peer_group(vty, group); } } if (group_name && !found) vty_out(vty, "%% No such peer-group\n"); return CMD_SUCCESS; } DEFUN (show_ip_bgp_peer_groups, show_ip_bgp_peer_groups_cmd, "show [ip] bgp [ VIEWVRFNAME] peer-group [PGNAME]", SHOW_STR IP_STR BGP_STR BGP_INSTANCE_HELP_STR "Detailed information on BGP peer groups\n" "Peer group name\n") { char *vrf, *pg; int idx = 0; vrf = argv_find(argv, argc, "VIEWVRFNAME", &idx) ? argv[idx]->arg : NULL; pg = argv_find(argv, argc, "PGNAME", &idx) ? argv[idx]->arg : NULL; return bgp_show_peer_group_vty(vty, vrf, pg); } /* Redistribute VTY commands. */ DEFUN (bgp_redistribute_ipv4, bgp_redistribute_ipv4_cmd, "redistribute " FRR_IP_REDIST_STR_BGPD, "Redistribute information from another routing protocol\n" FRR_IP_REDIST_HELP_STR_BGPD) { VTY_DECLVAR_CONTEXT(bgp, bgp); int idx_protocol = 1; int type; type = proto_redistnum(AFI_IP, argv[idx_protocol]->text); if (type < 0) { vty_out(vty, "%% Invalid route type\n"); return CMD_WARNING_CONFIG_FAILED; } bgp_redist_add(bgp, AFI_IP, type, 0); return bgp_redistribute_set(bgp, AFI_IP, type, 0, false); } ALIAS_HIDDEN( bgp_redistribute_ipv4, bgp_redistribute_ipv4_hidden_cmd, "redistribute " FRR_IP_REDIST_STR_BGPD, "Redistribute information from another routing protocol\n" FRR_IP_REDIST_HELP_STR_BGPD) DEFUN (bgp_redistribute_ipv4_rmap, bgp_redistribute_ipv4_rmap_cmd, "redistribute " FRR_IP_REDIST_STR_BGPD " route-map WORD", "Redistribute information from another routing protocol\n" FRR_IP_REDIST_HELP_STR_BGPD "Route map reference\n" "Pointer to route-map entries\n") { VTY_DECLVAR_CONTEXT(bgp, bgp); int idx_protocol = 1; int idx_word = 3; int type; struct bgp_redist *red; bool changed; struct route_map *route_map = route_map_lookup_warn_noexist( vty, argv[idx_word]->arg); type = proto_redistnum(AFI_IP, argv[idx_protocol]->text); if (type < 0) { vty_out(vty, "%% Invalid route type\n"); return CMD_WARNING_CONFIG_FAILED; } red = bgp_redist_add(bgp, AFI_IP, type, 0); changed = bgp_redistribute_rmap_set(red, argv[idx_word]->arg, route_map); return bgp_redistribute_set(bgp, AFI_IP, type, 0, changed); } ALIAS_HIDDEN( bgp_redistribute_ipv4_rmap, bgp_redistribute_ipv4_rmap_hidden_cmd, "redistribute " FRR_IP_REDIST_STR_BGPD " route-map WORD", "Redistribute information from another routing protocol\n" FRR_IP_REDIST_HELP_STR_BGPD "Route map reference\n" "Pointer to route-map entries\n") DEFUN (bgp_redistribute_ipv4_metric, bgp_redistribute_ipv4_metric_cmd, "redistribute " FRR_IP_REDIST_STR_BGPD " metric (0-4294967295)", "Redistribute information from another routing protocol\n" FRR_IP_REDIST_HELP_STR_BGPD "Metric for redistributed routes\n" "Default metric\n") { VTY_DECLVAR_CONTEXT(bgp, bgp); int idx_protocol = 1; int idx_number = 3; int type; uint32_t metric; struct bgp_redist *red; bool changed; type = proto_redistnum(AFI_IP, argv[idx_protocol]->text); if (type < 0) { vty_out(vty, "%% Invalid route type\n"); return CMD_WARNING_CONFIG_FAILED; } metric = strtoul(argv[idx_number]->arg, NULL, 10); red = bgp_redist_add(bgp, AFI_IP, type, 0); changed = bgp_redistribute_metric_set(bgp, red, AFI_IP, type, metric); return bgp_redistribute_set(bgp, AFI_IP, type, 0, changed); } ALIAS_HIDDEN( bgp_redistribute_ipv4_metric, bgp_redistribute_ipv4_metric_hidden_cmd, "redistribute " FRR_IP_REDIST_STR_BGPD " metric (0-4294967295)", "Redistribute information from another routing protocol\n" FRR_IP_REDIST_HELP_STR_BGPD "Metric for redistributed routes\n" "Default metric\n") DEFUN (bgp_redistribute_ipv4_rmap_metric, bgp_redistribute_ipv4_rmap_metric_cmd, "redistribute " FRR_IP_REDIST_STR_BGPD " route-map WORD metric (0-4294967295)", "Redistribute information from another routing protocol\n" FRR_IP_REDIST_HELP_STR_BGPD "Route map reference\n" "Pointer to route-map entries\n" "Metric for redistributed routes\n" "Default metric\n") { VTY_DECLVAR_CONTEXT(bgp, bgp); int idx_protocol = 1; int idx_word = 3; int idx_number = 5; int type; uint32_t metric; struct bgp_redist *red; bool changed; struct route_map *route_map = route_map_lookup_warn_noexist(vty, argv[idx_word]->arg); type = proto_redistnum(AFI_IP, argv[idx_protocol]->text); if (type < 0) { vty_out(vty, "%% Invalid route type\n"); return CMD_WARNING_CONFIG_FAILED; } metric = strtoul(argv[idx_number]->arg, NULL, 10); red = bgp_redist_add(bgp, AFI_IP, type, 0); changed = bgp_redistribute_rmap_set(red, argv[idx_word]->arg, route_map); changed |= bgp_redistribute_metric_set(bgp, red, AFI_IP, type, metric); return bgp_redistribute_set(bgp, AFI_IP, type, 0, changed); } ALIAS_HIDDEN( bgp_redistribute_ipv4_rmap_metric, bgp_redistribute_ipv4_rmap_metric_hidden_cmd, "redistribute " FRR_IP_REDIST_STR_BGPD " route-map WORD metric (0-4294967295)", "Redistribute information from another routing protocol\n" FRR_IP_REDIST_HELP_STR_BGPD "Route map reference\n" "Pointer to route-map entries\n" "Metric for redistributed routes\n" "Default metric\n") DEFUN (bgp_redistribute_ipv4_metric_rmap, bgp_redistribute_ipv4_metric_rmap_cmd, "redistribute " FRR_IP_REDIST_STR_BGPD " metric (0-4294967295) route-map WORD", "Redistribute information from another routing protocol\n" FRR_IP_REDIST_HELP_STR_BGPD "Metric for redistributed routes\n" "Default metric\n" "Route map reference\n" "Pointer to route-map entries\n") { VTY_DECLVAR_CONTEXT(bgp, bgp); int idx_protocol = 1; int idx_number = 3; int idx_word = 5; int type; uint32_t metric; struct bgp_redist *red; bool changed; struct route_map *route_map = route_map_lookup_warn_noexist(vty, argv[idx_word]->arg); type = proto_redistnum(AFI_IP, argv[idx_protocol]->text); if (type < 0) { vty_out(vty, "%% Invalid route type\n"); return CMD_WARNING_CONFIG_FAILED; } metric = strtoul(argv[idx_number]->arg, NULL, 10); red = bgp_redist_add(bgp, AFI_IP, type, 0); changed = bgp_redistribute_metric_set(bgp, red, AFI_IP, type, metric); changed |= bgp_redistribute_rmap_set(red, argv[idx_word]->arg, route_map); return bgp_redistribute_set(bgp, AFI_IP, type, 0, changed); } ALIAS_HIDDEN( bgp_redistribute_ipv4_metric_rmap, bgp_redistribute_ipv4_metric_rmap_hidden_cmd, "redistribute " FRR_IP_REDIST_STR_BGPD " metric (0-4294967295) route-map WORD", "Redistribute information from another routing protocol\n" FRR_IP_REDIST_HELP_STR_BGPD "Metric for redistributed routes\n" "Default metric\n" "Route map reference\n" "Pointer to route-map entries\n") DEFUN (bgp_redistribute_ipv4_ospf, bgp_redistribute_ipv4_ospf_cmd, "redistribute (1-65535)", "Redistribute information from another routing protocol\n" "Open Shortest Path First (OSPFv2)\n" "Non-main Kernel Routing Table\n" "Instance ID/Table ID\n") { VTY_DECLVAR_CONTEXT(bgp, bgp); int idx_ospf_table = 1; int idx_number = 2; unsigned short instance; unsigned short protocol; instance = strtoul(argv[idx_number]->arg, NULL, 10); if (strncmp(argv[idx_ospf_table]->arg, "o", 1) == 0) protocol = ZEBRA_ROUTE_OSPF; else protocol = ZEBRA_ROUTE_TABLE; bgp_redist_add(bgp, AFI_IP, protocol, instance); return bgp_redistribute_set(bgp, AFI_IP, protocol, instance, false); } ALIAS_HIDDEN(bgp_redistribute_ipv4_ospf, bgp_redistribute_ipv4_ospf_hidden_cmd, "redistribute (1-65535)", "Redistribute information from another routing protocol\n" "Open Shortest Path First (OSPFv2)\n" "Non-main Kernel Routing Table\n" "Instance ID/Table ID\n") DEFUN (bgp_redistribute_ipv4_ospf_rmap, bgp_redistribute_ipv4_ospf_rmap_cmd, "redistribute (1-65535) route-map WORD", "Redistribute information from another routing protocol\n" "Open Shortest Path First (OSPFv2)\n" "Non-main Kernel Routing Table\n" "Instance ID/Table ID\n" "Route map reference\n" "Pointer to route-map entries\n") { VTY_DECLVAR_CONTEXT(bgp, bgp); int idx_ospf_table = 1; int idx_number = 2; int idx_word = 4; struct bgp_redist *red; unsigned short instance; int protocol; bool changed; struct route_map *route_map = route_map_lookup_warn_noexist(vty, argv[idx_word]->arg); if (strncmp(argv[idx_ospf_table]->arg, "o", 1) == 0) protocol = ZEBRA_ROUTE_OSPF; else protocol = ZEBRA_ROUTE_TABLE; instance = strtoul(argv[idx_number]->arg, NULL, 10); red = bgp_redist_add(bgp, AFI_IP, protocol, instance); changed = bgp_redistribute_rmap_set(red, argv[idx_word]->arg, route_map); return bgp_redistribute_set(bgp, AFI_IP, protocol, instance, changed); } ALIAS_HIDDEN(bgp_redistribute_ipv4_ospf_rmap, bgp_redistribute_ipv4_ospf_rmap_hidden_cmd, "redistribute (1-65535) route-map WORD", "Redistribute information from another routing protocol\n" "Open Shortest Path First (OSPFv2)\n" "Non-main Kernel Routing Table\n" "Instance ID/Table ID\n" "Route map reference\n" "Pointer to route-map entries\n") DEFUN (bgp_redistribute_ipv4_ospf_metric, bgp_redistribute_ipv4_ospf_metric_cmd, "redistribute (1-65535) metric (0-4294967295)", "Redistribute information from another routing protocol\n" "Open Shortest Path First (OSPFv2)\n" "Non-main Kernel Routing Table\n" "Instance ID/Table ID\n" "Metric for redistributed routes\n" "Default metric\n") { VTY_DECLVAR_CONTEXT(bgp, bgp); int idx_ospf_table = 1; int idx_number = 2; int idx_number_2 = 4; uint32_t metric; struct bgp_redist *red; unsigned short instance; int protocol; bool changed; if (strncmp(argv[idx_ospf_table]->arg, "o", 1) == 0) protocol = ZEBRA_ROUTE_OSPF; else protocol = ZEBRA_ROUTE_TABLE; instance = strtoul(argv[idx_number]->arg, NULL, 10); metric = strtoul(argv[idx_number_2]->arg, NULL, 10); red = bgp_redist_add(bgp, AFI_IP, protocol, instance); changed = bgp_redistribute_metric_set(bgp, red, AFI_IP, protocol, metric); return bgp_redistribute_set(bgp, AFI_IP, protocol, instance, changed); } ALIAS_HIDDEN(bgp_redistribute_ipv4_ospf_metric, bgp_redistribute_ipv4_ospf_metric_hidden_cmd, "redistribute (1-65535) metric (0-4294967295)", "Redistribute information from another routing protocol\n" "Open Shortest Path First (OSPFv2)\n" "Non-main Kernel Routing Table\n" "Instance ID/Table ID\n" "Metric for redistributed routes\n" "Default metric\n") DEFUN (bgp_redistribute_ipv4_ospf_rmap_metric, bgp_redistribute_ipv4_ospf_rmap_metric_cmd, "redistribute (1-65535) route-map WORD metric (0-4294967295)", "Redistribute information from another routing protocol\n" "Open Shortest Path First (OSPFv2)\n" "Non-main Kernel Routing Table\n" "Instance ID/Table ID\n" "Route map reference\n" "Pointer to route-map entries\n" "Metric for redistributed routes\n" "Default metric\n") { VTY_DECLVAR_CONTEXT(bgp, bgp); int idx_ospf_table = 1; int idx_number = 2; int idx_word = 4; int idx_number_2 = 6; uint32_t metric; struct bgp_redist *red; unsigned short instance; int protocol; bool changed; struct route_map *route_map = route_map_lookup_warn_noexist(vty, argv[idx_word]->arg); if (strncmp(argv[idx_ospf_table]->arg, "o", 1) == 0) protocol = ZEBRA_ROUTE_OSPF; else protocol = ZEBRA_ROUTE_TABLE; instance = strtoul(argv[idx_number]->arg, NULL, 10); metric = strtoul(argv[idx_number_2]->arg, NULL, 10); red = bgp_redist_add(bgp, AFI_IP, protocol, instance); changed = bgp_redistribute_rmap_set(red, argv[idx_word]->arg, route_map); changed |= bgp_redistribute_metric_set(bgp, red, AFI_IP, protocol, metric); return bgp_redistribute_set(bgp, AFI_IP, protocol, instance, changed); } ALIAS_HIDDEN( bgp_redistribute_ipv4_ospf_rmap_metric, bgp_redistribute_ipv4_ospf_rmap_metric_hidden_cmd, "redistribute (1-65535) route-map WORD metric (0-4294967295)", "Redistribute information from another routing protocol\n" "Open Shortest Path First (OSPFv2)\n" "Non-main Kernel Routing Table\n" "Instance ID/Table ID\n" "Route map reference\n" "Pointer to route-map entries\n" "Metric for redistributed routes\n" "Default metric\n") DEFUN (bgp_redistribute_ipv4_ospf_metric_rmap, bgp_redistribute_ipv4_ospf_metric_rmap_cmd, "redistribute (1-65535) metric (0-4294967295) route-map WORD", "Redistribute information from another routing protocol\n" "Open Shortest Path First (OSPFv2)\n" "Non-main Kernel Routing Table\n" "Instance ID/Table ID\n" "Metric for redistributed routes\n" "Default metric\n" "Route map reference\n" "Pointer to route-map entries\n") { VTY_DECLVAR_CONTEXT(bgp, bgp); int idx_ospf_table = 1; int idx_number = 2; int idx_number_2 = 4; int idx_word = 6; uint32_t metric; struct bgp_redist *red; unsigned short instance; int protocol; bool changed; struct route_map *route_map = route_map_lookup_warn_noexist(vty, argv[idx_word]->arg); if (strncmp(argv[idx_ospf_table]->arg, "o", 1) == 0) protocol = ZEBRA_ROUTE_OSPF; else protocol = ZEBRA_ROUTE_TABLE; instance = strtoul(argv[idx_number]->arg, NULL, 10); metric = strtoul(argv[idx_number_2]->arg, NULL, 10); red = bgp_redist_add(bgp, AFI_IP, protocol, instance); changed = bgp_redistribute_metric_set(bgp, red, AFI_IP, protocol, metric); changed |= bgp_redistribute_rmap_set(red, argv[idx_word]->arg, route_map); return bgp_redistribute_set(bgp, AFI_IP, protocol, instance, changed); } ALIAS_HIDDEN( bgp_redistribute_ipv4_ospf_metric_rmap, bgp_redistribute_ipv4_ospf_metric_rmap_hidden_cmd, "redistribute (1-65535) metric (0-4294967295) route-map WORD", "Redistribute information from another routing protocol\n" "Open Shortest Path First (OSPFv2)\n" "Non-main Kernel Routing Table\n" "Instance ID/Table ID\n" "Metric for redistributed routes\n" "Default metric\n" "Route map reference\n" "Pointer to route-map entries\n") DEFUN (no_bgp_redistribute_ipv4_ospf, no_bgp_redistribute_ipv4_ospf_cmd, "no redistribute (1-65535) [{metric (0-4294967295)|route-map WORD}]", NO_STR "Redistribute information from another routing protocol\n" "Open Shortest Path First (OSPFv2)\n" "Non-main Kernel Routing Table\n" "Instance ID/Table ID\n" "Metric for redistributed routes\n" "Default metric\n" "Route map reference\n" "Pointer to route-map entries\n") { VTY_DECLVAR_CONTEXT(bgp, bgp); int idx_ospf_table = 2; int idx_number = 3; unsigned short instance; int protocol; if (strncmp(argv[idx_ospf_table]->arg, "o", 1) == 0) protocol = ZEBRA_ROUTE_OSPF; else protocol = ZEBRA_ROUTE_TABLE; instance = strtoul(argv[idx_number]->arg, NULL, 10); return bgp_redistribute_unset(bgp, AFI_IP, protocol, instance); } ALIAS_HIDDEN( no_bgp_redistribute_ipv4_ospf, no_bgp_redistribute_ipv4_ospf_hidden_cmd, "no redistribute (1-65535) [{metric (0-4294967295)|route-map WORD}]", NO_STR "Redistribute information from another routing protocol\n" "Open Shortest Path First (OSPFv2)\n" "Non-main Kernel Routing Table\n" "Instance ID/Table ID\n" "Metric for redistributed routes\n" "Default metric\n" "Route map reference\n" "Pointer to route-map entries\n") DEFUN (no_bgp_redistribute_ipv4, no_bgp_redistribute_ipv4_cmd, "no redistribute " FRR_IP_REDIST_STR_BGPD " [{metric (0-4294967295)|route-map WORD}]", NO_STR "Redistribute information from another routing protocol\n" FRR_IP_REDIST_HELP_STR_BGPD "Metric for redistributed routes\n" "Default metric\n" "Route map reference\n" "Pointer to route-map entries\n") { VTY_DECLVAR_CONTEXT(bgp, bgp); int idx_protocol = 2; int type; type = proto_redistnum(AFI_IP, argv[idx_protocol]->text); if (type < 0) { vty_out(vty, "%% Invalid route type\n"); return CMD_WARNING_CONFIG_FAILED; } return bgp_redistribute_unset(bgp, AFI_IP, type, 0); } ALIAS_HIDDEN( no_bgp_redistribute_ipv4, no_bgp_redistribute_ipv4_hidden_cmd, "no redistribute " FRR_IP_REDIST_STR_BGPD " [{metric (0-4294967295)|route-map WORD}]", NO_STR "Redistribute information from another routing protocol\n" FRR_IP_REDIST_HELP_STR_BGPD "Metric for redistributed routes\n" "Default metric\n" "Route map reference\n" "Pointer to route-map entries\n") DEFUN (bgp_redistribute_ipv6, bgp_redistribute_ipv6_cmd, "redistribute " FRR_IP6_REDIST_STR_BGPD, "Redistribute information from another routing protocol\n" FRR_IP6_REDIST_HELP_STR_BGPD) { VTY_DECLVAR_CONTEXT(bgp, bgp); int idx_protocol = 1; int type; type = proto_redistnum(AFI_IP6, argv[idx_protocol]->text); if (type < 0) { vty_out(vty, "%% Invalid route type\n"); return CMD_WARNING_CONFIG_FAILED; } bgp_redist_add(bgp, AFI_IP6, type, 0); return bgp_redistribute_set(bgp, AFI_IP6, type, 0, false); } DEFUN (bgp_redistribute_ipv6_rmap, bgp_redistribute_ipv6_rmap_cmd, "redistribute " FRR_IP6_REDIST_STR_BGPD " route-map WORD", "Redistribute information from another routing protocol\n" FRR_IP6_REDIST_HELP_STR_BGPD "Route map reference\n" "Pointer to route-map entries\n") { VTY_DECLVAR_CONTEXT(bgp, bgp); int idx_protocol = 1; int idx_word = 3; int type; struct bgp_redist *red; bool changed; struct route_map *route_map = route_map_lookup_warn_noexist(vty, argv[idx_word]->arg); type = proto_redistnum(AFI_IP6, argv[idx_protocol]->text); if (type < 0) { vty_out(vty, "%% Invalid route type\n"); return CMD_WARNING_CONFIG_FAILED; } red = bgp_redist_add(bgp, AFI_IP6, type, 0); changed = bgp_redistribute_rmap_set(red, argv[idx_word]->arg, route_map); return bgp_redistribute_set(bgp, AFI_IP6, type, 0, changed); } DEFUN (bgp_redistribute_ipv6_metric, bgp_redistribute_ipv6_metric_cmd, "redistribute " FRR_IP6_REDIST_STR_BGPD " metric (0-4294967295)", "Redistribute information from another routing protocol\n" FRR_IP6_REDIST_HELP_STR_BGPD "Metric for redistributed routes\n" "Default metric\n") { VTY_DECLVAR_CONTEXT(bgp, bgp); int idx_protocol = 1; int idx_number = 3; int type; uint32_t metric; struct bgp_redist *red; bool changed; type = proto_redistnum(AFI_IP6, argv[idx_protocol]->text); if (type < 0) { vty_out(vty, "%% Invalid route type\n"); return CMD_WARNING_CONFIG_FAILED; } metric = strtoul(argv[idx_number]->arg, NULL, 10); red = bgp_redist_add(bgp, AFI_IP6, type, 0); changed = bgp_redistribute_metric_set(bgp, red, AFI_IP6, type, metric); return bgp_redistribute_set(bgp, AFI_IP6, type, 0, changed); } DEFUN (bgp_redistribute_ipv6_rmap_metric, bgp_redistribute_ipv6_rmap_metric_cmd, "redistribute " FRR_IP6_REDIST_STR_BGPD " route-map WORD metric (0-4294967295)", "Redistribute information from another routing protocol\n" FRR_IP6_REDIST_HELP_STR_BGPD "Route map reference\n" "Pointer to route-map entries\n" "Metric for redistributed routes\n" "Default metric\n") { VTY_DECLVAR_CONTEXT(bgp, bgp); int idx_protocol = 1; int idx_word = 3; int idx_number = 5; int type; uint32_t metric; struct bgp_redist *red; bool changed; struct route_map *route_map = route_map_lookup_warn_noexist(vty, argv[idx_word]->arg); type = proto_redistnum(AFI_IP6, argv[idx_protocol]->text); if (type < 0) { vty_out(vty, "%% Invalid route type\n"); return CMD_WARNING_CONFIG_FAILED; } metric = strtoul(argv[idx_number]->arg, NULL, 10); red = bgp_redist_add(bgp, AFI_IP6, type, 0); changed = bgp_redistribute_rmap_set(red, argv[idx_word]->arg, route_map); changed |= bgp_redistribute_metric_set(bgp, red, AFI_IP6, type, metric); return bgp_redistribute_set(bgp, AFI_IP6, type, 0, changed); } DEFUN (bgp_redistribute_ipv6_metric_rmap, bgp_redistribute_ipv6_metric_rmap_cmd, "redistribute " FRR_IP6_REDIST_STR_BGPD " metric (0-4294967295) route-map WORD", "Redistribute information from another routing protocol\n" FRR_IP6_REDIST_HELP_STR_BGPD "Metric for redistributed routes\n" "Default metric\n" "Route map reference\n" "Pointer to route-map entries\n") { VTY_DECLVAR_CONTEXT(bgp, bgp); int idx_protocol = 1; int idx_number = 3; int idx_word = 5; int type; uint32_t metric; struct bgp_redist *red; bool changed; struct route_map *route_map = route_map_lookup_warn_noexist(vty, argv[idx_word]->arg); type = proto_redistnum(AFI_IP6, argv[idx_protocol]->text); if (type < 0) { vty_out(vty, "%% Invalid route type\n"); return CMD_WARNING_CONFIG_FAILED; } metric = strtoul(argv[idx_number]->arg, NULL, 10); red = bgp_redist_add(bgp, AFI_IP6, type, 0); changed = bgp_redistribute_metric_set(bgp, red, AFI_IP6, SAFI_UNICAST, metric); changed |= bgp_redistribute_rmap_set(red, argv[idx_word]->arg, route_map); return bgp_redistribute_set(bgp, AFI_IP6, type, 0, changed); } DEFUN (no_bgp_redistribute_ipv6, no_bgp_redistribute_ipv6_cmd, "no redistribute " FRR_IP6_REDIST_STR_BGPD " [{metric (0-4294967295)|route-map WORD}]", NO_STR "Redistribute information from another routing protocol\n" FRR_IP6_REDIST_HELP_STR_BGPD "Metric for redistributed routes\n" "Default metric\n" "Route map reference\n" "Pointer to route-map entries\n") { VTY_DECLVAR_CONTEXT(bgp, bgp); int idx_protocol = 2; int type; type = proto_redistnum(AFI_IP6, argv[idx_protocol]->text); if (type < 0) { vty_out(vty, "%% Invalid route type\n"); return CMD_WARNING_CONFIG_FAILED; } return bgp_redistribute_unset(bgp, AFI_IP6, type, 0); } void bgp_config_write_redistribute(struct vty *vty, struct bgp *bgp, afi_t afi, safi_t safi) { int i; /* Unicast redistribution only. */ if (safi != SAFI_UNICAST) return; for (i = 0; i < ZEBRA_ROUTE_MAX; i++) { /* Redistribute BGP does not make sense. */ if (i != ZEBRA_ROUTE_BGP) { struct list *red_list; struct listnode *node; struct bgp_redist *red; red_list = bgp->redist[afi][i]; if (!red_list) continue; for (ALL_LIST_ELEMENTS_RO(red_list, node, red)) { /* "redistribute" configuration. */ vty_out(vty, " redistribute %s", zebra_route_string(i)); if (red->instance) vty_out(vty, " %d", red->instance); if (red->redist_metric_flag) vty_out(vty, " metric %u", red->redist_metric); if (red->rmap.name) vty_out(vty, " route-map %s", red->rmap.name); vty_out(vty, "\n"); } } } } /* This is part of the address-family block (unicast only) */ void bgp_vpn_policy_config_write_afi(struct vty *vty, struct bgp *bgp, afi_t afi) { int indent = 2; if (bgp->vpn_policy[afi].rmap_name[BGP_VPN_POLICY_DIR_FROMVPN]) { if (listcount(bgp->vpn_policy[afi].import_vrf)) vty_out(vty, "%*simport vrf route-map %s\n", indent, "", bgp->vpn_policy[afi] .rmap_name[BGP_VPN_POLICY_DIR_FROMVPN]); else vty_out(vty, "%*sroute-map vpn import %s\n", indent, "", bgp->vpn_policy[afi] .rmap_name[BGP_VPN_POLICY_DIR_FROMVPN]); } if (CHECK_FLAG(bgp->af_flags[afi][SAFI_UNICAST], BGP_CONFIG_VRF_TO_VRF_IMPORT) || CHECK_FLAG(bgp->af_flags[afi][SAFI_UNICAST], BGP_CONFIG_VRF_TO_VRF_EXPORT)) return; if (CHECK_FLAG(bgp->vpn_policy[afi].flags, BGP_VPN_POLICY_TOVPN_LABEL_AUTO)) { vty_out(vty, "%*slabel vpn export %s\n", indent, "", "auto"); } else { if (bgp->vpn_policy[afi].tovpn_label != MPLS_LABEL_NONE) { vty_out(vty, "%*slabel vpn export %u\n", indent, "", bgp->vpn_policy[afi].tovpn_label); } } if (CHECK_FLAG(bgp->vpn_policy[afi].flags, BGP_VPN_POLICY_TOVPN_RD_SET)) { char buf[RD_ADDRSTRLEN]; vty_out(vty, "%*srd vpn export %s\n", indent, "", prefix_rd2str(&bgp->vpn_policy[afi].tovpn_rd, buf, sizeof(buf))); } if (CHECK_FLAG(bgp->vpn_policy[afi].flags, BGP_VPN_POLICY_TOVPN_NEXTHOP_SET)) { char buf[PREFIX_STRLEN]; if (inet_ntop(bgp->vpn_policy[afi].tovpn_nexthop.family, &bgp->vpn_policy[afi].tovpn_nexthop.u.prefix, buf, sizeof(buf))) { vty_out(vty, "%*snexthop vpn export %s\n", indent, "", buf); } } if (bgp->vpn_policy[afi].rtlist[BGP_VPN_POLICY_DIR_FROMVPN] && bgp->vpn_policy[afi].rtlist[BGP_VPN_POLICY_DIR_TOVPN] && ecommunity_cmp( bgp->vpn_policy[afi].rtlist[BGP_VPN_POLICY_DIR_FROMVPN], bgp->vpn_policy[afi].rtlist[BGP_VPN_POLICY_DIR_TOVPN])) { char *b = ecommunity_ecom2str( bgp->vpn_policy[afi].rtlist[BGP_VPN_POLICY_DIR_TOVPN], ECOMMUNITY_FORMAT_ROUTE_MAP, ECOMMUNITY_ROUTE_TARGET); vty_out(vty, "%*srt vpn both %s\n", indent, "", b); XFREE(MTYPE_ECOMMUNITY_STR, b); } else { if (bgp->vpn_policy[afi].rtlist[BGP_VPN_POLICY_DIR_FROMVPN]) { char *b = ecommunity_ecom2str( bgp->vpn_policy[afi] .rtlist[BGP_VPN_POLICY_DIR_FROMVPN], ECOMMUNITY_FORMAT_ROUTE_MAP, ECOMMUNITY_ROUTE_TARGET); vty_out(vty, "%*srt vpn import %s\n", indent, "", b); XFREE(MTYPE_ECOMMUNITY_STR, b); } if (bgp->vpn_policy[afi].rtlist[BGP_VPN_POLICY_DIR_TOVPN]) { char *b = ecommunity_ecom2str( bgp->vpn_policy[afi] .rtlist[BGP_VPN_POLICY_DIR_TOVPN], ECOMMUNITY_FORMAT_ROUTE_MAP, ECOMMUNITY_ROUTE_TARGET); vty_out(vty, "%*srt vpn export %s\n", indent, "", b); XFREE(MTYPE_ECOMMUNITY_STR, b); } } if (bgp->vpn_policy[afi].rmap_name[BGP_VPN_POLICY_DIR_TOVPN]) vty_out(vty, "%*sroute-map vpn export %s\n", indent, "", bgp->vpn_policy[afi] .rmap_name[BGP_VPN_POLICY_DIR_TOVPN]); if (bgp->vpn_policy[afi].import_redirect_rtlist) { char *b = ecommunity_ecom2str( bgp->vpn_policy[afi] .import_redirect_rtlist, ECOMMUNITY_FORMAT_ROUTE_MAP, ECOMMUNITY_ROUTE_TARGET); vty_out(vty, "%*srt redirect import %s\n", indent, "", b); XFREE(MTYPE_ECOMMUNITY_STR, b); } } /* BGP node structure. */ static struct cmd_node bgp_node = { BGP_NODE, "%s(config-router)# ", 1, }; static struct cmd_node bgp_ipv4_unicast_node = { BGP_IPV4_NODE, "%s(config-router-af)# ", 1, }; static struct cmd_node bgp_ipv4_multicast_node = { BGP_IPV4M_NODE, "%s(config-router-af)# ", 1, }; static struct cmd_node bgp_ipv4_labeled_unicast_node = { BGP_IPV4L_NODE, "%s(config-router-af)# ", 1, }; static struct cmd_node bgp_ipv6_unicast_node = { BGP_IPV6_NODE, "%s(config-router-af)# ", 1, }; static struct cmd_node bgp_ipv6_multicast_node = { BGP_IPV6M_NODE, "%s(config-router-af)# ", 1, }; static struct cmd_node bgp_ipv6_labeled_unicast_node = { BGP_IPV6L_NODE, "%s(config-router-af)# ", 1, }; static struct cmd_node bgp_vpnv4_node = {BGP_VPNV4_NODE, "%s(config-router-af)# ", 1}; static struct cmd_node bgp_vpnv6_node = {BGP_VPNV6_NODE, "%s(config-router-af-vpnv6)# ", 1}; static struct cmd_node bgp_evpn_node = {BGP_EVPN_NODE, "%s(config-router-evpn)# ", 1}; static struct cmd_node bgp_evpn_vni_node = {BGP_EVPN_VNI_NODE, "%s(config-router-af-vni)# ", 1}; static struct cmd_node bgp_flowspecv4_node = {BGP_FLOWSPECV4_NODE, "%s(config-router-af)# ", 1}; static struct cmd_node bgp_flowspecv6_node = {BGP_FLOWSPECV6_NODE, "%s(config-router-af-vpnv6)# ", 1}; static void community_list_vty(void); static void bgp_ac_neighbor(vector comps, struct cmd_token *token) { struct bgp *bgp; struct peer *peer; struct listnode *lnbgp, *lnpeer; for (ALL_LIST_ELEMENTS_RO(bm->bgp, lnbgp, bgp)) { for (ALL_LIST_ELEMENTS_RO(bgp->peer, lnpeer, peer)) { /* only provide suggestions on the appropriate input * token type, * they'll otherwise show up multiple times */ enum cmd_token_type match_type; char *name = peer->host; if (peer->conf_if) { match_type = VARIABLE_TKN; name = peer->conf_if; } else if (strchr(peer->host, ':')) match_type = IPV6_TKN; else match_type = IPV4_TKN; if (token->type != match_type) continue; vector_set(comps, XSTRDUP(MTYPE_COMPLETION, name)); } } } static const struct cmd_variable_handler bgp_var_neighbor[] = { {.varname = "neighbor", .completions = bgp_ac_neighbor}, {.varname = "neighbors", .completions = bgp_ac_neighbor}, {.varname = "peer", .completions = bgp_ac_neighbor}, {.completions = NULL}}; static void bgp_ac_peergroup(vector comps, struct cmd_token *token) { struct bgp *bgp; struct peer_group *group; struct listnode *lnbgp, *lnpeer; for (ALL_LIST_ELEMENTS_RO(bm->bgp, lnbgp, bgp)) { for (ALL_LIST_ELEMENTS_RO(bgp->group, lnpeer, group)) vector_set(comps, XSTRDUP(MTYPE_COMPLETION, group->name)); } } static const struct cmd_variable_handler bgp_var_peergroup[] = { {.tokenname = "PGNAME", .completions = bgp_ac_peergroup}, {.completions = NULL} }; void bgp_vty_init(void) { cmd_variable_handler_register(bgp_var_neighbor); cmd_variable_handler_register(bgp_var_peergroup); /* Install bgp top node. */ install_node(&bgp_node, bgp_config_write); install_node(&bgp_ipv4_unicast_node, NULL); install_node(&bgp_ipv4_multicast_node, NULL); install_node(&bgp_ipv4_labeled_unicast_node, NULL); install_node(&bgp_ipv6_unicast_node, NULL); install_node(&bgp_ipv6_multicast_node, NULL); install_node(&bgp_ipv6_labeled_unicast_node, NULL); install_node(&bgp_vpnv4_node, NULL); install_node(&bgp_vpnv6_node, NULL); install_node(&bgp_evpn_node, NULL); install_node(&bgp_evpn_vni_node, NULL); install_node(&bgp_flowspecv4_node, NULL); install_node(&bgp_flowspecv6_node, NULL); /* Install default VTY commands to new nodes. */ install_default(BGP_NODE); install_default(BGP_IPV4_NODE); install_default(BGP_IPV4M_NODE); install_default(BGP_IPV4L_NODE); install_default(BGP_IPV6_NODE); install_default(BGP_IPV6M_NODE); install_default(BGP_IPV6L_NODE); install_default(BGP_VPNV4_NODE); install_default(BGP_VPNV6_NODE); install_default(BGP_FLOWSPECV4_NODE); install_default(BGP_FLOWSPECV6_NODE); install_default(BGP_EVPN_NODE); install_default(BGP_EVPN_VNI_NODE); /* "bgp local-mac" hidden commands. */ install_element(CONFIG_NODE, &bgp_local_mac_cmd); install_element(CONFIG_NODE, &no_bgp_local_mac_cmd); /* bgp route-map delay-timer commands. */ install_element(CONFIG_NODE, &bgp_set_route_map_delay_timer_cmd); install_element(CONFIG_NODE, &no_bgp_set_route_map_delay_timer_cmd); /* Dummy commands (Currently not supported) */ install_element(BGP_NODE, &no_synchronization_cmd); install_element(BGP_NODE, &no_auto_summary_cmd); /* "router bgp" commands. */ install_element(CONFIG_NODE, &router_bgp_cmd); /* "no router bgp" commands. */ install_element(CONFIG_NODE, &no_router_bgp_cmd); /* "bgp router-id" commands. */ install_element(BGP_NODE, &bgp_router_id_cmd); install_element(BGP_NODE, &no_bgp_router_id_cmd); /* "bgp cluster-id" commands. */ install_element(BGP_NODE, &bgp_cluster_id_cmd); install_element(BGP_NODE, &no_bgp_cluster_id_cmd); /* "bgp confederation" commands. */ install_element(BGP_NODE, &bgp_confederation_identifier_cmd); install_element(BGP_NODE, &no_bgp_confederation_identifier_cmd); /* "bgp confederation peers" commands. */ install_element(BGP_NODE, &bgp_confederation_peers_cmd); install_element(BGP_NODE, &no_bgp_confederation_peers_cmd); /* bgp max-med command */ install_element(BGP_NODE, &bgp_maxmed_admin_cmd); install_element(BGP_NODE, &no_bgp_maxmed_admin_cmd); install_element(BGP_NODE, &bgp_maxmed_admin_medv_cmd); install_element(BGP_NODE, &bgp_maxmed_onstartup_cmd); install_element(BGP_NODE, &no_bgp_maxmed_onstartup_cmd); /* bgp disable-ebgp-connected-nh-check */ install_element(BGP_NODE, &bgp_disable_connected_route_check_cmd); install_element(BGP_NODE, &no_bgp_disable_connected_route_check_cmd); /* bgp update-delay command */ install_element(BGP_NODE, &bgp_update_delay_cmd); install_element(BGP_NODE, &no_bgp_update_delay_cmd); install_element(BGP_NODE, &bgp_update_delay_establish_wait_cmd); install_element(BGP_NODE, &bgp_wpkt_quanta_cmd); install_element(BGP_NODE, &no_bgp_wpkt_quanta_cmd); install_element(BGP_NODE, &bgp_rpkt_quanta_cmd); install_element(BGP_NODE, &no_bgp_rpkt_quanta_cmd); install_element(BGP_NODE, &bgp_coalesce_time_cmd); install_element(BGP_NODE, &no_bgp_coalesce_time_cmd); /* "maximum-paths" commands. */ install_element(BGP_NODE, &bgp_maxpaths_hidden_cmd); install_element(BGP_NODE, &no_bgp_maxpaths_hidden_cmd); install_element(BGP_IPV4_NODE, &bgp_maxpaths_cmd); install_element(BGP_IPV4_NODE, &no_bgp_maxpaths_cmd); install_element(BGP_IPV6_NODE, &bgp_maxpaths_cmd); install_element(BGP_IPV6_NODE, &no_bgp_maxpaths_cmd); install_element(BGP_NODE, &bgp_maxpaths_ibgp_hidden_cmd); install_element(BGP_NODE, &bgp_maxpaths_ibgp_cluster_hidden_cmd); install_element(BGP_NODE, &no_bgp_maxpaths_ibgp_hidden_cmd); install_element(BGP_IPV4_NODE, &bgp_maxpaths_ibgp_cmd); install_element(BGP_IPV4_NODE, &bgp_maxpaths_ibgp_cluster_cmd); install_element(BGP_IPV4_NODE, &no_bgp_maxpaths_ibgp_cmd); install_element(BGP_IPV6_NODE, &bgp_maxpaths_ibgp_cmd); install_element(BGP_IPV6_NODE, &bgp_maxpaths_ibgp_cluster_cmd); install_element(BGP_IPV6_NODE, &no_bgp_maxpaths_ibgp_cmd); install_element(BGP_IPV6L_NODE, &bgp_maxpaths_cmd); install_element(BGP_IPV6L_NODE, &no_bgp_maxpaths_cmd); install_element(BGP_IPV6L_NODE, &bgp_maxpaths_ibgp_cmd); install_element(BGP_IPV6L_NODE, &bgp_maxpaths_ibgp_cluster_cmd); install_element(BGP_IPV6L_NODE, &no_bgp_maxpaths_ibgp_cmd); /* "timers bgp" commands. */ install_element(BGP_NODE, &bgp_timers_cmd); install_element(BGP_NODE, &no_bgp_timers_cmd); /* route-map delay-timer commands - per instance for backwards compat. */ install_element(BGP_NODE, &bgp_set_route_map_delay_timer_cmd); install_element(BGP_NODE, &no_bgp_set_route_map_delay_timer_cmd); /* "bgp client-to-client reflection" commands */ install_element(BGP_NODE, &no_bgp_client_to_client_reflection_cmd); install_element(BGP_NODE, &bgp_client_to_client_reflection_cmd); /* "bgp always-compare-med" commands */ install_element(BGP_NODE, &bgp_always_compare_med_cmd); install_element(BGP_NODE, &no_bgp_always_compare_med_cmd); /* bgp ebgp-requires-policy */ install_element(BGP_NODE, &bgp_ebgp_requires_policy_cmd); install_element(BGP_NODE, &no_bgp_ebgp_requires_policy_cmd); /* "bgp deterministic-med" commands */ install_element(BGP_NODE, &bgp_deterministic_med_cmd); install_element(BGP_NODE, &no_bgp_deterministic_med_cmd); /* "bgp graceful-restart" commands */ install_element(BGP_NODE, &bgp_graceful_restart_cmd); install_element(BGP_NODE, &no_bgp_graceful_restart_cmd); install_element(BGP_NODE, &bgp_graceful_restart_stalepath_time_cmd); install_element(BGP_NODE, &no_bgp_graceful_restart_stalepath_time_cmd); install_element(BGP_NODE, &bgp_graceful_restart_restart_time_cmd); install_element(BGP_NODE, &no_bgp_graceful_restart_restart_time_cmd); install_element(BGP_NODE, &bgp_graceful_restart_preserve_fw_cmd); install_element(BGP_NODE, &no_bgp_graceful_restart_preserve_fw_cmd); /* "bgp graceful-shutdown" commands */ install_element(BGP_NODE, &bgp_graceful_shutdown_cmd); install_element(BGP_NODE, &no_bgp_graceful_shutdown_cmd); /* "bgp fast-external-failover" commands */ install_element(BGP_NODE, &bgp_fast_external_failover_cmd); install_element(BGP_NODE, &no_bgp_fast_external_failover_cmd); /* "bgp bestpath compare-routerid" commands */ install_element(BGP_NODE, &bgp_bestpath_compare_router_id_cmd); install_element(BGP_NODE, &no_bgp_bestpath_compare_router_id_cmd); /* "bgp bestpath as-path ignore" commands */ install_element(BGP_NODE, &bgp_bestpath_aspath_ignore_cmd); install_element(BGP_NODE, &no_bgp_bestpath_aspath_ignore_cmd); /* "bgp bestpath as-path confed" commands */ install_element(BGP_NODE, &bgp_bestpath_aspath_confed_cmd); install_element(BGP_NODE, &no_bgp_bestpath_aspath_confed_cmd); /* "bgp bestpath as-path multipath-relax" commands */ install_element(BGP_NODE, &bgp_bestpath_aspath_multipath_relax_cmd); install_element(BGP_NODE, &no_bgp_bestpath_aspath_multipath_relax_cmd); /* "bgp log-neighbor-changes" commands */ install_element(BGP_NODE, &bgp_log_neighbor_changes_cmd); install_element(BGP_NODE, &no_bgp_log_neighbor_changes_cmd); /* "bgp bestpath med" commands */ install_element(BGP_NODE, &bgp_bestpath_med_cmd); install_element(BGP_NODE, &no_bgp_bestpath_med_cmd); /* "no bgp default ipv4-unicast" commands. */ install_element(BGP_NODE, &no_bgp_default_ipv4_unicast_cmd); install_element(BGP_NODE, &bgp_default_ipv4_unicast_cmd); /* "bgp network import-check" commands. */ install_element(BGP_NODE, &bgp_network_import_check_cmd); install_element(BGP_NODE, &bgp_network_import_check_exact_cmd); install_element(BGP_NODE, &no_bgp_network_import_check_cmd); /* "bgp default local-preference" commands. */ install_element(BGP_NODE, &bgp_default_local_preference_cmd); install_element(BGP_NODE, &no_bgp_default_local_preference_cmd); /* bgp default show-hostname */ install_element(BGP_NODE, &bgp_default_show_hostname_cmd); install_element(BGP_NODE, &no_bgp_default_show_hostname_cmd); /* "bgp default subgroup-pkt-queue-max" commands. */ install_element(BGP_NODE, &bgp_default_subgroup_pkt_queue_max_cmd); install_element(BGP_NODE, &no_bgp_default_subgroup_pkt_queue_max_cmd); /* bgp ibgp-allow-policy-mods command */ install_element(BGP_NODE, &bgp_rr_allow_outbound_policy_cmd); install_element(BGP_NODE, &no_bgp_rr_allow_outbound_policy_cmd); /* "bgp listen limit" commands. */ install_element(BGP_NODE, &bgp_listen_limit_cmd); install_element(BGP_NODE, &no_bgp_listen_limit_cmd); /* "bgp listen range" commands. */ install_element(BGP_NODE, &bgp_listen_range_cmd); install_element(BGP_NODE, &no_bgp_listen_range_cmd); /* "bgp default shutdown" command */ install_element(BGP_NODE, &bgp_default_shutdown_cmd); /* "neighbor remote-as" commands. */ install_element(BGP_NODE, &neighbor_remote_as_cmd); install_element(BGP_NODE, &neighbor_interface_config_cmd); install_element(BGP_NODE, &neighbor_interface_config_v6only_cmd); install_element(BGP_NODE, &neighbor_interface_config_remote_as_cmd); install_element(BGP_NODE, &neighbor_interface_v6only_config_remote_as_cmd); install_element(BGP_NODE, &no_neighbor_cmd); install_element(BGP_NODE, &no_neighbor_interface_config_cmd); /* "neighbor peer-group" commands. */ install_element(BGP_NODE, &neighbor_peer_group_cmd); install_element(BGP_NODE, &no_neighbor_peer_group_cmd); install_element(BGP_NODE, &no_neighbor_interface_peer_group_remote_as_cmd); /* "neighbor local-as" commands. */ install_element(BGP_NODE, &neighbor_local_as_cmd); install_element(BGP_NODE, &neighbor_local_as_no_prepend_cmd); install_element(BGP_NODE, &neighbor_local_as_no_prepend_replace_as_cmd); install_element(BGP_NODE, &no_neighbor_local_as_cmd); /* "neighbor solo" commands. */ install_element(BGP_NODE, &neighbor_solo_cmd); install_element(BGP_NODE, &no_neighbor_solo_cmd); /* "neighbor password" commands. */ install_element(BGP_NODE, &neighbor_password_cmd); install_element(BGP_NODE, &no_neighbor_password_cmd); /* "neighbor activate" commands. */ install_element(BGP_NODE, &neighbor_activate_hidden_cmd); install_element(BGP_IPV4_NODE, &neighbor_activate_cmd); install_element(BGP_IPV4M_NODE, &neighbor_activate_cmd); install_element(BGP_IPV4L_NODE, &neighbor_activate_cmd); install_element(BGP_IPV6_NODE, &neighbor_activate_cmd); install_element(BGP_IPV6M_NODE, &neighbor_activate_cmd); install_element(BGP_IPV6L_NODE, &neighbor_activate_cmd); install_element(BGP_VPNV4_NODE, &neighbor_activate_cmd); install_element(BGP_VPNV6_NODE, &neighbor_activate_cmd); install_element(BGP_FLOWSPECV4_NODE, &neighbor_activate_cmd); install_element(BGP_FLOWSPECV6_NODE, &neighbor_activate_cmd); install_element(BGP_EVPN_NODE, &neighbor_activate_cmd); /* "no neighbor activate" commands. */ install_element(BGP_NODE, &no_neighbor_activate_hidden_cmd); install_element(BGP_IPV4_NODE, &no_neighbor_activate_cmd); install_element(BGP_IPV4M_NODE, &no_neighbor_activate_cmd); install_element(BGP_IPV4L_NODE, &no_neighbor_activate_cmd); install_element(BGP_IPV6_NODE, &no_neighbor_activate_cmd); install_element(BGP_IPV6M_NODE, &no_neighbor_activate_cmd); install_element(BGP_IPV6L_NODE, &no_neighbor_activate_cmd); install_element(BGP_VPNV4_NODE, &no_neighbor_activate_cmd); install_element(BGP_VPNV6_NODE, &no_neighbor_activate_cmd); install_element(BGP_FLOWSPECV4_NODE, &no_neighbor_activate_cmd); install_element(BGP_FLOWSPECV6_NODE, &no_neighbor_activate_cmd); install_element(BGP_EVPN_NODE, &no_neighbor_activate_cmd); /* "neighbor peer-group" set commands. */ install_element(BGP_NODE, &neighbor_set_peer_group_cmd); install_element(BGP_IPV4_NODE, &neighbor_set_peer_group_hidden_cmd); install_element(BGP_IPV4M_NODE, &neighbor_set_peer_group_hidden_cmd); install_element(BGP_IPV6_NODE, &neighbor_set_peer_group_hidden_cmd); install_element(BGP_IPV6M_NODE, &neighbor_set_peer_group_hidden_cmd); install_element(BGP_IPV6L_NODE, &neighbor_set_peer_group_hidden_cmd); install_element(BGP_VPNV4_NODE, &neighbor_set_peer_group_hidden_cmd); install_element(BGP_VPNV6_NODE, &neighbor_set_peer_group_hidden_cmd); install_element(BGP_FLOWSPECV4_NODE, &neighbor_set_peer_group_hidden_cmd); install_element(BGP_FLOWSPECV6_NODE, &neighbor_set_peer_group_hidden_cmd); /* "no neighbor peer-group unset" commands. */ install_element(BGP_NODE, &no_neighbor_set_peer_group_cmd); install_element(BGP_IPV4_NODE, &no_neighbor_set_peer_group_hidden_cmd); install_element(BGP_IPV4M_NODE, &no_neighbor_set_peer_group_hidden_cmd); install_element(BGP_IPV6_NODE, &no_neighbor_set_peer_group_hidden_cmd); install_element(BGP_IPV6M_NODE, &no_neighbor_set_peer_group_hidden_cmd); install_element(BGP_IPV6L_NODE, &no_neighbor_set_peer_group_hidden_cmd); install_element(BGP_VPNV4_NODE, &no_neighbor_set_peer_group_hidden_cmd); install_element(BGP_VPNV6_NODE, &no_neighbor_set_peer_group_hidden_cmd); install_element(BGP_FLOWSPECV4_NODE, &no_neighbor_set_peer_group_hidden_cmd); install_element(BGP_FLOWSPECV6_NODE, &no_neighbor_set_peer_group_hidden_cmd); /* "neighbor softreconfiguration inbound" commands.*/ install_element(BGP_NODE, &neighbor_soft_reconfiguration_hidden_cmd); install_element(BGP_NODE, &no_neighbor_soft_reconfiguration_hidden_cmd); install_element(BGP_IPV4_NODE, &neighbor_soft_reconfiguration_cmd); install_element(BGP_IPV4_NODE, &no_neighbor_soft_reconfiguration_cmd); install_element(BGP_IPV4L_NODE, &neighbor_soft_reconfiguration_cmd); install_element(BGP_IPV4L_NODE, &no_neighbor_soft_reconfiguration_cmd); install_element(BGP_IPV4M_NODE, &neighbor_soft_reconfiguration_cmd); install_element(BGP_IPV4M_NODE, &no_neighbor_soft_reconfiguration_cmd); install_element(BGP_IPV6_NODE, &neighbor_soft_reconfiguration_cmd); install_element(BGP_IPV6_NODE, &no_neighbor_soft_reconfiguration_cmd); install_element(BGP_IPV6M_NODE, &neighbor_soft_reconfiguration_cmd); install_element(BGP_IPV6M_NODE, &no_neighbor_soft_reconfiguration_cmd); install_element(BGP_IPV6L_NODE, &neighbor_soft_reconfiguration_cmd); install_element(BGP_IPV6L_NODE, &no_neighbor_soft_reconfiguration_cmd); install_element(BGP_VPNV4_NODE, &neighbor_soft_reconfiguration_cmd); install_element(BGP_VPNV4_NODE, &no_neighbor_soft_reconfiguration_cmd); install_element(BGP_VPNV6_NODE, &neighbor_soft_reconfiguration_cmd); install_element(BGP_VPNV6_NODE, &no_neighbor_soft_reconfiguration_cmd); install_element(BGP_FLOWSPECV4_NODE, &neighbor_soft_reconfiguration_cmd); install_element(BGP_FLOWSPECV4_NODE, &no_neighbor_soft_reconfiguration_cmd); install_element(BGP_FLOWSPECV6_NODE, &neighbor_soft_reconfiguration_cmd); install_element(BGP_FLOWSPECV6_NODE, &no_neighbor_soft_reconfiguration_cmd); install_element(BGP_EVPN_NODE, &neighbor_soft_reconfiguration_cmd); install_element(BGP_EVPN_NODE, &no_neighbor_soft_reconfiguration_cmd); /* "neighbor attribute-unchanged" commands. */ install_element(BGP_NODE, &neighbor_attr_unchanged_hidden_cmd); install_element(BGP_NODE, &no_neighbor_attr_unchanged_hidden_cmd); install_element(BGP_IPV4_NODE, &neighbor_attr_unchanged_cmd); install_element(BGP_IPV4_NODE, &no_neighbor_attr_unchanged_cmd); install_element(BGP_IPV4M_NODE, &neighbor_attr_unchanged_cmd); install_element(BGP_IPV4M_NODE, &no_neighbor_attr_unchanged_cmd); install_element(BGP_IPV4L_NODE, &neighbor_attr_unchanged_cmd); install_element(BGP_IPV4L_NODE, &no_neighbor_attr_unchanged_cmd); install_element(BGP_IPV6_NODE, &neighbor_attr_unchanged_cmd); install_element(BGP_IPV6_NODE, &no_neighbor_attr_unchanged_cmd); install_element(BGP_IPV6M_NODE, &neighbor_attr_unchanged_cmd); install_element(BGP_IPV6M_NODE, &no_neighbor_attr_unchanged_cmd); install_element(BGP_IPV6L_NODE, &neighbor_attr_unchanged_cmd); install_element(BGP_IPV6L_NODE, &no_neighbor_attr_unchanged_cmd); install_element(BGP_VPNV4_NODE, &neighbor_attr_unchanged_cmd); install_element(BGP_VPNV4_NODE, &no_neighbor_attr_unchanged_cmd); install_element(BGP_VPNV6_NODE, &neighbor_attr_unchanged_cmd); install_element(BGP_VPNV6_NODE, &no_neighbor_attr_unchanged_cmd); install_element(BGP_EVPN_NODE, &neighbor_attr_unchanged_cmd); install_element(BGP_EVPN_NODE, &no_neighbor_attr_unchanged_cmd); /* "nexthop-local unchanged" commands */ install_element(BGP_IPV6_NODE, &neighbor_nexthop_local_unchanged_cmd); install_element(BGP_IPV6_NODE, &no_neighbor_nexthop_local_unchanged_cmd); /* "neighbor next-hop-self" commands. */ install_element(BGP_NODE, &neighbor_nexthop_self_hidden_cmd); install_element(BGP_NODE, &no_neighbor_nexthop_self_hidden_cmd); install_element(BGP_IPV4_NODE, &neighbor_nexthop_self_cmd); install_element(BGP_IPV4_NODE, &no_neighbor_nexthop_self_cmd); install_element(BGP_IPV4M_NODE, &neighbor_nexthop_self_cmd); install_element(BGP_IPV4M_NODE, &no_neighbor_nexthop_self_cmd); install_element(BGP_IPV4L_NODE, &neighbor_nexthop_self_cmd); install_element(BGP_IPV4L_NODE, &no_neighbor_nexthop_self_cmd); install_element(BGP_IPV6_NODE, &neighbor_nexthop_self_cmd); install_element(BGP_IPV6_NODE, &no_neighbor_nexthop_self_cmd); install_element(BGP_IPV6M_NODE, &neighbor_nexthop_self_cmd); install_element(BGP_IPV6M_NODE, &no_neighbor_nexthop_self_cmd); install_element(BGP_IPV6L_NODE, &neighbor_nexthop_self_cmd); install_element(BGP_IPV6L_NODE, &no_neighbor_nexthop_self_cmd); install_element(BGP_VPNV4_NODE, &neighbor_nexthop_self_cmd); install_element(BGP_VPNV4_NODE, &no_neighbor_nexthop_self_cmd); install_element(BGP_VPNV6_NODE, &neighbor_nexthop_self_cmd); install_element(BGP_VPNV6_NODE, &no_neighbor_nexthop_self_cmd); install_element(BGP_EVPN_NODE, &neighbor_nexthop_self_cmd); install_element(BGP_EVPN_NODE, &no_neighbor_nexthop_self_cmd); /* "neighbor next-hop-self force" commands. */ install_element(BGP_NODE, &neighbor_nexthop_self_force_hidden_cmd); install_element(BGP_NODE, &no_neighbor_nexthop_self_force_hidden_cmd); install_element(BGP_NODE, &neighbor_nexthop_self_all_hidden_cmd); install_element(BGP_NODE, &no_neighbor_nexthop_self_all_hidden_cmd); install_element(BGP_IPV4_NODE, &neighbor_nexthop_self_force_cmd); install_element(BGP_IPV4_NODE, &no_neighbor_nexthop_self_force_cmd); install_element(BGP_IPV4_NODE, &neighbor_nexthop_self_all_hidden_cmd); install_element(BGP_IPV4_NODE, &no_neighbor_nexthop_self_all_hidden_cmd); install_element(BGP_IPV4M_NODE, &neighbor_nexthop_self_force_cmd); install_element(BGP_IPV4M_NODE, &no_neighbor_nexthop_self_force_cmd); install_element(BGP_IPV4M_NODE, &neighbor_nexthop_self_all_hidden_cmd); install_element(BGP_IPV4M_NODE, &no_neighbor_nexthop_self_all_hidden_cmd); install_element(BGP_IPV4L_NODE, &neighbor_nexthop_self_force_cmd); install_element(BGP_IPV4L_NODE, &no_neighbor_nexthop_self_force_cmd); install_element(BGP_IPV4L_NODE, &neighbor_nexthop_self_all_hidden_cmd); install_element(BGP_IPV4L_NODE, &no_neighbor_nexthop_self_all_hidden_cmd); install_element(BGP_IPV6_NODE, &neighbor_nexthop_self_force_cmd); install_element(BGP_IPV6_NODE, &no_neighbor_nexthop_self_force_cmd); install_element(BGP_IPV6_NODE, &neighbor_nexthop_self_all_hidden_cmd); install_element(BGP_IPV6_NODE, &no_neighbor_nexthop_self_all_hidden_cmd); install_element(BGP_IPV6M_NODE, &neighbor_nexthop_self_force_cmd); install_element(BGP_IPV6M_NODE, &no_neighbor_nexthop_self_force_cmd); install_element(BGP_IPV6M_NODE, &neighbor_nexthop_self_all_hidden_cmd); install_element(BGP_IPV6M_NODE, &no_neighbor_nexthop_self_all_hidden_cmd); install_element(BGP_IPV6L_NODE, &neighbor_nexthop_self_force_cmd); install_element(BGP_IPV6L_NODE, &no_neighbor_nexthop_self_force_cmd); install_element(BGP_IPV6L_NODE, &neighbor_nexthop_self_all_hidden_cmd); install_element(BGP_IPV6L_NODE, &no_neighbor_nexthop_self_all_hidden_cmd); install_element(BGP_VPNV4_NODE, &neighbor_nexthop_self_force_cmd); install_element(BGP_VPNV4_NODE, &no_neighbor_nexthop_self_force_cmd); install_element(BGP_VPNV4_NODE, &neighbor_nexthop_self_all_hidden_cmd); install_element(BGP_VPNV4_NODE, &no_neighbor_nexthop_self_all_hidden_cmd); install_element(BGP_VPNV6_NODE, &neighbor_nexthop_self_force_cmd); install_element(BGP_VPNV6_NODE, &no_neighbor_nexthop_self_force_cmd); install_element(BGP_VPNV6_NODE, &neighbor_nexthop_self_all_hidden_cmd); install_element(BGP_VPNV6_NODE, &no_neighbor_nexthop_self_all_hidden_cmd); /* "neighbor as-override" commands. */ install_element(BGP_NODE, &neighbor_as_override_hidden_cmd); install_element(BGP_NODE, &no_neighbor_as_override_hidden_cmd); install_element(BGP_IPV4_NODE, &neighbor_as_override_cmd); install_element(BGP_IPV4_NODE, &no_neighbor_as_override_cmd); install_element(BGP_IPV4M_NODE, &neighbor_as_override_cmd); install_element(BGP_IPV4M_NODE, &no_neighbor_as_override_cmd); install_element(BGP_IPV4L_NODE, &neighbor_as_override_cmd); install_element(BGP_IPV4L_NODE, &no_neighbor_as_override_cmd); install_element(BGP_IPV6_NODE, &neighbor_as_override_cmd); install_element(BGP_IPV6_NODE, &no_neighbor_as_override_cmd); install_element(BGP_IPV6M_NODE, &neighbor_as_override_cmd); install_element(BGP_IPV6M_NODE, &no_neighbor_as_override_cmd); install_element(BGP_IPV6L_NODE, &neighbor_as_override_cmd); install_element(BGP_IPV6L_NODE, &no_neighbor_as_override_cmd); install_element(BGP_VPNV4_NODE, &neighbor_as_override_cmd); install_element(BGP_VPNV4_NODE, &no_neighbor_as_override_cmd); install_element(BGP_VPNV6_NODE, &neighbor_as_override_cmd); install_element(BGP_VPNV6_NODE, &no_neighbor_as_override_cmd); /* "neighbor remove-private-AS" commands. */ install_element(BGP_NODE, &neighbor_remove_private_as_hidden_cmd); install_element(BGP_NODE, &no_neighbor_remove_private_as_hidden_cmd); install_element(BGP_NODE, &neighbor_remove_private_as_all_hidden_cmd); install_element(BGP_NODE, &no_neighbor_remove_private_as_all_hidden_cmd); install_element(BGP_NODE, &neighbor_remove_private_as_replace_as_hidden_cmd); install_element(BGP_NODE, &no_neighbor_remove_private_as_replace_as_hidden_cmd); install_element(BGP_NODE, &neighbor_remove_private_as_all_replace_as_hidden_cmd); install_element( BGP_NODE, &no_neighbor_remove_private_as_all_replace_as_hidden_cmd); install_element(BGP_IPV4_NODE, &neighbor_remove_private_as_cmd); install_element(BGP_IPV4_NODE, &no_neighbor_remove_private_as_cmd); install_element(BGP_IPV4_NODE, &neighbor_remove_private_as_all_cmd); install_element(BGP_IPV4_NODE, &no_neighbor_remove_private_as_all_cmd); install_element(BGP_IPV4_NODE, &neighbor_remove_private_as_replace_as_cmd); install_element(BGP_IPV4_NODE, &no_neighbor_remove_private_as_replace_as_cmd); install_element(BGP_IPV4_NODE, &neighbor_remove_private_as_all_replace_as_cmd); install_element(BGP_IPV4_NODE, &no_neighbor_remove_private_as_all_replace_as_cmd); install_element(BGP_IPV4M_NODE, &neighbor_remove_private_as_cmd); install_element(BGP_IPV4M_NODE, &no_neighbor_remove_private_as_cmd); install_element(BGP_IPV4M_NODE, &neighbor_remove_private_as_all_cmd); install_element(BGP_IPV4M_NODE, &no_neighbor_remove_private_as_all_cmd); install_element(BGP_IPV4M_NODE, &neighbor_remove_private_as_replace_as_cmd); install_element(BGP_IPV4M_NODE, &no_neighbor_remove_private_as_replace_as_cmd); install_element(BGP_IPV4M_NODE, &neighbor_remove_private_as_all_replace_as_cmd); install_element(BGP_IPV4M_NODE, &no_neighbor_remove_private_as_all_replace_as_cmd); install_element(BGP_IPV4L_NODE, &neighbor_remove_private_as_cmd); install_element(BGP_IPV4L_NODE, &no_neighbor_remove_private_as_cmd); install_element(BGP_IPV4L_NODE, &neighbor_remove_private_as_all_cmd); install_element(BGP_IPV4L_NODE, &no_neighbor_remove_private_as_all_cmd); install_element(BGP_IPV4L_NODE, &neighbor_remove_private_as_replace_as_cmd); install_element(BGP_IPV4L_NODE, &no_neighbor_remove_private_as_replace_as_cmd); install_element(BGP_IPV4L_NODE, &neighbor_remove_private_as_all_replace_as_cmd); install_element(BGP_IPV4L_NODE, &no_neighbor_remove_private_as_all_replace_as_cmd); install_element(BGP_IPV6_NODE, &neighbor_remove_private_as_cmd); install_element(BGP_IPV6_NODE, &no_neighbor_remove_private_as_cmd); install_element(BGP_IPV6_NODE, &neighbor_remove_private_as_all_cmd); install_element(BGP_IPV6_NODE, &no_neighbor_remove_private_as_all_cmd); install_element(BGP_IPV6_NODE, &neighbor_remove_private_as_replace_as_cmd); install_element(BGP_IPV6_NODE, &no_neighbor_remove_private_as_replace_as_cmd); install_element(BGP_IPV6_NODE, &neighbor_remove_private_as_all_replace_as_cmd); install_element(BGP_IPV6_NODE, &no_neighbor_remove_private_as_all_replace_as_cmd); install_element(BGP_IPV6M_NODE, &neighbor_remove_private_as_cmd); install_element(BGP_IPV6M_NODE, &no_neighbor_remove_private_as_cmd); install_element(BGP_IPV6M_NODE, &neighbor_remove_private_as_all_cmd); install_element(BGP_IPV6M_NODE, &no_neighbor_remove_private_as_all_cmd); install_element(BGP_IPV6M_NODE, &neighbor_remove_private_as_replace_as_cmd); install_element(BGP_IPV6M_NODE, &no_neighbor_remove_private_as_replace_as_cmd); install_element(BGP_IPV6M_NODE, &neighbor_remove_private_as_all_replace_as_cmd); install_element(BGP_IPV6M_NODE, &no_neighbor_remove_private_as_all_replace_as_cmd); install_element(BGP_IPV6L_NODE, &neighbor_remove_private_as_cmd); install_element(BGP_IPV6L_NODE, &no_neighbor_remove_private_as_cmd); install_element(BGP_IPV6L_NODE, &neighbor_remove_private_as_all_cmd); install_element(BGP_IPV6L_NODE, &no_neighbor_remove_private_as_all_cmd); install_element(BGP_IPV6L_NODE, &neighbor_remove_private_as_replace_as_cmd); install_element(BGP_IPV6L_NODE, &no_neighbor_remove_private_as_replace_as_cmd); install_element(BGP_IPV6L_NODE, &neighbor_remove_private_as_all_replace_as_cmd); install_element(BGP_IPV6L_NODE, &no_neighbor_remove_private_as_all_replace_as_cmd); install_element(BGP_VPNV4_NODE, &neighbor_remove_private_as_cmd); install_element(BGP_VPNV4_NODE, &no_neighbor_remove_private_as_cmd); install_element(BGP_VPNV4_NODE, &neighbor_remove_private_as_all_cmd); install_element(BGP_VPNV4_NODE, &no_neighbor_remove_private_as_all_cmd); install_element(BGP_VPNV4_NODE, &neighbor_remove_private_as_replace_as_cmd); install_element(BGP_VPNV4_NODE, &no_neighbor_remove_private_as_replace_as_cmd); install_element(BGP_VPNV4_NODE, &neighbor_remove_private_as_all_replace_as_cmd); install_element(BGP_VPNV4_NODE, &no_neighbor_remove_private_as_all_replace_as_cmd); install_element(BGP_VPNV6_NODE, &neighbor_remove_private_as_cmd); install_element(BGP_VPNV6_NODE, &no_neighbor_remove_private_as_cmd); install_element(BGP_VPNV6_NODE, &neighbor_remove_private_as_all_cmd); install_element(BGP_VPNV6_NODE, &no_neighbor_remove_private_as_all_cmd); install_element(BGP_VPNV6_NODE, &neighbor_remove_private_as_replace_as_cmd); install_element(BGP_VPNV6_NODE, &no_neighbor_remove_private_as_replace_as_cmd); install_element(BGP_VPNV6_NODE, &neighbor_remove_private_as_all_replace_as_cmd); install_element(BGP_VPNV6_NODE, &no_neighbor_remove_private_as_all_replace_as_cmd); /* "neighbor send-community" commands.*/ install_element(BGP_NODE, &neighbor_send_community_hidden_cmd); install_element(BGP_NODE, &neighbor_send_community_type_hidden_cmd); install_element(BGP_NODE, &no_neighbor_send_community_hidden_cmd); install_element(BGP_NODE, &no_neighbor_send_community_type_hidden_cmd); install_element(BGP_IPV4_NODE, &neighbor_send_community_cmd); install_element(BGP_IPV4_NODE, &neighbor_send_community_type_cmd); install_element(BGP_IPV4_NODE, &no_neighbor_send_community_cmd); install_element(BGP_IPV4_NODE, &no_neighbor_send_community_type_cmd); install_element(BGP_IPV4M_NODE, &neighbor_send_community_cmd); install_element(BGP_IPV4M_NODE, &neighbor_send_community_type_cmd); install_element(BGP_IPV4M_NODE, &no_neighbor_send_community_cmd); install_element(BGP_IPV4M_NODE, &no_neighbor_send_community_type_cmd); install_element(BGP_IPV4L_NODE, &neighbor_send_community_cmd); install_element(BGP_IPV4L_NODE, &neighbor_send_community_type_cmd); install_element(BGP_IPV4L_NODE, &no_neighbor_send_community_cmd); install_element(BGP_IPV4L_NODE, &no_neighbor_send_community_type_cmd); install_element(BGP_IPV6_NODE, &neighbor_send_community_cmd); install_element(BGP_IPV6_NODE, &neighbor_send_community_type_cmd); install_element(BGP_IPV6_NODE, &no_neighbor_send_community_cmd); install_element(BGP_IPV6_NODE, &no_neighbor_send_community_type_cmd); install_element(BGP_IPV6M_NODE, &neighbor_send_community_cmd); install_element(BGP_IPV6M_NODE, &neighbor_send_community_type_cmd); install_element(BGP_IPV6M_NODE, &no_neighbor_send_community_cmd); install_element(BGP_IPV6M_NODE, &no_neighbor_send_community_type_cmd); install_element(BGP_IPV6L_NODE, &neighbor_send_community_cmd); install_element(BGP_IPV6L_NODE, &neighbor_send_community_type_cmd); install_element(BGP_IPV6L_NODE, &no_neighbor_send_community_cmd); install_element(BGP_IPV6L_NODE, &no_neighbor_send_community_type_cmd); install_element(BGP_VPNV4_NODE, &neighbor_send_community_cmd); install_element(BGP_VPNV4_NODE, &neighbor_send_community_type_cmd); install_element(BGP_VPNV4_NODE, &no_neighbor_send_community_cmd); install_element(BGP_VPNV4_NODE, &no_neighbor_send_community_type_cmd); install_element(BGP_VPNV6_NODE, &neighbor_send_community_cmd); install_element(BGP_VPNV6_NODE, &neighbor_send_community_type_cmd); install_element(BGP_VPNV6_NODE, &no_neighbor_send_community_cmd); install_element(BGP_VPNV6_NODE, &no_neighbor_send_community_type_cmd); /* "neighbor route-reflector" commands.*/ install_element(BGP_NODE, &neighbor_route_reflector_client_hidden_cmd); install_element(BGP_NODE, &no_neighbor_route_reflector_client_hidden_cmd); install_element(BGP_IPV4_NODE, &neighbor_route_reflector_client_cmd); install_element(BGP_IPV4_NODE, &no_neighbor_route_reflector_client_cmd); install_element(BGP_IPV4M_NODE, &neighbor_route_reflector_client_cmd); install_element(BGP_IPV4M_NODE, &no_neighbor_route_reflector_client_cmd); install_element(BGP_IPV4L_NODE, &neighbor_route_reflector_client_cmd); install_element(BGP_IPV4L_NODE, &no_neighbor_route_reflector_client_cmd); install_element(BGP_IPV6_NODE, &neighbor_route_reflector_client_cmd); install_element(BGP_IPV6_NODE, &no_neighbor_route_reflector_client_cmd); install_element(BGP_IPV6M_NODE, &neighbor_route_reflector_client_cmd); install_element(BGP_IPV6M_NODE, &no_neighbor_route_reflector_client_cmd); install_element(BGP_IPV6L_NODE, &neighbor_route_reflector_client_cmd); install_element(BGP_IPV6L_NODE, &no_neighbor_route_reflector_client_cmd); install_element(BGP_VPNV4_NODE, &neighbor_route_reflector_client_cmd); install_element(BGP_VPNV4_NODE, &no_neighbor_route_reflector_client_cmd); install_element(BGP_VPNV6_NODE, &neighbor_route_reflector_client_cmd); install_element(BGP_VPNV6_NODE, &no_neighbor_route_reflector_client_cmd); install_element(BGP_FLOWSPECV4_NODE, &neighbor_route_reflector_client_cmd); install_element(BGP_FLOWSPECV4_NODE, &no_neighbor_route_reflector_client_cmd); install_element(BGP_FLOWSPECV6_NODE, &neighbor_route_reflector_client_cmd); install_element(BGP_FLOWSPECV6_NODE, &no_neighbor_route_reflector_client_cmd); install_element(BGP_EVPN_NODE, &neighbor_route_reflector_client_cmd); install_element(BGP_EVPN_NODE, &no_neighbor_route_reflector_client_cmd); /* "neighbor route-server" commands.*/ install_element(BGP_NODE, &neighbor_route_server_client_hidden_cmd); install_element(BGP_NODE, &no_neighbor_route_server_client_hidden_cmd); install_element(BGP_IPV4_NODE, &neighbor_route_server_client_cmd); install_element(BGP_IPV4_NODE, &no_neighbor_route_server_client_cmd); install_element(BGP_IPV4M_NODE, &neighbor_route_server_client_cmd); install_element(BGP_IPV4M_NODE, &no_neighbor_route_server_client_cmd); install_element(BGP_IPV4L_NODE, &neighbor_route_server_client_cmd); install_element(BGP_IPV4L_NODE, &no_neighbor_route_server_client_cmd); install_element(BGP_IPV6_NODE, &neighbor_route_server_client_cmd); install_element(BGP_IPV6_NODE, &no_neighbor_route_server_client_cmd); install_element(BGP_IPV6M_NODE, &neighbor_route_server_client_cmd); install_element(BGP_IPV6M_NODE, &no_neighbor_route_server_client_cmd); install_element(BGP_IPV6L_NODE, &neighbor_route_server_client_cmd); install_element(BGP_IPV6L_NODE, &no_neighbor_route_server_client_cmd); install_element(BGP_VPNV4_NODE, &neighbor_route_server_client_cmd); install_element(BGP_VPNV4_NODE, &no_neighbor_route_server_client_cmd); install_element(BGP_VPNV6_NODE, &neighbor_route_server_client_cmd); install_element(BGP_VPNV6_NODE, &no_neighbor_route_server_client_cmd); install_element(BGP_EVPN_NODE, &neighbor_route_server_client_cmd); install_element(BGP_EVPN_NODE, &no_neighbor_route_server_client_cmd); install_element(BGP_FLOWSPECV4_NODE, &neighbor_route_server_client_cmd); install_element(BGP_FLOWSPECV4_NODE, &no_neighbor_route_server_client_cmd); install_element(BGP_FLOWSPECV6_NODE, &neighbor_route_server_client_cmd); install_element(BGP_FLOWSPECV6_NODE, &no_neighbor_route_server_client_cmd); /* "neighbor addpath-tx-all-paths" commands.*/ install_element(BGP_NODE, &neighbor_addpath_tx_all_paths_hidden_cmd); install_element(BGP_NODE, &no_neighbor_addpath_tx_all_paths_hidden_cmd); install_element(BGP_IPV4_NODE, &neighbor_addpath_tx_all_paths_cmd); install_element(BGP_IPV4_NODE, &no_neighbor_addpath_tx_all_paths_cmd); install_element(BGP_IPV4M_NODE, &neighbor_addpath_tx_all_paths_cmd); install_element(BGP_IPV4M_NODE, &no_neighbor_addpath_tx_all_paths_cmd); install_element(BGP_IPV4L_NODE, &neighbor_addpath_tx_all_paths_cmd); install_element(BGP_IPV4L_NODE, &no_neighbor_addpath_tx_all_paths_cmd); install_element(BGP_IPV6_NODE, &neighbor_addpath_tx_all_paths_cmd); install_element(BGP_IPV6_NODE, &no_neighbor_addpath_tx_all_paths_cmd); install_element(BGP_IPV6M_NODE, &neighbor_addpath_tx_all_paths_cmd); install_element(BGP_IPV6M_NODE, &no_neighbor_addpath_tx_all_paths_cmd); install_element(BGP_IPV6L_NODE, &neighbor_addpath_tx_all_paths_cmd); install_element(BGP_IPV6L_NODE, &no_neighbor_addpath_tx_all_paths_cmd); install_element(BGP_VPNV4_NODE, &neighbor_addpath_tx_all_paths_cmd); install_element(BGP_VPNV4_NODE, &no_neighbor_addpath_tx_all_paths_cmd); install_element(BGP_VPNV6_NODE, &neighbor_addpath_tx_all_paths_cmd); install_element(BGP_VPNV6_NODE, &no_neighbor_addpath_tx_all_paths_cmd); /* "neighbor addpath-tx-bestpath-per-AS" commands.*/ install_element(BGP_NODE, &neighbor_addpath_tx_bestpath_per_as_hidden_cmd); install_element(BGP_NODE, &no_neighbor_addpath_tx_bestpath_per_as_hidden_cmd); install_element(BGP_IPV4_NODE, &neighbor_addpath_tx_bestpath_per_as_cmd); install_element(BGP_IPV4_NODE, &no_neighbor_addpath_tx_bestpath_per_as_cmd); install_element(BGP_IPV4M_NODE, &neighbor_addpath_tx_bestpath_per_as_cmd); install_element(BGP_IPV4M_NODE, &no_neighbor_addpath_tx_bestpath_per_as_cmd); install_element(BGP_IPV4L_NODE, &neighbor_addpath_tx_bestpath_per_as_cmd); install_element(BGP_IPV4L_NODE, &no_neighbor_addpath_tx_bestpath_per_as_cmd); install_element(BGP_IPV6_NODE, &neighbor_addpath_tx_bestpath_per_as_cmd); install_element(BGP_IPV6_NODE, &no_neighbor_addpath_tx_bestpath_per_as_cmd); install_element(BGP_IPV6M_NODE, &neighbor_addpath_tx_bestpath_per_as_cmd); install_element(BGP_IPV6M_NODE, &no_neighbor_addpath_tx_bestpath_per_as_cmd); install_element(BGP_IPV6L_NODE, &neighbor_addpath_tx_bestpath_per_as_cmd); install_element(BGP_IPV6L_NODE, &no_neighbor_addpath_tx_bestpath_per_as_cmd); install_element(BGP_VPNV4_NODE, &neighbor_addpath_tx_bestpath_per_as_cmd); install_element(BGP_VPNV4_NODE, &no_neighbor_addpath_tx_bestpath_per_as_cmd); install_element(BGP_VPNV6_NODE, &neighbor_addpath_tx_bestpath_per_as_cmd); install_element(BGP_VPNV6_NODE, &no_neighbor_addpath_tx_bestpath_per_as_cmd); /* "neighbor passive" commands. */ install_element(BGP_NODE, &neighbor_passive_cmd); install_element(BGP_NODE, &no_neighbor_passive_cmd); /* "neighbor shutdown" commands. */ install_element(BGP_NODE, &neighbor_shutdown_cmd); install_element(BGP_NODE, &no_neighbor_shutdown_cmd); install_element(BGP_NODE, &neighbor_shutdown_msg_cmd); install_element(BGP_NODE, &no_neighbor_shutdown_msg_cmd); /* "neighbor capability extended-nexthop" commands.*/ install_element(BGP_NODE, &neighbor_capability_enhe_cmd); install_element(BGP_NODE, &no_neighbor_capability_enhe_cmd); /* "neighbor capability orf prefix-list" commands.*/ install_element(BGP_NODE, &neighbor_capability_orf_prefix_hidden_cmd); install_element(BGP_NODE, &no_neighbor_capability_orf_prefix_hidden_cmd); install_element(BGP_IPV4_NODE, &neighbor_capability_orf_prefix_cmd); install_element(BGP_IPV4_NODE, &no_neighbor_capability_orf_prefix_cmd); install_element(BGP_IPV4M_NODE, &neighbor_capability_orf_prefix_cmd); install_element(BGP_IPV4M_NODE, &no_neighbor_capability_orf_prefix_cmd); install_element(BGP_IPV4L_NODE, &neighbor_capability_orf_prefix_cmd); install_element(BGP_IPV4L_NODE, &no_neighbor_capability_orf_prefix_cmd); install_element(BGP_IPV6_NODE, &neighbor_capability_orf_prefix_cmd); install_element(BGP_IPV6_NODE, &no_neighbor_capability_orf_prefix_cmd); install_element(BGP_IPV6M_NODE, &neighbor_capability_orf_prefix_cmd); install_element(BGP_IPV6M_NODE, &no_neighbor_capability_orf_prefix_cmd); install_element(BGP_IPV6L_NODE, &neighbor_capability_orf_prefix_cmd); install_element(BGP_IPV6L_NODE, &no_neighbor_capability_orf_prefix_cmd); /* "neighbor capability dynamic" commands.*/ install_element(BGP_NODE, &neighbor_capability_dynamic_cmd); install_element(BGP_NODE, &no_neighbor_capability_dynamic_cmd); /* "neighbor dont-capability-negotiate" commands. */ install_element(BGP_NODE, &neighbor_dont_capability_negotiate_cmd); install_element(BGP_NODE, &no_neighbor_dont_capability_negotiate_cmd); /* "neighbor ebgp-multihop" commands. */ install_element(BGP_NODE, &neighbor_ebgp_multihop_cmd); install_element(BGP_NODE, &neighbor_ebgp_multihop_ttl_cmd); install_element(BGP_NODE, &no_neighbor_ebgp_multihop_cmd); /* "neighbor disable-connected-check" commands. */ install_element(BGP_NODE, &neighbor_disable_connected_check_cmd); install_element(BGP_NODE, &no_neighbor_disable_connected_check_cmd); /* "neighbor enforce-first-as" commands. */ install_element(BGP_NODE, &neighbor_enforce_first_as_cmd); install_element(BGP_NODE, &no_neighbor_enforce_first_as_cmd); /* "neighbor description" commands. */ install_element(BGP_NODE, &neighbor_description_cmd); install_element(BGP_NODE, &no_neighbor_description_cmd); install_element(BGP_NODE, &no_neighbor_description_comment_cmd); /* "neighbor update-source" commands. "*/ install_element(BGP_NODE, &neighbor_update_source_cmd); install_element(BGP_NODE, &no_neighbor_update_source_cmd); /* "neighbor default-originate" commands. */ install_element(BGP_NODE, &neighbor_default_originate_hidden_cmd); install_element(BGP_NODE, &neighbor_default_originate_rmap_hidden_cmd); install_element(BGP_NODE, &no_neighbor_default_originate_hidden_cmd); install_element(BGP_IPV4_NODE, &neighbor_default_originate_cmd); install_element(BGP_IPV4_NODE, &neighbor_default_originate_rmap_cmd); install_element(BGP_IPV4_NODE, &no_neighbor_default_originate_cmd); install_element(BGP_IPV4M_NODE, &neighbor_default_originate_cmd); install_element(BGP_IPV4M_NODE, &neighbor_default_originate_rmap_cmd); install_element(BGP_IPV4M_NODE, &no_neighbor_default_originate_cmd); install_element(BGP_IPV4L_NODE, &neighbor_default_originate_cmd); install_element(BGP_IPV4L_NODE, &neighbor_default_originate_rmap_cmd); install_element(BGP_IPV4L_NODE, &no_neighbor_default_originate_cmd); install_element(BGP_IPV6_NODE, &neighbor_default_originate_cmd); install_element(BGP_IPV6_NODE, &neighbor_default_originate_rmap_cmd); install_element(BGP_IPV6_NODE, &no_neighbor_default_originate_cmd); install_element(BGP_IPV6M_NODE, &neighbor_default_originate_cmd); install_element(BGP_IPV6M_NODE, &neighbor_default_originate_rmap_cmd); install_element(BGP_IPV6M_NODE, &no_neighbor_default_originate_cmd); install_element(BGP_IPV6L_NODE, &neighbor_default_originate_cmd); install_element(BGP_IPV6L_NODE, &neighbor_default_originate_rmap_cmd); install_element(BGP_IPV6L_NODE, &no_neighbor_default_originate_cmd); /* "neighbor port" commands. */ install_element(BGP_NODE, &neighbor_port_cmd); install_element(BGP_NODE, &no_neighbor_port_cmd); /* "neighbor weight" commands. */ install_element(BGP_NODE, &neighbor_weight_hidden_cmd); install_element(BGP_NODE, &no_neighbor_weight_hidden_cmd); install_element(BGP_IPV4_NODE, &neighbor_weight_cmd); install_element(BGP_IPV4_NODE, &no_neighbor_weight_cmd); install_element(BGP_IPV4M_NODE, &neighbor_weight_cmd); install_element(BGP_IPV4M_NODE, &no_neighbor_weight_cmd); install_element(BGP_IPV4L_NODE, &neighbor_weight_cmd); install_element(BGP_IPV4L_NODE, &no_neighbor_weight_cmd); install_element(BGP_IPV6_NODE, &neighbor_weight_cmd); install_element(BGP_IPV6_NODE, &no_neighbor_weight_cmd); install_element(BGP_IPV6M_NODE, &neighbor_weight_cmd); install_element(BGP_IPV6M_NODE, &no_neighbor_weight_cmd); install_element(BGP_IPV6L_NODE, &neighbor_weight_cmd); install_element(BGP_IPV6L_NODE, &no_neighbor_weight_cmd); install_element(BGP_VPNV4_NODE, &neighbor_weight_cmd); install_element(BGP_VPNV4_NODE, &no_neighbor_weight_cmd); install_element(BGP_VPNV6_NODE, &neighbor_weight_cmd); install_element(BGP_VPNV6_NODE, &no_neighbor_weight_cmd); /* "neighbor override-capability" commands. */ install_element(BGP_NODE, &neighbor_override_capability_cmd); install_element(BGP_NODE, &no_neighbor_override_capability_cmd); /* "neighbor strict-capability-match" commands. */ install_element(BGP_NODE, &neighbor_strict_capability_cmd); install_element(BGP_NODE, &no_neighbor_strict_capability_cmd); /* "neighbor timers" commands. */ install_element(BGP_NODE, &neighbor_timers_cmd); install_element(BGP_NODE, &no_neighbor_timers_cmd); /* "neighbor timers connect" commands. */ install_element(BGP_NODE, &neighbor_timers_connect_cmd); install_element(BGP_NODE, &no_neighbor_timers_connect_cmd); /* "neighbor advertisement-interval" commands. */ install_element(BGP_NODE, &neighbor_advertise_interval_cmd); install_element(BGP_NODE, &no_neighbor_advertise_interval_cmd); /* "neighbor interface" commands. */ install_element(BGP_NODE, &neighbor_interface_cmd); install_element(BGP_NODE, &no_neighbor_interface_cmd); /* "neighbor distribute" commands. */ install_element(BGP_NODE, &neighbor_distribute_list_hidden_cmd); install_element(BGP_NODE, &no_neighbor_distribute_list_hidden_cmd); install_element(BGP_IPV4_NODE, &neighbor_distribute_list_cmd); install_element(BGP_IPV4_NODE, &no_neighbor_distribute_list_cmd); install_element(BGP_IPV4M_NODE, &neighbor_distribute_list_cmd); install_element(BGP_IPV4M_NODE, &no_neighbor_distribute_list_cmd); install_element(BGP_IPV4L_NODE, &neighbor_distribute_list_cmd); install_element(BGP_IPV4L_NODE, &no_neighbor_distribute_list_cmd); install_element(BGP_IPV6_NODE, &neighbor_distribute_list_cmd); install_element(BGP_IPV6_NODE, &no_neighbor_distribute_list_cmd); install_element(BGP_IPV6M_NODE, &neighbor_distribute_list_cmd); install_element(BGP_IPV6M_NODE, &no_neighbor_distribute_list_cmd); install_element(BGP_IPV6L_NODE, &neighbor_distribute_list_cmd); install_element(BGP_IPV6L_NODE, &no_neighbor_distribute_list_cmd); install_element(BGP_VPNV4_NODE, &neighbor_distribute_list_cmd); install_element(BGP_VPNV4_NODE, &no_neighbor_distribute_list_cmd); install_element(BGP_VPNV6_NODE, &neighbor_distribute_list_cmd); install_element(BGP_VPNV6_NODE, &no_neighbor_distribute_list_cmd); /* "neighbor prefix-list" commands. */ install_element(BGP_NODE, &neighbor_prefix_list_hidden_cmd); install_element(BGP_NODE, &no_neighbor_prefix_list_hidden_cmd); install_element(BGP_IPV4_NODE, &neighbor_prefix_list_cmd); install_element(BGP_IPV4_NODE, &no_neighbor_prefix_list_cmd); install_element(BGP_IPV4M_NODE, &neighbor_prefix_list_cmd); install_element(BGP_IPV4M_NODE, &no_neighbor_prefix_list_cmd); install_element(BGP_IPV4L_NODE, &neighbor_prefix_list_cmd); install_element(BGP_IPV4L_NODE, &no_neighbor_prefix_list_cmd); install_element(BGP_IPV6_NODE, &neighbor_prefix_list_cmd); install_element(BGP_IPV6_NODE, &no_neighbor_prefix_list_cmd); install_element(BGP_IPV6M_NODE, &neighbor_prefix_list_cmd); install_element(BGP_IPV6M_NODE, &no_neighbor_prefix_list_cmd); install_element(BGP_IPV6L_NODE, &neighbor_prefix_list_cmd); install_element(BGP_IPV6L_NODE, &no_neighbor_prefix_list_cmd); install_element(BGP_VPNV4_NODE, &neighbor_prefix_list_cmd); install_element(BGP_VPNV4_NODE, &no_neighbor_prefix_list_cmd); install_element(BGP_VPNV6_NODE, &neighbor_prefix_list_cmd); install_element(BGP_VPNV6_NODE, &no_neighbor_prefix_list_cmd); install_element(BGP_FLOWSPECV4_NODE, &neighbor_prefix_list_cmd); install_element(BGP_FLOWSPECV4_NODE, &no_neighbor_prefix_list_cmd); install_element(BGP_FLOWSPECV6_NODE, &neighbor_prefix_list_cmd); install_element(BGP_FLOWSPECV6_NODE, &no_neighbor_prefix_list_cmd); /* "neighbor filter-list" commands. */ install_element(BGP_NODE, &neighbor_filter_list_hidden_cmd); install_element(BGP_NODE, &no_neighbor_filter_list_hidden_cmd); install_element(BGP_IPV4_NODE, &neighbor_filter_list_cmd); install_element(BGP_IPV4_NODE, &no_neighbor_filter_list_cmd); install_element(BGP_IPV4M_NODE, &neighbor_filter_list_cmd); install_element(BGP_IPV4M_NODE, &no_neighbor_filter_list_cmd); install_element(BGP_IPV4L_NODE, &neighbor_filter_list_cmd); install_element(BGP_IPV4L_NODE, &no_neighbor_filter_list_cmd); install_element(BGP_IPV6_NODE, &neighbor_filter_list_cmd); install_element(BGP_IPV6_NODE, &no_neighbor_filter_list_cmd); install_element(BGP_IPV6M_NODE, &neighbor_filter_list_cmd); install_element(BGP_IPV6M_NODE, &no_neighbor_filter_list_cmd); install_element(BGP_IPV6L_NODE, &neighbor_filter_list_cmd); install_element(BGP_IPV6L_NODE, &no_neighbor_filter_list_cmd); install_element(BGP_VPNV4_NODE, &neighbor_filter_list_cmd); install_element(BGP_VPNV4_NODE, &no_neighbor_filter_list_cmd); install_element(BGP_VPNV6_NODE, &neighbor_filter_list_cmd); install_element(BGP_VPNV6_NODE, &no_neighbor_filter_list_cmd); install_element(BGP_FLOWSPECV4_NODE, &neighbor_filter_list_cmd); install_element(BGP_FLOWSPECV4_NODE, &no_neighbor_filter_list_cmd); install_element(BGP_FLOWSPECV6_NODE, &neighbor_filter_list_cmd); install_element(BGP_FLOWSPECV6_NODE, &no_neighbor_filter_list_cmd); /* "neighbor route-map" commands. */ install_element(BGP_NODE, &neighbor_route_map_hidden_cmd); install_element(BGP_NODE, &no_neighbor_route_map_hidden_cmd); install_element(BGP_IPV4_NODE, &neighbor_route_map_cmd); install_element(BGP_IPV4_NODE, &no_neighbor_route_map_cmd); install_element(BGP_IPV4M_NODE, &neighbor_route_map_cmd); install_element(BGP_IPV4M_NODE, &no_neighbor_route_map_cmd); install_element(BGP_IPV4L_NODE, &neighbor_route_map_cmd); install_element(BGP_IPV4L_NODE, &no_neighbor_route_map_cmd); install_element(BGP_IPV6_NODE, &neighbor_route_map_cmd); install_element(BGP_IPV6_NODE, &no_neighbor_route_map_cmd); install_element(BGP_IPV6M_NODE, &neighbor_route_map_cmd); install_element(BGP_IPV6M_NODE, &no_neighbor_route_map_cmd); install_element(BGP_IPV6L_NODE, &neighbor_route_map_cmd); install_element(BGP_IPV6L_NODE, &no_neighbor_route_map_cmd); install_element(BGP_VPNV4_NODE, &neighbor_route_map_cmd); install_element(BGP_VPNV4_NODE, &no_neighbor_route_map_cmd); install_element(BGP_VPNV6_NODE, &neighbor_route_map_cmd); install_element(BGP_VPNV6_NODE, &no_neighbor_route_map_cmd); install_element(BGP_FLOWSPECV4_NODE, &neighbor_route_map_cmd); install_element(BGP_FLOWSPECV4_NODE, &no_neighbor_route_map_cmd); install_element(BGP_FLOWSPECV6_NODE, &neighbor_route_map_cmd); install_element(BGP_FLOWSPECV6_NODE, &no_neighbor_route_map_cmd); install_element(BGP_EVPN_NODE, &neighbor_route_map_cmd); install_element(BGP_EVPN_NODE, &no_neighbor_route_map_cmd); /* "neighbor unsuppress-map" commands. */ install_element(BGP_NODE, &neighbor_unsuppress_map_hidden_cmd); install_element(BGP_NODE, &no_neighbor_unsuppress_map_hidden_cmd); install_element(BGP_IPV4_NODE, &neighbor_unsuppress_map_cmd); install_element(BGP_IPV4_NODE, &no_neighbor_unsuppress_map_cmd); install_element(BGP_IPV4M_NODE, &neighbor_unsuppress_map_cmd); install_element(BGP_IPV4M_NODE, &no_neighbor_unsuppress_map_cmd); install_element(BGP_IPV4L_NODE, &neighbor_unsuppress_map_cmd); install_element(BGP_IPV4L_NODE, &no_neighbor_unsuppress_map_cmd); install_element(BGP_IPV6_NODE, &neighbor_unsuppress_map_cmd); install_element(BGP_IPV6_NODE, &no_neighbor_unsuppress_map_cmd); install_element(BGP_IPV6M_NODE, &neighbor_unsuppress_map_cmd); install_element(BGP_IPV6M_NODE, &no_neighbor_unsuppress_map_cmd); install_element(BGP_IPV6L_NODE, &neighbor_unsuppress_map_cmd); install_element(BGP_IPV6L_NODE, &no_neighbor_unsuppress_map_cmd); install_element(BGP_VPNV4_NODE, &neighbor_unsuppress_map_cmd); install_element(BGP_VPNV4_NODE, &no_neighbor_unsuppress_map_cmd); install_element(BGP_VPNV6_NODE, &neighbor_unsuppress_map_cmd); install_element(BGP_VPNV6_NODE, &no_neighbor_unsuppress_map_cmd); /* "neighbor maximum-prefix" commands. */ install_element(BGP_NODE, &neighbor_maximum_prefix_hidden_cmd); install_element(BGP_NODE, &neighbor_maximum_prefix_threshold_hidden_cmd); install_element(BGP_NODE, &neighbor_maximum_prefix_warning_hidden_cmd); install_element(BGP_NODE, &neighbor_maximum_prefix_threshold_warning_hidden_cmd); install_element(BGP_NODE, &neighbor_maximum_prefix_restart_hidden_cmd); install_element(BGP_NODE, &neighbor_maximum_prefix_threshold_restart_hidden_cmd); install_element(BGP_NODE, &no_neighbor_maximum_prefix_hidden_cmd); install_element(BGP_IPV4_NODE, &neighbor_maximum_prefix_cmd); install_element(BGP_IPV4_NODE, &neighbor_maximum_prefix_threshold_cmd); install_element(BGP_IPV4_NODE, &neighbor_maximum_prefix_warning_cmd); install_element(BGP_IPV4_NODE, &neighbor_maximum_prefix_threshold_warning_cmd); install_element(BGP_IPV4_NODE, &neighbor_maximum_prefix_restart_cmd); install_element(BGP_IPV4_NODE, &neighbor_maximum_prefix_threshold_restart_cmd); install_element(BGP_IPV4_NODE, &no_neighbor_maximum_prefix_cmd); install_element(BGP_IPV4M_NODE, &neighbor_maximum_prefix_cmd); install_element(BGP_IPV4M_NODE, &neighbor_maximum_prefix_threshold_cmd); install_element(BGP_IPV4M_NODE, &neighbor_maximum_prefix_warning_cmd); install_element(BGP_IPV4M_NODE, &neighbor_maximum_prefix_threshold_warning_cmd); install_element(BGP_IPV4M_NODE, &neighbor_maximum_prefix_restart_cmd); install_element(BGP_IPV4M_NODE, &neighbor_maximum_prefix_threshold_restart_cmd); install_element(BGP_IPV4M_NODE, &no_neighbor_maximum_prefix_cmd); install_element(BGP_IPV4L_NODE, &neighbor_maximum_prefix_cmd); install_element(BGP_IPV4L_NODE, &neighbor_maximum_prefix_threshold_cmd); install_element(BGP_IPV4L_NODE, &neighbor_maximum_prefix_warning_cmd); install_element(BGP_IPV4L_NODE, &neighbor_maximum_prefix_threshold_warning_cmd); install_element(BGP_IPV4L_NODE, &neighbor_maximum_prefix_restart_cmd); install_element(BGP_IPV4L_NODE, &neighbor_maximum_prefix_threshold_restart_cmd); install_element(BGP_IPV4L_NODE, &no_neighbor_maximum_prefix_cmd); install_element(BGP_IPV6_NODE, &neighbor_maximum_prefix_cmd); install_element(BGP_IPV6_NODE, &neighbor_maximum_prefix_threshold_cmd); install_element(BGP_IPV6_NODE, &neighbor_maximum_prefix_warning_cmd); install_element(BGP_IPV6_NODE, &neighbor_maximum_prefix_threshold_warning_cmd); install_element(BGP_IPV6_NODE, &neighbor_maximum_prefix_restart_cmd); install_element(BGP_IPV6_NODE, &neighbor_maximum_prefix_threshold_restart_cmd); install_element(BGP_IPV6_NODE, &no_neighbor_maximum_prefix_cmd); install_element(BGP_IPV6M_NODE, &neighbor_maximum_prefix_cmd); install_element(BGP_IPV6M_NODE, &neighbor_maximum_prefix_threshold_cmd); install_element(BGP_IPV6M_NODE, &neighbor_maximum_prefix_warning_cmd); install_element(BGP_IPV6M_NODE, &neighbor_maximum_prefix_threshold_warning_cmd); install_element(BGP_IPV6M_NODE, &neighbor_maximum_prefix_restart_cmd); install_element(BGP_IPV6M_NODE, &neighbor_maximum_prefix_threshold_restart_cmd); install_element(BGP_IPV6M_NODE, &no_neighbor_maximum_prefix_cmd); install_element(BGP_IPV6L_NODE, &neighbor_maximum_prefix_cmd); install_element(BGP_IPV6L_NODE, &neighbor_maximum_prefix_threshold_cmd); install_element(BGP_IPV6L_NODE, &neighbor_maximum_prefix_warning_cmd); install_element(BGP_IPV6L_NODE, &neighbor_maximum_prefix_threshold_warning_cmd); install_element(BGP_IPV6L_NODE, &neighbor_maximum_prefix_restart_cmd); install_element(BGP_IPV6L_NODE, &neighbor_maximum_prefix_threshold_restart_cmd); install_element(BGP_IPV6L_NODE, &no_neighbor_maximum_prefix_cmd); install_element(BGP_VPNV4_NODE, &neighbor_maximum_prefix_cmd); install_element(BGP_VPNV4_NODE, &neighbor_maximum_prefix_threshold_cmd); install_element(BGP_VPNV4_NODE, &neighbor_maximum_prefix_warning_cmd); install_element(BGP_VPNV4_NODE, &neighbor_maximum_prefix_threshold_warning_cmd); install_element(BGP_VPNV4_NODE, &neighbor_maximum_prefix_restart_cmd); install_element(BGP_VPNV4_NODE, &neighbor_maximum_prefix_threshold_restart_cmd); install_element(BGP_VPNV4_NODE, &no_neighbor_maximum_prefix_cmd); install_element(BGP_VPNV6_NODE, &neighbor_maximum_prefix_cmd); install_element(BGP_VPNV6_NODE, &neighbor_maximum_prefix_threshold_cmd); install_element(BGP_VPNV6_NODE, &neighbor_maximum_prefix_warning_cmd); install_element(BGP_VPNV6_NODE, &neighbor_maximum_prefix_threshold_warning_cmd); install_element(BGP_VPNV6_NODE, &neighbor_maximum_prefix_restart_cmd); install_element(BGP_VPNV6_NODE, &neighbor_maximum_prefix_threshold_restart_cmd); install_element(BGP_VPNV6_NODE, &no_neighbor_maximum_prefix_cmd); /* "neighbor allowas-in" */ install_element(BGP_NODE, &neighbor_allowas_in_hidden_cmd); install_element(BGP_NODE, &no_neighbor_allowas_in_hidden_cmd); install_element(BGP_IPV4_NODE, &neighbor_allowas_in_cmd); install_element(BGP_IPV4_NODE, &no_neighbor_allowas_in_cmd); install_element(BGP_IPV4M_NODE, &neighbor_allowas_in_cmd); install_element(BGP_IPV4M_NODE, &no_neighbor_allowas_in_cmd); install_element(BGP_IPV4L_NODE, &neighbor_allowas_in_cmd); install_element(BGP_IPV4L_NODE, &no_neighbor_allowas_in_cmd); install_element(BGP_IPV6_NODE, &neighbor_allowas_in_cmd); install_element(BGP_IPV6_NODE, &no_neighbor_allowas_in_cmd); install_element(BGP_IPV6M_NODE, &neighbor_allowas_in_cmd); install_element(BGP_IPV6M_NODE, &no_neighbor_allowas_in_cmd); install_element(BGP_IPV6L_NODE, &neighbor_allowas_in_cmd); install_element(BGP_IPV6L_NODE, &no_neighbor_allowas_in_cmd); install_element(BGP_VPNV4_NODE, &neighbor_allowas_in_cmd); install_element(BGP_VPNV4_NODE, &no_neighbor_allowas_in_cmd); install_element(BGP_VPNV6_NODE, &neighbor_allowas_in_cmd); install_element(BGP_VPNV6_NODE, &no_neighbor_allowas_in_cmd); install_element(BGP_EVPN_NODE, &neighbor_allowas_in_cmd); install_element(BGP_EVPN_NODE, &no_neighbor_allowas_in_cmd); /* address-family commands. */ install_element(BGP_NODE, &address_family_ipv4_safi_cmd); install_element(BGP_NODE, &address_family_ipv6_safi_cmd); #ifdef KEEP_OLD_VPN_COMMANDS install_element(BGP_NODE, &address_family_vpnv4_cmd); install_element(BGP_NODE, &address_family_vpnv6_cmd); #endif /* KEEP_OLD_VPN_COMMANDS */ install_element(BGP_NODE, &address_family_evpn_cmd); /* "exit-address-family" command. */ install_element(BGP_IPV4_NODE, &exit_address_family_cmd); install_element(BGP_IPV4M_NODE, &exit_address_family_cmd); install_element(BGP_IPV4L_NODE, &exit_address_family_cmd); install_element(BGP_IPV6_NODE, &exit_address_family_cmd); install_element(BGP_IPV6M_NODE, &exit_address_family_cmd); install_element(BGP_IPV6L_NODE, &exit_address_family_cmd); install_element(BGP_VPNV4_NODE, &exit_address_family_cmd); install_element(BGP_VPNV6_NODE, &exit_address_family_cmd); install_element(BGP_FLOWSPECV4_NODE, &exit_address_family_cmd); install_element(BGP_FLOWSPECV6_NODE, &exit_address_family_cmd); install_element(BGP_EVPN_NODE, &exit_address_family_cmd); /* "clear ip bgp commands" */ install_element(ENABLE_NODE, &clear_ip_bgp_all_cmd); /* clear ip bgp prefix */ install_element(ENABLE_NODE, &clear_ip_bgp_prefix_cmd); install_element(ENABLE_NODE, &clear_bgp_ipv6_safi_prefix_cmd); install_element(ENABLE_NODE, &clear_bgp_instance_ipv6_safi_prefix_cmd); /* "show [ip] bgp summary" commands. */ install_element(VIEW_NODE, &show_bgp_instance_all_ipv6_updgrps_cmd); install_element(VIEW_NODE, &show_bgp_l2vpn_evpn_updgrps_cmd); install_element(VIEW_NODE, &show_bgp_instance_updgrps_stats_cmd); install_element(VIEW_NODE, &show_bgp_updgrps_stats_cmd); install_element(VIEW_NODE, &show_ip_bgp_instance_updgrps_adj_s_cmd); install_element(VIEW_NODE, &show_ip_bgp_summary_cmd); install_element(VIEW_NODE, &show_ip_bgp_updgrps_cmd); /* "show [ip] bgp neighbors" commands. */ install_element(VIEW_NODE, &show_ip_bgp_neighbors_cmd); /* "show [ip] bgp peer-group" commands. */ install_element(VIEW_NODE, &show_ip_bgp_peer_groups_cmd); /* "show [ip] bgp paths" commands. */ install_element(VIEW_NODE, &show_ip_bgp_paths_cmd); /* "show [ip] bgp community" commands. */ install_element(VIEW_NODE, &show_ip_bgp_community_info_cmd); /* "show ip bgp large-community" commands. */ install_element(VIEW_NODE, &show_ip_bgp_lcommunity_info_cmd); /* "show [ip] bgp attribute-info" commands. */ install_element(VIEW_NODE, &show_ip_bgp_attr_info_cmd); /* "show [ip] bgp route-leak" command */ install_element(VIEW_NODE, &show_ip_bgp_route_leak_cmd); /* "redistribute" commands. */ install_element(BGP_NODE, &bgp_redistribute_ipv4_hidden_cmd); install_element(BGP_NODE, &no_bgp_redistribute_ipv4_hidden_cmd); install_element(BGP_NODE, &bgp_redistribute_ipv4_rmap_hidden_cmd); install_element(BGP_NODE, &bgp_redistribute_ipv4_metric_hidden_cmd); install_element(BGP_NODE, &bgp_redistribute_ipv4_rmap_metric_hidden_cmd); install_element(BGP_NODE, &bgp_redistribute_ipv4_metric_rmap_hidden_cmd); install_element(BGP_NODE, &bgp_redistribute_ipv4_ospf_hidden_cmd); install_element(BGP_NODE, &no_bgp_redistribute_ipv4_ospf_hidden_cmd); install_element(BGP_NODE, &bgp_redistribute_ipv4_ospf_rmap_hidden_cmd); install_element(BGP_NODE, &bgp_redistribute_ipv4_ospf_metric_hidden_cmd); install_element(BGP_NODE, &bgp_redistribute_ipv4_ospf_rmap_metric_hidden_cmd); install_element(BGP_NODE, &bgp_redistribute_ipv4_ospf_metric_rmap_hidden_cmd); install_element(BGP_IPV4_NODE, &bgp_redistribute_ipv4_cmd); install_element(BGP_IPV4_NODE, &no_bgp_redistribute_ipv4_cmd); install_element(BGP_IPV4_NODE, &bgp_redistribute_ipv4_rmap_cmd); install_element(BGP_IPV4_NODE, &bgp_redistribute_ipv4_metric_cmd); install_element(BGP_IPV4_NODE, &bgp_redistribute_ipv4_rmap_metric_cmd); install_element(BGP_IPV4_NODE, &bgp_redistribute_ipv4_metric_rmap_cmd); install_element(BGP_IPV4_NODE, &bgp_redistribute_ipv4_ospf_cmd); install_element(BGP_IPV4_NODE, &no_bgp_redistribute_ipv4_ospf_cmd); install_element(BGP_IPV4_NODE, &bgp_redistribute_ipv4_ospf_rmap_cmd); install_element(BGP_IPV4_NODE, &bgp_redistribute_ipv4_ospf_metric_cmd); install_element(BGP_IPV4_NODE, &bgp_redistribute_ipv4_ospf_rmap_metric_cmd); install_element(BGP_IPV4_NODE, &bgp_redistribute_ipv4_ospf_metric_rmap_cmd); install_element(BGP_IPV6_NODE, &bgp_redistribute_ipv6_cmd); install_element(BGP_IPV6_NODE, &no_bgp_redistribute_ipv6_cmd); install_element(BGP_IPV6_NODE, &bgp_redistribute_ipv6_rmap_cmd); install_element(BGP_IPV6_NODE, &bgp_redistribute_ipv6_metric_cmd); install_element(BGP_IPV6_NODE, &bgp_redistribute_ipv6_rmap_metric_cmd); install_element(BGP_IPV6_NODE, &bgp_redistribute_ipv6_metric_rmap_cmd); /* import|export vpn [route-map WORD] */ install_element(BGP_IPV4_NODE, &bgp_imexport_vpn_cmd); install_element(BGP_IPV6_NODE, &bgp_imexport_vpn_cmd); install_element(BGP_IPV4_NODE, &bgp_imexport_vrf_cmd); install_element(BGP_IPV6_NODE, &bgp_imexport_vrf_cmd); /* ttl_security commands */ install_element(BGP_NODE, &neighbor_ttl_security_cmd); install_element(BGP_NODE, &no_neighbor_ttl_security_cmd); /* "show [ip] bgp memory" commands. */ install_element(VIEW_NODE, &show_bgp_memory_cmd); /* "show bgp martian next-hop" */ install_element(VIEW_NODE, &show_bgp_martian_nexthop_db_cmd); install_element(VIEW_NODE, &show_bgp_mac_hash_cmd); /* "show [ip] bgp views" commands. */ install_element(VIEW_NODE, &show_bgp_views_cmd); /* "show [ip] bgp vrfs" commands. */ install_element(VIEW_NODE, &show_bgp_vrfs_cmd); /* Community-list. */ community_list_vty(); /* vpn-policy commands */ install_element(BGP_IPV4_NODE, &af_rd_vpn_export_cmd); install_element(BGP_IPV6_NODE, &af_rd_vpn_export_cmd); install_element(BGP_IPV4_NODE, &af_label_vpn_export_cmd); install_element(BGP_IPV6_NODE, &af_label_vpn_export_cmd); install_element(BGP_IPV4_NODE, &af_nexthop_vpn_export_cmd); install_element(BGP_IPV6_NODE, &af_nexthop_vpn_export_cmd); install_element(BGP_IPV4_NODE, &af_rt_vpn_imexport_cmd); install_element(BGP_IPV6_NODE, &af_rt_vpn_imexport_cmd); install_element(BGP_IPV4_NODE, &af_route_map_vpn_imexport_cmd); install_element(BGP_IPV6_NODE, &af_route_map_vpn_imexport_cmd); install_element(BGP_IPV4_NODE, &af_import_vrf_route_map_cmd); install_element(BGP_IPV6_NODE, &af_import_vrf_route_map_cmd); install_element(BGP_IPV4_NODE, &af_routetarget_import_cmd); install_element(BGP_IPV6_NODE, &af_routetarget_import_cmd); install_element(BGP_IPV4_NODE, &af_no_rd_vpn_export_cmd); install_element(BGP_IPV6_NODE, &af_no_rd_vpn_export_cmd); install_element(BGP_IPV4_NODE, &af_no_label_vpn_export_cmd); install_element(BGP_IPV6_NODE, &af_no_label_vpn_export_cmd); install_element(BGP_IPV4_NODE, &af_no_nexthop_vpn_export_cmd); install_element(BGP_IPV6_NODE, &af_no_nexthop_vpn_export_cmd); install_element(BGP_IPV4_NODE, &af_no_rt_vpn_imexport_cmd); install_element(BGP_IPV6_NODE, &af_no_rt_vpn_imexport_cmd); install_element(BGP_IPV4_NODE, &af_no_route_map_vpn_imexport_cmd); install_element(BGP_IPV6_NODE, &af_no_route_map_vpn_imexport_cmd); install_element(BGP_IPV4_NODE, &af_no_import_vrf_route_map_cmd); install_element(BGP_IPV6_NODE, &af_no_import_vrf_route_map_cmd); } #include "memory.h" #include "bgp_regex.h" #include "bgp_clist.h" #include "bgp_ecommunity.h" /* VTY functions. */ /* Direction value to string conversion. */ static const char *community_direct_str(int direct) { switch (direct) { case COMMUNITY_DENY: return "deny"; case COMMUNITY_PERMIT: return "permit"; default: return "unknown"; } } /* Display error string. */ static void community_list_perror(struct vty *vty, int ret) { switch (ret) { case COMMUNITY_LIST_ERR_CANT_FIND_LIST: vty_out(vty, "%% Can't find community-list\n"); break; case COMMUNITY_LIST_ERR_MALFORMED_VAL: vty_out(vty, "%% Malformed community-list value\n"); break; case COMMUNITY_LIST_ERR_STANDARD_CONFLICT: vty_out(vty, "%% Community name conflict, previously defined as standard community\n"); break; case COMMUNITY_LIST_ERR_EXPANDED_CONFLICT: vty_out(vty, "%% Community name conflict, previously defined as expanded community\n"); break; } } /* "community-list" keyword help string. */ #define COMMUNITY_LIST_STR "Add a community list entry\n" /*community-list standard */ DEFUN (community_list_standard, bgp_community_list_standard_cmd, "bgp community-list <(1-99)|standard WORD> AA:NN...", BGP_STR COMMUNITY_LIST_STR "Community list number (standard)\n" "Add an standard community-list entry\n" "Community list name\n" "Specify community to reject\n" "Specify community to accept\n" COMMUNITY_VAL_STR) { char *cl_name_or_number = NULL; int direct = 0; int style = COMMUNITY_LIST_STANDARD; int idx = 0; if (argv_find(argv, argc, "ip", &idx)) { vty_out(vty, "This config option is deprecated, and is scheduled for removal.\n"); vty_out(vty, "if you are using this please migrate to the below command.\n"); vty_out(vty, "'bgp community-list <(1-99)|(100-500)|standard|expanded> AA:NN'\n"); zlog_warn("Deprecated option: 'ip community-list <(1-99)|(100-500)|standard|expanded> AA:NN' being used"); } argv_find(argv, argc, "(1-99)", &idx); argv_find(argv, argc, "WORD", &idx); cl_name_or_number = argv[idx]->arg; direct = argv_find(argv, argc, "permit", &idx) ? COMMUNITY_PERMIT : COMMUNITY_DENY; argv_find(argv, argc, "AA:NN", &idx); char *str = argv_concat(argv, argc, idx); int ret = community_list_set(bgp_clist, cl_name_or_number, str, direct, style); XFREE(MTYPE_TMP, str); if (ret < 0) { /* Display error string. */ community_list_perror(vty, ret); return CMD_WARNING_CONFIG_FAILED; } return CMD_SUCCESS; } #if CONFDATE > 20191005 CPP_NOTICE("bgpd: remove deprecated 'ip community-list <(1-99)|(100-500)|standard|expanded> AA:NN' command") #endif ALIAS (community_list_standard, ip_community_list_standard_cmd, "ip community-list <(1-99)|standard WORD> AA:NN...", IP_STR COMMUNITY_LIST_STR "Community list number (standard)\n" "Add an standard community-list entry\n" "Community list name\n" "Specify community to reject\n" "Specify community to accept\n" COMMUNITY_VAL_STR) DEFUN (no_community_list_standard_all, no_bgp_community_list_standard_all_cmd, "no bgp community-list <(1-99)|standard WORD> AA:NN...", NO_STR BGP_STR COMMUNITY_LIST_STR "Community list number (standard)\n" "Add an standard community-list entry\n" "Community list name\n" "Specify community to reject\n" "Specify community to accept\n" COMMUNITY_VAL_STR) { char *cl_name_or_number = NULL; char *str = NULL; int direct = 0; int style = COMMUNITY_LIST_STANDARD; int idx = 0; if (argv_find(argv, argc, "ip", &idx)) { vty_out(vty, "This config option is deprecated, and is scheduled for removal\n"); vty_out(vty, "if you are using this please migrate to the below command.\n"); vty_out(vty, "'no bgp community-list <(1-99)|(100-500)|standard|expanded> AA:NN'\n"); zlog_warn("Deprecated option: 'no ip community-list <(1-99)|(100-500)|standard|expanded> |AA:NN' being used"); } argv_find(argv, argc, "permit", &idx); argv_find(argv, argc, "deny", &idx); if (idx) { direct = argv_find(argv, argc, "permit", &idx) ? COMMUNITY_PERMIT : COMMUNITY_DENY; idx = 0; argv_find(argv, argc, "AA:NN", &idx); str = argv_concat(argv, argc, idx); } idx = 0; argv_find(argv, argc, "(1-99)", &idx); argv_find(argv, argc, "WORD", &idx); cl_name_or_number = argv[idx]->arg; int ret = community_list_unset(bgp_clist, cl_name_or_number, str, direct, style); XFREE(MTYPE_TMP, str); if (ret < 0) { community_list_perror(vty, ret); return CMD_WARNING_CONFIG_FAILED; } return CMD_SUCCESS; } ALIAS (no_community_list_standard_all, no_ip_community_list_standard_all_cmd, "no ip community-list <(1-99)|standard WORD> AA:NN...", NO_STR IP_STR COMMUNITY_LIST_STR "Community list number (standard)\n" "Add an standard community-list entry\n" "Community list name\n" "Specify community to reject\n" "Specify community to accept\n" COMMUNITY_VAL_STR) ALIAS(no_community_list_standard_all, no_bgp_community_list_standard_all_list_cmd, "no bgp community-list <(1-99)|standard WORD>", NO_STR BGP_STR COMMUNITY_LIST_STR "Community list number (standard)\n" "Add an standard community-list entry\n" "Community list name\n") ALIAS(no_community_list_standard_all, no_ip_community_list_standard_all_list_cmd, "no ip community-list <(1-99)|standard WORD>", NO_STR BGP_STR COMMUNITY_LIST_STR "Community list number (standard)\n" "Add an standard community-list entry\n" "Community list name\n") /*community-list expanded */ DEFUN (community_list_expanded_all, bgp_community_list_expanded_all_cmd, "bgp community-list <(100-500)|expanded WORD> AA:NN...", BGP_STR COMMUNITY_LIST_STR "Community list number (expanded)\n" "Add an expanded community-list entry\n" "Community list name\n" "Specify community to reject\n" "Specify community to accept\n" COMMUNITY_VAL_STR) { char *cl_name_or_number = NULL; int direct = 0; int style = COMMUNITY_LIST_EXPANDED; int idx = 0; if (argv_find(argv, argc, "ip", &idx)) { vty_out(vty, "This config option is deprecated, and is scheduled for removal.\n"); vty_out(vty, "if you are using this please migrate to the below command.\n"); vty_out(vty, "'bgp community-list <(1-99)|(100-500)|standard|expanded> AA:NN'\n"); zlog_warn("Deprecated option: 'ip community-list <(1-99)|(100-500)|standard|expanded> AA:NN' being used"); } argv_find(argv, argc, "(100-500)", &idx); argv_find(argv, argc, "WORD", &idx); cl_name_or_number = argv[idx]->arg; direct = argv_find(argv, argc, "permit", &idx) ? COMMUNITY_PERMIT : COMMUNITY_DENY; argv_find(argv, argc, "AA:NN", &idx); char *str = argv_concat(argv, argc, idx); int ret = community_list_set(bgp_clist, cl_name_or_number, str, direct, style); XFREE(MTYPE_TMP, str); if (ret < 0) { /* Display error string. */ community_list_perror(vty, ret); return CMD_WARNING_CONFIG_FAILED; } return CMD_SUCCESS; } ALIAS (community_list_expanded_all, ip_community_list_expanded_all_cmd, "ip community-list <(100-500)|expanded WORD> AA:NN...", IP_STR COMMUNITY_LIST_STR "Community list number (expanded)\n" "Add an expanded community-list entry\n" "Community list name\n" "Specify community to reject\n" "Specify community to accept\n" COMMUNITY_VAL_STR) DEFUN (no_community_list_expanded_all, no_bgp_community_list_expanded_all_cmd, "no bgp community-list <(100-500)|expanded WORD> AA:NN...", NO_STR BGP_STR COMMUNITY_LIST_STR "Community list number (expanded)\n" "Add an expanded community-list entry\n" "Community list name\n" "Specify community to reject\n" "Specify community to accept\n" COMMUNITY_VAL_STR) { char *cl_name_or_number = NULL; char *str = NULL; int direct = 0; int style = COMMUNITY_LIST_EXPANDED; int idx = 0; if (argv_find(argv, argc, "ip", &idx)) { vty_out(vty, "This config option is deprecated, and is scheduled for removal.\n"); vty_out(vty, "if you are using this please migrate to the below command.\n"); vty_out(vty, "'no bgp community-list <(1-99)|(100-500)|standard|expanded> AA:NN'\n"); zlog_warn("Deprecated option: 'no ip community-list <(1-99)|(100-500)|standard|expanded> AA:NN' being used"); } idx = 0; argv_find(argv, argc, "permit", &idx); argv_find(argv, argc, "deny", &idx); if (idx) { direct = argv_find(argv, argc, "permit", &idx) ? COMMUNITY_PERMIT : COMMUNITY_DENY; idx = 0; argv_find(argv, argc, "AA:NN", &idx); str = argv_concat(argv, argc, idx); } idx = 0; argv_find(argv, argc, "(100-500)", &idx); argv_find(argv, argc, "WORD", &idx); cl_name_or_number = argv[idx]->arg; int ret = community_list_unset(bgp_clist, cl_name_or_number, str, direct, style); XFREE(MTYPE_TMP, str); if (ret < 0) { community_list_perror(vty, ret); return CMD_WARNING_CONFIG_FAILED; } return CMD_SUCCESS; } ALIAS (no_community_list_expanded_all, no_ip_community_list_expanded_all_cmd, "no ip community-list <(100-500)|expanded WORD> AA:NN...", NO_STR IP_STR COMMUNITY_LIST_STR "Community list number (expanded)\n" "Add an expanded community-list entry\n" "Community list name\n" "Specify community to reject\n" "Specify community to accept\n" COMMUNITY_VAL_STR) ALIAS(no_community_list_expanded_all, no_bgp_community_list_expanded_all_list_cmd, "no bgp community-list <(100-500)|expanded WORD>", NO_STR IP_STR COMMUNITY_LIST_STR "Community list number (expanded)\n" "Add an expanded community-list entry\n" "Community list name\n") ALIAS(no_community_list_expanded_all, no_ip_community_list_expanded_all_list_cmd, "no ip community-list <(100-500)|expanded WORD>", NO_STR IP_STR COMMUNITY_LIST_STR "Community list number (expanded)\n" "Add an expanded community-list entry\n" "Community list name\n") /* Return configuration string of community-list entry. */ static const char *community_list_config_str(struct community_entry *entry) { const char *str; if (entry->any) str = ""; else { if (entry->style == COMMUNITY_LIST_STANDARD) str = community_str(entry->u.com, false); else if (entry->style == LARGE_COMMUNITY_LIST_STANDARD) str = lcommunity_str(entry->u.lcom, false); else str = entry->config; } return str; } static void community_list_show(struct vty *vty, struct community_list *list) { struct community_entry *entry; for (entry = list->head; entry; entry = entry->next) { if (entry == list->head) { if (all_digit(list->name)) vty_out(vty, "Community %s list %s\n", entry->style == COMMUNITY_LIST_STANDARD ? "standard" : "(expanded) access", list->name); else vty_out(vty, "Named Community %s list %s\n", entry->style == COMMUNITY_LIST_STANDARD ? "standard" : "expanded", list->name); } if (entry->any) vty_out(vty, " %s\n", community_direct_str(entry->direct)); else vty_out(vty, " %s %s\n", community_direct_str(entry->direct), community_list_config_str(entry)); } } DEFUN (show_community_list, show_bgp_community_list_cmd, "show bgp community-list", SHOW_STR BGP_STR "List community-list\n") { struct community_list *list; struct community_list_master *cm; int idx = 0; if (argv_find(argv, argc, "ip", &idx)) { vty_out(vty, "This config option is deprecated, and is scheduled for removal.\n"); vty_out(vty, "if you are using this please migrate to the below command.\n"); vty_out(vty, "'show bgp community-list <(1-500)|WORD>'\n"); zlog_warn("Deprecated option: 'ip show community-list <(1-500)|WORD>' being used"); } cm = community_list_master_lookup(bgp_clist, COMMUNITY_LIST_MASTER); if (!cm) return CMD_SUCCESS; for (list = cm->num.head; list; list = list->next) community_list_show(vty, list); for (list = cm->str.head; list; list = list->next) community_list_show(vty, list); return CMD_SUCCESS; } ALIAS (show_community_list, show_ip_community_list_cmd, "show ip community-list", SHOW_STR IP_STR "List community-list\n") DEFUN (show_community_list_arg, show_bgp_community_list_arg_cmd, "show bgp community-list <(1-500)|WORD> detail", SHOW_STR BGP_STR "List community-list\n" "Community-list number\n" "Community-list name\n" "Detailed information on community-list\n") { int idx_comm_list = 3; struct community_list *list; int idx = 0; if (argv_find(argv, argc, "ip", &idx)) { vty_out(vty, "This config option is deprecated, and is scheduled for removal.\n"); vty_out(vty, "if you are using this please migrate to the below command.\n"); vty_out(vty, "'show bgp community-list <(1-500)|WORD> detail'\n"); zlog_warn("Deprecated option: 'show ip community-list <(1-500)|WORD>' being used"); } list = community_list_lookup(bgp_clist, argv[idx_comm_list]->arg, 0, COMMUNITY_LIST_MASTER); if (!list) { vty_out(vty, "%% Can't find community-list\n"); return CMD_WARNING; } community_list_show(vty, list); return CMD_SUCCESS; } ALIAS (show_community_list_arg, show_ip_community_list_arg_cmd, "show ip community-list <(1-500)|WORD>", SHOW_STR IP_STR "List community-list\n" "Community-list number\n" "Community-list name\n") /* * Large Community code. */ static int lcommunity_list_set_vty(struct vty *vty, int argc, struct cmd_token **argv, int style, int reject_all_digit_name) { int ret; int direct; char *str; int idx = 0; char *cl_name; if (argv_find(argv, argc, "ip", &idx)) { vty_out(vty, "This config option is deprecated, and is scheduled for removal.\n"); vty_out(vty, "if you are using this please migrate to the below command.\n"); vty_out(vty, "'bgp large-community-list <(1-99)|(100-500)|standard|expanded> '\n"); zlog_warn("Deprecated option: 'large-community-list <(1-99)|(100-500)|standard|expanded> ' being used"); } direct = argv_find(argv, argc, "permit", &idx) ? COMMUNITY_PERMIT : COMMUNITY_DENY; /* All digit name check. */ idx = 0; argv_find(argv, argc, "WORD", &idx); argv_find(argv, argc, "(1-99)", &idx); argv_find(argv, argc, "(100-500)", &idx); cl_name = argv[idx]->arg; if (reject_all_digit_name && all_digit(cl_name)) { vty_out(vty, "%% Community name cannot have all digits\n"); return CMD_WARNING_CONFIG_FAILED; } idx = 0; argv_find(argv, argc, "AA:BB:CC", &idx); argv_find(argv, argc, "LINE", &idx); /* Concat community string argument. */ if (idx) str = argv_concat(argv, argc, idx); else str = NULL; ret = lcommunity_list_set(bgp_clist, cl_name, str, direct, style); /* Free temporary community list string allocated by argv_concat(). */ XFREE(MTYPE_TMP, str); if (ret < 0) { community_list_perror(vty, ret); return CMD_WARNING_CONFIG_FAILED; } return CMD_SUCCESS; } static int lcommunity_list_unset_vty(struct vty *vty, int argc, struct cmd_token **argv, int style) { int ret; int direct = 0; char *str = NULL; int idx = 0; if (argv_find(argv, argc, "ip", &idx)) { vty_out(vty, "This config option is deprecated, and is scheduled for removal.\n"); vty_out(vty, "if you are using this please migrate to the below command.\n"); vty_out(vty, "'no bgp large-community-list <(1-99)|(100-500)|standard|expanded> '\n"); zlog_warn("Deprecated option: 'no ip large-community-list <(1-99)|(100-500)|standard|expanded> ' being used"); } argv_find(argv, argc, "permit", &idx); argv_find(argv, argc, "deny", &idx); if (idx) { /* Check the list direct. */ if (strncmp(argv[idx]->arg, "p", 1) == 0) direct = COMMUNITY_PERMIT; else direct = COMMUNITY_DENY; idx = 0; argv_find(argv, argc, "LINE", &idx); argv_find(argv, argc, "AA:AA:NN", &idx); /* Concat community string argument. */ str = argv_concat(argv, argc, idx); } idx = 0; argv_find(argv, argc, "(1-99)", &idx); argv_find(argv, argc, "(100-500)", &idx); argv_find(argv, argc, "WORD", &idx); /* Unset community list. */ ret = lcommunity_list_unset(bgp_clist, argv[idx]->arg, str, direct, style); /* Free temporary community list string allocated by argv_concat(). */ XFREE(MTYPE_TMP, str); if (ret < 0) { community_list_perror(vty, ret); return CMD_WARNING_CONFIG_FAILED; } return CMD_SUCCESS; } /* "large-community-list" keyword help string. */ #define LCOMMUNITY_LIST_STR "Add a large community list entry\n" #define LCOMMUNITY_VAL_STR "large community in 'aa:bb:cc' format\n" #if CONFDATE > 20191005 CPP_NOTICE("bgpd: remove deprecated 'ip large-community-list <(1-99)|(100-500)|standard|expanded> ' command") #endif DEFUN (lcommunity_list_standard, bgp_lcommunity_list_standard_cmd, "bgp large-community-list (1-99) AA:BB:CC...", BGP_STR LCOMMUNITY_LIST_STR "Large Community list number (standard)\n" "Specify large community to reject\n" "Specify large community to accept\n" LCOMMUNITY_VAL_STR) { return lcommunity_list_set_vty(vty, argc, argv, LARGE_COMMUNITY_LIST_STANDARD, 0); } ALIAS (lcommunity_list_standard, ip_lcommunity_list_standard_cmd, "ip large-community-list (1-99) AA:BB:CC...", IP_STR LCOMMUNITY_LIST_STR "Large Community list number (standard)\n" "Specify large community to reject\n" "Specify large community to accept\n" LCOMMUNITY_VAL_STR) DEFUN (lcommunity_list_expanded, bgp_lcommunity_list_expanded_cmd, "bgp large-community-list (100-500) LINE...", BGP_STR LCOMMUNITY_LIST_STR "Large Community list number (expanded)\n" "Specify large community to reject\n" "Specify large community to accept\n" "An ordered list as a regular-expression\n") { return lcommunity_list_set_vty(vty, argc, argv, LARGE_COMMUNITY_LIST_EXPANDED, 0); } ALIAS (lcommunity_list_expanded, ip_lcommunity_list_expanded_cmd, "ip large-community-list (100-500) LINE...", IP_STR LCOMMUNITY_LIST_STR "Large Community list number (expanded)\n" "Specify large community to reject\n" "Specify large community to accept\n" "An ordered list as a regular-expression\n") DEFUN (lcommunity_list_name_standard, bgp_lcommunity_list_name_standard_cmd, "bgp large-community-list standard WORD AA:BB:CC...", BGP_STR LCOMMUNITY_LIST_STR "Specify standard large-community-list\n" "Large Community list name\n" "Specify large community to reject\n" "Specify large community to accept\n" LCOMMUNITY_VAL_STR) { return lcommunity_list_set_vty(vty, argc, argv, LARGE_COMMUNITY_LIST_STANDARD, 1); } ALIAS (lcommunity_list_name_standard, ip_lcommunity_list_name_standard_cmd, "ip large-community-list standard WORD AA:BB:CC...", IP_STR LCOMMUNITY_LIST_STR "Specify standard large-community-list\n" "Large Community list name\n" "Specify large community to reject\n" "Specify large community to accept\n" LCOMMUNITY_VAL_STR) DEFUN (lcommunity_list_name_expanded, bgp_lcommunity_list_name_expanded_cmd, "bgp large-community-list expanded WORD LINE...", BGP_STR LCOMMUNITY_LIST_STR "Specify expanded large-community-list\n" "Large Community list name\n" "Specify large community to reject\n" "Specify large community to accept\n" "An ordered list as a regular-expression\n") { return lcommunity_list_set_vty(vty, argc, argv, LARGE_COMMUNITY_LIST_EXPANDED, 1); } ALIAS (lcommunity_list_name_expanded, ip_lcommunity_list_name_expanded_cmd, "ip large-community-list expanded WORD LINE...", IP_STR LCOMMUNITY_LIST_STR "Specify expanded large-community-list\n" "Large Community list name\n" "Specify large community to reject\n" "Specify large community to accept\n" "An ordered list as a regular-expression\n") DEFUN (no_lcommunity_list_standard_all, no_bgp_lcommunity_list_standard_all_cmd, "no bgp large-community-list <(1-99)|(100-500)|WORD>", NO_STR BGP_STR LCOMMUNITY_LIST_STR "Large Community list number (standard)\n" "Large Community list number (expanded)\n" "Large Community list name\n") { return lcommunity_list_unset_vty(vty, argc, argv, LARGE_COMMUNITY_LIST_STANDARD); } ALIAS (no_lcommunity_list_standard_all, no_ip_lcommunity_list_standard_all_cmd, "no ip large-community-list <(1-99)|(100-500)|WORD>", NO_STR IP_STR LCOMMUNITY_LIST_STR "Large Community list number (standard)\n" "Large Community list number (expanded)\n" "Large Community list name\n") DEFUN (no_lcommunity_list_name_expanded_all, no_bgp_lcommunity_list_name_expanded_all_cmd, "no bgp large-community-list expanded WORD", NO_STR BGP_STR LCOMMUNITY_LIST_STR "Specify expanded large-community-list\n" "Large Community list name\n") { return lcommunity_list_unset_vty(vty, argc, argv, LARGE_COMMUNITY_LIST_EXPANDED); } ALIAS (no_lcommunity_list_name_expanded_all, no_ip_lcommunity_list_name_expanded_all_cmd, "no ip large-community-list expanded WORD", NO_STR IP_STR LCOMMUNITY_LIST_STR "Specify expanded large-community-list\n" "Large Community list name\n") DEFUN (no_lcommunity_list_standard, no_bgp_lcommunity_list_standard_cmd, "no bgp large-community-list (1-99) AA:AA:NN...", NO_STR BGP_STR LCOMMUNITY_LIST_STR "Large Community list number (standard)\n" "Specify large community to reject\n" "Specify large community to accept\n" LCOMMUNITY_VAL_STR) { return lcommunity_list_unset_vty(vty, argc, argv, LARGE_COMMUNITY_LIST_STANDARD); } ALIAS (no_lcommunity_list_standard, no_ip_lcommunity_list_standard_cmd, "no ip large-community-list (1-99) AA:AA:NN...", NO_STR IP_STR LCOMMUNITY_LIST_STR "Large Community list number (standard)\n" "Specify large community to reject\n" "Specify large community to accept\n" LCOMMUNITY_VAL_STR) DEFUN (no_lcommunity_list_expanded, no_bgp_lcommunity_list_expanded_cmd, "no bgp large-community-list (100-500) LINE...", NO_STR BGP_STR LCOMMUNITY_LIST_STR "Large Community list number (expanded)\n" "Specify large community to reject\n" "Specify large community to accept\n" "An ordered list as a regular-expression\n") { return lcommunity_list_unset_vty(vty, argc, argv, LARGE_COMMUNITY_LIST_EXPANDED); } ALIAS (no_lcommunity_list_expanded, no_ip_lcommunity_list_expanded_cmd, "no ip large-community-list (100-500) LINE...", NO_STR IP_STR LCOMMUNITY_LIST_STR "Large Community list number (expanded)\n" "Specify large community to reject\n" "Specify large community to accept\n" "An ordered list as a regular-expression\n") DEFUN (no_lcommunity_list_name_standard, no_bgp_lcommunity_list_name_standard_cmd, "no bgp large-community-list standard WORD AA:AA:NN...", NO_STR BGP_STR LCOMMUNITY_LIST_STR "Specify standard large-community-list\n" "Large Community list name\n" "Specify large community to reject\n" "Specify large community to accept\n" LCOMMUNITY_VAL_STR) { return lcommunity_list_unset_vty(vty, argc, argv, LARGE_COMMUNITY_LIST_STANDARD); } ALIAS (no_lcommunity_list_name_standard, no_ip_lcommunity_list_name_standard_cmd, "no ip large-community-list standard WORD AA:AA:NN...", NO_STR IP_STR LCOMMUNITY_LIST_STR "Specify standard large-community-list\n" "Large Community list name\n" "Specify large community to reject\n" "Specify large community to accept\n" LCOMMUNITY_VAL_STR) DEFUN (no_lcommunity_list_name_expanded, no_bgp_lcommunity_list_name_expanded_cmd, "no bgp large-community-list expanded WORD LINE...", NO_STR BGP_STR LCOMMUNITY_LIST_STR "Specify expanded large-community-list\n" "Large community list name\n" "Specify large community to reject\n" "Specify large community to accept\n" "An ordered list as a regular-expression\n") { return lcommunity_list_unset_vty(vty, argc, argv, LARGE_COMMUNITY_LIST_EXPANDED); } ALIAS (no_lcommunity_list_name_expanded, no_ip_lcommunity_list_name_expanded_cmd, "no ip large-community-list expanded WORD LINE...", NO_STR IP_STR LCOMMUNITY_LIST_STR "Specify expanded large-community-list\n" "Large community list name\n" "Specify large community to reject\n" "Specify large community to accept\n" "An ordered list as a regular-expression\n") static void lcommunity_list_show(struct vty *vty, struct community_list *list) { struct community_entry *entry; for (entry = list->head; entry; entry = entry->next) { if (entry == list->head) { if (all_digit(list->name)) vty_out(vty, "Large community %s list %s\n", entry->style == LARGE_COMMUNITY_LIST_STANDARD ? "standard" : "(expanded) access", list->name); else vty_out(vty, "Named large community %s list %s\n", entry->style == LARGE_COMMUNITY_LIST_STANDARD ? "standard" : "expanded", list->name); } if (entry->any) vty_out(vty, " %s\n", community_direct_str(entry->direct)); else vty_out(vty, " %s %s\n", community_direct_str(entry->direct), community_list_config_str(entry)); } } DEFUN (show_lcommunity_list, show_bgp_lcommunity_list_cmd, "show bgp large-community-list", SHOW_STR BGP_STR "List large-community list\n") { struct community_list *list; struct community_list_master *cm; int idx = 0; if (argv_find(argv, argc, "ip", &idx)) { vty_out(vty, "This config option is deprecated, and is scheduled for removal.\n"); vty_out(vty, "if you are using this please migrate to the below command.\n"); vty_out(vty, "'show bgp large-community-list <(1-500)|WORD>'\n"); zlog_warn("Deprecated option: 'ip show large-community-list <(1-500)|WORD>' being used"); } cm = community_list_master_lookup(bgp_clist, LARGE_COMMUNITY_LIST_MASTER); if (!cm) return CMD_SUCCESS; for (list = cm->num.head; list; list = list->next) lcommunity_list_show(vty, list); for (list = cm->str.head; list; list = list->next) lcommunity_list_show(vty, list); return CMD_SUCCESS; } ALIAS (show_lcommunity_list, show_ip_lcommunity_list_cmd, "show ip large-community-list", SHOW_STR IP_STR "List large-community list\n") DEFUN (show_lcommunity_list_arg, show_bgp_lcommunity_list_arg_cmd, "show bgp large-community-list <(1-500)|WORD> detail", SHOW_STR BGP_STR "List large-community list\n" "Large-community-list number\n" "Large-community-list name\n" "Detailed information on large-community-list\n") { struct community_list *list; int idx = 0; if (argv_find(argv, argc, "ip", &idx)) { vty_out(vty, "This config option is deprecated, and is scheduled for removal.\n"); vty_out(vty, "if you are using this please migrate to the below command.\n"); vty_out(vty, "'show bgp large-community-list <(1-500)|WORD> detail'\n"); zlog_warn("Deprecated option: 'show ip large-community-list <(1-500)|WORD>' being used"); } list = community_list_lookup(bgp_clist, argv[3]->arg, 0, LARGE_COMMUNITY_LIST_MASTER); if (!list) { vty_out(vty, "%% Can't find large-community-list\n"); return CMD_WARNING; } lcommunity_list_show(vty, list); return CMD_SUCCESS; } ALIAS (show_lcommunity_list_arg, show_ip_lcommunity_list_arg_cmd, "show ip large-community-list <(1-500)|WORD>", SHOW_STR IP_STR "List large-community list\n" "large-community-list number\n" "large-community-list name\n") /* "extcommunity-list" keyword help string. */ #define EXTCOMMUNITY_LIST_STR "Add a extended community list entry\n" #define EXTCOMMUNITY_VAL_STR "Extended community attribute in 'rt aa:nn_or_IPaddr:nn' OR 'soo aa:nn_or_IPaddr:nn' format\n" DEFUN (extcommunity_list_standard, bgp_extcommunity_list_standard_cmd, "bgp extcommunity-list <(1-99)|standard WORD> AA:NN...", BGP_STR EXTCOMMUNITY_LIST_STR "Extended Community list number (standard)\n" "Specify standard extcommunity-list\n" "Community list name\n" "Specify community to reject\n" "Specify community to accept\n" EXTCOMMUNITY_VAL_STR) { int style = EXTCOMMUNITY_LIST_STANDARD; int direct = 0; char *cl_number_or_name = NULL; int idx = 0; if (argv_find(argv, argc, "ip", &idx)) { vty_out(vty, "This config option is deprecated, and is scheduled for removal.\n"); vty_out(vty, "if you are using this please migrate to the below command.\n"); vty_out(vty, "'bgp extcommunity-list <(1-99)|(100-500)|standard|expanded> '\n"); zlog_warn("Deprecated option: 'ip extcommunity-list <(1-99)|(100-500)|standard|expanded> ' being used"); } argv_find(argv, argc, "(1-99)", &idx); argv_find(argv, argc, "WORD", &idx); cl_number_or_name = argv[idx]->arg; direct = argv_find(argv, argc, "permit", &idx) ? COMMUNITY_PERMIT : COMMUNITY_DENY; argv_find(argv, argc, "AA:NN", &idx); char *str = argv_concat(argv, argc, idx); int ret = extcommunity_list_set(bgp_clist, cl_number_or_name, str, direct, style); XFREE(MTYPE_TMP, str); if (ret < 0) { community_list_perror(vty, ret); return CMD_WARNING_CONFIG_FAILED; } return CMD_SUCCESS; } #if CONFDATE > 20191005 CPP_NOTICE("bgpd: remove deprecated 'ip extcommunity-list <(1-99)|(100-500)|standard|expanded> ' command") #endif ALIAS (extcommunity_list_standard, ip_extcommunity_list_standard_cmd, "ip extcommunity-list <(1-99)|standard WORD> AA:NN...", IP_STR EXTCOMMUNITY_LIST_STR "Extended Community list number (standard)\n" "Specify standard extcommunity-list\n" "Community list name\n" "Specify community to reject\n" "Specify community to accept\n" EXTCOMMUNITY_VAL_STR) DEFUN (extcommunity_list_name_expanded, bgp_extcommunity_list_name_expanded_cmd, "bgp extcommunity-list <(100-500)|expanded WORD> LINE...", BGP_STR EXTCOMMUNITY_LIST_STR "Extended Community list number (expanded)\n" "Specify expanded extcommunity-list\n" "Extended Community list name\n" "Specify community to reject\n" "Specify community to accept\n" "An ordered list as a regular-expression\n") { int style = EXTCOMMUNITY_LIST_EXPANDED; int direct = 0; char *cl_number_or_name = NULL; int idx = 0; if (argv_find(argv, argc, "ip", &idx)) { vty_out(vty, "This config option is deprecated, and is scheduled for removal.\n"); vty_out(vty, "if you are using this please migrate to the below command.\n"); vty_out(vty, "'extcommunity-list <(1-99)|(100-500)|standard|expanded> '\n"); zlog_warn("Deprecated option: ‘ip extcommunity-list <(1-99)|(100-500)|standard|expanded> ' being used"); } argv_find(argv, argc, "(100-500)", &idx); argv_find(argv, argc, "WORD", &idx); cl_number_or_name = argv[idx]->arg; direct = argv_find(argv, argc, "permit", &idx) ? COMMUNITY_PERMIT : COMMUNITY_DENY; argv_find(argv, argc, "LINE", &idx); char *str = argv_concat(argv, argc, idx); int ret = extcommunity_list_set(bgp_clist, cl_number_or_name, str, direct, style); XFREE(MTYPE_TMP, str); if (ret < 0) { community_list_perror(vty, ret); return CMD_WARNING_CONFIG_FAILED; } return CMD_SUCCESS; } ALIAS (extcommunity_list_name_expanded, ip_extcommunity_list_name_expanded_cmd, "ip extcommunity-list <(100-500)|expanded WORD> LINE...", IP_STR EXTCOMMUNITY_LIST_STR "Extended Community list number (expanded)\n" "Specify expanded extcommunity-list\n" "Extended Community list name\n" "Specify community to reject\n" "Specify community to accept\n" "An ordered list as a regular-expression\n") DEFUN (no_extcommunity_list_standard_all, no_bgp_extcommunity_list_standard_all_cmd, "no bgp extcommunity-list <(1-99)|standard WORD> AA:NN...", NO_STR BGP_STR EXTCOMMUNITY_LIST_STR "Extended Community list number (standard)\n" "Specify standard extcommunity-list\n" "Community list name\n" "Specify community to reject\n" "Specify community to accept\n" EXTCOMMUNITY_VAL_STR) { int style = EXTCOMMUNITY_LIST_STANDARD; int direct = 0; char *cl_number_or_name = NULL; char *str = NULL; int idx = 0; if (argv_find(argv, argc, "ip", &idx)) { vty_out(vty, "This config option is deprecated, and is scheduled for removal\n"); vty_out(vty, "if you are using this please migrate to the below command.\n"); vty_out(vty, "'no bgp extcommunity-list <(1-99)|(100-500)|standard|expanded> '\n"); zlog_warn("Deprecated option: ‘no ip extcommunity-list <(1-99)|(100-500)|standard|expanded> ' being used"); } idx = 0; argv_find(argv, argc, "permit", &idx); argv_find(argv, argc, "deny", &idx); if (idx) { direct = argv_find(argv, argc, "permit", &idx) ? COMMUNITY_PERMIT : COMMUNITY_DENY; idx = 0; argv_find(argv, argc, "AA:NN", &idx); str = argv_concat(argv, argc, idx); } idx = 0; argv_find(argv, argc, "(1-99)", &idx); argv_find(argv, argc, "WORD", &idx); cl_number_or_name = argv[idx]->arg; int ret = extcommunity_list_unset(bgp_clist, cl_number_or_name, str, direct, style); XFREE(MTYPE_TMP, str); if (ret < 0) { community_list_perror(vty, ret); return CMD_WARNING_CONFIG_FAILED; } return CMD_SUCCESS; } ALIAS (no_extcommunity_list_standard_all, no_ip_extcommunity_list_standard_all_cmd, "no ip extcommunity-list <(1-99)|standard WORD> AA:NN...", NO_STR IP_STR EXTCOMMUNITY_LIST_STR "Extended Community list number (standard)\n" "Specify standard extcommunity-list\n" "Community list name\n" "Specify community to reject\n" "Specify community to accept\n" EXTCOMMUNITY_VAL_STR) ALIAS(no_extcommunity_list_standard_all, no_bgp_extcommunity_list_standard_all_list_cmd, "no bgp extcommunity-list <(1-99)|standard WORD>", NO_STR IP_STR EXTCOMMUNITY_LIST_STR "Extended Community list number (standard)\n" "Specify standard extcommunity-list\n" "Community list name\n") ALIAS(no_extcommunity_list_standard_all, no_ip_extcommunity_list_standard_all_list_cmd, "no ip extcommunity-list <(1-99)|standard WORD>", NO_STR IP_STR EXTCOMMUNITY_LIST_STR "Extended Community list number (standard)\n" "Specify standard extcommunity-list\n" "Community list name\n") DEFUN (no_extcommunity_list_expanded_all, no_bgp_extcommunity_list_expanded_all_cmd, "no bgp extcommunity-list <(100-500)|expanded WORD> LINE...", NO_STR BGP_STR EXTCOMMUNITY_LIST_STR "Extended Community list number (expanded)\n" "Specify expanded extcommunity-list\n" "Extended Community list name\n" "Specify community to reject\n" "Specify community to accept\n" "An ordered list as a regular-expression\n") { int style = EXTCOMMUNITY_LIST_EXPANDED; int direct = 0; char *cl_number_or_name = NULL; char *str = NULL; int idx = 0; if (argv_find(argv, argc, "ip", &idx)) { vty_out(vty, "This config option is deprecated, and is scheduled for removal.\n"); vty_out(vty, "if you are using this please migrate to the below command.\n"); vty_out(vty, "'no bgp extcommunity-list <(1-99)|(100-500)|standard|expanded> '\n"); zlog_warn("Deprecated option: ‘no ip extcommunity-list <(1-99)|(100-500)|standard|expanded> ' being used"); } idx = 0; argv_find(argv, argc, "permit", &idx); argv_find(argv, argc, "deny", &idx); if (idx) { direct = argv_find(argv, argc, "permit", &idx) ? COMMUNITY_PERMIT : COMMUNITY_DENY; idx = 0; argv_find(argv, argc, "LINE", &idx); str = argv_concat(argv, argc, idx); } idx = 0; argv_find(argv, argc, "(100-500)", &idx); argv_find(argv, argc, "WORD", &idx); cl_number_or_name = argv[idx]->arg; int ret = extcommunity_list_unset(bgp_clist, cl_number_or_name, str, direct, style); XFREE(MTYPE_TMP, str); if (ret < 0) { community_list_perror(vty, ret); return CMD_WARNING_CONFIG_FAILED; } return CMD_SUCCESS; } ALIAS (no_extcommunity_list_expanded_all, no_ip_extcommunity_list_expanded_all_cmd, "no ip extcommunity-list <(100-500)|expanded WORD> LINE...", NO_STR IP_STR EXTCOMMUNITY_LIST_STR "Extended Community list number (expanded)\n" "Specify expanded extcommunity-list\n" "Extended Community list name\n" "Specify community to reject\n" "Specify community to accept\n" "An ordered list as a regular-expression\n") ALIAS(no_extcommunity_list_expanded_all, no_ip_extcommunity_list_expanded_all_list_cmd, "no ip extcommunity-list <(100-500)|expanded WORD>", NO_STR IP_STR EXTCOMMUNITY_LIST_STR "Extended Community list number (expanded)\n" "Specify expanded extcommunity-list\n" "Extended Community list name\n") ALIAS(no_extcommunity_list_expanded_all, no_bgp_extcommunity_list_expanded_all_list_cmd, "no bgp extcommunity-list <(100-500)|expanded WORD>", NO_STR IP_STR EXTCOMMUNITY_LIST_STR "Extended Community list number (expanded)\n" "Specify expanded extcommunity-list\n" "Extended Community list name\n") static void extcommunity_list_show(struct vty *vty, struct community_list *list) { struct community_entry *entry; for (entry = list->head; entry; entry = entry->next) { if (entry == list->head) { if (all_digit(list->name)) vty_out(vty, "Extended community %s list %s\n", entry->style == EXTCOMMUNITY_LIST_STANDARD ? "standard" : "(expanded) access", list->name); else vty_out(vty, "Named extended community %s list %s\n", entry->style == EXTCOMMUNITY_LIST_STANDARD ? "standard" : "expanded", list->name); } if (entry->any) vty_out(vty, " %s\n", community_direct_str(entry->direct)); else vty_out(vty, " %s %s\n", community_direct_str(entry->direct), community_list_config_str(entry)); } } DEFUN (show_extcommunity_list, show_bgp_extcommunity_list_cmd, "show bgp extcommunity-list", SHOW_STR BGP_STR "List extended-community list\n") { struct community_list *list; struct community_list_master *cm; int idx = 0; if (argv_find(argv, argc, "ip", &idx)) { vty_out(vty, "This config option is deprecated, and is scheduled for removal\n"); vty_out(vty, "if you are using this please migrate to the below command.\n"); vty_out(vty, "'show bgp extcommunity-list <(1-500)|WORD>'\n"); zlog_warn("Deprecated option: 'ip show extcommunity-list <(1-500)|WORD>' being used"); } cm = community_list_master_lookup(bgp_clist, EXTCOMMUNITY_LIST_MASTER); if (!cm) return CMD_SUCCESS; for (list = cm->num.head; list; list = list->next) extcommunity_list_show(vty, list); for (list = cm->str.head; list; list = list->next) extcommunity_list_show(vty, list); return CMD_SUCCESS; } ALIAS (show_extcommunity_list, show_ip_extcommunity_list_cmd, "show ip extcommunity-list", SHOW_STR IP_STR "List extended-community list\n") DEFUN (show_extcommunity_list_arg, show_bgp_extcommunity_list_arg_cmd, "show bgp extcommunity-list <(1-500)|WORD> detail", SHOW_STR BGP_STR "List extended-community list\n" "Extcommunity-list number\n" "Extcommunity-list name\n" "Detailed information on extcommunity-list\n") { int idx_comm_list = 3; struct community_list *list; int idx = 0; if (argv_find(argv, argc, "ip", &idx)) { vty_out(vty, "This config option is deprecated, and is scheduled for removal.\n"); vty_out(vty, "if you are using this please migrate to the below command.\n"); vty_out(vty, "'show bgp extcommunity-list <(1-500)|WORD> detail'\n"); zlog_warn("Deprecated option: 'show ip extcommunity-list <(1-500)|WORD>' being used"); } list = community_list_lookup(bgp_clist, argv[idx_comm_list]->arg, 0, EXTCOMMUNITY_LIST_MASTER); if (!list) { vty_out(vty, "%% Can't find extcommunity-list\n"); return CMD_WARNING; } extcommunity_list_show(vty, list); return CMD_SUCCESS; } ALIAS (show_extcommunity_list_arg, show_ip_extcommunity_list_arg_cmd, "show ip extcommunity-list <(1-500)|WORD>", SHOW_STR IP_STR "List extended-community list\n" "Extcommunity-list number\n" "Extcommunity-list name\n") /* Display community-list and extcommunity-list configuration. */ static int community_list_config_write(struct vty *vty) { struct community_list *list; struct community_entry *entry; struct community_list_master *cm; int write = 0; /* Community-list. */ cm = community_list_master_lookup(bgp_clist, COMMUNITY_LIST_MASTER); for (list = cm->num.head; list; list = list->next) for (entry = list->head; entry; entry = entry->next) { vty_out(vty, "bgp community-list %s %s %s\n", list->name, community_direct_str(entry->direct), community_list_config_str(entry)); write++; } for (list = cm->str.head; list; list = list->next) for (entry = list->head; entry; entry = entry->next) { vty_out(vty, "bgp community-list %s %s %s %s\n", entry->style == COMMUNITY_LIST_STANDARD ? "standard" : "expanded", list->name, community_direct_str(entry->direct), community_list_config_str(entry)); write++; } /* Extcommunity-list. */ cm = community_list_master_lookup(bgp_clist, EXTCOMMUNITY_LIST_MASTER); for (list = cm->num.head; list; list = list->next) for (entry = list->head; entry; entry = entry->next) { vty_out(vty, "bgp extcommunity-list %s %s %s\n", list->name, community_direct_str(entry->direct), community_list_config_str(entry)); write++; } for (list = cm->str.head; list; list = list->next) for (entry = list->head; entry; entry = entry->next) { vty_out(vty, "bgp extcommunity-list %s %s %s %s\n", entry->style == EXTCOMMUNITY_LIST_STANDARD ? "standard" : "expanded", list->name, community_direct_str(entry->direct), community_list_config_str(entry)); write++; } /* lcommunity-list. */ cm = community_list_master_lookup(bgp_clist, LARGE_COMMUNITY_LIST_MASTER); for (list = cm->num.head; list; list = list->next) for (entry = list->head; entry; entry = entry->next) { vty_out(vty, "bgp large-community-list %s %s %s\n", list->name, community_direct_str(entry->direct), community_list_config_str(entry)); write++; } for (list = cm->str.head; list; list = list->next) for (entry = list->head; entry; entry = entry->next) { vty_out(vty, "bgp large-community-list %s %s %s %s\n", entry->style == LARGE_COMMUNITY_LIST_STANDARD ? "standard" : "expanded", list->name, community_direct_str(entry->direct), community_list_config_str(entry)); write++; } return write; } static struct cmd_node community_list_node = { COMMUNITY_LIST_NODE, "", 1 /* Export to vtysh. */ }; static void community_list_vty(void) { install_node(&community_list_node, community_list_config_write); /* Community-list. */ install_element(CONFIG_NODE, &bgp_community_list_standard_cmd); install_element(CONFIG_NODE, &bgp_community_list_expanded_all_cmd); install_element(CONFIG_NODE, &no_bgp_community_list_standard_all_cmd); install_element(CONFIG_NODE, &no_bgp_community_list_standard_all_list_cmd); install_element(CONFIG_NODE, &no_bgp_community_list_expanded_all_cmd); install_element(CONFIG_NODE, &no_bgp_community_list_expanded_all_list_cmd); install_element(VIEW_NODE, &show_bgp_community_list_cmd); install_element(VIEW_NODE, &show_bgp_community_list_arg_cmd); install_element(CONFIG_NODE, &ip_community_list_standard_cmd); install_element(CONFIG_NODE, &ip_community_list_expanded_all_cmd); install_element(CONFIG_NODE, &no_ip_community_list_standard_all_cmd); install_element(CONFIG_NODE, &no_ip_community_list_standard_all_list_cmd); install_element(CONFIG_NODE, &no_ip_community_list_expanded_all_cmd); install_element(CONFIG_NODE, &no_ip_community_list_expanded_all_list_cmd); install_element(VIEW_NODE, &show_ip_community_list_cmd); install_element(VIEW_NODE, &show_ip_community_list_arg_cmd); /* Extcommunity-list. */ install_element(CONFIG_NODE, &bgp_extcommunity_list_standard_cmd); install_element(CONFIG_NODE, &bgp_extcommunity_list_name_expanded_cmd); install_element(CONFIG_NODE, &no_bgp_extcommunity_list_standard_all_cmd); install_element(CONFIG_NODE, &no_bgp_extcommunity_list_standard_all_list_cmd); install_element(CONFIG_NODE, &no_bgp_extcommunity_list_expanded_all_cmd); install_element(CONFIG_NODE, &no_bgp_extcommunity_list_expanded_all_list_cmd); install_element(VIEW_NODE, &show_bgp_extcommunity_list_cmd); install_element(VIEW_NODE, &show_bgp_extcommunity_list_arg_cmd); install_element(CONFIG_NODE, &ip_extcommunity_list_standard_cmd); install_element(CONFIG_NODE, &ip_extcommunity_list_name_expanded_cmd); install_element(CONFIG_NODE, &no_ip_extcommunity_list_standard_all_cmd); install_element(CONFIG_NODE, &no_ip_extcommunity_list_standard_all_list_cmd); install_element(CONFIG_NODE, &no_ip_extcommunity_list_expanded_all_cmd); install_element(CONFIG_NODE, &no_ip_extcommunity_list_expanded_all_list_cmd); install_element(VIEW_NODE, &show_ip_extcommunity_list_cmd); install_element(VIEW_NODE, &show_ip_extcommunity_list_arg_cmd); /* Large Community List */ install_element(CONFIG_NODE, &bgp_lcommunity_list_standard_cmd); install_element(CONFIG_NODE, &bgp_lcommunity_list_expanded_cmd); install_element(CONFIG_NODE, &bgp_lcommunity_list_name_standard_cmd); install_element(CONFIG_NODE, &bgp_lcommunity_list_name_expanded_cmd); install_element(CONFIG_NODE, &no_bgp_lcommunity_list_standard_all_cmd); install_element(CONFIG_NODE, &no_bgp_lcommunity_list_name_expanded_all_cmd); install_element(CONFIG_NODE, &no_bgp_lcommunity_list_standard_cmd); install_element(CONFIG_NODE, &no_bgp_lcommunity_list_expanded_cmd); install_element(CONFIG_NODE, &no_bgp_lcommunity_list_name_standard_cmd); install_element(CONFIG_NODE, &no_bgp_lcommunity_list_name_expanded_cmd); install_element(VIEW_NODE, &show_bgp_lcommunity_list_cmd); install_element(VIEW_NODE, &show_bgp_lcommunity_list_arg_cmd); install_element(CONFIG_NODE, &ip_lcommunity_list_standard_cmd); install_element(CONFIG_NODE, &ip_lcommunity_list_expanded_cmd); install_element(CONFIG_NODE, &ip_lcommunity_list_name_standard_cmd); install_element(CONFIG_NODE, &ip_lcommunity_list_name_expanded_cmd); install_element(CONFIG_NODE, &no_ip_lcommunity_list_standard_all_cmd); install_element(CONFIG_NODE, &no_ip_lcommunity_list_name_expanded_all_cmd); install_element(CONFIG_NODE, &no_ip_lcommunity_list_standard_cmd); install_element(CONFIG_NODE, &no_ip_lcommunity_list_expanded_cmd); install_element(CONFIG_NODE, &no_ip_lcommunity_list_name_standard_cmd); install_element(CONFIG_NODE, &no_ip_lcommunity_list_name_expanded_cmd); install_element(VIEW_NODE, &show_ip_lcommunity_list_cmd); install_element(VIEW_NODE, &show_ip_lcommunity_list_arg_cmd); } frr-7.2.1/bgpd/bgp_vty.h0000644000000000000000000000673013610377563011761 00000000000000/* BGP VTY interface. * Copyright (C) 1996, 97, 98, 99, 2000 Kunihiro Ishiguro * * This file is part of GNU Zebra. * * GNU Zebra is free software; you can redistribute it and/or modify it * under the terms of the GNU General Public License as published by the * Free Software Foundation; either version 2, or (at your option) any * later version. * * GNU Zebra 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; see the file COPYING; if not, write to the Free Software * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA */ #ifndef _QUAGGA_BGP_VTY_H #define _QUAGGA_BGP_VTY_H struct bgp; #define BGP_INSTANCE_HELP_STR "BGP view\nBGP VRF\nView/VRF name\n" #define BGP_INSTANCE_ALL_HELP_STR "BGP view\nBGP VRF\nAll Views/VRFs\n" #define BGP_AFI_CMD_STR "" #define BGP_AFI_HELP_STR "Address Family\nAddress Family\n" #define BGP_SAFI_CMD_STR "" #define BGP_SAFI_HELP_STR \ "Address Family modifier\n" \ "Address Family modifier\n" \ "Address Family modifier\n" #define BGP_AFI_SAFI_CMD_STR BGP_AFI_CMD_STR" "BGP_SAFI_CMD_STR #define BGP_AFI_SAFI_HELP_STR BGP_AFI_HELP_STR BGP_SAFI_HELP_STR #define BGP_SAFI_WITH_LABEL_CMD_STR "" #define BGP_SAFI_WITH_LABEL_HELP_STR \ "Address Family modifier\n" \ "Address Family modifier\n" \ "Address Family modifier\n" \ "Address Family modifier\n" \ "Address Family modifier\n" extern void bgp_vty_init(void); extern const char *get_afi_safi_str(afi_t afi, safi_t safi, bool for_json); extern void bgp_config_write_update_delay(struct vty *vty, struct bgp *bgp); extern void bgp_config_write_wpkt_quanta(struct vty *vty, struct bgp *bgp); extern void bgp_config_write_rpkt_quanta(struct vty *vty, struct bgp *bgp); extern void bgp_config_write_listen(struct vty *vty, struct bgp *bgp); extern void bgp_config_write_coalesce_time(struct vty *vty, struct bgp *bgp); extern int bgp_vty_return(struct vty *vty, int ret); extern struct peer *peer_and_group_lookup_vty(struct vty *vty, const char *peer_str); extern afi_t bgp_vty_afi_from_str(const char *afi_str); extern safi_t bgp_vty_safi_from_str(const char *safi_str); extern int argv_find_and_parse_afi(struct cmd_token **argv, int argc, int *index, afi_t *afi); extern int argv_find_and_parse_safi(struct cmd_token **argv, int argc, int *index, safi_t *safi); extern int bgp_vty_find_and_parse_afi_safi_bgp(struct vty *vty, struct cmd_token **argv, int argc, int *idx, afi_t *afi, safi_t *safi, struct bgp **bgp, bool use_json); extern int bgp_show_summary_vty(struct vty *vty, const char *name, afi_t afi, safi_t safi, bool show_failed, bool use_json); extern void bgp_vpn_policy_config_write_afi(struct vty *vty, struct bgp *bgp, afi_t afi); #endif /* _QUAGGA_BGP_VTY_H */ frr-7.2.1/bgpd/bgp_zebra.c0000644000000000000000000022772613610377563012247 00000000000000/* zebra client * Copyright (C) 1997, 98, 99 Kunihiro Ishiguro * * This file is part of GNU Zebra. * * GNU Zebra is free software; you can redistribute it and/or modify it * under the terms of the GNU General Public License as published by the * Free Software Foundation; either version 2, or (at your option) any * later version. * * GNU Zebra 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; see the file COPYING; if not, write to the Free Software * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA */ #include #include "command.h" #include "stream.h" #include "network.h" #include "prefix.h" #include "log.h" #include "sockunion.h" #include "zclient.h" #include "routemap.h" #include "thread.h" #include "queue.h" #include "memory.h" #include "lib/json.h" #include "lib/bfd.h" #include "filter.h" #include "mpls.h" #include "vxlan.h" #include "pbr.h" #include "bgpd/bgpd.h" #include "bgpd/bgp_route.h" #include "bgpd/bgp_attr.h" #include "bgpd/bgp_nexthop.h" #include "bgpd/bgp_zebra.h" #include "bgpd/bgp_fsm.h" #include "bgpd/bgp_debug.h" #include "bgpd/bgp_errors.h" #include "bgpd/bgp_mpath.h" #include "bgpd/bgp_nexthop.h" #include "bgpd/bgp_nht.h" #include "bgpd/bgp_bfd.h" #include "bgpd/bgp_label.h" #if ENABLE_BGP_VNC #include "bgpd/rfapi/rfapi_backend.h" #include "bgpd/rfapi/vnc_export_bgp.h" #endif #include "bgpd/bgp_evpn.h" #include "bgpd/bgp_mplsvpn.h" #include "bgpd/bgp_labelpool.h" #include "bgpd/bgp_pbr.h" #include "bgpd/bgp_evpn_private.h" #include "bgpd/bgp_mac.h" /* All information about zebra. */ struct zclient *zclient = NULL; /* Can we install into zebra? */ static inline int bgp_install_info_to_zebra(struct bgp *bgp) { if (zclient->sock <= 0) return 0; if (!IS_BGP_INST_KNOWN_TO_ZEBRA(bgp)) { zlog_debug("%s: No zebra instance to talk to, not installing information", __PRETTY_FUNCTION__); return 0; } return 1; } int zclient_num_connects; /* Router-id update message from zebra. */ static int bgp_router_id_update(ZAPI_CALLBACK_ARGS) { struct prefix router_id; zebra_router_id_update_read(zclient->ibuf, &router_id); if (BGP_DEBUG(zebra, ZEBRA)) { char buf[PREFIX2STR_BUFFER]; prefix2str(&router_id, buf, sizeof(buf)); zlog_debug("Rx Router Id update VRF %u Id %s", vrf_id, buf); } bgp_router_id_zebra_bump(vrf_id, &router_id); return 0; } /* Nexthop update message from zebra. */ static int bgp_read_nexthop_update(ZAPI_CALLBACK_ARGS) { bgp_parse_nexthop_update(cmd, vrf_id); return 0; } static int bgp_read_import_check_update(ZAPI_CALLBACK_ARGS) { bgp_parse_nexthop_update(cmd, vrf_id); return 0; } /* Set or clear interface on which unnumbered neighbor is configured. This * would in turn cause BGP to initiate or turn off IPv6 RAs on this * interface. */ static void bgp_update_interface_nbrs(struct bgp *bgp, struct interface *ifp, struct interface *upd_ifp) { struct listnode *node, *nnode; struct peer *peer; for (ALL_LIST_ELEMENTS(bgp->peer, node, nnode, peer)) { if (peer->conf_if && (strcmp(peer->conf_if, ifp->name) == 0)) { if (upd_ifp) { peer->ifp = upd_ifp; bgp_zebra_initiate_radv(bgp, peer); } else { bgp_zebra_terminate_radv(bgp, peer); peer->ifp = upd_ifp; } } } } static int bgp_read_fec_update(int command, struct zclient *zclient, zebra_size_t length) { bgp_parse_fec_update(); return 0; } static void bgp_start_interface_nbrs(struct bgp *bgp, struct interface *ifp) { struct listnode *node, *nnode; struct peer *peer; for (ALL_LIST_ELEMENTS(bgp->peer, node, nnode, peer)) { if (peer->conf_if && (strcmp(peer->conf_if, ifp->name) == 0) && peer->status != Established) { if (peer_active(peer)) BGP_EVENT_ADD(peer, BGP_Stop); BGP_EVENT_ADD(peer, BGP_Start); } } } static void bgp_nbr_connected_add(struct bgp *bgp, struct nbr_connected *ifc) { struct listnode *node; struct connected *connected; struct interface *ifp; struct prefix *p; /* Kick-off the FSM for any relevant peers only if there is a * valid local address on the interface. */ ifp = ifc->ifp; for (ALL_LIST_ELEMENTS_RO(ifp->connected, node, connected)) { p = connected->address; if (p->family == AF_INET6 && IN6_IS_ADDR_LINKLOCAL(&p->u.prefix6)) break; } if (!connected) return; bgp_start_interface_nbrs(bgp, ifp); } static void bgp_nbr_connected_delete(struct bgp *bgp, struct nbr_connected *ifc, int del) { struct listnode *node, *nnode; struct peer *peer; struct interface *ifp; for (ALL_LIST_ELEMENTS(bgp->peer, node, nnode, peer)) { if (peer->conf_if && (strcmp(peer->conf_if, ifc->ifp->name) == 0)) { peer->last_reset = PEER_DOWN_NBR_ADDR_DEL; BGP_EVENT_ADD(peer, BGP_Stop); } } /* Free neighbor also, if we're asked to. */ if (del) { ifp = ifc->ifp; listnode_delete(ifp->nbr_connected, ifc); nbr_connected_free(ifc); } } /* Inteface addition message from zebra. */ static int bgp_interface_add(ZAPI_CALLBACK_ARGS) { struct interface *ifp; struct bgp *bgp; ifp = zebra_interface_add_read(zclient->ibuf, vrf_id); if (!ifp) // unexpected return 0; if (BGP_DEBUG(zebra, ZEBRA) && ifp) zlog_debug("Rx Intf add VRF %u IF %s", vrf_id, ifp->name); bgp = bgp_lookup_by_vrf_id(vrf_id); if (!bgp) return 0; bgp_mac_add_mac_entry(ifp); bgp_update_interface_nbrs(bgp, ifp, ifp); return 0; } static int bgp_interface_delete(ZAPI_CALLBACK_ARGS) { struct stream *s; struct interface *ifp; struct bgp *bgp; bgp = bgp_lookup_by_vrf_id(vrf_id); s = zclient->ibuf; ifp = zebra_interface_state_read(s, vrf_id); if (!ifp) /* This may happen if we've just unregistered for a VRF. */ return 0; if (BGP_DEBUG(zebra, ZEBRA)) zlog_debug("Rx Intf del VRF %u IF %s", vrf_id, ifp->name); if (bgp) bgp_update_interface_nbrs(bgp, ifp, NULL); bgp_mac_del_mac_entry(ifp); if_set_index(ifp, IFINDEX_INTERNAL); return 0; } static int bgp_interface_up(ZAPI_CALLBACK_ARGS) { struct stream *s; struct interface *ifp; struct connected *c; struct nbr_connected *nc; struct listnode *node, *nnode; struct bgp *bgp; bgp = bgp_lookup_by_vrf_id(vrf_id); s = zclient->ibuf; ifp = zebra_interface_state_read(s, vrf_id); if (!ifp) return 0; bgp_mac_add_mac_entry(ifp); if (BGP_DEBUG(zebra, ZEBRA)) zlog_debug("Rx Intf up VRF %u IF %s", vrf_id, ifp->name); if (!bgp) return 0; for (ALL_LIST_ELEMENTS(ifp->connected, node, nnode, c)) bgp_connected_add(bgp, c); for (ALL_LIST_ELEMENTS(ifp->nbr_connected, node, nnode, nc)) bgp_nbr_connected_add(bgp, nc); return 0; } static int bgp_interface_down(ZAPI_CALLBACK_ARGS) { struct stream *s; struct interface *ifp; struct connected *c; struct nbr_connected *nc; struct listnode *node, *nnode; struct bgp *bgp; struct peer *peer; bgp = bgp_lookup_by_vrf_id(vrf_id); s = zclient->ibuf; ifp = zebra_interface_state_read(s, vrf_id); if (!ifp) return 0; bgp_mac_del_mac_entry(ifp); if (BGP_DEBUG(zebra, ZEBRA)) zlog_debug("Rx Intf down VRF %u IF %s", vrf_id, ifp->name); if (!bgp) return 0; for (ALL_LIST_ELEMENTS(ifp->connected, node, nnode, c)) bgp_connected_delete(bgp, c); for (ALL_LIST_ELEMENTS(ifp->nbr_connected, node, nnode, nc)) bgp_nbr_connected_delete(bgp, nc, 1); /* Fast external-failover */ if (!CHECK_FLAG(bgp->flags, BGP_FLAG_NO_FAST_EXT_FAILOVER)) { for (ALL_LIST_ELEMENTS(bgp->peer, node, nnode, peer)) { #if defined(HAVE_CUMULUS) /* Take down directly connected EBGP peers as well as * 1-hop BFD * tracked (directly connected) IBGP peers. */ if ((peer->ttl != 1) && (peer->gtsm_hops != 1) && (!peer->bfd_info || bgp_bfd_is_peer_multihop(peer))) #else /* Take down directly connected EBGP peers */ if ((peer->ttl != 1) && (peer->gtsm_hops != 1)) #endif continue; if (ifp == peer->nexthop.ifp) { BGP_EVENT_ADD(peer, BGP_Stop); peer->last_reset = PEER_DOWN_IF_DOWN; } } } return 0; } static int bgp_interface_address_add(ZAPI_CALLBACK_ARGS) { struct connected *ifc; struct bgp *bgp; bgp = bgp_lookup_by_vrf_id(vrf_id); ifc = zebra_interface_address_read(cmd, zclient->ibuf, vrf_id); if (ifc == NULL) return 0; if (bgp_debug_zebra(ifc->address)) { char buf[PREFIX2STR_BUFFER]; prefix2str(ifc->address, buf, sizeof(buf)); zlog_debug("Rx Intf address add VRF %u IF %s addr %s", vrf_id, ifc->ifp->name, buf); } if (!bgp) return 0; if (if_is_operative(ifc->ifp)) { bgp_connected_add(bgp, ifc); /* If we have learnt of any neighbors on this interface, * check to kick off any BGP interface-based neighbors, * but only if this is a link-local address. */ if (IN6_IS_ADDR_LINKLOCAL(&ifc->address->u.prefix6) && !list_isempty(ifc->ifp->nbr_connected)) bgp_start_interface_nbrs(bgp, ifc->ifp); } return 0; } static int bgp_interface_address_delete(ZAPI_CALLBACK_ARGS) { struct connected *ifc; struct bgp *bgp; bgp = bgp_lookup_by_vrf_id(vrf_id); ifc = zebra_interface_address_read(cmd, zclient->ibuf, vrf_id); if (ifc == NULL) return 0; if (bgp_debug_zebra(ifc->address)) { char buf[PREFIX2STR_BUFFER]; prefix2str(ifc->address, buf, sizeof(buf)); zlog_debug("Rx Intf address del VRF %u IF %s addr %s", vrf_id, ifc->ifp->name, buf); } if (bgp && if_is_operative(ifc->ifp)) { bgp_connected_delete(bgp, ifc); } connected_free(ifc); return 0; } static int bgp_interface_nbr_address_add(ZAPI_CALLBACK_ARGS) { struct nbr_connected *ifc = NULL; struct bgp *bgp; ifc = zebra_interface_nbr_address_read(cmd, zclient->ibuf, vrf_id); if (ifc == NULL) return 0; if (bgp_debug_zebra(ifc->address)) { char buf[PREFIX2STR_BUFFER]; prefix2str(ifc->address, buf, sizeof(buf)); zlog_debug("Rx Intf neighbor add VRF %u IF %s addr %s", vrf_id, ifc->ifp->name, buf); } if (if_is_operative(ifc->ifp)) { bgp = bgp_lookup_by_vrf_id(vrf_id); if (bgp) bgp_nbr_connected_add(bgp, ifc); } return 0; } static int bgp_interface_nbr_address_delete(ZAPI_CALLBACK_ARGS) { struct nbr_connected *ifc = NULL; struct bgp *bgp; ifc = zebra_interface_nbr_address_read(cmd, zclient->ibuf, vrf_id); if (ifc == NULL) return 0; if (bgp_debug_zebra(ifc->address)) { char buf[PREFIX2STR_BUFFER]; prefix2str(ifc->address, buf, sizeof(buf)); zlog_debug("Rx Intf neighbor del VRF %u IF %s addr %s", vrf_id, ifc->ifp->name, buf); } if (if_is_operative(ifc->ifp)) { bgp = bgp_lookup_by_vrf_id(vrf_id); if (bgp) bgp_nbr_connected_delete(bgp, ifc, 0); } nbr_connected_free(ifc); return 0; } /* VRF update for an interface. */ static int bgp_interface_vrf_update(ZAPI_CALLBACK_ARGS) { struct interface *ifp; vrf_id_t new_vrf_id; struct connected *c; struct nbr_connected *nc; struct listnode *node, *nnode; struct bgp *bgp; struct peer *peer; ifp = zebra_interface_vrf_update_read(zclient->ibuf, vrf_id, &new_vrf_id); if (!ifp) return 0; if (BGP_DEBUG(zebra, ZEBRA) && ifp) zlog_debug("Rx Intf VRF change VRF %u IF %s NewVRF %u", vrf_id, ifp->name, new_vrf_id); bgp = bgp_lookup_by_vrf_id(vrf_id); if (bgp) { for (ALL_LIST_ELEMENTS(ifp->connected, node, nnode, c)) bgp_connected_delete(bgp, c); for (ALL_LIST_ELEMENTS(ifp->nbr_connected, node, nnode, nc)) bgp_nbr_connected_delete(bgp, nc, 1); /* Fast external-failover */ if (!CHECK_FLAG(bgp->flags, BGP_FLAG_NO_FAST_EXT_FAILOVER)) { for (ALL_LIST_ELEMENTS(bgp->peer, node, nnode, peer)) { if ((peer->ttl != 1) && (peer->gtsm_hops != 1)) continue; if (ifp == peer->nexthop.ifp) BGP_EVENT_ADD(peer, BGP_Stop); } } } if_update_to_new_vrf(ifp, new_vrf_id); bgp = bgp_lookup_by_vrf_id(new_vrf_id); if (!bgp) return 0; for (ALL_LIST_ELEMENTS(ifp->connected, node, nnode, c)) bgp_connected_add(bgp, c); for (ALL_LIST_ELEMENTS(ifp->nbr_connected, node, nnode, nc)) bgp_nbr_connected_add(bgp, nc); return 0; } /* Zebra route add and delete treatment. */ static int zebra_read_route(ZAPI_CALLBACK_ARGS) { enum nexthop_types_t nhtype; struct zapi_route api; union g_addr nexthop; ifindex_t ifindex; int add, i; struct bgp *bgp; bgp = bgp_lookup_by_vrf_id(vrf_id); if (!bgp) return 0; if (zapi_route_decode(zclient->ibuf, &api) < 0) return -1; /* we completely ignore srcdest routes for now. */ if (CHECK_FLAG(api.message, ZAPI_MESSAGE_SRCPFX)) return 0; /* ignore link-local address. */ if (api.prefix.family == AF_INET6 && IN6_IS_ADDR_LINKLOCAL(&api.prefix.u.prefix6)) return 0; nexthop = api.nexthops[0].gate; ifindex = api.nexthops[0].ifindex; nhtype = api.nexthops[0].type; add = (cmd == ZEBRA_REDISTRIBUTE_ROUTE_ADD); if (add) { /* * The ADD message is actually an UPDATE and there is no * explicit DEL * for a prior redistributed route, if any. So, perform an * implicit * DEL processing for the same redistributed route from any * other * source type. */ for (i = 0; i < ZEBRA_ROUTE_MAX; i++) { if (i != api.type) bgp_redistribute_delete(bgp, &api.prefix, i, api.instance); } /* Now perform the add/update. */ bgp_redistribute_add(bgp, &api.prefix, &nexthop, ifindex, nhtype, api.metric, api.type, api.instance, api.tag); } else { bgp_redistribute_delete(bgp, &api.prefix, api.type, api.instance); } if (bgp_debug_zebra(&api.prefix)) { char buf[2][PREFIX_STRLEN]; prefix2str(&api.prefix, buf[0], sizeof(buf[0])); if (add) { inet_ntop(api.prefix.family, &nexthop, buf[1], sizeof(buf[1])); zlog_debug( "Rx route ADD VRF %u %s[%d] %s nexthop %s (type %d if %u) metric %u tag %" ROUTE_TAG_PRI, vrf_id, zebra_route_string(api.type), api.instance, buf[0], buf[1], nhtype, ifindex, api.metric, api.tag); } else { zlog_debug( "Rx route DEL VRF %u %s[%d] %s", vrf_id, zebra_route_string(api.type), api.instance, buf[0]); } } return 0; } struct interface *if_lookup_by_ipv4(struct in_addr *addr, vrf_id_t vrf_id) { struct vrf *vrf; struct listnode *cnode; struct interface *ifp; struct connected *connected; struct prefix_ipv4 p; struct prefix *cp; vrf = vrf_lookup_by_id(vrf_id); if (!vrf) return NULL; p.family = AF_INET; p.prefix = *addr; p.prefixlen = IPV4_MAX_BITLEN; FOR_ALL_INTERFACES (vrf, ifp) { for (ALL_LIST_ELEMENTS_RO(ifp->connected, cnode, connected)) { cp = connected->address; if (cp->family == AF_INET) if (prefix_match(cp, (struct prefix *)&p)) return ifp; } } return NULL; } struct interface *if_lookup_by_ipv4_exact(struct in_addr *addr, vrf_id_t vrf_id) { struct vrf *vrf; struct listnode *cnode; struct interface *ifp; struct connected *connected; struct prefix *cp; vrf = vrf_lookup_by_id(vrf_id); if (!vrf) return NULL; FOR_ALL_INTERFACES (vrf, ifp) { for (ALL_LIST_ELEMENTS_RO(ifp->connected, cnode, connected)) { cp = connected->address; if (cp->family == AF_INET) if (IPV4_ADDR_SAME(&cp->u.prefix4, addr)) return ifp; } } return NULL; } struct interface *if_lookup_by_ipv6(struct in6_addr *addr, ifindex_t ifindex, vrf_id_t vrf_id) { struct vrf *vrf; struct listnode *cnode; struct interface *ifp; struct connected *connected; struct prefix_ipv6 p; struct prefix *cp; vrf = vrf_lookup_by_id(vrf_id); if (!vrf) return NULL; p.family = AF_INET6; p.prefix = *addr; p.prefixlen = IPV6_MAX_BITLEN; FOR_ALL_INTERFACES (vrf, ifp) { for (ALL_LIST_ELEMENTS_RO(ifp->connected, cnode, connected)) { cp = connected->address; if (cp->family == AF_INET6) if (prefix_match(cp, (struct prefix *)&p)) { if (IN6_IS_ADDR_LINKLOCAL( &cp->u.prefix6)) { if (ifindex == ifp->ifindex) return ifp; } else return ifp; } } } return NULL; } struct interface *if_lookup_by_ipv6_exact(struct in6_addr *addr, ifindex_t ifindex, vrf_id_t vrf_id) { struct vrf *vrf; struct listnode *cnode; struct interface *ifp; struct connected *connected; struct prefix *cp; vrf = vrf_lookup_by_id(vrf_id); if (!vrf) return NULL; FOR_ALL_INTERFACES (vrf, ifp) { for (ALL_LIST_ELEMENTS_RO(ifp->connected, cnode, connected)) { cp = connected->address; if (cp->family == AF_INET6) if (IPV6_ADDR_SAME(&cp->u.prefix6, addr)) { if (IN6_IS_ADDR_LINKLOCAL( &cp->u.prefix6)) { if (ifindex == ifp->ifindex) return ifp; } else return ifp; } } } return NULL; } static int if_get_ipv6_global(struct interface *ifp, struct in6_addr *addr) { struct listnode *cnode; struct connected *connected; struct prefix *cp; for (ALL_LIST_ELEMENTS_RO(ifp->connected, cnode, connected)) { cp = connected->address; if (cp->family == AF_INET6) if (!IN6_IS_ADDR_LINKLOCAL(&cp->u.prefix6)) { memcpy(addr, &cp->u.prefix6, IPV6_MAX_BYTELEN); return 1; } } return 0; } static int if_get_ipv6_local(struct interface *ifp, struct in6_addr *addr) { struct listnode *cnode; struct connected *connected; struct prefix *cp; for (ALL_LIST_ELEMENTS_RO(ifp->connected, cnode, connected)) { cp = connected->address; if (cp->family == AF_INET6) if (IN6_IS_ADDR_LINKLOCAL(&cp->u.prefix6)) { memcpy(addr, &cp->u.prefix6, IPV6_MAX_BYTELEN); return 1; } } return 0; } static int if_get_ipv4_address(struct interface *ifp, struct in_addr *addr) { struct listnode *cnode; struct connected *connected; struct prefix *cp; for (ALL_LIST_ELEMENTS_RO(ifp->connected, cnode, connected)) { cp = connected->address; if ((cp->family == AF_INET) && !ipv4_martian(&(cp->u.prefix4))) { *addr = cp->u.prefix4; return 1; } } return 0; } bool bgp_zebra_nexthop_set(union sockunion *local, union sockunion *remote, struct bgp_nexthop *nexthop, struct peer *peer) { int ret = 0; struct interface *ifp = NULL; memset(nexthop, 0, sizeof(struct bgp_nexthop)); if (!local) return false; if (!remote) return false; if (local->sa.sa_family == AF_INET) { nexthop->v4 = local->sin.sin_addr; if (peer->update_if) ifp = if_lookup_by_name(peer->update_if, peer->bgp->vrf_id); else ifp = if_lookup_by_ipv4_exact(&local->sin.sin_addr, peer->bgp->vrf_id); } if (local->sa.sa_family == AF_INET6) { memcpy(&nexthop->v6_global, &local->sin6.sin6_addr, IPV6_MAX_BYTELEN); if (IN6_IS_ADDR_LINKLOCAL(&local->sin6.sin6_addr)) { if (peer->conf_if || peer->ifname) ifp = if_lookup_by_name(peer->conf_if ? peer->conf_if : peer->ifname, peer->bgp->vrf_id); } else if (peer->update_if) ifp = if_lookup_by_name(peer->update_if, peer->bgp->vrf_id); else ifp = if_lookup_by_ipv6_exact(&local->sin6.sin6_addr, local->sin6.sin6_scope_id, peer->bgp->vrf_id); } if (!ifp) { /* * BGP views do not currently get proper data * from zebra( when attached ) to be able to * properly resolve nexthops, so give this * instance type a pass. */ if (peer->bgp->inst_type == BGP_INSTANCE_TYPE_VIEW) return true; /* * If we have no interface data but we have established * some connection w/ zebra than something has gone * terribly terribly wrong here, so say this failed * If we do not any zebra connection then not * having a ifp pointer is ok. */ return zclient_num_connects ? false : true; } nexthop->ifp = ifp; /* IPv4 connection, fetch and store IPv6 local address(es) if any. */ if (local->sa.sa_family == AF_INET) { /* IPv6 nexthop*/ ret = if_get_ipv6_global(ifp, &nexthop->v6_global); if (!ret) { /* There is no global nexthop. Use link-local address as * both the * global and link-local nexthop. In this scenario, the * expectation * for interop is that the network admin would use a * route-map to * specify the global IPv6 nexthop. */ if_get_ipv6_local(ifp, &nexthop->v6_global); memcpy(&nexthop->v6_local, &nexthop->v6_global, IPV6_MAX_BYTELEN); } else if_get_ipv6_local(ifp, &nexthop->v6_local); if (if_lookup_by_ipv4(&remote->sin.sin_addr, peer->bgp->vrf_id)) peer->shared_network = 1; else peer->shared_network = 0; } /* IPv6 connection, fetch and store IPv4 local address if any. */ if (local->sa.sa_family == AF_INET6) { struct interface *direct = NULL; /* IPv4 nexthop. */ ret = if_get_ipv4_address(ifp, &nexthop->v4); if (!ret && peer->local_id.s_addr) nexthop->v4 = peer->local_id; /* Global address*/ if (!IN6_IS_ADDR_LINKLOCAL(&local->sin6.sin6_addr)) { memcpy(&nexthop->v6_global, &local->sin6.sin6_addr, IPV6_MAX_BYTELEN); /* If directory connected set link-local address. */ direct = if_lookup_by_ipv6(&remote->sin6.sin6_addr, remote->sin6.sin6_scope_id, peer->bgp->vrf_id); if (direct) if_get_ipv6_local(ifp, &nexthop->v6_local); } else /* Link-local address. */ { ret = if_get_ipv6_global(ifp, &nexthop->v6_global); /* If there is no global address. Set link-local address as global. I know this break RFC specification... */ /* In this scenario, the expectation for interop is that * the * network admin would use a route-map to specify the * global * IPv6 nexthop. */ if (!ret) memcpy(&nexthop->v6_global, &local->sin6.sin6_addr, IPV6_MAX_BYTELEN); /* Always set the link-local address */ memcpy(&nexthop->v6_local, &local->sin6.sin6_addr, IPV6_MAX_BYTELEN); } if (IN6_IS_ADDR_LINKLOCAL(&local->sin6.sin6_addr) || if_lookup_by_ipv6(&remote->sin6.sin6_addr, remote->sin6.sin6_scope_id, peer->bgp->vrf_id)) peer->shared_network = 1; else peer->shared_network = 0; } /* KAME stack specific treatment. */ #ifdef KAME if (IN6_IS_ADDR_LINKLOCAL(&nexthop->v6_global) && IN6_LINKLOCAL_IFINDEX(nexthop->v6_global)) { SET_IN6_LINKLOCAL_IFINDEX(nexthop->v6_global, 0); } if (IN6_IS_ADDR_LINKLOCAL(&nexthop->v6_local) && IN6_LINKLOCAL_IFINDEX(nexthop->v6_local)) { SET_IN6_LINKLOCAL_IFINDEX(nexthop->v6_local, 0); } #endif /* KAME */ /* If we have identified the local interface, there is no error for now. */ return true; } static struct in6_addr * bgp_path_info_to_ipv6_nexthop(struct bgp_path_info *path, ifindex_t *ifindex) { struct in6_addr *nexthop = NULL; /* Only global address nexthop exists. */ if (path->attr->mp_nexthop_len == BGP_ATTR_NHLEN_IPV6_GLOBAL || path->attr->mp_nexthop_len == BGP_ATTR_NHLEN_VPNV6_GLOBAL) { nexthop = &path->attr->mp_nexthop_global; if (IN6_IS_ADDR_LINKLOCAL(nexthop)) *ifindex = path->attr->nh_ifindex; } /* If both global and link-local address present. */ if (path->attr->mp_nexthop_len == BGP_ATTR_NHLEN_IPV6_GLOBAL_AND_LL || path->attr->mp_nexthop_len == BGP_ATTR_NHLEN_VPNV6_GLOBAL_AND_LL) { /* Check if route-map is set to prefer global over link-local */ if (path->attr->mp_nexthop_prefer_global) { nexthop = &path->attr->mp_nexthop_global; if (IN6_IS_ADDR_LINKLOCAL(nexthop)) *ifindex = path->attr->nh_ifindex; } else { /* Workaround for Cisco's nexthop bug. */ if (IN6_IS_ADDR_UNSPECIFIED( &path->attr->mp_nexthop_global) && path->peer->su_remote->sa.sa_family == AF_INET6) { nexthop = &path->peer->su_remote->sin6.sin6_addr; if (IN6_IS_ADDR_LINKLOCAL(nexthop)) *ifindex = path->peer->nexthop.ifp ->ifindex; } else { nexthop = &path->attr->mp_nexthop_local; if (IN6_IS_ADDR_LINKLOCAL(nexthop)) *ifindex = path->attr->nh_lla_ifindex; } } } return nexthop; } static int bgp_table_map_apply(struct route_map *map, struct prefix *p, struct bgp_path_info *path) { route_map_result_t ret; ret = route_map_apply(map, p, RMAP_BGP, path); bgp_attr_flush(path->attr); if (ret != RMAP_DENYMATCH) return 1; if (bgp_debug_zebra(p)) { if (p->family == AF_INET) { char buf[2][INET_ADDRSTRLEN]; zlog_debug( "Zebra rmap deny: IPv4 route %s/%d nexthop %s", inet_ntop(AF_INET, &p->u.prefix4, buf[0], sizeof(buf[0])), p->prefixlen, inet_ntop(AF_INET, &path->attr->nexthop, buf[1], sizeof(buf[1]))); } if (p->family == AF_INET6) { char buf[2][INET6_ADDRSTRLEN]; ifindex_t ifindex; struct in6_addr *nexthop; nexthop = bgp_path_info_to_ipv6_nexthop(path, &ifindex); zlog_debug( "Zebra rmap deny: IPv6 route %s/%d nexthop %s", inet_ntop(AF_INET6, &p->u.prefix6, buf[0], sizeof(buf[0])), p->prefixlen, inet_ntop(AF_INET6, nexthop, buf[1], sizeof(buf[1]))); } } return 0; } static struct thread *bgp_tm_thread_connect; static bool bgp_tm_status_connected; static bool bgp_tm_chunk_obtained; #define BGP_FLOWSPEC_TABLE_CHUNK 100000 static uint32_t bgp_tm_min, bgp_tm_max, bgp_tm_chunk_size; struct bgp *bgp_tm_bgp; static int bgp_zebra_tm_connect(struct thread *t) { struct zclient *zclient; int delay = 10, ret = 0; zclient = THREAD_ARG(t); if (bgp_tm_status_connected && zclient->sock > 0) delay = 60; else { bgp_tm_status_connected = false; ret = tm_table_manager_connect(zclient); } if (ret < 0) { zlog_info("Error connecting to table manager!"); bgp_tm_status_connected = false; } else { if (!bgp_tm_status_connected) zlog_debug("Connecting to table manager. Success"); bgp_tm_status_connected = true; if (!bgp_tm_chunk_obtained) { if (bgp_zebra_get_table_range(bgp_tm_chunk_size, &bgp_tm_min, &bgp_tm_max) >= 0) { bgp_tm_chunk_obtained = true; /* parse non installed entries */ bgp_zebra_announce_table(bgp_tm_bgp, AFI_IP, SAFI_FLOWSPEC); } } } thread_add_timer(bm->master, bgp_zebra_tm_connect, zclient, delay, &bgp_tm_thread_connect); return 0; } bool bgp_zebra_tm_chunk_obtained(void) { return bgp_tm_chunk_obtained; } uint32_t bgp_zebra_tm_get_id(void) { static int table_id; if (!bgp_tm_chunk_obtained) return ++table_id; return bgp_tm_min++; } void bgp_zebra_init_tm_connect(struct bgp *bgp) { int delay = 1; /* if already set, do nothing */ if (bgp_tm_thread_connect != NULL) return; bgp_tm_status_connected = false; bgp_tm_chunk_obtained = false; bgp_tm_min = bgp_tm_max = 0; bgp_tm_chunk_size = BGP_FLOWSPEC_TABLE_CHUNK; bgp_tm_bgp = bgp; thread_add_timer(bm->master, bgp_zebra_tm_connect, zclient, delay, &bgp_tm_thread_connect); } int bgp_zebra_get_table_range(uint32_t chunk_size, uint32_t *start, uint32_t *end) { int ret; if (!bgp_tm_status_connected) return -1; ret = tm_get_table_chunk(zclient, chunk_size, start, end); if (ret < 0) { flog_err(EC_BGP_TABLE_CHUNK, "BGP: Error getting table chunk %u", chunk_size); return -1; } zlog_info("BGP: Table Manager returns range from chunk %u is [%u %u]", chunk_size, *start, *end); return 0; } static int update_ipv4nh_for_route_install(int nh_othervrf, struct bgp *nh_bgp, struct in_addr *nexthop, struct attr *attr, bool is_evpn, struct zapi_nexthop *api_nh) { api_nh->gate.ipv4 = *nexthop; api_nh->vrf_id = nh_bgp->vrf_id; /* Need to set fields appropriately for EVPN routes imported into * a VRF (which are programmed as onlink on l3-vni SVI) as well as * connected routes leaked into a VRF. */ if (is_evpn) { api_nh->type = NEXTHOP_TYPE_IPV4_IFINDEX; api_nh->onlink = true; api_nh->ifindex = nh_bgp->l3vni_svi_ifindex; } else if (nh_othervrf && api_nh->gate.ipv4.s_addr == INADDR_ANY) { api_nh->type = NEXTHOP_TYPE_IFINDEX; api_nh->ifindex = attr->nh_ifindex; } else api_nh->type = NEXTHOP_TYPE_IPV4; return 1; } static int update_ipv6nh_for_route_install(int nh_othervrf, struct bgp *nh_bgp, struct in6_addr *nexthop, ifindex_t ifindex, struct bgp_path_info *pi, struct bgp_path_info *best_pi, bool is_evpn, struct zapi_nexthop *api_nh) { struct attr *attr; attr = pi->attr; api_nh->vrf_id = nh_bgp->vrf_id; if (is_evpn) { api_nh->type = NEXTHOP_TYPE_IPV6_IFINDEX; api_nh->onlink = true; api_nh->ifindex = nh_bgp->l3vni_svi_ifindex; } else if (nh_othervrf) { if (IN6_IS_ADDR_UNSPECIFIED(nexthop)) { api_nh->type = NEXTHOP_TYPE_IFINDEX; api_nh->ifindex = attr->nh_ifindex; } else if (IN6_IS_ADDR_LINKLOCAL(nexthop)) { if (ifindex == 0) return 0; api_nh->type = NEXTHOP_TYPE_IPV6_IFINDEX; api_nh->ifindex = ifindex; } else { api_nh->type = NEXTHOP_TYPE_IPV6; api_nh->ifindex = 0; } } else { if (IN6_IS_ADDR_LINKLOCAL(nexthop)) { if (pi == best_pi && attr->mp_nexthop_len == BGP_ATTR_NHLEN_IPV6_GLOBAL_AND_LL) if (pi->peer->nexthop.ifp) ifindex = pi->peer->nexthop.ifp->ifindex; if (!ifindex) { if (pi->peer->conf_if) ifindex = pi->peer->ifp->ifindex; else if (pi->peer->ifname) ifindex = ifname2ifindex( pi->peer->ifname, pi->peer->bgp->vrf_id); else if (pi->peer->nexthop.ifp) ifindex = pi->peer->nexthop.ifp->ifindex; } if (ifindex == 0) return 0; api_nh->type = NEXTHOP_TYPE_IPV6_IFINDEX; api_nh->ifindex = ifindex; } else { api_nh->type = NEXTHOP_TYPE_IPV6; api_nh->ifindex = 0; } } api_nh->gate.ipv6 = *nexthop; return 1; } void bgp_zebra_announce(struct bgp_node *rn, struct prefix *p, struct bgp_path_info *info, struct bgp *bgp, afi_t afi, safi_t safi) { struct zapi_route api; struct zapi_nexthop *api_nh; int nh_family; unsigned int valid_nh_count = 0; int has_valid_label = 0; uint8_t distance; struct peer *peer; struct bgp_path_info *mpinfo; uint32_t metric; struct attr local_attr; struct bgp_path_info local_info; struct bgp_path_info *mpinfo_cp = &local_info; route_tag_t tag; mpls_label_t label; int nh_othervrf = 0; char buf_prefix[PREFIX_STRLEN]; /* filled in if we are debugging */ bool is_evpn; int nh_updated; /* Don't try to install if we're not connected to Zebra or Zebra doesn't * know of this instance. */ if (!bgp_install_info_to_zebra(bgp)) return; if (bgp->main_zebra_update_hold) return; if (bgp_debug_zebra(p)) prefix2str(p, buf_prefix, sizeof(buf_prefix)); if (safi == SAFI_FLOWSPEC) { bgp_pbr_update_entry(bgp, &rn->p, info, afi, safi, true); return; } /* * vrf leaking support (will have only one nexthop) */ if (info->extra && info->extra->bgp_orig) nh_othervrf = 1; /* Make Zebra API structure. */ memset(&api, 0, sizeof(api)); api.vrf_id = bgp->vrf_id; api.type = ZEBRA_ROUTE_BGP; api.safi = safi; api.prefix = *p; SET_FLAG(api.message, ZAPI_MESSAGE_NEXTHOP); peer = info->peer; if (info->type == ZEBRA_ROUTE_BGP && info->sub_type == BGP_ROUTE_IMPORTED) { /* Obtain peer from parent */ if (info->extra && info->extra->parent) peer = ((struct bgp_path_info *)(info->extra->parent)) ->peer; } tag = info->attr->tag; /* If the route's source is EVPN, flag as such. */ is_evpn = is_route_parent_evpn(info); if (is_evpn) SET_FLAG(api.flags, ZEBRA_FLAG_EVPN_ROUTE); if (peer->sort == BGP_PEER_IBGP || peer->sort == BGP_PEER_CONFED || info->sub_type == BGP_ROUTE_AGGREGATE) { SET_FLAG(api.flags, ZEBRA_FLAG_IBGP); SET_FLAG(api.flags, ZEBRA_FLAG_ALLOW_RECURSION); } if ((peer->sort == BGP_PEER_EBGP && peer->ttl != 1) || CHECK_FLAG(peer->flags, PEER_FLAG_DISABLE_CONNECTED_CHECK) || bgp_flag_check(bgp, BGP_FLAG_DISABLE_NH_CONNECTED_CHK)) SET_FLAG(api.flags, ZEBRA_FLAG_ALLOW_RECURSION); /* Metric is currently based on the best-path only */ metric = info->attr->med; for (mpinfo = info; mpinfo; mpinfo = bgp_path_info_mpath_next(mpinfo)) { if (valid_nh_count >= multipath_num) break; *mpinfo_cp = *mpinfo; /* Get nexthop address-family */ if (p->family == AF_INET && !BGP_ATTR_NEXTHOP_AFI_IP6(mpinfo_cp->attr)) nh_family = AF_INET; else if (p->family == AF_INET6 || (p->family == AF_INET && BGP_ATTR_NEXTHOP_AFI_IP6(mpinfo_cp->attr))) nh_family = AF_INET6; else continue; api_nh = &api.nexthops[valid_nh_count]; if (nh_family == AF_INET) { if (bgp_debug_zebra(&api.prefix)) { if (mpinfo->extra) { zlog_debug( "%s: p=%s, bgp_is_valid_label: %d", __func__, buf_prefix, bgp_is_valid_label( &mpinfo->extra ->label[0])); } else { zlog_debug( "%s: p=%s, extra is NULL, no label", __func__, buf_prefix); } } if (bgp->table_map[afi][safi].name) { /* Copy info and attributes, so the route-map apply doesn't modify the BGP route info. */ local_attr = *mpinfo->attr; mpinfo_cp->attr = &local_attr; } if (bgp->table_map[afi][safi].name) { if (!bgp_table_map_apply( bgp->table_map[afi][safi].map, p, mpinfo_cp)) continue; /* metric/tag is only allowed to be * overridden on 1st nexthop */ if (mpinfo == info) { metric = mpinfo_cp->attr->med; tag = mpinfo_cp->attr->tag; } } nh_updated = update_ipv4nh_for_route_install( nh_othervrf, nh_othervrf ? info->extra->bgp_orig : bgp, &mpinfo_cp->attr->nexthop, mpinfo_cp->attr, is_evpn, api_nh); } else { ifindex_t ifindex = IFINDEX_INTERNAL; struct in6_addr *nexthop; if (bgp->table_map[afi][safi].name) { /* Copy info and attributes, so the route-map apply doesn't modify the BGP route info. */ local_attr = *mpinfo->attr; mpinfo_cp->attr = &local_attr; } if (bgp->table_map[afi][safi].name) { /* Copy info and attributes, so the route-map apply doesn't modify the BGP route info. */ local_attr = *mpinfo->attr; mpinfo_cp->attr = &local_attr; if (!bgp_table_map_apply( bgp->table_map[afi][safi].map, p, mpinfo_cp)) continue; /* metric/tag is only allowed to be * overridden on 1st nexthop */ if (mpinfo == info) { metric = mpinfo_cp->attr->med; tag = mpinfo_cp->attr->tag; } } nexthop = bgp_path_info_to_ipv6_nexthop(mpinfo_cp, &ifindex); nh_updated = update_ipv6nh_for_route_install( nh_othervrf, nh_othervrf ? info->extra->bgp_orig : bgp, nexthop, ifindex, mpinfo, info, is_evpn, api_nh); } /* Did we get proper nexthop info to update zebra? */ if (!nh_updated) continue; if (mpinfo->extra && bgp_is_valid_label(&mpinfo->extra->label[0]) && !CHECK_FLAG(api.flags, ZEBRA_FLAG_EVPN_ROUTE)) { has_valid_label = 1; label = label_pton(&mpinfo->extra->label[0]); api_nh->label_num = 1; api_nh->labels[0] = label; } memcpy(&api_nh->rmac, &(mpinfo->attr->rmac), sizeof(struct ethaddr)); valid_nh_count++; } /* if this is a evpn route we don't have to include the label */ if (has_valid_label && !(CHECK_FLAG(api.flags, ZEBRA_FLAG_EVPN_ROUTE))) SET_FLAG(api.message, ZAPI_MESSAGE_LABEL); /* * When we create an aggregate route we must also * install a Null0 route in the RIB, so overwrite * what was written into api with a blackhole route */ if (info->sub_type == BGP_ROUTE_AGGREGATE) zapi_route_set_blackhole(&api, BLACKHOLE_NULL); else api.nexthop_num = valid_nh_count; SET_FLAG(api.message, ZAPI_MESSAGE_METRIC); api.metric = metric; if (tag) { SET_FLAG(api.message, ZAPI_MESSAGE_TAG); api.tag = tag; } distance = bgp_distance_apply(p, info, afi, safi, bgp); if (distance) { SET_FLAG(api.message, ZAPI_MESSAGE_DISTANCE); api.distance = distance; } if (bgp_debug_zebra(p)) { char prefix_buf[PREFIX_STRLEN]; char nh_buf[INET6_ADDRSTRLEN]; char label_buf[20]; int i; prefix2str(&api.prefix, prefix_buf, sizeof(prefix_buf)); zlog_debug("Tx route %s VRF %u %s metric %u tag %" ROUTE_TAG_PRI " count %d", valid_nh_count ? "add" : "delete", bgp->vrf_id, prefix_buf, api.metric, api.tag, api.nexthop_num); for (i = 0; i < api.nexthop_num; i++) { api_nh = &api.nexthops[i]; switch (api_nh->type) { case NEXTHOP_TYPE_IFINDEX: nh_buf[0] = '\0'; break; case NEXTHOP_TYPE_IPV4: case NEXTHOP_TYPE_IPV4_IFINDEX: nh_family = AF_INET; inet_ntop(nh_family, &api_nh->gate, nh_buf, sizeof(nh_buf)); break; case NEXTHOP_TYPE_IPV6: case NEXTHOP_TYPE_IPV6_IFINDEX: nh_family = AF_INET6; inet_ntop(nh_family, &api_nh->gate, nh_buf, sizeof(nh_buf)); break; case NEXTHOP_TYPE_BLACKHOLE: strlcpy(nh_buf, "blackhole", sizeof(nh_buf)); break; default: /* Note: add new nexthop case */ assert(0); break; } label_buf[0] = '\0'; if (has_valid_label && !CHECK_FLAG(api.flags, ZEBRA_FLAG_EVPN_ROUTE)) sprintf(label_buf, "label %u", api_nh->labels[0]); zlog_debug(" nhop [%d]: %s if %u VRF %u %s", i + 1, nh_buf, api_nh->ifindex, api_nh->vrf_id, label_buf); } } if (bgp_debug_zebra(p)) { int recursion_flag = 0; if (CHECK_FLAG(api.flags, ZEBRA_FLAG_ALLOW_RECURSION)) recursion_flag = 1; zlog_debug("%s: %s: announcing to zebra (recursion %sset)", __func__, buf_prefix, (recursion_flag ? "" : "NOT ")); } zclient_route_send(valid_nh_count ? ZEBRA_ROUTE_ADD : ZEBRA_ROUTE_DELETE, zclient, &api); } /* Announce all routes of a table to zebra */ void bgp_zebra_announce_table(struct bgp *bgp, afi_t afi, safi_t safi) { struct bgp_node *rn; struct bgp_table *table; struct bgp_path_info *pi; /* Don't try to install if we're not connected to Zebra or Zebra doesn't * know of this instance. */ if (!bgp_install_info_to_zebra(bgp)) return; table = bgp->rib[afi][safi]; if (!table) return; for (rn = bgp_table_top(table); rn; rn = bgp_route_next(rn)) for (pi = bgp_node_get_bgp_path_info(rn); pi; pi = pi->next) if (CHECK_FLAG(pi->flags, BGP_PATH_SELECTED) && (pi->type == ZEBRA_ROUTE_BGP && (pi->sub_type == BGP_ROUTE_NORMAL || pi->sub_type == BGP_ROUTE_IMPORTED))) bgp_zebra_announce(rn, &rn->p, pi, bgp, afi, safi); } void bgp_zebra_withdraw(struct prefix *p, struct bgp_path_info *info, struct bgp *bgp, safi_t safi) { struct zapi_route api; struct peer *peer; /* Don't try to install if we're not connected to Zebra or Zebra doesn't * know of this instance. */ if (!bgp_install_info_to_zebra(bgp)) return; if (safi == SAFI_FLOWSPEC) { peer = info->peer; bgp_pbr_update_entry(peer->bgp, p, info, AFI_IP, safi, false); return; } memset(&api, 0, sizeof(api)); api.vrf_id = bgp->vrf_id; api.type = ZEBRA_ROUTE_BGP; api.safi = safi; api.prefix = *p; /* If the route's source is EVPN, flag as such. */ if (is_route_parent_evpn(info)) SET_FLAG(api.flags, ZEBRA_FLAG_EVPN_ROUTE); if (bgp_debug_zebra(p)) { char buf[PREFIX_STRLEN]; prefix2str(&api.prefix, buf, sizeof(buf)); zlog_debug("Tx route delete VRF %u %s", bgp->vrf_id, buf); } zclient_route_send(ZEBRA_ROUTE_DELETE, zclient, &api); } struct bgp_redist *bgp_redist_lookup(struct bgp *bgp, afi_t afi, uint8_t type, unsigned short instance) { struct list *red_list; struct listnode *node; struct bgp_redist *red; red_list = bgp->redist[afi][type]; if (!red_list) return (NULL); for (ALL_LIST_ELEMENTS_RO(red_list, node, red)) if (red->instance == instance) return red; return NULL; } struct bgp_redist *bgp_redist_add(struct bgp *bgp, afi_t afi, uint8_t type, unsigned short instance) { struct list *red_list; struct bgp_redist *red; red = bgp_redist_lookup(bgp, afi, type, instance); if (red) return red; if (!bgp->redist[afi][type]) bgp->redist[afi][type] = list_new(); red_list = bgp->redist[afi][type]; red = XCALLOC(MTYPE_BGP_REDIST, sizeof(struct bgp_redist)); red->instance = instance; listnode_add(red_list, red); return red; } static void bgp_redist_del(struct bgp *bgp, afi_t afi, uint8_t type, unsigned short instance) { struct bgp_redist *red; red = bgp_redist_lookup(bgp, afi, type, instance); if (red) { listnode_delete(bgp->redist[afi][type], red); XFREE(MTYPE_BGP_REDIST, red); if (!bgp->redist[afi][type]->count) list_delete(&bgp->redist[afi][type]); } } /* Other routes redistribution into BGP. */ int bgp_redistribute_set(struct bgp *bgp, afi_t afi, int type, unsigned short instance, bool changed) { /* If redistribute options are changed call * bgp_redistribute_unreg() to reset the option and withdraw * the routes */ if (changed) bgp_redistribute_unreg(bgp, afi, type, instance); /* Return if already redistribute flag is set. */ if (instance) { if (redist_check_instance(&zclient->mi_redist[afi][type], instance)) return CMD_WARNING; redist_add_instance(&zclient->mi_redist[afi][type], instance); } else { if (vrf_bitmap_check(zclient->redist[afi][type], bgp->vrf_id)) return CMD_WARNING; #if ENABLE_BGP_VNC if (EVPN_ENABLED(bgp) && type == ZEBRA_ROUTE_VNC_DIRECT) { vnc_export_bgp_enable( bgp, afi); /* only enables if mode bits cfg'd */ } #endif vrf_bitmap_set(zclient->redist[afi][type], bgp->vrf_id); } /* * Don't try to register if we're not connected to Zebra or Zebra * doesn't know of this instance. * * When we come up later well resend if needed. */ if (!bgp_install_info_to_zebra(bgp)) return CMD_SUCCESS; if (BGP_DEBUG(zebra, ZEBRA)) zlog_debug("Tx redistribute add VRF %u afi %d %s %d", bgp->vrf_id, afi, zebra_route_string(type), instance); /* Send distribute add message to zebra. */ zebra_redistribute_send(ZEBRA_REDISTRIBUTE_ADD, zclient, afi, type, instance, bgp->vrf_id); return CMD_SUCCESS; } int bgp_redistribute_resend(struct bgp *bgp, afi_t afi, int type, unsigned short instance) { /* Don't try to send if we're not connected to Zebra or Zebra doesn't * know of this instance. */ if (!bgp_install_info_to_zebra(bgp)) return -1; if (BGP_DEBUG(zebra, ZEBRA)) zlog_debug("Tx redistribute del/add VRF %u afi %d %s %d", bgp->vrf_id, afi, zebra_route_string(type), instance); /* Send distribute add message to zebra. */ zebra_redistribute_send(ZEBRA_REDISTRIBUTE_DELETE, zclient, afi, type, instance, bgp->vrf_id); zebra_redistribute_send(ZEBRA_REDISTRIBUTE_ADD, zclient, afi, type, instance, bgp->vrf_id); return 0; } /* Redistribute with route-map specification. */ int bgp_redistribute_rmap_set(struct bgp_redist *red, const char *name, struct route_map *route_map) { if (red->rmap.name && (strcmp(red->rmap.name, name) == 0)) return 0; XFREE(MTYPE_ROUTE_MAP_NAME, red->rmap.name); /* Decrement the count for existing routemap and * increment the count for new route map. */ route_map_counter_decrement(red->rmap.map); red->rmap.name = XSTRDUP(MTYPE_ROUTE_MAP_NAME, name); red->rmap.map = route_map; route_map_counter_increment(red->rmap.map); return 1; } /* Redistribute with metric specification. */ int bgp_redistribute_metric_set(struct bgp *bgp, struct bgp_redist *red, afi_t afi, int type, uint32_t metric) { struct bgp_node *rn; struct bgp_path_info *pi; if (red->redist_metric_flag && red->redist_metric == metric) return 0; red->redist_metric_flag = 1; red->redist_metric = metric; for (rn = bgp_table_top(bgp->rib[afi][SAFI_UNICAST]); rn; rn = bgp_route_next(rn)) { for (pi = bgp_node_get_bgp_path_info(rn); pi; pi = pi->next) { if (pi->sub_type == BGP_ROUTE_REDISTRIBUTE && pi->type == type && pi->instance == red->instance) { struct attr *old_attr; struct attr new_attr; new_attr = *pi->attr; new_attr.med = red->redist_metric; old_attr = pi->attr; pi->attr = bgp_attr_intern(&new_attr); bgp_attr_unintern(&old_attr); bgp_path_info_set_flag(rn, pi, BGP_PATH_ATTR_CHANGED); bgp_process(bgp, rn, afi, SAFI_UNICAST); } } } return 1; } /* Unset redistribution. */ int bgp_redistribute_unreg(struct bgp *bgp, afi_t afi, int type, unsigned short instance) { struct bgp_redist *red; red = bgp_redist_lookup(bgp, afi, type, instance); if (!red) return CMD_SUCCESS; /* Return if zebra connection is disabled. */ if (instance) { if (!redist_check_instance(&zclient->mi_redist[afi][type], instance)) return CMD_WARNING; redist_del_instance(&zclient->mi_redist[afi][type], instance); } else { if (!vrf_bitmap_check(zclient->redist[afi][type], bgp->vrf_id)) return CMD_WARNING; vrf_bitmap_unset(zclient->redist[afi][type], bgp->vrf_id); } if (bgp_install_info_to_zebra(bgp)) { /* Send distribute delete message to zebra. */ if (BGP_DEBUG(zebra, ZEBRA)) zlog_debug("Tx redistribute del VRF %u afi %d %s %d", bgp->vrf_id, afi, zebra_route_string(type), instance); zebra_redistribute_send(ZEBRA_REDISTRIBUTE_DELETE, zclient, afi, type, instance, bgp->vrf_id); } /* Withdraw redistributed routes from current BGP's routing table. */ bgp_redistribute_withdraw(bgp, afi, type, instance); return CMD_SUCCESS; } /* Unset redistribution. */ int bgp_redistribute_unset(struct bgp *bgp, afi_t afi, int type, unsigned short instance) { struct bgp_redist *red; /* * vnc and vpn->vrf checks must be before red check because * they operate within bgpd irrespective of zebra connection * status. red lookup fails if there is no zebra connection. */ #if ENABLE_BGP_VNC if (EVPN_ENABLED(bgp) && type == ZEBRA_ROUTE_VNC_DIRECT) { vnc_export_bgp_disable(bgp, afi); } #endif red = bgp_redist_lookup(bgp, afi, type, instance); if (!red) return CMD_SUCCESS; bgp_redistribute_unreg(bgp, afi, type, instance); /* Unset route-map. */ XFREE(MTYPE_ROUTE_MAP_NAME, red->rmap.name); route_map_counter_decrement(red->rmap.map); red->rmap.name = NULL; red->rmap.map = NULL; /* Unset metric. */ red->redist_metric_flag = 0; red->redist_metric = 0; bgp_redist_del(bgp, afi, type, instance); return CMD_SUCCESS; } void bgp_redistribute_redo(struct bgp *bgp) { afi_t afi; int i; struct list *red_list; struct listnode *node; struct bgp_redist *red; for (afi = AFI_IP; afi < AFI_MAX; afi++) { for (i = 0; i < ZEBRA_ROUTE_MAX; i++) { red_list = bgp->redist[afi][i]; if (!red_list) continue; for (ALL_LIST_ELEMENTS_RO(red_list, node, red)) { bgp_redistribute_resend(bgp, afi, i, red->instance); } } } } /* Unset redistribute vrf bitmap during triggers like restart networking or delete VRFs */ void bgp_unset_redist_vrf_bitmaps(struct bgp *bgp, vrf_id_t old_vrf_id) { int i; afi_t afi; for (afi = AFI_IP; afi < AFI_MAX; afi++) for (i = 0; i < ZEBRA_ROUTE_MAX; i++) if (vrf_bitmap_check(zclient->redist[afi][i], old_vrf_id)) vrf_bitmap_unset(zclient->redist[afi][i], old_vrf_id); return; } void bgp_zclient_reset(void) { zclient_reset(zclient); } /* Register this instance with Zebra. Invoked upon connect (for * default instance) and when other VRFs are learnt (or created and * already learnt). */ void bgp_zebra_instance_register(struct bgp *bgp) { /* Don't try to register if we're not connected to Zebra */ if (!zclient || zclient->sock < 0) return; if (BGP_DEBUG(zebra, ZEBRA)) zlog_debug("Registering VRF %u", bgp->vrf_id); /* Register for router-id, interfaces, redistributed routes. */ zclient_send_reg_requests(zclient, bgp->vrf_id); /* For EVPN instance, register to learn about VNIs, if appropriate. */ if (bgp->advertise_all_vni) bgp_zebra_advertise_all_vni(bgp, 1); bgp_nht_register_nexthops(bgp); } /* Deregister this instance with Zebra. Invoked upon the instance * being deleted (default or VRF) and it is already registered. */ void bgp_zebra_instance_deregister(struct bgp *bgp) { /* Don't try to deregister if we're not connected to Zebra */ if (zclient->sock < 0) return; if (BGP_DEBUG(zebra, ZEBRA)) zlog_debug("Deregistering VRF %u", bgp->vrf_id); /* For EVPN instance, unregister learning about VNIs, if appropriate. */ if (bgp->advertise_all_vni) bgp_zebra_advertise_all_vni(bgp, 0); /* Deregister for router-id, interfaces, redistributed routes. */ zclient_send_dereg_requests(zclient, bgp->vrf_id); } void bgp_zebra_initiate_radv(struct bgp *bgp, struct peer *peer) { int ra_interval = BGP_UNNUM_DEFAULT_RA_INTERVAL; /* Don't try to initiate if we're not connected to Zebra */ if (zclient->sock < 0) return; if (BGP_DEBUG(zebra, ZEBRA)) zlog_debug("%u: Initiating RA for peer %s", bgp->vrf_id, peer->host); zclient_send_interface_radv_req(zclient, bgp->vrf_id, peer->ifp, 1, ra_interval); } void bgp_zebra_terminate_radv(struct bgp *bgp, struct peer *peer) { /* Don't try to terminate if we're not connected to Zebra */ if (zclient->sock < 0) return; if (BGP_DEBUG(zebra, ZEBRA)) zlog_debug("%u: Terminating RA for peer %s", bgp->vrf_id, peer->host); zclient_send_interface_radv_req(zclient, bgp->vrf_id, peer->ifp, 0, 0); } int bgp_zebra_advertise_subnet(struct bgp *bgp, int advertise, vni_t vni) { struct stream *s = NULL; /* Check socket. */ if (!zclient || zclient->sock < 0) return 0; /* Don't try to register if Zebra doesn't know of this instance. */ if (!IS_BGP_INST_KNOWN_TO_ZEBRA(bgp)) { if (BGP_DEBUG(zebra, ZEBRA)) zlog_debug("%s: No zebra instance to talk to, cannot advertise subnet", __PRETTY_FUNCTION__); return 0; } s = zclient->obuf; stream_reset(s); zclient_create_header(s, ZEBRA_ADVERTISE_SUBNET, bgp->vrf_id); stream_putc(s, advertise); stream_put3(s, vni); stream_putw_at(s, 0, stream_get_endp(s)); return zclient_send_message(zclient); } int bgp_zebra_advertise_svi_macip(struct bgp *bgp, int advertise, vni_t vni) { struct stream *s = NULL; /* Check socket. */ if (!zclient || zclient->sock < 0) return 0; /* Don't try to register if Zebra doesn't know of this instance. */ if (!IS_BGP_INST_KNOWN_TO_ZEBRA(bgp)) return 0; s = zclient->obuf; stream_reset(s); zclient_create_header(s, ZEBRA_ADVERTISE_SVI_MACIP, bgp->vrf_id); stream_putc(s, advertise); stream_putl(s, vni); stream_putw_at(s, 0, stream_get_endp(s)); return zclient_send_message(zclient); } int bgp_zebra_advertise_gw_macip(struct bgp *bgp, int advertise, vni_t vni) { struct stream *s = NULL; /* Check socket. */ if (!zclient || zclient->sock < 0) return 0; /* Don't try to register if Zebra doesn't know of this instance. */ if (!IS_BGP_INST_KNOWN_TO_ZEBRA(bgp)) { if (BGP_DEBUG(zebra, ZEBRA)) zlog_debug("%s: No zebra instance to talk to, not installing gw_macip", __PRETTY_FUNCTION__); return 0; } s = zclient->obuf; stream_reset(s); zclient_create_header(s, ZEBRA_ADVERTISE_DEFAULT_GW, bgp->vrf_id); stream_putc(s, advertise); stream_putl(s, vni); stream_putw_at(s, 0, stream_get_endp(s)); return zclient_send_message(zclient); } int bgp_zebra_vxlan_flood_control(struct bgp *bgp, enum vxlan_flood_control flood_ctrl) { struct stream *s; /* Check socket. */ if (!zclient || zclient->sock < 0) return 0; /* Don't try to register if Zebra doesn't know of this instance. */ if (!IS_BGP_INST_KNOWN_TO_ZEBRA(bgp)) { if (BGP_DEBUG(zebra, ZEBRA)) zlog_debug("%s: No zebra instance to talk to, not installing all vni", __PRETTY_FUNCTION__); return 0; } s = zclient->obuf; stream_reset(s); zclient_create_header(s, ZEBRA_VXLAN_FLOOD_CONTROL, bgp->vrf_id); stream_putc(s, flood_ctrl); stream_putw_at(s, 0, stream_get_endp(s)); return zclient_send_message(zclient); } int bgp_zebra_advertise_all_vni(struct bgp *bgp, int advertise) { struct stream *s; /* Check socket. */ if (!zclient || zclient->sock < 0) return 0; /* Don't try to register if Zebra doesn't know of this instance. */ if (!IS_BGP_INST_KNOWN_TO_ZEBRA(bgp)) return 0; s = zclient->obuf; stream_reset(s); zclient_create_header(s, ZEBRA_ADVERTISE_ALL_VNI, bgp->vrf_id); stream_putc(s, advertise); /* Also inform current BUM handling setting. This is really * relevant only when 'advertise' is set. */ stream_putc(s, bgp->vxlan_flood_ctrl); stream_putw_at(s, 0, stream_get_endp(s)); return zclient_send_message(zclient); } int bgp_zebra_dup_addr_detection(struct bgp *bgp) { struct stream *s; /* Check socket. */ if (!zclient || zclient->sock < 0) return 0; /* Don't try to register if Zebra doesn't know of this instance. */ if (!IS_BGP_INST_KNOWN_TO_ZEBRA(bgp)) return 0; if (BGP_DEBUG(zebra, ZEBRA)) zlog_debug("dup addr detect %s max_moves %u time %u freeze %s freeze_time %u", bgp->evpn_info->dup_addr_detect ? "enable" : "disable", bgp->evpn_info->dad_max_moves, bgp->evpn_info->dad_time, bgp->evpn_info->dad_freeze ? "enable" : "disable", bgp->evpn_info->dad_freeze_time); s = zclient->obuf; stream_reset(s); zclient_create_header(s, ZEBRA_DUPLICATE_ADDR_DETECTION, bgp->vrf_id); stream_putl(s, bgp->evpn_info->dup_addr_detect); stream_putl(s, bgp->evpn_info->dad_time); stream_putl(s, bgp->evpn_info->dad_max_moves); stream_putl(s, bgp->evpn_info->dad_freeze); stream_putl(s, bgp->evpn_info->dad_freeze_time); stream_putw_at(s, 0, stream_get_endp(s)); return zclient_send_message(zclient); } static int rule_notify_owner(ZAPI_CALLBACK_ARGS) { uint32_t seqno, priority, unique; enum zapi_rule_notify_owner note; struct bgp_pbr_action *bgp_pbra; struct bgp_pbr_rule *bgp_pbr = NULL; ifindex_t ifi; if (!zapi_rule_notify_decode(zclient->ibuf, &seqno, &priority, &unique, &ifi, ¬e)) return -1; bgp_pbra = bgp_pbr_action_rule_lookup(vrf_id, unique); if (!bgp_pbra) { /* look in bgp pbr rule */ bgp_pbr = bgp_pbr_rule_lookup(vrf_id, unique); if (!bgp_pbr && note != ZAPI_RULE_REMOVED) { if (BGP_DEBUG(zebra, ZEBRA)) zlog_debug("%s: Fail to look BGP rule (%u)", __PRETTY_FUNCTION__, unique); return 0; } } switch (note) { case ZAPI_RULE_FAIL_INSTALL: if (BGP_DEBUG(zebra, ZEBRA)) zlog_debug("%s: Received RULE_FAIL_INSTALL", __PRETTY_FUNCTION__); if (bgp_pbra) { bgp_pbra->installed = false; bgp_pbra->install_in_progress = false; } else { bgp_pbr->installed = false; bgp_pbr->install_in_progress = false; } break; case ZAPI_RULE_INSTALLED: if (bgp_pbra) { bgp_pbra->installed = true; bgp_pbra->install_in_progress = false; } else { struct bgp_path_info *path; struct bgp_path_info_extra *extra; bgp_pbr->installed = true; bgp_pbr->install_in_progress = false; bgp_pbr->action->refcnt++; /* link bgp_info to bgp_pbr */ path = (struct bgp_path_info *)bgp_pbr->path; extra = bgp_path_info_extra_get(path); listnode_add_force(&extra->bgp_fs_iprule, bgp_pbr); } if (BGP_DEBUG(zebra, ZEBRA)) zlog_debug("%s: Received RULE_INSTALLED", __PRETTY_FUNCTION__); break; case ZAPI_RULE_FAIL_REMOVE: case ZAPI_RULE_REMOVED: if (BGP_DEBUG(zebra, ZEBRA)) zlog_debug("%s: Received RULE REMOVED", __PRETTY_FUNCTION__); break; } return 0; } static int ipset_notify_owner(ZAPI_CALLBACK_ARGS) { uint32_t unique; enum zapi_ipset_notify_owner note; struct bgp_pbr_match *bgp_pbim; if (!zapi_ipset_notify_decode(zclient->ibuf, &unique, ¬e)) return -1; bgp_pbim = bgp_pbr_match_ipset_lookup(vrf_id, unique); if (!bgp_pbim) { if (BGP_DEBUG(zebra, ZEBRA)) zlog_debug("%s: Fail to look BGP match ( %u, ID %u)", __PRETTY_FUNCTION__, note, unique); return 0; } switch (note) { case ZAPI_IPSET_FAIL_INSTALL: if (BGP_DEBUG(zebra, ZEBRA)) zlog_debug("%s: Received IPSET_FAIL_INSTALL", __PRETTY_FUNCTION__); bgp_pbim->installed = false; bgp_pbim->install_in_progress = false; break; case ZAPI_IPSET_INSTALLED: bgp_pbim->installed = true; bgp_pbim->install_in_progress = false; if (BGP_DEBUG(zebra, ZEBRA)) zlog_debug("%s: Received IPSET_INSTALLED", __PRETTY_FUNCTION__); break; case ZAPI_IPSET_FAIL_REMOVE: case ZAPI_IPSET_REMOVED: if (BGP_DEBUG(zebra, ZEBRA)) zlog_debug("%s: Received IPSET REMOVED", __PRETTY_FUNCTION__); break; } return 0; } static int ipset_entry_notify_owner(ZAPI_CALLBACK_ARGS) { uint32_t unique; char ipset_name[ZEBRA_IPSET_NAME_SIZE]; enum zapi_ipset_entry_notify_owner note; struct bgp_pbr_match_entry *bgp_pbime; if (!zapi_ipset_entry_notify_decode( zclient->ibuf, &unique, ipset_name, ¬e)) return -1; bgp_pbime = bgp_pbr_match_ipset_entry_lookup(vrf_id, ipset_name, unique); if (!bgp_pbime) { if (BGP_DEBUG(zebra, ZEBRA)) zlog_debug("%s: Fail to look BGP match entry (%u, ID %u)", __PRETTY_FUNCTION__, note, unique); return 0; } switch (note) { case ZAPI_IPSET_ENTRY_FAIL_INSTALL: if (BGP_DEBUG(zebra, ZEBRA)) zlog_debug("%s: Received IPSET_ENTRY_FAIL_INSTALL", __PRETTY_FUNCTION__); bgp_pbime->installed = false; bgp_pbime->install_in_progress = false; break; case ZAPI_IPSET_ENTRY_INSTALLED: { struct bgp_path_info *path; struct bgp_path_info_extra *extra; bgp_pbime->installed = true; bgp_pbime->install_in_progress = false; if (BGP_DEBUG(zebra, ZEBRA)) zlog_debug("%s: Received IPSET_ENTRY_INSTALLED", __PRETTY_FUNCTION__); /* link bgp_path_info to bpme */ path = (struct bgp_path_info *)bgp_pbime->path; extra = bgp_path_info_extra_get(path); listnode_add_force(&extra->bgp_fs_pbr, bgp_pbime); } break; case ZAPI_IPSET_ENTRY_FAIL_REMOVE: case ZAPI_IPSET_ENTRY_REMOVED: if (BGP_DEBUG(zebra, ZEBRA)) zlog_debug("%s: Received IPSET_ENTRY_REMOVED", __PRETTY_FUNCTION__); break; } return 0; } static int iptable_notify_owner(ZAPI_CALLBACK_ARGS) { uint32_t unique; enum zapi_iptable_notify_owner note; struct bgp_pbr_match *bgpm; if (!zapi_iptable_notify_decode( zclient->ibuf, &unique, ¬e)) return -1; bgpm = bgp_pbr_match_iptable_lookup(vrf_id, unique); if (!bgpm) { if (BGP_DEBUG(zebra, ZEBRA)) zlog_debug("%s: Fail to look BGP iptable (%u %u)", __PRETTY_FUNCTION__, note, unique); return 0; } switch (note) { case ZAPI_IPTABLE_FAIL_INSTALL: if (BGP_DEBUG(zebra, ZEBRA)) zlog_debug("%s: Received IPTABLE_FAIL_INSTALL", __PRETTY_FUNCTION__); bgpm->installed_in_iptable = false; bgpm->install_iptable_in_progress = false; break; case ZAPI_IPTABLE_INSTALLED: bgpm->installed_in_iptable = true; bgpm->install_iptable_in_progress = false; if (BGP_DEBUG(zebra, ZEBRA)) zlog_debug("%s: Received IPTABLE_INSTALLED", __PRETTY_FUNCTION__); bgpm->action->refcnt++; break; case ZAPI_IPTABLE_FAIL_REMOVE: case ZAPI_IPTABLE_REMOVED: if (BGP_DEBUG(zebra, ZEBRA)) zlog_debug("%s: Received IPTABLE REMOVED", __PRETTY_FUNCTION__); break; } return 0; } /* this function is used to forge ip rule, * - either for iptable/ipset using fwmark id * - or for sample ip rule cmd */ static void bgp_encode_pbr_rule_action(struct stream *s, struct bgp_pbr_action *pbra, struct bgp_pbr_rule *pbr) { struct prefix pfx; stream_putl(s, 0); /* seqno unused */ if (pbr) stream_putl(s, pbr->priority); else stream_putl(s, 0); /* ruleno unused - priority change * ruleno permits distinguishing various FS PBR entries * - FS PBR entries based on ipset/iptables * - FS PBR entries based on iprule * the latter may contain default routing information injected by FS */ if (pbr) stream_putl(s, pbr->unique); else stream_putl(s, pbra->unique); if (pbr && pbr->flags & MATCH_IP_SRC_SET) memcpy(&pfx, &(pbr->src), sizeof(struct prefix)); else { memset(&pfx, 0, sizeof(pfx)); pfx.family = AF_INET; } stream_putc(s, pfx.family); stream_putc(s, pfx.prefixlen); stream_put(s, &pfx.u.prefix, prefix_blen(&pfx)); stream_putw(s, 0); /* src port */ if (pbr && pbr->flags & MATCH_IP_DST_SET) memcpy(&pfx, &(pbr->dst), sizeof(struct prefix)); else { memset(&pfx, 0, sizeof(pfx)); pfx.family = AF_INET; } stream_putc(s, pfx.family); stream_putc(s, pfx.prefixlen); stream_put(s, &pfx.u.prefix, prefix_blen(&pfx)); stream_putw(s, 0); /* dst port */ /* if pbr present, fwmark is not used */ if (pbr) stream_putl(s, 0); else stream_putl(s, pbra->fwmark); /* fwmark */ stream_putl(s, pbra->table_id); stream_putl(s, 0); /* ifindex unused */ } static void bgp_encode_pbr_ipset_match(struct stream *s, struct bgp_pbr_match *pbim) { stream_putl(s, pbim->unique); stream_putl(s, pbim->type); stream_put(s, pbim->ipset_name, ZEBRA_IPSET_NAME_SIZE); } static void bgp_encode_pbr_ipset_entry_match(struct stream *s, struct bgp_pbr_match_entry *pbime) { stream_putl(s, pbime->unique); /* check that back pointer is not null */ stream_put(s, pbime->backpointer->ipset_name, ZEBRA_IPSET_NAME_SIZE); stream_putc(s, pbime->src.family); stream_putc(s, pbime->src.prefixlen); stream_put(s, &pbime->src.u.prefix, prefix_blen(&pbime->src)); stream_putc(s, pbime->dst.family); stream_putc(s, pbime->dst.prefixlen); stream_put(s, &pbime->dst.u.prefix, prefix_blen(&pbime->dst)); stream_putw(s, pbime->src_port_min); stream_putw(s, pbime->src_port_max); stream_putw(s, pbime->dst_port_min); stream_putw(s, pbime->dst_port_max); stream_putc(s, pbime->proto); } static void bgp_encode_pbr_iptable_match(struct stream *s, struct bgp_pbr_action *bpa, struct bgp_pbr_match *pbm) { stream_putl(s, pbm->unique2); stream_putl(s, pbm->type); stream_putl(s, pbm->flags); /* TODO: correlate with what is contained * into bgp_pbr_action. * currently only forward supported */ if (bpa->nh.type == NEXTHOP_TYPE_BLACKHOLE) stream_putl(s, ZEBRA_IPTABLES_DROP); else stream_putl(s, ZEBRA_IPTABLES_FORWARD); stream_putl(s, bpa->fwmark); stream_put(s, pbm->ipset_name, ZEBRA_IPSET_NAME_SIZE); stream_putw(s, pbm->pkt_len_min); stream_putw(s, pbm->pkt_len_max); stream_putw(s, pbm->tcp_flags); stream_putw(s, pbm->tcp_mask_flags); stream_putc(s, pbm->dscp_value); stream_putc(s, pbm->fragment); stream_putc(s, pbm->protocol); } /* BGP has established connection with Zebra. */ static void bgp_zebra_connected(struct zclient *zclient) { struct bgp *bgp; zclient_num_connects++; /* increment even if not responding */ /* At this point, we may or may not have BGP instances configured, but * we're only interested in the default VRF (others wouldn't have learnt * the VRF from Zebra yet.) */ bgp = bgp_get_default(); if (!bgp) return; bgp_zebra_instance_register(bgp); /* Send the client registration */ bfd_client_sendmsg(zclient, ZEBRA_BFD_CLIENT_REGISTER, bgp->vrf_id); /* tell label pool that zebra is connected */ bgp_lp_event_zebra_up(); /* TODO - What if we have peers and networks configured, do we have to * kick-start them? */ } static int bgp_zebra_process_local_es(ZAPI_CALLBACK_ARGS) { esi_t esi; struct bgp *bgp = NULL; struct stream *s = NULL; char buf[ESI_STR_LEN]; char buf1[INET6_ADDRSTRLEN]; struct ipaddr originator_ip; memset(&esi, 0, sizeof(esi_t)); memset(&originator_ip, 0, sizeof(struct ipaddr)); bgp = bgp_lookup_by_vrf_id(vrf_id); if (!bgp) return 0; s = zclient->ibuf; stream_get(&esi, s, sizeof(esi_t)); stream_get(&originator_ip, s, sizeof(struct ipaddr)); if (BGP_DEBUG(zebra, ZEBRA)) zlog_debug("Rx %s ESI %s originator-ip %s", (cmd == ZEBRA_LOCAL_ES_ADD) ? "add" : "del", esi_to_str(&esi, buf, sizeof(buf)), ipaddr2str(&originator_ip, buf1, sizeof(buf1))); if (cmd == ZEBRA_LOCAL_ES_ADD) bgp_evpn_local_es_add(bgp, &esi, &originator_ip); else bgp_evpn_local_es_del(bgp, &esi, &originator_ip); return 0; } static int bgp_zebra_process_local_l3vni(ZAPI_CALLBACK_ARGS) { int filter = 0; char buf[ETHER_ADDR_STRLEN]; vni_t l3vni = 0; struct ethaddr rmac; struct in_addr originator_ip; struct stream *s; ifindex_t svi_ifindex; memset(&rmac, 0, sizeof(struct ethaddr)); memset(&originator_ip, 0, sizeof(struct in_addr)); s = zclient->ibuf; l3vni = stream_getl(s); if (cmd == ZEBRA_L3VNI_ADD) { stream_get(&rmac, s, sizeof(struct ethaddr)); originator_ip.s_addr = stream_get_ipv4(s); stream_get(&filter, s, sizeof(int)); svi_ifindex = stream_getl(s); if (BGP_DEBUG(zebra, ZEBRA)) zlog_debug("Rx L3-VNI ADD VRF %s VNI %u RMAC %s filter %s svi-if %u", vrf_id_to_name(vrf_id), l3vni, prefix_mac2str(&rmac, buf, sizeof(buf)), filter ? "prefix-routes-only" : "none", svi_ifindex); bgp_evpn_local_l3vni_add(l3vni, vrf_id, &rmac, originator_ip, filter, svi_ifindex); } else { if (BGP_DEBUG(zebra, ZEBRA)) zlog_debug("Rx L3-VNI DEL VRF %s VNI %u", vrf_id_to_name(vrf_id), l3vni); bgp_evpn_local_l3vni_del(l3vni, vrf_id); } return 0; } static int bgp_zebra_process_local_vni(ZAPI_CALLBACK_ARGS) { struct stream *s; vni_t vni; struct bgp *bgp; struct in_addr vtep_ip = {INADDR_ANY}; vrf_id_t tenant_vrf_id = VRF_DEFAULT; struct in_addr mcast_grp = {INADDR_ANY}; s = zclient->ibuf; vni = stream_getl(s); if (cmd == ZEBRA_VNI_ADD) { vtep_ip.s_addr = stream_get_ipv4(s); stream_get(&tenant_vrf_id, s, sizeof(vrf_id_t)); mcast_grp.s_addr = stream_get_ipv4(s); } bgp = bgp_lookup_by_vrf_id(vrf_id); if (!bgp) return 0; if (BGP_DEBUG(zebra, ZEBRA)) zlog_debug("Rx VNI %s VRF %s VNI %u tenant-vrf %s", (cmd == ZEBRA_VNI_ADD) ? "add" : "del", vrf_id_to_name(vrf_id), vni, vrf_id_to_name(tenant_vrf_id)); if (cmd == ZEBRA_VNI_ADD) return bgp_evpn_local_vni_add( bgp, vni, vtep_ip.s_addr ? vtep_ip : bgp->router_id, tenant_vrf_id, mcast_grp); else return bgp_evpn_local_vni_del(bgp, vni); } static int bgp_zebra_process_local_macip(ZAPI_CALLBACK_ARGS) { struct stream *s; vni_t vni; struct bgp *bgp; struct ethaddr mac; struct ipaddr ip; int ipa_len; char buf[ETHER_ADDR_STRLEN]; char buf1[INET6_ADDRSTRLEN]; uint8_t flags = 0; uint32_t seqnum = 0; int state = 0; memset(&ip, 0, sizeof(ip)); s = zclient->ibuf; vni = stream_getl(s); stream_get(&mac.octet, s, ETH_ALEN); ipa_len = stream_getl(s); if (ipa_len != 0 && ipa_len != IPV4_MAX_BYTELEN && ipa_len != IPV6_MAX_BYTELEN) { flog_err(EC_BGP_MACIP_LEN, "%u:Recv MACIP %s with invalid IP addr length %d", vrf_id, (cmd == ZEBRA_MACIP_ADD) ? "Add" : "Del", ipa_len); return -1; } if (ipa_len) { ip.ipa_type = (ipa_len == IPV4_MAX_BYTELEN) ? IPADDR_V4 : IPADDR_V6; stream_get(&ip.ip.addr, s, ipa_len); } if (cmd == ZEBRA_MACIP_ADD) { flags = stream_getc(s); seqnum = stream_getl(s); } else { state = stream_getl(s); } bgp = bgp_lookup_by_vrf_id(vrf_id); if (!bgp) return 0; if (BGP_DEBUG(zebra, ZEBRA)) zlog_debug("%u:Recv MACIP %s flags 0x%x MAC %s IP %s VNI %u seq %u state %d", vrf_id, (cmd == ZEBRA_MACIP_ADD) ? "Add" : "Del", flags, prefix_mac2str(&mac, buf, sizeof(buf)), ipaddr2str(&ip, buf1, sizeof(buf1)), vni, seqnum, state); if (cmd == ZEBRA_MACIP_ADD) return bgp_evpn_local_macip_add(bgp, vni, &mac, &ip, flags, seqnum); else return bgp_evpn_local_macip_del(bgp, vni, &mac, &ip, state); } static void bgp_zebra_process_local_ip_prefix(ZAPI_CALLBACK_ARGS) { struct stream *s = NULL; struct bgp *bgp_vrf = NULL; struct prefix p; char buf[PREFIX_STRLEN]; memset(&p, 0, sizeof(struct prefix)); s = zclient->ibuf; stream_get(&p, s, sizeof(struct prefix)); bgp_vrf = bgp_lookup_by_vrf_id(vrf_id); if (!bgp_vrf) return; if (BGP_DEBUG(zebra, ZEBRA)) zlog_debug("Recv prefix %s %s on vrf %s", prefix2str(&p, buf, sizeof(buf)), (cmd == ZEBRA_IP_PREFIX_ROUTE_ADD) ? "ADD" : "DEL", vrf_id_to_name(vrf_id)); if (cmd == ZEBRA_IP_PREFIX_ROUTE_ADD) { if (p.family == AF_INET) bgp_evpn_advertise_type5_route(bgp_vrf, &p, NULL, AFI_IP, SAFI_UNICAST); else bgp_evpn_advertise_type5_route(bgp_vrf, &p, NULL, AFI_IP6, SAFI_UNICAST); } else { if (p.family == AF_INET) bgp_evpn_withdraw_type5_route(bgp_vrf, &p, AFI_IP, SAFI_UNICAST); else bgp_evpn_withdraw_type5_route(bgp_vrf, &p, AFI_IP6, SAFI_UNICAST); } } static void bgp_zebra_process_label_chunk(ZAPI_CALLBACK_ARGS) { struct stream *s = NULL; uint8_t response_keep; uint32_t first; uint32_t last; uint8_t proto; unsigned short instance; s = zclient->ibuf; STREAM_GETC(s, proto); STREAM_GETW(s, instance); STREAM_GETC(s, response_keep); STREAM_GETL(s, first); STREAM_GETL(s, last); if (zclient->redist_default != proto) { flog_err(EC_BGP_LM_ERROR, "Got LM msg with wrong proto %u", proto); return; } if (zclient->instance != instance) { flog_err(EC_BGP_LM_ERROR, "Got LM msg with wrong instance %u", proto); return; } if (first > last || first < MPLS_LABEL_UNRESERVED_MIN || last > MPLS_LABEL_UNRESERVED_MAX) { flog_err(EC_BGP_LM_ERROR, "%s: Invalid Label chunk: %u - %u", __func__, first, last); return; } if (BGP_DEBUG(zebra, ZEBRA)) { zlog_debug("Label Chunk assign: %u - %u (%u) ", first, last, response_keep); } bgp_lp_event_chunk(response_keep, first, last); stream_failure: /* for STREAM_GETX */ return; } extern struct zebra_privs_t bgpd_privs; void bgp_zebra_init(struct thread_master *master, unsigned short instance) { zclient_num_connects = 0; /* Set default values. */ zclient = zclient_new(master, &zclient_options_default); zclient_init(zclient, ZEBRA_ROUTE_BGP, 0, &bgpd_privs); zclient->zebra_connected = bgp_zebra_connected; zclient->router_id_update = bgp_router_id_update; zclient->interface_add = bgp_interface_add; zclient->interface_delete = bgp_interface_delete; zclient->interface_address_add = bgp_interface_address_add; zclient->interface_address_delete = bgp_interface_address_delete; zclient->interface_nbr_address_add = bgp_interface_nbr_address_add; zclient->interface_nbr_address_delete = bgp_interface_nbr_address_delete; zclient->interface_vrf_update = bgp_interface_vrf_update; zclient->redistribute_route_add = zebra_read_route; zclient->redistribute_route_del = zebra_read_route; zclient->interface_up = bgp_interface_up; zclient->interface_down = bgp_interface_down; zclient->nexthop_update = bgp_read_nexthop_update; zclient->import_check_update = bgp_read_import_check_update; zclient->fec_update = bgp_read_fec_update; zclient->local_es_add = bgp_zebra_process_local_es; zclient->local_es_del = bgp_zebra_process_local_es; zclient->local_vni_add = bgp_zebra_process_local_vni; zclient->local_vni_del = bgp_zebra_process_local_vni; zclient->local_macip_add = bgp_zebra_process_local_macip; zclient->local_macip_del = bgp_zebra_process_local_macip; zclient->local_l3vni_add = bgp_zebra_process_local_l3vni; zclient->local_l3vni_del = bgp_zebra_process_local_l3vni; zclient->local_ip_prefix_add = bgp_zebra_process_local_ip_prefix; zclient->local_ip_prefix_del = bgp_zebra_process_local_ip_prefix; zclient->label_chunk = bgp_zebra_process_label_chunk; zclient->rule_notify_owner = rule_notify_owner; zclient->ipset_notify_owner = ipset_notify_owner; zclient->ipset_entry_notify_owner = ipset_entry_notify_owner; zclient->iptable_notify_owner = iptable_notify_owner; zclient->instance = instance; } void bgp_zebra_destroy(void) { if (zclient == NULL) return; zclient_stop(zclient); zclient_free(zclient); zclient = NULL; } int bgp_zebra_num_connects(void) { return zclient_num_connects; } void bgp_send_pbr_rule_action(struct bgp_pbr_action *pbra, struct bgp_pbr_rule *pbr, bool install) { struct stream *s; if (pbra->install_in_progress && !pbr) return; if (pbr && pbr->install_in_progress) return; if (BGP_DEBUG(zebra, ZEBRA)) { if (pbr) zlog_debug("%s: table %d (ip rule) %d", __PRETTY_FUNCTION__, pbra->table_id, install); else zlog_debug("%s: table %d fwmark %d %d", __PRETTY_FUNCTION__, pbra->table_id, pbra->fwmark, install); } s = zclient->obuf; stream_reset(s); zclient_create_header(s, install ? ZEBRA_RULE_ADD : ZEBRA_RULE_DELETE, VRF_DEFAULT); stream_putl(s, 1); /* send one pbr action */ bgp_encode_pbr_rule_action(s, pbra, pbr); stream_putw_at(s, 0, stream_get_endp(s)); if (!zclient_send_message(zclient) && install) { if (!pbr) pbra->install_in_progress = true; else pbr->install_in_progress = true; } } void bgp_send_pbr_ipset_match(struct bgp_pbr_match *pbrim, bool install) { struct stream *s; if (pbrim->install_in_progress) return; if (BGP_DEBUG(zebra, ZEBRA)) zlog_debug("%s: name %s type %d %d, ID %u", __PRETTY_FUNCTION__, pbrim->ipset_name, pbrim->type, install, pbrim->unique); s = zclient->obuf; stream_reset(s); zclient_create_header(s, install ? ZEBRA_IPSET_CREATE : ZEBRA_IPSET_DESTROY, VRF_DEFAULT); stream_putl(s, 1); /* send one pbr action */ bgp_encode_pbr_ipset_match(s, pbrim); stream_putw_at(s, 0, stream_get_endp(s)); if (!zclient_send_message(zclient) && install) pbrim->install_in_progress = true; } void bgp_send_pbr_ipset_entry_match(struct bgp_pbr_match_entry *pbrime, bool install) { struct stream *s; if (pbrime->install_in_progress) return; if (BGP_DEBUG(zebra, ZEBRA)) zlog_debug("%s: name %s %d %d, ID %u", __PRETTY_FUNCTION__, pbrime->backpointer->ipset_name, pbrime->unique, install, pbrime->unique); s = zclient->obuf; stream_reset(s); zclient_create_header(s, install ? ZEBRA_IPSET_ENTRY_ADD : ZEBRA_IPSET_ENTRY_DELETE, VRF_DEFAULT); stream_putl(s, 1); /* send one pbr action */ bgp_encode_pbr_ipset_entry_match(s, pbrime); stream_putw_at(s, 0, stream_get_endp(s)); if (!zclient_send_message(zclient) && install) pbrime->install_in_progress = true; } static void bgp_encode_pbr_interface_list(struct bgp *bgp, struct stream *s) { struct bgp_pbr_config *bgp_pbr_cfg = bgp->bgp_pbr_cfg; struct bgp_pbr_interface_head *head; struct bgp_pbr_interface *pbr_if; struct interface *ifp; if (!bgp_pbr_cfg) return; head = &(bgp_pbr_cfg->ifaces_by_name_ipv4); RB_FOREACH (pbr_if, bgp_pbr_interface_head, head) { ifp = if_lookup_by_name(pbr_if->name, bgp->vrf_id); if (ifp) stream_putl(s, ifp->ifindex); } } static int bgp_pbr_get_ifnumber(struct bgp *bgp) { struct bgp_pbr_config *bgp_pbr_cfg = bgp->bgp_pbr_cfg; struct bgp_pbr_interface_head *head; struct bgp_pbr_interface *pbr_if; int cnt = 0; if (!bgp_pbr_cfg) return 0; head = &(bgp_pbr_cfg->ifaces_by_name_ipv4); RB_FOREACH (pbr_if, bgp_pbr_interface_head, head) { if (if_lookup_by_name(pbr_if->name, bgp->vrf_id)) cnt++; } return cnt; } void bgp_send_pbr_iptable(struct bgp_pbr_action *pba, struct bgp_pbr_match *pbm, bool install) { struct stream *s; int ret = 0; int nb_interface; if (pbm->install_iptable_in_progress) return; if (BGP_DEBUG(zebra, ZEBRA)) zlog_debug("%s: name %s type %d mark %d %d, ID %u", __PRETTY_FUNCTION__, pbm->ipset_name, pbm->type, pba->fwmark, install, pbm->unique2); s = zclient->obuf; stream_reset(s); zclient_create_header(s, install ? ZEBRA_IPTABLE_ADD : ZEBRA_IPTABLE_DELETE, VRF_DEFAULT); bgp_encode_pbr_iptable_match(s, pba, pbm); nb_interface = bgp_pbr_get_ifnumber(pba->bgp); stream_putl(s, nb_interface); if (nb_interface) bgp_encode_pbr_interface_list(pba->bgp, s); stream_putw_at(s, 0, stream_get_endp(s)); ret = zclient_send_message(zclient); if (install) { if (ret) pba->refcnt++; else pbm->install_iptable_in_progress = true; } } /* inject in table a default route to: * - if nexthop IP is present : to this nexthop * - if vrf is different from local : to the matching VRF */ void bgp_zebra_announce_default(struct bgp *bgp, struct nexthop *nh, afi_t afi, uint32_t table_id, bool announce) { struct zapi_nexthop *api_nh; struct zapi_route api; struct prefix p; if (!nh || nh->type != NEXTHOP_TYPE_IPV4 || nh->vrf_id == VRF_UNKNOWN) return; memset(&p, 0, sizeof(struct prefix)); /* default route */ if (afi != AFI_IP) return; p.family = AF_INET; memset(&api, 0, sizeof(api)); api.vrf_id = bgp->vrf_id; api.type = ZEBRA_ROUTE_BGP; api.safi = SAFI_UNICAST; api.prefix = p; api.tableid = table_id; api.nexthop_num = 1; SET_FLAG(api.message, ZAPI_MESSAGE_TABLEID); SET_FLAG(api.message, ZAPI_MESSAGE_NEXTHOP); api_nh = &api.nexthops[0]; api.distance = ZEBRA_EBGP_DISTANCE_DEFAULT; SET_FLAG(api.message, ZAPI_MESSAGE_DISTANCE); /* redirect IP */ if (nh->gate.ipv4.s_addr) { char buff[PREFIX_STRLEN]; api_nh->vrf_id = nh->vrf_id; api_nh->gate.ipv4 = nh->gate.ipv4; api_nh->type = NEXTHOP_TYPE_IPV4; inet_ntop(AF_INET, &(nh->gate.ipv4), buff, INET_ADDRSTRLEN); if (BGP_DEBUG(zebra, ZEBRA)) zlog_info("BGP: %s default route to %s table %d (redirect IP)", announce ? "adding" : "withdrawing", buff, table_id); zclient_route_send(announce ? ZEBRA_ROUTE_ADD : ZEBRA_ROUTE_DELETE, zclient, &api); } else if (nh->vrf_id != bgp->vrf_id) { struct vrf *vrf; struct interface *ifp; vrf = vrf_lookup_by_id(nh->vrf_id); if (!vrf) return; /* create default route with interface * with nexthop-vrf */ ifp = if_lookup_by_name_all_vrf(vrf->name); if (!ifp) return; api_nh->vrf_id = nh->vrf_id; api_nh->type = NEXTHOP_TYPE_IFINDEX; api_nh->ifindex = ifp->ifindex; if (BGP_DEBUG(zebra, ZEBRA)) zlog_info("BGP: %s default route to %s table %d (redirect VRF)", announce ? "adding" : "withdrawing", vrf->name, table_id); zclient_route_send(announce ? ZEBRA_ROUTE_ADD : ZEBRA_ROUTE_DELETE, zclient, &api); return; } } frr-7.2.1/bgpd/bgp_zebra.h0000644000000000000000000001073713610377563012244 00000000000000/* zebra connection and redistribute fucntions. * Copyright (C) 1999 Kunihiro Ishiguro * * This file is part of GNU Zebra. * * GNU Zebra is free software; you can redistribute it and/or modify it * under the terms of the GNU General Public License as published by the * Free Software Foundation; either version 2, or (at your option) any * later version. * * GNU Zebra 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; see the file COPYING; if not, write to the Free Software * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA */ #ifndef _QUAGGA_BGP_ZEBRA_H #define _QUAGGA_BGP_ZEBRA_H #include "vxlan.h" extern void bgp_zebra_init(struct thread_master *master, unsigned short instance); extern void bgp_zebra_init_tm_connect(struct bgp *bgp); extern uint32_t bgp_zebra_tm_get_id(void); extern bool bgp_zebra_tm_chunk_obtained(void); extern void bgp_zebra_destroy(void); extern int bgp_zebra_get_table_range(uint32_t chunk_size, uint32_t *start, uint32_t *end); extern int bgp_if_update_all(void); extern void bgp_config_write_maxpaths(struct vty *, struct bgp *, afi_t, safi_t); extern void bgp_config_write_redistribute(struct vty *, struct bgp *, afi_t, safi_t); extern void bgp_zebra_announce(struct bgp_node *rn, struct prefix *p, struct bgp_path_info *path, struct bgp *bgp, afi_t afi, safi_t safi); extern void bgp_zebra_announce_table(struct bgp *, afi_t, safi_t); extern void bgp_zebra_withdraw(struct prefix *p, struct bgp_path_info *path, struct bgp *bgp, safi_t safi); extern void bgp_zebra_initiate_radv(struct bgp *bgp, struct peer *peer); extern void bgp_zebra_terminate_radv(struct bgp *bgp, struct peer *peer); extern void bgp_zebra_instance_register(struct bgp *); extern void bgp_zebra_instance_deregister(struct bgp *); extern void bgp_redistribute_redo(struct bgp *bgp); extern struct bgp_redist *bgp_redist_lookup(struct bgp *, afi_t, uint8_t, unsigned short); extern struct bgp_redist *bgp_redist_add(struct bgp *, afi_t, uint8_t, unsigned short); extern int bgp_redistribute_set(struct bgp *, afi_t, int, unsigned short, bool changed); extern int bgp_redistribute_resend(struct bgp *, afi_t, int, unsigned short); extern int bgp_redistribute_rmap_set(struct bgp_redist *red, const char *name, struct route_map *route_map); extern int bgp_redistribute_metric_set(struct bgp *, struct bgp_redist *, afi_t, int, uint32_t); extern int bgp_redistribute_unset(struct bgp *, afi_t, int, unsigned short); extern int bgp_redistribute_unreg(struct bgp *, afi_t, int, unsigned short); extern struct interface *if_lookup_by_ipv4(struct in_addr *, vrf_id_t); extern struct interface *if_lookup_by_ipv4_exact(struct in_addr *, vrf_id_t); extern struct interface *if_lookup_by_ipv6(struct in6_addr *, ifindex_t, vrf_id_t); extern struct interface *if_lookup_by_ipv6_exact(struct in6_addr *, ifindex_t, vrf_id_t); extern int bgp_zebra_advertise_subnet(struct bgp *bgp, int advertise, vni_t vni); extern int bgp_zebra_advertise_gw_macip(struct bgp *, int, vni_t); extern int bgp_zebra_advertise_svi_macip(struct bgp *bgp, int advertise, vni_t vni); extern int bgp_zebra_advertise_all_vni(struct bgp *, int); extern int bgp_zebra_dup_addr_detection(struct bgp *bgp); extern int bgp_zebra_vxlan_flood_control(struct bgp *bgp, enum vxlan_flood_control flood_ctrl); extern int bgp_zebra_num_connects(void); extern bool bgp_zebra_nexthop_set(union sockunion *, union sockunion *, struct bgp_nexthop *, struct peer *); struct bgp_pbr_action; struct bgp_pbr_match; struct bgp_pbr_rule; struct bgp_pbr_match_entry; extern void bgp_send_pbr_rule_action(struct bgp_pbr_action *pbra, struct bgp_pbr_rule *pbr, bool install); extern void bgp_send_pbr_ipset_match(struct bgp_pbr_match *pbrim, bool install); extern void bgp_send_pbr_ipset_entry_match(struct bgp_pbr_match_entry *pbrime, bool install); extern void bgp_send_pbr_iptable(struct bgp_pbr_action *pba, struct bgp_pbr_match *pbm, bool install); extern void bgp_zebra_announce_default(struct bgp *bgp, struct nexthop *nh, afi_t afi, uint32_t table_id, bool announce); #endif /* _QUAGGA_BGP_ZEBRA_H */ frr-7.2.1/bgpd/bgpd.c0000644000000000000000000067131013610377563011220 00000000000000/* BGP-4, BGP-4+ daemon program * Copyright (C) 1996, 97, 98, 99, 2000 Kunihiro Ishiguro * * This file is part of GNU Zebra. * * GNU Zebra is free software; you can redistribute it and/or modify it * under the terms of the GNU General Public License as published by the * Free Software Foundation; either version 2, or (at your option) any * later version. * * GNU Zebra 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; see the file COPYING; if not, write to the Free Software * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA */ #include #include "prefix.h" #include "thread.h" #include "buffer.h" #include "stream.h" #include "ringbuf.h" #include "command.h" #include "sockunion.h" #include "sockopt.h" #include "network.h" #include "memory.h" #include "filter.h" #include "routemap.h" #include "log.h" #include "plist.h" #include "linklist.h" #include "workqueue.h" #include "queue.h" #include "zclient.h" #include "bfd.h" #include "hash.h" #include "jhash.h" #include "table.h" #include "lib/json.h" #include "frr_pthread.h" #include "bitfield.h" #include "bgpd/bgpd.h" #include "bgpd/bgp_table.h" #include "bgpd/bgp_aspath.h" #include "bgpd/bgp_route.h" #include "bgpd/bgp_dump.h" #include "bgpd/bgp_debug.h" #include "bgpd/bgp_errors.h" #include "bgpd/bgp_community.h" #include "bgpd/bgp_attr.h" #include "bgpd/bgp_regex.h" #include "bgpd/bgp_clist.h" #include "bgpd/bgp_fsm.h" #include "bgpd/bgp_packet.h" #include "bgpd/bgp_zebra.h" #include "bgpd/bgp_open.h" #include "bgpd/bgp_filter.h" #include "bgpd/bgp_nexthop.h" #include "bgpd/bgp_damp.h" #include "bgpd/bgp_mplsvpn.h" #if ENABLE_BGP_VNC #include "bgpd/rfapi/bgp_rfapi_cfg.h" #include "bgpd/rfapi/rfapi_backend.h" #endif #include "bgpd/bgp_evpn.h" #include "bgpd/bgp_advertise.h" #include "bgpd/bgp_network.h" #include "bgpd/bgp_vty.h" #include "bgpd/bgp_mpath.h" #include "bgpd/bgp_nht.h" #include "bgpd/bgp_updgrp.h" #include "bgpd/bgp_bfd.h" #include "bgpd/bgp_memory.h" #include "bgpd/bgp_evpn_vty.h" #include "bgpd/bgp_keepalives.h" #include "bgpd/bgp_io.h" #include "bgpd/bgp_ecommunity.h" #include "bgpd/bgp_flowspec.h" #include "bgpd/bgp_labelpool.h" #include "bgpd/bgp_pbr.h" #include "bgpd/bgp_addpath.h" #include "bgpd/bgp_evpn_private.h" #include "bgpd/bgp_mac.h" DEFINE_MTYPE_STATIC(BGPD, PEER_TX_SHUTDOWN_MSG, "Peer shutdown message (TX)"); DEFINE_MTYPE_STATIC(BGPD, BGP_EVPN_INFO, "BGP EVPN instance information"); DEFINE_QOBJ_TYPE(bgp_master) DEFINE_QOBJ_TYPE(bgp) DEFINE_QOBJ_TYPE(peer) DEFINE_HOOK(bgp_inst_delete, (struct bgp *bgp), (bgp)) DEFINE_HOOK(bgp_inst_config_write, (struct bgp *bgp, struct vty *vty), (bgp, vty)) /* BGP process wide configuration. */ static struct bgp_master bgp_master; /* BGP process wide configuration pointer to export. */ struct bgp_master *bm; /* BGP community-list. */ struct community_list_handler *bgp_clist; unsigned int multipath_num = MULTIPATH_NUM; static void bgp_if_finish(struct bgp *bgp); static void peer_drop_dynamic_neighbor(struct peer *peer); extern struct zclient *zclient; /* handle main socket creation or deletion */ static int bgp_check_main_socket(bool create, struct bgp *bgp) { static int bgp_server_main_created; if (create) { if (bgp_server_main_created) return 0; if (bgp_socket(bgp, bm->port, bm->address) < 0) return BGP_ERR_INVALID_VALUE; bgp_server_main_created = 1; return 0; } if (!bgp_server_main_created) return 0; bgp_close(); bgp_server_main_created = 0; return 0; } void bgp_session_reset(struct peer *peer) { if (peer->doppelganger && (peer->doppelganger->status != Deleted) && !(CHECK_FLAG(peer->doppelganger->flags, PEER_FLAG_CONFIG_NODE))) peer_delete(peer->doppelganger); BGP_EVENT_ADD(peer, BGP_Stop); } /* * During session reset, we may delete the doppelganger peer, which would * be the next node to the current node. If the session reset was invoked * during walk of peer list, we would end up accessing the freed next * node. This function moves the next node along. */ static void bgp_session_reset_safe(struct peer *peer, struct listnode **nnode) { struct listnode *n; struct peer *npeer; n = (nnode) ? *nnode : NULL; npeer = (n) ? listgetdata(n) : NULL; if (peer->doppelganger && (peer->doppelganger->status != Deleted) && !(CHECK_FLAG(peer->doppelganger->flags, PEER_FLAG_CONFIG_NODE))) { if (peer->doppelganger == npeer) /* nnode and *nnode are confirmed to be non-NULL here */ *nnode = (*nnode)->next; peer_delete(peer->doppelganger); } BGP_EVENT_ADD(peer, BGP_Stop); } /* BGP global flag manipulation. */ int bgp_option_set(int flag) { switch (flag) { case BGP_OPT_NO_FIB: case BGP_OPT_NO_LISTEN: case BGP_OPT_NO_ZEBRA: SET_FLAG(bm->options, flag); break; default: return BGP_ERR_INVALID_FLAG; } return 0; } int bgp_option_unset(int flag) { switch (flag) { /* Fall through. */ case BGP_OPT_NO_ZEBRA: case BGP_OPT_NO_FIB: UNSET_FLAG(bm->options, flag); break; default: return BGP_ERR_INVALID_FLAG; } return 0; } int bgp_option_check(int flag) { return CHECK_FLAG(bm->options, flag); } /* BGP flag manipulation. */ int bgp_flag_set(struct bgp *bgp, int flag) { SET_FLAG(bgp->flags, flag); return 0; } int bgp_flag_unset(struct bgp *bgp, int flag) { UNSET_FLAG(bgp->flags, flag); return 0; } int bgp_flag_check(struct bgp *bgp, int flag) { return CHECK_FLAG(bgp->flags, flag); } /* Internal function to set BGP structure configureation flag. */ static void bgp_config_set(struct bgp *bgp, int config) { SET_FLAG(bgp->config, config); } static void bgp_config_unset(struct bgp *bgp, int config) { UNSET_FLAG(bgp->config, config); } static int bgp_config_check(struct bgp *bgp, int config) { return CHECK_FLAG(bgp->config, config); } /* Set BGP router identifier; distinguish between explicit config and other * cases. */ static int bgp_router_id_set(struct bgp *bgp, const struct in_addr *id, bool is_config) { struct peer *peer; struct listnode *node, *nnode; if (IPV4_ADDR_SAME(&bgp->router_id, id)) return 0; /* EVPN uses router id in RD, withdraw them */ if (is_evpn_enabled()) bgp_evpn_handle_router_id_update(bgp, true); vpn_handle_router_id_update(bgp, true, is_config); IPV4_ADDR_COPY(&bgp->router_id, id); /* Set all peer's local identifier with this value. */ for (ALL_LIST_ELEMENTS(bgp->peer, node, nnode, peer)) { IPV4_ADDR_COPY(&peer->local_id, id); if (BGP_IS_VALID_STATE_FOR_NOTIF(peer->status)) { peer->last_reset = PEER_DOWN_RID_CHANGE; bgp_notify_send(peer, BGP_NOTIFY_CEASE, BGP_NOTIFY_CEASE_CONFIG_CHANGE); } } /* EVPN uses router id in RD, update them */ if (is_evpn_enabled()) bgp_evpn_handle_router_id_update(bgp, false); vpn_handle_router_id_update(bgp, false, is_config); return 0; } void bgp_router_id_zebra_bump(vrf_id_t vrf_id, const struct prefix *router_id) { struct listnode *node, *nnode; struct bgp *bgp; struct in_addr *addr = NULL; if (router_id != NULL) addr = (struct in_addr *)&(router_id->u.prefix4); if (vrf_id == VRF_DEFAULT) { /* Router-id change for default VRF has to also update all * views. */ for (ALL_LIST_ELEMENTS(bm->bgp, node, nnode, bgp)) { if (bgp->inst_type == BGP_INSTANCE_TYPE_VRF) continue; if (addr) bgp->router_id_zebra = *addr; else addr = &bgp->router_id_zebra; if (!bgp->router_id_static.s_addr) { /* Router ID is updated if there are no active * peer sessions */ if (bgp->established_peers == 0) { if (BGP_DEBUG(zebra, ZEBRA)) zlog_debug("RID change : vrf %u, RTR ID %s", bgp->vrf_id, inet_ntoa(*addr)); bgp_router_id_set(bgp, addr, false); } } } } else { bgp = bgp_lookup_by_vrf_id(vrf_id); if (bgp) { if (addr) bgp->router_id_zebra = *addr; else addr = &bgp->router_id_zebra; if (!bgp->router_id_static.s_addr) { /* Router ID is updated if there are no active * peer sessions */ if (bgp->established_peers == 0) { if (BGP_DEBUG(zebra, ZEBRA)) zlog_debug("RID change : vrf %u, RTR ID %s", bgp->vrf_id, inet_ntoa(*addr)); bgp_router_id_set(bgp, addr, false); } } } } } int bgp_router_id_static_set(struct bgp *bgp, struct in_addr id) { bgp->router_id_static = id; bgp_router_id_set(bgp, id.s_addr ? &id : &bgp->router_id_zebra, true /* is config */); return 0; } /* BGP's cluster-id control. */ int bgp_cluster_id_set(struct bgp *bgp, struct in_addr *cluster_id) { struct peer *peer; struct listnode *node, *nnode; if (bgp_config_check(bgp, BGP_CONFIG_CLUSTER_ID) && IPV4_ADDR_SAME(&bgp->cluster_id, cluster_id)) return 0; IPV4_ADDR_COPY(&bgp->cluster_id, cluster_id); bgp_config_set(bgp, BGP_CONFIG_CLUSTER_ID); /* Clear all IBGP peer. */ for (ALL_LIST_ELEMENTS(bgp->peer, node, nnode, peer)) { if (peer->sort != BGP_PEER_IBGP) continue; if (BGP_IS_VALID_STATE_FOR_NOTIF(peer->status)) { peer->last_reset = PEER_DOWN_CLID_CHANGE; bgp_notify_send(peer, BGP_NOTIFY_CEASE, BGP_NOTIFY_CEASE_CONFIG_CHANGE); } } return 0; } int bgp_cluster_id_unset(struct bgp *bgp) { struct peer *peer; struct listnode *node, *nnode; if (!bgp_config_check(bgp, BGP_CONFIG_CLUSTER_ID)) return 0; bgp->cluster_id.s_addr = 0; bgp_config_unset(bgp, BGP_CONFIG_CLUSTER_ID); /* Clear all IBGP peer. */ for (ALL_LIST_ELEMENTS(bgp->peer, node, nnode, peer)) { if (peer->sort != BGP_PEER_IBGP) continue; if (BGP_IS_VALID_STATE_FOR_NOTIF(peer->status)) { peer->last_reset = PEER_DOWN_CLID_CHANGE; bgp_notify_send(peer, BGP_NOTIFY_CEASE, BGP_NOTIFY_CEASE_CONFIG_CHANGE); } } return 0; } /* time_t value that is monotonicly increasing * and uneffected by adjustments to system clock */ time_t bgp_clock(void) { struct timeval tv; monotime(&tv); return tv.tv_sec; } /* BGP timer configuration. */ int bgp_timers_set(struct bgp *bgp, uint32_t keepalive, uint32_t holdtime) { bgp->default_keepalive = (keepalive < holdtime / 3 ? keepalive : holdtime / 3); bgp->default_holdtime = holdtime; return 0; } int bgp_timers_unset(struct bgp *bgp) { bgp->default_keepalive = BGP_DEFAULT_KEEPALIVE; bgp->default_holdtime = BGP_DEFAULT_HOLDTIME; return 0; } /* BGP confederation configuration. */ int bgp_confederation_id_set(struct bgp *bgp, as_t as) { struct peer *peer; struct listnode *node, *nnode; int already_confed; if (as == 0) return BGP_ERR_INVALID_AS; /* Remember - were we doing confederation before? */ already_confed = bgp_config_check(bgp, BGP_CONFIG_CONFEDERATION); bgp->confed_id = as; bgp_config_set(bgp, BGP_CONFIG_CONFEDERATION); /* If we were doing confederation already, this is just an external AS change. Just Reset EBGP sessions, not CONFED sessions. If we were not doing confederation before, reset all EBGP sessions. */ for (ALL_LIST_ELEMENTS(bgp->peer, node, nnode, peer)) { /* We're looking for peers who's AS is not local or part of our confederation. */ if (already_confed) { if (peer_sort(peer) == BGP_PEER_EBGP) { peer->local_as = as; if (BGP_IS_VALID_STATE_FOR_NOTIF( peer->status)) { peer->last_reset = PEER_DOWN_CONFED_ID_CHANGE; bgp_notify_send( peer, BGP_NOTIFY_CEASE, BGP_NOTIFY_CEASE_CONFIG_CHANGE); } else bgp_session_reset_safe(peer, &nnode); } } else { /* Not doign confederation before, so reset every non-local session */ if (peer_sort(peer) != BGP_PEER_IBGP) { /* Reset the local_as to be our EBGP one */ if (peer_sort(peer) == BGP_PEER_EBGP) peer->local_as = as; if (BGP_IS_VALID_STATE_FOR_NOTIF( peer->status)) { peer->last_reset = PEER_DOWN_CONFED_ID_CHANGE; bgp_notify_send( peer, BGP_NOTIFY_CEASE, BGP_NOTIFY_CEASE_CONFIG_CHANGE); } else bgp_session_reset_safe(peer, &nnode); } } } return 0; } int bgp_confederation_id_unset(struct bgp *bgp) { struct peer *peer; struct listnode *node, *nnode; bgp->confed_id = 0; bgp_config_unset(bgp, BGP_CONFIG_CONFEDERATION); for (ALL_LIST_ELEMENTS(bgp->peer, node, nnode, peer)) { /* We're looking for peers who's AS is not local */ if (peer_sort(peer) != BGP_PEER_IBGP) { peer->local_as = bgp->as; if (BGP_IS_VALID_STATE_FOR_NOTIF(peer->status)) { peer->last_reset = PEER_DOWN_CONFED_ID_CHANGE; bgp_notify_send(peer, BGP_NOTIFY_CEASE, BGP_NOTIFY_CEASE_CONFIG_CHANGE); } else bgp_session_reset_safe(peer, &nnode); } } return 0; } /* Is an AS part of the confed or not? */ int bgp_confederation_peers_check(struct bgp *bgp, as_t as) { int i; if (!bgp) return 0; for (i = 0; i < bgp->confed_peers_cnt; i++) if (bgp->confed_peers[i] == as) return 1; return 0; } /* Add an AS to the confederation set. */ int bgp_confederation_peers_add(struct bgp *bgp, as_t as) { struct peer *peer; struct listnode *node, *nnode; if (!bgp) return BGP_ERR_INVALID_BGP; if (bgp->as == as) return BGP_ERR_INVALID_AS; if (bgp_confederation_peers_check(bgp, as)) return -1; if (bgp->confed_peers) bgp->confed_peers = XREALLOC(MTYPE_BGP_CONFED_LIST, bgp->confed_peers, (bgp->confed_peers_cnt + 1) * sizeof(as_t)); else bgp->confed_peers = XMALLOC(MTYPE_BGP_CONFED_LIST, (bgp->confed_peers_cnt + 1) * sizeof(as_t)); bgp->confed_peers[bgp->confed_peers_cnt] = as; bgp->confed_peers_cnt++; if (bgp_config_check(bgp, BGP_CONFIG_CONFEDERATION)) { for (ALL_LIST_ELEMENTS(bgp->peer, node, nnode, peer)) { if (peer->as == as) { peer->local_as = bgp->as; if (BGP_IS_VALID_STATE_FOR_NOTIF( peer->status)) { peer->last_reset = PEER_DOWN_CONFED_PEER_CHANGE; bgp_notify_send( peer, BGP_NOTIFY_CEASE, BGP_NOTIFY_CEASE_CONFIG_CHANGE); } else bgp_session_reset_safe(peer, &nnode); } } } return 0; } /* Delete an AS from the confederation set. */ int bgp_confederation_peers_remove(struct bgp *bgp, as_t as) { int i; int j; struct peer *peer; struct listnode *node, *nnode; if (!bgp) return -1; if (!bgp_confederation_peers_check(bgp, as)) return -1; for (i = 0; i < bgp->confed_peers_cnt; i++) if (bgp->confed_peers[i] == as) for (j = i + 1; j < bgp->confed_peers_cnt; j++) bgp->confed_peers[j - 1] = bgp->confed_peers[j]; bgp->confed_peers_cnt--; if (bgp->confed_peers_cnt == 0) { if (bgp->confed_peers) XFREE(MTYPE_BGP_CONFED_LIST, bgp->confed_peers); bgp->confed_peers = NULL; } else bgp->confed_peers = XREALLOC(MTYPE_BGP_CONFED_LIST, bgp->confed_peers, bgp->confed_peers_cnt * sizeof(as_t)); /* Now reset any peer who's remote AS has just been removed from the CONFED */ if (bgp_config_check(bgp, BGP_CONFIG_CONFEDERATION)) { for (ALL_LIST_ELEMENTS(bgp->peer, node, nnode, peer)) { if (peer->as == as) { peer->local_as = bgp->confed_id; if (BGP_IS_VALID_STATE_FOR_NOTIF( peer->status)) { peer->last_reset = PEER_DOWN_CONFED_PEER_CHANGE; bgp_notify_send( peer, BGP_NOTIFY_CEASE, BGP_NOTIFY_CEASE_CONFIG_CHANGE); } else bgp_session_reset_safe(peer, &nnode); } } } return 0; } /* Local preference configuration. */ int bgp_default_local_preference_set(struct bgp *bgp, uint32_t local_pref) { if (!bgp) return -1; bgp->default_local_pref = local_pref; return 0; } int bgp_default_local_preference_unset(struct bgp *bgp) { if (!bgp) return -1; bgp->default_local_pref = BGP_DEFAULT_LOCAL_PREF; return 0; } /* Local preference configuration. */ int bgp_default_subgroup_pkt_queue_max_set(struct bgp *bgp, uint32_t queue_size) { if (!bgp) return -1; bgp->default_subgroup_pkt_queue_max = queue_size; return 0; } int bgp_default_subgroup_pkt_queue_max_unset(struct bgp *bgp) { if (!bgp) return -1; bgp->default_subgroup_pkt_queue_max = BGP_DEFAULT_SUBGROUP_PKT_QUEUE_MAX; return 0; } /* Listen limit configuration. */ int bgp_listen_limit_set(struct bgp *bgp, int listen_limit) { if (!bgp) return -1; bgp->dynamic_neighbors_limit = listen_limit; return 0; } int bgp_listen_limit_unset(struct bgp *bgp) { if (!bgp) return -1; bgp->dynamic_neighbors_limit = BGP_DYNAMIC_NEIGHBORS_LIMIT_DEFAULT; return 0; } int bgp_map_afi_safi_iana2int(iana_afi_t pkt_afi, iana_safi_t pkt_safi, afi_t *afi, safi_t *safi) { /* Map from IANA values to internal values, return error if * values are unrecognized. */ *afi = afi_iana2int(pkt_afi); *safi = safi_iana2int(pkt_safi); if (*afi == AFI_MAX || *safi == SAFI_MAX) return -1; return 0; } int bgp_map_afi_safi_int2iana(afi_t afi, safi_t safi, iana_afi_t *pkt_afi, iana_safi_t *pkt_safi) { /* Map from internal values to IANA values, return error if * internal values are bad (unexpected). */ if (afi == AFI_MAX || safi == SAFI_MAX) return -1; *pkt_afi = afi_int2iana(afi); *pkt_safi = safi_int2iana(safi); return 0; } struct peer_af *peer_af_create(struct peer *peer, afi_t afi, safi_t safi) { struct peer_af *af; int afid; struct bgp *bgp; if (!peer) return NULL; afid = afindex(afi, safi); if (afid >= BGP_AF_MAX) return NULL; bgp = peer->bgp; assert(peer->peer_af_array[afid] == NULL); /* Allocate new peer af */ af = XCALLOC(MTYPE_BGP_PEER_AF, sizeof(struct peer_af)); peer->peer_af_array[afid] = af; af->afi = afi; af->safi = safi; af->afid = afid; af->peer = peer; bgp->af_peer_count[afi][safi]++; return af; } struct peer_af *peer_af_find(struct peer *peer, afi_t afi, safi_t safi) { int afid; if (!peer) return NULL; afid = afindex(afi, safi); if (afid >= BGP_AF_MAX) return NULL; return peer->peer_af_array[afid]; } int peer_af_delete(struct peer *peer, afi_t afi, safi_t safi) { struct peer_af *af; int afid; struct bgp *bgp; if (!peer) return -1; afid = afindex(afi, safi); if (afid >= BGP_AF_MAX) return -1; af = peer->peer_af_array[afid]; if (!af) return -1; bgp = peer->bgp; bgp_stop_announce_route_timer(af); if (PAF_SUBGRP(af)) { if (BGP_DEBUG(update_groups, UPDATE_GROUPS)) zlog_debug("u%" PRIu64 ":s%" PRIu64 " remove peer %s", af->subgroup->update_group->id, af->subgroup->id, peer->host); } update_subgroup_remove_peer(af->subgroup, af); if (bgp->af_peer_count[afi][safi]) bgp->af_peer_count[afi][safi]--; peer->peer_af_array[afid] = NULL; XFREE(MTYPE_BGP_PEER_AF, af); return 0; } /* Peer comparison function for sorting. */ int peer_cmp(struct peer *p1, struct peer *p2) { if (p1->group && !p2->group) return -1; if (!p1->group && p2->group) return 1; if (p1->group == p2->group) { if (p1->conf_if && !p2->conf_if) return -1; if (!p1->conf_if && p2->conf_if) return 1; if (p1->conf_if && p2->conf_if) return if_cmp_name_func(p1->conf_if, p2->conf_if); } else return strcmp(p1->group->name, p2->group->name); return sockunion_cmp(&p1->su, &p2->su); } static unsigned int peer_hash_key_make(const void *p) { const struct peer *peer = p; return sockunion_hash(&peer->su); } static bool peer_hash_same(const void *p1, const void *p2) { const struct peer *peer1 = p1; const struct peer *peer2 = p2; return (sockunion_same(&peer1->su, &peer2->su) && CHECK_FLAG(peer1->flags, PEER_FLAG_CONFIG_NODE) == CHECK_FLAG(peer2->flags, PEER_FLAG_CONFIG_NODE)); } void peer_flag_inherit(struct peer *peer, uint32_t flag) { bool group_val; /* Skip if peer is not a peer-group member. */ if (!peer_group_active(peer)) return; /* Unset override flag to signal inheritance from peer-group. */ UNSET_FLAG(peer->flags_override, flag); /* * Inherit flag state from peer-group. If the flag of the peer-group is * not being inverted, the peer must inherit the inverse of the current * peer-group flag state. */ group_val = CHECK_FLAG(peer->group->conf->flags, flag); if (!CHECK_FLAG(peer->group->conf->flags_invert, flag) && CHECK_FLAG(peer->flags_invert, flag)) COND_FLAG(peer->flags, flag, !group_val); else COND_FLAG(peer->flags, flag, group_val); } int peer_af_flag_check(struct peer *peer, afi_t afi, safi_t safi, uint32_t flag) { return CHECK_FLAG(peer->af_flags[afi][safi], flag); } void peer_af_flag_inherit(struct peer *peer, afi_t afi, safi_t safi, uint32_t flag) { bool group_val; /* Skip if peer is not a peer-group member. */ if (!peer_group_active(peer)) return; /* Unset override flag to signal inheritance from peer-group. */ UNSET_FLAG(peer->af_flags_override[afi][safi], flag); /* * Inherit flag state from peer-group. If the flag of the peer-group is * not being inverted, the peer must inherit the inverse of the current * peer-group flag state. */ group_val = CHECK_FLAG(peer->group->conf->af_flags[afi][safi], flag); if (!CHECK_FLAG(peer->group->conf->af_flags_invert[afi][safi], flag) && CHECK_FLAG(peer->af_flags_invert[afi][safi], flag)) COND_FLAG(peer->af_flags[afi][safi], flag, !group_val); else COND_FLAG(peer->af_flags[afi][safi], flag, group_val); } static bool peergroup_flag_check(struct peer *peer, uint32_t flag) { if (!peer_group_active(peer)) { if (CHECK_FLAG(peer->flags_invert, flag)) return !CHECK_FLAG(peer->flags, flag); else return !!CHECK_FLAG(peer->flags, flag); } return !!CHECK_FLAG(peer->flags_override, flag); } static bool peergroup_af_flag_check(struct peer *peer, afi_t afi, safi_t safi, uint32_t flag) { if (!peer_group_active(peer)) { if (CHECK_FLAG(peer->af_flags_invert[afi][safi], flag)) return !peer_af_flag_check(peer, afi, safi, flag); else return !!peer_af_flag_check(peer, afi, safi, flag); } return !!CHECK_FLAG(peer->af_flags_override[afi][safi], flag); } static bool peergroup_filter_check(struct peer *peer, afi_t afi, safi_t safi, uint8_t type, int direct) { struct bgp_filter *filter; if (peer_group_active(peer)) return !!CHECK_FLAG(peer->filter_override[afi][safi][direct], type); filter = &peer->filter[afi][safi]; switch (type) { case PEER_FT_DISTRIBUTE_LIST: return !!(filter->dlist[direct].name); case PEER_FT_FILTER_LIST: return !!(filter->aslist[direct].name); case PEER_FT_PREFIX_LIST: return !!(filter->plist[direct].name); case PEER_FT_ROUTE_MAP: return !!(filter->map[direct].name); case PEER_FT_UNSUPPRESS_MAP: return !!(filter->usmap.name); default: return false; } } /* Return true if the addpath type is set for peer and different from * peer-group. */ static int peergroup_af_addpath_check(struct peer *peer, afi_t afi, safi_t safi) { enum bgp_addpath_strat type, g_type; type = peer->addpath_type[afi][safi]; if (type != BGP_ADDPATH_NONE) { if (peer_group_active(peer)) { g_type = peer->group->conf->addpath_type[afi][safi]; if (type != g_type) return 1; else return 0; } return 1; } return 0; } /* Check peer's AS number and determines if this peer is IBGP or EBGP */ static inline bgp_peer_sort_t peer_calc_sort(struct peer *peer) { struct bgp *bgp; bgp = peer->bgp; /* Peer-group */ if (CHECK_FLAG(peer->sflags, PEER_STATUS_GROUP)) { if (peer->as_type == AS_INTERNAL) return BGP_PEER_IBGP; else if (peer->as_type == AS_EXTERNAL) return BGP_PEER_EBGP; else if (peer->as_type == AS_SPECIFIED && peer->as) { assert(bgp); return (bgp->as == peer->as ? BGP_PEER_IBGP : BGP_PEER_EBGP); } else { struct peer *peer1; assert(peer->group); peer1 = listnode_head(peer->group->peer); if (peer1) return peer1->sort; } return BGP_PEER_INTERNAL; } /* Normal peer */ if (bgp && CHECK_FLAG(bgp->config, BGP_CONFIG_CONFEDERATION)) { if (peer->local_as == 0) return BGP_PEER_INTERNAL; if (peer->local_as == peer->as) { if (bgp->as == bgp->confed_id) { if (peer->local_as == bgp->as) return BGP_PEER_IBGP; else return BGP_PEER_EBGP; } else { if (peer->local_as == bgp->confed_id) return BGP_PEER_EBGP; else return BGP_PEER_IBGP; } } if (bgp_confederation_peers_check(bgp, peer->as)) return BGP_PEER_CONFED; return BGP_PEER_EBGP; } else { if (peer->as_type == AS_UNSPECIFIED) { /* check if in peer-group with AS information */ if (peer->group && (peer->group->conf->as_type != AS_UNSPECIFIED)) { if (peer->group->conf->as_type == AS_SPECIFIED) { if (peer->local_as == peer->group->conf->as) return BGP_PEER_IBGP; else return BGP_PEER_EBGP; } else if (peer->group->conf->as_type == AS_INTERNAL) return BGP_PEER_IBGP; else return BGP_PEER_EBGP; } /* no AS information anywhere, let caller know */ return BGP_PEER_UNSPECIFIED; } else if (peer->as_type != AS_SPECIFIED) return (peer->as_type == AS_INTERNAL ? BGP_PEER_IBGP : BGP_PEER_EBGP); return (peer->local_as == 0 ? BGP_PEER_INTERNAL : peer->local_as == peer->as ? BGP_PEER_IBGP : BGP_PEER_EBGP); } } /* Calculate and cache the peer "sort" */ bgp_peer_sort_t peer_sort(struct peer *peer) { peer->sort = peer_calc_sort(peer); return peer->sort; } static void peer_free(struct peer *peer) { afi_t afi; safi_t safi; assert(peer->status == Deleted); QOBJ_UNREG(peer); /* this /ought/ to have been done already through bgp_stop earlier, * but just to be sure.. */ bgp_timer_set(peer); bgp_reads_off(peer); bgp_writes_off(peer); assert(!peer->t_write); assert(!peer->t_read); BGP_EVENT_FLUSH(peer); pthread_mutex_destroy(&peer->io_mtx); /* Free connected nexthop, if present */ if (CHECK_FLAG(peer->flags, PEER_FLAG_CONFIG_NODE) && !peer_dynamic_neighbor(peer)) bgp_delete_connected_nexthop(family2afi(peer->su.sa.sa_family), peer); XFREE(MTYPE_PEER_TX_SHUTDOWN_MSG, peer->tx_shutdown_message); if (peer->desc) { XFREE(MTYPE_PEER_DESC, peer->desc); peer->desc = NULL; } /* Free allocated host character. */ if (peer->host) { XFREE(MTYPE_BGP_PEER_HOST, peer->host); peer->host = NULL; } if (peer->domainname) { XFREE(MTYPE_BGP_PEER_HOST, peer->domainname); peer->domainname = NULL; } if (peer->ifname) { XFREE(MTYPE_BGP_PEER_IFNAME, peer->ifname); peer->ifname = NULL; } /* Update source configuration. */ if (peer->update_source) { sockunion_free(peer->update_source); peer->update_source = NULL; } if (peer->update_if) { XFREE(MTYPE_PEER_UPDATE_SOURCE, peer->update_if); peer->update_if = NULL; } XFREE(MTYPE_TMP, peer->notify.data); memset(&peer->notify, 0, sizeof(struct bgp_notify)); if (peer->clear_node_queue) work_queue_free_and_null(&peer->clear_node_queue); bgp_sync_delete(peer); if (peer->conf_if) { XFREE(MTYPE_PEER_CONF_IF, peer->conf_if); peer->conf_if = NULL; } bfd_info_free(&(peer->bfd_info)); for (afi = AFI_IP; afi < AFI_MAX; afi++) { for (safi = SAFI_UNICAST; safi < SAFI_MAX; safi++) { bgp_addpath_set_peer_type(peer, afi, safi, BGP_ADDPATH_NONE); } } bgp_unlock(peer->bgp); memset(peer, 0, sizeof(struct peer)); XFREE(MTYPE_BGP_PEER, peer); } /* increase reference count on a struct peer */ struct peer *peer_lock_with_caller(const char *name, struct peer *peer) { assert(peer && (peer->lock >= 0)); #if 0 zlog_debug("%s peer_lock %p %d", name, peer, peer->lock); #endif peer->lock++; return peer; } /* decrease reference count on a struct peer * struct peer is freed and NULL returned if last reference */ struct peer *peer_unlock_with_caller(const char *name, struct peer *peer) { assert(peer && (peer->lock > 0)); #if 0 zlog_debug("%s peer_unlock %p %d", name, peer, peer->lock); #endif peer->lock--; if (peer->lock == 0) { peer_free(peer); return NULL; } return peer; } /* Allocate new peer object, implicitely locked. */ struct peer *peer_new(struct bgp *bgp) { afi_t afi; safi_t safi; struct peer *peer; struct servent *sp; /* bgp argument is absolutely required */ assert(bgp); if (!bgp) return NULL; /* Allocate new peer. */ peer = XCALLOC(MTYPE_BGP_PEER, sizeof(struct peer)); /* Set default value. */ peer->fd = -1; peer->v_start = BGP_INIT_START_TIMER; peer->v_connect = BGP_DEFAULT_CONNECT_RETRY; peer->status = Idle; peer->ostatus = Idle; peer->cur_event = peer->last_event = peer->last_major_event = 0; peer->bgp = bgp_lock(bgp); peer = peer_lock(peer); /* initial reference */ peer->password = NULL; /* Set default flags. */ FOREACH_AFI_SAFI (afi, safi) { SET_FLAG(peer->af_flags[afi][safi], PEER_FLAG_SEND_COMMUNITY); SET_FLAG(peer->af_flags[afi][safi], PEER_FLAG_SEND_EXT_COMMUNITY); SET_FLAG(peer->af_flags[afi][safi], PEER_FLAG_SEND_LARGE_COMMUNITY); SET_FLAG(peer->af_flags_invert[afi][safi], PEER_FLAG_SEND_COMMUNITY); SET_FLAG(peer->af_flags_invert[afi][safi], PEER_FLAG_SEND_EXT_COMMUNITY); SET_FLAG(peer->af_flags_invert[afi][safi], PEER_FLAG_SEND_LARGE_COMMUNITY); peer->addpath_type[afi][safi] = BGP_ADDPATH_NONE; } /* set nexthop-unchanged for l2vpn evpn by default */ SET_FLAG(peer->af_flags[AFI_L2VPN][SAFI_EVPN], PEER_FLAG_NEXTHOP_UNCHANGED); SET_FLAG(peer->sflags, PEER_STATUS_CAPABILITY_OPEN); /* Create buffers. */ peer->ibuf = stream_fifo_new(); peer->obuf = stream_fifo_new(); pthread_mutex_init(&peer->io_mtx, NULL); /* We use a larger buffer for peer->obuf_work in the event that: * - We RX a BGP_UPDATE where the attributes alone are just * under BGP_MAX_PACKET_SIZE * - The user configures an outbound route-map that does many as-path * prepends or adds many communities. At most they can have * CMD_ARGC_MAX args in a route-map so there is a finite limit on how * large they can make the attributes. * * Having a buffer with BGP_MAX_PACKET_SIZE_OVERFLOW allows us to avoid * bounds checking for every single attribute as we construct an * UPDATE. */ peer->obuf_work = stream_new(BGP_MAX_PACKET_SIZE + BGP_MAX_PACKET_SIZE_OVERFLOW); peer->ibuf_work = ringbuf_new(BGP_MAX_PACKET_SIZE * BGP_READ_PACKET_MAX); peer->scratch = stream_new(BGP_MAX_PACKET_SIZE); bgp_sync_init(peer); /* Get service port number. */ sp = getservbyname("bgp", "tcp"); peer->port = (sp == NULL) ? BGP_PORT_DEFAULT : ntohs(sp->s_port); QOBJ_REG(peer, peer); return peer; } /* * This function is invoked when a duplicate peer structure associated with * a neighbor is being deleted. If this about-to-be-deleted structure is * the one with all the config, then we have to copy over the info. */ void peer_xfer_config(struct peer *peer_dst, struct peer *peer_src) { struct peer_af *paf; afi_t afi; safi_t safi; int afidx; assert(peer_src); assert(peer_dst); /* The following function is used by both peer group config copy to * individual peer and when we transfer config */ if (peer_src->change_local_as) peer_dst->change_local_as = peer_src->change_local_as; /* peer flags apply */ peer_dst->flags = peer_src->flags; peer_dst->cap = peer_src->cap; peer_dst->local_as = peer_src->local_as; peer_dst->port = peer_src->port; (void)peer_sort(peer_dst); peer_dst->rmap_type = peer_src->rmap_type; /* Timers */ peer_dst->holdtime = peer_src->holdtime; peer_dst->keepalive = peer_src->keepalive; peer_dst->connect = peer_src->connect; peer_dst->v_holdtime = peer_src->v_holdtime; peer_dst->v_keepalive = peer_src->v_keepalive; peer_dst->routeadv = peer_src->routeadv; peer_dst->v_routeadv = peer_src->v_routeadv; /* password apply */ if (peer_src->password && !peer_dst->password) peer_dst->password = XSTRDUP(MTYPE_PEER_PASSWORD, peer_src->password); FOREACH_AFI_SAFI (afi, safi) { peer_dst->afc[afi][safi] = peer_src->afc[afi][safi]; peer_dst->af_flags[afi][safi] = peer_src->af_flags[afi][safi]; peer_dst->allowas_in[afi][safi] = peer_src->allowas_in[afi][safi]; peer_dst->weight[afi][safi] = peer_src->weight[afi][safi]; peer_dst->addpath_type[afi][safi] = peer_src->addpath_type[afi][safi]; } for (afidx = BGP_AF_START; afidx < BGP_AF_MAX; afidx++) { paf = peer_src->peer_af_array[afidx]; if (paf != NULL) peer_af_create(peer_dst, paf->afi, paf->safi); } /* update-source apply */ if (peer_src->update_source) { if (peer_dst->update_source) sockunion_free(peer_dst->update_source); if (peer_dst->update_if) { XFREE(MTYPE_PEER_UPDATE_SOURCE, peer_dst->update_if); peer_dst->update_if = NULL; } peer_dst->update_source = sockunion_dup(peer_src->update_source); } else if (peer_src->update_if) { XFREE(MTYPE_PEER_UPDATE_SOURCE, peer_dst->update_if); if (peer_dst->update_source) { sockunion_free(peer_dst->update_source); peer_dst->update_source = NULL; } peer_dst->update_if = XSTRDUP(MTYPE_PEER_UPDATE_SOURCE, peer_src->update_if); } if (peer_src->ifname) { XFREE(MTYPE_BGP_PEER_IFNAME, peer_dst->ifname); peer_dst->ifname = XSTRDUP(MTYPE_BGP_PEER_IFNAME, peer_src->ifname); } } static int bgp_peer_conf_if_to_su_update_v4(struct peer *peer, struct interface *ifp) { struct connected *ifc; struct prefix p; uint32_t addr; struct listnode *node; /* If our IPv4 address on the interface is /30 or /31, we can derive the * IPv4 address of the other end. */ for (ALL_LIST_ELEMENTS_RO(ifp->connected, node, ifc)) { if (ifc->address && (ifc->address->family == AF_INET)) { PREFIX_COPY_IPV4(&p, CONNECTED_PREFIX(ifc)); if (p.prefixlen == 30) { peer->su.sa.sa_family = AF_INET; addr = ntohl(p.u.prefix4.s_addr); if (addr % 4 == 1) peer->su.sin.sin_addr.s_addr = htonl(addr + 1); else if (addr % 4 == 2) peer->su.sin.sin_addr.s_addr = htonl(addr - 1); #ifdef HAVE_STRUCT_SOCKADDR_IN_SIN_LEN peer->su.sin.sin_len = sizeof(struct sockaddr_in); #endif /* HAVE_STRUCT_SOCKADDR_IN_SIN_LEN */ return 1; } else if (p.prefixlen == 31) { peer->su.sa.sa_family = AF_INET; addr = ntohl(p.u.prefix4.s_addr); if (addr % 2 == 0) peer->su.sin.sin_addr.s_addr = htonl(addr + 1); else peer->su.sin.sin_addr.s_addr = htonl(addr - 1); #ifdef HAVE_STRUCT_SOCKADDR_IN_SIN_LEN peer->su.sin.sin_len = sizeof(struct sockaddr_in); #endif /* HAVE_STRUCT_SOCKADDR_IN_SIN_LEN */ return 1; } else if (bgp_debug_neighbor_events(peer)) zlog_debug( "%s: IPv4 interface address is not /30 or /31, v4 session not started", peer->conf_if); } } return 0; } static int bgp_peer_conf_if_to_su_update_v6(struct peer *peer, struct interface *ifp) { struct nbr_connected *ifc_nbr; /* Have we learnt the peer's IPv6 link-local address? */ if (ifp->nbr_connected && (ifc_nbr = listnode_head(ifp->nbr_connected))) { peer->su.sa.sa_family = AF_INET6; memcpy(&peer->su.sin6.sin6_addr, &ifc_nbr->address->u.prefix, sizeof(struct in6_addr)); #ifdef SIN6_LEN peer->su.sin6.sin6_len = sizeof(struct sockaddr_in6); #endif peer->su.sin6.sin6_scope_id = ifp->ifindex; return 1; } return 0; } /* * Set or reset the peer address socketunion structure based on the * learnt/derived peer address. If the address has changed, update the * password on the listen socket, if needed. */ void bgp_peer_conf_if_to_su_update(struct peer *peer) { struct interface *ifp; int prev_family; int peer_addr_updated = 0; if (!peer->conf_if) return; /* * Our peer structure is stored in the bgp->peerhash * release it before we modify anything. */ hash_release(peer->bgp->peerhash, peer); prev_family = peer->su.sa.sa_family; if ((ifp = if_lookup_by_name(peer->conf_if, peer->bgp->vrf_id))) { peer->ifp = ifp; /* If BGP unnumbered is not "v6only", we first see if we can * derive the * peer's IPv4 address. */ if (!CHECK_FLAG(peer->flags, PEER_FLAG_IFPEER_V6ONLY)) peer_addr_updated = bgp_peer_conf_if_to_su_update_v4(peer, ifp); /* If "v6only" or we can't derive peer's IPv4 address, see if * we've * learnt the peer's IPv6 link-local address. This is from the * source * IPv6 address in router advertisement. */ if (!peer_addr_updated) peer_addr_updated = bgp_peer_conf_if_to_su_update_v6(peer, ifp); } /* If we could derive the peer address, we may need to install the * password * configured for the peer, if any, on the listen socket. Otherwise, * mark * that peer's address is not available and uninstall the password, if * needed. */ if (peer_addr_updated) { if (CHECK_FLAG(peer->flags, PEER_FLAG_PASSWORD) && prev_family == AF_UNSPEC) bgp_md5_set(peer); } else { if (CHECK_FLAG(peer->flags, PEER_FLAG_PASSWORD) && prev_family != AF_UNSPEC) bgp_md5_unset(peer); peer->su.sa.sa_family = AF_UNSPEC; memset(&peer->su.sin6.sin6_addr, 0, sizeof(struct in6_addr)); } /* * Since our su changed we need to del/add peer to the peerhash */ hash_get(peer->bgp->peerhash, peer, hash_alloc_intern); } static void bgp_recalculate_afi_safi_bestpaths(struct bgp *bgp, afi_t afi, safi_t safi) { struct bgp_node *rn, *nrn; struct bgp_table *table; for (rn = bgp_table_top(bgp->rib[afi][safi]); rn; rn = bgp_route_next(rn)) { table = bgp_node_get_bgp_table_info(rn); if (table != NULL) { /* Special handling for 2-level routing * tables. */ if (safi == SAFI_MPLS_VPN || safi == SAFI_ENCAP || safi == SAFI_EVPN) { for (nrn = bgp_table_top(table); nrn; nrn = bgp_route_next(nrn)) bgp_process(bgp, nrn, afi, safi); } else bgp_process(bgp, rn, afi, safi); } } } /* Force a bestpath recalculation for all prefixes. This is used * when 'bgp bestpath' commands are entered. */ void bgp_recalculate_all_bestpaths(struct bgp *bgp) { afi_t afi; safi_t safi; FOREACH_AFI_SAFI (afi, safi) { bgp_recalculate_afi_safi_bestpaths(bgp, afi, safi); } } /* * Create new BGP peer. * * conf_if and su are mutually exclusive if configuring from the cli. * If we are handing a doppelganger, then we *must* pass in both * the original peer's su and conf_if, so that we can appropriately * track the bgp->peerhash( ie we don't want to remove the current * one from the config ). */ struct peer *peer_create(union sockunion *su, const char *conf_if, struct bgp *bgp, as_t local_as, as_t remote_as, int as_type, afi_t afi, safi_t safi, struct peer_group *group) { int active; struct peer *peer; char buf[SU_ADDRSTRLEN]; peer = peer_new(bgp); if (conf_if) { peer->conf_if = XSTRDUP(MTYPE_PEER_CONF_IF, conf_if); if (su) peer->su = *su; else bgp_peer_conf_if_to_su_update(peer); XFREE(MTYPE_BGP_PEER_HOST, peer->host); peer->host = XSTRDUP(MTYPE_BGP_PEER_HOST, conf_if); } else if (su) { peer->su = *su; sockunion2str(su, buf, SU_ADDRSTRLEN); XFREE(MTYPE_BGP_PEER_HOST, peer->host); peer->host = XSTRDUP(MTYPE_BGP_PEER_HOST, buf); } peer->local_as = local_as; peer->as = remote_as; peer->as_type = as_type; peer->local_id = bgp->router_id; peer->v_holdtime = bgp->default_holdtime; peer->v_keepalive = bgp->default_keepalive; peer->v_routeadv = (peer_sort(peer) == BGP_PEER_IBGP) ? BGP_DEFAULT_IBGP_ROUTEADV : BGP_DEFAULT_EBGP_ROUTEADV; peer = peer_lock(peer); /* bgp peer list reference */ peer->group = group; listnode_add_sort(bgp->peer, peer); hash_get(bgp->peerhash, peer, hash_alloc_intern); /* Adjust update-group coalesce timer heuristics for # peers. */ if (bgp->heuristic_coalesce) { long ct = BGP_DEFAULT_SUBGROUP_COALESCE_TIME + (bgp->peer->count * BGP_PEER_ADJUST_SUBGROUP_COALESCE_TIME); bgp->coalesce_time = MIN(BGP_MAX_SUBGROUP_COALESCE_TIME, ct); } active = peer_active(peer); if (!active) { if (peer->su.sa.sa_family == AF_UNSPEC) peer->last_reset = PEER_DOWN_NBR_ADDR; else peer->last_reset = PEER_DOWN_NOAFI_ACTIVATED; } /* Last read and reset time set */ peer->readtime = peer->resettime = bgp_clock(); /* Default TTL set. */ peer->ttl = (peer->sort == BGP_PEER_IBGP) ? MAXTTL : BGP_DEFAULT_TTL; SET_FLAG(peer->flags, PEER_FLAG_CONFIG_NODE); if (afi && safi) { peer->afc[afi][safi] = 1; peer_af_create(peer, afi, safi); } /* auto shutdown if configured */ if (bgp->autoshutdown) peer_flag_set(peer, PEER_FLAG_SHUTDOWN); /* Set up peer's events and timers. */ else if (!active && peer_active(peer)) bgp_timer_set(peer); return peer; } /* Make accept BGP peer. This function is only called from the test code */ struct peer *peer_create_accept(struct bgp *bgp) { struct peer *peer; peer = peer_new(bgp); peer = peer_lock(peer); /* bgp peer list reference */ listnode_add_sort(bgp->peer, peer); return peer; } /* * Return true if we have a peer configured to use this afi/safi */ int bgp_afi_safi_peer_exists(struct bgp *bgp, afi_t afi, safi_t safi) { struct listnode *node; struct peer *peer; for (ALL_LIST_ELEMENTS_RO(bgp->peer, node, peer)) { if (!CHECK_FLAG(peer->flags, PEER_FLAG_CONFIG_NODE)) continue; if (peer->afc[afi][safi]) return 1; } return 0; } /* Change peer's AS number. */ void peer_as_change(struct peer *peer, as_t as, int as_specified) { bgp_peer_sort_t type; /* Stop peer. */ if (!CHECK_FLAG(peer->sflags, PEER_STATUS_GROUP)) { if (BGP_IS_VALID_STATE_FOR_NOTIF(peer->status)) { peer->last_reset = PEER_DOWN_REMOTE_AS_CHANGE; bgp_notify_send(peer, BGP_NOTIFY_CEASE, BGP_NOTIFY_CEASE_CONFIG_CHANGE); } else bgp_session_reset(peer); } type = peer_sort(peer); peer->as = as; peer->as_type = as_specified; if (bgp_config_check(peer->bgp, BGP_CONFIG_CONFEDERATION) && !bgp_confederation_peers_check(peer->bgp, as) && peer->bgp->as != as) peer->local_as = peer->bgp->confed_id; else peer->local_as = peer->bgp->as; /* Advertisement-interval reset */ if (!CHECK_FLAG(peer->flags, PEER_FLAG_ROUTEADV)) { peer->v_routeadv = (peer_sort(peer) == BGP_PEER_IBGP) ? BGP_DEFAULT_IBGP_ROUTEADV : BGP_DEFAULT_EBGP_ROUTEADV; } /* TTL reset */ if (peer_sort(peer) == BGP_PEER_IBGP) peer->ttl = MAXTTL; else if (type == BGP_PEER_IBGP) peer->ttl = BGP_DEFAULT_TTL; /* reflector-client reset */ if (peer_sort(peer) != BGP_PEER_IBGP) { UNSET_FLAG(peer->af_flags[AFI_IP][SAFI_UNICAST], PEER_FLAG_REFLECTOR_CLIENT); UNSET_FLAG(peer->af_flags[AFI_IP][SAFI_MULTICAST], PEER_FLAG_REFLECTOR_CLIENT); UNSET_FLAG(peer->af_flags[AFI_IP][SAFI_LABELED_UNICAST], PEER_FLAG_REFLECTOR_CLIENT); UNSET_FLAG(peer->af_flags[AFI_IP][SAFI_MPLS_VPN], PEER_FLAG_REFLECTOR_CLIENT); UNSET_FLAG(peer->af_flags[AFI_IP][SAFI_ENCAP], PEER_FLAG_REFLECTOR_CLIENT); UNSET_FLAG(peer->af_flags[AFI_IP][SAFI_FLOWSPEC], PEER_FLAG_REFLECTOR_CLIENT); UNSET_FLAG(peer->af_flags[AFI_IP6][SAFI_UNICAST], PEER_FLAG_REFLECTOR_CLIENT); UNSET_FLAG(peer->af_flags[AFI_IP6][SAFI_MULTICAST], PEER_FLAG_REFLECTOR_CLIENT); UNSET_FLAG(peer->af_flags[AFI_IP6][SAFI_LABELED_UNICAST], PEER_FLAG_REFLECTOR_CLIENT); UNSET_FLAG(peer->af_flags[AFI_IP6][SAFI_MPLS_VPN], PEER_FLAG_REFLECTOR_CLIENT); UNSET_FLAG(peer->af_flags[AFI_IP6][SAFI_ENCAP], PEER_FLAG_REFLECTOR_CLIENT); UNSET_FLAG(peer->af_flags[AFI_IP6][SAFI_FLOWSPEC], PEER_FLAG_REFLECTOR_CLIENT); UNSET_FLAG(peer->af_flags[AFI_L2VPN][SAFI_EVPN], PEER_FLAG_REFLECTOR_CLIENT); } /* local-as reset */ if (peer_sort(peer) != BGP_PEER_EBGP) { peer->change_local_as = 0; peer_flag_unset(peer, PEER_FLAG_LOCAL_AS); peer_flag_unset(peer, PEER_FLAG_LOCAL_AS_NO_PREPEND); peer_flag_unset(peer, PEER_FLAG_LOCAL_AS_REPLACE_AS); } } /* If peer does not exist, create new one. If peer already exists, set AS number to the peer. */ int peer_remote_as(struct bgp *bgp, union sockunion *su, const char *conf_if, as_t *as, int as_type, afi_t afi, safi_t safi) { struct peer *peer; as_t local_as; if (conf_if) peer = peer_lookup_by_conf_if(bgp, conf_if); else peer = peer_lookup(bgp, su); if (peer) { /* Not allowed for a dynamic peer. */ if (peer_dynamic_neighbor(peer)) { *as = peer->as; return BGP_ERR_INVALID_FOR_DYNAMIC_PEER; } /* When this peer is a member of peer-group. */ if (peer->group) { /* peer-group already has AS number/internal/external */ if (peer->group->conf->as || peer->group->conf->as_type) { /* Return peer group's AS number. */ *as = peer->group->conf->as; return BGP_ERR_PEER_GROUP_MEMBER; } bgp_peer_sort_t peer_sort_type = peer_sort(peer->group->conf); /* Explicit AS numbers used, compare AS numbers */ if (as_type == AS_SPECIFIED) { if (((peer_sort_type == BGP_PEER_IBGP) && (bgp->as != *as)) || ((peer_sort_type == BGP_PEER_EBGP) && (bgp->as == *as))) { *as = peer->as; return BGP_ERR_PEER_GROUP_PEER_TYPE_DIFFERENT; } } else { /* internal/external used, compare as-types */ if (((peer_sort_type == BGP_PEER_IBGP) && (as_type != AS_INTERNAL)) || ((peer_sort_type == BGP_PEER_EBGP) && (as_type != AS_EXTERNAL))) { *as = peer->as; return BGP_ERR_PEER_GROUP_PEER_TYPE_DIFFERENT; } } } /* Existing peer's AS number change. */ if (((peer->as_type == AS_SPECIFIED) && peer->as != *as) || (peer->as_type != as_type)) peer_as_change(peer, *as, as_type); } else { if (conf_if) return BGP_ERR_NO_INTERFACE_CONFIG; /* If the peer is not part of our confederation, and its not an iBGP peer then spoof the source AS */ if (bgp_config_check(bgp, BGP_CONFIG_CONFEDERATION) && !bgp_confederation_peers_check(bgp, *as) && bgp->as != *as) local_as = bgp->confed_id; else local_as = bgp->as; /* If this is IPv4 unicast configuration and "no bgp default ipv4-unicast" is specified. */ if (bgp_flag_check(bgp, BGP_FLAG_NO_DEFAULT_IPV4) && afi == AFI_IP && safi == SAFI_UNICAST) peer_create(su, conf_if, bgp, local_as, *as, as_type, 0, 0, NULL); else peer_create(su, conf_if, bgp, local_as, *as, as_type, afi, safi, NULL); } return 0; } static void peer_group2peer_config_copy_af(struct peer_group *group, struct peer *peer, afi_t afi, safi_t safi) { int in = FILTER_IN; int out = FILTER_OUT; uint32_t flags_tmp; uint32_t pflags_ovrd; uint8_t *pfilter_ovrd; struct peer *conf; conf = group->conf; pflags_ovrd = peer->af_flags_override[afi][safi]; pfilter_ovrd = &peer->filter_override[afi][safi][in]; /* peer af_flags apply */ flags_tmp = conf->af_flags[afi][safi] & ~pflags_ovrd; flags_tmp ^= conf->af_flags_invert[afi][safi] ^ peer->af_flags_invert[afi][safi]; flags_tmp &= ~pflags_ovrd; UNSET_FLAG(peer->af_flags[afi][safi], ~pflags_ovrd); SET_FLAG(peer->af_flags[afi][safi], flags_tmp); SET_FLAG(peer->af_flags_invert[afi][safi], conf->af_flags_invert[afi][safi]); /* maximum-prefix */ if (!CHECK_FLAG(pflags_ovrd, PEER_FLAG_MAX_PREFIX)) { PEER_ATTR_INHERIT(peer, group, pmax[afi][safi]); PEER_ATTR_INHERIT(peer, group, pmax_threshold[afi][safi]); PEER_ATTR_INHERIT(peer, group, pmax_restart[afi][safi]); } /* allowas-in */ if (!CHECK_FLAG(pflags_ovrd, PEER_FLAG_ALLOWAS_IN)) PEER_ATTR_INHERIT(peer, group, allowas_in[afi][safi]); /* weight */ if (!CHECK_FLAG(pflags_ovrd, PEER_FLAG_WEIGHT)) PEER_ATTR_INHERIT(peer, group, weight[afi][safi]); /* default-originate route-map */ if (!CHECK_FLAG(pflags_ovrd, PEER_FLAG_DEFAULT_ORIGINATE)) { PEER_STR_ATTR_INHERIT(peer, group, default_rmap[afi][safi].name, MTYPE_ROUTE_MAP_NAME); PEER_ATTR_INHERIT(peer, group, default_rmap[afi][safi].map); } /* inbound filter apply */ if (!CHECK_FLAG(pfilter_ovrd[in], PEER_FT_DISTRIBUTE_LIST)) { PEER_STR_ATTR_INHERIT(peer, group, filter[afi][safi].dlist[in].name, MTYPE_BGP_FILTER_NAME); PEER_ATTR_INHERIT(peer, group, filter[afi][safi].dlist[in].alist); } if (!CHECK_FLAG(pfilter_ovrd[in], PEER_FT_PREFIX_LIST)) { PEER_STR_ATTR_INHERIT(peer, group, filter[afi][safi].plist[in].name, MTYPE_BGP_FILTER_NAME); PEER_ATTR_INHERIT(peer, group, filter[afi][safi].plist[in].plist); } if (!CHECK_FLAG(pfilter_ovrd[in], PEER_FT_FILTER_LIST)) { PEER_STR_ATTR_INHERIT(peer, group, filter[afi][safi].aslist[in].name, MTYPE_BGP_FILTER_NAME); PEER_ATTR_INHERIT(peer, group, filter[afi][safi].aslist[in].aslist); } if (!CHECK_FLAG(pfilter_ovrd[RMAP_IN], PEER_FT_ROUTE_MAP)) { PEER_STR_ATTR_INHERIT(peer, group, filter[afi][safi].map[in].name, MTYPE_BGP_FILTER_NAME); PEER_ATTR_INHERIT(peer, group, filter[afi][safi].map[RMAP_IN].map); } /* outbound filter apply */ if (!CHECK_FLAG(pfilter_ovrd[out], PEER_FT_DISTRIBUTE_LIST)) { PEER_STR_ATTR_INHERIT(peer, group, filter[afi][safi].dlist[out].name, MTYPE_BGP_FILTER_NAME); PEER_ATTR_INHERIT(peer, group, filter[afi][safi].dlist[out].alist); } if (!CHECK_FLAG(pfilter_ovrd[out], PEER_FT_PREFIX_LIST)) { PEER_STR_ATTR_INHERIT(peer, group, filter[afi][safi].plist[out].name, MTYPE_BGP_FILTER_NAME); PEER_ATTR_INHERIT(peer, group, filter[afi][safi].plist[out].plist); } if (!CHECK_FLAG(pfilter_ovrd[out], PEER_FT_FILTER_LIST)) { PEER_STR_ATTR_INHERIT(peer, group, filter[afi][safi].aslist[out].name, MTYPE_BGP_FILTER_NAME); PEER_ATTR_INHERIT(peer, group, filter[afi][safi].aslist[out].aslist); } if (!CHECK_FLAG(pfilter_ovrd[RMAP_OUT], PEER_FT_ROUTE_MAP)) { PEER_STR_ATTR_INHERIT(peer, group, filter[afi][safi].map[RMAP_OUT].name, MTYPE_BGP_FILTER_NAME); PEER_ATTR_INHERIT(peer, group, filter[afi][safi].map[RMAP_OUT].map); } /* nondirectional filter apply */ if (!CHECK_FLAG(pfilter_ovrd[0], PEER_FT_UNSUPPRESS_MAP)) { PEER_STR_ATTR_INHERIT(peer, group, filter[afi][safi].usmap.name, MTYPE_BGP_FILTER_NAME); PEER_ATTR_INHERIT(peer, group, filter[afi][safi].usmap.map); } if (peer->addpath_type[afi][safi] == BGP_ADDPATH_NONE) { peer->addpath_type[afi][safi] = conf->addpath_type[afi][safi]; bgp_addpath_type_changed(conf->bgp); } } static int peer_activate_af(struct peer *peer, afi_t afi, safi_t safi) { int active; struct peer *other; if (CHECK_FLAG(peer->sflags, PEER_STATUS_GROUP)) { flog_err(EC_BGP_PEER_GROUP, "%s was called for peer-group %s", __func__, peer->host); return 1; } /* Do not activate a peer for both SAFI_UNICAST and SAFI_LABELED_UNICAST */ if ((safi == SAFI_UNICAST && peer->afc[afi][SAFI_LABELED_UNICAST]) || (safi == SAFI_LABELED_UNICAST && peer->afc[afi][SAFI_UNICAST])) return BGP_ERR_PEER_SAFI_CONFLICT; /* Nothing to do if we've already activated this peer */ if (peer->afc[afi][safi]) return 0; if (peer_af_create(peer, afi, safi) == NULL) return 1; active = peer_active(peer); peer->afc[afi][safi] = 1; if (peer->group) peer_group2peer_config_copy_af(peer->group, peer, afi, safi); if (!active && peer_active(peer)) { bgp_timer_set(peer); } else { if (peer->status == Established) { if (CHECK_FLAG(peer->cap, PEER_CAP_DYNAMIC_RCV)) { peer->afc_adv[afi][safi] = 1; bgp_capability_send(peer, afi, safi, CAPABILITY_CODE_MP, CAPABILITY_ACTION_SET); if (peer->afc_recv[afi][safi]) { peer->afc_nego[afi][safi] = 1; bgp_announce_route(peer, afi, safi); } } else { peer->last_reset = PEER_DOWN_AF_ACTIVATE; bgp_notify_send(peer, BGP_NOTIFY_CEASE, BGP_NOTIFY_CEASE_CONFIG_CHANGE); } } if (peer->status == OpenSent || peer->status == OpenConfirm) { peer->last_reset = PEER_DOWN_AF_ACTIVATE; bgp_notify_send(peer, BGP_NOTIFY_CEASE, BGP_NOTIFY_CEASE_CONFIG_CHANGE); } /* * If we are turning on a AFI/SAFI locally and we've * started bringing a peer up, we need to tell * the other peer to restart because we might loose * configuration here because when the doppelganger * gets to a established state due to how * we resolve we could just overwrite the afi/safi * activation. */ other = peer->doppelganger; if (other && (other->status == OpenSent || other->status == OpenConfirm)) { other->last_reset = PEER_DOWN_AF_ACTIVATE; bgp_notify_send(other, BGP_NOTIFY_CEASE, BGP_NOTIFY_CEASE_CONFIG_CHANGE); } } return 0; } /* Activate the peer or peer group for specified AFI and SAFI. */ int peer_activate(struct peer *peer, afi_t afi, safi_t safi) { int ret = 0; struct peer_group *group; struct listnode *node, *nnode; struct peer *tmp_peer; struct bgp *bgp; /* Nothing to do if we've already activated this peer */ if (peer->afc[afi][safi]) return ret; bgp = peer->bgp; /* This is a peer-group so activate all of the members of the * peer-group as well */ if (CHECK_FLAG(peer->sflags, PEER_STATUS_GROUP)) { /* Do not activate a peer for both SAFI_UNICAST and * SAFI_LABELED_UNICAST */ if ((safi == SAFI_UNICAST && peer->afc[afi][SAFI_LABELED_UNICAST]) || (safi == SAFI_LABELED_UNICAST && peer->afc[afi][SAFI_UNICAST])) return BGP_ERR_PEER_SAFI_CONFLICT; peer->afc[afi][safi] = 1; group = peer->group; for (ALL_LIST_ELEMENTS(group->peer, node, nnode, tmp_peer)) { ret |= peer_activate_af(tmp_peer, afi, safi); } } else { ret |= peer_activate_af(peer, afi, safi); } /* If this is the first peer to be activated for this * afi/labeled-unicast recalc bestpaths to trigger label allocation */ if (safi == SAFI_LABELED_UNICAST && !bgp->allocate_mpls_labels[afi][SAFI_UNICAST]) { if (BGP_DEBUG(zebra, ZEBRA)) zlog_info( "peer(s) are now active for labeled-unicast, allocate MPLS labels"); bgp->allocate_mpls_labels[afi][SAFI_UNICAST] = 1; bgp_recalculate_afi_safi_bestpaths(bgp, afi, SAFI_UNICAST); } if (safi == SAFI_FLOWSPEC) { /* connect to table manager */ bgp_zebra_init_tm_connect(bgp); } return ret; } static int non_peergroup_deactivate_af(struct peer *peer, afi_t afi, safi_t safi) { if (CHECK_FLAG(peer->sflags, PEER_STATUS_GROUP)) { flog_err(EC_BGP_PEER_GROUP, "%s was called for peer-group %s", __func__, peer->host); return 1; } /* Nothing to do if we've already deactivated this peer */ if (!peer->afc[afi][safi]) return 0; /* De-activate the address family configuration. */ peer->afc[afi][safi] = 0; if (peer_af_delete(peer, afi, safi) != 0) { flog_err(EC_BGP_PEER_DELETE, "couldn't delete af structure for peer %s", peer->host); return 1; } if (peer->status == Established) { if (CHECK_FLAG(peer->cap, PEER_CAP_DYNAMIC_RCV)) { peer->afc_adv[afi][safi] = 0; peer->afc_nego[afi][safi] = 0; if (peer_active_nego(peer)) { bgp_capability_send(peer, afi, safi, CAPABILITY_CODE_MP, CAPABILITY_ACTION_UNSET); bgp_clear_route(peer, afi, safi); peer->pcount[afi][safi] = 0; } else { peer->last_reset = PEER_DOWN_NEIGHBOR_DELETE; bgp_notify_send(peer, BGP_NOTIFY_CEASE, BGP_NOTIFY_CEASE_CONFIG_CHANGE); } } else { peer->last_reset = PEER_DOWN_NEIGHBOR_DELETE; bgp_notify_send(peer, BGP_NOTIFY_CEASE, BGP_NOTIFY_CEASE_CONFIG_CHANGE); } } return 0; } int peer_deactivate(struct peer *peer, afi_t afi, safi_t safi) { int ret = 0; struct peer_group *group; struct peer *tmp_peer; struct listnode *node, *nnode; struct bgp *bgp; /* Nothing to do if we've already de-activated this peer */ if (!peer->afc[afi][safi]) return ret; /* This is a peer-group so de-activate all of the members of the * peer-group as well */ if (CHECK_FLAG(peer->sflags, PEER_STATUS_GROUP)) { peer->afc[afi][safi] = 0; group = peer->group; if (peer_af_delete(peer, afi, safi) != 0) { flog_err(EC_BGP_PEER_DELETE, "couldn't delete af structure for peer %s", peer->host); } for (ALL_LIST_ELEMENTS(group->peer, node, nnode, tmp_peer)) { ret |= non_peergroup_deactivate_af(tmp_peer, afi, safi); } } else { ret |= non_peergroup_deactivate_af(peer, afi, safi); } bgp = peer->bgp; /* If this is the last peer to be deactivated for this * afi/labeled-unicast recalc bestpaths to trigger label deallocation */ if (safi == SAFI_LABELED_UNICAST && bgp->allocate_mpls_labels[afi][SAFI_UNICAST] && !bgp_afi_safi_peer_exists(bgp, afi, safi)) { if (BGP_DEBUG(zebra, ZEBRA)) zlog_info( "peer(s) are no longer active for labeled-unicast, deallocate MPLS labels"); bgp->allocate_mpls_labels[afi][SAFI_UNICAST] = 0; bgp_recalculate_afi_safi_bestpaths(bgp, afi, SAFI_UNICAST); } return ret; } int peer_afc_set(struct peer *peer, afi_t afi, safi_t safi, int enable) { if (enable) return peer_activate(peer, afi, safi); else return peer_deactivate(peer, afi, safi); } static void peer_nsf_stop(struct peer *peer) { afi_t afi; safi_t safi; UNSET_FLAG(peer->sflags, PEER_STATUS_NSF_WAIT); UNSET_FLAG(peer->sflags, PEER_STATUS_NSF_MODE); for (afi = AFI_IP; afi < AFI_MAX; afi++) for (safi = SAFI_UNICAST; safi <= SAFI_MPLS_VPN; safi++) peer->nsf[afi][safi] = 0; if (peer->t_gr_restart) { BGP_TIMER_OFF(peer->t_gr_restart); if (bgp_debug_neighbor_events(peer)) zlog_debug("%s graceful restart timer stopped", peer->host); } if (peer->t_gr_stale) { BGP_TIMER_OFF(peer->t_gr_stale); if (bgp_debug_neighbor_events(peer)) zlog_debug( "%s graceful restart stalepath timer stopped", peer->host); } bgp_clear_route_all(peer); } /* Delete peer from confguration. * * The peer is moved to a dead-end "Deleted" neighbour-state, to allow * it to "cool off" and refcounts to hit 0, at which state it is freed. * * This function /should/ take care to be idempotent, to guard against * it being called multiple times through stray events that come in * that happen to result in this function being called again. That * said, getting here for a "Deleted" peer is a bug in the neighbour * FSM. */ int peer_delete(struct peer *peer) { int i; afi_t afi; safi_t safi; struct bgp *bgp; struct bgp_filter *filter; struct listnode *pn; int accept_peer; assert(peer->status != Deleted); bgp = peer->bgp; accept_peer = CHECK_FLAG(peer->sflags, PEER_STATUS_ACCEPT_PEER); bgp_keepalives_off(peer); bgp_reads_off(peer); bgp_writes_off(peer); assert(!CHECK_FLAG(peer->thread_flags, PEER_THREAD_WRITES_ON)); assert(!CHECK_FLAG(peer->thread_flags, PEER_THREAD_READS_ON)); assert(!CHECK_FLAG(peer->thread_flags, PEER_THREAD_KEEPALIVES_ON)); if (CHECK_FLAG(peer->sflags, PEER_STATUS_NSF_WAIT)) peer_nsf_stop(peer); SET_FLAG(peer->flags, PEER_FLAG_DELETE); bgp_bfd_deregister_peer(peer); /* If this peer belongs to peer group, clear up the relationship. */ if (peer->group) { if (peer_dynamic_neighbor(peer)) peer_drop_dynamic_neighbor(peer); if ((pn = listnode_lookup(peer->group->peer, peer))) { peer = peer_unlock( peer); /* group->peer list reference */ list_delete_node(peer->group->peer, pn); } peer->group = NULL; } /* Withdraw all information from routing table. We can not use * BGP_EVENT_ADD (peer, BGP_Stop) at here. Because the event is * executed after peer structure is deleted. */ peer->last_reset = PEER_DOWN_NEIGHBOR_DELETE; bgp_stop(peer); UNSET_FLAG(peer->flags, PEER_FLAG_DELETE); if (peer->doppelganger) { peer->doppelganger->doppelganger = NULL; peer->doppelganger = NULL; } UNSET_FLAG(peer->sflags, PEER_STATUS_ACCEPT_PEER); bgp_fsm_change_status(peer, Deleted); /* Remove from NHT */ if (CHECK_FLAG(peer->flags, PEER_FLAG_CONFIG_NODE)) bgp_unlink_nexthop_by_peer(peer); /* Password configuration */ if (CHECK_FLAG(peer->flags, PEER_FLAG_PASSWORD)) { XFREE(MTYPE_PEER_PASSWORD, peer->password); if (!accept_peer && !BGP_PEER_SU_UNSPEC(peer) && !CHECK_FLAG(peer->sflags, PEER_STATUS_GROUP)) bgp_md5_unset(peer); } bgp_timer_set(peer); /* stops all timers for Deleted */ /* Delete from all peer list. */ if (!CHECK_FLAG(peer->sflags, PEER_STATUS_GROUP) && (pn = listnode_lookup(bgp->peer, peer))) { peer_unlock(peer); /* bgp peer list reference */ list_delete_node(bgp->peer, pn); hash_release(bgp->peerhash, peer); } /* Buffers. */ if (peer->ibuf) { stream_fifo_free(peer->ibuf); peer->ibuf = NULL; } if (peer->obuf) { stream_fifo_free(peer->obuf); peer->obuf = NULL; } if (peer->ibuf_work) { ringbuf_del(peer->ibuf_work); peer->ibuf_work = NULL; } if (peer->obuf_work) { stream_free(peer->obuf_work); peer->obuf_work = NULL; } if (peer->scratch) { stream_free(peer->scratch); peer->scratch = NULL; } /* Local and remote addresses. */ if (peer->su_local) { sockunion_free(peer->su_local); peer->su_local = NULL; } if (peer->su_remote) { sockunion_free(peer->su_remote); peer->su_remote = NULL; } /* Free filter related memory. */ FOREACH_AFI_SAFI (afi, safi) { filter = &peer->filter[afi][safi]; for (i = FILTER_IN; i < FILTER_MAX; i++) { if (filter->dlist[i].name) { XFREE(MTYPE_BGP_FILTER_NAME, filter->dlist[i].name); filter->dlist[i].name = NULL; } if (filter->plist[i].name) { XFREE(MTYPE_BGP_FILTER_NAME, filter->plist[i].name); filter->plist[i].name = NULL; } if (filter->aslist[i].name) { XFREE(MTYPE_BGP_FILTER_NAME, filter->aslist[i].name); filter->aslist[i].name = NULL; } } for (i = RMAP_IN; i < RMAP_MAX; i++) { if (filter->map[i].name) { XFREE(MTYPE_BGP_FILTER_NAME, filter->map[i].name); filter->map[i].name = NULL; } } if (filter->usmap.name) { XFREE(MTYPE_BGP_FILTER_NAME, filter->usmap.name); filter->usmap.name = NULL; } if (peer->default_rmap[afi][safi].name) { XFREE(MTYPE_ROUTE_MAP_NAME, peer->default_rmap[afi][safi].name); peer->default_rmap[afi][safi].name = NULL; } } FOREACH_AFI_SAFI (afi, safi) peer_af_delete(peer, afi, safi); if (peer->hostname) { XFREE(MTYPE_BGP_PEER_HOST, peer->hostname); peer->hostname = NULL; } if (peer->domainname) { XFREE(MTYPE_BGP_PEER_HOST, peer->domainname); peer->domainname = NULL; } peer_unlock(peer); /* initial reference */ return 0; } static int peer_group_cmp(struct peer_group *g1, struct peer_group *g2) { return strcmp(g1->name, g2->name); } /* Peer group cofiguration. */ static struct peer_group *peer_group_new(void) { return XCALLOC(MTYPE_PEER_GROUP, sizeof(struct peer_group)); } static void peer_group_free(struct peer_group *group) { XFREE(MTYPE_PEER_GROUP, group); } struct peer_group *peer_group_lookup(struct bgp *bgp, const char *name) { struct peer_group *group; struct listnode *node, *nnode; for (ALL_LIST_ELEMENTS(bgp->group, node, nnode, group)) { if (strcmp(group->name, name) == 0) return group; } return NULL; } struct peer_group *peer_group_get(struct bgp *bgp, const char *name) { struct peer_group *group; afi_t afi; group = peer_group_lookup(bgp, name); if (group) return group; group = peer_group_new(); group->bgp = bgp; XFREE(MTYPE_PEER_GROUP_HOST, group->name); group->name = XSTRDUP(MTYPE_PEER_GROUP_HOST, name); group->peer = list_new(); for (afi = AFI_IP; afi < AFI_MAX; afi++) group->listen_range[afi] = list_new(); group->conf = peer_new(bgp); if (!bgp_flag_check(bgp, BGP_FLAG_NO_DEFAULT_IPV4)) group->conf->afc[AFI_IP][SAFI_UNICAST] = 1; XFREE(MTYPE_BGP_PEER_HOST, group->conf->host); group->conf->host = XSTRDUP(MTYPE_BGP_PEER_HOST, name); group->conf->group = group; group->conf->as = 0; group->conf->ttl = BGP_DEFAULT_TTL; group->conf->gtsm_hops = 0; group->conf->v_routeadv = BGP_DEFAULT_EBGP_ROUTEADV; SET_FLAG(group->conf->sflags, PEER_STATUS_GROUP); listnode_add_sort(bgp->group, group); return group; } static void peer_group2peer_config_copy(struct peer_group *group, struct peer *peer) { uint32_t flags_tmp; struct peer *conf; conf = group->conf; /* remote-as */ if (conf->as) peer->as = conf->as; /* local-as */ if (!CHECK_FLAG(peer->flags_override, PEER_FLAG_LOCAL_AS)) peer->change_local_as = conf->change_local_as; /* If peer-group has configured TTL then override it */ if (conf->ttl != BGP_DEFAULT_TTL) peer->ttl = conf->ttl; /* GTSM hops */ peer->gtsm_hops = conf->gtsm_hops; /* peer flags apply */ flags_tmp = conf->flags & ~peer->flags_override; flags_tmp ^= conf->flags_invert ^ peer->flags_invert; flags_tmp &= ~peer->flags_override; UNSET_FLAG(peer->flags, ~peer->flags_override); SET_FLAG(peer->flags, flags_tmp); SET_FLAG(peer->flags_invert, conf->flags_invert); /* peer timers apply */ if (!CHECK_FLAG(peer->flags_override, PEER_FLAG_TIMER)) { PEER_ATTR_INHERIT(peer, group, holdtime); PEER_ATTR_INHERIT(peer, group, keepalive); } if (!CHECK_FLAG(peer->flags_override, PEER_FLAG_TIMER_CONNECT)) { PEER_ATTR_INHERIT(peer, group, connect); if (CHECK_FLAG(conf->flags, PEER_FLAG_TIMER_CONNECT)) peer->v_connect = conf->connect; else peer->v_connect = BGP_DEFAULT_CONNECT_RETRY; } /* advertisement-interval apply */ if (!CHECK_FLAG(peer->flags_override, PEER_FLAG_ROUTEADV)) { PEER_ATTR_INHERIT(peer, group, routeadv); if (CHECK_FLAG(conf->flags, PEER_FLAG_ROUTEADV)) peer->v_routeadv = conf->routeadv; else peer->v_routeadv = (peer_sort(peer) == BGP_PEER_IBGP) ? BGP_DEFAULT_IBGP_ROUTEADV : BGP_DEFAULT_EBGP_ROUTEADV; } /* password apply */ if (!CHECK_FLAG(peer->flags_override, PEER_FLAG_PASSWORD)) PEER_STR_ATTR_INHERIT(peer, group, password, MTYPE_PEER_PASSWORD); if (!BGP_PEER_SU_UNSPEC(peer)) bgp_md5_set(peer); /* update-source apply */ if (!CHECK_FLAG(peer->flags_override, PEER_FLAG_UPDATE_SOURCE)) { if (conf->update_source) { XFREE(MTYPE_PEER_UPDATE_SOURCE, peer->update_if); PEER_SU_ATTR_INHERIT(peer, group, update_source); } else if (conf->update_if) { sockunion_free(peer->update_source); PEER_STR_ATTR_INHERIT(peer, group, update_if, MTYPE_PEER_UPDATE_SOURCE); } } bgp_bfd_peer_group2peer_copy(conf, peer); } /* Peer group's remote AS configuration. */ int peer_group_remote_as(struct bgp *bgp, const char *group_name, as_t *as, int as_type) { struct peer_group *group; struct peer *peer; struct listnode *node, *nnode; group = peer_group_lookup(bgp, group_name); if (!group) return -1; if ((as_type == group->conf->as_type) && (group->conf->as == *as)) return 0; /* When we setup peer-group AS number all peer group member's AS number must be updated to same number. */ peer_as_change(group->conf, *as, as_type); for (ALL_LIST_ELEMENTS(group->peer, node, nnode, peer)) { if (((peer->as_type == AS_SPECIFIED) && peer->as != *as) || (peer->as_type != as_type)) peer_as_change(peer, *as, as_type); } return 0; } int peer_notify_unconfig(struct peer *peer) { if (BGP_IS_VALID_STATE_FOR_NOTIF(peer->status)) bgp_notify_send(peer, BGP_NOTIFY_CEASE, BGP_NOTIFY_CEASE_PEER_UNCONFIG); return 0; } int peer_group_notify_unconfig(struct peer_group *group) { struct peer *peer, *other; struct listnode *node, *nnode; for (ALL_LIST_ELEMENTS(group->peer, node, nnode, peer)) { other = peer->doppelganger; if (other && other->status != Deleted) { other->group = NULL; peer_notify_unconfig(other); } else peer_notify_unconfig(peer); } return 0; } int peer_group_delete(struct peer_group *group) { struct bgp *bgp; struct peer *peer; struct prefix *prefix; struct peer *other; struct listnode *node, *nnode; afi_t afi; bgp = group->bgp; for (ALL_LIST_ELEMENTS(group->peer, node, nnode, peer)) { other = peer->doppelganger; peer_delete(peer); if (other && other->status != Deleted) { other->group = NULL; peer_delete(other); } } list_delete(&group->peer); for (afi = AFI_IP; afi < AFI_MAX; afi++) { for (ALL_LIST_ELEMENTS(group->listen_range[afi], node, nnode, prefix)) { prefix_free(prefix); } list_delete(&group->listen_range[afi]); } XFREE(MTYPE_PEER_GROUP_HOST, group->name); group->name = NULL; bfd_info_free(&(group->conf->bfd_info)); group->conf->group = NULL; peer_delete(group->conf); /* Delete from all peer_group list. */ listnode_delete(bgp->group, group); peer_group_free(group); return 0; } int peer_group_remote_as_delete(struct peer_group *group) { struct peer *peer, *other; struct listnode *node, *nnode; if ((group->conf->as_type == AS_UNSPECIFIED) || ((!group->conf->as) && (group->conf->as_type == AS_SPECIFIED))) return 0; for (ALL_LIST_ELEMENTS(group->peer, node, nnode, peer)) { other = peer->doppelganger; peer_delete(peer); if (other && other->status != Deleted) { other->group = NULL; peer_delete(other); } } list_delete_all_node(group->peer); group->conf->as = 0; group->conf->as_type = AS_UNSPECIFIED; return 0; } int peer_group_listen_range_add(struct peer_group *group, struct prefix *range) { struct prefix *prefix; struct listnode *node, *nnode; afi_t afi; afi = family2afi(range->family); /* Group needs remote AS configured. */ if (group->conf->as_type == AS_UNSPECIFIED) return BGP_ERR_PEER_GROUP_NO_REMOTE_AS; /* Ensure no duplicates. Currently we don't care about overlaps. */ for (ALL_LIST_ELEMENTS(group->listen_range[afi], node, nnode, prefix)) { if (prefix_same(range, prefix)) return 0; } prefix = prefix_new(); prefix_copy(prefix, range); listnode_add(group->listen_range[afi], prefix); /* Update passwords for new ranges */ if (group->conf->password) bgp_md5_set_prefix(prefix, group->conf->password); return 0; } int peer_group_listen_range_del(struct peer_group *group, struct prefix *range) { struct prefix *prefix, prefix2; struct listnode *node, *nnode; struct peer *peer; afi_t afi; char buf[PREFIX2STR_BUFFER]; afi = family2afi(range->family); /* Identify the listen range. */ for (ALL_LIST_ELEMENTS(group->listen_range[afi], node, nnode, prefix)) { if (prefix_same(range, prefix)) break; } if (!prefix) return BGP_ERR_DYNAMIC_NEIGHBORS_RANGE_NOT_FOUND; prefix2str(prefix, buf, sizeof(buf)); /* Dispose off any dynamic neighbors that exist due to this listen range */ for (ALL_LIST_ELEMENTS(group->peer, node, nnode, peer)) { if (!peer_dynamic_neighbor(peer)) continue; sockunion2hostprefix(&peer->su, &prefix2); if (prefix_match(prefix, &prefix2)) { if (bgp_debug_neighbor_events(peer)) zlog_debug( "Deleting dynamic neighbor %s group %s upon " "delete of listen range %s", peer->host, group->name, buf); peer_delete(peer); } } /* Get rid of the listen range */ listnode_delete(group->listen_range[afi], prefix); /* Remove passwords for deleted ranges */ if (group->conf->password) bgp_md5_unset_prefix(prefix); return 0; } /* Bind specified peer to peer group. */ int peer_group_bind(struct bgp *bgp, union sockunion *su, struct peer *peer, struct peer_group *group, as_t *as) { int first_member = 0; afi_t afi; safi_t safi; /* Lookup the peer. */ if (!peer) peer = peer_lookup(bgp, su); /* The peer exist, bind it to the peer-group */ if (peer) { /* When the peer already belongs to a peer-group, check the * consistency. */ if (peer_group_active(peer)) { /* The peer is already bound to the peer-group, * nothing to do */ if (strcmp(peer->group->name, group->name) == 0) return 0; else return BGP_ERR_PEER_GROUP_CANT_CHANGE; } /* The peer has not specified a remote-as, inherit it from the * peer-group */ if (peer->as_type == AS_UNSPECIFIED) { peer->as_type = group->conf->as_type; peer->as = group->conf->as; peer->sort = group->conf->sort; } if (!group->conf->as && peer_sort(peer)) { if (peer_sort(group->conf) != BGP_PEER_INTERNAL && peer_sort(group->conf) != peer_sort(peer)) { if (as) *as = peer->as; return BGP_ERR_PEER_GROUP_PEER_TYPE_DIFFERENT; } if (peer_sort(group->conf) == BGP_PEER_INTERNAL) first_member = 1; } peer_group2peer_config_copy(group, peer); FOREACH_AFI_SAFI (afi, safi) { if (group->conf->afc[afi][safi]) { peer->afc[afi][safi] = 1; if (peer_af_find(peer, afi, safi) || peer_af_create(peer, afi, safi)) { peer_group2peer_config_copy_af( group, peer, afi, safi); } } else if (peer->afc[afi][safi]) peer_deactivate(peer, afi, safi); } if (peer->group) { assert(group && peer->group == group); } else { listnode_delete(bgp->peer, peer); peer->group = group; listnode_add_sort(bgp->peer, peer); peer = peer_lock(peer); /* group->peer list reference */ listnode_add(group->peer, peer); } if (first_member) { /* Advertisement-interval reset */ if (!CHECK_FLAG(group->conf->flags, PEER_FLAG_ROUTEADV)) { group->conf->v_routeadv = (peer_sort(group->conf) == BGP_PEER_IBGP) ? BGP_DEFAULT_IBGP_ROUTEADV : BGP_DEFAULT_EBGP_ROUTEADV; } /* ebgp-multihop reset */ if (peer_sort(group->conf) == BGP_PEER_IBGP) group->conf->ttl = MAXTTL; /* local-as reset */ if (peer_sort(group->conf) != BGP_PEER_EBGP) { group->conf->change_local_as = 0; peer_flag_unset(group->conf, PEER_FLAG_LOCAL_AS); peer_flag_unset(group->conf, PEER_FLAG_LOCAL_AS_NO_PREPEND); peer_flag_unset(group->conf, PEER_FLAG_LOCAL_AS_REPLACE_AS); } } SET_FLAG(peer->flags, PEER_FLAG_CONFIG_NODE); if (BGP_IS_VALID_STATE_FOR_NOTIF(peer->status)) { peer->last_reset = PEER_DOWN_RMAP_BIND; bgp_notify_send(peer, BGP_NOTIFY_CEASE, BGP_NOTIFY_CEASE_CONFIG_CHANGE); } else { bgp_session_reset(peer); } } /* Create a new peer. */ else { if ((group->conf->as_type == AS_SPECIFIED) && (!group->conf->as)) { return BGP_ERR_PEER_GROUP_NO_REMOTE_AS; } peer = peer_create(su, NULL, bgp, bgp->as, group->conf->as, group->conf->as_type, 0, 0, group); peer = peer_lock(peer); /* group->peer list reference */ listnode_add(group->peer, peer); peer_group2peer_config_copy(group, peer); /* If the peer-group is active for this afi/safi then activate * for this peer */ FOREACH_AFI_SAFI (afi, safi) { if (group->conf->afc[afi][safi]) { peer->afc[afi][safi] = 1; peer_af_create(peer, afi, safi); peer_group2peer_config_copy_af(group, peer, afi, safi); } else if (peer->afc[afi][safi]) peer_deactivate(peer, afi, safi); } SET_FLAG(peer->flags, PEER_FLAG_CONFIG_NODE); /* Set up peer's events and timers. */ if (peer_active(peer)) bgp_timer_set(peer); } return 0; } static int bgp_startup_timer_expire(struct thread *thread) { struct bgp *bgp; bgp = THREAD_ARG(thread); bgp->t_startup = NULL; return 0; } /* * On shutdown we call the cleanup function which * does a free of the link list nodes, free up * the data we are pointing at too. */ static void bgp_vrf_string_name_delete(void *data) { char *vname = data; XFREE(MTYPE_TMP, vname); } /* BGP instance creation by `router bgp' commands. */ static struct bgp *bgp_create(as_t *as, const char *name, enum bgp_instance_type inst_type) { struct bgp *bgp; afi_t afi; safi_t safi; if ((bgp = XCALLOC(MTYPE_BGP, sizeof(struct bgp))) == NULL) return NULL; if (BGP_DEBUG(zebra, ZEBRA)) { if (inst_type == BGP_INSTANCE_TYPE_DEFAULT) zlog_debug("Creating Default VRF, AS %u", *as); else zlog_debug("Creating %s %s, AS %u", (inst_type == BGP_INSTANCE_TYPE_VRF) ? "VRF" : "VIEW", name, *as); } /* Default the EVPN VRF to the default one */ if (inst_type == BGP_INSTANCE_TYPE_DEFAULT && !bgp_master.bgp_evpn) { bgp_lock(bgp); bm->bgp_evpn = bgp; } bgp_lock(bgp); bgp->heuristic_coalesce = true; bgp->inst_type = inst_type; bgp->vrf_id = (inst_type == BGP_INSTANCE_TYPE_DEFAULT) ? VRF_DEFAULT : VRF_UNKNOWN; bgp->peer_self = peer_new(bgp); XFREE(MTYPE_BGP_PEER_HOST, bgp->peer_self->host); bgp->peer_self->host = XSTRDUP(MTYPE_BGP_PEER_HOST, "Static announcement"); if (bgp->peer_self->hostname != NULL) { XFREE(MTYPE_BGP_PEER_HOST, bgp->peer_self->hostname); bgp->peer_self->hostname = NULL; } if (cmd_hostname_get()) bgp->peer_self->hostname = XSTRDUP(MTYPE_BGP_PEER_HOST, cmd_hostname_get()); if (bgp->peer_self->domainname != NULL) { XFREE(MTYPE_BGP_PEER_HOST, bgp->peer_self->domainname); bgp->peer_self->domainname = NULL; } if (cmd_domainname_get()) bgp->peer_self->domainname = XSTRDUP(MTYPE_BGP_PEER_HOST, cmd_domainname_get()); bgp->peer = list_new(); bgp->peer->cmp = (int (*)(void *, void *))peer_cmp; bgp->peerhash = hash_create(peer_hash_key_make, peer_hash_same, "BGP Peer Hash"); bgp->peerhash->max_size = BGP_PEER_MAX_HASH_SIZE; bgp->group = list_new(); bgp->group->cmp = (int (*)(void *, void *))peer_group_cmp; FOREACH_AFI_SAFI (afi, safi) { bgp->route[afi][safi] = bgp_table_init(bgp, afi, safi); bgp->aggregate[afi][safi] = bgp_table_init(bgp, afi, safi); bgp->rib[afi][safi] = bgp_table_init(bgp, afi, safi); /* Enable maximum-paths */ bgp_maximum_paths_set(bgp, afi, safi, BGP_PEER_EBGP, multipath_num, 0); bgp_maximum_paths_set(bgp, afi, safi, BGP_PEER_IBGP, multipath_num, 0); } bgp->v_update_delay = BGP_UPDATE_DELAY_DEF; bgp->default_local_pref = BGP_DEFAULT_LOCAL_PREF; bgp->default_subgroup_pkt_queue_max = BGP_DEFAULT_SUBGROUP_PKT_QUEUE_MAX; bgp->default_holdtime = BGP_DEFAULT_HOLDTIME; bgp->default_keepalive = BGP_DEFAULT_KEEPALIVE; bgp->restart_time = BGP_DEFAULT_RESTART_TIME; bgp->stalepath_time = BGP_DEFAULT_STALEPATH_TIME; bgp->dynamic_neighbors_limit = BGP_DYNAMIC_NEIGHBORS_LIMIT_DEFAULT; bgp->dynamic_neighbors_count = 0; bgp->ebgp_requires_policy = DEFAULT_EBGP_POLICY_DISABLED; #if DFLT_BGP_IMPORT_CHECK bgp_flag_set(bgp, BGP_FLAG_IMPORT_CHECK); #endif #if DFLT_BGP_SHOW_HOSTNAME bgp_flag_set(bgp, BGP_FLAG_SHOW_HOSTNAME); #endif #if DFLT_BGP_LOG_NEIGHBOR_CHANGES bgp_flag_set(bgp, BGP_FLAG_LOG_NEIGHBOR_CHANGES); #endif #if DFLT_BGP_DETERMINISTIC_MED bgp_flag_set(bgp, BGP_FLAG_DETERMINISTIC_MED); #endif bgp_addpath_init_bgp_data(&bgp->tx_addpath); bgp->as = *as; #if ENABLE_BGP_VNC if (inst_type != BGP_INSTANCE_TYPE_VRF) { bgp->rfapi = bgp_rfapi_new(bgp); assert(bgp->rfapi); assert(bgp->rfapi_cfg); } #endif /* ENABLE_BGP_VNC */ for (afi = AFI_IP; afi < AFI_MAX; afi++) { bgp->vpn_policy[afi].bgp = bgp; bgp->vpn_policy[afi].afi = afi; bgp->vpn_policy[afi].tovpn_label = MPLS_LABEL_NONE; bgp->vpn_policy[afi].tovpn_zebra_vrf_label_last_sent = MPLS_LABEL_NONE; bgp->vpn_policy[afi].import_vrf = list_new(); bgp->vpn_policy[afi].import_vrf->del = bgp_vrf_string_name_delete; bgp->vpn_policy[afi].export_vrf = list_new(); bgp->vpn_policy[afi].export_vrf->del = bgp_vrf_string_name_delete; } if (name) { bgp->name = XSTRDUP(MTYPE_BGP, name); } else { /* TODO - The startup timer needs to be run for the whole of BGP */ thread_add_timer(bm->master, bgp_startup_timer_expire, bgp, bgp->restart_time, &bgp->t_startup); } /* printable name we can use in debug messages */ if (inst_type == BGP_INSTANCE_TYPE_DEFAULT) { bgp->name_pretty = XSTRDUP(MTYPE_BGP, "VRF default"); } else { const char *n; int len; if (bgp->name) n = bgp->name; else n = "?"; len = 4 + 1 + strlen(n) + 1; /* "view foo\0" */ bgp->name_pretty = XCALLOC(MTYPE_BGP, len); snprintf(bgp->name_pretty, len, "%s %s", (bgp->inst_type == BGP_INSTANCE_TYPE_VRF) ? "VRF" : "VIEW", n); } atomic_store_explicit(&bgp->wpkt_quanta, BGP_WRITE_PACKET_MAX, memory_order_relaxed); atomic_store_explicit(&bgp->rpkt_quanta, BGP_READ_PACKET_MAX, memory_order_relaxed); bgp->coalesce_time = BGP_DEFAULT_SUBGROUP_COALESCE_TIME; QOBJ_REG(bgp, bgp); update_bgp_group_init(bgp); /* assign a unique rd id for auto derivation of vrf's RD */ bf_assign_index(bm->rd_idspace, bgp->vrf_rd_id); bgp->evpn_info = XCALLOC(MTYPE_BGP_EVPN_INFO, sizeof(struct bgp_evpn_info)); bgp_evpn_init(bgp); bgp_pbr_init(bgp); return bgp; } /* Return the "default VRF" instance of BGP. */ struct bgp *bgp_get_default(void) { struct bgp *bgp; struct listnode *node, *nnode; for (ALL_LIST_ELEMENTS(bm->bgp, node, nnode, bgp)) if (bgp->inst_type == BGP_INSTANCE_TYPE_DEFAULT) return bgp; return NULL; } /* Lookup BGP entry. */ struct bgp *bgp_lookup(as_t as, const char *name) { struct bgp *bgp; struct listnode *node, *nnode; for (ALL_LIST_ELEMENTS(bm->bgp, node, nnode, bgp)) if (bgp->as == as && ((bgp->name == NULL && name == NULL) || (bgp->name && name && strcmp(bgp->name, name) == 0))) return bgp; return NULL; } /* Lookup BGP structure by view name. */ struct bgp *bgp_lookup_by_name(const char *name) { struct bgp *bgp; struct listnode *node, *nnode; for (ALL_LIST_ELEMENTS(bm->bgp, node, nnode, bgp)) if ((bgp->name == NULL && name == NULL) || (bgp->name && name && strcmp(bgp->name, name) == 0)) return bgp; return NULL; } /* Lookup BGP instance based on VRF id. */ /* Note: Only to be used for incoming messages from Zebra. */ struct bgp *bgp_lookup_by_vrf_id(vrf_id_t vrf_id) { struct vrf *vrf; /* Lookup VRF (in tree) and follow link. */ vrf = vrf_lookup_by_id(vrf_id); if (!vrf) return NULL; return (vrf->info) ? (struct bgp *)vrf->info : NULL; } /* Sets the BGP instance where EVPN is enabled */ void bgp_set_evpn(struct bgp *bgp) { if (bm->bgp_evpn == bgp) return; /* First, release the reference count we hold on the instance */ if (bm->bgp_evpn) bgp_unlock(bm->bgp_evpn); bm->bgp_evpn = bgp; /* Increase the reference count on this new VRF */ if (bm->bgp_evpn) bgp_lock(bm->bgp_evpn); } /* Returns the BGP instance where EVPN is enabled, if any */ struct bgp *bgp_get_evpn(void) { return bm->bgp_evpn; } /* handle socket creation or deletion, if necessary * this is called for all new BGP instances */ int bgp_handle_socket(struct bgp *bgp, struct vrf *vrf, vrf_id_t old_vrf_id, bool create) { int ret = 0; /* Create BGP server socket, if listen mode not disabled */ if (!bgp || bgp_option_check(BGP_OPT_NO_LISTEN)) return 0; if (bgp->inst_type == BGP_INSTANCE_TYPE_VRF) { /* * suppress vrf socket */ if (create == false) { bgp_close_vrf_socket(bgp); return 0; } if (vrf == NULL) return BGP_ERR_INVALID_VALUE; /* do nothing * if vrf_id did not change */ if (vrf->vrf_id == old_vrf_id) return 0; if (old_vrf_id != VRF_UNKNOWN) { /* look for old socket. close it. */ bgp_close_vrf_socket(bgp); } /* if backend is not yet identified ( VRF_UNKNOWN) then * creation will be done later */ if (vrf->vrf_id == VRF_UNKNOWN) return 0; ret = bgp_socket(bgp, bm->port, bm->address); if (ret < 0) return BGP_ERR_INVALID_VALUE; return 0; } else return bgp_check_main_socket(create, bgp); } /* Called from VTY commands. */ int bgp_get(struct bgp **bgp_val, as_t *as, const char *name, enum bgp_instance_type inst_type) { struct bgp *bgp; struct vrf *vrf = NULL; /* Multiple instance check. */ if (name) bgp = bgp_lookup_by_name(name); else bgp = bgp_get_default(); /* Already exists. */ if (bgp) { if (bgp->as != *as) { *as = bgp->as; return BGP_ERR_INSTANCE_MISMATCH; } if (bgp->inst_type != inst_type) return BGP_ERR_INSTANCE_MISMATCH; *bgp_val = bgp; return BGP_SUCCESS; } bgp = bgp_create(as, name, inst_type); if (bgp_option_check(BGP_OPT_NO_ZEBRA) && name) bgp->vrf_id = vrf_generate_id(); bgp_router_id_set(bgp, &bgp->router_id_zebra, true); bgp_address_init(bgp); bgp_tip_hash_init(bgp); bgp_scan_init(bgp); *bgp_val = bgp; bgp->t_rmap_def_originate_eval = NULL; /* If Default instance or VRF, link to the VRF structure, if present. */ if (bgp->inst_type == BGP_INSTANCE_TYPE_DEFAULT || bgp->inst_type == BGP_INSTANCE_TYPE_VRF) { vrf = bgp_vrf_lookup_by_instance_type(bgp); if (vrf) bgp_vrf_link(bgp, vrf); } /* BGP server socket already processed if BGP instance * already part of the list */ bgp_handle_socket(bgp, vrf, VRF_UNKNOWN, true); listnode_add(bm->bgp, bgp); if (IS_BGP_INST_KNOWN_TO_ZEBRA(bgp)) { if (BGP_DEBUG(zebra, ZEBRA)) zlog_debug("%s: Registering BGP instance %s to zebra", __PRETTY_FUNCTION__, name); bgp_zebra_instance_register(bgp); } return BGP_SUCCESS; } /* * Make BGP instance "up". Applies only to VRFs (non-default) and * implies the VRF has been learnt from Zebra. */ void bgp_instance_up(struct bgp *bgp) { struct peer *peer; struct listnode *node, *next; /* Register with zebra. */ bgp_zebra_instance_register(bgp); /* Kick off any peers that may have been configured. */ for (ALL_LIST_ELEMENTS(bgp->peer, node, next, peer)) { if (!BGP_PEER_START_SUPPRESSED(peer)) BGP_EVENT_ADD(peer, BGP_Start); } /* Process any networks that have been configured. */ bgp_static_add(bgp); } /* * Make BGP instance "down". Applies only to VRFs (non-default) and * implies the VRF has been deleted by Zebra. */ void bgp_instance_down(struct bgp *bgp) { struct peer *peer; struct listnode *node; struct listnode *next; /* Stop timers. */ if (bgp->t_rmap_def_originate_eval) { BGP_TIMER_OFF(bgp->t_rmap_def_originate_eval); bgp_unlock(bgp); /* TODO - This timer is started with a lock - why? */ } /* Bring down peers, so corresponding routes are purged. */ for (ALL_LIST_ELEMENTS(bgp->peer, node, next, peer)) { if (BGP_IS_VALID_STATE_FOR_NOTIF(peer->status)) bgp_notify_send(peer, BGP_NOTIFY_CEASE, BGP_NOTIFY_CEASE_ADMIN_SHUTDOWN); else bgp_session_reset(peer); } /* Purge network and redistributed routes. */ bgp_purge_static_redist_routes(bgp); /* Cleanup registered nexthops (flags) */ bgp_cleanup_nexthops(bgp); } /* Delete BGP instance. */ int bgp_delete(struct bgp *bgp) { struct peer *peer; struct peer_group *group; struct listnode *node, *next; struct vrf *vrf; afi_t afi; int i; assert(bgp); hook_call(bgp_inst_delete, bgp); THREAD_OFF(bgp->t_startup); THREAD_OFF(bgp->t_maxmed_onstartup); THREAD_OFF(bgp->t_update_delay); THREAD_OFF(bgp->t_establish_wait); /* Set flag indicating bgp instance delete in progress */ bgp_flag_set(bgp, BGP_FLAG_DELETE_IN_PROGRESS); if (BGP_DEBUG(zebra, ZEBRA)) { if (bgp->inst_type == BGP_INSTANCE_TYPE_DEFAULT) zlog_debug("Deleting Default VRF"); else zlog_debug("Deleting %s %s", (bgp->inst_type == BGP_INSTANCE_TYPE_VRF) ? "VRF" : "VIEW", bgp->name); } /* unmap from RT list */ bgp_evpn_vrf_delete(bgp); /* unmap bgp vrf label */ vpn_leak_zebra_vrf_label_withdraw(bgp, AFI_IP); vpn_leak_zebra_vrf_label_withdraw(bgp, AFI_IP6); /* Stop timers. */ if (bgp->t_rmap_def_originate_eval) { BGP_TIMER_OFF(bgp->t_rmap_def_originate_eval); bgp_unlock(bgp); /* TODO - This timer is started with a lock - why? */ } /* Inform peers we're going down. */ for (ALL_LIST_ELEMENTS(bgp->peer, node, next, peer)) { if (BGP_IS_VALID_STATE_FOR_NOTIF(peer->status)) bgp_notify_send(peer, BGP_NOTIFY_CEASE, BGP_NOTIFY_CEASE_ADMIN_SHUTDOWN); } /* Delete static routes (networks). */ bgp_static_delete(bgp); /* Unset redistribution. */ for (afi = AFI_IP; afi < AFI_MAX; afi++) for (i = 0; i < ZEBRA_ROUTE_MAX; i++) if (i != ZEBRA_ROUTE_BGP) bgp_redistribute_unset(bgp, afi, i, 0); /* Free peers and peer-groups. */ for (ALL_LIST_ELEMENTS(bgp->group, node, next, group)) peer_group_delete(group); for (ALL_LIST_ELEMENTS(bgp->peer, node, next, peer)) peer_delete(peer); if (bgp->peer_self) { peer_delete(bgp->peer_self); bgp->peer_self = NULL; } update_bgp_group_free(bgp); /* TODO - Other memory may need to be freed - e.g., NHT */ #if ENABLE_BGP_VNC rfapi_delete(bgp); #endif bgp_cleanup_routes(bgp); for (afi = 0; afi < AFI_MAX; ++afi) { if (!bgp->vpn_policy[afi].import_redirect_rtlist) continue; ecommunity_free( &bgp->vpn_policy[afi] .import_redirect_rtlist); bgp->vpn_policy[afi].import_redirect_rtlist = NULL; } /* Deregister from Zebra, if needed */ if (IS_BGP_INST_KNOWN_TO_ZEBRA(bgp)) { if (BGP_DEBUG(zebra, ZEBRA)) zlog_debug("%s: deregistering this bgp %s instance from zebra", __PRETTY_FUNCTION__, bgp->name); bgp_zebra_instance_deregister(bgp); } /* Remove visibility via the master list - there may however still be * routes to be processed still referencing the struct bgp. */ listnode_delete(bm->bgp, bgp); /* Free interfaces in this instance. */ bgp_if_finish(bgp); vrf = bgp_vrf_lookup_by_instance_type(bgp); bgp_handle_socket(bgp, vrf, VRF_UNKNOWN, false); if (vrf) bgp_vrf_unlink(bgp, vrf); /* Update EVPN VRF pointer */ if (bm->bgp_evpn == bgp) { if (bgp->inst_type == BGP_INSTANCE_TYPE_DEFAULT) bgp_set_evpn(NULL); else bgp_set_evpn(bgp_get_default()); } thread_master_free_unused(bm->master); bgp_unlock(bgp); /* initial reference */ return 0; } void bgp_free(struct bgp *bgp) { afi_t afi; safi_t safi; struct bgp_table *table; struct bgp_node *rn; struct bgp_rmap *rmap; QOBJ_UNREG(bgp); list_delete(&bgp->group); list_delete(&bgp->peer); if (bgp->peerhash) { hash_free(bgp->peerhash); bgp->peerhash = NULL; } FOREACH_AFI_SAFI (afi, safi) { /* Special handling for 2-level routing tables. */ if (safi == SAFI_MPLS_VPN || safi == SAFI_ENCAP || safi == SAFI_EVPN) { for (rn = bgp_table_top(bgp->rib[afi][safi]); rn; rn = bgp_route_next(rn)) { table = bgp_node_get_bgp_table_info(rn); bgp_table_finish(&table); } } if (bgp->route[afi][safi]) bgp_table_finish(&bgp->route[afi][safi]); if (bgp->aggregate[afi][safi]) bgp_table_finish(&bgp->aggregate[afi][safi]); if (bgp->rib[afi][safi]) bgp_table_finish(&bgp->rib[afi][safi]); rmap = &bgp->table_map[afi][safi]; XFREE(MTYPE_ROUTE_MAP_NAME, rmap->name); } bgp_scan_finish(bgp); bgp_address_destroy(bgp); bgp_tip_hash_destroy(bgp); /* release the auto RD id */ bf_release_index(bm->rd_idspace, bgp->vrf_rd_id); bgp_evpn_cleanup(bgp); bgp_pbr_cleanup(bgp); XFREE(MTYPE_BGP_EVPN_INFO, bgp->evpn_info); for (afi = AFI_IP; afi < AFI_MAX; afi++) { vpn_policy_direction_t dir; if (bgp->vpn_policy[afi].import_vrf) list_delete(&bgp->vpn_policy[afi].import_vrf); if (bgp->vpn_policy[afi].export_vrf) list_delete(&bgp->vpn_policy[afi].export_vrf); dir = BGP_VPN_POLICY_DIR_FROMVPN; if (bgp->vpn_policy[afi].rtlist[dir]) ecommunity_free(&bgp->vpn_policy[afi].rtlist[dir]); dir = BGP_VPN_POLICY_DIR_TOVPN; if (bgp->vpn_policy[afi].rtlist[dir]) ecommunity_free(&bgp->vpn_policy[afi].rtlist[dir]); } XFREE(MTYPE_BGP, bgp->name); XFREE(MTYPE_BGP, bgp->name_pretty); XFREE(MTYPE_BGP, bgp); } struct peer *peer_lookup_by_conf_if(struct bgp *bgp, const char *conf_if) { struct peer *peer; struct listnode *node, *nnode; if (!conf_if) return NULL; if (bgp != NULL) { for (ALL_LIST_ELEMENTS(bgp->peer, node, nnode, peer)) if (peer->conf_if && !strcmp(peer->conf_if, conf_if) && !CHECK_FLAG(peer->sflags, PEER_STATUS_ACCEPT_PEER)) return peer; } else if (bm->bgp != NULL) { struct listnode *bgpnode, *nbgpnode; for (ALL_LIST_ELEMENTS(bm->bgp, bgpnode, nbgpnode, bgp)) for (ALL_LIST_ELEMENTS(bgp->peer, node, nnode, peer)) if (peer->conf_if && !strcmp(peer->conf_if, conf_if) && !CHECK_FLAG(peer->sflags, PEER_STATUS_ACCEPT_PEER)) return peer; } return NULL; } struct peer *peer_lookup_by_hostname(struct bgp *bgp, const char *hostname) { struct peer *peer; struct listnode *node, *nnode; if (!hostname) return NULL; if (bgp != NULL) { for (ALL_LIST_ELEMENTS(bgp->peer, node, nnode, peer)) if (peer->hostname && !strcmp(peer->hostname, hostname) && !CHECK_FLAG(peer->sflags, PEER_STATUS_ACCEPT_PEER)) return peer; } else if (bm->bgp != NULL) { struct listnode *bgpnode, *nbgpnode; for (ALL_LIST_ELEMENTS(bm->bgp, bgpnode, nbgpnode, bgp)) for (ALL_LIST_ELEMENTS(bgp->peer, node, nnode, peer)) if (peer->hostname && !strcmp(peer->hostname, hostname) && !CHECK_FLAG(peer->sflags, PEER_STATUS_ACCEPT_PEER)) return peer; } return NULL; } struct peer *peer_lookup(struct bgp *bgp, union sockunion *su) { struct peer *peer = NULL; struct peer tmp_peer; memset(&tmp_peer, 0, sizeof(struct peer)); /* * We do not want to find the doppelganger peer so search for the peer * in * the hash that has PEER_FLAG_CONFIG_NODE */ SET_FLAG(tmp_peer.flags, PEER_FLAG_CONFIG_NODE); tmp_peer.su = *su; if (bgp != NULL) { peer = hash_lookup(bgp->peerhash, &tmp_peer); } else if (bm->bgp != NULL) { struct listnode *bgpnode, *nbgpnode; for (ALL_LIST_ELEMENTS(bm->bgp, bgpnode, nbgpnode, bgp)) { peer = hash_lookup(bgp->peerhash, &tmp_peer); if (peer) break; } } return peer; } struct peer *peer_create_bind_dynamic_neighbor(struct bgp *bgp, union sockunion *su, struct peer_group *group) { struct peer *peer; afi_t afi; safi_t safi; /* Create peer first; we've already checked group config is valid. */ peer = peer_create(su, NULL, bgp, bgp->as, group->conf->as, group->conf->as_type, 0, 0, group); if (!peer) return NULL; /* Link to group */ peer = peer_lock(peer); listnode_add(group->peer, peer); peer_group2peer_config_copy(group, peer); /* * Bind peer for all AFs configured for the group. We don't call * peer_group_bind as that is sub-optimal and does some stuff we don't * want. */ FOREACH_AFI_SAFI (afi, safi) { if (!group->conf->afc[afi][safi]) continue; peer->afc[afi][safi] = 1; if (!peer_af_find(peer, afi, safi)) peer_af_create(peer, afi, safi); peer_group2peer_config_copy_af(group, peer, afi, safi); } /* Mark as dynamic, but also as a "config node" for other things to * work. */ SET_FLAG(peer->flags, PEER_FLAG_DYNAMIC_NEIGHBOR); SET_FLAG(peer->flags, PEER_FLAG_CONFIG_NODE); return peer; } struct prefix * peer_group_lookup_dynamic_neighbor_range(struct peer_group *group, struct prefix *prefix) { struct listnode *node, *nnode; struct prefix *range; afi_t afi; afi = family2afi(prefix->family); if (group->listen_range[afi]) for (ALL_LIST_ELEMENTS(group->listen_range[afi], node, nnode, range)) if (prefix_match(range, prefix)) return range; return NULL; } struct peer_group * peer_group_lookup_dynamic_neighbor(struct bgp *bgp, struct prefix *prefix, struct prefix **listen_range) { struct prefix *range = NULL; struct peer_group *group = NULL; struct listnode *node, *nnode; *listen_range = NULL; if (bgp != NULL) { for (ALL_LIST_ELEMENTS(bgp->group, node, nnode, group)) if ((range = peer_group_lookup_dynamic_neighbor_range( group, prefix))) break; } else if (bm->bgp != NULL) { struct listnode *bgpnode, *nbgpnode; for (ALL_LIST_ELEMENTS(bm->bgp, bgpnode, nbgpnode, bgp)) for (ALL_LIST_ELEMENTS(bgp->group, node, nnode, group)) if ((range = peer_group_lookup_dynamic_neighbor_range( group, prefix))) goto found_range; } found_range: *listen_range = range; return (group && range) ? group : NULL; } struct peer *peer_lookup_dynamic_neighbor(struct bgp *bgp, union sockunion *su) { struct peer_group *group; struct bgp *gbgp; struct peer *peer; struct prefix prefix; struct prefix *listen_range; int dncount; char buf[PREFIX2STR_BUFFER]; char buf1[PREFIX2STR_BUFFER]; sockunion2hostprefix(su, &prefix); /* See if incoming connection matches a configured listen range. */ group = peer_group_lookup_dynamic_neighbor(bgp, &prefix, &listen_range); if (!group) return NULL; gbgp = group->bgp; if (!gbgp) return NULL; prefix2str(&prefix, buf, sizeof(buf)); prefix2str(listen_range, buf1, sizeof(buf1)); if (bgp_debug_neighbor_events(NULL)) zlog_debug( "Dynamic Neighbor %s matches group %s listen range %s", buf, group->name, buf1); /* Are we within the listen limit? */ dncount = gbgp->dynamic_neighbors_count; if (dncount >= gbgp->dynamic_neighbors_limit) { if (bgp_debug_neighbor_events(NULL)) zlog_debug("Dynamic Neighbor %s rejected - at limit %d", inet_sutop(su, buf), gbgp->dynamic_neighbors_limit); return NULL; } /* Ensure group is not disabled. */ if (CHECK_FLAG(group->conf->flags, PEER_FLAG_SHUTDOWN)) { if (bgp_debug_neighbor_events(NULL)) zlog_debug( "Dynamic Neighbor %s rejected - group %s disabled", buf, group->name); return NULL; } /* Check that at least one AF is activated for the group. */ if (!peer_group_af_configured(group)) { if (bgp_debug_neighbor_events(NULL)) zlog_debug( "Dynamic Neighbor %s rejected - no AF activated for group %s", buf, group->name); return NULL; } /* Create dynamic peer and bind to associated group. */ peer = peer_create_bind_dynamic_neighbor(gbgp, su, group); assert(peer); gbgp->dynamic_neighbors_count = ++dncount; if (bgp_debug_neighbor_events(peer)) zlog_debug("%s Dynamic Neighbor added, group %s count %d", peer->host, group->name, dncount); return peer; } static void peer_drop_dynamic_neighbor(struct peer *peer) { int dncount = -1; if (peer->group->bgp) { dncount = peer->group->bgp->dynamic_neighbors_count; if (dncount) peer->group->bgp->dynamic_neighbors_count = --dncount; } if (bgp_debug_neighbor_events(peer)) zlog_debug("%s dropped from group %s, count %d", peer->host, peer->group->name, dncount); } /* If peer is configured at least one address family return 1. */ int peer_active(struct peer *peer) { if (BGP_PEER_SU_UNSPEC(peer)) return 0; if (peer->afc[AFI_IP][SAFI_UNICAST] || peer->afc[AFI_IP][SAFI_MULTICAST] || peer->afc[AFI_IP][SAFI_LABELED_UNICAST] || peer->afc[AFI_IP][SAFI_MPLS_VPN] || peer->afc[AFI_IP][SAFI_ENCAP] || peer->afc[AFI_IP][SAFI_FLOWSPEC] || peer->afc[AFI_IP6][SAFI_UNICAST] || peer->afc[AFI_IP6][SAFI_MULTICAST] || peer->afc[AFI_IP6][SAFI_LABELED_UNICAST] || peer->afc[AFI_IP6][SAFI_MPLS_VPN] || peer->afc[AFI_IP6][SAFI_ENCAP] || peer->afc[AFI_IP6][SAFI_FLOWSPEC] || peer->afc[AFI_L2VPN][SAFI_EVPN]) return 1; return 0; } /* If peer is negotiated at least one address family return 1. */ int peer_active_nego(struct peer *peer) { if (peer->afc_nego[AFI_IP][SAFI_UNICAST] || peer->afc_nego[AFI_IP][SAFI_MULTICAST] || peer->afc_nego[AFI_IP][SAFI_LABELED_UNICAST] || peer->afc_nego[AFI_IP][SAFI_MPLS_VPN] || peer->afc_nego[AFI_IP][SAFI_ENCAP] || peer->afc_nego[AFI_IP][SAFI_FLOWSPEC] || peer->afc_nego[AFI_IP6][SAFI_UNICAST] || peer->afc_nego[AFI_IP6][SAFI_MULTICAST] || peer->afc_nego[AFI_IP6][SAFI_LABELED_UNICAST] || peer->afc_nego[AFI_IP6][SAFI_MPLS_VPN] || peer->afc_nego[AFI_IP6][SAFI_ENCAP] || peer->afc_nego[AFI_IP6][SAFI_FLOWSPEC] || peer->afc_nego[AFI_L2VPN][SAFI_EVPN]) return 1; return 0; } void peer_change_action(struct peer *peer, afi_t afi, safi_t safi, enum peer_change_type type) { if (CHECK_FLAG(peer->sflags, PEER_STATUS_GROUP)) return; if (peer->status != Established) return; if (type == peer_change_reset) { /* If we're resetting session, we've to delete both peer struct */ if ((peer->doppelganger) && (peer->doppelganger->status != Deleted) && (!CHECK_FLAG(peer->doppelganger->flags, PEER_FLAG_CONFIG_NODE))) peer_delete(peer->doppelganger); bgp_notify_send(peer, BGP_NOTIFY_CEASE, BGP_NOTIFY_CEASE_CONFIG_CHANGE); } else if (type == peer_change_reset_in) { if (CHECK_FLAG(peer->cap, PEER_CAP_REFRESH_OLD_RCV) || CHECK_FLAG(peer->cap, PEER_CAP_REFRESH_NEW_RCV)) bgp_route_refresh_send(peer, afi, safi, 0, 0, 0); else { if ((peer->doppelganger) && (peer->doppelganger->status != Deleted) && (!CHECK_FLAG(peer->doppelganger->flags, PEER_FLAG_CONFIG_NODE))) peer_delete(peer->doppelganger); bgp_notify_send(peer, BGP_NOTIFY_CEASE, BGP_NOTIFY_CEASE_CONFIG_CHANGE); } } else if (type == peer_change_reset_out) { update_group_adjust_peer(peer_af_find(peer, afi, safi)); bgp_announce_route(peer, afi, safi); } } struct peer_flag_action { /* Peer's flag. */ uint32_t flag; /* This flag can be set for peer-group member. */ uint8_t not_for_member; /* Action when the flag is changed. */ enum peer_change_type type; }; static const struct peer_flag_action peer_flag_action_list[] = { {PEER_FLAG_PASSIVE, 0, peer_change_reset}, {PEER_FLAG_SHUTDOWN, 0, peer_change_reset}, {PEER_FLAG_DONT_CAPABILITY, 0, peer_change_none}, {PEER_FLAG_OVERRIDE_CAPABILITY, 0, peer_change_none}, {PEER_FLAG_STRICT_CAP_MATCH, 0, peer_change_none}, {PEER_FLAG_DYNAMIC_CAPABILITY, 0, peer_change_reset}, {PEER_FLAG_DISABLE_CONNECTED_CHECK, 0, peer_change_reset}, {PEER_FLAG_CAPABILITY_ENHE, 0, peer_change_reset}, {PEER_FLAG_ENFORCE_FIRST_AS, 0, peer_change_reset_in}, {PEER_FLAG_IFPEER_V6ONLY, 0, peer_change_reset}, {PEER_FLAG_ROUTEADV, 0, peer_change_none}, {PEER_FLAG_TIMER, 0, peer_change_none}, {PEER_FLAG_TIMER_CONNECT, 0, peer_change_none}, {PEER_FLAG_PASSWORD, 0, peer_change_none}, {PEER_FLAG_LOCAL_AS, 0, peer_change_none}, {PEER_FLAG_LOCAL_AS_NO_PREPEND, 0, peer_change_none}, {PEER_FLAG_LOCAL_AS_REPLACE_AS, 0, peer_change_none}, {PEER_FLAG_UPDATE_SOURCE, 0, peer_change_none}, {0, 0, 0}}; static const struct peer_flag_action peer_af_flag_action_list[] = { {PEER_FLAG_SEND_COMMUNITY, 1, peer_change_reset_out}, {PEER_FLAG_SEND_EXT_COMMUNITY, 1, peer_change_reset_out}, {PEER_FLAG_SEND_LARGE_COMMUNITY, 1, peer_change_reset_out}, {PEER_FLAG_NEXTHOP_SELF, 1, peer_change_reset_out}, {PEER_FLAG_REFLECTOR_CLIENT, 1, peer_change_reset}, {PEER_FLAG_RSERVER_CLIENT, 1, peer_change_reset}, {PEER_FLAG_SOFT_RECONFIG, 0, peer_change_reset_in}, {PEER_FLAG_AS_PATH_UNCHANGED, 1, peer_change_reset_out}, {PEER_FLAG_NEXTHOP_UNCHANGED, 1, peer_change_reset_out}, {PEER_FLAG_MED_UNCHANGED, 1, peer_change_reset_out}, {PEER_FLAG_DEFAULT_ORIGINATE, 0, peer_change_none}, {PEER_FLAG_REMOVE_PRIVATE_AS, 1, peer_change_reset_out}, {PEER_FLAG_ALLOWAS_IN, 0, peer_change_reset_in}, {PEER_FLAG_ALLOWAS_IN_ORIGIN, 0, peer_change_reset_in}, {PEER_FLAG_ORF_PREFIX_SM, 1, peer_change_reset}, {PEER_FLAG_ORF_PREFIX_RM, 1, peer_change_reset}, {PEER_FLAG_MAX_PREFIX, 0, peer_change_none}, {PEER_FLAG_MAX_PREFIX_WARNING, 0, peer_change_none}, {PEER_FLAG_NEXTHOP_LOCAL_UNCHANGED, 0, peer_change_reset_out}, {PEER_FLAG_FORCE_NEXTHOP_SELF, 1, peer_change_reset_out}, {PEER_FLAG_REMOVE_PRIVATE_AS_ALL, 1, peer_change_reset_out}, {PEER_FLAG_REMOVE_PRIVATE_AS_REPLACE, 1, peer_change_reset_out}, {PEER_FLAG_AS_OVERRIDE, 1, peer_change_reset_out}, {PEER_FLAG_REMOVE_PRIVATE_AS_ALL_REPLACE, 1, peer_change_reset_out}, {PEER_FLAG_WEIGHT, 0, peer_change_reset_in}, {0, 0, 0}}; /* Proper action set. */ static int peer_flag_action_set(const struct peer_flag_action *action_list, int size, struct peer_flag_action *action, uint32_t flag) { int i; int found = 0; int reset_in = 0; int reset_out = 0; const struct peer_flag_action *match = NULL; /* Check peer's frag action. */ for (i = 0; i < size; i++) { match = &action_list[i]; if (match->flag == 0) break; if (match->flag & flag) { found = 1; if (match->type == peer_change_reset_in) reset_in = 1; if (match->type == peer_change_reset_out) reset_out = 1; if (match->type == peer_change_reset) { reset_in = 1; reset_out = 1; } if (match->not_for_member) action->not_for_member = 1; } } /* Set peer clear type. */ if (reset_in && reset_out) action->type = peer_change_reset; else if (reset_in) action->type = peer_change_reset_in; else if (reset_out) action->type = peer_change_reset_out; else action->type = peer_change_none; return found; } static void peer_flag_modify_action(struct peer *peer, uint32_t flag) { if (flag == PEER_FLAG_SHUTDOWN) { if (CHECK_FLAG(peer->flags, flag)) { if (CHECK_FLAG(peer->sflags, PEER_STATUS_NSF_WAIT)) peer_nsf_stop(peer); UNSET_FLAG(peer->sflags, PEER_STATUS_PREFIX_OVERFLOW); if (peer->t_pmax_restart) { BGP_TIMER_OFF(peer->t_pmax_restart); if (bgp_debug_neighbor_events(peer)) zlog_debug( "%s Maximum-prefix restart timer canceled", peer->host); } if (CHECK_FLAG(peer->sflags, PEER_STATUS_NSF_WAIT)) peer_nsf_stop(peer); if (BGP_IS_VALID_STATE_FOR_NOTIF(peer->status)) { char *msg = peer->tx_shutdown_message; size_t msglen; if (!msg && peer_group_active(peer)) msg = peer->group->conf ->tx_shutdown_message; msglen = msg ? strlen(msg) : 0; if (msglen > 128) msglen = 128; if (msglen) { uint8_t msgbuf[129]; msgbuf[0] = msglen; memcpy(msgbuf + 1, msg, msglen); bgp_notify_send_with_data( peer, BGP_NOTIFY_CEASE, BGP_NOTIFY_CEASE_ADMIN_SHUTDOWN, msgbuf, msglen + 1); } else bgp_notify_send( peer, BGP_NOTIFY_CEASE, BGP_NOTIFY_CEASE_ADMIN_SHUTDOWN); } else bgp_session_reset(peer); } else { peer->v_start = BGP_INIT_START_TIMER; BGP_EVENT_ADD(peer, BGP_Stop); } } else if (BGP_IS_VALID_STATE_FOR_NOTIF(peer->status)) { if (flag == PEER_FLAG_DYNAMIC_CAPABILITY) peer->last_reset = PEER_DOWN_CAPABILITY_CHANGE; else if (flag == PEER_FLAG_PASSIVE) peer->last_reset = PEER_DOWN_PASSIVE_CHANGE; else if (flag == PEER_FLAG_DISABLE_CONNECTED_CHECK) peer->last_reset = PEER_DOWN_MULTIHOP_CHANGE; bgp_notify_send(peer, BGP_NOTIFY_CEASE, BGP_NOTIFY_CEASE_CONFIG_CHANGE); } else bgp_session_reset(peer); } /* Change specified peer flag. */ static int peer_flag_modify(struct peer *peer, uint32_t flag, int set) { int found; int size; bool invert, member_invert; struct peer *member; struct listnode *node, *nnode; struct peer_flag_action action; memset(&action, 0, sizeof(struct peer_flag_action)); size = sizeof peer_flag_action_list / sizeof(struct peer_flag_action); invert = CHECK_FLAG(peer->flags_invert, flag); found = peer_flag_action_set(peer_flag_action_list, size, &action, flag); /* Abort if no flag action exists. */ if (!found) return BGP_ERR_INVALID_FLAG; /* Check for flag conflict: STRICT_CAP_MATCH && OVERRIDE_CAPABILITY */ if (set && CHECK_FLAG(peer->flags | flag, PEER_FLAG_STRICT_CAP_MATCH) && CHECK_FLAG(peer->flags | flag, PEER_FLAG_OVERRIDE_CAPABILITY)) return BGP_ERR_PEER_FLAG_CONFLICT; /* Handle flag updates where desired state matches current state. */ if (!CHECK_FLAG(peer->sflags, PEER_STATUS_GROUP)) { if (set && CHECK_FLAG(peer->flags, flag)) { COND_FLAG(peer->flags_override, flag, !invert); return 0; } if (!set && !CHECK_FLAG(peer->flags, flag)) { COND_FLAG(peer->flags_override, flag, invert); return 0; } } /* Inherit from peer-group or set/unset flags accordingly. */ if (peer_group_active(peer) && set == invert) peer_flag_inherit(peer, flag); else COND_FLAG(peer->flags, flag, set); /* Check if handling a regular peer. */ if (!CHECK_FLAG(peer->sflags, PEER_STATUS_GROUP)) { /* Update flag override state accordingly. */ COND_FLAG(peer->flags_override, flag, set != invert); /* Execute flag action on peer. */ if (action.type == peer_change_reset) peer_flag_modify_action(peer, flag); /* Skip peer-group mechanics for regular peers. */ return 0; } if (set && flag == PEER_FLAG_CAPABILITY_ENHE) bgp_nht_register_enhe_capability_interfaces(peer); /* * Update peer-group members, unless they are explicitely overriding * peer-group configuration. */ for (ALL_LIST_ELEMENTS(peer->group->peer, node, nnode, member)) { /* Skip peers with overridden configuration. */ if (CHECK_FLAG(member->flags_override, flag)) continue; /* Check if only member without group is inverted. */ member_invert = CHECK_FLAG(member->flags_invert, flag) && !invert; /* Skip peers with equivalent configuration. */ if (set != member_invert && CHECK_FLAG(member->flags, flag)) continue; if (set == member_invert && !CHECK_FLAG(member->flags, flag)) continue; /* Update flag on peer-group member. */ COND_FLAG(member->flags, flag, set != member_invert); if (set && flag == PEER_FLAG_CAPABILITY_ENHE) bgp_nht_register_enhe_capability_interfaces(member); /* Execute flag action on peer-group member. */ if (action.type == peer_change_reset) peer_flag_modify_action(member, flag); } return 0; } int peer_flag_set(struct peer *peer, uint32_t flag) { return peer_flag_modify(peer, flag, 1); } int peer_flag_unset(struct peer *peer, uint32_t flag) { return peer_flag_modify(peer, flag, 0); } static int peer_af_flag_modify(struct peer *peer, afi_t afi, safi_t safi, uint32_t flag, bool set) { int found; int size; bool invert, member_invert; struct peer *member; struct listnode *node, *nnode; struct peer_flag_action action; memset(&action, 0, sizeof(struct peer_flag_action)); size = sizeof peer_af_flag_action_list / sizeof(struct peer_flag_action); invert = CHECK_FLAG(peer->af_flags_invert[afi][safi], flag); found = peer_flag_action_set(peer_af_flag_action_list, size, &action, flag); /* Abort if flag action exists. */ if (!found) return BGP_ERR_INVALID_FLAG; /* Special check for reflector client. */ if (flag & PEER_FLAG_REFLECTOR_CLIENT && peer_sort(peer) != BGP_PEER_IBGP) return BGP_ERR_NOT_INTERNAL_PEER; /* Special check for remove-private-AS. */ if (flag & PEER_FLAG_REMOVE_PRIVATE_AS && peer_sort(peer) == BGP_PEER_IBGP) return BGP_ERR_REMOVE_PRIVATE_AS; /* as-override is not allowed for IBGP peers */ if (flag & PEER_FLAG_AS_OVERRIDE && peer_sort(peer) == BGP_PEER_IBGP) return BGP_ERR_AS_OVERRIDE; /* Handle flag updates where desired state matches current state. */ if (!CHECK_FLAG(peer->sflags, PEER_STATUS_GROUP)) { if (set && CHECK_FLAG(peer->af_flags[afi][safi], flag)) { COND_FLAG(peer->af_flags_override[afi][safi], flag, !invert); return 0; } if (!set && !CHECK_FLAG(peer->af_flags[afi][safi], flag)) { COND_FLAG(peer->af_flags_override[afi][safi], flag, invert); return 0; } } /* * For EVPN we implicitly set the NEXTHOP_UNCHANGED flag, * if we are setting/unsetting flags which conflict with this flag * handle accordingly */ if (afi == AFI_L2VPN && safi == SAFI_EVPN) { if (set) { /* * if we are setting NEXTHOP_SELF, we need to unset the * NEXTHOP_UNCHANGED flag */ if (CHECK_FLAG(flag, PEER_FLAG_NEXTHOP_SELF) || CHECK_FLAG(flag, PEER_FLAG_FORCE_NEXTHOP_SELF)) UNSET_FLAG(peer->af_flags[afi][safi], PEER_FLAG_NEXTHOP_UNCHANGED); } else { /* * if we are unsetting NEXTHOP_SELF, we need to set the * NEXTHOP_UNCHANGED flag to reset the defaults for EVPN */ if (CHECK_FLAG(flag, PEER_FLAG_NEXTHOP_SELF) || CHECK_FLAG(flag, PEER_FLAG_FORCE_NEXTHOP_SELF)) SET_FLAG(peer->af_flags[afi][safi], PEER_FLAG_NEXTHOP_UNCHANGED); } } /* Inherit from peer-group or set/unset flags accordingly. */ if (peer_group_active(peer) && set == invert) peer_af_flag_inherit(peer, afi, safi, flag); else COND_FLAG(peer->af_flags[afi][safi], flag, set); /* Execute action when peer is established. */ if (!CHECK_FLAG(peer->sflags, PEER_STATUS_GROUP) && peer->status == Established) { if (!set && flag == PEER_FLAG_SOFT_RECONFIG) bgp_clear_adj_in(peer, afi, safi); else { if (flag == PEER_FLAG_REFLECTOR_CLIENT) peer->last_reset = PEER_DOWN_RR_CLIENT_CHANGE; else if (flag == PEER_FLAG_RSERVER_CLIENT) peer->last_reset = PEER_DOWN_RS_CLIENT_CHANGE; else if (flag == PEER_FLAG_ORF_PREFIX_SM) peer->last_reset = PEER_DOWN_CAPABILITY_CHANGE; else if (flag == PEER_FLAG_ORF_PREFIX_RM) peer->last_reset = PEER_DOWN_CAPABILITY_CHANGE; peer_change_action(peer, afi, safi, action.type); } } /* Check if handling a regular peer. */ if (!CHECK_FLAG(peer->sflags, PEER_STATUS_GROUP)) { COND_FLAG(peer->af_flags_override[afi][safi], flag, set != invert); } else { /* * Update peer-group members, unless they are explicitely * overriding peer-group configuration. */ for (ALL_LIST_ELEMENTS(peer->group->peer, node, nnode, member)) { /* Skip peers with overridden configuration. */ if (CHECK_FLAG(member->af_flags_override[afi][safi], flag)) continue; /* Check if only member without group is inverted. */ member_invert = CHECK_FLAG(member->af_flags_invert[afi][safi], flag) && !invert; /* Skip peers with equivalent configuration. */ if (set != member_invert && CHECK_FLAG(member->af_flags[afi][safi], flag)) continue; if (set == member_invert && !CHECK_FLAG(member->af_flags[afi][safi], flag)) continue; /* Update flag on peer-group member. */ COND_FLAG(member->af_flags[afi][safi], flag, set != member_invert); /* Execute flag action on peer-group member. */ if (member->status == Established) { if (!set && flag == PEER_FLAG_SOFT_RECONFIG) bgp_clear_adj_in(member, afi, safi); else { if (flag == PEER_FLAG_REFLECTOR_CLIENT) member->last_reset = PEER_DOWN_RR_CLIENT_CHANGE; else if (flag == PEER_FLAG_RSERVER_CLIENT) member->last_reset = PEER_DOWN_RS_CLIENT_CHANGE; else if (flag == PEER_FLAG_ORF_PREFIX_SM) member->last_reset = PEER_DOWN_CAPABILITY_CHANGE; else if (flag == PEER_FLAG_ORF_PREFIX_RM) member->last_reset = PEER_DOWN_CAPABILITY_CHANGE; peer_change_action(member, afi, safi, action.type); } } } } return 0; } int peer_af_flag_set(struct peer *peer, afi_t afi, safi_t safi, uint32_t flag) { return peer_af_flag_modify(peer, afi, safi, flag, 1); } int peer_af_flag_unset(struct peer *peer, afi_t afi, safi_t safi, uint32_t flag) { return peer_af_flag_modify(peer, afi, safi, flag, 0); } int peer_tx_shutdown_message_set(struct peer *peer, const char *msg) { XFREE(MTYPE_PEER_TX_SHUTDOWN_MSG, peer->tx_shutdown_message); peer->tx_shutdown_message = msg ? XSTRDUP(MTYPE_PEER_TX_SHUTDOWN_MSG, msg) : NULL; return 0; } int peer_tx_shutdown_message_unset(struct peer *peer) { XFREE(MTYPE_PEER_TX_SHUTDOWN_MSG, peer->tx_shutdown_message); return 0; } /* EBGP multihop configuration. */ int peer_ebgp_multihop_set(struct peer *peer, int ttl) { struct peer_group *group; struct listnode *node, *nnode; struct peer *peer1; if (peer->sort == BGP_PEER_IBGP || peer->conf_if) return 0; /* see comment in peer_ttl_security_hops_set() */ if (ttl != MAXTTL) { if (CHECK_FLAG(peer->sflags, PEER_STATUS_GROUP)) { group = peer->group; if (group->conf->gtsm_hops != 0) return BGP_ERR_NO_EBGP_MULTIHOP_WITH_TTLHACK; for (ALL_LIST_ELEMENTS(group->peer, node, nnode, peer1)) { if (peer1->sort == BGP_PEER_IBGP) continue; if (peer1->gtsm_hops != 0) return BGP_ERR_NO_EBGP_MULTIHOP_WITH_TTLHACK; } } else { if (peer->gtsm_hops != 0) return BGP_ERR_NO_EBGP_MULTIHOP_WITH_TTLHACK; } } peer->ttl = ttl; if (!CHECK_FLAG(peer->sflags, PEER_STATUS_GROUP)) { if (peer->fd >= 0 && peer->sort != BGP_PEER_IBGP) { if (BGP_IS_VALID_STATE_FOR_NOTIF(peer->status)) bgp_notify_send(peer, BGP_NOTIFY_CEASE, BGP_NOTIFY_CEASE_CONFIG_CHANGE); else bgp_session_reset(peer); } } else { group = peer->group; for (ALL_LIST_ELEMENTS(group->peer, node, nnode, peer)) { if (peer->sort == BGP_PEER_IBGP) continue; peer->ttl = group->conf->ttl; if (BGP_IS_VALID_STATE_FOR_NOTIF(peer->status)) bgp_notify_send(peer, BGP_NOTIFY_CEASE, BGP_NOTIFY_CEASE_CONFIG_CHANGE); else bgp_session_reset(peer); } } return 0; } int peer_ebgp_multihop_unset(struct peer *peer) { struct peer_group *group; struct listnode *node, *nnode; if (peer->sort == BGP_PEER_IBGP) return 0; if (peer->gtsm_hops != 0 && peer->ttl != MAXTTL) return BGP_ERR_NO_EBGP_MULTIHOP_WITH_TTLHACK; if (peer_group_active(peer)) peer->ttl = peer->group->conf->ttl; else peer->ttl = BGP_DEFAULT_TTL; if (!CHECK_FLAG(peer->sflags, PEER_STATUS_GROUP)) { if (BGP_IS_VALID_STATE_FOR_NOTIF(peer->status)) bgp_notify_send(peer, BGP_NOTIFY_CEASE, BGP_NOTIFY_CEASE_CONFIG_CHANGE); else bgp_session_reset(peer); } else { group = peer->group; for (ALL_LIST_ELEMENTS(group->peer, node, nnode, peer)) { if (peer->sort == BGP_PEER_IBGP) continue; peer->ttl = BGP_DEFAULT_TTL; if (peer->fd >= 0) { if (BGP_IS_VALID_STATE_FOR_NOTIF(peer->status)) bgp_notify_send( peer, BGP_NOTIFY_CEASE, BGP_NOTIFY_CEASE_CONFIG_CHANGE); else bgp_session_reset(peer); } } } return 0; } /* Neighbor description. */ int peer_description_set(struct peer *peer, const char *desc) { XFREE(MTYPE_PEER_DESC, peer->desc); peer->desc = XSTRDUP(MTYPE_PEER_DESC, desc); return 0; } int peer_description_unset(struct peer *peer) { XFREE(MTYPE_PEER_DESC, peer->desc); peer->desc = NULL; return 0; } /* Neighbor update-source. */ int peer_update_source_if_set(struct peer *peer, const char *ifname) { struct peer *member; struct listnode *node, *nnode; /* Set flag and configuration on peer. */ peer_flag_set(peer, PEER_FLAG_UPDATE_SOURCE); if (peer->update_if) { if (strcmp(peer->update_if, ifname) == 0) return 0; XFREE(MTYPE_PEER_UPDATE_SOURCE, peer->update_if); } peer->update_if = XSTRDUP(MTYPE_PEER_UPDATE_SOURCE, ifname); sockunion_free(peer->update_source); peer->update_source = NULL; /* Check if handling a regular peer. */ if (!CHECK_FLAG(peer->sflags, PEER_STATUS_GROUP)) { /* Send notification or reset peer depending on state. */ if (BGP_IS_VALID_STATE_FOR_NOTIF(peer->status)) { peer->last_reset = PEER_DOWN_UPDATE_SOURCE_CHANGE; bgp_notify_send(peer, BGP_NOTIFY_CEASE, BGP_NOTIFY_CEASE_CONFIG_CHANGE); } else bgp_session_reset(peer); /* Skip peer-group mechanics for regular peers. */ return 0; } /* * Set flag and configuration on all peer-group members, unless they are * explicitely overriding peer-group configuration. */ for (ALL_LIST_ELEMENTS(peer->group->peer, node, nnode, member)) { /* Skip peers with overridden configuration. */ if (CHECK_FLAG(member->flags_override, PEER_FLAG_UPDATE_SOURCE)) continue; /* Skip peers with the same configuration. */ if (member->update_if) { if (strcmp(member->update_if, ifname) == 0) continue; XFREE(MTYPE_PEER_UPDATE_SOURCE, member->update_if); } /* Set flag and configuration on peer-group member. */ SET_FLAG(member->flags, PEER_FLAG_UPDATE_SOURCE); member->update_if = XSTRDUP(MTYPE_PEER_UPDATE_SOURCE, ifname); sockunion_free(member->update_source); member->update_source = NULL; /* Send notification or reset peer depending on state. */ if (BGP_IS_VALID_STATE_FOR_NOTIF(member->status)) { member->last_reset = PEER_DOWN_UPDATE_SOURCE_CHANGE; bgp_notify_send(member, BGP_NOTIFY_CEASE, BGP_NOTIFY_CEASE_CONFIG_CHANGE); } else bgp_session_reset(member); } return 0; } int peer_update_source_addr_set(struct peer *peer, const union sockunion *su) { struct peer *member; struct listnode *node, *nnode; /* Set flag and configuration on peer. */ peer_flag_set(peer, PEER_FLAG_UPDATE_SOURCE); if (peer->update_source) { if (sockunion_cmp(peer->update_source, su) == 0) return 0; sockunion_free(peer->update_source); } peer->update_source = sockunion_dup(su); XFREE(MTYPE_PEER_UPDATE_SOURCE, peer->update_if); /* Check if handling a regular peer. */ if (!CHECK_FLAG(peer->sflags, PEER_STATUS_GROUP)) { /* Send notification or reset peer depending on state. */ if (BGP_IS_VALID_STATE_FOR_NOTIF(peer->status)) { peer->last_reset = PEER_DOWN_UPDATE_SOURCE_CHANGE; bgp_notify_send(peer, BGP_NOTIFY_CEASE, BGP_NOTIFY_CEASE_CONFIG_CHANGE); } else bgp_session_reset(peer); /* Skip peer-group mechanics for regular peers. */ return 0; } /* * Set flag and configuration on all peer-group members, unless they are * explicitely overriding peer-group configuration. */ for (ALL_LIST_ELEMENTS(peer->group->peer, node, nnode, member)) { /* Skip peers with overridden configuration. */ if (CHECK_FLAG(member->flags_override, PEER_FLAG_UPDATE_SOURCE)) continue; /* Skip peers with the same configuration. */ if (member->update_source) { if (sockunion_cmp(member->update_source, su) == 0) continue; sockunion_free(member->update_source); } /* Set flag and configuration on peer-group member. */ SET_FLAG(member->flags, PEER_FLAG_UPDATE_SOURCE); member->update_source = sockunion_dup(su); XFREE(MTYPE_PEER_UPDATE_SOURCE, member->update_if); /* Send notification or reset peer depending on state. */ if (BGP_IS_VALID_STATE_FOR_NOTIF(member->status)) { member->last_reset = PEER_DOWN_UPDATE_SOURCE_CHANGE; bgp_notify_send(member, BGP_NOTIFY_CEASE, BGP_NOTIFY_CEASE_CONFIG_CHANGE); } else bgp_session_reset(member); } return 0; } int peer_update_source_unset(struct peer *peer) { struct peer *member; struct listnode *node, *nnode; if (!CHECK_FLAG(peer->flags, PEER_FLAG_UPDATE_SOURCE)) return 0; /* Inherit configuration from peer-group if peer is member. */ if (peer_group_active(peer)) { peer_flag_inherit(peer, PEER_FLAG_UPDATE_SOURCE); PEER_SU_ATTR_INHERIT(peer, peer->group, update_source); PEER_STR_ATTR_INHERIT(peer, peer->group, update_if, MTYPE_PEER_UPDATE_SOURCE); } else { /* Otherwise remove flag and configuration from peer. */ peer_flag_unset(peer, PEER_FLAG_UPDATE_SOURCE); sockunion_free(peer->update_source); peer->update_source = NULL; XFREE(MTYPE_PEER_UPDATE_SOURCE, peer->update_if); } /* Check if handling a regular peer. */ if (!CHECK_FLAG(peer->sflags, PEER_STATUS_GROUP)) { /* Send notification or reset peer depending on state. */ if (BGP_IS_VALID_STATE_FOR_NOTIF(peer->status)) { peer->last_reset = PEER_DOWN_UPDATE_SOURCE_CHANGE; bgp_notify_send(peer, BGP_NOTIFY_CEASE, BGP_NOTIFY_CEASE_CONFIG_CHANGE); } else bgp_session_reset(peer); /* Skip peer-group mechanics for regular peers. */ return 0; } /* * Set flag and configuration on all peer-group members, unless they are * explicitely overriding peer-group configuration. */ for (ALL_LIST_ELEMENTS(peer->group->peer, node, nnode, member)) { /* Skip peers with overridden configuration. */ if (CHECK_FLAG(member->flags_override, PEER_FLAG_UPDATE_SOURCE)) continue; /* Skip peers with the same configuration. */ if (!CHECK_FLAG(member->flags, PEER_FLAG_UPDATE_SOURCE) && !member->update_source && !member->update_if) continue; /* Remove flag and configuration on peer-group member. */ UNSET_FLAG(member->flags, PEER_FLAG_UPDATE_SOURCE); sockunion_free(member->update_source); member->update_source = NULL; XFREE(MTYPE_PEER_UPDATE_SOURCE, member->update_if); /* Send notification or reset peer depending on state. */ if (BGP_IS_VALID_STATE_FOR_NOTIF(member->status)) { member->last_reset = PEER_DOWN_UPDATE_SOURCE_CHANGE; bgp_notify_send(member, BGP_NOTIFY_CEASE, BGP_NOTIFY_CEASE_CONFIG_CHANGE); } else bgp_session_reset(member); } return 0; } int peer_default_originate_set(struct peer *peer, afi_t afi, safi_t safi, const char *rmap, struct route_map *route_map) { struct peer *member; struct listnode *node, *nnode; /* Set flag and configuration on peer. */ peer_af_flag_set(peer, afi, safi, PEER_FLAG_DEFAULT_ORIGINATE); if (rmap) { if (!peer->default_rmap[afi][safi].name || strcmp(rmap, peer->default_rmap[afi][safi].name) != 0) { if (peer->default_rmap[afi][safi].name) XFREE(MTYPE_ROUTE_MAP_NAME, peer->default_rmap[afi][safi].name); route_map_counter_decrement(peer->default_rmap[afi][safi].map); peer->default_rmap[afi][safi].name = XSTRDUP(MTYPE_ROUTE_MAP_NAME, rmap); peer->default_rmap[afi][safi].map = route_map; route_map_counter_increment(route_map); } } else if (!rmap) { if (peer->default_rmap[afi][safi].name) XFREE(MTYPE_ROUTE_MAP_NAME, peer->default_rmap[afi][safi].name); route_map_counter_decrement(peer->default_rmap[afi][safi].map); peer->default_rmap[afi][safi].name = NULL; peer->default_rmap[afi][safi].map = NULL; } /* Check if handling a regular peer. */ if (!CHECK_FLAG(peer->sflags, PEER_STATUS_GROUP)) { /* Update peer route announcements. */ if (peer->status == Established && peer->afc_nego[afi][safi]) { update_group_adjust_peer(peer_af_find(peer, afi, safi)); bgp_default_originate(peer, afi, safi, 0); bgp_announce_route(peer, afi, safi); } /* Skip peer-group mechanics for regular peers. */ return 0; } /* * Set flag and configuration on all peer-group members, unless they are * explicitely overriding peer-group configuration. */ for (ALL_LIST_ELEMENTS(peer->group->peer, node, nnode, member)) { /* Skip peers with overridden configuration. */ if (CHECK_FLAG(member->af_flags_override[afi][safi], PEER_FLAG_DEFAULT_ORIGINATE)) continue; /* Set flag and configuration on peer-group member. */ SET_FLAG(member->af_flags[afi][safi], PEER_FLAG_DEFAULT_ORIGINATE); if (rmap) { if (member->default_rmap[afi][safi].name) XFREE(MTYPE_ROUTE_MAP_NAME, member->default_rmap[afi][safi].name); route_map_counter_decrement( member->default_rmap[afi][safi].map); member->default_rmap[afi][safi].name = XSTRDUP(MTYPE_ROUTE_MAP_NAME, rmap); member->default_rmap[afi][safi].map = route_map; route_map_counter_increment(route_map); } /* Update peer route announcements. */ if (member->status == Established && member->afc_nego[afi][safi]) { update_group_adjust_peer( peer_af_find(member, afi, safi)); bgp_default_originate(member, afi, safi, 0); bgp_announce_route(member, afi, safi); } } return 0; } int peer_default_originate_unset(struct peer *peer, afi_t afi, safi_t safi) { struct peer *member; struct listnode *node, *nnode; /* Inherit configuration from peer-group if peer is member. */ if (peer_group_active(peer)) { peer_af_flag_inherit(peer, afi, safi, PEER_FLAG_DEFAULT_ORIGINATE); PEER_STR_ATTR_INHERIT(peer, peer->group, default_rmap[afi][safi].name, MTYPE_ROUTE_MAP_NAME); PEER_ATTR_INHERIT(peer, peer->group, default_rmap[afi][safi].map); } else { /* Otherwise remove flag and configuration from peer. */ peer_af_flag_unset(peer, afi, safi, PEER_FLAG_DEFAULT_ORIGINATE); if (peer->default_rmap[afi][safi].name) XFREE(MTYPE_ROUTE_MAP_NAME, peer->default_rmap[afi][safi].name); route_map_counter_decrement(peer->default_rmap[afi][safi].map); peer->default_rmap[afi][safi].name = NULL; peer->default_rmap[afi][safi].map = NULL; } /* Check if handling a regular peer. */ if (!CHECK_FLAG(peer->sflags, PEER_STATUS_GROUP)) { /* Update peer route announcements. */ if (peer->status == Established && peer->afc_nego[afi][safi]) { update_group_adjust_peer(peer_af_find(peer, afi, safi)); bgp_default_originate(peer, afi, safi, 1); bgp_announce_route(peer, afi, safi); } /* Skip peer-group mechanics for regular peers. */ return 0; } /* * Remove flag and configuration from all peer-group members, unless * they are explicitely overriding peer-group configuration. */ for (ALL_LIST_ELEMENTS(peer->group->peer, node, nnode, member)) { /* Skip peers with overridden configuration. */ if (CHECK_FLAG(member->af_flags_override[afi][safi], PEER_FLAG_DEFAULT_ORIGINATE)) continue; /* Remove flag and configuration on peer-group member. */ UNSET_FLAG(peer->af_flags[afi][safi], PEER_FLAG_DEFAULT_ORIGINATE); if (peer->default_rmap[afi][safi].name) XFREE(MTYPE_ROUTE_MAP_NAME, peer->default_rmap[afi][safi].name); route_map_counter_decrement(peer->default_rmap[afi][safi].map); peer->default_rmap[afi][safi].name = NULL; peer->default_rmap[afi][safi].map = NULL; /* Update peer route announcements. */ if (peer->status == Established && peer->afc_nego[afi][safi]) { update_group_adjust_peer(peer_af_find(peer, afi, safi)); bgp_default_originate(peer, afi, safi, 1); bgp_announce_route(peer, afi, safi); } } return 0; } int peer_port_set(struct peer *peer, uint16_t port) { peer->port = port; return 0; } int peer_port_unset(struct peer *peer) { peer->port = BGP_PORT_DEFAULT; return 0; } /* * Helper function that is called after the name of the policy * being used by a peer has changed (AF specific). Automatically * initiates inbound or outbound processing as needed. */ static void peer_on_policy_change(struct peer *peer, afi_t afi, safi_t safi, int outbound) { if (outbound) { update_group_adjust_peer(peer_af_find(peer, afi, safi)); if (peer->status == Established) bgp_announce_route(peer, afi, safi); } else { if (peer->status != Established) return; if (CHECK_FLAG(peer->af_flags[afi][safi], PEER_FLAG_SOFT_RECONFIG)) bgp_soft_reconfig_in(peer, afi, safi); else if (CHECK_FLAG(peer->cap, PEER_CAP_REFRESH_OLD_RCV) || CHECK_FLAG(peer->cap, PEER_CAP_REFRESH_NEW_RCV)) bgp_route_refresh_send(peer, afi, safi, 0, 0, 0); } } /* neighbor weight. */ int peer_weight_set(struct peer *peer, afi_t afi, safi_t safi, uint16_t weight) { struct peer *member; struct listnode *node, *nnode; /* Set flag and configuration on peer. */ peer_af_flag_set(peer, afi, safi, PEER_FLAG_WEIGHT); if (peer->weight[afi][safi] != weight) { peer->weight[afi][safi] = weight; peer_on_policy_change(peer, afi, safi, 0); } /* Skip peer-group mechanics for regular peers. */ if (!CHECK_FLAG(peer->sflags, PEER_STATUS_GROUP)) return 0; /* * Set flag and configuration on all peer-group members, unless they are * explicitely overriding peer-group configuration. */ for (ALL_LIST_ELEMENTS(peer->group->peer, node, nnode, member)) { /* Skip peers with overridden configuration. */ if (CHECK_FLAG(member->af_flags_override[afi][safi], PEER_FLAG_WEIGHT)) continue; /* Set flag and configuration on peer-group member. */ SET_FLAG(member->af_flags[afi][safi], PEER_FLAG_WEIGHT); if (member->weight[afi][safi] != weight) { member->weight[afi][safi] = weight; peer_on_policy_change(member, afi, safi, 0); } } return 0; } int peer_weight_unset(struct peer *peer, afi_t afi, safi_t safi) { struct peer *member; struct listnode *node, *nnode; if (!CHECK_FLAG(peer->af_flags[afi][safi], PEER_FLAG_WEIGHT)) return 0; /* Inherit configuration from peer-group if peer is member. */ if (peer_group_active(peer)) { peer_af_flag_inherit(peer, afi, safi, PEER_FLAG_WEIGHT); PEER_ATTR_INHERIT(peer, peer->group, weight[afi][safi]); peer_on_policy_change(peer, afi, safi, 0); return 0; } /* Remove flag and configuration from peer. */ peer_af_flag_unset(peer, afi, safi, PEER_FLAG_WEIGHT); peer->weight[afi][safi] = 0; peer_on_policy_change(peer, afi, safi, 0); /* Skip peer-group mechanics for regular peers. */ if (!CHECK_FLAG(peer->sflags, PEER_STATUS_GROUP)) return 0; /* * Remove flag and configuration from all peer-group members, unless * they are explicitely overriding peer-group configuration. */ for (ALL_LIST_ELEMENTS(peer->group->peer, node, nnode, member)) { /* Skip peers with overridden configuration. */ if (CHECK_FLAG(member->af_flags_override[afi][safi], PEER_FLAG_WEIGHT)) continue; /* Skip peers where flag is already disabled. */ if (!CHECK_FLAG(member->af_flags[afi][safi], PEER_FLAG_WEIGHT)) continue; /* Remove flag and configuration on peer-group member. */ UNSET_FLAG(member->af_flags[afi][safi], PEER_FLAG_WEIGHT); member->weight[afi][safi] = 0; peer_on_policy_change(member, afi, safi, 0); } return 0; } int peer_timers_set(struct peer *peer, uint32_t keepalive, uint32_t holdtime) { struct peer *member; struct listnode *node, *nnode; if (keepalive > 65535) return BGP_ERR_INVALID_VALUE; if (holdtime > 65535) return BGP_ERR_INVALID_VALUE; if (holdtime < 3 && holdtime != 0) return BGP_ERR_INVALID_VALUE; /* Set flag and configuration on peer. */ peer_flag_set(peer, PEER_FLAG_TIMER); peer->holdtime = holdtime; peer->keepalive = (keepalive < holdtime / 3 ? keepalive : holdtime / 3); /* Skip peer-group mechanics for regular peers. */ if (!CHECK_FLAG(peer->sflags, PEER_STATUS_GROUP)) return 0; /* * Set flag and configuration on all peer-group members, unless they are * explicitely overriding peer-group configuration. */ for (ALL_LIST_ELEMENTS(peer->group->peer, node, nnode, member)) { /* Skip peers with overridden configuration. */ if (CHECK_FLAG(member->flags_override, PEER_FLAG_TIMER)) continue; /* Set flag and configuration on peer-group member. */ SET_FLAG(member->flags, PEER_FLAG_TIMER); PEER_ATTR_INHERIT(peer, peer->group, holdtime); PEER_ATTR_INHERIT(peer, peer->group, keepalive); } return 0; } int peer_timers_unset(struct peer *peer) { struct peer *member; struct listnode *node, *nnode; /* Inherit configuration from peer-group if peer is member. */ if (peer_group_active(peer)) { peer_flag_inherit(peer, PEER_FLAG_TIMER); PEER_ATTR_INHERIT(peer, peer->group, holdtime); PEER_ATTR_INHERIT(peer, peer->group, keepalive); } else { /* Otherwise remove flag and configuration from peer. */ peer_flag_unset(peer, PEER_FLAG_TIMER); peer->holdtime = 0; peer->keepalive = 0; } /* Skip peer-group mechanics for regular peers. */ if (!CHECK_FLAG(peer->sflags, PEER_STATUS_GROUP)) return 0; /* * Remove flag and configuration from all peer-group members, unless * they are explicitely overriding peer-group configuration. */ for (ALL_LIST_ELEMENTS(peer->group->peer, node, nnode, member)) { /* Skip peers with overridden configuration. */ if (CHECK_FLAG(member->flags_override, PEER_FLAG_TIMER)) continue; /* Remove flag and configuration on peer-group member. */ UNSET_FLAG(member->flags, PEER_FLAG_TIMER); member->holdtime = 0; member->keepalive = 0; } return 0; } int peer_timers_connect_set(struct peer *peer, uint32_t connect) { struct peer *member; struct listnode *node, *nnode; if (connect > 65535) return BGP_ERR_INVALID_VALUE; /* Set flag and configuration on peer. */ peer_flag_set(peer, PEER_FLAG_TIMER_CONNECT); peer->connect = connect; peer->v_connect = connect; /* Skip peer-group mechanics for regular peers. */ if (!CHECK_FLAG(peer->sflags, PEER_STATUS_GROUP)) return 0; /* * Set flag and configuration on all peer-group members, unless they are * explicitely overriding peer-group configuration. */ for (ALL_LIST_ELEMENTS(peer->group->peer, node, nnode, member)) { /* Skip peers with overridden configuration. */ if (CHECK_FLAG(member->flags_override, PEER_FLAG_TIMER_CONNECT)) continue; /* Set flag and configuration on peer-group member. */ SET_FLAG(member->flags, PEER_FLAG_TIMER_CONNECT); member->connect = connect; member->v_connect = connect; } return 0; } int peer_timers_connect_unset(struct peer *peer) { struct peer *member; struct listnode *node, *nnode; /* Inherit configuration from peer-group if peer is member. */ if (peer_group_active(peer)) { peer_flag_inherit(peer, PEER_FLAG_TIMER_CONNECT); PEER_ATTR_INHERIT(peer, peer->group, connect); } else { /* Otherwise remove flag and configuration from peer. */ peer_flag_unset(peer, PEER_FLAG_TIMER_CONNECT); peer->connect = 0; } /* Set timer with fallback to default value. */ if (peer->connect) peer->v_connect = peer->connect; else peer->v_connect = BGP_DEFAULT_CONNECT_RETRY; /* Skip peer-group mechanics for regular peers. */ if (!CHECK_FLAG(peer->sflags, PEER_STATUS_GROUP)) return 0; /* * Remove flag and configuration from all peer-group members, unless * they are explicitely overriding peer-group configuration. */ for (ALL_LIST_ELEMENTS(peer->group->peer, node, nnode, member)) { /* Skip peers with overridden configuration. */ if (CHECK_FLAG(member->flags_override, PEER_FLAG_TIMER_CONNECT)) continue; /* Remove flag and configuration on peer-group member. */ UNSET_FLAG(member->flags, PEER_FLAG_TIMER_CONNECT); member->connect = 0; member->v_connect = BGP_DEFAULT_CONNECT_RETRY; } return 0; } int peer_advertise_interval_set(struct peer *peer, uint32_t routeadv) { struct peer *member; struct listnode *node, *nnode; if (routeadv > 600) return BGP_ERR_INVALID_VALUE; /* Set flag and configuration on peer. */ peer_flag_set(peer, PEER_FLAG_ROUTEADV); peer->routeadv = routeadv; peer->v_routeadv = routeadv; /* Check if handling a regular peer. */ if (!CHECK_FLAG(peer->sflags, PEER_STATUS_GROUP)) { /* Update peer route announcements. */ update_group_adjust_peer_afs(peer); if (peer->status == Established) bgp_announce_route_all(peer); /* Skip peer-group mechanics for regular peers. */ return 0; } /* * Set flag and configuration on all peer-group members, unless they are * explicitely overriding peer-group configuration. */ for (ALL_LIST_ELEMENTS(peer->group->peer, node, nnode, member)) { /* Skip peers with overridden configuration. */ if (CHECK_FLAG(member->flags_override, PEER_FLAG_ROUTEADV)) continue; /* Set flag and configuration on peer-group member. */ SET_FLAG(member->flags, PEER_FLAG_ROUTEADV); member->routeadv = routeadv; member->v_routeadv = routeadv; /* Update peer route announcements. */ update_group_adjust_peer_afs(member); if (member->status == Established) bgp_announce_route_all(member); } return 0; } int peer_advertise_interval_unset(struct peer *peer) { struct peer *member; struct listnode *node, *nnode; /* Inherit configuration from peer-group if peer is member. */ if (peer_group_active(peer)) { peer_flag_inherit(peer, PEER_FLAG_ROUTEADV); PEER_ATTR_INHERIT(peer, peer->group, routeadv); } else { /* Otherwise remove flag and configuration from peer. */ peer_flag_unset(peer, PEER_FLAG_ROUTEADV); peer->routeadv = 0; } /* Set timer with fallback to default value. */ if (peer->routeadv) peer->v_routeadv = peer->routeadv; else peer->v_routeadv = (peer->sort == BGP_PEER_IBGP) ? BGP_DEFAULT_IBGP_ROUTEADV : BGP_DEFAULT_EBGP_ROUTEADV; /* Check if handling a regular peer. */ if (!CHECK_FLAG(peer->sflags, PEER_STATUS_GROUP)) { /* Update peer route announcements. */ update_group_adjust_peer_afs(peer); if (peer->status == Established) bgp_announce_route_all(peer); /* Skip peer-group mechanics for regular peers. */ return 0; } /* * Remove flag and configuration from all peer-group members, unless * they are explicitely overriding peer-group configuration. */ for (ALL_LIST_ELEMENTS(peer->group->peer, node, nnode, member)) { /* Skip peers with overridden configuration. */ if (CHECK_FLAG(member->flags_override, PEER_FLAG_ROUTEADV)) continue; /* Remove flag and configuration on peer-group member. */ UNSET_FLAG(member->flags, PEER_FLAG_ROUTEADV); member->routeadv = 0; member->v_routeadv = (member->sort == BGP_PEER_IBGP) ? BGP_DEFAULT_IBGP_ROUTEADV : BGP_DEFAULT_EBGP_ROUTEADV; /* Update peer route announcements. */ update_group_adjust_peer_afs(member); if (member->status == Established) bgp_announce_route_all(member); } return 0; } /* neighbor interface */ void peer_interface_set(struct peer *peer, const char *str) { XFREE(MTYPE_BGP_PEER_IFNAME, peer->ifname); peer->ifname = XSTRDUP(MTYPE_BGP_PEER_IFNAME, str); } void peer_interface_unset(struct peer *peer) { XFREE(MTYPE_BGP_PEER_IFNAME, peer->ifname); peer->ifname = NULL; } /* Allow-as in. */ int peer_allowas_in_set(struct peer *peer, afi_t afi, safi_t safi, int allow_num, int origin) { struct peer *member; struct listnode *node, *nnode; if (!origin && (allow_num < 1 || allow_num > 10)) return BGP_ERR_INVALID_VALUE; /* Set flag and configuration on peer. */ peer_af_flag_set(peer, afi, safi, PEER_FLAG_ALLOWAS_IN); if (origin) { if (peer->allowas_in[afi][safi] != 0 || !CHECK_FLAG(peer->af_flags[afi][safi], PEER_FLAG_ALLOWAS_IN_ORIGIN)) { peer_af_flag_set(peer, afi, safi, PEER_FLAG_ALLOWAS_IN_ORIGIN); peer->allowas_in[afi][safi] = 0; peer_on_policy_change(peer, afi, safi, 0); } } else { if (peer->allowas_in[afi][safi] != allow_num || CHECK_FLAG(peer->af_flags[afi][safi], PEER_FLAG_ALLOWAS_IN_ORIGIN)) { peer_af_flag_unset(peer, afi, safi, PEER_FLAG_ALLOWAS_IN_ORIGIN); peer->allowas_in[afi][safi] = allow_num; peer_on_policy_change(peer, afi, safi, 0); } } /* Skip peer-group mechanics for regular peers. */ if (!CHECK_FLAG(peer->sflags, PEER_STATUS_GROUP)) return 0; /* * Set flag and configuration on all peer-group members, unless * they are explicitely overriding peer-group configuration. */ for (ALL_LIST_ELEMENTS(peer->group->peer, node, nnode, member)) { /* Skip peers with overridden configuration. */ if (CHECK_FLAG(member->af_flags_override[afi][safi], PEER_FLAG_ALLOWAS_IN)) continue; /* Set flag and configuration on peer-group member. */ SET_FLAG(member->af_flags[afi][safi], PEER_FLAG_ALLOWAS_IN); if (origin) { if (member->allowas_in[afi][safi] != 0 || !CHECK_FLAG(member->af_flags[afi][safi], PEER_FLAG_ALLOWAS_IN_ORIGIN)) { SET_FLAG(member->af_flags[afi][safi], PEER_FLAG_ALLOWAS_IN_ORIGIN); member->allowas_in[afi][safi] = 0; peer_on_policy_change(peer, afi, safi, 0); } } else { if (member->allowas_in[afi][safi] != allow_num || CHECK_FLAG(member->af_flags[afi][safi], PEER_FLAG_ALLOWAS_IN_ORIGIN)) { UNSET_FLAG(member->af_flags[afi][safi], PEER_FLAG_ALLOWAS_IN_ORIGIN); member->allowas_in[afi][safi] = allow_num; peer_on_policy_change(peer, afi, safi, 0); } } } return 0; } int peer_allowas_in_unset(struct peer *peer, afi_t afi, safi_t safi) { struct peer *member; struct listnode *node, *nnode; /* Skip peer if flag is already disabled. */ if (!CHECK_FLAG(peer->af_flags[afi][safi], PEER_FLAG_ALLOWAS_IN)) return 0; /* Inherit configuration from peer-group if peer is member. */ if (peer_group_active(peer)) { peer_af_flag_inherit(peer, afi, safi, PEER_FLAG_ALLOWAS_IN); peer_af_flag_inherit(peer, afi, safi, PEER_FLAG_ALLOWAS_IN_ORIGIN); PEER_ATTR_INHERIT(peer, peer->group, allowas_in[afi][safi]); peer_on_policy_change(peer, afi, safi, 0); return 0; } /* Remove flag and configuration from peer. */ peer_af_flag_unset(peer, afi, safi, PEER_FLAG_ALLOWAS_IN); peer_af_flag_unset(peer, afi, safi, PEER_FLAG_ALLOWAS_IN_ORIGIN); peer->allowas_in[afi][safi] = 0; peer_on_policy_change(peer, afi, safi, 0); /* Skip peer-group mechanics if handling a regular peer. */ if (!CHECK_FLAG(peer->sflags, PEER_STATUS_GROUP)) return 0; /* * Remove flags and configuration from all peer-group members, unless * they are explicitely overriding peer-group configuration. */ for (ALL_LIST_ELEMENTS(peer->group->peer, node, nnode, member)) { /* Skip peers with overridden configuration. */ if (CHECK_FLAG(member->af_flags_override[afi][safi], PEER_FLAG_ALLOWAS_IN)) continue; /* Skip peers where flag is already disabled. */ if (!CHECK_FLAG(member->af_flags[afi][safi], PEER_FLAG_ALLOWAS_IN)) continue; /* Remove flags and configuration on peer-group member. */ UNSET_FLAG(member->af_flags[afi][safi], PEER_FLAG_ALLOWAS_IN); UNSET_FLAG(member->af_flags[afi][safi], PEER_FLAG_ALLOWAS_IN_ORIGIN); member->allowas_in[afi][safi] = 0; peer_on_policy_change(member, afi, safi, 0); } return 0; } int peer_local_as_set(struct peer *peer, as_t as, int no_prepend, int replace_as) { bool old_no_prepend, old_replace_as; struct bgp *bgp = peer->bgp; struct peer *member; struct listnode *node, *nnode; if (peer_sort(peer) != BGP_PEER_EBGP && peer_sort(peer) != BGP_PEER_INTERNAL) return BGP_ERR_LOCAL_AS_ALLOWED_ONLY_FOR_EBGP; if (bgp->as == as) return BGP_ERR_CANNOT_HAVE_LOCAL_AS_SAME_AS; if (peer->as == as) return BGP_ERR_CANNOT_HAVE_LOCAL_AS_SAME_AS_REMOTE_AS; /* Save previous flag states. */ old_no_prepend = !!CHECK_FLAG(peer->flags, PEER_FLAG_LOCAL_AS_NO_PREPEND); old_replace_as = !!CHECK_FLAG(peer->flags, PEER_FLAG_LOCAL_AS_REPLACE_AS); /* Set flag and configuration on peer. */ peer_flag_set(peer, PEER_FLAG_LOCAL_AS); peer_flag_modify(peer, PEER_FLAG_LOCAL_AS_NO_PREPEND, no_prepend); peer_flag_modify(peer, PEER_FLAG_LOCAL_AS_REPLACE_AS, replace_as); if (peer->change_local_as == as && old_no_prepend == no_prepend && old_replace_as == replace_as) return 0; peer->change_local_as = as; /* Check if handling a regular peer. */ if (!CHECK_FLAG(peer->sflags, PEER_STATUS_GROUP)) { /* Send notification or reset peer depending on state. */ if (BGP_IS_VALID_STATE_FOR_NOTIF(peer->status)) { peer->last_reset = PEER_DOWN_LOCAL_AS_CHANGE; bgp_notify_send(peer, BGP_NOTIFY_CEASE, BGP_NOTIFY_CEASE_CONFIG_CHANGE); } else bgp_session_reset(peer); /* Skip peer-group mechanics for regular peers. */ return 0; } /* * Set flag and configuration on all peer-group members, unless they are * explicitely overriding peer-group configuration. */ for (ALL_LIST_ELEMENTS(peer->group->peer, node, nnode, member)) { /* Skip peers with overridden configuration. */ if (CHECK_FLAG(member->flags_override, PEER_FLAG_LOCAL_AS)) continue; /* Skip peers with the same configuration. */ old_no_prepend = CHECK_FLAG(member->flags, PEER_FLAG_LOCAL_AS_NO_PREPEND); old_replace_as = CHECK_FLAG(member->flags, PEER_FLAG_LOCAL_AS_REPLACE_AS); if (member->change_local_as == as && CHECK_FLAG(member->flags, PEER_FLAG_LOCAL_AS) && old_no_prepend == no_prepend && old_replace_as == replace_as) continue; /* Set flag and configuration on peer-group member. */ SET_FLAG(member->flags, PEER_FLAG_LOCAL_AS); COND_FLAG(member->flags, PEER_FLAG_LOCAL_AS_NO_PREPEND, no_prepend); COND_FLAG(member->flags, PEER_FLAG_LOCAL_AS_REPLACE_AS, replace_as); member->change_local_as = as; /* Send notification or stop peer depending on state. */ if (BGP_IS_VALID_STATE_FOR_NOTIF(member->status)) { member->last_reset = PEER_DOWN_LOCAL_AS_CHANGE; bgp_notify_send(member, BGP_NOTIFY_CEASE, BGP_NOTIFY_CEASE_CONFIG_CHANGE); } else BGP_EVENT_ADD(member, BGP_Stop); } return 0; } int peer_local_as_unset(struct peer *peer) { struct peer *member; struct listnode *node, *nnode; if (!CHECK_FLAG(peer->flags, PEER_FLAG_LOCAL_AS)) return 0; /* Inherit configuration from peer-group if peer is member. */ if (peer_group_active(peer)) { peer_flag_inherit(peer, PEER_FLAG_LOCAL_AS); peer_flag_inherit(peer, PEER_FLAG_LOCAL_AS_NO_PREPEND); peer_flag_inherit(peer, PEER_FLAG_LOCAL_AS_REPLACE_AS); PEER_ATTR_INHERIT(peer, peer->group, change_local_as); } else { /* Otherwise remove flag and configuration from peer. */ peer_flag_unset(peer, PEER_FLAG_LOCAL_AS); peer_flag_unset(peer, PEER_FLAG_LOCAL_AS_NO_PREPEND); peer_flag_unset(peer, PEER_FLAG_LOCAL_AS_REPLACE_AS); peer->change_local_as = 0; } /* Check if handling a regular peer. */ if (!CHECK_FLAG(peer->sflags, PEER_STATUS_GROUP)) { /* Send notification or stop peer depending on state. */ if (BGP_IS_VALID_STATE_FOR_NOTIF(peer->status)) { peer->last_reset = PEER_DOWN_LOCAL_AS_CHANGE; bgp_notify_send(peer, BGP_NOTIFY_CEASE, BGP_NOTIFY_CEASE_CONFIG_CHANGE); } else BGP_EVENT_ADD(peer, BGP_Stop); /* Skip peer-group mechanics for regular peers. */ return 0; } /* * Remove flag and configuration from all peer-group members, unless * they are explicitely overriding peer-group configuration. */ for (ALL_LIST_ELEMENTS(peer->group->peer, node, nnode, member)) { /* Skip peers with overridden configuration. */ if (CHECK_FLAG(member->flags_override, PEER_FLAG_LOCAL_AS)) continue; /* Remove flag and configuration on peer-group member. */ UNSET_FLAG(member->flags, PEER_FLAG_LOCAL_AS); UNSET_FLAG(member->flags, PEER_FLAG_LOCAL_AS_NO_PREPEND); UNSET_FLAG(member->flags, PEER_FLAG_LOCAL_AS_REPLACE_AS); member->change_local_as = 0; /* Send notification or stop peer depending on state. */ if (BGP_IS_VALID_STATE_FOR_NOTIF(member->status)) { member->last_reset = PEER_DOWN_LOCAL_AS_CHANGE; bgp_notify_send(member, BGP_NOTIFY_CEASE, BGP_NOTIFY_CEASE_CONFIG_CHANGE); } else bgp_session_reset(member); } return 0; } /* Set password for authenticating with the peer. */ int peer_password_set(struct peer *peer, const char *password) { struct peer *member; struct listnode *node, *nnode; int len = password ? strlen(password) : 0; int ret = BGP_SUCCESS; if ((len < PEER_PASSWORD_MINLEN) || (len > PEER_PASSWORD_MAXLEN)) return BGP_ERR_INVALID_VALUE; /* Set flag and configuration on peer. */ peer_flag_set(peer, PEER_FLAG_PASSWORD); if (peer->password && strcmp(peer->password, password) == 0) return 0; XFREE(MTYPE_PEER_PASSWORD, peer->password); peer->password = XSTRDUP(MTYPE_PEER_PASSWORD, password); /* Check if handling a regular peer. */ if (!CHECK_FLAG(peer->sflags, PEER_STATUS_GROUP)) { /* Send notification or reset peer depending on state. */ if (BGP_IS_VALID_STATE_FOR_NOTIF(peer->status)) bgp_notify_send(peer, BGP_NOTIFY_CEASE, BGP_NOTIFY_CEASE_CONFIG_CHANGE); else bgp_session_reset(peer); /* * Attempt to install password on socket and skip peer-group * mechanics. */ if (BGP_PEER_SU_UNSPEC(peer)) return BGP_SUCCESS; return (bgp_md5_set(peer) >= 0) ? BGP_SUCCESS : BGP_ERR_TCPSIG_FAILED; } /* * Set flag and configuration on all peer-group members, unless they are * explicitely overriding peer-group configuration. */ for (ALL_LIST_ELEMENTS(peer->group->peer, node, nnode, member)) { /* Skip peers with overridden configuration. */ if (CHECK_FLAG(member->flags_override, PEER_FLAG_PASSWORD)) continue; /* Skip peers with the same password. */ if (member->password && strcmp(member->password, password) == 0) continue; /* Set flag and configuration on peer-group member. */ SET_FLAG(member->flags, PEER_FLAG_PASSWORD); if (member->password) XFREE(MTYPE_PEER_PASSWORD, member->password); member->password = XSTRDUP(MTYPE_PEER_PASSWORD, password); /* Send notification or reset peer depending on state. */ if (BGP_IS_VALID_STATE_FOR_NOTIF(member->status)) bgp_notify_send(member, BGP_NOTIFY_CEASE, BGP_NOTIFY_CEASE_CONFIG_CHANGE); else bgp_session_reset(member); /* Attempt to install password on socket. */ if (!BGP_PEER_SU_UNSPEC(member) && bgp_md5_set(member) < 0) ret = BGP_ERR_TCPSIG_FAILED; } /* Set flag and configuration on all peer-group listen ranges */ struct listnode *ln; struct prefix *lr; for (ALL_LIST_ELEMENTS_RO(peer->group->listen_range[AFI_IP], ln, lr)) bgp_md5_set_prefix(lr, password); for (ALL_LIST_ELEMENTS_RO(peer->group->listen_range[AFI_IP6], ln, lr)) bgp_md5_set_prefix(lr, password); return ret; } int peer_password_unset(struct peer *peer) { struct peer *member; struct listnode *node, *nnode; if (!CHECK_FLAG(peer->flags, PEER_FLAG_PASSWORD)) return 0; /* Inherit configuration from peer-group if peer is member. */ if (peer_group_active(peer)) { peer_flag_inherit(peer, PEER_FLAG_PASSWORD); PEER_STR_ATTR_INHERIT(peer, peer->group, password, MTYPE_PEER_PASSWORD); } else { /* Otherwise remove flag and configuration from peer. */ peer_flag_unset(peer, PEER_FLAG_PASSWORD); XFREE(MTYPE_PEER_PASSWORD, peer->password); } /* Check if handling a regular peer. */ if (!CHECK_FLAG(peer->sflags, PEER_STATUS_GROUP)) { /* Send notification or reset peer depending on state. */ if (BGP_IS_VALID_STATE_FOR_NOTIF(peer->status)) bgp_notify_send(peer, BGP_NOTIFY_CEASE, BGP_NOTIFY_CEASE_CONFIG_CHANGE); else bgp_session_reset(peer); /* Attempt to uninstall password on socket. */ if (!BGP_PEER_SU_UNSPEC(peer)) bgp_md5_unset(peer); /* Skip peer-group mechanics for regular peers. */ return 0; } /* * Remove flag and configuration from all peer-group members, unless * they are explicitely overriding peer-group configuration. */ for (ALL_LIST_ELEMENTS(peer->group->peer, node, nnode, member)) { /* Skip peers with overridden configuration. */ if (CHECK_FLAG(member->flags_override, PEER_FLAG_PASSWORD)) continue; /* Remove flag and configuration on peer-group member. */ UNSET_FLAG(member->flags, PEER_FLAG_PASSWORD); XFREE(MTYPE_PEER_PASSWORD, member->password); /* Send notification or reset peer depending on state. */ if (BGP_IS_VALID_STATE_FOR_NOTIF(member->status)) bgp_notify_send(member, BGP_NOTIFY_CEASE, BGP_NOTIFY_CEASE_CONFIG_CHANGE); else bgp_session_reset(member); /* Attempt to uninstall password on socket. */ if (!BGP_PEER_SU_UNSPEC(member)) bgp_md5_unset(member); } /* Set flag and configuration on all peer-group listen ranges */ struct listnode *ln; struct prefix *lr; for (ALL_LIST_ELEMENTS_RO(peer->group->listen_range[AFI_IP], ln, lr)) bgp_md5_unset_prefix(lr); for (ALL_LIST_ELEMENTS_RO(peer->group->listen_range[AFI_IP6], ln, lr)) bgp_md5_unset_prefix(lr); return 0; } /* Set distribute list to the peer. */ int peer_distribute_set(struct peer *peer, afi_t afi, safi_t safi, int direct, const char *name) { struct peer *member; struct bgp_filter *filter; struct listnode *node, *nnode; if (direct != FILTER_IN && direct != FILTER_OUT) return BGP_ERR_INVALID_VALUE; /* Set configuration on peer. */ filter = &peer->filter[afi][safi]; if (filter->plist[direct].name) return BGP_ERR_PEER_FILTER_CONFLICT; if (filter->dlist[direct].name) XFREE(MTYPE_BGP_FILTER_NAME, filter->dlist[direct].name); filter->dlist[direct].name = XSTRDUP(MTYPE_BGP_FILTER_NAME, name); filter->dlist[direct].alist = access_list_lookup(afi, name); /* Check if handling a regular peer. */ if (!CHECK_FLAG(peer->sflags, PEER_STATUS_GROUP)) { /* Set override-flag and process peer route updates. */ SET_FLAG(peer->filter_override[afi][safi][direct], PEER_FT_DISTRIBUTE_LIST); peer_on_policy_change(peer, afi, safi, (direct == FILTER_OUT) ? 1 : 0); /* Skip peer-group mechanics for regular peers. */ return 0; } /* * Set configuration on all peer-group members, un less they are * explicitely overriding peer-group configuration. */ for (ALL_LIST_ELEMENTS(peer->group->peer, node, nnode, member)) { /* Skip peers with overridden configuration. */ if (CHECK_FLAG(member->filter_override[afi][safi][direct], PEER_FT_DISTRIBUTE_LIST)) continue; /* Set configuration on peer-group member. */ filter = &member->filter[afi][safi]; if (filter->dlist[direct].name) XFREE(MTYPE_BGP_FILTER_NAME, filter->dlist[direct].name); filter->dlist[direct].name = XSTRDUP(MTYPE_BGP_FILTER_NAME, name); filter->dlist[direct].alist = access_list_lookup(afi, name); /* Process peer route updates. */ peer_on_policy_change(member, afi, safi, (direct == FILTER_OUT) ? 1 : 0); } return 0; } int peer_distribute_unset(struct peer *peer, afi_t afi, safi_t safi, int direct) { struct peer *member; struct bgp_filter *filter; struct listnode *node, *nnode; if (direct != FILTER_IN && direct != FILTER_OUT) return BGP_ERR_INVALID_VALUE; /* Unset override-flag unconditionally. */ UNSET_FLAG(peer->filter_override[afi][safi][direct], PEER_FT_DISTRIBUTE_LIST); /* Inherit configuration from peer-group if peer is member. */ if (peer_group_active(peer)) { PEER_STR_ATTR_INHERIT(peer, peer->group, filter[afi][safi].dlist[direct].name, MTYPE_BGP_FILTER_NAME); PEER_ATTR_INHERIT(peer, peer->group, filter[afi][safi].dlist[direct].alist); } else { /* Otherwise remove configuration from peer. */ filter = &peer->filter[afi][safi]; if (filter->dlist[direct].name) XFREE(MTYPE_BGP_FILTER_NAME, filter->dlist[direct].name); filter->dlist[direct].name = NULL; filter->dlist[direct].alist = NULL; } /* Check if handling a regular peer. */ if (!CHECK_FLAG(peer->sflags, PEER_STATUS_GROUP)) { /* Process peer route updates. */ peer_on_policy_change(peer, afi, safi, (direct == FILTER_OUT) ? 1 : 0); /* Skip peer-group mechanics for regular peers. */ return 0; } /* * Remove configuration on all peer-group members, unless they are * explicitely overriding peer-group configuration. */ for (ALL_LIST_ELEMENTS(peer->group->peer, node, nnode, member)) { /* Skip peers with overridden configuration. */ if (CHECK_FLAG(member->filter_override[afi][safi][direct], PEER_FT_DISTRIBUTE_LIST)) continue; /* Remove configuration on peer-group member. */ filter = &member->filter[afi][safi]; if (filter->dlist[direct].name) XFREE(MTYPE_BGP_FILTER_NAME, filter->dlist[direct].name); filter->dlist[direct].name = NULL; filter->dlist[direct].alist = NULL; /* Process peer route updates. */ peer_on_policy_change(member, afi, safi, (direct == FILTER_OUT) ? 1 : 0); } return 0; } /* Update distribute list. */ static void peer_distribute_update(struct access_list *access) { afi_t afi; safi_t safi; int direct; struct listnode *mnode, *mnnode; struct listnode *node, *nnode; struct bgp *bgp; struct peer *peer; struct peer_group *group; struct bgp_filter *filter; for (ALL_LIST_ELEMENTS(bm->bgp, mnode, mnnode, bgp)) { if (access->name) update_group_policy_update(bgp, BGP_POLICY_FILTER_LIST, access->name, 0, 0); for (ALL_LIST_ELEMENTS(bgp->peer, node, nnode, peer)) { FOREACH_AFI_SAFI (afi, safi) { filter = &peer->filter[afi][safi]; for (direct = FILTER_IN; direct < FILTER_MAX; direct++) { if (filter->dlist[direct].name) filter->dlist[direct] .alist = access_list_lookup( afi, filter->dlist[direct] .name); else filter->dlist[direct].alist = NULL; } } } for (ALL_LIST_ELEMENTS(bgp->group, node, nnode, group)) { FOREACH_AFI_SAFI (afi, safi) { filter = &group->conf->filter[afi][safi]; for (direct = FILTER_IN; direct < FILTER_MAX; direct++) { if (filter->dlist[direct].name) filter->dlist[direct] .alist = access_list_lookup( afi, filter->dlist[direct] .name); else filter->dlist[direct].alist = NULL; } } } #if ENABLE_BGP_VNC vnc_prefix_list_update(bgp); #endif } } /* Set prefix list to the peer. */ int peer_prefix_list_set(struct peer *peer, afi_t afi, safi_t safi, int direct, const char *name) { struct peer *member; struct bgp_filter *filter; struct listnode *node, *nnode; if (direct != FILTER_IN && direct != FILTER_OUT) return BGP_ERR_INVALID_VALUE; /* Set configuration on peer. */ filter = &peer->filter[afi][safi]; if (filter->dlist[direct].name) return BGP_ERR_PEER_FILTER_CONFLICT; if (filter->plist[direct].name) XFREE(MTYPE_BGP_FILTER_NAME, filter->plist[direct].name); filter->plist[direct].name = XSTRDUP(MTYPE_BGP_FILTER_NAME, name); filter->plist[direct].plist = prefix_list_lookup(afi, name); /* Check if handling a regular peer. */ if (!CHECK_FLAG(peer->sflags, PEER_STATUS_GROUP)) { /* Set override-flag and process peer route updates. */ SET_FLAG(peer->filter_override[afi][safi][direct], PEER_FT_PREFIX_LIST); peer_on_policy_change(peer, afi, safi, (direct == FILTER_OUT) ? 1 : 0); /* Skip peer-group mechanics for regular peers. */ return 0; } /* * Set configuration on all peer-group members, unless they are * explicitely overriding peer-group configuration. */ for (ALL_LIST_ELEMENTS(peer->group->peer, node, nnode, member)) { /* Skip peers with overridden configuration. */ if (CHECK_FLAG(member->filter_override[afi][safi][direct], PEER_FT_PREFIX_LIST)) continue; /* Set configuration on peer-group member. */ filter = &member->filter[afi][safi]; if (filter->plist[direct].name) XFREE(MTYPE_BGP_FILTER_NAME, filter->plist[direct].name); filter->plist[direct].name = XSTRDUP(MTYPE_BGP_FILTER_NAME, name); filter->plist[direct].plist = prefix_list_lookup(afi, name); /* Process peer route updates. */ peer_on_policy_change(member, afi, safi, (direct == FILTER_OUT) ? 1 : 0); } return 0; } int peer_prefix_list_unset(struct peer *peer, afi_t afi, safi_t safi, int direct) { struct peer *member; struct bgp_filter *filter; struct listnode *node, *nnode; if (direct != FILTER_IN && direct != FILTER_OUT) return BGP_ERR_INVALID_VALUE; /* Unset override-flag unconditionally. */ UNSET_FLAG(peer->filter_override[afi][safi][direct], PEER_FT_PREFIX_LIST); /* Inherit configuration from peer-group if peer is member. */ if (peer_group_active(peer)) { PEER_STR_ATTR_INHERIT(peer, peer->group, filter[afi][safi].plist[direct].name, MTYPE_BGP_FILTER_NAME); PEER_ATTR_INHERIT(peer, peer->group, filter[afi][safi].plist[direct].plist); } else { /* Otherwise remove configuration from peer. */ filter = &peer->filter[afi][safi]; if (filter->plist[direct].name) XFREE(MTYPE_BGP_FILTER_NAME, filter->plist[direct].name); filter->plist[direct].name = NULL; filter->plist[direct].plist = NULL; } /* Check if handling a regular peer. */ if (!CHECK_FLAG(peer->sflags, PEER_STATUS_GROUP)) { /* Process peer route updates. */ peer_on_policy_change(peer, afi, safi, (direct == FILTER_OUT) ? 1 : 0); /* Skip peer-group mechanics for regular peers. */ return 0; } /* * Remove configuration on all peer-group members, unless they are * explicitely overriding peer-group configuration. */ for (ALL_LIST_ELEMENTS(peer->group->peer, node, nnode, member)) { /* Skip peers with overridden configuration. */ if (CHECK_FLAG(member->filter_override[afi][safi][direct], PEER_FT_PREFIX_LIST)) continue; /* Remove configuration on peer-group member. */ filter = &member->filter[afi][safi]; if (filter->plist[direct].name) XFREE(MTYPE_BGP_FILTER_NAME, filter->plist[direct].name); filter->plist[direct].name = NULL; filter->plist[direct].plist = NULL; /* Process peer route updates. */ peer_on_policy_change(member, afi, safi, (direct == FILTER_OUT) ? 1 : 0); } return 0; } /* Update prefix-list list. */ static void peer_prefix_list_update(struct prefix_list *plist) { struct listnode *mnode, *mnnode; struct listnode *node, *nnode; struct bgp *bgp; struct peer *peer; struct peer_group *group; struct bgp_filter *filter; afi_t afi; safi_t safi; int direct; for (ALL_LIST_ELEMENTS(bm->bgp, mnode, mnnode, bgp)) { /* * Update the prefix-list on update groups. */ update_group_policy_update( bgp, BGP_POLICY_PREFIX_LIST, plist ? prefix_list_name(plist) : NULL, 0, 0); for (ALL_LIST_ELEMENTS(bgp->peer, node, nnode, peer)) { FOREACH_AFI_SAFI (afi, safi) { filter = &peer->filter[afi][safi]; for (direct = FILTER_IN; direct < FILTER_MAX; direct++) { if (filter->plist[direct].name) filter->plist[direct] .plist = prefix_list_lookup( afi, filter->plist[direct] .name); else filter->plist[direct].plist = NULL; } } } for (ALL_LIST_ELEMENTS(bgp->group, node, nnode, group)) { FOREACH_AFI_SAFI (afi, safi) { filter = &group->conf->filter[afi][safi]; for (direct = FILTER_IN; direct < FILTER_MAX; direct++) { if (filter->plist[direct].name) filter->plist[direct] .plist = prefix_list_lookup( afi, filter->plist[direct] .name); else filter->plist[direct].plist = NULL; } } } } } int peer_aslist_set(struct peer *peer, afi_t afi, safi_t safi, int direct, const char *name) { struct peer *member; struct bgp_filter *filter; struct listnode *node, *nnode; if (direct != FILTER_IN && direct != FILTER_OUT) return BGP_ERR_INVALID_VALUE; /* Set configuration on peer. */ filter = &peer->filter[afi][safi]; if (filter->aslist[direct].name) XFREE(MTYPE_BGP_FILTER_NAME, filter->aslist[direct].name); filter->aslist[direct].name = XSTRDUP(MTYPE_BGP_FILTER_NAME, name); filter->aslist[direct].aslist = as_list_lookup(name); /* Check if handling a regular peer. */ if (!CHECK_FLAG(peer->sflags, PEER_STATUS_GROUP)) { /* Set override-flag and process peer route updates. */ SET_FLAG(peer->filter_override[afi][safi][direct], PEER_FT_FILTER_LIST); peer_on_policy_change(peer, afi, safi, (direct == FILTER_OUT) ? 1 : 0); /* Skip peer-group mechanics for regular peers. */ return 0; } /* * Set configuration on all peer-group members, unless they are * explicitely overriding peer-group configuration. */ for (ALL_LIST_ELEMENTS(peer->group->peer, node, nnode, member)) { /* Skip peers with overridden configuration. */ if (CHECK_FLAG(member->filter_override[afi][safi][direct], PEER_FT_FILTER_LIST)) continue; /* Set configuration on peer-group member. */ filter = &member->filter[afi][safi]; if (filter->aslist[direct].name) XFREE(MTYPE_BGP_FILTER_NAME, filter->aslist[direct].name); filter->aslist[direct].name = XSTRDUP(MTYPE_BGP_FILTER_NAME, name); filter->aslist[direct].aslist = as_list_lookup(name); /* Process peer route updates. */ peer_on_policy_change(member, afi, safi, (direct == FILTER_OUT) ? 1 : 0); } return 0; } int peer_aslist_unset(struct peer *peer, afi_t afi, safi_t safi, int direct) { struct peer *member; struct bgp_filter *filter; struct listnode *node, *nnode; if (direct != FILTER_IN && direct != FILTER_OUT) return BGP_ERR_INVALID_VALUE; /* Unset override-flag unconditionally. */ UNSET_FLAG(peer->filter_override[afi][safi][direct], PEER_FT_FILTER_LIST); /* Inherit configuration from peer-group if peer is member. */ if (peer_group_active(peer)) { PEER_STR_ATTR_INHERIT(peer, peer->group, filter[afi][safi].aslist[direct].name, MTYPE_BGP_FILTER_NAME); PEER_ATTR_INHERIT(peer, peer->group, filter[afi][safi].aslist[direct].aslist); } else { /* Otherwise remove configuration from peer. */ filter = &peer->filter[afi][safi]; if (filter->aslist[direct].name) XFREE(MTYPE_BGP_FILTER_NAME, filter->aslist[direct].name); filter->aslist[direct].name = NULL; filter->aslist[direct].aslist = NULL; } /* Check if handling a regular peer. */ if (!CHECK_FLAG(peer->sflags, PEER_STATUS_GROUP)) { /* Process peer route updates. */ peer_on_policy_change(peer, afi, safi, (direct == FILTER_OUT) ? 1 : 0); /* Skip peer-group mechanics for regular peers. */ return 0; } /* * Remove configuration on all peer-group members, unless they are * explicitely overriding peer-group configuration. */ for (ALL_LIST_ELEMENTS(peer->group->peer, node, nnode, member)) { /* Skip peers with overridden configuration. */ if (CHECK_FLAG(member->filter_override[afi][safi][direct], PEER_FT_FILTER_LIST)) continue; /* Remove configuration on peer-group member. */ filter = &member->filter[afi][safi]; if (filter->aslist[direct].name) XFREE(MTYPE_BGP_FILTER_NAME, filter->aslist[direct].name); filter->aslist[direct].name = NULL; filter->aslist[direct].aslist = NULL; /* Process peer route updates. */ peer_on_policy_change(member, afi, safi, (direct == FILTER_OUT) ? 1 : 0); } return 0; } static void peer_aslist_update(const char *aslist_name) { afi_t afi; safi_t safi; int direct; struct listnode *mnode, *mnnode; struct listnode *node, *nnode; struct bgp *bgp; struct peer *peer; struct peer_group *group; struct bgp_filter *filter; for (ALL_LIST_ELEMENTS(bm->bgp, mnode, mnnode, bgp)) { update_group_policy_update(bgp, BGP_POLICY_FILTER_LIST, aslist_name, 0, 0); for (ALL_LIST_ELEMENTS(bgp->peer, node, nnode, peer)) { FOREACH_AFI_SAFI (afi, safi) { filter = &peer->filter[afi][safi]; for (direct = FILTER_IN; direct < FILTER_MAX; direct++) { if (filter->aslist[direct].name) filter->aslist[direct] .aslist = as_list_lookup( filter->aslist[direct] .name); else filter->aslist[direct].aslist = NULL; } } } for (ALL_LIST_ELEMENTS(bgp->group, node, nnode, group)) { FOREACH_AFI_SAFI (afi, safi) { filter = &group->conf->filter[afi][safi]; for (direct = FILTER_IN; direct < FILTER_MAX; direct++) { if (filter->aslist[direct].name) filter->aslist[direct] .aslist = as_list_lookup( filter->aslist[direct] .name); else filter->aslist[direct].aslist = NULL; } } } } } static void peer_aslist_add(char *aslist_name) { peer_aslist_update(aslist_name); route_map_notify_dependencies((char *)aslist_name, RMAP_EVENT_ASLIST_ADDED); } static void peer_aslist_del(const char *aslist_name) { peer_aslist_update(aslist_name); route_map_notify_dependencies(aslist_name, RMAP_EVENT_ASLIST_DELETED); } int peer_route_map_set(struct peer *peer, afi_t afi, safi_t safi, int direct, const char *name, struct route_map *route_map) { struct peer *member; struct bgp_filter *filter; struct listnode *node, *nnode; if (direct != RMAP_IN && direct != RMAP_OUT) return BGP_ERR_INVALID_VALUE; /* Set configuration on peer. */ filter = &peer->filter[afi][safi]; if (filter->map[direct].name) { /* If the neighbor is configured with the same route-map * again then, ignore the duplicate configuration. */ if (strcmp(filter->map[direct].name, name) == 0) return 0; XFREE(MTYPE_BGP_FILTER_NAME, filter->map[direct].name); } route_map_counter_decrement(filter->map[direct].map); filter->map[direct].name = XSTRDUP(MTYPE_BGP_FILTER_NAME, name); filter->map[direct].map = route_map; route_map_counter_increment(route_map); /* Check if handling a regular peer. */ if (!CHECK_FLAG(peer->sflags, PEER_STATUS_GROUP)) { /* Set override-flag and process peer route updates. */ SET_FLAG(peer->filter_override[afi][safi][direct], PEER_FT_ROUTE_MAP); peer_on_policy_change(peer, afi, safi, (direct == RMAP_OUT) ? 1 : 0); /* Skip peer-group mechanics for regular peers. */ return 0; } /* * Set configuration on all peer-group members, unless they are * explicitely overriding peer-group configuration. */ for (ALL_LIST_ELEMENTS(peer->group->peer, node, nnode, member)) { /* Skip peers with overridden configuration. */ if (CHECK_FLAG(member->filter_override[afi][safi][direct], PEER_FT_ROUTE_MAP)) continue; /* Set configuration on peer-group member. */ filter = &member->filter[afi][safi]; if (filter->map[direct].name) XFREE(MTYPE_BGP_FILTER_NAME, filter->map[direct].name); route_map_counter_decrement(filter->map[direct].map); filter->map[direct].name = XSTRDUP(MTYPE_BGP_FILTER_NAME, name); filter->map[direct].map = route_map; route_map_counter_increment(route_map); /* Process peer route updates. */ peer_on_policy_change(member, afi, safi, (direct == RMAP_OUT) ? 1 : 0); } return 0; } /* Unset route-map from the peer. */ int peer_route_map_unset(struct peer *peer, afi_t afi, safi_t safi, int direct) { struct peer *member; struct bgp_filter *filter; struct listnode *node, *nnode; if (direct != RMAP_IN && direct != RMAP_OUT) return BGP_ERR_INVALID_VALUE; /* Unset override-flag unconditionally. */ UNSET_FLAG(peer->filter_override[afi][safi][direct], PEER_FT_ROUTE_MAP); /* Inherit configuration from peer-group if peer is member. */ if (peer_group_active(peer)) { PEER_STR_ATTR_INHERIT(peer, peer->group, filter[afi][safi].map[direct].name, MTYPE_BGP_FILTER_NAME); PEER_ATTR_INHERIT(peer, peer->group, filter[afi][safi].map[direct].map); } else { /* Otherwise remove configuration from peer. */ filter = &peer->filter[afi][safi]; if (filter->map[direct].name) XFREE(MTYPE_BGP_FILTER_NAME, filter->map[direct].name); route_map_counter_decrement(filter->map[direct].map); filter->map[direct].name = NULL; filter->map[direct].map = NULL; } /* Check if handling a regular peer. */ if (!CHECK_FLAG(peer->sflags, PEER_STATUS_GROUP)) { /* Process peer route updates. */ peer_on_policy_change(peer, afi, safi, (direct == RMAP_OUT) ? 1 : 0); /* Skip peer-group mechanics for regular peers. */ return 0; } /* * Remove configuration on all peer-group members, unless they are * explicitely overriding peer-group configuration. */ for (ALL_LIST_ELEMENTS(peer->group->peer, node, nnode, member)) { /* Skip peers with overridden configuration. */ if (CHECK_FLAG(member->filter_override[afi][safi][direct], PEER_FT_ROUTE_MAP)) continue; /* Remove configuration on peer-group member. */ filter = &member->filter[afi][safi]; if (filter->map[direct].name) XFREE(MTYPE_BGP_FILTER_NAME, filter->map[direct].name); route_map_counter_decrement(filter->map[direct].map); filter->map[direct].name = NULL; filter->map[direct].map = NULL; /* Process peer route updates. */ peer_on_policy_change(member, afi, safi, (direct == RMAP_OUT) ? 1 : 0); } return 0; } /* Set unsuppress-map to the peer. */ int peer_unsuppress_map_set(struct peer *peer, afi_t afi, safi_t safi, const char *name, struct route_map *route_map) { struct peer *member; struct bgp_filter *filter; struct listnode *node, *nnode; /* Set configuration on peer. */ filter = &peer->filter[afi][safi]; if (filter->usmap.name) XFREE(MTYPE_BGP_FILTER_NAME, filter->usmap.name); route_map_counter_decrement(filter->usmap.map); filter->usmap.name = XSTRDUP(MTYPE_BGP_FILTER_NAME, name); filter->usmap.map = route_map; route_map_counter_increment(route_map); /* Check if handling a regular peer. */ if (!CHECK_FLAG(peer->sflags, PEER_STATUS_GROUP)) { /* Set override-flag and process peer route updates. */ SET_FLAG(peer->filter_override[afi][safi][0], PEER_FT_UNSUPPRESS_MAP); peer_on_policy_change(peer, afi, safi, 1); /* Skip peer-group mechanics for regular peers. */ return 0; } /* * Set configuration on all peer-group members, unless they are * explicitely overriding peer-group configuration. */ for (ALL_LIST_ELEMENTS(peer->group->peer, node, nnode, member)) { /* Skip peers with overridden configuration. */ if (CHECK_FLAG(member->filter_override[afi][safi][0], PEER_FT_UNSUPPRESS_MAP)) continue; /* Set configuration on peer-group member. */ filter = &member->filter[afi][safi]; if (filter->usmap.name) XFREE(MTYPE_BGP_FILTER_NAME, filter->usmap.name); route_map_counter_decrement(filter->usmap.map); filter->usmap.name = XSTRDUP(MTYPE_BGP_FILTER_NAME, name); filter->usmap.map = route_map; route_map_counter_increment(route_map); /* Process peer route updates. */ peer_on_policy_change(member, afi, safi, 1); } return 0; } /* Unset route-map from the peer. */ int peer_unsuppress_map_unset(struct peer *peer, afi_t afi, safi_t safi) { struct peer *member; struct bgp_filter *filter; struct listnode *node, *nnode; /* Unset override-flag unconditionally. */ UNSET_FLAG(peer->filter_override[afi][safi][0], PEER_FT_UNSUPPRESS_MAP); /* Inherit configuration from peer-group if peer is member. */ if (peer_group_active(peer)) { PEER_STR_ATTR_INHERIT(peer, peer->group, filter[afi][safi].usmap.name, MTYPE_BGP_FILTER_NAME); PEER_ATTR_INHERIT(peer, peer->group, filter[afi][safi].usmap.map); } else { /* Otherwise remove configuration from peer. */ filter = &peer->filter[afi][safi]; if (filter->usmap.name) XFREE(MTYPE_BGP_FILTER_NAME, filter->usmap.name); route_map_counter_decrement(filter->usmap.map); filter->usmap.name = NULL; filter->usmap.map = NULL; } /* Check if handling a regular peer. */ if (!CHECK_FLAG(peer->sflags, PEER_STATUS_GROUP)) { /* Process peer route updates. */ peer_on_policy_change(peer, afi, safi, 1); /* Skip peer-group mechanics for regular peers. */ return 0; } /* * Remove configuration on all peer-group members, unless they are * explicitely overriding peer-group configuration. */ for (ALL_LIST_ELEMENTS(peer->group->peer, node, nnode, member)) { /* Skip peers with overridden configuration. */ if (CHECK_FLAG(member->filter_override[afi][safi][0], PEER_FT_UNSUPPRESS_MAP)) continue; /* Remove configuration on peer-group member. */ filter = &member->filter[afi][safi]; if (filter->usmap.name) XFREE(MTYPE_BGP_FILTER_NAME, filter->usmap.name); route_map_counter_decrement(filter->usmap.map); filter->usmap.name = NULL; filter->usmap.map = NULL; /* Process peer route updates. */ peer_on_policy_change(member, afi, safi, 1); } return 0; } int peer_maximum_prefix_set(struct peer *peer, afi_t afi, safi_t safi, uint32_t max, uint8_t threshold, int warning, uint16_t restart) { struct peer *member; struct listnode *node, *nnode; /* Set flags and configuration on peer. */ peer_af_flag_set(peer, afi, safi, PEER_FLAG_MAX_PREFIX); if (warning) peer_af_flag_set(peer, afi, safi, PEER_FLAG_MAX_PREFIX_WARNING); else peer_af_flag_unset(peer, afi, safi, PEER_FLAG_MAX_PREFIX_WARNING); peer->pmax[afi][safi] = max; peer->pmax_threshold[afi][safi] = threshold; peer->pmax_restart[afi][safi] = restart; /* Check if handling a regular peer. */ if (!CHECK_FLAG(peer->sflags, PEER_STATUS_GROUP)) { /* Re-check if peer violates maximum-prefix. */ if ((peer->status == Established) && (peer->afc[afi][safi])) bgp_maximum_prefix_overflow(peer, afi, safi, 1); /* Skip peer-group mechanics for regular peers. */ return 0; } /* * Set flags and configuration on all peer-group members, unless they * are explicitely overriding peer-group configuration. */ for (ALL_LIST_ELEMENTS(peer->group->peer, node, nnode, member)) { /* Skip peers with overridden configuration. */ if (CHECK_FLAG(member->af_flags_override[afi][safi], PEER_FLAG_MAX_PREFIX)) continue; /* Set flag and configuration on peer-group member. */ member->pmax[afi][safi] = max; member->pmax_threshold[afi][safi] = threshold; member->pmax_restart[afi][safi] = restart; if (warning) SET_FLAG(member->af_flags[afi][safi], PEER_FLAG_MAX_PREFIX_WARNING); else UNSET_FLAG(member->af_flags[afi][safi], PEER_FLAG_MAX_PREFIX_WARNING); /* Re-check if peer violates maximum-prefix. */ if ((member->status == Established) && (member->afc[afi][safi])) bgp_maximum_prefix_overflow(member, afi, safi, 1); } return 0; } int peer_maximum_prefix_unset(struct peer *peer, afi_t afi, safi_t safi) { /* Inherit configuration from peer-group if peer is member. */ if (peer_group_active(peer)) { peer_af_flag_inherit(peer, afi, safi, PEER_FLAG_MAX_PREFIX); peer_af_flag_inherit(peer, afi, safi, PEER_FLAG_MAX_PREFIX_WARNING); PEER_ATTR_INHERIT(peer, peer->group, pmax[afi][safi]); PEER_ATTR_INHERIT(peer, peer->group, pmax_threshold[afi][safi]); PEER_ATTR_INHERIT(peer, peer->group, pmax_restart[afi][safi]); return 0; } /* Remove flags and configuration from peer. */ peer_af_flag_unset(peer, afi, safi, PEER_FLAG_MAX_PREFIX); peer_af_flag_unset(peer, afi, safi, PEER_FLAG_MAX_PREFIX_WARNING); peer->pmax[afi][safi] = 0; peer->pmax_threshold[afi][safi] = 0; peer->pmax_restart[afi][safi] = 0; /* * Remove flags and configuration from all peer-group members, unless * they are explicitely overriding peer-group configuration. */ if (CHECK_FLAG(peer->sflags, PEER_STATUS_GROUP)) { struct peer *member; struct listnode *node; for (ALL_LIST_ELEMENTS_RO(peer->group->peer, node, member)) { /* Skip peers with overridden configuration. */ if (CHECK_FLAG(member->af_flags_override[afi][safi], PEER_FLAG_MAX_PREFIX)) continue; /* Remove flag and configuration on peer-group member. */ UNSET_FLAG(member->af_flags[afi][safi], PEER_FLAG_MAX_PREFIX); UNSET_FLAG(member->af_flags[afi][safi], PEER_FLAG_MAX_PREFIX_WARNING); member->pmax[afi][safi] = 0; member->pmax_threshold[afi][safi] = 0; member->pmax_restart[afi][safi] = 0; } } return 0; } int is_ebgp_multihop_configured(struct peer *peer) { struct peer_group *group; struct listnode *node, *nnode; struct peer *peer1; if (CHECK_FLAG(peer->sflags, PEER_STATUS_GROUP)) { group = peer->group; if ((peer_sort(peer) != BGP_PEER_IBGP) && (group->conf->ttl != 1)) return 1; for (ALL_LIST_ELEMENTS(group->peer, node, nnode, peer1)) { if ((peer_sort(peer1) != BGP_PEER_IBGP) && (peer1->ttl != 1)) return 1; } } else { if ((peer_sort(peer) != BGP_PEER_IBGP) && (peer->ttl != 1)) return 1; } return 0; } /* Set # of hops between us and BGP peer. */ int peer_ttl_security_hops_set(struct peer *peer, int gtsm_hops) { struct peer_group *group; struct listnode *node, *nnode; int ret; zlog_debug("peer_ttl_security_hops_set: set gtsm_hops to %d for %s", gtsm_hops, peer->host); /* We cannot configure ttl-security hops when ebgp-multihop is already set. For non peer-groups, the check is simple. For peer-groups, it's slightly messy, because we need to check both the peer-group structure and all peer-group members for any trace of ebgp-multihop configuration before actually applying the ttl-security rules. Cisco really made a mess of this configuration parameter, and OpenBGPD got it right. */ if ((peer->gtsm_hops == 0) && (peer->sort != BGP_PEER_IBGP)) { if (is_ebgp_multihop_configured(peer)) return BGP_ERR_NO_EBGP_MULTIHOP_WITH_TTLHACK; if (!CHECK_FLAG(peer->sflags, PEER_STATUS_GROUP)) { peer->gtsm_hops = gtsm_hops; /* Calling ebgp multihop also resets the session. * On restart, NHT will get setup correctly as will the * min & max ttls on the socket. The return value is * irrelevant. */ ret = peer_ebgp_multihop_set(peer, MAXTTL); if (ret != 0) return ret; } else { group = peer->group; for (ALL_LIST_ELEMENTS(group->peer, node, nnode, peer)) { peer->gtsm_hops = group->conf->gtsm_hops; /* Calling ebgp multihop also resets the * session. * On restart, NHT will get setup correctly as * will the * min & max ttls on the socket. The return * value is * irrelevant. */ peer_ebgp_multihop_set(peer, MAXTTL); } } } else { /* Post the first gtsm setup or if its ibgp, maxttl setting * isn't * necessary, just set the minttl. */ if (!CHECK_FLAG(peer->sflags, PEER_STATUS_GROUP)) { peer->gtsm_hops = gtsm_hops; if (peer->fd >= 0) sockopt_minttl(peer->su.sa.sa_family, peer->fd, MAXTTL + 1 - gtsm_hops); if ((peer->status < Established) && peer->doppelganger && (peer->doppelganger->fd >= 0)) sockopt_minttl(peer->su.sa.sa_family, peer->doppelganger->fd, MAXTTL + 1 - gtsm_hops); } else { group = peer->group; for (ALL_LIST_ELEMENTS(group->peer, node, nnode, peer)) { peer->gtsm_hops = group->conf->gtsm_hops; /* Change setting of existing peer * established then change value (may break * connectivity) * not established yet (teardown session and * restart) * no session then do nothing (will get * handled by next connection) */ if (peer->fd >= 0 && peer->gtsm_hops != 0) sockopt_minttl( peer->su.sa.sa_family, peer->fd, MAXTTL + 1 - peer->gtsm_hops); if ((peer->status < Established) && peer->doppelganger && (peer->doppelganger->fd >= 0)) sockopt_minttl(peer->su.sa.sa_family, peer->doppelganger->fd, MAXTTL + 1 - gtsm_hops); } } } return 0; } int peer_ttl_security_hops_unset(struct peer *peer) { struct peer_group *group; struct listnode *node, *nnode; int ret = 0; zlog_debug("peer_ttl_security_hops_unset: set gtsm_hops to zero for %s", peer->host); /* if a peer-group member, then reset to peer-group default rather than * 0 */ if (peer_group_active(peer)) peer->gtsm_hops = peer->group->conf->gtsm_hops; else peer->gtsm_hops = 0; if (!CHECK_FLAG(peer->sflags, PEER_STATUS_GROUP)) { /* Invoking ebgp_multihop_set will set the TTL back to the * original * value as well as restting the NHT and such. The session is * reset. */ if (peer->sort == BGP_PEER_EBGP) ret = peer_ebgp_multihop_unset(peer); else { if (peer->fd >= 0) sockopt_minttl(peer->su.sa.sa_family, peer->fd, 0); if ((peer->status < Established) && peer->doppelganger && (peer->doppelganger->fd >= 0)) sockopt_minttl(peer->su.sa.sa_family, peer->doppelganger->fd, 0); } } else { group = peer->group; for (ALL_LIST_ELEMENTS(group->peer, node, nnode, peer)) { peer->gtsm_hops = 0; if (peer->sort == BGP_PEER_EBGP) ret = peer_ebgp_multihop_unset(peer); else { if (peer->fd >= 0) sockopt_minttl(peer->su.sa.sa_family, peer->fd, 0); if ((peer->status < Established) && peer->doppelganger && (peer->doppelganger->fd >= 0)) sockopt_minttl(peer->su.sa.sa_family, peer->doppelganger->fd, 0); } } } return ret; } /* * If peer clear is invoked in a loop for all peers on the BGP instance, * it may end up freeing the doppelganger, and if this was the next node * to the current node, we would end up accessing the freed next node. * Pass along additional parameter which can be updated if next node * is freed; only required when walking the peer list on BGP instance. */ int peer_clear(struct peer *peer, struct listnode **nnode) { if (!CHECK_FLAG(peer->flags, PEER_FLAG_SHUTDOWN)) { if (CHECK_FLAG(peer->sflags, PEER_STATUS_PREFIX_OVERFLOW)) { UNSET_FLAG(peer->sflags, PEER_STATUS_PREFIX_OVERFLOW); if (peer->t_pmax_restart) { BGP_TIMER_OFF(peer->t_pmax_restart); if (bgp_debug_neighbor_events(peer)) zlog_debug( "%s Maximum-prefix restart timer canceled", peer->host); } BGP_EVENT_ADD(peer, BGP_Start); return 0; } peer->v_start = BGP_INIT_START_TIMER; if (BGP_IS_VALID_STATE_FOR_NOTIF(peer->status)) bgp_notify_send(peer, BGP_NOTIFY_CEASE, BGP_NOTIFY_CEASE_ADMIN_RESET); else bgp_session_reset_safe(peer, nnode); } return 0; } int peer_clear_soft(struct peer *peer, afi_t afi, safi_t safi, enum bgp_clear_type stype) { struct peer_af *paf; if (peer->status != Established) return 0; if (!peer->afc[afi][safi]) return BGP_ERR_AF_UNCONFIGURED; peer->rtt = sockopt_tcp_rtt(peer->fd); if (stype == BGP_CLEAR_SOFT_OUT || stype == BGP_CLEAR_SOFT_BOTH) { /* Clear the "neighbor x.x.x.x default-originate" flag */ paf = peer_af_find(peer, afi, safi); if (paf && paf->subgroup && CHECK_FLAG(paf->subgroup->sflags, SUBGRP_STATUS_DEFAULT_ORIGINATE)) UNSET_FLAG(paf->subgroup->sflags, SUBGRP_STATUS_DEFAULT_ORIGINATE); bgp_announce_route(peer, afi, safi); } if (stype == BGP_CLEAR_SOFT_IN_ORF_PREFIX) { if (CHECK_FLAG(peer->af_cap[afi][safi], PEER_CAP_ORF_PREFIX_SM_ADV) && (CHECK_FLAG(peer->af_cap[afi][safi], PEER_CAP_ORF_PREFIX_RM_RCV) || CHECK_FLAG(peer->af_cap[afi][safi], PEER_CAP_ORF_PREFIX_RM_OLD_RCV))) { struct bgp_filter *filter = &peer->filter[afi][safi]; uint8_t prefix_type; if (CHECK_FLAG(peer->af_cap[afi][safi], PEER_CAP_ORF_PREFIX_RM_RCV)) prefix_type = ORF_TYPE_PREFIX; else prefix_type = ORF_TYPE_PREFIX_OLD; if (filter->plist[FILTER_IN].plist) { if (CHECK_FLAG(peer->af_sflags[afi][safi], PEER_STATUS_ORF_PREFIX_SEND)) bgp_route_refresh_send( peer, afi, safi, prefix_type, REFRESH_DEFER, 1); bgp_route_refresh_send(peer, afi, safi, prefix_type, REFRESH_IMMEDIATE, 0); } else { if (CHECK_FLAG(peer->af_sflags[afi][safi], PEER_STATUS_ORF_PREFIX_SEND)) bgp_route_refresh_send( peer, afi, safi, prefix_type, REFRESH_IMMEDIATE, 1); else bgp_route_refresh_send(peer, afi, safi, 0, 0, 0); } return 0; } } if (stype == BGP_CLEAR_SOFT_IN || stype == BGP_CLEAR_SOFT_BOTH || stype == BGP_CLEAR_SOFT_IN_ORF_PREFIX) { /* If neighbor has soft reconfiguration inbound flag. Use Adj-RIB-In database. */ if (CHECK_FLAG(peer->af_flags[afi][safi], PEER_FLAG_SOFT_RECONFIG)) bgp_soft_reconfig_in(peer, afi, safi); else { /* If neighbor has route refresh capability, send route refresh message to the peer. */ if (CHECK_FLAG(peer->cap, PEER_CAP_REFRESH_OLD_RCV) || CHECK_FLAG(peer->cap, PEER_CAP_REFRESH_NEW_RCV)) bgp_route_refresh_send(peer, afi, safi, 0, 0, 0); else return BGP_ERR_SOFT_RECONFIG_UNCONFIGURED; } } return 0; } /* Display peer uptime.*/ char *peer_uptime(time_t uptime2, char *buf, size_t len, bool use_json, json_object *json) { time_t uptime1, epoch_tbuf; struct tm *tm; /* If there is no connection has been done before print `never'. */ if (uptime2 == 0) { if (use_json) { json_object_string_add(json, "peerUptime", "never"); json_object_int_add(json, "peerUptimeMsec", 0); } else snprintf(buf, len, "never"); return buf; } /* Get current time. */ uptime1 = bgp_clock(); uptime1 -= uptime2; tm = gmtime(&uptime1); if (uptime1 < ONE_DAY_SECOND) snprintf(buf, len, "%02d:%02d:%02d", tm->tm_hour, tm->tm_min, tm->tm_sec); else if (uptime1 < ONE_WEEK_SECOND) snprintf(buf, len, "%dd%02dh%02dm", tm->tm_yday, tm->tm_hour, tm->tm_min); else if (uptime1 < ONE_YEAR_SECOND) snprintf(buf, len, "%02dw%dd%02dh", tm->tm_yday / 7, tm->tm_yday - ((tm->tm_yday / 7) * 7), tm->tm_hour); else snprintf(buf, len, "%02dy%02dw%dd", tm->tm_year - 70, tm->tm_yday / 7, tm->tm_yday - ((tm->tm_yday / 7) * 7)); if (use_json) { epoch_tbuf = time(NULL) - uptime1; json_object_string_add(json, "peerUptime", buf); json_object_int_add(json, "peerUptimeMsec", uptime1 * 1000); json_object_int_add(json, "peerUptimeEstablishedEpoch", epoch_tbuf); } return buf; } static void bgp_config_write_filter(struct vty *vty, struct peer *peer, afi_t afi, safi_t safi) { struct bgp_filter *filter; char *addr; addr = peer->host; filter = &peer->filter[afi][safi]; /* distribute-list. */ if (peergroup_filter_check(peer, afi, safi, PEER_FT_DISTRIBUTE_LIST, FILTER_IN)) vty_out(vty, " neighbor %s distribute-list %s in\n", addr, filter->dlist[FILTER_IN].name); if (peergroup_filter_check(peer, afi, safi, PEER_FT_DISTRIBUTE_LIST, FILTER_OUT)) vty_out(vty, " neighbor %s distribute-list %s out\n", addr, filter->dlist[FILTER_OUT].name); /* prefix-list. */ if (peergroup_filter_check(peer, afi, safi, PEER_FT_PREFIX_LIST, FILTER_IN)) vty_out(vty, " neighbor %s prefix-list %s in\n", addr, filter->plist[FILTER_IN].name); if (peergroup_filter_check(peer, afi, safi, PEER_FT_PREFIX_LIST, FILTER_OUT)) vty_out(vty, " neighbor %s prefix-list %s out\n", addr, filter->plist[FILTER_OUT].name); /* route-map. */ if (peergroup_filter_check(peer, afi, safi, PEER_FT_ROUTE_MAP, RMAP_IN)) vty_out(vty, " neighbor %s route-map %s in\n", addr, filter->map[RMAP_IN].name); if (peergroup_filter_check(peer, afi, safi, PEER_FT_ROUTE_MAP, RMAP_OUT)) vty_out(vty, " neighbor %s route-map %s out\n", addr, filter->map[RMAP_OUT].name); /* unsuppress-map */ if (peergroup_filter_check(peer, afi, safi, PEER_FT_UNSUPPRESS_MAP, 0)) vty_out(vty, " neighbor %s unsuppress-map %s\n", addr, filter->usmap.name); /* filter-list. */ if (peergroup_filter_check(peer, afi, safi, PEER_FT_FILTER_LIST, FILTER_IN)) vty_out(vty, " neighbor %s filter-list %s in\n", addr, filter->aslist[FILTER_IN].name); if (peergroup_filter_check(peer, afi, safi, PEER_FT_FILTER_LIST, FILTER_OUT)) vty_out(vty, " neighbor %s filter-list %s out\n", addr, filter->aslist[FILTER_OUT].name); } /* BGP peer configuration display function. */ static void bgp_config_write_peer_global(struct vty *vty, struct bgp *bgp, struct peer *peer) { struct peer *g_peer = NULL; char buf[SU_ADDRSTRLEN]; char *addr; int if_pg_printed = false; int if_ras_printed = false; /* Skip dynamic neighbors. */ if (peer_dynamic_neighbor(peer)) return; if (peer->conf_if) addr = peer->conf_if; else addr = peer->host; /************************************ ****** Global to the neighbor ****** ************************************/ if (peer->conf_if) { if (CHECK_FLAG(peer->flags, PEER_FLAG_IFPEER_V6ONLY)) vty_out(vty, " neighbor %s interface v6only", addr); else vty_out(vty, " neighbor %s interface", addr); if (peer_group_active(peer)) { vty_out(vty, " peer-group %s", peer->group->name); if_pg_printed = true; } else if (peer->as_type == AS_SPECIFIED) { vty_out(vty, " remote-as %u", peer->as); if_ras_printed = true; } else if (peer->as_type == AS_INTERNAL) { vty_out(vty, " remote-as internal"); if_ras_printed = true; } else if (peer->as_type == AS_EXTERNAL) { vty_out(vty, " remote-as external"); if_ras_printed = true; } vty_out(vty, "\n"); } /* remote-as and peer-group */ /* peer is a member of a peer-group */ if (peer_group_active(peer)) { g_peer = peer->group->conf; if (g_peer->as_type == AS_UNSPECIFIED && !if_ras_printed) { if (peer->as_type == AS_SPECIFIED) { vty_out(vty, " neighbor %s remote-as %u\n", addr, peer->as); } else if (peer->as_type == AS_INTERNAL) { vty_out(vty, " neighbor %s remote-as internal\n", addr); } else if (peer->as_type == AS_EXTERNAL) { vty_out(vty, " neighbor %s remote-as external\n", addr); } } /* For swpX peers we displayed the peer-group * via 'neighbor swpX interface peer-group PGNAME' */ if (!if_pg_printed) vty_out(vty, " neighbor %s peer-group %s\n", addr, peer->group->name); } /* peer is NOT a member of a peer-group */ else { /* peer is a peer-group, declare the peer-group */ if (CHECK_FLAG(peer->sflags, PEER_STATUS_GROUP)) { vty_out(vty, " neighbor %s peer-group\n", addr); } if (!if_ras_printed) { if (peer->as_type == AS_SPECIFIED) { vty_out(vty, " neighbor %s remote-as %u\n", addr, peer->as); } else if (peer->as_type == AS_INTERNAL) { vty_out(vty, " neighbor %s remote-as internal\n", addr); } else if (peer->as_type == AS_EXTERNAL) { vty_out(vty, " neighbor %s remote-as external\n", addr); } } } /* local-as */ if (peergroup_flag_check(peer, PEER_FLAG_LOCAL_AS)) { vty_out(vty, " neighbor %s local-as %u", addr, peer->change_local_as); if (peergroup_flag_check(peer, PEER_FLAG_LOCAL_AS_NO_PREPEND)) vty_out(vty, " no-prepend"); if (peergroup_flag_check(peer, PEER_FLAG_LOCAL_AS_REPLACE_AS)) vty_out(vty, " replace-as"); vty_out(vty, "\n"); } /* description */ if (peer->desc) { vty_out(vty, " neighbor %s description %s\n", addr, peer->desc); } /* shutdown */ if (peergroup_flag_check(peer, PEER_FLAG_SHUTDOWN)) { if (peer->tx_shutdown_message) vty_out(vty, " neighbor %s shutdown message %s\n", addr, peer->tx_shutdown_message); else vty_out(vty, " neighbor %s shutdown\n", addr); } /* bfd */ if (peer->bfd_info) { if (!peer_group_active(peer) || !g_peer->bfd_info) { bgp_bfd_peer_config_write(vty, peer, addr); } } /* password */ if (peergroup_flag_check(peer, PEER_FLAG_PASSWORD)) vty_out(vty, " neighbor %s password %s\n", addr, peer->password); /* neighbor solo */ if (CHECK_FLAG(peer->flags, PEER_FLAG_LONESOUL)) { if (!peer_group_active(peer)) { vty_out(vty, " neighbor %s solo\n", addr); } } /* BGP port */ if (peer->port != BGP_PORT_DEFAULT) { vty_out(vty, " neighbor %s port %d\n", addr, peer->port); } /* Local interface name */ if (peer->ifname) { vty_out(vty, " neighbor %s interface %s\n", addr, peer->ifname); } /* passive */ if (peergroup_flag_check(peer, PEER_FLAG_PASSIVE)) vty_out(vty, " neighbor %s passive\n", addr); /* ebgp-multihop */ if (peer->sort != BGP_PEER_IBGP && peer->ttl != 1 && !(peer->gtsm_hops != 0 && peer->ttl == MAXTTL)) { if (!peer_group_active(peer) || g_peer->ttl != peer->ttl) { vty_out(vty, " neighbor %s ebgp-multihop %d\n", addr, peer->ttl); } } /* ttl-security hops */ if (peer->gtsm_hops != 0) { if (!peer_group_active(peer) || g_peer->gtsm_hops != peer->gtsm_hops) { vty_out(vty, " neighbor %s ttl-security hops %d\n", addr, peer->gtsm_hops); } } /* disable-connected-check */ if (peergroup_flag_check(peer, PEER_FLAG_DISABLE_CONNECTED_CHECK)) vty_out(vty, " neighbor %s disable-connected-check\n", addr); /* enforce-first-as */ if (peergroup_flag_check(peer, PEER_FLAG_ENFORCE_FIRST_AS)) vty_out(vty, " neighbor %s enforce-first-as\n", addr); /* update-source */ if (peergroup_flag_check(peer, PEER_FLAG_UPDATE_SOURCE)) { if (peer->update_source) vty_out(vty, " neighbor %s update-source %s\n", addr, sockunion2str(peer->update_source, buf, SU_ADDRSTRLEN)); else if (peer->update_if) vty_out(vty, " neighbor %s update-source %s\n", addr, peer->update_if); } /* advertisement-interval */ if (peergroup_flag_check(peer, PEER_FLAG_ROUTEADV)) vty_out(vty, " neighbor %s advertisement-interval %u\n", addr, peer->routeadv); /* timers */ if (peergroup_flag_check(peer, PEER_FLAG_TIMER)) vty_out(vty, " neighbor %s timers %u %u\n", addr, peer->keepalive, peer->holdtime); /* timers connect */ if (peergroup_flag_check(peer, PEER_FLAG_TIMER_CONNECT)) vty_out(vty, " neighbor %s timers connect %u\n", addr, peer->connect); /* capability dynamic */ if (peergroup_flag_check(peer, PEER_FLAG_DYNAMIC_CAPABILITY)) vty_out(vty, " neighbor %s capability dynamic\n", addr); /* capability extended-nexthop */ if (peergroup_flag_check(peer, PEER_FLAG_CAPABILITY_ENHE)) { if (!peer->conf_if) { if (CHECK_FLAG(peer->flags_invert, PEER_FLAG_CAPABILITY_ENHE)) vty_out(vty, " no neighbor %s capability extended-nexthop\n", addr); else vty_out(vty, " neighbor %s capability extended-nexthop\n", addr); } } /* dont-capability-negotiation */ if (peergroup_flag_check(peer, PEER_FLAG_DONT_CAPABILITY)) vty_out(vty, " neighbor %s dont-capability-negotiate\n", addr); /* override-capability */ if (peergroup_flag_check(peer, PEER_FLAG_OVERRIDE_CAPABILITY)) vty_out(vty, " neighbor %s override-capability\n", addr); /* strict-capability-match */ if (peergroup_flag_check(peer, PEER_FLAG_STRICT_CAP_MATCH)) vty_out(vty, " neighbor %s strict-capability-match\n", addr); } /* BGP peer configuration display function. */ static void bgp_config_write_peer_af(struct vty *vty, struct bgp *bgp, struct peer *peer, afi_t afi, safi_t safi) { struct peer *g_peer = NULL; char *addr; bool flag_scomm, flag_secomm, flag_slcomm; /* Skip dynamic neighbors. */ if (peer_dynamic_neighbor(peer)) return; if (peer->conf_if) addr = peer->conf_if; else addr = peer->host; /************************************ ****** Per AF to the neighbor ****** ************************************/ if (peer_group_active(peer)) { g_peer = peer->group->conf; /* If the peer-group is active but peer is not, print a 'no * activate' */ if (g_peer->afc[afi][safi] && !peer->afc[afi][safi]) { vty_out(vty, " no neighbor %s activate\n", addr); } /* If the peer-group is not active but peer is, print an 'activate' */ else if (!g_peer->afc[afi][safi] && peer->afc[afi][safi]) { vty_out(vty, " neighbor %s activate\n", addr); } } else { if (peer->afc[afi][safi]) { if ((afi == AFI_IP) && (safi == SAFI_UNICAST)) { if (bgp_flag_check(bgp, BGP_FLAG_NO_DEFAULT_IPV4)) { vty_out(vty, " neighbor %s activate\n", addr); } } else vty_out(vty, " neighbor %s activate\n", addr); } else { if ((afi == AFI_IP) && (safi == SAFI_UNICAST)) { if (!bgp_flag_check(bgp, BGP_FLAG_NO_DEFAULT_IPV4)) { vty_out(vty, " no neighbor %s activate\n", addr); } } } } /* addpath TX knobs */ if (peergroup_af_addpath_check(peer, afi, safi)) { switch (peer->addpath_type[afi][safi]) { case BGP_ADDPATH_ALL: vty_out(vty, " neighbor %s addpath-tx-all-paths\n", addr); break; case BGP_ADDPATH_BEST_PER_AS: vty_out(vty, " neighbor %s addpath-tx-bestpath-per-AS\n", addr); break; case BGP_ADDPATH_MAX: case BGP_ADDPATH_NONE: break; } } /* ORF capability. */ if (peergroup_af_flag_check(peer, afi, safi, PEER_FLAG_ORF_PREFIX_SM) || peergroup_af_flag_check(peer, afi, safi, PEER_FLAG_ORF_PREFIX_RM)) { vty_out(vty, " neighbor %s capability orf prefix-list", addr); if (peergroup_af_flag_check(peer, afi, safi, PEER_FLAG_ORF_PREFIX_SM) && peergroup_af_flag_check(peer, afi, safi, PEER_FLAG_ORF_PREFIX_RM)) vty_out(vty, " both"); else if (peergroup_af_flag_check(peer, afi, safi, PEER_FLAG_ORF_PREFIX_SM)) vty_out(vty, " send"); else vty_out(vty, " receive"); vty_out(vty, "\n"); } /* BGP flag dampening. */ if (CHECK_FLAG(bgp->af_flags[afi][safi], BGP_CONFIG_DAMPENING)) bgp_config_write_damp(vty, afi, safi); /* Route reflector client. */ if (peergroup_af_flag_check(peer, afi, safi, PEER_FLAG_REFLECTOR_CLIENT)) { vty_out(vty, " neighbor %s route-reflector-client\n", addr); } /* next-hop-self force */ if (peergroup_af_flag_check(peer, afi, safi, PEER_FLAG_FORCE_NEXTHOP_SELF)) { vty_out(vty, " neighbor %s next-hop-self force\n", addr); } /* next-hop-self */ if (peergroup_af_flag_check(peer, afi, safi, PEER_FLAG_NEXTHOP_SELF)) { vty_out(vty, " neighbor %s next-hop-self\n", addr); } /* remove-private-AS */ if (peergroup_af_flag_check(peer, afi, safi, PEER_FLAG_REMOVE_PRIVATE_AS_ALL_REPLACE)) { vty_out(vty, " neighbor %s remove-private-AS all replace-AS\n", addr); } else if (peergroup_af_flag_check(peer, afi, safi, PEER_FLAG_REMOVE_PRIVATE_AS_REPLACE)) { vty_out(vty, " neighbor %s remove-private-AS replace-AS\n", addr); } else if (peergroup_af_flag_check(peer, afi, safi, PEER_FLAG_REMOVE_PRIVATE_AS_ALL)) { vty_out(vty, " neighbor %s remove-private-AS all\n", addr); } else if (peergroup_af_flag_check(peer, afi, safi, PEER_FLAG_REMOVE_PRIVATE_AS)) { vty_out(vty, " neighbor %s remove-private-AS\n", addr); } /* as-override */ if (peergroup_af_flag_check(peer, afi, safi, PEER_FLAG_AS_OVERRIDE)) { vty_out(vty, " neighbor %s as-override\n", addr); } /* send-community print. */ flag_scomm = peergroup_af_flag_check(peer, afi, safi, PEER_FLAG_SEND_COMMUNITY); flag_secomm = peergroup_af_flag_check(peer, afi, safi, PEER_FLAG_SEND_EXT_COMMUNITY); flag_slcomm = peergroup_af_flag_check(peer, afi, safi, PEER_FLAG_SEND_LARGE_COMMUNITY); if (flag_scomm && flag_secomm && flag_slcomm) { vty_out(vty, " no neighbor %s send-community all\n", addr); } else { if (flag_scomm) vty_out(vty, " no neighbor %s send-community\n", addr); if (flag_secomm) vty_out(vty, " no neighbor %s send-community extended\n", addr); if (flag_slcomm) vty_out(vty, " no neighbor %s send-community large\n", addr); } /* Default information */ if (peergroup_af_flag_check(peer, afi, safi, PEER_FLAG_DEFAULT_ORIGINATE)) { vty_out(vty, " neighbor %s default-originate", addr); if (peer->default_rmap[afi][safi].name) vty_out(vty, " route-map %s", peer->default_rmap[afi][safi].name); vty_out(vty, "\n"); } /* Soft reconfiguration inbound. */ if (peergroup_af_flag_check(peer, afi, safi, PEER_FLAG_SOFT_RECONFIG)) { vty_out(vty, " neighbor %s soft-reconfiguration inbound\n", addr); } /* maximum-prefix. */ if (peergroup_af_flag_check(peer, afi, safi, PEER_FLAG_MAX_PREFIX)) { vty_out(vty, " neighbor %s maximum-prefix %" PRIu32, addr, peer->pmax[afi][safi]); if (peer->pmax_threshold[afi][safi] != MAXIMUM_PREFIX_THRESHOLD_DEFAULT) vty_out(vty, " %u", peer->pmax_threshold[afi][safi]); if (peer_af_flag_check(peer, afi, safi, PEER_FLAG_MAX_PREFIX_WARNING)) vty_out(vty, " warning-only"); if (peer->pmax_restart[afi][safi]) vty_out(vty, " restart %u", peer->pmax_restart[afi][safi]); vty_out(vty, "\n"); } /* Route server client. */ if (peergroup_af_flag_check(peer, afi, safi, PEER_FLAG_RSERVER_CLIENT)) { vty_out(vty, " neighbor %s route-server-client\n", addr); } /* Nexthop-local unchanged. */ if (peergroup_af_flag_check(peer, afi, safi, PEER_FLAG_NEXTHOP_LOCAL_UNCHANGED)) { vty_out(vty, " neighbor %s nexthop-local unchanged\n", addr); } /* allowas-in <1-10> */ if (peergroup_af_flag_check(peer, afi, safi, PEER_FLAG_ALLOWAS_IN)) { if (peer_af_flag_check(peer, afi, safi, PEER_FLAG_ALLOWAS_IN_ORIGIN)) { vty_out(vty, " neighbor %s allowas-in origin\n", addr); } else if (peer->allowas_in[afi][safi] == 3) { vty_out(vty, " neighbor %s allowas-in\n", addr); } else { vty_out(vty, " neighbor %s allowas-in %d\n", addr, peer->allowas_in[afi][safi]); } } /* weight */ if (peergroup_af_flag_check(peer, afi, safi, PEER_FLAG_WEIGHT)) vty_out(vty, " neighbor %s weight %lu\n", addr, peer->weight[afi][safi]); /* Filter. */ bgp_config_write_filter(vty, peer, afi, safi); /* atribute-unchanged. */ if (peer_af_flag_check(peer, afi, safi, PEER_FLAG_AS_PATH_UNCHANGED) || (safi != SAFI_EVPN && peer_af_flag_check(peer, afi, safi, PEER_FLAG_NEXTHOP_UNCHANGED)) || peer_af_flag_check(peer, afi, safi, PEER_FLAG_MED_UNCHANGED)) { if (!peer_group_active(peer) || peergroup_af_flag_check(peer, afi, safi, PEER_FLAG_AS_PATH_UNCHANGED) || peergroup_af_flag_check(peer, afi, safi, PEER_FLAG_NEXTHOP_UNCHANGED) || peergroup_af_flag_check(peer, afi, safi, PEER_FLAG_MED_UNCHANGED)) { vty_out(vty, " neighbor %s attribute-unchanged%s%s%s\n", addr, peer_af_flag_check(peer, afi, safi, PEER_FLAG_AS_PATH_UNCHANGED) ? " as-path" : "", peer_af_flag_check(peer, afi, safi, PEER_FLAG_NEXTHOP_UNCHANGED) ? " next-hop" : "", peer_af_flag_check(peer, afi, safi, PEER_FLAG_MED_UNCHANGED) ? " med" : ""); } } } /* Address family based peer configuration display. */ static void bgp_config_write_family(struct vty *vty, struct bgp *bgp, afi_t afi, safi_t safi) { struct peer *peer; struct peer_group *group; struct listnode *node, *nnode; vty_frame(vty, " !\n address-family "); if (afi == AFI_IP) { if (safi == SAFI_UNICAST) vty_frame(vty, "ipv4 unicast"); else if (safi == SAFI_LABELED_UNICAST) vty_frame(vty, "ipv4 labeled-unicast"); else if (safi == SAFI_MULTICAST) vty_frame(vty, "ipv4 multicast"); else if (safi == SAFI_MPLS_VPN) vty_frame(vty, "ipv4 vpn"); else if (safi == SAFI_ENCAP) vty_frame(vty, "ipv4 encap"); else if (safi == SAFI_FLOWSPEC) vty_frame(vty, "ipv4 flowspec"); } else if (afi == AFI_IP6) { if (safi == SAFI_UNICAST) vty_frame(vty, "ipv6 unicast"); else if (safi == SAFI_LABELED_UNICAST) vty_frame(vty, "ipv6 labeled-unicast"); else if (safi == SAFI_MULTICAST) vty_frame(vty, "ipv6 multicast"); else if (safi == SAFI_MPLS_VPN) vty_frame(vty, "ipv6 vpn"); else if (safi == SAFI_ENCAP) vty_frame(vty, "ipv6 encap"); else if (safi == SAFI_FLOWSPEC) vty_frame(vty, "ipv6 flowspec"); } else if (afi == AFI_L2VPN) { if (safi == SAFI_EVPN) vty_frame(vty, "l2vpn evpn"); } vty_frame(vty, "\n"); bgp_config_write_distance(vty, bgp, afi, safi); bgp_config_write_network(vty, bgp, afi, safi); bgp_config_write_redistribute(vty, bgp, afi, safi); for (ALL_LIST_ELEMENTS(bgp->group, node, nnode, group)) bgp_config_write_peer_af(vty, bgp, group->conf, afi, safi); for (ALL_LIST_ELEMENTS(bgp->peer, node, nnode, peer)) { /* Skip dynamic neighbors. */ if (peer_dynamic_neighbor(peer)) continue; /* Do not display doppelganger peers */ if (CHECK_FLAG(peer->flags, PEER_FLAG_CONFIG_NODE)) bgp_config_write_peer_af(vty, bgp, peer, afi, safi); } bgp_config_write_maxpaths(vty, bgp, afi, safi); bgp_config_write_table_map(vty, bgp, afi, safi); if (safi == SAFI_EVPN) bgp_config_write_evpn_info(vty, bgp, afi, safi); if (safi == SAFI_FLOWSPEC) bgp_fs_config_write_pbr(vty, bgp, afi, safi); if (safi == SAFI_UNICAST) { bgp_vpn_policy_config_write_afi(vty, bgp, afi); if (CHECK_FLAG(bgp->af_flags[afi][safi], BGP_CONFIG_VRF_TO_MPLSVPN_EXPORT)) { vty_out(vty, " export vpn\n"); } if (CHECK_FLAG(bgp->af_flags[afi][safi], BGP_CONFIG_MPLSVPN_TO_VRF_IMPORT)) { vty_out(vty, " import vpn\n"); } if (CHECK_FLAG(bgp->af_flags[afi][safi], BGP_CONFIG_VRF_TO_VRF_IMPORT)) { char *name; for (ALL_LIST_ELEMENTS_RO( bgp->vpn_policy[afi].import_vrf, node, name)) vty_out(vty, " import vrf %s\n", name); } } vty_endframe(vty, " exit-address-family\n"); } int bgp_config_write(struct vty *vty) { int write = 0; struct bgp *bgp; struct peer_group *group; struct peer *peer; struct listnode *node, *nnode; struct listnode *mnode, *mnnode; if (bm->rmap_update_timer != RMAP_DEFAULT_UPDATE_TIMER) vty_out(vty, "bgp route-map delay-timer %u\n", bm->rmap_update_timer); if (write) vty_out(vty, "!\n"); /* BGP configuration. */ for (ALL_LIST_ELEMENTS(bm->bgp, mnode, mnnode, bgp)) { /* skip all auto created vrf as they dont have user config */ if (CHECK_FLAG(bgp->vrf_flags, BGP_VRF_AUTO)) continue; /* Router bgp ASN */ vty_out(vty, "router bgp %u", bgp->as); if (bgp->name) vty_out(vty, " %s %s", (bgp->inst_type == BGP_INSTANCE_TYPE_VIEW) ? "view" : "vrf", bgp->name); vty_out(vty, "\n"); /* BGP fast-external-failover. */ if (CHECK_FLAG(bgp->flags, BGP_FLAG_NO_FAST_EXT_FAILOVER)) vty_out(vty, " no bgp fast-external-failover\n"); /* BGP router ID. */ if (bgp->router_id_static.s_addr != 0) vty_out(vty, " bgp router-id %s\n", inet_ntoa(bgp->router_id_static)); /* BGP log-neighbor-changes. */ if (!!bgp_flag_check(bgp, BGP_FLAG_LOG_NEIGHBOR_CHANGES) != DFLT_BGP_LOG_NEIGHBOR_CHANGES) vty_out(vty, " %sbgp log-neighbor-changes\n", bgp_flag_check(bgp, BGP_FLAG_LOG_NEIGHBOR_CHANGES) ? "" : "no "); /* BGP configuration. */ if (bgp_flag_check(bgp, BGP_FLAG_ALWAYS_COMPARE_MED)) vty_out(vty, " bgp always-compare-med\n"); /* RFC8212 default eBGP policy. */ if (bgp->ebgp_requires_policy == DEFAULT_EBGP_POLICY_ENABLED) vty_out(vty, " bgp ebgp-requires-policy\n"); /* BGP default ipv4-unicast. */ if (bgp_flag_check(bgp, BGP_FLAG_NO_DEFAULT_IPV4)) vty_out(vty, " no bgp default ipv4-unicast\n"); /* BGP default local-preference. */ if (bgp->default_local_pref != BGP_DEFAULT_LOCAL_PREF) vty_out(vty, " bgp default local-preference %u\n", bgp->default_local_pref); /* BGP default show-hostname */ if (!!bgp_flag_check(bgp, BGP_FLAG_SHOW_HOSTNAME) != DFLT_BGP_SHOW_HOSTNAME) vty_out(vty, " %sbgp default show-hostname\n", bgp_flag_check(bgp, BGP_FLAG_SHOW_HOSTNAME) ? "" : "no "); /* BGP default subgroup-pkt-queue-max. */ if (bgp->default_subgroup_pkt_queue_max != BGP_DEFAULT_SUBGROUP_PKT_QUEUE_MAX) vty_out(vty, " bgp default subgroup-pkt-queue-max %u\n", bgp->default_subgroup_pkt_queue_max); /* BGP client-to-client reflection. */ if (bgp_flag_check(bgp, BGP_FLAG_NO_CLIENT_TO_CLIENT)) vty_out(vty, " no bgp client-to-client reflection\n"); /* BGP cluster ID. */ if (CHECK_FLAG(bgp->config, BGP_CONFIG_CLUSTER_ID)) vty_out(vty, " bgp cluster-id %s\n", inet_ntoa(bgp->cluster_id)); /* Disable ebgp connected nexthop check */ if (bgp_flag_check(bgp, BGP_FLAG_DISABLE_NH_CONNECTED_CHK)) vty_out(vty, " bgp disable-ebgp-connected-route-check\n"); /* Confederation identifier*/ if (CHECK_FLAG(bgp->config, BGP_CONFIG_CONFEDERATION)) vty_out(vty, " bgp confederation identifier %u\n", bgp->confed_id); /* Confederation peer */ if (bgp->confed_peers_cnt > 0) { int i; vty_out(vty, " bgp confederation peers"); for (i = 0; i < bgp->confed_peers_cnt; i++) vty_out(vty, " %u", bgp->confed_peers[i]); vty_out(vty, "\n"); } /* BGP deterministic-med. */ if (!!bgp_flag_check(bgp, BGP_FLAG_DETERMINISTIC_MED) != DFLT_BGP_DETERMINISTIC_MED) vty_out(vty, " %sbgp deterministic-med\n", bgp_flag_check(bgp, BGP_FLAG_DETERMINISTIC_MED) ? "" : "no "); /* BGP update-delay. */ bgp_config_write_update_delay(vty, bgp); if (bgp->v_maxmed_onstartup != BGP_MAXMED_ONSTARTUP_UNCONFIGURED) { vty_out(vty, " bgp max-med on-startup %u", bgp->v_maxmed_onstartup); if (bgp->maxmed_onstartup_value != BGP_MAXMED_VALUE_DEFAULT) vty_out(vty, " %u", bgp->maxmed_onstartup_value); vty_out(vty, "\n"); } if (bgp->v_maxmed_admin != BGP_MAXMED_ADMIN_UNCONFIGURED) { vty_out(vty, " bgp max-med administrative"); if (bgp->maxmed_admin_value != BGP_MAXMED_VALUE_DEFAULT) vty_out(vty, " %u", bgp->maxmed_admin_value); vty_out(vty, "\n"); } /* write quanta */ bgp_config_write_wpkt_quanta(vty, bgp); /* read quanta */ bgp_config_write_rpkt_quanta(vty, bgp); /* coalesce time */ bgp_config_write_coalesce_time(vty, bgp); /* BGP graceful-restart. */ if (bgp->stalepath_time != BGP_DEFAULT_STALEPATH_TIME) vty_out(vty, " bgp graceful-restart stalepath-time %u\n", bgp->stalepath_time); if (bgp->restart_time != BGP_DEFAULT_RESTART_TIME) vty_out(vty, " bgp graceful-restart restart-time %u\n", bgp->restart_time); if (bgp_flag_check(bgp, BGP_FLAG_GRACEFUL_RESTART)) vty_out(vty, " bgp graceful-restart\n"); /* BGP graceful-shutdown */ if (bgp_flag_check(bgp, BGP_FLAG_GRACEFUL_SHUTDOWN)) vty_out(vty, " bgp graceful-shutdown\n"); /* BGP graceful-restart Preserve State F bit. */ if (bgp_flag_check(bgp, BGP_FLAG_GR_PRESERVE_FWD)) vty_out(vty, " bgp graceful-restart preserve-fw-state\n"); /* BGP bestpath method. */ if (bgp_flag_check(bgp, BGP_FLAG_ASPATH_IGNORE)) vty_out(vty, " bgp bestpath as-path ignore\n"); if (bgp_flag_check(bgp, BGP_FLAG_ASPATH_CONFED)) vty_out(vty, " bgp bestpath as-path confed\n"); if (bgp_flag_check(bgp, BGP_FLAG_ASPATH_MULTIPATH_RELAX)) { if (bgp_flag_check(bgp, BGP_FLAG_MULTIPATH_RELAX_AS_SET)) { vty_out(vty, " bgp bestpath as-path multipath-relax as-set\n"); } else { vty_out(vty, " bgp bestpath as-path multipath-relax\n"); } } if (bgp_flag_check(bgp, BGP_FLAG_RR_ALLOW_OUTBOUND_POLICY)) { vty_out(vty, " bgp route-reflector allow-outbound-policy\n"); } if (bgp_flag_check(bgp, BGP_FLAG_COMPARE_ROUTER_ID)) vty_out(vty, " bgp bestpath compare-routerid\n"); if (bgp_flag_check(bgp, BGP_FLAG_MED_CONFED) || bgp_flag_check(bgp, BGP_FLAG_MED_MISSING_AS_WORST)) { vty_out(vty, " bgp bestpath med"); if (bgp_flag_check(bgp, BGP_FLAG_MED_CONFED)) vty_out(vty, " confed"); if (bgp_flag_check(bgp, BGP_FLAG_MED_MISSING_AS_WORST)) vty_out(vty, " missing-as-worst"); vty_out(vty, "\n"); } /* BGP network import check. */ if (!!bgp_flag_check(bgp, BGP_FLAG_IMPORT_CHECK) != DFLT_BGP_IMPORT_CHECK) vty_out(vty, " %sbgp network import-check\n", bgp_flag_check(bgp, BGP_FLAG_IMPORT_CHECK) ? "" : "no "); /* BGP timers configuration. */ if (bgp->default_keepalive != BGP_DEFAULT_KEEPALIVE && bgp->default_holdtime != BGP_DEFAULT_HOLDTIME) vty_out(vty, " timers bgp %u %u\n", bgp->default_keepalive, bgp->default_holdtime); /* peer-group */ for (ALL_LIST_ELEMENTS(bgp->group, node, nnode, group)) { bgp_config_write_peer_global(vty, bgp, group->conf); } /* Normal neighbor configuration. */ for (ALL_LIST_ELEMENTS(bgp->peer, node, nnode, peer)) { if (CHECK_FLAG(peer->flags, PEER_FLAG_CONFIG_NODE)) bgp_config_write_peer_global(vty, bgp, peer); } /* listen range and limit for dynamic BGP neighbors */ bgp_config_write_listen(vty, bgp); /* * BGP default autoshutdown neighbors * * This must be placed after any peer and peer-group * configuration, to avoid setting all peers to shutdown after * a daemon restart, which is undesired behavior. (see #2286) */ if (bgp->autoshutdown) vty_out(vty, " bgp default shutdown\n"); /* IPv4 unicast configuration. */ bgp_config_write_family(vty, bgp, AFI_IP, SAFI_UNICAST); /* IPv4 multicast configuration. */ bgp_config_write_family(vty, bgp, AFI_IP, SAFI_MULTICAST); /* IPv4 labeled-unicast configuration. */ bgp_config_write_family(vty, bgp, AFI_IP, SAFI_LABELED_UNICAST); /* IPv4 VPN configuration. */ bgp_config_write_family(vty, bgp, AFI_IP, SAFI_MPLS_VPN); /* ENCAPv4 configuration. */ bgp_config_write_family(vty, bgp, AFI_IP, SAFI_ENCAP); /* FLOWSPEC v4 configuration. */ bgp_config_write_family(vty, bgp, AFI_IP, SAFI_FLOWSPEC); /* IPv6 unicast configuration. */ bgp_config_write_family(vty, bgp, AFI_IP6, SAFI_UNICAST); /* IPv6 multicast configuration. */ bgp_config_write_family(vty, bgp, AFI_IP6, SAFI_MULTICAST); /* IPv6 labeled-unicast configuration. */ bgp_config_write_family(vty, bgp, AFI_IP6, SAFI_LABELED_UNICAST); /* IPv6 VPN configuration. */ bgp_config_write_family(vty, bgp, AFI_IP6, SAFI_MPLS_VPN); /* ENCAPv6 configuration. */ bgp_config_write_family(vty, bgp, AFI_IP6, SAFI_ENCAP); /* FLOWSPEC v6 configuration. */ bgp_config_write_family(vty, bgp, AFI_IP6, SAFI_FLOWSPEC); /* EVPN configuration. */ bgp_config_write_family(vty, bgp, AFI_L2VPN, SAFI_EVPN); hook_call(bgp_inst_config_write, bgp, vty); #if ENABLE_BGP_VNC bgp_rfapi_cfg_write(vty, bgp); #endif vty_out(vty, "!\n"); } return 0; } void bgp_master_init(struct thread_master *master) { qobj_init(); memset(&bgp_master, 0, sizeof(struct bgp_master)); bm = &bgp_master; bm->bgp = list_new(); bm->listen_sockets = list_new(); bm->port = BGP_PORT_DEFAULT; bm->master = master; bm->start_time = bgp_clock(); bm->t_rmap_update = NULL; bm->rmap_update_timer = RMAP_DEFAULT_UPDATE_TIMER; bm->terminating = false; bgp_process_queue_init(); bgp_mac_init(); /* init the rd id space. assign 0th index in the bitfield, so that we start with id 1 */ bf_init(bm->rd_idspace, UINT16_MAX); bf_assign_zero_index(bm->rd_idspace); /* mpls label dynamic allocation pool */ bgp_lp_init(bm->master, &bm->labelpool); QOBJ_REG(bm, bgp_master); } /* * Free up connected routes and interfaces for a BGP instance. Invoked upon * instance delete (non-default only) or BGP exit. */ static void bgp_if_finish(struct bgp *bgp) { struct vrf *vrf; struct interface *ifp; vrf = bgp_vrf_lookup_by_instance_type(bgp); if (bgp->inst_type == BGP_INSTANCE_TYPE_VIEW || !vrf) return; FOR_ALL_INTERFACES (vrf, ifp) { struct listnode *c_node, *c_nnode; struct connected *c; for (ALL_LIST_ELEMENTS(ifp->connected, c_node, c_nnode, c)) bgp_connected_delete(bgp, c); } } static void bgp_viewvrf_autocomplete(vector comps, struct cmd_token *token) { struct vrf *vrf = NULL; struct listnode *next; struct bgp *bgp; RB_FOREACH (vrf, vrf_name_head, &vrfs_by_name) vector_set(comps, XSTRDUP(MTYPE_COMPLETION, vrf->name)); for (ALL_LIST_ELEMENTS_RO(bm->bgp, next, bgp)) { if (bgp->inst_type != BGP_INSTANCE_TYPE_VIEW) continue; vector_set(comps, XSTRDUP(MTYPE_COMPLETION, bgp->name)); } } static void bgp_instasn_autocomplete(vector comps, struct cmd_token *token) { struct listnode *next, *next2; struct bgp *bgp, *bgp2; char buf[11]; for (ALL_LIST_ELEMENTS_RO(bm->bgp, next, bgp)) { /* deduplicate */ for (ALL_LIST_ELEMENTS_RO(bm->bgp, next2, bgp2)) { if (bgp2->as == bgp->as) break; if (bgp2 == bgp) break; } if (bgp2 != bgp) continue; snprintf(buf, sizeof(buf), "%u", bgp->as); vector_set(comps, XSTRDUP(MTYPE_COMPLETION, buf)); } } static const struct cmd_variable_handler bgp_viewvrf_var_handlers[] = { {.tokenname = "VIEWVRFNAME", .completions = bgp_viewvrf_autocomplete}, {.varname = "instasn", .completions = bgp_instasn_autocomplete}, {.completions = NULL}, }; struct frr_pthread *bgp_pth_io; struct frr_pthread *bgp_pth_ka; static void bgp_pthreads_init(void) { assert(!bgp_pth_io); assert(!bgp_pth_ka); frr_pthread_init(); struct frr_pthread_attr io = { .start = frr_pthread_attr_default.start, .stop = frr_pthread_attr_default.stop, }; struct frr_pthread_attr ka = { .start = bgp_keepalives_start, .stop = bgp_keepalives_stop, }; bgp_pth_io = frr_pthread_new(&io, "BGP I/O thread", "bgpd_io"); bgp_pth_ka = frr_pthread_new(&ka, "BGP Keepalives thread", "bgpd_ka"); } void bgp_pthreads_run(void) { frr_pthread_run(bgp_pth_io, NULL); frr_pthread_run(bgp_pth_ka, NULL); /* Wait until threads are ready. */ frr_pthread_wait_running(bgp_pth_io); frr_pthread_wait_running(bgp_pth_ka); } void bgp_pthreads_finish(void) { frr_pthread_stop_all(); frr_pthread_finish(); } void bgp_init(unsigned short instance) { /* allocates some vital data structures used by peer commands in * vty_init */ /* pre-init pthreads */ bgp_pthreads_init(); /* Init zebra. */ bgp_zebra_init(bm->master, instance); #if ENABLE_BGP_VNC vnc_zebra_init(bm->master); #endif /* BGP VTY commands installation. */ bgp_vty_init(); /* BGP inits. */ bgp_attr_init(); bgp_debug_init(); bgp_dump_init(); bgp_route_init(); bgp_route_map_init(); bgp_scan_vty_init(); bgp_mplsvpn_init(); #if ENABLE_BGP_VNC rfapi_init(); #endif bgp_ethernetvpn_init(); bgp_flowspec_vty_init(); /* Access list initialize. */ access_list_init(); access_list_add_hook(peer_distribute_update); access_list_delete_hook(peer_distribute_update); /* Filter list initialize. */ bgp_filter_init(); as_list_add_hook(peer_aslist_add); as_list_delete_hook(peer_aslist_del); /* Prefix list initialize.*/ prefix_list_init(); prefix_list_add_hook(peer_prefix_list_update); prefix_list_delete_hook(peer_prefix_list_update); /* Community list initialize. */ bgp_clist = community_list_init(); /* BFD init */ bgp_bfd_init(); cmd_variable_handler_register(bgp_viewvrf_var_handlers); } void bgp_terminate(void) { struct bgp *bgp; struct peer *peer; struct listnode *node, *nnode; struct listnode *mnode, *mnnode; QOBJ_UNREG(bm); /* Close the listener sockets first as this prevents peers from * attempting * to reconnect on receiving the peer unconfig message. In the presence * of a large number of peers this will ensure that no peer is left with * a dangling connection */ /* reverse bgp_master_init */ bgp_close(); if (bm->listen_sockets) list_delete(&bm->listen_sockets); for (ALL_LIST_ELEMENTS(bm->bgp, mnode, mnnode, bgp)) for (ALL_LIST_ELEMENTS(bgp->peer, node, nnode, peer)) if (peer->status == Established || peer->status == OpenSent || peer->status == OpenConfirm) bgp_notify_send(peer, BGP_NOTIFY_CEASE, BGP_NOTIFY_CEASE_PEER_UNCONFIG); if (bm->process_main_queue) work_queue_free_and_null(&bm->process_main_queue); if (bm->t_rmap_update) BGP_TIMER_OFF(bm->t_rmap_update); bgp_mac_finish(); } struct peer *peer_lookup_in_view(struct vty *vty, struct bgp *bgp, const char *ip_str, bool use_json) { int ret; struct peer *peer; union sockunion su; /* Get peer sockunion. */ ret = str2sockunion(ip_str, &su); if (ret < 0) { peer = peer_lookup_by_conf_if(bgp, ip_str); if (!peer) { peer = peer_lookup_by_hostname(bgp, ip_str); if (!peer) { if (use_json) { json_object *json_no = NULL; json_no = json_object_new_object(); json_object_string_add( json_no, "malformedAddressOrName", ip_str); vty_out(vty, "%s\n", json_object_to_json_string_ext( json_no, JSON_C_TO_STRING_PRETTY)); json_object_free(json_no); } else vty_out(vty, "%% Malformed address or name: %s\n", ip_str); return NULL; } } return peer; } /* Peer structure lookup. */ peer = peer_lookup(bgp, &su); if (!peer) { if (use_json) { json_object *json_no = NULL; json_no = json_object_new_object(); json_object_string_add(json_no, "warning", "No such neighbor in this view/vrf"); vty_out(vty, "%s\n", json_object_to_json_string_ext( json_no, JSON_C_TO_STRING_PRETTY)); json_object_free(json_no); } else vty_out(vty, "No such neighbor in this view/vrf\n"); return NULL; } return peer; } frr-7.2.1/bgpd/bgpd.conf.sample0000644000000000000000000000113013610377563013166 00000000000000! -*- bgp -*- ! ! BGPd sample configuration file ! ! $Id: bgpd.conf.sample,v 1.1 2002/12/13 20:15:29 paul Exp $ ! hostname bgpd password zebra !enable password please-set-at-here ! ! router bgp 7675 ! bgp router-id 10.0.0.1 ! network 10.0.0.0/8 ! neighbor 10.0.0.2 remote-as 7675 ! neighbor 10.0.0.2 ebgp-multihop ! ! address-family ipv4 unicast ! neighbor 10.0.0.2 route-map set-nexthop out ! neighbor 10.0.0.2 next-hop-self ! exit-address-family ! ! access-list all permit any ! !route-map set-nexthop permit 10 ! match ip address all ! set ip next-hop 10.0.0.1 ! !log file bgpd.log ! log stdout frr-7.2.1/bgpd/bgpd.conf.sample20000644000000000000000000000536113610377563013262 00000000000000! ! Zebra configuration saved from vty ! 2002/07/01 03:16:33 ! hostname bgpd password zebra log file bgpd.log log stdout ! router bgp 7675 no bgp default ipv4-unicast neighbor 3ffe:506:1000::2 remote-as 7675 neighbor fe80::200:c0ff:fe30:9be3 remote-as 9377 neighbor fe80::200:c0ff:fe30:9be3 interface sit3 neighbor fe80::210:5aff:fe6b:3cee remote-as 7675 neighbor fe80::210:5aff:fe6b:3cee interface eth0 neighbor fe80::290:27ff:fe51:84c7 remote-as 4691 neighbor fe80::290:27ff:fe51:84c7 description DTI neighbor fe80::290:27ff:fe51:84c7 interface sit7 neighbor fe80::2a0:c9ff:fec8:82ec remote-as 7530 neighbor fe80::2a0:c9ff:fec8:82ec description IRI neighbor fe80::2a0:c9ff:fec8:82ec interface sit8 neighbor fe80::2e0:18ff:fe98:2725 remote-as 2500 neighbor fe80::2e0:18ff:fe98:2725 description WIDE neighbor fe80::2e0:18ff:fe98:2725 interface sit5 neighbor fe80::2e0:18ff:fea8:bf5 remote-as 65000 neighbor fe80::2e0:18ff:fea8:bf5 interface sit6 ! address-family ipv6 network 3ffe:506::/33 network 3ffe:1800:e800::/40 aggregate-address 3ffe:506::/32 redistribute connected neighbor 3ffe:506:1000::2 activate neighbor fe80::200:c0ff:fe30:9be3 activate neighbor fe80::200:c0ff:fe30:9be3 route-map set-nexthop out neighbor fe80::210:5aff:fe6b:3cee activate neighbor fe80::290:27ff:fe51:84c7 activate neighbor fe80::290:27ff:fe51:84c7 route-map set-nexthop out neighbor fe80::2a0:c9ff:fec8:82ec activate neighbor fe80::2a0:c9ff:fec8:82ec route-map set-nexthop out neighbor fe80::2e0:18ff:fe98:2725 activate neighbor fe80::2e0:18ff:fe98:2725 distribute-list nla1 out neighbor fe80::2e0:18ff:fe98:2725 route-map set-nexthop out neighbor fe80::2e0:18ff:fea8:bf5 activate neighbor fe80::2e0:18ff:fea8:bf5 route-map set-nexthop out exit-address-family ! ipv6 access-list all permit any ipv6 access-list nla1 deny 3ffe:506::/33 ipv6 access-list nla1 permit 3ffe:506::/32 ipv6 access-list nla1 deny any ipv6 access-list ntt-nla1 permit 3ffe:1800:0:ffff::c/127 ipv6 access-list ntt-nla1 deny 3ffe:1800:e800::/41 ipv6 access-list ntt-nla1 permit 3ffe:1800:e800::/40 ipv6 access-list ntt-nla1 deny any ! ipv6 prefix-list 6bone-filter seq 5 permit 3ffe::/17 ge 24 le 24 ipv6 prefix-list 6bone-filter seq 10 permit 3ffe:8000::/17 ge 28 le 28 ipv6 prefix-list 6bone-filter seq 12 deny 3ffe::/16 ipv6 prefix-list 6bone-filter seq 15 permit 2000::/3 ge 16 le 16 ipv6 prefix-list 6bone-filter seq 20 permit 2001::/16 ge 35 le 35 ! route-map set-nexthop permit 10 match ipv6 address all set ipv6 next-hop global 3ffe:506::1 set ipv6 next-hop local fe80::cbb5:591a set ip next-hop 203.181.89.26 set community 7675:0 ! route-map set-link-local permit 10 match ipv6 address all set ipv6 next-hop local fe80::cbb5:591a set ipv6 next-hop global 3ffe:1800:0:ffff::d ! line vty ! frr-7.2.1/bgpd/bgpd.conf.vnc.sample0000644000000000000000000000354113610377563013763 00000000000000hostname H192.1.1.1 password zebra #enable password zebra log stdout notifications log monitor notifications #debug bgp line vty exec-timeout 1000 exit router bgp 64512 # Must set a router-id if no zebra (default 0.0.0.0) bgp router-id 192.1.1.1 neighbor 192.1.1.2 remote-as 64512 neighbor 192.1.1.2 description H192.1.1.2 neighbor 192.1.1.2 update-source 192.1.1.1 neighbor 192.1.1.2 advertisement-interval 1 neighbor 192.1.1.3 remote-as 64512 neighbor 192.1.1.3 description H192.1.1.3 neighbor 192.1.1.3 update-source 192.1.1.1 neighbor 192.1.1.3 advertisement-interval 1 address-family ipv4 unicast no neighbor 192.1.1.2 activate no neighbor 192.1.1.3 activate address-family vpnv4 neighbor 192.1.1.2 activate neighbor 192.1.1.3 activate exit-address-family address-family vpnv6 neighbor 192.1.1.2 activate neighbor 192.1.1.3 activate exit-address-family vnc defaults rd auto:vn:5226 response-lifetime 45 rt both 1000:1 1000:2 exit-vnc vnc nve-group group1 prefix vn 172.16.0.0/16 exit-vnc vnc nve-group red prefix vn 10.0.0.0/8 rd auto:vn:10 rt both 1000:10 exit-vnc vnc nve-group blue prefix vn 20.0.0.0/8 rd auto:vn:20 rt both 1000:20 exit-vnc vnc nve-group green prefix vn 30.0.0.0/8 rd auto:vn:20 rt both 1000:30 exit-vnc vnc nve-group rfc4291v6c prefix vn ::ac10:0/112 rd auto:vn:5227 rt both 2000:1 exit-vnc vnc nve-group rfc4291v6m prefix vn ::ffff:ac10:0/112 rd auto:vn:5528 rt both 3000:1 exit-vnc vnc nve-group rfc6052v6 prefix vn 64:ff9b::ac10:0/112 rd auto:vn:5529 rt both 4000:1 exit-vnc exit frr-7.2.1/bgpd/bgpd.h0000644000000000000000000021013513610377563011217 00000000000000/* BGP message definition header. * Copyright (C) 1996, 97, 98, 99, 2000 Kunihiro Ishiguro * * This file is part of GNU Zebra. * * GNU Zebra is free software; you can redistribute it and/or modify it * under the terms of the GNU General Public License as published by the * Free Software Foundation; either version 2, or (at your option) any * later version. * * GNU Zebra 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; see the file COPYING; if not, write to the Free Software * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA */ #ifndef _QUAGGA_BGPD_H #define _QUAGGA_BGPD_H #include "qobj.h" #include #include "hook.h" #include "frr_pthread.h" #include "lib/json.h" #include "vrf.h" #include "vty.h" #include "iana_afi.h" /* For union sockunion. */ #include "queue.h" #include "sockunion.h" #include "routemap.h" #include "linklist.h" #include "defaults.h" #include "bgp_memory.h" #include "bitfield.h" #include "vxlan.h" #include "bgp_labelpool.h" #include "bgp_addpath_types.h" #define BGP_MAX_HOSTNAME 64 /* Linux max, is larger than most other sys */ #define BGP_PEER_MAX_HASH_SIZE 16384 /* Default interval for IPv6 RAs when triggered by BGP unnumbered neighbor. */ #define BGP_UNNUM_DEFAULT_RA_INTERVAL 10 struct update_subgroup; struct bpacket; struct bgp_pbr_config; /* * Allow the neighbor XXXX remote-as to take internal or external * AS_SPECIFIED is zero to auto-inherit original non-feature/enhancement * behavior * in the system. */ enum { AS_UNSPECIFIED = 0, AS_SPECIFIED, AS_INTERNAL, AS_EXTERNAL, }; /* Typedef BGP specific types. */ typedef uint32_t as_t; typedef uint16_t as16_t; /* we may still encounter 16 Bit asnums */ typedef uint16_t bgp_size_t; #define max(a, b) \ ({ \ __typeof__(a) _a = (a); \ __typeof__(b) _b = (b); \ _a > _b ? _a : _b; \ }) enum bgp_af_index { BGP_AF_START, BGP_AF_IPV4_UNICAST = BGP_AF_START, BGP_AF_IPV4_MULTICAST, BGP_AF_IPV4_VPN, BGP_AF_IPV6_UNICAST, BGP_AF_IPV6_MULTICAST, BGP_AF_IPV6_VPN, BGP_AF_IPV4_ENCAP, BGP_AF_IPV6_ENCAP, BGP_AF_L2VPN_EVPN, BGP_AF_IPV4_LBL_UNICAST, BGP_AF_IPV6_LBL_UNICAST, BGP_AF_IPV4_FLOWSPEC, BGP_AF_IPV6_FLOWSPEC, BGP_AF_MAX }; #define AF_FOREACH(af) for ((af) = BGP_AF_START; (af) < BGP_AF_MAX; (af)++) #define FOREACH_AFI_SAFI(afi, safi) \ for (afi = AFI_IP; afi < AFI_MAX; afi++) \ for (safi = SAFI_UNICAST; safi < SAFI_MAX; safi++) #define FOREACH_SAFI(safi) \ for (safi = SAFI_UNICAST; safi < SAFI_MAX; safi++) extern struct frr_pthread *bgp_pth_io; extern struct frr_pthread *bgp_pth_ka; /* BGP master for system wide configurations and variables. */ struct bgp_master { /* BGP instance list. */ struct list *bgp; /* BGP thread master. */ struct thread_master *master; /* work queues */ struct work_queue *process_main_queue; /* Listening sockets */ struct list *listen_sockets; /* BGP port number. */ uint16_t port; /* Listener address */ char *address; /* The Mac table */ struct hash *self_mac_hash; /* BGP start time. */ time_t start_time; /* Various BGP global configuration. */ uint8_t options; #define BGP_OPT_NO_FIB (1 << 0) #define BGP_OPT_NO_LISTEN (1 << 1) #define BGP_OPT_NO_ZEBRA (1 << 2) uint64_t updgrp_idspace; uint64_t subgrp_idspace; /* timer to dampen route map changes */ struct thread *t_rmap_update; /* Handle route map updates */ uint32_t rmap_update_timer; /* Route map update timer */ #define RMAP_DEFAULT_UPDATE_TIMER 5 /* disabled by default */ /* Id space for automatic RD derivation for an EVI/VRF */ bitfield_t rd_idspace; /* dynamic mpls label allocation pool */ struct labelpool labelpool; /* BGP-EVPN VRF ID. Defaults to default VRF (if any) */ struct bgp* bgp_evpn; bool terminating; /* global flag that sigint terminate seen */ QOBJ_FIELDS }; DECLARE_QOBJ_TYPE(bgp_master) /* BGP route-map structure. */ struct bgp_rmap { char *name; struct route_map *map; }; struct bgp_redist { unsigned short instance; /* BGP redistribute metric configuration. */ uint8_t redist_metric_flag; uint32_t redist_metric; /* BGP redistribute route-map. */ struct bgp_rmap rmap; }; typedef enum { BGP_VPN_POLICY_DIR_FROMVPN = 0, BGP_VPN_POLICY_DIR_TOVPN = 1, BGP_VPN_POLICY_DIR_MAX = 2 } vpn_policy_direction_t; struct vpn_policy { struct bgp *bgp; /* parent */ afi_t afi; struct ecommunity *rtlist[BGP_VPN_POLICY_DIR_MAX]; struct ecommunity *import_redirect_rtlist; char *rmap_name[BGP_VPN_POLICY_DIR_MAX]; struct route_map *rmap[BGP_VPN_POLICY_DIR_MAX]; /* should be mpls_label_t? */ uint32_t tovpn_label; /* may be MPLS_LABEL_NONE */ uint32_t tovpn_zebra_vrf_label_last_sent; struct prefix_rd tovpn_rd; struct prefix tovpn_nexthop; /* unset => set to 0 */ uint32_t flags; #define BGP_VPN_POLICY_TOVPN_LABEL_AUTO (1 << 0) #define BGP_VPN_POLICY_TOVPN_RD_SET (1 << 1) #define BGP_VPN_POLICY_TOVPN_NEXTHOP_SET (1 << 2) /* * If we are importing another vrf into us keep a list of * vrf names that are being imported into us. */ struct list *import_vrf; /* * if we are being exported to another vrf keep a list of * vrf names that we are being exported to. */ struct list *export_vrf; }; /* * Type of 'struct bgp'. * - Default: The default instance * - VRF: A specific (non-default) VRF * - View: An instance used for route exchange * The "default" instance is treated separately to simplify the code. Note * that if deployed in a Multi-VRF environment, it may not exist. */ enum bgp_instance_type { BGP_INSTANCE_TYPE_DEFAULT, BGP_INSTANCE_TYPE_VRF, BGP_INSTANCE_TYPE_VIEW }; /* BGP instance structure. */ struct bgp { /* AS number of this BGP instance. */ as_t as; /* Name of this BGP instance. */ char *name; char *name_pretty; /* printable "VRF|VIEW name|default" */ /* Type of instance and VRF id. */ enum bgp_instance_type inst_type; vrf_id_t vrf_id; /* Reference count to allow peer_delete to finish after bgp_delete */ int lock; /* Self peer. */ struct peer *peer_self; /* BGP peer. */ struct list *peer; struct hash *peerhash; /* BGP peer group. */ struct list *group; /* The maximum number of BGP dynamic neighbors that can be created */ int dynamic_neighbors_limit; /* The current number of BGP dynamic neighbors */ int dynamic_neighbors_count; struct hash *update_groups[BGP_AF_MAX]; /* * Global statistics for update groups. */ struct { uint32_t join_events; uint32_t prune_events; uint32_t merge_events; uint32_t split_events; uint32_t updgrp_switch_events; uint32_t peer_refreshes_combined; uint32_t adj_count; uint32_t merge_checks_triggered; uint32_t updgrps_created; uint32_t updgrps_deleted; uint32_t subgrps_created; uint32_t subgrps_deleted; } update_group_stats; /* BGP configuration. */ uint16_t config; #define BGP_CONFIG_CLUSTER_ID (1 << 0) #define BGP_CONFIG_CONFEDERATION (1 << 1) /* BGP router identifier. */ struct in_addr router_id; struct in_addr router_id_static; struct in_addr router_id_zebra; /* BGP route reflector cluster ID. */ struct in_addr cluster_id; /* BGP confederation information. */ as_t confed_id; as_t *confed_peers; int confed_peers_cnt; struct thread *t_startup; /* start-up timer on only once at the beginning */ uint32_t v_maxmed_onstartup; /* Duration of max-med on start-up */ #define BGP_MAXMED_ONSTARTUP_UNCONFIGURED 0 /* 0 means off, its the default */ uint32_t maxmed_onstartup_value; /* Max-med value when active on start-up */ struct thread *t_maxmed_onstartup; /* non-null when max-med onstartup is on */ uint8_t maxmed_onstartup_over; /* Flag to make it effective only once */ uint8_t v_maxmed_admin; /* 1/0 if max-med administrative is on/off */ #define BGP_MAXMED_ADMIN_UNCONFIGURED 0 /* Off by default */ uint32_t maxmed_admin_value; /* Max-med value when administrative in on */ #define BGP_MAXMED_VALUE_DEFAULT 4294967294 /* Maximum by default */ uint8_t maxmed_active; /* 1/0 if max-med is active or not */ uint32_t maxmed_value; /* Max-med value when its active */ /* BGP update delay on startup */ struct thread *t_update_delay; struct thread *t_establish_wait; uint8_t update_delay_over; uint8_t main_zebra_update_hold; uint8_t main_peers_update_hold; uint16_t v_update_delay; uint16_t v_establish_wait; char update_delay_begin_time[64]; char update_delay_end_time[64]; char update_delay_zebra_resume_time[64]; char update_delay_peers_resume_time[64]; uint32_t established; uint32_t restarted_peers; uint32_t implicit_eors; uint32_t explicit_eors; #define BGP_UPDATE_DELAY_DEF 0 #define BGP_UPDATE_DELAY_MIN 0 #define BGP_UPDATE_DELAY_MAX 3600 /* BGP flags. */ uint32_t flags; #define BGP_FLAG_ALWAYS_COMPARE_MED (1 << 0) #define BGP_FLAG_DETERMINISTIC_MED (1 << 1) #define BGP_FLAG_MED_MISSING_AS_WORST (1 << 2) #define BGP_FLAG_MED_CONFED (1 << 3) #define BGP_FLAG_NO_DEFAULT_IPV4 (1 << 4) #define BGP_FLAG_NO_CLIENT_TO_CLIENT (1 << 5) #define BGP_FLAG_COMPARE_ROUTER_ID (1 << 7) #define BGP_FLAG_ASPATH_IGNORE (1 << 8) #define BGP_FLAG_IMPORT_CHECK (1 << 9) #define BGP_FLAG_NO_FAST_EXT_FAILOVER (1 << 10) #define BGP_FLAG_LOG_NEIGHBOR_CHANGES (1 << 11) #define BGP_FLAG_GRACEFUL_RESTART (1 << 12) #define BGP_FLAG_ASPATH_CONFED (1 << 13) #define BGP_FLAG_ASPATH_MULTIPATH_RELAX (1 << 14) #define BGP_FLAG_RR_ALLOW_OUTBOUND_POLICY (1 << 15) #define BGP_FLAG_DISABLE_NH_CONNECTED_CHK (1 << 16) #define BGP_FLAG_MULTIPATH_RELAX_AS_SET (1 << 17) #define BGP_FLAG_FORCE_STATIC_PROCESS (1 << 18) #define BGP_FLAG_SHOW_HOSTNAME (1 << 19) #define BGP_FLAG_GR_PRESERVE_FWD (1 << 20) #define BGP_FLAG_GRACEFUL_SHUTDOWN (1 << 21) #define BGP_FLAG_DELETE_IN_PROGRESS (1 << 22) /* BGP Per AF flags */ uint16_t af_flags[AFI_MAX][SAFI_MAX]; #define BGP_CONFIG_DAMPENING (1 << 0) /* l2vpn evpn flags - 1 << 0 is used for DAMPENNG */ #define BGP_L2VPN_EVPN_ADVERTISE_IPV4_UNICAST (1 << 1) #define BGP_L2VPN_EVPN_ADVERTISE_IPV6_UNICAST (1 << 2) #define BGP_L2VPN_EVPN_DEFAULT_ORIGINATE_IPV4 (1 << 3) #define BGP_L2VPN_EVPN_DEFAULT_ORIGINATE_IPV6 (1 << 4) /* import/export between address families */ #define BGP_CONFIG_VRF_TO_MPLSVPN_EXPORT (1 << 5) #define BGP_CONFIG_MPLSVPN_TO_VRF_IMPORT (1 << 6) /* vrf-route leaking flags */ #define BGP_CONFIG_VRF_TO_VRF_IMPORT (1 << 7) #define BGP_CONFIG_VRF_TO_VRF_EXPORT (1 << 8) /* BGP per AF peer count */ uint32_t af_peer_count[AFI_MAX][SAFI_MAX]; /* Route table for next-hop lookup cache. */ struct bgp_table *nexthop_cache_table[AFI_MAX]; /* Route table for import-check */ struct bgp_table *import_check_table[AFI_MAX]; struct bgp_table *connected_table[AFI_MAX]; struct hash *address_hash; /* DB for all local tunnel-ips - used mainly for martian checks Currently it only has all VxLan tunnel IPs*/ struct hash *tip_hash; /* Static route configuration. */ struct bgp_table *route[AFI_MAX][SAFI_MAX]; /* Aggregate address configuration. */ struct bgp_table *aggregate[AFI_MAX][SAFI_MAX]; /* BGP routing information base. */ struct bgp_table *rib[AFI_MAX][SAFI_MAX]; /* BGP table route-map. */ struct bgp_rmap table_map[AFI_MAX][SAFI_MAX]; /* BGP redistribute configuration. */ struct list *redist[AFI_MAX][ZEBRA_ROUTE_MAX]; /* Allocate MPLS labels */ uint8_t allocate_mpls_labels[AFI_MAX][SAFI_MAX]; /* Allocate hash entries to store policy routing information * The hash are used to host pbr rules somewhere. * Actually, pbr will only be used by flowspec * those hash elements will have relationship together as * illustrated in below diagram: * * pbr_action a <----- pbr_match i <--- pbr_match_entry 1..n * <----- pbr_match j <--- pbr_match_entry 1..m * <----- pbr_rule k * * - here in BGP structure, the list of match and actions will * stand for the list of ipset sets, and table_ids in the kernel * - the arrow above between pbr_match and pbr_action indicate * that a backpointer permits match to find the action * - the arrow betwen match_entry and match is a hash list * contained in match, that lists the whole set of entries */ struct hash *pbr_match_hash; struct hash *pbr_rule_hash; struct hash *pbr_action_hash; /* timer to re-evaluate neighbor default-originate route-maps */ struct thread *t_rmap_def_originate_eval; #define RMAP_DEFAULT_ORIGINATE_EVAL_TIMER 5 /* BGP distance configuration. */ uint8_t distance_ebgp[AFI_MAX][SAFI_MAX]; uint8_t distance_ibgp[AFI_MAX][SAFI_MAX]; uint8_t distance_local[AFI_MAX][SAFI_MAX]; /* BGP default local-preference. */ uint32_t default_local_pref; /* BGP default subgroup pkt queue max */ uint32_t default_subgroup_pkt_queue_max; /* BGP default timer. */ uint32_t default_holdtime; uint32_t default_keepalive; /* BGP graceful restart */ uint32_t restart_time; uint32_t stalepath_time; /* Maximum-paths configuration */ struct bgp_maxpaths_cfg { uint16_t maxpaths_ebgp; uint16_t maxpaths_ibgp; uint16_t ibgp_flags; #define BGP_FLAG_IBGP_MULTIPATH_SAME_CLUSTERLEN (1 << 0) } maxpaths[AFI_MAX][SAFI_MAX]; _Atomic uint32_t wpkt_quanta; // max # packets to write per i/o cycle _Atomic uint32_t rpkt_quanta; // max # packets to read per i/o cycle /* Automatic coalesce adjust on/off */ bool heuristic_coalesce; /* Actual coalesce time */ uint32_t coalesce_time; /* Auto-shutdown new peers */ bool autoshutdown; struct bgp_addpath_bgp_data tx_addpath; #if ENABLE_BGP_VNC struct rfapi_cfg *rfapi_cfg; struct rfapi *rfapi; #endif /* EVPN related information */ /* EVI hash table */ struct hash *vnihash; /* EVPN enable - advertise gateway macip routes */ int advertise_gw_macip; /* EVPN enable - advertise local VNIs and their MACs etc. */ int advertise_all_vni; /* RFC 8212 - prevent route leaks. */ int ebgp_requires_policy; #define DEFAULT_EBGP_POLICY_DISABLED 0 #define DEFAULT_EBGP_POLICY_ENABLED 1 struct bgp_evpn_info *evpn_info; /* EVPN - use RFC 8365 to auto-derive RT */ int advertise_autort_rfc8365; /* * Flooding mechanism for BUM packets for VxLAN-EVPN. */ enum vxlan_flood_control vxlan_flood_ctrl; /* Hash table of Import RTs to EVIs */ struct hash *import_rt_hash; /* Hash table of VRF import RTs to VRFs */ struct hash *vrf_import_rt_hash; /* L3-VNI corresponding to this vrf */ vni_t l3vni; /* router-mac to be used in mac-ip routes for this vrf */ struct ethaddr rmac; /* originator ip - to be used as NH for type-5 routes */ struct in_addr originator_ip; /* SVI associated with the L3-VNI corresponding to this vrf */ ifindex_t l3vni_svi_ifindex; /* vrf flags */ uint32_t vrf_flags; #define BGP_VRF_AUTO (1 << 0) #define BGP_VRF_IMPORT_RT_CFGD (1 << 1) #define BGP_VRF_EXPORT_RT_CFGD (1 << 2) #define BGP_VRF_RD_CFGD (1 << 3) #define BGP_VRF_L3VNI_PREFIX_ROUTES_ONLY (1 << 4) /* unique ID for auto derivation of RD for this vrf */ uint16_t vrf_rd_id; /* Automatically derived RD for this VRF */ struct prefix_rd vrf_prd_auto; /* RD for this VRF */ struct prefix_rd vrf_prd; /* import rt list for the vrf instance */ struct list *vrf_import_rtl; /* export rt list for the vrf instance */ struct list *vrf_export_rtl; /* list of corresponding l2vnis (struct bgpevpn) */ struct list *l2vnis; /* route map for advertise ipv4/ipv6 unicast (type-5 routes) */ struct bgp_rmap adv_cmd_rmap[AFI_MAX][SAFI_MAX]; struct vpn_policy vpn_policy[AFI_MAX]; struct bgp_pbr_config *bgp_pbr_cfg; /* local esi hash table */ struct hash *esihash; /* Count of peers in established state */ uint32_t established_peers; QOBJ_FIELDS }; DECLARE_QOBJ_TYPE(bgp) DECLARE_HOOK(bgp_inst_delete, (struct bgp *bgp), (bgp)) DECLARE_HOOK(bgp_inst_config_write, (struct bgp *bgp, struct vty *vty), (bgp, vty)) #define BGP_ROUTE_ADV_HOLD(bgp) (bgp->main_peers_update_hold) #define IS_BGP_INST_KNOWN_TO_ZEBRA(bgp) \ (bgp->inst_type == BGP_INSTANCE_TYPE_DEFAULT \ || (bgp->inst_type == BGP_INSTANCE_TYPE_VRF \ && bgp->vrf_id != VRF_UNKNOWN)) /* BGP peer-group support. */ struct peer_group { /* Name of the peer-group. */ char *name; /* Pointer to BGP. */ struct bgp *bgp; /* Peer-group client list. */ struct list *peer; /** Dynamic neighbor listening ranges */ struct list *listen_range[AFI_MAX]; /* Peer-group config */ struct peer *conf; }; /* BGP Notify message format. */ struct bgp_notify { uint8_t code; uint8_t subcode; char *data; bgp_size_t length; uint8_t *raw_data; }; /* Next hop self address. */ struct bgp_nexthop { struct interface *ifp; struct in_addr v4; struct in6_addr v6_global; struct in6_addr v6_local; }; /* BGP addpath values */ #define BGP_ADDPATH_RX 1 #define BGP_ADDPATH_TX 2 #define BGP_ADDPATH_ID_LEN 4 #define BGP_ADDPATH_TX_ID_FOR_DEFAULT_ORIGINATE 1 /* Route map direction */ #define RMAP_IN 0 #define RMAP_OUT 1 #define RMAP_MAX 2 #define BGP_DEFAULT_TTL 1 #include "filter.h" /* BGP filter structure. */ struct bgp_filter { /* Distribute-list. */ struct { char *name; struct access_list *alist; } dlist[FILTER_MAX]; /* Prefix-list. */ struct { char *name; struct prefix_list *plist; } plist[FILTER_MAX]; /* Filter-list. */ struct { char *name; struct as_list *aslist; } aslist[FILTER_MAX]; /* Route-map. */ struct { char *name; struct route_map *map; } map[RMAP_MAX]; /* Unsuppress-map. */ struct { char *name; struct route_map *map; } usmap; }; /* IBGP/EBGP identifier. We also have a CONFED peer, which is to say, a peer who's AS is part of our Confederation. */ typedef enum { BGP_PEER_UNSPECIFIED, BGP_PEER_IBGP, BGP_PEER_EBGP, BGP_PEER_INTERNAL, BGP_PEER_CONFED, } bgp_peer_sort_t; /* BGP message header and packet size. */ #define BGP_MARKER_SIZE 16 #define BGP_HEADER_SIZE 19 #define BGP_MAX_PACKET_SIZE 4096 #define BGP_MAX_PACKET_SIZE_OVERFLOW 1024 /* * Trigger delay for bgp_announce_route(). */ #define BGP_ANNOUNCE_ROUTE_SHORT_DELAY_MS 100 #define BGP_ANNOUNCE_ROUTE_DELAY_MS 500 struct peer_af { /* back pointer to the peer */ struct peer *peer; /* which subgroup the peer_af belongs to */ struct update_subgroup *subgroup; /* for being part of an update subgroup's peer list */ LIST_ENTRY(peer_af) subgrp_train; /* for being part of a packet's peer list */ LIST_ENTRY(peer_af) pkt_train; struct bpacket *next_pkt_to_send; /* * Trigger timer for bgp_announce_route(). */ struct thread *t_announce_route; afi_t afi; safi_t safi; int afid; }; /* BGP neighbor structure. */ struct peer { /* BGP structure. */ struct bgp *bgp; /* reference count, primarily to allow bgp_process'ing of route_node's * to be done after a struct peer is deleted. * * named 'lock' for hysterical reasons within Quagga. */ int lock; /* BGP peer group. */ struct peer_group *group; uint64_t version[AFI_MAX][SAFI_MAX]; /* BGP peer_af structures, per configured AF on this peer */ struct peer_af *peer_af_array[BGP_AF_MAX]; /* Peer's remote AS number. */ int as_type; as_t as; /* Peer's local AS number. */ as_t local_as; bgp_peer_sort_t sort; /* Peer's Change local AS number. */ as_t change_local_as; /* Remote router ID. */ struct in_addr remote_id; /* Local router ID. */ struct in_addr local_id; /* Packet receive and send buffer. */ pthread_mutex_t io_mtx; // guards ibuf, obuf struct stream_fifo *ibuf; // packets waiting to be processed struct stream_fifo *obuf; // packets waiting to be written struct ringbuf *ibuf_work; // WiP buffer used by bgp_read() only struct stream *obuf_work; // WiP buffer used to construct packets struct stream *curr; // the current packet being parsed /* We use a separate stream to encode MP_REACH_NLRI for efficient * NLRI packing. peer->obuf_work stores all the other attributes. The * actual packet is then constructed by concatenating the two. */ struct stream *scratch; /* the doppelganger peer structure, due to dual TCP conn setup */ struct peer *doppelganger; /* Status of the peer. */ int status; int ostatus; /* FSM events, stored for debug purposes. * Note: uchar used for reduced memory usage. */ unsigned char cur_event; unsigned char last_event; unsigned char last_major_event; /* Peer index, used for dumping TABLE_DUMP_V2 format */ uint16_t table_dump_index; /* Peer information */ int fd; /* File descriptor */ int ttl; /* TTL of TCP connection to the peer. */ int rtt; /* Estimated round-trip-time from TCP_INFO */ int gtsm_hops; /* minimum hopcount to peer */ char *desc; /* Description of the peer. */ unsigned short port; /* Destination port for peer */ char *host; /* Printable address of the peer. */ union sockunion su; /* Sockunion address of the peer. */ #define BGP_PEER_SU_UNSPEC(peer) (peer->su.sa.sa_family == AF_UNSPEC) time_t uptime; /* Last Up/Down time */ time_t readtime; /* Last read time */ time_t resettime; /* Last reset time */ char *conf_if; /* neighbor interface config name. */ struct interface *ifp; /* corresponding interface */ char *ifname; /* bind interface name. */ char *update_if; union sockunion *update_source; union sockunion *su_local; /* Sockunion of local address. */ union sockunion *su_remote; /* Sockunion of remote address. */ int shared_network; /* Is this peer shared same network. */ struct bgp_nexthop nexthop; /* Nexthop */ /* Peer address family configuration. */ uint8_t afc[AFI_MAX][SAFI_MAX]; uint8_t afc_nego[AFI_MAX][SAFI_MAX]; uint8_t afc_adv[AFI_MAX][SAFI_MAX]; uint8_t afc_recv[AFI_MAX][SAFI_MAX]; /* Capability flags (reset in bgp_stop) */ uint32_t cap; #define PEER_CAP_REFRESH_ADV (1 << 0) /* refresh advertised */ #define PEER_CAP_REFRESH_OLD_RCV (1 << 1) /* refresh old received */ #define PEER_CAP_REFRESH_NEW_RCV (1 << 2) /* refresh rfc received */ #define PEER_CAP_DYNAMIC_ADV (1 << 3) /* dynamic advertised */ #define PEER_CAP_DYNAMIC_RCV (1 << 4) /* dynamic received */ #define PEER_CAP_RESTART_ADV (1 << 5) /* restart advertised */ #define PEER_CAP_RESTART_RCV (1 << 6) /* restart received */ #define PEER_CAP_AS4_ADV (1 << 7) /* as4 advertised */ #define PEER_CAP_AS4_RCV (1 << 8) /* as4 received */ #define PEER_CAP_RESTART_BIT_ADV (1 << 9) /* sent restart state */ #define PEER_CAP_RESTART_BIT_RCV (1 << 10) /* peer restart state */ #define PEER_CAP_ADDPATH_ADV (1 << 11) /* addpath advertised */ #define PEER_CAP_ADDPATH_RCV (1 << 12) /* addpath received */ #define PEER_CAP_ENHE_ADV (1 << 13) /* Extended nexthop advertised */ #define PEER_CAP_ENHE_RCV (1 << 14) /* Extended nexthop received */ #define PEER_CAP_HOSTNAME_ADV (1 << 15) /* hostname advertised */ #define PEER_CAP_HOSTNAME_RCV (1 << 16) /* hostname received */ /* Capability flags (reset in bgp_stop) */ uint32_t af_cap[AFI_MAX][SAFI_MAX]; #define PEER_CAP_ORF_PREFIX_SM_ADV (1 << 0) /* send-mode advertised */ #define PEER_CAP_ORF_PREFIX_RM_ADV (1 << 1) /* receive-mode advertised */ #define PEER_CAP_ORF_PREFIX_SM_RCV (1 << 2) /* send-mode received */ #define PEER_CAP_ORF_PREFIX_RM_RCV (1 << 3) /* receive-mode received */ #define PEER_CAP_ORF_PREFIX_SM_OLD_RCV (1 << 4) /* send-mode received */ #define PEER_CAP_ORF_PREFIX_RM_OLD_RCV (1 << 5) /* receive-mode received */ #define PEER_CAP_RESTART_AF_RCV (1 << 6) /* graceful restart afi/safi received */ #define PEER_CAP_RESTART_AF_PRESERVE_RCV (1 << 7) /* graceful restart afi/safi F-bit received */ #define PEER_CAP_ADDPATH_AF_TX_ADV (1 << 8) /* addpath tx advertised */ #define PEER_CAP_ADDPATH_AF_TX_RCV (1 << 9) /* addpath tx received */ #define PEER_CAP_ADDPATH_AF_RX_ADV (1 << 10) /* addpath rx advertised */ #define PEER_CAP_ADDPATH_AF_RX_RCV (1 << 11) /* addpath rx received */ #define PEER_CAP_ENHE_AF_ADV (1 << 12) /* Extended nexthopi afi/safi advertised */ #define PEER_CAP_ENHE_AF_RCV (1 << 13) /* Extended nexthop afi/safi received */ #define PEER_CAP_ENHE_AF_NEGO (1 << 14) /* Extended nexthop afi/safi negotiated */ /* Global configuration flags. */ /* * Parallel array to flags that indicates whether each flag originates * from a peer-group or if it is config that is specific to this * individual peer. If a flag is set independent of the peer-group, the * same bit should be set here. If this peer is a peer-group, this * memory region should be all zeros. * * The assumption is that the default state for all flags is unset, * so if a flag is unset, the corresponding override flag is unset too. * However if a flag is set, the corresponding override flag is set. */ uint32_t flags_override; /* * Parallel array to flags that indicates whether the default behavior * of *flags_override* should be inverted. If a flag is unset and the * corresponding invert flag is set, the corresponding override flag * would be set. However if a flag is set and the corresponding invert * flag is unset, the corresponding override flag would be unset. * * This can be used for attributes like *send-community*, which are * implicitely enabled and have to be disabled explicitely, compared to * 'normal' attributes like *next-hop-self* which are implicitely set. * * All operations dealing with flags should apply the following boolean * logic to keep the internal flag system in a sane state: * * value=0 invert=0 Inherit flag if member, otherwise unset flag * value=0 invert=1 Unset flag unconditionally * value=1 invert=0 Set flag unconditionally * value=1 invert=1 Inherit flag if member, otherwise set flag * * Contrary to the implementation of *flags_override*, the flag * inversion state can be set either on the peer OR the peer *and* the * peer-group. This was done on purpose, as the inversion state of a * flag can be determined on either the peer or the peer-group. * * Example: Enabling the cisco configuration mode inverts all flags * related to *send-community* unconditionally for both peer-groups and * peers. * * This behavior is different for interface peers though, which enable * the *extended-nexthop* flag by default, which regular peers do not. * As the peer-group can contain both regular and interface peers, the * flag inversion state must be set on the peer only. * * When a peer inherits the configuration from a peer-group and the * inversion state of the flag differs between peer and peer-group, the * newly set value must equal to the inverted state of the peer-group. */ uint32_t flags_invert; /* * Effective array for storing the peer/peer-group flags. In case of a * peer-group, the peer-specific overrides (see flags_override and * flags_invert) must be respected. */ uint32_t flags; #define PEER_FLAG_PASSIVE (1 << 0) /* passive mode */ #define PEER_FLAG_SHUTDOWN (1 << 1) /* shutdown */ #define PEER_FLAG_DONT_CAPABILITY (1 << 2) /* dont-capability */ #define PEER_FLAG_OVERRIDE_CAPABILITY (1 << 3) /* override-capability */ #define PEER_FLAG_STRICT_CAP_MATCH (1 << 4) /* strict-match */ #define PEER_FLAG_DYNAMIC_CAPABILITY (1 << 5) /* dynamic capability */ #define PEER_FLAG_DISABLE_CONNECTED_CHECK (1 << 6) /* disable-connected-check */ #define PEER_FLAG_LOCAL_AS_NO_PREPEND (1 << 7) /* local-as no-prepend */ #define PEER_FLAG_LOCAL_AS_REPLACE_AS (1 << 8) /* local-as no-prepend replace-as */ #define PEER_FLAG_DELETE (1 << 9) /* mark the peer for deleting */ #define PEER_FLAG_CONFIG_NODE (1 << 10) /* the node to update configs on */ #define PEER_FLAG_LONESOUL (1 << 11) #define PEER_FLAG_DYNAMIC_NEIGHBOR (1 << 12) /* dynamic neighbor */ #define PEER_FLAG_CAPABILITY_ENHE (1 << 13) /* Extended next-hop (rfc 5549)*/ #define PEER_FLAG_IFPEER_V6ONLY (1 << 14) /* if-based peer is v6 only */ #define PEER_FLAG_IS_RFAPI_HD (1 << 15) /* attached to rfapi HD */ #define PEER_FLAG_ENFORCE_FIRST_AS (1 << 16) /* enforce-first-as */ #define PEER_FLAG_ROUTEADV (1 << 17) /* route advertise */ #define PEER_FLAG_TIMER (1 << 18) /* keepalive & holdtime */ #define PEER_FLAG_TIMER_CONNECT (1 << 19) /* connect timer */ #define PEER_FLAG_PASSWORD (1 << 20) /* password */ #define PEER_FLAG_LOCAL_AS (1 << 21) /* local-as */ #define PEER_FLAG_UPDATE_SOURCE (1 << 22) /* update-source */ /* outgoing message sent in CEASE_ADMIN_SHUTDOWN notify */ char *tx_shutdown_message; /* NSF mode (graceful restart) */ uint8_t nsf[AFI_MAX][SAFI_MAX]; /* Peer Per AF flags */ /* * Please consult the comments for *flags_override*, *flags_invert* and * *flags* to understand what these three arrays do. The address-family * specific attributes are being treated the exact same way as global * peer attributes. */ uint32_t af_flags_override[AFI_MAX][SAFI_MAX]; uint32_t af_flags_invert[AFI_MAX][SAFI_MAX]; uint32_t af_flags[AFI_MAX][SAFI_MAX]; #define PEER_FLAG_SEND_COMMUNITY (1 << 0) /* send-community */ #define PEER_FLAG_SEND_EXT_COMMUNITY (1 << 1) /* send-community ext. */ #define PEER_FLAG_NEXTHOP_SELF (1 << 2) /* next-hop-self */ #define PEER_FLAG_REFLECTOR_CLIENT (1 << 3) /* reflector-client */ #define PEER_FLAG_RSERVER_CLIENT (1 << 4) /* route-server-client */ #define PEER_FLAG_SOFT_RECONFIG (1 << 5) /* soft-reconfiguration */ #define PEER_FLAG_AS_PATH_UNCHANGED (1 << 6) /* transparent-as */ #define PEER_FLAG_NEXTHOP_UNCHANGED (1 << 7) /* transparent-next-hop */ #define PEER_FLAG_MED_UNCHANGED (1 << 8) /* transparent-next-hop */ #define PEER_FLAG_DEFAULT_ORIGINATE (1 << 9) /* default-originate */ #define PEER_FLAG_REMOVE_PRIVATE_AS (1 << 10) /* remove-private-as */ #define PEER_FLAG_ALLOWAS_IN (1 << 11) /* set allowas-in */ #define PEER_FLAG_ORF_PREFIX_SM (1 << 12) /* orf capability send-mode */ #define PEER_FLAG_ORF_PREFIX_RM (1 << 13) /* orf capability receive-mode */ #define PEER_FLAG_MAX_PREFIX (1 << 14) /* maximum prefix */ #define PEER_FLAG_MAX_PREFIX_WARNING (1 << 15) /* maximum prefix warning-only */ #define PEER_FLAG_NEXTHOP_LOCAL_UNCHANGED (1 << 16) /* leave link-local nexthop unchanged */ #define PEER_FLAG_FORCE_NEXTHOP_SELF (1 << 17) /* next-hop-self force */ #define PEER_FLAG_REMOVE_PRIVATE_AS_ALL (1 << 18) /* remove-private-as all */ #define PEER_FLAG_REMOVE_PRIVATE_AS_REPLACE (1 << 19) /* remove-private-as replace-as */ #define PEER_FLAG_AS_OVERRIDE (1 << 20) /* as-override */ #define PEER_FLAG_REMOVE_PRIVATE_AS_ALL_REPLACE (1 << 21) /* remove-private-as all replace-as */ #define PEER_FLAG_WEIGHT (1 << 24) /* weight */ #define PEER_FLAG_ALLOWAS_IN_ORIGIN (1 << 25) /* allowas-in origin */ #define PEER_FLAG_SEND_LARGE_COMMUNITY (1 << 26) /* Send large Communities */ enum bgp_addpath_strat addpath_type[AFI_MAX][SAFI_MAX]; /* MD5 password */ char *password; /* default-originate route-map. */ struct { char *name; struct route_map *map; } default_rmap[AFI_MAX][SAFI_MAX]; /* Peer status flags. */ uint16_t sflags; #define PEER_STATUS_ACCEPT_PEER (1 << 0) /* accept peer */ #define PEER_STATUS_PREFIX_OVERFLOW (1 << 1) /* prefix-overflow */ #define PEER_STATUS_CAPABILITY_OPEN (1 << 2) /* capability open send */ #define PEER_STATUS_HAVE_ACCEPT (1 << 3) /* accept peer's parent */ #define PEER_STATUS_GROUP (1 << 4) /* peer-group conf */ #define PEER_STATUS_NSF_MODE (1 << 5) /* NSF aware peer */ #define PEER_STATUS_NSF_WAIT (1 << 6) /* wait comeback peer */ /* Peer status af flags (reset in bgp_stop) */ uint16_t af_sflags[AFI_MAX][SAFI_MAX]; #define PEER_STATUS_ORF_PREFIX_SEND (1 << 0) /* prefix-list send peer */ #define PEER_STATUS_ORF_WAIT_REFRESH (1 << 1) /* wait refresh received peer */ #define PEER_STATUS_PREFIX_THRESHOLD (1 << 2) /* exceed prefix-threshold */ #define PEER_STATUS_PREFIX_LIMIT (1 << 3) /* exceed prefix-limit */ #define PEER_STATUS_EOR_SEND (1 << 4) /* end-of-rib send to peer */ #define PEER_STATUS_EOR_RECEIVED (1 << 5) /* end-of-rib received from peer */ /* Configured timer values. */ _Atomic uint32_t holdtime; _Atomic uint32_t keepalive; _Atomic uint32_t connect; _Atomic uint32_t routeadv; /* Timer values. */ _Atomic uint32_t v_start; _Atomic uint32_t v_connect; _Atomic uint32_t v_holdtime; _Atomic uint32_t v_keepalive; _Atomic uint32_t v_routeadv; _Atomic uint32_t v_pmax_restart; _Atomic uint32_t v_gr_restart; /* Threads. */ struct thread *t_read; struct thread *t_write; struct thread *t_start; struct thread *t_connect_check_r; struct thread *t_connect_check_w; struct thread *t_connect; struct thread *t_holdtime; struct thread *t_routeadv; struct thread *t_pmax_restart; struct thread *t_gr_restart; struct thread *t_gr_stale; struct thread *t_generate_updgrp_packets; struct thread *t_process_packet; /* Thread flags. */ _Atomic uint32_t thread_flags; #define PEER_THREAD_WRITES_ON (1 << 0) #define PEER_THREAD_READS_ON (1 << 1) #define PEER_THREAD_KEEPALIVES_ON (1 << 2) /* workqueues */ struct work_queue *clear_node_queue; #define PEER_TOTAL_RX(peer) \ atomic_load_explicit(&peer->open_in, memory_order_relaxed) \ + atomic_load_explicit(&peer->update_in, memory_order_relaxed) \ + atomic_load_explicit(&peer->notify_in, memory_order_relaxed) \ + atomic_load_explicit(&peer->refresh_in, \ memory_order_relaxed) \ + atomic_load_explicit(&peer->keepalive_in, \ memory_order_relaxed) \ + atomic_load_explicit(&peer->dynamic_cap_in, \ memory_order_relaxed) #define PEER_TOTAL_TX(peer) \ atomic_load_explicit(&peer->open_out, memory_order_relaxed) \ + atomic_load_explicit(&peer->update_out, \ memory_order_relaxed) \ + atomic_load_explicit(&peer->notify_out, \ memory_order_relaxed) \ + atomic_load_explicit(&peer->refresh_out, \ memory_order_relaxed) \ + atomic_load_explicit(&peer->keepalive_out, \ memory_order_relaxed) \ + atomic_load_explicit(&peer->dynamic_cap_out, \ memory_order_relaxed) /* Statistics field */ _Atomic uint32_t open_in; /* Open message input count */ _Atomic uint32_t open_out; /* Open message output count */ _Atomic uint32_t update_in; /* Update message input count */ _Atomic uint32_t update_out; /* Update message ouput count */ _Atomic time_t update_time; /* Update message received time. */ _Atomic uint32_t keepalive_in; /* Keepalive input count */ _Atomic uint32_t keepalive_out; /* Keepalive output count */ _Atomic uint32_t notify_in; /* Notify input count */ _Atomic uint32_t notify_out; /* Notify output count */ _Atomic uint32_t refresh_in; /* Route Refresh input count */ _Atomic uint32_t refresh_out; /* Route Refresh output count */ _Atomic uint32_t dynamic_cap_in; /* Dynamic Capability input count. */ _Atomic uint32_t dynamic_cap_out; /* Dynamic Capability output count. */ uint32_t stat_pfx_filter; uint32_t stat_pfx_aspath_loop; uint32_t stat_pfx_originator_loop; uint32_t stat_pfx_cluster_loop; uint32_t stat_pfx_nh_invalid; uint32_t stat_pfx_dup_withdraw; uint32_t stat_upd_7606; /* RFC7606: treat-as-withdraw */ /* BGP state count */ uint32_t established; /* Established */ uint32_t dropped; /* Dropped */ /* Update delay related fields */ uint8_t update_delay_over; /* When this is set, BGP is no more waiting for EOR */ /* Syncronization list and time. */ struct bgp_synchronize *sync[AFI_MAX][SAFI_MAX]; time_t synctime; /* timestamp when the last UPDATE msg was written */ _Atomic time_t last_write; /* timestamp when the last msg was written */ _Atomic time_t last_update; /* Send prefix count. */ unsigned long scount[AFI_MAX][SAFI_MAX]; /* Notify data. */ struct bgp_notify notify; /* Filter structure. */ struct bgp_filter filter[AFI_MAX][SAFI_MAX]; /* * Parallel array to filter that indicates whether each filter * originates from a peer-group or if it is config that is specific to * this individual peer. If a filter is set independent of the * peer-group the appropriate bit should be set here. If this peer is a * peer-group, this memory region should be all zeros. The assumption * is that the default state for all flags is unset. Due to filters * having a direction (e.g. in/out/...), this array has a third * dimension for storing the overrides independently per direction. * * Notes: * - if a filter for an individual peer is unset, the corresponding * override flag is unset and the peer is considered to be back in * sync with the peer-group. * - This does *not* contain the filter values, rather it contains * whether the filter in filter (struct bgp_filter) is peer-specific. */ uint8_t filter_override[AFI_MAX][SAFI_MAX][(FILTER_MAX > RMAP_MAX) ? FILTER_MAX : RMAP_MAX]; #define PEER_FT_DISTRIBUTE_LIST (1 << 0) /* distribute-list */ #define PEER_FT_FILTER_LIST (1 << 1) /* filter-list */ #define PEER_FT_PREFIX_LIST (1 << 2) /* prefix-list */ #define PEER_FT_ROUTE_MAP (1 << 3) /* route-map */ #define PEER_FT_UNSUPPRESS_MAP (1 << 4) /* unsuppress-map */ /* ORF Prefix-list */ struct prefix_list *orf_plist[AFI_MAX][SAFI_MAX]; /* Text description of last attribute rcvd */ char rcvd_attr_str[BUFSIZ]; /* Track if we printed the attribute in debugs */ int rcvd_attr_printed; /* Prefix count. */ uint32_t pcount[AFI_MAX][SAFI_MAX]; /* Max prefix count. */ uint32_t pmax[AFI_MAX][SAFI_MAX]; uint8_t pmax_threshold[AFI_MAX][SAFI_MAX]; uint16_t pmax_restart[AFI_MAX][SAFI_MAX]; #define MAXIMUM_PREFIX_THRESHOLD_DEFAULT 75 /* allowas-in. */ char allowas_in[AFI_MAX][SAFI_MAX]; /* weight */ unsigned long weight[AFI_MAX][SAFI_MAX]; /* peer reset cause */ uint8_t last_reset; #define PEER_DOWN_RID_CHANGE 1 /* bgp router-id command */ #define PEER_DOWN_REMOTE_AS_CHANGE 2 /* neighbor remote-as command */ #define PEER_DOWN_LOCAL_AS_CHANGE 3 /* neighbor local-as command */ #define PEER_DOWN_CLID_CHANGE 4 /* bgp cluster-id command */ #define PEER_DOWN_CONFED_ID_CHANGE 5 /* bgp confederation identifier command */ #define PEER_DOWN_CONFED_PEER_CHANGE 6 /* bgp confederation peer command */ #define PEER_DOWN_RR_CLIENT_CHANGE 7 /* neighbor route-reflector-client command */ #define PEER_DOWN_RS_CLIENT_CHANGE 8 /* neighbor route-server-client command */ #define PEER_DOWN_UPDATE_SOURCE_CHANGE 9 /* neighbor update-source command */ #define PEER_DOWN_AF_ACTIVATE 10 /* neighbor activate command */ #define PEER_DOWN_USER_SHUTDOWN 11 /* neighbor shutdown command */ #define PEER_DOWN_USER_RESET 12 /* clear ip bgp command */ #define PEER_DOWN_NOTIFY_RECEIVED 13 /* notification received */ #define PEER_DOWN_NOTIFY_SEND 14 /* notification send */ #define PEER_DOWN_CLOSE_SESSION 15 /* tcp session close */ #define PEER_DOWN_NEIGHBOR_DELETE 16 /* neghbor delete */ #define PEER_DOWN_RMAP_BIND 17 /* neghbor peer-group command */ #define PEER_DOWN_RMAP_UNBIND 18 /* no neighbor peer-group command */ #define PEER_DOWN_CAPABILITY_CHANGE 19 /* neighbor capability command */ #define PEER_DOWN_PASSIVE_CHANGE 20 /* neighbor passive command */ #define PEER_DOWN_MULTIHOP_CHANGE 21 /* neighbor multihop command */ #define PEER_DOWN_NSF_CLOSE_SESSION 22 /* NSF tcp session close */ #define PEER_DOWN_V6ONLY_CHANGE 23 /* if-based peering v6only toggled */ #define PEER_DOWN_BFD_DOWN 24 /* BFD down */ #define PEER_DOWN_IF_DOWN 25 /* Interface down */ #define PEER_DOWN_NBR_ADDR_DEL 26 /* Peer address lost */ #define PEER_DOWN_WAITING_NHT 27 /* Waiting for NHT to resolve */ #define PEER_DOWN_NBR_ADDR 28 /* Waiting for peer IPv6 IP Addr */ #define PEER_DOWN_VRF_UNINIT 29 /* Associated VRF is not init yet */ #define PEER_DOWN_NOAFI_ACTIVATED 30 /* No AFI/SAFI activated for peer */ size_t last_reset_cause_size; uint8_t last_reset_cause[BGP_MAX_PACKET_SIZE]; /* The kind of route-map Flags.*/ uint16_t rmap_type; #define PEER_RMAP_TYPE_IN (1 << 0) /* neighbor route-map in */ #define PEER_RMAP_TYPE_OUT (1 << 1) /* neighbor route-map out */ #define PEER_RMAP_TYPE_NETWORK (1 << 2) /* network route-map */ #define PEER_RMAP_TYPE_REDISTRIBUTE (1 << 3) /* redistribute route-map */ #define PEER_RMAP_TYPE_DEFAULT (1 << 4) /* default-originate route-map */ #define PEER_RMAP_TYPE_NOSET (1 << 5) /* not allow to set commands */ #define PEER_RMAP_TYPE_IMPORT (1 << 6) /* neighbor route-map import */ #define PEER_RMAP_TYPE_EXPORT (1 << 7) /* neighbor route-map export */ #define PEER_RMAP_TYPE_AGGREGATE (1 << 8) /* aggregate-address route-map */ /* peer specific BFD information */ struct bfd_info *bfd_info; /* hostname and domainname advertised by host */ char *hostname; char *domainname; QOBJ_FIELDS }; DECLARE_QOBJ_TYPE(peer) /* Inherit peer attribute from peer-group. */ #define PEER_ATTR_INHERIT(peer, group, attr) \ ((peer)->attr = (group)->conf->attr) #define PEER_STR_ATTR_INHERIT(peer, group, attr, mt) \ do { \ if ((peer)->attr) \ XFREE(mt, (peer)->attr); \ if ((group)->conf->attr) \ (peer)->attr = XSTRDUP(mt, (group)->conf->attr); \ else \ (peer)->attr = NULL; \ } while (0) #define PEER_SU_ATTR_INHERIT(peer, group, attr) \ do { \ if ((peer)->attr) \ sockunion_free((peer)->attr); \ if ((group)->conf->attr) \ (peer)->attr = sockunion_dup((group)->conf->attr); \ else \ (peer)->attr = NULL; \ } while (0) /* Check if suppress start/restart of sessions to peer. */ #define BGP_PEER_START_SUPPRESSED(P) \ (CHECK_FLAG((P)->flags, PEER_FLAG_SHUTDOWN) \ || CHECK_FLAG((P)->sflags, PEER_STATUS_PREFIX_OVERFLOW)) #define PEER_PASSWORD_MINLEN (1) #define PEER_PASSWORD_MAXLEN (80) /* This structure's member directly points incoming packet data stream. */ struct bgp_nlri { /* AFI. */ uint16_t afi; /* iana_afi_t */ /* SAFI. */ uint8_t safi; /* iana_safi_t */ /* Pointer to NLRI byte stream. */ uint8_t *nlri; /* Length of whole NLRI. */ bgp_size_t length; }; /* BGP versions. */ #define BGP_VERSION_4 4 /* Default BGP port number. */ #define BGP_PORT_DEFAULT 179 /* BGP minimum message size. */ #define BGP_MSG_OPEN_MIN_SIZE (BGP_HEADER_SIZE + 10) #define BGP_MSG_UPDATE_MIN_SIZE (BGP_HEADER_SIZE + 4) #define BGP_MSG_NOTIFY_MIN_SIZE (BGP_HEADER_SIZE + 2) #define BGP_MSG_KEEPALIVE_MIN_SIZE (BGP_HEADER_SIZE + 0) #define BGP_MSG_ROUTE_REFRESH_MIN_SIZE (BGP_HEADER_SIZE + 4) #define BGP_MSG_CAPABILITY_MIN_SIZE (BGP_HEADER_SIZE + 3) /* BGP message types. */ #define BGP_MSG_OPEN 1 #define BGP_MSG_UPDATE 2 #define BGP_MSG_NOTIFY 3 #define BGP_MSG_KEEPALIVE 4 #define BGP_MSG_ROUTE_REFRESH_NEW 5 #define BGP_MSG_CAPABILITY 6 #define BGP_MSG_ROUTE_REFRESH_OLD 128 /* BGP open optional parameter. */ #define BGP_OPEN_OPT_AUTH 1 #define BGP_OPEN_OPT_CAP 2 /* BGP4 attribute type codes. */ #define BGP_ATTR_ORIGIN 1 #define BGP_ATTR_AS_PATH 2 #define BGP_ATTR_NEXT_HOP 3 #define BGP_ATTR_MULTI_EXIT_DISC 4 #define BGP_ATTR_LOCAL_PREF 5 #define BGP_ATTR_ATOMIC_AGGREGATE 6 #define BGP_ATTR_AGGREGATOR 7 #define BGP_ATTR_COMMUNITIES 8 #define BGP_ATTR_ORIGINATOR_ID 9 #define BGP_ATTR_CLUSTER_LIST 10 #define BGP_ATTR_DPA 11 #define BGP_ATTR_ADVERTISER 12 #define BGP_ATTR_RCID_PATH 13 #define BGP_ATTR_MP_REACH_NLRI 14 #define BGP_ATTR_MP_UNREACH_NLRI 15 #define BGP_ATTR_EXT_COMMUNITIES 16 #define BGP_ATTR_AS4_PATH 17 #define BGP_ATTR_AS4_AGGREGATOR 18 #define BGP_ATTR_AS_PATHLIMIT 21 #define BGP_ATTR_PMSI_TUNNEL 22 #define BGP_ATTR_ENCAP 23 #define BGP_ATTR_LARGE_COMMUNITIES 32 #define BGP_ATTR_PREFIX_SID 40 #if ENABLE_BGP_VNC_ATTR #define BGP_ATTR_VNC 255 #endif /* BGP update origin. */ #define BGP_ORIGIN_IGP 0 #define BGP_ORIGIN_EGP 1 #define BGP_ORIGIN_INCOMPLETE 2 /* BGP notify message codes. */ #define BGP_NOTIFY_HEADER_ERR 1 #define BGP_NOTIFY_OPEN_ERR 2 #define BGP_NOTIFY_UPDATE_ERR 3 #define BGP_NOTIFY_HOLD_ERR 4 #define BGP_NOTIFY_FSM_ERR 5 #define BGP_NOTIFY_CEASE 6 #define BGP_NOTIFY_CAPABILITY_ERR 7 #define BGP_NOTIFY_SUBCODE_UNSPECIFIC 0 /* BGP_NOTIFY_HEADER_ERR sub codes. */ #define BGP_NOTIFY_HEADER_NOT_SYNC 1 #define BGP_NOTIFY_HEADER_BAD_MESLEN 2 #define BGP_NOTIFY_HEADER_BAD_MESTYPE 3 /* BGP_NOTIFY_OPEN_ERR sub codes. */ #define BGP_NOTIFY_OPEN_MALFORMED_ATTR 0 #define BGP_NOTIFY_OPEN_UNSUP_VERSION 1 #define BGP_NOTIFY_OPEN_BAD_PEER_AS 2 #define BGP_NOTIFY_OPEN_BAD_BGP_IDENT 3 #define BGP_NOTIFY_OPEN_UNSUP_PARAM 4 #define BGP_NOTIFY_OPEN_AUTH_FAILURE 5 #define BGP_NOTIFY_OPEN_UNACEP_HOLDTIME 6 #define BGP_NOTIFY_OPEN_UNSUP_CAPBL 7 /* BGP_NOTIFY_UPDATE_ERR sub codes. */ #define BGP_NOTIFY_UPDATE_MAL_ATTR 1 #define BGP_NOTIFY_UPDATE_UNREC_ATTR 2 #define BGP_NOTIFY_UPDATE_MISS_ATTR 3 #define BGP_NOTIFY_UPDATE_ATTR_FLAG_ERR 4 #define BGP_NOTIFY_UPDATE_ATTR_LENG_ERR 5 #define BGP_NOTIFY_UPDATE_INVAL_ORIGIN 6 #define BGP_NOTIFY_UPDATE_AS_ROUTE_LOOP 7 #define BGP_NOTIFY_UPDATE_INVAL_NEXT_HOP 8 #define BGP_NOTIFY_UPDATE_OPT_ATTR_ERR 9 #define BGP_NOTIFY_UPDATE_INVAL_NETWORK 10 #define BGP_NOTIFY_UPDATE_MAL_AS_PATH 11 /* BGP_NOTIFY_CEASE sub codes (RFC 4486). */ #define BGP_NOTIFY_CEASE_MAX_PREFIX 1 #define BGP_NOTIFY_CEASE_ADMIN_SHUTDOWN 2 #define BGP_NOTIFY_CEASE_PEER_UNCONFIG 3 #define BGP_NOTIFY_CEASE_ADMIN_RESET 4 #define BGP_NOTIFY_CEASE_CONNECT_REJECT 5 #define BGP_NOTIFY_CEASE_CONFIG_CHANGE 6 #define BGP_NOTIFY_CEASE_COLLISION_RESOLUTION 7 #define BGP_NOTIFY_CEASE_OUT_OF_RESOURCE 8 /* BGP_NOTIFY_CAPABILITY_ERR sub codes (draft-ietf-idr-dynamic-cap-02). */ #define BGP_NOTIFY_CAPABILITY_INVALID_ACTION 1 #define BGP_NOTIFY_CAPABILITY_INVALID_LENGTH 2 #define BGP_NOTIFY_CAPABILITY_MALFORMED_CODE 3 /* BGP finite state machine status. */ #define Idle 1 #define Connect 2 #define Active 3 #define OpenSent 4 #define OpenConfirm 5 #define Established 6 #define Clearing 7 #define Deleted 8 #define BGP_STATUS_MAX 9 /* BGP finite state machine events. */ #define BGP_Start 1 #define BGP_Stop 2 #define TCP_connection_open 3 #define TCP_connection_closed 4 #define TCP_connection_open_failed 5 #define TCP_fatal_error 6 #define ConnectRetry_timer_expired 7 #define Hold_Timer_expired 8 #define KeepAlive_timer_expired 9 #define Receive_OPEN_message 10 #define Receive_KEEPALIVE_message 11 #define Receive_UPDATE_message 12 #define Receive_NOTIFICATION_message 13 #define Clearing_Completed 14 #define BGP_EVENTS_MAX 15 /* BGP timers default value. */ /* note: the DFLT_ ones depend on compile-time "defaults" selection */ #define BGP_INIT_START_TIMER 1 #define BGP_DEFAULT_HOLDTIME DFLT_BGP_HOLDTIME #define BGP_DEFAULT_KEEPALIVE DFLT_BGP_KEEPALIVE #define BGP_DEFAULT_EBGP_ROUTEADV 0 #define BGP_DEFAULT_IBGP_ROUTEADV 0 #define BGP_DEFAULT_CONNECT_RETRY DFLT_BGP_TIMERS_CONNECT /* BGP default local preference. */ #define BGP_DEFAULT_LOCAL_PREF 100 /* BGP local-preference to send when 'bgp graceful-shutdown' * is configured */ #define BGP_GSHUT_LOCAL_PREF 0 /* BGP default subgroup packet queue max . */ #define BGP_DEFAULT_SUBGROUP_PKT_QUEUE_MAX 40 /* BGP graceful restart */ #define BGP_DEFAULT_RESTART_TIME 120 #define BGP_DEFAULT_STALEPATH_TIME 360 /* BGP uptime string length. */ #define BGP_UPTIME_LEN 25 /* Default configuration settings for bgpd. */ #define BGP_VTY_PORT 2605 #define BGP_DEFAULT_CONFIG "bgpd.conf" /* Check AS path loop when we send NLRI. */ /* #define BGP_SEND_ASPATH_CHECK */ /* BGP Dynamic Neighbors feature */ #define BGP_DYNAMIC_NEIGHBORS_LIMIT_DEFAULT 100 #define BGP_DYNAMIC_NEIGHBORS_LIMIT_MIN 1 #define BGP_DYNAMIC_NEIGHBORS_LIMIT_MAX 5000 /* Flag for peer_clear_soft(). */ enum bgp_clear_type { BGP_CLEAR_SOFT_NONE, BGP_CLEAR_SOFT_OUT, BGP_CLEAR_SOFT_IN, BGP_CLEAR_SOFT_BOTH, BGP_CLEAR_SOFT_IN_ORF_PREFIX }; /* Macros. */ #define BGP_INPUT(P) ((P)->curr) #define BGP_INPUT_PNT(P) (stream_pnt(BGP_INPUT(P))) #define BGP_IS_VALID_STATE_FOR_NOTIF(S) \ (((S) == OpenSent) || ((S) == OpenConfirm) || ((S) == Established)) /* BGP error codes. */ #define BGP_SUCCESS 0 #define BGP_ERR_INVALID_VALUE -1 #define BGP_ERR_INVALID_FLAG -2 #define BGP_ERR_INVALID_AS -3 #define BGP_ERR_INVALID_BGP -4 #define BGP_ERR_PEER_GROUP_MEMBER -5 #define BGP_ERR_PEER_GROUP_NO_REMOTE_AS -7 #define BGP_ERR_PEER_GROUP_CANT_CHANGE -8 #define BGP_ERR_PEER_GROUP_MISMATCH -9 #define BGP_ERR_PEER_GROUP_PEER_TYPE_DIFFERENT -10 #define BGP_ERR_AS_MISMATCH -12 #define BGP_ERR_PEER_FLAG_CONFLICT -13 #define BGP_ERR_PEER_GROUP_SHUTDOWN -14 #define BGP_ERR_PEER_FILTER_CONFLICT -15 #define BGP_ERR_NOT_INTERNAL_PEER -16 #define BGP_ERR_REMOVE_PRIVATE_AS -17 #define BGP_ERR_AF_UNCONFIGURED -18 #define BGP_ERR_SOFT_RECONFIG_UNCONFIGURED -19 #define BGP_ERR_INSTANCE_MISMATCH -20 #define BGP_ERR_LOCAL_AS_ALLOWED_ONLY_FOR_EBGP -21 #define BGP_ERR_CANNOT_HAVE_LOCAL_AS_SAME_AS -22 #define BGP_ERR_TCPSIG_FAILED -23 #define BGP_ERR_NO_EBGP_MULTIHOP_WITH_TTLHACK -24 #define BGP_ERR_NO_IBGP_WITH_TTLHACK -25 #define BGP_ERR_NO_INTERFACE_CONFIG -26 #define BGP_ERR_CANNOT_HAVE_LOCAL_AS_SAME_AS_REMOTE_AS -27 #define BGP_ERR_AS_OVERRIDE -28 #define BGP_ERR_INVALID_DYNAMIC_NEIGHBORS_LIMIT -29 #define BGP_ERR_DYNAMIC_NEIGHBORS_RANGE_EXISTS -30 #define BGP_ERR_DYNAMIC_NEIGHBORS_RANGE_NOT_FOUND -31 #define BGP_ERR_INVALID_FOR_DYNAMIC_PEER -32 #define BGP_ERR_MAX -33 #define BGP_ERR_INVALID_FOR_DIRECT_PEER -34 #define BGP_ERR_PEER_SAFI_CONFLICT -35 /* * Enumeration of different policy kinds a peer can be configured with. */ typedef enum { BGP_POLICY_ROUTE_MAP, BGP_POLICY_FILTER_LIST, BGP_POLICY_PREFIX_LIST, BGP_POLICY_DISTRIBUTE_LIST, } bgp_policy_type_e; /* peer_flag_change_type. */ enum peer_change_type { peer_change_none, peer_change_reset, peer_change_reset_in, peer_change_reset_out, }; extern struct bgp_master *bm; extern unsigned int multipath_num; /* Prototypes. */ extern void bgp_terminate(void); extern void bgp_reset(void); extern time_t bgp_clock(void); extern void bgp_zclient_reset(void); extern struct bgp *bgp_get_default(void); extern struct bgp *bgp_lookup(as_t, const char *); extern struct bgp *bgp_lookup_by_name(const char *); extern struct bgp *bgp_lookup_by_vrf_id(vrf_id_t); extern struct bgp *bgp_get_evpn(void); extern void bgp_set_evpn(struct bgp *bgp); extern struct peer *peer_lookup(struct bgp *, union sockunion *); extern struct peer *peer_lookup_by_conf_if(struct bgp *, const char *); extern struct peer *peer_lookup_by_hostname(struct bgp *, const char *); extern void bgp_peer_conf_if_to_su_update(struct peer *); extern int peer_group_listen_range_del(struct peer_group *, struct prefix *); extern struct peer_group *peer_group_lookup(struct bgp *, const char *); extern struct peer_group *peer_group_get(struct bgp *, const char *); extern struct peer *peer_create_bind_dynamic_neighbor(struct bgp *, union sockunion *, struct peer_group *); extern struct prefix * peer_group_lookup_dynamic_neighbor_range(struct peer_group *, struct prefix *); extern struct peer_group *peer_group_lookup_dynamic_neighbor(struct bgp *, struct prefix *, struct prefix **); extern struct peer *peer_lookup_dynamic_neighbor(struct bgp *, union sockunion *); /* * Peers are incredibly easy to memory leak * due to the various ways that they are actually used * Provide some functionality to debug locks and unlocks */ extern struct peer *peer_lock_with_caller(const char *, struct peer *); extern struct peer *peer_unlock_with_caller(const char *, struct peer *); #define peer_unlock(A) peer_unlock_with_caller(__FUNCTION__, (A)) #define peer_lock(B) peer_lock_with_caller(__FUNCTION__, (B)) extern bgp_peer_sort_t peer_sort(struct peer *peer); extern int peer_active(struct peer *); extern int peer_active_nego(struct peer *); extern void bgp_recalculate_all_bestpaths(struct bgp *bgp); extern struct peer *peer_create(union sockunion *, const char *, struct bgp *, as_t, as_t, int, afi_t, safi_t, struct peer_group *); extern struct peer *peer_create_accept(struct bgp *); extern void peer_xfer_config(struct peer *dst, struct peer *src); extern char *peer_uptime(time_t uptime2, char *buf, size_t len, bool use_json, json_object *json); extern int bgp_config_write(struct vty *); extern void bgp_master_init(struct thread_master *master); extern void bgp_init(unsigned short instance); extern void bgp_pthreads_run(void); extern void bgp_pthreads_finish(void); extern void bgp_route_map_init(void); extern void bgp_session_reset(struct peer *); extern int bgp_option_set(int); extern int bgp_option_unset(int); extern int bgp_option_check(int); extern int bgp_get(struct bgp **, as_t *, const char *, enum bgp_instance_type); extern void bgp_instance_up(struct bgp *); extern void bgp_instance_down(struct bgp *); extern int bgp_delete(struct bgp *); extern int bgp_handle_socket(struct bgp *bgp, struct vrf *vrf, vrf_id_t old_vrf_id, bool create); extern int bgp_flag_set(struct bgp *, int); extern int bgp_flag_unset(struct bgp *, int); extern int bgp_flag_check(struct bgp *, int); extern void bgp_router_id_zebra_bump(vrf_id_t, const struct prefix *); extern int bgp_router_id_static_set(struct bgp *, struct in_addr); extern int bgp_cluster_id_set(struct bgp *, struct in_addr *); extern int bgp_cluster_id_unset(struct bgp *); extern int bgp_confederation_id_set(struct bgp *, as_t); extern int bgp_confederation_id_unset(struct bgp *); extern int bgp_confederation_peers_check(struct bgp *, as_t); extern int bgp_confederation_peers_add(struct bgp *, as_t); extern int bgp_confederation_peers_remove(struct bgp *, as_t); extern int bgp_timers_set(struct bgp *, uint32_t keepalive, uint32_t holdtime); extern int bgp_timers_unset(struct bgp *); extern int bgp_default_local_preference_set(struct bgp *, uint32_t); extern int bgp_default_local_preference_unset(struct bgp *); extern int bgp_default_subgroup_pkt_queue_max_set(struct bgp *bgp, uint32_t); extern int bgp_default_subgroup_pkt_queue_max_unset(struct bgp *bgp); extern int bgp_listen_limit_set(struct bgp *, int); extern int bgp_listen_limit_unset(struct bgp *); extern int bgp_update_delay_active(struct bgp *); extern int bgp_update_delay_configured(struct bgp *); extern int bgp_afi_safi_peer_exists(struct bgp *bgp, afi_t afi, safi_t safi); extern void peer_as_change(struct peer *, as_t, int); extern int peer_remote_as(struct bgp *, union sockunion *, const char *, as_t *, int, afi_t, safi_t); extern int peer_group_remote_as(struct bgp *, const char *, as_t *, int); extern int peer_delete(struct peer *peer); extern int peer_notify_unconfig(struct peer *peer); extern int peer_group_delete(struct peer_group *); extern int peer_group_remote_as_delete(struct peer_group *); extern int peer_group_listen_range_add(struct peer_group *, struct prefix *); extern int peer_group_notify_unconfig(struct peer_group *group); extern int peer_activate(struct peer *, afi_t, safi_t); extern int peer_deactivate(struct peer *, afi_t, safi_t); extern int peer_afc_set(struct peer *, afi_t, safi_t, int); extern int peer_group_bind(struct bgp *, union sockunion *, struct peer *, struct peer_group *, as_t *); extern int peer_flag_set(struct peer *, uint32_t); extern int peer_flag_unset(struct peer *, uint32_t); extern void peer_flag_inherit(struct peer *peer, uint32_t flag); extern int peer_af_flag_set(struct peer *, afi_t, safi_t, uint32_t); extern int peer_af_flag_unset(struct peer *, afi_t, safi_t, uint32_t); extern int peer_af_flag_check(struct peer *, afi_t, safi_t, uint32_t); extern void peer_af_flag_inherit(struct peer *peer, afi_t afi, safi_t safi, uint32_t flag); extern void peer_change_action(struct peer *peer, afi_t afi, safi_t safi, enum peer_change_type type); extern int peer_ebgp_multihop_set(struct peer *, int); extern int peer_ebgp_multihop_unset(struct peer *); extern int is_ebgp_multihop_configured(struct peer *peer); extern int peer_description_set(struct peer *, const char *); extern int peer_description_unset(struct peer *); extern int peer_update_source_if_set(struct peer *, const char *); extern int peer_update_source_addr_set(struct peer *, const union sockunion *); extern int peer_update_source_unset(struct peer *); extern int peer_default_originate_set(struct peer *peer, afi_t afi, safi_t safi, const char *rmap, struct route_map *route_map); extern int peer_default_originate_unset(struct peer *, afi_t, safi_t); extern int peer_port_set(struct peer *, uint16_t); extern int peer_port_unset(struct peer *); extern int peer_weight_set(struct peer *, afi_t, safi_t, uint16_t); extern int peer_weight_unset(struct peer *, afi_t, safi_t); extern int peer_timers_set(struct peer *, uint32_t keepalive, uint32_t holdtime); extern int peer_timers_unset(struct peer *); extern int peer_timers_connect_set(struct peer *, uint32_t); extern int peer_timers_connect_unset(struct peer *); extern int peer_advertise_interval_set(struct peer *, uint32_t); extern int peer_advertise_interval_unset(struct peer *); extern void peer_interface_set(struct peer *, const char *); extern void peer_interface_unset(struct peer *); extern int peer_distribute_set(struct peer *, afi_t, safi_t, int, const char *); extern int peer_distribute_unset(struct peer *, afi_t, safi_t, int); extern int peer_allowas_in_set(struct peer *, afi_t, safi_t, int, int); extern int peer_allowas_in_unset(struct peer *, afi_t, safi_t); extern int peer_local_as_set(struct peer *, as_t, int, int); extern int peer_local_as_unset(struct peer *); extern int peer_prefix_list_set(struct peer *, afi_t, safi_t, int, const char *); extern int peer_prefix_list_unset(struct peer *, afi_t, safi_t, int); extern int peer_aslist_set(struct peer *, afi_t, safi_t, int, const char *); extern int peer_aslist_unset(struct peer *, afi_t, safi_t, int); extern int peer_route_map_set(struct peer *peer, afi_t afi, safi_t safi, int, const char *name, struct route_map *route_map); extern int peer_route_map_unset(struct peer *, afi_t, safi_t, int); extern int peer_unsuppress_map_set(struct peer *peer, afi_t afi, safi_t safi, const char *name, struct route_map *route_map); extern int peer_password_set(struct peer *, const char *); extern int peer_password_unset(struct peer *); extern int peer_unsuppress_map_unset(struct peer *, afi_t, safi_t); extern int peer_maximum_prefix_set(struct peer *, afi_t, safi_t, uint32_t, uint8_t, int, uint16_t); extern int peer_maximum_prefix_unset(struct peer *, afi_t, safi_t); extern int peer_clear(struct peer *, struct listnode **); extern int peer_clear_soft(struct peer *, afi_t, safi_t, enum bgp_clear_type); extern int peer_ttl_security_hops_set(struct peer *, int); extern int peer_ttl_security_hops_unset(struct peer *); extern int peer_tx_shutdown_message_set(struct peer *, const char *msg); extern int peer_tx_shutdown_message_unset(struct peer *); extern int bgp_route_map_update_timer(struct thread *thread); extern void bgp_route_map_terminate(void); extern int peer_cmp(struct peer *p1, struct peer *p2); extern int bgp_map_afi_safi_iana2int(iana_afi_t pkt_afi, iana_safi_t pkt_safi, afi_t *afi, safi_t *safi); extern int bgp_map_afi_safi_int2iana(afi_t afi, safi_t safi, iana_afi_t *pkt_afi, iana_safi_t *pkt_safi); extern struct peer_af *peer_af_create(struct peer *, afi_t, safi_t); extern struct peer_af *peer_af_find(struct peer *, afi_t, safi_t); extern int peer_af_delete(struct peer *, afi_t, safi_t); extern void bgp_close(void); extern void bgp_free(struct bgp *); static inline struct bgp *bgp_lock(struct bgp *bgp) { bgp->lock++; return bgp; } static inline void bgp_unlock(struct bgp *bgp) { assert(bgp->lock > 0); if (--bgp->lock == 0) bgp_free(bgp); } static inline int afindex(afi_t afi, safi_t safi) { switch (afi) { case AFI_IP: switch (safi) { case SAFI_UNICAST: return BGP_AF_IPV4_UNICAST; break; case SAFI_MULTICAST: return BGP_AF_IPV4_MULTICAST; break; case SAFI_LABELED_UNICAST: return BGP_AF_IPV4_LBL_UNICAST; break; case SAFI_MPLS_VPN: return BGP_AF_IPV4_VPN; break; case SAFI_ENCAP: return BGP_AF_IPV4_ENCAP; break; case SAFI_FLOWSPEC: return BGP_AF_IPV4_FLOWSPEC; default: return BGP_AF_MAX; break; } break; case AFI_IP6: switch (safi) { case SAFI_UNICAST: return BGP_AF_IPV6_UNICAST; break; case SAFI_MULTICAST: return BGP_AF_IPV6_MULTICAST; break; case SAFI_LABELED_UNICAST: return BGP_AF_IPV6_LBL_UNICAST; break; case SAFI_MPLS_VPN: return BGP_AF_IPV6_VPN; break; case SAFI_ENCAP: return BGP_AF_IPV6_ENCAP; break; case SAFI_FLOWSPEC: return BGP_AF_IPV6_FLOWSPEC; default: return BGP_AF_MAX; break; } break; case AFI_L2VPN: switch (safi) { case SAFI_EVPN: return BGP_AF_L2VPN_EVPN; break; default: return BGP_AF_MAX; break; } default: return BGP_AF_MAX; break; } } /* If the peer is not a peer-group but is bound to a peer-group return 1 */ static inline int peer_group_active(struct peer *peer) { if (!CHECK_FLAG(peer->sflags, PEER_STATUS_GROUP) && peer->group) return 1; return 0; } /* If peer is negotiated at least one address family return 1. */ static inline int peer_afi_active_nego(const struct peer *peer, afi_t afi) { if (peer->afc_nego[afi][SAFI_UNICAST] || peer->afc_nego[afi][SAFI_MULTICAST] || peer->afc_nego[afi][SAFI_LABELED_UNICAST] || peer->afc_nego[afi][SAFI_MPLS_VPN] || peer->afc_nego[afi][SAFI_ENCAP] || peer->afc_nego[afi][SAFI_FLOWSPEC] || peer->afc_nego[afi][SAFI_EVPN]) return 1; return 0; } /* If at least one address family activated for group, return 1. */ static inline int peer_group_af_configured(struct peer_group *group) { struct peer *peer = group->conf; if (peer->afc[AFI_IP][SAFI_UNICAST] || peer->afc[AFI_IP][SAFI_MULTICAST] || peer->afc[AFI_IP][SAFI_LABELED_UNICAST] || peer->afc[AFI_IP][SAFI_FLOWSPEC] || peer->afc[AFI_IP][SAFI_MPLS_VPN] || peer->afc[AFI_IP][SAFI_ENCAP] || peer->afc[AFI_IP6][SAFI_UNICAST] || peer->afc[AFI_IP6][SAFI_MULTICAST] || peer->afc[AFI_IP6][SAFI_LABELED_UNICAST] || peer->afc[AFI_IP6][SAFI_MPLS_VPN] || peer->afc[AFI_IP6][SAFI_ENCAP] || peer->afc[AFI_IP6][SAFI_FLOWSPEC] || peer->afc[AFI_L2VPN][SAFI_EVPN]) return 1; return 0; } static inline char *timestamp_string(time_t ts) { time_t tbuf; tbuf = time(NULL) - (bgp_clock() - ts); return ctime(&tbuf); } static inline int peer_established(struct peer *peer) { if (peer->status == Established) return 1; return 0; } static inline int peer_dynamic_neighbor(struct peer *peer) { return (CHECK_FLAG(peer->flags, PEER_FLAG_DYNAMIC_NEIGHBOR)) ? 1 : 0; } static inline int peer_cap_enhe(struct peer *peer, afi_t afi, safi_t safi) { return (CHECK_FLAG(peer->af_cap[afi][safi], PEER_CAP_ENHE_AF_NEGO)); } /* Lookup VRF for BGP instance based on its type. */ static inline struct vrf *bgp_vrf_lookup_by_instance_type(struct bgp *bgp) { struct vrf *vrf; if (bgp->inst_type == BGP_INSTANCE_TYPE_DEFAULT) vrf = vrf_lookup_by_id(VRF_DEFAULT); else if (bgp->inst_type == BGP_INSTANCE_TYPE_VRF) vrf = vrf_lookup_by_name(bgp->name); else vrf = NULL; return vrf; } /* Link BGP instance to VRF. */ static inline void bgp_vrf_link(struct bgp *bgp, struct vrf *vrf) { bgp->vrf_id = vrf->vrf_id; if (vrf->info != (void *)bgp) vrf->info = (void *)bgp_lock(bgp); } /* Unlink BGP instance from VRF. */ static inline void bgp_vrf_unlink(struct bgp *bgp, struct vrf *vrf) { if (vrf->info == (void *)bgp) { vrf->info = NULL; bgp_unlock(bgp); } bgp->vrf_id = VRF_UNKNOWN; } extern void bgp_unset_redist_vrf_bitmaps(struct bgp *, vrf_id_t); /* For benefit of rfapi */ extern struct peer *peer_new(struct bgp *bgp); extern struct peer *peer_lookup_in_view(struct vty *vty, struct bgp *bgp, const char *ip_str, bool use_json); #endif /* _QUAGGA_BGPD_H */ frr-7.2.1/bgpd/rfapi/0000755000000000000000000000000013610377563011311 500000000000000frr-7.2.1/bgpd/rfapi/bgp_rfapi_cfg.c0000644000000000000000000037025513610377563014161 00000000000000/* * * Copyright 2009-2016, LabN Consulting, L.L.C. * * * This program is free software; you can redistribute it and/or * modify it under the terms of the GNU General Public License * as published by the Free Software Foundation; either version 2 * of the License, or (at your option) any later version. * * 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; see the file COPYING; if not, write to the Free Software * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA */ #include "lib/zebra.h" #include "lib/command.h" #include "lib/prefix.h" #include "lib/memory.h" #include "lib/linklist.h" #include "lib/agg_table.h" #include "lib/plist.h" #include "lib/routemap.h" #include "bgpd/bgpd.h" #include "bgpd/bgp_attr.h" #include "bgpd/bgp_route.h" #include "bgpd/bgp_mplsvpn.h" #include "bgpd/bgp_vty.h" #include "bgpd/bgp_ecommunity.h" #include "bgpd/rfapi/rfapi.h" #include "bgpd/rfapi/bgp_rfapi_cfg.h" #include "bgpd/rfapi/rfapi_backend.h" #include "bgpd/rfapi/rfapi_import.h" #include "bgpd/rfapi/rfapi_private.h" #include "bgpd/rfapi/rfapi_monitor.h" #include "bgpd/rfapi/vnc_zebra.h" #include "bgpd/rfapi/vnc_export_bgp.h" #include "bgpd/rfapi/vnc_export_bgp_p.h" #include "bgpd/rfapi/rfapi_vty.h" #include "bgpd/rfapi/vnc_import_bgp.h" #include "bgpd/rfapi/vnc_debug.h" #if ENABLE_BGP_VNC #undef BGP_VNC_DEBUG_MATCH_GROUP DEFINE_MGROUP(RFAPI, "rfapi") DEFINE_MTYPE(RFAPI, RFAPI_CFG, "NVE Configuration") DEFINE_MTYPE(RFAPI, RFAPI_GROUP_CFG, "NVE Group Configuration") DEFINE_MTYPE(RFAPI, RFAPI_L2_CFG, "RFAPI L2 Group Configuration") DEFINE_MTYPE(RFAPI, RFAPI_RFP_GROUP_CFG, "RFAPI RFP Group Configuration") DEFINE_MTYPE(RFAPI, RFAPI, "RFAPI Generic") DEFINE_MTYPE(RFAPI, RFAPI_DESC, "RFAPI Descriptor") DEFINE_MTYPE(RFAPI, RFAPI_IMPORTTABLE, "RFAPI Import Table") DEFINE_MTYPE(RFAPI, RFAPI_MONITOR, "RFAPI Monitor VPN") DEFINE_MTYPE(RFAPI, RFAPI_MONITOR_ENCAP, "RFAPI Monitor Encap") DEFINE_MTYPE(RFAPI, RFAPI_NEXTHOP, "RFAPI Next Hop") DEFINE_MTYPE(RFAPI, RFAPI_VN_OPTION, "RFAPI VN Option") DEFINE_MTYPE(RFAPI, RFAPI_UN_OPTION, "RFAPI UN Option") DEFINE_MTYPE(RFAPI, RFAPI_WITHDRAW, "RFAPI Withdraw") DEFINE_MTYPE(RFAPI, RFAPI_RFG_NAME, "RFAPI RFGName") DEFINE_MTYPE(RFAPI, RFAPI_ADB, "RFAPI Advertisement Data") DEFINE_MTYPE(RFAPI, RFAPI_ETI, "RFAPI Export Table Info") DEFINE_MTYPE(RFAPI, RFAPI_NVE_ADDR, "RFAPI NVE Address") DEFINE_MTYPE(RFAPI, RFAPI_PREFIX_BAG, "RFAPI Prefix Bag") DEFINE_MTYPE(RFAPI, RFAPI_IT_EXTRA, "RFAPI IT Extra") DEFINE_MTYPE(RFAPI, RFAPI_INFO, "RFAPI Info") DEFINE_MTYPE(RFAPI, RFAPI_ADDR, "RFAPI Addr") DEFINE_MTYPE(RFAPI, RFAPI_UPDATED_RESPONSE_QUEUE, "RFAPI Updated Rsp Queue") DEFINE_MTYPE(RFAPI, RFAPI_RECENT_DELETE, "RFAPI Recently Deleted Route") DEFINE_MTYPE(RFAPI, RFAPI_L2ADDR_OPT, "RFAPI L2 Address Option") DEFINE_MTYPE(RFAPI, RFAPI_AP, "RFAPI Advertised Prefix") DEFINE_MTYPE(RFAPI, RFAPI_MONITOR_ETH, "RFAPI Monitor Ethernet") DEFINE_QOBJ_TYPE(rfapi_nve_group_cfg) DEFINE_QOBJ_TYPE(rfapi_l2_group_cfg) /*********************************************************************** * RFAPI Support ***********************************************************************/ /* * compaitibility to old quagga_time call * time_t value in terms of stabilised absolute time. * replacement for POSIX time() */ time_t rfapi_time(time_t *t) { time_t clock = bgp_clock(); if (t) *t = clock; return clock; } void nve_group_to_nve_list(struct rfapi_nve_group_cfg *rfg, struct list **nves, uint8_t family) /* AF_INET, AF_INET6 */ { struct listnode *hln; struct rfapi_descriptor *rfd; /* * loop over nves in this grp, add to list */ for (ALL_LIST_ELEMENTS_RO(rfg->nves, hln, rfd)) { if (rfd->vn_addr.addr_family == family) { if (!*nves) *nves = list_new(); listnode_add(*nves, rfd); } } } struct rfapi_nve_group_cfg *bgp_rfapi_cfg_match_group(struct rfapi_cfg *hc, struct prefix *vn, struct prefix *un) { struct rfapi_nve_group_cfg *rfg_vn = NULL; struct rfapi_nve_group_cfg *rfg_un = NULL; struct agg_table *rt_vn; struct agg_table *rt_un; struct agg_node *rn_vn; struct agg_node *rn_un; struct rfapi_nve_group_cfg *rfg; struct listnode *node, *nnode; switch (vn->family) { case AF_INET: rt_vn = hc->nve_groups_vn[AFI_IP]; break; case AF_INET6: rt_vn = hc->nve_groups_vn[AFI_IP6]; break; default: return NULL; } switch (un->family) { case AF_INET: rt_un = hc->nve_groups_un[AFI_IP]; break; case AF_INET6: rt_un = hc->nve_groups_un[AFI_IP6]; break; default: return NULL; } rn_vn = agg_node_match(rt_vn, vn); /* NB locks node */ if (rn_vn) { rfg_vn = rn_vn->info; agg_unlock_node(rn_vn); } rn_un = agg_node_match(rt_un, un); /* NB locks node */ if (rn_un) { rfg_un = rn_un->info; agg_unlock_node(rn_un); } #if BGP_VNC_DEBUG_MATCH_GROUP { char buf[PREFIX_STRLEN]; prefix2str(vn, buf, sizeof(buf)); vnc_zlog_debug_verbose("%s: vn prefix: %s", __func__, buf); prefix2str(un, buf, sizeof(buf)); vnc_zlog_debug_verbose("%s: un prefix: %s", __func__, buf); vnc_zlog_debug_verbose( "%s: rn_vn=%p, rn_un=%p, rfg_vn=%p, rfg_un=%p", __func__, rn_vn, rn_un, rfg_vn, rfg_un); } #endif if (rfg_un == rfg_vn) /* same group */ return rfg_un; if (!rfg_un) /* un doesn't match, return vn-matched grp */ return rfg_vn; if (!rfg_vn) /* vn doesn't match, return un-matched grp */ return rfg_un; /* * Two different nve groups match: the group configured earlier wins. * For now, just walk the sequential list and pick the first one. * If this approach is too slow, then store serial numbers in the * nve group structures as they are defined and just compare * serial numbers. */ for (ALL_LIST_ELEMENTS(hc->nve_groups_sequential, node, nnode, rfg)) { if ((rfg == rfg_un) || (rfg == rfg_vn)) { return rfg; } } vnc_zlog_debug_verbose( "%s: shouldn't happen, returning NULL when un and vn match", __func__); return NULL; /* shouldn't happen */ } /*------------------------------------------ * rfapi_get_rfp_start_val * * Returns value passed to rfapi on rfp_start * * input: * void * bgp structure * * returns: * void * *------------------------------------------*/ void *rfapi_get_rfp_start_val(void *bgpv) { struct bgp *bgp = bgpv; if (bgp == NULL || bgp->rfapi == NULL) return NULL; return bgp->rfapi->rfp; } /*------------------------------------------ * bgp_rfapi_is_vnc_configured * * Returns if VNC is configured * * input: * bgp NULL (=use default instance) * * output: * * return value: If VNC is configured for the bgpd instance * 0 Success * EPERM Not Default instance (VNC operations not allowed) * ENXIO VNC not configured --------------------------------------------*/ int bgp_rfapi_is_vnc_configured(struct bgp *bgp) { if (bgp == NULL) bgp = bgp_get_default(); if (bgp && bgp->inst_type != BGP_INSTANCE_TYPE_DEFAULT) return EPERM; if (bgp && bgp->rfapi_cfg) return 0; return ENXIO; } /*********************************************************************** * VNC Configuration/CLI ***********************************************************************/ #define VNC_VTY_CONFIG_CHECK(bgp) \ { \ switch (bgp_rfapi_is_vnc_configured(bgp)) { \ case EPERM: \ vty_out(vty, \ "VNC operations only permitted on default BGP instance.\n"); \ return CMD_WARNING_CONFIG_FAILED; \ break; \ case ENXIO: \ vty_out(vty, "VNC not configured.\n"); \ return CMD_WARNING_CONFIG_FAILED; \ break; \ default: \ break; \ } \ } DEFUN (vnc_advertise_un_method, vnc_advertise_un_method_cmd, "vnc advertise-un-method encap-attr", VNC_CONFIG_STR "Method of advertising UN addresses\n" "Via Tunnel Encap attribute (in VPN SAFI)\n") { VTY_DECLVAR_CONTEXT(bgp, bgp); VNC_VTY_CONFIG_CHECK(bgp); if (!strncmp(argv[2]->arg, "encap-safi", 7)) { bgp->rfapi_cfg->flags |= BGP_VNC_CONFIG_ADV_UN_METHOD_ENCAP; } else { bgp->rfapi_cfg->flags &= ~BGP_VNC_CONFIG_ADV_UN_METHOD_ENCAP; } return CMD_SUCCESS; } /*------------------------------------------------------------------------- * RFG defaults *-----------------------------------------------------------------------*/ DEFUN_NOSH (vnc_defaults, vnc_defaults_cmd, "vnc defaults", VNC_CONFIG_STR "Configure default NVE group\n") { VTY_DECLVAR_CONTEXT(bgp, bgp); VNC_VTY_CONFIG_CHECK(bgp); if (bgp->inst_type != BGP_INSTANCE_TYPE_DEFAULT) { vty_out(vty, "Malformed community-list value\n"); return CMD_WARNING_CONFIG_FAILED; } vty->node = BGP_VNC_DEFAULTS_NODE; return CMD_SUCCESS; } static int set_ecom_list(struct vty *vty, int argc, struct cmd_token **argv, struct ecommunity **list) { struct ecommunity *ecom = NULL; struct ecommunity *ecomadd; for (; argc; --argc, ++argv) { ecomadd = ecommunity_str2com(argv[0]->arg, ECOMMUNITY_ROUTE_TARGET, 0); if (!ecomadd) { vty_out(vty, "Malformed community-list value\n"); if (ecom) ecommunity_free(&ecom); return CMD_WARNING_CONFIG_FAILED; } if (ecom) { ecommunity_merge(ecom, ecomadd); ecommunity_free(&ecomadd); } else { ecom = ecomadd; } } if (*list) { ecommunity_free(&*list); } *list = ecom; return CMD_SUCCESS; } DEFUN (vnc_defaults_rt_import, vnc_defaults_rt_import_cmd, "rt import RTLIST...", "Specify default route targets\n" "Import filter\n" "Space separated route target list (A.B.C.D:MN|EF:OPQR|GHJK:MN)\n") { VTY_DECLVAR_CONTEXT(bgp, bgp); return set_ecom_list(vty, argc - 2, argv + 2, &bgp->rfapi_cfg->default_rt_import_list); } DEFUN (vnc_defaults_rt_export, vnc_defaults_rt_export_cmd, "rt export RTLIST...", "Configure default route targets\n" "Export filter\n" "Space separated route target list (A.B.C.D:MN|EF:OPQR|GHJK:MN)\n") { VTY_DECLVAR_CONTEXT(bgp, bgp); return set_ecom_list(vty, argc - 2, argv + 2, &bgp->rfapi_cfg->default_rt_export_list); } DEFUN (vnc_defaults_rt_both, vnc_defaults_rt_both_cmd, "rt both RTLIST...", "Configure default route targets\n" "Export+import filters\n" "Space separated route target list (A.B.C.D:MN|EF:OPQR|GHJK:MN)\n") { VTY_DECLVAR_CONTEXT(bgp, bgp); int rc; rc = set_ecom_list(vty, argc - 2, argv + 2, &bgp->rfapi_cfg->default_rt_import_list); if (rc != CMD_SUCCESS) return rc; return set_ecom_list(vty, argc - 2, argv + 2, &bgp->rfapi_cfg->default_rt_export_list); } DEFUN (vnc_defaults_rd, vnc_defaults_rd_cmd, "rd ASN:NN_OR_IP-ADDRESS:NN", "Specify default route distinguisher\n" "Route Distinguisher (: | : | auto:vn: )\n") { VTY_DECLVAR_CONTEXT(bgp, bgp); int ret; struct prefix_rd prd; if (!strncmp(argv[1]->arg, "auto:vn:", 8)) { /* * use AF_UNIX to designate automatically-assigned RD * auto:vn:nn where nn is a 2-octet quantity */ char *end = NULL; uint32_t value32 = strtoul(argv[1]->arg + 8, &end, 10); uint16_t value = value32 & 0xffff; if (!argv[1]->arg[8] || *end) { vty_out(vty, "%% Malformed rd\n"); return CMD_WARNING_CONFIG_FAILED; } if (value32 > 0xffff) { vty_out(vty, "%% Malformed rd (must be less than %u\n", 0x0ffff); return CMD_WARNING_CONFIG_FAILED; } memset(&prd, 0, sizeof(prd)); prd.family = AF_UNIX; prd.prefixlen = 64; prd.val[0] = (RD_TYPE_IP >> 8) & 0x0ff; prd.val[1] = RD_TYPE_IP & 0x0ff; prd.val[6] = (value >> 8) & 0x0ff; prd.val[7] = value & 0x0ff; } else { ret = str2prefix_rd(argv[1]->arg, &prd); if (!ret) { vty_out(vty, "%% Malformed rd\n"); return CMD_WARNING_CONFIG_FAILED; } } bgp->rfapi_cfg->default_rd = prd; return CMD_SUCCESS; } DEFUN (vnc_defaults_l2rd, vnc_defaults_l2rd_cmd, "l2rd <(1-255)|auto-vn>", "Specify default Local Nve ID value to use in RD for L2 routes\n" "Fixed value 1-255\n" "use the low-order octet of the NVE's VN address\n") { VTY_DECLVAR_CONTEXT(bgp, bgp); uint8_t value = 0; if (strmatch(argv[1]->text, "auto-vn")) { value = 0; } else { char *end = NULL; unsigned long value_l = strtoul(argv[1]->arg, &end, 10); value = value_l & 0xff; if (!argv[1]->arg[0] || *end) { vty_out(vty, "%% Malformed l2 nve ID \"%s\"\n", argv[1]->arg); return CMD_WARNING_CONFIG_FAILED; } if ((value_l < 1) || (value_l > 0xff)) { vty_out(vty, "%% Malformed l2 nve id (must be greater than 0 and less than %u\n", 0x100); return CMD_WARNING_CONFIG_FAILED; } } bgp->rfapi_cfg->flags |= BGP_VNC_CONFIG_L2RD; bgp->rfapi_cfg->default_l2rd = value; return CMD_SUCCESS; } DEFUN (vnc_defaults_no_l2rd, vnc_defaults_no_l2rd_cmd, "no l2rd", NO_STR "Specify default Local Nve ID value to use in RD for L2 routes\n") { VTY_DECLVAR_CONTEXT(bgp, bgp); bgp->rfapi_cfg->default_l2rd = 0; bgp->rfapi_cfg->flags &= ~BGP_VNC_CONFIG_L2RD; return CMD_SUCCESS; } DEFUN (vnc_defaults_responselifetime, vnc_defaults_responselifetime_cmd, "response-lifetime ", "Specify default response lifetime\n" "Response lifetime in seconds\n" "Infinite response lifetime\n") { VTY_DECLVAR_CONTEXT(bgp, bgp); uint32_t rspint; struct rfapi *h = NULL; struct listnode *hdnode; struct rfapi_descriptor *rfd; h = bgp->rfapi; if (!h) return CMD_WARNING_CONFIG_FAILED; if (strmatch(argv[1]->text, "infinite")) { rspint = RFAPI_INFINITE_LIFETIME; } else { rspint = strtoul(argv[1]->arg, NULL, 10); if (rspint > INT32_MAX) rspint = INT32_MAX; /* is really an int, not an unsigned int */ } bgp->rfapi_cfg->default_response_lifetime = rspint; for (ALL_LIST_ELEMENTS_RO(&h->descriptors, hdnode, rfd)) if (rfd->rfg && !(rfd->rfg->flags & RFAPI_RFG_RESPONSE_LIFETIME)) rfd->response_lifetime = rfd->rfg->response_lifetime = rspint; return CMD_SUCCESS; } struct rfapi_nve_group_cfg * bgp_rfapi_cfg_match_byname(struct bgp *bgp, const char *name, rfapi_group_cfg_type_t type) /* _MAX = any */ { struct rfapi_nve_group_cfg *rfg; struct listnode *node, *nnode; for (ALL_LIST_ELEMENTS(bgp->rfapi_cfg->nve_groups_sequential, node, nnode, rfg)) { if ((type == RFAPI_GROUP_CFG_MAX || type == rfg->type) && !strcmp(rfg->name, name)) return rfg; } return NULL; } static struct rfapi_nve_group_cfg * rfapi_group_new(struct bgp *bgp, rfapi_group_cfg_type_t type, const char *name) { struct rfapi_nve_group_cfg *rfg; rfg = XCALLOC(MTYPE_RFAPI_GROUP_CFG, sizeof(struct rfapi_nve_group_cfg)); rfg->type = type; rfg->name = strdup(name); /* add to tail of list */ listnode_add(bgp->rfapi_cfg->nve_groups_sequential, rfg); rfg->label = MPLS_LABEL_NONE; QOBJ_REG(rfg, rfapi_nve_group_cfg); return rfg; } static struct rfapi_l2_group_cfg *rfapi_l2_group_lookup_byname(struct bgp *bgp, const char *name) { struct rfapi_l2_group_cfg *rfg; struct listnode *node, *nnode; if (bgp->rfapi_cfg->l2_groups == NULL) /* not the best place for this */ bgp->rfapi_cfg->l2_groups = list_new(); for (ALL_LIST_ELEMENTS(bgp->rfapi_cfg->l2_groups, node, nnode, rfg)) { if (!strcmp(rfg->name, name)) return rfg; } return NULL; } static struct rfapi_l2_group_cfg *rfapi_l2_group_new(void) { struct rfapi_l2_group_cfg *rfg; rfg = XCALLOC(MTYPE_RFAPI_L2_CFG, sizeof(struct rfapi_l2_group_cfg)); QOBJ_REG(rfg, rfapi_l2_group_cfg); return rfg; } static void rfapi_l2_group_del(struct rfapi_l2_group_cfg *rfg) { QOBJ_UNREG(rfg); XFREE(MTYPE_RFAPI_L2_CFG, rfg); } static int rfapi_str2route_type(const char *l3str, const char *pstr, afi_t *afi, int *type) { if (!l3str || !pstr) return EINVAL; if (!strcmp(l3str, "ipv4")) { *afi = AFI_IP; } else { if (!strcmp(l3str, "ipv6")) *afi = AFI_IP6; else return ENOENT; } if (!strcmp(pstr, "connected")) *type = ZEBRA_ROUTE_CONNECT; if (!strcmp(pstr, "kernel")) *type = ZEBRA_ROUTE_KERNEL; if (!strcmp(pstr, "static")) *type = ZEBRA_ROUTE_STATIC; if (!strcmp(pstr, "bgp")) *type = ZEBRA_ROUTE_BGP; if (!strcmp(pstr, "bgp-direct")) *type = ZEBRA_ROUTE_BGP_DIRECT; if (!strcmp(pstr, "bgp-direct-to-nve-groups")) *type = ZEBRA_ROUTE_BGP_DIRECT_EXT; if (!strcmp(pstr, "rip")) { if (*afi == AFI_IP) *type = ZEBRA_ROUTE_RIP; else *type = ZEBRA_ROUTE_RIPNG; } if (!strcmp(pstr, "ripng")) { if (*afi == AFI_IP) return EAFNOSUPPORT; *type = ZEBRA_ROUTE_RIPNG; } if (!strcmp(pstr, "ospf")) { if (*afi == AFI_IP) *type = ZEBRA_ROUTE_OSPF; else *type = ZEBRA_ROUTE_OSPF6; } if (!strcmp(pstr, "ospf6")) { if (*afi == AFI_IP) return EAFNOSUPPORT; *type = ZEBRA_ROUTE_OSPF6; } return 0; } /*------------------------------------------------------------------------- * redistribute *-----------------------------------------------------------------------*/ #define VNC_REDIST_ENABLE(bgp, afi, type) \ do { \ switch (type) { \ case ZEBRA_ROUTE_BGP_DIRECT: \ vnc_import_bgp_redist_enable((bgp), (afi)); \ break; \ case ZEBRA_ROUTE_BGP_DIRECT_EXT: \ vnc_import_bgp_exterior_redist_enable((bgp), (afi)); \ break; \ default: \ if ((type) < ZEBRA_ROUTE_MAX) \ vnc_redistribute_set((bgp), (afi), (type)); \ break; \ } \ } while (0) #define VNC_REDIST_DISABLE(bgp, afi, type) \ do { \ switch (type) { \ case ZEBRA_ROUTE_BGP_DIRECT: \ vnc_import_bgp_redist_disable((bgp), (afi)); \ break; \ case ZEBRA_ROUTE_BGP_DIRECT_EXT: \ vnc_import_bgp_exterior_redist_disable((bgp), (afi)); \ break; \ default: \ if ((type) < ZEBRA_ROUTE_MAX) \ vnc_redistribute_unset((bgp), (afi), (type)); \ break; \ } \ } while (0) static uint8_t redist_was_enabled[AFI_MAX][ZEBRA_ROUTE_MAX]; static void vnc_redistribute_prechange(struct bgp *bgp) { afi_t afi; int type; vnc_zlog_debug_verbose("%s: entry", __func__); memset(redist_was_enabled, 0, sizeof(redist_was_enabled)); /* * Look to see if we have any redistribution enabled. If so, flush * the corresponding routes and turn off redistribution temporarily. * We need to do it because the RD's used for the redistributed * routes depend on the nve group. */ for (afi = AFI_IP; afi < AFI_MAX; ++afi) { for (type = 0; type < ZEBRA_ROUTE_MAX; ++type) { if (bgp->rfapi_cfg->redist[afi][type]) { redist_was_enabled[afi][type] = 1; VNC_REDIST_DISABLE(bgp, afi, type); } } } vnc_zlog_debug_verbose("%s: return", __func__); } static void vnc_redistribute_postchange(struct bgp *bgp) { afi_t afi; int type; vnc_zlog_debug_verbose("%s: entry", __func__); /* * If we turned off redistribution above, turn it back on. Doing so * will tell zebra to resend the routes to us */ for (afi = AFI_IP; afi < AFI_MAX; ++afi) { for (type = 0; type < ZEBRA_ROUTE_MAX; ++type) { if (redist_was_enabled[afi][type]) { VNC_REDIST_ENABLE(bgp, afi, type); } } } vnc_zlog_debug_verbose("%s: return", __func__); } DEFUN (vnc_redistribute_rh_roo_localadmin, vnc_redistribute_rh_roo_localadmin_cmd, "vnc redistribute resolve-nve roo-ec-local-admin (0-65535)", VNC_CONFIG_STR "Redistribute routes into VNC\n" "Resolve-NVE mode\n" "Route Origin Extended Community Local Admin Field\n" "Field value\n") { VTY_DECLVAR_CONTEXT(bgp, bgp); uint32_t localadmin; char *endptr; VNC_VTY_CONFIG_CHECK(bgp); localadmin = strtoul(argv[4]->arg, &endptr, 0); if (!argv[4]->arg[0] || *endptr) { vty_out(vty, "%% Malformed value\n"); return CMD_WARNING_CONFIG_FAILED; } if (localadmin > 0xffff) { vty_out(vty, "%% Value out of range (0-%d)\n", 0xffff); return CMD_WARNING_CONFIG_FAILED; } if (bgp->rfapi_cfg->resolve_nve_roo_local_admin == localadmin) return CMD_SUCCESS; if ((bgp->rfapi_cfg->flags & BGP_VNC_CONFIG_EXPORT_BGP_MODE_BITS) == BGP_VNC_CONFIG_EXPORT_BGP_MODE_CE) { vnc_export_bgp_prechange(bgp); } vnc_redistribute_prechange(bgp); bgp->rfapi_cfg->resolve_nve_roo_local_admin = localadmin; if ((bgp->rfapi_cfg->flags & BGP_VNC_CONFIG_EXPORT_BGP_MODE_BITS) == BGP_VNC_CONFIG_EXPORT_BGP_MODE_CE) { vnc_export_bgp_postchange(bgp); } vnc_redistribute_postchange(bgp); return CMD_SUCCESS; } DEFUN (vnc_redistribute_mode, vnc_redistribute_mode_cmd, "vnc redistribute mode ", VNC_CONFIG_STR "Redistribute routes into VNC\n" "Redistribution mode\n" "Based on redistribute nve-group\n" "Unmodified\n" "Resolve each nexthop to connected NVEs\n") { VTY_DECLVAR_CONTEXT(bgp, bgp); vnc_redist_mode_t newmode; VNC_VTY_CONFIG_CHECK(bgp); switch (argv[3]->arg[0]) { case 'n': newmode = VNC_REDIST_MODE_RFG; break; case 'p': newmode = VNC_REDIST_MODE_PLAIN; break; case 'r': newmode = VNC_REDIST_MODE_RESOLVE_NVE; break; default: vty_out(vty, "unknown redistribute mode\n"); return CMD_WARNING_CONFIG_FAILED; } if (newmode != bgp->rfapi_cfg->redist_mode) { vnc_redistribute_prechange(bgp); bgp->rfapi_cfg->redist_mode = newmode; vnc_redistribute_postchange(bgp); } return CMD_SUCCESS; } DEFUN (vnc_redistribute_protocol, vnc_redistribute_protocol_cmd, "vnc redistribute ", VNC_CONFIG_STR "Redistribute routes into VNC\n" "IPv4 routes\n" "IPv6 routes\n" "From BGP\n" "From BGP without Zebra\n" "From BGP without Zebra, only to configured NVE groups\n" "Connected interfaces\n" "From kernel routes\n" "From Open Shortest Path First (OSPF)\n" "From Routing Information Protocol (RIP)\n" "From Static routes\n") { VTY_DECLVAR_CONTEXT(bgp, bgp); int type = ZEBRA_ROUTE_MAX; /* init to bogus value */ afi_t afi; VNC_VTY_CONFIG_CHECK(bgp); if (rfapi_str2route_type(argv[2]->arg, argv[3]->arg, &afi, &type)) { vty_out(vty, "%% Invalid route type\n"); return CMD_WARNING_CONFIG_FAILED; } if (type == ZEBRA_ROUTE_BGP_DIRECT_EXT) { if (bgp->rfapi_cfg->redist_bgp_exterior_view_name) { VNC_REDIST_DISABLE(bgp, afi, type); /* disabled view implicitly */ free(bgp->rfapi_cfg->redist_bgp_exterior_view_name); bgp->rfapi_cfg->redist_bgp_exterior_view_name = NULL; } bgp->rfapi_cfg->redist_bgp_exterior_view = bgp; } VNC_REDIST_ENABLE(bgp, afi, type); return CMD_SUCCESS; } DEFUN (vnc_no_redistribute_protocol, vnc_no_redistribute_protocol_cmd, "no vnc redistribute ", NO_STR VNC_CONFIG_STR "Redistribute from other protocol\n" "IPv4 routes\n" "IPv6 routes\n" "From BGP\n" "From BGP without Zebra\n" "From BGP without Zebra, only to configured NVE groups\n" "Connected interfaces\n" "From kernel routes\n" "From Open Shortest Path First (OSPF)\n" "From Routing Information Protocol (RIP)\n" "From Static routes\n") { VTY_DECLVAR_CONTEXT(bgp, bgp); int type; afi_t afi; VNC_VTY_CONFIG_CHECK(bgp); if (rfapi_str2route_type(argv[3]->arg, argv[4]->arg, &afi, &type)) { vty_out(vty, "%% Invalid route type\n"); return CMD_WARNING_CONFIG_FAILED; } VNC_REDIST_DISABLE(bgp, afi, type); if (type == ZEBRA_ROUTE_BGP_DIRECT_EXT) { if (bgp->rfapi_cfg->redist_bgp_exterior_view_name) { free(bgp->rfapi_cfg->redist_bgp_exterior_view_name); bgp->rfapi_cfg->redist_bgp_exterior_view_name = NULL; } bgp->rfapi_cfg->redist_bgp_exterior_view = NULL; } return CMD_SUCCESS; } DEFUN (vnc_redistribute_bgp_exterior, vnc_redistribute_bgp_exterior_cmd, "vnc redistribute bgp-direct-to-nve-groups view NAME", VNC_CONFIG_STR "Redistribute routes into VNC\n" "IPv4 routes\n" "IPv6 routes\n" "From BGP without Zebra, only to configured NVE groups\n" "From BGP view\n" "BGP view name\n") { VTY_DECLVAR_CONTEXT(bgp, bgp); int type; afi_t afi; VNC_VTY_CONFIG_CHECK(bgp); if (rfapi_str2route_type(argv[2]->arg, "bgp-direct-to-nve-groups", &afi, &type)) { vty_out(vty, "%% Invalid route type\n"); return CMD_WARNING_CONFIG_FAILED; } if (bgp->rfapi_cfg->redist_bgp_exterior_view_name) free(bgp->rfapi_cfg->redist_bgp_exterior_view_name); bgp->rfapi_cfg->redist_bgp_exterior_view_name = strdup(argv[5]->arg); /* could be NULL if name is not defined yet */ bgp->rfapi_cfg->redist_bgp_exterior_view = bgp_lookup_by_name(argv[5]->arg); VNC_REDIST_ENABLE(bgp, afi, type); return CMD_SUCCESS; } DEFUN (vnc_redistribute_nvegroup, vnc_redistribute_nvegroup_cmd, "vnc redistribute nve-group NAME", VNC_CONFIG_STR "Assign a NVE group to routes redistributed from another routing protocol\n" "NVE group\n" "Group name\n") { VTY_DECLVAR_CONTEXT(bgp, bgp); VNC_VTY_CONFIG_CHECK(bgp); vnc_redistribute_prechange(bgp); /* * OK if nve group doesn't exist yet; we'll set the pointer * when the group is defined later */ bgp->rfapi_cfg->rfg_redist = bgp_rfapi_cfg_match_byname( bgp, argv[3]->arg, RFAPI_GROUP_CFG_NVE); if (bgp->rfapi_cfg->rfg_redist_name) free(bgp->rfapi_cfg->rfg_redist_name); bgp->rfapi_cfg->rfg_redist_name = strdup(argv[3]->arg); vnc_redistribute_postchange(bgp); return CMD_SUCCESS; } DEFUN (vnc_redistribute_no_nvegroup, vnc_redistribute_no_nvegroup_cmd, "no vnc redistribute nve-group", NO_STR VNC_CONFIG_STR "Redistribute from other protocol\n" "Assign a NVE group to routes redistributed from another routing protocol\n") { VTY_DECLVAR_CONTEXT(bgp, bgp); VNC_VTY_CONFIG_CHECK(bgp); vnc_redistribute_prechange(bgp); bgp->rfapi_cfg->rfg_redist = NULL; if (bgp->rfapi_cfg->rfg_redist_name) free(bgp->rfapi_cfg->rfg_redist_name); bgp->rfapi_cfg->rfg_redist_name = NULL; vnc_redistribute_postchange(bgp); return CMD_SUCCESS; } DEFUN (vnc_redistribute_lifetime, vnc_redistribute_lifetime_cmd, "vnc redistribute lifetime ", VNC_CONFIG_STR "Redistribute\n" "Assign a lifetime to routes redistributed from another routing protocol\n" "lifetime value (32 bit)\n" "Allow lifetime to never expire\n") { VTY_DECLVAR_CONTEXT(bgp, bgp); VNC_VTY_CONFIG_CHECK(bgp); vnc_redistribute_prechange(bgp); if (strmatch(argv[3]->text, "infinite")) { bgp->rfapi_cfg->redist_lifetime = RFAPI_INFINITE_LIFETIME; } else { bgp->rfapi_cfg->redist_lifetime = strtoul(argv[3]->arg, NULL, 10); } vnc_redistribute_postchange(bgp); return CMD_SUCCESS; } /*-- redist policy, non-nvegroup start --*/ DEFUN (vnc_redist_bgpdirect_no_prefixlist, vnc_redist_bgpdirect_no_prefixlist_cmd, "no vnc redistribute prefix-list", NO_STR VNC_CONFIG_STR "Redistribute from other protocol\n" "Redistribute from BGP directly\n" "Redistribute from BGP without Zebra, only to configured NVE groups\n" "IPv4 routes\n" "IPv6 routes\n" "Prefix-list for filtering redistributed routes\n") { VTY_DECLVAR_CONTEXT(bgp, bgp); afi_t afi; struct rfapi_cfg *hc; uint8_t route_type = 0; VNC_VTY_CONFIG_CHECK(bgp); hc = bgp->rfapi_cfg; if (strmatch(argv[3]->text, "bgp-direct")) { route_type = ZEBRA_ROUTE_BGP_DIRECT; } else { route_type = ZEBRA_ROUTE_BGP_DIRECT_EXT; } if (strmatch(argv[4]->text, "ipv4")) { afi = AFI_IP; } else { afi = AFI_IP6; } vnc_redistribute_prechange(bgp); if (hc->plist_redist_name[route_type][afi]) free(hc->plist_redist_name[route_type][afi]); hc->plist_redist_name[route_type][afi] = NULL; hc->plist_redist[route_type][afi] = NULL; vnc_redistribute_postchange(bgp); return CMD_SUCCESS; } DEFUN (vnc_redist_bgpdirect_prefixlist, vnc_redist_bgpdirect_prefixlist_cmd, "vnc redistribute prefix-list NAME", VNC_CONFIG_STR "Redistribute from other protocol\n" "Redistribute from BGP directly\n" "Redistribute from BGP without Zebra, only to configured NVE groups\n" "IPv4 routes\n" "IPv6 routes\n" "Prefix-list for filtering redistributed routes\n" "prefix list name\n") { VTY_DECLVAR_CONTEXT(bgp, bgp); struct rfapi_cfg *hc; afi_t afi; uint8_t route_type = 0; VNC_VTY_CONFIG_CHECK(bgp); hc = bgp->rfapi_cfg; if (strmatch(argv[2]->text, "bgp-direct")) { route_type = ZEBRA_ROUTE_BGP_DIRECT; } else { route_type = ZEBRA_ROUTE_BGP_DIRECT_EXT; } if (strmatch(argv[3]->text, "ipv4")) { afi = AFI_IP; } else { afi = AFI_IP6; } vnc_redistribute_prechange(bgp); if (hc->plist_redist_name[route_type][afi]) free(hc->plist_redist_name[route_type][afi]); hc->plist_redist_name[route_type][afi] = strdup(argv[5]->arg); hc->plist_redist[route_type][afi] = prefix_list_lookup(afi, argv[5]->arg); vnc_redistribute_postchange(bgp); return CMD_SUCCESS; } DEFUN (vnc_redist_bgpdirect_no_routemap, vnc_redist_bgpdirect_no_routemap_cmd, "no vnc redistribute route-map", NO_STR VNC_CONFIG_STR "Redistribute from other protocols\n" "Redistribute from BGP directly\n" "Redistribute from BGP without Zebra, only to configured NVE groups\n" "Route-map for filtering redistributed routes\n") { VTY_DECLVAR_CONTEXT(bgp, bgp); struct rfapi_cfg *hc; uint8_t route_type = 0; VNC_VTY_CONFIG_CHECK(bgp); hc = bgp->rfapi_cfg; if (strmatch(argv[3]->text, "bgp-direct")) { route_type = ZEBRA_ROUTE_BGP_DIRECT; } else { route_type = ZEBRA_ROUTE_BGP_DIRECT_EXT; } vnc_redistribute_prechange(bgp); if (hc->routemap_redist_name[route_type]) free(hc->routemap_redist_name[route_type]); hc->routemap_redist_name[route_type] = NULL; hc->routemap_redist[route_type] = NULL; vnc_redistribute_postchange(bgp); return CMD_SUCCESS; } DEFUN (vnc_redist_bgpdirect_routemap, vnc_redist_bgpdirect_routemap_cmd, "vnc redistribute route-map NAME", VNC_CONFIG_STR "Redistribute from other protocols\n" "Redistribute from BGP directly\n" "Redistribute from BGP without Zebra, only to configured NVE groups\n" "Route-map for filtering exported routes\n" "route map name\n") { VTY_DECLVAR_CONTEXT(bgp, bgp); struct rfapi_cfg *hc; uint8_t route_type = 0; VNC_VTY_CONFIG_CHECK(bgp); hc = bgp->rfapi_cfg; if (strmatch(argv[2]->text, "bgp-direct")) { route_type = ZEBRA_ROUTE_BGP_DIRECT; } else { route_type = ZEBRA_ROUTE_BGP_DIRECT_EXT; } vnc_redistribute_prechange(bgp); if (hc->routemap_redist_name[route_type]) free(hc->routemap_redist_name[route_type]); /* If the old route map config overwrite with new * route map config , old routemap counter have to be * reduced. */ route_map_counter_decrement(hc->routemap_redist[route_type]); hc->routemap_redist_name[route_type] = strdup(argv[4]->arg); hc->routemap_redist[route_type] = route_map_lookup_by_name(argv[4]->arg); route_map_counter_increment(hc->routemap_redist[route_type]); vnc_redistribute_postchange(bgp); return CMD_SUCCESS; } /*-- redist policy, non-nvegroup end --*/ /*-- redist policy, nvegroup start --*/ DEFUN (vnc_nve_group_redist_bgpdirect_no_prefixlist, vnc_nve_group_redist_bgpdirect_no_prefixlist_cmd, "no redistribute bgp-direct prefix-list", NO_STR "Redistribute from other protocol\n" "Redistribute from BGP directly\n" "IPv4 routes\n" "IPv6 routes\n" "Prefix-list for filtering redistributed routes\n") { VTY_DECLVAR_CONTEXT(bgp, bgp); VTY_DECLVAR_CONTEXT_SUB(rfapi_nve_group_cfg, rfg) afi_t afi; VNC_VTY_CONFIG_CHECK(bgp); /* make sure it's still in list */ if (!listnode_lookup(bgp->rfapi_cfg->nve_groups_sequential, rfg)) { /* Not in list anymore */ vty_out(vty, "Current NVE group no longer exists\n"); return CMD_WARNING_CONFIG_FAILED; } if (strmatch(argv[3]->text, "ipv4")) { afi = AFI_IP; } else { afi = AFI_IP6; } vnc_redistribute_prechange(bgp); if (rfg->plist_redist_name[ZEBRA_ROUTE_BGP_DIRECT][afi]) free(rfg->plist_redist_name[ZEBRA_ROUTE_BGP_DIRECT][afi]); rfg->plist_redist_name[ZEBRA_ROUTE_BGP_DIRECT][afi] = NULL; rfg->plist_redist[ZEBRA_ROUTE_BGP_DIRECT][afi] = NULL; vnc_redistribute_postchange(bgp); return CMD_SUCCESS; } DEFUN (vnc_nve_group_redist_bgpdirect_prefixlist, vnc_nve_group_redist_bgpdirect_prefixlist_cmd, "redistribute bgp-direct prefix-list NAME", "Redistribute from other protocol\n" "Redistribute from BGP directly\n" "IPv4 routes\n" "IPv6 routes\n" "Prefix-list for filtering redistributed routes\n" "prefix list name\n") { VTY_DECLVAR_CONTEXT(bgp, bgp); VTY_DECLVAR_CONTEXT_SUB(rfapi_nve_group_cfg, rfg); afi_t afi; VNC_VTY_CONFIG_CHECK(bgp); /* make sure it's still in list */ if (!listnode_lookup(bgp->rfapi_cfg->nve_groups_sequential, rfg)) { /* Not in list anymore */ vty_out(vty, "Current NVE group no longer exists\n"); return CMD_WARNING_CONFIG_FAILED; } if (strmatch(argv[2]->text, "ipv4")) { afi = AFI_IP; } else { afi = AFI_IP6; } vnc_redistribute_prechange(bgp); if (rfg->plist_redist_name[ZEBRA_ROUTE_BGP_DIRECT][afi]) free(rfg->plist_redist_name[ZEBRA_ROUTE_BGP_DIRECT][afi]); rfg->plist_redist_name[ZEBRA_ROUTE_BGP_DIRECT][afi] = strdup(argv[4]->arg); rfg->plist_redist[ZEBRA_ROUTE_BGP_DIRECT][afi] = prefix_list_lookup(afi, argv[4]->arg); vnc_redistribute_postchange(bgp); return CMD_SUCCESS; } DEFUN (vnc_nve_group_redist_bgpdirect_no_routemap, vnc_nve_group_redist_bgpdirect_no_routemap_cmd, "no redistribute bgp-direct route-map", NO_STR "Redistribute from other protocols\n" "Redistribute from BGP directly\n" "Route-map for filtering redistributed routes\n") { VTY_DECLVAR_CONTEXT(bgp, bgp); VTY_DECLVAR_CONTEXT_SUB(rfapi_nve_group_cfg, rfg); VNC_VTY_CONFIG_CHECK(bgp); /* make sure it's still in list */ if (!listnode_lookup(bgp->rfapi_cfg->nve_groups_sequential, rfg)) { /* Not in list anymore */ vty_out(vty, "Current NVE group no longer exists\n"); return CMD_WARNING_CONFIG_FAILED; } vnc_redistribute_prechange(bgp); if (rfg->routemap_redist_name[ZEBRA_ROUTE_BGP_DIRECT]) free(rfg->routemap_redist_name[ZEBRA_ROUTE_BGP_DIRECT]); route_map_counter_decrement( rfg->routemap_redist[ZEBRA_ROUTE_BGP_DIRECT]); rfg->routemap_redist_name[ZEBRA_ROUTE_BGP_DIRECT] = NULL; rfg->routemap_redist[ZEBRA_ROUTE_BGP_DIRECT] = NULL; vnc_redistribute_postchange(bgp); return CMD_SUCCESS; } DEFUN (vnc_nve_group_redist_bgpdirect_routemap, vnc_nve_group_redist_bgpdirect_routemap_cmd, "redistribute bgp-direct route-map NAME", "Redistribute from other protocols\n" "Redistribute from BGP directly\n" "Route-map for filtering exported routes\n" "route map name\n") { VTY_DECLVAR_CONTEXT(bgp, bgp); VTY_DECLVAR_CONTEXT_SUB(rfapi_nve_group_cfg, rfg); VNC_VTY_CONFIG_CHECK(bgp); /* make sure it's still in list */ if (!listnode_lookup(bgp->rfapi_cfg->nve_groups_sequential, rfg)) { /* Not in list anymore */ vty_out(vty, "Current NVE group no longer exists\n"); return CMD_WARNING_CONFIG_FAILED; } vnc_redistribute_prechange(bgp); if (rfg->routemap_redist_name[ZEBRA_ROUTE_BGP_DIRECT]) free(rfg->routemap_redist_name[ZEBRA_ROUTE_BGP_DIRECT]); route_map_counter_decrement( rfg->routemap_redist[ZEBRA_ROUTE_BGP_DIRECT]); rfg->routemap_redist_name[ZEBRA_ROUTE_BGP_DIRECT] = strdup(argv[3]->arg); rfg->routemap_redist[ZEBRA_ROUTE_BGP_DIRECT] = route_map_lookup_by_name(argv[3]->arg); route_map_counter_increment( rfg->routemap_redist[ZEBRA_ROUTE_BGP_DIRECT]); vnc_redistribute_postchange(bgp); return CMD_SUCCESS; } /*-- redist policy, nvegroup end --*/ /*------------------------------------------------------------------------- * export *-----------------------------------------------------------------------*/ DEFUN (vnc_export_mode, vnc_export_mode_cmd, "vnc export mode ", VNC_CONFIG_STR "Export to other protocols\n" "Export to BGP\n" "Export to Zebra (experimental)\n" "Select export mode\n" "Export routes with nve-group next-hops\n" "Export routes with NVE connected router next-hops\n" "Disable export\n" "Export routes with registering NVE as next-hop\n") { VTY_DECLVAR_CONTEXT(bgp, bgp); uint32_t oldmode = 0; uint32_t newmode = 0; VNC_VTY_CONFIG_CHECK(bgp); if (argv[2]->arg[0] == 'b') { oldmode = bgp->rfapi_cfg->flags & BGP_VNC_CONFIG_EXPORT_BGP_MODE_BITS; switch (argv[4]->arg[0]) { case 'g': newmode = BGP_VNC_CONFIG_EXPORT_BGP_MODE_GRP; break; case 'c': newmode = BGP_VNC_CONFIG_EXPORT_BGP_MODE_CE; break; case 'n': newmode = 0; break; case 'r': newmode = BGP_VNC_CONFIG_EXPORT_BGP_MODE_RH; break; default: vty_out(vty, "Invalid mode specified\n"); return CMD_WARNING_CONFIG_FAILED; } if (newmode == oldmode) { vty_out(vty, "Mode unchanged\n"); return CMD_SUCCESS; } vnc_export_bgp_prechange(bgp); bgp->rfapi_cfg->flags &= ~BGP_VNC_CONFIG_EXPORT_BGP_MODE_BITS; bgp->rfapi_cfg->flags |= newmode; vnc_export_bgp_postchange(bgp); } else { /* * export to zebra with RH mode is not yet implemented */ vty_out(vty, "Changing modes for zebra export not implemented yet\n"); return CMD_WARNING_CONFIG_FAILED; } return CMD_SUCCESS; } static struct rfapi_rfg_name *rfgn_new(void) { return XCALLOC(MTYPE_RFAPI_RFG_NAME, sizeof(struct rfapi_rfg_name)); } static void rfgn_free(struct rfapi_rfg_name *rfgn) { XFREE(MTYPE_RFAPI_RFG_NAME, rfgn); } DEFUN (vnc_export_nvegroup, vnc_export_nvegroup_cmd, "vnc export group-nve group NAME", VNC_CONFIG_STR "Export to other protocols\n" "Export to BGP\n" "Export to Zebra (experimental)\n" "NVE group, used in 'group-nve' export mode\n" "NVE group\n" "Group name\n") { VTY_DECLVAR_CONTEXT(bgp, bgp); struct rfapi_nve_group_cfg *rfg_new; VNC_VTY_CONFIG_CHECK(bgp); rfg_new = bgp_rfapi_cfg_match_byname(bgp, argv[5]->arg, RFAPI_GROUP_CFG_NVE); if (rfg_new == NULL) { rfg_new = bgp_rfapi_cfg_match_byname(bgp, argv[5]->arg, RFAPI_GROUP_CFG_VRF); if (rfg_new) vnc_add_vrf_opener(bgp, rfg_new); } if (rfg_new == NULL) { vty_out(vty, "Can't find group named \"%s\".\n", argv[5]->arg); return CMD_WARNING_CONFIG_FAILED; } if (argv[2]->arg[0] == 'b') { struct listnode *node; struct rfapi_rfg_name *rfgn; /* * Set group for export to BGP Direct */ /* see if group is already included in export list */ for (ALL_LIST_ELEMENTS_RO( bgp->rfapi_cfg->rfg_export_direct_bgp_l, node, rfgn)) { if (!strcmp(rfgn->name, argv[5]->arg)) { /* already in the list: we're done */ return CMD_SUCCESS; } } rfgn = rfgn_new(); rfgn->name = strdup(argv[5]->arg); rfgn->rfg = rfg_new; /* OK if not set yet */ listnode_add(bgp->rfapi_cfg->rfg_export_direct_bgp_l, rfgn); vnc_zlog_debug_verbose("%s: testing rfg_new", __func__); if (rfg_new) { vnc_zlog_debug_verbose( "%s: testing bgp grp mode enabled", __func__); if (VNC_EXPORT_BGP_GRP_ENABLED(bgp->rfapi_cfg)) vnc_zlog_debug_verbose( "%s: calling vnc_direct_bgp_add_group", __func__); vnc_direct_bgp_add_group(bgp, rfg_new); } } else { struct listnode *node; struct rfapi_rfg_name *rfgn; /* * Set group for export to Zebra */ /* see if group is already included in export list */ for (ALL_LIST_ELEMENTS_RO(bgp->rfapi_cfg->rfg_export_zebra_l, node, rfgn)) { if (!strcmp(rfgn->name, argv[5]->arg)) { /* already in the list: we're done */ return CMD_SUCCESS; } } rfgn = rfgn_new(); rfgn->name = strdup(argv[5]->arg); rfgn->rfg = rfg_new; /* OK if not set yet */ listnode_add(bgp->rfapi_cfg->rfg_export_zebra_l, rfgn); if (rfg_new) { if (VNC_EXPORT_ZEBRA_GRP_ENABLED(bgp->rfapi_cfg)) vnc_zebra_add_group(bgp, rfg_new); } } return CMD_SUCCESS; } /* * This command applies to routes exported from VNC to BGP directly * without going though zebra */ DEFUN (vnc_no_export_nvegroup, vnc_no_export_nvegroup_cmd, "vnc export group-nve no group NAME", VNC_CONFIG_STR "Export to other protocols\n" "Export to BGP\n" "Export to Zebra (experimental)\n" "NVE group, used in 'group-nve' export mode\n" "Disable export of VNC routes\n" "NVE group\n" "Group name\n") { VTY_DECLVAR_CONTEXT(bgp, bgp); struct listnode *node, *nnode; struct rfapi_rfg_name *rfgn; VNC_VTY_CONFIG_CHECK(bgp); if (argv[2]->arg[0] == 'b') { for (ALL_LIST_ELEMENTS(bgp->rfapi_cfg->rfg_export_direct_bgp_l, node, nnode, rfgn)) { if (rfgn->name && !strcmp(rfgn->name, argv[6]->arg)) { vnc_zlog_debug_verbose("%s: matched \"%s\"", __func__, rfgn->name); if (rfgn->rfg) vnc_direct_bgp_del_group(bgp, rfgn->rfg); free(rfgn->name); list_delete_node( bgp->rfapi_cfg->rfg_export_direct_bgp_l, node); rfgn_free(rfgn); break; } } } else { for (ALL_LIST_ELEMENTS(bgp->rfapi_cfg->rfg_export_zebra_l, node, nnode, rfgn)) { vnc_zlog_debug_verbose("does rfg \"%s\" match?", rfgn->name); if (rfgn->name && !strcmp(rfgn->name, argv[6]->arg)) { if (rfgn->rfg) vnc_zebra_del_group(bgp, rfgn->rfg); free(rfgn->name); list_delete_node( bgp->rfapi_cfg->rfg_export_zebra_l, node); rfgn_free(rfgn); break; } } } return CMD_SUCCESS; } DEFUN (vnc_nve_group_export_no_prefixlist, vnc_nve_group_export_no_prefixlist_cmd, "no export prefix-list [NAME]", NO_STR "Export to other protocols\n" "Export to BGP\n" "Export to Zebra (experimental)\n" "IPv4 routes\n" "IPv6 routes\n" "Prefix-list for filtering exported routes\n" "prefix list name\n") { VTY_DECLVAR_CONTEXT(bgp, bgp); VTY_DECLVAR_CONTEXT_SUB(rfapi_nve_group_cfg, rfg); int idx = 0; int is_bgp = 1; afi_t afi; VNC_VTY_CONFIG_CHECK(bgp); /* make sure it's still in list */ if (!listnode_lookup(bgp->rfapi_cfg->nve_groups_sequential, rfg)) { /* Not in list anymore */ vty_out(vty, "Current NVE group no longer exists\n"); return CMD_WARNING_CONFIG_FAILED; } if (!argv_find_and_parse_afi(argv, argc, &idx, &afi)) { vty_out(vty, "%% Malformed Address Family\n"); return CMD_WARNING_CONFIG_FAILED; } if (argv[idx - 1]->text[0] == 'z') is_bgp = 0; idx += 2; /* skip afi and keyword */ if (is_bgp) { if (idx == argc || (rfg->plist_export_bgp_name[afi] && strmatch(argv[idx]->arg, rfg->plist_export_bgp_name[afi]))) { if (rfg->plist_export_bgp_name[afi]) free(rfg->plist_export_bgp_name[afi]); rfg->plist_export_bgp_name[afi] = NULL; rfg->plist_export_bgp[afi] = NULL; vnc_direct_bgp_reexport_group_afi(bgp, rfg, afi); } } else { if (idx == argc || (rfg->plist_export_zebra_name[afi] && strmatch(argv[idx]->arg, rfg->plist_export_zebra_name[afi]))) { if (rfg->plist_export_zebra_name[afi]) free(rfg->plist_export_zebra_name[afi]); rfg->plist_export_zebra_name[afi] = NULL; rfg->plist_export_zebra[afi] = NULL; vnc_zebra_reexport_group_afi(bgp, rfg, afi); } } return CMD_SUCCESS; } ALIAS (vnc_nve_group_export_no_prefixlist, vnc_vrf_policy_export_no_prefixlist_cmd, "no export prefix-list [NAME]", NO_STR "Export to VRF\n" "IPv4 routes\n" "IPv6 routes\n" "Prefix-list for filtering exported routes\n" "prefix list name\n") DEFUN (vnc_nve_group_export_prefixlist, vnc_nve_group_export_prefixlist_cmd, "export prefix-list NAME", "Export to other protocols\n" "Export to BGP\n" "Export to Zebra (experimental)\n" "IPv4 routes\n" "IPv6 routes\n" "Prefix-list for filtering exported routes\n" "prefix list name\n") { VTY_DECLVAR_CONTEXT(bgp, bgp); VTY_DECLVAR_CONTEXT_SUB(rfapi_nve_group_cfg, rfg); int idx = 0; int is_bgp = 1; afi_t afi; VNC_VTY_CONFIG_CHECK(bgp); /* make sure it's still in list */ if (!listnode_lookup(bgp->rfapi_cfg->nve_groups_sequential, rfg)) { /* Not in list anymore */ vty_out(vty, "Current NVE group no longer exists\n"); return CMD_WARNING_CONFIG_FAILED; } if (!argv_find_and_parse_afi(argv, argc, &idx, &afi)) { vty_out(vty, "%% Malformed Address Family\n"); return CMD_WARNING_CONFIG_FAILED; } if (argv[idx - 1]->text[0] == 'z') is_bgp = 0; idx = argc - 1; if (is_bgp) { if (rfg->plist_export_bgp_name[afi]) free(rfg->plist_export_bgp_name[afi]); rfg->plist_export_bgp_name[afi] = strdup(argv[idx]->arg); rfg->plist_export_bgp[afi] = prefix_list_lookup(afi, argv[idx]->arg); vnc_direct_bgp_reexport_group_afi(bgp, rfg, afi); } else { if (rfg->plist_export_zebra_name[afi]) free(rfg->plist_export_zebra_name[afi]); rfg->plist_export_zebra_name[afi] = strdup(argv[idx]->arg); rfg->plist_export_zebra[afi] = prefix_list_lookup(afi, argv[idx]->arg); vnc_zebra_reexport_group_afi(bgp, rfg, afi); } return CMD_SUCCESS; } ALIAS (vnc_nve_group_export_prefixlist, vnc_vrf_policy_export_prefixlist_cmd, "export prefix-list NAME", "Export to VRF\n" "IPv4 routes\n" "IPv6 routes\n" "Prefix-list for filtering exported routes\n" "prefix list name\n") DEFUN (vnc_nve_group_export_no_routemap, vnc_nve_group_export_no_routemap_cmd, "no export route-map [NAME]", NO_STR "Export to other protocols\n" "Export to BGP\n" "Export to Zebra (experimental)\n" "Route-map for filtering exported routes\n" "route map name\n") { VTY_DECLVAR_CONTEXT(bgp, bgp); VTY_DECLVAR_CONTEXT_SUB(rfapi_nve_group_cfg, rfg); int idx = 2; int is_bgp = 1; VNC_VTY_CONFIG_CHECK(bgp); /* make sure it's still in list */ if (!listnode_lookup(bgp->rfapi_cfg->nve_groups_sequential, rfg)) { /* Not in list anymore */ vty_out(vty, "Current NVE group no longer exists\n"); return CMD_WARNING_CONFIG_FAILED; } switch (argv[idx]->text[0]) { case 'z': is_bgp = 0; /* fall thru */ case 'b': idx += 2; break; default: /* route-map */ idx++; break; } if (is_bgp) { if (idx == argc || (rfg->routemap_export_bgp_name && strmatch(argv[idx]->arg, rfg->routemap_export_bgp_name))) { if (rfg->routemap_export_bgp_name) free(rfg->routemap_export_bgp_name); route_map_counter_decrement(rfg->routemap_export_bgp); rfg->routemap_export_bgp_name = NULL; rfg->routemap_export_bgp = NULL; vnc_direct_bgp_reexport_group_afi(bgp, rfg, AFI_IP); vnc_direct_bgp_reexport_group_afi(bgp, rfg, AFI_IP6); } } else { if (idx == argc || (rfg->routemap_export_zebra_name && strmatch(argv[idx]->arg, rfg->routemap_export_zebra_name))) { if (rfg->routemap_export_zebra_name) free(rfg->routemap_export_zebra_name); route_map_counter_decrement(rfg->routemap_export_zebra); rfg->routemap_export_zebra_name = NULL; rfg->routemap_export_zebra = NULL; vnc_zebra_reexport_group_afi(bgp, rfg, AFI_IP); vnc_zebra_reexport_group_afi(bgp, rfg, AFI_IP6); } } return CMD_SUCCESS; } ALIAS (vnc_nve_group_export_no_routemap, vnc_vrf_policy_export_no_routemap_cmd, "no export route-map [NAME]", NO_STR "Export to VRF\n" "Route-map for filtering exported routes\n" "route map name\n") DEFUN (vnc_nve_group_export_routemap, vnc_nve_group_export_routemap_cmd, "export route-map NAME", "Export to other protocols\n" "Export to BGP\n" "Export to Zebra (experimental)\n" "Route-map for filtering exported routes\n" "route map name\n") { VTY_DECLVAR_CONTEXT(bgp, bgp); VTY_DECLVAR_CONTEXT_SUB(rfapi_nve_group_cfg, rfg); int idx = 0; int is_bgp = 1; VNC_VTY_CONFIG_CHECK(bgp); /* make sure it's still in list */ if (!listnode_lookup(bgp->rfapi_cfg->nve_groups_sequential, rfg)) { /* Not in list anymore */ vty_out(vty, "Current NVE group no longer exists\n"); return CMD_WARNING_CONFIG_FAILED; } if (argv[1]->text[0] == 'z') is_bgp = 0; idx = argc - 1; if (is_bgp) { if (rfg->routemap_export_bgp_name) free(rfg->routemap_export_bgp_name); route_map_counter_decrement(rfg->routemap_export_bgp); rfg->routemap_export_bgp_name = strdup(argv[idx]->arg); rfg->routemap_export_bgp = route_map_lookup_by_name(argv[idx]->arg); route_map_counter_increment(rfg->routemap_export_bgp); vnc_direct_bgp_reexport_group_afi(bgp, rfg, AFI_IP); vnc_direct_bgp_reexport_group_afi(bgp, rfg, AFI_IP6); } else { if (rfg->routemap_export_zebra_name) free(rfg->routemap_export_zebra_name); route_map_counter_decrement(rfg->routemap_export_zebra); rfg->routemap_export_zebra_name = strdup(argv[idx]->arg); rfg->routemap_export_zebra = route_map_lookup_by_name(argv[idx]->arg); route_map_counter_increment(rfg->routemap_export_zebra); vnc_zebra_reexport_group_afi(bgp, rfg, AFI_IP); vnc_zebra_reexport_group_afi(bgp, rfg, AFI_IP6); } return CMD_SUCCESS; } ALIAS (vnc_nve_group_export_routemap, vnc_vrf_policy_export_routemap_cmd, "export route-map NAME", "Export to VRF\n" "Route-map for filtering exported routes\n" "route map name\n") DEFUN (vnc_nve_export_no_prefixlist, vnc_nve_export_no_prefixlist_cmd, "no vnc export prefix-list [NAME]", NO_STR VNC_CONFIG_STR "Export to other protocols\n" "Export to BGP\n" "Export to Zebra (experimental)\n" "IPv4 prefixes\n" "IPv6 prefixes\n" "Prefix-list for filtering exported routes\n" "Prefix list name\n") { VTY_DECLVAR_CONTEXT(bgp, bgp); struct rfapi_cfg *hc; afi_t afi; VNC_VTY_CONFIG_CHECK(bgp); hc = bgp->rfapi_cfg; if (strmatch(argv[4]->text, "ipv4")) { afi = AFI_IP; } else { afi = AFI_IP6; } if (argv[3]->arg[0] == 'b') { if (((argc > 6) && hc->plist_export_bgp_name[afi] && strmatch(argv[6]->text, hc->plist_export_bgp_name[afi])) || (argc <= 6)) { free(hc->plist_export_bgp_name[afi]); hc->plist_export_bgp_name[afi] = NULL; hc->plist_export_bgp[afi] = NULL; vnc_direct_bgp_reexport(bgp, afi); } } else { if (((argc > 6) && hc->plist_export_zebra_name[afi] && strmatch(argv[6]->text, hc->plist_export_zebra_name[afi])) || (argc <= 6)) { free(hc->plist_export_zebra_name[afi]); hc->plist_export_zebra_name[afi] = NULL; hc->plist_export_zebra[afi] = NULL; /* TBD vnc_zebra_rh_reexport(bgp, afi); */ } } return CMD_SUCCESS; } DEFUN (vnc_nve_export_prefixlist, vnc_nve_export_prefixlist_cmd, "vnc export prefix-list NAME", VNC_CONFIG_STR "Export to other protocols\n" "Export to BGP\n" "Export to Zebra (experimental)\n" "IPv4 prefixes\n" "IPv6 prefixes\n" "Prefix-list for filtering exported routes\n" "Prefix list name\n") { VTY_DECLVAR_CONTEXT(bgp, bgp); struct rfapi_cfg *hc; afi_t afi; VNC_VTY_CONFIG_CHECK(bgp); hc = bgp->rfapi_cfg; if (strmatch(argv[3]->text, "ipv4")) { afi = AFI_IP; } else { afi = AFI_IP6; } if (argv[2]->arg[0] == 'b') { if (hc->plist_export_bgp_name[afi]) free(hc->plist_export_bgp_name[afi]); hc->plist_export_bgp_name[afi] = strdup(argv[5]->arg); hc->plist_export_bgp[afi] = prefix_list_lookup(afi, argv[5]->arg); vnc_direct_bgp_reexport(bgp, afi); } else { if (hc->plist_export_zebra_name[afi]) free(hc->plist_export_zebra_name[afi]); hc->plist_export_zebra_name[afi] = strdup(argv[5]->arg); hc->plist_export_zebra[afi] = prefix_list_lookup(afi, argv[5]->arg); /* TBD vnc_zebra_rh_reexport(bgp, afi); */ } return CMD_SUCCESS; } DEFUN (vnc_nve_export_no_routemap, vnc_nve_export_no_routemap_cmd, "no vnc export route-map [NAME]", NO_STR VNC_CONFIG_STR "Export to other protocols\n" "Export to BGP\n" "Export to Zebra (experimental)\n" "Route-map for filtering exported routes\n" "Route map name\n") { VTY_DECLVAR_CONTEXT(bgp, bgp); struct rfapi_cfg *hc; VNC_VTY_CONFIG_CHECK(bgp); hc = bgp->rfapi_cfg; if (argv[3]->arg[0] == 'b') { if (((argc > 5) && hc->routemap_export_bgp_name && strmatch(argv[5]->text, hc->routemap_export_bgp_name)) || (argc <= 5)) { free(hc->routemap_export_bgp_name); route_map_counter_decrement(hc->routemap_export_bgp); hc->routemap_export_bgp_name = NULL; hc->routemap_export_bgp = NULL; vnc_direct_bgp_reexport(bgp, AFI_IP); vnc_direct_bgp_reexport(bgp, AFI_IP6); } } else { if (((argc > 5) && hc->routemap_export_zebra_name && strmatch(argv[5]->text, hc->routemap_export_zebra_name)) || (argc <= 5)) { free(hc->routemap_export_zebra_name); route_map_counter_decrement(hc->routemap_export_zebra); hc->routemap_export_zebra_name = NULL; hc->routemap_export_zebra = NULL; /* TBD vnc_zebra_rh_reexport(bgp, AFI_IP); */ /* TBD vnc_zebra_rh_reexport(bgp, AFI_IP6); */ } } return CMD_SUCCESS; } DEFUN (vnc_nve_export_routemap, vnc_nve_export_routemap_cmd, "vnc export route-map NAME", VNC_CONFIG_STR "Export to other protocols\n" "Export to BGP\n" "Export to Zebra (experimental)\n" "Route-map for filtering exported routes\n" "Route map name\n") { VTY_DECLVAR_CONTEXT(bgp, bgp); struct rfapi_cfg *hc; VNC_VTY_CONFIG_CHECK(bgp); hc = bgp->rfapi_cfg; if (argv[2]->arg[0] == 'b') { if (hc->routemap_export_bgp_name) free(hc->routemap_export_bgp_name); route_map_counter_decrement(hc->routemap_export_bgp); hc->routemap_export_bgp_name = strdup(argv[4]->arg); hc->routemap_export_bgp = route_map_lookup_by_name(argv[4]->arg); route_map_counter_increment(hc->routemap_export_bgp); vnc_direct_bgp_reexport(bgp, AFI_IP); vnc_direct_bgp_reexport(bgp, AFI_IP6); } else { if (hc->routemap_export_zebra_name) free(hc->routemap_export_zebra_name); route_map_counter_decrement(hc->routemap_export_zebra); hc->routemap_export_zebra_name = strdup(argv[4]->arg); hc->routemap_export_zebra = route_map_lookup_by_name(argv[4]->arg); route_map_counter_increment(hc->routemap_export_zebra); /* TBD vnc_zebra_rh_reexport(bgp, AFI_IP); */ /* TBD vnc_zebra_rh_reexport(bgp, AFI_IP6); */ } return CMD_SUCCESS; } /* * respond to changes in the global prefix list configuration */ void vnc_prefix_list_update(struct bgp *bgp) { afi_t afi; struct listnode *n; struct rfapi_nve_group_cfg *rfg; struct rfapi_cfg *hc; int i; if (!bgp) { vnc_zlog_debug_verbose("%s: No BGP process is configured", __func__); return; } if (!(hc = bgp->rfapi_cfg)) { vnc_zlog_debug_verbose("%s: rfapi not configured", __func__); return; } for (afi = AFI_IP; afi < AFI_MAX; afi++) { /* * Loop over nve groups */ for (ALL_LIST_ELEMENTS_RO(bgp->rfapi_cfg->nve_groups_sequential, n, rfg)) { if (rfg->plist_export_bgp_name[afi]) { rfg->plist_export_bgp[afi] = prefix_list_lookup( afi, rfg->plist_export_bgp_name[afi]); } if (rfg->plist_export_zebra_name[afi]) { rfg->plist_export_zebra [afi] = prefix_list_lookup( afi, rfg->plist_export_zebra_name[afi]); } for (i = 0; i < ZEBRA_ROUTE_MAX; ++i) { if (rfg->plist_redist_name[i][afi]) { rfg->plist_redist [i][afi] = prefix_list_lookup( afi, rfg->plist_redist_name[i][afi]); } } vnc_direct_bgp_reexport_group_afi(bgp, rfg, afi); /* TBD vnc_zebra_reexport_group_afi(bgp, rfg, afi); */ } /* * RH config, too */ if (hc->plist_export_bgp_name[afi]) { hc->plist_export_bgp[afi] = prefix_list_lookup( afi, hc->plist_export_bgp_name[afi]); } if (hc->plist_export_zebra_name[afi]) { hc->plist_export_zebra[afi] = prefix_list_lookup( afi, hc->plist_export_zebra_name[afi]); } for (i = 0; i < ZEBRA_ROUTE_MAX; ++i) { if (hc->plist_redist_name[i][afi]) { hc->plist_redist[i][afi] = prefix_list_lookup( afi, hc->plist_redist_name[i][afi]); } } } vnc_direct_bgp_reexport(bgp, AFI_IP); vnc_direct_bgp_reexport(bgp, AFI_IP6); /* TBD vnc_zebra_rh_reexport(bgp, AFI_IP); */ /* TBD vnc_zebra_rh_reexport(bgp, AFI_IP6); */ vnc_redistribute_prechange(bgp); vnc_redistribute_postchange(bgp); } /* * respond to changes in the global route map configuration */ void vnc_routemap_update(struct bgp *bgp, const char *unused) { struct listnode *n; struct rfapi_nve_group_cfg *rfg; struct rfapi_cfg *hc; int i; struct route_map *old = NULL; vnc_zlog_debug_verbose("%s(arg=%s)", __func__, unused); if (!bgp) { vnc_zlog_debug_verbose("%s: No BGP process is configured", __func__); return; } if (!(hc = bgp->rfapi_cfg)) { vnc_zlog_debug_verbose("%s: rfapi not configured", __func__); return; } /* * Loop over nve groups */ for (ALL_LIST_ELEMENTS_RO(bgp->rfapi_cfg->nve_groups_sequential, n, rfg)) { if (rfg->routemap_export_bgp_name) { old = rfg->routemap_export_bgp; rfg->routemap_export_bgp = route_map_lookup_by_name( rfg->routemap_export_bgp_name); /* old is NULL. i.e Route map creation event. * So update applied_counter. * If Old is not NULL, i.e It may be routemap * updation or deletion. * So no need to update the counter. */ if (!old) route_map_counter_increment( rfg->routemap_export_bgp); } if (rfg->routemap_export_zebra_name) { old = rfg->routemap_export_bgp; rfg->routemap_export_bgp = route_map_lookup_by_name( rfg->routemap_export_zebra_name); if (!old) route_map_counter_increment( rfg->routemap_export_bgp); } for (i = 0; i < ZEBRA_ROUTE_MAX; ++i) { if (rfg->routemap_redist_name[i]) { old = rfg->routemap_redist[i]; rfg->routemap_redist[i] = route_map_lookup_by_name( rfg->routemap_redist_name[i]); if (!old) route_map_counter_increment( rfg->routemap_redist[i]); } } vnc_direct_bgp_reexport_group_afi(bgp, rfg, AFI_IP); vnc_direct_bgp_reexport_group_afi(bgp, rfg, AFI_IP6); /* TBD vnc_zebra_reexport_group_afi(bgp, rfg, afi); */ } /* * RH config, too */ if (hc->routemap_export_bgp_name) { old = hc->routemap_export_bgp; hc->routemap_export_bgp = route_map_lookup_by_name(hc->routemap_export_bgp_name); if (!old) route_map_counter_increment(hc->routemap_export_bgp); } if (hc->routemap_export_zebra_name) { old = hc->routemap_export_bgp; hc->routemap_export_bgp = route_map_lookup_by_name( hc->routemap_export_zebra_name); if (!old) route_map_counter_increment(hc->routemap_export_bgp); } for (i = 0; i < ZEBRA_ROUTE_MAX; ++i) { if (hc->routemap_redist_name[i]) { old = hc->routemap_redist[i]; hc->routemap_redist[i] = route_map_lookup_by_name( hc->routemap_redist_name[i]); if (!old) route_map_counter_increment( hc->routemap_redist[i]); } } vnc_direct_bgp_reexport(bgp, AFI_IP); vnc_direct_bgp_reexport(bgp, AFI_IP6); /* TBD vnc_zebra_rh_reexport(bgp, AFI_IP); */ /* TBD vnc_zebra_rh_reexport(bgp, AFI_IP6); */ vnc_redistribute_prechange(bgp); vnc_redistribute_postchange(bgp); vnc_zlog_debug_verbose("%s done", __func__); } /*------------------------------------------------------------------------- * nve-group *-----------------------------------------------------------------------*/ DEFUN_NOSH (vnc_nve_group, vnc_nve_group_cmd, "vnc nve-group NAME", VNC_CONFIG_STR "Configure a NVE group\n" "Group name\n") { VTY_DECLVAR_CONTEXT(bgp, bgp); struct rfapi_nve_group_cfg *rfg; struct listnode *node, *nnode; struct rfapi_rfg_name *rfgn; VNC_VTY_CONFIG_CHECK(bgp); /* Search for name */ rfg = bgp_rfapi_cfg_match_byname(bgp, argv[2]->arg, RFAPI_GROUP_CFG_NVE); if (!rfg) { rfg = rfapi_group_new(bgp, RFAPI_GROUP_CFG_NVE, argv[2]->arg); if (!rfg) { /* Error out of memory */ vty_out(vty, "Can't allocate memory for NVE group\n"); return CMD_WARNING_CONFIG_FAILED; } /* Copy defaults from struct rfapi_cfg */ rfg->rd = bgp->rfapi_cfg->default_rd; if (bgp->rfapi_cfg->flags & BGP_VNC_CONFIG_L2RD) { rfg->l2rd = bgp->rfapi_cfg->default_l2rd; rfg->flags |= RFAPI_RFG_L2RD; } rfg->rd = bgp->rfapi_cfg->default_rd; rfg->response_lifetime = bgp->rfapi_cfg->default_response_lifetime; if (bgp->rfapi_cfg->default_rt_export_list) { rfg->rt_export_list = ecommunity_dup( bgp->rfapi_cfg->default_rt_export_list); } if (bgp->rfapi_cfg->default_rt_import_list) { rfg->rt_import_list = ecommunity_dup( bgp->rfapi_cfg->default_rt_import_list); rfg->rfapi_import_table = rfapiImportTableRefAdd( bgp, rfg->rt_import_list, rfg); } /* * If a redist nve group was named but the group was not * defined, * make the linkage now */ if (!bgp->rfapi_cfg->rfg_redist) { if (bgp->rfapi_cfg->rfg_redist_name && !strcmp(bgp->rfapi_cfg->rfg_redist_name, rfg->name)) { vnc_redistribute_prechange(bgp); bgp->rfapi_cfg->rfg_redist = rfg; vnc_redistribute_postchange(bgp); } } /* * Same treatment for bgp-direct export group */ for (ALL_LIST_ELEMENTS(bgp->rfapi_cfg->rfg_export_direct_bgp_l, node, nnode, rfgn)) { if (!strcmp(rfgn->name, rfg->name)) { rfgn->rfg = rfg; vnc_direct_bgp_add_group(bgp, rfg); break; } } /* * Same treatment for zebra export group */ for (ALL_LIST_ELEMENTS(bgp->rfapi_cfg->rfg_export_zebra_l, node, nnode, rfgn)) { vnc_zlog_debug_verbose( "%s: ezport zebra: checking if \"%s\" == \"%s\"", __func__, rfgn->name, rfg->name); if (!strcmp(rfgn->name, rfg->name)) { rfgn->rfg = rfg; vnc_zebra_add_group(bgp, rfg); break; } } } /* * XXX subsequent calls will need to make sure this item is still * in the linked list and has the same name */ VTY_PUSH_CONTEXT_SUB(BGP_VNC_NVE_GROUP_NODE, rfg); return CMD_SUCCESS; } static void bgp_rfapi_delete_nve_group(struct vty *vty, /* NULL = no output */ struct bgp *bgp, struct rfapi_nve_group_cfg *rfg) { struct list *orphaned_nves = NULL; struct listnode *node, *nnode; /* * If there are currently-open NVEs that belong to this group, * zero out their references to this group structure. */ if (rfg->nves) { struct rfapi_descriptor *rfd; orphaned_nves = list_new(); while ((rfd = listnode_head(rfg->nves))) { rfd->rfg = NULL; listnode_delete(rfg->nves, rfd); listnode_add(orphaned_nves, rfd); } list_delete(&rfg->nves); } /* delete it */ free(rfg->name); if (rfg->rfapi_import_table) rfapiImportTableRefDelByIt(bgp, rfg->rfapi_import_table); if (rfg->rt_import_list) ecommunity_free(&rfg->rt_import_list); if (rfg->rt_export_list) ecommunity_free(&rfg->rt_export_list); if (rfg->vn_node) { rfg->vn_node->info = NULL; agg_unlock_node(rfg->vn_node); /* frees */ } if (rfg->un_node) { rfg->un_node->info = NULL; agg_unlock_node(rfg->un_node); /* frees */ } if (rfg->rfp_cfg) XFREE(MTYPE_RFAPI_RFP_GROUP_CFG, rfg->rfp_cfg); listnode_delete(bgp->rfapi_cfg->nve_groups_sequential, rfg); QOBJ_UNREG(rfg); XFREE(MTYPE_RFAPI_GROUP_CFG, rfg); /* * Attempt to reassign the orphaned nves to a new group. If * a NVE can not be reassigned, its rfd->rfg will remain NULL * and it will become a zombie until released by rfapi_close(). */ if (orphaned_nves) { struct rfapi_descriptor *rfd; for (ALL_LIST_ELEMENTS(orphaned_nves, node, nnode, rfd)) { /* * 1. rfapi_close() equivalent except: * a. don't free original descriptor * b. remember query list * c. remember advertised route list * 2. rfapi_open() equivalent except: * a. reuse original descriptor * 3. rfapi_register() on remembered advertised route * list * 4. rfapi_query on rememebred query list */ int rc; rc = rfapi_reopen(rfd, bgp); if (!rc) { list_delete_node(orphaned_nves, node); if (vty) vty_out(vty, "WARNING: reassigned NVE vn="); rfapiPrintRfapiIpAddr(vty, &rfd->vn_addr); if (vty) vty_out(vty, " un="); rfapiPrintRfapiIpAddr(vty, &rfd->un_addr); if (vty) vty_out(vty, " to new group \"%s\"\n", rfd->rfg->name); } } for (ALL_LIST_ELEMENTS_RO(orphaned_nves, node, rfd)) { if (vty) vty_out(vty, "WARNING: orphaned NVE vn="); rfapiPrintRfapiIpAddr(vty, &rfd->vn_addr); if (vty) vty_out(vty, " un="); rfapiPrintRfapiIpAddr(vty, &rfd->un_addr); if (vty) vty_out(vty, "\n"); } list_delete(&orphaned_nves); } } static int bgp_rfapi_delete_named_nve_group(struct vty *vty, /* NULL = no output */ struct bgp *bgp, const char *rfg_name, /* NULL = any */ rfapi_group_cfg_type_t type) /* _MAX = any */ { struct rfapi_nve_group_cfg *rfg = NULL; struct listnode *node, *nnode; struct rfapi_rfg_name *rfgn; /* Search for name */ if (rfg_name) { rfg = bgp_rfapi_cfg_match_byname(bgp, rfg_name, type); if (!rfg) { if (vty) vty_out(vty, "No NVE group named \"%s\"\n", rfg_name); return CMD_WARNING_CONFIG_FAILED; } } /* * If this group is the redist nve group, unlink it */ if (rfg_name == NULL || bgp->rfapi_cfg->rfg_redist == rfg) { vnc_redistribute_prechange(bgp); bgp->rfapi_cfg->rfg_redist = NULL; vnc_redistribute_postchange(bgp); } /* * remove reference from bgp direct export list */ for (ALL_LIST_ELEMENTS_RO(bgp->rfapi_cfg->rfg_export_direct_bgp_l, node, rfgn)) { if (rfgn->rfg == rfg) { rfgn->rfg = NULL; /* remove exported routes from this group */ vnc_direct_bgp_del_group(bgp, rfg); break; } } /* * remove reference from zebra export list */ for (ALL_LIST_ELEMENTS_RO(bgp->rfapi_cfg->rfg_export_zebra_l, node, rfgn)) { if (rfgn->rfg == rfg) { rfgn->rfg = NULL; /* remove exported routes from this group */ vnc_zebra_del_group(bgp, rfg); break; } } if (rfg) { if (rfg->rfd) clear_vnc_vrf_closer(rfg); bgp_rfapi_delete_nve_group(vty, bgp, rfg); } else /* must be delete all */ for (ALL_LIST_ELEMENTS(bgp->rfapi_cfg->nve_groups_sequential, node, nnode, rfg)) { if (rfg->rfd) clear_vnc_vrf_closer(rfg); bgp_rfapi_delete_nve_group(vty, bgp, rfg); } return CMD_SUCCESS; } DEFUN (vnc_no_nve_group, vnc_no_nve_group_cmd, "no vnc nve-group NAME", NO_STR VNC_CONFIG_STR "Configure a NVE group\n" "Group name\n") { VTY_DECLVAR_CONTEXT(bgp, bgp); return bgp_rfapi_delete_named_nve_group(vty, bgp, argv[3]->arg, RFAPI_GROUP_CFG_NVE); } DEFUN (vnc_nve_group_prefix, vnc_nve_group_prefix_cmd, "prefix ", "Specify prefixes matching NVE VN or UN interfaces\n" "VN prefix\n" "UN prefix\n" "IPv4 prefix\n" "IPv6 prefix\n") { VTY_DECLVAR_CONTEXT(bgp, bgp); VTY_DECLVAR_CONTEXT_SUB(rfapi_nve_group_cfg, rfg); struct prefix p; afi_t afi; struct agg_table *rt; struct agg_node *rn; int is_un_prefix = 0; /* make sure it's still in list */ if (!listnode_lookup(bgp->rfapi_cfg->nve_groups_sequential, rfg)) { /* Not in list anymore */ vty_out(vty, "Current NVE group no longer exists\n"); return CMD_WARNING_CONFIG_FAILED; } if (!str2prefix(argv[2]->arg, &p)) { vty_out(vty, "Malformed prefix \"%s\"\n", argv[2]->arg); return CMD_WARNING_CONFIG_FAILED; } afi = family2afi(p.family); if (!afi) { vty_out(vty, "Unsupported address family\n"); return CMD_WARNING_CONFIG_FAILED; } if (argv[1]->arg[0] == 'u') { rt = bgp->rfapi_cfg->nve_groups_un[afi]; is_un_prefix = 1; } else { rt = bgp->rfapi_cfg->nve_groups_vn[afi]; } rn = agg_node_get(rt, &p); /* NB locks node */ if (rn->info) { /* * There is already a group with this prefix */ agg_unlock_node(rn); if (rn->info != rfg) { /* * different group name: fail */ vty_out(vty, "nve group \"%s\" already has \"%s\" prefix %s\n", ((struct rfapi_nve_group_cfg *)(rn->info)) ->name, argv[1]->arg, argv[2]->arg); return CMD_WARNING_CONFIG_FAILED; } else { /* * same group name: it's already in the correct place * in the table, so we're done. * * Implies rfg->(vn|un)_prefix is already correct. */ return CMD_SUCCESS; } } if (bgp->rfapi_cfg->rfg_redist == rfg) { vnc_redistribute_prechange(bgp); } /* New prefix, new node */ if (is_un_prefix) { /* detach rfg from previous route table location */ if (rfg->un_node) { rfg->un_node->info = NULL; agg_unlock_node(rfg->un_node); /* frees */ } rfg->un_node = rn; /* back ref */ rfg->un_prefix = p; } else { /* detach rfg from previous route table location */ if (rfg->vn_node) { rfg->vn_node->info = NULL; agg_unlock_node(rfg->vn_node); /* frees */ } rfg->vn_node = rn; /* back ref */ rfg->vn_prefix = p; } /* attach */ rn->info = rfg; if (bgp->rfapi_cfg->rfg_redist == rfg) { vnc_redistribute_postchange(bgp); } return CMD_SUCCESS; } DEFUN (vnc_nve_group_rt_import, vnc_nve_group_rt_import_cmd, "rt import RTLIST...", "Specify route targets\n" "Import filter\n" "Space separated route target list (A.B.C.D:MN|EF:OPQR|GHJK:MN)\n") { VTY_DECLVAR_CONTEXT(bgp, bgp); VTY_DECLVAR_CONTEXT_SUB(rfapi_nve_group_cfg, rfg); int rc; struct listnode *node; struct rfapi_rfg_name *rfgn; int is_export_bgp = 0; int is_export_zebra = 0; /* make sure it's still in list */ if (!listnode_lookup(bgp->rfapi_cfg->nve_groups_sequential, rfg)) { /* Not in list anymore */ vty_out(vty, "Current NVE group no longer exists\n"); return CMD_WARNING_CONFIG_FAILED; } rc = set_ecom_list(vty, argc - 2, argv + 2, &rfg->rt_import_list); if (rc != CMD_SUCCESS) return rc; for (ALL_LIST_ELEMENTS_RO(bgp->rfapi_cfg->rfg_export_direct_bgp_l, node, rfgn)) { if (rfgn->rfg == rfg) { is_export_bgp = 1; break; } } if (is_export_bgp) vnc_direct_bgp_del_group(bgp, rfg); for (ALL_LIST_ELEMENTS_RO(bgp->rfapi_cfg->rfg_export_zebra_l, node, rfgn)) { if (rfgn->rfg == rfg) { is_export_zebra = 1; break; } } if (is_export_zebra) vnc_zebra_del_group(bgp, rfg); /* * stop referencing old import table, now reference new one */ if (rfg->rfapi_import_table) rfapiImportTableRefDelByIt(bgp, rfg->rfapi_import_table); rfg->rfapi_import_table = rfapiImportTableRefAdd(bgp, rfg->rt_import_list, rfg); if (is_export_bgp) vnc_direct_bgp_add_group(bgp, rfg); if (is_export_zebra) vnc_zebra_add_group(bgp, rfg); return CMD_SUCCESS; } DEFUN (vnc_nve_group_rt_export, vnc_nve_group_rt_export_cmd, "rt export RTLIST...", "Specify route targets\n" "Export filter\n" "Space separated route target list (A.B.C.D:MN|EF:OPQR|GHJK:MN)\n") { VTY_DECLVAR_CONTEXT(bgp, bgp); VTY_DECLVAR_CONTEXT_SUB(rfapi_nve_group_cfg, rfg); int rc; /* make sure it's still in list */ if (!listnode_lookup(bgp->rfapi_cfg->nve_groups_sequential, rfg)) { /* Not in list anymore */ vty_out(vty, "Current NVE group no longer exists\n"); return CMD_WARNING_CONFIG_FAILED; } if (bgp->rfapi_cfg->rfg_redist == rfg) { vnc_redistribute_prechange(bgp); } rc = set_ecom_list(vty, argc - 2, argv + 2, &rfg->rt_export_list); if (bgp->rfapi_cfg->rfg_redist == rfg) { vnc_redistribute_postchange(bgp); } return rc; } DEFUN (vnc_nve_group_rt_both, vnc_nve_group_rt_both_cmd, "rt both RTLIST...", "Specify route targets\n" "Export+import filters\n" "Space separated route target list (A.B.C.D:MN|EF:OPQR|GHJK:MN)\n") { VTY_DECLVAR_CONTEXT(bgp, bgp); VTY_DECLVAR_CONTEXT_SUB(rfapi_nve_group_cfg, rfg); int rc; int is_export_bgp = 0; int is_export_zebra = 0; struct listnode *node; struct rfapi_rfg_name *rfgn; /* make sure it's still in list */ if (!listnode_lookup(bgp->rfapi_cfg->nve_groups_sequential, rfg)) { /* Not in list anymore */ vty_out(vty, "Current NVE group no longer exists\n"); return CMD_WARNING_CONFIG_FAILED; } rc = set_ecom_list(vty, argc - 2, argv + 2, &rfg->rt_import_list); if (rc != CMD_SUCCESS) return rc; for (ALL_LIST_ELEMENTS_RO(bgp->rfapi_cfg->rfg_export_direct_bgp_l, node, rfgn)) { if (rfgn->rfg == rfg) { is_export_bgp = 1; break; } } if (is_export_bgp) vnc_direct_bgp_del_group(bgp, rfg); for (ALL_LIST_ELEMENTS_RO(bgp->rfapi_cfg->rfg_export_zebra_l, node, rfgn)) { if (rfgn->rfg == rfg) { is_export_zebra = 1; break; } } if (is_export_zebra) { vnc_zlog_debug_verbose("%s: is_export_zebra", __func__); vnc_zebra_del_group(bgp, rfg); } /* * stop referencing old import table, now reference new one */ if (rfg->rfapi_import_table) rfapiImportTableRefDelByIt(bgp, rfg->rfapi_import_table); rfg->rfapi_import_table = rfapiImportTableRefAdd(bgp, rfg->rt_import_list, rfg); if (is_export_bgp) vnc_direct_bgp_add_group(bgp, rfg); if (is_export_zebra) vnc_zebra_add_group(bgp, rfg); if (bgp->rfapi_cfg->rfg_redist == rfg) { vnc_redistribute_prechange(bgp); } rc = set_ecom_list(vty, argc - 2, argv + 2, &rfg->rt_export_list); if (bgp->rfapi_cfg->rfg_redist == rfg) { vnc_redistribute_postchange(bgp); } return rc; } DEFUN (vnc_nve_group_l2rd, vnc_nve_group_l2rd_cmd, "l2rd <(1-255)|auto-vn>", "Specify default Local Nve ID value to use in RD for L2 routes\n" "Fixed value 1-255\n" "use the low-order octet of the NVE's VN address\n") { VTY_DECLVAR_CONTEXT(bgp, bgp); VTY_DECLVAR_CONTEXT_SUB(rfapi_nve_group_cfg, rfg); /* make sure it's still in list */ if (!listnode_lookup(bgp->rfapi_cfg->nve_groups_sequential, rfg)) { /* Not in list anymore */ vty_out(vty, "Current NVE group no longer exists\n"); return CMD_WARNING_CONFIG_FAILED; } if (strmatch(argv[1]->text, "auto:vn")) { rfg->l2rd = 0; } else { char *end = NULL; unsigned long value_l = strtoul(argv[1]->arg, &end, 10); uint8_t value = value_l & 0xff; if (!argv[1]->arg[0] || *end) { vty_out(vty, "%% Malformed l2 nve ID \"%s\"\n", argv[1]->arg); return CMD_WARNING_CONFIG_FAILED; } if ((value_l < 1) || (value_l > 0xff)) { vty_out(vty, "%% Malformed l2 nve id (must be greater than 0 and less than %u\n", 0x100); return CMD_WARNING_CONFIG_FAILED; } rfg->l2rd = value; } rfg->flags |= RFAPI_RFG_L2RD; return CMD_SUCCESS; } DEFUN (vnc_nve_group_no_l2rd, vnc_nve_group_no_l2rd_cmd, "no l2rd", NO_STR "Specify default Local Nve ID value to use in RD for L2 routes\n") { VTY_DECLVAR_CONTEXT(bgp, bgp); VTY_DECLVAR_CONTEXT_SUB(rfapi_nve_group_cfg, rfg); /* make sure it's still in list */ if (!listnode_lookup(bgp->rfapi_cfg->nve_groups_sequential, rfg)) { /* Not in list anymore */ vty_out(vty, "Current NVE group no longer exists\n"); return CMD_WARNING_CONFIG_FAILED; } rfg->l2rd = 0; rfg->flags &= ~RFAPI_RFG_L2RD; return CMD_SUCCESS; } DEFUN (vnc_nve_group_rd, vnc_nve_group_rd_cmd, "rd ASN:NN_OR_IP-ADDRESS:NN", "Specify route distinguisher\n" "Route Distinguisher (: | : | auto:vn: )\n") { VTY_DECLVAR_CONTEXT(bgp, bgp); int ret; struct prefix_rd prd; VTY_DECLVAR_CONTEXT_SUB(rfapi_nve_group_cfg, rfg); /* make sure it's still in list */ if (!listnode_lookup(bgp->rfapi_cfg->nve_groups_sequential, rfg)) { /* Not in list anymore */ vty_out(vty, "Current NVE group no longer exists\n"); return CMD_WARNING_CONFIG_FAILED; } if (!strncmp(argv[1]->arg, "auto:vn:", 8)) { /* * use AF_UNIX to designate automatically-assigned RD * auto:vn:nn where nn is a 2-octet quantity */ char *end = NULL; uint32_t value32 = strtoul(argv[1]->arg + 8, &end, 10); uint16_t value = value32 & 0xffff; if (!argv[1]->arg[8] || *end) { vty_out(vty, "%% Malformed rd\n"); return CMD_WARNING_CONFIG_FAILED; } if (value32 > 0xffff) { vty_out(vty, "%% Malformed rd (must be less than %u\n", 0x0ffff); return CMD_WARNING_CONFIG_FAILED; } memset(&prd, 0, sizeof(prd)); prd.family = AF_UNIX; prd.prefixlen = 64; prd.val[0] = (RD_TYPE_IP >> 8) & 0x0ff; prd.val[1] = RD_TYPE_IP & 0x0ff; prd.val[6] = (value >> 8) & 0x0ff; prd.val[7] = value & 0x0ff; } else { ret = str2prefix_rd(argv[1]->arg, &prd); if (!ret) { vty_out(vty, "%% Malformed rd\n"); return CMD_WARNING_CONFIG_FAILED; } } if (bgp->rfapi_cfg->rfg_redist == rfg) { vnc_redistribute_prechange(bgp); } rfg->rd = prd; if (bgp->rfapi_cfg->rfg_redist == rfg) { vnc_redistribute_postchange(bgp); } return CMD_SUCCESS; } DEFUN (vnc_nve_group_responselifetime, vnc_nve_group_responselifetime_cmd, "response-lifetime ", "Specify response lifetime\n" "Response lifetime in seconds\n" "Infinite response lifetime\n") { VTY_DECLVAR_CONTEXT(bgp, bgp); unsigned int rspint; VTY_DECLVAR_CONTEXT_SUB(rfapi_nve_group_cfg, rfg); struct rfapi_descriptor *rfd; struct listnode *hdnode; /* make sure it's still in list */ if (!listnode_lookup(bgp->rfapi_cfg->nve_groups_sequential, rfg)) { /* Not in list anymore */ vty_out(vty, "Current NVE group no longer exists\n"); return CMD_WARNING_CONFIG_FAILED; } if (strmatch(argv[1]->text, "infinite")) { rspint = RFAPI_INFINITE_LIFETIME; } else { rspint = strtoul(argv[1]->arg, NULL, 10); } rfg->response_lifetime = rspint; rfg->flags |= RFAPI_RFG_RESPONSE_LIFETIME; if (rfg->nves) for (ALL_LIST_ELEMENTS_RO(rfg->nves, hdnode, rfd)) rfd->response_lifetime = rspint; return CMD_SUCCESS; } /* * Sigh. This command, like exit-address-family, is a hack to deal * with the lack of rigorous level control in the command handler. * TBD fix command handler. */ DEFUN_NOSH (exit_vnc, exit_vnc_cmd, "exit-vnc", "Exit VNC configuration mode\n") { if (vty->node == BGP_VNC_DEFAULTS_NODE || vty->node == BGP_VNC_NVE_GROUP_NODE || vty->node == BGP_VNC_L2_GROUP_NODE) { vty->node = BGP_NODE; } return CMD_SUCCESS; } static struct cmd_node bgp_vnc_defaults_node = { BGP_VNC_DEFAULTS_NODE, "%s(config-router-vnc-defaults)# ", 1}; static struct cmd_node bgp_vnc_nve_group_node = { BGP_VNC_NVE_GROUP_NODE, "%s(config-router-vnc-nve-group)# ", 1}; /*------------------------------------------------------------------------- * VNC nve-group * Note there are two types of NVEs, one for VPNs one for RFP NVEs *-----------------------------------------------------------------------*/ DEFUN_NOSH (vnc_vrf_policy, vnc_vrf_policy_cmd, "vrf-policy NAME", "Configure a VRF policy group\n" "VRF name\n") { struct rfapi_nve_group_cfg *rfg; VTY_DECLVAR_CONTEXT(bgp, bgp); if (bgp->inst_type == BGP_INSTANCE_TYPE_VRF) { vty_out(vty, "Can't configure vrf-policy within a BGP VRF instance\n"); return CMD_WARNING_CONFIG_FAILED; } /* Search for name */ rfg = bgp_rfapi_cfg_match_byname(bgp, argv[1]->arg, RFAPI_GROUP_CFG_VRF); if (!rfg) { rfg = rfapi_group_new(bgp, RFAPI_GROUP_CFG_VRF, argv[1]->arg); if (!rfg) { /* Error out of memory */ vty_out(vty, "Can't allocate memory for NVE group\n"); return CMD_WARNING_CONFIG_FAILED; } } /* * XXX subsequent calls will need to make sure this item is still * in the linked list and has the same name */ VTY_PUSH_CONTEXT_SUB(BGP_VRF_POLICY_NODE, rfg); return CMD_SUCCESS; } DEFUN (vnc_no_vrf_policy, vnc_no_vrf_policy_cmd, "no vrf-policy NAME", NO_STR "Remove a VRF policy group\n" "VRF name\n") { VTY_DECLVAR_CONTEXT(bgp, bgp); /* silently return */ if (bgp->inst_type == BGP_INSTANCE_TYPE_VRF) return CMD_SUCCESS; return bgp_rfapi_delete_named_nve_group(vty, bgp, argv[2]->arg, RFAPI_GROUP_CFG_VRF); } DEFUN (vnc_vrf_policy_label, vnc_vrf_policy_label_cmd, "label (0-1048575)", "Default label value for VRF\n" "Label Value <0-1048575>\n") { VTY_DECLVAR_CONTEXT_SUB(rfapi_nve_group_cfg, rfg); uint32_t label; VTY_DECLVAR_CONTEXT(bgp, bgp); /* make sure it's still in list */ if (!listnode_lookup(bgp->rfapi_cfg->nve_groups_sequential, rfg)) { /* Not in list anymore */ vty_out(vty, "Current NVE group no longer exists\n"); return CMD_WARNING_CONFIG_FAILED; } label = strtoul(argv[1]->arg, NULL, 10); if (bgp->rfapi_cfg->rfg_redist == rfg) { vnc_redistribute_prechange(bgp); } rfg->label = label; if (bgp->rfapi_cfg->rfg_redist == rfg) { vnc_redistribute_postchange(bgp); } return CMD_SUCCESS; } DEFUN (vnc_vrf_policy_no_label, vnc_vrf_policy_no_label_cmd, "no label", NO_STR "Remove VRF default label\n") { VTY_DECLVAR_CONTEXT_SUB(rfapi_nve_group_cfg, rfg); VTY_DECLVAR_CONTEXT(bgp, bgp); /* make sure it's still in list */ if (!listnode_lookup(bgp->rfapi_cfg->nve_groups_sequential, rfg)) { /* Not in list anymore */ vty_out(vty, "Current VRF group no longer exists\n"); return CMD_WARNING_CONFIG_FAILED; } if (bgp->rfapi_cfg->rfg_redist == rfg) { vnc_redistribute_prechange(bgp); } rfg->label = MPLS_LABEL_NONE; if (bgp->rfapi_cfg->rfg_redist == rfg) { vnc_redistribute_postchange(bgp); } return CMD_SUCCESS; } DEFUN (vnc_vrf_policy_nexthop, vnc_vrf_policy_nexthop_cmd, "nexthop ", "Specify next hop to use for VRF advertised prefixes\n" "IPv4 prefix\n" "IPv6 prefix\n" "Use configured router-id (default)\n") { VTY_DECLVAR_CONTEXT_SUB(rfapi_nve_group_cfg, rfg); struct prefix p; VTY_DECLVAR_CONTEXT(bgp, bgp); /* make sure it's still in list */ if (!listnode_lookup(bgp->rfapi_cfg->nve_groups_sequential, rfg)) { /* Not in list anymore */ vty_out(vty, "Current VRF no longer exists\n"); return CMD_WARNING_CONFIG_FAILED; } if (bgp->rfapi_cfg->rfg_redist == rfg) { vnc_redistribute_prechange(bgp); } if (!str2prefix(argv[1]->arg, &p) && p.family) { // vty_out (vty, "Nexthop set to self\n"); SET_FLAG(rfg->flags, RFAPI_RFG_VPN_NH_SELF); memset(&rfg->vn_prefix, 0, sizeof(struct prefix)); } else { UNSET_FLAG(rfg->flags, RFAPI_RFG_VPN_NH_SELF); rfg->vn_prefix = p; rfg->un_prefix = p; } /* TBD handle router-id/ nexthop changes when have advertised prefixes */ if (bgp->rfapi_cfg->rfg_redist == rfg) { vnc_redistribute_postchange(bgp); } return CMD_SUCCESS; } /* The RT code should be refactored/simplified with above... */ DEFUN (vnc_vrf_policy_rt_import, vnc_vrf_policy_rt_import_cmd, "rt import RTLIST...", "Specify route targets\n" "Import filter\n" "Space separated route target list (A.B.C.D:MN|EF:OPQR|GHJK:MN)\n") { VTY_DECLVAR_CONTEXT_SUB(rfapi_nve_group_cfg, rfg); VTY_DECLVAR_CONTEXT(bgp, bgp); int rc; struct listnode *node; struct rfapi_rfg_name *rfgn; int is_export_bgp = 0; int is_export_zebra = 0; /* make sure it's still in list */ if (!listnode_lookup(bgp->rfapi_cfg->nve_groups_sequential, rfg)) { /* Not in list anymore */ vty_out(vty, "Current NVE group no longer exists\n"); return CMD_WARNING_CONFIG_FAILED; } rc = set_ecom_list(vty, argc - 2, argv + 2, &rfg->rt_import_list); if (rc != CMD_SUCCESS) return rc; for (ALL_LIST_ELEMENTS_RO(bgp->rfapi_cfg->rfg_export_direct_bgp_l, node, rfgn)) { if (rfgn->rfg == rfg) { is_export_bgp = 1; break; } } if (is_export_bgp) vnc_direct_bgp_del_group(bgp, rfg); for (ALL_LIST_ELEMENTS_RO(bgp->rfapi_cfg->rfg_export_zebra_l, node, rfgn)) { if (rfgn->rfg == rfg) { is_export_zebra = 1; break; } } if (is_export_zebra) vnc_zebra_del_group(bgp, rfg); /* * stop referencing old import table, now reference new one */ if (rfg->rfapi_import_table) rfapiImportTableRefDelByIt(bgp, rfg->rfapi_import_table); rfg->rfapi_import_table = rfapiImportTableRefAdd(bgp, rfg->rt_import_list, rfg); if (is_export_bgp) vnc_direct_bgp_add_group(bgp, rfg); if (is_export_zebra) vnc_zebra_add_group(bgp, rfg); return CMD_SUCCESS; } DEFUN (vnc_vrf_policy_rt_export, vnc_vrf_policy_rt_export_cmd, "rt export RTLIST...", "Specify route targets\n" "Export filter\n" "Space separated route target list (A.B.C.D:MN|EF:OPQR|GHJK:MN)\n") { VTY_DECLVAR_CONTEXT_SUB(rfapi_nve_group_cfg, rfg); VTY_DECLVAR_CONTEXT(bgp, bgp); int rc; /* make sure it's still in list */ if (!listnode_lookup(bgp->rfapi_cfg->nve_groups_sequential, rfg)) { /* Not in list anymore */ vty_out(vty, "Current NVE group no longer exists\n"); return CMD_WARNING_CONFIG_FAILED; } if (bgp->rfapi_cfg->rfg_redist == rfg) { vnc_redistribute_prechange(bgp); } rc = set_ecom_list(vty, argc - 2, argv + 2, &rfg->rt_export_list); if (bgp->rfapi_cfg->rfg_redist == rfg) { vnc_redistribute_postchange(bgp); } return rc; } DEFUN (vnc_vrf_policy_rt_both, vnc_vrf_policy_rt_both_cmd, "rt both RTLIST...", "Specify route targets\n" "Export+import filters\n" "Space separated route target list (A.B.C.D:MN|EF:OPQR|GHJK:MN)\n") { VTY_DECLVAR_CONTEXT_SUB(rfapi_nve_group_cfg, rfg); VTY_DECLVAR_CONTEXT(bgp, bgp); int rc; int is_export_bgp = 0; int is_export_zebra = 0; struct listnode *node; struct rfapi_rfg_name *rfgn; /* make sure it's still in list */ if (!listnode_lookup(bgp->rfapi_cfg->nve_groups_sequential, rfg)) { /* Not in list anymore */ vty_out(vty, "Current NVE group no longer exists\n"); return CMD_WARNING_CONFIG_FAILED; } rc = set_ecom_list(vty, argc - 2, argv + 2, &rfg->rt_import_list); if (rc != CMD_SUCCESS) return rc; for (ALL_LIST_ELEMENTS_RO(bgp->rfapi_cfg->rfg_export_direct_bgp_l, node, rfgn)) { if (rfgn->rfg == rfg) { is_export_bgp = 1; break; } } if (is_export_bgp) vnc_direct_bgp_del_group(bgp, rfg); for (ALL_LIST_ELEMENTS_RO(bgp->rfapi_cfg->rfg_export_zebra_l, node, rfgn)) { if (rfgn->rfg == rfg) { is_export_zebra = 1; break; } } if (is_export_zebra) { vnc_zlog_debug_verbose("%s: is_export_zebra", __func__); vnc_zebra_del_group(bgp, rfg); } /* * stop referencing old import table, now reference new one */ if (rfg->rfapi_import_table) rfapiImportTableRefDelByIt(bgp, rfg->rfapi_import_table); rfg->rfapi_import_table = rfapiImportTableRefAdd(bgp, rfg->rt_import_list, rfg); if (is_export_bgp) vnc_direct_bgp_add_group(bgp, rfg); if (is_export_zebra) vnc_zebra_add_group(bgp, rfg); if (bgp->rfapi_cfg->rfg_redist == rfg) { vnc_redistribute_prechange(bgp); } rc = set_ecom_list(vty, argc - 2, argv + 2, &rfg->rt_export_list); if (bgp->rfapi_cfg->rfg_redist == rfg) { vnc_redistribute_postchange(bgp); } return rc; } DEFUN (vnc_vrf_policy_rd, vnc_vrf_policy_rd_cmd, "rd ASN:NN_OR_IP-ADDRESS:NN", "Specify default VRF route distinguisher\n" "Route Distinguisher (: | : | auto:nh: )\n") { int ret; struct prefix_rd prd; VTY_DECLVAR_CONTEXT_SUB(rfapi_nve_group_cfg, rfg); VTY_DECLVAR_CONTEXT(bgp, bgp); /* make sure it's still in list */ if (!listnode_lookup(bgp->rfapi_cfg->nve_groups_sequential, rfg)) { /* Not in list anymore */ vty_out(vty, "Current NVE group no longer exists\n"); return CMD_WARNING_CONFIG_FAILED; } if (!strncmp(argv[1]->arg, "auto:nh:", 8)) { /* * use AF_UNIX to designate automatically-assigned RD * auto:vn:nn where nn is a 2-octet quantity */ char *end = NULL; uint32_t value32 = strtoul(argv[1]->arg + 8, &end, 10); uint16_t value = value32 & 0xffff; if (!*(argv[1]->arg + 5) || *end) { vty_out(vty, "%% Malformed rd\n"); return CMD_WARNING_CONFIG_FAILED; } if (value32 > 0xffff) { vty_out(vty, "%% Malformed rd (must be less than %u\n", 0x0ffff); return CMD_WARNING_CONFIG_FAILED; } memset(&prd, 0, sizeof(prd)); prd.family = AF_UNIX; prd.prefixlen = 64; prd.val[0] = (RD_TYPE_IP >> 8) & 0x0ff; prd.val[1] = RD_TYPE_IP & 0x0ff; prd.val[6] = (value >> 8) & 0x0ff; prd.val[7] = value & 0x0ff; } else { ret = str2prefix_rd(argv[1]->arg, &prd); if (!ret) { vty_out(vty, "%% Malformed rd\n"); return CMD_WARNING_CONFIG_FAILED; } } if (bgp->rfapi_cfg->rfg_redist == rfg) { vnc_redistribute_prechange(bgp); } rfg->rd = prd; if (bgp->rfapi_cfg->rfg_redist == rfg) { vnc_redistribute_postchange(bgp); } return CMD_SUCCESS; } DEFUN_NOSH (exit_vrf_policy, exit_vrf_policy_cmd, "exit-vrf-policy", "Exit VRF policy configuration mode\n") { if (vty->node == BGP_VRF_POLICY_NODE) { vty->node = BGP_NODE; } return CMD_SUCCESS; } static struct cmd_node bgp_vrf_policy_node = { BGP_VRF_POLICY_NODE, "%s(config-router-vrf-policy)# ", 1}; /*------------------------------------------------------------------------- * vnc-l2-group *-----------------------------------------------------------------------*/ DEFUN_NOSH (vnc_l2_group, vnc_l2_group_cmd, "vnc l2-group NAME", VNC_CONFIG_STR "Configure a L2 group\n" "Group name\n") { struct rfapi_l2_group_cfg *rfg; VTY_DECLVAR_CONTEXT(bgp, bgp); VNC_VTY_CONFIG_CHECK(bgp); /* Search for name */ rfg = rfapi_l2_group_lookup_byname(bgp, argv[2]->arg); if (!rfg) { rfg = rfapi_l2_group_new(); if (!rfg) { /* Error out of memory */ vty_out(vty, "Can't allocate memory for L2 group\n"); return CMD_WARNING_CONFIG_FAILED; } rfg->name = strdup(argv[2]->arg); /* add to tail of list */ listnode_add(bgp->rfapi_cfg->l2_groups, rfg); } /* * XXX subsequent calls will need to make sure this item is still * in the linked list and has the same name */ VTY_PUSH_CONTEXT_SUB(BGP_VNC_L2_GROUP_NODE, rfg); return CMD_SUCCESS; } static void bgp_rfapi_delete_l2_group(struct vty *vty, /* NULL = no output */ struct bgp *bgp, struct rfapi_l2_group_cfg *rfg) { /* delete it */ free(rfg->name); if (rfg->rt_import_list) ecommunity_free(&rfg->rt_import_list); if (rfg->rt_export_list) ecommunity_free(&rfg->rt_export_list); if (rfg->labels) list_delete(&rfg->labels); XFREE(MTYPE_RFAPI_RFP_GROUP_CFG, rfg->rfp_cfg); listnode_delete(bgp->rfapi_cfg->l2_groups, rfg); rfapi_l2_group_del(rfg); } static int bgp_rfapi_delete_named_l2_group(struct vty *vty, /* NULL = no output */ struct bgp *bgp, const char *rfg_name) /* NULL = any */ { struct rfapi_l2_group_cfg *rfg = NULL; struct listnode *node, *nnode; /* Search for name */ if (rfg_name) { rfg = rfapi_l2_group_lookup_byname(bgp, rfg_name); if (!rfg) { if (vty) vty_out(vty, "No L2 group named \"%s\"\n", rfg_name); return CMD_WARNING_CONFIG_FAILED; } } if (rfg) bgp_rfapi_delete_l2_group(vty, bgp, rfg); else /* must be delete all */ for (ALL_LIST_ELEMENTS(bgp->rfapi_cfg->l2_groups, node, nnode, rfg)) bgp_rfapi_delete_l2_group(vty, bgp, rfg); return CMD_SUCCESS; } DEFUN (vnc_no_l2_group, vnc_no_l2_group_cmd, "no vnc l2-group NAME", NO_STR VNC_CONFIG_STR "Configure a L2 group\n" "Group name\n") { VTY_DECLVAR_CONTEXT(bgp, bgp); return bgp_rfapi_delete_named_l2_group(vty, bgp, argv[3]->arg); } DEFUN (vnc_l2_group_lni, vnc_l2_group_lni_cmd, "logical-network-id (0-4294967295)", "Specify Logical Network ID associated with group\n" "value\n") { VTY_DECLVAR_CONTEXT_SUB(rfapi_l2_group_cfg, rfg); VTY_DECLVAR_CONTEXT(bgp, bgp); /* make sure it's still in list */ if (!listnode_lookup(bgp->rfapi_cfg->l2_groups, rfg)) { /* Not in list anymore */ vty_out(vty, "Current L2 group no longer exists\n"); return CMD_WARNING_CONFIG_FAILED; } rfg->logical_net_id = strtoul(argv[1]->arg, NULL, 10); return CMD_SUCCESS; } DEFUN (vnc_l2_group_labels, vnc_l2_group_labels_cmd, "labels (0-1048575)...", "Specify label values associated with group\n" "Space separated list of label values <0-1048575>\n") { VTY_DECLVAR_CONTEXT_SUB(rfapi_l2_group_cfg, rfg); VTY_DECLVAR_CONTEXT(bgp, bgp); struct list *ll; /* make sure it's still in list */ if (!listnode_lookup(bgp->rfapi_cfg->l2_groups, rfg)) { /* Not in list anymore */ vty_out(vty, "Current L2 group no longer exists\n"); return CMD_WARNING_CONFIG_FAILED; } ll = rfg->labels; if (ll == NULL) { ll = list_new(); rfg->labels = ll; } argc--; argv++; for (; argc; --argc, ++argv) { uint32_t label; label = strtoul(argv[0]->arg, NULL, 10); if (!listnode_lookup(ll, (void *)(uintptr_t)label)) listnode_add(ll, (void *)(uintptr_t)label); } return CMD_SUCCESS; } DEFUN (vnc_l2_group_no_labels, vnc_l2_group_no_labels_cmd, "no labels (0-1048575)...", NO_STR "Specify label values associated with L2 group\n" "Space separated list of label values <0-1048575>\n") { VTY_DECLVAR_CONTEXT_SUB(rfapi_l2_group_cfg, rfg); VTY_DECLVAR_CONTEXT(bgp, bgp); struct list *ll; /* make sure it's still in list */ if (!listnode_lookup(bgp->rfapi_cfg->l2_groups, rfg)) { /* Not in list anymore */ vty_out(vty, "Current L2 group no longer exists\n"); return CMD_WARNING_CONFIG_FAILED; } ll = rfg->labels; if (ll == NULL) { vty_out(vty, "Label no longer associated with group\n"); return CMD_WARNING_CONFIG_FAILED; } argc -= 2; argv += 2; for (; argc; --argc, ++argv) { uint32_t label; label = strtoul(argv[0]->arg, NULL, 10); listnode_delete(ll, (void *)(uintptr_t)label); } return CMD_SUCCESS; } DEFUN (vnc_l2_group_rt, vnc_l2_group_rt_cmd, "rt ASN:NN_OR_IP-ADDRESS:NN", "Specify route targets\n" "Export+import filters\n" "Export filters\n" "Import filters\n" "A route target\n") { VTY_DECLVAR_CONTEXT_SUB(rfapi_l2_group_cfg, rfg); VTY_DECLVAR_CONTEXT(bgp, bgp); int rc = CMD_SUCCESS; int do_import = 0; int do_export = 0; switch (argv[1]->arg[0]) { case 'b': do_export = 1; /* fall through */ case 'i': do_import = 1; break; case 'e': do_export = 1; break; default: vty_out(vty, "Unknown option, %s\n", argv[1]->arg); return CMD_ERR_NO_MATCH; } /* make sure it's still in list */ if (!listnode_lookup(bgp->rfapi_cfg->l2_groups, rfg)) { /* Not in list anymore */ vty_out(vty, "Current L2 group no longer exists\n"); return CMD_WARNING_CONFIG_FAILED; } if (do_import) rc = set_ecom_list(vty, argc - 2, argv + 2, &rfg->rt_import_list); if (rc == CMD_SUCCESS && do_export) rc = set_ecom_list(vty, argc - 2, argv + 2, &rfg->rt_export_list); return rc; } static struct cmd_node bgp_vnc_l2_group_node = { BGP_VNC_L2_GROUP_NODE, "%s(config-router-vnc-l2-group)# ", 1}; struct rfapi_l2_group_cfg * bgp_rfapi_get_group_by_lni_label(struct bgp *bgp, uint32_t logical_net_id, uint32_t label) { struct rfapi_l2_group_cfg *rfg; struct listnode *node; if (bgp->rfapi_cfg->l2_groups == NULL) /* not the best place for this */ return NULL; label = label & 0xfffff; /* label is 20 bits! */ for (ALL_LIST_ELEMENTS_RO(bgp->rfapi_cfg->l2_groups, node, rfg)) { if (rfg->logical_net_id == logical_net_id) { struct listnode *lnode; void *data; for (ALL_LIST_ELEMENTS_RO(rfg->labels, lnode, data)) if (((uint32_t)((uintptr_t)data)) == label) { /* match! */ return rfg; } } } return NULL; } struct list *bgp_rfapi_get_labellist_by_lni_label(struct bgp *bgp, uint32_t logical_net_id, uint32_t label) { struct rfapi_l2_group_cfg *rfg; rfg = bgp_rfapi_get_group_by_lni_label(bgp, logical_net_id, label); if (rfg) { return rfg->labels; } return NULL; } struct ecommunity * bgp_rfapi_get_ecommunity_by_lni_label(struct bgp *bgp, uint32_t is_import, uint32_t logical_net_id, uint32_t label) { struct rfapi_l2_group_cfg *rfg; rfg = bgp_rfapi_get_group_by_lni_label(bgp, logical_net_id, label); if (rfg) { if (is_import) return rfg->rt_import_list; else return rfg->rt_export_list; } return NULL; } void bgp_rfapi_cfg_init(void) { install_node(&bgp_vnc_defaults_node, NULL); install_node(&bgp_vnc_nve_group_node, NULL); install_node(&bgp_vrf_policy_node, NULL); install_node(&bgp_vnc_l2_group_node, NULL); install_default(BGP_VRF_POLICY_NODE); install_default(BGP_VNC_DEFAULTS_NODE); install_default(BGP_VNC_NVE_GROUP_NODE); install_default(BGP_VNC_L2_GROUP_NODE); /* * Add commands */ install_element(BGP_NODE, &vnc_defaults_cmd); install_element(BGP_NODE, &vnc_nve_group_cmd); install_element(BGP_NODE, &vnc_no_nve_group_cmd); install_element(BGP_NODE, &vnc_vrf_policy_cmd); install_element(BGP_NODE, &vnc_no_vrf_policy_cmd); install_element(BGP_NODE, &vnc_l2_group_cmd); install_element(BGP_NODE, &vnc_no_l2_group_cmd); install_element(BGP_NODE, &vnc_advertise_un_method_cmd); install_element(BGP_NODE, &vnc_export_mode_cmd); install_element(BGP_VNC_DEFAULTS_NODE, &vnc_defaults_rt_import_cmd); install_element(BGP_VNC_DEFAULTS_NODE, &vnc_defaults_rt_export_cmd); install_element(BGP_VNC_DEFAULTS_NODE, &vnc_defaults_rt_both_cmd); install_element(BGP_VNC_DEFAULTS_NODE, &vnc_defaults_rd_cmd); install_element(BGP_VNC_DEFAULTS_NODE, &vnc_defaults_l2rd_cmd); install_element(BGP_VNC_DEFAULTS_NODE, &vnc_defaults_no_l2rd_cmd); install_element(BGP_VNC_DEFAULTS_NODE, &vnc_defaults_responselifetime_cmd); install_element(BGP_VNC_DEFAULTS_NODE, &exit_vnc_cmd); install_element(BGP_NODE, &vnc_redistribute_protocol_cmd); install_element(BGP_NODE, &vnc_no_redistribute_protocol_cmd); install_element(BGP_NODE, &vnc_redistribute_nvegroup_cmd); install_element(BGP_NODE, &vnc_redistribute_no_nvegroup_cmd); install_element(BGP_NODE, &vnc_redistribute_lifetime_cmd); install_element(BGP_NODE, &vnc_redistribute_rh_roo_localadmin_cmd); install_element(BGP_NODE, &vnc_redistribute_mode_cmd); install_element(BGP_NODE, &vnc_redistribute_bgp_exterior_cmd); install_element(BGP_NODE, &vnc_redist_bgpdirect_no_prefixlist_cmd); install_element(BGP_NODE, &vnc_redist_bgpdirect_prefixlist_cmd); install_element(BGP_NODE, &vnc_redist_bgpdirect_no_routemap_cmd); install_element(BGP_NODE, &vnc_redist_bgpdirect_routemap_cmd); install_element(BGP_VNC_NVE_GROUP_NODE, &vnc_nve_group_redist_bgpdirect_no_prefixlist_cmd); install_element(BGP_VNC_NVE_GROUP_NODE, &vnc_nve_group_redist_bgpdirect_prefixlist_cmd); install_element(BGP_VNC_NVE_GROUP_NODE, &vnc_nve_group_redist_bgpdirect_no_routemap_cmd); install_element(BGP_VNC_NVE_GROUP_NODE, &vnc_nve_group_redist_bgpdirect_routemap_cmd); install_element(BGP_NODE, &vnc_export_nvegroup_cmd); install_element(BGP_NODE, &vnc_no_export_nvegroup_cmd); install_element(BGP_NODE, &vnc_nve_export_prefixlist_cmd); install_element(BGP_NODE, &vnc_nve_export_routemap_cmd); install_element(BGP_NODE, &vnc_nve_export_no_prefixlist_cmd); install_element(BGP_NODE, &vnc_nve_export_no_routemap_cmd); install_element(BGP_VNC_NVE_GROUP_NODE, &vnc_nve_group_l2rd_cmd); install_element(BGP_VNC_NVE_GROUP_NODE, &vnc_nve_group_no_l2rd_cmd); install_element(BGP_VNC_NVE_GROUP_NODE, &vnc_nve_group_prefix_cmd); install_element(BGP_VNC_NVE_GROUP_NODE, &vnc_nve_group_rt_import_cmd); install_element(BGP_VNC_NVE_GROUP_NODE, &vnc_nve_group_rt_export_cmd); install_element(BGP_VNC_NVE_GROUP_NODE, &vnc_nve_group_rt_both_cmd); install_element(BGP_VNC_NVE_GROUP_NODE, &vnc_nve_group_rd_cmd); install_element(BGP_VNC_NVE_GROUP_NODE, &vnc_nve_group_responselifetime_cmd); install_element(BGP_VNC_NVE_GROUP_NODE, &vnc_nve_group_export_prefixlist_cmd); install_element(BGP_VNC_NVE_GROUP_NODE, &vnc_nve_group_export_routemap_cmd); install_element(BGP_VNC_NVE_GROUP_NODE, &vnc_nve_group_export_no_prefixlist_cmd); install_element(BGP_VNC_NVE_GROUP_NODE, &vnc_nve_group_export_no_routemap_cmd); install_element(BGP_VNC_NVE_GROUP_NODE, &exit_vnc_cmd); install_element(BGP_VRF_POLICY_NODE, &vnc_vrf_policy_label_cmd); install_element(BGP_VRF_POLICY_NODE, &vnc_vrf_policy_no_label_cmd); // Reenable to support VRF controller use case and testing install_element(BGP_VRF_POLICY_NODE, &vnc_vrf_policy_nexthop_cmd); install_element(BGP_VRF_POLICY_NODE, &vnc_vrf_policy_rt_import_cmd); install_element(BGP_VRF_POLICY_NODE, &vnc_vrf_policy_rt_export_cmd); install_element(BGP_VRF_POLICY_NODE, &vnc_vrf_policy_rt_both_cmd); install_element(BGP_VRF_POLICY_NODE, &vnc_vrf_policy_rd_cmd); install_element(BGP_VRF_POLICY_NODE, &vnc_vrf_policy_export_prefixlist_cmd); install_element(BGP_VRF_POLICY_NODE, &vnc_vrf_policy_export_routemap_cmd); install_element(BGP_VRF_POLICY_NODE, &vnc_vrf_policy_export_no_prefixlist_cmd); install_element(BGP_VRF_POLICY_NODE, &vnc_vrf_policy_export_no_routemap_cmd); install_element(BGP_VRF_POLICY_NODE, &exit_vrf_policy_cmd); install_element(BGP_VNC_L2_GROUP_NODE, &vnc_l2_group_lni_cmd); install_element(BGP_VNC_L2_GROUP_NODE, &vnc_l2_group_labels_cmd); install_element(BGP_VNC_L2_GROUP_NODE, &vnc_l2_group_no_labels_cmd); install_element(BGP_VNC_L2_GROUP_NODE, &vnc_l2_group_rt_cmd); install_element(BGP_VNC_L2_GROUP_NODE, &exit_vnc_cmd); } struct rfapi_cfg *bgp_rfapi_cfg_new(struct rfapi_rfp_cfg *cfg) { struct rfapi_cfg *h; afi_t afi; h = XCALLOC(MTYPE_RFAPI_CFG, sizeof(struct rfapi_cfg)); assert(h); h->nve_groups_sequential = list_new(); assert(h->nve_groups_sequential); for (afi = AFI_IP; afi < AFI_MAX; afi++) { h->nve_groups_vn[afi] = agg_table_init(); h->nve_groups_un[afi] = agg_table_init(); } h->default_response_lifetime = BGP_VNC_DEFAULT_RESPONSE_LIFETIME_DEFAULT; h->rfg_export_direct_bgp_l = list_new(); h->rfg_export_zebra_l = list_new(); h->resolve_nve_roo_local_admin = BGP_VNC_CONFIG_RESOLVE_NVE_ROO_LOCAL_ADMIN_DEFAULT; SET_FLAG(h->flags, BGP_VNC_CONFIG_FLAGS_DEFAULT); if (cfg == NULL) { h->rfp_cfg.download_type = RFAPI_RFP_DOWNLOAD_PARTIAL; h->rfp_cfg.ftd_advertisement_interval = RFAPI_RFP_CFG_DEFAULT_FTD_ADVERTISEMENT_INTERVAL; h->rfp_cfg.holddown_factor = RFAPI_RFP_CFG_DEFAULT_HOLDDOWN_FACTOR; h->rfp_cfg.use_updated_response = 0; h->rfp_cfg.use_removes = 0; } else { h->rfp_cfg.download_type = cfg->download_type; h->rfp_cfg.ftd_advertisement_interval = cfg->ftd_advertisement_interval; h->rfp_cfg.holddown_factor = cfg->holddown_factor; h->rfp_cfg.use_updated_response = cfg->use_updated_response; h->rfp_cfg.use_removes = cfg->use_removes; if (cfg->use_updated_response) h->flags &= ~BGP_VNC_CONFIG_CALLBACK_DISABLE; else h->flags |= BGP_VNC_CONFIG_CALLBACK_DISABLE; if (cfg->use_removes) h->flags &= ~BGP_VNC_CONFIG_RESPONSE_REMOVAL_DISABLE; else h->flags |= BGP_VNC_CONFIG_RESPONSE_REMOVAL_DISABLE; } return h; } void bgp_rfapi_cfg_destroy(struct bgp *bgp, struct rfapi_cfg *h) { afi_t afi; if (h == NULL) return; bgp_rfapi_delete_named_nve_group(NULL, bgp, NULL, RFAPI_GROUP_CFG_MAX); bgp_rfapi_delete_named_l2_group(NULL, bgp, NULL); if (h->l2_groups != NULL) list_delete(&h->l2_groups); list_delete(&h->nve_groups_sequential); list_delete(&h->rfg_export_direct_bgp_l); list_delete(&h->rfg_export_zebra_l); if (h->default_rt_export_list) ecommunity_free(&h->default_rt_export_list); if (h->default_rt_import_list) ecommunity_free(&h->default_rt_import_list); XFREE(MTYPE_RFAPI_RFP_GROUP_CFG, h->default_rfp_cfg); for (afi = AFI_IP; afi < AFI_MAX; afi++) { agg_table_finish(h->nve_groups_vn[afi]); agg_table_finish(h->nve_groups_un[afi]); } XFREE(MTYPE_RFAPI_CFG, h); } int bgp_rfapi_cfg_write(struct vty *vty, struct bgp *bgp) { struct listnode *node, *nnode; struct rfapi_nve_group_cfg *rfg; struct rfapi_cfg *hc = bgp->rfapi_cfg; struct rfapi_rfg_name *rfgn; int write = 0; afi_t afi; int type; if (bgp->rfapi == NULL || hc == NULL) return write; vty_out(vty, "!\n"); for (ALL_LIST_ELEMENTS(hc->nve_groups_sequential, node, nnode, rfg)) if (rfg->type == RFAPI_GROUP_CFG_VRF) { ++write; vty_out(vty, " vrf-policy %s\n", rfg->name); if (rfg->label <= MPLS_LABEL_MAX) { vty_out(vty, " label %u\n", rfg->label); } if (CHECK_FLAG(rfg->flags, RFAPI_RFG_VPN_NH_SELF)) { vty_out(vty, " nexthop self\n"); } else { if (rfg->vn_prefix.family) { char buf[BUFSIZ]; buf[0] = buf[BUFSIZ - 1] = 0; inet_ntop(rfg->vn_prefix.family, &rfg->vn_prefix.u.prefix, buf, sizeof(buf)); if (!buf[0] || buf[BUFSIZ - 1]) { // vty_out (vty, "nexthop // self\n"); } else { vty_out(vty, " nexthop %s\n", buf); } } } if (rfg->rd.prefixlen) { char buf[RD_ADDRSTRLEN]; if (AF_UNIX == rfg->rd.family) { uint16_t value = 0; value = ((rfg->rd.val[6] << 8) & 0x0ff00) | (rfg->rd.val[7] & 0x0ff); vty_out(vty, " rd auto:nh:%d\n", value); } else vty_out(vty, " rd %s\n", prefix_rd2str(&rfg->rd, buf, sizeof(buf))); } if (rfg->rt_import_list && rfg->rt_export_list && ecommunity_cmp(rfg->rt_import_list, rfg->rt_export_list)) { char *b = ecommunity_ecom2str( rfg->rt_import_list, ECOMMUNITY_FORMAT_ROUTE_MAP, ECOMMUNITY_ROUTE_TARGET); vty_out(vty, " rt both %s\n", b); XFREE(MTYPE_ECOMMUNITY_STR, b); } else { if (rfg->rt_import_list) { char *b = ecommunity_ecom2str( rfg->rt_import_list, ECOMMUNITY_FORMAT_ROUTE_MAP, ECOMMUNITY_ROUTE_TARGET); vty_out(vty, " rt import %s\n", b); XFREE(MTYPE_ECOMMUNITY_STR, b); } if (rfg->rt_export_list) { char *b = ecommunity_ecom2str( rfg->rt_export_list, ECOMMUNITY_FORMAT_ROUTE_MAP, ECOMMUNITY_ROUTE_TARGET); vty_out(vty, " rt export %s\n", b); XFREE(MTYPE_ECOMMUNITY_STR, b); } } /* * route filtering: prefix-lists and route-maps */ for (afi = AFI_IP; afi < AFI_MAX; ++afi) { const char *afistr = (afi == AFI_IP) ? "ipv4" : "ipv6"; if (rfg->plist_export_bgp_name[afi]) { vty_out(vty, " export %s%s prefix-list %s\n", (rfg->type == RFAPI_GROUP_CFG_VRF ? "" : "bgp "), afistr, rfg->plist_export_bgp_name [afi]); } if (rfg->plist_export_zebra_name[afi]) { vty_out(vty, " export %s%s prefix-list %s\n", (rfg->type == RFAPI_GROUP_CFG_VRF ? "" : "zebra "), afistr, rfg->plist_export_zebra_name [afi]); } /* * currently we only support redist plists for * bgp-direct. * If we later add plist support for * redistributing other * protocols, we'll need to loop over protocols * here */ if (rfg->plist_redist_name [ZEBRA_ROUTE_BGP_DIRECT][afi]) { vty_out(vty, " redistribute bgp-direct %s prefix-list %s\n", afistr, rfg->plist_redist_name [ZEBRA_ROUTE_BGP_DIRECT] [afi]); } if (rfg->plist_redist_name [ZEBRA_ROUTE_BGP_DIRECT_EXT][afi]) { vty_out(vty, " redistribute bgp-direct-to-nve-groups %s prefix-list %s\n", afistr, rfg->plist_redist_name [ZEBRA_ROUTE_BGP_DIRECT_EXT] [afi]); } } if (rfg->routemap_export_bgp_name) { vty_out(vty, " export %sroute-map %s\n", (rfg->type == RFAPI_GROUP_CFG_VRF ? "" : "bgp "), rfg->routemap_export_bgp_name); } if (rfg->routemap_export_zebra_name) { vty_out(vty, " export %sroute-map %s\n", (rfg->type == RFAPI_GROUP_CFG_VRF ? "" : "zebra "), rfg->routemap_export_zebra_name); } if (rfg->routemap_redist_name[ZEBRA_ROUTE_BGP_DIRECT]) { vty_out(vty, " redistribute bgp-direct route-map %s\n", rfg->routemap_redist_name [ZEBRA_ROUTE_BGP_DIRECT]); } if (rfg->routemap_redist_name [ZEBRA_ROUTE_BGP_DIRECT_EXT]) { vty_out(vty, " redistribute bgp-direct-to-nve-groups route-map %s\n", rfg->routemap_redist_name [ZEBRA_ROUTE_BGP_DIRECT_EXT]); } vty_out(vty, " exit-vrf-policy\n"); vty_out(vty, "!\n"); } if (hc->flags & BGP_VNC_CONFIG_ADV_UN_METHOD_ENCAP) { vty_out(vty, " vnc advertise-un-method encap-safi\n"); write++; } { /* was based on listen ports */ /* for now allow both old and new */ if (bgp->rfapi->rfp_methods.cfg_cb) write += (bgp->rfapi->rfp_methods.cfg_cb)( vty, bgp->rfapi->rfp); if (write) vty_out(vty, "!\n"); if (hc->l2_groups) { struct rfapi_l2_group_cfg *rfgc = NULL; struct listnode *gnode; for (ALL_LIST_ELEMENTS_RO(hc->l2_groups, gnode, rfgc)) { struct listnode *lnode; void *data; ++write; vty_out(vty, " vnc l2-group %s\n", rfgc->name); if (rfgc->logical_net_id != 0) vty_out(vty, " logical-network-id %u\n", rfgc->logical_net_id); if (rfgc->labels != NULL && listhead(rfgc->labels) != NULL) { vty_out(vty, " labels "); for (ALL_LIST_ELEMENTS_RO(rfgc->labels, lnode, data)) { vty_out(vty, "%hu ", (uint16_t)( (uintptr_t) data)); } vty_out(vty, "\n"); } if (rfgc->rt_import_list && rfgc->rt_export_list && ecommunity_cmp(rfgc->rt_import_list, rfgc->rt_export_list)) { char *b = ecommunity_ecom2str( rfgc->rt_import_list, ECOMMUNITY_FORMAT_ROUTE_MAP, ECOMMUNITY_ROUTE_TARGET); vty_out(vty, " rt both %s\n", b); XFREE(MTYPE_ECOMMUNITY_STR, b); } else { if (rfgc->rt_import_list) { char *b = ecommunity_ecom2str( rfgc->rt_import_list, ECOMMUNITY_FORMAT_ROUTE_MAP, ECOMMUNITY_ROUTE_TARGET); vty_out(vty, " rt import %s\n", b); XFREE(MTYPE_ECOMMUNITY_STR, b); } if (rfgc->rt_export_list) { char *b = ecommunity_ecom2str( rfgc->rt_export_list, ECOMMUNITY_FORMAT_ROUTE_MAP, ECOMMUNITY_ROUTE_TARGET); vty_out(vty, " rt export %s\n", b); XFREE(MTYPE_ECOMMUNITY_STR, b); } } if (bgp->rfapi->rfp_methods.cfg_group_cb) write += (bgp->rfapi->rfp_methods .cfg_group_cb)( vty, bgp->rfapi->rfp, RFAPI_RFP_CFG_GROUP_L2, rfgc->name, rfgc->rfp_cfg); vty_out(vty, " exit-vnc\n"); vty_out(vty, "!\n"); } } if (hc->default_rd.prefixlen || hc->default_response_lifetime != BGP_VNC_DEFAULT_RESPONSE_LIFETIME_DEFAULT || hc->default_rt_import_list || hc->default_rt_export_list || hc->nve_groups_sequential->count) { ++write; vty_out(vty, " vnc defaults\n"); if (hc->default_rd.prefixlen) { char buf[RD_ADDRSTRLEN]; if (AF_UNIX == hc->default_rd.family) { uint16_t value = 0; value = ((hc->default_rd.val[6] << 8) & 0x0ff00) | (hc->default_rd.val[7] & 0x0ff); vty_out(vty, " rd auto:vn:%d\n", value); } else vty_out(vty, " rd %s\n", prefix_rd2str(&hc->default_rd, buf, sizeof(buf))); } if (hc->default_response_lifetime != BGP_VNC_DEFAULT_RESPONSE_LIFETIME_DEFAULT) { vty_out(vty, " response-lifetime "); if (hc->default_response_lifetime != UINT32_MAX) vty_out(vty, "%d", hc->default_response_lifetime); else vty_out(vty, "infinite"); vty_out(vty, "\n"); } if (hc->default_rt_import_list && hc->default_rt_export_list && ecommunity_cmp(hc->default_rt_import_list, hc->default_rt_export_list)) { char *b = ecommunity_ecom2str( hc->default_rt_import_list, ECOMMUNITY_FORMAT_ROUTE_MAP, ECOMMUNITY_ROUTE_TARGET); vty_out(vty, " rt both %s\n", b); XFREE(MTYPE_ECOMMUNITY_STR, b); } else { if (hc->default_rt_import_list) { char *b = ecommunity_ecom2str( hc->default_rt_import_list, ECOMMUNITY_FORMAT_ROUTE_MAP, ECOMMUNITY_ROUTE_TARGET); vty_out(vty, " rt import %s\n", b); XFREE(MTYPE_ECOMMUNITY_STR, b); } if (hc->default_rt_export_list) { char *b = ecommunity_ecom2str( hc->default_rt_export_list, ECOMMUNITY_FORMAT_ROUTE_MAP, ECOMMUNITY_ROUTE_TARGET); vty_out(vty, " rt export %s\n", b); XFREE(MTYPE_ECOMMUNITY_STR, b); } } if (bgp->rfapi->rfp_methods.cfg_group_cb) write += (bgp->rfapi->rfp_methods.cfg_group_cb)( vty, bgp->rfapi->rfp, RFAPI_RFP_CFG_GROUP_DEFAULT, NULL, bgp->rfapi_cfg->default_rfp_cfg); vty_out(vty, " exit-vnc\n"); vty_out(vty, "!\n"); } for (ALL_LIST_ELEMENTS(hc->nve_groups_sequential, node, nnode, rfg)) if (rfg->type == RFAPI_GROUP_CFG_NVE) { ++write; vty_out(vty, " vnc nve-group %s\n", rfg->name); if (rfg->vn_prefix.family && rfg->vn_node) { char buf[PREFIX_STRLEN]; prefix2str(&rfg->vn_prefix, buf, sizeof(buf)); vty_out(vty, " prefix %s %s\n", "vn", buf); } if (rfg->un_prefix.family && rfg->un_node) { char buf[PREFIX_STRLEN]; prefix2str(&rfg->un_prefix, buf, sizeof(buf)); vty_out(vty, " prefix %s %s\n", "un", buf); } if (rfg->rd.prefixlen) { char buf[RD_ADDRSTRLEN]; if (AF_UNIX == rfg->rd.family) { uint16_t value = 0; value = ((rfg->rd.val[6] << 8) & 0x0ff00) | (rfg->rd.val[7] & 0x0ff); vty_out(vty, " rd auto:vn:%d\n", value); } else vty_out(vty, " rd %s\n", prefix_rd2str( &rfg->rd, buf, sizeof(buf))); } if (rfg->flags & RFAPI_RFG_RESPONSE_LIFETIME) { vty_out(vty, " response-lifetime "); if (rfg->response_lifetime != UINT32_MAX) vty_out(vty, "%d", rfg->response_lifetime); else vty_out(vty, "infinite"); vty_out(vty, "\n"); } if (rfg->rt_import_list && rfg->rt_export_list && ecommunity_cmp(rfg->rt_import_list, rfg->rt_export_list)) { char *b = ecommunity_ecom2str( rfg->rt_import_list, ECOMMUNITY_FORMAT_ROUTE_MAP, ECOMMUNITY_ROUTE_TARGET); vty_out(vty, " rt both %s\n", b); XFREE(MTYPE_ECOMMUNITY_STR, b); } else { if (rfg->rt_import_list) { char *b = ecommunity_ecom2str( rfg->rt_import_list, ECOMMUNITY_FORMAT_ROUTE_MAP, ECOMMUNITY_ROUTE_TARGET); vty_out(vty, " rt import %s\n", b); XFREE(MTYPE_ECOMMUNITY_STR, b); } if (rfg->rt_export_list) { char *b = ecommunity_ecom2str( rfg->rt_export_list, ECOMMUNITY_FORMAT_ROUTE_MAP, ECOMMUNITY_ROUTE_TARGET); vty_out(vty, " rt export %s\n", b); XFREE(MTYPE_ECOMMUNITY_STR, b); } } /* * route filtering: prefix-lists and route-maps */ for (afi = AFI_IP; afi < AFI_MAX; ++afi) { const char *afistr = (afi == AFI_IP) ? "ipv4" : "ipv6"; if (rfg->plist_export_bgp_name[afi]) { vty_out(vty, " export bgp %s prefix-list %s\n", afistr, rfg->plist_export_bgp_name [afi]); } if (rfg->plist_export_zebra_name[afi]) { vty_out(vty, " export zebra %s prefix-list %s\n", afistr, rfg->plist_export_zebra_name [afi]); } /* * currently we only support redist * plists for bgp-direct. * If we later add plist support for * redistributing other * protocols, we'll need to loop over * protocols here */ if (rfg->plist_redist_name [ZEBRA_ROUTE_BGP_DIRECT] [afi]) { vty_out(vty, " redistribute bgp-direct %s prefix-list %s\n", afistr, rfg->plist_redist_name [ZEBRA_ROUTE_BGP_DIRECT] [afi]); } if (rfg->plist_redist_name [ZEBRA_ROUTE_BGP_DIRECT_EXT] [afi]) { vty_out(vty, " redistribute bgp-direct-to-nve-groups %s prefix-list %s\n", afistr, rfg->plist_redist_name [ZEBRA_ROUTE_BGP_DIRECT_EXT] [afi]); } } if (rfg->routemap_export_bgp_name) { vty_out(vty, " export bgp route-map %s\n", rfg->routemap_export_bgp_name); } if (rfg->routemap_export_zebra_name) { vty_out(vty, " export zebra route-map %s\n", rfg->routemap_export_zebra_name); } if (rfg->routemap_redist_name [ZEBRA_ROUTE_BGP_DIRECT]) { vty_out(vty, " redistribute bgp-direct route-map %s\n", rfg->routemap_redist_name [ZEBRA_ROUTE_BGP_DIRECT]); } if (rfg->routemap_redist_name [ZEBRA_ROUTE_BGP_DIRECT_EXT]) { vty_out(vty, " redistribute bgp-direct-to-nve-groups route-map %s\n", rfg->routemap_redist_name [ZEBRA_ROUTE_BGP_DIRECT_EXT]); } if (bgp->rfapi->rfp_methods.cfg_group_cb) write += (bgp->rfapi->rfp_methods .cfg_group_cb)( vty, bgp->rfapi->rfp, RFAPI_RFP_CFG_GROUP_NVE, rfg->name, rfg->rfp_cfg); vty_out(vty, " exit-vnc\n"); vty_out(vty, "!\n"); } } /* have listen ports */ /* * route export to other protocols */ if (VNC_EXPORT_BGP_GRP_ENABLED(hc)) { vty_out(vty, " vnc export bgp mode group-nve\n"); } else if (VNC_EXPORT_BGP_RH_ENABLED(hc)) { vty_out(vty, " vnc export bgp mode registering-nve\n"); } else if (VNC_EXPORT_BGP_CE_ENABLED(hc)) { vty_out(vty, " vnc export bgp mode ce\n"); } if (VNC_EXPORT_ZEBRA_GRP_ENABLED(hc)) { vty_out(vty, " vnc export zebra mode group-nve\n"); } else if (VNC_EXPORT_ZEBRA_RH_ENABLED(hc)) { vty_out(vty, " vnc export zebra mode registering-nve\n"); } if (hc->rfg_export_direct_bgp_l) { for (ALL_LIST_ELEMENTS(hc->rfg_export_direct_bgp_l, node, nnode, rfgn)) { vty_out(vty, " vnc export bgp group-nve group %s\n", rfgn->name); } } if (hc->rfg_export_zebra_l) { for (ALL_LIST_ELEMENTS(hc->rfg_export_zebra_l, node, nnode, rfgn)) { vty_out(vty, " vnc export zebra group-nve group %s\n", rfgn->name); } } if (hc->rfg_redist_name) { vty_out(vty, " vnc redistribute nve-group %s\n", hc->rfg_redist_name); } if (hc->redist_lifetime) { vty_out(vty, " vnc redistribute lifetime %d\n", hc->redist_lifetime); } if (hc->resolve_nve_roo_local_admin != BGP_VNC_CONFIG_RESOLVE_NVE_ROO_LOCAL_ADMIN_DEFAULT) { vty_out(vty, " vnc redistribute resolve-nve roo-ec-local-admin %d\n", hc->resolve_nve_roo_local_admin); } if (hc->redist_mode) /* ! default */ { const char *s = ""; switch (hc->redist_mode) { case VNC_REDIST_MODE_PLAIN: s = "plain"; break; case VNC_REDIST_MODE_RFG: s = "nve-group"; break; case VNC_REDIST_MODE_RESOLVE_NVE: s = "resolve-nve"; break; } if (s) { vty_out(vty, " vnc redistribute mode %s\n", s); } } /* * route filtering: prefix-lists and route-maps */ for (afi = AFI_IP; afi < AFI_MAX; ++afi) { const char *afistr = (afi == AFI_IP) ? "ipv4" : "ipv6"; if (hc->plist_export_bgp_name[afi]) { vty_out(vty, " vnc export bgp %s prefix-list %s\n", afistr, hc->plist_export_bgp_name[afi]); } if (hc->plist_export_zebra_name[afi]) { vty_out(vty, " vnc export zebra %s prefix-list %s\n", afistr, hc->plist_export_zebra_name[afi]); } if (hc->plist_redist_name[ZEBRA_ROUTE_BGP_DIRECT][afi]) { vty_out(vty, " vnc redistribute bgp-direct %s prefix-list %s\n", afistr, hc->plist_redist_name[ZEBRA_ROUTE_BGP_DIRECT] [afi]); } } if (hc->routemap_export_bgp_name) { vty_out(vty, " vnc export bgp route-map %s\n", hc->routemap_export_bgp_name); } if (hc->routemap_export_zebra_name) { vty_out(vty, " vnc export zebra route-map %s\n", hc->routemap_export_zebra_name); } if (hc->routemap_redist_name[ZEBRA_ROUTE_BGP_DIRECT]) { vty_out(vty, " vnc redistribute bgp-direct route-map %s\n", hc->routemap_redist_name[ZEBRA_ROUTE_BGP_DIRECT]); } for (afi = AFI_IP; afi < AFI_MAX; ++afi) { for (type = 0; type < ZEBRA_ROUTE_MAX; ++type) { if (hc->redist[afi][type]) { if (type == ZEBRA_ROUTE_BGP_DIRECT_EXT && hc->redist_bgp_exterior_view_name) { vty_out(vty, " vnc redistribute %s %s view %s\n", ((afi == AFI_IP) ? "ipv4" : "ipv6"), zebra_route_string(type), hc->redist_bgp_exterior_view_name); } else { vty_out(vty, " vnc redistribute %s %s\n", ((afi == AFI_IP) ? "ipv4" : "ipv6"), zebra_route_string(type)); } } } } return write; } void bgp_rfapi_show_summary(struct bgp *bgp, struct vty *vty) { struct rfapi_cfg *hc = bgp->rfapi_cfg; afi_t afi; int type, redist = 0; char tmp[40]; if (hc == NULL) return; vty_out(vty, "%-39s %-19s %s\n", "VNC Advertise method:", (hc->flags & BGP_VNC_CONFIG_ADV_UN_METHOD_ENCAP ? "Encapsulation SAFI" : "Tunnel Encap attribute"), ((hc->flags & BGP_VNC_CONFIG_ADV_UN_METHOD_ENCAP) == (BGP_VNC_CONFIG_ADV_UN_METHOD_ENCAP & BGP_VNC_CONFIG_FLAGS_DEFAULT) ? "(default)" : "")); /* export */ vty_out(vty, "%-39s ", "Export from VNC:"); /* * route export to other protocols */ if (VNC_EXPORT_BGP_GRP_ENABLED(hc)) { redist++; vty_out(vty, "ToBGP Groups={"); if (hc->rfg_export_direct_bgp_l) { int cnt = 0; struct listnode *node, *nnode; struct rfapi_rfg_name *rfgn; for (ALL_LIST_ELEMENTS(hc->rfg_export_direct_bgp_l, node, nnode, rfgn)) { if (cnt++ != 0) vty_out(vty, ","); vty_out(vty, "%s", rfgn->name); } } vty_out(vty, "}"); } else if (VNC_EXPORT_BGP_RH_ENABLED(hc)) { redist++; vty_out(vty, "ToBGP {Registering NVE}"); /* note filters, route-maps not shown */ } else if (VNC_EXPORT_BGP_CE_ENABLED(hc)) { redist++; vty_out(vty, "ToBGP {NVE connected router:%d}", hc->resolve_nve_roo_local_admin); /* note filters, route-maps not shown */ } if (VNC_EXPORT_ZEBRA_GRP_ENABLED(hc)) { redist++; vty_out(vty, "%sToZebra Groups={", (redist == 1 ? "" : " ")); if (hc->rfg_export_zebra_l) { int cnt = 0; struct listnode *node, *nnode; struct rfapi_rfg_name *rfgn; for (ALL_LIST_ELEMENTS(hc->rfg_export_zebra_l, node, nnode, rfgn)) { if (cnt++ != 0) vty_out(vty, ","); vty_out(vty, "%s", rfgn->name); } } vty_out(vty, "}"); } else if (VNC_EXPORT_ZEBRA_RH_ENABLED(hc)) { redist++; vty_out(vty, "%sToZebra {Registering NVE}", (redist == 1 ? "" : " ")); /* note filters, route-maps not shown */ } vty_out(vty, "%-19s %s\n", (redist ? "" : "Off"), (redist ? "" : "(default)")); /* Redistribution */ redist = 0; vty_out(vty, "%-39s ", "Redistribution into VNC:"); for (afi = AFI_IP; afi < AFI_MAX; ++afi) { for (type = 0; type < ZEBRA_ROUTE_MAX; ++type) { if (hc->redist[afi][type]) { vty_out(vty, "{%s,%s} ", ((afi == AFI_IP) ? "ipv4" : "ipv6"), zebra_route_string(type)); redist++; } } } vty_out(vty, "%-19s %s\n", (redist ? "" : "Off"), (redist ? "" : "(default)")); vty_out(vty, "%-39s %3u%-16s %s\n", "RFP Registration Hold-Down Factor:", hc->rfp_cfg.holddown_factor, "%", (hc->rfp_cfg.holddown_factor == RFAPI_RFP_CFG_DEFAULT_HOLDDOWN_FACTOR ? "(default)" : "")); vty_out(vty, "%-39s %-19s %s\n", "RFP Updated responses:", (hc->rfp_cfg.use_updated_response == 0 ? "Off" : "On"), (hc->rfp_cfg.use_updated_response == 0 ? "(default)" : "")); vty_out(vty, "%-39s %-19s %s\n", "RFP Removal responses:", (hc->rfp_cfg.use_removes == 0 ? "Off" : "On"), (hc->rfp_cfg.use_removes == 0 ? "(default)" : "")); vty_out(vty, "%-39s %-19s %s\n", "RFP Full table download:", (hc->rfp_cfg.download_type == RFAPI_RFP_DOWNLOAD_FULL ? "On" : "Off"), (hc->rfp_cfg.download_type == RFAPI_RFP_DOWNLOAD_PARTIAL ? "(default)" : "")); sprintf(tmp, "%u seconds", hc->rfp_cfg.ftd_advertisement_interval); vty_out(vty, "%-39s %-19s %s\n", " Advertisement Interval:", tmp, (hc->rfp_cfg.ftd_advertisement_interval == RFAPI_RFP_CFG_DEFAULT_FTD_ADVERTISEMENT_INTERVAL ? "(default)" : "")); vty_out(vty, "%-39s %d seconds\n", "Default RFP response lifetime:", hc->default_response_lifetime); vty_out(vty, "\n"); return; } struct rfapi_cfg *bgp_rfapi_get_config(struct bgp *bgp) { struct rfapi_cfg *hc = NULL; if (bgp == NULL) bgp = bgp_get_default(); if (bgp != NULL) hc = bgp->rfapi_cfg; return hc; } #endif /* ENABLE_BGP_VNC */ frr-7.2.1/bgpd/rfapi/bgp_rfapi_cfg.h0000644000000000000000000002372113610377563014157 00000000000000/* * * Copyright 2009-2016, LabN Consulting, L.L.C. * * * This program is free software; you can redistribute it and/or * modify it under the terms of the GNU General Public License * as published by the Free Software Foundation; either version 2 * of the License, or (at your option) any later version. * * 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; see the file COPYING; if not, write to the Free Software * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA */ #ifndef _QUAGGA_BGP_RFAPI_CFG_H #define _QUAGGA_BGP_RFAPI_CFG_H #include "lib/table.h" #include "lib/routemap.h" #if ENABLE_BGP_VNC #include "rfapi.h" struct rfapi_l2_group_cfg { char *name; uint32_t logical_net_id; struct list *labels; /* list of uint32_t */ struct ecommunity *rt_import_list; struct ecommunity *rt_export_list; void *rfp_cfg; /* rfp owned group config */ QOBJ_FIELDS }; DECLARE_QOBJ_TYPE(rfapi_l2_group_cfg) typedef enum { RFAPI_GROUP_CFG_NVE = 1, RFAPI_GROUP_CFG_VRF, RFAPI_GROUP_CFG_L2, RFAPI_GROUP_CFG_MAX } rfapi_group_cfg_type_t; struct rfapi_nve_group_cfg { struct agg_node *vn_node; /* backref */ struct agg_node *un_node; /* backref */ rfapi_group_cfg_type_t type; /* NVE|VPN */ char *name; /* unique by type! */ struct prefix vn_prefix; struct prefix un_prefix; struct prefix_rd rd; uint8_t l2rd; /* 0 = VN addr LSB */ uint32_t response_lifetime; uint32_t flags; #define RFAPI_RFG_RESPONSE_LIFETIME 0x01 /* bits */ #define RFAPI_RFG_L2RD 0x02 #define RFAPI_RFG_VPN_NH_SELF 0x04 struct ecommunity *rt_import_list; struct ecommunity *rt_export_list; struct rfapi_import_table *rfapi_import_table; void *rfp_cfg; /* rfp owned group config */ /* * List of NVE descriptors that are assigned to this NVE group * * Currently (Mar 2010) this list is used only by the route * export code to generate per-NVE nexthops for each route. * * The nve descriptors listed here have pointers back to * this nve group config structure to enable them to delete * their own list entries when they are closed. Consequently, * if an instance of this nve group config structure is deleted, * we must first set the nve descriptor references to it to NULL. */ struct list *nves; /* * Route filtering * * Prefix lists are segregated by afi (part of the base plist code) * Route-maps are not segregated */ char *plist_export_bgp_name[AFI_MAX]; struct prefix_list *plist_export_bgp[AFI_MAX]; char *plist_export_zebra_name[AFI_MAX]; struct prefix_list *plist_export_zebra[AFI_MAX]; char *plist_redist_name[ZEBRA_ROUTE_MAX][AFI_MAX]; struct prefix_list *plist_redist[ZEBRA_ROUTE_MAX][AFI_MAX]; char *routemap_export_bgp_name; struct route_map *routemap_export_bgp; char *routemap_export_zebra_name; struct route_map *routemap_export_zebra; char *routemap_redist_name[ZEBRA_ROUTE_MAX]; struct route_map *routemap_redist[ZEBRA_ROUTE_MAX]; /* for VRF type groups */ uint32_t label; struct rfapi_descriptor *rfd; QOBJ_FIELDS }; DECLARE_QOBJ_TYPE(rfapi_nve_group_cfg) struct rfapi_rfg_name { struct rfapi_nve_group_cfg *rfg; char *name; }; typedef enum { VNC_REDIST_MODE_PLAIN = 0, /* 0 = default */ VNC_REDIST_MODE_RFG, VNC_REDIST_MODE_RESOLVE_NVE } vnc_redist_mode_t; struct rfapi_cfg { struct prefix_rd default_rd; uint8_t default_l2rd; struct ecommunity *default_rt_import_list; struct ecommunity *default_rt_export_list; uint32_t default_response_lifetime; #define BGP_VNC_DEFAULT_RESPONSE_LIFETIME_DEFAULT 3600 void *default_rfp_cfg; /* rfp owned group config */ struct list *l2_groups; /* rfapi_l2_group_cfg list */ /* three views into the same collection of rfapi_nve_group_cfg */ struct list *nve_groups_sequential; struct agg_table *nve_groups_vn[AFI_MAX]; struct agg_table *nve_groups_un[AFI_MAX]; /* * For Single VRF export to ordinary routing protocols. This is * the nve-group that the ordinary protocols belong to. We use it * to set the RD when sending unicast Zebra routes to VNC */ uint8_t redist[AFI_MAX][ZEBRA_ROUTE_MAX]; uint32_t redist_lifetime; vnc_redist_mode_t redist_mode; /* * view name of BGP unicast instance that holds * exterior routes */ char *redist_bgp_exterior_view_name; struct bgp *redist_bgp_exterior_view; /* * nve group for redistribution of routes from zebra to VNC * (which is probably not useful for production networks) */ char *rfg_redist_name; struct rfapi_nve_group_cfg *rfg_redist; /* * List of NVE groups on whose behalf we will export VNC * routes to zebra. ((NB: it's actually a list of ) * This list is used when BGP_VNC_CONFIG_EXPORT_ZEBRA_MODE_BITS is * BGP_VNC_CONFIG_EXPORT_ZEBRA_MODE_GRP */ struct list *rfg_export_zebra_l; /* * List of NVE groups on whose behalf we will export VNC * routes directly to the bgp unicast RIB. (NB: it's actually * a list of ) * This list is used when BGP_VNC_CONFIG_EXPORT_BGP_MODE_BITS is * BGP_VNC_CONFIG_EXPORT_BGP_MODE_GRP */ struct list *rfg_export_direct_bgp_l; /* * Exported Route filtering * * Prefix lists are segregated by afi (part of the base plist code) * Route-maps are not segregated */ char *plist_export_bgp_name[AFI_MAX]; struct prefix_list *plist_export_bgp[AFI_MAX]; char *plist_export_zebra_name[AFI_MAX]; struct prefix_list *plist_export_zebra[AFI_MAX]; char *routemap_export_bgp_name; struct route_map *routemap_export_bgp; char *routemap_export_zebra_name; struct route_map *routemap_export_zebra; /* * Redistributed route filtering (routes from other * protocols into VNC) */ char *plist_redist_name[ZEBRA_ROUTE_MAX][AFI_MAX]; struct prefix_list *plist_redist[ZEBRA_ROUTE_MAX][AFI_MAX]; char *routemap_redist_name[ZEBRA_ROUTE_MAX]; struct route_map *routemap_redist[ZEBRA_ROUTE_MAX]; /* * For importing bgp unicast routes to VNC, we encode the CE * (route nexthop) in a Route Origin extended community. The * local part (16-bit) is user-configurable. */ uint16_t resolve_nve_roo_local_admin; #define BGP_VNC_CONFIG_RESOLVE_NVE_ROO_LOCAL_ADMIN_DEFAULT 5226 uint32_t flags; #define BGP_VNC_CONFIG_ADV_UN_METHOD_ENCAP 0x00000001 #define BGP_VNC_CONFIG_CALLBACK_DISABLE 0x00000002 #define BGP_VNC_CONFIG_RESPONSE_REMOVAL_DISABLE 0x00000004 #define BGP_VNC_CONFIG_EXPORT_BGP_MODE_BITS 0x000000f0 #define BGP_VNC_CONFIG_EXPORT_ZEBRA_MODE_BITS 0x00000f00 #define BGP_VNC_CONFIG_EXPORT_BGP_MODE_NONE 0x00000000 #define BGP_VNC_CONFIG_EXPORT_BGP_MODE_GRP 0x00000010 #define BGP_VNC_CONFIG_EXPORT_BGP_MODE_RH 0x00000020 /* registerd nve */ #define BGP_VNC_CONFIG_EXPORT_BGP_MODE_CE 0x00000040 #define BGP_VNC_CONFIG_EXPORT_ZEBRA_MODE_NONE 0x00000000 #define BGP_VNC_CONFIG_EXPORT_ZEBRA_MODE_GRP 0x00000100 #define BGP_VNC_CONFIG_EXPORT_ZEBRA_MODE_RH 0x00000200 #define BGP_VNC_CONFIG_FILTER_SELF_FROM_RSP 0x00001000 #define BGP_VNC_CONFIG_L2RD 0x00002000 /* Use new NVE RIB to filter callback routes */ /* Filter querying NVE's registrations from responses */ /* Default to updated-responses off */ /* Default to removal-responses off */ #define BGP_VNC_CONFIG_FLAGS_DEFAULT \ (BGP_VNC_CONFIG_FILTER_SELF_FROM_RSP | BGP_VNC_CONFIG_CALLBACK_DISABLE \ | BGP_VNC_CONFIG_RESPONSE_REMOVAL_DISABLE) struct rfapi_rfp_cfg rfp_cfg; /* rfp related configuration */ }; #define VNC_EXPORT_ZEBRA_GRP_ENABLED(hc) \ (((hc)->flags & BGP_VNC_CONFIG_EXPORT_ZEBRA_MODE_BITS) \ == BGP_VNC_CONFIG_EXPORT_ZEBRA_MODE_GRP) #define VNC_EXPORT_ZEBRA_RH_ENABLED(hc) \ (((hc)->flags & BGP_VNC_CONFIG_EXPORT_ZEBRA_MODE_BITS) \ == BGP_VNC_CONFIG_EXPORT_ZEBRA_MODE_RH) #define VNC_EXPORT_BGP_GRP_ENABLED(hc) \ (((hc)->flags & BGP_VNC_CONFIG_EXPORT_BGP_MODE_BITS) \ == BGP_VNC_CONFIG_EXPORT_BGP_MODE_GRP) #define VNC_EXPORT_BGP_RH_ENABLED(hc) \ (((hc)->flags & BGP_VNC_CONFIG_EXPORT_BGP_MODE_BITS) \ == BGP_VNC_CONFIG_EXPORT_BGP_MODE_RH) #define VNC_EXPORT_BGP_CE_ENABLED(hc) \ (((hc)->flags & BGP_VNC_CONFIG_EXPORT_BGP_MODE_BITS) \ == BGP_VNC_CONFIG_EXPORT_BGP_MODE_CE) void bgp_rfapi_cfg_init(void); struct rfapi_cfg *bgp_rfapi_cfg_new(struct rfapi_rfp_cfg *cfg); void bgp_rfapi_cfg_destroy(struct bgp *bgp, struct rfapi_cfg *h); int bgp_rfapi_cfg_write(struct vty *vty, struct bgp *bgp); extern int bgp_rfapi_is_vnc_configured(struct bgp *bgp); extern void nve_group_to_nve_list(struct rfapi_nve_group_cfg *rfg, struct list **nves, uint8_t family); /* AF_INET, AF_INET6 */ struct rfapi_nve_group_cfg *bgp_rfapi_cfg_match_group(struct rfapi_cfg *hc, struct prefix *vn, struct prefix *un); struct rfapi_nve_group_cfg * bgp_rfapi_cfg_match_byname(struct bgp *bgp, const char *name, rfapi_group_cfg_type_t type); /* _MAX = any */ extern void vnc_prefix_list_update(struct bgp *bgp); extern void vnc_routemap_update(struct bgp *bgp, const char *unused); extern void bgp_rfapi_show_summary(struct bgp *bgp, struct vty *vty); extern struct rfapi_cfg *bgp_rfapi_get_config(struct bgp *bgp); extern struct rfapi_l2_group_cfg * bgp_rfapi_get_group_by_lni_label(struct bgp *bgp, uint32_t logical_net_id, uint32_t label); extern struct ecommunity * bgp_rfapi_get_ecommunity_by_lni_label(struct bgp *bgp, uint32_t is_import, uint32_t logical_net_id, uint32_t label); /* note, 20bit label! */ extern struct list * bgp_rfapi_get_labellist_by_lni_label(struct bgp *bgp, uint32_t logical_net_id, uint32_t label); /* note, 20bit label! */ #endif /* ENABLE_BGP_VNC */ #endif /* _QUAGGA_BGP_RFAPI_CFG_H */ frr-7.2.1/bgpd/rfapi/rfapi.c0000644000000000000000000031006313610377563012501 00000000000000/* * * Copyright 2009-2016, LabN Consulting, L.L.C. * * * This program is free software; you can redistribute it and/or * modify it under the terms of the GNU General Public License * as published by the Free Software Foundation; either version 2 * of the License, or (at your option) any later version. * * 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; see the file COPYING; if not, write to the Free Software * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA */ #include "lib/zebra.h" #include "lib/prefix.h" #include "lib/agg_table.h" #include "lib/vty.h" #include "lib/memory.h" #include "lib/routemap.h" #include "lib/log.h" #include "lib/linklist.h" #include "lib/command.h" #include "lib/stream.h" #include "lib/ringbuf.h" #include "lib/lib_errors.h" #include "bgpd/bgpd.h" #include "bgpd/bgp_ecommunity.h" #include "bgpd/bgp_attr.h" #include "bgpd/rfapi/bgp_rfapi_cfg.h" #include "bgpd/rfapi/rfapi.h" #include "bgpd/rfapi/rfapi_backend.h" #include "bgpd/bgp_route.h" #include "bgpd/bgp_mplsvpn.h" #include "bgpd/bgp_aspath.h" #include "bgpd/bgp_advertise.h" #include "bgpd/bgp_vnc_types.h" #include "bgpd/bgp_zebra.h" #include "bgpd/rfapi/rfapi_import.h" #include "bgpd/rfapi/rfapi_private.h" #include "bgpd/rfapi/rfapi_monitor.h" #include "bgpd/rfapi/rfapi_vty.h" #include "bgpd/rfapi/vnc_export_bgp.h" #include "bgpd/rfapi/vnc_export_bgp_p.h" #include "bgpd/rfapi/vnc_zebra.h" #include "bgpd/rfapi/vnc_import_bgp.h" #include "bgpd/rfapi/rfapi_rib.h" #include "bgpd/rfapi/rfapi_ap.h" #include "bgpd/rfapi/rfapi_encap_tlv.h" #include "bgpd/rfapi/vnc_debug.h" #ifdef HAVE_GLIBC_BACKTRACE /* for backtrace and friends */ #include #endif /* HAVE_GLIBC_BACKTRACE */ struct ethaddr rfapi_ethaddr0 = {{0}}; #define DEBUG_RFAPI_STR "RF API debugging/testing command\n" const char *rfapi_error_str(int code) { switch (code) { case 0: return "Success"; case ENXIO: return "BGP or VNC not configured"; case ENOENT: return "No match"; case EEXIST: return "Handle already open"; case ENOMSG: return "Incomplete configuration"; case EAFNOSUPPORT: return "Invalid address family"; case EDEADLK: return "Called from within a callback procedure"; case EBADF: return "Invalid handle"; case EINVAL: return "Invalid argument"; case ESTALE: return "Stale descriptor"; default: return "Unknown error"; } } /*------------------------------------------ * rfapi_get_response_lifetime_default * * Returns the default lifetime for a response. * rfp_start_val value returned by rfp_start or * NULL (=use default instance) * * input: * None * * output: * * return value: The bgp instance default lifetime for a response. --------------------------------------------*/ int rfapi_get_response_lifetime_default(void *rfp_start_val) { struct bgp *bgp = rfapi_bgp_lookup_by_rfp(rfp_start_val); if (bgp) return bgp->rfapi_cfg->default_response_lifetime; return BGP_VNC_DEFAULT_RESPONSE_LIFETIME_DEFAULT; } /*------------------------------------------ * rfapi_is_vnc_configured * * Returns if VNC is configured * * input: * rfp_start_val value returned by rfp_start or * NULL (=use default instance) * * output: * * return value: If VNC is configured for the bgpd instance * 0 Success * ENXIO VNC not configured --------------------------------------------*/ int rfapi_is_vnc_configured(void *rfp_start_val) { struct bgp *bgp = rfapi_bgp_lookup_by_rfp(rfp_start_val); if (bgp_rfapi_is_vnc_configured(bgp) == 0) return 0; return ENXIO; } /*------------------------------------------ * rfapi_get_vn_addr * * Get the virtual network address used by an NVE based on it's RFD * * input: * rfd: rfapi descriptor returned by rfapi_open or rfapi_create_generic * * output: * * return value: * vn NVE virtual network address *------------------------------------------*/ struct rfapi_ip_addr *rfapi_get_vn_addr(void *rfd) { struct rfapi_descriptor *rrfd = (struct rfapi_descriptor *)rfd; return &rrfd->vn_addr; } /*------------------------------------------ * rfapi_get_un_addr * * Get the underlay network address used by an NVE based on it's RFD * * input: * rfd: rfapi descriptor returned by rfapi_open or rfapi_create_generic * * output: * * return value: * un NVE underlay network address *------------------------------------------*/ struct rfapi_ip_addr *rfapi_get_un_addr(void *rfd) { struct rfapi_descriptor *rrfd = (struct rfapi_descriptor *)rfd; return &rrfd->un_addr; } int rfapi_ip_addr_cmp(struct rfapi_ip_addr *a1, struct rfapi_ip_addr *a2) { if (a1->addr_family != a2->addr_family) return a1->addr_family - a2->addr_family; if (a1->addr_family == AF_INET) { return IPV4_ADDR_CMP(&a1->addr.v4, &a2->addr.v4); } if (a1->addr_family == AF_INET6) { return IPV6_ADDR_CMP(&a1->addr.v6, &a2->addr.v6); } assert(1); /* NOTREACHED */ return 1; } static int rfapi_find_node(struct bgp *bgp, struct rfapi_ip_addr *vn_addr, struct rfapi_ip_addr *un_addr, struct agg_node **node) { struct rfapi *h; struct prefix p; struct agg_node *rn; int rc; afi_t afi; if (!bgp) { return ENXIO; } h = bgp->rfapi; if (!h) { return ENXIO; } afi = family2afi(un_addr->addr_family); if (!afi) { return EAFNOSUPPORT; } if ((rc = rfapiRaddr2Qprefix(un_addr, &p))) return rc; rn = agg_node_lookup(h->un[afi], &p); if (!rn) return ENOENT; agg_unlock_node(rn); *node = rn; return 0; } int rfapi_find_rfd(struct bgp *bgp, struct rfapi_ip_addr *vn_addr, struct rfapi_ip_addr *un_addr, struct rfapi_descriptor **rfd) { struct agg_node *rn; int rc; rc = rfapi_find_node(bgp, vn_addr, un_addr, &rn); if (rc) return rc; for (*rfd = (struct rfapi_descriptor *)(rn->info); *rfd; *rfd = (*rfd)->next) { if (!rfapi_ip_addr_cmp(&(*rfd)->vn_addr, vn_addr)) break; } if (!*rfd) return ENOENT; return 0; } /*------------------------------------------ * rfapi_find_handle * * input: * un underlay network address * vn virtual network address * * output: * pHandle pointer to location to store handle * * return value: * 0 Success * ENOENT no matching handle * ENXIO BGP or VNC not configured *------------------------------------------*/ static int rfapi_find_handle(struct bgp *bgp, struct rfapi_ip_addr *vn_addr, struct rfapi_ip_addr *un_addr, rfapi_handle *handle) { struct rfapi_descriptor **rfd; rfd = (struct rfapi_descriptor **)handle; return rfapi_find_rfd(bgp, vn_addr, un_addr, rfd); } static int rfapi_find_handle_vty(struct vty *vty, struct rfapi_ip_addr *vn_addr, struct rfapi_ip_addr *un_addr, rfapi_handle *handle) { struct bgp *bgp; struct rfapi_descriptor **rfd; bgp = bgp_get_default(); /* assume 1 instance for now */ rfd = (struct rfapi_descriptor **)handle; return rfapi_find_rfd(bgp, vn_addr, un_addr, rfd); } static int is_valid_rfd(struct rfapi_descriptor *rfd) { rfapi_handle hh; if (!rfd || rfd->bgp == NULL) return 0; if (CHECK_FLAG( rfd->flags, RFAPI_HD_FLAG_IS_VRF)) /* assume VRF/internal are valid */ return 1; if (rfapi_find_handle(rfd->bgp, &rfd->vn_addr, &rfd->un_addr, &hh)) return 0; if (rfd != hh) return 0; return 1; } /* * check status of descriptor */ int rfapi_check(void *handle) { struct rfapi_descriptor *rfd = (struct rfapi_descriptor *)handle; rfapi_handle hh; int rc; if (!rfd || rfd->bgp == NULL) return EINVAL; if (CHECK_FLAG( rfd->flags, RFAPI_HD_FLAG_IS_VRF)) /* assume VRF/internal are valid */ return 0; if ((rc = rfapi_find_handle(rfd->bgp, &rfd->vn_addr, &rfd->un_addr, &hh))) return rc; if (rfd != hh) return ENOENT; if (!rfd->rfg) return ESTALE; return 0; } void del_vnc_route(struct rfapi_descriptor *rfd, struct peer *peer, /* rfd->peer for RFP regs */ struct bgp *bgp, safi_t safi, struct prefix *p, struct prefix_rd *prd, uint8_t type, uint8_t sub_type, struct rfapi_nexthop *lnh, int kill) { afi_t afi; /* of the VN address */ struct bgp_node *bn; struct bgp_path_info *bpi; char buf[PREFIX_STRLEN]; char buf2[RD_ADDRSTRLEN]; struct prefix_rd prd0; prefix2str(p, buf, sizeof(buf)); afi = family2afi(p->family); assert(afi == AFI_IP || afi == AFI_IP6); if (safi == SAFI_ENCAP) { memset(&prd0, 0, sizeof(prd0)); prd0.family = AF_UNSPEC; prd0.prefixlen = 64; prd = &prd0; } bn = bgp_afi_node_get(bgp->rib[afi][safi], afi, safi, p, prd); vnc_zlog_debug_verbose( "%s: peer=%p, prefix=%s, prd=%s afi=%d, safi=%d bn=%p, bn->info=%p", __func__, peer, buf, prefix_rd2str(prd, buf2, sizeof(buf2)), afi, safi, bn, (bn ? bgp_node_get_bgp_path_info(bn) : NULL)); for (bpi = (bn ? bgp_node_get_bgp_path_info(bn) : NULL); bpi; bpi = bpi->next) { vnc_zlog_debug_verbose( "%s: trying bpi=%p, bpi->peer=%p, bpi->type=%d, bpi->sub_type=%d, bpi->extra->vnc.export.rfapi_handle=%p, local_pref=%u", __func__, bpi, bpi->peer, bpi->type, bpi->sub_type, (bpi->extra ? bpi->extra->vnc.export.rfapi_handle : NULL), ((bpi->attr && CHECK_FLAG(bpi->attr->flag, ATTR_FLAG_BIT(BGP_ATTR_LOCAL_PREF))) ? bpi->attr->local_pref : 0)); if (bpi->peer == peer && bpi->type == type && bpi->sub_type == sub_type && bpi->extra && bpi->extra->vnc.export.rfapi_handle == (void *)rfd) { vnc_zlog_debug_verbose("%s: matched it", __func__); break; } } if (lnh) { /* * lnh set means to JUST delete the local nexthop from this * route. Leave the route itself in place. * TBD add return code reporting of success/failure */ if (!bpi || !bpi->extra || !bpi->extra->vnc.export.local_nexthops) { /* * no local nexthops */ vnc_zlog_debug_verbose( "%s: lnh list already empty at prefix %s", __func__, buf); goto done; } /* * look for it */ struct listnode *node; struct rfapi_nexthop *pLnh = NULL; for (ALL_LIST_ELEMENTS_RO(bpi->extra->vnc.export.local_nexthops, node, pLnh)) { if (prefix_same(&pLnh->addr, &lnh->addr)) { break; } } if (pLnh) { listnode_delete(bpi->extra->vnc.export.local_nexthops, pLnh); /* silly rabbit, listnode_delete doesn't invoke * list->del on data */ rfapi_nexthop_free(pLnh); } else { vnc_zlog_debug_verbose("%s: desired lnh not found %s", __func__, buf); } goto done; } /* * loop back to import tables * Do this before removing from BGP RIB because rfapiProcessWithdraw * might refer to it */ rfapiProcessWithdraw(peer, rfd, p, prd, NULL, afi, safi, type, kill); if (bpi) { prefix2str(p, buf, sizeof(buf)); vnc_zlog_debug_verbose( "%s: Found route (safi=%d) to delete at prefix %s", __func__, safi, buf); if (safi == SAFI_MPLS_VPN) { struct bgp_node *prn = NULL; struct bgp_table *table = NULL; prn = bgp_node_get(bgp->rib[afi][safi], (struct prefix *)prd); table = bgp_node_get_bgp_table_info(prn); if (table) vnc_import_bgp_del_vnc_host_route_mode_resolve_nve( bgp, prd, table, p, bpi); bgp_unlock_node(prn); } /* * Delete local_nexthops list */ if (bpi->extra && bpi->extra->vnc.export.local_nexthops) list_delete(&bpi->extra->vnc.export.local_nexthops); bgp_aggregate_decrement(bgp, p, bpi, afi, safi); bgp_path_info_delete(bn, bpi); bgp_process(bgp, bn, afi, safi); } else { vnc_zlog_debug_verbose( "%s: Couldn't find route (safi=%d) at prefix %s", __func__, safi, buf); } done: bgp_unlock_node(bn); } struct rfapi_nexthop *rfapi_nexthop_new(struct rfapi_nexthop *copyme) { struct rfapi_nexthop *new = XCALLOC(MTYPE_RFAPI_NEXTHOP, sizeof(struct rfapi_nexthop)); if (copyme) *new = *copyme; return new; } void rfapi_nexthop_free(void *p) { struct rfapi_nexthop *goner = p; XFREE(MTYPE_RFAPI_NEXTHOP, goner); } struct rfapi_vn_option *rfapi_vn_options_dup(struct rfapi_vn_option *existing) { struct rfapi_vn_option *p; struct rfapi_vn_option *head = NULL; struct rfapi_vn_option *tail = NULL; for (p = existing; p; p = p->next) { struct rfapi_vn_option *new; new = XCALLOC(MTYPE_RFAPI_VN_OPTION, sizeof(struct rfapi_vn_option)); *new = *p; new->next = NULL; if (tail) (tail)->next = new; tail = new; if (!head) { head = new; } } return head; } void rfapi_un_options_free(struct rfapi_un_option *p) { struct rfapi_un_option *next; while (p) { next = p->next; XFREE(MTYPE_RFAPI_UN_OPTION, p); p = next; } } void rfapi_vn_options_free(struct rfapi_vn_option *p) { struct rfapi_vn_option *next; while (p) { next = p->next; XFREE(MTYPE_RFAPI_VN_OPTION, p); p = next; } } /* Based on bgp_redistribute_add() */ void add_vnc_route(struct rfapi_descriptor *rfd, /* cookie, VPN UN addr, peer */ struct bgp *bgp, int safi, struct prefix *p, struct prefix_rd *prd, struct rfapi_ip_addr *nexthop, uint32_t *local_pref, uint32_t *lifetime, /* NULL => dont send lifetime */ struct bgp_tea_options *rfp_options, struct rfapi_un_option *options_un, struct rfapi_vn_option *options_vn, struct ecommunity *rt_export_list, /* Copied, not consumed */ uint32_t *med, /* NULL => don't set med */ uint32_t *label, /* low order 3 bytes */ uint8_t type, uint8_t sub_type, /* RFP, NORMAL or REDIST */ int flags) { afi_t afi; /* of the VN address */ struct bgp_path_info *new; struct bgp_path_info *bpi; struct bgp_node *bn; struct attr attr = {0}; struct attr *new_attr; uint32_t label_val; struct bgp_attr_encap_subtlv *encaptlv; char buf[PREFIX_STRLEN]; char buf2[RD_ADDRSTRLEN]; #if 0 /* unused? */ struct prefix pfx_buf; #endif struct rfapi_nexthop *lnh = NULL; /* local nexthop */ struct rfapi_vn_option *vo; struct rfapi_l2address_option *l2o = NULL; struct rfapi_ip_addr *un_addr = &rfd->un_addr; bgp_encap_types TunnelType = BGP_ENCAP_TYPE_RESERVED; struct bgp_redist *red; if (safi == SAFI_ENCAP && !(bgp->rfapi_cfg->flags & BGP_VNC_CONFIG_ADV_UN_METHOD_ENCAP)) { /* * Encap mode not enabled. UN addresses will be communicated * via VNC Tunnel subtlv instead. */ vnc_zlog_debug_verbose( "%s: encap mode not enabled, not adding SAFI_ENCAP route", __func__); return; } #if 0 /* unused? */ if ((safi == SAFI_MPLS_VPN) && (flags & RFAPI_AHR_SET_PFX_TO_NEXTHOP)) { if (rfapiRaddr2Qprefix (nexthop, &pfx_buf)) { vnc_zlog_debug_verbose ("%s: can't set pfx to vn addr, not adding SAFI_MPLS_VPN route", __func__); return; } p = &pfx_buf; } #endif for (vo = options_vn; vo; vo = vo->next) { if (RFAPI_VN_OPTION_TYPE_L2ADDR == vo->type) { l2o = &vo->v.l2addr; if (RFAPI_0_ETHERADDR(&l2o->macaddr)) l2o = NULL; /* not MAC resolution */ } if (RFAPI_VN_OPTION_TYPE_LOCAL_NEXTHOP == vo->type) { lnh = &vo->v.local_nexthop; } } if (label) label_val = *label; else label_val = MPLS_LABEL_IMPLICIT_NULL; prefix_rd2str(prd, buf2, sizeof(buf2)); afi = family2afi(p->family); assert(afi == AFI_IP || afi == AFI_IP6); vnc_zlog_debug_verbose("%s: afi=%s, safi=%s", __func__, afi2str(afi), safi2str(safi)); /* Make default attribute. Produces already-interned attr.aspath */ /* Cripes, the memory management of attributes is byzantine */ bgp_attr_default_set(&attr, BGP_ORIGIN_INCOMPLETE); /* * At this point: * attr: static * extra: dynamically allocated, owned by attr * aspath: points to interned hash from aspath hash table */ /* * Route-specific un_options get added to the VPN SAFI * advertisement tunnel encap attribute. (the per-NVE * "default" un_options are put into the 1-per-NVE ENCAP * SAFI advertisement). The VPN SAFI also gets the * default un_options if there are no route-specific options. */ if (options_un) { struct rfapi_un_option *uo; for (uo = options_un; uo; uo = uo->next) { if (RFAPI_UN_OPTION_TYPE_TUNNELTYPE == uo->type) { TunnelType = rfapi_tunneltype_option_to_tlv( bgp, un_addr, &uo->v.tunnel, &attr, l2o != NULL); } } } else { /* * Add encap attr * These are the NVE-specific "default" un_options which are * put into the 1-per-NVE ENCAP advertisement. */ if (rfd->default_tunneltype_option.type) { TunnelType = rfapi_tunneltype_option_to_tlv( bgp, un_addr, &rfd->default_tunneltype_option, &attr, l2o != NULL); } else /* create default for local addse */ if (type == ZEBRA_ROUTE_BGP && sub_type == BGP_ROUTE_RFP) TunnelType = rfapi_tunneltype_option_to_tlv( bgp, un_addr, NULL, &attr, l2o != NULL); } if (TunnelType == BGP_ENCAP_TYPE_MPLS) { if (safi == SAFI_ENCAP) { /* Encap SAFI not used with MPLS */ vnc_zlog_debug_verbose( "%s: mpls tunnel type, encap safi omitted", __func__); aspath_unintern(&attr.aspath); /* Unintern original. */ return; } } if (local_pref) { attr.local_pref = *local_pref; attr.flag |= ATTR_FLAG_BIT(BGP_ATTR_LOCAL_PREF); } if (med) { attr.med = *med; attr.flag |= ATTR_FLAG_BIT(BGP_ATTR_MULTI_EXIT_DISC); } /* override default weight assigned by bgp_attr_default_set() */ attr.weight = rfd->peer ? rfd->peer->weight[afi][safi] : 0; /* * NB: ticket 81: do not reset attr.aspath here because it would * cause iBGP peers to drop route */ /* * Set originator ID for routes imported from BGP directly. * These routes could be synthetic, and therefore could * reuse the peer pointers of the routes they are derived * from. Setting the originator ID to "us" prevents the * wrong originator ID from being sent when this route is * sent from a route reflector. */ if (type == ZEBRA_ROUTE_BGP_DIRECT || type == ZEBRA_ROUTE_BGP_DIRECT_EXT) { attr.flag |= ATTR_FLAG_BIT(BGP_ATTR_ORIGINATOR_ID); attr.originator_id = bgp->router_id; } /* Set up vnc attribute (sub-tlv for Prefix Lifetime) */ if (lifetime && *lifetime != RFAPI_INFINITE_LIFETIME) { uint32_t lt; encaptlv = XCALLOC(MTYPE_ENCAP_TLV, sizeof(struct bgp_attr_encap_subtlv) + 4); assert(encaptlv); encaptlv->type = BGP_VNC_SUBTLV_TYPE_LIFETIME; /* prefix lifetime */ encaptlv->length = 4; lt = htonl(*lifetime); memcpy(encaptlv->value, <, 4); attr.vnc_subtlvs = encaptlv; vnc_zlog_debug_verbose( "%s: set Encap Attr Prefix Lifetime to %d", __func__, *lifetime); } /* add rfp options to vnc attr */ if (rfp_options) { if (flags & RFAPI_AHR_RFPOPT_IS_VNCTLV) { /* * this flag means we're passing a pointer to an * existing encap tlv chain which we should copy. * It's a hack to avoid adding yet another argument * to add_vnc_route() */ encaptlv = encap_tlv_dup( (struct bgp_attr_encap_subtlv *)rfp_options); if (attr.vnc_subtlvs) { attr.vnc_subtlvs->next = encaptlv; } else { attr.vnc_subtlvs = encaptlv; } } else { struct bgp_tea_options *hop; /* XXX max of one tlv present so far from above code */ struct bgp_attr_encap_subtlv *tail = attr.vnc_subtlvs; for (hop = rfp_options; hop; hop = hop->next) { /* * Construct subtlv */ encaptlv = XCALLOC( MTYPE_ENCAP_TLV, sizeof(struct bgp_attr_encap_subtlv) + 2 + hop->length); assert(encaptlv); encaptlv->type = BGP_VNC_SUBTLV_TYPE_RFPOPTION; /* RFP option */ encaptlv->length = 2 + hop->length; *((uint8_t *)(encaptlv->value) + 0) = hop->type; *((uint8_t *)(encaptlv->value) + 1) = hop->length; memcpy(((uint8_t *)encaptlv->value) + 2, hop->value, hop->length); /* * add to end of subtlv chain */ if (tail) { tail->next = encaptlv; } else { attr.vnc_subtlvs = encaptlv; } tail = encaptlv; } } } /* * At this point: * attr: static * extra: dynamically allocated, owned by attr * vnc_subtlvs: dynamic chain, length 1 * aspath: points to interned hash from aspath hash table */ attr.ecommunity = ecommunity_new(); assert(attr.ecommunity); if (TunnelType != BGP_ENCAP_TYPE_MPLS && TunnelType != BGP_ENCAP_TYPE_RESERVED) { /* * Add BGP Encapsulation Extended Community. Format described in * section 4.5 of RFC 5512. * Always include when not MPLS type, to disambiguate this case. */ struct ecommunity_val beec; memset(&beec, 0, sizeof(beec)); beec.val[0] = ECOMMUNITY_ENCODE_OPAQUE; beec.val[1] = ECOMMUNITY_OPAQUE_SUBTYPE_ENCAP; beec.val[6] = ((TunnelType) >> 8) & 0xff; beec.val[7] = (TunnelType)&0xff; ecommunity_add_val(attr.ecommunity, &beec); } /* * Add extended community attributes to match rt export list */ if (rt_export_list) { attr.ecommunity = ecommunity_merge(attr.ecommunity, rt_export_list); } if (attr.ecommunity->size) { attr.flag |= ATTR_FLAG_BIT(BGP_ATTR_EXT_COMMUNITIES); } else { ecommunity_free(&attr.ecommunity); attr.ecommunity = NULL; } vnc_zlog_debug_verbose("%s: attr.ecommunity=%p", __func__, attr.ecommunity); /* * At this point: * attr: static * extra: dynamically allocated, owned by attr * vnc_subtlvs: dynamic chain, length 1 * ecommunity: dynamic 2-part * aspath: points to interned hash from aspath hash table */ /* stuff nexthop in attr_extra; which field depends on IPv4 or IPv6 */ switch (nexthop->addr_family) { case AF_INET: /* * set this field to prevent bgp_route.c code from setting * mp_nexthop_global_in to self */ attr.nexthop.s_addr = nexthop->addr.v4.s_addr; attr.mp_nexthop_global_in = nexthop->addr.v4; attr.mp_nexthop_len = 4; break; case AF_INET6: attr.mp_nexthop_global = nexthop->addr.v6; attr.mp_nexthop_len = 16; break; default: assert(0); } prefix2str(p, buf, sizeof(buf)); /* * At this point: * * attr: static * extra: dynamically allocated, owned by attr * vnc_subtlvs: dynamic chain, length 1 * ecommunity: dynamic 2-part * aspath: points to interned hash from aspath hash table */ red = bgp_redist_lookup(bgp, afi, type, 0); if (red && red->redist_metric_flag) { attr.med = red->redist_metric; attr.flag |= ATTR_FLAG_BIT(BGP_ATTR_MULTI_EXIT_DISC); } bn = bgp_afi_node_get(bgp->rib[afi][safi], afi, safi, p, prd); /* * bgp_attr_intern creates a new reference to a cached * attribute, but leaves the following bits of trash: * - old attr * - old attr->extra (free via bgp_attr_extra_free(attr)) * * Note that it frees the original attr->extra->ecommunity * but leaves the new attribute pointing to the ORIGINAL * vnc options (which therefore we needn't free from the * static attr) */ new_attr = bgp_attr_intern(&attr); aspath_unintern(&attr.aspath); /* Unintern original. */ /* * At this point: * * attr: static * extra: dynamically allocated, owned by attr * vnc_subtlvs: dynamic chain, length 1 * ecommunity: POINTS TO INTERNED ecom, THIS REF NOT COUNTED * * new_attr: an attr that is part of the hash table, distinct * from attr which is static. * extra: dynamically allocated, owned by new_attr (in hash table) * vnc_subtlvs: POINTS TO SAME dynamic chain AS attr * ecommunity: POINTS TO interned/refcounted dynamic 2-part AS attr * aspath: POINTS TO interned/refcounted hashed block */ for (bpi = bgp_node_get_bgp_path_info(bn); bpi; bpi = bpi->next) { /* probably only need to check * bpi->extra->vnc.export.rfapi_handle */ if (bpi->peer == rfd->peer && bpi->type == type && bpi->sub_type == sub_type && bpi->extra && bpi->extra->vnc.export.rfapi_handle == (void *)rfd) { break; } } if (bpi) { /* * Adding new local_nexthop, which does not by itself change * what is advertised via BGP */ if (lnh) { if (!bpi->extra->vnc.export.local_nexthops) { /* TBD make arrangements to free when needed */ bpi->extra->vnc.export.local_nexthops = list_new(); bpi->extra->vnc.export.local_nexthops->del = rfapi_nexthop_free; } /* * already present? */ struct listnode *node; struct rfapi_nexthop *pLnh = NULL; for (ALL_LIST_ELEMENTS_RO( bpi->extra->vnc.export.local_nexthops, node, pLnh)) { if (prefix_same(&pLnh->addr, &lnh->addr)) { break; } } /* * Not present, add new one */ if (!pLnh) { pLnh = rfapi_nexthop_new(lnh); listnode_add( bpi->extra->vnc.export.local_nexthops, pLnh); } } if (attrhash_cmp(bpi->attr, new_attr) && !CHECK_FLAG(bpi->flags, BGP_PATH_REMOVED)) { bgp_attr_unintern(&new_attr); bgp_unlock_node(bn); vnc_zlog_debug_any( "%s: Found route (safi=%d) at prefix %s, no change", __func__, safi, buf); goto done; } else { /* The attribute is changed. */ bgp_path_info_set_flag(bn, bpi, BGP_PATH_ATTR_CHANGED); if (safi == SAFI_MPLS_VPN) { struct bgp_node *prn = NULL; struct bgp_table *table = NULL; prn = bgp_node_get(bgp->rib[afi][safi], (struct prefix *)prd); table = bgp_node_get_bgp_table_info(prn); if (table) vnc_import_bgp_del_vnc_host_route_mode_resolve_nve( bgp, prd, table, p, bpi); bgp_unlock_node(prn); } /* Rewrite BGP route information. */ if (CHECK_FLAG(bpi->flags, BGP_PATH_REMOVED)) bgp_path_info_restore(bn, bpi); else bgp_aggregate_decrement(bgp, p, bpi, afi, safi); bgp_attr_unintern(&bpi->attr); bpi->attr = new_attr; bpi->uptime = bgp_clock(); if (safi == SAFI_MPLS_VPN) { struct bgp_node *prn = NULL; struct bgp_table *table = NULL; prn = bgp_node_get(bgp->rib[afi][safi], (struct prefix *)prd); table = bgp_node_get_bgp_table_info(prn); if (table) vnc_import_bgp_add_vnc_host_route_mode_resolve_nve( bgp, prd, table, p, bpi); bgp_unlock_node(prn); } /* Process change. */ bgp_aggregate_increment(bgp, p, bpi, afi, safi); bgp_process(bgp, bn, afi, safi); bgp_unlock_node(bn); vnc_zlog_debug_any( "%s: Found route (safi=%d) at prefix %s, changed attr", __func__, safi, buf); goto done; } } new = info_make(type, sub_type, 0, rfd->peer, new_attr, NULL); SET_FLAG(new->flags, BGP_PATH_VALID); /* save backref to rfapi handle */ assert(bgp_path_info_extra_get(new)); new->extra->vnc.export.rfapi_handle = (void *)rfd; encode_label(label_val, &new->extra->label[0]); /* debug */ if (VNC_DEBUG(VERBOSE)) { vnc_zlog_debug_verbose("%s: printing BPI", __func__); rfapiPrintBi(NULL, new); } bgp_aggregate_increment(bgp, p, new, afi, safi); bgp_path_info_add(bn, new); if (safi == SAFI_MPLS_VPN) { struct bgp_node *prn = NULL; struct bgp_table *table = NULL; prn = bgp_node_get(bgp->rib[afi][safi], (struct prefix *)prd); table = bgp_node_get_bgp_table_info(prn); if (table) vnc_import_bgp_add_vnc_host_route_mode_resolve_nve( bgp, prd, table, p, new); bgp_unlock_node(prn); encode_label(label_val, &bn->local_label); } bgp_unlock_node(bn); bgp_process(bgp, bn, afi, safi); vnc_zlog_debug_any( "%s: Added route (safi=%s) at prefix %s (bn=%p, prd=%s)", __func__, safi2str(safi), buf, bn, buf2); done: /* Loop back to import tables */ rfapiProcessUpdate(rfd->peer, rfd, p, prd, new_attr, afi, safi, type, sub_type, &label_val); vnc_zlog_debug_verbose("%s: looped back import route (safi=%d)", __func__, safi); } uint32_t rfp_cost_to_localpref(uint8_t cost) { return 255 - cost; } static void rfapiTunnelRouteAnnounce(struct bgp *bgp, struct rfapi_descriptor *rfd, uint32_t *pLifetime) { struct prefix_rd prd; struct prefix pfx_vn; int rc; uint32_t local_pref = rfp_cost_to_localpref(0); rc = rfapiRaddr2Qprefix(&(rfd->vn_addr), &pfx_vn); assert(!rc); /* * Construct route distinguisher = 0 */ memset(&prd, 0, sizeof(prd)); prd.family = AF_UNSPEC; prd.prefixlen = 64; add_vnc_route(rfd, /* rfapi descr, for export list & backref */ bgp, /* which bgp instance */ SAFI_ENCAP, /* which SAFI */ &pfx_vn, /* prefix to advertise */ &prd, /* route distinguisher to use */ &rfd->un_addr, /* nexthop */ &local_pref, pLifetime, /* max lifetime of child VPN routes */ NULL, /* no rfp options for ENCAP safi */ NULL, /* rfp un options */ NULL, /* rfp vn options */ rfd->rt_export_list, NULL, /* med */ NULL, /* label: default */ ZEBRA_ROUTE_BGP, BGP_ROUTE_RFP, 0); } /*********************************************************************** * RFP processing behavior configuration ***********************************************************************/ /*------------------------------------------ * rfapi_rfp_set_configuration * * This is used to change rfapi's processing behavior based on * RFP requirements. * * input: * rfp_start_val value returned by rfp_start * rfapi_rfp_cfg Pointer to configuration structure * * output: * none * * return value: * 0 Success * ENXIO Unabled to locate configured BGP/VNC --------------------------------------------*/ int rfapi_rfp_set_configuration(void *rfp_start_val, struct rfapi_rfp_cfg *new) { struct rfapi_rfp_cfg *rcfg; struct bgp *bgp; bgp = rfapi_bgp_lookup_by_rfp(rfp_start_val); if (!new || !bgp || !bgp->rfapi_cfg) return ENXIO; rcfg = &bgp->rfapi_cfg->rfp_cfg; rcfg->download_type = new->download_type; rcfg->ftd_advertisement_interval = new->ftd_advertisement_interval; rcfg->holddown_factor = new->holddown_factor; if (rcfg->use_updated_response != new->use_updated_response) { rcfg->use_updated_response = new->use_updated_response; if (rcfg->use_updated_response) rfapiMonitorCallbacksOn(bgp); else rfapiMonitorCallbacksOff(bgp); } if (rcfg->use_removes != new->use_removes) { rcfg->use_removes = new->use_removes; if (rcfg->use_removes) rfapiMonitorResponseRemovalOn(bgp); else rfapiMonitorResponseRemovalOff(bgp); } return 0; } /*------------------------------------------ * rfapi_rfp_set_cb_methods * * Change registered callback functions for asynchronous notifications * from RFAPI to the RFP client. * * input: * rfp_start_val value returned by rfp_start * methods Pointer to struct rfapi_rfp_cb_methods containing * pointers to callback methods as described above * * return value: * 0 Success * ENXIO BGP or VNC not configured *------------------------------------------*/ int rfapi_rfp_set_cb_methods(void *rfp_start_val, struct rfapi_rfp_cb_methods *methods) { struct rfapi *h; struct bgp *bgp; bgp = rfapi_bgp_lookup_by_rfp(rfp_start_val); if (!bgp) return ENXIO; h = bgp->rfapi; if (!h) return ENXIO; h->rfp_methods = *methods; return 0; } /*********************************************************************** * NVE Sessions ***********************************************************************/ /* * Caller must supply an already-allocated rfd with the "caller" * fields already set (vn_addr, un_addr, callback, cookie) * The advertised_prefixes[] array elements should be NULL to * have this function set them to newly-allocated radix trees. */ static int rfapi_open_inner(struct rfapi_descriptor *rfd, struct bgp *bgp, struct rfapi *h, struct rfapi_nve_group_cfg *rfg) { int ret; if (h->flags & RFAPI_INCALLBACK) return EDEADLK; /* * Fill in configured fields */ /* * If group's RD is specified as "auto", then fill in based * on NVE's VN address */ rfd->rd = rfg->rd; if (rfd->rd.family == AF_UNIX) { ret = rfapi_set_autord_from_vn(&rfd->rd, &rfd->vn_addr); if (ret != 0) return ret; } rfd->rt_export_list = (rfg->rt_export_list) ? ecommunity_dup(rfg->rt_export_list) : NULL; rfd->response_lifetime = rfg->response_lifetime; rfd->rfg = rfg; /* * Fill in BGP peer structure */ rfd->peer = peer_new(bgp); rfd->peer->status = Established; /* keep bgp core happy */ bgp_sync_delete(rfd->peer); /* don't need these */ /* * since this peer is not on the I/O thread, this lock is not strictly * necessary, but serves as a reminder to those who may meddle... */ frr_with_mutex(&rfd->peer->io_mtx) { // we don't need any I/O related facilities if (rfd->peer->ibuf) stream_fifo_free(rfd->peer->ibuf); if (rfd->peer->obuf) stream_fifo_free(rfd->peer->obuf); if (rfd->peer->ibuf_work) ringbuf_del(rfd->peer->ibuf_work); if (rfd->peer->obuf_work) stream_free(rfd->peer->obuf_work); rfd->peer->ibuf = NULL; rfd->peer->obuf = NULL; rfd->peer->obuf_work = NULL; rfd->peer->ibuf_work = NULL; } { /* base code assumes have valid host pointer */ char buf[BUFSIZ]; buf[0] = 0; if (rfd->vn_addr.addr_family == AF_INET) { inet_ntop(AF_INET, &rfd->vn_addr.addr.v4, buf, BUFSIZ); } else if (rfd->vn_addr.addr_family == AF_INET6) { inet_ntop(AF_INET6, &rfd->vn_addr.addr.v6, buf, BUFSIZ); } rfd->peer->host = XSTRDUP(MTYPE_BGP_PEER_HOST, buf); } /* Mark peer as belonging to HD */ SET_FLAG(rfd->peer->flags, PEER_FLAG_IS_RFAPI_HD); /* * Set min prefix lifetime to max value so it will get set * upon first rfapi_register() */ rfd->min_prefix_lifetime = UINT32_MAX; /* * Allocate response tables if needed */ #define RFD_RTINIT_AFI(rh, ary, afi) \ do { \ if (!ary[afi]) { \ ary[afi] = agg_table_init(); \ agg_set_table_info(ary[afi], rh); \ } \ } while (0) #define RFD_RTINIT(rh, ary) \ do { \ RFD_RTINIT_AFI(rh, ary, AFI_IP); \ RFD_RTINIT_AFI(rh, ary, AFI_IP6); \ RFD_RTINIT_AFI(rh, ary, AFI_L2VPN); \ } while (0) RFD_RTINIT(rfd, rfd->rib); RFD_RTINIT(rfd, rfd->rib_pending); RFD_RTINIT(rfd, rfd->rsp_times); /* * Link to Import Table */ rfd->import_table = rfg->rfapi_import_table; rfd->import_table->refcount += 1; rfapiApInit(&rfd->advertised); /* * add this NVE descriptor to the list of NVEs in the NVE group */ if (!rfg->nves) { rfg->nves = list_new(); } listnode_add(rfg->nves, rfd); vnc_direct_bgp_add_nve(bgp, rfd); vnc_zebra_add_nve(bgp, rfd); return 0; } /* moved from rfapi_register */ int rfapi_init_and_open(struct bgp *bgp, struct rfapi_descriptor *rfd, struct rfapi_nve_group_cfg *rfg) { struct rfapi *h = bgp->rfapi; char buf_vn[BUFSIZ]; char buf_un[BUFSIZ]; afi_t afi_vn, afi_un; struct prefix pfx_un; struct agg_node *rn; rfapi_time(&rfd->open_time); if (rfg->type == RFAPI_GROUP_CFG_VRF) SET_FLAG(rfd->flags, RFAPI_HD_FLAG_IS_VRF); rfapiRfapiIpAddr2Str(&rfd->vn_addr, buf_vn, BUFSIZ); rfapiRfapiIpAddr2Str(&rfd->un_addr, buf_un, BUFSIZ); vnc_zlog_debug_verbose("%s: new RFD with VN=%s UN=%s cookie=%p", __func__, buf_vn, buf_un, rfd->cookie); if (rfg->type != RFAPI_GROUP_CFG_VRF) /* unclear if needed for VRF */ { listnode_add(&h->descriptors, rfd); if (h->descriptors.count > h->stat.max_descriptors) { h->stat.max_descriptors = h->descriptors.count; } /* * attach to UN radix tree */ afi_vn = family2afi(rfd->vn_addr.addr_family); afi_un = family2afi(rfd->un_addr.addr_family); assert(afi_vn && afi_un); assert(!rfapiRaddr2Qprefix(&rfd->un_addr, &pfx_un)); rn = agg_node_get(h->un[afi_un], &pfx_un); assert(rn); rfd->next = rn->info; rn->info = rfd; rfd->un_node = rn; } return rfapi_open_inner(rfd, bgp, h, rfg); } struct rfapi_vn_option *rfapiVnOptionsDup(struct rfapi_vn_option *orig) { struct rfapi_vn_option *head = NULL; struct rfapi_vn_option *tail = NULL; struct rfapi_vn_option *vo = NULL; for (vo = orig; vo; vo = vo->next) { struct rfapi_vn_option *new; new = XCALLOC(MTYPE_RFAPI_VN_OPTION, sizeof(struct rfapi_vn_option)); memcpy(new, vo, sizeof(struct rfapi_vn_option)); new->next = NULL; if (tail) { tail->next = new; } else { head = tail = new; } } return head; } struct rfapi_un_option *rfapiUnOptionsDup(struct rfapi_un_option *orig) { struct rfapi_un_option *head = NULL; struct rfapi_un_option *tail = NULL; struct rfapi_un_option *uo = NULL; for (uo = orig; uo; uo = uo->next) { struct rfapi_un_option *new; new = XCALLOC(MTYPE_RFAPI_UN_OPTION, sizeof(struct rfapi_un_option)); memcpy(new, uo, sizeof(struct rfapi_un_option)); new->next = NULL; if (tail) { tail->next = new; } else { head = tail = new; } } return head; } struct bgp_tea_options *rfapiOptionsDup(struct bgp_tea_options *orig) { struct bgp_tea_options *head = NULL; struct bgp_tea_options *tail = NULL; struct bgp_tea_options *hop = NULL; for (hop = orig; hop; hop = hop->next) { struct bgp_tea_options *new; new = XCALLOC(MTYPE_BGP_TEA_OPTIONS, sizeof(struct bgp_tea_options)); memcpy(new, hop, sizeof(struct bgp_tea_options)); new->next = NULL; if (hop->value) { new->value = XCALLOC(MTYPE_BGP_TEA_OPTIONS_VALUE, hop->length); memcpy(new->value, hop->value, hop->length); } if (tail) { tail->next = new; } else { head = tail = new; } } return head; } void rfapiFreeBgpTeaOptionChain(struct bgp_tea_options *p) { struct bgp_tea_options *next; while (p) { next = p->next; if (p->value) { XFREE(MTYPE_BGP_TEA_OPTIONS_VALUE, p->value); p->value = NULL; } XFREE(MTYPE_BGP_TEA_OPTIONS, p); p = next; } } void rfapiAdbFree(struct rfapi_adb *adb) { XFREE(MTYPE_RFAPI_ADB, adb); } static int rfapi_query_inner(void *handle, struct rfapi_ip_addr *target, struct rfapi_l2address_option *l2o, /* may be NULL */ struct rfapi_next_hop_entry **ppNextHopEntry) { afi_t afi; struct prefix p; struct prefix p_original; struct agg_node *rn; struct rfapi_descriptor *rfd = (struct rfapi_descriptor *)handle; struct bgp *bgp = rfd->bgp; struct rfapi_next_hop_entry *pNHE = NULL; struct rfapi_ip_addr *self_vn_addr = NULL; int eth_is_0 = 0; int use_eth_resolution = 0; struct rfapi_next_hop_entry *i_nhe; /* preemptive */ if (!bgp) { vnc_zlog_debug_verbose("%s: No BGP instance, returning ENXIO", __func__); return ENXIO; } if (!bgp->rfapi) { vnc_zlog_debug_verbose("%s: No RFAPI instance, returning ENXIO", __func__); return ENXIO; } if (bgp->rfapi->flags & RFAPI_INCALLBACK) { vnc_zlog_debug_verbose( "%s: Called during calback, returning EDEADLK", __func__); return EDEADLK; } if (!is_valid_rfd(rfd)) { vnc_zlog_debug_verbose("%s: invalid handle, returning EBADF", __func__); return EBADF; } rfd->rsp_counter++; /* dedup: identify this generation */ rfd->rsp_time = rfapi_time(NULL); /* response content dedup */ rfd->ftd_last_allowed_time = bgp_clock() - bgp->rfapi_cfg->rfp_cfg.ftd_advertisement_interval; if (l2o) { if (!memcmp(l2o->macaddr.octet, rfapi_ethaddr0.octet, ETH_ALEN)) { eth_is_0 = 1; } /* per t/c Paul/Lou 151022 */ if (!eth_is_0 || l2o->logical_net_id) { use_eth_resolution = 1; } } if (ppNextHopEntry) *ppNextHopEntry = NULL; /* * Save original target in prefix form. In case of L2-based queries, * p_original will be modified to reflect the L2 target */ assert(!rfapiRaddr2Qprefix(target, &p_original)); if (bgp->rfapi_cfg->rfp_cfg.download_type == RFAPI_RFP_DOWNLOAD_FULL) { /* convert query to 0/0 when full-table download is enabled */ memset((char *)&p, 0, sizeof(p)); p.family = target->addr_family; } else { p = p_original; } { char buf[PREFIX_STRLEN]; char *s; prefix2str(&p, buf, sizeof(buf)); vnc_zlog_debug_verbose("%s(rfd=%p, target=%s, ppNextHop=%p)", __func__, rfd, buf, ppNextHopEntry); s = ecommunity_ecom2str(rfd->import_table->rt_import_list, ECOMMUNITY_FORMAT_ROUTE_MAP, 0); vnc_zlog_debug_verbose( "%s rfd->import_table=%p, rfd->import_table->rt_import_list: %s", __func__, rfd->import_table, s); XFREE(MTYPE_ECOMMUNITY_STR, s); } afi = family2afi(p.family); assert(afi); if (CHECK_FLAG(bgp->rfapi_cfg->flags, BGP_VNC_CONFIG_FILTER_SELF_FROM_RSP)) { self_vn_addr = &rfd->vn_addr; } if (use_eth_resolution) { uint32_t logical_net_id = l2o->logical_net_id; struct ecommunity *l2com; /* * fix up p_original to contain L2 address */ rfapiL2o2Qprefix(l2o, &p_original); l2com = bgp_rfapi_get_ecommunity_by_lni_label( bgp, 1, logical_net_id, l2o->label); if (l2com) { uint8_t *v = l2com->val; logical_net_id = (v[5] << 16) + (v[6] << 8) + (v[7]); } /* * Ethernet/L2-based lookup * * Always returns IT node corresponding to route */ if (RFAPI_RFP_DOWNLOAD_FULL == bgp->rfapi_cfg->rfp_cfg.download_type) { eth_is_0 = 1; } rn = rfapiMonitorEthAdd( bgp, rfd, (eth_is_0 ? &rfapi_ethaddr0 : &l2o->macaddr), logical_net_id); if (eth_is_0) { struct rfapi_ip_prefix rprefix; memset(&rprefix, 0, sizeof(rprefix)); rprefix.prefix.addr_family = target->addr_family; if (target->addr_family == AF_INET) { rprefix.length = 32; } else { rprefix.length = 128; } pNHE = rfapiEthRouteTable2NextHopList( logical_net_id, &rprefix, rfd->response_lifetime, self_vn_addr, rfd->rib[afi], &p_original); goto done; } } else { /* * IP-based lookup */ rn = rfapiMonitorAdd(bgp, rfd, &p); /* * If target address is 0, this request is special: means to * return ALL routes in the table * * Monitors for All-Routes queries get put on a special list, * not in the VPN tree */ if (RFAPI_0_PREFIX(&p)) { vnc_zlog_debug_verbose("%s: 0-prefix", __func__); /* * Generate nexthop list for caller */ pNHE = rfapiRouteTable2NextHopList( rfd->import_table->imported_vpn[afi], rfd->response_lifetime, self_vn_addr, rfd->rib[afi], &p_original); goto done; } if (rn) { agg_lock_node(rn); /* so we can unlock below */ } else { /* * returns locked node. Don't unlock yet because the * unlock * might free it before we're done with it. This * situation * could occur when rfapiMonitorGetAttachNode() returns * a * newly-created default node. */ rn = rfapiMonitorGetAttachNode(rfd, &p); } } assert(rn); if (!rn->info) { agg_unlock_node(rn); vnc_zlog_debug_verbose( "%s: VPN route not found, returning ENOENT", __func__); return ENOENT; } if (VNC_DEBUG(RFAPI_QUERY)) { rfapiShowImportTable(NULL, "query", rfd->import_table->imported_vpn[afi], 1); } if (use_eth_resolution) { struct rfapi_ip_prefix rprefix; memset(&rprefix, 0, sizeof(rprefix)); rprefix.prefix.addr_family = target->addr_family; if (target->addr_family == AF_INET) { rprefix.length = 32; } else { rprefix.length = 128; } pNHE = rfapiEthRouteNode2NextHopList( rn, &rprefix, rfd->response_lifetime, self_vn_addr, rfd->rib[afi], &p_original); } else { /* * Generate answer to query */ pNHE = rfapiRouteNode2NextHopList(rn, rfd->response_lifetime, self_vn_addr, rfd->rib[afi], &p_original); } agg_unlock_node(rn); done: if (ppNextHopEntry) { /* only count if caller gets it */ ++bgp->rfapi->response_immediate_count; } if (!pNHE) { vnc_zlog_debug_verbose("%s: NO NHEs, returning ENOENT", __func__); return ENOENT; } /* * count nexthops for statistics */ for (i_nhe = pNHE; i_nhe; i_nhe = i_nhe->next) { ++rfd->stat_count_nh_reachable; } if (ppNextHopEntry) { *ppNextHopEntry = pNHE; } else { rfapi_free_next_hop_list(pNHE); } vnc_zlog_debug_verbose("%s: success", __func__); return 0; } /* * support on-the-fly reassignment of an already-open nve to a new * nve-group in the event that its original nve-group is * administratively deleted. */ static int rfapi_open_rfd(struct rfapi_descriptor *rfd, struct bgp *bgp) { struct prefix pfx_vn; struct prefix pfx_un; struct rfapi_nve_group_cfg *rfg; struct rfapi *h; struct rfapi_cfg *hc; int rc; h = bgp->rfapi; if (!h) return ENXIO; hc = bgp->rfapi_cfg; if (!hc) return ENXIO; rc = rfapiRaddr2Qprefix(&rfd->vn_addr, &pfx_vn); assert(!rc); rc = rfapiRaddr2Qprefix(&rfd->un_addr, &pfx_un); assert(!rc); /* * Find the matching nve group config block */ rfg = bgp_rfapi_cfg_match_group(hc, &pfx_vn, &pfx_un); if (!rfg) { return ENOENT; } /* * check nve group config block for required values */ if (!rfg->rt_export_list || !rfg->rfapi_import_table) { return ENOMSG; } rc = rfapi_open_inner(rfd, bgp, h, rfg); if (rc) { return rc; } /* * re-advertise registered routes, this time as part of new NVE-group */ rfapiApReadvertiseAll(bgp, rfd); /* * re-attach callbacks to import table */ if (!(bgp->rfapi_cfg->flags & BGP_VNC_CONFIG_CALLBACK_DISABLE)) { rfapiMonitorAttachImportHd(rfd); } return 0; } /*------------------------------------------ * rfapi_open * * This function initializes a NVE record and associates it with * the specified VN and underlay network addresses * * input: * rfp_start_val value returned by rfp_start * vn NVE virtual network address * * un NVE underlay network address * * default_options Default options to use on registrations. * For now only tunnel type is supported. * May be overridden per-prefix in rfapi_register(). * Caller owns (rfapi_open() does not free) * * response_cb Pointer to next hop list update callback function or * NULL when no callbacks are desired. * * userdata Passed to subsequent response_cb invocations. * * output: * response_lifetime The length of time that responses sent to this * NVE are valid. * * pHandle pointer to location to store rfapi handle. The * handle must be passed on subsequent rfapi_ calls. * * * return value: * 0 Success * EEXIST NVE with this {vn,un} already open * ENOENT No matching nve group config * ENOMSG Matched nve group config was incomplete * ENXIO BGP or VNC not configured * EAFNOSUPPORT Matched nve group specifies auto-assignment of RD, * but underlay network address is not IPv4 * EDEADLK Called from within a callback procedure *------------------------------------------*/ int rfapi_open(void *rfp_start_val, struct rfapi_ip_addr *vn, struct rfapi_ip_addr *un, struct rfapi_un_option *default_options, uint32_t *response_lifetime, void *userdata, /* callback cookie */ rfapi_handle *pHandle) { struct bgp *bgp; struct rfapi *h; struct rfapi_descriptor *rfd; struct rfapi_cfg *hc; struct rfapi_nve_group_cfg *rfg; struct prefix pfx_vn; struct prefix pfx_un; int rc; rfapi_handle hh = NULL; int reusing_provisional = 0; { char buf[2][INET_ADDRSTRLEN]; vnc_zlog_debug_verbose( "%s: VN=%s UN=%s", __func__, rfapiRfapiIpAddr2Str(vn, buf[0], INET_ADDRSTRLEN), rfapiRfapiIpAddr2Str(un, buf[1], INET_ADDRSTRLEN)); } assert(pHandle); *pHandle = NULL; bgp = rfapi_bgp_lookup_by_rfp(rfp_start_val); if (!bgp) return ENXIO; h = bgp->rfapi; if (!h) return ENXIO; hc = bgp->rfapi_cfg; if (!hc) return ENXIO; if (h->flags & RFAPI_INCALLBACK) return EDEADLK; rc = rfapiRaddr2Qprefix(vn, &pfx_vn); assert(!rc); rc = rfapiRaddr2Qprefix(un, &pfx_un); assert(!rc); /* * already have a descriptor with VN and UN? */ if (!rfapi_find_handle(bgp, vn, un, &hh)) { /* * we might have set up a handle for static routes before * this NVE was opened. In that case, reuse the handle */ rfd = hh; if (!CHECK_FLAG(rfd->flags, RFAPI_HD_FLAG_PROVISIONAL)) { return EEXIST; } /* * reuse provisional descriptor * hh is not NULL */ reusing_provisional = 1; } /* * Find the matching nve group config block */ rfg = bgp_rfapi_cfg_match_group(hc, &pfx_vn, &pfx_un); if (!rfg) { ++h->stat.count_unknown_nves; { char buf[2][INET_ADDRSTRLEN]; zlog_notice("%s: no matching group VN=%s UN=%s", __func__, rfapiRfapiIpAddr2Str(vn, buf[0], INET_ADDRSTRLEN), rfapiRfapiIpAddr2Str(un, buf[1], INET_ADDRSTRLEN)); } return ENOENT; } /* * check nve group config block for required values */ if (!rfg->rt_export_list || !rfg->rfapi_import_table) { ++h->stat.count_unknown_nves; return ENOMSG; } /* * If group config specifies auto-rd assignment, check that * VN address is IPv4|v6 so we don't fail in rfapi_open_inner(). * Check here so we don't need to unwind memory allocations, &c. */ if ((rfg->rd.family == AF_UNIX) && (vn->addr_family != AF_INET) && (vn->addr_family != AF_INET6)) { return EAFNOSUPPORT; } if (hh) { /* * reusing provisional rfd */ rfd = hh; } else { rfd = XCALLOC(MTYPE_RFAPI_DESC, sizeof(struct rfapi_descriptor)); } assert(rfd); rfd->bgp = bgp; if (default_options) { struct rfapi_un_option *p; for (p = default_options; p; p = p->next) { if ((RFAPI_UN_OPTION_TYPE_PROVISIONAL == p->type)) { rfd->flags |= RFAPI_HD_FLAG_PROVISIONAL; } if ((RFAPI_UN_OPTION_TYPE_TUNNELTYPE == p->type)) { rfd->default_tunneltype_option = p->v.tunnel; } } } /* * Fill in caller fields */ rfd->vn_addr = *vn; rfd->un_addr = *un; rfd->cookie = userdata; if (!reusing_provisional) { rc = rfapi_init_and_open(bgp, rfd, rfg); /* * This can fail only if the VN address is IPv6 and the group * specified auto-assignment of RDs, which only works for v4, * and the check above should catch it. * * Another failure possibility is that we were called * during an rfapi callback. Also checked above. */ assert(!rc); } if (response_lifetime) *response_lifetime = rfd->response_lifetime; *pHandle = rfd; return 0; } /* * For use with debug functions */ static int rfapi_set_response_cb(struct rfapi_descriptor *rfd, rfapi_response_cb_t *response_cb) { if (!is_valid_rfd(rfd)) return EBADF; rfd->response_cb = response_cb; return 0; } /* * rfapi_close_inner * * Does almost all the work of rfapi_close, except: * 1. preserves the descriptor (doesn't free it) * 2. preserves the prefix query list (i.e., rfd->mon list) * 3. preserves the advertised prefix list (rfd->advertised) * 4. preserves the rib and rib_pending tables * * The purpose of organizing it this way is to support on-the-fly * reassignment of an already-open nve to a new nve-group in the * event that its original nve-group is administratively deleted. */ static int rfapi_close_inner(struct rfapi_descriptor *rfd, struct bgp *bgp) { int rc; struct prefix pfx_vn; struct prefix_rd prd; /* currently always 0 for VN->UN */ if (!is_valid_rfd(rfd)) return EBADF; rc = rfapiRaddr2Qprefix(&rfd->vn_addr, &pfx_vn); assert(!rc); /* should never have bad AF in stored vn address */ /* * update exported routes to reflect disappearance of this NVE as * nexthop */ vnc_direct_bgp_del_nve(bgp, rfd); vnc_zebra_del_nve(bgp, rfd); /* * unlink this HD's monitors from import table */ rfapiMonitorDetachImportHd(rfd); /* * Unlink from Import Table * NB rfd->import_table will be NULL if we are closing a stale * descriptor */ if (rfd->import_table) rfapiImportTableRefDelByIt(bgp, rfd->import_table); rfd->import_table = NULL; /* * Construct route distinguisher */ memset(&prd, 0, sizeof(prd)); prd = rfd->rd; prd.family = AF_UNSPEC; prd.prefixlen = 64; /* * withdraw tunnel */ del_vnc_route(rfd, rfd->peer, bgp, SAFI_ENCAP, &pfx_vn, /* prefix being advertised */ &prd, /* route distinguisher to use (0 for ENCAP) */ ZEBRA_ROUTE_BGP, BGP_ROUTE_RFP, NULL, 0); /* no kill */ /* * Construct route distinguisher for VPN routes */ prd = rfd->rd; prd.family = AF_UNSPEC; prd.prefixlen = 64; /* * find all VPN routes associated with this rfd and delete them, too */ rfapiApWithdrawAll(bgp, rfd); /* * remove this nve descriptor from the list of nves * associated with the nve group */ if (rfd->rfg) { listnode_delete(rfd->rfg->nves, rfd); rfd->rfg = NULL; /* XXX mark as orphaned/stale */ } if (rfd->rt_export_list) ecommunity_free(&rfd->rt_export_list); rfd->rt_export_list = NULL; /* * free peer structure (possibly delayed until its * refcount reaches zero) */ if (rfd->peer) { vnc_zlog_debug_verbose("%s: calling peer_delete(%p), #%d", __func__, rfd->peer, rfd->peer->lock); peer_delete(rfd->peer); } rfd->peer = NULL; return 0; } int rfapi_close(void *handle) { struct rfapi_descriptor *rfd = (struct rfapi_descriptor *)handle; int rc; struct agg_node *node; struct bgp *bgp; struct rfapi *h; vnc_zlog_debug_verbose("%s: rfd=%p", __func__, rfd); #if RFAPI_WHO_IS_CALLING_ME #ifdef HAVE_GLIBC_BACKTRACE #define RFAPI_DEBUG_BACKTRACE_NENTRIES 5 { void *buf[RFAPI_DEBUG_BACKTRACE_NENTRIES]; char **syms; int i; size_t size; size = backtrace(buf, RFAPI_DEBUG_BACKTRACE_NENTRIES); syms = backtrace_symbols(buf, size); for (i = 0; i < size && i < RFAPI_DEBUG_BACKTRACE_NENTRIES; ++i) { vnc_zlog_debug_verbose("backtrace[%2d]: %s", i, syms[i]); } free(syms); } #endif #endif bgp = rfd->bgp; if (!bgp) return ENXIO; h = bgp->rfapi; if (!h) return ENXIO; if (!is_valid_rfd(rfd)) return EBADF; if (h->flags & RFAPI_INCALLBACK) { /* * Queue these close requests for processing after callback * is finished */ if (!CHECK_FLAG(rfd->flags, RFAPI_HD_FLAG_CLOSING_ADMINISTRATIVELY)) { work_queue_add(h->deferred_close_q, handle); vnc_zlog_debug_verbose( "%s: added handle %p to deferred close queue", __func__, handle); } return 0; } if (CHECK_FLAG(rfd->flags, RFAPI_HD_FLAG_CLOSING_ADMINISTRATIVELY)) { vnc_zlog_debug_verbose("%s administrative close rfd=%p", __func__, rfd); if (h && h->rfp_methods.close_cb) { vnc_zlog_debug_verbose( "%s calling close callback rfd=%p", __func__, rfd); /* * call the callback fairly early so that it can still * lookup un/vn * from handle, etc. * * NB RFAPI_INCALLBACK is tested above, so if we reach * this point * we are not already in the context of a callback. */ h->flags |= RFAPI_INCALLBACK; (*h->rfp_methods.close_cb)(handle, EIDRM); h->flags &= ~RFAPI_INCALLBACK; } } if (rfd->rfg) { /* * Orphaned descriptors have already done this part, so do * only for non-orphaned descriptors. */ if ((rc = rfapi_close_inner(rfd, bgp))) return rc; } /* * Remove descriptor from UN index * (remove from chain at node) */ rc = rfapi_find_node(bgp, &rfd->vn_addr, &rfd->un_addr, &node); if (!rc) { struct rfapi_descriptor *hh; if (node->info == rfd) { node->info = rfd->next; } else { for (hh = node->info; hh; hh = hh->next) { if (hh->next == rfd) { hh->next = rfd->next; break; } } } agg_unlock_node(node); } /* * remove from descriptor list */ listnode_delete(&h->descriptors, rfd); /* * Delete monitor list items and free monitor structures */ (void)rfapiMonitorDelHd(rfd); /* * release advertised prefix data */ rfapiApRelease(&rfd->advertised); /* * Release RFP callback RIB */ rfapiRibFree(rfd); /* * free descriptor */ memset(rfd, 0, sizeof(struct rfapi_descriptor)); XFREE(MTYPE_RFAPI_DESC, rfd); return 0; } /* * Reopen a nve descriptor. If the descriptor's NVE-group * does not exist (e.g., if it has been administratively removed), * reassignment to a new NVE-group is attempted. * * If NVE-group reassignment fails, the descriptor becomes "stale" * (rfd->rfg == NULL implies "stale:). The only permissible API operation * on a stale descriptor is rfapi_close(). Any other rfapi_* API operation * on the descriptor will return ESTALE. * * Reopening a descriptor is a potentially expensive operation, because * it involves withdrawing any routes advertised by the NVE, withdrawing * the NVE's route queries, and then re-adding them all after a new * NVE-group is assigned. There are also possible route-export affects * caused by deleting and then adding the NVE: advertised prefixes * and nexthop lists for exported routes can turn over. */ int rfapi_reopen(struct rfapi_descriptor *rfd, struct bgp *bgp) { struct rfapi *h; int rc; if ((rc = rfapi_close_inner(rfd, bgp))) { return rc; } if ((rc = rfapi_open_rfd(rfd, bgp))) { h = bgp->rfapi; assert(h != NULL && !CHECK_FLAG(h->flags, RFAPI_INCALLBACK)); if (CHECK_FLAG(rfd->flags, RFAPI_HD_FLAG_CLOSING_ADMINISTRATIVELY) && h && h->rfp_methods.close_cb) { /* * NB RFAPI_INCALLBACK is tested above, so if we reach * this point * we are not already in the context of a callback. */ h->flags |= RFAPI_INCALLBACK; (*h->rfp_methods.close_cb)((rfapi_handle)rfd, ESTALE); h->flags &= ~RFAPI_INCALLBACK; } return rc; } return 0; } /*********************************************************************** * NVE Routes ***********************************************************************/ /* * Announce reachability to this prefix via the NVE */ int rfapi_register(void *handle, struct rfapi_ip_prefix *prefix, uint32_t lifetime, /* host byte order */ struct rfapi_un_option *options_un, struct rfapi_vn_option *options_vn, rfapi_register_action action) { struct rfapi_descriptor *rfd = (struct rfapi_descriptor *)handle; struct bgp *bgp; struct prefix p; struct prefix *pfx_ip = NULL; struct prefix_rd prd; afi_t afi; struct prefix pfx_mac_buf; struct prefix *pfx_mac = NULL; struct prefix pfx_vn_buf; const char *action_str = NULL; uint32_t *label = NULL; struct rfapi_vn_option *vo; struct rfapi_l2address_option *l2o = NULL; struct prefix_rd *prd_override = NULL; switch (action) { case RFAPI_REGISTER_ADD: action_str = "add"; break; case RFAPI_REGISTER_WITHDRAW: action_str = "withdraw"; break; case RFAPI_REGISTER_KILL: action_str = "kill"; break; default: assert(0); break; } /* * Inspect VN options */ for (vo = options_vn; vo; vo = vo->next) { if (RFAPI_VN_OPTION_TYPE_L2ADDR == vo->type) { l2o = &vo->v.l2addr; } if (RFAPI_VN_OPTION_TYPE_INTERNAL_RD == vo->type) { prd_override = &vo->v.internal_rd; } } /********************************************************************* * advertise prefix *********************************************************************/ /* * set

based on */ assert(!rfapiRprefix2Qprefix(prefix, &p)); afi = family2afi(prefix->prefix.addr_family); assert(afi); { char buf[PREFIX_STRLEN]; prefix2str(&p, buf, sizeof(buf)); vnc_zlog_debug_verbose( "%s(rfd=%p, pfx=%s, lifetime=%d, opts_un=%p, opts_vn=%p, action=%s)", __func__, rfd, buf, lifetime, options_un, options_vn, action_str); } /* * These tests come after the prefix conversion so that we can * print the prefix in a debug message before failing */ bgp = rfd->bgp; if (!bgp) { vnc_zlog_debug_verbose("%s: no BGP instance: returning ENXIO", __func__); return ENXIO; } if (!bgp->rfapi) { vnc_zlog_debug_verbose("%s: no RFAPI instance: returning ENXIO", __func__); return ENXIO; } if (!rfd->rfg) { if (RFAPI_REGISTER_ADD == action) { ++bgp->rfapi->stat.count_registrations_failed; } vnc_zlog_debug_verbose( "%s: rfd=%p, no RF GRP instance: returning ESTALE", __func__, rfd); return ESTALE; } if (bgp->rfapi->flags & RFAPI_INCALLBACK) { if (RFAPI_REGISTER_ADD == action) { ++bgp->rfapi->stat.count_registrations_failed; } vnc_zlog_debug_verbose("%s: in callback: returning EDEADLK", __func__); return EDEADLK; } if (!is_valid_rfd(rfd)) { if (RFAPI_REGISTER_ADD == action) { ++bgp->rfapi->stat.count_registrations_failed; } vnc_zlog_debug_verbose("%s: invalid handle: returning EBADF", __func__); return EBADF; } /* * Is there a MAC address in this registration? */ if (l2o && !RFAPI_0_ETHERADDR(&l2o->macaddr)) { rfapiL2o2Qprefix(l2o, &pfx_mac_buf); pfx_mac = &pfx_mac_buf; } /* * Is there an IP prefix in this registration? */ if (!(RFAPI_0_PREFIX(&p) && RFAPI_HOST_PREFIX(&p))) { pfx_ip = &p; } else { if (!pfx_mac) { vnc_zlog_debug_verbose( "%s: missing mac addr that is required for host 0 pfx", __func__); if (RFAPI_REGISTER_ADD == action) { ++bgp->rfapi->stat.count_registrations_failed; } return EINVAL; } if (rfapiRaddr2Qprefix(&rfd->vn_addr, &pfx_vn_buf)) { vnc_zlog_debug_verbose( "%s: handle has bad vn_addr: returning EBADF", __func__); if (RFAPI_REGISTER_ADD == action) { ++bgp->rfapi->stat.count_registrations_failed; } return EBADF; } } if (RFAPI_REGISTER_ADD == action) { ++bgp->rfapi->stat.count_registrations; } /* * Figure out if this registration is missing an IP address * * MAC-addr based: * * In RFAPI, we use prefixes in family AF_LINK to store * the MAC addresses. These prefixes are used for the * list of advertised prefixes and in the RFAPI import * tables. * * In BGP proper, we use the prefix matching the NVE's * VN address with a host prefix-length (i.e., 32 or 128). * */ if (l2o && l2o->logical_net_id && RFAPI_0_PREFIX(&p) && RFAPI_HOST_PREFIX(&p)) { rfapiL2o2Qprefix(l2o, &pfx_mac_buf); pfx_mac = &pfx_mac_buf; } /* * Construct route distinguisher */ if (prd_override) { prd = *prd_override; } else { memset(&prd, 0, sizeof(prd)); if (pfx_mac) { prd.family = AF_UNSPEC; prd.prefixlen = 64; encode_rd_type(RD_TYPE_VNC_ETH, prd.val); if (l2o->local_nve_id || !(rfd->rfg->flags & RFAPI_RFG_L2RD)) { /* * If Local NVE ID is specified in message, use * it. * (if no local default configured, also use it * even if 0) */ prd.val[1] = l2o->local_nve_id; } else { if (rfd->rfg->l2rd) { /* * locally-configured literal value */ prd.val[1] = rfd->rfg->l2rd; } else { /* * 0 means auto:vn, which means use LSB * of VN addr */ if (rfd->vn_addr.addr_family == AF_INET) { prd.val[1] = *(((char *)&rfd->vn_addr .addr.v4 .s_addr) + 3); } else { prd.val[1] = *(((char *)&rfd->vn_addr .addr.v6 .s6_addr) + 15); } } } memcpy(prd.val + 2, pfx_mac->u.prefix_eth.octet, 6); } else { prd = rfd->rd; prd.family = AF_UNSPEC; prd.prefixlen = 64; } } if (action == RFAPI_REGISTER_WITHDRAW || action == RFAPI_REGISTER_KILL) { int adv_tunnel = 0; /* * withdraw previous advertisement */ del_vnc_route( rfd, rfd->peer, bgp, SAFI_MPLS_VPN, pfx_ip ? pfx_ip : &pfx_vn_buf, /* prefix being advertised */ &prd, /* route distinguisher (0 for ENCAP) */ ZEBRA_ROUTE_BGP, BGP_ROUTE_RFP, NULL, action == RFAPI_REGISTER_KILL); if (0 == rfapiApDelete(bgp, rfd, &p, pfx_mac, &prd, &adv_tunnel)) { if (adv_tunnel) rfapiTunnelRouteAnnounce( bgp, rfd, &rfd->max_prefix_lifetime); } } else { int adv_tunnel = 0; uint32_t local_pref; struct ecommunity *rtlist = NULL; struct ecommunity_val ecom_value; if (!rfapiApCount(rfd)) { /* * make sure we advertise tunnel route upon adding the * first VPN route */ adv_tunnel = 1; } if (rfapiApAdd(bgp, rfd, &p, pfx_mac, &prd, lifetime, prefix->cost, l2o)) { adv_tunnel = 1; } vnc_zlog_debug_verbose("%s: adv_tunnel = %d", __func__, adv_tunnel); if (adv_tunnel) { vnc_zlog_debug_verbose("%s: announcing tunnel route", __func__); rfapiTunnelRouteAnnounce(bgp, rfd, &rfd->max_prefix_lifetime); } vnc_zlog_debug_verbose("%s: calling add_vnc_route", __func__); local_pref = rfp_cost_to_localpref(prefix->cost); if (l2o && l2o->label) label = &l2o->label; if (pfx_mac) { struct ecommunity *l2com = NULL; if (label) { l2com = bgp_rfapi_get_ecommunity_by_lni_label( bgp, 1, l2o->logical_net_id, *label); } if (l2com) { rtlist = ecommunity_dup(l2com); } else { /* * If mac address is set, add an RT based on the * registered LNI */ memset((char *)&ecom_value, 0, sizeof(ecom_value)); ecom_value.val[1] = ECOMMUNITY_ROUTE_TARGET; ecom_value.val[5] = (l2o->logical_net_id >> 16) & 0xff; ecom_value.val[6] = (l2o->logical_net_id >> 8) & 0xff; ecom_value.val[7] = (l2o->logical_net_id >> 0) & 0xff; rtlist = ecommunity_new(); ecommunity_add_val(rtlist, &ecom_value); } if (l2o->tag_id) { as_t as = bgp->as; uint16_t val = l2o->tag_id; memset((char *)&ecom_value, 0, sizeof(ecom_value)); ecom_value.val[1] = ECOMMUNITY_ROUTE_TARGET; if (as > BGP_AS_MAX) { ecom_value.val[0] = ECOMMUNITY_ENCODE_AS4; ecom_value.val[2] = (as >> 24) & 0xff; ecom_value.val[3] = (as >> 16) & 0xff; ecom_value.val[4] = (as >> 8) & 0xff; ecom_value.val[5] = as & 0xff; } else { ecom_value.val[0] = ECOMMUNITY_ENCODE_AS; ecom_value.val[2] = (as >> 8) & 0xff; ecom_value.val[3] = as & 0xff; } ecom_value.val[6] = (val >> 8) & 0xff; ecom_value.val[7] = val & 0xff; if (rtlist == NULL) rtlist = ecommunity_new(); ecommunity_add_val(rtlist, &ecom_value); } } /* * advertise prefix via tunnel endpoint */ add_vnc_route( rfd, /* rfapi descr, for export list & backref */ bgp, /* which bgp instance */ SAFI_MPLS_VPN, /* which SAFI */ (pfx_ip ? pfx_ip : &pfx_vn_buf), /* prefix being advertised */ &prd, /* route distinguisher to use (0 for ENCAP) */ &rfd->vn_addr, /* nexthop */ &local_pref, &lifetime, /* prefix lifetime -> Tunnel Encap attr */ NULL, options_un, /* rfapi un options */ options_vn, /* rfapi vn options */ (rtlist ? rtlist : rfd->rt_export_list), NULL, /* med */ label, /* label: default */ ZEBRA_ROUTE_BGP, BGP_ROUTE_RFP, 0); if (rtlist) ecommunity_free(&rtlist); /* sets rtlist = NULL */ } vnc_zlog_debug_verbose("%s: success", __func__); return 0; } int rfapi_query(void *handle, struct rfapi_ip_addr *target, struct rfapi_l2address_option *l2o, /* may be NULL */ struct rfapi_next_hop_entry **ppNextHopEntry) { struct rfapi_descriptor *rfd = (struct rfapi_descriptor *)handle; struct bgp *bgp = rfd->bgp; int rc; assert(ppNextHopEntry); *ppNextHopEntry = NULL; if (bgp && bgp->rfapi) { bgp->rfapi->stat.count_queries++; } if (!rfd->rfg) { if (bgp && bgp->rfapi) ++bgp->rfapi->stat.count_queries_failed; return ESTALE; } if ((rc = rfapi_query_inner(handle, target, l2o, ppNextHopEntry))) { if (bgp && bgp->rfapi) ++bgp->rfapi->stat.count_queries_failed; } return rc; } int rfapi_query_done(rfapi_handle handle, struct rfapi_ip_addr *target) { struct prefix p; int rc; struct rfapi_descriptor *rfd = (struct rfapi_descriptor *)handle; struct bgp *bgp = rfd->bgp; if (!rfd->rfg) return ESTALE; assert(target); rc = rfapiRaddr2Qprefix(target, &p); assert(!rc); if (!is_valid_rfd(rfd)) return EBADF; /* preemptive */ if (!bgp || !bgp->rfapi) return ENXIO; if (bgp->rfapi->flags & RFAPI_INCALLBACK) return EDEADLK; rfapiMonitorDel(bgp, rfd, &p); return 0; } int rfapi_query_done_all(rfapi_handle handle, int *count) { struct rfapi_descriptor *rfd = (struct rfapi_descriptor *)handle; struct bgp *bgp = rfd->bgp; ; int num; if (!rfd->rfg) return ESTALE; if (!is_valid_rfd(rfd)) return EBADF; /* preemptive */ if (!bgp || !bgp->rfapi) return ENXIO; if (bgp->rfapi->flags & RFAPI_INCALLBACK) return EDEADLK; num = rfapiMonitorDelHd(rfd); if (count) *count = num; return 0; } void rfapi_free_next_hop_list(struct rfapi_next_hop_entry *list) { struct rfapi_next_hop_entry *nh; struct rfapi_next_hop_entry *next; for (nh = list; nh; nh = next) { next = nh->next; rfapi_un_options_free(nh->un_options); nh->un_options = NULL; rfapi_vn_options_free(nh->vn_options); nh->vn_options = NULL; XFREE(MTYPE_RFAPI_NEXTHOP, nh); } } /* * NULL handle => return total count across all nves */ uint32_t rfapi_monitor_count(void *handle) { struct bgp *bgp = bgp_get_default(); uint32_t count; if (handle) { struct rfapi_descriptor *rfd = (struct rfapi_descriptor *)handle; count = rfd->monitor_count; } else { if (!bgp || !bgp->rfapi) return 0; count = bgp->rfapi->monitor_count; } return count; } /*********************************************************************** * CLI/CONFIG ***********************************************************************/ DEFUN (debug_rfapi_show_nves, debug_rfapi_show_nves_cmd, "debug rfapi-dev show nves", DEBUG_STR DEBUG_RFAPI_STR SHOW_STR "NVE Information\n") { rfapiPrintMatchingDescriptors(vty, NULL, NULL); return CMD_SUCCESS; } DEFUN ( debug_rfapi_show_nves_vn_un, debug_rfapi_show_nves_vn_un_cmd, "debug rfapi-dev show nves ", /* prefix also ok */ DEBUG_STR DEBUG_RFAPI_STR SHOW_STR "NVE Information\n" "Specify virtual network\n" "Specify underlay network interface\n" "IPv4 address\n" "IPv6 address\n") { struct prefix pfx; if (!str2prefix(argv[5]->arg, &pfx)) { vty_out(vty, "Malformed address \"%s\"\n", argv[5]->arg); return CMD_WARNING_CONFIG_FAILED; } if (pfx.family != AF_INET && pfx.family != AF_INET6) { vty_out(vty, "Invalid address \"%s\"\n", argv[5]->arg); return CMD_WARNING_CONFIG_FAILED; } if (argv[4]->arg[0] == 'u') { rfapiPrintMatchingDescriptors(vty, NULL, &pfx); } else { rfapiPrintMatchingDescriptors(vty, &pfx, NULL); } return CMD_SUCCESS; } /* * Note: this function does not flush vty output, so if it is called * with a stream pointing to a vty, the user will have to type something * before the callback output shows up */ static void test_nexthops_callback( // struct rfapi_ip_addr *target, struct rfapi_next_hop_entry *next_hops, void *userdata) { void *stream = userdata; int (*fp)(void *, const char *, ...); struct vty *vty; void *out; const char *vty_newline; if (rfapiStream2Vty(stream, &fp, &vty, &out, &vty_newline) == 0) return; fp(out, "Nexthops Callback, Target=("); // rfapiPrintRfapiIpAddr(stream, target); fp(out, ")\n"); rfapiPrintNhl(stream, next_hops); fp(out, "\n"); rfapi_free_next_hop_list(next_hops); } DEFUN (debug_rfapi_open, debug_rfapi_open_cmd, "debug rfapi-dev open vn un ", DEBUG_STR DEBUG_RFAPI_STR "rfapi_open\n" "indicate vn addr follows\n" "virtual network interface IPv4 address\n" "virtual network interface IPv6 address\n" "indicate xt addr follows\n" "underlay network interface IPv4 address\n" "underlay network interface IPv6 address\n") { struct rfapi_ip_addr vn; struct rfapi_ip_addr un; uint32_t lifetime = 0; int rc; rfapi_handle handle; /* * Get VN addr */ if ((rc = rfapiCliGetRfapiIpAddr(vty, argv[4]->arg, &vn))) return rc; /* * Get UN addr */ if ((rc = rfapiCliGetRfapiIpAddr(vty, argv[6]->arg, &un))) return rc; rc = rfapi_open(rfapi_get_rfp_start_val_by_bgp(bgp_get_default()), &vn, &un, /*&uo */ NULL, &lifetime, NULL, &handle); vty_out(vty, "rfapi_open: status %d, handle %p, lifetime %d\n", rc, handle, lifetime); rc = rfapi_set_response_cb(handle, test_nexthops_callback); vty_out(vty, "rfapi_set_response_cb: status %d\n", rc); return CMD_SUCCESS; } DEFUN (debug_rfapi_close_vn_un, debug_rfapi_close_vn_un_cmd, "debug rfapi-dev close vn un ", DEBUG_STR DEBUG_RFAPI_STR "rfapi_close\n" "indicate vn addr follows\n" "virtual network interface IPv4 address\n" "virtual network interface IPv6 address\n" "indicate xt addr follows\n" "underlay network interface IPv4 address\n" "underlay network interface IPv6 address\n") { struct rfapi_ip_addr vn; struct rfapi_ip_addr un; rfapi_handle handle; int rc; /* * Get VN addr */ if ((rc = rfapiCliGetRfapiIpAddr(vty, argv[4]->arg, &vn))) return rc; /* * Get UN addr */ if ((rc = rfapiCliGetRfapiIpAddr(vty, argv[6]->arg, &un))) return rc; if (rfapi_find_handle_vty(vty, &vn, &un, &handle)) { vty_out(vty, "can't locate handle matching vn=%s, un=%s\n", argv[4]->arg, argv[6]->arg); return CMD_WARNING_CONFIG_FAILED; } rc = rfapi_close(handle); vty_out(vty, "rfapi_close(handle=%p): status %d\n", handle, rc); return CMD_SUCCESS; } DEFUN (debug_rfapi_close_rfd, debug_rfapi_close_rfd_cmd, "debug rfapi-dev close rfd HANDLE", DEBUG_STR DEBUG_RFAPI_STR "rfapi_close\n" "indicate handle follows\n" "rfapi handle in hexadecimal\n") { rfapi_handle handle; int rc; char *endptr = NULL; handle = (rfapi_handle)(uintptr_t)(strtoull(argv[4]->arg, &endptr, 16)); if (*endptr != '\0' || (uintptr_t)handle == UINTPTR_MAX) { vty_out(vty, "Invalid value: %s\n", argv[4]->arg); return CMD_WARNING_CONFIG_FAILED; } rc = rfapi_close(handle); vty_out(vty, "rfapi_close(handle=%p): status %d\n", handle, rc); return CMD_SUCCESS; } DEFUN (debug_rfapi_register_vn_un, debug_rfapi_register_vn_un_cmd, "debug rfapi-dev register vn un prefix lifetime SECONDS [cost (0-255)]", DEBUG_STR DEBUG_RFAPI_STR "rfapi_register\n" "indicate vn addr follows\n" "virtual network IPv4 interface address\n" "virtual network IPv6 interface address\n" "indicate un addr follows\n" "underlay network IPv4 interface address\n" "underlay network IPv6 interface address\n" "indicate prefix follows\n" "IPv4 prefix\n" "IPv6 prefix\n" "indicate lifetime follows\n" "lifetime\n" "Cost (localpref = 255-cost)\n" "0-255\n") { struct rfapi_ip_addr vn; struct rfapi_ip_addr un; rfapi_handle handle; struct prefix pfx; uint32_t lifetime; struct rfapi_ip_prefix hpfx; int rc; uint8_t cost = 100; /* * Get VN addr */ if ((rc = rfapiCliGetRfapiIpAddr(vty, argv[4]->arg, &vn))) return rc; /* * Get UN addr */ if ((rc = rfapiCliGetRfapiIpAddr(vty, argv[6]->arg, &un))) return rc; if (rfapi_find_handle_vty(vty, &vn, &un, &handle)) { vty_out(vty, "can't locate handle matching vn=%s, un=%s\n", argv[4]->arg, argv[6]->arg); return CMD_WARNING_CONFIG_FAILED; } /* * Get prefix to advertise */ if (!str2prefix(argv[8]->arg, &pfx)) { vty_out(vty, "Malformed prefix \"%s\"\n", argv[8]->arg); return CMD_WARNING_CONFIG_FAILED; } if (pfx.family != AF_INET && pfx.family != AF_INET6) { vty_out(vty, "Bad family for prefix \"%s\"\n", argv[8]->arg); return CMD_WARNING_CONFIG_FAILED; } rfapiQprefix2Rprefix(&pfx, &hpfx); if (strmatch(argv[10]->text, "infinite")) { lifetime = RFAPI_INFINITE_LIFETIME; } else { lifetime = strtoul(argv[10]->arg, NULL, 10); } if (argc >= 13) cost = (uint8_t) strtoul(argv[12]->arg, NULL, 10); hpfx.cost = cost; rc = rfapi_register(handle, &hpfx, lifetime, NULL, NULL, RFAPI_REGISTER_ADD); if (rc) { vty_out(vty, "rfapi_register failed with rc=%d (%s)\n", rc, strerror(rc)); } return CMD_SUCCESS; } DEFUN (debug_rfapi_register_vn_un_l2o, debug_rfapi_register_vn_un_l2o_cmd, "debug rfapi-dev register vn un prefix lifetime SECONDS macaddr YY:YY:YY:YY:YY:YY lni (0-16777215)", DEBUG_STR DEBUG_RFAPI_STR "rfapi_register\n" "indicate vn addr follows\n" "virtual network IPv4 interface address\n" "virtual network IPv6 interface address\n" "indicate un addr follows\n" "underlay network IPv4 interface address\n" "underlay network IPv6 interface address\n" "indicate prefix follows\n" "IPv4 prefix\n" "IPv6 prefix\n" "indicate lifetime follows\n" "Seconds of lifetime\n" "indicate MAC address follows\n" "MAC address\n" "indicate lni follows\n" "lni value range\n") { struct rfapi_ip_addr vn; struct rfapi_ip_addr un; rfapi_handle handle; struct prefix pfx; uint32_t lifetime; struct rfapi_ip_prefix hpfx; int rc; struct rfapi_vn_option optary[10]; /* XXX must be big enough */ struct rfapi_vn_option *opt = NULL; int opt_next = 0; /* * Get VN addr */ if ((rc = rfapiCliGetRfapiIpAddr(vty, argv[4]->arg, &vn))) return rc; /* * Get UN addr */ if ((rc = rfapiCliGetRfapiIpAddr(vty, argv[6]->arg, &un))) return rc; if (rfapi_find_handle_vty(vty, &vn, &un, &handle)) { vty_out(vty, "can't locate handle matching vn=%s, un=%s\n", argv[4]->arg, argv[6]->arg); return CMD_WARNING_CONFIG_FAILED; } /* * Get prefix to advertise */ if (!str2prefix(argv[8]->arg, &pfx)) { vty_out(vty, "Malformed prefix \"%s\"\n", argv[8]->arg); return CMD_WARNING_CONFIG_FAILED; } if (pfx.family != AF_INET && pfx.family != AF_INET6) { vty_out(vty, "Bad family for prefix \"%s\"\n", argv[8]->arg); return CMD_WARNING_CONFIG_FAILED; } rfapiQprefix2Rprefix(&pfx, &hpfx); if (strmatch(argv[10]->text, "infinite")) { lifetime = RFAPI_INFINITE_LIFETIME; } else { lifetime = strtoul(argv[10]->arg, NULL, 10); } /* L2 option parsing START */ memset(optary, 0, sizeof(optary)); optary[opt_next].v.l2addr.logical_net_id = strtoul(argv[14]->arg, NULL, 10); if (rfapiStr2EthAddr(argv[12]->arg, &optary[opt_next].v.l2addr.macaddr)) { vty_out(vty, "Bad mac address \"%s\"\n", argv[12]->arg); return CMD_WARNING_CONFIG_FAILED; } optary[opt_next].type = RFAPI_VN_OPTION_TYPE_L2ADDR; if (opt_next) { optary[opt_next - 1].next = optary + opt_next; } else { opt = optary; } ++opt_next; /* L2 option parsing END */ /* TBD fixme */ rc = rfapi_register(handle, &hpfx, lifetime, NULL /* &uo */, opt, RFAPI_REGISTER_ADD); if (rc) { vty_out(vty, "rfapi_register failed with rc=%d (%s)\n", rc, strerror(rc)); } return CMD_SUCCESS; } DEFUN (debug_rfapi_unregister_vn_un, debug_rfapi_unregister_vn_un_cmd, "debug rfapi-dev unregister vn un prefix [kill]", DEBUG_STR DEBUG_RFAPI_STR "rfapi_register\n" "indicate vn addr follows\n" "virtual network interface address\n" "indicate xt addr follows\n" "underlay network interface address\n" "prefix to remove\n" "Remove without holddown") { struct rfapi_ip_addr vn; struct rfapi_ip_addr un; rfapi_handle handle; struct prefix pfx; struct rfapi_ip_prefix hpfx; int rc; /* * Get VN addr */ if ((rc = rfapiCliGetRfapiIpAddr(vty, argv[4]->arg, &vn))) return rc; /* * Get UN addr */ if ((rc = rfapiCliGetRfapiIpAddr(vty, argv[6]->arg, &un))) return rc; if (rfapi_find_handle_vty(vty, &vn, &un, &handle)) { vty_out(vty, "can't locate handle matching vn=%s, un=%s\n", argv[4]->arg, argv[6]->arg); return CMD_WARNING_CONFIG_FAILED; } /* * Get prefix to advertise */ if (!str2prefix(argv[8]->arg, &pfx)) { vty_out(vty, "Malformed prefix \"%s\"\n", argv[8]->arg); return CMD_WARNING_CONFIG_FAILED; } if (pfx.family != AF_INET && pfx.family != AF_INET6) { vty_out(vty, "Bad family for prefix \"%s\"\n", argv[8]->arg); return CMD_WARNING_CONFIG_FAILED; } rfapiQprefix2Rprefix(&pfx, &hpfx); rfapi_register(handle, &hpfx, 0, NULL, NULL, (argc == 10 ? RFAPI_REGISTER_KILL : RFAPI_REGISTER_WITHDRAW)); return CMD_SUCCESS; } DEFUN (debug_rfapi_query_vn_un, debug_rfapi_query_vn_un_cmd, "debug rfapi-dev query vn un target ", DEBUG_STR DEBUG_RFAPI_STR "rfapi_query\n" "indicate vn addr follows\n" "virtual network interface IPv4 address\n" "virtual network interface IPv6 address\n" "indicate un addr follows\n" "IPv4 un address\n" "IPv6 un address\n" "indicate target follows\n" "target IPv4 address\n" "target IPv6 address\n") { struct rfapi_ip_addr vn; struct rfapi_ip_addr un; struct rfapi_ip_addr target; rfapi_handle handle; int rc; struct rfapi_next_hop_entry *pNextHopEntry; /* * Get VN addr */ if ((rc = rfapiCliGetRfapiIpAddr(vty, argv[4]->arg, &vn))) return rc; /* * Get UN addr */ if ((rc = rfapiCliGetRfapiIpAddr(vty, argv[6]->arg, &un))) return rc; /* * Get target addr */ if ((rc = rfapiCliGetRfapiIpAddr(vty, argv[8]->arg, &target))) return rc; if (rfapi_find_handle_vty(vty, &vn, &un, &handle)) { vty_out(vty, "can't locate handle matching vn=%s, un=%s\n", argv[4]->arg, argv[6]->arg); return CMD_WARNING_CONFIG_FAILED; } /* * options parameter not used? Set to NULL for now */ rc = rfapi_query(handle, &target, NULL, &pNextHopEntry); if (rc) { vty_out(vty, "rfapi_query failed with rc=%d (%s)\n", rc, strerror(rc)); } else { /* * print nexthop list */ test_nexthops_callback(/*&target, */ pNextHopEntry, vty); /* frees nh list! */ } return CMD_SUCCESS; } DEFUN (debug_rfapi_query_vn_un_l2o, debug_rfapi_query_vn_un_l2o_cmd, "debug rfapi-dev query vn un lni LNI target YY:YY:YY:YY:YY:YY", DEBUG_STR DEBUG_RFAPI_STR "rfapi_query\n" "indicate vn addr follows\n" "virtual network interface IPv4 address\n" "virtual network interface IPv6 address\n" "indicate xt addr follows\n" "underlay network interface IPv4 address\n" "underlay network interface IPv6 address\n" "logical network ID follows\n" "logical network ID\n" "indicate target MAC addr follows\n" "target MAC addr\n") { struct rfapi_ip_addr vn; struct rfapi_ip_addr un; int rc; /* * Get VN addr */ if ((rc = rfapiCliGetRfapiIpAddr(vty, argv[4]->arg, &vn))) return rc; /* * Get UN addr */ if ((rc = rfapiCliGetRfapiIpAddr(vty, argv[6]->arg, &un))) return rc; vty_out(vty, "%% This command is broken.\n"); return CMD_WARNING_CONFIG_FAILED; } DEFUN (debug_rfapi_query_done_vn_un, debug_rfapi_query_vn_un_done_cmd, "debug rfapi-dev query done vn un target ", DEBUG_STR DEBUG_RFAPI_STR "rfapi_query_done\n" "rfapi_query_done\n" "indicate vn addr follows\n" "virtual network interface IPv4 address\n" "virtual network interface IPv6 address\n" "indicate xt addr follows\n" "underlay network interface IPv4 address\n" "underlay network interface IPv6 address\n" "indicate target follows\n" "Target IPv4 address\n" "Target IPv6 address\n") { struct rfapi_ip_addr vn; struct rfapi_ip_addr un; struct rfapi_ip_addr target; rfapi_handle handle; int rc; /* * Get VN addr */ if ((rc = rfapiCliGetRfapiIpAddr(vty, argv[5]->arg, &vn))) return rc; /* * Get UN addr */ if ((rc = rfapiCliGetRfapiIpAddr(vty, argv[7]->arg, &un))) return rc; /* * Get target addr */ if ((rc = rfapiCliGetRfapiIpAddr(vty, argv[9]->arg, &target))) return rc; if (rfapi_find_handle_vty(vty, &vn, &un, &handle)) { vty_out(vty, "can't locate handle matching vn=%s, un=%s\n", argv[5]->arg, argv[7]->arg); return CMD_WARNING_CONFIG_FAILED; } /* * options parameter not used? Set to NULL for now */ rc = rfapi_query_done(handle, &target); vty_out(vty, "rfapi_query_done returned %d\n", rc); return CMD_SUCCESS; } DEFUN (debug_rfapi_show_import, debug_rfapi_show_import_cmd, "debug rfapi-dev show import", DEBUG_STR DEBUG_RFAPI_STR SHOW_STR "import\n") { struct bgp *bgp; struct rfapi *h; struct rfapi_import_table *it; char *s; int first_l2 = 1; /* * Show all import tables */ bgp = bgp_get_default(); /* assume 1 instance for now */ if (!bgp) { vty_out(vty, "No BGP instance\n"); return CMD_WARNING_CONFIG_FAILED; } h = bgp->rfapi; if (!h) { vty_out(vty, "No RFAPI instance\n"); return CMD_WARNING_CONFIG_FAILED; } /* * Iterate over all import tables; do a filtered import * for the afi/safi combination */ for (it = h->imports; it; it = it->next) { s = ecommunity_ecom2str(it->rt_import_list, ECOMMUNITY_FORMAT_ROUTE_MAP, 0); vty_out(vty, "Import Table %p, RTs: %s\n", it, s); XFREE(MTYPE_ECOMMUNITY_STR, s); rfapiShowImportTable(vty, "IP VPN", it->imported_vpn[AFI_IP], 1); rfapiShowImportTable(vty, "IP ENCAP", it->imported_encap[AFI_IP], 0); rfapiShowImportTable(vty, "IP6 VPN", it->imported_vpn[AFI_IP6], 1); rfapiShowImportTable(vty, "IP6 ENCAP", it->imported_encap[AFI_IP6], 0); } if (h->import_mac) { void *cursor = NULL; uint32_t lni; uintptr_t lni_as_ptr; int rc; char buf[BUFSIZ]; for (rc = skiplist_next(h->import_mac, (void **)&lni_as_ptr, (void **)&it, &cursor); !rc; rc = skiplist_next(h->import_mac, (void **)&lni_as_ptr, (void **)&it, &cursor)) { if (it->imported_vpn[AFI_L2VPN]) { lni = lni_as_ptr; if (first_l2) { vty_out(vty, "\nLNI-based Ethernet Tables:\n"); first_l2 = 0; } snprintf(buf, BUFSIZ, "L2VPN LNI=%u", lni); rfapiShowImportTable( vty, buf, it->imported_vpn[AFI_L2VPN], 1); } } } rfapiShowImportTable(vty, "CE IT - IP VPN", h->it_ce->imported_vpn[AFI_IP], 1); return CMD_SUCCESS; } DEFUN (debug_rfapi_show_import_vn_un, debug_rfapi_show_import_vn_un_cmd, "debug rfapi-dev show import vn un ", DEBUG_STR DEBUG_RFAPI_STR SHOW_STR "import\n" "indicate vn addr follows\n" "virtual network interface IPv4 address\n" "virtual network interface IPv6 address\n" "indicate xt addr follows\n" "underlay network interface IPv4 address\n" "underlay network interface IPv6 address\n") { struct rfapi_ip_addr vn; struct rfapi_ip_addr un; rfapi_handle handle; int rc; struct rfapi_descriptor *rfd; /* * Get VN addr */ if ((rc = rfapiCliGetRfapiIpAddr(vty, argv[5]->arg, &vn))) return rc; /* * Get UN addr */ if ((rc = rfapiCliGetRfapiIpAddr(vty, argv[7]->arg, &un))) return rc; if (rfapi_find_handle_vty(vty, &vn, &un, &handle)) { vty_out(vty, "can't locate handle matching vn=%s, un=%s\n", argv[5]->arg, argv[7]->arg); return CMD_WARNING_CONFIG_FAILED; } rfd = (struct rfapi_descriptor *)handle; rfapiShowImportTable(vty, "IP VPN", rfd->import_table->imported_vpn[AFI_IP], 1); rfapiShowImportTable(vty, "IP ENCAP", rfd->import_table->imported_encap[AFI_IP], 0); rfapiShowImportTable(vty, "IP6 VPN", rfd->import_table->imported_vpn[AFI_IP6], 1); rfapiShowImportTable(vty, "IP6 ENCAP", rfd->import_table->imported_encap[AFI_IP6], 0); return CMD_SUCCESS; } DEFUN (debug_rfapi_response_omit_self, debug_rfapi_response_omit_self_cmd, "debug rfapi-dev response-omit-self ", DEBUG_STR DEBUG_RFAPI_STR "Omit self in RFP responses\n" "filter out self from responses\n" "leave self in responses\n") { struct bgp *bgp = bgp_get_default(); if (!bgp) { vty_out(vty, "No BGP process is configured\n"); return CMD_WARNING_CONFIG_FAILED; } if (!bgp->rfapi_cfg) { vty_out(vty, "VNC not configured\n"); return CMD_WARNING_CONFIG_FAILED; } if (strmatch(argv[3]->text, "on")) SET_FLAG(bgp->rfapi_cfg->flags, BGP_VNC_CONFIG_FILTER_SELF_FROM_RSP); else UNSET_FLAG(bgp->rfapi_cfg->flags, BGP_VNC_CONFIG_FILTER_SELF_FROM_RSP); return CMD_SUCCESS; } #ifdef RFAPI_DEBUG_SKIPLIST_CLI #include "lib/skiplist.h" DEFUN (skiplist_test_cli, skiplist_test_cli_cmd, "skiplist test", "skiplist command\n" "test\n") { skiplist_test(vty); return CMD_SUCCESS; } DEFUN (skiplist_debug_cli, skiplist_debug_cli_cmd, "skiplist debug", "skiplist command\n" "debug\n") { skiplist_debug(vty, NULL); return CMD_SUCCESS; } #endif /* RFAPI_DEBUG_SKIPLIST_CLI */ void rfapi_init(void) { bgp_rfapi_cfg_init(); vnc_debug_init(); install_element(ENABLE_NODE, &debug_rfapi_show_import_cmd); install_element(ENABLE_NODE, &debug_rfapi_show_import_vn_un_cmd); install_element(ENABLE_NODE, &debug_rfapi_open_cmd); install_element(ENABLE_NODE, &debug_rfapi_close_vn_un_cmd); install_element(ENABLE_NODE, &debug_rfapi_close_rfd_cmd); install_element(ENABLE_NODE, &debug_rfapi_register_vn_un_cmd); install_element(ENABLE_NODE, &debug_rfapi_unregister_vn_un_cmd); install_element(ENABLE_NODE, &debug_rfapi_query_vn_un_cmd); install_element(ENABLE_NODE, &debug_rfapi_query_vn_un_done_cmd); install_element(ENABLE_NODE, &debug_rfapi_query_vn_un_l2o_cmd); install_element(ENABLE_NODE, &debug_rfapi_response_omit_self_cmd); /* Need the following show commands for gpz test scripts */ install_element(ENABLE_NODE, &debug_rfapi_show_nves_cmd); install_element(ENABLE_NODE, &debug_rfapi_show_nves_vn_un_cmd); install_element(ENABLE_NODE, &debug_rfapi_register_vn_un_l2o_cmd); #ifdef RFAPI_DEBUG_SKIPLIST_CLI install_element(ENABLE_NODE, &skiplist_test_cli_cmd); install_element(ENABLE_NODE, &skiplist_debug_cli_cmd); #endif rfapi_vty_init(); } #ifdef DEBUG_RFAPI static void rfapi_print_exported(struct bgp *bgp) { struct bgp_node *rdn; struct bgp_node *rn; struct bgp_path_info *bpi; if (!bgp) return; for (rdn = bgp_table_top(bgp->rib[AFI_IP][SAFI_MPLS_VPN]); rdn; rdn = bgp_route_next(rdn)) { struct bgp_table *table; table = bgp_node_get_bgp_table_info(rdn); if (!table) continue; fprintf(stderr, "%s: vpn rdn=%p\n", __func__, rdn); for (rn = bgp_table_top(table); rn; rn = bgp_route_next(rn)) { bpi = bgp_node_get_bgp_path_info(rn); if (!bpi) continue; fprintf(stderr, "%s: rn=%p\n", __func__, rn); for (; bpi; bpi = bpi->next) { rfapiPrintBi((void *)2, bpi); /* 2 => stderr */ } } } for (rdn = bgp_table_top(bgp->rib[AFI_IP][SAFI_ENCAP]); rdn; rdn = bgp_route_next(rdn)) { struct bgp_table *table; table = bgp_node_get_bgp_table_info(rdn); if (!table) continue; fprintf(stderr, "%s: encap rdn=%p\n", __func__, rdn); for (rn = bgp_table_top(table)); rn; rn = bgp_route_next(rn)) { bpi = bgp_node_get_bgp_path_info(rn); if (!bpi) continue; fprintf(stderr, "%s: rn=%p\n", __func__, rn); for (; bpi; bpi = bpi->next) { rfapiPrintBi((void *)2, bpi); /* 2 => stderr */ } } } } #endif /* defined(DEBUG_RFAPI) */ /* * Free all memory to prepare for clean exit as seen by valgrind memcheck */ void rfapi_delete(struct bgp *bgp) { extern void rfp_clear_vnc_nve_all(void); /* can't fix correctly yet */ /* * This clears queries and registered routes, and closes nves */ if (bgp->rfapi) rfp_clear_vnc_nve_all(); bgp_rfapi_cfg_destroy(bgp, bgp->rfapi_cfg); bgp->rfapi_cfg = NULL; bgp_rfapi_destroy(bgp, bgp->rfapi); bgp->rfapi = NULL; #ifdef DEBUG_RFAPI /* * show what's left in the BGP MPLSVPN RIB */ rfapi_print_exported(bgp); #endif } int rfapi_set_autord_from_vn(struct prefix_rd *rd, struct rfapi_ip_addr *vn) { vnc_zlog_debug_verbose("%s: auto-assigning RD", __func__); if (vn->addr_family != AF_INET && vn->addr_family != AF_INET6) { vnc_zlog_debug_verbose( "%s: can't auto-assign RD, VN addr family is not IPv4" "|v6", __func__); return EAFNOSUPPORT; } rd->family = AF_UNSPEC; rd->prefixlen = 64; rd->val[1] = RD_TYPE_IP; if (vn->addr_family == AF_INET) { memcpy(rd->val + 2, &vn->addr.v4.s_addr, 4); } else { /* is v6 */ memcpy(rd->val + 2, &vn->addr.v6.s6_addr32[3], 4); /* low order 4 bytes */ } { char buf[RD_ADDRSTRLEN]; vnc_zlog_debug_verbose("%s: auto-RD is set to %s", __func__, prefix_rd2str(rd, buf, sizeof(buf))); } return 0; } /*------------------------------------------ * rfapi_bgp_lookup_by_rfp * * Find bgp instance pointer based on value returned by rfp_start * * input: * rfp_start_val value returned by rfp_startor * NULL (=get default instance) * * output: * none * * return value: * bgp bgp instance pointer * NULL = not found * --------------------------------------------*/ struct bgp *rfapi_bgp_lookup_by_rfp(void *rfp_start_val) { struct bgp *bgp = NULL; struct listnode *node, *nnode; if (rfp_start_val == NULL) bgp = bgp_get_default(); else for (ALL_LIST_ELEMENTS(bm->bgp, node, nnode, bgp)) if (bgp->rfapi != NULL && bgp->rfapi->rfp == rfp_start_val) return bgp; return bgp; } /*------------------------------------------ * rfapi_get_rfp_start_val_by_bgp * * Find bgp instance pointer based on value returned by rfp_start * * input: * bgp bgp instance pointer * * output: * none * * return value: * rfp_start_val * NULL = not found * --------------------------------------------*/ void *rfapi_get_rfp_start_val_by_bgp(struct bgp *bgp) { if (!bgp || !bgp->rfapi) return NULL; return bgp->rfapi->rfp; } /*********************************************************************** * RFP group specific configuration ***********************************************************************/ static void *rfapi_rfp_get_or_init_group_config_default(struct rfapi_cfg *rfc, struct vty *vty, uint32_t size) { if (rfc->default_rfp_cfg == NULL && size > 0) { rfc->default_rfp_cfg = XCALLOC(MTYPE_RFAPI_RFP_GROUP_CFG, size); vnc_zlog_debug_verbose("%s: allocated, size=%d", __func__, size); } return rfc->default_rfp_cfg; } static void *rfapi_rfp_get_or_init_group_config_nve(struct rfapi_cfg *rfc, struct vty *vty, uint32_t size) { struct rfapi_nve_group_cfg *rfg = VTY_GET_CONTEXT_SUB(rfapi_nve_group_cfg); /* make sure group is still in list */ if (!rfg || !listnode_lookup(rfc->nve_groups_sequential, rfg)) { /* Not in list anymore */ vty_out(vty, "Current NVE group no longer exists\n"); return NULL; } if (rfg->rfp_cfg == NULL && size > 0) { rfg->rfp_cfg = XCALLOC(MTYPE_RFAPI_RFP_GROUP_CFG, size); vnc_zlog_debug_verbose("%s: allocated, size=%d", __func__, size); } return rfg->rfp_cfg; } static void *rfapi_rfp_get_or_init_group_config_l2(struct rfapi_cfg *rfc, struct vty *vty, uint32_t size) { struct rfapi_l2_group_cfg *rfg = VTY_GET_CONTEXT_SUB(rfapi_l2_group_cfg); /* make sure group is still in list */ if (!rfg || !listnode_lookup(rfc->l2_groups, rfg)) { /* Not in list anymore */ vty_out(vty, "Current L2 group no longer exists\n"); return NULL; } if (rfg->rfp_cfg == NULL && size > 0) { rfg->rfp_cfg = XCALLOC(MTYPE_RFAPI_RFP_GROUP_CFG, size); vnc_zlog_debug_verbose("%s: allocated, size=%d", __func__, size); } return rfg->rfp_cfg; } /*------------------------------------------ * rfapi_rfp_init_group_config_ptr_vty * * This is used to init or return a previously init'ed group specific * configuration pointer. Group is identified by vty context. * NOTE: size is ignored when a previously init'ed value is returned. * RFAPI frees rfp_cfg_group when group is deleted during reconfig, * bgp restart or shutdown. * * input: * rfp_start_val value returned by rfp_start * type group type * vty quagga vty context * size number of bytes to allocation * * output: * none * * return value: * rfp_cfg_group NULL or Pointer to configuration structure --------------------------------------------*/ void *rfapi_rfp_init_group_config_ptr_vty(void *rfp_start_val, rfapi_rfp_cfg_group_type type, struct vty *vty, uint32_t size) { struct bgp *bgp; void *ret = NULL; if (rfp_start_val == NULL || vty == NULL) return NULL; bgp = rfapi_bgp_lookup_by_rfp(rfp_start_val); if (!bgp || !bgp->rfapi_cfg) return NULL; switch (type) { case RFAPI_RFP_CFG_GROUP_DEFAULT: ret = rfapi_rfp_get_or_init_group_config_default(bgp->rfapi_cfg, vty, size); break; case RFAPI_RFP_CFG_GROUP_NVE: ret = rfapi_rfp_get_or_init_group_config_nve(bgp->rfapi_cfg, vty, size); break; case RFAPI_RFP_CFG_GROUP_L2: ret = rfapi_rfp_get_or_init_group_config_l2(bgp->rfapi_cfg, vty, size); break; default: flog_err(EC_LIB_DEVELOPMENT, "%s: Unknown group type=%d", __func__, type); /* should never happen */ assert("Unknown type" == NULL); break; } return ret; } /*------------------------------------------ * rfapi_rfp_get_group_config_ptr_vty * * This is used to get group specific configuration pointer. * Group is identified by type and vty context. * RFAPI frees rfp_cfg_group when group is deleted during reconfig, * bgp restart or shutdown. * * input: * rfp_start_val value returned by rfp_start * type group type * vty quagga vty context * * output: * none * * return value: * rfp_cfg_group Pointer to configuration structure --------------------------------------------*/ void *rfapi_rfp_get_group_config_ptr_vty(void *rfp_start_val, rfapi_rfp_cfg_group_type type, struct vty *vty) { return rfapi_rfp_init_group_config_ptr_vty(rfp_start_val, type, vty, 0); } static void * rfapi_rfp_get_group_config_name_nve(struct rfapi_cfg *rfc, const char *name, void *criteria, rfp_group_config_search_cb_t *search_cb) { struct rfapi_nve_group_cfg *rfg; struct listnode *node; for (ALL_LIST_ELEMENTS_RO(rfc->nve_groups_sequential, node, rfg)) { if (!strcmp(rfg->name, name) && /* name match */ (search_cb == NULL || !search_cb(criteria, rfg->rfp_cfg))) return rfg->rfp_cfg; } return NULL; } static void * rfapi_rfp_get_group_config_name_l2(struct rfapi_cfg *rfc, const char *name, void *criteria, rfp_group_config_search_cb_t *search_cb) { struct rfapi_l2_group_cfg *rfg; struct listnode *node; for (ALL_LIST_ELEMENTS_RO(rfc->l2_groups, node, rfg)) { if (!strcmp(rfg->name, name) && /* name match */ (search_cb == NULL || !search_cb(criteria, rfg->rfp_cfg))) return rfg->rfp_cfg; } return NULL; } /*------------------------------------------ * rfapi_rfp_get_group_config_ptr_name * * This is used to get group specific configuration pointer. * Group is identified by type and name context. * RFAPI frees rfp_cfg_group when group is deleted during reconfig, * bgp restart or shutdown. * * input: * rfp_start_val value returned by rfp_start * type group type * name group name * criteria RFAPI caller provided serach criteria * search_cb optional rfp_group_config_search_cb_t * * output: * none * * return value: * rfp_cfg_group Pointer to configuration structure --------------------------------------------*/ void *rfapi_rfp_get_group_config_ptr_name( void *rfp_start_val, rfapi_rfp_cfg_group_type type, const char *name, void *criteria, rfp_group_config_search_cb_t *search_cb) { struct bgp *bgp; void *ret = NULL; if (rfp_start_val == NULL || name == NULL) return NULL; bgp = rfapi_bgp_lookup_by_rfp(rfp_start_val); if (!bgp || !bgp->rfapi_cfg) return NULL; switch (type) { case RFAPI_RFP_CFG_GROUP_DEFAULT: ret = bgp->rfapi_cfg->default_rfp_cfg; break; case RFAPI_RFP_CFG_GROUP_NVE: ret = rfapi_rfp_get_group_config_name_nve(bgp->rfapi_cfg, name, criteria, search_cb); break; case RFAPI_RFP_CFG_GROUP_L2: ret = rfapi_rfp_get_group_config_name_l2(bgp->rfapi_cfg, name, criteria, search_cb); break; default: flog_err(EC_LIB_DEVELOPMENT, "%s: Unknown group type=%d", __func__, type); /* should never happen */ assert("Unknown type" == NULL); break; } return ret; } /*------------------------------------------ * rfapi_rfp_get_l2_group_config_ptr_lni * * This is used to get group specific configuration pointer. * Group is identified by type and logical network identifier. * RFAPI frees rfp_cfg_group when group is deleted during reconfig, * bgp restart or shutdown. * * input: * rfp_start_val value returned by rfp_start * type group type * logical_net_id group logical network identifier * criteria RFAPI caller provided serach criteria * search_cb optional rfp_group_config_search_cb_t * * output: * none * * return value: * rfp_cfg_group Pointer to configuration structure --------------------------------------------*/ void * rfapi_rfp_get_l2_group_config_ptr_lni(void *rfp_start_val, uint32_t logical_net_id, void *criteria, rfp_group_config_search_cb_t *search_cb) { struct bgp *bgp; struct rfapi_l2_group_cfg *rfg; struct listnode *node; if (rfp_start_val == NULL) return NULL; bgp = rfapi_bgp_lookup_by_rfp(rfp_start_val); if (!bgp || !bgp->rfapi_cfg) return NULL; for (ALL_LIST_ELEMENTS_RO(bgp->rfapi_cfg->l2_groups, node, rfg)) { if (rfg->logical_net_id == logical_net_id && (search_cb == NULL || !search_cb(criteria, rfg->rfp_cfg))) { if (rfg->rfp_cfg == NULL) vnc_zlog_debug_verbose( "%s: returning rfp group config for lni=0", __func__); return rfg->rfp_cfg; } } return NULL; } frr-7.2.1/bgpd/rfapi/rfapi.h0000644000000000000000000006753613610377563012524 00000000000000/* * * Copyright 2009-2016, LabN Consulting, L.L.C. * * * This program is free software; you can redistribute it and/or * modify it under the terms of the GNU General Public License * as published by the Free Software Foundation; either version 2 * of the License, or (at your option) any later version. * * 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; see the file COPYING; if not, write to the Free Software * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA */ #ifndef _QUAGGA_BGP_RFAPI_H #define _QUAGGA_BGP_RFAPI_H #if ENABLE_BGP_VNC #include #include #include "lib/zebra.h" #include "lib/vty.h" #include "lib/prefix.h" #include "bgpd/bgpd.h" #include "bgpd/bgp_encap_types.h" /* probably ought to have a field-specific define in config.h */ #ifndef s6_addr32 /* for solaris/bsd */ #ifdef SOLARIS_IPV6 # define s6_addr32 _S6_un._S6_u32 #else # define s6_addr32 __u6_addr.__u6_addr32 #endif #endif #define RFAPI_V4_ADDR 0x04 #define RFAPI_V6_ADDR 0x06 #define RFAPI_SHOW_STR "VNC information\n" struct rfapi_ip_addr { uint8_t addr_family; /* AF_INET | AF_INET6 */ union { struct in_addr v4; /* in network order */ struct in6_addr v6; /* in network order */ } addr; }; struct rfapi_ip_prefix { uint8_t length; uint8_t cost; /* bgp local pref = 255 - cost */ struct rfapi_ip_addr prefix; }; struct rfapi_nexthop { struct prefix addr; uint8_t cost; }; struct rfapi_next_hop_entry { struct rfapi_next_hop_entry *next; struct rfapi_ip_prefix prefix; uint32_t lifetime; struct rfapi_ip_addr un_address; struct rfapi_ip_addr vn_address; struct rfapi_vn_option *vn_options; struct rfapi_un_option *un_options; }; #define RFAPI_REMOVE_RESPONSE_LIFETIME 0 #define RFAPI_INFINITE_LIFETIME 0xFFFFFFFF struct rfapi_l2address_option { struct ethaddr macaddr; /* use 0 to assign label to IP prefix */ uint32_t label; /* 20bit label in low bits, no TC, S, or TTL */ uint32_t logical_net_id; /* ~= EVPN Ethernet Segment Id, must not be zero for mac regis. */ uint8_t local_nve_id; uint16_t tag_id; /* EVPN Ethernet Tag ID, 0 = none */ }; typedef enum { RFAPI_UN_OPTION_TYPE_PROVISIONAL, /* internal use only */ RFAPI_UN_OPTION_TYPE_TUNNELTYPE, } rfapi_un_option_type; struct rfapi_tunneltype_option { bgp_encap_types type; union { struct bgp_encap_type_reserved reserved; struct bgp_encap_type_l2tpv3_over_ip l2tpv3_ip; struct bgp_encap_type_gre gre; struct bgp_encap_type_transmit_tunnel_endpoint transmit_tunnel_endpoint; struct bgp_encap_type_ipsec_in_tunnel_mode ipsec_tunnel; struct bgp_encap_type_ip_in_ip_tunnel_with_ipsec_transport_mode ip_ipsec; struct bgp_encap_type_mpls_in_ip_tunnel_with_ipsec_transport_mode mpls_ipsec; struct bgp_encap_type_ip_in_ip ip_ip; struct bgp_encap_type_vxlan vxlan; struct bgp_encap_type_nvgre nvgre; struct bgp_encap_type_mpls mpls; struct bgp_encap_type_mpls_in_gre mpls_gre; struct bgp_encap_type_vxlan_gpe vxlan_gpe; struct bgp_encap_type_mpls_in_udp mpls_udp; struct bgp_encap_type_pbb pbb; } bgpinfo; }; struct rfapi_un_option { struct rfapi_un_option *next; rfapi_un_option_type type; union { struct rfapi_tunneltype_option tunnel; } v; }; typedef enum { RFAPI_VN_OPTION_TYPE_L2ADDR = 3, /* Layer 2 address, 3 for legacy compatibility */ RFAPI_VN_OPTION_TYPE_LOCAL_NEXTHOP, /* for static routes */ RFAPI_VN_OPTION_TYPE_INTERNAL_RD, /* internal use only */ } rfapi_vn_option_type; struct rfapi_vn_option { struct rfapi_vn_option *next; rfapi_vn_option_type type; union { struct rfapi_l2address_option l2addr; /* * If this option is present, the next hop is local to the * client NVE (i.e., not via a tunnel). */ struct rfapi_nexthop local_nexthop; /* * For rfapi internal use only */ struct prefix_rd internal_rd; } v; }; struct rfapi_l2address_option_match { struct rfapi_l2address_option o; uint32_t flags; #define RFAPI_L2O_MACADDR 0x00000001 #define RFAPI_L2O_LABEL 0x00000002 #define RFAPI_L2O_LNI 0x00000004 #define RFAPI_L2O_LHI 0x00000008 }; #define VNC_CONFIG_STR "VNC/RFP related configuration\n" typedef void *rfapi_handle; /*********************************************************************** * RFP Callbacks ***********************************************************************/ /*------------------------------------------ * rfapi_response_cb_t (callback typedef) * * Callbacks of this type are used to provide asynchronous * route updates from RFAPI to the RFP client. * * response_cb * called to notify the rfp client that a next hop list * that has previously been provided in response to an * rfapi_query call has been updated. Deleted routes are indicated * with lifetime==RFAPI_REMOVE_RESPONSE_LIFETIME. * * By default, the routes an NVE receives via this callback include * its own routes (that it has registered). However, these may be * filtered out if the global BGP_VNC_CONFIG_FILTER_SELF_FROM_RSP * flag is set. * * local_cb * called to notify the rfp client that a local route * has been added or deleted. Deleted routes are indicated * with lifetime==RFAPI_REMOVE_RESPONSE_LIFETIME. * * input: * next_hops a list of possible next hops. * This is a linked list allocated within the * rfapi. The response_cb callback function is responsible * for freeing this memory via rfapi_free_next_hop_list() * in order to avoid memory leaks. * * userdata value (cookie) originally specified in call to * rfapi_open() * *------------------------------------------*/ typedef void(rfapi_response_cb_t)(struct rfapi_next_hop_entry *next_hops, void *userdata); /*------------------------------------------ * rfapi_nve_close_cb_t (callback typedef) * * Callbacks of this type are used to provide asynchronous * notification that an rfapi_handle was invalidated * * input: * pHandle Firmerly valid rfapi_handle returned to * client via rfapi_open(). * * reason EIDRM handle administratively closed (clear nve ...) * ESTALE handle invalidated by configuration change * *------------------------------------------*/ typedef void(rfapi_nve_close_cb_t)(rfapi_handle pHandle, int reason); /*------------------------------------------ * rfp_cfg_write_cb_t (callback typedef) * * This callback is used to generate output for any config parameters * that may supported by RFP via RFP defined vty commands at the bgp * level. See loglevel as an example. * * input: * vty -- quagga vty context * rfp_start_val -- value returned by rfp_start * * output: * to vty, rfp related configuration * * return value: * lines written --------------------------------------------*/ typedef int(rfp_cfg_write_cb_t)(struct vty *vty, void *rfp_start_val); /*------------------------------------------ * rfp_cfg_group_write_cb_t (callback typedef) * * This callback is used to generate output for any config parameters * that may supported by RFP via RFP defined vty commands at the * L2 or NVE level. See loglevel as an example. * * input: * vty quagga vty context * rfp_start_val value returned by rfp_start * type group type * name group name * rfp_cfg_group Pointer to configuration structure * * output: * to vty, rfp related configuration * * return value: * lines written --------------------------------------------*/ typedef enum { RFAPI_RFP_CFG_GROUP_DEFAULT, RFAPI_RFP_CFG_GROUP_NVE, RFAPI_RFP_CFG_GROUP_L2 } rfapi_rfp_cfg_group_type; typedef int(rfp_cfg_group_write_cb_t)(struct vty *vty, void *rfp_start_val, rfapi_rfp_cfg_group_type type, const char *name, void *rfp_cfg_group); /*********************************************************************** * Configuration related defines and structures ***********************************************************************/ struct rfapi_rfp_cb_methods { rfp_cfg_write_cb_t *cfg_cb; /* show top level config */ rfp_cfg_group_write_cb_t *cfg_group_cb; /* show group level config */ rfapi_response_cb_t *response_cb; /* unsolicited responses */ rfapi_response_cb_t *local_cb; /* local route add/delete */ rfapi_nve_close_cb_t *close_cb; /* handle closed */ }; /* * If a route with infinite lifetime is withdrawn, this is * how long (in seconds) to wait before expiring it (because * RFAPI_LIFETIME_MULTIPLIER_PCT * infinity is too long to wait) */ #define RFAPI_LIFETIME_INFINITE_WITHDRAW_DELAY (60*120) /* * the factor that should be applied to a prefix's value * before using it to expire a withdrawn prefix, expressed as a percent. * Thus, a value of 100 means to use the exact value of , * a value of 200 means to use twice the value of , etc. */ #define RFAPI_RFP_CFG_DEFAULT_HOLDDOWN_FACTOR 150 /* * This is used by rfapi to determine if RFP is using/supports * a partial (i.e., cache) or full table download approach for * mapping information. When full table download approach is * used all information is passed to RFP after an initial * rfapi_query. When partial table download is used, only * information matching a query is passed. */ typedef enum { RFAPI_RFP_DOWNLOAD_PARTIAL = 0, RFAPI_RFP_DOWNLOAD_FULL } rfapi_rfp_download_type; #define RFAPI_RFP_CFG_DEFAULT_FTD_ADVERTISEMENT_INTERVAL 1 struct rfapi_rfp_cfg { /* partial or full table download */ rfapi_rfp_download_type download_type; /* default=partial */ /* * When full-table-download is enabled, this is the minimum * number of seconds between times a non-queried prefix will * be updated to a particular NVE. * default: RFAPI_RFP_CFG_DEFAULT_FTD_ADVERTISEMENT_INTERVAL */ uint32_t ftd_advertisement_interval; /* * percentage of registration lifetime to continue to use information * post soft-state refresh timeout default: RFAPI_RFP_CFG_DEFAULT_HOLDDOWN_FACTOR */ uint32_t holddown_factor; /* Control generation of updated RFP responses */ uint8_t use_updated_response; /* default=0/no */ /* when use_updated_response, also generate remove responses */ uint8_t use_removes; /* default=0/no */ }; /*********************************************************************** * Process related functions -- MUST be provided by the RFAPI user <<=== ***********************************************************************/ /*------------------------------------------ * rfp_start * * This function will start the RFP code * * input: * master quagga thread_master to tie into bgpd threads * * output: * cfgp Pointer to rfapi_rfp_cfg (null = use defaults), * copied by caller, updated via rfp_set_configuration * cbmp Pointer to rfapi_rfp_cb_methods, may be null * copied by caller, updated via rfapi_rfp_set_cb_methods * return value: * rfp_start_val rfp returned value passed on rfp_stop and other rfapi calls --------------------------------------------*/ extern void *rfp_start(struct thread_master *master, struct rfapi_rfp_cfg **cfgp, struct rfapi_rfp_cb_methods **cbmp); /*------------------------------------------ * rfp_stop * * This function is called on shutdown to trigger RFP cleanup * * input: * rfp_start_val * * output: * none * * return value: --------------------------------------------*/ extern void rfp_stop(void *rfp_start_val); /*********************************************************************** * RFP processing behavior configuration ***********************************************************************/ /*------------------------------------------ * rfapi_rfp_set_configuration * * This is used to change rfapi's processing behavior based on * RFP requirements. * * input: * rfp_start_val value returned by rfp_start * rfapi_rfp_cfg Pointer to configuration structure * * output: * none * * return value: * 0 Success * ENXIO Unabled to locate configured BGP/VNC --------------------------------------------*/ extern int rfapi_rfp_set_configuration(void *rfp_start_val, struct rfapi_rfp_cfg *rfp_cfg); /*------------------------------------------ * rfapi_rfp_set_cb_methods * * Change registered callback functions for asynchronous notifications * from RFAPI to the RFP client. * * input: * rfp_start_val value by rfp_start * methods Pointer to struct rfapi_rfp_cb_methods containing * pointers to callback methods as described above * * return value: * 0 Success * ENXIO BGP or VNC not configured *------------------------------------------*/ extern int rfapi_rfp_set_cb_methods(void *rfp_start_val, struct rfapi_rfp_cb_methods *methods); /*********************************************************************** * RFP group specific configuration ***********************************************************************/ /*------------------------------------------ * rfapi_rfp_init_group_config_ptr_vty * * This is used to init or return a previously init'ed group specific * configuration pointer. Group is identified by vty context. * NOTE: size is ignored when a previously init'ed value is returned. * RFAPI frees rfp_cfg_group when group is deleted during reconfig, * bgp restart or shutdown. * * input: * rfp_start_val value returned by rfp_start * type group type * vty quagga vty context * size number of bytes to allocation * * output: * none * * return value: * rfp_cfg_group NULL or Pointer to configuration structure --------------------------------------------*/ extern void *rfapi_rfp_init_group_config_ptr_vty(void *rfp_start_val, rfapi_rfp_cfg_group_type type, struct vty *vty, uint32_t size); /*------------------------------------------ * rfapi_rfp_get_group_config_ptr_vty * * This is used to get group specific configuration pointer. * Group is identified by type and vty context. * RFAPI frees rfp_cfg_group when group is deleted during reconfig, * bgp restart or shutdown. * * input: * rfp_start_val value returned by rfp_start * type group type * vty quagga vty context * * output: * none * * return value: * rfp_cfg_group Pointer to configuration structure --------------------------------------------*/ extern void *rfapi_rfp_get_group_config_ptr_vty(void *rfp_start_val, rfapi_rfp_cfg_group_type type, struct vty *vty); /*------------------------------------------ * rfp_group_config_search_cb_t (callback typedef) * * This callback is used to called from within a * rfapi_rfp_get_group_config_ptr to check if the rfp_cfg_group * matches the search criteria * * input: * criteria RFAPI caller provided serach criteria * rfp_cfg_group Pointer to configuration structure | NULL * * output: * * return value: * 0 Match/Success * ENOENT No matching --------------------------------------------*/ typedef int(rfp_group_config_search_cb_t)(void *criteria, void *rfp_cfg_group); /*------------------------------------------ * rfapi_rfp_get_group_config_ptr_name * * This is used to get group specific configuration pointer. * Group is identified by type and name context. * RFAPI frees rfp_cfg_group when group is deleted during reconfig, * bgp restart or shutdown. * * input: * rfp_start_val value returned by rfp_start * type group type * name group name * criteria RFAPI caller provided serach criteria * search_cb optional rfp_group_config_search_cb_t * * output: * none * * return value: * rfp_cfg_group Pointer to configuration structure --------------------------------------------*/ extern void *rfapi_rfp_get_group_config_ptr_name( void *rfp_start_val, rfapi_rfp_cfg_group_type type, const char *name, void *criteria, rfp_group_config_search_cb_t *search_cb); /*------------------------------------------ * rfapi_rfp_get_l2_group_config_ptr_lni * * This is used to get group specific configuration pointer. * Group is identified by type and logical network identifier. * RFAPI frees rfp_cfg_group when group is deleted during reconfig, * bgp restart or shutdown. * * input: * rfp_start_val value returned by rfp_start * logical_net_id group logical network identifier * criteria RFAPI caller provided serach criteria * search_cb optional rfp_group_config_search_cb_t * * output: * none * * return value: * rfp_cfg_group Pointer to configuration structure --------------------------------------------*/ extern void * rfapi_rfp_get_l2_group_config_ptr_lni(void *rfp_start_val, uint32_t logical_net_id, void *criteria, rfp_group_config_search_cb_t *search_cb); /*********************************************************************** * NVE Sessions ***********************************************************************/ /*------------------------------------------ * rfapi_open * * This function initializes a NVE record and associates it with * the specified VN and underlay network addresses * * input: * rfp_start_val value returned by rfp_start * vn NVE virtual network address * * un NVE underlay network address * * default_options Default options to use on registrations. * For now only tunnel type is supported. * May be overridden per-prefix in rfapi_register(). * Caller owns (rfapi_open() does not free) * * response_cb Pointer to next hop list update callback function or * NULL when no callbacks are desired. * * userdata Passed to subsequent response_cb invocations. * * output: * response_lifetime The length of time that responses sent to this * NVE are valid. * * pHandle pointer to location to store rfapi handle. The * handle must be passed on subsequent rfapi_ calls. * * * return value: * 0 Success * EEXIST NVE with this {vn,un} already open * ENOENT No matching nve group config * ENOMSG Matched nve group config was incomplete * ENXIO BGP or VNC not configured * EAFNOSUPPORT Matched nve group specifies auto-assignment of RD, * but underlay network address is not IPv4 * EDEADLK Called from within a callback procedure *------------------------------------------*/ extern int rfapi_open(void *rfp_start_val, struct rfapi_ip_addr *vn, struct rfapi_ip_addr *un, struct rfapi_un_option *default_options, uint32_t *response_lifetime, void *userdata, rfapi_handle *pHandle); /*------------------------------------------ * rfapi_close * * Shut down NVE session and release associated data. Calling * from within a rfapi callback procedure is permitted (the close * will be completed asynchronously after the callback finishes). * * input: * rfd: rfapi descriptor returned by rfapi_open * * output: * * return value: * 0 Success * EBADF invalid handle * ENXIO BGP or VNC not configured *------------------------------------------*/ extern int rfapi_close(rfapi_handle rfd); /*------------------------------------------ * rfapi_check * * Test rfapi descriptor * * input: * rfd: rfapi descriptor returned by rfapi_open * * output: * * return value: * 0 Success: handle is valid and usable * EINVAL null argument * ESTALE formerly valid handle invalidated by config, needs close * EBADF invalid handle * ENXIO BGP or VNC not configured * EAFNOSUPPORT Internal addressing error *------------------------------------------*/ extern int rfapi_check(rfapi_handle rfd); /*********************************************************************** * NVE Routes ***********************************************************************/ /*------------------------------------------ * rfapi_query * * This function queries the RIB for a * particular route. Note that this call may result in subsequent * callbacks to response_cb. Response callbacks can be cancelled * by calling rfapi_query_done. A duplicate query using the same target * will result in only one callback per change in next_hops. (i.e., * cancel/replace the prior query results.) * * input: * rfd: rfapi descriptor returned by rfapi_open * target: the destination address * l2o ptr to L2 Options struct, NULL if not present in query * * output: * ppNextHopEntry pointer to a location to store a pointer * to the returned list of nexthops. It is the * caller's responsibility to free this list * via rfapi_free_next_hop_list(). * * * return value: * 0 Success * EBADF invalid handle * ENOENT no valid route * ENXIO BGP or VNC not configured * ESTALE descriptor is no longer usable; should be closed * EDEADLK Called from within a callback procedure --------------------------------------------*/ extern int rfapi_query(rfapi_handle rfd, struct rfapi_ip_addr *target, struct rfapi_l2address_option *l2o, struct rfapi_next_hop_entry **ppNextHopEntry); /*------------------------------------------ * rfapi_query_done * * Notifies the rfapi that the user is no longer interested * in the specified target. * * input: * rfd: rfapi descriptor returned by rfapi_open * target: the destination address * * output: * * return value: * 0 Success * EBADF invalid handle * ENOENT no match found for target * ENXIO BGP or VNC not configured * ESTALE descriptor is no longer usable; should be closed * EDEADLK Called from within a callback procedure --------------------------------------------*/ extern int rfapi_query_done(rfapi_handle rfd, struct rfapi_ip_addr *target); /*------------------------------------------ * rfapi_query_done_all * * Notifies the rfapi that the user is no longer interested * in any target. * * input: * rfd: rfapi descriptor returned by rfapi_open * * output: * count: number of queries cleared * * return value: * 0 Success * EBADF invalid handle * ENXIO BGP or VNC not configured * ESTALE descriptor is no longer usable; should be closed * EDEADLK Called from within a callback procedure --------------------------------------------*/ extern int rfapi_query_done_all(rfapi_handle rfd, int *count); /*------------------------------------------ * rfapi_register * * Requests that reachability to the indicated prefix via this NVE * be advertised by BGP. If is non-zero, then the previously- * advertised prefix should be withdrawn. * * (This function should NOT be called if the rfapi_open() function * returns NULL) * * input: * rfd: rfapi descriptor returned by rfapi_open * prefix: A prefix to be registered or deregistered * lifetime Prefix lifetime in seconds, host byte order * options_un underlay netowrk options, may include tunnel-type * Caller owns (rfapi_register() does not free). * options_vn virtual network options, may include layer 2 address * option and local-nexthop option * Caller owns (rfapi_register() does not free). * * action: RFAPI_REGISTER_ADD add the route * RFAPI_REGISTER_WITHDRAW withdraw route * RFAPI_REGISTER_KILL withdraw without holddown * * return value: * 0 Success * EBADF invalid handle * ENXIO BGP or VNC not configured * ESTALE descriptor is no longer usable; should be closed * EDEADLK Called from within a callback procedure --------------------------------------------*/ typedef enum { RFAPI_REGISTER_ADD, RFAPI_REGISTER_WITHDRAW, RFAPI_REGISTER_KILL } rfapi_register_action; extern int rfapi_register(rfapi_handle rfd, struct rfapi_ip_prefix *prefix, uint32_t lifetime, struct rfapi_un_option *options_un, struct rfapi_vn_option *options_vn, rfapi_register_action action); /*********************************************************************** * Helper / Utility functions ***********************************************************************/ /*------------------------------------------ * rfapi_get_vn_addr * * Get the virtual network address used by an NVE based on it's RFD * * input: * rfd: rfapi descriptor returned by rfapi_open or rfapi_create_generic * * output: * * return value: * vn NVE virtual network address *------------------------------------------*/ extern struct rfapi_ip_addr *rfapi_get_vn_addr(void *); /*------------------------------------------ * rfapi_get_un_addr * * Get the underlay network address used by an NVE based on it's RFD * * input: * rfd: rfapi descriptor returned by rfapi_open or rfapi_create_generic * * output: * * return value: * un NVE underlay network address *------------------------------------------*/ extern struct rfapi_ip_addr *rfapi_get_un_addr(void *); /*------------------------------------------ * rfapi_error_str * * Returns a string describing the rfapi error code. * * input: * * code Error code returned by rfapi function * * returns: * * const char * String *------------------------------------------*/ extern const char *rfapi_error_str(int code); /*------------------------------------------ * rfapi_get_rfp_start_val * * Returns value passed to rfapi on rfp_start * * input: * void * bgp structure * * returns: * void * *------------------------------------------*/ extern void *rfapi_get_rfp_start_val(void *bgpv); /*------------------------------------------ * rfapi_compare_rfds * * Compare two generic rfapi descriptors. * * input: * rfd1: rfapi descriptor returned by rfapi_open or rfapi_create_generic * rfd2: rfapi descriptor returned by rfapi_open or rfapi_create_generic * * output: * * return value: * 0 Mismatch * 1 Match *------------------------------------------*/ extern int rfapi_compare_rfds(void *rfd1, void *rfd2); /*------------------------------------------ * rfapi_free_next_hop_list * * Frees a next_hop_list returned by a rfapi_query invocation * * input: * list: a pointer to a response list (as a * struct rfapi_next_hop_entry) to free. * * output: * * return value: None --------------------------------------------*/ extern void rfapi_free_next_hop_list(struct rfapi_next_hop_entry *list); /*------------------------------------------ * rfapi_get_response_lifetime_default * * Returns the default lifetime for a response. * rfp_start_val value returned by rfp_start or * NULL (=use default instance) * * input: * None * * output: * * return value: The bgp instance default lifetime for a response. --------------------------------------------*/ extern int rfapi_get_response_lifetime_default(void *rfp_start_val); /*------------------------------------------ * rfapi_is_vnc_configured * * Returns if VNC is configured * * input: * rfp_start_val value returned by rfp_start or * NULL (=use default instance) * * output: * * return value: If VNC is configured for the bgpd instance * 0 Success * ENXIO VNC not configured --------------------------------------------*/ extern int rfapi_is_vnc_configured(void *rfp_start_val); /*------------------------------------------ * rfapi_bgp_lookup_by_rfp * * Find bgp instance pointer based on value returned by rfp_start * * input: * rfp_start_val value returned by rfp_startor * NULL (=get default instance) * * output: * none * * return value: * bgp bgp instance pointer * NULL = not found * --------------------------------------------*/ extern struct bgp *rfapi_bgp_lookup_by_rfp(void *rfp_start_val); /*------------------------------------------ * rfapi_get_rfp_start_val_by_bgp * * Find bgp instance pointer based on value returned by rfp_start * * input: * bgp bgp instance pointer * * output: * none * * return value: * rfp_start_val * NULL = not found * --------------------------------------------*/ extern void *rfapi_get_rfp_start_val_by_bgp(struct bgp *bgp); #endif /* ENABLE_BGP_VNC */ #endif /* _QUAGGA_BGP_RFAPI_H */ frr-7.2.1/bgpd/rfapi/rfapi_ap.c0000644000000000000000000003226513610377563013166 00000000000000/* * * Copyright 2009-2016, LabN Consulting, L.L.C. * * * This program is free software; you can redistribute it and/or * modify it under the terms of the GNU General Public License * as published by the Free Software Foundation; either version 2 * of the License, or (at your option) any later version. * * 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; see the file COPYING; if not, write to the Free Software * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA */ #include "lib/zebra.h" #include "lib/prefix.h" #include "lib/agg_table.h" #include "lib/vty.h" #include "lib/memory.h" #include "lib/routemap.h" #include "lib/log.h" #include "lib/linklist.h" #include "lib/command.h" #include "lib/stream.h" #include "bgpd/bgpd.h" #include "bgpd/bgp_ecommunity.h" #include "bgpd/bgp_attr.h" #include "bgpd/rfapi/bgp_rfapi_cfg.h" #include "bgpd/rfapi/rfapi.h" #include "bgpd/rfapi/rfapi_backend.h" #include "bgpd/bgp_route.h" #include "bgpd/bgp_mplsvpn.h" #include "bgpd/bgp_aspath.h" #include "bgpd/bgp_advertise.h" #include "bgpd/rfapi/rfapi_import.h" #include "bgpd/rfapi/rfapi_private.h" #include "bgpd/rfapi/rfapi_monitor.h" #include "bgpd/rfapi/rfapi_vty.h" #include "bgpd/rfapi/vnc_export_bgp.h" #include "bgpd/rfapi/vnc_export_bgp_p.h" #include "bgpd/rfapi/vnc_zebra.h" #include "bgpd/rfapi/vnc_import_bgp.h" #include "bgpd/rfapi/rfapi_rib.h" #include "bgpd/rfapi/rfapi_ap.h" #include "bgpd/rfapi/vnc_debug.h" /* * Per-NVE Advertised prefixes * * We maintain a list of prefixes advertised by each NVE. * There are two indices: by prefix and by lifetime. * * BY-PREFIX skiplist * * key: ptr to struct prefix (when storing, point to prefix that * is part of rfapi_adb). * * value: ptr to struct rfapi_adb * * BY-LIFETIME skiplist * * key: ptr to struct rfapi_adb * value: ptr to struct rfapi_adb * */ /* * Skiplist sort function that sorts first according to lifetime * and then according to adb pointer value. The adb pointer * is used to spread out the sort for adbs with the same lifetime * and thereby make the skip list operations more efficient. */ static int sl_adb_lifetime_cmp(void *adb1, void *adb2) { struct rfapi_adb *a1 = adb1; struct rfapi_adb *a2 = adb2; if (a1->lifetime < a2->lifetime) return -1; if (a1->lifetime > a2->lifetime) return 1; if (a1 < a2) return -1; if (a1 > a2) return 1; return 0; } void rfapiApInit(struct rfapi_advertised_prefixes *ap) { ap->ipN_by_prefix = skiplist_new(0, rfapi_rib_key_cmp, NULL); ap->ip0_by_ether = skiplist_new(0, rfapi_rib_key_cmp, NULL); ap->by_lifetime = skiplist_new(0, sl_adb_lifetime_cmp, NULL); } void rfapiApRelease(struct rfapi_advertised_prefixes *ap) { struct rfapi_adb *adb; /* Free ADBs and lifetime items */ while (0 == skiplist_first(ap->by_lifetime, NULL, (void **)&adb)) { rfapiAdbFree(adb); skiplist_delete_first(ap->by_lifetime); } while (0 == skiplist_delete_first(ap->ipN_by_prefix)) ; while (0 == skiplist_delete_first(ap->ip0_by_ether)) ; /* Free lists */ skiplist_free(ap->ipN_by_prefix); skiplist_free(ap->ip0_by_ether); skiplist_free(ap->by_lifetime); ap->ipN_by_prefix = NULL; ap->ip0_by_ether = NULL; ap->by_lifetime = NULL; } int rfapiApCount(struct rfapi_descriptor *rfd) { if (!rfd->advertised.by_lifetime) return 0; return skiplist_count(rfd->advertised.by_lifetime); } int rfapiApCountAll(struct bgp *bgp) { struct rfapi *h; struct listnode *node; struct rfapi_descriptor *rfd; int total = 0; h = bgp->rfapi; if (h) { for (ALL_LIST_ELEMENTS_RO(&h->descriptors, node, rfd)) { total += rfapiApCount(rfd); } } return total; } void rfapiApReadvertiseAll(struct bgp *bgp, struct rfapi_descriptor *rfd) { struct rfapi_adb *adb; void *cursor = NULL; int rc; for (rc = skiplist_next(rfd->advertised.by_lifetime, NULL, (void **)&adb, &cursor); rc == 0; rc = skiplist_next(rfd->advertised.by_lifetime, NULL, (void **)&adb, &cursor)) { struct prefix_rd prd; uint32_t local_pref = rfp_cost_to_localpref(adb->cost); prd = rfd->rd; prd.family = AF_UNSPEC; prd.prefixlen = 64; /* * TBD this is not quite right. When pfx_ip is 0/32 or 0/128, * we need to substitute the VN address as the prefix */ add_vnc_route(rfd, bgp, SAFI_MPLS_VPN, &adb->u.s.prefix_ip, &prd, /* RD to use (0 for ENCAP) */ &rfd->vn_addr, /* nexthop */ &local_pref, &adb->lifetime, NULL, NULL, /* struct rfapi_un_option */ NULL, /* struct rfapi_vn_option */ rfd->rt_export_list, NULL, /* med */ NULL, ZEBRA_ROUTE_BGP, BGP_ROUTE_RFP, 0); } } void rfapiApWithdrawAll(struct bgp *bgp, struct rfapi_descriptor *rfd) { struct rfapi_adb *adb; void *cursor; int rc; cursor = NULL; for (rc = skiplist_next(rfd->advertised.by_lifetime, NULL, (void **)&adb, &cursor); rc == 0; rc = skiplist_next(rfd->advertised.by_lifetime, NULL, (void **)&adb, &cursor)) { struct prefix pfx_vn_buf; struct prefix *pfx_ip; if (!(RFAPI_0_PREFIX(&adb->u.s.prefix_ip) && RFAPI_HOST_PREFIX(&adb->u.s.prefix_ip))) { pfx_ip = &adb->u.s.prefix_ip; } else { pfx_ip = NULL; /* * 0/32 or 0/128 => mac advertisement */ if (rfapiRaddr2Qprefix(&rfd->vn_addr, &pfx_vn_buf)) { /* * Bad: it means we can't delete the route */ vnc_zlog_debug_verbose( "%s: BAD: handle has bad vn_addr: skipping", __func__); continue; } } del_vnc_route(rfd, rfd->peer, bgp, SAFI_MPLS_VPN, pfx_ip ? pfx_ip : &pfx_vn_buf, &adb->u.s.prd, /* RD to use (0 for ENCAP) */ ZEBRA_ROUTE_BGP, BGP_ROUTE_RFP, NULL, 0); } } /* * returns nonzero if tunnel readvertisement is needed, 0 otherwise */ static int rfapiApAdjustLifetimeStats( struct rfapi_descriptor *rfd, uint32_t *old_lifetime, /* set if removing/replacing */ uint32_t *new_lifetime) /* set if replacing/adding */ { int advertise = 0; int find_max = 0; int find_min = 0; vnc_zlog_debug_verbose("%s: rfd=%p, pOldLife=%p, pNewLife=%p", __func__, rfd, old_lifetime, new_lifetime); if (old_lifetime) vnc_zlog_debug_verbose("%s: OldLife=%d", __func__, *old_lifetime); if (new_lifetime) vnc_zlog_debug_verbose("%s: NewLife=%d", __func__, *new_lifetime); if (new_lifetime) { /* * Adding new lifetime */ if (old_lifetime) { /* * replacing existing lifetime */ /* old and new are same */ if (*old_lifetime == *new_lifetime) return 0; if (*old_lifetime == rfd->min_prefix_lifetime) { find_min = 1; } if (*old_lifetime == rfd->max_prefix_lifetime) { find_max = 1; } /* no need to search if new value is at or equals * min|max */ if (*new_lifetime <= rfd->min_prefix_lifetime) { rfd->min_prefix_lifetime = *new_lifetime; find_min = 0; } if (*new_lifetime >= rfd->max_prefix_lifetime) { rfd->max_prefix_lifetime = *new_lifetime; advertise = 1; find_max = 0; } } else { /* * Just adding new lifetime */ if (*new_lifetime < rfd->min_prefix_lifetime) { rfd->min_prefix_lifetime = *new_lifetime; } if (*new_lifetime > rfd->max_prefix_lifetime) { advertise = 1; rfd->max_prefix_lifetime = *new_lifetime; } } } else { /* * Deleting */ /* * See if the max prefix lifetime for this NVE has decreased. * The easy optimization: track min & max; walk the table only * if they are different. * The general optimization: index the advertised_prefixes * table by lifetime. * * Note: for a given nve_descriptor, only one of the * advertised_prefixes[] tables will be used: viz., the * address family that matches the VN address. * */ if (rfd->max_prefix_lifetime == rfd->min_prefix_lifetime) { /* * Common case: all lifetimes are the same. Only * thing we need to do here is check if there are * no exported routes left. In that case, reinitialize * the max and min values. */ if (!rfapiApCount(rfd)) { rfd->max_prefix_lifetime = 0; rfd->min_prefix_lifetime = UINT32_MAX; } } else { if (old_lifetime) { if (*old_lifetime == rfd->min_prefix_lifetime) { find_min = 1; } if (*old_lifetime == rfd->max_prefix_lifetime) { find_max = 1; } } } } if (find_min || find_max) { uint32_t min = UINT32_MAX; uint32_t max = 0; struct rfapi_adb *adb_min; struct rfapi_adb *adb_max; if (!skiplist_first(rfd->advertised.by_lifetime, (void **)&adb_min, NULL) && !skiplist_last(rfd->advertised.by_lifetime, (void **)&adb_max, NULL)) { /* * This should always work */ min = adb_min->lifetime; max = adb_max->lifetime; } else { void *cursor; struct rfapi_rib_key rk; struct rfapi_adb *adb; int rc; vnc_zlog_debug_verbose( "%s: walking to find new min/max", __func__); cursor = NULL; for (rc = skiplist_next(rfd->advertised.ipN_by_prefix, (void **)&rk, (void **)&adb, &cursor); !rc; rc = skiplist_next(rfd->advertised.ipN_by_prefix, (void **)&rk, (void **)&adb, &cursor)) { uint32_t lt = adb->lifetime; if (lt > max) max = lt; if (lt < min) min = lt; } cursor = NULL; for (rc = skiplist_next(rfd->advertised.ip0_by_ether, (void **)&rk, (void **)&adb, &cursor); !rc; rc = skiplist_next(rfd->advertised.ip0_by_ether, (void **)&rk, (void **)&adb, &cursor)) { uint32_t lt = adb->lifetime; if (lt > max) max = lt; if (lt < min) min = lt; } } /* * trigger tunnel route update * but only if we found a VPN route and it had * a lifetime greater than 0 */ if (max && rfd->max_prefix_lifetime != max) advertise = 1; rfd->max_prefix_lifetime = max; rfd->min_prefix_lifetime = min; } vnc_zlog_debug_verbose("%s: returning advertise=%d, min=%d, max=%d", __func__, advertise, rfd->min_prefix_lifetime, rfd->max_prefix_lifetime); return (advertise != 0); } /* * Return Value * * 0 No need to advertise tunnel route * non-0 advertise tunnel route */ int rfapiApAdd(struct bgp *bgp, struct rfapi_descriptor *rfd, struct prefix *pfx_ip, struct prefix *pfx_eth, struct prefix_rd *prd, uint32_t lifetime, uint8_t cost, struct rfapi_l2address_option *l2o) /* other options TBD */ { int rc; struct rfapi_adb *adb; uint32_t old_lifetime = 0; int use_ip0 = 0; struct rfapi_rib_key rk; rfapi_rib_key_init(pfx_ip, prd, pfx_eth, &rk); if (RFAPI_0_PREFIX(pfx_ip) && RFAPI_HOST_PREFIX(pfx_ip)) { use_ip0 = 1; assert(pfx_eth); rc = skiplist_search(rfd->advertised.ip0_by_ether, &rk, (void **)&adb); } else { /* find prefix in advertised prefixes list */ rc = skiplist_search(rfd->advertised.ipN_by_prefix, &rk, (void **)&adb); } if (rc) { /* Not found */ adb = XCALLOC(MTYPE_RFAPI_ADB, sizeof(struct rfapi_adb)); assert(adb); adb->lifetime = lifetime; adb->u.key = rk; if (use_ip0) { assert(pfx_eth); skiplist_insert(rfd->advertised.ip0_by_ether, &adb->u.key, adb); } else { skiplist_insert(rfd->advertised.ipN_by_prefix, &adb->u.key, adb); } skiplist_insert(rfd->advertised.by_lifetime, adb, adb); } else { old_lifetime = adb->lifetime; if (old_lifetime != lifetime) { assert(!skiplist_delete(rfd->advertised.by_lifetime, adb, NULL)); adb->lifetime = lifetime; assert(!skiplist_insert(rfd->advertised.by_lifetime, adb, adb)); } } adb->cost = cost; if (l2o) adb->l2o = *l2o; else memset(&adb->l2o, 0, sizeof(struct rfapi_l2address_option)); if (rfapiApAdjustLifetimeStats(rfd, (rc ? NULL : &old_lifetime), &lifetime)) return 1; return 0; } /* * After this function returns successfully, caller should call * rfapiAdjustLifetimeStats() and possibly rfapiTunnelRouteAnnounce() */ int rfapiApDelete(struct bgp *bgp, struct rfapi_descriptor *rfd, struct prefix *pfx_ip, struct prefix *pfx_eth, struct prefix_rd *prd, int *advertise_tunnel) /* out */ { int rc; struct rfapi_adb *adb; uint32_t old_lifetime; int use_ip0 = 0; struct rfapi_rib_key rk; if (advertise_tunnel) *advertise_tunnel = 0; rfapi_rib_key_init(pfx_ip, prd, pfx_eth, &rk); /* find prefix in advertised prefixes list */ if (RFAPI_0_PREFIX(pfx_ip) && RFAPI_HOST_PREFIX(pfx_ip)) { use_ip0 = 1; assert(pfx_eth); rc = skiplist_search(rfd->advertised.ip0_by_ether, &rk, (void **)&adb); } else { /* find prefix in advertised prefixes list */ rc = skiplist_search(rfd->advertised.ipN_by_prefix, &rk, (void **)&adb); } if (rc) { return ENOENT; } old_lifetime = adb->lifetime; if (use_ip0) { rc = skiplist_delete(rfd->advertised.ip0_by_ether, &rk, NULL); } else { rc = skiplist_delete(rfd->advertised.ipN_by_prefix, &rk, NULL); } assert(!rc); rc = skiplist_delete(rfd->advertised.by_lifetime, adb, NULL); assert(!rc); rfapiAdbFree(adb); if (rfapiApAdjustLifetimeStats(rfd, &old_lifetime, NULL)) { if (advertise_tunnel) *advertise_tunnel = 1; } return 0; } frr-7.2.1/bgpd/rfapi/rfapi_ap.h0000644000000000000000000000463513610377563013173 00000000000000/* * * Copyright 2009-2016, LabN Consulting, L.L.C. * * * This program is free software; you can redistribute it and/or * modify it under the terms of the GNU General Public License * as published by the Free Software Foundation; either version 2 * of the License, or (at your option) any later version. * * 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; see the file COPYING; if not, write to the Free Software * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA */ #ifndef _QUAGGA_BGP_RFAPI_AP_H #define _QUAGGA_BGP_RFAPI_AP_H /* TBD delete some of these #includes */ #include #include "lib/zebra.h" #include "lib/prefix.h" #include "lib/table.h" #include "lib/vty.h" #include "lib/memory.h" #include "lib/routemap.h" #include "lib/log.h" #include "lib/linklist.h" #include "lib/command.h" #include "lib/stream.h" #include "bgpd/bgpd.h" #include "bgp_rfapi_cfg.h" #include "rfapi.h" #include "rfapi_backend.h" #include "bgpd/bgp_route.h" #include "bgpd/bgp_aspath.h" #include "bgpd/bgp_advertise.h" #include "rfapi_import.h" #include "rfapi_private.h" #include "rfapi_monitor.h" #include "rfapi_vty.h" #include "vnc_export_bgp.h" #include "vnc_export_bgp_p.h" #include "vnc_zebra.h" #include "vnc_import_bgp.h" #include "rfapi_rib.h" extern void rfapiApInit(struct rfapi_advertised_prefixes *ap); extern void rfapiApRelease(struct rfapi_advertised_prefixes *ap); extern int rfapiApCount(struct rfapi_descriptor *rfd); extern int rfapiApCountAll(struct bgp *bgp); extern void rfapiApReadvertiseAll(struct bgp *bgp, struct rfapi_descriptor *rfd); extern void rfapiApWithdrawAll(struct bgp *bgp, struct rfapi_descriptor *rfd); extern int rfapiApAdd(struct bgp *bgp, struct rfapi_descriptor *rfd, struct prefix *pfx_ip, struct prefix *pfx_eth, struct prefix_rd *prd, uint32_t lifetime, uint8_t cost, struct rfapi_l2address_option *l2o); /* other options TBD */ extern int rfapiApDelete(struct bgp *bgp, struct rfapi_descriptor *rfd, struct prefix *pfx_ip, struct prefix *pfx_eth, struct prefix_rd *prd, int *advertise_tunnel); /* out */ #endif /* _QUAGGA_BGP_RFAPI_AP_H */ frr-7.2.1/bgpd/rfapi/rfapi_backend.h0000644000000000000000000000451213610377563014154 00000000000000/* * * Copyright 2009-2016, LabN Consulting, L.L.C. * * * This program is free software; you can redistribute it and/or * modify it under the terms of the GNU General Public License * as published by the Free Software Foundation; either version 2 * of the License, or (at your option) any later version. * * 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; see the file COPYING; if not, write to the Free Software * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA */ #ifndef _QUAGGA_BGP_RFAPI_BACKEND_H #define _QUAGGA_BGP_RFAPI_BACKEND_H #if ENABLE_BGP_VNC #include "bgpd/bgp_route.h" #include "bgpd/bgp_nexthop.h" extern void rfapi_init(void); extern void vnc_zebra_init(struct thread_master *master); extern void vnc_zebra_destroy(void); extern void rfapi_delete(struct bgp *); struct rfapi *bgp_rfapi_new(struct bgp *bgp); void bgp_rfapi_destroy(struct bgp *bgp, struct rfapi *h); extern void rfapiProcessUpdate(struct peer *peer, void *rfd, struct prefix *p, struct prefix_rd *prd, struct attr *attr, afi_t afi, safi_t safi, uint8_t type, uint8_t sub_type, uint32_t *label); extern void rfapiProcessWithdraw(struct peer *peer, void *rfd, struct prefix *p, struct prefix_rd *prd, struct attr *attr, afi_t afi, safi_t safi, uint8_t type, int kill); extern void rfapiProcessPeerDown(struct peer *peer); extern void vnc_zebra_announce(struct prefix *p, struct bgp_path_info *new_select, struct bgp *bgp); extern void vnc_zebra_withdraw(struct prefix *p, struct bgp_path_info *old_select); extern void rfapi_vty_out_vncinfo(struct vty *vty, struct prefix *p, struct bgp_path_info *bpi, safi_t safi); extern void vnc_direct_bgp_vpn_enable(struct bgp *bgp, afi_t afi); extern void vnc_direct_bgp_vpn_disable(struct bgp *bgp, afi_t afi); extern void vnc_direct_bgp_rh_vpn_enable(struct bgp *bgp, afi_t afi); extern void vnc_direct_bgp_rh_vpn_disable(struct bgp *bgp, afi_t afi); #endif /* ENABLE_BGP_VNC */ #endif /* _QUAGGA_BGP_RFAPI_BACKEND_H */ frr-7.2.1/bgpd/rfapi/rfapi_descriptor_rfp_utils.c0000644000000000000000000000636413610377563017034 00000000000000/* * * Copyright 2009-2016, LabN Consulting, L.L.C. * * * This program is free software; you can redistribute it and/or * modify it under the terms of the GNU General Public License * as published by the Free Software Foundation; either version 2 * of the License, or (at your option) any later version. * * 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; see the file COPYING; if not, write to the Free Software * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA */ #include "lib/zebra.h" #include "lib/prefix.h" #include "lib/table.h" #include "lib/vty.h" #include "lib/memory.h" #include "lib/log.h" #include "bgpd/bgpd.h" #include "bgpd/rfapi/rfapi.h" #include "bgpd/rfapi/rfapi_private.h" #include "bgpd/rfapi/rfapi_descriptor_rfp_utils.h" #include "bgpd/rfapi/vnc_debug.h" void *rfapi_create_generic(struct rfapi_ip_addr *vn, struct rfapi_ip_addr *un) { struct rfapi_descriptor *rfd; rfd = XCALLOC(MTYPE_RFAPI_DESC, sizeof(struct rfapi_descriptor)); vnc_zlog_debug_verbose("%s: rfd=%p", __func__, rfd); rfd->vn_addr = *vn; rfd->un_addr = *un; return (void *)rfd; } /*------------------------------------------ * rfapi_free_generic * * Compare two generic rfapi descriptors. * * input: * grfd: rfapi descriptor returned by rfapi_open or rfapi_create_generic * * output: * * return value: * *------------------------------------------*/ void rfapi_free_generic(void *grfd) { struct rfapi_descriptor *rfd; rfd = (struct rfapi_descriptor *)grfd; XFREE(MTYPE_RFAPI_DESC, rfd); } /*------------------------------------------ * rfapi_compare_rfds * * Compare two generic rfapi descriptors. * * input: * rfd1: rfapi descriptor returned by rfapi_open or rfapi_create_generic * rfd2: rfapi descriptor returned by rfapi_open or rfapi_create_generic * * output: * * return value: * 0 Mismatch * 1 Match *------------------------------------------*/ int rfapi_compare_rfds(void *rfd1, void *rfd2) { struct rfapi_descriptor *rrfd1, *rrfd2; int match = 0; rrfd1 = (struct rfapi_descriptor *)rfd1; rrfd2 = (struct rfapi_descriptor *)rfd2; if (rrfd1->vn_addr.addr_family == rrfd2->vn_addr.addr_family) { if (rrfd1->vn_addr.addr_family == AF_INET) match = IPV4_ADDR_SAME(&(rrfd1->vn_addr.addr.v4), &(rrfd2->vn_addr.addr.v4)); else match = IPV6_ADDR_SAME(&(rrfd1->vn_addr.addr.v6), &(rrfd2->vn_addr.addr.v6)); } /* * If the VN addresses don't match in all forms, * give up. */ if (!match) return 0; /* * do the process again for the UN addresses. */ match = 0; if (rrfd1->un_addr.addr_family == rrfd2->un_addr.addr_family) { /* VN addresses match * UN address families match * now check the actual UN addresses */ if (rrfd1->un_addr.addr_family == AF_INET) match = IPV4_ADDR_SAME(&(rrfd1->un_addr.addr.v4), &(rrfd2->un_addr.addr.v4)); else match = IPV6_ADDR_SAME(&(rrfd1->un_addr.addr.v6), &(rrfd2->un_addr.addr.v6)); } return match; } frr-7.2.1/bgpd/rfapi/rfapi_descriptor_rfp_utils.h0000644000000000000000000000230713610377563017032 00000000000000/* * * Copyright 2009-2016, LabN Consulting, L.L.C. * * * This program is free software; you can redistribute it and/or * modify it under the terms of the GNU General Public License * as published by the Free Software Foundation; either version 2 * of the License, or (at your option) any later version. * * 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; see the file COPYING; if not, write to the Free Software * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA */ extern void *rfapi_create_generic(struct rfapi_ip_addr *vn, struct rfapi_ip_addr *un); /*------------------------------------------ * rfapi_free_generic * * Compare two generic rfapi descriptors. * * input: * grfd: rfapi descriptor returned by rfapi_open or rfapi_create_generic * * output: * * return value: * *------------------------------------------*/ extern void rfapi_free_generic(void *grfd); frr-7.2.1/bgpd/rfapi/rfapi_encap_tlv.c0000644000000000000000000005021313610377563014532 00000000000000/* * Copyright 2015-2016, LabN Consulting, L.L.C. * * This program is free software; you can redistribute it and/or * modify it under the terms of the GNU General Public License * as published by the Free Software Foundation; either version 2 * of the License, or (at your option) any later version. * * 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; see the file COPYING; if not, write to the Free Software * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA */ #include "lib/zebra.h" #include "lib/memory.h" #include "lib/prefix.h" #include "lib/table.h" #include "lib/vty.h" #include "bgpd/bgpd.h" #include "bgpd/bgp_attr.h" #include "bgpd/bgp_encap_types.h" #include "bgpd/bgp_encap_tlv.h" #include "bgpd/rfapi/rfapi.h" #include "bgpd/rfapi/rfapi_encap_tlv.h" #include "bgpd/rfapi/rfapi_private.h" #include "bgpd/rfapi/rfapi_monitor.h" #include "bgpd/rfapi/rfapi_vty.h" #include "bgpd/rfapi/bgp_rfapi_cfg.h" #include "bgpd/rfapi/vnc_debug.h" static void rfapi_add_endpoint_address_to_subtlv( struct bgp *bgp, struct rfapi_ip_addr *ea, struct bgp_tea_subtlv_remote_endpoint *subtlv) { subtlv->family = ea->addr_family; if (subtlv->family == AF_INET) subtlv->ip_address.v4 = ea->addr.v4; else subtlv->ip_address.v6 = ea->addr.v6; subtlv->as4 = htonl(bgp->as); } bgp_encap_types rfapi_tunneltype_option_to_tlv(struct bgp *bgp, struct rfapi_ip_addr *ea, struct rfapi_tunneltype_option *tto, struct attr *attr, int always_add) { #define _RTTO_MAYBE_ADD_ENDPOINT_ADDRESS(ttype) \ if ((always_add \ || (bgp->rfapi_cfg \ && !CHECK_FLAG(bgp->rfapi_cfg->flags, \ BGP_VNC_CONFIG_ADV_UN_METHOD_ENCAP))) \ && ea \ && !CHECK_SUBTLV_FLAG(&tto->bgpinfo.ttype, \ BGP_TEA_SUBTLV_REMOTE_ENDPOINT)) { \ rfapi_add_endpoint_address_to_subtlv( \ bgp, ea, &tto->bgpinfo.ttype.st_endpoint); \ SET_SUBTLV_FLAG(&tto->bgpinfo.ttype, \ BGP_TEA_SUBTLV_REMOTE_ENDPOINT); \ } struct rfapi_tunneltype_option dto; if (tto == NULL) { /* create default type */ tto = &dto; memset(tto, 0, sizeof(dto)); tto->type = RFAPI_BGP_ENCAP_TYPE_DEFAULT; } switch (tto->type) { case BGP_ENCAP_TYPE_L2TPV3_OVER_IP: _RTTO_MAYBE_ADD_ENDPOINT_ADDRESS(l2tpv3_ip); bgp_encap_type_l2tpv3overip_to_tlv(&tto->bgpinfo.l2tpv3_ip, attr); break; case BGP_ENCAP_TYPE_GRE: _RTTO_MAYBE_ADD_ENDPOINT_ADDRESS(gre); bgp_encap_type_gre_to_tlv(&tto->bgpinfo.gre, attr); break; case BGP_ENCAP_TYPE_TRANSMIT_TUNNEL_ENDPOINT: _RTTO_MAYBE_ADD_ENDPOINT_ADDRESS(transmit_tunnel_endpoint); bgp_encap_type_transmit_tunnel_endpoint( &tto->bgpinfo.transmit_tunnel_endpoint, attr); break; case BGP_ENCAP_TYPE_IPSEC_IN_TUNNEL_MODE: _RTTO_MAYBE_ADD_ENDPOINT_ADDRESS(ipsec_tunnel); bgp_encap_type_ipsec_in_tunnel_mode_to_tlv( &tto->bgpinfo.ipsec_tunnel, attr); break; case BGP_ENCAP_TYPE_IP_IN_IP_TUNNEL_WITH_IPSEC_TRANSPORT_MODE: _RTTO_MAYBE_ADD_ENDPOINT_ADDRESS(ip_ipsec); bgp_encap_type_ip_in_ip_tunnel_with_ipsec_transport_mode_to_tlv( &tto->bgpinfo.ip_ipsec, attr); break; case BGP_ENCAP_TYPE_MPLS_IN_IP_TUNNEL_WITH_IPSEC_TRANSPORT_MODE: _RTTO_MAYBE_ADD_ENDPOINT_ADDRESS(mpls_ipsec); bgp_encap_type_mpls_in_ip_tunnel_with_ipsec_transport_mode_to_tlv( &tto->bgpinfo.mpls_ipsec, attr); break; case BGP_ENCAP_TYPE_IP_IN_IP: _RTTO_MAYBE_ADD_ENDPOINT_ADDRESS(ip_ip); bgp_encap_type_ip_in_ip_to_tlv(&tto->bgpinfo.ip_ip, attr); break; case BGP_ENCAP_TYPE_VXLAN: _RTTO_MAYBE_ADD_ENDPOINT_ADDRESS(vxlan); bgp_encap_type_vxlan_to_tlv(&tto->bgpinfo.vxlan, attr); break; case BGP_ENCAP_TYPE_NVGRE: _RTTO_MAYBE_ADD_ENDPOINT_ADDRESS(nvgre); bgp_encap_type_nvgre_to_tlv(&tto->bgpinfo.nvgre, attr); break; case BGP_ENCAP_TYPE_MPLS: /* nothing to do for MPLS */ break; case BGP_ENCAP_TYPE_MPLS_IN_GRE: _RTTO_MAYBE_ADD_ENDPOINT_ADDRESS(mpls_gre); bgp_encap_type_mpls_in_gre_to_tlv(&tto->bgpinfo.mpls_gre, attr); break; case BGP_ENCAP_TYPE_VXLAN_GPE: _RTTO_MAYBE_ADD_ENDPOINT_ADDRESS(vxlan_gpe); bgp_encap_type_vxlan_gpe_to_tlv(&tto->bgpinfo.vxlan_gpe, attr); break; case BGP_ENCAP_TYPE_MPLS_IN_UDP: _RTTO_MAYBE_ADD_ENDPOINT_ADDRESS(mpls_udp); bgp_encap_type_mpls_in_udp_to_tlv(&tto->bgpinfo.mpls_udp, attr); break; case BGP_ENCAP_TYPE_PBB: _RTTO_MAYBE_ADD_ENDPOINT_ADDRESS(pbb); bgp_encap_type_pbb_to_tlv(&tto->bgpinfo.pbb, attr); break; default: assert(0); } return tto->type; } struct rfapi_un_option *rfapi_encap_tlv_to_un_option(struct attr *attr) { struct rfapi_un_option *uo = NULL; struct rfapi_tunneltype_option *tto; int rc; struct bgp_attr_encap_subtlv *stlv; /* no tunnel encap attr stored */ if (!attr->encap_tunneltype) return NULL; stlv = attr->encap_subtlvs; uo = XCALLOC(MTYPE_RFAPI_UN_OPTION, sizeof(struct rfapi_un_option)); assert(uo); uo->type = RFAPI_UN_OPTION_TYPE_TUNNELTYPE; uo->v.tunnel.type = attr->encap_tunneltype; tto = &uo->v.tunnel; switch (attr->encap_tunneltype) { case BGP_ENCAP_TYPE_L2TPV3_OVER_IP: rc = tlv_to_bgp_encap_type_l2tpv3overip( stlv, &tto->bgpinfo.l2tpv3_ip); break; case BGP_ENCAP_TYPE_GRE: rc = tlv_to_bgp_encap_type_gre(stlv, &tto->bgpinfo.gre); break; case BGP_ENCAP_TYPE_TRANSMIT_TUNNEL_ENDPOINT: rc = tlv_to_bgp_encap_type_transmit_tunnel_endpoint( stlv, &tto->bgpinfo.transmit_tunnel_endpoint); break; case BGP_ENCAP_TYPE_IPSEC_IN_TUNNEL_MODE: rc = tlv_to_bgp_encap_type_ipsec_in_tunnel_mode( stlv, &tto->bgpinfo.ipsec_tunnel); break; case BGP_ENCAP_TYPE_IP_IN_IP_TUNNEL_WITH_IPSEC_TRANSPORT_MODE: rc = tlv_to_bgp_encap_type_ip_in_ip_tunnel_with_ipsec_transport_mode( stlv, &tto->bgpinfo.ip_ipsec); break; case BGP_ENCAP_TYPE_MPLS_IN_IP_TUNNEL_WITH_IPSEC_TRANSPORT_MODE: rc = tlv_to_bgp_encap_type_mpls_in_ip_tunnel_with_ipsec_transport_mode( stlv, &tto->bgpinfo.mpls_ipsec); break; case BGP_ENCAP_TYPE_IP_IN_IP: rc = tlv_to_bgp_encap_type_ip_in_ip(stlv, &tto->bgpinfo.ip_ip); break; case BGP_ENCAP_TYPE_VXLAN: rc = tlv_to_bgp_encap_type_vxlan(stlv, &tto->bgpinfo.vxlan); break; case BGP_ENCAP_TYPE_NVGRE: rc = tlv_to_bgp_encap_type_nvgre(stlv, &tto->bgpinfo.nvgre); break; case BGP_ENCAP_TYPE_MPLS: rc = tlv_to_bgp_encap_type_mpls(stlv, &tto->bgpinfo.mpls); break; case BGP_ENCAP_TYPE_MPLS_IN_GRE: rc = tlv_to_bgp_encap_type_mpls_in_gre(stlv, &tto->bgpinfo.mpls_gre); break; case BGP_ENCAP_TYPE_VXLAN_GPE: rc = tlv_to_bgp_encap_type_vxlan_gpe(stlv, &tto->bgpinfo.vxlan_gpe); break; case BGP_ENCAP_TYPE_MPLS_IN_UDP: rc = tlv_to_bgp_encap_type_mpls_in_udp(stlv, &tto->bgpinfo.mpls_udp); break; case BGP_ENCAP_TYPE_PBB: rc = tlv_to_bgp_encap_type_pbb(stlv, &tto->bgpinfo.pbb); break; default: vnc_zlog_debug_verbose("%s: unknown tunnel type %d", __func__, attr->encap_tunneltype); rc = -1; break; } if (rc) { XFREE(MTYPE_RFAPI_UN_OPTION, uo); uo = NULL; } return uo; } /*********************************************************************** * SUBTLV PRINT ***********************************************************************/ static void subtlv_print_encap_l2tpv3_over_ip( void *stream, int column_offset, struct bgp_tea_subtlv_encap_l2tpv3_over_ip *st) { int (*fp)(void *, const char *, ...); struct vty *vty; void *out; const char *vty_newline; if (rfapiStream2Vty(stream, &fp, &vty, &out, &vty_newline) == 0) return; if (!st) return; fp(out, "%*s%s%s", column_offset, "", "SubTLV: Encap(L2TPv3 over IP)", vty_newline); fp(out, "%*s SessionID: %d%s", column_offset, "", st->sessionid, vty_newline); fp(out, "%*s Cookie: (length %d)%s", column_offset, "", st->cookie_length, vty_newline); } static void subtlv_print_encap_gre(void *stream, int column_offset, struct bgp_tea_subtlv_encap_gre_key *st) { int (*fp)(void *, const char *, ...); struct vty *vty; void *out; const char *vty_newline; if (rfapiStream2Vty(stream, &fp, &vty, &out, &vty_newline) == 0) return; if (!st) return; fp(out, "%*s%s%s", column_offset, "", "SubTLV: Encap(GRE)", vty_newline); fp(out, "%*s GRE key: %d (0x%x)%s", column_offset, "", st->gre_key, st->gre_key, vty_newline); } static void subtlv_print_encap_pbb(void *stream, int column_offset, struct bgp_tea_subtlv_encap_pbb *st) { int (*fp)(void *, const char *, ...); struct vty *vty; void *out; const char *vty_newline; if (rfapiStream2Vty(stream, &fp, &vty, &out, &vty_newline) == 0) return; if (!st) return; fp(out, "%*s%s%s", column_offset, "", "SubTLV: Encap(PBB)", vty_newline); if (st->flag_isid) { fp(out, "%*s ISID: %d (0x%x)%s", column_offset, "", st->isid, st->isid, vty_newline); } if (st->flag_vid) { fp(out, "%*s VID: %d (0x%x)%s", column_offset, "", st->vid, st->vid, vty_newline); } fp(out, "%*s MACADDR %02x:%02x:%02x:%02x:%02x:%02x%s", column_offset, "", st->macaddr[0], st->macaddr[1], st->macaddr[2], st->macaddr[3], st->macaddr[4], st->macaddr[5], vty_newline); } static void subtlv_print_proto_type(void *stream, int column_offset, struct bgp_tea_subtlv_proto_type *st) { int (*fp)(void *, const char *, ...); struct vty *vty; void *out; const char *vty_newline; if (rfapiStream2Vty(stream, &fp, &vty, &out, &vty_newline) == 0) return; if (!st) return; fp(out, "%*s%s%s", column_offset, "", "SubTLV: Encap(Proto Type)", vty_newline); fp(out, "%*s Proto %d (0x%x)%s", column_offset, "", st->proto, st->proto, vty_newline); } static void subtlv_print_color(void *stream, int column_offset, struct bgp_tea_subtlv_color *st) { int (*fp)(void *, const char *, ...); struct vty *vty; void *out; const char *vty_newline; if (rfapiStream2Vty(stream, &fp, &vty, &out, &vty_newline) == 0) return; if (!st) return; fp(out, "%*s%s%s", column_offset, "", "SubTLV: Color", vty_newline); fp(out, "%*s Color: %d (0x%x)", column_offset, "", st->color, st->color, vty_newline); } static void subtlv_print_ipsec_ta(void *stream, int column_offset, struct bgp_tea_subtlv_ipsec_ta *st) { int (*fp)(void *, const char *, ...); struct vty *vty; void *out; const char *vty_newline; if (rfapiStream2Vty(stream, &fp, &vty, &out, &vty_newline) == 0) return; if (!st) return; fp(out, "%*s%s%s", column_offset, "", "SubTLV: IPSEC TA", vty_newline); fp(out, "%*s Authenticator Type: %d (0x%x)", column_offset, "", st->authenticator_type, st->authenticator_type, vty_newline); fp(out, "%*s Authenticator: (length %d)", column_offset, "", st->authenticator_length, vty_newline); } /*********************************************************************** * TLV PRINT ***********************************************************************/ static void print_encap_type_l2tpv3overip(void *stream, int column_offset, struct bgp_encap_type_l2tpv3_over_ip *bet) { const char *type = "L2TPv3 over IP"; int (*fp)(void *, const char *, ...); struct vty *vty; void *out; const char *vty_newline; if (rfapiStream2Vty(stream, &fp, &vty, &out, &vty_newline) == 0) return; if (!bet) return; fp(out, "%*sTEA type %s%s", column_offset, "", type, vty_newline); subtlv_print_encap_l2tpv3_over_ip(stream, column_offset + 2, &bet->st_encap); subtlv_print_proto_type(stream, column_offset + 2, &bet->st_proto); subtlv_print_color(stream, column_offset + 2, &bet->st_color); } static void print_encap_type_gre(void *stream, int column_offset, struct bgp_encap_type_gre *bet) { const char *type = "GRE"; int (*fp)(void *, const char *, ...); struct vty *vty; void *out; const char *vty_newline; if (rfapiStream2Vty(stream, &fp, &vty, &out, &vty_newline) == 0) return; if (!bet) return; fp(out, "%*sTEA type %s%s", column_offset, "", type, vty_newline); subtlv_print_encap_gre(stream, column_offset + 2, &bet->st_encap); subtlv_print_proto_type(stream, column_offset + 2, &bet->st_proto); subtlv_print_color(stream, column_offset + 2, &bet->st_color); } static void print_encap_type_ip_in_ip(void *stream, int column_offset, struct bgp_encap_type_ip_in_ip *bet) { const char *type = "IP in IP"; int (*fp)(void *, const char *, ...); struct vty *vty; void *out; const char *vty_newline; if (rfapiStream2Vty(stream, &fp, &vty, &out, &vty_newline) == 0) return; if (!bet) return; fp(out, "%*sTEA type %s%s", column_offset, "", type, vty_newline); subtlv_print_proto_type(stream, column_offset + 2, &bet->st_proto); subtlv_print_color(stream, column_offset + 2, &bet->st_color); } static void print_encap_type_transmit_tunnel_endpoint( void *stream, int column_offset, struct bgp_encap_type_transmit_tunnel_endpoint *bet) { const char *type = "Transmit Tunnel Endpoint"; int (*fp)(void *, const char *, ...); struct vty *vty; void *out; const char *vty_newline; if (rfapiStream2Vty(stream, &fp, &vty, &out, &vty_newline) == 0) return; if (!bet) return; fp(out, "%*sTEA type %s%s", column_offset, "", type, vty_newline); /* no subtlvs for this type */ } static void print_encap_type_ipsec_in_tunnel_mode( void *stream, int column_offset, struct bgp_encap_type_ipsec_in_tunnel_mode *bet) { const char *type = "IPSEC in Tunnel mode"; int (*fp)(void *, const char *, ...); struct vty *vty; void *out; const char *vty_newline; if (rfapiStream2Vty(stream, &fp, &vty, &out, &vty_newline) == 0) return; if (!bet) return; fp(out, "%*sTEA type %s%s", column_offset, "", type, vty_newline); subtlv_print_ipsec_ta(stream, column_offset + 2, &bet->st_ipsec_ta); } static void print_encap_type_ip_in_ip_tunnel_with_ipsec_transport_mode( void *stream, int column_offset, struct bgp_encap_type_ip_in_ip_tunnel_with_ipsec_transport_mode *bet) { const char *type = "IP in IP Tunnel with IPSEC transport mode"; int (*fp)(void *, const char *, ...); struct vty *vty; void *out; const char *vty_newline; if (rfapiStream2Vty(stream, &fp, &vty, &out, &vty_newline) == 0) return; if (!bet) return; fp(out, "%*sTEA type %s%s", column_offset, "", type, vty_newline); subtlv_print_ipsec_ta(stream, column_offset + 2, &bet->st_ipsec_ta); } static void print_encap_type_mpls_in_ip_tunnel_with_ipsec_transport_mode( void *stream, int column_offset, struct bgp_encap_type_mpls_in_ip_tunnel_with_ipsec_transport_mode *bet) { const char *type = "MPLS in IP Tunnel with IPSEC transport mode"; int (*fp)(void *, const char *, ...); struct vty *vty; void *out; const char *vty_newline; if (rfapiStream2Vty(stream, &fp, &vty, &out, &vty_newline) == 0) return; if (!bet) return; fp(out, "%*sTEA type %s%s", column_offset, "", type, vty_newline); subtlv_print_ipsec_ta(stream, column_offset + 2, &bet->st_ipsec_ta); } static void print_encap_type_pbb(void *stream, int column_offset, struct bgp_encap_type_pbb *bet) { const char *type = "PBB"; int (*fp)(void *, const char *, ...); struct vty *vty; void *out; const char *vty_newline; if (rfapiStream2Vty(stream, &fp, &vty, &out, &vty_newline) == 0) return; if (!bet) return; fp(out, "%*sTEA type %s%s", column_offset, "", type, vty_newline); subtlv_print_encap_pbb(stream, column_offset + 2, &bet->st_encap); } static void print_encap_type_vxlan(void *stream, int column_offset, struct bgp_encap_type_vxlan *bet) { const char *type = "VXLAN"; int (*fp)(void *, const char *, ...); struct vty *vty; void *out; const char *vty_newline; if (rfapiStream2Vty(stream, &fp, &vty, &out, &vty_newline) == 0) return; if (!bet) return; fp(out, "%*sTEA type %s%s", column_offset, "", type, vty_newline); /* no subtlvs for this type */ } static void print_encap_type_nvgre(void *stream, int column_offset, struct bgp_encap_type_nvgre *bet) { const char *type = "NVGRE"; int (*fp)(void *, const char *, ...); struct vty *vty; void *out; const char *vty_newline; if (rfapiStream2Vty(stream, &fp, &vty, &out, &vty_newline) == 0) return; if (!bet) return; fp(out, "%*sTEA type %s%s", column_offset, "", type, vty_newline); /* no subtlvs for this type */ } static void print_encap_type_mpls(void *stream, int column_offset, struct bgp_encap_type_mpls *bet) { const char *type = "MPLS"; int (*fp)(void *, const char *, ...); struct vty *vty; void *out; const char *vty_newline; if (rfapiStream2Vty(stream, &fp, &vty, &out, &vty_newline) == 0) return; if (!bet) return; fp(out, "%*sTEA type %s%s", column_offset, "", type, vty_newline); /* no subtlvs for this type */ } static void print_encap_type_mpls_in_gre(void *stream, int column_offset, struct bgp_encap_type_mpls_in_gre *bet) { const char *type = "MPLS in GRE"; int (*fp)(void *, const char *, ...); struct vty *vty; void *out; const char *vty_newline; if (rfapiStream2Vty(stream, &fp, &vty, &out, &vty_newline) == 0) return; if (!bet) return; fp(out, "%*sTEA type %s%s", column_offset, "", type, vty_newline); /* no subtlvs for this type */ } static void print_encap_type_vxlan_gpe(void *stream, int column_offset, struct bgp_encap_type_vxlan_gpe *bet) { const char *type = "VXLAN GPE"; int (*fp)(void *, const char *, ...); struct vty *vty; void *out; const char *vty_newline; if (rfapiStream2Vty(stream, &fp, &vty, &out, &vty_newline) == 0) return; if (!bet) return; fp(out, "%*sTEA type %s%s", column_offset, "", type, vty_newline); /* no subtlvs for this type */ } static void print_encap_type_mpls_in_udp(void *stream, int column_offset, struct bgp_encap_type_mpls_in_udp *bet) { const char *type = "MPLS in UDP"; int (*fp)(void *, const char *, ...); struct vty *vty; void *out; const char *vty_newline; if (rfapiStream2Vty(stream, &fp, &vty, &out, &vty_newline) == 0) return; if (!bet) return; fp(out, "%*sTEA type %s%s", column_offset, "", type, vty_newline); /* no subtlvs for this type */ } void rfapi_print_tunneltype_option(void *stream, int column_offset, struct rfapi_tunneltype_option *tto) { switch (tto->type) { case BGP_ENCAP_TYPE_L2TPV3_OVER_IP: print_encap_type_l2tpv3overip(stream, column_offset, &tto->bgpinfo.l2tpv3_ip); break; case BGP_ENCAP_TYPE_GRE: print_encap_type_gre(stream, column_offset, &tto->bgpinfo.gre); break; case BGP_ENCAP_TYPE_TRANSMIT_TUNNEL_ENDPOINT: print_encap_type_transmit_tunnel_endpoint( stream, column_offset, &tto->bgpinfo.transmit_tunnel_endpoint); break; case BGP_ENCAP_TYPE_IPSEC_IN_TUNNEL_MODE: print_encap_type_ipsec_in_tunnel_mode( stream, column_offset, &tto->bgpinfo.ipsec_tunnel); break; case BGP_ENCAP_TYPE_IP_IN_IP_TUNNEL_WITH_IPSEC_TRANSPORT_MODE: print_encap_type_ip_in_ip_tunnel_with_ipsec_transport_mode( stream, column_offset, &tto->bgpinfo.ip_ipsec); break; case BGP_ENCAP_TYPE_MPLS_IN_IP_TUNNEL_WITH_IPSEC_TRANSPORT_MODE: print_encap_type_mpls_in_ip_tunnel_with_ipsec_transport_mode( stream, column_offset, &tto->bgpinfo.mpls_ipsec); break; case BGP_ENCAP_TYPE_IP_IN_IP: print_encap_type_ip_in_ip(stream, column_offset, &tto->bgpinfo.ip_ip); break; case BGP_ENCAP_TYPE_VXLAN: print_encap_type_vxlan(stream, column_offset, &tto->bgpinfo.vxlan); break; case BGP_ENCAP_TYPE_NVGRE: print_encap_type_nvgre(stream, column_offset, &tto->bgpinfo.nvgre); break; case BGP_ENCAP_TYPE_MPLS: print_encap_type_mpls(stream, column_offset, &tto->bgpinfo.mpls); break; case BGP_ENCAP_TYPE_MPLS_IN_GRE: print_encap_type_mpls_in_gre(stream, column_offset, &tto->bgpinfo.mpls_gre); break; case BGP_ENCAP_TYPE_VXLAN_GPE: print_encap_type_vxlan_gpe(stream, column_offset, &tto->bgpinfo.vxlan_gpe); break; case BGP_ENCAP_TYPE_MPLS_IN_UDP: print_encap_type_mpls_in_udp(stream, column_offset, &tto->bgpinfo.mpls_udp); break; case BGP_ENCAP_TYPE_PBB: print_encap_type_pbb(stream, column_offset, &tto->bgpinfo.pbb); break; default: assert(0); } } frr-7.2.1/bgpd/rfapi/rfapi_encap_tlv.h0000644000000000000000000000253113610377563014537 00000000000000/* * Copyright 2015-2016, LabN Consulting, L.L.C. * * This program is free software; you can redistribute it and/or * modify it under the terms of the GNU General Public License * as published by the Free Software Foundation; either version 2 * of the License, or (at your option) any later version. * * 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; see the file COPYING; if not, write to the Free Software * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA */ #ifndef _QUAGGA_BGP_RFAPI_ENCAP_TLV_H #define _QUAGGA_BGP_RFAPI_ENCAP_TLV_H #define RFAPI_BGP_ENCAP_TYPE_DEFAULT BGP_ENCAP_TYPE_IP_IN_IP extern bgp_encap_types rfapi_tunneltype_option_to_tlv(struct bgp *bgp, struct rfapi_ip_addr *ea, struct rfapi_tunneltype_option *tto, struct attr *attr, int always_add); extern struct rfapi_un_option *rfapi_encap_tlv_to_un_option(struct attr *attr); extern void rfapi_print_tunneltype_option(void *stream, int column_offset, struct rfapi_tunneltype_option *tto); #endif /* _QUAGGA_BGP_RFAPI_ENCAP_TLV_H */ frr-7.2.1/bgpd/rfapi/rfapi_import.c0000644000000000000000000035302513610377563014100 00000000000000/* * * Copyright 2009-2016, LabN Consulting, L.L.C. * * * This program is free software; you can redistribute it and/or * modify it under the terms of the GNU General Public License * as published by the Free Software Foundation; either version 2 * of the License, or (at your option) any later version. * * 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; see the file COPYING; if not, write to the Free Software * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA */ /* * File: rfapi_import.c * Purpose: Handle import of routes from BGP to RFAPI */ #include "lib/zebra.h" #include "lib/prefix.h" #include "lib/agg_table.h" #include "lib/vty.h" #include "lib/memory.h" #include "lib/log.h" #include "lib/skiplist.h" #include "lib/thread.h" #include "lib/stream.h" #include "lib/lib_errors.h" #include "bgpd/bgpd.h" #include "bgpd/bgp_ecommunity.h" #include "bgpd/bgp_attr.h" #include "bgpd/bgp_route.h" #include "bgpd/bgp_mplsvpn.h" /* prefix_rd2str() */ #include "bgpd/bgp_vnc_types.h" #include "bgpd/bgp_rd.h" #include "bgpd/rfapi/rfapi.h" #include "bgpd/rfapi/bgp_rfapi_cfg.h" #include "bgpd/rfapi/rfapi_backend.h" #include "bgpd/rfapi/rfapi_import.h" #include "bgpd/rfapi/rfapi_private.h" #include "bgpd/rfapi/rfapi_monitor.h" #include "bgpd/rfapi/rfapi_nve_addr.h" #include "bgpd/rfapi/rfapi_vty.h" #include "bgpd/rfapi/vnc_export_bgp.h" #include "bgpd/rfapi/vnc_export_bgp_p.h" #include "bgpd/rfapi/vnc_zebra.h" #include "bgpd/rfapi/vnc_import_bgp.h" #include "bgpd/rfapi/vnc_import_bgp_p.h" #include "bgpd/rfapi/rfapi_rib.h" #include "bgpd/rfapi/rfapi_encap_tlv.h" #include "bgpd/rfapi/vnc_debug.h" #ifdef HAVE_GLIBC_BACKTRACE /* for backtrace and friends */ #include #endif /* HAVE_GLIBC_BACKTRACE */ #undef DEBUG_MONITOR_MOVE_SHORTER #undef DEBUG_RETURNED_NHL #undef DEBUG_ROUTE_COUNTERS #undef DEBUG_ENCAP_MONITOR #undef DEBUG_L2_EXTRA #undef DEBUG_IT_NODES #undef DEBUG_BI_SEARCH /* * Allocated for each withdraw timer instance; freed when the timer * expires or is canceled */ struct rfapi_withdraw { struct rfapi_import_table *import_table; struct agg_node *node; struct bgp_path_info *info; safi_t safi; /* used only for bulk operations */ /* * For import table node reference count checking (i.e., debugging). * Normally when a timer expires, lockoffset should be 0. However, if * the timer expiration function is called directly (e.g., * rfapiExpireVpnNow), the node could be locked by a preceding * agg_route_top() or agg_route_next() in a loop, so we need to pass * this value in. */ int lockoffset; }; /* * DEBUG FUNCTION * It's evil and fiendish. It's compiler-dependent. * ? Might need LDFLAGS -rdynamic to produce all function names */ void rfapiDebugBacktrace(void) { #ifdef HAVE_GLIBC_BACKTRACE #define RFAPI_DEBUG_BACKTRACE_NENTRIES 200 void *buf[RFAPI_DEBUG_BACKTRACE_NENTRIES]; char **syms; size_t i; size_t size; size = backtrace(buf, RFAPI_DEBUG_BACKTRACE_NENTRIES); syms = backtrace_symbols(buf, size); for (i = 0; i < size && i < RFAPI_DEBUG_BACKTRACE_NENTRIES; ++i) { vnc_zlog_debug_verbose("backtrace[%2zu]: %s", i, syms[i]); } free(syms); #else #endif } /* * DEBUG FUNCTION * Count remote routes and compare with actively-maintained values. * Abort if they disagree. */ void rfapiCheckRouteCount(void) { struct bgp *bgp = bgp_get_default(); struct rfapi *h; struct rfapi_import_table *it; afi_t afi; assert(bgp); h = bgp->rfapi; assert(h); for (it = h->imports; it; it = it->next) { for (afi = AFI_IP; afi < AFI_MAX; ++afi) { struct agg_table *rt; struct agg_node *rn; int holddown_count = 0; int local_count = 0; int imported_count = 0; int remote_count = 0; rt = it->imported_vpn[afi]; for (rn = agg_route_top(rt); rn; rn = agg_route_next(rn)) { struct bgp_path_info *bpi; struct bgp_path_info *next; for (bpi = rn->info; bpi; bpi = next) { next = bpi->next; if (CHECK_FLAG(bpi->flags, BGP_PATH_REMOVED)) { ++holddown_count; } else { if (RFAPI_LOCAL_BI(bpi)) { ++local_count; } else { if (RFAPI_DIRECT_IMPORT_BI( bpi)) { ++imported_count; } else { ++remote_count; } } } } } if (it->holddown_count[afi] != holddown_count) { vnc_zlog_debug_verbose( "%s: it->holddown_count %d != holddown_count %d", __func__, it->holddown_count[afi], holddown_count); assert(0); } if (it->remote_count[afi] != remote_count) { vnc_zlog_debug_verbose( "%s: it->remote_count %d != remote_count %d", __func__, it->remote_count[afi], remote_count); assert(0); } if (it->imported_count[afi] != imported_count) { vnc_zlog_debug_verbose( "%s: it->imported_count %d != imported_count %d", __func__, it->imported_count[afi], imported_count); assert(0); } } } } #if DEBUG_ROUTE_COUNTERS #define VNC_ITRCCK do {rfapiCheckRouteCount();} while (0) #else #define VNC_ITRCCK #endif /* * Validate reference count for a node in an import table * * Normally lockoffset is 0 for nodes in quiescent state. However, * agg_unlock_node will delete the node if it is called when * node->lock == 1, and we have to validate the refcount before * the node is deleted. In this case, we specify lockoffset 1. */ void rfapiCheckRefcount(struct agg_node *rn, safi_t safi, int lockoffset) { unsigned int count_bpi = 0; unsigned int count_monitor = 0; struct bgp_path_info *bpi; struct rfapi_monitor_encap *hme; struct rfapi_monitor_vpn *hmv; for (bpi = rn->info; bpi; bpi = bpi->next) ++count_bpi; if (rn->aggregate) { ++count_monitor; /* rfapi_it_extra */ switch (safi) { void *cursor; int rc; case SAFI_ENCAP: for (hme = RFAPI_MONITOR_ENCAP(rn); hme; hme = hme->next) ++count_monitor; break; case SAFI_MPLS_VPN: for (hmv = RFAPI_MONITOR_VPN(rn); hmv; hmv = hmv->next) ++count_monitor; if (RFAPI_MONITOR_EXTERIOR(rn)->source) { ++count_monitor; /* sl */ cursor = NULL; for (rc = skiplist_next( RFAPI_MONITOR_EXTERIOR(rn)->source, NULL, NULL, &cursor); !rc; rc = skiplist_next( RFAPI_MONITOR_EXTERIOR(rn)->source, NULL, NULL, &cursor)) { ++count_monitor; /* sl entry */ } } break; default: assert(0); } } if (count_bpi + count_monitor + lockoffset != rn->lock) { vnc_zlog_debug_verbose( "%s: count_bpi=%d, count_monitor=%d, lockoffset=%d, rn->lock=%d", __func__, count_bpi, count_monitor, lockoffset, rn->lock); assert(0); } } /* * Perform deferred rfapi_close operations that were queued * during callbacks. */ static wq_item_status rfapi_deferred_close_workfunc(struct work_queue *q, void *data) { struct rfapi_descriptor *rfd = data; struct rfapi *h = q->spec.data; assert(!(h->flags & RFAPI_INCALLBACK)); rfapi_close(rfd); vnc_zlog_debug_verbose("%s: completed deferred close on handle %p", __func__, rfd); return WQ_SUCCESS; } /* * Extract layer 2 option from Encap TLVS in BGP attrs */ int rfapiGetL2o(struct attr *attr, struct rfapi_l2address_option *l2o) { if (attr) { struct bgp_attr_encap_subtlv *pEncap; for (pEncap = attr->vnc_subtlvs; pEncap; pEncap = pEncap->next) { if (pEncap->type == BGP_VNC_SUBTLV_TYPE_RFPOPTION) { if (pEncap->value[0] == RFAPI_VN_OPTION_TYPE_L2ADDR) { if (pEncap->value[1] == 14) { memcpy(l2o->macaddr.octet, pEncap->value + 2, ETH_ALEN); l2o->label = ((pEncap->value[10] >> 4) & 0x0f) + ((pEncap->value[9] << 4) & 0xff0) + ((pEncap->value[8] << 12) & 0xff000); l2o->local_nve_id = pEncap->value[12]; l2o->logical_net_id = (pEncap->value[15] & 0xff) + ((pEncap->value[14] << 8) & 0xff00) + ((pEncap->value[13] << 16) & 0xff0000); } return 0; } } } } return ENOENT; } /* * Extract the lifetime from the Tunnel Encap attribute of a route in * an import table */ int rfapiGetVncLifetime(struct attr *attr, uint32_t *lifetime) { struct bgp_attr_encap_subtlv *pEncap; *lifetime = RFAPI_INFINITE_LIFETIME; /* default to infinite */ if (attr) { for (pEncap = attr->vnc_subtlvs; pEncap; pEncap = pEncap->next) { if (pEncap->type == BGP_VNC_SUBTLV_TYPE_LIFETIME) { /* lifetime */ if (pEncap->length == 4) { memcpy(lifetime, pEncap->value, 4); *lifetime = ntohl(*lifetime); return 0; } } } } return ENOENT; } /* * Look for UN address in Encap attribute */ int rfapiGetVncTunnelUnAddr(struct attr *attr, struct prefix *p) { struct bgp_attr_encap_subtlv *pEncap; bgp_encap_types tun_type = BGP_ENCAP_TYPE_MPLS;/*Default tunnel type*/ bgp_attr_extcom_tunnel_type(attr, &tun_type); if (tun_type == BGP_ENCAP_TYPE_MPLS) { if (!p) return 0; /* MPLS carries UN address in next hop */ rfapiNexthop2Prefix(attr, p); if (p->family != 0) return 0; return ENOENT; } if (attr) { for (pEncap = attr->encap_subtlvs; pEncap; pEncap = pEncap->next) { if (pEncap->type == BGP_ENCAP_SUBTLV_TYPE_REMOTE_ENDPOINT) { /* un addr */ switch (pEncap->length) { case 8: if (p) { p->family = AF_INET; p->prefixlen = 32; memcpy(p->u.val, pEncap->value, 4); } return 0; case 20: if (p) { p->family = AF_INET6; p->prefixlen = 128; memcpy(p->u.val, pEncap->value, 16); } return 0; } } } } return ENOENT; } /* * Get UN address wherever it might be */ int rfapiGetUnAddrOfVpnBi(struct bgp_path_info *bpi, struct prefix *p) { /* If it's in this route's VNC attribute, we're done */ if (!rfapiGetVncTunnelUnAddr(bpi->attr, p)) return 0; /* * Otherwise, see if it's cached from a corresponding ENCAP SAFI * advertisement */ if (bpi->extra) { switch (bpi->extra->vnc.import.un_family) { case AF_INET: if (p) { p->family = bpi->extra->vnc.import.un_family; p->u.prefix4 = bpi->extra->vnc.import.un.addr4; p->prefixlen = 32; } return 0; case AF_INET6: if (p) { p->family = bpi->extra->vnc.import.un_family; p->u.prefix6 = bpi->extra->vnc.import.un.addr6; p->prefixlen = 128; } return 0; default: if (p) p->family = 0; #if DEBUG_ENCAP_MONITOR vnc_zlog_debug_verbose( "%s: bpi->extra->vnc.import.un_family is 0, no UN addr", __func__); #endif break; } } return ENOENT; } /* * Make a new bgp_path_info from gathered parameters */ static struct bgp_path_info *rfapiBgpInfoCreate(struct attr *attr, struct peer *peer, void *rfd, struct prefix_rd *prd, uint8_t type, uint8_t sub_type, uint32_t *label) { struct bgp_path_info *new; new = info_make(type, sub_type, 0, peer, attr, NULL); if (attr) new->attr = bgp_attr_intern(attr); bgp_path_info_extra_get(new); if (prd) { new->extra->vnc.import.rd = *prd; rfapi_time(&new->extra->vnc.import.create_time); } if (label) encode_label(*label, &new->extra->label[0]); peer_lock(peer); return new; } /* * Frees bgp_path_info as used in import tables (parts are not * allocated exactly the way they are in the main RIBs) */ static void rfapiBgpInfoFree(struct bgp_path_info *goner) { if (!goner) return; if (goner->peer) { vnc_zlog_debug_verbose("%s: calling peer_unlock(%p), #%d", __func__, goner->peer, goner->peer->lock); peer_unlock(goner->peer); } if (goner->attr) { bgp_attr_unintern(&goner->attr); } if (goner->extra) bgp_path_info_extra_free(&goner->extra); XFREE(MTYPE_BGP_ROUTE, goner); } struct rfapi_import_table *rfapiMacImportTableGetNoAlloc(struct bgp *bgp, uint32_t lni) { struct rfapi *h; struct rfapi_import_table *it = NULL; uintptr_t lni_as_ptr = lni; h = bgp->rfapi; if (!h) return NULL; if (!h->import_mac) return NULL; if (skiplist_search(h->import_mac, (void *)lni_as_ptr, (void **)&it)) return NULL; return it; } struct rfapi_import_table *rfapiMacImportTableGet(struct bgp *bgp, uint32_t lni) { struct rfapi *h; struct rfapi_import_table *it = NULL; uintptr_t lni_as_ptr = lni; h = bgp->rfapi; assert(h); if (!h->import_mac) { /* default cmp is good enough for LNI */ h->import_mac = skiplist_new(0, NULL, NULL); } if (skiplist_search(h->import_mac, (void *)lni_as_ptr, (void **)&it)) { struct ecommunity *enew; struct ecommunity_val eval; afi_t afi; it = XCALLOC(MTYPE_RFAPI_IMPORTTABLE, sizeof(struct rfapi_import_table)); /* set RT list of new import table based on LNI */ memset((char *)&eval, 0, sizeof(eval)); eval.val[0] = 0; /* VNC L2VPN */ eval.val[1] = 2; /* VNC L2VPN */ eval.val[5] = (lni >> 16) & 0xff; eval.val[6] = (lni >> 8) & 0xff; eval.val[7] = (lni >> 0) & 0xff; enew = ecommunity_new(); ecommunity_add_val(enew, &eval); it->rt_import_list = enew; for (afi = AFI_IP; afi < AFI_MAX; ++afi) { it->imported_vpn[afi] = agg_table_init(); it->imported_encap[afi] = agg_table_init(); } it->l2_logical_net_id = lni; skiplist_insert(h->import_mac, (void *)lni_as_ptr, it); } assert(it); return it; } /* * Implement MONITOR_MOVE_SHORTER(original_node) from * RFAPI-Import-Event-Handling.txt * * Returns pointer to the list of moved monitors */ static struct rfapi_monitor_vpn * rfapiMonitorMoveShorter(struct agg_node *original_vpn_node, int lockoffset) { struct bgp_path_info *bpi; struct agg_node *par; struct rfapi_monitor_vpn *m; struct rfapi_monitor_vpn *mlast; struct rfapi_monitor_vpn *moved; int movecount = 0; int parent_already_refcounted = 0; RFAPI_CHECK_REFCOUNT(original_vpn_node, SAFI_MPLS_VPN, lockoffset); #if DEBUG_MONITOR_MOVE_SHORTER { char buf[PREFIX_STRLEN]; prefix2str(&original_vpn_node->p, buf, sizeof(buf)); vnc_zlog_debug_verbose("%s: called with node pfx=%s", __func__, buf); } #endif /* * 1. If there is at least one bpi (either regular route or * route marked as withdrawn, with a pending timer) at * original_node with a valid UN address, we're done. Return. */ for (bpi = original_vpn_node->info; bpi; bpi = bpi->next) { struct prefix pfx; if (!rfapiGetUnAddrOfVpnBi(bpi, &pfx)) { #if DEBUG_MONITOR_MOVE_SHORTER vnc_zlog_debug_verbose( "%s: have valid UN at original node, no change", __func__); #endif return NULL; } } /* * 2. Travel up the tree (toward less-specific prefixes) from * original_node to find the first node that has at least * one route (even if it is only a withdrawn route) with a * valid UN address. Call this node "Node P." */ for (par = agg_node_parent(original_vpn_node); par; par = agg_node_parent(par)) { for (bpi = par->info; bpi; bpi = bpi->next) { struct prefix pfx; if (!rfapiGetUnAddrOfVpnBi(bpi, &pfx)) { break; } } if (bpi) break; } if (par) { RFAPI_CHECK_REFCOUNT(par, SAFI_MPLS_VPN, 0); } /* * If no less-specific routes, try to use the 0/0 node */ if (!par) { /* this isn't necessarily 0/0 */ par = agg_route_table_top(original_vpn_node); /* * If we got the top node but it wasn't 0/0, * ignore it */ if (par && par->p.prefixlen) { agg_unlock_node(par); /* maybe free */ par = NULL; } if (par) { ++parent_already_refcounted; } } /* * Create 0/0 node if it isn't there */ if (!par) { struct prefix pfx_default; memset(&pfx_default, 0, sizeof(pfx_default)); pfx_default.family = original_vpn_node->p.family; /* creates default node if none exists */ par = agg_node_get(agg_get_table(original_vpn_node), &pfx_default); ++parent_already_refcounted; } /* * 3. Move each of the monitors found at original_node to Node P. * These are "Moved Monitors." * */ /* * Attach at end so that the list pointer we return points * only to the moved routes */ for (m = RFAPI_MONITOR_VPN(par), mlast = NULL; m; mlast = m, m = m->next) ; if (mlast) { moved = mlast->next = RFAPI_MONITOR_VPN(original_vpn_node); } else { moved = RFAPI_MONITOR_VPN_W_ALLOC(par) = RFAPI_MONITOR_VPN(original_vpn_node); } if (RFAPI_MONITOR_VPN( original_vpn_node)) /* check agg, so not allocated */ RFAPI_MONITOR_VPN_W_ALLOC(original_vpn_node) = NULL; /* * update the node pointers on the monitors */ for (m = moved; m; m = m->next) { ++movecount; m->node = par; } RFAPI_CHECK_REFCOUNT(par, SAFI_MPLS_VPN, parent_already_refcounted - movecount); while (movecount > parent_already_refcounted) { agg_lock_node(par); ++parent_already_refcounted; } while (movecount < parent_already_refcounted) { /* unlikely, but code defensively */ agg_unlock_node(par); --parent_already_refcounted; } RFAPI_CHECK_REFCOUNT(original_vpn_node, SAFI_MPLS_VPN, movecount + lockoffset); while (movecount--) { agg_unlock_node(original_vpn_node); } #if DEBUG_MONITOR_MOVE_SHORTER { char buf[PREFIX_STRLEN]; prefix2str(&par->p, buf, sizeof(buf)); vnc_zlog_debug_verbose("%s: moved to node pfx=%s", __func__, buf); } #endif return moved; } /* * Implement MONITOR_MOVE_LONGER(new_node) from * RFAPI-Import-Event-Handling.txt */ static void rfapiMonitorMoveLonger(struct agg_node *new_vpn_node) { struct rfapi_monitor_vpn *monitor; struct rfapi_monitor_vpn *mlast; struct bgp_path_info *bpi; struct agg_node *par; RFAPI_CHECK_REFCOUNT(new_vpn_node, SAFI_MPLS_VPN, 0); /* * Make sure we have at least one valid route at the new node */ for (bpi = new_vpn_node->info; bpi; bpi = bpi->next) { struct prefix pfx; if (!rfapiGetUnAddrOfVpnBi(bpi, &pfx)) break; } if (!bpi) { vnc_zlog_debug_verbose( "%s: no valid routes at node %p, so not attempting moves", __func__, new_vpn_node); return; } /* * Find first parent node that has monitors */ for (par = agg_node_parent(new_vpn_node); par; par = agg_node_parent(par)) { if (RFAPI_MONITOR_VPN(par)) break; } if (!par) { vnc_zlog_debug_verbose( "%s: no parent nodes with monitors, done", __func__); return; } /* * Check each of these monitors to see of their longest-match * is now the updated node. Move any such monitors to the more- * specific updated node */ for (mlast = NULL, monitor = RFAPI_MONITOR_VPN(par); monitor;) { /* * If new longest match for monitor prefix is the new * route's prefix, move monitor to new route's prefix */ if (prefix_match(&new_vpn_node->p, &monitor->p)) { /* detach */ if (mlast) { mlast->next = monitor->next; } else { RFAPI_MONITOR_VPN_W_ALLOC(par) = monitor->next; } /* attach */ monitor->next = RFAPI_MONITOR_VPN(new_vpn_node); RFAPI_MONITOR_VPN_W_ALLOC(new_vpn_node) = monitor; monitor->node = new_vpn_node; agg_lock_node(new_vpn_node); /* incr refcount */ monitor = mlast ? mlast->next : RFAPI_MONITOR_VPN(par); RFAPI_CHECK_REFCOUNT(par, SAFI_MPLS_VPN, 1); /* decr refcount after we're done with par as this might * free it */ agg_unlock_node(par); continue; } mlast = monitor; monitor = monitor->next; } RFAPI_CHECK_REFCOUNT(new_vpn_node, SAFI_MPLS_VPN, 0); } static void rfapiBgpInfoChainFree(struct bgp_path_info *bpi) { struct bgp_path_info *next; while (bpi) { /* * If there is a timer waiting to delete this bpi, cancel * the timer and delete immediately */ if (CHECK_FLAG(bpi->flags, BGP_PATH_REMOVED) && bpi->extra->vnc.import.timer) { struct thread *t = (struct thread *)bpi->extra->vnc.import.timer; struct rfapi_withdraw *wcb = t->arg; XFREE(MTYPE_RFAPI_WITHDRAW, wcb); thread_cancel(t); } next = bpi->next; bpi->next = NULL; rfapiBgpInfoFree(bpi); bpi = next; } } static void rfapiImportTableFlush(struct rfapi_import_table *it) { afi_t afi; /* * Free ecommunity */ ecommunity_free(&it->rt_import_list); it->rt_import_list = NULL; for (afi = AFI_IP; afi < AFI_MAX; ++afi) { struct agg_node *rn; for (rn = agg_route_top(it->imported_vpn[afi]); rn; rn = agg_route_next(rn)) { /* * Each route_node has: * aggregate: points to rfapi_it_extra with monitor * chain(s) * info: points to chain of bgp_path_info */ /* free bgp_path_info and its children */ rfapiBgpInfoChainFree(rn->info); rn->info = NULL; rfapiMonitorExtraFlush(SAFI_MPLS_VPN, rn); } for (rn = agg_route_top(it->imported_encap[afi]); rn; rn = agg_route_next(rn)) { /* free bgp_path_info and its children */ rfapiBgpInfoChainFree(rn->info); rn->info = NULL; rfapiMonitorExtraFlush(SAFI_ENCAP, rn); } agg_table_finish(it->imported_vpn[afi]); agg_table_finish(it->imported_encap[afi]); } if (it->monitor_exterior_orphans) { skiplist_free(it->monitor_exterior_orphans); } } void rfapiImportTableRefDelByIt(struct bgp *bgp, struct rfapi_import_table *it_target) { struct rfapi *h; struct rfapi_import_table *it; struct rfapi_import_table *prev = NULL; assert(it_target); h = bgp->rfapi; assert(h); for (it = h->imports; it; prev = it, it = it->next) { if (it == it_target) break; } assert(it); assert(it->refcount); it->refcount -= 1; if (!it->refcount) { if (prev) { prev->next = it->next; } else { h->imports = it->next; } rfapiImportTableFlush(it); XFREE(MTYPE_RFAPI_IMPORTTABLE, it); } } #if RFAPI_REQUIRE_ENCAP_BEEC /* * Look for magic BGP Encapsulation Extended Community value * Format in RFC 5512 Sect. 4.5 */ static int rfapiEcommunitiesMatchBeec(struct ecommunity *ecom, bgp_encap_types type) { int i; if (!ecom) return 0; for (i = 0; i < (ecom->size * ECOMMUNITY_SIZE); i += ECOMMUNITY_SIZE) { uint8_t *ep; ep = ecom->val + i; if (ep[0] == ECOMMUNITY_ENCODE_OPAQUE && ep[1] == ECOMMUNITY_OPAQUE_SUBTYPE_ENCAP && ep[6] == ((type && 0xff00) >> 8) && ep[7] == (type & 0xff)) { return 1; } } return 0; } #endif int rfapiEcommunitiesIntersect(struct ecommunity *e1, struct ecommunity *e2) { int i, j; if (!e1 || !e2) return 0; { char *s1, *s2; s1 = ecommunity_ecom2str(e1, ECOMMUNITY_FORMAT_DISPLAY, 0); s2 = ecommunity_ecom2str(e2, ECOMMUNITY_FORMAT_DISPLAY, 0); vnc_zlog_debug_verbose("%s: e1[%s], e2[%s]", __func__, s1, s2); XFREE(MTYPE_ECOMMUNITY_STR, s1); XFREE(MTYPE_ECOMMUNITY_STR, s2); } for (i = 0; i < e1->size; ++i) { for (j = 0; j < e2->size; ++j) { if (!memcmp(e1->val + (i * ECOMMUNITY_SIZE), e2->val + (j * ECOMMUNITY_SIZE), ECOMMUNITY_SIZE)) { return 1; } } } return 0; } int rfapiEcommunityGetLNI(struct ecommunity *ecom, uint32_t *lni) { if (ecom) { int i; for (i = 0; i < ecom->size; ++i) { uint8_t *p = ecom->val + (i * ECOMMUNITY_SIZE); if ((*(p + 0) == 0x00) && (*(p + 1) == 0x02)) { *lni = (*(p + 5) << 16) | (*(p + 6) << 8) | (*(p + 7)); return 0; } } } return ENOENT; } int rfapiEcommunityGetEthernetTag(struct ecommunity *ecom, uint16_t *tag_id) { struct bgp *bgp = bgp_get_default(); *tag_id = 0; /* default to untagged */ if (ecom) { int i; for (i = 0; i < ecom->size; ++i) { as_t as = 0; int encode = 0; uint8_t *p = ecom->val + (i * ECOMMUNITY_SIZE); /* High-order octet of type. */ encode = *p++; if (*p++ == ECOMMUNITY_ROUTE_TARGET) { if (encode == ECOMMUNITY_ENCODE_AS4) { p = ptr_get_be32(p, &as); } else if (encode == ECOMMUNITY_ENCODE_AS) { as = (*p++ << 8); as |= (*p++); p += 2; /* skip next two, tag/vid always in lowest bytes */ } if (as == bgp->as) { *tag_id = *p++ << 8; *tag_id |= (*p++); return 0; } } } } return ENOENT; } static int rfapiVpnBiNhEqualsPt(struct bgp_path_info *bpi, struct rfapi_ip_addr *hpt) { uint8_t family; if (!hpt || !bpi) return 0; family = BGP_MP_NEXTHOP_FAMILY(bpi->attr->mp_nexthop_len); if (hpt->addr_family != family) return 0; switch (family) { case AF_INET: if (bpi->attr->mp_nexthop_global_in.s_addr != hpt->addr.v4.s_addr) return 0; break; case AF_INET6: if (IPV6_ADDR_CMP(&bpi->attr->mp_nexthop_global, &hpt->addr.v6)) return 0; break; default: return 0; break; } return 1; } /* * Compare 2 VPN BIs. Return true if they have the same VN and UN addresses */ static int rfapiVpnBiSamePtUn(struct bgp_path_info *bpi1, struct bgp_path_info *bpi2) { struct prefix pfx_un1; struct prefix pfx_un2; if (!bpi1 || !bpi2) return 0; if (!bpi1->attr || !bpi2->attr) return 0; /* * VN address comparisons */ if (BGP_MP_NEXTHOP_FAMILY(bpi1->attr->mp_nexthop_len) != BGP_MP_NEXTHOP_FAMILY(bpi2->attr->mp_nexthop_len)) { return 0; } switch (BGP_MP_NEXTHOP_FAMILY(bpi1->attr->mp_nexthop_len)) { case AF_INET: if (bpi1->attr->mp_nexthop_global_in.s_addr != bpi2->attr->mp_nexthop_global_in.s_addr) return 0; break; case AF_INET6: if (IPV6_ADDR_CMP(&bpi1->attr->mp_nexthop_global, &bpi2->attr->mp_nexthop_global)) return 0; break; default: return 0; break; } memset(&pfx_un1, 0, sizeof(pfx_un1)); memset(&pfx_un2, 0, sizeof(pfx_un2)); /* * UN address comparisons */ if (rfapiGetVncTunnelUnAddr(bpi1->attr, &pfx_un1)) { if (bpi1->extra) { pfx_un1.family = bpi1->extra->vnc.import.un_family; switch (bpi1->extra->vnc.import.un_family) { case AF_INET: pfx_un1.u.prefix4 = bpi1->extra->vnc.import.un.addr4; break; case AF_INET6: pfx_un1.u.prefix6 = bpi1->extra->vnc.import.un.addr6; break; default: pfx_un1.family = 0; break; } } } if (rfapiGetVncTunnelUnAddr(bpi2->attr, &pfx_un2)) { if (bpi2->extra) { pfx_un2.family = bpi2->extra->vnc.import.un_family; switch (bpi2->extra->vnc.import.un_family) { case AF_INET: pfx_un2.u.prefix4 = bpi2->extra->vnc.import.un.addr4; break; case AF_INET6: pfx_un2.u.prefix6 = bpi2->extra->vnc.import.un.addr6; break; default: pfx_un2.family = 0; break; } } } if (pfx_un1.family == 0 || pfx_un2.family == 0) return 0; if (pfx_un1.family != pfx_un2.family) return 0; switch (pfx_un1.family) { case AF_INET: if (!IPV4_ADDR_SAME(&pfx_un1.u.prefix4, &pfx_un2.u.prefix4)) return 0; break; case AF_INET6: if (!IPV6_ADDR_SAME(&pfx_un1.u.prefix6, &pfx_un2.u.prefix6)) return 0; break; } return 1; } uint8_t rfapiRfpCost(struct attr *attr) { if (attr->flag & ATTR_FLAG_BIT(BGP_ATTR_LOCAL_PREF)) { if (attr->local_pref > 255) { return 0; } return 255 - attr->local_pref; } return 255; } /*------------------------------------------ * rfapi_extract_l2o * * Find Layer 2 options in an option chain * * input: * pHop option chain * * output: * l2o layer 2 options extracted * * return value: * 0 OK * 1 no options found * --------------------------------------------*/ int rfapi_extract_l2o( struct bgp_tea_options *pHop, /* chain of options */ struct rfapi_l2address_option *l2o) /* return extracted value */ { struct bgp_tea_options *p; for (p = pHop; p; p = p->next) { if ((p->type == RFAPI_VN_OPTION_TYPE_L2ADDR) && (p->length >= 8)) { char *v = p->value; memcpy(&l2o->macaddr, v, 6); l2o->label = ((v[6] << 12) & 0xff000) + ((v[7] << 4) & 0xff0) + ((v[8] >> 4) & 0xf); l2o->local_nve_id = (uint8_t)v[10]; l2o->logical_net_id = (v[11] << 16) + (v[12] << 8) + (v[13] << 0); return 0; } } return 1; } static struct rfapi_next_hop_entry * rfapiRouteInfo2NextHopEntry(struct rfapi_ip_prefix *rprefix, struct bgp_path_info *bpi, /* route to encode */ uint32_t lifetime, /* use this in nhe */ struct agg_node *rn) /* req for L2 eth addr */ { struct rfapi_next_hop_entry *new; int have_vnc_tunnel_un = 0; #if DEBUG_ENCAP_MONITOR vnc_zlog_debug_verbose("%s: entry, bpi %p, rn %p", __func__, bpi, rn); #endif new = XCALLOC(MTYPE_RFAPI_NEXTHOP, sizeof(struct rfapi_next_hop_entry)); assert(new); new->prefix = *rprefix; if (bpi->extra && decode_rd_type(bpi->extra->vnc.import.rd.val) == RD_TYPE_VNC_ETH) { /* ethernet */ struct rfapi_vn_option *vo; vo = XCALLOC(MTYPE_RFAPI_VN_OPTION, sizeof(struct rfapi_vn_option)); assert(vo); vo->type = RFAPI_VN_OPTION_TYPE_L2ADDR; memcpy(&vo->v.l2addr.macaddr, &rn->p.u.prefix_eth.octet, ETH_ALEN); /* only low 3 bytes of this are significant */ if (bpi->attr) { (void)rfapiEcommunityGetLNI( bpi->attr->ecommunity, &vo->v.l2addr.logical_net_id); (void)rfapiEcommunityGetEthernetTag( bpi->attr->ecommunity, &vo->v.l2addr.tag_id); } /* local_nve_id comes from lower byte of RD type */ vo->v.l2addr.local_nve_id = bpi->extra->vnc.import.rd.val[1]; /* label comes from MP_REACH_NLRI label */ vo->v.l2addr.label = decode_label(&bpi->extra->label[0]); new->vn_options = vo; /* * If there is an auxiliary prefix (i.e., host IP address), * use it as the nexthop prefix instead of the query prefix */ if (bpi->extra->vnc.import.aux_prefix.family) { rfapiQprefix2Rprefix(&bpi->extra->vnc.import.aux_prefix, &new->prefix); } } if (bpi->attr) { bgp_encap_types tun_type = BGP_ENCAP_TYPE_MPLS; /*Default*/ new->prefix.cost = rfapiRfpCost(bpi->attr); struct bgp_attr_encap_subtlv *pEncap; switch (BGP_MP_NEXTHOP_FAMILY(bpi->attr->mp_nexthop_len)) { case AF_INET: new->vn_address.addr_family = AF_INET; new->vn_address.addr.v4 = bpi->attr->mp_nexthop_global_in; break; case AF_INET6: new->vn_address.addr_family = AF_INET6; new->vn_address.addr.v6 = bpi->attr->mp_nexthop_global; break; default: zlog_warn("%s: invalid vpn nexthop length: %d", __func__, bpi->attr->mp_nexthop_len); rfapi_free_next_hop_list(new); return NULL; } for (pEncap = bpi->attr->vnc_subtlvs; pEncap; pEncap = pEncap->next) { switch (pEncap->type) { case BGP_VNC_SUBTLV_TYPE_LIFETIME: /* use configured lifetime, not attr lifetime */ break; default: zlog_warn("%s: unknown VNC option type %d", __func__, pEncap->type); break; } } bgp_attr_extcom_tunnel_type(bpi->attr, &tun_type); if (tun_type == BGP_ENCAP_TYPE_MPLS) { struct prefix p; /* MPLS carries UN address in next hop */ rfapiNexthop2Prefix(bpi->attr, &p); if (p.family != 0) { rfapiQprefix2Raddr(&p, &new->un_address); have_vnc_tunnel_un = 1; } } for (pEncap = bpi->attr->encap_subtlvs; pEncap; pEncap = pEncap->next) { switch (pEncap->type) { case BGP_ENCAP_SUBTLV_TYPE_REMOTE_ENDPOINT: /* * Overrides ENCAP UN address, if any */ switch (pEncap->length) { case 8: new->un_address.addr_family = AF_INET; memcpy(&new->un_address.addr.v4, pEncap->value, 4); have_vnc_tunnel_un = 1; break; case 20: new->un_address.addr_family = AF_INET6; memcpy(&new->un_address.addr.v6, pEncap->value, 16); have_vnc_tunnel_un = 1; break; default: zlog_warn( "%s: invalid tunnel subtlv UN addr length (%d) for bpi %p", __func__, pEncap->length, bpi); } break; default: zlog_warn( "%s: unknown Encap Attribute option type %d", __func__, pEncap->type); break; } } new->un_options = rfapi_encap_tlv_to_un_option(bpi->attr); #if DEBUG_ENCAP_MONITOR vnc_zlog_debug_verbose("%s: line %d: have_vnc_tunnel_un=%d", __func__, __LINE__, have_vnc_tunnel_un); #endif if (!have_vnc_tunnel_un && bpi->extra) { /* * use cached UN address from ENCAP route */ new->un_address.addr_family = bpi->extra->vnc.import.un_family; switch (new->un_address.addr_family) { case AF_INET: new->un_address.addr.v4 = bpi->extra->vnc.import.un.addr4; break; case AF_INET6: new->un_address.addr.v6 = bpi->extra->vnc.import.un.addr6; break; default: zlog_warn( "%s: invalid UN addr family (%d) for bpi %p", __func__, new->un_address.addr_family, bpi); rfapi_free_next_hop_list(new); return NULL; break; } } } new->lifetime = lifetime; return new; } int rfapiHasNonRemovedRoutes(struct agg_node *rn) { struct bgp_path_info *bpi; for (bpi = rn->info; bpi; bpi = bpi->next) { struct prefix pfx; if (!CHECK_FLAG(bpi->flags, BGP_PATH_REMOVED) && (bpi->extra && !rfapiGetUnAddrOfVpnBi(bpi, &pfx))) { return 1; } } return 0; } #if DEBUG_IT_NODES /* * DEBUG FUNCTION */ void rfapiDumpNode(struct agg_node *rn) { struct bgp_path_info *bpi; vnc_zlog_debug_verbose("%s: rn=%p", __func__, rn); for (bpi = rn->info; bpi; bpi = bpi->next) { struct prefix pfx; int ctrc = rfapiGetUnAddrOfVpnBi(bpi, &pfx); int nr; if (!CHECK_FLAG(bpi->flags, BGP_PATH_REMOVED) && (bpi->extra && !ctrc)) { nr = 1; } else { nr = 0; } vnc_zlog_debug_verbose( " bpi=%p, nr=%d, flags=0x%x, extra=%p, ctrc=%d", bpi, nr, bpi->flags, bpi->extra, ctrc); } } #endif static int rfapiNhlAddNodeRoutes( struct agg_node *rn, /* in */ struct rfapi_ip_prefix *rprefix, /* in */ uint32_t lifetime, /* in */ int removed, /* in */ struct rfapi_next_hop_entry **head, /* in/out */ struct rfapi_next_hop_entry **tail, /* in/out */ struct rfapi_ip_addr *exclude_vnaddr, /* omit routes to same NVE */ struct agg_node *rfd_rib_node, /* preload this NVE rib node */ struct prefix *pfx_target_original) /* query target */ { struct bgp_path_info *bpi; struct rfapi_next_hop_entry *new; struct prefix pfx_un; struct skiplist *seen_nexthops; int count = 0; int is_l2 = (rn->p.family == AF_ETHERNET); if (rfd_rib_node) { struct agg_table *atable = agg_get_table(rfd_rib_node); struct rfapi_descriptor *rfd; if (atable) { rfd = agg_get_table_info(atable); if (rfapiRibFTDFilterRecentPrefix(rfd, rn, pfx_target_original)) return 0; } } seen_nexthops = skiplist_new(0, vnc_prefix_cmp, (void (*)(void *))prefix_free); for (bpi = rn->info; bpi; bpi = bpi->next) { struct prefix pfx_vn; struct prefix *newpfx; if (removed && !CHECK_FLAG(bpi->flags, BGP_PATH_REMOVED)) { #if DEBUG_RETURNED_NHL vnc_zlog_debug_verbose( "%s: want holddown, this route not holddown, skip", __func__); #endif continue; } if (!removed && CHECK_FLAG(bpi->flags, BGP_PATH_REMOVED)) { continue; } if (!bpi->extra) { continue; } /* * Check for excluded VN address */ if (rfapiVpnBiNhEqualsPt(bpi, exclude_vnaddr)) continue; /* * Check for VN address (nexthop) copied already */ if (is_l2) { /* L2 routes: semantic nexthop in aux_prefix; VN addr * ain't it */ pfx_vn = bpi->extra->vnc.import.aux_prefix; } else { rfapiNexthop2Prefix(bpi->attr, &pfx_vn); } if (!skiplist_search(seen_nexthops, &pfx_vn, NULL)) { #if DEBUG_RETURNED_NHL char buf[PREFIX_STRLEN]; prefix2str(&pfx_vn, buf, sizeof(buf)); vnc_zlog_debug_verbose( "%s: already put VN/nexthop %s, skip", __func__, buf); #endif continue; } if (rfapiGetUnAddrOfVpnBi(bpi, &pfx_un)) { #if DEBUG_ENCAP_MONITOR vnc_zlog_debug_verbose( "%s: failed to get UN address of this VPN bpi", __func__); #endif continue; } newpfx = prefix_new(); *newpfx = pfx_vn; skiplist_insert(seen_nexthops, newpfx, newpfx); new = rfapiRouteInfo2NextHopEntry(rprefix, bpi, lifetime, rn); if (new) { if (rfapiRibPreloadBi(rfd_rib_node, &pfx_vn, &pfx_un, lifetime, bpi)) { /* duplicate filtered by RIB */ rfapi_free_next_hop_list(new); new = NULL; } } if (new) { if (*tail) { (*tail)->next = new; } else { *head = new; } *tail = new; ++count; } } skiplist_free(seen_nexthops); return count; } /* * Breadth-first * * omit_node is meant for the situation where we are adding a subtree * of a parent of some original requested node. The response already * contains the original requested node, and we don't want to duplicate * its routes in the list, so we skip it if the right or left node * matches (of course, we still travel down its child subtrees). */ static int rfapiNhlAddSubtree( struct agg_node *rn, /* in */ uint32_t lifetime, /* in */ struct rfapi_next_hop_entry **head, /* in/out */ struct rfapi_next_hop_entry **tail, /* in/out */ struct agg_node *omit_node, /* in */ struct rfapi_ip_addr *exclude_vnaddr, /* omit routes to same NVE */ struct agg_table *rfd_rib_table, /* preload here */ struct prefix *pfx_target_original) /* query target */ { struct rfapi_ip_prefix rprefix; int rcount = 0; /* FIXME: need to find a better way here to work without sticking our * hands in node->link */ if (agg_node_left(rn) && agg_node_left(rn) != omit_node) { if (agg_node_left(rn)->info) { int count = 0; struct agg_node *rib_rn = NULL; rfapiQprefix2Rprefix(&agg_node_left(rn)->p, &rprefix); if (rfd_rib_table) { rib_rn = agg_node_get(rfd_rib_table, &agg_node_left(rn)->p); } count = rfapiNhlAddNodeRoutes( agg_node_left(rn), &rprefix, lifetime, 0, head, tail, exclude_vnaddr, rib_rn, pfx_target_original); if (!count) { count = rfapiNhlAddNodeRoutes( agg_node_left(rn), &rprefix, lifetime, 1, head, tail, exclude_vnaddr, rib_rn, pfx_target_original); } rcount += count; if (rib_rn) agg_unlock_node(rib_rn); } } if (agg_node_right(rn) && agg_node_right(rn) != omit_node) { if (agg_node_right(rn)->info) { int count = 0; struct agg_node *rib_rn = NULL; rfapiQprefix2Rprefix(&agg_node_right(rn)->p, &rprefix); if (rfd_rib_table) { rib_rn = agg_node_get(rfd_rib_table, &agg_node_right(rn)->p); } count = rfapiNhlAddNodeRoutes( agg_node_right(rn), &rprefix, lifetime, 0, head, tail, exclude_vnaddr, rib_rn, pfx_target_original); if (!count) { count = rfapiNhlAddNodeRoutes( agg_node_right(rn), &rprefix, lifetime, 1, head, tail, exclude_vnaddr, rib_rn, pfx_target_original); } rcount += count; if (rib_rn) agg_unlock_node(rib_rn); } } if (agg_node_left(rn)) { rcount += rfapiNhlAddSubtree( agg_node_left(rn), lifetime, head, tail, omit_node, exclude_vnaddr, rfd_rib_table, pfx_target_original); } if (agg_node_right(rn)) { rcount += rfapiNhlAddSubtree( agg_node_right(rn), lifetime, head, tail, omit_node, exclude_vnaddr, rfd_rib_table, pfx_target_original); } return rcount; } /* * Implementation of ROUTE_LIST(node) from RFAPI-Import-Event-Handling.txt * * Construct an rfapi nexthop list based on the routes attached to * the specified node. * * If there are any routes that do NOT have BGP_PATH_REMOVED set, * return those only. If there are ONLY routes with BGP_PATH_REMOVED, * then return those, and also include all the non-removed routes from the * next less-specific node (i.e., this node's parent) at the end. */ struct rfapi_next_hop_entry *rfapiRouteNode2NextHopList( struct agg_node *rn, uint32_t lifetime, /* put into nexthop entries */ struct rfapi_ip_addr *exclude_vnaddr, /* omit routes to same NVE */ struct agg_table *rfd_rib_table, /* preload here */ struct prefix *pfx_target_original) /* query target */ { struct rfapi_ip_prefix rprefix; struct rfapi_next_hop_entry *answer = NULL; struct rfapi_next_hop_entry *last = NULL; struct agg_node *parent; int count = 0; struct agg_node *rib_rn; #if DEBUG_RETURNED_NHL { char buf[PREFIX_STRLEN]; prefix2str(&rn->p, buf, sizeof(buf)); vnc_zlog_debug_verbose("%s: called with node pfx=%s", __func__, buf); } rfapiDebugBacktrace(); #endif rfapiQprefix2Rprefix(&rn->p, &rprefix); rib_rn = rfd_rib_table ? agg_node_get(rfd_rib_table, &rn->p) : NULL; /* * Add non-withdrawn routes at this node */ count = rfapiNhlAddNodeRoutes(rn, &rprefix, lifetime, 0, &answer, &last, exclude_vnaddr, rib_rn, pfx_target_original); /* * If the list has at least one entry, it's finished */ if (count) { count += rfapiNhlAddSubtree(rn, lifetime, &answer, &last, NULL, exclude_vnaddr, rfd_rib_table, pfx_target_original); vnc_zlog_debug_verbose("%s: %d nexthops, answer=%p", __func__, count, answer); #if DEBUG_RETURNED_NHL rfapiPrintNhl(NULL, answer); #endif if (rib_rn) agg_unlock_node(rib_rn); return answer; } /* * Add withdrawn routes at this node */ count = rfapiNhlAddNodeRoutes(rn, &rprefix, lifetime, 1, &answer, &last, exclude_vnaddr, rib_rn, pfx_target_original); if (rib_rn) agg_unlock_node(rib_rn); // rfapiPrintNhl(NULL, answer); /* * walk up the tree until we find a node with non-deleted * routes, then add them */ for (parent = agg_node_parent(rn); parent; parent = agg_node_parent(parent)) { if (rfapiHasNonRemovedRoutes(parent)) { break; } } /* * Add non-withdrawn routes from less-specific prefix */ if (parent) { rib_rn = rfd_rib_table ? agg_node_get(rfd_rib_table, &parent->p) : NULL; rfapiQprefix2Rprefix(&parent->p, &rprefix); count += rfapiNhlAddNodeRoutes(parent, &rprefix, lifetime, 0, &answer, &last, exclude_vnaddr, rib_rn, pfx_target_original); count += rfapiNhlAddSubtree(parent, lifetime, &answer, &last, rn, exclude_vnaddr, rfd_rib_table, pfx_target_original); if (rib_rn) agg_unlock_node(rib_rn); } else { /* * There is no parent with non-removed routes. Still need to * add subtree of original node if it contributed routes to the * answer. */ if (count) count += rfapiNhlAddSubtree(rn, lifetime, &answer, &last, rn, exclude_vnaddr, rfd_rib_table, pfx_target_original); } vnc_zlog_debug_verbose("%s: %d nexthops, answer=%p", __func__, count, answer); #if DEBUG_RETURNED_NHL rfapiPrintNhl(NULL, answer); #endif return answer; } /* * Construct nexthop list of all routes in table */ struct rfapi_next_hop_entry *rfapiRouteTable2NextHopList( struct agg_table *rt, uint32_t lifetime, /* put into nexthop entries */ struct rfapi_ip_addr *exclude_vnaddr, /* omit routes to same NVE */ struct agg_table *rfd_rib_table, /* preload this NVE rib table */ struct prefix *pfx_target_original) /* query target */ { struct agg_node *rn; struct rfapi_next_hop_entry *biglist = NULL; struct rfapi_next_hop_entry *nhl; struct rfapi_next_hop_entry *tail = NULL; int count = 0; for (rn = agg_route_top(rt); rn; rn = agg_route_next(rn)) { nhl = rfapiRouteNode2NextHopList(rn, lifetime, exclude_vnaddr, rfd_rib_table, pfx_target_original); if (!tail) { tail = biglist = nhl; if (tail) count = 1; } else { tail->next = nhl; } if (tail) { while (tail->next) { ++count; tail = tail->next; } } } vnc_zlog_debug_verbose("%s: returning %d routes", __func__, count); return biglist; } struct rfapi_next_hop_entry *rfapiEthRouteNode2NextHopList( struct agg_node *rn, struct rfapi_ip_prefix *rprefix, uint32_t lifetime, /* put into nexthop entries */ struct rfapi_ip_addr *exclude_vnaddr, /* omit routes to same NVE */ struct agg_table *rfd_rib_table, /* preload NVE rib table */ struct prefix *pfx_target_original) /* query target */ { int count = 0; struct rfapi_next_hop_entry *answer = NULL; struct rfapi_next_hop_entry *last = NULL; struct agg_node *rib_rn; rib_rn = rfd_rib_table ? agg_node_get(rfd_rib_table, &rn->p) : NULL; count = rfapiNhlAddNodeRoutes(rn, rprefix, lifetime, 0, &answer, &last, NULL, rib_rn, pfx_target_original); #if DEBUG_ENCAP_MONITOR vnc_zlog_debug_verbose("%s: node %p: %d non-holddown routes", __func__, rn, count); #endif if (!count) { count = rfapiNhlAddNodeRoutes(rn, rprefix, lifetime, 1, &answer, &last, exclude_vnaddr, rib_rn, pfx_target_original); vnc_zlog_debug_verbose("%s: node %p: %d holddown routes", __func__, rn, count); } if (rib_rn) agg_unlock_node(rib_rn); #if DEBUG_RETURNED_NHL rfapiPrintNhl(NULL, answer); #endif return answer; } /* * Construct nexthop list of all routes in table */ struct rfapi_next_hop_entry *rfapiEthRouteTable2NextHopList( uint32_t logical_net_id, struct rfapi_ip_prefix *rprefix, uint32_t lifetime, /* put into nexthop entries */ struct rfapi_ip_addr *exclude_vnaddr, /* omit routes to same NVE */ struct agg_table *rfd_rib_table, /* preload NVE rib node */ struct prefix *pfx_target_original) /* query target */ { struct rfapi_import_table *it; struct bgp *bgp = bgp_get_default(); struct agg_table *rt; struct agg_node *rn; struct rfapi_next_hop_entry *biglist = NULL; struct rfapi_next_hop_entry *nhl; struct rfapi_next_hop_entry *tail = NULL; int count = 0; it = rfapiMacImportTableGet(bgp, logical_net_id); rt = it->imported_vpn[AFI_L2VPN]; for (rn = agg_route_top(rt); rn; rn = agg_route_next(rn)) { nhl = rfapiEthRouteNode2NextHopList( rn, rprefix, lifetime, exclude_vnaddr, rfd_rib_table, pfx_target_original); if (!tail) { tail = biglist = nhl; if (tail) count = 1; } else { tail->next = nhl; } if (tail) { while (tail->next) { ++count; tail = tail->next; } } } vnc_zlog_debug_verbose("%s: returning %d routes", __func__, count); return biglist; } /* * Insert a new bpi to the imported route table node, * keeping the list of BPIs sorted best route first */ static void rfapiBgpInfoAttachSorted(struct agg_node *rn, struct bgp_path_info *info_new, afi_t afi, safi_t safi) { struct bgp *bgp; struct bgp_path_info *prev; struct bgp_path_info *next; char pfx_buf[PREFIX2STR_BUFFER]; bgp = bgp_get_default(); /* assume 1 instance for now */ if (VNC_DEBUG(IMPORT_BI_ATTACH)) { vnc_zlog_debug_verbose("%s: info_new->peer=%p", __func__, info_new->peer); vnc_zlog_debug_verbose("%s: info_new->peer->su_remote=%p", __func__, info_new->peer->su_remote); } for (prev = NULL, next = rn->info; next; prev = next, next = next->next) { enum bgp_path_selection_reason reason; if (!bgp || (!CHECK_FLAG(info_new->flags, BGP_PATH_REMOVED) && CHECK_FLAG(next->flags, BGP_PATH_REMOVED)) || bgp_path_info_cmp_compatible(bgp, info_new, next, pfx_buf, afi, safi, &reason) == -1) { /* -1 if 1st is better */ break; } } vnc_zlog_debug_verbose("%s: prev=%p, next=%p", __func__, prev, next); if (prev) { prev->next = info_new; } else { rn->info = info_new; } info_new->prev = prev; info_new->next = next; if (next) next->prev = info_new; bgp_attr_intern(info_new->attr); } static void rfapiBgpInfoDetach(struct agg_node *rn, struct bgp_path_info *bpi) { /* * Remove the route (doubly-linked) */ // bgp_attr_unintern (&bpi->attr); if (bpi->next) bpi->next->prev = bpi->prev; if (bpi->prev) bpi->prev->next = bpi->next; else rn->info = bpi->next; } /* * For L3-indexed import tables */ static int rfapi_bi_peer_rd_cmp(void *b1, void *b2) { struct bgp_path_info *bpi1 = b1; struct bgp_path_info *bpi2 = b2; /* * Compare peers */ if (bpi1->peer < bpi2->peer) return -1; if (bpi1->peer > bpi2->peer) return 1; /* * compare RDs */ return vnc_prefix_cmp((struct prefix *)&bpi1->extra->vnc.import.rd, (struct prefix *)&bpi2->extra->vnc.import.rd); } /* * For L2-indexed import tables * The BPIs in these tables should ALWAYS have an aux_prefix set because * they arrive via IPv4 or IPv6 advertisements. */ static int rfapi_bi_peer_rd_aux_cmp(void *b1, void *b2) { struct bgp_path_info *bpi1 = b1; struct bgp_path_info *bpi2 = b2; int rc; /* * Compare peers */ if (bpi1->peer < bpi2->peer) return -1; if (bpi1->peer > bpi2->peer) return 1; /* * compare RDs */ rc = vnc_prefix_cmp((struct prefix *)&bpi1->extra->vnc.import.rd, (struct prefix *)&bpi2->extra->vnc.import.rd); if (rc) { return rc; } /* * L2 import tables can have multiple entries with the * same MAC address, same RD, but different L3 addresses. * * Use presence of aux_prefix with AF=ethernet and prefixlen=1 * as magic value to signify explicit wildcarding of the aux_prefix. * This magic value will not appear in bona fide bpi entries in * the import table, but is allowed in the "fake" bpi used to * probe the table when searching. (We have to test both b1 and b2 * because there is no guarantee of the order the test key and * the real key will be passed) */ if ((bpi1->extra->vnc.import.aux_prefix.family == AF_ETHERNET && (bpi1->extra->vnc.import.aux_prefix.prefixlen == 1)) || (bpi2->extra->vnc.import.aux_prefix.family == AF_ETHERNET && (bpi2->extra->vnc.import.aux_prefix.prefixlen == 1))) { /* * wildcard aux address specified */ return 0; } return vnc_prefix_cmp(&bpi1->extra->vnc.import.aux_prefix, &bpi2->extra->vnc.import.aux_prefix); } /* * Index on RD and Peer */ static void rfapiItBiIndexAdd(struct agg_node *rn, /* Import table VPN node */ struct bgp_path_info *bpi) /* new BPI */ { struct skiplist *sl; assert(rn); assert(bpi); assert(bpi->extra); { char buf[RD_ADDRSTRLEN]; vnc_zlog_debug_verbose("%s: bpi %p, peer %p, rd %s", __func__, bpi, bpi->peer, prefix_rd2str(&bpi->extra->vnc.import.rd, buf, sizeof(buf))); } sl = RFAPI_RDINDEX_W_ALLOC(rn); if (!sl) { if (AF_ETHERNET == rn->p.family) { sl = skiplist_new(0, rfapi_bi_peer_rd_aux_cmp, NULL); } else { sl = skiplist_new(0, rfapi_bi_peer_rd_cmp, NULL); } RFAPI_IT_EXTRA_GET(rn)->u.vpn.idx_rd = sl; agg_lock_node(rn); /* for skiplist */ } assert(!skiplist_insert(sl, (void *)bpi, (void *)bpi)); agg_lock_node(rn); /* for skiplist entry */ /* NB: BPIs in import tables are not refcounted */ } static void rfapiItBiIndexDump(struct agg_node *rn) { struct skiplist *sl; void *cursor = NULL; struct bgp_path_info *k; struct bgp_path_info *v; int rc; sl = RFAPI_RDINDEX(rn); if (!sl) return; for (rc = skiplist_next(sl, (void **)&k, (void **)&v, &cursor); !rc; rc = skiplist_next(sl, (void **)&k, (void **)&v, &cursor)) { char buf[RD_ADDRSTRLEN]; char buf_aux_pfx[PREFIX_STRLEN]; prefix_rd2str(&k->extra->vnc.import.rd, buf, sizeof(buf)); if (k->extra->vnc.import.aux_prefix.family) { prefix2str(&k->extra->vnc.import.aux_prefix, buf_aux_pfx, sizeof(buf_aux_pfx)); } else strlcpy(buf_aux_pfx, "(none)", sizeof(buf_aux_pfx)); vnc_zlog_debug_verbose("bpi %p, peer %p, rd %s, aux_prefix %s", k, k->peer, buf, buf_aux_pfx); } } static struct bgp_path_info *rfapiItBiIndexSearch( struct agg_node *rn, /* Import table VPN node */ struct prefix_rd *prd, struct peer *peer, struct prefix *aux_prefix) /* optional L3 addr for L2 ITs */ { struct skiplist *sl; int rc; struct bgp_path_info bpi_fake = {0}; struct bgp_path_info_extra bpi_extra = {0}; struct bgp_path_info *bpi_result; sl = RFAPI_RDINDEX(rn); if (!sl) return NULL; #if DEBUG_BI_SEARCH { char buf[RD_ADDRSTRLEN]; char buf_aux_pfx[PREFIX_STRLEN]; if (aux_prefix) { prefix2str(aux_prefix, buf_aux_pfx, sizeof(buf_aux_pfx)); } else strlcpy(buf_aux_pfx, "(nil)", sizeof(buf_aux_pfx)); vnc_zlog_debug_verbose("%s want prd=%s, peer=%p, aux_prefix=%s", __func__, prefix_rd2str(prd, buf, sizeof(buf)), peer, buf_aux_pfx); rfapiItBiIndexDump(rn); } #endif /* threshold is a WAG */ if (sl->count < 3) { #if DEBUG_BI_SEARCH vnc_zlog_debug_verbose("%s: short list algorithm", __func__); #endif /* if short list, linear search might be faster */ for (bpi_result = rn->info; bpi_result; bpi_result = bpi_result->next) { #if DEBUG_BI_SEARCH { char buf[RD_ADDRSTRLEN]; vnc_zlog_debug_verbose( "%s: bpi has prd=%s, peer=%p", __func__, prefix_rd2str(&bpi_result->extra->vnc .import.rd, buf, sizeof(buf)), bpi_result->peer); } #endif if (peer == bpi_result->peer && !prefix_cmp((struct prefix *)&bpi_result->extra ->vnc.import.rd, (struct prefix *)prd)) { #if DEBUG_BI_SEARCH vnc_zlog_debug_verbose( "%s: peer and RD same, doing aux_prefix check", __func__); #endif if (!aux_prefix || !prefix_cmp( aux_prefix, &bpi_result->extra->vnc.import .aux_prefix)) { #if DEBUG_BI_SEARCH vnc_zlog_debug_verbose("%s: match", __func__); #endif break; } } } return bpi_result; } bpi_fake.peer = peer; bpi_fake.extra = &bpi_extra; bpi_fake.extra->vnc.import.rd = *(struct prefix_rd *)prd; if (aux_prefix) { bpi_fake.extra->vnc.import.aux_prefix = *aux_prefix; } else { /* wildcard */ bpi_fake.extra->vnc.import.aux_prefix.family = AF_ETHERNET; bpi_fake.extra->vnc.import.aux_prefix.prefixlen = 1; } rc = skiplist_search(sl, (void *)&bpi_fake, (void *)&bpi_result); if (rc) { #if DEBUG_BI_SEARCH vnc_zlog_debug_verbose("%s: no match", __func__); #endif return NULL; } #if DEBUG_BI_SEARCH vnc_zlog_debug_verbose("%s: matched bpi=%p", __func__, bpi_result); #endif return bpi_result; } static void rfapiItBiIndexDel(struct agg_node *rn, /* Import table VPN node */ struct bgp_path_info *bpi) /* old BPI */ { struct skiplist *sl; int rc; { char buf[RD_ADDRSTRLEN]; vnc_zlog_debug_verbose("%s: bpi %p, peer %p, rd %s", __func__, bpi, bpi->peer, prefix_rd2str(&bpi->extra->vnc.import.rd, buf, sizeof(buf))); } sl = RFAPI_RDINDEX(rn); assert(sl); rc = skiplist_delete(sl, (void *)(bpi), (void *)bpi); if (rc) { rfapiItBiIndexDump(rn); } assert(!rc); agg_unlock_node(rn); /* for skiplist entry */ /* NB: BPIs in import tables are not refcounted */ } /* * Add a backreference at the ENCAP node to the VPN route that * refers to it */ static void rfapiMonitorEncapAdd(struct rfapi_import_table *import_table, struct prefix *p, /* VN address */ struct agg_node *vpn_rn, /* VPN node */ struct bgp_path_info *vpn_bpi) /* VPN bpi/route */ { afi_t afi = family2afi(p->family); struct agg_node *rn; struct rfapi_monitor_encap *m; assert(afi); rn = agg_node_get(import_table->imported_encap[afi], p); /* locks rn */ assert(rn); m = XCALLOC(MTYPE_RFAPI_MONITOR_ENCAP, sizeof(struct rfapi_monitor_encap)); assert(m); m->node = vpn_rn; m->bpi = vpn_bpi; m->rn = rn; /* insert to encap node's list */ m->next = RFAPI_MONITOR_ENCAP(rn); if (m->next) m->next->prev = m; RFAPI_MONITOR_ENCAP_W_ALLOC(rn) = m; /* for easy lookup when deleting vpn route */ vpn_bpi->extra->vnc.import.hme = m; vnc_zlog_debug_verbose( "%s: it=%p, vpn_bpi=%p, afi=%d, encap rn=%p, setting vpn_bpi->extra->vnc.import.hme=%p", __func__, import_table, vpn_bpi, afi, rn, m); RFAPI_CHECK_REFCOUNT(rn, SAFI_ENCAP, 0); bgp_attr_intern(vpn_bpi->attr); } static void rfapiMonitorEncapDelete(struct bgp_path_info *vpn_bpi) { /* * Remove encap monitor */ vnc_zlog_debug_verbose("%s: vpn_bpi=%p", __func__, vpn_bpi); if (vpn_bpi->extra) { struct rfapi_monitor_encap *hme = vpn_bpi->extra->vnc.import.hme; if (hme) { vnc_zlog_debug_verbose("%s: hme=%p", __func__, hme); /* Refcount checking takes too long here */ // RFAPI_CHECK_REFCOUNT(hme->rn, SAFI_ENCAP, 0); if (hme->next) hme->next->prev = hme->prev; if (hme->prev) hme->prev->next = hme->next; else RFAPI_MONITOR_ENCAP_W_ALLOC(hme->rn) = hme->next; /* Refcount checking takes too long here */ // RFAPI_CHECK_REFCOUNT(hme->rn, SAFI_ENCAP, 1); /* see if the struct rfapi_it_extra is empty and can be * freed */ rfapiMonitorExtraPrune(SAFI_ENCAP, hme->rn); agg_unlock_node(hme->rn); /* decr ref count */ XFREE(MTYPE_RFAPI_MONITOR_ENCAP, hme); vpn_bpi->extra->vnc.import.hme = NULL; } } } /* * quagga lib/thread.h says this must return int even though * it doesn't do anything with the return value */ static int rfapiWithdrawTimerVPN(struct thread *t) { struct rfapi_withdraw *wcb = t->arg; struct bgp_path_info *bpi = wcb->info; struct bgp *bgp = bgp_get_default(); struct rfapi_monitor_vpn *moved; afi_t afi; if (bgp == NULL) { vnc_zlog_debug_verbose( "%s: NULL BGP pointer, assume shutdown race condition!!!", __func__); return 0; } if (bgp_flag_check(bgp, BGP_FLAG_DELETE_IN_PROGRESS)) { vnc_zlog_debug_verbose( "%s: BGP delete in progress, assume shutdown race condition!!!", __func__); return 0; } assert(wcb->node); assert(bpi); assert(wcb->import_table); assert(bpi->extra); RFAPI_CHECK_REFCOUNT(wcb->node, SAFI_MPLS_VPN, wcb->lockoffset); { char buf[BUFSIZ]; vnc_zlog_debug_verbose( "%s: removing bpi %p at prefix %s/%d", __func__, bpi, rfapi_ntop(wcb->node->p.family, &wcb->node->p.u.prefix, buf, BUFSIZ), wcb->node->p.prefixlen); } /* * Remove the route (doubly-linked) */ if (CHECK_FLAG(bpi->flags, BGP_PATH_VALID) && VALID_INTERIOR_TYPE(bpi->type)) RFAPI_MONITOR_EXTERIOR(wcb->node)->valid_interior_count--; afi = family2afi(wcb->node->p.family); wcb->import_table->holddown_count[afi] -= 1; /* keep count consistent */ rfapiItBiIndexDel(wcb->node, bpi); rfapiBgpInfoDetach(wcb->node, bpi); /* with removed bpi */ vnc_import_bgp_exterior_del_route_interior(bgp, wcb->import_table, wcb->node, bpi); /* * If VNC is configured to send response remove messages, AND * if the removed route had a UN address, do response removal * processing. */ if (!(bgp->rfapi_cfg->flags & BGP_VNC_CONFIG_RESPONSE_REMOVAL_DISABLE)) { int has_valid_duplicate = 0; struct bgp_path_info *bpii; /* * First check if there are any OTHER routes at this node * that have the same nexthop and a valid UN address. If * there are (e.g., from other peers), then the route isn't * really gone, so skip sending a response removal message. */ for (bpii = wcb->node->info; bpii; bpii = bpii->next) { if (rfapiVpnBiSamePtUn(bpi, bpii)) { has_valid_duplicate = 1; break; } } vnc_zlog_debug_verbose("%s: has_valid_duplicate=%d", __func__, has_valid_duplicate); if (!has_valid_duplicate) { rfapiRibPendingDeleteRoute(bgp, wcb->import_table, afi, wcb->node); } } rfapiMonitorEncapDelete(bpi); /* * If there are no VPN monitors at this VPN Node A, * we are done */ if (!RFAPI_MONITOR_VPN(wcb->node)) { vnc_zlog_debug_verbose("%s: no VPN monitors at this node", __func__); goto done; } /* * rfapiMonitorMoveShorter only moves monitors if there are * no remaining valid routes at the current node */ moved = rfapiMonitorMoveShorter(wcb->node, 1); if (moved) { rfapiMonitorMovedUp(wcb->import_table, wcb->node, moved->node, moved); } done: /* * Free VPN bpi */ rfapiBgpInfoFree(bpi); wcb->info = NULL; /* * If route count at this node has gone to 0, withdraw exported prefix */ if (!wcb->node->info) { /* see if the struct rfapi_it_extra is empty and can be freed */ rfapiMonitorExtraPrune(SAFI_MPLS_VPN, wcb->node); vnc_direct_bgp_del_prefix(bgp, wcb->import_table, wcb->node); vnc_zebra_del_prefix(bgp, wcb->import_table, wcb->node); } else { /* * nexthop change event * vnc_direct_bgp_add_prefix() will recompute the VN addr * ecommunity */ vnc_direct_bgp_add_prefix(bgp, wcb->import_table, wcb->node); } RFAPI_CHECK_REFCOUNT(wcb->node, SAFI_MPLS_VPN, 1 + wcb->lockoffset); agg_unlock_node(wcb->node); /* decr ref count */ XFREE(MTYPE_RFAPI_WITHDRAW, wcb); return 0; } /* * This works for multiprotocol extension, but not for plain ol' * unicast IPv4 because that nexthop is stored in attr->nexthop */ void rfapiNexthop2Prefix(struct attr *attr, struct prefix *p) { assert(p); assert(attr); memset(p, 0, sizeof(struct prefix)); switch (p->family = BGP_MP_NEXTHOP_FAMILY(attr->mp_nexthop_len)) { case AF_INET: p->u.prefix4 = attr->mp_nexthop_global_in; p->prefixlen = 32; break; case AF_INET6: p->u.prefix6 = attr->mp_nexthop_global; p->prefixlen = 128; break; default: vnc_zlog_debug_verbose("%s: Family is unknown = %d", __func__, p->family); } } void rfapiUnicastNexthop2Prefix(afi_t afi, struct attr *attr, struct prefix *p) { if (afi == AFI_IP) { p->family = AF_INET; p->prefixlen = 32; p->u.prefix4 = attr->nexthop; } else { rfapiNexthop2Prefix(attr, p); } } static int rfapiAttrNexthopAddrDifferent(struct prefix *p1, struct prefix *p2) { if (!p1 || !p2) { vnc_zlog_debug_verbose("%s: p1 or p2 is NULL", __func__); return 1; } /* * Are address families the same? */ if (p1->family != p2->family) { return 1; } switch (p1->family) { case AF_INET: if (IPV4_ADDR_SAME(&p1->u.prefix4, &p2->u.prefix4)) return 0; break; case AF_INET6: if (IPV6_ADDR_SAME(&p1->u.prefix6, &p2->u.prefix6)) return 0; break; default: assert(1); } return 1; } static void rfapiCopyUnEncap2VPN(struct bgp_path_info *encap_bpi, struct bgp_path_info *vpn_bpi) { if (!encap_bpi->attr) { zlog_warn("%s: no encap bpi attr/extra, can't copy UN address", __func__); return; } if (!vpn_bpi || !vpn_bpi->extra) { zlog_warn("%s: no vpn bpi attr/extra, can't copy UN address", __func__); return; } switch (BGP_MP_NEXTHOP_FAMILY(encap_bpi->attr->mp_nexthop_len)) { case AF_INET: /* * instrumentation to debug segfault of 091127 */ vnc_zlog_debug_verbose("%s: vpn_bpi=%p", __func__, vpn_bpi); if (vpn_bpi) { vnc_zlog_debug_verbose("%s: vpn_bpi->extra=%p", __func__, vpn_bpi->extra); } vpn_bpi->extra->vnc.import.un_family = AF_INET; vpn_bpi->extra->vnc.import.un.addr4 = encap_bpi->attr->mp_nexthop_global_in; break; case AF_INET6: vpn_bpi->extra->vnc.import.un_family = AF_INET6; vpn_bpi->extra->vnc.import.un.addr6 = encap_bpi->attr->mp_nexthop_global; break; default: zlog_warn("%s: invalid encap nexthop length: %d", __func__, encap_bpi->attr->mp_nexthop_len); vpn_bpi->extra->vnc.import.un_family = 0; break; } } /* * returns 0 on success, nonzero on error */ static int rfapiWithdrawEncapUpdateCachedUn(struct rfapi_import_table *import_table, struct bgp_path_info *encap_bpi, struct agg_node *vpn_rn, struct bgp_path_info *vpn_bpi) { if (!encap_bpi) { /* * clear cached UN address */ if (!vpn_bpi || !vpn_bpi->extra) { zlog_warn( "%s: missing VPN bpi/extra, can't clear UN addr", __func__); return 1; } vpn_bpi->extra->vnc.import.un_family = 0; memset(&vpn_bpi->extra->vnc.import.un, 0, sizeof(vpn_bpi->extra->vnc.import.un)); if (CHECK_FLAG(vpn_bpi->flags, BGP_PATH_VALID)) { if (rfapiGetVncTunnelUnAddr(vpn_bpi->attr, NULL)) { UNSET_FLAG(vpn_bpi->flags, BGP_PATH_VALID); if (VALID_INTERIOR_TYPE(vpn_bpi->type)) RFAPI_MONITOR_EXTERIOR(vpn_rn) ->valid_interior_count--; /* signal interior route withdrawal to * import-exterior */ vnc_import_bgp_exterior_del_route_interior( bgp_get_default(), import_table, vpn_rn, vpn_bpi); } } } else { if (!vpn_bpi) { zlog_warn("%s: missing VPN bpi, can't clear UN addr", __func__); return 1; } rfapiCopyUnEncap2VPN(encap_bpi, vpn_bpi); if (!CHECK_FLAG(vpn_bpi->flags, BGP_PATH_VALID)) { SET_FLAG(vpn_bpi->flags, BGP_PATH_VALID); if (VALID_INTERIOR_TYPE(vpn_bpi->type)) RFAPI_MONITOR_EXTERIOR(vpn_rn) ->valid_interior_count++; /* signal interior route withdrawal to import-exterior */ vnc_import_bgp_exterior_add_route_interior( bgp_get_default(), import_table, vpn_rn, vpn_bpi); } } return 0; } static int rfapiWithdrawTimerEncap(struct thread *t) { struct rfapi_withdraw *wcb = t->arg; struct bgp_path_info *bpi = wcb->info; int was_first_route = 0; struct rfapi_monitor_encap *em; struct skiplist *vpn_node_sl = skiplist_new(0, NULL, NULL); assert(wcb->node); assert(bpi); assert(wcb->import_table); RFAPI_CHECK_REFCOUNT(wcb->node, SAFI_ENCAP, 0); if (wcb->node->info == bpi) was_first_route = 1; /* * Remove the route/bpi and free it */ rfapiBgpInfoDetach(wcb->node, bpi); rfapiBgpInfoFree(bpi); if (!was_first_route) goto done; for (em = RFAPI_MONITOR_ENCAP(wcb->node); em; em = em->next) { /* * Update monitoring VPN BPIs with new encap info at the * head of the encap bpi chain (which could be NULL after * removing the expiring bpi above) */ if (rfapiWithdrawEncapUpdateCachedUn(wcb->import_table, wcb->node->info, em->node, em->bpi)) continue; /* * Build a list of unique VPN nodes referenced by these * monitors. * Use a skiplist for speed. */ skiplist_insert(vpn_node_sl, em->node, em->node); } /* * for each VPN node referenced in the ENCAP monitors: */ struct agg_node *rn; while (!skiplist_first(vpn_node_sl, (void **)&rn, NULL)) { if (!wcb->node->info) { struct rfapi_monitor_vpn *moved; moved = rfapiMonitorMoveShorter(rn, 0); if (moved) { // rfapiDoRouteCallback(wcb->import_table, // moved->node, moved); rfapiMonitorMovedUp(wcb->import_table, rn, moved->node, moved); } } else { // rfapiDoRouteCallback(wcb->import_table, rn, NULL); rfapiMonitorItNodeChanged(wcb->import_table, rn, NULL); } skiplist_delete_first(vpn_node_sl); } done: RFAPI_CHECK_REFCOUNT(wcb->node, SAFI_ENCAP, 1); agg_unlock_node(wcb->node); /* decr ref count */ XFREE(MTYPE_RFAPI_WITHDRAW, wcb); skiplist_free(vpn_node_sl); return 0; } /* * Works for both VPN and ENCAP routes; timer_service_func is different * in each case */ static void rfapiBiStartWithdrawTimer(struct rfapi_import_table *import_table, struct agg_node *rn, struct bgp_path_info *bpi, afi_t afi, safi_t safi, int (*timer_service_func)(struct thread *)) { uint32_t lifetime; struct rfapi_withdraw *wcb; if (CHECK_FLAG(bpi->flags, BGP_PATH_REMOVED)) { /* * Already on the path to being withdrawn, * should already have a timer set up to * delete it. */ vnc_zlog_debug_verbose( "%s: already being withdrawn, do nothing", __func__); return; } rfapiGetVncLifetime(bpi->attr, &lifetime); vnc_zlog_debug_verbose("%s: VNC lifetime is %u", __func__, lifetime); /* * withdrawn routes get to hang around for a while */ SET_FLAG(bpi->flags, BGP_PATH_REMOVED); /* set timer to remove the route later */ lifetime = rfapiGetHolddownFromLifetime(lifetime); vnc_zlog_debug_verbose("%s: using timeout %u", __func__, lifetime); /* * Stash import_table, node, and info for use by timer * service routine, which is supposed to free the wcb. */ wcb = XCALLOC(MTYPE_RFAPI_WITHDRAW, sizeof(struct rfapi_withdraw)); assert(wcb); wcb->node = rn; wcb->info = bpi; wcb->import_table = import_table; bgp_attr_intern(bpi->attr); if (VNC_DEBUG(VERBOSE)) { vnc_zlog_debug_verbose( "%s: wcb values: node=%p, info=%p, import_table=%p (bpi follows)", __func__, wcb->node, wcb->info, wcb->import_table); rfapiPrintBi(NULL, bpi); } assert(bpi->extra); if (lifetime > UINT32_MAX / 1001) { /* sub-optimal case, but will probably never happen */ bpi->extra->vnc.import.timer = NULL; thread_add_timer(bm->master, timer_service_func, wcb, lifetime, &bpi->extra->vnc.import.timer); } else { static uint32_t jitter; uint32_t lifetime_msec; /* * the goal here is to spread out the timers so they are * sortable in the skip list */ if (++jitter >= 1000) jitter = 0; lifetime_msec = (lifetime * 1000) + jitter; bpi->extra->vnc.import.timer = NULL; thread_add_timer_msec(bm->master, timer_service_func, wcb, lifetime_msec, &bpi->extra->vnc.import.timer); } /* re-sort route list (BGP_PATH_REMOVED routes are last) */ if (((struct bgp_path_info *)rn->info)->next) { rfapiBgpInfoDetach(rn, bpi); rfapiBgpInfoAttachSorted(rn, bpi, afi, safi); } } typedef void(rfapi_bi_filtered_import_f)(struct rfapi_import_table *, int, struct peer *, void *, struct prefix *, struct prefix *, afi_t, struct prefix_rd *, struct attr *, uint8_t, uint8_t, uint32_t *); static void rfapiExpireEncapNow(struct rfapi_import_table *it, struct agg_node *rn, struct bgp_path_info *bpi) { struct rfapi_withdraw *wcb; struct thread t; /* * pretend we're an expiring timer */ wcb = XCALLOC(MTYPE_RFAPI_WITHDRAW, sizeof(struct rfapi_withdraw)); wcb->info = bpi; wcb->node = rn; wcb->import_table = it; memset(&t, 0, sizeof(t)); t.arg = wcb; rfapiWithdrawTimerEncap(&t); /* frees wcb */ } static int rfapiGetNexthop(struct attr *attr, struct prefix *prefix) { switch (BGP_MP_NEXTHOP_FAMILY(attr->mp_nexthop_len)) { case AF_INET: prefix->family = AF_INET; prefix->prefixlen = 32; prefix->u.prefix4 = attr->mp_nexthop_global_in; break; case AF_INET6: prefix->family = AF_INET6; prefix->prefixlen = 128; prefix->u.prefix6 = attr->mp_nexthop_global; break; default: vnc_zlog_debug_verbose("%s: unknown attr->mp_nexthop_len %d", __func__, attr->mp_nexthop_len); return EINVAL; } return 0; } /* * import a bgp_path_info if its route target list intersects with the * import table's route target list */ static void rfapiBgpInfoFilteredImportEncap( struct rfapi_import_table *import_table, int action, struct peer *peer, void *rfd, /* set for looped back routes */ struct prefix *p, struct prefix *aux_prefix, /* Unused for encap routes */ afi_t afi, struct prefix_rd *prd, struct attr *attr, /* part of bgp_path_info */ uint8_t type, /* part of bgp_path_info */ uint8_t sub_type, /* part of bgp_path_info */ uint32_t *label) /* part of bgp_path_info */ { struct agg_table *rt = NULL; struct agg_node *rn; struct bgp_path_info *info_new; struct bgp_path_info *bpi; struct bgp_path_info *next; char buf[BUFSIZ]; struct prefix p_firstbpi_old; struct prefix p_firstbpi_new; int replacing = 0; const char *action_str = NULL; struct prefix un_prefix; struct bgp *bgp; bgp = bgp_get_default(); /* assume 1 instance for now */ switch (action) { case FIF_ACTION_UPDATE: action_str = "update"; break; case FIF_ACTION_WITHDRAW: action_str = "withdraw"; break; case FIF_ACTION_KILL: action_str = "kill"; break; default: assert(0); break; } vnc_zlog_debug_verbose( "%s: entry: %s: prefix %s/%d", __func__, action_str, inet_ntop(p->family, &p->u.prefix, buf, BUFSIZ), p->prefixlen); memset(&p_firstbpi_old, 0, sizeof(p_firstbpi_old)); memset(&p_firstbpi_new, 0, sizeof(p_firstbpi_new)); if (action == FIF_ACTION_UPDATE) { /* * Compare rt lists. If no intersection, don't import this route * On a withdraw, peer and RD are sufficient to determine if * we should act. */ if (!attr || !attr->ecommunity) { vnc_zlog_debug_verbose( "%s: attr, extra, or ecommunity missing, not importing", __func__); return; } #if RFAPI_REQUIRE_ENCAP_BEEC if (!rfapiEcommunitiesMatchBeec(attr->ecommunity)) { vnc_zlog_debug_verbose( "%s: it=%p: no match for BGP Encapsulation ecommunity", __func__, import_table); return; } #endif if (!rfapiEcommunitiesIntersect(import_table->rt_import_list, attr->ecommunity)) { vnc_zlog_debug_verbose( "%s: it=%p: no ecommunity intersection", __func__, import_table); return; } /* * Updates must also have a nexthop address */ memset(&un_prefix, 0, sizeof(un_prefix)); /* keep valgrind happy */ if (rfapiGetNexthop(attr, &un_prefix)) { vnc_zlog_debug_verbose("%s: missing nexthop address", __func__); return; } } /* * Figure out which radix tree the route would go into */ switch (afi) { case AFI_IP: case AFI_IP6: rt = import_table->imported_encap[afi]; break; default: flog_err(EC_LIB_DEVELOPMENT, "%s: bad afi %d", __func__, afi); return; } /* * agg_node_lookup returns a node only if there is at least * one route attached. */ rn = agg_node_lookup(rt, p); #if DEBUG_ENCAP_MONITOR vnc_zlog_debug_verbose("%s: initial encap lookup(it=%p) rn=%p", __func__, import_table, rn); #endif if (rn) { RFAPI_CHECK_REFCOUNT(rn, SAFI_ENCAP, 1); agg_unlock_node(rn); /* undo lock in agg_node_lookup */ /* * capture nexthop of first bpi */ if (rn->info) { rfapiNexthop2Prefix( ((struct bgp_path_info *)(rn->info))->attr, &p_firstbpi_old); } for (bpi = rn->info; bpi; bpi = bpi->next) { /* * Does this bgp_path_info refer to the same route * as we are trying to add? */ vnc_zlog_debug_verbose("%s: comparing BPI %p", __func__, bpi); /* * Compare RDs * * RD of import table bpi is in * bpi->extra->vnc.import.rd RD of info_orig is in prd */ if (!bpi->extra) { vnc_zlog_debug_verbose("%s: no bpi->extra", __func__); continue; } if (prefix_cmp( (struct prefix *)&bpi->extra->vnc.import.rd, (struct prefix *)prd)) { vnc_zlog_debug_verbose("%s: prd does not match", __func__); continue; } /* * Compare peers */ if (bpi->peer != peer) { vnc_zlog_debug_verbose( "%s: peer does not match", __func__); continue; } vnc_zlog_debug_verbose("%s: found matching bpi", __func__); /* Same route. Delete this bpi, replace with new one */ if (action == FIF_ACTION_WITHDRAW) { vnc_zlog_debug_verbose( "%s: withdrawing at prefix %s/%d", __func__, inet_ntop(rn->p.family, &rn->p.u.prefix, buf, BUFSIZ), rn->p.prefixlen); rfapiBiStartWithdrawTimer( import_table, rn, bpi, afi, SAFI_ENCAP, rfapiWithdrawTimerEncap); } else { vnc_zlog_debug_verbose( "%s: %s at prefix %s/%d", __func__, ((action == FIF_ACTION_KILL) ? "killing" : "replacing"), inet_ntop(rn->p.family, &rn->p.u.prefix, buf, BUFSIZ), rn->p.prefixlen); /* * If this route is waiting to be deleted * because of * a previous withdraw, we must cancel its * timer. */ if (CHECK_FLAG(bpi->flags, BGP_PATH_REMOVED) && bpi->extra->vnc.import.timer) { struct thread *t = (struct thread *)bpi->extra->vnc .import.timer; struct rfapi_withdraw *wcb = t->arg; XFREE(MTYPE_RFAPI_WITHDRAW, wcb); thread_cancel(t); } if (action == FIF_ACTION_UPDATE) { rfapiBgpInfoDetach(rn, bpi); rfapiBgpInfoFree(bpi); replacing = 1; } else { /* * Kill: do export stuff when removing * bpi */ struct rfapi_withdraw *wcb; struct thread t; /* * pretend we're an expiring timer */ wcb = XCALLOC( MTYPE_RFAPI_WITHDRAW, sizeof(struct rfapi_withdraw)); wcb->info = bpi; wcb->node = rn; wcb->import_table = import_table; memset(&t, 0, sizeof(t)); t.arg = wcb; rfapiWithdrawTimerEncap( &t); /* frees wcb */ } } break; } } if (rn) RFAPI_CHECK_REFCOUNT(rn, SAFI_ENCAP, replacing ? 1 : 0); if (action == FIF_ACTION_WITHDRAW || action == FIF_ACTION_KILL) return; info_new = rfapiBgpInfoCreate(attr, peer, rfd, prd, type, sub_type, NULL); if (rn) { if (!replacing) agg_lock_node(rn); /* incr ref count for new BPI */ } else { rn = agg_node_get(rt, p); } vnc_zlog_debug_verbose( "%s: (afi=%d, rn=%p) inserting at prefix %s/%d", __func__, afi, rn, inet_ntop(rn->p.family, &rn->p.u.prefix, buf, BUFSIZ), rn->p.prefixlen); rfapiBgpInfoAttachSorted(rn, info_new, afi, SAFI_ENCAP); /* * Delete holddown routes from same NVE. See details in * rfapiBgpInfoFilteredImportVPN() */ for (bpi = info_new->next; bpi; bpi = next) { struct prefix pfx_un; int un_match = 0; next = bpi->next; if (!CHECK_FLAG(bpi->flags, BGP_PATH_REMOVED)) continue; /* * We already match the VN address (it is the prefix * of the route node) */ if (!rfapiGetNexthop(bpi->attr, &pfx_un) && prefix_same(&pfx_un, &un_prefix)) { un_match = 1; } if (!un_match) continue; vnc_zlog_debug_verbose( "%s: removing holddown bpi matching NVE of new route", __func__); if (bpi->extra->vnc.import.timer) { struct thread *t = (struct thread *)bpi->extra->vnc.import.timer; struct rfapi_withdraw *wcb = t->arg; XFREE(MTYPE_RFAPI_WITHDRAW, wcb); thread_cancel(t); } rfapiExpireEncapNow(import_table, rn, bpi); } rfapiNexthop2Prefix(((struct bgp_path_info *)(rn->info))->attr, &p_firstbpi_new); /* * If the nexthop address of the selected Encap route (i.e., * the UN address) has changed, then we must update the VPN * routes that refer to this Encap route and possibly force * rfapi callbacks. */ if (rfapiAttrNexthopAddrDifferent(&p_firstbpi_old, &p_firstbpi_new)) { struct rfapi_monitor_encap *m; struct rfapi_monitor_encap *mnext; struct agg_node *referenced_vpn_prefix; /* * Optimized approach: build radix tree on the fly to * hold list of VPN nodes referenced by the ENCAP monitors * * The nodes in this table correspond to prefixes of VPN routes. * The "info" pointer of the node points to a chain of * struct rfapi_monitor_encap, each of which refers to a * specific VPN node. */ struct agg_table *referenced_vpn_table; referenced_vpn_table = agg_table_init(); assert(referenced_vpn_table); /* * iterate over the set of monitors at this ENCAP node. */ #if DEBUG_ENCAP_MONITOR vnc_zlog_debug_verbose("%s: examining monitors at rn=%p", __func__, rn); #endif for (m = RFAPI_MONITOR_ENCAP(rn); m; m = m->next) { /* * For each referenced bpi/route, copy the ENCAP route's * nexthop to the VPN route's cached UN address field * and set * the address family of the cached UN address field. */ rfapiCopyUnEncap2VPN(info_new, m->bpi); if (!CHECK_FLAG(m->bpi->flags, BGP_PATH_VALID)) { SET_FLAG(m->bpi->flags, BGP_PATH_VALID); if (VALID_INTERIOR_TYPE(m->bpi->type)) RFAPI_MONITOR_EXTERIOR(m->node) ->valid_interior_count++; vnc_import_bgp_exterior_add_route_interior( bgp, import_table, m->node, m->bpi); } /* * Build a list of unique VPN nodes referenced by these * monitors * * There could be more than one VPN node here with a * given * prefix. Those are currently in an unsorted linear * list * per prefix. */ referenced_vpn_prefix = agg_node_get(referenced_vpn_table, &m->node->p); assert(referenced_vpn_prefix); for (mnext = referenced_vpn_prefix->info; mnext; mnext = mnext->next) { if (mnext->node == m->node) break; } if (mnext) { /* * already have an entry for this VPN node */ agg_unlock_node(referenced_vpn_prefix); } else { mnext = XCALLOC( MTYPE_RFAPI_MONITOR_ENCAP, sizeof(struct rfapi_monitor_encap)); assert(mnext); mnext->node = m->node; mnext->next = referenced_vpn_prefix->info; referenced_vpn_prefix->info = mnext; } } /* * for each VPN node referenced in the ENCAP monitors: */ for (referenced_vpn_prefix = agg_route_top(referenced_vpn_table); referenced_vpn_prefix; referenced_vpn_prefix = agg_route_next(referenced_vpn_prefix)) { while ((m = referenced_vpn_prefix->info)) { struct agg_node *n; rfapiMonitorMoveLonger(m->node); for (n = m->node; n; n = agg_node_parent(n)) { // rfapiDoRouteCallback(import_table, n, // NULL); } rfapiMonitorItNodeChanged(import_table, m->node, NULL); referenced_vpn_prefix->info = m->next; agg_unlock_node(referenced_vpn_prefix); XFREE(MTYPE_RFAPI_MONITOR_ENCAP, m); } } agg_table_finish(referenced_vpn_table); } RFAPI_CHECK_REFCOUNT(rn, SAFI_ENCAP, 0); } static void rfapiExpireVpnNow(struct rfapi_import_table *it, struct agg_node *rn, struct bgp_path_info *bpi, int lockoffset) { struct rfapi_withdraw *wcb; struct thread t; /* * pretend we're an expiring timer */ wcb = XCALLOC(MTYPE_RFAPI_WITHDRAW, sizeof(struct rfapi_withdraw)); wcb->info = bpi; wcb->node = rn; wcb->import_table = it; wcb->lockoffset = lockoffset; memset(&t, 0, sizeof(t)); t.arg = wcb; rfapiWithdrawTimerVPN(&t); /* frees wcb */ } /* * import a bgp_path_info if its route target list intersects with the * import table's route target list */ void rfapiBgpInfoFilteredImportVPN( struct rfapi_import_table *import_table, int action, struct peer *peer, void *rfd, /* set for looped back routes */ struct prefix *p, struct prefix *aux_prefix, /* AFI_L2VPN: optional IP */ afi_t afi, struct prefix_rd *prd, struct attr *attr, /* part of bgp_path_info */ uint8_t type, /* part of bgp_path_info */ uint8_t sub_type, /* part of bgp_path_info */ uint32_t *label) /* part of bgp_path_info */ { struct agg_table *rt = NULL; struct agg_node *rn; struct agg_node *n; struct bgp_path_info *info_new; struct bgp_path_info *bpi; struct bgp_path_info *next; char buf[BUFSIZ]; struct prefix vn_prefix; struct prefix un_prefix; int un_prefix_valid = 0; struct agg_node *ern; int replacing = 0; int original_had_routes = 0; struct prefix original_nexthop; const char *action_str = NULL; int is_it_ce = 0; struct bgp *bgp; bgp = bgp_get_default(); /* assume 1 instance for now */ switch (action) { case FIF_ACTION_UPDATE: action_str = "update"; break; case FIF_ACTION_WITHDRAW: action_str = "withdraw"; break; case FIF_ACTION_KILL: action_str = "kill"; break; default: assert(0); break; } if (import_table == bgp->rfapi->it_ce) is_it_ce = 1; vnc_zlog_debug_verbose("%s: entry: %s%s: prefix %s/%d: it %p, afi %s", __func__, (is_it_ce ? "CE-IT " : ""), action_str, rfapi_ntop(p->family, &p->u.prefix, buf, BUFSIZ), p->prefixlen, import_table, afi2str(afi)); VNC_ITRCCK; /* * Compare rt lists. If no intersection, don't import this route * On a withdraw, peer and RD are sufficient to determine if * we should act. */ if (action == FIF_ACTION_UPDATE) { if (!attr || !attr->ecommunity) { vnc_zlog_debug_verbose( "%s: attr, extra, or ecommunity missing, not importing", __func__); return; } if ((import_table != bgp->rfapi->it_ce) && !rfapiEcommunitiesIntersect(import_table->rt_import_list, attr->ecommunity)) { vnc_zlog_debug_verbose( "%s: it=%p: no ecommunity intersection", __func__, import_table); return; } memset(&vn_prefix, 0, sizeof(vn_prefix)); /* keep valgrind happy */ if (rfapiGetNexthop(attr, &vn_prefix)) { /* missing nexthop address would be a bad, bad thing */ vnc_zlog_debug_verbose("%s: missing nexthop", __func__); return; } } /* * Figure out which radix tree the route would go into */ switch (afi) { case AFI_IP: case AFI_IP6: case AFI_L2VPN: rt = import_table->imported_vpn[afi]; break; default: flog_err(EC_LIB_DEVELOPMENT, "%s: bad afi %d", __func__, afi); return; } /* clear it */ memset(&original_nexthop, 0, sizeof(original_nexthop)); /* * agg_node_lookup returns a node only if there is at least * one route attached. */ rn = agg_node_lookup(rt, p); vnc_zlog_debug_verbose("%s: rn=%p", __func__, rn); if (rn) { RFAPI_CHECK_REFCOUNT(rn, SAFI_MPLS_VPN, 1); agg_unlock_node(rn); /* undo lock in agg_node_lookup */ if (rn->info) original_had_routes = 1; if (VNC_DEBUG(VERBOSE)) { vnc_zlog_debug_verbose("%s: showing IT node on entry", __func__); rfapiShowItNode(NULL, rn); /* debug */ } /* * Look for same route (will have same RD and peer) */ bpi = rfapiItBiIndexSearch(rn, prd, peer, aux_prefix); if (bpi) { /* * This was an old test when we iterated over the * BPIs linearly. Since we're now looking up with * RD and peer, comparing types should not be * needed. Changed to assertion. * * Compare types. Doing so prevents a RFP-originated * route from matching an imported route, for example. */ if (VNC_DEBUG(VERBOSE) && bpi->type != type) /* should be handled by RDs, but warn for now */ zlog_warn("%s: type mismatch! (bpi=%d, arg=%d)", __func__, bpi->type, type); vnc_zlog_debug_verbose("%s: found matching bpi", __func__); /* * In the special CE table, withdrawals occur without * holddown */ if (import_table == bgp->rfapi->it_ce) { vnc_direct_bgp_del_route_ce(bgp, rn, bpi); if (action == FIF_ACTION_WITHDRAW) action = FIF_ACTION_KILL; } if (action == FIF_ACTION_WITHDRAW) { int washolddown = CHECK_FLAG(bpi->flags, BGP_PATH_REMOVED); vnc_zlog_debug_verbose( "%s: withdrawing at prefix %s/%d%s", __func__, rfapi_ntop(rn->p.family, &rn->p.u.prefix, buf, BUFSIZ), rn->p.prefixlen, (washolddown ? " (already being withdrawn)" : "")); VNC_ITRCCK; if (!washolddown) { rfapiBiStartWithdrawTimer( import_table, rn, bpi, afi, SAFI_MPLS_VPN, rfapiWithdrawTimerVPN); RFAPI_UPDATE_ITABLE_COUNT( bpi, import_table, afi, -1); import_table->holddown_count[afi] += 1; } VNC_ITRCCK; } else { vnc_zlog_debug_verbose( "%s: %s at prefix %s/%d", __func__, ((action == FIF_ACTION_KILL) ? "killing" : "replacing"), rfapi_ntop(rn->p.family, &rn->p.u.prefix, buf, BUFSIZ), rn->p.prefixlen); /* * If this route is waiting to be deleted * because of * a previous withdraw, we must cancel its * timer. */ if (CHECK_FLAG(bpi->flags, BGP_PATH_REMOVED) && bpi->extra->vnc.import.timer) { struct thread *t = (struct thread *)bpi->extra->vnc .import.timer; struct rfapi_withdraw *wcb = t->arg; XFREE(MTYPE_RFAPI_WITHDRAW, wcb); thread_cancel(t); import_table->holddown_count[afi] -= 1; RFAPI_UPDATE_ITABLE_COUNT( bpi, import_table, afi, 1); } /* * decrement remote count (if route is remote) * because * we are going to remove it below */ RFAPI_UPDATE_ITABLE_COUNT(bpi, import_table, afi, -1); if (action == FIF_ACTION_UPDATE) { replacing = 1; /* * make copy of original nexthop so we * can see if it changed */ rfapiGetNexthop(bpi->attr, &original_nexthop); /* * remove bpi without doing any export * processing */ if (CHECK_FLAG(bpi->flags, BGP_PATH_VALID) && VALID_INTERIOR_TYPE(bpi->type)) RFAPI_MONITOR_EXTERIOR(rn) ->valid_interior_count--; rfapiItBiIndexDel(rn, bpi); rfapiBgpInfoDetach(rn, bpi); rfapiMonitorEncapDelete(bpi); vnc_import_bgp_exterior_del_route_interior( bgp, import_table, rn, bpi); rfapiBgpInfoFree(bpi); } else { /* Kill */ /* * remove bpi and do export processing */ import_table->holddown_count[afi] += 1; rfapiExpireVpnNow(import_table, rn, bpi, 0); } } } } if (rn) RFAPI_CHECK_REFCOUNT(rn, SAFI_MPLS_VPN, replacing ? 1 : 0); if (action == FIF_ACTION_WITHDRAW || action == FIF_ACTION_KILL) { VNC_ITRCCK; return; } info_new = rfapiBgpInfoCreate(attr, peer, rfd, prd, type, sub_type, label); /* * lookup un address in encap table */ ern = agg_node_match(import_table->imported_encap[afi], &vn_prefix); if (ern) { rfapiCopyUnEncap2VPN(ern->info, info_new); agg_unlock_node(ern); /* undo lock in route_note_match */ } else { char bpf[PREFIX_STRLEN]; prefix2str(&vn_prefix, bpf, sizeof(bpf)); /* Not a big deal, just means VPN route got here first */ vnc_zlog_debug_verbose("%s: no encap route for vn addr %s", __func__, bpf); info_new->extra->vnc.import.un_family = 0; } if (rn) { if (!replacing) agg_lock_node(rn); } else { /* * No need to increment reference count, so only "get" * if the node is not there already */ rn = agg_node_get(rt, p); } /* * For ethernet routes, if there is an accompanying IP address, * save it in the bpi */ if ((AFI_L2VPN == afi) && aux_prefix) { vnc_zlog_debug_verbose("%s: setting BPI's aux_prefix", __func__); info_new->extra->vnc.import.aux_prefix = *aux_prefix; } vnc_zlog_debug_verbose( "%s: inserting bpi %p at prefix %s/%d #%d", __func__, info_new, rfapi_ntop(rn->p.family, &rn->p.u.prefix, buf, BUFSIZ), rn->p.prefixlen, rn->lock); rfapiBgpInfoAttachSorted(rn, info_new, afi, SAFI_MPLS_VPN); rfapiItBiIndexAdd(rn, info_new); if (!rfapiGetUnAddrOfVpnBi(info_new, NULL)) { if (VALID_INTERIOR_TYPE(info_new->type)) RFAPI_MONITOR_EXTERIOR(rn)->valid_interior_count++; SET_FLAG(info_new->flags, BGP_PATH_VALID); } RFAPI_UPDATE_ITABLE_COUNT(info_new, import_table, afi, 1); vnc_import_bgp_exterior_add_route_interior(bgp, import_table, rn, info_new); if (import_table == bgp->rfapi->it_ce) vnc_direct_bgp_add_route_ce(bgp, rn, info_new); if (VNC_DEBUG(VERBOSE)) { vnc_zlog_debug_verbose("%s: showing IT node", __func__); rfapiShowItNode(NULL, rn); /* debug */ } rfapiMonitorEncapAdd(import_table, &vn_prefix, rn, info_new); if (!rfapiGetUnAddrOfVpnBi(info_new, &un_prefix)) { /* * if we have a valid UN address (either via Encap route * or via tunnel attribute), then we should attempt * to move any monitors at less-specific nodes to this node */ rfapiMonitorMoveLonger(rn); un_prefix_valid = 1; } /* * 101129 Enhancement: if we add a route (implication: it is not * in holddown), delete all other routes from this nve at this * node that are in holddown, regardless of peer. * * Reasons it's OK to do that: * * - if the holddown route being deleted originally came from BGP VPN, * it is already gone from BGP (implication of holddown), so there * won't be any added inconsistency with the BGP RIB. * * - once a fresh route is added at a prefix, any routes in holddown * at that prefix will not show up in RFP responses, so deleting * the holddown routes won't affect the contents of responses. * * - lifetimes are supposed to be consistent, so there should not * be a case where the fresh route has a shorter lifetime than * the holddown route, so we don't expect the fresh route to * disappear and complete its holddown time before the existing * holddown routes time out. Therefore, we won't have a situation * where we expect the existing holddown routes to be hidden and * then to reappear sometime later (as holddown routes) in a * RFP response. * * Among other things, this would enable us to skirt the problem * of local holddown routes that refer to NVE descriptors that * have already been closed (if the same NVE triggers a subsequent * rfapi_open(), the new peer is different and doesn't match the * peer of the holddown route, so the stale holddown route still * hangs around until it times out instead of just being replaced * by the fresh route). */ /* * We know that the new bpi will have been inserted before any routes * in holddown, so we can skip any that came before it */ for (bpi = info_new->next; bpi; bpi = next) { struct prefix pfx_vn; struct prefix pfx_un; int un_match = 0; int remote_peer_match = 0; next = bpi->next; /* * Must be holddown */ if (!CHECK_FLAG(bpi->flags, BGP_PATH_REMOVED)) continue; /* * Must match VN address (nexthop of VPN route) */ if (rfapiGetNexthop(bpi->attr, &pfx_vn)) continue; if (!prefix_same(&pfx_vn, &vn_prefix)) continue; if (un_prefix_valid && /* new route UN addr */ !rfapiGetUnAddrOfVpnBi(bpi, &pfx_un) && /* old route UN addr */ prefix_same(&pfx_un, &un_prefix)) { /* compare */ un_match = 1; } if (!RFAPI_LOCAL_BI(bpi) && !RFAPI_LOCAL_BI(info_new) && sockunion_same(&bpi->peer->su, &info_new->peer->su)) { /* old & new are both remote, same peer */ remote_peer_match = 1; } if (!un_match & !remote_peer_match) continue; vnc_zlog_debug_verbose( "%s: removing holddown bpi matching NVE of new route", __func__); if (bpi->extra->vnc.import.timer) { struct thread *t = (struct thread *)bpi->extra->vnc.import.timer; struct rfapi_withdraw *wcb = t->arg; XFREE(MTYPE_RFAPI_WITHDRAW, wcb); thread_cancel(t); } rfapiExpireVpnNow(import_table, rn, bpi, 0); } if (!original_had_routes) { /* * We went from 0 usable routes to 1 usable route. Perform the * "Adding a Route" export process. */ vnc_direct_bgp_add_prefix(bgp, import_table, rn); vnc_zebra_add_prefix(bgp, import_table, rn); } else { /* * Check for nexthop change event * Note: the prefix_same() test below detects two situations: * 1. route is replaced, new route has different nexthop * 2. new route is added (original_nexthop is 0) */ struct prefix new_nexthop; rfapiGetNexthop(attr, &new_nexthop); if (!prefix_same(&original_nexthop, &new_nexthop)) { /* * nexthop change event * vnc_direct_bgp_add_prefix() will recompute VN addr * ecommunity */ vnc_direct_bgp_add_prefix(bgp, import_table, rn); } } if (!(bgp->rfapi_cfg->flags & BGP_VNC_CONFIG_CALLBACK_DISABLE)) { for (n = rn; n; n = agg_node_parent(n)) { // rfapiDoRouteCallback(import_table, n, NULL); } rfapiMonitorItNodeChanged(import_table, rn, NULL); } RFAPI_CHECK_REFCOUNT(rn, SAFI_MPLS_VPN, 0); VNC_ITRCCK; } static void rfapiBgpInfoFilteredImportBadSafi( struct rfapi_import_table *import_table, int action, struct peer *peer, void *rfd, /* set for looped back routes */ struct prefix *p, struct prefix *aux_prefix, /* AFI_L2VPN: optional IP */ afi_t afi, struct prefix_rd *prd, struct attr *attr, /* part of bgp_path_info */ uint8_t type, /* part of bgp_path_info */ uint8_t sub_type, /* part of bgp_path_info */ uint32_t *label) /* part of bgp_path_info */ { vnc_zlog_debug_verbose("%s: Error, bad safi", __func__); } static rfapi_bi_filtered_import_f * rfapiBgpInfoFilteredImportFunction(safi_t safi) { switch (safi) { case SAFI_MPLS_VPN: return rfapiBgpInfoFilteredImportVPN; case SAFI_ENCAP: return rfapiBgpInfoFilteredImportEncap; default: /* not expected */ flog_err(EC_LIB_DEVELOPMENT, "%s: bad safi %d", __func__, safi); return rfapiBgpInfoFilteredImportBadSafi; } } void rfapiProcessUpdate(struct peer *peer, void *rfd, /* set when looped from RFP/RFAPI */ struct prefix *p, struct prefix_rd *prd, struct attr *attr, afi_t afi, safi_t safi, uint8_t type, uint8_t sub_type, uint32_t *label) { struct bgp *bgp; struct rfapi *h; struct rfapi_import_table *it; int has_ip_route = 1; uint32_t lni = 0; bgp = bgp_get_default(); /* assume 1 instance for now */ assert(bgp); h = bgp->rfapi; assert(h); /* * look at high-order byte of RD. FF means MAC * address is present (VNC L2VPN) */ if ((safi == SAFI_MPLS_VPN) && (decode_rd_type(prd->val) == RD_TYPE_VNC_ETH)) { struct prefix pfx_mac_buf; struct prefix pfx_nexthop_buf; int rc; /* * Set flag if prefix and nexthop are the same - don't * add the route to normal IP-based import tables */ if (!rfapiGetNexthop(attr, &pfx_nexthop_buf)) { if (!prefix_cmp(&pfx_nexthop_buf, p)) { has_ip_route = 0; } } memset(&pfx_mac_buf, 0, sizeof(pfx_mac_buf)); pfx_mac_buf.family = AF_ETHERNET; pfx_mac_buf.prefixlen = 48; memcpy(&pfx_mac_buf.u.prefix_eth.octet, prd->val + 2, 6); /* * Find rt containing LNI (Logical Network ID), which * _should_ always be present when mac address is present */ rc = rfapiEcommunityGetLNI(attr->ecommunity, &lni); vnc_zlog_debug_verbose( "%s: rfapiEcommunityGetLNI returned %d, lni=%d, attr=%p", __func__, rc, lni, attr); if (!rc) { it = rfapiMacImportTableGet(bgp, lni); rfapiBgpInfoFilteredImportVPN( it, FIF_ACTION_UPDATE, peer, rfd, &pfx_mac_buf, /* prefix */ p, /* aux prefix: IP addr */ AFI_L2VPN, prd, attr, type, sub_type, label); } } if (!has_ip_route) return; /* * Iterate over all import tables; do a filtered import * for the afi/safi combination */ for (it = h->imports; it; it = it->next) { (*rfapiBgpInfoFilteredImportFunction(safi))( it, FIF_ACTION_UPDATE, peer, rfd, p, /* prefix */ NULL, afi, prd, attr, type, sub_type, label); } if (safi == SAFI_MPLS_VPN) { vnc_direct_bgp_rh_add_route(bgp, afi, p, peer, attr); rfapiBgpInfoFilteredImportVPN( bgp->rfapi->it_ce, FIF_ACTION_UPDATE, peer, rfd, p, /* prefix */ NULL, afi, prd, attr, type, sub_type, label); } } void rfapiProcessWithdraw(struct peer *peer, void *rfd, struct prefix *p, struct prefix_rd *prd, struct attr *attr, afi_t afi, safi_t safi, uint8_t type, int kill) { struct bgp *bgp; struct rfapi *h; struct rfapi_import_table *it; bgp = bgp_get_default(); /* assume 1 instance for now */ assert(bgp); h = bgp->rfapi; assert(h); /* * look at high-order byte of RD. FF means MAC * address is present (VNC L2VPN) */ if (h->import_mac != NULL && safi == SAFI_MPLS_VPN && decode_rd_type(prd->val) == RD_TYPE_VNC_ETH) { struct prefix pfx_mac_buf; void *cursor = NULL; int rc; memset(&pfx_mac_buf, 0, sizeof(pfx_mac_buf)); pfx_mac_buf.family = AF_ETHERNET; pfx_mac_buf.prefixlen = 48; memcpy(&pfx_mac_buf.u.prefix_eth, prd->val + 2, 6); /* * withdraw does not contain attrs, so we don't have * access to the route's LNI, which would ordinarily * select the specific mac-based import table. Instead, * we must iterate over all mac-based tables and rely * on the RD to match. * * If this approach is too slow, add an index where * key is {RD, peer} and value is the import table */ for (rc = skiplist_next(h->import_mac, NULL, (void **)&it, &cursor); rc == 0; rc = skiplist_next(h->import_mac, NULL, (void **)&it, &cursor)) { #if DEBUG_L2_EXTRA vnc_zlog_debug_verbose( "%s: calling rfapiBgpInfoFilteredImportVPN(it=%p, afi=AFI_L2VPN)", __func__, it); #endif rfapiBgpInfoFilteredImportVPN( it, (kill ? FIF_ACTION_KILL : FIF_ACTION_WITHDRAW), peer, rfd, &pfx_mac_buf, /* prefix */ p, /* aux_prefix: IP */ AFI_L2VPN, prd, attr, type, 0, NULL); /* sub_type & label unused for withdraw */ } } /* * XXX For the case where the withdraw involves an L2 * route with no IP information, we rely on the lack * of RT-list intersection to filter out the withdraw * from the IP-based import tables below */ /* * Iterate over all import tables; do a filtered import * for the afi/safi combination */ for (it = h->imports; it; it = it->next) { (*rfapiBgpInfoFilteredImportFunction(safi))( it, (kill ? FIF_ACTION_KILL : FIF_ACTION_WITHDRAW), peer, rfd, p, /* prefix */ NULL, afi, prd, attr, type, 0, NULL); /* sub_type & label unused for withdraw */ } /* TBD the deletion should happen after the lifetime expires */ if (safi == SAFI_MPLS_VPN) vnc_direct_bgp_rh_del_route(bgp, afi, p, peer); if (safi == SAFI_MPLS_VPN) { rfapiBgpInfoFilteredImportVPN( bgp->rfapi->it_ce, (kill ? FIF_ACTION_KILL : FIF_ACTION_WITHDRAW), peer, rfd, p, /* prefix */ NULL, afi, prd, attr, type, 0, NULL); /* sub_type & label unused for withdraw */ } } /* * TBD optimized withdraw timer algorithm for case of many * routes expiring at the same time due to peer drop. */ /* * 1. Visit all BPIs in all ENCAP import tables. * * a. If a bpi's peer is the failed peer, remove the bpi. * b. If the removed ENCAP bpi was first in the list of * BPIs at this ENCAP node, loop over all monitors * at this node: * * (1) for each ENCAP monitor, loop over all its * VPN node monitors and set their RFAPI_MON_FLAG_NEEDCALLBACK * flags. * * 2. Visit all BPIs in all VPN import tables. * a. If a bpi's peer is the failed peer, remove the bpi. * b. loop over all the VPN node monitors and set their * RFAPI_MON_FLAG_NEEDCALLBACK flags * c. If there are no BPIs left at this VPN node, * */ /* surprise, this gets called from peer_delete(), from rfapi_close() */ static void rfapiProcessPeerDownRt(struct peer *peer, struct rfapi_import_table *import_table, afi_t afi, safi_t safi) { struct agg_node *rn; struct bgp_path_info *bpi; struct agg_table *rt; int (*timer_service_func)(struct thread *); assert(afi == AFI_IP || afi == AFI_IP6); VNC_ITRCCK; switch (safi) { case SAFI_MPLS_VPN: rt = import_table->imported_vpn[afi]; timer_service_func = rfapiWithdrawTimerVPN; break; case SAFI_ENCAP: rt = import_table->imported_encap[afi]; timer_service_func = rfapiWithdrawTimerEncap; break; default: /* Suppress uninitialized variable warning */ rt = NULL; timer_service_func = NULL; assert(0); } for (rn = agg_route_top(rt); rn; rn = agg_route_next(rn)) { for (bpi = rn->info; bpi; bpi = bpi->next) { if (bpi->peer == peer) { if (CHECK_FLAG(bpi->flags, BGP_PATH_REMOVED)) { /* already in holddown, skip */ continue; } if (safi == SAFI_MPLS_VPN) { RFAPI_UPDATE_ITABLE_COUNT( bpi, import_table, afi, -1); import_table->holddown_count[afi] += 1; } rfapiBiStartWithdrawTimer(import_table, rn, bpi, afi, safi, timer_service_func); } } } VNC_ITRCCK; } /* * This gets called when a peer connection drops. We have to remove * all the routes from this peer. * * Current approach is crude. TBD Optimize by setting fewer timers and * grouping withdrawn routes so we can generate callbacks more * efficiently. */ void rfapiProcessPeerDown(struct peer *peer) { struct bgp *bgp; struct rfapi *h; struct rfapi_import_table *it; /* * If this peer is a "dummy" peer structure atached to a RFAPI * nve_descriptor, we don't need to walk the import tables * because the routes are already withdrawn by rfapi_close() */ if (CHECK_FLAG(peer->flags, PEER_FLAG_IS_RFAPI_HD)) return; /* * 1. Visit all BPIs in all ENCAP import tables. * Start withdraw timer on the BPIs that match peer. * * 2. Visit All BPIs in all VPN import tables. * Start withdraw timer on the BPIs that match peer. */ bgp = bgp_get_default(); /* assume 1 instance for now */ if (!bgp) return; h = bgp->rfapi; assert(h); for (it = h->imports; it; it = it->next) { rfapiProcessPeerDownRt(peer, it, AFI_IP, SAFI_ENCAP); rfapiProcessPeerDownRt(peer, it, AFI_IP6, SAFI_ENCAP); rfapiProcessPeerDownRt(peer, it, AFI_IP, SAFI_MPLS_VPN); rfapiProcessPeerDownRt(peer, it, AFI_IP6, SAFI_MPLS_VPN); } if (h->it_ce) { rfapiProcessPeerDownRt(peer, h->it_ce, AFI_IP, SAFI_MPLS_VPN); rfapiProcessPeerDownRt(peer, h->it_ce, AFI_IP6, SAFI_MPLS_VPN); } } /* * Import an entire RIB (for an afi/safi) to an import table RIB, * filtered according to the import table's RT list * * TBD: does this function need additions to match rfapiProcessUpdate() * for, e.g., L2 handling? */ static void rfapiBgpTableFilteredImport(struct bgp *bgp, struct rfapi_import_table *it, afi_t afi, safi_t safi) { struct bgp_node *rn1; struct bgp_node *rn2; /* Only these SAFIs have 2-level RIBS */ assert(safi == SAFI_MPLS_VPN || safi == SAFI_ENCAP); /* * Now visit all the rd nodes and the nodes of all the * route tables attached to them, and import the routes * if they have matching route targets */ for (rn1 = bgp_table_top(bgp->rib[afi][safi]); rn1; rn1 = bgp_route_next(rn1)) { if (bgp_node_has_bgp_path_info_data(rn1)) { for (rn2 = bgp_table_top(bgp_node_get_bgp_table_info(rn1)); rn2; rn2 = bgp_route_next(rn2)) { struct bgp_path_info *bpi; for (bpi = bgp_node_get_bgp_path_info(rn2); bpi; bpi = bpi->next) { uint32_t label = 0; if (CHECK_FLAG(bpi->flags, BGP_PATH_REMOVED)) continue; if (bpi->extra) label = decode_label( &bpi->extra->label[0]); (*rfapiBgpInfoFilteredImportFunction( safi))( it, /* which import table */ FIF_ACTION_UPDATE, bpi->peer, NULL, &rn2->p, /* prefix */ NULL, afi, (struct prefix_rd *)&rn1->p, bpi->attr, bpi->type, bpi->sub_type, &label); } } } } } /* per-bgp-instance rfapi data */ struct rfapi *bgp_rfapi_new(struct bgp *bgp) { struct rfapi *h; afi_t afi; struct rfapi_rfp_cfg *cfg = NULL; struct rfapi_rfp_cb_methods *cbm = NULL; assert(bgp->rfapi_cfg == NULL); h = XCALLOC(MTYPE_RFAPI, sizeof(struct rfapi)); for (afi = AFI_IP; afi < AFI_MAX; afi++) { h->un[afi] = agg_table_init(); } /* * initialize the ce import table */ h->it_ce = XCALLOC(MTYPE_RFAPI_IMPORTTABLE, sizeof(struct rfapi_import_table)); h->it_ce->imported_vpn[AFI_IP] = agg_table_init(); h->it_ce->imported_vpn[AFI_IP6] = agg_table_init(); h->it_ce->imported_encap[AFI_IP] = agg_table_init(); h->it_ce->imported_encap[AFI_IP6] = agg_table_init(); rfapiBgpTableFilteredImport(bgp, h->it_ce, AFI_IP, SAFI_MPLS_VPN); rfapiBgpTableFilteredImport(bgp, h->it_ce, AFI_IP6, SAFI_MPLS_VPN); /* * Set up work queue for deferred rfapi_close operations */ h->deferred_close_q = work_queue_new(bm->master, "rfapi deferred close"); h->deferred_close_q->spec.workfunc = rfapi_deferred_close_workfunc; h->deferred_close_q->spec.data = h; h->rfp = rfp_start(bm->master, &cfg, &cbm); bgp->rfapi_cfg = bgp_rfapi_cfg_new(cfg); if (cbm != NULL) { h->rfp_methods = *cbm; } return h; } void bgp_rfapi_destroy(struct bgp *bgp, struct rfapi *h) { afi_t afi; if (bgp == NULL || h == NULL) return; if (h->resolve_nve_nexthop) { skiplist_free(h->resolve_nve_nexthop); h->resolve_nve_nexthop = NULL; } agg_table_finish(h->it_ce->imported_vpn[AFI_IP]); agg_table_finish(h->it_ce->imported_vpn[AFI_IP6]); agg_table_finish(h->it_ce->imported_encap[AFI_IP]); agg_table_finish(h->it_ce->imported_encap[AFI_IP6]); if (h->import_mac) { struct rfapi_import_table *it; void *cursor; int rc; for (cursor = NULL, rc = skiplist_next(h->import_mac, NULL, (void **)&it, &cursor); !rc; rc = skiplist_next(h->import_mac, NULL, (void **)&it, &cursor)) { rfapiImportTableFlush(it); XFREE(MTYPE_RFAPI_IMPORTTABLE, it); } skiplist_free(h->import_mac); h->import_mac = NULL; } work_queue_free_and_null(&h->deferred_close_q); if (h->rfp != NULL) rfp_stop(h->rfp); for (afi = AFI_IP; afi < AFI_MAX; afi++) { agg_table_finish(h->un[afi]); } XFREE(MTYPE_RFAPI_IMPORTTABLE, h->it_ce); XFREE(MTYPE_RFAPI, h); } struct rfapi_import_table * rfapiImportTableRefAdd(struct bgp *bgp, struct ecommunity *rt_import_list, struct rfapi_nve_group_cfg *rfg) { struct rfapi *h; struct rfapi_import_table *it; afi_t afi; h = bgp->rfapi; assert(h); for (it = h->imports; it; it = it->next) { if (ecommunity_cmp(it->rt_import_list, rt_import_list)) break; } vnc_zlog_debug_verbose("%s: matched it=%p", __func__, it); if (!it) { it = XCALLOC(MTYPE_RFAPI_IMPORTTABLE, sizeof(struct rfapi_import_table)); assert(it); it->next = h->imports; h->imports = it; it->rt_import_list = ecommunity_dup(rt_import_list); it->rfg = rfg; it->monitor_exterior_orphans = skiplist_new(0, NULL, (void (*)(void *))prefix_free); /* * fill import route tables from RIBs * * Potential area for optimization. If this occurs when * tables are large (e.g., the operator adds a nve group * with a new RT list to a running system), it could take * a while. * */ for (afi = AFI_IP; afi < AFI_MAX; ++afi) { it->imported_vpn[afi] = agg_table_init(); it->imported_encap[afi] = agg_table_init(); rfapiBgpTableFilteredImport(bgp, it, afi, SAFI_MPLS_VPN); rfapiBgpTableFilteredImport(bgp, it, afi, SAFI_ENCAP); vnc_import_bgp_exterior_redist_enable_it(bgp, afi, it); } } it->refcount += 1; return it; } /* * skiplist element free function */ static void delete_rem_pfx_na_free(void *na) { uint32_t *pCounter = ((struct rfapi_nve_addr *)na)->info; *pCounter += 1; XFREE(MTYPE_RFAPI_NVE_ADDR, na); } /* * Common deleter for IP and MAC import tables */ static void rfapiDeleteRemotePrefixesIt( struct bgp *bgp, struct rfapi_import_table *it, struct prefix *un, struct prefix *vn, struct prefix *p, int delete_active, int delete_holddown, uint32_t *pARcount, uint32_t *pAHcount, uint32_t *pHRcount, uint32_t *pHHcount, struct skiplist *uniq_active_nves, struct skiplist *uniq_holddown_nves) { afi_t afi; #if DEBUG_L2_EXTRA { char buf_pfx[PREFIX_STRLEN]; if (p) { prefix2str(p, buf_pfx, sizeof(buf_pfx)); } else { buf_pfx[0] = '*'; buf_pfx[1] = 0; } vnc_zlog_debug_verbose( "%s: entry, p=%s, delete_active=%d, delete_holddown=%d", __func__, buf_pfx, delete_active, delete_holddown); } #endif for (afi = AFI_IP; afi < AFI_MAX; ++afi) { struct agg_table *rt; struct agg_node *rn; if (p && (family2afi(p->family) != afi)) { continue; } rt = it->imported_vpn[afi]; if (!rt) continue; vnc_zlog_debug_verbose("%s: scanning rt for afi=%d", __func__, afi); for (rn = agg_route_top(rt); rn; rn = agg_route_next(rn)) { struct bgp_path_info *bpi; struct bgp_path_info *next; if (p && VNC_DEBUG(IMPORT_DEL_REMOTE)) { char p1line[PREFIX_STRLEN]; char p2line[PREFIX_STRLEN]; prefix2str(p, p1line, sizeof(p1line)); prefix2str(&rn->p, p2line, sizeof(p2line)); vnc_zlog_debug_any("%s: want %s, have %s", __func__, p1line, p2line); } if (p && prefix_cmp(p, &rn->p)) continue; { char buf_pfx[PREFIX_STRLEN]; prefix2str(&rn->p, buf_pfx, sizeof(buf_pfx)); vnc_zlog_debug_verbose("%s: rn pfx=%s", __func__, buf_pfx); } /* TBD is this valid for afi == AFI_L2VPN? */ RFAPI_CHECK_REFCOUNT(rn, SAFI_MPLS_VPN, 1); for (bpi = rn->info; bpi; bpi = next) { next = bpi->next; struct prefix qpt; struct prefix qct; int qpt_valid = 0; int qct_valid = 0; int is_active = 0; vnc_zlog_debug_verbose("%s: examining bpi %p", __func__, bpi); if (bpi->attr) { if (!rfapiGetNexthop(bpi->attr, &qpt)) qpt_valid = 1; } if (vn) { if (!qpt_valid || !prefix_match(vn, &qpt)) { #if DEBUG_L2_EXTRA vnc_zlog_debug_verbose( "%s: continue at vn && !qpt_valid || !prefix_match(vn, &qpt)", __func__); #endif continue; } } if (!rfapiGetUnAddrOfVpnBi(bpi, &qct)) qct_valid = 1; if (un) { if (!qct_valid || !prefix_match(un, &qct)) { #if DEBUG_L2_EXTRA vnc_zlog_debug_verbose( "%s: continue at un && !qct_valid || !prefix_match(un, &qct)", __func__); #endif continue; } } /* * Blow bpi away */ /* * If this route is waiting to be deleted * because of * a previous withdraw, we must cancel its * timer. */ if (CHECK_FLAG(bpi->flags, BGP_PATH_REMOVED)) { if (!delete_holddown) continue; if (bpi->extra->vnc.import.timer) { struct thread *t = (struct thread *)bpi ->extra->vnc .import.timer; struct rfapi_withdraw *wcb = t->arg; wcb->import_table ->holddown_count[afi] -= 1; RFAPI_UPDATE_ITABLE_COUNT( bpi, wcb->import_table, afi, 1); XFREE(MTYPE_RFAPI_WITHDRAW, wcb); thread_cancel(t); } } else { if (!delete_active) continue; is_active = 1; } vnc_zlog_debug_verbose( "%s: deleting bpi %p (qct_valid=%d, qpt_valid=%d, delete_holddown=%d, delete_active=%d)", __func__, bpi, qct_valid, qpt_valid, delete_holddown, delete_active); /* * add nve to list */ if (qct_valid && qpt_valid) { struct rfapi_nve_addr na; struct rfapi_nve_addr *nap; memset(&na, 0, sizeof(na)); assert(!rfapiQprefix2Raddr(&qct, &na.un)); assert(!rfapiQprefix2Raddr(&qpt, &na.vn)); if (skiplist_search( (is_active ? uniq_active_nves : uniq_holddown_nves), &na, (void **)&nap)) { char line[BUFSIZ]; nap = XCALLOC( MTYPE_RFAPI_NVE_ADDR, sizeof(struct rfapi_nve_addr)); assert(nap); *nap = na; nap->info = is_active ? pAHcount : pHHcount; skiplist_insert( (is_active ? uniq_active_nves : uniq_holddown_nves), nap, nap); rfapiNveAddr2Str(nap, line, BUFSIZ); } } vnc_direct_bgp_rh_del_route(bgp, afi, &rn->p, bpi->peer); RFAPI_UPDATE_ITABLE_COUNT(bpi, it, afi, -1); it->holddown_count[afi] += 1; rfapiExpireVpnNow(it, rn, bpi, 1); vnc_zlog_debug_verbose( "%s: incrementing count (is_active=%d)", __func__, is_active); if (is_active) ++*pARcount; else ++*pHRcount; } } } } /* * For use by the "clear vnc prefixes" command */ /*------------------------------------------ * rfapiDeleteRemotePrefixes * * UI helper: For use by the "clear vnc prefixes" command * * input: * un if set, tunnel must match this prefix * vn if set, nexthop prefix must match this prefix * p if set, prefix must match this prefix * it if set, only look in this import table * * output * pARcount number of active routes deleted * pAHcount number of active nves deleted * pHRcount number of holddown routes deleted * pHHcount number of holddown nves deleted * * return value: * void --------------------------------------------*/ void rfapiDeleteRemotePrefixes(struct prefix *un, struct prefix *vn, struct prefix *p, struct rfapi_import_table *arg_it, int delete_active, int delete_holddown, uint32_t *pARcount, uint32_t *pAHcount, uint32_t *pHRcount, uint32_t *pHHcount) { struct bgp *bgp; struct rfapi *h; struct rfapi_import_table *it; uint32_t deleted_holddown_route_count = 0; uint32_t deleted_active_route_count = 0; uint32_t deleted_holddown_nve_count = 0; uint32_t deleted_active_nve_count = 0; struct skiplist *uniq_holddown_nves; struct skiplist *uniq_active_nves; VNC_ITRCCK; bgp = bgp_get_default(); /* assume 1 instance for now */ /* If no bgp instantiated yet, no vnc prefixes exist */ if (!bgp) return; h = bgp->rfapi; assert(h); uniq_holddown_nves = skiplist_new(0, rfapi_nve_addr_cmp, delete_rem_pfx_na_free); uniq_active_nves = skiplist_new(0, rfapi_nve_addr_cmp, delete_rem_pfx_na_free); /* * Iterate over all import tables; do a filtered import * for the afi/safi combination */ if (arg_it) it = arg_it; else it = h->imports; for (; it;) { vnc_zlog_debug_verbose( "%s: calling rfapiDeleteRemotePrefixesIt() on (IP) import %p", __func__, it); rfapiDeleteRemotePrefixesIt( bgp, it, un, vn, p, delete_active, delete_holddown, &deleted_active_route_count, &deleted_active_nve_count, &deleted_holddown_route_count, &deleted_holddown_nve_count, uniq_active_nves, uniq_holddown_nves); if (arg_it) it = NULL; else it = it->next; } /* * Now iterate over L2 import tables */ if (h->import_mac && !(p && (p->family != AF_ETHERNET))) { void *cursor = NULL; int rc; for (cursor = NULL, rc = skiplist_next(h->import_mac, NULL, (void **)&it, &cursor); !rc; rc = skiplist_next(h->import_mac, NULL, (void **)&it, &cursor)) { vnc_zlog_debug_verbose( "%s: calling rfapiDeleteRemotePrefixesIt() on import_mac %p", __func__, it); rfapiDeleteRemotePrefixesIt( bgp, it, un, vn, p, delete_active, delete_holddown, &deleted_active_route_count, &deleted_active_nve_count, &deleted_holddown_route_count, &deleted_holddown_nve_count, uniq_active_nves, uniq_holddown_nves); } } /* * our custom element freeing function above counts as it deletes */ skiplist_free(uniq_holddown_nves); skiplist_free(uniq_active_nves); if (pARcount) *pARcount = deleted_active_route_count; if (pAHcount) *pAHcount = deleted_active_nve_count; if (pHRcount) *pHRcount = deleted_holddown_route_count; if (pHHcount) *pHHcount = deleted_holddown_nve_count; VNC_ITRCCK; } /*------------------------------------------ * rfapiCountRemoteRoutes * * UI helper: count VRF routes from BGP side * * input: * * output * pALRcount count of active local routes * pARRcount count of active remote routes * pHRcount count of holddown routes * pIRcount count of direct imported routes * * return value: * void --------------------------------------------*/ void rfapiCountAllItRoutes(int *pALRcount, /* active local routes */ int *pARRcount, /* active remote routes */ int *pHRcount, /* holddown routes */ int *pIRcount) /* imported routes */ { struct bgp *bgp; struct rfapi *h; struct rfapi_import_table *it; afi_t afi; int total_active_local = 0; int total_active_remote = 0; int total_holddown = 0; int total_imported = 0; bgp = bgp_get_default(); /* assume 1 instance for now */ assert(bgp); h = bgp->rfapi; assert(h); /* * Iterate over all import tables; do a filtered import * for the afi/safi combination */ for (it = h->imports; it; it = it->next) { for (afi = AFI_IP; afi < AFI_MAX; ++afi) { total_active_local += it->local_count[afi]; total_active_remote += it->remote_count[afi]; total_holddown += it->holddown_count[afi]; total_imported += it->imported_count[afi]; } } void *cursor; int rc; if (h->import_mac) { for (cursor = NULL, rc = skiplist_next(h->import_mac, NULL, (void **)&it, &cursor); !rc; rc = skiplist_next(h->import_mac, NULL, (void **)&it, &cursor)) { total_active_local += it->local_count[AFI_L2VPN]; total_active_remote += it->remote_count[AFI_L2VPN]; total_holddown += it->holddown_count[AFI_L2VPN]; total_imported += it->imported_count[AFI_L2VPN]; } } if (pALRcount) { *pALRcount = total_active_local; } if (pARRcount) { *pARRcount = total_active_remote; } if (pHRcount) { *pHRcount = total_holddown; } if (pIRcount) { *pIRcount = total_imported; } } /*------------------------------------------ * rfapiGetHolddownFromLifetime * * calculate holddown value based on lifetime * * input: * lifetime lifetime * * return value: * Holddown value based on lifetime, holddown_factor, * and RFAPI_LIFETIME_INFINITE_WITHDRAW_DELAY * --------------------------------------------*/ /* hold down time maxes out at RFAPI_LIFETIME_INFINITE_WITHDRAW_DELAY */ uint32_t rfapiGetHolddownFromLifetime(uint32_t lifetime) { uint32_t factor; struct bgp *bgp; bgp = bgp_get_default(); if (bgp && bgp->rfapi_cfg) factor = bgp->rfapi_cfg->rfp_cfg.holddown_factor; else factor = RFAPI_RFP_CFG_DEFAULT_HOLDDOWN_FACTOR; if (factor < 100 || lifetime < RFAPI_LIFETIME_INFINITE_WITHDRAW_DELAY) lifetime = lifetime * factor / 100; if (lifetime < RFAPI_LIFETIME_INFINITE_WITHDRAW_DELAY) return lifetime; else return RFAPI_LIFETIME_INFINITE_WITHDRAW_DELAY; } frr-7.2.1/bgpd/rfapi/rfapi_import.h0000644000000000000000000002134113610377563014076 00000000000000/* * * Copyright 2009-2016, LabN Consulting, L.L.C. * * * This program is free software; you can redistribute it and/or * modify it under the terms of the GNU General Public License * as published by the Free Software Foundation; either version 2 * of the License, or (at your option) any later version. * * 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; see the file COPYING; if not, write to the Free Software * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA */ /* * File: rfapi_import.h * Purpose: Handle import of routes from BGP to RFAPI */ #ifndef QUAGGA_HGP_RFAPI_IMPORT_H #define QUAGGA_HGP_RFAPI_IMPORT_H #include "lib/thread.h" /* * These are per-rt-import-list * * routes are not segregated by RD - the RD is stored in bgp_path_info_extra * and is needed to determine if two prefixes are the same. */ struct rfapi_import_table { struct rfapi_import_table *next; struct rfapi_nve_group_cfg *rfg; struct ecommunity *rt_import_list; /* copied from nve grp */ int refcount; /* nve grps and nves */ uint32_t l2_logical_net_id; /* L2 only: EVPN Eth Seg Id */ struct agg_table *imported_vpn[AFI_MAX]; struct rfapi_monitor_vpn *vpn0_queries[AFI_MAX]; struct rfapi_monitor_eth *eth0_queries; struct agg_table *imported_encap[AFI_MAX]; struct skiplist *monitor_exterior_orphans; int local_count[AFI_MAX]; int remote_count[AFI_MAX]; int holddown_count[AFI_MAX]; int imported_count[AFI_MAX]; }; #define RFAPI_LOCAL_BI(bpi) \ (((bpi)->type == ZEBRA_ROUTE_BGP) && ((bpi)->sub_type == BGP_ROUTE_RFP)) #define RFAPI_DIRECT_IMPORT_BI(bpi) \ (((bpi)->type == ZEBRA_ROUTE_BGP_DIRECT) \ || ((bpi)->type == ZEBRA_ROUTE_BGP_DIRECT_EXT)) #define RFAPI_UPDATE_ITABLE_COUNT(bpi, itable, afi, cnt) \ if (RFAPI_LOCAL_BI(bpi)) { \ (itable)->local_count[(afi)] += (cnt); \ } else { \ if (RFAPI_DIRECT_IMPORT_BI(bpi)) \ (itable)->imported_count[(afi)] += (cnt); \ else \ (itable)->remote_count[(afi)] += (cnt); \ } extern uint8_t rfapiRfpCost(struct attr *attr); extern void rfapiDebugBacktrace(void); extern void rfapiCheckRouteCount(void); /* * Print BPI in an Import Table */ extern void rfapiPrintBi(void *stream, struct bgp_path_info *bpi); extern void rfapiShowImportTable(void *stream, const char *label, struct agg_table *rt, int isvpn); extern struct rfapi_import_table * rfapiImportTableRefAdd(struct bgp *bgp, struct ecommunity *rt_import_list, struct rfapi_nve_group_cfg *rfg); extern void rfapiImportTableRefDelByIt(struct bgp *bgp, struct rfapi_import_table *it_target); /* * Construct an rfapi nexthop list based on the routes attached to * the specified node. * * If there are any routes that do NOT have BGP_PATH_REMOVED set, * return those only. If there are ONLY routes with BGP_INFO_REMOVED, * then return those, and also include all the non-removed routes from the * next less-specific node (i.e., this node's parent) at the end. */ extern struct rfapi_next_hop_entry *rfapiRouteNode2NextHopList( struct agg_node *rn, uint32_t lifetime, /* put into nexthop entries */ struct rfapi_ip_addr *exclude_vnaddr, /* omit routes to same NVE */ struct agg_table *rfd_rib_table, /* preload this NVE rib table */ struct prefix *pfx_target_original); /* query target */ extern struct rfapi_next_hop_entry *rfapiRouteTable2NextHopList( struct agg_table *rt, uint32_t lifetime, /* put into nexthop entries */ struct rfapi_ip_addr *exclude_vnaddr, /* omit routes to same NVE */ struct agg_table *rfd_rib_table, /* preload this NVE rib table */ struct prefix *pfx_target_original); /* query target */ extern struct rfapi_next_hop_entry *rfapiEthRouteTable2NextHopList( uint32_t logical_net_id, struct rfapi_ip_prefix *rprefix, uint32_t lifetime, /* put into nexthop entries */ struct rfapi_ip_addr *exclude_vnaddr, /* omit routes to same NVE */ struct agg_table *rib_route_table, /* preload NVE rib node */ struct prefix *pfx_target_original); /* query target */ extern int rfapiEcommunitiesIntersect(struct ecommunity *e1, struct ecommunity *e2); extern void rfapiCheckRefcount(struct agg_node *rn, safi_t safi, int lockoffset); extern int rfapiHasNonRemovedRoutes(struct agg_node *rn); extern int rfapiProcessDeferredClose(struct thread *t); extern int rfapiGetUnAddrOfVpnBi(struct bgp_path_info *bpi, struct prefix *p); extern void rfapiNexthop2Prefix(struct attr *attr, struct prefix *p); extern void rfapiUnicastNexthop2Prefix(afi_t afi, struct attr *attr, struct prefix *p); /* Filtered Import Function actions */ #define FIF_ACTION_UPDATE 0 #define FIF_ACTION_WITHDRAW 1 #define FIF_ACTION_KILL 2 extern void rfapiBgpInfoFilteredImportVPN( struct rfapi_import_table *import_table, int action, struct peer *peer, void *rfd, /* set for looped back routes */ struct prefix *p, struct prefix *aux_prefix, /* AFI_ETHER: optional IP */ afi_t afi, struct prefix_rd *prd, struct attr *attr, /* part of bgp_path_info */ uint8_t type, /* part of bgp_path_info */ uint8_t sub_type, /* part of bgp_path_info */ uint32_t *label); /* part of bgp_path_info */ extern struct rfapi_next_hop_entry *rfapiEthRouteNode2NextHopList( struct agg_node *rn, struct rfapi_ip_prefix *rprefix, uint32_t lifetime, /* put into nexthop entries */ struct rfapi_ip_addr *exclude_vnaddr, /* omit routes to same NVE */ struct agg_table *rib_route_table, /* preload NVE rib table */ struct prefix *pfx_target_original); /* query target */ extern struct rfapi_import_table *rfapiMacImportTableGetNoAlloc(struct bgp *bgp, uint32_t lni); extern struct rfapi_import_table *rfapiMacImportTableGet(struct bgp *bgp, uint32_t lni); extern int rfapiGetL2o(struct attr *attr, struct rfapi_l2address_option *l2o); extern int rfapiEcommunityGetLNI(struct ecommunity *ecom, uint32_t *lni); extern int rfapiEcommunityGetEthernetTag(struct ecommunity *ecom, uint16_t *tag_id); /* enable for debugging; disable for performance */ #if 0 #define RFAPI_CHECK_REFCOUNT(rn, safi, lo) rfapiCheckRefcount((rn),(safi),(lo)) #else #define RFAPI_CHECK_REFCOUNT(rn, safi, lo) {} #endif /*------------------------------------------ * rfapiDeleteRemotePrefixes * * UI helper: For use by the "clear vnc prefixes" command * * input: * un if set, tunnel must match this prefix * vn if set, nexthop prefix must match this prefix * p if set, prefix must match this prefix * it if set, only look in this import table * * output * pARcount number of active routes deleted * pAHcount number of active nves deleted * pHRcount number of holddown routes deleted * pHHcount number of holddown nves deleted * * return value: * void --------------------------------------------*/ extern void rfapiDeleteRemotePrefixes(struct prefix *un, struct prefix *vn, struct prefix *p, struct rfapi_import_table *it, int delete_active, int delete_holddown, uint32_t *pARcount, /* active routes */ uint32_t *pAHcount, /* active nves */ uint32_t *pHRcount, /* holddown routes */ uint32_t *pHHcount); /* holddown nves */ /*------------------------------------------ * rfapiCountAllItRoutes * * UI helper: count VRF routes from BGP side * * input: * * output * pARcount count of active routes * pHRcount count of holddown routes * pIRcount count of holddown routes * * return value: * void --------------------------------------------*/ extern void rfapiCountAllItRoutes(int *pALRcount, /* active local routes */ int *pARRcount, /* active remote routes */ int *pHRcount, /* holddown routes */ int *pIRcount); /* direct imported routes */ /*------------------------------------------ * rfapiGetHolddownFromLifetime * * calculate holddown value based on lifetime * * input: * lifetime lifetime * * return value: * Holddown value based on lifetime, holddown_factor, * and RFAPI_LIFETIME_INFINITE_WITHDRAW_DELAY * --------------------------------------------*/ extern uint32_t rfapiGetHolddownFromLifetime(uint32_t lifetime); #endif /* QUAGGA_HGP_RFAPI_IMPORT_H */ frr-7.2.1/bgpd/rfapi/rfapi_monitor.c0000644000000000000000000010620513610377563014251 00000000000000/* * * Copyright 2009-2016, LabN Consulting, L.L.C. * * * This program is free software; you can redistribute it and/or * modify it under the terms of the GNU General Public License * as published by the Free Software Foundation; either version 2 * of the License, or (at your option) any later version. * * 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; see the file COPYING; if not, write to the Free Software * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA */ /* * File: rfapi_monitor.c */ /* TBD remove unneeded includes */ #include "lib/zebra.h" #include "lib/prefix.h" #include "lib/agg_table.h" #include "lib/vty.h" #include "lib/memory.h" #include "lib/log.h" #include "lib/table.h" #include "lib/skiplist.h" #include "bgpd/bgpd.h" #include "bgpd/rfapi/bgp_rfapi_cfg.h" #include "bgpd/rfapi/rfapi.h" #include "bgpd/rfapi/rfapi_backend.h" #include "bgpd/rfapi/rfapi.h" #include "bgpd/rfapi/rfapi_import.h" #include "bgpd/rfapi/vnc_import_bgp.h" #include "bgpd/rfapi/rfapi_private.h" #include "bgpd/rfapi/rfapi_monitor.h" #include "bgpd/rfapi/rfapi_vty.h" #include "bgpd/rfapi/rfapi_rib.h" #include "bgpd/rfapi/vnc_debug.h" #define DEBUG_L2_EXTRA 0 #define DEBUG_DUP_CHECK 0 #define DEBUG_ETH_SL 0 static void rfapiMonitorTimerRestart(struct rfapi_monitor_vpn *m); static void rfapiMonitorEthTimerRestart(struct rfapi_monitor_eth *m); /* * Forward declarations */ static void rfapiMonitorEthDetachImport(struct bgp *bgp, struct rfapi_monitor_eth *mon); #if DEBUG_ETH_SL /* * Debug function, special case */ void rfapiMonitorEthSlCheck(struct agg_node *rn, const char *tag1, const char *tag2) { struct agg_node *rn_saved = NULL; static struct skiplist *sl_saved = NULL; struct skiplist *sl; if (!rn) return; if (rn_saved && (rn != rn_saved)) return; if (!rn_saved) rn_saved = rn; sl = RFAPI_MONITOR_ETH(rn); if (sl || sl_saved) { vnc_zlog_debug_verbose( "%s[%s%s]: rn=%p, rn->lock=%d, old sl=%p, new sl=%p", __func__, (tag1 ? tag1 : ""), (tag2 ? tag2 : ""), rn, rn->lock, sl_saved, sl); sl_saved = sl; } } #endif /* * Debugging function that aborts when it finds monitors whose * "next" pointer * references themselves */ void rfapiMonitorLoopCheck(struct rfapi_monitor_vpn *mchain) { struct rfapi_monitor_vpn *m; for (m = mchain; m; m = m->next) assert(m != m->next); } #if DEBUG_DUP_CHECK /* * Debugging code: see if a monitor is mentioned more than once * in a HD's monitor list */ void rfapiMonitorDupCheck(struct bgp *bgp) { struct listnode *hnode; struct rfapi_descriptor *rfd; for (ALL_LIST_ELEMENTS_RO(&bgp->rfapi->descriptors, hnode, rfd)) { struct agg_node *mrn; if (!rfd->mon) continue; for (mrn = agg_route_top(rfd->mon); mrn; mrn = agg_route_next(mrn)) { struct rfapi_monitor_vpn *m; for (m = (struct rfapi_monitor_vpn *)(mrn->info); m; m = m->next) m->dcount = 0; } } for (ALL_LIST_ELEMENTS_RO(&bgp->rfapi->descriptors, hnode, rfd)) { struct agg_node *mrn; if (!rfd->mon) continue; for (mrn = agg_route_top(rfd->mon); mrn; mrn = agg_route_next(mrn)) { struct rfapi_monitor_vpn *m; for (m = (struct rfapi_monitor_vpn *)(mrn->info); m; m = m->next) assert(++m->dcount == 1); } } } #endif /* debug */ void rfapiMonitorCleanCheck(struct bgp *bgp) { struct listnode *hnode; struct rfapi_descriptor *rfd; for (ALL_LIST_ELEMENTS_RO(&bgp->rfapi->descriptors, hnode, rfd)) { assert(!rfd->import_table->vpn0_queries[AFI_IP]); assert(!rfd->import_table->vpn0_queries[AFI_IP6]); struct agg_node *rn; for (rn = agg_route_top( rfd->import_table->imported_vpn[AFI_IP]); rn; rn = agg_route_next(rn)) { assert(!RFAPI_MONITOR_VPN(rn)); } for (rn = agg_route_top( rfd->import_table->imported_vpn[AFI_IP6]); rn; rn = agg_route_next(rn)) { assert(!RFAPI_MONITOR_VPN(rn)); } } } /* debug */ void rfapiMonitorCheckAttachAllowed(void) { struct bgp *bgp = bgp_get_default(); assert(!(bgp->rfapi_cfg->flags & BGP_VNC_CONFIG_CALLBACK_DISABLE)); } void rfapiMonitorExtraFlush(safi_t safi, struct agg_node *rn) { struct rfapi_it_extra *hie; struct rfapi_monitor_vpn *v; struct rfapi_monitor_vpn *v_next; struct rfapi_monitor_encap *e = NULL; struct rfapi_monitor_encap *e_next = NULL; if (!rn) return; if (!rn->aggregate) return; hie = (struct rfapi_it_extra *)(rn->aggregate); switch (safi) { case SAFI_ENCAP: for (e = hie->u.encap.e; e; e = e_next) { e_next = e->next; e->next = NULL; XFREE(MTYPE_RFAPI_MONITOR_ENCAP, e); agg_unlock_node(rn); } hie->u.encap.e = NULL; break; case SAFI_MPLS_VPN: for (v = hie->u.vpn.v; v; v = v_next) { v_next = v->next; v->next = NULL; XFREE(MTYPE_RFAPI_MONITOR, e); agg_unlock_node(rn); } hie->u.vpn.v = NULL; if (hie->u.vpn.e.source) { while (!skiplist_delete_first(hie->u.vpn.e.source)) { agg_unlock_node(rn); } skiplist_free(hie->u.vpn.e.source); hie->u.vpn.e.source = NULL; agg_unlock_node(rn); } if (hie->u.vpn.idx_rd) { /* looping through bpi->extra->vnc.import.rd is tbd */ while (!skiplist_delete_first(hie->u.vpn.idx_rd)) { agg_unlock_node(rn); } skiplist_free(hie->u.vpn.idx_rd); hie->u.vpn.idx_rd = NULL; agg_unlock_node(rn); } if (hie->u.vpn.mon_eth) { while (!skiplist_delete_first(hie->u.vpn.mon_eth)) { agg_unlock_node(rn); } skiplist_free(hie->u.vpn.mon_eth); hie->u.vpn.mon_eth = NULL; agg_unlock_node(rn); } break; default: assert(0); } XFREE(MTYPE_RFAPI_IT_EXTRA, hie); rn->aggregate = NULL; agg_unlock_node(rn); } /* * If the child lists are empty, release the rfapi_it_extra struct */ void rfapiMonitorExtraPrune(safi_t safi, struct agg_node *rn) { struct rfapi_it_extra *hie; if (!rn) return; if (!rn->aggregate) return; hie = (struct rfapi_it_extra *)(rn->aggregate); switch (safi) { case SAFI_ENCAP: if (hie->u.encap.e) return; break; case SAFI_MPLS_VPN: if (hie->u.vpn.v) return; if (hie->u.vpn.mon_eth) { if (skiplist_count(hie->u.vpn.mon_eth)) return; skiplist_free(hie->u.vpn.mon_eth); hie->u.vpn.mon_eth = NULL; agg_unlock_node(rn); /* uncount skiplist */ } if (hie->u.vpn.e.source) { if (skiplist_count(hie->u.vpn.e.source)) return; skiplist_free(hie->u.vpn.e.source); hie->u.vpn.e.source = NULL; agg_unlock_node(rn); } if (hie->u.vpn.idx_rd) { if (skiplist_count(hie->u.vpn.idx_rd)) return; skiplist_free(hie->u.vpn.idx_rd); hie->u.vpn.idx_rd = NULL; agg_unlock_node(rn); } if (hie->u.vpn.mon_eth) { if (skiplist_count(hie->u.vpn.mon_eth)) return; skiplist_free(hie->u.vpn.mon_eth); hie->u.vpn.mon_eth = NULL; agg_unlock_node(rn); } break; default: assert(0); } XFREE(MTYPE_RFAPI_IT_EXTRA, hie); rn->aggregate = NULL; agg_unlock_node(rn); } /* * returns locked node */ struct agg_node *rfapiMonitorGetAttachNode(struct rfapi_descriptor *rfd, struct prefix *p) { afi_t afi; struct agg_node *rn; if (RFAPI_0_PREFIX(p)) { assert(1); } afi = family2afi(p->family); assert(afi); /* * It's possible that even though there is a route at this node, * there are no routes with valid UN addresses (i.e,. with no * valid tunnel routes). Check for that and walk back up the * tree if necessary. * * When the outer loop completes, the matched node, if any, is * locked (i.e., its reference count has been incremented) to * account for the VPN monitor we are about to attach. * * if a monitor is moved to another node, there must be * corresponding unlock/locks */ for (rn = agg_node_match(rfd->import_table->imported_vpn[afi], p); rn;) { struct bgp_path_info *bpi; struct prefix pfx_dummy; /* TBD update this code to use new valid_interior_count */ for (bpi = rn->info; bpi; bpi = bpi->next) { /* * If there is a cached ENCAP UN address, it's a usable * VPN route */ if (bpi->extra && bpi->extra->vnc.import.un_family) { break; } /* * Or if there is a valid Encap Attribute tunnel subtlv * address, * it's a usable VPN route. */ if (!rfapiGetVncTunnelUnAddr(bpi->attr, &pfx_dummy)) { break; } } if (bpi) break; agg_unlock_node(rn); if ((rn = agg_node_parent(rn))) { agg_lock_node(rn); } } if (!rn) { struct prefix pfx_default; memset(&pfx_default, 0, sizeof(pfx_default)); pfx_default.family = p->family; /* creates default node if none exists, and increments ref count */ rn = agg_node_get(rfd->import_table->imported_vpn[afi], &pfx_default); } return rn; } /* * If this function happens to attach the monitor to a radix tree * node (as opposed to the 0-prefix list), the node pointer is * returned (for the benefit of caller which might like to use it * to generate an immediate query response). */ static struct agg_node *rfapiMonitorAttachImport(struct rfapi_descriptor *rfd, struct rfapi_monitor_vpn *m) { struct agg_node *rn; rfapiMonitorCheckAttachAllowed(); if (RFAPI_0_PREFIX(&m->p)) { /* * Add new monitor entry to vpn0 list */ afi_t afi; afi = family2afi(m->p.family); assert(afi); m->next = rfd->import_table->vpn0_queries[afi]; rfd->import_table->vpn0_queries[afi] = m; vnc_zlog_debug_verbose("%s: attached monitor %p to vpn0 list", __func__, m); return NULL; } /* * Attach new monitor entry to import table node */ rn = rfapiMonitorGetAttachNode(rfd, &m->p); /* returns locked rn */ m->node = rn; m->next = RFAPI_MONITOR_VPN(rn); RFAPI_MONITOR_VPN_W_ALLOC(rn) = m; RFAPI_CHECK_REFCOUNT(rn, SAFI_MPLS_VPN, 0); vnc_zlog_debug_verbose("%s: attached monitor %p to rn %p", __func__, m, rn); return rn; } /* * reattach monitors for this HD to import table */ void rfapiMonitorAttachImportHd(struct rfapi_descriptor *rfd) { struct agg_node *mrn; if (!rfd->mon) { /* * No monitors for this HD */ return; } for (mrn = agg_route_top(rfd->mon); mrn; mrn = agg_route_next(mrn)) { if (!mrn->info) continue; (void)rfapiMonitorAttachImport( rfd, (struct rfapi_monitor_vpn *)(mrn->info)); } } /* * Adds a monitor for a query to the NVE descriptor's list * and, if callbacks are enabled, attaches it to the import table. * * If we happened to locate the import table radix tree attachment * point, return it so the caller can use it to generate a query * response without repeating the lookup. Note that when callbacks * are disabled, this function will not perform a lookup, and the * caller will have to do its own lookup. */ struct agg_node *rfapiMonitorAdd(struct bgp *bgp, struct rfapi_descriptor *rfd, struct prefix *p) { struct rfapi_monitor_vpn *m; struct agg_node *rn; /* * Initialize nve's monitor list if needed * NB use the same radix tree for IPv4 and IPv6 targets. * The prefix will always have full-length mask (/32, /128) * or be 0/0 so they won't get mixed up. */ if (!rfd->mon) { rfd->mon = agg_table_init(); } rn = agg_node_get(rfd->mon, p); if (rn->info) { /* * received this query before, no further action needed */ rfapiMonitorTimerRestart((struct rfapi_monitor_vpn *)rn->info); agg_unlock_node(rn); return NULL; } /* * New query for this nve, record it in the HD */ rn->info = XCALLOC(MTYPE_RFAPI_MONITOR, sizeof(struct rfapi_monitor_vpn)); m = (struct rfapi_monitor_vpn *)(rn->info); m->rfd = rfd; prefix_copy(&m->p, p); ++rfd->monitor_count; ++bgp->rfapi->monitor_count; rfapiMonitorTimerRestart(m); if (bgp->rfapi_cfg->flags & BGP_VNC_CONFIG_CALLBACK_DISABLE) { /* * callbacks turned off, so don't attach monitor to import table */ return NULL; } /* * attach to import table */ return rfapiMonitorAttachImport(rfd, m); } /* * returns monitor pointer if found, NULL if not */ static struct rfapi_monitor_vpn * rfapiMonitorDetachImport(struct rfapi_monitor_vpn *m) { struct rfapi_monitor_vpn *prev; struct rfapi_monitor_vpn *this = NULL; if (RFAPI_0_PREFIX(&m->p)) { afi_t afi; /* * 0-prefix monitors are stored in a special list and not * in the import VPN tree */ afi = family2afi(m->p.family); assert(afi); if (m->rfd->import_table) { for (prev = NULL, this = m->rfd->import_table->vpn0_queries[afi]; this; prev = this, this = this->next) { if (this == m) break; } if (this) { if (!prev) { m->rfd->import_table ->vpn0_queries[afi] = this->next; } else { prev->next = this->next; } } } } else { if (m->node) { for (prev = NULL, this = RFAPI_MONITOR_VPN(m->node); this; prev = this, this = this->next) { if (this == m) break; } if (this) { if (prev) { prev->next = this->next; } else { RFAPI_MONITOR_VPN_W_ALLOC(m->node) = this->next; } RFAPI_CHECK_REFCOUNT(m->node, SAFI_MPLS_VPN, 1); agg_unlock_node(m->node); } m->node = NULL; } } return this; } void rfapiMonitorDetachImportHd(struct rfapi_descriptor *rfd) { struct agg_node *rn; if (!rfd->mon) return; for (rn = agg_route_top(rfd->mon); rn; rn = agg_route_next(rn)) { if (rn->info) { rfapiMonitorDetachImport( (struct rfapi_monitor_vpn *)(rn->info)); } } } void rfapiMonitorDel(struct bgp *bgp, struct rfapi_descriptor *rfd, struct prefix *p) { struct agg_node *rn; struct rfapi_monitor_vpn *m; assert(rfd->mon); rn = agg_node_get(rfd->mon, p); /* locks node */ m = rn->info; assert(m); /* * remove from import table */ if (!(bgp->rfapi_cfg->flags & BGP_VNC_CONFIG_CALLBACK_DISABLE)) { rfapiMonitorDetachImport(m); } if (m->timer) { thread_cancel(m->timer); m->timer = NULL; } /* * remove from rfd list */ XFREE(MTYPE_RFAPI_MONITOR, m); rn->info = NULL; agg_unlock_node(rn); /* undo original lock when created */ agg_unlock_node(rn); /* undo lock in agg_node_get */ --rfd->monitor_count; --bgp->rfapi->monitor_count; } /* * returns count of monitors deleted */ int rfapiMonitorDelHd(struct rfapi_descriptor *rfd) { struct agg_node *rn; struct bgp *bgp; int count = 0; vnc_zlog_debug_verbose("%s: entry rfd=%p", __func__, rfd); bgp = bgp_get_default(); if (rfd->mon) { for (rn = agg_route_top(rfd->mon); rn; rn = agg_route_next(rn)) { struct rfapi_monitor_vpn *m; if ((m = rn->info)) { if (!(bgp->rfapi_cfg->flags & BGP_VNC_CONFIG_CALLBACK_DISABLE)) { rfapiMonitorDetachImport(m); } if (m->timer) { thread_cancel(m->timer); m->timer = NULL; } XFREE(MTYPE_RFAPI_MONITOR, m); rn->info = NULL; agg_unlock_node(rn); /* undo original lock when created */ ++count; --rfd->monitor_count; --bgp->rfapi->monitor_count; } } agg_table_finish(rfd->mon); rfd->mon = NULL; } if (rfd->mon_eth) { struct rfapi_monitor_eth *mon_eth; while (!skiplist_first(rfd->mon_eth, NULL, (void **)&mon_eth)) { int rc; if (!(bgp->rfapi_cfg->flags & BGP_VNC_CONFIG_CALLBACK_DISABLE)) { rfapiMonitorEthDetachImport(bgp, mon_eth); } else { #if DEBUG_L2_EXTRA vnc_zlog_debug_verbose( "%s: callbacks disabled, not attempting to detach mon_eth %p", __func__, mon_eth); #endif } if (mon_eth->timer) { thread_cancel(mon_eth->timer); mon_eth->timer = NULL; } /* * remove from rfd list */ rc = skiplist_delete(rfd->mon_eth, mon_eth, mon_eth); assert(!rc); vnc_zlog_debug_verbose("%s: freeing mon_eth %p", __func__, mon_eth); XFREE(MTYPE_RFAPI_MONITOR_ETH, mon_eth); ++count; --rfd->monitor_count; --bgp->rfapi->monitor_count; } skiplist_free(rfd->mon_eth); rfd->mon_eth = NULL; } return count; } void rfapiMonitorResponseRemovalOff(struct bgp *bgp) { if (bgp->rfapi_cfg->flags & BGP_VNC_CONFIG_RESPONSE_REMOVAL_DISABLE) { return; } bgp->rfapi_cfg->flags |= BGP_VNC_CONFIG_RESPONSE_REMOVAL_DISABLE; } void rfapiMonitorResponseRemovalOn(struct bgp *bgp) { if (!(bgp->rfapi_cfg->flags & BGP_VNC_CONFIG_RESPONSE_REMOVAL_DISABLE)) { return; } bgp->rfapi_cfg->flags &= ~BGP_VNC_CONFIG_RESPONSE_REMOVAL_DISABLE; } static int rfapiMonitorTimerExpire(struct thread *t) { struct rfapi_monitor_vpn *m = t->arg; /* forget reference to thread, it's gone */ m->timer = NULL; /* delete the monitor */ rfapiMonitorDel(bgp_get_default(), m->rfd, &m->p); return 0; } static void rfapiMonitorTimerRestart(struct rfapi_monitor_vpn *m) { if (m->timer) { unsigned long remain = thread_timer_remain_second(m->timer); /* unexpected case, but avoid wraparound problems below */ if (remain > m->rfd->response_lifetime) return; /* don't restart if we just restarted recently */ if (m->rfd->response_lifetime - remain < 2) return; thread_cancel(m->timer); m->timer = NULL; } { char buf[BUFSIZ]; vnc_zlog_debug_verbose( "%s: target %s life %u", __func__, rfapi_ntop(m->p.family, m->p.u.val, buf, BUFSIZ), m->rfd->response_lifetime); } m->timer = NULL; thread_add_timer(bm->master, rfapiMonitorTimerExpire, m, m->rfd->response_lifetime, &m->timer); } /* * called when an updated response is sent to the NVE. Per * ticket 255, restart timers for any monitors that could have * been responsible for the response, i.e., any monitors for * the exact prefix or a parent of it. */ void rfapiMonitorTimersRestart(struct rfapi_descriptor *rfd, struct prefix *p) { struct agg_node *rn; if (AF_ETHERNET == p->family) { struct rfapi_monitor_eth *mon_eth; int rc; void *cursor; /* * XXX match any LNI */ for (cursor = NULL, rc = skiplist_next(rfd->mon_eth, NULL, (void **)&mon_eth, &cursor); rc == 0; rc = skiplist_next(rfd->mon_eth, NULL, (void **)&mon_eth, &cursor)) { if (!memcmp(mon_eth->macaddr.octet, p->u.prefix_eth.octet, ETH_ALEN)) { rfapiMonitorEthTimerRestart(mon_eth); } } } else { for (rn = agg_route_top(rfd->mon); rn; rn = agg_route_next(rn)) { struct rfapi_monitor_vpn *m; if (!((m = rn->info))) continue; /* NB order of test is significant ! */ if (!m->node || prefix_match(&m->node->p, p)) { rfapiMonitorTimerRestart(m); } } } } /* * Find monitors at this node and all its parents. Call * rfapiRibUpdatePendingNode with this node and all corresponding NVEs. */ void rfapiMonitorItNodeChanged( struct rfapi_import_table *import_table, struct agg_node *it_node, struct rfapi_monitor_vpn *monitor_list) /* for base it node, NULL=all */ { struct skiplist *nves_seen; struct agg_node *rn = it_node; struct bgp *bgp = bgp_get_default(); afi_t afi = family2afi(rn->p.family); #if DEBUG_L2_EXTRA char buf_prefix[PREFIX_STRLEN]; #endif assert(bgp); assert(import_table); nves_seen = skiplist_new(0, NULL, NULL); #if DEBUG_L2_EXTRA prefix2str(&it_node->p, buf_prefix, sizeof(buf_prefix)); vnc_zlog_debug_verbose("%s: it=%p, it_node=%p, it_node->prefix=%s", __func__, import_table, it_node, buf_prefix); #endif if (AFI_L2VPN == afi) { struct rfapi_monitor_eth *m; struct skiplist *sl; void *cursor; int rc; if ((sl = RFAPI_MONITOR_ETH(rn))) { for (cursor = NULL, rc = skiplist_next(sl, NULL, (void **)&m, (void **)&cursor); !rc; rc = skiplist_next(sl, NULL, (void **)&m, (void **)&cursor)) { if (skiplist_search(nves_seen, m->rfd, NULL)) { /* * Haven't done this NVE yet. Add to * "seen" list. */ assert(!skiplist_insert(nves_seen, m->rfd, NULL)); /* * update its RIB */ rfapiRibUpdatePendingNode( bgp, m->rfd, import_table, it_node, m->rfd->response_lifetime); } } } } else { struct rfapi_monitor_vpn *m; if (monitor_list) { m = monitor_list; } else { m = RFAPI_MONITOR_VPN(rn); } do { /* * If we have reached the root node (parent==NULL) and * there * are no routes here (info==NULL), and the IT node that * changed was not the root node (it_node->parent != * NULL), * then any monitors at this node are here because they * had * no match at all. Therefore, do not send route updates * to them * because we haven't sent them an initial route. */ if (!agg_node_parent(rn) && !rn->info && it_node->parent) break; for (; m; m = m->next) { if (RFAPI_0_PREFIX(&m->p)) { /* shouldn't happen, but be safe */ continue; } if (skiplist_search(nves_seen, m->rfd, NULL)) { /* * Haven't done this NVE yet. Add to * "seen" list. */ assert(!skiplist_insert(nves_seen, m->rfd, NULL)); char buf_attach_pfx[PREFIX_STRLEN]; char buf_target_pfx[PREFIX_STRLEN]; prefix2str(&m->node->p, buf_attach_pfx, sizeof(buf_attach_pfx)); prefix2str(&m->p, buf_target_pfx, sizeof(buf_target_pfx)); vnc_zlog_debug_verbose( "%s: update rfd %p attached to pfx %s (targ=%s)", __func__, m->rfd, buf_attach_pfx, buf_target_pfx); /* * update its RIB */ rfapiRibUpdatePendingNode( bgp, m->rfd, import_table, it_node, m->rfd->response_lifetime); } } rn = agg_node_parent(rn); if (rn) m = RFAPI_MONITOR_VPN(rn); } while (rn); } /* * All-routes L2 monitors */ if (AFI_L2VPN == afi) { struct rfapi_monitor_eth *e; #if DEBUG_L2_EXTRA vnc_zlog_debug_verbose("%s: checking L2 all-routes monitors", __func__); #endif for (e = import_table->eth0_queries; e; e = e->next) { #if DEBUG_L2_EXTRA vnc_zlog_debug_verbose("%s: checking eth0 mon=%p", __func__, e); #endif if (skiplist_search(nves_seen, e->rfd, NULL)) { /* * Haven't done this NVE yet. Add to "seen" * list. */ assert(!skiplist_insert(nves_seen, e->rfd, NULL)); /* * update its RIB */ #if DEBUG_L2_EXTRA vnc_zlog_debug_verbose( "%s: found L2 all-routes monitor %p", __func__, e); #endif rfapiRibUpdatePendingNode( bgp, e->rfd, import_table, it_node, e->rfd->response_lifetime); } } } else { struct rfapi_monitor_vpn *m; /* * All-routes IPv4. IPv6 monitors */ for (m = import_table->vpn0_queries[afi]; m; m = m->next) { if (skiplist_search(nves_seen, m->rfd, NULL)) { /* * Haven't done this NVE yet. Add to "seen" * list. */ assert(!skiplist_insert(nves_seen, m->rfd, NULL)); /* * update its RIB */ rfapiRibUpdatePendingNode( bgp, m->rfd, import_table, it_node, m->rfd->response_lifetime); } } } skiplist_free(nves_seen); } /* * For the listed monitors, update new node and its subtree, but * omit old node and its subtree */ void rfapiMonitorMovedUp(struct rfapi_import_table *import_table, struct agg_node *old_node, struct agg_node *new_node, struct rfapi_monitor_vpn *monitor_list) { struct bgp *bgp = bgp_get_default(); struct rfapi_monitor_vpn *m; assert(new_node); assert(old_node); assert(new_node != old_node); /* * If new node is 0/0 and there is no route there, don't * generate an update because it will not contain any * routes including the target. */ if (!new_node->parent && !new_node->info) { vnc_zlog_debug_verbose( "%s: new monitor at 0/0 and no routes, no updates", __func__); return; } for (m = monitor_list; m; m = m->next) { rfapiRibUpdatePendingNode(bgp, m->rfd, import_table, new_node, m->rfd->response_lifetime); rfapiRibUpdatePendingNodeSubtree(bgp, m->rfd, import_table, new_node, old_node, m->rfd->response_lifetime); } } static int rfapiMonitorEthTimerExpire(struct thread *t) { struct rfapi_monitor_eth *m = t->arg; /* forget reference to thread, it's gone */ m->timer = NULL; /* delete the monitor */ rfapiMonitorEthDel(bgp_get_default(), m->rfd, &m->macaddr, m->logical_net_id); return 0; } static void rfapiMonitorEthTimerRestart(struct rfapi_monitor_eth *m) { if (m->timer) { unsigned long remain = thread_timer_remain_second(m->timer); /* unexpected case, but avoid wraparound problems below */ if (remain > m->rfd->response_lifetime) return; /* don't restart if we just restarted recently */ if (m->rfd->response_lifetime - remain < 2) return; thread_cancel(m->timer); m->timer = NULL; } { char buf[BUFSIZ]; vnc_zlog_debug_verbose( "%s: target %s life %u", __func__, rfapiEthAddr2Str(&m->macaddr, buf, BUFSIZ), m->rfd->response_lifetime); } m->timer = NULL; thread_add_timer(bm->master, rfapiMonitorEthTimerExpire, m, m->rfd->response_lifetime, &m->timer); } static int mon_eth_cmp(void *a, void *b) { struct rfapi_monitor_eth *m1; struct rfapi_monitor_eth *m2; int i; m1 = (struct rfapi_monitor_eth *)a; m2 = (struct rfapi_monitor_eth *)b; /* * compare ethernet addresses */ for (i = 0; i < ETH_ALEN; ++i) { if (m1->macaddr.octet[i] != m2->macaddr.octet[i]) return (m1->macaddr.octet[i] - m2->macaddr.octet[i]); } /* * compare LNIs */ return (m1->logical_net_id - m2->logical_net_id); } static void rfapiMonitorEthAttachImport( struct rfapi_import_table *it, struct agg_node *rn, /* it node attach point if non-0 */ struct rfapi_monitor_eth *mon) /* monitor struct to attach */ { struct skiplist *sl; int rc; vnc_zlog_debug_verbose("%s: it=%p", __func__, it); rfapiMonitorCheckAttachAllowed(); if (RFAPI_0_ETHERADDR(&mon->macaddr)) { /* * These go on a different list */ mon->next = it->eth0_queries; it->eth0_queries = mon; #if DEBUG_L2_EXTRA vnc_zlog_debug_verbose("%s: attached monitor %p to eth0 list", __func__, mon); #endif return; } if (rn == NULL) { #if DEBUG_L2_EXTRA vnc_zlog_debug_verbose("%s: rn is null!", __func__); #endif return; } /* * Get sl to attach to */ sl = RFAPI_MONITOR_ETH_W_ALLOC(rn); if (!sl) { sl = RFAPI_MONITOR_ETH_W_ALLOC(rn) = skiplist_new(0, NULL, NULL); agg_lock_node(rn); /* count skiplist mon_eth */ } #if DEBUG_L2_EXTRA vnc_zlog_debug_verbose( "%s: rn=%p, rn->lock=%d, sl=%p, attaching eth mon %p", __func__, rn, rn->lock, sl, mon); #endif rc = skiplist_insert(sl, (void *)mon, (void *)mon); assert(!rc); /* count eth monitor */ agg_lock_node(rn); } /* * reattach monitors for this HD to import table */ static void rfapiMonitorEthAttachImportHd(struct bgp *bgp, struct rfapi_descriptor *rfd) { void *cursor; struct rfapi_monitor_eth *mon; int rc; if (!rfd->mon_eth) { /* * No monitors for this HD */ return; } for (cursor = NULL, rc = skiplist_next(rfd->mon_eth, NULL, (void **)&mon, &cursor); rc == 0; rc = skiplist_next(rfd->mon_eth, NULL, (void **)&mon, &cursor)) { struct rfapi_import_table *it; struct prefix pfx_mac_buf; struct agg_node *rn; it = rfapiMacImportTableGet(bgp, mon->logical_net_id); assert(it); memset((void *)&pfx_mac_buf, 0, sizeof(struct prefix)); pfx_mac_buf.family = AF_ETHERNET; pfx_mac_buf.prefixlen = 48; pfx_mac_buf.u.prefix_eth = mon->macaddr; rn = agg_node_get(it->imported_vpn[AFI_L2VPN], &pfx_mac_buf); assert(rn); (void)rfapiMonitorEthAttachImport(it, rn, mon); } } static void rfapiMonitorEthDetachImport( struct bgp *bgp, struct rfapi_monitor_eth *mon) /* monitor struct to detach */ { struct rfapi_import_table *it; struct prefix pfx_mac_buf; struct skiplist *sl; struct agg_node *rn; int rc; it = rfapiMacImportTableGet(bgp, mon->logical_net_id); assert(it); if (RFAPI_0_ETHERADDR(&mon->macaddr)) { struct rfapi_monitor_eth *prev; struct rfapi_monitor_eth *this = NULL; for (prev = NULL, this = it->eth0_queries; this; prev = this, this = this->next) { if (this == mon) break; } if (this) { if (!prev) { it->eth0_queries = this->next; } else { prev->next = this->next; } } #if DEBUG_L2_EXTRA vnc_zlog_debug_verbose( "%s: it=%p, LNI=%d, detached eth0 mon %p", __func__, it, mon->logical_net_id, mon); #endif return; } memset((void *)&pfx_mac_buf, 0, sizeof(struct prefix)); pfx_mac_buf.family = AF_ETHERNET; pfx_mac_buf.prefixlen = 48; pfx_mac_buf.u.prefix_eth = mon->macaddr; rn = agg_node_get(it->imported_vpn[AFI_L2VPN], &pfx_mac_buf); assert(rn); #if DEBUG_L2_EXTRA char buf_prefix[PREFIX_STRLEN]; prefix2str(&rn->p, buf_prefix, sizeof(buf_prefix)); #endif /* * Get sl to detach from */ sl = RFAPI_MONITOR_ETH(rn); #if DEBUG_L2_EXTRA vnc_zlog_debug_verbose( "%s: it=%p, rn=%p, rn->lock=%d, sl=%p, pfx=%s, LNI=%d, detaching eth mon %p", __func__, it, rn, rn->lock, sl, buf_prefix, mon->logical_net_id, mon); #endif assert(sl); rc = skiplist_delete(sl, (void *)mon, (void *)mon); assert(!rc); /* uncount eth monitor */ agg_unlock_node(rn); } struct agg_node *rfapiMonitorEthAdd(struct bgp *bgp, struct rfapi_descriptor *rfd, struct ethaddr *macaddr, uint32_t logical_net_id) { int rc; struct rfapi_monitor_eth mon_buf; struct rfapi_monitor_eth *val; struct rfapi_import_table *it; struct agg_node *rn = NULL; struct prefix pfx_mac_buf; if (!rfd->mon_eth) { rfd->mon_eth = skiplist_new(0, mon_eth_cmp, NULL); } it = rfapiMacImportTableGet(bgp, logical_net_id); assert(it); /* * Get route node in import table. Here is where we attach the * monitor. * * Look it up now because we return it to caller regardless of * whether we create a new monitor or not. */ memset((void *)&pfx_mac_buf, 0, sizeof(struct prefix)); pfx_mac_buf.family = AF_ETHERNET; pfx_mac_buf.prefixlen = 48; pfx_mac_buf.u.prefix_eth = *macaddr; if (!RFAPI_0_ETHERADDR(macaddr)) { rn = agg_node_get(it->imported_vpn[AFI_L2VPN], &pfx_mac_buf); assert(rn); } memset((void *)&mon_buf, 0, sizeof(mon_buf)); mon_buf.rfd = rfd; mon_buf.macaddr = *macaddr; mon_buf.logical_net_id = logical_net_id; { char buf[BUFSIZ]; vnc_zlog_debug_verbose( "%s: LNI=%d: rfd=%p, pfx=%s", __func__, logical_net_id, rfd, rfapi_ntop(pfx_mac_buf.family, pfx_mac_buf.u.val, buf, BUFSIZ)); } /* * look up query */ rc = skiplist_search(rfd->mon_eth, (void *)&mon_buf, (void **)&val); if (!rc) { /* * Found monitor - we have seen this query before * restart timer */ vnc_zlog_debug_verbose( "%s: already present in rfd->mon_eth, not adding", __func__); rfapiMonitorEthTimerRestart(val); return rn; } /* * New query */ val = XCALLOC(MTYPE_RFAPI_MONITOR_ETH, sizeof(struct rfapi_monitor_eth)); assert(val); *val = mon_buf; ++rfd->monitor_count; ++bgp->rfapi->monitor_count; rc = skiplist_insert(rfd->mon_eth, val, val); #if DEBUG_L2_EXTRA vnc_zlog_debug_verbose("%s: inserted rfd=%p mon_eth=%p, rc=%d", __func__, rfd, val, rc); #else (void)rc; #endif /* * start timer */ rfapiMonitorEthTimerRestart(val); if (bgp->rfapi_cfg->flags & BGP_VNC_CONFIG_CALLBACK_DISABLE) { /* * callbacks turned off, so don't attach monitor to import table */ #if DEBUG_L2_EXTRA vnc_zlog_debug_verbose( "%s: callbacks turned off, not attaching mon_eth %p to import table", __func__, val); #endif return rn; } /* * attach to import table */ rfapiMonitorEthAttachImport(it, rn, val); return rn; } void rfapiMonitorEthDel(struct bgp *bgp, struct rfapi_descriptor *rfd, struct ethaddr *macaddr, uint32_t logical_net_id) { struct rfapi_monitor_eth *val; struct rfapi_monitor_eth mon_buf; int rc; vnc_zlog_debug_verbose("%s: entry rfd=%p", __func__, rfd); assert(rfd->mon_eth); memset((void *)&mon_buf, 0, sizeof(mon_buf)); mon_buf.macaddr = *macaddr; mon_buf.logical_net_id = logical_net_id; rc = skiplist_search(rfd->mon_eth, (void *)&mon_buf, (void **)&val); assert(!rc); /* * remove from import table */ if (!(bgp->rfapi_cfg->flags & BGP_VNC_CONFIG_CALLBACK_DISABLE)) { rfapiMonitorEthDetachImport(bgp, val); } if (val->timer) { thread_cancel(val->timer); val->timer = NULL; } /* * remove from rfd list */ rc = skiplist_delete(rfd->mon_eth, val, val); assert(!rc); #if DEBUG_L2_EXTRA vnc_zlog_debug_verbose("%s: freeing mon_eth %p", __func__, val); #endif XFREE(MTYPE_RFAPI_MONITOR_ETH, val); --rfd->monitor_count; --bgp->rfapi->monitor_count; } void rfapiMonitorCallbacksOff(struct bgp *bgp) { struct rfapi_import_table *it; afi_t afi; struct agg_table *rt; struct agg_node *rn; void *cursor; int rc; struct rfapi *h = bgp->rfapi; if (bgp->rfapi_cfg->flags & BGP_VNC_CONFIG_CALLBACK_DISABLE) { /* * Already off. */ return; } bgp->rfapi_cfg->flags |= BGP_VNC_CONFIG_CALLBACK_DISABLE; #if DEBUG_L2_EXTRA vnc_zlog_debug_verbose("%s: turned off callbacks", __func__); #endif if (h == NULL) return; /* * detach monitors from import VPN tables. The monitors * will still be linked in per-nve monitor lists. */ for (it = h->imports; it; it = it->next) { for (afi = AFI_IP; afi < AFI_MAX; ++afi) { struct rfapi_monitor_vpn *m; struct rfapi_monitor_vpn *next; rt = it->imported_vpn[afi]; for (rn = agg_route_top(rt); rn; rn = agg_route_next(rn)) { m = RFAPI_MONITOR_VPN(rn); if (RFAPI_MONITOR_VPN(rn)) RFAPI_MONITOR_VPN_W_ALLOC(rn) = NULL; for (; m; m = next) { next = m->next; m->next = NULL; /* gratuitous safeness */ m->node = NULL; agg_unlock_node(rn); /* uncount */ } } for (m = it->vpn0_queries[afi]; m; m = next) { next = m->next; m->next = NULL; /* gratuitous safeness */ m->node = NULL; } it->vpn0_queries[afi] = NULL; /* detach first monitor */ } } /* * detach monitors from import Eth tables. The monitors * will still be linked in per-nve monitor lists. */ /* * Loop over ethernet import tables */ for (cursor = NULL, rc = skiplist_next(h->import_mac, NULL, (void **)&it, &cursor); !rc; rc = skiplist_next(h->import_mac, NULL, (void **)&it, &cursor)) { struct rfapi_monitor_eth *e; struct rfapi_monitor_eth *enext; /* * The actual route table */ rt = it->imported_vpn[AFI_L2VPN]; /* * Find non-0 monitors (i.e., actual addresses, not FTD * monitors) */ for (rn = agg_route_top(rt); rn; rn = agg_route_next(rn)) { struct skiplist *sl; sl = RFAPI_MONITOR_ETH(rn); while (!skiplist_delete_first(sl)) { agg_unlock_node(rn); /* uncount monitor */ } } /* * Find 0-monitors (FTD queries) */ for (e = it->eth0_queries; e; e = enext) { #if DEBUG_L2_EXTRA vnc_zlog_debug_verbose("%s: detaching eth0 mon %p", __func__, e); #endif enext = e->next; e->next = NULL; /* gratuitous safeness */ } it->eth0_queries = NULL; /* detach first monitor */ } } void rfapiMonitorCallbacksOn(struct bgp *bgp) { struct listnode *hnode; struct rfapi_descriptor *rfd; if (!(bgp->rfapi_cfg->flags & BGP_VNC_CONFIG_CALLBACK_DISABLE)) { /* * Already on. It's important that we don't try to reattach * monitors that are already attached because, in the interest * of performance, there is no checking at the lower level * whether a monitor is already attached. It leads to * corrupted chains (e.g., looped pointers) */ return; } bgp->rfapi_cfg->flags &= ~BGP_VNC_CONFIG_CALLBACK_DISABLE; #if DEBUG_L2_EXTRA vnc_zlog_debug_verbose("%s: turned on callbacks", __func__); #endif if (bgp->rfapi == NULL) return; /* * reattach monitors */ for (ALL_LIST_ELEMENTS_RO(&bgp->rfapi->descriptors, hnode, rfd)) { rfapiMonitorAttachImportHd(rfd); rfapiMonitorEthAttachImportHd(bgp, rfd); } } frr-7.2.1/bgpd/rfapi/rfapi_monitor.h0000644000000000000000000001503213610377563014253 00000000000000/* * * Copyright 2009-2016, LabN Consulting, L.L.C. * * * This program is free software; you can redistribute it and/or * modify it under the terms of the GNU General Public License * as published by the Free Software Foundation; either version 2 * of the License, or (at your option) any later version. * * 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; see the file COPYING; if not, write to the Free Software * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA */ #ifndef QUAGGA_HGP_RFAPI_MONITOR_H #define QUAGGA_HGP_RFAPI_MONITOR_H #include "lib/zebra.h" #include "lib/prefix.h" #include "lib/table.h" /* * These get attached to the nodes in an import table (using "aggregate" ptr) * to indicate which nves are interested in a prefix/target */ struct rfapi_monitor_vpn { struct rfapi_monitor_vpn *next; /* chain from struct agg_node */ struct rfapi_descriptor *rfd; /* which NVE requested the route */ struct prefix p; /* constant: pfx in original request */ struct agg_node *node; /* node we're currently attached to */ uint32_t flags; #define RFAPI_MON_FLAG_NEEDCALLBACK 0x00000001 /* deferred callback */ // int dcount; /* debugging counter */ struct thread *timer; }; struct rfapi_monitor_encap { struct rfapi_monitor_encap *next; struct rfapi_monitor_encap *prev; struct agg_node *node; /* VPN node */ struct bgp_path_info *bpi; /* VPN bpi */ struct agg_node *rn; /* parent node */ }; struct rfapi_monitor_eth { struct rfapi_monitor_eth *next; /* for use in vpn0_queries list */ struct rfapi_descriptor *rfd; /* which NVE requested the route */ struct ethaddr macaddr; uint32_t logical_net_id; struct thread *timer; }; /* * This is referenced by the "aggregate" field of a route node * in an RFAPI import table. * * node lock/unlock: * - one lock increment for this structure itself * - one lock per chained struct rfapi_monitor_vpn * - one lock for the mon_eth skiplist itself * - one lock per mon_eth skiplist entry * - one lock for the ext skiplist itself * - one lock for each ext skiplist entry * remember to free skiplist when freeing rfapi_it_extra * - one lock per chained struct rfapi_monitor_encap * */ struct rfapi_it_extra { union { struct { struct rfapi_monitor_vpn *v; struct skiplist *idx_rd; /* RD index */ struct skiplist *mon_eth; /* ether queries */ struct { /* routes with UN addrs, either cached encap or * Encap TLV */ int valid_interior_count; /* unicast exterior routes, key=bpi, * val=allocated prefix */ struct skiplist *source; } e; } vpn; struct { struct rfapi_monitor_encap *e; } encap; } u; }; #define RFAPI_IT_EXTRA_GET(rn) \ ((struct rfapi_it_extra \ *)((rn)->aggregate \ ? (rn)->aggregate \ : (agg_lock_node(rn), \ (rn)->aggregate = XCALLOC( \ MTYPE_RFAPI_IT_EXTRA, \ sizeof(struct rfapi_it_extra))))) #define RFAPI_RDINDEX(rn) \ ((rn)->aggregate ? RFAPI_IT_EXTRA_GET(rn)->u.vpn.idx_rd : NULL) #define RFAPI_RDINDEX_W_ALLOC(rn) (RFAPI_IT_EXTRA_GET(rn)->u.vpn.idx_rd) #define RFAPI_MONITOR_ETH(rn) \ ((rn)->aggregate ? RFAPI_IT_EXTRA_GET(rn)->u.vpn.mon_eth : NULL) #define RFAPI_MONITOR_ETH_W_ALLOC(rn) (RFAPI_IT_EXTRA_GET(rn)->u.vpn.mon_eth) #define RFAPI_MONITOR_VPN(rn) \ ((rn)->aggregate ? RFAPI_IT_EXTRA_GET(rn)->u.vpn.v : NULL) #define RFAPI_MONITOR_VPN_W_ALLOC(rn) (RFAPI_IT_EXTRA_GET(rn)->u.vpn.v) #define RFAPI_MONITOR_ENCAP(rn) \ ((rn)->aggregate ? RFAPI_IT_EXTRA_GET(rn)->u.encap.e : NULL) #define RFAPI_MONITOR_ENCAP_W_ALLOC(rn) (RFAPI_IT_EXTRA_GET(rn)->u.encap.e) #define RFAPI_MONITOR_EXTERIOR(rn) (&(RFAPI_IT_EXTRA_GET(rn)->u.vpn.e)) #define RFAPI_HAS_MONITOR_EXTERIOR(rn) \ (rn && rn->aggregate \ && ((struct rfapi_it_extra *)(rn->aggregate))->u.vpn.e.source \ && !skiplist_first(((struct rfapi_it_extra *)(rn->aggregate)) \ ->u.vpn.e.source, \ NULL, NULL)) extern void rfapiMonitorLoopCheck(struct rfapi_monitor_vpn *mchain); extern void rfapiMonitorCleanCheck(struct bgp *bgp); extern void rfapiMonitorCheckAttachAllowed(void); extern void rfapiMonitorExtraFlush(safi_t safi, struct agg_node *rn); extern struct agg_node *rfapiMonitorGetAttachNode(struct rfapi_descriptor *rfd, struct prefix *p); extern void rfapiMonitorAttachImportHd(struct rfapi_descriptor *rfd); extern struct agg_node *rfapiMonitorAdd(struct bgp *bgp, struct rfapi_descriptor *rfd, struct prefix *p); extern void rfapiMonitorDetachImportHd(struct rfapi_descriptor *rfd); extern void rfapiMonitorDel(struct bgp *bgp, struct rfapi_descriptor *rfd, struct prefix *p); extern int rfapiMonitorDelHd(struct rfapi_descriptor *rfd); extern void rfapiMonitorCallbacksOff(struct bgp *bgp); extern void rfapiMonitorCallbacksOn(struct bgp *bgp); extern void rfapiMonitorResponseRemovalOff(struct bgp *bgp); extern void rfapiMonitorResponseRemovalOn(struct bgp *bgp); extern void rfapiMonitorExtraPrune(safi_t safi, struct agg_node *rn); extern void rfapiMonitorTimersRestart(struct rfapi_descriptor *rfd, struct prefix *p); extern void rfapiMonitorItNodeChanged(struct rfapi_import_table *import_table, struct agg_node *it_node, struct rfapi_monitor_vpn *monitor_list); extern void rfapiMonitorMovedUp(struct rfapi_import_table *import_table, struct agg_node *old_node, struct agg_node *new_node, struct rfapi_monitor_vpn *monitor_list); extern struct agg_node *rfapiMonitorEthAdd(struct bgp *bgp, struct rfapi_descriptor *rfd, struct ethaddr *macaddr, uint32_t logical_net_id); extern void rfapiMonitorEthDel(struct bgp *bgp, struct rfapi_descriptor *rfd, struct ethaddr *macaddr, uint32_t logical_net_id); #endif /* QUAGGA_HGP_RFAPI_MONITOR_H */ frr-7.2.1/bgpd/rfapi/rfapi_nve_addr.c0000644000000000000000000000734113610377563014345 00000000000000/* * * Copyright 2009-2016, LabN Consulting, L.L.C. * * * This program is free software; you can redistribute it and/or * modify it under the terms of the GNU General Public License * as published by the Free Software Foundation; either version 2 * of the License, or (at your option) any later version. * * 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; see the file COPYING; if not, write to the Free Software * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA */ #include "lib/zebra.h" #include "lib/prefix.h" #include "lib/agg_table.h" #include "lib/vty.h" #include "lib/memory.h" #include "lib/skiplist.h" #include "bgpd/bgpd.h" #include "bgpd/rfapi/bgp_rfapi_cfg.h" #include "bgpd/rfapi/rfapi.h" #include "bgpd/rfapi/rfapi_backend.h" #include "bgpd/rfapi/rfapi_import.h" #include "bgpd/rfapi/rfapi_private.h" #include "bgpd/rfapi/rfapi_nve_addr.h" #include "bgpd/rfapi/rfapi_vty.h" #include "bgpd/rfapi/vnc_debug.h" #define DEBUG_NVE_ADDR 0 void rfapiNveAddr2Str(struct rfapi_nve_addr *, char *, int); #if DEBUG_NVE_ADDR static void logdifferent(const char *tag, struct rfapi_nve_addr *a, struct rfapi_nve_addr *b) { char a_str[BUFSIZ]; char b_str[BUFSIZ]; rfapiNveAddr2Str(a, a_str, BUFSIZ); rfapiNveAddr2Str(b, b_str, BUFSIZ); vnc_zlog_debug_verbose("%s: [%s] [%s]", tag, a_str, b_str); } #endif int rfapi_nve_addr_cmp(void *k1, void *k2) { struct rfapi_nve_addr *a = (struct rfapi_nve_addr *)k1; struct rfapi_nve_addr *b = (struct rfapi_nve_addr *)k2; int ret = 0; if (!a || !b) { #if DEBUG_NVE_ADDR vnc_zlog_debug_verbose("%s: missing address a=%p b=%p", __func__, a, b); #endif return (a - b); } if (a->un.addr_family != b->un.addr_family) { #if DEBUG_NVE_ADDR vnc_zlog_debug_verbose( "diff: UN addr fam a->un.af=%d, b->un.af=%d", a->un.addr_family, b->un.addr_family); #endif return (a->un.addr_family - b->un.addr_family); } if (a->un.addr_family == AF_INET) { ret = IPV4_ADDR_CMP(&a->un.addr.v4, &b->un.addr.v4); if (ret != 0) { #if DEBUG_NVE_ADDR logdifferent("diff: UN addr", a, b); #endif return ret; } } else if (a->un.addr_family == AF_INET6) { ret = IPV6_ADDR_CMP(&a->un.addr.v6, &b->un.addr.v6); if (ret == 0) { #if DEBUG_NVE_ADDR logdifferent("diff: UN addr", a, b); #endif return ret; } } else { assert(0); } if (a->vn.addr_family != b->vn.addr_family) { #if DEBUG_NVE_ADDR vnc_zlog_debug_verbose( "diff: pT addr fam a->vn.af=%d, b->vn.af=%d", a->vn.addr_family, b->vn.addr_family); #endif return (a->vn.addr_family - b->vn.addr_family); } if (a->vn.addr_family == AF_INET) { ret = IPV4_ADDR_CMP(&a->vn.addr.v4, &b->vn.addr.v4); if (ret != 0) { #if DEBUG_NVE_ADDR logdifferent("diff: VN addr", a, b); #endif return ret; } } else if (a->vn.addr_family == AF_INET6) { ret = IPV6_ADDR_CMP(&a->vn.addr.v6, &b->vn.addr.v6); if (ret == 0) { #if DEBUG_NVE_ADDR logdifferent("diff: VN addr", a, b); #endif return ret; } } else { assert(0); } return 0; } void rfapiNveAddr2Str(struct rfapi_nve_addr *na, char *buf, int bufsize) { char *p = buf; int r; #define REMAIN (bufsize - (p-buf)) #define INCP {p += (r > REMAIN)? REMAIN: r;} if (bufsize < 1) return; r = snprintf(p, REMAIN, "VN="); INCP; if (!rfapiRfapiIpAddr2Str(&na->vn, p, REMAIN)) goto done; buf[bufsize - 1] = 0; p = buf + strlen(buf); r = snprintf(p, REMAIN, ", UN="); INCP; rfapiRfapiIpAddr2Str(&na->un, p, REMAIN); done: buf[bufsize - 1] = 0; } frr-7.2.1/bgpd/rfapi/rfapi_nve_addr.h0000644000000000000000000000221513610377563014345 00000000000000/* * * Copyright 2009-2016, LabN Consulting, L.L.C. * * * This program is free software; you can redistribute it and/or * modify it under the terms of the GNU General Public License * as published by the Free Software Foundation; either version 2 * of the License, or (at your option) any later version. * * 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; see the file COPYING; if not, write to the Free Software * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA */ #ifndef _QUAGGA_BGP_RFAPI_NVE_ADDR_H #define _QUAGGA_BGP_RFAPI_NVE_ADDR_H #include "rfapi.h" struct rfapi_nve_addr { struct rfapi_ip_addr vn; struct rfapi_ip_addr un; void *info; }; extern int rfapi_nve_addr_cmp(void *k1, void *k2); extern void rfapiNveAddr2Str(struct rfapi_nve_addr *na, char *buf, int bufsize); #endif /* _QUAGGA_BGP_RFAPI_NVE_ADDR_H */ frr-7.2.1/bgpd/rfapi/rfapi_private.h0000644000000000000000000003336613610377563014250 00000000000000/* * * Copyright 2009-2016, LabN Consulting, L.L.C. * * * This program is free software; you can redistribute it and/or * modify it under the terms of the GNU General Public License * as published by the Free Software Foundation; either version 2 * of the License, or (at your option) any later version. * * 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; see the file COPYING; if not, write to the Free Software * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA */ /* * Internal definitions for RFAPI. Not for use by other code */ #ifndef _QUAGGA_BGP_RFAPI_PRIVATE_H #define _QUAGGA_BGP_RFAPI_PRIVATE_H #include "lib/linklist.h" #include "lib/skiplist.h" #include "lib/workqueue.h" #include "bgpd/bgp_attr.h" #include "bgpd/bgp_route.h" #include "rfapi.h" /* * Lists of rfapi_adb. Each rfapi_adb is referenced twice: * * 1. each is referenced in by_lifetime * 2. each is referenced by exactly one of: ipN_by_prefix, ip0_by_ether */ struct rfapi_advertised_prefixes { struct skiplist *ipN_by_prefix; /* all except 0/32, 0/128 */ struct skiplist *ip0_by_ether; /* ip prefix 0/32, 0/128 */ struct skiplist *by_lifetime; /* all */ }; struct rfapi_descriptor { struct agg_node *un_node; /* backref to un table */ struct rfapi_descriptor *next; /* next vn_addr */ /* supplied by client */ struct bgp *bgp; /* from rfp_start_val */ struct rfapi_ip_addr vn_addr; struct rfapi_ip_addr un_addr; rfapi_response_cb_t *response_cb; /* override per-bgp response_cb */ void *cookie; /* for callbacks */ struct rfapi_tunneltype_option default_tunneltype_option; /* supplied by matched configuration */ struct prefix_rd rd; struct ecommunity *rt_export_list; uint32_t response_lifetime; /* list of prefixes currently being advertised by this nve */ struct rfapi_advertised_prefixes advertised; time_t open_time; uint32_t max_prefix_lifetime; uint32_t min_prefix_lifetime; /* reference to this nve's import table */ struct rfapi_import_table *import_table; uint32_t monitor_count; struct agg_table *mon; /* rfapi_monitors */ struct skiplist *mon_eth; /* ethernet monitors */ /* * rib RIB as seen by NVE * rib_pending RIB containing nodes with updated info chains * rsp_times last time we sent response containing pfx */ uint32_t rib_prefix_count; /* pfxes with routes */ struct agg_table *rib[AFI_MAX]; struct agg_table *rib_pending[AFI_MAX]; struct work_queue *updated_responses_queue; struct agg_table *rsp_times[AFI_MAX]; uint32_t rsp_counter; /* dedup initial rsp */ time_t rsp_time; /* dedup initial rsp */ time_t ftd_last_allowed_time; /* FTD filter */ unsigned int stat_count_nh_reachable; unsigned int stat_count_nh_removal; /* * points to the original nve group structure that matched * when this nve_descriptor was created. We use this pointer * in rfapi_close() to find the nve group structure and * delete its reference back to us. * * If the nve group structure is deleted (via configuration * change) while this nve_descriptor exists, this rfg pointer * will be set to NULL. */ struct rfapi_nve_group_cfg *rfg; /* * This ~7kB structure is here to permit multiple routes for * a prefix to be injected to BGP. There are at least two * situations where such conditions obtain: * * When an VNC route is exported to BGP on behalf of the set of * NVEs that belong to the export NVE group, it is replicated * so that there is one route per NVE (and the route's nexthop * is the NVE's VN address). * * Each of these routes being injected to BGP must have a distinct * peer pointer (otherwise, if they have the same peer pointer, each * route will be considered an implicit waithdraw of the previous * route injected from that peer, and the new route will replace * rather than augment the old one(s)). */ struct peer *peer; uint32_t flags; #define RFAPI_HD_FLAG_CALLBACK_SCHEDULED_AFI_IP 0x00000001 #define RFAPI_HD_FLAG_CALLBACK_SCHEDULED_AFI_IP6 0x00000002 #define RFAPI_HD_FLAG_CALLBACK_SCHEDULED_AFI_L2VPN 0x00000004 #define RFAPI_HD_FLAG_PROVISIONAL 0x00000008 #define RFAPI_HD_FLAG_CLOSING_ADMINISTRATIVELY 0x00000010 #define RFAPI_HD_FLAG_IS_VRF 0x00000012 }; #define RFAPI_QUEUED_FLAG(afi) \ (((afi) == AFI_IP) \ ? RFAPI_HD_FLAG_CALLBACK_SCHEDULED_AFI_IP \ : (((afi) == AFI_IP6) \ ? RFAPI_HD_FLAG_CALLBACK_SCHEDULED_AFI_IP6 \ : (((afi) == AFI_L2VPN) \ ? RFAPI_HD_FLAG_CALLBACK_SCHEDULED_AFI_L2VPN \ : (assert(0), 0)))) struct rfapi_global_stats { time_t last_reset; unsigned int max_descriptors; unsigned int count_unknown_nves; unsigned int count_queries; unsigned int count_queries_failed; unsigned int max_responses; /* semantics? */ unsigned int count_registrations; unsigned int count_registrations_failed; unsigned int count_updated_response_updates; unsigned int count_updated_response_deletes; }; /* * There is one of these per BGP instance. * * Radix tree is indexed by un address; follow chain and * check vn address to get exact match. */ struct rfapi { struct agg_table *un[AFI_MAX]; struct rfapi_import_table *imports; /* IPv4, IPv6 */ struct list descriptors; /* debug & resolve-nve imports */ struct rfapi_global_stats stat; /* * callbacks into RFP, set at startup time (bgp_rfapi_new() gets * values from rfp_start()) or via rfapi_rfp_set_cb_methods() * (otherwise NULL). Note that the response_cb method can also * be overridden per-rfd (currently used only for debug/test scenarios) */ struct rfapi_rfp_cb_methods rfp_methods; /* * Import tables for Ethernet over IPSEC * * The skiplist keys are LNIs. Values are pointers * to struct rfapi_import_table. */ struct skiplist *import_mac; /* L2 */ /* * when exporting plain routes ("registered-nve" mode) to * bgp unicast or zebra, we need to keep track of information * related to expiring the routes according to the VNC lifetime */ struct agg_table *rt_export_bgp[AFI_MAX]; struct agg_table *rt_export_zebra[AFI_MAX]; /* * For VNC->BGP unicast exports in CE mode, we need a * routing table that collects all of the VPN routes * in a single tree. The VPN rib is split up according * to RD first, so we can't use that. This is an import * table that matches all RTs. */ struct rfapi_import_table *it_ce; /* * when importing bgp-direct routes in resolve-nve mode, * this list maps unicast route nexthops to their bgp_path_infos * in the unicast table */ struct skiplist *resolve_nve_nexthop; /* * Descriptors for which rfapi_close() was called during a callback. * They will be closed after the callback finishes. */ struct work_queue *deferred_close_q; /* * For "show vnc responses" */ uint32_t response_immediate_count; uint32_t response_updated_count; uint32_t monitor_count; uint32_t rib_prefix_count_total; uint32_t rib_prefix_count_total_max; uint32_t flags; #define RFAPI_INCALLBACK 0x00000001 void *rfp; /* from rfp_start */ }; #define RFAPI_RIB_PREFIX_COUNT_INCR(rfd, rfapi) \ do { \ ++(rfd)->rib_prefix_count; \ ++(rfapi)->rib_prefix_count_total; \ if ((rfapi)->rib_prefix_count_total \ > (rfapi)->rib_prefix_count_total_max) \ ++(rfapi)->rib_prefix_count_total_max; \ } while (0) #define RFAPI_RIB_PREFIX_COUNT_DECR(rfd, rfapi) \ do { \ --(rfd)->rib_prefix_count; \ --(rfapi)->rib_prefix_count_total; \ } while (0) #define RFAPI_0_PREFIX(prefix) \ ((((prefix)->family == AF_INET) \ ? (prefix)->u.prefix4.s_addr == 0 \ : (((prefix)->family == AF_INET6) \ ? (IN6_IS_ADDR_UNSPECIFIED(&(prefix)->u.prefix6)) \ : 0))) #define RFAPI_0_ETHERADDR(ea) \ (((ea)->octet[0] | (ea)->octet[1] | (ea)->octet[2] | (ea)->octet[3] \ | (ea)->octet[4] | (ea)->octet[5]) \ == 0) #define RFAPI_HOST_PREFIX(prefix) \ (((prefix)->family == AF_INET) \ ? ((prefix)->prefixlen == 32) \ : (((prefix)->family == AF_INET6) \ ? ((prefix)->prefixlen == 128) \ : 0)) extern void rfapiQprefix2Rprefix(struct prefix *qprefix, struct rfapi_ip_prefix *rprefix); extern int rfapi_find_rfd(struct bgp *bgp, struct rfapi_ip_addr *vn_addr, struct rfapi_ip_addr *un_addr, struct rfapi_descriptor **rfd); extern void add_vnc_route(struct rfapi_descriptor *rfd, /* cookie + UN addr for VPN */ struct bgp *bgp, int safi, struct prefix *p, struct prefix_rd *prd, struct rfapi_ip_addr *nexthop, uint32_t *local_pref, /* host byte order */ uint32_t *lifetime, /* host byte order */ struct bgp_tea_options *rfp_options, struct rfapi_un_option *options_un, struct rfapi_vn_option *options_vn, struct ecommunity *rt_export_list, uint32_t *med, uint32_t *label, uint8_t type, uint8_t sub_type, int flags); #define RFAPI_AHR_NO_TUNNEL_SUBTLV 0x00000001 #define RFAPI_AHR_RFPOPT_IS_VNCTLV 0x00000002 /* hack! */ #if 0 /* unused? */ # define RFAPI_AHR_SET_PFX_TO_NEXTHOP 0x00000004 #endif extern void del_vnc_route(struct rfapi_descriptor *rfd, struct peer *peer, struct bgp *bgp, safi_t safi, struct prefix *p, struct prefix_rd *prd, uint8_t type, uint8_t sub_type, struct rfapi_nexthop *lnh, int kill); extern int rfapiCliGetPrefixAddr(struct vty *vty, const char *str, struct prefix *p); extern int rfapiGetVncLifetime(struct attr *attr, uint32_t *lifetime); extern int rfapiGetVncTunnelUnAddr(struct attr *attr, struct prefix *p); extern int rfapi_reopen(struct rfapi_descriptor *rfd, struct bgp *bgp); extern void vnc_import_bgp_add_rfp_host_route_mode_resolve_nve( struct bgp *bgp, struct rfapi_descriptor *rfd, struct prefix *prefix); extern void vnc_import_bgp_del_rfp_host_route_mode_resolve_nve( struct bgp *bgp, struct rfapi_descriptor *rfd, struct prefix *prefix); extern void rfapiFreeBgpTeaOptionChain(struct bgp_tea_options *p); extern struct rfapi_vn_option *rfapiVnOptionsDup(struct rfapi_vn_option *orig); extern struct rfapi_un_option *rfapiUnOptionsDup(struct rfapi_un_option *orig); extern struct bgp_tea_options *rfapiOptionsDup(struct bgp_tea_options *orig); extern int rfapi_ip_addr_cmp(struct rfapi_ip_addr *a1, struct rfapi_ip_addr *a2); extern uint32_t rfp_cost_to_localpref(uint8_t cost); extern int rfapi_set_autord_from_vn(struct prefix_rd *rd, struct rfapi_ip_addr *vn); extern struct rfapi_nexthop *rfapi_nexthop_new(struct rfapi_nexthop *copyme); extern void rfapi_nexthop_free(void *goner); extern struct rfapi_vn_option * rfapi_vn_options_dup(struct rfapi_vn_option *existing); extern void rfapi_un_options_free(struct rfapi_un_option *goner); extern void rfapi_vn_options_free(struct rfapi_vn_option *goner); extern void vnc_add_vrf_opener(struct bgp *bgp, struct rfapi_nve_group_cfg *rfg); extern void clear_vnc_vrf_closer(struct rfapi_nve_group_cfg *rfg); /*------------------------------------------ * rfapi_extract_l2o * * Find Layer 2 options in an option chain * * input: * pHop option chain * * output: * l2o layer 2 options extracted * * return value: * 0 OK * 1 no options found * --------------------------------------------*/ extern int rfapi_extract_l2o( struct bgp_tea_options *pHop, /* chain of options */ struct rfapi_l2address_option *l2o); /* return extracted value */ /* * compaitibility to old quagga_time call * time_t value in terms of stabilised absolute time. * replacement for POSIX time() */ extern time_t rfapi_time(time_t *t); DECLARE_MGROUP(RFAPI) DECLARE_MTYPE(RFAPI_CFG) DECLARE_MTYPE(RFAPI_GROUP_CFG) DECLARE_MTYPE(RFAPI_L2_CFG) DECLARE_MTYPE(RFAPI_RFP_GROUP_CFG) DECLARE_MTYPE(RFAPI) DECLARE_MTYPE(RFAPI_DESC) DECLARE_MTYPE(RFAPI_IMPORTTABLE) DECLARE_MTYPE(RFAPI_MONITOR) DECLARE_MTYPE(RFAPI_MONITOR_ENCAP) DECLARE_MTYPE(RFAPI_NEXTHOP) DECLARE_MTYPE(RFAPI_VN_OPTION) DECLARE_MTYPE(RFAPI_UN_OPTION) DECLARE_MTYPE(RFAPI_WITHDRAW) DECLARE_MTYPE(RFAPI_RFG_NAME) DECLARE_MTYPE(RFAPI_ADB) DECLARE_MTYPE(RFAPI_ETI) DECLARE_MTYPE(RFAPI_NVE_ADDR) DECLARE_MTYPE(RFAPI_PREFIX_BAG) DECLARE_MTYPE(RFAPI_IT_EXTRA) DECLARE_MTYPE(RFAPI_INFO) DECLARE_MTYPE(RFAPI_ADDR) DECLARE_MTYPE(RFAPI_UPDATED_RESPONSE_QUEUE) DECLARE_MTYPE(RFAPI_RECENT_DELETE) DECLARE_MTYPE(RFAPI_L2ADDR_OPT) DECLARE_MTYPE(RFAPI_AP) DECLARE_MTYPE(RFAPI_MONITOR_ETH) /* * Caller must supply an already-allocated rfd with the "caller" * fields already set (vn_addr, un_addr, callback, cookie) * The advertised_prefixes[] array elements should be NULL to * have this function set them to newly-allocated radix trees. */ extern int rfapi_init_and_open(struct bgp *bgp, struct rfapi_descriptor *rfd, struct rfapi_nve_group_cfg *rfg); #endif /* _QUAGGA_BGP_RFAPI_PRIVATE_H */ frr-7.2.1/bgpd/rfapi/rfapi_rib.c0000644000000000000000000016630613610377563013346 00000000000000/* * * Copyright 2009-2016, LabN Consulting, L.L.C. * * * This program is free software; you can redistribute it and/or * modify it under the terms of the GNU General Public License * as published by the Free Software Foundation; either version 2 * of the License, or (at your option) any later version. * * 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; see the file COPYING; if not, write to the Free Software * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA */ /* * File: rfapi_rib.c * Purpose: maintain per-nve ribs and generate change lists */ #include "lib/zebra.h" #include "lib/prefix.h" #include "lib/agg_table.h" #include "lib/vty.h" #include "lib/memory.h" #include "lib/log.h" #include "lib/skiplist.h" #include "lib/workqueue.h" #include "bgpd/bgpd.h" #include "bgpd/bgp_route.h" #include "bgpd/bgp_ecommunity.h" #include "bgpd/bgp_mplsvpn.h" #include "bgpd/bgp_vnc_types.h" #include "bgpd/rfapi/rfapi.h" #include "bgpd/rfapi/bgp_rfapi_cfg.h" #include "bgpd/rfapi/rfapi_import.h" #include "bgpd/rfapi/rfapi_private.h" #include "bgpd/rfapi/rfapi_vty.h" #include "bgpd/rfapi/vnc_import_bgp.h" #include "bgpd/rfapi/rfapi_rib.h" #include "bgpd/rfapi/rfapi_monitor.h" #include "bgpd/rfapi/rfapi_encap_tlv.h" #include "bgpd/rfapi/vnc_debug.h" #define DEBUG_PROCESS_PENDING_NODE 0 #define DEBUG_PENDING_DELETE_ROUTE 0 #define DEBUG_NHL 0 #define DEBUG_RIB_SL_RD 0 /* forward decl */ #if DEBUG_NHL static void rfapiRibShowRibSl(void *stream, struct prefix *pfx, struct skiplist *sl); #endif /* * RIB * --- * Model of the set of routes currently in the NVE's RIB. * * node->info ptr to "struct skiplist". * MUST be NULL if there are no routes. * key = ptr to struct prefix {vn} * val = ptr to struct rfapi_info * skiplist.del = NULL * skiplist.cmp = vnc_prefix_cmp * * node->aggregate ptr to "struct skiplist". * key = ptr to struct prefix {vn} * val = ptr to struct rfapi_info * skiplist.del = rfapi_info_free * skiplist.cmp = vnc_prefix_cmp * * This skiplist at "aggregate" * contains the routes recently * deleted * * * Pending RIB * ----------- * Sparse list of prefixes that need to be updated. Each node * will have the complete set of routes for the prefix. * * node->info ptr to "struct list" (lib/linklist.h) * "Cost List" * List of routes sorted lowest cost first. * This list is how the new complete set * of routes should look. * Set if there are updates to the prefix; * MUST be NULL if there are no updates. * * .data = ptr to struct rfapi_info * list.cmp = NULL (sorted manually) * list.del = rfapi_info_free * * Special case: if node->info is 1, it means * "delete all routes at this prefix". * * node->aggregate ptr to struct skiplist * key = ptr to struct prefix {vn} (part of ri) * val = struct rfapi_info * skiplist.cmp = vnc_prefix_cmp * skiplist.del = NULL * * ptlist is rewritten anew each time * rfapiRibUpdatePendingNode() is called * * THE ptlist VALUES ARE REFERENCES TO THE * rfapi_info STRUCTS IN THE node->info LIST. */ /* * iterate over RIB to count responses, compare with running counters */ void rfapiRibCheckCounts( int checkstats, /* validate rfd & global counts */ unsigned int offset) /* number of ri's held separately */ { struct rfapi_descriptor *rfd; struct listnode *node; struct bgp *bgp = bgp_get_default(); uint32_t t_pfx_active = 0; uint32_t t_pfx_deleted = 0; uint32_t t_ri_active = 0; uint32_t t_ri_deleted = 0; uint32_t t_ri_pend = 0; unsigned int alloc_count; /* * loop over NVEs */ for (ALL_LIST_ELEMENTS_RO(&bgp->rfapi->descriptors, node, rfd)) { afi_t afi; uint32_t pfx_active = 0; uint32_t pfx_deleted = 0; for (afi = AFI_IP; afi < AFI_MAX; ++afi) { struct agg_node *rn; for (rn = agg_route_top(rfd->rib[afi]); rn; rn = agg_route_next(rn)) { struct skiplist *sl = rn->info; struct skiplist *dsl = rn->aggregate; uint32_t ri_active = 0; uint32_t ri_deleted = 0; if (sl) { ri_active = skiplist_count(sl); assert(ri_active); t_ri_active += ri_active; ++pfx_active; ++t_pfx_active; } if (dsl) { ri_deleted = skiplist_count(dsl); t_ri_deleted += ri_deleted; ++pfx_deleted; ++t_pfx_deleted; } } for (rn = agg_route_top(rfd->rib_pending[afi]); rn; rn = agg_route_next(rn)) { struct list *l = rn->info; /* sorted by cost */ struct skiplist *sl = rn->aggregate; uint32_t ri_pend_cost = 0; uint32_t ri_pend_uniq = 0; if (sl) { ri_pend_uniq = skiplist_count(sl); } if (l && (l != (void *)1)) { ri_pend_cost = l->count; t_ri_pend += l->count; } assert(ri_pend_uniq == ri_pend_cost); } } if (checkstats) { if (pfx_active != rfd->rib_prefix_count) { vnc_zlog_debug_verbose( "%s: rfd %p actual pfx count %u != running %u", __func__, rfd, pfx_active, rfd->rib_prefix_count); assert(0); } } } if (checkstats && bgp->rfapi) { if (t_pfx_active != bgp->rfapi->rib_prefix_count_total) { vnc_zlog_debug_verbose( "%s: actual total pfx count %u != running %u", __func__, t_pfx_active, bgp->rfapi->rib_prefix_count_total); assert(0); } } /* * Check against memory allocation count */ alloc_count = mtype_stats_alloc(MTYPE_RFAPI_INFO); assert(t_ri_active + t_ri_deleted + t_ri_pend + offset == alloc_count); } static struct rfapi_info *rfapi_info_new(void) { return XCALLOC(MTYPE_RFAPI_INFO, sizeof(struct rfapi_info)); } void rfapiFreeRfapiUnOptionChain(struct rfapi_un_option *p) { while (p) { struct rfapi_un_option *next; next = p->next; XFREE(MTYPE_RFAPI_UN_OPTION, p); p = next; } } void rfapiFreeRfapiVnOptionChain(struct rfapi_vn_option *p) { while (p) { struct rfapi_vn_option *next; next = p->next; XFREE(MTYPE_RFAPI_VN_OPTION, p); p = next; } } static void rfapi_info_free(struct rfapi_info *goner) { if (goner) { if (goner->tea_options) { rfapiFreeBgpTeaOptionChain(goner->tea_options); goner->tea_options = NULL; } if (goner->un_options) { rfapiFreeRfapiUnOptionChain(goner->un_options); goner->un_options = NULL; } if (goner->vn_options) { rfapiFreeRfapiVnOptionChain(goner->vn_options); goner->vn_options = NULL; } if (goner->timer) { struct rfapi_rib_tcb *tcb; tcb = ((struct thread *)goner->timer)->arg; thread_cancel((struct thread *)goner->timer); XFREE(MTYPE_RFAPI_RECENT_DELETE, tcb); goner->timer = NULL; } XFREE(MTYPE_RFAPI_INFO, goner); } } /* * Timer control block for recently-deleted and expired routes */ struct rfapi_rib_tcb { struct rfapi_descriptor *rfd; struct skiplist *sl; struct rfapi_info *ri; struct agg_node *rn; int flags; #define RFAPI_RIB_TCB_FLAG_DELETED 0x00000001 }; /* * remove route from rib */ static int rfapiRibExpireTimer(struct thread *t) { struct rfapi_rib_tcb *tcb = t->arg; RFAPI_RIB_CHECK_COUNTS(1, 0); /* * Forget reference to thread. Otherwise rfapi_info_free() will * attempt to free thread pointer as an option chain */ tcb->ri->timer = NULL; /* "deleted" skiplist frees ri, "active" doesn't */ assert(!skiplist_delete(tcb->sl, &tcb->ri->rk, NULL)); if (!tcb->sl->del) { /* * XXX in this case, skiplist has no delete function: we must * therefore delete rfapi_info explicitly. */ rfapi_info_free(tcb->ri); } if (skiplist_empty(tcb->sl)) { if (CHECK_FLAG(tcb->flags, RFAPI_RIB_TCB_FLAG_DELETED)) tcb->rn->aggregate = NULL; else { struct bgp *bgp = bgp_get_default(); tcb->rn->info = NULL; RFAPI_RIB_PREFIX_COUNT_DECR(tcb->rfd, bgp->rfapi); } skiplist_free(tcb->sl); agg_unlock_node(tcb->rn); } XFREE(MTYPE_RFAPI_RECENT_DELETE, tcb); RFAPI_RIB_CHECK_COUNTS(1, 0); return 0; } static void rfapiRibStartTimer(struct rfapi_descriptor *rfd, struct rfapi_info *ri, struct agg_node *rn, /* route node attached to */ int deleted) { struct thread *t = ri->timer; struct rfapi_rib_tcb *tcb = NULL; char buf_prefix[PREFIX_STRLEN]; if (t) { tcb = t->arg; thread_cancel(t); ri->timer = NULL; } else { tcb = XCALLOC(MTYPE_RFAPI_RECENT_DELETE, sizeof(struct rfapi_rib_tcb)); } tcb->rfd = rfd; tcb->ri = ri; tcb->rn = rn; if (deleted) { tcb->sl = (struct skiplist *)rn->aggregate; SET_FLAG(tcb->flags, RFAPI_RIB_TCB_FLAG_DELETED); } else { tcb->sl = (struct skiplist *)rn->info; UNSET_FLAG(tcb->flags, RFAPI_RIB_TCB_FLAG_DELETED); } prefix2str(&rn->p, buf_prefix, sizeof(buf_prefix)); vnc_zlog_debug_verbose("%s: rfd %p pfx %s life %u", __func__, rfd, buf_prefix, ri->lifetime); ri->timer = NULL; thread_add_timer(bm->master, rfapiRibExpireTimer, tcb, ri->lifetime, &ri->timer); assert(ri->timer); } extern void rfapi_rib_key_init(struct prefix *prefix, /* may be NULL */ struct prefix_rd *rd, /* may be NULL */ struct prefix *aux, /* may be NULL */ struct rfapi_rib_key *rk) { memset((void *)rk, 0, sizeof(struct rfapi_rib_key)); if (prefix) rk->vn = *prefix; if (rd) rk->rd = *rd; if (aux) rk->aux_prefix = *aux; } /* * Compares two s */ int rfapi_rib_key_cmp(void *k1, void *k2) { struct rfapi_rib_key *a = (struct rfapi_rib_key *)k1; struct rfapi_rib_key *b = (struct rfapi_rib_key *)k2; int ret; if (!a || !b) return (a - b); ret = vnc_prefix_cmp(&a->vn, &b->vn); if (ret) return ret; ret = vnc_prefix_cmp(&a->rd, &b->rd); if (ret) return ret; ret = vnc_prefix_cmp(&a->aux_prefix, &b->aux_prefix); return ret; } /* * Note: this function will claim that two option chains are * different unless their option items are in identical order. * The consequence is that RFP updated responses can be sent * unnecessarily, or that they might contain nexthop items * that are not strictly needed. * * This function could be modified to compare option chains more * thoroughly, but it's not clear that the extra compuation would * be worth it. */ static int bgp_tea_options_cmp(struct bgp_tea_options *a, struct bgp_tea_options *b) { int rc; if (!a || !b) { return (a - b); } if (a->type != b->type) return (a->type - b->type); if (a->length != b->length) return (a->length = b->length); if ((rc = memcmp(a->value, b->value, a->length))) return rc; if (!a->next != !b->next) { /* logical xor */ return (a->next - b->next); } if (a->next) return bgp_tea_options_cmp(a->next, b->next); return 0; } static int rfapi_info_cmp(struct rfapi_info *a, struct rfapi_info *b) { int rc; if (!a || !b) return (a - b); if ((rc = rfapi_rib_key_cmp(&a->rk, &b->rk))) return rc; if ((rc = vnc_prefix_cmp(&a->un, &b->un))) return rc; if (a->cost != b->cost) return (a->cost - b->cost); if (a->lifetime != b->lifetime) return (a->lifetime - b->lifetime); if ((rc = bgp_tea_options_cmp(a->tea_options, b->tea_options))) return rc; return 0; } void rfapiRibClear(struct rfapi_descriptor *rfd) { struct bgp *bgp; afi_t afi; if (rfd->bgp) bgp = rfd->bgp; else bgp = bgp_get_default(); #if DEBUG_L2_EXTRA vnc_zlog_debug_verbose("%s: rfd=%p", __func__, rfd); #endif for (afi = AFI_IP; afi < AFI_MAX; ++afi) { struct agg_node *pn; struct agg_node *rn; if (rfd->rib_pending[afi]) { for (pn = agg_route_top(rfd->rib_pending[afi]); pn; pn = agg_route_next(pn)) { if (pn->aggregate) { /* * free references into the rfapi_info * structures before * freeing the structures themselves */ skiplist_free( (struct skiplist *)(pn->aggregate)); pn->aggregate = NULL; agg_unlock_node( pn); /* skiplist deleted */ } /* * free the rfapi_info structures */ if (pn->info) { if (pn->info != (void *)1) { list_delete( (struct list * *)(&pn->info)); } pn->info = NULL; /* linklist or 1 deleted */ agg_unlock_node(pn); } } } if (rfd->rib[afi]) { for (rn = agg_route_top(rfd->rib[afi]); rn; rn = agg_route_next(rn)) { if (rn->info) { struct rfapi_info *ri; while (0 == skiplist_first( (struct skiplist *) rn->info, NULL, (void **)&ri)) { rfapi_info_free(ri); skiplist_delete_first( (struct skiplist *) rn->info); } skiplist_free( (struct skiplist *)rn->info); rn->info = NULL; agg_unlock_node(rn); RFAPI_RIB_PREFIX_COUNT_DECR(rfd, bgp->rfapi); } if (rn->aggregate) { struct rfapi_info *ri_del; /* delete skiplist & contents */ while (!skiplist_first( (struct skiplist *)(rn->aggregate), NULL, (void **)&ri_del)) { /* sl->del takes care of ri_del */ skiplist_delete_first(( struct skiplist *)(rn->aggregate)); } skiplist_free( (struct skiplist *)(rn->aggregate)); rn->aggregate = NULL; agg_unlock_node(rn); } } } } if (rfd->updated_responses_queue) work_queue_free_and_null(&rfd->updated_responses_queue); } /* * Release all dynamically-allocated memory that is part of an HD's RIB */ void rfapiRibFree(struct rfapi_descriptor *rfd) { afi_t afi; /* * NB rfd is typically detached from master list, so is not included * in the count performed by RFAPI_RIB_CHECK_COUNTS */ /* * Free routes attached to radix trees */ rfapiRibClear(rfd); /* Now the uncounted rfapi_info's are freed, so the check should succeed */ RFAPI_RIB_CHECK_COUNTS(1, 0); /* * Free radix trees */ for (afi = AFI_IP; afi < AFI_MAX; ++afi) { if (rfd->rib_pending[afi]) agg_table_finish(rfd->rib_pending[afi]); rfd->rib_pending[afi] = NULL; if (rfd->rib[afi]) agg_table_finish(rfd->rib[afi]); rfd->rib[afi] = NULL; /* NB agg_table_finish frees only prefix nodes, not chained * info */ if (rfd->rsp_times[afi]) agg_table_finish(rfd->rsp_times[afi]); rfd->rib[afi] = NULL; } } /* * Copies struct bgp_path_info to struct rfapi_info, except for rk fields and un */ static void rfapiRibBi2Ri(struct bgp_path_info *bpi, struct rfapi_info *ri, uint32_t lifetime) { struct bgp_attr_encap_subtlv *pEncap; ri->cost = rfapiRfpCost(bpi->attr); ri->lifetime = lifetime; /* This loop based on rfapiRouteInfo2NextHopEntry() */ for (pEncap = bpi->attr->vnc_subtlvs; pEncap; pEncap = pEncap->next) { struct bgp_tea_options *hop; switch (pEncap->type) { case BGP_VNC_SUBTLV_TYPE_LIFETIME: /* use configured lifetime, not attr lifetime */ break; case BGP_VNC_SUBTLV_TYPE_RFPOPTION: hop = XCALLOC(MTYPE_BGP_TEA_OPTIONS, sizeof(struct bgp_tea_options)); assert(hop); hop->type = pEncap->value[0]; hop->length = pEncap->value[1]; hop->value = XCALLOC(MTYPE_BGP_TEA_OPTIONS_VALUE, pEncap->length - 2); assert(hop->value); memcpy(hop->value, pEncap->value + 2, pEncap->length - 2); if (hop->length > pEncap->length - 2) { zlog_warn( "%s: VNC subtlv length mismatch: " "RFP option says %d, attr says %d " "(shrinking)", __func__, hop->length, pEncap->length - 2); hop->length = pEncap->length - 2; } hop->next = ri->tea_options; ri->tea_options = hop; break; default: break; } } rfapi_un_options_free(ri->un_options); /* maybe free old version */ ri->un_options = rfapi_encap_tlv_to_un_option(bpi->attr); /* * VN options */ if (bpi->extra && decode_rd_type(bpi->extra->vnc.import.rd.val) == RD_TYPE_VNC_ETH) { /* ethernet route */ struct rfapi_vn_option *vo; vo = XCALLOC(MTYPE_RFAPI_VN_OPTION, sizeof(struct rfapi_vn_option)); assert(vo); vo->type = RFAPI_VN_OPTION_TYPE_L2ADDR; /* copy from RD already stored in bpi, so we don't need it_node */ memcpy(&vo->v.l2addr.macaddr, bpi->extra->vnc.import.rd.val + 2, ETH_ALEN); (void)rfapiEcommunityGetLNI(bpi->attr->ecommunity, &vo->v.l2addr.logical_net_id); (void)rfapiEcommunityGetEthernetTag(bpi->attr->ecommunity, &vo->v.l2addr.tag_id); /* local_nve_id comes from RD */ vo->v.l2addr.local_nve_id = bpi->extra->vnc.import.rd.val[1]; /* label comes from MP_REACH_NLRI label */ vo->v.l2addr.label = decode_label(&bpi->extra->label[0]); rfapi_vn_options_free( ri->vn_options); /* maybe free old version */ ri->vn_options = vo; } /* * If there is an auxiliary IP address (L2 can have it), copy it */ if (bpi->extra && bpi->extra->vnc.import.aux_prefix.family) { ri->rk.aux_prefix = bpi->extra->vnc.import.aux_prefix; } } /* * rfapiRibPreloadBi * * Install route into NVE RIB model so as to be consistent with * caller's response to rfapi_query(). * * Also: return indication to caller whether this specific route * should be included in the response to the NVE according to * the following tests: * * 1. If there were prior duplicates of this route in this same * query response, don't include the route. * * RETURN VALUE: * * 0 OK to include route in response * !0 do not include route in response */ int rfapiRibPreloadBi( struct agg_node *rfd_rib_node, /* NULL = don't preload or filter */ struct prefix *pfx_vn, struct prefix *pfx_un, uint32_t lifetime, struct bgp_path_info *bpi) { struct rfapi_descriptor *rfd; struct skiplist *slRibPt = NULL; struct rfapi_info *ori = NULL; struct rfapi_rib_key rk; struct agg_node *trn; afi_t afi; if (!rfd_rib_node) return 0; afi = family2afi(rfd_rib_node->p.family); rfd = agg_get_table_info(agg_get_table(rfd_rib_node)); memset((void *)&rk, 0, sizeof(rk)); rk.vn = *pfx_vn; rk.rd = bpi->extra->vnc.import.rd; /* * If there is an auxiliary IP address (L2 can have it), copy it */ if (bpi->extra->vnc.import.aux_prefix.family) { rk.aux_prefix = bpi->extra->vnc.import.aux_prefix; } /* * is this route already in NVE's RIB? */ slRibPt = (struct skiplist *)rfd_rib_node->info; if (slRibPt && !skiplist_search(slRibPt, &rk, (void **)&ori)) { if ((ori->rsp_counter == rfd->rsp_counter) && (ori->last_sent_time == rfd->rsp_time)) { return -1; /* duplicate in this response */ } /* found: update contents of existing route in RIB */ ori->un = *pfx_un; rfapiRibBi2Ri(bpi, ori, lifetime); } else { /* not found: add new route to RIB */ ori = rfapi_info_new(); ori->rk = rk; ori->un = *pfx_un; rfapiRibBi2Ri(bpi, ori, lifetime); if (!slRibPt) { slRibPt = skiplist_new(0, rfapi_rib_key_cmp, NULL); rfd_rib_node->info = slRibPt; agg_lock_node(rfd_rib_node); RFAPI_RIB_PREFIX_COUNT_INCR(rfd, rfd->bgp->rfapi); } skiplist_insert(slRibPt, &ori->rk, ori); } ori->last_sent_time = rfapi_time(NULL); /* * poke timer */ RFAPI_RIB_CHECK_COUNTS(0, 0); rfapiRibStartTimer(rfd, ori, rfd_rib_node, 0); RFAPI_RIB_CHECK_COUNTS(0, 0); /* * Update last sent time for prefix */ trn = agg_node_get(rfd->rsp_times[afi], &rfd_rib_node->p); /* locks trn */ trn->info = (void *)(uintptr_t)bgp_clock(); if (trn->lock > 1) agg_unlock_node(trn); return 0; } /* * Frees rfapi_info items at node * * Adjust 'rib' and 'rib_pending' as follows: * * If rib_pending node->info is 1 (magic value): * callback: NHL = RIB NHL with lifetime = withdraw_lifetime_value * RIB = remove all routes at the node * DONE * * For each item at rib node: * if not present in pending node, move RIB item to "delete list" * * For each item at pending rib node: * if present (same vn/un) in rib node with same lifetime & options, drop * matching item from pending node * * For each remaining item at pending rib node, add or replace item * at rib node. * * Construct NHL as concatenation of pending list + delete list * * Clear pending node */ static void process_pending_node(struct bgp *bgp, struct rfapi_descriptor *rfd, afi_t afi, struct agg_node *pn, /* pending node */ struct rfapi_next_hop_entry **head, struct rfapi_next_hop_entry **tail) { struct listnode *node = NULL; struct listnode *nnode = NULL; struct rfapi_info *ri = NULL; /* happy valgrind */ struct rfapi_ip_prefix hp = {0}; /* pfx to put in NHE */ struct agg_node *rn = NULL; struct skiplist *slRibPt = NULL; /* rib list */ struct skiplist *slPendPt = NULL; struct list *lPendCost = NULL; struct list *delete_list = NULL; int printedprefix = 0; char buf_prefix[PREFIX_STRLEN]; int rib_node_started_nonempty = 0; int sendingsomeroutes = 0; #if DEBUG_PROCESS_PENDING_NODE unsigned int count_rib_initial = 0; unsigned int count_pend_vn_initial = 0; unsigned int count_pend_cost_initial = 0; #endif assert(pn); prefix2str(&pn->p, buf_prefix, sizeof(buf_prefix)); vnc_zlog_debug_verbose("%s: afi=%d, %s pn->info=%p", __func__, afi, buf_prefix, pn->info); if (AFI_L2VPN != afi) { rfapiQprefix2Rprefix(&pn->p, &hp); } RFAPI_RIB_CHECK_COUNTS(1, 0); /* * Find corresponding RIB node */ rn = agg_node_get(rfd->rib[afi], &pn->p); /* locks rn */ /* * RIB skiplist has key=rfapi_addr={vn,un}, val = rfapi_info, * skiplist.del = NULL */ slRibPt = (struct skiplist *)rn->info; if (slRibPt) rib_node_started_nonempty = 1; slPendPt = (struct skiplist *)(pn->aggregate); lPendCost = (struct list *)(pn->info); #if DEBUG_PROCESS_PENDING_NODE /* debugging */ if (slRibPt) count_rib_initial = skiplist_count(slRibPt); if (slPendPt) count_pend_vn_initial = skiplist_count(slPendPt); if (lPendCost && lPendCost != (struct list *)1) count_pend_cost_initial = lPendCost->count; #endif /* * Handle special case: delete all routes at prefix */ if (lPendCost == (struct list *)1) { vnc_zlog_debug_verbose("%s: lPendCost=1 => delete all", __func__); if (slRibPt && !skiplist_empty(slRibPt)) { delete_list = list_new(); while (0 == skiplist_first(slRibPt, NULL, (void **)&ri)) { char buf[PREFIX_STRLEN]; char buf2[PREFIX_STRLEN]; listnode_add(delete_list, ri); vnc_zlog_debug_verbose( "%s: after listnode_add, delete_list->count=%d", __func__, delete_list->count); rfapiFreeBgpTeaOptionChain(ri->tea_options); ri->tea_options = NULL; if (ri->timer) { struct rfapi_rib_tcb *tcb; tcb = ((struct thread *)ri->timer)->arg; thread_cancel(ri->timer); XFREE(MTYPE_RFAPI_RECENT_DELETE, tcb); ri->timer = NULL; } prefix2str(&ri->rk.vn, buf, sizeof(buf)); prefix2str(&ri->un, buf2, sizeof(buf2)); vnc_zlog_debug_verbose( "%s: put dl pfx=%s vn=%s un=%s cost=%d life=%d vn_options=%p", __func__, buf_prefix, buf, buf2, ri->cost, ri->lifetime, ri->vn_options); skiplist_delete_first(slRibPt); } assert(skiplist_empty(slRibPt)); skiplist_free(slRibPt); rn->info = slRibPt = NULL; agg_unlock_node(rn); lPendCost = pn->info = NULL; agg_unlock_node(pn); goto callback; } if (slRibPt) { skiplist_free(slRibPt); rn->info = NULL; agg_unlock_node(rn); } assert(!slPendPt); if (slPendPt) { /* TBD I think we can toss this block */ skiplist_free(slPendPt); pn->aggregate = NULL; agg_unlock_node(pn); } pn->info = NULL; agg_unlock_node(pn); agg_unlock_node(rn); /* agg_node_get() */ if (rib_node_started_nonempty) { RFAPI_RIB_PREFIX_COUNT_DECR(rfd, bgp->rfapi); } RFAPI_RIB_CHECK_COUNTS(1, 0); return; } vnc_zlog_debug_verbose("%s: lPendCost->count=%d, slRibPt->count=%d", __func__, (lPendCost ? (int)lPendCost->count : -1), (slRibPt ? (int)slRibPt->count : -1)); /* * Iterate over routes at RIB Node. * If not found at Pending Node, delete from RIB Node and add to * deletelist * If found at Pending Node * If identical rfapi_info, delete from Pending Node */ if (slRibPt) { void *cursor = NULL; struct rfapi_info *ori; /* * Iterate over RIB List * */ while (!skiplist_next(slRibPt, NULL, (void **)&ori, &cursor)) { if (skiplist_search(slPendPt, &ori->rk, (void **)&ri)) { /* * Not in Pending list, so it should be deleted */ if (!delete_list) delete_list = list_new(); listnode_add(delete_list, ori); rfapiFreeBgpTeaOptionChain(ori->tea_options); ori->tea_options = NULL; if (ori->timer) { struct rfapi_rib_tcb *tcb; tcb = ((struct thread *)ori->timer) ->arg; thread_cancel(ori->timer); XFREE(MTYPE_RFAPI_RECENT_DELETE, tcb); ori->timer = NULL; } #if DEBUG_PROCESS_PENDING_NODE /* deleted from slRibPt below, after we're done * iterating */ vnc_zlog_debug_verbose( "%s: slRibPt ri %p not matched in pending list, delete", __func__, ori); #endif } else { /* * Found in pending list. If same lifetime, * cost, options, * then remove from pending list because the * route * hasn't changed. */ if (!rfapi_info_cmp(ori, ri)) { skiplist_delete(slPendPt, &ri->rk, NULL); assert(lPendCost); if (lPendCost) { /* linear walk: might need * optimization */ listnode_delete(lPendCost, ri); /* XXX doesn't free data! bug? */ rfapi_info_free( ri); /* grr... */ } } #if DEBUG_PROCESS_PENDING_NODE vnc_zlog_debug_verbose( "%s: slRibPt ri %p matched in pending list, %s", __func__, ori, (same ? "same info" : "different info")); #endif } } /* * Go back and delete items from RIB */ if (delete_list) { for (ALL_LIST_ELEMENTS_RO(delete_list, node, ri)) { vnc_zlog_debug_verbose( "%s: deleting ri %p from slRibPt", __func__, ri); assert(!skiplist_delete(slRibPt, &ri->rk, NULL)); } if (skiplist_empty(slRibPt)) { skiplist_free(slRibPt); slRibPt = rn->info = NULL; agg_unlock_node(rn); } } } RFAPI_RIB_CHECK_COUNTS(0, (delete_list ? delete_list->count : 0)); /* * Iterate over routes at Pending Node * * If {vn} found at RIB Node, update RIB Node route contents to match PN * If {vn} NOT found at RIB Node, add copy to RIB Node */ if (lPendCost) { for (ALL_LIST_ELEMENTS_RO(lPendCost, node, ri)) { struct rfapi_info *ori; if (slRibPt && !skiplist_search(slRibPt, &ri->rk, (void **)&ori)) { /* found: update contents of existing route in * RIB */ ori->un = ri->un; ori->cost = ri->cost; ori->lifetime = ri->lifetime; rfapiFreeBgpTeaOptionChain(ori->tea_options); ori->tea_options = rfapiOptionsDup(ri->tea_options); ori->last_sent_time = rfapi_time(NULL); rfapiFreeRfapiVnOptionChain(ori->vn_options); ori->vn_options = rfapiVnOptionsDup(ri->vn_options); rfapiFreeRfapiUnOptionChain(ori->un_options); ori->un_options = rfapiUnOptionsDup(ri->un_options); vnc_zlog_debug_verbose( "%s: matched lPendCost item %p in slRibPt, rewrote", __func__, ri); } else { char buf_rd[RD_ADDRSTRLEN]; /* not found: add new route to RIB */ ori = rfapi_info_new(); ori->rk = ri->rk; ori->un = ri->un; ori->cost = ri->cost; ori->lifetime = ri->lifetime; ori->tea_options = rfapiOptionsDup(ri->tea_options); ori->last_sent_time = rfapi_time(NULL); ori->vn_options = rfapiVnOptionsDup(ri->vn_options); ori->un_options = rfapiUnOptionsDup(ri->un_options); if (!slRibPt) { slRibPt = skiplist_new( 0, rfapi_rib_key_cmp, NULL); rn->info = slRibPt; agg_lock_node(rn); } skiplist_insert(slRibPt, &ori->rk, ori); #if DEBUG_RIB_SL_RD prefix_rd2str(&ori->rk.rd, buf_rd, sizeof(buf_rd)); #else buf_rd[0] = 0; #endif vnc_zlog_debug_verbose( "%s: nomatch lPendCost item %p in slRibPt, added (rd=%s)", __func__, ri, buf_rd); } /* * poke timer */ RFAPI_RIB_CHECK_COUNTS( 0, (delete_list ? delete_list->count : 0)); rfapiRibStartTimer(rfd, ori, rn, 0); RFAPI_RIB_CHECK_COUNTS( 0, (delete_list ? delete_list->count : 0)); } } callback: /* * Construct NHL as concatenation of pending list + delete list */ RFAPI_RIB_CHECK_COUNTS(0, (delete_list ? delete_list->count : 0)); if (lPendCost) { char buf[BUFSIZ]; char buf2[BUFSIZ]; vnc_zlog_debug_verbose("%s: lPendCost->count now %d", __func__, lPendCost->count); vnc_zlog_debug_verbose("%s: For prefix %s (a)", __func__, buf_prefix); printedprefix = 1; for (ALL_LIST_ELEMENTS(lPendCost, node, nnode, ri)) { struct rfapi_next_hop_entry *new; struct agg_node *trn; new = XCALLOC(MTYPE_RFAPI_NEXTHOP, sizeof(struct rfapi_next_hop_entry)); assert(new); if (ri->rk.aux_prefix.family) { rfapiQprefix2Rprefix(&ri->rk.aux_prefix, &new->prefix); } else { new->prefix = hp; if (AFI_L2VPN == afi) { /* hp is 0; need to set length to match * AF of vn */ new->prefix.length = (ri->rk.vn.family == AF_INET) ? 32 : 128; } } new->prefix.cost = ri->cost; new->lifetime = ri->lifetime; rfapiQprefix2Raddr(&ri->rk.vn, &new->vn_address); rfapiQprefix2Raddr(&ri->un, &new->un_address); /* free option chain from ri */ rfapiFreeBgpTeaOptionChain(ri->tea_options); ri->tea_options = NULL; /* option chain was transferred to NHL */ new->vn_options = ri->vn_options; ri->vn_options = NULL; /* option chain was transferred to NHL */ new->un_options = ri->un_options; ri->un_options = NULL; /* option chain was transferred to NHL */ if (*tail) (*tail)->next = new; *tail = new; if (!*head) { *head = new; } sendingsomeroutes = 1; ++rfd->stat_count_nh_reachable; ++bgp->rfapi->stat.count_updated_response_updates; /* * update this NVE's timestamp for this prefix */ trn = agg_node_get(rfd->rsp_times[afi], &pn->p); /* locks trn */ trn->info = (void *)(uintptr_t)bgp_clock(); if (trn->lock > 1) agg_unlock_node(trn); rfapiRfapiIpAddr2Str(&new->vn_address, buf, BUFSIZ); rfapiRfapiIpAddr2Str(&new->un_address, buf2, BUFSIZ); vnc_zlog_debug_verbose( "%s: add vn=%s un=%s cost=%d life=%d", __func__, buf, buf2, new->prefix.cost, new->lifetime); } } RFAPI_RIB_CHECK_COUNTS(0, (delete_list ? delete_list->count : 0)); if (delete_list) { char buf[BUFSIZ]; char buf2[BUFSIZ]; if (!printedprefix) { vnc_zlog_debug_verbose("%s: For prefix %s (d)", __func__, buf_prefix); } vnc_zlog_debug_verbose("%s: delete_list has %d elements", __func__, delete_list->count); RFAPI_RIB_CHECK_COUNTS(0, delete_list->count); if (!CHECK_FLAG(bgp->rfapi_cfg->flags, BGP_VNC_CONFIG_RESPONSE_REMOVAL_DISABLE)) { for (ALL_LIST_ELEMENTS(delete_list, node, nnode, ri)) { struct rfapi_next_hop_entry *new; struct rfapi_info *ri_del; RFAPI_RIB_CHECK_COUNTS(0, delete_list->count); new = XCALLOC( MTYPE_RFAPI_NEXTHOP, sizeof(struct rfapi_next_hop_entry)); assert(new); if (ri->rk.aux_prefix.family) { rfapiQprefix2Rprefix(&ri->rk.aux_prefix, &new->prefix); } else { new->prefix = hp; if (AFI_L2VPN == afi) { /* hp is 0; need to set length * to match AF of vn */ new->prefix.length = (ri->rk.vn.family == AF_INET) ? 32 : 128; } } new->prefix.cost = ri->cost; new->lifetime = RFAPI_REMOVE_RESPONSE_LIFETIME; rfapiQprefix2Raddr(&ri->rk.vn, &new->vn_address); rfapiQprefix2Raddr(&ri->un, &new->un_address); new->vn_options = ri->vn_options; ri->vn_options = NULL; /* option chain was transferred to NHL */ new->un_options = ri->un_options; ri->un_options = NULL; /* option chain was transferred to NHL */ if (*tail) (*tail)->next = new; *tail = new; if (!*head) { *head = new; } ++rfd->stat_count_nh_removal; ++bgp->rfapi->stat .count_updated_response_deletes; rfapiRfapiIpAddr2Str(&new->vn_address, buf, BUFSIZ); rfapiRfapiIpAddr2Str(&new->un_address, buf2, BUFSIZ); vnc_zlog_debug_verbose( "%s: DEL vn=%s un=%s cost=%d life=%d", __func__, buf, buf2, new->prefix.cost, new->lifetime); RFAPI_RIB_CHECK_COUNTS(0, delete_list->count); /* * Update/add to list of recent deletions at * this prefix */ if (!rn->aggregate) { rn->aggregate = skiplist_new( 0, rfapi_rib_key_cmp, (void (*)(void *)) rfapi_info_free); agg_lock_node(rn); } RFAPI_RIB_CHECK_COUNTS(0, delete_list->count); /* sanity check lifetime */ if (ri->lifetime > RFAPI_LIFETIME_INFINITE_WITHDRAW_DELAY) ri->lifetime = RFAPI_LIFETIME_INFINITE_WITHDRAW_DELAY; RFAPI_RIB_CHECK_COUNTS(0, delete_list->count); /* cancel normal expire timer */ if (ri->timer) { struct rfapi_rib_tcb *tcb; tcb = ((struct thread *)ri->timer)->arg; thread_cancel( (struct thread *)ri->timer); XFREE(MTYPE_RFAPI_RECENT_DELETE, tcb); ri->timer = NULL; } RFAPI_RIB_CHECK_COUNTS(0, delete_list->count); /* * Look in "recently-deleted" list */ if (skiplist_search( (struct skiplist *)(rn->aggregate), &ri->rk, (void **)&ri_del)) { int rc; RFAPI_RIB_CHECK_COUNTS( 0, delete_list->count); /* * NOT in "recently-deleted" list */ list_delete_node( delete_list, node); /* does not free ri */ rc = skiplist_insert( (struct skiplist *)(rn->aggregate), &ri->rk, ri); assert(!rc); RFAPI_RIB_CHECK_COUNTS( 0, delete_list->count); rfapiRibStartTimer(rfd, ri, rn, 1); RFAPI_RIB_CHECK_COUNTS( 0, delete_list->count); ri->last_sent_time = rfapi_time(NULL); #if DEBUG_RIB_SL_RD { char buf_rd[RD_ADDRSTRLEN]; vnc_zlog_debug_verbose( "%s: move route to recently deleted list, rd=%s", __func__, prefix_rd2str( &ri->rk.rd, buf_rd, sizeof(buf_rd))); } #endif } else { /* * IN "recently-deleted" list */ RFAPI_RIB_CHECK_COUNTS( 0, delete_list->count); rfapiRibStartTimer(rfd, ri_del, rn, 1); RFAPI_RIB_CHECK_COUNTS( 0, delete_list->count); ri->last_sent_time = rfapi_time(NULL); } } } else { vnc_zlog_debug_verbose( "%s: response removal disabled, omitting removals", __func__); } delete_list->del = (void (*)(void *))rfapi_info_free; list_delete(&delete_list); } RFAPI_RIB_CHECK_COUNTS(0, 0); /* * Reset pending lists. The final agg_unlock_node() will probably * cause the pending node to be released. */ if (slPendPt) { skiplist_free(slPendPt); pn->aggregate = NULL; agg_unlock_node(pn); } if (lPendCost) { list_delete(&lPendCost); pn->info = NULL; agg_unlock_node(pn); } RFAPI_RIB_CHECK_COUNTS(0, 0); if (rib_node_started_nonempty) { if (!rn->info) { RFAPI_RIB_PREFIX_COUNT_DECR(rfd, bgp->rfapi); } } else { if (rn->info) { RFAPI_RIB_PREFIX_COUNT_INCR(rfd, bgp->rfapi); } } if (sendingsomeroutes) rfapiMonitorTimersRestart(rfd, &pn->p); agg_unlock_node(rn); /* agg_node_get() */ RFAPI_RIB_CHECK_COUNTS(1, 0); } /* * regardless of targets, construct a single callback by doing * only one traversal of the pending RIB * * * Do callback * */ static void rib_do_callback_onepass(struct rfapi_descriptor *rfd, afi_t afi) { struct bgp *bgp = bgp_get_default(); struct rfapi_next_hop_entry *head = NULL; struct rfapi_next_hop_entry *tail = NULL; struct agg_node *rn; #if DEBUG_L2_EXTRA vnc_zlog_debug_verbose("%s: rfd=%p, afi=%d", __func__, rfd, afi); #endif if (!rfd->rib_pending[afi]) return; assert(bgp->rfapi); for (rn = agg_route_top(rfd->rib_pending[afi]); rn; rn = agg_route_next(rn)) { process_pending_node(bgp, rfd, afi, rn, &head, &tail); } if (head) { rfapi_response_cb_t *f; #if DEBUG_NHL vnc_zlog_debug_verbose("%s: response callback NHL follows:", __func__); rfapiPrintNhl(NULL, head); #endif if (rfd->response_cb) f = rfd->response_cb; else f = bgp->rfapi->rfp_methods.response_cb; bgp->rfapi->flags |= RFAPI_INCALLBACK; vnc_zlog_debug_verbose("%s: invoking updated response callback", __func__); (*f)(head, rfd->cookie); bgp->rfapi->flags &= ~RFAPI_INCALLBACK; ++bgp->rfapi->response_updated_count; } } static wq_item_status rfapiRibDoQueuedCallback(struct work_queue *wq, void *data) { struct rfapi_descriptor *rfd; afi_t afi; uint32_t queued_flag; RFAPI_RIB_CHECK_COUNTS(1, 0); rfd = ((struct rfapi_updated_responses_queue *)data)->rfd; afi = ((struct rfapi_updated_responses_queue *)data)->afi; /* Make sure the HD wasn't closed after the work item was scheduled */ if (rfapi_check(rfd)) return WQ_SUCCESS; rib_do_callback_onepass(rfd, afi); queued_flag = RFAPI_QUEUED_FLAG(afi); UNSET_FLAG(rfd->flags, queued_flag); RFAPI_RIB_CHECK_COUNTS(1, 0); return WQ_SUCCESS; } static void rfapiRibQueueItemDelete(struct work_queue *wq, void *data) { XFREE(MTYPE_RFAPI_UPDATED_RESPONSE_QUEUE, data); } static void updated_responses_queue_init(struct rfapi_descriptor *rfd) { if (rfd->updated_responses_queue) return; rfd->updated_responses_queue = work_queue_new(bm->master, "rfapi updated responses"); assert(rfd->updated_responses_queue); rfd->updated_responses_queue->spec.workfunc = rfapiRibDoQueuedCallback; rfd->updated_responses_queue->spec.del_item_data = rfapiRibQueueItemDelete; rfd->updated_responses_queue->spec.max_retries = 0; rfd->updated_responses_queue->spec.hold = 1; } /* * Called when an import table node is modified. Construct a * new complete nexthop list, sorted by cost (lowest first), * based on the import table node. * * Filter out duplicate nexthops (vn address). There should be * only one UN address per VN address from the point of view of * a given import table, so we can probably ignore UN addresses * while filtering. * * Based on rfapiNhlAddNodeRoutes() */ void rfapiRibUpdatePendingNode( struct bgp *bgp, struct rfapi_descriptor *rfd, struct rfapi_import_table *it, /* needed for L2 */ struct agg_node *it_node, uint32_t lifetime) { struct prefix *prefix; struct bgp_path_info *bpi; struct agg_node *pn; afi_t afi; uint32_t queued_flag; int count = 0; char buf[PREFIX_STRLEN]; vnc_zlog_debug_verbose("%s: entry", __func__); if (CHECK_FLAG(bgp->rfapi_cfg->flags, BGP_VNC_CONFIG_CALLBACK_DISABLE)) return; vnc_zlog_debug_verbose("%s: callbacks are not disabled", __func__); RFAPI_RIB_CHECK_COUNTS(1, 0); prefix = &it_node->p; afi = family2afi(prefix->family); prefix2str(prefix, buf, sizeof(buf)); vnc_zlog_debug_verbose("%s: prefix=%s", __func__, buf); pn = agg_node_get(rfd->rib_pending[afi], prefix); assert(pn); vnc_zlog_debug_verbose("%s: pn->info=%p, pn->aggregate=%p", __func__, pn->info, pn->aggregate); if (pn->aggregate) { /* * free references into the rfapi_info structures before * freeing the structures themselves */ skiplist_free((struct skiplist *)(pn->aggregate)); pn->aggregate = NULL; agg_unlock_node(pn); /* skiplist deleted */ } /* * free the rfapi_info structures */ if (pn->info) { if (pn->info != (void *)1) { list_delete((struct list **)(&pn->info)); } pn->info = NULL; agg_unlock_node(pn); /* linklist or 1 deleted */ } /* * The BPIs in the import table are already sorted by cost */ for (bpi = it_node->info; bpi; bpi = bpi->next) { struct rfapi_info *ri; struct prefix pfx_nh; if (!bpi->attr) { /* shouldn't happen */ /* TBD increment error stats counter */ continue; } if (!bpi->extra) { /* shouldn't happen */ /* TBD increment error stats counter */ continue; } rfapiNexthop2Prefix(bpi->attr, &pfx_nh); /* * Omit route if nexthop is self */ if (CHECK_FLAG(bgp->rfapi_cfg->flags, BGP_VNC_CONFIG_FILTER_SELF_FROM_RSP)) { struct prefix pfx_vn; assert(!rfapiRaddr2Qprefix(&rfd->vn_addr, &pfx_vn)); if (prefix_same(&pfx_vn, &pfx_nh)) continue; } ri = rfapi_info_new(); ri->rk.vn = pfx_nh; ri->rk.rd = bpi->extra->vnc.import.rd; /* * If there is an auxiliary IP address (L2 can have it), copy it */ if (bpi->extra->vnc.import.aux_prefix.family) { ri->rk.aux_prefix = bpi->extra->vnc.import.aux_prefix; } if (rfapiGetUnAddrOfVpnBi(bpi, &ri->un)) { rfapi_info_free(ri); continue; } if (!pn->aggregate) { pn->aggregate = skiplist_new(0, rfapi_rib_key_cmp, NULL); agg_lock_node(pn); } /* * If we have already added this nexthop, the insert will fail. * Note that the skiplist key is a pointer INTO the rfapi_info * structure which will be added to the "info" list. * The skiplist entry VALUE is not used for anything but * might be useful during debugging. */ if (skiplist_insert((struct skiplist *)pn->aggregate, &ri->rk, ri)) { /* * duplicate */ rfapi_info_free(ri); continue; } rfapiRibBi2Ri(bpi, ri, lifetime); if (!pn->info) { pn->info = list_new(); ((struct list *)(pn->info))->del = (void (*)(void *))rfapi_info_free; agg_lock_node(pn); } listnode_add((struct list *)(pn->info), ri); } if (pn->info) { count = ((struct list *)(pn->info))->count; } if (!count) { assert(!pn->info); assert(!pn->aggregate); pn->info = (void *)1; /* magic value means this node has no routes */ agg_lock_node(pn); } agg_unlock_node(pn); /* agg_node_get */ queued_flag = RFAPI_QUEUED_FLAG(afi); if (!CHECK_FLAG(rfd->flags, queued_flag)) { struct rfapi_updated_responses_queue *urq; urq = XCALLOC(MTYPE_RFAPI_UPDATED_RESPONSE_QUEUE, sizeof(struct rfapi_updated_responses_queue)); assert(urq); if (!rfd->updated_responses_queue) updated_responses_queue_init(rfd); SET_FLAG(rfd->flags, queued_flag); urq->rfd = rfd; urq->afi = afi; work_queue_add(rfd->updated_responses_queue, urq); } RFAPI_RIB_CHECK_COUNTS(1, 0); } void rfapiRibUpdatePendingNodeSubtree( struct bgp *bgp, struct rfapi_descriptor *rfd, struct rfapi_import_table *it, struct agg_node *it_node, struct agg_node *omit_subtree, /* may be NULL */ uint32_t lifetime) { /* FIXME: need to find a better way here to work without sticking our * hands in node->link */ if (agg_node_left(it_node) && (agg_node_left(it_node) != omit_subtree)) { if (agg_node_left(it_node)->info) rfapiRibUpdatePendingNode( bgp, rfd, it, agg_node_left(it_node), lifetime); rfapiRibUpdatePendingNodeSubtree(bgp, rfd, it, agg_node_left(it_node), omit_subtree, lifetime); } if (agg_node_right(it_node) && (agg_node_right(it_node) != omit_subtree)) { if (agg_node_right(it_node)->info) rfapiRibUpdatePendingNode(bgp, rfd, it, agg_node_right(it_node), lifetime); rfapiRibUpdatePendingNodeSubtree(bgp, rfd, it, agg_node_right(it_node), omit_subtree, lifetime); } } /* * RETURN VALUE * * 0 allow prefix to be included in response * !0 don't allow prefix to be included in response */ int rfapiRibFTDFilterRecentPrefix( struct rfapi_descriptor *rfd, struct agg_node *it_rn, /* import table node */ struct prefix *pfx_target_original) /* query target */ { struct bgp *bgp = rfd->bgp; afi_t afi = family2afi(it_rn->p.family); time_t prefix_time; struct agg_node *trn; /* * Not in FTD mode, so allow prefix */ if (bgp->rfapi_cfg->rfp_cfg.download_type != RFAPI_RFP_DOWNLOAD_FULL) return 0; /* * TBD * This matches behavior of now-obsolete rfapiRibFTDFilterRecent(), * but we need to decide if that is correct. */ if (it_rn->p.family == AF_ETHERNET) return 0; #if DEBUG_FTD_FILTER_RECENT { char buf_pfx[PREFIX_STRLEN]; prefix2str(&it_rn->p, buf_pfx, sizeof(buf_pfx)); vnc_zlog_debug_verbose("%s: prefix %s", __func__, buf_pfx); } #endif /* * prefix covers target address, so allow prefix */ if (prefix_match(&it_rn->p, pfx_target_original)) { #if DEBUG_FTD_FILTER_RECENT vnc_zlog_debug_verbose("%s: prefix covers target, allowed", __func__); #endif return 0; } /* * check this NVE's timestamp for this prefix */ trn = agg_node_get(rfd->rsp_times[afi], &it_rn->p); /* locks trn */ prefix_time = (time_t)trn->info; if (trn->lock > 1) agg_unlock_node(trn); #if DEBUG_FTD_FILTER_RECENT vnc_zlog_debug_verbose("%s: last sent time %lu, last allowed time %lu", __func__, prefix_time, rfd->ftd_last_allowed_time); #endif /* * haven't sent this prefix, which doesn't cover target address, * to NVE since ftd_advertisement_interval, so OK to send now. */ if (prefix_time <= rfd->ftd_last_allowed_time) return 0; return 1; } /* * Call when rfapi returns from rfapi_query() so the RIB reflects * the routes sent to the NVE before the first updated response * * Also: remove duplicates from response. Caller should use returned * value of nexthop chain. */ struct rfapi_next_hop_entry * rfapiRibPreload(struct bgp *bgp, struct rfapi_descriptor *rfd, struct rfapi_next_hop_entry *response, int use_eth_resolution) { struct rfapi_next_hop_entry *nhp; struct rfapi_next_hop_entry *nhp_next; struct rfapi_next_hop_entry *head = NULL; struct rfapi_next_hop_entry *tail = NULL; time_t new_last_sent_time; vnc_zlog_debug_verbose("%s: loading response=%p, use_eth_resolution=%d", __func__, response, use_eth_resolution); new_last_sent_time = rfapi_time(NULL); for (nhp = response; nhp; nhp = nhp_next) { struct prefix pfx; struct rfapi_rib_key rk; afi_t afi; struct rfapi_info *ri; int need_insert; struct agg_node *rn; int rib_node_started_nonempty = 0; struct agg_node *trn; int allowed = 0; /* save in case we delete nhp */ nhp_next = nhp->next; if (nhp->lifetime == RFAPI_REMOVE_RESPONSE_LIFETIME) { /* * weird, shouldn't happen */ vnc_zlog_debug_verbose( "%s: got nhp->lifetime == RFAPI_REMOVE_RESPONSE_LIFETIME", __func__); continue; } if (use_eth_resolution) { /* get the prefix of the ethernet address in the L2 * option */ struct rfapi_l2address_option *pL2o; struct rfapi_vn_option *vo; /* * Look for VN option of type * RFAPI_VN_OPTION_TYPE_L2ADDR */ for (pL2o = NULL, vo = nhp->vn_options; vo; vo = vo->next) { if (RFAPI_VN_OPTION_TYPE_L2ADDR == vo->type) { pL2o = &vo->v.l2addr; break; } } if (!pL2o) { /* * not supposed to happen */ vnc_zlog_debug_verbose("%s: missing L2 info", __func__); continue; } afi = AFI_L2VPN; rfapiL2o2Qprefix(pL2o, &pfx); } else { rfapiRprefix2Qprefix(&nhp->prefix, &pfx); afi = family2afi(pfx.family); } /* * TBD for ethernet, rib must know the right way to distinguish * duplicate routes * * Current approach: prefix is key to radix tree; then * each prefix has a set of routes with unique VN addrs */ /* * Look up prefix in RIB */ rn = agg_node_get(rfd->rib[afi], &pfx); /* locks rn */ if (rn->info) { rib_node_started_nonempty = 1; } else { rn->info = skiplist_new(0, rfapi_rib_key_cmp, NULL); agg_lock_node(rn); } /* * Look up route at prefix */ need_insert = 0; memset((void *)&rk, 0, sizeof(rk)); assert(!rfapiRaddr2Qprefix(&nhp->vn_address, &rk.vn)); if (use_eth_resolution) { /* copy what came from aux_prefix to rk.aux_prefix */ rfapiRprefix2Qprefix(&nhp->prefix, &rk.aux_prefix); if (RFAPI_0_PREFIX(&rk.aux_prefix) && RFAPI_HOST_PREFIX(&rk.aux_prefix)) { /* mark as "none" if nhp->prefix is 0/32 or * 0/128 */ rk.aux_prefix.family = 0; } } #if DEBUG_NHL { char str_vn[PREFIX_STRLEN]; char str_aux_prefix[PREFIX_STRLEN]; str_vn[0] = 0; str_aux_prefix[0] = 0; prefix2str(&rk.vn, str_vn, sizeof(str_vn)); prefix2str(&rk.aux_prefix, str_aux_prefix, sizeof(str_aux_prefix)); if (!rk.aux_prefix.family) { } vnc_zlog_debug_verbose( "%s: rk.vn=%s rk.aux_prefix=%s", __func__, str_vn, (rk.aux_prefix.family ? str_aux_prefix : "-")); } vnc_zlog_debug_verbose( "%s: RIB skiplist for this prefix follows", __func__); rfapiRibShowRibSl(NULL, &rn->p, (struct skiplist *)rn->info); #endif if (!skiplist_search((struct skiplist *)rn->info, &rk, (void **)&ri)) { /* * Already have this route; make values match */ rfapiFreeRfapiUnOptionChain(ri->un_options); ri->un_options = NULL; rfapiFreeRfapiVnOptionChain(ri->vn_options); ri->vn_options = NULL; #if DEBUG_NHL vnc_zlog_debug_verbose("%s: found in RIB", __func__); #endif /* * Filter duplicate routes from initial response. * Check timestamps to avoid wraparound problems */ if ((ri->rsp_counter != rfd->rsp_counter) || (ri->last_sent_time != new_last_sent_time)) { #if DEBUG_NHL vnc_zlog_debug_verbose( "%s: allowed due to counter/timestamp diff", __func__); #endif allowed = 1; } } else { #if DEBUG_NHL vnc_zlog_debug_verbose( "%s: allowed due to not yet in RIB", __func__); #endif /* not found: add new route to RIB */ ri = rfapi_info_new(); need_insert = 1; allowed = 1; } ri->rk = rk; assert(!rfapiRaddr2Qprefix(&nhp->un_address, &ri->un)); ri->cost = nhp->prefix.cost; ri->lifetime = nhp->lifetime; ri->vn_options = rfapiVnOptionsDup(nhp->vn_options); ri->rsp_counter = rfd->rsp_counter; ri->last_sent_time = rfapi_time(NULL); if (need_insert) { int rc; rc = skiplist_insert((struct skiplist *)rn->info, &ri->rk, ri); assert(!rc); } if (!rib_node_started_nonempty) { RFAPI_RIB_PREFIX_COUNT_INCR(rfd, bgp->rfapi); } RFAPI_RIB_CHECK_COUNTS(0, 0); rfapiRibStartTimer(rfd, ri, rn, 0); RFAPI_RIB_CHECK_COUNTS(0, 0); agg_unlock_node(rn); /* * update this NVE's timestamp for this prefix */ trn = agg_node_get(rfd->rsp_times[afi], &pfx); /* locks trn */ trn->info = (void *)(uintptr_t)bgp_clock(); if (trn->lock > 1) agg_unlock_node(trn); { char str_pfx[PREFIX_STRLEN]; char str_pfx_vn[PREFIX_STRLEN]; prefix2str(&pfx, str_pfx, sizeof(str_pfx)); prefix2str(&rk.vn, str_pfx_vn, sizeof(str_pfx_vn)); vnc_zlog_debug_verbose( "%s: added pfx=%s nh[vn]=%s, cost=%u, lifetime=%u, allowed=%d", __func__, str_pfx, str_pfx_vn, nhp->prefix.cost, nhp->lifetime, allowed); } if (allowed) { if (tail) (tail)->next = nhp; tail = nhp; if (!head) { head = nhp; } } else { rfapi_un_options_free(nhp->un_options); nhp->un_options = NULL; rfapi_vn_options_free(nhp->vn_options); nhp->vn_options = NULL; XFREE(MTYPE_RFAPI_NEXTHOP, nhp); } } if (tail) tail->next = NULL; return head; } void rfapiRibPendingDeleteRoute(struct bgp *bgp, struct rfapi_import_table *it, afi_t afi, struct agg_node *it_node) { struct rfapi_descriptor *rfd; struct listnode *node; char buf[PREFIX_STRLEN]; prefix2str(&it_node->p, buf, sizeof(buf)); vnc_zlog_debug_verbose("%s: entry, it=%p, afi=%d, it_node=%p, pfx=%s", __func__, it, afi, it_node, buf); if (AFI_L2VPN == afi) { /* * ethernet import tables are per-LNI and each ethernet monitor * identifies the rfd that owns it. */ struct rfapi_monitor_eth *m; struct agg_node *rn; struct skiplist *sl; void *cursor; int rc; /* * route-specific monitors */ if ((sl = RFAPI_MONITOR_ETH(it_node))) { vnc_zlog_debug_verbose( "%s: route-specific skiplist: %p", __func__, sl); for (cursor = NULL, rc = skiplist_next(sl, NULL, (void **)&m, (void **)&cursor); !rc; rc = skiplist_next(sl, NULL, (void **)&m, (void **)&cursor)) { #if DEBUG_PENDING_DELETE_ROUTE vnc_zlog_debug_verbose("%s: eth monitor rfd=%p", __func__, m->rfd); #endif /* * If we have already sent a route with this * prefix to this * NVE, it's OK to send an update with the * delete */ if ((rn = agg_node_lookup(m->rfd->rib[afi], &it_node->p))) { rfapiRibUpdatePendingNode( bgp, m->rfd, it, it_node, m->rfd->response_lifetime); agg_unlock_node(rn); } } } /* * all-routes/FTD monitors */ for (m = it->eth0_queries; m; m = m->next) { #if DEBUG_PENDING_DELETE_ROUTE vnc_zlog_debug_verbose("%s: eth0 monitor rfd=%p", __func__, m->rfd); #endif /* * If we have already sent a route with this prefix to * this * NVE, it's OK to send an update with the delete */ if ((rn = agg_node_lookup(m->rfd->rib[afi], &it_node->p))) { rfapiRibUpdatePendingNode( bgp, m->rfd, it, it_node, m->rfd->response_lifetime); } } } else { /* * Find RFDs that reference this import table */ for (ALL_LIST_ELEMENTS_RO(&bgp->rfapi->descriptors, node, rfd)) { struct agg_node *rn; vnc_zlog_debug_verbose( "%s: comparing rfd(%p)->import_table=%p to it=%p", __func__, rfd, rfd->import_table, it); if (rfd->import_table != it) continue; vnc_zlog_debug_verbose("%s: matched rfd %p", __func__, rfd); /* * If we have sent a response to this NVE with this * prefix * previously, we should send an updated response. */ if ((rn = agg_node_lookup(rfd->rib[afi], &it_node->p))) { rfapiRibUpdatePendingNode( bgp, rfd, it, it_node, rfd->response_lifetime); agg_unlock_node(rn); } } } } void rfapiRibShowResponsesSummary(void *stream) { int (*fp)(void *, const char *, ...); struct vty *vty; void *out; const char *vty_newline; struct bgp *bgp = bgp_get_default(); int nves = 0; int nves_with_nonempty_ribs = 0; struct rfapi_descriptor *rfd; struct listnode *node; if (rfapiStream2Vty(stream, &fp, &vty, &out, &vty_newline) == 0) return; if (!bgp) { fp(out, "Unable to find default BGP instance\n"); return; } fp(out, "%-24s ", "Responses: (Prefixes)"); fp(out, "%-8s %-8u ", "Active:", bgp->rfapi->rib_prefix_count_total); fp(out, "%-8s %-8u", "Maximum:", bgp->rfapi->rib_prefix_count_total_max); fp(out, "\n"); fp(out, "%-24s ", " (Updated)"); fp(out, "%-8s %-8u ", "Update:", bgp->rfapi->stat.count_updated_response_updates); fp(out, "%-8s %-8u", "Remove:", bgp->rfapi->stat.count_updated_response_deletes); fp(out, "%-8s %-8u", "Total:", bgp->rfapi->stat.count_updated_response_updates + bgp->rfapi->stat.count_updated_response_deletes); fp(out, "\n"); fp(out, "%-24s ", " (NVEs)"); for (ALL_LIST_ELEMENTS_RO(&bgp->rfapi->descriptors, node, rfd)) { ++nves; if (rfd->rib_prefix_count) ++nves_with_nonempty_ribs; } fp(out, "%-8s %-8u ", "Active:", nves_with_nonempty_ribs); fp(out, "%-8s %-8u", "Total:", nves); fp(out, "\n"); } void rfapiRibShowResponsesSummaryClear(void) { struct bgp *bgp = bgp_get_default(); bgp->rfapi->rib_prefix_count_total_max = bgp->rfapi->rib_prefix_count_total; } static int print_rib_sl(int (*fp)(void *, const char *, ...), struct vty *vty, void *out, struct skiplist *sl, int deleted, char *str_pfx, int *printedprefix) { struct rfapi_info *ri; int rc; void *cursor; int routes_displayed = 0; cursor = NULL; for (rc = skiplist_next(sl, NULL, (void **)&ri, &cursor); !rc; rc = skiplist_next(sl, NULL, (void **)&ri, &cursor)) { char str_vn[PREFIX_STRLEN]; char str_un[PREFIX_STRLEN]; char str_lifetime[BUFSIZ]; char str_age[BUFSIZ]; char *p; char str_rd[RD_ADDRSTRLEN]; ++routes_displayed; prefix2str(&ri->rk.vn, str_vn, sizeof(str_vn)); p = index(str_vn, '/'); if (p) *p = 0; prefix2str(&ri->un, str_un, sizeof(str_un)); p = index(str_un, '/'); if (p) *p = 0; rfapiFormatSeconds(ri->lifetime, str_lifetime, BUFSIZ); #if RFAPI_REGISTRATIONS_REPORT_AGE rfapiFormatAge(ri->last_sent_time, str_age, BUFSIZ); #else { time_t now = rfapi_time(NULL); time_t expire = ri->last_sent_time + (time_t)ri->lifetime; /* allow for delayed/async removal */ rfapiFormatSeconds((expire > now ? expire - now : 1), str_age, BUFSIZ); } #endif str_rd[0] = 0; /* start empty */ #if DEBUG_RIB_SL_RD prefix_rd2str(&ri->rk.rd, str_rd, sizeof(str_rd)); #endif fp(out, " %c %-20s %-15s %-15s %-4u %-8s %-8s %s\n", deleted ? 'r' : ' ', *printedprefix ? "" : str_pfx, str_vn, str_un, ri->cost, str_lifetime, str_age, str_rd); if (!*printedprefix) *printedprefix = 1; } return routes_displayed; } #if DEBUG_NHL /* * This one is for debugging (set stream to NULL to send output to log) */ static void rfapiRibShowRibSl(void *stream, struct prefix *pfx, struct skiplist *sl) { int (*fp)(void *, const char *, ...); struct vty *vty; void *out; const char *vty_newline; int nhs_displayed = 0; char str_pfx[PREFIX_STRLEN]; int printedprefix = 0; if (rfapiStream2Vty(stream, &fp, &vty, &out, &vty_newline) == 0) return; prefix2str(pfx, str_pfx, sizeof(str_pfx)); nhs_displayed += print_rib_sl(fp, vty, out, sl, 0, str_pfx, &printedprefix); } #endif void rfapiRibShowResponses(void *stream, struct prefix *pfx_match, int show_removed) { int (*fp)(void *, const char *, ...); struct vty *vty; void *out; const char *vty_newline; struct rfapi_descriptor *rfd; struct listnode *node; struct bgp *bgp = bgp_get_default(); int printedheader = 0; int routes_total = 0; int nhs_total = 0; int prefixes_total = 0; int prefixes_displayed = 0; int nves_total = 0; int nves_with_routes = 0; int nves_displayed = 0; int routes_displayed = 0; int nhs_displayed = 0; if (rfapiStream2Vty(stream, &fp, &vty, &out, &vty_newline) == 0) return; if (!bgp) { fp(out, "Unable to find default BGP instance\n"); return; } /* * loop over NVEs */ for (ALL_LIST_ELEMENTS_RO(&bgp->rfapi->descriptors, node, rfd)) { int printednve = 0; afi_t afi; ++nves_total; if (rfd->rib_prefix_count) ++nves_with_routes; for (afi = AFI_IP; afi < AFI_MAX; ++afi) { struct agg_node *rn; if (!rfd->rib[afi]) continue; for (rn = agg_route_top(rfd->rib[afi]); rn; rn = agg_route_next(rn)) { struct skiplist *sl; char str_pfx[PREFIX_STRLEN]; int printedprefix = 0; if (!show_removed) sl = rn->info; else sl = rn->aggregate; if (!sl) continue; routes_total++; nhs_total += skiplist_count(sl); ++prefixes_total; if (pfx_match && !prefix_match(pfx_match, &rn->p) && !prefix_match(&rn->p, pfx_match)) continue; ++prefixes_displayed; if (!printedheader) { ++printedheader; fp(out, "\n[%s]\n", show_removed ? "Removed" : "Active"); fp(out, "%-15s %-15s\n", "Querying VN", "Querying UN"); fp(out, " %-20s %-15s %-15s %4s %-8s %-8s\n", "Prefix", "Registered VN", "Registered UN", "Cost", "Lifetime", #if RFAPI_REGISTRATIONS_REPORT_AGE "Age" #else "Remaining" #endif ); } if (!printednve) { char str_vn[BUFSIZ]; char str_un[BUFSIZ]; ++printednve; ++nves_displayed; fp(out, "%-15s %-15s\n", rfapiRfapiIpAddr2Str(&rfd->vn_addr, str_vn, BUFSIZ), rfapiRfapiIpAddr2Str(&rfd->un_addr, str_un, BUFSIZ)); } prefix2str(&rn->p, str_pfx, sizeof(str_pfx)); // fp(out, " %s\n", buf); /* prefix */ routes_displayed++; nhs_displayed += print_rib_sl( fp, vty, out, sl, show_removed, str_pfx, &printedprefix); } } } if (routes_total) { fp(out, "\n"); fp(out, "Displayed %u NVEs, and %u out of %u %s prefixes", nves_displayed, routes_displayed, routes_total, show_removed ? "removed" : "active"); if (nhs_displayed != routes_displayed || nhs_total != routes_total) fp(out, " with %u out of %u next hops", nhs_displayed, nhs_total); fp(out, "\n"); } } frr-7.2.1/bgpd/rfapi/rfapi_rib.h0000644000000000000000000001067613610377563013351 00000000000000/* * * Copyright 2009-2016, LabN Consulting, L.L.C. * * * This program is free software; you can redistribute it and/or * modify it under the terms of the GNU General Public License * as published by the Free Software Foundation; either version 2 * of the License, or (at your option) any later version. * * 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; see the file COPYING; if not, write to the Free Software * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA */ /* * File: rfapi_rib.h * Purpose: per-nve rib */ #ifndef QUAGGA_HGP_RFAPI_RIB_H #define QUAGGA_HGP_RFAPI_RIB_H /* * Key for indexing RIB and Pending RIB skiplists. For L3 RIBs, * the VN address is sufficient because it represents the actual next hop. * * For L2 RIBs, it is possible to have multiple routes to a given L2 * prefix via a given VN address, but each route having a unique aux_prefix. */ struct rfapi_rib_key { struct prefix vn; struct prefix_rd rd; /* * for L2 routes: optional IP addr * .family == 0 means "none" */ struct prefix aux_prefix; }; #include "rfapi.h" /* * RFAPI Advertisement Data Block * * Holds NVE prefix advertisement information */ struct rfapi_adb { union { struct { struct prefix prefix_ip; struct prefix_rd prd; struct prefix prefix_eth; } s; /* mainly for legacy use */ struct rfapi_rib_key key; } u; uint32_t lifetime; uint8_t cost; struct rfapi_l2address_option l2o; }; struct rfapi_info { struct rfapi_rib_key rk; /* NVE VN addr + aux addr */ struct prefix un; uint8_t cost; uint32_t lifetime; time_t last_sent_time; uint32_t rsp_counter; /* dedup initial responses */ struct bgp_tea_options *tea_options; struct rfapi_un_option *un_options; struct rfapi_vn_option *vn_options; struct thread *timer; }; /* * Work item for updated responses queue */ struct rfapi_updated_responses_queue { struct rfapi_descriptor *rfd; afi_t afi; }; extern void rfapiRibClear(struct rfapi_descriptor *rfd); extern void rfapiRibFree(struct rfapi_descriptor *rfd); extern void rfapiRibUpdatePendingNode(struct bgp *bgp, struct rfapi_descriptor *rfd, struct rfapi_import_table *it, struct agg_node *it_node, uint32_t lifetime); extern void rfapiRibUpdatePendingNodeSubtree(struct bgp *bgp, struct rfapi_descriptor *rfd, struct rfapi_import_table *it, struct agg_node *it_node, struct agg_node *omit_subtree, uint32_t lifetime); extern int rfapiRibPreloadBi(struct agg_node *rfd_rib_node, struct prefix *pfx_vn, struct prefix *pfx_un, uint32_t lifetime, struct bgp_path_info *bpi); extern struct rfapi_next_hop_entry * rfapiRibPreload(struct bgp *bgp, struct rfapi_descriptor *rfd, struct rfapi_next_hop_entry *response, int use_eth_resolution); extern void rfapiRibPendingDeleteRoute(struct bgp *bgp, struct rfapi_import_table *it, afi_t afi, struct agg_node *it_node); extern void rfapiRibShowResponsesSummary(void *stream); extern void rfapiRibShowResponsesSummaryClear(void); extern void rfapiRibShowResponses(void *stream, struct prefix *pfx_match, int show_removed); extern int rfapiRibFTDFilterRecentPrefix( struct rfapi_descriptor *rfd, struct agg_node *it_rn, /* import table node */ struct prefix *pfx_target_original); /* query target */ extern void rfapiFreeRfapiUnOptionChain(struct rfapi_un_option *p); extern void rfapiFreeRfapiVnOptionChain(struct rfapi_vn_option *p); extern void rfapiRibCheckCounts(int checkstats, /* validate rfd & global counts */ unsigned int offset); /* number of ri's held separately */ /* enable for debugging; disable for performance */ #if 0 #define RFAPI_RIB_CHECK_COUNTS(checkstats, offset) rfapiRibCheckCounts(checkstats, offset) #else #define RFAPI_RIB_CHECK_COUNTS(checkstats, offset) #endif extern void rfapi_rib_key_init(struct prefix *prefix, /* may be NULL */ struct prefix_rd *rd, /* may be NULL */ struct prefix *aux, /* may be NULL */ struct rfapi_rib_key *rk); extern int rfapi_rib_key_cmp(void *k1, void *k2); extern void rfapiAdbFree(struct rfapi_adb *adb); #endif /* QUAGGA_HGP_RFAPI_RIB_H */ frr-7.2.1/bgpd/rfapi/rfapi_vty.c0000644000000000000000000040001013610377563013373 00000000000000/* * * Copyright 2009-2016, LabN Consulting, L.L.C. * * * This program is free software; you can redistribute it and/or * modify it under the terms of the GNU General Public License * as published by the Free Software Foundation; either version 2 * of the License, or (at your option) any later version. * * 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; see the file COPYING; if not, write to the Free Software * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA */ #include "lib/zebra.h" #include "lib/prefix.h" #include "lib/agg_table.h" #include "lib/vty.h" #include "lib/memory.h" #include "lib/routemap.h" #include "lib/log.h" #include "lib/log_int.h" #include "lib/linklist.h" #include "lib/command.h" #include "bgpd/bgpd.h" #include "bgpd/bgp_ecommunity.h" #include "bgpd/bgp_attr.h" #include "bgpd/bgp_route.h" #include "bgpd/bgp_mplsvpn.h" #include "bgpd/rfapi/bgp_rfapi_cfg.h" #include "bgpd/rfapi/rfapi.h" #include "bgpd/rfapi/rfapi_backend.h" #include "bgpd/bgp_route.h" #include "bgpd/bgp_aspath.h" #include "bgpd/bgp_community.h" #include "bgpd/bgp_vnc_types.h" #include "bgpd/bgp_label.h" #include "bgpd/rfapi/rfapi_import.h" #include "bgpd/rfapi/rfapi_private.h" #include "bgpd/rfapi/rfapi_monitor.h" #include "bgpd/rfapi/rfapi_rib.h" #include "bgpd/rfapi/rfapi_vty.h" #include "bgpd/rfapi/rfapi_ap.h" #include "bgpd/rfapi/rfapi_encap_tlv.h" #include "bgpd/rfapi/vnc_debug.h" #define DEBUG_L2_EXTRA 0 #define DEBUG_SHOW_EXTRA 0 #define VNC_SHOW_STR "VNC information\n" /* format related utilies */ #define FMT_MIN 60 /* seconds */ #define FMT_HOUR (60 * FMT_MIN) #define FMT_DAY (24 * FMT_HOUR) #define FMT_YEAR (365 * FMT_DAY) char *rfapiFormatSeconds(uint32_t seconds, char *buf, size_t len) { int year, day, hour, min; if (seconds >= FMT_YEAR) { year = seconds / FMT_YEAR; seconds -= year * FMT_YEAR; } else year = 0; if (seconds >= FMT_DAY) { day = seconds / FMT_DAY; seconds -= day * FMT_DAY; } else day = 0; if (seconds >= FMT_HOUR) { hour = seconds / FMT_HOUR; seconds -= hour * FMT_HOUR; } else hour = 0; if (seconds >= FMT_MIN) { min = seconds / FMT_MIN; seconds -= min * FMT_MIN; } else min = 0; if (year > 0) { snprintf(buf, len, "%dy%dd%dh", year, day, hour); } else if (day > 0) { snprintf(buf, len, "%dd%dh%dm", day, hour, min); } else { snprintf(buf, len, "%02d:%02d:%02d", hour, min, seconds); } return buf; } char *rfapiFormatAge(time_t age, char *buf, size_t len) { time_t now, age_adjusted; now = rfapi_time(NULL); age_adjusted = now - age; return rfapiFormatSeconds(age_adjusted, buf, len); } /* * Reimplementation of quagga/lib/prefix.c function, but * for RFAPI-style prefixes */ void rfapiRprefixApplyMask(struct rfapi_ip_prefix *rprefix) { uint8_t *pnt; int index; int offset; static uint8_t maskbit[] = {0x00, 0x80, 0xc0, 0xe0, 0xf0, 0xf8, 0xfc, 0xfe, 0xff}; switch (rprefix->prefix.addr_family) { case AF_INET: index = rprefix->length / 8; if (index < 4) { pnt = (uint8_t *)&rprefix->prefix.addr.v4; offset = rprefix->length % 8; pnt[index] &= maskbit[offset]; index++; while (index < 4) pnt[index++] = 0; } break; case AF_INET6: index = rprefix->length / 8; if (index < 16) { pnt = (uint8_t *)&rprefix->prefix.addr.v6; offset = rprefix->length % 8; pnt[index] &= maskbit[offset]; index++; while (index < 16) pnt[index++] = 0; } break; default: assert(0); } } /* * translate a quagga prefix into a rfapi IP address. The * prefix is REQUIRED to be 32 bits for IPv4 and 128 bits for IPv6 * * RETURNS: * * 0 Success * <0 Error */ int rfapiQprefix2Raddr(struct prefix *qprefix, struct rfapi_ip_addr *raddr) { memset(raddr, 0, sizeof(struct rfapi_ip_addr)); raddr->addr_family = qprefix->family; switch (qprefix->family) { case AF_INET: if (qprefix->prefixlen != 32) return -1; raddr->addr.v4 = qprefix->u.prefix4; break; case AF_INET6: if (qprefix->prefixlen != 128) return -1; raddr->addr.v6 = qprefix->u.prefix6; break; default: return -1; } return 0; } /* * Translate Quagga prefix to RFAPI prefix */ /* rprefix->cost set to 0 */ void rfapiQprefix2Rprefix(struct prefix *qprefix, struct rfapi_ip_prefix *rprefix) { memset(rprefix, 0, sizeof(struct rfapi_ip_prefix)); rprefix->length = qprefix->prefixlen; rprefix->prefix.addr_family = qprefix->family; switch (qprefix->family) { case AF_INET: rprefix->prefix.addr.v4 = qprefix->u.prefix4; break; case AF_INET6: rprefix->prefix.addr.v6 = qprefix->u.prefix6; break; default: assert(0); } } int rfapiRprefix2Qprefix(struct rfapi_ip_prefix *rprefix, struct prefix *qprefix) { memset(qprefix, 0, sizeof(struct prefix)); qprefix->prefixlen = rprefix->length; qprefix->family = rprefix->prefix.addr_family; switch (rprefix->prefix.addr_family) { case AF_INET: qprefix->u.prefix4 = rprefix->prefix.addr.v4; break; case AF_INET6: qprefix->u.prefix6 = rprefix->prefix.addr.v6; break; default: return EAFNOSUPPORT; } return 0; } /* * returns 1 if prefixes have same addr family, prefix len, and address * Note that host bits matter in this comparison! * * For paralellism with quagga/lib/prefix.c. if we need a comparison * where host bits are ignored, call that function rfapiRprefixCmp. */ int rfapiRprefixSame(struct rfapi_ip_prefix *hp1, struct rfapi_ip_prefix *hp2) { if (hp1->prefix.addr_family != hp2->prefix.addr_family) return 0; if (hp1->length != hp2->length) return 0; if (hp1->prefix.addr_family == AF_INET) if (IPV4_ADDR_SAME(&hp1->prefix.addr.v4, &hp2->prefix.addr.v4)) return 1; if (hp1->prefix.addr_family == AF_INET6) if (IPV6_ADDR_SAME(&hp1->prefix.addr.v6, &hp2->prefix.addr.v6)) return 1; return 0; } int rfapiRaddr2Qprefix(struct rfapi_ip_addr *hia, struct prefix *pfx) { memset(pfx, 0, sizeof(struct prefix)); pfx->family = hia->addr_family; switch (hia->addr_family) { case AF_INET: pfx->prefixlen = 32; pfx->u.prefix4 = hia->addr.v4; break; case AF_INET6: pfx->prefixlen = 128; pfx->u.prefix6 = hia->addr.v6; break; default: return EAFNOSUPPORT; } return 0; } void rfapiL2o2Qprefix(struct rfapi_l2address_option *l2o, struct prefix *pfx) { memset(pfx, 0, sizeof(struct prefix)); pfx->family = AF_ETHERNET; pfx->prefixlen = 48; pfx->u.prefix_eth = l2o->macaddr; } char *rfapiEthAddr2Str(const struct ethaddr *ea, char *buf, int bufsize) { return prefix_mac2str(ea, buf, bufsize); } int rfapiStr2EthAddr(const char *str, struct ethaddr *ea) { unsigned int a[6]; int i; if (sscanf(str, "%2x:%2x:%2x:%2x:%2x:%2x", a + 0, a + 1, a + 2, a + 3, a + 4, a + 5) != 6) { return EINVAL; } for (i = 0; i < 6; ++i) ea->octet[i] = a[i] & 0xff; return 0; } const char *rfapi_ntop(int af, const void *src, char *buf, socklen_t size) { if (af == AF_ETHERNET) { return rfapiEthAddr2Str((const struct ethaddr *)src, buf, size); } return inet_ntop(af, src, buf, size); } int rfapiDebugPrintf(void *dummy, const char *format, ...) { va_list args; va_start(args, format); vzlog(LOG_DEBUG, format, args); va_end(args); return 0; } static int rfapiStdioPrintf(void *stream, const char *format, ...) { FILE *file = NULL; va_list args; va_start(args, format); switch ((uintptr_t)stream) { case 1: file = stdout; break; case 2: file = stderr; break; default: assert(0); } vfprintf(file, format, args); va_end(args); return 0; } /* Fake out for debug logging */ static struct vty vty_dummy_zlog; static struct vty vty_dummy_stdio; #define HVTYNL ((vty == &vty_dummy_zlog)? "": "\n") static const char *str_vty_newline(struct vty *vty) { if (vty == &vty_dummy_zlog) return ""; return "\n"; } int rfapiStream2Vty(void *stream, /* input */ int (**fp)(void *, const char *, ...), /* output */ struct vty **vty, /* output */ void **outstream, /* output */ const char **vty_newline) /* output */ { if (!stream) { vty_dummy_zlog.type = VTY_SHELL; /* for VTYNL */ *vty = &vty_dummy_zlog; *fp = (int (*)(void *, const char *, ...))rfapiDebugPrintf; *outstream = NULL; *vty_newline = str_vty_newline(*vty); return (vzlog_test(LOG_DEBUG)); } if (((uintptr_t)stream == (uintptr_t)1) || ((uintptr_t)stream == (uintptr_t)2)) { vty_dummy_stdio.type = VTY_SHELL; /* for VTYNL */ *vty = &vty_dummy_stdio; *fp = (int (*)(void *, const char *, ...))rfapiStdioPrintf; *outstream = stream; *vty_newline = str_vty_newline(*vty); return 1; } *vty = stream; /* VTYNL requires vty to be legit */ *fp = (int (*)(void *, const char *, ...))vty_out; *outstream = stream; *vty_newline = str_vty_newline(*vty); return 1; } /* called from bgpd/bgp_vty.c'route_vty_out() */ void rfapi_vty_out_vncinfo(struct vty *vty, struct prefix *p, struct bgp_path_info *bpi, safi_t safi) { char *s; uint32_t lifetime; /* * Print, on an indented line: * UN address [if VPN route and VNC UN addr subtlv] * EC list * VNC lifetime */ vty_out(vty, " "); if (safi == SAFI_MPLS_VPN) { struct prefix pfx_un; if (!rfapiGetVncTunnelUnAddr(bpi->attr, &pfx_un)) { char buf[BUFSIZ]; vty_out(vty, "UN=%s", inet_ntop(pfx_un.family, pfx_un.u.val, buf, BUFSIZ)); } } if (bpi->attr && bpi->attr->ecommunity) { s = ecommunity_ecom2str(bpi->attr->ecommunity, ECOMMUNITY_FORMAT_ROUTE_MAP, 0); vty_out(vty, " EC{%s}", s); XFREE(MTYPE_ECOMMUNITY_STR, s); } if (bpi->extra != NULL) { if (bpi->extra->label[0] == BGP_PREVENT_VRF_2_VRF_LEAK) vty_out(vty, " label=VRF2VRF"); else vty_out(vty, " label=%u", decode_label(&bpi->extra->label[0])); } if (!rfapiGetVncLifetime(bpi->attr, &lifetime)) { vty_out(vty, " life=%d", lifetime); } vty_out(vty, " type=%s, subtype=%d", zebra_route_string(bpi->type), bpi->sub_type); vty_out(vty, "%s", HVTYNL); } void rfapiPrintAttrPtrs(void *stream, struct attr *attr) { int (*fp)(void *, const char *, ...); struct vty *vty; void *out; const char *vty_newline; char buf[BUFSIZ]; if (rfapiStream2Vty(stream, &fp, &vty, &out, &vty_newline) == 0) return; fp(out, "Attr[%p]:%s", attr, HVTYNL); if (!attr) return; /* IPv4 Nexthop */ inet_ntop(AF_INET, &attr->nexthop, buf, BUFSIZ); fp(out, " nexthop=%s%s", buf, HVTYNL); fp(out, " aspath=%p, refcnt=%d%s", attr->aspath, (attr->aspath ? attr->aspath->refcnt : 0), HVTYNL); fp(out, " community=%p, refcnt=%d%s", attr->community, (attr->community ? attr->community->refcnt : 0), HVTYNL); fp(out, " ecommunity=%p, refcnt=%d%s", attr->ecommunity, (attr->ecommunity ? attr->ecommunity->refcnt : 0), HVTYNL); fp(out, " cluster=%p, refcnt=%d%s", attr->cluster, (attr->cluster ? attr->cluster->refcnt : 0), HVTYNL); fp(out, " transit=%p, refcnt=%d%s", attr->transit, (attr->transit ? attr->transit->refcnt : 0), HVTYNL); } /* * Print BPI in an Import Table */ void rfapiPrintBi(void *stream, struct bgp_path_info *bpi) { char buf[BUFSIZ]; char *s; int (*fp)(void *, const char *, ...); struct vty *vty; void *out; const char *vty_newline; char line[BUFSIZ]; char *p = line; int r; int has_macaddr = 0; struct ethaddr macaddr = {{0}}; struct rfapi_l2address_option l2o_buf; uint8_t l2hid = 0; /* valid if has_macaddr */ #define REMAIN (BUFSIZ - (p-line)) #define INCP {p += (r > REMAIN)? REMAIN: r;} if (rfapiStream2Vty(stream, &fp, &vty, &out, &vty_newline) == 0) return; if (!bpi) return; if (CHECK_FLAG(bpi->flags, BGP_PATH_REMOVED) && bpi->extra && bpi->extra->vnc.import.timer) { struct thread *t = (struct thread *)bpi->extra->vnc.import.timer; r = snprintf(p, REMAIN, " [%4lu] ", thread_timer_remain_second(t)); INCP; } else { r = snprintf(p, REMAIN, " "); INCP; } if (bpi->extra) { /* TBD This valid only for SAFI_MPLS_VPN, but not for encap */ if (decode_rd_type(bpi->extra->vnc.import.rd.val) == RD_TYPE_VNC_ETH) { has_macaddr = 1; memcpy(macaddr.octet, bpi->extra->vnc.import.rd.val + 2, 6); l2hid = bpi->extra->vnc.import.rd.val[1]; } } /* * Print these items: * type/subtype * nexthop address * lifetime * RFP option sizes (they are opaque values) * extended communities (RTs) */ if (bpi->attr) { uint32_t lifetime; int printed_1st_gol = 0; struct bgp_attr_encap_subtlv *pEncap; struct prefix pfx_un; int af = BGP_MP_NEXTHOP_FAMILY(bpi->attr->mp_nexthop_len); /* Nexthop */ if (af == AF_INET) { r = snprintf(p, REMAIN, "%s", inet_ntop(AF_INET, &bpi->attr->mp_nexthop_global_in, buf, BUFSIZ)); INCP; } else if (af == AF_INET6) { r = snprintf(p, REMAIN, "%s", inet_ntop(AF_INET6, &bpi->attr->mp_nexthop_global, buf, BUFSIZ)); INCP; } else { r = snprintf(p, REMAIN, "?"); INCP; } /* * VNC tunnel subtlv, if present, contains UN address */ if (!rfapiGetVncTunnelUnAddr(bpi->attr, &pfx_un)) { r = snprintf(p, REMAIN, " un=%s", inet_ntop(pfx_un.family, pfx_un.u.val, buf, BUFSIZ)); INCP; } /* Lifetime */ if (rfapiGetVncLifetime(bpi->attr, &lifetime)) { r = snprintf(p, REMAIN, " nolife"); INCP; } else { if (lifetime == 0xffffffff) r = snprintf(p, REMAIN, " %6s", "infini"); else r = snprintf(p, REMAIN, " %6u", lifetime); INCP; } /* RFP option lengths */ for (pEncap = bpi->attr->vnc_subtlvs; pEncap; pEncap = pEncap->next) { if (pEncap->type == BGP_VNC_SUBTLV_TYPE_RFPOPTION) { if (printed_1st_gol) { r = snprintf(p, REMAIN, ","); INCP; } else { r = snprintf(p, REMAIN, " "); /* leading space */ INCP; } r = snprintf(p, REMAIN, "%d", pEncap->length); INCP; printed_1st_gol = 1; } } /* RT list */ if (bpi->attr->ecommunity) { s = ecommunity_ecom2str(bpi->attr->ecommunity, ECOMMUNITY_FORMAT_ROUTE_MAP, 0); r = snprintf(p, REMAIN, " %s", s); INCP; XFREE(MTYPE_ECOMMUNITY_STR, s); } } r = snprintf(p, REMAIN, " bpi@%p", bpi); INCP; r = snprintf(p, REMAIN, " p@%p", bpi->peer); INCP; if (CHECK_FLAG(bpi->flags, BGP_PATH_REMOVED)) { r = snprintf(p, REMAIN, " HD=yes"); INCP; } else { r = snprintf(p, REMAIN, " HD=no"); INCP; } if (bpi->attr) { if (bpi->attr->weight) { r = snprintf(p, REMAIN, " W=%d", bpi->attr->weight); INCP; } if (bpi->attr->flag & ATTR_FLAG_BIT(BGP_ATTR_LOCAL_PREF)) { r = snprintf(p, REMAIN, " LP=%d", bpi->attr->local_pref); INCP; } else { r = snprintf(p, REMAIN, " LP=unset"); INCP; } } r = snprintf(p, REMAIN, " %c:%u", zebra_route_char(bpi->type), bpi->sub_type); INCP; fp(out, "%s%s", line, HVTYNL); if (has_macaddr) { fp(out, " RD HID=%d ETH=%02x:%02x:%02x:%02x:%02x:%02x%s", l2hid, macaddr.octet[0], macaddr.octet[1], macaddr.octet[2], macaddr.octet[3], macaddr.octet[4], macaddr.octet[5], HVTYNL); } if (!rfapiGetL2o(bpi->attr, &l2o_buf)) { fp(out, " L2O ETH=%02x:%02x:%02x:%02x:%02x:%02x LBL=%d LNI=%d LHI=%hhu%s", l2o_buf.macaddr.octet[0], l2o_buf.macaddr.octet[1], l2o_buf.macaddr.octet[2], l2o_buf.macaddr.octet[3], l2o_buf.macaddr.octet[4], l2o_buf.macaddr.octet[5], l2o_buf.label, l2o_buf.logical_net_id, l2o_buf.local_nve_id, HVTYNL); } if (bpi->extra && bpi->extra->vnc.import.aux_prefix.family) { const char *sp; sp = rfapi_ntop(bpi->extra->vnc.import.aux_prefix.family, &bpi->extra->vnc.import.aux_prefix.u.prefix, buf, BUFSIZ); buf[BUFSIZ - 1] = 0; if (sp) { fp(out, " IP: %s%s", sp, HVTYNL); } } { struct rfapi_un_option *uo = rfapi_encap_tlv_to_un_option(bpi->attr); if (uo) { rfapi_print_tunneltype_option(stream, 8, &uo->v.tunnel); rfapi_un_options_free(uo); } } } char *rfapiMonitorVpn2Str(struct rfapi_monitor_vpn *m, char *buf, int size) { char buf_pfx[BUFSIZ]; char buf_vn[BUFSIZ]; char buf_un[BUFSIZ]; int rc; rfapiRfapiIpAddr2Str(&m->rfd->un_addr, buf_vn, BUFSIZ); rfapiRfapiIpAddr2Str(&m->rfd->vn_addr, buf_un, BUFSIZ); rc = snprintf(buf, size, "m=%p, next=%p, rfd=%p(vn=%s un=%s), p=%s/%d, node=%p", m, m->next, m->rfd, buf_vn, buf_un, inet_ntop(m->p.family, &m->p.u.prefix, buf_pfx, BUFSIZ), m->p.prefixlen, m->node); buf[size - 1] = 0; if (rc >= size) return NULL; return buf; } static void rfapiDebugPrintMonitorVpn(void *stream, struct rfapi_monitor_vpn *m) { char buf[BUFSIZ]; int (*fp)(void *, const char *, ...); struct vty *vty; void *out; const char *vty_newline; if (rfapiStream2Vty(stream, &fp, &vty, &out, &vty_newline) == 0) return; rfapiMonitorVpn2Str(m, buf, BUFSIZ); fp(out, " Mon %s%s", buf, HVTYNL); } static void rfapiDebugPrintMonitorEncap(void *stream, struct rfapi_monitor_encap *m) { int (*fp)(void *, const char *, ...); struct vty *vty; void *out = NULL; const char *vty_newline; if (rfapiStream2Vty(stream, &fp, &vty, &out, &vty_newline) == 0) return; fp(out, " Mon m=%p, next=%p, node=%p, bpi=%p%s", m, m->next, m->node, m->bpi, HVTYNL); } void rfapiShowItNode(void *stream, struct agg_node *rn) { struct bgp_path_info *bpi; char buf[BUFSIZ]; int (*fp)(void *, const char *, ...); struct vty *vty; void *out; const char *vty_newline; if (rfapiStream2Vty(stream, &fp, &vty, &out, &vty_newline) == 0) return; fp(out, "%s/%d @%p #%d%s", rfapi_ntop(rn->p.family, &rn->p.u.prefix, buf, BUFSIZ), rn->p.prefixlen, rn, rn->lock, HVTYNL); for (bpi = rn->info; bpi; bpi = bpi->next) { rfapiPrintBi(stream, bpi); } /* doesn't show montors */ } void rfapiShowImportTable(void *stream, const char *label, struct agg_table *rt, int isvpn) { struct agg_node *rn; char buf[BUFSIZ]; int (*fp)(void *, const char *, ...); struct vty *vty; void *out; const char *vty_newline; if (rfapiStream2Vty(stream, &fp, &vty, &out, &vty_newline) == 0) return; fp(out, "Import Table [%s]%s", label, HVTYNL); for (rn = agg_route_top(rt); rn; rn = agg_route_next(rn)) { struct bgp_path_info *bpi; if (rn->p.family == AF_ETHERNET) { rfapiEthAddr2Str(&rn->p.u.prefix_eth, buf, BUFSIZ); } else { inet_ntop(rn->p.family, &rn->p.u.prefix, buf, BUFSIZ); } fp(out, "%s/%d @%p #%d%s", buf, rn->p.prefixlen, rn, rn->lock - 1, /* account for loop iterator locking */ HVTYNL); for (bpi = rn->info; bpi; bpi = bpi->next) { rfapiPrintBi(stream, bpi); } if (isvpn) { struct rfapi_monitor_vpn *m; for (m = RFAPI_MONITOR_VPN(rn); m; m = m->next) { rfapiDebugPrintMonitorVpn(stream, m); } } else { struct rfapi_monitor_encap *m; for (m = RFAPI_MONITOR_ENCAP(rn); m; m = m->next) { rfapiDebugPrintMonitorEncap(stream, m); } } } } int rfapiShowVncQueries(void *stream, struct prefix *pfx_match) { struct bgp *bgp; struct rfapi *h; struct listnode *node; struct rfapi_descriptor *rfd; int (*fp)(void *, const char *, ...); struct vty *vty; void *out; const char *vty_newline; int printedheader = 0; int nves_total = 0; int nves_with_queries = 0; int nves_displayed = 0; int queries_total = 0; int queries_displayed = 0; if (rfapiStream2Vty(stream, &fp, &vty, &out, &vty_newline) == 0) return CMD_WARNING; bgp = bgp_get_default(); /* assume 1 instance for now */ if (!bgp) { vty_out(vty, "No BGP instance\n"); return CMD_WARNING; } h = bgp->rfapi; if (!h) { vty_out(vty, "No RFAPI instance\n"); return CMD_WARNING; } for (ALL_LIST_ELEMENTS_RO(&h->descriptors, node, rfd)) { struct agg_node *rn; int printedquerier = 0; ++nves_total; if (rfd->mon || (rfd->mon_eth && skiplist_count(rfd->mon_eth))) { ++nves_with_queries; } else { continue; } /* * IP Queries */ if (rfd->mon) { for (rn = agg_route_top(rfd->mon); rn; rn = agg_route_next(rn)) { struct rfapi_monitor_vpn *m; char buf_remain[BUFSIZ]; char buf_pfx[BUFSIZ]; if (!rn->info) continue; m = rn->info; ++queries_total; if (pfx_match && !prefix_match(pfx_match, &rn->p) && !prefix_match(&rn->p, pfx_match)) continue; ++queries_displayed; if (!printedheader) { ++printedheader; fp(out, "\n"); fp(out, "%-15s %-15s %-15s %-10s\n", "VN Address", "UN Address", "Target", "Remaining"); } if (!printedquerier) { char buf_vn[BUFSIZ]; char buf_un[BUFSIZ]; rfapiRfapiIpAddr2Str(&rfd->un_addr, buf_un, BUFSIZ); rfapiRfapiIpAddr2Str(&rfd->vn_addr, buf_vn, BUFSIZ); fp(out, "%-15s %-15s", buf_vn, buf_un); printedquerier = 1; ++nves_displayed; } else fp(out, "%-15s %-15s", "", ""); buf_remain[0] = 0; if (m->timer) { rfapiFormatSeconds( thread_timer_remain_second( m->timer), buf_remain, BUFSIZ); } fp(out, " %-15s %-10s\n", inet_ntop(m->p.family, &m->p.u.prefix, buf_pfx, BUFSIZ), buf_remain); } } /* * Ethernet Queries */ if (rfd->mon_eth && skiplist_count(rfd->mon_eth)) { int rc; void *cursor; struct rfapi_monitor_eth *mon_eth; for (cursor = NULL, rc = skiplist_next(rfd->mon_eth, NULL, (void **)&mon_eth, &cursor); rc == 0; rc = skiplist_next(rfd->mon_eth, NULL, (void **)&mon_eth, &cursor)) { char buf_remain[BUFSIZ]; char buf_pfx[BUFSIZ]; struct prefix pfx_mac; ++queries_total; vnc_zlog_debug_verbose( "%s: checking rfd=%p mon_eth=%p", __func__, rfd, mon_eth); memset((void *)&pfx_mac, 0, sizeof(struct prefix)); pfx_mac.family = AF_ETHERNET; pfx_mac.prefixlen = 48; pfx_mac.u.prefix_eth = mon_eth->macaddr; if (pfx_match && !prefix_match(pfx_match, &pfx_mac) && !prefix_match(&pfx_mac, pfx_match)) continue; ++queries_displayed; if (!printedheader) { ++printedheader; fp(out, "\n"); fp(out, "%-15s %-15s %-17s %10s %-10s\n", "VN Address", "UN Address", "Target", "LNI", "Remaining"); } if (!printedquerier) { char buf_vn[BUFSIZ]; char buf_un[BUFSIZ]; rfapiRfapiIpAddr2Str(&rfd->un_addr, buf_un, BUFSIZ); rfapiRfapiIpAddr2Str(&rfd->vn_addr, buf_vn, BUFSIZ); fp(out, "%-15s %-15s", buf_vn, buf_un); printedquerier = 1; ++nves_displayed; } else fp(out, "%-15s %-15s", "", ""); buf_remain[0] = 0; if (mon_eth->timer) { rfapiFormatSeconds( thread_timer_remain_second( mon_eth->timer), buf_remain, BUFSIZ); } fp(out, " %-17s %10d %-10s\n", rfapi_ntop(pfx_mac.family, &pfx_mac.u.prefix, buf_pfx, BUFSIZ), mon_eth->logical_net_id, buf_remain); } } } if (queries_total) { fp(out, "\n"); fp(out, "Displayed %d out of %d total queries\n", queries_displayed, queries_total); } return CMD_SUCCESS; } static int rfapiPrintRemoteRegBi(struct bgp *bgp, void *stream, struct agg_node *rn, struct bgp_path_info *bpi) { int (*fp)(void *, const char *, ...); struct vty *vty; void *out; const char *vty_newline; struct prefix pfx_un; struct prefix pfx_vn; uint8_t cost; uint32_t lifetime; bgp_encap_types tun_type = BGP_ENCAP_TYPE_MPLS;/*Default tunnel type*/ char buf_pfx[BUFSIZ]; char buf_ntop[BUFSIZ]; char buf_un[BUFSIZ]; char buf_vn[BUFSIZ]; char buf_lifetime[BUFSIZ]; int nlines = 0; if (!stream) return 0; /* for debug log, print into buf & call output once */ if (rfapiStream2Vty(stream, &fp, &vty, &out, &vty_newline) == 0) return 0; /* * Prefix */ buf_pfx[0] = 0; snprintf(buf_pfx, BUFSIZ, "%s/%d", rfapi_ntop(rn->p.family, &rn->p.u.prefix, buf_ntop, BUFSIZ), rn->p.prefixlen); buf_pfx[BUFSIZ - 1] = 0; nlines++; /* * UN addr */ buf_un[0] = 0; if (!rfapiGetUnAddrOfVpnBi(bpi, &pfx_un)) { snprintf(buf_un, BUFSIZ, "%s", inet_ntop(pfx_un.family, &pfx_un.u.prefix, buf_ntop, BUFSIZ)); } bgp_attr_extcom_tunnel_type(bpi->attr, &tun_type); /* * VN addr */ buf_vn[0] = 0; rfapiNexthop2Prefix(bpi->attr, &pfx_vn); if (tun_type == BGP_ENCAP_TYPE_MPLS) { /* MPLS carries un in nrli next hop (same as vn for IP tunnels) */ snprintf(buf_un, BUFSIZ, "%s", inet_ntop(pfx_vn.family, &pfx_vn.u.prefix, buf_ntop, BUFSIZ)); if (bpi->extra) { uint32_t l = decode_label(&bpi->extra->label[0]); snprintf(buf_vn, BUFSIZ, "Label: %d", l); } else /* should never happen */ { snprintf(buf_vn, BUFSIZ, "Label: N/A"); } } else { snprintf(buf_vn, BUFSIZ, "%s", inet_ntop(pfx_vn.family, &pfx_vn.u.prefix, buf_ntop, BUFSIZ)); } buf_vn[BUFSIZ - 1] = 0; buf_un[BUFSIZ - 1] = 0; /* * Cost is encoded in local_pref as (255-cost) * See rfapi_import.c'rfapiRouteInfo2NextHopEntry() for conversion * back to cost. */ if (bpi->attr) { uint32_t local_pref; if (bpi->attr->flag & ATTR_FLAG_BIT(BGP_ATTR_LOCAL_PREF)) local_pref = bpi->attr->local_pref; else local_pref = 0; cost = (local_pref > 255) ? 0 : 255 - local_pref; } else { cost = 0; } fp(out, "%-20s ", buf_pfx); fp(out, "%-15s ", buf_vn); fp(out, "%-15s ", buf_un); fp(out, "%-4d ", cost); /* Lifetime */ /* NB rfapiGetVncLifetime sets infinite value when returning !0 */ if (rfapiGetVncLifetime(bpi->attr, &lifetime) || (lifetime == RFAPI_INFINITE_LIFETIME)) { fp(out, "%-10s ", "infinite"); } else { time_t t_lifetime = lifetime; rfapiFormatSeconds(t_lifetime, buf_lifetime, BUFSIZ); fp(out, "%-10s ", buf_lifetime); } if (CHECK_FLAG(bpi->flags, BGP_PATH_REMOVED) && bpi->extra && bpi->extra->vnc.import.timer) { uint32_t remaining; time_t age; char buf_age[BUFSIZ]; struct thread *t = (struct thread *)bpi->extra->vnc.import.timer; remaining = thread_timer_remain_second(t); #if RFAPI_REGISTRATIONS_REPORT_AGE /* * Calculate when the timer started. Doing so here saves * us a timestamp field in "struct bgp_path_info". * * See rfapi_import.c'rfapiBiStartWithdrawTimer() for the * original calculation. */ age = rfapiGetHolddownFromLifetime(lifetime, factor) - remaining; #else /* report remaining time */ age = remaining; #endif rfapiFormatSeconds(age, buf_age, BUFSIZ); fp(out, "%-10s ", buf_age); } else if (RFAPI_LOCAL_BI(bpi)) { char buf_age[BUFSIZ]; if (bpi->extra && bpi->extra->vnc.import.create_time) { rfapiFormatAge(bpi->extra->vnc.import.create_time, buf_age, BUFSIZ); } else { buf_age[0] = '?'; buf_age[1] = 0; } fp(out, "%-10s ", buf_age); } fp(out, "%s", HVTYNL); if (rn->p.family == AF_ETHERNET) { /* * If there is a corresponding IP address && != VN address, * print that on the next line */ if (bpi->extra && bpi->extra->vnc.import.aux_prefix.family) { const char *sp; sp = rfapi_ntop( bpi->extra->vnc.import.aux_prefix.family, &bpi->extra->vnc.import.aux_prefix.u.prefix, buf_ntop, BUFSIZ); buf_ntop[BUFSIZ - 1] = 0; if (sp && strcmp(buf_vn, sp) != 0) { fp(out, " IP: %s", sp); if (nlines == 1) nlines++; } } } if (tun_type != BGP_ENCAP_TYPE_MPLS && bpi->extra) { uint32_t l = decode_label(&bpi->extra->label[0]); if (!MPLS_LABEL_IS_NULL(l)) { fp(out, " Label: %d", l); if (nlines == 1) nlines++; } } if (nlines > 1) fp(out, "%s", HVTYNL); return 1; } static int rfapiShowRemoteRegistrationsIt(struct bgp *bgp, void *stream, struct rfapi_import_table *it, struct prefix *prefix_only, int show_expiring, /* either/or */ int show_local, int show_remote, int show_imported, /* either/or */ uint32_t *pLni) /* AFI_L2VPN only */ { afi_t afi; int printed_rtlist_hdr = 0; int (*fp)(void *, const char *, ...); struct vty *vty; void *out; const char *vty_newline; int total = 0; int printed = 0; if (rfapiStream2Vty(stream, &fp, &vty, &out, &vty_newline) == 0) return printed; for (afi = AFI_IP; afi < AFI_MAX; ++afi) { struct agg_node *rn; if (!it->imported_vpn[afi]) continue; for (rn = agg_route_top(it->imported_vpn[afi]); rn; rn = agg_route_next(rn)) { struct bgp_path_info *bpi; int count_only; /* allow for wider or more narrow mask from user */ if (prefix_only && !prefix_match(prefix_only, &rn->p) && !prefix_match(&rn->p, prefix_only)) count_only = 1; else count_only = 0; for (bpi = rn->info; bpi; bpi = bpi->next) { if (!show_local && RFAPI_LOCAL_BI(bpi)) { /* local route from RFP */ continue; } if (!show_remote && !RFAPI_LOCAL_BI(bpi)) { /* remote route */ continue; } if (show_expiring && !CHECK_FLAG(bpi->flags, BGP_PATH_REMOVED)) continue; if (!show_expiring && CHECK_FLAG(bpi->flags, BGP_PATH_REMOVED)) continue; if (bpi->type == ZEBRA_ROUTE_BGP_DIRECT || bpi->type == ZEBRA_ROUTE_BGP_DIRECT_EXT) { if (!show_imported) continue; } else { if (show_imported) continue; } total++; if (count_only == 1) continue; if (!printed_rtlist_hdr) { const char *agetype = ""; char *s; const char *type = ""; if (show_imported) { type = "Imported"; } else { if (show_expiring) { type = "Holddown"; } else { if (RFAPI_LOCAL_BI( bpi)) { type = "Local"; } else { type = "Remote"; } } } s = ecommunity_ecom2str( it->rt_import_list, ECOMMUNITY_FORMAT_ROUTE_MAP, 0); if (pLni) { fp(out, "%s[%s] L2VPN Network 0x%x (%u) RT={%s}", HVTYNL, type, *pLni, (*pLni & 0xfff), s); } else { fp(out, "%s[%s] Prefix RT={%s}", HVTYNL, type, s); } XFREE(MTYPE_ECOMMUNITY_STR, s); if (it->rfg && it->rfg->name) { fp(out, " %s \"%s\"", (it->rfg->type == RFAPI_GROUP_CFG_VRF ? "VRF" : "NVE group"), it->rfg->name); } fp(out, "%s", HVTYNL); if (show_expiring) { #if RFAPI_REGISTRATIONS_REPORT_AGE agetype = "Age"; #else agetype = "Remaining"; #endif } else if (show_local) { agetype = "Age"; } printed_rtlist_hdr = 1; fp(out, "%-20s %-15s %-15s %4s %-10s %-10s%s", (pLni ? "L2 Address/IP" : "Prefix"), "VN Address", "UN Address", "Cost", "Lifetime", agetype, HVTYNL); } printed += rfapiPrintRemoteRegBi(bgp, stream, rn, bpi); } } } if (printed > 0) { const char *type = "prefixes"; if (show_imported) { type = "imported prefixes"; } else { if (show_expiring) { type = "prefixes in holddown"; } else { if (show_local && !show_remote) { type = "locally registered prefixes"; } else if (!show_local && show_remote) { type = "remotely registered prefixes"; } } } fp(out, "Displayed %d out of %d %s%s", printed, total, type, HVTYNL); #if DEBUG_SHOW_EXTRA fp(out, "IT table above: it=%p%s", it, HVTYNL); #endif } return printed; } /* * rfapiShowRemoteRegistrations * * Similar to rfapiShowImportTable() above. This function * is mean to produce the "remote" portion of the output * of "show vnc registrations". */ int rfapiShowRemoteRegistrations(void *stream, struct prefix *prefix_only, int show_expiring, int show_local, int show_remote, int show_imported) { struct bgp *bgp; struct rfapi *h; struct rfapi_import_table *it; int printed = 0; bgp = bgp_get_default(); if (!bgp) { return printed; } h = bgp->rfapi; if (!h) { return printed; } for (it = h->imports; it; it = it->next) { printed += rfapiShowRemoteRegistrationsIt( bgp, stream, it, prefix_only, show_expiring, show_local, show_remote, show_imported, NULL); } if (h->import_mac) { void *cursor = NULL; int rc; uintptr_t lni_as_ptr; uint32_t lni; uint32_t *pLni; for (rc = skiplist_next(h->import_mac, (void **)&lni_as_ptr, (void **)&it, &cursor); !rc; rc = skiplist_next(h->import_mac, (void **)&lni_as_ptr, (void **)&it, &cursor)) { pLni = NULL; if ((lni_as_ptr & 0xffffffff) == lni_as_ptr) { lni = (uint32_t)(lni_as_ptr & 0xffffffff); pLni = &lni; } printed += rfapiShowRemoteRegistrationsIt( bgp, stream, it, prefix_only, show_expiring, show_local, show_remote, show_imported, pLni); } } return printed; } /*------------------------------------------ * rfapiRfapiIpAddr2Str * * UI helper: generate string from rfapi_ip_addr * * input: * a IP v4/v6 address * * output * buf put string here * bufsize max space to write * * return value: * NULL conversion failed * non-NULL pointer to buf --------------------------------------------*/ const char *rfapiRfapiIpAddr2Str(struct rfapi_ip_addr *a, char *buf, int bufsize) { const char *rc = NULL; switch (a->addr_family) { case AF_INET: rc = inet_ntop(a->addr_family, &a->addr.v4, buf, bufsize); break; case AF_INET6: rc = inet_ntop(a->addr_family, &a->addr.v6, buf, bufsize); break; } return rc; } void rfapiPrintRfapiIpAddr(void *stream, struct rfapi_ip_addr *a) { char buf[BUFSIZ]; const char *rc = NULL; int (*fp)(void *, const char *, ...); struct vty *vty; void *out = NULL; const char *vty_newline; if (rfapiStream2Vty(stream, &fp, &vty, &out, &vty_newline) == 0) return; rc = rfapiRfapiIpAddr2Str(a, buf, BUFSIZ); if (rc) fp(out, "%s", buf); } const char *rfapiRfapiIpPrefix2Str(struct rfapi_ip_prefix *p, char *buf, int bufsize) { struct rfapi_ip_addr *a = &p->prefix; const char *rc = NULL; switch (a->addr_family) { case AF_INET: rc = inet_ntop(a->addr_family, &a->addr.v4, buf, bufsize); break; case AF_INET6: rc = inet_ntop(a->addr_family, &a->addr.v6, buf, bufsize); break; } if (rc) { int alen = strlen(buf); int remaining = bufsize - alen - 1; int slen; if (remaining > 0) { slen = snprintf(buf + alen, remaining, "/%u", p->length); if (slen < remaining) /* see man page for snprintf(3) */ return rc; } } return NULL; } void rfapiPrintRfapiIpPrefix(void *stream, struct rfapi_ip_prefix *p) { char buf[BUFSIZ]; const char *rc; int (*fp)(void *, const char *, ...); struct vty *vty; void *out = NULL; const char *vty_newline; if (rfapiStream2Vty(stream, &fp, &vty, &out, &vty_newline) == 0) return; rc = rfapiRfapiIpPrefix2Str(p, buf, BUFSIZ); if (rc) fp(out, "%s:%u", buf, p->cost); else fp(out, "?/?:?"); } void rfapiPrintRd(struct vty *vty, struct prefix_rd *prd) { char buf[RD_ADDRSTRLEN]; prefix_rd2str(prd, buf, sizeof(buf)); vty_out(vty, "%s", buf); } void rfapiPrintAdvertisedInfo(struct vty *vty, struct rfapi_descriptor *rfd, safi_t safi, struct prefix *p) { afi_t afi; /* of the VN address */ struct bgp_node *bn; struct bgp_path_info *bpi; uint8_t type = ZEBRA_ROUTE_BGP; struct bgp *bgp; int printed = 0; struct prefix_rd prd0; struct prefix_rd *prd; /* * Find the bgp_path in the RIB corresponding to this * prefix and rfd */ afi = family2afi(p->family); assert(afi == AFI_IP || afi == AFI_IP6); bgp = bgp_get_default(); /* assume 1 instance for now */ assert(bgp); if (safi == SAFI_ENCAP) { memset(&prd0, 0, sizeof(prd0)); prd0.family = AF_UNSPEC; prd0.prefixlen = 64; prd = &prd0; } else { prd = &rfd->rd; } bn = bgp_afi_node_get(bgp->rib[afi][safi], afi, safi, p, prd); vty_out(vty, " bn=%p%s", bn, HVTYNL); for (bpi = bgp_node_get_bgp_path_info(bn); bpi; bpi = bpi->next) { if (bpi->peer == rfd->peer && bpi->type == type && bpi->sub_type == BGP_ROUTE_RFP && bpi->extra && bpi->extra->vnc.export.rfapi_handle == (void *)rfd) { rfapiPrintBi(vty, bpi); printed = 1; } } if (!printed) { vty_out(vty, " --?--%s", HVTYNL); return; } } void rfapiPrintDescriptor(struct vty *vty, struct rfapi_descriptor *rfd) { /* pHD un-addr vn-addr pCB cookie rd lifetime */ /* RT export list */ /* RT import list */ /* list of advertised prefixes */ /* dump import table */ char *s; void *cursor; int rc; afi_t afi; struct rfapi_adb *adb; char buf[PREFIX_STRLEN]; vty_out(vty, "%-10p ", rfd); rfapiPrintRfapiIpAddr(vty, &rfd->un_addr); vty_out(vty, " "); rfapiPrintRfapiIpAddr(vty, &rfd->vn_addr); vty_out(vty, " %p %p ", rfd->response_cb, rfd->cookie); rfapiPrintRd(vty, &rfd->rd); vty_out(vty, " %d", rfd->response_lifetime); vty_out(vty, " %s", (rfd->rfg ? rfd->rfg->name : "")); vty_out(vty, "%s", HVTYNL); vty_out(vty, " Peer %p #%d%s", rfd->peer, rfd->peer->lock, HVTYNL); /* export RT list */ if (rfd->rt_export_list) { s = ecommunity_ecom2str(rfd->rt_export_list, ECOMMUNITY_FORMAT_ROUTE_MAP, 0); vty_out(vty, " Export %s%s", s, HVTYNL); XFREE(MTYPE_ECOMMUNITY_STR, s); } else { vty_out(vty, " Export (nil)%s", HVTYNL); } /* import RT list */ if (rfd->import_table) { s = ecommunity_ecom2str(rfd->import_table->rt_import_list, ECOMMUNITY_FORMAT_ROUTE_MAP, 0); vty_out(vty, " Import %s%s", s, HVTYNL); XFREE(MTYPE_ECOMMUNITY_STR, s); } else { vty_out(vty, " Import (nil)%s", HVTYNL); } for (afi = AFI_IP; afi < AFI_MAX; ++afi) { uint8_t family; family = afi2family(afi); if (!family) continue; cursor = NULL; for (rc = skiplist_next(rfd->advertised.ipN_by_prefix, NULL, (void **)&adb, &cursor); rc == 0; rc = skiplist_next(rfd->advertised.ipN_by_prefix, NULL, (void **)&adb, &cursor)) { /* group like family prefixes together in output */ if (family != adb->u.s.prefix_ip.family) continue; prefix2str(&adb->u.s.prefix_ip, buf, sizeof(buf)); vty_out(vty, " Adv Pfx: %s%s", buf, HVTYNL); rfapiPrintAdvertisedInfo(vty, rfd, SAFI_MPLS_VPN, &adb->u.s.prefix_ip); } } for (rc = skiplist_next(rfd->advertised.ip0_by_ether, NULL, (void **)&adb, &cursor); rc == 0; rc = skiplist_next(rfd->advertised.ip0_by_ether, NULL, (void **)&adb, &cursor)) { prefix2str(&adb->u.s.prefix_eth, buf, sizeof(buf)); vty_out(vty, " Adv Pfx: %s%s", buf, HVTYNL); /* TBD update the following function to print ethernet info */ /* Also need to pass/use rd */ rfapiPrintAdvertisedInfo(vty, rfd, SAFI_MPLS_VPN, &adb->u.s.prefix_ip); } vty_out(vty, "%s", HVTYNL); } /* * test scripts rely on first line for each nve starting in 1st column, * leading whitespace for additional detail of that nve */ void rfapiPrintMatchingDescriptors(struct vty *vty, struct prefix *vn_prefix, struct prefix *un_prefix) { struct bgp *bgp; struct rfapi *h; struct listnode *ln; struct rfapi_descriptor *rfd; int printed = 0; bgp = bgp_get_default(); /* assume 1 instance for now */ if (!bgp) return; h = bgp->rfapi; assert(h); for (ln = listhead(&h->descriptors); ln; ln = listnextnode(ln)) { rfd = listgetdata(ln); struct prefix pfx; if (vn_prefix) { assert(!rfapiRaddr2Qprefix(&rfd->vn_addr, &pfx)); if (!prefix_match(vn_prefix, &pfx)) continue; } if (un_prefix) { assert(!rfapiRaddr2Qprefix(&rfd->un_addr, &pfx)); if (!prefix_match(un_prefix, &pfx)) continue; } if (!printed) { /* print column header */ vty_out(vty, "%s %s %s %s %s %s %s %s%s", "descriptor", "un-addr", "vn-addr", "callback", "cookie", "RD", "lifetime", "group", HVTYNL); } rfapiPrintDescriptor(vty, rfd); printed = 1; } } /* * Parse an address and put into a struct prefix */ int rfapiCliGetPrefixAddr(struct vty *vty, const char *str, struct prefix *p) { if (!str2prefix(str, p)) { vty_out(vty, "Malformed address \"%s\"%s", str ? str : "null", HVTYNL); return CMD_WARNING; } switch (p->family) { case AF_INET: if (p->prefixlen != 32) { vty_out(vty, "Not a host address: \"%s\"%s", str, HVTYNL); return CMD_WARNING; } break; case AF_INET6: if (p->prefixlen != 128) { vty_out(vty, "Not a host address: \"%s\"%s", str, HVTYNL); return CMD_WARNING; } break; default: vty_out(vty, "Invalid address \"%s\"%s", str, HVTYNL); return CMD_WARNING; } return 0; } int rfapiCliGetRfapiIpAddr(struct vty *vty, const char *str, struct rfapi_ip_addr *hai) { struct prefix pfx; int rc; rc = rfapiCliGetPrefixAddr(vty, str, &pfx); if (rc) return rc; hai->addr_family = pfx.family; if (pfx.family == AF_INET) hai->addr.v4 = pfx.u.prefix4; else hai->addr.v6 = pfx.u.prefix6; return 0; } /* * Note: this function does not flush vty output, so if it is called * with a stream pointing to a vty, the user will have to type something * before the callback output shows up */ void rfapiPrintNhl(void *stream, struct rfapi_next_hop_entry *next_hops) { struct rfapi_next_hop_entry *nh; int count; int (*fp)(void *, const char *, ...); struct vty *vty; void *out; const char *vty_newline; #define REMAIN (BUFSIZ - (p-line)) #define INCP {p += (r > REMAIN)? REMAIN: r;} if (rfapiStream2Vty(stream, &fp, &vty, &out, &vty_newline) == 0) return; for (nh = next_hops, count = 1; nh; nh = nh->next, ++count) { char line[BUFSIZ]; char *p = line; int r; r = snprintf(p, REMAIN, "%3d pfx=", count); INCP; if (rfapiRfapiIpPrefix2Str(&nh->prefix, p, REMAIN)) { /* it fit, so count length */ r = strlen(p); } else { /* didn't fit */ goto truncate; } INCP; r = snprintf(p, REMAIN, ", un="); INCP; if (rfapiRfapiIpAddr2Str(&nh->un_address, p, REMAIN)) { /* it fit, so count length */ r = strlen(p); } else { /* didn't fit */ goto truncate; } INCP; r = snprintf(p, REMAIN, ", vn="); INCP; if (rfapiRfapiIpAddr2Str(&nh->vn_address, p, REMAIN)) { /* it fit, so count length */ r = strlen(p); } else { /* didn't fit */ goto truncate; } INCP; truncate: line[BUFSIZ - 1] = 0; fp(out, "%s%s", line, HVTYNL); /* * options */ if (nh->vn_options) { struct rfapi_vn_option *vo; char offset[] = " "; for (vo = nh->vn_options; vo; vo = vo->next) { char pbuf[100]; switch (vo->type) { case RFAPI_VN_OPTION_TYPE_L2ADDR: rfapiEthAddr2Str(&vo->v.l2addr.macaddr, pbuf, sizeof(pbuf)); fp(out, "%sL2 %s LBL=0x%06x NETID=0x%06x NVEID=%d%s", offset, pbuf, (vo->v.l2addr.label & 0x00ffffff), (vo->v.l2addr.logical_net_id & 0x00ffffff), vo->v.l2addr.local_nve_id, HVTYNL); break; case RFAPI_VN_OPTION_TYPE_LOCAL_NEXTHOP: prefix2str(&vo->v.local_nexthop.addr, pbuf, sizeof(pbuf)); fp(out, "%sLNH %s cost=%d%s", offset, pbuf, vo->v.local_nexthop.cost, HVTYNL); break; default: fp(out, "%svn option type %d (unknown)%s", offset, vo->type, HVTYNL); break; } } } if (nh->un_options) { struct rfapi_un_option *uo; char offset[] = " "; for (uo = nh->un_options; uo; uo = uo->next) { switch (uo->type) { case RFAPI_UN_OPTION_TYPE_TUNNELTYPE: rfapi_print_tunneltype_option( stream, 8, &uo->v.tunnel); break; default: fp(out, "%sUN Option type %d%s", offset, uo->type, vty_newline); break; } } } } } /*********************************************************************** * STATIC ROUTES ***********************************************************************/ /* * Add another nexthop to the NHL */ static void rfapiAddDeleteLocalRfpPrefix(struct rfapi_ip_addr *un_addr, struct rfapi_ip_addr *vn_addr, struct rfapi_ip_prefix *rprefix, int is_add, uint32_t lifetime, /* add only */ struct rfapi_vn_option *vn_options, struct rfapi_next_hop_entry **head, struct rfapi_next_hop_entry **tail) { struct rfapi_next_hop_entry *new; /* * construct NHL */ new = XCALLOC(MTYPE_RFAPI_NEXTHOP, sizeof(struct rfapi_next_hop_entry)); new->prefix = *rprefix; new->un_address = *un_addr; new->vn_address = *vn_addr; new->vn_options = vn_options; if (is_add) { new->lifetime = lifetime; } else { new->lifetime = RFAPI_REMOVE_RESPONSE_LIFETIME; } if (*tail) (*tail)->next = new; *tail = new; if (!*head) { *head = new; } } static int register_add(struct vty *vty, struct cmd_token *carg_prefix, struct cmd_token *carg_vn, struct cmd_token *carg_un, struct cmd_token *carg_cost, /* optional */ struct cmd_token *carg_lifetime, /* optional */ struct cmd_token *carg_macaddr, /* optional */ struct cmd_token *carg_vni, /* mac present=>mandatory Virtual Network ID */ int argc, struct cmd_token **argv) { const char *arg_prefix = carg_prefix ? carg_prefix->arg : NULL; const char *arg_vn = carg_vn ? carg_vn->arg : NULL; const char *arg_un = carg_un ? carg_un->arg : NULL; const char *arg_cost = carg_cost ? carg_cost->arg : NULL; const char *arg_lifetime = carg_lifetime ? carg_lifetime->arg : NULL; const char *arg_macaddr = carg_macaddr ? carg_macaddr->arg : NULL; const char *arg_vni = carg_vni ? carg_vni->arg : NULL; struct rfapi_ip_addr vn_address; struct rfapi_ip_addr un_address; struct prefix pfx; struct rfapi_ip_prefix rpfx; uint32_t cost; uint32_t lnh_cost; uint32_t lifetime; rfapi_handle rfd; struct rfapi_vn_option optary[10]; /* XXX must be big enough */ struct rfapi_vn_option *opt = NULL; int opt_next = 0; int rc = CMD_WARNING_CONFIG_FAILED; char *endptr; struct bgp *bgp; struct rfapi *h; struct rfapi_cfg *rfapi_cfg; const char *arg_lnh = NULL; const char *arg_lnh_cost = NULL; bgp = bgp_get_default(); /* assume 1 instance for now */ if (!bgp) { if (vty) vty_out(vty, "BGP not configured\n"); return CMD_WARNING_CONFIG_FAILED; } h = bgp->rfapi; rfapi_cfg = bgp->rfapi_cfg; if (!h || !rfapi_cfg) { if (vty) vty_out(vty, "RFAPI not configured\n"); return CMD_WARNING_CONFIG_FAILED; } for (; argc; --argc, ++argv) { if (strmatch(argv[0]->text, "local-next-hop")) { if (arg_lnh) { vty_out(vty, "local-next-hop specified more than once\n"); return CMD_WARNING_CONFIG_FAILED; } if (argc <= 1) { vty_out(vty, "Missing parameter for local-next-hop\n"); return CMD_WARNING_CONFIG_FAILED; } ++argv; --argc; arg_lnh = argv[0]->arg; } if (strmatch(argv[0]->text, "local-cost")) { if (arg_lnh_cost) { vty_out(vty, "local-cost specified more than once\n"); return CMD_WARNING_CONFIG_FAILED; } if (argc <= 1) { vty_out(vty, "Missing parameter for local-cost\n"); return CMD_WARNING_CONFIG_FAILED; } ++argv; --argc; arg_lnh_cost = argv[0]->arg; } } if ((rc = rfapiCliGetRfapiIpAddr(vty, arg_vn, &vn_address))) goto fail; if ((rc = rfapiCliGetRfapiIpAddr(vty, arg_un, &un_address))) goto fail; /* arg_prefix is optional if mac address is given */ if (arg_macaddr && !arg_prefix) { /* * fake up a 0/32 or 0/128 prefix */ switch (vn_address.addr_family) { case AF_INET: arg_prefix = "0.0.0.0/32"; break; case AF_INET6: arg_prefix = "0::0/128"; break; default: vty_out(vty, "Internal error, unknown VN address family\n"); return CMD_WARNING_CONFIG_FAILED; } } if (!str2prefix(arg_prefix, &pfx)) { vty_out(vty, "Malformed prefix \"%s\"\n", arg_prefix); goto fail; } if (pfx.family != AF_INET && pfx.family != AF_INET6) { vty_out(vty, "prefix \"%s\" has invalid address family\n", arg_prefix); goto fail; } memset(optary, 0, sizeof(optary)); if (arg_cost) { endptr = NULL; cost = strtoul(arg_cost, &endptr, 10); if (*endptr != '\0' || cost > 255) { vty_out(vty, "%% Invalid %s value\n", "cost"); goto fail; } } else { cost = 255; } if (arg_lifetime) { if (!strcmp(arg_lifetime, "infinite")) { lifetime = RFAPI_INFINITE_LIFETIME; } else { endptr = NULL; lifetime = strtoul(arg_lifetime, &endptr, 10); if (*endptr != '\0') { vty_out(vty, "%% Invalid %s value\n", "lifetime"); goto fail; } } } else { lifetime = RFAPI_INFINITE_LIFETIME; /* default infinite */ } if (arg_lnh_cost) { if (!arg_lnh) { vty_out(vty, "%% %s may only be specified with local-next-hop\n", "local-cost"); goto fail; } endptr = NULL; lnh_cost = strtoul(arg_lnh_cost, &endptr, 10); if (*endptr != '\0' || lnh_cost > 255) { vty_out(vty, "%% Invalid %s value\n", "local-cost"); goto fail; } } else { lnh_cost = 255; } if (arg_lnh) { if (!arg_prefix) { vty_out(vty, "%% %s may only be specified with prefix\n", "local-next-hop"); goto fail; } if ((rc = rfapiCliGetPrefixAddr( vty, arg_lnh, &optary[opt_next].v.local_nexthop.addr))) { goto fail; } optary[opt_next].v.local_nexthop.cost = lnh_cost; optary[opt_next].type = RFAPI_VN_OPTION_TYPE_LOCAL_NEXTHOP; if (opt_next) { optary[opt_next - 1].next = optary + opt_next; } else { opt = optary; } ++opt_next; } if (arg_vni && !arg_macaddr) { vty_out(vty, "%% %s may only be specified with mac address\n", "virtual-network-identifier"); goto fail; } if (arg_macaddr) { if (!arg_vni) { vty_out(vty, "Missing \"vni\" parameter (mandatory with mac)\n"); return CMD_WARNING_CONFIG_FAILED; } optary[opt_next].v.l2addr.logical_net_id = strtoul(arg_vni, NULL, 10); if ((rc = rfapiStr2EthAddr( arg_macaddr, &optary[opt_next].v.l2addr.macaddr))) { vty_out(vty, "Invalid %s value\n", "mac address"); goto fail; } /* TBD label, NVE ID */ optary[opt_next].type = RFAPI_VN_OPTION_TYPE_L2ADDR; if (opt_next) { optary[opt_next - 1].next = optary + opt_next; } else { opt = optary; } ++opt_next; } vnc_zlog_debug_verbose( "%s: vn=%s, un=%s, prefix=%s, cost=%s, lifetime=%s, lnh=%s", __func__, arg_vn, arg_un, arg_prefix, (arg_cost ? arg_cost : "NULL"), (arg_lifetime ? arg_lifetime : "NULL"), (arg_lnh ? arg_lnh : "NULL")); rfapiQprefix2Rprefix(&pfx, &rpfx); rpfx.cost = cost & 255; /* look up rf descriptor, call open if it doesn't exist */ rc = rfapi_find_rfd(bgp, &vn_address, &un_address, (struct rfapi_descriptor **)&rfd); if (rc) { if (ENOENT == rc) { struct rfapi_un_option uo; /* * flag descriptor as provisionally opened for static * route * registration so that we can fix up the other * parameters * when the real open comes along */ memset(&uo, 0, sizeof(uo)); uo.type = RFAPI_UN_OPTION_TYPE_PROVISIONAL; rc = rfapi_open(rfapi_get_rfp_start_val_by_bgp(bgp), &vn_address, &un_address, &uo, /* flags */ NULL, NULL, /* no userdata */ &rfd); if (rc) { vty_out(vty, "Can't open session for this NVE: %s\n", rfapi_error_str(rc)); rc = CMD_WARNING_CONFIG_FAILED; goto fail; } } else { vty_out(vty, "Can't find session for this NVE: %s\n", rfapi_error_str(rc)); goto fail; } } rc = rfapi_register(rfd, &rpfx, lifetime, NULL, opt, RFAPI_REGISTER_ADD); if (!rc) { struct rfapi_next_hop_entry *head = NULL; struct rfapi_next_hop_entry *tail = NULL; struct rfapi_vn_option *vn_opt_new; vnc_zlog_debug_verbose( "%s: rfapi_register succeeded, returning 0", __func__); if (h->rfp_methods.local_cb) { struct rfapi_descriptor *r = (struct rfapi_descriptor *)rfd; vn_opt_new = rfapi_vn_options_dup(opt); rfapiAddDeleteLocalRfpPrefix(&r->un_addr, &r->vn_addr, &rpfx, 1, lifetime, vn_opt_new, &head, &tail); if (head) { h->flags |= RFAPI_INCALLBACK; (*h->rfp_methods.local_cb)(head, r->cookie); h->flags &= ~RFAPI_INCALLBACK; } head = tail = NULL; } return 0; } vnc_zlog_debug_verbose("%s: rfapi_register failed", __func__); vty_out(vty, "\n"); vty_out(vty, "Registration failed.\n"); vty_out(vty, "Confirm that either the VN or UN address matches a configured NVE group.\n"); return CMD_WARNING_CONFIG_FAILED; fail: vnc_zlog_debug_verbose("%s: fail, rc=%d", __func__, rc); return rc; } /************************************************************************ * Add prefix With LNH_OPTIONS... ************************************************************************/ DEFUN (add_vnc_prefix_cost_life_lnh, add_vnc_prefix_cost_life_lnh_cmd, "add vnc prefix vn un cost (0-255) lifetime (1-4294967295) LNH_OPTIONS...", "Add registration\n" "VNC Information\n" "Add/modify prefix related information\n" "IPv4 prefix\n" "IPv6 prefix\n" "VN address of NVE\n" "VN IPv4 interface address\n" "VN IPv6 interface address\n" "UN address of NVE\n" "UN IPv4 interface address\n" "UN IPv6 interface address\n" "Administrative cost [default: 255]\n" "Administrative cost\n" "Registration lifetime [default: infinite]\n" "Lifetime value in seconds\n" "[local-next-hop (A.B.C.D|X:X::X:X)] [local-cost <0-255>]\n") { /* pfx vn un cost life */ return register_add(vty, argv[3], argv[5], argv[7], argv[9], argv[11], /* mac vni */ NULL, NULL, argc - 12, argv + 12); } DEFUN (add_vnc_prefix_life_cost_lnh, add_vnc_prefix_life_cost_lnh_cmd, "add vnc prefix vn un lifetime (1-4294967295) cost (0-255) LNH_OPTIONS...", "Add registration\n" "VNC Information\n" "Add/modify prefix related information\n" "IPv4 prefix\n" "IPv6 prefix\n" "VN address of NVE\n" "VN IPv4 interface address\n" "VN IPv6 interface address\n" "UN address of NVE\n" "UN IPv4 interface address\n" "UN IPv6 interface address\n" "Registration lifetime [default: infinite]\n" "Lifetime value in seconds\n" "Administrative cost [default: 255]\n" "Administrative cost\n" "[local-next-hop (A.B.C.D|X:X::X:X)] [local-cost <0-255>]\n") { /* pfx vn un cost life */ return register_add(vty, argv[3], argv[5], argv[7], argv[11], argv[9], /* mac vni */ NULL, NULL, argc - 12, argv + 12); } DEFUN (add_vnc_prefix_cost_lnh, add_vnc_prefix_cost_lnh_cmd, "add vnc prefix vn un cost (0-255) LNH_OPTIONS...", "Add registration\n" "VNC Information\n" "Add/modify prefix related information\n" "IPv4 prefix\n" "IPv6 prefix\n" "VN address of NVE\n" "VN IPv4 interface address\n" "VN IPv6 interface address\n" "UN address of NVE\n" "UN IPv4 interface address\n" "UN IPv6 interface address\n" "Administrative cost [default: 255]\n" "Administrative cost\n" "[local-next-hop (A.B.C.D|X:X::X:X)] [local-cost <0-255>]\n") { /* pfx vn un cost life */ return register_add(vty, argv[3], argv[5], argv[7], argv[9], NULL, /* mac vni */ NULL, NULL, argc - 10, argv + 10); } DEFUN (add_vnc_prefix_life_lnh, add_vnc_prefix_life_lnh_cmd, "add vnc prefix vn un lifetime (1-4294967295) LNH_OPTIONS...", "Add registration\n" "VNC Information\n" "Add/modify prefix related information\n" "IPv4 prefix\n" "IPv6 prefix\n" "VN address of NVE\n" "VN IPv4 interface address\n" "VN IPv6 interface address\n" "UN address of NVE\n" "UN IPv4 interface address\n" "UN IPv6 interface address\n" "Registration lifetime [default: infinite]\n" "Lifetime value in seconds\n" "[local-next-hop (A.B.C.D|X:X::X:X)] [local-cost <0-255>]\n") { /* pfx vn un cost life */ return register_add(vty, argv[3], argv[5], argv[7], NULL, argv[9], /* mac vni */ NULL, NULL, argc - 10, argv + 10); } DEFUN (add_vnc_prefix_lnh, add_vnc_prefix_lnh_cmd, "add vnc prefix vn un LNH_OPTIONS...", "Add registration\n" "VNC Information\n" "Add/modify prefix related information\n" "IPv4 prefix\n" "IPv6 prefix\n" "VN address of NVE\n" "VN IPv4 interface address\n" "VN IPv6 interface address\n" "UN address of NVE\n" "UN IPv4 interface address\n" "UN IPv6 interface address\n" "[local-next-hop (A.B.C.D|X:X::X:X)] [local-cost <0-255>]\n") { /* pfx vn un cost life */ return register_add(vty, argv[3], argv[5], argv[7], NULL, NULL, /* mac vni */ NULL, NULL, argc - 8, argv + 8); } /************************************************************************ * Add prefix Without LNH_OPTIONS... ************************************************************************/ DEFUN (add_vnc_prefix_cost_life, add_vnc_prefix_cost_life_cmd, "add vnc prefix vn un cost (0-255) lifetime (1-4294967295)", "Add registration\n" "VNC Information\n" "Add/modify prefix related information\n" "IPv4 prefix\n" "IPv6 prefix\n" "VN address of NVE\n" "VN IPv4 interface address\n" "VN IPv6 interface address\n" "UN address of NVE\n" "UN IPv4 interface address\n" "UN IPv6 interface address\n" "Administrative cost [default: 255]\n" "Administrative cost\n" "Registration lifetime [default: infinite]\n" "Lifetime value in seconds\n") { /* pfx vn un cost life */ return register_add(vty, argv[3], argv[5], argv[7], argv[9], argv[11], /* mac vni */ NULL, NULL, 0, NULL); } DEFUN (add_vnc_prefix_life_cost, add_vnc_prefix_life_cost_cmd, "add vnc prefix vn un lifetime (1-4294967295) cost (0-255)", "Add registration\n" "VNC Information\n" "Add/modify prefix related information\n" "IPv4 prefix\n" "IPv6 prefix\n" "VN address of NVE\n" "VN IPv4 interface address\n" "VN IPv6 interface address\n" "UN address of NVE\n" "UN IPv4 interface address\n" "UN IPv6 interface address\n" "Registration lifetime [default: infinite]\n" "Lifetime value in seconds\n" "Administrative cost [default: 255]\n" "Administrative cost\n") { /* pfx vn un cost life */ return register_add(vty, argv[3], argv[5], argv[7], argv[11], argv[9], /* mac vni */ NULL, NULL, 0, NULL); } DEFUN (add_vnc_prefix_cost, add_vnc_prefix_cost_cmd, "add vnc prefix vn un cost (0-255)", "Add registration\n" "VNC Information\n" "Add/modify prefix related information\n" "IPv4 prefix\n" "IPv6 prefix\n" "VN address of NVE\n" "VN IPv4 interface address\n" "VN IPv6 interface address\n" "UN address of NVE\n" "UN IPv4 interface address\n" "UN IPv6 interface address\n" "Administrative cost [default: 255]\n" "Administrative cost\n") { /* pfx vn un cost life */ return register_add(vty, argv[3], argv[5], argv[7], argv[9], NULL, /* mac vni */ NULL, NULL, 0, NULL); } DEFUN (add_vnc_prefix_life, add_vnc_prefix_life_cmd, "add vnc prefix vn un lifetime (1-4294967295)", "Add registration\n" "VNC Information\n" "Add/modify prefix related information\n" "IPv4 prefix\n" "IPv6 prefix\n" "VN address of NVE\n" "VN IPv4 interface address\n" "VN IPv6 interface address\n" "UN address of NVE\n" "UN IPv4 interface address\n" "UN IPv6 interface address\n" "Registration lifetime [default: infinite]\n" "Lifetime value in seconds\n") { /* pfx vn un cost life */ return register_add(vty, argv[3], argv[5], argv[7], NULL, argv[9], /* mac vni */ NULL, NULL, 0, NULL); } DEFUN (add_vnc_prefix, add_vnc_prefix_cmd, "add vnc prefix vn un ", "Add registration\n" "VNC Information\n" "Add/modify prefix related information\n" "IPv4 prefix\n" "IPv6 prefix\n" "VN address of NVE\n" "VN IPv4 interface address\n" "VN IPv6 interface address\n" "UN address of NVE\n" "UN IPv4 interface address\n" "UN IPv6 interface address\n") { /* pfx vn un cost life */ return register_add(vty, argv[3], argv[5], argv[7], NULL, NULL, /* mac vni */ NULL, NULL, 0, NULL); } /************************************************************************ * Mac address registrations ************************************************************************/ DEFUN (add_vnc_mac_vni_prefix_cost_life, add_vnc_mac_vni_prefix_cost_life_cmd, "add vnc mac YY:YY:YY:YY:YY:YY virtual-network-identifier (1-4294967295) vn un prefix cost (0-255) lifetime (1-4294967295)", "Add registration\n" "VNC Information\n" "Add/modify mac address information\n" "MAC address\n" "Virtual Network Identifier follows\n" "Virtual Network Identifier\n" "VN address of NVE\n" "VN IPv4 interface address\n" "VN IPv6 interface address\n" "UN address of NVE\n" "UN IPv4 interface address\n" "UN IPv6 interface address\n" "Add/modify prefix related information\n" "IPv4 prefix\n" "IPv6 prefix\n" "Administrative cost [default: 255]\n" "Administrative cost\n" "Registration lifetime [default: infinite]\n" "Lifetime value in seconds\n") { /* pfx vn un cost life */ return register_add(vty, argv[11], argv[7], argv[9], argv[13], argv[15], /* mac vni */ argv[3], argv[5], 0, NULL); } DEFUN (add_vnc_mac_vni_prefix_life, add_vnc_mac_vni_prefix_life_cmd, "add vnc mac YY:YY:YY:YY:YY:YY virtual-network-identifier (1-4294967295) vn un prefix lifetime (1-4294967295)", "Add registration\n" "VNC Information\n" "Add/modify mac address information\n" "MAC address\n" "Virtual Network Identifier follows\n" "Virtual Network Identifier\n" "VN address of NVE\n" "VN IPv4 interface address\n" "VN IPv6 interface address\n" "UN address of NVE\n" "UN IPv4 interface address\n" "UN IPv6 interface address\n" "Add/modify prefix related information\n" "IPv4 prefix\n" "IPv6 prefix\n" "Registration lifetime [default: infinite]\n" "Lifetime value in seconds\n") { /* pfx vn un cost life */ return register_add(vty, argv[11], argv[7], argv[9], NULL, argv[13], /* mac vni */ argv[3], argv[5], 0, NULL); } DEFUN (add_vnc_mac_vni_prefix_cost, add_vnc_mac_vni_prefix_cost_cmd, "add vnc mac YY:YY:YY:YY:YY:YY virtual-network-identifier (1-4294967295) vn un prefix cost (0-255)", "Add registration\n" "VNC Information\n" "Add/modify mac address information\n" "MAC address\n" "Virtual Network Identifier follows\n" "Virtual Network Identifier\n" "VN address of NVE\n" "VN IPv4 interface address\n" "VN IPv6 interface address\n" "UN address of NVE\n" "UN IPv4 interface address\n" "UN IPv6 interface address\n" "Add/modify prefix related information\n" "IPv4 prefix\n" "IPv6 prefix\n" "Administrative cost [default: 255]\n" "Administrative cost\n") { /* pfx vn un cost life */ return register_add(vty, argv[11], argv[7], argv[9], argv[13], NULL, /* mac vni */ argv[3], argv[5], 0, NULL); } DEFUN (add_vnc_mac_vni_prefix, add_vnc_mac_vni_prefix_cmd, "add vnc mac YY:YY:YY:YY:YY:YY virtual-network-identifier (1-4294967295) vn un prefix ", "Add registration\n" "VNC Information\n" "Add/modify mac address information\n" "MAC address\n" "Virtual Network Identifier follows\n" "Virtual Network Identifier\n" "VN address of NVE\n" "VN IPv4 interface address\n" "VN IPv6 interface address\n" "UN address of NVE\n" "UN IPv4 interface address\n" "UN IPv6 interface address\n" "Add/modify prefix related information\n" "IPv4 prefix\n" "IPv6 prefix\n") { /* pfx vn un cost life */ return register_add(vty, argv[11], argv[7], argv[9], NULL, NULL, /* mac vni */ argv[3], argv[5], 0, NULL); } DEFUN (add_vnc_mac_vni_cost_life, add_vnc_mac_vni_cost_life_cmd, "add vnc mac YY:YY:YY:YY:YY:YY virtual-network-identifier (1-4294967295) vn un cost (0-255) lifetime (1-4294967295)", "Add registration\n" "VNC Information\n" "Add/modify mac address information\n" "MAC address\n" "Virtual Network Identifier follows\n" "Virtual Network Identifier\n" "VN address of NVE\n" "VN IPv4 interface address\n" "VN IPv6 interface address\n" "UN address of NVE\n" "UN IPv4 interface address\n" "UN IPv6 interface address\n" "Administrative cost [default: 255]\n" "Administrative cost\n" "Registration lifetime [default: infinite]\n" "Lifetime value in seconds\n") { /* pfx vn un cost life */ return register_add(vty, NULL, argv[7], argv[9], argv[11], argv[13], /* mac vni */ argv[3], argv[5], 0, NULL); } DEFUN (add_vnc_mac_vni_cost, add_vnc_mac_vni_cost_cmd, "add vnc mac YY:YY:YY:YY:YY:YY virtual-network-identifier (1-4294967295) vn un cost (0-255)", "Add registration\n" "VNC Information\n" "Add/modify mac address information\n" "MAC address\n" "Virtual Network Identifier follows\n" "Virtual Network Identifier\n" "VN address of NVE\n" "VN IPv4 interface address\n" "VN IPv6 interface address\n" "UN address of NVE\n" "UN IPv4 interface address\n" "UN IPv6 interface address\n" "Administrative cost [default: 255]\n" "Administrative cost\n") { /* pfx vn un cost life */ return register_add(vty, NULL, argv[7], argv[9], argv[11], NULL, /* mac vni */ argv[3], argv[5], 0, NULL); } DEFUN (add_vnc_mac_vni_life, add_vnc_mac_vni_life_cmd, "add vnc mac YY:YY:YY:YY:YY:YY virtual-network-identifier (1-4294967295) vn un lifetime (1-4294967295)", "Add registration\n" "VNC Information\n" "Add/modify mac address information\n" "MAC address\n" "Virtual Network Identifier follows\n" "Virtual Network Identifier\n" "VN address of NVE\n" "VN IPv4 interface address\n" "VN IPv6 interface address\n" "UN address of NVE\n" "UN IPv4 interface address\n" "UN IPv6 interface address\n" "Registration lifetime [default: infinite]\n" "Lifetime value in seconds\n") { /* pfx vn un cost life */ return register_add(vty, NULL, argv[7], argv[9], NULL, argv[11], /* mac vni */ argv[3], argv[5], 0, NULL); } DEFUN (add_vnc_mac_vni, add_vnc_mac_vni_cmd, "add vnc mac YY:YY:YY:YY:YY:YY virtual-network-identifier (1-4294967295) vn un ", "Add registration\n" "VNC Information\n" "Add/modify mac address information\n" "MAC address\n" "Virtual Network Identifier follows\n" "Virtual Network Identifier\n" "VN address of NVE\n" "VN IPv4 interface address\n" "VN IPv6 interface address\n" "UN address of NVE\n" "UN IPv4 interface address\n" "UN IPv6 interface address\n") { /* pfx vn un cost life */ return register_add(vty, NULL, argv[7], argv[9], NULL, NULL, /* mac vni */ argv[3], argv[5], 0, NULL); } /************************************************************************ * Delete prefix ************************************************************************/ struct rfapi_local_reg_delete_arg { /* * match parameters */ struct bgp *bgp; struct rfapi_ip_addr un_address; /* AF==0: wildcard */ struct rfapi_ip_addr vn_address; /* AF==0: wildcard */ struct prefix prefix; /* AF==0: wildcard */ struct prefix_rd rd; /* plen!=64: wildcard */ struct rfapi_nve_group_cfg *rfg; /* NULL: wildcard */ struct rfapi_l2address_option_match l2o; /* * result parameters */ struct vty *vty; uint32_t reg_count; uint32_t pfx_count; uint32_t query_count; uint32_t failed_pfx_count; uint32_t nve_count; struct skiplist *nves; uint32_t remote_active_nve_count; uint32_t remote_active_pfx_count; uint32_t remote_holddown_nve_count; uint32_t remote_holddown_pfx_count; }; struct nve_addr { struct rfapi_ip_addr vn; struct rfapi_ip_addr un; struct rfapi_descriptor *rfd; struct rfapi_local_reg_delete_arg *cda; }; static void nve_addr_free(void *hap) { ((struct nve_addr *)hap)->cda->nve_count += 1; XFREE(MTYPE_RFAPI_NVE_ADDR, hap); } static int nve_addr_cmp(void *k1, void *k2) { struct nve_addr *a = (struct nve_addr *)k1; struct nve_addr *b = (struct nve_addr *)k2; int ret = 0; if (!a || !b) { return (a - b); } if (a->un.addr_family != b->un.addr_family) { return (a->un.addr_family - b->un.addr_family); } if (a->vn.addr_family != b->vn.addr_family) { return (a->vn.addr_family - b->vn.addr_family); } if (a->un.addr_family == AF_INET) { ret = IPV4_ADDR_CMP(&a->un.addr.v4, &b->un.addr.v4); if (ret != 0) { return ret; } } else if (a->un.addr_family == AF_INET6) { ret = IPV6_ADDR_CMP(&a->un.addr.v6, &b->un.addr.v6); if (ret != 0) { return ret; } } else { assert(0); } if (a->vn.addr_family == AF_INET) { ret = IPV4_ADDR_CMP(&a->vn.addr.v4, &b->vn.addr.v4); if (ret != 0) return ret; } else if (a->vn.addr_family == AF_INET6) { ret = IPV6_ADDR_CMP(&a->vn.addr.v6, &b->vn.addr.v6); if (ret == 0) { return ret; } } else { assert(0); } return 0; } static int parse_deleter_args(struct vty *vty, struct bgp *bgp, const char *arg_prefix, const char *arg_vn, const char *arg_un, const char *arg_l2addr, const char *arg_vni, const char *arg_rd, struct rfapi_nve_group_cfg *arg_rfg, struct rfapi_local_reg_delete_arg *rcdarg) { int rc = CMD_WARNING; memset(rcdarg, 0, sizeof(struct rfapi_local_reg_delete_arg)); rcdarg->vty = vty; if (bgp == NULL) bgp = bgp_get_default(); rcdarg->bgp = bgp; rcdarg->rfg = arg_rfg; /* may be NULL */ if (arg_vn && strcmp(arg_vn, "*")) { if ((rc = rfapiCliGetRfapiIpAddr(vty, arg_vn, &rcdarg->vn_address))) return rc; } if (arg_un && strcmp(arg_un, "*")) { if ((rc = rfapiCliGetRfapiIpAddr(vty, arg_un, &rcdarg->un_address))) return rc; } if (arg_prefix && strcmp(arg_prefix, "*")) { if (!str2prefix(arg_prefix, &rcdarg->prefix)) { vty_out(vty, "Malformed prefix \"%s\"\n", arg_prefix); return rc; } } if (arg_l2addr) { if (!arg_vni) { vty_out(vty, "Missing VNI\n"); return rc; } if (strcmp(arg_l2addr, "*")) { if ((rc = rfapiStr2EthAddr(arg_l2addr, &rcdarg->l2o.o.macaddr))) { vty_out(vty, "Malformed L2 Address \"%s\"\n", arg_l2addr); return rc; } rcdarg->l2o.flags |= RFAPI_L2O_MACADDR; } if (strcmp(arg_vni, "*")) { rcdarg->l2o.o.logical_net_id = strtoul(arg_vni, NULL, 10); rcdarg->l2o.flags |= RFAPI_L2O_LNI; } } if (arg_rd) { if (!str2prefix_rd(arg_rd, &rcdarg->rd)) { vty_out(vty, "Malformed RD \"%s\"\n", arg_rd); return rc; } } return CMD_SUCCESS; } static int parse_deleter_tokens(struct vty *vty, struct bgp *bgp, struct cmd_token *carg_prefix, struct cmd_token *carg_vn, struct cmd_token *carg_un, struct cmd_token *carg_l2addr, struct cmd_token *carg_vni, struct cmd_token *carg_rd, struct rfapi_nve_group_cfg *arg_rfg, struct rfapi_local_reg_delete_arg *rcdarg) { const char *arg_prefix = carg_prefix ? carg_prefix->arg : NULL; const char *arg_vn = carg_vn ? carg_vn->arg : NULL; const char *arg_un = carg_un ? carg_un->arg : NULL; const char *arg_l2addr = carg_l2addr ? carg_l2addr->arg : NULL; const char *arg_vni = carg_vni ? carg_vni->arg : NULL; const char *arg_rd = carg_rd ? carg_rd->arg : NULL; return parse_deleter_args(vty, bgp, arg_prefix, arg_vn, arg_un, arg_l2addr, arg_vni, arg_rd, arg_rfg, rcdarg); } static void record_nve_in_cda_list(struct rfapi_local_reg_delete_arg *cda, struct rfapi_ip_addr *un_address, struct rfapi_ip_addr *vn_address, struct rfapi_descriptor *rfd) { struct nve_addr ha; struct nve_addr *hap; memset(&ha, 0, sizeof(ha)); ha.un = *un_address; ha.vn = *vn_address; ha.rfd = rfd; if (!cda->nves) cda->nves = skiplist_new(0, nve_addr_cmp, nve_addr_free); if (skiplist_search(cda->nves, &ha, (void *)&hap)) { hap = XCALLOC(MTYPE_RFAPI_NVE_ADDR, sizeof(struct nve_addr)); assert(hap); ha.cda = cda; *hap = ha; skiplist_insert(cda->nves, hap, hap); } } static void clear_vnc_responses(struct rfapi_local_reg_delete_arg *cda) { struct rfapi *h; struct rfapi_descriptor *rfd; int query_count = 0; struct listnode *node; struct bgp *bgp_default = bgp_get_default(); if (cda->vn_address.addr_family && cda->un_address.addr_family) { /* * Single nve case */ if (rfapi_find_rfd(bgp_default, &cda->vn_address, &cda->un_address, &rfd)) return; rfapiRibClear(rfd); rfapi_query_done_all(rfd, &query_count); cda->query_count += query_count; /* * Track unique nves seen */ record_nve_in_cda_list(cda, &rfd->un_addr, &rfd->vn_addr, rfd); return; } /* * wildcard case */ if (!bgp_default) return; /* ENXIO */ h = bgp_default->rfapi; if (!h) return; /* ENXIO */ for (ALL_LIST_ELEMENTS_RO(&h->descriptors, node, rfd)) { /* * match un, vn addresses of NVEs */ if (cda->un_address.addr_family && rfapi_ip_addr_cmp(&cda->un_address, &rfd->un_addr)) { continue; } if (cda->vn_address.addr_family && rfapi_ip_addr_cmp(&cda->vn_address, &rfd->vn_addr)) { continue; } rfapiRibClear(rfd); rfapi_query_done_all(rfd, &query_count); cda->query_count += query_count; /* * Track unique nves seen */ record_nve_in_cda_list(cda, &rfd->un_addr, &rfd->vn_addr, rfd); } } /* * TBD need to count deleted prefixes and nves? * * ENXIO BGP or VNC not configured */ static int rfapiDeleteLocalPrefixesByRFD(struct rfapi_local_reg_delete_arg *cda, struct rfapi_descriptor *rfd) { struct rfapi_ip_addr *pUn; /* NULL = wildcard */ struct rfapi_ip_addr *pVn; /* NULL = wildcard */ struct prefix *pPrefix; /* NULL = wildcard */ struct prefix_rd *pPrd; /* NULL = wildcard */ struct rfapi_ip_prefix rprefix; struct rfapi_next_hop_entry *head = NULL; struct rfapi_next_hop_entry *tail = NULL; #if DEBUG_L2_EXTRA vnc_zlog_debug_verbose("%s: entry", __func__); #endif pUn = (cda->un_address.addr_family ? &cda->un_address : NULL); pVn = (cda->vn_address.addr_family ? &cda->vn_address : NULL); pPrefix = (cda->prefix.family ? &cda->prefix : NULL); pPrd = (cda->rd.prefixlen == 64 ? &cda->rd : NULL); if (pPrefix) { rfapiQprefix2Rprefix(pPrefix, &rprefix); } do /* to preserve old code structure */ { struct rfapi *h = cda->bgp->rfapi; ; struct rfapi_adb *adb; int rc; int deleted_from_this_nve; struct nve_addr ha; struct nve_addr *hap; #if DEBUG_L2_EXTRA vnc_zlog_debug_verbose("%s: rfd=%p", __func__, rfd); #endif /* * match un, vn addresses of NVEs */ if (pUn && (rfapi_ip_addr_cmp(pUn, &rfd->un_addr))) break; if (pVn && (rfapi_ip_addr_cmp(pVn, &rfd->vn_addr))) break; #if DEBUG_L2_EXTRA vnc_zlog_debug_verbose("%s: un, vn match", __func__); #endif /* * match prefix */ deleted_from_this_nve = 0; { struct skiplist *sl; struct rfapi_ip_prefix rp; void *cursor; struct list *adb_delete_list; /* * The advertisements are stored in a skiplist. * Withdrawing * the registration deletes the advertisement from the * skiplist, which we can't do while iterating over that * same skiplist using the current skiplist API. * * Strategy: iterate over the skiplist and build another * list containing only the matching ADBs. Then delete * _everything_ in that second list (which can be done * using either skiplists or quagga linklists). */ adb_delete_list = list_new(); /* * Advertised IP prefixes (not 0/32 or 0/128) */ sl = rfd->advertised.ipN_by_prefix; for (cursor = NULL, rc = skiplist_next(sl, NULL, (void **)&adb, &cursor); !rc; rc = skiplist_next(sl, NULL, (void **)&adb, &cursor)) { if (pPrefix) { if (!prefix_same(pPrefix, &adb->u.s.prefix_ip)) { #if DEBUG_L2_EXTRA vnc_zlog_debug_verbose( "%s: adb=%p, prefix doesn't match, skipping", __func__, adb); #endif continue; } } if (pPrd) { if (memcmp(pPrd->val, adb->u.s.prd.val, 8) != 0) { #if DEBUG_L2_EXTRA vnc_zlog_debug_verbose( "%s: adb=%p, RD doesn't match, skipping", __func__, adb); #endif continue; } } if (CHECK_FLAG(cda->l2o.flags, RFAPI_L2O_MACADDR)) { if (memcmp(cda->l2o.o.macaddr.octet, adb->u.s.prefix_eth.u .prefix_eth.octet, ETH_ALEN)) { #if DEBUG_L2_EXTRA vnc_zlog_debug_verbose( "%s: adb=%p, macaddr doesn't match, skipping", __func__, adb); #endif continue; } } if (CHECK_FLAG(cda->l2o.flags, RFAPI_L2O_LNI)) { if (cda->l2o.o.logical_net_id != adb->l2o.logical_net_id) { #if DEBUG_L2_EXTRA vnc_zlog_debug_verbose( "%s: adb=%p, LNI doesn't match, skipping", __func__, adb); #endif continue; } } #if DEBUG_L2_EXTRA vnc_zlog_debug_verbose( "%s: ipN adding adb %p to delete list", __func__, adb); #endif listnode_add(adb_delete_list, adb); } struct listnode *node; for (ALL_LIST_ELEMENTS_RO(adb_delete_list, node, adb)) { int this_advertisement_prefix_count; struct rfapi_vn_option optary[3]; struct rfapi_vn_option *opt = NULL; int cur_opt = 0; this_advertisement_prefix_count = 1; rfapiQprefix2Rprefix(&adb->u.s.prefix_ip, &rp); memset(optary, 0, sizeof(optary)); /* if mac addr present in advert, make l2o vn * option */ if (adb->u.s.prefix_eth.family == AF_ETHERNET) { if (opt != NULL) opt->next = &optary[cur_opt]; opt = &optary[cur_opt++]; opt->type = RFAPI_VN_OPTION_TYPE_L2ADDR; opt->v.l2addr.macaddr = adb->u.s.prefix_eth.u .prefix_eth; ++this_advertisement_prefix_count; } /* * use saved RD value instead of trying to * invert * complex RD computation in rfapi_register() */ if (opt != NULL) opt->next = &optary[cur_opt]; opt = &optary[cur_opt++]; opt->type = RFAPI_VN_OPTION_TYPE_INTERNAL_RD; opt->v.internal_rd = adb->u.s.prd; #if DEBUG_L2_EXTRA vnc_zlog_debug_verbose( "%s: ipN killing reg from adb %p ", __func__, adb); #endif rc = rfapi_register(rfd, &rp, 0, NULL, (cur_opt ? optary : NULL), RFAPI_REGISTER_KILL); if (!rc) { cda->pfx_count += this_advertisement_prefix_count; cda->reg_count += 1; deleted_from_this_nve = 1; } if (h->rfp_methods.local_cb) { rfapiAddDeleteLocalRfpPrefix( &rfd->un_addr, &rfd->vn_addr, &rp, 0, 0, NULL, &head, &tail); } } list_delete_all_node(adb_delete_list); if (!(pPrefix && !RFAPI_0_PREFIX(pPrefix))) { /* * Caller didn't specify a prefix, or specified * (0/32 or 0/128) */ /* * Advertised 0/32 and 0/128 (indexed by * ethernet address) */ sl = rfd->advertised.ip0_by_ether; for (cursor = NULL, rc = skiplist_next(sl, NULL, (void **)&adb, &cursor); !rc; rc = skiplist_next(sl, NULL, (void **)&adb, &cursor)) { if (CHECK_FLAG(cda->l2o.flags, RFAPI_L2O_MACADDR)) { if (memcmp(cda->l2o.o.macaddr .octet, adb->u.s.prefix_eth.u .prefix_eth .octet, ETH_ALEN)) { continue; } } if (CHECK_FLAG(cda->l2o.flags, RFAPI_L2O_LNI)) { if (cda->l2o.o.logical_net_id != adb->l2o.logical_net_id) { continue; } } #if DEBUG_L2_EXTRA vnc_zlog_debug_verbose( "%s: ip0 adding adb %p to delete list", __func__, adb); #endif listnode_add(adb_delete_list, adb); } for (ALL_LIST_ELEMENTS_RO(adb_delete_list, node, adb)) { struct rfapi_vn_option vn; rfapiQprefix2Rprefix( &adb->u.s.prefix_ip, &rp); memset(&vn, 0, sizeof(vn)); vn.type = RFAPI_VN_OPTION_TYPE_L2ADDR; vn.v.l2addr = adb->l2o; #if DEBUG_L2_EXTRA vnc_zlog_debug_verbose( "%s: ip0 killing reg from adb %p ", __func__, adb); #endif rc = rfapi_register( rfd, &rp, 0, NULL, &vn, RFAPI_REGISTER_KILL); if (!rc) { cda->pfx_count += 1; cda->reg_count += 1; deleted_from_this_nve = 1; } if (h->rfp_methods.local_cb) { struct rfapi_vn_option *vn_opt_new; vn_opt_new = rfapi_vn_options_dup( &vn); rfapiAddDeleteLocalRfpPrefix( &rfd->un_addr, &rfd->vn_addr, &rp, 0, 0, vn_opt_new, &head, &tail); } } list_delete_all_node(adb_delete_list); } list_delete(&adb_delete_list); } if (head) { /* should not be set if (NULL == rfapi_cfg->local_cb) */ h->flags |= RFAPI_INCALLBACK; (*h->rfp_methods.local_cb)(head, rfd->cookie); h->flags &= ~RFAPI_INCALLBACK; head = tail = NULL; } if (deleted_from_this_nve) { /* * track unique NVEs seen */ memset(&ha, 0, sizeof(ha)); ha.un = rfd->un_addr; ha.vn = rfd->vn_addr; if (!cda->nves) cda->nves = skiplist_new(0, nve_addr_cmp, nve_addr_free); if (skiplist_search(cda->nves, &ha, (void **)&hap)) { hap = XCALLOC(MTYPE_RFAPI_NVE_ADDR, sizeof(struct nve_addr)); assert(hap); ha.cda = cda; *hap = ha; skiplist_insert(cda->nves, hap, hap); } } } while (0); /* to preserve old code structure */ return 0; } static int rfapiDeleteLocalPrefixes(struct rfapi_local_reg_delete_arg *cda) { int rc = 0; if (cda->rfg) { if (cda->rfg->rfd) /* if not open, nothing to delete */ rc = rfapiDeleteLocalPrefixesByRFD(cda, cda->rfg->rfd); } else { struct bgp *bgp = cda->bgp; struct rfapi *h; struct rfapi_cfg *rfapi_cfg; struct listnode *node; struct rfapi_descriptor *rfd; if (!bgp) return ENXIO; h = bgp->rfapi; rfapi_cfg = bgp->rfapi_cfg; if (!h || !rfapi_cfg) return ENXIO; vnc_zlog_debug_verbose("%s: starting descriptor loop", __func__); for (ALL_LIST_ELEMENTS_RO(&h->descriptors, node, rfd)) { rc = rfapiDeleteLocalPrefixesByRFD(cda, rfd); } } return rc; } /* * clear_vnc_prefix * * Deletes local and remote prefixes that match */ static void clear_vnc_prefix(struct rfapi_local_reg_delete_arg *cda) { struct prefix pfx_un; struct prefix pfx_vn; struct prefix *pUN = NULL; struct prefix *pVN = NULL; struct prefix *pPrefix = NULL; struct rfapi_import_table *it = NULL; /* * Delete matching remote prefixes in holddown */ if (cda->vn_address.addr_family) { if (!rfapiRaddr2Qprefix(&cda->vn_address, &pfx_vn)) pVN = &pfx_vn; } if (cda->un_address.addr_family) { if (!rfapiRaddr2Qprefix(&cda->un_address, &pfx_un)) pUN = &pfx_un; } if (cda->prefix.family) { pPrefix = &cda->prefix; } if (cda->rfg) { it = cda->rfg->rfapi_import_table; } rfapiDeleteRemotePrefixes( pUN, pVN, pPrefix, it, 0, 1, &cda->remote_active_pfx_count, &cda->remote_active_nve_count, &cda->remote_holddown_pfx_count, &cda->remote_holddown_nve_count); /* * Now do local prefixes */ rfapiDeleteLocalPrefixes(cda); } static void print_cleared_stats(struct rfapi_local_reg_delete_arg *cda) { struct vty *vty = cda->vty; /* for benefit of VTYNL */ /* Our special element-deleting function counts nves */ if (cda->nves) { skiplist_free(cda->nves); cda->nves = NULL; } if (cda->failed_pfx_count) vty_out(vty, "Failed to delete %d prefixes\n", cda->failed_pfx_count); /* left as "prefixes" even in single case for ease of machine parsing */ vty_out(vty, "[Local] Cleared %u registrations, %u prefixes, %u responses from %d NVEs\n", cda->reg_count, cda->pfx_count, cda->query_count, cda->nve_count); /* * We don't currently allow deletion of active remote prefixes from * the command line */ vty_out(vty, "[Holddown] Cleared %u prefixes from %u NVEs\n", cda->remote_holddown_pfx_count, cda->remote_holddown_nve_count); } /* * Caller has already deleted registrations and queries for this/these * NVEs. Now we just have to close their descriptors. */ static void clear_vnc_nve_closer(struct rfapi_local_reg_delete_arg *cda) { struct skiplist *sl = cda->nves; /* contains affected NVEs */ struct nve_addr *pKey; struct nve_addr *pValue; void *cursor = NULL; int rc; if (!sl) return; for (rc = skiplist_next(sl, (void **)&pKey, (void **)&pValue, &cursor); !rc; rc = skiplist_next(sl, (void **)&pKey, (void **)&pValue, &cursor)) { if (pValue->rfd) { ((struct rfapi_descriptor *)pValue->rfd)->flags |= RFAPI_HD_FLAG_CLOSING_ADMINISTRATIVELY; rfapi_close(pValue->rfd); } } } DEFUN (clear_vnc_nve_all, clear_vnc_nve_all_cmd, "clear vnc nve *", "clear\n" "VNC Information\n" "Clear per NVE information\n" "For all NVEs\n") { struct rfapi_local_reg_delete_arg cda; int rc; if ((rc = parse_deleter_args(vty, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, &cda))) return rc; cda.vty = vty; clear_vnc_responses(&cda); clear_vnc_prefix(&cda); clear_vnc_nve_closer(&cda); print_cleared_stats(&cda); return 0; } DEFUN (clear_vnc_nve_vn_un, clear_vnc_nve_vn_un_cmd, "clear vnc nve vn <*|A.B.C.D|X:X::X:X> un <*|A.B.C.D|X:X::X:X>", "clear\n" "VNC Information\n" "Clear prefix registration information\n" "VN address of NVE\n" "For all NVEs\n" "VN IPv4 interface address\n" "VN IPv6 interface address\n" "UN address of NVE\n" "For all UN addresses\n" "UN IPv4 interface address\n" "UN IPv6 interface address\n") { struct rfapi_local_reg_delete_arg cda; int rc; if ((rc = parse_deleter_tokens(vty, NULL, NULL, argv[4], argv[6], NULL, NULL, NULL, NULL, &cda))) return rc; cda.vty = vty; clear_vnc_responses(&cda); clear_vnc_prefix(&cda); clear_vnc_nve_closer(&cda); print_cleared_stats(&cda); return 0; } DEFUN (clear_vnc_nve_un_vn, clear_vnc_nve_un_vn_cmd, "clear vnc nve un <*|A.B.C.D|X:X::X:X> vn <*|A.B.C.D|X:X::X:X>", "clear\n" "VNC Information\n" "Clear prefix registration information\n" "UN address of NVE\n" "For all un NVEs\n" "UN IPv4 interface address\n" "UN IPv6 interface address\n" "VN address of NVE\n" "For all vn NVEs\n" "VN IPv4 interface address\n" "VN IPv6 interface address\n") { struct rfapi_local_reg_delete_arg cda; int rc; if ((rc = parse_deleter_tokens(vty, NULL, NULL, argv[6], argv[4], NULL, NULL, NULL, NULL, &cda))) return rc; cda.vty = vty; clear_vnc_responses(&cda); clear_vnc_prefix(&cda); clear_vnc_nve_closer(&cda); print_cleared_stats(&cda); return 0; } DEFUN (clear_vnc_nve_vn, clear_vnc_nve_vn_cmd, "clear vnc nve vn <*|A.B.C.D|X:X::X:X>", "clear\n" "VNC Information\n" "Clear prefix registration information\n" "VN address of NVE\n" "All addresses\n" "VN IPv4 interface address\n" "VN IPv6 interface address\n") { struct rfapi_local_reg_delete_arg cda; int rc; if ((rc = parse_deleter_tokens(vty, NULL, NULL, argv[4], NULL, NULL, NULL, NULL, NULL, &cda))) return rc; cda.vty = vty; clear_vnc_responses(&cda); clear_vnc_prefix(&cda); clear_vnc_nve_closer(&cda); print_cleared_stats(&cda); return 0; } DEFUN (clear_vnc_nve_un, clear_vnc_nve_un_cmd, "clear vnc nve un <*|A.B.C.D|X:X::X:X>", "clear\n" "VNC Information\n" "Clear prefix registration information\n" "UN address of NVE\n" "All un nves\n" "UN IPv4 interface address\n" "UN IPv6 interface address\n") { struct rfapi_local_reg_delete_arg cda; int rc; if ((rc = parse_deleter_tokens(vty, NULL, NULL, NULL, argv[4], NULL, NULL, NULL, NULL, &cda))) return rc; cda.vty = vty; clear_vnc_responses(&cda); clear_vnc_prefix(&cda); clear_vnc_nve_closer(&cda); print_cleared_stats(&cda); return 0; } /*------------------------------------------------- * Clear VNC Prefix *-------------------------------------------------*/ /* * This function is defined in this file (rather than in rfp_registration.c) * because here we have access to all the task handles. */ DEFUN (clear_vnc_prefix_vn_un, clear_vnc_prefix_vn_un_cmd, "clear vnc prefix <*|A.B.C.D/M|X:X::X:X/M> vn <*|A.B.C.D|X:X::X:X> un <*|A.B.C.D|X:X::X:X>", "clear\n" "VNC Information\n" "Clear prefix registration information\n" "All prefixes\n" "IPv4 prefix\n" "IPv6 prefix\n" "VN address of NVE\n" "All VN addresses\n" "VN IPv4 interface address\n" "VN IPv6 interface address\n" "UN address of NVE\n" "All UN addresses\n" "UN IPv4 interface address\n" "UN IPv6 interface address\n") { struct rfapi_local_reg_delete_arg cda; int rc; if ((rc = parse_deleter_tokens(vty, NULL, argv[3], argv[5], argv[7], NULL, NULL, NULL, NULL, &cda))) return rc; cda.vty = vty; clear_vnc_prefix(&cda); print_cleared_stats(&cda); return 0; } DEFUN (clear_vnc_prefix_un_vn, clear_vnc_prefix_un_vn_cmd, "clear vnc prefix <*|A.B.C.D/M|X:X::X:X/M> un <*|A.B.C.D|X:X::X:X> vn <*|A.B.C.D|X:X::X:X>", "clear\n" "VNC Information\n" "Clear prefix registration information\n" "All prefixes\n" "IPv4 prefix\n" "IPv6 prefix\n" "UN address of NVE\n" "All UN addresses\n" "UN IPv4 interface address\n" "UN IPv6 interface address\n" "VN address of NVE\n" "All VN addresses\n" "VN IPv4 interface address\n" "VN IPv6 interface address\n") { struct rfapi_local_reg_delete_arg cda; int rc; if ((rc = parse_deleter_tokens(vty, NULL, argv[3], argv[7], argv[5], NULL, NULL, NULL, NULL, &cda))) return rc; cda.vty = vty; clear_vnc_prefix(&cda); print_cleared_stats(&cda); return 0; } DEFUN (clear_vnc_prefix_un, clear_vnc_prefix_un_cmd, "clear vnc prefix <*|A.B.C.D/M|X:X::X:X/M> un <*|A.B.C.D|X:X::X:X>", "clear\n" "VNC Information\n" "Clear prefix registration information\n" "All prefixes\n" "IPv4 prefix\n" "IPv6 prefix\n" "UN address of NVE\n" "All UN addresses\n" "UN IPv4 interface address\n" "UN IPv6 interface address\n") { struct rfapi_local_reg_delete_arg cda; int rc; if ((rc = parse_deleter_tokens(vty, NULL, argv[3], NULL, argv[5], NULL, NULL, NULL, NULL, &cda))) return rc; cda.vty = vty; clear_vnc_prefix(&cda); print_cleared_stats(&cda); return 0; } DEFUN (clear_vnc_prefix_vn, clear_vnc_prefix_vn_cmd, "clear vnc prefix <*|A.B.C.D/M|X:X::X:X/M> vn <*|A.B.C.D|X:X::X:X>", "clear\n" "VNC Information\n" "Clear prefix registration information\n" "All prefixes\n" "IPv4 prefix\n" "IPv6 prefix\n" "UN address of NVE\n" "All VN addresses\n" "VN IPv4 interface address\n" "VN IPv6 interface address\n") { struct rfapi_local_reg_delete_arg cda; int rc; if ((rc = parse_deleter_tokens(vty, NULL, argv[3], argv[5], NULL, NULL, NULL, NULL, NULL, &cda))) return rc; cda.vty = vty; clear_vnc_prefix(&cda); print_cleared_stats(&cda); return 0; } DEFUN (clear_vnc_prefix_all, clear_vnc_prefix_all_cmd, "clear vnc prefix <*|A.B.C.D/M|X:X::X:X/M> *", "clear\n" "VNC Information\n" "Clear prefix registration information\n" "All prefixes\n" "IPv4 prefix\n" "IPv6 prefix\n" "From any NVE\n") { struct rfapi_local_reg_delete_arg cda; int rc; if ((rc = parse_deleter_tokens(vty, NULL, argv[3], NULL, NULL, NULL, NULL, NULL, NULL, &cda))) return rc; cda.vty = vty; clear_vnc_prefix(&cda); print_cleared_stats(&cda); return 0; } /*------------------------------------------------- * Clear VNC MAC *-------------------------------------------------*/ /* * This function is defined in this file (rather than in rfp_registration.c) * because here we have access to all the task handles. */ DEFUN (clear_vnc_mac_vn_un, clear_vnc_mac_vn_un_cmd, "clear vnc mac <*|YY:YY:YY:YY:YY:YY> virtual-network-identifier <*|(1-4294967295)> vn <*|A.B.C.D|X:X::X:X> un <*|A.B.C.D|X:X::X:X>", "clear\n" "VNC Information\n" "Clear mac registration information\n" "All macs\n" "MAC address\n" "VNI keyword\n" "Any virtual network identifier\n" "Virtual network identifier\n" "VN address of NVE\n" "All VN addresses\n" "VN IPv4 interface address\n" "VN IPv6 interface address\n" "UN address of NVE\n" "All UN addresses\n" "UN IPv4 interface address\n" "UN IPv6 interface address\n") { struct rfapi_local_reg_delete_arg cda; int rc; /* pfx vn un L2 VNI */ if ((rc = parse_deleter_tokens(vty, NULL, NULL, argv[7], argv[9], argv[3], argv[5], NULL, NULL, &cda))) return rc; cda.vty = vty; clear_vnc_prefix(&cda); print_cleared_stats(&cda); return 0; } DEFUN (clear_vnc_mac_un_vn, clear_vnc_mac_un_vn_cmd, "clear vnc mac <*|YY:YY:YY:YY:YY:YY> virtual-network-identifier <*|(1-4294967295)> un <*|A.B.C.D|X:X::X:X> vn <*|A.B.C.D|X:X::X:X>", "clear\n" "VNC Information\n" "Clear mac registration information\n" "All macs\n" "MAC address\n" "VNI keyword\n" "Any virtual network identifier\n" "Virtual network identifier\n" "UN address of NVE\n" "All UN addresses\n" "UN IPv4 interface address\n" "UN IPv6 interface address\n" "VN address of NVE\n" "All VN addresses\n" "VN IPv4 interface address\n" "VN IPv6 interface address\n") { struct rfapi_local_reg_delete_arg cda; int rc; /* pfx vn un L2 VNI */ if ((rc = parse_deleter_tokens(vty, NULL, NULL, argv[9], argv[7], argv[3], argv[5], NULL, NULL, &cda))) return rc; cda.vty = vty; clear_vnc_prefix(&cda); print_cleared_stats(&cda); return 0; } DEFUN (clear_vnc_mac_un, clear_vnc_mac_un_cmd, "clear vnc mac <*|YY:YY:YY:YY:YY:YY> virtual-network-identifier <*|(1-4294967295)> un <*|A.B.C.D|X:X::X:X>", "clear\n" "VNC Information\n" "Clear mac registration information\n" "All macs\n" "MAC address\n" "VNI keyword\n" "Any virtual network identifier\n" "Virtual network identifier\n" "UN address of NVE\n" "All UN addresses\n" "UN IPv4 interface address\n" "UN IPv6 interface address\n") { struct rfapi_local_reg_delete_arg cda; int rc; /* pfx vn un L2 VNI */ if ((rc = parse_deleter_tokens(vty, NULL, NULL, NULL, argv[7], argv[3], argv[5], NULL, NULL, &cda))) return rc; cda.vty = vty; clear_vnc_prefix(&cda); print_cleared_stats(&cda); return 0; } DEFUN (clear_vnc_mac_vn, clear_vnc_mac_vn_cmd, "clear vnc mac <*|YY:YY:YY:YY:YY:YY> virtual-network-identifier <*|(1-4294967295)> vn <*|A.B.C.D|X:X::X:X>", "clear\n" "VNC Information\n" "Clear mac registration information\n" "All macs\n" "MAC address\n" "VNI keyword\n" "Any virtual network identifier\n" "Virtual network identifier\n" "UN address of NVE\n" "All VN addresses\n" "VN IPv4 interface address\n" "VN IPv6 interface address\n") { struct rfapi_local_reg_delete_arg cda; int rc; /* pfx vn un L2 VNI */ if ((rc = parse_deleter_tokens(vty, NULL, NULL, argv[7], NULL, argv[3], argv[5], NULL, NULL, &cda))) return rc; cda.vty = vty; clear_vnc_prefix(&cda); print_cleared_stats(&cda); return 0; } DEFUN (clear_vnc_mac_all, clear_vnc_mac_all_cmd, "clear vnc mac <*|YY:YY:YY:YY:YY:YY> virtual-network-identifier <*|(1-4294967295)> *", "clear\n" "VNC Information\n" "Clear mac registration information\n" "All macs\n" "MAC address\n" "VNI keyword\n" "Any virtual network identifier\n" "Virtual network identifier\n" "From any NVE\n") { struct rfapi_local_reg_delete_arg cda; int rc; /* pfx vn un L2 VNI */ if ((rc = parse_deleter_tokens(vty, NULL, NULL, NULL, NULL, argv[3], argv[5], NULL, NULL, &cda))) return rc; cda.vty = vty; clear_vnc_prefix(&cda); print_cleared_stats(&cda); return 0; } /*------------------------------------------------- * Clear VNC MAC PREFIX *-------------------------------------------------*/ DEFUN (clear_vnc_mac_vn_un_prefix, clear_vnc_mac_vn_un_prefix_cmd, "clear vnc mac <*|YY:YY:YY:YY:YY:YY> virtual-network-identifier <*|(1-4294967295)> vn <*|A.B.C.D|X:X::X:X> un <*|A.B.C.D|X:X::X:X> prefix <*|A.B.C.D/M|X:X::X:X/M>", "clear\n" "VNC Information\n" "Clear mac registration information\n" "All macs\n" "MAC address\n" "VNI keyword\n" "Any virtual network identifier\n" "Virtual network identifier\n" "VN address of NVE\n" "All VN addresses\n" "VN IPv4 interface address\n" "VN IPv6 interface address\n" "UN address of NVE\n" "All UN addresses\n" "UN IPv4 interface address\n" "UN IPv6 interface address\n" "Clear prefix registration information\n" "All prefixes\n" "IPv4 prefix\n" "IPv6 prefix\n") { struct rfapi_local_reg_delete_arg cda; int rc; /* pfx vn un L2 VNI */ if ((rc = parse_deleter_tokens(vty, NULL, argv[11], argv[7], argv[9], argv[3], argv[5], NULL, NULL, &cda))) return rc; cda.vty = vty; clear_vnc_prefix(&cda); print_cleared_stats(&cda); return 0; } DEFUN (clear_vnc_mac_un_vn_prefix, clear_vnc_mac_un_vn_prefix_cmd, "clear vnc mac <*|YY:YY:YY:YY:YY:YY> virtual-network-identifier <*|(1-4294967295)> un <*|A.B.C.D|X:X::X:X> vn <*|A.B.C.D|X:X::X:X> prefix <*|A.B.C.D/M|X:X::X:X/M> prefix <*|A.B.C.D/M|X:X::X:X/M>", "clear\n" "VNC Information\n" "Clear mac registration information\n" "All macs\n" "MAC address\n" "VNI keyword\n" "Any virtual network identifier\n" "Virtual network identifier\n" "UN address of NVE\n" "All UN addresses\n" "UN IPv4 interface address\n" "UN IPv6 interface address\n" "VN address of NVE\n" "All VN addresses\n" "VN IPv4 interface address\n" "VN IPv6 interface address\n" "Clear prefix registration information\n" "All prefixes\n" "IPv4 prefix\n" "IPv6 prefix\n" "Clear prefix registration information\n" "All prefixes\n" "IPv4 prefix\n" "IPv6 prefix\n") { struct rfapi_local_reg_delete_arg cda; int rc; /* pfx vn un L2 VNI */ if ((rc = parse_deleter_tokens(vty, NULL, argv[11], argv[9], argv[7], argv[3], argv[5], NULL, NULL, &cda))) return rc; cda.vty = vty; clear_vnc_prefix(&cda); print_cleared_stats(&cda); return 0; } DEFUN (clear_vnc_mac_un_prefix, clear_vnc_mac_un_prefix_cmd, "clear vnc mac <*|YY:YY:YY:YY:YY:YY> virtual-network-identifier <*|(1-4294967295)> un <*|A.B.C.D|X:X::X:X> prefix <*|A.B.C.D/M|X:X::X:X/M>", "clear\n" "VNC Information\n" "Clear mac registration information\n" "All macs\n" "MAC address\n" "VNI keyword\n" "Any virtual network identifier\n" "Virtual network identifier\n" "UN address of NVE\n" "All UN addresses\n" "UN IPv4 interface address\n" "UN IPv6 interface address\n" "Clear prefix registration information\n" "All prefixes\n" "IPv4 Prefix\n" "IPv6 Prefix\n") { struct rfapi_local_reg_delete_arg cda; int rc; /* pfx vn un L2 VNI */ if ((rc = parse_deleter_tokens(vty, NULL, argv[9], NULL, argv[7], argv[3], argv[5], NULL, NULL, &cda))) return rc; cda.vty = vty; clear_vnc_prefix(&cda); print_cleared_stats(&cda); return 0; } DEFUN (clear_vnc_mac_vn_prefix, clear_vnc_mac_vn_prefix_cmd, "clear vnc mac <*|YY:YY:YY:YY:YY:YY> virtual-network-identifier <*|(1-4294967295)> vn <*|A.B.C.D|X:X::X:X> prefix <*|A.B.C.D/M|X:X::X:X/M>", "clear\n" "VNC Information\n" "Clear mac registration information\n" "All macs\n" "MAC address\n" "VNI keyword\n" "Any virtual network identifier\n" "Virtual network identifier\n" "UN address of NVE\n" "All VN addresses\n" "VN IPv4 interface address\n" "VN IPv6 interface address\n" "Clear prefix registration information\n" "All prefixes\n" "IPv4 Prefix\n" "IPv6 Prefix\n") { struct rfapi_local_reg_delete_arg cda; int rc; /* pfx vn un L2 VNI */ if ((rc = parse_deleter_tokens(vty, NULL, argv[9], argv[7], NULL, argv[3], argv[5], NULL, NULL, &cda))) return rc; cda.vty = vty; clear_vnc_prefix(&cda); print_cleared_stats(&cda); return 0; } DEFUN (clear_vnc_mac_all_prefix, clear_vnc_mac_all_prefix_cmd, "clear vnc mac <*|YY:YY:YY:YY:YY:YY> virtual-network-identifier <*|(1-4294967295)> prefix <*|A.B.C.D/M|X:X::X:X/M>", "clear\n" "VNC Information\n" "Clear mac registration information\n" "All macs\n" "MAC address\n" "VNI keyword\n" "Any virtual network identifier\n" "Virtual network identifier\n" "UN address of NVE\n" "All VN addresses\n" "VN IPv4 interface address\n" "VN IPv6 interface address\n") { struct rfapi_local_reg_delete_arg cda; int rc; /* pfx vn un L2 VNI */ if ((rc = parse_deleter_tokens(vty, NULL, argv[7], NULL, NULL, argv[3], argv[5], NULL, NULL, &cda))) return rc; cda.vty = vty; clear_vnc_prefix(&cda); print_cleared_stats(&cda); return 0; } /************************************************************************ * Show commands ************************************************************************/ /* copied from rfp_vty.c */ static int check_and_display_is_vnc_running(struct vty *vty) { if (bgp_rfapi_is_vnc_configured(NULL) == 0) return 1; /* is running */ if (vty) { vty_out(vty, "VNC is not configured.\n"); } return 0; /* not running */ } static int rfapi_vty_show_nve_summary(struct vty *vty, show_nve_summary_t show_type) { struct bgp *bgp_default = bgp_get_default(); struct rfapi *h; int is_vnc_running = (bgp_rfapi_is_vnc_configured(bgp_default) == 0); int active_local_routes; int active_remote_routes; int holddown_remote_routes; int imported_remote_routes; if (!bgp_default) goto notcfg; h = bgp_default->rfapi; if (!h) goto notcfg; /* don't show local info if not running RFP */ if (is_vnc_running || show_type == SHOW_NVE_SUMMARY_REGISTERED) { switch (show_type) { case SHOW_NVE_SUMMARY_ACTIVE_NVES: vty_out(vty, "%-24s ", "NVEs:"); vty_out(vty, "%-8s %-8u ", "Active:", h->descriptors.count); vty_out(vty, "%-8s %-8u ", "Maximum:", h->stat.max_descriptors); vty_out(vty, "%-8s %-8u", "Unknown:", h->stat.count_unknown_nves); break; case SHOW_NVE_SUMMARY_REGISTERED: /* * NB: With the introduction of L2 route support, we no * longer have a one-to-one correspondence between * locally-originated route advertisements and routes in * the import tables that have local origin. This * discrepancy arises because a single advertisement * may contain both an IP prefix and a MAC address. * Such an advertisement results in two import table * entries: one indexed by IP prefix, the other indexed * by MAC address. * * TBD: update computation and display of registration * statistics to reflect the underlying semantics. */ if (is_vnc_running) { vty_out(vty, "%-24s ", "Registrations:"); vty_out(vty, "%-8s %-8u ", "Active:", rfapiApCountAll(bgp_default)); vty_out(vty, "%-8s %-8u ", "Failed:", h->stat.count_registrations_failed); vty_out(vty, "%-8s %-8u", "Total:", h->stat.count_registrations); vty_out(vty, "\n"); } vty_out(vty, "%-24s ", "Prefixes registered:"); vty_out(vty, "\n"); rfapiCountAllItRoutes(&active_local_routes, &active_remote_routes, &holddown_remote_routes, &imported_remote_routes); /* local */ if (is_vnc_running) { vty_out(vty, " %-20s ", "Locally:"); vty_out(vty, "%-8s %-8u ", "Active:", active_local_routes); vty_out(vty, "\n"); } vty_out(vty, " %-20s ", "Remotely:"); vty_out(vty, "%-8s %-8u", "Active:", active_remote_routes); vty_out(vty, "\n"); vty_out(vty, " %-20s ", "In Holddown:"); vty_out(vty, "%-8s %-8u", "Active:", holddown_remote_routes); vty_out(vty, "\n"); vty_out(vty, " %-20s ", "Imported:"); vty_out(vty, "%-8s %-8u", "Active:", imported_remote_routes); break; case SHOW_NVE_SUMMARY_QUERIES: vty_out(vty, "%-24s ", "Queries:"); vty_out(vty, "%-8s %-8u ", "Active:", rfapi_monitor_count(NULL)); vty_out(vty, "%-8s %-8u ", "Failed:", h->stat.count_queries_failed); vty_out(vty, "%-8s %-8u", "Total:", h->stat.count_queries); break; case SHOW_NVE_SUMMARY_RESPONSES: rfapiRibShowResponsesSummary(vty); default: break; } vty_out(vty, "\n"); } return 0; notcfg: vty_out(vty, "VNC is not configured.\n"); return CMD_WARNING; } static int rfapi_show_nves(struct vty *vty, struct prefix *vn_prefix, struct prefix *un_prefix) { // struct hash *rfds; // struct rfp_rfapi_descriptor_param param; struct bgp *bgp_default = bgp_get_default(); struct rfapi *h; struct listnode *node; struct rfapi_descriptor *rfd; int total = 0; int printed = 0; int rc; if (!bgp_default) goto notcfg; h = bgp_default->rfapi; if (!h) goto notcfg; rc = rfapi_vty_show_nve_summary(vty, SHOW_NVE_SUMMARY_ACTIVE_NVES); if (rc) return rc; for (ALL_LIST_ELEMENTS_RO(&h->descriptors, node, rfd)) { struct prefix pfx; char vn_addr_buf[INET6_ADDRSTRLEN] = { 0, }; char un_addr_buf[INET6_ADDRSTRLEN] = { 0, }; char age[10]; ++total; if (vn_prefix) { assert(!rfapiRaddr2Qprefix(&rfd->vn_addr, &pfx)); if (!prefix_match(vn_prefix, &pfx)) continue; } if (un_prefix) { assert(!rfapiRaddr2Qprefix(&rfd->un_addr, &pfx)); if (!prefix_match(un_prefix, &pfx)) continue; } rfapiRfapiIpAddr2Str(&rfd->vn_addr, vn_addr_buf, INET6_ADDRSTRLEN); rfapiRfapiIpAddr2Str(&rfd->un_addr, un_addr_buf, INET6_ADDRSTRLEN); if (!printed) { /* print out a header */ vty_out(vty, " Active Next Hops\n"); vty_out(vty, "%-15s %-15s %-5s %-5s %-6s %-6s %s\n", "VN Address", "UN Address", "Regis", "Resps", "Reach", "Remove", "Age"); } ++printed; vty_out(vty, "%-15s %-15s %-5u %-5u %-6u %-6u %s\n", vn_addr_buf, un_addr_buf, rfapiApCount(rfd), rfapi_monitor_count(rfd), rfd->stat_count_nh_reachable, rfd->stat_count_nh_removal, rfapiFormatAge(rfd->open_time, age, 10)); } if (printed > 0 || vn_prefix || un_prefix) vty_out(vty, "Displayed %d out of %d active NVEs\n", printed, total); return 0; notcfg: vty_out(vty, "VNC is not configured.\n"); return CMD_WARNING; } DEFUN (vnc_show_summary, vnc_show_summary_cmd, "show vnc summary", SHOW_STR VNC_SHOW_STR "Display VNC status summary\n") { if (!check_and_display_is_vnc_running(vty)) return CMD_SUCCESS; bgp_rfapi_show_summary(bgp_get_default(), vty); vty_out(vty, "\n"); rfapi_vty_show_nve_summary(vty, SHOW_NVE_SUMMARY_ACTIVE_NVES); rfapi_vty_show_nve_summary(vty, SHOW_NVE_SUMMARY_QUERIES); rfapi_vty_show_nve_summary(vty, SHOW_NVE_SUMMARY_RESPONSES); rfapi_vty_show_nve_summary(vty, SHOW_NVE_SUMMARY_REGISTERED); return CMD_SUCCESS; } DEFUN (vnc_show_nves, vnc_show_nves_cmd, "show vnc nves", SHOW_STR VNC_SHOW_STR "List known NVEs\n") { rfapi_show_nves(vty, NULL, NULL); return CMD_SUCCESS; } DEFUN (vnc_show_nves_ptct, vnc_show_nves_ptct_cmd, "show vnc nves ", SHOW_STR VNC_SHOW_STR "List known NVEs\n" "VN address of NVE\n" "UN address of NVE\n" "IPv4 interface address\n" "IPv6 interface address\n") { struct prefix pfx; if (!check_and_display_is_vnc_running(vty)) return CMD_SUCCESS; if (!str2prefix(argv[4]->arg, &pfx)) { vty_out(vty, "Malformed address \"%s\"\n", argv[4]->arg); return CMD_WARNING; } if (pfx.family != AF_INET && pfx.family != AF_INET6) { vty_out(vty, "Invalid address \"%s\"\n", argv[4]->arg); return CMD_WARNING; } if (argv[3]->arg[0] == 'u') { rfapi_show_nves(vty, NULL, &pfx); } else { rfapi_show_nves(vty, &pfx, NULL); } return CMD_SUCCESS; } /* adapted from rfp_registration_cache_log() */ static void rfapi_show_registrations(struct vty *vty, struct prefix *restrict_to, int show_local, int show_remote, int show_holddown, int show_imported) { int printed = 0; if (!vty) return; rfapi_vty_show_nve_summary(vty, SHOW_NVE_SUMMARY_REGISTERED); if (show_local) { /* non-expiring, local */ printed += rfapiShowRemoteRegistrations(vty, restrict_to, 0, 1, 0, 0); } if (show_remote) { /* non-expiring, non-local */ printed += rfapiShowRemoteRegistrations(vty, restrict_to, 0, 0, 1, 0); } if (show_holddown) { /* expiring, including local */ printed += rfapiShowRemoteRegistrations(vty, restrict_to, 1, 1, 1, 0); } if (show_imported) { /* non-expiring, non-local */ printed += rfapiShowRemoteRegistrations(vty, restrict_to, 0, 0, 1, 1); } if (!printed) { vty_out(vty, "\n"); } } DEFUN (vnc_show_registrations_pfx, vnc_show_registrations_pfx_cmd, "show vnc registrations []", SHOW_STR VNC_SHOW_STR "List active prefix registrations\n" "Limit output to a particular IPv4 prefix\n" "Limit output to a particular IPv6 prefix\n" "Limit output to a particular IPv6 address\n") { struct prefix p; struct prefix *p_addr = NULL; if (argc > 3) { if (!str2prefix(argv[3]->arg, &p)) { vty_out(vty, "Invalid prefix: %s\n", argv[3]->arg); return CMD_SUCCESS; } else { p_addr = &p; } } rfapi_show_registrations(vty, p_addr, 1, 1, 1, 1); return CMD_SUCCESS; } DEFUN (vnc_show_registrations_some_pfx, vnc_show_registrations_some_pfx_cmd, "show vnc registrations []", SHOW_STR VNC_SHOW_STR "List active prefix registrations\n" "show all registrations\n" "show only registrations in holddown\n" "show only imported prefixes\n" "show only local registrations\n" "show only remote registrations\n" "Limit output to a particular prefix or address\n" "Limit output to a particular prefix or address\n" "Limit output to a particular prefix or address\n") { struct prefix p; struct prefix *p_addr = NULL; int show_local = 0; int show_remote = 0; int show_holddown = 0; int show_imported = 0; if (argc > 4) { if (!str2prefix(argv[4]->arg, &p)) { vty_out(vty, "Invalid prefix: %s\n", argv[4]->arg); return CMD_SUCCESS; } else { p_addr = &p; } } switch (argv[3]->arg[0]) { case 'a': show_local = 1; show_remote = 1; show_holddown = 1; show_imported = 1; break; case 'h': show_holddown = 1; break; case 'i': show_imported = 1; break; case 'l': show_local = 1; break; case 'r': show_remote = 1; break; } rfapi_show_registrations(vty, p_addr, show_local, show_remote, show_holddown, show_imported); return CMD_SUCCESS; } DEFUN (vnc_show_responses_pfx, vnc_show_responses_pfx_cmd, "show vnc responses []", SHOW_STR VNC_SHOW_STR "List recent query responses\n" "Limit output to a particular IPv4 prefix\n" "Limit output to a particular IPv6 prefix\n" "Limit output to a particular IPv6 address\n" ) { struct prefix p; struct prefix *p_addr = NULL; if (argc > 3) { if (!str2prefix(argv[3]->arg, &p)) { vty_out(vty, "Invalid prefix: %s\n", argv[3]->arg); return CMD_SUCCESS; } else { p_addr = &p; } } rfapi_vty_show_nve_summary(vty, SHOW_NVE_SUMMARY_QUERIES); rfapiRibShowResponsesSummary(vty); rfapiRibShowResponses(vty, p_addr, 0); rfapiRibShowResponses(vty, p_addr, 1); return CMD_SUCCESS; } DEFUN (vnc_show_responses_some_pfx, vnc_show_responses_some_pfx_cmd, "show vnc responses []", SHOW_STR VNC_SHOW_STR "List recent query responses\n" "show only active query responses\n" "show only removed query responses\n" "Limit output to a particular IPv4 prefix\n" "Limit output to a particular IPv6 prefix\n" "Limit output to a particular IPV6 address\n") { struct prefix p; struct prefix *p_addr = NULL; int show_active = 0; int show_removed = 0; if (!check_and_display_is_vnc_running(vty)) return CMD_SUCCESS; if (argc > 4) { if (!str2prefix(argv[4]->arg, &p)) { vty_out(vty, "Invalid prefix: %s\n", argv[4]->arg); return CMD_SUCCESS; } else { p_addr = &p; } } switch (argv[3]->arg[0]) { case 'a': show_active = 1; break; case 'r': show_removed = 1; break; } rfapi_vty_show_nve_summary(vty, SHOW_NVE_SUMMARY_QUERIES); rfapiRibShowResponsesSummary(vty); if (show_active) rfapiRibShowResponses(vty, p_addr, 0); if (show_removed) rfapiRibShowResponses(vty, p_addr, 1); return CMD_SUCCESS; } DEFUN (show_vnc_queries_pfx, show_vnc_queries_pfx_cmd, "show vnc queries []", SHOW_STR VNC_SHOW_STR "List active queries\n" "Limit output to a particular IPv4 prefix or address\n" "Limit output to a particular IPv6 prefix\n" "Limit output to a particualr IPV6 address\n") { struct prefix pfx; struct prefix *p = NULL; if (argc > 3) { if (!str2prefix(argv[3]->arg, &pfx)) { vty_out(vty, "Invalid prefix: %s\n", argv[3]->arg); return CMD_WARNING; } p = &pfx; } rfapi_vty_show_nve_summary(vty, SHOW_NVE_SUMMARY_QUERIES); return rfapiShowVncQueries(vty, p); } DEFUN (vnc_clear_counters, vnc_clear_counters_cmd, "clear vnc counters", CLEAR_STR VNC_SHOW_STR "Reset VNC counters\n") { struct bgp *bgp_default = bgp_get_default(); struct rfapi *h; struct listnode *node; struct rfapi_descriptor *rfd; if (!bgp_default) goto notcfg; h = bgp_default->rfapi; if (!h) goto notcfg; /* per-rfd */ for (ALL_LIST_ELEMENTS_RO(&h->descriptors, node, rfd)) { rfd->stat_count_nh_reachable = 0; rfd->stat_count_nh_removal = 0; } /* global */ memset(&h->stat, 0, sizeof(h->stat)); /* * 151122 per bug 103, set count_registrations = number active. * Do same for queries */ h->stat.count_registrations = rfapiApCountAll(bgp_default); h->stat.count_queries = rfapi_monitor_count(NULL); rfapiRibShowResponsesSummaryClear(); return CMD_SUCCESS; notcfg: vty_out(vty, "VNC is not configured.\n"); return CMD_WARNING; } /************************************************************************ * Add prefix with vrf * * add [vrf ] prefix * [rd ] [label ] [local-preference <0-4294967295>] ************************************************************************/ void vnc_add_vrf_opener(struct bgp *bgp, struct rfapi_nve_group_cfg *rfg) { if (rfg->rfd == NULL) { /* need new rfapi_handle */ /* based on rfapi_open */ struct rfapi_descriptor *rfd; rfd = XCALLOC(MTYPE_RFAPI_DESC, sizeof(struct rfapi_descriptor)); rfd->bgp = bgp; rfg->rfd = rfd; /* leave most fields empty as will get from (dynamic) config * when needed */ rfd->default_tunneltype_option.type = BGP_ENCAP_TYPE_MPLS; rfd->cookie = rfg; if (rfg->vn_prefix.family && !CHECK_FLAG(rfg->flags, RFAPI_RFG_VPN_NH_SELF)) { rfapiQprefix2Raddr(&rfg->vn_prefix, &rfd->vn_addr); } else { memset(&rfd->vn_addr, 0, sizeof(struct rfapi_ip_addr)); rfd->vn_addr.addr_family = AF_INET; rfd->vn_addr.addr.v4 = bgp->router_id; } rfd->un_addr = rfd->vn_addr; /* sigh, need something in UN for lookups */ vnc_zlog_debug_verbose("%s: Opening RFD for VRF %s", __func__, rfg->name); rfapi_init_and_open(bgp, rfd, rfg); } } /* NOTE: this functions parallels vnc_direct_add_rn_group_rd */ static int vnc_add_vrf_prefix(struct vty *vty, const char *arg_vrf, const char *arg_prefix, const char *arg_rd, /* optional */ const char *arg_label, /* optional */ const char *arg_pref) /* optional */ { struct bgp *bgp; struct rfapi_nve_group_cfg *rfg; struct prefix pfx; struct rfapi_ip_prefix rpfx; uint32_t pref = 0; struct rfapi_vn_option optary[3]; struct rfapi_vn_option *opt = NULL; int cur_opt = 0; bgp = bgp_get_default(); /* assume main instance for now */ if (!bgp) { vty_out(vty, "No BGP process is configured\n"); return CMD_WARNING_CONFIG_FAILED; } if (!bgp->rfapi || !bgp->rfapi_cfg) { vty_out(vty, "VRF support not configured\n"); return CMD_WARNING_CONFIG_FAILED; } rfg = bgp_rfapi_cfg_match_byname(bgp, arg_vrf, RFAPI_GROUP_CFG_VRF); /* arg checks */ if (!rfg) { vty_out(vty, "VRF \"%s\" appears not to be configured.\n", arg_vrf); return CMD_WARNING_CONFIG_FAILED; } if (!rfg->rt_export_list || !rfg->rfapi_import_table) { vty_out(vty, "VRF \"%s\" is missing RT import/export RT configuration.\n", arg_vrf); return CMD_WARNING_CONFIG_FAILED; } if (!rfg->rd.prefixlen && !arg_rd) { vty_out(vty, "VRF \"%s\" isn't configured with an RD, so RD must be provided.\n", arg_vrf); return CMD_WARNING_CONFIG_FAILED; } if (rfg->label > MPLS_LABEL_MAX && !arg_label) { vty_out(vty, "VRF \"%s\" isn't configured with a default labels, so a label must be provided.\n", arg_vrf); return CMD_WARNING_CONFIG_FAILED; } if (!str2prefix(arg_prefix, &pfx)) { vty_out(vty, "Malformed prefix \"%s\"\n", arg_prefix); return CMD_WARNING_CONFIG_FAILED; } rfapiQprefix2Rprefix(&pfx, &rpfx); memset(optary, 0, sizeof(optary)); if (arg_rd) { if (opt != NULL) opt->next = &optary[cur_opt]; opt = &optary[cur_opt++]; opt->type = RFAPI_VN_OPTION_TYPE_INTERNAL_RD; if (!str2prefix_rd(arg_rd, &opt->v.internal_rd)) { vty_out(vty, "Malformed RD \"%s\"\n", arg_rd); return CMD_WARNING_CONFIG_FAILED; } } if (rfg->label <= MPLS_LABEL_MAX || arg_label) { struct rfapi_l2address_option *l2o; if (opt != NULL) opt->next = &optary[cur_opt]; opt = &optary[cur_opt++]; opt->type = RFAPI_VN_OPTION_TYPE_L2ADDR; l2o = &opt->v.l2addr; if (arg_label) { int32_t label; label = strtoul(arg_label, NULL, 10); l2o->label = label; } else l2o->label = rfg->label; } if (arg_pref) { char *endptr = NULL; pref = strtoul(arg_pref, &endptr, 10); if (*endptr != '\0') { vty_out(vty, "%% Invalid local-preference value \"%s\"\n", arg_pref); return CMD_WARNING_CONFIG_FAILED; } } rpfx.cost = 255 - (pref & 255); vnc_add_vrf_opener(bgp, rfg); if (!rfapi_register(rfg->rfd, &rpfx, RFAPI_INFINITE_LIFETIME, NULL, (cur_opt ? optary : NULL), RFAPI_REGISTER_ADD)) { struct rfapi_next_hop_entry *head = NULL; struct rfapi_next_hop_entry *tail = NULL; struct rfapi_vn_option *vn_opt_new; vnc_zlog_debug_verbose("%s: rfapi_register succeeded", __func__); if (bgp->rfapi->rfp_methods.local_cb) { struct rfapi_descriptor *r = (struct rfapi_descriptor *)rfg->rfd; vn_opt_new = rfapi_vn_options_dup(opt); rfapiAddDeleteLocalRfpPrefix(&r->un_addr, &r->vn_addr, &rpfx, 1, RFAPI_INFINITE_LIFETIME, vn_opt_new, &head, &tail); if (head) { bgp->rfapi->flags |= RFAPI_INCALLBACK; (*bgp->rfapi->rfp_methods.local_cb)(head, r->cookie); bgp->rfapi->flags &= ~RFAPI_INCALLBACK; } head = tail = NULL; } vnc_zlog_debug_verbose( "%s completed, count=%d/%d", __func__, rfg->rfapi_import_table->local_count[AFI_IP], rfg->rfapi_import_table->local_count[AFI_IP6]); return CMD_SUCCESS; } vnc_zlog_debug_verbose("%s: rfapi_register failed", __func__); vty_out(vty, "Add failed.\n"); return CMD_WARNING_CONFIG_FAILED; } DEFUN (add_vrf_prefix_rd_label_pref, add_vrf_prefix_rd_label_pref_cmd, "add vrf NAME prefix [{rd ASN:NN_OR_IP-ADDRESS|label (0-1048575)|preference (0-4294967295)}]", "Add\n" "To a VRF\n" "VRF name\n" "Add/modify prefix related information\n" "IPv4 prefix\n" "IPv6 prefix\n" "Override configured VRF Route Distinguisher\n" ": or :\n" "Override configured VRF label\n" "Label Value <0-1048575>\n" "Set advertised local preference\n" "local preference (higher=more preferred)\n") { char *arg_vrf = argv[2]->arg; char *arg_prefix = argv[4]->arg; char *arg_rd = NULL; /* optional */ char *arg_label = NULL; /* optional */ char *arg_pref = NULL; /* optional */ int pargc = 5; argc--; /* don't parse argument */ while (pargc < argc) { switch (argv[pargc++]->arg[0]) { case 'r': arg_rd = argv[pargc]->arg; break; case 'l': arg_label = argv[pargc]->arg; break; case 'p': arg_pref = argv[pargc]->arg; break; default: break; } pargc++; } return vnc_add_vrf_prefix(vty, arg_vrf, arg_prefix, arg_rd, arg_label, arg_pref); } /************************************************************************ * del prefix with vrf * * clear [vrf ] prefix [rd ] ************************************************************************/ static int rfapi_cfg_group_it_count(struct rfapi_nve_group_cfg *rfg) { int count = 0; if (rfg->rfapi_import_table == NULL) return 0; afi_t afi = AFI_MAX; while (afi-- > 0) { count += rfg->rfapi_import_table->local_count[afi]; } return count; } void clear_vnc_vrf_closer(struct rfapi_nve_group_cfg *rfg) { struct rfapi_descriptor *rfd = rfg->rfd; afi_t afi; if (rfd == NULL) return; /* check if IT is empty */ for (afi = 0; afi < AFI_MAX && rfg->rfapi_import_table->local_count[afi] == 0; afi++) ; if (afi == AFI_MAX) { vnc_zlog_debug_verbose("%s: closing RFD for VRF %s", __func__, rfg->name); rfg->rfd = NULL; rfapi_close(rfd); } else { vnc_zlog_debug_verbose( "%s: VRF %s afi=%d count=%d", __func__, rfg->name, afi, rfg->rfapi_import_table->local_count[afi]); } } static int vnc_clear_vrf(struct vty *vty, struct bgp *bgp, const char *arg_vrf, const char *arg_prefix, /* NULL = all */ const char *arg_rd) /* optional */ { struct rfapi_nve_group_cfg *rfg; struct rfapi_local_reg_delete_arg cda; int rc; int start_count; if (bgp == NULL) bgp = bgp_get_default(); /* assume main instance for now */ if (!bgp) { vty_out(vty, "No BGP process is configured\n"); return CMD_WARNING; } if (!bgp->rfapi || !bgp->rfapi_cfg) { vty_out(vty, "VRF support not configured\n"); return CMD_WARNING; } rfg = bgp_rfapi_cfg_match_byname(bgp, arg_vrf, RFAPI_GROUP_CFG_VRF); /* arg checks */ if (!rfg) { vty_out(vty, "VRF \"%s\" appears not to be configured.\n", arg_vrf); return CMD_WARNING; } rc = parse_deleter_args(vty, bgp, arg_prefix, NULL, NULL, NULL, NULL, arg_rd, rfg, &cda); if (rc != CMD_SUCCESS) /* parse error */ return rc; start_count = rfapi_cfg_group_it_count(rfg); clear_vnc_prefix(&cda); vty_out(vty, "Cleared %u out of %d prefixes.\n", cda.pfx_count, start_count); return CMD_SUCCESS; } DEFUN (clear_vrf_prefix_rd, clear_vrf_prefix_rd_cmd, "clear vrf NAME [prefix ] [rd ASN:NN_OR_IP-ADDRESS]", "Clear stored data\n" "From a VRF\n" "VRF name\n" "Prefix related information\n" "IPv4 prefix\n" "IPv6 prefix\n" "Specific VRF Route Distinguisher\n" ": or :\n") { char *arg_vrf = argv[2]->arg; char *arg_prefix = NULL; /* optional */ char *arg_rd = NULL; /* optional */ int pargc = 3; argc--; /* don't check parameter */ while (pargc < argc) { switch (argv[pargc++]->arg[0]) { case 'r': arg_rd = argv[pargc]->arg; break; case 'p': arg_prefix = argv[pargc]->arg; break; default: break; } pargc++; } return vnc_clear_vrf(vty, NULL, arg_vrf, arg_prefix, arg_rd); } DEFUN (clear_vrf_all, clear_vrf_all_cmd, "clear vrf NAME all", "Clear stored data\n" "From a VRF\n" "VRF name\n" "All prefixes\n") { char *arg_vrf = argv[2]->arg; return vnc_clear_vrf(vty, NULL, arg_vrf, NULL, NULL); } void rfapi_vty_init(void) { install_element(ENABLE_NODE, &add_vnc_prefix_cost_life_lnh_cmd); install_element(ENABLE_NODE, &add_vnc_prefix_life_cost_lnh_cmd); install_element(ENABLE_NODE, &add_vnc_prefix_cost_lnh_cmd); install_element(ENABLE_NODE, &add_vnc_prefix_life_lnh_cmd); install_element(ENABLE_NODE, &add_vnc_prefix_lnh_cmd); install_element(ENABLE_NODE, &add_vnc_prefix_cost_life_cmd); install_element(ENABLE_NODE, &add_vnc_prefix_life_cost_cmd); install_element(ENABLE_NODE, &add_vnc_prefix_cost_cmd); install_element(ENABLE_NODE, &add_vnc_prefix_life_cmd); install_element(ENABLE_NODE, &add_vnc_prefix_cmd); install_element(ENABLE_NODE, &add_vnc_mac_vni_prefix_cost_life_cmd); install_element(ENABLE_NODE, &add_vnc_mac_vni_prefix_life_cmd); install_element(ENABLE_NODE, &add_vnc_mac_vni_prefix_cost_cmd); install_element(ENABLE_NODE, &add_vnc_mac_vni_prefix_cmd); install_element(ENABLE_NODE, &add_vnc_mac_vni_cost_life_cmd); install_element(ENABLE_NODE, &add_vnc_mac_vni_cost_cmd); install_element(ENABLE_NODE, &add_vnc_mac_vni_life_cmd); install_element(ENABLE_NODE, &add_vnc_mac_vni_cmd); install_element(ENABLE_NODE, &add_vrf_prefix_rd_label_pref_cmd); install_element(ENABLE_NODE, &clear_vnc_nve_all_cmd); install_element(ENABLE_NODE, &clear_vnc_nve_vn_un_cmd); install_element(ENABLE_NODE, &clear_vnc_nve_un_vn_cmd); install_element(ENABLE_NODE, &clear_vnc_nve_vn_cmd); install_element(ENABLE_NODE, &clear_vnc_nve_un_cmd); install_element(ENABLE_NODE, &clear_vnc_prefix_vn_un_cmd); install_element(ENABLE_NODE, &clear_vnc_prefix_un_vn_cmd); install_element(ENABLE_NODE, &clear_vnc_prefix_un_cmd); install_element(ENABLE_NODE, &clear_vnc_prefix_vn_cmd); install_element(ENABLE_NODE, &clear_vnc_prefix_all_cmd); install_element(ENABLE_NODE, &clear_vnc_mac_vn_un_cmd); install_element(ENABLE_NODE, &clear_vnc_mac_un_vn_cmd); install_element(ENABLE_NODE, &clear_vnc_mac_un_cmd); install_element(ENABLE_NODE, &clear_vnc_mac_vn_cmd); install_element(ENABLE_NODE, &clear_vnc_mac_all_cmd); install_element(ENABLE_NODE, &clear_vnc_mac_vn_un_prefix_cmd); install_element(ENABLE_NODE, &clear_vnc_mac_un_vn_prefix_cmd); install_element(ENABLE_NODE, &clear_vnc_mac_un_prefix_cmd); install_element(ENABLE_NODE, &clear_vnc_mac_vn_prefix_cmd); install_element(ENABLE_NODE, &clear_vnc_mac_all_prefix_cmd); install_element(ENABLE_NODE, &clear_vrf_prefix_rd_cmd); install_element(ENABLE_NODE, &clear_vrf_all_cmd); install_element(ENABLE_NODE, &vnc_clear_counters_cmd); install_element(VIEW_NODE, &vnc_show_summary_cmd); install_element(VIEW_NODE, &vnc_show_nves_cmd); install_element(VIEW_NODE, &vnc_show_nves_ptct_cmd); install_element(VIEW_NODE, &vnc_show_registrations_pfx_cmd); install_element(VIEW_NODE, &vnc_show_registrations_some_pfx_cmd); install_element(VIEW_NODE, &vnc_show_responses_pfx_cmd); install_element(VIEW_NODE, &vnc_show_responses_some_pfx_cmd); install_element(VIEW_NODE, &show_vnc_queries_pfx_cmd); } frr-7.2.1/bgpd/rfapi/rfapi_vty.h0000644000000000000000000001241213610377563013405 00000000000000/* * * Copyright 2009-2016, LabN Consulting, L.L.C. * * * This program is free software; you can redistribute it and/or * modify it under the terms of the GNU General Public License * as published by the Free Software Foundation; either version 2 * of the License, or (at your option) any later version. * * 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; see the file COPYING; if not, write to the Free Software * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA */ #ifndef RFAPI_VTY_H #define RFAPI_VTY_H #include "lib/vty.h" typedef enum { SHOW_NVE_SUMMARY_ACTIVE_NVES, SHOW_NVE_SUMMARY_UNKNOWN_NVES, /* legacy */ SHOW_NVE_SUMMARY_REGISTERED, SHOW_NVE_SUMMARY_QUERIES, SHOW_NVE_SUMMARY_RESPONSES, SHOW_NVE_SUMMARY_MAX } show_nve_summary_t; #define VNC_SHOW_STR "VNC information\n" extern char *rfapiFormatSeconds(uint32_t seconds, char *buf, size_t len); extern char *rfapiFormatAge(time_t age, char *buf, size_t len); extern void rfapiRprefixApplyMask(struct rfapi_ip_prefix *rprefix); extern int rfapiQprefix2Raddr(struct prefix *qprefix, struct rfapi_ip_addr *raddr); extern void rfapiQprefix2Rprefix(struct prefix *qprefix, struct rfapi_ip_prefix *rprefix); extern int rfapiRprefix2Qprefix(struct rfapi_ip_prefix *rprefix, struct prefix *qprefix); extern int rfapiRaddr2Qprefix(struct rfapi_ip_addr *hia, struct prefix *pfx); extern int rfapiRprefixSame(struct rfapi_ip_prefix *hp1, struct rfapi_ip_prefix *hp2); extern void rfapiL2o2Qprefix(struct rfapi_l2address_option *l2o, struct prefix *pfx); extern int rfapiStr2EthAddr(const char *str, struct ethaddr *ea); extern const char *rfapi_ntop(int af, const void *src, char *buf, socklen_t size); extern int rfapiDebugPrintf(void *dummy, const char *format, ...); extern int rfapiStream2Vty(void *stream, /* input */ int (**fp)(void *, const char *, ...), /* output */ struct vty **vty, /* output */ void **outstream, /* output */ const char **vty_newline); /* output */ /*------------------------------------------ * rfapiRfapiIpAddr2Str * * UI helper: generate string from rfapi_ip_addr * * input: * a IP v4/v6 address * * output * buf put string here * bufsize max space to write * * return value: * NULL conversion failed * non-NULL pointer to buf --------------------------------------------*/ extern const char *rfapiRfapiIpAddr2Str(struct rfapi_ip_addr *a, char *buf, int bufsize); extern void rfapiPrintRfapiIpAddr(void *stream, struct rfapi_ip_addr *a); extern void rfapiPrintRfapiIpPrefix(void *stream, struct rfapi_ip_prefix *p); void rfapiPrintRd(struct vty *vty, struct prefix_rd *prd); extern void rfapiPrintAdvertisedInfo(struct vty *vty, struct rfapi_descriptor *rfd, safi_t safi, struct prefix *p); extern void rfapiPrintDescriptor(struct vty *vty, struct rfapi_descriptor *rfd); extern void rfapiPrintMatchingDescriptors(struct vty *vty, struct prefix *vn_prefix, struct prefix *un_prefix); extern void rfapiPrintAttrPtrs(void *stream, struct attr *attr); /* * Parse an address and put into a struct prefix */ extern int rfapiCliGetPrefixAddr(struct vty *vty, const char *str, struct prefix *p); extern int rfapiCliGetRfapiIpAddr(struct vty *vty, const char *str, struct rfapi_ip_addr *hai); extern void rfapiPrintNhl(void *stream, struct rfapi_next_hop_entry *next_hops); extern char *rfapiMonitorVpn2Str(struct rfapi_monitor_vpn *m, char *buf, int size); extern const char *rfapiRfapiIpPrefix2Str(struct rfapi_ip_prefix *p, char *buf, int bufsize); extern void rfapiShowItNode(void *stream, struct agg_node *rn); extern char *rfapiEthAddr2Str(const struct ethaddr *ea, char *buf, int bufsize); /* install vty commands */ extern void rfapi_vty_init(void); /*------------------------------------------ * rfapiShowRemoteRegistrations * * UI helper: produces the "remote" portion of the output * of "show vnc registrations". * * input: * stream pointer to output stream * prefix_only pointer to prefix. If non-NULL, print only registrations * matching the specified prefix * show_expiring if non-zero, show expiring registrations * show_local if non-zero, show local registrations * show_imported if non-zero, show imported registrations * * return value: * 0 nothing printed * >0 something printed --------------------------------------------*/ extern int rfapiShowRemoteRegistrations(void *stream, struct prefix *prefix_only, int show_expiring, int show_local, int show_remote, int show_imported); /*------------------------------------------ * rfapi_monitor_count * * UI helper: count number of active monitors * * input: * handle rfapi handle (NULL to count across * all open handles) * * output * * return value: * count of monitors --------------------------------------------*/ extern uint32_t rfapi_monitor_count(rfapi_handle); extern int rfapiShowVncQueries(void *stream, struct prefix *pfx_match); #endif frr-7.2.1/bgpd/rfapi/vnc_debug.c0000644000000000000000000001225213610377563013333 00000000000000/* * * Copyright 2016, LabN Consulting, L.L.C. * * This program is free software; you can redistribute it and/or * modify it under the terms of the GNU General Public License * as published by the Free Software Foundation; either version 2 * of the License, or (at your option) any later version. * * 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; see the file COPYING; if not, write to the Free Software * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA */ #include "lib/zebra.h" #include #include "lib/prefix.h" #include "lib/linklist.h" #include "lib/stream.h" #include "lib/command.h" #include "lib/log.h" #include "bgpd/rfapi/vnc_debug.h" /* * debug state storage */ unsigned long conf_vnc_debug; unsigned long term_vnc_debug; struct vnc_debug { unsigned long bit; const char *name; }; struct vnc_debug vncdebug[] = { {VNC_DEBUG_RFAPI_QUERY, "rfapi-query"}, {VNC_DEBUG_IMPORT_BI_ATTACH, "import-bi-attach"}, {VNC_DEBUG_IMPORT_DEL_REMOTE, "import-del-remote"}, {VNC_DEBUG_EXPORT_BGP_GETCE, "export-bgp-getce"}, {VNC_DEBUG_EXPORT_BGP_DIRECT_ADD, "export-bgp-direct-add"}, {VNC_DEBUG_IMPORT_BGP_ADD_ROUTE, "import-bgp-add-route"}, {VNC_DEBUG_VERBOSE, "verbose"}, }; #define VNC_STR "VNC information\n" /*********************************************************************** * debug bgp vnc ***********************************************************************/ DEFUN (debug_bgp_vnc, debug_bgp_vnc_cmd, "debug bgp vnc ", DEBUG_STR BGP_STR VNC_STR "rfapi query handling\n" "import BI atachment\n" "import delete remote routes\n" "verbose logging\n") { size_t i; for (i = 0; i < (sizeof(vncdebug) / sizeof(struct vnc_debug)); ++i) { if (strmatch(argv[3]->text, vncdebug[i].name)) { if (vty->node == CONFIG_NODE) { conf_vnc_debug |= vncdebug[i].bit; term_vnc_debug |= vncdebug[i].bit; } else { term_vnc_debug |= vncdebug[i].bit; vty_out(vty, "BGP vnc %s debugging is on\n", vncdebug[i].name); } return CMD_SUCCESS; } } vty_out(vty, "Unknown debug flag: %s\n", argv[3]->arg); return CMD_WARNING_CONFIG_FAILED; } DEFUN (no_debug_bgp_vnc, no_debug_bgp_vnc_cmd, "no debug bgp vnc ", NO_STR DEBUG_STR BGP_STR VNC_STR "rfapi query handling\n" "import BI atachment\n" "import delete remote routes\n" "verbose logging\n") { size_t i; for (i = 0; i < (sizeof(vncdebug) / sizeof(struct vnc_debug)); ++i) { if (strmatch(argv[argc - 1]->text, vncdebug[i].name)) { if (vty->node == CONFIG_NODE) { conf_vnc_debug &= ~vncdebug[i].bit; term_vnc_debug &= ~vncdebug[i].bit; } else { term_vnc_debug &= ~vncdebug[i].bit; vty_out(vty, "BGP vnc %s debugging is off\n", vncdebug[i].name); } return CMD_SUCCESS; } } vty_out(vty, "Unknown debug flag: %s\n", argv[3]->arg); return CMD_WARNING_CONFIG_FAILED; } /*********************************************************************** * no debug bgp vnc all ***********************************************************************/ DEFUN (no_debug_bgp_vnc_all, no_debug_bgp_vnc_all_cmd, "no debug all bgp vnc", NO_STR DEBUG_STR "Disable all VNC debugging\n" BGP_STR VNC_STR) { term_vnc_debug = 0; vty_out(vty, "All possible VNC debugging has been turned off\n"); return CMD_SUCCESS; } /*********************************************************************** * show/save ***********************************************************************/ DEFUN_NOSH (show_debugging_bgp_vnc, show_debugging_bgp_vnc_cmd, "show debugging bgp vnc", SHOW_STR DEBUG_STR BGP_STR VNC_STR) { size_t i; vty_out(vty, "BGP VNC debugging status:\n"); for (i = 0; i < (sizeof(vncdebug) / sizeof(struct vnc_debug)); ++i) { if (term_vnc_debug & vncdebug[i].bit) { vty_out(vty, " BGP VNC %s debugging is on\n", vncdebug[i].name); } } vty_out(vty, "\n"); return CMD_SUCCESS; } static int bgp_vnc_config_write_debug(struct vty *vty) { int write = 0; size_t i; for (i = 0; i < array_size(vncdebug); ++i) { if (conf_vnc_debug & vncdebug[i].bit) { vty_out(vty, "debug bgp vnc %s\n", vncdebug[i].name); write++; } } return write; } static struct cmd_node debug_node = {DEBUG_VNC_NODE, "", 1}; void vnc_debug_init(void) { install_node(&debug_node, bgp_vnc_config_write_debug); install_element(ENABLE_NODE, &show_debugging_bgp_vnc_cmd); install_element(ENABLE_NODE, &debug_bgp_vnc_cmd); install_element(CONFIG_NODE, &debug_bgp_vnc_cmd); install_element(ENABLE_NODE, &no_debug_bgp_vnc_cmd); install_element(CONFIG_NODE, &no_debug_bgp_vnc_cmd); install_element(ENABLE_NODE, &no_debug_bgp_vnc_all_cmd); install_element(CONFIG_NODE, &no_debug_bgp_vnc_all_cmd); } frr-7.2.1/bgpd/rfapi/vnc_debug.h0000644000000000000000000000323013610377563013334 00000000000000/* * * Copyright 2016, LabN Consulting, L.L.C. * * This program is free software; you can redistribute it and/or * modify it under the terms of the GNU General Public License * as published by the Free Software Foundation; either version 2 * of the License, or (at your option) any later version. * * 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; see the file COPYING; if not, write to the Free Software * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA */ #ifndef _QUAGGA_BGP_VNC_DEBUG_H #define _QUAGGA_BGP_VNC_DEBUG_H #if ENABLE_BGP_VNC /* * debug state storage */ extern unsigned long conf_vnc_debug; extern unsigned long term_vnc_debug; /* * debug flag bits */ #define VNC_DEBUG_RFAPI_QUERY 0x00000001 #define VNC_DEBUG_IMPORT_BI_ATTACH 0x00000002 #define VNC_DEBUG_IMPORT_DEL_REMOTE 0x00000004 #define VNC_DEBUG_EXPORT_BGP_GETCE 0x00000008 #define VNC_DEBUG_EXPORT_BGP_DIRECT_ADD 0x00000010 #define VNC_DEBUG_IMPORT_BGP_ADD_ROUTE 0x00000020 #define VNC_DEBUG_VERBOSE 0x00000040 #define VNC_DEBUG_ANY 0xFFFFFFFF #define VNC_DEBUG(bit) (term_vnc_debug & (VNC_DEBUG_ ## bit)) #define vnc_zlog_debug_verbose if (VNC_DEBUG(VERBOSE)) zlog_debug #define vnc_zlog_debug_any if (VNC_DEBUG(ANY)) zlog_debug extern void vnc_debug_init(void); #endif /* ENABLE_BGP_VNC */ #endif /* _QUAGGA_BGP_VNC_DEBUG_H */ frr-7.2.1/bgpd/rfapi/vnc_export_bgp.c0000644000000000000000000014366313610377563014431 00000000000000/* * * Copyright 2009-2016, LabN Consulting, L.L.C. * * * This program is free software; you can redistribute it and/or * modify it under the terms of the GNU General Public License * as published by the Free Software Foundation; either version 2 * of the License, or (at your option) any later version. * * 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; see the file COPYING; if not, write to the Free Software * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA */ /* * File: vnc_export_bgp.c * Purpose: Export routes to BGP directly (not via zebra) */ #include "lib/zebra.h" #include "lib/prefix.h" #include "lib/agg_table.h" #include "lib/vty.h" #include "lib/log.h" #include "lib/stream.h" #include "lib/memory.h" #include "lib/linklist.h" #include "lib/plist.h" #include "lib/routemap.h" #include "lib/lib_errors.h" #include "bgpd/bgpd.h" #include "bgpd/bgp_ecommunity.h" #include "bgpd/bgp_attr.h" #include "bgpd/bgp_aspath.h" #include "bgpd/rfapi/vnc_export_bgp.h" #include "bgpd/rfapi/vnc_export_bgp_p.h" #include "bgpd/rfapi/vnc_export_table.h" #include "bgpd/rfapi/bgp_rfapi_cfg.h" #include "bgpd/rfapi/rfapi.h" #include "bgpd/rfapi/rfapi_import.h" #include "bgpd/rfapi/rfapi_private.h" #include "bgpd/rfapi/rfapi_backend.h" #include "bgpd/rfapi/rfapi_vty.h" #include "bgpd/rfapi/vnc_debug.h" static void vnc_direct_add_rn_group_rd(struct bgp *bgp, struct rfapi_nve_group_cfg *rfg, struct agg_node *rn, struct attr *attr, afi_t afi, struct rfapi_descriptor *irfd); /*********************************************************************** * Export methods that set nexthop to CE (from 5226 roo EC) BEGIN ***********************************************************************/ /* * Memory allocation approach: make a ghost attr that * has non-interned parts for the modifications. ghost attr * memory is allocated by caller. * * - extract ce (=5226) EC and use as new nexthop * - strip Tunnel Encap attr * - copy all ECs */ static void encap_attr_export_ce(struct attr *new, struct attr *orig, struct prefix *use_nexthop) { /* * Make "new" a ghost attr copy of "orig" */ memset(new, 0, sizeof(struct attr)); *new = *orig; /* * Set nexthop */ switch (use_nexthop->family) { case AF_INET: new->nexthop = use_nexthop->u.prefix4; new->mp_nexthop_len = 4; /* bytes */ new->flag |= ATTR_FLAG_BIT(BGP_ATTR_NEXT_HOP); break; case AF_INET6: new->mp_nexthop_global = use_nexthop->u.prefix6; new->mp_nexthop_len = 16; /* bytes */ break; default: assert(0); break; } /* * Set MED * * Note that it will be deleted when BGP sends to any eBGP * peer unless PEER_FLAG_MED_UNCHANGED is set: * * neighbor NEIGHBOR attribute-unchanged med */ if (!CHECK_FLAG(new->flag, BGP_ATTR_MULTI_EXIT_DISC)) { if (CHECK_FLAG(new->flag, BGP_ATTR_LOCAL_PREF)) { if (new->local_pref > 255) new->med = 0; else new->med = 255 - new->local_pref; } else { new->med = 255; /* shouldn't happen */ } new->flag |= ATTR_FLAG_BIT(BGP_ATTR_MULTI_EXIT_DISC); } /* * "new" is now a ghost attr: * - it owns an "extra" struct * - it owns any non-interned parts * - any references to interned parts are not counted * * Caller should, after using the attr, call: * - bgp_attr_flush() to free non-interned parts */ } static int getce(struct bgp *bgp, struct attr *attr, struct prefix *pfx_ce) { uint8_t *ecp; int i; uint16_t localadmin = bgp->rfapi_cfg->resolve_nve_roo_local_admin; for (ecp = attr->ecommunity->val, i = 0; i < attr->ecommunity->size; ++i, ecp += ECOMMUNITY_SIZE) { if (VNC_DEBUG(EXPORT_BGP_GETCE)) { vnc_zlog_debug_any( "%s: %02x %02x %02x %02x %02x %02x %02x %02x", __func__, ecp[0], ecp[1], ecp[2], ecp[3], ecp[4], ecp[5], ecp[6], ecp[7]); } /* * is it ROO? */ if (ecp[0] != 1 || ecp[1] != 3) { continue; } /* * Match local admin value? */ if (ecp[6] != ((localadmin & 0xff00) >> 8) || ecp[7] != (localadmin & 0xff)) continue; memset((uint8_t *)pfx_ce, 0, sizeof(*pfx_ce)); memcpy(&pfx_ce->u.prefix4, ecp + 2, 4); pfx_ce->family = AF_INET; pfx_ce->prefixlen = 32; return 0; } return -1; } void vnc_direct_bgp_add_route_ce(struct bgp *bgp, struct agg_node *rn, struct bgp_path_info *bpi) { struct attr *attr = bpi->attr; struct peer *peer = bpi->peer; struct prefix *prefix = &rn->p; afi_t afi = family2afi(prefix->family); struct bgp_node *urn; struct bgp_path_info *ubpi; struct attr hattr; struct attr *iattr; struct prefix ce_nexthop; struct prefix post_routemap_nexthop; if (!afi) { flog_err(EC_LIB_DEVELOPMENT, "%s: can't get afi of route node", __func__); return; } if ((bpi->type != ZEBRA_ROUTE_BGP) || (bpi->sub_type != BGP_ROUTE_NORMAL && bpi->sub_type != BGP_ROUTE_RFP && bpi->sub_type != BGP_ROUTE_STATIC)) { vnc_zlog_debug_verbose( "%s: wrong route type/sub_type for export, skipping", __func__); return; } /* check bgp redist flag for vnc direct ("vpn") routes */ if (!bgp->redist[afi][ZEBRA_ROUTE_VNC_DIRECT]) { vnc_zlog_debug_verbose( "%s: bgp redistribution of VNC direct routes is off", __func__); return; } if (!bgp->rfapi_cfg) { vnc_zlog_debug_verbose("%s: bgp->rfapi_cfg is NULL, skipping", __func__); return; } if (!VNC_EXPORT_BGP_CE_ENABLED(bgp->rfapi_cfg)) { vnc_zlog_debug_verbose( "%s: export-to-bgp ce mode not enabled, skipping", __func__); return; } /* * prefix list check */ if (bgp->rfapi_cfg->plist_export_bgp[afi]) { if (prefix_list_apply(bgp->rfapi_cfg->plist_export_bgp[afi], prefix) == PREFIX_DENY) { vnc_zlog_debug_verbose( "%s: prefix list denied, skipping", __func__); return; } } /* * Extract CE * This works only for IPv4 because IPv6 addresses are too big * to fit in an extended community */ if (getce(bgp, attr, &ce_nexthop)) { vnc_zlog_debug_verbose("%s: EC has no encoded CE, skipping", __func__); return; } /* * Is this route already represented in the unicast RIB? * (look up prefix; compare route type, sub_type, peer, nexthop) */ urn = bgp_afi_node_get(bgp->rib[afi][SAFI_UNICAST], afi, SAFI_UNICAST, prefix, NULL); for (ubpi = bgp_node_get_bgp_path_info(urn); ubpi; ubpi = ubpi->next) { struct prefix unicast_nexthop; if (CHECK_FLAG(ubpi->flags, BGP_PATH_REMOVED)) continue; rfapiUnicastNexthop2Prefix(afi, ubpi->attr, &unicast_nexthop); if (ubpi->type == ZEBRA_ROUTE_VNC_DIRECT && ubpi->sub_type == BGP_ROUTE_REDISTRIBUTE && ubpi->peer == peer && prefix_same(&unicast_nexthop, &ce_nexthop)) { vnc_zlog_debug_verbose( "%s: already have matching exported unicast route, skipping", __func__); return; } } /* * Construct new attribute set with CE addr as * nexthop and without Tunnel Encap attr */ encap_attr_export_ce(&hattr, attr, &ce_nexthop); if (bgp->rfapi_cfg->routemap_export_bgp) { struct bgp_path_info info; route_map_result_t ret; memset(&info, 0, sizeof(info)); info.peer = peer; info.attr = &hattr; ret = route_map_apply(bgp->rfapi_cfg->routemap_export_bgp, prefix, RMAP_BGP, &info); if (ret == RMAP_DENYMATCH) { bgp_attr_flush(&hattr); return; } } iattr = bgp_attr_intern(&hattr); bgp_attr_flush(&hattr); /* * Rule: disallow route-map alteration of next-hop, because it * would make it too difficult to keep track of the correspondence * between VPN routes and unicast routes. */ rfapiUnicastNexthop2Prefix(afi, iattr, &post_routemap_nexthop); if (!prefix_same(&ce_nexthop, &post_routemap_nexthop)) { vnc_zlog_debug_verbose( "%s: route-map modification of nexthop not allowed, skipping", __func__); bgp_attr_unintern(&iattr); return; } bgp_update(peer, prefix, 0, /* addpath_id */ iattr, /* bgp_update copies this attr */ afi, SAFI_UNICAST, ZEBRA_ROUTE_VNC_DIRECT, BGP_ROUTE_REDISTRIBUTE, NULL, /* RD not used for unicast */ NULL, 0, /* tag not used for unicast */ 0, NULL); /* EVPN not used */ bgp_attr_unintern(&iattr); } /* * "Withdrawing a Route" export process */ void vnc_direct_bgp_del_route_ce(struct bgp *bgp, struct agg_node *rn, struct bgp_path_info *bpi) { afi_t afi = family2afi(rn->p.family); struct bgp_path_info *vbpi; struct prefix ce_nexthop; if (!afi) { flog_err(EC_LIB_DEVELOPMENT, "%s: bad afi", __func__); return; } /* check bgp redist flag for vnc direct ("vpn") routes */ if (!bgp->redist[afi][ZEBRA_ROUTE_VNC_DIRECT]) { vnc_zlog_debug_verbose( "%s: bgp redistribution of VNC direct routes is off", __func__); return; } if (!bgp->rfapi_cfg) { vnc_zlog_debug_verbose("%s: bgp->rfapi_cfg is NULL, skipping", __func__); return; } if (!VNC_EXPORT_BGP_CE_ENABLED(bgp->rfapi_cfg)) { vnc_zlog_debug_verbose( "%s: export-to-bgp ce mode not enabled, skipping", __func__); return; } /* * Extract CE * This works only for IPv4 because IPv6 addresses are too big * to fit in an extended community */ if (getce(bgp, bpi->attr, &ce_nexthop)) { vnc_zlog_debug_verbose("%s: EC has no encoded CE, skipping", __func__); return; } /* * Look for other VPN routes with same prefix, same 5226 CE, * same peer. If at least one is present, don't remove the * route from the unicast RIB */ for (vbpi = rn->info; vbpi; vbpi = vbpi->next) { struct prefix ce; if (bpi == vbpi) continue; if (bpi->peer != vbpi->peer) continue; if (getce(bgp, vbpi->attr, &ce)) continue; if (prefix_same(&ce, &ce_nexthop)) { vnc_zlog_debug_verbose( "%s: still have a route via CE, not deleting unicast", __func__); return; } } /* * withdraw the route */ bgp_withdraw(bpi->peer, &rn->p, 0, /* addpath_id */ NULL, /* attr, ignored */ afi, SAFI_UNICAST, ZEBRA_ROUTE_VNC_DIRECT, BGP_ROUTE_REDISTRIBUTE, NULL, /* RD not used for unicast */ NULL, 0, NULL); /* tag not used for unicast */ } static void vnc_direct_bgp_vpn_enable_ce(struct bgp *bgp, afi_t afi) { struct agg_node *rn; struct bgp_path_info *ri; vnc_zlog_debug_verbose("%s: entry, afi=%d", __func__, afi); if (!bgp) return; if (!(bgp->rfapi_cfg)) return; if (!VNC_EXPORT_BGP_CE_ENABLED(bgp->rfapi_cfg)) { vnc_zlog_debug_verbose( "%s: export of CE routes not enabled, skipping", __func__); return; } if (afi != AFI_IP && afi != AFI_IP6) { vnc_zlog_debug_verbose("%s: bad afi: %d", __func__, afi); return; } /* * Go through entire ce import table and export to BGP unicast. */ for (rn = agg_route_top(bgp->rfapi->it_ce->imported_vpn[afi]); rn; rn = agg_route_next(rn)) { if (!rn->info) continue; { char prefixstr[PREFIX_STRLEN]; prefix2str(&rn->p, prefixstr, sizeof(prefixstr)); vnc_zlog_debug_verbose("%s: checking prefix %s", __func__, prefixstr); } for (ri = rn->info; ri; ri = ri->next) { vnc_zlog_debug_verbose("%s: ri->sub_type: %d", __func__, ri->sub_type); if (ri->sub_type == BGP_ROUTE_NORMAL || ri->sub_type == BGP_ROUTE_RFP || ri->sub_type == BGP_ROUTE_STATIC) { vnc_direct_bgp_add_route_ce(bgp, rn, ri); } } } } static void vnc_direct_bgp_vpn_disable_ce(struct bgp *bgp, afi_t afi) { struct bgp_node *rn; vnc_zlog_debug_verbose("%s: entry, afi=%d", __func__, afi); if (!bgp) return; if (afi != AFI_IP && afi != AFI_IP6) { vnc_zlog_debug_verbose("%s: bad afi: %d", __func__, afi); return; } /* * Go through the entire BGP unicast table and remove routes that * originated from us */ for (rn = bgp_table_top(bgp->rib[afi][SAFI_UNICAST]); rn; rn = bgp_route_next(rn)) { struct bgp_path_info *ri; struct bgp_path_info *next; for (ri = bgp_node_get_bgp_path_info(rn), next = NULL; ri; ri = next) { next = ri->next; if (ri->type == ZEBRA_ROUTE_VNC_DIRECT && ri->sub_type == BGP_ROUTE_REDISTRIBUTE) { bgp_withdraw( ri->peer, &rn->p, /* prefix */ 0, /* addpath_id */ NULL, /* ignored */ AFI_IP, SAFI_UNICAST, ZEBRA_ROUTE_VNC_DIRECT, BGP_ROUTE_REDISTRIBUTE, NULL, /* RD not used for unicast */ NULL, 0, NULL); /* tag not used for unicast */ } } } } /*********************************************************************** * Export methods that set nexthop to CE (from 5226 roo EC) END ***********************************************************************/ /*********************************************************************** * Export methods that proxy nexthop BEGIN ***********************************************************************/ static struct ecommunity *vnc_route_origin_ecom(struct agg_node *rn) { struct ecommunity *new; struct bgp_path_info *bpi; if (!rn->info) return NULL; new = ecommunity_new(); for (bpi = rn->info; bpi; bpi = bpi->next) { struct ecommunity_val roec; switch (BGP_MP_NEXTHOP_FAMILY(bpi->attr->mp_nexthop_len)) { case AF_INET: memset(&roec, 0, sizeof(roec)); roec.val[0] = 0x01; roec.val[1] = 0x03; memcpy(roec.val + 2, &bpi->attr->mp_nexthop_global_in.s_addr, 4); roec.val[6] = 0; roec.val[7] = 0; ecommunity_add_val(new, &roec); break; case AF_INET6: /* No support for IPv6 addresses in extended communities */ break; } } if (!new->size) { ecommunity_free(&new); new = NULL; } return new; } static struct ecommunity *vnc_route_origin_ecom_single(struct in_addr *origin) { struct ecommunity *new; struct ecommunity_val roec; memset(&roec, 0, sizeof(roec)); roec.val[0] = 0x01; roec.val[1] = 0x03; memcpy(roec.val + 2, &origin->s_addr, 4); roec.val[6] = 0; roec.val[7] = 0; new = ecommunity_new(); assert(new); ecommunity_add_val(new, &roec); if (!new->size) { ecommunity_free(&new); new = NULL; } return new; } /* * New memory allocation approach: make a ghost attr that * has non-interned parts for the modifications. ghost attr * memory is allocated by caller. */ static int encap_attr_export(struct attr *new, struct attr *orig, struct prefix *new_nexthop, struct agg_node *rn) /* for VN addrs for ecom list */ /* if rn is 0, use route's nexthop */ { struct prefix orig_nexthop; struct prefix *use_nexthop; static struct ecommunity *ecom_ro; if (new_nexthop) { use_nexthop = new_nexthop; } else { use_nexthop = &orig_nexthop; orig_nexthop.family = BGP_MP_NEXTHOP_FAMILY(orig->mp_nexthop_len); if (orig_nexthop.family == AF_INET) { orig_nexthop.prefixlen = 32; orig_nexthop.u.prefix4 = orig->mp_nexthop_global_in; } else if (orig_nexthop.family == AF_INET6) { orig_nexthop.prefixlen = 128; orig_nexthop.u.prefix6 = orig->mp_nexthop_global; } else { return -1; /* FAIL - can't compute nexthop */ } } /* * Make "new" a ghost attr copy of "orig" */ memset(new, 0, sizeof(struct attr)); *new = *orig; /* * Set nexthop */ switch (use_nexthop->family) { case AF_INET: new->nexthop = use_nexthop->u.prefix4; new->mp_nexthop_len = 4; /* bytes */ new->flag |= ATTR_FLAG_BIT(BGP_ATTR_NEXT_HOP); break; case AF_INET6: new->mp_nexthop_global = use_nexthop->u.prefix6; new->mp_nexthop_len = 16; /* bytes */ break; default: assert(0); break; } if (rn) { ecom_ro = vnc_route_origin_ecom(rn); } else { /* TBD use lcom for IPv6 */ ecom_ro = vnc_route_origin_ecom_single(&use_nexthop->u.prefix4); } if (new->ecommunity) { if (ecom_ro) new->ecommunity = ecommunity_merge(ecom_ro, new->ecommunity); } else { new->ecommunity = ecom_ro; } if (ecom_ro) { new->flag |= ATTR_FLAG_BIT(BGP_ATTR_EXT_COMMUNITIES); } /* * Set MED * * Note that it will be deleted when BGP sends to any eBGP * peer unless PEER_FLAG_MED_UNCHANGED is set: * * neighbor NEIGHBOR attribute-unchanged med */ if (!CHECK_FLAG(new->flag, BGP_ATTR_MULTI_EXIT_DISC)) { if (CHECK_FLAG(new->flag, BGP_ATTR_LOCAL_PREF)) { if (new->local_pref > 255) new->med = 0; else new->med = 255 - new->local_pref; } else { new->med = 255; /* shouldn't happen */ } new->flag |= ATTR_FLAG_BIT(BGP_ATTR_MULTI_EXIT_DISC); } /* * "new" is now a ghost attr: * - it owns an "extra" struct * - it owns any non-interned parts * - any references to interned parts are not counted * * Caller should, after using the attr, call: * - bgp_attr_flush() to free non-interned parts */ return 0; } /* * "Adding a Route" export process */ void vnc_direct_bgp_add_prefix(struct bgp *bgp, struct rfapi_import_table *import_table, struct agg_node *rn) { struct attr attr = {0}; struct listnode *node, *nnode; struct rfapi_rfg_name *rfgn; afi_t afi = family2afi(rn->p.family); if (!afi) { flog_err(EC_LIB_DEVELOPMENT, "%s: can't get afi of route node", __func__); return; } /* check bgp redist flag for vnc direct ("vpn") routes */ if (!bgp->redist[afi][ZEBRA_ROUTE_VNC_DIRECT]) { vnc_zlog_debug_verbose( "%s: bgp redistribution of VNC direct routes is off", __func__); return; } if (!bgp->rfapi_cfg) { vnc_zlog_debug_verbose("%s: bgp->rfapi_cfg is NULL, skipping", __func__); return; } if (!VNC_EXPORT_BGP_GRP_ENABLED(bgp->rfapi_cfg)) { vnc_zlog_debug_verbose( "%s: export-to-bgp group mode not enabled, skipping", __func__); return; } if (!listcount(bgp->rfapi_cfg->rfg_export_direct_bgp_l)) { vnc_zlog_debug_verbose( "%s: no bgp-direct export nve group, skipping", __func__); return; } bgp_attr_default_set(&attr, BGP_ORIGIN_INCOMPLETE); /* TBD set some configured med, see add_vnc_route() */ vnc_zlog_debug_verbose( "%s: looping over nve-groups in direct-bgp export list", __func__); for (ALL_LIST_ELEMENTS(bgp->rfapi_cfg->rfg_export_direct_bgp_l, node, nnode, rfgn)) { struct listnode *ln; /* * If nve group is not defined yet, skip it */ if (!rfgn->rfg) continue; /* * If the nve group uses a different import table, skip it */ if (import_table != rfgn->rfg->rfapi_import_table) continue; /* * if no NVEs currently associated with this group, skip it */ if (rfgn->rfg->type != RFAPI_GROUP_CFG_VRF && !rfgn->rfg->nves) continue; /* * per-nve-group prefix list check */ if (rfgn->rfg->plist_export_bgp[afi]) { if (prefix_list_apply(rfgn->rfg->plist_export_bgp[afi], &rn->p) == PREFIX_DENY) continue; } if (rfgn->rfg->type == RFAPI_GROUP_CFG_VRF) { vnc_direct_add_rn_group_rd(bgp, rfgn->rfg, rn, &attr, afi, rfgn->rfg->rfd); /* * yuck! * - but consistent with rest of function */ continue; } /* * For each NVE that is assigned to the export nve group, * generate * a route with that NVE as its next hop */ for (ln = listhead(rfgn->rfg->nves); ln; ln = listnextnode(ln)) { vnc_direct_add_rn_group_rd(bgp, rfgn->rfg, rn, &attr, afi, listgetdata(ln)); } } aspath_unintern(&attr.aspath); } /* * "Withdrawing a Route" export process */ void vnc_direct_bgp_del_prefix(struct bgp *bgp, struct rfapi_import_table *import_table, struct agg_node *rn) { struct listnode *node, *nnode; struct rfapi_rfg_name *rfgn; afi_t afi = family2afi(rn->p.family); if (!afi) { flog_err(EC_LIB_DEVELOPMENT, "%s: can't get afi route node", __func__); return; } /* check bgp redist flag for vnc direct ("vpn") routes */ if (!bgp->redist[afi][ZEBRA_ROUTE_VNC_DIRECT]) { vnc_zlog_debug_verbose( "%s: bgp redistribution of VNC direct routes is off", __func__); return; } if (!bgp->rfapi_cfg) { vnc_zlog_debug_verbose("%s: bgp->rfapi_cfg is NULL, skipping", __func__); return; } if (!VNC_EXPORT_BGP_GRP_ENABLED(bgp->rfapi_cfg)) { vnc_zlog_debug_verbose( "%s: export-to-bgp group mode not enabled, skipping", __func__); return; } if (!listcount(bgp->rfapi_cfg->rfg_export_direct_bgp_l)) { vnc_zlog_debug_verbose( "%s: no bgp-direct export nve group, skipping", __func__); return; } for (ALL_LIST_ELEMENTS(bgp->rfapi_cfg->rfg_export_direct_bgp_l, node, nnode, rfgn)) { struct listnode *ln; /* * If nve group is not defined yet, skip it */ if (!rfgn->rfg) continue; /* * if no NVEs currently associated with this group, skip it */ if (rfgn->rfg->type != RFAPI_GROUP_CFG_VRF && !rfgn->rfg->nves) continue; /* * If the nve group uses a different import table, * skip it */ if (import_table != rfgn->rfg->rfapi_import_table) continue; if (rfgn->rfg->type == RFAPI_GROUP_CFG_VRF) { struct prefix nhp; struct rfapi_descriptor *irfd; irfd = rfgn->rfg->rfd; if (rfapiRaddr2Qprefix(&irfd->vn_addr, &nhp)) continue; bgp_withdraw(irfd->peer, &rn->p, /* prefix */ 0, /* addpath_id */ NULL, /* attr, ignored */ afi, SAFI_UNICAST, ZEBRA_ROUTE_VNC_DIRECT, BGP_ROUTE_REDISTRIBUTE, NULL, /* RD not used for unicast */ NULL, 0, NULL); /* tag not used for unicast */ /* * yuck! * - but consistent with rest of function */ continue; } /* * For each NVE that is assigned to the export nve group, * generate * a route with that NVE as its next hop */ for (ln = listhead(rfgn->rfg->nves); ln; ln = listnextnode(ln)) { struct prefix nhp; struct rfapi_descriptor *irfd; irfd = listgetdata(ln); if (rfapiRaddr2Qprefix(&irfd->vn_addr, &nhp)) continue; bgp_withdraw(irfd->peer, &rn->p, /* prefix */ 0, /* addpath_id */ NULL, /* attr, ignored */ afi, SAFI_UNICAST, ZEBRA_ROUTE_VNC_DIRECT, BGP_ROUTE_REDISTRIBUTE, NULL, /* RD not used for unicast */ NULL, 0, NULL); /* tag not used for unicast */ } } } void vnc_direct_bgp_add_nve(struct bgp *bgp, struct rfapi_descriptor *rfd) { struct listnode *node, *nnode; struct rfapi_rfg_name *rfgn; struct rfapi_nve_group_cfg *rfg = rfd->rfg; afi_t afi = family2afi(rfd->vn_addr.addr_family); if (!afi) { flog_err(EC_LIB_DEVELOPMENT, "%s: can't get afi of nve vn addr", __func__); return; } if (!bgp) return; if (!bgp->rfapi_cfg) { vnc_zlog_debug_verbose("%s: bgp->rfapi_cfg is NULL, skipping", __func__); return; } if (!VNC_EXPORT_BGP_GRP_ENABLED(bgp->rfapi_cfg)) { vnc_zlog_debug_verbose( "%s: export-to-bgp group mode not enabled, skipping", __func__); return; } if (!bgp->redist[afi][ZEBRA_ROUTE_VNC_DIRECT]) { vnc_zlog_debug_verbose( "%s: bgp redistribution of VNC direct routes is off", __func__); return; } /* * Loop over the list of NVE-Groups configured for * exporting to direct-bgp and see if this new NVE's * group is among them. */ for (ALL_LIST_ELEMENTS(bgp->rfapi_cfg->rfg_export_direct_bgp_l, node, nnode, rfgn)) { /* * Yes, this NVE's group is configured for export to direct-bgp */ if (rfgn->rfg == rfg) { struct agg_table *rt = NULL; struct agg_node *rn; struct attr attr = {0}; struct rfapi_import_table *import_table; import_table = rfg->rfapi_import_table; bgp_attr_default_set(&attr, BGP_ORIGIN_INCOMPLETE); /* TBD set some configured med, see add_vnc_route() */ if (afi == AFI_IP || afi == AFI_IP6) { rt = import_table->imported_vpn[afi]; } else { flog_err(EC_LIB_DEVELOPMENT, "%s: bad afi %d", __func__, afi); return; } /* * Walk the NVE-Group's VNC Import table */ for (rn = agg_route_top(rt); rn; rn = agg_route_next(rn)) { if (rn->info) { struct prefix nhp; struct rfapi_descriptor *irfd = rfd; struct attr hattr; struct attr *iattr; struct bgp_path_info info; if (rfapiRaddr2Qprefix(&irfd->vn_addr, &nhp)) continue; /* * per-nve-group prefix list check */ if (rfgn->rfg->plist_export_bgp[afi]) { if (prefix_list_apply( rfgn->rfg->plist_export_bgp [afi], &rn->p) == PREFIX_DENY) continue; } /* * Construct new attribute set with * NVE's VN addr as * nexthop and without Tunnel Encap attr */ if (encap_attr_export(&hattr, &attr, &nhp, rn)) continue; if (rfgn->rfg->routemap_export_bgp) { route_map_result_t ret; info.peer = irfd->peer; info.attr = &hattr; ret = route_map_apply( rfgn->rfg ->routemap_export_bgp, &rn->p, RMAP_BGP, &info); if (ret == RMAP_DENYMATCH) { bgp_attr_flush(&hattr); continue; } } iattr = bgp_attr_intern(&hattr); bgp_attr_flush(&hattr); bgp_update( irfd->peer, &rn->p, /* prefix */ 0, /* addpath_id */ iattr, /* bgp_update copies it */ afi, SAFI_UNICAST, ZEBRA_ROUTE_VNC_DIRECT, BGP_ROUTE_REDISTRIBUTE, NULL, /* RD not used for unicast */ NULL, /* tag not used for unicast */ 0, 0, NULL); /* EVPN not used */ bgp_attr_unintern(&iattr); } } aspath_unintern(&attr.aspath); } } } void vnc_direct_bgp_del_nve(struct bgp *bgp, struct rfapi_descriptor *rfd) { struct listnode *node, *nnode; struct rfapi_rfg_name *rfgn; struct rfapi_nve_group_cfg *rfg = rfd->rfg; afi_t afi = family2afi(rfd->vn_addr.addr_family); if (!afi) { flog_err(EC_LIB_DEVELOPMENT, "%s: can't get afi of nve vn addr", __func__); return; } if (!bgp) return; if (!bgp->rfapi_cfg) { vnc_zlog_debug_verbose("%s: bgp->rfapi_cfg is NULL, skipping", __func__); return; } if (!VNC_EXPORT_BGP_GRP_ENABLED(bgp->rfapi_cfg)) { vnc_zlog_debug_verbose( "%s: export-to-bgp group mode not enabled, skipping", __func__); return; } if (!bgp->redist[afi][ZEBRA_ROUTE_VNC_DIRECT]) { vnc_zlog_debug_verbose( "%s: bgp redistribution of VNC direct routes is off", __func__); return; } /* * Loop over the list of NVE-Groups configured for * exporting to direct-bgp and see if this new NVE's * group is among them. */ for (ALL_LIST_ELEMENTS(bgp->rfapi_cfg->rfg_export_direct_bgp_l, node, nnode, rfgn)) { /* * Yes, this NVE's group is configured for export to direct-bgp */ if (rfg && rfgn->rfg == rfg) { struct agg_table *rt = NULL; struct agg_node *rn; struct rfapi_import_table *import_table; import_table = rfg->rfapi_import_table; if (afi == AFI_IP || afi == AFI_IP6) { rt = import_table->imported_vpn[afi]; } else { flog_err(EC_LIB_DEVELOPMENT, "%s: bad afi %d", __func__, afi); return; } /* * Walk the NVE-Group's VNC Import table */ for (rn = agg_route_top(rt); rn; rn = agg_route_next(rn)) { if (rn->info) { struct prefix nhp; struct rfapi_descriptor *irfd = rfd; if (rfapiRaddr2Qprefix(&irfd->vn_addr, &nhp)) continue; bgp_withdraw(irfd->peer, &rn->p, /* prefix */ 0, /* addpath_id */ NULL, /* attr, ignored */ afi, SAFI_UNICAST, ZEBRA_ROUTE_VNC_DIRECT, BGP_ROUTE_REDISTRIBUTE, NULL, /* RD not used for unicast */ NULL, 0, NULL); /* tag not used for unicast */ } } } } } static void vnc_direct_add_rn_group_rd(struct bgp *bgp, struct rfapi_nve_group_cfg *rfg, struct agg_node *rn, struct attr *attr, afi_t afi, struct rfapi_descriptor *irfd) { struct prefix nhp; struct bgp_path_info info; struct attr hattr; struct attr *iattr; if (irfd == NULL && rfg->type != RFAPI_GROUP_CFG_VRF) { /* need new rfapi_handle, for peer strcture * -- based on vnc_add_vrf_prefi */ assert(rfg->rfd == NULL); if (!rfg->rt_export_list || !rfg->rfapi_import_table) { vnc_zlog_debug_verbose( "%s: VRF \"%s\" is missing RT import/export configuration.\n", __func__, rfg->name); return; } if (!rfg->rd.prefixlen) { vnc_zlog_debug_verbose( "%s: VRF \"%s\" is missing RD configuration.\n", __func__, rfg->name); return; } if (rfg->label > MPLS_LABEL_MAX) { vnc_zlog_debug_verbose( "%s: VRF \"%s\" is missing defaul label configuration.\n", __func__, rfg->name); return; } irfd = XCALLOC(MTYPE_RFAPI_DESC, sizeof(struct rfapi_descriptor)); irfd->bgp = bgp; rfg->rfd = irfd; /* * leave most fields empty as will get from (dynamic) config * when needed */ irfd->default_tunneltype_option.type = BGP_ENCAP_TYPE_MPLS; irfd->cookie = rfg; if (rfg->vn_prefix.family && !CHECK_FLAG(rfg->flags, RFAPI_RFG_VPN_NH_SELF)) { rfapiQprefix2Raddr(&rfg->vn_prefix, &irfd->vn_addr); } else { memset(&irfd->vn_addr, 0, sizeof(struct rfapi_ip_addr)); irfd->vn_addr.addr_family = AF_INET; irfd->vn_addr.addr.v4 = bgp->router_id; } irfd->un_addr = irfd->vn_addr; /* sigh, need something in UN for lookups */ vnc_zlog_debug_verbose("%s: Opening RFD for VRF %s", __func__, rfg->name); rfapi_init_and_open(bgp, irfd, rfg); } if (irfd == NULL || rfapiRaddr2Qprefix(&irfd->vn_addr, &nhp)) return; /* * Construct new attribute set with NVE's VN * addr as * nexthop and without Tunnel Encap attr */ if (encap_attr_export(&hattr, attr, &nhp, rn)) return; if (VNC_DEBUG(EXPORT_BGP_DIRECT_ADD)) { vnc_zlog_debug_any("%s: attr follows", __func__); rfapiPrintAttrPtrs(NULL, attr); vnc_zlog_debug_any("%s: hattr follows", __func__); rfapiPrintAttrPtrs(NULL, &hattr); } if (rfg->routemap_export_bgp) { route_map_result_t ret; info.peer = irfd->peer; info.attr = &hattr; ret = route_map_apply(rfg->routemap_export_bgp, &rn->p, RMAP_BGP, &info); if (ret == RMAP_DENYMATCH) { bgp_attr_flush(&hattr); vnc_zlog_debug_verbose( "%s: route map says DENY, so not calling bgp_update", __func__); return; } } if (VNC_DEBUG(EXPORT_BGP_DIRECT_ADD)) { vnc_zlog_debug_any("%s: hattr after route_map_apply:", __func__); rfapiPrintAttrPtrs(NULL, &hattr); } iattr = bgp_attr_intern(&hattr); bgp_attr_flush(&hattr); bgp_update(irfd->peer, &rn->p, /* prefix */ 0, /* addpath_id */ iattr, /* bgp_update copies it */ afi, SAFI_UNICAST, ZEBRA_ROUTE_VNC_DIRECT, BGP_ROUTE_REDISTRIBUTE, NULL, /* RD not used for unicast */ NULL, /* tag not used for unicast */ 0, 0, NULL); /* EVPN not used */ bgp_attr_unintern(&iattr); return; } /* * Caller is responsible for ensuring that the specified nve-group * is actually part of the list of exported nve groups. */ static void vnc_direct_bgp_add_group_afi(struct bgp *bgp, struct rfapi_nve_group_cfg *rfg, afi_t afi) { struct agg_table *rt = NULL; struct agg_node *rn; struct attr attr = {0}; struct rfapi_import_table *import_table; vnc_zlog_debug_verbose("%s: entry", __func__); import_table = rfg->rfapi_import_table; if (!import_table) { vnc_zlog_debug_verbose( "%s: import table not defined, returning", __func__); return; } if (afi == AFI_IP || afi == AFI_IP6) { rt = import_table->imported_vpn[afi]; } else { flog_err(EC_LIB_DEVELOPMENT, "%s: bad afi %d", __func__, afi); return; } if (!rfg->nves && rfg->type != RFAPI_GROUP_CFG_VRF) { vnc_zlog_debug_verbose("%s: no NVEs in this group", __func__); return; } bgp_attr_default_set(&attr, BGP_ORIGIN_INCOMPLETE); /* TBD set some configured med, see add_vnc_route() */ /* * Walk the NVE-Group's VNC Import table */ for (rn = agg_route_top(rt); rn; rn = agg_route_next(rn)) { if (rn->info) { struct listnode *ln; /* * per-nve-group prefix list check */ if (rfg->plist_export_bgp[afi]) { if (prefix_list_apply( rfg->plist_export_bgp[afi], &rn->p) == PREFIX_DENY) continue; } if (rfg->type == RFAPI_GROUP_CFG_VRF) { vnc_direct_add_rn_group_rd(bgp, rfg, rn, &attr, afi, rfg->rfd); /* * yuck! * - but consistent with rest of function */ continue; } /* * For each NVE that is assigned to the export nve * group, generate * a route with that NVE as its next hop */ for (ln = listhead(rfg->nves); ln; ln = listnextnode(ln)) { vnc_direct_add_rn_group_rd(bgp, rfg, rn, &attr, afi, listgetdata(ln)); } } } aspath_unintern(&attr.aspath); } /* * Caller is responsible for ensuring that the specified nve-group * is actually part of the list of exported nve groups. */ void vnc_direct_bgp_add_group(struct bgp *bgp, struct rfapi_nve_group_cfg *rfg) { vnc_direct_bgp_add_group_afi(bgp, rfg, AFI_IP); vnc_direct_bgp_add_group_afi(bgp, rfg, AFI_IP6); } static void vnc_direct_del_rn_group_rd(struct bgp *bgp, struct rfapi_nve_group_cfg *rfg, struct agg_node *rn, afi_t afi, struct rfapi_descriptor *irfd) { if (irfd == NULL) return; bgp_withdraw(irfd->peer, &rn->p, /* prefix */ 0, /* addpath_id */ NULL, /* attr, ignored */ afi, SAFI_UNICAST, ZEBRA_ROUTE_VNC_DIRECT, BGP_ROUTE_REDISTRIBUTE, NULL, /* RD not used for unicast */ NULL, 0, NULL); /* tag not used for unicast */ return; } /* * Caller is responsible for ensuring that the specified nve-group * was actually part of the list of exported nve groups. */ static void vnc_direct_bgp_del_group_afi(struct bgp *bgp, struct rfapi_nve_group_cfg *rfg, afi_t afi) { struct agg_table *rt = NULL; struct agg_node *rn; struct rfapi_import_table *import_table; vnc_zlog_debug_verbose("%s: entry", __func__); import_table = rfg->rfapi_import_table; if (!import_table) { vnc_zlog_debug_verbose( "%s: import table not defined, returning", __func__); return; } rt = import_table->imported_vpn[afi]; if (!rfg->nves && rfg->type != RFAPI_GROUP_CFG_VRF) { vnc_zlog_debug_verbose("%s: no NVEs in this group", __func__); return; } /* * Walk the NVE-Group's VNC Import table */ for (rn = agg_route_top(rt); rn; rn = agg_route_next(rn)) if (rn->info) { if (rfg->type == RFAPI_GROUP_CFG_VRF) vnc_direct_del_rn_group_rd(bgp, rfg, rn, afi, rfg->rfd); else { struct listnode *ln; /* * For each NVE that is assigned to the export * nve * group, generate * a route with that NVE as its next hop */ for (ln = listhead(rfg->nves); ln; ln = listnextnode(ln)) vnc_direct_del_rn_group_rd( bgp, rfg, rn, afi, listgetdata(ln)); } } } /* * Caller is responsible for ensuring that the specified nve-group * was actually part of the list of exported nve groups. */ void vnc_direct_bgp_del_group(struct bgp *bgp, struct rfapi_nve_group_cfg *rfg) { vnc_direct_bgp_del_group_afi(bgp, rfg, AFI_IP); vnc_direct_bgp_del_group_afi(bgp, rfg, AFI_IP6); } void vnc_direct_bgp_reexport_group_afi(struct bgp *bgp, struct rfapi_nve_group_cfg *rfg, afi_t afi) { struct listnode *node; struct rfapi_rfg_name *rfgn; if (VNC_EXPORT_BGP_GRP_ENABLED(bgp->rfapi_cfg)) { /* * look in the list of currently-exported groups */ for (ALL_LIST_ELEMENTS_RO( bgp->rfapi_cfg->rfg_export_direct_bgp_l, node, rfgn)) { if (rfgn->rfg == rfg) { /* * If it matches, reexport it */ vnc_direct_bgp_del_group_afi(bgp, rfg, afi); vnc_direct_bgp_add_group_afi(bgp, rfg, afi); break; } } } } static void vnc_direct_bgp_unexport_table(afi_t afi, struct agg_table *rt, struct list *nve_list) { if (nve_list) { struct agg_node *rn; for (rn = agg_route_top(rt); rn; rn = agg_route_next(rn)) { if (rn->info) { struct listnode *hln; struct rfapi_descriptor *irfd; for (ALL_LIST_ELEMENTS_RO(nve_list, hln, irfd)) { bgp_withdraw(irfd->peer, &rn->p, /* prefix */ 0, /* addpath_id */ NULL, /* attr, ignored */ afi, SAFI_UNICAST, ZEBRA_ROUTE_VNC_DIRECT, BGP_ROUTE_REDISTRIBUTE, NULL, /* RD not used for unicast */ NULL, 0, NULL); /* tag not used for unicast, EVPN neither */ } } } } } static void import_table_to_nve_list_direct_bgp(struct bgp *bgp, struct rfapi_import_table *it, struct list **nves, uint8_t family) { struct listnode *node; struct rfapi_rfg_name *rfgn; /* * Loop over the list of NVE-Groups configured for * exporting to direct-bgp. * * Build a list of NVEs that use this import table */ *nves = NULL; for (ALL_LIST_ELEMENTS_RO(bgp->rfapi_cfg->rfg_export_direct_bgp_l, node, rfgn)) { /* * If this NVE-Group's import table matches the current one */ if (rfgn->rfg && rfgn->rfg->rfapi_import_table == it) { if (rfgn->rfg->nves) nve_group_to_nve_list(rfgn->rfg, nves, family); else if (rfgn->rfg->rfd && rfgn->rfg->type == RFAPI_GROUP_CFG_VRF) { if (!*nves) *nves = list_new(); listnode_add(*nves, rfgn->rfg->rfd); } } } } void vnc_direct_bgp_vpn_enable(struct bgp *bgp, afi_t afi) { struct listnode *rfgn; struct rfapi_nve_group_cfg *rfg; if (!bgp) return; if (!VNC_EXPORT_BGP_GRP_ENABLED(bgp->rfapi_cfg)) { vnc_zlog_debug_verbose( "%s: export-to-bgp group mode not enabled, skipping", __func__); return; } if (afi != AFI_IP && afi != AFI_IP6) { vnc_zlog_debug_verbose("%s: bad afi: %d", __func__, afi); return; } /* * Policy is applied per-nve-group, so we need to iterate * over the groups to add everything. */ for (ALL_LIST_ELEMENTS_RO(bgp->rfapi_cfg->nve_groups_sequential, rfgn, rfg)) { /* * contains policy management */ vnc_direct_bgp_add_group_afi(bgp, rfg, afi); } } void vnc_direct_bgp_vpn_disable(struct bgp *bgp, afi_t afi) { struct rfapi_import_table *it; uint8_t family = afi2family(afi); vnc_zlog_debug_verbose("%s: entry, afi=%d", __func__, afi); if (!bgp) return; if (!bgp->rfapi) { vnc_zlog_debug_verbose("%s: rfapi not initialized", __func__); return; } if (!family || (afi != AFI_IP && afi != AFI_IP6)) { vnc_zlog_debug_verbose("%s: bad afi: %d", __func__, afi); return; } for (it = bgp->rfapi->imports; it; it = it->next) { struct list *nve_list = NULL; import_table_to_nve_list_direct_bgp(bgp, it, &nve_list, family); if (nve_list) { vnc_direct_bgp_unexport_table( afi, it->imported_vpn[afi], nve_list); list_delete(&nve_list); } } } /*********************************************************************** * Export methods that proxy nexthop END ***********************************************************************/ /*********************************************************************** * Export methods that preserve original nexthop BEGIN * rh = "registering nve" ***********************************************************************/ /* * "Adding a Route" export process * TBD do we need to check bpi->type and bpi->sub_type here, or does * caller do it? */ void vnc_direct_bgp_rh_add_route(struct bgp *bgp, afi_t afi, struct prefix *prefix, struct peer *peer, struct attr *attr) { struct vnc_export_info *eti; struct attr hattr; struct rfapi_cfg *hc; struct attr *iattr; if (!afi) { flog_err(EC_LIB_DEVELOPMENT, "%s: can't get afi of route node", __func__); return; } /* check bgp redist flag for vnc direct ("vpn") routes */ if (!bgp->redist[afi][ZEBRA_ROUTE_VNC_DIRECT]) { vnc_zlog_debug_verbose( "%s: bgp redistribution of VNC direct routes is off", __func__); return; } if (!(hc = bgp->rfapi_cfg)) { vnc_zlog_debug_verbose("%s: bgp->rfapi_cfg is NULL, skipping", __func__); return; } if (!VNC_EXPORT_BGP_RH_ENABLED(bgp->rfapi_cfg)) { vnc_zlog_debug_verbose( "%s: export-to-bgp RH mode not enabled, skipping", __func__); return; } /* * prefix list check */ if (hc->plist_export_bgp[afi]) { if (prefix_list_apply(hc->plist_export_bgp[afi], prefix) == PREFIX_DENY) return; } /* * Construct new attribute set with NVE's VN addr as * nexthop and without Tunnel Encap attr */ if (encap_attr_export(&hattr, attr, NULL, NULL)) return; if (hc->routemap_export_bgp) { struct bgp_path_info info; route_map_result_t ret; memset(&info, 0, sizeof(info)); info.peer = peer; info.attr = &hattr; ret = route_map_apply(hc->routemap_export_bgp, prefix, RMAP_BGP, &info); if (ret == RMAP_DENYMATCH) { bgp_attr_flush(&hattr); return; } } iattr = bgp_attr_intern(&hattr); bgp_attr_flush(&hattr); /* * record route information that we will need to expire * this route */ eti = vnc_eti_get(bgp, EXPORT_TYPE_BGP, prefix, peer, ZEBRA_ROUTE_VNC_DIRECT_RH, BGP_ROUTE_REDISTRIBUTE); rfapiGetVncLifetime(attr, &eti->lifetime); eti->lifetime = rfapiGetHolddownFromLifetime(eti->lifetime); if (eti->timer) { /* * export expiration timer is already running on * this route: cancel it */ thread_cancel(eti->timer); eti->timer = NULL; } bgp_update(peer, prefix, /* prefix */ 0, /* addpath_id */ iattr, /* bgp_update copies this attr */ afi, SAFI_UNICAST, ZEBRA_ROUTE_VNC_DIRECT_RH, BGP_ROUTE_REDISTRIBUTE, NULL, /* RD not used for unicast */ NULL, /* tag not used for unicast, EVPN neither */ 0, 0, NULL); /* EVPN not used */ bgp_attr_unintern(&iattr); } static int vncExportWithdrawTimer(struct thread *t) { struct vnc_export_info *eti = t->arg; /* * withdraw the route */ bgp_withdraw(eti->peer, &eti->node->p, 0, /* addpath_id */ NULL, /* attr, ignored */ family2afi(eti->node->p.family), SAFI_UNICAST, eti->type, eti->subtype, NULL, /* RD not used for unicast */ NULL, 0, NULL); /* tag not used for unicast, EVPN neither */ /* * Free the eti */ vnc_eti_delete(eti); return 0; } /* * "Withdrawing a Route" export process * TBD do we need to check bpi->type and bpi->sub_type here, or does * caller do it? */ void vnc_direct_bgp_rh_del_route(struct bgp *bgp, afi_t afi, struct prefix *prefix, struct peer *peer) { struct vnc_export_info *eti; if (!afi) { flog_err(EC_LIB_DEVELOPMENT, "%s: can't get afi route node", __func__); return; } /* check bgp redist flag for vnc direct ("vpn") routes */ if (!bgp->redist[afi][ZEBRA_ROUTE_VNC_DIRECT]) { vnc_zlog_debug_verbose( "%s: bgp redistribution of VNC direct routes is off", __func__); return; } if (!bgp->rfapi_cfg) { vnc_zlog_debug_verbose("%s: bgp->rfapi_cfg is NULL, skipping", __func__); return; } if (!VNC_EXPORT_BGP_RH_ENABLED(bgp->rfapi_cfg)) { vnc_zlog_debug_verbose( "%s: export-to-bgp group mode not enabled, skipping", __func__); return; } eti = vnc_eti_get(bgp, EXPORT_TYPE_BGP, prefix, peer, ZEBRA_ROUTE_VNC_DIRECT_RH, BGP_ROUTE_REDISTRIBUTE); if (!eti->timer && eti->lifetime <= INT32_MAX) { eti->timer = NULL; thread_add_timer(bm->master, vncExportWithdrawTimer, eti, eti->lifetime, &eti->timer); vnc_zlog_debug_verbose( "%s: set expiration timer for %u seconds", __func__, eti->lifetime); } } void vnc_direct_bgp_rh_vpn_enable(struct bgp *bgp, afi_t afi) { struct prefix_rd prd; struct bgp_node *prn; struct rfapi_cfg *hc; vnc_zlog_debug_verbose("%s: entry, afi=%d", __func__, afi); if (!bgp) return; if (!(hc = bgp->rfapi_cfg)) return; if (!VNC_EXPORT_BGP_RH_ENABLED(bgp->rfapi_cfg)) { vnc_zlog_debug_verbose( "%s: export of RH routes not enabled, skipping", __func__); return; } if (afi != AFI_IP && afi != AFI_IP6) { vnc_zlog_debug_verbose("%s: bad afi: %d", __func__, afi); return; } /* * Go through the entire BGP VPN table and export to BGP unicast. */ vnc_zlog_debug_verbose("%s: starting RD loop", __func__); /* Loop over all the RDs */ for (prn = bgp_table_top(bgp->rib[afi][SAFI_MPLS_VPN]); prn; prn = bgp_route_next(prn)) { struct bgp_table *table; struct bgp_node *rn; struct bgp_path_info *ri; memset(&prd, 0, sizeof(prd)); prd.family = AF_UNSPEC; prd.prefixlen = 64; memcpy(prd.val, prn->p.u.val, 8); /* This is the per-RD table of prefixes */ table = bgp_node_get_bgp_table_info(prn); if (!table) continue; for (rn = bgp_table_top(table); rn; rn = bgp_route_next(rn)) { /* * skip prefix list check if no routes here */ if (!bgp_node_has_bgp_path_info_data(rn)) continue; { char prefixstr[PREFIX_STRLEN]; prefix2str(&rn->p, prefixstr, sizeof(prefixstr)); vnc_zlog_debug_verbose("%s: checking prefix %s", __func__, prefixstr); } /* * prefix list check */ if (hc->plist_export_bgp[afi]) { if (prefix_list_apply(hc->plist_export_bgp[afi], &rn->p) == PREFIX_DENY) { vnc_zlog_debug_verbose( "%s: prefix list says DENY", __func__); continue; } } for (ri = bgp_node_get_bgp_path_info(rn); ri; ri = ri->next) { vnc_zlog_debug_verbose("%s: ri->sub_type: %d", __func__, ri->sub_type); if (ri->sub_type == BGP_ROUTE_NORMAL || ri->sub_type == BGP_ROUTE_RFP) { struct vnc_export_info *eti; struct attr hattr; struct attr *iattr; /* * Construct new attribute set with * NVE's VN addr as * nexthop and without Tunnel Encap attr */ if (encap_attr_export(&hattr, ri->attr, NULL, NULL)) { vnc_zlog_debug_verbose( "%s: encap_attr_export failed", __func__); continue; } if (hc->routemap_export_bgp) { struct bgp_path_info info; route_map_result_t ret; memset(&info, 0, sizeof(info)); info.peer = ri->peer; info.attr = &hattr; ret = route_map_apply( hc->routemap_export_bgp, &rn->p, RMAP_BGP, &info); if (ret == RMAP_DENYMATCH) { bgp_attr_flush(&hattr); vnc_zlog_debug_verbose( "%s: route map says DENY", __func__); continue; } } iattr = bgp_attr_intern(&hattr); bgp_attr_flush(&hattr); /* * record route information that we will * need to expire * this route */ eti = vnc_eti_get( bgp, EXPORT_TYPE_BGP, &rn->p, ri->peer, ZEBRA_ROUTE_VNC_DIRECT_RH, BGP_ROUTE_REDISTRIBUTE); rfapiGetVncLifetime(ri->attr, &eti->lifetime); if (eti->timer) { /* * export expiration timer is * already running on * this route: cancel it */ thread_cancel(eti->timer); eti->timer = NULL; } vnc_zlog_debug_verbose( "%s: calling bgp_update", __func__); bgp_update( ri->peer, &rn->p, /* prefix */ 0, /* addpath_id */ iattr, /* bgp_update copies it */ AFI_IP, SAFI_UNICAST, ZEBRA_ROUTE_VNC_DIRECT_RH, BGP_ROUTE_REDISTRIBUTE, NULL, /* RD not used for unicast */ NULL, /* tag not used for unicast, or EVPN */ 0, 0, NULL); /* EVPN not used */ bgp_attr_unintern(&iattr); } } } } } void vnc_direct_bgp_rh_vpn_disable(struct bgp *bgp, afi_t afi) { struct bgp_node *rn; vnc_zlog_debug_verbose("%s: entry, afi=%d", __func__, afi); if (!bgp) return; if (afi != AFI_IP && afi != AFI_IP6) { vnc_zlog_debug_verbose("%s: bad afi: %d", __func__, afi); return; } /* * Go through the entire BGP unicast table and remove routes that * originated from us */ for (rn = bgp_table_top(bgp->rib[afi][SAFI_UNICAST]); rn; rn = bgp_route_next(rn)) { struct bgp_path_info *ri; struct bgp_path_info *next; for (ri = bgp_node_get_bgp_path_info(rn), next = NULL; ri; ri = next) { next = ri->next; if (ri->type == ZEBRA_ROUTE_VNC_DIRECT_RH && ri->sub_type == BGP_ROUTE_REDISTRIBUTE) { struct vnc_export_info *eti; /* * Delete routes immediately (no timer) */ eti = vnc_eti_checktimer( bgp, EXPORT_TYPE_BGP, &rn->p, ri->peer, ZEBRA_ROUTE_VNC_DIRECT_RH, BGP_ROUTE_REDISTRIBUTE); if (eti) { if (eti->timer) thread_cancel(eti->timer); vnc_eti_delete(eti); } bgp_withdraw(ri->peer, &rn->p, /* prefix */ 0, /* addpath_id */ NULL, /* ignored */ AFI_IP, SAFI_UNICAST, ZEBRA_ROUTE_VNC_DIRECT_RH, BGP_ROUTE_REDISTRIBUTE, NULL, /* RD not used for unicast */ NULL, 0, NULL); /* tag not used for unicast, EVPN neither */ } } } } void vnc_direct_bgp_rh_reexport(struct bgp *bgp, afi_t afi) { if (VNC_EXPORT_BGP_RH_ENABLED(bgp->rfapi_cfg)) { vnc_direct_bgp_rh_vpn_disable(bgp, afi); vnc_direct_bgp_rh_vpn_enable(bgp, afi); } } /*********************************************************************** * Generic Export methods ***********************************************************************/ /* * Assumes the correct mode bits are already turned on. Thus it * is OK to call this function from, e.g., bgp_redistribute_set() * without caring if export is enabled or not */ void vnc_export_bgp_enable(struct bgp *bgp, afi_t afi) { if (!bgp->rfapi_cfg) return; switch (bgp->rfapi_cfg->flags & BGP_VNC_CONFIG_EXPORT_BGP_MODE_BITS) { case BGP_VNC_CONFIG_EXPORT_BGP_MODE_NONE: break; case BGP_VNC_CONFIG_EXPORT_BGP_MODE_GRP: vnc_direct_bgp_vpn_enable(bgp, afi); break; case BGP_VNC_CONFIG_EXPORT_BGP_MODE_RH: vnc_direct_bgp_rh_vpn_enable(bgp, afi); break; case BGP_VNC_CONFIG_EXPORT_BGP_MODE_CE: vnc_direct_bgp_vpn_enable_ce(bgp, afi); break; } } void vnc_export_bgp_disable(struct bgp *bgp, afi_t afi) { if (!bgp->rfapi_cfg) return; switch (bgp->rfapi_cfg->flags & BGP_VNC_CONFIG_EXPORT_BGP_MODE_BITS) { case BGP_VNC_CONFIG_EXPORT_BGP_MODE_NONE: break; case BGP_VNC_CONFIG_EXPORT_BGP_MODE_GRP: vnc_direct_bgp_vpn_disable(bgp, afi); break; case BGP_VNC_CONFIG_EXPORT_BGP_MODE_RH: vnc_direct_bgp_rh_vpn_disable(bgp, afi); break; case BGP_VNC_CONFIG_EXPORT_BGP_MODE_CE: vnc_direct_bgp_vpn_disable_ce(bgp, afi); break; } } void vnc_export_bgp_prechange(struct bgp *bgp) { vnc_export_bgp_disable(bgp, AFI_IP); vnc_export_bgp_disable(bgp, AFI_IP6); } void vnc_export_bgp_postchange(struct bgp *bgp) { vnc_export_bgp_enable(bgp, AFI_IP); vnc_export_bgp_enable(bgp, AFI_IP6); } void vnc_direct_bgp_reexport(struct bgp *bgp, afi_t afi) { vnc_export_bgp_disable(bgp, afi); vnc_export_bgp_enable(bgp, afi); } frr-7.2.1/bgpd/rfapi/vnc_export_bgp.h0000644000000000000000000000247113610377563014425 00000000000000/* * * Copyright 2009-2016, LabN Consulting, L.L.C. * * * This program is free software; you can redistribute it and/or * modify it under the terms of the GNU General Public License * as published by the Free Software Foundation; either version 2 * of the License, or (at your option) any later version. * * 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; see the file COPYING; if not, write to the Free Software * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA */ #ifndef _QUAGGA_RFAPI_VNC_EXPORT_BGP_H_ #define _QUAGGA_RFAPI_VNC_EXPORT_BGP_H_ #include "lib/zebra.h" #include "lib/prefix.h" #include "bgpd/bgpd.h" #include "bgpd/bgp_route.h" extern void vnc_direct_bgp_rh_reexport(struct bgp *bgp, afi_t afi); extern void vnc_export_bgp_prechange(struct bgp *bgp); extern void vnc_export_bgp_postchange(struct bgp *bgp); extern void vnc_export_bgp_enable(struct bgp *bgp, afi_t afi); extern void vnc_export_bgp_disable(struct bgp *bgp, afi_t afi); #endif /* _QUAGGA_RFAPI_VNC_EXPORT_BGP_H_ */ frr-7.2.1/bgpd/rfapi/vnc_export_bgp_p.h0000644000000000000000000000454113610377563014744 00000000000000/* * * Copyright 2009-2016, LabN Consulting, L.L.C. * * * This program is free software; you can redistribute it and/or * modify it under the terms of the GNU General Public License * as published by the Free Software Foundation; either version 2 * of the License, or (at your option) any later version. * * 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; see the file COPYING; if not, write to the Free Software * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA */ #ifndef _QUAGGA_RFAPI_VNC_EXPORT_BGP_P_H_ #define _QUAGGA_RFAPI_VNC_EXPORT_BGP_P_H_ #include "lib/zebra.h" #include "lib/prefix.h" #include "bgpd/bgpd.h" #include "bgpd/bgp_route.h" #include "rfapi_private.h" extern void vnc_direct_bgp_add_route_ce(struct bgp *bgp, struct agg_node *rn, struct bgp_path_info *bpi); extern void vnc_direct_bgp_del_route_ce(struct bgp *bgp, struct agg_node *rn, struct bgp_path_info *bpi); extern void vnc_direct_bgp_add_prefix(struct bgp *bgp, struct rfapi_import_table *import_table, struct agg_node *rn); extern void vnc_direct_bgp_del_prefix(struct bgp *bgp, struct rfapi_import_table *import_table, struct agg_node *rn); extern void vnc_direct_bgp_add_nve(struct bgp *bgp, struct rfapi_descriptor *rfd); extern void vnc_direct_bgp_del_nve(struct bgp *bgp, struct rfapi_descriptor *rfd); extern void vnc_direct_bgp_add_group(struct bgp *bgp, struct rfapi_nve_group_cfg *rfg); extern void vnc_direct_bgp_del_group(struct bgp *bgp, struct rfapi_nve_group_cfg *rfg); extern void vnc_direct_bgp_reexport_group_afi(struct bgp *bgp, struct rfapi_nve_group_cfg *rfg, afi_t afi); extern void vnc_direct_bgp_rh_add_route(struct bgp *bgp, afi_t afi, struct prefix *prefix, struct peer *peer, struct attr *attr); extern void vnc_direct_bgp_rh_del_route(struct bgp *bgp, afi_t afi, struct prefix *prefix, struct peer *peer); extern void vnc_direct_bgp_reexport(struct bgp *bgp, afi_t afi); #endif /* _QUAGGA_RFAPI_VNC_EXPORT_BGP_P_H_ */ frr-7.2.1/bgpd/rfapi/vnc_export_table.c0000644000000000000000000001030113610377563014726 00000000000000/* * * Copyright 2009-2016, LabN Consulting, L.L.C. * * * This program is free software; you can redistribute it and/or * modify it under the terms of the GNU General Public License * as published by the Free Software Foundation; either version 2 * of the License, or (at your option) any later version. * * 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; see the file COPYING; if not, write to the Free Software * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA */ #include "lib/zebra.h" #include "lib/prefix.h" #include "lib/agg_table.h" #include "lib/memory.h" #include "lib/vty.h" #include "bgpd/bgpd.h" #include "bgpd/bgp_route.h" #include "bgpd/rfapi/vnc_export_table.h" #include "bgpd/rfapi/rfapi_private.h" #include "bgpd/rfapi/rfapi_import.h" #include "bgpd/rfapi/vnc_debug.h" struct agg_node *vnc_etn_get(struct bgp *bgp, vnc_export_type_t type, struct prefix *p) { struct agg_table *t = NULL; struct agg_node *rn = NULL; afi_t afi; if (!bgp || !bgp->rfapi) return NULL; afi = family2afi(p->family); assert(afi == AFI_IP || afi == AFI_IP6); switch (type) { case EXPORT_TYPE_BGP: if (!bgp->rfapi->rt_export_bgp[afi]) bgp->rfapi->rt_export_bgp[afi] = agg_table_init(); t = bgp->rfapi->rt_export_bgp[afi]; break; case EXPORT_TYPE_ZEBRA: if (!bgp->rfapi->rt_export_zebra[afi]) bgp->rfapi->rt_export_zebra[afi] = agg_table_init(); t = bgp->rfapi->rt_export_zebra[afi]; break; } if (t) rn = agg_node_get(t, p); return rn; } struct agg_node *vnc_etn_lookup(struct bgp *bgp, vnc_export_type_t type, struct prefix *p) { struct agg_table *t = NULL; struct agg_node *rn = NULL; afi_t afi; if (!bgp || !bgp->rfapi) return NULL; afi = family2afi(p->family); assert(afi == AFI_IP || afi == AFI_IP6); switch (type) { case EXPORT_TYPE_BGP: if (!bgp->rfapi->rt_export_bgp[afi]) bgp->rfapi->rt_export_bgp[afi] = agg_table_init(); t = bgp->rfapi->rt_export_bgp[afi]; break; case EXPORT_TYPE_ZEBRA: if (!bgp->rfapi->rt_export_zebra[afi]) bgp->rfapi->rt_export_zebra[afi] = agg_table_init(); t = bgp->rfapi->rt_export_zebra[afi]; break; } if (t) rn = agg_node_lookup(t, p); return rn; } struct vnc_export_info *vnc_eti_get(struct bgp *bgp, vnc_export_type_t etype, struct prefix *p, struct peer *peer, uint8_t type, uint8_t subtype) { struct agg_node *etn; struct vnc_export_info *eti; etn = vnc_etn_get(bgp, etype, p); assert(etn); for (eti = etn->info; eti; eti = eti->next) { if (peer == eti->peer && type == eti->type && subtype == eti->subtype) { break; } } if (eti) { agg_unlock_node(etn); } else { eti = XCALLOC(MTYPE_RFAPI_ETI, sizeof(struct vnc_export_info)); assert(eti); eti->node = etn; eti->peer = peer; peer_lock(peer); eti->type = type; eti->subtype = subtype; eti->next = etn->info; etn->info = eti; } return eti; } void vnc_eti_delete(struct vnc_export_info *goner) { struct agg_node *etn; struct vnc_export_info *eti; struct vnc_export_info *eti_prev = NULL; etn = goner->node; for (eti = etn->info; eti; eti_prev = eti, eti = eti->next) { if (eti == goner) break; } if (!eti) { vnc_zlog_debug_verbose("%s: COULDN'T FIND ETI", __func__); return; } if (eti_prev) { eti_prev->next = goner->next; } else { etn->info = goner->next; } peer_unlock(eti->peer); goner->node = NULL; XFREE(MTYPE_RFAPI_ETI, goner); agg_unlock_node(etn); } struct vnc_export_info *vnc_eti_checktimer(struct bgp *bgp, vnc_export_type_t etype, struct prefix *p, struct peer *peer, uint8_t type, uint8_t subtype) { struct agg_node *etn; struct vnc_export_info *eti; etn = vnc_etn_lookup(bgp, etype, p); if (!etn) return NULL; for (eti = etn->info; eti; eti = eti->next) { if (peer == eti->peer && type == eti->type && subtype == eti->subtype) { break; } } agg_unlock_node(etn); if (eti && eti->timer) return eti; return NULL; } frr-7.2.1/bgpd/rfapi/vnc_export_table.h0000644000000000000000000000366713610377563014754 00000000000000/* * * Copyright 2009-2016, LabN Consulting, L.L.C. * * * This program is free software; you can redistribute it and/or * modify it under the terms of the GNU General Public License * as published by the Free Software Foundation; either version 2 * of the License, or (at your option) any later version. * * 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; see the file COPYING; if not, write to the Free Software * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA */ #ifndef _QUAGGA_VNC_VNC_EXPORT_TABLE_H_ #define _QUAGGA_VNC_VNC_EXPORT_TABLE_H_ #include "lib/table.h" #include "lib/thread.h" #include "lib/vty.h" #include "bgpd/bgpd.h" #define VNC_EXPORT_TYPE_BGP 1 #define VNC_EXPORT_TYPE_ZEBRA 2 typedef enum vnc_export_type { EXPORT_TYPE_BGP, EXPORT_TYPE_ZEBRA } vnc_export_type_t; struct vnc_export_info { struct vnc_export_info *next; struct agg_node *node; struct peer *peer; uint8_t type; uint8_t subtype; uint32_t lifetime; struct thread *timer; }; extern struct agg_node *vnc_etn_get(struct bgp *bgp, vnc_export_type_t type, struct prefix *p); extern struct agg_node *vnc_etn_lookup(struct bgp *bgp, vnc_export_type_t type, struct prefix *p); extern struct vnc_export_info *vnc_eti_get(struct bgp *bgp, vnc_export_type_t etype, struct prefix *p, struct peer *peer, uint8_t type, uint8_t subtype); extern void vnc_eti_delete(struct vnc_export_info *goner); extern struct vnc_export_info * vnc_eti_checktimer(struct bgp *bgp, vnc_export_type_t etype, struct prefix *p, struct peer *peer, uint8_t type, uint8_t subtype); #endif /* _QUAGGA_VNC_VNC_EXPORT_TABLE_H_ */ frr-7.2.1/bgpd/rfapi/vnc_import_bgp.c0000644000000000000000000022435113610377563014414 00000000000000/* * * Copyright 2009-2016, LabN Consulting, L.L.C. * * * This program is free software; you can redistribute it and/or * modify it under the terms of the GNU General Public License * as published by the Free Software Foundation; either version 2 * of the License, or (at your option) any later version. * * 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; see the file COPYING; if not, write to the Free Software * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA */ /* * File: vnc_import_bgp.c * Purpose: Import routes from BGP unicast directly (not via zebra) */ #include "lib/zebra.h" #include "lib/prefix.h" #include "lib/agg_table.h" #include "lib/vty.h" #include "lib/log.h" #include "lib/memory.h" #include "lib/linklist.h" #include "lib/plist.h" #include "lib/routemap.h" #include "lib/lib_errors.h" #include "bgpd/bgpd.h" #include "bgpd/bgp_ecommunity.h" #include "bgpd/bgp_attr.h" #include "bgpd/bgp_route.h" #include "bgpd/bgp_mplsvpn.h" /* for RD_TYPE_IP */ #include "bgpd/rfapi/vnc_export_bgp.h" #include "bgpd/rfapi/bgp_rfapi_cfg.h" #include "bgpd/rfapi/rfapi.h" #include "bgpd/rfapi/rfapi_import.h" #include "bgpd/rfapi/rfapi_private.h" #include "bgpd/rfapi/rfapi_monitor.h" #include "bgpd/rfapi/rfapi_vty.h" #include "bgpd/rfapi/vnc_import_bgp.h" #include "bgpd/rfapi/vnc_import_bgp_p.h" #include "bgpd/rfapi/vnc_debug.h" #define ENABLE_VNC_RHNCK #define DEBUG_RHN_LIST 0 static struct rfapi_descriptor vncHDBgpDirect; /* dummy nve descriptor */ static struct rfapi_descriptor vncHDResolveNve; /* dummy nve descriptor */ /* * For routes from another AS: * * If MED is set, * LOCAL_PREF = 255 - MIN(255, MED) * else * LOCAL_PREF = default_local_pref * * For routes from the same AS: * * LOCAL_PREF unchanged */ uint32_t calc_local_pref(struct attr *attr, struct peer *peer) { uint32_t local_pref = 0; if (!attr) { if (peer) { return peer->bgp->default_local_pref; } return bgp_get_default()->default_local_pref; } if (peer && (peer->as != peer->bgp->as)) { if (attr->flag & ATTR_FLAG_BIT(BGP_ATTR_MULTI_EXIT_DISC)) { if (attr->med > 255) { local_pref = 0; } else { local_pref = 255 - attr->med; } } else { local_pref = peer->bgp->default_local_pref; } } else { if (attr->flag & ATTR_FLAG_BIT(BGP_ATTR_LOCAL_PREF)) { local_pref = attr->local_pref; } else { if (peer && peer->bgp) { local_pref = peer->bgp->default_local_pref; } } } return local_pref; } static int is_host_prefix(struct prefix *p) { switch (p->family) { case AF_INET: return (p->prefixlen == 32); case AF_INET6: return (p->prefixlen == 128); } return 0; } /*********************************************************************** * RHN list ***********************************************************************/ struct prefix_bag { struct prefix hpfx; /* ce address = unicast nexthop */ struct prefix upfx; /* unicast prefix */ struct bgp_path_info *ubpi; /* unicast route */ }; static const uint8_t maskbit[] = {0x00, 0x80, 0xc0, 0xe0, 0xf0, 0xf8, 0xfc, 0xfe, 0xff}; int vnc_prefix_cmp(void *pfx1, void *pfx2) { int offset; int shift; uint8_t mask; struct prefix *p1 = pfx1; struct prefix *p2 = pfx2; if (p1->family < p2->family) return -1; if (p1->family > p2->family) return 1; if (p1->prefixlen < p2->prefixlen) return -1; if (p1->prefixlen > p2->prefixlen) return 1; offset = p1->prefixlen / 8; shift = p1->prefixlen % 8; if (shift == 0 && offset) { /* catch aligned case */ offset--; shift = 8; } /* Set both prefix's head pointer. */ const uint8_t *pp1 = (const uint8_t *)&p1->u.prefix; const uint8_t *pp2 = (const uint8_t *)&p2->u.prefix; while (offset--) { if (*pp1 < *pp2) return -1; if (*pp1 > *pp2) return 1; ++pp1; ++pp2; } mask = maskbit[shift]; if ((*pp1 & mask) < (*pp2 & mask)) return -1; if ((*pp1 & mask) > (*pp2 & mask)) return 1; return 0; } static void prefix_bag_free(void *pb) { XFREE(MTYPE_RFAPI_PREFIX_BAG, pb); } #if DEBUG_RHN_LIST static void print_rhn_list(const char *tag1, const char *tag2) { struct bgp *bgp; struct skiplist *sl; struct skiplistnode *p; struct prefix_bag *pb; int count = 0; bgp = bgp_get_default(); if (!bgp) return; sl = bgp->frapi->resolve_nve_nexthop; if (!sl) { vnc_zlog_debug_verbose("%s: %s: RHN List is empty", (tag1 ? tag1 : ""), (tag2 ? tag2 : "")); return; } vnc_zlog_debug_verbose("%s: %s: RHN list:", (tag1 ? tag1 : ""), (tag2 ? tag2 : "")); /* XXX uses secret knowledge of skiplist structure */ for (p = sl->header->forward[0]; p; p = p->forward[0]) { char kbuf[PREFIX_STRLEN]; char hbuf[PREFIX_STRLEN]; char ubuf[PREFIX_STRLEN]; pb = p->value; prefix2str(p->key, kbuf, sizeof(kbuf)); prefix2str(&pb->hpfx, hbuf, sizeof(hbuf)); prefix2str(&pb->upfx, ubuf, sizeof(ubuf)); vnc_zlog_debug_verbose( "RHN Entry %d (q=%p): kpfx=%s, upfx=%s, hpfx=%s, ubpi=%p", ++count, p, kbuf, ubuf, hbuf, pb->ubpi); } } #endif #ifdef ENABLE_VNC_RHNCK static void vnc_rhnck(char *tag) { struct bgp *bgp; struct skiplist *sl; struct skiplistnode *p; bgp = bgp_get_default(); if (!bgp) return; sl = bgp->rfapi->resolve_nve_nexthop; if (!sl) return; /* XXX uses secret knowledge of skiplist structure */ for (p = sl->header->forward[0]; p; p = p->forward[0]) { struct prefix_bag *pb; struct prefix *pkey; afi_t afi; struct prefix pfx_orig_nexthop; memset(&pfx_orig_nexthop, 0, sizeof(struct prefix)); /* keep valgrind happy */ pkey = p->key; pb = p->value; afi = family2afi(pb->upfx.family); rfapiUnicastNexthop2Prefix(afi, pb->ubpi->attr, &pfx_orig_nexthop); /* pb->hpfx, pb->ubpi nexthop, pkey should all reflect the same * pfx */ assert(!vnc_prefix_cmp(&pb->hpfx, pkey)); if (vnc_prefix_cmp(&pb->hpfx, &pfx_orig_nexthop)) { char str_onh[PREFIX_STRLEN]; char str_nve_pfx[PREFIX_STRLEN]; prefix2str(&pfx_orig_nexthop, str_onh, sizeof(str_onh)); prefix2str(&pb->hpfx, str_nve_pfx, sizeof(str_nve_pfx)); vnc_zlog_debug_verbose( "%s: %s: FATAL: resolve_nve_nexthop list item bpi nexthop %s != nve pfx %s", __func__, tag, str_onh, str_nve_pfx); assert(0); } } vnc_zlog_debug_verbose("%s: vnc_rhnck OK", tag); } #define VNC_RHNCK(n) do {char buf[BUFSIZ];sprintf(buf,"%s: %s", __func__, #n);vnc_rhnck(buf);} while (0) #else #define VNC_RHNCK(n) #endif /*********************************************************************** * Add/Delete Unicast Route ***********************************************************************/ /* * "Adding a Route" import process */ /* * extract and package information from the BGP unicast route. * Return code 0 means OK, non-0 means drop. * * If return code is 0, caller MUST release ecom */ static int process_unicast_route(struct bgp *bgp, /* in */ afi_t afi, /* in */ struct prefix *prefix, /* in */ struct bgp_path_info *info, /* in */ struct ecommunity **ecom, /* OUT */ struct prefix *unicast_nexthop) /* OUT */ { struct rfapi_cfg *hc = bgp->rfapi_cfg; struct peer *peer = info->peer; struct attr *attr = info->attr; struct attr hattr; struct route_map *rmap = NULL; struct prefix pfx_orig_nexthop; memset(&pfx_orig_nexthop, 0, sizeof(struct prefix)); /* keep valgrind happy */ /* * prefix list check */ if (hc->plist_redist[ZEBRA_ROUTE_BGP_DIRECT][afi]) { vnc_zlog_debug_verbose("%s: HC prefix list is set, checking", __func__); if (prefix_list_apply( hc->plist_redist[ZEBRA_ROUTE_BGP_DIRECT][afi], prefix) == PREFIX_DENY) { vnc_zlog_debug_verbose( "%s: prefix list returns DENY, blocking route", __func__); return -1; } vnc_zlog_debug_verbose( "%s: prefix list returns PASS, allowing route", __func__); } /* apply routemap, if any, later */ rmap = hc->routemap_redist[ZEBRA_ROUTE_BGP_DIRECT]; /* * Extract original nexthop, which we expect to be a NVE connected * router * Note that this is the nexthop before any possible application of * policy */ /* * Incoming prefix is unicast. If v6, it is in multiprotocol area, * but if v4 it is in attr->nexthop */ rfapiUnicastNexthop2Prefix(afi, attr, &pfx_orig_nexthop); /* * route map handling * This code is here because it allocates an interned attr which * must be freed before we return. It's easier to put it after * all of the possible returns above. */ memset(&hattr, 0, sizeof(struct attr)); /* hattr becomes a ghost attr */ hattr = *attr; if (rmap) { struct bgp_path_info info; route_map_result_t ret; memset(&info, 0, sizeof(info)); info.peer = peer; info.attr = &hattr; ret = route_map_apply(rmap, prefix, RMAP_BGP, &info); if (ret == RMAP_DENYMATCH) { bgp_attr_flush(&hattr); vnc_zlog_debug_verbose( "%s: route map \"%s\" says DENY, returning", __func__, rmap->name); return -1; } } /* * Get the (possibly altered by policy) unicast nexthop * for later lookup in the Import Table by caller */ rfapiUnicastNexthop2Prefix(afi, &hattr, unicast_nexthop); if (hattr.ecommunity) *ecom = ecommunity_dup(hattr.ecommunity); else *ecom = ecommunity_new(); /* * Done with hattr, clean up */ bgp_attr_flush(&hattr); /* * Add EC that carries original NH of iBGP route (2 bytes = magic * value indicating it came from an VNC gateway; default 5226, but * must be user configurable). Note that this is the nexthop before * any application of policy. */ { struct ecommunity_val vnc_gateway_magic; uint16_t localadmin; /* Using route origin extended community type */ memset(&vnc_gateway_magic, 0, sizeof(vnc_gateway_magic)); vnc_gateway_magic.val[0] = 0x01; vnc_gateway_magic.val[1] = 0x03; /* Only works for IPv4 nexthops */ if (prefix->family == AF_INET) { memcpy(vnc_gateway_magic.val + 2, &unicast_nexthop->u.prefix4, 4); } localadmin = htons(hc->resolve_nve_roo_local_admin); memcpy(vnc_gateway_magic.val + 6, (char *)&localadmin, 2); ecommunity_add_val(*ecom, &vnc_gateway_magic); } return 0; } static void vnc_import_bgp_add_route_mode_resolve_nve_one_bi( struct bgp *bgp, afi_t afi, struct bgp_path_info *bpi, /* VPN bpi */ struct prefix_rd *prd, /* RD */ struct prefix *prefix, /* unicast route prefix */ uint32_t *local_pref, /* NULL = no local_pref */ uint32_t *med, /* NULL = no med */ struct ecommunity *ecom) /* generated ecoms */ { struct prefix un; struct prefix nexthop; struct rfapi_ip_addr nexthop_h; uint32_t lifetime; uint32_t *plifetime; struct bgp_attr_encap_subtlv *encaptlvs; uint32_t label = 0; struct rfapi_un_option optary[3]; struct rfapi_un_option *opt = NULL; int cur_opt = 0; vnc_zlog_debug_verbose("%s: entry", __func__); if (bpi->type != ZEBRA_ROUTE_BGP && bpi->type != ZEBRA_ROUTE_BGP_DIRECT) { return; } if (bpi->sub_type != BGP_ROUTE_NORMAL && bpi->sub_type != BGP_ROUTE_STATIC && bpi->sub_type != BGP_ROUTE_RFP) { return; } if (CHECK_FLAG(bpi->flags, BGP_PATH_REMOVED)) return; vncHDResolveNve.peer = bpi->peer; if (!rfapiGetVncTunnelUnAddr(bpi->attr, &un)) { if (rfapiQprefix2Raddr(&un, &vncHDResolveNve.un_addr)) return; } else { memset(&vncHDResolveNve.un_addr, 0, sizeof(vncHDResolveNve.un_addr)); } /* Use nexthop of VPN route as nexthop of constructed route */ rfapiNexthop2Prefix(bpi->attr, &nexthop); rfapiQprefix2Raddr(&nexthop, &nexthop_h); if (rfapiGetVncLifetime(bpi->attr, &lifetime)) { plifetime = NULL; } else { plifetime = &lifetime; } if (bpi->attr) { encaptlvs = bpi->attr->vnc_subtlvs; if (bpi->attr->encap_tunneltype != BGP_ENCAP_TYPE_RESERVED && bpi->attr->encap_tunneltype != BGP_ENCAP_TYPE_MPLS) { if (opt != NULL) opt->next = &optary[cur_opt]; opt = &optary[cur_opt++]; memset(opt, 0, sizeof(struct rfapi_un_option)); opt->type = RFAPI_UN_OPTION_TYPE_TUNNELTYPE; opt->v.tunnel.type = bpi->attr->encap_tunneltype; /* TBD parse bpi->attr->extra->encap_subtlvs */ } } else { encaptlvs = NULL; } struct ecommunity *new_ecom = ecommunity_dup(ecom); if (bpi->attr && bpi->attr->ecommunity) ecommunity_merge(new_ecom, bpi->attr->ecommunity); if (bpi->extra) label = decode_label(&bpi->extra->label[0]); add_vnc_route(&vncHDResolveNve, bgp, SAFI_MPLS_VPN, prefix, /* unicast route prefix */ prd, &nexthop_h, /* new nexthop */ local_pref, plifetime, (struct bgp_tea_options *)encaptlvs, /* RFP options */ opt, NULL, new_ecom, med, /* NULL => don't set med */ (label ? &label : NULL), /* NULL= default */ ZEBRA_ROUTE_BGP_DIRECT, BGP_ROUTE_REDISTRIBUTE, RFAPI_AHR_RFPOPT_IS_VNCTLV); /* flags */ ecommunity_free(&new_ecom); } static void vnc_import_bgp_add_route_mode_resolve_nve_one_rd( struct prefix_rd *prd, /* RD */ struct bgp_table *table_rd, /* per-rd VPN route table */ afi_t afi, struct bgp *bgp, struct prefix *prefix, /* unicast prefix */ struct ecommunity *ecom, /* generated ecoms */ uint32_t *local_pref, /* NULL = no local_pref */ uint32_t *med, /* NULL = no med */ struct prefix *ubpi_nexthop) /* unicast nexthop */ { struct bgp_node *bn; struct bgp_path_info *bpi; if (!table_rd) return; { char str_nh[PREFIX_STRLEN]; prefix2str(ubpi_nexthop, str_nh, sizeof(str_nh)); vnc_zlog_debug_verbose("%s: ubpi_nexthop=%s", __func__, str_nh); } /* exact match */ bn = bgp_node_lookup(table_rd, ubpi_nexthop); if (!bn) { vnc_zlog_debug_verbose( "%s: no match in RD's table for ubpi_nexthop", __func__); return; } /* Iterate over bgp_info items at this node */ for (bpi = bgp_node_get_bgp_path_info(bn); bpi; bpi = bpi->next) { vnc_import_bgp_add_route_mode_resolve_nve_one_bi( bgp, afi, bpi, /* VPN bpi */ prd, prefix, local_pref, med, ecom); } bgp_unlock_node(bn); } static void vnc_import_bgp_add_route_mode_resolve_nve( struct bgp *bgp, struct prefix *prefix, /* unicast prefix */ struct bgp_path_info *info) /* unicast info */ { afi_t afi = family2afi(prefix->family); struct prefix pfx_unicast_nexthop = {0}; /* happy valgrind */ struct ecommunity *ecom = NULL; uint32_t local_pref; uint32_t *med = NULL; struct prefix_bag *pb; struct bgp_node *bnp; /* prd table node */ /*debugging */ if (VNC_DEBUG(VERBOSE)) { char str_pfx[PREFIX_STRLEN]; char str_nh[PREFIX_STRLEN]; struct prefix nh; prefix2str(prefix, str_pfx, sizeof(str_pfx)); nh.prefixlen = 0; rfapiUnicastNexthop2Prefix(afi, info->attr, &nh); if (nh.prefixlen) { prefix2str(&nh, str_nh, sizeof(str_nh)); } else { str_nh[0] = '?'; str_nh[1] = 0; } vnc_zlog_debug_verbose( "%s(bgp=%p, unicast prefix=%s, unicast nh=%s)", __func__, bgp, str_pfx, str_nh); } if (info->type != ZEBRA_ROUTE_BGP) { vnc_zlog_debug_verbose( "%s: unicast type %d=\"%s\" is not %d=%s, skipping", __func__, info->type, zebra_route_string(info->type), ZEBRA_ROUTE_BGP, "ZEBRA_ROUTE_BGP"); return; } /* * Preliminary checks */ if (!afi) { flog_err(EC_LIB_DEVELOPMENT, "%s: can't get afi of prefix", __func__); return; } if (!(bgp->rfapi_cfg)) { vnc_zlog_debug_verbose("%s: bgp->rfapi_cfg is NULL, skipping", __func__); return; } /* check vnc redist flag for bgp direct routes */ if (!bgp->rfapi_cfg->redist[afi][ZEBRA_ROUTE_BGP_DIRECT]) { vnc_zlog_debug_verbose( "%s: bgp->rfapi_cfg->redist[afi=%d][type=ZEBRA_ROUTE_BGP_DIRECT] is 0, skipping", __func__, afi); return; } if (process_unicast_route(bgp, afi, prefix, info, &ecom, &pfx_unicast_nexthop)) { vnc_zlog_debug_verbose( "%s: process_unicast_route error, skipping", __func__); return; } local_pref = calc_local_pref(info->attr, info->peer); if (info->attr && (info->attr->flag & ATTR_FLAG_BIT(BGP_ATTR_MULTI_EXIT_DISC))) { med = &info->attr->med; } /* * At this point, we have allocated: * * ecom ecommunity ptr, union of unicast and ROO parts (no NVE part) * * And we have set: * * pfx_unicast_nexthop nexthop of uncast route */ if (!bgp->rfapi->resolve_nve_nexthop) { bgp->rfapi->resolve_nve_nexthop = skiplist_new(SKIPLIST_FLAG_ALLOW_DUPLICATES, vnc_prefix_cmp, prefix_bag_free); } pb = XCALLOC(MTYPE_RFAPI_PREFIX_BAG, sizeof(struct prefix_bag)); pb->hpfx = pfx_unicast_nexthop; pb->ubpi = info; pb->upfx = *prefix; bgp_path_info_lock(info); /* skiplist refers to it */ skiplist_insert(bgp->rfapi->resolve_nve_nexthop, &pb->hpfx, pb); /* * Iterate over RDs in VPN RIB. For each RD, look up unicast nexthop * (exact match, /32). If an exact match is found, call add_vnc_route. */ for (bnp = bgp_table_top(bgp->rib[afi][SAFI_MPLS_VPN]); bnp; bnp = bgp_route_next(bnp)) { struct bgp_table *table; table = bgp_node_get_bgp_table_info(bnp); if (!table) continue; vnc_import_bgp_add_route_mode_resolve_nve_one_rd( (struct prefix_rd *)&bnp->p, table, afi, bgp, prefix, ecom, &local_pref, med, &pfx_unicast_nexthop); } if (ecom) ecommunity_free(&ecom); vnc_zlog_debug_verbose("%s: done", __func__); } static void vnc_import_bgp_add_route_mode_plain(struct bgp *bgp, struct prefix *prefix, struct bgp_path_info *info) { afi_t afi = family2afi(prefix->family); struct peer *peer = info->peer; struct attr *attr = info->attr; struct attr hattr; struct rfapi_cfg *hc = bgp->rfapi_cfg; struct attr *iattr = NULL; struct rfapi_ip_addr vnaddr; struct prefix vn_pfx_space; struct prefix *vn_pfx = NULL; int ahr_flags = 0; struct ecommunity *ecom = NULL; struct prefix_rd prd; struct route_map *rmap = NULL; uint32_t local_pref; uint32_t *med = NULL; { char buf[PREFIX_STRLEN]; prefix2str(prefix, buf, sizeof(buf)); vnc_zlog_debug_verbose("%s(prefix=%s) entry", __func__, buf); } if (!afi) { flog_err(EC_LIB_DEVELOPMENT, "%s: can't get afi of prefix", __func__); return; } if (!hc) { vnc_zlog_debug_verbose("%s: bgp->rfapi_cfg is NULL, skipping", __func__); return; } /* check vnc redist flag for bgp direct routes */ if (!bgp->rfapi_cfg->redist[afi][ZEBRA_ROUTE_BGP_DIRECT]) { vnc_zlog_debug_verbose( "%s: bgp->rfapi_cfg->redist[afi=%d][type=ZEBRA_ROUTE_BGP_DIRECT] is 0, skipping", __func__, afi); return; } /* * mode "plain" specific code */ { vnc_zlog_debug_verbose("%s: NOT using redist RFG", __func__); /* * prefix list check */ if (hc->plist_redist[ZEBRA_ROUTE_BGP_DIRECT][afi]) { vnc_zlog_debug_verbose( "%s: HC prefix list is set, checking", __func__); if (prefix_list_apply( hc->plist_redist[ZEBRA_ROUTE_BGP_DIRECT] [afi], prefix) == PREFIX_DENY) { vnc_zlog_debug_verbose( "%s: prefix list returns DENY, blocking route", __func__); return; } vnc_zlog_debug_verbose( "%s: prefix list returns PASS, allowing route", __func__); } /* apply routemap, if any, later */ rmap = hc->routemap_redist[ZEBRA_ROUTE_BGP_DIRECT]; /* * Incoming prefix is unicast. If v6, it is in multiprotocol * area, * but if v4 it is in attr->nexthop */ rfapiUnicastNexthop2Prefix(afi, attr, &vn_pfx_space); vn_pfx = &vn_pfx_space; /* UN address */ ahr_flags |= RFAPI_AHR_NO_TUNNEL_SUBTLV; } if (VNC_DEBUG(IMPORT_BGP_ADD_ROUTE)) { char buf[PREFIX_STRLEN]; prefix2str(vn_pfx, buf, sizeof(buf)); vnc_zlog_debug_any("%s vn_pfx=%s", __func__, buf); } /* * Compute VN address */ if (rfapiQprefix2Raddr(vn_pfx, &vnaddr)) { vnc_zlog_debug_verbose("%s: redist VN invalid, skipping", __func__); return; } /* * route map handling * This code is here because it allocates an interned attr which * must be freed before we return. It's easier to put it after * all of the possible returns above. */ memset(&hattr, 0, sizeof(struct attr)); /* hattr becomes a ghost attr */ hattr = *attr; if (rmap) { struct bgp_path_info info; route_map_result_t ret; memset(&info, 0, sizeof(info)); info.peer = peer; info.attr = &hattr; ret = route_map_apply(rmap, prefix, RMAP_BGP, &info); if (ret == RMAP_DENYMATCH) { bgp_attr_flush(&hattr); vnc_zlog_debug_verbose( "%s: route map \"%s\" says DENY, returning", __func__, rmap->name); return; } } iattr = bgp_attr_intern(&hattr); bgp_attr_flush(&hattr); /* Now iattr is an allocated interned attr */ /* * Mode "plain" specific code * * Sets RD in dummy HD * Allocates ecom */ { if (vnaddr.addr_family != AF_INET) { vnc_zlog_debug_verbose( "%s: can't auto-assign RD, VN AF (%d) is not IPv4, skipping", __func__, vnaddr.addr_family); if (iattr) { bgp_attr_unintern(&iattr); } return; } memset(&prd, 0, sizeof(prd)); rfapi_set_autord_from_vn(&prd, &vnaddr); if (iattr && iattr->ecommunity) ecom = ecommunity_dup(iattr->ecommunity); } local_pref = calc_local_pref(iattr, peer); if (iattr && (iattr->flag & ATTR_FLAG_BIT(BGP_ATTR_MULTI_EXIT_DISC))) { med = &iattr->med; } if (VNC_DEBUG(IMPORT_BGP_ADD_ROUTE)) { char buf[PREFIX_STRLEN]; rfapiRfapiIpAddr2Str(&vnaddr, buf, sizeof(buf)); vnc_zlog_debug_any("%s: setting vnaddr to %s", __func__, buf); } vncHDBgpDirect.peer = peer; add_vnc_route(&vncHDBgpDirect, bgp, SAFI_MPLS_VPN, prefix, &prd, &vnaddr, &local_pref, &(bgp->rfapi_cfg->redist_lifetime), NULL, /* RFP options */ NULL, NULL, ecom, med, /* med */ NULL, /* label: default */ ZEBRA_ROUTE_BGP_DIRECT, BGP_ROUTE_REDISTRIBUTE, ahr_flags); vncHDBgpDirect.peer = NULL; if (ecom) ecommunity_free(&ecom); } static void vnc_import_bgp_add_route_mode_nvegroup(struct bgp *bgp, struct prefix *prefix, struct bgp_path_info *info, struct rfapi_nve_group_cfg *rfg) { afi_t afi = family2afi(prefix->family); struct peer *peer = info->peer; struct attr *attr = info->attr; struct attr hattr; struct attr *iattr = NULL; struct rfapi_ip_addr vnaddr; struct prefix *vn_pfx = NULL; int ahr_flags = 0; struct ecommunity *ecom = NULL; struct prefix_rd prd; struct route_map *rmap = NULL; uint32_t local_pref; { char buf[PREFIX_STRLEN]; prefix2str(prefix, buf, sizeof(buf)); vnc_zlog_debug_verbose("%s(prefix=%s) entry", __func__, buf); } assert(rfg); if (!afi) { flog_err(EC_LIB_DEVELOPMENT, "%s: can't get afi of prefix", __func__); return; } if (!(bgp->rfapi_cfg)) { vnc_zlog_debug_verbose("%s: bgp->rfapi_cfg is NULL, skipping", __func__); return; } /* check vnc redist flag for bgp direct routes */ if (!bgp->rfapi_cfg->redist[afi][ZEBRA_ROUTE_BGP_DIRECT]) { vnc_zlog_debug_verbose( "%s: bgp->rfapi_cfg->redist[afi=%d][type=ZEBRA_ROUTE_BGP_DIRECT] is 0, skipping", __func__, afi); return; } /* * RFG-specific code */ { struct rfapi_ip_prefix pfx_un; vnc_zlog_debug_verbose("%s: using redist RFG", __func__); /* * RFG prefix list check */ if (rfg->plist_redist[ZEBRA_ROUTE_BGP_DIRECT][afi]) { vnc_zlog_debug_verbose( "%s: RFG prefix list is set, checking", __func__); if (prefix_list_apply( rfg->plist_redist[ZEBRA_ROUTE_BGP_DIRECT] [afi], prefix) == PREFIX_DENY) { vnc_zlog_debug_verbose( "%s: prefix list returns DENY, blocking route", __func__); return; } vnc_zlog_debug_verbose( "%s: prefix list returns PASS, allowing route", __func__); } /* apply routemap, if any, later */ rmap = rfg->routemap_redist[ZEBRA_ROUTE_BGP_DIRECT]; /* * export nve group's VN addr prefix must be a /32 which * will yield the VN addr to use */ vn_pfx = &rfg->vn_prefix; /* * UN Address */ if (!is_host_prefix(&rfg->un_prefix)) { /* NB prefixlen==0 means it has not been configured */ vnc_zlog_debug_verbose( "%s: redist RFG UN pfx not host pfx (plen=%d), skipping", __func__, rfg->un_prefix.prefixlen); return; } rfapiQprefix2Rprefix(&rfg->un_prefix, &pfx_un); vncHDBgpDirect.un_addr = pfx_un.prefix; } if (VNC_DEBUG(IMPORT_BGP_ADD_ROUTE)) { char buf[PREFIX_STRLEN]; prefix2str(vn_pfx, buf, sizeof(buf)); vnc_zlog_debug_any("%s vn_pfx=%s", __func__, buf); } /* * Compute VN address */ if (rfapiQprefix2Raddr(vn_pfx, &vnaddr)) { vnc_zlog_debug_verbose("%s: redist VN invalid, skipping", __func__); return; } /* * route map handling * This code is here because it allocates an interned attr which * must be freed before we return. It's easier to put it after * all of the possible returns above. */ memset(&hattr, 0, sizeof(struct attr)); /* hattr becomes a ghost attr */ hattr = *attr; if (rmap) { struct bgp_path_info path; route_map_result_t ret; memset(&path, 0, sizeof(path)); path.peer = peer; path.attr = &hattr; ret = route_map_apply(rmap, prefix, RMAP_BGP, &path); if (ret == RMAP_DENYMATCH) { bgp_attr_flush(&hattr); vnc_zlog_debug_verbose( "%s: route map \"%s\" says DENY, returning", __func__, rmap->name); return; } } iattr = bgp_attr_intern(&hattr); bgp_attr_flush(&hattr); /* Now iattr is an allocated interned attr */ /* * RFG-specific code * * Sets RD in dummy HD * Allocates ecom */ { memset(&prd, 0, sizeof(prd)); prd = rfg->rd; prd.family = AF_UNSPEC; prd.prefixlen = 64; if (rfg->rd.family == AF_UNIX) { rfapi_set_autord_from_vn(&prd, &vnaddr); } if (rfg->rt_export_list) ecom = ecommunity_dup( bgp->rfapi_cfg->rfg_redist->rt_export_list); else ecom = ecommunity_new(); if (iattr && iattr->ecommunity) ecom = ecommunity_merge(ecom, iattr->ecommunity); } local_pref = calc_local_pref(iattr, peer); if (VNC_DEBUG(IMPORT_BGP_ADD_ROUTE)) { char buf[BUFSIZ]; buf[0] = 0; rfapiRfapiIpAddr2Str(&vnaddr, buf, BUFSIZ); buf[BUFSIZ - 1] = 0; vnc_zlog_debug_any("%s: setting vnaddr to %s", __func__, buf); } vncHDBgpDirect.peer = peer; add_vnc_route(&vncHDBgpDirect, bgp, SAFI_MPLS_VPN, prefix, &prd, &vnaddr, &local_pref, &(bgp->rfapi_cfg->redist_lifetime), NULL, /* RFP options */ NULL, NULL, ecom, NULL, /* med */ NULL, /* label: default */ ZEBRA_ROUTE_BGP_DIRECT, BGP_ROUTE_REDISTRIBUTE, ahr_flags); vncHDBgpDirect.peer = NULL; if (ecom) ecommunity_free(&ecom); } static void vnc_import_bgp_del_route_mode_plain(struct bgp *bgp, struct prefix *prefix, struct bgp_path_info *info) { struct prefix_rd prd; afi_t afi = family2afi(prefix->family); struct prefix *vn_pfx = NULL; struct rfapi_ip_addr vnaddr; struct prefix vn_pfx_space; assert(afi); /* * Compute VN address */ if (info && info->attr) { rfapiUnicastNexthop2Prefix(afi, info->attr, &vn_pfx_space); } else { vnc_zlog_debug_verbose("%s: no attr, can't delete route", __func__); return; } vn_pfx = &vn_pfx_space; vnaddr.addr_family = vn_pfx->family; switch (vn_pfx->family) { case AF_INET: if (vn_pfx->prefixlen != 32) { vnc_zlog_debug_verbose( "%s: redist VN plen (%d) != 32, skipping", __func__, vn_pfx->prefixlen); return; } vnaddr.addr.v4 = vn_pfx->u.prefix4; break; case AF_INET6: if (vn_pfx->prefixlen != 128) { vnc_zlog_debug_verbose( "%s: redist VN plen (%d) != 128, skipping", __func__, vn_pfx->prefixlen); return; } vnaddr.addr.v6 = vn_pfx->u.prefix6; break; default: vnc_zlog_debug_verbose( "%s: no redist RFG VN host pfx configured, skipping", __func__); return; } memset(&prd, 0, sizeof(prd)); if (rfapi_set_autord_from_vn(&prd, &vnaddr)) { vnc_zlog_debug_verbose("%s: can't auto-assign RD, skipping", __func__); return; } vncHDBgpDirect.peer = info->peer; vnc_zlog_debug_verbose("%s: setting peer to %p", __func__, vncHDBgpDirect.peer); del_vnc_route(&vncHDBgpDirect, info->peer, bgp, SAFI_MPLS_VPN, prefix, &prd, ZEBRA_ROUTE_BGP_DIRECT, BGP_ROUTE_REDISTRIBUTE, NULL, 1); vncHDBgpDirect.peer = NULL; } static void vnc_import_bgp_del_route_mode_nvegroup(struct bgp *bgp, struct prefix *prefix, struct bgp_path_info *info) { struct prefix_rd prd; afi_t afi = family2afi(prefix->family); struct rfapi_nve_group_cfg *rfg = NULL; struct prefix *vn_pfx = NULL; struct rfapi_ip_addr vnaddr; assert(afi); rfg = bgp->rfapi_cfg->rfg_redist; assert(rfg); /* * Compute VN address */ /* * export nve group's VN addr prefix must be a /32 which * will yield the VN addr to use */ vn_pfx = &rfg->vn_prefix; vnaddr.addr_family = vn_pfx->family; switch (vn_pfx->family) { case AF_INET: if (vn_pfx->prefixlen != 32) { vnc_zlog_debug_verbose( "%s: redist VN plen (%d) != 32, skipping", __func__, vn_pfx->prefixlen); return; } vnaddr.addr.v4 = vn_pfx->u.prefix4; break; case AF_INET6: if (vn_pfx->prefixlen != 128) { vnc_zlog_debug_verbose( "%s: redist VN plen (%d) != 128, skipping", __func__, vn_pfx->prefixlen); return; } vnaddr.addr.v6 = vn_pfx->u.prefix6; break; default: vnc_zlog_debug_verbose( "%s: no redist RFG VN host pfx configured, skipping", __func__); return; } memset(&prd, 0, sizeof(prd)); prd = rfg->rd; prd.family = AF_UNSPEC; prd.prefixlen = 64; if (rfg->rd.family == AF_UNIX) { /* means "auto" with VN addr */ if (rfapi_set_autord_from_vn(&prd, &vnaddr)) { vnc_zlog_debug_verbose( "%s: can't auto-assign RD, skipping", __func__); return; } } vncHDBgpDirect.peer = info->peer; vnc_zlog_debug_verbose("%s: setting peer to %p", __func__, vncHDBgpDirect.peer); del_vnc_route(&vncHDBgpDirect, info->peer, bgp, SAFI_MPLS_VPN, prefix, &prd, ZEBRA_ROUTE_BGP_DIRECT, BGP_ROUTE_REDISTRIBUTE, NULL, 1); vncHDBgpDirect.peer = NULL; } static void vnc_import_bgp_del_route_mode_resolve_nve_one_bi( struct bgp *bgp, afi_t afi, struct bgp_path_info *bpi, /* VPN bpi */ struct prefix_rd *prd, /* RD */ struct prefix *prefix) /* unicast route prefix */ { struct prefix un; if (bpi->type != ZEBRA_ROUTE_BGP && bpi->type != ZEBRA_ROUTE_BGP_DIRECT) { return; } if (bpi->sub_type != BGP_ROUTE_NORMAL && bpi->sub_type != BGP_ROUTE_STATIC && bpi->sub_type != BGP_ROUTE_RFP) { return; } if (CHECK_FLAG(bpi->flags, BGP_PATH_REMOVED)) return; vncHDResolveNve.peer = bpi->peer; if (!rfapiGetVncTunnelUnAddr(bpi->attr, &un)) { if (rfapiQprefix2Raddr(&un, &vncHDResolveNve.un_addr)) return; } else { memset(&vncHDResolveNve.un_addr, 0, sizeof(vncHDResolveNve.un_addr)); } del_vnc_route(&vncHDResolveNve, vncHDResolveNve.peer, bgp, SAFI_MPLS_VPN, prefix, /* unicast route prefix */ prd, ZEBRA_ROUTE_BGP_DIRECT, BGP_ROUTE_REDISTRIBUTE, NULL, 0); /* flags */ } static void vnc_import_bgp_del_route_mode_resolve_nve_one_rd( struct prefix_rd *prd, struct bgp_table *table_rd, /* per-rd VPN route table */ afi_t afi, struct bgp *bgp, struct prefix *prefix, /* unicast prefix */ struct prefix *ubpi_nexthop) /* unicast bpi's nexthop */ { struct bgp_node *bn; struct bgp_path_info *bpi; if (!table_rd) return; { char str_nh[PREFIX_STRLEN]; prefix2str(ubpi_nexthop, str_nh, sizeof(str_nh)); vnc_zlog_debug_verbose("%s: ubpi_nexthop=%s", __func__, str_nh); } /* exact match */ bn = bgp_node_lookup(table_rd, ubpi_nexthop); if (!bn) { vnc_zlog_debug_verbose( "%s: no match in RD's table for ubpi_nexthop", __func__); return; } /* Iterate over bgp_info items at this node */ for (bpi = bgp_node_get_bgp_path_info(bn); bpi; bpi = bpi->next) { vnc_import_bgp_del_route_mode_resolve_nve_one_bi( bgp, afi, bpi, /* VPN bpi */ prd, /* VPN RD */ prefix); /* unicast route prefix */ } bgp_unlock_node(bn); } static void vnc_import_bgp_del_route_mode_resolve_nve(struct bgp *bgp, afi_t afi, struct prefix *prefix, struct bgp_path_info *info) { struct ecommunity *ecom = NULL; struct prefix pfx_unicast_nexthop = {0}; /* happy valgrind */ // struct listnode *hnode; // struct rfapi_descriptor *rfd; struct prefix_bag *pb; void *cursor; struct skiplist *sl = bgp->rfapi->resolve_nve_nexthop; int rc; struct bgp_node *bnp; /* prd table node */ if (!sl) { vnc_zlog_debug_verbose("%s: no RHN entries, skipping", __func__); return; } if (info->type != ZEBRA_ROUTE_BGP) { vnc_zlog_debug_verbose( "%s: unicast type %d=\"%s\" is not %d=%s, skipping", __func__, info->type, zebra_route_string(info->type), ZEBRA_ROUTE_BGP, "ZEBRA_ROUTE_BGP"); return; } if (process_unicast_route(bgp, afi, prefix, info, &ecom, &pfx_unicast_nexthop)) { vnc_zlog_debug_verbose( "%s: process_unicast_route error, skipping", __func__); return; } rc = skiplist_first_value(sl, &pfx_unicast_nexthop, (void *)&pb, &cursor); while (!rc) { if (pb->ubpi == info) { skiplist_delete(sl, &pfx_unicast_nexthop, pb); bgp_path_info_unlock(info); break; } rc = skiplist_next_value(sl, &pfx_unicast_nexthop, (void *)&pb, &cursor); } /* * Iterate over RDs in VPN RIB. For each RD, look up unicast nexthop * (exact match, /32). If an exact match is found, call add_vnc_route. */ for (bnp = bgp_table_top(bgp->rib[afi][SAFI_MPLS_VPN]); bnp; bnp = bgp_route_next(bnp)) { struct bgp_table *table; table = bgp_node_get_bgp_table_info(bnp); if (!table) continue; vnc_import_bgp_del_route_mode_resolve_nve_one_rd( (struct prefix_rd *)&bnp->p, table, afi, bgp, prefix, &pfx_unicast_nexthop); /* TBD how is this set? */ } if (ecom) ecommunity_free(&ecom); } /*********************************************************************** * Add/Delete CE->NVE routes ***********************************************************************/ /* * Should be called whan a bpi is added to VPN RIB. This function * will check if it is a host route and return immediately if not. */ void vnc_import_bgp_add_vnc_host_route_mode_resolve_nve( struct bgp *bgp, struct prefix_rd *prd, /* RD */ struct bgp_table *table_rd, /* per-rd VPN route table */ struct prefix *prefix, /* VPN prefix */ struct bgp_path_info *bpi) /* new VPN host route */ { afi_t afi = family2afi(prefix->family); struct skiplist *sl = NULL; int rc; struct prefix_bag *pb; void *cursor; struct rfapi_cfg *hc = NULL; vnc_zlog_debug_verbose("%s: entry", __func__); if (afi != AFI_IP && afi != AFI_IP6) { vnc_zlog_debug_verbose("%s: bad afi %d, skipping", __func__, afi); return; } if (!(hc = bgp->rfapi_cfg)) { vnc_zlog_debug_verbose("%s: bgp->rfapi_cfg is NULL, skipping", __func__); return; } /* check vnc redist flag for bgp direct routes */ if (!hc->redist[afi][ZEBRA_ROUTE_BGP_DIRECT]) { vnc_zlog_debug_verbose( "%s: bgp->rfapi_cfg->redist[afi=%d][type=ZEBRA_ROUTE_BGP_DIRECT] is 0, skipping", __func__, afi); return; } if (hc->redist_mode != VNC_REDIST_MODE_RESOLVE_NVE) { vnc_zlog_debug_verbose("%s: not in resolve-nve mode, skipping", __func__); return; } if (bgp->rfapi) sl = bgp->rfapi->resolve_nve_nexthop; if (!sl) { vnc_zlog_debug_verbose( "%s: no resolve_nve_nexthop skiplist, skipping", __func__); return; } if (!is_host_prefix(prefix)) { vnc_zlog_debug_verbose("%s: not host prefix, skipping", __func__); return; } rc = skiplist_first_value(sl, prefix, (void *)&pb, &cursor); while (!rc) { struct ecommunity *ecom; struct prefix pfx_unicast_nexthop; uint32_t *med = NULL; uint32_t local_pref; memset(&pfx_unicast_nexthop, 0, sizeof(struct prefix)); /* keep valgrind happy */ if (VNC_DEBUG(IMPORT_BGP_ADD_ROUTE)) { char hbuf[PREFIX_STRLEN]; char ubuf[PREFIX_STRLEN]; prefix2str(&pb->hpfx, hbuf, sizeof(hbuf)); prefix2str(&pb->upfx, ubuf, sizeof(ubuf)); vnc_zlog_debug_any( "%s: examining RHN Entry (q=%p): upfx=%s, hpfx=%s, ubpi=%p", __func__, cursor, ubuf, hbuf, pb->ubpi); } if (process_unicast_route(bgp, afi, &pb->upfx, pb->ubpi, &ecom, &pfx_unicast_nexthop)) { vnc_zlog_debug_verbose( "%s: process_unicast_route error, skipping", __func__); continue; } local_pref = calc_local_pref(pb->ubpi->attr, pb->ubpi->peer); if (pb->ubpi->attr && (pb->ubpi->attr->flag & ATTR_FLAG_BIT(BGP_ATTR_MULTI_EXIT_DISC))) { med = &pb->ubpi->attr->med; } /* * Sanity check */ if (vnc_prefix_cmp(&pfx_unicast_nexthop, prefix)) { char str_unh[PREFIX_STRLEN]; char str_nve_pfx[PREFIX_STRLEN]; prefix2str(&pfx_unicast_nexthop, str_unh, sizeof(str_unh)); prefix2str(prefix, str_nve_pfx, sizeof(str_nve_pfx)); vnc_zlog_debug_verbose( "%s: FATAL: resolve_nve_nexthop list item bpi nexthop %s != nve pfx %s", __func__, str_unh, str_nve_pfx); assert(0); } vnc_import_bgp_add_route_mode_resolve_nve_one_bi( bgp, afi, bpi, /* VPN bpi */ prd, &pb->upfx, /* unicast prefix */ &local_pref, med, ecom); if (ecom) ecommunity_free(&ecom); #if DEBUG_RHN_LIST /* debug */ { char pbuf[PREFIX_STRLEN]; prefix2str(prefix, pbuf, sizeof(pbuf)); vnc_zlog_debug_verbose( "%s: advancing past RHN Entry (q=%p): with prefix %s", __func__, cursor, pbuf); print_rhn_list(__func__, NULL); /* debug */ } #endif rc = skiplist_next_value(sl, prefix, (void *)&pb, &cursor); } vnc_zlog_debug_verbose("%s: done", __func__); } void vnc_import_bgp_del_vnc_host_route_mode_resolve_nve( struct bgp *bgp, struct prefix_rd *prd, /* RD */ struct bgp_table *table_rd, /* per-rd VPN route table */ struct prefix *prefix, /* VPN prefix */ struct bgp_path_info *bpi) /* old VPN host route */ { afi_t afi = family2afi(prefix->family); struct skiplist *sl = NULL; struct prefix_bag *pb; void *cursor; struct rfapi_cfg *hc = NULL; int rc; { char str_pfx[PREFIX_STRLEN]; prefix2str(prefix, str_pfx, sizeof(str_pfx)); vnc_zlog_debug_verbose("%s(bgp=%p, nve prefix=%s)", __func__, bgp, str_pfx); } if (afi != AFI_IP && afi != AFI_IP6) return; if (!(hc = bgp->rfapi_cfg)) { vnc_zlog_debug_verbose("%s: bgp->rfapi_cfg is NULL, skipping", __func__); return; } /* check vnc redist flag for bgp direct routes */ if (!hc->redist[afi][ZEBRA_ROUTE_BGP_DIRECT]) { vnc_zlog_debug_verbose( "%s: bgp->rfapi_cfg->redist[afi=%d][type=ZEBRA_ROUTE_BGP_DIRECT] is 0, skipping", __func__, afi); return; } if (hc->redist_mode != VNC_REDIST_MODE_RESOLVE_NVE) { vnc_zlog_debug_verbose("%s: not in resolve-nve mode, skipping", __func__); return; } if (bgp->rfapi) sl = bgp->rfapi->resolve_nve_nexthop; if (!sl) { vnc_zlog_debug_verbose("%s: no RHN entries, skipping", __func__); return; } if (!is_host_prefix(prefix)) { vnc_zlog_debug_verbose("%s: not host route, skip", __func__); return; } /* * Find all entries with key == CE in the RHN list */ rc = skiplist_first_value(sl, prefix, (void *)&pb, &cursor); while (!rc) { struct ecommunity *ecom; struct prefix pfx_unicast_nexthop; memset(&pfx_unicast_nexthop, 0, sizeof(struct prefix)); /* keep valgrind happy */ if (process_unicast_route(bgp, afi, &pb->upfx, pb->ubpi, &ecom, &pfx_unicast_nexthop)) { vnc_zlog_debug_verbose( "%s: process_unicast_route error, skipping", __func__); continue; } /* * Sanity check */ if (vnc_prefix_cmp(&pfx_unicast_nexthop, prefix)) { char str_unh[PREFIX_STRLEN]; char str_nve_pfx[PREFIX_STRLEN]; prefix2str(&pfx_unicast_nexthop, str_unh, sizeof(str_unh)); prefix2str(prefix, str_nve_pfx, sizeof(str_nve_pfx)); vnc_zlog_debug_verbose( "%s: FATAL: resolve_nve_nexthop list item bpi nexthop %s != nve pfx %s", __func__, str_unh, str_nve_pfx); assert(0); } vnc_import_bgp_del_route_mode_resolve_nve_one_bi( bgp, afi, bpi, prd, &pb->upfx); if (ecom) ecommunity_free(&ecom); rc = skiplist_next_value(sl, prefix, (void *)&pb, &cursor); } } /*********************************************************************** * Exterior Routes ***********************************************************************/ #define DEBUG_IS_USABLE_INTERIOR 1 static int is_usable_interior_route(struct bgp_path_info *bpi_interior) { if (!VALID_INTERIOR_TYPE(bpi_interior->type)) { #if DEBUG_IS_USABLE_INTERIOR vnc_zlog_debug_verbose( "%s: NO: type %d is not valid interior type", __func__, bpi_interior->type); #endif return 0; } if (!CHECK_FLAG(bpi_interior->flags, BGP_PATH_VALID)) { #if DEBUG_IS_USABLE_INTERIOR vnc_zlog_debug_verbose("%s: NO: BGP_PATH_VALID not set", __func__); #endif return 0; } return 1; } /* * There should be only one of these per prefix at a time. * This should be called as a result of selection operation * * NB should be called espacially for bgp instances that are named, * because the exterior routes will always come from one of those. * We filter here on the instance name to make sure we get only the * right routes. */ static void vnc_import_bgp_exterior_add_route_it( struct bgp *bgp, /* exterior instance, we hope */ struct prefix *prefix, /* unicast prefix */ struct bgp_path_info *info, /* unicast info */ struct rfapi_import_table *it_only) /* NULL, or limit to this IT */ { struct rfapi *h; struct rfapi_cfg *hc; struct prefix pfx_orig_nexthop; struct rfapi_import_table *it; struct bgp *bgp_default = bgp_get_default(); afi_t afi = family2afi(prefix->family); if (!bgp_default) return; h = bgp_default->rfapi; hc = bgp_default->rfapi_cfg; vnc_zlog_debug_verbose("%s: entry with it=%p", __func__, it_only); if (!h || !hc) { vnc_zlog_debug_verbose( "%s: rfapi or rfapi_cfg not instantiated, skipping", __func__); return; } if (!hc->redist_bgp_exterior_view) { vnc_zlog_debug_verbose("%s: exterior view not set, skipping", __func__); return; } if (bgp != hc->redist_bgp_exterior_view) { vnc_zlog_debug_verbose( "%s: bgp %p != hc->redist_bgp_exterior_view %p, skipping", __func__, bgp, hc->redist_bgp_exterior_view); return; } if (!hc->redist[afi][ZEBRA_ROUTE_BGP_DIRECT_EXT]) { vnc_zlog_debug_verbose( "%s: redist of exterior routes not enabled, skipping", __func__); return; } if (!info->attr) { vnc_zlog_debug_verbose("%s: no info, skipping", __func__); return; } /* * Extract nexthop from exterior route * * Incoming prefix is unicast. If v6, it is in multiprotocol area, * but if v4 it is in attr->nexthop */ rfapiUnicastNexthop2Prefix(afi, info->attr, &pfx_orig_nexthop); for (it = h->imports; it; it = it->next) { struct agg_table *table; struct agg_node *rn; struct agg_node *par; struct bgp_path_info *bpi_interior; int have_usable_route; vnc_zlog_debug_verbose("%s: doing it %p", __func__, it); if (it_only && (it_only != it)) { vnc_zlog_debug_verbose("%s: doesn't match it_only %p", __func__, it_only); continue; } table = it->imported_vpn[afi]; for (rn = agg_node_match(table, &pfx_orig_nexthop), have_usable_route = 0; (!have_usable_route) && rn;) { vnc_zlog_debug_verbose("%s: it %p trying rn %p", __func__, it, rn); for (bpi_interior = rn->info; bpi_interior; bpi_interior = bpi_interior->next) { struct prefix_rd *prd; struct attr new_attr; uint32_t label = 0; if (!is_usable_interior_route(bpi_interior)) continue; vnc_zlog_debug_verbose( "%s: usable: bpi_interior %p", __func__, bpi_interior); /* * have a legitimate route to exterior's nexthop * via NVE. * * Import unicast route to the import table */ have_usable_route = 1; if (bpi_interior->extra) { prd = &bpi_interior->extra->vnc.import .rd; label = decode_label( &bpi_interior->extra->label[0]); } else prd = NULL; /* use local_pref from unicast route */ memset(&new_attr, 0, sizeof(struct attr)); new_attr = *bpi_interior->attr; if (info->attr->flag & ATTR_FLAG_BIT(BGP_ATTR_LOCAL_PREF)) { new_attr.local_pref = info->attr->local_pref; new_attr.flag |= ATTR_FLAG_BIT( BGP_ATTR_LOCAL_PREF); } rfapiBgpInfoFilteredImportVPN( it, FIF_ACTION_UPDATE, bpi_interior->peer, NULL, /* rfd */ prefix, NULL, afi, prd, &new_attr, ZEBRA_ROUTE_BGP_DIRECT_EXT, BGP_ROUTE_REDISTRIBUTE, &label); } if (have_usable_route) { /* * Make monitor * * TBD factor this out into its own function */ struct prefix *pfx_mon = prefix_new(); if (!RFAPI_MONITOR_EXTERIOR(rn)->source) { RFAPI_MONITOR_EXTERIOR(rn)->source = skiplist_new( 0, NULL, (void (*)(void *)) prefix_free); agg_lock_node(rn); /* for skiplist */ } agg_lock_node(rn); /* for skiplist entry */ prefix_copy(pfx_mon, prefix); if (!skiplist_insert( RFAPI_MONITOR_EXTERIOR(rn)->source, info, pfx_mon)) { bgp_path_info_lock(info); } } par = agg_node_parent(rn); if (par) agg_lock_node(par); agg_unlock_node(rn); rn = par; } if (rn) agg_unlock_node(rn); if (!have_usable_route) { struct prefix *pfx_mon = prefix_new(); prefix_copy(pfx_mon, prefix); if (!skiplist_insert(it->monitor_exterior_orphans, info, pfx_mon)) { bgp_path_info_lock(info); } } } } void vnc_import_bgp_exterior_add_route( struct bgp *bgp, /* exterior instance, we hope */ struct prefix *prefix, /* unicast prefix */ struct bgp_path_info *info) /* unicast info */ { vnc_import_bgp_exterior_add_route_it(bgp, prefix, info, NULL); } /* * There should be only one of these per prefix at a time. * This should probably be called as a result of selection operation. * * NB should be called espacially for bgp instances that are named, * because the exterior routes will always come from one of those. * We filter here on the instance name to make sure we get only the * right routes. */ void vnc_import_bgp_exterior_del_route( struct bgp *bgp, struct prefix *prefix, /* unicast prefix */ struct bgp_path_info *info) /* unicast info */ { struct rfapi *h; struct rfapi_cfg *hc; struct rfapi_import_table *it; struct prefix pfx_orig_nexthop; afi_t afi = family2afi(prefix->family); struct bgp *bgp_default = bgp_get_default(); if (!bgp_default) return; memset(&pfx_orig_nexthop, 0, sizeof(struct prefix)); /* keep valgrind happy */ h = bgp_default->rfapi; hc = bgp_default->rfapi_cfg; if (!h || !hc) { vnc_zlog_debug_verbose( "%s: rfapi or rfapi_cfg not instantiated, skipping", __func__); return; } if (!hc->redist_bgp_exterior_view) { vnc_zlog_debug_verbose("%s: exterior view not set, skipping", __func__); return; } if (bgp != hc->redist_bgp_exterior_view) { vnc_zlog_debug_verbose( "%s: bgp %p != hc->redist_bgp_exterior_view %p, skipping", __func__, bgp, hc->redist_bgp_exterior_view); return; } if (!hc->redist[afi][ZEBRA_ROUTE_BGP_DIRECT_EXT]) { vnc_zlog_debug_verbose( "%s: redist of exterior routes no enabled, skipping", __func__); return; } if (!info->attr) { vnc_zlog_debug_verbose("%s: no info, skipping", __func__); return; } /* * Extract nexthop from exterior route * * Incoming prefix is unicast. If v6, it is in multiprotocol area, * but if v4 it is in attr->nexthop */ rfapiUnicastNexthop2Prefix(afi, info->attr, &pfx_orig_nexthop); for (it = h->imports; it; it = it->next) { struct agg_table *table; struct agg_node *rn; struct agg_node *par; struct bgp_path_info *bpi_interior; int have_usable_route; table = it->imported_vpn[afi]; for (rn = agg_node_match(table, &pfx_orig_nexthop), have_usable_route = 0; (!have_usable_route) && rn;) { for (bpi_interior = rn->info; bpi_interior; bpi_interior = bpi_interior->next) { struct prefix_rd *prd; uint32_t label = 0; if (!is_usable_interior_route(bpi_interior)) continue; /* * have a legitimate route to exterior's nexthop * via NVE. * * Import unicast route to the import table */ have_usable_route = 1; if (bpi_interior->extra) { prd = &bpi_interior->extra->vnc.import .rd; label = decode_label( &bpi_interior->extra->label[0]); } else prd = NULL; rfapiBgpInfoFilteredImportVPN( it, FIF_ACTION_KILL, bpi_interior->peer, NULL, /* rfd */ prefix, NULL, afi, prd, bpi_interior->attr, ZEBRA_ROUTE_BGP_DIRECT_EXT, BGP_ROUTE_REDISTRIBUTE, &label); /* * Delete monitor * * TBD factor this out into its own function */ { if (RFAPI_MONITOR_EXTERIOR(rn) ->source) { if (!skiplist_delete( RFAPI_MONITOR_EXTERIOR( rn) ->source, info, NULL)) { bgp_path_info_unlock( info); agg_unlock_node( rn); /* sl entry */ } if (skiplist_empty( RFAPI_MONITOR_EXTERIOR( rn) ->source)) { skiplist_free( RFAPI_MONITOR_EXTERIOR( rn) ->source); RFAPI_MONITOR_EXTERIOR( rn) ->source = NULL; agg_unlock_node( rn); /* skiplist itself */ } } } } par = agg_node_parent(rn); if (par) agg_lock_node(par); agg_unlock_node(rn); rn = par; } if (rn) agg_unlock_node(rn); if (!have_usable_route) { if (!skiplist_delete(it->monitor_exterior_orphans, info, NULL)) { bgp_path_info_unlock(info); } } } } /* * This function should be called after a new interior VPN route * has been added to an import_table. * * NB should also be called whenever an existing vpn interior route * becomes valid (e.g., valid_interior_count is inremented) */ void vnc_import_bgp_exterior_add_route_interior( struct bgp *bgp, struct rfapi_import_table *it, struct agg_node *rn_interior, /* VPN IT node */ struct bgp_path_info *bpi_interior) /* VPN IT route */ { afi_t afi = family2afi(rn_interior->p.family); struct agg_node *par; struct bgp_path_info *bpi_exterior; struct prefix *pfx_exterior; /* exterior pfx */ void *cursor; int rc; struct list *list_adopted; vnc_zlog_debug_verbose("%s: entry", __func__); if (!is_usable_interior_route(bpi_interior)) { vnc_zlog_debug_verbose( "%s: not usable interior route, skipping", __func__); return; } if (!bgp->rfapi_cfg->redist[afi][ZEBRA_ROUTE_BGP_DIRECT_EXT]) { vnc_zlog_debug_verbose( "%s: redist of exterior routes no enabled, skipping", __func__); return; } if (it == bgp->rfapi->it_ce) { vnc_zlog_debug_verbose("%s: import table is it_ce, skipping", __func__); return; } /*debugging */ { char str_pfx[PREFIX_STRLEN]; prefix2str(&rn_interior->p, str_pfx, sizeof(str_pfx)); vnc_zlog_debug_verbose("%s: interior prefix=%s, bpi type=%d", __func__, str_pfx, bpi_interior->type); } if (RFAPI_HAS_MONITOR_EXTERIOR(rn_interior)) { int count = 0; /* debugging */ vnc_zlog_debug_verbose( "%s: has exterior monitor; ext src: %p", __func__, RFAPI_MONITOR_EXTERIOR(rn_interior)->source); /* * There is a monitor here already. Therefore, we do not need * to do any pulldown. Just construct exterior routes based * on the new interior route. */ cursor = NULL; for (rc = skiplist_next( RFAPI_MONITOR_EXTERIOR(rn_interior)->source, (void **)&bpi_exterior, (void **)&pfx_exterior, &cursor); !rc; rc = skiplist_next( RFAPI_MONITOR_EXTERIOR(rn_interior)->source, (void **)&bpi_exterior, (void **)&pfx_exterior, &cursor)) { struct prefix_rd *prd; struct attr new_attr; uint32_t label = 0; ++count; /* debugging */ assert(bpi_exterior); assert(pfx_exterior); if (bpi_interior->extra) { prd = &bpi_interior->extra->vnc.import.rd; label = decode_label( &bpi_interior->extra->label[0]); } else prd = NULL; /* use local_pref from unicast route */ memset(&new_attr, 0, sizeof(struct attr)); new_attr = *bpi_interior->attr; if (bpi_exterior && (bpi_exterior->attr->flag & ATTR_FLAG_BIT(BGP_ATTR_LOCAL_PREF))) { new_attr.local_pref = bpi_exterior->attr->local_pref; new_attr.flag |= ATTR_FLAG_BIT(BGP_ATTR_LOCAL_PREF); } rfapiBgpInfoFilteredImportVPN( it, FIF_ACTION_UPDATE, bpi_interior->peer, NULL, /* rfd */ pfx_exterior, NULL, afi, prd, &new_attr, ZEBRA_ROUTE_BGP_DIRECT_EXT, BGP_ROUTE_REDISTRIBUTE, &label); } vnc_zlog_debug_verbose( "%s: finished constructing exteriors based on existing monitors", __func__); return; } vnc_zlog_debug_verbose("%s: no exterior monitor", __func__); /* * No monitor at this node. Is this the first valid interior * route at this node? */ if (RFAPI_MONITOR_EXTERIOR(rn_interior)->valid_interior_count > 1) { vnc_zlog_debug_verbose( "%s: new interior route not first valid one, skipping pulldown", __func__); return; } /* * Look up the tree for possible pulldown candidates. * Find nearest parent with an exterior route monitor */ for (par = agg_node_parent(rn_interior); par; par = agg_node_parent(par)) { if (RFAPI_HAS_MONITOR_EXTERIOR(par)) break; } if (par) { vnc_zlog_debug_verbose( "%s: checking parent %p for possible pulldowns", __func__, par); /* check monitors at par for possible pulldown */ cursor = NULL; for (rc = skiplist_next(RFAPI_MONITOR_EXTERIOR(par)->source, (void **)&bpi_exterior, (void **)&pfx_exterior, &cursor); !rc; rc = skiplist_next(RFAPI_MONITOR_EXTERIOR(par)->source, (void **)&bpi_exterior, (void **)&pfx_exterior, &cursor)) { struct prefix pfx_nexthop; memset(&pfx_nexthop, 0, sizeof(struct prefix)); /* keep valgrind happy */ /* check original nexthop for prefix match */ rfapiUnicastNexthop2Prefix(afi, bpi_exterior->attr, &pfx_nexthop); if (prefix_match(&rn_interior->p, &pfx_nexthop)) { struct bgp_path_info *bpi; struct prefix_rd *prd; struct attr new_attr; uint32_t label = 0; /* do pull-down */ /* * add monitor to longer prefix */ struct prefix *pfx_mon = prefix_new(); prefix_copy(pfx_mon, pfx_exterior); if (!RFAPI_MONITOR_EXTERIOR(rn_interior) ->source) { RFAPI_MONITOR_EXTERIOR(rn_interior) ->source = skiplist_new( 0, NULL, (void (*)(void *))prefix_free); agg_lock_node(rn_interior); } skiplist_insert( RFAPI_MONITOR_EXTERIOR(rn_interior) ->source, bpi_exterior, pfx_mon); agg_lock_node(rn_interior); /* * Delete constructed exterior routes based on * parent routes. */ for (bpi = par->info; bpi; bpi = bpi->next) { if (bpi->extra) { prd = &bpi->extra->vnc.import .rd; label = decode_label( &bpi->extra->label[0]); } else prd = NULL; rfapiBgpInfoFilteredImportVPN( it, FIF_ACTION_KILL, bpi->peer, NULL, /* rfd */ pfx_exterior, NULL, afi, prd, bpi->attr, ZEBRA_ROUTE_BGP_DIRECT_EXT, BGP_ROUTE_REDISTRIBUTE, &label); } /* * Add constructed exterior routes based on * the new interior route at longer prefix. */ if (bpi_interior->extra) { prd = &bpi_interior->extra->vnc.import .rd; label = decode_label( &bpi_interior->extra->label[0]); } else prd = NULL; /* use local_pref from unicast route */ memset(&new_attr, 0, sizeof(struct attr)); new_attr = *bpi_interior->attr; if (bpi_exterior && (bpi_exterior->attr->flag & ATTR_FLAG_BIT(BGP_ATTR_LOCAL_PREF))) { new_attr.local_pref = bpi_exterior->attr->local_pref; new_attr.flag |= ATTR_FLAG_BIT( BGP_ATTR_LOCAL_PREF); } rfapiBgpInfoFilteredImportVPN( it, FIF_ACTION_UPDATE, bpi_interior->peer, NULL, /* rfd */ pfx_exterior, NULL, afi, prd, &new_attr, ZEBRA_ROUTE_BGP_DIRECT_EXT, BGP_ROUTE_REDISTRIBUTE, &label); } } /* * The only monitors at rn_interior are the ones we added just * above, so we can use the rn_interior list to identify which * monitors to delete from the parent. */ cursor = NULL; for (rc = skiplist_next( RFAPI_MONITOR_EXTERIOR(rn_interior)->source, (void **)&bpi_exterior, NULL, &cursor); !rc; rc = skiplist_next( RFAPI_MONITOR_EXTERIOR(rn_interior)->source, (void **)&bpi_exterior, NULL, &cursor)) { skiplist_delete(RFAPI_MONITOR_EXTERIOR(par)->source, bpi_exterior, NULL); agg_unlock_node(par); /* sl entry */ } if (skiplist_empty(RFAPI_MONITOR_EXTERIOR(par)->source)) { skiplist_free(RFAPI_MONITOR_EXTERIOR(par)->source); RFAPI_MONITOR_EXTERIOR(par)->source = NULL; agg_unlock_node(par); /* sl itself */ } } vnc_zlog_debug_verbose("%s: checking orphans", __func__); /* * See if any orphans can be pulled down to the current node */ cursor = NULL; list_adopted = NULL; for (rc = skiplist_next(it->monitor_exterior_orphans, (void **)&bpi_exterior, (void **)&pfx_exterior, &cursor); !rc; rc = skiplist_next(it->monitor_exterior_orphans, (void **)&bpi_exterior, (void **)&pfx_exterior, &cursor)) { struct prefix pfx_nexthop; char buf[PREFIX_STRLEN]; afi_t afi_exterior = family2afi(pfx_exterior->family); prefix2str(pfx_exterior, buf, sizeof(buf)); vnc_zlog_debug_verbose( "%s: checking exterior orphan at prefix %s", __func__, buf); if (afi_exterior != afi) { vnc_zlog_debug_verbose( "%s: exterior orphan afi %d != interior afi %d, skip", __func__, afi_exterior, afi); continue; } /* check original nexthop for prefix match */ rfapiUnicastNexthop2Prefix(afi, bpi_exterior->attr, &pfx_nexthop); if (prefix_match(&rn_interior->p, &pfx_nexthop)) { struct prefix_rd *prd; struct attr new_attr; uint32_t label = 0; /* do pull-down */ /* * add monitor to longer prefix */ struct prefix *pfx_mon = prefix_new(); prefix_copy(pfx_mon, pfx_exterior); if (!RFAPI_MONITOR_EXTERIOR(rn_interior)->source) { RFAPI_MONITOR_EXTERIOR(rn_interior)->source = skiplist_new( 0, NULL, (void (*)(void *))prefix_free); agg_lock_node(rn_interior); /* sl */ } skiplist_insert( RFAPI_MONITOR_EXTERIOR(rn_interior)->source, bpi_exterior, pfx_mon); agg_lock_node(rn_interior); /* sl entry */ if (!list_adopted) { list_adopted = list_new(); } listnode_add(list_adopted, bpi_exterior); /* * Add constructed exterior routes based on the * new interior route at the longer prefix. */ if (bpi_interior->extra) { prd = &bpi_interior->extra->vnc.import.rd; label = decode_label( &bpi_interior->extra->label[0]); } else prd = NULL; /* use local_pref from unicast route */ memset(&new_attr, 0, sizeof(struct attr)); new_attr = *bpi_interior->attr; if (bpi_exterior && (bpi_exterior->attr->flag & ATTR_FLAG_BIT(BGP_ATTR_LOCAL_PREF))) { new_attr.local_pref = bpi_exterior->attr->local_pref; new_attr.flag |= ATTR_FLAG_BIT(BGP_ATTR_LOCAL_PREF); } rfapiBgpInfoFilteredImportVPN( it, FIF_ACTION_UPDATE, bpi_interior->peer, NULL, /* rfd */ pfx_exterior, NULL, afi, prd, &new_attr, ZEBRA_ROUTE_BGP_DIRECT_EXT, BGP_ROUTE_REDISTRIBUTE, &label); } } if (list_adopted) { struct listnode *node; struct agg_node *an_bpi_exterior; for (ALL_LIST_ELEMENTS_RO(list_adopted, node, an_bpi_exterior)) { skiplist_delete(it->monitor_exterior_orphans, an_bpi_exterior, NULL); } list_delete(&list_adopted); } } /* * This function should be called after an interior VPN route * has been deleted from an import_table. * bpi_interior must still be valid, but it must already be detached * from its route node and the route node's valid_interior_count * must already be decremented. * * NB should also be called whenever an existing vpn interior route * becomes invalid (e.g., valid_interior_count is decremented) */ void vnc_import_bgp_exterior_del_route_interior( struct bgp *bgp, struct rfapi_import_table *it, struct agg_node *rn_interior, /* VPN IT node */ struct bgp_path_info *bpi_interior) /* VPN IT route */ { afi_t afi = family2afi(rn_interior->p.family); struct agg_node *par; struct bgp_path_info *bpi_exterior; struct prefix *pfx_exterior; /* exterior pfx */ void *cursor; int rc; if (!VALID_INTERIOR_TYPE(bpi_interior->type)) { vnc_zlog_debug_verbose( "%s: type %d not valid interior type, skipping", __func__, bpi_interior->type); return; } if (!bgp->rfapi_cfg->redist[afi][ZEBRA_ROUTE_BGP_DIRECT_EXT]) { vnc_zlog_debug_verbose( "%s: redist of exterior routes no enabled, skipping", __func__); return; } if (it == bgp->rfapi->it_ce) { vnc_zlog_debug_verbose("%s: it is it_ce, skipping", __func__); return; } /* If no exterior routes depend on this prefix, nothing to do */ if (!RFAPI_HAS_MONITOR_EXTERIOR(rn_interior)) { vnc_zlog_debug_verbose("%s: no exterior monitor, skipping", __func__); return; } /*debugging */ { char str_pfx[PREFIX_STRLEN]; prefix2str(&rn_interior->p, str_pfx, sizeof(str_pfx)); vnc_zlog_debug_verbose("%s: interior prefix=%s, bpi type=%d", __func__, str_pfx, bpi_interior->type); } /* * Remove constructed routes based on the deleted interior route */ cursor = NULL; for (rc = skiplist_next(RFAPI_MONITOR_EXTERIOR(rn_interior)->source, (void **)&bpi_exterior, (void **)&pfx_exterior, &cursor); !rc; rc = skiplist_next(RFAPI_MONITOR_EXTERIOR(rn_interior)->source, (void **)&bpi_exterior, (void **)&pfx_exterior, &cursor)) { struct prefix_rd *prd; uint32_t label = 0; if (bpi_interior->extra) { prd = &bpi_interior->extra->vnc.import.rd; label = decode_label(&bpi_interior->extra->label[0]); } else prd = NULL; rfapiBgpInfoFilteredImportVPN( it, FIF_ACTION_KILL, bpi_interior->peer, NULL, /* rfd */ pfx_exterior, NULL, afi, prd, bpi_interior->attr, ZEBRA_ROUTE_BGP_DIRECT_EXT, BGP_ROUTE_REDISTRIBUTE, &label); } /* * If there are no remaining valid interior routes at this prefix, * we need to look up the tree for a possible node to move monitors to */ if (RFAPI_MONITOR_EXTERIOR(rn_interior)->valid_interior_count) { vnc_zlog_debug_verbose( "%s: interior routes still present, skipping", __func__); return; } /* * Find nearest parent with at least one valid interior route * If none is found, par will end up NULL, and we will move * the monitors to the orphan list for this import table */ for (par = agg_node_parent(rn_interior); par; par = agg_node_parent(par)) { if (RFAPI_MONITOR_EXTERIOR(par)->valid_interior_count) break; } vnc_zlog_debug_verbose("%s: par=%p, ext src: %p", __func__, par, RFAPI_MONITOR_EXTERIOR(rn_interior)->source); /* move all monitors */ /* * We will use and delete every element of the source skiplist */ while (!skiplist_first(RFAPI_MONITOR_EXTERIOR(rn_interior)->source, (void **)&bpi_exterior, (void **)&pfx_exterior)) { struct prefix *pfx_mon = prefix_new(); prefix_copy(pfx_mon, pfx_exterior); if (par) { struct bgp_path_info *bpi; /* * Add monitor to parent node */ if (!RFAPI_MONITOR_EXTERIOR(par)->source) { RFAPI_MONITOR_EXTERIOR(par)->source = skiplist_new( 0, NULL, (void (*)(void *))prefix_free); agg_lock_node(par); /* sl */ } skiplist_insert(RFAPI_MONITOR_EXTERIOR(par)->source, bpi_exterior, pfx_mon); agg_lock_node(par); /* sl entry */ /* Add constructed exterior routes based on parent */ for (bpi = par->info; bpi; bpi = bpi->next) { struct prefix_rd *prd; struct attr new_attr; uint32_t label = 0; if (bpi->type == ZEBRA_ROUTE_BGP_DIRECT_EXT) continue; if (bpi->extra) { prd = &bpi->extra->vnc.import.rd; label = decode_label( &bpi->extra->label[0]); } else prd = NULL; /* use local_pref from unicast route */ memset(&new_attr, 0, sizeof(struct attr)); new_attr = *bpi->attr; if (bpi_exterior && (bpi_exterior->attr->flag & ATTR_FLAG_BIT(BGP_ATTR_LOCAL_PREF))) { new_attr.local_pref = bpi_exterior->attr->local_pref; new_attr.flag |= ATTR_FLAG_BIT( BGP_ATTR_LOCAL_PREF); } rfapiBgpInfoFilteredImportVPN( it, FIF_ACTION_UPDATE, bpi->peer, NULL, /* rfd */ pfx_exterior, NULL, afi, prd, &new_attr, ZEBRA_ROUTE_BGP_DIRECT_EXT, BGP_ROUTE_REDISTRIBUTE, &label); } } else { /* * No interior route for exterior's nexthop. Save * monitor * in orphan list to await future route. */ skiplist_insert(it->monitor_exterior_orphans, bpi_exterior, pfx_mon); } skiplist_delete_first( RFAPI_MONITOR_EXTERIOR(rn_interior)->source); agg_unlock_node(rn_interior); /* sl entry */ } if (skiplist_empty(RFAPI_MONITOR_EXTERIOR(rn_interior)->source)) { skiplist_free(RFAPI_MONITOR_EXTERIOR(rn_interior)->source); RFAPI_MONITOR_EXTERIOR(rn_interior)->source = NULL; agg_unlock_node(rn_interior); /* sl itself */ } } /*********************************************************************** * Generic add/delete unicast routes ***********************************************************************/ void vnc_import_bgp_add_route(struct bgp *bgp, struct prefix *prefix, struct bgp_path_info *info) { afi_t afi = family2afi(prefix->family); if (VNC_DEBUG(VERBOSE)) { struct prefix pfx_nexthop; char buf[PREFIX_STRLEN]; char buf_nh[PREFIX_STRLEN]; prefix2str(prefix, buf, sizeof(buf)); rfapiUnicastNexthop2Prefix(afi, info->attr, &pfx_nexthop); prefix2str(&pfx_nexthop, buf_nh, sizeof(buf_nh)); vnc_zlog_debug_verbose("%s: pfx %s, nh %s", __func__, buf, buf_nh); } #if DEBUG_RHN_LIST print_rhn_list(__func__, "ENTER "); #endif VNC_RHNCK(enter); if (!afi) { flog_err(EC_LIB_DEVELOPMENT, "%s: can't get afi of prefix", __func__); return; } if (!bgp->rfapi_cfg) { vnc_zlog_debug_verbose("%s: bgp->rfapi_cfg is NULL, skipping", __func__); return; } /* check vnc redist flag for bgp direct routes */ if (!bgp->rfapi_cfg->redist[afi][ZEBRA_ROUTE_BGP_DIRECT]) { vnc_zlog_debug_verbose( "%s: bgp->rfapi_cfg->redist[afi=%d][type=%d=ZEBRA_ROUTE_BGP_DIRECT] is 0, skipping", __func__, afi, ZEBRA_ROUTE_BGP_DIRECT); return; } switch (bgp->rfapi_cfg->redist_mode) { case VNC_REDIST_MODE_PLAIN: vnc_import_bgp_add_route_mode_plain(bgp, prefix, info); break; case VNC_REDIST_MODE_RFG: if (bgp->rfapi_cfg->rfg_redist) vnc_import_bgp_add_route_mode_nvegroup( bgp, prefix, info, bgp->rfapi_cfg->rfg_redist); else vnc_zlog_debug_verbose("%s: mode RFG but no redist RFG", __func__); break; case VNC_REDIST_MODE_RESOLVE_NVE: vnc_import_bgp_add_route_mode_resolve_nve(bgp, prefix, info); break; } #if DEBUG_RHN_LIST print_rhn_list(__func__, "LEAVE "); #endif VNC_RHNCK(leave); } /* * "Withdrawing a Route" import process */ void vnc_import_bgp_del_route(struct bgp *bgp, struct prefix *prefix, struct bgp_path_info *info) /* unicast info */ { afi_t afi = family2afi(prefix->family); assert(afi); { struct prefix pfx_nexthop; char buf[PREFIX_STRLEN]; char buf_nh[PREFIX_STRLEN]; prefix2str(prefix, buf, sizeof(buf)); rfapiUnicastNexthop2Prefix(afi, info->attr, &pfx_nexthop); prefix2str(&pfx_nexthop, buf_nh, sizeof(buf_nh)); vnc_zlog_debug_verbose("%s: pfx %s, nh %s", __func__, buf, buf_nh); } #if DEBUG_RHN_LIST print_rhn_list(__func__, "ENTER "); #endif VNC_RHNCK(enter); if (!bgp->rfapi_cfg) { vnc_zlog_debug_verbose("%s: bgp->rfapi_cfg is NULL, skipping", __func__); return; } /* check bgp redist flag for vnc direct ("vpn") routes */ if (!bgp->rfapi_cfg->redist[afi][ZEBRA_ROUTE_BGP_DIRECT]) { vnc_zlog_debug_verbose( "%s: bgp redistribution of afi=%d VNC direct routes is off", __func__, afi); return; } switch (bgp->rfapi_cfg->redist_mode) { case VNC_REDIST_MODE_PLAIN: vnc_import_bgp_del_route_mode_plain(bgp, prefix, info); break; case VNC_REDIST_MODE_RFG: if (bgp->rfapi_cfg->rfg_redist) vnc_import_bgp_del_route_mode_nvegroup(bgp, prefix, info); else vnc_zlog_debug_verbose("%s: mode RFG but no redist RFG", __func__); break; case VNC_REDIST_MODE_RESOLVE_NVE: vnc_import_bgp_del_route_mode_resolve_nve(bgp, afi, prefix, info); break; } #if DEBUG_RHN_LIST print_rhn_list(__func__, "LEAVE "); #endif VNC_RHNCK(leave); } /*********************************************************************** * Enable/Disable ***********************************************************************/ void vnc_import_bgp_redist_enable(struct bgp *bgp, afi_t afi) { /* iterate over bgp unicast v4 and v6 routes, call * vnc_import_bgp_add_route */ struct bgp_node *rn; vnc_zlog_debug_verbose("%s: entry, afi=%d", __func__, afi); if (bgp->rfapi_cfg->redist[afi][ZEBRA_ROUTE_BGP_DIRECT]) { vnc_zlog_debug_verbose( "%s: already enabled for afi %d, skipping", __func__, afi); return; } bgp->rfapi_cfg->redist[afi][ZEBRA_ROUTE_BGP_DIRECT] = 1; for (rn = bgp_table_top(bgp->rib[afi][SAFI_UNICAST]); rn; rn = bgp_route_next(rn)) { struct bgp_path_info *bpi; for (bpi = bgp_node_get_bgp_path_info(rn); bpi; bpi = bpi->next) { if (CHECK_FLAG(bpi->flags, BGP_PATH_REMOVED)) continue; vnc_import_bgp_add_route(bgp, &rn->p, bpi); } } vnc_zlog_debug_verbose( "%s: set redist[afi=%d][type=%d=ZEBRA_ROUTE_BGP_DIRECT] return", __func__, afi, ZEBRA_ROUTE_BGP_DIRECT); } void vnc_import_bgp_exterior_redist_enable(struct bgp *bgp, afi_t afi) { struct bgp *bgp_exterior; struct bgp_node *rn; bgp_exterior = bgp->rfapi_cfg->redist_bgp_exterior_view; if (bgp->rfapi_cfg->redist[afi][ZEBRA_ROUTE_BGP_DIRECT_EXT]) { vnc_zlog_debug_verbose( "%s: already enabled for afi %d, skipping", __func__, afi); return; } bgp->rfapi_cfg->redist[afi][ZEBRA_ROUTE_BGP_DIRECT_EXT] = 1; if (!bgp_exterior) { vnc_zlog_debug_verbose( "%s: no exterior view set yet, no routes to import yet", __func__); return; } for (rn = bgp_table_top(bgp_exterior->rib[afi][SAFI_UNICAST]); rn; rn = bgp_route_next(rn)) { struct bgp_path_info *bpi; for (bpi = bgp_node_get_bgp_path_info(rn); bpi; bpi = bpi->next) { if (CHECK_FLAG(bpi->flags, BGP_PATH_REMOVED)) continue; vnc_import_bgp_exterior_add_route(bgp_exterior, &rn->p, bpi); } } vnc_zlog_debug_verbose( "%s: set redist[afi=%d][type=%d=ZEBRA_ROUTE_BGP_DIRECT] return", __func__, afi, ZEBRA_ROUTE_BGP_DIRECT); } /* * This function is for populating a newly-created Import Table */ void vnc_import_bgp_exterior_redist_enable_it( struct bgp *bgp, afi_t afi, struct rfapi_import_table *it_only) { struct bgp *bgp_exterior; struct bgp_node *rn; vnc_zlog_debug_verbose("%s: entry", __func__); bgp_exterior = bgp->rfapi_cfg->redist_bgp_exterior_view; if (!bgp->rfapi_cfg->redist[afi][ZEBRA_ROUTE_BGP_DIRECT_EXT]) { vnc_zlog_debug_verbose("%s: not enabled for afi %d, skipping", __func__, afi); return; } if (!bgp_exterior) { vnc_zlog_debug_verbose( "%s: no exterior view set yet, no routes to import yet", __func__); return; } for (rn = bgp_table_top(bgp_exterior->rib[afi][SAFI_UNICAST]); rn; rn = bgp_route_next(rn)) { struct bgp_path_info *bpi; for (bpi = bgp_node_get_bgp_path_info(rn); bpi; bpi = bpi->next) { if (CHECK_FLAG(bpi->flags, BGP_PATH_REMOVED)) continue; vnc_import_bgp_exterior_add_route_it( bgp_exterior, &rn->p, bpi, it_only); } } } void vnc_import_bgp_redist_disable(struct bgp *bgp, afi_t afi) { /* * iterate over vpn routes, find routes of type ZEBRA_ROUTE_BGP_DIRECT, * delete (call timer expire immediately) */ struct bgp_node *rn1; struct bgp_node *rn2; vnc_zlog_debug_verbose("%s: entry", __func__); if (!bgp->rfapi_cfg->redist[afi][ZEBRA_ROUTE_BGP_DIRECT]) { vnc_zlog_debug_verbose( "%s: already disabled for afi %d, skipping", __func__, afi); return; } /* * Two-level table for SAFI_MPLS_VPN * Be careful when changing the things we iterate over */ for (rn1 = bgp_table_top(bgp->rib[afi][SAFI_MPLS_VPN]); rn1; rn1 = bgp_route_next(rn1)) { if (bgp_node_has_bgp_path_info_data(rn1)) { for (rn2 = bgp_table_top( bgp_node_get_bgp_table_info(rn1)); rn2; rn2 = bgp_route_next(rn2)) { struct bgp_path_info *bpi; struct bgp_path_info *nextbpi; for (bpi = bgp_node_get_bgp_path_info(rn2); bpi; bpi = nextbpi) { nextbpi = bpi->next; if (bpi->type == ZEBRA_ROUTE_BGP_DIRECT) { struct rfapi_descriptor *rfd; vncHDBgpDirect.peer = bpi->peer; assert(bpi->extra); rfd = bpi->extra->vnc.export .rfapi_handle; vnc_zlog_debug_verbose( "%s: deleting bpi=%p, bpi->peer=%p, bpi->type=%d, bpi->sub_type=%d, bpi->extra->vnc.export.rfapi_handle=%p [passing rfd=%p]", __func__, bpi, bpi->peer, bpi->type, bpi->sub_type, (bpi->extra ? bpi->extra ->vnc .export .rfapi_handle : NULL), rfd); del_vnc_route( rfd, bpi->peer, bgp, SAFI_MPLS_VPN, &rn2->p, (struct prefix_rd *)&rn1 ->p, bpi->type, bpi->sub_type, NULL, 1); /* kill */ vncHDBgpDirect.peer = NULL; } } } } } /* Clear RHN list */ if (bgp->rfapi->resolve_nve_nexthop) { struct prefix_bag *pb; struct bgp_path_info *info; while (!skiplist_first(bgp->rfapi->resolve_nve_nexthop, NULL, (void *)&pb)) { info = pb->ubpi; skiplist_delete_first(bgp->rfapi->resolve_nve_nexthop); bgp_path_info_unlock(info); } } bgp->rfapi_cfg->redist[afi][ZEBRA_ROUTE_BGP_DIRECT] = 0; vnc_zlog_debug_verbose("%s: return", __func__); } void vnc_import_bgp_exterior_redist_disable(struct bgp *bgp, afi_t afi) { struct rfapi_cfg *hc = bgp->rfapi_cfg; struct bgp *bgp_exterior = hc->redist_bgp_exterior_view; vnc_zlog_debug_verbose("%s: entry", __func__); if (!hc->redist[afi][ZEBRA_ROUTE_BGP_DIRECT_EXT]) { vnc_zlog_debug_verbose( "%s: already disabled for afi %d, skipping", __func__, afi); return; } if (!bgp_exterior) { vnc_zlog_debug_verbose( "%s: bgp exterior view not defined, skipping", __func__); return; } { struct bgp_node *rn; for (rn = bgp_table_top(bgp_exterior->rib[afi][SAFI_UNICAST]); rn; rn = bgp_route_next(rn)) { struct bgp_path_info *bpi; for (bpi = bgp_node_get_bgp_path_info(rn); bpi; bpi = bpi->next) { if (CHECK_FLAG(bpi->flags, BGP_PATH_REMOVED)) continue; vnc_import_bgp_exterior_del_route(bgp_exterior, &rn->p, bpi); } } #if DEBUG_RHN_LIST print_rhn_list(__func__, NULL); #endif } bgp->rfapi_cfg->redist[afi][ZEBRA_ROUTE_BGP_DIRECT_EXT] = 0; vnc_zlog_debug_verbose("%s: return", __func__); } frr-7.2.1/bgpd/rfapi/vnc_import_bgp.h0000644000000000000000000000527113610377563014417 00000000000000/* * * Copyright 2009-2016, LabN Consulting, L.L.C. * * * This program is free software; you can redistribute it and/or * modify it under the terms of the GNU General Public License * as published by the Free Software Foundation; either version 2 * of the License, or (at your option) any later version. * * 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; see the file COPYING; if not, write to the Free Software * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA */ #ifndef _QUAGGA_RFAPI_VNC_IMPORT_BGP_H_ #define _QUAGGA_RFAPI_VNC_IMPORT_BGP_H_ #include "lib/zebra.h" #include "lib/prefix.h" #include "bgpd/bgpd.h" #include "bgpd/bgp_route.h" #define VALID_INTERIOR_TYPE(type) \ (((type) == ZEBRA_ROUTE_BGP) || ((type) == ZEBRA_ROUTE_BGP_DIRECT)) extern uint32_t calc_local_pref(struct attr *attr, struct peer *peer); extern int vnc_prefix_cmp(void *pfx1, void *pfx2); extern void vnc_import_bgp_add_route(struct bgp *bgp, struct prefix *prefix, struct bgp_path_info *info); extern void vnc_import_bgp_del_route(struct bgp *bgp, struct prefix *prefix, struct bgp_path_info *info); extern void vnc_import_bgp_redist_enable(struct bgp *bgp, afi_t afi); extern void vnc_import_bgp_redist_disable(struct bgp *bgp, afi_t afi); extern void vnc_import_bgp_exterior_redist_enable(struct bgp *bgp, afi_t afi); extern void vnc_import_bgp_exterior_redist_disable(struct bgp *bgp, afi_t afi); extern void vnc_import_bgp_exterior_add_route( struct bgp *bgp, /* exterior instance, we hope */ struct prefix *prefix, /* unicast prefix */ struct bgp_path_info *info); /* unicast info */ extern void vnc_import_bgp_exterior_del_route( struct bgp *bgp, struct prefix *prefix, /* unicast prefix */ struct bgp_path_info *info); /* unicast info */ extern void vnc_import_bgp_add_vnc_host_route_mode_resolve_nve( struct bgp *bgp, struct prefix_rd *prd, /* RD */ struct bgp_table *table_rd, /* per-rd VPN route table */ struct prefix *prefix, /* VPN prefix */ struct bgp_path_info *bpi); /* new VPN host route */ extern void vnc_import_bgp_del_vnc_host_route_mode_resolve_nve( struct bgp *bgp, struct prefix_rd *prd, /* RD */ struct bgp_table *table_rd, /* per-rd VPN route table */ struct prefix *prefix, /* VPN prefix */ struct bgp_path_info *bpi); /* old VPN host route */ #endif /* _QUAGGA_RFAPI_VNC_IMPORT_BGP_H_ */ frr-7.2.1/bgpd/rfapi/vnc_import_bgp_p.h0000644000000000000000000000305413610377563014733 00000000000000/* * * Copyright 2009-2016, LabN Consulting, L.L.C. * * * This program is free software; you can redistribute it and/or * modify it under the terms of the GNU General Public License * as published by the Free Software Foundation; either version 2 * of the License, or (at your option) any later version. * * 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; see the file COPYING; if not, write to the Free Software * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA */ #ifndef _QUAGGA_RFAPI_VNC_IMPORT_BGP_P_H_ #define _QUAGGA_RFAPI_VNC_IMPORT_BGP_P_H_ #include "lib/zebra.h" #include "lib/prefix.h" #include "bgpd/bgpd.h" #include "bgpd/bgp_route.h" extern void vnc_import_bgp_exterior_add_route_interior( struct bgp *bgp, struct rfapi_import_table *it, struct agg_node *rn_interior, /* VPN IT node */ struct bgp_path_info *bpi_interior); /* VPN IT route */ extern void vnc_import_bgp_exterior_del_route_interior( struct bgp *bgp, struct rfapi_import_table *it, struct agg_node *rn_interior, /* VPN IT node */ struct bgp_path_info *bpi_interior); /* VPN IT route */ extern void vnc_import_bgp_exterior_redist_enable_it(struct bgp *bgp, afi_t afi, struct rfapi_import_table *it_only); #endif /* _QUAGGA_RFAPI_VNC_IMPORT_BGP_P_H_ */ frr-7.2.1/bgpd/rfapi/vnc_zebra.c0000644000000000000000000005513213610377563013354 00000000000000/* * * Copyright 2009-2016, LabN Consulting, L.L.C. * * * This program is free software; you can redistribute it and/or * modify it under the terms of the GNU General Public License * as published by the Free Software Foundation; either version 2 * of the License, or (at your option) any later version. * * 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; see the file COPYING; if not, write to the Free Software * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA */ /* * File: vnc_zebra.c * Purpose: Handle exchange of routes between VNC and Zebra */ #include "lib/zebra.h" #include "lib/prefix.h" #include "lib/agg_table.h" #include "lib/log.h" #include "lib/command.h" #include "lib/zclient.h" #include "lib/stream.h" #include "lib/ringbuf.h" #include "lib/memory.h" #include "lib/lib_errors.h" #include "bgpd/bgpd.h" #include "bgpd/bgp_ecommunity.h" #include "bgpd/bgp_route.h" #include "bgpd/bgp_debug.h" #include "bgpd/bgp_advertise.h" #include "bgpd/rfapi/bgp_rfapi_cfg.h" #include "bgpd/rfapi/rfapi.h" #include "bgpd/rfapi/rfapi_import.h" #include "bgpd/rfapi/rfapi_private.h" #include "bgpd/rfapi/vnc_zebra.h" #include "bgpd/rfapi/rfapi_vty.h" #include "bgpd/rfapi/rfapi_backend.h" #include "bgpd/rfapi/vnc_debug.h" static struct rfapi_descriptor vncHD1VR; /* Single-VR export dummy nve descr */ static struct zclient *zclient_vnc = NULL; /*********************************************************************** * REDISTRIBUTE: Zebra sends updates/withdraws to BGPD ***********************************************************************/ /* * Routes coming from zebra get added to VNC here */ static void vnc_redistribute_add(struct prefix *p, uint32_t metric, uint8_t type) { struct bgp *bgp = bgp_get_default(); struct prefix_rd prd; struct rfapi_ip_addr vnaddr; afi_t afi; uint32_t local_pref = rfp_cost_to_localpref(metric > 255 ? 255 : metric); if (!bgp) return; if (!bgp->rfapi_cfg) { vnc_zlog_debug_verbose("%s: bgp->rfapi_cfg is NULL, skipping", __func__); return; } afi = family2afi(p->family); if (!afi) { vnc_zlog_debug_verbose("%s: unknown prefix address family %d", __func__, p->family); return; } if (!bgp->rfapi_cfg->redist[afi][type]) { vnc_zlog_debug_verbose( "%s: bgp->rfapi_cfg->redist[afi=%d][type=%d] is 0, skipping", __func__, afi, type); return; } if (!bgp->rfapi_cfg->rfg_redist) { vnc_zlog_debug_verbose("%s: no redist nve group, skipping", __func__); return; } /* * Assume nve group's configured VN address prefix is a host * route which also happens to give the NVE VN address to use * for redistributing into VNC. */ vnaddr.addr_family = bgp->rfapi_cfg->rfg_redist->vn_prefix.family; switch (bgp->rfapi_cfg->rfg_redist->vn_prefix.family) { case AF_INET: if (bgp->rfapi_cfg->rfg_redist->vn_prefix.prefixlen != 32) { vnc_zlog_debug_verbose( "%s: redist nve group VN prefix len (%d) != 32, skipping", __func__, bgp->rfapi_cfg->rfg_redist->vn_prefix .prefixlen); return; } vnaddr.addr.v4 = bgp->rfapi_cfg->rfg_redist->vn_prefix.u.prefix4; break; case AF_INET6: if (bgp->rfapi_cfg->rfg_redist->vn_prefix.prefixlen != 128) { vnc_zlog_debug_verbose( "%s: redist nve group VN prefix len (%d) != 128, skipping", __func__, bgp->rfapi_cfg->rfg_redist->vn_prefix .prefixlen); return; } vnaddr.addr.v6 = bgp->rfapi_cfg->rfg_redist->vn_prefix.u.prefix6; break; default: vnc_zlog_debug_verbose( "%s: no redist nve group VN host prefix configured, skipping", __func__); return; } /* * Assume nve group's configured UN address prefix is a host * route which also happens to give the NVE UN address to use * for redistributing into VNC. */ /* * Set UN address in dummy nve descriptor so add_vnc_route * can use it in VNC tunnel SubTLV */ { struct rfapi_ip_prefix pfx_un; rfapiQprefix2Rprefix(&bgp->rfapi_cfg->rfg_redist->un_prefix, &pfx_un); switch (pfx_un.prefix.addr_family) { case AF_INET: if (pfx_un.length != 32) { vnc_zlog_debug_verbose( "%s: redist nve group UN prefix len (%d) != 32, skipping", __func__, pfx_un.length); return; } break; case AF_INET6: if (pfx_un.length != 128) { vnc_zlog_debug_verbose( "%s: redist nve group UN prefix len (%d) != 128, skipping", __func__, pfx_un.length); return; } break; default: vnc_zlog_debug_verbose( "%s: no redist nve group UN host prefix configured, skipping", __func__); return; } vncHD1VR.un_addr = pfx_un.prefix; if (!vncHD1VR.peer) { /* * Same setup as in rfapi_open() */ vncHD1VR.peer = peer_new(bgp); vncHD1VR.peer->status = Established; /* keep bgp core happy */ bgp_sync_delete(vncHD1VR.peer); /* don't need these */ /* * since this peer is not on the I/O thread, this lock * is not strictly necessary, but serves as a reminder * to those who may meddle... */ frr_with_mutex(&vncHD1VR.peer->io_mtx) { // we don't need any I/O related facilities if (vncHD1VR.peer->ibuf) stream_fifo_free(vncHD1VR.peer->ibuf); if (vncHD1VR.peer->obuf) stream_fifo_free(vncHD1VR.peer->obuf); if (vncHD1VR.peer->ibuf_work) ringbuf_del(vncHD1VR.peer->ibuf_work); if (vncHD1VR.peer->obuf_work) stream_free(vncHD1VR.peer->obuf_work); vncHD1VR.peer->ibuf = NULL; vncHD1VR.peer->obuf = NULL; vncHD1VR.peer->obuf_work = NULL; vncHD1VR.peer->ibuf_work = NULL; } /* base code assumes have valid host pointer */ vncHD1VR.peer->host = XSTRDUP(MTYPE_BGP_PEER_HOST, ".zebra."); /* Mark peer as belonging to HD */ SET_FLAG(vncHD1VR.peer->flags, PEER_FLAG_IS_RFAPI_HD); } } memset(&prd, 0, sizeof(prd)); prd = bgp->rfapi_cfg->rfg_redist->rd; prd.family = AF_UNSPEC; prd.prefixlen = 64; add_vnc_route(&vncHD1VR, /* cookie + UN addr */ bgp, SAFI_MPLS_VPN, p, &prd, &vnaddr, &local_pref, &(bgp->rfapi_cfg->redist_lifetime), NULL, /* RFP options */ NULL, /* struct rfapi_un_option */ NULL, /* struct rfapi_vn_option */ bgp->rfapi_cfg->rfg_redist->rt_export_list, NULL, NULL, /* label: default */ type, BGP_ROUTE_REDISTRIBUTE, 0); /* flags */ } /* * Route deletions from zebra propagate to VNC here */ static void vnc_redistribute_delete(struct prefix *p, uint8_t type) { struct bgp *bgp = bgp_get_default(); struct prefix_rd prd; afi_t afi; if (!bgp) return; if (!bgp->rfapi_cfg) { vnc_zlog_debug_verbose("%s: bgp->rfapi_cfg is NULL, skipping", __func__); return; } afi = family2afi(p->family); if (!afi) { vnc_zlog_debug_verbose("%s: unknown prefix address family %d", __func__, p->family); return; } if (!bgp->rfapi_cfg->redist[afi][type]) { vnc_zlog_debug_verbose( "%s: bgp->rfapi_cfg->redist[afi=%d][type=%d] is 0, skipping", __func__, afi, type); return; } if (!bgp->rfapi_cfg->rfg_redist) { vnc_zlog_debug_verbose("%s: no redist nve group, skipping", __func__); return; } memset(&prd, 0, sizeof(prd)); prd = bgp->rfapi_cfg->rfg_redist->rd; prd.family = AF_UNSPEC; prd.prefixlen = 64; del_vnc_route(&vncHD1VR, /* use dummy ptr as cookie */ vncHD1VR.peer, bgp, SAFI_MPLS_VPN, p, &prd, type, BGP_ROUTE_REDISTRIBUTE, NULL, 0); } /* * Flush all redistributed routes of type */ static void vnc_redistribute_withdraw(struct bgp *bgp, afi_t afi, uint8_t type) { struct prefix_rd prd; struct bgp_table *table; struct bgp_node *prn; struct bgp_node *rn; vnc_zlog_debug_verbose("%s: entry", __func__); if (!bgp) return; if (!bgp->rfapi_cfg) { vnc_zlog_debug_verbose("%s: bgp->rfapi_cfg is NULL, skipping", __func__); return; } /* * Loop over all the RDs */ for (prn = bgp_table_top(bgp->rib[afi][SAFI_MPLS_VPN]); prn; prn = bgp_route_next(prn)) { memset(&prd, 0, sizeof(prd)); prd.family = AF_UNSPEC; prd.prefixlen = 64; memcpy(prd.val, prn->p.u.val, 8); /* This is the per-RD table of prefixes */ table = bgp_node_get_bgp_table_info(prn); if (!table) continue; for (rn = bgp_table_top(table); rn; rn = bgp_route_next(rn)) { struct bgp_path_info *ri; for (ri = bgp_node_get_bgp_path_info(rn); ri; ri = ri->next) { if (ri->type == type) { /* has matching redist type */ break; } } if (ri) { del_vnc_route( &vncHD1VR, /* use dummy ptr as cookie */ vncHD1VR.peer, bgp, SAFI_MPLS_VPN, &(rn->p), &prd, type, BGP_ROUTE_REDISTRIBUTE, NULL, 0); } } } vnc_zlog_debug_verbose("%s: return", __func__); } /* * Zebra route add and delete treatment. * * Assumes 1 nexthop */ static int vnc_zebra_read_route(ZAPI_CALLBACK_ARGS) { struct zapi_route api; int add; if (zapi_route_decode(zclient->ibuf, &api) < 0) return -1; /* we completely ignore srcdest routes for now. */ if (CHECK_FLAG(api.message, ZAPI_MESSAGE_SRCPFX)) return 0; add = (cmd == ZEBRA_REDISTRIBUTE_ROUTE_ADD); if (add) vnc_redistribute_add(&api.prefix, api.metric, api.type); else vnc_redistribute_delete(&api.prefix, api.type); if (BGP_DEBUG(zebra, ZEBRA)) { char buf[PREFIX_STRLEN]; prefix2str(&api.prefix, buf, sizeof(buf)); vnc_zlog_debug_verbose( "%s: Zebra rcvd: route delete %s %s metric %u", __func__, zebra_route_string(api.type), buf, api.metric); } return 0; } /*********************************************************************** * vnc_bgp_zebra_*: VNC sends updates/withdraws to Zebra ***********************************************************************/ /* * low-level message builder */ static void vnc_zebra_route_msg(struct prefix *p, unsigned int nhp_count, void *nhp_ary, int add) /* 1 = add, 0 = del */ { struct zapi_route api; struct zapi_nexthop *api_nh; int i; struct in_addr **nhp_ary4 = nhp_ary; struct in6_addr **nhp_ary6 = nhp_ary; if (!nhp_count) { vnc_zlog_debug_verbose("%s: empty nexthop list, skipping", __func__); return; } memset(&api, 0, sizeof(api)); api.vrf_id = VRF_DEFAULT; api.type = ZEBRA_ROUTE_VNC; api.safi = SAFI_UNICAST; api.prefix = *p; /* Nexthops */ SET_FLAG(api.message, ZAPI_MESSAGE_NEXTHOP); api.nexthop_num = MIN(nhp_count, multipath_num); for (i = 0; i < api.nexthop_num; i++) { api_nh = &api.nexthops[i]; api_nh->vrf_id = VRF_DEFAULT; switch (p->family) { case AF_INET: memcpy(&api_nh->gate.ipv4, nhp_ary4[i], sizeof(api_nh->gate.ipv4)); api_nh->type = NEXTHOP_TYPE_IPV4; break; case AF_INET6: memcpy(&api_nh->gate.ipv6, nhp_ary6[i], sizeof(api_nh->gate.ipv6)); api_nh->type = NEXTHOP_TYPE_IPV6; break; } } if (BGP_DEBUG(zebra, ZEBRA)) { char buf[PREFIX_STRLEN]; prefix2str(&api.prefix, buf, sizeof(buf)); vnc_zlog_debug_verbose( "%s: Zebra send: route %s %s, nhp_count=%d", __func__, (add ? "add" : "del"), buf, nhp_count); } zclient_route_send((add ? ZEBRA_ROUTE_ADD : ZEBRA_ROUTE_DELETE), zclient_vnc, &api); } static void nve_list_to_nh_array(uint8_t family, struct list *nve_list, unsigned int *nh_count_ret, void **nh_ary_ret, /* returned address array */ void **nhp_ary_ret) /* returned pointer array */ { int nve_count = listcount(nve_list); *nh_count_ret = 0; *nh_ary_ret = NULL; *nhp_ary_ret = NULL; if (!nve_count) { vnc_zlog_debug_verbose("%s: empty nve_list, skipping", __func__); return; } if (family == AF_INET) { struct listnode *ln; struct in_addr *iap; struct in_addr **v; /* * Array of nexthop addresses */ *nh_ary_ret = XCALLOC(MTYPE_TMP, nve_count * sizeof(struct in_addr)); /* * Array of pointers to nexthop addresses */ *nhp_ary_ret = XCALLOC(MTYPE_TMP, nve_count * sizeof(struct in_addr *)); iap = *nh_ary_ret; v = *nhp_ary_ret; for (ln = listhead(nve_list); ln; ln = listnextnode(ln)) { struct rfapi_descriptor *irfd; struct prefix nhp; irfd = listgetdata(ln); if (rfapiRaddr2Qprefix(&irfd->vn_addr, &nhp)) continue; *iap = nhp.u.prefix4; *v = iap; vnc_zlog_debug_verbose( "%s: ipadr: (%p)<-0x%x, ptr: (%p)<-%p", __func__, iap, nhp.u.prefix4.s_addr, v, iap); ++iap; ++v; ++*nh_count_ret; } } else if (family == AF_INET6) { struct listnode *ln; *nh_ary_ret = XCALLOC(MTYPE_TMP, nve_count * sizeof(struct in6_addr)); *nhp_ary_ret = XCALLOC(MTYPE_TMP, nve_count * sizeof(struct in6_addr *)); for (ln = listhead(nve_list); ln; ln = listnextnode(ln)) { struct rfapi_descriptor *irfd; struct in6_addr *iap = *nh_ary_ret; struct in6_addr **v = *nhp_ary_ret; struct prefix nhp; irfd = listgetdata(ln); if (rfapiRaddr2Qprefix(&irfd->vn_addr, &nhp)) continue; *iap = nhp.u.prefix6; *v = iap; ++iap; ++v; ++*nh_count_ret; } } } static void import_table_to_nve_list_zebra(struct bgp *bgp, struct rfapi_import_table *it, struct list **nves, uint8_t family) { struct listnode *node; struct rfapi_rfg_name *rfgn; /* * Loop over the list of NVE-Groups configured for * exporting to direct-bgp. * * Build a list of NVEs that use this import table */ *nves = NULL; for (ALL_LIST_ELEMENTS_RO(bgp->rfapi_cfg->rfg_export_zebra_l, node, rfgn)) { /* * If this NVE-Group's import table matches the current one */ if (rfgn->rfg && rfgn->rfg->nves && rfgn->rfg->rfapi_import_table == it) { nve_group_to_nve_list(rfgn->rfg, nves, family); } } } static void vnc_zebra_add_del_prefix(struct bgp *bgp, struct rfapi_import_table *import_table, struct agg_node *rn, int add) /* !0 = add, 0 = del */ { struct list *nves; unsigned int nexthop_count = 0; void *nh_ary = NULL; void *nhp_ary = NULL; vnc_zlog_debug_verbose("%s: entry, add=%d", __func__, add); if (zclient_vnc->sock < 0) return; if (rn->p.family != AF_INET && rn->p.family != AF_INET6) { flog_err(EC_LIB_DEVELOPMENT, "%s: invalid route node addr family", __func__); return; } if (!vrf_bitmap_check(zclient_vnc->redist[family2afi(rn->p.family)] [ZEBRA_ROUTE_VNC], VRF_DEFAULT)) return; if (!bgp->rfapi_cfg) { vnc_zlog_debug_verbose("%s: bgp->rfapi_cfg is NULL, skipping", __func__); return; } if (!listcount(bgp->rfapi_cfg->rfg_export_zebra_l)) { vnc_zlog_debug_verbose( "%s: no zebra export nve group, skipping", __func__); return; } import_table_to_nve_list_zebra(bgp, import_table, &nves, rn->p.family); if (nves) { nve_list_to_nh_array(rn->p.family, nves, &nexthop_count, &nh_ary, &nhp_ary); list_delete(&nves); if (nexthop_count) vnc_zebra_route_msg(&rn->p, nexthop_count, nhp_ary, add); } XFREE(MTYPE_TMP, nhp_ary); XFREE(MTYPE_TMP, nh_ary); } void vnc_zebra_add_prefix(struct bgp *bgp, struct rfapi_import_table *import_table, struct agg_node *rn) { vnc_zebra_add_del_prefix(bgp, import_table, rn, 1); } void vnc_zebra_del_prefix(struct bgp *bgp, struct rfapi_import_table *import_table, struct agg_node *rn) { vnc_zebra_add_del_prefix(bgp, import_table, rn, 0); } static void vnc_zebra_add_del_nve(struct bgp *bgp, struct rfapi_descriptor *rfd, int add) /* 0 = del, !0 = add */ { struct listnode *node; struct rfapi_rfg_name *rfgn; struct rfapi_nve_group_cfg *rfg = rfd->rfg; afi_t afi = family2afi(rfd->vn_addr.addr_family); struct prefix nhp; // struct prefix *nhpp; void *pAddr; vnc_zlog_debug_verbose("%s: entry, add=%d", __func__, add); if (zclient_vnc->sock < 0) return; if (!vrf_bitmap_check(zclient_vnc->redist[afi][ZEBRA_ROUTE_VNC], VRF_DEFAULT)) return; if (afi != AFI_IP && afi != AFI_IP6) { flog_err(EC_LIB_DEVELOPMENT, "%s: invalid vn addr family", __func__); return; } if (!bgp) return; if (!bgp->rfapi_cfg) { vnc_zlog_debug_verbose("%s: bgp->rfapi_cfg is NULL, skipping", __func__); return; } if (rfapiRaddr2Qprefix(&rfd->vn_addr, &nhp)) { vnc_zlog_debug_verbose("%s: can't convert vn address, skipping", __func__); return; } pAddr = &nhp.u.prefix4; /* * Loop over the list of NVE-Groups configured for * exporting to zebra and see if this new NVE's * group is among them. */ for (ALL_LIST_ELEMENTS_RO(bgp->rfapi_cfg->rfg_export_zebra_l, node, rfgn)) { /* * Yes, this NVE's group is configured for export to zebra */ if (rfgn->rfg == rfg) { struct agg_table *rt = NULL; struct agg_node *rn; struct rfapi_import_table *import_table; import_table = rfg->rfapi_import_table; vnc_zlog_debug_verbose( "%s: this nve's group is in zebra export list", __func__); rt = import_table->imported_vpn[afi]; /* * Walk the NVE-Group's VNC Import table */ for (rn = agg_route_top(rt); rn; rn = agg_route_next(rn)) { if (rn->info) { vnc_zlog_debug_verbose( "%s: sending %s", __func__, (add ? "add" : "del")); vnc_zebra_route_msg(&rn->p, 1, &pAddr, add); } } } } } void vnc_zebra_add_nve(struct bgp *bgp, struct rfapi_descriptor *rfd) { vnc_zebra_add_del_nve(bgp, rfd, 1); } void vnc_zebra_del_nve(struct bgp *bgp, struct rfapi_descriptor *rfd) { vnc_zebra_add_del_nve(bgp, rfd, 0); } static void vnc_zebra_add_del_group_afi(struct bgp *bgp, struct rfapi_nve_group_cfg *rfg, afi_t afi, int add) { struct agg_table *rt = NULL; struct agg_node *rn; struct rfapi_import_table *import_table; uint8_t family = afi2family(afi); struct list *nves = NULL; unsigned int nexthop_count = 0; void *nh_ary = NULL; void *nhp_ary = NULL; vnc_zlog_debug_verbose("%s: entry", __func__); import_table = rfg->rfapi_import_table; if (!import_table) { vnc_zlog_debug_verbose( "%s: import table not defined, returning", __func__); return; } if (afi == AFI_IP || afi == AFI_IP6) { rt = import_table->imported_vpn[afi]; } else { flog_err(EC_LIB_DEVELOPMENT, "%s: bad afi %d", __func__, afi); return; } if (!family) { flog_err(EC_LIB_DEVELOPMENT, "%s: computed bad family: %d", __func__, family); return; } if (!rfg->nves) { /* avoid segfault below if list doesn't exist */ vnc_zlog_debug_verbose("%s: no NVEs in this group", __func__); return; } nve_group_to_nve_list(rfg, &nves, family); if (nves) { vnc_zlog_debug_verbose("%s: have nves", __func__); nve_list_to_nh_array(family, nves, &nexthop_count, &nh_ary, &nhp_ary); vnc_zlog_debug_verbose("%s: family: %d, nve count: %d", __func__, family, nexthop_count); list_delete(&nves); if (nexthop_count) { /* * Walk the NVE-Group's VNC Import table */ for (rn = agg_route_top(rt); rn; rn = agg_route_next(rn)) { if (rn->info) { vnc_zebra_route_msg(&rn->p, nexthop_count, nhp_ary, add); } } } XFREE(MTYPE_TMP, nhp_ary); XFREE(MTYPE_TMP, nh_ary); } } void vnc_zebra_add_group(struct bgp *bgp, struct rfapi_nve_group_cfg *rfg) { vnc_zebra_add_del_group_afi(bgp, rfg, AFI_IP, 1); vnc_zebra_add_del_group_afi(bgp, rfg, AFI_IP6, 1); } void vnc_zebra_del_group(struct bgp *bgp, struct rfapi_nve_group_cfg *rfg) { vnc_zlog_debug_verbose("%s: entry", __func__); vnc_zebra_add_del_group_afi(bgp, rfg, AFI_IP, 0); vnc_zebra_add_del_group_afi(bgp, rfg, AFI_IP6, 0); } void vnc_zebra_reexport_group_afi(struct bgp *bgp, struct rfapi_nve_group_cfg *rfg, afi_t afi) { struct listnode *node; struct rfapi_rfg_name *rfgn; for (ALL_LIST_ELEMENTS_RO(bgp->rfapi_cfg->rfg_export_zebra_l, node, rfgn)) { if (rfgn->rfg == rfg) { vnc_zebra_add_del_group_afi(bgp, rfg, afi, 0); vnc_zebra_add_del_group_afi(bgp, rfg, afi, 1); break; } } } /*********************************************************************** * CONTROL INTERFACE ***********************************************************************/ /* Other routes redistribution into BGP. */ int vnc_redistribute_set(struct bgp *bgp, afi_t afi, int type) { if (!bgp->rfapi_cfg) { return CMD_WARNING_CONFIG_FAILED; } /* Set flag to BGP instance. */ bgp->rfapi_cfg->redist[afi][type] = 1; // bgp->redist[afi][type] = 1; /* Return if already redistribute flag is set. */ if (vrf_bitmap_check(zclient_vnc->redist[afi][type], VRF_DEFAULT)) return CMD_WARNING_CONFIG_FAILED; vrf_bitmap_set(zclient_vnc->redist[afi][type], VRF_DEFAULT); // vrf_bitmap_set(zclient_vnc->redist[afi][type], VRF_DEFAULT); /* Return if zebra connection is not established. */ if (zclient_vnc->sock < 0) return CMD_WARNING_CONFIG_FAILED; if (BGP_DEBUG(zebra, ZEBRA)) vnc_zlog_debug_verbose("Zebra send: redistribute add %s", zebra_route_string(type)); /* Send distribute add message to zebra. */ zebra_redistribute_send(ZEBRA_REDISTRIBUTE_ADD, zclient_vnc, afi, type, 0, VRF_DEFAULT); return CMD_SUCCESS; } /* Unset redistribution. */ int vnc_redistribute_unset(struct bgp *bgp, afi_t afi, int type) { vnc_zlog_debug_verbose("%s: type=%d entry", __func__, type); if (!bgp->rfapi_cfg) { vnc_zlog_debug_verbose("%s: return (no rfapi_cfg)", __func__); return CMD_WARNING_CONFIG_FAILED; } /* Unset flag from BGP instance. */ bgp->rfapi_cfg->redist[afi][type] = 0; /* Return if zebra connection is disabled. */ if (!vrf_bitmap_check(zclient_vnc->redist[afi][type], VRF_DEFAULT)) return CMD_WARNING_CONFIG_FAILED; vrf_bitmap_unset(zclient_vnc->redist[afi][type], VRF_DEFAULT); if (bgp->rfapi_cfg->redist[AFI_IP][type] == 0 && bgp->rfapi_cfg->redist[AFI_IP6][type] == 0 && zclient_vnc->sock >= 0) { /* Send distribute delete message to zebra. */ if (BGP_DEBUG(zebra, ZEBRA)) vnc_zlog_debug_verbose( "Zebra send: redistribute delete %s", zebra_route_string(type)); zebra_redistribute_send(ZEBRA_REDISTRIBUTE_DELETE, zclient_vnc, afi, type, 0, VRF_DEFAULT); } /* Withdraw redistributed routes from current BGP's routing table. */ vnc_redistribute_withdraw(bgp, afi, type); vnc_zlog_debug_verbose("%s: return", __func__); return CMD_SUCCESS; } extern struct zebra_privs_t bgpd_privs; /* * Modeled after bgp_zebra.c'bgp_zebra_init() * Charriere asks, "Is it possible to carry two?" */ void vnc_zebra_init(struct thread_master *master) { /* Set default values. */ zclient_vnc = zclient_new(master, &zclient_options_default); zclient_init(zclient_vnc, ZEBRA_ROUTE_VNC, 0, &bgpd_privs); zclient_vnc->redistribute_route_add = vnc_zebra_read_route; zclient_vnc->redistribute_route_del = vnc_zebra_read_route; } void vnc_zebra_destroy(void) { if (zclient_vnc == NULL) return; zclient_stop(zclient_vnc); zclient_free(zclient_vnc); zclient_vnc = NULL; } frr-7.2.1/bgpd/rfapi/vnc_zebra.h0000644000000000000000000000340613610377563013356 00000000000000/* * * Copyright 2009-2016, LabN Consulting, L.L.C. * * * This program is free software; you can redistribute it and/or * modify it under the terms of the GNU General Public License * as published by the Free Software Foundation; either version 2 * of the License, or (at your option) any later version. * * 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; see the file COPYING; if not, write to the Free Software * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA */ /* * File: vnc_zebra.h */ #ifndef _QUAGGA_BGP_VNC_ZEBRA_H #define _QUAGGA_BGP_VNC_ZEBRA_H #include "lib/zebra.h" extern void vnc_zebra_add_prefix(struct bgp *bgp, struct rfapi_import_table *import_table, struct agg_node *rn); extern void vnc_zebra_del_prefix(struct bgp *bgp, struct rfapi_import_table *import_table, struct agg_node *rn); extern void vnc_zebra_add_nve(struct bgp *bgp, struct rfapi_descriptor *rfd); extern void vnc_zebra_del_nve(struct bgp *bgp, struct rfapi_descriptor *rfd); extern void vnc_zebra_add_group(struct bgp *bgp, struct rfapi_nve_group_cfg *rfg); extern void vnc_zebra_del_group(struct bgp *bgp, struct rfapi_nve_group_cfg *rfg); extern void vnc_zebra_reexport_group_afi(struct bgp *bgp, struct rfapi_nve_group_cfg *rfg, afi_t afi); extern int vnc_redistribute_set(struct bgp *bgp, afi_t afi, int type); extern int vnc_redistribute_unset(struct bgp *bgp, afi_t afi, int type); #endif /* _QUAGGA_BGP_VNC_ZEBRA_H */ frr-7.2.1/bgpd/rfp-example/0000755000000000000000000000000013610377563012430 500000000000000frr-7.2.1/bgpd/rfp-example/librfp/0000755000000000000000000000000013610377563013706 500000000000000frr-7.2.1/bgpd/rfp-example/librfp/Makefile0000644000000000000000000000030513610377563015264 00000000000000all: ALWAYS @$(MAKE) -s -C ../../.. bgpd/rfp-example/librfp/librfp.a %: ALWAYS @$(MAKE) -s -C ../../.. bgpd/rfp-example/librfp/$@ Makefile: #nothing ALWAYS: .PHONY: ALWAYS makefiles .SUFFIXES: frr-7.2.1/bgpd/rfp-example/librfp/rfp.h0000644000000000000000000000176413610377563014576 00000000000000/* * * Copyright 2015-2016, LabN Consulting, L.L.C. * * * This program is free software; you can redistribute it and/or * modify it under the terms of the GNU General Public License * as published by the Free Software Foundation; either version 2 * of the License, or (at your option) any later version. * * 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; see the file COPYING; if not, write to the Free Software * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA */ /* Sample header file */ #ifndef _RFP_H #define _RFP_H #include "bgpd/rfapi/rfapi.h" extern int bgp_rfp_cfg_write(void *vty, void *bgp); /* TO BE REMOVED */ void rfp_clear_vnc_nve_all(void); #endif /* _RFP_H */ frr-7.2.1/bgpd/rfp-example/librfp/rfp_example.c0000644000000000000000000002372213610377563016302 00000000000000/* * * Copyright 2015-2016, LabN Consulting, L.L.C. * * * This program is free software; you can redistribute it and/or * modify it under the terms of the GNU General Public License * as published by the Free Software Foundation; either version 2 * of the License, or (at your option) any later version. * * 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; see the file COPYING; if not, write to the Free Software * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA */ #ifdef HAVE_CONFIG_H #include "config.h" #endif /* stub rfp */ #include "rfp_internal.h" #include "bgpd/rfapi/rfapi.h" #include "lib/command.h" struct rfp_instance_t { struct rfapi_rfp_cfg rfapi_config; struct rfapi_rfp_cb_methods rfapi_callbacks; struct thread_master *master; uint32_t config_var; }; struct rfp_instance_t global_rfi; /* dynamically allocate in full implementation */ /*********************************************************************** * Sample VTY / internal function **********************************************************************/ #define RFP_SHOW_STR "RFP information\n" DEFUN (rfp_example_config_value, rfp_example_config_value_cmd, "rfp example-config-value VALUE", RFP_SHOW_STR "Example value to be configured\n" "Value to display\n") { uint32_t value = 0; struct rfp_instance_t *rfi = NULL; rfi = rfapi_get_rfp_start_val(VTY_GET_CONTEXT(bgp)); /* BGP_NODE */ assert(rfi != NULL); value = strtoul(argv[2]->arg, NULL, 10); if (rfi) rfi->config_var = value; return CMD_SUCCESS; } DEFUN (rfp_holddown_factor, rfp_holddown_factor_cmd, "rfp holddown-factor (0-4294967295)", RFP_SHOW_STR "Set Hold-Down Factor as a percentage of registration lifetime.\n" "Percentage of registration lifetime\n") { struct rfp_instance_t *rfi; uint32_t value = 0; value = strtoul((argv[--argc]->arg), NULL, 10); rfi = rfapi_get_rfp_start_val(VTY_GET_CONTEXT(bgp)); /* BGP_NODE */ if (!rfi) { vty_out(vty, "VNC not configured\n"); return CMD_WARNING; } rfi->rfapi_config.holddown_factor = value; rfapi_rfp_set_configuration(rfi, &rfi->rfapi_config); return CMD_SUCCESS; } DEFUN (rfp_full_table_download, rfp_full_table_download_cmd, "rfp full-table-download ", RFP_SHOW_STR "RFP full table download support (default=on)\n" "Enable RFP full table download\n" "Disable RFP full table download\n") { struct rfp_instance_t *rfi; rfapi_rfp_download_type old; rfi = rfapi_get_rfp_start_val(VTY_GET_CONTEXT(bgp)); /* BGP_NODE */ if (!rfi) { vty_out(vty, "VNC not configured\n"); return CMD_WARNING; } old = rfi->rfapi_config.download_type; if (argv[--argc]->arg[1] == 'n' || argv[argc]->arg[1] == 'N') rfi->rfapi_config.download_type = RFAPI_RFP_DOWNLOAD_FULL; else rfi->rfapi_config.download_type = RFAPI_RFP_DOWNLOAD_PARTIAL; if (old != rfi->rfapi_config.download_type) rfapi_rfp_set_configuration(rfi, &rfi->rfapi_config); return CMD_SUCCESS; } static void rfp_vty_install(void) { static int installed = 0; if (installed) /* do this only once */ return; installed = 1; /* example of new cli command */ install_element(BGP_NODE, &rfp_example_config_value_cmd); install_element(BGP_NODE, &rfp_holddown_factor_cmd); install_element(BGP_NODE, &rfp_full_table_download_cmd); } /*********************************************************************** * RFAPI Callbacks **********************************************************************/ /*------------------------------------------ * rfp_response_cb * * Callbacks of this type are used to provide asynchronous * route updates from RFAPI to the RFP client. * * response_cb * called to notify the rfp client that a next hop list * that has previously been provided in response to an * rfapi_query call has been updated. Deleted routes are indicated * with lifetime==RFAPI_REMOVE_RESPONSE_LIFETIME. * * By default, the routes an NVE receives via this callback include * its own routes (that it has registered). However, these may be * filtered out if the global BGP_VNC_CONFIG_FILTER_SELF_FROM_RSP * flag is set. * * input: * next_hops a list of possible next hops. * This is a linked list allocated within the * rfapi. The response_cb callback function is responsible * for freeing this memory via rfapi_free_next_hop_list() * in order to avoid memory leaks. * * userdata value (cookie) originally specified in call to * rfapi_open() * *------------------------------------------*/ static void rfp_response_cb(struct rfapi_next_hop_entry *next_hops, void *userdata) { /* * Identify NVE based on userdata, which is a value passed * to RFAPI in the rfapi_open call */ /* process list of next_hops */ /* free next hops */ rfapi_free_next_hop_list(next_hops); return; } /*------------------------------------------ * rfp_local_cb * * Callbacks of this type are used to provide asynchronous * route updates from RFAPI to the RFP client. * * local_cb * called to notify the rfp client that a local route * has been added or deleted. Deleted routes are indicated * with lifetime==RFAPI_REMOVE_RESPONSE_LIFETIME. * * input: * next_hops a list of possible next hops. * This is a linked list allocated within the * rfapi. The local_cb callback function is responsible * for freeing this memory via rfapi_free_next_hop_list() * in order to avoid memory leaks. * * userdata value (cookie) originally specified in call to * rfapi_open() * *------------------------------------------*/ static void rfp_local_cb(struct rfapi_next_hop_entry *next_hops, void *userdata) { /* * Identify NVE based on userdata, which is a value passed * to RFAPI in the rfapi_open call */ /* process list of local next_hops */ /* free next hops */ rfapi_free_next_hop_list(next_hops); return; } /*------------------------------------------ * rfp_close_cb * * Callbacks used to provide asynchronous * notification that an rfapi_handle was invalidated * * input: * pHandle Firmerly valid rfapi_handle returned to * client via rfapi_open(). * * reason EIDRM handle administratively closed (clear nve ...) * ESTALE handle invalidated by configuration change * *------------------------------------------*/ static void rfp_close_cb(rfapi_handle pHandle, int reason) { /* close / invalidate NVE with the pHandle returned by the rfapi_open * call */ return; } /*------------------------------------------ * rfp_cfg_write_cb * * This callback is used to generate output for any config parameters * that may supported by RFP via RFP defined vty commands at the bgp * level. See loglevel as an example. * * input: * vty -- quagga vty context * rfp_start_val -- value returned by rfp_start * * output: * to vty, rfp related configuration * * return value: * lines written --------------------------------------------*/ static int rfp_cfg_write_cb(struct vty *vty, void *rfp_start_val) { struct rfp_instance_t *rfi = rfp_start_val; int write = 0; assert(rfp_start_val != NULL); if (rfi->config_var != 0) { vty_out(vty, " rfp example-config-value %u", rfi->config_var); vty_out(vty, "\n"); write++; } if (rfi->rfapi_config.holddown_factor != 0) { vty_out(vty, " rfp holddown-factor %u\n", rfi->rfapi_config.holddown_factor); write++; } if (rfi->rfapi_config.download_type == RFAPI_RFP_DOWNLOAD_FULL) { vty_out(vty, " rfp full-table-download on\n"); write++; } return write; } /*********************************************************************** * RFAPI required functions **********************************************************************/ /*------------------------------------------ * rfp_start * * This function will start the RFP code * * input: * master quagga thread_master to tie into bgpd threads * * output: * cfgp Pointer to rfapi_rfp_cfg (null = use defaults), * copied by caller, updated via rfp_set_configuration * cbmp Pointer to rfapi_rfp_cb_methods, may be null * copied by caller, updated via rfapi_rfp_set_cb_methods * * return value: * rfp_start_val rfp returned value passed on rfp_stop and rfp_cfg_write * --------------------------------------------*/ void *rfp_start(struct thread_master *master, struct rfapi_rfp_cfg **cfgp, struct rfapi_rfp_cb_methods **cbmp) { memset(&global_rfi, 0, sizeof(struct rfp_instance_t)); global_rfi.master = master; /* for BGPD threads */ /* initilize struct rfapi_rfp_cfg, see rfapi.h */ global_rfi.rfapi_config.download_type = RFAPI_RFP_DOWNLOAD_PARTIAL; /* default=partial */ global_rfi.rfapi_config.ftd_advertisement_interval = RFAPI_RFP_CFG_DEFAULT_FTD_ADVERTISEMENT_INTERVAL; global_rfi.rfapi_config.holddown_factor = 0; /* default: RFAPI_RFP_CFG_DEFAULT_HOLDDOWN_FACTOR */ global_rfi.rfapi_config.use_updated_response = 1; /* 0=no */ global_rfi.rfapi_config.use_removes = 1; /* 0=no */ /* initilize structrfapi_rfp_cb_methods , see rfapi.h */ global_rfi.rfapi_callbacks.cfg_cb = rfp_cfg_write_cb; /* no group config */ global_rfi.rfapi_callbacks.response_cb = rfp_response_cb; global_rfi.rfapi_callbacks.local_cb = rfp_local_cb; global_rfi.rfapi_callbacks.close_cb = rfp_close_cb; if (cfgp != NULL) *cfgp = &global_rfi.rfapi_config; if (cbmp != NULL) *cbmp = &global_rfi.rfapi_callbacks; rfp_vty_install(); return &global_rfi; } /*------------------------------------------ * rfp_stop * * This function is called on shutdown to trigger RFP cleanup * * input: * none * * output: * none * * return value: * rfp_start_val --------------------------------------------*/ void rfp_stop(void *rfp_start_val) { assert(rfp_start_val != NULL); } /* TO BE REMOVED */ void rfp_clear_vnc_nve_all(void) { return; } frr-7.2.1/bgpd/rfp-example/librfp/rfp_internal.h0000644000000000000000000000171413610377563016465 00000000000000/* * * Copyright 2015-2016, LabN Consulting, L.L.C. * * * This program is free software; you can redistribute it and/or * modify it under the terms of the GNU General Public License * as published by the Free Software Foundation; either version 2 * of the License, or (at your option) any later version. * * 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; see the file COPYING; if not, write to the Free Software * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA */ /* Sample header file */ #ifndef _RFP_INTERNAL_H #define _RFP_INTERNAL_H #include "lib/zebra.h" #include "rfp.h" #include "bgpd/rfapi/rfapi.h" #endif /* _RFP_INTERNAL_H */ frr-7.2.1/bgpd/rfp-example/librfp/subdir.am0000644000000000000000000000051413610377563015435 00000000000000# # librfp # if ENABLE_BGP_VNC noinst_LIBRARIES += bgpd/rfp-example/librfp/librfp.a RFPLDADD = bgpd/rfp-example/librfp/librfp.a endif bgpd_rfp_example_librfp_librfp_a_SOURCES = \ bgpd/rfp-example/librfp/rfp_example.c \ # end noinst_HEADERS += \ bgpd/rfp-example/librfp/rfp.h \ bgpd/rfp-example/librfp/rfp_internal.h \ # end frr-7.2.1/bgpd/rfp-example/rfptest/0000755000000000000000000000000013610377563014117 500000000000000frr-7.2.1/bgpd/rfp-example/rfptest/Makefile0000644000000000000000000000030613610377563015476 00000000000000all: ALWAYS @$(MAKE) -s -C ../../.. bgpd/rfp-example/rfptest/rfptest %: ALWAYS @$(MAKE) -s -C ../../.. bgpd/rfp-example/rfptest/$@ Makefile: #nothing ALWAYS: .PHONY: ALWAYS makefiles .SUFFIXES: frr-7.2.1/bgpd/rfp-example/rfptest/rfptest.c0000644000000000000000000000175213610377563015677 00000000000000/* * * Copyright 2015-2016, LabN Consulting, L.L.C. * * * This program is free software; you can redistribute it and/or * modify it under the terms of the GNU General Public License * as published by the Free Software Foundation; either version 2 * of the License, or (at your option) any later version. * * 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; see the file COPYING; if not, write to the Free Software * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA */ #ifdef HAVE_CONFIG_H #include "config.h" #endif /* dummy test program */ #include #include #include "rfptest.h" int main(void) { printf("Your test code goes here.\n"); exit(1); } frr-7.2.1/bgpd/rfp-example/rfptest/rfptest.h0000644000000000000000000000156713610377563015710 00000000000000/* * * Copyright 2015-2016, LabN Consulting, L.L.C. * * * This program is free software; you can redistribute it and/or * modify it under the terms of the GNU General Public License * as published by the Free Software Foundation; either version 2 * of the License, or (at your option) any later version. * * 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; see the file COPYING; if not, write to the Free Software * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA */ /* Sample header file */ #ifndef _RFPTEST_H #define _RFPTEST_H #endif /* _RFPTEST_H */ frr-7.2.1/bgpd/rfp-example/rfptest/subdir.am0000644000000000000000000000061713610377563015652 00000000000000# # libtest # if ENABLE_BGP_VNC noinst_PROGRAMS += bgpd/rfp-example/rfptest/rfptest endif bgpd_rfp_example_rfptest_rfptest_CFLAGS = -I$(top_srcdir)/bgpd/rfapi bgpd_rfp_example_rfptest_rfptest_SOURCES = \ bgpd/rfp-example/rfptest/rfptest.c \ # end noinst_HEADERS += \ bgpd/rfp-example/rfptest/rfptest.h \ # end bgpd_rfp_example_rfptest_rfptest_LDADD = \ lib/libfrr.la \ $(RFPLDADD) \ # end frr-7.2.1/bgpd/subdir.am0000644000000000000000000001431513610377563011743 00000000000000# # bgpd # if BGPD noinst_LIBRARIES += bgpd/libbgp.a sbin_PROGRAMS += bgpd/bgpd noinst_PROGRAMS += bgpd/bgp_btoa dist_examples_DATA += \ bgpd/bgpd.conf.sample \ bgpd/bgpd.conf.sample2 \ bgpd/bgpd.conf.vnc.sample \ # end vtysh_scan += \ $(top_srcdir)/bgpd/bgp_bfd.c \ $(top_srcdir)/bgpd/bgp_debug.c \ $(top_srcdir)/bgpd/bgp_dump.c \ $(top_srcdir)/bgpd/bgp_evpn_vty.c \ $(top_srcdir)/bgpd/bgp_filter.c \ $(top_srcdir)/bgpd/bgp_mplsvpn.c \ $(top_srcdir)/bgpd/bgp_nexthop.c \ $(top_srcdir)/bgpd/bgp_route.c \ $(top_srcdir)/bgpd/bgp_routemap.c \ $(top_srcdir)/bgpd/bgp_vty.c \ $(top_srcdir)/bgpd/bgp_flowspec_vty.c \ # end # can be loaded as DSO - always include for vtysh vtysh_scan += $(top_srcdir)/bgpd/bgp_rpki.c vtysh_scan += $(top_srcdir)/bgpd/bgp_bmp.c if ENABLE_BGP_VNC vtysh_scan += \ $(top_srcdir)/bgpd/rfapi/bgp_rfapi_cfg.c \ $(top_srcdir)/bgpd/rfapi/rfapi.c \ $(top_srcdir)/bgpd/rfapi/rfapi_vty.c \ $(top_srcdir)/bgpd/rfapi/vnc_debug.c \ # end endif if SNMP module_LTLIBRARIES += bgpd/bgpd_snmp.la endif if RPKI module_LTLIBRARIES += bgpd/bgpd_rpki.la endif if BGP_BMP module_LTLIBRARIES += bgpd/bgpd_bmp.la endif man8 += $(MANBUILD)/frr-bgpd.8 endif bgpd_libbgp_a_SOURCES = \ bgpd/bgp_addpath.c \ bgpd/bgp_advertise.c \ bgpd/bgp_aspath.c \ bgpd/bgp_attr.c \ bgpd/bgp_attr_evpn.c \ bgpd/bgp_bfd.c \ bgpd/bgp_clist.c \ bgpd/bgp_community.c \ bgpd/bgp_damp.c \ bgpd/bgp_debug.c \ bgpd/bgp_dump.c \ bgpd/bgp_ecommunity.c \ bgpd/bgp_encap_tlv.c \ bgpd/bgp_errors.c \ bgpd/bgp_evpn.c \ bgpd/bgp_evpn_vty.c \ bgpd/bgp_filter.c \ bgpd/bgp_flowspec.c \ bgpd/bgp_flowspec_util.c \ bgpd/bgp_flowspec_vty.c \ bgpd/bgp_fsm.c \ bgpd/bgp_io.c \ bgpd/bgp_keepalives.c \ bgpd/bgp_label.c \ bgpd/bgp_labelpool.c \ bgpd/bgp_lcommunity.c \ bgpd/bgp_mac.c \ bgpd/bgp_memory.c \ bgpd/bgp_mpath.c \ bgpd/bgp_mplsvpn.c \ bgpd/bgp_network.c \ bgpd/bgp_nexthop.c \ bgpd/bgp_nht.c \ bgpd/bgp_open.c \ bgpd/bgp_packet.c \ bgpd/bgp_pbr.c \ bgpd/bgp_rd.c \ bgpd/bgp_regex.c \ bgpd/bgp_route.c \ bgpd/bgp_routemap.c \ bgpd/bgp_table.c \ bgpd/bgp_updgrp.c \ bgpd/bgp_updgrp_adv.c \ bgpd/bgp_updgrp_packet.c \ bgpd/bgp_vpn.c \ bgpd/bgp_vty.c \ bgpd/bgp_zebra.c \ bgpd/bgpd.c \ # end if ENABLE_BGP_VNC bgpd_libbgp_a_SOURCES += \ bgpd/rfapi/bgp_rfapi_cfg.c \ bgpd/rfapi/rfapi_import.c \ bgpd/rfapi/rfapi.c \ bgpd/rfapi/rfapi_ap.c \ bgpd/rfapi/rfapi_descriptor_rfp_utils.c \ bgpd/rfapi/rfapi_encap_tlv.c \ bgpd/rfapi/rfapi_nve_addr.c \ bgpd/rfapi/rfapi_monitor.c \ bgpd/rfapi/rfapi_rib.c \ bgpd/rfapi/rfapi_vty.c \ bgpd/rfapi/vnc_debug.c \ bgpd/rfapi/vnc_export_bgp.c \ bgpd/rfapi/vnc_export_table.c \ bgpd/rfapi/vnc_import_bgp.c \ bgpd/rfapi/vnc_zebra.c \ # end endif noinst_HEADERS += \ bgpd/bgp_addpath.h \ bgpd/bgp_addpath_types.h \ bgpd/bgp_advertise.h \ bgpd/bgp_aspath.h \ bgpd/bgp_attr.h \ bgpd/bgp_attr_evpn.h \ bgpd/bgp_bfd.h \ bgpd/bgp_clist.h \ bgpd/bgp_community.h \ bgpd/bgp_damp.h \ bgpd/bgp_debug.h \ bgpd/bgp_dump.h \ bgpd/bgp_bmp.h \ bgpd/bgp_ecommunity.h \ bgpd/bgp_encap_tlv.h \ bgpd/bgp_encap_types.h \ bgpd/bgp_errors.h \ bgpd/bgp_evpn.h \ bgpd/bgp_evpn_private.h \ bgpd/bgp_evpn_vty.h \ bgpd/bgp_filter.h \ bgpd/bgp_flowspec.h \ bgpd/bgp_flowspec_private.h \ bgpd/bgp_flowspec_util.h \ bgpd/bgp_fsm.h \ bgpd/bgp_io.h \ bgpd/bgp_keepalives.h \ bgpd/bgp_label.h \ bgpd/bgp_labelpool.h \ bgpd/bgp_lcommunity.h \ bgpd/bgp_mac.h \ bgpd/bgp_memory.h \ bgpd/bgp_mpath.h \ bgpd/bgp_mplsvpn.h \ bgpd/bgp_network.h \ bgpd/bgp_nexthop.h \ bgpd/bgp_nht.h \ bgpd/bgp_open.h \ bgpd/bgp_packet.h \ bgpd/bgp_pbr.h \ bgpd/bgp_rd.h \ bgpd/bgp_regex.h \ bgpd/bgp_route.h \ bgpd/bgp_table.h \ bgpd/bgp_updgrp.h \ bgpd/bgp_vpn.h \ bgpd/bgp_vty.h \ bgpd/bgp_zebra.h \ bgpd/bgpd.h \ \ bgpd/rfapi/bgp_rfapi_cfg.h \ bgpd/rfapi/rfapi_import.h \ bgpd/rfapi/rfapi.h \ bgpd/rfapi/rfapi_ap.h \ bgpd/rfapi/rfapi_backend.h \ bgpd/rfapi/rfapi_descriptor_rfp_utils.h \ bgpd/rfapi/rfapi_encap_tlv.h \ bgpd/rfapi/rfapi_nve_addr.h \ bgpd/rfapi/rfapi_monitor.h \ bgpd/rfapi/rfapi_private.h \ bgpd/rfapi/rfapi_rib.h \ bgpd/rfapi/rfapi_vty.h \ bgpd/rfapi/vnc_debug.h \ bgpd/rfapi/vnc_export_bgp.h \ bgpd/rfapi/vnc_export_table.h \ bgpd/rfapi/vnc_import_bgp.h \ bgpd/rfapi/vnc_zebra.h \ bgpd/rfapi/vnc_export_bgp_p.h \ bgpd/rfapi/vnc_import_bgp_p.h \ bgpd/bgp_vnc_types.h \ # end bgpd_bgpd_SOURCES = bgpd/bgp_main.c bgpd_bgp_btoa_SOURCES = bgpd/bgp_btoa.c bgpd_bgpd_CFLAGS = $(AM_CFLAGS) bgpd_bgp_btoa_CFLAGS = $(AM_CFLAGS) if ENABLE_BGP_VNC bgpd_bgpd_SOURCES += bgpd/rfapi/rfapi_descriptor_rfp_utils.c bgpd_bgpd_CFLAGS += -Irfapi -I@top_srcdir@/$(RFPINC) bgpd_bgp_btoa_SOURCES += bgpd/rfapi/rfapi_descriptor_rfp_utils.c bgpd_bgp_btoa_CFLAGS += -Irfapi -I@top_srcdir@/$(RFPINC) endif # RFPLDADD is set in bgpd/rfp-example/librfp/subdir.am bgpd_bgpd_LDADD = bgpd/libbgp.a $(RFPLDADD) lib/libfrr.la $(LIBCAP) $(LIBM) bgpd_bgp_btoa_LDADD = bgpd/libbgp.a $(RFPLDADD) lib/libfrr.la $(LIBCAP) $(LIBM) bgpd_bgpd_snmp_la_SOURCES = bgpd/bgp_snmp.c bgpd_bgpd_snmp_la_CFLAGS = $(WERROR) $(SNMP_CFLAGS) -std=gnu99 bgpd_bgpd_snmp_la_LDFLAGS = -avoid-version -module -shared -export-dynamic bgpd_bgpd_snmp_la_LIBADD = lib/libfrrsnmp.la bgpd_bgpd_rpki_la_SOURCES = bgpd/bgp_rpki.c bgpd_bgpd_rpki_la_CFLAGS = $(WERROR) $(RTRLIB_CFLAGS) bgpd_bgpd_rpki_la_LDFLAGS = -avoid-version -module -shared -export-dynamic bgpd_bgpd_rpki_la_LIBADD = $(RTRLIB_LIBS) bgpd_bgpd_bmp_la_SOURCES = bgpd/bgp_bmp.c bgpd_bgpd_bmp_la_LIBADD = lib/libfrrcares.la bgpd_bgpd_bmp_la_LDFLAGS = -avoid-version -module -shared -export-dynamic bgpd/bgp_evpn_vty_clippy.c: $(CLIPPY_DEPS) bgpd/bgp_evpn_vty.$(OBJEXT): bgpd/bgp_evpn_vty_clippy.c bgpd/bgp_vty_clippy.c: $(CLIPPY_DEPS) bgpd/bgp_vty.$(OBJEXT): bgpd/bgp_vty_clippy.c bgpd/bgp_route_clippy.c: $(CLIPPY_DEPS) bgpd/bgp_route.$(OBJEXT): bgpd/bgp_route_clippy.c bgpd/bgp_debug_clippy.c: $(CLIPPY_DEPS) bgpd/bgp_debug.$(OBJEXT): bgpd/bgp_debug_clippy.c bgpd/bgp_routemap_clippy.c: $(CLIPPY_DEPS) bgpd/bgp_routemap.$(OBJEXT): bgpd/bgp_routemap_clippy.c bgpd/bgp_rpki_clippy.c: $(CLIPPY_DEPS) $(AUTOMAKE_DUMMY)bgpd/bgpd_bgpd_rpki_la-bgp_rpki.lo: bgpd/bgp_rpki_clippy.c $(AUTOMAKE_DUMMY)bgpd/bgpd_rpki_la-bgp_rpki.lo: bgpd/bgp_rpki_clippy.c bgpd/bgp_bmp_clippy.c: $(CLIPPY_DEPS) bgpd/bgp_bmp.lo: bgpd/bgp_bmp_clippy.c frr-7.2.1/changelog-auto0000644000000000000000000015242313610377563012043 00000000000000frr (7.2.1-0) UNRELEASED; urgency=medium * autoconf changelog entry -- for git autobuilds only. remove and replace when creating releases! (tools/tarsource.sh will handle this) -- FRRouting-Dev Fri, 17 Jan 2020 16:41:00 +0200 frr (7.2-1) testing; urgency=medium * upstream 7.2.1 release -- Martin Winter Fri, 17 Jan 2020 16:40:00 +0200 frr (7.2-0) testing; urgency=medium * upstream 7.2 release -- Martin Winter Tue, 17 Oct 2019 16:42:20 +0200 frr (7.1-0) testing; urgency=medium * upstream 7.1 release -- David Lamparter Tue, 18 Jun 2019 06:08:13 +0200 frr (6.0-2) testing; urgency=medium * add install-info to build deps * remove trailing whitespace from control * cleanup tcp-zebra configure options * drop unused SMUX client OID MIBs * remove /proc check * remove --enable-poll * remove libtool .la files * drop texlive-latex-base, texlive-generic-recommended build deps * consistently allow python2 or python3 * remove bad USE_* options, add WERROR * drop libncurses5 dep * remove backports mechanism * use better dependency for pythontools (binNMU compatible) * remove bogus shlib:Depends on frr-dbg * create frr-snmp and frr-rpki-rtrlib * make frr-pythontools a "Recommends:" * use redistclean target * update to Debian Policy version 4.2.1 * raise debhelper compat level to 9 * ditch development-only files * modernise dh_missing and use fail mode * disable zeromq and FPM * always install /etc/init.d/frr * put frr-doc package in 'doc' section * install HTML docs, drop tools/ * fix install for {frr,rfptest,ospfclient} * add watch file * change python dependency and shebang to python3:any * use set -e in maintscripts * put myself in as maintainer * update copyright file * closes: #863249 -- David Lamparter Thu, 25 Oct 2018 16:36:50 +0200 frr (6.0-1) RELEASED; urgency=medium * New Enabled: PIM draft Unnumbered -- FRRouting-Dev Wed, 18 Oct 2017 17:01:42 -0700 frr (3.0-1) RELEASED; urgency=medium * Added Debian 9 Backport -- FRRouting-Dev Mon, 16 Oct 2017 03:28:00 -0700 frr (3.0-0) RELEASED; urgency=medium * New Enabled: BGP Shutdown Message * New Enabled: BGP Large Community * New Enabled: BGP RFC 7432 Partial Support w/ Ethernet VPN * New Enabled: BGP EVPN RT-5 * New Enabled: LDP RFC 5561 * New Enabled: LDP RFC 5918 * New Enabled: LDP RFC 5919 * New Enabled: LDP RFC 6667 * New Enabled: LDP RFC 7473 * New Enabled: OSPF RFC 4552 * New Enabled: ISIS SPF Backoff draft * New Enabled: PIM Unnumbered Interfaces * New Enabled: PIM RFC 4611 * New Enabled: PIM Sparse Mode * New Enabled: NHRP RFC 2332 * New Enabled: Label Manager * Switched from hardening-wrapper to dpkg-buildflags. -- FRRouting-Dev Fri, 13 Oct 2017 16:17:26 -0700 frr (2.0-0) RELEASED; urgency=medium * Switchover to FRR -- FRRouting-Dev Mon, 23 Jan 2017 16:30:22 -0400 quagga (0.99.24+cl3u5) RELEASED; urgency=medium * Closes: CM-12846 - Resolve Memory leaks in 'show ip bgp neighbor json' * Closes: CM-5878 - Display all ospf peers with 'show ip ospf neighbor detail all' * Closes: CM-5794 - Add support for IPv6 static to null0 * Closes: CM-13060 - Reduce JSON memory usage. * Closes: CM-10394 - protect 'could not get instance' error messages with debug * Closes: CM-11173 - Move netlink error messages undeer a debug * Closes: CM-13328 - Fixes route missing in hardware after reboot -- dev-support Fri, 11 Nov 2016 22:13:29 -0400 quagga (0.99.24+cl3u4) RELEASED; urgency=medium * Closes: CM-12687 - Buffer overflow in zebra RA code -- dev-support Wed, 31 Aug 2016 12:36:10 -0400 quagga (0.99.24+cl3u3) RELEASED; urgency=medium * New Enabled: Merge up-to 0.99.24 code from upstream * New Enabled: Additional CLI simplification * New Enabled: Various Bug Fixes -- dev-support Thu, 04 Aug 2016 08:43:36 -0700 quagga (0.99.23.1-1+cl3u2) RELEASED; urgency=medium * New Enabled: VRF - See Documentation for how to use * New Enabled: Improved interface statistics * New Enabled: Various vtysh improvements * New Enabled: Numerous compile warnings and SA fixes * New Enabled: Improved priviledge handlingA * New Enabled: Various OSPF CLI fixes * New Enabled: Prefix-list Performance Improvements. * New Enabled: Allow more than 1k peers in Quagga and Performance Improvements * New Enabled: Systemd integration * New Enabled: Various ISIS fixes * New Enabled: BGP MRT improvements * New Enabled: Lowered default MRAI timers * New Enabled: Lowered default 'timers connect' * New Enabled: 'bgp log-neighbor-changes' enabled by default * New Enabled: BGP default keepalive to 3s and holdtime to 9s * New Enabled: OSPF spf timers are now '0 50 5000' by default * New Enabled: BGP hostname is displayed by default * New Enabled: BGP 'no-as-set' is the default for 'bgp as-path multipath-relax" * New Enabled: RA is on by default if using 5549 on an interface * New Enabled: peer-group restrictions relaxed, update-groups determine outbund policy anyway * New Enabled: BGP enabled 'maximum-paths 64' by default * New Enabled: OSPF "log-adjacency-changes" on by default * New Enabled: Zebra: Add IPv6 protocol filtering support * and setting src of IPv6 routes. * New Enabled: BGP and OSPF JSON commands added. * New Enabled: BGP Enable multiple instances support by default * New Enabled: 'banner motd file' command * New Enabled: Remove bad default passwords from default conf * New Enabled: BGP addpath TX * New Enabled: Simplified configuration for BGP Unnumbered * New Deprecated: Remove unused 'show memory XXX' functionality * New Deprecated: Remove babel protocol * Closes: CM-10435 Addition on hidden command "bfd multihop/singlehop" and "ptm-enable" per interface command * Closes: CM-9974 Get route counts right for show ip route summary * Closes: CM-9786 BGP memory leak in peer hostname * Closes: CM-9340 BGP: Ensure correct sequence of processing at exit * Closes: CM-9270 ripd: Fix crash when a default route is passed to rip * Closes: CM-9255 BGPD crash around bgp_config_write () * Closes: CM-9134 ospf6d: Fix for crash when non area 0 network entered first * Closes: CM-8934 OSPFv3: Check area before scheduling SPF * Closes: CM-8514 zebra: Crash upon disabling a link * Closes: CM-8295 BGP crash in group_announce_route_walkcb * Closes: CM-8191 BGP: crash in update_subgroup_merge() * Closes: CM-8015 lib: Memory reporting fails over 2GB * Closes: CM-7926 BGP: crash from not NULLing freed pointers -- dev-support Wed, 04 May 2016 16:22:52 -0700 quagga (0.99.23.1-1) unstable; urgency=medium * New upstream release * Added .png figures for info files to quagga-doc package. * Changed dependency from iproute to iproute2 (thanks to Andreas Henriksson). Closes: #753736 * Added texlive-fonts-recommended to build-depends to get ecrm1095 font (thanks to Christoph Biedl). Closes: #651545 -- Christian Brunotte Tue, 30 Sep 2014 00:20:12 +0200 quagga (0.99.23-1) unstable; urgency=low * New upstream release * Removed debian/patches/readline-6.3.diff which was already in upstream. -- Christian Hammers Tue, 08 Jul 2014 09:15:48 +0200 quagga (0.99.22.4-4) unstable; urgency=medium * Fix build failure with readline-6.3 (thanks to Matthias Klose). Closes: #741774 -- Christian Hammers Sun, 23 Mar 2014 15:28:42 +0100 quagga (0.99.22.4-3) unstable; urgency=low * Added status to init script (thanks to Peter J. Holzer). Closes: #730625 * Init script now sources /lib/lsb/init-functions. * Switched from hardening-wrapper to dpkg-buildflags. -- Christian Hammers Wed, 01 Jan 2014 19:12:01 +0100 quagga (0.99.22.4-2) unstable; urgency=low * Fixed typo in package description (thanks to Davide Prina). Closes: #625860 * Added Italian Debconf translation (thanks to Beatrice Torracca) Closes: #729798 -- Christian Hammers Tue, 26 Nov 2013 00:47:11 +0100 quagga (0.99.22.4-1) unstable; urgency=high * SECURITY: "ospfd: CVE-2013-2236, stack overrun in apiserver the OSPF API-server (exporting the LSDB and allowing announcement of Opaque-LSAs) writes past the end of fixed on-stack buffers. This leads to an exploitable stack overflow. For this condition to occur, the following two conditions must be true: - Quagga is configured with --enable-opaque-lsa - ospfd is started with the "-a" command line option If either of these does not hold, the relevant code is not executed and the issue does not get triggered." Closes: #726724 * New upstream release - ospfd: protect vs. VU#229804 (malformed Router-LSA) (Quagga is said to be non-vulnerable but still adds some protection) -- Christian Hammers Thu, 24 Oct 2013 22:58:37 +0200 quagga (0.99.22.1-2) unstable; urgency=low * Added autopkgtests (thanks to Yolanda Robla). Closes: #710147 * Added "status" command to init script (thanks to James Andrewartha). Closes: #690013 * Added "libsnmp-dev" to Build-Deps. There not needed for the official builds but for people who compile Quagga themselves to activate the SNMP feature (which for licence reasons cannot be done by Debian). Thanks to Ben Winslow). Closes: #694852 * Changed watchquagga_options to an array so that quotes can finally be used as expected. Closes: #681088 * Fixed bug that prevented restarting only the watchquagga daemon (thanks to Harald Kappe). Closes: #687124 -- Christian Hammers Sat, 27 Jul 2013 16:06:25 +0200 quagga (0.99.22.1-1) unstable; urgency=low * New upstream release - ospfd restore nexthop IP for p2p interfaces - ospfd: fix LSA initialization for build without opaque LSA - ripd: correctly redistribute ifindex routes (BZ#664) - bgpd: fix lost passwords of grouped neighbors * Removed 91_ld_as_needed.diff as it was found in the upstream source. -- Christian Hammers Mon, 22 Apr 2013 22:21:20 +0200 quagga (0.99.22-1) unstable; urgency=low * New upstream release. - [bgpd] The semantics of default-originate route-map have changed. The route-map is now used to advertise the default route conditionally. The old behaviour which allowed to set attributes on the originated default route is no longer supported. - [bgpd] this version of bgpd implements draft-idr-error-handling. This was added in 0.99.21 and may not be desirable. If you need a version without this behaviour, please use 0.99.20.1. There will be a runtime configuration switch for this in future versions. - [isisd] is in "beta" state. - [ospf6d] is in "alpha/experimental" state - More changes are documented in the upstream changelog! * debian/watch: Adjusted to new savannah.gnu.org site, thanks to Bart Martens. * debian/patches/99_CVE-2012-1820_bgp_capability_orf.diff removed as its in the changelog. * debian/patches/99_distribute_list.diff removed as its in the changelog. * debian/patches/10_doc__Makefiles__makeinfo-force.diff removed as it was just for Debian woody. -- Christian Hammers Thu, 14 Feb 2013 00:22:00 +0100 quagga (0.99.21-4) unstable; urgency=medium * Fixed regression bug that caused OSPF "distribute-list" statements to be silently ignored. The patch has already been applied upstream but there has been no new Quagga release since then. Thanks to Hans van Kranenburg for reporting. Closes: #697240 -- Christian Hammers Sun, 06 Jan 2013 15:50:32 +0100 quagga (0.99.21-3) unstable; urgency=high * SECURITY: CVE-2012-1820 - Quagga contained a bug in BGP OPEN message handling. A denial-of-service condition could be caused by an attacker controlling one of the pre-configured BGP peers. In most cases this means, that the attack must be originated from an adjacent network. Closes: #676510 -- Christian Hammers Fri, 08 Jun 2012 01:15:32 +0200 quagga (0.99.21-2) unstable; urgency=low * Renamed babeld.8 to quagga-babeld.8 as it conflicted with the original mapage of the babeld package which users might want to install in parallel as it is slightly more capable. Closes: #671916 -- Christian Hammers Thu, 10 May 2012 07:53:01 +0200 quagga (0.99.21-1) unstable; urgency=low * New upstream release - [bgpd] BGP multipath support has been merged - [bgpd] SAFI (Multicast topology) support has been extended to propagate the topology to zebra. - [bgpd] AS path limit functionality has been removed - [babeld] a new routing daemon implementing the BABEL ad-hoc mesh routing protocol has been merged. - [isisd] a major overhaul has been picked up. Please note that isisd is STILL NOT SUITABLE FOR PRODUCTION USE. - a lot of bugs have been fixed * Added watchquagga daemon. * Added DEP-3 conforming patch comments. -- Christian Hammers Sun, 06 May 2012 15:33:33 +0200 quagga (0.99.20.1-1) unstable; urgency=high * SECURITY: CVE-2012-0249 - Quagga ospfd DoS on malformed LS-Update packet CVE-2012-0250 - Quagga ospfd DoS on malformed Network-LSA data CVE-2012-0255 - Quagga bgpd DoS on malformed OPEN message * New upstream release. Closes: #664033 -- Christian Hammers Fri, 16 Mar 2012 22:14:05 +0100 quagga (0.99.20-4) unstable; urgency=low * Switch to dpkg-source 3.0 (quilt) format. * Switch to changelog-format-1.0. -- Christian Hammers Sat, 25 Feb 2012 18:52:06 +0100 quagga (0.99.20-3) unstable; urgency=low * Added --sysconfdir back to the configure options (thanks to Sven-Haegar Koch). Closes: #645649 -- Christian Hammers Tue, 18 Oct 2011 00:24:37 +0200 quagga (0.99.20-2) unstable; urgency=low * Bumped standards version to 0.9.2. * Migrated to "dh" build system. * Added quagga-dbg package. -- Christian Hammers Fri, 14 Oct 2011 23:59:26 +0200 quagga (0.99.20-1) unstable; urgency=low * New upstream release: "The primary focus of this release is a fix of SEGV regression in ospfd, which was introduced in 0.99.19. It also features a series of minor improvements, including better RFC compliance in bgpd, better support of FreeBSD and some enhancements to isisd." * Fixes off-by-one bug (removed 20_ospf6_area_argv.dpatch). Closes: #519488 -- Christian Hammers Fri, 30 Sep 2011 00:59:24 +0200 quagga (0.99.19-1) unstable; urgency=high * SECURITY: "This release provides security fixes, which address assorted vulnerabilities in bgpd, ospfd and ospf6d (CVE-2011-3323, CVE-2011-3324, CVE-2011-3325, CVE-2011-3326 and CVE-2011-3327). * New upstream release. * Removed incorporated debian/patches/92_opaque_lsa_enable.dpatch. * Removed incorporated debian/patches/93_opaque_lsa_fix.dpatch. * Removed obsolete debian/README.Debian.Woody and README.Debian.MD5. -- Christian Hammers Tue, 27 Sep 2011 00:16:27 +0200 quagga (0.99.18-1) unstable; urgency=low * SECURITY: "This release fixes 2 denial of services in bgpd, which can be remotely triggered by malformed AS-Pathlimit or Extended-Community attributes. These issues have been assigned CVE-2010-1674 and CVE-2010-1675. Support for AS-Pathlimit has been removed with this release." * Added Brazilian Portuguese debconf translation. Closes: #617735 * Changed section for quagga-doc from "doc" to "net". * Added patch to fix FTBFS with latest GCC. Closes: #614459 -- Christian Hammers Tue, 22 Mar 2011 23:13:34 +0100 quagga (0.99.17-4) unstable; urgency=low * Added comment to init script (thanks to Marc Haber). Closes: #599524 -- Christian Hammers Thu, 13 Jan 2011 23:53:29 +0100 quagga (0.99.17-3) unstable; urgency=low * Fix FTBFS with ld --as-needed (thanks to Matthias Klose at Ubuntu). Closes: #609555 -- Christian Hammers Thu, 13 Jan 2011 23:27:06 +0100 quagga (0.99.17-2) unstable; urgency=low * Added Danisch Debconf translation (thanks to Joe Dalton). Closes: #596259 -- Christian Hammers Sat, 18 Sep 2010 12:20:07 +0200 quagga (0.99.17-1) unstable; urgency=high * SECURITY: "This release provides two important bugfixes, which address remote crash possibility in bgpd discovered by CROSS team.": 1. Stack buffer overflow by processing certain Route-Refresh messages CVE-2010-2948 2. DoS (crash) while processing certain BGP update AS path messages CVE-2010-2949 Closes: #594262 -- Christian Hammers Wed, 25 Aug 2010 00:52:48 +0200 quagga (0.99.16-1) unstable; urgency=low * New upstream release. Closes: #574527 * Added chrpath to debian/rules to fix rpath problems that lintian spottet. -- Christian Hammers Sun, 21 Mar 2010 17:05:40 +0100 quagga (0.99.15-2) unstable; urgency=low * Applied patch for off-by-one bug in ospf6d that caused a segmentation fault when using the "area a.b.c.d filter-list prefix" command (thanks to Steinar H. Gunderson). Closes: 519488 -- Christian Hammers Sun, 14 Feb 2010 20:02:03 +0100 quagga (0.99.15-1) unstable; urgency=low * New upstream release "This fixes some annoying little ospfd and ospf6d regressions, which made 0.99.14 a bit of a problem release (...) This release still contains a regression in the "no ip address ..." command, at least on Linux. See bug #486, which contains a workaround patch. This release should be considered a 1.0.0 release candidate. Please test this release as widely as possible." * Fixed wrong port number in zebra.8 (thanks to Thijs Kinkhorst). Closes: #517860 * Added Russian Debconf tanslation (thanks to Yuri Kozlov). Closes: #539464 * Removed so-version in build-dep to libreadline-dev on request of Matthias Klose. * Added README.source with reference to dpatch as suggested by lintian. * Bumped standards versionto 3.8.3. -- Christian Hammers Sun, 13 Sep 2009 18:12:06 +0200 quagga (0.99.14-1) unstable; urgency=low * New upstream release "This release contains a regression fix for ospf6d, various small fixes and some hopefully very significant bgpd stability fixes. This release should be considered a 1.0.0 release candidate. Please test this release as widely as possible." * Fixes bug with premature LSA aging in ospf6d. Closes: #535030 * Fixes section number in zebra.8 manpage. Closes: #517860 -- Christian Hammers Sat, 25 Jul 2009 00:40:38 +0200 quagga (0.99.13-2) unstable; urgency=low * Added Japanese Debconf translation (thanks to Hideki Yamane). Closes: #510714 * When checking for obsoleted config options in preinst, print filename where it occures (thanks to Michael Bussmann). Closes: #339489 -- Christian Hammers Sun, 19 Jul 2009 17:13:23 +0200 quagga (0.99.13-1) unstable; urgency=low * New upstream release "This release is contains a number of small fixes, for potentially irritating issues, as well as small enhancements to vtysh and support for linking to PCRE (a much faster regex library)." * Added build-dep to gawk as configure required it for memtypes.awk * Replaced build-dep to gs-gpl with ghostscript as requested by lintian * Minor changes to copyright and control files to make lintian happy. -- Christian Hammers Wed, 24 Jun 2009 17:53:28 +0200 quagga (0.99.12-1) unstable; urgency=high * New upstream release "This release fixes an urgent bug in bgpd where it could hit an assert if it received a long AS_PATH with a 4-byte ASN." Noteworthy bugfixes: + [bgpd] Fix bgp ipv4/ipv6 accept handling + [bgpd] AS4 bugfix by Chris Caputo + [bgpd] Allow accepted peers to progress even if realpeer is in Connect + [ospfd] Switch Fletcher checksum back to old ospfd version -- Christian Hammers Mon, 22 Jun 2009 00:16:33 +0200 quagga (0.99.11-1) unstable; urgency=low * New upstream release "Most regressions in 0.99 over 0.98 are now believed to be fixed. This release should be considered a release-candidate for a new stable series." + bgpd: Preliminary UI and Linux-IPv4 support for TCP-MD5 merged + zebra: ignore dead routes in RIB update + [ospfd] Default route needs to be refreshed after neighbour state change + [zebra:netlink] Set proto/scope on all route update messages * Removed debian/patches/20_*bgp*md5*.dpatch due to upstream support. -- Christian Hammers Thu, 09 Oct 2008 22:56:38 +0200 quagga (0.99.10-1) unstable; urgency=medium * New upstream release + bgpd: 4-Byte AS Number support + Sessions were incorrectly reset if a partial AS-Pathlimit attribute was received. + Advertisement of Multi-Protocol prefixes (i.e. non-IPv4) had been broken in the 0.99.9 release. Closes: #467656 -- Christian Hammers Tue, 08 Jul 2008 23:32:42 +0200 quagga (0.99.9-6) unstable; urgency=low * Fixed FTBFS by adding a build-dep to libpcre3-dev (thanks to Luk Claes). Closes: #469891 -- Christian Hammers Sat, 12 Apr 2008 12:53:51 +0200 quagga (0.99.9-5) unstable; urgency=low * C.J. Adams-Collier and Paul Jakma suggested to build against libpcre3 which is supposed to be faster. -- Christian Hammers Sun, 02 Mar 2008 13:19:42 +0100 quagga (0.99.9-4) unstable; urgency=low * Added hardening-wrapper to the build-deps (thanks to Moritz Muehlenhoff). -- Christian Hammers Tue, 29 Jan 2008 22:33:56 +0100 quagga (0.99.9-3) unstable; urgency=low * Replaced the BGP patch by a new one so that the package builds again with kernels above 2.6.21! * debian/control: + Moved quagga-doc to section doc to make lintian happy. * Added Spanish debconf translation (thanks to Carlos Galisteo de Cabo). Closes: #428574 * debian/control: (thanks to Marco Rodrigues) + Bump Standards-Version to 3.7.3 (no changes needed). + Add Homepage field. -- Christian Hammers Mon, 28 Jan 2008 22:29:18 +0100 quagga (0.99.9-2.1) unstable; urgency=low * Non-maintainer upload. * debian/rules: fixed bashisms. (Closes: #459122) -- Miguel Angel Ruiz Manzano Tue, 22 Jan 2008 14:37:21 -0300 quagga (0.99.9-2) unstable; urgency=low * Added CVE id for the security bug to the last changelog entry. Closes: 442133 -- Christian Hammers Tue, 25 Sep 2007 22:01:31 +0200 quagga (0.99.9-1) unstable; urgency=high * SECURITY: "This release fixes two potential DoS conditions in bgpd, reported by Mu Security, where a bgpd could be crashed if a peer sent a malformed OPEN message or a malformed COMMUNITY attribute. Only configured peers can do this, hence we consider these issues to be very low impact." CVE-2007-4826 -- Christian Hammers Wed, 12 Sep 2007 21:12:41 +0200 quagga (0.99.8-1) unstable; urgency=low * New upstream version. -- Christian Hammers Fri, 17 Aug 2007 00:07:04 +0200 quagga (0.99.7-3) unstable; urgency=medium * Applied patch for FTBFS with linux-libc-dev (thanks to Andrew J. Schorr and Lucas Nussbaum). Closes: #429003 -- Christian Hammers Fri, 22 Jun 2007 21:34:55 +0200 quagga (0.99.7-2) unstable; urgency=low * Added Florian Weimar as co-maintainer. Closes: 421977 * Added Dutch debconf translation (thanks to Bart Cornelis). Closes: #420932 * Added Portuguese debconf translation (thanks to Rui Branco). Closes: #421185 * Improved package description (thanks to Reuben Thomas). Closes: #418933 * Added CVE Id to 0.99.6-5 changelog entry. -- Christian Hammers Wed, 02 May 2007 20:27:12 +0200 quagga (0.99.7-1) unstable; urgency=low * New upstream release. Closes: #421553 -- Christian Hammers Mon, 30 Apr 2007 14:22:34 +0200 quagga (0.99.6-6) unstable; urgency=medium * Fixes FTBFS with tetex-live. Closes: #420468 -- Christian Hammers Mon, 23 Apr 2007 21:34:13 +0200 quagga (0.99.6-5) unstable; urgency=high * SECURITY: The bgpd daemon was vulnerable to a Denial-of-Service. Configured peers could cause a Quagga bgpd to, typically, assert() and abort. The DoS could be triggered by peers by sending an UPDATE message with a crafted, malformed Multi-Protocol reachable/unreachable NLRI attribute. This is CVE-2007-1995 and Quagga Bug#354. Closes: #418323 -- Christian Hammers Thu, 12 Apr 2007 23:21:58 +0200 quagga (0.99.6-4) unstable; urgency=low * Improved note in README.Debian for SNMP self-builders (thanks to Matthias Wamser). Closes: #414788 -- Christian Hammers Wed, 14 Mar 2007 02:18:57 +0100 quagga (0.99.6-3) unstable; urgency=low * Updated German Debconf translation (thanks to Matthias Julius). Closes: #409327 -- Christian Hammers Sat, 10 Feb 2007 15:06:16 +0100 quagga (0.99.6-2) unstable; urgency=low * Updated config.guess/config.sub as suggested by lintian. * Corrected README.Debian text regarding the WANT_SNMP flag. -- Christian Hammers Sun, 17 Dec 2006 01:45:37 +0100 quagga (0.99.6-1) unstable; urgency=low * New upstream release. Closes: #402361 -- Christian Hammers Mon, 11 Dec 2006 00:28:09 +0100 quagga (0.99.5-5) unstable; urgency=high * Changed Depends on adduser to Pre-Depends to avoid uninstallability in certain cases (thanks to Steve Langasek, Lucas Nussbaum). Closes: #398562 -- Christian Hammers Wed, 15 Nov 2006 17:46:34 +0100 quagga (0.99.5-4) unstable; urgency=low * Added default PAM file and some explanations regarding PAM authentication of vtysh which could prevent the start at boot-time when used wrong. Now PAM permits anybody to access the vtysh tool (a malicious user could build his own vtysh without PAM anyway) and the access is controled by the read/write permissions of the vtysh socket which are only granted to users belonging to the quaggavty group (thanks to Wakko Warner). Closes: #389496 * Added "case" to prerm script so that the Debconf question is not called a second time in e.g. "new-prerm abort-upgrade" after being NACKed in the old-prerm. -- Christian Hammers Fri, 3 Nov 2006 01:22:15 +0100 quagga (0.99.5-3) unstable; urgency=medium * Backport CVS fix for an OSPF DD Exchange regression (thanks to Matt Brown). Closes: #391040 -- Christian Hammers Wed, 25 Oct 2006 19:47:11 +0200 quagga (0.99.5-2) unstable; urgency=medium * Added LSB info section to initscript. * Removed unnecessary depends to libncurses5 to make checklib happy. The one to libcap should remain though as it is just temporarily unused. -- Christian Hammers Thu, 21 Sep 2006 00:04:07 +0200 quagga (0.99.5-1) unstable; urgency=low * New upstream release. Closes: #38704 * Upstream fixes ospfd documentary inconsistency. Closes: #347897 * Changed debconf question in prerm to "high" (thanks to Rafal Pietrak). -- Christian Hammers Mon, 11 Sep 2006 23:43:42 +0200 quagga (0.99.4-4) unstable; urgency=low * Recreate /var/run if not present because /var is e.g. on a tmpfs filesystem (thanks to Martin Pitt). Closes: #376142 * Removed nonexistant option from ospfd.8 manpage (thanks to David Medberry). Closes: 378274 -- Christian Hammers Sat, 15 Jul 2006 20:22:12 +0200 quagga (0.99.4-3) unstable; urgency=low * Removed invalid semicolon from rules file (thanks to Philippe Gramoulle). -- Christian Hammers Tue, 27 Jun 2006 23:36:07 +0200 quagga (0.99.4-2) unstable; urgency=high * Set urgency to high as 0.99.4-1 fixes a security problem! * Fixed building of the info file. -- Christian Hammers Sun, 14 May 2006 23:04:28 +0200 quagga (0.99.4-1) unstable; urgency=low * New upstream release to fix a security problem in the telnet interface of the BGP daemon which could be used for DoS attacks (CVE-2006-2276). Closes: 366980 -- Christian Hammers Sat, 13 May 2006 19:54:40 +0200 quagga (0.99.3-3) unstable; urgency=low * Added CVE numbers for the security patch in 0.99.3-2. -- Christian Hammers Sat, 6 May 2006 17:14:22 +0200 quagga (0.99.3-2) unstable; urgency=high * SECURITY: Added security bugfix patch from upstream BTS for security problem that could lead to injected routes when using RIPv1. CVE-2006-2223 - missing configuration to disable RIPv1 or require plaintext or MD5 authentication CVE-2006-2224 - lack of enforcement of RIPv2 authentication requirements Closes: #365940 * First amd64 upload. -- Christian Hammers Thu, 4 May 2006 00:22:09 +0200 quagga (0.99.3-1) unstable; urgency=low * New upstream release -- Christian Hammers Wed, 25 Jan 2006 13:37:27 +0100 quagga (0.99.2-1) unstable; urgency=low * New upstream release Closes: #330248, #175553 -- Christian Hammers Wed, 16 Nov 2005 00:25:52 +0100 quagga (0.99.1-7) unstable; urgency=low * Changed debian/rules check for mounted /proc directory to check for /proc/1 as not all systems (e.g. 2.6 arm kernels) have /proc/kcore which is a optional feature only (thanks to Lennert Buytenhek). Closes: #335695 * Added Swedish Debconf translation (thanks to Daniel Nylander). Closes: #331367 -- Christian Hammers Thu, 27 Oct 2005 20:53:19 +0200 quagga (0.99.1-6) unstable; urgency=low * Fixed debconf dependency as requested by Joey Hess. -- Christian Hammers Mon, 26 Sep 2005 20:47:35 +0200 quagga (0.99.1-5) unstable; urgency=low * Rebuild with libreadline5-dev as build-dep as requested by Matthias Klose. Closes: #326306 * Made initscript more fault tolerant against missing lines in /etc/quagga/daemons (thanks to Ralf Hildebrandt). Closes: #323774 * Added dependency to adduser. -- Christian Hammers Tue, 13 Sep 2005 21:42:17 +0200 quagga (0.99.1-4) unstable; urgency=low * Added French Debconf translation (thanks to Mohammed Adnene Trojette). Closes: #319324 * Added Czech Debconf translation (thanks to Miroslav Kure). Closes: #318127 -- Christian Hammers Sun, 31 Jul 2005 04:19:41 +0200 quagga (0.99.1-3) unstable; urgency=low * A Debconf question now asks the admin before upgrading if the daemon should really be stopped as this could lead to the loss of network connectivity or BGP flaps (thanks to Michael Horn and Achilleas Kotsis). Also added a hint about setting Quagga "on hold" to README.Debian. Closes: #315467 * Added patch to build on Linux/ARM. -- Christian Hammers Sun, 10 Jul 2005 22:19:38 +0200 quagga (0.99.1-2) unstable; urgency=low * Fixed SNMP enabled command in debian/rules (thanks to Christoph Kluenter). Closes: #306840 -- Christian Hammers Sat, 4 Jun 2005 14:04:01 +0200 quagga (0.99.1-1) unstable; urgency=low * New upstream version. Among others: - BGP graceful restart and "match ip route-source" added - support for interface renaming - improved threading for better responsivness under load * Switched to dpatch to make diffs cleaner. * Made autoreconf unnecessary. * Replaced quagga.dvi and quagga.ps by quagga.pdf in quagga-doc. (the PostScript would have needed Makefile corrections and PDF is more preferable anyway) * Added isisd to the list of daemons in /etc/init.d/quagga (thanks to Ernesto Elbe). * Added hint for "netlink-listen: overrun" messages (thanks to Hasso Tepper). * Added preinst check that bails out if old smux options are in use as Quagga would not start up else anyway (thanks to Bjorn Mork). Closes: #308320 -- Christian Hammers Fri, 13 May 2005 01:18:24 +0200 quagga (0.98.3-7) unstable; urgency=high * Removed SNMP support as linking against NetSNMP introduced a dependency to OpenSSL which is not compatible to the GPL which governs this application (thanks to Faidon Liambotis). See README.Debian for more information. Closes: #306840 * Changed listening address of ospf6d and ripngd from 127.0.0.1 to "::1". * Added build-dep to groff to let drafz-zebra-00.txt build correctly. -- Christian Hammers Wed, 4 May 2005 20:08:14 +0200 quagga (0.98.3-6) testing-proposed-updates; urgency=high * Removed "Recommends kernel-image-2.4" as aptitude then installes a kernel-image for an arbitrary architecture as long as it fullfill that recommendation which can obviously fatal at the next reboot :) Also it is a violation of the policy which mandates a reference to real packages (thanks to Holger Levsen). Closes: #307281 -- Christian Hammers Tue, 3 May 2005 22:53:39 +0200 quagga (0.98.3-5) unstable; urgency=high * The patch which tried to remove the OpenSSL dependency, which is not only unneccessary but also a violation of the licence and thus RC, stopped working a while ago, since autoreconf is no longer run before building the binaries. So now ./configure is patched directly (thanks to Faidon Liambotis for reporting). Closes: #306840 * Raised Debhelper compatibility level from 3 to 4. Nothing changed. * Added build-dep to texinfo (>= 4.7) to ease work for www.backports.org. -- Christian Hammers Fri, 29 Apr 2005 02:31:03 +0200 quagga (0.98.3-4) unstable; urgency=low * Removed Debconf upgrade note as it was considered a Debconf abuse and apart from that so obvious that it was not even worth to be put into NEWS.Debian (thanks to Steve Langasek). Closes: #306384 -- Christian Hammers Wed, 27 Apr 2005 00:10:24 +0200 quagga (0.98.3-3) unstable; urgency=medium * Adding the debconf module due to a lintian suggestion is a very bad idea if no db_stop is called as the script hangs then (thanks to Tore Anderson for reporting). Closes: #306324 -- Christian Hammers Mon, 25 Apr 2005 21:55:58 +0200 quagga (0.98.3-2) unstable; urgency=low * Added debconf confmodule to postinst as lintian suggested. -- Christian Hammers Sun, 24 Apr 2005 13:16:00 +0200 quagga (0.98.3-1) unstable; urgency=low * New upstream release. Mmost notably fixes last regression in bgpd (reannounce of prefixes with changed attributes works again), race condition in netlink handling while using IPv6, MTU changes handling in ospfd and several crashes in ospfd, bgpd and ospf6d. -- Christian Hammers Mon, 4 Apr 2005 12:51:24 +0200 quagga (0.98.2-2) unstable; urgency=low * Added patch to let Quagga compile with gcc-4.0 (thanks to Andreas Jochens). Closes: #300949 -- Christian Hammers Fri, 25 Mar 2005 19:33:30 +0100 quagga (0.98.2-1) unstable; urgency=medium * Quoting the upstream announcement: The 0.98.1 release unfortunately was a brown paper bag release with respect to ospfd. [...] 0.98.2 has been released, with one crucial change to fix the unfortunate mistake in 0.98.1, which caused problems if ospfd became DR. * Note: the upstream tarball had a strange problem, apparently redhat.spec was twice in it? At least debuild gave a strange error message so I unpacked it by hand. No changes were made to the .orig.tar.gz! -- Christian Hammers Fri, 4 Feb 2005 01:31:36 +0100 quagga (0.98.1-1) unstable; urgency=medium * New upstream version "fixing a fatal OSPF + MD5 auth regression, and a non-fatal high-load regression in bgpd which were present in the 0.98.0 release." * Upstream version fixes bug in ospfd that could lead to crash when OSPF packages had a MTU > 1500. Closes: #290566 * Added notice regarding capability kernel support to README.Debian (thanks to Florian Weimer). Closes: #291509 * Changed permission setting in postinst script (thanks to Bastian Blank). Closes: #292690 -- Christian Hammers Tue, 1 Feb 2005 02:01:27 +0100 quagga (0.98.0-3) unstable; urgency=low * Fixed problem in init script. Closes: #290317 * Removed obsolete "smux peer enable" patch. -- Christian Hammers Fri, 14 Jan 2005 17:37:27 +0100 quagga (0.98.0-2) unstable; urgency=low * Updated broken TCP MD5 patch for BGP (thanks to John P. Looney for telling me). -- Christian Hammers Thu, 13 Jan 2005 02:03:54 +0100 quagga (0.98.0-1) unstable; urgency=low * New upstream release * Added kernel-image-2.6 as alternative to 2.4 to the recommends (thanks to Faidon Liambotis). Closes: #289530 -- Christian Hammers Mon, 10 Jan 2005 19:36:17 +0100 quagga (0.97.5-1) unstable; urgency=low * New upstream version. * Added Czech debconf translation (thanks to Miroslav Kure). Closes: #287293 * Added Brazilian debconf translation (thanks to Andre Luis Lopes). Closes: #279352 -- Christian Hammers Wed, 5 Jan 2005 23:49:57 +0100 quagga (0.97.4-2) unstable; urgency=low * Fixed quagga.info build problem. -- Christian Hammers Wed, 5 Jan 2005 22:38:01 +0100 quagga (0.97.4-1) unstable; urgency=low * New upstream release. -- Christian Hammers Tue, 4 Jan 2005 01:45:22 +0100 quagga (0.97.3-2) unstable; urgency=low * Included isisd in the daemon list. * Wrote an isisd manpage. * It is now ensured that zebra is always the last daemon to be stopped. * (Thanks to Hasso Tepper for mailing me a long list of suggestions which lead to this release) -- Christian Hammers Sat, 18 Dec 2004 13:14:55 +0100 quagga (0.97.3-1) unstable; urgency=medium * New upstream version. - Fixes important OSPF bug. * Added ht-20040911-smux.patch regarding Quagga bug #112. * Updated ht-20041109-0.97.3-bgp-md5.patch for BGP with TCP MD5 (thanks to Matthias Wamser). -- Christian Hammers Tue, 9 Nov 2004 17:45:26 +0100 quagga (0.97.2-4) unstable; urgency=low * Added Portuguese debconf translation (thanks to Andre Luis Lopes). Closes: #279352 * Disabled ospfapi server by default on recommendation of Paul Jakma. -- Christian Hammers Sun, 7 Nov 2004 15:07:05 +0100 quagga (0.97.2-3) unstable; urgency=low * Added Andrew Schorrs VTY Buffer patch from the [quagga-dev 1729]. -- Christian Hammers Tue, 2 Nov 2004 00:46:56 +0100 quagga (0.97.2-2) unstable; urgency=low * Changed file and directory permissions and ownerships according to a suggestion from Paul Jakma. Still not perfect though. * Fixed upstream vtysh.conf.sample file. * "ip ospf network broadcast" is now saved correctly. Closes: #244116 * Daemon options are now in /etc/quagga/debian.conf to be user configurable (thanks to Simon Raven and Hasso Tepper). Closes: #266715 -- Christian Hammers Tue, 26 Oct 2004 23:35:45 +0200 quagga (0.97.2-1) unstable; urgency=low * New upstream version. Closes: #254541 * Fixed warning on unmodular kernels (thanks to Christoph Biedl). Closes: #277973 -- Christian Hammers Mon, 25 Oct 2004 00:47:04 +0200 quagga (0.97.1-2) unstable; urgency=low * Version 0.97 introduced shared libraries. They are now included. (thanks to Raf D'Halleweyn). Closes: #277446 -- Christian Hammers Wed, 20 Oct 2004 15:32:06 +0200 quagga (0.97.1-1) unstable; urgency=low * New upstream version. * Removed some obsolete files from debian/patches. * Added patch from upstream bug 113. Closes: #254541 * Added patch from upstream that fixes a compilation problem in the ospfclient code (thanks to Hasso Tepper). * Updated German debconf translation (thanks to Jens Nachtigall) Closes: #277059 -- Christian Hammers Mon, 18 Oct 2004 01:16:35 +0200 quagga (0.96.5-11) unstable; urgency=low * Fixed /tmp/buildd/* paths in binaries. For some unknown reason the upstream Makefile modified a .h file at the end of the "debian/rules build" target. During the following "make install" one library got thus be re*compiled* - with /tmp/buildd paths as sysconfdir (thanks to Peder Chr. Norgaard). Closes: #274050 -- Christian Hammers Fri, 1 Oct 2004 01:21:02 +0200 quagga (0.96.5-10) unstable; urgency=medium * The BGP routing daemon might freeze on network disturbances when their peer is also a Quagga/Zebra router. Applied patch from http://bugzilla.quagga.net/show_bug.cgi?id=102 which has been confirmed by the upstream author. (thanks to Gunther Stammwitz) * Changed --enable-pam to --with-libpam (thanks to Hasso Tepper). Closes: #264562 * Added patch for vtysh (thanks to Hasso Tepper). Closes: #215919 -- Christian Hammers Mon, 9 Aug 2004 15:33:02 +0200 quagga (0.96.5-9) unstable; urgency=low * Rewrote the documentation chapter about SNMP support. Closes: #195653 * Added MPLS docs. -- Christian Hammers Thu, 29 Jul 2004 21:01:52 +0200 quagga (0.96.5-8) unstable; urgency=low * Adjusted a grep in the initscript to also match a modprobe message from older modutils packages (thanks to Faidon Paravoid). -- Christian Hammers Wed, 28 Jul 2004 21:19:02 +0200 quagga (0.96.5-7) unstable; urgency=low * Added a "cd /etc/quagga/" to the init script as quagga tries to load the config file first from the current working dir and then from the config dir which could lead to confusion (thanks to Marco d'Itri). Closes: #255078 * Removed warning regarding problems with the Debian kernels from README.Debian as they are no longer valid (thanks to Raphael Hertzog). Closes: #257580 * Added patch from Hasso Tepper that makes "terminal length 0" work in vtysh (thanks to Matthias Wamser). Closes: #252579 -- Christian Hammers Thu, 8 Jul 2004 21:53:21 +0200 quagga (0.96.5-6) unstable; urgency=low * Try to load the capability module as it is needed now. -- Christian Hammers Tue, 8 Jun 2004 23:25:29 +0200 quagga (0.96.5-5) unstable; urgency=low * Changed the homedir of the quagga user to /etc/quagga/ to allow admins to put ~/.ssh/authorized_keys there (thanks to Matthias Wamser). Closes: #252577 -- Christian Hammers Sat, 5 Jun 2004 14:47:31 +0200 quagga (0.96.5-4) unstable; urgency=medium * Fixed rules file to use the renamed ./configure option --enable-tcp-md5 (thanks to Matthias Wamser). Closes: #252141 -- Christian Hammers Tue, 1 Jun 2004 22:58:32 +0200 quagga (0.96.5-3) unstable; urgency=low * Provided default binary package name to all build depends that were virtual packages (thanks to Goswin von Brederlow). Closes: #251625 -- Christian Hammers Sat, 29 May 2004 22:48:53 +0200 quagga (0.96.5-2) unstable; urgency=low * New upstream version. * New md5 patch version (thanks to Niklas Jakobsson and Hasso Tepper). Closes: #250985 * Fixes info file generation (thanks to Peder Chr. Norgaard). Closes: #250992 * Added catalan debconf translation (thanks to Aleix Badia i Bosch). Closes: #250118 * PATCHES: This release contains BGP4 MD5 support which requires a kernel patch to work. See /usr/share/doc/quagga/README.Debian.MD5. (The patch is ht-20040525-0.96.5-bgp-md5.patch from Hasso Tepper) -- Christian Hammers Thu, 27 May 2004 20:09:37 +0200 quagga (0.96.5-1) unstable; urgency=low * New upstream version. * PATCHES: This release contains BGP4 MD5 support which also requires a kernel patch. See /usr/share/doc/quagga/README.Debian.MD5 and search for CAN-2004-0230. -- Christian Hammers Sun, 16 May 2004 17:40:40 +0200 quagga (0.96.4x-10) unstable; urgency=low * SECURITY: This release contains support for MD5 for BGP which is one suggested prevention of the actually long known TCP SYN/RST attacks which got much news in the last days as ideas were revealed that made them much easier probable agains especially the BGP sessions than commonly known. There are a lot of arguments agains the MD5 approach but some ISPs started to require it. See: CAN-2004-0230, http://www.us-cert.gov/cas/techalerts/TA04-111A.html * PATCHES: This release contains the MD5 patch from Hasso Tepper. It also seems to required a kernel patch. See /usr/share/doc/quagga/README.Debian.MD5. -- Christian Hammers Thu, 29 Apr 2004 01:01:38 +0200 quagga (0.96.4x-9) unstable; urgency=low * Fixed daemon loading order (thanks to Matt Kemner). * Fixed typo in init script (thanks to Charlie Brett). Closes: #238582 -- Christian Hammers Sun, 4 Apr 2004 15:32:18 +0200 quagga (0.96.4x-8) unstable; urgency=low * Patched upstream source so that quagga header files end up in /usr/include/quagga/. Closes: #233792 -- Christian Hammers Mon, 23 Feb 2004 01:42:53 +0100 quagga (0.96.4x-7) unstable; urgency=low * Fixed info file installation (thanks to Holger Dietze). Closes: #227579 * Added Japanese translation (thanks to Hideki Yamane). Closes: #227812 -- Christian Hammers Sun, 18 Jan 2004 17:28:29 +0100 quagga (0.96.4x-6) unstable; urgency=low * Added dependency to iproute. * Initscript now checks not only for the pid file but also for the daemons presence (thanks to Phil Gregory). Closes: #224389 * Added my patch to configure file permissions. -- Christian Hammers Mon, 15 Dec 2003 22:34:29 +0100 quagga (0.96.4x-5) unstable; urgency=low * Added patch which gives bgpd the CAP_NET_RAW capability to allow it to bind to special IPv6 link-local interfaces (Thanks to Bastian Blank). Closes: #222930 * Made woody backport easier by applying Colin Watsons po-debconf hack. Thanks to Marc Haber for suggesting it. Closes: #223527 * Made woody backport easier by applying a patch that removes some obscure whitespaces inside an C macro. (Thanks to Marc Haber). Closes: #223529 * Now uses /usr/bin/pager. Closes: #204070 * Added note about the "official woody backports" on my homepage. -- Christian Hammers Mon, 15 Dec 2003 20:39:06 +0100 quagga (0.96.4x-4) unstable; urgency=high * SECURITY: Fixes another bug that was originally reported against Zebra. . http://rhn.redhat.com/errata/RHSA-2003-307.html Herbert Xu reported that Zebra can accept spoofed messages sent on the kernel netlink interface by other users on the local machine. This could lead to a local denial of service attack. The Common Vulnerabilities and Exposures project (cve.mitre.org) has assigned the name CAN-2003-0858 to this issue. * Minor improvements to init script (thanks to Iustin Pop). Closes: #220938 -- Christian Hammers Sat, 22 Nov 2003 13:27:57 +0100 quagga (0.96.4x-3) unstable; urgency=low * Changed "more" to "/usr/bin/pager" as default pager if $PAGER or $VTYSH_PAGER is not set (thanks to Bastian Blank). Closes: #204070 * Made the directory (but not the config/log files!) world accessible again on user request (thanks to Anand Kumria)). Closes: #213129 * No longer providing sample configuration in /etc/quagga/. They are now only available in /usr/share/doc/quagga/ to avoid accidently using them without changing the adresses (thanks to Marc Haber). Closes: #215918 -- Christian Hammers Sun, 16 Nov 2003 16:59:30 +0100 quagga (0.96.4x-2) unstable; urgency=low * Fixed permission problem with pidfile (thanks to Kir Kostuchenko). Closes: #220938 -- Christian Hammers Sun, 16 Nov 2003 14:24:08 +0100 quagga (0.96.4x-1) unstable; urgency=low * Reupload of 0.96.4. Last upload-in-a-hurry produced a totally crappy .tar.gz file. Closes: #220621 -- Christian Hammers Fri, 14 Nov 2003 19:45:57 +0100 quagga (0.96.4-1) unstable; urgency=high * SECURITY: Remote DoS of protocol daemons. Fix for a remote triggerable crash in vty layer. The management ports ("telnet myrouter ospfd") should not be open to the internet! * New upstream version. - OSPF bugfixes. - Some improvements for bgp and rip. -- Christian Hammers Thu, 13 Nov 2003 11:52:27 +0100 quagga (0.96.3-3) unstable; urgency=low * Fixed pid file generation by substituting the daemons "-d" by the start-stop-daemon option "--background" (thanks to Micha Gaisser). Closes: #218103 -- Christian Hammers Wed, 29 Oct 2003 05:17:49 +0100 quagga (0.96.3-2) unstable; urgency=low * Readded GNOME-PRODUCT-ZEBRA-MIB. -- Christian Hammers Thu, 23 Oct 2003 06:17:03 +0200 quagga (0.96.3-1) unstable; urgency=medium * New upstream version. * Removed -u and -e in postrm due to problems with debhelper and userdel (thanks to Adam Majer and Jaakko Niemi). Closes: #216770 * Removed SNMP MIBs as they are now included in libsnmp-base (thanks to David Engel and Peter Gervai). Closes: #216138, #216086 * Fixed seq command in init script (thanks to Marc Haber). Closes: #215915 * Improved /proc check (thanks to Marc Haber). Closes: #212331 -- Christian Hammers Thu, 23 Oct 2003 03:42:02 +0200 quagga (0.96.2-9) unstable; urgency=medium * Removed /usr/share/info/dir.* which were accidently there and prevented the installation by dpkg (thanks to Simon Raven). Closes: #212614 * Reworded package description (thanks to Anand Kumria). Closes: #213125 * Added french debconf translation (thanks to Christian Perrier). Closes: #212803 -- Christian Hammers Tue, 7 Oct 2003 13:26:58 +0200 quagga (0.96.2-8) unstable; urgency=low * debian/rules now checks if /proc is mounted as ./configure needs it but just fails with an obscure error message if it is absent. (Thanks to Norbert Tretkowski). Closes: #212331 -- Christian Hammers Tue, 23 Sep 2003 12:57:38 +0200 quagga (0.96.2-7) unstable; urgency=low * Last build was rejected due to a buggy dpkg-dev version. Rebuild. -- Christian Hammers Mon, 22 Sep 2003 20:34:12 +0200 quagga (0.96.2-6) unstable; urgency=low * Fixed init script so that is is now possible to just start the bgpd but not the zebra daemon. Also daemons are now actually started in the order defined their priority. (Thanks to Thomas Kaehn and Jochen Friedrich) Closes: #210924 -- Christian Hammers Fri, 19 Sep 2003 21:17:02 +0200 quagga (0.96.2-5) unstable; urgency=low * For using quagga as BGP route server or similar, it is not wanted to have the zebra daemon running too. For this reason it can now be disabled in /etc/quagga/daemons, too. (Thanks to Jochen Friedrich). Closes: #210924 * Attached *unapplied* patch for the ISIS protocol. I did not dare to apply it as long as upstream does not do it but this way give users the possibilities to use it if they like to. (Thanks to Remco van Mook) -- Christian Hammers Wed, 17 Sep 2003 19:57:31 +0200 quagga (0.96.2-4) unstable; urgency=low * Enabled IPV6 router advertisement feature by default on user request (thanks to Jochen Friedrich and Hasso Tepper). Closes: #210732 * Updated GNU autoconf to let it build on hppa/parisc64 (thanks to lamont). Closes: #210492 -- Christian Hammers Sat, 13 Sep 2003 14:11:13 +0200 quagga (0.96.2-3) unstable; urgency=medium * Removed unnecessary "-lcrypto" to avoid dependency against OpenSSL which would require further copyright addtions. -- Christian Hammers Wed, 10 Sep 2003 01:37:28 +0200 quagga (0.96.2-2) unstable; urgency=low * Added note that config files of quagga are in /etc/quagga and not /etc/zebra for the zebra users that migrate to quagga. (Thanks to Roberto Suarez Soto for the idea) * Fixed setgid rights in /etc/quagga. -- Christian Hammers Wed, 27 Aug 2003 14:05:39 +0200 quagga (0.96.2-1) unstable; urgency=low * This package has formally been known as "zebra-pj"! * New upstream release. Fixes "anoying OSPF problem". * Modified group ownerships so that vtysh can now be used by normal uses if they are in the quaggavty group. -- Christian Hammers Mon, 25 Aug 2003 23:40:14 +0200 quagga (0.96.1-1) unstable; urgency=low * Zebra-pj, the fork of zebra has been renamed to quagga as the original upstream author asked the new project membed not to use "zebra" in the name. zebra-pj is obsolete. -- Christian Hammers Mon, 18 Aug 2003 23:37:20 +0200 zebra-pj (0.94+cvs20030721-1) unstable; urgency=low * New CVS build. - OSPF changes (integration of the OSPF API?) - code cleanups (for ipv6?) * Tightened Build-Deps to gcc-2.95 as 3.x does not compile a stable ospfd. This is a known problem and has been discussed on the mailing list. No other solutions so far. -- Christian Hammers Mon, 21 Jul 2003 23:52:00 +0200 zebra-pj (0.94+cvs20030701-1) unstable; urgency=low * Initial Release. -- Christian Hammers Tue, 1 Jul 2003 01:58:06 +0200 frr-7.2.1/changelog-auto.in0000644000000000000000000015242713610377563012454 00000000000000frr (@VERSION@-0) UNRELEASED; urgency=medium * autoconf changelog entry -- for git autobuilds only. remove and replace when creating releases! (tools/tarsource.sh will handle this) -- FRRouting-Dev Fri, 17 Jan 2020 16:41:00 +0200 frr (7.2-1) testing; urgency=medium * upstream 7.2.1 release -- Martin Winter Fri, 17 Jan 2020 16:40:00 +0200 frr (7.2-0) testing; urgency=medium * upstream 7.2 release -- Martin Winter Tue, 17 Oct 2019 16:42:20 +0200 frr (7.1-0) testing; urgency=medium * upstream 7.1 release -- David Lamparter Tue, 18 Jun 2019 06:08:13 +0200 frr (6.0-2) testing; urgency=medium * add install-info to build deps * remove trailing whitespace from control * cleanup tcp-zebra configure options * drop unused SMUX client OID MIBs * remove /proc check * remove --enable-poll * remove libtool .la files * drop texlive-latex-base, texlive-generic-recommended build deps * consistently allow python2 or python3 * remove bad USE_* options, add WERROR * drop libncurses5 dep * remove backports mechanism * use better dependency for pythontools (binNMU compatible) * remove bogus shlib:Depends on frr-dbg * create frr-snmp and frr-rpki-rtrlib * make frr-pythontools a "Recommends:" * use redistclean target * update to Debian Policy version 4.2.1 * raise debhelper compat level to 9 * ditch development-only files * modernise dh_missing and use fail mode * disable zeromq and FPM * always install /etc/init.d/frr * put frr-doc package in 'doc' section * install HTML docs, drop tools/ * fix install for {frr,rfptest,ospfclient} * add watch file * change python dependency and shebang to python3:any * use set -e in maintscripts * put myself in as maintainer * update copyright file * closes: #863249 -- David Lamparter Thu, 25 Oct 2018 16:36:50 +0200 frr (6.0-1) RELEASED; urgency=medium * New Enabled: PIM draft Unnumbered -- FRRouting-Dev Wed, 18 Oct 2017 17:01:42 -0700 frr (3.0-1) RELEASED; urgency=medium * Added Debian 9 Backport -- FRRouting-Dev Mon, 16 Oct 2017 03:28:00 -0700 frr (3.0-0) RELEASED; urgency=medium * New Enabled: BGP Shutdown Message * New Enabled: BGP Large Community * New Enabled: BGP RFC 7432 Partial Support w/ Ethernet VPN * New Enabled: BGP EVPN RT-5 * New Enabled: LDP RFC 5561 * New Enabled: LDP RFC 5918 * New Enabled: LDP RFC 5919 * New Enabled: LDP RFC 6667 * New Enabled: LDP RFC 7473 * New Enabled: OSPF RFC 4552 * New Enabled: ISIS SPF Backoff draft * New Enabled: PIM Unnumbered Interfaces * New Enabled: PIM RFC 4611 * New Enabled: PIM Sparse Mode * New Enabled: NHRP RFC 2332 * New Enabled: Label Manager * Switched from hardening-wrapper to dpkg-buildflags. -- FRRouting-Dev Fri, 13 Oct 2017 16:17:26 -0700 frr (2.0-0) RELEASED; urgency=medium * Switchover to FRR -- FRRouting-Dev Mon, 23 Jan 2017 16:30:22 -0400 quagga (0.99.24+cl3u5) RELEASED; urgency=medium * Closes: CM-12846 - Resolve Memory leaks in 'show ip bgp neighbor json' * Closes: CM-5878 - Display all ospf peers with 'show ip ospf neighbor detail all' * Closes: CM-5794 - Add support for IPv6 static to null0 * Closes: CM-13060 - Reduce JSON memory usage. * Closes: CM-10394 - protect 'could not get instance' error messages with debug * Closes: CM-11173 - Move netlink error messages undeer a debug * Closes: CM-13328 - Fixes route missing in hardware after reboot -- dev-support Fri, 11 Nov 2016 22:13:29 -0400 quagga (0.99.24+cl3u4) RELEASED; urgency=medium * Closes: CM-12687 - Buffer overflow in zebra RA code -- dev-support Wed, 31 Aug 2016 12:36:10 -0400 quagga (0.99.24+cl3u3) RELEASED; urgency=medium * New Enabled: Merge up-to 0.99.24 code from upstream * New Enabled: Additional CLI simplification * New Enabled: Various Bug Fixes -- dev-support Thu, 04 Aug 2016 08:43:36 -0700 quagga (0.99.23.1-1+cl3u2) RELEASED; urgency=medium * New Enabled: VRF - See Documentation for how to use * New Enabled: Improved interface statistics * New Enabled: Various vtysh improvements * New Enabled: Numerous compile warnings and SA fixes * New Enabled: Improved priviledge handlingA * New Enabled: Various OSPF CLI fixes * New Enabled: Prefix-list Performance Improvements. * New Enabled: Allow more than 1k peers in Quagga and Performance Improvements * New Enabled: Systemd integration * New Enabled: Various ISIS fixes * New Enabled: BGP MRT improvements * New Enabled: Lowered default MRAI timers * New Enabled: Lowered default 'timers connect' * New Enabled: 'bgp log-neighbor-changes' enabled by default * New Enabled: BGP default keepalive to 3s and holdtime to 9s * New Enabled: OSPF spf timers are now '0 50 5000' by default * New Enabled: BGP hostname is displayed by default * New Enabled: BGP 'no-as-set' is the default for 'bgp as-path multipath-relax" * New Enabled: RA is on by default if using 5549 on an interface * New Enabled: peer-group restrictions relaxed, update-groups determine outbund policy anyway * New Enabled: BGP enabled 'maximum-paths 64' by default * New Enabled: OSPF "log-adjacency-changes" on by default * New Enabled: Zebra: Add IPv6 protocol filtering support * and setting src of IPv6 routes. * New Enabled: BGP and OSPF JSON commands added. * New Enabled: BGP Enable multiple instances support by default * New Enabled: 'banner motd file' command * New Enabled: Remove bad default passwords from default conf * New Enabled: BGP addpath TX * New Enabled: Simplified configuration for BGP Unnumbered * New Deprecated: Remove unused 'show memory XXX' functionality * New Deprecated: Remove babel protocol * Closes: CM-10435 Addition on hidden command "bfd multihop/singlehop" and "ptm-enable" per interface command * Closes: CM-9974 Get route counts right for show ip route summary * Closes: CM-9786 BGP memory leak in peer hostname * Closes: CM-9340 BGP: Ensure correct sequence of processing at exit * Closes: CM-9270 ripd: Fix crash when a default route is passed to rip * Closes: CM-9255 BGPD crash around bgp_config_write () * Closes: CM-9134 ospf6d: Fix for crash when non area 0 network entered first * Closes: CM-8934 OSPFv3: Check area before scheduling SPF * Closes: CM-8514 zebra: Crash upon disabling a link * Closes: CM-8295 BGP crash in group_announce_route_walkcb * Closes: CM-8191 BGP: crash in update_subgroup_merge() * Closes: CM-8015 lib: Memory reporting fails over 2GB * Closes: CM-7926 BGP: crash from not NULLing freed pointers -- dev-support Wed, 04 May 2016 16:22:52 -0700 quagga (0.99.23.1-1) unstable; urgency=medium * New upstream release * Added .png figures for info files to quagga-doc package. * Changed dependency from iproute to iproute2 (thanks to Andreas Henriksson). Closes: #753736 * Added texlive-fonts-recommended to build-depends to get ecrm1095 font (thanks to Christoph Biedl). Closes: #651545 -- Christian Brunotte Tue, 30 Sep 2014 00:20:12 +0200 quagga (0.99.23-1) unstable; urgency=low * New upstream release * Removed debian/patches/readline-6.3.diff which was already in upstream. -- Christian Hammers Tue, 08 Jul 2014 09:15:48 +0200 quagga (0.99.22.4-4) unstable; urgency=medium * Fix build failure with readline-6.3 (thanks to Matthias Klose). Closes: #741774 -- Christian Hammers Sun, 23 Mar 2014 15:28:42 +0100 quagga (0.99.22.4-3) unstable; urgency=low * Added status to init script (thanks to Peter J. Holzer). Closes: #730625 * Init script now sources /lib/lsb/init-functions. * Switched from hardening-wrapper to dpkg-buildflags. -- Christian Hammers Wed, 01 Jan 2014 19:12:01 +0100 quagga (0.99.22.4-2) unstable; urgency=low * Fixed typo in package description (thanks to Davide Prina). Closes: #625860 * Added Italian Debconf translation (thanks to Beatrice Torracca) Closes: #729798 -- Christian Hammers Tue, 26 Nov 2013 00:47:11 +0100 quagga (0.99.22.4-1) unstable; urgency=high * SECURITY: "ospfd: CVE-2013-2236, stack overrun in apiserver the OSPF API-server (exporting the LSDB and allowing announcement of Opaque-LSAs) writes past the end of fixed on-stack buffers. This leads to an exploitable stack overflow. For this condition to occur, the following two conditions must be true: - Quagga is configured with --enable-opaque-lsa - ospfd is started with the "-a" command line option If either of these does not hold, the relevant code is not executed and the issue does not get triggered." Closes: #726724 * New upstream release - ospfd: protect vs. VU#229804 (malformed Router-LSA) (Quagga is said to be non-vulnerable but still adds some protection) -- Christian Hammers Thu, 24 Oct 2013 22:58:37 +0200 quagga (0.99.22.1-2) unstable; urgency=low * Added autopkgtests (thanks to Yolanda Robla). Closes: #710147 * Added "status" command to init script (thanks to James Andrewartha). Closes: #690013 * Added "libsnmp-dev" to Build-Deps. There not needed for the official builds but for people who compile Quagga themselves to activate the SNMP feature (which for licence reasons cannot be done by Debian). Thanks to Ben Winslow). Closes: #694852 * Changed watchquagga_options to an array so that quotes can finally be used as expected. Closes: #681088 * Fixed bug that prevented restarting only the watchquagga daemon (thanks to Harald Kappe). Closes: #687124 -- Christian Hammers Sat, 27 Jul 2013 16:06:25 +0200 quagga (0.99.22.1-1) unstable; urgency=low * New upstream release - ospfd restore nexthop IP for p2p interfaces - ospfd: fix LSA initialization for build without opaque LSA - ripd: correctly redistribute ifindex routes (BZ#664) - bgpd: fix lost passwords of grouped neighbors * Removed 91_ld_as_needed.diff as it was found in the upstream source. -- Christian Hammers Mon, 22 Apr 2013 22:21:20 +0200 quagga (0.99.22-1) unstable; urgency=low * New upstream release. - [bgpd] The semantics of default-originate route-map have changed. The route-map is now used to advertise the default route conditionally. The old behaviour which allowed to set attributes on the originated default route is no longer supported. - [bgpd] this version of bgpd implements draft-idr-error-handling. This was added in 0.99.21 and may not be desirable. If you need a version without this behaviour, please use 0.99.20.1. There will be a runtime configuration switch for this in future versions. - [isisd] is in "beta" state. - [ospf6d] is in "alpha/experimental" state - More changes are documented in the upstream changelog! * debian/watch: Adjusted to new savannah.gnu.org site, thanks to Bart Martens. * debian/patches/99_CVE-2012-1820_bgp_capability_orf.diff removed as its in the changelog. * debian/patches/99_distribute_list.diff removed as its in the changelog. * debian/patches/10_doc__Makefiles__makeinfo-force.diff removed as it was just for Debian woody. -- Christian Hammers Thu, 14 Feb 2013 00:22:00 +0100 quagga (0.99.21-4) unstable; urgency=medium * Fixed regression bug that caused OSPF "distribute-list" statements to be silently ignored. The patch has already been applied upstream but there has been no new Quagga release since then. Thanks to Hans van Kranenburg for reporting. Closes: #697240 -- Christian Hammers Sun, 06 Jan 2013 15:50:32 +0100 quagga (0.99.21-3) unstable; urgency=high * SECURITY: CVE-2012-1820 - Quagga contained a bug in BGP OPEN message handling. A denial-of-service condition could be caused by an attacker controlling one of the pre-configured BGP peers. In most cases this means, that the attack must be originated from an adjacent network. Closes: #676510 -- Christian Hammers Fri, 08 Jun 2012 01:15:32 +0200 quagga (0.99.21-2) unstable; urgency=low * Renamed babeld.8 to quagga-babeld.8 as it conflicted with the original mapage of the babeld package which users might want to install in parallel as it is slightly more capable. Closes: #671916 -- Christian Hammers Thu, 10 May 2012 07:53:01 +0200 quagga (0.99.21-1) unstable; urgency=low * New upstream release - [bgpd] BGP multipath support has been merged - [bgpd] SAFI (Multicast topology) support has been extended to propagate the topology to zebra. - [bgpd] AS path limit functionality has been removed - [babeld] a new routing daemon implementing the BABEL ad-hoc mesh routing protocol has been merged. - [isisd] a major overhaul has been picked up. Please note that isisd is STILL NOT SUITABLE FOR PRODUCTION USE. - a lot of bugs have been fixed * Added watchquagga daemon. * Added DEP-3 conforming patch comments. -- Christian Hammers Sun, 06 May 2012 15:33:33 +0200 quagga (0.99.20.1-1) unstable; urgency=high * SECURITY: CVE-2012-0249 - Quagga ospfd DoS on malformed LS-Update packet CVE-2012-0250 - Quagga ospfd DoS on malformed Network-LSA data CVE-2012-0255 - Quagga bgpd DoS on malformed OPEN message * New upstream release. Closes: #664033 -- Christian Hammers Fri, 16 Mar 2012 22:14:05 +0100 quagga (0.99.20-4) unstable; urgency=low * Switch to dpkg-source 3.0 (quilt) format. * Switch to changelog-format-1.0. -- Christian Hammers Sat, 25 Feb 2012 18:52:06 +0100 quagga (0.99.20-3) unstable; urgency=low * Added --sysconfdir back to the configure options (thanks to Sven-Haegar Koch). Closes: #645649 -- Christian Hammers Tue, 18 Oct 2011 00:24:37 +0200 quagga (0.99.20-2) unstable; urgency=low * Bumped standards version to 0.9.2. * Migrated to "dh" build system. * Added quagga-dbg package. -- Christian Hammers Fri, 14 Oct 2011 23:59:26 +0200 quagga (0.99.20-1) unstable; urgency=low * New upstream release: "The primary focus of this release is a fix of SEGV regression in ospfd, which was introduced in 0.99.19. It also features a series of minor improvements, including better RFC compliance in bgpd, better support of FreeBSD and some enhancements to isisd." * Fixes off-by-one bug (removed 20_ospf6_area_argv.dpatch). Closes: #519488 -- Christian Hammers Fri, 30 Sep 2011 00:59:24 +0200 quagga (0.99.19-1) unstable; urgency=high * SECURITY: "This release provides security fixes, which address assorted vulnerabilities in bgpd, ospfd and ospf6d (CVE-2011-3323, CVE-2011-3324, CVE-2011-3325, CVE-2011-3326 and CVE-2011-3327). * New upstream release. * Removed incorporated debian/patches/92_opaque_lsa_enable.dpatch. * Removed incorporated debian/patches/93_opaque_lsa_fix.dpatch. * Removed obsolete debian/README.Debian.Woody and README.Debian.MD5. -- Christian Hammers Tue, 27 Sep 2011 00:16:27 +0200 quagga (0.99.18-1) unstable; urgency=low * SECURITY: "This release fixes 2 denial of services in bgpd, which can be remotely triggered by malformed AS-Pathlimit or Extended-Community attributes. These issues have been assigned CVE-2010-1674 and CVE-2010-1675. Support for AS-Pathlimit has been removed with this release." * Added Brazilian Portuguese debconf translation. Closes: #617735 * Changed section for quagga-doc from "doc" to "net". * Added patch to fix FTBFS with latest GCC. Closes: #614459 -- Christian Hammers Tue, 22 Mar 2011 23:13:34 +0100 quagga (0.99.17-4) unstable; urgency=low * Added comment to init script (thanks to Marc Haber). Closes: #599524 -- Christian Hammers Thu, 13 Jan 2011 23:53:29 +0100 quagga (0.99.17-3) unstable; urgency=low * Fix FTBFS with ld --as-needed (thanks to Matthias Klose at Ubuntu). Closes: #609555 -- Christian Hammers Thu, 13 Jan 2011 23:27:06 +0100 quagga (0.99.17-2) unstable; urgency=low * Added Danisch Debconf translation (thanks to Joe Dalton). Closes: #596259 -- Christian Hammers Sat, 18 Sep 2010 12:20:07 +0200 quagga (0.99.17-1) unstable; urgency=high * SECURITY: "This release provides two important bugfixes, which address remote crash possibility in bgpd discovered by CROSS team.": 1. Stack buffer overflow by processing certain Route-Refresh messages CVE-2010-2948 2. DoS (crash) while processing certain BGP update AS path messages CVE-2010-2949 Closes: #594262 -- Christian Hammers Wed, 25 Aug 2010 00:52:48 +0200 quagga (0.99.16-1) unstable; urgency=low * New upstream release. Closes: #574527 * Added chrpath to debian/rules to fix rpath problems that lintian spottet. -- Christian Hammers Sun, 21 Mar 2010 17:05:40 +0100 quagga (0.99.15-2) unstable; urgency=low * Applied patch for off-by-one bug in ospf6d that caused a segmentation fault when using the "area a.b.c.d filter-list prefix" command (thanks to Steinar H. Gunderson). Closes: 519488 -- Christian Hammers Sun, 14 Feb 2010 20:02:03 +0100 quagga (0.99.15-1) unstable; urgency=low * New upstream release "This fixes some annoying little ospfd and ospf6d regressions, which made 0.99.14 a bit of a problem release (...) This release still contains a regression in the "no ip address ..." command, at least on Linux. See bug #486, which contains a workaround patch. This release should be considered a 1.0.0 release candidate. Please test this release as widely as possible." * Fixed wrong port number in zebra.8 (thanks to Thijs Kinkhorst). Closes: #517860 * Added Russian Debconf tanslation (thanks to Yuri Kozlov). Closes: #539464 * Removed so-version in build-dep to libreadline-dev on request of Matthias Klose. * Added README.source with reference to dpatch as suggested by lintian. * Bumped standards versionto 3.8.3. -- Christian Hammers Sun, 13 Sep 2009 18:12:06 +0200 quagga (0.99.14-1) unstable; urgency=low * New upstream release "This release contains a regression fix for ospf6d, various small fixes and some hopefully very significant bgpd stability fixes. This release should be considered a 1.0.0 release candidate. Please test this release as widely as possible." * Fixes bug with premature LSA aging in ospf6d. Closes: #535030 * Fixes section number in zebra.8 manpage. Closes: #517860 -- Christian Hammers Sat, 25 Jul 2009 00:40:38 +0200 quagga (0.99.13-2) unstable; urgency=low * Added Japanese Debconf translation (thanks to Hideki Yamane). Closes: #510714 * When checking for obsoleted config options in preinst, print filename where it occures (thanks to Michael Bussmann). Closes: #339489 -- Christian Hammers Sun, 19 Jul 2009 17:13:23 +0200 quagga (0.99.13-1) unstable; urgency=low * New upstream release "This release is contains a number of small fixes, for potentially irritating issues, as well as small enhancements to vtysh and support for linking to PCRE (a much faster regex library)." * Added build-dep to gawk as configure required it for memtypes.awk * Replaced build-dep to gs-gpl with ghostscript as requested by lintian * Minor changes to copyright and control files to make lintian happy. -- Christian Hammers Wed, 24 Jun 2009 17:53:28 +0200 quagga (0.99.12-1) unstable; urgency=high * New upstream release "This release fixes an urgent bug in bgpd where it could hit an assert if it received a long AS_PATH with a 4-byte ASN." Noteworthy bugfixes: + [bgpd] Fix bgp ipv4/ipv6 accept handling + [bgpd] AS4 bugfix by Chris Caputo + [bgpd] Allow accepted peers to progress even if realpeer is in Connect + [ospfd] Switch Fletcher checksum back to old ospfd version -- Christian Hammers Mon, 22 Jun 2009 00:16:33 +0200 quagga (0.99.11-1) unstable; urgency=low * New upstream release "Most regressions in 0.99 over 0.98 are now believed to be fixed. This release should be considered a release-candidate for a new stable series." + bgpd: Preliminary UI and Linux-IPv4 support for TCP-MD5 merged + zebra: ignore dead routes in RIB update + [ospfd] Default route needs to be refreshed after neighbour state change + [zebra:netlink] Set proto/scope on all route update messages * Removed debian/patches/20_*bgp*md5*.dpatch due to upstream support. -- Christian Hammers Thu, 09 Oct 2008 22:56:38 +0200 quagga (0.99.10-1) unstable; urgency=medium * New upstream release + bgpd: 4-Byte AS Number support + Sessions were incorrectly reset if a partial AS-Pathlimit attribute was received. + Advertisement of Multi-Protocol prefixes (i.e. non-IPv4) had been broken in the 0.99.9 release. Closes: #467656 -- Christian Hammers Tue, 08 Jul 2008 23:32:42 +0200 quagga (0.99.9-6) unstable; urgency=low * Fixed FTBFS by adding a build-dep to libpcre3-dev (thanks to Luk Claes). Closes: #469891 -- Christian Hammers Sat, 12 Apr 2008 12:53:51 +0200 quagga (0.99.9-5) unstable; urgency=low * C.J. Adams-Collier and Paul Jakma suggested to build against libpcre3 which is supposed to be faster. -- Christian Hammers Sun, 02 Mar 2008 13:19:42 +0100 quagga (0.99.9-4) unstable; urgency=low * Added hardening-wrapper to the build-deps (thanks to Moritz Muehlenhoff). -- Christian Hammers Tue, 29 Jan 2008 22:33:56 +0100 quagga (0.99.9-3) unstable; urgency=low * Replaced the BGP patch by a new one so that the package builds again with kernels above 2.6.21! * debian/control: + Moved quagga-doc to section doc to make lintian happy. * Added Spanish debconf translation (thanks to Carlos Galisteo de Cabo). Closes: #428574 * debian/control: (thanks to Marco Rodrigues) + Bump Standards-Version to 3.7.3 (no changes needed). + Add Homepage field. -- Christian Hammers Mon, 28 Jan 2008 22:29:18 +0100 quagga (0.99.9-2.1) unstable; urgency=low * Non-maintainer upload. * debian/rules: fixed bashisms. (Closes: #459122) -- Miguel Angel Ruiz Manzano Tue, 22 Jan 2008 14:37:21 -0300 quagga (0.99.9-2) unstable; urgency=low * Added CVE id for the security bug to the last changelog entry. Closes: 442133 -- Christian Hammers Tue, 25 Sep 2007 22:01:31 +0200 quagga (0.99.9-1) unstable; urgency=high * SECURITY: "This release fixes two potential DoS conditions in bgpd, reported by Mu Security, where a bgpd could be crashed if a peer sent a malformed OPEN message or a malformed COMMUNITY attribute. Only configured peers can do this, hence we consider these issues to be very low impact." CVE-2007-4826 -- Christian Hammers Wed, 12 Sep 2007 21:12:41 +0200 quagga (0.99.8-1) unstable; urgency=low * New upstream version. -- Christian Hammers Fri, 17 Aug 2007 00:07:04 +0200 quagga (0.99.7-3) unstable; urgency=medium * Applied patch for FTBFS with linux-libc-dev (thanks to Andrew J. Schorr and Lucas Nussbaum). Closes: #429003 -- Christian Hammers Fri, 22 Jun 2007 21:34:55 +0200 quagga (0.99.7-2) unstable; urgency=low * Added Florian Weimar as co-maintainer. Closes: 421977 * Added Dutch debconf translation (thanks to Bart Cornelis). Closes: #420932 * Added Portuguese debconf translation (thanks to Rui Branco). Closes: #421185 * Improved package description (thanks to Reuben Thomas). Closes: #418933 * Added CVE Id to 0.99.6-5 changelog entry. -- Christian Hammers Wed, 02 May 2007 20:27:12 +0200 quagga (0.99.7-1) unstable; urgency=low * New upstream release. Closes: #421553 -- Christian Hammers Mon, 30 Apr 2007 14:22:34 +0200 quagga (0.99.6-6) unstable; urgency=medium * Fixes FTBFS with tetex-live. Closes: #420468 -- Christian Hammers Mon, 23 Apr 2007 21:34:13 +0200 quagga (0.99.6-5) unstable; urgency=high * SECURITY: The bgpd daemon was vulnerable to a Denial-of-Service. Configured peers could cause a Quagga bgpd to, typically, assert() and abort. The DoS could be triggered by peers by sending an UPDATE message with a crafted, malformed Multi-Protocol reachable/unreachable NLRI attribute. This is CVE-2007-1995 and Quagga Bug#354. Closes: #418323 -- Christian Hammers Thu, 12 Apr 2007 23:21:58 +0200 quagga (0.99.6-4) unstable; urgency=low * Improved note in README.Debian for SNMP self-builders (thanks to Matthias Wamser). Closes: #414788 -- Christian Hammers Wed, 14 Mar 2007 02:18:57 +0100 quagga (0.99.6-3) unstable; urgency=low * Updated German Debconf translation (thanks to Matthias Julius). Closes: #409327 -- Christian Hammers Sat, 10 Feb 2007 15:06:16 +0100 quagga (0.99.6-2) unstable; urgency=low * Updated config.guess/config.sub as suggested by lintian. * Corrected README.Debian text regarding the WANT_SNMP flag. -- Christian Hammers Sun, 17 Dec 2006 01:45:37 +0100 quagga (0.99.6-1) unstable; urgency=low * New upstream release. Closes: #402361 -- Christian Hammers Mon, 11 Dec 2006 00:28:09 +0100 quagga (0.99.5-5) unstable; urgency=high * Changed Depends on adduser to Pre-Depends to avoid uninstallability in certain cases (thanks to Steve Langasek, Lucas Nussbaum). Closes: #398562 -- Christian Hammers Wed, 15 Nov 2006 17:46:34 +0100 quagga (0.99.5-4) unstable; urgency=low * Added default PAM file and some explanations regarding PAM authentication of vtysh which could prevent the start at boot-time when used wrong. Now PAM permits anybody to access the vtysh tool (a malicious user could build his own vtysh without PAM anyway) and the access is controled by the read/write permissions of the vtysh socket which are only granted to users belonging to the quaggavty group (thanks to Wakko Warner). Closes: #389496 * Added "case" to prerm script so that the Debconf question is not called a second time in e.g. "new-prerm abort-upgrade" after being NACKed in the old-prerm. -- Christian Hammers Fri, 3 Nov 2006 01:22:15 +0100 quagga (0.99.5-3) unstable; urgency=medium * Backport CVS fix for an OSPF DD Exchange regression (thanks to Matt Brown). Closes: #391040 -- Christian Hammers Wed, 25 Oct 2006 19:47:11 +0200 quagga (0.99.5-2) unstable; urgency=medium * Added LSB info section to initscript. * Removed unnecessary depends to libncurses5 to make checklib happy. The one to libcap should remain though as it is just temporarily unused. -- Christian Hammers Thu, 21 Sep 2006 00:04:07 +0200 quagga (0.99.5-1) unstable; urgency=low * New upstream release. Closes: #38704 * Upstream fixes ospfd documentary inconsistency. Closes: #347897 * Changed debconf question in prerm to "high" (thanks to Rafal Pietrak). -- Christian Hammers Mon, 11 Sep 2006 23:43:42 +0200 quagga (0.99.4-4) unstable; urgency=low * Recreate /var/run if not present because /var is e.g. on a tmpfs filesystem (thanks to Martin Pitt). Closes: #376142 * Removed nonexistant option from ospfd.8 manpage (thanks to David Medberry). Closes: 378274 -- Christian Hammers Sat, 15 Jul 2006 20:22:12 +0200 quagga (0.99.4-3) unstable; urgency=low * Removed invalid semicolon from rules file (thanks to Philippe Gramoulle). -- Christian Hammers Tue, 27 Jun 2006 23:36:07 +0200 quagga (0.99.4-2) unstable; urgency=high * Set urgency to high as 0.99.4-1 fixes a security problem! * Fixed building of the info file. -- Christian Hammers Sun, 14 May 2006 23:04:28 +0200 quagga (0.99.4-1) unstable; urgency=low * New upstream release to fix a security problem in the telnet interface of the BGP daemon which could be used for DoS attacks (CVE-2006-2276). Closes: 366980 -- Christian Hammers Sat, 13 May 2006 19:54:40 +0200 quagga (0.99.3-3) unstable; urgency=low * Added CVE numbers for the security patch in 0.99.3-2. -- Christian Hammers Sat, 6 May 2006 17:14:22 +0200 quagga (0.99.3-2) unstable; urgency=high * SECURITY: Added security bugfix patch from upstream BTS for security problem that could lead to injected routes when using RIPv1. CVE-2006-2223 - missing configuration to disable RIPv1 or require plaintext or MD5 authentication CVE-2006-2224 - lack of enforcement of RIPv2 authentication requirements Closes: #365940 * First amd64 upload. -- Christian Hammers Thu, 4 May 2006 00:22:09 +0200 quagga (0.99.3-1) unstable; urgency=low * New upstream release -- Christian Hammers Wed, 25 Jan 2006 13:37:27 +0100 quagga (0.99.2-1) unstable; urgency=low * New upstream release Closes: #330248, #175553 -- Christian Hammers Wed, 16 Nov 2005 00:25:52 +0100 quagga (0.99.1-7) unstable; urgency=low * Changed debian/rules check for mounted /proc directory to check for /proc/1 as not all systems (e.g. 2.6 arm kernels) have /proc/kcore which is a optional feature only (thanks to Lennert Buytenhek). Closes: #335695 * Added Swedish Debconf translation (thanks to Daniel Nylander). Closes: #331367 -- Christian Hammers Thu, 27 Oct 2005 20:53:19 +0200 quagga (0.99.1-6) unstable; urgency=low * Fixed debconf dependency as requested by Joey Hess. -- Christian Hammers Mon, 26 Sep 2005 20:47:35 +0200 quagga (0.99.1-5) unstable; urgency=low * Rebuild with libreadline5-dev as build-dep as requested by Matthias Klose. Closes: #326306 * Made initscript more fault tolerant against missing lines in /etc/quagga/daemons (thanks to Ralf Hildebrandt). Closes: #323774 * Added dependency to adduser. -- Christian Hammers Tue, 13 Sep 2005 21:42:17 +0200 quagga (0.99.1-4) unstable; urgency=low * Added French Debconf translation (thanks to Mohammed Adnene Trojette). Closes: #319324 * Added Czech Debconf translation (thanks to Miroslav Kure). Closes: #318127 -- Christian Hammers Sun, 31 Jul 2005 04:19:41 +0200 quagga (0.99.1-3) unstable; urgency=low * A Debconf question now asks the admin before upgrading if the daemon should really be stopped as this could lead to the loss of network connectivity or BGP flaps (thanks to Michael Horn and Achilleas Kotsis). Also added a hint about setting Quagga "on hold" to README.Debian. Closes: #315467 * Added patch to build on Linux/ARM. -- Christian Hammers Sun, 10 Jul 2005 22:19:38 +0200 quagga (0.99.1-2) unstable; urgency=low * Fixed SNMP enabled command in debian/rules (thanks to Christoph Kluenter). Closes: #306840 -- Christian Hammers Sat, 4 Jun 2005 14:04:01 +0200 quagga (0.99.1-1) unstable; urgency=low * New upstream version. Among others: - BGP graceful restart and "match ip route-source" added - support for interface renaming - improved threading for better responsivness under load * Switched to dpatch to make diffs cleaner. * Made autoreconf unnecessary. * Replaced quagga.dvi and quagga.ps by quagga.pdf in quagga-doc. (the PostScript would have needed Makefile corrections and PDF is more preferable anyway) * Added isisd to the list of daemons in /etc/init.d/quagga (thanks to Ernesto Elbe). * Added hint for "netlink-listen: overrun" messages (thanks to Hasso Tepper). * Added preinst check that bails out if old smux options are in use as Quagga would not start up else anyway (thanks to Bjorn Mork). Closes: #308320 -- Christian Hammers Fri, 13 May 2005 01:18:24 +0200 quagga (0.98.3-7) unstable; urgency=high * Removed SNMP support as linking against NetSNMP introduced a dependency to OpenSSL which is not compatible to the GPL which governs this application (thanks to Faidon Liambotis). See README.Debian for more information. Closes: #306840 * Changed listening address of ospf6d and ripngd from 127.0.0.1 to "::1". * Added build-dep to groff to let drafz-zebra-00.txt build correctly. -- Christian Hammers Wed, 4 May 2005 20:08:14 +0200 quagga (0.98.3-6) testing-proposed-updates; urgency=high * Removed "Recommends kernel-image-2.4" as aptitude then installes a kernel-image for an arbitrary architecture as long as it fullfill that recommendation which can obviously fatal at the next reboot :) Also it is a violation of the policy which mandates a reference to real packages (thanks to Holger Levsen). Closes: #307281 -- Christian Hammers Tue, 3 May 2005 22:53:39 +0200 quagga (0.98.3-5) unstable; urgency=high * The patch which tried to remove the OpenSSL dependency, which is not only unneccessary but also a violation of the licence and thus RC, stopped working a while ago, since autoreconf is no longer run before building the binaries. So now ./configure is patched directly (thanks to Faidon Liambotis for reporting). Closes: #306840 * Raised Debhelper compatibility level from 3 to 4. Nothing changed. * Added build-dep to texinfo (>= 4.7) to ease work for www.backports.org. -- Christian Hammers Fri, 29 Apr 2005 02:31:03 +0200 quagga (0.98.3-4) unstable; urgency=low * Removed Debconf upgrade note as it was considered a Debconf abuse and apart from that so obvious that it was not even worth to be put into NEWS.Debian (thanks to Steve Langasek). Closes: #306384 -- Christian Hammers Wed, 27 Apr 2005 00:10:24 +0200 quagga (0.98.3-3) unstable; urgency=medium * Adding the debconf module due to a lintian suggestion is a very bad idea if no db_stop is called as the script hangs then (thanks to Tore Anderson for reporting). Closes: #306324 -- Christian Hammers Mon, 25 Apr 2005 21:55:58 +0200 quagga (0.98.3-2) unstable; urgency=low * Added debconf confmodule to postinst as lintian suggested. -- Christian Hammers Sun, 24 Apr 2005 13:16:00 +0200 quagga (0.98.3-1) unstable; urgency=low * New upstream release. Mmost notably fixes last regression in bgpd (reannounce of prefixes with changed attributes works again), race condition in netlink handling while using IPv6, MTU changes handling in ospfd and several crashes in ospfd, bgpd and ospf6d. -- Christian Hammers Mon, 4 Apr 2005 12:51:24 +0200 quagga (0.98.2-2) unstable; urgency=low * Added patch to let Quagga compile with gcc-4.0 (thanks to Andreas Jochens). Closes: #300949 -- Christian Hammers Fri, 25 Mar 2005 19:33:30 +0100 quagga (0.98.2-1) unstable; urgency=medium * Quoting the upstream announcement: The 0.98.1 release unfortunately was a brown paper bag release with respect to ospfd. [...] 0.98.2 has been released, with one crucial change to fix the unfortunate mistake in 0.98.1, which caused problems if ospfd became DR. * Note: the upstream tarball had a strange problem, apparently redhat.spec was twice in it? At least debuild gave a strange error message so I unpacked it by hand. No changes were made to the .orig.tar.gz! -- Christian Hammers Fri, 4 Feb 2005 01:31:36 +0100 quagga (0.98.1-1) unstable; urgency=medium * New upstream version "fixing a fatal OSPF + MD5 auth regression, and a non-fatal high-load regression in bgpd which were present in the 0.98.0 release." * Upstream version fixes bug in ospfd that could lead to crash when OSPF packages had a MTU > 1500. Closes: #290566 * Added notice regarding capability kernel support to README.Debian (thanks to Florian Weimer). Closes: #291509 * Changed permission setting in postinst script (thanks to Bastian Blank). Closes: #292690 -- Christian Hammers Tue, 1 Feb 2005 02:01:27 +0100 quagga (0.98.0-3) unstable; urgency=low * Fixed problem in init script. Closes: #290317 * Removed obsolete "smux peer enable" patch. -- Christian Hammers Fri, 14 Jan 2005 17:37:27 +0100 quagga (0.98.0-2) unstable; urgency=low * Updated broken TCP MD5 patch for BGP (thanks to John P. Looney for telling me). -- Christian Hammers Thu, 13 Jan 2005 02:03:54 +0100 quagga (0.98.0-1) unstable; urgency=low * New upstream release * Added kernel-image-2.6 as alternative to 2.4 to the recommends (thanks to Faidon Liambotis). Closes: #289530 -- Christian Hammers Mon, 10 Jan 2005 19:36:17 +0100 quagga (0.97.5-1) unstable; urgency=low * New upstream version. * Added Czech debconf translation (thanks to Miroslav Kure). Closes: #287293 * Added Brazilian debconf translation (thanks to Andre Luis Lopes). Closes: #279352 -- Christian Hammers Wed, 5 Jan 2005 23:49:57 +0100 quagga (0.97.4-2) unstable; urgency=low * Fixed quagga.info build problem. -- Christian Hammers Wed, 5 Jan 2005 22:38:01 +0100 quagga (0.97.4-1) unstable; urgency=low * New upstream release. -- Christian Hammers Tue, 4 Jan 2005 01:45:22 +0100 quagga (0.97.3-2) unstable; urgency=low * Included isisd in the daemon list. * Wrote an isisd manpage. * It is now ensured that zebra is always the last daemon to be stopped. * (Thanks to Hasso Tepper for mailing me a long list of suggestions which lead to this release) -- Christian Hammers Sat, 18 Dec 2004 13:14:55 +0100 quagga (0.97.3-1) unstable; urgency=medium * New upstream version. - Fixes important OSPF bug. * Added ht-20040911-smux.patch regarding Quagga bug #112. * Updated ht-20041109-0.97.3-bgp-md5.patch for BGP with TCP MD5 (thanks to Matthias Wamser). -- Christian Hammers Tue, 9 Nov 2004 17:45:26 +0100 quagga (0.97.2-4) unstable; urgency=low * Added Portuguese debconf translation (thanks to Andre Luis Lopes). Closes: #279352 * Disabled ospfapi server by default on recommendation of Paul Jakma. -- Christian Hammers Sun, 7 Nov 2004 15:07:05 +0100 quagga (0.97.2-3) unstable; urgency=low * Added Andrew Schorrs VTY Buffer patch from the [quagga-dev 1729]. -- Christian Hammers Tue, 2 Nov 2004 00:46:56 +0100 quagga (0.97.2-2) unstable; urgency=low * Changed file and directory permissions and ownerships according to a suggestion from Paul Jakma. Still not perfect though. * Fixed upstream vtysh.conf.sample file. * "ip ospf network broadcast" is now saved correctly. Closes: #244116 * Daemon options are now in /etc/quagga/debian.conf to be user configurable (thanks to Simon Raven and Hasso Tepper). Closes: #266715 -- Christian Hammers Tue, 26 Oct 2004 23:35:45 +0200 quagga (0.97.2-1) unstable; urgency=low * New upstream version. Closes: #254541 * Fixed warning on unmodular kernels (thanks to Christoph Biedl). Closes: #277973 -- Christian Hammers Mon, 25 Oct 2004 00:47:04 +0200 quagga (0.97.1-2) unstable; urgency=low * Version 0.97 introduced shared libraries. They are now included. (thanks to Raf D'Halleweyn). Closes: #277446 -- Christian Hammers Wed, 20 Oct 2004 15:32:06 +0200 quagga (0.97.1-1) unstable; urgency=low * New upstream version. * Removed some obsolete files from debian/patches. * Added patch from upstream bug 113. Closes: #254541 * Added patch from upstream that fixes a compilation problem in the ospfclient code (thanks to Hasso Tepper). * Updated German debconf translation (thanks to Jens Nachtigall) Closes: #277059 -- Christian Hammers Mon, 18 Oct 2004 01:16:35 +0200 quagga (0.96.5-11) unstable; urgency=low * Fixed /tmp/buildd/* paths in binaries. For some unknown reason the upstream Makefile modified a .h file at the end of the "debian/rules build" target. During the following "make install" one library got thus be re*compiled* - with /tmp/buildd paths as sysconfdir (thanks to Peder Chr. Norgaard). Closes: #274050 -- Christian Hammers Fri, 1 Oct 2004 01:21:02 +0200 quagga (0.96.5-10) unstable; urgency=medium * The BGP routing daemon might freeze on network disturbances when their peer is also a Quagga/Zebra router. Applied patch from http://bugzilla.quagga.net/show_bug.cgi?id=102 which has been confirmed by the upstream author. (thanks to Gunther Stammwitz) * Changed --enable-pam to --with-libpam (thanks to Hasso Tepper). Closes: #264562 * Added patch for vtysh (thanks to Hasso Tepper). Closes: #215919 -- Christian Hammers Mon, 9 Aug 2004 15:33:02 +0200 quagga (0.96.5-9) unstable; urgency=low * Rewrote the documentation chapter about SNMP support. Closes: #195653 * Added MPLS docs. -- Christian Hammers Thu, 29 Jul 2004 21:01:52 +0200 quagga (0.96.5-8) unstable; urgency=low * Adjusted a grep in the initscript to also match a modprobe message from older modutils packages (thanks to Faidon Paravoid). -- Christian Hammers Wed, 28 Jul 2004 21:19:02 +0200 quagga (0.96.5-7) unstable; urgency=low * Added a "cd /etc/quagga/" to the init script as quagga tries to load the config file first from the current working dir and then from the config dir which could lead to confusion (thanks to Marco d'Itri). Closes: #255078 * Removed warning regarding problems with the Debian kernels from README.Debian as they are no longer valid (thanks to Raphael Hertzog). Closes: #257580 * Added patch from Hasso Tepper that makes "terminal length 0" work in vtysh (thanks to Matthias Wamser). Closes: #252579 -- Christian Hammers Thu, 8 Jul 2004 21:53:21 +0200 quagga (0.96.5-6) unstable; urgency=low * Try to load the capability module as it is needed now. -- Christian Hammers Tue, 8 Jun 2004 23:25:29 +0200 quagga (0.96.5-5) unstable; urgency=low * Changed the homedir of the quagga user to /etc/quagga/ to allow admins to put ~/.ssh/authorized_keys there (thanks to Matthias Wamser). Closes: #252577 -- Christian Hammers Sat, 5 Jun 2004 14:47:31 +0200 quagga (0.96.5-4) unstable; urgency=medium * Fixed rules file to use the renamed ./configure option --enable-tcp-md5 (thanks to Matthias Wamser). Closes: #252141 -- Christian Hammers Tue, 1 Jun 2004 22:58:32 +0200 quagga (0.96.5-3) unstable; urgency=low * Provided default binary package name to all build depends that were virtual packages (thanks to Goswin von Brederlow). Closes: #251625 -- Christian Hammers Sat, 29 May 2004 22:48:53 +0200 quagga (0.96.5-2) unstable; urgency=low * New upstream version. * New md5 patch version (thanks to Niklas Jakobsson and Hasso Tepper). Closes: #250985 * Fixes info file generation (thanks to Peder Chr. Norgaard). Closes: #250992 * Added catalan debconf translation (thanks to Aleix Badia i Bosch). Closes: #250118 * PATCHES: This release contains BGP4 MD5 support which requires a kernel patch to work. See /usr/share/doc/quagga/README.Debian.MD5. (The patch is ht-20040525-0.96.5-bgp-md5.patch from Hasso Tepper) -- Christian Hammers Thu, 27 May 2004 20:09:37 +0200 quagga (0.96.5-1) unstable; urgency=low * New upstream version. * PATCHES: This release contains BGP4 MD5 support which also requires a kernel patch. See /usr/share/doc/quagga/README.Debian.MD5 and search for CAN-2004-0230. -- Christian Hammers Sun, 16 May 2004 17:40:40 +0200 quagga (0.96.4x-10) unstable; urgency=low * SECURITY: This release contains support for MD5 for BGP which is one suggested prevention of the actually long known TCP SYN/RST attacks which got much news in the last days as ideas were revealed that made them much easier probable agains especially the BGP sessions than commonly known. There are a lot of arguments agains the MD5 approach but some ISPs started to require it. See: CAN-2004-0230, http://www.us-cert.gov/cas/techalerts/TA04-111A.html * PATCHES: This release contains the MD5 patch from Hasso Tepper. It also seems to required a kernel patch. See /usr/share/doc/quagga/README.Debian.MD5. -- Christian Hammers Thu, 29 Apr 2004 01:01:38 +0200 quagga (0.96.4x-9) unstable; urgency=low * Fixed daemon loading order (thanks to Matt Kemner). * Fixed typo in init script (thanks to Charlie Brett). Closes: #238582 -- Christian Hammers Sun, 4 Apr 2004 15:32:18 +0200 quagga (0.96.4x-8) unstable; urgency=low * Patched upstream source so that quagga header files end up in /usr/include/quagga/. Closes: #233792 -- Christian Hammers Mon, 23 Feb 2004 01:42:53 +0100 quagga (0.96.4x-7) unstable; urgency=low * Fixed info file installation (thanks to Holger Dietze). Closes: #227579 * Added Japanese translation (thanks to Hideki Yamane). Closes: #227812 -- Christian Hammers Sun, 18 Jan 2004 17:28:29 +0100 quagga (0.96.4x-6) unstable; urgency=low * Added dependency to iproute. * Initscript now checks not only for the pid file but also for the daemons presence (thanks to Phil Gregory). Closes: #224389 * Added my patch to configure file permissions. -- Christian Hammers Mon, 15 Dec 2003 22:34:29 +0100 quagga (0.96.4x-5) unstable; urgency=low * Added patch which gives bgpd the CAP_NET_RAW capability to allow it to bind to special IPv6 link-local interfaces (Thanks to Bastian Blank). Closes: #222930 * Made woody backport easier by applying Colin Watsons po-debconf hack. Thanks to Marc Haber for suggesting it. Closes: #223527 * Made woody backport easier by applying a patch that removes some obscure whitespaces inside an C macro. (Thanks to Marc Haber). Closes: #223529 * Now uses /usr/bin/pager. Closes: #204070 * Added note about the "official woody backports" on my homepage. -- Christian Hammers Mon, 15 Dec 2003 20:39:06 +0100 quagga (0.96.4x-4) unstable; urgency=high * SECURITY: Fixes another bug that was originally reported against Zebra. . http://rhn.redhat.com/errata/RHSA-2003-307.html Herbert Xu reported that Zebra can accept spoofed messages sent on the kernel netlink interface by other users on the local machine. This could lead to a local denial of service attack. The Common Vulnerabilities and Exposures project (cve.mitre.org) has assigned the name CAN-2003-0858 to this issue. * Minor improvements to init script (thanks to Iustin Pop). Closes: #220938 -- Christian Hammers Sat, 22 Nov 2003 13:27:57 +0100 quagga (0.96.4x-3) unstable; urgency=low * Changed "more" to "/usr/bin/pager" as default pager if $PAGER or $VTYSH_PAGER is not set (thanks to Bastian Blank). Closes: #204070 * Made the directory (but not the config/log files!) world accessible again on user request (thanks to Anand Kumria)). Closes: #213129 * No longer providing sample configuration in /etc/quagga/. They are now only available in /usr/share/doc/quagga/ to avoid accidently using them without changing the adresses (thanks to Marc Haber). Closes: #215918 -- Christian Hammers Sun, 16 Nov 2003 16:59:30 +0100 quagga (0.96.4x-2) unstable; urgency=low * Fixed permission problem with pidfile (thanks to Kir Kostuchenko). Closes: #220938 -- Christian Hammers Sun, 16 Nov 2003 14:24:08 +0100 quagga (0.96.4x-1) unstable; urgency=low * Reupload of 0.96.4. Last upload-in-a-hurry produced a totally crappy .tar.gz file. Closes: #220621 -- Christian Hammers Fri, 14 Nov 2003 19:45:57 +0100 quagga (0.96.4-1) unstable; urgency=high * SECURITY: Remote DoS of protocol daemons. Fix for a remote triggerable crash in vty layer. The management ports ("telnet myrouter ospfd") should not be open to the internet! * New upstream version. - OSPF bugfixes. - Some improvements for bgp and rip. -- Christian Hammers Thu, 13 Nov 2003 11:52:27 +0100 quagga (0.96.3-3) unstable; urgency=low * Fixed pid file generation by substituting the daemons "-d" by the start-stop-daemon option "--background" (thanks to Micha Gaisser). Closes: #218103 -- Christian Hammers Wed, 29 Oct 2003 05:17:49 +0100 quagga (0.96.3-2) unstable; urgency=low * Readded GNOME-PRODUCT-ZEBRA-MIB. -- Christian Hammers Thu, 23 Oct 2003 06:17:03 +0200 quagga (0.96.3-1) unstable; urgency=medium * New upstream version. * Removed -u and -e in postrm due to problems with debhelper and userdel (thanks to Adam Majer and Jaakko Niemi). Closes: #216770 * Removed SNMP MIBs as they are now included in libsnmp-base (thanks to David Engel and Peter Gervai). Closes: #216138, #216086 * Fixed seq command in init script (thanks to Marc Haber). Closes: #215915 * Improved /proc check (thanks to Marc Haber). Closes: #212331 -- Christian Hammers Thu, 23 Oct 2003 03:42:02 +0200 quagga (0.96.2-9) unstable; urgency=medium * Removed /usr/share/info/dir.* which were accidently there and prevented the installation by dpkg (thanks to Simon Raven). Closes: #212614 * Reworded package description (thanks to Anand Kumria). Closes: #213125 * Added french debconf translation (thanks to Christian Perrier). Closes: #212803 -- Christian Hammers Tue, 7 Oct 2003 13:26:58 +0200 quagga (0.96.2-8) unstable; urgency=low * debian/rules now checks if /proc is mounted as ./configure needs it but just fails with an obscure error message if it is absent. (Thanks to Norbert Tretkowski). Closes: #212331 -- Christian Hammers Tue, 23 Sep 2003 12:57:38 +0200 quagga (0.96.2-7) unstable; urgency=low * Last build was rejected due to a buggy dpkg-dev version. Rebuild. -- Christian Hammers Mon, 22 Sep 2003 20:34:12 +0200 quagga (0.96.2-6) unstable; urgency=low * Fixed init script so that is is now possible to just start the bgpd but not the zebra daemon. Also daemons are now actually started in the order defined their priority. (Thanks to Thomas Kaehn and Jochen Friedrich) Closes: #210924 -- Christian Hammers Fri, 19 Sep 2003 21:17:02 +0200 quagga (0.96.2-5) unstable; urgency=low * For using quagga as BGP route server or similar, it is not wanted to have the zebra daemon running too. For this reason it can now be disabled in /etc/quagga/daemons, too. (Thanks to Jochen Friedrich). Closes: #210924 * Attached *unapplied* patch for the ISIS protocol. I did not dare to apply it as long as upstream does not do it but this way give users the possibilities to use it if they like to. (Thanks to Remco van Mook) -- Christian Hammers Wed, 17 Sep 2003 19:57:31 +0200 quagga (0.96.2-4) unstable; urgency=low * Enabled IPV6 router advertisement feature by default on user request (thanks to Jochen Friedrich and Hasso Tepper). Closes: #210732 * Updated GNU autoconf to let it build on hppa/parisc64 (thanks to lamont). Closes: #210492 -- Christian Hammers Sat, 13 Sep 2003 14:11:13 +0200 quagga (0.96.2-3) unstable; urgency=medium * Removed unnecessary "-lcrypto" to avoid dependency against OpenSSL which would require further copyright addtions. -- Christian Hammers Wed, 10 Sep 2003 01:37:28 +0200 quagga (0.96.2-2) unstable; urgency=low * Added note that config files of quagga are in /etc/quagga and not /etc/zebra for the zebra users that migrate to quagga. (Thanks to Roberto Suarez Soto for the idea) * Fixed setgid rights in /etc/quagga. -- Christian Hammers Wed, 27 Aug 2003 14:05:39 +0200 quagga (0.96.2-1) unstable; urgency=low * This package has formally been known as "zebra-pj"! * New upstream release. Fixes "anoying OSPF problem". * Modified group ownerships so that vtysh can now be used by normal uses if they are in the quaggavty group. -- Christian Hammers Mon, 25 Aug 2003 23:40:14 +0200 quagga (0.96.1-1) unstable; urgency=low * Zebra-pj, the fork of zebra has been renamed to quagga as the original upstream author asked the new project membed not to use "zebra" in the name. zebra-pj is obsolete. -- Christian Hammers Mon, 18 Aug 2003 23:37:20 +0200 zebra-pj (0.94+cvs20030721-1) unstable; urgency=low * New CVS build. - OSPF changes (integration of the OSPF API?) - code cleanups (for ipv6?) * Tightened Build-Deps to gcc-2.95 as 3.x does not compile a stable ospfd. This is a known problem and has been discussed on the mailing list. No other solutions so far. -- Christian Hammers Mon, 21 Jul 2003 23:52:00 +0200 zebra-pj (0.94+cvs20030701-1) unstable; urgency=low * Initial Release. -- Christian Hammers Tue, 1 Jul 2003 01:58:06 +0200 frr-7.2.1/compile0000755000000000000000000001632713610377563010603 00000000000000#! /bin/sh # Wrapper for compilers which do not understand '-c -o'. scriptversion=2018-03-07.03; # UTC # Copyright (C) 1999-2018 Free Software Foundation, Inc. # Written by Tom Tromey . # # This program is free software; you can redistribute it and/or modify # it under the terms of the GNU General Public License as published by # the Free Software Foundation; either version 2, or (at your option) # any later version. # # This program is distributed in the hope that it will be useful, # but WITHOUT ANY WARRANTY; without even the implied warranty of # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the # GNU General Public License for more details. # # You should have received a copy of the GNU General Public License # along with this program. If not, see . # As a special exception to the GNU General Public License, if you # distribute this file as part of a program that contains a # configuration script generated by Autoconf, you may include it under # the same distribution terms that you use for the rest of that program. # This file is maintained in Automake, please report # bugs to or send patches to # . nl=' ' # We need space, tab and new line, in precisely that order. Quoting is # there to prevent tools from complaining about whitespace usage. IFS=" "" $nl" file_conv= # func_file_conv build_file lazy # Convert a $build file to $host form and store it in $file # Currently only supports Windows hosts. If the determined conversion # type is listed in (the comma separated) LAZY, no conversion will # take place. func_file_conv () { file=$1 case $file in / | /[!/]*) # absolute file, and not a UNC file if test -z "$file_conv"; then # lazily determine how to convert abs files case `uname -s` in MINGW*) file_conv=mingw ;; CYGWIN*) file_conv=cygwin ;; *) file_conv=wine ;; esac fi case $file_conv/,$2, in *,$file_conv,*) ;; mingw/*) file=`cmd //C echo "$file " | sed -e 's/"\(.*\) " *$/\1/'` ;; cygwin/*) file=`cygpath -m "$file" || echo "$file"` ;; wine/*) file=`winepath -w "$file" || echo "$file"` ;; esac ;; esac } # func_cl_dashL linkdir # Make cl look for libraries in LINKDIR func_cl_dashL () { func_file_conv "$1" if test -z "$lib_path"; then lib_path=$file else lib_path="$lib_path;$file" fi linker_opts="$linker_opts -LIBPATH:$file" } # func_cl_dashl library # Do a library search-path lookup for cl func_cl_dashl () { lib=$1 found=no save_IFS=$IFS IFS=';' for dir in $lib_path $LIB do IFS=$save_IFS if $shared && test -f "$dir/$lib.dll.lib"; then found=yes lib=$dir/$lib.dll.lib break fi if test -f "$dir/$lib.lib"; then found=yes lib=$dir/$lib.lib break fi if test -f "$dir/lib$lib.a"; then found=yes lib=$dir/lib$lib.a break fi done IFS=$save_IFS if test "$found" != yes; then lib=$lib.lib fi } # func_cl_wrapper cl arg... # Adjust compile command to suit cl func_cl_wrapper () { # Assume a capable shell lib_path= shared=: linker_opts= for arg do if test -n "$eat"; then eat= else case $1 in -o) # configure might choose to run compile as 'compile cc -o foo foo.c'. eat=1 case $2 in *.o | *.[oO][bB][jJ]) func_file_conv "$2" set x "$@" -Fo"$file" shift ;; *) func_file_conv "$2" set x "$@" -Fe"$file" shift ;; esac ;; -I) eat=1 func_file_conv "$2" mingw set x "$@" -I"$file" shift ;; -I*) func_file_conv "${1#-I}" mingw set x "$@" -I"$file" shift ;; -l) eat=1 func_cl_dashl "$2" set x "$@" "$lib" shift ;; -l*) func_cl_dashl "${1#-l}" set x "$@" "$lib" shift ;; -L) eat=1 func_cl_dashL "$2" ;; -L*) func_cl_dashL "${1#-L}" ;; -static) shared=false ;; -Wl,*) arg=${1#-Wl,} save_ifs="$IFS"; IFS=',' for flag in $arg; do IFS="$save_ifs" linker_opts="$linker_opts $flag" done IFS="$save_ifs" ;; -Xlinker) eat=1 linker_opts="$linker_opts $2" ;; -*) set x "$@" "$1" shift ;; *.cc | *.CC | *.cxx | *.CXX | *.[cC]++) func_file_conv "$1" set x "$@" -Tp"$file" shift ;; *.c | *.cpp | *.CPP | *.lib | *.LIB | *.Lib | *.OBJ | *.obj | *.[oO]) func_file_conv "$1" mingw set x "$@" "$file" shift ;; *) set x "$@" "$1" shift ;; esac fi shift done if test -n "$linker_opts"; then linker_opts="-link$linker_opts" fi exec "$@" $linker_opts exit 1 } eat= case $1 in '') echo "$0: No command. Try '$0 --help' for more information." 1>&2 exit 1; ;; -h | --h*) cat <<\EOF Usage: compile [--help] [--version] PROGRAM [ARGS] Wrapper for compilers which do not understand '-c -o'. Remove '-o dest.o' from ARGS, run PROGRAM with the remaining arguments, and rename the output as expected. If you are trying to build a whole package this is not the right script to run: please start by reading the file 'INSTALL'. Report bugs to . EOF exit $? ;; -v | --v*) echo "compile $scriptversion" exit $? ;; cl | *[/\\]cl | cl.exe | *[/\\]cl.exe | \ icl | *[/\\]icl | icl.exe | *[/\\]icl.exe ) func_cl_wrapper "$@" # Doesn't return... ;; esac ofile= cfile= for arg do if test -n "$eat"; then eat= else case $1 in -o) # configure might choose to run compile as 'compile cc -o foo foo.c'. # So we strip '-o arg' only if arg is an object. eat=1 case $2 in *.o | *.obj) ofile=$2 ;; *) set x "$@" -o "$2" shift ;; esac ;; *.c) cfile=$1 set x "$@" "$1" shift ;; *) set x "$@" "$1" shift ;; esac fi shift done if test -z "$ofile" || test -z "$cfile"; then # If no '-o' option was seen then we might have been invoked from a # pattern rule where we don't need one. That is ok -- this is a # normal compilation that the losing compiler can handle. If no # '.c' file was seen then we are probably linking. That is also # ok. exec "$@" fi # Name of file we expect compiler to create. cofile=`echo "$cfile" | sed 's|^.*[\\/]||; s|^[a-zA-Z]:||; s/\.c$/.o/'` # Create the lock directory. # Note: use '[/\\:.-]' here to ensure that we don't use the same name # that we are using for the .o file. Also, base the name on the expected # object file name, since that is what matters with a parallel build. lockdir=`echo "$cofile" | sed -e 's|[/\\:.-]|_|g'`.d while true; do if mkdir "$lockdir" >/dev/null 2>&1; then break fi sleep 1 done # FIXME: race condition here if user kills between mkdir and trap. trap "rmdir '$lockdir'; exit 1" 1 2 15 # Run the compile. "$@" ret=$? if test -f "$cofile"; then test "$cofile" = "$ofile" || mv "$cofile" "$ofile" elif test -f "${cofile}bj"; then test "${cofile}bj" = "$ofile" || mv "${cofile}bj" "$ofile" fi rmdir "$lockdir" exit $ret # Local Variables: # mode: shell-script # sh-indentation: 2 # eval: (add-hook 'before-save-hook 'time-stamp) # time-stamp-start: "scriptversion=" # time-stamp-format: "%:y-%02m-%02d.%02H" # time-stamp-time-zone: "UTC0" # time-stamp-end: "; # UTC" # End: frr-7.2.1/config.guess0000755000000000000000000012637313610377563011550 00000000000000#! /bin/sh # Attempt to guess a canonical system name. # Copyright 1992-2018 Free Software Foundation, Inc. timestamp='2018-02-24' # This file is free software; you can redistribute it and/or modify it # under the terms of the GNU General Public License as published by # the Free Software Foundation; either version 3 of the License, or # (at your option) any later version. # # This program is distributed in the hope that it will be useful, but # WITHOUT ANY WARRANTY; without even the implied warranty of # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU # General Public License for more details. # # You should have received a copy of the GNU General Public License # along with this program; if not, see . # # As a special exception to the GNU General Public License, if you # distribute this file as part of a program that contains a # configuration script generated by Autoconf, you may include it under # the same distribution terms that you use for the rest of that # program. This Exception is an additional permission under section 7 # of the GNU General Public License, version 3 ("GPLv3"). # # Originally written by Per Bothner; maintained since 2000 by Ben Elliston. # # You can get the latest version of this script from: # https://git.savannah.gnu.org/gitweb/?p=config.git;a=blob_plain;f=config.guess # # Please send patches to . me=`echo "$0" | sed -e 's,.*/,,'` usage="\ Usage: $0 [OPTION] Output the configuration name of the system \`$me' is run on. Options: -h, --help print this help, then exit -t, --time-stamp print date of last modification, then exit -v, --version print version number, then exit Report bugs and patches to ." version="\ GNU config.guess ($timestamp) Originally written by Per Bothner. Copyright 1992-2018 Free Software Foundation, Inc. This is free software; see the source for copying conditions. There is NO warranty; not even for MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE." help=" Try \`$me --help' for more information." # Parse command line while test $# -gt 0 ; do case $1 in --time-stamp | --time* | -t ) echo "$timestamp" ; exit ;; --version | -v ) echo "$version" ; exit ;; --help | --h* | -h ) echo "$usage"; exit ;; -- ) # Stop option processing shift; break ;; - ) # Use stdin as input. break ;; -* ) echo "$me: invalid option $1$help" >&2 exit 1 ;; * ) break ;; esac done if test $# != 0; then echo "$me: too many arguments$help" >&2 exit 1 fi trap 'exit 1' 1 2 15 # CC_FOR_BUILD -- compiler used by this script. Note that the use of a # compiler to aid in system detection is discouraged as it requires # temporary files to be created and, as you can see below, it is a # headache to deal with in a portable fashion. # Historically, `CC_FOR_BUILD' used to be named `HOST_CC'. We still # use `HOST_CC' if defined, but it is deprecated. # Portable tmp directory creation inspired by the Autoconf team. set_cc_for_build=' trap "exitcode=\$?; (rm -f \$tmpfiles 2>/dev/null; rmdir \$tmp 2>/dev/null) && exit \$exitcode" 0 ; trap "rm -f \$tmpfiles 2>/dev/null; rmdir \$tmp 2>/dev/null; exit 1" 1 2 13 15 ; : ${TMPDIR=/tmp} ; { tmp=`(umask 077 && mktemp -d "$TMPDIR/cgXXXXXX") 2>/dev/null` && test -n "$tmp" && test -d "$tmp" ; } || { test -n "$RANDOM" && tmp=$TMPDIR/cg$$-$RANDOM && (umask 077 && mkdir $tmp) ; } || { tmp=$TMPDIR/cg-$$ && (umask 077 && mkdir $tmp) && echo "Warning: creating insecure temp directory" >&2 ; } || { echo "$me: cannot create a temporary directory in $TMPDIR" >&2 ; exit 1 ; } ; dummy=$tmp/dummy ; tmpfiles="$dummy.c $dummy.o $dummy.rel $dummy" ; case $CC_FOR_BUILD,$HOST_CC,$CC in ,,) echo "int x;" > "$dummy.c" ; for c in cc gcc c89 c99 ; do if ($c -c -o "$dummy.o" "$dummy.c") >/dev/null 2>&1 ; then CC_FOR_BUILD="$c"; break ; fi ; done ; if test x"$CC_FOR_BUILD" = x ; then CC_FOR_BUILD=no_compiler_found ; fi ;; ,,*) CC_FOR_BUILD=$CC ;; ,*,*) CC_FOR_BUILD=$HOST_CC ;; esac ; set_cc_for_build= ;' # This is needed to find uname on a Pyramid OSx when run in the BSD universe. # (ghazi@noc.rutgers.edu 1994-08-24) if (test -f /.attbin/uname) >/dev/null 2>&1 ; then PATH=$PATH:/.attbin ; export PATH fi UNAME_MACHINE=`(uname -m) 2>/dev/null` || UNAME_MACHINE=unknown UNAME_RELEASE=`(uname -r) 2>/dev/null` || UNAME_RELEASE=unknown UNAME_SYSTEM=`(uname -s) 2>/dev/null` || UNAME_SYSTEM=unknown UNAME_VERSION=`(uname -v) 2>/dev/null` || UNAME_VERSION=unknown case "$UNAME_SYSTEM" in Linux|GNU|GNU/*) # If the system lacks a compiler, then just pick glibc. # We could probably try harder. LIBC=gnu eval "$set_cc_for_build" cat <<-EOF > "$dummy.c" #include #if defined(__UCLIBC__) LIBC=uclibc #elif defined(__dietlibc__) LIBC=dietlibc #else LIBC=gnu #endif EOF eval "`$CC_FOR_BUILD -E "$dummy.c" 2>/dev/null | grep '^LIBC' | sed 's, ,,g'`" # If ldd exists, use it to detect musl libc. if command -v ldd >/dev/null && \ ldd --version 2>&1 | grep -q ^musl then LIBC=musl fi ;; esac # Note: order is significant - the case branches are not exclusive. case "$UNAME_MACHINE:$UNAME_SYSTEM:$UNAME_RELEASE:$UNAME_VERSION" in *:NetBSD:*:*) # NetBSD (nbsd) targets should (where applicable) match one or # more of the tuples: *-*-netbsdelf*, *-*-netbsdaout*, # *-*-netbsdecoff* and *-*-netbsd*. For targets that recently # switched to ELF, *-*-netbsd* would select the old # object file format. This provides both forward # compatibility and a consistent mechanism for selecting the # object file format. # # Note: NetBSD doesn't particularly care about the vendor # portion of the name. We always set it to "unknown". sysctl="sysctl -n hw.machine_arch" UNAME_MACHINE_ARCH=`(uname -p 2>/dev/null || \ "/sbin/$sysctl" 2>/dev/null || \ "/usr/sbin/$sysctl" 2>/dev/null || \ echo unknown)` case "$UNAME_MACHINE_ARCH" in armeb) machine=armeb-unknown ;; arm*) machine=arm-unknown ;; sh3el) machine=shl-unknown ;; sh3eb) machine=sh-unknown ;; sh5el) machine=sh5le-unknown ;; earmv*) arch=`echo "$UNAME_MACHINE_ARCH" | sed -e 's,^e\(armv[0-9]\).*$,\1,'` endian=`echo "$UNAME_MACHINE_ARCH" | sed -ne 's,^.*\(eb\)$,\1,p'` machine="${arch}${endian}"-unknown ;; *) machine="$UNAME_MACHINE_ARCH"-unknown ;; esac # The Operating System including object format, if it has switched # to ELF recently (or will in the future) and ABI. case "$UNAME_MACHINE_ARCH" in earm*) os=netbsdelf ;; arm*|i386|m68k|ns32k|sh3*|sparc|vax) eval "$set_cc_for_build" if echo __ELF__ | $CC_FOR_BUILD -E - 2>/dev/null \ | grep -q __ELF__ then # Once all utilities can be ECOFF (netbsdecoff) or a.out (netbsdaout). # Return netbsd for either. FIX? os=netbsd else os=netbsdelf fi ;; *) os=netbsd ;; esac # Determine ABI tags. case "$UNAME_MACHINE_ARCH" in earm*) expr='s/^earmv[0-9]/-eabi/;s/eb$//' abi=`echo "$UNAME_MACHINE_ARCH" | sed -e "$expr"` ;; esac # The OS release # Debian GNU/NetBSD machines have a different userland, and # thus, need a distinct triplet. However, they do not need # kernel version information, so it can be replaced with a # suitable tag, in the style of linux-gnu. case "$UNAME_VERSION" in Debian*) release='-gnu' ;; *) release=`echo "$UNAME_RELEASE" | sed -e 's/[-_].*//' | cut -d. -f1,2` ;; esac # Since CPU_TYPE-MANUFACTURER-KERNEL-OPERATING_SYSTEM: # contains redundant information, the shorter form: # CPU_TYPE-MANUFACTURER-OPERATING_SYSTEM is used. echo "$machine-${os}${release}${abi}" exit ;; *:Bitrig:*:*) UNAME_MACHINE_ARCH=`arch | sed 's/Bitrig.//'` echo "$UNAME_MACHINE_ARCH"-unknown-bitrig"$UNAME_RELEASE" exit ;; *:OpenBSD:*:*) UNAME_MACHINE_ARCH=`arch | sed 's/OpenBSD.//'` echo "$UNAME_MACHINE_ARCH"-unknown-openbsd"$UNAME_RELEASE" exit ;; *:LibertyBSD:*:*) UNAME_MACHINE_ARCH=`arch | sed 's/^.*BSD\.//'` echo "$UNAME_MACHINE_ARCH"-unknown-libertybsd"$UNAME_RELEASE" exit ;; *:MidnightBSD:*:*) echo "$UNAME_MACHINE"-unknown-midnightbsd"$UNAME_RELEASE" exit ;; *:ekkoBSD:*:*) echo "$UNAME_MACHINE"-unknown-ekkobsd"$UNAME_RELEASE" exit ;; *:SolidBSD:*:*) echo "$UNAME_MACHINE"-unknown-solidbsd"$UNAME_RELEASE" exit ;; macppc:MirBSD:*:*) echo powerpc-unknown-mirbsd"$UNAME_RELEASE" exit ;; *:MirBSD:*:*) echo "$UNAME_MACHINE"-unknown-mirbsd"$UNAME_RELEASE" exit ;; *:Sortix:*:*) echo "$UNAME_MACHINE"-unknown-sortix exit ;; *:Redox:*:*) echo "$UNAME_MACHINE"-unknown-redox exit ;; mips:OSF1:*.*) echo mips-dec-osf1 exit ;; alpha:OSF1:*:*) case $UNAME_RELEASE in *4.0) UNAME_RELEASE=`/usr/sbin/sizer -v | awk '{print $3}'` ;; *5.*) UNAME_RELEASE=`/usr/sbin/sizer -v | awk '{print $4}'` ;; esac # According to Compaq, /usr/sbin/psrinfo has been available on # OSF/1 and Tru64 systems produced since 1995. I hope that # covers most systems running today. This code pipes the CPU # types through head -n 1, so we only detect the type of CPU 0. ALPHA_CPU_TYPE=`/usr/sbin/psrinfo -v | sed -n -e 's/^ The alpha \(.*\) processor.*$/\1/p' | head -n 1` case "$ALPHA_CPU_TYPE" in "EV4 (21064)") UNAME_MACHINE=alpha ;; "EV4.5 (21064)") UNAME_MACHINE=alpha ;; "LCA4 (21066/21068)") UNAME_MACHINE=alpha ;; "EV5 (21164)") UNAME_MACHINE=alphaev5 ;; "EV5.6 (21164A)") UNAME_MACHINE=alphaev56 ;; "EV5.6 (21164PC)") UNAME_MACHINE=alphapca56 ;; "EV5.7 (21164PC)") UNAME_MACHINE=alphapca57 ;; "EV6 (21264)") UNAME_MACHINE=alphaev6 ;; "EV6.7 (21264A)") UNAME_MACHINE=alphaev67 ;; "EV6.8CB (21264C)") UNAME_MACHINE=alphaev68 ;; "EV6.8AL (21264B)") UNAME_MACHINE=alphaev68 ;; "EV6.8CX (21264D)") UNAME_MACHINE=alphaev68 ;; "EV6.9A (21264/EV69A)") UNAME_MACHINE=alphaev69 ;; "EV7 (21364)") UNAME_MACHINE=alphaev7 ;; "EV7.9 (21364A)") UNAME_MACHINE=alphaev79 ;; esac # A Pn.n version is a patched version. # A Vn.n version is a released version. # A Tn.n version is a released field test version. # A Xn.n version is an unreleased experimental baselevel. # 1.2 uses "1.2" for uname -r. echo "$UNAME_MACHINE"-dec-osf"`echo "$UNAME_RELEASE" | sed -e 's/^[PVTX]//' | tr ABCDEFGHIJKLMNOPQRSTUVWXYZ abcdefghijklmnopqrstuvwxyz`" # Reset EXIT trap before exiting to avoid spurious non-zero exit code. exitcode=$? trap '' 0 exit $exitcode ;; Amiga*:UNIX_System_V:4.0:*) echo m68k-unknown-sysv4 exit ;; *:[Aa]miga[Oo][Ss]:*:*) echo "$UNAME_MACHINE"-unknown-amigaos exit ;; *:[Mm]orph[Oo][Ss]:*:*) echo "$UNAME_MACHINE"-unknown-morphos exit ;; *:OS/390:*:*) echo i370-ibm-openedition exit ;; *:z/VM:*:*) echo s390-ibm-zvmoe exit ;; *:OS400:*:*) echo powerpc-ibm-os400 exit ;; arm:RISC*:1.[012]*:*|arm:riscix:1.[012]*:*) echo arm-acorn-riscix"$UNAME_RELEASE" exit ;; arm*:riscos:*:*|arm*:RISCOS:*:*) echo arm-unknown-riscos exit ;; SR2?01:HI-UX/MPP:*:* | SR8000:HI-UX/MPP:*:*) echo hppa1.1-hitachi-hiuxmpp exit ;; Pyramid*:OSx*:*:* | MIS*:OSx*:*:* | MIS*:SMP_DC-OSx*:*:*) # akee@wpdis03.wpafb.af.mil (Earle F. Ake) contributed MIS and NILE. if test "`(/bin/universe) 2>/dev/null`" = att ; then echo pyramid-pyramid-sysv3 else echo pyramid-pyramid-bsd fi exit ;; NILE*:*:*:dcosx) echo pyramid-pyramid-svr4 exit ;; DRS?6000:unix:4.0:6*) echo sparc-icl-nx6 exit ;; DRS?6000:UNIX_SV:4.2*:7* | DRS?6000:isis:4.2*:7*) case `/usr/bin/uname -p` in sparc) echo sparc-icl-nx7; exit ;; esac ;; s390x:SunOS:*:*) echo "$UNAME_MACHINE"-ibm-solaris2"`echo "$UNAME_RELEASE" | sed -e 's/[^.]*//'`" exit ;; sun4H:SunOS:5.*:*) echo sparc-hal-solaris2"`echo "$UNAME_RELEASE"|sed -e 's/[^.]*//'`" exit ;; sun4*:SunOS:5.*:* | tadpole*:SunOS:5.*:*) echo sparc-sun-solaris2"`echo "$UNAME_RELEASE" | sed -e 's/[^.]*//'`" exit ;; i86pc:AuroraUX:5.*:* | i86xen:AuroraUX:5.*:*) echo i386-pc-auroraux"$UNAME_RELEASE" exit ;; i86pc:SunOS:5.*:* | i86xen:SunOS:5.*:*) eval "$set_cc_for_build" SUN_ARCH=i386 # If there is a compiler, see if it is configured for 64-bit objects. # Note that the Sun cc does not turn __LP64__ into 1 like gcc does. # This test works for both compilers. if [ "$CC_FOR_BUILD" != no_compiler_found ]; then if (echo '#ifdef __amd64'; echo IS_64BIT_ARCH; echo '#endif') | \ (CCOPTS="" $CC_FOR_BUILD -E - 2>/dev/null) | \ grep IS_64BIT_ARCH >/dev/null then SUN_ARCH=x86_64 fi fi echo "$SUN_ARCH"-pc-solaris2"`echo "$UNAME_RELEASE"|sed -e 's/[^.]*//'`" exit ;; sun4*:SunOS:6*:*) # According to config.sub, this is the proper way to canonicalize # SunOS6. Hard to guess exactly what SunOS6 will be like, but # it's likely to be more like Solaris than SunOS4. echo sparc-sun-solaris3"`echo "$UNAME_RELEASE"|sed -e 's/[^.]*//'`" exit ;; sun4*:SunOS:*:*) case "`/usr/bin/arch -k`" in Series*|S4*) UNAME_RELEASE=`uname -v` ;; esac # Japanese Language versions have a version number like `4.1.3-JL'. echo sparc-sun-sunos"`echo "$UNAME_RELEASE"|sed -e 's/-/_/'`" exit ;; sun3*:SunOS:*:*) echo m68k-sun-sunos"$UNAME_RELEASE" exit ;; sun*:*:4.2BSD:*) UNAME_RELEASE=`(sed 1q /etc/motd | awk '{print substr($5,1,3)}') 2>/dev/null` test "x$UNAME_RELEASE" = x && UNAME_RELEASE=3 case "`/bin/arch`" in sun3) echo m68k-sun-sunos"$UNAME_RELEASE" ;; sun4) echo sparc-sun-sunos"$UNAME_RELEASE" ;; esac exit ;; aushp:SunOS:*:*) echo sparc-auspex-sunos"$UNAME_RELEASE" exit ;; # The situation for MiNT is a little confusing. The machine name # can be virtually everything (everything which is not # "atarist" or "atariste" at least should have a processor # > m68000). The system name ranges from "MiNT" over "FreeMiNT" # to the lowercase version "mint" (or "freemint"). Finally # the system name "TOS" denotes a system which is actually not # MiNT. But MiNT is downward compatible to TOS, so this should # be no problem. atarist[e]:*MiNT:*:* | atarist[e]:*mint:*:* | atarist[e]:*TOS:*:*) echo m68k-atari-mint"$UNAME_RELEASE" exit ;; atari*:*MiNT:*:* | atari*:*mint:*:* | atarist[e]:*TOS:*:*) echo m68k-atari-mint"$UNAME_RELEASE" exit ;; *falcon*:*MiNT:*:* | *falcon*:*mint:*:* | *falcon*:*TOS:*:*) echo m68k-atari-mint"$UNAME_RELEASE" exit ;; milan*:*MiNT:*:* | milan*:*mint:*:* | *milan*:*TOS:*:*) echo m68k-milan-mint"$UNAME_RELEASE" exit ;; hades*:*MiNT:*:* | hades*:*mint:*:* | *hades*:*TOS:*:*) echo m68k-hades-mint"$UNAME_RELEASE" exit ;; *:*MiNT:*:* | *:*mint:*:* | *:*TOS:*:*) echo m68k-unknown-mint"$UNAME_RELEASE" exit ;; m68k:machten:*:*) echo m68k-apple-machten"$UNAME_RELEASE" exit ;; powerpc:machten:*:*) echo powerpc-apple-machten"$UNAME_RELEASE" exit ;; RISC*:Mach:*:*) echo mips-dec-mach_bsd4.3 exit ;; RISC*:ULTRIX:*:*) echo mips-dec-ultrix"$UNAME_RELEASE" exit ;; VAX*:ULTRIX*:*:*) echo vax-dec-ultrix"$UNAME_RELEASE" exit ;; 2020:CLIX:*:* | 2430:CLIX:*:*) echo clipper-intergraph-clix"$UNAME_RELEASE" exit ;; mips:*:*:UMIPS | mips:*:*:RISCos) eval "$set_cc_for_build" sed 's/^ //' << EOF > "$dummy.c" #ifdef __cplusplus #include /* for printf() prototype */ int main (int argc, char *argv[]) { #else int main (argc, argv) int argc; char *argv[]; { #endif #if defined (host_mips) && defined (MIPSEB) #if defined (SYSTYPE_SYSV) printf ("mips-mips-riscos%ssysv\\n", argv[1]); exit (0); #endif #if defined (SYSTYPE_SVR4) printf ("mips-mips-riscos%ssvr4\\n", argv[1]); exit (0); #endif #if defined (SYSTYPE_BSD43) || defined(SYSTYPE_BSD) printf ("mips-mips-riscos%sbsd\\n", argv[1]); exit (0); #endif #endif exit (-1); } EOF $CC_FOR_BUILD -o "$dummy" "$dummy.c" && dummyarg=`echo "$UNAME_RELEASE" | sed -n 's/\([0-9]*\).*/\1/p'` && SYSTEM_NAME=`"$dummy" "$dummyarg"` && { echo "$SYSTEM_NAME"; exit; } echo mips-mips-riscos"$UNAME_RELEASE" exit ;; Motorola:PowerMAX_OS:*:*) echo powerpc-motorola-powermax exit ;; Motorola:*:4.3:PL8-*) echo powerpc-harris-powermax exit ;; Night_Hawk:*:*:PowerMAX_OS | Synergy:PowerMAX_OS:*:*) echo powerpc-harris-powermax exit ;; Night_Hawk:Power_UNIX:*:*) echo powerpc-harris-powerunix exit ;; m88k:CX/UX:7*:*) echo m88k-harris-cxux7 exit ;; m88k:*:4*:R4*) echo m88k-motorola-sysv4 exit ;; m88k:*:3*:R3*) echo m88k-motorola-sysv3 exit ;; AViiON:dgux:*:*) # DG/UX returns AViiON for all architectures UNAME_PROCESSOR=`/usr/bin/uname -p` if [ "$UNAME_PROCESSOR" = mc88100 ] || [ "$UNAME_PROCESSOR" = mc88110 ] then if [ "$TARGET_BINARY_INTERFACE"x = m88kdguxelfx ] || \ [ "$TARGET_BINARY_INTERFACE"x = x ] then echo m88k-dg-dgux"$UNAME_RELEASE" else echo m88k-dg-dguxbcs"$UNAME_RELEASE" fi else echo i586-dg-dgux"$UNAME_RELEASE" fi exit ;; M88*:DolphinOS:*:*) # DolphinOS (SVR3) echo m88k-dolphin-sysv3 exit ;; M88*:*:R3*:*) # Delta 88k system running SVR3 echo m88k-motorola-sysv3 exit ;; XD88*:*:*:*) # Tektronix XD88 system running UTekV (SVR3) echo m88k-tektronix-sysv3 exit ;; Tek43[0-9][0-9]:UTek:*:*) # Tektronix 4300 system running UTek (BSD) echo m68k-tektronix-bsd exit ;; *:IRIX*:*:*) echo mips-sgi-irix"`echo "$UNAME_RELEASE"|sed -e 's/-/_/g'`" exit ;; ????????:AIX?:[12].1:2) # AIX 2.2.1 or AIX 2.1.1 is RT/PC AIX. echo romp-ibm-aix # uname -m gives an 8 hex-code CPU id exit ;; # Note that: echo "'`uname -s`'" gives 'AIX ' i*86:AIX:*:*) echo i386-ibm-aix exit ;; ia64:AIX:*:*) if [ -x /usr/bin/oslevel ] ; then IBM_REV=`/usr/bin/oslevel` else IBM_REV="$UNAME_VERSION.$UNAME_RELEASE" fi echo "$UNAME_MACHINE"-ibm-aix"$IBM_REV" exit ;; *:AIX:2:3) if grep bos325 /usr/include/stdio.h >/dev/null 2>&1; then eval "$set_cc_for_build" sed 's/^ //' << EOF > "$dummy.c" #include main() { if (!__power_pc()) exit(1); puts("powerpc-ibm-aix3.2.5"); exit(0); } EOF if $CC_FOR_BUILD -o "$dummy" "$dummy.c" && SYSTEM_NAME=`"$dummy"` then echo "$SYSTEM_NAME" else echo rs6000-ibm-aix3.2.5 fi elif grep bos324 /usr/include/stdio.h >/dev/null 2>&1; then echo rs6000-ibm-aix3.2.4 else echo rs6000-ibm-aix3.2 fi exit ;; *:AIX:*:[4567]) IBM_CPU_ID=`/usr/sbin/lsdev -C -c processor -S available | sed 1q | awk '{ print $1 }'` if /usr/sbin/lsattr -El "$IBM_CPU_ID" | grep ' POWER' >/dev/null 2>&1; then IBM_ARCH=rs6000 else IBM_ARCH=powerpc fi if [ -x /usr/bin/lslpp ] ; then IBM_REV=`/usr/bin/lslpp -Lqc bos.rte.libc | awk -F: '{ print $3 }' | sed s/[0-9]*$/0/` else IBM_REV="$UNAME_VERSION.$UNAME_RELEASE" fi echo "$IBM_ARCH"-ibm-aix"$IBM_REV" exit ;; *:AIX:*:*) echo rs6000-ibm-aix exit ;; ibmrt:4.4BSD:*|romp-ibm:4.4BSD:*) echo romp-ibm-bsd4.4 exit ;; ibmrt:*BSD:*|romp-ibm:BSD:*) # covers RT/PC BSD and echo romp-ibm-bsd"$UNAME_RELEASE" # 4.3 with uname added to exit ;; # report: romp-ibm BSD 4.3 *:BOSX:*:*) echo rs6000-bull-bosx exit ;; DPX/2?00:B.O.S.:*:*) echo m68k-bull-sysv3 exit ;; 9000/[34]??:4.3bsd:1.*:*) echo m68k-hp-bsd exit ;; hp300:4.4BSD:*:* | 9000/[34]??:4.3bsd:2.*:*) echo m68k-hp-bsd4.4 exit ;; 9000/[34678]??:HP-UX:*:*) HPUX_REV=`echo "$UNAME_RELEASE"|sed -e 's/[^.]*.[0B]*//'` case "$UNAME_MACHINE" in 9000/31?) HP_ARCH=m68000 ;; 9000/[34]??) HP_ARCH=m68k ;; 9000/[678][0-9][0-9]) if [ -x /usr/bin/getconf ]; then sc_cpu_version=`/usr/bin/getconf SC_CPU_VERSION 2>/dev/null` sc_kernel_bits=`/usr/bin/getconf SC_KERNEL_BITS 2>/dev/null` case "$sc_cpu_version" in 523) HP_ARCH=hppa1.0 ;; # CPU_PA_RISC1_0 528) HP_ARCH=hppa1.1 ;; # CPU_PA_RISC1_1 532) # CPU_PA_RISC2_0 case "$sc_kernel_bits" in 32) HP_ARCH=hppa2.0n ;; 64) HP_ARCH=hppa2.0w ;; '') HP_ARCH=hppa2.0 ;; # HP-UX 10.20 esac ;; esac fi if [ "$HP_ARCH" = "" ]; then eval "$set_cc_for_build" sed 's/^ //' << EOF > "$dummy.c" #define _HPUX_SOURCE #include #include int main () { #if defined(_SC_KERNEL_BITS) long bits = sysconf(_SC_KERNEL_BITS); #endif long cpu = sysconf (_SC_CPU_VERSION); switch (cpu) { case CPU_PA_RISC1_0: puts ("hppa1.0"); break; case CPU_PA_RISC1_1: puts ("hppa1.1"); break; case CPU_PA_RISC2_0: #if defined(_SC_KERNEL_BITS) switch (bits) { case 64: puts ("hppa2.0w"); break; case 32: puts ("hppa2.0n"); break; default: puts ("hppa2.0"); break; } break; #else /* !defined(_SC_KERNEL_BITS) */ puts ("hppa2.0"); break; #endif default: puts ("hppa1.0"); break; } exit (0); } EOF (CCOPTS="" $CC_FOR_BUILD -o "$dummy" "$dummy.c" 2>/dev/null) && HP_ARCH=`"$dummy"` test -z "$HP_ARCH" && HP_ARCH=hppa fi ;; esac if [ "$HP_ARCH" = hppa2.0w ] then eval "$set_cc_for_build" # hppa2.0w-hp-hpux* has a 64-bit kernel and a compiler generating # 32-bit code. hppa64-hp-hpux* has the same kernel and a compiler # generating 64-bit code. GNU and HP use different nomenclature: # # $ CC_FOR_BUILD=cc ./config.guess # => hppa2.0w-hp-hpux11.23 # $ CC_FOR_BUILD="cc +DA2.0w" ./config.guess # => hppa64-hp-hpux11.23 if echo __LP64__ | (CCOPTS="" $CC_FOR_BUILD -E - 2>/dev/null) | grep -q __LP64__ then HP_ARCH=hppa2.0w else HP_ARCH=hppa64 fi fi echo "$HP_ARCH"-hp-hpux"$HPUX_REV" exit ;; ia64:HP-UX:*:*) HPUX_REV=`echo "$UNAME_RELEASE"|sed -e 's/[^.]*.[0B]*//'` echo ia64-hp-hpux"$HPUX_REV" exit ;; 3050*:HI-UX:*:*) eval "$set_cc_for_build" sed 's/^ //' << EOF > "$dummy.c" #include int main () { long cpu = sysconf (_SC_CPU_VERSION); /* The order matters, because CPU_IS_HP_MC68K erroneously returns true for CPU_PA_RISC1_0. CPU_IS_PA_RISC returns correct results, however. */ if (CPU_IS_PA_RISC (cpu)) { switch (cpu) { case CPU_PA_RISC1_0: puts ("hppa1.0-hitachi-hiuxwe2"); break; case CPU_PA_RISC1_1: puts ("hppa1.1-hitachi-hiuxwe2"); break; case CPU_PA_RISC2_0: puts ("hppa2.0-hitachi-hiuxwe2"); break; default: puts ("hppa-hitachi-hiuxwe2"); break; } } else if (CPU_IS_HP_MC68K (cpu)) puts ("m68k-hitachi-hiuxwe2"); else puts ("unknown-hitachi-hiuxwe2"); exit (0); } EOF $CC_FOR_BUILD -o "$dummy" "$dummy.c" && SYSTEM_NAME=`"$dummy"` && { echo "$SYSTEM_NAME"; exit; } echo unknown-hitachi-hiuxwe2 exit ;; 9000/7??:4.3bsd:*:* | 9000/8?[79]:4.3bsd:*:*) echo hppa1.1-hp-bsd exit ;; 9000/8??:4.3bsd:*:*) echo hppa1.0-hp-bsd exit ;; *9??*:MPE/iX:*:* | *3000*:MPE/iX:*:*) echo hppa1.0-hp-mpeix exit ;; hp7??:OSF1:*:* | hp8?[79]:OSF1:*:*) echo hppa1.1-hp-osf exit ;; hp8??:OSF1:*:*) echo hppa1.0-hp-osf exit ;; i*86:OSF1:*:*) if [ -x /usr/sbin/sysversion ] ; then echo "$UNAME_MACHINE"-unknown-osf1mk else echo "$UNAME_MACHINE"-unknown-osf1 fi exit ;; parisc*:Lites*:*:*) echo hppa1.1-hp-lites exit ;; C1*:ConvexOS:*:* | convex:ConvexOS:C1*:*) echo c1-convex-bsd exit ;; C2*:ConvexOS:*:* | convex:ConvexOS:C2*:*) if getsysinfo -f scalar_acc then echo c32-convex-bsd else echo c2-convex-bsd fi exit ;; C34*:ConvexOS:*:* | convex:ConvexOS:C34*:*) echo c34-convex-bsd exit ;; C38*:ConvexOS:*:* | convex:ConvexOS:C38*:*) echo c38-convex-bsd exit ;; C4*:ConvexOS:*:* | convex:ConvexOS:C4*:*) echo c4-convex-bsd exit ;; CRAY*Y-MP:*:*:*) echo ymp-cray-unicos"$UNAME_RELEASE" | sed -e 's/\.[^.]*$/.X/' exit ;; CRAY*[A-Z]90:*:*:*) echo "$UNAME_MACHINE"-cray-unicos"$UNAME_RELEASE" \ | sed -e 's/CRAY.*\([A-Z]90\)/\1/' \ -e y/ABCDEFGHIJKLMNOPQRSTUVWXYZ/abcdefghijklmnopqrstuvwxyz/ \ -e 's/\.[^.]*$/.X/' exit ;; CRAY*TS:*:*:*) echo t90-cray-unicos"$UNAME_RELEASE" | sed -e 's/\.[^.]*$/.X/' exit ;; CRAY*T3E:*:*:*) echo alphaev5-cray-unicosmk"$UNAME_RELEASE" | sed -e 's/\.[^.]*$/.X/' exit ;; CRAY*SV1:*:*:*) echo sv1-cray-unicos"$UNAME_RELEASE" | sed -e 's/\.[^.]*$/.X/' exit ;; *:UNICOS/mp:*:*) echo craynv-cray-unicosmp"$UNAME_RELEASE" | sed -e 's/\.[^.]*$/.X/' exit ;; F30[01]:UNIX_System_V:*:* | F700:UNIX_System_V:*:*) FUJITSU_PROC=`uname -m | tr ABCDEFGHIJKLMNOPQRSTUVWXYZ abcdefghijklmnopqrstuvwxyz` FUJITSU_SYS=`uname -p | tr ABCDEFGHIJKLMNOPQRSTUVWXYZ abcdefghijklmnopqrstuvwxyz | sed -e 's/\///'` FUJITSU_REL=`echo "$UNAME_RELEASE" | sed -e 's/ /_/'` echo "${FUJITSU_PROC}-fujitsu-${FUJITSU_SYS}${FUJITSU_REL}" exit ;; 5000:UNIX_System_V:4.*:*) FUJITSU_SYS=`uname -p | tr ABCDEFGHIJKLMNOPQRSTUVWXYZ abcdefghijklmnopqrstuvwxyz | sed -e 's/\///'` FUJITSU_REL=`echo "$UNAME_RELEASE" | tr ABCDEFGHIJKLMNOPQRSTUVWXYZ abcdefghijklmnopqrstuvwxyz | sed -e 's/ /_/'` echo "sparc-fujitsu-${FUJITSU_SYS}${FUJITSU_REL}" exit ;; i*86:BSD/386:*:* | i*86:BSD/OS:*:* | *:Ascend\ Embedded/OS:*:*) echo "$UNAME_MACHINE"-pc-bsdi"$UNAME_RELEASE" exit ;; sparc*:BSD/OS:*:*) echo sparc-unknown-bsdi"$UNAME_RELEASE" exit ;; *:BSD/OS:*:*) echo "$UNAME_MACHINE"-unknown-bsdi"$UNAME_RELEASE" exit ;; *:FreeBSD:*:*) UNAME_PROCESSOR=`/usr/bin/uname -p` case "$UNAME_PROCESSOR" in amd64) UNAME_PROCESSOR=x86_64 ;; i386) UNAME_PROCESSOR=i586 ;; esac echo "$UNAME_PROCESSOR"-unknown-freebsd"`echo "$UNAME_RELEASE"|sed -e 's/[-(].*//'`" exit ;; i*:CYGWIN*:*) echo "$UNAME_MACHINE"-pc-cygwin exit ;; *:MINGW64*:*) echo "$UNAME_MACHINE"-pc-mingw64 exit ;; *:MINGW*:*) echo "$UNAME_MACHINE"-pc-mingw32 exit ;; *:MSYS*:*) echo "$UNAME_MACHINE"-pc-msys exit ;; i*:PW*:*) echo "$UNAME_MACHINE"-pc-pw32 exit ;; *:Interix*:*) case "$UNAME_MACHINE" in x86) echo i586-pc-interix"$UNAME_RELEASE" exit ;; authenticamd | genuineintel | EM64T) echo x86_64-unknown-interix"$UNAME_RELEASE" exit ;; IA64) echo ia64-unknown-interix"$UNAME_RELEASE" exit ;; esac ;; i*:UWIN*:*) echo "$UNAME_MACHINE"-pc-uwin exit ;; amd64:CYGWIN*:*:* | x86_64:CYGWIN*:*:*) echo x86_64-unknown-cygwin exit ;; prep*:SunOS:5.*:*) echo powerpcle-unknown-solaris2"`echo "$UNAME_RELEASE"|sed -e 's/[^.]*//'`" exit ;; *:GNU:*:*) # the GNU system echo "`echo "$UNAME_MACHINE"|sed -e 's,[-/].*$,,'`-unknown-$LIBC`echo "$UNAME_RELEASE"|sed -e 's,/.*$,,'`" exit ;; *:GNU/*:*:*) # other systems with GNU libc and userland echo "$UNAME_MACHINE-unknown-`echo "$UNAME_SYSTEM" | sed 's,^[^/]*/,,' | tr "[:upper:]" "[:lower:]"``echo "$UNAME_RELEASE"|sed -e 's/[-(].*//'`-$LIBC" exit ;; i*86:Minix:*:*) echo "$UNAME_MACHINE"-pc-minix exit ;; aarch64:Linux:*:*) echo "$UNAME_MACHINE"-unknown-linux-"$LIBC" exit ;; aarch64_be:Linux:*:*) UNAME_MACHINE=aarch64_be echo "$UNAME_MACHINE"-unknown-linux-"$LIBC" exit ;; alpha:Linux:*:*) case `sed -n '/^cpu model/s/^.*: \(.*\)/\1/p' < /proc/cpuinfo` in EV5) UNAME_MACHINE=alphaev5 ;; EV56) UNAME_MACHINE=alphaev56 ;; PCA56) UNAME_MACHINE=alphapca56 ;; PCA57) UNAME_MACHINE=alphapca56 ;; EV6) UNAME_MACHINE=alphaev6 ;; EV67) UNAME_MACHINE=alphaev67 ;; EV68*) UNAME_MACHINE=alphaev68 ;; esac objdump --private-headers /bin/sh | grep -q ld.so.1 if test "$?" = 0 ; then LIBC=gnulibc1 ; fi echo "$UNAME_MACHINE"-unknown-linux-"$LIBC" exit ;; arc:Linux:*:* | arceb:Linux:*:*) echo "$UNAME_MACHINE"-unknown-linux-"$LIBC" exit ;; arm*:Linux:*:*) eval "$set_cc_for_build" if echo __ARM_EABI__ | $CC_FOR_BUILD -E - 2>/dev/null \ | grep -q __ARM_EABI__ then echo "$UNAME_MACHINE"-unknown-linux-"$LIBC" else if echo __ARM_PCS_VFP | $CC_FOR_BUILD -E - 2>/dev/null \ | grep -q __ARM_PCS_VFP then echo "$UNAME_MACHINE"-unknown-linux-"$LIBC"eabi else echo "$UNAME_MACHINE"-unknown-linux-"$LIBC"eabihf fi fi exit ;; avr32*:Linux:*:*) echo "$UNAME_MACHINE"-unknown-linux-"$LIBC" exit ;; cris:Linux:*:*) echo "$UNAME_MACHINE"-axis-linux-"$LIBC" exit ;; crisv32:Linux:*:*) echo "$UNAME_MACHINE"-axis-linux-"$LIBC" exit ;; e2k:Linux:*:*) echo "$UNAME_MACHINE"-unknown-linux-"$LIBC" exit ;; frv:Linux:*:*) echo "$UNAME_MACHINE"-unknown-linux-"$LIBC" exit ;; hexagon:Linux:*:*) echo "$UNAME_MACHINE"-unknown-linux-"$LIBC" exit ;; i*86:Linux:*:*) echo "$UNAME_MACHINE"-pc-linux-"$LIBC" exit ;; ia64:Linux:*:*) echo "$UNAME_MACHINE"-unknown-linux-"$LIBC" exit ;; k1om:Linux:*:*) echo "$UNAME_MACHINE"-unknown-linux-"$LIBC" exit ;; m32r*:Linux:*:*) echo "$UNAME_MACHINE"-unknown-linux-"$LIBC" exit ;; m68*:Linux:*:*) echo "$UNAME_MACHINE"-unknown-linux-"$LIBC" exit ;; mips:Linux:*:* | mips64:Linux:*:*) eval "$set_cc_for_build" sed 's/^ //' << EOF > "$dummy.c" #undef CPU #undef ${UNAME_MACHINE} #undef ${UNAME_MACHINE}el #if defined(__MIPSEL__) || defined(__MIPSEL) || defined(_MIPSEL) || defined(MIPSEL) CPU=${UNAME_MACHINE}el #else #if defined(__MIPSEB__) || defined(__MIPSEB) || defined(_MIPSEB) || defined(MIPSEB) CPU=${UNAME_MACHINE} #else CPU= #endif #endif EOF eval "`$CC_FOR_BUILD -E "$dummy.c" 2>/dev/null | grep '^CPU'`" test "x$CPU" != x && { echo "$CPU-unknown-linux-$LIBC"; exit; } ;; mips64el:Linux:*:*) echo "$UNAME_MACHINE"-unknown-linux-"$LIBC" exit ;; openrisc*:Linux:*:*) echo or1k-unknown-linux-"$LIBC" exit ;; or32:Linux:*:* | or1k*:Linux:*:*) echo "$UNAME_MACHINE"-unknown-linux-"$LIBC" exit ;; padre:Linux:*:*) echo sparc-unknown-linux-"$LIBC" exit ;; parisc64:Linux:*:* | hppa64:Linux:*:*) echo hppa64-unknown-linux-"$LIBC" exit ;; parisc:Linux:*:* | hppa:Linux:*:*) # Look for CPU level case `grep '^cpu[^a-z]*:' /proc/cpuinfo 2>/dev/null | cut -d' ' -f2` in PA7*) echo hppa1.1-unknown-linux-"$LIBC" ;; PA8*) echo hppa2.0-unknown-linux-"$LIBC" ;; *) echo hppa-unknown-linux-"$LIBC" ;; esac exit ;; ppc64:Linux:*:*) echo powerpc64-unknown-linux-"$LIBC" exit ;; ppc:Linux:*:*) echo powerpc-unknown-linux-"$LIBC" exit ;; ppc64le:Linux:*:*) echo powerpc64le-unknown-linux-"$LIBC" exit ;; ppcle:Linux:*:*) echo powerpcle-unknown-linux-"$LIBC" exit ;; riscv32:Linux:*:* | riscv64:Linux:*:*) echo "$UNAME_MACHINE"-unknown-linux-"$LIBC" exit ;; s390:Linux:*:* | s390x:Linux:*:*) echo "$UNAME_MACHINE"-ibm-linux-"$LIBC" exit ;; sh64*:Linux:*:*) echo "$UNAME_MACHINE"-unknown-linux-"$LIBC" exit ;; sh*:Linux:*:*) echo "$UNAME_MACHINE"-unknown-linux-"$LIBC" exit ;; sparc:Linux:*:* | sparc64:Linux:*:*) echo "$UNAME_MACHINE"-unknown-linux-"$LIBC" exit ;; tile*:Linux:*:*) echo "$UNAME_MACHINE"-unknown-linux-"$LIBC" exit ;; vax:Linux:*:*) echo "$UNAME_MACHINE"-dec-linux-"$LIBC" exit ;; x86_64:Linux:*:*) if objdump -f /bin/sh | grep -q elf32-x86-64; then echo "$UNAME_MACHINE"-pc-linux-"$LIBC"x32 else echo "$UNAME_MACHINE"-pc-linux-"$LIBC" fi exit ;; xtensa*:Linux:*:*) echo "$UNAME_MACHINE"-unknown-linux-"$LIBC" exit ;; i*86:DYNIX/ptx:4*:*) # ptx 4.0 does uname -s correctly, with DYNIX/ptx in there. # earlier versions are messed up and put the nodename in both # sysname and nodename. echo i386-sequent-sysv4 exit ;; i*86:UNIX_SV:4.2MP:2.*) # Unixware is an offshoot of SVR4, but it has its own version # number series starting with 2... # I am not positive that other SVR4 systems won't match this, # I just have to hope. -- rms. # Use sysv4.2uw... so that sysv4* matches it. echo "$UNAME_MACHINE"-pc-sysv4.2uw"$UNAME_VERSION" exit ;; i*86:OS/2:*:*) # If we were able to find `uname', then EMX Unix compatibility # is probably installed. echo "$UNAME_MACHINE"-pc-os2-emx exit ;; i*86:XTS-300:*:STOP) echo "$UNAME_MACHINE"-unknown-stop exit ;; i*86:atheos:*:*) echo "$UNAME_MACHINE"-unknown-atheos exit ;; i*86:syllable:*:*) echo "$UNAME_MACHINE"-pc-syllable exit ;; i*86:LynxOS:2.*:* | i*86:LynxOS:3.[01]*:* | i*86:LynxOS:4.[02]*:*) echo i386-unknown-lynxos"$UNAME_RELEASE" exit ;; i*86:*DOS:*:*) echo "$UNAME_MACHINE"-pc-msdosdjgpp exit ;; i*86:*:4.*:*) UNAME_REL=`echo "$UNAME_RELEASE" | sed 's/\/MP$//'` if grep Novell /usr/include/link.h >/dev/null 2>/dev/null; then echo "$UNAME_MACHINE"-univel-sysv"$UNAME_REL" else echo "$UNAME_MACHINE"-pc-sysv"$UNAME_REL" fi exit ;; i*86:*:5:[678]*) # UnixWare 7.x, OpenUNIX and OpenServer 6. case `/bin/uname -X | grep "^Machine"` in *486*) UNAME_MACHINE=i486 ;; *Pentium) UNAME_MACHINE=i586 ;; *Pent*|*Celeron) UNAME_MACHINE=i686 ;; esac echo "$UNAME_MACHINE-unknown-sysv${UNAME_RELEASE}${UNAME_SYSTEM}{$UNAME_VERSION}" exit ;; i*86:*:3.2:*) if test -f /usr/options/cb.name; then UNAME_REL=`sed -n 's/.*Version //p' /dev/null >/dev/null ; then UNAME_REL=`(/bin/uname -X|grep Release|sed -e 's/.*= //')` (/bin/uname -X|grep i80486 >/dev/null) && UNAME_MACHINE=i486 (/bin/uname -X|grep '^Machine.*Pentium' >/dev/null) \ && UNAME_MACHINE=i586 (/bin/uname -X|grep '^Machine.*Pent *II' >/dev/null) \ && UNAME_MACHINE=i686 (/bin/uname -X|grep '^Machine.*Pentium Pro' >/dev/null) \ && UNAME_MACHINE=i686 echo "$UNAME_MACHINE"-pc-sco"$UNAME_REL" else echo "$UNAME_MACHINE"-pc-sysv32 fi exit ;; pc:*:*:*) # Left here for compatibility: # uname -m prints for DJGPP always 'pc', but it prints nothing about # the processor, so we play safe by assuming i586. # Note: whatever this is, it MUST be the same as what config.sub # prints for the "djgpp" host, or else GDB configure will decide that # this is a cross-build. echo i586-pc-msdosdjgpp exit ;; Intel:Mach:3*:*) echo i386-pc-mach3 exit ;; paragon:*:*:*) echo i860-intel-osf1 exit ;; i860:*:4.*:*) # i860-SVR4 if grep Stardent /usr/include/sys/uadmin.h >/dev/null 2>&1 ; then echo i860-stardent-sysv"$UNAME_RELEASE" # Stardent Vistra i860-SVR4 else # Add other i860-SVR4 vendors below as they are discovered. echo i860-unknown-sysv"$UNAME_RELEASE" # Unknown i860-SVR4 fi exit ;; mini*:CTIX:SYS*5:*) # "miniframe" echo m68010-convergent-sysv exit ;; mc68k:UNIX:SYSTEM5:3.51m) echo m68k-convergent-sysv exit ;; M680?0:D-NIX:5.3:*) echo m68k-diab-dnix exit ;; M68*:*:R3V[5678]*:*) test -r /sysV68 && { echo 'm68k-motorola-sysv'; exit; } ;; 3[345]??:*:4.0:3.0 | 3[34]??A:*:4.0:3.0 | 3[34]??,*:*:4.0:3.0 | 3[34]??/*:*:4.0:3.0 | 4400:*:4.0:3.0 | 4850:*:4.0:3.0 | SKA40:*:4.0:3.0 | SDS2:*:4.0:3.0 | SHG2:*:4.0:3.0 | S7501*:*:4.0:3.0) OS_REL='' test -r /etc/.relid \ && OS_REL=.`sed -n 's/[^ ]* [^ ]* \([0-9][0-9]\).*/\1/p' < /etc/.relid` /bin/uname -p 2>/dev/null | grep 86 >/dev/null \ && { echo i486-ncr-sysv4.3"$OS_REL"; exit; } /bin/uname -p 2>/dev/null | /bin/grep entium >/dev/null \ && { echo i586-ncr-sysv4.3"$OS_REL"; exit; } ;; 3[34]??:*:4.0:* | 3[34]??,*:*:4.0:*) /bin/uname -p 2>/dev/null | grep 86 >/dev/null \ && { echo i486-ncr-sysv4; exit; } ;; NCR*:*:4.2:* | MPRAS*:*:4.2:*) OS_REL='.3' test -r /etc/.relid \ && OS_REL=.`sed -n 's/[^ ]* [^ ]* \([0-9][0-9]\).*/\1/p' < /etc/.relid` /bin/uname -p 2>/dev/null | grep 86 >/dev/null \ && { echo i486-ncr-sysv4.3"$OS_REL"; exit; } /bin/uname -p 2>/dev/null | /bin/grep entium >/dev/null \ && { echo i586-ncr-sysv4.3"$OS_REL"; exit; } /bin/uname -p 2>/dev/null | /bin/grep pteron >/dev/null \ && { echo i586-ncr-sysv4.3"$OS_REL"; exit; } ;; m68*:LynxOS:2.*:* | m68*:LynxOS:3.0*:*) echo m68k-unknown-lynxos"$UNAME_RELEASE" exit ;; mc68030:UNIX_System_V:4.*:*) echo m68k-atari-sysv4 exit ;; TSUNAMI:LynxOS:2.*:*) echo sparc-unknown-lynxos"$UNAME_RELEASE" exit ;; rs6000:LynxOS:2.*:*) echo rs6000-unknown-lynxos"$UNAME_RELEASE" exit ;; PowerPC:LynxOS:2.*:* | PowerPC:LynxOS:3.[01]*:* | PowerPC:LynxOS:4.[02]*:*) echo powerpc-unknown-lynxos"$UNAME_RELEASE" exit ;; SM[BE]S:UNIX_SV:*:*) echo mips-dde-sysv"$UNAME_RELEASE" exit ;; RM*:ReliantUNIX-*:*:*) echo mips-sni-sysv4 exit ;; RM*:SINIX-*:*:*) echo mips-sni-sysv4 exit ;; *:SINIX-*:*:*) if uname -p 2>/dev/null >/dev/null ; then UNAME_MACHINE=`(uname -p) 2>/dev/null` echo "$UNAME_MACHINE"-sni-sysv4 else echo ns32k-sni-sysv fi exit ;; PENTIUM:*:4.0*:*) # Unisys `ClearPath HMP IX 4000' SVR4/MP effort # says echo i586-unisys-sysv4 exit ;; *:UNIX_System_V:4*:FTX*) # From Gerald Hewes . # How about differentiating between stratus architectures? -djm echo hppa1.1-stratus-sysv4 exit ;; *:*:*:FTX*) # From seanf@swdc.stratus.com. echo i860-stratus-sysv4 exit ;; i*86:VOS:*:*) # From Paul.Green@stratus.com. echo "$UNAME_MACHINE"-stratus-vos exit ;; *:VOS:*:*) # From Paul.Green@stratus.com. echo hppa1.1-stratus-vos exit ;; mc68*:A/UX:*:*) echo m68k-apple-aux"$UNAME_RELEASE" exit ;; news*:NEWS-OS:6*:*) echo mips-sony-newsos6 exit ;; R[34]000:*System_V*:*:* | R4000:UNIX_SYSV:*:* | R*000:UNIX_SV:*:*) if [ -d /usr/nec ]; then echo mips-nec-sysv"$UNAME_RELEASE" else echo mips-unknown-sysv"$UNAME_RELEASE" fi exit ;; BeBox:BeOS:*:*) # BeOS running on hardware made by Be, PPC only. echo powerpc-be-beos exit ;; BeMac:BeOS:*:*) # BeOS running on Mac or Mac clone, PPC only. echo powerpc-apple-beos exit ;; BePC:BeOS:*:*) # BeOS running on Intel PC compatible. echo i586-pc-beos exit ;; BePC:Haiku:*:*) # Haiku running on Intel PC compatible. echo i586-pc-haiku exit ;; x86_64:Haiku:*:*) echo x86_64-unknown-haiku exit ;; SX-4:SUPER-UX:*:*) echo sx4-nec-superux"$UNAME_RELEASE" exit ;; SX-5:SUPER-UX:*:*) echo sx5-nec-superux"$UNAME_RELEASE" exit ;; SX-6:SUPER-UX:*:*) echo sx6-nec-superux"$UNAME_RELEASE" exit ;; SX-7:SUPER-UX:*:*) echo sx7-nec-superux"$UNAME_RELEASE" exit ;; SX-8:SUPER-UX:*:*) echo sx8-nec-superux"$UNAME_RELEASE" exit ;; SX-8R:SUPER-UX:*:*) echo sx8r-nec-superux"$UNAME_RELEASE" exit ;; SX-ACE:SUPER-UX:*:*) echo sxace-nec-superux"$UNAME_RELEASE" exit ;; Power*:Rhapsody:*:*) echo powerpc-apple-rhapsody"$UNAME_RELEASE" exit ;; *:Rhapsody:*:*) echo "$UNAME_MACHINE"-apple-rhapsody"$UNAME_RELEASE" exit ;; *:Darwin:*:*) UNAME_PROCESSOR=`uname -p` || UNAME_PROCESSOR=unknown eval "$set_cc_for_build" if test "$UNAME_PROCESSOR" = unknown ; then UNAME_PROCESSOR=powerpc fi if test "`echo "$UNAME_RELEASE" | sed -e 's/\..*//'`" -le 10 ; then if [ "$CC_FOR_BUILD" != no_compiler_found ]; then if (echo '#ifdef __LP64__'; echo IS_64BIT_ARCH; echo '#endif') | \ (CCOPTS="" $CC_FOR_BUILD -E - 2>/dev/null) | \ grep IS_64BIT_ARCH >/dev/null then case $UNAME_PROCESSOR in i386) UNAME_PROCESSOR=x86_64 ;; powerpc) UNAME_PROCESSOR=powerpc64 ;; esac fi # On 10.4-10.6 one might compile for PowerPC via gcc -arch ppc if (echo '#ifdef __POWERPC__'; echo IS_PPC; echo '#endif') | \ (CCOPTS="" $CC_FOR_BUILD -E - 2>/dev/null) | \ grep IS_PPC >/dev/null then UNAME_PROCESSOR=powerpc fi fi elif test "$UNAME_PROCESSOR" = i386 ; then # Avoid executing cc on OS X 10.9, as it ships with a stub # that puts up a graphical alert prompting to install # developer tools. Any system running Mac OS X 10.7 or # later (Darwin 11 and later) is required to have a 64-bit # processor. This is not true of the ARM version of Darwin # that Apple uses in portable devices. UNAME_PROCESSOR=x86_64 fi echo "$UNAME_PROCESSOR"-apple-darwin"$UNAME_RELEASE" exit ;; *:procnto*:*:* | *:QNX:[0123456789]*:*) UNAME_PROCESSOR=`uname -p` if test "$UNAME_PROCESSOR" = x86; then UNAME_PROCESSOR=i386 UNAME_MACHINE=pc fi echo "$UNAME_PROCESSOR"-"$UNAME_MACHINE"-nto-qnx"$UNAME_RELEASE" exit ;; *:QNX:*:4*) echo i386-pc-qnx exit ;; NEO-*:NONSTOP_KERNEL:*:*) echo neo-tandem-nsk"$UNAME_RELEASE" exit ;; NSE-*:NONSTOP_KERNEL:*:*) echo nse-tandem-nsk"$UNAME_RELEASE" exit ;; NSR-*:NONSTOP_KERNEL:*:*) echo nsr-tandem-nsk"$UNAME_RELEASE" exit ;; NSV-*:NONSTOP_KERNEL:*:*) echo nsv-tandem-nsk"$UNAME_RELEASE" exit ;; NSX-*:NONSTOP_KERNEL:*:*) echo nsx-tandem-nsk"$UNAME_RELEASE" exit ;; *:NonStop-UX:*:*) echo mips-compaq-nonstopux exit ;; BS2000:POSIX*:*:*) echo bs2000-siemens-sysv exit ;; DS/*:UNIX_System_V:*:*) echo "$UNAME_MACHINE"-"$UNAME_SYSTEM"-"$UNAME_RELEASE" exit ;; *:Plan9:*:*) # "uname -m" is not consistent, so use $cputype instead. 386 # is converted to i386 for consistency with other x86 # operating systems. if test "$cputype" = 386; then UNAME_MACHINE=i386 else UNAME_MACHINE="$cputype" fi echo "$UNAME_MACHINE"-unknown-plan9 exit ;; *:TOPS-10:*:*) echo pdp10-unknown-tops10 exit ;; *:TENEX:*:*) echo pdp10-unknown-tenex exit ;; KS10:TOPS-20:*:* | KL10:TOPS-20:*:* | TYPE4:TOPS-20:*:*) echo pdp10-dec-tops20 exit ;; XKL-1:TOPS-20:*:* | TYPE5:TOPS-20:*:*) echo pdp10-xkl-tops20 exit ;; *:TOPS-20:*:*) echo pdp10-unknown-tops20 exit ;; *:ITS:*:*) echo pdp10-unknown-its exit ;; SEI:*:*:SEIUX) echo mips-sei-seiux"$UNAME_RELEASE" exit ;; *:DragonFly:*:*) echo "$UNAME_MACHINE"-unknown-dragonfly"`echo "$UNAME_RELEASE"|sed -e 's/[-(].*//'`" exit ;; *:*VMS:*:*) UNAME_MACHINE=`(uname -p) 2>/dev/null` case "$UNAME_MACHINE" in A*) echo alpha-dec-vms ; exit ;; I*) echo ia64-dec-vms ; exit ;; V*) echo vax-dec-vms ; exit ;; esac ;; *:XENIX:*:SysV) echo i386-pc-xenix exit ;; i*86:skyos:*:*) echo "$UNAME_MACHINE"-pc-skyos"`echo "$UNAME_RELEASE" | sed -e 's/ .*$//'`" exit ;; i*86:rdos:*:*) echo "$UNAME_MACHINE"-pc-rdos exit ;; i*86:AROS:*:*) echo "$UNAME_MACHINE"-pc-aros exit ;; x86_64:VMkernel:*:*) echo "$UNAME_MACHINE"-unknown-esx exit ;; amd64:Isilon\ OneFS:*:*) echo x86_64-unknown-onefs exit ;; esac echo "$0: unable to guess system type" >&2 case "$UNAME_MACHINE:$UNAME_SYSTEM" in mips:Linux | mips64:Linux) # If we got here on MIPS GNU/Linux, output extra information. cat >&2 <&2 </dev/null || echo unknown` uname -r = `(uname -r) 2>/dev/null || echo unknown` uname -s = `(uname -s) 2>/dev/null || echo unknown` uname -v = `(uname -v) 2>/dev/null || echo unknown` /usr/bin/uname -p = `(/usr/bin/uname -p) 2>/dev/null` /bin/uname -X = `(/bin/uname -X) 2>/dev/null` hostinfo = `(hostinfo) 2>/dev/null` /bin/universe = `(/bin/universe) 2>/dev/null` /usr/bin/arch -k = `(/usr/bin/arch -k) 2>/dev/null` /bin/arch = `(/bin/arch) 2>/dev/null` /usr/bin/oslevel = `(/usr/bin/oslevel) 2>/dev/null` /usr/convex/getsysinfo = `(/usr/convex/getsysinfo) 2>/dev/null` UNAME_MACHINE = "$UNAME_MACHINE" UNAME_RELEASE = "$UNAME_RELEASE" UNAME_SYSTEM = "$UNAME_SYSTEM" UNAME_VERSION = "$UNAME_VERSION" EOF exit 1 # Local variables: # eval: (add-hook 'write-file-functions 'time-stamp) # time-stamp-start: "timestamp='" # time-stamp-format: "%:y-%02m-%02d" # time-stamp-end: "'" # End: frr-7.2.1/config.h.in0000644000000000000000000003774713610377563011261 00000000000000/* config.h.in. Generated from configure.ac by autoheader. */ /* Define if building universal (internal helper macro) */ #undef AC_APPLE_UNIVERSAL_BUILD /* bfdd control socket */ #undef BFDD_CONTROL_SOCKET /* bfdd */ #undef BFD_BSD /* bfdd */ #undef BFD_LINUX /* BSD v6 sysctl to turn on and off forwarding */ #undef BSD_V6_SYSCTL /* Mask for config files */ #undef CONFIGFILE_MASK /* Consumed Time Check */ #undef CONSUMED_TIME_CHECK /* Compile with internal cryptographic implementation */ #undef CRYPTO_INTERNAL /* Compile with openssl support */ #undef CRYPTO_OPENSSL /* daemon database directory */ #undef DAEMON_DB_DIR /* daemon vty directory */ #undef DAEMON_VTY_DIR /* Build for development */ #undef DEV_BUILD /* Name of the configuration default set */ #undef DFLT_NAME /* Disable BGP installation to zebra */ #undef DISABLE_BGP_ANNOUNCE /* Enable BGP VNC support */ #undef ENABLE_BGP_VNC /* found_ssh */ #undef FOUND_SSH /* did autoconf checks for atomic funcs */ #undef FRR_AUTOCONF_ATOMIC /* frr Group */ #undef FRR_GROUP /* frr User */ #undef FRR_USER /* include git version info */ #undef GIT_VERSION /* GNU Linux */ #undef GNU_LINUX /* Compile extensions to use with a fuzzer for netlink */ #undef HANDLE_NETLINK_FUZZING /* Compile extensions to use with a fuzzer */ #undef HANDLE_ZAPI_FUZZING /* Have history.h append_history */ #undef HAVE_APPEND_HISTORY /* Define to 1 if you have the header file. */ #undef HAVE_ASM_TYPES_H /* bfdd */ #undef HAVE_BFDD /* BSD ifi_link_state available */ #undef HAVE_BSD_IFI_LINK_STATE /* BSD link-detect */ #undef HAVE_BSD_LINK_DETECT /* Can pass ifindex in struct ip_mreq */ #undef HAVE_BSD_STRUCT_IP_MREQ_HACK /* capabilities */ #undef HAVE_CAPABILITIES /* Have monotonic clock */ #undef HAVE_CLOCK_MONOTONIC /* Enable confd integration */ #undef HAVE_CONFD /* Enable configuration rollbacks */ #undef HAVE_CONFIG_ROLLBACKS /* Compile Special Cumulus Code in */ #undef HAVE_CUMULUS /* Compile extensions for a DataCenter */ #undef HAVE_DATACENTER /* Define to 1 if you have the declaration of `be32dec', and to 0 if you don't. */ #undef HAVE_DECL_BE32DEC /* Define to 1 if you have the declaration of `be32enc', and to 0 if you don't. */ #undef HAVE_DECL_BE32ENC /* Define to 1 if you have the declaration of `TCP_MD5SIG', and to 0 if you don't. */ #undef HAVE_DECL_TCP_MD5SIG /* Define to 1 if you have the header file. */ #undef HAVE_DLFCN_H /* Have dlinfo RTLD_DI_LINKMAP */ #undef HAVE_DLINFO_LINKMAP /* Have dlinfo RTLD_DI_ORIGIN */ #undef HAVE_DLINFO_ORIGIN /* Define to 1 if you have the `getgrouplist' function. */ #undef HAVE_GETGROUPLIST /* Glibc backtrace */ #undef HAVE_GLIBC_BACKTRACE /* Enable the gRPC northbound plugin */ #undef HAVE_GRPC /* Define to 1 if you have the header file. */ #undef HAVE_INET_ND_H /* Define to 1 if you have the header file. */ #undef HAVE_INTTYPES_H /* Have IP_FREEBIND */ #undef HAVE_IP_FREEBIND /* Have IP_PKTINFO */ #undef HAVE_IP_PKTINFO /* Have IP_RECVDSTADDR */ #undef HAVE_IP_RECVDSTADDR /* Have IP_RECVIF */ #undef HAVE_IP_RECVIF /* Define to 1 if you have the header file. */ #undef HAVE_JSON_C_JSON_H /* Define to 1 if you have the header file. */ #undef HAVE_LAUXLIB_H /* Capabilities */ #undef HAVE_LCAPS /* ldpd */ #undef HAVE_LDPD /* Define to 1 if you have the `crypt' library (-lcrypt). */ #undef HAVE_LIBCRYPT /* Define to 1 if you have the `crypto' library (-lcrypto). */ #undef HAVE_LIBCRYPTO /* Define to 1 if you have the `nsl' library (-lnsl). */ #undef HAVE_LIBNSL /* Define to 1 if you have the `pcreposix' library (-lpcreposix). */ #undef HAVE_LIBPCREPOSIX /* Define to 1 if you have the `resolv' library (-lresolv). */ #undef HAVE_LIBRESOLV /* Define to 1 if you have the `socket' library (-lsocket). */ #undef HAVE_LIBSOCKET /* Define to 1 if you have the `umem' library (-lumem). */ #undef HAVE_LIBUMEM /* libunwind */ #undef HAVE_LIBUNWIND /* Define to 1 if you have the header file. */ #undef HAVE_LINK_H /* Define to 1 if you have the header file. */ #undef HAVE_LINUX_MROUTE_H /* Define to 1 if you have the header file. */ #undef HAVE_LINUX_VERSION_H /* Have support for Lua interpreter */ #undef HAVE_LUA /* Define to 1 if you have the header file. */ #undef HAVE_LUACONF_H /* Define to 1 if you have the header file. */ #undef HAVE_LUALIB_H /* Define to 1 if you have the header file. */ #undef HAVE_LUA_H /* mallinfo */ #undef HAVE_MALLINFO /* Define to 1 if you have the header file. */ #undef HAVE_MALLOC_H /* Define to 1 if you have the header file. */ #undef HAVE_MALLOC_MALLOC_H /* Define to 1 if you have the header file. */ #undef HAVE_MALLOC_NP_H /* malloc_size */ #undef HAVE_MALLOC_SIZE /* malloc_usable_size */ #undef HAVE_MALLOC_USABLE_SIZE /* Define to 1 if you have the header file. */ #undef HAVE_MEMORY_H /* Define to 1 if you have the header file. */ #undef HAVE_NETINET6_IN6_H /* Define to 1 if you have the header file. */ #undef HAVE_NETINET6_IN6_VAR_H /* Define to 1 if you have the header file. */ #undef HAVE_NETINET6_ND6_H /* Define to 1 if you have the header file. */ #undef HAVE_NETINET_IN6_VAR_H /* Define to 1 if you have the header file. */ #undef HAVE_NETINET_IN_VAR_H /* Define to 1 if you have the header file. */ #undef HAVE_NETINET_IP_ICMP_H /* Define to 1 if you have the header file. */ #undef HAVE_NETINET_IP_MROUTE_H /* netlink */ #undef HAVE_NETLINK /* Have netns */ #undef HAVE_NETNS /* Define to 1 if you have the header file. */ #undef HAVE_NET_IF_DL_H /* Define to 1 if you have the header file. */ #undef HAVE_NET_IF_VAR_H /* Define to 1 if you have the header file. */ #undef HAVE_NET_NETOPT_H /* NET_RT_IFLIST */ #undef HAVE_NET_RT_IFLIST /* Have openpam.h */ #undef HAVE_OPENPAM_H /* Have pam_misc.h */ #undef HAVE_PAM_MISC_H /* have NetBSD pollts() */ #undef HAVE_POLLTS /* have Linux/BSD ppoll() */ #undef HAVE_PPOLL /* Solaris printstack */ #undef HAVE_PRINTSTACK /* Define to 1 if you have the header file. */ #undef HAVE_PRIV_H /* protobuf */ #undef HAVE_PROTOBUF /* prctl */ #undef HAVE_PR_SET_KEEPCAPS /* Have pthread.h pthread_condattr_setclock */ #undef HAVE_PTHREAD_CONDATTR_SETCLOCK /* Define to 1 if you have the header file. */ #undef HAVE_PTHREAD_NP_H /* Have PTHREAD_PRIO_INHERIT. */ #undef HAVE_PTHREAD_PRIO_INHERIT /* Define to 1 if you have the `pthread_setname_np' function. */ #undef HAVE_PTHREAD_SETNAME_NP /* Define to 1 if you have the `pthread_set_name_np' function. */ #undef HAVE_PTHREAD_SET_NAME_NP /* Have RFC3678 protocol-independed API */ #undef HAVE_RFC3678 /* Enable IPv6 Routing Advertisement support */ #undef HAVE_RTADV /* Define to 1 if you have the `setns' function. */ #undef HAVE_SETNS /* Allow user to use ssh/telnet/bash, be aware this is considered insecure */ #undef HAVE_SHELL_ACCESS /* getpflags */ #undef HAVE_SOLARIS_CAPABILITIES /* Have SO_BINDANY */ #undef HAVE_SO_BINDANY /* Enable sqlite3 database */ #undef HAVE_SQLITE3 /* found stdatomic.h */ #undef HAVE_STDATOMIC_H /* Define to 1 if you have the header file. */ #undef HAVE_STDINT_H /* Define to 1 if you have the header file. */ #undef HAVE_STDLIB_H /* Define to 1 if you have the header file. */ #undef HAVE_STRINGS_H /* Define to 1 if you have the header file. */ #undef HAVE_STRING_H /* Define to 1 if you have the `strlcat' function. */ #undef HAVE_STRLCAT /* Define to 1 if you have the `strlcpy' function. */ #undef HAVE_STRLCPY /* Define to 1 if you have the header file. */ #undef HAVE_STROPTS_H /* Define to 1 if the system has the type `struct icmphdr'. */ #undef HAVE_STRUCT_ICMPHDR /* Define to 1 if the system has the type `struct if6_aliasreq'. */ #undef HAVE_STRUCT_IF6_ALIASREQ /* Define to 1 if `ifra_lifetime' is a member of `struct if6_aliasreq'. */ #undef HAVE_STRUCT_IF6_ALIASREQ_IFRA_LIFETIME /* Define to 1 if the system has the type `struct ifaliasreq'. */ #undef HAVE_STRUCT_IFALIASREQ /* Define to 1 if `ifm_status' is a member of `struct ifmediareq'. */ #undef HAVE_STRUCT_IFMEDIAREQ_IFM_STATUS /* Define to 1 if `ifi_link_state' is a member of `struct if_data'. */ #undef HAVE_STRUCT_IF_DATA_IFI_LINK_STATE /* Define to 1 if the system has the type `struct igmpmsg'. */ #undef HAVE_STRUCT_IGMPMSG /* Define to 1 if the system has the type `struct in6_aliasreq'. */ #undef HAVE_STRUCT_IN6_ALIASREQ /* Define to 1 if the system has the type `struct in_pktinfo'. */ #undef HAVE_STRUCT_IN_PKTINFO /* Define to 1 if `imr_ifindex' is a member of `struct ip_mreqn'. */ #undef HAVE_STRUCT_IP_MREQN_IMR_IFINDEX /* Define to 1 if the system has the type `struct mfcctl'. */ #undef HAVE_STRUCT_MFCCTL /* Define to 1 if the system has the type `struct nd_opt_adv_interval'. */ #undef HAVE_STRUCT_ND_OPT_ADV_INTERVAL /* Define to 1 if `nd_opt_ai_type' is a member of `struct nd_opt_adv_interval'. */ #undef HAVE_STRUCT_ND_OPT_ADV_INTERVAL_ND_OPT_AI_TYPE /* Define to 1 if the system has the type `struct nd_opt_dnssl'. */ #undef HAVE_STRUCT_ND_OPT_DNSSL /* Define to 1 if the system has the type `struct nd_opt_homeagent_info'. */ #undef HAVE_STRUCT_ND_OPT_HOMEAGENT_INFO /* Define to 1 if the system has the type `struct nd_opt_rdnss'. */ #undef HAVE_STRUCT_ND_OPT_RDNSS /* Define to 1 if the system has the type `struct sioc_sg_req'. */ #undef HAVE_STRUCT_SIOC_SG_REQ /* Define to 1 if the system has the type `struct sioc_vif_req'. */ #undef HAVE_STRUCT_SIOC_VIF_REQ /* Define to 1 if the system has the type `struct sockaddr_dl'. */ #undef HAVE_STRUCT_SOCKADDR_DL /* Define to 1 if `sdl_len' is a member of `struct sockaddr_dl'. */ #undef HAVE_STRUCT_SOCKADDR_DL_SDL_LEN /* Define to 1 if `sin_len' is a member of `struct sockaddr_in'. */ #undef HAVE_STRUCT_SOCKADDR_IN_SIN_LEN /* Define to 1 if `sa_len' is a member of `struct sockaddr'. */ #undef HAVE_STRUCT_SOCKADDR_SA_LEN /* Define to 1 if `sun_len' is a member of `struct sockaddr_un'. */ #undef HAVE_STRUCT_SOCKADDR_UN_SUN_LEN /* Define to 1 if `domainname' is a member of `struct utsname'. */ #undef HAVE_STRUCT_UTSNAME_DOMAINNAME /* Define to 1 if the system has the type `struct vifctl'. */ #undef HAVE_STRUCT_VIFCTL /* Have Linux futex support */ #undef HAVE_SYNC_LINUX_FUTEX /* Have OpenBSD futex support */ #undef HAVE_SYNC_OPENBSD_FUTEX /* Have FreeBSD _umtx_op() support */ #undef HAVE_SYNC_UMTX_OP /* Enable sysrepo integration */ #undef HAVE_SYSREPO /* Compile systemd support in */ #undef HAVE_SYSTEMD /* Define to 1 if you have the header file. */ #undef HAVE_SYS_CAPABILITY_H /* Define to 1 if you have the header file. */ #undef HAVE_SYS_CONF_H /* Define to 1 if you have the header file. */ #undef HAVE_SYS_KSYM_H /* Define to 1 if you have the header file. */ #undef HAVE_SYS_SOCKIO_H /* Define to 1 if you have the header file. */ #undef HAVE_SYS_STAT_H /* Define to 1 if you have the header file. */ #undef HAVE_SYS_SYSCTL_H /* Define to 1 if you have the header file. */ #undef HAVE_SYS_TYPES_H /* Define to 1 if you have the header file. */ #undef HAVE_UCONTEXT_H /* Define to 1 if `uc_mcontext.gregs' is a member of `ucontext_t'. */ #undef HAVE_UCONTEXT_T_UC_MCONTEXT_GREGS /* Define to 1 if `uc_mcontext.regs' is a member of `ucontext_t'. */ #undef HAVE_UCONTEXT_T_UC_MCONTEXT_REGS /* Define to 1 if `uc_mcontext.regs.nip' is a member of `ucontext_t'. */ #undef HAVE_UCONTEXT_T_UC_MCONTEXT_REGS_NIP /* Define to 1 if `uc_mcontext.uc_regs' is a member of `ucontext_t'. */ #undef HAVE_UCONTEXT_T_UC_MCONTEXT_UC_REGS /* Define to 1 if you have the header file. */ #undef HAVE_UNISTD_H /* Compile in v6 Route Replacement Semantics */ #undef HAVE_V6_RR_SEMANTICS /* Define to 1 if the system has the type `vifi_t'. */ #undef HAVE_VIFI_T /* Enable ZeroMQ support */ #undef HAVE_ZEROMQ /* found __atomic builtins */ #undef HAVE___ATOMIC /* found __sync builtins */ #undef HAVE___SYNC /* found __sync_swap builtin */ #undef HAVE___SYNC_SWAP /* .interp value */ #undef INTERP /* Linux ipv6 Min Hop Count */ #undef IPV6_MINHOPCOUNT /* selected method for isis, == one of the constants */ #undef ISIS_METHOD /* constant value for isis method bpf */ #undef ISIS_METHOD_BPF /* constant value for isis method dlpi */ #undef ISIS_METHOD_DLPI /* constant value for isis method pfpacket */ #undef ISIS_METHOD_PFPACKET /* KAME IPv6 */ #undef KAME /* Define for compiling with old vpn commands */ #undef KEEP_OLD_VPN_COMMANDS /* ldpd control socket */ #undef LDPD_SOCKET /* Linux IPv6 stack */ #undef LINUX_IPV6 /* Mask for log files */ #undef LOGFILE_MASK /* Define to the sub-directory where libtool stores uninstalled libraries. */ #undef LT_OBJDIR /* path to modules */ #undef MODULE_PATH /* Maximum number of paths for a route */ #undef MULTIPATH_NUM /* OpenBSD */ #undef OPEN_BSD /* Name of package */ #undef PACKAGE /* Define to the address where bug reports for this package should be sent. */ #undef PACKAGE_BUGREPORT /* Define to the full name of this package. */ #undef PACKAGE_NAME /* Define to the full name and version of this package. */ #undef PACKAGE_STRING /* Define to the one symbol short name of this package. */ #undef PACKAGE_TARNAME /* Define to the home page for this package. */ #undef PACKAGE_URL /* Define to the version of this package. */ #undef PACKAGE_VERSION /* Have openpam_ttyconv */ #undef PAM_CONV_FUNC /* Define to necessary symbol if this constant uses a non-standard name on your system. */ #undef PTHREAD_CREATE_JOINABLE /* Use SNMP AgentX to interface with snmpd */ #undef SNMP_AGENTX /* Solaris IPv6 */ #undef SOLARIS_IPV6 /* Define to 1 if you have the ANSI C header files. */ #undef STDC_HEADERS /* SunOS 5 */ #undef SUNOS_5 /* OSPFAPI */ #undef SUPPORT_OSPF_API /* Realms support */ #undef SUPPORT_REALMS /* Use PAM for authentication */ #undef USE_PAM /* Enable extensions on AIX 3, Interix. */ #ifndef _ALL_SOURCE # undef _ALL_SOURCE #endif /* Enable GNU extensions on systems that have them. */ #ifndef _GNU_SOURCE # undef _GNU_SOURCE #endif /* Enable threading extensions on Solaris. */ #ifndef _POSIX_PTHREAD_SEMANTICS # undef _POSIX_PTHREAD_SEMANTICS #endif /* Enable extensions on HP NonStop. */ #ifndef _TANDEM_SOURCE # undef _TANDEM_SOURCE #endif /* Enable general extensions on Solaris. */ #ifndef __EXTENSIONS__ # undef __EXTENSIONS__ #endif /* Version number of package */ #undef VERSION /* VTY shell */ #undef VTYSH /* path to vtysh binary */ #undef VTYSH_BIN_PATH /* What pager to use */ #undef VTYSH_PAGER /* VTY Sockets Group */ #undef VTY_GROUP /* path to watchfrr.sh */ #undef WATCHFRR_SH_PATH /* Define WORDS_BIGENDIAN to 1 if your processor stores words with the most significant byte first (like Motorola and SPARC, unlike Intel). */ #if defined AC_APPLE_UNIVERSAL_BUILD # if defined __BIG_ENDIAN__ # define WORDS_BIGENDIAN 1 # endif #else # ifndef WORDS_BIGENDIAN # undef WORDS_BIGENDIAN # endif #endif /* path to YANG data models */ #undef YANG_MODELS_PATH /* Define to 1 if `lex' declares `yytext' as a `char *' by default, not a `char[]'. */ #undef YYTEXT_POINTER /* zebra api socket */ #undef ZEBRA_SERV_PATH /* Enable large inode numbers on Mac OS X 10.5. */ #ifndef _DARWIN_USE_64_BIT_INODE # define _DARWIN_USE_64_BIT_INODE 1 #endif /* Number of bits in a file offset, on hosts where this is settable. */ #undef _FILE_OFFSET_BITS /* Define for large files, on AIX-style hosts. */ #undef _LARGE_FILES /* Define to 1 if on MINIX. */ #undef _MINIX /* Define to 2 if the system does not provide POSIX.1 features except with this defined. */ #undef _POSIX_1_SOURCE /* enable POSIX.1-2008 and XPG7/SUSv4 */ #undef _POSIX_C_SOURCE /* Define to 1 if you need to in order for `stat' and other things to work. */ #undef _POSIX_SOURCE /* Old readline */ #undef rl_completion_matches frr-7.2.1/config.sub0000755000000000000000000010645013610377563011205 00000000000000#! /bin/sh # Configuration validation subroutine script. # Copyright 1992-2018 Free Software Foundation, Inc. timestamp='2018-02-22' # This file is free software; you can redistribute it and/or modify it # under the terms of the GNU General Public License as published by # the Free Software Foundation; either version 3 of the License, or # (at your option) any later version. # # This program is distributed in the hope that it will be useful, but # WITHOUT ANY WARRANTY; without even the implied warranty of # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU # General Public License for more details. # # You should have received a copy of the GNU General Public License # along with this program; if not, see . # # As a special exception to the GNU General Public License, if you # distribute this file as part of a program that contains a # configuration script generated by Autoconf, you may include it under # the same distribution terms that you use for the rest of that # program. This Exception is an additional permission under section 7 # of the GNU General Public License, version 3 ("GPLv3"). # Please send patches to . # # Configuration subroutine to validate and canonicalize a configuration type. # Supply the specified configuration type as an argument. # If it is invalid, we print an error message on stderr and exit with code 1. # Otherwise, we print the canonical config type on stdout and succeed. # You can get the latest version of this script from: # https://git.savannah.gnu.org/gitweb/?p=config.git;a=blob_plain;f=config.sub # This file is supposed to be the same for all GNU packages # and recognize all the CPU types, system types and aliases # that are meaningful with *any* GNU software. # Each package is responsible for reporting which valid configurations # it does not support. The user should be able to distinguish # a failure to support a valid configuration from a meaningless # configuration. # The goal of this file is to map all the various variations of a given # machine specification into a single specification in the form: # CPU_TYPE-MANUFACTURER-OPERATING_SYSTEM # or in some cases, the newer four-part form: # CPU_TYPE-MANUFACTURER-KERNEL-OPERATING_SYSTEM # It is wrong to echo any other type of specification. me=`echo "$0" | sed -e 's,.*/,,'` usage="\ Usage: $0 [OPTION] CPU-MFR-OPSYS or ALIAS Canonicalize a configuration name. Options: -h, --help print this help, then exit -t, --time-stamp print date of last modification, then exit -v, --version print version number, then exit Report bugs and patches to ." version="\ GNU config.sub ($timestamp) Copyright 1992-2018 Free Software Foundation, Inc. This is free software; see the source for copying conditions. There is NO warranty; not even for MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE." help=" Try \`$me --help' for more information." # Parse command line while test $# -gt 0 ; do case $1 in --time-stamp | --time* | -t ) echo "$timestamp" ; exit ;; --version | -v ) echo "$version" ; exit ;; --help | --h* | -h ) echo "$usage"; exit ;; -- ) # Stop option processing shift; break ;; - ) # Use stdin as input. break ;; -* ) echo "$me: invalid option $1$help" exit 1 ;; *local*) # First pass through any local machine types. echo "$1" exit ;; * ) break ;; esac done case $# in 0) echo "$me: missing argument$help" >&2 exit 1;; 1) ;; *) echo "$me: too many arguments$help" >&2 exit 1;; esac # Separate what the user gave into CPU-COMPANY and OS or KERNEL-OS (if any). # Here we must recognize all the valid KERNEL-OS combinations. maybe_os=`echo "$1" | sed 's/^\(.*\)-\([^-]*-[^-]*\)$/\2/'` case $maybe_os in nto-qnx* | linux-gnu* | linux-android* | linux-dietlibc | linux-newlib* | \ linux-musl* | linux-uclibc* | uclinux-uclibc* | uclinux-gnu* | kfreebsd*-gnu* | \ knetbsd*-gnu* | netbsd*-gnu* | netbsd*-eabi* | \ kopensolaris*-gnu* | cloudabi*-eabi* | \ storm-chaos* | os2-emx* | rtmk-nova*) os=-$maybe_os basic_machine=`echo "$1" | sed 's/^\(.*\)-\([^-]*-[^-]*\)$/\1/'` ;; android-linux) os=-linux-android basic_machine=`echo "$1" | sed 's/^\(.*\)-\([^-]*-[^-]*\)$/\1/'`-unknown ;; *) basic_machine=`echo "$1" | sed 's/-[^-]*$//'` if [ "$basic_machine" != "$1" ] then os=`echo "$1" | sed 's/.*-/-/'` else os=; fi ;; esac ### Let's recognize common machines as not being operating systems so ### that things like config.sub decstation-3100 work. We also ### recognize some manufacturers as not being operating systems, so we ### can provide default operating systems below. case $os in -sun*os*) # Prevent following clause from handling this invalid input. ;; -dec* | -mips* | -sequent* | -encore* | -pc532* | -sgi* | -sony* | \ -att* | -7300* | -3300* | -delta* | -motorola* | -sun[234]* | \ -unicom* | -ibm* | -next | -hp | -isi* | -apollo | -altos* | \ -convergent* | -ncr* | -news | -32* | -3600* | -3100* | -hitachi* |\ -c[123]* | -convex* | -sun | -crds | -omron* | -dg | -ultra | -tti* | \ -harris | -dolphin | -highlevel | -gould | -cbm | -ns | -masscomp | \ -apple | -axis | -knuth | -cray | -microblaze*) os= basic_machine=$1 ;; -bluegene*) os=-cnk ;; -sim | -cisco | -oki | -wec | -winbond) os= basic_machine=$1 ;; -scout) ;; -wrs) os=-vxworks basic_machine=$1 ;; -chorusos*) os=-chorusos basic_machine=$1 ;; -chorusrdb) os=-chorusrdb basic_machine=$1 ;; -hiux*) os=-hiuxwe2 ;; -sco6) os=-sco5v6 basic_machine=`echo "$1" | sed -e 's/86-.*/86-pc/'` ;; -sco5) os=-sco3.2v5 basic_machine=`echo "$1" | sed -e 's/86-.*/86-pc/'` ;; -sco4) os=-sco3.2v4 basic_machine=`echo "$1" | sed -e 's/86-.*/86-pc/'` ;; -sco3.2.[4-9]*) os=`echo $os | sed -e 's/sco3.2./sco3.2v/'` basic_machine=`echo "$1" | sed -e 's/86-.*/86-pc/'` ;; -sco3.2v[4-9]*) # Don't forget version if it is 3.2v4 or newer. basic_machine=`echo "$1" | sed -e 's/86-.*/86-pc/'` ;; -sco5v6*) # Don't forget version if it is 3.2v4 or newer. basic_machine=`echo "$1" | sed -e 's/86-.*/86-pc/'` ;; -sco*) os=-sco3.2v2 basic_machine=`echo "$1" | sed -e 's/86-.*/86-pc/'` ;; -udk*) basic_machine=`echo "$1" | sed -e 's/86-.*/86-pc/'` ;; -isc) os=-isc2.2 basic_machine=`echo "$1" | sed -e 's/86-.*/86-pc/'` ;; -clix*) basic_machine=clipper-intergraph ;; -isc*) basic_machine=`echo "$1" | sed -e 's/86-.*/86-pc/'` ;; -lynx*178) os=-lynxos178 ;; -lynx*5) os=-lynxos5 ;; -lynx*) os=-lynxos ;; -ptx*) basic_machine=`echo "$1" | sed -e 's/86-.*/86-sequent/'` ;; -psos*) os=-psos ;; -mint | -mint[0-9]*) basic_machine=m68k-atari os=-mint ;; esac # Decode aliases for certain CPU-COMPANY combinations. case $basic_machine in # Recognize the basic CPU types without company name. # Some are omitted here because they have special meanings below. 1750a | 580 \ | a29k \ | aarch64 | aarch64_be \ | alpha | alphaev[4-8] | alphaev56 | alphaev6[78] | alphapca5[67] \ | alpha64 | alpha64ev[4-8] | alpha64ev56 | alpha64ev6[78] | alpha64pca5[67] \ | am33_2.0 \ | arc | arceb \ | arm | arm[bl]e | arme[lb] | armv[2-8] | armv[3-8][lb] | armv7[arm] \ | avr | avr32 \ | ba \ | be32 | be64 \ | bfin \ | c4x | c8051 | clipper \ | d10v | d30v | dlx | dsp16xx \ | e2k | epiphany \ | fido | fr30 | frv | ft32 \ | h8300 | h8500 | hppa | hppa1.[01] | hppa2.0 | hppa2.0[nw] | hppa64 \ | hexagon \ | i370 | i860 | i960 | ia16 | ia64 \ | ip2k | iq2000 \ | k1om \ | le32 | le64 \ | lm32 \ | m32c | m32r | m32rle | m68000 | m68k | m88k \ | maxq | mb | microblaze | microblazeel | mcore | mep | metag \ | mips | mipsbe | mipseb | mipsel | mipsle \ | mips16 \ | mips64 | mips64el \ | mips64octeon | mips64octeonel \ | mips64orion | mips64orionel \ | mips64r5900 | mips64r5900el \ | mips64vr | mips64vrel \ | mips64vr4100 | mips64vr4100el \ | mips64vr4300 | mips64vr4300el \ | mips64vr5000 | mips64vr5000el \ | mips64vr5900 | mips64vr5900el \ | mipsisa32 | mipsisa32el \ | mipsisa32r2 | mipsisa32r2el \ | mipsisa32r6 | mipsisa32r6el \ | mipsisa64 | mipsisa64el \ | mipsisa64r2 | mipsisa64r2el \ | mipsisa64r6 | mipsisa64r6el \ | mipsisa64sb1 | mipsisa64sb1el \ | mipsisa64sr71k | mipsisa64sr71kel \ | mipsr5900 | mipsr5900el \ | mipstx39 | mipstx39el \ | mn10200 | mn10300 \ | moxie \ | mt \ | msp430 \ | nds32 | nds32le | nds32be \ | nios | nios2 | nios2eb | nios2el \ | ns16k | ns32k \ | open8 | or1k | or1knd | or32 \ | pdp10 | pj | pjl \ | powerpc | powerpc64 | powerpc64le | powerpcle \ | pru \ | pyramid \ | riscv32 | riscv64 \ | rl78 | rx \ | score \ | sh | sh[1234] | sh[24]a | sh[24]aeb | sh[23]e | sh[234]eb | sheb | shbe | shle | sh[1234]le | sh3ele \ | sh64 | sh64le \ | sparc | sparc64 | sparc64b | sparc64v | sparc86x | sparclet | sparclite \ | sparcv8 | sparcv9 | sparcv9b | sparcv9v \ | spu \ | tahoe | tic4x | tic54x | tic55x | tic6x | tic80 | tron \ | ubicom32 \ | v850 | v850e | v850e1 | v850e2 | v850es | v850e2v3 \ | visium \ | wasm32 \ | x86 | xc16x | xstormy16 | xtensa \ | z8k | z80) basic_machine=$basic_machine-unknown ;; c54x) basic_machine=tic54x-unknown ;; c55x) basic_machine=tic55x-unknown ;; c6x) basic_machine=tic6x-unknown ;; leon|leon[3-9]) basic_machine=sparc-$basic_machine ;; m6811 | m68hc11 | m6812 | m68hc12 | m68hcs12x | nvptx | picochip) basic_machine=$basic_machine-unknown os=-none ;; m88110 | m680[12346]0 | m683?2 | m68360 | m5200 | v70 | w65) ;; ms1) basic_machine=mt-unknown ;; strongarm | thumb | xscale) basic_machine=arm-unknown ;; xgate) basic_machine=$basic_machine-unknown os=-none ;; xscaleeb) basic_machine=armeb-unknown ;; xscaleel) basic_machine=armel-unknown ;; # We use `pc' rather than `unknown' # because (1) that's what they normally are, and # (2) the word "unknown" tends to confuse beginning users. i*86 | x86_64) basic_machine=$basic_machine-pc ;; # Object if more than one company name word. *-*-*) echo Invalid configuration \`"$1"\': machine \`"$basic_machine"\' not recognized 1>&2 exit 1 ;; # Recognize the basic CPU types with company name. 580-* \ | a29k-* \ | aarch64-* | aarch64_be-* \ | alpha-* | alphaev[4-8]-* | alphaev56-* | alphaev6[78]-* \ | alpha64-* | alpha64ev[4-8]-* | alpha64ev56-* | alpha64ev6[78]-* \ | alphapca5[67]-* | alpha64pca5[67]-* | arc-* | arceb-* \ | arm-* | armbe-* | armle-* | armeb-* | armv*-* \ | avr-* | avr32-* \ | ba-* \ | be32-* | be64-* \ | bfin-* | bs2000-* \ | c[123]* | c30-* | [cjt]90-* | c4x-* \ | c8051-* | clipper-* | craynv-* | cydra-* \ | d10v-* | d30v-* | dlx-* \ | e2k-* | elxsi-* \ | f30[01]-* | f700-* | fido-* | fr30-* | frv-* | fx80-* \ | h8300-* | h8500-* \ | hppa-* | hppa1.[01]-* | hppa2.0-* | hppa2.0[nw]-* | hppa64-* \ | hexagon-* \ | i*86-* | i860-* | i960-* | ia16-* | ia64-* \ | ip2k-* | iq2000-* \ | k1om-* \ | le32-* | le64-* \ | lm32-* \ | m32c-* | m32r-* | m32rle-* \ | m68000-* | m680[012346]0-* | m68360-* | m683?2-* | m68k-* \ | m88110-* | m88k-* | maxq-* | mcore-* | metag-* \ | microblaze-* | microblazeel-* \ | mips-* | mipsbe-* | mipseb-* | mipsel-* | mipsle-* \ | mips16-* \ | mips64-* | mips64el-* \ | mips64octeon-* | mips64octeonel-* \ | mips64orion-* | mips64orionel-* \ | mips64r5900-* | mips64r5900el-* \ | mips64vr-* | mips64vrel-* \ | mips64vr4100-* | mips64vr4100el-* \ | mips64vr4300-* | mips64vr4300el-* \ | mips64vr5000-* | mips64vr5000el-* \ | mips64vr5900-* | mips64vr5900el-* \ | mipsisa32-* | mipsisa32el-* \ | mipsisa32r2-* | mipsisa32r2el-* \ | mipsisa32r6-* | mipsisa32r6el-* \ | mipsisa64-* | mipsisa64el-* \ | mipsisa64r2-* | mipsisa64r2el-* \ | mipsisa64r6-* | mipsisa64r6el-* \ | mipsisa64sb1-* | mipsisa64sb1el-* \ | mipsisa64sr71k-* | mipsisa64sr71kel-* \ | mipsr5900-* | mipsr5900el-* \ | mipstx39-* | mipstx39el-* \ | mmix-* \ | mt-* \ | msp430-* \ | nds32-* | nds32le-* | nds32be-* \ | nios-* | nios2-* | nios2eb-* | nios2el-* \ | none-* | np1-* | ns16k-* | ns32k-* \ | open8-* \ | or1k*-* \ | orion-* \ | pdp10-* | pdp11-* | pj-* | pjl-* | pn-* | power-* \ | powerpc-* | powerpc64-* | powerpc64le-* | powerpcle-* \ | pru-* \ | pyramid-* \ | riscv32-* | riscv64-* \ | rl78-* | romp-* | rs6000-* | rx-* \ | sh-* | sh[1234]-* | sh[24]a-* | sh[24]aeb-* | sh[23]e-* | sh[34]eb-* | sheb-* | shbe-* \ | shle-* | sh[1234]le-* | sh3ele-* | sh64-* | sh64le-* \ | sparc-* | sparc64-* | sparc64b-* | sparc64v-* | sparc86x-* | sparclet-* \ | sparclite-* \ | sparcv8-* | sparcv9-* | sparcv9b-* | sparcv9v-* | sv1-* | sx*-* \ | tahoe-* \ | tic30-* | tic4x-* | tic54x-* | tic55x-* | tic6x-* | tic80-* \ | tile*-* \ | tron-* \ | ubicom32-* \ | v850-* | v850e-* | v850e1-* | v850es-* | v850e2-* | v850e2v3-* \ | vax-* \ | visium-* \ | wasm32-* \ | we32k-* \ | x86-* | x86_64-* | xc16x-* | xps100-* \ | xstormy16-* | xtensa*-* \ | ymp-* \ | z8k-* | z80-*) ;; # Recognize the basic CPU types without company name, with glob match. xtensa*) basic_machine=$basic_machine-unknown ;; # Recognize the various machine names and aliases which stand # for a CPU type and a company and sometimes even an OS. 386bsd) basic_machine=i386-pc os=-bsd ;; 3b1 | 7300 | 7300-att | att-7300 | pc7300 | safari | unixpc) basic_machine=m68000-att ;; 3b*) basic_machine=we32k-att ;; a29khif) basic_machine=a29k-amd os=-udi ;; abacus) basic_machine=abacus-unknown ;; adobe68k) basic_machine=m68010-adobe os=-scout ;; alliant | fx80) basic_machine=fx80-alliant ;; altos | altos3068) basic_machine=m68k-altos ;; am29k) basic_machine=a29k-none os=-bsd ;; amd64) basic_machine=x86_64-pc ;; amd64-*) basic_machine=x86_64-`echo "$basic_machine" | sed 's/^[^-]*-//'` ;; amdahl) basic_machine=580-amdahl os=-sysv ;; amiga | amiga-*) basic_machine=m68k-unknown ;; amigaos | amigados) basic_machine=m68k-unknown os=-amigaos ;; amigaunix | amix) basic_machine=m68k-unknown os=-sysv4 ;; apollo68) basic_machine=m68k-apollo os=-sysv ;; apollo68bsd) basic_machine=m68k-apollo os=-bsd ;; aros) basic_machine=i386-pc os=-aros ;; asmjs) basic_machine=asmjs-unknown ;; aux) basic_machine=m68k-apple os=-aux ;; balance) basic_machine=ns32k-sequent os=-dynix ;; blackfin) basic_machine=bfin-unknown os=-linux ;; blackfin-*) basic_machine=bfin-`echo "$basic_machine" | sed 's/^[^-]*-//'` os=-linux ;; bluegene*) basic_machine=powerpc-ibm os=-cnk ;; c54x-*) basic_machine=tic54x-`echo "$basic_machine" | sed 's/^[^-]*-//'` ;; c55x-*) basic_machine=tic55x-`echo "$basic_machine" | sed 's/^[^-]*-//'` ;; c6x-*) basic_machine=tic6x-`echo "$basic_machine" | sed 's/^[^-]*-//'` ;; c90) basic_machine=c90-cray os=-unicos ;; cegcc) basic_machine=arm-unknown os=-cegcc ;; convex-c1) basic_machine=c1-convex os=-bsd ;; convex-c2) basic_machine=c2-convex os=-bsd ;; convex-c32) basic_machine=c32-convex os=-bsd ;; convex-c34) basic_machine=c34-convex os=-bsd ;; convex-c38) basic_machine=c38-convex os=-bsd ;; cray | j90) basic_machine=j90-cray os=-unicos ;; craynv) basic_machine=craynv-cray os=-unicosmp ;; cr16 | cr16-*) basic_machine=cr16-unknown os=-elf ;; crds | unos) basic_machine=m68k-crds ;; crisv32 | crisv32-* | etraxfs*) basic_machine=crisv32-axis ;; cris | cris-* | etrax*) basic_machine=cris-axis ;; crx) basic_machine=crx-unknown os=-elf ;; da30 | da30-*) basic_machine=m68k-da30 ;; decstation | decstation-3100 | pmax | pmax-* | pmin | dec3100 | decstatn) basic_machine=mips-dec ;; decsystem10* | dec10*) basic_machine=pdp10-dec os=-tops10 ;; decsystem20* | dec20*) basic_machine=pdp10-dec os=-tops20 ;; delta | 3300 | motorola-3300 | motorola-delta \ | 3300-motorola | delta-motorola) basic_machine=m68k-motorola ;; delta88) basic_machine=m88k-motorola os=-sysv3 ;; dicos) basic_machine=i686-pc os=-dicos ;; djgpp) basic_machine=i586-pc os=-msdosdjgpp ;; dpx20 | dpx20-*) basic_machine=rs6000-bull os=-bosx ;; dpx2*) basic_machine=m68k-bull os=-sysv3 ;; e500v[12]) basic_machine=powerpc-unknown os=$os"spe" ;; e500v[12]-*) basic_machine=powerpc-`echo "$basic_machine" | sed 's/^[^-]*-//'` os=$os"spe" ;; ebmon29k) basic_machine=a29k-amd os=-ebmon ;; elxsi) basic_machine=elxsi-elxsi os=-bsd ;; encore | umax | mmax) basic_machine=ns32k-encore ;; es1800 | OSE68k | ose68k | ose | OSE) basic_machine=m68k-ericsson os=-ose ;; fx2800) basic_machine=i860-alliant ;; genix) basic_machine=ns32k-ns ;; gmicro) basic_machine=tron-gmicro os=-sysv ;; go32) basic_machine=i386-pc os=-go32 ;; h3050r* | hiux*) basic_machine=hppa1.1-hitachi os=-hiuxwe2 ;; h8300hms) basic_machine=h8300-hitachi os=-hms ;; h8300xray) basic_machine=h8300-hitachi os=-xray ;; h8500hms) basic_machine=h8500-hitachi os=-hms ;; harris) basic_machine=m88k-harris os=-sysv3 ;; hp300-*) basic_machine=m68k-hp ;; hp300bsd) basic_machine=m68k-hp os=-bsd ;; hp300hpux) basic_machine=m68k-hp os=-hpux ;; hp3k9[0-9][0-9] | hp9[0-9][0-9]) basic_machine=hppa1.0-hp ;; hp9k2[0-9][0-9] | hp9k31[0-9]) basic_machine=m68000-hp ;; hp9k3[2-9][0-9]) basic_machine=m68k-hp ;; hp9k6[0-9][0-9] | hp6[0-9][0-9]) basic_machine=hppa1.0-hp ;; hp9k7[0-79][0-9] | hp7[0-79][0-9]) basic_machine=hppa1.1-hp ;; hp9k78[0-9] | hp78[0-9]) # FIXME: really hppa2.0-hp basic_machine=hppa1.1-hp ;; hp9k8[67]1 | hp8[67]1 | hp9k80[24] | hp80[24] | hp9k8[78]9 | hp8[78]9 | hp9k893 | hp893) # FIXME: really hppa2.0-hp basic_machine=hppa1.1-hp ;; hp9k8[0-9][13679] | hp8[0-9][13679]) basic_machine=hppa1.1-hp ;; hp9k8[0-9][0-9] | hp8[0-9][0-9]) basic_machine=hppa1.0-hp ;; hppaosf) basic_machine=hppa1.1-hp os=-osf ;; hppro) basic_machine=hppa1.1-hp os=-proelf ;; i370-ibm* | ibm*) basic_machine=i370-ibm ;; i*86v32) basic_machine=`echo "$1" | sed -e 's/86.*/86-pc/'` os=-sysv32 ;; i*86v4*) basic_machine=`echo "$1" | sed -e 's/86.*/86-pc/'` os=-sysv4 ;; i*86v) basic_machine=`echo "$1" | sed -e 's/86.*/86-pc/'` os=-sysv ;; i*86sol2) basic_machine=`echo "$1" | sed -e 's/86.*/86-pc/'` os=-solaris2 ;; i386mach) basic_machine=i386-mach os=-mach ;; vsta) basic_machine=i386-unknown os=-vsta ;; iris | iris4d) basic_machine=mips-sgi case $os in -irix*) ;; *) os=-irix4 ;; esac ;; isi68 | isi) basic_machine=m68k-isi os=-sysv ;; leon-*|leon[3-9]-*) basic_machine=sparc-`echo "$basic_machine" | sed 's/-.*//'` ;; m68knommu) basic_machine=m68k-unknown os=-linux ;; m68knommu-*) basic_machine=m68k-`echo "$basic_machine" | sed 's/^[^-]*-//'` os=-linux ;; magnum | m3230) basic_machine=mips-mips os=-sysv ;; merlin) basic_machine=ns32k-utek os=-sysv ;; microblaze*) basic_machine=microblaze-xilinx ;; mingw64) basic_machine=x86_64-pc os=-mingw64 ;; mingw32) basic_machine=i686-pc os=-mingw32 ;; mingw32ce) basic_machine=arm-unknown os=-mingw32ce ;; miniframe) basic_machine=m68000-convergent ;; *mint | -mint[0-9]* | *MiNT | *MiNT[0-9]*) basic_machine=m68k-atari os=-mint ;; mips3*-*) basic_machine=`echo "$basic_machine" | sed -e 's/mips3/mips64/'` ;; mips3*) basic_machine=`echo "$basic_machine" | sed -e 's/mips3/mips64/'`-unknown ;; monitor) basic_machine=m68k-rom68k os=-coff ;; morphos) basic_machine=powerpc-unknown os=-morphos ;; moxiebox) basic_machine=moxie-unknown os=-moxiebox ;; msdos) basic_machine=i386-pc os=-msdos ;; ms1-*) basic_machine=`echo "$basic_machine" | sed -e 's/ms1-/mt-/'` ;; msys) basic_machine=i686-pc os=-msys ;; mvs) basic_machine=i370-ibm os=-mvs ;; nacl) basic_machine=le32-unknown os=-nacl ;; ncr3000) basic_machine=i486-ncr os=-sysv4 ;; netbsd386) basic_machine=i386-unknown os=-netbsd ;; netwinder) basic_machine=armv4l-rebel os=-linux ;; news | news700 | news800 | news900) basic_machine=m68k-sony os=-newsos ;; news1000) basic_machine=m68030-sony os=-newsos ;; news-3600 | risc-news) basic_machine=mips-sony os=-newsos ;; necv70) basic_machine=v70-nec os=-sysv ;; next | m*-next) basic_machine=m68k-next case $os in -nextstep* ) ;; -ns2*) os=-nextstep2 ;; *) os=-nextstep3 ;; esac ;; nh3000) basic_machine=m68k-harris os=-cxux ;; nh[45]000) basic_machine=m88k-harris os=-cxux ;; nindy960) basic_machine=i960-intel os=-nindy ;; mon960) basic_machine=i960-intel os=-mon960 ;; nonstopux) basic_machine=mips-compaq os=-nonstopux ;; np1) basic_machine=np1-gould ;; neo-tandem) basic_machine=neo-tandem ;; nse-tandem) basic_machine=nse-tandem ;; nsr-tandem) basic_machine=nsr-tandem ;; nsv-tandem) basic_machine=nsv-tandem ;; nsx-tandem) basic_machine=nsx-tandem ;; op50n-* | op60c-*) basic_machine=hppa1.1-oki os=-proelf ;; openrisc | openrisc-*) basic_machine=or32-unknown ;; os400) basic_machine=powerpc-ibm os=-os400 ;; OSE68000 | ose68000) basic_machine=m68000-ericsson os=-ose ;; os68k) basic_machine=m68k-none os=-os68k ;; pa-hitachi) basic_machine=hppa1.1-hitachi os=-hiuxwe2 ;; paragon) basic_machine=i860-intel os=-osf ;; parisc) basic_machine=hppa-unknown os=-linux ;; parisc-*) basic_machine=hppa-`echo "$basic_machine" | sed 's/^[^-]*-//'` os=-linux ;; pbd) basic_machine=sparc-tti ;; pbb) basic_machine=m68k-tti ;; pc532 | pc532-*) basic_machine=ns32k-pc532 ;; pc98) basic_machine=i386-pc ;; pc98-*) basic_machine=i386-`echo "$basic_machine" | sed 's/^[^-]*-//'` ;; pentium | p5 | k5 | k6 | nexgen | viac3) basic_machine=i586-pc ;; pentiumpro | p6 | 6x86 | athlon | athlon_*) basic_machine=i686-pc ;; pentiumii | pentium2 | pentiumiii | pentium3) basic_machine=i686-pc ;; pentium4) basic_machine=i786-pc ;; pentium-* | p5-* | k5-* | k6-* | nexgen-* | viac3-*) basic_machine=i586-`echo "$basic_machine" | sed 's/^[^-]*-//'` ;; pentiumpro-* | p6-* | 6x86-* | athlon-*) basic_machine=i686-`echo "$basic_machine" | sed 's/^[^-]*-//'` ;; pentiumii-* | pentium2-* | pentiumiii-* | pentium3-*) basic_machine=i686-`echo "$basic_machine" | sed 's/^[^-]*-//'` ;; pentium4-*) basic_machine=i786-`echo "$basic_machine" | sed 's/^[^-]*-//'` ;; pn) basic_machine=pn-gould ;; power) basic_machine=power-ibm ;; ppc | ppcbe) basic_machine=powerpc-unknown ;; ppc-* | ppcbe-*) basic_machine=powerpc-`echo "$basic_machine" | sed 's/^[^-]*-//'` ;; ppcle | powerpclittle) basic_machine=powerpcle-unknown ;; ppcle-* | powerpclittle-*) basic_machine=powerpcle-`echo "$basic_machine" | sed 's/^[^-]*-//'` ;; ppc64) basic_machine=powerpc64-unknown ;; ppc64-*) basic_machine=powerpc64-`echo "$basic_machine" | sed 's/^[^-]*-//'` ;; ppc64le | powerpc64little) basic_machine=powerpc64le-unknown ;; ppc64le-* | powerpc64little-*) basic_machine=powerpc64le-`echo "$basic_machine" | sed 's/^[^-]*-//'` ;; ps2) basic_machine=i386-ibm ;; pw32) basic_machine=i586-unknown os=-pw32 ;; rdos | rdos64) basic_machine=x86_64-pc os=-rdos ;; rdos32) basic_machine=i386-pc os=-rdos ;; rom68k) basic_machine=m68k-rom68k os=-coff ;; rm[46]00) basic_machine=mips-siemens ;; rtpc | rtpc-*) basic_machine=romp-ibm ;; s390 | s390-*) basic_machine=s390-ibm ;; s390x | s390x-*) basic_machine=s390x-ibm ;; sa29200) basic_machine=a29k-amd os=-udi ;; sb1) basic_machine=mipsisa64sb1-unknown ;; sb1el) basic_machine=mipsisa64sb1el-unknown ;; sde) basic_machine=mipsisa32-sde os=-elf ;; sei) basic_machine=mips-sei os=-seiux ;; sequent) basic_machine=i386-sequent ;; sh5el) basic_machine=sh5le-unknown ;; simso-wrs) basic_machine=sparclite-wrs os=-vxworks ;; sps7) basic_machine=m68k-bull os=-sysv2 ;; spur) basic_machine=spur-unknown ;; st2000) basic_machine=m68k-tandem ;; stratus) basic_machine=i860-stratus os=-sysv4 ;; strongarm-* | thumb-*) basic_machine=arm-`echo "$basic_machine" | sed 's/^[^-]*-//'` ;; sun2) basic_machine=m68000-sun ;; sun2os3) basic_machine=m68000-sun os=-sunos3 ;; sun2os4) basic_machine=m68000-sun os=-sunos4 ;; sun3os3) basic_machine=m68k-sun os=-sunos3 ;; sun3os4) basic_machine=m68k-sun os=-sunos4 ;; sun4os3) basic_machine=sparc-sun os=-sunos3 ;; sun4os4) basic_machine=sparc-sun os=-sunos4 ;; sun4sol2) basic_machine=sparc-sun os=-solaris2 ;; sun3 | sun3-*) basic_machine=m68k-sun ;; sun4) basic_machine=sparc-sun ;; sun386 | sun386i | roadrunner) basic_machine=i386-sun ;; sv1) basic_machine=sv1-cray os=-unicos ;; symmetry) basic_machine=i386-sequent os=-dynix ;; t3e) basic_machine=alphaev5-cray os=-unicos ;; t90) basic_machine=t90-cray os=-unicos ;; tile*) basic_machine=$basic_machine-unknown os=-linux-gnu ;; tx39) basic_machine=mipstx39-unknown ;; tx39el) basic_machine=mipstx39el-unknown ;; toad1) basic_machine=pdp10-xkl os=-tops20 ;; tower | tower-32) basic_machine=m68k-ncr ;; tpf) basic_machine=s390x-ibm os=-tpf ;; udi29k) basic_machine=a29k-amd os=-udi ;; ultra3) basic_machine=a29k-nyu os=-sym1 ;; v810 | necv810) basic_machine=v810-nec os=-none ;; vaxv) basic_machine=vax-dec os=-sysv ;; vms) basic_machine=vax-dec os=-vms ;; vpp*|vx|vx-*) basic_machine=f301-fujitsu ;; vxworks960) basic_machine=i960-wrs os=-vxworks ;; vxworks68) basic_machine=m68k-wrs os=-vxworks ;; vxworks29k) basic_machine=a29k-wrs os=-vxworks ;; w65*) basic_machine=w65-wdc os=-none ;; w89k-*) basic_machine=hppa1.1-winbond os=-proelf ;; x64) basic_machine=x86_64-pc ;; xbox) basic_machine=i686-pc os=-mingw32 ;; xps | xps100) basic_machine=xps100-honeywell ;; xscale-* | xscalee[bl]-*) basic_machine=`echo "$basic_machine" | sed 's/^xscale/arm/'` ;; ymp) basic_machine=ymp-cray os=-unicos ;; none) basic_machine=none-none os=-none ;; # Here we handle the default manufacturer of certain CPU types. It is in # some cases the only manufacturer, in others, it is the most popular. w89k) basic_machine=hppa1.1-winbond ;; op50n) basic_machine=hppa1.1-oki ;; op60c) basic_machine=hppa1.1-oki ;; romp) basic_machine=romp-ibm ;; mmix) basic_machine=mmix-knuth ;; rs6000) basic_machine=rs6000-ibm ;; vax) basic_machine=vax-dec ;; pdp11) basic_machine=pdp11-dec ;; we32k) basic_machine=we32k-att ;; sh[1234] | sh[24]a | sh[24]aeb | sh[34]eb | sh[1234]le | sh[23]ele) basic_machine=sh-unknown ;; cydra) basic_machine=cydra-cydrome ;; orion) basic_machine=orion-highlevel ;; orion105) basic_machine=clipper-highlevel ;; mac | mpw | mac-mpw) basic_machine=m68k-apple ;; pmac | pmac-mpw) basic_machine=powerpc-apple ;; *-unknown) # Make sure to match an already-canonicalized machine name. ;; *) echo Invalid configuration \`"$1"\': machine \`"$basic_machine"\' not recognized 1>&2 exit 1 ;; esac # Here we canonicalize certain aliases for manufacturers. case $basic_machine in *-digital*) basic_machine=`echo "$basic_machine" | sed 's/digital.*/dec/'` ;; *-commodore*) basic_machine=`echo "$basic_machine" | sed 's/commodore.*/cbm/'` ;; *) ;; esac # Decode manufacturer-specific aliases for certain operating systems. if [ x"$os" != x"" ] then case $os in # First match some system type aliases that might get confused # with valid system types. # -solaris* is a basic system type, with this one exception. -auroraux) os=-auroraux ;; -solaris1 | -solaris1.*) os=`echo $os | sed -e 's|solaris1|sunos4|'` ;; -solaris) os=-solaris2 ;; -unixware*) os=-sysv4.2uw ;; -gnu/linux*) os=`echo $os | sed -e 's|gnu/linux|linux-gnu|'` ;; # es1800 is here to avoid being matched by es* (a different OS) -es1800*) os=-ose ;; # Now accept the basic system types. # The portable systems comes first. # Each alternative MUST end in a * to match a version number. # -sysv* is not here because it comes later, after sysvr4. -gnu* | -bsd* | -mach* | -minix* | -genix* | -ultrix* | -irix* \ | -*vms* | -sco* | -esix* | -isc* | -aix* | -cnk* | -sunos | -sunos[34]*\ | -hpux* | -unos* | -osf* | -luna* | -dgux* | -auroraux* | -solaris* \ | -sym* | -kopensolaris* | -plan9* \ | -amigaos* | -amigados* | -msdos* | -newsos* | -unicos* | -aof* \ | -aos* | -aros* | -cloudabi* | -sortix* \ | -nindy* | -vxsim* | -vxworks* | -ebmon* | -hms* | -mvs* \ | -clix* | -riscos* | -uniplus* | -iris* | -rtu* | -xenix* \ | -hiux* | -knetbsd* | -mirbsd* | -netbsd* \ | -bitrig* | -openbsd* | -solidbsd* | -libertybsd* \ | -ekkobsd* | -kfreebsd* | -freebsd* | -riscix* | -lynxos* \ | -bosx* | -nextstep* | -cxux* | -aout* | -elf* | -oabi* \ | -ptx* | -coff* | -ecoff* | -winnt* | -domain* | -vsta* \ | -udi* | -eabi* | -lites* | -ieee* | -go32* | -aux* \ | -chorusos* | -chorusrdb* | -cegcc* | -glidix* \ | -cygwin* | -msys* | -pe* | -psos* | -moss* | -proelf* | -rtems* \ | -midipix* | -mingw32* | -mingw64* | -linux-gnu* | -linux-android* \ | -linux-newlib* | -linux-musl* | -linux-uclibc* \ | -uxpv* | -beos* | -mpeix* | -udk* | -moxiebox* \ | -interix* | -uwin* | -mks* | -rhapsody* | -darwin* \ | -openstep* | -oskit* | -conix* | -pw32* | -nonstopux* \ | -storm-chaos* | -tops10* | -tenex* | -tops20* | -its* \ | -os2* | -vos* | -palmos* | -uclinux* | -nucleus* \ | -morphos* | -superux* | -rtmk* | -windiss* \ | -powermax* | -dnix* | -nx6 | -nx7 | -sei* | -dragonfly* \ | -skyos* | -haiku* | -rdos* | -toppers* | -drops* | -es* \ | -onefs* | -tirtos* | -phoenix* | -fuchsia* | -redox* | -bme* \ | -midnightbsd*) # Remember, each alternative MUST END IN *, to match a version number. ;; -qnx*) case $basic_machine in x86-* | i*86-*) ;; *) os=-nto$os ;; esac ;; -nto-qnx*) ;; -nto*) os=`echo $os | sed -e 's|nto|nto-qnx|'` ;; -sim | -xray | -os68k* | -v88r* \ | -windows* | -osx | -abug | -netware* | -os9* \ | -macos* | -mpw* | -magic* | -mmixware* | -mon960* | -lnews*) ;; -mac*) os=`echo "$os" | sed -e 's|mac|macos|'` ;; -linux-dietlibc) os=-linux-dietlibc ;; -linux*) os=`echo $os | sed -e 's|linux|linux-gnu|'` ;; -sunos5*) os=`echo "$os" | sed -e 's|sunos5|solaris2|'` ;; -sunos6*) os=`echo "$os" | sed -e 's|sunos6|solaris3|'` ;; -opened*) os=-openedition ;; -os400*) os=-os400 ;; -wince*) os=-wince ;; -utek*) os=-bsd ;; -dynix*) os=-bsd ;; -acis*) os=-aos ;; -atheos*) os=-atheos ;; -syllable*) os=-syllable ;; -386bsd) os=-bsd ;; -ctix* | -uts*) os=-sysv ;; -nova*) os=-rtmk-nova ;; -ns2) os=-nextstep2 ;; -nsk*) os=-nsk ;; # Preserve the version number of sinix5. -sinix5.*) os=`echo $os | sed -e 's|sinix|sysv|'` ;; -sinix*) os=-sysv4 ;; -tpf*) os=-tpf ;; -triton*) os=-sysv3 ;; -oss*) os=-sysv3 ;; -svr4*) os=-sysv4 ;; -svr3) os=-sysv3 ;; -sysvr4) os=-sysv4 ;; # This must come after -sysvr4. -sysv*) ;; -ose*) os=-ose ;; -*mint | -mint[0-9]* | -*MiNT | -MiNT[0-9]*) os=-mint ;; -zvmoe) os=-zvmoe ;; -dicos*) os=-dicos ;; -pikeos*) # Until real need of OS specific support for # particular features comes up, bare metal # configurations are quite functional. case $basic_machine in arm*) os=-eabi ;; *) os=-elf ;; esac ;; -nacl*) ;; -ios) ;; -none) ;; *) # Get rid of the `-' at the beginning of $os. os=`echo $os | sed 's/[^-]*-//'` echo Invalid configuration \`"$1"\': system \`"$os"\' not recognized 1>&2 exit 1 ;; esac else # Here we handle the default operating systems that come with various machines. # The value should be what the vendor currently ships out the door with their # machine or put another way, the most popular os provided with the machine. # Note that if you're going to try to match "-MANUFACTURER" here (say, # "-sun"), then you have to tell the case statement up towards the top # that MANUFACTURER isn't an operating system. Otherwise, code above # will signal an error saying that MANUFACTURER isn't an operating # system, and we'll never get to this point. case $basic_machine in score-*) os=-elf ;; spu-*) os=-elf ;; *-acorn) os=-riscix1.2 ;; arm*-rebel) os=-linux ;; arm*-semi) os=-aout ;; c4x-* | tic4x-*) os=-coff ;; c8051-*) os=-elf ;; hexagon-*) os=-elf ;; tic54x-*) os=-coff ;; tic55x-*) os=-coff ;; tic6x-*) os=-coff ;; # This must come before the *-dec entry. pdp10-*) os=-tops20 ;; pdp11-*) os=-none ;; *-dec | vax-*) os=-ultrix4.2 ;; m68*-apollo) os=-domain ;; i386-sun) os=-sunos4.0.2 ;; m68000-sun) os=-sunos3 ;; m68*-cisco) os=-aout ;; mep-*) os=-elf ;; mips*-cisco) os=-elf ;; mips*-*) os=-elf ;; or32-*) os=-coff ;; *-tti) # must be before sparc entry or we get the wrong os. os=-sysv3 ;; sparc-* | *-sun) os=-sunos4.1.1 ;; pru-*) os=-elf ;; *-be) os=-beos ;; *-ibm) os=-aix ;; *-knuth) os=-mmixware ;; *-wec) os=-proelf ;; *-winbond) os=-proelf ;; *-oki) os=-proelf ;; *-hp) os=-hpux ;; *-hitachi) os=-hiux ;; i860-* | *-att | *-ncr | *-altos | *-motorola | *-convergent) os=-sysv ;; *-cbm) os=-amigaos ;; *-dg) os=-dgux ;; *-dolphin) os=-sysv3 ;; m68k-ccur) os=-rtu ;; m88k-omron*) os=-luna ;; *-next) os=-nextstep ;; *-sequent) os=-ptx ;; *-crds) os=-unos ;; *-ns) os=-genix ;; i370-*) os=-mvs ;; *-gould) os=-sysv ;; *-highlevel) os=-bsd ;; *-encore) os=-bsd ;; *-sgi) os=-irix ;; *-siemens) os=-sysv4 ;; *-masscomp) os=-rtu ;; f30[01]-fujitsu | f700-fujitsu) os=-uxpv ;; *-rom68k) os=-coff ;; *-*bug) os=-coff ;; *-apple) os=-macos ;; *-atari*) os=-mint ;; *) os=-none ;; esac fi # Here we handle the case where we know the os, and the CPU type, but not the # manufacturer. We pick the logical manufacturer. vendor=unknown case $basic_machine in *-unknown) case $os in -riscix*) vendor=acorn ;; -sunos*) vendor=sun ;; -cnk*|-aix*) vendor=ibm ;; -beos*) vendor=be ;; -hpux*) vendor=hp ;; -mpeix*) vendor=hp ;; -hiux*) vendor=hitachi ;; -unos*) vendor=crds ;; -dgux*) vendor=dg ;; -luna*) vendor=omron ;; -genix*) vendor=ns ;; -mvs* | -opened*) vendor=ibm ;; -os400*) vendor=ibm ;; -ptx*) vendor=sequent ;; -tpf*) vendor=ibm ;; -vxsim* | -vxworks* | -windiss*) vendor=wrs ;; -aux*) vendor=apple ;; -hms*) vendor=hitachi ;; -mpw* | -macos*) vendor=apple ;; -*mint | -mint[0-9]* | -*MiNT | -MiNT[0-9]*) vendor=atari ;; -vos*) vendor=stratus ;; esac basic_machine=`echo "$basic_machine" | sed "s/unknown/$vendor/"` ;; esac echo "$basic_machine$os" exit # Local variables: # eval: (add-hook 'write-file-functions 'time-stamp) # time-stamp-start: "timestamp='" # time-stamp-format: "%:y-%02m-%02d" # time-stamp-end: "'" # End: frr-7.2.1/config.version0000644000000000000000000000025013610377563012065 00000000000000# this file is used to carry --with-pkg-extra-version into tarballs EXTRAVERSION="" # for easy access by scripts before ./configure is run DIST_PACKAGE_VERSION="7.2.1" frr-7.2.1/config.version.in0000644000000000000000000000030213610377563012470 00000000000000# this file is used to carry --with-pkg-extra-version into tarballs EXTRAVERSION="@EXTRAVERSION@" # for easy access by scripts before ./configure is run DIST_PACKAGE_VERSION="@PACKAGE_VERSION@" frr-7.2.1/configure0000755000000000000000000350331113610377563011131 00000000000000#! /bin/sh # Guess values for system-dependent variables and create Makefiles. # Generated by GNU Autoconf 2.69 for frr 7.2.1. # # Report bugs to . # # # Copyright (C) 1992-1996, 1998-2012 Free Software Foundation, Inc. # # # This configure script is free software; the Free Software Foundation # gives unlimited permission to copy, distribute and modify it. ## -------------------- ## ## M4sh Initialization. ## ## -------------------- ## # Be more Bourne compatible DUALCASE=1; export DUALCASE # for MKS sh if test -n "${ZSH_VERSION+set}" && (emulate sh) >/dev/null 2>&1; then : emulate sh NULLCMD=: # Pre-4.2 versions of Zsh do word splitting on ${1+"$@"}, which # is contrary to our usage. Disable this feature. alias -g '${1+"$@"}'='"$@"' setopt NO_GLOB_SUBST else case `(set -o) 2>/dev/null` in #( *posix*) : set -o posix ;; #( *) : ;; esac fi as_nl=' ' export as_nl # Printing a long string crashes Solaris 7 /usr/bin/printf. as_echo='\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\' as_echo=$as_echo$as_echo$as_echo$as_echo$as_echo as_echo=$as_echo$as_echo$as_echo$as_echo$as_echo$as_echo # Prefer a ksh shell builtin over an external printf program on Solaris, # but without wasting forks for bash or zsh. if test -z "$BASH_VERSION$ZSH_VERSION" \ && (test "X`print -r -- $as_echo`" = "X$as_echo") 2>/dev/null; then as_echo='print -r --' as_echo_n='print -rn --' elif (test "X`printf %s $as_echo`" = "X$as_echo") 2>/dev/null; then as_echo='printf %s\n' as_echo_n='printf %s' else if test "X`(/usr/ucb/echo -n -n $as_echo) 2>/dev/null`" = "X-n $as_echo"; then as_echo_body='eval /usr/ucb/echo -n "$1$as_nl"' as_echo_n='/usr/ucb/echo -n' else as_echo_body='eval expr "X$1" : "X\\(.*\\)"' as_echo_n_body='eval arg=$1; case $arg in #( *"$as_nl"*) expr "X$arg" : "X\\(.*\\)$as_nl"; arg=`expr "X$arg" : ".*$as_nl\\(.*\\)"`;; esac; expr "X$arg" : "X\\(.*\\)" | tr -d "$as_nl" ' export as_echo_n_body as_echo_n='sh -c $as_echo_n_body as_echo' fi export as_echo_body as_echo='sh -c $as_echo_body as_echo' fi # The user is always right. if test "${PATH_SEPARATOR+set}" != set; then PATH_SEPARATOR=: (PATH='/bin;/bin'; FPATH=$PATH; sh -c :) >/dev/null 2>&1 && { (PATH='/bin:/bin'; FPATH=$PATH; sh -c :) >/dev/null 2>&1 || PATH_SEPARATOR=';' } fi # IFS # We need space, tab and new line, in precisely that order. Quoting is # there to prevent editors from complaining about space-tab. # (If _AS_PATH_WALK were called with IFS unset, it would disable word # splitting by setting IFS to empty value.) IFS=" "" $as_nl" # Find who we are. Look in the path if we contain no directory separator. as_myself= case $0 in #(( *[\\/]* ) as_myself=$0 ;; *) as_save_IFS=$IFS; IFS=$PATH_SEPARATOR for as_dir in $PATH do IFS=$as_save_IFS test -z "$as_dir" && as_dir=. test -r "$as_dir/$0" && as_myself=$as_dir/$0 && break done IFS=$as_save_IFS ;; esac # We did not find ourselves, most probably we were run as `sh COMMAND' # in which case we are not to be found in the path. if test "x$as_myself" = x; then as_myself=$0 fi if test ! -f "$as_myself"; then $as_echo "$as_myself: error: cannot find myself; rerun with an absolute file name" >&2 exit 1 fi # Unset variables that we do not need and which cause bugs (e.g. in # pre-3.0 UWIN ksh). But do not cause bugs in bash 2.01; the "|| exit 1" # suppresses any "Segmentation fault" message there. '((' could # trigger a bug in pdksh 5.2.14. for as_var in BASH_ENV ENV MAIL MAILPATH do eval test x\${$as_var+set} = xset \ && ( (unset $as_var) || exit 1) >/dev/null 2>&1 && unset $as_var || : done PS1='$ ' PS2='> ' PS4='+ ' # NLS nuisances. LC_ALL=C export LC_ALL LANGUAGE=C export LANGUAGE # CDPATH. (unset CDPATH) >/dev/null 2>&1 && unset CDPATH # Use a proper internal environment variable to ensure we don't fall # into an infinite loop, continuously re-executing ourselves. if test x"${_as_can_reexec}" != xno && test "x$CONFIG_SHELL" != x; then _as_can_reexec=no; export _as_can_reexec; # We cannot yet assume a decent shell, so we have to provide a # neutralization value for shells without unset; and this also # works around shells that cannot unset nonexistent variables. # Preserve -v and -x to the replacement shell. BASH_ENV=/dev/null ENV=/dev/null (unset BASH_ENV) >/dev/null 2>&1 && unset BASH_ENV ENV case $- in # (((( *v*x* | *x*v* ) as_opts=-vx ;; *v* ) as_opts=-v ;; *x* ) as_opts=-x ;; * ) as_opts= ;; esac exec $CONFIG_SHELL $as_opts "$as_myself" ${1+"$@"} # Admittedly, this is quite paranoid, since all the known shells bail # out after a failed `exec'. $as_echo "$0: could not re-execute with $CONFIG_SHELL" >&2 as_fn_exit 255 fi # We don't want this to propagate to other subprocesses. { _as_can_reexec=; unset _as_can_reexec;} if test "x$CONFIG_SHELL" = x; then as_bourne_compatible="if test -n \"\${ZSH_VERSION+set}\" && (emulate sh) >/dev/null 2>&1; then : emulate sh NULLCMD=: # Pre-4.2 versions of Zsh do word splitting on \${1+\"\$@\"}, which # is contrary to our usage. Disable this feature. alias -g '\${1+\"\$@\"}'='\"\$@\"' setopt NO_GLOB_SUBST else case \`(set -o) 2>/dev/null\` in #( *posix*) : set -o posix ;; #( *) : ;; esac fi " as_required="as_fn_return () { (exit \$1); } as_fn_success () { as_fn_return 0; } as_fn_failure () { as_fn_return 1; } as_fn_ret_success () { return 0; } as_fn_ret_failure () { return 1; } exitcode=0 as_fn_success || { exitcode=1; echo as_fn_success failed.; } as_fn_failure && { exitcode=1; echo as_fn_failure succeeded.; } as_fn_ret_success || { exitcode=1; echo as_fn_ret_success failed.; } as_fn_ret_failure && { exitcode=1; echo as_fn_ret_failure succeeded.; } if ( set x; as_fn_ret_success y && test x = \"\$1\" ); then : else exitcode=1; echo positional parameters were not saved. fi test x\$exitcode = x0 || exit 1 test -x / || exit 1" as_suggested=" as_lineno_1=";as_suggested=$as_suggested$LINENO;as_suggested=$as_suggested" as_lineno_1a=\$LINENO as_lineno_2=";as_suggested=$as_suggested$LINENO;as_suggested=$as_suggested" as_lineno_2a=\$LINENO eval 'test \"x\$as_lineno_1'\$as_run'\" != \"x\$as_lineno_2'\$as_run'\" && test \"x\`expr \$as_lineno_1'\$as_run' + 1\`\" = \"x\$as_lineno_2'\$as_run'\"' || exit 1 test \$(( 1 + 1 )) = 2 || exit 1 test -n \"\${ZSH_VERSION+set}\${BASH_VERSION+set}\" || ( ECHO='\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\' ECHO=\$ECHO\$ECHO\$ECHO\$ECHO\$ECHO ECHO=\$ECHO\$ECHO\$ECHO\$ECHO\$ECHO\$ECHO PATH=/empty FPATH=/empty; export PATH FPATH test \"X\`printf %s \$ECHO\`\" = \"X\$ECHO\" \\ || test \"X\`print -r -- \$ECHO\`\" = \"X\$ECHO\" ) || exit 1" if (eval "$as_required") 2>/dev/null; then : as_have_required=yes else as_have_required=no fi if test x$as_have_required = xyes && (eval "$as_suggested") 2>/dev/null; then : else as_save_IFS=$IFS; IFS=$PATH_SEPARATOR as_found=false for as_dir in /bin$PATH_SEPARATOR/usr/bin$PATH_SEPARATOR$PATH do IFS=$as_save_IFS test -z "$as_dir" && as_dir=. as_found=: case $as_dir in #( /*) for as_base in sh bash ksh sh5; do # Try only shells that exist, to save several forks. as_shell=$as_dir/$as_base if { test -f "$as_shell" || test -f "$as_shell.exe"; } && { $as_echo "$as_bourne_compatible""$as_required" | as_run=a "$as_shell"; } 2>/dev/null; then : CONFIG_SHELL=$as_shell as_have_required=yes if { $as_echo "$as_bourne_compatible""$as_suggested" | as_run=a "$as_shell"; } 2>/dev/null; then : break 2 fi fi done;; esac as_found=false done $as_found || { if { test -f "$SHELL" || test -f "$SHELL.exe"; } && { $as_echo "$as_bourne_compatible""$as_required" | as_run=a "$SHELL"; } 2>/dev/null; then : CONFIG_SHELL=$SHELL as_have_required=yes fi; } IFS=$as_save_IFS if test "x$CONFIG_SHELL" != x; then : export CONFIG_SHELL # We cannot yet assume a decent shell, so we have to provide a # neutralization value for shells without unset; and this also # works around shells that cannot unset nonexistent variables. # Preserve -v and -x to the replacement shell. BASH_ENV=/dev/null ENV=/dev/null (unset BASH_ENV) >/dev/null 2>&1 && unset BASH_ENV ENV case $- in # (((( *v*x* | *x*v* ) as_opts=-vx ;; *v* ) as_opts=-v ;; *x* ) as_opts=-x ;; * ) as_opts= ;; esac exec $CONFIG_SHELL $as_opts "$as_myself" ${1+"$@"} # Admittedly, this is quite paranoid, since all the known shells bail # out after a failed `exec'. $as_echo "$0: could not re-execute with $CONFIG_SHELL" >&2 exit 255 fi if test x$as_have_required = xno; then : $as_echo "$0: This script requires a shell more modern than all" $as_echo "$0: the shells that I found on your system." if test x${ZSH_VERSION+set} = xset ; then $as_echo "$0: In particular, zsh $ZSH_VERSION has bugs and should" $as_echo "$0: be upgraded to zsh 4.3.4 or later." else $as_echo "$0: Please tell bug-autoconf@gnu.org and $0: https://github.com/frrouting/frr/issues about your $0: system, including any error possibly output before this $0: message. Then install a modern shell, or manually run $0: the script under such a shell if you do have one." fi exit 1 fi fi fi SHELL=${CONFIG_SHELL-/bin/sh} export SHELL # Unset more variables known to interfere with behavior of common tools. CLICOLOR_FORCE= GREP_OPTIONS= unset CLICOLOR_FORCE GREP_OPTIONS ## --------------------- ## ## M4sh Shell Functions. ## ## --------------------- ## # as_fn_unset VAR # --------------- # Portably unset VAR. as_fn_unset () { { eval $1=; unset $1;} } as_unset=as_fn_unset # as_fn_set_status STATUS # ----------------------- # Set $? to STATUS, without forking. as_fn_set_status () { return $1 } # as_fn_set_status # as_fn_exit STATUS # ----------------- # Exit the shell with STATUS, even in a "trap 0" or "set -e" context. as_fn_exit () { set +e as_fn_set_status $1 exit $1 } # as_fn_exit # as_fn_mkdir_p # ------------- # Create "$as_dir" as a directory, including parents if necessary. as_fn_mkdir_p () { case $as_dir in #( -*) as_dir=./$as_dir;; esac test -d "$as_dir" || eval $as_mkdir_p || { as_dirs= while :; do case $as_dir in #( *\'*) as_qdir=`$as_echo "$as_dir" | sed "s/'/'\\\\\\\\''/g"`;; #'( *) as_qdir=$as_dir;; esac as_dirs="'$as_qdir' $as_dirs" as_dir=`$as_dirname -- "$as_dir" || $as_expr X"$as_dir" : 'X\(.*[^/]\)//*[^/][^/]*/*$' \| \ X"$as_dir" : 'X\(//\)[^/]' \| \ X"$as_dir" : 'X\(//\)$' \| \ X"$as_dir" : 'X\(/\)' \| . 2>/dev/null || $as_echo X"$as_dir" | sed '/^X\(.*[^/]\)\/\/*[^/][^/]*\/*$/{ s//\1/ q } /^X\(\/\/\)[^/].*/{ s//\1/ q } /^X\(\/\/\)$/{ s//\1/ q } /^X\(\/\).*/{ s//\1/ q } s/.*/./; q'` test -d "$as_dir" && break done test -z "$as_dirs" || eval "mkdir $as_dirs" } || test -d "$as_dir" || as_fn_error $? "cannot create directory $as_dir" } # as_fn_mkdir_p # as_fn_executable_p FILE # ----------------------- # Test if FILE is an executable regular file. as_fn_executable_p () { test -f "$1" && test -x "$1" } # as_fn_executable_p # as_fn_append VAR VALUE # ---------------------- # Append the text in VALUE to the end of the definition contained in VAR. Take # advantage of any shell optimizations that allow amortized linear growth over # repeated appends, instead of the typical quadratic growth present in naive # implementations. if (eval "as_var=1; as_var+=2; test x\$as_var = x12") 2>/dev/null; then : eval 'as_fn_append () { eval $1+=\$2 }' else as_fn_append () { eval $1=\$$1\$2 } fi # as_fn_append # as_fn_arith ARG... # ------------------ # Perform arithmetic evaluation on the ARGs, and store the result in the # global $as_val. Take advantage of shells that can avoid forks. The arguments # must be portable across $(()) and expr. if (eval "test \$(( 1 + 1 )) = 2") 2>/dev/null; then : eval 'as_fn_arith () { as_val=$(( $* )) }' else as_fn_arith () { as_val=`expr "$@" || test $? -eq 1` } fi # as_fn_arith # as_fn_error STATUS ERROR [LINENO LOG_FD] # ---------------------------------------- # Output "`basename $0`: error: ERROR" to stderr. If LINENO and LOG_FD are # provided, also output the error to LOG_FD, referencing LINENO. Then exit the # script with STATUS, using 1 if that was 0. as_fn_error () { as_status=$1; test $as_status -eq 0 && as_status=1 if test "$4"; then as_lineno=${as_lineno-"$3"} as_lineno_stack=as_lineno_stack=$as_lineno_stack $as_echo "$as_me:${as_lineno-$LINENO}: error: $2" >&$4 fi $as_echo "$as_me: error: $2" >&2 as_fn_exit $as_status } # as_fn_error if expr a : '\(a\)' >/dev/null 2>&1 && test "X`expr 00001 : '.*\(...\)'`" = X001; then as_expr=expr else as_expr=false fi if (basename -- /) >/dev/null 2>&1 && test "X`basename -- / 2>&1`" = "X/"; then as_basename=basename else as_basename=false fi if (as_dir=`dirname -- /` && test "X$as_dir" = X/) >/dev/null 2>&1; then as_dirname=dirname else as_dirname=false fi as_me=`$as_basename -- "$0" || $as_expr X/"$0" : '.*/\([^/][^/]*\)/*$' \| \ X"$0" : 'X\(//\)$' \| \ X"$0" : 'X\(/\)' \| . 2>/dev/null || $as_echo X/"$0" | sed '/^.*\/\([^/][^/]*\)\/*$/{ s//\1/ q } /^X\/\(\/\/\)$/{ s//\1/ q } /^X\/\(\/\).*/{ s//\1/ q } s/.*/./; q'` # Avoid depending upon Character Ranges. as_cr_letters='abcdefghijklmnopqrstuvwxyz' as_cr_LETTERS='ABCDEFGHIJKLMNOPQRSTUVWXYZ' as_cr_Letters=$as_cr_letters$as_cr_LETTERS as_cr_digits='0123456789' as_cr_alnum=$as_cr_Letters$as_cr_digits as_lineno_1=$LINENO as_lineno_1a=$LINENO as_lineno_2=$LINENO as_lineno_2a=$LINENO eval 'test "x$as_lineno_1'$as_run'" != "x$as_lineno_2'$as_run'" && test "x`expr $as_lineno_1'$as_run' + 1`" = "x$as_lineno_2'$as_run'"' || { # Blame Lee E. McMahon (1931-1989) for sed's syntax. :-) sed -n ' p /[$]LINENO/= ' <$as_myself | sed ' s/[$]LINENO.*/&-/ t lineno b :lineno N :loop s/[$]LINENO\([^'$as_cr_alnum'_].*\n\)\(.*\)/\2\1\2/ t loop s/-\n.*// ' >$as_me.lineno && chmod +x "$as_me.lineno" || { $as_echo "$as_me: error: cannot create $as_me.lineno; rerun with a POSIX shell" >&2; as_fn_exit 1; } # If we had to re-execute with $CONFIG_SHELL, we're ensured to have # already done that, so ensure we don't try to do so again and fall # in an infinite loop. This has already happened in practice. _as_can_reexec=no; export _as_can_reexec # Don't try to exec as it changes $[0], causing all sort of problems # (the dirname of $[0] is not the place where we might find the # original and so on. Autoconf is especially sensitive to this). . "./$as_me.lineno" # Exit status is that of the last command. exit } ECHO_C= ECHO_N= ECHO_T= case `echo -n x` in #((((( -n*) case `echo 'xy\c'` in *c*) ECHO_T=' ';; # ECHO_T is single tab character. xy) ECHO_C='\c';; *) echo `echo ksh88 bug on AIX 6.1` > /dev/null ECHO_T=' ';; esac;; *) ECHO_N='-n';; esac rm -f conf$$ conf$$.exe conf$$.file if test -d conf$$.dir; then rm -f conf$$.dir/conf$$.file else rm -f conf$$.dir mkdir conf$$.dir 2>/dev/null fi if (echo >conf$$.file) 2>/dev/null; then if ln -s conf$$.file conf$$ 2>/dev/null; then as_ln_s='ln -s' # ... but there are two gotchas: # 1) On MSYS, both `ln -s file dir' and `ln file dir' fail. # 2) DJGPP < 2.04 has no symlinks; `ln -s' creates a wrapper executable. # In both cases, we have to default to `cp -pR'. ln -s conf$$.file conf$$.dir 2>/dev/null && test ! -f conf$$.exe || as_ln_s='cp -pR' elif ln conf$$.file conf$$ 2>/dev/null; then as_ln_s=ln else as_ln_s='cp -pR' fi else as_ln_s='cp -pR' fi rm -f conf$$ conf$$.exe conf$$.dir/conf$$.file conf$$.file rmdir conf$$.dir 2>/dev/null if mkdir -p . 2>/dev/null; then as_mkdir_p='mkdir -p "$as_dir"' else test -d ./-p && rmdir ./-p as_mkdir_p=false fi as_test_x='test -x' as_executable_p=as_fn_executable_p # Sed expression to map a string onto a valid CPP name. as_tr_cpp="eval sed 'y%*$as_cr_letters%P$as_cr_LETTERS%;s%[^_$as_cr_alnum]%_%g'" # Sed expression to map a string onto a valid variable name. as_tr_sh="eval sed 'y%*+%pp%;s%[^_$as_cr_alnum]%_%g'" SHELL=${CONFIG_SHELL-/bin/sh} test -n "$DJDIR" || exec 7<&0 &1 # Name of the host. # hostname on some systems (SVR3.2, old GNU/Linux) returns a bogus exit status, # so uname gets run too. ac_hostname=`(hostname || uname -n) 2>/dev/null | sed 1q` # # Initializations. # ac_default_prefix=/usr/local ac_clean_files= ac_config_libobj_dir=. LIBOBJS= cross_compiling=no subdirs= MFLAGS= MAKEFLAGS= # Identity of this package. PACKAGE_NAME='frr' PACKAGE_TARNAME='frr' PACKAGE_VERSION='7.2.1' PACKAGE_STRING='frr 7.2.1' PACKAGE_BUGREPORT='https://github.com/frrouting/frr/issues' PACKAGE_URL='' ac_unique_file="lib/zebra.h" # Factoring default headers for most tests. ac_includes_default="\ #include #ifdef HAVE_SYS_TYPES_H # include #endif #ifdef HAVE_SYS_STAT_H # include #endif #ifdef STDC_HEADERS # include # include #else # ifdef HAVE_STDLIB_H # include # endif #endif #ifdef HAVE_STRING_H # if !defined STDC_HEADERS && defined HAVE_MEMORY_H # include # endif # include #endif #ifdef HAVE_STRINGS_H # include #endif #ifdef HAVE_INTTYPES_H # include #endif #ifdef HAVE_STDINT_H # include #endif #ifdef HAVE_UNISTD_H # include #endif" ac_subst_vars='am__EXEEXT_FALSE am__EXEEXT_TRUE LTLIBOBJS LIBOBJS VRRPD_FALSE VRRPD_TRUE FABRICD_FALSE FABRICD_TRUE STATICD_FALSE STATICD_TRUE SHARPD_FALSE SHARPD_TRUE PBRD_FALSE PBRD_TRUE PIMD_FALSE PIMD_TRUE ISISD_FALSE ISISD_TRUE OSPF6D_FALSE OSPF6D_TRUE BABELD_FALSE BABELD_TRUE RIPNGD_FALSE RIPNGD_TRUE OSPFCLIENT_FALSE OSPFCLIENT_TRUE WATCHFRR_FALSE WATCHFRR_TRUE EIGRPD_FALSE EIGRPD_TRUE NHRPD_FALSE NHRPD_TRUE BFDD_FALSE BFDD_TRUE LDPD_FALSE LDPD_TRUE OSPFD_FALSE OSPFD_TRUE RIPD_FALSE RIPD_TRUE BGPD_FALSE BGPD_TRUE ZEBRA_FALSE ZEBRA_TRUE VTYSH_FALSE VTYSH_TRUE HAVE_PROTOBUF_FALSE HAVE_PROTOBUF_TRUE FPM_FALSE FPM_TRUE IRDP_FALSE IRDP_TRUE SNMP_FALSE SNMP_TRUE RPKI_FALSE RPKI_TRUE ZEROMQ_FALSE ZEROMQ_TRUE GRPC_FALSE GRPC_TRUE SYSREPO_FALSE SYSREPO_TRUE CONFD_FALSE CONFD_TRUE SQLITE3_FALSE SQLITE3_TRUE BGP_BMP_FALSE BGP_BMP_TRUE ENABLE_BGP_VNC_FALSE ENABLE_BGP_VNC_TRUE SUPPORT_REALMS_FALSE SUPPORT_REALMS_TRUE CFG_YANGMODELS CFG_MODULE CFG_STATE CFG_SBIN CFG_SYSCONF vtysh_bin frr_statedir CONFDATE UNWIND_LIBS UNWIND_CFLAGS LIBCAP BISON_VERBOSE BISON_CLOSEBRACE BISON_OPENBRACE YFLAGS YACC LEXLIB LEX_OUTPUT_ROOT LEX RTRLIB_LIBS RTRLIB_CFLAGS ZEROMQ_LIBS ZEROMQ_CFLAGS GRPC_LIBS GRPC_CFLAGS SYSREPO_LIBS SYSREPO_CFLAGS CONFD_LIBS CONFD_CFLAGS CONFD SQLITE3_LIBS SQLITE3_CFLAGS LIBYANG_LIBS LIBYANG_CFLAGS SNMP_CFLAGS SNMP_LIBS NETSNMP_CONFIG CARES_FALSE CARES_TRUE CARES_LIBS CARES_CFLAGS HAVE_LIBPCREPOSIX SOLARIS LIBM LIBPAM LIBREADLINE PROTOBUF_C_LIBS PROTOBUF_C_CFLAGS PROTOC_C PROTOC SOLARIS_FALSE SOLARIS_TRUE OBJCOPY GIT_VERSION_FALSE GIT_VERSION_TRUE EXTRAVERSION PACKAGE_EXTRAVERSION HAVE_GCOV_FALSE HAVE_GCOV_TRUE enable_vty_group enable_group enable_user PYSPHINX DOC_HTML_FALSE DOC_HTML_TRUE DOC_FALSE DOC_TRUE PYTHON PYTHON_LIBS PYTHON_CFLAGS DFLT_NAME AR_FLAGS ARFLAGS STATIC_BIN_FALSE STATIC_BIN_TRUE AC_LDFLAGS CXXCPP LT_SYS_LIBRARY_PATH OTOOL64 OTOOL LIPO NMEDIT DSYMUTIL MANIFEST_TOOL RANLIB ac_ct_AR DLLTOOL OBJDUMP NM ac_ct_DUMPBIN DUMPBIN LD FGREP LIBTOOL AR LN_S PTHREAD_CFLAGS PTHREAD_LIBS PTHREAD_CC ax_pthread_config SAN_FLAGS WERROR CXX_COMPAT_CFLAGS DEV_BUILD_FALSE DEV_BUILD_TRUE LUA_LIB LUA_INCLUDE pkgluaexecdir luaexecdir pkgluadir luadir LUA_EXEC_PREFIX LUA_PREFIX LUA_PLATFORM LUA_SHORT_VERSION LUA_VERSION LUA EGREP GREP SED am__fastdepCXX_FALSE am__fastdepCXX_TRUE CXXDEPMODE ac_ct_CXX CXXFLAGS CXX CPP am__fastdepCC_FALSE am__fastdepCC_TRUE CCDEPMODE am__nodep AMDEPBACKSLASH AMDEP_FALSE AMDEP_TRUE am__include DEPDIR OBJEXT EXEEXT ac_ct_CC CPPFLAGS LDFLAGS CFLAGS CC yangmodelsdir moduledir PKGSRC_FALSE PKGSRC_TRUE pkgsrcrcdir exampledir PKG_CONFIG_LIBDIR PKG_CONFIG_PATH PKG_CONFIG PERL AM_BACKSLASH AM_DEFAULT_VERBOSITY AM_DEFAULT_V AM_V am__untar am__tar AMTAR am__leading_dot SET_MAKE AWK mkdir_p MKDIR_P INSTALL_STRIP_PROGRAM STRIP install_sh MAKEINFO AUTOHEADER AUTOMAKE AUTOCONF ACLOCAL VERSION PACKAGE CYGPATH_W am__isrc INSTALL_DATA INSTALL_SCRIPT INSTALL_PROGRAM ONLY_CLIPPY_FALSE ONLY_CLIPPY_TRUE HOSTTOOLS_CLIPPY_FALSE HOSTTOOLS_CLIPPY_TRUE BUILD_CLIPPY_FALSE BUILD_CLIPPY_TRUE CLIPPY host_os host_vendor host_cpu host build_os build_vendor build_cpu build CONFIG_ARGS PACKAGE_FULLNAME target_alias host_alias build_alias LIBS ECHO_T ECHO_N ECHO_C DEFS mandir localedir libdir psdir pdfdir dvidir htmldir infodir docdir oldincludedir includedir runstatedir localstatedir sharedstatedir sysconfdir datadir datarootdir libexecdir sbindir bindir program_transform_name prefix exec_prefix PACKAGE_URL PACKAGE_BUGREPORT PACKAGE_STRING PACKAGE_VERSION PACKAGE_TARNAME PACKAGE_NAME PATH_SEPARATOR SHELL am__quote' ac_subst_files='' ac_user_opts=' enable_option_checking enable_silent_rules enable_exampledir enable_pkgsrcrcdir with_moduledir with_yangmodelsdir enable_tcmalloc enable_dependency_tracking enable_static_bin enable_shared enable_static with_pic enable_fast_install with_aix_soname with_gnu_ld with_sysroot enable_libtool_lock with_pkg_extra_version with_pkg_git_version with_clippy with_vtysh_pager enable_vtysh enable_doc enable_doc_html enable_zebra enable_bgpd enable_ripd enable_ripngd enable_ospfd enable_ospf6d enable_ldpd enable_nhrpd enable_eigrpd enable_babeld enable_watchfrr enable_isisd enable_pimd enable_pbrd enable_sharpd enable_staticd enable_fabricd enable_vrrpd enable_bgp_announce enable_bgp_vnc enable_bgp_bmp enable_snmp enable_config_rollbacks enable_confd enable_sysrepo enable_grpc enable_zeromq with_libpam enable_ospfapi enable_ospfclient enable_multipath enable_user enable_group enable_vty_group enable_configfile_mask enable_logfile_mask enable_shell_access enable_realms enable_rtadv enable_irdp enable_capabilities enable_rusage enable_gcc_ultra_verbose enable_backtrace enable_time_check enable_pcreposix enable_fpm enable_systemd enable_werror enable_cumulus enable_datacenter enable_fuzzing enable_netlink_fuzzing enable_rr_semantics enable_protobuf enable_oldvpn_commands enable_rpki enable_clippy_only enable_numeric_version enable_gcov enable_bfdd enable_address_sanitizer enable_thread_sanitizer enable_memory_sanitizer with_crypto enable_dev_build enable_lua enable_largefile ' ac_precious_vars='build_alias host_alias target_alias PKG_CONFIG PKG_CONFIG_PATH PKG_CONFIG_LIBDIR CC CFLAGS LDFLAGS LIBS CPPFLAGS CPP CXX CXXFLAGS CCC LUA LUA_INCLUDE LUA_LIB LT_SYS_LIBRARY_PATH CXXCPP PYTHON_CFLAGS PYTHON_LIBS PYTHON PROTOBUF_C_CFLAGS PROTOBUF_C_LIBS CARES_CFLAGS CARES_LIBS LIBYANG_CFLAGS LIBYANG_LIBS SQLITE3_CFLAGS SQLITE3_LIBS SYSREPO_CFLAGS SYSREPO_LIBS GRPC_CFLAGS GRPC_LIBS ZEROMQ_CFLAGS ZEROMQ_LIBS RTRLIB_CFLAGS RTRLIB_LIBS YACC YFLAGS UNWIND_CFLAGS UNWIND_LIBS' # Initialize some variables set by options. ac_init_help= ac_init_version=false ac_unrecognized_opts= ac_unrecognized_sep= # The variables have the same names as the options, with # dashes changed to underlines. cache_file=/dev/null exec_prefix=NONE no_create= no_recursion= prefix=NONE program_prefix=NONE program_suffix=NONE program_transform_name=s,x,x, silent= site= srcdir= verbose= x_includes=NONE x_libraries=NONE # Installation directory options. # These are left unexpanded so users can "make install exec_prefix=/foo" # and all the variables that are supposed to be based on exec_prefix # by default will actually change. # Use braces instead of parens because sh, perl, etc. also accept them. # (The list follows the same order as the GNU Coding Standards.) bindir='${exec_prefix}/bin' sbindir='${exec_prefix}/sbin' libexecdir='${exec_prefix}/libexec' datarootdir='${prefix}/share' datadir='${datarootdir}' sysconfdir='${prefix}/etc' sharedstatedir='${prefix}/com' localstatedir='${prefix}/var' runstatedir='${localstatedir}/run' includedir='${prefix}/include' oldincludedir='/usr/include' docdir='${datarootdir}/doc/${PACKAGE_TARNAME}' infodir='${datarootdir}/info' htmldir='${docdir}' dvidir='${docdir}' pdfdir='${docdir}' psdir='${docdir}' libdir='${exec_prefix}/lib' localedir='${datarootdir}/locale' mandir='${datarootdir}/man' ac_prev= ac_dashdash= for ac_option do # If the previous option needs an argument, assign it. if test -n "$ac_prev"; then eval $ac_prev=\$ac_option ac_prev= continue fi case $ac_option in *=?*) ac_optarg=`expr "X$ac_option" : '[^=]*=\(.*\)'` ;; *=) ac_optarg= ;; *) ac_optarg=yes ;; esac # Accept the important Cygnus configure options, so we can diagnose typos. case $ac_dashdash$ac_option in --) ac_dashdash=yes ;; -bindir | --bindir | --bindi | --bind | --bin | --bi) ac_prev=bindir ;; -bindir=* | --bindir=* | --bindi=* | --bind=* | --bin=* | --bi=*) bindir=$ac_optarg ;; -build | --build | --buil | --bui | --bu) ac_prev=build_alias ;; -build=* | --build=* | --buil=* | --bui=* | --bu=*) build_alias=$ac_optarg ;; -cache-file | --cache-file | --cache-fil | --cache-fi \ | --cache-f | --cache- | --cache | --cach | --cac | --ca | --c) ac_prev=cache_file ;; -cache-file=* | --cache-file=* | --cache-fil=* | --cache-fi=* \ | --cache-f=* | --cache-=* | --cache=* | --cach=* | --cac=* | --ca=* | --c=*) cache_file=$ac_optarg ;; --config-cache | -C) cache_file=config.cache ;; -datadir | --datadir | --datadi | --datad) ac_prev=datadir ;; -datadir=* | --datadir=* | --datadi=* | --datad=*) datadir=$ac_optarg ;; -datarootdir | --datarootdir | --datarootdi | --datarootd | --dataroot \ | --dataroo | --dataro | --datar) ac_prev=datarootdir ;; -datarootdir=* | --datarootdir=* | --datarootdi=* | --datarootd=* \ | --dataroot=* | --dataroo=* | --dataro=* | --datar=*) datarootdir=$ac_optarg ;; -disable-* | --disable-*) ac_useropt=`expr "x$ac_option" : 'x-*disable-\(.*\)'` # Reject names that are not valid shell variable names. expr "x$ac_useropt" : ".*[^-+._$as_cr_alnum]" >/dev/null && as_fn_error $? "invalid feature name: $ac_useropt" ac_useropt_orig=$ac_useropt ac_useropt=`$as_echo "$ac_useropt" | sed 's/[-+.]/_/g'` case $ac_user_opts in *" "enable_$ac_useropt" "*) ;; *) ac_unrecognized_opts="$ac_unrecognized_opts$ac_unrecognized_sep--disable-$ac_useropt_orig" ac_unrecognized_sep=', ';; esac eval enable_$ac_useropt=no ;; -docdir | --docdir | --docdi | --doc | --do) ac_prev=docdir ;; -docdir=* | --docdir=* | --docdi=* | --doc=* | --do=*) docdir=$ac_optarg ;; -dvidir | --dvidir | --dvidi | --dvid | --dvi | --dv) ac_prev=dvidir ;; -dvidir=* | --dvidir=* | --dvidi=* | --dvid=* | --dvi=* | --dv=*) dvidir=$ac_optarg ;; -enable-* | --enable-*) ac_useropt=`expr "x$ac_option" : 'x-*enable-\([^=]*\)'` # Reject names that are not valid shell variable names. expr "x$ac_useropt" : ".*[^-+._$as_cr_alnum]" >/dev/null && as_fn_error $? "invalid feature name: $ac_useropt" ac_useropt_orig=$ac_useropt ac_useropt=`$as_echo "$ac_useropt" | sed 's/[-+.]/_/g'` case $ac_user_opts in *" "enable_$ac_useropt" "*) ;; *) ac_unrecognized_opts="$ac_unrecognized_opts$ac_unrecognized_sep--enable-$ac_useropt_orig" ac_unrecognized_sep=', ';; esac eval enable_$ac_useropt=\$ac_optarg ;; -exec-prefix | --exec_prefix | --exec-prefix | --exec-prefi \ | --exec-pref | --exec-pre | --exec-pr | --exec-p | --exec- \ | --exec | --exe | --ex) ac_prev=exec_prefix ;; -exec-prefix=* | --exec_prefix=* | --exec-prefix=* | --exec-prefi=* \ | --exec-pref=* | --exec-pre=* | --exec-pr=* | --exec-p=* | --exec-=* \ | --exec=* | --exe=* | --ex=*) exec_prefix=$ac_optarg ;; -gas | --gas | --ga | --g) # Obsolete; use --with-gas. with_gas=yes ;; -help | --help | --hel | --he | -h) ac_init_help=long ;; -help=r* | --help=r* | --hel=r* | --he=r* | -hr*) ac_init_help=recursive ;; -help=s* | --help=s* | --hel=s* | --he=s* | -hs*) ac_init_help=short ;; -host | --host | --hos | --ho) ac_prev=host_alias ;; -host=* | --host=* | --hos=* | --ho=*) host_alias=$ac_optarg ;; -htmldir | --htmldir | --htmldi | --htmld | --html | --htm | --ht) ac_prev=htmldir ;; -htmldir=* | --htmldir=* | --htmldi=* | --htmld=* | --html=* | --htm=* \ | --ht=*) htmldir=$ac_optarg ;; -includedir | --includedir | --includedi | --included | --include \ | --includ | --inclu | --incl | --inc) ac_prev=includedir ;; -includedir=* | --includedir=* | --includedi=* | --included=* | --include=* \ | --includ=* | --inclu=* | --incl=* | --inc=*) includedir=$ac_optarg ;; -infodir | --infodir | --infodi | --infod | --info | --inf) ac_prev=infodir ;; -infodir=* | --infodir=* | --infodi=* | --infod=* | --info=* | --inf=*) infodir=$ac_optarg ;; -libdir | --libdir | --libdi | --libd) ac_prev=libdir ;; -libdir=* | --libdir=* | --libdi=* | --libd=*) libdir=$ac_optarg ;; -libexecdir | --libexecdir | --libexecdi | --libexecd | --libexec \ | --libexe | --libex | --libe) ac_prev=libexecdir ;; -libexecdir=* | --libexecdir=* | --libexecdi=* | --libexecd=* | --libexec=* \ | --libexe=* | --libex=* | --libe=*) libexecdir=$ac_optarg ;; -localedir | --localedir | --localedi | --localed | --locale) ac_prev=localedir ;; -localedir=* | --localedir=* | --localedi=* | --localed=* | --locale=*) localedir=$ac_optarg ;; -localstatedir | --localstatedir | --localstatedi | --localstated \ | --localstate | --localstat | --localsta | --localst | --locals) ac_prev=localstatedir ;; -localstatedir=* | --localstatedir=* | --localstatedi=* | --localstated=* \ | --localstate=* | --localstat=* | --localsta=* | --localst=* | --locals=*) localstatedir=$ac_optarg ;; -mandir | --mandir | --mandi | --mand | --man | --ma | --m) ac_prev=mandir ;; -mandir=* | --mandir=* | --mandi=* | --mand=* | --man=* | --ma=* | --m=*) mandir=$ac_optarg ;; -nfp | --nfp | --nf) # Obsolete; use --without-fp. with_fp=no ;; -no-create | --no-create | --no-creat | --no-crea | --no-cre \ | --no-cr | --no-c | -n) no_create=yes ;; -no-recursion | --no-recursion | --no-recursio | --no-recursi \ | --no-recurs | --no-recur | --no-recu | --no-rec | --no-re | --no-r) no_recursion=yes ;; -oldincludedir | --oldincludedir | --oldincludedi | --oldincluded \ | --oldinclude | --oldinclud | --oldinclu | --oldincl | --oldinc \ | --oldin | --oldi | --old | --ol | --o) ac_prev=oldincludedir ;; -oldincludedir=* | --oldincludedir=* | --oldincludedi=* | --oldincluded=* \ | --oldinclude=* | --oldinclud=* | --oldinclu=* | --oldincl=* | --oldinc=* \ | --oldin=* | --oldi=* | --old=* | --ol=* | --o=*) oldincludedir=$ac_optarg ;; -prefix | --prefix | --prefi | --pref | --pre | --pr | --p) ac_prev=prefix ;; -prefix=* | --prefix=* | --prefi=* | --pref=* | --pre=* | --pr=* | --p=*) prefix=$ac_optarg ;; -program-prefix | --program-prefix | --program-prefi | --program-pref \ | --program-pre | --program-pr | --program-p) ac_prev=program_prefix ;; -program-prefix=* | --program-prefix=* | --program-prefi=* \ | --program-pref=* | --program-pre=* | --program-pr=* | --program-p=*) program_prefix=$ac_optarg ;; -program-suffix | --program-suffix | --program-suffi | --program-suff \ | --program-suf | --program-su | --program-s) ac_prev=program_suffix ;; -program-suffix=* | --program-suffix=* | --program-suffi=* \ | --program-suff=* | --program-suf=* | --program-su=* | --program-s=*) program_suffix=$ac_optarg ;; -program-transform-name | --program-transform-name \ | --program-transform-nam | --program-transform-na \ | --program-transform-n | --program-transform- \ | --program-transform | --program-transfor \ | --program-transfo | --program-transf \ | --program-trans | --program-tran \ | --progr-tra | --program-tr | --program-t) ac_prev=program_transform_name ;; -program-transform-name=* | --program-transform-name=* \ | --program-transform-nam=* | --program-transform-na=* \ | --program-transform-n=* | --program-transform-=* \ | --program-transform=* | --program-transfor=* \ | --program-transfo=* | --program-transf=* \ | --program-trans=* | --program-tran=* \ | --progr-tra=* | --program-tr=* | --program-t=*) program_transform_name=$ac_optarg ;; -pdfdir | --pdfdir | --pdfdi | --pdfd | --pdf | --pd) ac_prev=pdfdir ;; -pdfdir=* | --pdfdir=* | --pdfdi=* | --pdfd=* | --pdf=* | --pd=*) pdfdir=$ac_optarg ;; -psdir | --psdir | --psdi | --psd | --ps) ac_prev=psdir ;; -psdir=* | --psdir=* | --psdi=* | --psd=* | --ps=*) psdir=$ac_optarg ;; -q | -quiet | --quiet | --quie | --qui | --qu | --q \ | -silent | --silent | --silen | --sile | --sil) silent=yes ;; -runstatedir | --runstatedir | --runstatedi | --runstated \ | --runstate | --runstat | --runsta | --runst | --runs \ | --run | --ru | --r) ac_prev=runstatedir ;; -runstatedir=* | --runstatedir=* | --runstatedi=* | --runstated=* \ | --runstate=* | --runstat=* | --runsta=* | --runst=* | --runs=* \ | --run=* | --ru=* | --r=*) runstatedir=$ac_optarg ;; -sbindir | --sbindir | --sbindi | --sbind | --sbin | --sbi | --sb) ac_prev=sbindir ;; -sbindir=* | --sbindir=* | --sbindi=* | --sbind=* | --sbin=* \ | --sbi=* | --sb=*) sbindir=$ac_optarg ;; -sharedstatedir | --sharedstatedir | --sharedstatedi \ | --sharedstated | --sharedstate | --sharedstat | --sharedsta \ | --sharedst | --shareds | --shared | --share | --shar \ | --sha | --sh) ac_prev=sharedstatedir ;; -sharedstatedir=* | --sharedstatedir=* | --sharedstatedi=* \ | --sharedstated=* | --sharedstate=* | --sharedstat=* | --sharedsta=* \ | --sharedst=* | --shareds=* | --shared=* | --share=* | --shar=* \ | --sha=* | --sh=*) sharedstatedir=$ac_optarg ;; -site | --site | --sit) ac_prev=site ;; -site=* | --site=* | --sit=*) site=$ac_optarg ;; -srcdir | --srcdir | --srcdi | --srcd | --src | --sr) ac_prev=srcdir ;; -srcdir=* | --srcdir=* | --srcdi=* | --srcd=* | --src=* | --sr=*) srcdir=$ac_optarg ;; -sysconfdir | --sysconfdir | --sysconfdi | --sysconfd | --sysconf \ | --syscon | --sysco | --sysc | --sys | --sy) ac_prev=sysconfdir ;; -sysconfdir=* | --sysconfdir=* | --sysconfdi=* | --sysconfd=* | --sysconf=* \ | --syscon=* | --sysco=* | --sysc=* | --sys=* | --sy=*) sysconfdir=$ac_optarg ;; -target | --target | --targe | --targ | --tar | --ta | --t) ac_prev=target_alias ;; -target=* | --target=* | --targe=* | --targ=* | --tar=* | --ta=* | --t=*) target_alias=$ac_optarg ;; -v | -verbose | --verbose | --verbos | --verbo | --verb) verbose=yes ;; -version | --version | --versio | --versi | --vers | -V) ac_init_version=: ;; -with-* | --with-*) ac_useropt=`expr "x$ac_option" : 'x-*with-\([^=]*\)'` # Reject names that are not valid shell variable names. expr "x$ac_useropt" : ".*[^-+._$as_cr_alnum]" >/dev/null && as_fn_error $? "invalid package name: $ac_useropt" ac_useropt_orig=$ac_useropt ac_useropt=`$as_echo "$ac_useropt" | sed 's/[-+.]/_/g'` case $ac_user_opts in *" "with_$ac_useropt" "*) ;; *) ac_unrecognized_opts="$ac_unrecognized_opts$ac_unrecognized_sep--with-$ac_useropt_orig" ac_unrecognized_sep=', ';; esac eval with_$ac_useropt=\$ac_optarg ;; -without-* | --without-*) ac_useropt=`expr "x$ac_option" : 'x-*without-\(.*\)'` # Reject names that are not valid shell variable names. expr "x$ac_useropt" : ".*[^-+._$as_cr_alnum]" >/dev/null && as_fn_error $? "invalid package name: $ac_useropt" ac_useropt_orig=$ac_useropt ac_useropt=`$as_echo "$ac_useropt" | sed 's/[-+.]/_/g'` case $ac_user_opts in *" "with_$ac_useropt" "*) ;; *) ac_unrecognized_opts="$ac_unrecognized_opts$ac_unrecognized_sep--without-$ac_useropt_orig" ac_unrecognized_sep=', ';; esac eval with_$ac_useropt=no ;; --x) # Obsolete; use --with-x. with_x=yes ;; -x-includes | --x-includes | --x-include | --x-includ | --x-inclu \ | --x-incl | --x-inc | --x-in | --x-i) ac_prev=x_includes ;; -x-includes=* | --x-includes=* | --x-include=* | --x-includ=* | --x-inclu=* \ | --x-incl=* | --x-inc=* | --x-in=* | --x-i=*) x_includes=$ac_optarg ;; -x-libraries | --x-libraries | --x-librarie | --x-librari \ | --x-librar | --x-libra | --x-libr | --x-lib | --x-li | --x-l) ac_prev=x_libraries ;; -x-libraries=* | --x-libraries=* | --x-librarie=* | --x-librari=* \ | --x-librar=* | --x-libra=* | --x-libr=* | --x-lib=* | --x-li=* | --x-l=*) x_libraries=$ac_optarg ;; -*) as_fn_error $? "unrecognized option: \`$ac_option' Try \`$0 --help' for more information" ;; *=*) ac_envvar=`expr "x$ac_option" : 'x\([^=]*\)='` # Reject names that are not valid shell variable names. case $ac_envvar in #( '' | [0-9]* | *[!_$as_cr_alnum]* ) as_fn_error $? "invalid variable name: \`$ac_envvar'" ;; esac eval $ac_envvar=\$ac_optarg export $ac_envvar ;; *) # FIXME: should be removed in autoconf 3.0. $as_echo "$as_me: WARNING: you should use --build, --host, --target" >&2 expr "x$ac_option" : ".*[^-._$as_cr_alnum]" >/dev/null && $as_echo "$as_me: WARNING: invalid host type: $ac_option" >&2 : "${build_alias=$ac_option} ${host_alias=$ac_option} ${target_alias=$ac_option}" ;; esac done if test -n "$ac_prev"; then ac_option=--`echo $ac_prev | sed 's/_/-/g'` as_fn_error $? "missing argument to $ac_option" fi if test -n "$ac_unrecognized_opts"; then case $enable_option_checking in no) ;; fatal) as_fn_error $? "unrecognized options: $ac_unrecognized_opts" ;; *) $as_echo "$as_me: WARNING: unrecognized options: $ac_unrecognized_opts" >&2 ;; esac fi # Check all directory arguments for consistency. for ac_var in exec_prefix prefix bindir sbindir libexecdir datarootdir \ datadir sysconfdir sharedstatedir localstatedir includedir \ oldincludedir docdir infodir htmldir dvidir pdfdir psdir \ libdir localedir mandir runstatedir do eval ac_val=\$$ac_var # Remove trailing slashes. case $ac_val in */ ) ac_val=`expr "X$ac_val" : 'X\(.*[^/]\)' \| "X$ac_val" : 'X\(.*\)'` eval $ac_var=\$ac_val;; esac # Be sure to have absolute directory names. case $ac_val in [\\/$]* | ?:[\\/]* ) continue;; NONE | '' ) case $ac_var in *prefix ) continue;; esac;; esac as_fn_error $? "expected an absolute directory name for --$ac_var: $ac_val" done # There might be people who depend on the old broken behavior: `$host' # used to hold the argument of --host etc. # FIXME: To remove some day. build=$build_alias host=$host_alias target=$target_alias # FIXME: To remove some day. if test "x$host_alias" != x; then if test "x$build_alias" = x; then cross_compiling=maybe elif test "x$build_alias" != "x$host_alias"; then cross_compiling=yes fi fi ac_tool_prefix= test -n "$host_alias" && ac_tool_prefix=$host_alias- test "$silent" = yes && exec 6>/dev/null ac_pwd=`pwd` && test -n "$ac_pwd" && ac_ls_di=`ls -di .` && ac_pwd_ls_di=`cd "$ac_pwd" && ls -di .` || as_fn_error $? "working directory cannot be determined" test "X$ac_ls_di" = "X$ac_pwd_ls_di" || as_fn_error $? "pwd does not report name of working directory" # Find the source files, if location was not specified. if test -z "$srcdir"; then ac_srcdir_defaulted=yes # Try the directory containing this script, then the parent directory. ac_confdir=`$as_dirname -- "$as_myself" || $as_expr X"$as_myself" : 'X\(.*[^/]\)//*[^/][^/]*/*$' \| \ X"$as_myself" : 'X\(//\)[^/]' \| \ X"$as_myself" : 'X\(//\)$' \| \ X"$as_myself" : 'X\(/\)' \| . 2>/dev/null || $as_echo X"$as_myself" | sed '/^X\(.*[^/]\)\/\/*[^/][^/]*\/*$/{ s//\1/ q } /^X\(\/\/\)[^/].*/{ s//\1/ q } /^X\(\/\/\)$/{ s//\1/ q } /^X\(\/\).*/{ s//\1/ q } s/.*/./; q'` srcdir=$ac_confdir if test ! -r "$srcdir/$ac_unique_file"; then srcdir=.. fi else ac_srcdir_defaulted=no fi if test ! -r "$srcdir/$ac_unique_file"; then test "$ac_srcdir_defaulted" = yes && srcdir="$ac_confdir or .." as_fn_error $? "cannot find sources ($ac_unique_file) in $srcdir" fi ac_msg="sources are in $srcdir, but \`cd $srcdir' does not work" ac_abs_confdir=`( cd "$srcdir" && test -r "./$ac_unique_file" || as_fn_error $? "$ac_msg" pwd)` # When building in place, set srcdir=. if test "$ac_abs_confdir" = "$ac_pwd"; then srcdir=. fi # Remove unnecessary trailing slashes from srcdir. # Double slashes in file names in object file debugging info # mess up M-x gdb in Emacs. case $srcdir in */) srcdir=`expr "X$srcdir" : 'X\(.*[^/]\)' \| "X$srcdir" : 'X\(.*\)'`;; esac for ac_var in $ac_precious_vars; do eval ac_env_${ac_var}_set=\${${ac_var}+set} eval ac_env_${ac_var}_value=\$${ac_var} eval ac_cv_env_${ac_var}_set=\${${ac_var}+set} eval ac_cv_env_${ac_var}_value=\$${ac_var} done # # Report the --help message. # if test "$ac_init_help" = "long"; then # Omit some internal or obsolete options to make the list less imposing. # This message is too long to be a string in the A/UX 3.1 sh. cat <<_ACEOF \`configure' configures frr 7.2.1 to adapt to many kinds of systems. Usage: $0 [OPTION]... [VAR=VALUE]... To assign environment variables (e.g., CC, CFLAGS...), specify them as VAR=VALUE. See below for descriptions of some of the useful variables. Defaults for the options are specified in brackets. Configuration: -h, --help display this help and exit --help=short display options specific to this package --help=recursive display the short help of all the included packages -V, --version display version information and exit -q, --quiet, --silent do not print \`checking ...' messages --cache-file=FILE cache test results in FILE [disabled] -C, --config-cache alias for \`--cache-file=config.cache' -n, --no-create do not create output files --srcdir=DIR find the sources in DIR [configure dir or \`..'] Installation directories: --prefix=PREFIX install architecture-independent files in PREFIX [$ac_default_prefix] --exec-prefix=EPREFIX install architecture-dependent files in EPREFIX [PREFIX] By default, \`make install' will install all the files in \`$ac_default_prefix/bin', \`$ac_default_prefix/lib' etc. You can specify an installation prefix other than \`$ac_default_prefix' using \`--prefix', for instance \`--prefix=\$HOME'. For better control, use the options below. Fine tuning of the installation directories: --bindir=DIR user executables [EPREFIX/bin] --sbindir=DIR system admin executables [EPREFIX/sbin] --libexecdir=DIR program executables [EPREFIX/libexec] --sysconfdir=DIR read-only single-machine data [PREFIX/etc] --sharedstatedir=DIR modifiable architecture-independent data [PREFIX/com] --localstatedir=DIR modifiable single-machine data [PREFIX/var] --runstatedir=DIR modifiable per-process data [LOCALSTATEDIR/run] --libdir=DIR object code libraries [EPREFIX/lib] --includedir=DIR C header files [PREFIX/include] --oldincludedir=DIR C header files for non-gcc [/usr/include] --datarootdir=DIR read-only arch.-independent data root [PREFIX/share] --datadir=DIR read-only architecture-independent data [DATAROOTDIR] --infodir=DIR info documentation [DATAROOTDIR/info] --localedir=DIR locale-dependent data [DATAROOTDIR/locale] --mandir=DIR man documentation [DATAROOTDIR/man] --docdir=DIR documentation root [DATAROOTDIR/doc/frr] --htmldir=DIR html documentation [DOCDIR] --dvidir=DIR dvi documentation [DOCDIR] --pdfdir=DIR pdf documentation [DOCDIR] --psdir=DIR ps documentation [DOCDIR] _ACEOF cat <<\_ACEOF Program names: --program-prefix=PREFIX prepend PREFIX to installed program names --program-suffix=SUFFIX append SUFFIX to installed program names --program-transform-name=PROGRAM run sed PROGRAM on installed program names System types: --build=BUILD configure for building on BUILD [guessed] --host=HOST cross-compile to build programs to run on HOST [BUILD] _ACEOF fi if test -n "$ac_init_help"; then case $ac_init_help in short | recursive ) echo "Configuration of frr 7.2.1:";; esac cat <<\_ACEOF Optional Features: --disable-option-checking ignore unrecognized --enable/--with options --disable-FEATURE do not include FEATURE (same as --enable-FEATURE=no) --enable-FEATURE[=ARG] include FEATURE [ARG=yes] --enable-silent-rules less verbose build output (undo: "make V=1") --disable-silent-rules verbose build output (undo: "make V=0") --enable-exampledir specify alternate directory for examples --enable-pkgsrcrcdir specify directory for rc.d scripts --enable-tcmalloc Turn on tcmalloc --enable-dependency-tracking do not reject slow dependency extractors --disable-dependency-tracking speeds up one-time build --enable-static-bin link binaries statically --enable-shared[=PKGS] build shared libraries [default=yes] --enable-static[=PKGS] build static libraries [default=yes] --enable-fast-install[=PKGS] optimize for fast installation [default=yes] --disable-libtool-lock avoid locking (might break parallel builds) --disable-vtysh do not build integrated vty shell for FRR --disable-doc do not build docs --enable-doc-html build HTML docs --disable-zebra do not build zebra daemon --disable-bgpd do not build bgpd --disable-ripd do not build ripd --disable-ripngd do not build ripngd --disable-ospfd do not build ospfd --disable-ospf6d do not build ospf6d --disable-ldpd do not build ldpd --disable-nhrpd do not build nhrpd --disable-eigrpd do not build eigrpd --disable-babeld do not build babeld --disable-watchfrr do not build watchfrr --disable-isisd do not build isisd --disable-pimd do not build pimd --disable-pbrd do not build pbrd --enable-sharpd build sharpd --disable-staticd do not build staticd --disable-fabricd do not build fabricd --disable-vrrpd do not build vrrpd --disable-bgp-announce, turn off BGP route announcement --disable-bgp-vnc turn off BGP VNC support --disable-bgp-bmp turn off BGP BMP support --enable-snmp enable SNMP support for agentx --enable-config-rollbacks enable configuration rollbacks (requires sqlite3) --enable-confd=ARG enable confd integration --enable-sysrepo enable sysrepo integration --enable-grpc enable the gRPC northbound plugin --enable-zeromq enable ZeroMQ handler (libfrrzmq) --disable-ospfapi do not build OSPFAPI to access the OSPF LSA Database --disable-ospfclient do not build OSPFAPI client for OSPFAPI, (this is the default if --disable-ospfapi is set) --enable-multipath=ARG enable multipath function, ARG must be digit --enable-user=USER user to run FRR suite as (default frr) --enable-group=GROUP group to run FRR suite as (default frr) --enable-vty-group=ARG set vty sockets to have specified group as owner --enable-configfile-mask=ARG set mask for config files --enable-logfile-mask=ARG set mask for log files --enable-shell-access Allow users to access shell/telnet/ssh --enable-realms enable REALMS support under Linux --disable-rtadv disable IPV6 router advertisement feature --disable-irdp enable IRDP server support in zebra (default if supported) --disable-capabilities disable using POSIX capabilities --disable-rusage disable using getrusage --enable-gcc-ultra-verbose enable ultra verbose GCC warnings --disable-backtrace, disable crash backtraces (default autodetect) --disable-time-check disable slow thread warning messages --enable-pcreposix enable using PCRE Posix libs for regex functions --enable-fpm enable Forwarding Plane Manager support --enable-systemd enable Systemd support --enable-werror enable -Werror (recommended for developers only) --enable-cumulus enable Cumulus Switch Special Extensions --enable-datacenter enable Compilation for Data Center Extensions --enable-fuzzing enable ability to fuzz various parts of FRR --enable-netlink-fuzzing enable ability to fuzz netlink listening socket in zebra --disable-rr-semantics disable the v6 Route Replace semantics --enable-protobuf Enable experimental protobuf support --enable-oldvpn-commands Keep old vpn commands --enable-rpki enable RPKI prefix validation support --enable-clippy-only Only build clippy --enable-numeric-version Only numeric digits allowed in version (for Alpine) --enable-gcov Add code coverage information --disable-bfdd do not build bfdd --enable-address-sanitizer enable AddressSanitizer support for detecting a wide variety of memory allocation and deallocation errors --enable-thread-sanitizer enable ThreadSanitizer support for detecting data races --enable-memory-sanitizer enable MemorySanitizer support for detecting uninitialized memory reads --enable-dev-build build for development --enable-lua Build Lua scripting --disable-largefile omit support for large files Optional Packages: --with-PACKAGE[=ARG] use PACKAGE [ARG=yes] --without-PACKAGE do not use PACKAGE (same as --with-PACKAGE=no) --with-moduledir=DIR module directory (${libdir}/frr/modules) --with-yangmodelsdir=DIR yang models directory (${datarootdir}/yang) --with-pic[=PKGS] try to use only PIC/non-PIC objects [default=use both] --with-aix-soname=aix|svr4|both shared library versioning (aka "SONAME") variant to provide on AIX, [default=aix]. --with-gnu-ld assume the C compiler uses GNU ld [default=no] --with-sysroot[=DIR] Search for dependent libraries within DIR (or the compiler's sysroot if not specified). --with-pkg-extra-version=VER add extra version field, for packagers/distributions --with-pkg-git-version add git information to MOTD and build version string --with-clippy=PATH use external clippy helper program --with-vtysh-pager=PAGER control what pager is compiled in as default --with-libpam use libpam for PAM support in vtysh --with-crypto= choose between different implementations of cryptographic functions(default value is --with-crypto=internal) Some influential environment variables: PKG_CONFIG path to pkg-config utility PKG_CONFIG_PATH directories to add to pkg-config's search path PKG_CONFIG_LIBDIR path overriding pkg-config's built-in search path CC C compiler command CFLAGS C compiler flags LDFLAGS linker flags, e.g. -L if you have libraries in a nonstandard directory LIBS libraries to pass to the linker, e.g. -l CPPFLAGS (Objective) C/C++ preprocessor flags, e.g. -I if you have headers in a nonstandard directory CPP C preprocessor CXX C++ compiler command CXXFLAGS C++ compiler flags LUA The Lua interpreter, e.g. /usr/bin/lua5.1 LUA_INCLUDE The Lua includes, e.g. -I/usr/include/lua5.1 LUA_LIB The Lua library, e.g. -llua5.1 LT_SYS_LIBRARY_PATH User-defined run-time library search path. CXXCPP C++ preprocessor PYTHON_CFLAGS C compiler flags for Python PYTHON_LIBS linker flags for Python PYTHON Python interpreter to use PROTOBUF_C_CFLAGS C compiler flags for PROTOBUF_C, overriding pkg-config PROTOBUF_C_LIBS linker flags for PROTOBUF_C, overriding pkg-config CARES_CFLAGS C compiler flags for CARES, overriding pkg-config CARES_LIBS linker flags for CARES, overriding pkg-config LIBYANG_CFLAGS C compiler flags for LIBYANG, overriding pkg-config LIBYANG_LIBS linker flags for LIBYANG, overriding pkg-config SQLITE3_CFLAGS C compiler flags for SQLITE3, overriding pkg-config SQLITE3_LIBS linker flags for SQLITE3, overriding pkg-config SYSREPO_CFLAGS C compiler flags for SYSREPO, overriding pkg-config SYSREPO_LIBS linker flags for SYSREPO, overriding pkg-config GRPC_CFLAGS C compiler flags for GRPC, overriding pkg-config GRPC_LIBS linker flags for GRPC, overriding pkg-config ZEROMQ_CFLAGS C compiler flags for ZEROMQ, overriding pkg-config ZEROMQ_LIBS linker flags for ZEROMQ, overriding pkg-config RTRLIB_CFLAGS C compiler flags for RTRLIB, overriding pkg-config RTRLIB_LIBS linker flags for RTRLIB, overriding pkg-config YACC The `Yet Another Compiler Compiler' implementation to use. Defaults to the first program found out of: `bison -y', `byacc', `yacc'. YFLAGS The list of arguments that will be passed by default to $YACC. This script will default YFLAGS to the empty string to avoid a default value of `-d' given by some make applications. UNWIND_CFLAGS C compiler flags for UNWIND, overriding pkg-config UNWIND_LIBS linker flags for UNWIND, overriding pkg-config Use these variables to override the choices made by `configure' or to help it to find libraries and programs with nonstandard names/locations. Report bugs to . _ACEOF ac_status=$? fi if test "$ac_init_help" = "recursive"; then # If there are subdirs, report their specific --help. for ac_dir in : $ac_subdirs_all; do test "x$ac_dir" = x: && continue test -d "$ac_dir" || { cd "$srcdir" && ac_pwd=`pwd` && srcdir=. && test -d "$ac_dir"; } || continue ac_builddir=. case "$ac_dir" in .) ac_dir_suffix= ac_top_builddir_sub=. ac_top_build_prefix= ;; *) ac_dir_suffix=/`$as_echo "$ac_dir" | sed 's|^\.[\\/]||'` # A ".." for each directory in $ac_dir_suffix. ac_top_builddir_sub=`$as_echo "$ac_dir_suffix" | sed 's|/[^\\/]*|/..|g;s|/||'` case $ac_top_builddir_sub in "") ac_top_builddir_sub=. ac_top_build_prefix= ;; *) ac_top_build_prefix=$ac_top_builddir_sub/ ;; esac ;; esac ac_abs_top_builddir=$ac_pwd ac_abs_builddir=$ac_pwd$ac_dir_suffix # for backward compatibility: ac_top_builddir=$ac_top_build_prefix case $srcdir in .) # We are building in place. ac_srcdir=. ac_top_srcdir=$ac_top_builddir_sub ac_abs_top_srcdir=$ac_pwd ;; [\\/]* | ?:[\\/]* ) # Absolute name. ac_srcdir=$srcdir$ac_dir_suffix; ac_top_srcdir=$srcdir ac_abs_top_srcdir=$srcdir ;; *) # Relative name. ac_srcdir=$ac_top_build_prefix$srcdir$ac_dir_suffix ac_top_srcdir=$ac_top_build_prefix$srcdir ac_abs_top_srcdir=$ac_pwd/$srcdir ;; esac ac_abs_srcdir=$ac_abs_top_srcdir$ac_dir_suffix cd "$ac_dir" || { ac_status=$?; continue; } # Check for guested configure. if test -f "$ac_srcdir/configure.gnu"; then echo && $SHELL "$ac_srcdir/configure.gnu" --help=recursive elif test -f "$ac_srcdir/configure"; then echo && $SHELL "$ac_srcdir/configure" --help=recursive else $as_echo "$as_me: WARNING: no configuration information is in $ac_dir" >&2 fi || ac_status=$? cd "$ac_pwd" || { ac_status=$?; break; } done fi test -n "$ac_init_help" && exit $ac_status if $ac_init_version; then cat <<\_ACEOF frr configure 7.2.1 generated by GNU Autoconf 2.69 Copyright (C) 2012 Free Software Foundation, Inc. This configure script is free software; the Free Software Foundation gives unlimited permission to copy, distribute and modify it. _ACEOF exit fi ## ------------------------ ## ## Autoconf initialization. ## ## ------------------------ ## # ac_fn_c_try_compile LINENO # -------------------------- # Try to compile conftest.$ac_ext, and return whether this succeeded. ac_fn_c_try_compile () { as_lineno=${as_lineno-"$1"} as_lineno_stack=as_lineno_stack=$as_lineno_stack rm -f conftest.$ac_objext if { { ac_try="$ac_compile" case "(($ac_try" in *\"* | *\`* | *\\*) ac_try_echo=\$ac_try;; *) ac_try_echo=$ac_try;; esac eval ac_try_echo="\"\$as_me:${as_lineno-$LINENO}: $ac_try_echo\"" $as_echo "$ac_try_echo"; } >&5 (eval "$ac_compile") 2>conftest.err ac_status=$? if test -s conftest.err; then grep -v '^ *+' conftest.err >conftest.er1 cat conftest.er1 >&5 mv -f conftest.er1 conftest.err fi $as_echo "$as_me:${as_lineno-$LINENO}: \$? = $ac_status" >&5 test $ac_status = 0; } && { test -z "$ac_c_werror_flag" || test ! -s conftest.err } && test -s conftest.$ac_objext; then : ac_retval=0 else $as_echo "$as_me: failed program was:" >&5 sed 's/^/| /' conftest.$ac_ext >&5 ac_retval=1 fi eval $as_lineno_stack; ${as_lineno_stack:+:} unset as_lineno as_fn_set_status $ac_retval } # ac_fn_c_try_compile # ac_fn_c_try_cpp LINENO # ---------------------- # Try to preprocess conftest.$ac_ext, and return whether this succeeded. ac_fn_c_try_cpp () { as_lineno=${as_lineno-"$1"} as_lineno_stack=as_lineno_stack=$as_lineno_stack if { { ac_try="$ac_cpp conftest.$ac_ext" case "(($ac_try" in *\"* | *\`* | *\\*) ac_try_echo=\$ac_try;; *) ac_try_echo=$ac_try;; esac eval ac_try_echo="\"\$as_me:${as_lineno-$LINENO}: $ac_try_echo\"" $as_echo "$ac_try_echo"; } >&5 (eval "$ac_cpp conftest.$ac_ext") 2>conftest.err ac_status=$? if test -s conftest.err; then grep -v '^ *+' conftest.err >conftest.er1 cat conftest.er1 >&5 mv -f conftest.er1 conftest.err fi $as_echo "$as_me:${as_lineno-$LINENO}: \$? = $ac_status" >&5 test $ac_status = 0; } > conftest.i && { test -z "$ac_c_preproc_warn_flag$ac_c_werror_flag" || test ! -s conftest.err }; then : ac_retval=0 else $as_echo "$as_me: failed program was:" >&5 sed 's/^/| /' conftest.$ac_ext >&5 ac_retval=1 fi eval $as_lineno_stack; ${as_lineno_stack:+:} unset as_lineno as_fn_set_status $ac_retval } # ac_fn_c_try_cpp # ac_fn_cxx_try_compile LINENO # ---------------------------- # Try to compile conftest.$ac_ext, and return whether this succeeded. ac_fn_cxx_try_compile () { as_lineno=${as_lineno-"$1"} as_lineno_stack=as_lineno_stack=$as_lineno_stack rm -f conftest.$ac_objext if { { ac_try="$ac_compile" case "(($ac_try" in *\"* | *\`* | *\\*) ac_try_echo=\$ac_try;; *) ac_try_echo=$ac_try;; esac eval ac_try_echo="\"\$as_me:${as_lineno-$LINENO}: $ac_try_echo\"" $as_echo "$ac_try_echo"; } >&5 (eval "$ac_compile") 2>conftest.err ac_status=$? if test -s conftest.err; then grep -v '^ *+' conftest.err >conftest.er1 cat conftest.er1 >&5 mv -f conftest.er1 conftest.err fi $as_echo "$as_me:${as_lineno-$LINENO}: \$? = $ac_status" >&5 test $ac_status = 0; } && { test -z "$ac_cxx_werror_flag" || test ! -s conftest.err } && test -s conftest.$ac_objext; then : ac_retval=0 else $as_echo "$as_me: failed program was:" >&5 sed 's/^/| /' conftest.$ac_ext >&5 ac_retval=1 fi eval $as_lineno_stack; ${as_lineno_stack:+:} unset as_lineno as_fn_set_status $ac_retval } # ac_fn_cxx_try_compile # ac_fn_c_check_header_mongrel LINENO HEADER VAR INCLUDES # ------------------------------------------------------- # Tests whether HEADER exists, giving a warning if it cannot be compiled using # the include files in INCLUDES and setting the cache variable VAR # accordingly. ac_fn_c_check_header_mongrel () { as_lineno=${as_lineno-"$1"} as_lineno_stack=as_lineno_stack=$as_lineno_stack if eval \${$3+:} false; then : { $as_echo "$as_me:${as_lineno-$LINENO}: checking for $2" >&5 $as_echo_n "checking for $2... " >&6; } if eval \${$3+:} false; then : $as_echo_n "(cached) " >&6 fi eval ac_res=\$$3 { $as_echo "$as_me:${as_lineno-$LINENO}: result: $ac_res" >&5 $as_echo "$ac_res" >&6; } else # Is the header compilable? { $as_echo "$as_me:${as_lineno-$LINENO}: checking $2 usability" >&5 $as_echo_n "checking $2 usability... " >&6; } cat confdefs.h - <<_ACEOF >conftest.$ac_ext /* end confdefs.h. */ $4 #include <$2> _ACEOF if ac_fn_c_try_compile "$LINENO"; then : ac_header_compiler=yes else ac_header_compiler=no fi rm -f core conftest.err conftest.$ac_objext conftest.$ac_ext { $as_echo "$as_me:${as_lineno-$LINENO}: result: $ac_header_compiler" >&5 $as_echo "$ac_header_compiler" >&6; } # Is the header present? { $as_echo "$as_me:${as_lineno-$LINENO}: checking $2 presence" >&5 $as_echo_n "checking $2 presence... " >&6; } cat confdefs.h - <<_ACEOF >conftest.$ac_ext /* end confdefs.h. */ #include <$2> _ACEOF if ac_fn_c_try_cpp "$LINENO"; then : ac_header_preproc=yes else ac_header_preproc=no fi rm -f conftest.err conftest.i conftest.$ac_ext { $as_echo "$as_me:${as_lineno-$LINENO}: result: $ac_header_preproc" >&5 $as_echo "$ac_header_preproc" >&6; } # So? What about this header? case $ac_header_compiler:$ac_header_preproc:$ac_c_preproc_warn_flag in #(( yes:no: ) { $as_echo "$as_me:${as_lineno-$LINENO}: WARNING: $2: accepted by the compiler, rejected by the preprocessor!" >&5 $as_echo "$as_me: WARNING: $2: accepted by the compiler, rejected by the preprocessor!" >&2;} { $as_echo "$as_me:${as_lineno-$LINENO}: WARNING: $2: proceeding with the compiler's result" >&5 $as_echo "$as_me: WARNING: $2: proceeding with the compiler's result" >&2;} ;; no:yes:* ) { $as_echo "$as_me:${as_lineno-$LINENO}: WARNING: $2: present but cannot be compiled" >&5 $as_echo "$as_me: WARNING: $2: present but cannot be compiled" >&2;} { $as_echo "$as_me:${as_lineno-$LINENO}: WARNING: $2: check for missing prerequisite headers?" >&5 $as_echo "$as_me: WARNING: $2: check for missing prerequisite headers?" >&2;} { $as_echo "$as_me:${as_lineno-$LINENO}: WARNING: $2: see the Autoconf documentation" >&5 $as_echo "$as_me: WARNING: $2: see the Autoconf documentation" >&2;} { $as_echo "$as_me:${as_lineno-$LINENO}: WARNING: $2: section \"Present But Cannot Be Compiled\"" >&5 $as_echo "$as_me: WARNING: $2: section \"Present But Cannot Be Compiled\"" >&2;} { $as_echo "$as_me:${as_lineno-$LINENO}: WARNING: $2: proceeding with the compiler's result" >&5 $as_echo "$as_me: WARNING: $2: proceeding with the compiler's result" >&2;} ( $as_echo "## ------------------------------------------------------ ## ## Report this to https://github.com/frrouting/frr/issues ## ## ------------------------------------------------------ ##" ) | sed "s/^/$as_me: WARNING: /" >&2 ;; esac { $as_echo "$as_me:${as_lineno-$LINENO}: checking for $2" >&5 $as_echo_n "checking for $2... " >&6; } if eval \${$3+:} false; then : $as_echo_n "(cached) " >&6 else eval "$3=\$ac_header_compiler" fi eval ac_res=\$$3 { $as_echo "$as_me:${as_lineno-$LINENO}: result: $ac_res" >&5 $as_echo "$ac_res" >&6; } fi eval $as_lineno_stack; ${as_lineno_stack:+:} unset as_lineno } # ac_fn_c_check_header_mongrel # ac_fn_c_try_run LINENO # ---------------------- # Try to link conftest.$ac_ext, and return whether this succeeded. Assumes # that executables *can* be run. ac_fn_c_try_run () { as_lineno=${as_lineno-"$1"} as_lineno_stack=as_lineno_stack=$as_lineno_stack if { { ac_try="$ac_link" case "(($ac_try" in *\"* | *\`* | *\\*) ac_try_echo=\$ac_try;; *) ac_try_echo=$ac_try;; esac eval ac_try_echo="\"\$as_me:${as_lineno-$LINENO}: $ac_try_echo\"" $as_echo "$ac_try_echo"; } >&5 (eval "$ac_link") 2>&5 ac_status=$? $as_echo "$as_me:${as_lineno-$LINENO}: \$? = $ac_status" >&5 test $ac_status = 0; } && { ac_try='./conftest$ac_exeext' { { case "(($ac_try" in *\"* | *\`* | *\\*) ac_try_echo=\$ac_try;; *) ac_try_echo=$ac_try;; esac eval ac_try_echo="\"\$as_me:${as_lineno-$LINENO}: $ac_try_echo\"" $as_echo "$ac_try_echo"; } >&5 (eval "$ac_try") 2>&5 ac_status=$? $as_echo "$as_me:${as_lineno-$LINENO}: \$? = $ac_status" >&5 test $ac_status = 0; }; }; then : ac_retval=0 else $as_echo "$as_me: program exited with status $ac_status" >&5 $as_echo "$as_me: failed program was:" >&5 sed 's/^/| /' conftest.$ac_ext >&5 ac_retval=$ac_status fi rm -rf conftest.dSYM conftest_ipa8_conftest.oo eval $as_lineno_stack; ${as_lineno_stack:+:} unset as_lineno as_fn_set_status $ac_retval } # ac_fn_c_try_run # ac_fn_c_check_header_compile LINENO HEADER VAR INCLUDES # ------------------------------------------------------- # Tests whether HEADER exists and can be compiled using the include files in # INCLUDES, setting the cache variable VAR accordingly. ac_fn_c_check_header_compile () { as_lineno=${as_lineno-"$1"} as_lineno_stack=as_lineno_stack=$as_lineno_stack { $as_echo "$as_me:${as_lineno-$LINENO}: checking for $2" >&5 $as_echo_n "checking for $2... " >&6; } if eval \${$3+:} false; then : $as_echo_n "(cached) " >&6 else cat confdefs.h - <<_ACEOF >conftest.$ac_ext /* end confdefs.h. */ $4 #include <$2> _ACEOF if ac_fn_c_try_compile "$LINENO"; then : eval "$3=yes" else eval "$3=no" fi rm -f core conftest.err conftest.$ac_objext conftest.$ac_ext fi eval ac_res=\$$3 { $as_echo "$as_me:${as_lineno-$LINENO}: result: $ac_res" >&5 $as_echo "$ac_res" >&6; } eval $as_lineno_stack; ${as_lineno_stack:+:} unset as_lineno } # ac_fn_c_check_header_compile # ac_fn_c_try_link LINENO # ----------------------- # Try to link conftest.$ac_ext, and return whether this succeeded. ac_fn_c_try_link () { as_lineno=${as_lineno-"$1"} as_lineno_stack=as_lineno_stack=$as_lineno_stack rm -f conftest.$ac_objext conftest$ac_exeext if { { ac_try="$ac_link" case "(($ac_try" in *\"* | *\`* | *\\*) ac_try_echo=\$ac_try;; *) ac_try_echo=$ac_try;; esac eval ac_try_echo="\"\$as_me:${as_lineno-$LINENO}: $ac_try_echo\"" $as_echo "$ac_try_echo"; } >&5 (eval "$ac_link") 2>conftest.err ac_status=$? if test -s conftest.err; then grep -v '^ *+' conftest.err >conftest.er1 cat conftest.er1 >&5 mv -f conftest.er1 conftest.err fi $as_echo "$as_me:${as_lineno-$LINENO}: \$? = $ac_status" >&5 test $ac_status = 0; } && { test -z "$ac_c_werror_flag" || test ! -s conftest.err } && test -s conftest$ac_exeext && { test "$cross_compiling" = yes || test -x conftest$ac_exeext }; then : ac_retval=0 else $as_echo "$as_me: failed program was:" >&5 sed 's/^/| /' conftest.$ac_ext >&5 ac_retval=1 fi # Delete the IPA/IPO (Inter Procedural Analysis/Optimization) information # created by the PGI compiler (conftest_ipa8_conftest.oo), as it would # interfere with the next link command; also delete a directory that is # left behind by Apple's compiler. We do this before executing the actions. rm -rf conftest.dSYM conftest_ipa8_conftest.oo eval $as_lineno_stack; ${as_lineno_stack:+:} unset as_lineno as_fn_set_status $ac_retval } # ac_fn_c_try_link # ac_fn_c_check_func LINENO FUNC VAR # ---------------------------------- # Tests whether FUNC exists, setting the cache variable VAR accordingly ac_fn_c_check_func () { as_lineno=${as_lineno-"$1"} as_lineno_stack=as_lineno_stack=$as_lineno_stack { $as_echo "$as_me:${as_lineno-$LINENO}: checking for $2" >&5 $as_echo_n "checking for $2... " >&6; } if eval \${$3+:} false; then : $as_echo_n "(cached) " >&6 else cat confdefs.h - <<_ACEOF >conftest.$ac_ext /* end confdefs.h. */ /* Define $2 to an innocuous variant, in case declares $2. For example, HP-UX 11i declares gettimeofday. */ #define $2 innocuous_$2 /* System header to define __stub macros and hopefully few prototypes, which can conflict with char $2 (); below. Prefer to if __STDC__ is defined, since exists even on freestanding compilers. */ #ifdef __STDC__ # include #else # include #endif #undef $2 /* Override any GCC internal prototype to avoid an error. Use char because int might match the return type of a GCC builtin and then its argument prototype would still apply. */ #ifdef __cplusplus extern "C" #endif char $2 (); /* The GNU C library defines this for functions which it implements to always fail with ENOSYS. Some functions are actually named something starting with __ and the normal name is an alias. */ #if defined __stub_$2 || defined __stub___$2 choke me #endif int main () { return $2 (); ; return 0; } _ACEOF if ac_fn_c_try_link "$LINENO"; then : eval "$3=yes" else eval "$3=no" fi rm -f core conftest.err conftest.$ac_objext \ conftest$ac_exeext conftest.$ac_ext fi eval ac_res=\$$3 { $as_echo "$as_me:${as_lineno-$LINENO}: result: $ac_res" >&5 $as_echo "$ac_res" >&6; } eval $as_lineno_stack; ${as_lineno_stack:+:} unset as_lineno } # ac_fn_c_check_func # ac_fn_cxx_try_cpp LINENO # ------------------------ # Try to preprocess conftest.$ac_ext, and return whether this succeeded. ac_fn_cxx_try_cpp () { as_lineno=${as_lineno-"$1"} as_lineno_stack=as_lineno_stack=$as_lineno_stack if { { ac_try="$ac_cpp conftest.$ac_ext" case "(($ac_try" in *\"* | *\`* | *\\*) ac_try_echo=\$ac_try;; *) ac_try_echo=$ac_try;; esac eval ac_try_echo="\"\$as_me:${as_lineno-$LINENO}: $ac_try_echo\"" $as_echo "$ac_try_echo"; } >&5 (eval "$ac_cpp conftest.$ac_ext") 2>conftest.err ac_status=$? if test -s conftest.err; then grep -v '^ *+' conftest.err >conftest.er1 cat conftest.er1 >&5 mv -f conftest.er1 conftest.err fi $as_echo "$as_me:${as_lineno-$LINENO}: \$? = $ac_status" >&5 test $ac_status = 0; } > conftest.i && { test -z "$ac_cxx_preproc_warn_flag$ac_cxx_werror_flag" || test ! -s conftest.err }; then : ac_retval=0 else $as_echo "$as_me: failed program was:" >&5 sed 's/^/| /' conftest.$ac_ext >&5 ac_retval=1 fi eval $as_lineno_stack; ${as_lineno_stack:+:} unset as_lineno as_fn_set_status $ac_retval } # ac_fn_cxx_try_cpp # ac_fn_cxx_try_link LINENO # ------------------------- # Try to link conftest.$ac_ext, and return whether this succeeded. ac_fn_cxx_try_link () { as_lineno=${as_lineno-"$1"} as_lineno_stack=as_lineno_stack=$as_lineno_stack rm -f conftest.$ac_objext conftest$ac_exeext if { { ac_try="$ac_link" case "(($ac_try" in *\"* | *\`* | *\\*) ac_try_echo=\$ac_try;; *) ac_try_echo=$ac_try;; esac eval ac_try_echo="\"\$as_me:${as_lineno-$LINENO}: $ac_try_echo\"" $as_echo "$ac_try_echo"; } >&5 (eval "$ac_link") 2>conftest.err ac_status=$? if test -s conftest.err; then grep -v '^ *+' conftest.err >conftest.er1 cat conftest.er1 >&5 mv -f conftest.er1 conftest.err fi $as_echo "$as_me:${as_lineno-$LINENO}: \$? = $ac_status" >&5 test $ac_status = 0; } && { test -z "$ac_cxx_werror_flag" || test ! -s conftest.err } && test -s conftest$ac_exeext && { test "$cross_compiling" = yes || test -x conftest$ac_exeext }; then : ac_retval=0 else $as_echo "$as_me: failed program was:" >&5 sed 's/^/| /' conftest.$ac_ext >&5 ac_retval=1 fi # Delete the IPA/IPO (Inter Procedural Analysis/Optimization) information # created by the PGI compiler (conftest_ipa8_conftest.oo), as it would # interfere with the next link command; also delete a directory that is # left behind by Apple's compiler. We do this before executing the actions. rm -rf conftest.dSYM conftest_ipa8_conftest.oo eval $as_lineno_stack; ${as_lineno_stack:+:} unset as_lineno as_fn_set_status $ac_retval } # ac_fn_cxx_try_link # ac_fn_c_check_member LINENO AGGR MEMBER VAR INCLUDES # ---------------------------------------------------- # Tries to find if the field MEMBER exists in type AGGR, after including # INCLUDES, setting cache variable VAR accordingly. ac_fn_c_check_member () { as_lineno=${as_lineno-"$1"} as_lineno_stack=as_lineno_stack=$as_lineno_stack { $as_echo "$as_me:${as_lineno-$LINENO}: checking for $2.$3" >&5 $as_echo_n "checking for $2.$3... " >&6; } if eval \${$4+:} false; then : $as_echo_n "(cached) " >&6 else cat confdefs.h - <<_ACEOF >conftest.$ac_ext /* end confdefs.h. */ $5 int main () { static $2 ac_aggr; if (ac_aggr.$3) return 0; ; return 0; } _ACEOF if ac_fn_c_try_compile "$LINENO"; then : eval "$4=yes" else cat confdefs.h - <<_ACEOF >conftest.$ac_ext /* end confdefs.h. */ $5 int main () { static $2 ac_aggr; if (sizeof ac_aggr.$3) return 0; ; return 0; } _ACEOF if ac_fn_c_try_compile "$LINENO"; then : eval "$4=yes" else eval "$4=no" fi rm -f core conftest.err conftest.$ac_objext conftest.$ac_ext fi rm -f core conftest.err conftest.$ac_objext conftest.$ac_ext fi eval ac_res=\$$4 { $as_echo "$as_me:${as_lineno-$LINENO}: result: $ac_res" >&5 $as_echo "$ac_res" >&6; } eval $as_lineno_stack; ${as_lineno_stack:+:} unset as_lineno } # ac_fn_c_check_member # ac_fn_c_check_decl LINENO SYMBOL VAR INCLUDES # --------------------------------------------- # Tests whether SYMBOL is declared in INCLUDES, setting cache variable VAR # accordingly. ac_fn_c_check_decl () { as_lineno=${as_lineno-"$1"} as_lineno_stack=as_lineno_stack=$as_lineno_stack as_decl_name=`echo $2|sed 's/ *(.*//'` as_decl_use=`echo $2|sed -e 's/(/((/' -e 's/)/) 0&/' -e 's/,/) 0& (/g'` { $as_echo "$as_me:${as_lineno-$LINENO}: checking whether $as_decl_name is declared" >&5 $as_echo_n "checking whether $as_decl_name is declared... " >&6; } if eval \${$3+:} false; then : $as_echo_n "(cached) " >&6 else cat confdefs.h - <<_ACEOF >conftest.$ac_ext /* end confdefs.h. */ $4 int main () { #ifndef $as_decl_name #ifdef __cplusplus (void) $as_decl_use; #else (void) $as_decl_name; #endif #endif ; return 0; } _ACEOF if ac_fn_c_try_compile "$LINENO"; then : eval "$3=yes" else eval "$3=no" fi rm -f core conftest.err conftest.$ac_objext conftest.$ac_ext fi eval ac_res=\$$3 { $as_echo "$as_me:${as_lineno-$LINENO}: result: $ac_res" >&5 $as_echo "$ac_res" >&6; } eval $as_lineno_stack; ${as_lineno_stack:+:} unset as_lineno } # ac_fn_c_check_decl # ac_fn_c_check_type LINENO TYPE VAR INCLUDES # ------------------------------------------- # Tests whether TYPE exists after having included INCLUDES, setting cache # variable VAR accordingly. ac_fn_c_check_type () { as_lineno=${as_lineno-"$1"} as_lineno_stack=as_lineno_stack=$as_lineno_stack { $as_echo "$as_me:${as_lineno-$LINENO}: checking for $2" >&5 $as_echo_n "checking for $2... " >&6; } if eval \${$3+:} false; then : $as_echo_n "(cached) " >&6 else eval "$3=no" cat confdefs.h - <<_ACEOF >conftest.$ac_ext /* end confdefs.h. */ $4 int main () { if (sizeof ($2)) return 0; ; return 0; } _ACEOF if ac_fn_c_try_compile "$LINENO"; then : cat confdefs.h - <<_ACEOF >conftest.$ac_ext /* end confdefs.h. */ $4 int main () { if (sizeof (($2))) return 0; ; return 0; } _ACEOF if ac_fn_c_try_compile "$LINENO"; then : else eval "$3=yes" fi rm -f core conftest.err conftest.$ac_objext conftest.$ac_ext fi rm -f core conftest.err conftest.$ac_objext conftest.$ac_ext fi eval ac_res=\$$3 { $as_echo "$as_me:${as_lineno-$LINENO}: result: $ac_res" >&5 $as_echo "$ac_res" >&6; } eval $as_lineno_stack; ${as_lineno_stack:+:} unset as_lineno } # ac_fn_c_check_type cat >config.log <<_ACEOF This file contains any messages produced by compilers while running configure, to aid debugging if configure makes a mistake. It was created by frr $as_me 7.2.1, which was generated by GNU Autoconf 2.69. Invocation command line was $ $0 $@ _ACEOF exec 5>>config.log { cat <<_ASUNAME ## --------- ## ## Platform. ## ## --------- ## hostname = `(hostname || uname -n) 2>/dev/null | sed 1q` uname -m = `(uname -m) 2>/dev/null || echo unknown` uname -r = `(uname -r) 2>/dev/null || echo unknown` uname -s = `(uname -s) 2>/dev/null || echo unknown` uname -v = `(uname -v) 2>/dev/null || echo unknown` /usr/bin/uname -p = `(/usr/bin/uname -p) 2>/dev/null || echo unknown` /bin/uname -X = `(/bin/uname -X) 2>/dev/null || echo unknown` /bin/arch = `(/bin/arch) 2>/dev/null || echo unknown` /usr/bin/arch -k = `(/usr/bin/arch -k) 2>/dev/null || echo unknown` /usr/convex/getsysinfo = `(/usr/convex/getsysinfo) 2>/dev/null || echo unknown` /usr/bin/hostinfo = `(/usr/bin/hostinfo) 2>/dev/null || echo unknown` /bin/machine = `(/bin/machine) 2>/dev/null || echo unknown` /usr/bin/oslevel = `(/usr/bin/oslevel) 2>/dev/null || echo unknown` /bin/universe = `(/bin/universe) 2>/dev/null || echo unknown` _ASUNAME as_save_IFS=$IFS; IFS=$PATH_SEPARATOR for as_dir in $PATH do IFS=$as_save_IFS test -z "$as_dir" && as_dir=. $as_echo "PATH: $as_dir" done IFS=$as_save_IFS } >&5 cat >&5 <<_ACEOF ## ----------- ## ## Core tests. ## ## ----------- ## _ACEOF # Keep a trace of the command line. # Strip out --no-create and --no-recursion so they do not pile up. # Strip out --silent because we don't want to record it for future runs. # Also quote any args containing shell meta-characters. # Make two passes to allow for proper duplicate-argument suppression. ac_configure_args= ac_configure_args0= ac_configure_args1= ac_must_keep_next=false for ac_pass in 1 2 do for ac_arg do case $ac_arg in -no-create | --no-c* | -n | -no-recursion | --no-r*) continue ;; -q | -quiet | --quiet | --quie | --qui | --qu | --q \ | -silent | --silent | --silen | --sile | --sil) continue ;; *\'*) ac_arg=`$as_echo "$ac_arg" | sed "s/'/'\\\\\\\\''/g"` ;; esac case $ac_pass in 1) as_fn_append ac_configure_args0 " '$ac_arg'" ;; 2) as_fn_append ac_configure_args1 " '$ac_arg'" if test $ac_must_keep_next = true; then ac_must_keep_next=false # Got value, back to normal. else case $ac_arg in *=* | --config-cache | -C | -disable-* | --disable-* \ | -enable-* | --enable-* | -gas | --g* | -nfp | --nf* \ | -q | -quiet | --q* | -silent | --sil* | -v | -verb* \ | -with-* | --with-* | -without-* | --without-* | --x) case "$ac_configure_args0 " in "$ac_configure_args1"*" '$ac_arg' "* ) continue ;; esac ;; -* ) ac_must_keep_next=true ;; esac fi as_fn_append ac_configure_args " '$ac_arg'" ;; esac done done { ac_configure_args0=; unset ac_configure_args0;} { ac_configure_args1=; unset ac_configure_args1;} # When interrupted or exit'd, cleanup temporary files, and complete # config.log. We remove comments because anyway the quotes in there # would cause problems or look ugly. # WARNING: Use '\'' to represent an apostrophe within the trap. # WARNING: Do not start the trap code with a newline, due to a FreeBSD 4.0 bug. trap 'exit_status=$? # Save into config.log some information that might help in debugging. { echo $as_echo "## ---------------- ## ## Cache variables. ## ## ---------------- ##" echo # The following way of writing the cache mishandles newlines in values, ( for ac_var in `(set) 2>&1 | sed -n '\''s/^\([a-zA-Z_][a-zA-Z0-9_]*\)=.*/\1/p'\''`; do eval ac_val=\$$ac_var case $ac_val in #( *${as_nl}*) case $ac_var in #( *_cv_*) { $as_echo "$as_me:${as_lineno-$LINENO}: WARNING: cache variable $ac_var contains a newline" >&5 $as_echo "$as_me: WARNING: cache variable $ac_var contains a newline" >&2;} ;; esac case $ac_var in #( _ | IFS | as_nl) ;; #( BASH_ARGV | BASH_SOURCE) eval $ac_var= ;; #( *) { eval $ac_var=; unset $ac_var;} ;; esac ;; esac done (set) 2>&1 | case $as_nl`(ac_space='\'' '\''; set) 2>&1` in #( *${as_nl}ac_space=\ *) sed -n \ "s/'\''/'\''\\\\'\'''\''/g; s/^\\([_$as_cr_alnum]*_cv_[_$as_cr_alnum]*\\)=\\(.*\\)/\\1='\''\\2'\''/p" ;; #( *) sed -n "/^[_$as_cr_alnum]*_cv_[_$as_cr_alnum]*=/p" ;; esac | sort ) echo $as_echo "## ----------------- ## ## Output variables. ## ## ----------------- ##" echo for ac_var in $ac_subst_vars do eval ac_val=\$$ac_var case $ac_val in *\'\''*) ac_val=`$as_echo "$ac_val" | sed "s/'\''/'\''\\\\\\\\'\'''\''/g"`;; esac $as_echo "$ac_var='\''$ac_val'\''" done | sort echo if test -n "$ac_subst_files"; then $as_echo "## ------------------- ## ## File substitutions. ## ## ------------------- ##" echo for ac_var in $ac_subst_files do eval ac_val=\$$ac_var case $ac_val in *\'\''*) ac_val=`$as_echo "$ac_val" | sed "s/'\''/'\''\\\\\\\\'\'''\''/g"`;; esac $as_echo "$ac_var='\''$ac_val'\''" done | sort echo fi if test -s confdefs.h; then $as_echo "## ----------- ## ## confdefs.h. ## ## ----------- ##" echo cat confdefs.h echo fi test "$ac_signal" != 0 && $as_echo "$as_me: caught signal $ac_signal" $as_echo "$as_me: exit $exit_status" } >&5 rm -f core *.core core.conftest.* && rm -f -r conftest* confdefs* conf$$* $ac_clean_files && exit $exit_status ' 0 for ac_signal in 1 2 13 15; do trap 'ac_signal='$ac_signal'; as_fn_exit 1' $ac_signal done ac_signal=0 # confdefs.h avoids OS command line length limits that DEFS can exceed. rm -f -r conftest* confdefs.h $as_echo "/* confdefs.h */" > confdefs.h # Predefined preprocessor variables. cat >>confdefs.h <<_ACEOF #define PACKAGE_NAME "$PACKAGE_NAME" _ACEOF cat >>confdefs.h <<_ACEOF #define PACKAGE_TARNAME "$PACKAGE_TARNAME" _ACEOF cat >>confdefs.h <<_ACEOF #define PACKAGE_VERSION "$PACKAGE_VERSION" _ACEOF cat >>confdefs.h <<_ACEOF #define PACKAGE_STRING "$PACKAGE_STRING" _ACEOF cat >>confdefs.h <<_ACEOF #define PACKAGE_BUGREPORT "$PACKAGE_BUGREPORT" _ACEOF cat >>confdefs.h <<_ACEOF #define PACKAGE_URL "$PACKAGE_URL" _ACEOF # Let the site file select an alternate cache file if it wants to. # Prefer an explicitly selected file to automatically selected ones. ac_site_file1=NONE ac_site_file2=NONE if test -n "$CONFIG_SITE"; then # We do not want a PATH search for config.site. case $CONFIG_SITE in #(( -*) ac_site_file1=./$CONFIG_SITE;; */*) ac_site_file1=$CONFIG_SITE;; *) ac_site_file1=./$CONFIG_SITE;; esac elif test "x$prefix" != xNONE; then ac_site_file1=$prefix/share/config.site ac_site_file2=$prefix/etc/config.site else ac_site_file1=$ac_default_prefix/share/config.site ac_site_file2=$ac_default_prefix/etc/config.site fi for ac_site_file in "$ac_site_file1" "$ac_site_file2" do test "x$ac_site_file" = xNONE && continue if test /dev/null != "$ac_site_file" && test -r "$ac_site_file"; then { $as_echo "$as_me:${as_lineno-$LINENO}: loading site script $ac_site_file" >&5 $as_echo "$as_me: loading site script $ac_site_file" >&6;} sed 's/^/| /' "$ac_site_file" >&5 . "$ac_site_file" \ || { { $as_echo "$as_me:${as_lineno-$LINENO}: error: in \`$ac_pwd':" >&5 $as_echo "$as_me: error: in \`$ac_pwd':" >&2;} as_fn_error $? "failed to load site script $ac_site_file See \`config.log' for more details" "$LINENO" 5; } fi done if test -r "$cache_file"; then # Some versions of bash will fail to source /dev/null (special files # actually), so we avoid doing that. DJGPP emulates it as a regular file. if test /dev/null != "$cache_file" && test -f "$cache_file"; then { $as_echo "$as_me:${as_lineno-$LINENO}: loading cache $cache_file" >&5 $as_echo "$as_me: loading cache $cache_file" >&6;} case $cache_file in [\\/]* | ?:[\\/]* ) . "$cache_file";; *) . "./$cache_file";; esac fi else { $as_echo "$as_me:${as_lineno-$LINENO}: creating cache $cache_file" >&5 $as_echo "$as_me: creating cache $cache_file" >&6;} >$cache_file fi # Check that the precious variables saved in the cache have kept the same # value. ac_cache_corrupted=false for ac_var in $ac_precious_vars; do eval ac_old_set=\$ac_cv_env_${ac_var}_set eval ac_new_set=\$ac_env_${ac_var}_set eval ac_old_val=\$ac_cv_env_${ac_var}_value eval ac_new_val=\$ac_env_${ac_var}_value case $ac_old_set,$ac_new_set in set,) { $as_echo "$as_me:${as_lineno-$LINENO}: error: \`$ac_var' was set to \`$ac_old_val' in the previous run" >&5 $as_echo "$as_me: error: \`$ac_var' was set to \`$ac_old_val' in the previous run" >&2;} ac_cache_corrupted=: ;; ,set) { $as_echo "$as_me:${as_lineno-$LINENO}: error: \`$ac_var' was not set in the previous run" >&5 $as_echo "$as_me: error: \`$ac_var' was not set in the previous run" >&2;} ac_cache_corrupted=: ;; ,);; *) if test "x$ac_old_val" != "x$ac_new_val"; then # differences in whitespace do not lead to failure. ac_old_val_w=`echo x $ac_old_val` ac_new_val_w=`echo x $ac_new_val` if test "$ac_old_val_w" != "$ac_new_val_w"; then { $as_echo "$as_me:${as_lineno-$LINENO}: error: \`$ac_var' has changed since the previous run:" >&5 $as_echo "$as_me: error: \`$ac_var' has changed since the previous run:" >&2;} ac_cache_corrupted=: else { $as_echo "$as_me:${as_lineno-$LINENO}: warning: ignoring whitespace changes in \`$ac_var' since the previous run:" >&5 $as_echo "$as_me: warning: ignoring whitespace changes in \`$ac_var' since the previous run:" >&2;} eval $ac_var=\$ac_old_val fi { $as_echo "$as_me:${as_lineno-$LINENO}: former value: \`$ac_old_val'" >&5 $as_echo "$as_me: former value: \`$ac_old_val'" >&2;} { $as_echo "$as_me:${as_lineno-$LINENO}: current value: \`$ac_new_val'" >&5 $as_echo "$as_me: current value: \`$ac_new_val'" >&2;} fi;; esac # Pass precious variables to config.status. if test "$ac_new_set" = set; then case $ac_new_val in *\'*) ac_arg=$ac_var=`$as_echo "$ac_new_val" | sed "s/'/'\\\\\\\\''/g"` ;; *) ac_arg=$ac_var=$ac_new_val ;; esac case " $ac_configure_args " in *" '$ac_arg' "*) ;; # Avoid dups. Use of quotes ensures accuracy. *) as_fn_append ac_configure_args " '$ac_arg'" ;; esac fi done if $ac_cache_corrupted; then { $as_echo "$as_me:${as_lineno-$LINENO}: error: in \`$ac_pwd':" >&5 $as_echo "$as_me: error: in \`$ac_pwd':" >&2;} { $as_echo "$as_me:${as_lineno-$LINENO}: error: changes in the environment can compromise the build" >&5 $as_echo "$as_me: error: changes in the environment can compromise the build" >&2;} as_fn_error $? "run \`make distclean' and/or \`rm $cache_file' and start over" "$LINENO" 5 fi ## -------------------- ## ## Main body of script. ## ## -------------------- ## ac_ext=c ac_cpp='$CPP $CPPFLAGS' ac_compile='$CC -c $CFLAGS $CPPFLAGS conftest.$ac_ext >&5' ac_link='$CC -o conftest$ac_exeext $CFLAGS $CPPFLAGS $LDFLAGS conftest.$ac_ext $LIBS >&5' ac_compiler_gnu=$ac_cv_c_compiler_gnu PACKAGE_URL="https://frrouting.org/" PACKAGE_FULLNAME="FRRouting" CONFIG_ARGS="`echo $ac_configure_args | sed -e \"s% '[A-Z]*FLAGS=[^']\+'%%g\"`" ac_aux_dir= for ac_dir in "$srcdir" "$srcdir/.." "$srcdir/../.."; do if test -f "$ac_dir/install-sh"; then ac_aux_dir=$ac_dir ac_install_sh="$ac_aux_dir/install-sh -c" break elif test -f "$ac_dir/install.sh"; then ac_aux_dir=$ac_dir ac_install_sh="$ac_aux_dir/install.sh -c" break elif test -f "$ac_dir/shtool"; then ac_aux_dir=$ac_dir ac_install_sh="$ac_aux_dir/shtool install -c" break fi done if test -z "$ac_aux_dir"; then as_fn_error $? "cannot find install-sh, install.sh, or shtool in \"$srcdir\" \"$srcdir/..\" \"$srcdir/../..\"" "$LINENO" 5 fi # These three variables are undocumented and unsupported, # and are intended to be withdrawn in a future Autoconf release. # They can cause serious problems if a builder's source tree is in a directory # whose full name contains unusual characters. ac_config_guess="$SHELL $ac_aux_dir/config.guess" # Please don't use this var. ac_config_sub="$SHELL $ac_aux_dir/config.sub" # Please don't use this var. ac_configure="$SHELL $ac_aux_dir/configure" # Please don't use this var. # Make sure we can run config.sub. $SHELL "$ac_aux_dir/config.sub" sun4 >/dev/null 2>&1 || as_fn_error $? "cannot run $SHELL $ac_aux_dir/config.sub" "$LINENO" 5 { $as_echo "$as_me:${as_lineno-$LINENO}: checking build system type" >&5 $as_echo_n "checking build system type... " >&6; } if ${ac_cv_build+:} false; then : $as_echo_n "(cached) " >&6 else ac_build_alias=$build_alias test "x$ac_build_alias" = x && ac_build_alias=`$SHELL "$ac_aux_dir/config.guess"` test "x$ac_build_alias" = x && as_fn_error $? "cannot guess build type; you must specify one" "$LINENO" 5 ac_cv_build=`$SHELL "$ac_aux_dir/config.sub" $ac_build_alias` || as_fn_error $? "$SHELL $ac_aux_dir/config.sub $ac_build_alias failed" "$LINENO" 5 fi { $as_echo "$as_me:${as_lineno-$LINENO}: result: $ac_cv_build" >&5 $as_echo "$ac_cv_build" >&6; } case $ac_cv_build in *-*-*) ;; *) as_fn_error $? "invalid value of canonical build" "$LINENO" 5;; esac build=$ac_cv_build ac_save_IFS=$IFS; IFS='-' set x $ac_cv_build shift build_cpu=$1 build_vendor=$2 shift; shift # Remember, the first character of IFS is used to create $*, # except with old shells: build_os=$* IFS=$ac_save_IFS case $build_os in *\ *) build_os=`echo "$build_os" | sed 's/ /-/g'`;; esac { $as_echo "$as_me:${as_lineno-$LINENO}: checking host system type" >&5 $as_echo_n "checking host system type... " >&6; } if ${ac_cv_host+:} false; then : $as_echo_n "(cached) " >&6 else if test "x$host_alias" = x; then ac_cv_host=$ac_cv_build else ac_cv_host=`$SHELL "$ac_aux_dir/config.sub" $host_alias` || as_fn_error $? "$SHELL $ac_aux_dir/config.sub $host_alias failed" "$LINENO" 5 fi fi { $as_echo "$as_me:${as_lineno-$LINENO}: result: $ac_cv_host" >&5 $as_echo "$ac_cv_host" >&6; } case $ac_cv_host in *-*-*) ;; *) as_fn_error $? "invalid value of canonical host" "$LINENO" 5;; esac host=$ac_cv_host ac_save_IFS=$IFS; IFS='-' set x $ac_cv_host shift host_cpu=$1 host_vendor=$2 shift; shift # Remember, the first character of IFS is used to create $*, # except with old shells: host_os=$* IFS=$ac_save_IFS case $host_os in *\ *) host_os=`echo "$host_os" | sed 's/ /-/g'`;; esac hosttools_clippy="false" build_clippy="true" if test -n "$with_clippy" -a "$with_clippy" != "no" -a "$with_clippy" != "yes"; then if test "$enable_clippy_only" == "yes"; then as_fn_error $? "--enable-clippy-only does not make sense with --with-clippy" "$LINENO" 5 fi CLIPPY="$with_clippy" build_clippy="false" if test ! -x "$with_clippy"; then as_fn_error $? "clippy tool ($with_clippy) is not executable" "$LINENO" 5 fi elif test "$host" != "$build"; then if test "$srcdir" = "."; then as_fn_error $? "cross-compilation is only possible with builddir separate from srcdir or by building clippy separately and using the --with-clippy option. create a separate directory and run as .../path-to-frr/configure." "$LINENO" 5 fi test -d hosttools || mkdir hosttools abssrc="`cd \"${srcdir}\"; pwd`" { $as_echo "$as_me:${as_lineno-$LINENO}: ..." >&5 $as_echo "$as_me: ..." >&6;} { $as_echo "$as_me:${as_lineno-$LINENO}: ... cross-compilation: creating hosttools directory and self-configuring for build platform tools" >&5 $as_echo "$as_me: ... cross-compilation: creating hosttools directory and self-configuring for build platform tools" >&6;} { $as_echo "$as_me:${as_lineno-$LINENO}: ... use HOST_CPPFLAGS / HOST_CFLAGS / HOST_LDFLAGS if neccessary" >&5 $as_echo "$as_me: ... use HOST_CPPFLAGS / HOST_CFLAGS / HOST_LDFLAGS if neccessary" >&6;} { $as_echo "$as_me:${as_lineno-$LINENO}: ..." >&5 $as_echo "$as_me: ..." >&6;} ( for var in $ac_precious_vars; do case "$var" in YACC|YFLAGS) continue;; PYTHON*) retain=true;; *) retain=false; esac eval "hostvar=\"\${HOST_$var}\"" eval "targetvar=\"\${$var}\"" if test -n "$hostvar"; then eval "$var='$hostvar'" $as_echo "$as_me:${as_lineno-$LINENO}: host $var='$hostvar'" >&5 elif $retain; then $as_echo "$as_me:${as_lineno-$LINENO}: host retain $var='$targetvar'" >&5 else eval "unset $var" $as_echo "$as_me:${as_lineno-$LINENO}: host unset $var" >&5 fi done cd hosttools "${abssrc}/configure" "--host=$build" "--build=$build" "--enable-clippy-only" "--disable-nhrpd" "--disable-vtysh" ) || exit 1 { $as_echo "$as_me:${as_lineno-$LINENO}: ..." >&5 $as_echo "$as_me: ..." >&6;} { $as_echo "$as_me:${as_lineno-$LINENO}: ... cross-compilation: finished self-configuring for build platform tools" >&5 $as_echo "$as_me: ... cross-compilation: finished self-configuring for build platform tools" >&6;} { $as_echo "$as_me:${as_lineno-$LINENO}: ..." >&5 $as_echo "$as_me: ..." >&6;} build_clippy="false" hosttools_clippy="true" CLIPPY="hosttools/lib/clippy" else CLIPPY="lib/clippy\$(EXEEXT)" fi if $build_clippy; then BUILD_CLIPPY_TRUE= BUILD_CLIPPY_FALSE='#' else BUILD_CLIPPY_TRUE='#' BUILD_CLIPPY_FALSE= fi if $hosttools_clippy; then HOSTTOOLS_CLIPPY_TRUE= HOSTTOOLS_CLIPPY_FALSE='#' else HOSTTOOLS_CLIPPY_TRUE='#' HOSTTOOLS_CLIPPY_FALSE= fi if test "$enable_clippy_only" = "yes"; then ONLY_CLIPPY_TRUE= ONLY_CLIPPY_FALSE='#' else ONLY_CLIPPY_TRUE='#' ONLY_CLIPPY_FALSE= fi # Disable portability warnings -- our automake code (in particular # common.am) uses some constructs specific to gmake. am__api_version='1.16' # Find a good install program. We prefer a C program (faster), # so one script is as good as another. But avoid the broken or # incompatible versions: # SysV /etc/install, /usr/sbin/install # SunOS /usr/etc/install # IRIX /sbin/install # AIX /bin/install # AmigaOS /C/install, which installs bootblocks on floppy discs # AIX 4 /usr/bin/installbsd, which doesn't work without a -g flag # AFS /usr/afsws/bin/install, which mishandles nonexistent args # SVR4 /usr/ucb/install, which tries to use the nonexistent group "staff" # OS/2's system install, which has a completely different semantic # ./install, which can be erroneously created by make from ./install.sh. # Reject install programs that cannot install multiple files. { $as_echo "$as_me:${as_lineno-$LINENO}: checking for a BSD-compatible install" >&5 $as_echo_n "checking for a BSD-compatible install... " >&6; } if test -z "$INSTALL"; then if ${ac_cv_path_install+:} false; then : $as_echo_n "(cached) " >&6 else as_save_IFS=$IFS; IFS=$PATH_SEPARATOR for as_dir in $PATH do IFS=$as_save_IFS test -z "$as_dir" && as_dir=. # Account for people who put trailing slashes in PATH elements. case $as_dir/ in #(( ./ | .// | /[cC]/* | \ /etc/* | /usr/sbin/* | /usr/etc/* | /sbin/* | /usr/afsws/bin/* | \ ?:[\\/]os2[\\/]install[\\/]* | ?:[\\/]OS2[\\/]INSTALL[\\/]* | \ /usr/ucb/* ) ;; *) # OSF1 and SCO ODT 3.0 have their own names for install. # Don't use installbsd from OSF since it installs stuff as root # by default. for ac_prog in ginstall scoinst install; do for ac_exec_ext in '' $ac_executable_extensions; do if as_fn_executable_p "$as_dir/$ac_prog$ac_exec_ext"; then if test $ac_prog = install && grep dspmsg "$as_dir/$ac_prog$ac_exec_ext" >/dev/null 2>&1; then # AIX install. It has an incompatible calling convention. : elif test $ac_prog = install && grep pwplus "$as_dir/$ac_prog$ac_exec_ext" >/dev/null 2>&1; then # program-specific install script used by HP pwplus--don't use. : else rm -rf conftest.one conftest.two conftest.dir echo one > conftest.one echo two > conftest.two mkdir conftest.dir if "$as_dir/$ac_prog$ac_exec_ext" -c conftest.one conftest.two "`pwd`/conftest.dir" && test -s conftest.one && test -s conftest.two && test -s conftest.dir/conftest.one && test -s conftest.dir/conftest.two then ac_cv_path_install="$as_dir/$ac_prog$ac_exec_ext -c" break 3 fi fi fi done done ;; esac done IFS=$as_save_IFS rm -rf conftest.one conftest.two conftest.dir fi if test "${ac_cv_path_install+set}" = set; then INSTALL=$ac_cv_path_install else # As a last resort, use the slow shell script. Don't cache a # value for INSTALL within a source directory, because that will # break other packages using the cache if that directory is # removed, or if the value is a relative name. INSTALL=$ac_install_sh fi fi { $as_echo "$as_me:${as_lineno-$LINENO}: result: $INSTALL" >&5 $as_echo "$INSTALL" >&6; } # Use test -z because SunOS4 sh mishandles braces in ${var-val}. # It thinks the first close brace ends the variable substitution. test -z "$INSTALL_PROGRAM" && INSTALL_PROGRAM='${INSTALL}' test -z "$INSTALL_SCRIPT" && INSTALL_SCRIPT='${INSTALL}' test -z "$INSTALL_DATA" && INSTALL_DATA='${INSTALL} -m 644' { $as_echo "$as_me:${as_lineno-$LINENO}: checking whether build environment is sane" >&5 $as_echo_n "checking whether build environment is sane... " >&6; } # Reject unsafe characters in $srcdir or the absolute working directory # name. Accept space and tab only in the latter. am_lf=' ' case `pwd` in *[\\\"\#\$\&\'\`$am_lf]*) as_fn_error $? "unsafe absolute working directory name" "$LINENO" 5;; esac case $srcdir in *[\\\"\#\$\&\'\`$am_lf\ \ ]*) as_fn_error $? "unsafe srcdir value: '$srcdir'" "$LINENO" 5;; esac # Do 'set' in a subshell so we don't clobber the current shell's # arguments. Must try -L first in case configure is actually a # symlink; some systems play weird games with the mod time of symlinks # (eg FreeBSD returns the mod time of the symlink's containing # directory). if ( am_has_slept=no for am_try in 1 2; do echo "timestamp, slept: $am_has_slept" > conftest.file set X `ls -Lt "$srcdir/configure" conftest.file 2> /dev/null` if test "$*" = "X"; then # -L didn't work. set X `ls -t "$srcdir/configure" conftest.file` fi if test "$*" != "X $srcdir/configure conftest.file" \ && test "$*" != "X conftest.file $srcdir/configure"; then # If neither matched, then we have a broken ls. This can happen # if, for instance, CONFIG_SHELL is bash and it inherits a # broken ls alias from the environment. This has actually # happened. Such a system could not be considered "sane". as_fn_error $? "ls -t appears to fail. Make sure there is not a broken alias in your environment" "$LINENO" 5 fi if test "$2" = conftest.file || test $am_try -eq 2; then break fi # Just in case. sleep 1 am_has_slept=yes done test "$2" = conftest.file ) then # Ok. : else as_fn_error $? "newly created file is older than distributed files! Check your system clock" "$LINENO" 5 fi { $as_echo "$as_me:${as_lineno-$LINENO}: result: yes" >&5 $as_echo "yes" >&6; } # If we didn't sleep, we still need to ensure time stamps of config.status and # generated files are strictly newer. am_sleep_pid= if grep 'slept: no' conftest.file >/dev/null 2>&1; then ( sleep 1 ) & am_sleep_pid=$! fi rm -f conftest.file test "$program_prefix" != NONE && program_transform_name="s&^&$program_prefix&;$program_transform_name" # Use a double $ so make ignores it. test "$program_suffix" != NONE && program_transform_name="s&\$&$program_suffix&;$program_transform_name" # Double any \ or $. # By default was `s,x,x', remove it if useless. ac_script='s/[\\$]/&&/g;s/;s,x,x,$//' program_transform_name=`$as_echo "$program_transform_name" | sed "$ac_script"` # Expand $ac_aux_dir to an absolute path. am_aux_dir=`cd "$ac_aux_dir" && pwd` if test x"${MISSING+set}" != xset; then case $am_aux_dir in *\ * | *\ *) MISSING="\${SHELL} \"$am_aux_dir/missing\"" ;; *) MISSING="\${SHELL} $am_aux_dir/missing" ;; esac fi # Use eval to expand $SHELL if eval "$MISSING --is-lightweight"; then am_missing_run="$MISSING " else am_missing_run= { $as_echo "$as_me:${as_lineno-$LINENO}: WARNING: 'missing' script is too old or missing" >&5 $as_echo "$as_me: WARNING: 'missing' script is too old or missing" >&2;} fi if test x"${install_sh+set}" != xset; then case $am_aux_dir in *\ * | *\ *) install_sh="\${SHELL} '$am_aux_dir/install-sh'" ;; *) install_sh="\${SHELL} $am_aux_dir/install-sh" esac fi # Installed binaries are usually stripped using 'strip' when the user # run "make install-strip". However 'strip' might not be the right # tool to use in cross-compilation environments, therefore Automake # will honor the 'STRIP' environment variable to overrule this program. if test "$cross_compiling" != no; then if test -n "$ac_tool_prefix"; then # Extract the first word of "${ac_tool_prefix}strip", so it can be a program name with args. set dummy ${ac_tool_prefix}strip; ac_word=$2 { $as_echo "$as_me:${as_lineno-$LINENO}: checking for $ac_word" >&5 $as_echo_n "checking for $ac_word... " >&6; } if ${ac_cv_prog_STRIP+:} false; then : $as_echo_n "(cached) " >&6 else if test -n "$STRIP"; then ac_cv_prog_STRIP="$STRIP" # Let the user override the test. else as_save_IFS=$IFS; IFS=$PATH_SEPARATOR for as_dir in $PATH do IFS=$as_save_IFS test -z "$as_dir" && as_dir=. for ac_exec_ext in '' $ac_executable_extensions; do if as_fn_executable_p "$as_dir/$ac_word$ac_exec_ext"; then ac_cv_prog_STRIP="${ac_tool_prefix}strip" $as_echo "$as_me:${as_lineno-$LINENO}: found $as_dir/$ac_word$ac_exec_ext" >&5 break 2 fi done done IFS=$as_save_IFS fi fi STRIP=$ac_cv_prog_STRIP if test -n "$STRIP"; then { $as_echo "$as_me:${as_lineno-$LINENO}: result: $STRIP" >&5 $as_echo "$STRIP" >&6; } else { $as_echo "$as_me:${as_lineno-$LINENO}: result: no" >&5 $as_echo "no" >&6; } fi fi if test -z "$ac_cv_prog_STRIP"; then ac_ct_STRIP=$STRIP # Extract the first word of "strip", so it can be a program name with args. set dummy strip; ac_word=$2 { $as_echo "$as_me:${as_lineno-$LINENO}: checking for $ac_word" >&5 $as_echo_n "checking for $ac_word... " >&6; } if ${ac_cv_prog_ac_ct_STRIP+:} false; then : $as_echo_n "(cached) " >&6 else if test -n "$ac_ct_STRIP"; then ac_cv_prog_ac_ct_STRIP="$ac_ct_STRIP" # Let the user override the test. else as_save_IFS=$IFS; IFS=$PATH_SEPARATOR for as_dir in $PATH do IFS=$as_save_IFS test -z "$as_dir" && as_dir=. for ac_exec_ext in '' $ac_executable_extensions; do if as_fn_executable_p "$as_dir/$ac_word$ac_exec_ext"; then ac_cv_prog_ac_ct_STRIP="strip" $as_echo "$as_me:${as_lineno-$LINENO}: found $as_dir/$ac_word$ac_exec_ext" >&5 break 2 fi done done IFS=$as_save_IFS fi fi ac_ct_STRIP=$ac_cv_prog_ac_ct_STRIP if test -n "$ac_ct_STRIP"; then { $as_echo "$as_me:${as_lineno-$LINENO}: result: $ac_ct_STRIP" >&5 $as_echo "$ac_ct_STRIP" >&6; } else { $as_echo "$as_me:${as_lineno-$LINENO}: result: no" >&5 $as_echo "no" >&6; } fi if test "x$ac_ct_STRIP" = x; then STRIP=":" else case $cross_compiling:$ac_tool_warned in yes:) { $as_echo "$as_me:${as_lineno-$LINENO}: WARNING: using cross tools not prefixed with host triplet" >&5 $as_echo "$as_me: WARNING: using cross tools not prefixed with host triplet" >&2;} ac_tool_warned=yes ;; esac STRIP=$ac_ct_STRIP fi else STRIP="$ac_cv_prog_STRIP" fi fi INSTALL_STRIP_PROGRAM="\$(install_sh) -c -s" { $as_echo "$as_me:${as_lineno-$LINENO}: checking for a thread-safe mkdir -p" >&5 $as_echo_n "checking for a thread-safe mkdir -p... " >&6; } if test -z "$MKDIR_P"; then if ${ac_cv_path_mkdir+:} false; then : $as_echo_n "(cached) " >&6 else as_save_IFS=$IFS; IFS=$PATH_SEPARATOR for as_dir in $PATH$PATH_SEPARATOR/opt/sfw/bin do IFS=$as_save_IFS test -z "$as_dir" && as_dir=. for ac_prog in mkdir gmkdir; do for ac_exec_ext in '' $ac_executable_extensions; do as_fn_executable_p "$as_dir/$ac_prog$ac_exec_ext" || continue case `"$as_dir/$ac_prog$ac_exec_ext" --version 2>&1` in #( 'mkdir (GNU coreutils) '* | \ 'mkdir (coreutils) '* | \ 'mkdir (fileutils) '4.1*) ac_cv_path_mkdir=$as_dir/$ac_prog$ac_exec_ext break 3;; esac done done done IFS=$as_save_IFS fi test -d ./--version && rmdir ./--version if test "${ac_cv_path_mkdir+set}" = set; then MKDIR_P="$ac_cv_path_mkdir -p" else # As a last resort, use the slow shell script. Don't cache a # value for MKDIR_P within a source directory, because that will # break other packages using the cache if that directory is # removed, or if the value is a relative name. MKDIR_P="$ac_install_sh -d" fi fi { $as_echo "$as_me:${as_lineno-$LINENO}: result: $MKDIR_P" >&5 $as_echo "$MKDIR_P" >&6; } for ac_prog in gawk mawk nawk awk do # Extract the first word of "$ac_prog", so it can be a program name with args. set dummy $ac_prog; ac_word=$2 { $as_echo "$as_me:${as_lineno-$LINENO}: checking for $ac_word" >&5 $as_echo_n "checking for $ac_word... " >&6; } if ${ac_cv_prog_AWK+:} false; then : $as_echo_n "(cached) " >&6 else if test -n "$AWK"; then ac_cv_prog_AWK="$AWK" # Let the user override the test. else as_save_IFS=$IFS; IFS=$PATH_SEPARATOR for as_dir in $PATH do IFS=$as_save_IFS test -z "$as_dir" && as_dir=. for ac_exec_ext in '' $ac_executable_extensions; do if as_fn_executable_p "$as_dir/$ac_word$ac_exec_ext"; then ac_cv_prog_AWK="$ac_prog" $as_echo "$as_me:${as_lineno-$LINENO}: found $as_dir/$ac_word$ac_exec_ext" >&5 break 2 fi done done IFS=$as_save_IFS fi fi AWK=$ac_cv_prog_AWK if test -n "$AWK"; then { $as_echo "$as_me:${as_lineno-$LINENO}: result: $AWK" >&5 $as_echo "$AWK" >&6; } else { $as_echo "$as_me:${as_lineno-$LINENO}: result: no" >&5 $as_echo "no" >&6; } fi test -n "$AWK" && break done { $as_echo "$as_me:${as_lineno-$LINENO}: checking whether ${MAKE-make} sets \$(MAKE)" >&5 $as_echo_n "checking whether ${MAKE-make} sets \$(MAKE)... " >&6; } set x ${MAKE-make} ac_make=`$as_echo "$2" | sed 's/+/p/g; s/[^a-zA-Z0-9_]/_/g'` if eval \${ac_cv_prog_make_${ac_make}_set+:} false; then : $as_echo_n "(cached) " >&6 else cat >conftest.make <<\_ACEOF SHELL = /bin/sh all: @echo '@@@%%%=$(MAKE)=@@@%%%' _ACEOF # GNU make sometimes prints "make[1]: Entering ...", which would confuse us. case `${MAKE-make} -f conftest.make 2>/dev/null` in *@@@%%%=?*=@@@%%%*) eval ac_cv_prog_make_${ac_make}_set=yes;; *) eval ac_cv_prog_make_${ac_make}_set=no;; esac rm -f conftest.make fi if eval test \$ac_cv_prog_make_${ac_make}_set = yes; then { $as_echo "$as_me:${as_lineno-$LINENO}: result: yes" >&5 $as_echo "yes" >&6; } SET_MAKE= else { $as_echo "$as_me:${as_lineno-$LINENO}: result: no" >&5 $as_echo "no" >&6; } SET_MAKE="MAKE=${MAKE-make}" fi rm -rf .tst 2>/dev/null mkdir .tst 2>/dev/null if test -d .tst; then am__leading_dot=. else am__leading_dot=_ fi rmdir .tst 2>/dev/null # Check whether --enable-silent-rules was given. if test "${enable_silent_rules+set}" = set; then : enableval=$enable_silent_rules; fi case $enable_silent_rules in # ((( yes) AM_DEFAULT_VERBOSITY=0;; no) AM_DEFAULT_VERBOSITY=1;; *) AM_DEFAULT_VERBOSITY=1;; esac am_make=${MAKE-make} { $as_echo "$as_me:${as_lineno-$LINENO}: checking whether $am_make supports nested variables" >&5 $as_echo_n "checking whether $am_make supports nested variables... " >&6; } if ${am_cv_make_support_nested_variables+:} false; then : $as_echo_n "(cached) " >&6 else if $as_echo 'TRUE=$(BAR$(V)) BAR0=false BAR1=true V=1 am__doit: @$(TRUE) .PHONY: am__doit' | $am_make -f - >/dev/null 2>&1; then am_cv_make_support_nested_variables=yes else am_cv_make_support_nested_variables=no fi fi { $as_echo "$as_me:${as_lineno-$LINENO}: result: $am_cv_make_support_nested_variables" >&5 $as_echo "$am_cv_make_support_nested_variables" >&6; } if test $am_cv_make_support_nested_variables = yes; then AM_V='$(V)' AM_DEFAULT_V='$(AM_DEFAULT_VERBOSITY)' else AM_V=$AM_DEFAULT_VERBOSITY AM_DEFAULT_V=$AM_DEFAULT_VERBOSITY fi AM_BACKSLASH='\' if test "`cd $srcdir && pwd`" != "`pwd`"; then # Use -I$(srcdir) only when $(srcdir) != ., so that make's output # is not polluted with repeated "-I." am__isrc=' -I$(srcdir)' # test to see if srcdir already configured if test -f $srcdir/config.status; then as_fn_error $? "source directory already configured; run \"make distclean\" there first" "$LINENO" 5 fi fi # test whether we have cygpath if test -z "$CYGPATH_W"; then if (cygpath --version) >/dev/null 2>/dev/null; then CYGPATH_W='cygpath -w' else CYGPATH_W=echo fi fi # Define the identity of the package. PACKAGE='frr' VERSION='7.2.1' cat >>confdefs.h <<_ACEOF #define PACKAGE "$PACKAGE" _ACEOF cat >>confdefs.h <<_ACEOF #define VERSION "$VERSION" _ACEOF # Some tools Automake needs. ACLOCAL=${ACLOCAL-"${am_missing_run}aclocal-${am__api_version}"} AUTOCONF=${AUTOCONF-"${am_missing_run}autoconf"} AUTOMAKE=${AUTOMAKE-"${am_missing_run}automake-${am__api_version}"} AUTOHEADER=${AUTOHEADER-"${am_missing_run}autoheader"} MAKEINFO=${MAKEINFO-"${am_missing_run}makeinfo"} # For better backward compatibility. To be removed once Automake 1.9.x # dies out for good. For more background, see: # # mkdir_p='$(MKDIR_P)' # We need awk for the "check" target (and possibly the TAP driver). The # system "awk" is bad on some platforms. # Always define AMTAR for backward compatibility. Yes, it's still used # in the wild :-( We should find a proper way to deprecate it ... AMTAR='$${TAR-tar}' # We'll loop over all known methods to create a tar archive until one works. _am_tools='gnutar pax cpio none' am__tar='$${TAR-tar} chof - "$$tardir"' am__untar='$${TAR-tar} xf -' # POSIX will say in a future version that running "rm -f" with no argument # is OK; and we want to be able to make that assumption in our Makefile # recipes. So use an aggressive probe to check that the usage we want is # actually supported "in the wild" to an acceptable degree. # See automake bug#10828. # To make any issue more visible, cause the running configure to be aborted # by default if the 'rm' program in use doesn't match our expectations; the # user can still override this though. if rm -f && rm -fr && rm -rf; then : OK; else cat >&2 <<'END' Oops! Your 'rm' program seems unable to run without file operands specified on the command line, even when the '-f' option is present. This is contrary to the behaviour of most rm programs out there, and not conforming with the upcoming POSIX standard: Please tell bug-automake@gnu.org about your system, including the value of your $PATH and any error possibly output before this message. This can help us improve future automake versions. END if test x"$ACCEPT_INFERIOR_RM_PROGRAM" = x"yes"; then echo 'Configuration will proceed anyway, since you have set the' >&2 echo 'ACCEPT_INFERIOR_RM_PROGRAM variable to "yes"' >&2 echo >&2 else cat >&2 <<'END' Aborting the configuration process, to ensure you take notice of the issue. You can download and install GNU coreutils to get an 'rm' implementation that behaves properly: . If you want to complete the configuration process using your problematic 'rm' anyway, export the environment variable ACCEPT_INFERIOR_RM_PROGRAM to "yes", and re-run configure. END as_fn_error $? "Your 'rm' program is bad, sorry." "$LINENO" 5 fi fi # Check whether --enable-silent-rules was given. if test "${enable_silent_rules+set}" = set; then : enableval=$enable_silent_rules; fi case $enable_silent_rules in # ((( yes) AM_DEFAULT_VERBOSITY=0;; no) AM_DEFAULT_VERBOSITY=1;; *) AM_DEFAULT_VERBOSITY=0;; esac am_make=${MAKE-make} { $as_echo "$as_me:${as_lineno-$LINENO}: checking whether $am_make supports nested variables" >&5 $as_echo_n "checking whether $am_make supports nested variables... " >&6; } if ${am_cv_make_support_nested_variables+:} false; then : $as_echo_n "(cached) " >&6 else if $as_echo 'TRUE=$(BAR$(V)) BAR0=false BAR1=true V=1 am__doit: @$(TRUE) .PHONY: am__doit' | $am_make -f - >/dev/null 2>&1; then am_cv_make_support_nested_variables=yes else am_cv_make_support_nested_variables=no fi fi { $as_echo "$as_me:${as_lineno-$LINENO}: result: $am_cv_make_support_nested_variables" >&5 $as_echo "$am_cv_make_support_nested_variables" >&6; } if test $am_cv_make_support_nested_variables = yes; then AM_V='$(V)' AM_DEFAULT_V='$(AM_DEFAULT_VERBOSITY)' else AM_V=$AM_DEFAULT_VERBOSITY AM_DEFAULT_V=$AM_DEFAULT_VERBOSITY fi AM_BACKSLASH='\' ac_config_headers="$ac_config_headers config.h" # Extract the first word of "perl", so it can be a program name with args. set dummy perl; ac_word=$2 { $as_echo "$as_me:${as_lineno-$LINENO}: checking for $ac_word" >&5 $as_echo_n "checking for $ac_word... " >&6; } if ${ac_cv_path_PERL+:} false; then : $as_echo_n "(cached) " >&6 else case $PERL in [\\/]* | ?:[\\/]*) ac_cv_path_PERL="$PERL" # Let the user override the test with a path. ;; *) as_save_IFS=$IFS; IFS=$PATH_SEPARATOR for as_dir in $PATH do IFS=$as_save_IFS test -z "$as_dir" && as_dir=. for ac_exec_ext in '' $ac_executable_extensions; do if as_fn_executable_p "$as_dir/$ac_word$ac_exec_ext"; then ac_cv_path_PERL="$as_dir/$ac_word$ac_exec_ext" $as_echo "$as_me:${as_lineno-$LINENO}: found $as_dir/$ac_word$ac_exec_ext" >&5 break 2 fi done done IFS=$as_save_IFS ;; esac fi PERL=$ac_cv_path_PERL if test -n "$PERL"; then { $as_echo "$as_me:${as_lineno-$LINENO}: result: $PERL" >&5 $as_echo "$PERL" >&6; } else { $as_echo "$as_me:${as_lineno-$LINENO}: result: no" >&5 $as_echo "no" >&6; } fi if test "x$ac_cv_env_PKG_CONFIG_set" != "xset"; then if test -n "$ac_tool_prefix"; then # Extract the first word of "${ac_tool_prefix}pkg-config", so it can be a program name with args. set dummy ${ac_tool_prefix}pkg-config; ac_word=$2 { $as_echo "$as_me:${as_lineno-$LINENO}: checking for $ac_word" >&5 $as_echo_n "checking for $ac_word... " >&6; } if ${ac_cv_path_PKG_CONFIG+:} false; then : $as_echo_n "(cached) " >&6 else case $PKG_CONFIG in [\\/]* | ?:[\\/]*) ac_cv_path_PKG_CONFIG="$PKG_CONFIG" # Let the user override the test with a path. ;; *) as_save_IFS=$IFS; IFS=$PATH_SEPARATOR for as_dir in $PATH do IFS=$as_save_IFS test -z "$as_dir" && as_dir=. for ac_exec_ext in '' $ac_executable_extensions; do if as_fn_executable_p "$as_dir/$ac_word$ac_exec_ext"; then ac_cv_path_PKG_CONFIG="$as_dir/$ac_word$ac_exec_ext" $as_echo "$as_me:${as_lineno-$LINENO}: found $as_dir/$ac_word$ac_exec_ext" >&5 break 2 fi done done IFS=$as_save_IFS ;; esac fi PKG_CONFIG=$ac_cv_path_PKG_CONFIG if test -n "$PKG_CONFIG"; then { $as_echo "$as_me:${as_lineno-$LINENO}: result: $PKG_CONFIG" >&5 $as_echo "$PKG_CONFIG" >&6; } else { $as_echo "$as_me:${as_lineno-$LINENO}: result: no" >&5 $as_echo "no" >&6; } fi fi if test -z "$ac_cv_path_PKG_CONFIG"; then ac_pt_PKG_CONFIG=$PKG_CONFIG # Extract the first word of "pkg-config", so it can be a program name with args. set dummy pkg-config; ac_word=$2 { $as_echo "$as_me:${as_lineno-$LINENO}: checking for $ac_word" >&5 $as_echo_n "checking for $ac_word... " >&6; } if ${ac_cv_path_ac_pt_PKG_CONFIG+:} false; then : $as_echo_n "(cached) " >&6 else case $ac_pt_PKG_CONFIG in [\\/]* | ?:[\\/]*) ac_cv_path_ac_pt_PKG_CONFIG="$ac_pt_PKG_CONFIG" # Let the user override the test with a path. ;; *) as_save_IFS=$IFS; IFS=$PATH_SEPARATOR for as_dir in $PATH do IFS=$as_save_IFS test -z "$as_dir" && as_dir=. for ac_exec_ext in '' $ac_executable_extensions; do if as_fn_executable_p "$as_dir/$ac_word$ac_exec_ext"; then ac_cv_path_ac_pt_PKG_CONFIG="$as_dir/$ac_word$ac_exec_ext" $as_echo "$as_me:${as_lineno-$LINENO}: found $as_dir/$ac_word$ac_exec_ext" >&5 break 2 fi done done IFS=$as_save_IFS ;; esac fi ac_pt_PKG_CONFIG=$ac_cv_path_ac_pt_PKG_CONFIG if test -n "$ac_pt_PKG_CONFIG"; then { $as_echo "$as_me:${as_lineno-$LINENO}: result: $ac_pt_PKG_CONFIG" >&5 $as_echo "$ac_pt_PKG_CONFIG" >&6; } else { $as_echo "$as_me:${as_lineno-$LINENO}: result: no" >&5 $as_echo "no" >&6; } fi if test "x$ac_pt_PKG_CONFIG" = x; then PKG_CONFIG="" else case $cross_compiling:$ac_tool_warned in yes:) { $as_echo "$as_me:${as_lineno-$LINENO}: WARNING: using cross tools not prefixed with host triplet" >&5 $as_echo "$as_me: WARNING: using cross tools not prefixed with host triplet" >&2;} ac_tool_warned=yes ;; esac PKG_CONFIG=$ac_pt_PKG_CONFIG fi else PKG_CONFIG="$ac_cv_path_PKG_CONFIG" fi fi if test -n "$PKG_CONFIG"; then _pkg_min_version=0.9.0 { $as_echo "$as_me:${as_lineno-$LINENO}: checking pkg-config is at least version $_pkg_min_version" >&5 $as_echo_n "checking pkg-config is at least version $_pkg_min_version... " >&6; } if $PKG_CONFIG --atleast-pkgconfig-version $_pkg_min_version; then { $as_echo "$as_me:${as_lineno-$LINENO}: result: yes" >&5 $as_echo "yes" >&6; } else { $as_echo "$as_me:${as_lineno-$LINENO}: result: no" >&5 $as_echo "no" >&6; } PKG_CONFIG="" fi fi exampledir=${sysconfdir} # Check whether --enable-exampledir was given. if test "${enable_exampledir+set}" = set; then : enableval=$enable_exampledir; exampledir="$enableval" fi pkgsrcrcdir="" # Check whether --enable-pkgsrcrcdir was given. if test "${enable_pkgsrcrcdir+set}" = set; then : enableval=$enable_pkgsrcrcdir; pkgsrcrcdir="$enableval" fi if test "x$pkgsrcrcdir" != "x"; then PKGSRC_TRUE= PKGSRC_FALSE='#' else PKGSRC_TRUE='#' PKGSRC_FALSE= fi # Check whether --with-moduledir was given. if test "${with_moduledir+set}" = set; then : withval=$with_moduledir; moduledir="$withval" else moduledir="\${libdir}/frr/modules" fi moduledir=$moduledir # Check whether --with-yangmodelsdir was given. if test "${with_yangmodelsdir+set}" = set; then : withval=$with_yangmodelsdir; yangmodelsdir="$withval" else yangmodelsdir="\${datarootdir}/yang" fi # Check whether --enable-tcmalloc was given. if test "${enable_tcmalloc+set}" = set; then : enableval=$enable_tcmalloc; case "${enableval}" in yes) tcmalloc_enabled=true LIBS="$LIBS -ltcmalloc_minimal" ;; no) tcmalloc_enabled=false ;; *) as_fn_error $? "bad value ${enableval} for --enable-tcmalloc" "$LINENO" 5 ;; esac else tcmalloc_enabled=false fi CFLAGS="${CFLAGS:-}" orig_cflags="$CFLAGS" orig_cxxflags="$CXXFLAGS" ac_ext=c ac_cpp='$CPP $CPPFLAGS' ac_compile='$CC -c $CFLAGS $CPPFLAGS conftest.$ac_ext >&5' ac_link='$CC -o conftest$ac_exeext $CFLAGS $CPPFLAGS $LDFLAGS conftest.$ac_ext $LIBS >&5' ac_compiler_gnu=$ac_cv_c_compiler_gnu ac_ext=c ac_cpp='$CPP $CPPFLAGS' ac_compile='$CC -c $CFLAGS $CPPFLAGS conftest.$ac_ext >&5' ac_link='$CC -o conftest$ac_exeext $CFLAGS $CPPFLAGS $LDFLAGS conftest.$ac_ext $LIBS >&5' ac_compiler_gnu=$ac_cv_c_compiler_gnu if test -n "$ac_tool_prefix"; then # Extract the first word of "${ac_tool_prefix}gcc", so it can be a program name with args. set dummy ${ac_tool_prefix}gcc; ac_word=$2 { $as_echo "$as_me:${as_lineno-$LINENO}: checking for $ac_word" >&5 $as_echo_n "checking for $ac_word... " >&6; } if ${ac_cv_prog_CC+:} false; then : $as_echo_n "(cached) " >&6 else if test -n "$CC"; then ac_cv_prog_CC="$CC" # Let the user override the test. else as_save_IFS=$IFS; IFS=$PATH_SEPARATOR for as_dir in $PATH do IFS=$as_save_IFS test -z "$as_dir" && as_dir=. for ac_exec_ext in '' $ac_executable_extensions; do if as_fn_executable_p "$as_dir/$ac_word$ac_exec_ext"; then ac_cv_prog_CC="${ac_tool_prefix}gcc" $as_echo "$as_me:${as_lineno-$LINENO}: found $as_dir/$ac_word$ac_exec_ext" >&5 break 2 fi done done IFS=$as_save_IFS fi fi CC=$ac_cv_prog_CC if test -n "$CC"; then { $as_echo "$as_me:${as_lineno-$LINENO}: result: $CC" >&5 $as_echo "$CC" >&6; } else { $as_echo "$as_me:${as_lineno-$LINENO}: result: no" >&5 $as_echo "no" >&6; } fi fi if test -z "$ac_cv_prog_CC"; then ac_ct_CC=$CC # Extract the first word of "gcc", so it can be a program name with args. set dummy gcc; ac_word=$2 { $as_echo "$as_me:${as_lineno-$LINENO}: checking for $ac_word" >&5 $as_echo_n "checking for $ac_word... " >&6; } if ${ac_cv_prog_ac_ct_CC+:} false; then : $as_echo_n "(cached) " >&6 else if test -n "$ac_ct_CC"; then ac_cv_prog_ac_ct_CC="$ac_ct_CC" # Let the user override the test. else as_save_IFS=$IFS; IFS=$PATH_SEPARATOR for as_dir in $PATH do IFS=$as_save_IFS test -z "$as_dir" && as_dir=. for ac_exec_ext in '' $ac_executable_extensions; do if as_fn_executable_p "$as_dir/$ac_word$ac_exec_ext"; then ac_cv_prog_ac_ct_CC="gcc" $as_echo "$as_me:${as_lineno-$LINENO}: found $as_dir/$ac_word$ac_exec_ext" >&5 break 2 fi done done IFS=$as_save_IFS fi fi ac_ct_CC=$ac_cv_prog_ac_ct_CC if test -n "$ac_ct_CC"; then { $as_echo "$as_me:${as_lineno-$LINENO}: result: $ac_ct_CC" >&5 $as_echo "$ac_ct_CC" >&6; } else { $as_echo "$as_me:${as_lineno-$LINENO}: result: no" >&5 $as_echo "no" >&6; } fi if test "x$ac_ct_CC" = x; then CC="" else case $cross_compiling:$ac_tool_warned in yes:) { $as_echo "$as_me:${as_lineno-$LINENO}: WARNING: using cross tools not prefixed with host triplet" >&5 $as_echo "$as_me: WARNING: using cross tools not prefixed with host triplet" >&2;} ac_tool_warned=yes ;; esac CC=$ac_ct_CC fi else CC="$ac_cv_prog_CC" fi if test -z "$CC"; then if test -n "$ac_tool_prefix"; then # Extract the first word of "${ac_tool_prefix}cc", so it can be a program name with args. set dummy ${ac_tool_prefix}cc; ac_word=$2 { $as_echo "$as_me:${as_lineno-$LINENO}: checking for $ac_word" >&5 $as_echo_n "checking for $ac_word... " >&6; } if ${ac_cv_prog_CC+:} false; then : $as_echo_n "(cached) " >&6 else if test -n "$CC"; then ac_cv_prog_CC="$CC" # Let the user override the test. else as_save_IFS=$IFS; IFS=$PATH_SEPARATOR for as_dir in $PATH do IFS=$as_save_IFS test -z "$as_dir" && as_dir=. for ac_exec_ext in '' $ac_executable_extensions; do if as_fn_executable_p "$as_dir/$ac_word$ac_exec_ext"; then ac_cv_prog_CC="${ac_tool_prefix}cc" $as_echo "$as_me:${as_lineno-$LINENO}: found $as_dir/$ac_word$ac_exec_ext" >&5 break 2 fi done done IFS=$as_save_IFS fi fi CC=$ac_cv_prog_CC if test -n "$CC"; then { $as_echo "$as_me:${as_lineno-$LINENO}: result: $CC" >&5 $as_echo "$CC" >&6; } else { $as_echo "$as_me:${as_lineno-$LINENO}: result: no" >&5 $as_echo "no" >&6; } fi fi fi if test -z "$CC"; then # Extract the first word of "cc", so it can be a program name with args. set dummy cc; ac_word=$2 { $as_echo "$as_me:${as_lineno-$LINENO}: checking for $ac_word" >&5 $as_echo_n "checking for $ac_word... " >&6; } if ${ac_cv_prog_CC+:} false; then : $as_echo_n "(cached) " >&6 else if test -n "$CC"; then ac_cv_prog_CC="$CC" # Let the user override the test. else ac_prog_rejected=no as_save_IFS=$IFS; IFS=$PATH_SEPARATOR for as_dir in $PATH do IFS=$as_save_IFS test -z "$as_dir" && as_dir=. for ac_exec_ext in '' $ac_executable_extensions; do if as_fn_executable_p "$as_dir/$ac_word$ac_exec_ext"; then if test "$as_dir/$ac_word$ac_exec_ext" = "/usr/ucb/cc"; then ac_prog_rejected=yes continue fi ac_cv_prog_CC="cc" $as_echo "$as_me:${as_lineno-$LINENO}: found $as_dir/$ac_word$ac_exec_ext" >&5 break 2 fi done done IFS=$as_save_IFS if test $ac_prog_rejected = yes; then # We found a bogon in the path, so make sure we never use it. set dummy $ac_cv_prog_CC shift if test $# != 0; then # We chose a different compiler from the bogus one. # However, it has the same basename, so the bogon will be chosen # first if we set CC to just the basename; use the full file name. shift ac_cv_prog_CC="$as_dir/$ac_word${1+' '}$@" fi fi fi fi CC=$ac_cv_prog_CC if test -n "$CC"; then { $as_echo "$as_me:${as_lineno-$LINENO}: result: $CC" >&5 $as_echo "$CC" >&6; } else { $as_echo "$as_me:${as_lineno-$LINENO}: result: no" >&5 $as_echo "no" >&6; } fi fi if test -z "$CC"; then if test -n "$ac_tool_prefix"; then for ac_prog in cl.exe do # Extract the first word of "$ac_tool_prefix$ac_prog", so it can be a program name with args. set dummy $ac_tool_prefix$ac_prog; ac_word=$2 { $as_echo "$as_me:${as_lineno-$LINENO}: checking for $ac_word" >&5 $as_echo_n "checking for $ac_word... " >&6; } if ${ac_cv_prog_CC+:} false; then : $as_echo_n "(cached) " >&6 else if test -n "$CC"; then ac_cv_prog_CC="$CC" # Let the user override the test. else as_save_IFS=$IFS; IFS=$PATH_SEPARATOR for as_dir in $PATH do IFS=$as_save_IFS test -z "$as_dir" && as_dir=. for ac_exec_ext in '' $ac_executable_extensions; do if as_fn_executable_p "$as_dir/$ac_word$ac_exec_ext"; then ac_cv_prog_CC="$ac_tool_prefix$ac_prog" $as_echo "$as_me:${as_lineno-$LINENO}: found $as_dir/$ac_word$ac_exec_ext" >&5 break 2 fi done done IFS=$as_save_IFS fi fi CC=$ac_cv_prog_CC if test -n "$CC"; then { $as_echo "$as_me:${as_lineno-$LINENO}: result: $CC" >&5 $as_echo "$CC" >&6; } else { $as_echo "$as_me:${as_lineno-$LINENO}: result: no" >&5 $as_echo "no" >&6; } fi test -n "$CC" && break done fi if test -z "$CC"; then ac_ct_CC=$CC for ac_prog in cl.exe do # Extract the first word of "$ac_prog", so it can be a program name with args. set dummy $ac_prog; ac_word=$2 { $as_echo "$as_me:${as_lineno-$LINENO}: checking for $ac_word" >&5 $as_echo_n "checking for $ac_word... " >&6; } if ${ac_cv_prog_ac_ct_CC+:} false; then : $as_echo_n "(cached) " >&6 else if test -n "$ac_ct_CC"; then ac_cv_prog_ac_ct_CC="$ac_ct_CC" # Let the user override the test. else as_save_IFS=$IFS; IFS=$PATH_SEPARATOR for as_dir in $PATH do IFS=$as_save_IFS test -z "$as_dir" && as_dir=. for ac_exec_ext in '' $ac_executable_extensions; do if as_fn_executable_p "$as_dir/$ac_word$ac_exec_ext"; then ac_cv_prog_ac_ct_CC="$ac_prog" $as_echo "$as_me:${as_lineno-$LINENO}: found $as_dir/$ac_word$ac_exec_ext" >&5 break 2 fi done done IFS=$as_save_IFS fi fi ac_ct_CC=$ac_cv_prog_ac_ct_CC if test -n "$ac_ct_CC"; then { $as_echo "$as_me:${as_lineno-$LINENO}: result: $ac_ct_CC" >&5 $as_echo "$ac_ct_CC" >&6; } else { $as_echo "$as_me:${as_lineno-$LINENO}: result: no" >&5 $as_echo "no" >&6; } fi test -n "$ac_ct_CC" && break done if test "x$ac_ct_CC" = x; then CC="" else case $cross_compiling:$ac_tool_warned in yes:) { $as_echo "$as_me:${as_lineno-$LINENO}: WARNING: using cross tools not prefixed with host triplet" >&5 $as_echo "$as_me: WARNING: using cross tools not prefixed with host triplet" >&2;} ac_tool_warned=yes ;; esac CC=$ac_ct_CC fi fi fi test -z "$CC" && { { $as_echo "$as_me:${as_lineno-$LINENO}: error: in \`$ac_pwd':" >&5 $as_echo "$as_me: error: in \`$ac_pwd':" >&2;} as_fn_error $? "no acceptable C compiler found in \$PATH See \`config.log' for more details" "$LINENO" 5; } # Provide some information about the compiler. $as_echo "$as_me:${as_lineno-$LINENO}: checking for C compiler version" >&5 set X $ac_compile ac_compiler=$2 for ac_option in --version -v -V -qversion; do { { ac_try="$ac_compiler $ac_option >&5" case "(($ac_try" in *\"* | *\`* | *\\*) ac_try_echo=\$ac_try;; *) ac_try_echo=$ac_try;; esac eval ac_try_echo="\"\$as_me:${as_lineno-$LINENO}: $ac_try_echo\"" $as_echo "$ac_try_echo"; } >&5 (eval "$ac_compiler $ac_option >&5") 2>conftest.err ac_status=$? if test -s conftest.err; then sed '10a\ ... rest of stderr output deleted ... 10q' conftest.err >conftest.er1 cat conftest.er1 >&5 fi rm -f conftest.er1 conftest.err $as_echo "$as_me:${as_lineno-$LINENO}: \$? = $ac_status" >&5 test $ac_status = 0; } done cat confdefs.h - <<_ACEOF >conftest.$ac_ext /* end confdefs.h. */ int main () { ; return 0; } _ACEOF ac_clean_files_save=$ac_clean_files ac_clean_files="$ac_clean_files a.out a.out.dSYM a.exe b.out" # Try to create an executable without -o first, disregard a.out. # It will help us diagnose broken compilers, and finding out an intuition # of exeext. { $as_echo "$as_me:${as_lineno-$LINENO}: checking whether the C compiler works" >&5 $as_echo_n "checking whether the C compiler works... " >&6; } ac_link_default=`$as_echo "$ac_link" | sed 's/ -o *conftest[^ ]*//'` # The possible output files: ac_files="a.out conftest.exe conftest a.exe a_out.exe b.out conftest.*" ac_rmfiles= for ac_file in $ac_files do case $ac_file in *.$ac_ext | *.xcoff | *.tds | *.d | *.pdb | *.xSYM | *.bb | *.bbg | *.map | *.inf | *.dSYM | *.o | *.obj ) ;; * ) ac_rmfiles="$ac_rmfiles $ac_file";; esac done rm -f $ac_rmfiles if { { ac_try="$ac_link_default" case "(($ac_try" in *\"* | *\`* | *\\*) ac_try_echo=\$ac_try;; *) ac_try_echo=$ac_try;; esac eval ac_try_echo="\"\$as_me:${as_lineno-$LINENO}: $ac_try_echo\"" $as_echo "$ac_try_echo"; } >&5 (eval "$ac_link_default") 2>&5 ac_status=$? $as_echo "$as_me:${as_lineno-$LINENO}: \$? = $ac_status" >&5 test $ac_status = 0; }; then : # Autoconf-2.13 could set the ac_cv_exeext variable to `no'. # So ignore a value of `no', otherwise this would lead to `EXEEXT = no' # in a Makefile. We should not override ac_cv_exeext if it was cached, # so that the user can short-circuit this test for compilers unknown to # Autoconf. for ac_file in $ac_files '' do test -f "$ac_file" || continue case $ac_file in *.$ac_ext | *.xcoff | *.tds | *.d | *.pdb | *.xSYM | *.bb | *.bbg | *.map | *.inf | *.dSYM | *.o | *.obj ) ;; [ab].out ) # We found the default executable, but exeext='' is most # certainly right. break;; *.* ) if test "${ac_cv_exeext+set}" = set && test "$ac_cv_exeext" != no; then :; else ac_cv_exeext=`expr "$ac_file" : '[^.]*\(\..*\)'` fi # We set ac_cv_exeext here because the later test for it is not # safe: cross compilers may not add the suffix if given an `-o' # argument, so we may need to know it at that point already. # Even if this section looks crufty: it has the advantage of # actually working. break;; * ) break;; esac done test "$ac_cv_exeext" = no && ac_cv_exeext= else ac_file='' fi if test -z "$ac_file"; then : { $as_echo "$as_me:${as_lineno-$LINENO}: result: no" >&5 $as_echo "no" >&6; } $as_echo "$as_me: failed program was:" >&5 sed 's/^/| /' conftest.$ac_ext >&5 { { $as_echo "$as_me:${as_lineno-$LINENO}: error: in \`$ac_pwd':" >&5 $as_echo "$as_me: error: in \`$ac_pwd':" >&2;} as_fn_error 77 "C compiler cannot create executables See \`config.log' for more details" "$LINENO" 5; } else { $as_echo "$as_me:${as_lineno-$LINENO}: result: yes" >&5 $as_echo "yes" >&6; } fi { $as_echo "$as_me:${as_lineno-$LINENO}: checking for C compiler default output file name" >&5 $as_echo_n "checking for C compiler default output file name... " >&6; } { $as_echo "$as_me:${as_lineno-$LINENO}: result: $ac_file" >&5 $as_echo "$ac_file" >&6; } ac_exeext=$ac_cv_exeext rm -f -r a.out a.out.dSYM a.exe conftest$ac_cv_exeext b.out ac_clean_files=$ac_clean_files_save { $as_echo "$as_me:${as_lineno-$LINENO}: checking for suffix of executables" >&5 $as_echo_n "checking for suffix of executables... " >&6; } if { { ac_try="$ac_link" case "(($ac_try" in *\"* | *\`* | *\\*) ac_try_echo=\$ac_try;; *) ac_try_echo=$ac_try;; esac eval ac_try_echo="\"\$as_me:${as_lineno-$LINENO}: $ac_try_echo\"" $as_echo "$ac_try_echo"; } >&5 (eval "$ac_link") 2>&5 ac_status=$? $as_echo "$as_me:${as_lineno-$LINENO}: \$? = $ac_status" >&5 test $ac_status = 0; }; then : # If both `conftest.exe' and `conftest' are `present' (well, observable) # catch `conftest.exe'. For instance with Cygwin, `ls conftest' will # work properly (i.e., refer to `conftest.exe'), while it won't with # `rm'. for ac_file in conftest.exe conftest conftest.*; do test -f "$ac_file" || continue case $ac_file in *.$ac_ext | *.xcoff | *.tds | *.d | *.pdb | *.xSYM | *.bb | *.bbg | *.map | *.inf | *.dSYM | *.o | *.obj ) ;; *.* ) ac_cv_exeext=`expr "$ac_file" : '[^.]*\(\..*\)'` break;; * ) break;; esac done else { { $as_echo "$as_me:${as_lineno-$LINENO}: error: in \`$ac_pwd':" >&5 $as_echo "$as_me: error: in \`$ac_pwd':" >&2;} as_fn_error $? "cannot compute suffix of executables: cannot compile and link See \`config.log' for more details" "$LINENO" 5; } fi rm -f conftest conftest$ac_cv_exeext { $as_echo "$as_me:${as_lineno-$LINENO}: result: $ac_cv_exeext" >&5 $as_echo "$ac_cv_exeext" >&6; } rm -f conftest.$ac_ext EXEEXT=$ac_cv_exeext ac_exeext=$EXEEXT cat confdefs.h - <<_ACEOF >conftest.$ac_ext /* end confdefs.h. */ #include int main () { FILE *f = fopen ("conftest.out", "w"); return ferror (f) || fclose (f) != 0; ; return 0; } _ACEOF ac_clean_files="$ac_clean_files conftest.out" # Check that the compiler produces executables we can run. If not, either # the compiler is broken, or we cross compile. { $as_echo "$as_me:${as_lineno-$LINENO}: checking whether we are cross compiling" >&5 $as_echo_n "checking whether we are cross compiling... " >&6; } if test "$cross_compiling" != yes; then { { ac_try="$ac_link" case "(($ac_try" in *\"* | *\`* | *\\*) ac_try_echo=\$ac_try;; *) ac_try_echo=$ac_try;; esac eval ac_try_echo="\"\$as_me:${as_lineno-$LINENO}: $ac_try_echo\"" $as_echo "$ac_try_echo"; } >&5 (eval "$ac_link") 2>&5 ac_status=$? $as_echo "$as_me:${as_lineno-$LINENO}: \$? = $ac_status" >&5 test $ac_status = 0; } if { ac_try='./conftest$ac_cv_exeext' { { case "(($ac_try" in *\"* | *\`* | *\\*) ac_try_echo=\$ac_try;; *) ac_try_echo=$ac_try;; esac eval ac_try_echo="\"\$as_me:${as_lineno-$LINENO}: $ac_try_echo\"" $as_echo "$ac_try_echo"; } >&5 (eval "$ac_try") 2>&5 ac_status=$? $as_echo "$as_me:${as_lineno-$LINENO}: \$? = $ac_status" >&5 test $ac_status = 0; }; }; then cross_compiling=no else if test "$cross_compiling" = maybe; then cross_compiling=yes else { { $as_echo "$as_me:${as_lineno-$LINENO}: error: in \`$ac_pwd':" >&5 $as_echo "$as_me: error: in \`$ac_pwd':" >&2;} as_fn_error $? "cannot run C compiled programs. If you meant to cross compile, use \`--host'. See \`config.log' for more details" "$LINENO" 5; } fi fi fi { $as_echo "$as_me:${as_lineno-$LINENO}: result: $cross_compiling" >&5 $as_echo "$cross_compiling" >&6; } rm -f conftest.$ac_ext conftest$ac_cv_exeext conftest.out ac_clean_files=$ac_clean_files_save { $as_echo "$as_me:${as_lineno-$LINENO}: checking for suffix of object files" >&5 $as_echo_n "checking for suffix of object files... " >&6; } if ${ac_cv_objext+:} false; then : $as_echo_n "(cached) " >&6 else cat confdefs.h - <<_ACEOF >conftest.$ac_ext /* end confdefs.h. */ int main () { ; return 0; } _ACEOF rm -f conftest.o conftest.obj if { { ac_try="$ac_compile" case "(($ac_try" in *\"* | *\`* | *\\*) ac_try_echo=\$ac_try;; *) ac_try_echo=$ac_try;; esac eval ac_try_echo="\"\$as_me:${as_lineno-$LINENO}: $ac_try_echo\"" $as_echo "$ac_try_echo"; } >&5 (eval "$ac_compile") 2>&5 ac_status=$? $as_echo "$as_me:${as_lineno-$LINENO}: \$? = $ac_status" >&5 test $ac_status = 0; }; then : for ac_file in conftest.o conftest.obj conftest.*; do test -f "$ac_file" || continue; case $ac_file in *.$ac_ext | *.xcoff | *.tds | *.d | *.pdb | *.xSYM | *.bb | *.bbg | *.map | *.inf | *.dSYM ) ;; *) ac_cv_objext=`expr "$ac_file" : '.*\.\(.*\)'` break;; esac done else $as_echo "$as_me: failed program was:" >&5 sed 's/^/| /' conftest.$ac_ext >&5 { { $as_echo "$as_me:${as_lineno-$LINENO}: error: in \`$ac_pwd':" >&5 $as_echo "$as_me: error: in \`$ac_pwd':" >&2;} as_fn_error $? "cannot compute suffix of object files: cannot compile See \`config.log' for more details" "$LINENO" 5; } fi rm -f conftest.$ac_cv_objext conftest.$ac_ext fi { $as_echo "$as_me:${as_lineno-$LINENO}: result: $ac_cv_objext" >&5 $as_echo "$ac_cv_objext" >&6; } OBJEXT=$ac_cv_objext ac_objext=$OBJEXT { $as_echo "$as_me:${as_lineno-$LINENO}: checking whether we are using the GNU C compiler" >&5 $as_echo_n "checking whether we are using the GNU C compiler... " >&6; } if ${ac_cv_c_compiler_gnu+:} false; then : $as_echo_n "(cached) " >&6 else cat confdefs.h - <<_ACEOF >conftest.$ac_ext /* end confdefs.h. */ int main () { #ifndef __GNUC__ choke me #endif ; return 0; } _ACEOF if ac_fn_c_try_compile "$LINENO"; then : ac_compiler_gnu=yes else ac_compiler_gnu=no fi rm -f core conftest.err conftest.$ac_objext conftest.$ac_ext ac_cv_c_compiler_gnu=$ac_compiler_gnu fi { $as_echo "$as_me:${as_lineno-$LINENO}: result: $ac_cv_c_compiler_gnu" >&5 $as_echo "$ac_cv_c_compiler_gnu" >&6; } if test $ac_compiler_gnu = yes; then GCC=yes else GCC= fi ac_test_CFLAGS=${CFLAGS+set} ac_save_CFLAGS=$CFLAGS { $as_echo "$as_me:${as_lineno-$LINENO}: checking whether $CC accepts -g" >&5 $as_echo_n "checking whether $CC accepts -g... " >&6; } if ${ac_cv_prog_cc_g+:} false; then : $as_echo_n "(cached) " >&6 else ac_save_c_werror_flag=$ac_c_werror_flag ac_c_werror_flag=yes ac_cv_prog_cc_g=no CFLAGS="-g" cat confdefs.h - <<_ACEOF >conftest.$ac_ext /* end confdefs.h. */ int main () { ; return 0; } _ACEOF if ac_fn_c_try_compile "$LINENO"; then : ac_cv_prog_cc_g=yes else CFLAGS="" cat confdefs.h - <<_ACEOF >conftest.$ac_ext /* end confdefs.h. */ int main () { ; return 0; } _ACEOF if ac_fn_c_try_compile "$LINENO"; then : else ac_c_werror_flag=$ac_save_c_werror_flag CFLAGS="-g" cat confdefs.h - <<_ACEOF >conftest.$ac_ext /* end confdefs.h. */ int main () { ; return 0; } _ACEOF if ac_fn_c_try_compile "$LINENO"; then : ac_cv_prog_cc_g=yes fi rm -f core conftest.err conftest.$ac_objext conftest.$ac_ext fi rm -f core conftest.err conftest.$ac_objext conftest.$ac_ext fi rm -f core conftest.err conftest.$ac_objext conftest.$ac_ext ac_c_werror_flag=$ac_save_c_werror_flag fi { $as_echo "$as_me:${as_lineno-$LINENO}: result: $ac_cv_prog_cc_g" >&5 $as_echo "$ac_cv_prog_cc_g" >&6; } if test "$ac_test_CFLAGS" = set; then CFLAGS=$ac_save_CFLAGS elif test $ac_cv_prog_cc_g = yes; then if test "$GCC" = yes; then CFLAGS="-g -O2" else CFLAGS="-g" fi else if test "$GCC" = yes; then CFLAGS="-O2" else CFLAGS= fi fi { $as_echo "$as_me:${as_lineno-$LINENO}: checking for $CC option to accept ISO C89" >&5 $as_echo_n "checking for $CC option to accept ISO C89... " >&6; } if ${ac_cv_prog_cc_c89+:} false; then : $as_echo_n "(cached) " >&6 else ac_cv_prog_cc_c89=no ac_save_CC=$CC cat confdefs.h - <<_ACEOF >conftest.$ac_ext /* end confdefs.h. */ #include #include struct stat; /* Most of the following tests are stolen from RCS 5.7's src/conf.sh. */ struct buf { int x; }; FILE * (*rcsopen) (struct buf *, struct stat *, int); static char *e (p, i) char **p; int i; { return p[i]; } static char *f (char * (*g) (char **, int), char **p, ...) { char *s; va_list v; va_start (v,p); s = g (p, va_arg (v,int)); va_end (v); return s; } /* OSF 4.0 Compaq cc is some sort of almost-ANSI by default. It has function prototypes and stuff, but not '\xHH' hex character constants. These don't provoke an error unfortunately, instead are silently treated as 'x'. The following induces an error, until -std is added to get proper ANSI mode. Curiously '\x00'!='x' always comes out true, for an array size at least. It's necessary to write '\x00'==0 to get something that's true only with -std. */ int osf4_cc_array ['\x00' == 0 ? 1 : -1]; /* IBM C 6 for AIX is almost-ANSI by default, but it replaces macro parameters inside strings and character constants. */ #define FOO(x) 'x' int xlc6_cc_array[FOO(a) == 'x' ? 1 : -1]; int test (int i, double x); struct s1 {int (*f) (int a);}; struct s2 {int (*f) (double a);}; int pairnames (int, char **, FILE *(*)(struct buf *, struct stat *, int), int, int); int argc; char **argv; int main () { return f (e, argv, 0) != argv[0] || f (e, argv, 1) != argv[1]; ; return 0; } _ACEOF for ac_arg in '' -qlanglvl=extc89 -qlanglvl=ansi -std \ -Ae "-Aa -D_HPUX_SOURCE" "-Xc -D__EXTENSIONS__" do CC="$ac_save_CC $ac_arg" if ac_fn_c_try_compile "$LINENO"; then : ac_cv_prog_cc_c89=$ac_arg fi rm -f core conftest.err conftest.$ac_objext test "x$ac_cv_prog_cc_c89" != "xno" && break done rm -f conftest.$ac_ext CC=$ac_save_CC fi # AC_CACHE_VAL case "x$ac_cv_prog_cc_c89" in x) { $as_echo "$as_me:${as_lineno-$LINENO}: result: none needed" >&5 $as_echo "none needed" >&6; } ;; xno) { $as_echo "$as_me:${as_lineno-$LINENO}: result: unsupported" >&5 $as_echo "unsupported" >&6; } ;; *) CC="$CC $ac_cv_prog_cc_c89" { $as_echo "$as_me:${as_lineno-$LINENO}: result: $ac_cv_prog_cc_c89" >&5 $as_echo "$ac_cv_prog_cc_c89" >&6; } ;; esac if test "x$ac_cv_prog_cc_c89" != xno; then : fi ac_ext=c ac_cpp='$CPP $CPPFLAGS' ac_compile='$CC -c $CFLAGS $CPPFLAGS conftest.$ac_ext >&5' ac_link='$CC -o conftest$ac_exeext $CFLAGS $CPPFLAGS $LDFLAGS conftest.$ac_ext $LIBS >&5' ac_compiler_gnu=$ac_cv_c_compiler_gnu ac_ext=c ac_cpp='$CPP $CPPFLAGS' ac_compile='$CC -c $CFLAGS $CPPFLAGS conftest.$ac_ext >&5' ac_link='$CC -o conftest$ac_exeext $CFLAGS $CPPFLAGS $LDFLAGS conftest.$ac_ext $LIBS >&5' ac_compiler_gnu=$ac_cv_c_compiler_gnu { $as_echo "$as_me:${as_lineno-$LINENO}: checking whether $CC understands -c and -o together" >&5 $as_echo_n "checking whether $CC understands -c and -o together... " >&6; } if ${am_cv_prog_cc_c_o+:} false; then : $as_echo_n "(cached) " >&6 else cat confdefs.h - <<_ACEOF >conftest.$ac_ext /* end confdefs.h. */ int main () { ; return 0; } _ACEOF # Make sure it works both with $CC and with simple cc. # Following AC_PROG_CC_C_O, we do the test twice because some # compilers refuse to overwrite an existing .o file with -o, # though they will create one. am_cv_prog_cc_c_o=yes for am_i in 1 2; do if { echo "$as_me:$LINENO: $CC -c conftest.$ac_ext -o conftest2.$ac_objext" >&5 ($CC -c conftest.$ac_ext -o conftest2.$ac_objext) >&5 2>&5 ac_status=$? echo "$as_me:$LINENO: \$? = $ac_status" >&5 (exit $ac_status); } \ && test -f conftest2.$ac_objext; then : OK else am_cv_prog_cc_c_o=no break fi done rm -f core conftest* unset am_i fi { $as_echo "$as_me:${as_lineno-$LINENO}: result: $am_cv_prog_cc_c_o" >&5 $as_echo "$am_cv_prog_cc_c_o" >&6; } if test "$am_cv_prog_cc_c_o" != yes; then # Losing compiler, so override with the script. # FIXME: It is wrong to rewrite CC. # But if we don't then we get into trouble of one sort or another. # A longer-term fix would be to have automake use am__CC in this case, # and then we could set am__CC="\$(top_srcdir)/compile \$(CC)" CC="$am_aux_dir/compile $CC" fi ac_ext=c ac_cpp='$CPP $CPPFLAGS' ac_compile='$CC -c $CFLAGS $CPPFLAGS conftest.$ac_ext >&5' ac_link='$CC -o conftest$ac_exeext $CFLAGS $CPPFLAGS $LDFLAGS conftest.$ac_ext $LIBS >&5' ac_compiler_gnu=$ac_cv_c_compiler_gnu DEPDIR="${am__leading_dot}deps" ac_config_commands="$ac_config_commands depfiles" { $as_echo "$as_me:${as_lineno-$LINENO}: checking whether ${MAKE-make} supports the include directive" >&5 $as_echo_n "checking whether ${MAKE-make} supports the include directive... " >&6; } cat > confinc.mk << 'END' am__doit: @echo this is the am__doit target >confinc.out .PHONY: am__doit END am__include="#" am__quote= # BSD make does it like this. echo '.include "confinc.mk" # ignored' > confmf.BSD # Other make implementations (GNU, Solaris 10, AIX) do it like this. echo 'include confinc.mk # ignored' > confmf.GNU _am_result=no for s in GNU BSD; do { echo "$as_me:$LINENO: ${MAKE-make} -f confmf.$s && cat confinc.out" >&5 (${MAKE-make} -f confmf.$s && cat confinc.out) >&5 2>&5 ac_status=$? echo "$as_me:$LINENO: \$? = $ac_status" >&5 (exit $ac_status); } case $?:`cat confinc.out 2>/dev/null` in #( '0:this is the am__doit target') : case $s in #( BSD) : am__include='.include' am__quote='"' ;; #( *) : am__include='include' am__quote='' ;; esac ;; #( *) : ;; esac if test "$am__include" != "#"; then _am_result="yes ($s style)" break fi done rm -f confinc.* confmf.* { $as_echo "$as_me:${as_lineno-$LINENO}: result: ${_am_result}" >&5 $as_echo "${_am_result}" >&6; } # Check whether --enable-dependency-tracking was given. if test "${enable_dependency_tracking+set}" = set; then : enableval=$enable_dependency_tracking; fi if test "x$enable_dependency_tracking" != xno; then am_depcomp="$ac_aux_dir/depcomp" AMDEPBACKSLASH='\' am__nodep='_no' fi if test "x$enable_dependency_tracking" != xno; then AMDEP_TRUE= AMDEP_FALSE='#' else AMDEP_TRUE='#' AMDEP_FALSE= fi depcc="$CC" am_compiler_list= { $as_echo "$as_me:${as_lineno-$LINENO}: checking dependency style of $depcc" >&5 $as_echo_n "checking dependency style of $depcc... " >&6; } if ${am_cv_CC_dependencies_compiler_type+:} false; then : $as_echo_n "(cached) " >&6 else if test -z "$AMDEP_TRUE" && test -f "$am_depcomp"; then # We make a subdir and do the tests there. Otherwise we can end up # making bogus files that we don't know about and never remove. For # instance it was reported that on HP-UX the gcc test will end up # making a dummy file named 'D' -- because '-MD' means "put the output # in D". rm -rf conftest.dir mkdir conftest.dir # Copy depcomp to subdir because otherwise we won't find it if we're # using a relative directory. cp "$am_depcomp" conftest.dir cd conftest.dir # We will build objects and dependencies in a subdirectory because # it helps to detect inapplicable dependency modes. For instance # both Tru64's cc and ICC support -MD to output dependencies as a # side effect of compilation, but ICC will put the dependencies in # the current directory while Tru64 will put them in the object # directory. mkdir sub am_cv_CC_dependencies_compiler_type=none if test "$am_compiler_list" = ""; then am_compiler_list=`sed -n 's/^#*\([a-zA-Z0-9]*\))$/\1/p' < ./depcomp` fi am__universal=false case " $depcc " in #( *\ -arch\ *\ -arch\ *) am__universal=true ;; esac for depmode in $am_compiler_list; do # Setup a source with many dependencies, because some compilers # like to wrap large dependency lists on column 80 (with \), and # we should not choose a depcomp mode which is confused by this. # # We need to recreate these files for each test, as the compiler may # overwrite some of them when testing with obscure command lines. # This happens at least with the AIX C compiler. : > sub/conftest.c for i in 1 2 3 4 5 6; do echo '#include "conftst'$i'.h"' >> sub/conftest.c # Using ": > sub/conftst$i.h" creates only sub/conftst1.h with # Solaris 10 /bin/sh. echo '/* dummy */' > sub/conftst$i.h done echo "${am__include} ${am__quote}sub/conftest.Po${am__quote}" > confmf # We check with '-c' and '-o' for the sake of the "dashmstdout" # mode. It turns out that the SunPro C++ compiler does not properly # handle '-M -o', and we need to detect this. Also, some Intel # versions had trouble with output in subdirs. am__obj=sub/conftest.${OBJEXT-o} am__minus_obj="-o $am__obj" case $depmode in gcc) # This depmode causes a compiler race in universal mode. test "$am__universal" = false || continue ;; nosideeffect) # After this tag, mechanisms are not by side-effect, so they'll # only be used when explicitly requested. if test "x$enable_dependency_tracking" = xyes; then continue else break fi ;; msvc7 | msvc7msys | msvisualcpp | msvcmsys) # This compiler won't grok '-c -o', but also, the minuso test has # not run yet. These depmodes are late enough in the game, and # so weak that their functioning should not be impacted. am__obj=conftest.${OBJEXT-o} am__minus_obj= ;; none) break ;; esac if depmode=$depmode \ source=sub/conftest.c object=$am__obj \ depfile=sub/conftest.Po tmpdepfile=sub/conftest.TPo \ $SHELL ./depcomp $depcc -c $am__minus_obj sub/conftest.c \ >/dev/null 2>conftest.err && grep sub/conftst1.h sub/conftest.Po > /dev/null 2>&1 && grep sub/conftst6.h sub/conftest.Po > /dev/null 2>&1 && grep $am__obj sub/conftest.Po > /dev/null 2>&1 && ${MAKE-make} -s -f confmf > /dev/null 2>&1; then # icc doesn't choke on unknown options, it will just issue warnings # or remarks (even with -Werror). So we grep stderr for any message # that says an option was ignored or not supported. # When given -MP, icc 7.0 and 7.1 complain thusly: # icc: Command line warning: ignoring option '-M'; no argument required # The diagnosis changed in icc 8.0: # icc: Command line remark: option '-MP' not supported if (grep 'ignoring option' conftest.err || grep 'not supported' conftest.err) >/dev/null 2>&1; then :; else am_cv_CC_dependencies_compiler_type=$depmode break fi fi done cd .. rm -rf conftest.dir else am_cv_CC_dependencies_compiler_type=none fi fi { $as_echo "$as_me:${as_lineno-$LINENO}: result: $am_cv_CC_dependencies_compiler_type" >&5 $as_echo "$am_cv_CC_dependencies_compiler_type" >&6; } CCDEPMODE=depmode=$am_cv_CC_dependencies_compiler_type if test "x$enable_dependency_tracking" != xno \ && test "$am_cv_CC_dependencies_compiler_type" = gcc3; then am__fastdepCC_TRUE= am__fastdepCC_FALSE='#' else am__fastdepCC_TRUE='#' am__fastdepCC_FALSE= fi ac_ext=c ac_cpp='$CPP $CPPFLAGS' ac_compile='$CC -c $CFLAGS $CPPFLAGS conftest.$ac_ext >&5' ac_link='$CC -o conftest$ac_exeext $CFLAGS $CPPFLAGS $LDFLAGS conftest.$ac_ext $LIBS >&5' ac_compiler_gnu=$ac_cv_c_compiler_gnu { $as_echo "$as_me:${as_lineno-$LINENO}: checking how to run the C preprocessor" >&5 $as_echo_n "checking how to run the C preprocessor... " >&6; } # On Suns, sometimes $CPP names a directory. if test -n "$CPP" && test -d "$CPP"; then CPP= fi if test -z "$CPP"; then if ${ac_cv_prog_CPP+:} false; then : $as_echo_n "(cached) " >&6 else # Double quotes because CPP needs to be expanded for CPP in "$CC -E" "$CC -E -traditional-cpp" "/lib/cpp" do ac_preproc_ok=false for ac_c_preproc_warn_flag in '' yes do # Use a header file that comes with gcc, so configuring glibc # with a fresh cross-compiler works. # Prefer to if __STDC__ is defined, since # exists even on freestanding compilers. # On the NeXT, cc -E runs the code through the compiler's parser, # not just through cpp. "Syntax error" is here to catch this case. cat confdefs.h - <<_ACEOF >conftest.$ac_ext /* end confdefs.h. */ #ifdef __STDC__ # include #else # include #endif Syntax error _ACEOF if ac_fn_c_try_cpp "$LINENO"; then : else # Broken: fails on valid input. continue fi rm -f conftest.err conftest.i conftest.$ac_ext # OK, works on sane cases. Now check whether nonexistent headers # can be detected and how. cat confdefs.h - <<_ACEOF >conftest.$ac_ext /* end confdefs.h. */ #include _ACEOF if ac_fn_c_try_cpp "$LINENO"; then : # Broken: success on invalid input. continue else # Passes both tests. ac_preproc_ok=: break fi rm -f conftest.err conftest.i conftest.$ac_ext done # Because of `break', _AC_PREPROC_IFELSE's cleaning code was skipped. rm -f conftest.i conftest.err conftest.$ac_ext if $ac_preproc_ok; then : break fi done ac_cv_prog_CPP=$CPP fi CPP=$ac_cv_prog_CPP else ac_cv_prog_CPP=$CPP fi { $as_echo "$as_me:${as_lineno-$LINENO}: result: $CPP" >&5 $as_echo "$CPP" >&6; } ac_preproc_ok=false for ac_c_preproc_warn_flag in '' yes do # Use a header file that comes with gcc, so configuring glibc # with a fresh cross-compiler works. # Prefer to if __STDC__ is defined, since # exists even on freestanding compilers. # On the NeXT, cc -E runs the code through the compiler's parser, # not just through cpp. "Syntax error" is here to catch this case. cat confdefs.h - <<_ACEOF >conftest.$ac_ext /* end confdefs.h. */ #ifdef __STDC__ # include #else # include #endif Syntax error _ACEOF if ac_fn_c_try_cpp "$LINENO"; then : else # Broken: fails on valid input. continue fi rm -f conftest.err conftest.i conftest.$ac_ext # OK, works on sane cases. Now check whether nonexistent headers # can be detected and how. cat confdefs.h - <<_ACEOF >conftest.$ac_ext /* end confdefs.h. */ #include _ACEOF if ac_fn_c_try_cpp "$LINENO"; then : # Broken: success on invalid input. continue else # Passes both tests. ac_preproc_ok=: break fi rm -f conftest.err conftest.i conftest.$ac_ext done # Because of `break', _AC_PREPROC_IFELSE's cleaning code was skipped. rm -f conftest.i conftest.err conftest.$ac_ext if $ac_preproc_ok; then : else { { $as_echo "$as_me:${as_lineno-$LINENO}: error: in \`$ac_pwd':" >&5 $as_echo "$as_me: error: in \`$ac_pwd':" >&2;} as_fn_error $? "C preprocessor \"$CPP\" fails sanity check See \`config.log' for more details" "$LINENO" 5; } fi ac_ext=c ac_cpp='$CPP $CPPFLAGS' ac_compile='$CC -c $CFLAGS $CPPFLAGS conftest.$ac_ext >&5' ac_link='$CC -o conftest$ac_exeext $CFLAGS $CPPFLAGS $LDFLAGS conftest.$ac_ext $LIBS >&5' ac_compiler_gnu=$ac_cv_c_compiler_gnu ac_ext=cpp ac_cpp='$CXXCPP $CPPFLAGS' ac_compile='$CXX -c $CXXFLAGS $CPPFLAGS conftest.$ac_ext >&5' ac_link='$CXX -o conftest$ac_exeext $CXXFLAGS $CPPFLAGS $LDFLAGS conftest.$ac_ext $LIBS >&5' ac_compiler_gnu=$ac_cv_cxx_compiler_gnu if test -z "$CXX"; then if test -n "$CCC"; then CXX=$CCC else if test -n "$ac_tool_prefix"; then for ac_prog in g++ c++ gpp aCC CC cxx cc++ cl.exe FCC KCC RCC xlC_r xlC do # Extract the first word of "$ac_tool_prefix$ac_prog", so it can be a program name with args. set dummy $ac_tool_prefix$ac_prog; ac_word=$2 { $as_echo "$as_me:${as_lineno-$LINENO}: checking for $ac_word" >&5 $as_echo_n "checking for $ac_word... " >&6; } if ${ac_cv_prog_CXX+:} false; then : $as_echo_n "(cached) " >&6 else if test -n "$CXX"; then ac_cv_prog_CXX="$CXX" # Let the user override the test. else as_save_IFS=$IFS; IFS=$PATH_SEPARATOR for as_dir in $PATH do IFS=$as_save_IFS test -z "$as_dir" && as_dir=. for ac_exec_ext in '' $ac_executable_extensions; do if as_fn_executable_p "$as_dir/$ac_word$ac_exec_ext"; then ac_cv_prog_CXX="$ac_tool_prefix$ac_prog" $as_echo "$as_me:${as_lineno-$LINENO}: found $as_dir/$ac_word$ac_exec_ext" >&5 break 2 fi done done IFS=$as_save_IFS fi fi CXX=$ac_cv_prog_CXX if test -n "$CXX"; then { $as_echo "$as_me:${as_lineno-$LINENO}: result: $CXX" >&5 $as_echo "$CXX" >&6; } else { $as_echo "$as_me:${as_lineno-$LINENO}: result: no" >&5 $as_echo "no" >&6; } fi test -n "$CXX" && break done fi if test -z "$CXX"; then ac_ct_CXX=$CXX for ac_prog in g++ c++ gpp aCC CC cxx cc++ cl.exe FCC KCC RCC xlC_r xlC do # Extract the first word of "$ac_prog", so it can be a program name with args. set dummy $ac_prog; ac_word=$2 { $as_echo "$as_me:${as_lineno-$LINENO}: checking for $ac_word" >&5 $as_echo_n "checking for $ac_word... " >&6; } if ${ac_cv_prog_ac_ct_CXX+:} false; then : $as_echo_n "(cached) " >&6 else if test -n "$ac_ct_CXX"; then ac_cv_prog_ac_ct_CXX="$ac_ct_CXX" # Let the user override the test. else as_save_IFS=$IFS; IFS=$PATH_SEPARATOR for as_dir in $PATH do IFS=$as_save_IFS test -z "$as_dir" && as_dir=. for ac_exec_ext in '' $ac_executable_extensions; do if as_fn_executable_p "$as_dir/$ac_word$ac_exec_ext"; then ac_cv_prog_ac_ct_CXX="$ac_prog" $as_echo "$as_me:${as_lineno-$LINENO}: found $as_dir/$ac_word$ac_exec_ext" >&5 break 2 fi done done IFS=$as_save_IFS fi fi ac_ct_CXX=$ac_cv_prog_ac_ct_CXX if test -n "$ac_ct_CXX"; then { $as_echo "$as_me:${as_lineno-$LINENO}: result: $ac_ct_CXX" >&5 $as_echo "$ac_ct_CXX" >&6; } else { $as_echo "$as_me:${as_lineno-$LINENO}: result: no" >&5 $as_echo "no" >&6; } fi test -n "$ac_ct_CXX" && break done if test "x$ac_ct_CXX" = x; then CXX="g++" else case $cross_compiling:$ac_tool_warned in yes:) { $as_echo "$as_me:${as_lineno-$LINENO}: WARNING: using cross tools not prefixed with host triplet" >&5 $as_echo "$as_me: WARNING: using cross tools not prefixed with host triplet" >&2;} ac_tool_warned=yes ;; esac CXX=$ac_ct_CXX fi fi fi fi # Provide some information about the compiler. $as_echo "$as_me:${as_lineno-$LINENO}: checking for C++ compiler version" >&5 set X $ac_compile ac_compiler=$2 for ac_option in --version -v -V -qversion; do { { ac_try="$ac_compiler $ac_option >&5" case "(($ac_try" in *\"* | *\`* | *\\*) ac_try_echo=\$ac_try;; *) ac_try_echo=$ac_try;; esac eval ac_try_echo="\"\$as_me:${as_lineno-$LINENO}: $ac_try_echo\"" $as_echo "$ac_try_echo"; } >&5 (eval "$ac_compiler $ac_option >&5") 2>conftest.err ac_status=$? if test -s conftest.err; then sed '10a\ ... rest of stderr output deleted ... 10q' conftest.err >conftest.er1 cat conftest.er1 >&5 fi rm -f conftest.er1 conftest.err $as_echo "$as_me:${as_lineno-$LINENO}: \$? = $ac_status" >&5 test $ac_status = 0; } done { $as_echo "$as_me:${as_lineno-$LINENO}: checking whether we are using the GNU C++ compiler" >&5 $as_echo_n "checking whether we are using the GNU C++ compiler... " >&6; } if ${ac_cv_cxx_compiler_gnu+:} false; then : $as_echo_n "(cached) " >&6 else cat confdefs.h - <<_ACEOF >conftest.$ac_ext /* end confdefs.h. */ int main () { #ifndef __GNUC__ choke me #endif ; return 0; } _ACEOF if ac_fn_cxx_try_compile "$LINENO"; then : ac_compiler_gnu=yes else ac_compiler_gnu=no fi rm -f core conftest.err conftest.$ac_objext conftest.$ac_ext ac_cv_cxx_compiler_gnu=$ac_compiler_gnu fi { $as_echo "$as_me:${as_lineno-$LINENO}: result: $ac_cv_cxx_compiler_gnu" >&5 $as_echo "$ac_cv_cxx_compiler_gnu" >&6; } if test $ac_compiler_gnu = yes; then GXX=yes else GXX= fi ac_test_CXXFLAGS=${CXXFLAGS+set} ac_save_CXXFLAGS=$CXXFLAGS { $as_echo "$as_me:${as_lineno-$LINENO}: checking whether $CXX accepts -g" >&5 $as_echo_n "checking whether $CXX accepts -g... " >&6; } if ${ac_cv_prog_cxx_g+:} false; then : $as_echo_n "(cached) " >&6 else ac_save_cxx_werror_flag=$ac_cxx_werror_flag ac_cxx_werror_flag=yes ac_cv_prog_cxx_g=no CXXFLAGS="-g" cat confdefs.h - <<_ACEOF >conftest.$ac_ext /* end confdefs.h. */ int main () { ; return 0; } _ACEOF if ac_fn_cxx_try_compile "$LINENO"; then : ac_cv_prog_cxx_g=yes else CXXFLAGS="" cat confdefs.h - <<_ACEOF >conftest.$ac_ext /* end confdefs.h. */ int main () { ; return 0; } _ACEOF if ac_fn_cxx_try_compile "$LINENO"; then : else ac_cxx_werror_flag=$ac_save_cxx_werror_flag CXXFLAGS="-g" cat confdefs.h - <<_ACEOF >conftest.$ac_ext /* end confdefs.h. */ int main () { ; return 0; } _ACEOF if ac_fn_cxx_try_compile "$LINENO"; then : ac_cv_prog_cxx_g=yes fi rm -f core conftest.err conftest.$ac_objext conftest.$ac_ext fi rm -f core conftest.err conftest.$ac_objext conftest.$ac_ext fi rm -f core conftest.err conftest.$ac_objext conftest.$ac_ext ac_cxx_werror_flag=$ac_save_cxx_werror_flag fi { $as_echo "$as_me:${as_lineno-$LINENO}: result: $ac_cv_prog_cxx_g" >&5 $as_echo "$ac_cv_prog_cxx_g" >&6; } if test "$ac_test_CXXFLAGS" = set; then CXXFLAGS=$ac_save_CXXFLAGS elif test $ac_cv_prog_cxx_g = yes; then if test "$GXX" = yes; then CXXFLAGS="-g -O2" else CXXFLAGS="-g" fi else if test "$GXX" = yes; then CXXFLAGS="-O2" else CXXFLAGS= fi fi ac_ext=c ac_cpp='$CPP $CPPFLAGS' ac_compile='$CC -c $CFLAGS $CPPFLAGS conftest.$ac_ext >&5' ac_link='$CC -o conftest$ac_exeext $CFLAGS $CPPFLAGS $LDFLAGS conftest.$ac_ext $LIBS >&5' ac_compiler_gnu=$ac_cv_c_compiler_gnu depcc="$CXX" am_compiler_list= { $as_echo "$as_me:${as_lineno-$LINENO}: checking dependency style of $depcc" >&5 $as_echo_n "checking dependency style of $depcc... " >&6; } if ${am_cv_CXX_dependencies_compiler_type+:} false; then : $as_echo_n "(cached) " >&6 else if test -z "$AMDEP_TRUE" && test -f "$am_depcomp"; then # We make a subdir and do the tests there. Otherwise we can end up # making bogus files that we don't know about and never remove. For # instance it was reported that on HP-UX the gcc test will end up # making a dummy file named 'D' -- because '-MD' means "put the output # in D". rm -rf conftest.dir mkdir conftest.dir # Copy depcomp to subdir because otherwise we won't find it if we're # using a relative directory. cp "$am_depcomp" conftest.dir cd conftest.dir # We will build objects and dependencies in a subdirectory because # it helps to detect inapplicable dependency modes. For instance # both Tru64's cc and ICC support -MD to output dependencies as a # side effect of compilation, but ICC will put the dependencies in # the current directory while Tru64 will put them in the object # directory. mkdir sub am_cv_CXX_dependencies_compiler_type=none if test "$am_compiler_list" = ""; then am_compiler_list=`sed -n 's/^#*\([a-zA-Z0-9]*\))$/\1/p' < ./depcomp` fi am__universal=false case " $depcc " in #( *\ -arch\ *\ -arch\ *) am__universal=true ;; esac for depmode in $am_compiler_list; do # Setup a source with many dependencies, because some compilers # like to wrap large dependency lists on column 80 (with \), and # we should not choose a depcomp mode which is confused by this. # # We need to recreate these files for each test, as the compiler may # overwrite some of them when testing with obscure command lines. # This happens at least with the AIX C compiler. : > sub/conftest.c for i in 1 2 3 4 5 6; do echo '#include "conftst'$i'.h"' >> sub/conftest.c # Using ": > sub/conftst$i.h" creates only sub/conftst1.h with # Solaris 10 /bin/sh. echo '/* dummy */' > sub/conftst$i.h done echo "${am__include} ${am__quote}sub/conftest.Po${am__quote}" > confmf # We check with '-c' and '-o' for the sake of the "dashmstdout" # mode. It turns out that the SunPro C++ compiler does not properly # handle '-M -o', and we need to detect this. Also, some Intel # versions had trouble with output in subdirs. am__obj=sub/conftest.${OBJEXT-o} am__minus_obj="-o $am__obj" case $depmode in gcc) # This depmode causes a compiler race in universal mode. test "$am__universal" = false || continue ;; nosideeffect) # After this tag, mechanisms are not by side-effect, so they'll # only be used when explicitly requested. if test "x$enable_dependency_tracking" = xyes; then continue else break fi ;; msvc7 | msvc7msys | msvisualcpp | msvcmsys) # This compiler won't grok '-c -o', but also, the minuso test has # not run yet. These depmodes are late enough in the game, and # so weak that their functioning should not be impacted. am__obj=conftest.${OBJEXT-o} am__minus_obj= ;; none) break ;; esac if depmode=$depmode \ source=sub/conftest.c object=$am__obj \ depfile=sub/conftest.Po tmpdepfile=sub/conftest.TPo \ $SHELL ./depcomp $depcc -c $am__minus_obj sub/conftest.c \ >/dev/null 2>conftest.err && grep sub/conftst1.h sub/conftest.Po > /dev/null 2>&1 && grep sub/conftst6.h sub/conftest.Po > /dev/null 2>&1 && grep $am__obj sub/conftest.Po > /dev/null 2>&1 && ${MAKE-make} -s -f confmf > /dev/null 2>&1; then # icc doesn't choke on unknown options, it will just issue warnings # or remarks (even with -Werror). So we grep stderr for any message # that says an option was ignored or not supported. # When given -MP, icc 7.0 and 7.1 complain thusly: # icc: Command line warning: ignoring option '-M'; no argument required # The diagnosis changed in icc 8.0: # icc: Command line remark: option '-MP' not supported if (grep 'ignoring option' conftest.err || grep 'not supported' conftest.err) >/dev/null 2>&1; then :; else am_cv_CXX_dependencies_compiler_type=$depmode break fi fi done cd .. rm -rf conftest.dir else am_cv_CXX_dependencies_compiler_type=none fi fi { $as_echo "$as_me:${as_lineno-$LINENO}: result: $am_cv_CXX_dependencies_compiler_type" >&5 $as_echo "$am_cv_CXX_dependencies_compiler_type" >&6; } CXXDEPMODE=depmode=$am_cv_CXX_dependencies_compiler_type if test "x$enable_dependency_tracking" != xno \ && test "$am_cv_CXX_dependencies_compiler_type" = gcc3; then am__fastdepCXX_TRUE= am__fastdepCXX_FALSE='#' else am__fastdepCXX_TRUE='#' am__fastdepCXX_FALSE= fi CFLAGS="$orig_cflags" CXXFLAGS="$orig_cxxflags" { $as_echo "$as_me:${as_lineno-$LINENO}: checking for $CC option to accept ISO C99" >&5 $as_echo_n "checking for $CC option to accept ISO C99... " >&6; } if ${ac_cv_prog_cc_c99+:} false; then : $as_echo_n "(cached) " >&6 else ac_cv_prog_cc_c99=no ac_save_CC=$CC cat confdefs.h - <<_ACEOF >conftest.$ac_ext /* end confdefs.h. */ #include #include #include #include #include // Check varargs macros. These examples are taken from C99 6.10.3.5. #define debug(...) fprintf (stderr, __VA_ARGS__) #define showlist(...) puts (#__VA_ARGS__) #define report(test,...) ((test) ? puts (#test) : printf (__VA_ARGS__)) static void test_varargs_macros (void) { int x = 1234; int y = 5678; debug ("Flag"); debug ("X = %d\n", x); showlist (The first, second, and third items.); report (x>y, "x is %d but y is %d", x, y); } // Check long long types. #define BIG64 18446744073709551615ull #define BIG32 4294967295ul #define BIG_OK (BIG64 / BIG32 == 4294967297ull && BIG64 % BIG32 == 0) #if !BIG_OK your preprocessor is broken; #endif #if BIG_OK #else your preprocessor is broken; #endif static long long int bignum = -9223372036854775807LL; static unsigned long long int ubignum = BIG64; struct incomplete_array { int datasize; double data[]; }; struct named_init { int number; const wchar_t *name; double average; }; typedef const char *ccp; static inline int test_restrict (ccp restrict text) { // See if C++-style comments work. // Iterate through items via the restricted pointer. // Also check for declarations in for loops. for (unsigned int i = 0; *(text+i) != '\0'; ++i) continue; return 0; } // Check varargs and va_copy. static void test_varargs (const char *format, ...) { va_list args; va_start (args, format); va_list args_copy; va_copy (args_copy, args); const char *str; int number; float fnumber; while (*format) { switch (*format++) { case 's': // string str = va_arg (args_copy, const char *); break; case 'd': // int number = va_arg (args_copy, int); break; case 'f': // float fnumber = va_arg (args_copy, double); break; default: break; } } va_end (args_copy); va_end (args); } int main () { // Check bool. _Bool success = false; // Check restrict. if (test_restrict ("String literal") == 0) success = true; char *restrict newvar = "Another string"; // Check varargs. test_varargs ("s, d' f .", "string", 65, 34.234); test_varargs_macros (); // Check flexible array members. struct incomplete_array *ia = malloc (sizeof (struct incomplete_array) + (sizeof (double) * 10)); ia->datasize = 10; for (int i = 0; i < ia->datasize; ++i) ia->data[i] = i * 1.234; // Check named initializers. struct named_init ni = { .number = 34, .name = L"Test wide string", .average = 543.34343, }; ni.number = 58; int dynamic_array[ni.number]; dynamic_array[ni.number - 1] = 543; // work around unused variable warnings return (!success || bignum == 0LL || ubignum == 0uLL || newvar[0] == 'x' || dynamic_array[ni.number - 1] != 543); ; return 0; } _ACEOF for ac_arg in '' -std=gnu99 -std=c99 -c99 -AC99 -D_STDC_C99= -qlanglvl=extc99 do CC="$ac_save_CC $ac_arg" if ac_fn_c_try_compile "$LINENO"; then : ac_cv_prog_cc_c99=$ac_arg fi rm -f core conftest.err conftest.$ac_objext test "x$ac_cv_prog_cc_c99" != "xno" && break done rm -f conftest.$ac_ext CC=$ac_save_CC fi # AC_CACHE_VAL case "x$ac_cv_prog_cc_c99" in x) { $as_echo "$as_me:${as_lineno-$LINENO}: result: none needed" >&5 $as_echo "none needed" >&6; } ;; xno) { $as_echo "$as_me:${as_lineno-$LINENO}: result: unsupported" >&5 $as_echo "unsupported" >&6; } ;; *) CC="$CC $ac_cv_prog_cc_c99" { $as_echo "$as_me:${as_lineno-$LINENO}: result: $ac_cv_prog_cc_c99" >&5 $as_echo "$ac_cv_prog_cc_c99" >&6; } ;; esac if test "x$ac_cv_prog_cc_c99" != xno; then : fi if test "x$ac_cv_env_PKG_CONFIG_set" != "xset"; then if test -n "$ac_tool_prefix"; then # Extract the first word of "${ac_tool_prefix}pkg-config", so it can be a program name with args. set dummy ${ac_tool_prefix}pkg-config; ac_word=$2 { $as_echo "$as_me:${as_lineno-$LINENO}: checking for $ac_word" >&5 $as_echo_n "checking for $ac_word... " >&6; } if ${ac_cv_path_PKG_CONFIG+:} false; then : $as_echo_n "(cached) " >&6 else case $PKG_CONFIG in [\\/]* | ?:[\\/]*) ac_cv_path_PKG_CONFIG="$PKG_CONFIG" # Let the user override the test with a path. ;; *) as_save_IFS=$IFS; IFS=$PATH_SEPARATOR for as_dir in $PATH do IFS=$as_save_IFS test -z "$as_dir" && as_dir=. for ac_exec_ext in '' $ac_executable_extensions; do if as_fn_executable_p "$as_dir/$ac_word$ac_exec_ext"; then ac_cv_path_PKG_CONFIG="$as_dir/$ac_word$ac_exec_ext" $as_echo "$as_me:${as_lineno-$LINENO}: found $as_dir/$ac_word$ac_exec_ext" >&5 break 2 fi done done IFS=$as_save_IFS ;; esac fi PKG_CONFIG=$ac_cv_path_PKG_CONFIG if test -n "$PKG_CONFIG"; then { $as_echo "$as_me:${as_lineno-$LINENO}: result: $PKG_CONFIG" >&5 $as_echo "$PKG_CONFIG" >&6; } else { $as_echo "$as_me:${as_lineno-$LINENO}: result: no" >&5 $as_echo "no" >&6; } fi fi if test -z "$ac_cv_path_PKG_CONFIG"; then ac_pt_PKG_CONFIG=$PKG_CONFIG # Extract the first word of "pkg-config", so it can be a program name with args. set dummy pkg-config; ac_word=$2 { $as_echo "$as_me:${as_lineno-$LINENO}: checking for $ac_word" >&5 $as_echo_n "checking for $ac_word... " >&6; } if ${ac_cv_path_ac_pt_PKG_CONFIG+:} false; then : $as_echo_n "(cached) " >&6 else case $ac_pt_PKG_CONFIG in [\\/]* | ?:[\\/]*) ac_cv_path_ac_pt_PKG_CONFIG="$ac_pt_PKG_CONFIG" # Let the user override the test with a path. ;; *) as_save_IFS=$IFS; IFS=$PATH_SEPARATOR for as_dir in $PATH do IFS=$as_save_IFS test -z "$as_dir" && as_dir=. for ac_exec_ext in '' $ac_executable_extensions; do if as_fn_executable_p "$as_dir/$ac_word$ac_exec_ext"; then ac_cv_path_ac_pt_PKG_CONFIG="$as_dir/$ac_word$ac_exec_ext" $as_echo "$as_me:${as_lineno-$LINENO}: found $as_dir/$ac_word$ac_exec_ext" >&5 break 2 fi done done IFS=$as_save_IFS ;; esac fi ac_pt_PKG_CONFIG=$ac_cv_path_ac_pt_PKG_CONFIG if test -n "$ac_pt_PKG_CONFIG"; then { $as_echo "$as_me:${as_lineno-$LINENO}: result: $ac_pt_PKG_CONFIG" >&5 $as_echo "$ac_pt_PKG_CONFIG" >&6; } else { $as_echo "$as_me:${as_lineno-$LINENO}: result: no" >&5 $as_echo "no" >&6; } fi if test "x$ac_pt_PKG_CONFIG" = x; then PKG_CONFIG="" else case $cross_compiling:$ac_tool_warned in yes:) { $as_echo "$as_me:${as_lineno-$LINENO}: WARNING: using cross tools not prefixed with host triplet" >&5 $as_echo "$as_me: WARNING: using cross tools not prefixed with host triplet" >&2;} ac_tool_warned=yes ;; esac PKG_CONFIG=$ac_pt_PKG_CONFIG fi else PKG_CONFIG="$ac_cv_path_PKG_CONFIG" fi fi if test -n "$PKG_CONFIG"; then _pkg_min_version=0.9.0 { $as_echo "$as_me:${as_lineno-$LINENO}: checking pkg-config is at least version $_pkg_min_version" >&5 $as_echo_n "checking pkg-config is at least version $_pkg_min_version... " >&6; } if $PKG_CONFIG --atleast-pkgconfig-version $_pkg_min_version; then { $as_echo "$as_me:${as_lineno-$LINENO}: result: yes" >&5 $as_echo "yes" >&6; } else { $as_echo "$as_me:${as_lineno-$LINENO}: result: no" >&5 $as_echo "no" >&6; } PKG_CONFIG="" fi fi SED=sed { $as_echo "$as_me:${as_lineno-$LINENO}: checking for grep that handles long lines and -e" >&5 $as_echo_n "checking for grep that handles long lines and -e... " >&6; } if ${ac_cv_path_GREP+:} false; then : $as_echo_n "(cached) " >&6 else if test -z "$GREP"; then ac_path_GREP_found=false # Loop through the user's path and test for each of PROGNAME-LIST as_save_IFS=$IFS; IFS=$PATH_SEPARATOR for as_dir in $PATH$PATH_SEPARATOR/usr/xpg4/bin do IFS=$as_save_IFS test -z "$as_dir" && as_dir=. for ac_prog in grep ggrep; do for ac_exec_ext in '' $ac_executable_extensions; do ac_path_GREP="$as_dir/$ac_prog$ac_exec_ext" as_fn_executable_p "$ac_path_GREP" || continue # Check for GNU ac_path_GREP and select it if it is found. # Check for GNU $ac_path_GREP case `"$ac_path_GREP" --version 2>&1` in *GNU*) ac_cv_path_GREP="$ac_path_GREP" ac_path_GREP_found=:;; *) ac_count=0 $as_echo_n 0123456789 >"conftest.in" while : do cat "conftest.in" "conftest.in" >"conftest.tmp" mv "conftest.tmp" "conftest.in" cp "conftest.in" "conftest.nl" $as_echo 'GREP' >> "conftest.nl" "$ac_path_GREP" -e 'GREP$' -e '-(cannot match)-' < "conftest.nl" >"conftest.out" 2>/dev/null || break diff "conftest.out" "conftest.nl" >/dev/null 2>&1 || break as_fn_arith $ac_count + 1 && ac_count=$as_val if test $ac_count -gt ${ac_path_GREP_max-0}; then # Best one so far, save it but keep looking for a better one ac_cv_path_GREP="$ac_path_GREP" ac_path_GREP_max=$ac_count fi # 10*(2^10) chars as input seems more than enough test $ac_count -gt 10 && break done rm -f conftest.in conftest.tmp conftest.nl conftest.out;; esac $ac_path_GREP_found && break 3 done done done IFS=$as_save_IFS if test -z "$ac_cv_path_GREP"; then as_fn_error $? "no acceptable grep could be found in $PATH$PATH_SEPARATOR/usr/xpg4/bin" "$LINENO" 5 fi else ac_cv_path_GREP=$GREP fi fi { $as_echo "$as_me:${as_lineno-$LINENO}: result: $ac_cv_path_GREP" >&5 $as_echo "$ac_cv_path_GREP" >&6; } GREP="$ac_cv_path_GREP" { $as_echo "$as_me:${as_lineno-$LINENO}: checking for egrep" >&5 $as_echo_n "checking for egrep... " >&6; } if ${ac_cv_path_EGREP+:} false; then : $as_echo_n "(cached) " >&6 else if echo a | $GREP -E '(a|b)' >/dev/null 2>&1 then ac_cv_path_EGREP="$GREP -E" else if test -z "$EGREP"; then ac_path_EGREP_found=false # Loop through the user's path and test for each of PROGNAME-LIST as_save_IFS=$IFS; IFS=$PATH_SEPARATOR for as_dir in $PATH$PATH_SEPARATOR/usr/xpg4/bin do IFS=$as_save_IFS test -z "$as_dir" && as_dir=. for ac_prog in egrep; do for ac_exec_ext in '' $ac_executable_extensions; do ac_path_EGREP="$as_dir/$ac_prog$ac_exec_ext" as_fn_executable_p "$ac_path_EGREP" || continue # Check for GNU ac_path_EGREP and select it if it is found. # Check for GNU $ac_path_EGREP case `"$ac_path_EGREP" --version 2>&1` in *GNU*) ac_cv_path_EGREP="$ac_path_EGREP" ac_path_EGREP_found=:;; *) ac_count=0 $as_echo_n 0123456789 >"conftest.in" while : do cat "conftest.in" "conftest.in" >"conftest.tmp" mv "conftest.tmp" "conftest.in" cp "conftest.in" "conftest.nl" $as_echo 'EGREP' >> "conftest.nl" "$ac_path_EGREP" 'EGREP$' < "conftest.nl" >"conftest.out" 2>/dev/null || break diff "conftest.out" "conftest.nl" >/dev/null 2>&1 || break as_fn_arith $ac_count + 1 && ac_count=$as_val if test $ac_count -gt ${ac_path_EGREP_max-0}; then # Best one so far, save it but keep looking for a better one ac_cv_path_EGREP="$ac_path_EGREP" ac_path_EGREP_max=$ac_count fi # 10*(2^10) chars as input seems more than enough test $ac_count -gt 10 && break done rm -f conftest.in conftest.tmp conftest.nl conftest.out;; esac $ac_path_EGREP_found && break 3 done done done IFS=$as_save_IFS if test -z "$ac_cv_path_EGREP"; then as_fn_error $? "no acceptable egrep could be found in $PATH$PATH_SEPARATOR/usr/xpg4/bin" "$LINENO" 5 fi else ac_cv_path_EGREP=$EGREP fi fi fi { $as_echo "$as_me:${as_lineno-$LINENO}: result: $ac_cv_path_EGREP" >&5 $as_echo "$ac_cv_path_EGREP" >&6; } EGREP="$ac_cv_path_EGREP" { $as_echo "$as_me:${as_lineno-$LINENO}: checking for ANSI C header files" >&5 $as_echo_n "checking for ANSI C header files... " >&6; } if ${ac_cv_header_stdc+:} false; then : $as_echo_n "(cached) " >&6 else cat confdefs.h - <<_ACEOF >conftest.$ac_ext /* end confdefs.h. */ #include #include #include #include int main () { ; return 0; } _ACEOF if ac_fn_c_try_compile "$LINENO"; then : ac_cv_header_stdc=yes else ac_cv_header_stdc=no fi rm -f core conftest.err conftest.$ac_objext conftest.$ac_ext if test $ac_cv_header_stdc = yes; then # SunOS 4.x string.h does not declare mem*, contrary to ANSI. cat confdefs.h - <<_ACEOF >conftest.$ac_ext /* end confdefs.h. */ #include _ACEOF if (eval "$ac_cpp conftest.$ac_ext") 2>&5 | $EGREP "memchr" >/dev/null 2>&1; then : else ac_cv_header_stdc=no fi rm -f conftest* fi if test $ac_cv_header_stdc = yes; then # ISC 2.0.2 stdlib.h does not declare free, contrary to ANSI. cat confdefs.h - <<_ACEOF >conftest.$ac_ext /* end confdefs.h. */ #include _ACEOF if (eval "$ac_cpp conftest.$ac_ext") 2>&5 | $EGREP "free" >/dev/null 2>&1; then : else ac_cv_header_stdc=no fi rm -f conftest* fi if test $ac_cv_header_stdc = yes; then # /bin/cc in Irix-4.0.5 gets non-ANSI ctype macros unless using -ansi. if test "$cross_compiling" = yes; then : : else cat confdefs.h - <<_ACEOF >conftest.$ac_ext /* end confdefs.h. */ #include #include #if ((' ' & 0x0FF) == 0x020) # define ISLOWER(c) ('a' <= (c) && (c) <= 'z') # define TOUPPER(c) (ISLOWER(c) ? 'A' + ((c) - 'a') : (c)) #else # define ISLOWER(c) \ (('a' <= (c) && (c) <= 'i') \ || ('j' <= (c) && (c) <= 'r') \ || ('s' <= (c) && (c) <= 'z')) # define TOUPPER(c) (ISLOWER(c) ? ((c) | 0x40) : (c)) #endif #define XOR(e, f) (((e) && !(f)) || (!(e) && (f))) int main () { int i; for (i = 0; i < 256; i++) if (XOR (islower (i), ISLOWER (i)) || toupper (i) != TOUPPER (i)) return 2; return 0; } _ACEOF if ac_fn_c_try_run "$LINENO"; then : else ac_cv_header_stdc=no fi rm -f core *.core core.conftest.* gmon.out bb.out conftest$ac_exeext \ conftest.$ac_objext conftest.beam conftest.$ac_ext fi fi fi { $as_echo "$as_me:${as_lineno-$LINENO}: result: $ac_cv_header_stdc" >&5 $as_echo "$ac_cv_header_stdc" >&6; } if test $ac_cv_header_stdc = yes; then $as_echo "#define STDC_HEADERS 1" >>confdefs.h fi # On IRIX 5.3, sys/types and inttypes.h are conflicting. for ac_header in sys/types.h sys/stat.h stdlib.h string.h memory.h strings.h \ inttypes.h stdint.h unistd.h do : as_ac_Header=`$as_echo "ac_cv_header_$ac_header" | $as_tr_sh` ac_fn_c_check_header_compile "$LINENO" "$ac_header" "$as_ac_Header" "$ac_includes_default " if eval test \"x\$"$as_ac_Header"\" = x"yes"; then : cat >>confdefs.h <<_ACEOF #define `$as_echo "HAVE_$ac_header" | $as_tr_cpp` 1 _ACEOF fi done ac_fn_c_check_header_mongrel "$LINENO" "minix/config.h" "ac_cv_header_minix_config_h" "$ac_includes_default" if test "x$ac_cv_header_minix_config_h" = xyes; then : MINIX=yes else MINIX= fi if test "$MINIX" = yes; then $as_echo "#define _POSIX_SOURCE 1" >>confdefs.h $as_echo "#define _POSIX_1_SOURCE 2" >>confdefs.h $as_echo "#define _MINIX 1" >>confdefs.h fi { $as_echo "$as_me:${as_lineno-$LINENO}: checking whether it is safe to define __EXTENSIONS__" >&5 $as_echo_n "checking whether it is safe to define __EXTENSIONS__... " >&6; } if ${ac_cv_safe_to_define___extensions__+:} false; then : $as_echo_n "(cached) " >&6 else cat confdefs.h - <<_ACEOF >conftest.$ac_ext /* end confdefs.h. */ # define __EXTENSIONS__ 1 $ac_includes_default int main () { ; return 0; } _ACEOF if ac_fn_c_try_compile "$LINENO"; then : ac_cv_safe_to_define___extensions__=yes else ac_cv_safe_to_define___extensions__=no fi rm -f core conftest.err conftest.$ac_objext conftest.$ac_ext fi { $as_echo "$as_me:${as_lineno-$LINENO}: result: $ac_cv_safe_to_define___extensions__" >&5 $as_echo "$ac_cv_safe_to_define___extensions__" >&6; } test $ac_cv_safe_to_define___extensions__ = yes && $as_echo "#define __EXTENSIONS__ 1" >>confdefs.h $as_echo "#define _ALL_SOURCE 1" >>confdefs.h $as_echo "#define _GNU_SOURCE 1" >>confdefs.h $as_echo "#define _POSIX_PTHREAD_SEMANTICS 1" >>confdefs.h $as_echo "#define _TANDEM_SOURCE 1" >>confdefs.h { { $as_echo "$as_me:${as_lineno-$LINENO}: checking whether $CC supports -diag-error 10006" >&5 $as_echo_n "checking whether $CC supports -diag-error 10006... " >&6; } if ${frr_cv_diagerror_+:} false; then : $as_echo_n "(cached) " >&6 else ac_ext=c ac_cpp='$CPP $CPPFLAGS' ac_compile='$CC -c $CFLAGS $CPPFLAGS conftest.$ac_ext >&5' ac_link='$CC -o conftest$ac_exeext $CFLAGS $CPPFLAGS $LDFLAGS conftest.$ac_ext $LIBS >&5' ac_compiler_gnu=$ac_cv_c_compiler_gnu ac_c_flag_save="$CFLAGS" CFLAGS="$CFLAGS -diag-error 10006" cat confdefs.h - <<_ACEOF >conftest.$ac_ext /* end confdefs.h. */ int main () { ; return 0; } _ACEOF if ac_fn_c_try_compile "$LINENO"; then : frr_cv_diagerror_=yes else frr_cv_diagerror_=no fi rm -f core conftest.err conftest.$ac_objext conftest.$ac_ext CFLAGS="$ac_c_flag_save" ac_ext=c ac_cpp='$CPP $CPPFLAGS' ac_compile='$CC -c $CFLAGS $CPPFLAGS conftest.$ac_ext >&5' ac_link='$CC -o conftest$ac_exeext $CFLAGS $CPPFLAGS $LDFLAGS conftest.$ac_ext $LIBS >&5' ac_compiler_gnu=$ac_cv_c_compiler_gnu fi { $as_echo "$as_me:${as_lineno-$LINENO}: result: $frr_cv_diagerror_" >&5 $as_echo "$frr_cv_diagerror_" >&6; } if test "${frr_cv_diagerror_}" = yes; then CFLAGS="$CFLAGS -diag-error 10006" else : fi } ac_cc="$CC" CC="${CC% -std=gnu99}" CC="${CC% -std=c99}" { { $as_echo "$as_me:${as_lineno-$LINENO}: checking whether $CC supports -std=gnu11" >&5 $as_echo_n "checking whether $CC supports -std=gnu11... " >&6; } if ${frr_cv_std_gnu+:} false; then : $as_echo_n "(cached) " >&6 else ac_ext=c ac_cpp='$CPP $CPPFLAGS' ac_compile='$CC -c $CFLAGS $CPPFLAGS conftest.$ac_ext >&5' ac_link='$CC -o conftest$ac_exeext $CFLAGS $CPPFLAGS $LDFLAGS conftest.$ac_ext $LIBS >&5' ac_compiler_gnu=$ac_cv_c_compiler_gnu ac_c_flag_save="$CFLAGS" CFLAGS="$CFLAGS -std=gnu11" cat confdefs.h - <<_ACEOF >conftest.$ac_ext /* end confdefs.h. */ int main () { ; return 0; } _ACEOF if ac_fn_c_try_compile "$LINENO"; then : frr_cv_std_gnu=yes else frr_cv_std_gnu=no fi rm -f core conftest.err conftest.$ac_objext conftest.$ac_ext CFLAGS="$ac_c_flag_save" ac_ext=c ac_cpp='$CPP $CPPFLAGS' ac_compile='$CC -c $CFLAGS $CPPFLAGS conftest.$ac_ext >&5' ac_link='$CC -o conftest$ac_exeext $CFLAGS $CPPFLAGS $LDFLAGS conftest.$ac_ext $LIBS >&5' ac_compiler_gnu=$ac_cv_c_compiler_gnu fi { $as_echo "$as_me:${as_lineno-$LINENO}: result: $frr_cv_std_gnu" >&5 $as_echo "$frr_cv_std_gnu" >&6; } if test "${frr_cv_std_gnu}" = yes; then CC="$CC -std=gnu11" else : CC="$ac_cc" fi } if test "x${enable_gcov}" = "xyes"; then if test "z$orig_cflags" = "z"; then { { $as_echo "$as_me:${as_lineno-$LINENO}: checking whether $CC supports -coverage" >&5 $as_echo_n "checking whether $CC supports -coverage... " >&6; } if ${frr_cv_coverage+:} false; then : $as_echo_n "(cached) " >&6 else ac_ext=c ac_cpp='$CPP $CPPFLAGS' ac_compile='$CC -c $CFLAGS $CPPFLAGS conftest.$ac_ext >&5' ac_link='$CC -o conftest$ac_exeext $CFLAGS $CPPFLAGS $LDFLAGS conftest.$ac_ext $LIBS >&5' ac_compiler_gnu=$ac_cv_c_compiler_gnu ac_c_flag_save="$CFLAGS" CFLAGS="$CFLAGS -coverage" cat confdefs.h - <<_ACEOF >conftest.$ac_ext /* end confdefs.h. */ int main () { ; return 0; } _ACEOF if ac_fn_c_try_compile "$LINENO"; then : frr_cv_coverage=yes else frr_cv_coverage=no fi rm -f core conftest.err conftest.$ac_objext conftest.$ac_ext CFLAGS="$ac_c_flag_save" ac_ext=c ac_cpp='$CPP $CPPFLAGS' ac_compile='$CC -c $CFLAGS $CPPFLAGS conftest.$ac_ext >&5' ac_link='$CC -o conftest$ac_exeext $CFLAGS $CPPFLAGS $LDFLAGS conftest.$ac_ext $LIBS >&5' ac_compiler_gnu=$ac_cv_c_compiler_gnu fi { $as_echo "$as_me:${as_lineno-$LINENO}: result: $frr_cv_coverage" >&5 $as_echo "$frr_cv_coverage" >&6; } if test "${frr_cv_coverage}" = yes; then CFLAGS="$CFLAGS -coverage" else : fi } { { $as_echo "$as_me:${as_lineno-$LINENO}: checking whether $CC supports -O0" >&5 $as_echo_n "checking whether $CC supports -O0... " >&6; } if ${frr_cv_O+:} false; then : $as_echo_n "(cached) " >&6 else ac_ext=c ac_cpp='$CPP $CPPFLAGS' ac_compile='$CC -c $CFLAGS $CPPFLAGS conftest.$ac_ext >&5' ac_link='$CC -o conftest$ac_exeext $CFLAGS $CPPFLAGS $LDFLAGS conftest.$ac_ext $LIBS >&5' ac_compiler_gnu=$ac_cv_c_compiler_gnu ac_c_flag_save="$CFLAGS" CFLAGS="$CFLAGS -O0" cat confdefs.h - <<_ACEOF >conftest.$ac_ext /* end confdefs.h. */ int main () { ; return 0; } _ACEOF if ac_fn_c_try_compile "$LINENO"; then : frr_cv_O=yes else frr_cv_O=no fi rm -f core conftest.err conftest.$ac_objext conftest.$ac_ext CFLAGS="$ac_c_flag_save" ac_ext=c ac_cpp='$CPP $CPPFLAGS' ac_compile='$CC -c $CFLAGS $CPPFLAGS conftest.$ac_ext >&5' ac_link='$CC -o conftest$ac_exeext $CFLAGS $CPPFLAGS $LDFLAGS conftest.$ac_ext $LIBS >&5' ac_compiler_gnu=$ac_cv_c_compiler_gnu fi { $as_echo "$as_me:${as_lineno-$LINENO}: result: $frr_cv_O" >&5 $as_echo "$frr_cv_O" >&6; } if test "${frr_cv_O}" = yes; then CFLAGS="$CFLAGS -O0" else : fi } fi LDFLAGS="${LDFLAGS} -lgcov" elif test "x${enable_dev_build}" = "xyes"; then $as_echo "#define DEV_BUILD 1" >>confdefs.h if test "z$orig_cflags" = "z"; then { { $as_echo "$as_me:${as_lineno-$LINENO}: checking whether $CC supports -g3" >&5 $as_echo_n "checking whether $CC supports -g3... " >&6; } if ${frr_cv_g+:} false; then : $as_echo_n "(cached) " >&6 else ac_ext=c ac_cpp='$CPP $CPPFLAGS' ac_compile='$CC -c $CFLAGS $CPPFLAGS conftest.$ac_ext >&5' ac_link='$CC -o conftest$ac_exeext $CFLAGS $CPPFLAGS $LDFLAGS conftest.$ac_ext $LIBS >&5' ac_compiler_gnu=$ac_cv_c_compiler_gnu ac_c_flag_save="$CFLAGS" CFLAGS="$CFLAGS -g3" cat confdefs.h - <<_ACEOF >conftest.$ac_ext /* end confdefs.h. */ int main () { ; return 0; } _ACEOF if ac_fn_c_try_compile "$LINENO"; then : frr_cv_g=yes else frr_cv_g=no fi rm -f core conftest.err conftest.$ac_objext conftest.$ac_ext CFLAGS="$ac_c_flag_save" ac_ext=c ac_cpp='$CPP $CPPFLAGS' ac_compile='$CC -c $CFLAGS $CPPFLAGS conftest.$ac_ext >&5' ac_link='$CC -o conftest$ac_exeext $CFLAGS $CPPFLAGS $LDFLAGS conftest.$ac_ext $LIBS >&5' ac_compiler_gnu=$ac_cv_c_compiler_gnu fi { $as_echo "$as_me:${as_lineno-$LINENO}: result: $frr_cv_g" >&5 $as_echo "$frr_cv_g" >&6; } if test "${frr_cv_g}" = yes; then CFLAGS="$CFLAGS -g3" else : fi } { { $as_echo "$as_me:${as_lineno-$LINENO}: checking whether $CC supports -O0" >&5 $as_echo_n "checking whether $CC supports -O0... " >&6; } if ${frr_cv_O+:} false; then : $as_echo_n "(cached) " >&6 else ac_ext=c ac_cpp='$CPP $CPPFLAGS' ac_compile='$CC -c $CFLAGS $CPPFLAGS conftest.$ac_ext >&5' ac_link='$CC -o conftest$ac_exeext $CFLAGS $CPPFLAGS $LDFLAGS conftest.$ac_ext $LIBS >&5' ac_compiler_gnu=$ac_cv_c_compiler_gnu ac_c_flag_save="$CFLAGS" CFLAGS="$CFLAGS -O0" cat confdefs.h - <<_ACEOF >conftest.$ac_ext /* end confdefs.h. */ int main () { ; return 0; } _ACEOF if ac_fn_c_try_compile "$LINENO"; then : frr_cv_O=yes else frr_cv_O=no fi rm -f core conftest.err conftest.$ac_objext conftest.$ac_ext CFLAGS="$ac_c_flag_save" ac_ext=c ac_cpp='$CPP $CPPFLAGS' ac_compile='$CC -c $CFLAGS $CPPFLAGS conftest.$ac_ext >&5' ac_link='$CC -o conftest$ac_exeext $CFLAGS $CPPFLAGS $LDFLAGS conftest.$ac_ext $LIBS >&5' ac_compiler_gnu=$ac_cv_c_compiler_gnu fi { $as_echo "$as_me:${as_lineno-$LINENO}: result: $frr_cv_O" >&5 $as_echo "$frr_cv_O" >&6; } if test "${frr_cv_O}" = yes; then CFLAGS="$CFLAGS -O0" else : fi } fi if test "x${enable_lua}" = "xyes"; then { $as_echo "$as_me:${as_lineno-$LINENO}: checking for a sed that does not truncate output" >&5 $as_echo_n "checking for a sed that does not truncate output... " >&6; } if ${ac_cv_path_SED+:} false; then : $as_echo_n "(cached) " >&6 else ac_script=s/aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa/bbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbb/ for ac_i in 1 2 3 4 5 6 7; do ac_script="$ac_script$as_nl$ac_script" done echo "$ac_script" 2>/dev/null | sed 99q >conftest.sed { ac_script=; unset ac_script;} if test -z "$SED"; then ac_path_SED_found=false # Loop through the user's path and test for each of PROGNAME-LIST as_save_IFS=$IFS; IFS=$PATH_SEPARATOR for as_dir in $PATH do IFS=$as_save_IFS test -z "$as_dir" && as_dir=. for ac_prog in sed gsed; do for ac_exec_ext in '' $ac_executable_extensions; do ac_path_SED="$as_dir/$ac_prog$ac_exec_ext" as_fn_executable_p "$ac_path_SED" || continue # Check for GNU ac_path_SED and select it if it is found. # Check for GNU $ac_path_SED case `"$ac_path_SED" --version 2>&1` in *GNU*) ac_cv_path_SED="$ac_path_SED" ac_path_SED_found=:;; *) ac_count=0 $as_echo_n 0123456789 >"conftest.in" while : do cat "conftest.in" "conftest.in" >"conftest.tmp" mv "conftest.tmp" "conftest.in" cp "conftest.in" "conftest.nl" $as_echo '' >> "conftest.nl" "$ac_path_SED" -f conftest.sed < "conftest.nl" >"conftest.out" 2>/dev/null || break diff "conftest.out" "conftest.nl" >/dev/null 2>&1 || break as_fn_arith $ac_count + 1 && ac_count=$as_val if test $ac_count -gt ${ac_path_SED_max-0}; then # Best one so far, save it but keep looking for a better one ac_cv_path_SED="$ac_path_SED" ac_path_SED_max=$ac_count fi # 10*(2^10) chars as input seems more than enough test $ac_count -gt 10 && break done rm -f conftest.in conftest.tmp conftest.nl conftest.out;; esac $ac_path_SED_found && break 3 done done done IFS=$as_save_IFS if test -z "$ac_cv_path_SED"; then as_fn_error $? "no acceptable sed could be found in \$PATH" "$LINENO" 5 fi else ac_cv_path_SED=$SED fi fi { $as_echo "$as_me:${as_lineno-$LINENO}: result: $ac_cv_path_SED" >&5 $as_echo "$ac_cv_path_SED" >&6; } SED="$ac_cv_path_SED" rm -f conftest.sed if test "x$LUA" != 'x'; then : { $as_echo "$as_me:${as_lineno-$LINENO}: checking if $LUA is a Lua interpreter" >&5 $as_echo_n "checking if $LUA is a Lua interpreter... " >&6; } _ax_lua_factorial=`$LUA 2>/dev/null -e ' -- a simple factorial function fact (n) if n == 0 then return 1 else return n * fact(n-1) end end print("fact(5) is " .. fact(5))'` if test "$_ax_lua_factorial" = 'fact(5) is 120'; then : { $as_echo "$as_me:${as_lineno-$LINENO}: result: yes" >&5 $as_echo "yes" >&6; } else { $as_echo "$as_me:${as_lineno-$LINENO}: result: no" >&5 $as_echo "no" >&6; } as_fn_error $? "not a Lua interpreter" "$LINENO" 5 fi _ax_check_text="whether $LUA version >= 5.3" { $as_echo "$as_me:${as_lineno-$LINENO}: checking $_ax_check_text" >&5 $as_echo_n "checking $_ax_check_text... " >&6; } _ax_lua_good_version=`$LUA -e ' -- a script to compare versions function verstr2num(verstr) local _, _, majorver, minorver = string.find(verstr, "^(%d+)%.(%d+)") if majorver and minorver then return tonumber(majorver) * 100 + tonumber(minorver) end end local minver = verstr2num("5.3") local _, _, trimver = string.find(_VERSION, "^Lua (.*)") local ver = verstr2num(trimver) local maxver = verstr2num("") or 1e9 if minver <= ver and ver < maxver then print("yes") else print("no") end'` if test "x$_ax_lua_good_version" = "xyes"; then : { $as_echo "$as_me:${as_lineno-$LINENO}: result: yes" >&5 $as_echo "yes" >&6; } else { $as_echo "$as_me:${as_lineno-$LINENO}: result: no" >&5 $as_echo "no" >&6; } as_fn_error $? "version is out of range for specified LUA" "$LINENO" 5 fi ax_display_LUA=$LUA else _ax_check_text="for a Lua interpreter with version >= 5.3" { $as_echo "$as_me:${as_lineno-$LINENO}: checking $_ax_check_text" >&5 $as_echo_n "checking $_ax_check_text... " >&6; } if ${ax_cv_pathless_LUA+:} false; then : $as_echo_n "(cached) " >&6 else for ax_cv_pathless_LUA in lua lua5.3 lua53 lua5.2 lua52 lua5.1 lua51 lua50 none; do test "x$ax_cv_pathless_LUA" = 'xnone' && break _ax_lua_factorial=`$ax_cv_pathless_LUA 2>/dev/null -e ' -- a simple factorial function fact (n) if n == 0 then return 1 else return n * fact(n-1) end end print("fact(5) is " .. fact(5))'` if test "$_ax_lua_factorial" = 'fact(5) is 120'; then : else continue fi _ax_lua_good_version=`$ax_cv_pathless_LUA -e ' -- a script to compare versions function verstr2num(verstr) local _, _, majorver, minorver = string.find(verstr, "^(%d+)%.(%d+)") if majorver and minorver then return tonumber(majorver) * 100 + tonumber(minorver) end end local minver = verstr2num("5.3") local _, _, trimver = string.find(_VERSION, "^Lua (.*)") local ver = verstr2num(trimver) local maxver = verstr2num("") or 1e9 if minver <= ver and ver < maxver then print("yes") else print("no") end'` if test "x$_ax_lua_good_version" = "xyes"; then : break fi done fi { $as_echo "$as_me:${as_lineno-$LINENO}: result: $ax_cv_pathless_LUA" >&5 $as_echo "$ax_cv_pathless_LUA" >&6; } if test "x$ax_cv_pathless_LUA" = 'xnone'; then : LUA=':' else # Extract the first word of "$ax_cv_pathless_LUA", so it can be a program name with args. set dummy $ax_cv_pathless_LUA; ac_word=$2 { $as_echo "$as_me:${as_lineno-$LINENO}: checking for $ac_word" >&5 $as_echo_n "checking for $ac_word... " >&6; } if ${ac_cv_path_LUA+:} false; then : $as_echo_n "(cached) " >&6 else case $LUA in [\\/]* | ?:[\\/]*) ac_cv_path_LUA="$LUA" # Let the user override the test with a path. ;; *) as_save_IFS=$IFS; IFS=$PATH_SEPARATOR for as_dir in $PATH do IFS=$as_save_IFS test -z "$as_dir" && as_dir=. for ac_exec_ext in '' $ac_executable_extensions; do if as_fn_executable_p "$as_dir/$ac_word$ac_exec_ext"; then ac_cv_path_LUA="$as_dir/$ac_word$ac_exec_ext" $as_echo "$as_me:${as_lineno-$LINENO}: found $as_dir/$ac_word$ac_exec_ext" >&5 break 2 fi done done IFS=$as_save_IFS ;; esac fi LUA=$ac_cv_path_LUA if test -n "$LUA"; then { $as_echo "$as_me:${as_lineno-$LINENO}: result: $LUA" >&5 $as_echo "$LUA" >&6; } else { $as_echo "$as_me:${as_lineno-$LINENO}: result: no" >&5 $as_echo "no" >&6; } fi fi ax_display_LUA=$ax_cv_pathless_LUA fi if test "x$LUA" = 'x:'; then : as_fn_error $? "cannot find suitable Lua interpreter" "$LINENO" 5 else { $as_echo "$as_me:${as_lineno-$LINENO}: checking for $ax_display_LUA version" >&5 $as_echo_n "checking for $ax_display_LUA version... " >&6; } if ${ax_cv_lua_version+:} false; then : $as_echo_n "(cached) " >&6 else ax_cv_lua_version=`$LUA -e ' -- return a version number in X.Y format local _, _, ver = string.find(_VERSION, "^Lua (%d+%.%d+)") print(ver)'` fi { $as_echo "$as_me:${as_lineno-$LINENO}: result: $ax_cv_lua_version" >&5 $as_echo "$ax_cv_lua_version" >&6; } if test "x$ax_cv_lua_version" = 'x'; then : as_fn_error $? "invalid Lua version number" "$LINENO" 5 fi LUA_VERSION=$ax_cv_lua_version LUA_SHORT_VERSION=`echo "$LUA_VERSION" | $SED 's|\.||'` { $as_echo "$as_me:${as_lineno-$LINENO}: checking for $ax_display_LUA platform" >&5 $as_echo_n "checking for $ax_display_LUA platform... " >&6; } if ${ax_cv_lua_platform+:} false; then : $as_echo_n "(cached) " >&6 else ax_cv_lua_platform=`$LUA -e 'print("unknown")'` fi { $as_echo "$as_me:${as_lineno-$LINENO}: result: $ax_cv_lua_platform" >&5 $as_echo "$ax_cv_lua_platform" >&6; } LUA_PLATFORM=$ax_cv_lua_platform LUA_PREFIX='${prefix}' LUA_EXEC_PREFIX='${exec_prefix}' { $as_echo "$as_me:${as_lineno-$LINENO}: checking for $ax_display_LUA script directory" >&5 $as_echo_n "checking for $ax_display_LUA script directory... " >&6; } if ${ax_cv_lua_luadir+:} false; then : $as_echo_n "(cached) " >&6 else if test "x$prefix" = 'xNONE'; then : ax_lua_prefix=$ac_default_prefix else ax_lua_prefix=$prefix fi ax_cv_lua_luadir="$LUA_PREFIX/share/lua/$LUA_VERSION" ax_lua_prefixed_path=`$LUA -e ' -- get the path based on search type local searchtype = "script" local paths = "" if searchtype == "script" then paths = (package and package.path) or LUA_PATH elseif searchtype == "module" then paths = (package and package.cpath) or LUA_CPATH end -- search for the prefix local prefix = "'$ax_lua_prefix'" local minpath = "" local mindepth = 1e9 string.gsub(paths, "([^;]+)", function (path) path = string.gsub(path, "%?.*$", "") path = string.gsub(path, "/[^/]*$", "") if string.find(path, prefix) then local depth = string.len(string.gsub(path, "[^/]", "")) if depth < mindepth then minpath = path mindepth = depth end end end) print(minpath)'` if test "x$ax_lua_prefixed_path" != 'x'; then : _ax_strip_prefix=`echo "$ax_lua_prefix" | $SED 's|.|.|g'` ax_cv_lua_luadir=`echo "$ax_lua_prefixed_path" | \ $SED "s|^$_ax_strip_prefix|$LUA_PREFIX|"` fi fi { $as_echo "$as_me:${as_lineno-$LINENO}: result: $ax_cv_lua_luadir" >&5 $as_echo "$ax_cv_lua_luadir" >&6; } luadir=$ax_cv_lua_luadir pkgluadir=\${luadir}/$PACKAGE { $as_echo "$as_me:${as_lineno-$LINENO}: checking for $ax_display_LUA module directory" >&5 $as_echo_n "checking for $ax_display_LUA module directory... " >&6; } if ${ax_cv_lua_luaexecdir+:} false; then : $as_echo_n "(cached) " >&6 else if test "x$exec_prefix" = 'xNONE'; then : ax_lua_exec_prefix=$ax_lua_prefix else ax_lua_exec_prefix=$exec_prefix fi ax_cv_lua_luaexecdir="$LUA_EXEC_PREFIX/lib/lua/$LUA_VERSION" ax_lua_prefixed_path=`$LUA -e ' -- get the path based on search type local searchtype = "module" local paths = "" if searchtype == "script" then paths = (package and package.path) or LUA_PATH elseif searchtype == "module" then paths = (package and package.cpath) or LUA_CPATH end -- search for the prefix local prefix = "'$ax_lua_exec_prefix'" local minpath = "" local mindepth = 1e9 string.gsub(paths, "([^;]+)", function (path) path = string.gsub(path, "%?.*$", "") path = string.gsub(path, "/[^/]*$", "") if string.find(path, prefix) then local depth = string.len(string.gsub(path, "[^/]", "")) if depth < mindepth then minpath = path mindepth = depth end end end) print(minpath)'` if test "x$ax_lua_prefixed_path" != 'x'; then : _ax_strip_prefix=`echo "$ax_lua_exec_prefix" | $SED 's|.|.|g'` ax_cv_lua_luaexecdir=`echo "$ax_lua_prefixed_path" | \ $SED "s|^$_ax_strip_prefix|$LUA_EXEC_PREFIX|"` fi fi { $as_echo "$as_me:${as_lineno-$LINENO}: result: $ax_cv_lua_luaexecdir" >&5 $as_echo "$ax_cv_lua_luaexecdir" >&6; } luaexecdir=$ax_cv_lua_luaexecdir pkgluaexecdir=\${luaexecdir}/$PACKAGE fi { $as_echo "$as_me:${as_lineno-$LINENO}: checking if LUA_VERSION is defined" >&5 $as_echo_n "checking if LUA_VERSION is defined... " >&6; } if test "x$LUA_VERSION" != 'x'; then : { $as_echo "$as_me:${as_lineno-$LINENO}: result: yes" >&5 $as_echo "yes" >&6; } else { $as_echo "$as_me:${as_lineno-$LINENO}: result: no" >&5 $as_echo "no" >&6; } as_fn_error $? "cannot check Lua headers without knowing LUA_VERSION" "$LINENO" 5 fi LUA_SHORT_VERSION=`echo "$LUA_VERSION" | $SED 's|\.||'` _ax_lua_saved_cppflags=$CPPFLAGS CPPFLAGS="$CPPFLAGS $LUA_INCLUDE" for ac_header in lua.h lualib.h lauxlib.h luaconf.h do : as_ac_Header=`$as_echo "ac_cv_header_$ac_header" | $as_tr_sh` ac_fn_c_check_header_mongrel "$LINENO" "$ac_header" "$as_ac_Header" "$ac_includes_default" if eval test \"x\$"$as_ac_Header"\" = x"yes"; then : cat >>confdefs.h <<_ACEOF #define `$as_echo "HAVE_$ac_header" | $as_tr_cpp` 1 _ACEOF fi done CPPFLAGS=$_ax_lua_saved_cppflags if test "x$LUA_INCLUDE" = 'x' && test "x$ac_cv_header_lua_h" != 'xyes'; then : for _ax_include_path in /usr/include/lua$LUA_VERSION \ /usr/include/lua-$LUA_VERSION \ /usr/include/lua/$LUA_VERSION \ /usr/include/lua$LUA_SHORT_VERSION \ /usr/local/include/lua$LUA_VERSION \ /usr/local/include/lua-$LUA_VERSION \ /usr/local/include/lua/$LUA_VERSION \ /usr/local/include/lua$LUA_SHORT_VERSION \ ; do test ! -d "$_ax_include_path" && continue { $as_echo "$as_me:${as_lineno-$LINENO}: checking for Lua headers in" >&5 $as_echo_n "checking for Lua headers in... " >&6; } { $as_echo "$as_me:${as_lineno-$LINENO}: result: $_ax_include_path" >&5 $as_echo "$_ax_include_path" >&6; } { ac_cv_header_lua_h=; unset ac_cv_header_lua_h;} { ac_cv_header_lualib_h=; unset ac_cv_header_lualib_h;} { ac_cv_header_lauxlib_h=; unset ac_cv_header_lauxlib_h;} { ac_cv_header_luaconf_h=; unset ac_cv_header_luaconf_h;} _ax_lua_saved_cppflags=$CPPFLAGS CPPFLAGS="$CPPFLAGS -I$_ax_include_path" for ac_header in lua.h lualib.h lauxlib.h luaconf.h do : as_ac_Header=`$as_echo "ac_cv_header_$ac_header" | $as_tr_sh` ac_fn_c_check_header_mongrel "$LINENO" "$ac_header" "$as_ac_Header" "$ac_includes_default" if eval test \"x\$"$as_ac_Header"\" = x"yes"; then : cat >>confdefs.h <<_ACEOF #define `$as_echo "HAVE_$ac_header" | $as_tr_cpp` 1 _ACEOF fi done CPPFLAGS=$_ax_lua_saved_cppflags if test "x$ac_cv_header_lua_h" = 'xyes'; then : LUA_INCLUDE="-I$_ax_include_path" break fi done fi if test "x$ac_cv_header_lua_h" = 'xyes'; then : if test "x$cross_compiling" != 'xyes'; then : { $as_echo "$as_me:${as_lineno-$LINENO}: checking for Lua header version" >&5 $as_echo_n "checking for Lua header version... " >&6; } if ${ax_cv_lua_header_version+:} false; then : $as_echo_n "(cached) " >&6 else _ax_lua_saved_cppflags=$CPPFLAGS CPPFLAGS="$CPPFLAGS $LUA_INCLUDE" if test "$cross_compiling" = yes; then : { { $as_echo "$as_me:${as_lineno-$LINENO}: error: in \`$ac_pwd':" >&5 $as_echo "$as_me: error: in \`$ac_pwd':" >&2;} as_fn_error $? "cannot run test program while cross compiling See \`config.log' for more details" "$LINENO" 5; } else cat confdefs.h - <<_ACEOF >conftest.$ac_ext /* end confdefs.h. */ #include #include #include int main(int argc, char ** argv) { if(argc > 1) printf("%s", LUA_VERSION); exit(EXIT_SUCCESS); } _ACEOF if ac_fn_c_try_run "$LINENO"; then : ax_cv_lua_header_version=`./conftest$EXEEXT p | \ $SED -n "s|^Lua \([0-9]\{1,\}\.[0-9]\{1,\}\).\{0,\}|\1|p"` else ax_cv_lua_header_version='unknown' fi rm -f core *.core core.conftest.* gmon.out bb.out conftest$ac_exeext \ conftest.$ac_objext conftest.beam conftest.$ac_ext fi CPPFLAGS=$_ax_lua_saved_cppflags fi { $as_echo "$as_me:${as_lineno-$LINENO}: result: $ax_cv_lua_header_version" >&5 $as_echo "$ax_cv_lua_header_version" >&6; } { $as_echo "$as_me:${as_lineno-$LINENO}: checking if Lua header version matches $LUA_VERSION" >&5 $as_echo_n "checking if Lua header version matches $LUA_VERSION... " >&6; } if test "x$ax_cv_lua_header_version" = "x$LUA_VERSION"; then : { $as_echo "$as_me:${as_lineno-$LINENO}: result: yes" >&5 $as_echo "yes" >&6; } ax_header_version_match='yes' else { $as_echo "$as_me:${as_lineno-$LINENO}: result: no" >&5 $as_echo "no" >&6; } ax_header_version_match='no' fi else { $as_echo "$as_me:${as_lineno-$LINENO}: WARNING: cross compiling so assuming header version number matches" >&5 $as_echo "$as_me: WARNING: cross compiling so assuming header version number matches" >&2;} ax_header_version_match='yes' fi fi if test "x$ax_header_version_match" != 'xyes' && test "x$LUA_INCLUDE" != 'x'; then : as_fn_error $? "cannot find headers for specified LUA_INCLUDE" "$LINENO" 5 fi if test "x$ax_header_version_match" = 'xyes'; then : else as_fn_error $? "cannot find Lua includes" "$LINENO" 5 fi { $as_echo "$as_me:${as_lineno-$LINENO}: checking if LUA_VERSION is defined" >&5 $as_echo_n "checking if LUA_VERSION is defined... " >&6; } if test "x$LUA_VERSION" != 'x'; then : { $as_echo "$as_me:${as_lineno-$LINENO}: result: yes" >&5 $as_echo "yes" >&6; } else { $as_echo "$as_me:${as_lineno-$LINENO}: result: no" >&5 $as_echo "no" >&6; } as_fn_error $? "cannot check Lua libs without knowing LUA_VERSION" "$LINENO" 5 fi if test "x$LUA_LIB" != 'x'; then : _ax_lua_saved_libs=$LIBS LIBS="$LIBS $LUA_LIB" { $as_echo "$as_me:${as_lineno-$LINENO}: checking for library containing lua_load" >&5 $as_echo_n "checking for library containing lua_load... " >&6; } if ${ac_cv_search_lua_load+:} false; then : $as_echo_n "(cached) " >&6 else ac_func_search_save_LIBS=$LIBS cat confdefs.h - <<_ACEOF >conftest.$ac_ext /* end confdefs.h. */ /* Override any GCC internal prototype to avoid an error. Use char because int might match the return type of a GCC builtin and then its argument prototype would still apply. */ #ifdef __cplusplus extern "C" #endif char lua_load (); int main () { return lua_load (); ; return 0; } _ACEOF for ac_lib in '' ; do if test -z "$ac_lib"; then ac_res="none required" else ac_res=-l$ac_lib LIBS="-l$ac_lib $ac_func_search_save_LIBS" fi if ac_fn_c_try_link "$LINENO"; then : ac_cv_search_lua_load=$ac_res fi rm -f core conftest.err conftest.$ac_objext \ conftest$ac_exeext if ${ac_cv_search_lua_load+:} false; then : break fi done if ${ac_cv_search_lua_load+:} false; then : else ac_cv_search_lua_load=no fi rm conftest.$ac_ext LIBS=$ac_func_search_save_LIBS fi { $as_echo "$as_me:${as_lineno-$LINENO}: result: $ac_cv_search_lua_load" >&5 $as_echo "$ac_cv_search_lua_load" >&6; } ac_res=$ac_cv_search_lua_load if test "$ac_res" != no; then : test "$ac_res" = "none required" || LIBS="$ac_res $LIBS" _ax_found_lua_libs='yes' else _ax_found_lua_libs='no' fi LIBS=$_ax_lua_saved_libs if test "x$_ax_found_lua_libs" != 'xyes'; then : as_fn_error $? "cannot find libs for specified LUA_LIB" "$LINENO" 5 fi else _ax_lua_extra_libs='' _ax_lua_saved_libs=$LIBS LIBS="$LIBS $LUA_LIB" { $as_echo "$as_me:${as_lineno-$LINENO}: checking for library containing exp" >&5 $as_echo_n "checking for library containing exp... " >&6; } if ${ac_cv_search_exp+:} false; then : $as_echo_n "(cached) " >&6 else ac_func_search_save_LIBS=$LIBS cat confdefs.h - <<_ACEOF >conftest.$ac_ext /* end confdefs.h. */ /* Override any GCC internal prototype to avoid an error. Use char because int might match the return type of a GCC builtin and then its argument prototype would still apply. */ #ifdef __cplusplus extern "C" #endif char exp (); int main () { return exp (); ; return 0; } _ACEOF for ac_lib in '' m; do if test -z "$ac_lib"; then ac_res="none required" else ac_res=-l$ac_lib LIBS="-l$ac_lib $ac_func_search_save_LIBS" fi if ac_fn_c_try_link "$LINENO"; then : ac_cv_search_exp=$ac_res fi rm -f core conftest.err conftest.$ac_objext \ conftest$ac_exeext if ${ac_cv_search_exp+:} false; then : break fi done if ${ac_cv_search_exp+:} false; then : else ac_cv_search_exp=no fi rm conftest.$ac_ext LIBS=$ac_func_search_save_LIBS fi { $as_echo "$as_me:${as_lineno-$LINENO}: result: $ac_cv_search_exp" >&5 $as_echo "$ac_cv_search_exp" >&6; } ac_res=$ac_cv_search_exp if test "$ac_res" != no; then : test "$ac_res" = "none required" || LIBS="$ac_res $LIBS" fi { $as_echo "$as_me:${as_lineno-$LINENO}: checking for library containing dlopen" >&5 $as_echo_n "checking for library containing dlopen... " >&6; } if ${ac_cv_search_dlopen+:} false; then : $as_echo_n "(cached) " >&6 else ac_func_search_save_LIBS=$LIBS cat confdefs.h - <<_ACEOF >conftest.$ac_ext /* end confdefs.h. */ /* Override any GCC internal prototype to avoid an error. Use char because int might match the return type of a GCC builtin and then its argument prototype would still apply. */ #ifdef __cplusplus extern "C" #endif char dlopen (); int main () { return dlopen (); ; return 0; } _ACEOF for ac_lib in '' dl; do if test -z "$ac_lib"; then ac_res="none required" else ac_res=-l$ac_lib LIBS="-l$ac_lib $ac_func_search_save_LIBS" fi if ac_fn_c_try_link "$LINENO"; then : ac_cv_search_dlopen=$ac_res fi rm -f core conftest.err conftest.$ac_objext \ conftest$ac_exeext if ${ac_cv_search_dlopen+:} false; then : break fi done if ${ac_cv_search_dlopen+:} false; then : else ac_cv_search_dlopen=no fi rm conftest.$ac_ext LIBS=$ac_func_search_save_LIBS fi { $as_echo "$as_me:${as_lineno-$LINENO}: result: $ac_cv_search_dlopen" >&5 $as_echo "$ac_cv_search_dlopen" >&6; } ac_res=$ac_cv_search_dlopen if test "$ac_res" != no; then : test "$ac_res" = "none required" || LIBS="$ac_res $LIBS" fi LIBS=$_ax_lua_saved_libs if test "x$ac_cv_search_exp" != 'xno' && test "x$ac_cv_search_exp" != 'xnone required'; then : _ax_lua_extra_libs="$_ax_lua_extra_libs $ac_cv_search_exp" fi if test "x$ac_cv_search_dlopen" != 'xno' && test "x$ac_cv_search_dlopen" != 'xnone required'; then : _ax_lua_extra_libs="$_ax_lua_extra_libs $ac_cv_search_dlopen" fi _ax_lua_saved_libs=$LIBS LIBS="$LIBS $LUA_LIB" { $as_echo "$as_me:${as_lineno-$LINENO}: checking for library containing lua_load" >&5 $as_echo_n "checking for library containing lua_load... " >&6; } if ${ac_cv_search_lua_load+:} false; then : $as_echo_n "(cached) " >&6 else ac_func_search_save_LIBS=$LIBS cat confdefs.h - <<_ACEOF >conftest.$ac_ext /* end confdefs.h. */ /* Override any GCC internal prototype to avoid an error. Use char because int might match the return type of a GCC builtin and then its argument prototype would still apply. */ #ifdef __cplusplus extern "C" #endif char lua_load (); int main () { return lua_load (); ; return 0; } _ACEOF for ac_lib in '' lua$LUA_VERSION \ lua$LUA_SHORT_VERSION \ lua-$LUA_VERSION \ lua-$LUA_SHORT_VERSION \ lua \ ; do if test -z "$ac_lib"; then ac_res="none required" else ac_res=-l$ac_lib LIBS="-l$ac_lib $_ax_lua_extra_libs $ac_func_search_save_LIBS" fi if ac_fn_c_try_link "$LINENO"; then : ac_cv_search_lua_load=$ac_res fi rm -f core conftest.err conftest.$ac_objext \ conftest$ac_exeext if ${ac_cv_search_lua_load+:} false; then : break fi done if ${ac_cv_search_lua_load+:} false; then : else ac_cv_search_lua_load=no fi rm conftest.$ac_ext LIBS=$ac_func_search_save_LIBS fi { $as_echo "$as_me:${as_lineno-$LINENO}: result: $ac_cv_search_lua_load" >&5 $as_echo "$ac_cv_search_lua_load" >&6; } ac_res=$ac_cv_search_lua_load if test "$ac_res" != no; then : test "$ac_res" = "none required" || LIBS="$ac_res $LIBS" _ax_found_lua_libs='yes' else _ax_found_lua_libs='no' fi LIBS=$_ax_lua_saved_libs if test "x$ac_cv_search_lua_load" != 'xno' && test "x$ac_cv_search_lua_load" != 'xnone required'; then : LUA_LIB="$ac_cv_search_lua_load $_ax_lua_extra_libs" fi fi if test "x$_ax_found_lua_libs" = 'xyes'; then : $as_echo "#define HAVE_LUA 1" >>confdefs.h LIBS="$LIBS $LUA_LIB" else as_fn_error $? "cannot find Lua libs" "$LINENO" 5 fi fi else if test "x${enable_lua}" = "xyes"; then as_fn_error $? "Lua is not meant to be built/used outside of development at this time" "$LINENO" 5 fi if test "z$orig_cflags" = "z"; then { { $as_echo "$as_me:${as_lineno-$LINENO}: checking whether $CC supports -g" >&5 $as_echo_n "checking whether $CC supports -g... " >&6; } if ${frr_cv_g+:} false; then : $as_echo_n "(cached) " >&6 else ac_ext=c ac_cpp='$CPP $CPPFLAGS' ac_compile='$CC -c $CFLAGS $CPPFLAGS conftest.$ac_ext >&5' ac_link='$CC -o conftest$ac_exeext $CFLAGS $CPPFLAGS $LDFLAGS conftest.$ac_ext $LIBS >&5' ac_compiler_gnu=$ac_cv_c_compiler_gnu ac_c_flag_save="$CFLAGS" CFLAGS="$CFLAGS -g" cat confdefs.h - <<_ACEOF >conftest.$ac_ext /* end confdefs.h. */ int main () { ; return 0; } _ACEOF if ac_fn_c_try_compile "$LINENO"; then : frr_cv_g=yes else frr_cv_g=no fi rm -f core conftest.err conftest.$ac_objext conftest.$ac_ext CFLAGS="$ac_c_flag_save" ac_ext=c ac_cpp='$CPP $CPPFLAGS' ac_compile='$CC -c $CFLAGS $CPPFLAGS conftest.$ac_ext >&5' ac_link='$CC -o conftest$ac_exeext $CFLAGS $CPPFLAGS $LDFLAGS conftest.$ac_ext $LIBS >&5' ac_compiler_gnu=$ac_cv_c_compiler_gnu fi { $as_echo "$as_me:${as_lineno-$LINENO}: result: $frr_cv_g" >&5 $as_echo "$frr_cv_g" >&6; } if test "${frr_cv_g}" = yes; then CFLAGS="$CFLAGS -g" else : fi } { { $as_echo "$as_me:${as_lineno-$LINENO}: checking whether $CC supports -O2" >&5 $as_echo_n "checking whether $CC supports -O2... " >&6; } if ${frr_cv_O+:} false; then : $as_echo_n "(cached) " >&6 else ac_ext=c ac_cpp='$CPP $CPPFLAGS' ac_compile='$CC -c $CFLAGS $CPPFLAGS conftest.$ac_ext >&5' ac_link='$CC -o conftest$ac_exeext $CFLAGS $CPPFLAGS $LDFLAGS conftest.$ac_ext $LIBS >&5' ac_compiler_gnu=$ac_cv_c_compiler_gnu ac_c_flag_save="$CFLAGS" CFLAGS="$CFLAGS -O2" cat confdefs.h - <<_ACEOF >conftest.$ac_ext /* end confdefs.h. */ int main () { ; return 0; } _ACEOF if ac_fn_c_try_compile "$LINENO"; then : frr_cv_O=yes else frr_cv_O=no fi rm -f core conftest.err conftest.$ac_objext conftest.$ac_ext CFLAGS="$ac_c_flag_save" ac_ext=c ac_cpp='$CPP $CPPFLAGS' ac_compile='$CC -c $CFLAGS $CPPFLAGS conftest.$ac_ext >&5' ac_link='$CC -o conftest$ac_exeext $CFLAGS $CPPFLAGS $LDFLAGS conftest.$ac_ext $LIBS >&5' ac_compiler_gnu=$ac_cv_c_compiler_gnu fi { $as_echo "$as_me:${as_lineno-$LINENO}: result: $frr_cv_O" >&5 $as_echo "$frr_cv_O" >&6; } if test "${frr_cv_O}" = yes; then CFLAGS="$CFLAGS -O2" else : fi } fi fi if test "x$enable_dev_build" = "xyes"; then DEV_BUILD_TRUE= DEV_BUILD_FALSE='#' else DEV_BUILD_TRUE='#' DEV_BUILD_FALSE= fi { { $as_echo "$as_me:${as_lineno-$LINENO}: checking whether $CC supports -fno-omit-frame-pointer" >&5 $as_echo_n "checking whether $CC supports -fno-omit-frame-pointer... " >&6; } if ${frr_cv_fnoomitframepointer+:} false; then : $as_echo_n "(cached) " >&6 else ac_ext=c ac_cpp='$CPP $CPPFLAGS' ac_compile='$CC -c $CFLAGS $CPPFLAGS conftest.$ac_ext >&5' ac_link='$CC -o conftest$ac_exeext $CFLAGS $CPPFLAGS $LDFLAGS conftest.$ac_ext $LIBS >&5' ac_compiler_gnu=$ac_cv_c_compiler_gnu ac_c_flag_save="$CFLAGS" CFLAGS="$CFLAGS -fno-omit-frame-pointer" cat confdefs.h - <<_ACEOF >conftest.$ac_ext /* end confdefs.h. */ int main () { ; return 0; } _ACEOF if ac_fn_c_try_compile "$LINENO"; then : frr_cv_fnoomitframepointer=yes else frr_cv_fnoomitframepointer=no fi rm -f core conftest.err conftest.$ac_objext conftest.$ac_ext CFLAGS="$ac_c_flag_save" ac_ext=c ac_cpp='$CPP $CPPFLAGS' ac_compile='$CC -c $CFLAGS $CPPFLAGS conftest.$ac_ext >&5' ac_link='$CC -o conftest$ac_exeext $CFLAGS $CPPFLAGS $LDFLAGS conftest.$ac_ext $LIBS >&5' ac_compiler_gnu=$ac_cv_c_compiler_gnu fi { $as_echo "$as_me:${as_lineno-$LINENO}: result: $frr_cv_fnoomitframepointer" >&5 $as_echo "$frr_cv_fnoomitframepointer" >&6; } if test "${frr_cv_fnoomitframepointer}" = yes; then CFLAGS="$CFLAGS -fno-omit-frame-pointer" else : fi } { { $as_echo "$as_me:${as_lineno-$LINENO}: checking whether $CC supports -funwind-tables" >&5 $as_echo_n "checking whether $CC supports -funwind-tables... " >&6; } if ${frr_cv_funwindtables+:} false; then : $as_echo_n "(cached) " >&6 else ac_ext=c ac_cpp='$CPP $CPPFLAGS' ac_compile='$CC -c $CFLAGS $CPPFLAGS conftest.$ac_ext >&5' ac_link='$CC -o conftest$ac_exeext $CFLAGS $CPPFLAGS $LDFLAGS conftest.$ac_ext $LIBS >&5' ac_compiler_gnu=$ac_cv_c_compiler_gnu ac_c_flag_save="$CFLAGS" CFLAGS="$CFLAGS -funwind-tables" cat confdefs.h - <<_ACEOF >conftest.$ac_ext /* end confdefs.h. */ int main () { ; return 0; } _ACEOF if ac_fn_c_try_compile "$LINENO"; then : frr_cv_funwindtables=yes else frr_cv_funwindtables=no fi rm -f core conftest.err conftest.$ac_objext conftest.$ac_ext CFLAGS="$ac_c_flag_save" ac_ext=c ac_cpp='$CPP $CPPFLAGS' ac_compile='$CC -c $CFLAGS $CPPFLAGS conftest.$ac_ext >&5' ac_link='$CC -o conftest$ac_exeext $CFLAGS $CPPFLAGS $LDFLAGS conftest.$ac_ext $LIBS >&5' ac_compiler_gnu=$ac_cv_c_compiler_gnu fi { $as_echo "$as_me:${as_lineno-$LINENO}: result: $frr_cv_funwindtables" >&5 $as_echo "$frr_cv_funwindtables" >&6; } if test "${frr_cv_funwindtables}" = yes; then CFLAGS="$CFLAGS -funwind-tables" else : fi } { { $as_echo "$as_me:${as_lineno-$LINENO}: checking whether $CC supports -Wall" >&5 $as_echo_n "checking whether $CC supports -Wall... " >&6; } if ${frr_cv_Wall+:} false; then : $as_echo_n "(cached) " >&6 else ac_ext=c ac_cpp='$CPP $CPPFLAGS' ac_compile='$CC -c $CFLAGS $CPPFLAGS conftest.$ac_ext >&5' ac_link='$CC -o conftest$ac_exeext $CFLAGS $CPPFLAGS $LDFLAGS conftest.$ac_ext $LIBS >&5' ac_compiler_gnu=$ac_cv_c_compiler_gnu ac_c_flag_save="$CFLAGS" CFLAGS="$CFLAGS -Wall" cat confdefs.h - <<_ACEOF >conftest.$ac_ext /* end confdefs.h. */ int main () { ; return 0; } _ACEOF if ac_fn_c_try_compile "$LINENO"; then : frr_cv_Wall=yes else frr_cv_Wall=no fi rm -f core conftest.err conftest.$ac_objext conftest.$ac_ext CFLAGS="$ac_c_flag_save" ac_ext=c ac_cpp='$CPP $CPPFLAGS' ac_compile='$CC -c $CFLAGS $CPPFLAGS conftest.$ac_ext >&5' ac_link='$CC -o conftest$ac_exeext $CFLAGS $CPPFLAGS $LDFLAGS conftest.$ac_ext $LIBS >&5' ac_compiler_gnu=$ac_cv_c_compiler_gnu fi { $as_echo "$as_me:${as_lineno-$LINENO}: result: $frr_cv_Wall" >&5 $as_echo "$frr_cv_Wall" >&6; } if test "${frr_cv_Wall}" = yes; then CFLAGS="$CFLAGS -Wall" else : fi } { { $as_echo "$as_me:${as_lineno-$LINENO}: checking whether $CC supports -Wextra" >&5 $as_echo_n "checking whether $CC supports -Wextra... " >&6; } if ${frr_cv_Wextra+:} false; then : $as_echo_n "(cached) " >&6 else ac_ext=c ac_cpp='$CPP $CPPFLAGS' ac_compile='$CC -c $CFLAGS $CPPFLAGS conftest.$ac_ext >&5' ac_link='$CC -o conftest$ac_exeext $CFLAGS $CPPFLAGS $LDFLAGS conftest.$ac_ext $LIBS >&5' ac_compiler_gnu=$ac_cv_c_compiler_gnu ac_c_flag_save="$CFLAGS" CFLAGS="$CFLAGS -Wextra" cat confdefs.h - <<_ACEOF >conftest.$ac_ext /* end confdefs.h. */ int main () { ; return 0; } _ACEOF if ac_fn_c_try_compile "$LINENO"; then : frr_cv_Wextra=yes else frr_cv_Wextra=no fi rm -f core conftest.err conftest.$ac_objext conftest.$ac_ext CFLAGS="$ac_c_flag_save" ac_ext=c ac_cpp='$CPP $CPPFLAGS' ac_compile='$CC -c $CFLAGS $CPPFLAGS conftest.$ac_ext >&5' ac_link='$CC -o conftest$ac_exeext $CFLAGS $CPPFLAGS $LDFLAGS conftest.$ac_ext $LIBS >&5' ac_compiler_gnu=$ac_cv_c_compiler_gnu fi { $as_echo "$as_me:${as_lineno-$LINENO}: result: $frr_cv_Wextra" >&5 $as_echo "$frr_cv_Wextra" >&6; } if test "${frr_cv_Wextra}" = yes; then CFLAGS="$CFLAGS -Wextra" else : fi } { { $as_echo "$as_me:${as_lineno-$LINENO}: checking whether $CC supports -Wmissing-prototypes" >&5 $as_echo_n "checking whether $CC supports -Wmissing-prototypes... " >&6; } if ${frr_cv_Wmissingprototypes+:} false; then : $as_echo_n "(cached) " >&6 else ac_ext=c ac_cpp='$CPP $CPPFLAGS' ac_compile='$CC -c $CFLAGS $CPPFLAGS conftest.$ac_ext >&5' ac_link='$CC -o conftest$ac_exeext $CFLAGS $CPPFLAGS $LDFLAGS conftest.$ac_ext $LIBS >&5' ac_compiler_gnu=$ac_cv_c_compiler_gnu ac_c_flag_save="$CFLAGS" CFLAGS="$CFLAGS -Wmissing-prototypes" cat confdefs.h - <<_ACEOF >conftest.$ac_ext /* end confdefs.h. */ int main () { ; return 0; } _ACEOF if ac_fn_c_try_compile "$LINENO"; then : frr_cv_Wmissingprototypes=yes else frr_cv_Wmissingprototypes=no fi rm -f core conftest.err conftest.$ac_objext conftest.$ac_ext CFLAGS="$ac_c_flag_save" ac_ext=c ac_cpp='$CPP $CPPFLAGS' ac_compile='$CC -c $CFLAGS $CPPFLAGS conftest.$ac_ext >&5' ac_link='$CC -o conftest$ac_exeext $CFLAGS $CPPFLAGS $LDFLAGS conftest.$ac_ext $LIBS >&5' ac_compiler_gnu=$ac_cv_c_compiler_gnu fi { $as_echo "$as_me:${as_lineno-$LINENO}: result: $frr_cv_Wmissingprototypes" >&5 $as_echo "$frr_cv_Wmissingprototypes" >&6; } if test "${frr_cv_Wmissingprototypes}" = yes; then CFLAGS="$CFLAGS -Wmissing-prototypes" else : fi } { { $as_echo "$as_me:${as_lineno-$LINENO}: checking whether $CC supports -Wmissing-declarations" >&5 $as_echo_n "checking whether $CC supports -Wmissing-declarations... " >&6; } if ${frr_cv_Wmissingdeclarations+:} false; then : $as_echo_n "(cached) " >&6 else ac_ext=c ac_cpp='$CPP $CPPFLAGS' ac_compile='$CC -c $CFLAGS $CPPFLAGS conftest.$ac_ext >&5' ac_link='$CC -o conftest$ac_exeext $CFLAGS $CPPFLAGS $LDFLAGS conftest.$ac_ext $LIBS >&5' ac_compiler_gnu=$ac_cv_c_compiler_gnu ac_c_flag_save="$CFLAGS" CFLAGS="$CFLAGS -Wmissing-declarations" cat confdefs.h - <<_ACEOF >conftest.$ac_ext /* end confdefs.h. */ int main () { ; return 0; } _ACEOF if ac_fn_c_try_compile "$LINENO"; then : frr_cv_Wmissingdeclarations=yes else frr_cv_Wmissingdeclarations=no fi rm -f core conftest.err conftest.$ac_objext conftest.$ac_ext CFLAGS="$ac_c_flag_save" ac_ext=c ac_cpp='$CPP $CPPFLAGS' ac_compile='$CC -c $CFLAGS $CPPFLAGS conftest.$ac_ext >&5' ac_link='$CC -o conftest$ac_exeext $CFLAGS $CPPFLAGS $LDFLAGS conftest.$ac_ext $LIBS >&5' ac_compiler_gnu=$ac_cv_c_compiler_gnu fi { $as_echo "$as_me:${as_lineno-$LINENO}: result: $frr_cv_Wmissingdeclarations" >&5 $as_echo "$frr_cv_Wmissingdeclarations" >&6; } if test "${frr_cv_Wmissingdeclarations}" = yes; then CFLAGS="$CFLAGS -Wmissing-declarations" else : fi } { { $as_echo "$as_me:${as_lineno-$LINENO}: checking whether $CC supports -Wpointer-arith" >&5 $as_echo_n "checking whether $CC supports -Wpointer-arith... " >&6; } if ${frr_cv_Wpointerarith+:} false; then : $as_echo_n "(cached) " >&6 else ac_ext=c ac_cpp='$CPP $CPPFLAGS' ac_compile='$CC -c $CFLAGS $CPPFLAGS conftest.$ac_ext >&5' ac_link='$CC -o conftest$ac_exeext $CFLAGS $CPPFLAGS $LDFLAGS conftest.$ac_ext $LIBS >&5' ac_compiler_gnu=$ac_cv_c_compiler_gnu ac_c_flag_save="$CFLAGS" CFLAGS="$CFLAGS -Wpointer-arith" cat confdefs.h - <<_ACEOF >conftest.$ac_ext /* end confdefs.h. */ int main () { ; return 0; } _ACEOF if ac_fn_c_try_compile "$LINENO"; then : frr_cv_Wpointerarith=yes else frr_cv_Wpointerarith=no fi rm -f core conftest.err conftest.$ac_objext conftest.$ac_ext CFLAGS="$ac_c_flag_save" ac_ext=c ac_cpp='$CPP $CPPFLAGS' ac_compile='$CC -c $CFLAGS $CPPFLAGS conftest.$ac_ext >&5' ac_link='$CC -o conftest$ac_exeext $CFLAGS $CPPFLAGS $LDFLAGS conftest.$ac_ext $LIBS >&5' ac_compiler_gnu=$ac_cv_c_compiler_gnu fi { $as_echo "$as_me:${as_lineno-$LINENO}: result: $frr_cv_Wpointerarith" >&5 $as_echo "$frr_cv_Wpointerarith" >&6; } if test "${frr_cv_Wpointerarith}" = yes; then CFLAGS="$CFLAGS -Wpointer-arith" else : fi } { { $as_echo "$as_me:${as_lineno-$LINENO}: checking whether $CC supports -Wbad-function-cast" >&5 $as_echo_n "checking whether $CC supports -Wbad-function-cast... " >&6; } if ${frr_cv_Wbadfunctioncast+:} false; then : $as_echo_n "(cached) " >&6 else ac_ext=c ac_cpp='$CPP $CPPFLAGS' ac_compile='$CC -c $CFLAGS $CPPFLAGS conftest.$ac_ext >&5' ac_link='$CC -o conftest$ac_exeext $CFLAGS $CPPFLAGS $LDFLAGS conftest.$ac_ext $LIBS >&5' ac_compiler_gnu=$ac_cv_c_compiler_gnu ac_c_flag_save="$CFLAGS" CFLAGS="$CFLAGS -Wbad-function-cast" cat confdefs.h - <<_ACEOF >conftest.$ac_ext /* end confdefs.h. */ int main () { ; return 0; } _ACEOF if ac_fn_c_try_compile "$LINENO"; then : frr_cv_Wbadfunctioncast=yes else frr_cv_Wbadfunctioncast=no fi rm -f core conftest.err conftest.$ac_objext conftest.$ac_ext CFLAGS="$ac_c_flag_save" ac_ext=c ac_cpp='$CPP $CPPFLAGS' ac_compile='$CC -c $CFLAGS $CPPFLAGS conftest.$ac_ext >&5' ac_link='$CC -o conftest$ac_exeext $CFLAGS $CPPFLAGS $LDFLAGS conftest.$ac_ext $LIBS >&5' ac_compiler_gnu=$ac_cv_c_compiler_gnu fi { $as_echo "$as_me:${as_lineno-$LINENO}: result: $frr_cv_Wbadfunctioncast" >&5 $as_echo "$frr_cv_Wbadfunctioncast" >&6; } if test "${frr_cv_Wbadfunctioncast}" = yes; then CFLAGS="$CFLAGS -Wbad-function-cast" else : fi } { { $as_echo "$as_me:${as_lineno-$LINENO}: checking whether $CC supports -Wwrite-strings" >&5 $as_echo_n "checking whether $CC supports -Wwrite-strings... " >&6; } if ${frr_cv_Wwritestrings+:} false; then : $as_echo_n "(cached) " >&6 else ac_ext=c ac_cpp='$CPP $CPPFLAGS' ac_compile='$CC -c $CFLAGS $CPPFLAGS conftest.$ac_ext >&5' ac_link='$CC -o conftest$ac_exeext $CFLAGS $CPPFLAGS $LDFLAGS conftest.$ac_ext $LIBS >&5' ac_compiler_gnu=$ac_cv_c_compiler_gnu ac_c_flag_save="$CFLAGS" CFLAGS="$CFLAGS -Wwrite-strings" cat confdefs.h - <<_ACEOF >conftest.$ac_ext /* end confdefs.h. */ int main () { ; return 0; } _ACEOF if ac_fn_c_try_compile "$LINENO"; then : frr_cv_Wwritestrings=yes else frr_cv_Wwritestrings=no fi rm -f core conftest.err conftest.$ac_objext conftest.$ac_ext CFLAGS="$ac_c_flag_save" ac_ext=c ac_cpp='$CPP $CPPFLAGS' ac_compile='$CC -c $CFLAGS $CPPFLAGS conftest.$ac_ext >&5' ac_link='$CC -o conftest$ac_exeext $CFLAGS $CPPFLAGS $LDFLAGS conftest.$ac_ext $LIBS >&5' ac_compiler_gnu=$ac_cv_c_compiler_gnu fi { $as_echo "$as_me:${as_lineno-$LINENO}: result: $frr_cv_Wwritestrings" >&5 $as_echo "$frr_cv_Wwritestrings" >&6; } if test "${frr_cv_Wwritestrings}" = yes; then CFLAGS="$CFLAGS -Wwrite-strings" else : fi } if test x"${enable_gcc_ultra_verbose}" = x"yes" ; then { { $as_echo "$as_me:${as_lineno-$LINENO}: checking whether $CC supports -Wcast-qual" >&5 $as_echo_n "checking whether $CC supports -Wcast-qual... " >&6; } if ${frr_cv_Wcastqual+:} false; then : $as_echo_n "(cached) " >&6 else ac_ext=c ac_cpp='$CPP $CPPFLAGS' ac_compile='$CC -c $CFLAGS $CPPFLAGS conftest.$ac_ext >&5' ac_link='$CC -o conftest$ac_exeext $CFLAGS $CPPFLAGS $LDFLAGS conftest.$ac_ext $LIBS >&5' ac_compiler_gnu=$ac_cv_c_compiler_gnu ac_c_flag_save="$CFLAGS" CFLAGS="$CFLAGS -Wcast-qual" cat confdefs.h - <<_ACEOF >conftest.$ac_ext /* end confdefs.h. */ int main () { ; return 0; } _ACEOF if ac_fn_c_try_compile "$LINENO"; then : frr_cv_Wcastqual=yes else frr_cv_Wcastqual=no fi rm -f core conftest.err conftest.$ac_objext conftest.$ac_ext CFLAGS="$ac_c_flag_save" ac_ext=c ac_cpp='$CPP $CPPFLAGS' ac_compile='$CC -c $CFLAGS $CPPFLAGS conftest.$ac_ext >&5' ac_link='$CC -o conftest$ac_exeext $CFLAGS $CPPFLAGS $LDFLAGS conftest.$ac_ext $LIBS >&5' ac_compiler_gnu=$ac_cv_c_compiler_gnu fi { $as_echo "$as_me:${as_lineno-$LINENO}: result: $frr_cv_Wcastqual" >&5 $as_echo "$frr_cv_Wcastqual" >&6; } if test "${frr_cv_Wcastqual}" = yes; then CFLAGS="$CFLAGS -Wcast-qual" else : fi } { { $as_echo "$as_me:${as_lineno-$LINENO}: checking whether $CC supports -Wstrict-prototypes" >&5 $as_echo_n "checking whether $CC supports -Wstrict-prototypes... " >&6; } if ${frr_cv_Wstrictprototypes+:} false; then : $as_echo_n "(cached) " >&6 else ac_ext=c ac_cpp='$CPP $CPPFLAGS' ac_compile='$CC -c $CFLAGS $CPPFLAGS conftest.$ac_ext >&5' ac_link='$CC -o conftest$ac_exeext $CFLAGS $CPPFLAGS $LDFLAGS conftest.$ac_ext $LIBS >&5' ac_compiler_gnu=$ac_cv_c_compiler_gnu ac_c_flag_save="$CFLAGS" CFLAGS="$CFLAGS -Wstrict-prototypes" cat confdefs.h - <<_ACEOF >conftest.$ac_ext /* end confdefs.h. */ int main () { ; return 0; } _ACEOF if ac_fn_c_try_compile "$LINENO"; then : frr_cv_Wstrictprototypes=yes else frr_cv_Wstrictprototypes=no fi rm -f core conftest.err conftest.$ac_objext conftest.$ac_ext CFLAGS="$ac_c_flag_save" ac_ext=c ac_cpp='$CPP $CPPFLAGS' ac_compile='$CC -c $CFLAGS $CPPFLAGS conftest.$ac_ext >&5' ac_link='$CC -o conftest$ac_exeext $CFLAGS $CPPFLAGS $LDFLAGS conftest.$ac_ext $LIBS >&5' ac_compiler_gnu=$ac_cv_c_compiler_gnu fi { $as_echo "$as_me:${as_lineno-$LINENO}: result: $frr_cv_Wstrictprototypes" >&5 $as_echo "$frr_cv_Wstrictprototypes" >&6; } if test "${frr_cv_Wstrictprototypes}" = yes; then CFLAGS="$CFLAGS -Wstrict-prototypes" else : fi } { { $as_echo "$as_me:${as_lineno-$LINENO}: checking whether $CC supports -Wmissing-noreturn" >&5 $as_echo_n "checking whether $CC supports -Wmissing-noreturn... " >&6; } if ${frr_cv_Wmissingnoreturn+:} false; then : $as_echo_n "(cached) " >&6 else ac_ext=c ac_cpp='$CPP $CPPFLAGS' ac_compile='$CC -c $CFLAGS $CPPFLAGS conftest.$ac_ext >&5' ac_link='$CC -o conftest$ac_exeext $CFLAGS $CPPFLAGS $LDFLAGS conftest.$ac_ext $LIBS >&5' ac_compiler_gnu=$ac_cv_c_compiler_gnu ac_c_flag_save="$CFLAGS" CFLAGS="$CFLAGS -Wmissing-noreturn" cat confdefs.h - <<_ACEOF >conftest.$ac_ext /* end confdefs.h. */ int main () { ; return 0; } _ACEOF if ac_fn_c_try_compile "$LINENO"; then : frr_cv_Wmissingnoreturn=yes else frr_cv_Wmissingnoreturn=no fi rm -f core conftest.err conftest.$ac_objext conftest.$ac_ext CFLAGS="$ac_c_flag_save" ac_ext=c ac_cpp='$CPP $CPPFLAGS' ac_compile='$CC -c $CFLAGS $CPPFLAGS conftest.$ac_ext >&5' ac_link='$CC -o conftest$ac_exeext $CFLAGS $CPPFLAGS $LDFLAGS conftest.$ac_ext $LIBS >&5' ac_compiler_gnu=$ac_cv_c_compiler_gnu fi { $as_echo "$as_me:${as_lineno-$LINENO}: result: $frr_cv_Wmissingnoreturn" >&5 $as_echo "$frr_cv_Wmissingnoreturn" >&6; } if test "${frr_cv_Wmissingnoreturn}" = yes; then CFLAGS="$CFLAGS -Wmissing-noreturn" else : fi } { { $as_echo "$as_me:${as_lineno-$LINENO}: checking whether $CC supports -Wmissing-format-attribute" >&5 $as_echo_n "checking whether $CC supports -Wmissing-format-attribute... " >&6; } if ${frr_cv_Wmissingformatattribute+:} false; then : $as_echo_n "(cached) " >&6 else ac_ext=c ac_cpp='$CPP $CPPFLAGS' ac_compile='$CC -c $CFLAGS $CPPFLAGS conftest.$ac_ext >&5' ac_link='$CC -o conftest$ac_exeext $CFLAGS $CPPFLAGS $LDFLAGS conftest.$ac_ext $LIBS >&5' ac_compiler_gnu=$ac_cv_c_compiler_gnu ac_c_flag_save="$CFLAGS" CFLAGS="$CFLAGS -Wmissing-format-attribute" cat confdefs.h - <<_ACEOF >conftest.$ac_ext /* end confdefs.h. */ int main () { ; return 0; } _ACEOF if ac_fn_c_try_compile "$LINENO"; then : frr_cv_Wmissingformatattribute=yes else frr_cv_Wmissingformatattribute=no fi rm -f core conftest.err conftest.$ac_objext conftest.$ac_ext CFLAGS="$ac_c_flag_save" ac_ext=c ac_cpp='$CPP $CPPFLAGS' ac_compile='$CC -c $CFLAGS $CPPFLAGS conftest.$ac_ext >&5' ac_link='$CC -o conftest$ac_exeext $CFLAGS $CPPFLAGS $LDFLAGS conftest.$ac_ext $LIBS >&5' ac_compiler_gnu=$ac_cv_c_compiler_gnu fi { $as_echo "$as_me:${as_lineno-$LINENO}: result: $frr_cv_Wmissingformatattribute" >&5 $as_echo "$frr_cv_Wmissingformatattribute" >&6; } if test "${frr_cv_Wmissingformatattribute}" = yes; then CFLAGS="$CFLAGS -Wmissing-format-attribute" else : fi } { { $as_echo "$as_me:${as_lineno-$LINENO}: checking whether $CC supports -Wunreachable-code" >&5 $as_echo_n "checking whether $CC supports -Wunreachable-code... " >&6; } if ${frr_cv_Wunreachablecode+:} false; then : $as_echo_n "(cached) " >&6 else ac_ext=c ac_cpp='$CPP $CPPFLAGS' ac_compile='$CC -c $CFLAGS $CPPFLAGS conftest.$ac_ext >&5' ac_link='$CC -o conftest$ac_exeext $CFLAGS $CPPFLAGS $LDFLAGS conftest.$ac_ext $LIBS >&5' ac_compiler_gnu=$ac_cv_c_compiler_gnu ac_c_flag_save="$CFLAGS" CFLAGS="$CFLAGS -Wunreachable-code" cat confdefs.h - <<_ACEOF >conftest.$ac_ext /* end confdefs.h. */ int main () { ; return 0; } _ACEOF if ac_fn_c_try_compile "$LINENO"; then : frr_cv_Wunreachablecode=yes else frr_cv_Wunreachablecode=no fi rm -f core conftest.err conftest.$ac_objext conftest.$ac_ext CFLAGS="$ac_c_flag_save" ac_ext=c ac_cpp='$CPP $CPPFLAGS' ac_compile='$CC -c $CFLAGS $CPPFLAGS conftest.$ac_ext >&5' ac_link='$CC -o conftest$ac_exeext $CFLAGS $CPPFLAGS $LDFLAGS conftest.$ac_ext $LIBS >&5' ac_compiler_gnu=$ac_cv_c_compiler_gnu fi { $as_echo "$as_me:${as_lineno-$LINENO}: result: $frr_cv_Wunreachablecode" >&5 $as_echo "$frr_cv_Wunreachablecode" >&6; } if test "${frr_cv_Wunreachablecode}" = yes; then CFLAGS="$CFLAGS -Wunreachable-code" else : fi } { { $as_echo "$as_me:${as_lineno-$LINENO}: checking whether $CC supports -Wpacked" >&5 $as_echo_n "checking whether $CC supports -Wpacked... " >&6; } if ${frr_cv_Wpacked+:} false; then : $as_echo_n "(cached) " >&6 else ac_ext=c ac_cpp='$CPP $CPPFLAGS' ac_compile='$CC -c $CFLAGS $CPPFLAGS conftest.$ac_ext >&5' ac_link='$CC -o conftest$ac_exeext $CFLAGS $CPPFLAGS $LDFLAGS conftest.$ac_ext $LIBS >&5' ac_compiler_gnu=$ac_cv_c_compiler_gnu ac_c_flag_save="$CFLAGS" CFLAGS="$CFLAGS -Wpacked" cat confdefs.h - <<_ACEOF >conftest.$ac_ext /* end confdefs.h. */ int main () { ; return 0; } _ACEOF if ac_fn_c_try_compile "$LINENO"; then : frr_cv_Wpacked=yes else frr_cv_Wpacked=no fi rm -f core conftest.err conftest.$ac_objext conftest.$ac_ext CFLAGS="$ac_c_flag_save" ac_ext=c ac_cpp='$CPP $CPPFLAGS' ac_compile='$CC -c $CFLAGS $CPPFLAGS conftest.$ac_ext >&5' ac_link='$CC -o conftest$ac_exeext $CFLAGS $CPPFLAGS $LDFLAGS conftest.$ac_ext $LIBS >&5' ac_compiler_gnu=$ac_cv_c_compiler_gnu fi { $as_echo "$as_me:${as_lineno-$LINENO}: result: $frr_cv_Wpacked" >&5 $as_echo "$frr_cv_Wpacked" >&6; } if test "${frr_cv_Wpacked}" = yes; then CFLAGS="$CFLAGS -Wpacked" else : fi } { { $as_echo "$as_me:${as_lineno-$LINENO}: checking whether $CC supports -Wpadded" >&5 $as_echo_n "checking whether $CC supports -Wpadded... " >&6; } if ${frr_cv_Wpadded+:} false; then : $as_echo_n "(cached) " >&6 else ac_ext=c ac_cpp='$CPP $CPPFLAGS' ac_compile='$CC -c $CFLAGS $CPPFLAGS conftest.$ac_ext >&5' ac_link='$CC -o conftest$ac_exeext $CFLAGS $CPPFLAGS $LDFLAGS conftest.$ac_ext $LIBS >&5' ac_compiler_gnu=$ac_cv_c_compiler_gnu ac_c_flag_save="$CFLAGS" CFLAGS="$CFLAGS -Wpadded" cat confdefs.h - <<_ACEOF >conftest.$ac_ext /* end confdefs.h. */ int main () { ; return 0; } _ACEOF if ac_fn_c_try_compile "$LINENO"; then : frr_cv_Wpadded=yes else frr_cv_Wpadded=no fi rm -f core conftest.err conftest.$ac_objext conftest.$ac_ext CFLAGS="$ac_c_flag_save" ac_ext=c ac_cpp='$CPP $CPPFLAGS' ac_compile='$CC -c $CFLAGS $CPPFLAGS conftest.$ac_ext >&5' ac_link='$CC -o conftest$ac_exeext $CFLAGS $CPPFLAGS $LDFLAGS conftest.$ac_ext $LIBS >&5' ac_compiler_gnu=$ac_cv_c_compiler_gnu fi { $as_echo "$as_me:${as_lineno-$LINENO}: result: $frr_cv_Wpadded" >&5 $as_echo "$frr_cv_Wpadded" >&6; } if test "${frr_cv_Wpadded}" = yes; then CFLAGS="$CFLAGS -Wpadded" else : fi } { { $as_echo "$as_me:${as_lineno-$LINENO}: checking whether $CC supports -Wshadow" >&5 $as_echo_n "checking whether $CC supports -Wshadow... " >&6; } if ${frr_cv_Wshadow+:} false; then : $as_echo_n "(cached) " >&6 else ac_ext=c ac_cpp='$CPP $CPPFLAGS' ac_compile='$CC -c $CFLAGS $CPPFLAGS conftest.$ac_ext >&5' ac_link='$CC -o conftest$ac_exeext $CFLAGS $CPPFLAGS $LDFLAGS conftest.$ac_ext $LIBS >&5' ac_compiler_gnu=$ac_cv_c_compiler_gnu ac_c_flag_save="$CFLAGS" CFLAGS="$CFLAGS -Wshadow" cat confdefs.h - <<_ACEOF >conftest.$ac_ext /* end confdefs.h. */ int main () { ; return 0; } _ACEOF if ac_fn_c_try_compile "$LINENO"; then : frr_cv_Wshadow=yes else frr_cv_Wshadow=no fi rm -f core conftest.err conftest.$ac_objext conftest.$ac_ext CFLAGS="$ac_c_flag_save" ac_ext=c ac_cpp='$CPP $CPPFLAGS' ac_compile='$CC -c $CFLAGS $CPPFLAGS conftest.$ac_ext >&5' ac_link='$CC -o conftest$ac_exeext $CFLAGS $CPPFLAGS $LDFLAGS conftest.$ac_ext $LIBS >&5' ac_compiler_gnu=$ac_cv_c_compiler_gnu fi { $as_echo "$as_me:${as_lineno-$LINENO}: result: $frr_cv_Wshadow" >&5 $as_echo "$frr_cv_Wshadow" >&6; } if test "${frr_cv_Wshadow}" = yes; then CFLAGS="$CFLAGS -Wshadow" else : fi } else { { $as_echo "$as_me:${as_lineno-$LINENO}: checking whether $CC supports -Wno-unused-result" >&5 $as_echo_n "checking whether $CC supports -Wno-unused-result... " >&6; } if ${frr_cv_Wnounusedresult+:} false; then : $as_echo_n "(cached) " >&6 else ac_ext=c ac_cpp='$CPP $CPPFLAGS' ac_compile='$CC -c $CFLAGS $CPPFLAGS conftest.$ac_ext >&5' ac_link='$CC -o conftest$ac_exeext $CFLAGS $CPPFLAGS $LDFLAGS conftest.$ac_ext $LIBS >&5' ac_compiler_gnu=$ac_cv_c_compiler_gnu ac_c_flag_save="$CFLAGS" CFLAGS="$CFLAGS -Wno-unused-result" cat confdefs.h - <<_ACEOF >conftest.$ac_ext /* end confdefs.h. */ int main () { ; return 0; } _ACEOF if ac_fn_c_try_compile "$LINENO"; then : frr_cv_Wnounusedresult=yes else frr_cv_Wnounusedresult=no fi rm -f core conftest.err conftest.$ac_objext conftest.$ac_ext CFLAGS="$ac_c_flag_save" ac_ext=c ac_cpp='$CPP $CPPFLAGS' ac_compile='$CC -c $CFLAGS $CPPFLAGS conftest.$ac_ext >&5' ac_link='$CC -o conftest$ac_exeext $CFLAGS $CPPFLAGS $LDFLAGS conftest.$ac_ext $LIBS >&5' ac_compiler_gnu=$ac_cv_c_compiler_gnu fi { $as_echo "$as_me:${as_lineno-$LINENO}: result: $frr_cv_Wnounusedresult" >&5 $as_echo "$frr_cv_Wnounusedresult" >&6; } if test "${frr_cv_Wnounusedresult}" = yes; then CFLAGS="$CFLAGS -Wno-unused-result" else : fi } fi { { $as_echo "$as_me:${as_lineno-$LINENO}: checking whether $CC supports -Wno-unused-parameter" >&5 $as_echo_n "checking whether $CC supports -Wno-unused-parameter... " >&6; } if ${frr_cv_Wnounusedparameter+:} false; then : $as_echo_n "(cached) " >&6 else ac_ext=c ac_cpp='$CPP $CPPFLAGS' ac_compile='$CC -c $CFLAGS $CPPFLAGS conftest.$ac_ext >&5' ac_link='$CC -o conftest$ac_exeext $CFLAGS $CPPFLAGS $LDFLAGS conftest.$ac_ext $LIBS >&5' ac_compiler_gnu=$ac_cv_c_compiler_gnu ac_c_flag_save="$CFLAGS" CFLAGS="$CFLAGS -Wno-unused-parameter" cat confdefs.h - <<_ACEOF >conftest.$ac_ext /* end confdefs.h. */ int main () { ; return 0; } _ACEOF if ac_fn_c_try_compile "$LINENO"; then : frr_cv_Wnounusedparameter=yes else frr_cv_Wnounusedparameter=no fi rm -f core conftest.err conftest.$ac_objext conftest.$ac_ext CFLAGS="$ac_c_flag_save" ac_ext=c ac_cpp='$CPP $CPPFLAGS' ac_compile='$CC -c $CFLAGS $CPPFLAGS conftest.$ac_ext >&5' ac_link='$CC -o conftest$ac_exeext $CFLAGS $CPPFLAGS $LDFLAGS conftest.$ac_ext $LIBS >&5' ac_compiler_gnu=$ac_cv_c_compiler_gnu fi { $as_echo "$as_me:${as_lineno-$LINENO}: result: $frr_cv_Wnounusedparameter" >&5 $as_echo "$frr_cv_Wnounusedparameter" >&6; } if test "${frr_cv_Wnounusedparameter}" = yes; then CFLAGS="$CFLAGS -Wno-unused-parameter" else : fi } { { $as_echo "$as_me:${as_lineno-$LINENO}: checking whether $CC supports -Wno-missing-field-initializers" >&5 $as_echo_n "checking whether $CC supports -Wno-missing-field-initializers... " >&6; } if ${frr_cv_Wnomissingfieldinitializers+:} false; then : $as_echo_n "(cached) " >&6 else ac_ext=c ac_cpp='$CPP $CPPFLAGS' ac_compile='$CC -c $CFLAGS $CPPFLAGS conftest.$ac_ext >&5' ac_link='$CC -o conftest$ac_exeext $CFLAGS $CPPFLAGS $LDFLAGS conftest.$ac_ext $LIBS >&5' ac_compiler_gnu=$ac_cv_c_compiler_gnu ac_c_flag_save="$CFLAGS" CFLAGS="$CFLAGS -Wno-missing-field-initializers" cat confdefs.h - <<_ACEOF >conftest.$ac_ext /* end confdefs.h. */ int main () { ; return 0; } _ACEOF if ac_fn_c_try_compile "$LINENO"; then : frr_cv_Wnomissingfieldinitializers=yes else frr_cv_Wnomissingfieldinitializers=no fi rm -f core conftest.err conftest.$ac_objext conftest.$ac_ext CFLAGS="$ac_c_flag_save" ac_ext=c ac_cpp='$CPP $CPPFLAGS' ac_compile='$CC -c $CFLAGS $CPPFLAGS conftest.$ac_ext >&5' ac_link='$CC -o conftest$ac_exeext $CFLAGS $CPPFLAGS $LDFLAGS conftest.$ac_ext $LIBS >&5' ac_compiler_gnu=$ac_cv_c_compiler_gnu fi { $as_echo "$as_me:${as_lineno-$LINENO}: result: $frr_cv_Wnomissingfieldinitializers" >&5 $as_echo "$frr_cv_Wnomissingfieldinitializers" >&6; } if test "${frr_cv_Wnomissingfieldinitializers}" = yes; then CFLAGS="$CFLAGS -Wno-missing-field-initializers" else : fi } { { $as_echo "$as_me:${as_lineno-$LINENO}: checking whether $CC supports -Wc++-compat" >&5 $as_echo_n "checking whether $CC supports -Wc++-compat... " >&6; } if ${frr_cv_Wccompat+:} false; then : $as_echo_n "(cached) " >&6 else ac_ext=c ac_cpp='$CPP $CPPFLAGS' ac_compile='$CC -c $CFLAGS $CPPFLAGS conftest.$ac_ext >&5' ac_link='$CC -o conftest$ac_exeext $CFLAGS $CPPFLAGS $LDFLAGS conftest.$ac_ext $LIBS >&5' ac_compiler_gnu=$ac_cv_c_compiler_gnu ac_c_flag_save="$CFLAGS" CFLAGS="$CFLAGS -Wc++-compat" cat confdefs.h - <<_ACEOF >conftest.$ac_ext /* end confdefs.h. */ int main () { ; return 0; } _ACEOF if ac_fn_c_try_compile "$LINENO"; then : frr_cv_Wccompat=yes else frr_cv_Wccompat=no fi rm -f core conftest.err conftest.$ac_objext conftest.$ac_ext CFLAGS="$ac_c_flag_save" ac_ext=c ac_cpp='$CPP $CPPFLAGS' ac_compile='$CC -c $CFLAGS $CPPFLAGS conftest.$ac_ext >&5' ac_link='$CC -o conftest$ac_exeext $CFLAGS $CPPFLAGS $LDFLAGS conftest.$ac_ext $LIBS >&5' ac_compiler_gnu=$ac_cv_c_compiler_gnu fi { $as_echo "$as_me:${as_lineno-$LINENO}: result: $frr_cv_Wccompat" >&5 $as_echo "$frr_cv_Wccompat" >&6; } if test "${frr_cv_Wccompat}" = yes; then CXX_COMPAT_CFLAGS="-Wc++-compat" else : fi } { { $as_echo "$as_me:${as_lineno-$LINENO}: checking whether $CC supports -diag-disable 3179" >&5 $as_echo_n "checking whether $CC supports -diag-disable 3179... " >&6; } if ${frr_cv_diagdisable_+:} false; then : $as_echo_n "(cached) " >&6 else ac_ext=c ac_cpp='$CPP $CPPFLAGS' ac_compile='$CC -c $CFLAGS $CPPFLAGS conftest.$ac_ext >&5' ac_link='$CC -o conftest$ac_exeext $CFLAGS $CPPFLAGS $LDFLAGS conftest.$ac_ext $LIBS >&5' ac_compiler_gnu=$ac_cv_c_compiler_gnu ac_c_flag_save="$CFLAGS" CFLAGS="$CFLAGS -diag-disable 3179" cat confdefs.h - <<_ACEOF >conftest.$ac_ext /* end confdefs.h. */ int main () { ; return 0; } _ACEOF if ac_fn_c_try_compile "$LINENO"; then : frr_cv_diagdisable_=yes else frr_cv_diagdisable_=no fi rm -f core conftest.err conftest.$ac_objext conftest.$ac_ext CFLAGS="$ac_c_flag_save" ac_ext=c ac_cpp='$CPP $CPPFLAGS' ac_compile='$CC -c $CFLAGS $CPPFLAGS conftest.$ac_ext >&5' ac_link='$CC -o conftest$ac_exeext $CFLAGS $CPPFLAGS $LDFLAGS conftest.$ac_ext $LIBS >&5' ac_compiler_gnu=$ac_cv_c_compiler_gnu fi { $as_echo "$as_me:${as_lineno-$LINENO}: result: $frr_cv_diagdisable_" >&5 $as_echo "$frr_cv_diagdisable_" >&6; } if test "${frr_cv_diagdisable_}" = yes; then CFLAGS="$CFLAGS -diag-disable 3179" else : fi } if test x"${enable_werror}" = x"yes" ; then WERROR="-Werror" fi SAN_FLAGS="" if test "$enable_address_sanitizer" = "yes"; then { { $as_echo "$as_me:${as_lineno-$LINENO}: checking whether $CC supports -fsanitize=address" >&5 $as_echo_n "checking whether $CC supports -fsanitize=address... " >&6; } if ${frr_cv_fsanitize_address+:} false; then : $as_echo_n "(cached) " >&6 else ac_ext=c ac_cpp='$CPP $CPPFLAGS' ac_compile='$CC -c $CFLAGS $CPPFLAGS conftest.$ac_ext >&5' ac_link='$CC -o conftest$ac_exeext $CFLAGS $CPPFLAGS $LDFLAGS conftest.$ac_ext $LIBS >&5' ac_compiler_gnu=$ac_cv_c_compiler_gnu ac_c_flag_save="$CFLAGS" CFLAGS="$CFLAGS -fsanitize=address" cat confdefs.h - <<_ACEOF >conftest.$ac_ext /* end confdefs.h. */ int main () { ; return 0; } _ACEOF if ac_fn_c_try_compile "$LINENO"; then : frr_cv_fsanitize_address=yes else frr_cv_fsanitize_address=no fi rm -f core conftest.err conftest.$ac_objext conftest.$ac_ext CFLAGS="$ac_c_flag_save" ac_ext=c ac_cpp='$CPP $CPPFLAGS' ac_compile='$CC -c $CFLAGS $CPPFLAGS conftest.$ac_ext >&5' ac_link='$CC -o conftest$ac_exeext $CFLAGS $CPPFLAGS $LDFLAGS conftest.$ac_ext $LIBS >&5' ac_compiler_gnu=$ac_cv_c_compiler_gnu fi { $as_echo "$as_me:${as_lineno-$LINENO}: result: $frr_cv_fsanitize_address" >&5 $as_echo "$frr_cv_fsanitize_address" >&6; } if test "${frr_cv_fsanitize_address}" = yes; then SAN_FLAGS="$SAN_FLAGS -fsanitize=address" else : as_fn_error $? "$CC does not support Address Sanitizer." "$LINENO" 5 fi } fi if test "$enable_thread_sanitizer" = "yes"; then { { $as_echo "$as_me:${as_lineno-$LINENO}: checking whether $CC supports -fsanitize=thread" >&5 $as_echo_n "checking whether $CC supports -fsanitize=thread... " >&6; } if ${frr_cv_fsanitize_thread+:} false; then : $as_echo_n "(cached) " >&6 else ac_ext=c ac_cpp='$CPP $CPPFLAGS' ac_compile='$CC -c $CFLAGS $CPPFLAGS conftest.$ac_ext >&5' ac_link='$CC -o conftest$ac_exeext $CFLAGS $CPPFLAGS $LDFLAGS conftest.$ac_ext $LIBS >&5' ac_compiler_gnu=$ac_cv_c_compiler_gnu ac_c_flag_save="$CFLAGS" CFLAGS="$CFLAGS -fsanitize=thread" cat confdefs.h - <<_ACEOF >conftest.$ac_ext /* end confdefs.h. */ int main () { ; return 0; } _ACEOF if ac_fn_c_try_compile "$LINENO"; then : frr_cv_fsanitize_thread=yes else frr_cv_fsanitize_thread=no fi rm -f core conftest.err conftest.$ac_objext conftest.$ac_ext CFLAGS="$ac_c_flag_save" ac_ext=c ac_cpp='$CPP $CPPFLAGS' ac_compile='$CC -c $CFLAGS $CPPFLAGS conftest.$ac_ext >&5' ac_link='$CC -o conftest$ac_exeext $CFLAGS $CPPFLAGS $LDFLAGS conftest.$ac_ext $LIBS >&5' ac_compiler_gnu=$ac_cv_c_compiler_gnu fi { $as_echo "$as_me:${as_lineno-$LINENO}: result: $frr_cv_fsanitize_thread" >&5 $as_echo "$frr_cv_fsanitize_thread" >&6; } if test "${frr_cv_fsanitize_thread}" = yes; then SAN_FLAGS="$SAN_FLAGS -fsanitize=thread" else : as_fn_error $? "$CC does not support Thread Sanitizer." "$LINENO" 5 fi } fi if test "$enable_memory_sanitizer" = "yes"; then { { $as_echo "$as_me:${as_lineno-$LINENO}: checking whether $CC supports -fsanitize=thread -fPIE -pie" >&5 $as_echo_n "checking whether $CC supports -fsanitize=thread -fPIE -pie... " >&6; } if ${frr_cv_fsanitize_thread_fPIE_pie+:} false; then : $as_echo_n "(cached) " >&6 else ac_ext=c ac_cpp='$CPP $CPPFLAGS' ac_compile='$CC -c $CFLAGS $CPPFLAGS conftest.$ac_ext >&5' ac_link='$CC -o conftest$ac_exeext $CFLAGS $CPPFLAGS $LDFLAGS conftest.$ac_ext $LIBS >&5' ac_compiler_gnu=$ac_cv_c_compiler_gnu ac_c_flag_save="$CFLAGS" CFLAGS="$CFLAGS -fsanitize=thread -fPIE -pie" cat confdefs.h - <<_ACEOF >conftest.$ac_ext /* end confdefs.h. */ int main () { ; return 0; } _ACEOF if ac_fn_c_try_compile "$LINENO"; then : frr_cv_fsanitize_thread_fPIE_pie=yes else frr_cv_fsanitize_thread_fPIE_pie=no fi rm -f core conftest.err conftest.$ac_objext conftest.$ac_ext CFLAGS="$ac_c_flag_save" ac_ext=c ac_cpp='$CPP $CPPFLAGS' ac_compile='$CC -c $CFLAGS $CPPFLAGS conftest.$ac_ext >&5' ac_link='$CC -o conftest$ac_exeext $CFLAGS $CPPFLAGS $LDFLAGS conftest.$ac_ext $LIBS >&5' ac_compiler_gnu=$ac_cv_c_compiler_gnu fi { $as_echo "$as_me:${as_lineno-$LINENO}: result: $frr_cv_fsanitize_thread_fPIE_pie" >&5 $as_echo "$frr_cv_fsanitize_thread_fPIE_pie" >&6; } if test "${frr_cv_fsanitize_thread_fPIE_pie}" = yes; then SAN_FLAGS="-fsanitize=memory -fPIE -pie" else : as_fn_error $? "$CC does not support Thread Sanitizer." "$LINENO" 5 fi } fi ac_ext=c ac_cpp='$CPP $CPPFLAGS' ac_compile='$CC -c $CFLAGS $CPPFLAGS conftest.$ac_ext >&5' ac_link='$CC -o conftest$ac_exeext $CFLAGS $CPPFLAGS $LDFLAGS conftest.$ac_ext $LIBS >&5' ac_compiler_gnu=$ac_cv_c_compiler_gnu ax_pthread_ok=no # We used to check for pthread.h first, but this fails if pthread.h # requires special compiler flags (e.g. on True64 or Sequent). # It gets checked for in the link test anyway. # First of all, check if the user has set any of the PTHREAD_LIBS, # etcetera environment variables, and if threads linking works using # them: if test x"$PTHREAD_LIBS$PTHREAD_CFLAGS" != x; then save_CFLAGS="$CFLAGS" CFLAGS="$CFLAGS $PTHREAD_CFLAGS" save_LIBS="$LIBS" LIBS="$PTHREAD_LIBS $LIBS" { $as_echo "$as_me:${as_lineno-$LINENO}: checking for pthread_join in LIBS=$PTHREAD_LIBS with CFLAGS=$PTHREAD_CFLAGS" >&5 $as_echo_n "checking for pthread_join in LIBS=$PTHREAD_LIBS with CFLAGS=$PTHREAD_CFLAGS... " >&6; } cat confdefs.h - <<_ACEOF >conftest.$ac_ext /* end confdefs.h. */ /* Override any GCC internal prototype to avoid an error. Use char because int might match the return type of a GCC builtin and then its argument prototype would still apply. */ #ifdef __cplusplus extern "C" #endif char pthread_join (); int main () { return pthread_join (); ; return 0; } _ACEOF if ac_fn_c_try_link "$LINENO"; then : ax_pthread_ok=yes fi rm -f core conftest.err conftest.$ac_objext \ conftest$ac_exeext conftest.$ac_ext { $as_echo "$as_me:${as_lineno-$LINENO}: result: $ax_pthread_ok" >&5 $as_echo "$ax_pthread_ok" >&6; } if test x"$ax_pthread_ok" = xno; then PTHREAD_LIBS="" PTHREAD_CFLAGS="" fi LIBS="$save_LIBS" CFLAGS="$save_CFLAGS" fi # We must check for the threads library under a number of different # names; the ordering is very important because some systems # (e.g. DEC) have both -lpthread and -lpthreads, where one of the # libraries is broken (non-POSIX). # Create a list of thread flags to try. Items starting with a "-" are # C compiler flags, and other items are library names, except for "none" # which indicates that we try without any flags at all, and "pthread-config" # which is a program returning the flags for the Pth emulation library. ax_pthread_flags="pthreads none -Kthread -kthread lthread -pthread -pthreads -mthreads pthread --thread-safe -mt pthread-config" # The ordering *is* (sometimes) important. Some notes on the # individual items follow: # pthreads: AIX (must check this before -lpthread) # none: in case threads are in libc; should be tried before -Kthread and # other compiler flags to prevent continual compiler warnings # -Kthread: Sequent (threads in libc, but -Kthread needed for pthread.h) # -kthread: FreeBSD kernel threads (preferred to -pthread since SMP-able) # lthread: LinuxThreads port on FreeBSD (also preferred to -pthread) # -pthread: Linux/gcc (kernel threads), BSD/gcc (userland threads) # -pthreads: Solaris/gcc # -mthreads: Mingw32/gcc, Lynx/gcc # -mt: Sun Workshop C (may only link SunOS threads [-lthread], but it # doesn't hurt to check since this sometimes defines pthreads too; # also defines -D_REENTRANT) # ... -mt is also the pthreads flag for HP/aCC # pthread: Linux, etcetera # --thread-safe: KAI C++ # pthread-config: use pthread-config program (for GNU Pth library) case ${host_os} in solaris*) # On Solaris (at least, for some versions), libc contains stubbed # (non-functional) versions of the pthreads routines, so link-based # tests will erroneously succeed. (We need to link with -pthreads/-mt/ # -lpthread.) (The stubs are missing pthread_cleanup_push, or rather # a function called by this macro, so we could check for that, but # who knows whether they'll stub that too in a future libc.) So, # we'll just look for -pthreads and -lpthread first: ax_pthread_flags="-pthreads pthread -mt -pthread $ax_pthread_flags" ;; darwin*) ax_pthread_flags="-pthread $ax_pthread_flags" ;; esac # Clang doesn't consider unrecognized options an error unless we specify # -Werror. We throw in some extra Clang-specific options to ensure that # this doesn't happen for GCC, which also accepts -Werror. { $as_echo "$as_me:${as_lineno-$LINENO}: checking if compiler needs -Werror to reject unknown flags" >&5 $as_echo_n "checking if compiler needs -Werror to reject unknown flags... " >&6; } save_CFLAGS="$CFLAGS" ax_pthread_extra_flags="-Werror" CFLAGS="$CFLAGS $ax_pthread_extra_flags -Wunknown-warning-option -Wsizeof-array-argument" cat confdefs.h - <<_ACEOF >conftest.$ac_ext /* end confdefs.h. */ int foo(void); int main () { foo() ; return 0; } _ACEOF if ac_fn_c_try_compile "$LINENO"; then : { $as_echo "$as_me:${as_lineno-$LINENO}: result: yes" >&5 $as_echo "yes" >&6; } else ax_pthread_extra_flags= { $as_echo "$as_me:${as_lineno-$LINENO}: result: no" >&5 $as_echo "no" >&6; } fi rm -f core conftest.err conftest.$ac_objext conftest.$ac_ext CFLAGS="$save_CFLAGS" if test x"$ax_pthread_ok" = xno; then for flag in $ax_pthread_flags; do case $flag in none) { $as_echo "$as_me:${as_lineno-$LINENO}: checking whether pthreads work without any flags" >&5 $as_echo_n "checking whether pthreads work without any flags... " >&6; } ;; -*) { $as_echo "$as_me:${as_lineno-$LINENO}: checking whether pthreads work with $flag" >&5 $as_echo_n "checking whether pthreads work with $flag... " >&6; } PTHREAD_CFLAGS="$flag" ;; pthread-config) # Extract the first word of "pthread-config", so it can be a program name with args. set dummy pthread-config; ac_word=$2 { $as_echo "$as_me:${as_lineno-$LINENO}: checking for $ac_word" >&5 $as_echo_n "checking for $ac_word... " >&6; } if ${ac_cv_prog_ax_pthread_config+:} false; then : $as_echo_n "(cached) " >&6 else if test -n "$ax_pthread_config"; then ac_cv_prog_ax_pthread_config="$ax_pthread_config" # Let the user override the test. else as_save_IFS=$IFS; IFS=$PATH_SEPARATOR for as_dir in $PATH do IFS=$as_save_IFS test -z "$as_dir" && as_dir=. for ac_exec_ext in '' $ac_executable_extensions; do if as_fn_executable_p "$as_dir/$ac_word$ac_exec_ext"; then ac_cv_prog_ax_pthread_config="yes" $as_echo "$as_me:${as_lineno-$LINENO}: found $as_dir/$ac_word$ac_exec_ext" >&5 break 2 fi done done IFS=$as_save_IFS test -z "$ac_cv_prog_ax_pthread_config" && ac_cv_prog_ax_pthread_config="no" fi fi ax_pthread_config=$ac_cv_prog_ax_pthread_config if test -n "$ax_pthread_config"; then { $as_echo "$as_me:${as_lineno-$LINENO}: result: $ax_pthread_config" >&5 $as_echo "$ax_pthread_config" >&6; } else { $as_echo "$as_me:${as_lineno-$LINENO}: result: no" >&5 $as_echo "no" >&6; } fi if test x"$ax_pthread_config" = xno; then continue; fi PTHREAD_CFLAGS="`pthread-config --cflags`" PTHREAD_LIBS="`pthread-config --ldflags` `pthread-config --libs`" ;; *) { $as_echo "$as_me:${as_lineno-$LINENO}: checking for the pthreads library -l$flag" >&5 $as_echo_n "checking for the pthreads library -l$flag... " >&6; } PTHREAD_LIBS="-l$flag" ;; esac save_LIBS="$LIBS" save_CFLAGS="$CFLAGS" LIBS="$PTHREAD_LIBS $LIBS" CFLAGS="$CFLAGS $PTHREAD_CFLAGS $ax_pthread_extra_flags" # Check for various functions. We must include pthread.h, # since some functions may be macros. (On the Sequent, we # need a special flag -Kthread to make this header compile.) # We check for pthread_join because it is in -lpthread on IRIX # while pthread_create is in libc. We check for pthread_attr_init # due to DEC craziness with -lpthreads. We check for # pthread_cleanup_push because it is one of the few pthread # functions on Solaris that doesn't have a non-functional libc stub. # We try pthread_create on general principles. cat confdefs.h - <<_ACEOF >conftest.$ac_ext /* end confdefs.h. */ #include static void routine(void *a) { a = 0; } static void *start_routine(void *a) { return a; } int main () { pthread_t th; pthread_attr_t attr; pthread_create(&th, 0, start_routine, 0); pthread_join(th, 0); pthread_attr_init(&attr); pthread_cleanup_push(routine, 0); pthread_cleanup_pop(0) /* ; */ ; return 0; } _ACEOF if ac_fn_c_try_link "$LINENO"; then : ax_pthread_ok=yes fi rm -f core conftest.err conftest.$ac_objext \ conftest$ac_exeext conftest.$ac_ext LIBS="$save_LIBS" CFLAGS="$save_CFLAGS" { $as_echo "$as_me:${as_lineno-$LINENO}: result: $ax_pthread_ok" >&5 $as_echo "$ax_pthread_ok" >&6; } if test "x$ax_pthread_ok" = xyes; then break; fi PTHREAD_LIBS="" PTHREAD_CFLAGS="" done fi # Various other checks: if test "x$ax_pthread_ok" = xyes; then save_LIBS="$LIBS" LIBS="$PTHREAD_LIBS $LIBS" save_CFLAGS="$CFLAGS" CFLAGS="$CFLAGS $PTHREAD_CFLAGS" # Detect AIX lossage: JOINABLE attribute is called UNDETACHED. { $as_echo "$as_me:${as_lineno-$LINENO}: checking for joinable pthread attribute" >&5 $as_echo_n "checking for joinable pthread attribute... " >&6; } attr_name=unknown for attr in PTHREAD_CREATE_JOINABLE PTHREAD_CREATE_UNDETACHED; do cat confdefs.h - <<_ACEOF >conftest.$ac_ext /* end confdefs.h. */ #include int main () { int attr = $attr; return attr /* ; */ ; return 0; } _ACEOF if ac_fn_c_try_link "$LINENO"; then : attr_name=$attr; break fi rm -f core conftest.err conftest.$ac_objext \ conftest$ac_exeext conftest.$ac_ext done { $as_echo "$as_me:${as_lineno-$LINENO}: result: $attr_name" >&5 $as_echo "$attr_name" >&6; } if test "$attr_name" != PTHREAD_CREATE_JOINABLE; then cat >>confdefs.h <<_ACEOF #define PTHREAD_CREATE_JOINABLE $attr_name _ACEOF fi { $as_echo "$as_me:${as_lineno-$LINENO}: checking if more special flags are required for pthreads" >&5 $as_echo_n "checking if more special flags are required for pthreads... " >&6; } flag=no case ${host_os} in aix* | freebsd* | darwin*) flag="-D_THREAD_SAFE";; osf* | hpux*) flag="-D_REENTRANT";; solaris*) if test "$GCC" = "yes"; then flag="-D_REENTRANT" else # TODO: What about Clang on Solaris? flag="-mt -D_REENTRANT" fi ;; esac { $as_echo "$as_me:${as_lineno-$LINENO}: result: $flag" >&5 $as_echo "$flag" >&6; } if test "x$flag" != xno; then PTHREAD_CFLAGS="$flag $PTHREAD_CFLAGS" fi { $as_echo "$as_me:${as_lineno-$LINENO}: checking for PTHREAD_PRIO_INHERIT" >&5 $as_echo_n "checking for PTHREAD_PRIO_INHERIT... " >&6; } if ${ax_cv_PTHREAD_PRIO_INHERIT+:} false; then : $as_echo_n "(cached) " >&6 else cat confdefs.h - <<_ACEOF >conftest.$ac_ext /* end confdefs.h. */ #include int main () { int i = PTHREAD_PRIO_INHERIT; ; return 0; } _ACEOF if ac_fn_c_try_link "$LINENO"; then : ax_cv_PTHREAD_PRIO_INHERIT=yes else ax_cv_PTHREAD_PRIO_INHERIT=no fi rm -f core conftest.err conftest.$ac_objext \ conftest$ac_exeext conftest.$ac_ext fi { $as_echo "$as_me:${as_lineno-$LINENO}: result: $ax_cv_PTHREAD_PRIO_INHERIT" >&5 $as_echo "$ax_cv_PTHREAD_PRIO_INHERIT" >&6; } if test "x$ax_cv_PTHREAD_PRIO_INHERIT" = "xyes"; then : $as_echo "#define HAVE_PTHREAD_PRIO_INHERIT 1" >>confdefs.h fi LIBS="$save_LIBS" CFLAGS="$save_CFLAGS" # More AIX lossage: compile with *_r variant if test "x$GCC" != xyes; then case $host_os in aix*) case "x/$CC" in #( x*/c89|x*/c89_128|x*/c99|x*/c99_128|x*/cc|x*/cc128|x*/xlc|x*/xlc_v6|x*/xlc128|x*/xlc128_v6) : #handle absolute path differently from PATH based program lookup case "x$CC" in #( x/*) : if as_fn_executable_p ${CC}_r; then : PTHREAD_CC="${CC}_r" fi ;; #( *) : for ac_prog in ${CC}_r do # Extract the first word of "$ac_prog", so it can be a program name with args. set dummy $ac_prog; ac_word=$2 { $as_echo "$as_me:${as_lineno-$LINENO}: checking for $ac_word" >&5 $as_echo_n "checking for $ac_word... " >&6; } if ${ac_cv_prog_PTHREAD_CC+:} false; then : $as_echo_n "(cached) " >&6 else if test -n "$PTHREAD_CC"; then ac_cv_prog_PTHREAD_CC="$PTHREAD_CC" # Let the user override the test. else as_save_IFS=$IFS; IFS=$PATH_SEPARATOR for as_dir in $PATH do IFS=$as_save_IFS test -z "$as_dir" && as_dir=. for ac_exec_ext in '' $ac_executable_extensions; do if as_fn_executable_p "$as_dir/$ac_word$ac_exec_ext"; then ac_cv_prog_PTHREAD_CC="$ac_prog" $as_echo "$as_me:${as_lineno-$LINENO}: found $as_dir/$ac_word$ac_exec_ext" >&5 break 2 fi done done IFS=$as_save_IFS fi fi PTHREAD_CC=$ac_cv_prog_PTHREAD_CC if test -n "$PTHREAD_CC"; then { $as_echo "$as_me:${as_lineno-$LINENO}: result: $PTHREAD_CC" >&5 $as_echo "$PTHREAD_CC" >&6; } else { $as_echo "$as_me:${as_lineno-$LINENO}: result: no" >&5 $as_echo "no" >&6; } fi test -n "$PTHREAD_CC" && break done test -n "$PTHREAD_CC" || PTHREAD_CC="$CC" ;; esac ;; #( *) : ;; esac ;; esac fi fi test -n "$PTHREAD_CC" || PTHREAD_CC="$CC" # Finally, execute ACTION-IF-FOUND/ACTION-IF-NOT-FOUND: if test x"$ax_pthread_ok" = xyes; then CC="$PTHREAD_CC" CFLAGS="$CFLAGS $PTHREAD_CFLAGS" LIBS="$PTHREAD_LIBS $LIBS" : else ax_pthread_ok=no { { $as_echo "$as_me:${as_lineno-$LINENO}: error: in \`$ac_pwd':" >&5 $as_echo "$as_me: error: in \`$ac_pwd':" >&2;} as_fn_error $? "This FRR version needs pthreads See \`config.log' for more details" "$LINENO" 5; } fi ac_ext=c ac_cpp='$CPP $CPPFLAGS' ac_compile='$CC -c $CFLAGS $CPPFLAGS conftest.$ac_ext >&5' ac_link='$CC -o conftest$ac_exeext $CFLAGS $CPPFLAGS $LDFLAGS conftest.$ac_ext $LIBS >&5' ac_compiler_gnu=$ac_cv_c_compiler_gnu { $as_echo "$as_me:${as_lineno-$LINENO}: checking for library containing pthread_condattr_setclock" >&5 $as_echo_n "checking for library containing pthread_condattr_setclock... " >&6; } if ${ac_cv_search_pthread_condattr_setclock+:} false; then : $as_echo_n "(cached) " >&6 else ac_func_search_save_LIBS=$LIBS cat confdefs.h - <<_ACEOF >conftest.$ac_ext /* end confdefs.h. */ /* Override any GCC internal prototype to avoid an error. Use char because int might match the return type of a GCC builtin and then its argument prototype would still apply. */ #ifdef __cplusplus extern "C" #endif char pthread_condattr_setclock (); int main () { return pthread_condattr_setclock (); ; return 0; } _ACEOF for ac_lib in '' ; do if test -z "$ac_lib"; then ac_res="none required" else ac_res=-l$ac_lib LIBS="-l$ac_lib $ac_func_search_save_LIBS" fi if ac_fn_c_try_link "$LINENO"; then : ac_cv_search_pthread_condattr_setclock=$ac_res fi rm -f core conftest.err conftest.$ac_objext \ conftest$ac_exeext if ${ac_cv_search_pthread_condattr_setclock+:} false; then : break fi done if ${ac_cv_search_pthread_condattr_setclock+:} false; then : else ac_cv_search_pthread_condattr_setclock=no fi rm conftest.$ac_ext LIBS=$ac_func_search_save_LIBS fi { $as_echo "$as_me:${as_lineno-$LINENO}: result: $ac_cv_search_pthread_condattr_setclock" >&5 $as_echo "$ac_cv_search_pthread_condattr_setclock" >&6; } ac_res=$ac_cv_search_pthread_condattr_setclock if test "$ac_res" != no; then : test "$ac_res" = "none required" || LIBS="$ac_res $LIBS" frr_cv_pthread_condattr_setclock=yes else frr_cv_pthread_condattr_setclock=no fi if test "$frr_cv_pthread_condattr_setclock" = yes; then $as_echo "#define HAVE_PTHREAD_CONDATTR_SETCLOCK 1" >>confdefs.h fi { $as_echo "$as_me:${as_lineno-$LINENO}: checking whether ln -s works" >&5 $as_echo_n "checking whether ln -s works... " >&6; } LN_S=$as_ln_s if test "$LN_S" = "ln -s"; then { $as_echo "$as_me:${as_lineno-$LINENO}: result: yes" >&5 $as_echo "yes" >&6; } else { $as_echo "$as_me:${as_lineno-$LINENO}: result: no, using $LN_S" >&5 $as_echo "no, using $LN_S" >&6; } fi if test -n "$ac_tool_prefix"; then # Extract the first word of "${ac_tool_prefix}ar", so it can be a program name with args. set dummy ${ac_tool_prefix}ar; ac_word=$2 { $as_echo "$as_me:${as_lineno-$LINENO}: checking for $ac_word" >&5 $as_echo_n "checking for $ac_word... " >&6; } if ${ac_cv_prog_AR+:} false; then : $as_echo_n "(cached) " >&6 else if test -n "$AR"; then ac_cv_prog_AR="$AR" # Let the user override the test. else as_save_IFS=$IFS; IFS=$PATH_SEPARATOR for as_dir in $PATH do IFS=$as_save_IFS test -z "$as_dir" && as_dir=. for ac_exec_ext in '' $ac_executable_extensions; do if as_fn_executable_p "$as_dir/$ac_word$ac_exec_ext"; then ac_cv_prog_AR="${ac_tool_prefix}ar" $as_echo "$as_me:${as_lineno-$LINENO}: found $as_dir/$ac_word$ac_exec_ext" >&5 break 2 fi done done IFS=$as_save_IFS fi fi AR=$ac_cv_prog_AR if test -n "$AR"; then { $as_echo "$as_me:${as_lineno-$LINENO}: result: $AR" >&5 $as_echo "$AR" >&6; } else { $as_echo "$as_me:${as_lineno-$LINENO}: result: no" >&5 $as_echo "no" >&6; } fi fi if test -z "$ac_cv_prog_AR"; then ac_ct_AR=$AR # Extract the first word of "ar", so it can be a program name with args. set dummy ar; ac_word=$2 { $as_echo "$as_me:${as_lineno-$LINENO}: checking for $ac_word" >&5 $as_echo_n "checking for $ac_word... " >&6; } if ${ac_cv_prog_ac_ct_AR+:} false; then : $as_echo_n "(cached) " >&6 else if test -n "$ac_ct_AR"; then ac_cv_prog_ac_ct_AR="$ac_ct_AR" # Let the user override the test. else as_save_IFS=$IFS; IFS=$PATH_SEPARATOR for as_dir in $PATH do IFS=$as_save_IFS test -z "$as_dir" && as_dir=. for ac_exec_ext in '' $ac_executable_extensions; do if as_fn_executable_p "$as_dir/$ac_word$ac_exec_ext"; then ac_cv_prog_ac_ct_AR="ar" $as_echo "$as_me:${as_lineno-$LINENO}: found $as_dir/$ac_word$ac_exec_ext" >&5 break 2 fi done done IFS=$as_save_IFS fi fi ac_ct_AR=$ac_cv_prog_ac_ct_AR if test -n "$ac_ct_AR"; then { $as_echo "$as_me:${as_lineno-$LINENO}: result: $ac_ct_AR" >&5 $as_echo "$ac_ct_AR" >&6; } else { $as_echo "$as_me:${as_lineno-$LINENO}: result: no" >&5 $as_echo "no" >&6; } fi if test "x$ac_ct_AR" = x; then AR="" else case $cross_compiling:$ac_tool_warned in yes:) { $as_echo "$as_me:${as_lineno-$LINENO}: WARNING: using cross tools not prefixed with host triplet" >&5 $as_echo "$as_me: WARNING: using cross tools not prefixed with host triplet" >&2;} ac_tool_warned=yes ;; esac AR=$ac_ct_AR fi else AR="$ac_cv_prog_AR" fi # Check whether --enable-static-bin was given. if test "${enable_static_bin+set}" = set; then : enableval=$enable_static_bin; fi case `pwd` in *\ * | *\ *) { $as_echo "$as_me:${as_lineno-$LINENO}: WARNING: Libtool does not cope well with whitespace in \`pwd\`" >&5 $as_echo "$as_me: WARNING: Libtool does not cope well with whitespace in \`pwd\`" >&2;} ;; esac macro_version='2.4.6' macro_revision='2.4.6' ltmain=$ac_aux_dir/ltmain.sh # Backslashify metacharacters that are still active within # double-quoted strings. sed_quote_subst='s/\(["`$\\]\)/\\\1/g' # Same as above, but do not quote variable references. double_quote_subst='s/\(["`\\]\)/\\\1/g' # Sed substitution to delay expansion of an escaped shell variable in a # double_quote_subst'ed string. delay_variable_subst='s/\\\\\\\\\\\$/\\\\\\$/g' # Sed substitution to delay expansion of an escaped single quote. delay_single_quote_subst='s/'\''/'\'\\\\\\\'\''/g' # Sed substitution to avoid accidental globbing in evaled expressions no_glob_subst='s/\*/\\\*/g' ECHO='\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\' ECHO=$ECHO$ECHO$ECHO$ECHO$ECHO ECHO=$ECHO$ECHO$ECHO$ECHO$ECHO$ECHO { $as_echo "$as_me:${as_lineno-$LINENO}: checking how to print strings" >&5 $as_echo_n "checking how to print strings... " >&6; } # Test print first, because it will be a builtin if present. if test "X`( print -r -- -n ) 2>/dev/null`" = X-n && \ test "X`print -r -- $ECHO 2>/dev/null`" = "X$ECHO"; then ECHO='print -r --' elif test "X`printf %s $ECHO 2>/dev/null`" = "X$ECHO"; then ECHO='printf %s\n' else # Use this function as a fallback that always works. func_fallback_echo () { eval 'cat <<_LTECHO_EOF $1 _LTECHO_EOF' } ECHO='func_fallback_echo' fi # func_echo_all arg... # Invoke $ECHO with all args, space-separated. func_echo_all () { $ECHO "" } case $ECHO in printf*) { $as_echo "$as_me:${as_lineno-$LINENO}: result: printf" >&5 $as_echo "printf" >&6; } ;; print*) { $as_echo "$as_me:${as_lineno-$LINENO}: result: print -r" >&5 $as_echo "print -r" >&6; } ;; *) { $as_echo "$as_me:${as_lineno-$LINENO}: result: cat" >&5 $as_echo "cat" >&6; } ;; esac { $as_echo "$as_me:${as_lineno-$LINENO}: checking for a sed that does not truncate output" >&5 $as_echo_n "checking for a sed that does not truncate output... " >&6; } if ${ac_cv_path_SED+:} false; then : $as_echo_n "(cached) " >&6 else ac_script=s/aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa/bbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbb/ for ac_i in 1 2 3 4 5 6 7; do ac_script="$ac_script$as_nl$ac_script" done echo "$ac_script" 2>/dev/null | sed 99q >conftest.sed { ac_script=; unset ac_script;} if test -z "$SED"; then ac_path_SED_found=false # Loop through the user's path and test for each of PROGNAME-LIST as_save_IFS=$IFS; IFS=$PATH_SEPARATOR for as_dir in $PATH do IFS=$as_save_IFS test -z "$as_dir" && as_dir=. for ac_prog in sed gsed; do for ac_exec_ext in '' $ac_executable_extensions; do ac_path_SED="$as_dir/$ac_prog$ac_exec_ext" as_fn_executable_p "$ac_path_SED" || continue # Check for GNU ac_path_SED and select it if it is found. # Check for GNU $ac_path_SED case `"$ac_path_SED" --version 2>&1` in *GNU*) ac_cv_path_SED="$ac_path_SED" ac_path_SED_found=:;; *) ac_count=0 $as_echo_n 0123456789 >"conftest.in" while : do cat "conftest.in" "conftest.in" >"conftest.tmp" mv "conftest.tmp" "conftest.in" cp "conftest.in" "conftest.nl" $as_echo '' >> "conftest.nl" "$ac_path_SED" -f conftest.sed < "conftest.nl" >"conftest.out" 2>/dev/null || break diff "conftest.out" "conftest.nl" >/dev/null 2>&1 || break as_fn_arith $ac_count + 1 && ac_count=$as_val if test $ac_count -gt ${ac_path_SED_max-0}; then # Best one so far, save it but keep looking for a better one ac_cv_path_SED="$ac_path_SED" ac_path_SED_max=$ac_count fi # 10*(2^10) chars as input seems more than enough test $ac_count -gt 10 && break done rm -f conftest.in conftest.tmp conftest.nl conftest.out;; esac $ac_path_SED_found && break 3 done done done IFS=$as_save_IFS if test -z "$ac_cv_path_SED"; then as_fn_error $? "no acceptable sed could be found in \$PATH" "$LINENO" 5 fi else ac_cv_path_SED=$SED fi fi { $as_echo "$as_me:${as_lineno-$LINENO}: result: $ac_cv_path_SED" >&5 $as_echo "$ac_cv_path_SED" >&6; } SED="$ac_cv_path_SED" rm -f conftest.sed test -z "$SED" && SED=sed Xsed="$SED -e 1s/^X//" { $as_echo "$as_me:${as_lineno-$LINENO}: checking for fgrep" >&5 $as_echo_n "checking for fgrep... " >&6; } if ${ac_cv_path_FGREP+:} false; then : $as_echo_n "(cached) " >&6 else if echo 'ab*c' | $GREP -F 'ab*c' >/dev/null 2>&1 then ac_cv_path_FGREP="$GREP -F" else if test -z "$FGREP"; then ac_path_FGREP_found=false # Loop through the user's path and test for each of PROGNAME-LIST as_save_IFS=$IFS; IFS=$PATH_SEPARATOR for as_dir in $PATH$PATH_SEPARATOR/usr/xpg4/bin do IFS=$as_save_IFS test -z "$as_dir" && as_dir=. for ac_prog in fgrep; do for ac_exec_ext in '' $ac_executable_extensions; do ac_path_FGREP="$as_dir/$ac_prog$ac_exec_ext" as_fn_executable_p "$ac_path_FGREP" || continue # Check for GNU ac_path_FGREP and select it if it is found. # Check for GNU $ac_path_FGREP case `"$ac_path_FGREP" --version 2>&1` in *GNU*) ac_cv_path_FGREP="$ac_path_FGREP" ac_path_FGREP_found=:;; *) ac_count=0 $as_echo_n 0123456789 >"conftest.in" while : do cat "conftest.in" "conftest.in" >"conftest.tmp" mv "conftest.tmp" "conftest.in" cp "conftest.in" "conftest.nl" $as_echo 'FGREP' >> "conftest.nl" "$ac_path_FGREP" FGREP < "conftest.nl" >"conftest.out" 2>/dev/null || break diff "conftest.out" "conftest.nl" >/dev/null 2>&1 || break as_fn_arith $ac_count + 1 && ac_count=$as_val if test $ac_count -gt ${ac_path_FGREP_max-0}; then # Best one so far, save it but keep looking for a better one ac_cv_path_FGREP="$ac_path_FGREP" ac_path_FGREP_max=$ac_count fi # 10*(2^10) chars as input seems more than enough test $ac_count -gt 10 && break done rm -f conftest.in conftest.tmp conftest.nl conftest.out;; esac $ac_path_FGREP_found && break 3 done done done IFS=$as_save_IFS if test -z "$ac_cv_path_FGREP"; then as_fn_error $? "no acceptable fgrep could be found in $PATH$PATH_SEPARATOR/usr/xpg4/bin" "$LINENO" 5 fi else ac_cv_path_FGREP=$FGREP fi fi fi { $as_echo "$as_me:${as_lineno-$LINENO}: result: $ac_cv_path_FGREP" >&5 $as_echo "$ac_cv_path_FGREP" >&6; } FGREP="$ac_cv_path_FGREP" test -z "$GREP" && GREP=grep # Check whether --with-gnu-ld was given. if test "${with_gnu_ld+set}" = set; then : withval=$with_gnu_ld; test no = "$withval" || with_gnu_ld=yes else with_gnu_ld=no fi ac_prog=ld if test yes = "$GCC"; then # Check if gcc -print-prog-name=ld gives a path. { $as_echo "$as_me:${as_lineno-$LINENO}: checking for ld used by $CC" >&5 $as_echo_n "checking for ld used by $CC... " >&6; } case $host in *-*-mingw*) # gcc leaves a trailing carriage return, which upsets mingw ac_prog=`($CC -print-prog-name=ld) 2>&5 | tr -d '\015'` ;; *) ac_prog=`($CC -print-prog-name=ld) 2>&5` ;; esac case $ac_prog in # Accept absolute paths. [\\/]* | ?:[\\/]*) re_direlt='/[^/][^/]*/\.\./' # Canonicalize the pathname of ld ac_prog=`$ECHO "$ac_prog"| $SED 's%\\\\%/%g'` while $ECHO "$ac_prog" | $GREP "$re_direlt" > /dev/null 2>&1; do ac_prog=`$ECHO $ac_prog| $SED "s%$re_direlt%/%"` done test -z "$LD" && LD=$ac_prog ;; "") # If it fails, then pretend we aren't using GCC. ac_prog=ld ;; *) # If it is relative, then search for the first ld in PATH. with_gnu_ld=unknown ;; esac elif test yes = "$with_gnu_ld"; then { $as_echo "$as_me:${as_lineno-$LINENO}: checking for GNU ld" >&5 $as_echo_n "checking for GNU ld... " >&6; } else { $as_echo "$as_me:${as_lineno-$LINENO}: checking for non-GNU ld" >&5 $as_echo_n "checking for non-GNU ld... " >&6; } fi if ${lt_cv_path_LD+:} false; then : $as_echo_n "(cached) " >&6 else if test -z "$LD"; then lt_save_ifs=$IFS; IFS=$PATH_SEPARATOR for ac_dir in $PATH; do IFS=$lt_save_ifs test -z "$ac_dir" && ac_dir=. if test -f "$ac_dir/$ac_prog" || test -f "$ac_dir/$ac_prog$ac_exeext"; then lt_cv_path_LD=$ac_dir/$ac_prog # Check to see if the program is GNU ld. I'd rather use --version, # but apparently some variants of GNU ld only accept -v. # Break only if it was the GNU/non-GNU ld that we prefer. case `"$lt_cv_path_LD" -v 2>&1 &5 $as_echo "$LD" >&6; } else { $as_echo "$as_me:${as_lineno-$LINENO}: result: no" >&5 $as_echo "no" >&6; } fi test -z "$LD" && as_fn_error $? "no acceptable ld found in \$PATH" "$LINENO" 5 { $as_echo "$as_me:${as_lineno-$LINENO}: checking if the linker ($LD) is GNU ld" >&5 $as_echo_n "checking if the linker ($LD) is GNU ld... " >&6; } if ${lt_cv_prog_gnu_ld+:} false; then : $as_echo_n "(cached) " >&6 else # I'd rather use --version here, but apparently some GNU lds only accept -v. case `$LD -v 2>&1 &5 $as_echo "$lt_cv_prog_gnu_ld" >&6; } with_gnu_ld=$lt_cv_prog_gnu_ld { $as_echo "$as_me:${as_lineno-$LINENO}: checking for BSD- or MS-compatible name lister (nm)" >&5 $as_echo_n "checking for BSD- or MS-compatible name lister (nm)... " >&6; } if ${lt_cv_path_NM+:} false; then : $as_echo_n "(cached) " >&6 else if test -n "$NM"; then # Let the user override the test. lt_cv_path_NM=$NM else lt_nm_to_check=${ac_tool_prefix}nm if test -n "$ac_tool_prefix" && test "$build" = "$host"; then lt_nm_to_check="$lt_nm_to_check nm" fi for lt_tmp_nm in $lt_nm_to_check; do lt_save_ifs=$IFS; IFS=$PATH_SEPARATOR for ac_dir in $PATH /usr/ccs/bin/elf /usr/ccs/bin /usr/ucb /bin; do IFS=$lt_save_ifs test -z "$ac_dir" && ac_dir=. tmp_nm=$ac_dir/$lt_tmp_nm if test -f "$tmp_nm" || test -f "$tmp_nm$ac_exeext"; then # Check to see if the nm accepts a BSD-compat flag. # Adding the 'sed 1q' prevents false positives on HP-UX, which says: # nm: unknown option "B" ignored # Tru64's nm complains that /dev/null is an invalid object file # MSYS converts /dev/null to NUL, MinGW nm treats NUL as empty case $build_os in mingw*) lt_bad_file=conftest.nm/nofile ;; *) lt_bad_file=/dev/null ;; esac case `"$tmp_nm" -B $lt_bad_file 2>&1 | sed '1q'` in *$lt_bad_file* | *'Invalid file or object type'*) lt_cv_path_NM="$tmp_nm -B" break 2 ;; *) case `"$tmp_nm" -p /dev/null 2>&1 | sed '1q'` in */dev/null*) lt_cv_path_NM="$tmp_nm -p" break 2 ;; *) lt_cv_path_NM=${lt_cv_path_NM="$tmp_nm"} # keep the first match, but continue # so that we can try to find one that supports BSD flags ;; esac ;; esac fi done IFS=$lt_save_ifs done : ${lt_cv_path_NM=no} fi fi { $as_echo "$as_me:${as_lineno-$LINENO}: result: $lt_cv_path_NM" >&5 $as_echo "$lt_cv_path_NM" >&6; } if test no != "$lt_cv_path_NM"; then NM=$lt_cv_path_NM else # Didn't find any BSD compatible name lister, look for dumpbin. if test -n "$DUMPBIN"; then : # Let the user override the test. else if test -n "$ac_tool_prefix"; then for ac_prog in dumpbin "link -dump" do # Extract the first word of "$ac_tool_prefix$ac_prog", so it can be a program name with args. set dummy $ac_tool_prefix$ac_prog; ac_word=$2 { $as_echo "$as_me:${as_lineno-$LINENO}: checking for $ac_word" >&5 $as_echo_n "checking for $ac_word... " >&6; } if ${ac_cv_prog_DUMPBIN+:} false; then : $as_echo_n "(cached) " >&6 else if test -n "$DUMPBIN"; then ac_cv_prog_DUMPBIN="$DUMPBIN" # Let the user override the test. else as_save_IFS=$IFS; IFS=$PATH_SEPARATOR for as_dir in $PATH do IFS=$as_save_IFS test -z "$as_dir" && as_dir=. for ac_exec_ext in '' $ac_executable_extensions; do if as_fn_executable_p "$as_dir/$ac_word$ac_exec_ext"; then ac_cv_prog_DUMPBIN="$ac_tool_prefix$ac_prog" $as_echo "$as_me:${as_lineno-$LINENO}: found $as_dir/$ac_word$ac_exec_ext" >&5 break 2 fi done done IFS=$as_save_IFS fi fi DUMPBIN=$ac_cv_prog_DUMPBIN if test -n "$DUMPBIN"; then { $as_echo "$as_me:${as_lineno-$LINENO}: result: $DUMPBIN" >&5 $as_echo "$DUMPBIN" >&6; } else { $as_echo "$as_me:${as_lineno-$LINENO}: result: no" >&5 $as_echo "no" >&6; } fi test -n "$DUMPBIN" && break done fi if test -z "$DUMPBIN"; then ac_ct_DUMPBIN=$DUMPBIN for ac_prog in dumpbin "link -dump" do # Extract the first word of "$ac_prog", so it can be a program name with args. set dummy $ac_prog; ac_word=$2 { $as_echo "$as_me:${as_lineno-$LINENO}: checking for $ac_word" >&5 $as_echo_n "checking for $ac_word... " >&6; } if ${ac_cv_prog_ac_ct_DUMPBIN+:} false; then : $as_echo_n "(cached) " >&6 else if test -n "$ac_ct_DUMPBIN"; then ac_cv_prog_ac_ct_DUMPBIN="$ac_ct_DUMPBIN" # Let the user override the test. else as_save_IFS=$IFS; IFS=$PATH_SEPARATOR for as_dir in $PATH do IFS=$as_save_IFS test -z "$as_dir" && as_dir=. for ac_exec_ext in '' $ac_executable_extensions; do if as_fn_executable_p "$as_dir/$ac_word$ac_exec_ext"; then ac_cv_prog_ac_ct_DUMPBIN="$ac_prog" $as_echo "$as_me:${as_lineno-$LINENO}: found $as_dir/$ac_word$ac_exec_ext" >&5 break 2 fi done done IFS=$as_save_IFS fi fi ac_ct_DUMPBIN=$ac_cv_prog_ac_ct_DUMPBIN if test -n "$ac_ct_DUMPBIN"; then { $as_echo "$as_me:${as_lineno-$LINENO}: result: $ac_ct_DUMPBIN" >&5 $as_echo "$ac_ct_DUMPBIN" >&6; } else { $as_echo "$as_me:${as_lineno-$LINENO}: result: no" >&5 $as_echo "no" >&6; } fi test -n "$ac_ct_DUMPBIN" && break done if test "x$ac_ct_DUMPBIN" = x; then DUMPBIN=":" else case $cross_compiling:$ac_tool_warned in yes:) { $as_echo "$as_me:${as_lineno-$LINENO}: WARNING: using cross tools not prefixed with host triplet" >&5 $as_echo "$as_me: WARNING: using cross tools not prefixed with host triplet" >&2;} ac_tool_warned=yes ;; esac DUMPBIN=$ac_ct_DUMPBIN fi fi case `$DUMPBIN -symbols -headers /dev/null 2>&1 | sed '1q'` in *COFF*) DUMPBIN="$DUMPBIN -symbols -headers" ;; *) DUMPBIN=: ;; esac fi if test : != "$DUMPBIN"; then NM=$DUMPBIN fi fi test -z "$NM" && NM=nm { $as_echo "$as_me:${as_lineno-$LINENO}: checking the name lister ($NM) interface" >&5 $as_echo_n "checking the name lister ($NM) interface... " >&6; } if ${lt_cv_nm_interface+:} false; then : $as_echo_n "(cached) " >&6 else lt_cv_nm_interface="BSD nm" echo "int some_variable = 0;" > conftest.$ac_ext (eval echo "\"\$as_me:$LINENO: $ac_compile\"" >&5) (eval "$ac_compile" 2>conftest.err) cat conftest.err >&5 (eval echo "\"\$as_me:$LINENO: $NM \\\"conftest.$ac_objext\\\"\"" >&5) (eval "$NM \"conftest.$ac_objext\"" 2>conftest.err > conftest.out) cat conftest.err >&5 (eval echo "\"\$as_me:$LINENO: output\"" >&5) cat conftest.out >&5 if $GREP 'External.*some_variable' conftest.out > /dev/null; then lt_cv_nm_interface="MS dumpbin" fi rm -f conftest* fi { $as_echo "$as_me:${as_lineno-$LINENO}: result: $lt_cv_nm_interface" >&5 $as_echo "$lt_cv_nm_interface" >&6; } # find the maximum length of command line arguments { $as_echo "$as_me:${as_lineno-$LINENO}: checking the maximum length of command line arguments" >&5 $as_echo_n "checking the maximum length of command line arguments... " >&6; } if ${lt_cv_sys_max_cmd_len+:} false; then : $as_echo_n "(cached) " >&6 else i=0 teststring=ABCD case $build_os in msdosdjgpp*) # On DJGPP, this test can blow up pretty badly due to problems in libc # (any single argument exceeding 2000 bytes causes a buffer overrun # during glob expansion). Even if it were fixed, the result of this # check would be larger than it should be. lt_cv_sys_max_cmd_len=12288; # 12K is about right ;; gnu*) # Under GNU Hurd, this test is not required because there is # no limit to the length of command line arguments. # Libtool will interpret -1 as no limit whatsoever lt_cv_sys_max_cmd_len=-1; ;; cygwin* | mingw* | cegcc*) # On Win9x/ME, this test blows up -- it succeeds, but takes # about 5 minutes as the teststring grows exponentially. # Worse, since 9x/ME are not pre-emptively multitasking, # you end up with a "frozen" computer, even though with patience # the test eventually succeeds (with a max line length of 256k). # Instead, let's just punt: use the minimum linelength reported by # all of the supported platforms: 8192 (on NT/2K/XP). lt_cv_sys_max_cmd_len=8192; ;; mint*) # On MiNT this can take a long time and run out of memory. lt_cv_sys_max_cmd_len=8192; ;; amigaos*) # On AmigaOS with pdksh, this test takes hours, literally. # So we just punt and use a minimum line length of 8192. lt_cv_sys_max_cmd_len=8192; ;; bitrig* | darwin* | dragonfly* | freebsd* | netbsd* | openbsd*) # This has been around since 386BSD, at least. Likely further. if test -x /sbin/sysctl; then lt_cv_sys_max_cmd_len=`/sbin/sysctl -n kern.argmax` elif test -x /usr/sbin/sysctl; then lt_cv_sys_max_cmd_len=`/usr/sbin/sysctl -n kern.argmax` else lt_cv_sys_max_cmd_len=65536 # usable default for all BSDs fi # And add a safety zone lt_cv_sys_max_cmd_len=`expr $lt_cv_sys_max_cmd_len \/ 4` lt_cv_sys_max_cmd_len=`expr $lt_cv_sys_max_cmd_len \* 3` ;; interix*) # We know the value 262144 and hardcode it with a safety zone (like BSD) lt_cv_sys_max_cmd_len=196608 ;; os2*) # The test takes a long time on OS/2. lt_cv_sys_max_cmd_len=8192 ;; osf*) # Dr. Hans Ekkehard Plesser reports seeing a kernel panic running configure # due to this test when exec_disable_arg_limit is 1 on Tru64. It is not # nice to cause kernel panics so lets avoid the loop below. # First set a reasonable default. lt_cv_sys_max_cmd_len=16384 # if test -x /sbin/sysconfig; then case `/sbin/sysconfig -q proc exec_disable_arg_limit` in *1*) lt_cv_sys_max_cmd_len=-1 ;; esac fi ;; sco3.2v5*) lt_cv_sys_max_cmd_len=102400 ;; sysv5* | sco5v6* | sysv4.2uw2*) kargmax=`grep ARG_MAX /etc/conf/cf.d/stune 2>/dev/null` if test -n "$kargmax"; then lt_cv_sys_max_cmd_len=`echo $kargmax | sed 's/.*[ ]//'` else lt_cv_sys_max_cmd_len=32768 fi ;; *) lt_cv_sys_max_cmd_len=`(getconf ARG_MAX) 2> /dev/null` if test -n "$lt_cv_sys_max_cmd_len" && \ test undefined != "$lt_cv_sys_max_cmd_len"; then lt_cv_sys_max_cmd_len=`expr $lt_cv_sys_max_cmd_len \/ 4` lt_cv_sys_max_cmd_len=`expr $lt_cv_sys_max_cmd_len \* 3` else # Make teststring a little bigger before we do anything with it. # a 1K string should be a reasonable start. for i in 1 2 3 4 5 6 7 8; do teststring=$teststring$teststring done SHELL=${SHELL-${CONFIG_SHELL-/bin/sh}} # If test is not a shell built-in, we'll probably end up computing a # maximum length that is only half of the actual maximum length, but # we can't tell. while { test X`env echo "$teststring$teststring" 2>/dev/null` \ = "X$teststring$teststring"; } >/dev/null 2>&1 && test 17 != "$i" # 1/2 MB should be enough do i=`expr $i + 1` teststring=$teststring$teststring done # Only check the string length outside the loop. lt_cv_sys_max_cmd_len=`expr "X$teststring" : ".*" 2>&1` teststring= # Add a significant safety factor because C++ compilers can tack on # massive amounts of additional arguments before passing them to the # linker. It appears as though 1/2 is a usable value. lt_cv_sys_max_cmd_len=`expr $lt_cv_sys_max_cmd_len \/ 2` fi ;; esac fi if test -n "$lt_cv_sys_max_cmd_len"; then { $as_echo "$as_me:${as_lineno-$LINENO}: result: $lt_cv_sys_max_cmd_len" >&5 $as_echo "$lt_cv_sys_max_cmd_len" >&6; } else { $as_echo "$as_me:${as_lineno-$LINENO}: result: none" >&5 $as_echo "none" >&6; } fi max_cmd_len=$lt_cv_sys_max_cmd_len : ${CP="cp -f"} : ${MV="mv -f"} : ${RM="rm -f"} if ( (MAIL=60; unset MAIL) || exit) >/dev/null 2>&1; then lt_unset=unset else lt_unset=false fi # test EBCDIC or ASCII case `echo X|tr X '\101'` in A) # ASCII based system # \n is not interpreted correctly by Solaris 8 /usr/ucb/tr lt_SP2NL='tr \040 \012' lt_NL2SP='tr \015\012 \040\040' ;; *) # EBCDIC based system lt_SP2NL='tr \100 \n' lt_NL2SP='tr \r\n \100\100' ;; esac { $as_echo "$as_me:${as_lineno-$LINENO}: checking how to convert $build file names to $host format" >&5 $as_echo_n "checking how to convert $build file names to $host format... " >&6; } if ${lt_cv_to_host_file_cmd+:} false; then : $as_echo_n "(cached) " >&6 else case $host in *-*-mingw* ) case $build in *-*-mingw* ) # actually msys lt_cv_to_host_file_cmd=func_convert_file_msys_to_w32 ;; *-*-cygwin* ) lt_cv_to_host_file_cmd=func_convert_file_cygwin_to_w32 ;; * ) # otherwise, assume *nix lt_cv_to_host_file_cmd=func_convert_file_nix_to_w32 ;; esac ;; *-*-cygwin* ) case $build in *-*-mingw* ) # actually msys lt_cv_to_host_file_cmd=func_convert_file_msys_to_cygwin ;; *-*-cygwin* ) lt_cv_to_host_file_cmd=func_convert_file_noop ;; * ) # otherwise, assume *nix lt_cv_to_host_file_cmd=func_convert_file_nix_to_cygwin ;; esac ;; * ) # unhandled hosts (and "normal" native builds) lt_cv_to_host_file_cmd=func_convert_file_noop ;; esac fi to_host_file_cmd=$lt_cv_to_host_file_cmd { $as_echo "$as_me:${as_lineno-$LINENO}: result: $lt_cv_to_host_file_cmd" >&5 $as_echo "$lt_cv_to_host_file_cmd" >&6; } { $as_echo "$as_me:${as_lineno-$LINENO}: checking how to convert $build file names to toolchain format" >&5 $as_echo_n "checking how to convert $build file names to toolchain format... " >&6; } if ${lt_cv_to_tool_file_cmd+:} false; then : $as_echo_n "(cached) " >&6 else #assume ordinary cross tools, or native build. lt_cv_to_tool_file_cmd=func_convert_file_noop case $host in *-*-mingw* ) case $build in *-*-mingw* ) # actually msys lt_cv_to_tool_file_cmd=func_convert_file_msys_to_w32 ;; esac ;; esac fi to_tool_file_cmd=$lt_cv_to_tool_file_cmd { $as_echo "$as_me:${as_lineno-$LINENO}: result: $lt_cv_to_tool_file_cmd" >&5 $as_echo "$lt_cv_to_tool_file_cmd" >&6; } { $as_echo "$as_me:${as_lineno-$LINENO}: checking for $LD option to reload object files" >&5 $as_echo_n "checking for $LD option to reload object files... " >&6; } if ${lt_cv_ld_reload_flag+:} false; then : $as_echo_n "(cached) " >&6 else lt_cv_ld_reload_flag='-r' fi { $as_echo "$as_me:${as_lineno-$LINENO}: result: $lt_cv_ld_reload_flag" >&5 $as_echo "$lt_cv_ld_reload_flag" >&6; } reload_flag=$lt_cv_ld_reload_flag case $reload_flag in "" | " "*) ;; *) reload_flag=" $reload_flag" ;; esac reload_cmds='$LD$reload_flag -o $output$reload_objs' case $host_os in cygwin* | mingw* | pw32* | cegcc*) if test yes != "$GCC"; then reload_cmds=false fi ;; darwin*) if test yes = "$GCC"; then reload_cmds='$LTCC $LTCFLAGS -nostdlib $wl-r -o $output$reload_objs' else reload_cmds='$LD$reload_flag -o $output$reload_objs' fi ;; esac if test -n "$ac_tool_prefix"; then # Extract the first word of "${ac_tool_prefix}objdump", so it can be a program name with args. set dummy ${ac_tool_prefix}objdump; ac_word=$2 { $as_echo "$as_me:${as_lineno-$LINENO}: checking for $ac_word" >&5 $as_echo_n "checking for $ac_word... " >&6; } if ${ac_cv_prog_OBJDUMP+:} false; then : $as_echo_n "(cached) " >&6 else if test -n "$OBJDUMP"; then ac_cv_prog_OBJDUMP="$OBJDUMP" # Let the user override the test. else as_save_IFS=$IFS; IFS=$PATH_SEPARATOR for as_dir in $PATH do IFS=$as_save_IFS test -z "$as_dir" && as_dir=. for ac_exec_ext in '' $ac_executable_extensions; do if as_fn_executable_p "$as_dir/$ac_word$ac_exec_ext"; then ac_cv_prog_OBJDUMP="${ac_tool_prefix}objdump" $as_echo "$as_me:${as_lineno-$LINENO}: found $as_dir/$ac_word$ac_exec_ext" >&5 break 2 fi done done IFS=$as_save_IFS fi fi OBJDUMP=$ac_cv_prog_OBJDUMP if test -n "$OBJDUMP"; then { $as_echo "$as_me:${as_lineno-$LINENO}: result: $OBJDUMP" >&5 $as_echo "$OBJDUMP" >&6; } else { $as_echo "$as_me:${as_lineno-$LINENO}: result: no" >&5 $as_echo "no" >&6; } fi fi if test -z "$ac_cv_prog_OBJDUMP"; then ac_ct_OBJDUMP=$OBJDUMP # Extract the first word of "objdump", so it can be a program name with args. set dummy objdump; ac_word=$2 { $as_echo "$as_me:${as_lineno-$LINENO}: checking for $ac_word" >&5 $as_echo_n "checking for $ac_word... " >&6; } if ${ac_cv_prog_ac_ct_OBJDUMP+:} false; then : $as_echo_n "(cached) " >&6 else if test -n "$ac_ct_OBJDUMP"; then ac_cv_prog_ac_ct_OBJDUMP="$ac_ct_OBJDUMP" # Let the user override the test. else as_save_IFS=$IFS; IFS=$PATH_SEPARATOR for as_dir in $PATH do IFS=$as_save_IFS test -z "$as_dir" && as_dir=. for ac_exec_ext in '' $ac_executable_extensions; do if as_fn_executable_p "$as_dir/$ac_word$ac_exec_ext"; then ac_cv_prog_ac_ct_OBJDUMP="objdump" $as_echo "$as_me:${as_lineno-$LINENO}: found $as_dir/$ac_word$ac_exec_ext" >&5 break 2 fi done done IFS=$as_save_IFS fi fi ac_ct_OBJDUMP=$ac_cv_prog_ac_ct_OBJDUMP if test -n "$ac_ct_OBJDUMP"; then { $as_echo "$as_me:${as_lineno-$LINENO}: result: $ac_ct_OBJDUMP" >&5 $as_echo "$ac_ct_OBJDUMP" >&6; } else { $as_echo "$as_me:${as_lineno-$LINENO}: result: no" >&5 $as_echo "no" >&6; } fi if test "x$ac_ct_OBJDUMP" = x; then OBJDUMP="false" else case $cross_compiling:$ac_tool_warned in yes:) { $as_echo "$as_me:${as_lineno-$LINENO}: WARNING: using cross tools not prefixed with host triplet" >&5 $as_echo "$as_me: WARNING: using cross tools not prefixed with host triplet" >&2;} ac_tool_warned=yes ;; esac OBJDUMP=$ac_ct_OBJDUMP fi else OBJDUMP="$ac_cv_prog_OBJDUMP" fi test -z "$OBJDUMP" && OBJDUMP=objdump { $as_echo "$as_me:${as_lineno-$LINENO}: checking how to recognize dependent libraries" >&5 $as_echo_n "checking how to recognize dependent libraries... " >&6; } if ${lt_cv_deplibs_check_method+:} false; then : $as_echo_n "(cached) " >&6 else lt_cv_file_magic_cmd='$MAGIC_CMD' lt_cv_file_magic_test_file= lt_cv_deplibs_check_method='unknown' # Need to set the preceding variable on all platforms that support # interlibrary dependencies. # 'none' -- dependencies not supported. # 'unknown' -- same as none, but documents that we really don't know. # 'pass_all' -- all dependencies passed with no checks. # 'test_compile' -- check by making test program. # 'file_magic [[regex]]' -- check by looking for files in library path # that responds to the $file_magic_cmd with a given extended regex. # If you have 'file' or equivalent on your system and you're not sure # whether 'pass_all' will *always* work, you probably want this one. case $host_os in aix[4-9]*) lt_cv_deplibs_check_method=pass_all ;; beos*) lt_cv_deplibs_check_method=pass_all ;; bsdi[45]*) lt_cv_deplibs_check_method='file_magic ELF [0-9][0-9]*-bit [ML]SB (shared object|dynamic lib)' lt_cv_file_magic_cmd='/usr/bin/file -L' lt_cv_file_magic_test_file=/shlib/libc.so ;; cygwin*) # func_win32_libid is a shell function defined in ltmain.sh lt_cv_deplibs_check_method='file_magic ^x86 archive import|^x86 DLL' lt_cv_file_magic_cmd='func_win32_libid' ;; mingw* | pw32*) # Base MSYS/MinGW do not provide the 'file' command needed by # func_win32_libid shell function, so use a weaker test based on 'objdump', # unless we find 'file', for example because we are cross-compiling. if ( file / ) >/dev/null 2>&1; then lt_cv_deplibs_check_method='file_magic ^x86 archive import|^x86 DLL' lt_cv_file_magic_cmd='func_win32_libid' else # Keep this pattern in sync with the one in func_win32_libid. lt_cv_deplibs_check_method='file_magic file format (pei*-i386(.*architecture: i386)?|pe-arm-wince|pe-x86-64)' lt_cv_file_magic_cmd='$OBJDUMP -f' fi ;; cegcc*) # use the weaker test based on 'objdump'. See mingw*. lt_cv_deplibs_check_method='file_magic file format pe-arm-.*little(.*architecture: arm)?' lt_cv_file_magic_cmd='$OBJDUMP -f' ;; darwin* | rhapsody*) lt_cv_deplibs_check_method=pass_all ;; freebsd* | dragonfly*) if echo __ELF__ | $CC -E - | $GREP __ELF__ > /dev/null; then case $host_cpu in i*86 ) # Not sure whether the presence of OpenBSD here was a mistake. # Let's accept both of them until this is cleared up. lt_cv_deplibs_check_method='file_magic (FreeBSD|OpenBSD|DragonFly)/i[3-9]86 (compact )?demand paged shared library' lt_cv_file_magic_cmd=/usr/bin/file lt_cv_file_magic_test_file=`echo /usr/lib/libc.so.*` ;; esac else lt_cv_deplibs_check_method=pass_all fi ;; haiku*) lt_cv_deplibs_check_method=pass_all ;; hpux10.20* | hpux11*) lt_cv_file_magic_cmd=/usr/bin/file case $host_cpu in ia64*) lt_cv_deplibs_check_method='file_magic (s[0-9][0-9][0-9]|ELF-[0-9][0-9]) shared object file - IA64' lt_cv_file_magic_test_file=/usr/lib/hpux32/libc.so ;; hppa*64*) lt_cv_deplibs_check_method='file_magic (s[0-9][0-9][0-9]|ELF[ -][0-9][0-9])(-bit)?( [LM]SB)? shared object( file)?[, -]* PA-RISC [0-9]\.[0-9]' lt_cv_file_magic_test_file=/usr/lib/pa20_64/libc.sl ;; *) lt_cv_deplibs_check_method='file_magic (s[0-9][0-9][0-9]|PA-RISC[0-9]\.[0-9]) shared library' lt_cv_file_magic_test_file=/usr/lib/libc.sl ;; esac ;; interix[3-9]*) # PIC code is broken on Interix 3.x, that's why |\.a not |_pic\.a here lt_cv_deplibs_check_method='match_pattern /lib[^/]+(\.so|\.a)$' ;; irix5* | irix6* | nonstopux*) case $LD in *-32|*"-32 ") libmagic=32-bit;; *-n32|*"-n32 ") libmagic=N32;; *-64|*"-64 ") libmagic=64-bit;; *) libmagic=never-match;; esac lt_cv_deplibs_check_method=pass_all ;; # This must be glibc/ELF. linux* | k*bsd*-gnu | kopensolaris*-gnu | gnu*) lt_cv_deplibs_check_method=pass_all ;; netbsd* | netbsdelf*-gnu) if echo __ELF__ | $CC -E - | $GREP __ELF__ > /dev/null; then lt_cv_deplibs_check_method='match_pattern /lib[^/]+(\.so\.[0-9]+\.[0-9]+|_pic\.a)$' else lt_cv_deplibs_check_method='match_pattern /lib[^/]+(\.so|_pic\.a)$' fi ;; newos6*) lt_cv_deplibs_check_method='file_magic ELF [0-9][0-9]*-bit [ML]SB (executable|dynamic lib)' lt_cv_file_magic_cmd=/usr/bin/file lt_cv_file_magic_test_file=/usr/lib/libnls.so ;; *nto* | *qnx*) lt_cv_deplibs_check_method=pass_all ;; openbsd* | bitrig*) if test -z "`echo __ELF__ | $CC -E - | $GREP __ELF__`"; then lt_cv_deplibs_check_method='match_pattern /lib[^/]+(\.so\.[0-9]+\.[0-9]+|\.so|_pic\.a)$' else lt_cv_deplibs_check_method='match_pattern /lib[^/]+(\.so\.[0-9]+\.[0-9]+|_pic\.a)$' fi ;; osf3* | osf4* | osf5*) lt_cv_deplibs_check_method=pass_all ;; rdos*) lt_cv_deplibs_check_method=pass_all ;; solaris*) lt_cv_deplibs_check_method=pass_all ;; sysv5* | sco3.2v5* | sco5v6* | unixware* | OpenUNIX* | sysv4*uw2*) lt_cv_deplibs_check_method=pass_all ;; sysv4 | sysv4.3*) case $host_vendor in motorola) lt_cv_deplibs_check_method='file_magic ELF [0-9][0-9]*-bit [ML]SB (shared object|dynamic lib) M[0-9][0-9]* Version [0-9]' lt_cv_file_magic_test_file=`echo /usr/lib/libc.so*` ;; ncr) lt_cv_deplibs_check_method=pass_all ;; sequent) lt_cv_file_magic_cmd='/bin/file' lt_cv_deplibs_check_method='file_magic ELF [0-9][0-9]*-bit [LM]SB (shared object|dynamic lib )' ;; sni) lt_cv_file_magic_cmd='/bin/file' lt_cv_deplibs_check_method="file_magic ELF [0-9][0-9]*-bit [LM]SB dynamic lib" lt_cv_file_magic_test_file=/lib/libc.so ;; siemens) lt_cv_deplibs_check_method=pass_all ;; pc) lt_cv_deplibs_check_method=pass_all ;; esac ;; tpf*) lt_cv_deplibs_check_method=pass_all ;; os2*) lt_cv_deplibs_check_method=pass_all ;; esac fi { $as_echo "$as_me:${as_lineno-$LINENO}: result: $lt_cv_deplibs_check_method" >&5 $as_echo "$lt_cv_deplibs_check_method" >&6; } file_magic_glob= want_nocaseglob=no if test "$build" = "$host"; then case $host_os in mingw* | pw32*) if ( shopt | grep nocaseglob ) >/dev/null 2>&1; then want_nocaseglob=yes else file_magic_glob=`echo aAbBcCdDeEfFgGhHiIjJkKlLmMnNoOpPqQrRsStTuUvVwWxXyYzZ | $SED -e "s/\(..\)/s\/[\1]\/[\1]\/g;/g"` fi ;; esac fi file_magic_cmd=$lt_cv_file_magic_cmd deplibs_check_method=$lt_cv_deplibs_check_method test -z "$deplibs_check_method" && deplibs_check_method=unknown if test -n "$ac_tool_prefix"; then # Extract the first word of "${ac_tool_prefix}dlltool", so it can be a program name with args. set dummy ${ac_tool_prefix}dlltool; ac_word=$2 { $as_echo "$as_me:${as_lineno-$LINENO}: checking for $ac_word" >&5 $as_echo_n "checking for $ac_word... " >&6; } if ${ac_cv_prog_DLLTOOL+:} false; then : $as_echo_n "(cached) " >&6 else if test -n "$DLLTOOL"; then ac_cv_prog_DLLTOOL="$DLLTOOL" # Let the user override the test. else as_save_IFS=$IFS; IFS=$PATH_SEPARATOR for as_dir in $PATH do IFS=$as_save_IFS test -z "$as_dir" && as_dir=. for ac_exec_ext in '' $ac_executable_extensions; do if as_fn_executable_p "$as_dir/$ac_word$ac_exec_ext"; then ac_cv_prog_DLLTOOL="${ac_tool_prefix}dlltool" $as_echo "$as_me:${as_lineno-$LINENO}: found $as_dir/$ac_word$ac_exec_ext" >&5 break 2 fi done done IFS=$as_save_IFS fi fi DLLTOOL=$ac_cv_prog_DLLTOOL if test -n "$DLLTOOL"; then { $as_echo "$as_me:${as_lineno-$LINENO}: result: $DLLTOOL" >&5 $as_echo "$DLLTOOL" >&6; } else { $as_echo "$as_me:${as_lineno-$LINENO}: result: no" >&5 $as_echo "no" >&6; } fi fi if test -z "$ac_cv_prog_DLLTOOL"; then ac_ct_DLLTOOL=$DLLTOOL # Extract the first word of "dlltool", so it can be a program name with args. set dummy dlltool; ac_word=$2 { $as_echo "$as_me:${as_lineno-$LINENO}: checking for $ac_word" >&5 $as_echo_n "checking for $ac_word... " >&6; } if ${ac_cv_prog_ac_ct_DLLTOOL+:} false; then : $as_echo_n "(cached) " >&6 else if test -n "$ac_ct_DLLTOOL"; then ac_cv_prog_ac_ct_DLLTOOL="$ac_ct_DLLTOOL" # Let the user override the test. else as_save_IFS=$IFS; IFS=$PATH_SEPARATOR for as_dir in $PATH do IFS=$as_save_IFS test -z "$as_dir" && as_dir=. for ac_exec_ext in '' $ac_executable_extensions; do if as_fn_executable_p "$as_dir/$ac_word$ac_exec_ext"; then ac_cv_prog_ac_ct_DLLTOOL="dlltool" $as_echo "$as_me:${as_lineno-$LINENO}: found $as_dir/$ac_word$ac_exec_ext" >&5 break 2 fi done done IFS=$as_save_IFS fi fi ac_ct_DLLTOOL=$ac_cv_prog_ac_ct_DLLTOOL if test -n "$ac_ct_DLLTOOL"; then { $as_echo "$as_me:${as_lineno-$LINENO}: result: $ac_ct_DLLTOOL" >&5 $as_echo "$ac_ct_DLLTOOL" >&6; } else { $as_echo "$as_me:${as_lineno-$LINENO}: result: no" >&5 $as_echo "no" >&6; } fi if test "x$ac_ct_DLLTOOL" = x; then DLLTOOL="false" else case $cross_compiling:$ac_tool_warned in yes:) { $as_echo "$as_me:${as_lineno-$LINENO}: WARNING: using cross tools not prefixed with host triplet" >&5 $as_echo "$as_me: WARNING: using cross tools not prefixed with host triplet" >&2;} ac_tool_warned=yes ;; esac DLLTOOL=$ac_ct_DLLTOOL fi else DLLTOOL="$ac_cv_prog_DLLTOOL" fi test -z "$DLLTOOL" && DLLTOOL=dlltool { $as_echo "$as_me:${as_lineno-$LINENO}: checking how to associate runtime and link libraries" >&5 $as_echo_n "checking how to associate runtime and link libraries... " >&6; } if ${lt_cv_sharedlib_from_linklib_cmd+:} false; then : $as_echo_n "(cached) " >&6 else lt_cv_sharedlib_from_linklib_cmd='unknown' case $host_os in cygwin* | mingw* | pw32* | cegcc*) # two different shell functions defined in ltmain.sh; # decide which one to use based on capabilities of $DLLTOOL case `$DLLTOOL --help 2>&1` in *--identify-strict*) lt_cv_sharedlib_from_linklib_cmd=func_cygming_dll_for_implib ;; *) lt_cv_sharedlib_from_linklib_cmd=func_cygming_dll_for_implib_fallback ;; esac ;; *) # fallback: assume linklib IS sharedlib lt_cv_sharedlib_from_linklib_cmd=$ECHO ;; esac fi { $as_echo "$as_me:${as_lineno-$LINENO}: result: $lt_cv_sharedlib_from_linklib_cmd" >&5 $as_echo "$lt_cv_sharedlib_from_linklib_cmd" >&6; } sharedlib_from_linklib_cmd=$lt_cv_sharedlib_from_linklib_cmd test -z "$sharedlib_from_linklib_cmd" && sharedlib_from_linklib_cmd=$ECHO if test -n "$ac_tool_prefix"; then for ac_prog in ar do # Extract the first word of "$ac_tool_prefix$ac_prog", so it can be a program name with args. set dummy $ac_tool_prefix$ac_prog; ac_word=$2 { $as_echo "$as_me:${as_lineno-$LINENO}: checking for $ac_word" >&5 $as_echo_n "checking for $ac_word... " >&6; } if ${ac_cv_prog_AR+:} false; then : $as_echo_n "(cached) " >&6 else if test -n "$AR"; then ac_cv_prog_AR="$AR" # Let the user override the test. else as_save_IFS=$IFS; IFS=$PATH_SEPARATOR for as_dir in $PATH do IFS=$as_save_IFS test -z "$as_dir" && as_dir=. for ac_exec_ext in '' $ac_executable_extensions; do if as_fn_executable_p "$as_dir/$ac_word$ac_exec_ext"; then ac_cv_prog_AR="$ac_tool_prefix$ac_prog" $as_echo "$as_me:${as_lineno-$LINENO}: found $as_dir/$ac_word$ac_exec_ext" >&5 break 2 fi done done IFS=$as_save_IFS fi fi AR=$ac_cv_prog_AR if test -n "$AR"; then { $as_echo "$as_me:${as_lineno-$LINENO}: result: $AR" >&5 $as_echo "$AR" >&6; } else { $as_echo "$as_me:${as_lineno-$LINENO}: result: no" >&5 $as_echo "no" >&6; } fi test -n "$AR" && break done fi if test -z "$AR"; then ac_ct_AR=$AR for ac_prog in ar do # Extract the first word of "$ac_prog", so it can be a program name with args. set dummy $ac_prog; ac_word=$2 { $as_echo "$as_me:${as_lineno-$LINENO}: checking for $ac_word" >&5 $as_echo_n "checking for $ac_word... " >&6; } if ${ac_cv_prog_ac_ct_AR+:} false; then : $as_echo_n "(cached) " >&6 else if test -n "$ac_ct_AR"; then ac_cv_prog_ac_ct_AR="$ac_ct_AR" # Let the user override the test. else as_save_IFS=$IFS; IFS=$PATH_SEPARATOR for as_dir in $PATH do IFS=$as_save_IFS test -z "$as_dir" && as_dir=. for ac_exec_ext in '' $ac_executable_extensions; do if as_fn_executable_p "$as_dir/$ac_word$ac_exec_ext"; then ac_cv_prog_ac_ct_AR="$ac_prog" $as_echo "$as_me:${as_lineno-$LINENO}: found $as_dir/$ac_word$ac_exec_ext" >&5 break 2 fi done done IFS=$as_save_IFS fi fi ac_ct_AR=$ac_cv_prog_ac_ct_AR if test -n "$ac_ct_AR"; then { $as_echo "$as_me:${as_lineno-$LINENO}: result: $ac_ct_AR" >&5 $as_echo "$ac_ct_AR" >&6; } else { $as_echo "$as_me:${as_lineno-$LINENO}: result: no" >&5 $as_echo "no" >&6; } fi test -n "$ac_ct_AR" && break done if test "x$ac_ct_AR" = x; then AR="false" else case $cross_compiling:$ac_tool_warned in yes:) { $as_echo "$as_me:${as_lineno-$LINENO}: WARNING: using cross tools not prefixed with host triplet" >&5 $as_echo "$as_me: WARNING: using cross tools not prefixed with host triplet" >&2;} ac_tool_warned=yes ;; esac AR=$ac_ct_AR fi fi : ${AR=ar} : ${AR_FLAGS=cru} { $as_echo "$as_me:${as_lineno-$LINENO}: checking for archiver @FILE support" >&5 $as_echo_n "checking for archiver @FILE support... " >&6; } if ${lt_cv_ar_at_file+:} false; then : $as_echo_n "(cached) " >&6 else lt_cv_ar_at_file=no cat confdefs.h - <<_ACEOF >conftest.$ac_ext /* end confdefs.h. */ int main () { ; return 0; } _ACEOF if ac_fn_c_try_compile "$LINENO"; then : echo conftest.$ac_objext > conftest.lst lt_ar_try='$AR $AR_FLAGS libconftest.a @conftest.lst >&5' { { eval echo "\"\$as_me\":${as_lineno-$LINENO}: \"$lt_ar_try\""; } >&5 (eval $lt_ar_try) 2>&5 ac_status=$? $as_echo "$as_me:${as_lineno-$LINENO}: \$? = $ac_status" >&5 test $ac_status = 0; } if test 0 -eq "$ac_status"; then # Ensure the archiver fails upon bogus file names. rm -f conftest.$ac_objext libconftest.a { { eval echo "\"\$as_me\":${as_lineno-$LINENO}: \"$lt_ar_try\""; } >&5 (eval $lt_ar_try) 2>&5 ac_status=$? $as_echo "$as_me:${as_lineno-$LINENO}: \$? = $ac_status" >&5 test $ac_status = 0; } if test 0 -ne "$ac_status"; then lt_cv_ar_at_file=@ fi fi rm -f conftest.* libconftest.a fi rm -f core conftest.err conftest.$ac_objext conftest.$ac_ext fi { $as_echo "$as_me:${as_lineno-$LINENO}: result: $lt_cv_ar_at_file" >&5 $as_echo "$lt_cv_ar_at_file" >&6; } if test no = "$lt_cv_ar_at_file"; then archiver_list_spec= else archiver_list_spec=$lt_cv_ar_at_file fi if test -n "$ac_tool_prefix"; then # Extract the first word of "${ac_tool_prefix}strip", so it can be a program name with args. set dummy ${ac_tool_prefix}strip; ac_word=$2 { $as_echo "$as_me:${as_lineno-$LINENO}: checking for $ac_word" >&5 $as_echo_n "checking for $ac_word... " >&6; } if ${ac_cv_prog_STRIP+:} false; then : $as_echo_n "(cached) " >&6 else if test -n "$STRIP"; then ac_cv_prog_STRIP="$STRIP" # Let the user override the test. else as_save_IFS=$IFS; IFS=$PATH_SEPARATOR for as_dir in $PATH do IFS=$as_save_IFS test -z "$as_dir" && as_dir=. for ac_exec_ext in '' $ac_executable_extensions; do if as_fn_executable_p "$as_dir/$ac_word$ac_exec_ext"; then ac_cv_prog_STRIP="${ac_tool_prefix}strip" $as_echo "$as_me:${as_lineno-$LINENO}: found $as_dir/$ac_word$ac_exec_ext" >&5 break 2 fi done done IFS=$as_save_IFS fi fi STRIP=$ac_cv_prog_STRIP if test -n "$STRIP"; then { $as_echo "$as_me:${as_lineno-$LINENO}: result: $STRIP" >&5 $as_echo "$STRIP" >&6; } else { $as_echo "$as_me:${as_lineno-$LINENO}: result: no" >&5 $as_echo "no" >&6; } fi fi if test -z "$ac_cv_prog_STRIP"; then ac_ct_STRIP=$STRIP # Extract the first word of "strip", so it can be a program name with args. set dummy strip; ac_word=$2 { $as_echo "$as_me:${as_lineno-$LINENO}: checking for $ac_word" >&5 $as_echo_n "checking for $ac_word... " >&6; } if ${ac_cv_prog_ac_ct_STRIP+:} false; then : $as_echo_n "(cached) " >&6 else if test -n "$ac_ct_STRIP"; then ac_cv_prog_ac_ct_STRIP="$ac_ct_STRIP" # Let the user override the test. else as_save_IFS=$IFS; IFS=$PATH_SEPARATOR for as_dir in $PATH do IFS=$as_save_IFS test -z "$as_dir" && as_dir=. for ac_exec_ext in '' $ac_executable_extensions; do if as_fn_executable_p "$as_dir/$ac_word$ac_exec_ext"; then ac_cv_prog_ac_ct_STRIP="strip" $as_echo "$as_me:${as_lineno-$LINENO}: found $as_dir/$ac_word$ac_exec_ext" >&5 break 2 fi done done IFS=$as_save_IFS fi fi ac_ct_STRIP=$ac_cv_prog_ac_ct_STRIP if test -n "$ac_ct_STRIP"; then { $as_echo "$as_me:${as_lineno-$LINENO}: result: $ac_ct_STRIP" >&5 $as_echo "$ac_ct_STRIP" >&6; } else { $as_echo "$as_me:${as_lineno-$LINENO}: result: no" >&5 $as_echo "no" >&6; } fi if test "x$ac_ct_STRIP" = x; then STRIP=":" else case $cross_compiling:$ac_tool_warned in yes:) { $as_echo "$as_me:${as_lineno-$LINENO}: WARNING: using cross tools not prefixed with host triplet" >&5 $as_echo "$as_me: WARNING: using cross tools not prefixed with host triplet" >&2;} ac_tool_warned=yes ;; esac STRIP=$ac_ct_STRIP fi else STRIP="$ac_cv_prog_STRIP" fi test -z "$STRIP" && STRIP=: if test -n "$ac_tool_prefix"; then # Extract the first word of "${ac_tool_prefix}ranlib", so it can be a program name with args. set dummy ${ac_tool_prefix}ranlib; ac_word=$2 { $as_echo "$as_me:${as_lineno-$LINENO}: checking for $ac_word" >&5 $as_echo_n "checking for $ac_word... " >&6; } if ${ac_cv_prog_RANLIB+:} false; then : $as_echo_n "(cached) " >&6 else if test -n "$RANLIB"; then ac_cv_prog_RANLIB="$RANLIB" # Let the user override the test. else as_save_IFS=$IFS; IFS=$PATH_SEPARATOR for as_dir in $PATH do IFS=$as_save_IFS test -z "$as_dir" && as_dir=. for ac_exec_ext in '' $ac_executable_extensions; do if as_fn_executable_p "$as_dir/$ac_word$ac_exec_ext"; then ac_cv_prog_RANLIB="${ac_tool_prefix}ranlib" $as_echo "$as_me:${as_lineno-$LINENO}: found $as_dir/$ac_word$ac_exec_ext" >&5 break 2 fi done done IFS=$as_save_IFS fi fi RANLIB=$ac_cv_prog_RANLIB if test -n "$RANLIB"; then { $as_echo "$as_me:${as_lineno-$LINENO}: result: $RANLIB" >&5 $as_echo "$RANLIB" >&6; } else { $as_echo "$as_me:${as_lineno-$LINENO}: result: no" >&5 $as_echo "no" >&6; } fi fi if test -z "$ac_cv_prog_RANLIB"; then ac_ct_RANLIB=$RANLIB # Extract the first word of "ranlib", so it can be a program name with args. set dummy ranlib; ac_word=$2 { $as_echo "$as_me:${as_lineno-$LINENO}: checking for $ac_word" >&5 $as_echo_n "checking for $ac_word... " >&6; } if ${ac_cv_prog_ac_ct_RANLIB+:} false; then : $as_echo_n "(cached) " >&6 else if test -n "$ac_ct_RANLIB"; then ac_cv_prog_ac_ct_RANLIB="$ac_ct_RANLIB" # Let the user override the test. else as_save_IFS=$IFS; IFS=$PATH_SEPARATOR for as_dir in $PATH do IFS=$as_save_IFS test -z "$as_dir" && as_dir=. for ac_exec_ext in '' $ac_executable_extensions; do if as_fn_executable_p "$as_dir/$ac_word$ac_exec_ext"; then ac_cv_prog_ac_ct_RANLIB="ranlib" $as_echo "$as_me:${as_lineno-$LINENO}: found $as_dir/$ac_word$ac_exec_ext" >&5 break 2 fi done done IFS=$as_save_IFS fi fi ac_ct_RANLIB=$ac_cv_prog_ac_ct_RANLIB if test -n "$ac_ct_RANLIB"; then { $as_echo "$as_me:${as_lineno-$LINENO}: result: $ac_ct_RANLIB" >&5 $as_echo "$ac_ct_RANLIB" >&6; } else { $as_echo "$as_me:${as_lineno-$LINENO}: result: no" >&5 $as_echo "no" >&6; } fi if test "x$ac_ct_RANLIB" = x; then RANLIB=":" else case $cross_compiling:$ac_tool_warned in yes:) { $as_echo "$as_me:${as_lineno-$LINENO}: WARNING: using cross tools not prefixed with host triplet" >&5 $as_echo "$as_me: WARNING: using cross tools not prefixed with host triplet" >&2;} ac_tool_warned=yes ;; esac RANLIB=$ac_ct_RANLIB fi else RANLIB="$ac_cv_prog_RANLIB" fi test -z "$RANLIB" && RANLIB=: # Determine commands to create old-style static archives. old_archive_cmds='$AR $AR_FLAGS $oldlib$oldobjs' old_postinstall_cmds='chmod 644 $oldlib' old_postuninstall_cmds= if test -n "$RANLIB"; then case $host_os in bitrig* | openbsd*) old_postinstall_cmds="$old_postinstall_cmds~\$RANLIB -t \$tool_oldlib" ;; *) old_postinstall_cmds="$old_postinstall_cmds~\$RANLIB \$tool_oldlib" ;; esac old_archive_cmds="$old_archive_cmds~\$RANLIB \$tool_oldlib" fi case $host_os in darwin*) lock_old_archive_extraction=yes ;; *) lock_old_archive_extraction=no ;; esac # If no C compiler was specified, use CC. LTCC=${LTCC-"$CC"} # If no C compiler flags were specified, use CFLAGS. LTCFLAGS=${LTCFLAGS-"$CFLAGS"} # Allow CC to be a program name with arguments. compiler=$CC # Check for command to grab the raw symbol name followed by C symbol from nm. { $as_echo "$as_me:${as_lineno-$LINENO}: checking command to parse $NM output from $compiler object" >&5 $as_echo_n "checking command to parse $NM output from $compiler object... " >&6; } if ${lt_cv_sys_global_symbol_pipe+:} false; then : $as_echo_n "(cached) " >&6 else # These are sane defaults that work on at least a few old systems. # [They come from Ultrix. What could be older than Ultrix?!! ;)] # Character class describing NM global symbol codes. symcode='[BCDEGRST]' # Regexp to match symbols that can be accessed directly from C. sympat='\([_A-Za-z][_A-Za-z0-9]*\)' # Define system-specific variables. case $host_os in aix*) symcode='[BCDT]' ;; cygwin* | mingw* | pw32* | cegcc*) symcode='[ABCDGISTW]' ;; hpux*) if test ia64 = "$host_cpu"; then symcode='[ABCDEGRST]' fi ;; irix* | nonstopux*) symcode='[BCDEGRST]' ;; osf*) symcode='[BCDEGQRST]' ;; solaris*) symcode='[BDRT]' ;; sco3.2v5*) symcode='[DT]' ;; sysv4.2uw2*) symcode='[DT]' ;; sysv5* | sco5v6* | unixware* | OpenUNIX*) symcode='[ABDT]' ;; sysv4) symcode='[DFNSTU]' ;; esac # If we're using GNU nm, then use its standard symbol codes. case `$NM -V 2>&1` in *GNU* | *'with BFD'*) symcode='[ABCDGIRSTW]' ;; esac if test "$lt_cv_nm_interface" = "MS dumpbin"; then # Gets list of data symbols to import. lt_cv_sys_global_symbol_to_import="sed -n -e 's/^I .* \(.*\)$/\1/p'" # Adjust the below global symbol transforms to fixup imported variables. lt_cdecl_hook=" -e 's/^I .* \(.*\)$/extern __declspec(dllimport) char \1;/p'" lt_c_name_hook=" -e 's/^I .* \(.*\)$/ {\"\1\", (void *) 0},/p'" lt_c_name_lib_hook="\ -e 's/^I .* \(lib.*\)$/ {\"\1\", (void *) 0},/p'\ -e 's/^I .* \(.*\)$/ {\"lib\1\", (void *) 0},/p'" else # Disable hooks by default. lt_cv_sys_global_symbol_to_import= lt_cdecl_hook= lt_c_name_hook= lt_c_name_lib_hook= fi # Transform an extracted symbol line into a proper C declaration. # Some systems (esp. on ia64) link data and code symbols differently, # so use this general approach. lt_cv_sys_global_symbol_to_cdecl="sed -n"\ $lt_cdecl_hook\ " -e 's/^T .* \(.*\)$/extern int \1();/p'"\ " -e 's/^$symcode$symcode* .* \(.*\)$/extern char \1;/p'" # Transform an extracted symbol line into symbol name and symbol address lt_cv_sys_global_symbol_to_c_name_address="sed -n"\ $lt_c_name_hook\ " -e 's/^: \(.*\) .*$/ {\"\1\", (void *) 0},/p'"\ " -e 's/^$symcode$symcode* .* \(.*\)$/ {\"\1\", (void *) \&\1},/p'" # Transform an extracted symbol line into symbol name with lib prefix and # symbol address. lt_cv_sys_global_symbol_to_c_name_address_lib_prefix="sed -n"\ $lt_c_name_lib_hook\ " -e 's/^: \(.*\) .*$/ {\"\1\", (void *) 0},/p'"\ " -e 's/^$symcode$symcode* .* \(lib.*\)$/ {\"\1\", (void *) \&\1},/p'"\ " -e 's/^$symcode$symcode* .* \(.*\)$/ {\"lib\1\", (void *) \&\1},/p'" # Handle CRLF in mingw tool chain opt_cr= case $build_os in mingw*) opt_cr=`$ECHO 'x\{0,1\}' | tr x '\015'` # option cr in regexp ;; esac # Try without a prefix underscore, then with it. for ac_symprfx in "" "_"; do # Transform symcode, sympat, and symprfx into a raw symbol and a C symbol. symxfrm="\\1 $ac_symprfx\\2 \\2" # Write the raw and C identifiers. if test "$lt_cv_nm_interface" = "MS dumpbin"; then # Fake it for dumpbin and say T for any non-static function, # D for any global variable and I for any imported variable. # Also find C++ and __fastcall symbols from MSVC++, # which start with @ or ?. lt_cv_sys_global_symbol_pipe="$AWK '"\ " {last_section=section; section=\$ 3};"\ " /^COFF SYMBOL TABLE/{for(i in hide) delete hide[i]};"\ " /Section length .*#relocs.*(pick any)/{hide[last_section]=1};"\ " /^ *Symbol name *: /{split(\$ 0,sn,\":\"); si=substr(sn[2],2)};"\ " /^ *Type *: code/{print \"T\",si,substr(si,length(prfx))};"\ " /^ *Type *: data/{print \"I\",si,substr(si,length(prfx))};"\ " \$ 0!~/External *\|/{next};"\ " / 0+ UNDEF /{next}; / UNDEF \([^|]\)*()/{next};"\ " {if(hide[section]) next};"\ " {f=\"D\"}; \$ 0~/\(\).*\|/{f=\"T\"};"\ " {split(\$ 0,a,/\||\r/); split(a[2],s)};"\ " s[1]~/^[@?]/{print f,s[1],s[1]; next};"\ " s[1]~prfx {split(s[1],t,\"@\"); print f,t[1],substr(t[1],length(prfx))}"\ " ' prfx=^$ac_symprfx" else lt_cv_sys_global_symbol_pipe="sed -n -e 's/^.*[ ]\($symcode$symcode*\)[ ][ ]*$ac_symprfx$sympat$opt_cr$/$symxfrm/p'" fi lt_cv_sys_global_symbol_pipe="$lt_cv_sys_global_symbol_pipe | sed '/ __gnu_lto/d'" # Check to see that the pipe works correctly. pipe_works=no rm -f conftest* cat > conftest.$ac_ext <<_LT_EOF #ifdef __cplusplus extern "C" { #endif char nm_test_var; void nm_test_func(void); void nm_test_func(void){} #ifdef __cplusplus } #endif int main(){nm_test_var='a';nm_test_func();return(0);} _LT_EOF if { { eval echo "\"\$as_me\":${as_lineno-$LINENO}: \"$ac_compile\""; } >&5 (eval $ac_compile) 2>&5 ac_status=$? $as_echo "$as_me:${as_lineno-$LINENO}: \$? = $ac_status" >&5 test $ac_status = 0; }; then # Now try to grab the symbols. nlist=conftest.nm $ECHO "$as_me:$LINENO: $NM conftest.$ac_objext | $lt_cv_sys_global_symbol_pipe > $nlist" >&5 if eval "$NM" conftest.$ac_objext \| "$lt_cv_sys_global_symbol_pipe" \> $nlist 2>&5 && test -s "$nlist"; then # Try sorting and uniquifying the output. if sort "$nlist" | uniq > "$nlist"T; then mv -f "$nlist"T "$nlist" else rm -f "$nlist"T fi # Make sure that we snagged all the symbols we need. if $GREP ' nm_test_var$' "$nlist" >/dev/null; then if $GREP ' nm_test_func$' "$nlist" >/dev/null; then cat <<_LT_EOF > conftest.$ac_ext /* Keep this code in sync between libtool.m4, ltmain, lt_system.h, and tests. */ #if defined _WIN32 || defined __CYGWIN__ || defined _WIN32_WCE /* DATA imports from DLLs on WIN32 can't be const, because runtime relocations are performed -- see ld's documentation on pseudo-relocs. */ # define LT_DLSYM_CONST #elif defined __osf__ /* This system does not cope well with relocations in const data. */ # define LT_DLSYM_CONST #else # define LT_DLSYM_CONST const #endif #ifdef __cplusplus extern "C" { #endif _LT_EOF # Now generate the symbol file. eval "$lt_cv_sys_global_symbol_to_cdecl"' < "$nlist" | $GREP -v main >> conftest.$ac_ext' cat <<_LT_EOF >> conftest.$ac_ext /* The mapping between symbol names and symbols. */ LT_DLSYM_CONST struct { const char *name; void *address; } lt__PROGRAM__LTX_preloaded_symbols[] = { { "@PROGRAM@", (void *) 0 }, _LT_EOF $SED "s/^$symcode$symcode* .* \(.*\)$/ {\"\1\", (void *) \&\1},/" < "$nlist" | $GREP -v main >> conftest.$ac_ext cat <<\_LT_EOF >> conftest.$ac_ext {0, (void *) 0} }; /* This works around a problem in FreeBSD linker */ #ifdef FREEBSD_WORKAROUND static const void *lt_preloaded_setup() { return lt__PROGRAM__LTX_preloaded_symbols; } #endif #ifdef __cplusplus } #endif _LT_EOF # Now try linking the two files. mv conftest.$ac_objext conftstm.$ac_objext lt_globsym_save_LIBS=$LIBS lt_globsym_save_CFLAGS=$CFLAGS LIBS=conftstm.$ac_objext CFLAGS="$CFLAGS$lt_prog_compiler_no_builtin_flag" if { { eval echo "\"\$as_me\":${as_lineno-$LINENO}: \"$ac_link\""; } >&5 (eval $ac_link) 2>&5 ac_status=$? $as_echo "$as_me:${as_lineno-$LINENO}: \$? = $ac_status" >&5 test $ac_status = 0; } && test -s conftest$ac_exeext; then pipe_works=yes fi LIBS=$lt_globsym_save_LIBS CFLAGS=$lt_globsym_save_CFLAGS else echo "cannot find nm_test_func in $nlist" >&5 fi else echo "cannot find nm_test_var in $nlist" >&5 fi else echo "cannot run $lt_cv_sys_global_symbol_pipe" >&5 fi else echo "$progname: failed program was:" >&5 cat conftest.$ac_ext >&5 fi rm -rf conftest* conftst* # Do not use the global_symbol_pipe unless it works. if test yes = "$pipe_works"; then break else lt_cv_sys_global_symbol_pipe= fi done fi if test -z "$lt_cv_sys_global_symbol_pipe"; then lt_cv_sys_global_symbol_to_cdecl= fi if test -z "$lt_cv_sys_global_symbol_pipe$lt_cv_sys_global_symbol_to_cdecl"; then { $as_echo "$as_me:${as_lineno-$LINENO}: result: failed" >&5 $as_echo "failed" >&6; } else { $as_echo "$as_me:${as_lineno-$LINENO}: result: ok" >&5 $as_echo "ok" >&6; } fi # Response file support. if test "$lt_cv_nm_interface" = "MS dumpbin"; then nm_file_list_spec='@' elif $NM --help 2>/dev/null | grep '[@]FILE' >/dev/null; then nm_file_list_spec='@' fi { $as_echo "$as_me:${as_lineno-$LINENO}: checking for sysroot" >&5 $as_echo_n "checking for sysroot... " >&6; } # Check whether --with-sysroot was given. if test "${with_sysroot+set}" = set; then : withval=$with_sysroot; else with_sysroot=no fi lt_sysroot= case $with_sysroot in #( yes) if test yes = "$GCC"; then lt_sysroot=`$CC --print-sysroot 2>/dev/null` fi ;; #( /*) lt_sysroot=`echo "$with_sysroot" | sed -e "$sed_quote_subst"` ;; #( no|'') ;; #( *) { $as_echo "$as_me:${as_lineno-$LINENO}: result: $with_sysroot" >&5 $as_echo "$with_sysroot" >&6; } as_fn_error $? "The sysroot must be an absolute path." "$LINENO" 5 ;; esac { $as_echo "$as_me:${as_lineno-$LINENO}: result: ${lt_sysroot:-no}" >&5 $as_echo "${lt_sysroot:-no}" >&6; } { $as_echo "$as_me:${as_lineno-$LINENO}: checking for a working dd" >&5 $as_echo_n "checking for a working dd... " >&6; } if ${ac_cv_path_lt_DD+:} false; then : $as_echo_n "(cached) " >&6 else printf 0123456789abcdef0123456789abcdef >conftest.i cat conftest.i conftest.i >conftest2.i : ${lt_DD:=$DD} if test -z "$lt_DD"; then ac_path_lt_DD_found=false # Loop through the user's path and test for each of PROGNAME-LIST as_save_IFS=$IFS; IFS=$PATH_SEPARATOR for as_dir in $PATH do IFS=$as_save_IFS test -z "$as_dir" && as_dir=. for ac_prog in dd; do for ac_exec_ext in '' $ac_executable_extensions; do ac_path_lt_DD="$as_dir/$ac_prog$ac_exec_ext" as_fn_executable_p "$ac_path_lt_DD" || continue if "$ac_path_lt_DD" bs=32 count=1 conftest.out 2>/dev/null; then cmp -s conftest.i conftest.out \ && ac_cv_path_lt_DD="$ac_path_lt_DD" ac_path_lt_DD_found=: fi $ac_path_lt_DD_found && break 3 done done done IFS=$as_save_IFS if test -z "$ac_cv_path_lt_DD"; then : fi else ac_cv_path_lt_DD=$lt_DD fi rm -f conftest.i conftest2.i conftest.out fi { $as_echo "$as_me:${as_lineno-$LINENO}: result: $ac_cv_path_lt_DD" >&5 $as_echo "$ac_cv_path_lt_DD" >&6; } { $as_echo "$as_me:${as_lineno-$LINENO}: checking how to truncate binary pipes" >&5 $as_echo_n "checking how to truncate binary pipes... " >&6; } if ${lt_cv_truncate_bin+:} false; then : $as_echo_n "(cached) " >&6 else printf 0123456789abcdef0123456789abcdef >conftest.i cat conftest.i conftest.i >conftest2.i lt_cv_truncate_bin= if "$ac_cv_path_lt_DD" bs=32 count=1 conftest.out 2>/dev/null; then cmp -s conftest.i conftest.out \ && lt_cv_truncate_bin="$ac_cv_path_lt_DD bs=4096 count=1" fi rm -f conftest.i conftest2.i conftest.out test -z "$lt_cv_truncate_bin" && lt_cv_truncate_bin="$SED -e 4q" fi { $as_echo "$as_me:${as_lineno-$LINENO}: result: $lt_cv_truncate_bin" >&5 $as_echo "$lt_cv_truncate_bin" >&6; } # Calculate cc_basename. Skip known compiler wrappers and cross-prefix. func_cc_basename () { for cc_temp in $*""; do case $cc_temp in compile | *[\\/]compile | ccache | *[\\/]ccache ) ;; distcc | *[\\/]distcc | purify | *[\\/]purify ) ;; \-*) ;; *) break;; esac done func_cc_basename_result=`$ECHO "$cc_temp" | $SED "s%.*/%%; s%^$host_alias-%%"` } # Check whether --enable-libtool-lock was given. if test "${enable_libtool_lock+set}" = set; then : enableval=$enable_libtool_lock; fi test no = "$enable_libtool_lock" || enable_libtool_lock=yes # Some flags need to be propagated to the compiler or linker for good # libtool support. case $host in ia64-*-hpux*) # Find out what ABI is being produced by ac_compile, and set mode # options accordingly. echo 'int i;' > conftest.$ac_ext if { { eval echo "\"\$as_me\":${as_lineno-$LINENO}: \"$ac_compile\""; } >&5 (eval $ac_compile) 2>&5 ac_status=$? $as_echo "$as_me:${as_lineno-$LINENO}: \$? = $ac_status" >&5 test $ac_status = 0; }; then case `/usr/bin/file conftest.$ac_objext` in *ELF-32*) HPUX_IA64_MODE=32 ;; *ELF-64*) HPUX_IA64_MODE=64 ;; esac fi rm -rf conftest* ;; *-*-irix6*) # Find out what ABI is being produced by ac_compile, and set linker # options accordingly. echo '#line '$LINENO' "configure"' > conftest.$ac_ext if { { eval echo "\"\$as_me\":${as_lineno-$LINENO}: \"$ac_compile\""; } >&5 (eval $ac_compile) 2>&5 ac_status=$? $as_echo "$as_me:${as_lineno-$LINENO}: \$? = $ac_status" >&5 test $ac_status = 0; }; then if test yes = "$lt_cv_prog_gnu_ld"; then case `/usr/bin/file conftest.$ac_objext` in *32-bit*) LD="${LD-ld} -melf32bsmip" ;; *N32*) LD="${LD-ld} -melf32bmipn32" ;; *64-bit*) LD="${LD-ld} -melf64bmip" ;; esac else case `/usr/bin/file conftest.$ac_objext` in *32-bit*) LD="${LD-ld} -32" ;; *N32*) LD="${LD-ld} -n32" ;; *64-bit*) LD="${LD-ld} -64" ;; esac fi fi rm -rf conftest* ;; mips64*-*linux*) # Find out what ABI is being produced by ac_compile, and set linker # options accordingly. echo '#line '$LINENO' "configure"' > conftest.$ac_ext if { { eval echo "\"\$as_me\":${as_lineno-$LINENO}: \"$ac_compile\""; } >&5 (eval $ac_compile) 2>&5 ac_status=$? $as_echo "$as_me:${as_lineno-$LINENO}: \$? = $ac_status" >&5 test $ac_status = 0; }; then emul=elf case `/usr/bin/file conftest.$ac_objext` in *32-bit*) emul="${emul}32" ;; *64-bit*) emul="${emul}64" ;; esac case `/usr/bin/file conftest.$ac_objext` in *MSB*) emul="${emul}btsmip" ;; *LSB*) emul="${emul}ltsmip" ;; esac case `/usr/bin/file conftest.$ac_objext` in *N32*) emul="${emul}n32" ;; esac LD="${LD-ld} -m $emul" fi rm -rf conftest* ;; x86_64-*kfreebsd*-gnu|x86_64-*linux*|powerpc*-*linux*| \ s390*-*linux*|s390*-*tpf*|sparc*-*linux*) # Find out what ABI is being produced by ac_compile, and set linker # options accordingly. Note that the listed cases only cover the # situations where additional linker options are needed (such as when # doing 32-bit compilation for a host where ld defaults to 64-bit, or # vice versa); the common cases where no linker options are needed do # not appear in the list. echo 'int i;' > conftest.$ac_ext if { { eval echo "\"\$as_me\":${as_lineno-$LINENO}: \"$ac_compile\""; } >&5 (eval $ac_compile) 2>&5 ac_status=$? $as_echo "$as_me:${as_lineno-$LINENO}: \$? = $ac_status" >&5 test $ac_status = 0; }; then case `/usr/bin/file conftest.o` in *32-bit*) case $host in x86_64-*kfreebsd*-gnu) LD="${LD-ld} -m elf_i386_fbsd" ;; x86_64-*linux*) case `/usr/bin/file conftest.o` in *x86-64*) LD="${LD-ld} -m elf32_x86_64" ;; *) LD="${LD-ld} -m elf_i386" ;; esac ;; powerpc64le-*linux*) LD="${LD-ld} -m elf32lppclinux" ;; powerpc64-*linux*) LD="${LD-ld} -m elf32ppclinux" ;; s390x-*linux*) LD="${LD-ld} -m elf_s390" ;; sparc64-*linux*) LD="${LD-ld} -m elf32_sparc" ;; esac ;; *64-bit*) case $host in x86_64-*kfreebsd*-gnu) LD="${LD-ld} -m elf_x86_64_fbsd" ;; x86_64-*linux*) LD="${LD-ld} -m elf_x86_64" ;; powerpcle-*linux*) LD="${LD-ld} -m elf64lppc" ;; powerpc-*linux*) LD="${LD-ld} -m elf64ppc" ;; s390*-*linux*|s390*-*tpf*) LD="${LD-ld} -m elf64_s390" ;; sparc*-*linux*) LD="${LD-ld} -m elf64_sparc" ;; esac ;; esac fi rm -rf conftest* ;; *-*-sco3.2v5*) # On SCO OpenServer 5, we need -belf to get full-featured binaries. SAVE_CFLAGS=$CFLAGS CFLAGS="$CFLAGS -belf" { $as_echo "$as_me:${as_lineno-$LINENO}: checking whether the C compiler needs -belf" >&5 $as_echo_n "checking whether the C compiler needs -belf... " >&6; } if ${lt_cv_cc_needs_belf+:} false; then : $as_echo_n "(cached) " >&6 else ac_ext=c ac_cpp='$CPP $CPPFLAGS' ac_compile='$CC -c $CFLAGS $CPPFLAGS conftest.$ac_ext >&5' ac_link='$CC -o conftest$ac_exeext $CFLAGS $CPPFLAGS $LDFLAGS conftest.$ac_ext $LIBS >&5' ac_compiler_gnu=$ac_cv_c_compiler_gnu cat confdefs.h - <<_ACEOF >conftest.$ac_ext /* end confdefs.h. */ int main () { ; return 0; } _ACEOF if ac_fn_c_try_link "$LINENO"; then : lt_cv_cc_needs_belf=yes else lt_cv_cc_needs_belf=no fi rm -f core conftest.err conftest.$ac_objext \ conftest$ac_exeext conftest.$ac_ext ac_ext=c ac_cpp='$CPP $CPPFLAGS' ac_compile='$CC -c $CFLAGS $CPPFLAGS conftest.$ac_ext >&5' ac_link='$CC -o conftest$ac_exeext $CFLAGS $CPPFLAGS $LDFLAGS conftest.$ac_ext $LIBS >&5' ac_compiler_gnu=$ac_cv_c_compiler_gnu fi { $as_echo "$as_me:${as_lineno-$LINENO}: result: $lt_cv_cc_needs_belf" >&5 $as_echo "$lt_cv_cc_needs_belf" >&6; } if test yes != "$lt_cv_cc_needs_belf"; then # this is probably gcc 2.8.0, egcs 1.0 or newer; no need for -belf CFLAGS=$SAVE_CFLAGS fi ;; *-*solaris*) # Find out what ABI is being produced by ac_compile, and set linker # options accordingly. echo 'int i;' > conftest.$ac_ext if { { eval echo "\"\$as_me\":${as_lineno-$LINENO}: \"$ac_compile\""; } >&5 (eval $ac_compile) 2>&5 ac_status=$? $as_echo "$as_me:${as_lineno-$LINENO}: \$? = $ac_status" >&5 test $ac_status = 0; }; then case `/usr/bin/file conftest.o` in *64-bit*) case $lt_cv_prog_gnu_ld in yes*) case $host in i?86-*-solaris*|x86_64-*-solaris*) LD="${LD-ld} -m elf_x86_64" ;; sparc*-*-solaris*) LD="${LD-ld} -m elf64_sparc" ;; esac # GNU ld 2.21 introduced _sol2 emulations. Use them if available. if ${LD-ld} -V | grep _sol2 >/dev/null 2>&1; then LD=${LD-ld}_sol2 fi ;; *) if ${LD-ld} -64 -r -o conftest2.o conftest.o >/dev/null 2>&1; then LD="${LD-ld} -64" fi ;; esac ;; esac fi rm -rf conftest* ;; esac need_locks=$enable_libtool_lock if test -n "$ac_tool_prefix"; then # Extract the first word of "${ac_tool_prefix}mt", so it can be a program name with args. set dummy ${ac_tool_prefix}mt; ac_word=$2 { $as_echo "$as_me:${as_lineno-$LINENO}: checking for $ac_word" >&5 $as_echo_n "checking for $ac_word... " >&6; } if ${ac_cv_prog_MANIFEST_TOOL+:} false; then : $as_echo_n "(cached) " >&6 else if test -n "$MANIFEST_TOOL"; then ac_cv_prog_MANIFEST_TOOL="$MANIFEST_TOOL" # Let the user override the test. else as_save_IFS=$IFS; IFS=$PATH_SEPARATOR for as_dir in $PATH do IFS=$as_save_IFS test -z "$as_dir" && as_dir=. for ac_exec_ext in '' $ac_executable_extensions; do if as_fn_executable_p "$as_dir/$ac_word$ac_exec_ext"; then ac_cv_prog_MANIFEST_TOOL="${ac_tool_prefix}mt" $as_echo "$as_me:${as_lineno-$LINENO}: found $as_dir/$ac_word$ac_exec_ext" >&5 break 2 fi done done IFS=$as_save_IFS fi fi MANIFEST_TOOL=$ac_cv_prog_MANIFEST_TOOL if test -n "$MANIFEST_TOOL"; then { $as_echo "$as_me:${as_lineno-$LINENO}: result: $MANIFEST_TOOL" >&5 $as_echo "$MANIFEST_TOOL" >&6; } else { $as_echo "$as_me:${as_lineno-$LINENO}: result: no" >&5 $as_echo "no" >&6; } fi fi if test -z "$ac_cv_prog_MANIFEST_TOOL"; then ac_ct_MANIFEST_TOOL=$MANIFEST_TOOL # Extract the first word of "mt", so it can be a program name with args. set dummy mt; ac_word=$2 { $as_echo "$as_me:${as_lineno-$LINENO}: checking for $ac_word" >&5 $as_echo_n "checking for $ac_word... " >&6; } if ${ac_cv_prog_ac_ct_MANIFEST_TOOL+:} false; then : $as_echo_n "(cached) " >&6 else if test -n "$ac_ct_MANIFEST_TOOL"; then ac_cv_prog_ac_ct_MANIFEST_TOOL="$ac_ct_MANIFEST_TOOL" # Let the user override the test. else as_save_IFS=$IFS; IFS=$PATH_SEPARATOR for as_dir in $PATH do IFS=$as_save_IFS test -z "$as_dir" && as_dir=. for ac_exec_ext in '' $ac_executable_extensions; do if as_fn_executable_p "$as_dir/$ac_word$ac_exec_ext"; then ac_cv_prog_ac_ct_MANIFEST_TOOL="mt" $as_echo "$as_me:${as_lineno-$LINENO}: found $as_dir/$ac_word$ac_exec_ext" >&5 break 2 fi done done IFS=$as_save_IFS fi fi ac_ct_MANIFEST_TOOL=$ac_cv_prog_ac_ct_MANIFEST_TOOL if test -n "$ac_ct_MANIFEST_TOOL"; then { $as_echo "$as_me:${as_lineno-$LINENO}: result: $ac_ct_MANIFEST_TOOL" >&5 $as_echo "$ac_ct_MANIFEST_TOOL" >&6; } else { $as_echo "$as_me:${as_lineno-$LINENO}: result: no" >&5 $as_echo "no" >&6; } fi if test "x$ac_ct_MANIFEST_TOOL" = x; then MANIFEST_TOOL=":" else case $cross_compiling:$ac_tool_warned in yes:) { $as_echo "$as_me:${as_lineno-$LINENO}: WARNING: using cross tools not prefixed with host triplet" >&5 $as_echo "$as_me: WARNING: using cross tools not prefixed with host triplet" >&2;} ac_tool_warned=yes ;; esac MANIFEST_TOOL=$ac_ct_MANIFEST_TOOL fi else MANIFEST_TOOL="$ac_cv_prog_MANIFEST_TOOL" fi test -z "$MANIFEST_TOOL" && MANIFEST_TOOL=mt { $as_echo "$as_me:${as_lineno-$LINENO}: checking if $MANIFEST_TOOL is a manifest tool" >&5 $as_echo_n "checking if $MANIFEST_TOOL is a manifest tool... " >&6; } if ${lt_cv_path_mainfest_tool+:} false; then : $as_echo_n "(cached) " >&6 else lt_cv_path_mainfest_tool=no echo "$as_me:$LINENO: $MANIFEST_TOOL '-?'" >&5 $MANIFEST_TOOL '-?' 2>conftest.err > conftest.out cat conftest.err >&5 if $GREP 'Manifest Tool' conftest.out > /dev/null; then lt_cv_path_mainfest_tool=yes fi rm -f conftest* fi { $as_echo "$as_me:${as_lineno-$LINENO}: result: $lt_cv_path_mainfest_tool" >&5 $as_echo "$lt_cv_path_mainfest_tool" >&6; } if test yes != "$lt_cv_path_mainfest_tool"; then MANIFEST_TOOL=: fi case $host_os in rhapsody* | darwin*) if test -n "$ac_tool_prefix"; then # Extract the first word of "${ac_tool_prefix}dsymutil", so it can be a program name with args. set dummy ${ac_tool_prefix}dsymutil; ac_word=$2 { $as_echo "$as_me:${as_lineno-$LINENO}: checking for $ac_word" >&5 $as_echo_n "checking for $ac_word... " >&6; } if ${ac_cv_prog_DSYMUTIL+:} false; then : $as_echo_n "(cached) " >&6 else if test -n "$DSYMUTIL"; then ac_cv_prog_DSYMUTIL="$DSYMUTIL" # Let the user override the test. else as_save_IFS=$IFS; IFS=$PATH_SEPARATOR for as_dir in $PATH do IFS=$as_save_IFS test -z "$as_dir" && as_dir=. for ac_exec_ext in '' $ac_executable_extensions; do if as_fn_executable_p "$as_dir/$ac_word$ac_exec_ext"; then ac_cv_prog_DSYMUTIL="${ac_tool_prefix}dsymutil" $as_echo "$as_me:${as_lineno-$LINENO}: found $as_dir/$ac_word$ac_exec_ext" >&5 break 2 fi done done IFS=$as_save_IFS fi fi DSYMUTIL=$ac_cv_prog_DSYMUTIL if test -n "$DSYMUTIL"; then { $as_echo "$as_me:${as_lineno-$LINENO}: result: $DSYMUTIL" >&5 $as_echo "$DSYMUTIL" >&6; } else { $as_echo "$as_me:${as_lineno-$LINENO}: result: no" >&5 $as_echo "no" >&6; } fi fi if test -z "$ac_cv_prog_DSYMUTIL"; then ac_ct_DSYMUTIL=$DSYMUTIL # Extract the first word of "dsymutil", so it can be a program name with args. set dummy dsymutil; ac_word=$2 { $as_echo "$as_me:${as_lineno-$LINENO}: checking for $ac_word" >&5 $as_echo_n "checking for $ac_word... " >&6; } if ${ac_cv_prog_ac_ct_DSYMUTIL+:} false; then : $as_echo_n "(cached) " >&6 else if test -n "$ac_ct_DSYMUTIL"; then ac_cv_prog_ac_ct_DSYMUTIL="$ac_ct_DSYMUTIL" # Let the user override the test. else as_save_IFS=$IFS; IFS=$PATH_SEPARATOR for as_dir in $PATH do IFS=$as_save_IFS test -z "$as_dir" && as_dir=. for ac_exec_ext in '' $ac_executable_extensions; do if as_fn_executable_p "$as_dir/$ac_word$ac_exec_ext"; then ac_cv_prog_ac_ct_DSYMUTIL="dsymutil" $as_echo "$as_me:${as_lineno-$LINENO}: found $as_dir/$ac_word$ac_exec_ext" >&5 break 2 fi done done IFS=$as_save_IFS fi fi ac_ct_DSYMUTIL=$ac_cv_prog_ac_ct_DSYMUTIL if test -n "$ac_ct_DSYMUTIL"; then { $as_echo "$as_me:${as_lineno-$LINENO}: result: $ac_ct_DSYMUTIL" >&5 $as_echo "$ac_ct_DSYMUTIL" >&6; } else { $as_echo "$as_me:${as_lineno-$LINENO}: result: no" >&5 $as_echo "no" >&6; } fi if test "x$ac_ct_DSYMUTIL" = x; then DSYMUTIL=":" else case $cross_compiling:$ac_tool_warned in yes:) { $as_echo "$as_me:${as_lineno-$LINENO}: WARNING: using cross tools not prefixed with host triplet" >&5 $as_echo "$as_me: WARNING: using cross tools not prefixed with host triplet" >&2;} ac_tool_warned=yes ;; esac DSYMUTIL=$ac_ct_DSYMUTIL fi else DSYMUTIL="$ac_cv_prog_DSYMUTIL" fi if test -n "$ac_tool_prefix"; then # Extract the first word of "${ac_tool_prefix}nmedit", so it can be a program name with args. set dummy ${ac_tool_prefix}nmedit; ac_word=$2 { $as_echo "$as_me:${as_lineno-$LINENO}: checking for $ac_word" >&5 $as_echo_n "checking for $ac_word... " >&6; } if ${ac_cv_prog_NMEDIT+:} false; then : $as_echo_n "(cached) " >&6 else if test -n "$NMEDIT"; then ac_cv_prog_NMEDIT="$NMEDIT" # Let the user override the test. else as_save_IFS=$IFS; IFS=$PATH_SEPARATOR for as_dir in $PATH do IFS=$as_save_IFS test -z "$as_dir" && as_dir=. for ac_exec_ext in '' $ac_executable_extensions; do if as_fn_executable_p "$as_dir/$ac_word$ac_exec_ext"; then ac_cv_prog_NMEDIT="${ac_tool_prefix}nmedit" $as_echo "$as_me:${as_lineno-$LINENO}: found $as_dir/$ac_word$ac_exec_ext" >&5 break 2 fi done done IFS=$as_save_IFS fi fi NMEDIT=$ac_cv_prog_NMEDIT if test -n "$NMEDIT"; then { $as_echo "$as_me:${as_lineno-$LINENO}: result: $NMEDIT" >&5 $as_echo "$NMEDIT" >&6; } else { $as_echo "$as_me:${as_lineno-$LINENO}: result: no" >&5 $as_echo "no" >&6; } fi fi if test -z "$ac_cv_prog_NMEDIT"; then ac_ct_NMEDIT=$NMEDIT # Extract the first word of "nmedit", so it can be a program name with args. set dummy nmedit; ac_word=$2 { $as_echo "$as_me:${as_lineno-$LINENO}: checking for $ac_word" >&5 $as_echo_n "checking for $ac_word... " >&6; } if ${ac_cv_prog_ac_ct_NMEDIT+:} false; then : $as_echo_n "(cached) " >&6 else if test -n "$ac_ct_NMEDIT"; then ac_cv_prog_ac_ct_NMEDIT="$ac_ct_NMEDIT" # Let the user override the test. else as_save_IFS=$IFS; IFS=$PATH_SEPARATOR for as_dir in $PATH do IFS=$as_save_IFS test -z "$as_dir" && as_dir=. for ac_exec_ext in '' $ac_executable_extensions; do if as_fn_executable_p "$as_dir/$ac_word$ac_exec_ext"; then ac_cv_prog_ac_ct_NMEDIT="nmedit" $as_echo "$as_me:${as_lineno-$LINENO}: found $as_dir/$ac_word$ac_exec_ext" >&5 break 2 fi done done IFS=$as_save_IFS fi fi ac_ct_NMEDIT=$ac_cv_prog_ac_ct_NMEDIT if test -n "$ac_ct_NMEDIT"; then { $as_echo "$as_me:${as_lineno-$LINENO}: result: $ac_ct_NMEDIT" >&5 $as_echo "$ac_ct_NMEDIT" >&6; } else { $as_echo "$as_me:${as_lineno-$LINENO}: result: no" >&5 $as_echo "no" >&6; } fi if test "x$ac_ct_NMEDIT" = x; then NMEDIT=":" else case $cross_compiling:$ac_tool_warned in yes:) { $as_echo "$as_me:${as_lineno-$LINENO}: WARNING: using cross tools not prefixed with host triplet" >&5 $as_echo "$as_me: WARNING: using cross tools not prefixed with host triplet" >&2;} ac_tool_warned=yes ;; esac NMEDIT=$ac_ct_NMEDIT fi else NMEDIT="$ac_cv_prog_NMEDIT" fi if test -n "$ac_tool_prefix"; then # Extract the first word of "${ac_tool_prefix}lipo", so it can be a program name with args. set dummy ${ac_tool_prefix}lipo; ac_word=$2 { $as_echo "$as_me:${as_lineno-$LINENO}: checking for $ac_word" >&5 $as_echo_n "checking for $ac_word... " >&6; } if ${ac_cv_prog_LIPO+:} false; then : $as_echo_n "(cached) " >&6 else if test -n "$LIPO"; then ac_cv_prog_LIPO="$LIPO" # Let the user override the test. else as_save_IFS=$IFS; IFS=$PATH_SEPARATOR for as_dir in $PATH do IFS=$as_save_IFS test -z "$as_dir" && as_dir=. for ac_exec_ext in '' $ac_executable_extensions; do if as_fn_executable_p "$as_dir/$ac_word$ac_exec_ext"; then ac_cv_prog_LIPO="${ac_tool_prefix}lipo" $as_echo "$as_me:${as_lineno-$LINENO}: found $as_dir/$ac_word$ac_exec_ext" >&5 break 2 fi done done IFS=$as_save_IFS fi fi LIPO=$ac_cv_prog_LIPO if test -n "$LIPO"; then { $as_echo "$as_me:${as_lineno-$LINENO}: result: $LIPO" >&5 $as_echo "$LIPO" >&6; } else { $as_echo "$as_me:${as_lineno-$LINENO}: result: no" >&5 $as_echo "no" >&6; } fi fi if test -z "$ac_cv_prog_LIPO"; then ac_ct_LIPO=$LIPO # Extract the first word of "lipo", so it can be a program name with args. set dummy lipo; ac_word=$2 { $as_echo "$as_me:${as_lineno-$LINENO}: checking for $ac_word" >&5 $as_echo_n "checking for $ac_word... " >&6; } if ${ac_cv_prog_ac_ct_LIPO+:} false; then : $as_echo_n "(cached) " >&6 else if test -n "$ac_ct_LIPO"; then ac_cv_prog_ac_ct_LIPO="$ac_ct_LIPO" # Let the user override the test. else as_save_IFS=$IFS; IFS=$PATH_SEPARATOR for as_dir in $PATH do IFS=$as_save_IFS test -z "$as_dir" && as_dir=. for ac_exec_ext in '' $ac_executable_extensions; do if as_fn_executable_p "$as_dir/$ac_word$ac_exec_ext"; then ac_cv_prog_ac_ct_LIPO="lipo" $as_echo "$as_me:${as_lineno-$LINENO}: found $as_dir/$ac_word$ac_exec_ext" >&5 break 2 fi done done IFS=$as_save_IFS fi fi ac_ct_LIPO=$ac_cv_prog_ac_ct_LIPO if test -n "$ac_ct_LIPO"; then { $as_echo "$as_me:${as_lineno-$LINENO}: result: $ac_ct_LIPO" >&5 $as_echo "$ac_ct_LIPO" >&6; } else { $as_echo "$as_me:${as_lineno-$LINENO}: result: no" >&5 $as_echo "no" >&6; } fi if test "x$ac_ct_LIPO" = x; then LIPO=":" else case $cross_compiling:$ac_tool_warned in yes:) { $as_echo "$as_me:${as_lineno-$LINENO}: WARNING: using cross tools not prefixed with host triplet" >&5 $as_echo "$as_me: WARNING: using cross tools not prefixed with host triplet" >&2;} ac_tool_warned=yes ;; esac LIPO=$ac_ct_LIPO fi else LIPO="$ac_cv_prog_LIPO" fi if test -n "$ac_tool_prefix"; then # Extract the first word of "${ac_tool_prefix}otool", so it can be a program name with args. set dummy ${ac_tool_prefix}otool; ac_word=$2 { $as_echo "$as_me:${as_lineno-$LINENO}: checking for $ac_word" >&5 $as_echo_n "checking for $ac_word... " >&6; } if ${ac_cv_prog_OTOOL+:} false; then : $as_echo_n "(cached) " >&6 else if test -n "$OTOOL"; then ac_cv_prog_OTOOL="$OTOOL" # Let the user override the test. else as_save_IFS=$IFS; IFS=$PATH_SEPARATOR for as_dir in $PATH do IFS=$as_save_IFS test -z "$as_dir" && as_dir=. for ac_exec_ext in '' $ac_executable_extensions; do if as_fn_executable_p "$as_dir/$ac_word$ac_exec_ext"; then ac_cv_prog_OTOOL="${ac_tool_prefix}otool" $as_echo "$as_me:${as_lineno-$LINENO}: found $as_dir/$ac_word$ac_exec_ext" >&5 break 2 fi done done IFS=$as_save_IFS fi fi OTOOL=$ac_cv_prog_OTOOL if test -n "$OTOOL"; then { $as_echo "$as_me:${as_lineno-$LINENO}: result: $OTOOL" >&5 $as_echo "$OTOOL" >&6; } else { $as_echo "$as_me:${as_lineno-$LINENO}: result: no" >&5 $as_echo "no" >&6; } fi fi if test -z "$ac_cv_prog_OTOOL"; then ac_ct_OTOOL=$OTOOL # Extract the first word of "otool", so it can be a program name with args. set dummy otool; ac_word=$2 { $as_echo "$as_me:${as_lineno-$LINENO}: checking for $ac_word" >&5 $as_echo_n "checking for $ac_word... " >&6; } if ${ac_cv_prog_ac_ct_OTOOL+:} false; then : $as_echo_n "(cached) " >&6 else if test -n "$ac_ct_OTOOL"; then ac_cv_prog_ac_ct_OTOOL="$ac_ct_OTOOL" # Let the user override the test. else as_save_IFS=$IFS; IFS=$PATH_SEPARATOR for as_dir in $PATH do IFS=$as_save_IFS test -z "$as_dir" && as_dir=. for ac_exec_ext in '' $ac_executable_extensions; do if as_fn_executable_p "$as_dir/$ac_word$ac_exec_ext"; then ac_cv_prog_ac_ct_OTOOL="otool" $as_echo "$as_me:${as_lineno-$LINENO}: found $as_dir/$ac_word$ac_exec_ext" >&5 break 2 fi done done IFS=$as_save_IFS fi fi ac_ct_OTOOL=$ac_cv_prog_ac_ct_OTOOL if test -n "$ac_ct_OTOOL"; then { $as_echo "$as_me:${as_lineno-$LINENO}: result: $ac_ct_OTOOL" >&5 $as_echo "$ac_ct_OTOOL" >&6; } else { $as_echo "$as_me:${as_lineno-$LINENO}: result: no" >&5 $as_echo "no" >&6; } fi if test "x$ac_ct_OTOOL" = x; then OTOOL=":" else case $cross_compiling:$ac_tool_warned in yes:) { $as_echo "$as_me:${as_lineno-$LINENO}: WARNING: using cross tools not prefixed with host triplet" >&5 $as_echo "$as_me: WARNING: using cross tools not prefixed with host triplet" >&2;} ac_tool_warned=yes ;; esac OTOOL=$ac_ct_OTOOL fi else OTOOL="$ac_cv_prog_OTOOL" fi if test -n "$ac_tool_prefix"; then # Extract the first word of "${ac_tool_prefix}otool64", so it can be a program name with args. set dummy ${ac_tool_prefix}otool64; ac_word=$2 { $as_echo "$as_me:${as_lineno-$LINENO}: checking for $ac_word" >&5 $as_echo_n "checking for $ac_word... " >&6; } if ${ac_cv_prog_OTOOL64+:} false; then : $as_echo_n "(cached) " >&6 else if test -n "$OTOOL64"; then ac_cv_prog_OTOOL64="$OTOOL64" # Let the user override the test. else as_save_IFS=$IFS; IFS=$PATH_SEPARATOR for as_dir in $PATH do IFS=$as_save_IFS test -z "$as_dir" && as_dir=. for ac_exec_ext in '' $ac_executable_extensions; do if as_fn_executable_p "$as_dir/$ac_word$ac_exec_ext"; then ac_cv_prog_OTOOL64="${ac_tool_prefix}otool64" $as_echo "$as_me:${as_lineno-$LINENO}: found $as_dir/$ac_word$ac_exec_ext" >&5 break 2 fi done done IFS=$as_save_IFS fi fi OTOOL64=$ac_cv_prog_OTOOL64 if test -n "$OTOOL64"; then { $as_echo "$as_me:${as_lineno-$LINENO}: result: $OTOOL64" >&5 $as_echo "$OTOOL64" >&6; } else { $as_echo "$as_me:${as_lineno-$LINENO}: result: no" >&5 $as_echo "no" >&6; } fi fi if test -z "$ac_cv_prog_OTOOL64"; then ac_ct_OTOOL64=$OTOOL64 # Extract the first word of "otool64", so it can be a program name with args. set dummy otool64; ac_word=$2 { $as_echo "$as_me:${as_lineno-$LINENO}: checking for $ac_word" >&5 $as_echo_n "checking for $ac_word... " >&6; } if ${ac_cv_prog_ac_ct_OTOOL64+:} false; then : $as_echo_n "(cached) " >&6 else if test -n "$ac_ct_OTOOL64"; then ac_cv_prog_ac_ct_OTOOL64="$ac_ct_OTOOL64" # Let the user override the test. else as_save_IFS=$IFS; IFS=$PATH_SEPARATOR for as_dir in $PATH do IFS=$as_save_IFS test -z "$as_dir" && as_dir=. for ac_exec_ext in '' $ac_executable_extensions; do if as_fn_executable_p "$as_dir/$ac_word$ac_exec_ext"; then ac_cv_prog_ac_ct_OTOOL64="otool64" $as_echo "$as_me:${as_lineno-$LINENO}: found $as_dir/$ac_word$ac_exec_ext" >&5 break 2 fi done done IFS=$as_save_IFS fi fi ac_ct_OTOOL64=$ac_cv_prog_ac_ct_OTOOL64 if test -n "$ac_ct_OTOOL64"; then { $as_echo "$as_me:${as_lineno-$LINENO}: result: $ac_ct_OTOOL64" >&5 $as_echo "$ac_ct_OTOOL64" >&6; } else { $as_echo "$as_me:${as_lineno-$LINENO}: result: no" >&5 $as_echo "no" >&6; } fi if test "x$ac_ct_OTOOL64" = x; then OTOOL64=":" else case $cross_compiling:$ac_tool_warned in yes:) { $as_echo "$as_me:${as_lineno-$LINENO}: WARNING: using cross tools not prefixed with host triplet" >&5 $as_echo "$as_me: WARNING: using cross tools not prefixed with host triplet" >&2;} ac_tool_warned=yes ;; esac OTOOL64=$ac_ct_OTOOL64 fi else OTOOL64="$ac_cv_prog_OTOOL64" fi { $as_echo "$as_me:${as_lineno-$LINENO}: checking for -single_module linker flag" >&5 $as_echo_n "checking for -single_module linker flag... " >&6; } if ${lt_cv_apple_cc_single_mod+:} false; then : $as_echo_n "(cached) " >&6 else lt_cv_apple_cc_single_mod=no if test -z "$LT_MULTI_MODULE"; then # By default we will add the -single_module flag. You can override # by either setting the environment variable LT_MULTI_MODULE # non-empty at configure time, or by adding -multi_module to the # link flags. rm -rf libconftest.dylib* echo "int foo(void){return 1;}" > conftest.c echo "$LTCC $LTCFLAGS $LDFLAGS -o libconftest.dylib \ -dynamiclib -Wl,-single_module conftest.c" >&5 $LTCC $LTCFLAGS $LDFLAGS -o libconftest.dylib \ -dynamiclib -Wl,-single_module conftest.c 2>conftest.err _lt_result=$? # If there is a non-empty error log, and "single_module" # appears in it, assume the flag caused a linker warning if test -s conftest.err && $GREP single_module conftest.err; then cat conftest.err >&5 # Otherwise, if the output was created with a 0 exit code from # the compiler, it worked. elif test -f libconftest.dylib && test 0 = "$_lt_result"; then lt_cv_apple_cc_single_mod=yes else cat conftest.err >&5 fi rm -rf libconftest.dylib* rm -f conftest.* fi fi { $as_echo "$as_me:${as_lineno-$LINENO}: result: $lt_cv_apple_cc_single_mod" >&5 $as_echo "$lt_cv_apple_cc_single_mod" >&6; } { $as_echo "$as_me:${as_lineno-$LINENO}: checking for -exported_symbols_list linker flag" >&5 $as_echo_n "checking for -exported_symbols_list linker flag... " >&6; } if ${lt_cv_ld_exported_symbols_list+:} false; then : $as_echo_n "(cached) " >&6 else lt_cv_ld_exported_symbols_list=no save_LDFLAGS=$LDFLAGS echo "_main" > conftest.sym LDFLAGS="$LDFLAGS -Wl,-exported_symbols_list,conftest.sym" cat confdefs.h - <<_ACEOF >conftest.$ac_ext /* end confdefs.h. */ int main () { ; return 0; } _ACEOF if ac_fn_c_try_link "$LINENO"; then : lt_cv_ld_exported_symbols_list=yes else lt_cv_ld_exported_symbols_list=no fi rm -f core conftest.err conftest.$ac_objext \ conftest$ac_exeext conftest.$ac_ext LDFLAGS=$save_LDFLAGS fi { $as_echo "$as_me:${as_lineno-$LINENO}: result: $lt_cv_ld_exported_symbols_list" >&5 $as_echo "$lt_cv_ld_exported_symbols_list" >&6; } { $as_echo "$as_me:${as_lineno-$LINENO}: checking for -force_load linker flag" >&5 $as_echo_n "checking for -force_load linker flag... " >&6; } if ${lt_cv_ld_force_load+:} false; then : $as_echo_n "(cached) " >&6 else lt_cv_ld_force_load=no cat > conftest.c << _LT_EOF int forced_loaded() { return 2;} _LT_EOF echo "$LTCC $LTCFLAGS -c -o conftest.o conftest.c" >&5 $LTCC $LTCFLAGS -c -o conftest.o conftest.c 2>&5 echo "$AR cru libconftest.a conftest.o" >&5 $AR cru libconftest.a conftest.o 2>&5 echo "$RANLIB libconftest.a" >&5 $RANLIB libconftest.a 2>&5 cat > conftest.c << _LT_EOF int main() { return 0;} _LT_EOF echo "$LTCC $LTCFLAGS $LDFLAGS -o conftest conftest.c -Wl,-force_load,./libconftest.a" >&5 $LTCC $LTCFLAGS $LDFLAGS -o conftest conftest.c -Wl,-force_load,./libconftest.a 2>conftest.err _lt_result=$? if test -s conftest.err && $GREP force_load conftest.err; then cat conftest.err >&5 elif test -f conftest && test 0 = "$_lt_result" && $GREP forced_load conftest >/dev/null 2>&1; then lt_cv_ld_force_load=yes else cat conftest.err >&5 fi rm -f conftest.err libconftest.a conftest conftest.c rm -rf conftest.dSYM fi { $as_echo "$as_me:${as_lineno-$LINENO}: result: $lt_cv_ld_force_load" >&5 $as_echo "$lt_cv_ld_force_load" >&6; } case $host_os in rhapsody* | darwin1.[012]) _lt_dar_allow_undefined='$wl-undefined ${wl}suppress' ;; darwin1.*) _lt_dar_allow_undefined='$wl-flat_namespace $wl-undefined ${wl}suppress' ;; darwin*) # darwin 5.x on # if running on 10.5 or later, the deployment target defaults # to the OS version, if on x86, and 10.4, the deployment # target defaults to 10.4. Don't you love it? case ${MACOSX_DEPLOYMENT_TARGET-10.0},$host in 10.0,*86*-darwin8*|10.0,*-darwin[91]*) _lt_dar_allow_undefined='$wl-undefined ${wl}dynamic_lookup' ;; 10.[012][,.]*) _lt_dar_allow_undefined='$wl-flat_namespace $wl-undefined ${wl}suppress' ;; 10.*) _lt_dar_allow_undefined='$wl-undefined ${wl}dynamic_lookup' ;; esac ;; esac if test yes = "$lt_cv_apple_cc_single_mod"; then _lt_dar_single_mod='$single_module' fi if test yes = "$lt_cv_ld_exported_symbols_list"; then _lt_dar_export_syms=' $wl-exported_symbols_list,$output_objdir/$libname-symbols.expsym' else _lt_dar_export_syms='~$NMEDIT -s $output_objdir/$libname-symbols.expsym $lib' fi if test : != "$DSYMUTIL" && test no = "$lt_cv_ld_force_load"; then _lt_dsymutil='~$DSYMUTIL $lib || :' else _lt_dsymutil= fi ;; esac # func_munge_path_list VARIABLE PATH # ----------------------------------- # VARIABLE is name of variable containing _space_ separated list of # directories to be munged by the contents of PATH, which is string # having a format: # "DIR[:DIR]:" # string "DIR[ DIR]" will be prepended to VARIABLE # ":DIR[:DIR]" # string "DIR[ DIR]" will be appended to VARIABLE # "DIRP[:DIRP]::[DIRA:]DIRA" # string "DIRP[ DIRP]" will be prepended to VARIABLE and string # "DIRA[ DIRA]" will be appended to VARIABLE # "DIR[:DIR]" # VARIABLE will be replaced by "DIR[ DIR]" func_munge_path_list () { case x$2 in x) ;; *:) eval $1=\"`$ECHO $2 | $SED 's/:/ /g'` \$$1\" ;; x:*) eval $1=\"\$$1 `$ECHO $2 | $SED 's/:/ /g'`\" ;; *::*) eval $1=\"\$$1\ `$ECHO $2 | $SED -e 's/.*:://' -e 's/:/ /g'`\" eval $1=\"`$ECHO $2 | $SED -e 's/::.*//' -e 's/:/ /g'`\ \$$1\" ;; *) eval $1=\"`$ECHO $2 | $SED 's/:/ /g'`\" ;; esac } for ac_header in dlfcn.h do : ac_fn_c_check_header_compile "$LINENO" "dlfcn.h" "ac_cv_header_dlfcn_h" "$ac_includes_default " if test "x$ac_cv_header_dlfcn_h" = xyes; then : cat >>confdefs.h <<_ACEOF #define HAVE_DLFCN_H 1 _ACEOF fi done func_stripname_cnf () { case $2 in .*) func_stripname_result=`$ECHO "$3" | $SED "s%^$1%%; s%\\\\$2\$%%"`;; *) func_stripname_result=`$ECHO "$3" | $SED "s%^$1%%; s%$2\$%%"`;; esac } # func_stripname_cnf # Set options enable_dlopen=no enable_win32_dll=no # Check whether --enable-shared was given. if test "${enable_shared+set}" = set; then : enableval=$enable_shared; p=${PACKAGE-default} case $enableval in yes) enable_shared=yes ;; no) enable_shared=no ;; *) enable_shared=no # Look at the argument we got. We use all the common list separators. lt_save_ifs=$IFS; IFS=$IFS$PATH_SEPARATOR, for pkg in $enableval; do IFS=$lt_save_ifs if test "X$pkg" = "X$p"; then enable_shared=yes fi done IFS=$lt_save_ifs ;; esac else enable_shared=yes fi # Check whether --enable-static was given. if test "${enable_static+set}" = set; then : enableval=$enable_static; p=${PACKAGE-default} case $enableval in yes) enable_static=yes ;; no) enable_static=no ;; *) enable_static=no # Look at the argument we got. We use all the common list separators. lt_save_ifs=$IFS; IFS=$IFS$PATH_SEPARATOR, for pkg in $enableval; do IFS=$lt_save_ifs if test "X$pkg" = "X$p"; then enable_static=yes fi done IFS=$lt_save_ifs ;; esac else enable_static=yes fi # Check whether --with-pic was given. if test "${with_pic+set}" = set; then : withval=$with_pic; lt_p=${PACKAGE-default} case $withval in yes|no) pic_mode=$withval ;; *) pic_mode=default # Look at the argument we got. We use all the common list separators. lt_save_ifs=$IFS; IFS=$IFS$PATH_SEPARATOR, for lt_pkg in $withval; do IFS=$lt_save_ifs if test "X$lt_pkg" = "X$lt_p"; then pic_mode=yes fi done IFS=$lt_save_ifs ;; esac else pic_mode=default fi # Check whether --enable-fast-install was given. if test "${enable_fast_install+set}" = set; then : enableval=$enable_fast_install; p=${PACKAGE-default} case $enableval in yes) enable_fast_install=yes ;; no) enable_fast_install=no ;; *) enable_fast_install=no # Look at the argument we got. We use all the common list separators. lt_save_ifs=$IFS; IFS=$IFS$PATH_SEPARATOR, for pkg in $enableval; do IFS=$lt_save_ifs if test "X$pkg" = "X$p"; then enable_fast_install=yes fi done IFS=$lt_save_ifs ;; esac else enable_fast_install=yes fi shared_archive_member_spec= case $host,$enable_shared in power*-*-aix[5-9]*,yes) { $as_echo "$as_me:${as_lineno-$LINENO}: checking which variant of shared library versioning to provide" >&5 $as_echo_n "checking which variant of shared library versioning to provide... " >&6; } # Check whether --with-aix-soname was given. if test "${with_aix_soname+set}" = set; then : withval=$with_aix_soname; case $withval in aix|svr4|both) ;; *) as_fn_error $? "Unknown argument to --with-aix-soname" "$LINENO" 5 ;; esac lt_cv_with_aix_soname=$with_aix_soname else if ${lt_cv_with_aix_soname+:} false; then : $as_echo_n "(cached) " >&6 else lt_cv_with_aix_soname=aix fi with_aix_soname=$lt_cv_with_aix_soname fi { $as_echo "$as_me:${as_lineno-$LINENO}: result: $with_aix_soname" >&5 $as_echo "$with_aix_soname" >&6; } if test aix != "$with_aix_soname"; then # For the AIX way of multilib, we name the shared archive member # based on the bitwidth used, traditionally 'shr.o' or 'shr_64.o', # and 'shr.imp' or 'shr_64.imp', respectively, for the Import File. # Even when GNU compilers ignore OBJECT_MODE but need '-maix64' flag, # the AIX toolchain works better with OBJECT_MODE set (default 32). if test 64 = "${OBJECT_MODE-32}"; then shared_archive_member_spec=shr_64 else shared_archive_member_spec=shr fi fi ;; *) with_aix_soname=aix ;; esac # This can be used to rebuild libtool when needed LIBTOOL_DEPS=$ltmain # Always use our own libtool. LIBTOOL='$(SHELL) $(top_builddir)/libtool' test -z "$LN_S" && LN_S="ln -s" if test -n "${ZSH_VERSION+set}"; then setopt NO_GLOB_SUBST fi { $as_echo "$as_me:${as_lineno-$LINENO}: checking for objdir" >&5 $as_echo_n "checking for objdir... " >&6; } if ${lt_cv_objdir+:} false; then : $as_echo_n "(cached) " >&6 else rm -f .libs 2>/dev/null mkdir .libs 2>/dev/null if test -d .libs; then lt_cv_objdir=.libs else # MS-DOS does not allow filenames that begin with a dot. lt_cv_objdir=_libs fi rmdir .libs 2>/dev/null fi { $as_echo "$as_me:${as_lineno-$LINENO}: result: $lt_cv_objdir" >&5 $as_echo "$lt_cv_objdir" >&6; } objdir=$lt_cv_objdir cat >>confdefs.h <<_ACEOF #define LT_OBJDIR "$lt_cv_objdir/" _ACEOF case $host_os in aix3*) # AIX sometimes has problems with the GCC collect2 program. For some # reason, if we set the COLLECT_NAMES environment variable, the problems # vanish in a puff of smoke. if test set != "${COLLECT_NAMES+set}"; then COLLECT_NAMES= export COLLECT_NAMES fi ;; esac # Global variables: ofile=libtool can_build_shared=yes # All known linkers require a '.a' archive for static linking (except MSVC, # which needs '.lib'). libext=a with_gnu_ld=$lt_cv_prog_gnu_ld old_CC=$CC old_CFLAGS=$CFLAGS # Set sane defaults for various variables test -z "$CC" && CC=cc test -z "$LTCC" && LTCC=$CC test -z "$LTCFLAGS" && LTCFLAGS=$CFLAGS test -z "$LD" && LD=ld test -z "$ac_objext" && ac_objext=o func_cc_basename $compiler cc_basename=$func_cc_basename_result # Only perform the check for file, if the check method requires it test -z "$MAGIC_CMD" && MAGIC_CMD=file case $deplibs_check_method in file_magic*) if test "$file_magic_cmd" = '$MAGIC_CMD'; then { $as_echo "$as_me:${as_lineno-$LINENO}: checking for ${ac_tool_prefix}file" >&5 $as_echo_n "checking for ${ac_tool_prefix}file... " >&6; } if ${lt_cv_path_MAGIC_CMD+:} false; then : $as_echo_n "(cached) " >&6 else case $MAGIC_CMD in [\\/*] | ?:[\\/]*) lt_cv_path_MAGIC_CMD=$MAGIC_CMD # Let the user override the test with a path. ;; *) lt_save_MAGIC_CMD=$MAGIC_CMD lt_save_ifs=$IFS; IFS=$PATH_SEPARATOR ac_dummy="/usr/bin$PATH_SEPARATOR$PATH" for ac_dir in $ac_dummy; do IFS=$lt_save_ifs test -z "$ac_dir" && ac_dir=. if test -f "$ac_dir/${ac_tool_prefix}file"; then lt_cv_path_MAGIC_CMD=$ac_dir/"${ac_tool_prefix}file" if test -n "$file_magic_test_file"; then case $deplibs_check_method in "file_magic "*) file_magic_regex=`expr "$deplibs_check_method" : "file_magic \(.*\)"` MAGIC_CMD=$lt_cv_path_MAGIC_CMD if eval $file_magic_cmd \$file_magic_test_file 2> /dev/null | $EGREP "$file_magic_regex" > /dev/null; then : else cat <<_LT_EOF 1>&2 *** Warning: the command libtool uses to detect shared libraries, *** $file_magic_cmd, produces output that libtool cannot recognize. *** The result is that libtool may fail to recognize shared libraries *** as such. This will affect the creation of libtool libraries that *** depend on shared libraries, but programs linked with such libtool *** libraries will work regardless of this problem. Nevertheless, you *** may want to report the problem to your system manager and/or to *** bug-libtool@gnu.org _LT_EOF fi ;; esac fi break fi done IFS=$lt_save_ifs MAGIC_CMD=$lt_save_MAGIC_CMD ;; esac fi MAGIC_CMD=$lt_cv_path_MAGIC_CMD if test -n "$MAGIC_CMD"; then { $as_echo "$as_me:${as_lineno-$LINENO}: result: $MAGIC_CMD" >&5 $as_echo "$MAGIC_CMD" >&6; } else { $as_echo "$as_me:${as_lineno-$LINENO}: result: no" >&5 $as_echo "no" >&6; } fi if test -z "$lt_cv_path_MAGIC_CMD"; then if test -n "$ac_tool_prefix"; then { $as_echo "$as_me:${as_lineno-$LINENO}: checking for file" >&5 $as_echo_n "checking for file... " >&6; } if ${lt_cv_path_MAGIC_CMD+:} false; then : $as_echo_n "(cached) " >&6 else case $MAGIC_CMD in [\\/*] | ?:[\\/]*) lt_cv_path_MAGIC_CMD=$MAGIC_CMD # Let the user override the test with a path. ;; *) lt_save_MAGIC_CMD=$MAGIC_CMD lt_save_ifs=$IFS; IFS=$PATH_SEPARATOR ac_dummy="/usr/bin$PATH_SEPARATOR$PATH" for ac_dir in $ac_dummy; do IFS=$lt_save_ifs test -z "$ac_dir" && ac_dir=. if test -f "$ac_dir/file"; then lt_cv_path_MAGIC_CMD=$ac_dir/"file" if test -n "$file_magic_test_file"; then case $deplibs_check_method in "file_magic "*) file_magic_regex=`expr "$deplibs_check_method" : "file_magic \(.*\)"` MAGIC_CMD=$lt_cv_path_MAGIC_CMD if eval $file_magic_cmd \$file_magic_test_file 2> /dev/null | $EGREP "$file_magic_regex" > /dev/null; then : else cat <<_LT_EOF 1>&2 *** Warning: the command libtool uses to detect shared libraries, *** $file_magic_cmd, produces output that libtool cannot recognize. *** The result is that libtool may fail to recognize shared libraries *** as such. This will affect the creation of libtool libraries that *** depend on shared libraries, but programs linked with such libtool *** libraries will work regardless of this problem. Nevertheless, you *** may want to report the problem to your system manager and/or to *** bug-libtool@gnu.org _LT_EOF fi ;; esac fi break fi done IFS=$lt_save_ifs MAGIC_CMD=$lt_save_MAGIC_CMD ;; esac fi MAGIC_CMD=$lt_cv_path_MAGIC_CMD if test -n "$MAGIC_CMD"; then { $as_echo "$as_me:${as_lineno-$LINENO}: result: $MAGIC_CMD" >&5 $as_echo "$MAGIC_CMD" >&6; } else { $as_echo "$as_me:${as_lineno-$LINENO}: result: no" >&5 $as_echo "no" >&6; } fi else MAGIC_CMD=: fi fi fi ;; esac # Use C for the default configuration in the libtool script lt_save_CC=$CC ac_ext=c ac_cpp='$CPP $CPPFLAGS' ac_compile='$CC -c $CFLAGS $CPPFLAGS conftest.$ac_ext >&5' ac_link='$CC -o conftest$ac_exeext $CFLAGS $CPPFLAGS $LDFLAGS conftest.$ac_ext $LIBS >&5' ac_compiler_gnu=$ac_cv_c_compiler_gnu # Source file extension for C test sources. ac_ext=c # Object file extension for compiled C test sources. objext=o objext=$objext # Code to be used in simple compile tests lt_simple_compile_test_code="int some_variable = 0;" # Code to be used in simple link tests lt_simple_link_test_code='int main(){return(0);}' # If no C compiler was specified, use CC. LTCC=${LTCC-"$CC"} # If no C compiler flags were specified, use CFLAGS. LTCFLAGS=${LTCFLAGS-"$CFLAGS"} # Allow CC to be a program name with arguments. compiler=$CC # Save the default compiler, since it gets overwritten when the other # tags are being tested, and _LT_TAGVAR(compiler, []) is a NOP. compiler_DEFAULT=$CC # save warnings/boilerplate of simple test code ac_outfile=conftest.$ac_objext echo "$lt_simple_compile_test_code" >conftest.$ac_ext eval "$ac_compile" 2>&1 >/dev/null | $SED '/^$/d; /^ *+/d' >conftest.err _lt_compiler_boilerplate=`cat conftest.err` $RM conftest* ac_outfile=conftest.$ac_objext echo "$lt_simple_link_test_code" >conftest.$ac_ext eval "$ac_link" 2>&1 >/dev/null | $SED '/^$/d; /^ *+/d' >conftest.err _lt_linker_boilerplate=`cat conftest.err` $RM -r conftest* ## CAVEAT EMPTOR: ## There is no encapsulation within the following macros, do not change ## the running order or otherwise move them around unless you know exactly ## what you are doing... if test -n "$compiler"; then lt_prog_compiler_no_builtin_flag= if test yes = "$GCC"; then case $cc_basename in nvcc*) lt_prog_compiler_no_builtin_flag=' -Xcompiler -fno-builtin' ;; *) lt_prog_compiler_no_builtin_flag=' -fno-builtin' ;; esac { $as_echo "$as_me:${as_lineno-$LINENO}: checking if $compiler supports -fno-rtti -fno-exceptions" >&5 $as_echo_n "checking if $compiler supports -fno-rtti -fno-exceptions... " >&6; } if ${lt_cv_prog_compiler_rtti_exceptions+:} false; then : $as_echo_n "(cached) " >&6 else lt_cv_prog_compiler_rtti_exceptions=no ac_outfile=conftest.$ac_objext echo "$lt_simple_compile_test_code" > conftest.$ac_ext lt_compiler_flag="-fno-rtti -fno-exceptions" ## exclude from sc_useless_quotes_in_assignment # Insert the option either (1) after the last *FLAGS variable, or # (2) before a word containing "conftest.", or (3) at the end. # Note that $ac_compile itself does not contain backslashes and begins # with a dollar sign (not a hyphen), so the echo should work correctly. # The option is referenced via a variable to avoid confusing sed. lt_compile=`echo "$ac_compile" | $SED \ -e 's:.*FLAGS}\{0,1\} :&$lt_compiler_flag :; t' \ -e 's: [^ ]*conftest\.: $lt_compiler_flag&:; t' \ -e 's:$: $lt_compiler_flag:'` (eval echo "\"\$as_me:$LINENO: $lt_compile\"" >&5) (eval "$lt_compile" 2>conftest.err) ac_status=$? cat conftest.err >&5 echo "$as_me:$LINENO: \$? = $ac_status" >&5 if (exit $ac_status) && test -s "$ac_outfile"; then # The compiler can only warn and ignore the option if not recognized # So say no if there are warnings other than the usual output. $ECHO "$_lt_compiler_boilerplate" | $SED '/^$/d' >conftest.exp $SED '/^$/d; /^ *+/d' conftest.err >conftest.er2 if test ! -s conftest.er2 || diff conftest.exp conftest.er2 >/dev/null; then lt_cv_prog_compiler_rtti_exceptions=yes fi fi $RM conftest* fi { $as_echo "$as_me:${as_lineno-$LINENO}: result: $lt_cv_prog_compiler_rtti_exceptions" >&5 $as_echo "$lt_cv_prog_compiler_rtti_exceptions" >&6; } if test yes = "$lt_cv_prog_compiler_rtti_exceptions"; then lt_prog_compiler_no_builtin_flag="$lt_prog_compiler_no_builtin_flag -fno-rtti -fno-exceptions" else : fi fi lt_prog_compiler_wl= lt_prog_compiler_pic= lt_prog_compiler_static= if test yes = "$GCC"; then lt_prog_compiler_wl='-Wl,' lt_prog_compiler_static='-static' case $host_os in aix*) # All AIX code is PIC. if test ia64 = "$host_cpu"; then # AIX 5 now supports IA64 processor lt_prog_compiler_static='-Bstatic' fi lt_prog_compiler_pic='-fPIC' ;; amigaos*) case $host_cpu in powerpc) # see comment about AmigaOS4 .so support lt_prog_compiler_pic='-fPIC' ;; m68k) # FIXME: we need at least 68020 code to build shared libraries, but # adding the '-m68020' flag to GCC prevents building anything better, # like '-m68040'. lt_prog_compiler_pic='-m68020 -resident32 -malways-restore-a4' ;; esac ;; beos* | irix5* | irix6* | nonstopux* | osf3* | osf4* | osf5*) # PIC is the default for these OSes. ;; mingw* | cygwin* | pw32* | os2* | cegcc*) # This hack is so that the source file can tell whether it is being # built for inclusion in a dll (and should export symbols for example). # Although the cygwin gcc ignores -fPIC, still need this for old-style # (--disable-auto-import) libraries lt_prog_compiler_pic='-DDLL_EXPORT' case $host_os in os2*) lt_prog_compiler_static='$wl-static' ;; esac ;; darwin* | rhapsody*) # PIC is the default on this platform # Common symbols not allowed in MH_DYLIB files lt_prog_compiler_pic='-fno-common' ;; haiku*) # PIC is the default for Haiku. # The "-static" flag exists, but is broken. lt_prog_compiler_static= ;; hpux*) # PIC is the default for 64-bit PA HP-UX, but not for 32-bit # PA HP-UX. On IA64 HP-UX, PIC is the default but the pic flag # sets the default TLS model and affects inlining. case $host_cpu in hppa*64*) # +Z the default ;; *) lt_prog_compiler_pic='-fPIC' ;; esac ;; interix[3-9]*) # Interix 3.x gcc -fpic/-fPIC options generate broken code. # Instead, we relocate shared libraries at runtime. ;; msdosdjgpp*) # Just because we use GCC doesn't mean we suddenly get shared libraries # on systems that don't support them. lt_prog_compiler_can_build_shared=no enable_shared=no ;; *nto* | *qnx*) # QNX uses GNU C++, but need to define -shared option too, otherwise # it will coredump. lt_prog_compiler_pic='-fPIC -shared' ;; sysv4*MP*) if test -d /usr/nec; then lt_prog_compiler_pic=-Kconform_pic fi ;; *) lt_prog_compiler_pic='-fPIC' ;; esac case $cc_basename in nvcc*) # Cuda Compiler Driver 2.2 lt_prog_compiler_wl='-Xlinker ' if test -n "$lt_prog_compiler_pic"; then lt_prog_compiler_pic="-Xcompiler $lt_prog_compiler_pic" fi ;; esac else # PORTME Check for flag to pass linker flags through the system compiler. case $host_os in aix*) lt_prog_compiler_wl='-Wl,' if test ia64 = "$host_cpu"; then # AIX 5 now supports IA64 processor lt_prog_compiler_static='-Bstatic' else lt_prog_compiler_static='-bnso -bI:/lib/syscalls.exp' fi ;; darwin* | rhapsody*) # PIC is the default on this platform # Common symbols not allowed in MH_DYLIB files lt_prog_compiler_pic='-fno-common' case $cc_basename in nagfor*) # NAG Fortran compiler lt_prog_compiler_wl='-Wl,-Wl,,' lt_prog_compiler_pic='-PIC' lt_prog_compiler_static='-Bstatic' ;; esac ;; mingw* | cygwin* | pw32* | os2* | cegcc*) # This hack is so that the source file can tell whether it is being # built for inclusion in a dll (and should export symbols for example). lt_prog_compiler_pic='-DDLL_EXPORT' case $host_os in os2*) lt_prog_compiler_static='$wl-static' ;; esac ;; hpux9* | hpux10* | hpux11*) lt_prog_compiler_wl='-Wl,' # PIC is the default for IA64 HP-UX and 64-bit HP-UX, but # not for PA HP-UX. case $host_cpu in hppa*64*|ia64*) # +Z the default ;; *) lt_prog_compiler_pic='+Z' ;; esac # Is there a better lt_prog_compiler_static that works with the bundled CC? lt_prog_compiler_static='$wl-a ${wl}archive' ;; irix5* | irix6* | nonstopux*) lt_prog_compiler_wl='-Wl,' # PIC (with -KPIC) is the default. lt_prog_compiler_static='-non_shared' ;; linux* | k*bsd*-gnu | kopensolaris*-gnu | gnu*) case $cc_basename in # old Intel for x86_64, which still supported -KPIC. ecc*) lt_prog_compiler_wl='-Wl,' lt_prog_compiler_pic='-KPIC' lt_prog_compiler_static='-static' ;; # flang / f18. f95 an alias for gfortran or flang on Debian flang* | f18* | f95*) lt_prog_compiler_wl='-Wl,' lt_prog_compiler_pic='-fPIC' lt_prog_compiler_static='-static' ;; # icc used to be incompatible with GCC. # ICC 10 doesn't accept -KPIC any more. icc* | ifort*) lt_prog_compiler_wl='-Wl,' lt_prog_compiler_pic='-fPIC' lt_prog_compiler_static='-static' ;; # Lahey Fortran 8.1. lf95*) lt_prog_compiler_wl='-Wl,' lt_prog_compiler_pic='--shared' lt_prog_compiler_static='--static' ;; nagfor*) # NAG Fortran compiler lt_prog_compiler_wl='-Wl,-Wl,,' lt_prog_compiler_pic='-PIC' lt_prog_compiler_static='-Bstatic' ;; tcc*) # Fabrice Bellard et al's Tiny C Compiler lt_prog_compiler_wl='-Wl,' lt_prog_compiler_pic='-fPIC' lt_prog_compiler_static='-static' ;; pgcc* | pgf77* | pgf90* | pgf95* | pgfortran*) # Portland Group compilers (*not* the Pentium gcc compiler, # which looks to be a dead project) lt_prog_compiler_wl='-Wl,' lt_prog_compiler_pic='-fpic' lt_prog_compiler_static='-Bstatic' ;; ccc*) lt_prog_compiler_wl='-Wl,' # All Alpha code is PIC. lt_prog_compiler_static='-non_shared' ;; xl* | bgxl* | bgf* | mpixl*) # IBM XL C 8.0/Fortran 10.1, 11.1 on PPC and BlueGene lt_prog_compiler_wl='-Wl,' lt_prog_compiler_pic='-qpic' lt_prog_compiler_static='-qstaticlink' ;; *) case `$CC -V 2>&1 | sed 5q` in *Sun\ Ceres\ Fortran* | *Sun*Fortran*\ [1-7].* | *Sun*Fortran*\ 8.[0-3]*) # Sun Fortran 8.3 passes all unrecognized flags to the linker lt_prog_compiler_pic='-KPIC' lt_prog_compiler_static='-Bstatic' lt_prog_compiler_wl='' ;; *Sun\ F* | *Sun*Fortran*) lt_prog_compiler_pic='-KPIC' lt_prog_compiler_static='-Bstatic' lt_prog_compiler_wl='-Qoption ld ' ;; *Sun\ C*) # Sun C 5.9 lt_prog_compiler_pic='-KPIC' lt_prog_compiler_static='-Bstatic' lt_prog_compiler_wl='-Wl,' ;; *Intel*\ [CF]*Compiler*) lt_prog_compiler_wl='-Wl,' lt_prog_compiler_pic='-fPIC' lt_prog_compiler_static='-static' ;; *Portland\ Group*) lt_prog_compiler_wl='-Wl,' lt_prog_compiler_pic='-fpic' lt_prog_compiler_static='-Bstatic' ;; esac ;; esac ;; newsos6) lt_prog_compiler_pic='-KPIC' lt_prog_compiler_static='-Bstatic' ;; *nto* | *qnx*) # QNX uses GNU C++, but need to define -shared option too, otherwise # it will coredump. lt_prog_compiler_pic='-fPIC -shared' ;; osf3* | osf4* | osf5*) lt_prog_compiler_wl='-Wl,' # All OSF/1 code is PIC. lt_prog_compiler_static='-non_shared' ;; rdos*) lt_prog_compiler_static='-non_shared' ;; solaris*) lt_prog_compiler_pic='-KPIC' lt_prog_compiler_static='-Bstatic' case $cc_basename in f77* | f90* | f95* | sunf77* | sunf90* | sunf95*) lt_prog_compiler_wl='-Qoption ld ';; *) lt_prog_compiler_wl='-Wl,';; esac ;; sunos4*) lt_prog_compiler_wl='-Qoption ld ' lt_prog_compiler_pic='-PIC' lt_prog_compiler_static='-Bstatic' ;; sysv4 | sysv4.2uw2* | sysv4.3*) lt_prog_compiler_wl='-Wl,' lt_prog_compiler_pic='-KPIC' lt_prog_compiler_static='-Bstatic' ;; sysv4*MP*) if test -d /usr/nec; then lt_prog_compiler_pic='-Kconform_pic' lt_prog_compiler_static='-Bstatic' fi ;; sysv5* | unixware* | sco3.2v5* | sco5v6* | OpenUNIX*) lt_prog_compiler_wl='-Wl,' lt_prog_compiler_pic='-KPIC' lt_prog_compiler_static='-Bstatic' ;; unicos*) lt_prog_compiler_wl='-Wl,' lt_prog_compiler_can_build_shared=no ;; uts4*) lt_prog_compiler_pic='-pic' lt_prog_compiler_static='-Bstatic' ;; *) lt_prog_compiler_can_build_shared=no ;; esac fi case $host_os in # For platforms that do not support PIC, -DPIC is meaningless: *djgpp*) lt_prog_compiler_pic= ;; *) lt_prog_compiler_pic="$lt_prog_compiler_pic -DPIC" ;; esac { $as_echo "$as_me:${as_lineno-$LINENO}: checking for $compiler option to produce PIC" >&5 $as_echo_n "checking for $compiler option to produce PIC... " >&6; } if ${lt_cv_prog_compiler_pic+:} false; then : $as_echo_n "(cached) " >&6 else lt_cv_prog_compiler_pic=$lt_prog_compiler_pic fi { $as_echo "$as_me:${as_lineno-$LINENO}: result: $lt_cv_prog_compiler_pic" >&5 $as_echo "$lt_cv_prog_compiler_pic" >&6; } lt_prog_compiler_pic=$lt_cv_prog_compiler_pic # # Check to make sure the PIC flag actually works. # if test -n "$lt_prog_compiler_pic"; then { $as_echo "$as_me:${as_lineno-$LINENO}: checking if $compiler PIC flag $lt_prog_compiler_pic works" >&5 $as_echo_n "checking if $compiler PIC flag $lt_prog_compiler_pic works... " >&6; } if ${lt_cv_prog_compiler_pic_works+:} false; then : $as_echo_n "(cached) " >&6 else lt_cv_prog_compiler_pic_works=no ac_outfile=conftest.$ac_objext echo "$lt_simple_compile_test_code" > conftest.$ac_ext lt_compiler_flag="$lt_prog_compiler_pic -DPIC" ## exclude from sc_useless_quotes_in_assignment # Insert the option either (1) after the last *FLAGS variable, or # (2) before a word containing "conftest.", or (3) at the end. # Note that $ac_compile itself does not contain backslashes and begins # with a dollar sign (not a hyphen), so the echo should work correctly. # The option is referenced via a variable to avoid confusing sed. lt_compile=`echo "$ac_compile" | $SED \ -e 's:.*FLAGS}\{0,1\} :&$lt_compiler_flag :; t' \ -e 's: [^ ]*conftest\.: $lt_compiler_flag&:; t' \ -e 's:$: $lt_compiler_flag:'` (eval echo "\"\$as_me:$LINENO: $lt_compile\"" >&5) (eval "$lt_compile" 2>conftest.err) ac_status=$? cat conftest.err >&5 echo "$as_me:$LINENO: \$? = $ac_status" >&5 if (exit $ac_status) && test -s "$ac_outfile"; then # The compiler can only warn and ignore the option if not recognized # So say no if there are warnings other than the usual output. $ECHO "$_lt_compiler_boilerplate" | $SED '/^$/d' >conftest.exp $SED '/^$/d; /^ *+/d' conftest.err >conftest.er2 if test ! -s conftest.er2 || diff conftest.exp conftest.er2 >/dev/null; then lt_cv_prog_compiler_pic_works=yes fi fi $RM conftest* fi { $as_echo "$as_me:${as_lineno-$LINENO}: result: $lt_cv_prog_compiler_pic_works" >&5 $as_echo "$lt_cv_prog_compiler_pic_works" >&6; } if test yes = "$lt_cv_prog_compiler_pic_works"; then case $lt_prog_compiler_pic in "" | " "*) ;; *) lt_prog_compiler_pic=" $lt_prog_compiler_pic" ;; esac else lt_prog_compiler_pic= lt_prog_compiler_can_build_shared=no fi fi # # Check to make sure the static flag actually works. # wl=$lt_prog_compiler_wl eval lt_tmp_static_flag=\"$lt_prog_compiler_static\" { $as_echo "$as_me:${as_lineno-$LINENO}: checking if $compiler static flag $lt_tmp_static_flag works" >&5 $as_echo_n "checking if $compiler static flag $lt_tmp_static_flag works... " >&6; } if ${lt_cv_prog_compiler_static_works+:} false; then : $as_echo_n "(cached) " >&6 else lt_cv_prog_compiler_static_works=no save_LDFLAGS=$LDFLAGS LDFLAGS="$LDFLAGS $lt_tmp_static_flag" echo "$lt_simple_link_test_code" > conftest.$ac_ext if (eval $ac_link 2>conftest.err) && test -s conftest$ac_exeext; then # The linker can only warn and ignore the option if not recognized # So say no if there are warnings if test -s conftest.err; then # Append any errors to the config.log. cat conftest.err 1>&5 $ECHO "$_lt_linker_boilerplate" | $SED '/^$/d' > conftest.exp $SED '/^$/d; /^ *+/d' conftest.err >conftest.er2 if diff conftest.exp conftest.er2 >/dev/null; then lt_cv_prog_compiler_static_works=yes fi else lt_cv_prog_compiler_static_works=yes fi fi $RM -r conftest* LDFLAGS=$save_LDFLAGS fi { $as_echo "$as_me:${as_lineno-$LINENO}: result: $lt_cv_prog_compiler_static_works" >&5 $as_echo "$lt_cv_prog_compiler_static_works" >&6; } if test yes = "$lt_cv_prog_compiler_static_works"; then : else lt_prog_compiler_static= fi { $as_echo "$as_me:${as_lineno-$LINENO}: checking if $compiler supports -c -o file.$ac_objext" >&5 $as_echo_n "checking if $compiler supports -c -o file.$ac_objext... " >&6; } if ${lt_cv_prog_compiler_c_o+:} false; then : $as_echo_n "(cached) " >&6 else lt_cv_prog_compiler_c_o=no $RM -r conftest 2>/dev/null mkdir conftest cd conftest mkdir out echo "$lt_simple_compile_test_code" > conftest.$ac_ext lt_compiler_flag="-o out/conftest2.$ac_objext" # Insert the option either (1) after the last *FLAGS variable, or # (2) before a word containing "conftest.", or (3) at the end. # Note that $ac_compile itself does not contain backslashes and begins # with a dollar sign (not a hyphen), so the echo should work correctly. lt_compile=`echo "$ac_compile" | $SED \ -e 's:.*FLAGS}\{0,1\} :&$lt_compiler_flag :; t' \ -e 's: [^ ]*conftest\.: $lt_compiler_flag&:; t' \ -e 's:$: $lt_compiler_flag:'` (eval echo "\"\$as_me:$LINENO: $lt_compile\"" >&5) (eval "$lt_compile" 2>out/conftest.err) ac_status=$? cat out/conftest.err >&5 echo "$as_me:$LINENO: \$? = $ac_status" >&5 if (exit $ac_status) && test -s out/conftest2.$ac_objext then # The compiler can only warn and ignore the option if not recognized # So say no if there are warnings $ECHO "$_lt_compiler_boilerplate" | $SED '/^$/d' > out/conftest.exp $SED '/^$/d; /^ *+/d' out/conftest.err >out/conftest.er2 if test ! -s out/conftest.er2 || diff out/conftest.exp out/conftest.er2 >/dev/null; then lt_cv_prog_compiler_c_o=yes fi fi chmod u+w . 2>&5 $RM conftest* # SGI C++ compiler will create directory out/ii_files/ for # template instantiation test -d out/ii_files && $RM out/ii_files/* && rmdir out/ii_files $RM out/* && rmdir out cd .. $RM -r conftest $RM conftest* fi { $as_echo "$as_me:${as_lineno-$LINENO}: result: $lt_cv_prog_compiler_c_o" >&5 $as_echo "$lt_cv_prog_compiler_c_o" >&6; } { $as_echo "$as_me:${as_lineno-$LINENO}: checking if $compiler supports -c -o file.$ac_objext" >&5 $as_echo_n "checking if $compiler supports -c -o file.$ac_objext... " >&6; } if ${lt_cv_prog_compiler_c_o+:} false; then : $as_echo_n "(cached) " >&6 else lt_cv_prog_compiler_c_o=no $RM -r conftest 2>/dev/null mkdir conftest cd conftest mkdir out echo "$lt_simple_compile_test_code" > conftest.$ac_ext lt_compiler_flag="-o out/conftest2.$ac_objext" # Insert the option either (1) after the last *FLAGS variable, or # (2) before a word containing "conftest.", or (3) at the end. # Note that $ac_compile itself does not contain backslashes and begins # with a dollar sign (not a hyphen), so the echo should work correctly. lt_compile=`echo "$ac_compile" | $SED \ -e 's:.*FLAGS}\{0,1\} :&$lt_compiler_flag :; t' \ -e 's: [^ ]*conftest\.: $lt_compiler_flag&:; t' \ -e 's:$: $lt_compiler_flag:'` (eval echo "\"\$as_me:$LINENO: $lt_compile\"" >&5) (eval "$lt_compile" 2>out/conftest.err) ac_status=$? cat out/conftest.err >&5 echo "$as_me:$LINENO: \$? = $ac_status" >&5 if (exit $ac_status) && test -s out/conftest2.$ac_objext then # The compiler can only warn and ignore the option if not recognized # So say no if there are warnings $ECHO "$_lt_compiler_boilerplate" | $SED '/^$/d' > out/conftest.exp $SED '/^$/d; /^ *+/d' out/conftest.err >out/conftest.er2 if test ! -s out/conftest.er2 || diff out/conftest.exp out/conftest.er2 >/dev/null; then lt_cv_prog_compiler_c_o=yes fi fi chmod u+w . 2>&5 $RM conftest* # SGI C++ compiler will create directory out/ii_files/ for # template instantiation test -d out/ii_files && $RM out/ii_files/* && rmdir out/ii_files $RM out/* && rmdir out cd .. $RM -r conftest $RM conftest* fi { $as_echo "$as_me:${as_lineno-$LINENO}: result: $lt_cv_prog_compiler_c_o" >&5 $as_echo "$lt_cv_prog_compiler_c_o" >&6; } hard_links=nottested if test no = "$lt_cv_prog_compiler_c_o" && test no != "$need_locks"; then # do not overwrite the value of need_locks provided by the user { $as_echo "$as_me:${as_lineno-$LINENO}: checking if we can lock with hard links" >&5 $as_echo_n "checking if we can lock with hard links... " >&6; } hard_links=yes $RM conftest* ln conftest.a conftest.b 2>/dev/null && hard_links=no touch conftest.a ln conftest.a conftest.b 2>&5 || hard_links=no ln conftest.a conftest.b 2>/dev/null && hard_links=no { $as_echo "$as_me:${as_lineno-$LINENO}: result: $hard_links" >&5 $as_echo "$hard_links" >&6; } if test no = "$hard_links"; then { $as_echo "$as_me:${as_lineno-$LINENO}: WARNING: '$CC' does not support '-c -o', so 'make -j' may be unsafe" >&5 $as_echo "$as_me: WARNING: '$CC' does not support '-c -o', so 'make -j' may be unsafe" >&2;} need_locks=warn fi else need_locks=no fi { $as_echo "$as_me:${as_lineno-$LINENO}: checking whether the $compiler linker ($LD) supports shared libraries" >&5 $as_echo_n "checking whether the $compiler linker ($LD) supports shared libraries... " >&6; } runpath_var= allow_undefined_flag= always_export_symbols=no archive_cmds= archive_expsym_cmds= compiler_needs_object=no enable_shared_with_static_runtimes=no export_dynamic_flag_spec= export_symbols_cmds='$NM $libobjs $convenience | $global_symbol_pipe | $SED '\''s/.* //'\'' | sort | uniq > $export_symbols' hardcode_automatic=no hardcode_direct=no hardcode_direct_absolute=no hardcode_libdir_flag_spec= hardcode_libdir_separator= hardcode_minus_L=no hardcode_shlibpath_var=unsupported inherit_rpath=no link_all_deplibs=unknown module_cmds= module_expsym_cmds= old_archive_from_new_cmds= old_archive_from_expsyms_cmds= thread_safe_flag_spec= whole_archive_flag_spec= # include_expsyms should be a list of space-separated symbols to be *always* # included in the symbol list include_expsyms= # exclude_expsyms can be an extended regexp of symbols to exclude # it will be wrapped by ' (' and ')$', so one must not match beginning or # end of line. Example: 'a|bc|.*d.*' will exclude the symbols 'a' and 'bc', # as well as any symbol that contains 'd'. exclude_expsyms='_GLOBAL_OFFSET_TABLE_|_GLOBAL__F[ID]_.*' # Although _GLOBAL_OFFSET_TABLE_ is a valid symbol C name, most a.out # platforms (ab)use it in PIC code, but their linkers get confused if # the symbol is explicitly referenced. Since portable code cannot # rely on this symbol name, it's probably fine to never include it in # preloaded symbol tables. # Exclude shared library initialization/finalization symbols. extract_expsyms_cmds= case $host_os in cygwin* | mingw* | pw32* | cegcc*) # FIXME: the MSVC++ port hasn't been tested in a loooong time # When not using gcc, we currently assume that we are using # Microsoft Visual C++. if test yes != "$GCC"; then with_gnu_ld=no fi ;; interix*) # we just hope/assume this is gcc and not c89 (= MSVC++) with_gnu_ld=yes ;; openbsd* | bitrig*) with_gnu_ld=no ;; linux* | k*bsd*-gnu | gnu*) link_all_deplibs=no ;; esac ld_shlibs=yes # On some targets, GNU ld is compatible enough with the native linker # that we're better off using the native interface for both. lt_use_gnu_ld_interface=no if test yes = "$with_gnu_ld"; then case $host_os in aix*) # The AIX port of GNU ld has always aspired to compatibility # with the native linker. However, as the warning in the GNU ld # block says, versions before 2.19.5* couldn't really create working # shared libraries, regardless of the interface used. case `$LD -v 2>&1` in *\ \(GNU\ Binutils\)\ 2.19.5*) ;; *\ \(GNU\ Binutils\)\ 2.[2-9]*) ;; *\ \(GNU\ Binutils\)\ [3-9]*) ;; *) lt_use_gnu_ld_interface=yes ;; esac ;; *) lt_use_gnu_ld_interface=yes ;; esac fi if test yes = "$lt_use_gnu_ld_interface"; then # If archive_cmds runs LD, not CC, wlarc should be empty wlarc='$wl' # Set some defaults for GNU ld with shared library support. These # are reset later if shared libraries are not supported. Putting them # here allows them to be overridden if necessary. runpath_var=LD_RUN_PATH hardcode_libdir_flag_spec='$wl-rpath $wl$libdir' export_dynamic_flag_spec='$wl--export-dynamic' # ancient GNU ld didn't support --whole-archive et. al. if $LD --help 2>&1 | $GREP 'no-whole-archive' > /dev/null; then whole_archive_flag_spec=$wlarc'--whole-archive$convenience '$wlarc'--no-whole-archive' else whole_archive_flag_spec= fi supports_anon_versioning=no case `$LD -v | $SED -e 's/(^)\+)\s\+//' 2>&1` in *GNU\ gold*) supports_anon_versioning=yes ;; *\ [01].* | *\ 2.[0-9].* | *\ 2.10.*) ;; # catch versions < 2.11 *\ 2.11.93.0.2\ *) supports_anon_versioning=yes ;; # RH7.3 ... *\ 2.11.92.0.12\ *) supports_anon_versioning=yes ;; # Mandrake 8.2 ... *\ 2.11.*) ;; # other 2.11 versions *) supports_anon_versioning=yes ;; esac # See if GNU ld supports shared libraries. case $host_os in aix[3-9]*) # On AIX/PPC, the GNU linker is very broken if test ia64 != "$host_cpu"; then ld_shlibs=no cat <<_LT_EOF 1>&2 *** Warning: the GNU linker, at least up to release 2.19, is reported *** to be unable to reliably create shared libraries on AIX. *** Therefore, libtool is disabling shared libraries support. If you *** really care for shared libraries, you may want to install binutils *** 2.20 or above, or modify your PATH so that a non-GNU linker is found. *** You will then need to restart the configuration process. _LT_EOF fi ;; amigaos*) case $host_cpu in powerpc) # see comment about AmigaOS4 .so support archive_cmds='$CC -shared $libobjs $deplibs $compiler_flags $wl-soname $wl$soname -o $lib' archive_expsym_cmds='' ;; m68k) archive_cmds='$RM $output_objdir/a2ixlibrary.data~$ECHO "#define NAME $libname" > $output_objdir/a2ixlibrary.data~$ECHO "#define LIBRARY_ID 1" >> $output_objdir/a2ixlibrary.data~$ECHO "#define VERSION $major" >> $output_objdir/a2ixlibrary.data~$ECHO "#define REVISION $revision" >> $output_objdir/a2ixlibrary.data~$AR $AR_FLAGS $lib $libobjs~$RANLIB $lib~(cd $output_objdir && a2ixlibrary -32)' hardcode_libdir_flag_spec='-L$libdir' hardcode_minus_L=yes ;; esac ;; beos*) if $LD --help 2>&1 | $GREP ': supported targets:.* elf' > /dev/null; then allow_undefined_flag=unsupported # Joseph Beckenbach says some releases of gcc # support --undefined. This deserves some investigation. FIXME archive_cmds='$CC -nostart $libobjs $deplibs $compiler_flags $wl-soname $wl$soname -o $lib' else ld_shlibs=no fi ;; cygwin* | mingw* | pw32* | cegcc*) # _LT_TAGVAR(hardcode_libdir_flag_spec, ) is actually meaningless, # as there is no search path for DLLs. hardcode_libdir_flag_spec='-L$libdir' export_dynamic_flag_spec='$wl--export-all-symbols' allow_undefined_flag=unsupported always_export_symbols=no enable_shared_with_static_runtimes=yes export_symbols_cmds='$NM $libobjs $convenience | $global_symbol_pipe | $SED -e '\''/^[BCDGRS][ ]/s/.*[ ]\([^ ]*\)/\1 DATA/;s/^.*[ ]__nm__\([^ ]*\)[ ][^ ]*/\1 DATA/;/^I[ ]/d;/^[AITW][ ]/s/.* //'\'' | sort | uniq > $export_symbols' exclude_expsyms='[_]+GLOBAL_OFFSET_TABLE_|[_]+GLOBAL__[FID]_.*|[_]+head_[A-Za-z0-9_]+_dll|[A-Za-z0-9_]+_dll_iname' if $LD --help 2>&1 | $GREP 'auto-import' > /dev/null; then archive_cmds='$CC -shared $libobjs $deplibs $compiler_flags -o $output_objdir/$soname $wl--enable-auto-image-base -Xlinker --out-implib -Xlinker $lib' # If the export-symbols file already is a .def file, use it as # is; otherwise, prepend EXPORTS... archive_expsym_cmds='if test DEF = "`$SED -n -e '\''s/^[ ]*//'\'' -e '\''/^\(;.*\)*$/d'\'' -e '\''s/^\(EXPORTS\|LIBRARY\)\([ ].*\)*$/DEF/p'\'' -e q $export_symbols`" ; then cp $export_symbols $output_objdir/$soname.def; else echo EXPORTS > $output_objdir/$soname.def; cat $export_symbols >> $output_objdir/$soname.def; fi~ $CC -shared $output_objdir/$soname.def $libobjs $deplibs $compiler_flags -o $output_objdir/$soname $wl--enable-auto-image-base -Xlinker --out-implib -Xlinker $lib' else ld_shlibs=no fi ;; haiku*) archive_cmds='$CC -shared $libobjs $deplibs $compiler_flags $wl-soname $wl$soname -o $lib' link_all_deplibs=yes ;; os2*) hardcode_libdir_flag_spec='-L$libdir' hardcode_minus_L=yes allow_undefined_flag=unsupported shrext_cmds=.dll archive_cmds='$ECHO "LIBRARY ${soname%$shared_ext} INITINSTANCE TERMINSTANCE" > $output_objdir/$libname.def~ $ECHO "DESCRIPTION \"$libname\"" >> $output_objdir/$libname.def~ $ECHO "DATA MULTIPLE NONSHARED" >> $output_objdir/$libname.def~ $ECHO EXPORTS >> $output_objdir/$libname.def~ emxexp $libobjs | $SED /"_DLL_InitTerm"/d >> $output_objdir/$libname.def~ $CC -Zdll -Zcrtdll -o $output_objdir/$soname $libobjs $deplibs $compiler_flags $output_objdir/$libname.def~ emximp -o $lib $output_objdir/$libname.def' archive_expsym_cmds='$ECHO "LIBRARY ${soname%$shared_ext} INITINSTANCE TERMINSTANCE" > $output_objdir/$libname.def~ $ECHO "DESCRIPTION \"$libname\"" >> $output_objdir/$libname.def~ $ECHO "DATA MULTIPLE NONSHARED" >> $output_objdir/$libname.def~ $ECHO EXPORTS >> $output_objdir/$libname.def~ prefix_cmds="$SED"~ if test EXPORTS = "`$SED 1q $export_symbols`"; then prefix_cmds="$prefix_cmds -e 1d"; fi~ prefix_cmds="$prefix_cmds -e \"s/^\(.*\)$/_\1/g\""~ cat $export_symbols | $prefix_cmds >> $output_objdir/$libname.def~ $CC -Zdll -Zcrtdll -o $output_objdir/$soname $libobjs $deplibs $compiler_flags $output_objdir/$libname.def~ emximp -o $lib $output_objdir/$libname.def' old_archive_From_new_cmds='emximp -o $output_objdir/${libname}_dll.a $output_objdir/$libname.def' enable_shared_with_static_runtimes=yes ;; interix[3-9]*) hardcode_direct=no hardcode_shlibpath_var=no hardcode_libdir_flag_spec='$wl-rpath,$libdir' export_dynamic_flag_spec='$wl-E' # Hack: On Interix 3.x, we cannot compile PIC because of a broken gcc. # Instead, shared libraries are loaded at an image base (0x10000000 by # default) and relocated if they conflict, which is a slow very memory # consuming and fragmenting process. To avoid this, we pick a random, # 256 KiB-aligned image base between 0x50000000 and 0x6FFC0000 at link # time. Moving up from 0x10000000 also allows more sbrk(2) space. archive_cmds='$CC -shared $pic_flag $libobjs $deplibs $compiler_flags $wl-h,$soname $wl--image-base,`expr ${RANDOM-$$} % 4096 / 2 \* 262144 + 1342177280` -o $lib' archive_expsym_cmds='sed "s|^|_|" $export_symbols >$output_objdir/$soname.expsym~$CC -shared $pic_flag $libobjs $deplibs $compiler_flags $wl-h,$soname $wl--retain-symbols-file,$output_objdir/$soname.expsym $wl--image-base,`expr ${RANDOM-$$} % 4096 / 2 \* 262144 + 1342177280` -o $lib' ;; gnu* | linux* | tpf* | k*bsd*-gnu | kopensolaris*-gnu) tmp_diet=no if test linux-dietlibc = "$host_os"; then case $cc_basename in diet\ *) tmp_diet=yes;; # linux-dietlibc with static linking (!diet-dyn) esac fi if $LD --help 2>&1 | $EGREP ': supported targets:.* elf' > /dev/null \ && test no = "$tmp_diet" then tmp_addflag=' $pic_flag' tmp_sharedflag='-shared' case $cc_basename,$host_cpu in pgcc*) # Portland Group C compiler whole_archive_flag_spec='$wl--whole-archive`for conv in $convenience\"\"; do test -n \"$conv\" && new_convenience=\"$new_convenience,$conv\"; done; func_echo_all \"$new_convenience\"` $wl--no-whole-archive' tmp_addflag=' $pic_flag' ;; pgf77* | pgf90* | pgf95* | pgfortran*) # Portland Group f77 and f90 compilers whole_archive_flag_spec='$wl--whole-archive`for conv in $convenience\"\"; do test -n \"$conv\" && new_convenience=\"$new_convenience,$conv\"; done; func_echo_all \"$new_convenience\"` $wl--no-whole-archive' tmp_addflag=' $pic_flag -Mnomain' ;; ecc*,ia64* | icc*,ia64*) # Intel C compiler on ia64 tmp_addflag=' -i_dynamic' ;; efc*,ia64* | ifort*,ia64*) # Intel Fortran compiler on ia64 tmp_addflag=' -i_dynamic -nofor_main' ;; ifc* | ifort*) # Intel Fortran compiler tmp_addflag=' -nofor_main' ;; lf95*) # Lahey Fortran 8.1 whole_archive_flag_spec= tmp_sharedflag='--shared' ;; nagfor*) # NAGFOR 5.3 tmp_sharedflag='-Wl,-shared' ;; xl[cC]* | bgxl[cC]* | mpixl[cC]*) # IBM XL C 8.0 on PPC (deal with xlf below) tmp_sharedflag='-qmkshrobj' tmp_addflag= ;; nvcc*) # Cuda Compiler Driver 2.2 whole_archive_flag_spec='$wl--whole-archive`for conv in $convenience\"\"; do test -n \"$conv\" && new_convenience=\"$new_convenience,$conv\"; done; func_echo_all \"$new_convenience\"` $wl--no-whole-archive' compiler_needs_object=yes ;; esac case `$CC -V 2>&1 | sed 5q` in *Sun\ C*) # Sun C 5.9 whole_archive_flag_spec='$wl--whole-archive`new_convenience=; for conv in $convenience\"\"; do test -z \"$conv\" || new_convenience=\"$new_convenience,$conv\"; done; func_echo_all \"$new_convenience\"` $wl--no-whole-archive' compiler_needs_object=yes tmp_sharedflag='-G' ;; *Sun\ F*) # Sun Fortran 8.3 tmp_sharedflag='-G' ;; esac archive_cmds='$CC '"$tmp_sharedflag""$tmp_addflag"' $libobjs $deplibs $compiler_flags $wl-soname $wl$soname -o $lib' if test yes = "$supports_anon_versioning"; then archive_expsym_cmds='echo "{ global:" > $output_objdir/$libname.ver~ cat $export_symbols | sed -e "s/\(.*\)/\1;/" >> $output_objdir/$libname.ver~ echo "local: *; };" >> $output_objdir/$libname.ver~ $CC '"$tmp_sharedflag""$tmp_addflag"' $libobjs $deplibs $compiler_flags $wl-soname $wl$soname $wl-version-script $wl$output_objdir/$libname.ver -o $lib' fi case $cc_basename in tcc*) export_dynamic_flag_spec='-rdynamic' ;; xlf* | bgf* | bgxlf* | mpixlf*) # IBM XL Fortran 10.1 on PPC cannot create shared libs itself whole_archive_flag_spec='--whole-archive$convenience --no-whole-archive' hardcode_libdir_flag_spec='$wl-rpath $wl$libdir' archive_cmds='$LD -shared $libobjs $deplibs $linker_flags -soname $soname -o $lib' if test yes = "$supports_anon_versioning"; then archive_expsym_cmds='echo "{ global:" > $output_objdir/$libname.ver~ cat $export_symbols | sed -e "s/\(.*\)/\1;/" >> $output_objdir/$libname.ver~ echo "local: *; };" >> $output_objdir/$libname.ver~ $LD -shared $libobjs $deplibs $linker_flags -soname $soname -version-script $output_objdir/$libname.ver -o $lib' fi ;; esac else ld_shlibs=no fi ;; netbsd* | netbsdelf*-gnu) if echo __ELF__ | $CC -E - | $GREP __ELF__ >/dev/null; then archive_cmds='$LD -Bshareable $libobjs $deplibs $linker_flags -o $lib' wlarc= else archive_cmds='$CC -shared $pic_flag $libobjs $deplibs $compiler_flags $wl-soname $wl$soname -o $lib' archive_expsym_cmds='$CC -shared $pic_flag $libobjs $deplibs $compiler_flags $wl-soname $wl$soname $wl-retain-symbols-file $wl$export_symbols -o $lib' fi ;; solaris*) if $LD -v 2>&1 | $GREP 'BFD 2\.8' > /dev/null; then ld_shlibs=no cat <<_LT_EOF 1>&2 *** Warning: The releases 2.8.* of the GNU linker cannot reliably *** create shared libraries on Solaris systems. Therefore, libtool *** is disabling shared libraries support. We urge you to upgrade GNU *** binutils to release 2.9.1 or newer. Another option is to modify *** your PATH or compiler configuration so that the native linker is *** used, and then restart. _LT_EOF elif $LD --help 2>&1 | $GREP ': supported targets:.* elf' > /dev/null; then archive_cmds='$CC -shared $pic_flag $libobjs $deplibs $compiler_flags $wl-soname $wl$soname -o $lib' archive_expsym_cmds='$CC -shared $pic_flag $libobjs $deplibs $compiler_flags $wl-soname $wl$soname $wl-retain-symbols-file $wl$export_symbols -o $lib' else ld_shlibs=no fi ;; sysv5* | sco3.2v5* | sco5v6* | unixware* | OpenUNIX*) case `$LD -v 2>&1` in *\ [01].* | *\ 2.[0-9].* | *\ 2.1[0-5].*) ld_shlibs=no cat <<_LT_EOF 1>&2 *** Warning: Releases of the GNU linker prior to 2.16.91.0.3 cannot *** reliably create shared libraries on SCO systems. Therefore, libtool *** is disabling shared libraries support. We urge you to upgrade GNU *** binutils to release 2.16.91.0.3 or newer. Another option is to modify *** your PATH or compiler configuration so that the native linker is *** used, and then restart. _LT_EOF ;; *) # For security reasons, it is highly recommended that you always # use absolute paths for naming shared libraries, and exclude the # DT_RUNPATH tag from executables and libraries. But doing so # requires that you compile everything twice, which is a pain. if $LD --help 2>&1 | $GREP ': supported targets:.* elf' > /dev/null; then hardcode_libdir_flag_spec='$wl-rpath $wl$libdir' archive_cmds='$CC -shared $libobjs $deplibs $compiler_flags $wl-soname $wl$soname -o $lib' archive_expsym_cmds='$CC -shared $libobjs $deplibs $compiler_flags $wl-soname $wl$soname $wl-retain-symbols-file $wl$export_symbols -o $lib' else ld_shlibs=no fi ;; esac ;; sunos4*) archive_cmds='$LD -assert pure-text -Bshareable -o $lib $libobjs $deplibs $linker_flags' wlarc= hardcode_direct=yes hardcode_shlibpath_var=no ;; *) if $LD --help 2>&1 | $GREP ': supported targets:.* elf' > /dev/null; then archive_cmds='$CC -shared $pic_flag $libobjs $deplibs $compiler_flags $wl-soname $wl$soname -o $lib' archive_expsym_cmds='$CC -shared $pic_flag $libobjs $deplibs $compiler_flags $wl-soname $wl$soname $wl-retain-symbols-file $wl$export_symbols -o $lib' else ld_shlibs=no fi ;; esac if test no = "$ld_shlibs"; then runpath_var= hardcode_libdir_flag_spec= export_dynamic_flag_spec= whole_archive_flag_spec= fi else # PORTME fill in a description of your system's linker (not GNU ld) case $host_os in aix3*) allow_undefined_flag=unsupported always_export_symbols=yes archive_expsym_cmds='$LD -o $output_objdir/$soname $libobjs $deplibs $linker_flags -bE:$export_symbols -T512 -H512 -bM:SRE~$AR $AR_FLAGS $lib $output_objdir/$soname' # Note: this linker hardcodes the directories in LIBPATH if there # are no directories specified by -L. hardcode_minus_L=yes if test yes = "$GCC" && test -z "$lt_prog_compiler_static"; then # Neither direct hardcoding nor static linking is supported with a # broken collect2. hardcode_direct=unsupported fi ;; aix[4-9]*) if test ia64 = "$host_cpu"; then # On IA64, the linker does run time linking by default, so we don't # have to do anything special. aix_use_runtimelinking=no exp_sym_flag='-Bexport' no_entry_flag= else # If we're using GNU nm, then we don't want the "-C" option. # -C means demangle to GNU nm, but means don't demangle to AIX nm. # Without the "-l" option, or with the "-B" option, AIX nm treats # weak defined symbols like other global defined symbols, whereas # GNU nm marks them as "W". # While the 'weak' keyword is ignored in the Export File, we need # it in the Import File for the 'aix-soname' feature, so we have # to replace the "-B" option with "-P" for AIX nm. if $NM -V 2>&1 | $GREP 'GNU' > /dev/null; then export_symbols_cmds='$NM -Bpg $libobjs $convenience | awk '\''{ if (((\$ 2 == "T") || (\$ 2 == "D") || (\$ 2 == "B") || (\$ 2 == "W")) && (substr(\$ 3,1,1) != ".")) { if (\$ 2 == "W") { print \$ 3 " weak" } else { print \$ 3 } } }'\'' | sort -u > $export_symbols' else export_symbols_cmds='`func_echo_all $NM | $SED -e '\''s/B\([^B]*\)$/P\1/'\''` -PCpgl $libobjs $convenience | awk '\''{ if (((\$ 2 == "T") || (\$ 2 == "D") || (\$ 2 == "B") || (\$ 2 == "W") || (\$ 2 == "V") || (\$ 2 == "Z")) && (substr(\$ 1,1,1) != ".")) { if ((\$ 2 == "W") || (\$ 2 == "V") || (\$ 2 == "Z")) { print \$ 1 " weak" } else { print \$ 1 } } }'\'' | sort -u > $export_symbols' fi aix_use_runtimelinking=no # Test if we are trying to use run time linking or normal # AIX style linking. If -brtl is somewhere in LDFLAGS, we # have runtime linking enabled, and use it for executables. # For shared libraries, we enable/disable runtime linking # depending on the kind of the shared library created - # when "with_aix_soname,aix_use_runtimelinking" is: # "aix,no" lib.a(lib.so.V) shared, rtl:no, for executables # "aix,yes" lib.so shared, rtl:yes, for executables # lib.a static archive # "both,no" lib.so.V(shr.o) shared, rtl:yes # lib.a(lib.so.V) shared, rtl:no, for executables # "both,yes" lib.so.V(shr.o) shared, rtl:yes, for executables # lib.a(lib.so.V) shared, rtl:no # "svr4,*" lib.so.V(shr.o) shared, rtl:yes, for executables # lib.a static archive case $host_os in aix4.[23]|aix4.[23].*|aix[5-9]*) for ld_flag in $LDFLAGS; do if (test x-brtl = "x$ld_flag" || test x-Wl,-brtl = "x$ld_flag"); then aix_use_runtimelinking=yes break fi done if test svr4,no = "$with_aix_soname,$aix_use_runtimelinking"; then # With aix-soname=svr4, we create the lib.so.V shared archives only, # so we don't have lib.a shared libs to link our executables. # We have to force runtime linking in this case. aix_use_runtimelinking=yes LDFLAGS="$LDFLAGS -Wl,-brtl" fi ;; esac exp_sym_flag='-bexport' no_entry_flag='-bnoentry' fi # When large executables or shared objects are built, AIX ld can # have problems creating the table of contents. If linking a library # or program results in "error TOC overflow" add -mminimal-toc to # CXXFLAGS/CFLAGS for g++/gcc. In the cases where that is not # enough to fix the problem, add -Wl,-bbigtoc to LDFLAGS. archive_cmds='' hardcode_direct=yes hardcode_direct_absolute=yes hardcode_libdir_separator=':' link_all_deplibs=yes file_list_spec='$wl-f,' case $with_aix_soname,$aix_use_runtimelinking in aix,*) ;; # traditional, no import file svr4,* | *,yes) # use import file # The Import File defines what to hardcode. hardcode_direct=no hardcode_direct_absolute=no ;; esac if test yes = "$GCC"; then case $host_os in aix4.[012]|aix4.[012].*) # We only want to do this on AIX 4.2 and lower, the check # below for broken collect2 doesn't work under 4.3+ collect2name=`$CC -print-prog-name=collect2` if test -f "$collect2name" && strings "$collect2name" | $GREP resolve_lib_name >/dev/null then # We have reworked collect2 : else # We have old collect2 hardcode_direct=unsupported # It fails to find uninstalled libraries when the uninstalled # path is not listed in the libpath. Setting hardcode_minus_L # to unsupported forces relinking hardcode_minus_L=yes hardcode_libdir_flag_spec='-L$libdir' hardcode_libdir_separator= fi ;; esac shared_flag='-shared' if test yes = "$aix_use_runtimelinking"; then shared_flag="$shared_flag "'$wl-G' fi # Need to ensure runtime linking is disabled for the traditional # shared library, or the linker may eventually find shared libraries # /with/ Import File - we do not want to mix them. shared_flag_aix='-shared' shared_flag_svr4='-shared $wl-G' else # not using gcc if test ia64 = "$host_cpu"; then # VisualAge C++, Version 5.5 for AIX 5L for IA-64, Beta 3 Release # chokes on -Wl,-G. The following line is correct: shared_flag='-G' else if test yes = "$aix_use_runtimelinking"; then shared_flag='$wl-G' else shared_flag='$wl-bM:SRE' fi shared_flag_aix='$wl-bM:SRE' shared_flag_svr4='$wl-G' fi fi export_dynamic_flag_spec='$wl-bexpall' # It seems that -bexpall does not export symbols beginning with # underscore (_), so it is better to generate a list of symbols to export. always_export_symbols=yes if test aix,yes = "$with_aix_soname,$aix_use_runtimelinking"; then # Warning - without using the other runtime loading flags (-brtl), # -berok will link without error, but may produce a broken library. allow_undefined_flag='-berok' # Determine the default libpath from the value encoded in an # empty executable. if test set = "${lt_cv_aix_libpath+set}"; then aix_libpath=$lt_cv_aix_libpath else if ${lt_cv_aix_libpath_+:} false; then : $as_echo_n "(cached) " >&6 else cat confdefs.h - <<_ACEOF >conftest.$ac_ext /* end confdefs.h. */ int main () { ; return 0; } _ACEOF if ac_fn_c_try_link "$LINENO"; then : lt_aix_libpath_sed=' /Import File Strings/,/^$/ { /^0/ { s/^0 *\([^ ]*\) *$/\1/ p } }' lt_cv_aix_libpath_=`dump -H conftest$ac_exeext 2>/dev/null | $SED -n -e "$lt_aix_libpath_sed"` # Check for a 64-bit object if we didn't find anything. if test -z "$lt_cv_aix_libpath_"; then lt_cv_aix_libpath_=`dump -HX64 conftest$ac_exeext 2>/dev/null | $SED -n -e "$lt_aix_libpath_sed"` fi fi rm -f core conftest.err conftest.$ac_objext \ conftest$ac_exeext conftest.$ac_ext if test -z "$lt_cv_aix_libpath_"; then lt_cv_aix_libpath_=/usr/lib:/lib fi fi aix_libpath=$lt_cv_aix_libpath_ fi hardcode_libdir_flag_spec='$wl-blibpath:$libdir:'"$aix_libpath" archive_expsym_cmds='$CC -o $output_objdir/$soname $libobjs $deplibs $wl'$no_entry_flag' $compiler_flags `if test -n "$allow_undefined_flag"; then func_echo_all "$wl$allow_undefined_flag"; else :; fi` $wl'$exp_sym_flag:\$export_symbols' '$shared_flag else if test ia64 = "$host_cpu"; then hardcode_libdir_flag_spec='$wl-R $libdir:/usr/lib:/lib' allow_undefined_flag="-z nodefs" archive_expsym_cmds="\$CC $shared_flag"' -o $output_objdir/$soname $libobjs $deplibs '"\$wl$no_entry_flag"' $compiler_flags $wl$allow_undefined_flag '"\$wl$exp_sym_flag:\$export_symbols" else # Determine the default libpath from the value encoded in an # empty executable. if test set = "${lt_cv_aix_libpath+set}"; then aix_libpath=$lt_cv_aix_libpath else if ${lt_cv_aix_libpath_+:} false; then : $as_echo_n "(cached) " >&6 else cat confdefs.h - <<_ACEOF >conftest.$ac_ext /* end confdefs.h. */ int main () { ; return 0; } _ACEOF if ac_fn_c_try_link "$LINENO"; then : lt_aix_libpath_sed=' /Import File Strings/,/^$/ { /^0/ { s/^0 *\([^ ]*\) *$/\1/ p } }' lt_cv_aix_libpath_=`dump -H conftest$ac_exeext 2>/dev/null | $SED -n -e "$lt_aix_libpath_sed"` # Check for a 64-bit object if we didn't find anything. if test -z "$lt_cv_aix_libpath_"; then lt_cv_aix_libpath_=`dump -HX64 conftest$ac_exeext 2>/dev/null | $SED -n -e "$lt_aix_libpath_sed"` fi fi rm -f core conftest.err conftest.$ac_objext \ conftest$ac_exeext conftest.$ac_ext if test -z "$lt_cv_aix_libpath_"; then lt_cv_aix_libpath_=/usr/lib:/lib fi fi aix_libpath=$lt_cv_aix_libpath_ fi hardcode_libdir_flag_spec='$wl-blibpath:$libdir:'"$aix_libpath" # Warning - without using the other run time loading flags, # -berok will link without error, but may produce a broken library. no_undefined_flag=' $wl-bernotok' allow_undefined_flag=' $wl-berok' if test yes = "$with_gnu_ld"; then # We only use this code for GNU lds that support --whole-archive. whole_archive_flag_spec='$wl--whole-archive$convenience $wl--no-whole-archive' else # Exported symbols can be pulled into shared objects from archives whole_archive_flag_spec='$convenience' fi archive_cmds_need_lc=yes archive_expsym_cmds='$RM -r $output_objdir/$realname.d~$MKDIR $output_objdir/$realname.d' # -brtl affects multiple linker settings, -berok does not and is overridden later compiler_flags_filtered='`func_echo_all "$compiler_flags " | $SED -e "s%-brtl\\([, ]\\)%-berok\\1%g"`' if test svr4 != "$with_aix_soname"; then # This is similar to how AIX traditionally builds its shared libraries. archive_expsym_cmds="$archive_expsym_cmds"'~$CC '$shared_flag_aix' -o $output_objdir/$realname.d/$soname $libobjs $deplibs $wl-bnoentry '$compiler_flags_filtered'$wl-bE:$export_symbols$allow_undefined_flag~$AR $AR_FLAGS $output_objdir/$libname$release.a $output_objdir/$realname.d/$soname' fi if test aix != "$with_aix_soname"; then archive_expsym_cmds="$archive_expsym_cmds"'~$CC '$shared_flag_svr4' -o $output_objdir/$realname.d/$shared_archive_member_spec.o $libobjs $deplibs $wl-bnoentry '$compiler_flags_filtered'$wl-bE:$export_symbols$allow_undefined_flag~$STRIP -e $output_objdir/$realname.d/$shared_archive_member_spec.o~( func_echo_all "#! $soname($shared_archive_member_spec.o)"; if test shr_64 = "$shared_archive_member_spec"; then func_echo_all "# 64"; else func_echo_all "# 32"; fi; cat $export_symbols ) > $output_objdir/$realname.d/$shared_archive_member_spec.imp~$AR $AR_FLAGS $output_objdir/$soname $output_objdir/$realname.d/$shared_archive_member_spec.o $output_objdir/$realname.d/$shared_archive_member_spec.imp' else # used by -dlpreopen to get the symbols archive_expsym_cmds="$archive_expsym_cmds"'~$MV $output_objdir/$realname.d/$soname $output_objdir' fi archive_expsym_cmds="$archive_expsym_cmds"'~$RM -r $output_objdir/$realname.d' fi fi ;; amigaos*) case $host_cpu in powerpc) # see comment about AmigaOS4 .so support archive_cmds='$CC -shared $libobjs $deplibs $compiler_flags $wl-soname $wl$soname -o $lib' archive_expsym_cmds='' ;; m68k) archive_cmds='$RM $output_objdir/a2ixlibrary.data~$ECHO "#define NAME $libname" > $output_objdir/a2ixlibrary.data~$ECHO "#define LIBRARY_ID 1" >> $output_objdir/a2ixlibrary.data~$ECHO "#define VERSION $major" >> $output_objdir/a2ixlibrary.data~$ECHO "#define REVISION $revision" >> $output_objdir/a2ixlibrary.data~$AR $AR_FLAGS $lib $libobjs~$RANLIB $lib~(cd $output_objdir && a2ixlibrary -32)' hardcode_libdir_flag_spec='-L$libdir' hardcode_minus_L=yes ;; esac ;; bsdi[45]*) export_dynamic_flag_spec=-rdynamic ;; cygwin* | mingw* | pw32* | cegcc*) # When not using gcc, we currently assume that we are using # Microsoft Visual C++. # hardcode_libdir_flag_spec is actually meaningless, as there is # no search path for DLLs. case $cc_basename in cl*) # Native MSVC hardcode_libdir_flag_spec=' ' allow_undefined_flag=unsupported always_export_symbols=yes file_list_spec='@' # Tell ltmain to make .lib files, not .a files. libext=lib # Tell ltmain to make .dll files, not .so files. shrext_cmds=.dll # FIXME: Setting linknames here is a bad hack. archive_cmds='$CC -o $output_objdir/$soname $libobjs $compiler_flags $deplibs -Wl,-DLL,-IMPLIB:"$tool_output_objdir$libname.dll.lib"~linknames=' archive_expsym_cmds='if test DEF = "`$SED -n -e '\''s/^[ ]*//'\'' -e '\''/^\(;.*\)*$/d'\'' -e '\''s/^\(EXPORTS\|LIBRARY\)\([ ].*\)*$/DEF/p'\'' -e q $export_symbols`" ; then cp "$export_symbols" "$output_objdir/$soname.def"; echo "$tool_output_objdir$soname.def" > "$output_objdir/$soname.exp"; else $SED -e '\''s/^/-link -EXPORT:/'\'' < $export_symbols > $output_objdir/$soname.exp; fi~ $CC -o $tool_output_objdir$soname $libobjs $compiler_flags $deplibs "@$tool_output_objdir$soname.exp" -Wl,-DLL,-IMPLIB:"$tool_output_objdir$libname.dll.lib"~ linknames=' # The linker will not automatically build a static lib if we build a DLL. # _LT_TAGVAR(old_archive_from_new_cmds, )='true' enable_shared_with_static_runtimes=yes exclude_expsyms='_NULL_IMPORT_DESCRIPTOR|_IMPORT_DESCRIPTOR_.*' export_symbols_cmds='$NM $libobjs $convenience | $global_symbol_pipe | $SED -e '\''/^[BCDGRS][ ]/s/.*[ ]\([^ ]*\)/\1,DATA/'\'' | $SED -e '\''/^[AITW][ ]/s/.*[ ]//'\'' | sort | uniq > $export_symbols' # Don't use ranlib old_postinstall_cmds='chmod 644 $oldlib' postlink_cmds='lt_outputfile="@OUTPUT@"~ lt_tool_outputfile="@TOOL_OUTPUT@"~ case $lt_outputfile in *.exe|*.EXE) ;; *) lt_outputfile=$lt_outputfile.exe lt_tool_outputfile=$lt_tool_outputfile.exe ;; esac~ if test : != "$MANIFEST_TOOL" && test -f "$lt_outputfile.manifest"; then $MANIFEST_TOOL -manifest "$lt_tool_outputfile.manifest" -outputresource:"$lt_tool_outputfile" || exit 1; $RM "$lt_outputfile.manifest"; fi' ;; *) # Assume MSVC wrapper hardcode_libdir_flag_spec=' ' allow_undefined_flag=unsupported # Tell ltmain to make .lib files, not .a files. libext=lib # Tell ltmain to make .dll files, not .so files. shrext_cmds=.dll # FIXME: Setting linknames here is a bad hack. archive_cmds='$CC -o $lib $libobjs $compiler_flags `func_echo_all "$deplibs" | $SED '\''s/ -lc$//'\''` -link -dll~linknames=' # The linker will automatically build a .lib file if we build a DLL. old_archive_from_new_cmds='true' # FIXME: Should let the user specify the lib program. old_archive_cmds='lib -OUT:$oldlib$oldobjs$old_deplibs' enable_shared_with_static_runtimes=yes ;; esac ;; darwin* | rhapsody*) archive_cmds_need_lc=no hardcode_direct=no hardcode_automatic=yes hardcode_shlibpath_var=unsupported if test yes = "$lt_cv_ld_force_load"; then whole_archive_flag_spec='`for conv in $convenience\"\"; do test -n \"$conv\" && new_convenience=\"$new_convenience $wl-force_load,$conv\"; done; func_echo_all \"$new_convenience\"`' else whole_archive_flag_spec='' fi link_all_deplibs=yes allow_undefined_flag=$_lt_dar_allow_undefined case $cc_basename in ifort*|nagfor*) _lt_dar_can_shared=yes ;; *) _lt_dar_can_shared=$GCC ;; esac if test yes = "$_lt_dar_can_shared"; then output_verbose_link_cmd=func_echo_all archive_cmds="\$CC -dynamiclib \$allow_undefined_flag -o \$lib \$libobjs \$deplibs \$compiler_flags -install_name \$rpath/\$soname \$verstring $_lt_dar_single_mod$_lt_dsymutil" module_cmds="\$CC \$allow_undefined_flag -o \$lib -bundle \$libobjs \$deplibs \$compiler_flags$_lt_dsymutil" archive_expsym_cmds="sed 's|^|_|' < \$export_symbols > \$output_objdir/\$libname-symbols.expsym~\$CC -dynamiclib \$allow_undefined_flag -o \$lib \$libobjs \$deplibs \$compiler_flags -install_name \$rpath/\$soname \$verstring $_lt_dar_single_mod$_lt_dar_export_syms$_lt_dsymutil" module_expsym_cmds="sed -e 's|^|_|' < \$export_symbols > \$output_objdir/\$libname-symbols.expsym~\$CC \$allow_undefined_flag -o \$lib -bundle \$libobjs \$deplibs \$compiler_flags$_lt_dar_export_syms$_lt_dsymutil" else ld_shlibs=no fi ;; dgux*) archive_cmds='$LD -G -h $soname -o $lib $libobjs $deplibs $linker_flags' hardcode_libdir_flag_spec='-L$libdir' hardcode_shlibpath_var=no ;; # FreeBSD 2.2.[012] allows us to include c++rt0.o to get C++ constructor # support. Future versions do this automatically, but an explicit c++rt0.o # does not break anything, and helps significantly (at the cost of a little # extra space). freebsd2.2*) archive_cmds='$LD -Bshareable -o $lib $libobjs $deplibs $linker_flags /usr/lib/c++rt0.o' hardcode_libdir_flag_spec='-R$libdir' hardcode_direct=yes hardcode_shlibpath_var=no ;; # Unfortunately, older versions of FreeBSD 2 do not have this feature. freebsd2.*) archive_cmds='$LD -Bshareable -o $lib $libobjs $deplibs $linker_flags' hardcode_direct=yes hardcode_minus_L=yes hardcode_shlibpath_var=no ;; # FreeBSD 3 and greater uses gcc -shared to do shared libraries. freebsd* | dragonfly*) archive_cmds='$CC -shared $pic_flag -o $lib $libobjs $deplibs $compiler_flags' hardcode_libdir_flag_spec='-R$libdir' hardcode_direct=yes hardcode_shlibpath_var=no ;; hpux9*) if test yes = "$GCC"; then archive_cmds='$RM $output_objdir/$soname~$CC -shared $pic_flag $wl+b $wl$install_libdir -o $output_objdir/$soname $libobjs $deplibs $compiler_flags~test "x$output_objdir/$soname" = "x$lib" || mv $output_objdir/$soname $lib' else archive_cmds='$RM $output_objdir/$soname~$LD -b +b $install_libdir -o $output_objdir/$soname $libobjs $deplibs $linker_flags~test "x$output_objdir/$soname" = "x$lib" || mv $output_objdir/$soname $lib' fi hardcode_libdir_flag_spec='$wl+b $wl$libdir' hardcode_libdir_separator=: hardcode_direct=yes # hardcode_minus_L: Not really in the search PATH, # but as the default location of the library. hardcode_minus_L=yes export_dynamic_flag_spec='$wl-E' ;; hpux10*) if test yes,no = "$GCC,$with_gnu_ld"; then archive_cmds='$CC -shared $pic_flag $wl+h $wl$soname $wl+b $wl$install_libdir -o $lib $libobjs $deplibs $compiler_flags' else archive_cmds='$LD -b +h $soname +b $install_libdir -o $lib $libobjs $deplibs $linker_flags' fi if test no = "$with_gnu_ld"; then hardcode_libdir_flag_spec='$wl+b $wl$libdir' hardcode_libdir_separator=: hardcode_direct=yes hardcode_direct_absolute=yes export_dynamic_flag_spec='$wl-E' # hardcode_minus_L: Not really in the search PATH, # but as the default location of the library. hardcode_minus_L=yes fi ;; hpux11*) if test yes,no = "$GCC,$with_gnu_ld"; then case $host_cpu in hppa*64*) archive_cmds='$CC -shared $wl+h $wl$soname -o $lib $libobjs $deplibs $compiler_flags' ;; ia64*) archive_cmds='$CC -shared $pic_flag $wl+h $wl$soname $wl+nodefaultrpath -o $lib $libobjs $deplibs $compiler_flags' ;; *) archive_cmds='$CC -shared $pic_flag $wl+h $wl$soname $wl+b $wl$install_libdir -o $lib $libobjs $deplibs $compiler_flags' ;; esac else case $host_cpu in hppa*64*) archive_cmds='$CC -b $wl+h $wl$soname -o $lib $libobjs $deplibs $compiler_flags' ;; ia64*) archive_cmds='$CC -b $wl+h $wl$soname $wl+nodefaultrpath -o $lib $libobjs $deplibs $compiler_flags' ;; *) # Older versions of the 11.00 compiler do not understand -b yet # (HP92453-01 A.11.01.20 doesn't, HP92453-01 B.11.X.35175-35176.GP does) { $as_echo "$as_me:${as_lineno-$LINENO}: checking if $CC understands -b" >&5 $as_echo_n "checking if $CC understands -b... " >&6; } if ${lt_cv_prog_compiler__b+:} false; then : $as_echo_n "(cached) " >&6 else lt_cv_prog_compiler__b=no save_LDFLAGS=$LDFLAGS LDFLAGS="$LDFLAGS -b" echo "$lt_simple_link_test_code" > conftest.$ac_ext if (eval $ac_link 2>conftest.err) && test -s conftest$ac_exeext; then # The linker can only warn and ignore the option if not recognized # So say no if there are warnings if test -s conftest.err; then # Append any errors to the config.log. cat conftest.err 1>&5 $ECHO "$_lt_linker_boilerplate" | $SED '/^$/d' > conftest.exp $SED '/^$/d; /^ *+/d' conftest.err >conftest.er2 if diff conftest.exp conftest.er2 >/dev/null; then lt_cv_prog_compiler__b=yes fi else lt_cv_prog_compiler__b=yes fi fi $RM -r conftest* LDFLAGS=$save_LDFLAGS fi { $as_echo "$as_me:${as_lineno-$LINENO}: result: $lt_cv_prog_compiler__b" >&5 $as_echo "$lt_cv_prog_compiler__b" >&6; } if test yes = "$lt_cv_prog_compiler__b"; then archive_cmds='$CC -b $wl+h $wl$soname $wl+b $wl$install_libdir -o $lib $libobjs $deplibs $compiler_flags' else archive_cmds='$LD -b +h $soname +b $install_libdir -o $lib $libobjs $deplibs $linker_flags' fi ;; esac fi if test no = "$with_gnu_ld"; then hardcode_libdir_flag_spec='$wl+b $wl$libdir' hardcode_libdir_separator=: case $host_cpu in hppa*64*|ia64*) hardcode_direct=no hardcode_shlibpath_var=no ;; *) hardcode_direct=yes hardcode_direct_absolute=yes export_dynamic_flag_spec='$wl-E' # hardcode_minus_L: Not really in the search PATH, # but as the default location of the library. hardcode_minus_L=yes ;; esac fi ;; irix5* | irix6* | nonstopux*) if test yes = "$GCC"; then archive_cmds='$CC -shared $pic_flag $libobjs $deplibs $compiler_flags $wl-soname $wl$soname `test -n "$verstring" && func_echo_all "$wl-set_version $wl$verstring"` $wl-update_registry $wl$output_objdir/so_locations -o $lib' # Try to use the -exported_symbol ld option, if it does not # work, assume that -exports_file does not work either and # implicitly export all symbols. # This should be the same for all languages, so no per-tag cache variable. { $as_echo "$as_me:${as_lineno-$LINENO}: checking whether the $host_os linker accepts -exported_symbol" >&5 $as_echo_n "checking whether the $host_os linker accepts -exported_symbol... " >&6; } if ${lt_cv_irix_exported_symbol+:} false; then : $as_echo_n "(cached) " >&6 else save_LDFLAGS=$LDFLAGS LDFLAGS="$LDFLAGS -shared $wl-exported_symbol ${wl}foo $wl-update_registry $wl/dev/null" cat confdefs.h - <<_ACEOF >conftest.$ac_ext /* end confdefs.h. */ int foo (void) { return 0; } _ACEOF if ac_fn_c_try_link "$LINENO"; then : lt_cv_irix_exported_symbol=yes else lt_cv_irix_exported_symbol=no fi rm -f core conftest.err conftest.$ac_objext \ conftest$ac_exeext conftest.$ac_ext LDFLAGS=$save_LDFLAGS fi { $as_echo "$as_me:${as_lineno-$LINENO}: result: $lt_cv_irix_exported_symbol" >&5 $as_echo "$lt_cv_irix_exported_symbol" >&6; } if test yes = "$lt_cv_irix_exported_symbol"; then archive_expsym_cmds='$CC -shared $pic_flag $libobjs $deplibs $compiler_flags $wl-soname $wl$soname `test -n "$verstring" && func_echo_all "$wl-set_version $wl$verstring"` $wl-update_registry $wl$output_objdir/so_locations $wl-exports_file $wl$export_symbols -o $lib' fi link_all_deplibs=no else archive_cmds='$CC -shared $libobjs $deplibs $compiler_flags -soname $soname `test -n "$verstring" && func_echo_all "-set_version $verstring"` -update_registry $output_objdir/so_locations -o $lib' archive_expsym_cmds='$CC -shared $libobjs $deplibs $compiler_flags -soname $soname `test -n "$verstring" && func_echo_all "-set_version $verstring"` -update_registry $output_objdir/so_locations -exports_file $export_symbols -o $lib' fi archive_cmds_need_lc='no' hardcode_libdir_flag_spec='$wl-rpath $wl$libdir' hardcode_libdir_separator=: inherit_rpath=yes link_all_deplibs=yes ;; linux*) case $cc_basename in tcc*) # Fabrice Bellard et al's Tiny C Compiler ld_shlibs=yes archive_cmds='$CC -shared $pic_flag -o $lib $libobjs $deplibs $compiler_flags' ;; esac ;; netbsd* | netbsdelf*-gnu) if echo __ELF__ | $CC -E - | $GREP __ELF__ >/dev/null; then archive_cmds='$LD -Bshareable -o $lib $libobjs $deplibs $linker_flags' # a.out else archive_cmds='$LD -shared -o $lib $libobjs $deplibs $linker_flags' # ELF fi hardcode_libdir_flag_spec='-R$libdir' hardcode_direct=yes hardcode_shlibpath_var=no ;; newsos6) archive_cmds='$LD -G -h $soname -o $lib $libobjs $deplibs $linker_flags' hardcode_direct=yes hardcode_libdir_flag_spec='$wl-rpath $wl$libdir' hardcode_libdir_separator=: hardcode_shlibpath_var=no ;; *nto* | *qnx*) ;; openbsd* | bitrig*) if test -f /usr/libexec/ld.so; then hardcode_direct=yes hardcode_shlibpath_var=no hardcode_direct_absolute=yes if test -z "`echo __ELF__ | $CC -E - | $GREP __ELF__`"; then archive_cmds='$CC -shared $pic_flag -o $lib $libobjs $deplibs $compiler_flags' archive_expsym_cmds='$CC -shared $pic_flag -o $lib $libobjs $deplibs $compiler_flags $wl-retain-symbols-file,$export_symbols' hardcode_libdir_flag_spec='$wl-rpath,$libdir' export_dynamic_flag_spec='$wl-E' else archive_cmds='$CC -shared $pic_flag -o $lib $libobjs $deplibs $compiler_flags' hardcode_libdir_flag_spec='$wl-rpath,$libdir' fi else ld_shlibs=no fi ;; os2*) hardcode_libdir_flag_spec='-L$libdir' hardcode_minus_L=yes allow_undefined_flag=unsupported shrext_cmds=.dll archive_cmds='$ECHO "LIBRARY ${soname%$shared_ext} INITINSTANCE TERMINSTANCE" > $output_objdir/$libname.def~ $ECHO "DESCRIPTION \"$libname\"" >> $output_objdir/$libname.def~ $ECHO "DATA MULTIPLE NONSHARED" >> $output_objdir/$libname.def~ $ECHO EXPORTS >> $output_objdir/$libname.def~ emxexp $libobjs | $SED /"_DLL_InitTerm"/d >> $output_objdir/$libname.def~ $CC -Zdll -Zcrtdll -o $output_objdir/$soname $libobjs $deplibs $compiler_flags $output_objdir/$libname.def~ emximp -o $lib $output_objdir/$libname.def' archive_expsym_cmds='$ECHO "LIBRARY ${soname%$shared_ext} INITINSTANCE TERMINSTANCE" > $output_objdir/$libname.def~ $ECHO "DESCRIPTION \"$libname\"" >> $output_objdir/$libname.def~ $ECHO "DATA MULTIPLE NONSHARED" >> $output_objdir/$libname.def~ $ECHO EXPORTS >> $output_objdir/$libname.def~ prefix_cmds="$SED"~ if test EXPORTS = "`$SED 1q $export_symbols`"; then prefix_cmds="$prefix_cmds -e 1d"; fi~ prefix_cmds="$prefix_cmds -e \"s/^\(.*\)$/_\1/g\""~ cat $export_symbols | $prefix_cmds >> $output_objdir/$libname.def~ $CC -Zdll -Zcrtdll -o $output_objdir/$soname $libobjs $deplibs $compiler_flags $output_objdir/$libname.def~ emximp -o $lib $output_objdir/$libname.def' old_archive_From_new_cmds='emximp -o $output_objdir/${libname}_dll.a $output_objdir/$libname.def' enable_shared_with_static_runtimes=yes ;; osf3*) if test yes = "$GCC"; then allow_undefined_flag=' $wl-expect_unresolved $wl\*' archive_cmds='$CC -shared$allow_undefined_flag $libobjs $deplibs $compiler_flags $wl-soname $wl$soname `test -n "$verstring" && func_echo_all "$wl-set_version $wl$verstring"` $wl-update_registry $wl$output_objdir/so_locations -o $lib' else allow_undefined_flag=' -expect_unresolved \*' archive_cmds='$CC -shared$allow_undefined_flag $libobjs $deplibs $compiler_flags -soname $soname `test -n "$verstring" && func_echo_all "-set_version $verstring"` -update_registry $output_objdir/so_locations -o $lib' fi archive_cmds_need_lc='no' hardcode_libdir_flag_spec='$wl-rpath $wl$libdir' hardcode_libdir_separator=: ;; osf4* | osf5*) # as osf3* with the addition of -msym flag if test yes = "$GCC"; then allow_undefined_flag=' $wl-expect_unresolved $wl\*' archive_cmds='$CC -shared$allow_undefined_flag $pic_flag $libobjs $deplibs $compiler_flags $wl-msym $wl-soname $wl$soname `test -n "$verstring" && func_echo_all "$wl-set_version $wl$verstring"` $wl-update_registry $wl$output_objdir/so_locations -o $lib' hardcode_libdir_flag_spec='$wl-rpath $wl$libdir' else allow_undefined_flag=' -expect_unresolved \*' archive_cmds='$CC -shared$allow_undefined_flag $libobjs $deplibs $compiler_flags -msym -soname $soname `test -n "$verstring" && func_echo_all "-set_version $verstring"` -update_registry $output_objdir/so_locations -o $lib' archive_expsym_cmds='for i in `cat $export_symbols`; do printf "%s %s\\n" -exported_symbol "\$i" >> $lib.exp; done; printf "%s\\n" "-hidden">> $lib.exp~ $CC -shared$allow_undefined_flag $wl-input $wl$lib.exp $compiler_flags $libobjs $deplibs -soname $soname `test -n "$verstring" && $ECHO "-set_version $verstring"` -update_registry $output_objdir/so_locations -o $lib~$RM $lib.exp' # Both c and cxx compiler support -rpath directly hardcode_libdir_flag_spec='-rpath $libdir' fi archive_cmds_need_lc='no' hardcode_libdir_separator=: ;; solaris*) no_undefined_flag=' -z defs' if test yes = "$GCC"; then wlarc='$wl' archive_cmds='$CC -shared $pic_flag $wl-z ${wl}text $wl-h $wl$soname -o $lib $libobjs $deplibs $compiler_flags' archive_expsym_cmds='echo "{ global:" > $lib.exp~cat $export_symbols | $SED -e "s/\(.*\)/\1;/" >> $lib.exp~echo "local: *; };" >> $lib.exp~ $CC -shared $pic_flag $wl-z ${wl}text $wl-M $wl$lib.exp $wl-h $wl$soname -o $lib $libobjs $deplibs $compiler_flags~$RM $lib.exp' else case `$CC -V 2>&1` in *"Compilers 5.0"*) wlarc='' archive_cmds='$LD -G$allow_undefined_flag -h $soname -o $lib $libobjs $deplibs $linker_flags' archive_expsym_cmds='echo "{ global:" > $lib.exp~cat $export_symbols | $SED -e "s/\(.*\)/\1;/" >> $lib.exp~echo "local: *; };" >> $lib.exp~ $LD -G$allow_undefined_flag -M $lib.exp -h $soname -o $lib $libobjs $deplibs $linker_flags~$RM $lib.exp' ;; *) wlarc='$wl' archive_cmds='$CC -G$allow_undefined_flag -h $soname -o $lib $libobjs $deplibs $compiler_flags' archive_expsym_cmds='echo "{ global:" > $lib.exp~cat $export_symbols | $SED -e "s/\(.*\)/\1;/" >> $lib.exp~echo "local: *; };" >> $lib.exp~ $CC -G$allow_undefined_flag -M $lib.exp -h $soname -o $lib $libobjs $deplibs $compiler_flags~$RM $lib.exp' ;; esac fi hardcode_libdir_flag_spec='-R$libdir' hardcode_shlibpath_var=no case $host_os in solaris2.[0-5] | solaris2.[0-5].*) ;; *) # The compiler driver will combine and reorder linker options, # but understands '-z linker_flag'. GCC discards it without '$wl', # but is careful enough not to reorder. # Supported since Solaris 2.6 (maybe 2.5.1?) if test yes = "$GCC"; then whole_archive_flag_spec='$wl-z ${wl}allextract$convenience $wl-z ${wl}defaultextract' else whole_archive_flag_spec='-z allextract$convenience -z defaultextract' fi ;; esac link_all_deplibs=yes ;; sunos4*) if test sequent = "$host_vendor"; then # Use $CC to link under sequent, because it throws in some extra .o # files that make .init and .fini sections work. archive_cmds='$CC -G $wl-h $soname -o $lib $libobjs $deplibs $compiler_flags' else archive_cmds='$LD -assert pure-text -Bstatic -o $lib $libobjs $deplibs $linker_flags' fi hardcode_libdir_flag_spec='-L$libdir' hardcode_direct=yes hardcode_minus_L=yes hardcode_shlibpath_var=no ;; sysv4) case $host_vendor in sni) archive_cmds='$LD -G -h $soname -o $lib $libobjs $deplibs $linker_flags' hardcode_direct=yes # is this really true??? ;; siemens) ## LD is ld it makes a PLAMLIB ## CC just makes a GrossModule. archive_cmds='$LD -G -o $lib $libobjs $deplibs $linker_flags' reload_cmds='$CC -r -o $output$reload_objs' hardcode_direct=no ;; motorola) archive_cmds='$LD -G -h $soname -o $lib $libobjs $deplibs $linker_flags' hardcode_direct=no #Motorola manual says yes, but my tests say they lie ;; esac runpath_var='LD_RUN_PATH' hardcode_shlibpath_var=no ;; sysv4.3*) archive_cmds='$LD -G -h $soname -o $lib $libobjs $deplibs $linker_flags' hardcode_shlibpath_var=no export_dynamic_flag_spec='-Bexport' ;; sysv4*MP*) if test -d /usr/nec; then archive_cmds='$LD -G -h $soname -o $lib $libobjs $deplibs $linker_flags' hardcode_shlibpath_var=no runpath_var=LD_RUN_PATH hardcode_runpath_var=yes ld_shlibs=yes fi ;; sysv4*uw2* | sysv5OpenUNIX* | sysv5UnixWare7.[01].[10]* | unixware7* | sco3.2v5.0.[024]*) no_undefined_flag='$wl-z,text' archive_cmds_need_lc=no hardcode_shlibpath_var=no runpath_var='LD_RUN_PATH' if test yes = "$GCC"; then archive_cmds='$CC -shared $wl-h,$soname -o $lib $libobjs $deplibs $compiler_flags' archive_expsym_cmds='$CC -shared $wl-Bexport:$export_symbols $wl-h,$soname -o $lib $libobjs $deplibs $compiler_flags' else archive_cmds='$CC -G $wl-h,$soname -o $lib $libobjs $deplibs $compiler_flags' archive_expsym_cmds='$CC -G $wl-Bexport:$export_symbols $wl-h,$soname -o $lib $libobjs $deplibs $compiler_flags' fi ;; sysv5* | sco3.2v5* | sco5v6*) # Note: We CANNOT use -z defs as we might desire, because we do not # link with -lc, and that would cause any symbols used from libc to # always be unresolved, which means just about no library would # ever link correctly. If we're not using GNU ld we use -z text # though, which does catch some bad symbols but isn't as heavy-handed # as -z defs. no_undefined_flag='$wl-z,text' allow_undefined_flag='$wl-z,nodefs' archive_cmds_need_lc=no hardcode_shlibpath_var=no hardcode_libdir_flag_spec='$wl-R,$libdir' hardcode_libdir_separator=':' link_all_deplibs=yes export_dynamic_flag_spec='$wl-Bexport' runpath_var='LD_RUN_PATH' if test yes = "$GCC"; then archive_cmds='$CC -shared $wl-h,$soname -o $lib $libobjs $deplibs $compiler_flags' archive_expsym_cmds='$CC -shared $wl-Bexport:$export_symbols $wl-h,$soname -o $lib $libobjs $deplibs $compiler_flags' else archive_cmds='$CC -G $wl-h,$soname -o $lib $libobjs $deplibs $compiler_flags' archive_expsym_cmds='$CC -G $wl-Bexport:$export_symbols $wl-h,$soname -o $lib $libobjs $deplibs $compiler_flags' fi ;; uts4*) archive_cmds='$LD -G -h $soname -o $lib $libobjs $deplibs $linker_flags' hardcode_libdir_flag_spec='-L$libdir' hardcode_shlibpath_var=no ;; *) ld_shlibs=no ;; esac if test sni = "$host_vendor"; then case $host in sysv4 | sysv4.2uw2* | sysv4.3* | sysv5*) export_dynamic_flag_spec='$wl-Blargedynsym' ;; esac fi fi { $as_echo "$as_me:${as_lineno-$LINENO}: result: $ld_shlibs" >&5 $as_echo "$ld_shlibs" >&6; } test no = "$ld_shlibs" && can_build_shared=no with_gnu_ld=$with_gnu_ld # # Do we need to explicitly link libc? # case "x$archive_cmds_need_lc" in x|xyes) # Assume -lc should be added archive_cmds_need_lc=yes if test yes,yes = "$GCC,$enable_shared"; then case $archive_cmds in *'~'*) # FIXME: we may have to deal with multi-command sequences. ;; '$CC '*) # Test whether the compiler implicitly links with -lc since on some # systems, -lgcc has to come before -lc. If gcc already passes -lc # to ld, don't add -lc before -lgcc. { $as_echo "$as_me:${as_lineno-$LINENO}: checking whether -lc should be explicitly linked in" >&5 $as_echo_n "checking whether -lc should be explicitly linked in... " >&6; } if ${lt_cv_archive_cmds_need_lc+:} false; then : $as_echo_n "(cached) " >&6 else $RM conftest* echo "$lt_simple_compile_test_code" > conftest.$ac_ext if { { eval echo "\"\$as_me\":${as_lineno-$LINENO}: \"$ac_compile\""; } >&5 (eval $ac_compile) 2>&5 ac_status=$? $as_echo "$as_me:${as_lineno-$LINENO}: \$? = $ac_status" >&5 test $ac_status = 0; } 2>conftest.err; then soname=conftest lib=conftest libobjs=conftest.$ac_objext deplibs= wl=$lt_prog_compiler_wl pic_flag=$lt_prog_compiler_pic compiler_flags=-v linker_flags=-v verstring= output_objdir=. libname=conftest lt_save_allow_undefined_flag=$allow_undefined_flag allow_undefined_flag= if { { eval echo "\"\$as_me\":${as_lineno-$LINENO}: \"$archive_cmds 2\>\&1 \| $GREP \" -lc \" \>/dev/null 2\>\&1\""; } >&5 (eval $archive_cmds 2\>\&1 \| $GREP \" -lc \" \>/dev/null 2\>\&1) 2>&5 ac_status=$? $as_echo "$as_me:${as_lineno-$LINENO}: \$? = $ac_status" >&5 test $ac_status = 0; } then lt_cv_archive_cmds_need_lc=no else lt_cv_archive_cmds_need_lc=yes fi allow_undefined_flag=$lt_save_allow_undefined_flag else cat conftest.err 1>&5 fi $RM conftest* fi { $as_echo "$as_me:${as_lineno-$LINENO}: result: $lt_cv_archive_cmds_need_lc" >&5 $as_echo "$lt_cv_archive_cmds_need_lc" >&6; } archive_cmds_need_lc=$lt_cv_archive_cmds_need_lc ;; esac fi ;; esac { $as_echo "$as_me:${as_lineno-$LINENO}: checking dynamic linker characteristics" >&5 $as_echo_n "checking dynamic linker characteristics... " >&6; } if test yes = "$GCC"; then case $host_os in darwin*) lt_awk_arg='/^libraries:/,/LR/' ;; *) lt_awk_arg='/^libraries:/' ;; esac case $host_os in mingw* | cegcc*) lt_sed_strip_eq='s|=\([A-Za-z]:\)|\1|g' ;; *) lt_sed_strip_eq='s|=/|/|g' ;; esac lt_search_path_spec=`$CC -print-search-dirs | awk $lt_awk_arg | $SED -e "s/^libraries://" -e $lt_sed_strip_eq` case $lt_search_path_spec in *\;*) # if the path contains ";" then we assume it to be the separator # otherwise default to the standard path separator (i.e. ":") - it is # assumed that no part of a normal pathname contains ";" but that should # okay in the real world where ";" in dirpaths is itself problematic. lt_search_path_spec=`$ECHO "$lt_search_path_spec" | $SED 's/;/ /g'` ;; *) lt_search_path_spec=`$ECHO "$lt_search_path_spec" | $SED "s/$PATH_SEPARATOR/ /g"` ;; esac # Ok, now we have the path, separated by spaces, we can step through it # and add multilib dir if necessary... lt_tmp_lt_search_path_spec= lt_multi_os_dir=/`$CC $CPPFLAGS $CFLAGS $LDFLAGS -print-multi-os-directory 2>/dev/null` # ...but if some path component already ends with the multilib dir we assume # that all is fine and trust -print-search-dirs as is (GCC 4.2? or newer). case "$lt_multi_os_dir; $lt_search_path_spec " in "/; "* | "/.; "* | "/./; "* | *"$lt_multi_os_dir "* | *"$lt_multi_os_dir/ "*) lt_multi_os_dir= ;; esac for lt_sys_path in $lt_search_path_spec; do if test -d "$lt_sys_path$lt_multi_os_dir"; then lt_tmp_lt_search_path_spec="$lt_tmp_lt_search_path_spec $lt_sys_path$lt_multi_os_dir" elif test -n "$lt_multi_os_dir"; then test -d "$lt_sys_path" && \ lt_tmp_lt_search_path_spec="$lt_tmp_lt_search_path_spec $lt_sys_path" fi done lt_search_path_spec=`$ECHO "$lt_tmp_lt_search_path_spec" | awk ' BEGIN {RS = " "; FS = "/|\n";} { lt_foo = ""; lt_count = 0; for (lt_i = NF; lt_i > 0; lt_i--) { if ($lt_i != "" && $lt_i != ".") { if ($lt_i == "..") { lt_count++; } else { if (lt_count == 0) { lt_foo = "/" $lt_i lt_foo; } else { lt_count--; } } } } if (lt_foo != "") { lt_freq[lt_foo]++; } if (lt_freq[lt_foo] == 1) { print lt_foo; } }'` # AWK program above erroneously prepends '/' to C:/dos/paths # for these hosts. case $host_os in mingw* | cegcc*) lt_search_path_spec=`$ECHO "$lt_search_path_spec" |\ $SED 's|/\([A-Za-z]:\)|\1|g'` ;; esac sys_lib_search_path_spec=`$ECHO "$lt_search_path_spec" | $lt_NL2SP` else sys_lib_search_path_spec="/lib /usr/lib /usr/local/lib" fi library_names_spec= libname_spec='lib$name' soname_spec= shrext_cmds=.so postinstall_cmds= postuninstall_cmds= finish_cmds= finish_eval= shlibpath_var= shlibpath_overrides_runpath=unknown version_type=none dynamic_linker="$host_os ld.so" sys_lib_dlsearch_path_spec="/lib /usr/lib" need_lib_prefix=unknown hardcode_into_libs=no # when you set need_version to no, make sure it does not cause -set_version # flags to be left without arguments need_version=unknown case $host_os in aix3*) version_type=linux # correct to gnu/linux during the next big refactor library_names_spec='$libname$release$shared_ext$versuffix $libname.a' shlibpath_var=LIBPATH # AIX 3 has no versioning support, so we append a major version to the name. soname_spec='$libname$release$shared_ext$major' ;; aix[4-9]*) version_type=linux # correct to gnu/linux during the next big refactor need_lib_prefix=no need_version=no hardcode_into_libs=yes if test ia64 = "$host_cpu"; then # AIX 5 supports IA64 library_names_spec='$libname$release$shared_ext$major $libname$release$shared_ext$versuffix $libname$shared_ext' shlibpath_var=LD_LIBRARY_PATH else # With GCC up to 2.95.x, collect2 would create an import file # for dependence libraries. The import file would start with # the line '#! .'. This would cause the generated library to # depend on '.', always an invalid library. This was fixed in # development snapshots of GCC prior to 3.0. case $host_os in aix4 | aix4.[01] | aix4.[01].*) if { echo '#if __GNUC__ > 2 || (__GNUC__ == 2 && __GNUC_MINOR__ >= 97)' echo ' yes ' echo '#endif'; } | $CC -E - | $GREP yes > /dev/null; then : else can_build_shared=no fi ;; esac # Using Import Files as archive members, it is possible to support # filename-based versioning of shared library archives on AIX. While # this would work for both with and without runtime linking, it will # prevent static linking of such archives. So we do filename-based # shared library versioning with .so extension only, which is used # when both runtime linking and shared linking is enabled. # Unfortunately, runtime linking may impact performance, so we do # not want this to be the default eventually. Also, we use the # versioned .so libs for executables only if there is the -brtl # linker flag in LDFLAGS as well, or --with-aix-soname=svr4 only. # To allow for filename-based versioning support, we need to create # libNAME.so.V as an archive file, containing: # *) an Import File, referring to the versioned filename of the # archive as well as the shared archive member, telling the # bitwidth (32 or 64) of that shared object, and providing the # list of exported symbols of that shared object, eventually # decorated with the 'weak' keyword # *) the shared object with the F_LOADONLY flag set, to really avoid # it being seen by the linker. # At run time we better use the real file rather than another symlink, # but for link time we create the symlink libNAME.so -> libNAME.so.V case $with_aix_soname,$aix_use_runtimelinking in # AIX (on Power*) has no versioning support, so currently we cannot hardcode correct # soname into executable. Probably we can add versioning support to # collect2, so additional links can be useful in future. aix,yes) # traditional libtool dynamic_linker='AIX unversionable lib.so' # If using run time linking (on AIX 4.2 or later) use lib.so # instead of lib.a to let people know that these are not # typical AIX shared libraries. library_names_spec='$libname$release$shared_ext$versuffix $libname$release$shared_ext$major $libname$shared_ext' ;; aix,no) # traditional AIX only dynamic_linker='AIX lib.a(lib.so.V)' # We preserve .a as extension for shared libraries through AIX4.2 # and later when we are not doing run time linking. library_names_spec='$libname$release.a $libname.a' soname_spec='$libname$release$shared_ext$major' ;; svr4,*) # full svr4 only dynamic_linker="AIX lib.so.V($shared_archive_member_spec.o)" library_names_spec='$libname$release$shared_ext$major $libname$shared_ext' # We do not specify a path in Import Files, so LIBPATH fires. shlibpath_overrides_runpath=yes ;; *,yes) # both, prefer svr4 dynamic_linker="AIX lib.so.V($shared_archive_member_spec.o), lib.a(lib.so.V)" library_names_spec='$libname$release$shared_ext$major $libname$shared_ext' # unpreferred sharedlib libNAME.a needs extra handling postinstall_cmds='test -n "$linkname" || linkname="$realname"~func_stripname "" ".so" "$linkname"~$install_shared_prog "$dir/$func_stripname_result.$libext" "$destdir/$func_stripname_result.$libext"~test -z "$tstripme" || test -z "$striplib" || $striplib "$destdir/$func_stripname_result.$libext"' postuninstall_cmds='for n in $library_names $old_library; do :; done~func_stripname "" ".so" "$n"~test "$func_stripname_result" = "$n" || func_append rmfiles " $odir/$func_stripname_result.$libext"' # We do not specify a path in Import Files, so LIBPATH fires. shlibpath_overrides_runpath=yes ;; *,no) # both, prefer aix dynamic_linker="AIX lib.a(lib.so.V), lib.so.V($shared_archive_member_spec.o)" library_names_spec='$libname$release.a $libname.a' soname_spec='$libname$release$shared_ext$major' # unpreferred sharedlib libNAME.so.V and symlink libNAME.so need extra handling postinstall_cmds='test -z "$dlname" || $install_shared_prog $dir/$dlname $destdir/$dlname~test -z "$tstripme" || test -z "$striplib" || $striplib $destdir/$dlname~test -n "$linkname" || linkname=$realname~func_stripname "" ".a" "$linkname"~(cd "$destdir" && $LN_S -f $dlname $func_stripname_result.so)' postuninstall_cmds='test -z "$dlname" || func_append rmfiles " $odir/$dlname"~for n in $old_library $library_names; do :; done~func_stripname "" ".a" "$n"~func_append rmfiles " $odir/$func_stripname_result.so"' ;; esac shlibpath_var=LIBPATH fi ;; amigaos*) case $host_cpu in powerpc) # Since July 2007 AmigaOS4 officially supports .so libraries. # When compiling the executable, add -use-dynld -Lsobjs: to the compileline. library_names_spec='$libname$release$shared_ext$versuffix $libname$release$shared_ext$major $libname$shared_ext' ;; m68k) library_names_spec='$libname.ixlibrary $libname.a' # Create ${libname}_ixlibrary.a entries in /sys/libs. finish_eval='for lib in `ls $libdir/*.ixlibrary 2>/dev/null`; do libname=`func_echo_all "$lib" | $SED '\''s%^.*/\([^/]*\)\.ixlibrary$%\1%'\''`; $RM /sys/libs/${libname}_ixlibrary.a; $show "cd /sys/libs && $LN_S $lib ${libname}_ixlibrary.a"; cd /sys/libs && $LN_S $lib ${libname}_ixlibrary.a || exit 1; done' ;; esac ;; beos*) library_names_spec='$libname$shared_ext' dynamic_linker="$host_os ld.so" shlibpath_var=LIBRARY_PATH ;; bsdi[45]*) version_type=linux # correct to gnu/linux during the next big refactor need_version=no library_names_spec='$libname$release$shared_ext$versuffix $libname$release$shared_ext$major $libname$shared_ext' soname_spec='$libname$release$shared_ext$major' finish_cmds='PATH="\$PATH:/sbin" ldconfig $libdir' shlibpath_var=LD_LIBRARY_PATH sys_lib_search_path_spec="/shlib /usr/lib /usr/X11/lib /usr/contrib/lib /lib /usr/local/lib" sys_lib_dlsearch_path_spec="/shlib /usr/lib /usr/local/lib" # the default ld.so.conf also contains /usr/contrib/lib and # /usr/X11R6/lib (/usr/X11 is a link to /usr/X11R6), but let us allow # libtool to hard-code these into programs ;; cygwin* | mingw* | pw32* | cegcc*) version_type=windows shrext_cmds=.dll need_version=no need_lib_prefix=no case $GCC,$cc_basename in yes,*) # gcc library_names_spec='$libname.dll.a' # DLL is installed to $(libdir)/../bin by postinstall_cmds postinstall_cmds='base_file=`basename \$file`~ dlpath=`$SHELL 2>&1 -c '\''. $dir/'\''\$base_file'\''i; echo \$dlname'\''`~ dldir=$destdir/`dirname \$dlpath`~ test -d \$dldir || mkdir -p \$dldir~ $install_prog $dir/$dlname \$dldir/$dlname~ chmod a+x \$dldir/$dlname~ if test -n '\''$stripme'\'' && test -n '\''$striplib'\''; then eval '\''$striplib \$dldir/$dlname'\'' || exit \$?; fi' postuninstall_cmds='dldll=`$SHELL 2>&1 -c '\''. $file; echo \$dlname'\''`~ dlpath=$dir/\$dldll~ $RM \$dlpath' shlibpath_overrides_runpath=yes case $host_os in cygwin*) # Cygwin DLLs use 'cyg' prefix rather than 'lib' soname_spec='`echo $libname | sed -e 's/^lib/cyg/'``echo $release | $SED -e 's/[.]/-/g'`$versuffix$shared_ext' sys_lib_search_path_spec="$sys_lib_search_path_spec /usr/lib/w32api" ;; mingw* | cegcc*) # MinGW DLLs use traditional 'lib' prefix soname_spec='$libname`echo $release | $SED -e 's/[.]/-/g'`$versuffix$shared_ext' ;; pw32*) # pw32 DLLs use 'pw' prefix rather than 'lib' library_names_spec='`echo $libname | sed -e 's/^lib/pw/'``echo $release | $SED -e 's/[.]/-/g'`$versuffix$shared_ext' ;; esac dynamic_linker='Win32 ld.exe' ;; *,cl*) # Native MSVC libname_spec='$name' soname_spec='$libname`echo $release | $SED -e 's/[.]/-/g'`$versuffix$shared_ext' library_names_spec='$libname.dll.lib' case $build_os in mingw*) sys_lib_search_path_spec= lt_save_ifs=$IFS IFS=';' for lt_path in $LIB do IFS=$lt_save_ifs # Let DOS variable expansion print the short 8.3 style file name. lt_path=`cd "$lt_path" 2>/dev/null && cmd //C "for %i in (".") do @echo %~si"` sys_lib_search_path_spec="$sys_lib_search_path_spec $lt_path" done IFS=$lt_save_ifs # Convert to MSYS style. sys_lib_search_path_spec=`$ECHO "$sys_lib_search_path_spec" | sed -e 's|\\\\|/|g' -e 's| \\([a-zA-Z]\\):| /\\1|g' -e 's|^ ||'` ;; cygwin*) # Convert to unix form, then to dos form, then back to unix form # but this time dos style (no spaces!) so that the unix form looks # like /cygdrive/c/PROGRA~1:/cygdr... sys_lib_search_path_spec=`cygpath --path --unix "$LIB"` sys_lib_search_path_spec=`cygpath --path --dos "$sys_lib_search_path_spec" 2>/dev/null` sys_lib_search_path_spec=`cygpath --path --unix "$sys_lib_search_path_spec" | $SED -e "s/$PATH_SEPARATOR/ /g"` ;; *) sys_lib_search_path_spec=$LIB if $ECHO "$sys_lib_search_path_spec" | $GREP ';[c-zC-Z]:/' >/dev/null; then # It is most probably a Windows format PATH. sys_lib_search_path_spec=`$ECHO "$sys_lib_search_path_spec" | $SED -e 's/;/ /g'` else sys_lib_search_path_spec=`$ECHO "$sys_lib_search_path_spec" | $SED -e "s/$PATH_SEPARATOR/ /g"` fi # FIXME: find the short name or the path components, as spaces are # common. (e.g. "Program Files" -> "PROGRA~1") ;; esac # DLL is installed to $(libdir)/../bin by postinstall_cmds postinstall_cmds='base_file=`basename \$file`~ dlpath=`$SHELL 2>&1 -c '\''. $dir/'\''\$base_file'\''i; echo \$dlname'\''`~ dldir=$destdir/`dirname \$dlpath`~ test -d \$dldir || mkdir -p \$dldir~ $install_prog $dir/$dlname \$dldir/$dlname' postuninstall_cmds='dldll=`$SHELL 2>&1 -c '\''. $file; echo \$dlname'\''`~ dlpath=$dir/\$dldll~ $RM \$dlpath' shlibpath_overrides_runpath=yes dynamic_linker='Win32 link.exe' ;; *) # Assume MSVC wrapper library_names_spec='$libname`echo $release | $SED -e 's/[.]/-/g'`$versuffix$shared_ext $libname.lib' dynamic_linker='Win32 ld.exe' ;; esac # FIXME: first we should search . and the directory the executable is in shlibpath_var=PATH ;; darwin* | rhapsody*) dynamic_linker="$host_os dyld" version_type=darwin need_lib_prefix=no need_version=no library_names_spec='$libname$release$major$shared_ext $libname$shared_ext' soname_spec='$libname$release$major$shared_ext' shlibpath_overrides_runpath=yes shlibpath_var=DYLD_LIBRARY_PATH shrext_cmds='`test .$module = .yes && echo .so || echo .dylib`' sys_lib_search_path_spec="$sys_lib_search_path_spec /usr/local/lib" sys_lib_dlsearch_path_spec='/usr/local/lib /lib /usr/lib' ;; dgux*) version_type=linux # correct to gnu/linux during the next big refactor need_lib_prefix=no need_version=no library_names_spec='$libname$release$shared_ext$versuffix $libname$release$shared_ext$major $libname$shared_ext' soname_spec='$libname$release$shared_ext$major' shlibpath_var=LD_LIBRARY_PATH ;; freebsd* | dragonfly*) # DragonFly does not have aout. When/if they implement a new # versioning mechanism, adjust this. if test -x /usr/bin/objformat; then objformat=`/usr/bin/objformat` else case $host_os in freebsd[23].*) objformat=aout ;; *) objformat=elf ;; esac fi version_type=freebsd-$objformat case $version_type in freebsd-elf*) library_names_spec='$libname$release$shared_ext$versuffix $libname$release$shared_ext$major $libname$shared_ext' soname_spec='$libname$release$shared_ext$major' need_version=no need_lib_prefix=no ;; freebsd-*) library_names_spec='$libname$release$shared_ext$versuffix $libname$shared_ext$versuffix' need_version=yes ;; esac shlibpath_var=LD_LIBRARY_PATH case $host_os in freebsd2.*) shlibpath_overrides_runpath=yes ;; freebsd3.[01]* | freebsdelf3.[01]*) shlibpath_overrides_runpath=yes hardcode_into_libs=yes ;; freebsd3.[2-9]* | freebsdelf3.[2-9]* | \ freebsd4.[0-5] | freebsdelf4.[0-5] | freebsd4.1.1 | freebsdelf4.1.1) shlibpath_overrides_runpath=no hardcode_into_libs=yes ;; *) # from 4.6 on, and DragonFly shlibpath_overrides_runpath=yes hardcode_into_libs=yes ;; esac ;; haiku*) version_type=linux # correct to gnu/linux during the next big refactor need_lib_prefix=no need_version=no dynamic_linker="$host_os runtime_loader" library_names_spec='$libname$release$shared_ext$versuffix $libname$release$shared_ext$major $libname$shared_ext' soname_spec='$libname$release$shared_ext$major' shlibpath_var=LIBRARY_PATH shlibpath_overrides_runpath=no sys_lib_dlsearch_path_spec='/boot/home/config/lib /boot/common/lib /boot/system/lib' hardcode_into_libs=yes ;; hpux9* | hpux10* | hpux11*) # Give a soname corresponding to the major version so that dld.sl refuses to # link against other versions. version_type=sunos need_lib_prefix=no need_version=no case $host_cpu in ia64*) shrext_cmds='.so' hardcode_into_libs=yes dynamic_linker="$host_os dld.so" shlibpath_var=LD_LIBRARY_PATH shlibpath_overrides_runpath=yes # Unless +noenvvar is specified. library_names_spec='$libname$release$shared_ext$versuffix $libname$release$shared_ext$major $libname$shared_ext' soname_spec='$libname$release$shared_ext$major' if test 32 = "$HPUX_IA64_MODE"; then sys_lib_search_path_spec="/usr/lib/hpux32 /usr/local/lib/hpux32 /usr/local/lib" sys_lib_dlsearch_path_spec=/usr/lib/hpux32 else sys_lib_search_path_spec="/usr/lib/hpux64 /usr/local/lib/hpux64" sys_lib_dlsearch_path_spec=/usr/lib/hpux64 fi ;; hppa*64*) shrext_cmds='.sl' hardcode_into_libs=yes dynamic_linker="$host_os dld.sl" shlibpath_var=LD_LIBRARY_PATH # How should we handle SHLIB_PATH shlibpath_overrides_runpath=yes # Unless +noenvvar is specified. library_names_spec='$libname$release$shared_ext$versuffix $libname$release$shared_ext$major $libname$shared_ext' soname_spec='$libname$release$shared_ext$major' sys_lib_search_path_spec="/usr/lib/pa20_64 /usr/ccs/lib/pa20_64" sys_lib_dlsearch_path_spec=$sys_lib_search_path_spec ;; *) shrext_cmds='.sl' dynamic_linker="$host_os dld.sl" shlibpath_var=SHLIB_PATH shlibpath_overrides_runpath=no # +s is required to enable SHLIB_PATH library_names_spec='$libname$release$shared_ext$versuffix $libname$release$shared_ext$major $libname$shared_ext' soname_spec='$libname$release$shared_ext$major' ;; esac # HP-UX runs *really* slowly unless shared libraries are mode 555, ... postinstall_cmds='chmod 555 $lib' # or fails outright, so override atomically: install_override_mode=555 ;; interix[3-9]*) version_type=linux # correct to gnu/linux during the next big refactor need_lib_prefix=no need_version=no library_names_spec='$libname$release$shared_ext$versuffix $libname$release$shared_ext$major $libname$shared_ext' soname_spec='$libname$release$shared_ext$major' dynamic_linker='Interix 3.x ld.so.1 (PE, like ELF)' shlibpath_var=LD_LIBRARY_PATH shlibpath_overrides_runpath=no hardcode_into_libs=yes ;; irix5* | irix6* | nonstopux*) case $host_os in nonstopux*) version_type=nonstopux ;; *) if test yes = "$lt_cv_prog_gnu_ld"; then version_type=linux # correct to gnu/linux during the next big refactor else version_type=irix fi ;; esac need_lib_prefix=no need_version=no soname_spec='$libname$release$shared_ext$major' library_names_spec='$libname$release$shared_ext$versuffix $libname$release$shared_ext$major $libname$release$shared_ext $libname$shared_ext' case $host_os in irix5* | nonstopux*) libsuff= shlibsuff= ;; *) case $LD in # libtool.m4 will add one of these switches to LD *-32|*"-32 "|*-melf32bsmip|*"-melf32bsmip ") libsuff= shlibsuff= libmagic=32-bit;; *-n32|*"-n32 "|*-melf32bmipn32|*"-melf32bmipn32 ") libsuff=32 shlibsuff=N32 libmagic=N32;; *-64|*"-64 "|*-melf64bmip|*"-melf64bmip ") libsuff=64 shlibsuff=64 libmagic=64-bit;; *) libsuff= shlibsuff= libmagic=never-match;; esac ;; esac shlibpath_var=LD_LIBRARY${shlibsuff}_PATH shlibpath_overrides_runpath=no sys_lib_search_path_spec="/usr/lib$libsuff /lib$libsuff /usr/local/lib$libsuff" sys_lib_dlsearch_path_spec="/usr/lib$libsuff /lib$libsuff" hardcode_into_libs=yes ;; # No shared lib support for Linux oldld, aout, or coff. linux*oldld* | linux*aout* | linux*coff*) dynamic_linker=no ;; linux*android*) version_type=none # Android doesn't support versioned libraries. need_lib_prefix=no need_version=no library_names_spec='$libname$release$shared_ext' soname_spec='$libname$release$shared_ext' finish_cmds= shlibpath_var=LD_LIBRARY_PATH shlibpath_overrides_runpath=yes # This implies no fast_install, which is unacceptable. # Some rework will be needed to allow for fast_install # before this can be enabled. hardcode_into_libs=yes dynamic_linker='Android linker' # Don't embed -rpath directories since the linker doesn't support them. hardcode_libdir_flag_spec='-L$libdir' ;; # This must be glibc/ELF. linux* | k*bsd*-gnu | kopensolaris*-gnu | gnu*) version_type=linux # correct to gnu/linux during the next big refactor need_lib_prefix=no need_version=no library_names_spec='$libname$release$shared_ext$versuffix $libname$release$shared_ext$major $libname$shared_ext' soname_spec='$libname$release$shared_ext$major' finish_cmds='PATH="\$PATH:/sbin" ldconfig -n $libdir' shlibpath_var=LD_LIBRARY_PATH shlibpath_overrides_runpath=no # Some binutils ld are patched to set DT_RUNPATH if ${lt_cv_shlibpath_overrides_runpath+:} false; then : $as_echo_n "(cached) " >&6 else lt_cv_shlibpath_overrides_runpath=no save_LDFLAGS=$LDFLAGS save_libdir=$libdir eval "libdir=/foo; wl=\"$lt_prog_compiler_wl\"; \ LDFLAGS=\"\$LDFLAGS $hardcode_libdir_flag_spec\"" cat confdefs.h - <<_ACEOF >conftest.$ac_ext /* end confdefs.h. */ int main () { ; return 0; } _ACEOF if ac_fn_c_try_link "$LINENO"; then : if ($OBJDUMP -p conftest$ac_exeext) 2>/dev/null | grep "RUNPATH.*$libdir" >/dev/null; then : lt_cv_shlibpath_overrides_runpath=yes fi fi rm -f core conftest.err conftest.$ac_objext \ conftest$ac_exeext conftest.$ac_ext LDFLAGS=$save_LDFLAGS libdir=$save_libdir fi shlibpath_overrides_runpath=$lt_cv_shlibpath_overrides_runpath # This implies no fast_install, which is unacceptable. # Some rework will be needed to allow for fast_install # before this can be enabled. hardcode_into_libs=yes # Ideally, we could use ldconfig to report *all* directores which are # searched for libraries, however this is still not possible. Aside from not # being certain /sbin/ldconfig is available, command # 'ldconfig -N -X -v | grep ^/' on 64bit Fedora does not report /usr/lib64, # even though it is searched at run-time. Try to do the best guess by # appending ld.so.conf contents (and includes) to the search path. if test -f /etc/ld.so.conf; then lt_ld_extra=`awk '/^include / { system(sprintf("cd /etc; cat %s 2>/dev/null", \$2)); skip = 1; } { if (!skip) print \$0; skip = 0; }' < /etc/ld.so.conf | $SED -e 's/#.*//;/^[ ]*hwcap[ ]/d;s/[:, ]/ /g;s/=[^=]*$//;s/=[^= ]* / /g;s/"//g;/^$/d' | tr '\n' ' '` sys_lib_dlsearch_path_spec="/lib /usr/lib $lt_ld_extra" fi # We used to test for /lib/ld.so.1 and disable shared libraries on # powerpc, because MkLinux only supported shared libraries with the # GNU dynamic linker. Since this was broken with cross compilers, # most powerpc-linux boxes support dynamic linking these days and # people can always --disable-shared, the test was removed, and we # assume the GNU/Linux dynamic linker is in use. dynamic_linker='GNU/Linux ld.so' ;; netbsdelf*-gnu) version_type=linux need_lib_prefix=no need_version=no library_names_spec='${libname}${release}${shared_ext}$versuffix ${libname}${release}${shared_ext}$major ${libname}${shared_ext}' soname_spec='${libname}${release}${shared_ext}$major' shlibpath_var=LD_LIBRARY_PATH shlibpath_overrides_runpath=no hardcode_into_libs=yes dynamic_linker='NetBSD ld.elf_so' ;; netbsd*) version_type=sunos need_lib_prefix=no need_version=no if echo __ELF__ | $CC -E - | $GREP __ELF__ >/dev/null; then library_names_spec='$libname$release$shared_ext$versuffix $libname$shared_ext$versuffix' finish_cmds='PATH="\$PATH:/sbin" ldconfig -m $libdir' dynamic_linker='NetBSD (a.out) ld.so' else library_names_spec='$libname$release$shared_ext$versuffix $libname$release$shared_ext$major $libname$shared_ext' soname_spec='$libname$release$shared_ext$major' dynamic_linker='NetBSD ld.elf_so' fi shlibpath_var=LD_LIBRARY_PATH shlibpath_overrides_runpath=yes hardcode_into_libs=yes ;; newsos6) version_type=linux # correct to gnu/linux during the next big refactor library_names_spec='$libname$release$shared_ext$versuffix $libname$release$shared_ext$major $libname$shared_ext' shlibpath_var=LD_LIBRARY_PATH shlibpath_overrides_runpath=yes ;; *nto* | *qnx*) version_type=qnx need_lib_prefix=no need_version=no library_names_spec='$libname$release$shared_ext$versuffix $libname$release$shared_ext$major $libname$shared_ext' soname_spec='$libname$release$shared_ext$major' shlibpath_var=LD_LIBRARY_PATH shlibpath_overrides_runpath=no hardcode_into_libs=yes dynamic_linker='ldqnx.so' ;; openbsd* | bitrig*) version_type=sunos sys_lib_dlsearch_path_spec=/usr/lib need_lib_prefix=no if test -z "`echo __ELF__ | $CC -E - | $GREP __ELF__`"; then need_version=no else need_version=yes fi library_names_spec='$libname$release$shared_ext$versuffix $libname$shared_ext$versuffix' finish_cmds='PATH="\$PATH:/sbin" ldconfig -m $libdir' shlibpath_var=LD_LIBRARY_PATH shlibpath_overrides_runpath=yes ;; os2*) libname_spec='$name' version_type=windows shrext_cmds=.dll need_version=no need_lib_prefix=no # OS/2 can only load a DLL with a base name of 8 characters or less. soname_spec='`test -n "$os2dllname" && libname="$os2dllname"; v=$($ECHO $release$versuffix | tr -d .-); n=$($ECHO $libname | cut -b -$((8 - ${#v})) | tr . _); $ECHO $n$v`$shared_ext' library_names_spec='${libname}_dll.$libext' dynamic_linker='OS/2 ld.exe' shlibpath_var=BEGINLIBPATH sys_lib_search_path_spec="/lib /usr/lib /usr/local/lib" sys_lib_dlsearch_path_spec=$sys_lib_search_path_spec postinstall_cmds='base_file=`basename \$file`~ dlpath=`$SHELL 2>&1 -c '\''. $dir/'\''\$base_file'\''i; $ECHO \$dlname'\''`~ dldir=$destdir/`dirname \$dlpath`~ test -d \$dldir || mkdir -p \$dldir~ $install_prog $dir/$dlname \$dldir/$dlname~ chmod a+x \$dldir/$dlname~ if test -n '\''$stripme'\'' && test -n '\''$striplib'\''; then eval '\''$striplib \$dldir/$dlname'\'' || exit \$?; fi' postuninstall_cmds='dldll=`$SHELL 2>&1 -c '\''. $file; $ECHO \$dlname'\''`~ dlpath=$dir/\$dldll~ $RM \$dlpath' ;; osf3* | osf4* | osf5*) version_type=osf need_lib_prefix=no need_version=no soname_spec='$libname$release$shared_ext$major' library_names_spec='$libname$release$shared_ext$versuffix $libname$release$shared_ext$major $libname$shared_ext' shlibpath_var=LD_LIBRARY_PATH sys_lib_search_path_spec="/usr/shlib /usr/ccs/lib /usr/lib/cmplrs/cc /usr/lib /usr/local/lib /var/shlib" sys_lib_dlsearch_path_spec=$sys_lib_search_path_spec ;; rdos*) dynamic_linker=no ;; solaris*) version_type=linux # correct to gnu/linux during the next big refactor need_lib_prefix=no need_version=no library_names_spec='$libname$release$shared_ext$versuffix $libname$release$shared_ext$major $libname$shared_ext' soname_spec='$libname$release$shared_ext$major' shlibpath_var=LD_LIBRARY_PATH shlibpath_overrides_runpath=yes hardcode_into_libs=yes # ldd complains unless libraries are executable postinstall_cmds='chmod +x $lib' ;; sunos4*) version_type=sunos library_names_spec='$libname$release$shared_ext$versuffix $libname$shared_ext$versuffix' finish_cmds='PATH="\$PATH:/usr/etc" ldconfig $libdir' shlibpath_var=LD_LIBRARY_PATH shlibpath_overrides_runpath=yes if test yes = "$with_gnu_ld"; then need_lib_prefix=no fi need_version=yes ;; sysv4 | sysv4.3*) version_type=linux # correct to gnu/linux during the next big refactor library_names_spec='$libname$release$shared_ext$versuffix $libname$release$shared_ext$major $libname$shared_ext' soname_spec='$libname$release$shared_ext$major' shlibpath_var=LD_LIBRARY_PATH case $host_vendor in sni) shlibpath_overrides_runpath=no need_lib_prefix=no runpath_var=LD_RUN_PATH ;; siemens) need_lib_prefix=no ;; motorola) need_lib_prefix=no need_version=no shlibpath_overrides_runpath=no sys_lib_search_path_spec='/lib /usr/lib /usr/ccs/lib' ;; esac ;; sysv4*MP*) if test -d /usr/nec; then version_type=linux # correct to gnu/linux during the next big refactor library_names_spec='$libname$shared_ext.$versuffix $libname$shared_ext.$major $libname$shared_ext' soname_spec='$libname$shared_ext.$major' shlibpath_var=LD_LIBRARY_PATH fi ;; sysv5* | sco3.2v5* | sco5v6* | unixware* | OpenUNIX* | sysv4*uw2*) version_type=sco need_lib_prefix=no need_version=no library_names_spec='$libname$release$shared_ext$versuffix $libname$release$shared_ext $libname$shared_ext' soname_spec='$libname$release$shared_ext$major' shlibpath_var=LD_LIBRARY_PATH shlibpath_overrides_runpath=yes hardcode_into_libs=yes if test yes = "$with_gnu_ld"; then sys_lib_search_path_spec='/usr/local/lib /usr/gnu/lib /usr/ccs/lib /usr/lib /lib' else sys_lib_search_path_spec='/usr/ccs/lib /usr/lib' case $host_os in sco3.2v5*) sys_lib_search_path_spec="$sys_lib_search_path_spec /lib" ;; esac fi sys_lib_dlsearch_path_spec='/usr/lib' ;; tpf*) # TPF is a cross-target only. Preferred cross-host = GNU/Linux. version_type=linux # correct to gnu/linux during the next big refactor need_lib_prefix=no need_version=no library_names_spec='$libname$release$shared_ext$versuffix $libname$release$shared_ext$major $libname$shared_ext' shlibpath_var=LD_LIBRARY_PATH shlibpath_overrides_runpath=no hardcode_into_libs=yes ;; uts4*) version_type=linux # correct to gnu/linux during the next big refactor library_names_spec='$libname$release$shared_ext$versuffix $libname$release$shared_ext$major $libname$shared_ext' soname_spec='$libname$release$shared_ext$major' shlibpath_var=LD_LIBRARY_PATH ;; *) dynamic_linker=no ;; esac { $as_echo "$as_me:${as_lineno-$LINENO}: result: $dynamic_linker" >&5 $as_echo "$dynamic_linker" >&6; } test no = "$dynamic_linker" && can_build_shared=no variables_saved_for_relink="PATH $shlibpath_var $runpath_var" if test yes = "$GCC"; then variables_saved_for_relink="$variables_saved_for_relink GCC_EXEC_PREFIX COMPILER_PATH LIBRARY_PATH" fi if test set = "${lt_cv_sys_lib_search_path_spec+set}"; then sys_lib_search_path_spec=$lt_cv_sys_lib_search_path_spec fi if test set = "${lt_cv_sys_lib_dlsearch_path_spec+set}"; then sys_lib_dlsearch_path_spec=$lt_cv_sys_lib_dlsearch_path_spec fi # remember unaugmented sys_lib_dlsearch_path content for libtool script decls... configure_time_dlsearch_path=$sys_lib_dlsearch_path_spec # ... but it needs LT_SYS_LIBRARY_PATH munging for other configure-time code func_munge_path_list sys_lib_dlsearch_path_spec "$LT_SYS_LIBRARY_PATH" # to be used as default LT_SYS_LIBRARY_PATH value in generated libtool configure_time_lt_sys_library_path=$LT_SYS_LIBRARY_PATH { $as_echo "$as_me:${as_lineno-$LINENO}: checking how to hardcode library paths into programs" >&5 $as_echo_n "checking how to hardcode library paths into programs... " >&6; } hardcode_action= if test -n "$hardcode_libdir_flag_spec" || test -n "$runpath_var" || test yes = "$hardcode_automatic"; then # We can hardcode non-existent directories. if test no != "$hardcode_direct" && # If the only mechanism to avoid hardcoding is shlibpath_var, we # have to relink, otherwise we might link with an installed library # when we should be linking with a yet-to-be-installed one ## test no != "$_LT_TAGVAR(hardcode_shlibpath_var, )" && test no != "$hardcode_minus_L"; then # Linking always hardcodes the temporary library directory. hardcode_action=relink else # We can link without hardcoding, and we can hardcode nonexisting dirs. hardcode_action=immediate fi else # We cannot hardcode anything, or else we can only hardcode existing # directories. hardcode_action=unsupported fi { $as_echo "$as_me:${as_lineno-$LINENO}: result: $hardcode_action" >&5 $as_echo "$hardcode_action" >&6; } if test relink = "$hardcode_action" || test yes = "$inherit_rpath"; then # Fast installation is not supported enable_fast_install=no elif test yes = "$shlibpath_overrides_runpath" || test no = "$enable_shared"; then # Fast installation is not necessary enable_fast_install=needless fi if test yes != "$enable_dlopen"; then enable_dlopen=unknown enable_dlopen_self=unknown enable_dlopen_self_static=unknown else lt_cv_dlopen=no lt_cv_dlopen_libs= case $host_os in beos*) lt_cv_dlopen=load_add_on lt_cv_dlopen_libs= lt_cv_dlopen_self=yes ;; mingw* | pw32* | cegcc*) lt_cv_dlopen=LoadLibrary lt_cv_dlopen_libs= ;; cygwin*) lt_cv_dlopen=dlopen lt_cv_dlopen_libs= ;; darwin*) # if libdl is installed we need to link against it { $as_echo "$as_me:${as_lineno-$LINENO}: checking for dlopen in -ldl" >&5 $as_echo_n "checking for dlopen in -ldl... " >&6; } if ${ac_cv_lib_dl_dlopen+:} false; then : $as_echo_n "(cached) " >&6 else ac_check_lib_save_LIBS=$LIBS LIBS="-ldl $LIBS" cat confdefs.h - <<_ACEOF >conftest.$ac_ext /* end confdefs.h. */ /* Override any GCC internal prototype to avoid an error. Use char because int might match the return type of a GCC builtin and then its argument prototype would still apply. */ #ifdef __cplusplus extern "C" #endif char dlopen (); int main () { return dlopen (); ; return 0; } _ACEOF if ac_fn_c_try_link "$LINENO"; then : ac_cv_lib_dl_dlopen=yes else ac_cv_lib_dl_dlopen=no fi rm -f core conftest.err conftest.$ac_objext \ conftest$ac_exeext conftest.$ac_ext LIBS=$ac_check_lib_save_LIBS fi { $as_echo "$as_me:${as_lineno-$LINENO}: result: $ac_cv_lib_dl_dlopen" >&5 $as_echo "$ac_cv_lib_dl_dlopen" >&6; } if test "x$ac_cv_lib_dl_dlopen" = xyes; then : lt_cv_dlopen=dlopen lt_cv_dlopen_libs=-ldl else lt_cv_dlopen=dyld lt_cv_dlopen_libs= lt_cv_dlopen_self=yes fi ;; tpf*) # Don't try to run any link tests for TPF. We know it's impossible # because TPF is a cross-compiler, and we know how we open DSOs. lt_cv_dlopen=dlopen lt_cv_dlopen_libs= lt_cv_dlopen_self=no ;; *) ac_fn_c_check_func "$LINENO" "shl_load" "ac_cv_func_shl_load" if test "x$ac_cv_func_shl_load" = xyes; then : lt_cv_dlopen=shl_load else { $as_echo "$as_me:${as_lineno-$LINENO}: checking for shl_load in -ldld" >&5 $as_echo_n "checking for shl_load in -ldld... " >&6; } if ${ac_cv_lib_dld_shl_load+:} false; then : $as_echo_n "(cached) " >&6 else ac_check_lib_save_LIBS=$LIBS LIBS="-ldld $LIBS" cat confdefs.h - <<_ACEOF >conftest.$ac_ext /* end confdefs.h. */ /* Override any GCC internal prototype to avoid an error. Use char because int might match the return type of a GCC builtin and then its argument prototype would still apply. */ #ifdef __cplusplus extern "C" #endif char shl_load (); int main () { return shl_load (); ; return 0; } _ACEOF if ac_fn_c_try_link "$LINENO"; then : ac_cv_lib_dld_shl_load=yes else ac_cv_lib_dld_shl_load=no fi rm -f core conftest.err conftest.$ac_objext \ conftest$ac_exeext conftest.$ac_ext LIBS=$ac_check_lib_save_LIBS fi { $as_echo "$as_me:${as_lineno-$LINENO}: result: $ac_cv_lib_dld_shl_load" >&5 $as_echo "$ac_cv_lib_dld_shl_load" >&6; } if test "x$ac_cv_lib_dld_shl_load" = xyes; then : lt_cv_dlopen=shl_load lt_cv_dlopen_libs=-ldld else ac_fn_c_check_func "$LINENO" "dlopen" "ac_cv_func_dlopen" if test "x$ac_cv_func_dlopen" = xyes; then : lt_cv_dlopen=dlopen else { $as_echo "$as_me:${as_lineno-$LINENO}: checking for dlopen in -ldl" >&5 $as_echo_n "checking for dlopen in -ldl... " >&6; } if ${ac_cv_lib_dl_dlopen+:} false; then : $as_echo_n "(cached) " >&6 else ac_check_lib_save_LIBS=$LIBS LIBS="-ldl $LIBS" cat confdefs.h - <<_ACEOF >conftest.$ac_ext /* end confdefs.h. */ /* Override any GCC internal prototype to avoid an error. Use char because int might match the return type of a GCC builtin and then its argument prototype would still apply. */ #ifdef __cplusplus extern "C" #endif char dlopen (); int main () { return dlopen (); ; return 0; } _ACEOF if ac_fn_c_try_link "$LINENO"; then : ac_cv_lib_dl_dlopen=yes else ac_cv_lib_dl_dlopen=no fi rm -f core conftest.err conftest.$ac_objext \ conftest$ac_exeext conftest.$ac_ext LIBS=$ac_check_lib_save_LIBS fi { $as_echo "$as_me:${as_lineno-$LINENO}: result: $ac_cv_lib_dl_dlopen" >&5 $as_echo "$ac_cv_lib_dl_dlopen" >&6; } if test "x$ac_cv_lib_dl_dlopen" = xyes; then : lt_cv_dlopen=dlopen lt_cv_dlopen_libs=-ldl else { $as_echo "$as_me:${as_lineno-$LINENO}: checking for dlopen in -lsvld" >&5 $as_echo_n "checking for dlopen in -lsvld... " >&6; } if ${ac_cv_lib_svld_dlopen+:} false; then : $as_echo_n "(cached) " >&6 else ac_check_lib_save_LIBS=$LIBS LIBS="-lsvld $LIBS" cat confdefs.h - <<_ACEOF >conftest.$ac_ext /* end confdefs.h. */ /* Override any GCC internal prototype to avoid an error. Use char because int might match the return type of a GCC builtin and then its argument prototype would still apply. */ #ifdef __cplusplus extern "C" #endif char dlopen (); int main () { return dlopen (); ; return 0; } _ACEOF if ac_fn_c_try_link "$LINENO"; then : ac_cv_lib_svld_dlopen=yes else ac_cv_lib_svld_dlopen=no fi rm -f core conftest.err conftest.$ac_objext \ conftest$ac_exeext conftest.$ac_ext LIBS=$ac_check_lib_save_LIBS fi { $as_echo "$as_me:${as_lineno-$LINENO}: result: $ac_cv_lib_svld_dlopen" >&5 $as_echo "$ac_cv_lib_svld_dlopen" >&6; } if test "x$ac_cv_lib_svld_dlopen" = xyes; then : lt_cv_dlopen=dlopen lt_cv_dlopen_libs=-lsvld else { $as_echo "$as_me:${as_lineno-$LINENO}: checking for dld_link in -ldld" >&5 $as_echo_n "checking for dld_link in -ldld... " >&6; } if ${ac_cv_lib_dld_dld_link+:} false; then : $as_echo_n "(cached) " >&6 else ac_check_lib_save_LIBS=$LIBS LIBS="-ldld $LIBS" cat confdefs.h - <<_ACEOF >conftest.$ac_ext /* end confdefs.h. */ /* Override any GCC internal prototype to avoid an error. Use char because int might match the return type of a GCC builtin and then its argument prototype would still apply. */ #ifdef __cplusplus extern "C" #endif char dld_link (); int main () { return dld_link (); ; return 0; } _ACEOF if ac_fn_c_try_link "$LINENO"; then : ac_cv_lib_dld_dld_link=yes else ac_cv_lib_dld_dld_link=no fi rm -f core conftest.err conftest.$ac_objext \ conftest$ac_exeext conftest.$ac_ext LIBS=$ac_check_lib_save_LIBS fi { $as_echo "$as_me:${as_lineno-$LINENO}: result: $ac_cv_lib_dld_dld_link" >&5 $as_echo "$ac_cv_lib_dld_dld_link" >&6; } if test "x$ac_cv_lib_dld_dld_link" = xyes; then : lt_cv_dlopen=dld_link lt_cv_dlopen_libs=-ldld fi fi fi fi fi fi ;; esac if test no = "$lt_cv_dlopen"; then enable_dlopen=no else enable_dlopen=yes fi case $lt_cv_dlopen in dlopen) save_CPPFLAGS=$CPPFLAGS test yes = "$ac_cv_header_dlfcn_h" && CPPFLAGS="$CPPFLAGS -DHAVE_DLFCN_H" save_LDFLAGS=$LDFLAGS wl=$lt_prog_compiler_wl eval LDFLAGS=\"\$LDFLAGS $export_dynamic_flag_spec\" save_LIBS=$LIBS LIBS="$lt_cv_dlopen_libs $LIBS" { $as_echo "$as_me:${as_lineno-$LINENO}: checking whether a program can dlopen itself" >&5 $as_echo_n "checking whether a program can dlopen itself... " >&6; } if ${lt_cv_dlopen_self+:} false; then : $as_echo_n "(cached) " >&6 else if test yes = "$cross_compiling"; then : lt_cv_dlopen_self=cross else lt_dlunknown=0; lt_dlno_uscore=1; lt_dlneed_uscore=2 lt_status=$lt_dlunknown cat > conftest.$ac_ext <<_LT_EOF #line $LINENO "configure" #include "confdefs.h" #if HAVE_DLFCN_H #include #endif #include #ifdef RTLD_GLOBAL # define LT_DLGLOBAL RTLD_GLOBAL #else # ifdef DL_GLOBAL # define LT_DLGLOBAL DL_GLOBAL # else # define LT_DLGLOBAL 0 # endif #endif /* We may have to define LT_DLLAZY_OR_NOW in the command line if we find out it does not work in some platform. */ #ifndef LT_DLLAZY_OR_NOW # ifdef RTLD_LAZY # define LT_DLLAZY_OR_NOW RTLD_LAZY # else # ifdef DL_LAZY # define LT_DLLAZY_OR_NOW DL_LAZY # else # ifdef RTLD_NOW # define LT_DLLAZY_OR_NOW RTLD_NOW # else # ifdef DL_NOW # define LT_DLLAZY_OR_NOW DL_NOW # else # define LT_DLLAZY_OR_NOW 0 # endif # endif # endif # endif #endif /* When -fvisibility=hidden is used, assume the code has been annotated correspondingly for the symbols needed. */ #if defined __GNUC__ && (((__GNUC__ == 3) && (__GNUC_MINOR__ >= 3)) || (__GNUC__ > 3)) int fnord () __attribute__((visibility("default"))); #endif int fnord () { return 42; } int main () { void *self = dlopen (0, LT_DLGLOBAL|LT_DLLAZY_OR_NOW); int status = $lt_dlunknown; if (self) { if (dlsym (self,"fnord")) status = $lt_dlno_uscore; else { if (dlsym( self,"_fnord")) status = $lt_dlneed_uscore; else puts (dlerror ()); } /* dlclose (self); */ } else puts (dlerror ()); return status; } _LT_EOF if { { eval echo "\"\$as_me\":${as_lineno-$LINENO}: \"$ac_link\""; } >&5 (eval $ac_link) 2>&5 ac_status=$? $as_echo "$as_me:${as_lineno-$LINENO}: \$? = $ac_status" >&5 test $ac_status = 0; } && test -s "conftest$ac_exeext" 2>/dev/null; then (./conftest; exit; ) >&5 2>/dev/null lt_status=$? case x$lt_status in x$lt_dlno_uscore) lt_cv_dlopen_self=yes ;; x$lt_dlneed_uscore) lt_cv_dlopen_self=yes ;; x$lt_dlunknown|x*) lt_cv_dlopen_self=no ;; esac else : # compilation failed lt_cv_dlopen_self=no fi fi rm -fr conftest* fi { $as_echo "$as_me:${as_lineno-$LINENO}: result: $lt_cv_dlopen_self" >&5 $as_echo "$lt_cv_dlopen_self" >&6; } if test yes = "$lt_cv_dlopen_self"; then wl=$lt_prog_compiler_wl eval LDFLAGS=\"\$LDFLAGS $lt_prog_compiler_static\" { $as_echo "$as_me:${as_lineno-$LINENO}: checking whether a statically linked program can dlopen itself" >&5 $as_echo_n "checking whether a statically linked program can dlopen itself... " >&6; } if ${lt_cv_dlopen_self_static+:} false; then : $as_echo_n "(cached) " >&6 else if test yes = "$cross_compiling"; then : lt_cv_dlopen_self_static=cross else lt_dlunknown=0; lt_dlno_uscore=1; lt_dlneed_uscore=2 lt_status=$lt_dlunknown cat > conftest.$ac_ext <<_LT_EOF #line $LINENO "configure" #include "confdefs.h" #if HAVE_DLFCN_H #include #endif #include #ifdef RTLD_GLOBAL # define LT_DLGLOBAL RTLD_GLOBAL #else # ifdef DL_GLOBAL # define LT_DLGLOBAL DL_GLOBAL # else # define LT_DLGLOBAL 0 # endif #endif /* We may have to define LT_DLLAZY_OR_NOW in the command line if we find out it does not work in some platform. */ #ifndef LT_DLLAZY_OR_NOW # ifdef RTLD_LAZY # define LT_DLLAZY_OR_NOW RTLD_LAZY # else # ifdef DL_LAZY # define LT_DLLAZY_OR_NOW DL_LAZY # else # ifdef RTLD_NOW # define LT_DLLAZY_OR_NOW RTLD_NOW # else # ifdef DL_NOW # define LT_DLLAZY_OR_NOW DL_NOW # else # define LT_DLLAZY_OR_NOW 0 # endif # endif # endif # endif #endif /* When -fvisibility=hidden is used, assume the code has been annotated correspondingly for the symbols needed. */ #if defined __GNUC__ && (((__GNUC__ == 3) && (__GNUC_MINOR__ >= 3)) || (__GNUC__ > 3)) int fnord () __attribute__((visibility("default"))); #endif int fnord () { return 42; } int main () { void *self = dlopen (0, LT_DLGLOBAL|LT_DLLAZY_OR_NOW); int status = $lt_dlunknown; if (self) { if (dlsym (self,"fnord")) status = $lt_dlno_uscore; else { if (dlsym( self,"_fnord")) status = $lt_dlneed_uscore; else puts (dlerror ()); } /* dlclose (self); */ } else puts (dlerror ()); return status; } _LT_EOF if { { eval echo "\"\$as_me\":${as_lineno-$LINENO}: \"$ac_link\""; } >&5 (eval $ac_link) 2>&5 ac_status=$? $as_echo "$as_me:${as_lineno-$LINENO}: \$? = $ac_status" >&5 test $ac_status = 0; } && test -s "conftest$ac_exeext" 2>/dev/null; then (./conftest; exit; ) >&5 2>/dev/null lt_status=$? case x$lt_status in x$lt_dlno_uscore) lt_cv_dlopen_self_static=yes ;; x$lt_dlneed_uscore) lt_cv_dlopen_self_static=yes ;; x$lt_dlunknown|x*) lt_cv_dlopen_self_static=no ;; esac else : # compilation failed lt_cv_dlopen_self_static=no fi fi rm -fr conftest* fi { $as_echo "$as_me:${as_lineno-$LINENO}: result: $lt_cv_dlopen_self_static" >&5 $as_echo "$lt_cv_dlopen_self_static" >&6; } fi CPPFLAGS=$save_CPPFLAGS LDFLAGS=$save_LDFLAGS LIBS=$save_LIBS ;; esac case $lt_cv_dlopen_self in yes|no) enable_dlopen_self=$lt_cv_dlopen_self ;; *) enable_dlopen_self=unknown ;; esac case $lt_cv_dlopen_self_static in yes|no) enable_dlopen_self_static=$lt_cv_dlopen_self_static ;; *) enable_dlopen_self_static=unknown ;; esac fi striplib= old_striplib= { $as_echo "$as_me:${as_lineno-$LINENO}: checking whether stripping libraries is possible" >&5 $as_echo_n "checking whether stripping libraries is possible... " >&6; } if test -n "$STRIP" && $STRIP -V 2>&1 | $GREP "GNU strip" >/dev/null; then test -z "$old_striplib" && old_striplib="$STRIP --strip-debug" test -z "$striplib" && striplib="$STRIP --strip-unneeded" { $as_echo "$as_me:${as_lineno-$LINENO}: result: yes" >&5 $as_echo "yes" >&6; } else # FIXME - insert some real tests, host_os isn't really good enough case $host_os in darwin*) if test -n "$STRIP"; then striplib="$STRIP -x" old_striplib="$STRIP -S" { $as_echo "$as_me:${as_lineno-$LINENO}: result: yes" >&5 $as_echo "yes" >&6; } else { $as_echo "$as_me:${as_lineno-$LINENO}: result: no" >&5 $as_echo "no" >&6; } fi ;; *) { $as_echo "$as_me:${as_lineno-$LINENO}: result: no" >&5 $as_echo "no" >&6; } ;; esac fi # Report what library types will actually be built { $as_echo "$as_me:${as_lineno-$LINENO}: checking if libtool supports shared libraries" >&5 $as_echo_n "checking if libtool supports shared libraries... " >&6; } { $as_echo "$as_me:${as_lineno-$LINENO}: result: $can_build_shared" >&5 $as_echo "$can_build_shared" >&6; } { $as_echo "$as_me:${as_lineno-$LINENO}: checking whether to build shared libraries" >&5 $as_echo_n "checking whether to build shared libraries... " >&6; } test no = "$can_build_shared" && enable_shared=no # On AIX, shared libraries and static libraries use the same namespace, and # are all built from PIC. case $host_os in aix3*) test yes = "$enable_shared" && enable_static=no if test -n "$RANLIB"; then archive_cmds="$archive_cmds~\$RANLIB \$lib" postinstall_cmds='$RANLIB $lib' fi ;; aix[4-9]*) if test ia64 != "$host_cpu"; then case $enable_shared,$with_aix_soname,$aix_use_runtimelinking in yes,aix,yes) ;; # shared object as lib.so file only yes,svr4,*) ;; # shared object as lib.so archive member only yes,*) enable_static=no ;; # shared object in lib.a archive as well esac fi ;; esac { $as_echo "$as_me:${as_lineno-$LINENO}: result: $enable_shared" >&5 $as_echo "$enable_shared" >&6; } { $as_echo "$as_me:${as_lineno-$LINENO}: checking whether to build static libraries" >&5 $as_echo_n "checking whether to build static libraries... " >&6; } # Make sure either enable_shared or enable_static is yes. test yes = "$enable_shared" || enable_static=yes { $as_echo "$as_me:${as_lineno-$LINENO}: result: $enable_static" >&5 $as_echo "$enable_static" >&6; } fi ac_ext=c ac_cpp='$CPP $CPPFLAGS' ac_compile='$CC -c $CFLAGS $CPPFLAGS conftest.$ac_ext >&5' ac_link='$CC -o conftest$ac_exeext $CFLAGS $CPPFLAGS $LDFLAGS conftest.$ac_ext $LIBS >&5' ac_compiler_gnu=$ac_cv_c_compiler_gnu CC=$lt_save_CC if test -n "$CXX" && ( test no != "$CXX" && ( (test g++ = "$CXX" && `g++ -v >/dev/null 2>&1` ) || (test g++ != "$CXX"))); then ac_ext=cpp ac_cpp='$CXXCPP $CPPFLAGS' ac_compile='$CXX -c $CXXFLAGS $CPPFLAGS conftest.$ac_ext >&5' ac_link='$CXX -o conftest$ac_exeext $CXXFLAGS $CPPFLAGS $LDFLAGS conftest.$ac_ext $LIBS >&5' ac_compiler_gnu=$ac_cv_cxx_compiler_gnu { $as_echo "$as_me:${as_lineno-$LINENO}: checking how to run the C++ preprocessor" >&5 $as_echo_n "checking how to run the C++ preprocessor... " >&6; } if test -z "$CXXCPP"; then if ${ac_cv_prog_CXXCPP+:} false; then : $as_echo_n "(cached) " >&6 else # Double quotes because CXXCPP needs to be expanded for CXXCPP in "$CXX -E" "/lib/cpp" do ac_preproc_ok=false for ac_cxx_preproc_warn_flag in '' yes do # Use a header file that comes with gcc, so configuring glibc # with a fresh cross-compiler works. # Prefer to if __STDC__ is defined, since # exists even on freestanding compilers. # On the NeXT, cc -E runs the code through the compiler's parser, # not just through cpp. "Syntax error" is here to catch this case. cat confdefs.h - <<_ACEOF >conftest.$ac_ext /* end confdefs.h. */ #ifdef __STDC__ # include #else # include #endif Syntax error _ACEOF if ac_fn_cxx_try_cpp "$LINENO"; then : else # Broken: fails on valid input. continue fi rm -f conftest.err conftest.i conftest.$ac_ext # OK, works on sane cases. Now check whether nonexistent headers # can be detected and how. cat confdefs.h - <<_ACEOF >conftest.$ac_ext /* end confdefs.h. */ #include _ACEOF if ac_fn_cxx_try_cpp "$LINENO"; then : # Broken: success on invalid input. continue else # Passes both tests. ac_preproc_ok=: break fi rm -f conftest.err conftest.i conftest.$ac_ext done # Because of `break', _AC_PREPROC_IFELSE's cleaning code was skipped. rm -f conftest.i conftest.err conftest.$ac_ext if $ac_preproc_ok; then : break fi done ac_cv_prog_CXXCPP=$CXXCPP fi CXXCPP=$ac_cv_prog_CXXCPP else ac_cv_prog_CXXCPP=$CXXCPP fi { $as_echo "$as_me:${as_lineno-$LINENO}: result: $CXXCPP" >&5 $as_echo "$CXXCPP" >&6; } ac_preproc_ok=false for ac_cxx_preproc_warn_flag in '' yes do # Use a header file that comes with gcc, so configuring glibc # with a fresh cross-compiler works. # Prefer to if __STDC__ is defined, since # exists even on freestanding compilers. # On the NeXT, cc -E runs the code through the compiler's parser, # not just through cpp. "Syntax error" is here to catch this case. cat confdefs.h - <<_ACEOF >conftest.$ac_ext /* end confdefs.h. */ #ifdef __STDC__ # include #else # include #endif Syntax error _ACEOF if ac_fn_cxx_try_cpp "$LINENO"; then : else # Broken: fails on valid input. continue fi rm -f conftest.err conftest.i conftest.$ac_ext # OK, works on sane cases. Now check whether nonexistent headers # can be detected and how. cat confdefs.h - <<_ACEOF >conftest.$ac_ext /* end confdefs.h. */ #include _ACEOF if ac_fn_cxx_try_cpp "$LINENO"; then : # Broken: success on invalid input. continue else # Passes both tests. ac_preproc_ok=: break fi rm -f conftest.err conftest.i conftest.$ac_ext done # Because of `break', _AC_PREPROC_IFELSE's cleaning code was skipped. rm -f conftest.i conftest.err conftest.$ac_ext if $ac_preproc_ok; then : else { { $as_echo "$as_me:${as_lineno-$LINENO}: error: in \`$ac_pwd':" >&5 $as_echo "$as_me: error: in \`$ac_pwd':" >&2;} as_fn_error $? "C++ preprocessor \"$CXXCPP\" fails sanity check See \`config.log' for more details" "$LINENO" 5; } fi ac_ext=c ac_cpp='$CPP $CPPFLAGS' ac_compile='$CC -c $CFLAGS $CPPFLAGS conftest.$ac_ext >&5' ac_link='$CC -o conftest$ac_exeext $CFLAGS $CPPFLAGS $LDFLAGS conftest.$ac_ext $LIBS >&5' ac_compiler_gnu=$ac_cv_c_compiler_gnu else _lt_caught_CXX_error=yes fi ac_ext=cpp ac_cpp='$CXXCPP $CPPFLAGS' ac_compile='$CXX -c $CXXFLAGS $CPPFLAGS conftest.$ac_ext >&5' ac_link='$CXX -o conftest$ac_exeext $CXXFLAGS $CPPFLAGS $LDFLAGS conftest.$ac_ext $LIBS >&5' ac_compiler_gnu=$ac_cv_cxx_compiler_gnu archive_cmds_need_lc_CXX=no allow_undefined_flag_CXX= always_export_symbols_CXX=no archive_expsym_cmds_CXX= compiler_needs_object_CXX=no export_dynamic_flag_spec_CXX= hardcode_direct_CXX=no hardcode_direct_absolute_CXX=no hardcode_libdir_flag_spec_CXX= hardcode_libdir_separator_CXX= hardcode_minus_L_CXX=no hardcode_shlibpath_var_CXX=unsupported hardcode_automatic_CXX=no inherit_rpath_CXX=no module_cmds_CXX= module_expsym_cmds_CXX= link_all_deplibs_CXX=unknown old_archive_cmds_CXX=$old_archive_cmds reload_flag_CXX=$reload_flag reload_cmds_CXX=$reload_cmds no_undefined_flag_CXX= whole_archive_flag_spec_CXX= enable_shared_with_static_runtimes_CXX=no # Source file extension for C++ test sources. ac_ext=cpp # Object file extension for compiled C++ test sources. objext=o objext_CXX=$objext # No sense in running all these tests if we already determined that # the CXX compiler isn't working. Some variables (like enable_shared) # are currently assumed to apply to all compilers on this platform, # and will be corrupted by setting them based on a non-working compiler. if test yes != "$_lt_caught_CXX_error"; then # Code to be used in simple compile tests lt_simple_compile_test_code="int some_variable = 0;" # Code to be used in simple link tests lt_simple_link_test_code='int main(int, char *[]) { return(0); }' # ltmain only uses $CC for tagged configurations so make sure $CC is set. # If no C compiler was specified, use CC. LTCC=${LTCC-"$CC"} # If no C compiler flags were specified, use CFLAGS. LTCFLAGS=${LTCFLAGS-"$CFLAGS"} # Allow CC to be a program name with arguments. compiler=$CC # save warnings/boilerplate of simple test code ac_outfile=conftest.$ac_objext echo "$lt_simple_compile_test_code" >conftest.$ac_ext eval "$ac_compile" 2>&1 >/dev/null | $SED '/^$/d; /^ *+/d' >conftest.err _lt_compiler_boilerplate=`cat conftest.err` $RM conftest* ac_outfile=conftest.$ac_objext echo "$lt_simple_link_test_code" >conftest.$ac_ext eval "$ac_link" 2>&1 >/dev/null | $SED '/^$/d; /^ *+/d' >conftest.err _lt_linker_boilerplate=`cat conftest.err` $RM -r conftest* # Allow CC to be a program name with arguments. lt_save_CC=$CC lt_save_CFLAGS=$CFLAGS lt_save_LD=$LD lt_save_GCC=$GCC GCC=$GXX lt_save_with_gnu_ld=$with_gnu_ld lt_save_path_LD=$lt_cv_path_LD if test -n "${lt_cv_prog_gnu_ldcxx+set}"; then lt_cv_prog_gnu_ld=$lt_cv_prog_gnu_ldcxx else $as_unset lt_cv_prog_gnu_ld fi if test -n "${lt_cv_path_LDCXX+set}"; then lt_cv_path_LD=$lt_cv_path_LDCXX else $as_unset lt_cv_path_LD fi test -z "${LDCXX+set}" || LD=$LDCXX CC=${CXX-"c++"} CFLAGS=$CXXFLAGS compiler=$CC compiler_CXX=$CC func_cc_basename $compiler cc_basename=$func_cc_basename_result if test -n "$compiler"; then # We don't want -fno-exception when compiling C++ code, so set the # no_builtin_flag separately if test yes = "$GXX"; then lt_prog_compiler_no_builtin_flag_CXX=' -fno-builtin' else lt_prog_compiler_no_builtin_flag_CXX= fi if test yes = "$GXX"; then # Set up default GNU C++ configuration # Check whether --with-gnu-ld was given. if test "${with_gnu_ld+set}" = set; then : withval=$with_gnu_ld; test no = "$withval" || with_gnu_ld=yes else with_gnu_ld=no fi ac_prog=ld if test yes = "$GCC"; then # Check if gcc -print-prog-name=ld gives a path. { $as_echo "$as_me:${as_lineno-$LINENO}: checking for ld used by $CC" >&5 $as_echo_n "checking for ld used by $CC... " >&6; } case $host in *-*-mingw*) # gcc leaves a trailing carriage return, which upsets mingw ac_prog=`($CC -print-prog-name=ld) 2>&5 | tr -d '\015'` ;; *) ac_prog=`($CC -print-prog-name=ld) 2>&5` ;; esac case $ac_prog in # Accept absolute paths. [\\/]* | ?:[\\/]*) re_direlt='/[^/][^/]*/\.\./' # Canonicalize the pathname of ld ac_prog=`$ECHO "$ac_prog"| $SED 's%\\\\%/%g'` while $ECHO "$ac_prog" | $GREP "$re_direlt" > /dev/null 2>&1; do ac_prog=`$ECHO $ac_prog| $SED "s%$re_direlt%/%"` done test -z "$LD" && LD=$ac_prog ;; "") # If it fails, then pretend we aren't using GCC. ac_prog=ld ;; *) # If it is relative, then search for the first ld in PATH. with_gnu_ld=unknown ;; esac elif test yes = "$with_gnu_ld"; then { $as_echo "$as_me:${as_lineno-$LINENO}: checking for GNU ld" >&5 $as_echo_n "checking for GNU ld... " >&6; } else { $as_echo "$as_me:${as_lineno-$LINENO}: checking for non-GNU ld" >&5 $as_echo_n "checking for non-GNU ld... " >&6; } fi if ${lt_cv_path_LD+:} false; then : $as_echo_n "(cached) " >&6 else if test -z "$LD"; then lt_save_ifs=$IFS; IFS=$PATH_SEPARATOR for ac_dir in $PATH; do IFS=$lt_save_ifs test -z "$ac_dir" && ac_dir=. if test -f "$ac_dir/$ac_prog" || test -f "$ac_dir/$ac_prog$ac_exeext"; then lt_cv_path_LD=$ac_dir/$ac_prog # Check to see if the program is GNU ld. I'd rather use --version, # but apparently some variants of GNU ld only accept -v. # Break only if it was the GNU/non-GNU ld that we prefer. case `"$lt_cv_path_LD" -v 2>&1 &5 $as_echo "$LD" >&6; } else { $as_echo "$as_me:${as_lineno-$LINENO}: result: no" >&5 $as_echo "no" >&6; } fi test -z "$LD" && as_fn_error $? "no acceptable ld found in \$PATH" "$LINENO" 5 { $as_echo "$as_me:${as_lineno-$LINENO}: checking if the linker ($LD) is GNU ld" >&5 $as_echo_n "checking if the linker ($LD) is GNU ld... " >&6; } if ${lt_cv_prog_gnu_ld+:} false; then : $as_echo_n "(cached) " >&6 else # I'd rather use --version here, but apparently some GNU lds only accept -v. case `$LD -v 2>&1 &5 $as_echo "$lt_cv_prog_gnu_ld" >&6; } with_gnu_ld=$lt_cv_prog_gnu_ld # Check if GNU C++ uses GNU ld as the underlying linker, since the # archiving commands below assume that GNU ld is being used. if test yes = "$with_gnu_ld"; then archive_cmds_CXX='$CC $pic_flag -shared -nostdlib $predep_objects $libobjs $deplibs $postdep_objects $compiler_flags $wl-soname $wl$soname -o $lib' archive_expsym_cmds_CXX='$CC $pic_flag -shared -nostdlib $predep_objects $libobjs $deplibs $postdep_objects $compiler_flags $wl-soname $wl$soname $wl-retain-symbols-file $wl$export_symbols -o $lib' hardcode_libdir_flag_spec_CXX='$wl-rpath $wl$libdir' export_dynamic_flag_spec_CXX='$wl--export-dynamic' # If archive_cmds runs LD, not CC, wlarc should be empty # XXX I think wlarc can be eliminated in ltcf-cxx, but I need to # investigate it a little bit more. (MM) wlarc='$wl' # ancient GNU ld didn't support --whole-archive et. al. if eval "`$CC -print-prog-name=ld` --help 2>&1" | $GREP 'no-whole-archive' > /dev/null; then whole_archive_flag_spec_CXX=$wlarc'--whole-archive$convenience '$wlarc'--no-whole-archive' else whole_archive_flag_spec_CXX= fi else with_gnu_ld=no wlarc= # A generic and very simple default shared library creation # command for GNU C++ for the case where it uses the native # linker, instead of GNU ld. If possible, this setting should # overridden to take advantage of the native linker features on # the platform it is being used on. archive_cmds_CXX='$CC -shared -nostdlib $predep_objects $libobjs $deplibs $postdep_objects $compiler_flags -o $lib' fi # Commands to make compiler produce verbose output that lists # what "hidden" libraries, object files and flags are used when # linking a shared library. output_verbose_link_cmd='$CC -shared $CFLAGS -v conftest.$objext 2>&1 | $GREP -v "^Configured with:" | $GREP " \-L"' else GXX=no with_gnu_ld=no wlarc= fi # PORTME: fill in a description of your system's C++ link characteristics { $as_echo "$as_me:${as_lineno-$LINENO}: checking whether the $compiler linker ($LD) supports shared libraries" >&5 $as_echo_n "checking whether the $compiler linker ($LD) supports shared libraries... " >&6; } ld_shlibs_CXX=yes case $host_os in aix3*) # FIXME: insert proper C++ library support ld_shlibs_CXX=no ;; aix[4-9]*) if test ia64 = "$host_cpu"; then # On IA64, the linker does run time linking by default, so we don't # have to do anything special. aix_use_runtimelinking=no exp_sym_flag='-Bexport' no_entry_flag= else aix_use_runtimelinking=no # Test if we are trying to use run time linking or normal # AIX style linking. If -brtl is somewhere in LDFLAGS, we # have runtime linking enabled, and use it for executables. # For shared libraries, we enable/disable runtime linking # depending on the kind of the shared library created - # when "with_aix_soname,aix_use_runtimelinking" is: # "aix,no" lib.a(lib.so.V) shared, rtl:no, for executables # "aix,yes" lib.so shared, rtl:yes, for executables # lib.a static archive # "both,no" lib.so.V(shr.o) shared, rtl:yes # lib.a(lib.so.V) shared, rtl:no, for executables # "both,yes" lib.so.V(shr.o) shared, rtl:yes, for executables # lib.a(lib.so.V) shared, rtl:no # "svr4,*" lib.so.V(shr.o) shared, rtl:yes, for executables # lib.a static archive case $host_os in aix4.[23]|aix4.[23].*|aix[5-9]*) for ld_flag in $LDFLAGS; do case $ld_flag in *-brtl*) aix_use_runtimelinking=yes break ;; esac done if test svr4,no = "$with_aix_soname,$aix_use_runtimelinking"; then # With aix-soname=svr4, we create the lib.so.V shared archives only, # so we don't have lib.a shared libs to link our executables. # We have to force runtime linking in this case. aix_use_runtimelinking=yes LDFLAGS="$LDFLAGS -Wl,-brtl" fi ;; esac exp_sym_flag='-bexport' no_entry_flag='-bnoentry' fi # When large executables or shared objects are built, AIX ld can # have problems creating the table of contents. If linking a library # or program results in "error TOC overflow" add -mminimal-toc to # CXXFLAGS/CFLAGS for g++/gcc. In the cases where that is not # enough to fix the problem, add -Wl,-bbigtoc to LDFLAGS. archive_cmds_CXX='' hardcode_direct_CXX=yes hardcode_direct_absolute_CXX=yes hardcode_libdir_separator_CXX=':' link_all_deplibs_CXX=yes file_list_spec_CXX='$wl-f,' case $with_aix_soname,$aix_use_runtimelinking in aix,*) ;; # no import file svr4,* | *,yes) # use import file # The Import File defines what to hardcode. hardcode_direct_CXX=no hardcode_direct_absolute_CXX=no ;; esac if test yes = "$GXX"; then case $host_os in aix4.[012]|aix4.[012].*) # We only want to do this on AIX 4.2 and lower, the check # below for broken collect2 doesn't work under 4.3+ collect2name=`$CC -print-prog-name=collect2` if test -f "$collect2name" && strings "$collect2name" | $GREP resolve_lib_name >/dev/null then # We have reworked collect2 : else # We have old collect2 hardcode_direct_CXX=unsupported # It fails to find uninstalled libraries when the uninstalled # path is not listed in the libpath. Setting hardcode_minus_L # to unsupported forces relinking hardcode_minus_L_CXX=yes hardcode_libdir_flag_spec_CXX='-L$libdir' hardcode_libdir_separator_CXX= fi esac shared_flag='-shared' if test yes = "$aix_use_runtimelinking"; then shared_flag=$shared_flag' $wl-G' fi # Need to ensure runtime linking is disabled for the traditional # shared library, or the linker may eventually find shared libraries # /with/ Import File - we do not want to mix them. shared_flag_aix='-shared' shared_flag_svr4='-shared $wl-G' else # not using gcc if test ia64 = "$host_cpu"; then # VisualAge C++, Version 5.5 for AIX 5L for IA-64, Beta 3 Release # chokes on -Wl,-G. The following line is correct: shared_flag='-G' else if test yes = "$aix_use_runtimelinking"; then shared_flag='$wl-G' else shared_flag='$wl-bM:SRE' fi shared_flag_aix='$wl-bM:SRE' shared_flag_svr4='$wl-G' fi fi export_dynamic_flag_spec_CXX='$wl-bexpall' # It seems that -bexpall does not export symbols beginning with # underscore (_), so it is better to generate a list of symbols to # export. always_export_symbols_CXX=yes if test aix,yes = "$with_aix_soname,$aix_use_runtimelinking"; then # Warning - without using the other runtime loading flags (-brtl), # -berok will link without error, but may produce a broken library. # The "-G" linker flag allows undefined symbols. no_undefined_flag_CXX='-bernotok' # Determine the default libpath from the value encoded in an empty # executable. if test set = "${lt_cv_aix_libpath+set}"; then aix_libpath=$lt_cv_aix_libpath else if ${lt_cv_aix_libpath__CXX+:} false; then : $as_echo_n "(cached) " >&6 else cat confdefs.h - <<_ACEOF >conftest.$ac_ext /* end confdefs.h. */ int main () { ; return 0; } _ACEOF if ac_fn_cxx_try_link "$LINENO"; then : lt_aix_libpath_sed=' /Import File Strings/,/^$/ { /^0/ { s/^0 *\([^ ]*\) *$/\1/ p } }' lt_cv_aix_libpath__CXX=`dump -H conftest$ac_exeext 2>/dev/null | $SED -n -e "$lt_aix_libpath_sed"` # Check for a 64-bit object if we didn't find anything. if test -z "$lt_cv_aix_libpath__CXX"; then lt_cv_aix_libpath__CXX=`dump -HX64 conftest$ac_exeext 2>/dev/null | $SED -n -e "$lt_aix_libpath_sed"` fi fi rm -f core conftest.err conftest.$ac_objext \ conftest$ac_exeext conftest.$ac_ext if test -z "$lt_cv_aix_libpath__CXX"; then lt_cv_aix_libpath__CXX=/usr/lib:/lib fi fi aix_libpath=$lt_cv_aix_libpath__CXX fi hardcode_libdir_flag_spec_CXX='$wl-blibpath:$libdir:'"$aix_libpath" archive_expsym_cmds_CXX='$CC -o $output_objdir/$soname $libobjs $deplibs $wl'$no_entry_flag' $compiler_flags `if test -n "$allow_undefined_flag"; then func_echo_all "$wl$allow_undefined_flag"; else :; fi` $wl'$exp_sym_flag:\$export_symbols' '$shared_flag else if test ia64 = "$host_cpu"; then hardcode_libdir_flag_spec_CXX='$wl-R $libdir:/usr/lib:/lib' allow_undefined_flag_CXX="-z nodefs" archive_expsym_cmds_CXX="\$CC $shared_flag"' -o $output_objdir/$soname $libobjs $deplibs '"\$wl$no_entry_flag"' $compiler_flags $wl$allow_undefined_flag '"\$wl$exp_sym_flag:\$export_symbols" else # Determine the default libpath from the value encoded in an # empty executable. if test set = "${lt_cv_aix_libpath+set}"; then aix_libpath=$lt_cv_aix_libpath else if ${lt_cv_aix_libpath__CXX+:} false; then : $as_echo_n "(cached) " >&6 else cat confdefs.h - <<_ACEOF >conftest.$ac_ext /* end confdefs.h. */ int main () { ; return 0; } _ACEOF if ac_fn_cxx_try_link "$LINENO"; then : lt_aix_libpath_sed=' /Import File Strings/,/^$/ { /^0/ { s/^0 *\([^ ]*\) *$/\1/ p } }' lt_cv_aix_libpath__CXX=`dump -H conftest$ac_exeext 2>/dev/null | $SED -n -e "$lt_aix_libpath_sed"` # Check for a 64-bit object if we didn't find anything. if test -z "$lt_cv_aix_libpath__CXX"; then lt_cv_aix_libpath__CXX=`dump -HX64 conftest$ac_exeext 2>/dev/null | $SED -n -e "$lt_aix_libpath_sed"` fi fi rm -f core conftest.err conftest.$ac_objext \ conftest$ac_exeext conftest.$ac_ext if test -z "$lt_cv_aix_libpath__CXX"; then lt_cv_aix_libpath__CXX=/usr/lib:/lib fi fi aix_libpath=$lt_cv_aix_libpath__CXX fi hardcode_libdir_flag_spec_CXX='$wl-blibpath:$libdir:'"$aix_libpath" # Warning - without using the other run time loading flags, # -berok will link without error, but may produce a broken library. no_undefined_flag_CXX=' $wl-bernotok' allow_undefined_flag_CXX=' $wl-berok' if test yes = "$with_gnu_ld"; then # We only use this code for GNU lds that support --whole-archive. whole_archive_flag_spec_CXX='$wl--whole-archive$convenience $wl--no-whole-archive' else # Exported symbols can be pulled into shared objects from archives whole_archive_flag_spec_CXX='$convenience' fi archive_cmds_need_lc_CXX=yes archive_expsym_cmds_CXX='$RM -r $output_objdir/$realname.d~$MKDIR $output_objdir/$realname.d' # -brtl affects multiple linker settings, -berok does not and is overridden later compiler_flags_filtered='`func_echo_all "$compiler_flags " | $SED -e "s%-brtl\\([, ]\\)%-berok\\1%g"`' if test svr4 != "$with_aix_soname"; then # This is similar to how AIX traditionally builds its shared # libraries. Need -bnortl late, we may have -brtl in LDFLAGS. archive_expsym_cmds_CXX="$archive_expsym_cmds_CXX"'~$CC '$shared_flag_aix' -o $output_objdir/$realname.d/$soname $libobjs $deplibs $wl-bnoentry '$compiler_flags_filtered'$wl-bE:$export_symbols$allow_undefined_flag~$AR $AR_FLAGS $output_objdir/$libname$release.a $output_objdir/$realname.d/$soname' fi if test aix != "$with_aix_soname"; then archive_expsym_cmds_CXX="$archive_expsym_cmds_CXX"'~$CC '$shared_flag_svr4' -o $output_objdir/$realname.d/$shared_archive_member_spec.o $libobjs $deplibs $wl-bnoentry '$compiler_flags_filtered'$wl-bE:$export_symbols$allow_undefined_flag~$STRIP -e $output_objdir/$realname.d/$shared_archive_member_spec.o~( func_echo_all "#! $soname($shared_archive_member_spec.o)"; if test shr_64 = "$shared_archive_member_spec"; then func_echo_all "# 64"; else func_echo_all "# 32"; fi; cat $export_symbols ) > $output_objdir/$realname.d/$shared_archive_member_spec.imp~$AR $AR_FLAGS $output_objdir/$soname $output_objdir/$realname.d/$shared_archive_member_spec.o $output_objdir/$realname.d/$shared_archive_member_spec.imp' else # used by -dlpreopen to get the symbols archive_expsym_cmds_CXX="$archive_expsym_cmds_CXX"'~$MV $output_objdir/$realname.d/$soname $output_objdir' fi archive_expsym_cmds_CXX="$archive_expsym_cmds_CXX"'~$RM -r $output_objdir/$realname.d' fi fi ;; beos*) if $LD --help 2>&1 | $GREP ': supported targets:.* elf' > /dev/null; then allow_undefined_flag_CXX=unsupported # Joseph Beckenbach says some releases of gcc # support --undefined. This deserves some investigation. FIXME archive_cmds_CXX='$CC -nostart $libobjs $deplibs $compiler_flags $wl-soname $wl$soname -o $lib' else ld_shlibs_CXX=no fi ;; chorus*) case $cc_basename in *) # FIXME: insert proper C++ library support ld_shlibs_CXX=no ;; esac ;; cygwin* | mingw* | pw32* | cegcc*) case $GXX,$cc_basename in ,cl* | no,cl*) # Native MSVC # hardcode_libdir_flag_spec is actually meaningless, as there is # no search path for DLLs. hardcode_libdir_flag_spec_CXX=' ' allow_undefined_flag_CXX=unsupported always_export_symbols_CXX=yes file_list_spec_CXX='@' # Tell ltmain to make .lib files, not .a files. libext=lib # Tell ltmain to make .dll files, not .so files. shrext_cmds=.dll # FIXME: Setting linknames here is a bad hack. archive_cmds_CXX='$CC -o $output_objdir/$soname $libobjs $compiler_flags $deplibs -Wl,-DLL,-IMPLIB:"$tool_output_objdir$libname.dll.lib"~linknames=' archive_expsym_cmds_CXX='if test DEF = "`$SED -n -e '\''s/^[ ]*//'\'' -e '\''/^\(;.*\)*$/d'\'' -e '\''s/^\(EXPORTS\|LIBRARY\)\([ ].*\)*$/DEF/p'\'' -e q $export_symbols`" ; then cp "$export_symbols" "$output_objdir/$soname.def"; echo "$tool_output_objdir$soname.def" > "$output_objdir/$soname.exp"; else $SED -e '\''s/^/-link -EXPORT:/'\'' < $export_symbols > $output_objdir/$soname.exp; fi~ $CC -o $tool_output_objdir$soname $libobjs $compiler_flags $deplibs "@$tool_output_objdir$soname.exp" -Wl,-DLL,-IMPLIB:"$tool_output_objdir$libname.dll.lib"~ linknames=' # The linker will not automatically build a static lib if we build a DLL. # _LT_TAGVAR(old_archive_from_new_cmds, CXX)='true' enable_shared_with_static_runtimes_CXX=yes # Don't use ranlib old_postinstall_cmds_CXX='chmod 644 $oldlib' postlink_cmds_CXX='lt_outputfile="@OUTPUT@"~ lt_tool_outputfile="@TOOL_OUTPUT@"~ case $lt_outputfile in *.exe|*.EXE) ;; *) lt_outputfile=$lt_outputfile.exe lt_tool_outputfile=$lt_tool_outputfile.exe ;; esac~ func_to_tool_file "$lt_outputfile"~ if test : != "$MANIFEST_TOOL" && test -f "$lt_outputfile.manifest"; then $MANIFEST_TOOL -manifest "$lt_tool_outputfile.manifest" -outputresource:"$lt_tool_outputfile" || exit 1; $RM "$lt_outputfile.manifest"; fi' ;; *) # g++ # _LT_TAGVAR(hardcode_libdir_flag_spec, CXX) is actually meaningless, # as there is no search path for DLLs. hardcode_libdir_flag_spec_CXX='-L$libdir' export_dynamic_flag_spec_CXX='$wl--export-all-symbols' allow_undefined_flag_CXX=unsupported always_export_symbols_CXX=no enable_shared_with_static_runtimes_CXX=yes if $LD --help 2>&1 | $GREP 'auto-import' > /dev/null; then archive_cmds_CXX='$CC -shared -nostdlib $predep_objects $libobjs $deplibs $postdep_objects $compiler_flags -o $output_objdir/$soname $wl--enable-auto-image-base -Xlinker --out-implib -Xlinker $lib' # If the export-symbols file already is a .def file, use it as # is; otherwise, prepend EXPORTS... archive_expsym_cmds_CXX='if test DEF = "`$SED -n -e '\''s/^[ ]*//'\'' -e '\''/^\(;.*\)*$/d'\'' -e '\''s/^\(EXPORTS\|LIBRARY\)\([ ].*\)*$/DEF/p'\'' -e q $export_symbols`" ; then cp $export_symbols $output_objdir/$soname.def; else echo EXPORTS > $output_objdir/$soname.def; cat $export_symbols >> $output_objdir/$soname.def; fi~ $CC -shared -nostdlib $output_objdir/$soname.def $predep_objects $libobjs $deplibs $postdep_objects $compiler_flags -o $output_objdir/$soname $wl--enable-auto-image-base -Xlinker --out-implib -Xlinker $lib' else ld_shlibs_CXX=no fi ;; esac ;; darwin* | rhapsody*) archive_cmds_need_lc_CXX=no hardcode_direct_CXX=no hardcode_automatic_CXX=yes hardcode_shlibpath_var_CXX=unsupported if test yes = "$lt_cv_ld_force_load"; then whole_archive_flag_spec_CXX='`for conv in $convenience\"\"; do test -n \"$conv\" && new_convenience=\"$new_convenience $wl-force_load,$conv\"; done; func_echo_all \"$new_convenience\"`' else whole_archive_flag_spec_CXX='' fi link_all_deplibs_CXX=yes allow_undefined_flag_CXX=$_lt_dar_allow_undefined case $cc_basename in ifort*|nagfor*) _lt_dar_can_shared=yes ;; *) _lt_dar_can_shared=$GCC ;; esac if test yes = "$_lt_dar_can_shared"; then output_verbose_link_cmd=func_echo_all archive_cmds_CXX="\$CC -dynamiclib \$allow_undefined_flag -o \$lib \$libobjs \$deplibs \$compiler_flags -install_name \$rpath/\$soname \$verstring $_lt_dar_single_mod$_lt_dsymutil" module_cmds_CXX="\$CC \$allow_undefined_flag -o \$lib -bundle \$libobjs \$deplibs \$compiler_flags$_lt_dsymutil" archive_expsym_cmds_CXX="sed 's|^|_|' < \$export_symbols > \$output_objdir/\$libname-symbols.expsym~\$CC -dynamiclib \$allow_undefined_flag -o \$lib \$libobjs \$deplibs \$compiler_flags -install_name \$rpath/\$soname \$verstring $_lt_dar_single_mod$_lt_dar_export_syms$_lt_dsymutil" module_expsym_cmds_CXX="sed -e 's|^|_|' < \$export_symbols > \$output_objdir/\$libname-symbols.expsym~\$CC \$allow_undefined_flag -o \$lib -bundle \$libobjs \$deplibs \$compiler_flags$_lt_dar_export_syms$_lt_dsymutil" if test yes != "$lt_cv_apple_cc_single_mod"; then archive_cmds_CXX="\$CC -r -keep_private_externs -nostdlib -o \$lib-master.o \$libobjs~\$CC -dynamiclib \$allow_undefined_flag -o \$lib \$lib-master.o \$deplibs \$compiler_flags -install_name \$rpath/\$soname \$verstring$_lt_dsymutil" archive_expsym_cmds_CXX="sed 's|^|_|' < \$export_symbols > \$output_objdir/\$libname-symbols.expsym~\$CC -r -keep_private_externs -nostdlib -o \$lib-master.o \$libobjs~\$CC -dynamiclib \$allow_undefined_flag -o \$lib \$lib-master.o \$deplibs \$compiler_flags -install_name \$rpath/\$soname \$verstring$_lt_dar_export_syms$_lt_dsymutil" fi else ld_shlibs_CXX=no fi ;; os2*) hardcode_libdir_flag_spec_CXX='-L$libdir' hardcode_minus_L_CXX=yes allow_undefined_flag_CXX=unsupported shrext_cmds=.dll archive_cmds_CXX='$ECHO "LIBRARY ${soname%$shared_ext} INITINSTANCE TERMINSTANCE" > $output_objdir/$libname.def~ $ECHO "DESCRIPTION \"$libname\"" >> $output_objdir/$libname.def~ $ECHO "DATA MULTIPLE NONSHARED" >> $output_objdir/$libname.def~ $ECHO EXPORTS >> $output_objdir/$libname.def~ emxexp $libobjs | $SED /"_DLL_InitTerm"/d >> $output_objdir/$libname.def~ $CC -Zdll -Zcrtdll -o $output_objdir/$soname $libobjs $deplibs $compiler_flags $output_objdir/$libname.def~ emximp -o $lib $output_objdir/$libname.def' archive_expsym_cmds_CXX='$ECHO "LIBRARY ${soname%$shared_ext} INITINSTANCE TERMINSTANCE" > $output_objdir/$libname.def~ $ECHO "DESCRIPTION \"$libname\"" >> $output_objdir/$libname.def~ $ECHO "DATA MULTIPLE NONSHARED" >> $output_objdir/$libname.def~ $ECHO EXPORTS >> $output_objdir/$libname.def~ prefix_cmds="$SED"~ if test EXPORTS = "`$SED 1q $export_symbols`"; then prefix_cmds="$prefix_cmds -e 1d"; fi~ prefix_cmds="$prefix_cmds -e \"s/^\(.*\)$/_\1/g\""~ cat $export_symbols | $prefix_cmds >> $output_objdir/$libname.def~ $CC -Zdll -Zcrtdll -o $output_objdir/$soname $libobjs $deplibs $compiler_flags $output_objdir/$libname.def~ emximp -o $lib $output_objdir/$libname.def' old_archive_From_new_cmds_CXX='emximp -o $output_objdir/${libname}_dll.a $output_objdir/$libname.def' enable_shared_with_static_runtimes_CXX=yes ;; dgux*) case $cc_basename in ec++*) # FIXME: insert proper C++ library support ld_shlibs_CXX=no ;; ghcx*) # Green Hills C++ Compiler # FIXME: insert proper C++ library support ld_shlibs_CXX=no ;; *) # FIXME: insert proper C++ library support ld_shlibs_CXX=no ;; esac ;; freebsd2.*) # C++ shared libraries reported to be fairly broken before # switch to ELF ld_shlibs_CXX=no ;; freebsd-elf*) archive_cmds_need_lc_CXX=no ;; freebsd* | dragonfly*) # FreeBSD 3 and later use GNU C++ and GNU ld with standard ELF # conventions ld_shlibs_CXX=yes ;; haiku*) archive_cmds_CXX='$CC -shared $libobjs $deplibs $compiler_flags $wl-soname $wl$soname -o $lib' link_all_deplibs_CXX=yes ;; hpux9*) hardcode_libdir_flag_spec_CXX='$wl+b $wl$libdir' hardcode_libdir_separator_CXX=: export_dynamic_flag_spec_CXX='$wl-E' hardcode_direct_CXX=yes hardcode_minus_L_CXX=yes # Not in the search PATH, # but as the default # location of the library. case $cc_basename in CC*) # FIXME: insert proper C++ library support ld_shlibs_CXX=no ;; aCC*) archive_cmds_CXX='$RM $output_objdir/$soname~$CC -b $wl+b $wl$install_libdir -o $output_objdir/$soname $predep_objects $libobjs $deplibs $postdep_objects $compiler_flags~test "x$output_objdir/$soname" = "x$lib" || mv $output_objdir/$soname $lib' # Commands to make compiler produce verbose output that lists # what "hidden" libraries, object files and flags are used when # linking a shared library. # # There doesn't appear to be a way to prevent this compiler from # explicitly linking system object files so we need to strip them # from the output so that they don't get included in the library # dependencies. output_verbose_link_cmd='templist=`($CC -b $CFLAGS -v conftest.$objext 2>&1) | $EGREP " \-L"`; list= ; for z in $templist; do case $z in conftest.$objext) list="$list $z";; *.$objext);; *) list="$list $z";;esac; done; func_echo_all "$list"' ;; *) if test yes = "$GXX"; then archive_cmds_CXX='$RM $output_objdir/$soname~$CC -shared -nostdlib $pic_flag $wl+b $wl$install_libdir -o $output_objdir/$soname $predep_objects $libobjs $deplibs $postdep_objects $compiler_flags~test "x$output_objdir/$soname" = "x$lib" || mv $output_objdir/$soname $lib' else # FIXME: insert proper C++ library support ld_shlibs_CXX=no fi ;; esac ;; hpux10*|hpux11*) if test no = "$with_gnu_ld"; then hardcode_libdir_flag_spec_CXX='$wl+b $wl$libdir' hardcode_libdir_separator_CXX=: case $host_cpu in hppa*64*|ia64*) ;; *) export_dynamic_flag_spec_CXX='$wl-E' ;; esac fi case $host_cpu in hppa*64*|ia64*) hardcode_direct_CXX=no hardcode_shlibpath_var_CXX=no ;; *) hardcode_direct_CXX=yes hardcode_direct_absolute_CXX=yes hardcode_minus_L_CXX=yes # Not in the search PATH, # but as the default # location of the library. ;; esac case $cc_basename in CC*) # FIXME: insert proper C++ library support ld_shlibs_CXX=no ;; aCC*) case $host_cpu in hppa*64*) archive_cmds_CXX='$CC -b $wl+h $wl$soname -o $lib $predep_objects $libobjs $deplibs $postdep_objects $compiler_flags' ;; ia64*) archive_cmds_CXX='$CC -b $wl+h $wl$soname $wl+nodefaultrpath -o $lib $predep_objects $libobjs $deplibs $postdep_objects $compiler_flags' ;; *) archive_cmds_CXX='$CC -b $wl+h $wl$soname $wl+b $wl$install_libdir -o $lib $predep_objects $libobjs $deplibs $postdep_objects $compiler_flags' ;; esac # Commands to make compiler produce verbose output that lists # what "hidden" libraries, object files and flags are used when # linking a shared library. # # There doesn't appear to be a way to prevent this compiler from # explicitly linking system object files so we need to strip them # from the output so that they don't get included in the library # dependencies. output_verbose_link_cmd='templist=`($CC -b $CFLAGS -v conftest.$objext 2>&1) | $GREP " \-L"`; list= ; for z in $templist; do case $z in conftest.$objext) list="$list $z";; *.$objext);; *) list="$list $z";;esac; done; func_echo_all "$list"' ;; *) if test yes = "$GXX"; then if test no = "$with_gnu_ld"; then case $host_cpu in hppa*64*) archive_cmds_CXX='$CC -shared -nostdlib -fPIC $wl+h $wl$soname -o $lib $predep_objects $libobjs $deplibs $postdep_objects $compiler_flags' ;; ia64*) archive_cmds_CXX='$CC -shared -nostdlib $pic_flag $wl+h $wl$soname $wl+nodefaultrpath -o $lib $predep_objects $libobjs $deplibs $postdep_objects $compiler_flags' ;; *) archive_cmds_CXX='$CC -shared -nostdlib $pic_flag $wl+h $wl$soname $wl+b $wl$install_libdir -o $lib $predep_objects $libobjs $deplibs $postdep_objects $compiler_flags' ;; esac fi else # FIXME: insert proper C++ library support ld_shlibs_CXX=no fi ;; esac ;; interix[3-9]*) hardcode_direct_CXX=no hardcode_shlibpath_var_CXX=no hardcode_libdir_flag_spec_CXX='$wl-rpath,$libdir' export_dynamic_flag_spec_CXX='$wl-E' # Hack: On Interix 3.x, we cannot compile PIC because of a broken gcc. # Instead, shared libraries are loaded at an image base (0x10000000 by # default) and relocated if they conflict, which is a slow very memory # consuming and fragmenting process. To avoid this, we pick a random, # 256 KiB-aligned image base between 0x50000000 and 0x6FFC0000 at link # time. Moving up from 0x10000000 also allows more sbrk(2) space. archive_cmds_CXX='$CC -shared $pic_flag $libobjs $deplibs $compiler_flags $wl-h,$soname $wl--image-base,`expr ${RANDOM-$$} % 4096 / 2 \* 262144 + 1342177280` -o $lib' archive_expsym_cmds_CXX='sed "s|^|_|" $export_symbols >$output_objdir/$soname.expsym~$CC -shared $pic_flag $libobjs $deplibs $compiler_flags $wl-h,$soname $wl--retain-symbols-file,$output_objdir/$soname.expsym $wl--image-base,`expr ${RANDOM-$$} % 4096 / 2 \* 262144 + 1342177280` -o $lib' ;; irix5* | irix6*) case $cc_basename in CC*) # SGI C++ archive_cmds_CXX='$CC -shared -all -multigot $predep_objects $libobjs $deplibs $postdep_objects $compiler_flags -soname $soname `test -n "$verstring" && func_echo_all "-set_version $verstring"` -update_registry $output_objdir/so_locations -o $lib' # Archives containing C++ object files must be created using # "CC -ar", where "CC" is the IRIX C++ compiler. This is # necessary to make sure instantiated templates are included # in the archive. old_archive_cmds_CXX='$CC -ar -WR,-u -o $oldlib $oldobjs' ;; *) if test yes = "$GXX"; then if test no = "$with_gnu_ld"; then archive_cmds_CXX='$CC -shared $pic_flag -nostdlib $predep_objects $libobjs $deplibs $postdep_objects $compiler_flags $wl-soname $wl$soname `test -n "$verstring" && func_echo_all "$wl-set_version $wl$verstring"` $wl-update_registry $wl$output_objdir/so_locations -o $lib' else archive_cmds_CXX='$CC -shared $pic_flag -nostdlib $predep_objects $libobjs $deplibs $postdep_objects $compiler_flags $wl-soname $wl$soname `test -n "$verstring" && func_echo_all "$wl-set_version $wl$verstring"` -o $lib' fi fi link_all_deplibs_CXX=yes ;; esac hardcode_libdir_flag_spec_CXX='$wl-rpath $wl$libdir' hardcode_libdir_separator_CXX=: inherit_rpath_CXX=yes ;; linux* | k*bsd*-gnu | kopensolaris*-gnu | gnu*) case $cc_basename in KCC*) # Kuck and Associates, Inc. (KAI) C++ Compiler # KCC will only create a shared library if the output file # ends with ".so" (or ".sl" for HP-UX), so rename the library # to its proper name (with version) after linking. archive_cmds_CXX='tempext=`echo $shared_ext | $SED -e '\''s/\([^()0-9A-Za-z{}]\)/\\\\\1/g'\''`; templib=`echo $lib | $SED -e "s/\$tempext\..*/.so/"`; $CC $predep_objects $libobjs $deplibs $postdep_objects $compiler_flags --soname $soname -o \$templib; mv \$templib $lib' archive_expsym_cmds_CXX='tempext=`echo $shared_ext | $SED -e '\''s/\([^()0-9A-Za-z{}]\)/\\\\\1/g'\''`; templib=`echo $lib | $SED -e "s/\$tempext\..*/.so/"`; $CC $predep_objects $libobjs $deplibs $postdep_objects $compiler_flags --soname $soname -o \$templib $wl-retain-symbols-file,$export_symbols; mv \$templib $lib' # Commands to make compiler produce verbose output that lists # what "hidden" libraries, object files and flags are used when # linking a shared library. # # There doesn't appear to be a way to prevent this compiler from # explicitly linking system object files so we need to strip them # from the output so that they don't get included in the library # dependencies. output_verbose_link_cmd='templist=`$CC $CFLAGS -v conftest.$objext -o libconftest$shared_ext 2>&1 | $GREP "ld"`; rm -f libconftest$shared_ext; list= ; for z in $templist; do case $z in conftest.$objext) list="$list $z";; *.$objext);; *) list="$list $z";;esac; done; func_echo_all "$list"' hardcode_libdir_flag_spec_CXX='$wl-rpath,$libdir' export_dynamic_flag_spec_CXX='$wl--export-dynamic' # Archives containing C++ object files must be created using # "CC -Bstatic", where "CC" is the KAI C++ compiler. old_archive_cmds_CXX='$CC -Bstatic -o $oldlib $oldobjs' ;; icpc* | ecpc* ) # Intel C++ with_gnu_ld=yes # version 8.0 and above of icpc choke on multiply defined symbols # if we add $predep_objects and $postdep_objects, however 7.1 and # earlier do not add the objects themselves. case `$CC -V 2>&1` in *"Version 7."*) archive_cmds_CXX='$CC -shared $predep_objects $libobjs $deplibs $postdep_objects $compiler_flags $wl-soname $wl$soname -o $lib' archive_expsym_cmds_CXX='$CC -shared $predep_objects $libobjs $deplibs $postdep_objects $compiler_flags $wl-soname $wl$soname $wl-retain-symbols-file $wl$export_symbols -o $lib' ;; *) # Version 8.0 or newer tmp_idyn= case $host_cpu in ia64*) tmp_idyn=' -i_dynamic';; esac archive_cmds_CXX='$CC -shared'"$tmp_idyn"' $libobjs $deplibs $compiler_flags $wl-soname $wl$soname -o $lib' archive_expsym_cmds_CXX='$CC -shared'"$tmp_idyn"' $libobjs $deplibs $compiler_flags $wl-soname $wl$soname $wl-retain-symbols-file $wl$export_symbols -o $lib' ;; esac archive_cmds_need_lc_CXX=no hardcode_libdir_flag_spec_CXX='$wl-rpath,$libdir' export_dynamic_flag_spec_CXX='$wl--export-dynamic' whole_archive_flag_spec_CXX='$wl--whole-archive$convenience $wl--no-whole-archive' ;; pgCC* | pgcpp*) # Portland Group C++ compiler case `$CC -V` in *pgCC\ [1-5].* | *pgcpp\ [1-5].*) prelink_cmds_CXX='tpldir=Template.dir~ rm -rf $tpldir~ $CC --prelink_objects --instantiation_dir $tpldir $objs $libobjs $compile_deplibs~ compile_command="$compile_command `find $tpldir -name \*.o | sort | $NL2SP`"' old_archive_cmds_CXX='tpldir=Template.dir~ rm -rf $tpldir~ $CC --prelink_objects --instantiation_dir $tpldir $oldobjs$old_deplibs~ $AR $AR_FLAGS $oldlib$oldobjs$old_deplibs `find $tpldir -name \*.o | sort | $NL2SP`~ $RANLIB $oldlib' archive_cmds_CXX='tpldir=Template.dir~ rm -rf $tpldir~ $CC --prelink_objects --instantiation_dir $tpldir $predep_objects $libobjs $deplibs $convenience $postdep_objects~ $CC -shared $pic_flag $predep_objects $libobjs $deplibs `find $tpldir -name \*.o | sort | $NL2SP` $postdep_objects $compiler_flags $wl-soname $wl$soname -o $lib' archive_expsym_cmds_CXX='tpldir=Template.dir~ rm -rf $tpldir~ $CC --prelink_objects --instantiation_dir $tpldir $predep_objects $libobjs $deplibs $convenience $postdep_objects~ $CC -shared $pic_flag $predep_objects $libobjs $deplibs `find $tpldir -name \*.o | sort | $NL2SP` $postdep_objects $compiler_flags $wl-soname $wl$soname $wl-retain-symbols-file $wl$export_symbols -o $lib' ;; *) # Version 6 and above use weak symbols archive_cmds_CXX='$CC -shared $pic_flag $predep_objects $libobjs $deplibs $postdep_objects $compiler_flags $wl-soname $wl$soname -o $lib' archive_expsym_cmds_CXX='$CC -shared $pic_flag $predep_objects $libobjs $deplibs $postdep_objects $compiler_flags $wl-soname $wl$soname $wl-retain-symbols-file $wl$export_symbols -o $lib' ;; esac hardcode_libdir_flag_spec_CXX='$wl--rpath $wl$libdir' export_dynamic_flag_spec_CXX='$wl--export-dynamic' whole_archive_flag_spec_CXX='$wl--whole-archive`for conv in $convenience\"\"; do test -n \"$conv\" && new_convenience=\"$new_convenience,$conv\"; done; func_echo_all \"$new_convenience\"` $wl--no-whole-archive' ;; cxx*) # Compaq C++ archive_cmds_CXX='$CC -shared $predep_objects $libobjs $deplibs $postdep_objects $compiler_flags $wl-soname $wl$soname -o $lib' archive_expsym_cmds_CXX='$CC -shared $predep_objects $libobjs $deplibs $postdep_objects $compiler_flags $wl-soname $wl$soname -o $lib $wl-retain-symbols-file $wl$export_symbols' runpath_var=LD_RUN_PATH hardcode_libdir_flag_spec_CXX='-rpath $libdir' hardcode_libdir_separator_CXX=: # Commands to make compiler produce verbose output that lists # what "hidden" libraries, object files and flags are used when # linking a shared library. # # There doesn't appear to be a way to prevent this compiler from # explicitly linking system object files so we need to strip them # from the output so that they don't get included in the library # dependencies. output_verbose_link_cmd='templist=`$CC -shared $CFLAGS -v conftest.$objext 2>&1 | $GREP "ld"`; templist=`func_echo_all "$templist" | $SED "s/\(^.*ld.*\)\( .*ld .*$\)/\1/"`; list= ; for z in $templist; do case $z in conftest.$objext) list="$list $z";; *.$objext);; *) list="$list $z";;esac; done; func_echo_all "X$list" | $Xsed' ;; xl* | mpixl* | bgxl*) # IBM XL 8.0 on PPC, with GNU ld hardcode_libdir_flag_spec_CXX='$wl-rpath $wl$libdir' export_dynamic_flag_spec_CXX='$wl--export-dynamic' archive_cmds_CXX='$CC -qmkshrobj $libobjs $deplibs $compiler_flags $wl-soname $wl$soname -o $lib' if test yes = "$supports_anon_versioning"; then archive_expsym_cmds_CXX='echo "{ global:" > $output_objdir/$libname.ver~ cat $export_symbols | sed -e "s/\(.*\)/\1;/" >> $output_objdir/$libname.ver~ echo "local: *; };" >> $output_objdir/$libname.ver~ $CC -qmkshrobj $libobjs $deplibs $compiler_flags $wl-soname $wl$soname $wl-version-script $wl$output_objdir/$libname.ver -o $lib' fi ;; *) case `$CC -V 2>&1 | sed 5q` in *Sun\ C*) # Sun C++ 5.9 no_undefined_flag_CXX=' -zdefs' archive_cmds_CXX='$CC -G$allow_undefined_flag -h$soname -o $lib $predep_objects $libobjs $deplibs $postdep_objects $compiler_flags' archive_expsym_cmds_CXX='$CC -G$allow_undefined_flag -h$soname -o $lib $predep_objects $libobjs $deplibs $postdep_objects $compiler_flags $wl-retain-symbols-file $wl$export_symbols' hardcode_libdir_flag_spec_CXX='-R$libdir' whole_archive_flag_spec_CXX='$wl--whole-archive`new_convenience=; for conv in $convenience\"\"; do test -z \"$conv\" || new_convenience=\"$new_convenience,$conv\"; done; func_echo_all \"$new_convenience\"` $wl--no-whole-archive' compiler_needs_object_CXX=yes # Not sure whether something based on # $CC $CFLAGS -v conftest.$objext -o libconftest$shared_ext 2>&1 # would be better. output_verbose_link_cmd='func_echo_all' # Archives containing C++ object files must be created using # "CC -xar", where "CC" is the Sun C++ compiler. This is # necessary to make sure instantiated templates are included # in the archive. old_archive_cmds_CXX='$CC -xar -o $oldlib $oldobjs' ;; esac ;; esac ;; lynxos*) # FIXME: insert proper C++ library support ld_shlibs_CXX=no ;; m88k*) # FIXME: insert proper C++ library support ld_shlibs_CXX=no ;; mvs*) case $cc_basename in cxx*) # FIXME: insert proper C++ library support ld_shlibs_CXX=no ;; *) # FIXME: insert proper C++ library support ld_shlibs_CXX=no ;; esac ;; netbsd*) if echo __ELF__ | $CC -E - | $GREP __ELF__ >/dev/null; then archive_cmds_CXX='$LD -Bshareable -o $lib $predep_objects $libobjs $deplibs $postdep_objects $linker_flags' wlarc= hardcode_libdir_flag_spec_CXX='-R$libdir' hardcode_direct_CXX=yes hardcode_shlibpath_var_CXX=no fi # Workaround some broken pre-1.5 toolchains output_verbose_link_cmd='$CC -shared $CFLAGS -v conftest.$objext 2>&1 | $GREP conftest.$objext | $SED -e "s:-lgcc -lc -lgcc::"' ;; *nto* | *qnx*) ld_shlibs_CXX=yes ;; openbsd* | bitrig*) if test -f /usr/libexec/ld.so; then hardcode_direct_CXX=yes hardcode_shlibpath_var_CXX=no hardcode_direct_absolute_CXX=yes archive_cmds_CXX='$CC -shared $pic_flag $predep_objects $libobjs $deplibs $postdep_objects $compiler_flags -o $lib' hardcode_libdir_flag_spec_CXX='$wl-rpath,$libdir' if test -z "`echo __ELF__ | $CC -E - | grep __ELF__`"; then archive_expsym_cmds_CXX='$CC -shared $pic_flag $predep_objects $libobjs $deplibs $postdep_objects $compiler_flags $wl-retain-symbols-file,$export_symbols -o $lib' export_dynamic_flag_spec_CXX='$wl-E' whole_archive_flag_spec_CXX=$wlarc'--whole-archive$convenience '$wlarc'--no-whole-archive' fi output_verbose_link_cmd=func_echo_all else ld_shlibs_CXX=no fi ;; osf3* | osf4* | osf5*) case $cc_basename in KCC*) # Kuck and Associates, Inc. (KAI) C++ Compiler # KCC will only create a shared library if the output file # ends with ".so" (or ".sl" for HP-UX), so rename the library # to its proper name (with version) after linking. archive_cmds_CXX='tempext=`echo $shared_ext | $SED -e '\''s/\([^()0-9A-Za-z{}]\)/\\\\\1/g'\''`; templib=`echo "$lib" | $SED -e "s/\$tempext\..*/.so/"`; $CC $predep_objects $libobjs $deplibs $postdep_objects $compiler_flags --soname $soname -o \$templib; mv \$templib $lib' hardcode_libdir_flag_spec_CXX='$wl-rpath,$libdir' hardcode_libdir_separator_CXX=: # Archives containing C++ object files must be created using # the KAI C++ compiler. case $host in osf3*) old_archive_cmds_CXX='$CC -Bstatic -o $oldlib $oldobjs' ;; *) old_archive_cmds_CXX='$CC -o $oldlib $oldobjs' ;; esac ;; RCC*) # Rational C++ 2.4.1 # FIXME: insert proper C++ library support ld_shlibs_CXX=no ;; cxx*) case $host in osf3*) allow_undefined_flag_CXX=' $wl-expect_unresolved $wl\*' archive_cmds_CXX='$CC -shared$allow_undefined_flag $predep_objects $libobjs $deplibs $postdep_objects $compiler_flags $wl-soname $soname `test -n "$verstring" && func_echo_all "$wl-set_version $verstring"` -update_registry $output_objdir/so_locations -o $lib' hardcode_libdir_flag_spec_CXX='$wl-rpath $wl$libdir' ;; *) allow_undefined_flag_CXX=' -expect_unresolved \*' archive_cmds_CXX='$CC -shared$allow_undefined_flag $predep_objects $libobjs $deplibs $postdep_objects $compiler_flags -msym -soname $soname `test -n "$verstring" && func_echo_all "-set_version $verstring"` -update_registry $output_objdir/so_locations -o $lib' archive_expsym_cmds_CXX='for i in `cat $export_symbols`; do printf "%s %s\\n" -exported_symbol "\$i" >> $lib.exp; done~ echo "-hidden">> $lib.exp~ $CC -shared$allow_undefined_flag $predep_objects $libobjs $deplibs $postdep_objects $compiler_flags -msym -soname $soname $wl-input $wl$lib.exp `test -n "$verstring" && $ECHO "-set_version $verstring"` -update_registry $output_objdir/so_locations -o $lib~ $RM $lib.exp' hardcode_libdir_flag_spec_CXX='-rpath $libdir' ;; esac hardcode_libdir_separator_CXX=: # Commands to make compiler produce verbose output that lists # what "hidden" libraries, object files and flags are used when # linking a shared library. # # There doesn't appear to be a way to prevent this compiler from # explicitly linking system object files so we need to strip them # from the output so that they don't get included in the library # dependencies. output_verbose_link_cmd='templist=`$CC -shared $CFLAGS -v conftest.$objext 2>&1 | $GREP "ld" | $GREP -v "ld:"`; templist=`func_echo_all "$templist" | $SED "s/\(^.*ld.*\)\( .*ld.*$\)/\1/"`; list= ; for z in $templist; do case $z in conftest.$objext) list="$list $z";; *.$objext);; *) list="$list $z";;esac; done; func_echo_all "$list"' ;; *) if test yes,no = "$GXX,$with_gnu_ld"; then allow_undefined_flag_CXX=' $wl-expect_unresolved $wl\*' case $host in osf3*) archive_cmds_CXX='$CC -shared -nostdlib $allow_undefined_flag $predep_objects $libobjs $deplibs $postdep_objects $compiler_flags $wl-soname $wl$soname `test -n "$verstring" && func_echo_all "$wl-set_version $wl$verstring"` $wl-update_registry $wl$output_objdir/so_locations -o $lib' ;; *) archive_cmds_CXX='$CC -shared $pic_flag -nostdlib $allow_undefined_flag $predep_objects $libobjs $deplibs $postdep_objects $compiler_flags $wl-msym $wl-soname $wl$soname `test -n "$verstring" && func_echo_all "$wl-set_version $wl$verstring"` $wl-update_registry $wl$output_objdir/so_locations -o $lib' ;; esac hardcode_libdir_flag_spec_CXX='$wl-rpath $wl$libdir' hardcode_libdir_separator_CXX=: # Commands to make compiler produce verbose output that lists # what "hidden" libraries, object files and flags are used when # linking a shared library. output_verbose_link_cmd='$CC -shared $CFLAGS -v conftest.$objext 2>&1 | $GREP -v "^Configured with:" | $GREP " \-L"' else # FIXME: insert proper C++ library support ld_shlibs_CXX=no fi ;; esac ;; psos*) # FIXME: insert proper C++ library support ld_shlibs_CXX=no ;; sunos4*) case $cc_basename in CC*) # Sun C++ 4.x # FIXME: insert proper C++ library support ld_shlibs_CXX=no ;; lcc*) # Lucid # FIXME: insert proper C++ library support ld_shlibs_CXX=no ;; *) # FIXME: insert proper C++ library support ld_shlibs_CXX=no ;; esac ;; solaris*) case $cc_basename in CC* | sunCC*) # Sun C++ 4.2, 5.x and Centerline C++ archive_cmds_need_lc_CXX=yes no_undefined_flag_CXX=' -zdefs' archive_cmds_CXX='$CC -G$allow_undefined_flag -h$soname -o $lib $predep_objects $libobjs $deplibs $postdep_objects $compiler_flags' archive_expsym_cmds_CXX='echo "{ global:" > $lib.exp~cat $export_symbols | $SED -e "s/\(.*\)/\1;/" >> $lib.exp~echo "local: *; };" >> $lib.exp~ $CC -G$allow_undefined_flag $wl-M $wl$lib.exp -h$soname -o $lib $predep_objects $libobjs $deplibs $postdep_objects $compiler_flags~$RM $lib.exp' hardcode_libdir_flag_spec_CXX='-R$libdir' hardcode_shlibpath_var_CXX=no case $host_os in solaris2.[0-5] | solaris2.[0-5].*) ;; *) # The compiler driver will combine and reorder linker options, # but understands '-z linker_flag'. # Supported since Solaris 2.6 (maybe 2.5.1?) whole_archive_flag_spec_CXX='-z allextract$convenience -z defaultextract' ;; esac link_all_deplibs_CXX=yes output_verbose_link_cmd='func_echo_all' # Archives containing C++ object files must be created using # "CC -xar", where "CC" is the Sun C++ compiler. This is # necessary to make sure instantiated templates are included # in the archive. old_archive_cmds_CXX='$CC -xar -o $oldlib $oldobjs' ;; gcx*) # Green Hills C++ Compiler archive_cmds_CXX='$CC -shared $predep_objects $libobjs $deplibs $postdep_objects $compiler_flags $wl-h $wl$soname -o $lib' # The C++ compiler must be used to create the archive. old_archive_cmds_CXX='$CC $LDFLAGS -archive -o $oldlib $oldobjs' ;; *) # GNU C++ compiler with Solaris linker if test yes,no = "$GXX,$with_gnu_ld"; then no_undefined_flag_CXX=' $wl-z ${wl}defs' if $CC --version | $GREP -v '^2\.7' > /dev/null; then archive_cmds_CXX='$CC -shared $pic_flag -nostdlib $predep_objects $libobjs $deplibs $postdep_objects $compiler_flags $wl-h $wl$soname -o $lib' archive_expsym_cmds_CXX='echo "{ global:" > $lib.exp~cat $export_symbols | $SED -e "s/\(.*\)/\1;/" >> $lib.exp~echo "local: *; };" >> $lib.exp~ $CC -shared $pic_flag -nostdlib $wl-M $wl$lib.exp $wl-h $wl$soname -o $lib $predep_objects $libobjs $deplibs $postdep_objects $compiler_flags~$RM $lib.exp' # Commands to make compiler produce verbose output that lists # what "hidden" libraries, object files and flags are used when # linking a shared library. output_verbose_link_cmd='$CC -shared $CFLAGS -v conftest.$objext 2>&1 | $GREP -v "^Configured with:" | $GREP " \-L"' else # g++ 2.7 appears to require '-G' NOT '-shared' on this # platform. archive_cmds_CXX='$CC -G -nostdlib $predep_objects $libobjs $deplibs $postdep_objects $compiler_flags $wl-h $wl$soname -o $lib' archive_expsym_cmds_CXX='echo "{ global:" > $lib.exp~cat $export_symbols | $SED -e "s/\(.*\)/\1;/" >> $lib.exp~echo "local: *; };" >> $lib.exp~ $CC -G -nostdlib $wl-M $wl$lib.exp $wl-h $wl$soname -o $lib $predep_objects $libobjs $deplibs $postdep_objects $compiler_flags~$RM $lib.exp' # Commands to make compiler produce verbose output that lists # what "hidden" libraries, object files and flags are used when # linking a shared library. output_verbose_link_cmd='$CC -G $CFLAGS -v conftest.$objext 2>&1 | $GREP -v "^Configured with:" | $GREP " \-L"' fi hardcode_libdir_flag_spec_CXX='$wl-R $wl$libdir' case $host_os in solaris2.[0-5] | solaris2.[0-5].*) ;; *) whole_archive_flag_spec_CXX='$wl-z ${wl}allextract$convenience $wl-z ${wl}defaultextract' ;; esac fi ;; esac ;; sysv4*uw2* | sysv5OpenUNIX* | sysv5UnixWare7.[01].[10]* | unixware7* | sco3.2v5.0.[024]*) no_undefined_flag_CXX='$wl-z,text' archive_cmds_need_lc_CXX=no hardcode_shlibpath_var_CXX=no runpath_var='LD_RUN_PATH' case $cc_basename in CC*) archive_cmds_CXX='$CC -G $wl-h,$soname -o $lib $libobjs $deplibs $compiler_flags' archive_expsym_cmds_CXX='$CC -G $wl-Bexport:$export_symbols $wl-h,$soname -o $lib $libobjs $deplibs $compiler_flags' ;; *) archive_cmds_CXX='$CC -shared $wl-h,$soname -o $lib $libobjs $deplibs $compiler_flags' archive_expsym_cmds_CXX='$CC -shared $wl-Bexport:$export_symbols $wl-h,$soname -o $lib $libobjs $deplibs $compiler_flags' ;; esac ;; sysv5* | sco3.2v5* | sco5v6*) # Note: We CANNOT use -z defs as we might desire, because we do not # link with -lc, and that would cause any symbols used from libc to # always be unresolved, which means just about no library would # ever link correctly. If we're not using GNU ld we use -z text # though, which does catch some bad symbols but isn't as heavy-handed # as -z defs. no_undefined_flag_CXX='$wl-z,text' allow_undefined_flag_CXX='$wl-z,nodefs' archive_cmds_need_lc_CXX=no hardcode_shlibpath_var_CXX=no hardcode_libdir_flag_spec_CXX='$wl-R,$libdir' hardcode_libdir_separator_CXX=':' link_all_deplibs_CXX=yes export_dynamic_flag_spec_CXX='$wl-Bexport' runpath_var='LD_RUN_PATH' case $cc_basename in CC*) archive_cmds_CXX='$CC -G $wl-h,$soname -o $lib $libobjs $deplibs $compiler_flags' archive_expsym_cmds_CXX='$CC -G $wl-Bexport:$export_symbols $wl-h,$soname -o $lib $libobjs $deplibs $compiler_flags' old_archive_cmds_CXX='$CC -Tprelink_objects $oldobjs~ '"$old_archive_cmds_CXX" reload_cmds_CXX='$CC -Tprelink_objects $reload_objs~ '"$reload_cmds_CXX" ;; *) archive_cmds_CXX='$CC -shared $wl-h,$soname -o $lib $libobjs $deplibs $compiler_flags' archive_expsym_cmds_CXX='$CC -shared $wl-Bexport:$export_symbols $wl-h,$soname -o $lib $libobjs $deplibs $compiler_flags' ;; esac ;; tandem*) case $cc_basename in NCC*) # NonStop-UX NCC 3.20 # FIXME: insert proper C++ library support ld_shlibs_CXX=no ;; *) # FIXME: insert proper C++ library support ld_shlibs_CXX=no ;; esac ;; vxworks*) # FIXME: insert proper C++ library support ld_shlibs_CXX=no ;; *) # FIXME: insert proper C++ library support ld_shlibs_CXX=no ;; esac { $as_echo "$as_me:${as_lineno-$LINENO}: result: $ld_shlibs_CXX" >&5 $as_echo "$ld_shlibs_CXX" >&6; } test no = "$ld_shlibs_CXX" && can_build_shared=no GCC_CXX=$GXX LD_CXX=$LD ## CAVEAT EMPTOR: ## There is no encapsulation within the following macros, do not change ## the running order or otherwise move them around unless you know exactly ## what you are doing... # Dependencies to place before and after the object being linked: predep_objects_CXX= postdep_objects_CXX= predeps_CXX= postdeps_CXX= compiler_lib_search_path_CXX= cat > conftest.$ac_ext <<_LT_EOF class Foo { public: Foo (void) { a = 0; } private: int a; }; _LT_EOF _lt_libdeps_save_CFLAGS=$CFLAGS case "$CC $CFLAGS " in #( *\ -flto*\ *) CFLAGS="$CFLAGS -fno-lto" ;; *\ -fwhopr*\ *) CFLAGS="$CFLAGS -fno-whopr" ;; *\ -fuse-linker-plugin*\ *) CFLAGS="$CFLAGS -fno-use-linker-plugin" ;; esac if { { eval echo "\"\$as_me\":${as_lineno-$LINENO}: \"$ac_compile\""; } >&5 (eval $ac_compile) 2>&5 ac_status=$? $as_echo "$as_me:${as_lineno-$LINENO}: \$? = $ac_status" >&5 test $ac_status = 0; }; then # Parse the compiler output and extract the necessary # objects, libraries and library flags. # Sentinel used to keep track of whether or not we are before # the conftest object file. pre_test_object_deps_done=no for p in `eval "$output_verbose_link_cmd"`; do case $prev$p in -L* | -R* | -l*) # Some compilers place space between "-{L,R}" and the path. # Remove the space. if test x-L = "$p" || test x-R = "$p"; then prev=$p continue fi # Expand the sysroot to ease extracting the directories later. if test -z "$prev"; then case $p in -L*) func_stripname_cnf '-L' '' "$p"; prev=-L; p=$func_stripname_result ;; -R*) func_stripname_cnf '-R' '' "$p"; prev=-R; p=$func_stripname_result ;; -l*) func_stripname_cnf '-l' '' "$p"; prev=-l; p=$func_stripname_result ;; esac fi case $p in =*) func_stripname_cnf '=' '' "$p"; p=$lt_sysroot$func_stripname_result ;; esac if test no = "$pre_test_object_deps_done"; then case $prev in -L | -R) # Internal compiler library paths should come after those # provided the user. The postdeps already come after the # user supplied libs so there is no need to process them. if test -z "$compiler_lib_search_path_CXX"; then compiler_lib_search_path_CXX=$prev$p else compiler_lib_search_path_CXX="${compiler_lib_search_path_CXX} $prev$p" fi ;; # The "-l" case would never come before the object being # linked, so don't bother handling this case. esac else if test -z "$postdeps_CXX"; then postdeps_CXX=$prev$p else postdeps_CXX="${postdeps_CXX} $prev$p" fi fi prev= ;; *.lto.$objext) ;; # Ignore GCC LTO objects *.$objext) # This assumes that the test object file only shows up # once in the compiler output. if test "$p" = "conftest.$objext"; then pre_test_object_deps_done=yes continue fi if test no = "$pre_test_object_deps_done"; then if test -z "$predep_objects_CXX"; then predep_objects_CXX=$p else predep_objects_CXX="$predep_objects_CXX $p" fi else if test -z "$postdep_objects_CXX"; then postdep_objects_CXX=$p else postdep_objects_CXX="$postdep_objects_CXX $p" fi fi ;; *) ;; # Ignore the rest. esac done # Clean up. rm -f a.out a.exe else echo "libtool.m4: error: problem compiling CXX test program" fi $RM -f confest.$objext CFLAGS=$_lt_libdeps_save_CFLAGS # PORTME: override above test on systems where it is broken case $host_os in interix[3-9]*) # Interix 3.5 installs completely hosed .la files for C++, so rather than # hack all around it, let's just trust "g++" to DTRT. predep_objects_CXX= postdep_objects_CXX= postdeps_CXX= ;; esac case " $postdeps_CXX " in *" -lc "*) archive_cmds_need_lc_CXX=no ;; esac compiler_lib_search_dirs_CXX= if test -n "${compiler_lib_search_path_CXX}"; then compiler_lib_search_dirs_CXX=`echo " ${compiler_lib_search_path_CXX}" | $SED -e 's! -L! !g' -e 's!^ !!'` fi lt_prog_compiler_wl_CXX= lt_prog_compiler_pic_CXX= lt_prog_compiler_static_CXX= # C++ specific cases for pic, static, wl, etc. if test yes = "$GXX"; then lt_prog_compiler_wl_CXX='-Wl,' lt_prog_compiler_static_CXX='-static' case $host_os in aix*) # All AIX code is PIC. if test ia64 = "$host_cpu"; then # AIX 5 now supports IA64 processor lt_prog_compiler_static_CXX='-Bstatic' fi lt_prog_compiler_pic_CXX='-fPIC' ;; amigaos*) case $host_cpu in powerpc) # see comment about AmigaOS4 .so support lt_prog_compiler_pic_CXX='-fPIC' ;; m68k) # FIXME: we need at least 68020 code to build shared libraries, but # adding the '-m68020' flag to GCC prevents building anything better, # like '-m68040'. lt_prog_compiler_pic_CXX='-m68020 -resident32 -malways-restore-a4' ;; esac ;; beos* | irix5* | irix6* | nonstopux* | osf3* | osf4* | osf5*) # PIC is the default for these OSes. ;; mingw* | cygwin* | os2* | pw32* | cegcc*) # This hack is so that the source file can tell whether it is being # built for inclusion in a dll (and should export symbols for example). # Although the cygwin gcc ignores -fPIC, still need this for old-style # (--disable-auto-import) libraries lt_prog_compiler_pic_CXX='-DDLL_EXPORT' case $host_os in os2*) lt_prog_compiler_static_CXX='$wl-static' ;; esac ;; darwin* | rhapsody*) # PIC is the default on this platform # Common symbols not allowed in MH_DYLIB files lt_prog_compiler_pic_CXX='-fno-common' ;; *djgpp*) # DJGPP does not support shared libraries at all lt_prog_compiler_pic_CXX= ;; haiku*) # PIC is the default for Haiku. # The "-static" flag exists, but is broken. lt_prog_compiler_static_CXX= ;; interix[3-9]*) # Interix 3.x gcc -fpic/-fPIC options generate broken code. # Instead, we relocate shared libraries at runtime. ;; sysv4*MP*) if test -d /usr/nec; then lt_prog_compiler_pic_CXX=-Kconform_pic fi ;; hpux*) # PIC is the default for 64-bit PA HP-UX, but not for 32-bit # PA HP-UX. On IA64 HP-UX, PIC is the default but the pic flag # sets the default TLS model and affects inlining. case $host_cpu in hppa*64*) ;; *) lt_prog_compiler_pic_CXX='-fPIC' ;; esac ;; *qnx* | *nto*) # QNX uses GNU C++, but need to define -shared option too, otherwise # it will coredump. lt_prog_compiler_pic_CXX='-fPIC -shared' ;; *) lt_prog_compiler_pic_CXX='-fPIC' ;; esac else case $host_os in aix[4-9]*) # All AIX code is PIC. if test ia64 = "$host_cpu"; then # AIX 5 now supports IA64 processor lt_prog_compiler_static_CXX='-Bstatic' else lt_prog_compiler_static_CXX='-bnso -bI:/lib/syscalls.exp' fi ;; chorus*) case $cc_basename in cxch68*) # Green Hills C++ Compiler # _LT_TAGVAR(lt_prog_compiler_static, CXX)="--no_auto_instantiation -u __main -u __premain -u _abort -r $COOL_DIR/lib/libOrb.a $MVME_DIR/lib/CC/libC.a $MVME_DIR/lib/classix/libcx.s.a" ;; esac ;; mingw* | cygwin* | os2* | pw32* | cegcc*) # This hack is so that the source file can tell whether it is being # built for inclusion in a dll (and should export symbols for example). lt_prog_compiler_pic_CXX='-DDLL_EXPORT' ;; dgux*) case $cc_basename in ec++*) lt_prog_compiler_pic_CXX='-KPIC' ;; ghcx*) # Green Hills C++ Compiler lt_prog_compiler_pic_CXX='-pic' ;; *) ;; esac ;; freebsd* | dragonfly*) # FreeBSD uses GNU C++ ;; hpux9* | hpux10* | hpux11*) case $cc_basename in CC*) lt_prog_compiler_wl_CXX='-Wl,' lt_prog_compiler_static_CXX='$wl-a ${wl}archive' if test ia64 != "$host_cpu"; then lt_prog_compiler_pic_CXX='+Z' fi ;; aCC*) lt_prog_compiler_wl_CXX='-Wl,' lt_prog_compiler_static_CXX='$wl-a ${wl}archive' case $host_cpu in hppa*64*|ia64*) # +Z the default ;; *) lt_prog_compiler_pic_CXX='+Z' ;; esac ;; *) ;; esac ;; interix*) # This is c89, which is MS Visual C++ (no shared libs) # Anyone wants to do a port? ;; irix5* | irix6* | nonstopux*) case $cc_basename in CC*) lt_prog_compiler_wl_CXX='-Wl,' lt_prog_compiler_static_CXX='-non_shared' # CC pic flag -KPIC is the default. ;; *) ;; esac ;; linux* | k*bsd*-gnu | kopensolaris*-gnu | gnu*) case $cc_basename in KCC*) # KAI C++ Compiler lt_prog_compiler_wl_CXX='--backend -Wl,' lt_prog_compiler_pic_CXX='-fPIC' ;; ecpc* ) # old Intel C++ for x86_64, which still supported -KPIC. lt_prog_compiler_wl_CXX='-Wl,' lt_prog_compiler_pic_CXX='-KPIC' lt_prog_compiler_static_CXX='-static' ;; icpc* ) # Intel C++, used to be incompatible with GCC. # ICC 10 doesn't accept -KPIC any more. lt_prog_compiler_wl_CXX='-Wl,' lt_prog_compiler_pic_CXX='-fPIC' lt_prog_compiler_static_CXX='-static' ;; pgCC* | pgcpp*) # Portland Group C++ compiler lt_prog_compiler_wl_CXX='-Wl,' lt_prog_compiler_pic_CXX='-fpic' lt_prog_compiler_static_CXX='-Bstatic' ;; cxx*) # Compaq C++ # Make sure the PIC flag is empty. It appears that all Alpha # Linux and Compaq Tru64 Unix objects are PIC. lt_prog_compiler_pic_CXX= lt_prog_compiler_static_CXX='-non_shared' ;; xlc* | xlC* | bgxl[cC]* | mpixl[cC]*) # IBM XL 8.0, 9.0 on PPC and BlueGene lt_prog_compiler_wl_CXX='-Wl,' lt_prog_compiler_pic_CXX='-qpic' lt_prog_compiler_static_CXX='-qstaticlink' ;; *) case `$CC -V 2>&1 | sed 5q` in *Sun\ C*) # Sun C++ 5.9 lt_prog_compiler_pic_CXX='-KPIC' lt_prog_compiler_static_CXX='-Bstatic' lt_prog_compiler_wl_CXX='-Qoption ld ' ;; esac ;; esac ;; lynxos*) ;; m88k*) ;; mvs*) case $cc_basename in cxx*) lt_prog_compiler_pic_CXX='-W c,exportall' ;; *) ;; esac ;; netbsd* | netbsdelf*-gnu) ;; *qnx* | *nto*) # QNX uses GNU C++, but need to define -shared option too, otherwise # it will coredump. lt_prog_compiler_pic_CXX='-fPIC -shared' ;; osf3* | osf4* | osf5*) case $cc_basename in KCC*) lt_prog_compiler_wl_CXX='--backend -Wl,' ;; RCC*) # Rational C++ 2.4.1 lt_prog_compiler_pic_CXX='-pic' ;; cxx*) # Digital/Compaq C++ lt_prog_compiler_wl_CXX='-Wl,' # Make sure the PIC flag is empty. It appears that all Alpha # Linux and Compaq Tru64 Unix objects are PIC. lt_prog_compiler_pic_CXX= lt_prog_compiler_static_CXX='-non_shared' ;; *) ;; esac ;; psos*) ;; solaris*) case $cc_basename in CC* | sunCC*) # Sun C++ 4.2, 5.x and Centerline C++ lt_prog_compiler_pic_CXX='-KPIC' lt_prog_compiler_static_CXX='-Bstatic' lt_prog_compiler_wl_CXX='-Qoption ld ' ;; gcx*) # Green Hills C++ Compiler lt_prog_compiler_pic_CXX='-PIC' ;; *) ;; esac ;; sunos4*) case $cc_basename in CC*) # Sun C++ 4.x lt_prog_compiler_pic_CXX='-pic' lt_prog_compiler_static_CXX='-Bstatic' ;; lcc*) # Lucid lt_prog_compiler_pic_CXX='-pic' ;; *) ;; esac ;; sysv5* | unixware* | sco3.2v5* | sco5v6* | OpenUNIX*) case $cc_basename in CC*) lt_prog_compiler_wl_CXX='-Wl,' lt_prog_compiler_pic_CXX='-KPIC' lt_prog_compiler_static_CXX='-Bstatic' ;; esac ;; tandem*) case $cc_basename in NCC*) # NonStop-UX NCC 3.20 lt_prog_compiler_pic_CXX='-KPIC' ;; *) ;; esac ;; vxworks*) ;; *) lt_prog_compiler_can_build_shared_CXX=no ;; esac fi case $host_os in # For platforms that do not support PIC, -DPIC is meaningless: *djgpp*) lt_prog_compiler_pic_CXX= ;; *) lt_prog_compiler_pic_CXX="$lt_prog_compiler_pic_CXX -DPIC" ;; esac { $as_echo "$as_me:${as_lineno-$LINENO}: checking for $compiler option to produce PIC" >&5 $as_echo_n "checking for $compiler option to produce PIC... " >&6; } if ${lt_cv_prog_compiler_pic_CXX+:} false; then : $as_echo_n "(cached) " >&6 else lt_cv_prog_compiler_pic_CXX=$lt_prog_compiler_pic_CXX fi { $as_echo "$as_me:${as_lineno-$LINENO}: result: $lt_cv_prog_compiler_pic_CXX" >&5 $as_echo "$lt_cv_prog_compiler_pic_CXX" >&6; } lt_prog_compiler_pic_CXX=$lt_cv_prog_compiler_pic_CXX # # Check to make sure the PIC flag actually works. # if test -n "$lt_prog_compiler_pic_CXX"; then { $as_echo "$as_me:${as_lineno-$LINENO}: checking if $compiler PIC flag $lt_prog_compiler_pic_CXX works" >&5 $as_echo_n "checking if $compiler PIC flag $lt_prog_compiler_pic_CXX works... " >&6; } if ${lt_cv_prog_compiler_pic_works_CXX+:} false; then : $as_echo_n "(cached) " >&6 else lt_cv_prog_compiler_pic_works_CXX=no ac_outfile=conftest.$ac_objext echo "$lt_simple_compile_test_code" > conftest.$ac_ext lt_compiler_flag="$lt_prog_compiler_pic_CXX -DPIC" ## exclude from sc_useless_quotes_in_assignment # Insert the option either (1) after the last *FLAGS variable, or # (2) before a word containing "conftest.", or (3) at the end. # Note that $ac_compile itself does not contain backslashes and begins # with a dollar sign (not a hyphen), so the echo should work correctly. # The option is referenced via a variable to avoid confusing sed. lt_compile=`echo "$ac_compile" | $SED \ -e 's:.*FLAGS}\{0,1\} :&$lt_compiler_flag :; t' \ -e 's: [^ ]*conftest\.: $lt_compiler_flag&:; t' \ -e 's:$: $lt_compiler_flag:'` (eval echo "\"\$as_me:$LINENO: $lt_compile\"" >&5) (eval "$lt_compile" 2>conftest.err) ac_status=$? cat conftest.err >&5 echo "$as_me:$LINENO: \$? = $ac_status" >&5 if (exit $ac_status) && test -s "$ac_outfile"; then # The compiler can only warn and ignore the option if not recognized # So say no if there are warnings other than the usual output. $ECHO "$_lt_compiler_boilerplate" | $SED '/^$/d' >conftest.exp $SED '/^$/d; /^ *+/d' conftest.err >conftest.er2 if test ! -s conftest.er2 || diff conftest.exp conftest.er2 >/dev/null; then lt_cv_prog_compiler_pic_works_CXX=yes fi fi $RM conftest* fi { $as_echo "$as_me:${as_lineno-$LINENO}: result: $lt_cv_prog_compiler_pic_works_CXX" >&5 $as_echo "$lt_cv_prog_compiler_pic_works_CXX" >&6; } if test yes = "$lt_cv_prog_compiler_pic_works_CXX"; then case $lt_prog_compiler_pic_CXX in "" | " "*) ;; *) lt_prog_compiler_pic_CXX=" $lt_prog_compiler_pic_CXX" ;; esac else lt_prog_compiler_pic_CXX= lt_prog_compiler_can_build_shared_CXX=no fi fi # # Check to make sure the static flag actually works. # wl=$lt_prog_compiler_wl_CXX eval lt_tmp_static_flag=\"$lt_prog_compiler_static_CXX\" { $as_echo "$as_me:${as_lineno-$LINENO}: checking if $compiler static flag $lt_tmp_static_flag works" >&5 $as_echo_n "checking if $compiler static flag $lt_tmp_static_flag works... " >&6; } if ${lt_cv_prog_compiler_static_works_CXX+:} false; then : $as_echo_n "(cached) " >&6 else lt_cv_prog_compiler_static_works_CXX=no save_LDFLAGS=$LDFLAGS LDFLAGS="$LDFLAGS $lt_tmp_static_flag" echo "$lt_simple_link_test_code" > conftest.$ac_ext if (eval $ac_link 2>conftest.err) && test -s conftest$ac_exeext; then # The linker can only warn and ignore the option if not recognized # So say no if there are warnings if test -s conftest.err; then # Append any errors to the config.log. cat conftest.err 1>&5 $ECHO "$_lt_linker_boilerplate" | $SED '/^$/d' > conftest.exp $SED '/^$/d; /^ *+/d' conftest.err >conftest.er2 if diff conftest.exp conftest.er2 >/dev/null; then lt_cv_prog_compiler_static_works_CXX=yes fi else lt_cv_prog_compiler_static_works_CXX=yes fi fi $RM -r conftest* LDFLAGS=$save_LDFLAGS fi { $as_echo "$as_me:${as_lineno-$LINENO}: result: $lt_cv_prog_compiler_static_works_CXX" >&5 $as_echo "$lt_cv_prog_compiler_static_works_CXX" >&6; } if test yes = "$lt_cv_prog_compiler_static_works_CXX"; then : else lt_prog_compiler_static_CXX= fi { $as_echo "$as_me:${as_lineno-$LINENO}: checking if $compiler supports -c -o file.$ac_objext" >&5 $as_echo_n "checking if $compiler supports -c -o file.$ac_objext... " >&6; } if ${lt_cv_prog_compiler_c_o_CXX+:} false; then : $as_echo_n "(cached) " >&6 else lt_cv_prog_compiler_c_o_CXX=no $RM -r conftest 2>/dev/null mkdir conftest cd conftest mkdir out echo "$lt_simple_compile_test_code" > conftest.$ac_ext lt_compiler_flag="-o out/conftest2.$ac_objext" # Insert the option either (1) after the last *FLAGS variable, or # (2) before a word containing "conftest.", or (3) at the end. # Note that $ac_compile itself does not contain backslashes and begins # with a dollar sign (not a hyphen), so the echo should work correctly. lt_compile=`echo "$ac_compile" | $SED \ -e 's:.*FLAGS}\{0,1\} :&$lt_compiler_flag :; t' \ -e 's: [^ ]*conftest\.: $lt_compiler_flag&:; t' \ -e 's:$: $lt_compiler_flag:'` (eval echo "\"\$as_me:$LINENO: $lt_compile\"" >&5) (eval "$lt_compile" 2>out/conftest.err) ac_status=$? cat out/conftest.err >&5 echo "$as_me:$LINENO: \$? = $ac_status" >&5 if (exit $ac_status) && test -s out/conftest2.$ac_objext then # The compiler can only warn and ignore the option if not recognized # So say no if there are warnings $ECHO "$_lt_compiler_boilerplate" | $SED '/^$/d' > out/conftest.exp $SED '/^$/d; /^ *+/d' out/conftest.err >out/conftest.er2 if test ! -s out/conftest.er2 || diff out/conftest.exp out/conftest.er2 >/dev/null; then lt_cv_prog_compiler_c_o_CXX=yes fi fi chmod u+w . 2>&5 $RM conftest* # SGI C++ compiler will create directory out/ii_files/ for # template instantiation test -d out/ii_files && $RM out/ii_files/* && rmdir out/ii_files $RM out/* && rmdir out cd .. $RM -r conftest $RM conftest* fi { $as_echo "$as_me:${as_lineno-$LINENO}: result: $lt_cv_prog_compiler_c_o_CXX" >&5 $as_echo "$lt_cv_prog_compiler_c_o_CXX" >&6; } { $as_echo "$as_me:${as_lineno-$LINENO}: checking if $compiler supports -c -o file.$ac_objext" >&5 $as_echo_n "checking if $compiler supports -c -o file.$ac_objext... " >&6; } if ${lt_cv_prog_compiler_c_o_CXX+:} false; then : $as_echo_n "(cached) " >&6 else lt_cv_prog_compiler_c_o_CXX=no $RM -r conftest 2>/dev/null mkdir conftest cd conftest mkdir out echo "$lt_simple_compile_test_code" > conftest.$ac_ext lt_compiler_flag="-o out/conftest2.$ac_objext" # Insert the option either (1) after the last *FLAGS variable, or # (2) before a word containing "conftest.", or (3) at the end. # Note that $ac_compile itself does not contain backslashes and begins # with a dollar sign (not a hyphen), so the echo should work correctly. lt_compile=`echo "$ac_compile" | $SED \ -e 's:.*FLAGS}\{0,1\} :&$lt_compiler_flag :; t' \ -e 's: [^ ]*conftest\.: $lt_compiler_flag&:; t' \ -e 's:$: $lt_compiler_flag:'` (eval echo "\"\$as_me:$LINENO: $lt_compile\"" >&5) (eval "$lt_compile" 2>out/conftest.err) ac_status=$? cat out/conftest.err >&5 echo "$as_me:$LINENO: \$? = $ac_status" >&5 if (exit $ac_status) && test -s out/conftest2.$ac_objext then # The compiler can only warn and ignore the option if not recognized # So say no if there are warnings $ECHO "$_lt_compiler_boilerplate" | $SED '/^$/d' > out/conftest.exp $SED '/^$/d; /^ *+/d' out/conftest.err >out/conftest.er2 if test ! -s out/conftest.er2 || diff out/conftest.exp out/conftest.er2 >/dev/null; then lt_cv_prog_compiler_c_o_CXX=yes fi fi chmod u+w . 2>&5 $RM conftest* # SGI C++ compiler will create directory out/ii_files/ for # template instantiation test -d out/ii_files && $RM out/ii_files/* && rmdir out/ii_files $RM out/* && rmdir out cd .. $RM -r conftest $RM conftest* fi { $as_echo "$as_me:${as_lineno-$LINENO}: result: $lt_cv_prog_compiler_c_o_CXX" >&5 $as_echo "$lt_cv_prog_compiler_c_o_CXX" >&6; } hard_links=nottested if test no = "$lt_cv_prog_compiler_c_o_CXX" && test no != "$need_locks"; then # do not overwrite the value of need_locks provided by the user { $as_echo "$as_me:${as_lineno-$LINENO}: checking if we can lock with hard links" >&5 $as_echo_n "checking if we can lock with hard links... " >&6; } hard_links=yes $RM conftest* ln conftest.a conftest.b 2>/dev/null && hard_links=no touch conftest.a ln conftest.a conftest.b 2>&5 || hard_links=no ln conftest.a conftest.b 2>/dev/null && hard_links=no { $as_echo "$as_me:${as_lineno-$LINENO}: result: $hard_links" >&5 $as_echo "$hard_links" >&6; } if test no = "$hard_links"; then { $as_echo "$as_me:${as_lineno-$LINENO}: WARNING: '$CC' does not support '-c -o', so 'make -j' may be unsafe" >&5 $as_echo "$as_me: WARNING: '$CC' does not support '-c -o', so 'make -j' may be unsafe" >&2;} need_locks=warn fi else need_locks=no fi { $as_echo "$as_me:${as_lineno-$LINENO}: checking whether the $compiler linker ($LD) supports shared libraries" >&5 $as_echo_n "checking whether the $compiler linker ($LD) supports shared libraries... " >&6; } export_symbols_cmds_CXX='$NM $libobjs $convenience | $global_symbol_pipe | $SED '\''s/.* //'\'' | sort | uniq > $export_symbols' exclude_expsyms_CXX='_GLOBAL_OFFSET_TABLE_|_GLOBAL__F[ID]_.*' case $host_os in aix[4-9]*) # If we're using GNU nm, then we don't want the "-C" option. # -C means demangle to GNU nm, but means don't demangle to AIX nm. # Without the "-l" option, or with the "-B" option, AIX nm treats # weak defined symbols like other global defined symbols, whereas # GNU nm marks them as "W". # While the 'weak' keyword is ignored in the Export File, we need # it in the Import File for the 'aix-soname' feature, so we have # to replace the "-B" option with "-P" for AIX nm. if $NM -V 2>&1 | $GREP 'GNU' > /dev/null; then export_symbols_cmds_CXX='$NM -Bpg $libobjs $convenience | awk '\''{ if (((\$ 2 == "T") || (\$ 2 == "D") || (\$ 2 == "B") || (\$ 2 == "W")) && (substr(\$ 3,1,1) != ".")) { if (\$ 2 == "W") { print \$ 3 " weak" } else { print \$ 3 } } }'\'' | sort -u > $export_symbols' else export_symbols_cmds_CXX='`func_echo_all $NM | $SED -e '\''s/B\([^B]*\)$/P\1/'\''` -PCpgl $libobjs $convenience | awk '\''{ if (((\$ 2 == "T") || (\$ 2 == "D") || (\$ 2 == "B") || (\$ 2 == "W") || (\$ 2 == "V") || (\$ 2 == "Z")) && (substr(\$ 1,1,1) != ".")) { if ((\$ 2 == "W") || (\$ 2 == "V") || (\$ 2 == "Z")) { print \$ 1 " weak" } else { print \$ 1 } } }'\'' | sort -u > $export_symbols' fi ;; pw32*) export_symbols_cmds_CXX=$ltdll_cmds ;; cygwin* | mingw* | cegcc*) case $cc_basename in cl*) exclude_expsyms_CXX='_NULL_IMPORT_DESCRIPTOR|_IMPORT_DESCRIPTOR_.*' ;; *) export_symbols_cmds_CXX='$NM $libobjs $convenience | $global_symbol_pipe | $SED -e '\''/^[BCDGRS][ ]/s/.*[ ]\([^ ]*\)/\1 DATA/;s/^.*[ ]__nm__\([^ ]*\)[ ][^ ]*/\1 DATA/;/^I[ ]/d;/^[AITW][ ]/s/.* //'\'' | sort | uniq > $export_symbols' exclude_expsyms_CXX='[_]+GLOBAL_OFFSET_TABLE_|[_]+GLOBAL__[FID]_.*|[_]+head_[A-Za-z0-9_]+_dll|[A-Za-z0-9_]+_dll_iname' ;; esac ;; linux* | k*bsd*-gnu | gnu*) link_all_deplibs_CXX=no ;; *) export_symbols_cmds_CXX='$NM $libobjs $convenience | $global_symbol_pipe | $SED '\''s/.* //'\'' | sort | uniq > $export_symbols' ;; esac { $as_echo "$as_me:${as_lineno-$LINENO}: result: $ld_shlibs_CXX" >&5 $as_echo "$ld_shlibs_CXX" >&6; } test no = "$ld_shlibs_CXX" && can_build_shared=no with_gnu_ld_CXX=$with_gnu_ld # # Do we need to explicitly link libc? # case "x$archive_cmds_need_lc_CXX" in x|xyes) # Assume -lc should be added archive_cmds_need_lc_CXX=yes if test yes,yes = "$GCC,$enable_shared"; then case $archive_cmds_CXX in *'~'*) # FIXME: we may have to deal with multi-command sequences. ;; '$CC '*) # Test whether the compiler implicitly links with -lc since on some # systems, -lgcc has to come before -lc. If gcc already passes -lc # to ld, don't add -lc before -lgcc. { $as_echo "$as_me:${as_lineno-$LINENO}: checking whether -lc should be explicitly linked in" >&5 $as_echo_n "checking whether -lc should be explicitly linked in... " >&6; } if ${lt_cv_archive_cmds_need_lc_CXX+:} false; then : $as_echo_n "(cached) " >&6 else $RM conftest* echo "$lt_simple_compile_test_code" > conftest.$ac_ext if { { eval echo "\"\$as_me\":${as_lineno-$LINENO}: \"$ac_compile\""; } >&5 (eval $ac_compile) 2>&5 ac_status=$? $as_echo "$as_me:${as_lineno-$LINENO}: \$? = $ac_status" >&5 test $ac_status = 0; } 2>conftest.err; then soname=conftest lib=conftest libobjs=conftest.$ac_objext deplibs= wl=$lt_prog_compiler_wl_CXX pic_flag=$lt_prog_compiler_pic_CXX compiler_flags=-v linker_flags=-v verstring= output_objdir=. libname=conftest lt_save_allow_undefined_flag=$allow_undefined_flag_CXX allow_undefined_flag_CXX= if { { eval echo "\"\$as_me\":${as_lineno-$LINENO}: \"$archive_cmds_CXX 2\>\&1 \| $GREP \" -lc \" \>/dev/null 2\>\&1\""; } >&5 (eval $archive_cmds_CXX 2\>\&1 \| $GREP \" -lc \" \>/dev/null 2\>\&1) 2>&5 ac_status=$? $as_echo "$as_me:${as_lineno-$LINENO}: \$? = $ac_status" >&5 test $ac_status = 0; } then lt_cv_archive_cmds_need_lc_CXX=no else lt_cv_archive_cmds_need_lc_CXX=yes fi allow_undefined_flag_CXX=$lt_save_allow_undefined_flag else cat conftest.err 1>&5 fi $RM conftest* fi { $as_echo "$as_me:${as_lineno-$LINENO}: result: $lt_cv_archive_cmds_need_lc_CXX" >&5 $as_echo "$lt_cv_archive_cmds_need_lc_CXX" >&6; } archive_cmds_need_lc_CXX=$lt_cv_archive_cmds_need_lc_CXX ;; esac fi ;; esac { $as_echo "$as_me:${as_lineno-$LINENO}: checking dynamic linker characteristics" >&5 $as_echo_n "checking dynamic linker characteristics... " >&6; } library_names_spec= libname_spec='lib$name' soname_spec= shrext_cmds=.so postinstall_cmds= postuninstall_cmds= finish_cmds= finish_eval= shlibpath_var= shlibpath_overrides_runpath=unknown version_type=none dynamic_linker="$host_os ld.so" sys_lib_dlsearch_path_spec="/lib /usr/lib" need_lib_prefix=unknown hardcode_into_libs=no # when you set need_version to no, make sure it does not cause -set_version # flags to be left without arguments need_version=unknown case $host_os in aix3*) version_type=linux # correct to gnu/linux during the next big refactor library_names_spec='$libname$release$shared_ext$versuffix $libname.a' shlibpath_var=LIBPATH # AIX 3 has no versioning support, so we append a major version to the name. soname_spec='$libname$release$shared_ext$major' ;; aix[4-9]*) version_type=linux # correct to gnu/linux during the next big refactor need_lib_prefix=no need_version=no hardcode_into_libs=yes if test ia64 = "$host_cpu"; then # AIX 5 supports IA64 library_names_spec='$libname$release$shared_ext$major $libname$release$shared_ext$versuffix $libname$shared_ext' shlibpath_var=LD_LIBRARY_PATH else # With GCC up to 2.95.x, collect2 would create an import file # for dependence libraries. The import file would start with # the line '#! .'. This would cause the generated library to # depend on '.', always an invalid library. This was fixed in # development snapshots of GCC prior to 3.0. case $host_os in aix4 | aix4.[01] | aix4.[01].*) if { echo '#if __GNUC__ > 2 || (__GNUC__ == 2 && __GNUC_MINOR__ >= 97)' echo ' yes ' echo '#endif'; } | $CC -E - | $GREP yes > /dev/null; then : else can_build_shared=no fi ;; esac # Using Import Files as archive members, it is possible to support # filename-based versioning of shared library archives on AIX. While # this would work for both with and without runtime linking, it will # prevent static linking of such archives. So we do filename-based # shared library versioning with .so extension only, which is used # when both runtime linking and shared linking is enabled. # Unfortunately, runtime linking may impact performance, so we do # not want this to be the default eventually. Also, we use the # versioned .so libs for executables only if there is the -brtl # linker flag in LDFLAGS as well, or --with-aix-soname=svr4 only. # To allow for filename-based versioning support, we need to create # libNAME.so.V as an archive file, containing: # *) an Import File, referring to the versioned filename of the # archive as well as the shared archive member, telling the # bitwidth (32 or 64) of that shared object, and providing the # list of exported symbols of that shared object, eventually # decorated with the 'weak' keyword # *) the shared object with the F_LOADONLY flag set, to really avoid # it being seen by the linker. # At run time we better use the real file rather than another symlink, # but for link time we create the symlink libNAME.so -> libNAME.so.V case $with_aix_soname,$aix_use_runtimelinking in # AIX (on Power*) has no versioning support, so currently we cannot hardcode correct # soname into executable. Probably we can add versioning support to # collect2, so additional links can be useful in future. aix,yes) # traditional libtool dynamic_linker='AIX unversionable lib.so' # If using run time linking (on AIX 4.2 or later) use lib.so # instead of lib.a to let people know that these are not # typical AIX shared libraries. library_names_spec='$libname$release$shared_ext$versuffix $libname$release$shared_ext$major $libname$shared_ext' ;; aix,no) # traditional AIX only dynamic_linker='AIX lib.a(lib.so.V)' # We preserve .a as extension for shared libraries through AIX4.2 # and later when we are not doing run time linking. library_names_spec='$libname$release.a $libname.a' soname_spec='$libname$release$shared_ext$major' ;; svr4,*) # full svr4 only dynamic_linker="AIX lib.so.V($shared_archive_member_spec.o)" library_names_spec='$libname$release$shared_ext$major $libname$shared_ext' # We do not specify a path in Import Files, so LIBPATH fires. shlibpath_overrides_runpath=yes ;; *,yes) # both, prefer svr4 dynamic_linker="AIX lib.so.V($shared_archive_member_spec.o), lib.a(lib.so.V)" library_names_spec='$libname$release$shared_ext$major $libname$shared_ext' # unpreferred sharedlib libNAME.a needs extra handling postinstall_cmds='test -n "$linkname" || linkname="$realname"~func_stripname "" ".so" "$linkname"~$install_shared_prog "$dir/$func_stripname_result.$libext" "$destdir/$func_stripname_result.$libext"~test -z "$tstripme" || test -z "$striplib" || $striplib "$destdir/$func_stripname_result.$libext"' postuninstall_cmds='for n in $library_names $old_library; do :; done~func_stripname "" ".so" "$n"~test "$func_stripname_result" = "$n" || func_append rmfiles " $odir/$func_stripname_result.$libext"' # We do not specify a path in Import Files, so LIBPATH fires. shlibpath_overrides_runpath=yes ;; *,no) # both, prefer aix dynamic_linker="AIX lib.a(lib.so.V), lib.so.V($shared_archive_member_spec.o)" library_names_spec='$libname$release.a $libname.a' soname_spec='$libname$release$shared_ext$major' # unpreferred sharedlib libNAME.so.V and symlink libNAME.so need extra handling postinstall_cmds='test -z "$dlname" || $install_shared_prog $dir/$dlname $destdir/$dlname~test -z "$tstripme" || test -z "$striplib" || $striplib $destdir/$dlname~test -n "$linkname" || linkname=$realname~func_stripname "" ".a" "$linkname"~(cd "$destdir" && $LN_S -f $dlname $func_stripname_result.so)' postuninstall_cmds='test -z "$dlname" || func_append rmfiles " $odir/$dlname"~for n in $old_library $library_names; do :; done~func_stripname "" ".a" "$n"~func_append rmfiles " $odir/$func_stripname_result.so"' ;; esac shlibpath_var=LIBPATH fi ;; amigaos*) case $host_cpu in powerpc) # Since July 2007 AmigaOS4 officially supports .so libraries. # When compiling the executable, add -use-dynld -Lsobjs: to the compileline. library_names_spec='$libname$release$shared_ext$versuffix $libname$release$shared_ext$major $libname$shared_ext' ;; m68k) library_names_spec='$libname.ixlibrary $libname.a' # Create ${libname}_ixlibrary.a entries in /sys/libs. finish_eval='for lib in `ls $libdir/*.ixlibrary 2>/dev/null`; do libname=`func_echo_all "$lib" | $SED '\''s%^.*/\([^/]*\)\.ixlibrary$%\1%'\''`; $RM /sys/libs/${libname}_ixlibrary.a; $show "cd /sys/libs && $LN_S $lib ${libname}_ixlibrary.a"; cd /sys/libs && $LN_S $lib ${libname}_ixlibrary.a || exit 1; done' ;; esac ;; beos*) library_names_spec='$libname$shared_ext' dynamic_linker="$host_os ld.so" shlibpath_var=LIBRARY_PATH ;; bsdi[45]*) version_type=linux # correct to gnu/linux during the next big refactor need_version=no library_names_spec='$libname$release$shared_ext$versuffix $libname$release$shared_ext$major $libname$shared_ext' soname_spec='$libname$release$shared_ext$major' finish_cmds='PATH="\$PATH:/sbin" ldconfig $libdir' shlibpath_var=LD_LIBRARY_PATH sys_lib_search_path_spec="/shlib /usr/lib /usr/X11/lib /usr/contrib/lib /lib /usr/local/lib" sys_lib_dlsearch_path_spec="/shlib /usr/lib /usr/local/lib" # the default ld.so.conf also contains /usr/contrib/lib and # /usr/X11R6/lib (/usr/X11 is a link to /usr/X11R6), but let us allow # libtool to hard-code these into programs ;; cygwin* | mingw* | pw32* | cegcc*) version_type=windows shrext_cmds=.dll need_version=no need_lib_prefix=no case $GCC,$cc_basename in yes,*) # gcc library_names_spec='$libname.dll.a' # DLL is installed to $(libdir)/../bin by postinstall_cmds postinstall_cmds='base_file=`basename \$file`~ dlpath=`$SHELL 2>&1 -c '\''. $dir/'\''\$base_file'\''i; echo \$dlname'\''`~ dldir=$destdir/`dirname \$dlpath`~ test -d \$dldir || mkdir -p \$dldir~ $install_prog $dir/$dlname \$dldir/$dlname~ chmod a+x \$dldir/$dlname~ if test -n '\''$stripme'\'' && test -n '\''$striplib'\''; then eval '\''$striplib \$dldir/$dlname'\'' || exit \$?; fi' postuninstall_cmds='dldll=`$SHELL 2>&1 -c '\''. $file; echo \$dlname'\''`~ dlpath=$dir/\$dldll~ $RM \$dlpath' shlibpath_overrides_runpath=yes case $host_os in cygwin*) # Cygwin DLLs use 'cyg' prefix rather than 'lib' soname_spec='`echo $libname | sed -e 's/^lib/cyg/'``echo $release | $SED -e 's/[.]/-/g'`$versuffix$shared_ext' ;; mingw* | cegcc*) # MinGW DLLs use traditional 'lib' prefix soname_spec='$libname`echo $release | $SED -e 's/[.]/-/g'`$versuffix$shared_ext' ;; pw32*) # pw32 DLLs use 'pw' prefix rather than 'lib' library_names_spec='`echo $libname | sed -e 's/^lib/pw/'``echo $release | $SED -e 's/[.]/-/g'`$versuffix$shared_ext' ;; esac dynamic_linker='Win32 ld.exe' ;; *,cl*) # Native MSVC libname_spec='$name' soname_spec='$libname`echo $release | $SED -e 's/[.]/-/g'`$versuffix$shared_ext' library_names_spec='$libname.dll.lib' case $build_os in mingw*) sys_lib_search_path_spec= lt_save_ifs=$IFS IFS=';' for lt_path in $LIB do IFS=$lt_save_ifs # Let DOS variable expansion print the short 8.3 style file name. lt_path=`cd "$lt_path" 2>/dev/null && cmd //C "for %i in (".") do @echo %~si"` sys_lib_search_path_spec="$sys_lib_search_path_spec $lt_path" done IFS=$lt_save_ifs # Convert to MSYS style. sys_lib_search_path_spec=`$ECHO "$sys_lib_search_path_spec" | sed -e 's|\\\\|/|g' -e 's| \\([a-zA-Z]\\):| /\\1|g' -e 's|^ ||'` ;; cygwin*) # Convert to unix form, then to dos form, then back to unix form # but this time dos style (no spaces!) so that the unix form looks # like /cygdrive/c/PROGRA~1:/cygdr... sys_lib_search_path_spec=`cygpath --path --unix "$LIB"` sys_lib_search_path_spec=`cygpath --path --dos "$sys_lib_search_path_spec" 2>/dev/null` sys_lib_search_path_spec=`cygpath --path --unix "$sys_lib_search_path_spec" | $SED -e "s/$PATH_SEPARATOR/ /g"` ;; *) sys_lib_search_path_spec=$LIB if $ECHO "$sys_lib_search_path_spec" | $GREP ';[c-zC-Z]:/' >/dev/null; then # It is most probably a Windows format PATH. sys_lib_search_path_spec=`$ECHO "$sys_lib_search_path_spec" | $SED -e 's/;/ /g'` else sys_lib_search_path_spec=`$ECHO "$sys_lib_search_path_spec" | $SED -e "s/$PATH_SEPARATOR/ /g"` fi # FIXME: find the short name or the path components, as spaces are # common. (e.g. "Program Files" -> "PROGRA~1") ;; esac # DLL is installed to $(libdir)/../bin by postinstall_cmds postinstall_cmds='base_file=`basename \$file`~ dlpath=`$SHELL 2>&1 -c '\''. $dir/'\''\$base_file'\''i; echo \$dlname'\''`~ dldir=$destdir/`dirname \$dlpath`~ test -d \$dldir || mkdir -p \$dldir~ $install_prog $dir/$dlname \$dldir/$dlname' postuninstall_cmds='dldll=`$SHELL 2>&1 -c '\''. $file; echo \$dlname'\''`~ dlpath=$dir/\$dldll~ $RM \$dlpath' shlibpath_overrides_runpath=yes dynamic_linker='Win32 link.exe' ;; *) # Assume MSVC wrapper library_names_spec='$libname`echo $release | $SED -e 's/[.]/-/g'`$versuffix$shared_ext $libname.lib' dynamic_linker='Win32 ld.exe' ;; esac # FIXME: first we should search . and the directory the executable is in shlibpath_var=PATH ;; darwin* | rhapsody*) dynamic_linker="$host_os dyld" version_type=darwin need_lib_prefix=no need_version=no library_names_spec='$libname$release$major$shared_ext $libname$shared_ext' soname_spec='$libname$release$major$shared_ext' shlibpath_overrides_runpath=yes shlibpath_var=DYLD_LIBRARY_PATH shrext_cmds='`test .$module = .yes && echo .so || echo .dylib`' sys_lib_dlsearch_path_spec='/usr/local/lib /lib /usr/lib' ;; dgux*) version_type=linux # correct to gnu/linux during the next big refactor need_lib_prefix=no need_version=no library_names_spec='$libname$release$shared_ext$versuffix $libname$release$shared_ext$major $libname$shared_ext' soname_spec='$libname$release$shared_ext$major' shlibpath_var=LD_LIBRARY_PATH ;; freebsd* | dragonfly*) # DragonFly does not have aout. When/if they implement a new # versioning mechanism, adjust this. if test -x /usr/bin/objformat; then objformat=`/usr/bin/objformat` else case $host_os in freebsd[23].*) objformat=aout ;; *) objformat=elf ;; esac fi version_type=freebsd-$objformat case $version_type in freebsd-elf*) library_names_spec='$libname$release$shared_ext$versuffix $libname$release$shared_ext$major $libname$shared_ext' soname_spec='$libname$release$shared_ext$major' need_version=no need_lib_prefix=no ;; freebsd-*) library_names_spec='$libname$release$shared_ext$versuffix $libname$shared_ext$versuffix' need_version=yes ;; esac shlibpath_var=LD_LIBRARY_PATH case $host_os in freebsd2.*) shlibpath_overrides_runpath=yes ;; freebsd3.[01]* | freebsdelf3.[01]*) shlibpath_overrides_runpath=yes hardcode_into_libs=yes ;; freebsd3.[2-9]* | freebsdelf3.[2-9]* | \ freebsd4.[0-5] | freebsdelf4.[0-5] | freebsd4.1.1 | freebsdelf4.1.1) shlibpath_overrides_runpath=no hardcode_into_libs=yes ;; *) # from 4.6 on, and DragonFly shlibpath_overrides_runpath=yes hardcode_into_libs=yes ;; esac ;; haiku*) version_type=linux # correct to gnu/linux during the next big refactor need_lib_prefix=no need_version=no dynamic_linker="$host_os runtime_loader" library_names_spec='$libname$release$shared_ext$versuffix $libname$release$shared_ext$major $libname$shared_ext' soname_spec='$libname$release$shared_ext$major' shlibpath_var=LIBRARY_PATH shlibpath_overrides_runpath=no sys_lib_dlsearch_path_spec='/boot/home/config/lib /boot/common/lib /boot/system/lib' hardcode_into_libs=yes ;; hpux9* | hpux10* | hpux11*) # Give a soname corresponding to the major version so that dld.sl refuses to # link against other versions. version_type=sunos need_lib_prefix=no need_version=no case $host_cpu in ia64*) shrext_cmds='.so' hardcode_into_libs=yes dynamic_linker="$host_os dld.so" shlibpath_var=LD_LIBRARY_PATH shlibpath_overrides_runpath=yes # Unless +noenvvar is specified. library_names_spec='$libname$release$shared_ext$versuffix $libname$release$shared_ext$major $libname$shared_ext' soname_spec='$libname$release$shared_ext$major' if test 32 = "$HPUX_IA64_MODE"; then sys_lib_search_path_spec="/usr/lib/hpux32 /usr/local/lib/hpux32 /usr/local/lib" sys_lib_dlsearch_path_spec=/usr/lib/hpux32 else sys_lib_search_path_spec="/usr/lib/hpux64 /usr/local/lib/hpux64" sys_lib_dlsearch_path_spec=/usr/lib/hpux64 fi ;; hppa*64*) shrext_cmds='.sl' hardcode_into_libs=yes dynamic_linker="$host_os dld.sl" shlibpath_var=LD_LIBRARY_PATH # How should we handle SHLIB_PATH shlibpath_overrides_runpath=yes # Unless +noenvvar is specified. library_names_spec='$libname$release$shared_ext$versuffix $libname$release$shared_ext$major $libname$shared_ext' soname_spec='$libname$release$shared_ext$major' sys_lib_search_path_spec="/usr/lib/pa20_64 /usr/ccs/lib/pa20_64" sys_lib_dlsearch_path_spec=$sys_lib_search_path_spec ;; *) shrext_cmds='.sl' dynamic_linker="$host_os dld.sl" shlibpath_var=SHLIB_PATH shlibpath_overrides_runpath=no # +s is required to enable SHLIB_PATH library_names_spec='$libname$release$shared_ext$versuffix $libname$release$shared_ext$major $libname$shared_ext' soname_spec='$libname$release$shared_ext$major' ;; esac # HP-UX runs *really* slowly unless shared libraries are mode 555, ... postinstall_cmds='chmod 555 $lib' # or fails outright, so override atomically: install_override_mode=555 ;; interix[3-9]*) version_type=linux # correct to gnu/linux during the next big refactor need_lib_prefix=no need_version=no library_names_spec='$libname$release$shared_ext$versuffix $libname$release$shared_ext$major $libname$shared_ext' soname_spec='$libname$release$shared_ext$major' dynamic_linker='Interix 3.x ld.so.1 (PE, like ELF)' shlibpath_var=LD_LIBRARY_PATH shlibpath_overrides_runpath=no hardcode_into_libs=yes ;; irix5* | irix6* | nonstopux*) case $host_os in nonstopux*) version_type=nonstopux ;; *) if test yes = "$lt_cv_prog_gnu_ld"; then version_type=linux # correct to gnu/linux during the next big refactor else version_type=irix fi ;; esac need_lib_prefix=no need_version=no soname_spec='$libname$release$shared_ext$major' library_names_spec='$libname$release$shared_ext$versuffix $libname$release$shared_ext$major $libname$release$shared_ext $libname$shared_ext' case $host_os in irix5* | nonstopux*) libsuff= shlibsuff= ;; *) case $LD in # libtool.m4 will add one of these switches to LD *-32|*"-32 "|*-melf32bsmip|*"-melf32bsmip ") libsuff= shlibsuff= libmagic=32-bit;; *-n32|*"-n32 "|*-melf32bmipn32|*"-melf32bmipn32 ") libsuff=32 shlibsuff=N32 libmagic=N32;; *-64|*"-64 "|*-melf64bmip|*"-melf64bmip ") libsuff=64 shlibsuff=64 libmagic=64-bit;; *) libsuff= shlibsuff= libmagic=never-match;; esac ;; esac shlibpath_var=LD_LIBRARY${shlibsuff}_PATH shlibpath_overrides_runpath=no sys_lib_search_path_spec="/usr/lib$libsuff /lib$libsuff /usr/local/lib$libsuff" sys_lib_dlsearch_path_spec="/usr/lib$libsuff /lib$libsuff" hardcode_into_libs=yes ;; # No shared lib support for Linux oldld, aout, or coff. linux*oldld* | linux*aout* | linux*coff*) dynamic_linker=no ;; linux*android*) version_type=none # Android doesn't support versioned libraries. need_lib_prefix=no need_version=no library_names_spec='$libname$release$shared_ext' soname_spec='$libname$release$shared_ext' finish_cmds= shlibpath_var=LD_LIBRARY_PATH shlibpath_overrides_runpath=yes # This implies no fast_install, which is unacceptable. # Some rework will be needed to allow for fast_install # before this can be enabled. hardcode_into_libs=yes dynamic_linker='Android linker' # Don't embed -rpath directories since the linker doesn't support them. hardcode_libdir_flag_spec_CXX='-L$libdir' ;; # This must be glibc/ELF. linux* | k*bsd*-gnu | kopensolaris*-gnu | gnu*) version_type=linux # correct to gnu/linux during the next big refactor need_lib_prefix=no need_version=no library_names_spec='$libname$release$shared_ext$versuffix $libname$release$shared_ext$major $libname$shared_ext' soname_spec='$libname$release$shared_ext$major' finish_cmds='PATH="\$PATH:/sbin" ldconfig -n $libdir' shlibpath_var=LD_LIBRARY_PATH shlibpath_overrides_runpath=no # Some binutils ld are patched to set DT_RUNPATH if ${lt_cv_shlibpath_overrides_runpath+:} false; then : $as_echo_n "(cached) " >&6 else lt_cv_shlibpath_overrides_runpath=no save_LDFLAGS=$LDFLAGS save_libdir=$libdir eval "libdir=/foo; wl=\"$lt_prog_compiler_wl_CXX\"; \ LDFLAGS=\"\$LDFLAGS $hardcode_libdir_flag_spec_CXX\"" cat confdefs.h - <<_ACEOF >conftest.$ac_ext /* end confdefs.h. */ int main () { ; return 0; } _ACEOF if ac_fn_cxx_try_link "$LINENO"; then : if ($OBJDUMP -p conftest$ac_exeext) 2>/dev/null | grep "RUNPATH.*$libdir" >/dev/null; then : lt_cv_shlibpath_overrides_runpath=yes fi fi rm -f core conftest.err conftest.$ac_objext \ conftest$ac_exeext conftest.$ac_ext LDFLAGS=$save_LDFLAGS libdir=$save_libdir fi shlibpath_overrides_runpath=$lt_cv_shlibpath_overrides_runpath # This implies no fast_install, which is unacceptable. # Some rework will be needed to allow for fast_install # before this can be enabled. hardcode_into_libs=yes # Ideally, we could use ldconfig to report *all* directores which are # searched for libraries, however this is still not possible. Aside from not # being certain /sbin/ldconfig is available, command # 'ldconfig -N -X -v | grep ^/' on 64bit Fedora does not report /usr/lib64, # even though it is searched at run-time. Try to do the best guess by # appending ld.so.conf contents (and includes) to the search path. if test -f /etc/ld.so.conf; then lt_ld_extra=`awk '/^include / { system(sprintf("cd /etc; cat %s 2>/dev/null", \$2)); skip = 1; } { if (!skip) print \$0; skip = 0; }' < /etc/ld.so.conf | $SED -e 's/#.*//;/^[ ]*hwcap[ ]/d;s/[:, ]/ /g;s/=[^=]*$//;s/=[^= ]* / /g;s/"//g;/^$/d' | tr '\n' ' '` sys_lib_dlsearch_path_spec="/lib /usr/lib $lt_ld_extra" fi # We used to test for /lib/ld.so.1 and disable shared libraries on # powerpc, because MkLinux only supported shared libraries with the # GNU dynamic linker. Since this was broken with cross compilers, # most powerpc-linux boxes support dynamic linking these days and # people can always --disable-shared, the test was removed, and we # assume the GNU/Linux dynamic linker is in use. dynamic_linker='GNU/Linux ld.so' ;; netbsdelf*-gnu) version_type=linux need_lib_prefix=no need_version=no library_names_spec='${libname}${release}${shared_ext}$versuffix ${libname}${release}${shared_ext}$major ${libname}${shared_ext}' soname_spec='${libname}${release}${shared_ext}$major' shlibpath_var=LD_LIBRARY_PATH shlibpath_overrides_runpath=no hardcode_into_libs=yes dynamic_linker='NetBSD ld.elf_so' ;; netbsd*) version_type=sunos need_lib_prefix=no need_version=no if echo __ELF__ | $CC -E - | $GREP __ELF__ >/dev/null; then library_names_spec='$libname$release$shared_ext$versuffix $libname$shared_ext$versuffix' finish_cmds='PATH="\$PATH:/sbin" ldconfig -m $libdir' dynamic_linker='NetBSD (a.out) ld.so' else library_names_spec='$libname$release$shared_ext$versuffix $libname$release$shared_ext$major $libname$shared_ext' soname_spec='$libname$release$shared_ext$major' dynamic_linker='NetBSD ld.elf_so' fi shlibpath_var=LD_LIBRARY_PATH shlibpath_overrides_runpath=yes hardcode_into_libs=yes ;; newsos6) version_type=linux # correct to gnu/linux during the next big refactor library_names_spec='$libname$release$shared_ext$versuffix $libname$release$shared_ext$major $libname$shared_ext' shlibpath_var=LD_LIBRARY_PATH shlibpath_overrides_runpath=yes ;; *nto* | *qnx*) version_type=qnx need_lib_prefix=no need_version=no library_names_spec='$libname$release$shared_ext$versuffix $libname$release$shared_ext$major $libname$shared_ext' soname_spec='$libname$release$shared_ext$major' shlibpath_var=LD_LIBRARY_PATH shlibpath_overrides_runpath=no hardcode_into_libs=yes dynamic_linker='ldqnx.so' ;; openbsd* | bitrig*) version_type=sunos sys_lib_dlsearch_path_spec=/usr/lib need_lib_prefix=no if test -z "`echo __ELF__ | $CC -E - | $GREP __ELF__`"; then need_version=no else need_version=yes fi library_names_spec='$libname$release$shared_ext$versuffix $libname$shared_ext$versuffix' finish_cmds='PATH="\$PATH:/sbin" ldconfig -m $libdir' shlibpath_var=LD_LIBRARY_PATH shlibpath_overrides_runpath=yes ;; os2*) libname_spec='$name' version_type=windows shrext_cmds=.dll need_version=no need_lib_prefix=no # OS/2 can only load a DLL with a base name of 8 characters or less. soname_spec='`test -n "$os2dllname" && libname="$os2dllname"; v=$($ECHO $release$versuffix | tr -d .-); n=$($ECHO $libname | cut -b -$((8 - ${#v})) | tr . _); $ECHO $n$v`$shared_ext' library_names_spec='${libname}_dll.$libext' dynamic_linker='OS/2 ld.exe' shlibpath_var=BEGINLIBPATH sys_lib_search_path_spec="/lib /usr/lib /usr/local/lib" sys_lib_dlsearch_path_spec=$sys_lib_search_path_spec postinstall_cmds='base_file=`basename \$file`~ dlpath=`$SHELL 2>&1 -c '\''. $dir/'\''\$base_file'\''i; $ECHO \$dlname'\''`~ dldir=$destdir/`dirname \$dlpath`~ test -d \$dldir || mkdir -p \$dldir~ $install_prog $dir/$dlname \$dldir/$dlname~ chmod a+x \$dldir/$dlname~ if test -n '\''$stripme'\'' && test -n '\''$striplib'\''; then eval '\''$striplib \$dldir/$dlname'\'' || exit \$?; fi' postuninstall_cmds='dldll=`$SHELL 2>&1 -c '\''. $file; $ECHO \$dlname'\''`~ dlpath=$dir/\$dldll~ $RM \$dlpath' ;; osf3* | osf4* | osf5*) version_type=osf need_lib_prefix=no need_version=no soname_spec='$libname$release$shared_ext$major' library_names_spec='$libname$release$shared_ext$versuffix $libname$release$shared_ext$major $libname$shared_ext' shlibpath_var=LD_LIBRARY_PATH sys_lib_search_path_spec="/usr/shlib /usr/ccs/lib /usr/lib/cmplrs/cc /usr/lib /usr/local/lib /var/shlib" sys_lib_dlsearch_path_spec=$sys_lib_search_path_spec ;; rdos*) dynamic_linker=no ;; solaris*) version_type=linux # correct to gnu/linux during the next big refactor need_lib_prefix=no need_version=no library_names_spec='$libname$release$shared_ext$versuffix $libname$release$shared_ext$major $libname$shared_ext' soname_spec='$libname$release$shared_ext$major' shlibpath_var=LD_LIBRARY_PATH shlibpath_overrides_runpath=yes hardcode_into_libs=yes # ldd complains unless libraries are executable postinstall_cmds='chmod +x $lib' ;; sunos4*) version_type=sunos library_names_spec='$libname$release$shared_ext$versuffix $libname$shared_ext$versuffix' finish_cmds='PATH="\$PATH:/usr/etc" ldconfig $libdir' shlibpath_var=LD_LIBRARY_PATH shlibpath_overrides_runpath=yes if test yes = "$with_gnu_ld"; then need_lib_prefix=no fi need_version=yes ;; sysv4 | sysv4.3*) version_type=linux # correct to gnu/linux during the next big refactor library_names_spec='$libname$release$shared_ext$versuffix $libname$release$shared_ext$major $libname$shared_ext' soname_spec='$libname$release$shared_ext$major' shlibpath_var=LD_LIBRARY_PATH case $host_vendor in sni) shlibpath_overrides_runpath=no need_lib_prefix=no runpath_var=LD_RUN_PATH ;; siemens) need_lib_prefix=no ;; motorola) need_lib_prefix=no need_version=no shlibpath_overrides_runpath=no sys_lib_search_path_spec='/lib /usr/lib /usr/ccs/lib' ;; esac ;; sysv4*MP*) if test -d /usr/nec; then version_type=linux # correct to gnu/linux during the next big refactor library_names_spec='$libname$shared_ext.$versuffix $libname$shared_ext.$major $libname$shared_ext' soname_spec='$libname$shared_ext.$major' shlibpath_var=LD_LIBRARY_PATH fi ;; sysv5* | sco3.2v5* | sco5v6* | unixware* | OpenUNIX* | sysv4*uw2*) version_type=sco need_lib_prefix=no need_version=no library_names_spec='$libname$release$shared_ext$versuffix $libname$release$shared_ext $libname$shared_ext' soname_spec='$libname$release$shared_ext$major' shlibpath_var=LD_LIBRARY_PATH shlibpath_overrides_runpath=yes hardcode_into_libs=yes if test yes = "$with_gnu_ld"; then sys_lib_search_path_spec='/usr/local/lib /usr/gnu/lib /usr/ccs/lib /usr/lib /lib' else sys_lib_search_path_spec='/usr/ccs/lib /usr/lib' case $host_os in sco3.2v5*) sys_lib_search_path_spec="$sys_lib_search_path_spec /lib" ;; esac fi sys_lib_dlsearch_path_spec='/usr/lib' ;; tpf*) # TPF is a cross-target only. Preferred cross-host = GNU/Linux. version_type=linux # correct to gnu/linux during the next big refactor need_lib_prefix=no need_version=no library_names_spec='$libname$release$shared_ext$versuffix $libname$release$shared_ext$major $libname$shared_ext' shlibpath_var=LD_LIBRARY_PATH shlibpath_overrides_runpath=no hardcode_into_libs=yes ;; uts4*) version_type=linux # correct to gnu/linux during the next big refactor library_names_spec='$libname$release$shared_ext$versuffix $libname$release$shared_ext$major $libname$shared_ext' soname_spec='$libname$release$shared_ext$major' shlibpath_var=LD_LIBRARY_PATH ;; *) dynamic_linker=no ;; esac { $as_echo "$as_me:${as_lineno-$LINENO}: result: $dynamic_linker" >&5 $as_echo "$dynamic_linker" >&6; } test no = "$dynamic_linker" && can_build_shared=no variables_saved_for_relink="PATH $shlibpath_var $runpath_var" if test yes = "$GCC"; then variables_saved_for_relink="$variables_saved_for_relink GCC_EXEC_PREFIX COMPILER_PATH LIBRARY_PATH" fi if test set = "${lt_cv_sys_lib_search_path_spec+set}"; then sys_lib_search_path_spec=$lt_cv_sys_lib_search_path_spec fi if test set = "${lt_cv_sys_lib_dlsearch_path_spec+set}"; then sys_lib_dlsearch_path_spec=$lt_cv_sys_lib_dlsearch_path_spec fi # remember unaugmented sys_lib_dlsearch_path content for libtool script decls... configure_time_dlsearch_path=$sys_lib_dlsearch_path_spec # ... but it needs LT_SYS_LIBRARY_PATH munging for other configure-time code func_munge_path_list sys_lib_dlsearch_path_spec "$LT_SYS_LIBRARY_PATH" # to be used as default LT_SYS_LIBRARY_PATH value in generated libtool configure_time_lt_sys_library_path=$LT_SYS_LIBRARY_PATH { $as_echo "$as_me:${as_lineno-$LINENO}: checking how to hardcode library paths into programs" >&5 $as_echo_n "checking how to hardcode library paths into programs... " >&6; } hardcode_action_CXX= if test -n "$hardcode_libdir_flag_spec_CXX" || test -n "$runpath_var_CXX" || test yes = "$hardcode_automatic_CXX"; then # We can hardcode non-existent directories. if test no != "$hardcode_direct_CXX" && # If the only mechanism to avoid hardcoding is shlibpath_var, we # have to relink, otherwise we might link with an installed library # when we should be linking with a yet-to-be-installed one ## test no != "$_LT_TAGVAR(hardcode_shlibpath_var, CXX)" && test no != "$hardcode_minus_L_CXX"; then # Linking always hardcodes the temporary library directory. hardcode_action_CXX=relink else # We can link without hardcoding, and we can hardcode nonexisting dirs. hardcode_action_CXX=immediate fi else # We cannot hardcode anything, or else we can only hardcode existing # directories. hardcode_action_CXX=unsupported fi { $as_echo "$as_me:${as_lineno-$LINENO}: result: $hardcode_action_CXX" >&5 $as_echo "$hardcode_action_CXX" >&6; } if test relink = "$hardcode_action_CXX" || test yes = "$inherit_rpath_CXX"; then # Fast installation is not supported enable_fast_install=no elif test yes = "$shlibpath_overrides_runpath" || test no = "$enable_shared"; then # Fast installation is not necessary enable_fast_install=needless fi fi # test -n "$compiler" CC=$lt_save_CC CFLAGS=$lt_save_CFLAGS LDCXX=$LD LD=$lt_save_LD GCC=$lt_save_GCC with_gnu_ld=$lt_save_with_gnu_ld lt_cv_path_LDCXX=$lt_cv_path_LD lt_cv_path_LD=$lt_save_path_LD lt_cv_prog_gnu_ldcxx=$lt_cv_prog_gnu_ld lt_cv_prog_gnu_ld=$lt_save_with_gnu_ld fi # test yes != "$_lt_caught_CXX_error" ac_ext=c ac_cpp='$CPP $CPPFLAGS' ac_compile='$CC -c $CFLAGS $CPPFLAGS conftest.$ac_ext >&5' ac_link='$CC -o conftest$ac_exeext $CFLAGS $CPPFLAGS $LDFLAGS conftest.$ac_ext $LIBS >&5' ac_compiler_gnu=$ac_cv_c_compiler_gnu ac_config_commands="$ac_config_commands libtool" # Only expand once: if test "$enable_static_bin" = "yes"; then AC_LDFLAGS="-static" if test "$enable_static" != "yes"; then as_fn_error $? "The --enable-static-bin option must be combined with --enable-static." "$LINENO" 5 fi fi if test "$enable_shared" != "yes"; then as_fn_error $? "FRR cannot be built with --disable-shared. If you want statically linked daemons, use --enable-shared --enable-static --enable-static-bin" "$LINENO" 5 fi if test "x$enable_static_bin" = "xyes"; then STATIC_BIN_TRUE= STATIC_BIN_FALSE='#' else STATIC_BIN_TRUE='#' STATIC_BIN_FALSE= fi { $as_echo "$as_me:${as_lineno-$LINENO}: checking whether $AR supports D option" >&5 $as_echo_n "checking whether $AR supports D option... " >&6; } if $AR crD conftest.a >/dev/null 2>/dev/null; then { $as_echo "$as_me:${as_lineno-$LINENO}: result: yes" >&5 $as_echo "yes" >&6; } ARFLAGS="crD" AR_FLAGS="crD" else { $as_echo "$as_me:${as_lineno-$LINENO}: result: no" >&5 $as_echo "no" >&6; } ARFLAGS="cru" AR_FLAGS="cru" fi { $as_echo "$as_me:${as_lineno-$LINENO}: checking whether $RANLIB supports D option" >&5 $as_echo_n "checking whether $RANLIB supports D option... " >&6; } if $RANLIB -D conftest.a >conftest.err 2>&1; then if grep -q -- '-D' conftest.err; then { $as_echo "$as_me:${as_lineno-$LINENO}: result: no" >&5 $as_echo "no" >&6; } else { $as_echo "$as_me:${as_lineno-$LINENO}: result: yes" >&5 $as_echo "yes" >&6; } RANLIB="$RANLIB -D" fi else { $as_echo "$as_me:${as_lineno-$LINENO}: result: no" >&5 $as_echo "no" >&6; } fi test -f conftest.err && rm conftest.err test -f conftest.a && rm conftest.a if test -f config.version; then . ./config.version elif test -f "${srcdir}/config.version"; then . "${srcdir}/config.version" fi # Check whether --with-pkg-extra-version was given. if test "${with_pkg_extra_version+set}" = set; then : withval=$with_pkg_extra_version; if test "$withval" = "no"; then EXTRAVERSION= else EXTRAVERSION=$withval fi fi # Check whether --with-pkg-git-version was given. if test "${with_pkg_git_version+set}" = set; then : withval=$with_pkg_git_version; test "x$withval" != "xno" && with_pkg_git_version="yes" fi # Check whether --with-clippy was given. if test "${with_clippy+set}" = set; then : withval=$with_clippy; fi # Check whether --with-vtysh_pager was given. if test "${with_vtysh_pager+set}" = set; then : withval=$with_vtysh_pager; VTYSH_PAGER=$withval else VTYSH_PAGER="more" fi # Check whether --enable-vtysh was given. if test "${enable_vtysh+set}" = set; then : enableval=$enable_vtysh; fi # Check whether --enable-doc was given. if test "${enable_doc+set}" = set; then : enableval=$enable_doc; fi # Check whether --enable-doc-html was given. if test "${enable_doc_html+set}" = set; then : enableval=$enable_doc_html; fi # Check whether --enable-zebra was given. if test "${enable_zebra+set}" = set; then : enableval=$enable_zebra; fi # Check whether --enable-bgpd was given. if test "${enable_bgpd+set}" = set; then : enableval=$enable_bgpd; fi # Check whether --enable-ripd was given. if test "${enable_ripd+set}" = set; then : enableval=$enable_ripd; fi # Check whether --enable-ripngd was given. if test "${enable_ripngd+set}" = set; then : enableval=$enable_ripngd; fi # Check whether --enable-ospfd was given. if test "${enable_ospfd+set}" = set; then : enableval=$enable_ospfd; fi # Check whether --enable-ospf6d was given. if test "${enable_ospf6d+set}" = set; then : enableval=$enable_ospf6d; fi # Check whether --enable-ldpd was given. if test "${enable_ldpd+set}" = set; then : enableval=$enable_ldpd; fi # Check whether --enable-nhrpd was given. if test "${enable_nhrpd+set}" = set; then : enableval=$enable_nhrpd; fi # Check whether --enable-eigrpd was given. if test "${enable_eigrpd+set}" = set; then : enableval=$enable_eigrpd; fi # Check whether --enable-babeld was given. if test "${enable_babeld+set}" = set; then : enableval=$enable_babeld; fi # Check whether --enable-watchfrr was given. if test "${enable_watchfrr+set}" = set; then : enableval=$enable_watchfrr; fi # Check whether --enable-isisd was given. if test "${enable_isisd+set}" = set; then : enableval=$enable_isisd; fi # Check whether --enable-pimd was given. if test "${enable_pimd+set}" = set; then : enableval=$enable_pimd; fi # Check whether --enable-pbrd was given. if test "${enable_pbrd+set}" = set; then : enableval=$enable_pbrd; fi # Check whether --enable-sharpd was given. if test "${enable_sharpd+set}" = set; then : enableval=$enable_sharpd; fi # Check whether --enable-staticd was given. if test "${enable_staticd+set}" = set; then : enableval=$enable_staticd; fi # Check whether --enable-fabricd was given. if test "${enable_fabricd+set}" = set; then : enableval=$enable_fabricd; fi # Check whether --enable-vrrpd was given. if test "${enable_vrrpd+set}" = set; then : enableval=$enable_vrrpd; fi # Check whether --enable-bgp-announce was given. if test "${enable_bgp_announce+set}" = set; then : enableval=$enable_bgp_announce; fi # Check whether --enable-bgp-vnc was given. if test "${enable_bgp_vnc+set}" = set; then : enableval=$enable_bgp_vnc; fi # Check whether --enable-bgp-bmp was given. if test "${enable_bgp_bmp+set}" = set; then : enableval=$enable_bgp_bmp; fi # Check whether --enable-snmp was given. if test "${enable_snmp+set}" = set; then : enableval=$enable_snmp; fi # Check whether --enable-config_rollbacks was given. if test "${enable_config_rollbacks+set}" = set; then : enableval=$enable_config_rollbacks; fi # Check whether --enable-confd was given. if test "${enable_confd+set}" = set; then : enableval=$enable_confd; fi # Check whether --enable-sysrepo was given. if test "${enable_sysrepo+set}" = set; then : enableval=$enable_sysrepo; fi # Check whether --enable-grpc was given. if test "${enable_grpc+set}" = set; then : enableval=$enable_grpc; fi # Check whether --enable-zeromq was given. if test "${enable_zeromq+set}" = set; then : enableval=$enable_zeromq; fi # Check whether --with-libpam was given. if test "${with_libpam+set}" = set; then : withval=$with_libpam; fi # Check whether --enable-ospfapi was given. if test "${enable_ospfapi+set}" = set; then : enableval=$enable_ospfapi; fi # Check whether --enable-ospfclient was given. if test "${enable_ospfclient+set}" = set; then : enableval=$enable_ospfclient; fi # Check whether --enable-multipath was given. if test "${enable_multipath+set}" = set; then : enableval=$enable_multipath; fi # Check whether --enable-user was given. if test "${enable_user+set}" = set; then : enableval=$enable_user; fi # Check whether --enable-group was given. if test "${enable_group+set}" = set; then : enableval=$enable_group; fi # Check whether --enable-vty_group was given. if test "${enable_vty_group+set}" = set; then : enableval=$enable_vty_group; fi # Check whether --enable-configfile_mask was given. if test "${enable_configfile_mask+set}" = set; then : enableval=$enable_configfile_mask; fi # Check whether --enable-logfile_mask was given. if test "${enable_logfile_mask+set}" = set; then : enableval=$enable_logfile_mask; fi # Check whether --enable-shell_access was given. if test "${enable_shell_access+set}" = set; then : enableval=$enable_shell_access; fi # Check whether --enable-realms was given. if test "${enable_realms+set}" = set; then : enableval=$enable_realms; fi # Check whether --enable-rtadv was given. if test "${enable_rtadv+set}" = set; then : enableval=$enable_rtadv; fi # Check whether --enable-irdp was given. if test "${enable_irdp+set}" = set; then : enableval=$enable_irdp; fi # Check whether --enable-capabilities was given. if test "${enable_capabilities+set}" = set; then : enableval=$enable_capabilities; fi # Check whether --enable-rusage was given. if test "${enable_rusage+set}" = set; then : enableval=$enable_rusage; fi # Check whether --enable-gcc_ultra_verbose was given. if test "${enable_gcc_ultra_verbose+set}" = set; then : enableval=$enable_gcc_ultra_verbose; fi # Check whether --enable-backtrace was given. if test "${enable_backtrace+set}" = set; then : enableval=$enable_backtrace; fi # Check whether --enable-time-check was given. if test "${enable_time_check+set}" = set; then : enableval=$enable_time_check; fi # Check whether --enable-pcreposix was given. if test "${enable_pcreposix+set}" = set; then : enableval=$enable_pcreposix; fi # Check whether --enable-fpm was given. if test "${enable_fpm+set}" = set; then : enableval=$enable_fpm; fi # Check whether --enable-systemd was given. if test "${enable_systemd+set}" = set; then : enableval=$enable_systemd; fi # Check whether --enable-werror was given. if test "${enable_werror+set}" = set; then : enableval=$enable_werror; fi # Check whether --enable-cumulus was given. if test "${enable_cumulus+set}" = set; then : enableval=$enable_cumulus; fi # Check whether --enable-datacenter was given. if test "${enable_datacenter+set}" = set; then : enableval=$enable_datacenter; fi # Check whether --enable-fuzzing was given. if test "${enable_fuzzing+set}" = set; then : enableval=$enable_fuzzing; fi # Check whether --enable-netlink_fuzzing was given. if test "${enable_netlink_fuzzing+set}" = set; then : enableval=$enable_netlink_fuzzing; fi # Check whether --enable-rr-semantics was given. if test "${enable_rr_semantics+set}" = set; then : enableval=$enable_rr_semantics; fi # Check whether --enable-protobuf was given. if test "${enable_protobuf+set}" = set; then : enableval=$enable_protobuf; fi # Check whether --enable-oldvpn_commands was given. if test "${enable_oldvpn_commands+set}" = set; then : enableval=$enable_oldvpn_commands; fi # Check whether --enable-rpki was given. if test "${enable_rpki+set}" = set; then : enableval=$enable_rpki; fi # Check whether --enable-clippy-only was given. if test "${enable_clippy_only+set}" = set; then : enableval=$enable_clippy_only; fi # Check whether --enable-numeric_version was given. if test "${enable_numeric_version+set}" = set; then : enableval=$enable_numeric_version; fi # Check whether --enable-gcov was given. if test "${enable_gcov+set}" = set; then : enableval=$enable_gcov; fi # Check whether --enable-bfdd was given. if test "${enable_bfdd+set}" = set; then : enableval=$enable_bfdd; fi # Check whether --enable-address-sanitizer was given. if test "${enable_address_sanitizer+set}" = set; then : enableval=$enable_address_sanitizer; fi # Check whether --enable-thread-sanitizer was given. if test "${enable_thread_sanitizer+set}" = set; then : enableval=$enable_thread_sanitizer; fi # Check whether --enable-memory-sanitizer was given. if test "${enable_memory_sanitizer+set}" = set; then : enableval=$enable_memory_sanitizer; fi # Check whether --with-crypto was given. if test "${with_crypto+set}" = set; then : withval=$with_crypto; fi #if openssl, else use the internal if test x"${with_crypto}" = x"openssl"; then : { $as_echo "$as_me:${as_lineno-$LINENO}: checking for EVP_DigestInit in -lcrypto" >&5 $as_echo_n "checking for EVP_DigestInit in -lcrypto... " >&6; } if ${ac_cv_lib_crypto_EVP_DigestInit+:} false; then : $as_echo_n "(cached) " >&6 else ac_check_lib_save_LIBS=$LIBS LIBS="-lcrypto $LIBS" cat confdefs.h - <<_ACEOF >conftest.$ac_ext /* end confdefs.h. */ /* Override any GCC internal prototype to avoid an error. Use char because int might match the return type of a GCC builtin and then its argument prototype would still apply. */ #ifdef __cplusplus extern "C" #endif char EVP_DigestInit (); int main () { return EVP_DigestInit (); ; return 0; } _ACEOF if ac_fn_c_try_link "$LINENO"; then : ac_cv_lib_crypto_EVP_DigestInit=yes else ac_cv_lib_crypto_EVP_DigestInit=no fi rm -f core conftest.err conftest.$ac_objext \ conftest$ac_exeext conftest.$ac_ext LIBS=$ac_check_lib_save_LIBS fi { $as_echo "$as_me:${as_lineno-$LINENO}: result: $ac_cv_lib_crypto_EVP_DigestInit" >&5 $as_echo "$ac_cv_lib_crypto_EVP_DigestInit" >&6; } if test "x$ac_cv_lib_crypto_EVP_DigestInit" = xyes; then : LIBS="$LIBS -lcrypto" fi if test $ac_cv_lib_crypto_EVP_DigestInit = no; then as_fn_error $? "build with openssl has been specified but openssl library was not found on your system" "$LINENO" 5 else $as_echo "#define CRYPTO_OPENSSL 1" >>confdefs.h fi elif test x"${with_crypto}" = x"internal" || test x"${with_crypto}" = x"" ; then : $as_echo "#define CRYPTO_INTERNAL 1" >>confdefs.h else as_fn_error $? "Unknown value for --with-crypto" "$LINENO" 5 fi if test "${enable_clippy_only}" != "yes"; then : for ac_header in json-c/json.h do : ac_fn_c_check_header_mongrel "$LINENO" "json-c/json.h" "ac_cv_header_json_c_json_h" "$ac_includes_default" if test "x$ac_cv_header_json_c_json_h" = xyes; then : cat >>confdefs.h <<_ACEOF #define HAVE_JSON_C_JSON_H 1 _ACEOF fi done { $as_echo "$as_me:${as_lineno-$LINENO}: checking for json_object_get in -ljson-c" >&5 $as_echo_n "checking for json_object_get in -ljson-c... " >&6; } if ${ac_cv_lib_json_c_json_object_get+:} false; then : $as_echo_n "(cached) " >&6 else ac_check_lib_save_LIBS=$LIBS LIBS="-ljson-c -lm $LIBS" cat confdefs.h - <<_ACEOF >conftest.$ac_ext /* end confdefs.h. */ /* Override any GCC internal prototype to avoid an error. Use char because int might match the return type of a GCC builtin and then its argument prototype would still apply. */ #ifdef __cplusplus extern "C" #endif char json_object_get (); int main () { return json_object_get (); ; return 0; } _ACEOF if ac_fn_c_try_link "$LINENO"; then : ac_cv_lib_json_c_json_object_get=yes else ac_cv_lib_json_c_json_object_get=no fi rm -f core conftest.err conftest.$ac_objext \ conftest$ac_exeext conftest.$ac_ext LIBS=$ac_check_lib_save_LIBS fi { $as_echo "$as_me:${as_lineno-$LINENO}: result: $ac_cv_lib_json_c_json_object_get" >&5 $as_echo "$ac_cv_lib_json_c_json_object_get" >&6; } if test "x$ac_cv_lib_json_c_json_object_get" = xyes; then : LIBS="$LIBS -ljson-c" fi if test "$ac_cv_lib_json_c_json_object_get" = no; then { $as_echo "$as_me:${as_lineno-$LINENO}: checking for json_object_get in -ljson" >&5 $as_echo_n "checking for json_object_get in -ljson... " >&6; } if ${ac_cv_lib_json_json_object_get+:} false; then : $as_echo_n "(cached) " >&6 else ac_check_lib_save_LIBS=$LIBS LIBS="-ljson $LIBS" cat confdefs.h - <<_ACEOF >conftest.$ac_ext /* end confdefs.h. */ /* Override any GCC internal prototype to avoid an error. Use char because int might match the return type of a GCC builtin and then its argument prototype would still apply. */ #ifdef __cplusplus extern "C" #endif char json_object_get (); int main () { return json_object_get (); ; return 0; } _ACEOF if ac_fn_c_try_link "$LINENO"; then : ac_cv_lib_json_json_object_get=yes else ac_cv_lib_json_json_object_get=no fi rm -f core conftest.err conftest.$ac_objext \ conftest$ac_exeext conftest.$ac_ext LIBS=$ac_check_lib_save_LIBS fi { $as_echo "$as_me:${as_lineno-$LINENO}: result: $ac_cv_lib_json_json_object_get" >&5 $as_echo "$ac_cv_lib_json_json_object_get" >&6; } if test "x$ac_cv_lib_json_json_object_get" = xyes; then : LIBS="$LIBS -ljson" fi if test "$ac_cv_lib_json_json_object_get" = no; then as_fn_error $? "libjson is needed to compile" "$LINENO" 5 fi fi fi # Check whether --enable-dev_build was given. if test "${enable_dev_build+set}" = set; then : enableval=$enable_dev_build; fi # Check whether --enable-lua was given. if test "${enable_lua+set}" = set; then : enableval=$enable_lua; fi if test x"${enable_time_check}" != x"no" ; then if test x"${enable_time_check}" = x"yes" -o x"${enable_time_check}" = x ; then $as_echo "#define CONSUMED_TIME_CHECK 5000000" >>confdefs.h else cat >>confdefs.h <<_ACEOF #define CONSUMED_TIME_CHECK $enable_time_check _ACEOF fi fi case "${enable_systemd}" in "no") ;; "yes") { $as_echo "$as_me:${as_lineno-$LINENO}: checking for sd_notify in -lsystemd" >&5 $as_echo_n "checking for sd_notify in -lsystemd... " >&6; } if ${ac_cv_lib_systemd_sd_notify+:} false; then : $as_echo_n "(cached) " >&6 else ac_check_lib_save_LIBS=$LIBS LIBS="-lsystemd $LIBS" cat confdefs.h - <<_ACEOF >conftest.$ac_ext /* end confdefs.h. */ /* Override any GCC internal prototype to avoid an error. Use char because int might match the return type of a GCC builtin and then its argument prototype would still apply. */ #ifdef __cplusplus extern "C" #endif char sd_notify (); int main () { return sd_notify (); ; return 0; } _ACEOF if ac_fn_c_try_link "$LINENO"; then : ac_cv_lib_systemd_sd_notify=yes else ac_cv_lib_systemd_sd_notify=no fi rm -f core conftest.err conftest.$ac_objext \ conftest$ac_exeext conftest.$ac_ext LIBS=$ac_check_lib_save_LIBS fi { $as_echo "$as_me:${as_lineno-$LINENO}: result: $ac_cv_lib_systemd_sd_notify" >&5 $as_echo "$ac_cv_lib_systemd_sd_notify" >&6; } if test "x$ac_cv_lib_systemd_sd_notify" = xyes; then : LIBS="$LIBS -lsystemd" fi if test $ac_cv_lib_systemd_sd_notify = no; then as_fn_error $? "enable systemd has been specified but systemd development env not found on your system" "$LINENO" 5 else $as_echo "#define HAVE_SYSTEMD 1" >>confdefs.h fi ;; "*") ;; esac if test "${enable_rr_semantics}" != "no" ; then $as_echo "#define HAVE_V6_RR_SEMANTICS 1" >>confdefs.h fi if test "${enable_datacenter}" = "yes" ; then $as_echo "#define HAVE_DATACENTER 1" >>confdefs.h DFLT_NAME="datacenter" else DFLT_NAME="traditional" fi if test "${enable_fuzzing}" = "yes" ; then $as_echo "#define HANDLE_ZAPI_FUZZING 1" >>confdefs.h fi if test "${enable_netlink_fuzzing}" = "yes" ; then $as_echo "#define HANDLE_NETLINK_FUZZING 1" >>confdefs.h fi if test "${enable_cumulus}" = "yes" ; then $as_echo "#define HAVE_CUMULUS 1" >>confdefs.h fi cat >>confdefs.h <<_ACEOF #define DFLT_NAME "$DFLT_NAME" _ACEOF if test "${enable_shell_access}" = "yes"; then $as_echo "#define HAVE_SHELL_ACCESS 1" >>confdefs.h fi # # Python for clippy # if test "$host" = "$build"; then : if test "x$PYTHON" != "x"; then { $as_echo "$as_me:${as_lineno-$LINENO}: checking python interpreter $PYTHON" >&5 $as_echo_n "checking python interpreter $PYTHON... " >&6; } { { $as_echo "$as_me:${as_lineno-$LINENO}: \"\$PYTHON\" -c 'import sys; open(\"conftest.pyver\", \"w\").write(sys.executable or \"\"); sys.exit(not (sys.version_info.major == 2 and sys.version_info.minor >= 7))'"; } >&5 ("$PYTHON" -c 'import sys; open("conftest.pyver", "w").write(sys.executable or ""); sys.exit(not (sys.version_info.major == 2 and sys.version_info.minor >= 7))') 2>&5 ac_status=$? $as_echo "$as_me:${as_lineno-$LINENO}: \$? = $ac_status" >&5 test $ac_status = 0; } py2=$ac_status _py2_full="`cat conftest.pyver 2>/dev/null`" rm -f "conftest.pyver" >/dev/null 2>/dev/null { { $as_echo "$as_me:${as_lineno-$LINENO}: \"\$PYTHON\" -c 'import sys; open(\"conftest.pyver\", \"w\").write(sys.executable or \"\"); sys.exit(not ((sys.version_info.major == 3 and sys.version_info.minor >= 2) or sys.version_info.major > 3))'"; } >&5 ("$PYTHON" -c 'import sys; open("conftest.pyver", "w").write(sys.executable or ""); sys.exit(not ((sys.version_info.major == 3 and sys.version_info.minor >= 2) or sys.version_info.major > 3))') 2>&5 ac_status=$? $as_echo "$as_me:${as_lineno-$LINENO}: \$? = $ac_status" >&5 test $ac_status = 0; } py3=$ac_status _py3_full="`cat conftest.pyver 2>/dev/null`" rm -f "conftest.pyver" >/dev/null 2>/dev/null case "p${py2}p${py3}" in p0p1) frr_cv_python=python2 _python_full="$_py2_full" ;; p1p0) frr_cv_python=python3 _python_full="$_py3_full" ;; *) frr_cv_python=none ;; esac if test "$frr_cv_python" = none; then { $as_echo "$as_me:${as_lineno-$LINENO}: result: not working" >&5 $as_echo "not working" >&6; } as_fn_error $? "PYTHON ($PYTHON) explicitly specified but not working" "$LINENO" 5 else test -n "$_python_full" -a -x "$_python_full" && PYTHON="$_python_full" { $as_echo "$as_me:${as_lineno-$LINENO}: result: $PYTHON ($frr_cv_python)" >&5 $as_echo "$PYTHON ($frr_cv_python)" >&6; } fi test "$frr_cv_python" != none py_abi="` \"$PYTHON\" -c \"import sys; print(getattr(sys, 'abiflags', ''))\"`" py_hex="` \"$PYTHON\" -c \"import sys; print(hex(sys.hexversion))\"`" py_ldver="` \"$PYTHON\" -c \"import sysconfig; print(sysconfig.get_config_var('LDVERSION') or '')\"`" py_ver="` \"$PYTHON\" -c \"import sysconfig; print(sysconfig.get_config_var('VERSION') or '')\"`" py_bindir="`\"$PYTHON\" -c \"import sysconfig; print(sysconfig.get_config_var('BINDIR') or '')\"`" test -z "$py_bindir" || py_bindir="$py_bindir/" echo "py_abi=${py_abi} py_ldver=${py_ldver} py_ver=${py_ver} py_bindir=${py_bindir}" >&5 py_found=false for tryver in "${py_ldver}" "${py_ver}"; do pycfg="${py_bindir}python${tryver}-config" { $as_echo "$as_me:${as_lineno-$LINENO}: checking whether ${pycfg} is available" >&5 $as_echo_n "checking whether ${pycfg} is available... " >&6; } if "$pycfg" --configdir >/dev/null 2>/dev/null; then { $as_echo "$as_me:${as_lineno-$LINENO}: result: yes" >&5 $as_echo "yes" >&6; } PYTHON_CFLAGS="`\"$pycfg\" --includes`" if test x"${py_ver}" == x"3.8" || test x"{py_ver}" == x"3.9"; then PYTHON_LIBS="`\"$pycfg\" --ldflags --embed`" else PYTHON_LIBS="`\"$pycfg\" --ldflags`" fi { $as_echo "$as_me:${as_lineno-$LINENO}: checking whether ${pycfg} provides a working build environment" >&5 $as_echo_n "checking whether ${pycfg} provides a working build environment... " >&6; } result=true { ac_ext=c ac_cpp='$CPP $CPPFLAGS' ac_compile='$CC -c $CFLAGS $CPPFLAGS conftest.$ac_ext >&5' ac_link='$CC -o conftest$ac_exeext $CFLAGS $CPPFLAGS $LDFLAGS conftest.$ac_ext $LIBS >&5' ac_compiler_gnu=$ac_cv_c_compiler_gnu ac_cflags_save="$CFLAGS" ac_libs_save="$LIBS" CFLAGS="$CFLAGS $PYTHON_CFLAGS" LIBS="$LIBS $PYTHON_LIBS" cat confdefs.h - <<_ACEOF >conftest.$ac_ext /* end confdefs.h. */ #include #if PY_VERSION_HEX < 0x02070000 #error python too old #endif int main(void); int main () { { Py_Initialize(); return 0; } ; return 0; } _ACEOF if ac_fn_c_try_link "$LINENO"; then : CFLAGS="$ac_cflags_save" LIBS="$ac_libs_save" : else CFLAGS="$ac_cflags_save" LIBS="$ac_libs_save" # some python installs are missing the zlib dependency... PYTHON_LIBS="${PYTHON_LIBS} -lz" { ac_ext=c ac_cpp='$CPP $CPPFLAGS' ac_compile='$CC -c $CFLAGS $CPPFLAGS conftest.$ac_ext >&5' ac_link='$CC -o conftest$ac_exeext $CFLAGS $CPPFLAGS $LDFLAGS conftest.$ac_ext $LIBS >&5' ac_compiler_gnu=$ac_cv_c_compiler_gnu ac_cflags_save="$CFLAGS" ac_libs_save="$LIBS" CFLAGS="$CFLAGS $PYTHON_CFLAGS" LIBS="$LIBS $PYTHON_LIBS" cat confdefs.h - <<_ACEOF >conftest.$ac_ext /* end confdefs.h. */ #include #if PY_VERSION_HEX < 0x02070000 #error python too old #endif int main(void); int main () { { Py_Initialize(); return 0; } ; return 0; } _ACEOF if ac_fn_c_try_link "$LINENO"; then : CFLAGS="$ac_cflags_save" LIBS="$ac_libs_save" : else CFLAGS="$ac_cflags_save" LIBS="$ac_libs_save" result=false { $as_echo "$as_me:${as_lineno-$LINENO}: result: no" >&5 $as_echo "no" >&6; } fi rm -f core conftest.err conftest.$ac_objext \ conftest$ac_exeext conftest.$ac_ext ac_ext=c ac_cpp='$CPP $CPPFLAGS' ac_compile='$CC -c $CFLAGS $CPPFLAGS conftest.$ac_ext >&5' ac_link='$CC -o conftest$ac_exeext $CFLAGS $CPPFLAGS $LDFLAGS conftest.$ac_ext $LIBS >&5' ac_compiler_gnu=$ac_cv_c_compiler_gnu } fi rm -f core conftest.err conftest.$ac_objext \ conftest$ac_exeext conftest.$ac_ext ac_ext=c ac_cpp='$CPP $CPPFLAGS' ac_compile='$CC -c $CFLAGS $CPPFLAGS conftest.$ac_ext >&5' ac_link='$CC -o conftest$ac_exeext $CFLAGS $CPPFLAGS $LDFLAGS conftest.$ac_ext $LIBS >&5' ac_compiler_gnu=$ac_cv_c_compiler_gnu } if $result; then { ac_ext=c ac_cpp='$CPP $CPPFLAGS' ac_compile='$CC -c $CFLAGS $CPPFLAGS conftest.$ac_ext >&5' ac_link='$CC -o conftest$ac_exeext $CFLAGS $CPPFLAGS $LDFLAGS conftest.$ac_ext $LIBS >&5' ac_compiler_gnu=$ac_cv_c_compiler_gnu ac_cflags_save="$CFLAGS" ac_libs_save="$LIBS" CFLAGS="$CFLAGS $PYTHON_CFLAGS" LIBS="$LIBS $PYTHON_LIBS" cat confdefs.h - <<_ACEOF >conftest.$ac_ext /* end confdefs.h. */ #include #if PY_VERSION_HEX != $py_hex #error python version mismatch #endif int main(void); int main () { { Py_Initialize(); return 0; } ; return 0; } _ACEOF if ac_fn_c_try_link "$LINENO"; then : CFLAGS="$ac_cflags_save" LIBS="$ac_libs_save" { $as_echo "$as_me:${as_lineno-$LINENO}: result: yes" >&5 $as_echo "yes" >&6; } else CFLAGS="$ac_cflags_save" LIBS="$ac_libs_save" result=false { $as_echo "$as_me:${as_lineno-$LINENO}: result: version mismatch" >&5 $as_echo "version mismatch" >&6; } fi rm -f core conftest.err conftest.$ac_objext \ conftest$ac_exeext conftest.$ac_ext ac_ext=c ac_cpp='$CPP $CPPFLAGS' ac_compile='$CC -c $CFLAGS $CPPFLAGS conftest.$ac_ext >&5' ac_link='$CC -o conftest$ac_exeext $CFLAGS $CPPFLAGS $LDFLAGS conftest.$ac_ext $LIBS >&5' ac_compiler_gnu=$ac_cv_c_compiler_gnu } fi if $result; then py_found=true break else unset PYTHON_LIBS unset PYTHON_CFLAGS fi else { $as_echo "$as_me:${as_lineno-$LINENO}: result: no" >&5 $as_echo "no" >&6; } fi pkg_failed=no { $as_echo "$as_me:${as_lineno-$LINENO}: checking whether pkg-config python-${tryver} is available" >&5 $as_echo_n "checking whether pkg-config python-${tryver} is available... " >&6; } unset PYTHON_CFLAGS unset PYTHON_LIBS pkg="python-${tryver}" pkg="${pkg%-}" if test -n "$PYTHON_CFLAGS"; then pkg_cv_PYTHON_CFLAGS="$PYTHON_CFLAGS" elif test -n "$PKG_CONFIG"; then if test -n "$PKG_CONFIG" && \ { { $as_echo "$as_me:${as_lineno-$LINENO}: \$PKG_CONFIG --exists --print-errors \"\${pkg}\""; } >&5 ($PKG_CONFIG --exists --print-errors "${pkg}") 2>&5 ac_status=$? $as_echo "$as_me:${as_lineno-$LINENO}: \$? = $ac_status" >&5 test $ac_status = 0; }; then pkg_cv_PYTHON_CFLAGS=`$PKG_CONFIG --cflags "${pkg}" 2>/dev/null` test "x$?" != "x0" && pkg_failed=yes else pkg_failed=yes fi else pkg_failed=untried fi if test -n "$PYTHON_LIBS"; then pkg_cv_PYTHON_LIBS="$PYTHON_LIBS" elif test -n "$PKG_CONFIG"; then if test -n "$PKG_CONFIG" && \ { { $as_echo "$as_me:${as_lineno-$LINENO}: \$PKG_CONFIG --exists --print-errors \"\${pkg}\""; } >&5 ($PKG_CONFIG --exists --print-errors "${pkg}") 2>&5 ac_status=$? $as_echo "$as_me:${as_lineno-$LINENO}: \$? = $ac_status" >&5 test $ac_status = 0; }; then pkg_cv_PYTHON_LIBS=`$PKG_CONFIG --libs "${pkg}" 2>/dev/null` test "x$?" != "x0" && pkg_failed=yes else pkg_failed=yes fi else pkg_failed=untried fi if test $pkg_failed = no; then { $as_echo "$as_me:${as_lineno-$LINENO}: result: yes" >&5 $as_echo "yes" >&6; } PYTHON_CFLAGS=$pkg_cv_PYTHON_CFLAGS PYTHON_LIBS=$pkg_cv_PYTHON_LIBS { $as_echo "$as_me:${as_lineno-$LINENO}: checking whether pkg-config python-${tryver} provides a working build environment" >&5 $as_echo_n "checking whether pkg-config python-${tryver} provides a working build environment... " >&6; } result=true { ac_ext=c ac_cpp='$CPP $CPPFLAGS' ac_compile='$CC -c $CFLAGS $CPPFLAGS conftest.$ac_ext >&5' ac_link='$CC -o conftest$ac_exeext $CFLAGS $CPPFLAGS $LDFLAGS conftest.$ac_ext $LIBS >&5' ac_compiler_gnu=$ac_cv_c_compiler_gnu ac_cflags_save="$CFLAGS" ac_libs_save="$LIBS" CFLAGS="$CFLAGS $PYTHON_CFLAGS" LIBS="$LIBS $PYTHON_LIBS" cat confdefs.h - <<_ACEOF >conftest.$ac_ext /* end confdefs.h. */ #include #if PY_VERSION_HEX < 0x02070000 #error python too old #endif int main(void); int main () { { Py_Initialize(); return 0; } ; return 0; } _ACEOF if ac_fn_c_try_link "$LINENO"; then : CFLAGS="$ac_cflags_save" LIBS="$ac_libs_save" : else CFLAGS="$ac_cflags_save" LIBS="$ac_libs_save" # some python installs are missing the zlib dependency... PYTHON_LIBS="${PYTHON_LIBS} -lz" { ac_ext=c ac_cpp='$CPP $CPPFLAGS' ac_compile='$CC -c $CFLAGS $CPPFLAGS conftest.$ac_ext >&5' ac_link='$CC -o conftest$ac_exeext $CFLAGS $CPPFLAGS $LDFLAGS conftest.$ac_ext $LIBS >&5' ac_compiler_gnu=$ac_cv_c_compiler_gnu ac_cflags_save="$CFLAGS" ac_libs_save="$LIBS" CFLAGS="$CFLAGS $PYTHON_CFLAGS" LIBS="$LIBS $PYTHON_LIBS" cat confdefs.h - <<_ACEOF >conftest.$ac_ext /* end confdefs.h. */ #include #if PY_VERSION_HEX < 0x02070000 #error python too old #endif int main(void); int main () { { Py_Initialize(); return 0; } ; return 0; } _ACEOF if ac_fn_c_try_link "$LINENO"; then : CFLAGS="$ac_cflags_save" LIBS="$ac_libs_save" : else CFLAGS="$ac_cflags_save" LIBS="$ac_libs_save" result=false { $as_echo "$as_me:${as_lineno-$LINENO}: result: no" >&5 $as_echo "no" >&6; } fi rm -f core conftest.err conftest.$ac_objext \ conftest$ac_exeext conftest.$ac_ext ac_ext=c ac_cpp='$CPP $CPPFLAGS' ac_compile='$CC -c $CFLAGS $CPPFLAGS conftest.$ac_ext >&5' ac_link='$CC -o conftest$ac_exeext $CFLAGS $CPPFLAGS $LDFLAGS conftest.$ac_ext $LIBS >&5' ac_compiler_gnu=$ac_cv_c_compiler_gnu } fi rm -f core conftest.err conftest.$ac_objext \ conftest$ac_exeext conftest.$ac_ext ac_ext=c ac_cpp='$CPP $CPPFLAGS' ac_compile='$CC -c $CFLAGS $CPPFLAGS conftest.$ac_ext >&5' ac_link='$CC -o conftest$ac_exeext $CFLAGS $CPPFLAGS $LDFLAGS conftest.$ac_ext $LIBS >&5' ac_compiler_gnu=$ac_cv_c_compiler_gnu } if $result; then { ac_ext=c ac_cpp='$CPP $CPPFLAGS' ac_compile='$CC -c $CFLAGS $CPPFLAGS conftest.$ac_ext >&5' ac_link='$CC -o conftest$ac_exeext $CFLAGS $CPPFLAGS $LDFLAGS conftest.$ac_ext $LIBS >&5' ac_compiler_gnu=$ac_cv_c_compiler_gnu ac_cflags_save="$CFLAGS" ac_libs_save="$LIBS" CFLAGS="$CFLAGS $PYTHON_CFLAGS" LIBS="$LIBS $PYTHON_LIBS" cat confdefs.h - <<_ACEOF >conftest.$ac_ext /* end confdefs.h. */ #include #if PY_VERSION_HEX != $py_hex #error python version mismatch #endif int main(void); int main () { { Py_Initialize(); return 0; } ; return 0; } _ACEOF if ac_fn_c_try_link "$LINENO"; then : CFLAGS="$ac_cflags_save" LIBS="$ac_libs_save" { $as_echo "$as_me:${as_lineno-$LINENO}: result: yes" >&5 $as_echo "yes" >&6; } else CFLAGS="$ac_cflags_save" LIBS="$ac_libs_save" result=false { $as_echo "$as_me:${as_lineno-$LINENO}: result: version mismatch" >&5 $as_echo "version mismatch" >&6; } fi rm -f core conftest.err conftest.$ac_objext \ conftest$ac_exeext conftest.$ac_ext ac_ext=c ac_cpp='$CPP $CPPFLAGS' ac_compile='$CC -c $CFLAGS $CPPFLAGS conftest.$ac_ext >&5' ac_link='$CC -o conftest$ac_exeext $CFLAGS $CPPFLAGS $LDFLAGS conftest.$ac_ext $LIBS >&5' ac_compiler_gnu=$ac_cv_c_compiler_gnu } fi if $result; then py_found=true break else unset PYTHON_LIBS unset PYTHON_CFLAGS fi else { $as_echo "$as_me:${as_lineno-$LINENO}: result: no" >&5 $as_echo "no" >&6; } fi done if $py_found; then : else unset PYTHON_CFLAGS unset PYTHON_LIBS as_fn_error $? "PYTHON ($PYTHON) explicitly specified but development environment not working" "$LINENO" 5 fi else for frr_pyver in _3 _ _2 _3.7 _3.6 _3.5 _3.4 _3.3 _3.2 _2.7; do PYTHON="python${frr_pyver#_}" { $as_echo "$as_me:${as_lineno-$LINENO}: checking python interpreter $PYTHON" >&5 $as_echo_n "checking python interpreter $PYTHON... " >&6; } { { $as_echo "$as_me:${as_lineno-$LINENO}: \"\$PYTHON\" -c 'import sys; open(\"conftest.pyver\", \"w\").write(sys.executable or \"\"); sys.exit(not (sys.version_info.major == 2 and sys.version_info.minor >= 7))'"; } >&5 ("$PYTHON" -c 'import sys; open("conftest.pyver", "w").write(sys.executable or ""); sys.exit(not (sys.version_info.major == 2 and sys.version_info.minor >= 7))') 2>&5 ac_status=$? $as_echo "$as_me:${as_lineno-$LINENO}: \$? = $ac_status" >&5 test $ac_status = 0; } py2=$ac_status _py2_full="`cat conftest.pyver 2>/dev/null`" rm -f "conftest.pyver" >/dev/null 2>/dev/null { { $as_echo "$as_me:${as_lineno-$LINENO}: \"\$PYTHON\" -c 'import sys; open(\"conftest.pyver\", \"w\").write(sys.executable or \"\"); sys.exit(not ((sys.version_info.major == 3 and sys.version_info.minor >= 2) or sys.version_info.major > 3))'"; } >&5 ("$PYTHON" -c 'import sys; open("conftest.pyver", "w").write(sys.executable or ""); sys.exit(not ((sys.version_info.major == 3 and sys.version_info.minor >= 2) or sys.version_info.major > 3))') 2>&5 ac_status=$? $as_echo "$as_me:${as_lineno-$LINENO}: \$? = $ac_status" >&5 test $ac_status = 0; } py3=$ac_status _py3_full="`cat conftest.pyver 2>/dev/null`" rm -f "conftest.pyver" >/dev/null 2>/dev/null case "p${py2}p${py3}" in p0p1) frr_cv_python=python2 _python_full="$_py2_full" ;; p1p0) frr_cv_python=python3 _python_full="$_py3_full" ;; *) frr_cv_python=none ;; esac if test "$frr_cv_python" = none; then { $as_echo "$as_me:${as_lineno-$LINENO}: result: not working" >&5 $as_echo "not working" >&6; } else test -n "$_python_full" -a -x "$_python_full" && PYTHON="$_python_full" { $as_echo "$as_me:${as_lineno-$LINENO}: result: $PYTHON ($frr_cv_python)" >&5 $as_echo "$PYTHON ($frr_cv_python)" >&6; } py_abi="` \"$PYTHON\" -c \"import sys; print(getattr(sys, 'abiflags', ''))\"`" py_hex="` \"$PYTHON\" -c \"import sys; print(hex(sys.hexversion))\"`" py_ldver="` \"$PYTHON\" -c \"import sysconfig; print(sysconfig.get_config_var('LDVERSION') or '')\"`" py_ver="` \"$PYTHON\" -c \"import sysconfig; print(sysconfig.get_config_var('VERSION') or '')\"`" py_bindir="`\"$PYTHON\" -c \"import sysconfig; print(sysconfig.get_config_var('BINDIR') or '')\"`" test -z "$py_bindir" || py_bindir="$py_bindir/" echo "py_abi=${py_abi} py_ldver=${py_ldver} py_ver=${py_ver} py_bindir=${py_bindir}" >&5 py_found=false for tryver in "${py_ldver}" "${py_ver}"; do pycfg="${py_bindir}python${tryver}-config" { $as_echo "$as_me:${as_lineno-$LINENO}: checking whether ${pycfg} is available" >&5 $as_echo_n "checking whether ${pycfg} is available... " >&6; } if "$pycfg" --configdir >/dev/null 2>/dev/null; then { $as_echo "$as_me:${as_lineno-$LINENO}: result: yes" >&5 $as_echo "yes" >&6; } PYTHON_CFLAGS="`\"$pycfg\" --includes`" if test x"${py_ver}" == x"3.8" || test x"{py_ver}" == x"3.9"; then PYTHON_LIBS="`\"$pycfg\" --ldflags --embed`" else PYTHON_LIBS="`\"$pycfg\" --ldflags`" fi { $as_echo "$as_me:${as_lineno-$LINENO}: checking whether ${pycfg} provides a working build environment" >&5 $as_echo_n "checking whether ${pycfg} provides a working build environment... " >&6; } result=true { ac_ext=c ac_cpp='$CPP $CPPFLAGS' ac_compile='$CC -c $CFLAGS $CPPFLAGS conftest.$ac_ext >&5' ac_link='$CC -o conftest$ac_exeext $CFLAGS $CPPFLAGS $LDFLAGS conftest.$ac_ext $LIBS >&5' ac_compiler_gnu=$ac_cv_c_compiler_gnu ac_cflags_save="$CFLAGS" ac_libs_save="$LIBS" CFLAGS="$CFLAGS $PYTHON_CFLAGS" LIBS="$LIBS $PYTHON_LIBS" cat confdefs.h - <<_ACEOF >conftest.$ac_ext /* end confdefs.h. */ #include #if PY_VERSION_HEX < 0x02070000 #error python too old #endif int main(void); int main () { { Py_Initialize(); return 0; } ; return 0; } _ACEOF if ac_fn_c_try_link "$LINENO"; then : CFLAGS="$ac_cflags_save" LIBS="$ac_libs_save" : else CFLAGS="$ac_cflags_save" LIBS="$ac_libs_save" # some python installs are missing the zlib dependency... PYTHON_LIBS="${PYTHON_LIBS} -lz" { ac_ext=c ac_cpp='$CPP $CPPFLAGS' ac_compile='$CC -c $CFLAGS $CPPFLAGS conftest.$ac_ext >&5' ac_link='$CC -o conftest$ac_exeext $CFLAGS $CPPFLAGS $LDFLAGS conftest.$ac_ext $LIBS >&5' ac_compiler_gnu=$ac_cv_c_compiler_gnu ac_cflags_save="$CFLAGS" ac_libs_save="$LIBS" CFLAGS="$CFLAGS $PYTHON_CFLAGS" LIBS="$LIBS $PYTHON_LIBS" cat confdefs.h - <<_ACEOF >conftest.$ac_ext /* end confdefs.h. */ #include #if PY_VERSION_HEX < 0x02070000 #error python too old #endif int main(void); int main () { { Py_Initialize(); return 0; } ; return 0; } _ACEOF if ac_fn_c_try_link "$LINENO"; then : CFLAGS="$ac_cflags_save" LIBS="$ac_libs_save" : else CFLAGS="$ac_cflags_save" LIBS="$ac_libs_save" result=false { $as_echo "$as_me:${as_lineno-$LINENO}: result: no" >&5 $as_echo "no" >&6; } fi rm -f core conftest.err conftest.$ac_objext \ conftest$ac_exeext conftest.$ac_ext ac_ext=c ac_cpp='$CPP $CPPFLAGS' ac_compile='$CC -c $CFLAGS $CPPFLAGS conftest.$ac_ext >&5' ac_link='$CC -o conftest$ac_exeext $CFLAGS $CPPFLAGS $LDFLAGS conftest.$ac_ext $LIBS >&5' ac_compiler_gnu=$ac_cv_c_compiler_gnu } fi rm -f core conftest.err conftest.$ac_objext \ conftest$ac_exeext conftest.$ac_ext ac_ext=c ac_cpp='$CPP $CPPFLAGS' ac_compile='$CC -c $CFLAGS $CPPFLAGS conftest.$ac_ext >&5' ac_link='$CC -o conftest$ac_exeext $CFLAGS $CPPFLAGS $LDFLAGS conftest.$ac_ext $LIBS >&5' ac_compiler_gnu=$ac_cv_c_compiler_gnu } if $result; then { ac_ext=c ac_cpp='$CPP $CPPFLAGS' ac_compile='$CC -c $CFLAGS $CPPFLAGS conftest.$ac_ext >&5' ac_link='$CC -o conftest$ac_exeext $CFLAGS $CPPFLAGS $LDFLAGS conftest.$ac_ext $LIBS >&5' ac_compiler_gnu=$ac_cv_c_compiler_gnu ac_cflags_save="$CFLAGS" ac_libs_save="$LIBS" CFLAGS="$CFLAGS $PYTHON_CFLAGS" LIBS="$LIBS $PYTHON_LIBS" cat confdefs.h - <<_ACEOF >conftest.$ac_ext /* end confdefs.h. */ #include #if PY_VERSION_HEX != $py_hex #error python version mismatch #endif int main(void); int main () { { Py_Initialize(); return 0; } ; return 0; } _ACEOF if ac_fn_c_try_link "$LINENO"; then : CFLAGS="$ac_cflags_save" LIBS="$ac_libs_save" { $as_echo "$as_me:${as_lineno-$LINENO}: result: yes" >&5 $as_echo "yes" >&6; } else CFLAGS="$ac_cflags_save" LIBS="$ac_libs_save" result=false { $as_echo "$as_me:${as_lineno-$LINENO}: result: version mismatch" >&5 $as_echo "version mismatch" >&6; } fi rm -f core conftest.err conftest.$ac_objext \ conftest$ac_exeext conftest.$ac_ext ac_ext=c ac_cpp='$CPP $CPPFLAGS' ac_compile='$CC -c $CFLAGS $CPPFLAGS conftest.$ac_ext >&5' ac_link='$CC -o conftest$ac_exeext $CFLAGS $CPPFLAGS $LDFLAGS conftest.$ac_ext $LIBS >&5' ac_compiler_gnu=$ac_cv_c_compiler_gnu } fi if $result; then py_found=true break else unset PYTHON_LIBS unset PYTHON_CFLAGS fi else { $as_echo "$as_me:${as_lineno-$LINENO}: result: no" >&5 $as_echo "no" >&6; } fi pkg_failed=no { $as_echo "$as_me:${as_lineno-$LINENO}: checking whether pkg-config python-${tryver} is available" >&5 $as_echo_n "checking whether pkg-config python-${tryver} is available... " >&6; } unset PYTHON_CFLAGS unset PYTHON_LIBS pkg="python-${tryver}" pkg="${pkg%-}" if test -n "$PYTHON_CFLAGS"; then pkg_cv_PYTHON_CFLAGS="$PYTHON_CFLAGS" elif test -n "$PKG_CONFIG"; then if test -n "$PKG_CONFIG" && \ { { $as_echo "$as_me:${as_lineno-$LINENO}: \$PKG_CONFIG --exists --print-errors \"\${pkg}\""; } >&5 ($PKG_CONFIG --exists --print-errors "${pkg}") 2>&5 ac_status=$? $as_echo "$as_me:${as_lineno-$LINENO}: \$? = $ac_status" >&5 test $ac_status = 0; }; then pkg_cv_PYTHON_CFLAGS=`$PKG_CONFIG --cflags "${pkg}" 2>/dev/null` test "x$?" != "x0" && pkg_failed=yes else pkg_failed=yes fi else pkg_failed=untried fi if test -n "$PYTHON_LIBS"; then pkg_cv_PYTHON_LIBS="$PYTHON_LIBS" elif test -n "$PKG_CONFIG"; then if test -n "$PKG_CONFIG" && \ { { $as_echo "$as_me:${as_lineno-$LINENO}: \$PKG_CONFIG --exists --print-errors \"\${pkg}\""; } >&5 ($PKG_CONFIG --exists --print-errors "${pkg}") 2>&5 ac_status=$? $as_echo "$as_me:${as_lineno-$LINENO}: \$? = $ac_status" >&5 test $ac_status = 0; }; then pkg_cv_PYTHON_LIBS=`$PKG_CONFIG --libs "${pkg}" 2>/dev/null` test "x$?" != "x0" && pkg_failed=yes else pkg_failed=yes fi else pkg_failed=untried fi if test $pkg_failed = no; then { $as_echo "$as_me:${as_lineno-$LINENO}: result: yes" >&5 $as_echo "yes" >&6; } PYTHON_CFLAGS=$pkg_cv_PYTHON_CFLAGS PYTHON_LIBS=$pkg_cv_PYTHON_LIBS { $as_echo "$as_me:${as_lineno-$LINENO}: checking whether pkg-config python-${tryver} provides a working build environment" >&5 $as_echo_n "checking whether pkg-config python-${tryver} provides a working build environment... " >&6; } result=true { ac_ext=c ac_cpp='$CPP $CPPFLAGS' ac_compile='$CC -c $CFLAGS $CPPFLAGS conftest.$ac_ext >&5' ac_link='$CC -o conftest$ac_exeext $CFLAGS $CPPFLAGS $LDFLAGS conftest.$ac_ext $LIBS >&5' ac_compiler_gnu=$ac_cv_c_compiler_gnu ac_cflags_save="$CFLAGS" ac_libs_save="$LIBS" CFLAGS="$CFLAGS $PYTHON_CFLAGS" LIBS="$LIBS $PYTHON_LIBS" cat confdefs.h - <<_ACEOF >conftest.$ac_ext /* end confdefs.h. */ #include #if PY_VERSION_HEX < 0x02070000 #error python too old #endif int main(void); int main () { { Py_Initialize(); return 0; } ; return 0; } _ACEOF if ac_fn_c_try_link "$LINENO"; then : CFLAGS="$ac_cflags_save" LIBS="$ac_libs_save" : else CFLAGS="$ac_cflags_save" LIBS="$ac_libs_save" # some python installs are missing the zlib dependency... PYTHON_LIBS="${PYTHON_LIBS} -lz" { ac_ext=c ac_cpp='$CPP $CPPFLAGS' ac_compile='$CC -c $CFLAGS $CPPFLAGS conftest.$ac_ext >&5' ac_link='$CC -o conftest$ac_exeext $CFLAGS $CPPFLAGS $LDFLAGS conftest.$ac_ext $LIBS >&5' ac_compiler_gnu=$ac_cv_c_compiler_gnu ac_cflags_save="$CFLAGS" ac_libs_save="$LIBS" CFLAGS="$CFLAGS $PYTHON_CFLAGS" LIBS="$LIBS $PYTHON_LIBS" cat confdefs.h - <<_ACEOF >conftest.$ac_ext /* end confdefs.h. */ #include #if PY_VERSION_HEX < 0x02070000 #error python too old #endif int main(void); int main () { { Py_Initialize(); return 0; } ; return 0; } _ACEOF if ac_fn_c_try_link "$LINENO"; then : CFLAGS="$ac_cflags_save" LIBS="$ac_libs_save" : else CFLAGS="$ac_cflags_save" LIBS="$ac_libs_save" result=false { $as_echo "$as_me:${as_lineno-$LINENO}: result: no" >&5 $as_echo "no" >&6; } fi rm -f core conftest.err conftest.$ac_objext \ conftest$ac_exeext conftest.$ac_ext ac_ext=c ac_cpp='$CPP $CPPFLAGS' ac_compile='$CC -c $CFLAGS $CPPFLAGS conftest.$ac_ext >&5' ac_link='$CC -o conftest$ac_exeext $CFLAGS $CPPFLAGS $LDFLAGS conftest.$ac_ext $LIBS >&5' ac_compiler_gnu=$ac_cv_c_compiler_gnu } fi rm -f core conftest.err conftest.$ac_objext \ conftest$ac_exeext conftest.$ac_ext ac_ext=c ac_cpp='$CPP $CPPFLAGS' ac_compile='$CC -c $CFLAGS $CPPFLAGS conftest.$ac_ext >&5' ac_link='$CC -o conftest$ac_exeext $CFLAGS $CPPFLAGS $LDFLAGS conftest.$ac_ext $LIBS >&5' ac_compiler_gnu=$ac_cv_c_compiler_gnu } if $result; then { ac_ext=c ac_cpp='$CPP $CPPFLAGS' ac_compile='$CC -c $CFLAGS $CPPFLAGS conftest.$ac_ext >&5' ac_link='$CC -o conftest$ac_exeext $CFLAGS $CPPFLAGS $LDFLAGS conftest.$ac_ext $LIBS >&5' ac_compiler_gnu=$ac_cv_c_compiler_gnu ac_cflags_save="$CFLAGS" ac_libs_save="$LIBS" CFLAGS="$CFLAGS $PYTHON_CFLAGS" LIBS="$LIBS $PYTHON_LIBS" cat confdefs.h - <<_ACEOF >conftest.$ac_ext /* end confdefs.h. */ #include #if PY_VERSION_HEX != $py_hex #error python version mismatch #endif int main(void); int main () { { Py_Initialize(); return 0; } ; return 0; } _ACEOF if ac_fn_c_try_link "$LINENO"; then : CFLAGS="$ac_cflags_save" LIBS="$ac_libs_save" { $as_echo "$as_me:${as_lineno-$LINENO}: result: yes" >&5 $as_echo "yes" >&6; } else CFLAGS="$ac_cflags_save" LIBS="$ac_libs_save" result=false { $as_echo "$as_me:${as_lineno-$LINENO}: result: version mismatch" >&5 $as_echo "version mismatch" >&6; } fi rm -f core conftest.err conftest.$ac_objext \ conftest$ac_exeext conftest.$ac_ext ac_ext=c ac_cpp='$CPP $CPPFLAGS' ac_compile='$CC -c $CFLAGS $CPPFLAGS conftest.$ac_ext >&5' ac_link='$CC -o conftest$ac_exeext $CFLAGS $CPPFLAGS $LDFLAGS conftest.$ac_ext $LIBS >&5' ac_compiler_gnu=$ac_cv_c_compiler_gnu } fi if $result; then py_found=true break else unset PYTHON_LIBS unset PYTHON_CFLAGS fi else { $as_echo "$as_me:${as_lineno-$LINENO}: result: no" >&5 $as_echo "no" >&6; } fi done if $py_found; then break else unset PYTHON_CFLAGS unset PYTHON_LIBS : fi fi test "$frr_cv_python" != none PYTHON=":" done if test "$PYTHON" = ":"; then as_fn_error $? "no working python version found" "$LINENO" 5 fi fi else if test "x$PYTHON" != "x"; then { $as_echo "$as_me:${as_lineno-$LINENO}: checking python interpreter $PYTHON" >&5 $as_echo_n "checking python interpreter $PYTHON... " >&6; } { { $as_echo "$as_me:${as_lineno-$LINENO}: \"\$PYTHON\" -c 'import sys; open(\"conftest.pyver\", \"w\").write(sys.executable or \"\"); sys.exit(not (sys.version_info.major == 2 and sys.version_info.minor >= 7))'"; } >&5 ("$PYTHON" -c 'import sys; open("conftest.pyver", "w").write(sys.executable or ""); sys.exit(not (sys.version_info.major == 2 and sys.version_info.minor >= 7))') 2>&5 ac_status=$? $as_echo "$as_me:${as_lineno-$LINENO}: \$? = $ac_status" >&5 test $ac_status = 0; } py2=$ac_status _py2_full="`cat conftest.pyver 2>/dev/null`" rm -f "conftest.pyver" >/dev/null 2>/dev/null { { $as_echo "$as_me:${as_lineno-$LINENO}: \"\$PYTHON\" -c 'import sys; open(\"conftest.pyver\", \"w\").write(sys.executable or \"\"); sys.exit(not ((sys.version_info.major == 3 and sys.version_info.minor >= 2) or sys.version_info.major > 3))'"; } >&5 ("$PYTHON" -c 'import sys; open("conftest.pyver", "w").write(sys.executable or ""); sys.exit(not ((sys.version_info.major == 3 and sys.version_info.minor >= 2) or sys.version_info.major > 3))') 2>&5 ac_status=$? $as_echo "$as_me:${as_lineno-$LINENO}: \$? = $ac_status" >&5 test $ac_status = 0; } py3=$ac_status _py3_full="`cat conftest.pyver 2>/dev/null`" rm -f "conftest.pyver" >/dev/null 2>/dev/null case "p${py2}p${py3}" in p0p1) frr_cv_python=python2 _python_full="$_py2_full" ;; p1p0) frr_cv_python=python3 _python_full="$_py3_full" ;; *) frr_cv_python=none ;; esac if test "$frr_cv_python" = none; then { $as_echo "$as_me:${as_lineno-$LINENO}: result: not working" >&5 $as_echo "not working" >&6; } as_fn_error $? "PYTHON ($PYTHON) explicitly specified but not working" "$LINENO" 5 else test -n "$_python_full" -a -x "$_python_full" && PYTHON="$_python_full" { $as_echo "$as_me:${as_lineno-$LINENO}: result: $PYTHON ($frr_cv_python)" >&5 $as_echo "$PYTHON ($frr_cv_python)" >&6; } fi test "$frr_cv_python" != none else for frr_pyver in _3 _ _2 _3.7 _3.6 _3.5 _3.4 _3.3 _3.2 _2.7; do PYTHON="python${frr_pyver#_}" { $as_echo "$as_me:${as_lineno-$LINENO}: checking python interpreter $PYTHON" >&5 $as_echo_n "checking python interpreter $PYTHON... " >&6; } { { $as_echo "$as_me:${as_lineno-$LINENO}: \"\$PYTHON\" -c 'import sys; open(\"conftest.pyver\", \"w\").write(sys.executable or \"\"); sys.exit(not (sys.version_info.major == 2 and sys.version_info.minor >= 7))'"; } >&5 ("$PYTHON" -c 'import sys; open("conftest.pyver", "w").write(sys.executable or ""); sys.exit(not (sys.version_info.major == 2 and sys.version_info.minor >= 7))') 2>&5 ac_status=$? $as_echo "$as_me:${as_lineno-$LINENO}: \$? = $ac_status" >&5 test $ac_status = 0; } py2=$ac_status _py2_full="`cat conftest.pyver 2>/dev/null`" rm -f "conftest.pyver" >/dev/null 2>/dev/null { { $as_echo "$as_me:${as_lineno-$LINENO}: \"\$PYTHON\" -c 'import sys; open(\"conftest.pyver\", \"w\").write(sys.executable or \"\"); sys.exit(not ((sys.version_info.major == 3 and sys.version_info.minor >= 2) or sys.version_info.major > 3))'"; } >&5 ("$PYTHON" -c 'import sys; open("conftest.pyver", "w").write(sys.executable or ""); sys.exit(not ((sys.version_info.major == 3 and sys.version_info.minor >= 2) or sys.version_info.major > 3))') 2>&5 ac_status=$? $as_echo "$as_me:${as_lineno-$LINENO}: \$? = $ac_status" >&5 test $ac_status = 0; } py3=$ac_status _py3_full="`cat conftest.pyver 2>/dev/null`" rm -f "conftest.pyver" >/dev/null 2>/dev/null case "p${py2}p${py3}" in p0p1) frr_cv_python=python2 _python_full="$_py2_full" ;; p1p0) frr_cv_python=python3 _python_full="$_py3_full" ;; *) frr_cv_python=none ;; esac if test "$frr_cv_python" = none; then { $as_echo "$as_me:${as_lineno-$LINENO}: result: not working" >&5 $as_echo "not working" >&6; } else test -n "$_python_full" -a -x "$_python_full" && PYTHON="$_python_full" { $as_echo "$as_me:${as_lineno-$LINENO}: result: $PYTHON ($frr_cv_python)" >&5 $as_echo "$PYTHON ($frr_cv_python)" >&6; } break fi test "$frr_cv_python" != none PYTHON=":" done if test "$PYTHON" = ":"; then as_fn_error $? "no working python version found" "$LINENO" 5 fi fi fi result=true for pymod in pytest; do { $as_echo "$as_me:${as_lineno-$LINENO}: checking whether $PYTHON module $pymod is available" >&5 $as_echo_n "checking whether $PYTHON module $pymod is available... " >&6; } { { $as_echo "$as_me:${as_lineno-$LINENO}: \"\$PYTHON\" -c \"import \$pymod\""; } >&5 ("$PYTHON" -c "import $pymod") 2>&5 ac_status=$? $as_echo "$as_me:${as_lineno-$LINENO}: \$? = $ac_status" >&5 test $ac_status = 0; } sane="`echo \"$pymod\" | tr -c 'a-zA-Z0-9\n' '_'`" if test "$ac_status" -eq 0; then { $as_echo "$as_me:${as_lineno-$LINENO}: result: yes" >&5 $as_echo "yes" >&6; } eval frr_py_mod_$sane=true else { $as_echo "$as_me:${as_lineno-$LINENO}: result: no" >&5 $as_echo "no" >&6; } eval frr_py_mod_$sane=false result=false fi done if $result; then : else : fi $result if test "${enable_doc}" != "no"; then result=true for pymod in sphinx; do { $as_echo "$as_me:${as_lineno-$LINENO}: checking whether $PYTHON module $pymod is available" >&5 $as_echo_n "checking whether $PYTHON module $pymod is available... " >&6; } { { $as_echo "$as_me:${as_lineno-$LINENO}: \"\$PYTHON\" -c \"import \$pymod\""; } >&5 ("$PYTHON" -c "import $pymod") 2>&5 ac_status=$? $as_echo "$as_me:${as_lineno-$LINENO}: \$? = $ac_status" >&5 test $ac_status = 0; } sane="`echo \"$pymod\" | tr -c 'a-zA-Z0-9\n' '_'`" if test "$ac_status" -eq 0; then { $as_echo "$as_me:${as_lineno-$LINENO}: result: yes" >&5 $as_echo "yes" >&6; } eval frr_py_mod_$sane=true else { $as_echo "$as_me:${as_lineno-$LINENO}: result: no" >&5 $as_echo "no" >&6; } eval frr_py_mod_$sane=false result=false fi done if $result; then : else if test "${enable_doc}" = "yes"; then as_fn_error $? "Documentation was explicitly requested with --enable-doc but sphinx is not available for $PYTHON. Please disable docs or install sphinx." "$LINENO" 5 fi fi $result fi if test "${enable_doc}" != "no" -a "$frr_py_mod_sphinx" != "false"; then DOC_TRUE= DOC_FALSE='#' else DOC_TRUE='#' DOC_FALSE= fi if test "${enable_doc_html}" = "yes"; then DOC_HTML_TRUE= DOC_HTML_FALSE='#' else DOC_HTML_TRUE='#' DOC_HTML_FALSE= fi result=true for pymod in sphinx; do { $as_echo "$as_me:${as_lineno-$LINENO}: checking whether $PYTHON module $pymod is executable" >&5 $as_echo_n "checking whether $PYTHON module $pymod is executable... " >&6; } { { $as_echo "$as_me:${as_lineno-$LINENO}: \"\$PYTHON\" -m \"\$pymod\" --version > /dev/null"; } >&5 ("$PYTHON" -m "$pymod" --version > /dev/null) 2>&5 ac_status=$? $as_echo "$as_me:${as_lineno-$LINENO}: \$? = $ac_status" >&5 test $ac_status = 0; } sane="`echo \"$pymod\" | tr -c 'a-zA-Z0-9\n' '_'`" if test "$ac_status" -eq 0; then { $as_echo "$as_me:${as_lineno-$LINENO}: result: yes" >&5 $as_echo "yes" >&6; } eval frr_py_modexec_$sane=true else { $as_echo "$as_me:${as_lineno-$LINENO}: result: no" >&5 $as_echo "no" >&6; } eval frr_py_modexec_$sane=false result=false fi done if $result; then PYSPHINX="-m sphinx" else PYSPHINX="-c 'import sys; from sphinx import main; sys.exit(main(sys.argv))'" fi $result # # Logic for old vpn commands support. # if test "$enable_oldvpn_commands" = "yes"; then $as_echo "#define KEEP_OLD_VPN_COMMANDS 1" >>confdefs.h fi # # End of logic for protobuf support. # { $as_echo "$as_me:${as_lineno-$LINENO}: checking if zebra should be configurable to send Route Advertisements" >&5 $as_echo_n "checking if zebra should be configurable to send Route Advertisements... " >&6; } if test "${enable_rtadv}" != "no"; then { $as_echo "$as_me:${as_lineno-$LINENO}: result: yes" >&5 $as_echo "yes" >&6; } $as_echo "#define HAVE_RTADV 1" >>confdefs.h else { $as_echo "$as_me:${as_lineno-$LINENO}: result: no" >&5 $as_echo "no" >&6; } fi if test x"${enable_user}" = x"no"; then enable_user="" else if test x"${enable_user}" = x"yes" || test x"${enable_user}" = x""; then enable_user="frr" fi cat >>confdefs.h <<_ACEOF #define FRR_USER "${enable_user}" _ACEOF fi if test x"${enable_group}" = x"no"; then enable_group="" else if test x"${enable_group}" = x"yes" || test x"${enable_group}" = x""; then enable_group="frr" fi cat >>confdefs.h <<_ACEOF #define FRR_GROUP "${enable_group}" _ACEOF fi if test x"${enable_vty_group}" = x"yes" ; then as_fn_error $? "--enable-vty-group requires a group as argument, not yes" "$LINENO" 5 elif test x"${enable_vty_group}" != x""; then if test x"${enable_vty_group}" != x"no"; then cat >>confdefs.h <<_ACEOF #define VTY_GROUP "${enable_vty_group}" _ACEOF fi fi enable_configfile_mask=${enable_configfile_mask:-0600} cat >>confdefs.h <<_ACEOF #define CONFIGFILE_MASK ${enable_configfile_mask} _ACEOF enable_logfile_mask=${enable_logfile_mask:-0600} cat >>confdefs.h <<_ACEOF #define LOGFILE_MASK ${enable_logfile_mask} _ACEOF MPATH_NUM=16 case "${enable_multipath}" in 0) MPATH_NUM=64 ;; [1-9]|[1-9][0-9]|[1-9][0-9][0-9]) MPATH_NUM="${enable_multipath}" ;; "") ;; *) { { $as_echo "$as_me:${as_lineno-$LINENO}: error: in \`$ac_pwd':" >&5 $as_echo "$as_me: error: in \`$ac_pwd':" >&2;} as_fn_error $? "Please specify digit to enable multipath ARG See \`config.log' for more details" "$LINENO" 5; } ;; esac cat >>confdefs.h <<_ACEOF #define MULTIPATH_NUM $MPATH_NUM _ACEOF cat >>confdefs.h <<_ACEOF #define VTYSH_PAGER "$VTYSH_PAGER" _ACEOF if test '!' "$enable_gcov" = no; then HAVE_GCOV_TRUE= HAVE_GCOV_FALSE='#' else HAVE_GCOV_TRUE='#' HAVE_GCOV_FALSE= fi if test "x${enable_numeric_version}" != "x" ; then VERSION="`echo ${VERSION} | tr -c -d '[.0-9]'`" PACKAGE_VERSION="`echo ${PACKAGE_VERSION} | tr -c -d '[.0-9]'`" fi if test "x${EXTRAVERSION}" != "x" ; then VERSION="${VERSION}${EXTRAVERSION}" PACKAGE_VERSION="${PACKAGE_VERSION}${EXTRAVERSION}" PACKAGE_EXTRAVERSION="${EXTRAVERSION}" PACKAGE_STRING="${PACKAGE_STRING}${EXTRAVERSION}" fi if test "x$with_pkg_git_version" = "xyes"; then if test -d "${srcdir}/.git"; then $as_echo "#define GIT_VERSION 1" >>confdefs.h else with_pkg_git_version="no" { $as_echo "$as_me:${as_lineno-$LINENO}: WARNING: --with-pkg-git-version given, but this is not a git checkout" >&5 $as_echo "$as_me: WARNING: --with-pkg-git-version given, but this is not a git checkout" >&2;} fi fi if test "x$with_pkg_git_version" = "xyes"; then GIT_VERSION_TRUE= GIT_VERSION_FALSE='#' else GIT_VERSION_TRUE='#' GIT_VERSION_FALSE= fi if test -n "$ac_tool_prefix"; then # Extract the first word of "${ac_tool_prefix}objcopy", so it can be a program name with args. set dummy ${ac_tool_prefix}objcopy; ac_word=$2 { $as_echo "$as_me:${as_lineno-$LINENO}: checking for $ac_word" >&5 $as_echo_n "checking for $ac_word... " >&6; } if ${ac_cv_prog_OBJCOPY+:} false; then : $as_echo_n "(cached) " >&6 else if test -n "$OBJCOPY"; then ac_cv_prog_OBJCOPY="$OBJCOPY" # Let the user override the test. else as_save_IFS=$IFS; IFS=$PATH_SEPARATOR for as_dir in $PATH do IFS=$as_save_IFS test -z "$as_dir" && as_dir=. for ac_exec_ext in '' $ac_executable_extensions; do if as_fn_executable_p "$as_dir/$ac_word$ac_exec_ext"; then ac_cv_prog_OBJCOPY="${ac_tool_prefix}objcopy" $as_echo "$as_me:${as_lineno-$LINENO}: found $as_dir/$ac_word$ac_exec_ext" >&5 break 2 fi done done IFS=$as_save_IFS fi fi OBJCOPY=$ac_cv_prog_OBJCOPY if test -n "$OBJCOPY"; then { $as_echo "$as_me:${as_lineno-$LINENO}: result: $OBJCOPY" >&5 $as_echo "$OBJCOPY" >&6; } else { $as_echo "$as_me:${as_lineno-$LINENO}: result: no" >&5 $as_echo "no" >&6; } fi fi if test -z "$ac_cv_prog_OBJCOPY"; then ac_ct_OBJCOPY=$OBJCOPY # Extract the first word of "objcopy", so it can be a program name with args. set dummy objcopy; ac_word=$2 { $as_echo "$as_me:${as_lineno-$LINENO}: checking for $ac_word" >&5 $as_echo_n "checking for $ac_word... " >&6; } if ${ac_cv_prog_ac_ct_OBJCOPY+:} false; then : $as_echo_n "(cached) " >&6 else if test -n "$ac_ct_OBJCOPY"; then ac_cv_prog_ac_ct_OBJCOPY="$ac_ct_OBJCOPY" # Let the user override the test. else as_save_IFS=$IFS; IFS=$PATH_SEPARATOR for as_dir in $PATH do IFS=$as_save_IFS test -z "$as_dir" && as_dir=. for ac_exec_ext in '' $ac_executable_extensions; do if as_fn_executable_p "$as_dir/$ac_word$ac_exec_ext"; then ac_cv_prog_ac_ct_OBJCOPY="objcopy" $as_echo "$as_me:${as_lineno-$LINENO}: found $as_dir/$ac_word$ac_exec_ext" >&5 break 2 fi done done IFS=$as_save_IFS fi fi ac_ct_OBJCOPY=$ac_cv_prog_ac_ct_OBJCOPY if test -n "$ac_ct_OBJCOPY"; then { $as_echo "$as_me:${as_lineno-$LINENO}: result: $ac_ct_OBJCOPY" >&5 $as_echo "$ac_ct_OBJCOPY" >&6; } else { $as_echo "$as_me:${as_lineno-$LINENO}: result: no" >&5 $as_echo "no" >&6; } fi if test "x$ac_ct_OBJCOPY" = x; then OBJCOPY=":" else case $cross_compiling:$ac_tool_warned in yes:) { $as_echo "$as_me:${as_lineno-$LINENO}: WARNING: using cross tools not prefixed with host triplet" >&5 $as_echo "$as_me: WARNING: using cross tools not prefixed with host triplet" >&2;} ac_tool_warned=yes ;; esac OBJCOPY=$ac_ct_OBJCOPY fi else OBJCOPY="$ac_cv_prog_OBJCOPY" fi if test "x${OBJCOPY}" != "x:"; then { $as_echo "$as_me:${as_lineno-$LINENO}: checking for .interp value to use" >&5 $as_echo_n "checking for .interp value to use... " >&6; } if ${frr_cv_interp+:} false; then : $as_echo_n "(cached) " >&6 else frr_cv_interp="" cat confdefs.h - <<_ACEOF >conftest.$ac_ext /* end confdefs.h. */ int main() { return 0; } _ACEOF if ac_fn_c_try_link "$LINENO"; then : if $OBJCOPY -j.interp -Obinary conftest conftest.interp; then frr_cv_interp="`xargs -0 echo < conftest.interp`" fi test -f conftest.interp && rm conftest.interp fi rm -f core conftest.err conftest.$ac_objext \ conftest$ac_exeext conftest.$ac_ext fi { $as_echo "$as_me:${as_lineno-$LINENO}: result: $frr_cv_interp" >&5 $as_echo "$frr_cv_interp" >&6; } fi if test -n "$frr_cv_interp"; then cat >>confdefs.h <<_ACEOF #define INTERP "$frr_cv_interp" _ACEOF fi for ac_header in stropts.h sys/ksym.h \ linux/version.h asm/types.h do : as_ac_Header=`$as_echo "ac_cv_header_$ac_header" | $as_tr_sh` ac_fn_c_check_header_mongrel "$LINENO" "$ac_header" "$as_ac_Header" "$ac_includes_default" if eval test \"x\$"$as_ac_Header"\" = x"yes"; then : cat >>confdefs.h <<_ACEOF #define `$as_echo "HAVE_$ac_header" | $as_tr_cpp` 1 _ACEOF fi done ac_stdatomic_ok=false $as_echo "#define FRR_AUTOCONF_ATOMIC 1" >>confdefs.h ac_fn_c_check_header_mongrel "$LINENO" "stdatomic.h" "ac_cv_header_stdatomic_h" "$ac_includes_default" if test "x$ac_cv_header_stdatomic_h" = xyes; then : { $as_echo "$as_me:${as_lineno-$LINENO}: checking whether _Atomic qualifier works" >&5 $as_echo_n "checking whether _Atomic qualifier works... " >&6; } cat confdefs.h - <<_ACEOF >conftest.$ac_ext /* end confdefs.h. */ #include int main(int argc, char **argv) { _Atomic int i = 0; return i; } _ACEOF if ac_fn_c_try_link "$LINENO"; then : $as_echo "#define HAVE_STDATOMIC_H 1" >>confdefs.h { $as_echo "$as_me:${as_lineno-$LINENO}: result: yes" >&5 $as_echo "yes" >&6; } ac_stdatomic_ok=true else { $as_echo "$as_me:${as_lineno-$LINENO}: result: no" >&5 $as_echo "no" >&6; } fi rm -f core conftest.err conftest.$ac_objext \ conftest$ac_exeext conftest.$ac_ext fi if $ac_stdatomic_ok; then : true else { $as_echo "$as_me:${as_lineno-$LINENO}: checking for __atomic_* builtins" >&5 $as_echo_n "checking for __atomic_* builtins... " >&6; } cat confdefs.h - <<_ACEOF >conftest.$ac_ext /* end confdefs.h. */ int main(int argc, char **argv) { volatile int i = 1; __atomic_store_n (&i, 0, __ATOMIC_RELEASE); return __atomic_load_n (&i, __ATOMIC_ACQUIRE); } _ACEOF if ac_fn_c_try_link "$LINENO"; then : $as_echo "#define HAVE___ATOMIC 1" >>confdefs.h { $as_echo "$as_me:${as_lineno-$LINENO}: result: yes" >&5 $as_echo "yes" >&6; } else { $as_echo "$as_me:${as_lineno-$LINENO}: result: no" >&5 $as_echo "no" >&6; } { $as_echo "$as_me:${as_lineno-$LINENO}: checking for __sync_* builtins" >&5 $as_echo_n "checking for __sync_* builtins... " >&6; } cat confdefs.h - <<_ACEOF >conftest.$ac_ext /* end confdefs.h. */ int main(int argc, char **argv) { volatile int i = 1; __sync_fetch_and_sub (&i, 1); return __sync_val_compare_and_swap (&i, 0, 1); } _ACEOF if ac_fn_c_try_link "$LINENO"; then : $as_echo "#define HAVE___SYNC 1" >>confdefs.h { $as_echo "$as_me:${as_lineno-$LINENO}: result: yes" >&5 $as_echo "yes" >&6; } { $as_echo "$as_me:${as_lineno-$LINENO}: checking for __sync_swap builtin" >&5 $as_echo_n "checking for __sync_swap builtin... " >&6; } cat confdefs.h - <<_ACEOF >conftest.$ac_ext /* end confdefs.h. */ int main(int argc, char **argv) { volatile int i = 1; return __sync_swap (&i, 2); } _ACEOF if ac_fn_c_try_link "$LINENO"; then : $as_echo "#define HAVE___SYNC_SWAP 1" >>confdefs.h { $as_echo "$as_me:${as_lineno-$LINENO}: result: yes" >&5 $as_echo "yes" >&6; } else { $as_echo "$as_me:${as_lineno-$LINENO}: result: no" >&5 $as_echo "no" >&6; } fi rm -f core conftest.err conftest.$ac_objext \ conftest$ac_exeext conftest.$ac_ext else { $as_echo "$as_me:${as_lineno-$LINENO}: result: no" >&5 $as_echo "no" >&6; } { { $as_echo "$as_me:${as_lineno-$LINENO}: error: in \`$ac_pwd':" >&5 $as_echo "$as_me: error: in \`$ac_pwd':" >&2;} as_fn_error $? "stdatomic.h unavailable and $CC has neither __atomic nor __sync builtins See \`config.log' for more details" "$LINENO" 5; } fi rm -f core conftest.err conftest.$ac_objext \ conftest$ac_exeext conftest.$ac_ext fi rm -f core conftest.err conftest.$ac_objext \ conftest$ac_exeext conftest.$ac_ext fi for ac_header in pthread_np.h do : ac_fn_c_check_header_compile "$LINENO" "pthread_np.h" "ac_cv_header_pthread_np_h" " #include " if test "x$ac_cv_header_pthread_np_h" = xyes; then : cat >>confdefs.h <<_ACEOF #define HAVE_PTHREAD_NP_H 1 _ACEOF fi done for ac_func in pthread_setname_np pthread_set_name_np do : as_ac_var=`$as_echo "ac_cv_func_$ac_func" | $as_tr_sh` ac_fn_c_check_func "$LINENO" "$ac_func" "$as_ac_var" if eval test \"x\$"$as_ac_var"\" = x"yes"; then : cat >>confdefs.h <<_ACEOF #define `$as_echo "HAVE_$ac_func" | $as_tr_cpp` 1 _ACEOF fi done needsync=true if $needsync; then : { $as_echo "$as_me:${as_lineno-$LINENO}: checking for Linux futex() support" >&5 $as_echo_n "checking for Linux futex() support... " >&6; } cat confdefs.h - <<_ACEOF >conftest.$ac_ext /* end confdefs.h. */ #ifndef _GNU_SOURCE #define _GNU_SOURCE #endif #include #include #include #include #include int main(void); int main () { { return syscall(SYS_futex, NULL, FUTEX_WAIT, 0, NULL, NULL, 0); } ; return 0; } _ACEOF if ac_fn_c_try_link "$LINENO"; then : { $as_echo "$as_me:${as_lineno-$LINENO}: result: yes" >&5 $as_echo "yes" >&6; } $as_echo "#define HAVE_SYNC_LINUX_FUTEX /**/" >>confdefs.h needsync=false else { $as_echo "$as_me:${as_lineno-$LINENO}: result: no" >&5 $as_echo "no" >&6; } fi rm -f core conftest.err conftest.$ac_objext \ conftest$ac_exeext conftest.$ac_ext fi if $needsync; then : { $as_echo "$as_me:${as_lineno-$LINENO}: checking for FreeBSD _umtx_op() support" >&5 $as_echo_n "checking for FreeBSD _umtx_op() support... " >&6; } cat confdefs.h - <<_ACEOF >conftest.$ac_ext /* end confdefs.h. */ #include #include #include #include int main(void); int main () { { return _umtx_op(NULL, UMTX_OP_WAIT_UINT, 0, NULL, NULL); } ; return 0; } _ACEOF if ac_fn_c_try_link "$LINENO"; then : { $as_echo "$as_me:${as_lineno-$LINENO}: result: yes" >&5 $as_echo "yes" >&6; } $as_echo "#define HAVE_SYNC_UMTX_OP /**/" >>confdefs.h needsync=false else { $as_echo "$as_me:${as_lineno-$LINENO}: result: no" >&5 $as_echo "no" >&6; } fi rm -f core conftest.err conftest.$ac_objext \ conftest$ac_exeext conftest.$ac_ext fi if $needsync; then : { $as_echo "$as_me:${as_lineno-$LINENO}: checking for OpenBSD futex() support" >&5 $as_echo_n "checking for OpenBSD futex() support... " >&6; } cat confdefs.h - <<_ACEOF >conftest.$ac_ext /* end confdefs.h. */ #include int main(void); int main () { { return futex(NULL, FUTEX_WAIT, 0, NULL, NULL, 0); } ; return 0; } _ACEOF if ac_fn_c_try_link "$LINENO"; then : { $as_echo "$as_me:${as_lineno-$LINENO}: result: yes" >&5 $as_echo "yes" >&6; } $as_echo "#define HAVE_SYNC_OPENBSD_FUTEX /**/" >>confdefs.h needsync=false else { $as_echo "$as_me:${as_lineno-$LINENO}: result: no" >&5 $as_echo "no" >&6; } fi rm -f core conftest.err conftest.$ac_objext \ conftest$ac_exeext conftest.$ac_ext fi for ac_header in net/if_var.h do : ac_fn_c_check_header_compile "$LINENO" "net/if_var.h" "ac_cv_header_net_if_var_h" "#ifdef SUNOS_5 #define _POSIX_C_SOURCE 200809L #define __EXTENSIONS__ #endif #include #include #include #include /* sys/conf.h depends on param.h on FBSD at least */ #include /* Required for MAXSIG */ #include #include #ifdef __APPLE__ # define __APPLE_USE_RFC_3542 #endif #include #include #include #include " if test "x$ac_cv_header_net_if_var_h" = xyes; then : cat >>confdefs.h <<_ACEOF #define HAVE_NET_IF_VAR_H 1 _ACEOF fi done for ac_header in netinet/in_var.h \ net/if_dl.h net/netopt.h \ inet/nd.h netinet/ip_icmp.h \ sys/sysctl.h sys/sockio.h sys/conf.h do : as_ac_Header=`$as_echo "ac_cv_header_$ac_header" | $as_tr_sh` ac_fn_c_check_header_compile "$LINENO" "$ac_header" "$as_ac_Header" "#ifdef SUNOS_5 #define _POSIX_C_SOURCE 200809L #define __EXTENSIONS__ #endif #include #include #include #include /* sys/conf.h depends on param.h on FBSD at least */ #include /* Required for MAXSIG */ #include #include #ifdef __APPLE__ # define __APPLE_USE_RFC_3542 #endif #include #include #include #include #if HAVE_NET_IF_VAR_H # include #endif " if eval test \"x\$"$as_ac_Header"\" = x"yes"; then : cat >>confdefs.h <<_ACEOF #define `$as_echo "HAVE_$ac_header" | $as_tr_cpp` 1 _ACEOF fi done for ac_header in ucontext.h do : ac_fn_c_check_header_compile "$LINENO" "ucontext.h" "ac_cv_header_ucontext_h" "#ifndef __USE_GNU #define __USE_GNU #endif /* __USE_GNU */ #ifdef SUNOS_5 #define _POSIX_C_SOURCE 200809L #define __EXTENSIONS__ #endif #include #include #include #include /* sys/conf.h depends on param.h on FBSD at least */ #include /* Required for MAXSIG */ #include #include #ifdef __APPLE__ # define __APPLE_USE_RFC_3542 #endif #include #include #include #include #if HAVE_NET_IF_VAR_H # include #endif " if test "x$ac_cv_header_ucontext_h" = xyes; then : cat >>confdefs.h <<_ACEOF #define HAVE_UCONTEXT_H 1 _ACEOF fi done ac_fn_c_check_member "$LINENO" "ucontext_t" "uc_mcontext.uc_regs" "ac_cv_member_ucontext_t_uc_mcontext_uc_regs" "#include " if test "x$ac_cv_member_ucontext_t_uc_mcontext_uc_regs" = xyes; then : cat >>confdefs.h <<_ACEOF #define HAVE_UCONTEXT_T_UC_MCONTEXT_UC_REGS 1 _ACEOF fi ac_fn_c_check_member "$LINENO" "ucontext_t" "uc_mcontext.regs" "ac_cv_member_ucontext_t_uc_mcontext_regs" "#include " if test "x$ac_cv_member_ucontext_t_uc_mcontext_regs" = xyes; then : cat >>confdefs.h <<_ACEOF #define HAVE_UCONTEXT_T_UC_MCONTEXT_REGS 1 _ACEOF ac_fn_c_check_member "$LINENO" "ucontext_t" "uc_mcontext.regs.nip" "ac_cv_member_ucontext_t_uc_mcontext_regs_nip" "#include " if test "x$ac_cv_member_ucontext_t_uc_mcontext_regs_nip" = xyes; then : cat >>confdefs.h <<_ACEOF #define HAVE_UCONTEXT_T_UC_MCONTEXT_REGS_NIP 1 _ACEOF fi fi ac_fn_c_check_member "$LINENO" "ucontext_t" "uc_mcontext.gregs" "ac_cv_member_ucontext_t_uc_mcontext_gregs" "#include " if test "x$ac_cv_member_ucontext_t_uc_mcontext_gregs" = xyes; then : cat >>confdefs.h <<_ACEOF #define HAVE_UCONTEXT_T_UC_MCONTEXT_GREGS 1 _ACEOF fi { $as_echo "$as_me:${as_lineno-$LINENO}: checking which operating system interface to use" >&5 $as_echo_n "checking which operating system interface to use... " >&6; } case "$host_os" in sunos* | solaris2*) { $as_echo "$as_me:${as_lineno-$LINENO}: result: Solaris" >&5 $as_echo "Solaris" >&6; } $as_echo "#define SUNOS_5 1" >>confdefs.h $as_echo "#define SOLARIS_IPV6 1" >>confdefs.h $as_echo "#define _POSIX_C_SOURCE 200809L" >>confdefs.h { $as_echo "$as_me:${as_lineno-$LINENO}: checking for main in -lsocket" >&5 $as_echo_n "checking for main in -lsocket... " >&6; } if ${ac_cv_lib_socket_main+:} false; then : $as_echo_n "(cached) " >&6 else ac_check_lib_save_LIBS=$LIBS LIBS="-lsocket $LIBS" cat confdefs.h - <<_ACEOF >conftest.$ac_ext /* end confdefs.h. */ int main () { return main (); ; return 0; } _ACEOF if ac_fn_c_try_link "$LINENO"; then : ac_cv_lib_socket_main=yes else ac_cv_lib_socket_main=no fi rm -f core conftest.err conftest.$ac_objext \ conftest$ac_exeext conftest.$ac_ext LIBS=$ac_check_lib_save_LIBS fi { $as_echo "$as_me:${as_lineno-$LINENO}: result: $ac_cv_lib_socket_main" >&5 $as_echo "$ac_cv_lib_socket_main" >&6; } if test "x$ac_cv_lib_socket_main" = xyes; then : cat >>confdefs.h <<_ACEOF #define HAVE_LIBSOCKET 1 _ACEOF LIBS="-lsocket $LIBS" fi { $as_echo "$as_me:${as_lineno-$LINENO}: checking for main in -lnsl" >&5 $as_echo_n "checking for main in -lnsl... " >&6; } if ${ac_cv_lib_nsl_main+:} false; then : $as_echo_n "(cached) " >&6 else ac_check_lib_save_LIBS=$LIBS LIBS="-lnsl $LIBS" cat confdefs.h - <<_ACEOF >conftest.$ac_ext /* end confdefs.h. */ int main () { return main (); ; return 0; } _ACEOF if ac_fn_c_try_link "$LINENO"; then : ac_cv_lib_nsl_main=yes else ac_cv_lib_nsl_main=no fi rm -f core conftest.err conftest.$ac_objext \ conftest$ac_exeext conftest.$ac_ext LIBS=$ac_check_lib_save_LIBS fi { $as_echo "$as_me:${as_lineno-$LINENO}: result: $ac_cv_lib_nsl_main" >&5 $as_echo "$ac_cv_lib_nsl_main" >&6; } if test "x$ac_cv_lib_nsl_main" = xyes; then : cat >>confdefs.h <<_ACEOF #define HAVE_LIBNSL 1 _ACEOF LIBS="-lnsl $LIBS" fi { $as_echo "$as_me:${as_lineno-$LINENO}: checking for main in -lumem" >&5 $as_echo_n "checking for main in -lumem... " >&6; } if ${ac_cv_lib_umem_main+:} false; then : $as_echo_n "(cached) " >&6 else ac_check_lib_save_LIBS=$LIBS LIBS="-lumem $LIBS" cat confdefs.h - <<_ACEOF >conftest.$ac_ext /* end confdefs.h. */ int main () { return main (); ; return 0; } _ACEOF if ac_fn_c_try_link "$LINENO"; then : ac_cv_lib_umem_main=yes else ac_cv_lib_umem_main=no fi rm -f core conftest.err conftest.$ac_objext \ conftest$ac_exeext conftest.$ac_ext LIBS=$ac_check_lib_save_LIBS fi { $as_echo "$as_me:${as_lineno-$LINENO}: result: $ac_cv_lib_umem_main" >&5 $as_echo "$ac_cv_lib_umem_main" >&6; } if test "x$ac_cv_lib_umem_main" = xyes; then : cat >>confdefs.h <<_ACEOF #define HAVE_LIBUMEM 1 _ACEOF LIBS="-lumem $LIBS" fi SOLARIS="solaris" { $as_echo "$as_me:${as_lineno-$LINENO}: WARNING: --Solaris support is being considered for deprecation, please let us know if you are still using this--" >&5 $as_echo "$as_me: WARNING: --Solaris support is being considered for deprecation, please let us know if you are still using this--" >&2;} ;; linux*) { $as_echo "$as_me:${as_lineno-$LINENO}: result: Linux" >&5 $as_echo "Linux" >&6; } $as_echo "#define GNU_LINUX 1" >>confdefs.h $as_echo "#define HAVE_NETLINK 1" >>confdefs.h $as_echo "#define LINUX_IPV6 1" >>confdefs.h $as_echo "#define IPV6_MINHOPCOUNT 73" >>confdefs.h ;; openbsd*) { $as_echo "$as_me:${as_lineno-$LINENO}: result: OpenBSD" >&5 $as_echo "OpenBSD" >&6; } $as_echo "#define OPEN_BSD 1" >>confdefs.h $as_echo "#define KAME 1" >>confdefs.h $as_echo "#define BSD_V6_SYSCTL 1" >>confdefs.h if test "x${enable_pimd}" != "xno"; then case "$host_os" in openbsd6.0) ;; openbsd6-9*) { { $as_echo "$as_me:${as_lineno-$LINENO}: error: in \`$ac_pwd':" >&5 $as_echo "$as_me: error: in \`$ac_pwd':" >&2;} as_fn_error $? "pimd cannot be enabled as PIM support has been removed from OpenBSD 6.1 See \`config.log' for more details" "$LINENO" 5; } ;; esac fi ;; *) { $as_echo "$as_me:${as_lineno-$LINENO}: result: BSD" >&5 $as_echo "BSD" >&6; } $as_echo "#define HAVE_NET_RT_IFLIST 1" >>confdefs.h $as_echo "#define KAME 1" >>confdefs.h $as_echo "#define BSD_V6_SYSCTL 1" >>confdefs.h ;; esac if test "${SOLARIS}" = "solaris"; then SOLARIS_TRUE= SOLARIS_FALSE='#' else SOLARIS_TRUE='#' SOLARIS_FALSE= fi # Check whether --enable-largefile was given. if test "${enable_largefile+set}" = set; then : enableval=$enable_largefile; fi if test "$enable_largefile" != no; then { $as_echo "$as_me:${as_lineno-$LINENO}: checking for special C compiler options needed for large files" >&5 $as_echo_n "checking for special C compiler options needed for large files... " >&6; } if ${ac_cv_sys_largefile_CC+:} false; then : $as_echo_n "(cached) " >&6 else ac_cv_sys_largefile_CC=no if test "$GCC" != yes; then ac_save_CC=$CC while :; do # IRIX 6.2 and later do not support large files by default, # so use the C compiler's -n32 option if that helps. cat confdefs.h - <<_ACEOF >conftest.$ac_ext /* end confdefs.h. */ #include /* Check that off_t can represent 2**63 - 1 correctly. We can't simply define LARGE_OFF_T to be 9223372036854775807, since some C++ compilers masquerading as C compilers incorrectly reject 9223372036854775807. */ #define LARGE_OFF_T ((((off_t) 1 << 31) << 31) - 1 + (((off_t) 1 << 31) << 31)) int off_t_is_large[(LARGE_OFF_T % 2147483629 == 721 && LARGE_OFF_T % 2147483647 == 1) ? 1 : -1]; int main () { ; return 0; } _ACEOF if ac_fn_c_try_compile "$LINENO"; then : break fi rm -f core conftest.err conftest.$ac_objext CC="$CC -n32" if ac_fn_c_try_compile "$LINENO"; then : ac_cv_sys_largefile_CC=' -n32'; break fi rm -f core conftest.err conftest.$ac_objext break done CC=$ac_save_CC rm -f conftest.$ac_ext fi fi { $as_echo "$as_me:${as_lineno-$LINENO}: result: $ac_cv_sys_largefile_CC" >&5 $as_echo "$ac_cv_sys_largefile_CC" >&6; } if test "$ac_cv_sys_largefile_CC" != no; then CC=$CC$ac_cv_sys_largefile_CC fi { $as_echo "$as_me:${as_lineno-$LINENO}: checking for _FILE_OFFSET_BITS value needed for large files" >&5 $as_echo_n "checking for _FILE_OFFSET_BITS value needed for large files... " >&6; } if ${ac_cv_sys_file_offset_bits+:} false; then : $as_echo_n "(cached) " >&6 else while :; do cat confdefs.h - <<_ACEOF >conftest.$ac_ext /* end confdefs.h. */ #include /* Check that off_t can represent 2**63 - 1 correctly. We can't simply define LARGE_OFF_T to be 9223372036854775807, since some C++ compilers masquerading as C compilers incorrectly reject 9223372036854775807. */ #define LARGE_OFF_T ((((off_t) 1 << 31) << 31) - 1 + (((off_t) 1 << 31) << 31)) int off_t_is_large[(LARGE_OFF_T % 2147483629 == 721 && LARGE_OFF_T % 2147483647 == 1) ? 1 : -1]; int main () { ; return 0; } _ACEOF if ac_fn_c_try_compile "$LINENO"; then : ac_cv_sys_file_offset_bits=no; break fi rm -f core conftest.err conftest.$ac_objext conftest.$ac_ext cat confdefs.h - <<_ACEOF >conftest.$ac_ext /* end confdefs.h. */ #define _FILE_OFFSET_BITS 64 #include /* Check that off_t can represent 2**63 - 1 correctly. We can't simply define LARGE_OFF_T to be 9223372036854775807, since some C++ compilers masquerading as C compilers incorrectly reject 9223372036854775807. */ #define LARGE_OFF_T ((((off_t) 1 << 31) << 31) - 1 + (((off_t) 1 << 31) << 31)) int off_t_is_large[(LARGE_OFF_T % 2147483629 == 721 && LARGE_OFF_T % 2147483647 == 1) ? 1 : -1]; int main () { ; return 0; } _ACEOF if ac_fn_c_try_compile "$LINENO"; then : ac_cv_sys_file_offset_bits=64; break fi rm -f core conftest.err conftest.$ac_objext conftest.$ac_ext ac_cv_sys_file_offset_bits=unknown break done fi { $as_echo "$as_me:${as_lineno-$LINENO}: result: $ac_cv_sys_file_offset_bits" >&5 $as_echo "$ac_cv_sys_file_offset_bits" >&6; } case $ac_cv_sys_file_offset_bits in #( no | unknown) ;; *) cat >>confdefs.h <<_ACEOF #define _FILE_OFFSET_BITS $ac_cv_sys_file_offset_bits _ACEOF ;; esac rm -rf conftest* if test $ac_cv_sys_file_offset_bits = unknown; then { $as_echo "$as_me:${as_lineno-$LINENO}: checking for _LARGE_FILES value needed for large files" >&5 $as_echo_n "checking for _LARGE_FILES value needed for large files... " >&6; } if ${ac_cv_sys_large_files+:} false; then : $as_echo_n "(cached) " >&6 else while :; do cat confdefs.h - <<_ACEOF >conftest.$ac_ext /* end confdefs.h. */ #include /* Check that off_t can represent 2**63 - 1 correctly. We can't simply define LARGE_OFF_T to be 9223372036854775807, since some C++ compilers masquerading as C compilers incorrectly reject 9223372036854775807. */ #define LARGE_OFF_T ((((off_t) 1 << 31) << 31) - 1 + (((off_t) 1 << 31) << 31)) int off_t_is_large[(LARGE_OFF_T % 2147483629 == 721 && LARGE_OFF_T % 2147483647 == 1) ? 1 : -1]; int main () { ; return 0; } _ACEOF if ac_fn_c_try_compile "$LINENO"; then : ac_cv_sys_large_files=no; break fi rm -f core conftest.err conftest.$ac_objext conftest.$ac_ext cat confdefs.h - <<_ACEOF >conftest.$ac_ext /* end confdefs.h. */ #define _LARGE_FILES 1 #include /* Check that off_t can represent 2**63 - 1 correctly. We can't simply define LARGE_OFF_T to be 9223372036854775807, since some C++ compilers masquerading as C compilers incorrectly reject 9223372036854775807. */ #define LARGE_OFF_T ((((off_t) 1 << 31) << 31) - 1 + (((off_t) 1 << 31) << 31)) int off_t_is_large[(LARGE_OFF_T % 2147483629 == 721 && LARGE_OFF_T % 2147483647 == 1) ? 1 : -1]; int main () { ; return 0; } _ACEOF if ac_fn_c_try_compile "$LINENO"; then : ac_cv_sys_large_files=1; break fi rm -f core conftest.err conftest.$ac_objext conftest.$ac_ext ac_cv_sys_large_files=unknown break done fi { $as_echo "$as_me:${as_lineno-$LINENO}: result: $ac_cv_sys_large_files" >&5 $as_echo "$ac_cv_sys_large_files" >&6; } case $ac_cv_sys_large_files in #( no | unknown) ;; *) cat >>confdefs.h <<_ACEOF #define _LARGE_FILES $ac_cv_sys_large_files _ACEOF ;; esac rm -rf conftest* fi fi if test "${enable_realms}" = "yes"; then case "$host_os" in linux*) $as_echo "#define SUPPORT_REALMS 1" >>confdefs.h ;; *) echo "Sorry, only Linux has REALMS support" exit 1 ;; esac fi { $as_echo "$as_me:${as_lineno-$LINENO}: checking whether byte ordering is bigendian" >&5 $as_echo_n "checking whether byte ordering is bigendian... " >&6; } if ${ac_cv_c_bigendian+:} false; then : $as_echo_n "(cached) " >&6 else ac_cv_c_bigendian=unknown # See if we're dealing with a universal compiler. cat confdefs.h - <<_ACEOF >conftest.$ac_ext /* end confdefs.h. */ #ifndef __APPLE_CC__ not a universal capable compiler #endif typedef int dummy; _ACEOF if ac_fn_c_try_compile "$LINENO"; then : # Check for potential -arch flags. It is not universal unless # there are at least two -arch flags with different values. ac_arch= ac_prev= for ac_word in $CC $CFLAGS $CPPFLAGS $LDFLAGS; do if test -n "$ac_prev"; then case $ac_word in i?86 | x86_64 | ppc | ppc64) if test -z "$ac_arch" || test "$ac_arch" = "$ac_word"; then ac_arch=$ac_word else ac_cv_c_bigendian=universal break fi ;; esac ac_prev= elif test "x$ac_word" = "x-arch"; then ac_prev=arch fi done fi rm -f core conftest.err conftest.$ac_objext conftest.$ac_ext if test $ac_cv_c_bigendian = unknown; then # See if sys/param.h defines the BYTE_ORDER macro. cat confdefs.h - <<_ACEOF >conftest.$ac_ext /* end confdefs.h. */ #include #include int main () { #if ! (defined BYTE_ORDER && defined BIG_ENDIAN \ && defined LITTLE_ENDIAN && BYTE_ORDER && BIG_ENDIAN \ && LITTLE_ENDIAN) bogus endian macros #endif ; return 0; } _ACEOF if ac_fn_c_try_compile "$LINENO"; then : # It does; now see whether it defined to BIG_ENDIAN or not. cat confdefs.h - <<_ACEOF >conftest.$ac_ext /* end confdefs.h. */ #include #include int main () { #if BYTE_ORDER != BIG_ENDIAN not big endian #endif ; return 0; } _ACEOF if ac_fn_c_try_compile "$LINENO"; then : ac_cv_c_bigendian=yes else ac_cv_c_bigendian=no fi rm -f core conftest.err conftest.$ac_objext conftest.$ac_ext fi rm -f core conftest.err conftest.$ac_objext conftest.$ac_ext fi if test $ac_cv_c_bigendian = unknown; then # See if defines _LITTLE_ENDIAN or _BIG_ENDIAN (e.g., Solaris). cat confdefs.h - <<_ACEOF >conftest.$ac_ext /* end confdefs.h. */ #include int main () { #if ! (defined _LITTLE_ENDIAN || defined _BIG_ENDIAN) bogus endian macros #endif ; return 0; } _ACEOF if ac_fn_c_try_compile "$LINENO"; then : # It does; now see whether it defined to _BIG_ENDIAN or not. cat confdefs.h - <<_ACEOF >conftest.$ac_ext /* end confdefs.h. */ #include int main () { #ifndef _BIG_ENDIAN not big endian #endif ; return 0; } _ACEOF if ac_fn_c_try_compile "$LINENO"; then : ac_cv_c_bigendian=yes else ac_cv_c_bigendian=no fi rm -f core conftest.err conftest.$ac_objext conftest.$ac_ext fi rm -f core conftest.err conftest.$ac_objext conftest.$ac_ext fi if test $ac_cv_c_bigendian = unknown; then # Compile a test program. if test "$cross_compiling" = yes; then : # Try to guess by grepping values from an object file. cat confdefs.h - <<_ACEOF >conftest.$ac_ext /* end confdefs.h. */ short int ascii_mm[] = { 0x4249, 0x4765, 0x6E44, 0x6961, 0x6E53, 0x7953, 0 }; short int ascii_ii[] = { 0x694C, 0x5454, 0x656C, 0x6E45, 0x6944, 0x6E61, 0 }; int use_ascii (int i) { return ascii_mm[i] + ascii_ii[i]; } short int ebcdic_ii[] = { 0x89D3, 0xE3E3, 0x8593, 0x95C5, 0x89C4, 0x9581, 0 }; short int ebcdic_mm[] = { 0xC2C9, 0xC785, 0x95C4, 0x8981, 0x95E2, 0xA8E2, 0 }; int use_ebcdic (int i) { return ebcdic_mm[i] + ebcdic_ii[i]; } extern int foo; int main () { return use_ascii (foo) == use_ebcdic (foo); ; return 0; } _ACEOF if ac_fn_c_try_compile "$LINENO"; then : if grep BIGenDianSyS conftest.$ac_objext >/dev/null; then ac_cv_c_bigendian=yes fi if grep LiTTleEnDian conftest.$ac_objext >/dev/null ; then if test "$ac_cv_c_bigendian" = unknown; then ac_cv_c_bigendian=no else # finding both strings is unlikely to happen, but who knows? ac_cv_c_bigendian=unknown fi fi fi rm -f core conftest.err conftest.$ac_objext conftest.$ac_ext else cat confdefs.h - <<_ACEOF >conftest.$ac_ext /* end confdefs.h. */ $ac_includes_default int main () { /* Are we little or big endian? From Harbison&Steele. */ union { long int l; char c[sizeof (long int)]; } u; u.l = 1; return u.c[sizeof (long int) - 1] == 1; ; return 0; } _ACEOF if ac_fn_c_try_run "$LINENO"; then : ac_cv_c_bigendian=no else ac_cv_c_bigendian=yes fi rm -f core *.core core.conftest.* gmon.out bb.out conftest$ac_exeext \ conftest.$ac_objext conftest.beam conftest.$ac_ext fi fi fi { $as_echo "$as_me:${as_lineno-$LINENO}: result: $ac_cv_c_bigendian" >&5 $as_echo "$ac_cv_c_bigendian" >&6; } case $ac_cv_c_bigendian in #( yes) $as_echo "#define WORDS_BIGENDIAN 1" >>confdefs.h ;; #( no) ;; #( universal) $as_echo "#define AC_APPLE_UNIVERSAL_BUILD 1" >>confdefs.h ;; #( *) as_fn_error $? "unknown endianness presetting ac_cv_c_bigendian=no (or yes) will help" "$LINENO" 5 ;; esac for ac_func in \ strlcat strlcpy \ getgrouplist do : as_ac_var=`$as_echo "ac_cv_func_$ac_func" | $as_tr_sh` ac_fn_c_check_func "$LINENO" "$ac_func" "$as_ac_var" if eval test \"x\$"$as_ac_var"\" = x"yes"; then : cat >>confdefs.h <<_ACEOF #define `$as_echo "HAVE_$ac_func" | $as_tr_cpp` 1 _ACEOF fi done if test "${enable_clippy_only}" != "yes"; then # # Logic for protobuf support. # if test "$enable_protobuf" = "yes"; then # Check for protoc & protoc-c # protoc is not required, it's only for a "be nice" helper target for ac_prog in protoc do # Extract the first word of "$ac_prog", so it can be a program name with args. set dummy $ac_prog; ac_word=$2 { $as_echo "$as_me:${as_lineno-$LINENO}: checking for $ac_word" >&5 $as_echo_n "checking for $ac_word... " >&6; } if ${ac_cv_prog_PROTOC+:} false; then : $as_echo_n "(cached) " >&6 else if test -n "$PROTOC"; then ac_cv_prog_PROTOC="$PROTOC" # Let the user override the test. else as_save_IFS=$IFS; IFS=$PATH_SEPARATOR for as_dir in $PATH do IFS=$as_save_IFS test -z "$as_dir" && as_dir=. for ac_exec_ext in '' $ac_executable_extensions; do if as_fn_executable_p "$as_dir/$ac_word$ac_exec_ext"; then ac_cv_prog_PROTOC="$ac_prog" $as_echo "$as_me:${as_lineno-$LINENO}: found $as_dir/$ac_word$ac_exec_ext" >&5 break 2 fi done done IFS=$as_save_IFS fi fi PROTOC=$ac_cv_prog_PROTOC if test -n "$PROTOC"; then { $as_echo "$as_me:${as_lineno-$LINENO}: result: $PROTOC" >&5 $as_echo "$PROTOC" >&6; } else { $as_echo "$as_me:${as_lineno-$LINENO}: result: no" >&5 $as_echo "no" >&6; } fi test -n "$PROTOC" && break done test -n "$PROTOC" || PROTOC="/bin/false" for ac_prog in protoc-c do # Extract the first word of "$ac_prog", so it can be a program name with args. set dummy $ac_prog; ac_word=$2 { $as_echo "$as_me:${as_lineno-$LINENO}: checking for $ac_word" >&5 $as_echo_n "checking for $ac_word... " >&6; } if ${ac_cv_prog_PROTOC_C+:} false; then : $as_echo_n "(cached) " >&6 else if test -n "$PROTOC_C"; then ac_cv_prog_PROTOC_C="$PROTOC_C" # Let the user override the test. else as_save_IFS=$IFS; IFS=$PATH_SEPARATOR for as_dir in $PATH do IFS=$as_save_IFS test -z "$as_dir" && as_dir=. for ac_exec_ext in '' $ac_executable_extensions; do if as_fn_executable_p "$as_dir/$ac_word$ac_exec_ext"; then ac_cv_prog_PROTOC_C="$ac_prog" $as_echo "$as_me:${as_lineno-$LINENO}: found $as_dir/$ac_word$ac_exec_ext" >&5 break 2 fi done done IFS=$as_save_IFS fi fi PROTOC_C=$ac_cv_prog_PROTOC_C if test -n "$PROTOC_C"; then { $as_echo "$as_me:${as_lineno-$LINENO}: result: $PROTOC_C" >&5 $as_echo "$PROTOC_C" >&6; } else { $as_echo "$as_me:${as_lineno-$LINENO}: result: no" >&5 $as_echo "no" >&6; } fi test -n "$PROTOC_C" && break done test -n "$PROTOC_C" || PROTOC_C="/bin/false" if test "$PROTOC_C" = "/bin/false"; then { { $as_echo "$as_me:${as_lineno-$LINENO}: error: in \`$ac_pwd':" >&5 $as_echo "$as_me: error: in \`$ac_pwd':" >&2;} as_fn_error $? "protobuf requested but protoc-c not found. Install protobuf-c. See \`config.log' for more details" "$LINENO" 5; } fi pkg_failed=no { $as_echo "$as_me:${as_lineno-$LINENO}: checking for PROTOBUF_C (libprotobuf-c >= 0.14)" >&5 $as_echo_n "checking for PROTOBUF_C (libprotobuf-c >= 0.14)... " >&6; } if test -n "$PROTOBUF_C_CFLAGS"; then pkg_cv_PROTOBUF_C_CFLAGS="$PROTOBUF_C_CFLAGS" elif test -n "$PKG_CONFIG"; then if test -n "$PKG_CONFIG" && \ { { $as_echo "$as_me:${as_lineno-$LINENO}: \$PKG_CONFIG --exists --print-errors \"libprotobuf-c >= 0.14\""; } >&5 ($PKG_CONFIG --exists --print-errors "libprotobuf-c >= 0.14") 2>&5 ac_status=$? $as_echo "$as_me:${as_lineno-$LINENO}: \$? = $ac_status" >&5 test $ac_status = 0; }; then pkg_cv_PROTOBUF_C_CFLAGS=`$PKG_CONFIG --cflags "libprotobuf-c >= 0.14" 2>/dev/null` test "x$?" != "x0" && pkg_failed=yes else pkg_failed=yes fi else pkg_failed=untried fi if test -n "$PROTOBUF_C_LIBS"; then pkg_cv_PROTOBUF_C_LIBS="$PROTOBUF_C_LIBS" elif test -n "$PKG_CONFIG"; then if test -n "$PKG_CONFIG" && \ { { $as_echo "$as_me:${as_lineno-$LINENO}: \$PKG_CONFIG --exists --print-errors \"libprotobuf-c >= 0.14\""; } >&5 ($PKG_CONFIG --exists --print-errors "libprotobuf-c >= 0.14") 2>&5 ac_status=$? $as_echo "$as_me:${as_lineno-$LINENO}: \$? = $ac_status" >&5 test $ac_status = 0; }; then pkg_cv_PROTOBUF_C_LIBS=`$PKG_CONFIG --libs "libprotobuf-c >= 0.14" 2>/dev/null` test "x$?" != "x0" && pkg_failed=yes else pkg_failed=yes fi else pkg_failed=untried fi if test $pkg_failed = yes; then { $as_echo "$as_me:${as_lineno-$LINENO}: result: no" >&5 $as_echo "no" >&6; } if $PKG_CONFIG --atleast-pkgconfig-version 0.20; then _pkg_short_errors_supported=yes else _pkg_short_errors_supported=no fi if test $_pkg_short_errors_supported = yes; then PROTOBUF_C_PKG_ERRORS=`$PKG_CONFIG --short-errors --print-errors --cflags --libs "libprotobuf-c >= 0.14" 2>&1` else PROTOBUF_C_PKG_ERRORS=`$PKG_CONFIG --print-errors --cflags --libs "libprotobuf-c >= 0.14" 2>&1` fi # Put the nasty error message in config.log where it belongs echo "$PROTOBUF_C_PKG_ERRORS" >&5 { { $as_echo "$as_me:${as_lineno-$LINENO}: error: in \`$ac_pwd':" >&5 $as_echo "$as_me: error: in \`$ac_pwd':" >&2;} as_fn_error $? "protobuf requested but libprotobuf-c not found. Install protobuf-c. See \`config.log' for more details" "$LINENO" 5; } elif test $pkg_failed = untried; then { $as_echo "$as_me:${as_lineno-$LINENO}: result: no" >&5 $as_echo "no" >&6; } { { $as_echo "$as_me:${as_lineno-$LINENO}: error: in \`$ac_pwd':" >&5 $as_echo "$as_me: error: in \`$ac_pwd':" >&2;} as_fn_error $? "protobuf requested but libprotobuf-c not found. Install protobuf-c. See \`config.log' for more details" "$LINENO" 5; } else PROTOBUF_C_CFLAGS=$pkg_cv_PROTOBUF_C_CFLAGS PROTOBUF_C_LIBS=$pkg_cv_PROTOBUF_C_LIBS { $as_echo "$as_me:${as_lineno-$LINENO}: result: yes" >&5 $as_echo "yes" >&6; } fi ac_fn_c_check_header_mongrel "$LINENO" "google/protobuf-c/protobuf-c.h" "ac_cv_header_google_protobuf_c_protobuf_c_h" "$ac_includes_default" if test "x$ac_cv_header_google_protobuf_c_protobuf_c_h" = xyes; then : else { { $as_echo "$as_me:${as_lineno-$LINENO}: error: in \`$ac_pwd':" >&5 $as_echo "$as_me: error: in \`$ac_pwd':" >&2;} as_fn_error $? "protobuf requested but protobuf-c.h not found. Install protobuf-c. See \`config.log' for more details" "$LINENO" 5; } fi $as_echo "#define HAVE_PROTOBUF 1" >>confdefs.h fi case "${enable_vtysh}" in "no") VTYSH="";; *) VTYSH="vtysh"; $as_echo "#define VTYSH 1" >>confdefs.h prev_libs="$LIBS" { $as_echo "$as_me:${as_lineno-$LINENO}: checking for main in -lreadline" >&5 $as_echo_n "checking for main in -lreadline... " >&6; } if ${ac_cv_lib_readline_main+:} false; then : $as_echo_n "(cached) " >&6 else ac_check_lib_save_LIBS=$LIBS LIBS="-lreadline $LIBS" cat confdefs.h - <<_ACEOF >conftest.$ac_ext /* end confdefs.h. */ int main () { return main (); ; return 0; } _ACEOF if ac_fn_c_try_link "$LINENO"; then : ac_cv_lib_readline_main=yes else ac_cv_lib_readline_main=no fi rm -f core conftest.err conftest.$ac_objext \ conftest$ac_exeext conftest.$ac_ext LIBS=$ac_check_lib_save_LIBS fi { $as_echo "$as_me:${as_lineno-$LINENO}: result: $ac_cv_lib_readline_main" >&5 $as_echo "$ac_cv_lib_readline_main" >&6; } if test "x$ac_cv_lib_readline_main" = xyes; then : LIBREADLINE="-lreadline" else { $as_echo "$as_me:${as_lineno-$LINENO}: checking for library containing tputs" >&5 $as_echo_n "checking for library containing tputs... " >&6; } if ${ac_cv_search_tputs+:} false; then : $as_echo_n "(cached) " >&6 else ac_func_search_save_LIBS=$LIBS cat confdefs.h - <<_ACEOF >conftest.$ac_ext /* end confdefs.h. */ /* Override any GCC internal prototype to avoid an error. Use char because int might match the return type of a GCC builtin and then its argument prototype would still apply. */ #ifdef __cplusplus extern "C" #endif char tputs (); int main () { return tputs (); ; return 0; } _ACEOF for ac_lib in '' termcap tinfo curses ncurses; do if test -z "$ac_lib"; then ac_res="none required" else ac_res=-l$ac_lib LIBS="-l$ac_lib $ac_func_search_save_LIBS" fi if ac_fn_c_try_link "$LINENO"; then : ac_cv_search_tputs=$ac_res fi rm -f core conftest.err conftest.$ac_objext \ conftest$ac_exeext if ${ac_cv_search_tputs+:} false; then : break fi done if ${ac_cv_search_tputs+:} false; then : else ac_cv_search_tputs=no fi rm conftest.$ac_ext LIBS=$ac_func_search_save_LIBS fi { $as_echo "$as_me:${as_lineno-$LINENO}: result: $ac_cv_search_tputs" >&5 $as_echo "$ac_cv_search_tputs" >&6; } ac_res=$ac_cv_search_tputs if test "$ac_res" != no; then : test "$ac_res" = "none required" || LIBS="$ac_res $LIBS" LIBREADLINE="$ac_cv_search_tputs" else as_fn_error $? "libreadline (needed for vtysh) not found and/or missing dependencies" "$LINENO" 5 fi unset ac_cv_lib_readline_main { $as_echo "$as_me:${as_lineno-$LINENO}: checking for main in -lreadline" >&5 $as_echo_n "checking for main in -lreadline... " >&6; } if ${ac_cv_lib_readline_main+:} false; then : $as_echo_n "(cached) " >&6 else ac_check_lib_save_LIBS=$LIBS LIBS="-lreadline $LIBREADLINE $LIBS" cat confdefs.h - <<_ACEOF >conftest.$ac_ext /* end confdefs.h. */ int main () { return main (); ; return 0; } _ACEOF if ac_fn_c_try_link "$LINENO"; then : ac_cv_lib_readline_main=yes else ac_cv_lib_readline_main=no fi rm -f core conftest.err conftest.$ac_objext \ conftest$ac_exeext conftest.$ac_ext LIBS=$ac_check_lib_save_LIBS fi { $as_echo "$as_me:${as_lineno-$LINENO}: result: $ac_cv_lib_readline_main" >&5 $as_echo "$ac_cv_lib_readline_main" >&6; } if test "x$ac_cv_lib_readline_main" = xyes; then : LIBREADLINE="-lreadline $LIBREADLINE" else as_fn_error $? "libreadline (needed for vtysh) not found and/or missing dependencies" "$LINENO" 5 fi fi LIBS="$prev_libs" ac_fn_c_check_header_mongrel "$LINENO" "readline/history.h" "ac_cv_header_readline_history_h" "$ac_includes_default" if test "x$ac_cv_header_readline_history_h" = xyes; then : fi if test $ac_cv_header_readline_history_h = no;then as_fn_error $? "readline is too old to have readline/history.h, please update to the latest readline library." "$LINENO" 5 fi { $as_echo "$as_me:${as_lineno-$LINENO}: checking for rl_completion_matches in -lreadline" >&5 $as_echo_n "checking for rl_completion_matches in -lreadline... " >&6; } if ${ac_cv_lib_readline_rl_completion_matches+:} false; then : $as_echo_n "(cached) " >&6 else ac_check_lib_save_LIBS=$LIBS LIBS="-lreadline $LIBREADLINE $LIBS" cat confdefs.h - <<_ACEOF >conftest.$ac_ext /* end confdefs.h. */ /* Override any GCC internal prototype to avoid an error. Use char because int might match the return type of a GCC builtin and then its argument prototype would still apply. */ #ifdef __cplusplus extern "C" #endif char rl_completion_matches (); int main () { return rl_completion_matches (); ; return 0; } _ACEOF if ac_fn_c_try_link "$LINENO"; then : ac_cv_lib_readline_rl_completion_matches=yes else ac_cv_lib_readline_rl_completion_matches=no fi rm -f core conftest.err conftest.$ac_objext \ conftest$ac_exeext conftest.$ac_ext LIBS=$ac_check_lib_save_LIBS fi { $as_echo "$as_me:${as_lineno-$LINENO}: result: $ac_cv_lib_readline_rl_completion_matches" >&5 $as_echo "$ac_cv_lib_readline_rl_completion_matches" >&6; } if test "x$ac_cv_lib_readline_rl_completion_matches" = xyes; then : true fi if test $ac_cv_lib_readline_rl_completion_matches = no; then $as_echo "#define rl_completion_matches completion_matches" >>confdefs.h fi { $as_echo "$as_me:${as_lineno-$LINENO}: checking for append_history in -lreadline" >&5 $as_echo_n "checking for append_history in -lreadline... " >&6; } if ${ac_cv_lib_readline_append_history+:} false; then : $as_echo_n "(cached) " >&6 else ac_check_lib_save_LIBS=$LIBS LIBS="-lreadline $LIBREADLINE $LIBS" cat confdefs.h - <<_ACEOF >conftest.$ac_ext /* end confdefs.h. */ /* Override any GCC internal prototype to avoid an error. Use char because int might match the return type of a GCC builtin and then its argument prototype would still apply. */ #ifdef __cplusplus extern "C" #endif char append_history (); int main () { return append_history (); ; return 0; } _ACEOF if ac_fn_c_try_link "$LINENO"; then : ac_cv_lib_readline_append_history=yes else ac_cv_lib_readline_append_history=no fi rm -f core conftest.err conftest.$ac_objext \ conftest$ac_exeext conftest.$ac_ext LIBS=$ac_check_lib_save_LIBS fi { $as_echo "$as_me:${as_lineno-$LINENO}: result: $ac_cv_lib_readline_append_history" >&5 $as_echo "$ac_cv_lib_readline_append_history" >&6; } if test "x$ac_cv_lib_readline_append_history" = xyes; then : frr_cv_append_history=yes else frr_cv_append_history=no fi if test "$frr_cv_append_history" = yes; then $as_echo "#define HAVE_APPEND_HISTORY 1" >>confdefs.h fi ;; esac if test "$with_libpam" = "yes"; then ac_fn_c_check_header_compile "$LINENO" "security/pam_misc.h" "ac_cv_header_security_pam_misc_h" "#ifdef SUNOS_5 #define _POSIX_C_SOURCE 200809L #define __EXTENSIONS__ #endif #include #include #include #include /* sys/conf.h depends on param.h on FBSD at least */ #include /* Required for MAXSIG */ #include #include #ifdef __APPLE__ # define __APPLE_USE_RFC_3542 #endif #include #include #include #include #if HAVE_NET_IF_VAR_H # include #endif #include #include #if HAVE_NETINET_IN_VAR_H # include #endif #if HAVE_NET_IF_DL_H # include #endif #if HAVE_NET_NETOPT_H # include #endif #include #if HAVE_INET_ND_H # include #endif #include /* Required for IDRP */ #if HAVE_NETINET_IP_ICMP_H # include #endif " if test "x$ac_cv_header_security_pam_misc_h" = xyes; then : $as_echo "#define HAVE_PAM_MISC_H 1" >>confdefs.h $as_echo "#define PAM_CONV_FUNC misc_conv" >>confdefs.h pam_conv_func="misc_conv" fi ac_fn_c_check_header_compile "$LINENO" "security/openpam.h" "ac_cv_header_security_openpam_h" "#ifdef SUNOS_5 #define _POSIX_C_SOURCE 200809L #define __EXTENSIONS__ #endif #include #include #include #include /* sys/conf.h depends on param.h on FBSD at least */ #include /* Required for MAXSIG */ #include #include #ifdef __APPLE__ # define __APPLE_USE_RFC_3542 #endif #include #include #include #include #if HAVE_NET_IF_VAR_H # include #endif #include #include #if HAVE_NETINET_IN_VAR_H # include #endif #if HAVE_NET_IF_DL_H # include #endif #if HAVE_NET_NETOPT_H # include #endif #include #if HAVE_INET_ND_H # include #endif #include /* Required for IDRP */ #if HAVE_NETINET_IP_ICMP_H # include #endif #include " if test "x$ac_cv_header_security_openpam_h" = xyes; then : $as_echo "#define HAVE_OPENPAM_H 1" >>confdefs.h $as_echo "#define PAM_CONV_FUNC openpam_ttyconv" >>confdefs.h pam_conv_func="openpam_ttyconv" fi if test -z "$ac_cv_header_security_pam_misc_h$ac_cv_header_security_openpam_h" ; then { $as_echo "$as_me:${as_lineno-$LINENO}: WARNING: *** pam support will not be built ***" >&5 $as_echo "$as_me: WARNING: *** pam support will not be built ***" >&2;} with_libpam="no" fi fi if test "$with_libpam" = "yes"; then { $as_echo "$as_me:${as_lineno-$LINENO}: checking for pam_start in -lpam" >&5 $as_echo_n "checking for pam_start in -lpam... " >&6; } if ${ac_cv_lib_pam_pam_start+:} false; then : $as_echo_n "(cached) " >&6 else ac_check_lib_save_LIBS=$LIBS LIBS="-lpam $LIBS" cat confdefs.h - <<_ACEOF >conftest.$ac_ext /* end confdefs.h. */ /* Override any GCC internal prototype to avoid an error. Use char because int might match the return type of a GCC builtin and then its argument prototype would still apply. */ #ifdef __cplusplus extern "C" #endif char pam_start (); int main () { return pam_start (); ; return 0; } _ACEOF if ac_fn_c_try_link "$LINENO"; then : ac_cv_lib_pam_pam_start=yes else ac_cv_lib_pam_pam_start=no fi rm -f core conftest.err conftest.$ac_objext \ conftest$ac_exeext conftest.$ac_ext LIBS=$ac_check_lib_save_LIBS fi { $as_echo "$as_me:${as_lineno-$LINENO}: result: $ac_cv_lib_pam_pam_start" >&5 $as_echo "$ac_cv_lib_pam_pam_start" >&6; } if test "x$ac_cv_lib_pam_pam_start" = xyes; then : as_ac_Lib=`$as_echo "ac_cv_lib_pam_$pam_conv_func" | $as_tr_sh` { $as_echo "$as_me:${as_lineno-$LINENO}: checking for $pam_conv_func in -lpam" >&5 $as_echo_n "checking for $pam_conv_func in -lpam... " >&6; } if eval \${$as_ac_Lib+:} false; then : $as_echo_n "(cached) " >&6 else ac_check_lib_save_LIBS=$LIBS LIBS="-lpam $LIBS" cat confdefs.h - <<_ACEOF >conftest.$ac_ext /* end confdefs.h. */ /* Override any GCC internal prototype to avoid an error. Use char because int might match the return type of a GCC builtin and then its argument prototype would still apply. */ #ifdef __cplusplus extern "C" #endif char $pam_conv_func (); int main () { return $pam_conv_func (); ; return 0; } _ACEOF if ac_fn_c_try_link "$LINENO"; then : eval "$as_ac_Lib=yes" else eval "$as_ac_Lib=no" fi rm -f core conftest.err conftest.$ac_objext \ conftest$ac_exeext conftest.$ac_ext LIBS=$ac_check_lib_save_LIBS fi eval ac_res=\$$as_ac_Lib { $as_echo "$as_me:${as_lineno-$LINENO}: result: $ac_res" >&5 $as_echo "$ac_res" >&6; } if eval test \"x\$"$as_ac_Lib"\" = x"yes"; then : $as_echo "#define USE_PAM 1" >>confdefs.h LIBPAM="-lpam" else $as_echo "#define USE_PAM 1" >>confdefs.h LIBPAM="-lpam -lpam_misc" fi else { $as_echo "$as_me:${as_lineno-$LINENO}: checking for pam_end in -lpam" >&5 $as_echo_n "checking for pam_end in -lpam... " >&6; } if ${ac_cv_lib_pam_pam_end+:} false; then : $as_echo_n "(cached) " >&6 else ac_check_lib_save_LIBS=$LIBS LIBS="-lpam -ldl $LIBS" cat confdefs.h - <<_ACEOF >conftest.$ac_ext /* end confdefs.h. */ /* Override any GCC internal prototype to avoid an error. Use char because int might match the return type of a GCC builtin and then its argument prototype would still apply. */ #ifdef __cplusplus extern "C" #endif char pam_end (); int main () { return pam_end (); ; return 0; } _ACEOF if ac_fn_c_try_link "$LINENO"; then : ac_cv_lib_pam_pam_end=yes else ac_cv_lib_pam_pam_end=no fi rm -f core conftest.err conftest.$ac_objext \ conftest$ac_exeext conftest.$ac_ext LIBS=$ac_check_lib_save_LIBS fi { $as_echo "$as_me:${as_lineno-$LINENO}: result: $ac_cv_lib_pam_pam_end" >&5 $as_echo "$ac_cv_lib_pam_pam_end" >&6; } if test "x$ac_cv_lib_pam_pam_end" = xyes; then : as_ac_Lib=`$as_echo "ac_cv_lib_pam_$pam_conv_func" | $as_tr_sh` { $as_echo "$as_me:${as_lineno-$LINENO}: checking for $pam_conv_func in -lpam" >&5 $as_echo_n "checking for $pam_conv_func in -lpam... " >&6; } if eval \${$as_ac_Lib+:} false; then : $as_echo_n "(cached) " >&6 else ac_check_lib_save_LIBS=$LIBS LIBS="-lpam $LIBS" cat confdefs.h - <<_ACEOF >conftest.$ac_ext /* end confdefs.h. */ /* Override any GCC internal prototype to avoid an error. Use char because int might match the return type of a GCC builtin and then its argument prototype would still apply. */ #ifdef __cplusplus extern "C" #endif char $pam_conv_func (); int main () { return $pam_conv_func (); ; return 0; } _ACEOF if ac_fn_c_try_link "$LINENO"; then : eval "$as_ac_Lib=yes" else eval "$as_ac_Lib=no" fi rm -f core conftest.err conftest.$ac_objext \ conftest$ac_exeext conftest.$ac_ext LIBS=$ac_check_lib_save_LIBS fi eval ac_res=\$$as_ac_Lib { $as_echo "$as_me:${as_lineno-$LINENO}: result: $ac_res" >&5 $as_echo "$ac_res" >&6; } if eval test \"x\$"$as_ac_Lib"\" = x"yes"; then : $as_echo "#define USE_PAM 1" >>confdefs.h LIBPAM="-lpam -ldl" else $as_echo "#define USE_PAM 1" >>confdefs.h LIBPAM="-lpam -ldl -lpam_misc" fi else { $as_echo "$as_me:${as_lineno-$LINENO}: WARNING: *** pam support will not be built ***" >&5 $as_echo "$as_me: WARNING: *** pam support will not be built ***" >&2;} fi fi fi TMPLIBS="$LIBS" LIBS="" { $as_echo "$as_me:${as_lineno-$LINENO}: checking for library containing pow" >&5 $as_echo_n "checking for library containing pow... " >&6; } if ${ac_cv_search_pow+:} false; then : $as_echo_n "(cached) " >&6 else ac_func_search_save_LIBS=$LIBS cat confdefs.h - <<_ACEOF >conftest.$ac_ext /* end confdefs.h. */ /* Override any GCC internal prototype to avoid an error. Use char because int might match the return type of a GCC builtin and then its argument prototype would still apply. */ #ifdef __cplusplus extern "C" #endif char pow (); int main () { return pow (); ; return 0; } _ACEOF for ac_lib in '' m; do if test -z "$ac_lib"; then ac_res="none required" else ac_res=-l$ac_lib LIBS="-l$ac_lib $ac_func_search_save_LIBS" fi if ac_fn_c_try_link "$LINENO"; then : ac_cv_search_pow=$ac_res fi rm -f core conftest.err conftest.$ac_objext \ conftest$ac_exeext if ${ac_cv_search_pow+:} false; then : break fi done if ${ac_cv_search_pow+:} false; then : else ac_cv_search_pow=no fi rm conftest.$ac_ext LIBS=$ac_func_search_save_LIBS fi { $as_echo "$as_me:${as_lineno-$LINENO}: result: $ac_cv_search_pow" >&5 $as_echo "$ac_cv_search_pow" >&6; } ac_res=$ac_cv_search_pow if test "$ac_res" != no; then : test "$ac_res" = "none required" || LIBS="$ac_res $LIBS" LIBM="$LIBS" else { $as_echo "$as_me:${as_lineno-$LINENO}: WARNING: Unable to find working pow function - bgpd may not link" >&5 $as_echo "$as_me: WARNING: Unable to find working pow function - bgpd may not link" >&2;} fi LIBS="$TMPLIBS" for ac_func in ppoll do : ac_fn_c_check_func "$LINENO" "ppoll" "ac_cv_func_ppoll" if test "x$ac_cv_func_ppoll" = xyes; then : cat >>confdefs.h <<_ACEOF #define HAVE_PPOLL 1 _ACEOF $as_echo "#define HAVE_PPOLL 1" >>confdefs.h fi done for ac_func in pollts do : ac_fn_c_check_func "$LINENO" "pollts" "ac_cv_func_pollts" if test "x$ac_cv_func_pollts" = xyes; then : cat >>confdefs.h <<_ACEOF #define HAVE_POLLTS 1 _ACEOF $as_echo "#define HAVE_POLLTS 1" >>confdefs.h fi done ac_fn_c_check_header_mongrel "$LINENO" "asm-generic/unistd.h" "ac_cv_header_asm_generic_unistd_h" "$ac_includes_default" if test "x$ac_cv_header_asm_generic_unistd_h" = xyes; then : ac_fn_c_check_decl "$LINENO" "__NR_setns" "ac_cv_have_decl___NR_setns" "#ifdef SUNOS_5 #define _POSIX_C_SOURCE 200809L #define __EXTENSIONS__ #endif #include #include #include #include /* sys/conf.h depends on param.h on FBSD at least */ #include /* Required for MAXSIG */ #include #include #ifdef __APPLE__ # define __APPLE_USE_RFC_3542 #endif #include #include #include #include #if HAVE_NET_IF_VAR_H # include #endif #include #include #if HAVE_NETINET_IN_VAR_H # include #endif #if HAVE_NET_IF_DL_H # include #endif #if HAVE_NET_NETOPT_H # include #endif #include #if HAVE_INET_ND_H # include #endif #include /* Required for IDRP */ #if HAVE_NETINET_IP_ICMP_H # include #endif #include " if test "x$ac_cv_have_decl___NR_setns" = xyes; then : $as_echo "#define HAVE_NETNS 1" >>confdefs.h fi for ac_func in setns do : ac_fn_c_check_func "$LINENO" "setns" "ac_cv_func_setns" if test "x$ac_cv_func_setns" = xyes; then : cat >>confdefs.h <<_ACEOF #define HAVE_SETNS 1 _ACEOF fi done fi $as_echo "#define ISIS_METHOD_PFPACKET 1" >>confdefs.h $as_echo "#define ISIS_METHOD_DLPI 2" >>confdefs.h $as_echo "#define ISIS_METHOD_BPF 3" >>confdefs.h ac_fn_c_check_header_mongrel "$LINENO" "net/bpf.h" "ac_cv_header_net_bpf_h" "$ac_includes_default" if test "x$ac_cv_header_net_bpf_h" = xyes; then : fi ac_fn_c_check_header_mongrel "$LINENO" "sys/dlpi.h" "ac_cv_header_sys_dlpi_h" "$ac_includes_default" if test "x$ac_cv_header_sys_dlpi_h" = xyes; then : fi { $as_echo "$as_me:${as_lineno-$LINENO}: checking zebra IS-IS I/O method" >&5 $as_echo_n "checking zebra IS-IS I/O method... " >&6; } case "$host_os" in linux*) { $as_echo "$as_me:${as_lineno-$LINENO}: result: pfpacket" >&5 $as_echo "pfpacket" >&6; } ISIS_METHOD_MACRO="ISIS_METHOD_PFPACKET" ;; solaris* | sunos*) { $as_echo "$as_me:${as_lineno-$LINENO}: result: DLPI" >&5 $as_echo "DLPI" >&6; } ISIS_METHOD_MACRO="ISIS_METHOD_DLPI" ;; *) if test $ac_cv_header_net_bpf_h = no; then if test $ac_cv_header_sys_dlpi_h = no; then { $as_echo "$as_me:${as_lineno-$LINENO}: result: none" >&5 $as_echo "none" >&6; } if test "${enable_isisd}" = yes -o "${enable_fabricd}" = yes; then { { $as_echo "$as_me:${as_lineno-$LINENO}: error: in \`$ac_pwd':" >&5 $as_echo "$as_me: error: in \`$ac_pwd':" >&2;} as_fn_error $? "IS-IS support requested but no packet backend found See \`config.log' for more details" "$LINENO" 5; } fi { $as_echo "$as_me:${as_lineno-$LINENO}: WARNING: *** IS-IS support will not be built ***" >&5 $as_echo "$as_me: WARNING: *** IS-IS support will not be built ***" >&2;} enable_isisd="no" enable_fabricd="no" else { $as_echo "$as_me:${as_lineno-$LINENO}: result: DLPI" >&5 $as_echo "DLPI" >&6; } fi ISIS_METHOD_MACRO="ISIS_METHOD_DLPI" else { $as_echo "$as_me:${as_lineno-$LINENO}: result: BPF" >&5 $as_echo "BPF" >&6; } ISIS_METHOD_MACRO="ISIS_METHOD_BPF" fi ;; esac cat >>confdefs.h <<_ACEOF #define ISIS_METHOD $ISIS_METHOD_MACRO _ACEOF ac_fn_c_check_member "$LINENO" "struct ip_mreqn" "imr_ifindex" "ac_cv_member_struct_ip_mreqn_imr_ifindex" "#ifdef SUNOS_5 #define _POSIX_C_SOURCE 200809L #define __EXTENSIONS__ #endif #include #include #include #include /* sys/conf.h depends on param.h on FBSD at least */ #include /* Required for MAXSIG */ #include #include #ifdef __APPLE__ # define __APPLE_USE_RFC_3542 #endif #include #include #include #include #if HAVE_NET_IF_VAR_H # include #endif #include #include #if HAVE_NETINET_IN_VAR_H # include #endif #if HAVE_NET_IF_DL_H # include #endif #if HAVE_NET_NETOPT_H # include #endif #include #if HAVE_INET_ND_H # include #endif #include /* Required for IDRP */ #if HAVE_NETINET_IP_ICMP_H # include #endif " if test "x$ac_cv_member_struct_ip_mreqn_imr_ifindex" = xyes; then : cat >>confdefs.h <<_ACEOF #define HAVE_STRUCT_IP_MREQN_IMR_IFINDEX 1 _ACEOF fi for ac_header in linux/mroute.h do : ac_fn_c_check_header_compile "$LINENO" "linux/mroute.h" "ac_cv_header_linux_mroute_h" " #include #include #define _LINUX_IN_H /* For Linux <= 2.6.25 */ #include " if test "x$ac_cv_header_linux_mroute_h" = xyes; then : cat >>confdefs.h <<_ACEOF #define HAVE_LINUX_MROUTE_H 1 _ACEOF fi done for ac_header in netinet/ip_mroute.h do : ac_fn_c_check_header_compile "$LINENO" "netinet/ip_mroute.h" "ac_cv_header_netinet_ip_mroute_h" " #include #include #include #include " if test "x$ac_cv_header_netinet_ip_mroute_h" = xyes; then : cat >>confdefs.h <<_ACEOF #define HAVE_NETINET_IP_MROUTE_H 1 _ACEOF fi done { $as_echo "$as_me:${as_lineno-$LINENO}: checking for BSD struct ip_mreq hack" >&5 $as_echo_n "checking for BSD struct ip_mreq hack... " >&6; } cat confdefs.h - <<_ACEOF >conftest.$ac_ext /* end confdefs.h. */ #include int main () { #if (defined(__FreeBSD__) && ((__FreeBSD_version >= 500022 && __FreeBSD_version < 700000) || (__FreeBSD_version < 500000 && __FreeBSD_version >= 440000))) || (defined(__NetBSD__) && defined(__NetBSD_Version__) && __NetBSD_Version__ >= 106010000) || defined(__OpenBSD__) || defined(__APPLE__) || defined(__DragonFly__) || defined(__sun) return (0); #else #error No support for BSD struct ip_mreq hack detected #endif ; return 0; } _ACEOF if ac_fn_c_try_compile "$LINENO"; then : { $as_echo "$as_me:${as_lineno-$LINENO}: result: yes" >&5 $as_echo "yes" >&6; } $as_echo "#define HAVE_BSD_STRUCT_IP_MREQ_HACK 1" >>confdefs.h else { $as_echo "$as_me:${as_lineno-$LINENO}: result: no" >&5 $as_echo "no" >&6; } fi rm -f core conftest.err conftest.$ac_objext conftest.$ac_ext { $as_echo "$as_me:${as_lineno-$LINENO}: checking for RFC3678 protocol-independed API" >&5 $as_echo_n "checking for RFC3678 protocol-independed API... " >&6; } cat confdefs.h - <<_ACEOF >conftest.$ac_ext /* end confdefs.h. */ #include #include int main () { struct group_req gr; int sock; setsockopt(sock, IPPROTO_IP, MCAST_JOIN_GROUP, (void*)&gr, sizeof(gr)); ; return 0; } _ACEOF if ac_fn_c_try_compile "$LINENO"; then : { $as_echo "$as_me:${as_lineno-$LINENO}: result: yes" >&5 $as_echo "yes" >&6; } $as_echo "#define HAVE_RFC3678 1" >>confdefs.h else { $as_echo "$as_me:${as_lineno-$LINENO}: result: no" >&5 $as_echo "no" >&6; } fi rm -f core conftest.err conftest.$ac_objext conftest.$ac_ext ac_fn_c_check_header_compile "$LINENO" "net/if_media.h" "ac_cv_header_net_if_media_h" "#ifdef SUNOS_5 #define _POSIX_C_SOURCE 200809L #define __EXTENSIONS__ #endif #include #include #include #include /* sys/conf.h depends on param.h on FBSD at least */ #include /* Required for MAXSIG */ #include #include #ifdef __APPLE__ # define __APPLE_USE_RFC_3542 #endif #include #include #include #include #if HAVE_NET_IF_VAR_H # include #endif #include #include #if HAVE_NETINET_IN_VAR_H # include #endif #if HAVE_NET_IF_DL_H # include #endif #if HAVE_NET_NETOPT_H # include #endif #include #if HAVE_INET_ND_H # include #endif #include /* Required for IDRP */ #if HAVE_NETINET_IP_ICMP_H # include #endif #if HAVE_LINUX_MROUTE_H # include #endif #if HAVE_NETINET_IP_MROUTE_H # include #endif " if test "x$ac_cv_header_net_if_media_h" = xyes; then : ac_fn_c_check_member "$LINENO" "struct ifmediareq" "ifm_status" "ac_cv_member_struct_ifmediareq_ifm_status" "#ifdef SUNOS_5 #define _POSIX_C_SOURCE 200809L #define __EXTENSIONS__ #endif #include #include #include #include /* sys/conf.h depends on param.h on FBSD at least */ #include /* Required for MAXSIG */ #include #include #ifdef __APPLE__ # define __APPLE_USE_RFC_3542 #endif #include #include #include #include #if HAVE_NET_IF_VAR_H # include #endif #include #include #if HAVE_NETINET_IN_VAR_H # include #endif #if HAVE_NET_IF_DL_H # include #endif #if HAVE_NET_NETOPT_H # include #endif #include #if HAVE_INET_ND_H # include #endif #include /* Required for IDRP */ #if HAVE_NETINET_IP_ICMP_H # include #endif #if HAVE_LINUX_MROUTE_H # include #endif #if HAVE_NETINET_IP_MROUTE_H # include #endif #include " if test "x$ac_cv_member_struct_ifmediareq_ifm_status" = xyes; then : cat >>confdefs.h <<_ACEOF #define HAVE_STRUCT_IFMEDIAREQ_IFM_STATUS 1 _ACEOF $as_echo "#define HAVE_BSD_LINK_DETECT 1" >>confdefs.h fi fi ac_fn_c_check_member "$LINENO" "struct if_data" "ifi_link_state" "ac_cv_member_struct_if_data_ifi_link_state" "#ifdef SUNOS_5 #define _POSIX_C_SOURCE 200809L #define __EXTENSIONS__ #endif #include #include #include #include /* sys/conf.h depends on param.h on FBSD at least */ #include /* Required for MAXSIG */ #include #include #ifdef __APPLE__ # define __APPLE_USE_RFC_3542 #endif #include #include #include #include #if HAVE_NET_IF_VAR_H # include #endif #include #include #if HAVE_NETINET_IN_VAR_H # include #endif #if HAVE_NET_IF_DL_H # include #endif #if HAVE_NET_NETOPT_H # include #endif #include #if HAVE_INET_ND_H # include #endif #include /* Required for IDRP */ #if HAVE_NETINET_IP_ICMP_H # include #endif #if HAVE_LINUX_MROUTE_H # include #endif #if HAVE_NETINET_IP_MROUTE_H # include #endif " if test "x$ac_cv_member_struct_if_data_ifi_link_state" = xyes; then : cat >>confdefs.h <<_ACEOF #define HAVE_STRUCT_IF_DATA_IFI_LINK_STATE 1 _ACEOF $as_echo "#define HAVE_BSD_IFI_LINK_STATE 1" >>confdefs.h fi ac_fn_c_check_header_compile "$LINENO" "netinet/tcp.h" "ac_cv_header_netinet_tcp_h" "#ifdef SUNOS_5 #define _POSIX_C_SOURCE 200809L #define __EXTENSIONS__ #endif #include #include #include #include /* sys/conf.h depends on param.h on FBSD at least */ #include /* Required for MAXSIG */ #include #include #ifdef __APPLE__ # define __APPLE_USE_RFC_3542 #endif #include #include #include #include #if HAVE_NET_IF_VAR_H # include #endif #include #include #if HAVE_NETINET_IN_VAR_H # include #endif #if HAVE_NET_IF_DL_H # include #endif #if HAVE_NET_NETOPT_H # include #endif #include #if HAVE_INET_ND_H # include #endif #include /* Required for IDRP */ #if HAVE_NETINET_IP_ICMP_H # include #endif #if HAVE_LINUX_MROUTE_H # include #endif #if HAVE_NETINET_IP_MROUTE_H # include #endif " if test "x$ac_cv_header_netinet_tcp_h" = xyes; then : ac_fn_c_check_decl "$LINENO" "TCP_MD5SIG" "ac_cv_have_decl_TCP_MD5SIG" "#ifdef SUNOS_5 #define _POSIX_C_SOURCE 200809L #define __EXTENSIONS__ #endif #include #include #include #include /* sys/conf.h depends on param.h on FBSD at least */ #include /* Required for MAXSIG */ #include #include #ifdef __APPLE__ # define __APPLE_USE_RFC_3542 #endif #include #include #include #include #if HAVE_NET_IF_VAR_H # include #endif #include #include #if HAVE_NETINET_IN_VAR_H # include #endif #if HAVE_NET_IF_DL_H # include #endif #if HAVE_NET_NETOPT_H # include #endif #include #if HAVE_INET_ND_H # include #endif #include /* Required for IDRP */ #if HAVE_NETINET_IP_ICMP_H # include #endif #if HAVE_LINUX_MROUTE_H # include #endif #if HAVE_NETINET_IP_MROUTE_H # include #endif #include " if test "x$ac_cv_have_decl_TCP_MD5SIG" = xyes; then : ac_have_decl=1 else ac_have_decl=0 fi cat >>confdefs.h <<_ACEOF #define HAVE_DECL_TCP_MD5SIG $ac_have_decl _ACEOF fi if test $ac_cv_have_decl_TCP_MD5SIG = no; then ac_fn_c_check_header_mongrel "$LINENO" "linux/tcp.h" "ac_cv_header_linux_tcp_h" "$ac_includes_default" if test "x$ac_cv_header_linux_tcp_h" = xyes; then : ac_fn_c_check_decl "$LINENO" "TCP_MD5SIG" "ac_cv_have_decl_TCP_MD5SIG" "#ifdef SUNOS_5 #define _POSIX_C_SOURCE 200809L #define __EXTENSIONS__ #endif #include #include #include #include /* sys/conf.h depends on param.h on FBSD at least */ #include /* Required for MAXSIG */ #include #include #ifdef __APPLE__ # define __APPLE_USE_RFC_3542 #endif #include #include #include #include #if HAVE_NET_IF_VAR_H # include #endif #include #include #if HAVE_NETINET_IN_VAR_H # include #endif #if HAVE_NET_IF_DL_H # include #endif #if HAVE_NET_NETOPT_H # include #endif #include #if HAVE_INET_ND_H # include #endif #include /* Required for IDRP */ #if HAVE_NETINET_IP_ICMP_H # include #endif #if HAVE_LINUX_MROUTE_H # include #endif #if HAVE_NETINET_IP_MROUTE_H # include #endif #include " if test "x$ac_cv_have_decl_TCP_MD5SIG" = xyes; then : ac_have_decl=1 else ac_have_decl=0 fi cat >>confdefs.h <<_ACEOF #define HAVE_DECL_TCP_MD5SIG $ac_have_decl _ACEOF fi fi { $as_echo "$as_me:${as_lineno-$LINENO}: checking for crypt in -lcrypt" >&5 $as_echo_n "checking for crypt in -lcrypt... " >&6; } if ${ac_cv_lib_crypt_crypt+:} false; then : $as_echo_n "(cached) " >&6 else ac_check_lib_save_LIBS=$LIBS LIBS="-lcrypt $LIBS" cat confdefs.h - <<_ACEOF >conftest.$ac_ext /* end confdefs.h. */ /* Override any GCC internal prototype to avoid an error. Use char because int might match the return type of a GCC builtin and then its argument prototype would still apply. */ #ifdef __cplusplus extern "C" #endif char crypt (); int main () { return crypt (); ; return 0; } _ACEOF if ac_fn_c_try_link "$LINENO"; then : ac_cv_lib_crypt_crypt=yes else ac_cv_lib_crypt_crypt=no fi rm -f core conftest.err conftest.$ac_objext \ conftest$ac_exeext conftest.$ac_ext LIBS=$ac_check_lib_save_LIBS fi { $as_echo "$as_me:${as_lineno-$LINENO}: result: $ac_cv_lib_crypt_crypt" >&5 $as_echo "$ac_cv_lib_crypt_crypt" >&6; } if test "x$ac_cv_lib_crypt_crypt" = xyes; then : cat >>confdefs.h <<_ACEOF #define HAVE_LIBCRYPT 1 _ACEOF LIBS="-lcrypt $LIBS" else { $as_echo "$as_me:${as_lineno-$LINENO}: checking for DES_crypt in -lcrypto" >&5 $as_echo_n "checking for DES_crypt in -lcrypto... " >&6; } if ${ac_cv_lib_crypto_DES_crypt+:} false; then : $as_echo_n "(cached) " >&6 else ac_check_lib_save_LIBS=$LIBS LIBS="-lcrypto $LIBS" cat confdefs.h - <<_ACEOF >conftest.$ac_ext /* end confdefs.h. */ /* Override any GCC internal prototype to avoid an error. Use char because int might match the return type of a GCC builtin and then its argument prototype would still apply. */ #ifdef __cplusplus extern "C" #endif char DES_crypt (); int main () { return DES_crypt (); ; return 0; } _ACEOF if ac_fn_c_try_link "$LINENO"; then : ac_cv_lib_crypto_DES_crypt=yes else ac_cv_lib_crypto_DES_crypt=no fi rm -f core conftest.err conftest.$ac_objext \ conftest$ac_exeext conftest.$ac_ext LIBS=$ac_check_lib_save_LIBS fi { $as_echo "$as_me:${as_lineno-$LINENO}: result: $ac_cv_lib_crypto_DES_crypt" >&5 $as_echo "$ac_cv_lib_crypto_DES_crypt" >&6; } if test "x$ac_cv_lib_crypto_DES_crypt" = xyes; then : cat >>confdefs.h <<_ACEOF #define HAVE_LIBCRYPTO 1 _ACEOF LIBS="-lcrypto $LIBS" fi fi { $as_echo "$as_me:${as_lineno-$LINENO}: checking for res_init in -lresolv" >&5 $as_echo_n "checking for res_init in -lresolv... " >&6; } if ${ac_cv_lib_resolv_res_init+:} false; then : $as_echo_n "(cached) " >&6 else ac_check_lib_save_LIBS=$LIBS LIBS="-lresolv $LIBS" cat confdefs.h - <<_ACEOF >conftest.$ac_ext /* end confdefs.h. */ /* Override any GCC internal prototype to avoid an error. Use char because int might match the return type of a GCC builtin and then its argument prototype would still apply. */ #ifdef __cplusplus extern "C" #endif char res_init (); int main () { return res_init (); ; return 0; } _ACEOF if ac_fn_c_try_link "$LINENO"; then : ac_cv_lib_resolv_res_init=yes else ac_cv_lib_resolv_res_init=no fi rm -f core conftest.err conftest.$ac_objext \ conftest$ac_exeext conftest.$ac_ext LIBS=$ac_check_lib_save_LIBS fi { $as_echo "$as_me:${as_lineno-$LINENO}: result: $ac_cv_lib_resolv_res_init" >&5 $as_echo "$ac_cv_lib_resolv_res_init" >&6; } if test "x$ac_cv_lib_resolv_res_init" = xyes; then : cat >>confdefs.h <<_ACEOF #define HAVE_LIBRESOLV 1 _ACEOF LIBS="-lresolv $LIBS" fi if test "x$enable_pcreposix" = "xyes"; then { $as_echo "$as_me:${as_lineno-$LINENO}: checking for regexec in -lpcreposix" >&5 $as_echo_n "checking for regexec in -lpcreposix... " >&6; } if ${ac_cv_lib_pcreposix_regexec+:} false; then : $as_echo_n "(cached) " >&6 else ac_check_lib_save_LIBS=$LIBS LIBS="-lpcreposix $LIBS" cat confdefs.h - <<_ACEOF >conftest.$ac_ext /* end confdefs.h. */ /* Override any GCC internal prototype to avoid an error. Use char because int might match the return type of a GCC builtin and then its argument prototype would still apply. */ #ifdef __cplusplus extern "C" #endif char regexec (); int main () { return regexec (); ; return 0; } _ACEOF if ac_fn_c_try_link "$LINENO"; then : ac_cv_lib_pcreposix_regexec=yes else ac_cv_lib_pcreposix_regexec=no fi rm -f core conftest.err conftest.$ac_objext \ conftest$ac_exeext conftest.$ac_ext LIBS=$ac_check_lib_save_LIBS fi { $as_echo "$as_me:${as_lineno-$LINENO}: result: $ac_cv_lib_pcreposix_regexec" >&5 $as_echo "$ac_cv_lib_pcreposix_regexec" >&6; } if test "x$ac_cv_lib_pcreposix_regexec" = xyes; then : cat >>confdefs.h <<_ACEOF #define HAVE_LIBPCREPOSIX 1 _ACEOF LIBS="-lpcreposix $LIBS" else as_fn_error $? "--enable-pcreposix given but unable to find libpcreposix" "$LINENO" 5 fi fi pkg_failed=no { $as_echo "$as_me:${as_lineno-$LINENO}: checking for CARES (libcares)" >&5 $as_echo_n "checking for CARES (libcares)... " >&6; } if test -n "$CARES_CFLAGS"; then pkg_cv_CARES_CFLAGS="$CARES_CFLAGS" elif test -n "$PKG_CONFIG"; then if test -n "$PKG_CONFIG" && \ { { $as_echo "$as_me:${as_lineno-$LINENO}: \$PKG_CONFIG --exists --print-errors \"libcares\""; } >&5 ($PKG_CONFIG --exists --print-errors "libcares") 2>&5 ac_status=$? $as_echo "$as_me:${as_lineno-$LINENO}: \$? = $ac_status" >&5 test $ac_status = 0; }; then pkg_cv_CARES_CFLAGS=`$PKG_CONFIG --cflags "libcares" 2>/dev/null` test "x$?" != "x0" && pkg_failed=yes else pkg_failed=yes fi else pkg_failed=untried fi if test -n "$CARES_LIBS"; then pkg_cv_CARES_LIBS="$CARES_LIBS" elif test -n "$PKG_CONFIG"; then if test -n "$PKG_CONFIG" && \ { { $as_echo "$as_me:${as_lineno-$LINENO}: \$PKG_CONFIG --exists --print-errors \"libcares\""; } >&5 ($PKG_CONFIG --exists --print-errors "libcares") 2>&5 ac_status=$? $as_echo "$as_me:${as_lineno-$LINENO}: \$? = $ac_status" >&5 test $ac_status = 0; }; then pkg_cv_CARES_LIBS=`$PKG_CONFIG --libs "libcares" 2>/dev/null` test "x$?" != "x0" && pkg_failed=yes else pkg_failed=yes fi else pkg_failed=untried fi if test $pkg_failed = yes; then { $as_echo "$as_me:${as_lineno-$LINENO}: result: no" >&5 $as_echo "no" >&6; } if $PKG_CONFIG --atleast-pkgconfig-version 0.20; then _pkg_short_errors_supported=yes else _pkg_short_errors_supported=no fi if test $_pkg_short_errors_supported = yes; then CARES_PKG_ERRORS=`$PKG_CONFIG --short-errors --print-errors --cflags --libs "libcares" 2>&1` else CARES_PKG_ERRORS=`$PKG_CONFIG --print-errors --cflags --libs "libcares" 2>&1` fi # Put the nasty error message in config.log where it belongs echo "$CARES_PKG_ERRORS" >&5 c_ares_found=false elif test $pkg_failed = untried; then { $as_echo "$as_me:${as_lineno-$LINENO}: result: no" >&5 $as_echo "no" >&6; } c_ares_found=false else CARES_CFLAGS=$pkg_cv_CARES_CFLAGS CARES_LIBS=$pkg_cv_CARES_LIBS { $as_echo "$as_me:${as_lineno-$LINENO}: result: yes" >&5 $as_echo "yes" >&6; } c_ares_found=true fi if $c_ares_found; then CARES_TRUE= CARES_FALSE='#' else CARES_TRUE='#' CARES_FALSE= fi fi ac_fn_c_check_member "$LINENO" "struct utsname" "domainname" "ac_cv_member_struct_utsname_domainname" "#include " if test "x$ac_cv_member_struct_utsname_domainname" = xyes; then : cat >>confdefs.h <<_ACEOF #define HAVE_STRUCT_UTSNAME_DOMAINNAME 1 _ACEOF fi for ac_header in netinet6/in6.h netinet/in6_var.h \ netinet6/in6_var.h netinet6/nd6.h do : as_ac_Header=`$as_echo "ac_cv_header_$ac_header" | $as_tr_sh` ac_fn_c_check_header_compile "$LINENO" "$ac_header" "$as_ac_Header" "#ifdef SUNOS_5 #define _POSIX_C_SOURCE 200809L #define __EXTENSIONS__ #endif #include #include #include #include /* sys/conf.h depends on param.h on FBSD at least */ #include /* Required for MAXSIG */ #include #include #ifdef __APPLE__ # define __APPLE_USE_RFC_3542 #endif #include #include #include #include #if HAVE_NET_IF_VAR_H # include #endif #include #include #if HAVE_NETINET_IN_VAR_H # include #endif #if HAVE_NET_IF_DL_H # include #endif #if HAVE_NET_NETOPT_H # include #endif #include #if HAVE_INET_ND_H # include #endif #include /* Required for IDRP */ #if HAVE_NETINET_IP_ICMP_H # include #endif #if HAVE_LINUX_MROUTE_H # include #endif #if HAVE_NETINET_IP_MROUTE_H # include #endif " if eval test \"x\$"$as_ac_Header"\" = x"yes"; then : cat >>confdefs.h <<_ACEOF #define `$as_echo "HAVE_$ac_header" | $as_tr_cpp` 1 _ACEOF fi done if test "${enable_ldpd}" != "no"; then : $as_echo "#define HAVE_LDPD 1" >>confdefs.h fi if test "$enable_bfdd" = "no"; then $as_echo "#define HAVE_BFDD 0" >>confdefs.h BFDD="" else $as_echo "#define HAVE_BFDD 1" >>confdefs.h BFDD="bfdd" case $host_os in linux*) $as_echo "#define BFD_LINUX 1" >>confdefs.h ;; *) $as_echo "#define BFD_BSD 1" >>confdefs.h ;; esac fi if test "$ac_cv_lib_json_c_json_object_get" = no -a "x$BFDD" = "xbfdd"; then as_fn_error $? "\"you must use json-c library to use bfdd\"" "$LINENO" 5 fi NHRPD="" case "$host_os" in linux*) case "${enable_nhrpd}" in no) ;; yes) if test "$c_ares_found" != "true" ; then as_fn_error $? "nhrpd requires libcares. Please install c-ares and its -dev headers." "$LINENO" 5 fi NHRPD="nhrpd" ;; *) if test "$c_ares_found" = "true" ; then NHRPD="nhrpd" fi ;; esac ;; *) if test "${enable_nhrpd}" = "yes"; then as_fn_error $? "nhrpd requires kernel APIs that are only present on Linux." "$LINENO" 5 fi ;; esac if test "${enable_watchfrr}" = "no";then WATCHFRR="" else WATCHFRR="watchfrr" fi OSPFCLIENT="" if test "${enable_ospfapi}" != "no";then $as_echo "#define SUPPORT_OSPF_API 1" >>confdefs.h if test "${enable_ospfclient}" != "no";then OSPFCLIENT="ospfclient" fi fi if test "${enable_bgp_announce}" = "no";then $as_echo "#define DISABLE_BGP_ANNOUNCE 1" >>confdefs.h else $as_echo "#define DISABLE_BGP_ANNOUNCE 0" >>confdefs.h fi if test "${enable_bgp_vnc}" != "no";then $as_echo "#define ENABLE_BGP_VNC 1" >>confdefs.h fi bgpd_bmp=false case "${enable_bmp}" in no) ;; yes) if test "$c_ares_found" != "true" ; then as_fn_error $? "BMP support requires libcares. Please install c-ares and its -dev headers." "$LINENO" 5 fi bgpd_bmp=true ;; *) if test "$c_ares_found" = "true" ; then bgpd_bmp=true fi ;; esac if test "${enable_clippy_only}" != "yes"; then if test "${enable_snmp}" != "" -a "${enable_snmp}" != "no"; then if test -n "$ac_tool_prefix"; then # Extract the first word of "${ac_tool_prefix}net-snmp-config", so it can be a program name with args. set dummy ${ac_tool_prefix}net-snmp-config; ac_word=$2 { $as_echo "$as_me:${as_lineno-$LINENO}: checking for $ac_word" >&5 $as_echo_n "checking for $ac_word... " >&6; } if ${ac_cv_path_NETSNMP_CONFIG+:} false; then : $as_echo_n "(cached) " >&6 else case $NETSNMP_CONFIG in [\\/]* | ?:[\\/]*) ac_cv_path_NETSNMP_CONFIG="$NETSNMP_CONFIG" # Let the user override the test with a path. ;; *) as_save_IFS=$IFS; IFS=$PATH_SEPARATOR for as_dir in $PATH do IFS=$as_save_IFS test -z "$as_dir" && as_dir=. for ac_exec_ext in '' $ac_executable_extensions; do if as_fn_executable_p "$as_dir/$ac_word$ac_exec_ext"; then ac_cv_path_NETSNMP_CONFIG="$as_dir/$ac_word$ac_exec_ext" $as_echo "$as_me:${as_lineno-$LINENO}: found $as_dir/$ac_word$ac_exec_ext" >&5 break 2 fi done done IFS=$as_save_IFS ;; esac fi NETSNMP_CONFIG=$ac_cv_path_NETSNMP_CONFIG if test -n "$NETSNMP_CONFIG"; then { $as_echo "$as_me:${as_lineno-$LINENO}: result: $NETSNMP_CONFIG" >&5 $as_echo "$NETSNMP_CONFIG" >&6; } else { $as_echo "$as_me:${as_lineno-$LINENO}: result: no" >&5 $as_echo "no" >&6; } fi fi if test -z "$ac_cv_path_NETSNMP_CONFIG"; then ac_pt_NETSNMP_CONFIG=$NETSNMP_CONFIG # Extract the first word of "net-snmp-config", so it can be a program name with args. set dummy net-snmp-config; ac_word=$2 { $as_echo "$as_me:${as_lineno-$LINENO}: checking for $ac_word" >&5 $as_echo_n "checking for $ac_word... " >&6; } if ${ac_cv_path_ac_pt_NETSNMP_CONFIG+:} false; then : $as_echo_n "(cached) " >&6 else case $ac_pt_NETSNMP_CONFIG in [\\/]* | ?:[\\/]*) ac_cv_path_ac_pt_NETSNMP_CONFIG="$ac_pt_NETSNMP_CONFIG" # Let the user override the test with a path. ;; *) as_save_IFS=$IFS; IFS=$PATH_SEPARATOR for as_dir in $PATH do IFS=$as_save_IFS test -z "$as_dir" && as_dir=. for ac_exec_ext in '' $ac_executable_extensions; do if as_fn_executable_p "$as_dir/$ac_word$ac_exec_ext"; then ac_cv_path_ac_pt_NETSNMP_CONFIG="$as_dir/$ac_word$ac_exec_ext" $as_echo "$as_me:${as_lineno-$LINENO}: found $as_dir/$ac_word$ac_exec_ext" >&5 break 2 fi done done IFS=$as_save_IFS ;; esac fi ac_pt_NETSNMP_CONFIG=$ac_cv_path_ac_pt_NETSNMP_CONFIG if test -n "$ac_pt_NETSNMP_CONFIG"; then { $as_echo "$as_me:${as_lineno-$LINENO}: result: $ac_pt_NETSNMP_CONFIG" >&5 $as_echo "$ac_pt_NETSNMP_CONFIG" >&6; } else { $as_echo "$as_me:${as_lineno-$LINENO}: result: no" >&5 $as_echo "no" >&6; } fi if test "x$ac_pt_NETSNMP_CONFIG" = x; then NETSNMP_CONFIG="no" else case $cross_compiling:$ac_tool_warned in yes:) { $as_echo "$as_me:${as_lineno-$LINENO}: WARNING: using cross tools not prefixed with host triplet" >&5 $as_echo "$as_me: WARNING: using cross tools not prefixed with host triplet" >&2;} ac_tool_warned=yes ;; esac NETSNMP_CONFIG=$ac_pt_NETSNMP_CONFIG fi else NETSNMP_CONFIG="$ac_cv_path_NETSNMP_CONFIG" fi if test x"$NETSNMP_CONFIG" = x"no"; then as_fn_error $? "--enable-snmp given but unable to find net-snmp-config" "$LINENO" 5 fi SNMP_LIBS="`${NETSNMP_CONFIG} --agent-libs`" SNMP_CFLAGS="`${NETSNMP_CONFIG} --base-cflags`" # net-snmp lists all of its own dependencies. we absolutely do not want that # among other things we avoid a GPL vs. OpenSSL license conflict here for removelib in crypto ssl sensors pci wrap; do SNMP_LIBS="`echo $SNMP_LIBS | sed -e 's/\(^\|\s\)-l'$removelib'\b/ /g' -e 's/\(^\|\s\)\(^\s*\/\)\?lib'$removelib'\.^\s\+\b/ /g'`" done { $as_echo "$as_me:${as_lineno-$LINENO}: checking whether we can link to Net-SNMP" >&5 $as_echo_n "checking whether we can link to Net-SNMP... " >&6; } { ac_ext=c ac_cpp='$CPP $CPPFLAGS' ac_compile='$CC -c $CFLAGS $CPPFLAGS conftest.$ac_ext >&5' ac_link='$CC -o conftest$ac_exeext $CFLAGS $CPPFLAGS $LDFLAGS conftest.$ac_ext $LIBS >&5' ac_compiler_gnu=$ac_cv_c_compiler_gnu ac_cflags_save="$CFLAGS" ac_libs_save="$LIBS" CFLAGS="$CFLAGS $SNMP_CFLAGS" LIBS="$LIBS $SNMP_LIBS" cat confdefs.h - <<_ACEOF >conftest.$ac_ext /* end confdefs.h. */ int main(void); int main () { { return 0; } ; return 0; } _ACEOF if ac_fn_c_try_link "$LINENO"; then : CFLAGS="$ac_cflags_save" LIBS="$ac_libs_save" { $as_echo "$as_me:${as_lineno-$LINENO}: result: yes" >&5 $as_echo "yes" >&6; } else CFLAGS="$ac_cflags_save" LIBS="$ac_libs_save" { $as_echo "$as_me:${as_lineno-$LINENO}: result: no" >&5 $as_echo "no" >&6; } as_fn_error $? "--enable-snmp given but not usable" "$LINENO" 5 fi rm -f core conftest.err conftest.$ac_objext \ conftest$ac_exeext conftest.$ac_ext ac_ext=c ac_cpp='$CPP $CPPFLAGS' ac_compile='$CC -c $CFLAGS $CPPFLAGS conftest.$ac_ext >&5' ac_link='$CC -o conftest$ac_exeext $CFLAGS $CPPFLAGS $LDFLAGS conftest.$ac_ext $LIBS >&5' ac_compiler_gnu=$ac_cv_c_compiler_gnu } case "${enable_snmp}" in yes) SNMP_METHOD=agentx ;; agentx) SNMP_METHOD="${enable_snmp}" ;; *) as_fn_error $? "--enable-snmp given with an unknown method (${enable_snmp}). Use yes or agentx" "$LINENO" 5 ;; esac cat >>confdefs.h <<_ACEOF #define `$as_echo "SNMP_${SNMP_METHOD}" | $as_tr_cpp` /**/ _ACEOF fi pkg_failed=no { $as_echo "$as_me:${as_lineno-$LINENO}: checking for LIBYANG (libyang >= 0.16.105)" >&5 $as_echo_n "checking for LIBYANG (libyang >= 0.16.105)... " >&6; } if test -n "$LIBYANG_CFLAGS"; then pkg_cv_LIBYANG_CFLAGS="$LIBYANG_CFLAGS" elif test -n "$PKG_CONFIG"; then if test -n "$PKG_CONFIG" && \ { { $as_echo "$as_me:${as_lineno-$LINENO}: \$PKG_CONFIG --exists --print-errors \"libyang >= 0.16.105\""; } >&5 ($PKG_CONFIG --exists --print-errors "libyang >= 0.16.105") 2>&5 ac_status=$? $as_echo "$as_me:${as_lineno-$LINENO}: \$? = $ac_status" >&5 test $ac_status = 0; }; then pkg_cv_LIBYANG_CFLAGS=`$PKG_CONFIG --cflags "libyang >= 0.16.105" 2>/dev/null` test "x$?" != "x0" && pkg_failed=yes else pkg_failed=yes fi else pkg_failed=untried fi if test -n "$LIBYANG_LIBS"; then pkg_cv_LIBYANG_LIBS="$LIBYANG_LIBS" elif test -n "$PKG_CONFIG"; then if test -n "$PKG_CONFIG" && \ { { $as_echo "$as_me:${as_lineno-$LINENO}: \$PKG_CONFIG --exists --print-errors \"libyang >= 0.16.105\""; } >&5 ($PKG_CONFIG --exists --print-errors "libyang >= 0.16.105") 2>&5 ac_status=$? $as_echo "$as_me:${as_lineno-$LINENO}: \$? = $ac_status" >&5 test $ac_status = 0; }; then pkg_cv_LIBYANG_LIBS=`$PKG_CONFIG --libs "libyang >= 0.16.105" 2>/dev/null` test "x$?" != "x0" && pkg_failed=yes else pkg_failed=yes fi else pkg_failed=untried fi if test $pkg_failed = yes; then { $as_echo "$as_me:${as_lineno-$LINENO}: result: no" >&5 $as_echo "no" >&6; } if $PKG_CONFIG --atleast-pkgconfig-version 0.20; then _pkg_short_errors_supported=yes else _pkg_short_errors_supported=no fi if test $_pkg_short_errors_supported = yes; then LIBYANG_PKG_ERRORS=`$PKG_CONFIG --short-errors --print-errors --cflags --libs "libyang >= 0.16.105" 2>&1` else LIBYANG_PKG_ERRORS=`$PKG_CONFIG --print-errors --cflags --libs "libyang >= 0.16.105" 2>&1` fi # Put the nasty error message in config.log where it belongs echo "$LIBYANG_PKG_ERRORS" >&5 as_fn_error $? "libyang (>= 0.16.105) was not found on your system." "$LINENO" 5 elif test $pkg_failed = untried; then { $as_echo "$as_me:${as_lineno-$LINENO}: result: no" >&5 $as_echo "no" >&6; } as_fn_error $? "libyang (>= 0.16.105) was not found on your system." "$LINENO" 5 else LIBYANG_CFLAGS=$pkg_cv_LIBYANG_CFLAGS LIBYANG_LIBS=$pkg_cv_LIBYANG_LIBS { $as_echo "$as_me:${as_lineno-$LINENO}: result: yes" >&5 $as_echo "yes" >&6; } fi ac_cflags_save="$CFLAGS" CFLAGS="$CFLAGS $LIBYANG_CFLAGS" ac_fn_c_check_member "$LINENO" "struct lyd_node" "priv" "ac_cv_member_struct_lyd_node_priv" "#include " if test "x$ac_cv_member_struct_lyd_node_priv" = xyes; then : else as_fn_error $? "libyang needs to be compiled with ENABLE_LYD_PRIV=ON. Instructions for this are included in the build documentation for your platform at http://docs.frrouting.org/projects/dev-guide/en/latest/building.html " "$LINENO" 5 fi CFLAGS="$ac_cflags_save" SQLITE3=false if test "$enable_config_rollbacks" = "yes"; then pkg_failed=no { $as_echo "$as_me:${as_lineno-$LINENO}: checking for SQLITE3 (sqlite3)" >&5 $as_echo_n "checking for SQLITE3 (sqlite3)... " >&6; } if test -n "$SQLITE3_CFLAGS"; then pkg_cv_SQLITE3_CFLAGS="$SQLITE3_CFLAGS" elif test -n "$PKG_CONFIG"; then if test -n "$PKG_CONFIG" && \ { { $as_echo "$as_me:${as_lineno-$LINENO}: \$PKG_CONFIG --exists --print-errors \"sqlite3\""; } >&5 ($PKG_CONFIG --exists --print-errors "sqlite3") 2>&5 ac_status=$? $as_echo "$as_me:${as_lineno-$LINENO}: \$? = $ac_status" >&5 test $ac_status = 0; }; then pkg_cv_SQLITE3_CFLAGS=`$PKG_CONFIG --cflags "sqlite3" 2>/dev/null` test "x$?" != "x0" && pkg_failed=yes else pkg_failed=yes fi else pkg_failed=untried fi if test -n "$SQLITE3_LIBS"; then pkg_cv_SQLITE3_LIBS="$SQLITE3_LIBS" elif test -n "$PKG_CONFIG"; then if test -n "$PKG_CONFIG" && \ { { $as_echo "$as_me:${as_lineno-$LINENO}: \$PKG_CONFIG --exists --print-errors \"sqlite3\""; } >&5 ($PKG_CONFIG --exists --print-errors "sqlite3") 2>&5 ac_status=$? $as_echo "$as_me:${as_lineno-$LINENO}: \$? = $ac_status" >&5 test $ac_status = 0; }; then pkg_cv_SQLITE3_LIBS=`$PKG_CONFIG --libs "sqlite3" 2>/dev/null` test "x$?" != "x0" && pkg_failed=yes else pkg_failed=yes fi else pkg_failed=untried fi if test $pkg_failed = yes; then { $as_echo "$as_me:${as_lineno-$LINENO}: result: no" >&5 $as_echo "no" >&6; } if $PKG_CONFIG --atleast-pkgconfig-version 0.20; then _pkg_short_errors_supported=yes else _pkg_short_errors_supported=no fi if test $_pkg_short_errors_supported = yes; then SQLITE3_PKG_ERRORS=`$PKG_CONFIG --short-errors --print-errors --cflags --libs "sqlite3" 2>&1` else SQLITE3_PKG_ERRORS=`$PKG_CONFIG --print-errors --cflags --libs "sqlite3" 2>&1` fi # Put the nasty error message in config.log where it belongs echo "$SQLITE3_PKG_ERRORS" >&5 as_fn_error $? "--enable-config-rollbacks given but sqlite3 was not found on your system." "$LINENO" 5 elif test $pkg_failed = untried; then { $as_echo "$as_me:${as_lineno-$LINENO}: result: no" >&5 $as_echo "no" >&6; } as_fn_error $? "--enable-config-rollbacks given but sqlite3 was not found on your system." "$LINENO" 5 else SQLITE3_CFLAGS=$pkg_cv_SQLITE3_CFLAGS SQLITE3_LIBS=$pkg_cv_SQLITE3_LIBS { $as_echo "$as_me:${as_lineno-$LINENO}: result: yes" >&5 $as_echo "yes" >&6; } $as_echo "#define HAVE_CONFIG_ROLLBACKS 1" >>confdefs.h $as_echo "#define HAVE_SQLITE3 1" >>confdefs.h SQLITE3=true fi fi if test "$enable_confd" != "" -a "$enable_confd" != "no"; then # Extract the first word of "confd", so it can be a program name with args. set dummy confd; ac_word=$2 { $as_echo "$as_me:${as_lineno-$LINENO}: checking for $ac_word" >&5 $as_echo_n "checking for $ac_word... " >&6; } if ${ac_cv_prog_CONFD+:} false; then : $as_echo_n "(cached) " >&6 else if test -n "$CONFD"; then ac_cv_prog_CONFD="$CONFD" # Let the user override the test. else as_save_IFS=$IFS; IFS=$PATH_SEPARATOR for as_dir in "${enable_confd}/bin" do IFS=$as_save_IFS test -z "$as_dir" && as_dir=. for ac_exec_ext in '' $ac_executable_extensions; do if as_fn_executable_p "$as_dir/$ac_word$ac_exec_ext"; then ac_cv_prog_CONFD="confd" $as_echo "$as_me:${as_lineno-$LINENO}: found $as_dir/$ac_word$ac_exec_ext" >&5 break 2 fi done done IFS=$as_save_IFS test -z "$ac_cv_prog_CONFD" && ac_cv_prog_CONFD="/bin/false" fi fi CONFD=$ac_cv_prog_CONFD if test -n "$CONFD"; then { $as_echo "$as_me:${as_lineno-$LINENO}: result: $CONFD" >&5 $as_echo "$CONFD" >&6; } else { $as_echo "$as_me:${as_lineno-$LINENO}: result: no" >&5 $as_echo "no" >&6; } fi if test "x$CONFD" = "x/bin/false"; then as_fn_error $? "confd was not found on your system." "$LINENO" 5] fi CONFD_CFLAGS="-I${enable_confd}/include -L${enable_confd}/lib" CONFD_LIBS="-lconfd" $as_echo "#define HAVE_CONFD 1" >>confdefs.h fi if test "$enable_sysrepo" = "yes"; then pkg_failed=no { $as_echo "$as_me:${as_lineno-$LINENO}: checking for SYSREPO (libsysrepo)" >&5 $as_echo_n "checking for SYSREPO (libsysrepo)... " >&6; } if test -n "$SYSREPO_CFLAGS"; then pkg_cv_SYSREPO_CFLAGS="$SYSREPO_CFLAGS" elif test -n "$PKG_CONFIG"; then if test -n "$PKG_CONFIG" && \ { { $as_echo "$as_me:${as_lineno-$LINENO}: \$PKG_CONFIG --exists --print-errors \"libsysrepo\""; } >&5 ($PKG_CONFIG --exists --print-errors "libsysrepo") 2>&5 ac_status=$? $as_echo "$as_me:${as_lineno-$LINENO}: \$? = $ac_status" >&5 test $ac_status = 0; }; then pkg_cv_SYSREPO_CFLAGS=`$PKG_CONFIG --cflags "libsysrepo" 2>/dev/null` test "x$?" != "x0" && pkg_failed=yes else pkg_failed=yes fi else pkg_failed=untried fi if test -n "$SYSREPO_LIBS"; then pkg_cv_SYSREPO_LIBS="$SYSREPO_LIBS" elif test -n "$PKG_CONFIG"; then if test -n "$PKG_CONFIG" && \ { { $as_echo "$as_me:${as_lineno-$LINENO}: \$PKG_CONFIG --exists --print-errors \"libsysrepo\""; } >&5 ($PKG_CONFIG --exists --print-errors "libsysrepo") 2>&5 ac_status=$? $as_echo "$as_me:${as_lineno-$LINENO}: \$? = $ac_status" >&5 test $ac_status = 0; }; then pkg_cv_SYSREPO_LIBS=`$PKG_CONFIG --libs "libsysrepo" 2>/dev/null` test "x$?" != "x0" && pkg_failed=yes else pkg_failed=yes fi else pkg_failed=untried fi if test $pkg_failed = yes; then { $as_echo "$as_me:${as_lineno-$LINENO}: result: no" >&5 $as_echo "no" >&6; } if $PKG_CONFIG --atleast-pkgconfig-version 0.20; then _pkg_short_errors_supported=yes else _pkg_short_errors_supported=no fi if test $_pkg_short_errors_supported = yes; then SYSREPO_PKG_ERRORS=`$PKG_CONFIG --short-errors --print-errors --cflags --libs "libsysrepo" 2>&1` else SYSREPO_PKG_ERRORS=`$PKG_CONFIG --print-errors --cflags --libs "libsysrepo" 2>&1` fi # Put the nasty error message in config.log where it belongs echo "$SYSREPO_PKG_ERRORS" >&5 SYSREPO=false as_fn_error $? "sysrepo was not found on your system." "$LINENO" 5 elif test $pkg_failed = untried; then { $as_echo "$as_me:${as_lineno-$LINENO}: result: no" >&5 $as_echo "no" >&6; } SYSREPO=false as_fn_error $? "sysrepo was not found on your system." "$LINENO" 5 else SYSREPO_CFLAGS=$pkg_cv_SYSREPO_CFLAGS SYSREPO_LIBS=$pkg_cv_SYSREPO_LIBS { $as_echo "$as_me:${as_lineno-$LINENO}: result: yes" >&5 $as_echo "yes" >&6; } $as_echo "#define HAVE_SYSREPO 1" >>confdefs.h SYSREPO=true fi fi if test "$enable_grpc" = "yes"; then pkg_failed=no { $as_echo "$as_me:${as_lineno-$LINENO}: checking for GRPC (grpc grpc++ protobuf)" >&5 $as_echo_n "checking for GRPC (grpc grpc++ protobuf)... " >&6; } if test -n "$GRPC_CFLAGS"; then pkg_cv_GRPC_CFLAGS="$GRPC_CFLAGS" elif test -n "$PKG_CONFIG"; then if test -n "$PKG_CONFIG" && \ { { $as_echo "$as_me:${as_lineno-$LINENO}: \$PKG_CONFIG --exists --print-errors \"grpc grpc++ protobuf\""; } >&5 ($PKG_CONFIG --exists --print-errors "grpc grpc++ protobuf") 2>&5 ac_status=$? $as_echo "$as_me:${as_lineno-$LINENO}: \$? = $ac_status" >&5 test $ac_status = 0; }; then pkg_cv_GRPC_CFLAGS=`$PKG_CONFIG --cflags "grpc grpc++ protobuf" 2>/dev/null` test "x$?" != "x0" && pkg_failed=yes else pkg_failed=yes fi else pkg_failed=untried fi if test -n "$GRPC_LIBS"; then pkg_cv_GRPC_LIBS="$GRPC_LIBS" elif test -n "$PKG_CONFIG"; then if test -n "$PKG_CONFIG" && \ { { $as_echo "$as_me:${as_lineno-$LINENO}: \$PKG_CONFIG --exists --print-errors \"grpc grpc++ protobuf\""; } >&5 ($PKG_CONFIG --exists --print-errors "grpc grpc++ protobuf") 2>&5 ac_status=$? $as_echo "$as_me:${as_lineno-$LINENO}: \$? = $ac_status" >&5 test $ac_status = 0; }; then pkg_cv_GRPC_LIBS=`$PKG_CONFIG --libs "grpc grpc++ protobuf" 2>/dev/null` test "x$?" != "x0" && pkg_failed=yes else pkg_failed=yes fi else pkg_failed=untried fi if test $pkg_failed = yes; then { $as_echo "$as_me:${as_lineno-$LINENO}: result: no" >&5 $as_echo "no" >&6; } if $PKG_CONFIG --atleast-pkgconfig-version 0.20; then _pkg_short_errors_supported=yes else _pkg_short_errors_supported=no fi if test $_pkg_short_errors_supported = yes; then GRPC_PKG_ERRORS=`$PKG_CONFIG --short-errors --print-errors --cflags --libs "grpc grpc++ protobuf" 2>&1` else GRPC_PKG_ERRORS=`$PKG_CONFIG --print-errors --cflags --libs "grpc grpc++ protobuf" 2>&1` fi # Put the nasty error message in config.log where it belongs echo "$GRPC_PKG_ERRORS" >&5 GRPC=false as_fn_error $? "grpc/grpc++ were not found on your system." "$LINENO" 5 elif test $pkg_failed = untried; then { $as_echo "$as_me:${as_lineno-$LINENO}: result: no" >&5 $as_echo "no" >&6; } GRPC=false as_fn_error $? "grpc/grpc++ were not found on your system." "$LINENO" 5 else GRPC_CFLAGS=$pkg_cv_GRPC_CFLAGS GRPC_LIBS=$pkg_cv_GRPC_LIBS { $as_echo "$as_me:${as_lineno-$LINENO}: result: yes" >&5 $as_echo "yes" >&6; } for ac_prog in protoc do # Extract the first word of "$ac_prog", so it can be a program name with args. set dummy $ac_prog; ac_word=$2 { $as_echo "$as_me:${as_lineno-$LINENO}: checking for $ac_word" >&5 $as_echo_n "checking for $ac_word... " >&6; } if ${ac_cv_prog_PROTOC+:} false; then : $as_echo_n "(cached) " >&6 else if test -n "$PROTOC"; then ac_cv_prog_PROTOC="$PROTOC" # Let the user override the test. else as_save_IFS=$IFS; IFS=$PATH_SEPARATOR for as_dir in $PATH do IFS=$as_save_IFS test -z "$as_dir" && as_dir=. for ac_exec_ext in '' $ac_executable_extensions; do if as_fn_executable_p "$as_dir/$ac_word$ac_exec_ext"; then ac_cv_prog_PROTOC="$ac_prog" $as_echo "$as_me:${as_lineno-$LINENO}: found $as_dir/$ac_word$ac_exec_ext" >&5 break 2 fi done done IFS=$as_save_IFS fi fi PROTOC=$ac_cv_prog_PROTOC if test -n "$PROTOC"; then { $as_echo "$as_me:${as_lineno-$LINENO}: result: $PROTOC" >&5 $as_echo "$PROTOC" >&6; } else { $as_echo "$as_me:${as_lineno-$LINENO}: result: no" >&5 $as_echo "no" >&6; } fi test -n "$PROTOC" && break done test -n "$PROTOC" || PROTOC="/bin/false" if test "$PROTOC" = "/bin/false"; then { { $as_echo "$as_me:${as_lineno-$LINENO}: error: in \`$ac_pwd':" >&5 $as_echo "$as_me: error: in \`$ac_pwd':" >&2;} as_fn_error $? "grpc requested but protoc not found. See \`config.log' for more details" "$LINENO" 5; } fi $as_echo "#define HAVE_GRPC 1" >>confdefs.h GRPC=true fi fi if test "x$enable_zeromq" != "xno"; then pkg_failed=no { $as_echo "$as_me:${as_lineno-$LINENO}: checking for ZEROMQ (libzmq >= 4.0.0)" >&5 $as_echo_n "checking for ZEROMQ (libzmq >= 4.0.0)... " >&6; } if test -n "$ZEROMQ_CFLAGS"; then pkg_cv_ZEROMQ_CFLAGS="$ZEROMQ_CFLAGS" elif test -n "$PKG_CONFIG"; then if test -n "$PKG_CONFIG" && \ { { $as_echo "$as_me:${as_lineno-$LINENO}: \$PKG_CONFIG --exists --print-errors \"libzmq >= 4.0.0\""; } >&5 ($PKG_CONFIG --exists --print-errors "libzmq >= 4.0.0") 2>&5 ac_status=$? $as_echo "$as_me:${as_lineno-$LINENO}: \$? = $ac_status" >&5 test $ac_status = 0; }; then pkg_cv_ZEROMQ_CFLAGS=`$PKG_CONFIG --cflags "libzmq >= 4.0.0" 2>/dev/null` test "x$?" != "x0" && pkg_failed=yes else pkg_failed=yes fi else pkg_failed=untried fi if test -n "$ZEROMQ_LIBS"; then pkg_cv_ZEROMQ_LIBS="$ZEROMQ_LIBS" elif test -n "$PKG_CONFIG"; then if test -n "$PKG_CONFIG" && \ { { $as_echo "$as_me:${as_lineno-$LINENO}: \$PKG_CONFIG --exists --print-errors \"libzmq >= 4.0.0\""; } >&5 ($PKG_CONFIG --exists --print-errors "libzmq >= 4.0.0") 2>&5 ac_status=$? $as_echo "$as_me:${as_lineno-$LINENO}: \$? = $ac_status" >&5 test $ac_status = 0; }; then pkg_cv_ZEROMQ_LIBS=`$PKG_CONFIG --libs "libzmq >= 4.0.0" 2>/dev/null` test "x$?" != "x0" && pkg_failed=yes else pkg_failed=yes fi else pkg_failed=untried fi if test $pkg_failed = yes; then { $as_echo "$as_me:${as_lineno-$LINENO}: result: no" >&5 $as_echo "no" >&6; } if $PKG_CONFIG --atleast-pkgconfig-version 0.20; then _pkg_short_errors_supported=yes else _pkg_short_errors_supported=no fi if test $_pkg_short_errors_supported = yes; then ZEROMQ_PKG_ERRORS=`$PKG_CONFIG --short-errors --print-errors --cflags --libs "libzmq >= 4.0.0" 2>&1` else ZEROMQ_PKG_ERRORS=`$PKG_CONFIG --print-errors --cflags --libs "libzmq >= 4.0.0" 2>&1` fi # Put the nasty error message in config.log where it belongs echo "$ZEROMQ_PKG_ERRORS" >&5 if test "x$enable_zeromq" = "xyes"; then as_fn_error $? "configuration specifies --enable-zeromq but libzmq was not found" "$LINENO" 5 fi elif test $pkg_failed = untried; then { $as_echo "$as_me:${as_lineno-$LINENO}: result: no" >&5 $as_echo "no" >&6; } if test "x$enable_zeromq" = "xyes"; then as_fn_error $? "configuration specifies --enable-zeromq but libzmq was not found" "$LINENO" 5 fi else ZEROMQ_CFLAGS=$pkg_cv_ZEROMQ_CFLAGS ZEROMQ_LIBS=$pkg_cv_ZEROMQ_LIBS { $as_echo "$as_me:${as_lineno-$LINENO}: result: yes" >&5 $as_echo "yes" >&6; } $as_echo "#define HAVE_ZEROMQ 1" >>confdefs.h ZEROMQ=true fi fi if test "${enable_rpki}" = "yes"; then pkg_failed=no { $as_echo "$as_me:${as_lineno-$LINENO}: checking for RTRLIB (rtrlib >= 0.5.0)" >&5 $as_echo_n "checking for RTRLIB (rtrlib >= 0.5.0)... " >&6; } if test -n "$RTRLIB_CFLAGS"; then pkg_cv_RTRLIB_CFLAGS="$RTRLIB_CFLAGS" elif test -n "$PKG_CONFIG"; then if test -n "$PKG_CONFIG" && \ { { $as_echo "$as_me:${as_lineno-$LINENO}: \$PKG_CONFIG --exists --print-errors \"rtrlib >= 0.5.0\""; } >&5 ($PKG_CONFIG --exists --print-errors "rtrlib >= 0.5.0") 2>&5 ac_status=$? $as_echo "$as_me:${as_lineno-$LINENO}: \$? = $ac_status" >&5 test $ac_status = 0; }; then pkg_cv_RTRLIB_CFLAGS=`$PKG_CONFIG --cflags "rtrlib >= 0.5.0" 2>/dev/null` test "x$?" != "x0" && pkg_failed=yes else pkg_failed=yes fi else pkg_failed=untried fi if test -n "$RTRLIB_LIBS"; then pkg_cv_RTRLIB_LIBS="$RTRLIB_LIBS" elif test -n "$PKG_CONFIG"; then if test -n "$PKG_CONFIG" && \ { { $as_echo "$as_me:${as_lineno-$LINENO}: \$PKG_CONFIG --exists --print-errors \"rtrlib >= 0.5.0\""; } >&5 ($PKG_CONFIG --exists --print-errors "rtrlib >= 0.5.0") 2>&5 ac_status=$? $as_echo "$as_me:${as_lineno-$LINENO}: \$? = $ac_status" >&5 test $ac_status = 0; }; then pkg_cv_RTRLIB_LIBS=`$PKG_CONFIG --libs "rtrlib >= 0.5.0" 2>/dev/null` test "x$?" != "x0" && pkg_failed=yes else pkg_failed=yes fi else pkg_failed=untried fi if test $pkg_failed = yes; then { $as_echo "$as_me:${as_lineno-$LINENO}: result: no" >&5 $as_echo "no" >&6; } if $PKG_CONFIG --atleast-pkgconfig-version 0.20; then _pkg_short_errors_supported=yes else _pkg_short_errors_supported=no fi if test $_pkg_short_errors_supported = yes; then RTRLIB_PKG_ERRORS=`$PKG_CONFIG --short-errors --print-errors --cflags --libs "rtrlib >= 0.5.0" 2>&1` else RTRLIB_PKG_ERRORS=`$PKG_CONFIG --print-errors --cflags --libs "rtrlib >= 0.5.0" 2>&1` fi # Put the nasty error message in config.log where it belongs echo "$RTRLIB_PKG_ERRORS" >&5 RPKI=false as_fn_error $? "rtrlib was not found on your system or is too old." "$LINENO" 5 elif test $pkg_failed = untried; then { $as_echo "$as_me:${as_lineno-$LINENO}: result: no" >&5 $as_echo "no" >&6; } RPKI=false as_fn_error $? "rtrlib was not found on your system or is too old." "$LINENO" 5 else RTRLIB_CFLAGS=$pkg_cv_RTRLIB_CFLAGS RTRLIB_LIBS=$pkg_cv_RTRLIB_LIBS { $as_echo "$as_me:${as_lineno-$LINENO}: result: yes" >&5 $as_echo "yes" >&6; } RPKI=true fi fi { $as_echo "$as_me:${as_lineno-$LINENO}: checking whether the RTR Library is compiled with SSH" >&5 $as_echo_n "checking whether the RTR Library is compiled with SSH... " >&6; } cat confdefs.h - <<_ACEOF >conftest.$ac_ext /* end confdefs.h. */ #include "rtrlib/rtrlib.h" int main () { struct tr_ssh_config config; ; return 0; } _ACEOF if ac_fn_c_try_link "$LINENO"; then : { $as_echo "$as_me:${as_lineno-$LINENO}: result: yes" >&5 $as_echo "yes" >&6; } $as_echo "#define FOUND_SSH 1" >>confdefs.h else { $as_echo "$as_me:${as_lineno-$LINENO}: result: no" >&5 $as_echo "no" >&6; } fi rm -f core conftest.err conftest.$ac_objext \ conftest$ac_exeext conftest.$ac_ext { $as_echo "$as_me:${as_lineno-$LINENO}: checking for library containing dlopen" >&5 $as_echo_n "checking for library containing dlopen... " >&6; } if ${ac_cv_search_dlopen+:} false; then : $as_echo_n "(cached) " >&6 else ac_func_search_save_LIBS=$LIBS cat confdefs.h - <<_ACEOF >conftest.$ac_ext /* end confdefs.h. */ /* Override any GCC internal prototype to avoid an error. Use char because int might match the return type of a GCC builtin and then its argument prototype would still apply. */ #ifdef __cplusplus extern "C" #endif char dlopen (); int main () { return dlopen (); ; return 0; } _ACEOF for ac_lib in '' dl dld; do if test -z "$ac_lib"; then ac_res="none required" else ac_res=-l$ac_lib LIBS="-l$ac_lib $ac_func_search_save_LIBS" fi if ac_fn_c_try_link "$LINENO"; then : ac_cv_search_dlopen=$ac_res fi rm -f core conftest.err conftest.$ac_objext \ conftest$ac_exeext if ${ac_cv_search_dlopen+:} false; then : break fi done if ${ac_cv_search_dlopen+:} false; then : else ac_cv_search_dlopen=no fi rm conftest.$ac_ext LIBS=$ac_func_search_save_LIBS fi { $as_echo "$as_me:${as_lineno-$LINENO}: result: $ac_cv_search_dlopen" >&5 $as_echo "$ac_cv_search_dlopen" >&6; } ac_res=$ac_cv_search_dlopen if test "$ac_res" != no; then : test "$ac_res" = "none required" || LIBS="$ac_res $LIBS" else as_fn_error $? "unable to find the dlopen()" "$LINENO" 5 fi for ac_header in link.h do : ac_fn_c_check_header_mongrel "$LINENO" "link.h" "ac_cv_header_link_h" "$ac_includes_default" if test "x$ac_cv_header_link_h" = xyes; then : cat >>confdefs.h <<_ACEOF #define HAVE_LINK_H 1 _ACEOF fi done { $as_echo "$as_me:${as_lineno-$LINENO}: checking for dlinfo(RTLD_DI_ORIGIN)" >&5 $as_echo_n "checking for dlinfo(RTLD_DI_ORIGIN)... " >&6; } if ${frr_cv_rtld_di_origin+:} false; then : $as_echo_n "(cached) " >&6 else cat confdefs.h - <<_ACEOF >conftest.$ac_ext /* end confdefs.h. */ #include #ifdef HAVE_LINK_H #include #endif #include int main () { char origin[1]; dlinfo (NULL, RTLD_DI_ORIGIN, &origin); ; return 0; } _ACEOF if ac_fn_c_try_link "$LINENO"; then : frr_cv_rtld_di_origin=yes else frr_cv_rtld_di_origin=no fi rm -f core conftest.err conftest.$ac_objext \ conftest$ac_exeext conftest.$ac_ext fi { $as_echo "$as_me:${as_lineno-$LINENO}: result: $frr_cv_rtld_di_origin" >&5 $as_echo "$frr_cv_rtld_di_origin" >&6; } if test "$frr_cv_rtld_di_origin" = yes; then $as_echo "#define HAVE_DLINFO_ORIGIN 1" >>confdefs.h fi { $as_echo "$as_me:${as_lineno-$LINENO}: checking for dlinfo(RTLD_DI_LINKMAP)" >&5 $as_echo_n "checking for dlinfo(RTLD_DI_LINKMAP)... " >&6; } if ${frr_cv_rtld_di_linkmap+:} false; then : $as_echo_n "(cached) " >&6 else cat confdefs.h - <<_ACEOF >conftest.$ac_ext /* end confdefs.h. */ #include #ifdef HAVE_LINK_H #include #endif #include int main () { struct link_map *lm = NULL; dlinfo (NULL, RTLD_DI_LINKMAP, &lm); ; return 0; } _ACEOF if ac_fn_c_try_link "$LINENO"; then : frr_cv_rtld_di_linkmap=yes else frr_cv_rtld_di_linkmap=no fi rm -f core conftest.err conftest.$ac_objext \ conftest$ac_exeext conftest.$ac_ext fi { $as_echo "$as_me:${as_lineno-$LINENO}: result: $frr_cv_rtld_di_linkmap" >&5 $as_echo "$frr_cv_rtld_di_linkmap" >&6; } if test "$frr_cv_rtld_di_linkmap" = yes; then $as_echo "#define HAVE_DLINFO_LINKMAP 1" >>confdefs.h fi fi ac_fn_c_check_type "$LINENO" "struct sockaddr_dl" "ac_cv_type_struct_sockaddr_dl" "#ifdef SUNOS_5 #define _POSIX_C_SOURCE 200809L #define __EXTENSIONS__ #endif #include #include #include #include /* sys/conf.h depends on param.h on FBSD at least */ #include /* Required for MAXSIG */ #include #include #ifdef __APPLE__ # define __APPLE_USE_RFC_3542 #endif #include #include #include #include #if HAVE_NET_IF_VAR_H # include #endif #include #include #if HAVE_NETINET_IN_VAR_H # include #endif #if HAVE_NET_IF_DL_H # include #endif #if HAVE_NET_NETOPT_H # include #endif #include #if HAVE_INET_ND_H # include #endif #include /* Required for IDRP */ #if HAVE_NETINET_IP_ICMP_H # include #endif #if HAVE_LINUX_MROUTE_H # include #endif #if HAVE_NETINET_IP_MROUTE_H # include #endif #if HAVE_NETINET6_IN6_H #include #endif #if HAVE_NETINET_IN6_VAR_H #include #endif #include #if HAVE_NETINET6_IN6_VAR_H # include #endif #if HAVE_NETINET6_ND6_H # include #endif " if test "x$ac_cv_type_struct_sockaddr_dl" = xyes; then : cat >>confdefs.h <<_ACEOF #define HAVE_STRUCT_SOCKADDR_DL 1 _ACEOF fi ac_fn_c_check_type "$LINENO" "struct vifctl" "ac_cv_type_struct_vifctl" "#ifdef SUNOS_5 #define _POSIX_C_SOURCE 200809L #define __EXTENSIONS__ #endif #include #include #include #include /* sys/conf.h depends on param.h on FBSD at least */ #include /* Required for MAXSIG */ #include #include #ifdef __APPLE__ # define __APPLE_USE_RFC_3542 #endif #include #include #include #include #if HAVE_NET_IF_VAR_H # include #endif #include #include #if HAVE_NETINET_IN_VAR_H # include #endif #if HAVE_NET_IF_DL_H # include #endif #if HAVE_NET_NETOPT_H # include #endif #include #if HAVE_INET_ND_H # include #endif #include /* Required for IDRP */ #if HAVE_NETINET_IP_ICMP_H # include #endif #if HAVE_LINUX_MROUTE_H # include #endif #if HAVE_NETINET_IP_MROUTE_H # include #endif #if HAVE_NETINET6_IN6_H #include #endif #if HAVE_NETINET_IN6_VAR_H #include #endif #include #if HAVE_NETINET6_IN6_VAR_H # include #endif #if HAVE_NETINET6_ND6_H # include #endif " if test "x$ac_cv_type_struct_vifctl" = xyes; then : cat >>confdefs.h <<_ACEOF #define HAVE_STRUCT_VIFCTL 1 _ACEOF fi ac_fn_c_check_type "$LINENO" "struct mfcctl" "ac_cv_type_struct_mfcctl" "#ifdef SUNOS_5 #define _POSIX_C_SOURCE 200809L #define __EXTENSIONS__ #endif #include #include #include #include /* sys/conf.h depends on param.h on FBSD at least */ #include /* Required for MAXSIG */ #include #include #ifdef __APPLE__ # define __APPLE_USE_RFC_3542 #endif #include #include #include #include #if HAVE_NET_IF_VAR_H # include #endif #include #include #if HAVE_NETINET_IN_VAR_H # include #endif #if HAVE_NET_IF_DL_H # include #endif #if HAVE_NET_NETOPT_H # include #endif #include #if HAVE_INET_ND_H # include #endif #include /* Required for IDRP */ #if HAVE_NETINET_IP_ICMP_H # include #endif #if HAVE_LINUX_MROUTE_H # include #endif #if HAVE_NETINET_IP_MROUTE_H # include #endif #if HAVE_NETINET6_IN6_H #include #endif #if HAVE_NETINET_IN6_VAR_H #include #endif #include #if HAVE_NETINET6_IN6_VAR_H # include #endif #if HAVE_NETINET6_ND6_H # include #endif " if test "x$ac_cv_type_struct_mfcctl" = xyes; then : cat >>confdefs.h <<_ACEOF #define HAVE_STRUCT_MFCCTL 1 _ACEOF fi ac_fn_c_check_type "$LINENO" "struct sioc_sg_req" "ac_cv_type_struct_sioc_sg_req" "#ifdef SUNOS_5 #define _POSIX_C_SOURCE 200809L #define __EXTENSIONS__ #endif #include #include #include #include /* sys/conf.h depends on param.h on FBSD at least */ #include /* Required for MAXSIG */ #include #include #ifdef __APPLE__ # define __APPLE_USE_RFC_3542 #endif #include #include #include #include #if HAVE_NET_IF_VAR_H # include #endif #include #include #if HAVE_NETINET_IN_VAR_H # include #endif #if HAVE_NET_IF_DL_H # include #endif #if HAVE_NET_NETOPT_H # include #endif #include #if HAVE_INET_ND_H # include #endif #include /* Required for IDRP */ #if HAVE_NETINET_IP_ICMP_H # include #endif #if HAVE_LINUX_MROUTE_H # include #endif #if HAVE_NETINET_IP_MROUTE_H # include #endif #if HAVE_NETINET6_IN6_H #include #endif #if HAVE_NETINET_IN6_VAR_H #include #endif #include #if HAVE_NETINET6_IN6_VAR_H # include #endif #if HAVE_NETINET6_ND6_H # include #endif " if test "x$ac_cv_type_struct_sioc_sg_req" = xyes; then : cat >>confdefs.h <<_ACEOF #define HAVE_STRUCT_SIOC_SG_REQ 1 _ACEOF fi ac_fn_c_check_type "$LINENO" "vifi_t" "ac_cv_type_vifi_t" "#ifdef SUNOS_5 #define _POSIX_C_SOURCE 200809L #define __EXTENSIONS__ #endif #include #include #include #include /* sys/conf.h depends on param.h on FBSD at least */ #include /* Required for MAXSIG */ #include #include #ifdef __APPLE__ # define __APPLE_USE_RFC_3542 #endif #include #include #include #include #if HAVE_NET_IF_VAR_H # include #endif #include #include #if HAVE_NETINET_IN_VAR_H # include #endif #if HAVE_NET_IF_DL_H # include #endif #if HAVE_NET_NETOPT_H # include #endif #include #if HAVE_INET_ND_H # include #endif #include /* Required for IDRP */ #if HAVE_NETINET_IP_ICMP_H # include #endif #if HAVE_LINUX_MROUTE_H # include #endif #if HAVE_NETINET_IP_MROUTE_H # include #endif #if HAVE_NETINET6_IN6_H #include #endif #if HAVE_NETINET_IN6_VAR_H #include #endif #include #if HAVE_NETINET6_IN6_VAR_H # include #endif #if HAVE_NETINET6_ND6_H # include #endif " if test "x$ac_cv_type_vifi_t" = xyes; then : cat >>confdefs.h <<_ACEOF #define HAVE_VIFI_T 1 _ACEOF fi ac_fn_c_check_type "$LINENO" "struct sioc_vif_req" "ac_cv_type_struct_sioc_vif_req" "#ifdef SUNOS_5 #define _POSIX_C_SOURCE 200809L #define __EXTENSIONS__ #endif #include #include #include #include /* sys/conf.h depends on param.h on FBSD at least */ #include /* Required for MAXSIG */ #include #include #ifdef __APPLE__ # define __APPLE_USE_RFC_3542 #endif #include #include #include #include #if HAVE_NET_IF_VAR_H # include #endif #include #include #if HAVE_NETINET_IN_VAR_H # include #endif #if HAVE_NET_IF_DL_H # include #endif #if HAVE_NET_NETOPT_H # include #endif #include #if HAVE_INET_ND_H # include #endif #include /* Required for IDRP */ #if HAVE_NETINET_IP_ICMP_H # include #endif #if HAVE_LINUX_MROUTE_H # include #endif #if HAVE_NETINET_IP_MROUTE_H # include #endif #if HAVE_NETINET6_IN6_H #include #endif #if HAVE_NETINET_IN6_VAR_H #include #endif #include #if HAVE_NETINET6_IN6_VAR_H # include #endif #if HAVE_NETINET6_ND6_H # include #endif " if test "x$ac_cv_type_struct_sioc_vif_req" = xyes; then : cat >>confdefs.h <<_ACEOF #define HAVE_STRUCT_SIOC_VIF_REQ 1 _ACEOF fi ac_fn_c_check_type "$LINENO" "struct igmpmsg" "ac_cv_type_struct_igmpmsg" "#ifdef SUNOS_5 #define _POSIX_C_SOURCE 200809L #define __EXTENSIONS__ #endif #include #include #include #include /* sys/conf.h depends on param.h on FBSD at least */ #include /* Required for MAXSIG */ #include #include #ifdef __APPLE__ # define __APPLE_USE_RFC_3542 #endif #include #include #include #include #if HAVE_NET_IF_VAR_H # include #endif #include #include #if HAVE_NETINET_IN_VAR_H # include #endif #if HAVE_NET_IF_DL_H # include #endif #if HAVE_NET_NETOPT_H # include #endif #include #if HAVE_INET_ND_H # include #endif #include /* Required for IDRP */ #if HAVE_NETINET_IP_ICMP_H # include #endif #if HAVE_LINUX_MROUTE_H # include #endif #if HAVE_NETINET_IP_MROUTE_H # include #endif #if HAVE_NETINET6_IN6_H #include #endif #if HAVE_NETINET_IN6_VAR_H #include #endif #include #if HAVE_NETINET6_IN6_VAR_H # include #endif #if HAVE_NETINET6_ND6_H # include #endif " if test "x$ac_cv_type_struct_igmpmsg" = xyes; then : cat >>confdefs.h <<_ACEOF #define HAVE_STRUCT_IGMPMSG 1 _ACEOF fi ac_fn_c_check_type "$LINENO" "struct ifaliasreq" "ac_cv_type_struct_ifaliasreq" "#ifdef SUNOS_5 #define _POSIX_C_SOURCE 200809L #define __EXTENSIONS__ #endif #include #include #include #include /* sys/conf.h depends on param.h on FBSD at least */ #include /* Required for MAXSIG */ #include #include #ifdef __APPLE__ # define __APPLE_USE_RFC_3542 #endif #include #include #include #include #if HAVE_NET_IF_VAR_H # include #endif #include #include #if HAVE_NETINET_IN_VAR_H # include #endif #if HAVE_NET_IF_DL_H # include #endif #if HAVE_NET_NETOPT_H # include #endif #include #if HAVE_INET_ND_H # include #endif #include /* Required for IDRP */ #if HAVE_NETINET_IP_ICMP_H # include #endif #if HAVE_LINUX_MROUTE_H # include #endif #if HAVE_NETINET_IP_MROUTE_H # include #endif #if HAVE_NETINET6_IN6_H #include #endif #if HAVE_NETINET_IN6_VAR_H #include #endif #include #if HAVE_NETINET6_IN6_VAR_H # include #endif #if HAVE_NETINET6_ND6_H # include #endif " if test "x$ac_cv_type_struct_ifaliasreq" = xyes; then : cat >>confdefs.h <<_ACEOF #define HAVE_STRUCT_IFALIASREQ 1 _ACEOF fi ac_fn_c_check_type "$LINENO" "struct if6_aliasreq" "ac_cv_type_struct_if6_aliasreq" "#ifdef SUNOS_5 #define _POSIX_C_SOURCE 200809L #define __EXTENSIONS__ #endif #include #include #include #include /* sys/conf.h depends on param.h on FBSD at least */ #include /* Required for MAXSIG */ #include #include #ifdef __APPLE__ # define __APPLE_USE_RFC_3542 #endif #include #include #include #include #if HAVE_NET_IF_VAR_H # include #endif #include #include #if HAVE_NETINET_IN_VAR_H # include #endif #if HAVE_NET_IF_DL_H # include #endif #if HAVE_NET_NETOPT_H # include #endif #include #if HAVE_INET_ND_H # include #endif #include /* Required for IDRP */ #if HAVE_NETINET_IP_ICMP_H # include #endif #if HAVE_LINUX_MROUTE_H # include #endif #if HAVE_NETINET_IP_MROUTE_H # include #endif #if HAVE_NETINET6_IN6_H #include #endif #if HAVE_NETINET_IN6_VAR_H #include #endif #include #if HAVE_NETINET6_IN6_VAR_H # include #endif #if HAVE_NETINET6_ND6_H # include #endif " if test "x$ac_cv_type_struct_if6_aliasreq" = xyes; then : cat >>confdefs.h <<_ACEOF #define HAVE_STRUCT_IF6_ALIASREQ 1 _ACEOF fi ac_fn_c_check_type "$LINENO" "struct in6_aliasreq" "ac_cv_type_struct_in6_aliasreq" "#ifdef SUNOS_5 #define _POSIX_C_SOURCE 200809L #define __EXTENSIONS__ #endif #include #include #include #include /* sys/conf.h depends on param.h on FBSD at least */ #include /* Required for MAXSIG */ #include #include #ifdef __APPLE__ # define __APPLE_USE_RFC_3542 #endif #include #include #include #include #if HAVE_NET_IF_VAR_H # include #endif #include #include #if HAVE_NETINET_IN_VAR_H # include #endif #if HAVE_NET_IF_DL_H # include #endif #if HAVE_NET_NETOPT_H # include #endif #include #if HAVE_INET_ND_H # include #endif #include /* Required for IDRP */ #if HAVE_NETINET_IP_ICMP_H # include #endif #if HAVE_LINUX_MROUTE_H # include #endif #if HAVE_NETINET_IP_MROUTE_H # include #endif #if HAVE_NETINET6_IN6_H #include #endif #if HAVE_NETINET_IN6_VAR_H #include #endif #include #if HAVE_NETINET6_IN6_VAR_H # include #endif #if HAVE_NETINET6_ND6_H # include #endif " if test "x$ac_cv_type_struct_in6_aliasreq" = xyes; then : cat >>confdefs.h <<_ACEOF #define HAVE_STRUCT_IN6_ALIASREQ 1 _ACEOF fi ac_fn_c_check_type "$LINENO" "struct nd_opt_adv_interval" "ac_cv_type_struct_nd_opt_adv_interval" "#ifdef SUNOS_5 #define _POSIX_C_SOURCE 200809L #define __EXTENSIONS__ #endif #include #include #include #include /* sys/conf.h depends on param.h on FBSD at least */ #include /* Required for MAXSIG */ #include #include #ifdef __APPLE__ # define __APPLE_USE_RFC_3542 #endif #include #include #include #include #if HAVE_NET_IF_VAR_H # include #endif #include #include #if HAVE_NETINET_IN_VAR_H # include #endif #if HAVE_NET_IF_DL_H # include #endif #if HAVE_NET_NETOPT_H # include #endif #include #if HAVE_INET_ND_H # include #endif #include /* Required for IDRP */ #if HAVE_NETINET_IP_ICMP_H # include #endif #if HAVE_LINUX_MROUTE_H # include #endif #if HAVE_NETINET_IP_MROUTE_H # include #endif #if HAVE_NETINET6_IN6_H #include #endif #if HAVE_NETINET_IN6_VAR_H #include #endif #include #if HAVE_NETINET6_IN6_VAR_H # include #endif #if HAVE_NETINET6_ND6_H # include #endif " if test "x$ac_cv_type_struct_nd_opt_adv_interval" = xyes; then : cat >>confdefs.h <<_ACEOF #define HAVE_STRUCT_ND_OPT_ADV_INTERVAL 1 _ACEOF fi ac_fn_c_check_type "$LINENO" "struct nd_opt_homeagent_info" "ac_cv_type_struct_nd_opt_homeagent_info" "#ifdef SUNOS_5 #define _POSIX_C_SOURCE 200809L #define __EXTENSIONS__ #endif #include #include #include #include /* sys/conf.h depends on param.h on FBSD at least */ #include /* Required for MAXSIG */ #include #include #ifdef __APPLE__ # define __APPLE_USE_RFC_3542 #endif #include #include #include #include #if HAVE_NET_IF_VAR_H # include #endif #include #include #if HAVE_NETINET_IN_VAR_H # include #endif #if HAVE_NET_IF_DL_H # include #endif #if HAVE_NET_NETOPT_H # include #endif #include #if HAVE_INET_ND_H # include #endif #include /* Required for IDRP */ #if HAVE_NETINET_IP_ICMP_H # include #endif #if HAVE_LINUX_MROUTE_H # include #endif #if HAVE_NETINET_IP_MROUTE_H # include #endif #if HAVE_NETINET6_IN6_H #include #endif #if HAVE_NETINET_IN6_VAR_H #include #endif #include #if HAVE_NETINET6_IN6_VAR_H # include #endif #if HAVE_NETINET6_ND6_H # include #endif " if test "x$ac_cv_type_struct_nd_opt_homeagent_info" = xyes; then : cat >>confdefs.h <<_ACEOF #define HAVE_STRUCT_ND_OPT_HOMEAGENT_INFO 1 _ACEOF fi ac_fn_c_check_type "$LINENO" "struct nd_opt_adv_interval" "ac_cv_type_struct_nd_opt_adv_interval" "#ifdef SUNOS_5 #define _POSIX_C_SOURCE 200809L #define __EXTENSIONS__ #endif #include #include #include #include /* sys/conf.h depends on param.h on FBSD at least */ #include /* Required for MAXSIG */ #include #include #ifdef __APPLE__ # define __APPLE_USE_RFC_3542 #endif #include #include #include #include #if HAVE_NET_IF_VAR_H # include #endif #include #include #if HAVE_NETINET_IN_VAR_H # include #endif #if HAVE_NET_IF_DL_H # include #endif #if HAVE_NET_NETOPT_H # include #endif #include #if HAVE_INET_ND_H # include #endif #include /* Required for IDRP */ #if HAVE_NETINET_IP_ICMP_H # include #endif #if HAVE_LINUX_MROUTE_H # include #endif #if HAVE_NETINET_IP_MROUTE_H # include #endif #if HAVE_NETINET6_IN6_H #include #endif #if HAVE_NETINET_IN6_VAR_H #include #endif #include #if HAVE_NETINET6_IN6_VAR_H # include #endif #if HAVE_NETINET6_ND6_H # include #endif " if test "x$ac_cv_type_struct_nd_opt_adv_interval" = xyes; then : cat >>confdefs.h <<_ACEOF #define HAVE_STRUCT_ND_OPT_ADV_INTERVAL 1 _ACEOF fi ac_fn_c_check_type "$LINENO" "struct nd_opt_rdnss" "ac_cv_type_struct_nd_opt_rdnss" "#ifdef SUNOS_5 #define _POSIX_C_SOURCE 200809L #define __EXTENSIONS__ #endif #include #include #include #include /* sys/conf.h depends on param.h on FBSD at least */ #include /* Required for MAXSIG */ #include #include #ifdef __APPLE__ # define __APPLE_USE_RFC_3542 #endif #include #include #include #include #if HAVE_NET_IF_VAR_H # include #endif #include #include #if HAVE_NETINET_IN_VAR_H # include #endif #if HAVE_NET_IF_DL_H # include #endif #if HAVE_NET_NETOPT_H # include #endif #include #if HAVE_INET_ND_H # include #endif #include /* Required for IDRP */ #if HAVE_NETINET_IP_ICMP_H # include #endif #if HAVE_LINUX_MROUTE_H # include #endif #if HAVE_NETINET_IP_MROUTE_H # include #endif #if HAVE_NETINET6_IN6_H #include #endif #if HAVE_NETINET_IN6_VAR_H #include #endif #include #if HAVE_NETINET6_IN6_VAR_H # include #endif #if HAVE_NETINET6_ND6_H # include #endif " if test "x$ac_cv_type_struct_nd_opt_rdnss" = xyes; then : cat >>confdefs.h <<_ACEOF #define HAVE_STRUCT_ND_OPT_RDNSS 1 _ACEOF fi ac_fn_c_check_type "$LINENO" "struct nd_opt_dnssl" "ac_cv_type_struct_nd_opt_dnssl" "#ifdef SUNOS_5 #define _POSIX_C_SOURCE 200809L #define __EXTENSIONS__ #endif #include #include #include #include /* sys/conf.h depends on param.h on FBSD at least */ #include /* Required for MAXSIG */ #include #include #ifdef __APPLE__ # define __APPLE_USE_RFC_3542 #endif #include #include #include #include #if HAVE_NET_IF_VAR_H # include #endif #include #include #if HAVE_NETINET_IN_VAR_H # include #endif #if HAVE_NET_IF_DL_H # include #endif #if HAVE_NET_NETOPT_H # include #endif #include #if HAVE_INET_ND_H # include #endif #include /* Required for IDRP */ #if HAVE_NETINET_IP_ICMP_H # include #endif #if HAVE_LINUX_MROUTE_H # include #endif #if HAVE_NETINET_IP_MROUTE_H # include #endif #if HAVE_NETINET6_IN6_H #include #endif #if HAVE_NETINET_IN6_VAR_H #include #endif #include #if HAVE_NETINET6_IN6_VAR_H # include #endif #if HAVE_NETINET6_ND6_H # include #endif " if test "x$ac_cv_type_struct_nd_opt_dnssl" = xyes; then : cat >>confdefs.h <<_ACEOF #define HAVE_STRUCT_ND_OPT_DNSSL 1 _ACEOF fi ac_fn_c_check_member "$LINENO" "struct sockaddr" "sa_len" "ac_cv_member_struct_sockaddr_sa_len" "#ifdef SUNOS_5 #define _POSIX_C_SOURCE 200809L #define __EXTENSIONS__ #endif #include #include #include #include /* sys/conf.h depends on param.h on FBSD at least */ #include /* Required for MAXSIG */ #include #include #ifdef __APPLE__ # define __APPLE_USE_RFC_3542 #endif #include #include #include #include #if HAVE_NET_IF_VAR_H # include #endif #include #include #if HAVE_NETINET_IN_VAR_H # include #endif #if HAVE_NET_IF_DL_H # include #endif #if HAVE_NET_NETOPT_H # include #endif #include #if HAVE_INET_ND_H # include #endif #include /* Required for IDRP */ #if HAVE_NETINET_IP_ICMP_H # include #endif #if HAVE_LINUX_MROUTE_H # include #endif #if HAVE_NETINET_IP_MROUTE_H # include #endif #if HAVE_NETINET6_IN6_H #include #endif #if HAVE_NETINET_IN6_VAR_H #include #endif #include #if HAVE_NETINET6_IN6_VAR_H # include #endif #if HAVE_NETINET6_ND6_H # include #endif " if test "x$ac_cv_member_struct_sockaddr_sa_len" = xyes; then : cat >>confdefs.h <<_ACEOF #define HAVE_STRUCT_SOCKADDR_SA_LEN 1 _ACEOF fi ac_fn_c_check_member "$LINENO" "struct sockaddr_in" "sin_len" "ac_cv_member_struct_sockaddr_in_sin_len" "#ifdef SUNOS_5 #define _POSIX_C_SOURCE 200809L #define __EXTENSIONS__ #endif #include #include #include #include /* sys/conf.h depends on param.h on FBSD at least */ #include /* Required for MAXSIG */ #include #include #ifdef __APPLE__ # define __APPLE_USE_RFC_3542 #endif #include #include #include #include #if HAVE_NET_IF_VAR_H # include #endif #include #include #if HAVE_NETINET_IN_VAR_H # include #endif #if HAVE_NET_IF_DL_H # include #endif #if HAVE_NET_NETOPT_H # include #endif #include #if HAVE_INET_ND_H # include #endif #include /* Required for IDRP */ #if HAVE_NETINET_IP_ICMP_H # include #endif #if HAVE_LINUX_MROUTE_H # include #endif #if HAVE_NETINET_IP_MROUTE_H # include #endif #if HAVE_NETINET6_IN6_H #include #endif #if HAVE_NETINET_IN6_VAR_H #include #endif #include #if HAVE_NETINET6_IN6_VAR_H # include #endif #if HAVE_NETINET6_ND6_H # include #endif " if test "x$ac_cv_member_struct_sockaddr_in_sin_len" = xyes; then : cat >>confdefs.h <<_ACEOF #define HAVE_STRUCT_SOCKADDR_IN_SIN_LEN 1 _ACEOF fi ac_fn_c_check_member "$LINENO" "struct sockaddr_un" "sun_len" "ac_cv_member_struct_sockaddr_un_sun_len" "#ifdef SUNOS_5 #define _POSIX_C_SOURCE 200809L #define __EXTENSIONS__ #endif #include #include #include #include /* sys/conf.h depends on param.h on FBSD at least */ #include /* Required for MAXSIG */ #include #include #ifdef __APPLE__ # define __APPLE_USE_RFC_3542 #endif #include #include #include #include #if HAVE_NET_IF_VAR_H # include #endif #include #include #if HAVE_NETINET_IN_VAR_H # include #endif #if HAVE_NET_IF_DL_H # include #endif #if HAVE_NET_NETOPT_H # include #endif #include #if HAVE_INET_ND_H # include #endif #include /* Required for IDRP */ #if HAVE_NETINET_IP_ICMP_H # include #endif #if HAVE_LINUX_MROUTE_H # include #endif #if HAVE_NETINET_IP_MROUTE_H # include #endif #if HAVE_NETINET6_IN6_H #include #endif #if HAVE_NETINET_IN6_VAR_H #include #endif #include #if HAVE_NETINET6_IN6_VAR_H # include #endif #if HAVE_NETINET6_ND6_H # include #endif " if test "x$ac_cv_member_struct_sockaddr_un_sun_len" = xyes; then : cat >>confdefs.h <<_ACEOF #define HAVE_STRUCT_SOCKADDR_UN_SUN_LEN 1 _ACEOF fi ac_fn_c_check_member "$LINENO" "struct sockaddr_dl" "sdl_len" "ac_cv_member_struct_sockaddr_dl_sdl_len" "#ifdef SUNOS_5 #define _POSIX_C_SOURCE 200809L #define __EXTENSIONS__ #endif #include #include #include #include /* sys/conf.h depends on param.h on FBSD at least */ #include /* Required for MAXSIG */ #include #include #ifdef __APPLE__ # define __APPLE_USE_RFC_3542 #endif #include #include #include #include #if HAVE_NET_IF_VAR_H # include #endif #include #include #if HAVE_NETINET_IN_VAR_H # include #endif #if HAVE_NET_IF_DL_H # include #endif #if HAVE_NET_NETOPT_H # include #endif #include #if HAVE_INET_ND_H # include #endif #include /* Required for IDRP */ #if HAVE_NETINET_IP_ICMP_H # include #endif #if HAVE_LINUX_MROUTE_H # include #endif #if HAVE_NETINET_IP_MROUTE_H # include #endif #if HAVE_NETINET6_IN6_H #include #endif #if HAVE_NETINET_IN6_VAR_H #include #endif #include #if HAVE_NETINET6_IN6_VAR_H # include #endif #if HAVE_NETINET6_ND6_H # include #endif " if test "x$ac_cv_member_struct_sockaddr_dl_sdl_len" = xyes; then : cat >>confdefs.h <<_ACEOF #define HAVE_STRUCT_SOCKADDR_DL_SDL_LEN 1 _ACEOF fi ac_fn_c_check_member "$LINENO" "struct if6_aliasreq" "ifra_lifetime" "ac_cv_member_struct_if6_aliasreq_ifra_lifetime" "#ifdef SUNOS_5 #define _POSIX_C_SOURCE 200809L #define __EXTENSIONS__ #endif #include #include #include #include /* sys/conf.h depends on param.h on FBSD at least */ #include /* Required for MAXSIG */ #include #include #ifdef __APPLE__ # define __APPLE_USE_RFC_3542 #endif #include #include #include #include #if HAVE_NET_IF_VAR_H # include #endif #include #include #if HAVE_NETINET_IN_VAR_H # include #endif #if HAVE_NET_IF_DL_H # include #endif #if HAVE_NET_NETOPT_H # include #endif #include #if HAVE_INET_ND_H # include #endif #include /* Required for IDRP */ #if HAVE_NETINET_IP_ICMP_H # include #endif #if HAVE_LINUX_MROUTE_H # include #endif #if HAVE_NETINET_IP_MROUTE_H # include #endif #if HAVE_NETINET6_IN6_H #include #endif #if HAVE_NETINET_IN6_VAR_H #include #endif #include #if HAVE_NETINET6_IN6_VAR_H # include #endif #if HAVE_NETINET6_ND6_H # include #endif " if test "x$ac_cv_member_struct_if6_aliasreq_ifra_lifetime" = xyes; then : cat >>confdefs.h <<_ACEOF #define HAVE_STRUCT_IF6_ALIASREQ_IFRA_LIFETIME 1 _ACEOF fi ac_fn_c_check_member "$LINENO" "struct nd_opt_adv_interval" "nd_opt_ai_type" "ac_cv_member_struct_nd_opt_adv_interval_nd_opt_ai_type" "#ifdef SUNOS_5 #define _POSIX_C_SOURCE 200809L #define __EXTENSIONS__ #endif #include #include #include #include /* sys/conf.h depends on param.h on FBSD at least */ #include /* Required for MAXSIG */ #include #include #ifdef __APPLE__ # define __APPLE_USE_RFC_3542 #endif #include #include #include #include #if HAVE_NET_IF_VAR_H # include #endif #include #include #if HAVE_NETINET_IN_VAR_H # include #endif #if HAVE_NET_IF_DL_H # include #endif #if HAVE_NET_NETOPT_H # include #endif #include #if HAVE_INET_ND_H # include #endif #include /* Required for IDRP */ #if HAVE_NETINET_IP_ICMP_H # include #endif #if HAVE_LINUX_MROUTE_H # include #endif #if HAVE_NETINET_IP_MROUTE_H # include #endif #if HAVE_NETINET6_IN6_H #include #endif #if HAVE_NETINET_IN6_VAR_H #include #endif #include #if HAVE_NETINET6_IN6_VAR_H # include #endif #if HAVE_NETINET6_ND6_H # include #endif " if test "x$ac_cv_member_struct_nd_opt_adv_interval_nd_opt_ai_type" = xyes; then : cat >>confdefs.h <<_ACEOF #define HAVE_STRUCT_ND_OPT_ADV_INTERVAL_ND_OPT_AI_TYPE 1 _ACEOF fi ac_fn_c_check_type "$LINENO" "struct in_pktinfo" "ac_cv_type_struct_in_pktinfo" "#ifdef SUNOS_5 #define _POSIX_C_SOURCE 200809L #define __EXTENSIONS__ #endif #include #include #include #include /* sys/conf.h depends on param.h on FBSD at least */ #include /* Required for MAXSIG */ #include #include #ifdef __APPLE__ # define __APPLE_USE_RFC_3542 #endif #include #include #include #include #if HAVE_NET_IF_VAR_H # include #endif #include #include #if HAVE_NETINET_IN_VAR_H # include #endif #if HAVE_NET_IF_DL_H # include #endif #if HAVE_NET_NETOPT_H # include #endif #include #if HAVE_INET_ND_H # include #endif #include /* Required for IDRP */ #if HAVE_NETINET_IP_ICMP_H # include #endif #if HAVE_LINUX_MROUTE_H # include #endif #if HAVE_NETINET_IP_MROUTE_H # include #endif #if HAVE_NETINET6_IN6_H #include #endif #if HAVE_NETINET_IN6_VAR_H #include #endif #include #if HAVE_NETINET6_IN6_VAR_H # include #endif #if HAVE_NETINET6_ND6_H # include #endif " if test "x$ac_cv_type_struct_in_pktinfo" = xyes; then : cat >>confdefs.h <<_ACEOF #define HAVE_STRUCT_IN_PKTINFO 1 _ACEOF ac_fn_c_check_type "$LINENO" "struct icmphdr" "ac_cv_type_struct_icmphdr" "#ifdef SUNOS_5 #define _POSIX_C_SOURCE 200809L #define __EXTENSIONS__ #endif #include #include #include #include /* sys/conf.h depends on param.h on FBSD at least */ #include /* Required for MAXSIG */ #include #include #ifdef __APPLE__ # define __APPLE_USE_RFC_3542 #endif #include #include #include #include #if HAVE_NET_IF_VAR_H # include #endif #include #include #if HAVE_NETINET_IN_VAR_H # include #endif #if HAVE_NET_IF_DL_H # include #endif #if HAVE_NET_NETOPT_H # include #endif #include #if HAVE_INET_ND_H # include #endif #include /* Required for IDRP */ #if HAVE_NETINET_IP_ICMP_H # include #endif #if HAVE_LINUX_MROUTE_H # include #endif #if HAVE_NETINET_IP_MROUTE_H # include #endif #if HAVE_NETINET6_IN6_H #include #endif #if HAVE_NETINET_IN6_VAR_H #include #endif #include #if HAVE_NETINET6_IN6_VAR_H # include #endif #if HAVE_NETINET6_ND6_H # include #endif " if test "x$ac_cv_type_struct_icmphdr" = xyes; then : cat >>confdefs.h <<_ACEOF #define HAVE_STRUCT_ICMPHDR 1 _ACEOF IRDP=true else IRDP=false fi else IRDP=false fi case "${enable_irdp}" in yes) $IRDP || as_fn_error $? "'IRDP requires in_pktinfo at the moment!'" "$LINENO" 5 ;; no) IRDP=false ;; esac { $as_echo "$as_me:${as_lineno-$LINENO}: checking for IP_PKTINFO" >&5 $as_echo_n "checking for IP_PKTINFO... " >&6; } cat confdefs.h - <<_ACEOF >conftest.$ac_ext /* end confdefs.h. */ #include int main () { int opt = IP_PKTINFO; ; return 0; } _ACEOF if ac_fn_c_try_compile "$LINENO"; then : { $as_echo "$as_me:${as_lineno-$LINENO}: result: yes" >&5 $as_echo "yes" >&6; } $as_echo "#define HAVE_IP_PKTINFO 1" >>confdefs.h else { $as_echo "$as_me:${as_lineno-$LINENO}: result: no" >&5 $as_echo "no" >&6; } fi rm -f core conftest.err conftest.$ac_objext conftest.$ac_ext { $as_echo "$as_me:${as_lineno-$LINENO}: checking for IP_RECVDSTADDR" >&5 $as_echo_n "checking for IP_RECVDSTADDR... " >&6; } cat confdefs.h - <<_ACEOF >conftest.$ac_ext /* end confdefs.h. */ #include int main () { int opt = IP_RECVDSTADDR; ; return 0; } _ACEOF if ac_fn_c_try_compile "$LINENO"; then : { $as_echo "$as_me:${as_lineno-$LINENO}: result: yes" >&5 $as_echo "yes" >&6; } $as_echo "#define HAVE_IP_RECVDSTADDR 1" >>confdefs.h else { $as_echo "$as_me:${as_lineno-$LINENO}: result: no" >&5 $as_echo "no" >&6; } fi rm -f core conftest.err conftest.$ac_objext conftest.$ac_ext { $as_echo "$as_me:${as_lineno-$LINENO}: checking for IP_RECVIF" >&5 $as_echo_n "checking for IP_RECVIF... " >&6; } cat confdefs.h - <<_ACEOF >conftest.$ac_ext /* end confdefs.h. */ #include int main () { int opt = IP_RECVIF; ; return 0; } _ACEOF if ac_fn_c_try_compile "$LINENO"; then : { $as_echo "$as_me:${as_lineno-$LINENO}: result: yes" >&5 $as_echo "yes" >&6; } $as_echo "#define HAVE_IP_RECVIF 1" >>confdefs.h else { $as_echo "$as_me:${as_lineno-$LINENO}: result: no" >&5 $as_echo "no" >&6; } fi rm -f core conftest.err conftest.$ac_objext conftest.$ac_ext { $as_echo "$as_me:${as_lineno-$LINENO}: checking for SO_BINDANY" >&5 $as_echo_n "checking for SO_BINDANY... " >&6; } cat confdefs.h - <<_ACEOF >conftest.$ac_ext /* end confdefs.h. */ #include int main () { int opt = SO_BINDANY; ; return 0; } _ACEOF if ac_fn_c_try_compile "$LINENO"; then : { $as_echo "$as_me:${as_lineno-$LINENO}: result: yes" >&5 $as_echo "yes" >&6; } $as_echo "#define HAVE_SO_BINDANY 1" >>confdefs.h else { $as_echo "$as_me:${as_lineno-$LINENO}: result: no" >&5 $as_echo "no" >&6; } fi rm -f core conftest.err conftest.$ac_objext conftest.$ac_ext { $as_echo "$as_me:${as_lineno-$LINENO}: checking for IP_FREEBIND" >&5 $as_echo_n "checking for IP_FREEBIND... " >&6; } cat confdefs.h - <<_ACEOF >conftest.$ac_ext /* end confdefs.h. */ #include int main () { int opt = IP_FREEBIND; ; return 0; } _ACEOF if ac_fn_c_try_compile "$LINENO"; then : { $as_echo "$as_me:${as_lineno-$LINENO}: result: yes" >&5 $as_echo "yes" >&6; } $as_echo "#define HAVE_IP_FREEBIND 1" >>confdefs.h else { $as_echo "$as_me:${as_lineno-$LINENO}: result: no" >&5 $as_echo "no" >&6; } fi rm -f core conftest.err conftest.$ac_objext conftest.$ac_ext ac_fn_c_check_decl "$LINENO" "be32enc" "ac_cv_have_decl_be32enc" "#include " if test "x$ac_cv_have_decl_be32enc" = xyes; then : ac_have_decl=1 else ac_have_decl=0 fi cat >>confdefs.h <<_ACEOF #define HAVE_DECL_BE32ENC $ac_have_decl _ACEOF ac_fn_c_check_decl "$LINENO" "be32dec" "ac_cv_have_decl_be32dec" "#include " if test "x$ac_cv_have_decl_be32dec" = xyes; then : ac_have_decl=1 else ac_have_decl=0 fi cat >>confdefs.h <<_ACEOF #define HAVE_DECL_BE32DEC $ac_have_decl _ACEOF ac_fn_c_check_decl "$LINENO" "CLOCK_MONOTONIC" "ac_cv_have_decl_CLOCK_MONOTONIC" "#ifdef SUNOS_5 #define _POSIX_C_SOURCE 200809L #define __EXTENSIONS__ #endif #include #include #include #include /* sys/conf.h depends on param.h on FBSD at least */ #include /* Required for MAXSIG */ #include #include #ifdef __APPLE__ # define __APPLE_USE_RFC_3542 #endif #include #include #include #include #if HAVE_NET_IF_VAR_H # include #endif #include #include #if HAVE_NETINET_IN_VAR_H # include #endif #if HAVE_NET_IF_DL_H # include #endif #if HAVE_NET_NETOPT_H # include #endif #include #if HAVE_INET_ND_H # include #endif #include /* Required for IDRP */ #if HAVE_NETINET_IP_ICMP_H # include #endif #if HAVE_LINUX_MROUTE_H # include #endif #if HAVE_NETINET_IP_MROUTE_H # include #endif #if HAVE_NETINET6_IN6_H #include #endif #if HAVE_NETINET_IN6_VAR_H #include #endif #include #if HAVE_NETINET6_IN6_VAR_H # include #endif #if HAVE_NETINET6_ND6_H # include #endif " if test "x$ac_cv_have_decl_CLOCK_MONOTONIC" = xyes; then : { $as_echo "$as_me:${as_lineno-$LINENO}: checking for clock_gettime in -lrt" >&5 $as_echo_n "checking for clock_gettime in -lrt... " >&6; } if ${ac_cv_lib_rt_clock_gettime+:} false; then : $as_echo_n "(cached) " >&6 else ac_check_lib_save_LIBS=$LIBS LIBS="-lrt $LIBS" cat confdefs.h - <<_ACEOF >conftest.$ac_ext /* end confdefs.h. */ /* Override any GCC internal prototype to avoid an error. Use char because int might match the return type of a GCC builtin and then its argument prototype would still apply. */ #ifdef __cplusplus extern "C" #endif char clock_gettime (); int main () { return clock_gettime (); ; return 0; } _ACEOF if ac_fn_c_try_link "$LINENO"; then : ac_cv_lib_rt_clock_gettime=yes else ac_cv_lib_rt_clock_gettime=no fi rm -f core conftest.err conftest.$ac_objext \ conftest$ac_exeext conftest.$ac_ext LIBS=$ac_check_lib_save_LIBS fi { $as_echo "$as_me:${as_lineno-$LINENO}: result: $ac_cv_lib_rt_clock_gettime" >&5 $as_echo "$ac_cv_lib_rt_clock_gettime" >&6; } if test "x$ac_cv_lib_rt_clock_gettime" = xyes; then : LIBS="$LIBS -lrt" fi $as_echo "#define HAVE_CLOCK_MONOTONIC 1" >>confdefs.h else { $as_echo "$as_me:${as_lineno-$LINENO}: result: no" >&5 $as_echo "no" >&6; } fi for ac_prog in flex lex do # Extract the first word of "$ac_prog", so it can be a program name with args. set dummy $ac_prog; ac_word=$2 { $as_echo "$as_me:${as_lineno-$LINENO}: checking for $ac_word" >&5 $as_echo_n "checking for $ac_word... " >&6; } if ${ac_cv_prog_LEX+:} false; then : $as_echo_n "(cached) " >&6 else if test -n "$LEX"; then ac_cv_prog_LEX="$LEX" # Let the user override the test. else as_save_IFS=$IFS; IFS=$PATH_SEPARATOR for as_dir in $PATH do IFS=$as_save_IFS test -z "$as_dir" && as_dir=. for ac_exec_ext in '' $ac_executable_extensions; do if as_fn_executable_p "$as_dir/$ac_word$ac_exec_ext"; then ac_cv_prog_LEX="$ac_prog" $as_echo "$as_me:${as_lineno-$LINENO}: found $as_dir/$ac_word$ac_exec_ext" >&5 break 2 fi done done IFS=$as_save_IFS fi fi LEX=$ac_cv_prog_LEX if test -n "$LEX"; then { $as_echo "$as_me:${as_lineno-$LINENO}: result: $LEX" >&5 $as_echo "$LEX" >&6; } else { $as_echo "$as_me:${as_lineno-$LINENO}: result: no" >&5 $as_echo "no" >&6; } fi test -n "$LEX" && break done test -n "$LEX" || LEX=":" if test "x$LEX" != "x:"; then cat >conftest.l <<_ACEOF %% a { ECHO; } b { REJECT; } c { yymore (); } d { yyless (1); } e { /* IRIX 6.5 flex 2.5.4 underquotes its yyless argument. */ yyless ((input () != 0)); } f { unput (yytext[0]); } . { BEGIN INITIAL; } %% #ifdef YYTEXT_POINTER extern char *yytext; #endif int main (void) { return ! yylex () + ! yywrap (); } _ACEOF { { ac_try="$LEX conftest.l" case "(($ac_try" in *\"* | *\`* | *\\*) ac_try_echo=\$ac_try;; *) ac_try_echo=$ac_try;; esac eval ac_try_echo="\"\$as_me:${as_lineno-$LINENO}: $ac_try_echo\"" $as_echo "$ac_try_echo"; } >&5 (eval "$LEX conftest.l") 2>&5 ac_status=$? $as_echo "$as_me:${as_lineno-$LINENO}: \$? = $ac_status" >&5 test $ac_status = 0; } { $as_echo "$as_me:${as_lineno-$LINENO}: checking lex output file root" >&5 $as_echo_n "checking lex output file root... " >&6; } if ${ac_cv_prog_lex_root+:} false; then : $as_echo_n "(cached) " >&6 else if test -f lex.yy.c; then ac_cv_prog_lex_root=lex.yy elif test -f lexyy.c; then ac_cv_prog_lex_root=lexyy else as_fn_error $? "cannot find output from $LEX; giving up" "$LINENO" 5 fi fi { $as_echo "$as_me:${as_lineno-$LINENO}: result: $ac_cv_prog_lex_root" >&5 $as_echo "$ac_cv_prog_lex_root" >&6; } LEX_OUTPUT_ROOT=$ac_cv_prog_lex_root if test -z "${LEXLIB+set}"; then { $as_echo "$as_me:${as_lineno-$LINENO}: checking lex library" >&5 $as_echo_n "checking lex library... " >&6; } if ${ac_cv_lib_lex+:} false; then : $as_echo_n "(cached) " >&6 else ac_save_LIBS=$LIBS ac_cv_lib_lex='none needed' for ac_lib in '' -lfl -ll; do LIBS="$ac_lib $ac_save_LIBS" cat confdefs.h - <<_ACEOF >conftest.$ac_ext /* end confdefs.h. */ `cat $LEX_OUTPUT_ROOT.c` _ACEOF if ac_fn_c_try_link "$LINENO"; then : ac_cv_lib_lex=$ac_lib fi rm -f core conftest.err conftest.$ac_objext \ conftest$ac_exeext conftest.$ac_ext test "$ac_cv_lib_lex" != 'none needed' && break done LIBS=$ac_save_LIBS fi { $as_echo "$as_me:${as_lineno-$LINENO}: result: $ac_cv_lib_lex" >&5 $as_echo "$ac_cv_lib_lex" >&6; } test "$ac_cv_lib_lex" != 'none needed' && LEXLIB=$ac_cv_lib_lex fi { $as_echo "$as_me:${as_lineno-$LINENO}: checking whether yytext is a pointer" >&5 $as_echo_n "checking whether yytext is a pointer... " >&6; } if ${ac_cv_prog_lex_yytext_pointer+:} false; then : $as_echo_n "(cached) " >&6 else # POSIX says lex can declare yytext either as a pointer or an array; the # default is implementation-dependent. Figure out which it is, since # not all implementations provide the %pointer and %array declarations. ac_cv_prog_lex_yytext_pointer=no ac_save_LIBS=$LIBS LIBS="$LEXLIB $ac_save_LIBS" cat confdefs.h - <<_ACEOF >conftest.$ac_ext /* end confdefs.h. */ #define YYTEXT_POINTER 1 `cat $LEX_OUTPUT_ROOT.c` _ACEOF if ac_fn_c_try_link "$LINENO"; then : ac_cv_prog_lex_yytext_pointer=yes fi rm -f core conftest.err conftest.$ac_objext \ conftest$ac_exeext conftest.$ac_ext LIBS=$ac_save_LIBS fi { $as_echo "$as_me:${as_lineno-$LINENO}: result: $ac_cv_prog_lex_yytext_pointer" >&5 $as_echo "$ac_cv_prog_lex_yytext_pointer" >&6; } if test $ac_cv_prog_lex_yytext_pointer = yes; then $as_echo "#define YYTEXT_POINTER 1" >>confdefs.h fi rm -f conftest.l $LEX_OUTPUT_ROOT.c fi if test "$LEX" = :; then LEX=${am_missing_run}flex fi { $as_echo "$as_me:${as_lineno-$LINENO}: checking version of flex" >&5 $as_echo_n "checking version of flex... " >&6; } frr_ac_flex_version="$(eval $LEX -V | grep flex | head -n 1)" frr_ac_flex_version="${frr_ac_flex_version##* }" { $as_echo "$as_me:${as_lineno-$LINENO}: result: $frr_ac_flex_version" >&5 $as_echo "$frr_ac_flex_version" >&6; } # Used to indicate true or false condition ax_compare_version=false # Convert the two version strings to be compared into a format that # allows a simple string comparison. The end result is that a version # string of the form 1.12.5-r617 will be converted to the form # 0001001200050617. In other words, each number is zero padded to four # digits, and non digits are removed. ax_compare_version_A=`echo "$frr_ac_flex_version" | sed -e 's/\([0-9]*\)/Z\1Z/g' \ -e 's/Z\([0-9]\)Z/Z0\1Z/g' \ -e 's/Z\([0-9][0-9]\)Z/Z0\1Z/g' \ -e 's/Z\([0-9][0-9][0-9]\)Z/Z0\1Z/g' \ -e 's/[^0-9]//g'` ax_compare_version_B=`echo "2.5.20" | sed -e 's/\([0-9]*\)/Z\1Z/g' \ -e 's/Z\([0-9]\)Z/Z0\1Z/g' \ -e 's/Z\([0-9][0-9]\)Z/Z0\1Z/g' \ -e 's/Z\([0-9][0-9][0-9]\)Z/Z0\1Z/g' \ -e 's/[^0-9]//g'` ax_compare_version=`echo "x$ax_compare_version_A x$ax_compare_version_B" | sed 's/^ *//' | sort -r | sed "s/x${ax_compare_version_A}/false/;s/x${ax_compare_version_B}/true/;1q"` if test "$ax_compare_version" = "true" ; then LEX="$SHELL $missing_dir/missing flex" if test -f "${srcdir}/lib/command_lex.c" -a -f "${srcdir}/lib/command_lex.h"; then { $as_echo "$as_me:${as_lineno-$LINENO}: WARNING: using pregenerated flex output files" >&5 $as_echo "$as_me: WARNING: using pregenerated flex output files" >&2;} else as_fn_error $? "flex failure and pregenerated files not included (probably a git build)" "$LINENO" 5 fi LEX_OUTPUT_ROOT=lex.yy LEXLIB='' fi for ac_prog in 'bison -y' byacc do # Extract the first word of "$ac_prog", so it can be a program name with args. set dummy $ac_prog; ac_word=$2 { $as_echo "$as_me:${as_lineno-$LINENO}: checking for $ac_word" >&5 $as_echo_n "checking for $ac_word... " >&6; } if ${ac_cv_prog_YACC+:} false; then : $as_echo_n "(cached) " >&6 else if test -n "$YACC"; then ac_cv_prog_YACC="$YACC" # Let the user override the test. else as_save_IFS=$IFS; IFS=$PATH_SEPARATOR for as_dir in $PATH do IFS=$as_save_IFS test -z "$as_dir" && as_dir=. for ac_exec_ext in '' $ac_executable_extensions; do if as_fn_executable_p "$as_dir/$ac_word$ac_exec_ext"; then ac_cv_prog_YACC="$ac_prog" $as_echo "$as_me:${as_lineno-$LINENO}: found $as_dir/$ac_word$ac_exec_ext" >&5 break 2 fi done done IFS=$as_save_IFS fi fi YACC=$ac_cv_prog_YACC if test -n "$YACC"; then { $as_echo "$as_me:${as_lineno-$LINENO}: result: $YACC" >&5 $as_echo "$YACC" >&6; } else { $as_echo "$as_me:${as_lineno-$LINENO}: result: no" >&5 $as_echo "no" >&6; } fi test -n "$YACC" && break done test -n "$YACC" || YACC="yacc" { $as_echo "$as_me:${as_lineno-$LINENO}: checking version of bison" >&5 $as_echo_n "checking version of bison... " >&6; } frr_ac_bison_version="$(eval $YACC -V | grep bison | head -n 1)" frr_ac_bison_version="${frr_ac_bison_version##* }" frr_ac_bison_missing="false" case "x${frr_ac_bison_version}x" in x2.7*) BISON_OPENBRACE='"' BISON_CLOSEBRACE='"' BISON_VERBOSE='' { $as_echo "$as_me:${as_lineno-$LINENO}: result: $frr_ac_bison_version - 2.7 or older" >&5 $as_echo "$frr_ac_bison_version - 2.7 or older" >&6; } ;; x2.*|x1.*) { $as_echo "$as_me:${as_lineno-$LINENO}: result: $frr_ac_bison_version" >&5 $as_echo "$frr_ac_bison_version" >&6; } { $as_echo "$as_me:${as_lineno-$LINENO}: WARNING: installed bison is too old. Please install GNU bison 2.7.x or newer." >&5 $as_echo "$as_me: WARNING: installed bison is too old. Please install GNU bison 2.7.x or newer." >&2;} frr_ac_bison_missing="true" ;; x) { $as_echo "$as_me:${as_lineno-$LINENO}: result: none" >&5 $as_echo "none" >&6; } { $as_echo "$as_me:${as_lineno-$LINENO}: WARNING: could not determine bison version. Please install GNU bison 2.7.x or newer." >&5 $as_echo "$as_me: WARNING: could not determine bison version. Please install GNU bison 2.7.x or newer." >&2;} frr_ac_bison_missing="true" ;; x3.012^0-9*) BISON_OPENBRACE='{' BISON_CLOSEBRACE='}' BISON_VERBOSE='-Dparse.error=verbose' { $as_echo "$as_me:${as_lineno-$LINENO}: result: $frr_ac_bison_version - 3.0 to 3.2" >&5 $as_echo "$frr_ac_bison_version - 3.0 to 3.2" >&6; } ;; *) BISON_OPENBRACE='{' BISON_CLOSEBRACE='}' BISON_VERBOSE='-Dparse.error=verbose -Wno-yacc' { $as_echo "$as_me:${as_lineno-$LINENO}: result: $frr_ac_bison_version - 3.3 or newer" >&5 $as_echo "$frr_ac_bison_version - 3.3 or newer" >&6; } ;; esac if $frr_ac_bison_missing; then YACC="$SHELL $missing_dir/missing bison -y" if test -f "${srcdir}/lib/command_parse.c" -a -f "${srcdir}/lib/command_parse.h"; then { $as_echo "$as_me:${as_lineno-$LINENO}: WARNING: using pregenerated bison output files" >&5 $as_echo "$as_me: WARNING: using pregenerated bison output files" >&2;} else as_fn_error $? "bison failure and pregenerated files not included (probably a git build)" "$LINENO" 5 fi fi if test "${enable_capabilities}" != "no"; then { $as_echo "$as_me:${as_lineno-$LINENO}: checking whether prctl PR_SET_KEEPCAPS is available" >&5 $as_echo_n "checking whether prctl PR_SET_KEEPCAPS is available... " >&6; } cat confdefs.h - <<_ACEOF >conftest.$ac_ext /* end confdefs.h. */ #include int main () { prctl(PR_SET_KEEPCAPS, 1, 0, 0, 0); ; return 0; } _ACEOF if ac_fn_c_try_compile "$LINENO"; then : { $as_echo "$as_me:${as_lineno-$LINENO}: result: yes" >&5 $as_echo "yes" >&6; } $as_echo "#define HAVE_PR_SET_KEEPCAPS 1" >>confdefs.h frr_ac_keepcaps="yes" else { $as_echo "$as_me:${as_lineno-$LINENO}: result: no" >&5 $as_echo "no" >&6; } fi rm -f core conftest.err conftest.$ac_objext conftest.$ac_ext if test x"${frr_ac_keepcaps}" = x"yes"; then for ac_header in sys/capability.h do : ac_fn_c_check_header_mongrel "$LINENO" "sys/capability.h" "ac_cv_header_sys_capability_h" "$ac_includes_default" if test "x$ac_cv_header_sys_capability_h" = xyes; then : cat >>confdefs.h <<_ACEOF #define HAVE_SYS_CAPABILITY_H 1 _ACEOF fi done fi if test x"${ac_cv_header_sys_capability_h}" = x"yes"; then { $as_echo "$as_me:${as_lineno-$LINENO}: checking for cap_init in -lcap" >&5 $as_echo_n "checking for cap_init in -lcap... " >&6; } if ${ac_cv_lib_cap_cap_init+:} false; then : $as_echo_n "(cached) " >&6 else ac_check_lib_save_LIBS=$LIBS LIBS="-lcap $LIBS" cat confdefs.h - <<_ACEOF >conftest.$ac_ext /* end confdefs.h. */ /* Override any GCC internal prototype to avoid an error. Use char because int might match the return type of a GCC builtin and then its argument prototype would still apply. */ #ifdef __cplusplus extern "C" #endif char cap_init (); int main () { return cap_init (); ; return 0; } _ACEOF if ac_fn_c_try_link "$LINENO"; then : ac_cv_lib_cap_cap_init=yes else ac_cv_lib_cap_cap_init=no fi rm -f core conftest.err conftest.$ac_objext \ conftest$ac_exeext conftest.$ac_ext LIBS=$ac_check_lib_save_LIBS fi { $as_echo "$as_me:${as_lineno-$LINENO}: result: $ac_cv_lib_cap_cap_init" >&5 $as_echo "$ac_cv_lib_cap_cap_init" >&6; } if test "x$ac_cv_lib_cap_cap_init" = xyes; then : $as_echo "#define HAVE_LCAPS 1" >>confdefs.h LIBCAP="-lcap" frr_ac_lcaps="yes" fi else for ac_header in priv.h do : ac_fn_c_check_header_mongrel "$LINENO" "priv.h" "ac_cv_header_priv_h" "$ac_includes_default" if test "x$ac_cv_header_priv_h" = xyes; then : cat >>confdefs.h <<_ACEOF #define HAVE_PRIV_H 1 _ACEOF { $as_echo "$as_me:${as_lineno-$LINENO}: checking Solaris style privileges are available" >&5 $as_echo_n "checking Solaris style privileges are available... " >&6; } cat confdefs.h - <<_ACEOF >conftest.$ac_ext /* end confdefs.h. */ #include int main () { getpflags(PRIV_AWARE); ; return 0; } _ACEOF if ac_fn_c_try_compile "$LINENO"; then : { $as_echo "$as_me:${as_lineno-$LINENO}: result: yes" >&5 $as_echo "yes" >&6; } $as_echo "#define HAVE_SOLARIS_CAPABILITIES 1" >>confdefs.h frr_ac_scaps="yes" else { $as_echo "$as_me:${as_lineno-$LINENO}: result: no" >&5 $as_echo "no" >&6; } fi rm -f core conftest.err conftest.$ac_objext conftest.$ac_ext fi done fi if test x"${frr_ac_scaps}" = x"yes" \ -o x"${frr_ac_lcaps}" = x"yes"; then $as_echo "#define HAVE_CAPABILITIES 1" >>confdefs.h fi case "$host_os" in linux*) if test "$frr_ac_lcaps" != "yes"; then as_fn_error $? "libcap and/or its headers were not found. Running FRR without libcap support built in causes a huge performance penalty." "$LINENO" 5 fi ;; esac else case "$host_os" in linux*) { $as_echo "$as_me:${as_lineno-$LINENO}: WARNING: Running FRR without libcap support built in causes a huge performance penalty." >&5 $as_echo "$as_me: WARNING: Running FRR without libcap support built in causes a huge performance penalty." >&2;} ;; esac fi if test x"${enable_backtrace}" != x"no" ; then backtrace_ok=no pkg_failed=no { $as_echo "$as_me:${as_lineno-$LINENO}: checking for UNWIND (libunwind)" >&5 $as_echo_n "checking for UNWIND (libunwind)... " >&6; } if test -n "$UNWIND_CFLAGS"; then pkg_cv_UNWIND_CFLAGS="$UNWIND_CFLAGS" elif test -n "$PKG_CONFIG"; then if test -n "$PKG_CONFIG" && \ { { $as_echo "$as_me:${as_lineno-$LINENO}: \$PKG_CONFIG --exists --print-errors \"libunwind\""; } >&5 ($PKG_CONFIG --exists --print-errors "libunwind") 2>&5 ac_status=$? $as_echo "$as_me:${as_lineno-$LINENO}: \$? = $ac_status" >&5 test $ac_status = 0; }; then pkg_cv_UNWIND_CFLAGS=`$PKG_CONFIG --cflags "libunwind" 2>/dev/null` test "x$?" != "x0" && pkg_failed=yes else pkg_failed=yes fi else pkg_failed=untried fi if test -n "$UNWIND_LIBS"; then pkg_cv_UNWIND_LIBS="$UNWIND_LIBS" elif test -n "$PKG_CONFIG"; then if test -n "$PKG_CONFIG" && \ { { $as_echo "$as_me:${as_lineno-$LINENO}: \$PKG_CONFIG --exists --print-errors \"libunwind\""; } >&5 ($PKG_CONFIG --exists --print-errors "libunwind") 2>&5 ac_status=$? $as_echo "$as_me:${as_lineno-$LINENO}: \$? = $ac_status" >&5 test $ac_status = 0; }; then pkg_cv_UNWIND_LIBS=`$PKG_CONFIG --libs "libunwind" 2>/dev/null` test "x$?" != "x0" && pkg_failed=yes else pkg_failed=yes fi else pkg_failed=untried fi if test $pkg_failed = yes; then { $as_echo "$as_me:${as_lineno-$LINENO}: result: no" >&5 $as_echo "no" >&6; } if $PKG_CONFIG --atleast-pkgconfig-version 0.20; then _pkg_short_errors_supported=yes else _pkg_short_errors_supported=no fi if test $_pkg_short_errors_supported = yes; then UNWIND_PKG_ERRORS=`$PKG_CONFIG --short-errors --print-errors --cflags --libs "libunwind" 2>&1` else UNWIND_PKG_ERRORS=`$PKG_CONFIG --print-errors --cflags --libs "libunwind" 2>&1` fi # Put the nasty error message in config.log where it belongs echo "$UNWIND_PKG_ERRORS" >&5 case "$host_os" in sunos* | solaris2*) for ac_func in printstack do : ac_fn_c_check_func "$LINENO" "printstack" "ac_cv_func_printstack" if test "x$ac_cv_func_printstack" = xyes; then : cat >>confdefs.h <<_ACEOF #define HAVE_PRINTSTACK 1 _ACEOF $as_echo "#define HAVE_PRINTSTACK 1" >>confdefs.h backtrace_ok=yes fi done ;; esac if test "$backtrace_ok" = no; then ac_fn_c_check_header_mongrel "$LINENO" "execinfo.h" "ac_cv_header_execinfo_h" "$ac_includes_default" if test "x$ac_cv_header_execinfo_h" = xyes; then : { $as_echo "$as_me:${as_lineno-$LINENO}: checking for library containing backtrace" >&5 $as_echo_n "checking for library containing backtrace... " >&6; } if ${ac_cv_search_backtrace+:} false; then : $as_echo_n "(cached) " >&6 else ac_func_search_save_LIBS=$LIBS cat confdefs.h - <<_ACEOF >conftest.$ac_ext /* end confdefs.h. */ /* Override any GCC internal prototype to avoid an error. Use char because int might match the return type of a GCC builtin and then its argument prototype would still apply. */ #ifdef __cplusplus extern "C" #endif char backtrace (); int main () { return backtrace (); ; return 0; } _ACEOF for ac_lib in '' execinfo; do if test -z "$ac_lib"; then ac_res="none required" else ac_res=-l$ac_lib LIBS="-l$ac_lib -lm $ac_func_search_save_LIBS" fi if ac_fn_c_try_link "$LINENO"; then : ac_cv_search_backtrace=$ac_res fi rm -f core conftest.err conftest.$ac_objext \ conftest$ac_exeext if ${ac_cv_search_backtrace+:} false; then : break fi done if ${ac_cv_search_backtrace+:} false; then : else ac_cv_search_backtrace=no fi rm conftest.$ac_ext LIBS=$ac_func_search_save_LIBS fi { $as_echo "$as_me:${as_lineno-$LINENO}: result: $ac_cv_search_backtrace" >&5 $as_echo "$ac_cv_search_backtrace" >&6; } ac_res=$ac_cv_search_backtrace if test "$ac_res" != no; then : test "$ac_res" = "none required" || LIBS="$ac_res $LIBS" $as_echo "#define HAVE_GLIBC_BACKTRACE 1" >>confdefs.h backtrace_ok=yes fi fi fi elif test $pkg_failed = untried; then { $as_echo "$as_me:${as_lineno-$LINENO}: result: no" >&5 $as_echo "no" >&6; } case "$host_os" in sunos* | solaris2*) for ac_func in printstack do : ac_fn_c_check_func "$LINENO" "printstack" "ac_cv_func_printstack" if test "x$ac_cv_func_printstack" = xyes; then : cat >>confdefs.h <<_ACEOF #define HAVE_PRINTSTACK 1 _ACEOF $as_echo "#define HAVE_PRINTSTACK 1" >>confdefs.h backtrace_ok=yes fi done ;; esac if test "$backtrace_ok" = no; then ac_fn_c_check_header_mongrel "$LINENO" "execinfo.h" "ac_cv_header_execinfo_h" "$ac_includes_default" if test "x$ac_cv_header_execinfo_h" = xyes; then : { $as_echo "$as_me:${as_lineno-$LINENO}: checking for library containing backtrace" >&5 $as_echo_n "checking for library containing backtrace... " >&6; } if ${ac_cv_search_backtrace+:} false; then : $as_echo_n "(cached) " >&6 else ac_func_search_save_LIBS=$LIBS cat confdefs.h - <<_ACEOF >conftest.$ac_ext /* end confdefs.h. */ /* Override any GCC internal prototype to avoid an error. Use char because int might match the return type of a GCC builtin and then its argument prototype would still apply. */ #ifdef __cplusplus extern "C" #endif char backtrace (); int main () { return backtrace (); ; return 0; } _ACEOF for ac_lib in '' execinfo; do if test -z "$ac_lib"; then ac_res="none required" else ac_res=-l$ac_lib LIBS="-l$ac_lib -lm $ac_func_search_save_LIBS" fi if ac_fn_c_try_link "$LINENO"; then : ac_cv_search_backtrace=$ac_res fi rm -f core conftest.err conftest.$ac_objext \ conftest$ac_exeext if ${ac_cv_search_backtrace+:} false; then : break fi done if ${ac_cv_search_backtrace+:} false; then : else ac_cv_search_backtrace=no fi rm conftest.$ac_ext LIBS=$ac_func_search_save_LIBS fi { $as_echo "$as_me:${as_lineno-$LINENO}: result: $ac_cv_search_backtrace" >&5 $as_echo "$ac_cv_search_backtrace" >&6; } ac_res=$ac_cv_search_backtrace if test "$ac_res" != no; then : test "$ac_res" = "none required" || LIBS="$ac_res $LIBS" $as_echo "#define HAVE_GLIBC_BACKTRACE 1" >>confdefs.h backtrace_ok=yes fi fi fi else UNWIND_CFLAGS=$pkg_cv_UNWIND_CFLAGS UNWIND_LIBS=$pkg_cv_UNWIND_LIBS { $as_echo "$as_me:${as_lineno-$LINENO}: result: yes" >&5 $as_echo "yes" >&6; } $as_echo "#define HAVE_LIBUNWIND 1" >>confdefs.h backtrace_ok=yes fi if test x"${enable_backtrace}" = x"yes" -a x"${backtrace_ok}" = x"no"; then { { $as_echo "$as_me:${as_lineno-$LINENO}: error: in \`$ac_pwd':" >&5 $as_echo "$as_me: error: in \`$ac_pwd':" >&2;} as_fn_error $? "failed to find backtrace or libunwind support See \`config.log' for more details" "$LINENO" 5; } fi fi for ac_header in malloc.h malloc_np.h malloc/malloc.h do : as_ac_Header=`$as_echo "ac_cv_header_$ac_header" | $as_tr_sh` ac_fn_c_check_header_compile "$LINENO" "$ac_header" "$as_ac_Header" "#ifdef SUNOS_5 #define _POSIX_C_SOURCE 200809L #define __EXTENSIONS__ #endif #include #include #include #include /* sys/conf.h depends on param.h on FBSD at least */ #include /* Required for MAXSIG */ #include #include #ifdef __APPLE__ # define __APPLE_USE_RFC_3542 #endif #include #include #include #include #if HAVE_NET_IF_VAR_H # include #endif #include #include #if HAVE_NETINET_IN_VAR_H # include #endif #if HAVE_NET_IF_DL_H # include #endif #if HAVE_NET_NETOPT_H # include #endif #include #if HAVE_INET_ND_H # include #endif #include /* Required for IDRP */ #if HAVE_NETINET_IP_ICMP_H # include #endif #if HAVE_LINUX_MROUTE_H # include #endif #if HAVE_NETINET_IP_MROUTE_H # include #endif #if HAVE_NETINET6_IN6_H #include #endif #if HAVE_NETINET_IN6_VAR_H #include #endif #include #if HAVE_NETINET6_IN6_VAR_H # include #endif #if HAVE_NETINET6_ND6_H # include #endif " if eval test \"x\$"$as_ac_Header"\" = x"yes"; then : cat >>confdefs.h <<_ACEOF #define `$as_echo "HAVE_$ac_header" | $as_tr_cpp` 1 _ACEOF fi done { $as_echo "$as_me:${as_lineno-$LINENO}: checking whether mallinfo is available" >&5 $as_echo_n "checking whether mallinfo is available... " >&6; } if ${frr_cv_mallinfo+:} false; then : $as_echo_n "(cached) " >&6 else cat confdefs.h - <<_ACEOF >conftest.$ac_ext /* end confdefs.h. */ #ifdef SUNOS_5 #define _POSIX_C_SOURCE 200809L #define __EXTENSIONS__ #endif #include #include #include #include /* sys/conf.h depends on param.h on FBSD at least */ #include /* Required for MAXSIG */ #include #include #ifdef __APPLE__ # define __APPLE_USE_RFC_3542 #endif #include #include #include #include #if HAVE_NET_IF_VAR_H # include #endif #include #include #if HAVE_NETINET_IN_VAR_H # include #endif #if HAVE_NET_IF_DL_H # include #endif #if HAVE_NET_NETOPT_H # include #endif #include #if HAVE_INET_ND_H # include #endif #include /* Required for IDRP */ #if HAVE_NETINET_IP_ICMP_H # include #endif #if HAVE_LINUX_MROUTE_H # include #endif #if HAVE_NETINET_IP_MROUTE_H # include #endif #if HAVE_NETINET6_IN6_H #include #endif #if HAVE_NETINET_IN6_VAR_H #include #endif #include #if HAVE_NETINET6_IN6_VAR_H # include #endif #if HAVE_NETINET6_ND6_H # include #endif #ifdef HAVE_MALLOC_H #include #endif #ifdef HAVE_MALLOC_NP_H #include #endif #ifdef HAVE_MALLOC_MALLOC_H #include #endif int main () { struct mallinfo ac_x; ac_x = mallinfo (); ; return 0; } _ACEOF if ac_fn_c_try_link "$LINENO"; then : frr_cv_mallinfo=yes else frr_cv_mallinfo=no fi rm -f core conftest.err conftest.$ac_objext \ conftest$ac_exeext conftest.$ac_ext fi { $as_echo "$as_me:${as_lineno-$LINENO}: result: $frr_cv_mallinfo" >&5 $as_echo "$frr_cv_mallinfo" >&6; } if test "$frr_cv_mallinfo" = yes; then $as_echo "#define HAVE_MALLINFO 1" >>confdefs.h fi { $as_echo "$as_me:${as_lineno-$LINENO}: checking whether malloc_usable_size is available" >&5 $as_echo_n "checking whether malloc_usable_size is available... " >&6; } cat confdefs.h - <<_ACEOF >conftest.$ac_ext /* end confdefs.h. */ #ifdef SUNOS_5 #define _POSIX_C_SOURCE 200809L #define __EXTENSIONS__ #endif #include #include #include #include /* sys/conf.h depends on param.h on FBSD at least */ #include /* Required for MAXSIG */ #include #include #ifdef __APPLE__ # define __APPLE_USE_RFC_3542 #endif #include #include #include #include #if HAVE_NET_IF_VAR_H # include #endif #include #include #if HAVE_NETINET_IN_VAR_H # include #endif #if HAVE_NET_IF_DL_H # include #endif #if HAVE_NET_NETOPT_H # include #endif #include #if HAVE_INET_ND_H # include #endif #include /* Required for IDRP */ #if HAVE_NETINET_IP_ICMP_H # include #endif #if HAVE_LINUX_MROUTE_H # include #endif #if HAVE_NETINET_IP_MROUTE_H # include #endif #if HAVE_NETINET6_IN6_H #include #endif #if HAVE_NETINET_IN6_VAR_H #include #endif #include #if HAVE_NETINET6_IN6_VAR_H # include #endif #if HAVE_NETINET6_ND6_H # include #endif #ifdef HAVE_MALLOC_H #include #endif #ifdef HAVE_MALLOC_MALLOC_H #include #endif int main () { size_t ac_x; ac_x = malloc_usable_size(NULL); ; return 0; } _ACEOF if ac_fn_c_try_link "$LINENO"; then : { $as_echo "$as_me:${as_lineno-$LINENO}: result: yes" >&5 $as_echo "yes" >&6; } $as_echo "#define HAVE_MALLOC_USABLE_SIZE 1" >>confdefs.h else { $as_echo "$as_me:${as_lineno-$LINENO}: result: no" >&5 $as_echo "no" >&6; } { $as_echo "$as_me:${as_lineno-$LINENO}: checking whether malloc_size is available" >&5 $as_echo_n "checking whether malloc_size is available... " >&6; } cat confdefs.h - <<_ACEOF >conftest.$ac_ext /* end confdefs.h. */ #ifdef HAVE_MALLOC_H #include #endif #ifdef HAVE_MALLOC_MALLOC_H #include #endif int main () { size_t ac_x; ac_x = malloc_size(NULL); ; return 0; } _ACEOF if ac_fn_c_try_link "$LINENO"; then : { $as_echo "$as_me:${as_lineno-$LINENO}: result: yes" >&5 $as_echo "yes" >&6; } $as_echo "#define HAVE_MALLOC_SIZE 1" >>confdefs.h else { $as_echo "$as_me:${as_lineno-$LINENO}: result: no" >&5 $as_echo "no" >&6; } fi rm -f core conftest.err conftest.$ac_objext \ conftest$ac_exeext conftest.$ac_ext fi rm -f core conftest.err conftest.$ac_objext \ conftest$ac_exeext conftest.$ac_ext dev_version=`echo $VERSION | grep dev` #don't expire deprecated code in non 'dev' branch if test "${dev_version}" = ""; then CONFDATE=0 else CONFDATE=`date '+%Y%m%d'` fi { $as_echo "$as_me:${as_lineno-$LINENO}: checking directory to use for state file" >&5 $as_echo_n "checking directory to use for state file... " >&6; } if test "${prefix}" = "NONE"; then frr_statedir_prefix=""; else frr_statedir_prefix=${prefix} fi if test "${localstatedir}" = '${prefix}/var'; then for FRR_STATE_DIR in ${frr_statedir_prefix}/var/run ${frr_statedir_prefix}/var/adm ${frr_statedir_prefix}/etc /var/run /var/adm /etc /dev/null; do test -d $FRR_STATE_DIR && break done frr_statedir=$FRR_STATE_DIR else frr_statedir=${localstatedir} fi if test $frr_statedir = "/dev/null"; then as_fn_error $? "STATE DIRECTORY NOT FOUND! FIX OR SPECIFY --localstatedir!" "$LINENO" 5 fi { $as_echo "$as_me:${as_lineno-$LINENO}: result: ${frr_statedir}" >&5 $as_echo "${frr_statedir}" >&6; } cat >>confdefs.h <<_ACEOF #define LDPD_SOCKET "$frr_statedir%s%s/ldpd.sock" _ACEOF cat >>confdefs.h <<_ACEOF #define ZEBRA_SERV_PATH "$frr_statedir%s%s/zserv.api" _ACEOF cat >>confdefs.h <<_ACEOF #define BFDD_CONTROL_SOCKET "$frr_statedir%s%s/bfdd.sock" _ACEOF cat >>confdefs.h <<_ACEOF #define DAEMON_VTY_DIR "$frr_statedir%s%s" _ACEOF cat >>confdefs.h <<_ACEOF #define DAEMON_DB_DIR "$frr_statedir" _ACEOF test "x$prefix" = xNONE && prefix=$ac_default_prefix test "x$exec_prefix" = xNONE && exec_prefix='${prefix}' vtysh_bin="$bindir/vtysh" for I in 1 2 3 4 5 6 7 8 9 10; do eval vtysh_bin="\"$vtysh_bin\"" done cat >>confdefs.h <<_ACEOF #define VTYSH_BIN_PATH "$vtysh_bin" _ACEOF CFG_SYSCONF="$sysconfdir" CFG_SBIN="$sbindir" CFG_STATE="$frr_statedir" CFG_MODULE="$moduledir" CFG_YANGMODELS="$yangmodelsdir" for I in 1 2 3 4 5 6 7 8 9 10; do eval CFG_SYSCONF="\"$CFG_SYSCONF\"" eval CFG_SBIN="\"$CFG_SBIN\"" eval CFG_STATE="\"$CFG_STATE\"" eval CFG_MODULE="\"$CFG_MODULE\"" eval CFG_YANGMODELS="\"$CFG_YANGMODELS\"" done cat >>confdefs.h <<_ACEOF #define MODULE_PATH "$CFG_MODULE" _ACEOF cat >>confdefs.h <<_ACEOF #define YANG_MODELS_PATH "$CFG_YANGMODELS" _ACEOF cat >>confdefs.h <<_ACEOF #define WATCHFRR_SH_PATH "${CFG_SBIN%/}/watchfrr.sh" _ACEOF if test "${enable_realms}" = "yes"; then SUPPORT_REALMS_TRUE= SUPPORT_REALMS_FALSE='#' else SUPPORT_REALMS_TRUE='#' SUPPORT_REALMS_FALSE= fi if test x${enable_bgp_vnc} != xno; then ENABLE_BGP_VNC_TRUE= ENABLE_BGP_VNC_FALSE='#' else ENABLE_BGP_VNC_TRUE='#' ENABLE_BGP_VNC_FALSE= fi if $bgpd_bmp; then BGP_BMP_TRUE= BGP_BMP_FALSE='#' else BGP_BMP_TRUE='#' BGP_BMP_FALSE= fi if $SQLITE3; then SQLITE3_TRUE= SQLITE3_FALSE='#' else SQLITE3_TRUE='#' SQLITE3_FALSE= fi if test "x$enable_confd" != "x"; then CONFD_TRUE= CONFD_FALSE='#' else CONFD_TRUE='#' CONFD_FALSE= fi if test "x$enable_sysrepo" = "xyes"; then SYSREPO_TRUE= SYSREPO_FALSE='#' else SYSREPO_TRUE='#' SYSREPO_FALSE= fi if test "x$enable_grpc" = "xyes"; then GRPC_TRUE= GRPC_FALSE='#' else GRPC_TRUE='#' GRPC_FALSE= fi if test "x$ZEROMQ" = "xtrue"; then ZEROMQ_TRUE= ZEROMQ_FALSE='#' else ZEROMQ_TRUE='#' ZEROMQ_FALSE= fi if test "x$RPKI" = "xtrue"; then RPKI_TRUE= RPKI_FALSE='#' else RPKI_TRUE='#' RPKI_FALSE= fi if test "x$SNMP_METHOD" = "xagentx"; then SNMP_TRUE= SNMP_FALSE='#' else SNMP_TRUE='#' SNMP_FALSE= fi if $IRDP; then IRDP_TRUE= IRDP_FALSE='#' else IRDP_TRUE='#' IRDP_FALSE= fi if test "x$enable_fpm" = "xyes"; then FPM_TRUE= FPM_FALSE='#' else FPM_TRUE='#' FPM_FALSE= fi if test "x$enable_protobuf" = "xyes"; then HAVE_PROTOBUF_TRUE= HAVE_PROTOBUF_FALSE='#' else HAVE_PROTOBUF_TRUE='#' HAVE_PROTOBUF_FALSE= fi if test "x$VTYSH" = "xvtysh"; then VTYSH_TRUE= VTYSH_FALSE='#' else VTYSH_TRUE='#' VTYSH_FALSE= fi if test "${enable_zebra}" != "no"; then ZEBRA_TRUE= ZEBRA_FALSE='#' else ZEBRA_TRUE='#' ZEBRA_FALSE= fi if test "x${enable_bgpd}" != "no"; then BGPD_TRUE= BGPD_FALSE='#' else BGPD_TRUE='#' BGPD_FALSE= fi if test "${enable_ripd}" != "no"; then RIPD_TRUE= RIPD_FALSE='#' else RIPD_TRUE='#' RIPD_FALSE= fi if test "${enable_ospfd}" != "no"; then OSPFD_TRUE= OSPFD_FALSE='#' else OSPFD_TRUE='#' OSPFD_FALSE= fi if test "${enable_ldpd}" != "no"; then LDPD_TRUE= LDPD_FALSE='#' else LDPD_TRUE='#' LDPD_FALSE= fi if test "x$BFDD" = "xbfdd"; then BFDD_TRUE= BFDD_FALSE='#' else BFDD_TRUE='#' BFDD_FALSE= fi if test "x$NHRPD" = "xnhrpd"; then NHRPD_TRUE= NHRPD_FALSE='#' else NHRPD_TRUE='#' NHRPD_FALSE= fi if test "${enable_eigrpd}" != "no"; then EIGRPD_TRUE= EIGRPD_FALSE='#' else EIGRPD_TRUE='#' EIGRPD_FALSE= fi if test "x$WATCHFRR" = "xwatchfrr"; then WATCHFRR_TRUE= WATCHFRR_FALSE='#' else WATCHFRR_TRUE='#' WATCHFRR_FALSE= fi if test "x$OSPFCLIENT" = "xospfclient"; then OSPFCLIENT_TRUE= OSPFCLIENT_FALSE='#' else OSPFCLIENT_TRUE='#' OSPFCLIENT_FALSE= fi if test "${enable_ripngd}" != "no"; then RIPNGD_TRUE= RIPNGD_FALSE='#' else RIPNGD_TRUE='#' RIPNGD_FALSE= fi if test "${enable_babeld}" != "no"; then BABELD_TRUE= BABELD_FALSE='#' else BABELD_TRUE='#' BABELD_FALSE= fi if test "${enable_ospf6d}" != "no"; then OSPF6D_TRUE= OSPF6D_FALSE='#' else OSPF6D_TRUE='#' OSPF6D_FALSE= fi if test "${enable_isisd}" != "no"; then ISISD_TRUE= ISISD_FALSE='#' else ISISD_TRUE='#' ISISD_FALSE= fi if test "${enable_pimd}" != "no"; then PIMD_TRUE= PIMD_FALSE='#' else PIMD_TRUE='#' PIMD_FALSE= fi if test "${enable_pbrd}" != "no"; then PBRD_TRUE= PBRD_FALSE='#' else PBRD_TRUE='#' PBRD_FALSE= fi if test "${enable_sharpd}" = "yes"; then SHARPD_TRUE= SHARPD_FALSE='#' else SHARPD_TRUE='#' SHARPD_FALSE= fi if test "${enable_staticd}" != "no"; then STATICD_TRUE= STATICD_FALSE='#' else STATICD_TRUE='#' STATICD_FALSE= fi if test "${enable_fabricd}" != "no"; then FABRICD_TRUE= FABRICD_FALSE='#' else FABRICD_TRUE='#' FABRICD_FALSE= fi if test "${enable_vrrpd}" != "no"; then VRRPD_TRUE= VRRPD_FALSE='#' else VRRPD_TRUE='#' VRRPD_FALSE= fi ac_config_files="$ac_config_files Makefile" ac_config_files="$ac_config_files config.version changelog-auto redhat/frr.spec solaris/Makefile alpine/APKBUILD snapcraft/snapcraft.yaml lib/version.h tests/lib/cli/test_cli.refout pkgsrc/bgpd.sh pkgsrc/ospf6d.sh pkgsrc/ospfd.sh pkgsrc/ripd.sh pkgsrc/ripngd.sh pkgsrc/zebra.sh pkgsrc/eigrpd.sh" ac_config_files="$ac_config_files vtysh/extract.pl" ac_config_files="$ac_config_files tools/frr" ac_config_files="$ac_config_files tools/watchfrr.sh" ac_config_files="$ac_config_files tools/frrinit.sh" ac_config_files="$ac_config_files tools/frrcommon.sh" ac_config_commands="$ac_config_commands lib/route_types.h" if test "x$with_pkg_git_version" = "xyes"; then : ac_config_commands="$ac_config_commands lib/gitversion.h" fi ## Hack, but working solution to avoid rebuilding of frr.info. ## It's already in CVS until texinfo 4.7 is more common. cat >confcache <<\_ACEOF # This file is a shell script that caches the results of configure # tests run on this system so they can be shared between configure # scripts and configure runs, see configure's option --config-cache. # It is not useful on other systems. If it contains results you don't # want to keep, you may remove or edit it. # # config.status only pays attention to the cache file if you give it # the --recheck option to rerun configure. # # `ac_cv_env_foo' variables (set or unset) will be overridden when # loading this file, other *unset* `ac_cv_foo' will be assigned the # following values. _ACEOF # The following way of writing the cache mishandles newlines in values, # but we know of no workaround that is simple, portable, and efficient. # So, we kill variables containing newlines. # Ultrix sh set writes to stderr and can't be redirected directly, # and sets the high bit in the cache file unless we assign to the vars. ( for ac_var in `(set) 2>&1 | sed -n 's/^\([a-zA-Z_][a-zA-Z0-9_]*\)=.*/\1/p'`; do eval ac_val=\$$ac_var case $ac_val in #( *${as_nl}*) case $ac_var in #( *_cv_*) { $as_echo "$as_me:${as_lineno-$LINENO}: WARNING: cache variable $ac_var contains a newline" >&5 $as_echo "$as_me: WARNING: cache variable $ac_var contains a newline" >&2;} ;; esac case $ac_var in #( _ | IFS | as_nl) ;; #( BASH_ARGV | BASH_SOURCE) eval $ac_var= ;; #( *) { eval $ac_var=; unset $ac_var;} ;; esac ;; esac done (set) 2>&1 | case $as_nl`(ac_space=' '; set) 2>&1` in #( *${as_nl}ac_space=\ *) # `set' does not quote correctly, so add quotes: double-quote # substitution turns \\\\ into \\, and sed turns \\ into \. sed -n \ "s/'/'\\\\''/g; s/^\\([_$as_cr_alnum]*_cv_[_$as_cr_alnum]*\\)=\\(.*\\)/\\1='\\2'/p" ;; #( *) # `set' quotes correctly as required by POSIX, so do not add quotes. sed -n "/^[_$as_cr_alnum]*_cv_[_$as_cr_alnum]*=/p" ;; esac | sort ) | sed ' /^ac_cv_env_/b end t clear :clear s/^\([^=]*\)=\(.*[{}].*\)$/test "${\1+set}" = set || &/ t end s/^\([^=]*\)=\(.*\)$/\1=${\1=\2}/ :end' >>confcache if diff "$cache_file" confcache >/dev/null 2>&1; then :; else if test -w "$cache_file"; then if test "x$cache_file" != "x/dev/null"; then { $as_echo "$as_me:${as_lineno-$LINENO}: updating cache $cache_file" >&5 $as_echo "$as_me: updating cache $cache_file" >&6;} if test ! -f "$cache_file" || test -h "$cache_file"; then cat confcache >"$cache_file" else case $cache_file in #( */* | ?:*) mv -f confcache "$cache_file"$$ && mv -f "$cache_file"$$ "$cache_file" ;; #( *) mv -f confcache "$cache_file" ;; esac fi fi else { $as_echo "$as_me:${as_lineno-$LINENO}: not updating unwritable cache $cache_file" >&5 $as_echo "$as_me: not updating unwritable cache $cache_file" >&6;} fi fi rm -f confcache test "x$prefix" = xNONE && prefix=$ac_default_prefix # Let make expand exec_prefix. test "x$exec_prefix" = xNONE && exec_prefix='${prefix}' DEFS=-DHAVE_CONFIG_H ac_libobjs= ac_ltlibobjs= U= for ac_i in : $LIBOBJS; do test "x$ac_i" = x: && continue # 1. Remove the extension, and $U if already installed. ac_script='s/\$U\././;s/\.o$//;s/\.obj$//' ac_i=`$as_echo "$ac_i" | sed "$ac_script"` # 2. Prepend LIBOBJDIR. When used with automake>=1.10 LIBOBJDIR # will be set to the directory where LIBOBJS objects are built. as_fn_append ac_libobjs " \${LIBOBJDIR}$ac_i\$U.$ac_objext" as_fn_append ac_ltlibobjs " \${LIBOBJDIR}$ac_i"'$U.lo' done LIBOBJS=$ac_libobjs LTLIBOBJS=$ac_ltlibobjs if test -z "${BUILD_CLIPPY_TRUE}" && test -z "${BUILD_CLIPPY_FALSE}"; then as_fn_error $? "conditional \"BUILD_CLIPPY\" was never defined. Usually this means the macro was only invoked conditionally." "$LINENO" 5 fi if test -z "${HOSTTOOLS_CLIPPY_TRUE}" && test -z "${HOSTTOOLS_CLIPPY_FALSE}"; then as_fn_error $? "conditional \"HOSTTOOLS_CLIPPY\" was never defined. Usually this means the macro was only invoked conditionally." "$LINENO" 5 fi if test -z "${ONLY_CLIPPY_TRUE}" && test -z "${ONLY_CLIPPY_FALSE}"; then as_fn_error $? "conditional \"ONLY_CLIPPY\" was never defined. Usually this means the macro was only invoked conditionally." "$LINENO" 5 fi { $as_echo "$as_me:${as_lineno-$LINENO}: checking that generated files are newer than configure" >&5 $as_echo_n "checking that generated files are newer than configure... " >&6; } if test -n "$am_sleep_pid"; then # Hide warnings about reused PIDs. wait $am_sleep_pid 2>/dev/null fi { $as_echo "$as_me:${as_lineno-$LINENO}: result: done" >&5 $as_echo "done" >&6; } if test -n "$EXEEXT"; then am__EXEEXT_TRUE= am__EXEEXT_FALSE='#' else am__EXEEXT_TRUE='#' am__EXEEXT_FALSE= fi if test -z "${PKGSRC_TRUE}" && test -z "${PKGSRC_FALSE}"; then as_fn_error $? "conditional \"PKGSRC\" was never defined. Usually this means the macro was only invoked conditionally." "$LINENO" 5 fi if test -z "${AMDEP_TRUE}" && test -z "${AMDEP_FALSE}"; then as_fn_error $? "conditional \"AMDEP\" was never defined. Usually this means the macro was only invoked conditionally." "$LINENO" 5 fi if test -z "${am__fastdepCC_TRUE}" && test -z "${am__fastdepCC_FALSE}"; then as_fn_error $? "conditional \"am__fastdepCC\" was never defined. Usually this means the macro was only invoked conditionally." "$LINENO" 5 fi if test -z "${am__fastdepCXX_TRUE}" && test -z "${am__fastdepCXX_FALSE}"; then as_fn_error $? "conditional \"am__fastdepCXX\" was never defined. Usually this means the macro was only invoked conditionally." "$LINENO" 5 fi if test -z "${DEV_BUILD_TRUE}" && test -z "${DEV_BUILD_FALSE}"; then as_fn_error $? "conditional \"DEV_BUILD\" was never defined. Usually this means the macro was only invoked conditionally." "$LINENO" 5 fi if test -z "${STATIC_BIN_TRUE}" && test -z "${STATIC_BIN_FALSE}"; then as_fn_error $? "conditional \"STATIC_BIN\" was never defined. Usually this means the macro was only invoked conditionally." "$LINENO" 5 fi if test -z "${DOC_TRUE}" && test -z "${DOC_FALSE}"; then as_fn_error $? "conditional \"DOC\" was never defined. Usually this means the macro was only invoked conditionally." "$LINENO" 5 fi if test -z "${DOC_HTML_TRUE}" && test -z "${DOC_HTML_FALSE}"; then as_fn_error $? "conditional \"DOC_HTML\" was never defined. Usually this means the macro was only invoked conditionally." "$LINENO" 5 fi if test -z "${HAVE_GCOV_TRUE}" && test -z "${HAVE_GCOV_FALSE}"; then as_fn_error $? "conditional \"HAVE_GCOV\" was never defined. Usually this means the macro was only invoked conditionally." "$LINENO" 5 fi if test -z "${GIT_VERSION_TRUE}" && test -z "${GIT_VERSION_FALSE}"; then as_fn_error $? "conditional \"GIT_VERSION\" was never defined. Usually this means the macro was only invoked conditionally." "$LINENO" 5 fi if test -z "${SOLARIS_TRUE}" && test -z "${SOLARIS_FALSE}"; then as_fn_error $? "conditional \"SOLARIS\" was never defined. Usually this means the macro was only invoked conditionally." "$LINENO" 5 fi if test -z "${CARES_TRUE}" && test -z "${CARES_FALSE}"; then as_fn_error $? "conditional \"CARES\" was never defined. Usually this means the macro was only invoked conditionally." "$LINENO" 5 fi if test -z "${SUPPORT_REALMS_TRUE}" && test -z "${SUPPORT_REALMS_FALSE}"; then as_fn_error $? "conditional \"SUPPORT_REALMS\" was never defined. Usually this means the macro was only invoked conditionally." "$LINENO" 5 fi if test -z "${ENABLE_BGP_VNC_TRUE}" && test -z "${ENABLE_BGP_VNC_FALSE}"; then as_fn_error $? "conditional \"ENABLE_BGP_VNC\" was never defined. Usually this means the macro was only invoked conditionally." "$LINENO" 5 fi if test -z "${BGP_BMP_TRUE}" && test -z "${BGP_BMP_FALSE}"; then as_fn_error $? "conditional \"BGP_BMP\" was never defined. Usually this means the macro was only invoked conditionally." "$LINENO" 5 fi if test -z "${SQLITE3_TRUE}" && test -z "${SQLITE3_FALSE}"; then as_fn_error $? "conditional \"SQLITE3\" was never defined. Usually this means the macro was only invoked conditionally." "$LINENO" 5 fi if test -z "${CONFD_TRUE}" && test -z "${CONFD_FALSE}"; then as_fn_error $? "conditional \"CONFD\" was never defined. Usually this means the macro was only invoked conditionally." "$LINENO" 5 fi if test -z "${SYSREPO_TRUE}" && test -z "${SYSREPO_FALSE}"; then as_fn_error $? "conditional \"SYSREPO\" was never defined. Usually this means the macro was only invoked conditionally." "$LINENO" 5 fi if test -z "${GRPC_TRUE}" && test -z "${GRPC_FALSE}"; then as_fn_error $? "conditional \"GRPC\" was never defined. Usually this means the macro was only invoked conditionally." "$LINENO" 5 fi if test -z "${ZEROMQ_TRUE}" && test -z "${ZEROMQ_FALSE}"; then as_fn_error $? "conditional \"ZEROMQ\" was never defined. Usually this means the macro was only invoked conditionally." "$LINENO" 5 fi if test -z "${RPKI_TRUE}" && test -z "${RPKI_FALSE}"; then as_fn_error $? "conditional \"RPKI\" was never defined. Usually this means the macro was only invoked conditionally." "$LINENO" 5 fi if test -z "${SNMP_TRUE}" && test -z "${SNMP_FALSE}"; then as_fn_error $? "conditional \"SNMP\" was never defined. Usually this means the macro was only invoked conditionally." "$LINENO" 5 fi if test -z "${IRDP_TRUE}" && test -z "${IRDP_FALSE}"; then as_fn_error $? "conditional \"IRDP\" was never defined. Usually this means the macro was only invoked conditionally." "$LINENO" 5 fi if test -z "${FPM_TRUE}" && test -z "${FPM_FALSE}"; then as_fn_error $? "conditional \"FPM\" was never defined. Usually this means the macro was only invoked conditionally." "$LINENO" 5 fi if test -z "${HAVE_PROTOBUF_TRUE}" && test -z "${HAVE_PROTOBUF_FALSE}"; then as_fn_error $? "conditional \"HAVE_PROTOBUF\" was never defined. Usually this means the macro was only invoked conditionally." "$LINENO" 5 fi if test -z "${VTYSH_TRUE}" && test -z "${VTYSH_FALSE}"; then as_fn_error $? "conditional \"VTYSH\" was never defined. Usually this means the macro was only invoked conditionally." "$LINENO" 5 fi if test -z "${ZEBRA_TRUE}" && test -z "${ZEBRA_FALSE}"; then as_fn_error $? "conditional \"ZEBRA\" was never defined. Usually this means the macro was only invoked conditionally." "$LINENO" 5 fi if test -z "${BGPD_TRUE}" && test -z "${BGPD_FALSE}"; then as_fn_error $? "conditional \"BGPD\" was never defined. Usually this means the macro was only invoked conditionally." "$LINENO" 5 fi if test -z "${RIPD_TRUE}" && test -z "${RIPD_FALSE}"; then as_fn_error $? "conditional \"RIPD\" was never defined. Usually this means the macro was only invoked conditionally." "$LINENO" 5 fi if test -z "${OSPFD_TRUE}" && test -z "${OSPFD_FALSE}"; then as_fn_error $? "conditional \"OSPFD\" was never defined. Usually this means the macro was only invoked conditionally." "$LINENO" 5 fi if test -z "${LDPD_TRUE}" && test -z "${LDPD_FALSE}"; then as_fn_error $? "conditional \"LDPD\" was never defined. Usually this means the macro was only invoked conditionally." "$LINENO" 5 fi if test -z "${BFDD_TRUE}" && test -z "${BFDD_FALSE}"; then as_fn_error $? "conditional \"BFDD\" was never defined. Usually this means the macro was only invoked conditionally." "$LINENO" 5 fi if test -z "${NHRPD_TRUE}" && test -z "${NHRPD_FALSE}"; then as_fn_error $? "conditional \"NHRPD\" was never defined. Usually this means the macro was only invoked conditionally." "$LINENO" 5 fi if test -z "${EIGRPD_TRUE}" && test -z "${EIGRPD_FALSE}"; then as_fn_error $? "conditional \"EIGRPD\" was never defined. Usually this means the macro was only invoked conditionally." "$LINENO" 5 fi if test -z "${WATCHFRR_TRUE}" && test -z "${WATCHFRR_FALSE}"; then as_fn_error $? "conditional \"WATCHFRR\" was never defined. Usually this means the macro was only invoked conditionally." "$LINENO" 5 fi if test -z "${OSPFCLIENT_TRUE}" && test -z "${OSPFCLIENT_FALSE}"; then as_fn_error $? "conditional \"OSPFCLIENT\" was never defined. Usually this means the macro was only invoked conditionally." "$LINENO" 5 fi if test -z "${RIPNGD_TRUE}" && test -z "${RIPNGD_FALSE}"; then as_fn_error $? "conditional \"RIPNGD\" was never defined. Usually this means the macro was only invoked conditionally." "$LINENO" 5 fi if test -z "${BABELD_TRUE}" && test -z "${BABELD_FALSE}"; then as_fn_error $? "conditional \"BABELD\" was never defined. Usually this means the macro was only invoked conditionally." "$LINENO" 5 fi if test -z "${OSPF6D_TRUE}" && test -z "${OSPF6D_FALSE}"; then as_fn_error $? "conditional \"OSPF6D\" was never defined. Usually this means the macro was only invoked conditionally." "$LINENO" 5 fi if test -z "${ISISD_TRUE}" && test -z "${ISISD_FALSE}"; then as_fn_error $? "conditional \"ISISD\" was never defined. Usually this means the macro was only invoked conditionally." "$LINENO" 5 fi if test -z "${PIMD_TRUE}" && test -z "${PIMD_FALSE}"; then as_fn_error $? "conditional \"PIMD\" was never defined. Usually this means the macro was only invoked conditionally." "$LINENO" 5 fi if test -z "${PBRD_TRUE}" && test -z "${PBRD_FALSE}"; then as_fn_error $? "conditional \"PBRD\" was never defined. Usually this means the macro was only invoked conditionally." "$LINENO" 5 fi if test -z "${SHARPD_TRUE}" && test -z "${SHARPD_FALSE}"; then as_fn_error $? "conditional \"SHARPD\" was never defined. Usually this means the macro was only invoked conditionally." "$LINENO" 5 fi if test -z "${STATICD_TRUE}" && test -z "${STATICD_FALSE}"; then as_fn_error $? "conditional \"STATICD\" was never defined. Usually this means the macro was only invoked conditionally." "$LINENO" 5 fi if test -z "${FABRICD_TRUE}" && test -z "${FABRICD_FALSE}"; then as_fn_error $? "conditional \"FABRICD\" was never defined. Usually this means the macro was only invoked conditionally." "$LINENO" 5 fi if test -z "${VRRPD_TRUE}" && test -z "${VRRPD_FALSE}"; then as_fn_error $? "conditional \"VRRPD\" was never defined. Usually this means the macro was only invoked conditionally." "$LINENO" 5 fi : "${CONFIG_STATUS=./config.status}" ac_write_fail=0 ac_clean_files_save=$ac_clean_files ac_clean_files="$ac_clean_files $CONFIG_STATUS" { $as_echo "$as_me:${as_lineno-$LINENO}: creating $CONFIG_STATUS" >&5 $as_echo "$as_me: creating $CONFIG_STATUS" >&6;} as_write_fail=0 cat >$CONFIG_STATUS <<_ASEOF || as_write_fail=1 #! $SHELL # Generated by $as_me. # Run this file to recreate the current configuration. # Compiler output produced by configure, useful for debugging # configure, is in config.log if it exists. debug=false ac_cs_recheck=false ac_cs_silent=false SHELL=\${CONFIG_SHELL-$SHELL} export SHELL _ASEOF cat >>$CONFIG_STATUS <<\_ASEOF || as_write_fail=1 ## -------------------- ## ## M4sh Initialization. ## ## -------------------- ## # Be more Bourne compatible DUALCASE=1; export DUALCASE # for MKS sh if test -n "${ZSH_VERSION+set}" && (emulate sh) >/dev/null 2>&1; then : emulate sh NULLCMD=: # Pre-4.2 versions of Zsh do word splitting on ${1+"$@"}, which # is contrary to our usage. Disable this feature. alias -g '${1+"$@"}'='"$@"' setopt NO_GLOB_SUBST else case `(set -o) 2>/dev/null` in #( *posix*) : set -o posix ;; #( *) : ;; esac fi as_nl=' ' export as_nl # Printing a long string crashes Solaris 7 /usr/bin/printf. as_echo='\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\' as_echo=$as_echo$as_echo$as_echo$as_echo$as_echo as_echo=$as_echo$as_echo$as_echo$as_echo$as_echo$as_echo # Prefer a ksh shell builtin over an external printf program on Solaris, # but without wasting forks for bash or zsh. if test -z "$BASH_VERSION$ZSH_VERSION" \ && (test "X`print -r -- $as_echo`" = "X$as_echo") 2>/dev/null; then as_echo='print -r --' as_echo_n='print -rn --' elif (test "X`printf %s $as_echo`" = "X$as_echo") 2>/dev/null; then as_echo='printf %s\n' as_echo_n='printf %s' else if test "X`(/usr/ucb/echo -n -n $as_echo) 2>/dev/null`" = "X-n $as_echo"; then as_echo_body='eval /usr/ucb/echo -n "$1$as_nl"' as_echo_n='/usr/ucb/echo -n' else as_echo_body='eval expr "X$1" : "X\\(.*\\)"' as_echo_n_body='eval arg=$1; case $arg in #( *"$as_nl"*) expr "X$arg" : "X\\(.*\\)$as_nl"; arg=`expr "X$arg" : ".*$as_nl\\(.*\\)"`;; esac; expr "X$arg" : "X\\(.*\\)" | tr -d "$as_nl" ' export as_echo_n_body as_echo_n='sh -c $as_echo_n_body as_echo' fi export as_echo_body as_echo='sh -c $as_echo_body as_echo' fi # The user is always right. if test "${PATH_SEPARATOR+set}" != set; then PATH_SEPARATOR=: (PATH='/bin;/bin'; FPATH=$PATH; sh -c :) >/dev/null 2>&1 && { (PATH='/bin:/bin'; FPATH=$PATH; sh -c :) >/dev/null 2>&1 || PATH_SEPARATOR=';' } fi # IFS # We need space, tab and new line, in precisely that order. Quoting is # there to prevent editors from complaining about space-tab. # (If _AS_PATH_WALK were called with IFS unset, it would disable word # splitting by setting IFS to empty value.) IFS=" "" $as_nl" # Find who we are. Look in the path if we contain no directory separator. as_myself= case $0 in #(( *[\\/]* ) as_myself=$0 ;; *) as_save_IFS=$IFS; IFS=$PATH_SEPARATOR for as_dir in $PATH do IFS=$as_save_IFS test -z "$as_dir" && as_dir=. test -r "$as_dir/$0" && as_myself=$as_dir/$0 && break done IFS=$as_save_IFS ;; esac # We did not find ourselves, most probably we were run as `sh COMMAND' # in which case we are not to be found in the path. if test "x$as_myself" = x; then as_myself=$0 fi if test ! -f "$as_myself"; then $as_echo "$as_myself: error: cannot find myself; rerun with an absolute file name" >&2 exit 1 fi # Unset variables that we do not need and which cause bugs (e.g. in # pre-3.0 UWIN ksh). But do not cause bugs in bash 2.01; the "|| exit 1" # suppresses any "Segmentation fault" message there. '((' could # trigger a bug in pdksh 5.2.14. for as_var in BASH_ENV ENV MAIL MAILPATH do eval test x\${$as_var+set} = xset \ && ( (unset $as_var) || exit 1) >/dev/null 2>&1 && unset $as_var || : done PS1='$ ' PS2='> ' PS4='+ ' # NLS nuisances. LC_ALL=C export LC_ALL LANGUAGE=C export LANGUAGE # CDPATH. (unset CDPATH) >/dev/null 2>&1 && unset CDPATH # as_fn_error STATUS ERROR [LINENO LOG_FD] # ---------------------------------------- # Output "`basename $0`: error: ERROR" to stderr. If LINENO and LOG_FD are # provided, also output the error to LOG_FD, referencing LINENO. Then exit the # script with STATUS, using 1 if that was 0. as_fn_error () { as_status=$1; test $as_status -eq 0 && as_status=1 if test "$4"; then as_lineno=${as_lineno-"$3"} as_lineno_stack=as_lineno_stack=$as_lineno_stack $as_echo "$as_me:${as_lineno-$LINENO}: error: $2" >&$4 fi $as_echo "$as_me: error: $2" >&2 as_fn_exit $as_status } # as_fn_error # as_fn_set_status STATUS # ----------------------- # Set $? to STATUS, without forking. as_fn_set_status () { return $1 } # as_fn_set_status # as_fn_exit STATUS # ----------------- # Exit the shell with STATUS, even in a "trap 0" or "set -e" context. as_fn_exit () { set +e as_fn_set_status $1 exit $1 } # as_fn_exit # as_fn_unset VAR # --------------- # Portably unset VAR. as_fn_unset () { { eval $1=; unset $1;} } as_unset=as_fn_unset # as_fn_append VAR VALUE # ---------------------- # Append the text in VALUE to the end of the definition contained in VAR. Take # advantage of any shell optimizations that allow amortized linear growth over # repeated appends, instead of the typical quadratic growth present in naive # implementations. if (eval "as_var=1; as_var+=2; test x\$as_var = x12") 2>/dev/null; then : eval 'as_fn_append () { eval $1+=\$2 }' else as_fn_append () { eval $1=\$$1\$2 } fi # as_fn_append # as_fn_arith ARG... # ------------------ # Perform arithmetic evaluation on the ARGs, and store the result in the # global $as_val. Take advantage of shells that can avoid forks. The arguments # must be portable across $(()) and expr. if (eval "test \$(( 1 + 1 )) = 2") 2>/dev/null; then : eval 'as_fn_arith () { as_val=$(( $* )) }' else as_fn_arith () { as_val=`expr "$@" || test $? -eq 1` } fi # as_fn_arith if expr a : '\(a\)' >/dev/null 2>&1 && test "X`expr 00001 : '.*\(...\)'`" = X001; then as_expr=expr else as_expr=false fi if (basename -- /) >/dev/null 2>&1 && test "X`basename -- / 2>&1`" = "X/"; then as_basename=basename else as_basename=false fi if (as_dir=`dirname -- /` && test "X$as_dir" = X/) >/dev/null 2>&1; then as_dirname=dirname else as_dirname=false fi as_me=`$as_basename -- "$0" || $as_expr X/"$0" : '.*/\([^/][^/]*\)/*$' \| \ X"$0" : 'X\(//\)$' \| \ X"$0" : 'X\(/\)' \| . 2>/dev/null || $as_echo X/"$0" | sed '/^.*\/\([^/][^/]*\)\/*$/{ s//\1/ q } /^X\/\(\/\/\)$/{ s//\1/ q } /^X\/\(\/\).*/{ s//\1/ q } s/.*/./; q'` # Avoid depending upon Character Ranges. as_cr_letters='abcdefghijklmnopqrstuvwxyz' as_cr_LETTERS='ABCDEFGHIJKLMNOPQRSTUVWXYZ' as_cr_Letters=$as_cr_letters$as_cr_LETTERS as_cr_digits='0123456789' as_cr_alnum=$as_cr_Letters$as_cr_digits ECHO_C= ECHO_N= ECHO_T= case `echo -n x` in #((((( -n*) case `echo 'xy\c'` in *c*) ECHO_T=' ';; # ECHO_T is single tab character. xy) ECHO_C='\c';; *) echo `echo ksh88 bug on AIX 6.1` > /dev/null ECHO_T=' ';; esac;; *) ECHO_N='-n';; esac rm -f conf$$ conf$$.exe conf$$.file if test -d conf$$.dir; then rm -f conf$$.dir/conf$$.file else rm -f conf$$.dir mkdir conf$$.dir 2>/dev/null fi if (echo >conf$$.file) 2>/dev/null; then if ln -s conf$$.file conf$$ 2>/dev/null; then as_ln_s='ln -s' # ... but there are two gotchas: # 1) On MSYS, both `ln -s file dir' and `ln file dir' fail. # 2) DJGPP < 2.04 has no symlinks; `ln -s' creates a wrapper executable. # In both cases, we have to default to `cp -pR'. ln -s conf$$.file conf$$.dir 2>/dev/null && test ! -f conf$$.exe || as_ln_s='cp -pR' elif ln conf$$.file conf$$ 2>/dev/null; then as_ln_s=ln else as_ln_s='cp -pR' fi else as_ln_s='cp -pR' fi rm -f conf$$ conf$$.exe conf$$.dir/conf$$.file conf$$.file rmdir conf$$.dir 2>/dev/null # as_fn_mkdir_p # ------------- # Create "$as_dir" as a directory, including parents if necessary. as_fn_mkdir_p () { case $as_dir in #( -*) as_dir=./$as_dir;; esac test -d "$as_dir" || eval $as_mkdir_p || { as_dirs= while :; do case $as_dir in #( *\'*) as_qdir=`$as_echo "$as_dir" | sed "s/'/'\\\\\\\\''/g"`;; #'( *) as_qdir=$as_dir;; esac as_dirs="'$as_qdir' $as_dirs" as_dir=`$as_dirname -- "$as_dir" || $as_expr X"$as_dir" : 'X\(.*[^/]\)//*[^/][^/]*/*$' \| \ X"$as_dir" : 'X\(//\)[^/]' \| \ X"$as_dir" : 'X\(//\)$' \| \ X"$as_dir" : 'X\(/\)' \| . 2>/dev/null || $as_echo X"$as_dir" | sed '/^X\(.*[^/]\)\/\/*[^/][^/]*\/*$/{ s//\1/ q } /^X\(\/\/\)[^/].*/{ s//\1/ q } /^X\(\/\/\)$/{ s//\1/ q } /^X\(\/\).*/{ s//\1/ q } s/.*/./; q'` test -d "$as_dir" && break done test -z "$as_dirs" || eval "mkdir $as_dirs" } || test -d "$as_dir" || as_fn_error $? "cannot create directory $as_dir" } # as_fn_mkdir_p if mkdir -p . 2>/dev/null; then as_mkdir_p='mkdir -p "$as_dir"' else test -d ./-p && rmdir ./-p as_mkdir_p=false fi # as_fn_executable_p FILE # ----------------------- # Test if FILE is an executable regular file. as_fn_executable_p () { test -f "$1" && test -x "$1" } # as_fn_executable_p as_test_x='test -x' as_executable_p=as_fn_executable_p # Sed expression to map a string onto a valid CPP name. as_tr_cpp="eval sed 'y%*$as_cr_letters%P$as_cr_LETTERS%;s%[^_$as_cr_alnum]%_%g'" # Sed expression to map a string onto a valid variable name. as_tr_sh="eval sed 'y%*+%pp%;s%[^_$as_cr_alnum]%_%g'" exec 6>&1 ## ----------------------------------- ## ## Main body of $CONFIG_STATUS script. ## ## ----------------------------------- ## _ASEOF test $as_write_fail = 0 && chmod +x $CONFIG_STATUS || ac_write_fail=1 cat >>$CONFIG_STATUS <<\_ACEOF || ac_write_fail=1 # Save the log message, to keep $0 and so on meaningful, and to # report actual input values of CONFIG_FILES etc. instead of their # values after options handling. ac_log=" This file was extended by frr $as_me 7.2.1, which was generated by GNU Autoconf 2.69. Invocation command line was CONFIG_FILES = $CONFIG_FILES CONFIG_HEADERS = $CONFIG_HEADERS CONFIG_LINKS = $CONFIG_LINKS CONFIG_COMMANDS = $CONFIG_COMMANDS $ $0 $@ on `(hostname || uname -n) 2>/dev/null | sed 1q` " _ACEOF case $ac_config_files in *" "*) set x $ac_config_files; shift; ac_config_files=$*;; esac case $ac_config_headers in *" "*) set x $ac_config_headers; shift; ac_config_headers=$*;; esac cat >>$CONFIG_STATUS <<_ACEOF || ac_write_fail=1 # Files that config.status was made for. config_files="$ac_config_files" config_headers="$ac_config_headers" config_commands="$ac_config_commands" _ACEOF cat >>$CONFIG_STATUS <<\_ACEOF || ac_write_fail=1 ac_cs_usage="\ \`$as_me' instantiates files and other configuration actions from templates according to the current configuration. Unless the files and actions are specified as TAGs, all are instantiated by default. Usage: $0 [OPTION]... [TAG]... -h, --help print this help, then exit -V, --version print version number and configuration settings, then exit --config print configuration, then exit -q, --quiet, --silent do not print progress messages -d, --debug don't remove temporary files --recheck update $as_me by reconfiguring in the same conditions --file=FILE[:TEMPLATE] instantiate the configuration file FILE --header=FILE[:TEMPLATE] instantiate the configuration header FILE Configuration files: $config_files Configuration headers: $config_headers Configuration commands: $config_commands Report bugs to ." _ACEOF cat >>$CONFIG_STATUS <<_ACEOF || ac_write_fail=1 ac_cs_config="`$as_echo "$ac_configure_args" | sed 's/^ //; s/[\\""\`\$]/\\\\&/g'`" ac_cs_version="\\ frr config.status 7.2.1 configured by $0, generated by GNU Autoconf 2.69, with options \\"\$ac_cs_config\\" Copyright (C) 2012 Free Software Foundation, Inc. This config.status script is free software; the Free Software Foundation gives unlimited permission to copy, distribute and modify it." ac_pwd='$ac_pwd' srcdir='$srcdir' INSTALL='$INSTALL' MKDIR_P='$MKDIR_P' AWK='$AWK' test -n "\$AWK" || AWK=awk _ACEOF cat >>$CONFIG_STATUS <<\_ACEOF || ac_write_fail=1 # The default lists apply if the user does not specify any file. ac_need_defaults=: while test $# != 0 do case $1 in --*=?*) ac_option=`expr "X$1" : 'X\([^=]*\)='` ac_optarg=`expr "X$1" : 'X[^=]*=\(.*\)'` ac_shift=: ;; --*=) ac_option=`expr "X$1" : 'X\([^=]*\)='` ac_optarg= ac_shift=: ;; *) ac_option=$1 ac_optarg=$2 ac_shift=shift ;; esac case $ac_option in # Handling of the options. -recheck | --recheck | --rechec | --reche | --rech | --rec | --re | --r) ac_cs_recheck=: ;; --version | --versio | --versi | --vers | --ver | --ve | --v | -V ) $as_echo "$ac_cs_version"; exit ;; --config | --confi | --conf | --con | --co | --c ) $as_echo "$ac_cs_config"; exit ;; --debug | --debu | --deb | --de | --d | -d ) debug=: ;; --file | --fil | --fi | --f ) $ac_shift case $ac_optarg in *\'*) ac_optarg=`$as_echo "$ac_optarg" | sed "s/'/'\\\\\\\\''/g"` ;; '') as_fn_error $? "missing file argument" ;; esac as_fn_append CONFIG_FILES " '$ac_optarg'" ac_need_defaults=false;; --header | --heade | --head | --hea ) $ac_shift case $ac_optarg in *\'*) ac_optarg=`$as_echo "$ac_optarg" | sed "s/'/'\\\\\\\\''/g"` ;; esac as_fn_append CONFIG_HEADERS " '$ac_optarg'" ac_need_defaults=false;; --he | --h) # Conflict between --help and --header as_fn_error $? "ambiguous option: \`$1' Try \`$0 --help' for more information.";; --help | --hel | -h ) $as_echo "$ac_cs_usage"; exit ;; -q | -quiet | --quiet | --quie | --qui | --qu | --q \ | -silent | --silent | --silen | --sile | --sil | --si | --s) ac_cs_silent=: ;; # This is an error. -*) as_fn_error $? "unrecognized option: \`$1' Try \`$0 --help' for more information." ;; *) as_fn_append ac_config_targets " $1" ac_need_defaults=false ;; esac shift done ac_configure_extra_args= if $ac_cs_silent; then exec 6>/dev/null ac_configure_extra_args="$ac_configure_extra_args --silent" fi _ACEOF cat >>$CONFIG_STATUS <<_ACEOF || ac_write_fail=1 if \$ac_cs_recheck; then set X $SHELL '$0' $ac_configure_args \$ac_configure_extra_args --no-create --no-recursion shift \$as_echo "running CONFIG_SHELL=$SHELL \$*" >&6 CONFIG_SHELL='$SHELL' export CONFIG_SHELL exec "\$@" fi _ACEOF cat >>$CONFIG_STATUS <<\_ACEOF || ac_write_fail=1 exec 5>>config.log { echo sed 'h;s/./-/g;s/^.../## /;s/...$/ ##/;p;x;p;x' <<_ASBOX ## Running $as_me. ## _ASBOX $as_echo "$ac_log" } >&5 _ACEOF cat >>$CONFIG_STATUS <<_ACEOF || ac_write_fail=1 # # INIT-COMMANDS # AMDEP_TRUE="$AMDEP_TRUE" MAKE="${MAKE-make}" # The HP-UX ksh and POSIX shell print the target directory to stdout # if CDPATH is set. (unset CDPATH) >/dev/null 2>&1 && unset CDPATH sed_quote_subst='$sed_quote_subst' double_quote_subst='$double_quote_subst' delay_variable_subst='$delay_variable_subst' macro_version='`$ECHO "$macro_version" | $SED "$delay_single_quote_subst"`' macro_revision='`$ECHO "$macro_revision" | $SED "$delay_single_quote_subst"`' enable_shared='`$ECHO "$enable_shared" | $SED "$delay_single_quote_subst"`' enable_static='`$ECHO "$enable_static" | $SED "$delay_single_quote_subst"`' pic_mode='`$ECHO "$pic_mode" | $SED "$delay_single_quote_subst"`' enable_fast_install='`$ECHO "$enable_fast_install" | $SED "$delay_single_quote_subst"`' shared_archive_member_spec='`$ECHO "$shared_archive_member_spec" | $SED "$delay_single_quote_subst"`' SHELL='`$ECHO "$SHELL" | $SED "$delay_single_quote_subst"`' ECHO='`$ECHO "$ECHO" | $SED "$delay_single_quote_subst"`' PATH_SEPARATOR='`$ECHO "$PATH_SEPARATOR" | $SED "$delay_single_quote_subst"`' host_alias='`$ECHO "$host_alias" | $SED "$delay_single_quote_subst"`' host='`$ECHO "$host" | $SED "$delay_single_quote_subst"`' host_os='`$ECHO "$host_os" | $SED "$delay_single_quote_subst"`' build_alias='`$ECHO "$build_alias" | $SED "$delay_single_quote_subst"`' build='`$ECHO "$build" | $SED "$delay_single_quote_subst"`' build_os='`$ECHO "$build_os" | $SED "$delay_single_quote_subst"`' SED='`$ECHO "$SED" | $SED "$delay_single_quote_subst"`' Xsed='`$ECHO "$Xsed" | $SED "$delay_single_quote_subst"`' GREP='`$ECHO "$GREP" | $SED "$delay_single_quote_subst"`' EGREP='`$ECHO "$EGREP" | $SED "$delay_single_quote_subst"`' FGREP='`$ECHO "$FGREP" | $SED "$delay_single_quote_subst"`' LD='`$ECHO "$LD" | $SED "$delay_single_quote_subst"`' NM='`$ECHO "$NM" | $SED "$delay_single_quote_subst"`' LN_S='`$ECHO "$LN_S" | $SED "$delay_single_quote_subst"`' max_cmd_len='`$ECHO "$max_cmd_len" | $SED "$delay_single_quote_subst"`' ac_objext='`$ECHO "$ac_objext" | $SED "$delay_single_quote_subst"`' exeext='`$ECHO "$exeext" | $SED "$delay_single_quote_subst"`' lt_unset='`$ECHO "$lt_unset" | $SED "$delay_single_quote_subst"`' lt_SP2NL='`$ECHO "$lt_SP2NL" | $SED "$delay_single_quote_subst"`' lt_NL2SP='`$ECHO "$lt_NL2SP" | $SED "$delay_single_quote_subst"`' lt_cv_to_host_file_cmd='`$ECHO "$lt_cv_to_host_file_cmd" | $SED "$delay_single_quote_subst"`' lt_cv_to_tool_file_cmd='`$ECHO "$lt_cv_to_tool_file_cmd" | $SED "$delay_single_quote_subst"`' reload_flag='`$ECHO "$reload_flag" | $SED "$delay_single_quote_subst"`' reload_cmds='`$ECHO "$reload_cmds" | $SED "$delay_single_quote_subst"`' OBJDUMP='`$ECHO "$OBJDUMP" | $SED "$delay_single_quote_subst"`' deplibs_check_method='`$ECHO "$deplibs_check_method" | $SED "$delay_single_quote_subst"`' file_magic_cmd='`$ECHO "$file_magic_cmd" | $SED "$delay_single_quote_subst"`' file_magic_glob='`$ECHO "$file_magic_glob" | $SED "$delay_single_quote_subst"`' want_nocaseglob='`$ECHO "$want_nocaseglob" | $SED "$delay_single_quote_subst"`' DLLTOOL='`$ECHO "$DLLTOOL" | $SED "$delay_single_quote_subst"`' sharedlib_from_linklib_cmd='`$ECHO "$sharedlib_from_linklib_cmd" | $SED "$delay_single_quote_subst"`' AR='`$ECHO "$AR" | $SED "$delay_single_quote_subst"`' AR_FLAGS='`$ECHO "$AR_FLAGS" | $SED "$delay_single_quote_subst"`' archiver_list_spec='`$ECHO "$archiver_list_spec" | $SED "$delay_single_quote_subst"`' STRIP='`$ECHO "$STRIP" | $SED "$delay_single_quote_subst"`' RANLIB='`$ECHO "$RANLIB" | $SED "$delay_single_quote_subst"`' old_postinstall_cmds='`$ECHO "$old_postinstall_cmds" | $SED "$delay_single_quote_subst"`' old_postuninstall_cmds='`$ECHO "$old_postuninstall_cmds" | $SED "$delay_single_quote_subst"`' old_archive_cmds='`$ECHO "$old_archive_cmds" | $SED "$delay_single_quote_subst"`' lock_old_archive_extraction='`$ECHO "$lock_old_archive_extraction" | $SED "$delay_single_quote_subst"`' CC='`$ECHO "$CC" | $SED "$delay_single_quote_subst"`' CFLAGS='`$ECHO "$CFLAGS" | $SED "$delay_single_quote_subst"`' compiler='`$ECHO "$compiler" | $SED "$delay_single_quote_subst"`' GCC='`$ECHO "$GCC" | $SED "$delay_single_quote_subst"`' lt_cv_sys_global_symbol_pipe='`$ECHO "$lt_cv_sys_global_symbol_pipe" | $SED "$delay_single_quote_subst"`' lt_cv_sys_global_symbol_to_cdecl='`$ECHO "$lt_cv_sys_global_symbol_to_cdecl" | $SED "$delay_single_quote_subst"`' lt_cv_sys_global_symbol_to_import='`$ECHO "$lt_cv_sys_global_symbol_to_import" | $SED "$delay_single_quote_subst"`' lt_cv_sys_global_symbol_to_c_name_address='`$ECHO "$lt_cv_sys_global_symbol_to_c_name_address" | $SED "$delay_single_quote_subst"`' lt_cv_sys_global_symbol_to_c_name_address_lib_prefix='`$ECHO "$lt_cv_sys_global_symbol_to_c_name_address_lib_prefix" | $SED "$delay_single_quote_subst"`' lt_cv_nm_interface='`$ECHO "$lt_cv_nm_interface" | $SED "$delay_single_quote_subst"`' nm_file_list_spec='`$ECHO "$nm_file_list_spec" | $SED "$delay_single_quote_subst"`' lt_sysroot='`$ECHO "$lt_sysroot" | $SED "$delay_single_quote_subst"`' lt_cv_truncate_bin='`$ECHO "$lt_cv_truncate_bin" | $SED "$delay_single_quote_subst"`' objdir='`$ECHO "$objdir" | $SED "$delay_single_quote_subst"`' MAGIC_CMD='`$ECHO "$MAGIC_CMD" | $SED "$delay_single_quote_subst"`' lt_prog_compiler_no_builtin_flag='`$ECHO "$lt_prog_compiler_no_builtin_flag" | $SED "$delay_single_quote_subst"`' lt_prog_compiler_pic='`$ECHO "$lt_prog_compiler_pic" | $SED "$delay_single_quote_subst"`' lt_prog_compiler_wl='`$ECHO "$lt_prog_compiler_wl" | $SED "$delay_single_quote_subst"`' lt_prog_compiler_static='`$ECHO "$lt_prog_compiler_static" | $SED "$delay_single_quote_subst"`' lt_cv_prog_compiler_c_o='`$ECHO "$lt_cv_prog_compiler_c_o" | $SED "$delay_single_quote_subst"`' need_locks='`$ECHO "$need_locks" | $SED "$delay_single_quote_subst"`' MANIFEST_TOOL='`$ECHO "$MANIFEST_TOOL" | $SED "$delay_single_quote_subst"`' DSYMUTIL='`$ECHO "$DSYMUTIL" | $SED "$delay_single_quote_subst"`' NMEDIT='`$ECHO "$NMEDIT" | $SED "$delay_single_quote_subst"`' LIPO='`$ECHO "$LIPO" | $SED "$delay_single_quote_subst"`' OTOOL='`$ECHO "$OTOOL" | $SED "$delay_single_quote_subst"`' OTOOL64='`$ECHO "$OTOOL64" | $SED "$delay_single_quote_subst"`' libext='`$ECHO "$libext" | $SED "$delay_single_quote_subst"`' shrext_cmds='`$ECHO "$shrext_cmds" | $SED "$delay_single_quote_subst"`' extract_expsyms_cmds='`$ECHO "$extract_expsyms_cmds" | $SED "$delay_single_quote_subst"`' archive_cmds_need_lc='`$ECHO "$archive_cmds_need_lc" | $SED "$delay_single_quote_subst"`' enable_shared_with_static_runtimes='`$ECHO "$enable_shared_with_static_runtimes" | $SED "$delay_single_quote_subst"`' export_dynamic_flag_spec='`$ECHO "$export_dynamic_flag_spec" | $SED "$delay_single_quote_subst"`' whole_archive_flag_spec='`$ECHO "$whole_archive_flag_spec" | $SED "$delay_single_quote_subst"`' compiler_needs_object='`$ECHO "$compiler_needs_object" | $SED "$delay_single_quote_subst"`' old_archive_from_new_cmds='`$ECHO "$old_archive_from_new_cmds" | $SED "$delay_single_quote_subst"`' old_archive_from_expsyms_cmds='`$ECHO "$old_archive_from_expsyms_cmds" | $SED "$delay_single_quote_subst"`' archive_cmds='`$ECHO "$archive_cmds" | $SED "$delay_single_quote_subst"`' archive_expsym_cmds='`$ECHO "$archive_expsym_cmds" | $SED "$delay_single_quote_subst"`' module_cmds='`$ECHO "$module_cmds" | $SED "$delay_single_quote_subst"`' module_expsym_cmds='`$ECHO "$module_expsym_cmds" | $SED "$delay_single_quote_subst"`' with_gnu_ld='`$ECHO "$with_gnu_ld" | $SED "$delay_single_quote_subst"`' allow_undefined_flag='`$ECHO "$allow_undefined_flag" | $SED "$delay_single_quote_subst"`' no_undefined_flag='`$ECHO "$no_undefined_flag" | $SED "$delay_single_quote_subst"`' hardcode_libdir_flag_spec='`$ECHO "$hardcode_libdir_flag_spec" | $SED "$delay_single_quote_subst"`' hardcode_libdir_separator='`$ECHO "$hardcode_libdir_separator" | $SED "$delay_single_quote_subst"`' hardcode_direct='`$ECHO "$hardcode_direct" | $SED "$delay_single_quote_subst"`' hardcode_direct_absolute='`$ECHO "$hardcode_direct_absolute" | $SED "$delay_single_quote_subst"`' hardcode_minus_L='`$ECHO "$hardcode_minus_L" | $SED "$delay_single_quote_subst"`' hardcode_shlibpath_var='`$ECHO "$hardcode_shlibpath_var" | $SED "$delay_single_quote_subst"`' hardcode_automatic='`$ECHO "$hardcode_automatic" | $SED "$delay_single_quote_subst"`' inherit_rpath='`$ECHO "$inherit_rpath" | $SED "$delay_single_quote_subst"`' link_all_deplibs='`$ECHO "$link_all_deplibs" | $SED "$delay_single_quote_subst"`' always_export_symbols='`$ECHO "$always_export_symbols" | $SED "$delay_single_quote_subst"`' export_symbols_cmds='`$ECHO "$export_symbols_cmds" | $SED "$delay_single_quote_subst"`' exclude_expsyms='`$ECHO "$exclude_expsyms" | $SED "$delay_single_quote_subst"`' include_expsyms='`$ECHO "$include_expsyms" | $SED "$delay_single_quote_subst"`' prelink_cmds='`$ECHO "$prelink_cmds" | $SED "$delay_single_quote_subst"`' postlink_cmds='`$ECHO "$postlink_cmds" | $SED "$delay_single_quote_subst"`' file_list_spec='`$ECHO "$file_list_spec" | $SED "$delay_single_quote_subst"`' variables_saved_for_relink='`$ECHO "$variables_saved_for_relink" | $SED "$delay_single_quote_subst"`' need_lib_prefix='`$ECHO "$need_lib_prefix" | $SED "$delay_single_quote_subst"`' need_version='`$ECHO "$need_version" | $SED "$delay_single_quote_subst"`' version_type='`$ECHO "$version_type" | $SED "$delay_single_quote_subst"`' runpath_var='`$ECHO "$runpath_var" | $SED "$delay_single_quote_subst"`' shlibpath_var='`$ECHO "$shlibpath_var" | $SED "$delay_single_quote_subst"`' shlibpath_overrides_runpath='`$ECHO "$shlibpath_overrides_runpath" | $SED "$delay_single_quote_subst"`' libname_spec='`$ECHO "$libname_spec" | $SED "$delay_single_quote_subst"`' library_names_spec='`$ECHO "$library_names_spec" | $SED "$delay_single_quote_subst"`' soname_spec='`$ECHO "$soname_spec" | $SED "$delay_single_quote_subst"`' install_override_mode='`$ECHO "$install_override_mode" | $SED "$delay_single_quote_subst"`' postinstall_cmds='`$ECHO "$postinstall_cmds" | $SED "$delay_single_quote_subst"`' postuninstall_cmds='`$ECHO "$postuninstall_cmds" | $SED "$delay_single_quote_subst"`' finish_cmds='`$ECHO "$finish_cmds" | $SED "$delay_single_quote_subst"`' finish_eval='`$ECHO "$finish_eval" | $SED "$delay_single_quote_subst"`' hardcode_into_libs='`$ECHO "$hardcode_into_libs" | $SED "$delay_single_quote_subst"`' sys_lib_search_path_spec='`$ECHO "$sys_lib_search_path_spec" | $SED "$delay_single_quote_subst"`' configure_time_dlsearch_path='`$ECHO "$configure_time_dlsearch_path" | $SED "$delay_single_quote_subst"`' configure_time_lt_sys_library_path='`$ECHO "$configure_time_lt_sys_library_path" | $SED "$delay_single_quote_subst"`' hardcode_action='`$ECHO "$hardcode_action" | $SED "$delay_single_quote_subst"`' enable_dlopen='`$ECHO "$enable_dlopen" | $SED "$delay_single_quote_subst"`' enable_dlopen_self='`$ECHO "$enable_dlopen_self" | $SED "$delay_single_quote_subst"`' enable_dlopen_self_static='`$ECHO "$enable_dlopen_self_static" | $SED "$delay_single_quote_subst"`' old_striplib='`$ECHO "$old_striplib" | $SED "$delay_single_quote_subst"`' striplib='`$ECHO "$striplib" | $SED "$delay_single_quote_subst"`' compiler_lib_search_dirs='`$ECHO "$compiler_lib_search_dirs" | $SED "$delay_single_quote_subst"`' predep_objects='`$ECHO "$predep_objects" | $SED "$delay_single_quote_subst"`' postdep_objects='`$ECHO "$postdep_objects" | $SED "$delay_single_quote_subst"`' predeps='`$ECHO "$predeps" | $SED "$delay_single_quote_subst"`' postdeps='`$ECHO "$postdeps" | $SED "$delay_single_quote_subst"`' compiler_lib_search_path='`$ECHO "$compiler_lib_search_path" | $SED "$delay_single_quote_subst"`' LD_CXX='`$ECHO "$LD_CXX" | $SED "$delay_single_quote_subst"`' reload_flag_CXX='`$ECHO "$reload_flag_CXX" | $SED "$delay_single_quote_subst"`' reload_cmds_CXX='`$ECHO "$reload_cmds_CXX" | $SED "$delay_single_quote_subst"`' old_archive_cmds_CXX='`$ECHO "$old_archive_cmds_CXX" | $SED "$delay_single_quote_subst"`' compiler_CXX='`$ECHO "$compiler_CXX" | $SED "$delay_single_quote_subst"`' GCC_CXX='`$ECHO "$GCC_CXX" | $SED "$delay_single_quote_subst"`' lt_prog_compiler_no_builtin_flag_CXX='`$ECHO "$lt_prog_compiler_no_builtin_flag_CXX" | $SED "$delay_single_quote_subst"`' lt_prog_compiler_pic_CXX='`$ECHO "$lt_prog_compiler_pic_CXX" | $SED "$delay_single_quote_subst"`' lt_prog_compiler_wl_CXX='`$ECHO "$lt_prog_compiler_wl_CXX" | $SED "$delay_single_quote_subst"`' lt_prog_compiler_static_CXX='`$ECHO "$lt_prog_compiler_static_CXX" | $SED "$delay_single_quote_subst"`' lt_cv_prog_compiler_c_o_CXX='`$ECHO "$lt_cv_prog_compiler_c_o_CXX" | $SED "$delay_single_quote_subst"`' archive_cmds_need_lc_CXX='`$ECHO "$archive_cmds_need_lc_CXX" | $SED "$delay_single_quote_subst"`' enable_shared_with_static_runtimes_CXX='`$ECHO "$enable_shared_with_static_runtimes_CXX" | $SED "$delay_single_quote_subst"`' export_dynamic_flag_spec_CXX='`$ECHO "$export_dynamic_flag_spec_CXX" | $SED "$delay_single_quote_subst"`' whole_archive_flag_spec_CXX='`$ECHO "$whole_archive_flag_spec_CXX" | $SED "$delay_single_quote_subst"`' compiler_needs_object_CXX='`$ECHO "$compiler_needs_object_CXX" | $SED "$delay_single_quote_subst"`' old_archive_from_new_cmds_CXX='`$ECHO "$old_archive_from_new_cmds_CXX" | $SED "$delay_single_quote_subst"`' old_archive_from_expsyms_cmds_CXX='`$ECHO "$old_archive_from_expsyms_cmds_CXX" | $SED "$delay_single_quote_subst"`' archive_cmds_CXX='`$ECHO "$archive_cmds_CXX" | $SED "$delay_single_quote_subst"`' archive_expsym_cmds_CXX='`$ECHO "$archive_expsym_cmds_CXX" | $SED "$delay_single_quote_subst"`' module_cmds_CXX='`$ECHO "$module_cmds_CXX" | $SED "$delay_single_quote_subst"`' module_expsym_cmds_CXX='`$ECHO "$module_expsym_cmds_CXX" | $SED "$delay_single_quote_subst"`' with_gnu_ld_CXX='`$ECHO "$with_gnu_ld_CXX" | $SED "$delay_single_quote_subst"`' allow_undefined_flag_CXX='`$ECHO "$allow_undefined_flag_CXX" | $SED "$delay_single_quote_subst"`' no_undefined_flag_CXX='`$ECHO "$no_undefined_flag_CXX" | $SED "$delay_single_quote_subst"`' hardcode_libdir_flag_spec_CXX='`$ECHO "$hardcode_libdir_flag_spec_CXX" | $SED "$delay_single_quote_subst"`' hardcode_libdir_separator_CXX='`$ECHO "$hardcode_libdir_separator_CXX" | $SED "$delay_single_quote_subst"`' hardcode_direct_CXX='`$ECHO "$hardcode_direct_CXX" | $SED "$delay_single_quote_subst"`' hardcode_direct_absolute_CXX='`$ECHO "$hardcode_direct_absolute_CXX" | $SED "$delay_single_quote_subst"`' hardcode_minus_L_CXX='`$ECHO "$hardcode_minus_L_CXX" | $SED "$delay_single_quote_subst"`' hardcode_shlibpath_var_CXX='`$ECHO "$hardcode_shlibpath_var_CXX" | $SED "$delay_single_quote_subst"`' hardcode_automatic_CXX='`$ECHO "$hardcode_automatic_CXX" | $SED "$delay_single_quote_subst"`' inherit_rpath_CXX='`$ECHO "$inherit_rpath_CXX" | $SED "$delay_single_quote_subst"`' link_all_deplibs_CXX='`$ECHO "$link_all_deplibs_CXX" | $SED "$delay_single_quote_subst"`' always_export_symbols_CXX='`$ECHO "$always_export_symbols_CXX" | $SED "$delay_single_quote_subst"`' export_symbols_cmds_CXX='`$ECHO "$export_symbols_cmds_CXX" | $SED "$delay_single_quote_subst"`' exclude_expsyms_CXX='`$ECHO "$exclude_expsyms_CXX" | $SED "$delay_single_quote_subst"`' include_expsyms_CXX='`$ECHO "$include_expsyms_CXX" | $SED "$delay_single_quote_subst"`' prelink_cmds_CXX='`$ECHO "$prelink_cmds_CXX" | $SED "$delay_single_quote_subst"`' postlink_cmds_CXX='`$ECHO "$postlink_cmds_CXX" | $SED "$delay_single_quote_subst"`' file_list_spec_CXX='`$ECHO "$file_list_spec_CXX" | $SED "$delay_single_quote_subst"`' hardcode_action_CXX='`$ECHO "$hardcode_action_CXX" | $SED "$delay_single_quote_subst"`' compiler_lib_search_dirs_CXX='`$ECHO "$compiler_lib_search_dirs_CXX" | $SED "$delay_single_quote_subst"`' predep_objects_CXX='`$ECHO "$predep_objects_CXX" | $SED "$delay_single_quote_subst"`' postdep_objects_CXX='`$ECHO "$postdep_objects_CXX" | $SED "$delay_single_quote_subst"`' predeps_CXX='`$ECHO "$predeps_CXX" | $SED "$delay_single_quote_subst"`' postdeps_CXX='`$ECHO "$postdeps_CXX" | $SED "$delay_single_quote_subst"`' compiler_lib_search_path_CXX='`$ECHO "$compiler_lib_search_path_CXX" | $SED "$delay_single_quote_subst"`' LTCC='$LTCC' LTCFLAGS='$LTCFLAGS' compiler='$compiler_DEFAULT' # A function that is used when there is no print builtin or printf. func_fallback_echo () { eval 'cat <<_LTECHO_EOF \$1 _LTECHO_EOF' } # Quote evaled strings. for var in SHELL \ ECHO \ PATH_SEPARATOR \ SED \ GREP \ EGREP \ FGREP \ LD \ NM \ LN_S \ lt_SP2NL \ lt_NL2SP \ reload_flag \ OBJDUMP \ deplibs_check_method \ file_magic_cmd \ file_magic_glob \ want_nocaseglob \ DLLTOOL \ sharedlib_from_linklib_cmd \ AR \ AR_FLAGS \ archiver_list_spec \ STRIP \ RANLIB \ CC \ CFLAGS \ compiler \ lt_cv_sys_global_symbol_pipe \ lt_cv_sys_global_symbol_to_cdecl \ lt_cv_sys_global_symbol_to_import \ lt_cv_sys_global_symbol_to_c_name_address \ lt_cv_sys_global_symbol_to_c_name_address_lib_prefix \ lt_cv_nm_interface \ nm_file_list_spec \ lt_cv_truncate_bin \ lt_prog_compiler_no_builtin_flag \ lt_prog_compiler_pic \ lt_prog_compiler_wl \ lt_prog_compiler_static \ lt_cv_prog_compiler_c_o \ need_locks \ MANIFEST_TOOL \ DSYMUTIL \ NMEDIT \ LIPO \ OTOOL \ OTOOL64 \ shrext_cmds \ export_dynamic_flag_spec \ whole_archive_flag_spec \ compiler_needs_object \ with_gnu_ld \ allow_undefined_flag \ no_undefined_flag \ hardcode_libdir_flag_spec \ hardcode_libdir_separator \ exclude_expsyms \ include_expsyms \ file_list_spec \ variables_saved_for_relink \ libname_spec \ library_names_spec \ soname_spec \ install_override_mode \ finish_eval \ old_striplib \ striplib \ compiler_lib_search_dirs \ predep_objects \ postdep_objects \ predeps \ postdeps \ compiler_lib_search_path \ LD_CXX \ reload_flag_CXX \ compiler_CXX \ lt_prog_compiler_no_builtin_flag_CXX \ lt_prog_compiler_pic_CXX \ lt_prog_compiler_wl_CXX \ lt_prog_compiler_static_CXX \ lt_cv_prog_compiler_c_o_CXX \ export_dynamic_flag_spec_CXX \ whole_archive_flag_spec_CXX \ compiler_needs_object_CXX \ with_gnu_ld_CXX \ allow_undefined_flag_CXX \ no_undefined_flag_CXX \ hardcode_libdir_flag_spec_CXX \ hardcode_libdir_separator_CXX \ exclude_expsyms_CXX \ include_expsyms_CXX \ file_list_spec_CXX \ compiler_lib_search_dirs_CXX \ predep_objects_CXX \ postdep_objects_CXX \ predeps_CXX \ postdeps_CXX \ compiler_lib_search_path_CXX; do case \`eval \\\\\$ECHO \\\\""\\\\\$\$var"\\\\"\` in *[\\\\\\\`\\"\\\$]*) eval "lt_\$var=\\\\\\"\\\`\\\$ECHO \\"\\\$\$var\\" | \\\$SED \\"\\\$sed_quote_subst\\"\\\`\\\\\\"" ## exclude from sc_prohibit_nested_quotes ;; *) eval "lt_\$var=\\\\\\"\\\$\$var\\\\\\"" ;; esac done # Double-quote double-evaled strings. for var in reload_cmds \ old_postinstall_cmds \ old_postuninstall_cmds \ old_archive_cmds \ extract_expsyms_cmds \ old_archive_from_new_cmds \ old_archive_from_expsyms_cmds \ archive_cmds \ archive_expsym_cmds \ module_cmds \ module_expsym_cmds \ export_symbols_cmds \ prelink_cmds \ postlink_cmds \ postinstall_cmds \ postuninstall_cmds \ finish_cmds \ sys_lib_search_path_spec \ configure_time_dlsearch_path \ configure_time_lt_sys_library_path \ reload_cmds_CXX \ old_archive_cmds_CXX \ old_archive_from_new_cmds_CXX \ old_archive_from_expsyms_cmds_CXX \ archive_cmds_CXX \ archive_expsym_cmds_CXX \ module_cmds_CXX \ module_expsym_cmds_CXX \ export_symbols_cmds_CXX \ prelink_cmds_CXX \ postlink_cmds_CXX; do case \`eval \\\\\$ECHO \\\\""\\\\\$\$var"\\\\"\` in *[\\\\\\\`\\"\\\$]*) eval "lt_\$var=\\\\\\"\\\`\\\$ECHO \\"\\\$\$var\\" | \\\$SED -e \\"\\\$double_quote_subst\\" -e \\"\\\$sed_quote_subst\\" -e \\"\\\$delay_variable_subst\\"\\\`\\\\\\"" ## exclude from sc_prohibit_nested_quotes ;; *) eval "lt_\$var=\\\\\\"\\\$\$var\\\\\\"" ;; esac done ac_aux_dir='$ac_aux_dir' # See if we are running on zsh, and set the options that allow our # commands through without removal of \ escapes INIT. if test -n "\${ZSH_VERSION+set}"; then setopt NO_GLOB_SUBST fi PACKAGE='$PACKAGE' VERSION='$VERSION' RM='$RM' ofile='$ofile' PERL="$PERL" PERL="$PERL" _ACEOF cat >>$CONFIG_STATUS <<\_ACEOF || ac_write_fail=1 # Handling of arguments. for ac_config_target in $ac_config_targets do case $ac_config_target in "config.h") CONFIG_HEADERS="$CONFIG_HEADERS config.h" ;; "depfiles") CONFIG_COMMANDS="$CONFIG_COMMANDS depfiles" ;; "libtool") CONFIG_COMMANDS="$CONFIG_COMMANDS libtool" ;; "Makefile") CONFIG_FILES="$CONFIG_FILES Makefile" ;; "config.version") CONFIG_FILES="$CONFIG_FILES config.version" ;; "changelog-auto") CONFIG_FILES="$CONFIG_FILES changelog-auto" ;; "redhat/frr.spec") CONFIG_FILES="$CONFIG_FILES redhat/frr.spec" ;; "solaris/Makefile") CONFIG_FILES="$CONFIG_FILES solaris/Makefile" ;; "alpine/APKBUILD") CONFIG_FILES="$CONFIG_FILES alpine/APKBUILD" ;; "snapcraft/snapcraft.yaml") CONFIG_FILES="$CONFIG_FILES snapcraft/snapcraft.yaml" ;; "lib/version.h") CONFIG_FILES="$CONFIG_FILES lib/version.h" ;; "tests/lib/cli/test_cli.refout") CONFIG_FILES="$CONFIG_FILES tests/lib/cli/test_cli.refout" ;; "pkgsrc/bgpd.sh") CONFIG_FILES="$CONFIG_FILES pkgsrc/bgpd.sh" ;; "pkgsrc/ospf6d.sh") CONFIG_FILES="$CONFIG_FILES pkgsrc/ospf6d.sh" ;; "pkgsrc/ospfd.sh") CONFIG_FILES="$CONFIG_FILES pkgsrc/ospfd.sh" ;; "pkgsrc/ripd.sh") CONFIG_FILES="$CONFIG_FILES pkgsrc/ripd.sh" ;; "pkgsrc/ripngd.sh") CONFIG_FILES="$CONFIG_FILES pkgsrc/ripngd.sh" ;; "pkgsrc/zebra.sh") CONFIG_FILES="$CONFIG_FILES pkgsrc/zebra.sh" ;; "pkgsrc/eigrpd.sh") CONFIG_FILES="$CONFIG_FILES pkgsrc/eigrpd.sh" ;; "vtysh/extract.pl") CONFIG_FILES="$CONFIG_FILES vtysh/extract.pl" ;; "tools/frr") CONFIG_FILES="$CONFIG_FILES tools/frr" ;; "tools/watchfrr.sh") CONFIG_FILES="$CONFIG_FILES tools/watchfrr.sh" ;; "tools/frrinit.sh") CONFIG_FILES="$CONFIG_FILES tools/frrinit.sh" ;; "tools/frrcommon.sh") CONFIG_FILES="$CONFIG_FILES tools/frrcommon.sh" ;; "lib/route_types.h") CONFIG_COMMANDS="$CONFIG_COMMANDS lib/route_types.h" ;; "lib/gitversion.h") CONFIG_COMMANDS="$CONFIG_COMMANDS lib/gitversion.h" ;; *) as_fn_error $? "invalid argument: \`$ac_config_target'" "$LINENO" 5;; esac done # If the user did not use the arguments to specify the items to instantiate, # then the envvar interface is used. Set only those that are not. # We use the long form for the default assignment because of an extremely # bizarre bug on SunOS 4.1.3. if $ac_need_defaults; then test "${CONFIG_FILES+set}" = set || CONFIG_FILES=$config_files test "${CONFIG_HEADERS+set}" = set || CONFIG_HEADERS=$config_headers test "${CONFIG_COMMANDS+set}" = set || CONFIG_COMMANDS=$config_commands fi # Have a temporary directory for convenience. Make it in the build tree # simply because there is no reason against having it here, and in addition, # creating and moving files from /tmp can sometimes cause problems. # Hook for its removal unless debugging. # Note that there is a small window in which the directory will not be cleaned: # after its creation but before its name has been assigned to `$tmp'. $debug || { tmp= ac_tmp= trap 'exit_status=$? : "${ac_tmp:=$tmp}" { test ! -d "$ac_tmp" || rm -fr "$ac_tmp"; } && exit $exit_status ' 0 trap 'as_fn_exit 1' 1 2 13 15 } # Create a (secure) tmp directory for tmp files. { tmp=`(umask 077 && mktemp -d "./confXXXXXX") 2>/dev/null` && test -d "$tmp" } || { tmp=./conf$$-$RANDOM (umask 077 && mkdir "$tmp") } || as_fn_error $? "cannot create a temporary directory in ." "$LINENO" 5 ac_tmp=$tmp # Set up the scripts for CONFIG_FILES section. # No need to generate them if there are no CONFIG_FILES. # This happens for instance with `./config.status config.h'. if test -n "$CONFIG_FILES"; then ac_cr=`echo X | tr X '\015'` # On cygwin, bash can eat \r inside `` if the user requested igncr. # But we know of no other shell where ac_cr would be empty at this # point, so we can use a bashism as a fallback. if test "x$ac_cr" = x; then eval ac_cr=\$\'\\r\' fi ac_cs_awk_cr=`$AWK 'BEGIN { print "a\rb" }' /dev/null` if test "$ac_cs_awk_cr" = "a${ac_cr}b"; then ac_cs_awk_cr='\\r' else ac_cs_awk_cr=$ac_cr fi echo 'BEGIN {' >"$ac_tmp/subs1.awk" && _ACEOF { echo "cat >conf$$subs.awk <<_ACEOF" && echo "$ac_subst_vars" | sed 's/.*/&!$&$ac_delim/' && echo "_ACEOF" } >conf$$subs.sh || as_fn_error $? "could not make $CONFIG_STATUS" "$LINENO" 5 ac_delim_num=`echo "$ac_subst_vars" | grep -c '^'` ac_delim='%!_!# ' for ac_last_try in false false false false false :; do . ./conf$$subs.sh || as_fn_error $? "could not make $CONFIG_STATUS" "$LINENO" 5 ac_delim_n=`sed -n "s/.*$ac_delim\$/X/p" conf$$subs.awk | grep -c X` if test $ac_delim_n = $ac_delim_num; then break elif $ac_last_try; then as_fn_error $? "could not make $CONFIG_STATUS" "$LINENO" 5 else ac_delim="$ac_delim!$ac_delim _$ac_delim!! " fi done rm -f conf$$subs.sh cat >>$CONFIG_STATUS <<_ACEOF || ac_write_fail=1 cat >>"\$ac_tmp/subs1.awk" <<\\_ACAWK && _ACEOF sed -n ' h s/^/S["/; s/!.*/"]=/ p g s/^[^!]*!// :repl t repl s/'"$ac_delim"'$// t delim :nl h s/\(.\{148\}\)..*/\1/ t more1 s/["\\]/\\&/g; s/^/"/; s/$/\\n"\\/ p n b repl :more1 s/["\\]/\\&/g; s/^/"/; s/$/"\\/ p g s/.\{148\}// t nl :delim h s/\(.\{148\}\)..*/\1/ t more2 s/["\\]/\\&/g; s/^/"/; s/$/"/ p b :more2 s/["\\]/\\&/g; s/^/"/; s/$/"\\/ p g s/.\{148\}// t delim ' >$CONFIG_STATUS || ac_write_fail=1 rm -f conf$$subs.awk cat >>$CONFIG_STATUS <<_ACEOF || ac_write_fail=1 _ACAWK cat >>"\$ac_tmp/subs1.awk" <<_ACAWK && for (key in S) S_is_set[key] = 1 FS = "" } { line = $ 0 nfields = split(line, field, "@") substed = 0 len = length(field[1]) for (i = 2; i < nfields; i++) { key = field[i] keylen = length(key) if (S_is_set[key]) { value = S[key] line = substr(line, 1, len) "" value "" substr(line, len + keylen + 3) len += length(value) + length(field[++i]) substed = 1 } else len += 1 + keylen } print line } _ACAWK _ACEOF cat >>$CONFIG_STATUS <<\_ACEOF || ac_write_fail=1 if sed "s/$ac_cr//" < /dev/null > /dev/null 2>&1; then sed "s/$ac_cr\$//; s/$ac_cr/$ac_cs_awk_cr/g" else cat fi < "$ac_tmp/subs1.awk" > "$ac_tmp/subs.awk" \ || as_fn_error $? "could not setup config files machinery" "$LINENO" 5 _ACEOF # VPATH may cause trouble with some makes, so we remove sole $(srcdir), # ${srcdir} and @srcdir@ entries from VPATH if srcdir is ".", strip leading and # trailing colons and then remove the whole line if VPATH becomes empty # (actually we leave an empty line to preserve line numbers). if test "x$srcdir" = x.; then ac_vpsub='/^[ ]*VPATH[ ]*=[ ]*/{ h s/// s/^/:/ s/[ ]*$/:/ s/:\$(srcdir):/:/g s/:\${srcdir}:/:/g s/:@srcdir@:/:/g s/^:*// s/:*$// x s/\(=[ ]*\).*/\1/ G s/\n// s/^[^=]*=[ ]*$// }' fi cat >>$CONFIG_STATUS <<\_ACEOF || ac_write_fail=1 fi # test -n "$CONFIG_FILES" # Set up the scripts for CONFIG_HEADERS section. # No need to generate them if there are no CONFIG_HEADERS. # This happens for instance with `./config.status Makefile'. if test -n "$CONFIG_HEADERS"; then cat >"$ac_tmp/defines.awk" <<\_ACAWK || BEGIN { _ACEOF # Transform confdefs.h into an awk script `defines.awk', embedded as # here-document in config.status, that substitutes the proper values into # config.h.in to produce config.h. # Create a delimiter string that does not exist in confdefs.h, to ease # handling of long lines. ac_delim='%!_!# ' for ac_last_try in false false :; do ac_tt=`sed -n "/$ac_delim/p" confdefs.h` if test -z "$ac_tt"; then break elif $ac_last_try; then as_fn_error $? "could not make $CONFIG_HEADERS" "$LINENO" 5 else ac_delim="$ac_delim!$ac_delim _$ac_delim!! " fi done # For the awk script, D is an array of macro values keyed by name, # likewise P contains macro parameters if any. Preserve backslash # newline sequences. ac_word_re=[_$as_cr_Letters][_$as_cr_alnum]* sed -n ' s/.\{148\}/&'"$ac_delim"'/g t rset :rset s/^[ ]*#[ ]*define[ ][ ]*/ / t def d :def s/\\$// t bsnl s/["\\]/\\&/g s/^ \('"$ac_word_re"'\)\(([^()]*)\)[ ]*\(.*\)/P["\1"]="\2"\ D["\1"]=" \3"/p s/^ \('"$ac_word_re"'\)[ ]*\(.*\)/D["\1"]=" \2"/p d :bsnl s/["\\]/\\&/g s/^ \('"$ac_word_re"'\)\(([^()]*)\)[ ]*\(.*\)/P["\1"]="\2"\ D["\1"]=" \3\\\\\\n"\\/p t cont s/^ \('"$ac_word_re"'\)[ ]*\(.*\)/D["\1"]=" \2\\\\\\n"\\/p t cont d :cont n s/.\{148\}/&'"$ac_delim"'/g t clear :clear s/\\$// t bsnlc s/["\\]/\\&/g; s/^/"/; s/$/"/p d :bsnlc s/["\\]/\\&/g; s/^/"/; s/$/\\\\\\n"\\/p b cont ' >$CONFIG_STATUS || ac_write_fail=1 cat >>$CONFIG_STATUS <<_ACEOF || ac_write_fail=1 for (key in D) D_is_set[key] = 1 FS = "" } /^[\t ]*#[\t ]*(define|undef)[\t ]+$ac_word_re([\t (]|\$)/ { line = \$ 0 split(line, arg, " ") if (arg[1] == "#") { defundef = arg[2] mac1 = arg[3] } else { defundef = substr(arg[1], 2) mac1 = arg[2] } split(mac1, mac2, "(") #) macro = mac2[1] prefix = substr(line, 1, index(line, defundef) - 1) if (D_is_set[macro]) { # Preserve the white space surrounding the "#". print prefix "define", macro P[macro] D[macro] next } else { # Replace #undef with comments. This is necessary, for example, # in the case of _POSIX_SOURCE, which is predefined and required # on some systems where configure will not decide to define it. if (defundef == "undef") { print "/*", prefix defundef, macro, "*/" next } } } { print } _ACAWK _ACEOF cat >>$CONFIG_STATUS <<\_ACEOF || ac_write_fail=1 as_fn_error $? "could not setup config headers machinery" "$LINENO" 5 fi # test -n "$CONFIG_HEADERS" eval set X " :F $CONFIG_FILES :H $CONFIG_HEADERS :C $CONFIG_COMMANDS" shift for ac_tag do case $ac_tag in :[FHLC]) ac_mode=$ac_tag; continue;; esac case $ac_mode$ac_tag in :[FHL]*:*);; :L* | :C*:*) as_fn_error $? "invalid tag \`$ac_tag'" "$LINENO" 5;; :[FH]-) ac_tag=-:-;; :[FH]*) ac_tag=$ac_tag:$ac_tag.in;; esac ac_save_IFS=$IFS IFS=: set x $ac_tag IFS=$ac_save_IFS shift ac_file=$1 shift case $ac_mode in :L) ac_source=$1;; :[FH]) ac_file_inputs= for ac_f do case $ac_f in -) ac_f="$ac_tmp/stdin";; *) # Look for the file first in the build tree, then in the source tree # (if the path is not absolute). The absolute path cannot be DOS-style, # because $ac_f cannot contain `:'. test -f "$ac_f" || case $ac_f in [\\/$]*) false;; *) test -f "$srcdir/$ac_f" && ac_f="$srcdir/$ac_f";; esac || as_fn_error 1 "cannot find input file: \`$ac_f'" "$LINENO" 5;; esac case $ac_f in *\'*) ac_f=`$as_echo "$ac_f" | sed "s/'/'\\\\\\\\''/g"`;; esac as_fn_append ac_file_inputs " '$ac_f'" done # Let's still pretend it is `configure' which instantiates (i.e., don't # use $as_me), people would be surprised to read: # /* config.h. Generated by config.status. */ configure_input='Generated from '` $as_echo "$*" | sed 's|^[^:]*/||;s|:[^:]*/|, |g' `' by configure.' if test x"$ac_file" != x-; then configure_input="$ac_file. $configure_input" { $as_echo "$as_me:${as_lineno-$LINENO}: creating $ac_file" >&5 $as_echo "$as_me: creating $ac_file" >&6;} fi # Neutralize special characters interpreted by sed in replacement strings. case $configure_input in #( *\&* | *\|* | *\\* ) ac_sed_conf_input=`$as_echo "$configure_input" | sed 's/[\\\\&|]/\\\\&/g'`;; #( *) ac_sed_conf_input=$configure_input;; esac case $ac_tag in *:-:* | *:-) cat >"$ac_tmp/stdin" \ || as_fn_error $? "could not create $ac_file" "$LINENO" 5 ;; esac ;; esac ac_dir=`$as_dirname -- "$ac_file" || $as_expr X"$ac_file" : 'X\(.*[^/]\)//*[^/][^/]*/*$' \| \ X"$ac_file" : 'X\(//\)[^/]' \| \ X"$ac_file" : 'X\(//\)$' \| \ X"$ac_file" : 'X\(/\)' \| . 2>/dev/null || $as_echo X"$ac_file" | sed '/^X\(.*[^/]\)\/\/*[^/][^/]*\/*$/{ s//\1/ q } /^X\(\/\/\)[^/].*/{ s//\1/ q } /^X\(\/\/\)$/{ s//\1/ q } /^X\(\/\).*/{ s//\1/ q } s/.*/./; q'` as_dir="$ac_dir"; as_fn_mkdir_p ac_builddir=. case "$ac_dir" in .) ac_dir_suffix= ac_top_builddir_sub=. ac_top_build_prefix= ;; *) ac_dir_suffix=/`$as_echo "$ac_dir" | sed 's|^\.[\\/]||'` # A ".." for each directory in $ac_dir_suffix. ac_top_builddir_sub=`$as_echo "$ac_dir_suffix" | sed 's|/[^\\/]*|/..|g;s|/||'` case $ac_top_builddir_sub in "") ac_top_builddir_sub=. ac_top_build_prefix= ;; *) ac_top_build_prefix=$ac_top_builddir_sub/ ;; esac ;; esac ac_abs_top_builddir=$ac_pwd ac_abs_builddir=$ac_pwd$ac_dir_suffix # for backward compatibility: ac_top_builddir=$ac_top_build_prefix case $srcdir in .) # We are building in place. ac_srcdir=. ac_top_srcdir=$ac_top_builddir_sub ac_abs_top_srcdir=$ac_pwd ;; [\\/]* | ?:[\\/]* ) # Absolute name. ac_srcdir=$srcdir$ac_dir_suffix; ac_top_srcdir=$srcdir ac_abs_top_srcdir=$srcdir ;; *) # Relative name. ac_srcdir=$ac_top_build_prefix$srcdir$ac_dir_suffix ac_top_srcdir=$ac_top_build_prefix$srcdir ac_abs_top_srcdir=$ac_pwd/$srcdir ;; esac ac_abs_srcdir=$ac_abs_top_srcdir$ac_dir_suffix case $ac_mode in :F) # # CONFIG_FILE # case $INSTALL in [\\/$]* | ?:[\\/]* ) ac_INSTALL=$INSTALL ;; *) ac_INSTALL=$ac_top_build_prefix$INSTALL ;; esac ac_MKDIR_P=$MKDIR_P case $MKDIR_P in [\\/$]* | ?:[\\/]* ) ;; */*) ac_MKDIR_P=$ac_top_build_prefix$MKDIR_P ;; esac _ACEOF cat >>$CONFIG_STATUS <<\_ACEOF || ac_write_fail=1 # If the template does not know about datarootdir, expand it. # FIXME: This hack should be removed a few years after 2.60. ac_datarootdir_hack=; ac_datarootdir_seen= ac_sed_dataroot=' /datarootdir/ { p q } /@datadir@/p /@docdir@/p /@infodir@/p /@localedir@/p /@mandir@/p' case `eval "sed -n \"\$ac_sed_dataroot\" $ac_file_inputs"` in *datarootdir*) ac_datarootdir_seen=yes;; *@datadir@*|*@docdir@*|*@infodir@*|*@localedir@*|*@mandir@*) { $as_echo "$as_me:${as_lineno-$LINENO}: WARNING: $ac_file_inputs seems to ignore the --datarootdir setting" >&5 $as_echo "$as_me: WARNING: $ac_file_inputs seems to ignore the --datarootdir setting" >&2;} _ACEOF cat >>$CONFIG_STATUS <<_ACEOF || ac_write_fail=1 ac_datarootdir_hack=' s&@datadir@&$datadir&g s&@docdir@&$docdir&g s&@infodir@&$infodir&g s&@localedir@&$localedir&g s&@mandir@&$mandir&g s&\\\${datarootdir}&$datarootdir&g' ;; esac _ACEOF # Neutralize VPATH when `$srcdir' = `.'. # Shell code in configure.ac might set extrasub. # FIXME: do we really want to maintain this feature? cat >>$CONFIG_STATUS <<_ACEOF || ac_write_fail=1 ac_sed_extra="$ac_vpsub $extrasub _ACEOF cat >>$CONFIG_STATUS <<\_ACEOF || ac_write_fail=1 :t /@[a-zA-Z_][a-zA-Z_0-9]*@/!b s|@configure_input@|$ac_sed_conf_input|;t t s&@top_builddir@&$ac_top_builddir_sub&;t t s&@top_build_prefix@&$ac_top_build_prefix&;t t s&@srcdir@&$ac_srcdir&;t t s&@abs_srcdir@&$ac_abs_srcdir&;t t s&@top_srcdir@&$ac_top_srcdir&;t t s&@abs_top_srcdir@&$ac_abs_top_srcdir&;t t s&@builddir@&$ac_builddir&;t t s&@abs_builddir@&$ac_abs_builddir&;t t s&@abs_top_builddir@&$ac_abs_top_builddir&;t t s&@INSTALL@&$ac_INSTALL&;t t s&@MKDIR_P@&$ac_MKDIR_P&;t t $ac_datarootdir_hack " eval sed \"\$ac_sed_extra\" "$ac_file_inputs" | $AWK -f "$ac_tmp/subs.awk" \ >$ac_tmp/out || as_fn_error $? "could not create $ac_file" "$LINENO" 5 test -z "$ac_datarootdir_hack$ac_datarootdir_seen" && { ac_out=`sed -n '/\${datarootdir}/p' "$ac_tmp/out"`; test -n "$ac_out"; } && { ac_out=`sed -n '/^[ ]*datarootdir[ ]*:*=/p' \ "$ac_tmp/out"`; test -z "$ac_out"; } && { $as_echo "$as_me:${as_lineno-$LINENO}: WARNING: $ac_file contains a reference to the variable \`datarootdir' which seems to be undefined. Please make sure it is defined" >&5 $as_echo "$as_me: WARNING: $ac_file contains a reference to the variable \`datarootdir' which seems to be undefined. Please make sure it is defined" >&2;} rm -f "$ac_tmp/stdin" case $ac_file in -) cat "$ac_tmp/out" && rm -f "$ac_tmp/out";; *) rm -f "$ac_file" && mv "$ac_tmp/out" "$ac_file";; esac \ || as_fn_error $? "could not create $ac_file" "$LINENO" 5 ;; :H) # # CONFIG_HEADER # if test x"$ac_file" != x-; then { $as_echo "/* $configure_input */" \ && eval '$AWK -f "$ac_tmp/defines.awk"' "$ac_file_inputs" } >"$ac_tmp/config.h" \ || as_fn_error $? "could not create $ac_file" "$LINENO" 5 if diff "$ac_file" "$ac_tmp/config.h" >/dev/null 2>&1; then { $as_echo "$as_me:${as_lineno-$LINENO}: $ac_file is unchanged" >&5 $as_echo "$as_me: $ac_file is unchanged" >&6;} else rm -f "$ac_file" mv "$ac_tmp/config.h" "$ac_file" \ || as_fn_error $? "could not create $ac_file" "$LINENO" 5 fi else $as_echo "/* $configure_input */" \ && eval '$AWK -f "$ac_tmp/defines.awk"' "$ac_file_inputs" \ || as_fn_error $? "could not create -" "$LINENO" 5 fi # Compute "$ac_file"'s index in $config_headers. _am_arg="$ac_file" _am_stamp_count=1 for _am_header in $config_headers :; do case $_am_header in $_am_arg | $_am_arg:* ) break ;; * ) _am_stamp_count=`expr $_am_stamp_count + 1` ;; esac done echo "timestamp for $_am_arg" >`$as_dirname -- "$_am_arg" || $as_expr X"$_am_arg" : 'X\(.*[^/]\)//*[^/][^/]*/*$' \| \ X"$_am_arg" : 'X\(//\)[^/]' \| \ X"$_am_arg" : 'X\(//\)$' \| \ X"$_am_arg" : 'X\(/\)' \| . 2>/dev/null || $as_echo X"$_am_arg" | sed '/^X\(.*[^/]\)\/\/*[^/][^/]*\/*$/{ s//\1/ q } /^X\(\/\/\)[^/].*/{ s//\1/ q } /^X\(\/\/\)$/{ s//\1/ q } /^X\(\/\).*/{ s//\1/ q } s/.*/./; q'`/stamp-h$_am_stamp_count ;; :C) { $as_echo "$as_me:${as_lineno-$LINENO}: executing $ac_file commands" >&5 $as_echo "$as_me: executing $ac_file commands" >&6;} ;; esac case $ac_file$ac_mode in "depfiles":C) test x"$AMDEP_TRUE" != x"" || { # Older Autoconf quotes --file arguments for eval, but not when files # are listed without --file. Let's play safe and only enable the eval # if we detect the quoting. # TODO: see whether this extra hack can be removed once we start # requiring Autoconf 2.70 or later. case $CONFIG_FILES in #( *\'*) : eval set x "$CONFIG_FILES" ;; #( *) : set x $CONFIG_FILES ;; #( *) : ;; esac shift # Used to flag and report bootstrapping failures. am_rc=0 for am_mf do # Strip MF so we end up with the name of the file. am_mf=`$as_echo "$am_mf" | sed -e 's/:.*$//'` # Check whether this is an Automake generated Makefile which includes # dependency-tracking related rules and includes. # Grep'ing the whole file directly is not great: AIX grep has a line # limit of 2048, but all sed's we know have understand at least 4000. sed -n 's,^am--depfiles:.*,X,p' "$am_mf" | grep X >/dev/null 2>&1 \ || continue am_dirpart=`$as_dirname -- "$am_mf" || $as_expr X"$am_mf" : 'X\(.*[^/]\)//*[^/][^/]*/*$' \| \ X"$am_mf" : 'X\(//\)[^/]' \| \ X"$am_mf" : 'X\(//\)$' \| \ X"$am_mf" : 'X\(/\)' \| . 2>/dev/null || $as_echo X"$am_mf" | sed '/^X\(.*[^/]\)\/\/*[^/][^/]*\/*$/{ s//\1/ q } /^X\(\/\/\)[^/].*/{ s//\1/ q } /^X\(\/\/\)$/{ s//\1/ q } /^X\(\/\).*/{ s//\1/ q } s/.*/./; q'` am_filepart=`$as_basename -- "$am_mf" || $as_expr X/"$am_mf" : '.*/\([^/][^/]*\)/*$' \| \ X"$am_mf" : 'X\(//\)$' \| \ X"$am_mf" : 'X\(/\)' \| . 2>/dev/null || $as_echo X/"$am_mf" | sed '/^.*\/\([^/][^/]*\)\/*$/{ s//\1/ q } /^X\/\(\/\/\)$/{ s//\1/ q } /^X\/\(\/\).*/{ s//\1/ q } s/.*/./; q'` { echo "$as_me:$LINENO: cd "$am_dirpart" \ && sed -e '/# am--include-marker/d' "$am_filepart" \ | $MAKE -f - am--depfiles" >&5 (cd "$am_dirpart" \ && sed -e '/# am--include-marker/d' "$am_filepart" \ | $MAKE -f - am--depfiles) >&5 2>&5 ac_status=$? echo "$as_me:$LINENO: \$? = $ac_status" >&5 (exit $ac_status); } || am_rc=$? done if test $am_rc -ne 0; then { { $as_echo "$as_me:${as_lineno-$LINENO}: error: in \`$ac_pwd':" >&5 $as_echo "$as_me: error: in \`$ac_pwd':" >&2;} as_fn_error $? "Something went wrong bootstrapping makefile fragments for automatic dependency tracking. Try re-running configure with the '--disable-dependency-tracking' option to at least be able to build the package (albeit without support for automatic dependency tracking). See \`config.log' for more details" "$LINENO" 5; } fi { am_dirpart=; unset am_dirpart;} { am_filepart=; unset am_filepart;} { am_mf=; unset am_mf;} { am_rc=; unset am_rc;} rm -f conftest-deps.mk } ;; "libtool":C) # See if we are running on zsh, and set the options that allow our # commands through without removal of \ escapes. if test -n "${ZSH_VERSION+set}"; then setopt NO_GLOB_SUBST fi cfgfile=${ofile}T trap "$RM \"$cfgfile\"; exit 1" 1 2 15 $RM "$cfgfile" cat <<_LT_EOF >> "$cfgfile" #! $SHELL # Generated automatically by $as_me ($PACKAGE) $VERSION # NOTE: Changes made to this file will be lost: look at ltmain.sh. # Provide generalized library-building support services. # Written by Gordon Matzigkeit, 1996 # Copyright (C) 2014 Free Software Foundation, Inc. # This is free software; see the source for copying conditions. There is NO # warranty; not even for MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. # GNU Libtool is free software; you can redistribute it and/or modify # it under the terms of the GNU General Public License as published by # the Free Software Foundation; either version 2 of of the License, or # (at your option) any later version. # # As a special exception to the GNU General Public License, if you # distribute this file as part of a program or library that is built # using GNU Libtool, you may include this file under the same # distribution terms that you use for the rest of that program. # # GNU Libtool is distributed in the hope that it will be useful, but # WITHOUT ANY WARRANTY; without even the implied warranty of # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the # GNU General Public License for more details. # # You should have received a copy of the GNU General Public License # along with this program. If not, see . # The names of the tagged configurations supported by this script. available_tags='CXX ' # Configured defaults for sys_lib_dlsearch_path munging. : \${LT_SYS_LIBRARY_PATH="$configure_time_lt_sys_library_path"} # ### BEGIN LIBTOOL CONFIG # Which release of libtool.m4 was used? macro_version=$macro_version macro_revision=$macro_revision # Whether or not to build shared libraries. build_libtool_libs=$enable_shared # Whether or not to build static libraries. build_old_libs=$enable_static # What type of objects to build. pic_mode=$pic_mode # Whether or not to optimize for fast installation. fast_install=$enable_fast_install # Shared archive member basename,for filename based shared library versioning on AIX. shared_archive_member_spec=$shared_archive_member_spec # Shell to use when invoking shell scripts. SHELL=$lt_SHELL # An echo program that protects backslashes. ECHO=$lt_ECHO # The PATH separator for the build system. PATH_SEPARATOR=$lt_PATH_SEPARATOR # The host system. host_alias=$host_alias host=$host host_os=$host_os # The build system. build_alias=$build_alias build=$build build_os=$build_os # A sed program that does not truncate output. SED=$lt_SED # Sed that helps us avoid accidentally triggering echo(1) options like -n. Xsed="\$SED -e 1s/^X//" # A grep program that handles long lines. GREP=$lt_GREP # An ERE matcher. EGREP=$lt_EGREP # A literal string matcher. FGREP=$lt_FGREP # A BSD- or MS-compatible name lister. NM=$lt_NM # Whether we need soft or hard links. LN_S=$lt_LN_S # What is the maximum length of a command? max_cmd_len=$max_cmd_len # Object file suffix (normally "o"). objext=$ac_objext # Executable file suffix (normally ""). exeext=$exeext # whether the shell understands "unset". lt_unset=$lt_unset # turn spaces into newlines. SP2NL=$lt_lt_SP2NL # turn newlines into spaces. NL2SP=$lt_lt_NL2SP # convert \$build file names to \$host format. to_host_file_cmd=$lt_cv_to_host_file_cmd # convert \$build files to toolchain format. to_tool_file_cmd=$lt_cv_to_tool_file_cmd # An object symbol dumper. OBJDUMP=$lt_OBJDUMP # Method to check whether dependent libraries are shared objects. deplibs_check_method=$lt_deplibs_check_method # Command to use when deplibs_check_method = "file_magic". file_magic_cmd=$lt_file_magic_cmd # How to find potential files when deplibs_check_method = "file_magic". file_magic_glob=$lt_file_magic_glob # Find potential files using nocaseglob when deplibs_check_method = "file_magic". want_nocaseglob=$lt_want_nocaseglob # DLL creation program. DLLTOOL=$lt_DLLTOOL # Command to associate shared and link libraries. sharedlib_from_linklib_cmd=$lt_sharedlib_from_linklib_cmd # The archiver. AR=$lt_AR # Flags to create an archive. AR_FLAGS=$lt_AR_FLAGS # How to feed a file listing to the archiver. archiver_list_spec=$lt_archiver_list_spec # A symbol stripping program. STRIP=$lt_STRIP # Commands used to install an old-style archive. RANLIB=$lt_RANLIB old_postinstall_cmds=$lt_old_postinstall_cmds old_postuninstall_cmds=$lt_old_postuninstall_cmds # Whether to use a lock for old archive extraction. lock_old_archive_extraction=$lock_old_archive_extraction # A C compiler. LTCC=$lt_CC # LTCC compiler flags. LTCFLAGS=$lt_CFLAGS # Take the output of nm and produce a listing of raw symbols and C names. global_symbol_pipe=$lt_lt_cv_sys_global_symbol_pipe # Transform the output of nm in a proper C declaration. global_symbol_to_cdecl=$lt_lt_cv_sys_global_symbol_to_cdecl # Transform the output of nm into a list of symbols to manually relocate. global_symbol_to_import=$lt_lt_cv_sys_global_symbol_to_import # Transform the output of nm in a C name address pair. global_symbol_to_c_name_address=$lt_lt_cv_sys_global_symbol_to_c_name_address # Transform the output of nm in a C name address pair when lib prefix is needed. global_symbol_to_c_name_address_lib_prefix=$lt_lt_cv_sys_global_symbol_to_c_name_address_lib_prefix # The name lister interface. nm_interface=$lt_lt_cv_nm_interface # Specify filename containing input files for \$NM. nm_file_list_spec=$lt_nm_file_list_spec # The root where to search for dependent libraries,and where our libraries should be installed. lt_sysroot=$lt_sysroot # Command to truncate a binary pipe. lt_truncate_bin=$lt_lt_cv_truncate_bin # The name of the directory that contains temporary libtool files. objdir=$objdir # Used to examine libraries when file_magic_cmd begins with "file". MAGIC_CMD=$MAGIC_CMD # Must we lock files when doing compilation? need_locks=$lt_need_locks # Manifest tool. MANIFEST_TOOL=$lt_MANIFEST_TOOL # Tool to manipulate archived DWARF debug symbol files on Mac OS X. DSYMUTIL=$lt_DSYMUTIL # Tool to change global to local symbols on Mac OS X. NMEDIT=$lt_NMEDIT # Tool to manipulate fat objects and archives on Mac OS X. LIPO=$lt_LIPO # ldd/readelf like tool for Mach-O binaries on Mac OS X. OTOOL=$lt_OTOOL # ldd/readelf like tool for 64 bit Mach-O binaries on Mac OS X 10.4. OTOOL64=$lt_OTOOL64 # Old archive suffix (normally "a"). libext=$libext # Shared library suffix (normally ".so"). shrext_cmds=$lt_shrext_cmds # The commands to extract the exported symbol list from a shared archive. extract_expsyms_cmds=$lt_extract_expsyms_cmds # Variables whose values should be saved in libtool wrapper scripts and # restored at link time. variables_saved_for_relink=$lt_variables_saved_for_relink # Do we need the "lib" prefix for modules? need_lib_prefix=$need_lib_prefix # Do we need a version for libraries? need_version=$need_version # Library versioning type. version_type=$version_type # Shared library runtime path variable. runpath_var=$runpath_var # Shared library path variable. shlibpath_var=$shlibpath_var # Is shlibpath searched before the hard-coded library search path? shlibpath_overrides_runpath=$shlibpath_overrides_runpath # Format of library name prefix. libname_spec=$lt_libname_spec # List of archive names. First name is the real one, the rest are links. # The last name is the one that the linker finds with -lNAME library_names_spec=$lt_library_names_spec # The coded name of the library, if different from the real name. soname_spec=$lt_soname_spec # Permission mode override for installation of shared libraries. install_override_mode=$lt_install_override_mode # Command to use after installation of a shared archive. postinstall_cmds=$lt_postinstall_cmds # Command to use after uninstallation of a shared archive. postuninstall_cmds=$lt_postuninstall_cmds # Commands used to finish a libtool library installation in a directory. finish_cmds=$lt_finish_cmds # As "finish_cmds", except a single script fragment to be evaled but # not shown. finish_eval=$lt_finish_eval # Whether we should hardcode library paths into libraries. hardcode_into_libs=$hardcode_into_libs # Compile-time system search path for libraries. sys_lib_search_path_spec=$lt_sys_lib_search_path_spec # Detected run-time system search path for libraries. sys_lib_dlsearch_path_spec=$lt_configure_time_dlsearch_path # Explicit LT_SYS_LIBRARY_PATH set during ./configure time. configure_time_lt_sys_library_path=$lt_configure_time_lt_sys_library_path # Whether dlopen is supported. dlopen_support=$enable_dlopen # Whether dlopen of programs is supported. dlopen_self=$enable_dlopen_self # Whether dlopen of statically linked programs is supported. dlopen_self_static=$enable_dlopen_self_static # Commands to strip libraries. old_striplib=$lt_old_striplib striplib=$lt_striplib # The linker used to build libraries. LD=$lt_LD # How to create reloadable object files. reload_flag=$lt_reload_flag reload_cmds=$lt_reload_cmds # Commands used to build an old-style archive. old_archive_cmds=$lt_old_archive_cmds # A language specific compiler. CC=$lt_compiler # Is the compiler the GNU compiler? with_gcc=$GCC # Compiler flag to turn off builtin functions. no_builtin_flag=$lt_lt_prog_compiler_no_builtin_flag # Additional compiler flags for building library objects. pic_flag=$lt_lt_prog_compiler_pic # How to pass a linker flag through the compiler. wl=$lt_lt_prog_compiler_wl # Compiler flag to prevent dynamic linking. link_static_flag=$lt_lt_prog_compiler_static # Does compiler simultaneously support -c and -o options? compiler_c_o=$lt_lt_cv_prog_compiler_c_o # Whether or not to add -lc for building shared libraries. build_libtool_need_lc=$archive_cmds_need_lc # Whether or not to disallow shared libs when runtime libs are static. allow_libtool_libs_with_static_runtimes=$enable_shared_with_static_runtimes # Compiler flag to allow reflexive dlopens. export_dynamic_flag_spec=$lt_export_dynamic_flag_spec # Compiler flag to generate shared objects directly from archives. whole_archive_flag_spec=$lt_whole_archive_flag_spec # Whether the compiler copes with passing no objects directly. compiler_needs_object=$lt_compiler_needs_object # Create an old-style archive from a shared archive. old_archive_from_new_cmds=$lt_old_archive_from_new_cmds # Create a temporary old-style archive to link instead of a shared archive. old_archive_from_expsyms_cmds=$lt_old_archive_from_expsyms_cmds # Commands used to build a shared archive. archive_cmds=$lt_archive_cmds archive_expsym_cmds=$lt_archive_expsym_cmds # Commands used to build a loadable module if different from building # a shared archive. module_cmds=$lt_module_cmds module_expsym_cmds=$lt_module_expsym_cmds # Whether we are building with GNU ld or not. with_gnu_ld=$lt_with_gnu_ld # Flag that allows shared libraries with undefined symbols to be built. allow_undefined_flag=$lt_allow_undefined_flag # Flag that enforces no undefined symbols. no_undefined_flag=$lt_no_undefined_flag # Flag to hardcode \$libdir into a binary during linking. # This must work even if \$libdir does not exist hardcode_libdir_flag_spec=$lt_hardcode_libdir_flag_spec # Whether we need a single "-rpath" flag with a separated argument. hardcode_libdir_separator=$lt_hardcode_libdir_separator # Set to "yes" if using DIR/libNAME\$shared_ext during linking hardcodes # DIR into the resulting binary. hardcode_direct=$hardcode_direct # Set to "yes" if using DIR/libNAME\$shared_ext during linking hardcodes # DIR into the resulting binary and the resulting library dependency is # "absolute",i.e impossible to change by setting \$shlibpath_var if the # library is relocated. hardcode_direct_absolute=$hardcode_direct_absolute # Set to "yes" if using the -LDIR flag during linking hardcodes DIR # into the resulting binary. hardcode_minus_L=$hardcode_minus_L # Set to "yes" if using SHLIBPATH_VAR=DIR during linking hardcodes DIR # into the resulting binary. hardcode_shlibpath_var=$hardcode_shlibpath_var # Set to "yes" if building a shared library automatically hardcodes DIR # into the library and all subsequent libraries and executables linked # against it. hardcode_automatic=$hardcode_automatic # Set to yes if linker adds runtime paths of dependent libraries # to runtime path list. inherit_rpath=$inherit_rpath # Whether libtool must link a program against all its dependency libraries. link_all_deplibs=$link_all_deplibs # Set to "yes" if exported symbols are required. always_export_symbols=$always_export_symbols # The commands to list exported symbols. export_symbols_cmds=$lt_export_symbols_cmds # Symbols that should not be listed in the preloaded symbols. exclude_expsyms=$lt_exclude_expsyms # Symbols that must always be exported. include_expsyms=$lt_include_expsyms # Commands necessary for linking programs (against libraries) with templates. prelink_cmds=$lt_prelink_cmds # Commands necessary for finishing linking programs. postlink_cmds=$lt_postlink_cmds # Specify filename containing input files. file_list_spec=$lt_file_list_spec # How to hardcode a shared library path into an executable. hardcode_action=$hardcode_action # The directories searched by this compiler when creating a shared library. compiler_lib_search_dirs=$lt_compiler_lib_search_dirs # Dependencies to place before and after the objects being linked to # create a shared library. predep_objects=$lt_predep_objects postdep_objects=$lt_postdep_objects predeps=$lt_predeps postdeps=$lt_postdeps # The library search path used internally by the compiler when linking # a shared library. compiler_lib_search_path=$lt_compiler_lib_search_path # ### END LIBTOOL CONFIG _LT_EOF cat <<'_LT_EOF' >> "$cfgfile" # ### BEGIN FUNCTIONS SHARED WITH CONFIGURE # func_munge_path_list VARIABLE PATH # ----------------------------------- # VARIABLE is name of variable containing _space_ separated list of # directories to be munged by the contents of PATH, which is string # having a format: # "DIR[:DIR]:" # string "DIR[ DIR]" will be prepended to VARIABLE # ":DIR[:DIR]" # string "DIR[ DIR]" will be appended to VARIABLE # "DIRP[:DIRP]::[DIRA:]DIRA" # string "DIRP[ DIRP]" will be prepended to VARIABLE and string # "DIRA[ DIRA]" will be appended to VARIABLE # "DIR[:DIR]" # VARIABLE will be replaced by "DIR[ DIR]" func_munge_path_list () { case x$2 in x) ;; *:) eval $1=\"`$ECHO $2 | $SED 's/:/ /g'` \$$1\" ;; x:*) eval $1=\"\$$1 `$ECHO $2 | $SED 's/:/ /g'`\" ;; *::*) eval $1=\"\$$1\ `$ECHO $2 | $SED -e 's/.*:://' -e 's/:/ /g'`\" eval $1=\"`$ECHO $2 | $SED -e 's/::.*//' -e 's/:/ /g'`\ \$$1\" ;; *) eval $1=\"`$ECHO $2 | $SED 's/:/ /g'`\" ;; esac } # Calculate cc_basename. Skip known compiler wrappers and cross-prefix. func_cc_basename () { for cc_temp in $*""; do case $cc_temp in compile | *[\\/]compile | ccache | *[\\/]ccache ) ;; distcc | *[\\/]distcc | purify | *[\\/]purify ) ;; \-*) ;; *) break;; esac done func_cc_basename_result=`$ECHO "$cc_temp" | $SED "s%.*/%%; s%^$host_alias-%%"` } # ### END FUNCTIONS SHARED WITH CONFIGURE _LT_EOF case $host_os in aix3*) cat <<\_LT_EOF >> "$cfgfile" # AIX sometimes has problems with the GCC collect2 program. For some # reason, if we set the COLLECT_NAMES environment variable, the problems # vanish in a puff of smoke. if test set != "${COLLECT_NAMES+set}"; then COLLECT_NAMES= export COLLECT_NAMES fi _LT_EOF ;; esac ltmain=$ac_aux_dir/ltmain.sh # We use sed instead of cat because bash on DJGPP gets confused if # if finds mixed CR/LF and LF-only lines. Since sed operates in # text mode, it properly converts lines to CR/LF. This bash problem # is reportedly fixed, but why not run on old versions too? sed '$q' "$ltmain" >> "$cfgfile" \ || (rm -f "$cfgfile"; exit 1) mv -f "$cfgfile" "$ofile" || (rm -f "$ofile" && cp "$cfgfile" "$ofile" && rm -f "$cfgfile") chmod +x "$ofile" cat <<_LT_EOF >> "$ofile" # ### BEGIN LIBTOOL TAG CONFIG: CXX # The linker used to build libraries. LD=$lt_LD_CXX # How to create reloadable object files. reload_flag=$lt_reload_flag_CXX reload_cmds=$lt_reload_cmds_CXX # Commands used to build an old-style archive. old_archive_cmds=$lt_old_archive_cmds_CXX # A language specific compiler. CC=$lt_compiler_CXX # Is the compiler the GNU compiler? with_gcc=$GCC_CXX # Compiler flag to turn off builtin functions. no_builtin_flag=$lt_lt_prog_compiler_no_builtin_flag_CXX # Additional compiler flags for building library objects. pic_flag=$lt_lt_prog_compiler_pic_CXX # How to pass a linker flag through the compiler. wl=$lt_lt_prog_compiler_wl_CXX # Compiler flag to prevent dynamic linking. link_static_flag=$lt_lt_prog_compiler_static_CXX # Does compiler simultaneously support -c and -o options? compiler_c_o=$lt_lt_cv_prog_compiler_c_o_CXX # Whether or not to add -lc for building shared libraries. build_libtool_need_lc=$archive_cmds_need_lc_CXX # Whether or not to disallow shared libs when runtime libs are static. allow_libtool_libs_with_static_runtimes=$enable_shared_with_static_runtimes_CXX # Compiler flag to allow reflexive dlopens. export_dynamic_flag_spec=$lt_export_dynamic_flag_spec_CXX # Compiler flag to generate shared objects directly from archives. whole_archive_flag_spec=$lt_whole_archive_flag_spec_CXX # Whether the compiler copes with passing no objects directly. compiler_needs_object=$lt_compiler_needs_object_CXX # Create an old-style archive from a shared archive. old_archive_from_new_cmds=$lt_old_archive_from_new_cmds_CXX # Create a temporary old-style archive to link instead of a shared archive. old_archive_from_expsyms_cmds=$lt_old_archive_from_expsyms_cmds_CXX # Commands used to build a shared archive. archive_cmds=$lt_archive_cmds_CXX archive_expsym_cmds=$lt_archive_expsym_cmds_CXX # Commands used to build a loadable module if different from building # a shared archive. module_cmds=$lt_module_cmds_CXX module_expsym_cmds=$lt_module_expsym_cmds_CXX # Whether we are building with GNU ld or not. with_gnu_ld=$lt_with_gnu_ld_CXX # Flag that allows shared libraries with undefined symbols to be built. allow_undefined_flag=$lt_allow_undefined_flag_CXX # Flag that enforces no undefined symbols. no_undefined_flag=$lt_no_undefined_flag_CXX # Flag to hardcode \$libdir into a binary during linking. # This must work even if \$libdir does not exist hardcode_libdir_flag_spec=$lt_hardcode_libdir_flag_spec_CXX # Whether we need a single "-rpath" flag with a separated argument. hardcode_libdir_separator=$lt_hardcode_libdir_separator_CXX # Set to "yes" if using DIR/libNAME\$shared_ext during linking hardcodes # DIR into the resulting binary. hardcode_direct=$hardcode_direct_CXX # Set to "yes" if using DIR/libNAME\$shared_ext during linking hardcodes # DIR into the resulting binary and the resulting library dependency is # "absolute",i.e impossible to change by setting \$shlibpath_var if the # library is relocated. hardcode_direct_absolute=$hardcode_direct_absolute_CXX # Set to "yes" if using the -LDIR flag during linking hardcodes DIR # into the resulting binary. hardcode_minus_L=$hardcode_minus_L_CXX # Set to "yes" if using SHLIBPATH_VAR=DIR during linking hardcodes DIR # into the resulting binary. hardcode_shlibpath_var=$hardcode_shlibpath_var_CXX # Set to "yes" if building a shared library automatically hardcodes DIR # into the library and all subsequent libraries and executables linked # against it. hardcode_automatic=$hardcode_automatic_CXX # Set to yes if linker adds runtime paths of dependent libraries # to runtime path list. inherit_rpath=$inherit_rpath_CXX # Whether libtool must link a program against all its dependency libraries. link_all_deplibs=$link_all_deplibs_CXX # Set to "yes" if exported symbols are required. always_export_symbols=$always_export_symbols_CXX # The commands to list exported symbols. export_symbols_cmds=$lt_export_symbols_cmds_CXX # Symbols that should not be listed in the preloaded symbols. exclude_expsyms=$lt_exclude_expsyms_CXX # Symbols that must always be exported. include_expsyms=$lt_include_expsyms_CXX # Commands necessary for linking programs (against libraries) with templates. prelink_cmds=$lt_prelink_cmds_CXX # Commands necessary for finishing linking programs. postlink_cmds=$lt_postlink_cmds_CXX # Specify filename containing input files. file_list_spec=$lt_file_list_spec_CXX # How to hardcode a shared library path into an executable. hardcode_action=$hardcode_action_CXX # The directories searched by this compiler when creating a shared library. compiler_lib_search_dirs=$lt_compiler_lib_search_dirs_CXX # Dependencies to place before and after the objects being linked to # create a shared library. predep_objects=$lt_predep_objects_CXX postdep_objects=$lt_postdep_objects_CXX predeps=$lt_predeps_CXX postdeps=$lt_postdeps_CXX # The library search path used internally by the compiler when linking # a shared library. compiler_lib_search_path=$lt_compiler_lib_search_path_CXX # ### END LIBTOOL TAG CONFIG: CXX _LT_EOF patch -N -i "${srcdir}/m4/libtool-whole-archive.patch" libtool >&5 || \ { $as_echo "$as_me:${as_lineno-$LINENO}: WARNING: Could not patch libtool for static linking support. Loading modules into a statically linked daemon will fail." >&5 $as_echo "$as_me: WARNING: Could not patch libtool for static linking support. Loading modules into a statically linked daemon will fail." >&2;} ;; "Makefile":F) sed -e 's/^#AUTODERP# //' -i Makefile ;; "vtysh/extract.pl":F) chmod +x vtysh/extract.pl ;; "tools/frr":F) chmod +x tools/frr ;; "tools/watchfrr.sh":F) chmod +x tools/watchfrr.sh ;; "tools/frrinit.sh":F) chmod +x tools/frrinit.sh ;; "lib/route_types.h":C) dst="${ac_abs_top_builddir}/lib/route_types.h" ${PERL} "${ac_abs_top_srcdir}/lib/route_types.pl" \ < "${ac_abs_top_srcdir}/lib/route_types.txt" \ > "${dst}.tmp" test -f "${dst}" \ && diff "${dst}.tmp" "${dst}" >/dev/null 2>/dev/null \ && rm "${dst}.tmp" \ || mv "${dst}.tmp" "${dst}" ;; "lib/gitversion.h":C) dst="${ac_abs_top_builddir}/lib/gitversion.h" ${PERL} "${ac_abs_top_srcdir}/lib/gitversion.pl" \ "${ac_abs_top_srcdir}" \ > "${dst}.tmp" test -f "${dst}" \ && diff "${dst}.tmp" "${dst}" >/dev/null 2>/dev/null \ && rm "${dst}.tmp" \ || mv "${dst}.tmp" "${dst}" ;; esac done # for ac_tag as_fn_exit 0 _ACEOF ac_clean_files=$ac_clean_files_save test $ac_write_fail = 0 || as_fn_error $? "write failure creating $CONFIG_STATUS" "$LINENO" 5 # configure is writing to config.log, and then calls config.status. # config.status does its own redirection, appending to config.log. # Unfortunately, on DOS this fails, as config.log is still kept open # by configure, so config.status won't be able to write to it; its # output is simply discarded. So we exec the FD to /dev/null, # effectively closing config.log, so it can be properly (re)opened and # appended to by config.status. When coming back to configure, we # need to make the FD available again. if test "$no_create" != yes; then ac_cs_success=: ac_config_status_args= test "$silent" = yes && ac_config_status_args="$ac_config_status_args --quiet" exec 5>/dev/null $SHELL $CONFIG_STATUS $ac_config_status_args || ac_cs_success=false exec 5>>config.log # Use ||, not &&, to avoid exiting from the if with $? = 1, which # would make configure fail if this is the last instruction. $ac_cs_success || as_fn_exit 1 fi if test -n "$ac_unrecognized_opts" && test "$enable_option_checking" != no; then { $as_echo "$as_me:${as_lineno-$LINENO}: WARNING: unrecognized options: $ac_unrecognized_opts" >&5 $as_echo "$as_me: WARNING: unrecognized options: $ac_unrecognized_opts" >&2;} fi echo " FRRouting configuration ------------------------------ FRR version : ${PACKAGE_VERSION} host operating system : ${host_os} source code location : ${srcdir} compiler : ${CC} compiler flags : ${CFLAGS} ${SAN_FLAGS} make : ${MAKE-make} linker flags : ${LDFLAGS} ${SAN_FLAGS} ${LIBS} ${LIBCAP} ${LIBREADLINE} ${LIBM} state file directory : ${frr_statedir} config file directory : `eval echo \`echo ${sysconfdir}\`` example directory : `eval echo \`echo ${exampledir}\`` module directory : ${CFG_MODULE} user to run as : ${enable_user} group to run as : ${enable_group} group for vty sockets : ${enable_vty_group} config file mask : ${enable_configfile_mask} log file mask : ${enable_logfile_mask} zebra protobuf enabled : ${enable_protobuf:-no} The above user and group must have read/write access to the state file directory and to the config files in the config file directory." if test "${enable_doc}" != "no" -a "$frr_py_mod_sphinx" = false; then { $as_echo "$as_me:${as_lineno-$LINENO}: WARNING: sphinx is missing but required to build documentation" >&5 $as_echo "$as_me: WARNING: sphinx is missing but required to build documentation" >&2;} fi if test "$frr_py_mod_pytest" = false; then { $as_echo "$as_me:${as_lineno-$LINENO}: WARNING: pytest is missing, unit tests cannot be performed" >&5 $as_echo "$as_me: WARNING: pytest is missing, unit tests cannot be performed" >&2;} fi frr-7.2.1/configure.ac0000755000000000000000000021610213610377563011507 00000000000000## ## Configure template file for FRRouting. ## autoconf will generate a configure script. ## ## Copyright (c) 1996, 97, 98, 99, 2000 Kunihiro Ishiguro ## Portions Copyright (c) 2003 Paul Jakma ## AC_PREREQ([2.60]) AC_INIT([frr], [7.2.1], [https://github.com/frrouting/frr/issues]) PACKAGE_URL="https://frrouting.org/" AC_SUBST([PACKAGE_URL]) PACKAGE_FULLNAME="FRRouting" AC_SUBST([PACKAGE_FULLNAME]) CONFIG_ARGS="`echo $ac_configure_args | sed -e \"s% '[[A-Z]]*FLAGS=[[^']]\+'%%g\"`" AC_SUBST([CONFIG_ARGS]) AC_CONFIG_SRCDIR([lib/zebra.h]) AC_CONFIG_MACRO_DIR([m4]) dnl ----------------------------------- dnl Get hostname and other information. dnl ----------------------------------- AC_CANONICAL_BUILD() AC_CANONICAL_HOST() hosttools_clippy="false" build_clippy="true" dnl case 1: external clippy if test -n "$with_clippy" -a "$with_clippy" != "no" -a "$with_clippy" != "yes"; then if test "$enable_clippy_only" == "yes"; then AC_MSG_ERROR([--enable-clippy-only does not make sense with --with-clippy]) fi CLIPPY="$with_clippy" build_clippy="false" if test ! -x "$with_clippy"; then AC_MSG_ERROR([clippy tool ($with_clippy) is not executable]) fi dnl case 2: cross-compiling internal clippy elif test "$host" != "$build"; then if test "$srcdir" = "."; then AC_MSG_ERROR([cross-compilation is only possible with builddir separate from srcdir or by building clippy separately and using the --with-clippy option. create a separate directory and run as .../path-to-frr/configure.]) fi test -d hosttools || mkdir hosttools abssrc="`cd \"${srcdir}\"; pwd`" AC_MSG_NOTICE([...]) AC_MSG_NOTICE([... cross-compilation: creating hosttools directory and self-configuring for build platform tools]) AC_MSG_NOTICE([... use HOST_CPPFLAGS / HOST_CFLAGS / HOST_LDFLAGS if neccessary]) AC_MSG_NOTICE([...]) ( for var in $ac_precious_vars; do dnl special cases case "$var" in YACC|YFLAGS) continue;; PYTHON*) retain=true;; *) retain=false; esac eval "hostvar=\"\${HOST_$var}\"" eval "targetvar=\"\${$var}\"" if test -n "$hostvar"; then eval "$var='$hostvar'" _AS_ECHO_LOG([host $var='$hostvar']) elif $retain; then _AS_ECHO_LOG([host retain $var='$targetvar']) else eval "unset $var" _AS_ECHO_LOG([host unset $var]) fi done cd hosttools "${abssrc}/configure" "--host=$build" "--build=$build" "--enable-clippy-only" "--disable-nhrpd" "--disable-vtysh" ) || exit 1 AC_MSG_NOTICE([...]) AC_MSG_NOTICE([... cross-compilation: finished self-configuring for build platform tools]) AC_MSG_NOTICE([...]) build_clippy="false" hosttools_clippy="true" CLIPPY="hosttools/lib/clippy" dnl case 3: normal build internal clippy else CLIPPY="lib/clippy\$(EXEEXT)" fi AC_SUBST([CLIPPY]) AM_CONDITIONAL([BUILD_CLIPPY], [$build_clippy]) AM_CONDITIONAL([HOSTTOOLS_CLIPPY], [$hosttools_clippy]) AM_CONDITIONAL([ONLY_CLIPPY], [test "$enable_clippy_only" = "yes"]) # Disable portability warnings -- our automake code (in particular # common.am) uses some constructs specific to gmake. AM_INIT_AUTOMAKE([1.12 -Wno-portability foreign]) m4_ifndef([AM_SILENT_RULES], [m4_define([AM_SILENT_RULES],[])]) AM_SILENT_RULES([yes]) AC_CONFIG_HEADERS([config.h]) AC_PATH_PROG([PERL], [perl]) PKG_PROG_PKG_CONFIG dnl default is to match previous behavior exampledir=${sysconfdir} AC_ARG_ENABLE([exampledir], AS_HELP_STRING([--enable-exampledir], [specify alternate directory for examples]), exampledir="$enableval",) dnl XXX add --exampledir to autoconf standard directory list somehow AC_SUBST([exampledir]) dnl default is to match previous behavior pkgsrcrcdir="" AC_ARG_ENABLE([pkgsrcrcdir], AS_HELP_STRING([--enable-pkgsrcrcdir], [specify directory for rc.d scripts]), pkgsrcrcdir="$enableval",) dnl XXX add --pkgsrcrcdir to autoconf standard directory list somehow AC_SUBST([pkgsrcrcdir]) AM_CONDITIONAL([PKGSRC], [test "x$pkgsrcrcdir" != "x"]) AC_ARG_WITH([moduledir], [AS_HELP_STRING([--with-moduledir=DIR], [module directory (${libdir}/frr/modules)])], [ moduledir="$withval" ], [ moduledir="\${libdir}/frr/modules" ]) AC_SUBST([moduledir], [$moduledir]) AC_ARG_WITH([yangmodelsdir], [AS_HELP_STRING([--with-yangmodelsdir=DIR], [yang models directory (${datarootdir}/yang)])], [ yangmodelsdir="$withval" ], [ yangmodelsdir="\${datarootdir}/yang" ]) AC_SUBST([yangmodelsdir]) AC_ARG_ENABLE(tcmalloc, AS_HELP_STRING([--enable-tcmalloc], [Turn on tcmalloc]), [case "${enableval}" in yes) tcmalloc_enabled=true LIBS="$LIBS -ltcmalloc_minimal" ;; no) tcmalloc_enabled=false ;; *) AC_MSG_ERROR([bad value ${enableval} for --enable-tcmalloc]) ;; esac],[tcmalloc_enabled=false]) dnl Thanks autoconf, but we don't want a default -g -O2. We have our own dnl flag determination logic. CFLAGS="${CFLAGS:-}" dnl -------------------- dnl Check CC and friends dnl -------------------- dnl note orig_cflags is also used further down orig_cflags="$CFLAGS" orig_cxxflags="$CXXFLAGS" AC_LANG([C]) AC_PROG_CC AC_PROG_CPP AC_PROG_CXX AM_PROG_CC_C_O dnl remove autoconf default "-g -O2" CFLAGS="$orig_cflags" CXXFLAGS="$orig_cxxflags" AC_PROG_CC_C99 dnl NB: see C11 below PKG_PROG_PKG_CONFIG dnl it's 2019, sed is sed. SED=sed AC_SUBST([SED]) dnl try and enable CFLAGS that are useful for FRR dnl - specifically, options to control warnings AC_USE_SYSTEM_EXTENSIONS AC_DEFUN([AC_C_FLAG], [{ m4_pushdef([cachename],[m4_translit([frr_cv_$1],[ =-+],[____])]) AC_CACHE_CHECK([[whether $CC supports $1]], cachename, [ AC_LANG_PUSH([C]) ac_c_flag_save="$CFLAGS" CFLAGS="$CFLAGS $1" AC_COMPILE_IFELSE( [AC_LANG_PROGRAM([[]])], [ cachename=yes ], [ cachename=no ]) CFLAGS="$ac_c_flag_save" AC_LANG_POP([C]) ]) if test "${cachename}" = yes; then m4_if([$3], [], [CFLAGS="$CFLAGS $1"], [$3]) else : $2 fi m4_popdef([cachename]) }]) AC_DEFUN([AC_LINK_IFELSE_FLAGS], [{ AC_LANG_PUSH([C]) ac_cflags_save="$CFLAGS" ac_libs_save="$LIBS" CFLAGS="$CFLAGS $1" LIBS="$LIBS $2" AC_LINK_IFELSE( [$3], [ CFLAGS="$ac_cflags_save" LIBS="$ac_libs_save" m4_default([$5], [ AC_MSG_RESULT([yes]) ]) ], [ CFLAGS="$ac_cflags_save" LIBS="$ac_libs_save" m4_default([$4], [ AC_MSG_RESULT([no]) ]) ]) AC_LANG_POP([C]) }]) dnl ICC won't bail on unknown options without -diag-error 10006 dnl need to do this first so we get useful results for the other options AC_C_FLAG([-diag-error 10006]) dnl AC_PROG_CC_C99 may change CC to include -std=gnu99 or something ac_cc="$CC" CC="${CC% -std=gnu99}" CC="${CC% -std=c99}" AC_C_FLAG([-std=gnu11], [CC="$ac_cc"], [CC="$CC -std=gnu11"]) dnl if the user has specified any CFLAGS, override our settings if test "x${enable_gcov}" = "xyes"; then if test "z$orig_cflags" = "z"; then AC_C_FLAG([-coverage]) AC_C_FLAG([-O0]) fi LDFLAGS="${LDFLAGS} -lgcov" elif test "x${enable_dev_build}" = "xyes"; then AC_DEFINE([DEV_BUILD], [1], [Build for development]) if test "z$orig_cflags" = "z"; then AC_C_FLAG([-g3]) AC_C_FLAG([-O0]) fi if test "x${enable_lua}" = "xyes"; then AX_PROG_LUA([5.3]) AX_LUA_HEADERS AX_LUA_LIBS([ AC_DEFINE([HAVE_LUA], [1], [Have support for Lua interpreter]) LIBS="$LIBS $LUA_LIB" ]) fi else if test "x${enable_lua}" = "xyes"; then AC_MSG_ERROR([Lua is not meant to be built/used outside of development at this time]) fi if test "z$orig_cflags" = "z"; then AC_C_FLAG([-g]) AC_C_FLAG([-O2]) fi fi AM_CONDITIONAL([DEV_BUILD], [test "x$enable_dev_build" = "xyes"]) dnl always want these CFLAGS AC_C_FLAG([-fno-omit-frame-pointer]) AC_C_FLAG([-funwind-tables]) AC_C_FLAG([-Wall]) AC_C_FLAG([-Wextra]) AC_C_FLAG([-Wmissing-prototypes]) AC_C_FLAG([-Wmissing-declarations]) AC_C_FLAG([-Wpointer-arith]) AC_C_FLAG([-Wbad-function-cast]) AC_C_FLAG([-Wwrite-strings]) if test x"${enable_gcc_ultra_verbose}" = x"yes" ; then AC_C_FLAG([-Wcast-qual]) AC_C_FLAG([-Wstrict-prototypes]) AC_C_FLAG([-Wmissing-noreturn]) AC_C_FLAG([-Wmissing-format-attribute]) AC_C_FLAG([-Wunreachable-code]) AC_C_FLAG([-Wpacked]) AC_C_FLAG([-Wpadded]) AC_C_FLAG([-Wshadow]) else AC_C_FLAG([-Wno-unused-result]) fi AC_C_FLAG([-Wno-unused-parameter]) AC_C_FLAG([-Wno-missing-field-initializers]) AC_C_FLAG([-Wc++-compat], [], [CXX_COMPAT_CFLAGS="-Wc++-compat"]) AC_SUBST([CXX_COMPAT_CFLAGS]) dnl ICC emits a broken warning for const char *x = a ? "b" : "c"; dnl for some reason the string consts get 'promoted' to char *, dnl triggering a const to non-const conversion warning. AC_C_FLAG([-diag-disable 3179]) if test x"${enable_werror}" = x"yes" ; then WERROR="-Werror" fi AC_SUBST([WERROR]) SAN_FLAGS="" if test "$enable_address_sanitizer" = "yes"; then AC_C_FLAG([-fsanitize=address], [ AC_MSG_ERROR([$CC does not support Address Sanitizer.]) ], [ SAN_FLAGS="$SAN_FLAGS -fsanitize=address" ]) fi if test "$enable_thread_sanitizer" = "yes"; then AC_C_FLAG([-fsanitize=thread], [ AC_MSG_ERROR([$CC does not support Thread Sanitizer.]) ], [ SAN_FLAGS="$SAN_FLAGS -fsanitize=thread" ]) fi if test "$enable_memory_sanitizer" = "yes"; then AC_C_FLAG([-fsanitize=thread -fPIE -pie], [ AC_MSG_ERROR([$CC does not support Thread Sanitizer.]) ], [ SAN_FLAGS="-fsanitize=memory -fPIE -pie" ]) fi AC_SUBST([SAN_FLAGS]) dnl ---------- dnl Essentials dnl ---------- AX_PTHREAD([ CC="$PTHREAD_CC" CFLAGS="$CFLAGS $PTHREAD_CFLAGS" LIBS="$PTHREAD_LIBS $LIBS" ], [ AC_MSG_FAILURE([This FRR version needs pthreads]) ]) AC_SEARCH_LIBS([pthread_condattr_setclock], [], [frr_cv_pthread_condattr_setclock=yes], [frr_cv_pthread_condattr_setclock=no]) if test "$frr_cv_pthread_condattr_setclock" = yes; then AC_DEFINE([HAVE_PTHREAD_CONDATTR_SETCLOCK], [1], [Have pthread.h pthread_condattr_setclock]) fi dnl -------------- dnl Check programs dnl -------------- AC_PROG_INSTALL AC_PROG_LN_S AC_CHECK_TOOL([AR], [ar]) dnl ------- dnl libtool dnl ------- AC_ARG_ENABLE([static-bin], AS_HELP_STRING([--enable-static-bin], [link binaries statically])) LT_INIT _LT_CONFIG_LIBTOOL([ patch -N -i "${srcdir}/m4/libtool-whole-archive.patch" libtool >&AS_MESSAGE_LOG_FD || \ AC_MSG_WARN([Could not patch libtool for static linking support. Loading modules into a statically linked daemon will fail.]) ]) if test "$enable_static_bin" = "yes"; then AC_LDFLAGS="-static" if test "$enable_static" != "yes"; then AC_MSG_ERROR([The --enable-static-bin option must be combined with --enable-static.]) fi fi if test "$enable_shared" != "yes"; then AC_MSG_ERROR([FRR cannot be built with --disable-shared. If you want statically linked daemons, use --enable-shared --enable-static --enable-static-bin]) fi AC_SUBST([AC_LDFLAGS]) AM_CONDITIONAL([STATIC_BIN], [test "x$enable_static_bin" = "xyes"]) dnl $AR and $RANLIB are set by LT_INIT above AC_MSG_CHECKING([whether $AR supports D option]) if $AR crD conftest.a >/dev/null 2>/dev/null; then AC_MSG_RESULT([yes]) dnl ARFLAGS is for automake, AR_FLAGS for libtool m-( ARFLAGS="crD" AR_FLAGS="crD" else AC_MSG_RESULT([no]) ARFLAGS="cru" AR_FLAGS="cru" fi AC_SUBST([ARFLAGS]) AC_SUBST([AR_FLAGS]) AC_MSG_CHECKING([whether $RANLIB supports D option]) if $RANLIB -D conftest.a >conftest.err 2>&1; then if grep -q -- '-D' conftest.err; then AC_MSG_RESULT([no]) else AC_MSG_RESULT([yes]) RANLIB="$RANLIB -D" fi else AC_MSG_RESULT([no]) fi AC_SUBST([RANLIB]) test -f conftest.err && rm conftest.err test -f conftest.a && rm conftest.a dnl ---------------------- dnl Packages configuration dnl ---------------------- if test -f config.version; then . ./config.version elif test -f "${srcdir}/config.version"; then . "${srcdir}/config.version" fi AC_ARG_WITH([pkg-extra-version], AS_HELP_STRING([--with-pkg-extra-version=VER], [add extra version field, for packagers/distributions]), [ if test "$withval" = "no"; then EXTRAVERSION= else EXTRAVERSION=$withval fi ], []) AC_ARG_WITH([pkg-git-version], AS_HELP_STRING([--with-pkg-git-version], [add git information to MOTD and build version string]), [ test "x$withval" != "xno" && with_pkg_git_version="yes" ]) AC_ARG_WITH([clippy], AS_HELP_STRING([--with-clippy=PATH], [use external clippy helper program])) AC_ARG_WITH([vtysh_pager], AS_HELP_STRING([--with-vtysh-pager=PAGER], [control what pager is compiled in as default]), VTYSH_PAGER=$withval, VTYSH_PAGER="more") AC_ARG_ENABLE([vtysh], AS_HELP_STRING([--disable-vtysh], [do not build integrated vty shell for FRR])) AC_ARG_ENABLE([doc], AS_HELP_STRING([--disable-doc], [do not build docs])) AC_ARG_ENABLE([doc-html], AS_HELP_STRING([--enable-doc-html], [build HTML docs])) AC_ARG_ENABLE([zebra], AS_HELP_STRING([--disable-zebra], [do not build zebra daemon])) AC_ARG_ENABLE([bgpd], AS_HELP_STRING([--disable-bgpd], [do not build bgpd])) AC_ARG_ENABLE([ripd], AS_HELP_STRING([--disable-ripd], [do not build ripd])) AC_ARG_ENABLE([ripngd], AS_HELP_STRING([--disable-ripngd], [do not build ripngd])) AC_ARG_ENABLE([ospfd], AS_HELP_STRING([--disable-ospfd], [do not build ospfd])) AC_ARG_ENABLE([ospf6d], AS_HELP_STRING([--disable-ospf6d], [do not build ospf6d])) AC_ARG_ENABLE([ldpd], AS_HELP_STRING([--disable-ldpd], [do not build ldpd])) AC_ARG_ENABLE([nhrpd], AS_HELP_STRING([--disable-nhrpd], [do not build nhrpd])) AC_ARG_ENABLE([eigrpd], AS_HELP_STRING([--disable-eigrpd], [do not build eigrpd])) AC_ARG_ENABLE([babeld], AS_HELP_STRING([--disable-babeld], [do not build babeld])) AC_ARG_ENABLE([watchfrr], AS_HELP_STRING([--disable-watchfrr], [do not build watchfrr])) AC_ARG_ENABLE([isisd], AS_HELP_STRING([--disable-isisd], [do not build isisd])) AC_ARG_ENABLE([pimd], AS_HELP_STRING([--disable-pimd], [do not build pimd])) AC_ARG_ENABLE([pbrd], AS_HELP_STRING([--disable-pbrd], [do not build pbrd])) AC_ARG_ENABLE([sharpd], AS_HELP_STRING([--enable-sharpd], [build sharpd])) AC_ARG_ENABLE([staticd], AS_HELP_STRING([--disable-staticd], [do not build staticd])) AC_ARG_ENABLE([fabricd], AS_HELP_STRING([--disable-fabricd], [do not build fabricd])) AC_ARG_ENABLE([vrrpd], AS_HELP_STRING([--disable-vrrpd], [do not build vrrpd])) AC_ARG_ENABLE([bgp-announce], AS_HELP_STRING([--disable-bgp-announce,], [turn off BGP route announcement])) AC_ARG_ENABLE([bgp-vnc], AS_HELP_STRING([--disable-bgp-vnc],[turn off BGP VNC support])) AC_ARG_ENABLE([bgp-bmp], AS_HELP_STRING([--disable-bgp-bmp],[turn off BGP BMP support])) AC_ARG_ENABLE([snmp], AS_HELP_STRING([--enable-snmp], [enable SNMP support for agentx])) AC_ARG_ENABLE([config_rollbacks], AS_HELP_STRING([--enable-config-rollbacks], [enable configuration rollbacks (requires sqlite3)])) AC_ARG_ENABLE([confd], AS_HELP_STRING([--enable-confd=ARG], [enable confd integration])) AC_ARG_ENABLE([sysrepo], AS_HELP_STRING([--enable-sysrepo], [enable sysrepo integration])) AC_ARG_ENABLE([grpc], AS_HELP_STRING([--enable-grpc], [enable the gRPC northbound plugin])) AC_ARG_ENABLE([zeromq], AS_HELP_STRING([--enable-zeromq], [enable ZeroMQ handler (libfrrzmq)])) AC_ARG_WITH([libpam], AS_HELP_STRING([--with-libpam], [use libpam for PAM support in vtysh])) AC_ARG_ENABLE([ospfapi], AS_HELP_STRING([--disable-ospfapi], [do not build OSPFAPI to access the OSPF LSA Database])) AC_ARG_ENABLE([ospfclient], AS_HELP_STRING([--disable-ospfclient], [do not build OSPFAPI client for OSPFAPI, (this is the default if --disable-ospfapi is set)])) AC_ARG_ENABLE([multipath], AS_HELP_STRING([--enable-multipath=ARG], [enable multipath function, ARG must be digit])) AC_ARG_ENABLE([user], AS_HELP_STRING([--enable-user=USER], [user to run FRR suite as (default frr)])) AC_ARG_ENABLE([group], AS_HELP_STRING([--enable-group=GROUP], [group to run FRR suite as (default frr)])) AC_ARG_ENABLE([vty_group], AS_HELP_STRING([--enable-vty-group=ARG], [set vty sockets to have specified group as owner])) AC_ARG_ENABLE([configfile_mask], AS_HELP_STRING([--enable-configfile-mask=ARG], [set mask for config files])) AC_ARG_ENABLE([logfile_mask], AS_HELP_STRING([--enable-logfile-mask=ARG], [set mask for log files])) AC_ARG_ENABLE([shell_access], AS_HELP_STRING([--enable-shell-access], [Allow users to access shell/telnet/ssh])) AC_ARG_ENABLE([realms], AS_HELP_STRING([--enable-realms], [enable REALMS support under Linux])) AC_ARG_ENABLE([rtadv], AS_HELP_STRING([--disable-rtadv], [disable IPV6 router advertisement feature])) AC_ARG_ENABLE([irdp], AS_HELP_STRING([--disable-irdp], [enable IRDP server support in zebra (default if supported)])) AC_ARG_ENABLE([capabilities], AS_HELP_STRING([--disable-capabilities], [disable using POSIX capabilities])) AC_ARG_ENABLE([rusage], AS_HELP_STRING([--disable-rusage], [disable using getrusage])) AC_ARG_ENABLE([gcc_ultra_verbose], AS_HELP_STRING([--enable-gcc-ultra-verbose], [enable ultra verbose GCC warnings])) AC_ARG_ENABLE([backtrace], AS_HELP_STRING([--disable-backtrace,], [disable crash backtraces (default autodetect)])) AC_ARG_ENABLE([time-check], AS_HELP_STRING([--disable-time-check], [disable slow thread warning messages])) AC_ARG_ENABLE([pcreposix], AS_HELP_STRING([--enable-pcreposix], [enable using PCRE Posix libs for regex functions])) AC_ARG_ENABLE([fpm], AS_HELP_STRING([--enable-fpm], [enable Forwarding Plane Manager support])) AC_ARG_ENABLE([systemd], AS_HELP_STRING([--enable-systemd], [enable Systemd support])) AC_ARG_ENABLE([werror], AS_HELP_STRING([--enable-werror], [enable -Werror (recommended for developers only)])) AC_ARG_ENABLE([cumulus], AS_HELP_STRING([--enable-cumulus], [enable Cumulus Switch Special Extensions])) AC_ARG_ENABLE([datacenter], AS_HELP_STRING([--enable-datacenter], [enable Compilation for Data Center Extensions])) AC_ARG_ENABLE([fuzzing], AS_HELP_STRING([--enable-fuzzing], [enable ability to fuzz various parts of FRR])) AC_ARG_ENABLE([netlink_fuzzing], AS_HELP_STRING([--enable-netlink-fuzzing], [enable ability to fuzz netlink listening socket in zebra])) AC_ARG_ENABLE([rr-semantics], AS_HELP_STRING([--disable-rr-semantics], [disable the v6 Route Replace semantics])) AC_ARG_ENABLE([protobuf], AS_HELP_STRING([--enable-protobuf], [Enable experimental protobuf support])) AC_ARG_ENABLE([oldvpn_commands], AS_HELP_STRING([--enable-oldvpn-commands], [Keep old vpn commands])) AC_ARG_ENABLE([rpki], AS_HELP_STRING([--enable-rpki], [enable RPKI prefix validation support])) AC_ARG_ENABLE([clippy-only], AS_HELP_STRING([--enable-clippy-only], [Only build clippy])) AC_ARG_ENABLE([numeric_version], AS_HELP_STRING([--enable-numeric-version], [Only numeric digits allowed in version (for Alpine)])) AC_ARG_ENABLE([gcov], AS_HELP_STRING([--enable-gcov], [Add code coverage information])) AC_ARG_ENABLE([bfdd], AS_HELP_STRING([--disable-bfdd], [do not build bfdd])) AC_ARG_ENABLE([address-sanitizer], AS_HELP_STRING([--enable-address-sanitizer], [enable AddressSanitizer support for detecting a wide variety of memory allocation and deallocation errors])) AC_ARG_ENABLE([thread-sanitizer], AS_HELP_STRING([--enable-thread-sanitizer], [enable ThreadSanitizer support for detecting data races])) AC_ARG_ENABLE([memory-sanitizer], AS_HELP_STRING([--enable-memory-sanitizer], [enable MemorySanitizer support for detecting uninitialized memory reads])) AC_ARG_WITH([crypto], AS_HELP_STRING([--with-crypto=], [choose between different implementations of cryptographic functions(default value is --with-crypto=internal)])) #if openssl, else use the internal AS_IF([test x"${with_crypto}" = x"openssl"], [ AC_CHECK_LIB([crypto], [EVP_DigestInit], [LIBS="$LIBS -lcrypto"], [], []) if test $ac_cv_lib_crypto_EVP_DigestInit = no; then AC_MSG_ERROR([build with openssl has been specified but openssl library was not found on your system]) else AC_DEFINE([CRYPTO_OPENSSL], [1], [Compile with openssl support]) fi ], [test x"${with_crypto}" = x"internal" || test x"${with_crypto}" = x"" ], [AC_DEFINE([CRYPTO_INTERNAL], [1], [Compile with internal cryptographic implementation]) ], [AC_MSG_ERROR([Unknown value for --with-crypto])] ) AS_IF([test "${enable_clippy_only}" != "yes"], [ AC_CHECK_HEADERS([json-c/json.h]) AC_CHECK_LIB([json-c], [json_object_get], [LIBS="$LIBS -ljson-c"], [], [-lm]) if test "$ac_cv_lib_json_c_json_object_get" = no; then AC_CHECK_LIB([json], [json_object_get], [LIBS="$LIBS -ljson"]) if test "$ac_cv_lib_json_json_object_get" = no; then AC_MSG_ERROR([libjson is needed to compile]) fi fi ]) AC_ARG_ENABLE([dev_build], AS_HELP_STRING([--enable-dev-build], [build for development])) AC_ARG_ENABLE([lua], AS_HELP_STRING([--enable-lua], [Build Lua scripting])) if test x"${enable_time_check}" != x"no" ; then if test x"${enable_time_check}" = x"yes" -o x"${enable_time_check}" = x ; then AC_DEFINE([CONSUMED_TIME_CHECK], [5000000], [Consumed Time Check]) else AC_DEFINE_UNQUOTED([CONSUMED_TIME_CHECK], [$enable_time_check], [Consumed Time Check]) fi fi case "${enable_systemd}" in "no") ;; "yes") AC_CHECK_LIB([systemd], [sd_notify], [LIBS="$LIBS -lsystemd"]) if test $ac_cv_lib_systemd_sd_notify = no; then AC_MSG_ERROR([enable systemd has been specified but systemd development env not found on your system]) else AC_DEFINE([HAVE_SYSTEMD], [1], [Compile systemd support in]) fi ;; "*") ;; esac if test "${enable_rr_semantics}" != "no" ; then AC_DEFINE([HAVE_V6_RR_SEMANTICS], [1], [Compile in v6 Route Replacement Semantics]) fi if test "${enable_datacenter}" = "yes" ; then AC_DEFINE([HAVE_DATACENTER], [1], [Compile extensions for a DataCenter]) DFLT_NAME="datacenter" else DFLT_NAME="traditional" fi if test "${enable_fuzzing}" = "yes" ; then AC_DEFINE([HANDLE_ZAPI_FUZZING], [1], [Compile extensions to use with a fuzzer]) fi if test "${enable_netlink_fuzzing}" = "yes" ; then AC_DEFINE([HANDLE_NETLINK_FUZZING], [1], [Compile extensions to use with a fuzzer for netlink]) fi if test "${enable_cumulus}" = "yes" ; then AC_DEFINE([HAVE_CUMULUS], [1], [Compile Special Cumulus Code in]) fi AC_SUBST([DFLT_NAME]) AC_DEFINE_UNQUOTED([DFLT_NAME], ["$DFLT_NAME"], [Name of the configuration default set]) if test "${enable_shell_access}" = "yes"; then AC_DEFINE([HAVE_SHELL_ACCESS], [1], [Allow user to use ssh/telnet/bash, be aware this is considered insecure]) fi # # Python for clippy # AS_IF([test "$host" = "$build"], [ FRR_PYTHON_DEV ], [ FRR_PYTHON ]) FRR_PYTHON_MODULES([pytest]) if test "${enable_doc}" != "no"; then FRR_PYTHON_MODULES([sphinx], , [ if test "${enable_doc}" = "yes"; then AC_MSG_ERROR([Documentation was explicitly requested with --enable-doc but sphinx is not available for $PYTHON. Please disable docs or install sphinx.]) fi ]) fi AM_CONDITIONAL([DOC], [test "${enable_doc}" != "no" -a "$frr_py_mod_sphinx" != "false"]) AM_CONDITIONAL([DOC_HTML], [test "${enable_doc_html}" = "yes"]) FRR_PYTHON_MOD_EXEC([sphinx], [--version], [ PYSPHINX="-m sphinx" ], [ PYSPHINX="-c 'import sys; from sphinx import main; sys.exit(main(sys.argv))'" ]) AC_SUBST([PYSPHINX]) # # Logic for old vpn commands support. # if test "$enable_oldvpn_commands" = "yes"; then AC_DEFINE([KEEP_OLD_VPN_COMMANDS], [1], [Define for compiling with old vpn commands]) fi # # End of logic for protobuf support. # AC_MSG_CHECKING([if zebra should be configurable to send Route Advertisements]) if test "${enable_rtadv}" != "no"; then AC_MSG_RESULT([yes]) AC_DEFINE([HAVE_RTADV], [1], [Enable IPv6 Routing Advertisement support]) else AC_MSG_RESULT([no]) fi if test x"${enable_user}" = x"no"; then enable_user="" else if test x"${enable_user}" = x"yes" || test x"${enable_user}" = x""; then enable_user="frr" fi AC_DEFINE_UNQUOTED([FRR_USER], ["${enable_user}"], [frr User]) fi if test x"${enable_group}" = x"no"; then enable_group="" else if test x"${enable_group}" = x"yes" || test x"${enable_group}" = x""; then enable_group="frr" fi AC_DEFINE_UNQUOTED([FRR_GROUP], ["${enable_group}"], [frr Group]) fi if test x"${enable_vty_group}" = x"yes" ; then AC_MSG_ERROR([--enable-vty-group requires a group as argument, not yes]) elif test x"${enable_vty_group}" != x""; then if test x"${enable_vty_group}" != x"no"; then AC_DEFINE_UNQUOTED([VTY_GROUP], ["${enable_vty_group}"], [VTY Sockets Group]) fi fi AC_SUBST([enable_user]) AC_SUBST([enable_group]) AC_SUBST([enable_vty_group]) enable_configfile_mask=${enable_configfile_mask:-0600} AC_DEFINE_UNQUOTED([CONFIGFILE_MASK], [${enable_configfile_mask}], [Mask for config files]) enable_logfile_mask=${enable_logfile_mask:-0600} AC_DEFINE_UNQUOTED([LOGFILE_MASK], [${enable_logfile_mask}], [Mask for log files]) MPATH_NUM=16 case "${enable_multipath}" in 0) MPATH_NUM=64 ;; [[1-9]|[1-9][0-9]|[1-9][0-9][0-9]]) MPATH_NUM="${enable_multipath}" ;; "") ;; *) AC_MSG_FAILURE([Please specify digit to enable multipath ARG]) ;; esac AC_DEFINE_UNQUOTED([MULTIPATH_NUM], [$MPATH_NUM], [Maximum number of paths for a route]) AC_DEFINE_UNQUOTED([VTYSH_PAGER], ["$VTYSH_PAGER"], [What pager to use]) dnl -------------------- dnl Enable code coverage dnl -------------------- AM_CONDITIONAL([HAVE_GCOV], [test '!' "$enable_gcov" = no]) dnl ------------------------------------ dnl Alpine only accepts numeric versions dnl ------------------------------------ if test "x${enable_numeric_version}" != "x" ; then VERSION="`echo ${VERSION} | tr -c -d '[[.0-9]]'`" PACKAGE_VERSION="`echo ${PACKAGE_VERSION} | tr -c -d '[[.0-9]]'`" fi dnl ----------------------------------- dnl Add extra version string to package dnl name, string and version fields. dnl ----------------------------------- if test "x${EXTRAVERSION}" != "x" ; then VERSION="${VERSION}${EXTRAVERSION}" PACKAGE_VERSION="${PACKAGE_VERSION}${EXTRAVERSION}" AC_SUBST(PACKAGE_EXTRAVERSION, ["${EXTRAVERSION}"]) PACKAGE_STRING="${PACKAGE_STRING}${EXTRAVERSION}" fi AC_SUBST([EXTRAVERSION]) if test "x$with_pkg_git_version" = "xyes"; then if test -d "${srcdir}/.git"; then AC_DEFINE([GIT_VERSION], [1], [include git version info]) else with_pkg_git_version="no" AC_MSG_WARN([--with-pkg-git-version given, but this is not a git checkout]) fi fi AM_CONDITIONAL([GIT_VERSION], [test "x$with_pkg_git_version" = "xyes"]) AC_CHECK_TOOL([OBJCOPY], [objcopy], [:]) if test "x${OBJCOPY}" != "x:"; then AC_CACHE_CHECK([for .interp value to use], [frr_cv_interp], [ frr_cv_interp="" AC_LINK_IFELSE([AC_LANG_SOURCE([[int main() { return 0; }]])], [ if $OBJCOPY -j.interp -Obinary conftest conftest.interp; then frr_cv_interp="`xargs -0 echo < conftest.interp`" fi test -f conftest.interp && rm conftest.interp ]) ]) fi if test -n "$frr_cv_interp"; then AC_DEFINE_UNQUOTED([INTERP], ["$frr_cv_interp"], [.interp value]) fi dnl ------------------------- dnl Check other header files. dnl ------------------------- AC_CHECK_HEADERS([stropts.h sys/ksym.h \ linux/version.h asm/types.h]) ac_stdatomic_ok=false AC_DEFINE([FRR_AUTOCONF_ATOMIC], [1], [did autoconf checks for atomic funcs]) AC_CHECK_HEADER([stdatomic.h],[ AC_MSG_CHECKING([whether _Atomic qualifier works]) AC_LINK_IFELSE([AC_LANG_SOURCE([[ #include int main(int argc, char **argv) { _Atomic int i = 0; return i; } ]])], [ AC_DEFINE([HAVE_STDATOMIC_H], [1], [found stdatomic.h]) AC_MSG_RESULT([yes]) ac_stdatomic_ok=true ], [ AC_MSG_RESULT([no]) ]) ]) AS_IF([$ac_stdatomic_ok], [true], [ AC_MSG_CHECKING([for __atomic_* builtins]) AC_LINK_IFELSE([AC_LANG_SOURCE([[ int main(int argc, char **argv) { volatile int i = 1; __atomic_store_n (&i, 0, __ATOMIC_RELEASE); return __atomic_load_n (&i, __ATOMIC_ACQUIRE); } ]])], [ AC_DEFINE([HAVE___ATOMIC], [1], [found __atomic builtins]) AC_MSG_RESULT([yes]) ], [ AC_MSG_RESULT([no]) dnl FreeBSD 9 has a broken stdatomic.h where _Atomic doesn't work AC_MSG_CHECKING([for __sync_* builtins]) AC_LINK_IFELSE([AC_LANG_SOURCE([[ int main(int argc, char **argv) { volatile int i = 1; __sync_fetch_and_sub (&i, 1); return __sync_val_compare_and_swap (&i, 0, 1); } ]])], [ AC_DEFINE([HAVE___SYNC], [1], [found __sync builtins]) AC_MSG_RESULT([yes]) AC_MSG_CHECKING([for __sync_swap builtin]) AC_LINK_IFELSE([AC_LANG_SOURCE([[ int main(int argc, char **argv) { volatile int i = 1; return __sync_swap (&i, 2); } ]])], [ AC_DEFINE([HAVE___SYNC_SWAP], 1, [found __sync_swap builtin]) AC_MSG_RESULT([yes]) ], [ AC_MSG_RESULT([no]) ]) ], [ AC_MSG_RESULT([no]) AC_MSG_FAILURE([stdatomic.h unavailable and $CC has neither __atomic nor __sync builtins]) ]) ]) ]) AC_CHECK_HEADERS([pthread_np.h],,, [ #include ]) AC_CHECK_FUNCS([pthread_setname_np pthread_set_name_np]) needsync=true AS_IF([$needsync], [ dnl Linux AC_MSG_CHECKING([for Linux futex() support]) AC_LINK_IFELSE([AC_LANG_PROGRAM([ #ifndef _GNU_SOURCE #define _GNU_SOURCE #endif #include #include #include #include #include int main(void); ], [ { return syscall(SYS_futex, NULL, FUTEX_WAIT, 0, NULL, NULL, 0); } ])], [ AC_MSG_RESULT([yes]) AC_DEFINE(HAVE_SYNC_LINUX_FUTEX,,Have Linux futex support) needsync=false ], [ AC_MSG_RESULT([no]) ]) ]) AS_IF([$needsync], [ dnl FreeBSD AC_MSG_CHECKING([for FreeBSD _umtx_op() support]) AC_LINK_IFELSE([AC_LANG_PROGRAM([ #include #include #include #include int main(void); ], [ { return _umtx_op(NULL, UMTX_OP_WAIT_UINT, 0, NULL, NULL); } ])], [ AC_MSG_RESULT([yes]) AC_DEFINE(HAVE_SYNC_UMTX_OP,,Have FreeBSD _umtx_op() support) needsync=false ], [ AC_MSG_RESULT([no]) ]) ]) AS_IF([$needsync], [ dnl OpenBSD patch (not upstream at the time of writing this) dnl https://marc.info/?l=openbsd-tech&m=147299508409549&w=2 AC_MSG_CHECKING([for OpenBSD futex() support]) AC_LINK_IFELSE([AC_LANG_PROGRAM([ #include int main(void); ], [ { return futex(NULL, FUTEX_WAIT, 0, NULL, NULL, 0); } ])], [ AC_MSG_RESULT([yes]) AC_DEFINE(HAVE_SYNC_OPENBSD_FUTEX,,Have OpenBSD futex support) needsync=false ], [ AC_MSG_RESULT([no]) ]) ]) dnl Utility macro to avoid retyping includes all the time m4_define([FRR_INCLUDES], [#ifdef SUNOS_5 #define _POSIX_C_SOURCE 200809L #define __EXTENSIONS__ #endif #include #include #include #include /* sys/conf.h depends on param.h on FBSD at least */ #include /* Required for MAXSIG */ #include #include #ifdef __APPLE__ # define __APPLE_USE_RFC_3542 #endif #include #include #include #include ])dnl dnl Same applies for HAVE_NET_IF_VAR_H, which HAVE_NETINET6_ND6_H and dnl HAVE_NETINET_IN_VAR_H depend upon. But if_var.h depends on if.h, hence dnl an additional round for it. AC_CHECK_HEADERS([net/if_var.h], [], [], [FRR_INCLUDES]) m4_define([FRR_INCLUDES], FRR_INCLUDES [#if HAVE_NET_IF_VAR_H # include #endif ])dnl AC_CHECK_HEADERS([netinet/in_var.h \ net/if_dl.h net/netopt.h \ inet/nd.h netinet/ip_icmp.h \ sys/sysctl.h sys/sockio.h sys/conf.h], [], [], [FRR_INCLUDES]) AC_CHECK_HEADERS([ucontext.h], [], [], [#ifndef __USE_GNU #define __USE_GNU #endif /* __USE_GNU */ FRR_INCLUDES ]) m4_define([UCONTEXT_INCLUDES], [#include ])dnl AC_CHECK_MEMBERS([ucontext_t.uc_mcontext.uc_regs], [], [], [UCONTEXT_INCLUDES]) AC_CHECK_MEMBERS([ucontext_t.uc_mcontext.regs], [AC_CHECK_MEMBERS([ucontext_t.uc_mcontext.regs.nip], [], [], [UCONTEXT_INCLUDES])], [], [UCONTEXT_INCLUDES]) AC_CHECK_MEMBERS([ucontext_t.uc_mcontext.gregs], [], [], [UCONTEXT_INCLUDES]) m4_define([FRR_INCLUDES], FRR_INCLUDES [ #include #include #if HAVE_NETINET_IN_VAR_H # include #endif #if HAVE_NET_IF_DL_H # include #endif #if HAVE_NET_NETOPT_H # include #endif #include #if HAVE_INET_ND_H # include #endif #include /* Required for IDRP */ #if HAVE_NETINET_IP_ICMP_H # include #endif ])dnl dnl V6 headers are checked below, after we check for v6 AC_MSG_CHECKING([which operating system interface to use]) case "$host_os" in sunos* | solaris2*) AC_MSG_RESULT([Solaris]) AC_DEFINE([SUNOS_5], [1], [SunOS 5]) AC_DEFINE([SOLARIS_IPV6], [1], Solaris IPv6) AC_DEFINE([_POSIX_C_SOURCE], [200809L], [enable POSIX.1-2008 and XPG7/SUSv4]) AC_CHECK_LIB([socket], [main]) AC_CHECK_LIB([nsl], [main]) AC_CHECK_LIB([umem], [main]) SOLARIS="solaris" AC_MSG_WARN([--Solaris support is being considered for deprecation, please let us know if you are still using this--]) ;; linux*) AC_MSG_RESULT([Linux]) AC_DEFINE([GNU_LINUX], [1], [GNU Linux]) AC_DEFINE([HAVE_NETLINK], [1], [netlink]) AC_DEFINE([LINUX_IPV6], [1], [Linux IPv6 stack]) dnl Linux has a compilation problem with mixing dnl netinet/in.h and linux/in6.h they are not dnl compatible. There has been discussion on dnl how to fix it but no real progress on implementation dnl when they fix it, remove this AC_DEFINE([IPV6_MINHOPCOUNT], [73], [Linux ipv6 Min Hop Count]) ;; openbsd*) AC_MSG_RESULT([OpenBSD]) AC_DEFINE([OPEN_BSD], [1], [OpenBSD]) AC_DEFINE([KAME], [1], [KAME IPv6]) AC_DEFINE([BSD_V6_SYSCTL], [1], [BSD v6 sysctl to turn on and off forwarding]) if test "x${enable_pimd}" != "xno"; then case "$host_os" in openbsd6.0) ;; openbsd[6-9]*) AC_MSG_FAILURE([pimd cannot be enabled as PIM support has been removed from OpenBSD 6.1]) ;; esac fi ;; *) AC_MSG_RESULT([BSD]) AC_DEFINE([HAVE_NET_RT_IFLIST], [1], [NET_RT_IFLIST]) AC_DEFINE([KAME], [1], [KAME IPv6]) AC_DEFINE([BSD_V6_SYSCTL], [1], [BSD v6 sysctl to turn on and off forwarding]) ;; esac AM_CONDITIONAL([SOLARIS], [test "${SOLARIS}" = "solaris"]) AC_SYS_LARGEFILE dnl ------------------------ dnl Integrated REALMS option dnl ------------------------ if test "${enable_realms}" = "yes"; then case "$host_os" in linux*) AC_DEFINE([SUPPORT_REALMS], [1], [Realms support]) ;; *) echo "Sorry, only Linux has REALMS support" exit 1 ;; esac fi dnl ------------------------------- dnl Endian-ness check dnl ------------------------------- AC_WORDS_BIGENDIAN dnl --------------- dnl other functions dnl --------------- AC_CHECK_FUNCS([ \ strlcat strlcpy \ getgrouplist]) dnl ########################################################################## dnl LARGE if block spans a lot of "configure"! if test "${enable_clippy_only}" != "yes"; then dnl ########################################################################## # # Logic for protobuf support. # if test "$enable_protobuf" = "yes"; then # Check for protoc & protoc-c # protoc is not required, it's only for a "be nice" helper target AC_CHECK_PROGS([PROTOC], [protoc], [/bin/false]) AC_CHECK_PROGS([PROTOC_C], [protoc-c], [/bin/false]) if test "$PROTOC_C" = "/bin/false"; then AC_MSG_FAILURE([protobuf requested but protoc-c not found. Install protobuf-c.]) fi PKG_CHECK_MODULES([PROTOBUF_C], [libprotobuf-c >= 0.14],, [ AC_MSG_FAILURE([protobuf requested but libprotobuf-c not found. Install protobuf-c.]) ]) AC_CHECK_HEADER([google/protobuf-c/protobuf-c.h], [], [ AC_MSG_FAILURE([protobuf requested but protobuf-c.h not found. Install protobuf-c.]) ]) AC_DEFINE([HAVE_PROTOBUF], [1], [protobuf]) fi dnl --------------------- dnl Integrated VTY option dnl --------------------- case "${enable_vtysh}" in "no") VTYSH="";; *) VTYSH="vtysh"; AC_DEFINE([VTYSH], [1], [VTY shell]) prev_libs="$LIBS" AC_CHECK_LIB([readline], [main], [ LIBREADLINE="-lreadline" ], [ dnl readline failed - it might be incorrectly linked and missing its dnl termcap/tinfo/curses dependency. see if we can fix that... AC_SEARCH_LIBS([tputs], [termcap tinfo curses ncurses], [ LIBREADLINE="$ac_cv_search_tputs" ], [ AC_MSG_ERROR([libreadline (needed for vtysh) not found and/or missing dependencies]) ]) dnl re-try with the lib we found above unset ac_cv_lib_readline_main AC_CHECK_LIB([readline], [main], [ LIBREADLINE="-lreadline $LIBREADLINE" ], [ AC_MSG_ERROR([libreadline (needed for vtysh) not found and/or missing dependencies]) ], [$LIBREADLINE]) ], []) LIBS="$prev_libs" AC_CHECK_HEADER([readline/history.h]) if test $ac_cv_header_readline_history_h = no;then AC_MSG_ERROR([readline is too old to have readline/history.h, please update to the latest readline library.]) fi AC_CHECK_LIB([readline], [rl_completion_matches], [true], [], [$LIBREADLINE]) if test $ac_cv_lib_readline_rl_completion_matches = no; then AC_DEFINE([rl_completion_matches], [completion_matches], [Old readline]) fi AC_CHECK_LIB([readline], [append_history], [frr_cv_append_history=yes], [frr_cv_append_history=no], [$LIBREADLINE]) if test "$frr_cv_append_history" = yes; then AC_DEFINE([HAVE_APPEND_HISTORY], [1], [Have history.h append_history]) fi ;; esac AC_SUBST([LIBREADLINE]) dnl ---------- dnl PAM module dnl dnl FRR detects the PAM library it is built against by checking for a dnl functional pam_misc.h (Linux-PAM) or openpam.h (OpenPAM) header. pam_misc.h dnl is known to #include pam_appl.h, the standard header of a PAM library, and dnl openpam.h doesn't do that, although depends on the header too. Hence a dnl little assistance to AC_CHECK_HEADER is necessary for the proper detection dnl of OpenPAM. dnl ---------- if test "$with_libpam" = "yes"; then AC_CHECK_HEADER([security/pam_misc.h], [AC_DEFINE([HAVE_PAM_MISC_H], [1], [Have pam_misc.h]) AC_DEFINE([PAM_CONV_FUNC], [misc_conv], [Have misc_conv]) pam_conv_func="misc_conv" ], [], FRR_INCLUDES) AC_CHECK_HEADER([security/openpam.h], [AC_DEFINE([HAVE_OPENPAM_H], [1], [Have openpam.h]) AC_DEFINE([PAM_CONV_FUNC], [openpam_ttyconv], [Have openpam_ttyconv]) pam_conv_func="openpam_ttyconv" ], [], FRR_INCLUDES[#include ]) if test -z "$ac_cv_header_security_pam_misc_h$ac_cv_header_security_openpam_h" ; then AC_MSG_WARN([*** pam support will not be built ***]) with_libpam="no" fi fi if test "$with_libpam" = "yes"; then dnl took this test from proftpds configure.in and suited to our needs dnl ------------------------------------------------------------------------- dnl dnl This next check looks funky due to a linker problem with some versions dnl of the PAM library. Prior to 0.72 release, the Linux PAM shared library dnl omitted requiring libdl linking information. PAM-0.72 or better ships dnl with RedHat 6.2 and Debian 2.2 or better. AC_CHECK_LIB([pam], [pam_start], [AC_CHECK_LIB([pam], [$pam_conv_func], [AC_DEFINE([USE_PAM], [1], [Use PAM for authentication]) LIBPAM="-lpam"], [AC_DEFINE([USE_PAM], [1], [Use PAM for authentication]) LIBPAM="-lpam -lpam_misc"] ) ], [AC_CHECK_LIB([pam], [pam_end], [AC_CHECK_LIB([pam], [$pam_conv_func], [AC_DEFINE([USE_PAM], [1], [Use PAM for authentication]) LIBPAM="-lpam -ldl"], [AC_DEFINE([USE_PAM], [1], [Use PAM for authentication]) LIBPAM="-lpam -ldl -lpam_misc"] ) ],AC_MSG_WARN([*** pam support will not be built ***]), [-ldl]) ] ) fi AC_SUBST([LIBPAM]) dnl ------------------------------- dnl bgpd needs pow() and hence libm dnl ------------------------------- TMPLIBS="$LIBS" LIBS="" AC_SEARCH_LIBS([pow], [m], [ LIBM="$LIBS" ], [ AC_MSG_WARN([Unable to find working pow function - bgpd may not link]) ]) LIBS="$TMPLIBS" AC_SUBST([LIBM]) AC_CHECK_FUNCS([ppoll], [ AC_DEFINE([HAVE_PPOLL], [1], [have Linux/BSD ppoll()]) ]) AC_CHECK_FUNCS([pollts], [ AC_DEFINE([HAVE_POLLTS], [1], [have NetBSD pollts()]) ]) AC_CHECK_HEADER([asm-generic/unistd.h], [AC_CHECK_DECL(__NR_setns, AC_DEFINE([HAVE_NETNS], [1], [Have netns]),, FRR_INCLUDES [#include ]) AC_CHECK_FUNCS([setns])] ) dnl -------------------------- dnl Determine IS-IS I/O method dnl -------------------------- AC_DEFINE([ISIS_METHOD_PFPACKET], [1], [constant value for isis method pfpacket]) AC_DEFINE([ISIS_METHOD_DLPI], [2], [constant value for isis method dlpi]) AC_DEFINE([ISIS_METHOD_BPF], [3], [constant value for isis method bpf]) AC_CHECK_HEADER([net/bpf.h]) AC_CHECK_HEADER([sys/dlpi.h]) AC_MSG_CHECKING([zebra IS-IS I/O method]) case "$host_os" in linux*) AC_MSG_RESULT([pfpacket]) ISIS_METHOD_MACRO="ISIS_METHOD_PFPACKET" ;; solaris* | sunos*) AC_MSG_RESULT([DLPI]) ISIS_METHOD_MACRO="ISIS_METHOD_DLPI" ;; *) if test $ac_cv_header_net_bpf_h = no; then if test $ac_cv_header_sys_dlpi_h = no; then AC_MSG_RESULT([none]) if test "${enable_isisd}" = yes -o "${enable_fabricd}" = yes; then AC_MSG_FAILURE([IS-IS support requested but no packet backend found]) fi AC_MSG_WARN([*** IS-IS support will not be built ***]) enable_isisd="no" enable_fabricd="no" else AC_MSG_RESULT([DLPI]) fi ISIS_METHOD_MACRO="ISIS_METHOD_DLPI" else AC_MSG_RESULT([BPF]) ISIS_METHOD_MACRO="ISIS_METHOD_BPF" fi ;; esac AC_DEFINE_UNQUOTED([ISIS_METHOD], [$ISIS_METHOD_MACRO], [selected method for isis, == one of the constants]) dnl --------------------------------------------------------------- dnl figure out how to specify an interface in multicast sockets API dnl --------------------------------------------------------------- AC_CHECK_MEMBERS([struct ip_mreqn.imr_ifindex], [], [], FRR_INCLUDES) AC_CHECK_HEADERS([linux/mroute.h], [], [],[ #include #include #define _LINUX_IN_H /* For Linux <= 2.6.25 */ #include ]) m4_define([FRR_INCLUDES], FRR_INCLUDES [#if HAVE_LINUX_MROUTE_H # include #endif ])dnl AC_CHECK_HEADERS([netinet/ip_mroute.h], [], [],[ #include #include #include #include ]) m4_define([FRR_INCLUDES], FRR_INCLUDES [#if HAVE_NETINET_IP_MROUTE_H # include #endif ])dnl AC_MSG_CHECKING([for BSD struct ip_mreq hack]) AC_TRY_COMPILE([#include ], [#if (defined(__FreeBSD__) && ((__FreeBSD_version >= 500022 && __FreeBSD_version < 700000) || (__FreeBSD_version < 500000 && __FreeBSD_version >= 440000))) || (defined(__NetBSD__) && defined(__NetBSD_Version__) && __NetBSD_Version__ >= 106010000) || defined(__OpenBSD__) || defined(__APPLE__) || defined(__DragonFly__) || defined(__sun) return (0); #else #error No support for BSD struct ip_mreq hack detected #endif],[AC_MSG_RESULT([yes]) AC_DEFINE([HAVE_BSD_STRUCT_IP_MREQ_HACK], [1], [Can pass ifindex in struct ip_mreq])], AC_MSG_RESULT([no])) AC_MSG_CHECKING([for RFC3678 protocol-independed API]) AC_TRY_COMPILE([ #include #include ], [struct group_req gr; int sock; setsockopt(sock, IPPROTO_IP, MCAST_JOIN_GROUP, (void*)&gr, sizeof(gr)); ], [AC_MSG_RESULT([yes]) AC_DEFINE([HAVE_RFC3678], [1], [Have RFC3678 protocol-independed API])], AC_MSG_RESULT([no])) dnl --------------------------------------------------------------- dnl figure out how to check link-state dnl --------------------------------------------------------------- AC_CHECK_HEADER([net/if_media.h], [m4_define([LINK_DETECT_INCLUDES], FRR_INCLUDES [#include ]) AC_CHECK_MEMBERS([struct ifmediareq.ifm_status], AC_DEFINE([HAVE_BSD_LINK_DETECT], [1], [BSD link-detect]), [], LINK_DETECT_INCLUDES)], [], FRR_INCLUDES) dnl --------------------------------------------------------------- dnl Additional, newer way to check link-state using ifi_link_state. dnl Not available in all BSD's when ifmediareq available dnl --------------------------------------------------------------- AC_CHECK_MEMBERS([struct if_data.ifi_link_state], AC_DEFINE([HAVE_BSD_IFI_LINK_STATE], [1], [BSD ifi_link_state available]), [], FRR_INCLUDES) dnl ------------------------ dnl TCP_MD5SIG socket option dnl ------------------------ AC_CHECK_HEADER([netinet/tcp.h], [m4_define([MD5_INCLUDES], FRR_INCLUDES [#include ]) AC_CHECK_DECLS([TCP_MD5SIG], [], [], MD5_INCLUDES)], [], FRR_INCLUDES) if test $ac_cv_have_decl_TCP_MD5SIG = no; then AC_CHECK_HEADER([linux/tcp.h], [m4_define([MD5_INCLUDES], FRR_INCLUDES [#include ]) AC_CHECK_DECLS([TCP_MD5SIG], [], [], MD5_INCLUDES)]) fi AC_SUBST([SOLARIS]) AC_CHECK_LIB([crypt], [crypt], [], [AC_CHECK_LIB([crypto], [DES_crypt])]) AC_CHECK_LIB([resolv], [res_init]) dnl --------------------------- dnl check system has PCRE regexp dnl --------------------------- if test "x$enable_pcreposix" = "xyes"; then AC_CHECK_LIB([pcreposix], [regexec], [], [ AC_MSG_ERROR([--enable-pcreposix given but unable to find libpcreposix]) ]) fi AC_SUBST([HAVE_LIBPCREPOSIX]) dnl ------------------ dnl check C-Ares library dnl ------------------ PKG_CHECK_MODULES([CARES], [libcares], [ c_ares_found=true ],[ c_ares_found=false ]) AM_CONDITIONAL([CARES], [$c_ares_found]) dnl ########################################################################## dnl test "${enable_clippy_only}" != "yes" fi dnl END OF LARGE if block dnl ########################################################################## dnl ---------------------------------------------------------------------------- dnl figure out if domainname is available in the utsname struct (GNU extension). dnl ---------------------------------------------------------------------------- AC_CHECK_MEMBERS([struct utsname.domainname], [], [], [#include ]) dnl ------------------ dnl IPv6 header checks dnl ------------------ AC_CHECK_HEADERS([netinet6/in6.h netinet/in6_var.h \ netinet6/in6_var.h netinet6/nd6.h], [], [], FRR_INCLUDES) m4_define([FRR_INCLUDES],dnl FRR_INCLUDES [#if HAVE_NETINET6_IN6_H #include #endif #if HAVE_NETINET_IN6_VAR_H #include #endif #include #if HAVE_NETINET6_IN6_VAR_H # include #endif #if HAVE_NETINET6_ND6_H # include #endif ])dnl dnl -------------------- dnl Daemon disable check dnl -------------------- AS_IF([test "${enable_ldpd}" != "no"], [ AC_DEFINE([HAVE_LDPD], [1], [ldpd]) ]) if test "$enable_bfdd" = "no"; then AC_DEFINE([HAVE_BFDD], [0], [bfdd]) BFDD="" else AC_DEFINE([HAVE_BFDD], [1], [bfdd]) BFDD="bfdd" case $host_os in linux*) AC_DEFINE([BFD_LINUX], [1], [bfdd]) ;; *) AC_DEFINE([BFD_BSD], [1], [bfdd]) ;; esac fi if test "$ac_cv_lib_json_c_json_object_get" = no -a "x$BFDD" = "xbfdd"; then AC_MSG_ERROR(["you must use json-c library to use bfdd"]) fi NHRPD="" case "$host_os" in linux*) case "${enable_nhrpd}" in no) ;; yes) if test "$c_ares_found" != "true" ; then AC_MSG_ERROR([nhrpd requires libcares. Please install c-ares and its -dev headers.]) fi NHRPD="nhrpd" ;; *) if test "$c_ares_found" = "true" ; then NHRPD="nhrpd" fi ;; esac ;; *) if test "${enable_nhrpd}" = "yes"; then AC_MSG_ERROR([nhrpd requires kernel APIs that are only present on Linux.]) fi ;; esac if test "${enable_watchfrr}" = "no";then WATCHFRR="" else WATCHFRR="watchfrr" fi OSPFCLIENT="" if test "${enable_ospfapi}" != "no";then AC_DEFINE([SUPPORT_OSPF_API], [1], [OSPFAPI]) if test "${enable_ospfclient}" != "no";then OSPFCLIENT="ospfclient" fi fi if test "${enable_bgp_announce}" = "no";then AC_DEFINE([DISABLE_BGP_ANNOUNCE], [1], [Disable BGP installation to zebra]) else AC_DEFINE([DISABLE_BGP_ANNOUNCE], [0], [Disable BGP installation to zebra]) fi if test "${enable_bgp_vnc}" != "no";then AC_DEFINE([ENABLE_BGP_VNC], [1], [Enable BGP VNC support]) fi bgpd_bmp=false case "${enable_bmp}" in no) ;; yes) if test "$c_ares_found" != "true" ; then AC_MSG_ERROR([BMP support requires libcares. Please install c-ares and its -dev headers.]) fi bgpd_bmp=true ;; *) if test "$c_ares_found" = "true" ; then bgpd_bmp=true fi ;; esac dnl ########################################################################## dnl LARGE if block if test "${enable_clippy_only}" != "yes"; then dnl ########################################################################## dnl ------------------ dnl check Net-SNMP library dnl ------------------ if test "${enable_snmp}" != "" -a "${enable_snmp}" != "no"; then AC_PATH_TOOL([NETSNMP_CONFIG], [net-snmp-config], [no]) if test x"$NETSNMP_CONFIG" = x"no"; then AC_MSG_ERROR([--enable-snmp given but unable to find net-snmp-config]) fi SNMP_LIBS="`${NETSNMP_CONFIG} --agent-libs`" SNMP_CFLAGS="`${NETSNMP_CONFIG} --base-cflags`" # net-snmp lists all of its own dependencies. we absolutely do not want that # among other things we avoid a GPL vs. OpenSSL license conflict here for removelib in crypto ssl sensors pci wrap; do SNMP_LIBS="`echo $SNMP_LIBS | sed -e 's/\(^\|\s\)-l'$removelib'\b/ /g' -e 's/\(^\|\s\)\([^\s]*\/\)\?lib'$removelib'\.[^\s]\+\b/ /g'`" done AC_MSG_CHECKING([whether we can link to Net-SNMP]) AC_LINK_IFELSE_FLAGS([$SNMP_CFLAGS], [$SNMP_LIBS], [AC_LANG_PROGRAM([ int main(void); ], [ { return 0; } ])], [ AC_MSG_RESULT([no]) AC_MSG_ERROR([--enable-snmp given but not usable])]) case "${enable_snmp}" in yes) SNMP_METHOD=agentx ;; agentx) SNMP_METHOD="${enable_snmp}" ;; *) AC_MSG_ERROR([--enable-snmp given with an unknown method (${enable_snmp}). Use yes or agentx]) ;; esac AH_TEMPLATE([SNMP_AGENTX], [Use SNMP AgentX to interface with snmpd]) AC_DEFINE_UNQUOTED(AS_TR_CPP(SNMP_${SNMP_METHOD}),,[SNMP method to interface with snmpd]) fi AC_SUBST([SNMP_LIBS]) AC_SUBST([SNMP_CFLAGS]) dnl --------------- dnl libyang dnl --------------- PKG_CHECK_MODULES([LIBYANG], [libyang >= 0.16.105], , [ AC_MSG_ERROR([libyang (>= 0.16.105) was not found on your system.]) ]) ac_cflags_save="$CFLAGS" CFLAGS="$CFLAGS $LIBYANG_CFLAGS" AC_CHECK_MEMBER([struct lyd_node.priv], [], [ AC_MSG_ERROR([m4_normalize([ libyang needs to be compiled with ENABLE_LYD_PRIV=ON. Instructions for this are included in the build documentation for your platform at http://docs.frrouting.org/projects/dev-guide/en/latest/building.html]) ]) ], [[#include ]]) CFLAGS="$ac_cflags_save" dnl --------------- dnl configuration rollbacks dnl --------------- SQLITE3=false if test "$enable_config_rollbacks" = "yes"; then PKG_CHECK_MODULES([SQLITE3], [sqlite3], [ AC_DEFINE([HAVE_CONFIG_ROLLBACKS], [1], [Enable configuration rollbacks]) AC_DEFINE([HAVE_SQLITE3], [1], [Enable sqlite3 database]) SQLITE3=true ], [ AC_MSG_ERROR([--enable-config-rollbacks given but sqlite3 was not found on your system.]) ]) fi dnl --------------- dnl confd dnl --------------- if test "$enable_confd" != "" -a "$enable_confd" != "no"; then AC_CHECK_PROG([CONFD], [confd], [confd], [/bin/false], "${enable_confd}/bin") if test "x$CONFD" = "x/bin/false"; then AC_MSG_ERROR([confd was not found on your system.])] fi CONFD_CFLAGS="-I${enable_confd}/include -L${enable_confd}/lib" AC_SUBST([CONFD_CFLAGS]) CONFD_LIBS="-lconfd" AC_SUBST([CONFD_LIBS]) AC_DEFINE([HAVE_CONFD], [1], [Enable confd integration]) fi dnl --------------- dnl sysrepo dnl --------------- if test "$enable_sysrepo" = "yes"; then PKG_CHECK_MODULES([SYSREPO], [libsysrepo], [AC_DEFINE([HAVE_SYSREPO], [1], [Enable sysrepo integration]) SYSREPO=true], [SYSREPO=false AC_MSG_ERROR([sysrepo was not found on your system.])] ) fi dnl --------------- dnl gRPC dnl --------------- if test "$enable_grpc" = "yes"; then PKG_CHECK_MODULES([GRPC], [grpc grpc++ protobuf], [ AC_CHECK_PROGS([PROTOC], [protoc], [/bin/false]) if test "$PROTOC" = "/bin/false"; then AC_MSG_FAILURE([grpc requested but protoc not found.]) fi AC_DEFINE([HAVE_GRPC], [1], [Enable the gRPC northbound plugin]) GRPC=true ], [ GRPC=false AC_MSG_ERROR([grpc/grpc++ were not found on your system.]) ]) fi dnl ------ dnl ZeroMQ dnl ------ if test "x$enable_zeromq" != "xno"; then PKG_CHECK_MODULES([ZEROMQ], [libzmq >= 4.0.0], [ AC_DEFINE([HAVE_ZEROMQ], [1], [Enable ZeroMQ support]) ZEROMQ=true ], [ if test "x$enable_zeromq" = "xyes"; then AC_MSG_ERROR([configuration specifies --enable-zeromq but libzmq was not found]) fi ]) fi dnl ------------------------------------ dnl Enable RPKI and add librtr to libs dnl ------------------------------------ if test "${enable_rpki}" = "yes"; then PKG_CHECK_MODULES([RTRLIB], [rtrlib >= 0.5.0], [RPKI=true], [RPKI=false AC_MSG_ERROR([rtrlib was not found on your system or is too old.])] ) fi dnl ------------------------------------------ dnl Check whether rtrlib was build with ssh support dnl ------------------------------------------ AC_MSG_CHECKING([whether the RTR Library is compiled with SSH]) AC_LINK_IFELSE([AC_LANG_PROGRAM([[#include "rtrlib/rtrlib.h"]], [[struct tr_ssh_config config;]])], [AC_MSG_RESULT([yes]) AC_DEFINE([FOUND_SSH], [1], [found_ssh])], AC_MSG_RESULT([no]) ) dnl --------------- dnl dlopen & dlinfo dnl --------------- AC_SEARCH_LIBS([dlopen], [dl dld], [], [ AC_MSG_ERROR([unable to find the dlopen()]) ]) AC_CHECK_HEADERS([link.h]) AC_CACHE_CHECK([for dlinfo(RTLD_DI_ORIGIN)], [frr_cv_rtld_di_origin], [ AC_LINK_IFELSE([AC_LANG_PROGRAM([[ #include #ifdef HAVE_LINK_H #include #endif #include ]], [[ char origin[1]; dlinfo (NULL, RTLD_DI_ORIGIN, &origin); ]])], [ frr_cv_rtld_di_origin=yes ], [ frr_cv_rtld_di_origin=no ]) ]) if test "$frr_cv_rtld_di_origin" = yes; then AC_DEFINE([HAVE_DLINFO_ORIGIN], [1], [Have dlinfo RTLD_DI_ORIGIN]) fi AC_CACHE_CHECK([for dlinfo(RTLD_DI_LINKMAP)], [frr_cv_rtld_di_linkmap], [ AC_LINK_IFELSE([AC_LANG_PROGRAM([[ #include #ifdef HAVE_LINK_H #include #endif #include ]], [[ struct link_map *lm = NULL; dlinfo (NULL, RTLD_DI_LINKMAP, &lm); ]])], [ frr_cv_rtld_di_linkmap=yes ], [ frr_cv_rtld_di_linkmap=no ]) ]) if test "$frr_cv_rtld_di_linkmap" = yes; then AC_DEFINE([HAVE_DLINFO_LINKMAP], [1], [Have dlinfo RTLD_DI_LINKMAP]) fi dnl ########################################################################## dnl test "${enable_clippy_only}" != "yes" fi dnl END OF LARGE if block dnl ########################################################################## dnl --------------------------- dnl sockaddr and netinet checks dnl --------------------------- AC_CHECK_TYPES([ struct sockaddr_dl, struct vifctl, struct mfcctl, struct sioc_sg_req, vifi_t, struct sioc_vif_req, struct igmpmsg, struct ifaliasreq, struct if6_aliasreq, struct in6_aliasreq, struct nd_opt_adv_interval, struct nd_opt_homeagent_info, struct nd_opt_adv_interval, struct nd_opt_rdnss, struct nd_opt_dnssl], [], [], FRR_INCLUDES) AC_CHECK_MEMBERS([struct sockaddr.sa_len, struct sockaddr_in.sin_len, struct sockaddr_un.sun_len, struct sockaddr_dl.sdl_len, struct if6_aliasreq.ifra_lifetime, struct nd_opt_adv_interval.nd_opt_ai_type], [], [], FRR_INCLUDES) dnl --------------------------- dnl IRDP/pktinfo/icmphdr checks dnl --------------------------- AC_CHECK_TYPES([struct in_pktinfo], [ AC_CHECK_TYPES([struct icmphdr], [ IRDP=true ], [ IRDP=false ], [FRR_INCLUDES]) ], [ IRDP=false ], [FRR_INCLUDES]) case "${enable_irdp}" in yes) $IRDP || AC_MSG_ERROR(['IRDP requires in_pktinfo at the moment!']) ;; no) IRDP=false ;; esac dnl ----------------------- dnl checking for IP_PKTINFO dnl ----------------------- AC_MSG_CHECKING([for IP_PKTINFO]) AC_TRY_COMPILE([#include ], [ int opt = IP_PKTINFO; ], [ AC_MSG_RESULT([yes]) AC_DEFINE([HAVE_IP_PKTINFO], [1], [Have IP_PKTINFO]) ], [ AC_MSG_RESULT([no]) ]) dnl --------------------------- dnl checking for IP_RECVDSTADDR dnl --------------------------- AC_MSG_CHECKING([for IP_RECVDSTADDR]) AC_TRY_COMPILE([#include ], [ int opt = IP_RECVDSTADDR; ], [ AC_MSG_RESULT([yes]) AC_DEFINE([HAVE_IP_RECVDSTADDR], [1], [Have IP_RECVDSTADDR]) ], [ AC_MSG_RESULT([no]) ]) dnl ---------------------- dnl checking for IP_RECVIF dnl ---------------------- AC_MSG_CHECKING([for IP_RECVIF]) AC_TRY_COMPILE([#include ], [ int opt = IP_RECVIF; ], [ AC_MSG_RESULT([yes]) AC_DEFINE([HAVE_IP_RECVIF], [1], [Have IP_RECVIF]) ], [ AC_MSG_RESULT([no]) ]) dnl ---------------------- dnl checking for SO_BINDANY dnl ---------------------- AC_MSG_CHECKING([for SO_BINDANY]) AC_TRY_COMPILE([#include ], [ int opt = SO_BINDANY; ], [ AC_MSG_RESULT([yes]) AC_DEFINE([HAVE_SO_BINDANY], [1], [Have SO_BINDANY]) ], [ AC_MSG_RESULT([no]) ]) dnl ---------------------- dnl checking for IP_FREEBIND dnl ---------------------- AC_MSG_CHECKING([for IP_FREEBIND]) AC_TRY_COMPILE([#include ], [ int opt = IP_FREEBIND; ], [ AC_MSG_RESULT([yes]) AC_DEFINE([HAVE_IP_FREEBIND], [1], [Have IP_FREEBIND]) ], [ AC_MSG_RESULT([no]) ]) dnl -------------------------------------- dnl checking for be32dec existence or not dnl -------------------------------------- AC_CHECK_DECLS([be32enc, be32dec], [], [], [#include ]) dnl -------------------------------------- dnl checking for clock_time monotonic struct and call dnl -------------------------------------- AC_CHECK_DECL([CLOCK_MONOTONIC], [AC_CHECK_LIB([rt], [clock_gettime], [LIBS="$LIBS -lrt"]) AC_DEFINE([HAVE_CLOCK_MONOTONIC], [1], [Have monotonic clock]) ], [AC_MSG_RESULT([no])], [FRR_INCLUDES]) dnl -------------------------------------- dnl checking for flex and bison dnl -------------------------------------- AM_PROG_LEX AC_MSG_CHECKING([version of flex]) frr_ac_flex_version="$(eval $LEX -V | grep flex | head -n 1)" frr_ac_flex_version="${frr_ac_flex_version##* }" AC_MSG_RESULT([$frr_ac_flex_version]) AX_COMPARE_VERSION([$frr_ac_flex_version], [lt], [2.5.20], [ LEX="$SHELL $missing_dir/missing flex" if test -f "${srcdir}/lib/command_lex.c" -a -f "${srcdir}/lib/command_lex.h"; then AC_MSG_WARN([using pregenerated flex output files]) else AC_MSG_ERROR([flex failure and pregenerated files not included (probably a git build)]) fi AC_SUBST([LEX_OUTPUT_ROOT], [lex.yy]) AC_SUBST([LEXLIB], ['']) ]) AC_PROG_YACC dnl thanks GNU bison for this b*llshit... AC_MSG_CHECKING([version of bison]) frr_ac_bison_version="$(eval $YACC -V | grep bison | head -n 1)" frr_ac_bison_version="${frr_ac_bison_version##* }" frr_ac_bison_missing="false" case "x${frr_ac_bison_version}x" in x2.7*) BISON_OPENBRACE='"' BISON_CLOSEBRACE='"' BISON_VERBOSE='' AC_MSG_RESULT([$frr_ac_bison_version - 2.7 or older]) ;; x2.*|x1.*) AC_MSG_RESULT([$frr_ac_bison_version]) AC_MSG_WARN([installed bison is too old. Please install GNU bison 2.7.x or newer.]) frr_ac_bison_missing="true" ;; x) AC_MSG_RESULT([none]) AC_MSG_WARN([could not determine bison version. Please install GNU bison 2.7.x or newer.]) frr_ac_bison_missing="true" ;; x3.[012][^0-9]*) BISON_OPENBRACE='{' BISON_CLOSEBRACE='}' BISON_VERBOSE='-Dparse.error=verbose' AC_MSG_RESULT([$frr_ac_bison_version - 3.0 to 3.2]) ;; *) BISON_OPENBRACE='{' BISON_CLOSEBRACE='}' BISON_VERBOSE='-Dparse.error=verbose -Wno-yacc' AC_MSG_RESULT([$frr_ac_bison_version - 3.3 or newer]) ;; esac AC_SUBST([BISON_OPENBRACE]) AC_SUBST([BISON_CLOSEBRACE]) AC_SUBST([BISON_VERBOSE]) if $frr_ac_bison_missing; then YACC="$SHELL $missing_dir/missing bison -y" if test -f "${srcdir}/lib/command_parse.c" -a -f "${srcdir}/lib/command_parse.h"; then AC_MSG_WARN([using pregenerated bison output files]) else AC_MSG_ERROR([bison failure and pregenerated files not included (probably a git build)]) fi fi dnl ------------------- dnl capabilities checks dnl ------------------- if test "${enable_capabilities}" != "no"; then AC_MSG_CHECKING([whether prctl PR_SET_KEEPCAPS is available]) AC_TRY_COMPILE([#include ], [prctl(PR_SET_KEEPCAPS, 1, 0, 0, 0);], [AC_MSG_RESULT([yes]) AC_DEFINE([HAVE_PR_SET_KEEPCAPS], [1], [prctl]) frr_ac_keepcaps="yes"], AC_MSG_RESULT([no]) ) if test x"${frr_ac_keepcaps}" = x"yes"; then AC_CHECK_HEADERS([sys/capability.h]) fi if test x"${ac_cv_header_sys_capability_h}" = x"yes"; then AC_CHECK_LIB([cap], [cap_init], [AC_DEFINE([HAVE_LCAPS], [1], [Capabilities]) LIBCAP="-lcap" frr_ac_lcaps="yes"] ) else AC_CHECK_HEADERS([priv.h], [AC_MSG_CHECKING([Solaris style privileges are available]) AC_TRY_COMPILE([#include ], [getpflags(PRIV_AWARE);], [AC_MSG_RESULT([yes]) AC_DEFINE([HAVE_SOLARIS_CAPABILITIES], [1], [getpflags]) frr_ac_scaps="yes"], AC_MSG_RESULT(no) ) ] ) fi if test x"${frr_ac_scaps}" = x"yes" \ -o x"${frr_ac_lcaps}" = x"yes"; then AC_DEFINE([HAVE_CAPABILITIES], [1], [capabilities]) fi case "$host_os" in linux*) if test "$frr_ac_lcaps" != "yes"; then AC_MSG_ERROR([libcap and/or its headers were not found. Running FRR without libcap support built in causes a huge performance penalty.]) fi ;; esac else case "$host_os" in linux*) AC_MSG_WARN([Running FRR without libcap support built in causes a huge performance penalty.]) ;; esac fi AC_SUBST([LIBCAP]) dnl --------------------------- dnl check for glibc 'backtrace' dnl --------------------------- if test x"${enable_backtrace}" != x"no" ; then backtrace_ok=no PKG_CHECK_MODULES([UNWIND], [libunwind], [ AC_DEFINE([HAVE_LIBUNWIND], [1], [libunwind]) backtrace_ok=yes ], [ case "$host_os" in sunos* | solaris2*) AC_CHECK_FUNCS([printstack], [ AC_DEFINE([HAVE_PRINTSTACK], [1], [Solaris printstack]) backtrace_ok=yes ]) ;; esac if test "$backtrace_ok" = no; then AC_CHECK_HEADER([execinfo.h], [ AC_SEARCH_LIBS([backtrace], [execinfo], [ AC_DEFINE([HAVE_GLIBC_BACKTRACE], [1], [Glibc backtrace]) backtrace_ok=yes ],, [-lm]) ]) fi ]) if test x"${enable_backtrace}" = x"yes" -a x"${backtrace_ok}" = x"no"; then dnl user explicitly requested backtrace but we failed to find support AC_MSG_FAILURE([failed to find backtrace or libunwind support]) fi fi dnl ----------------------------------------- dnl check for malloc mallinfo struct and call dnl this must try and link using LIBS, in dnl order to check no alternative allocator dnl has been specified, which might not provide dnl mallinfo, e.g. such as Umem on Solaris. dnl ----------------------------------------- AC_CHECK_HEADERS([malloc.h malloc_np.h malloc/malloc.h],,, [FRR_INCLUDES]) AC_CACHE_CHECK([whether mallinfo is available], [frr_cv_mallinfo], [ AC_LINK_IFELSE([AC_LANG_PROGRAM([FRR_INCLUDES [ #ifdef HAVE_MALLOC_H #include #endif #ifdef HAVE_MALLOC_NP_H #include #endif #ifdef HAVE_MALLOC_MALLOC_H #include #endif ]], [[ struct mallinfo ac_x; ac_x = mallinfo (); ]])], [ frr_cv_mallinfo=yes ], [ frr_cv_mallinfo=no ]) ]) if test "$frr_cv_mallinfo" = yes; then AC_DEFINE([HAVE_MALLINFO], [1], [mallinfo]) fi AC_MSG_CHECKING([whether malloc_usable_size is available]) AC_LINK_IFELSE([AC_LANG_PROGRAM([FRR_INCLUDES [ #ifdef HAVE_MALLOC_H #include #endif #ifdef HAVE_MALLOC_MALLOC_H #include #endif ]], [[ size_t ac_x; ac_x = malloc_usable_size(NULL); ]])], [ AC_MSG_RESULT([yes]) AC_DEFINE([HAVE_MALLOC_USABLE_SIZE], [1], [malloc_usable_size]) ], [ AC_MSG_RESULT([no]) AC_MSG_CHECKING([whether malloc_size is available]) AC_LINK_IFELSE([AC_LANG_PROGRAM([[ #ifdef HAVE_MALLOC_H #include #endif #ifdef HAVE_MALLOC_MALLOC_H #include #endif ]], [[ size_t ac_x; ac_x = malloc_size(NULL); ]])], [ AC_MSG_RESULT([yes]) AC_DEFINE([HAVE_MALLOC_SIZE], [1], [malloc_size]) ], [ AC_MSG_RESULT([no]) ]) ]) dnl ---------- dnl configure date dnl ---------- dev_version=`echo $VERSION | grep dev` #don't expire deprecated code in non 'dev' branch if test "${dev_version}" = ""; then CONFDATE=0 else CONFDATE=`date '+%Y%m%d'` fi AC_SUBST([CONFDATE]) dnl ------------------------------ dnl set paths for state directory dnl ------------------------------ AC_MSG_CHECKING([directory to use for state file]) if test "${prefix}" = "NONE"; then frr_statedir_prefix=""; else frr_statedir_prefix=${prefix} fi if test "${localstatedir}" = '${prefix}/var'; then for FRR_STATE_DIR in ${frr_statedir_prefix}/var/run dnl ${frr_statedir_prefix}/var/adm dnl ${frr_statedir_prefix}/etc dnl /var/run dnl /var/adm dnl /etc dnl /dev/null; do test -d $FRR_STATE_DIR && break done frr_statedir=$FRR_STATE_DIR else frr_statedir=${localstatedir} fi if test $frr_statedir = "/dev/null"; then AC_MSG_ERROR([STATE DIRECTORY NOT FOUND! FIX OR SPECIFY --localstatedir!]) fi AC_MSG_RESULT([${frr_statedir}]) AC_SUBST([frr_statedir]) AC_DEFINE_UNQUOTED([LDPD_SOCKET], ["$frr_statedir%s%s/ldpd.sock"], [ldpd control socket]) AC_DEFINE_UNQUOTED([ZEBRA_SERV_PATH], ["$frr_statedir%s%s/zserv.api"], [zebra api socket]) AC_DEFINE_UNQUOTED([BFDD_CONTROL_SOCKET], ["$frr_statedir%s%s/bfdd.sock"], [bfdd control socket]) AC_DEFINE_UNQUOTED([DAEMON_VTY_DIR], ["$frr_statedir%s%s"], [daemon vty directory]) AC_DEFINE_UNQUOTED([DAEMON_DB_DIR], ["$frr_statedir"], [daemon database directory]) dnl autoconf does this, but it does it too late... test "x$prefix" = xNONE && prefix=$ac_default_prefix test "x$exec_prefix" = xNONE && exec_prefix='${prefix}' dnl get the full path, recursing through variables... vtysh_bin="$bindir/vtysh" for I in 1 2 3 4 5 6 7 8 9 10; do eval vtysh_bin="\"$vtysh_bin\"" done AC_DEFINE_UNQUOTED([VTYSH_BIN_PATH], ["$vtysh_bin"], [path to vtysh binary]) AC_SUBST([vtysh_bin]) CFG_SYSCONF="$sysconfdir" CFG_SBIN="$sbindir" CFG_STATE="$frr_statedir" CFG_MODULE="$moduledir" CFG_YANGMODELS="$yangmodelsdir" for I in 1 2 3 4 5 6 7 8 9 10; do eval CFG_SYSCONF="\"$CFG_SYSCONF\"" eval CFG_SBIN="\"$CFG_SBIN\"" eval CFG_STATE="\"$CFG_STATE\"" eval CFG_MODULE="\"$CFG_MODULE\"" eval CFG_YANGMODELS="\"$CFG_YANGMODELS\"" done AC_SUBST([CFG_SYSCONF]) AC_SUBST([CFG_SBIN]) AC_SUBST([CFG_STATE]) AC_SUBST([CFG_MODULE]) AC_SUBST([CFG_YANGMODELS]) AC_DEFINE_UNQUOTED([MODULE_PATH], ["$CFG_MODULE"], [path to modules]) AC_DEFINE_UNQUOTED([YANG_MODELS_PATH], ["$CFG_YANGMODELS"], [path to YANG data models]) AC_DEFINE_UNQUOTED([WATCHFRR_SH_PATH], ["${CFG_SBIN%/}/watchfrr.sh"], [path to watchfrr.sh]) dnl various features AM_CONDITIONAL([SUPPORT_REALMS], [test "${enable_realms}" = "yes"]) AM_CONDITIONAL([ENABLE_BGP_VNC], [test x${enable_bgp_vnc} != xno]) AM_CONDITIONAL([BGP_BMP], [$bgpd_bmp]) dnl northbound AM_CONDITIONAL([SQLITE3], [$SQLITE3]) AM_CONDITIONAL([CONFD], [test "x$enable_confd" != "x"]) AM_CONDITIONAL([SYSREPO], [test "x$enable_sysrepo" = "xyes"]) AM_CONDITIONAL([GRPC], [test "x$enable_grpc" = "xyes"]) AM_CONDITIONAL([ZEROMQ], [test "x$ZEROMQ" = "xtrue"]) dnl plugins AM_CONDITIONAL([RPKI], [test "x$RPKI" = "xtrue"]) AM_CONDITIONAL([SNMP], [test "x$SNMP_METHOD" = "xagentx"]) AM_CONDITIONAL([IRDP], [$IRDP]) AM_CONDITIONAL([FPM], [test "x$enable_fpm" = "xyes"]) AM_CONDITIONAL([HAVE_PROTOBUF], [test "x$enable_protobuf" = "xyes"]) dnl daemons AM_CONDITIONAL([VTYSH], [test "x$VTYSH" = "xvtysh"]) AM_CONDITIONAL([ZEBRA], [test "${enable_zebra}" != "no"]) AM_CONDITIONAL([BGPD], [test "x${enable_bgpd}" != "no"]) AM_CONDITIONAL([RIPD], [test "${enable_ripd}" != "no"]) AM_CONDITIONAL([OSPFD], [test "${enable_ospfd}" != "no"]) AM_CONDITIONAL([LDPD], [test "${enable_ldpd}" != "no"]) AM_CONDITIONAL([BFDD], [test "x$BFDD" = "xbfdd"]) AM_CONDITIONAL([NHRPD], [test "x$NHRPD" = "xnhrpd"]) AM_CONDITIONAL([EIGRPD], [test "${enable_eigrpd}" != "no"]) AM_CONDITIONAL([WATCHFRR], [test "x$WATCHFRR" = "xwatchfrr"]) AM_CONDITIONAL([OSPFCLIENT], [test "x$OSPFCLIENT" = "xospfclient"]) AM_CONDITIONAL([RIPNGD], [test "${enable_ripngd}" != "no"]) AM_CONDITIONAL([BABELD], [test "${enable_babeld}" != "no"]) AM_CONDITIONAL([OSPF6D], [test "${enable_ospf6d}" != "no"]) AM_CONDITIONAL([ISISD], [test "${enable_isisd}" != "no"]) AM_CONDITIONAL([PIMD], [test "${enable_pimd}" != "no"]) AM_CONDITIONAL([PBRD], [test "${enable_pbrd}" != "no"]) AM_CONDITIONAL([SHARPD], [test "${enable_sharpd}" = "yes"]) AM_CONDITIONAL([STATICD], [test "${enable_staticd}" != "no"]) AM_CONDITIONAL([FABRICD], [test "${enable_fabricd}" != "no"]) AM_CONDITIONAL([VRRPD], [test "${enable_vrrpd}" != "no"]) AC_CONFIG_FILES([Makefile],[sed -e 's/^#AUTODERP# //' -i Makefile]) AC_CONFIG_FILES([ config.version changelog-auto redhat/frr.spec solaris/Makefile alpine/APKBUILD snapcraft/snapcraft.yaml lib/version.h tests/lib/cli/test_cli.refout pkgsrc/bgpd.sh pkgsrc/ospf6d.sh pkgsrc/ospfd.sh pkgsrc/ripd.sh pkgsrc/ripngd.sh pkgsrc/zebra.sh pkgsrc/eigrpd.sh]) AC_CONFIG_FILES([vtysh/extract.pl], [chmod +x vtysh/extract.pl]) AC_CONFIG_FILES([tools/frr], [chmod +x tools/frr]) AC_CONFIG_FILES([tools/watchfrr.sh], [chmod +x tools/watchfrr.sh]) AC_CONFIG_FILES([tools/frrinit.sh], [chmod +x tools/frrinit.sh]) AC_CONFIG_FILES([tools/frrcommon.sh]) AC_CONFIG_COMMANDS([lib/route_types.h], [ dst="${ac_abs_top_builddir}/lib/route_types.h" ${PERL} "${ac_abs_top_srcdir}/lib/route_types.pl" \ < "${ac_abs_top_srcdir}/lib/route_types.txt" \ > "${dst}.tmp" test -f "${dst}" \ && diff "${dst}.tmp" "${dst}" >/dev/null 2>/dev/null \ && rm "${dst}.tmp" \ || mv "${dst}.tmp" "${dst}" ], [ PERL="$PERL" ]) AS_IF([test "x$with_pkg_git_version" = "xyes"], [ AC_CONFIG_COMMANDS([lib/gitversion.h], [ dst="${ac_abs_top_builddir}/lib/gitversion.h" ${PERL} "${ac_abs_top_srcdir}/lib/gitversion.pl" \ "${ac_abs_top_srcdir}" \ > "${dst}.tmp" test -f "${dst}" \ && diff "${dst}.tmp" "${dst}" >/dev/null 2>/dev/null \ && rm "${dst}.tmp" \ || mv "${dst}.tmp" "${dst}" ], [ PERL="$PERL" ]) ]) ## Hack, but working solution to avoid rebuilding of frr.info. ## It's already in CVS until texinfo 4.7 is more common. AC_OUTPUT echo " FRRouting configuration ------------------------------ FRR version : ${PACKAGE_VERSION} host operating system : ${host_os} source code location : ${srcdir} compiler : ${CC} compiler flags : ${CFLAGS} ${SAN_FLAGS} make : ${MAKE-make} linker flags : ${LDFLAGS} ${SAN_FLAGS} ${LIBS} ${LIBCAP} ${LIBREADLINE} ${LIBM} state file directory : ${frr_statedir} config file directory : `eval echo \`echo ${sysconfdir}\`` example directory : `eval echo \`echo ${exampledir}\`` module directory : ${CFG_MODULE} user to run as : ${enable_user} group to run as : ${enable_group} group for vty sockets : ${enable_vty_group} config file mask : ${enable_configfile_mask} log file mask : ${enable_logfile_mask} zebra protobuf enabled : ${enable_protobuf:-no} The above user and group must have read/write access to the state file directory and to the config files in the config file directory." if test "${enable_doc}" != "no" -a "$frr_py_mod_sphinx" = false; then AC_MSG_WARN([sphinx is missing but required to build documentation]) fi if test "$frr_py_mod_pytest" = false; then AC_MSG_WARN([pytest is missing, unit tests cannot be performed]) fi frr-7.2.1/defaults.h0000644000000000000000000000323613610377563011200 00000000000000/* * FRR switchable defaults. * Copyright (C) 2017 David Lamparter for NetDEF, Inc. * * This file is part of FRRouting (FRR). * * FRR is free software; you can redistribute it and/or modify it under the * terms of the GNU General Public License as published by the Free Software * Foundation; either version 2, or (at your option) any later version. * * FRR 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; see the file COPYING; if not, write to the Free Software * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA */ #ifndef _FRR_DEFAULTS_H #define _FRR_DEFAULTS_H #include "config.h" #ifdef HAVE_DATACENTER #define DFLT_BGP_IMPORT_CHECK 1 #define DFLT_BGP_TIMERS_CONNECT 10 #define DFLT_BGP_HOLDTIME 9 #define DFLT_BGP_KEEPALIVE 3 #define DFLT_BGP_LOG_NEIGHBOR_CHANGES 1 #define DFLT_BGP_SHOW_HOSTNAME 1 #define DFLT_BGP_DETERMINISTIC_MED 1 #define DFLT_OSPF_LOG_ADJACENCY_CHANGES 1 #define DFLT_OSPF6_LOG_ADJACENCY_CHANGES 1 #else /* !HAVE_DATACENTER */ #define DFLT_BGP_IMPORT_CHECK 0 #define DFLT_BGP_TIMERS_CONNECT 120 #define DFLT_BGP_HOLDTIME 180 #define DFLT_BGP_KEEPALIVE 60 #define DFLT_BGP_LOG_NEIGHBOR_CHANGES 0 #define DFLT_BGP_SHOW_HOSTNAME 0 #define DFLT_BGP_DETERMINISTIC_MED 0 #define DFLT_OSPF_LOG_ADJACENCY_CHANGES 0 #define DFLT_OSPF6_LOG_ADJACENCY_CHANGES 0 #endif /* !HAVE_DATACENTER */ #endif /* _FRR_DEFAULTS_H */ frr-7.2.1/depcomp0000755000000000000000000005602013610377563010574 00000000000000#! /bin/sh # depcomp - compile a program generating dependencies as side-effects scriptversion=2018-03-07.03; # UTC # Copyright (C) 1999-2018 Free Software Foundation, Inc. # This program is free software; you can redistribute it and/or modify # it under the terms of the GNU General Public License as published by # the Free Software Foundation; either version 2, or (at your option) # any later version. # This program is distributed in the hope that it will be useful, # but WITHOUT ANY WARRANTY; without even the implied warranty of # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the # GNU General Public License for more details. # You should have received a copy of the GNU General Public License # along with this program. If not, see . # As a special exception to the GNU General Public License, if you # distribute this file as part of a program that contains a # configuration script generated by Autoconf, you may include it under # the same distribution terms that you use for the rest of that program. # Originally written by Alexandre Oliva . case $1 in '') echo "$0: No command. Try '$0 --help' for more information." 1>&2 exit 1; ;; -h | --h*) cat <<\EOF Usage: depcomp [--help] [--version] PROGRAM [ARGS] Run PROGRAMS ARGS to compile a file, generating dependencies as side-effects. Environment variables: depmode Dependency tracking mode. source Source file read by 'PROGRAMS ARGS'. object Object file output by 'PROGRAMS ARGS'. DEPDIR directory where to store dependencies. depfile Dependency file to output. tmpdepfile Temporary file to use when outputting dependencies. libtool Whether libtool is used (yes/no). Report bugs to . EOF exit $? ;; -v | --v*) echo "depcomp $scriptversion" exit $? ;; esac # Get the directory component of the given path, and save it in the # global variables '$dir'. Note that this directory component will # be either empty or ending with a '/' character. This is deliberate. set_dir_from () { case $1 in */*) dir=`echo "$1" | sed -e 's|/[^/]*$|/|'`;; *) dir=;; esac } # Get the suffix-stripped basename of the given path, and save it the # global variable '$base'. set_base_from () { base=`echo "$1" | sed -e 's|^.*/||' -e 's/\.[^.]*$//'` } # If no dependency file was actually created by the compiler invocation, # we still have to create a dummy depfile, to avoid errors with the # Makefile "include basename.Plo" scheme. make_dummy_depfile () { echo "#dummy" > "$depfile" } # Factor out some common post-processing of the generated depfile. # Requires the auxiliary global variable '$tmpdepfile' to be set. aix_post_process_depfile () { # If the compiler actually managed to produce a dependency file, # post-process it. if test -f "$tmpdepfile"; then # Each line is of the form 'foo.o: dependency.h'. # Do two passes, one to just change these to # $object: dependency.h # and one to simply output # dependency.h: # which is needed to avoid the deleted-header problem. { sed -e "s,^.*\.[$lower]*:,$object:," < "$tmpdepfile" sed -e "s,^.*\.[$lower]*:[$tab ]*,," -e 's,$,:,' < "$tmpdepfile" } > "$depfile" rm -f "$tmpdepfile" else make_dummy_depfile fi } # A tabulation character. tab=' ' # A newline character. nl=' ' # Character ranges might be problematic outside the C locale. # These definitions help. upper=ABCDEFGHIJKLMNOPQRSTUVWXYZ lower=abcdefghijklmnopqrstuvwxyz digits=0123456789 alpha=${upper}${lower} if test -z "$depmode" || test -z "$source" || test -z "$object"; then echo "depcomp: Variables source, object and depmode must be set" 1>&2 exit 1 fi # Dependencies for sub/bar.o or sub/bar.obj go into sub/.deps/bar.Po. depfile=${depfile-`echo "$object" | sed 's|[^\\/]*$|'${DEPDIR-.deps}'/&|;s|\.\([^.]*\)$|.P\1|;s|Pobj$|Po|'`} tmpdepfile=${tmpdepfile-`echo "$depfile" | sed 's/\.\([^.]*\)$/.T\1/'`} rm -f "$tmpdepfile" # Avoid interferences from the environment. gccflag= dashmflag= # Some modes work just like other modes, but use different flags. We # parameterize here, but still list the modes in the big case below, # to make depend.m4 easier to write. Note that we *cannot* use a case # here, because this file can only contain one case statement. if test "$depmode" = hp; then # HP compiler uses -M and no extra arg. gccflag=-M depmode=gcc fi if test "$depmode" = dashXmstdout; then # This is just like dashmstdout with a different argument. dashmflag=-xM depmode=dashmstdout fi cygpath_u="cygpath -u -f -" if test "$depmode" = msvcmsys; then # This is just like msvisualcpp but w/o cygpath translation. # Just convert the backslash-escaped backslashes to single forward # slashes to satisfy depend.m4 cygpath_u='sed s,\\\\,/,g' depmode=msvisualcpp fi if test "$depmode" = msvc7msys; then # This is just like msvc7 but w/o cygpath translation. # Just convert the backslash-escaped backslashes to single forward # slashes to satisfy depend.m4 cygpath_u='sed s,\\\\,/,g' depmode=msvc7 fi if test "$depmode" = xlc; then # IBM C/C++ Compilers xlc/xlC can output gcc-like dependency information. gccflag=-qmakedep=gcc,-MF depmode=gcc fi case "$depmode" in gcc3) ## gcc 3 implements dependency tracking that does exactly what ## we want. Yay! Note: for some reason libtool 1.4 doesn't like ## it if -MD -MP comes after the -MF stuff. Hmm. ## Unfortunately, FreeBSD c89 acceptance of flags depends upon ## the command line argument order; so add the flags where they ## appear in depend2.am. Note that the slowdown incurred here ## affects only configure: in makefiles, %FASTDEP% shortcuts this. for arg do case $arg in -c) set fnord "$@" -MT "$object" -MD -MP -MF "$tmpdepfile" "$arg" ;; *) set fnord "$@" "$arg" ;; esac shift # fnord shift # $arg done "$@" stat=$? if test $stat -ne 0; then rm -f "$tmpdepfile" exit $stat fi mv "$tmpdepfile" "$depfile" ;; gcc) ## Note that this doesn't just cater to obsosete pre-3.x GCC compilers. ## but also to in-use compilers like IMB xlc/xlC and the HP C compiler. ## (see the conditional assignment to $gccflag above). ## There are various ways to get dependency output from gcc. Here's ## why we pick this rather obscure method: ## - Don't want to use -MD because we'd like the dependencies to end ## up in a subdir. Having to rename by hand is ugly. ## (We might end up doing this anyway to support other compilers.) ## - The DEPENDENCIES_OUTPUT environment variable makes gcc act like ## -MM, not -M (despite what the docs say). Also, it might not be ## supported by the other compilers which use the 'gcc' depmode. ## - Using -M directly means running the compiler twice (even worse ## than renaming). if test -z "$gccflag"; then gccflag=-MD, fi "$@" -Wp,"$gccflag$tmpdepfile" stat=$? if test $stat -ne 0; then rm -f "$tmpdepfile" exit $stat fi rm -f "$depfile" echo "$object : \\" > "$depfile" # The second -e expression handles DOS-style file names with drive # letters. sed -e 's/^[^:]*: / /' \ -e 's/^['$alpha']:\/[^:]*: / /' < "$tmpdepfile" >> "$depfile" ## This next piece of magic avoids the "deleted header file" problem. ## The problem is that when a header file which appears in a .P file ## is deleted, the dependency causes make to die (because there is ## typically no way to rebuild the header). We avoid this by adding ## dummy dependencies for each header file. Too bad gcc doesn't do ## this for us directly. ## Some versions of gcc put a space before the ':'. On the theory ## that the space means something, we add a space to the output as ## well. hp depmode also adds that space, but also prefixes the VPATH ## to the object. Take care to not repeat it in the output. ## Some versions of the HPUX 10.20 sed can't process this invocation ## correctly. Breaking it into two sed invocations is a workaround. tr ' ' "$nl" < "$tmpdepfile" \ | sed -e 's/^\\$//' -e '/^$/d' -e "s|.*$object$||" -e '/:$/d' \ | sed -e 's/$/ :/' >> "$depfile" rm -f "$tmpdepfile" ;; hp) # This case exists only to let depend.m4 do its work. It works by # looking at the text of this script. This case will never be run, # since it is checked for above. exit 1 ;; sgi) if test "$libtool" = yes; then "$@" "-Wp,-MDupdate,$tmpdepfile" else "$@" -MDupdate "$tmpdepfile" fi stat=$? if test $stat -ne 0; then rm -f "$tmpdepfile" exit $stat fi rm -f "$depfile" if test -f "$tmpdepfile"; then # yes, the sourcefile depend on other files echo "$object : \\" > "$depfile" # Clip off the initial element (the dependent). Don't try to be # clever and replace this with sed code, as IRIX sed won't handle # lines with more than a fixed number of characters (4096 in # IRIX 6.2 sed, 8192 in IRIX 6.5). We also remove comment lines; # the IRIX cc adds comments like '#:fec' to the end of the # dependency line. tr ' ' "$nl" < "$tmpdepfile" \ | sed -e 's/^.*\.o://' -e 's/#.*$//' -e '/^$/ d' \ | tr "$nl" ' ' >> "$depfile" echo >> "$depfile" # The second pass generates a dummy entry for each header file. tr ' ' "$nl" < "$tmpdepfile" \ | sed -e 's/^.*\.o://' -e 's/#.*$//' -e '/^$/ d' -e 's/$/:/' \ >> "$depfile" else make_dummy_depfile fi rm -f "$tmpdepfile" ;; xlc) # This case exists only to let depend.m4 do its work. It works by # looking at the text of this script. This case will never be run, # since it is checked for above. exit 1 ;; aix) # The C for AIX Compiler uses -M and outputs the dependencies # in a .u file. In older versions, this file always lives in the # current directory. Also, the AIX compiler puts '$object:' at the # start of each line; $object doesn't have directory information. # Version 6 uses the directory in both cases. set_dir_from "$object" set_base_from "$object" if test "$libtool" = yes; then tmpdepfile1=$dir$base.u tmpdepfile2=$base.u tmpdepfile3=$dir.libs/$base.u "$@" -Wc,-M else tmpdepfile1=$dir$base.u tmpdepfile2=$dir$base.u tmpdepfile3=$dir$base.u "$@" -M fi stat=$? if test $stat -ne 0; then rm -f "$tmpdepfile1" "$tmpdepfile2" "$tmpdepfile3" exit $stat fi for tmpdepfile in "$tmpdepfile1" "$tmpdepfile2" "$tmpdepfile3" do test -f "$tmpdepfile" && break done aix_post_process_depfile ;; tcc) # tcc (Tiny C Compiler) understand '-MD -MF file' since version 0.9.26 # FIXME: That version still under development at the moment of writing. # Make that this statement remains true also for stable, released # versions. # It will wrap lines (doesn't matter whether long or short) with a # trailing '\', as in: # # foo.o : \ # foo.c \ # foo.h \ # # It will put a trailing '\' even on the last line, and will use leading # spaces rather than leading tabs (at least since its commit 0394caf7 # "Emit spaces for -MD"). "$@" -MD -MF "$tmpdepfile" stat=$? if test $stat -ne 0; then rm -f "$tmpdepfile" exit $stat fi rm -f "$depfile" # Each non-empty line is of the form 'foo.o : \' or ' dep.h \'. # We have to change lines of the first kind to '$object: \'. sed -e "s|.*:|$object :|" < "$tmpdepfile" > "$depfile" # And for each line of the second kind, we have to emit a 'dep.h:' # dummy dependency, to avoid the deleted-header problem. sed -n -e 's|^ *\(.*\) *\\$|\1:|p' < "$tmpdepfile" >> "$depfile" rm -f "$tmpdepfile" ;; ## The order of this option in the case statement is important, since the ## shell code in configure will try each of these formats in the order ## listed in this file. A plain '-MD' option would be understood by many ## compilers, so we must ensure this comes after the gcc and icc options. pgcc) # Portland's C compiler understands '-MD'. # Will always output deps to 'file.d' where file is the root name of the # source file under compilation, even if file resides in a subdirectory. # The object file name does not affect the name of the '.d' file. # pgcc 10.2 will output # foo.o: sub/foo.c sub/foo.h # and will wrap long lines using '\' : # foo.o: sub/foo.c ... \ # sub/foo.h ... \ # ... set_dir_from "$object" # Use the source, not the object, to determine the base name, since # that's sadly what pgcc will do too. set_base_from "$source" tmpdepfile=$base.d # For projects that build the same source file twice into different object # files, the pgcc approach of using the *source* file root name can cause # problems in parallel builds. Use a locking strategy to avoid stomping on # the same $tmpdepfile. lockdir=$base.d-lock trap " echo '$0: caught signal, cleaning up...' >&2 rmdir '$lockdir' exit 1 " 1 2 13 15 numtries=100 i=$numtries while test $i -gt 0; do # mkdir is a portable test-and-set. if mkdir "$lockdir" 2>/dev/null; then # This process acquired the lock. "$@" -MD stat=$? # Release the lock. rmdir "$lockdir" break else # If the lock is being held by a different process, wait # until the winning process is done or we timeout. while test -d "$lockdir" && test $i -gt 0; do sleep 1 i=`expr $i - 1` done fi i=`expr $i - 1` done trap - 1 2 13 15 if test $i -le 0; then echo "$0: failed to acquire lock after $numtries attempts" >&2 echo "$0: check lockdir '$lockdir'" >&2 exit 1 fi if test $stat -ne 0; then rm -f "$tmpdepfile" exit $stat fi rm -f "$depfile" # Each line is of the form `foo.o: dependent.h', # or `foo.o: dep1.h dep2.h \', or ` dep3.h dep4.h \'. # Do two passes, one to just change these to # `$object: dependent.h' and one to simply `dependent.h:'. sed "s,^[^:]*:,$object :," < "$tmpdepfile" > "$depfile" # Some versions of the HPUX 10.20 sed can't process this invocation # correctly. Breaking it into two sed invocations is a workaround. sed 's,^[^:]*: \(.*\)$,\1,;s/^\\$//;/^$/d;/:$/d' < "$tmpdepfile" \ | sed -e 's/$/ :/' >> "$depfile" rm -f "$tmpdepfile" ;; hp2) # The "hp" stanza above does not work with aCC (C++) and HP's ia64 # compilers, which have integrated preprocessors. The correct option # to use with these is +Maked; it writes dependencies to a file named # 'foo.d', which lands next to the object file, wherever that # happens to be. # Much of this is similar to the tru64 case; see comments there. set_dir_from "$object" set_base_from "$object" if test "$libtool" = yes; then tmpdepfile1=$dir$base.d tmpdepfile2=$dir.libs/$base.d "$@" -Wc,+Maked else tmpdepfile1=$dir$base.d tmpdepfile2=$dir$base.d "$@" +Maked fi stat=$? if test $stat -ne 0; then rm -f "$tmpdepfile1" "$tmpdepfile2" exit $stat fi for tmpdepfile in "$tmpdepfile1" "$tmpdepfile2" do test -f "$tmpdepfile" && break done if test -f "$tmpdepfile"; then sed -e "s,^.*\.[$lower]*:,$object:," "$tmpdepfile" > "$depfile" # Add 'dependent.h:' lines. sed -ne '2,${ s/^ *// s/ \\*$// s/$/:/ p }' "$tmpdepfile" >> "$depfile" else make_dummy_depfile fi rm -f "$tmpdepfile" "$tmpdepfile2" ;; tru64) # The Tru64 compiler uses -MD to generate dependencies as a side # effect. 'cc -MD -o foo.o ...' puts the dependencies into 'foo.o.d'. # At least on Alpha/Redhat 6.1, Compaq CCC V6.2-504 seems to put # dependencies in 'foo.d' instead, so we check for that too. # Subdirectories are respected. set_dir_from "$object" set_base_from "$object" if test "$libtool" = yes; then # Libtool generates 2 separate objects for the 2 libraries. These # two compilations output dependencies in $dir.libs/$base.o.d and # in $dir$base.o.d. We have to check for both files, because # one of the two compilations can be disabled. We should prefer # $dir$base.o.d over $dir.libs/$base.o.d because the latter is # automatically cleaned when .libs/ is deleted, while ignoring # the former would cause a distcleancheck panic. tmpdepfile1=$dir$base.o.d # libtool 1.5 tmpdepfile2=$dir.libs/$base.o.d # Likewise. tmpdepfile3=$dir.libs/$base.d # Compaq CCC V6.2-504 "$@" -Wc,-MD else tmpdepfile1=$dir$base.d tmpdepfile2=$dir$base.d tmpdepfile3=$dir$base.d "$@" -MD fi stat=$? if test $stat -ne 0; then rm -f "$tmpdepfile1" "$tmpdepfile2" "$tmpdepfile3" exit $stat fi for tmpdepfile in "$tmpdepfile1" "$tmpdepfile2" "$tmpdepfile3" do test -f "$tmpdepfile" && break done # Same post-processing that is required for AIX mode. aix_post_process_depfile ;; msvc7) if test "$libtool" = yes; then showIncludes=-Wc,-showIncludes else showIncludes=-showIncludes fi "$@" $showIncludes > "$tmpdepfile" stat=$? grep -v '^Note: including file: ' "$tmpdepfile" if test $stat -ne 0; then rm -f "$tmpdepfile" exit $stat fi rm -f "$depfile" echo "$object : \\" > "$depfile" # The first sed program below extracts the file names and escapes # backslashes for cygpath. The second sed program outputs the file # name when reading, but also accumulates all include files in the # hold buffer in order to output them again at the end. This only # works with sed implementations that can handle large buffers. sed < "$tmpdepfile" -n ' /^Note: including file: *\(.*\)/ { s//\1/ s/\\/\\\\/g p }' | $cygpath_u | sort -u | sed -n ' s/ /\\ /g s/\(.*\)/'"$tab"'\1 \\/p s/.\(.*\) \\/\1:/ H $ { s/.*/'"$tab"'/ G p }' >> "$depfile" echo >> "$depfile" # make sure the fragment doesn't end with a backslash rm -f "$tmpdepfile" ;; msvc7msys) # This case exists only to let depend.m4 do its work. It works by # looking at the text of this script. This case will never be run, # since it is checked for above. exit 1 ;; #nosideeffect) # This comment above is used by automake to tell side-effect # dependency tracking mechanisms from slower ones. dashmstdout) # Important note: in order to support this mode, a compiler *must* # always write the preprocessed file to stdout, regardless of -o. "$@" || exit $? # Remove the call to Libtool. if test "$libtool" = yes; then while test "X$1" != 'X--mode=compile'; do shift done shift fi # Remove '-o $object'. IFS=" " for arg do case $arg in -o) shift ;; $object) shift ;; *) set fnord "$@" "$arg" shift # fnord shift # $arg ;; esac done test -z "$dashmflag" && dashmflag=-M # Require at least two characters before searching for ':' # in the target name. This is to cope with DOS-style filenames: # a dependency such as 'c:/foo/bar' could be seen as target 'c' otherwise. "$@" $dashmflag | sed "s|^[$tab ]*[^:$tab ][^:][^:]*:[$tab ]*|$object: |" > "$tmpdepfile" rm -f "$depfile" cat < "$tmpdepfile" > "$depfile" # Some versions of the HPUX 10.20 sed can't process this sed invocation # correctly. Breaking it into two sed invocations is a workaround. tr ' ' "$nl" < "$tmpdepfile" \ | sed -e 's/^\\$//' -e '/^$/d' -e '/:$/d' \ | sed -e 's/$/ :/' >> "$depfile" rm -f "$tmpdepfile" ;; dashXmstdout) # This case only exists to satisfy depend.m4. It is never actually # run, as this mode is specially recognized in the preamble. exit 1 ;; makedepend) "$@" || exit $? # Remove any Libtool call if test "$libtool" = yes; then while test "X$1" != 'X--mode=compile'; do shift done shift fi # X makedepend shift cleared=no eat=no for arg do case $cleared in no) set ""; shift cleared=yes ;; esac if test $eat = yes; then eat=no continue fi case "$arg" in -D*|-I*) set fnord "$@" "$arg"; shift ;; # Strip any option that makedepend may not understand. Remove # the object too, otherwise makedepend will parse it as a source file. -arch) eat=yes ;; -*|$object) ;; *) set fnord "$@" "$arg"; shift ;; esac done obj_suffix=`echo "$object" | sed 's/^.*\././'` touch "$tmpdepfile" ${MAKEDEPEND-makedepend} -o"$obj_suffix" -f"$tmpdepfile" "$@" rm -f "$depfile" # makedepend may prepend the VPATH from the source file name to the object. # No need to regex-escape $object, excess matching of '.' is harmless. sed "s|^.*\($object *:\)|\1|" "$tmpdepfile" > "$depfile" # Some versions of the HPUX 10.20 sed can't process the last invocation # correctly. Breaking it into two sed invocations is a workaround. sed '1,2d' "$tmpdepfile" \ | tr ' ' "$nl" \ | sed -e 's/^\\$//' -e '/^$/d' -e '/:$/d' \ | sed -e 's/$/ :/' >> "$depfile" rm -f "$tmpdepfile" "$tmpdepfile".bak ;; cpp) # Important note: in order to support this mode, a compiler *must* # always write the preprocessed file to stdout. "$@" || exit $? # Remove the call to Libtool. if test "$libtool" = yes; then while test "X$1" != 'X--mode=compile'; do shift done shift fi # Remove '-o $object'. IFS=" " for arg do case $arg in -o) shift ;; $object) shift ;; *) set fnord "$@" "$arg" shift # fnord shift # $arg ;; esac done "$@" -E \ | sed -n -e '/^# [0-9][0-9]* "\([^"]*\)".*/ s:: \1 \\:p' \ -e '/^#line [0-9][0-9]* "\([^"]*\)".*/ s:: \1 \\:p' \ | sed '$ s: \\$::' > "$tmpdepfile" rm -f "$depfile" echo "$object : \\" > "$depfile" cat < "$tmpdepfile" >> "$depfile" sed < "$tmpdepfile" '/^$/d;s/^ //;s/ \\$//;s/$/ :/' >> "$depfile" rm -f "$tmpdepfile" ;; msvisualcpp) # Important note: in order to support this mode, a compiler *must* # always write the preprocessed file to stdout. "$@" || exit $? # Remove the call to Libtool. if test "$libtool" = yes; then while test "X$1" != 'X--mode=compile'; do shift done shift fi IFS=" " for arg do case "$arg" in -o) shift ;; $object) shift ;; "-Gm"|"/Gm"|"-Gi"|"/Gi"|"-ZI"|"/ZI") set fnord "$@" shift shift ;; *) set fnord "$@" "$arg" shift shift ;; esac done "$@" -E 2>/dev/null | sed -n '/^#line [0-9][0-9]* "\([^"]*\)"/ s::\1:p' | $cygpath_u | sort -u > "$tmpdepfile" rm -f "$depfile" echo "$object : \\" > "$depfile" sed < "$tmpdepfile" -n -e 's% %\\ %g' -e '/^\(.*\)$/ s::'"$tab"'\1 \\:p' >> "$depfile" echo "$tab" >> "$depfile" sed < "$tmpdepfile" -n -e 's% %\\ %g' -e '/^\(.*\)$/ s::\1\::p' >> "$depfile" rm -f "$tmpdepfile" ;; msvcmsys) # This case exists only to let depend.m4 do its work. It works by # looking at the text of this script. This case will never be run, # since it is checked for above. exit 1 ;; none) exec "$@" ;; *) echo "Unknown depmode $depmode" 1>&2 exit 1 ;; esac exit 0 # Local Variables: # mode: shell-script # sh-indentation: 2 # eval: (add-hook 'before-save-hook 'time-stamp) # time-stamp-start: "scriptversion=" # time-stamp-format: "%:y-%02m-%02d.%02H" # time-stamp-time-zone: "UTC0" # time-stamp-end: "; # UTC" # End: frr-7.2.1/doc/0000755000000000000000000000000013610377563010041 500000000000000frr-7.2.1/doc/Makefile0000644000000000000000000000054713610377563011427 00000000000000all: ALWAYS @$(MAKE) -s -C .. doc %: ALWAYS @$(MAKE) -s -C .. doc/$@ html: @$(MAKE) -s -C .. doc/user/_build/html/.buildinfo info: @$(MAKE) -s -C .. doc/user/_build/texinfo/frr.info pdf: @$(MAKE) -s -C .. doc/user/_build/latexpdf frr.info: info frr.pdf: pdf Makefile: #nothing ALWAYS: .PHONY: ALWAYS makefiles html info frr.info pdf frr.pdf .SUFFIXES: frr-7.2.1/doc/developer/0000755000000000000000000000000013610377563012026 500000000000000frr-7.2.1/doc/developer/Makefile0000644000000000000000000000053713610377563013413 00000000000000all: ALWAYS @$(MAKE) -s -C ../.. developer-html help: ALWAYS @$(MAKE) -s -C ../.. doc/help pdf: ALWAYS @$(MAKE) -s -C ../.. doc/developer/_build/latexpdf info: ALWAYS @$(MAKE) -s -C ../.. doc/developer/_build/texinfo/frr.info %: ALWAYS @$(MAKE) -s -C ../.. doc/developer/_build/$@ Makefile: #nothing ALWAYS: .PHONY: ALWAYS makefiles .SUFFIXES: frr-7.2.1/doc/developer/bgp-typecodes.rst0000644000000000000000000000344713610377563015255 00000000000000BGP-4[+] UPDATE Attribute Preprocessor Constants ================================================ This is a list of preprocessor constants that map to BGP attributes defined by various BGP RFCs. In the code these are defined as BGP_ATTR_. +-------+------------------+------------------------------------------+ | Value | Attribute | References | +=======+==================+==========================================+ | 1 | ORIGIN | [RFC 4271] | | 2 | AS_PATH | [RFC 4271] | | 3 | NEXT_HOP | [RFC 4271] | | 4 | MULTI_EXIT_DISC | [RFC 4271] | | 5 | LOCAL_PREF | [RFC 4271] | | 6 | ATOMIC_AGGREGATE | [RFC 4271] | | 7 | AGGREGATOR | [RFC 4271] | | 8 | COMMUNITIES | [RFC 1997] | | 9 | ORIGINATOR_ID | [RFC 4456] | | 10 | CLUSTER_LIST | [RFC 4456] | | 11 | DPA | [draft-ietf-idr-bgp-dpa-05.txt(expired)] | | 12 | ADVERTISER | [RFC 1863] | | 13 | RCID_PATH | [RFC 1863] | | 14 | MP_REACH_NLRI | [RFC 4760] | | 15 | MP_UNREACH_NLRI | [RFC 4760] | | 16 | EXT_COMMUNITIES | [RFC 4360] | | 17 | AS4_PATH | [RFC 4893] | | 18 | AS4_AGGREGATOR | [RFC 4893] | +-------+------------------+------------------------------------------+ frr-7.2.1/doc/developer/bgpd.rst0000644000000000000000000000013713610377563013415 00000000000000.. _bgpd: **** BGPD **** .. toctree:: :maxdepth: 2 next-hop-tracking bgp-typecodes frr-7.2.1/doc/developer/building-frr-for-alpine.rst0000644000000000000000000000510013610377563017112 00000000000000Alpine Linux 3.7+ ========================================================= For building Alpine Linux dev packages, we use docker. Install docker 17.05 or later ----------------------------- Depending on your host, there are different ways of installing docker. Refer to the documentation here for instructions on how to install a free version of docker: https://www.docker.com/community-edition Pre-built packages and docker images ------------------------------------ The master branch of https://github.com/frrouting/frr.git has a continuous delivery of docker images to docker hub at: https://hub.docker.com/r/ajones17/frr/. These images have the frr packages in /pkgs/apk and have the frr package pre-installed. To copy Alpine packages out of these images: :: id=`docker create ajones17/frr:latest` docker cp ${id}:/pkgs _some_directory_ docker rm $id To run the frr daemons (see below for how to configure them): :: docker run -it --rm --name frr ajones17/frr:latest docker exec -it frr /bin/sh Work with sources ----------------- :: git clone https://github.com/frrouting/frr.git frr cd frr Build apk packages ------------------ :: ./docker/alpine/build.sh This will put the apk packages in: :: ./docker/pkgs/apk/x86_64/ Usage ----- To create a base image with the frr packages installed: :: docker build --rm -f docker/alpine/Dockerfile -t frr:latest . Or, if you don't have a git checkout of the sources, you can build a base image directly off the github account: :: docker build --rm -f docker/alpine/Dockerfile -t frr:latest \ https://github.com/frrouting/frr.git And to run the image: :: docker run -it --rm --name frr frr:latest In the default configuration, none of the frr daemons will be running. To configure the daemons, exec into the container and edit the configuration files or mount a volume with configuration files into the container on startup. To configure by hand: :: docker exec -it frr /bin/sh vi /etc/frr/daemons cp /etc/frr/zebra.conf.sample /etc/frr/zebra.conf vi /etc/frr/zebra.conf /etc/init.d/frr start Or, to configure the daemons using /etc/frr from a host volume, put the config files in, say, ./docker/etc and bind mount that into the container: :: docker run -it --rm -v `pwd`/docker/etc:/etc/frr frr:latest We can also build the base image directly from docker-compose, with a docker-compose.yml file like this one: :: version: '2.2' services: frr: build: context: https://github.com/frrouting/frr.git dockerfile: docker/alpine/Dockerfile frr-7.2.1/doc/developer/building-frr-for-centos6.rst0000644000000000000000000001662113610377563017235 00000000000000.. _building-centos6: CentOS 6 ======================================== This document describes installation from source. If you want to build an RPM, see :ref:`packaging-redhat`. Instructions are tested with ``CentOS 6.8`` on ``x86_64`` platform Warning: -------- ``CentOS 6`` is very old and not fully supported by the FRR community anymore. Building FRR takes multiple manual steps to update the build system with newer packages than what's available from the archives. However, the built packages can still be installed afterwards on a standard ``CentOS 6`` without any special packages. Support for CentOS 6 is now on a best-effort base by the community. CentOS 6 restrictions: ---------------------- - PIMd is not supported on ``CentOS 6``. Upgrade to ``CentOS 7`` if PIMd is needed - MPLS is not supported on ``CentOS 6``. MPLS requires Linux Kernel 4.5 or higher (LDP can be built, but may have limited use without MPLS) - Zebra is unable to detect what bridge/vrf an interface is associated with (IFLA\_INFO\_SLAVE\_KIND does not exist in the kernel headers, you can use a newer kernel + headers to get this functionality) - frr\_reload.py will not work, as this requires Python 2.7, and CentOS 6 only has 2.6. You can install Python 2.7 via IUS, but it won't work properly unless you compile and install the ipaddr package for it. - Building the package requires Sphinx >= 1.1. Only a non-standard package provides a newer sphinx and requires manual installation (see below) Install required packages ------------------------- Add packages: .. code-block:: shell sudo yum install git autoconf automake libtool make \ readline-devel texinfo net-snmp-devel groff pkgconfig \ json-c-devel pam-devel flex epel-release c-ares-devel libcap-devel Install newer version of bison (CentOS 6 package source is too old) from CentOS 7: .. code-block:: shell sudo yum install rpm-build curl -O http://vault.centos.org/7.0.1406/os/Source/SPackages/bison-2.7-4.el7.src.rpm rpmbuild --rebuild ./bison-2.7-4.el7.src.rpm sudo yum install ./rpmbuild/RPMS/x86_64/bison-2.7-4.el6.x86_64.rpm rm -rf rpmbuild Install newer version of autoconf and automake (Package versions are too old): .. code-block:: shell curl -O http://ftp.gnu.org/gnu/autoconf/autoconf-2.69.tar.gz tar xvf autoconf-2.69.tar.gz cd autoconf-2.69 ./configure --prefix=/usr make sudo make install cd .. curl -O http://ftp.gnu.org/gnu/automake/automake-1.15.tar.gz tar xvf automake-1.15.tar.gz cd automake-1.15 ./configure --prefix=/usr make sudo make install cd .. Install ``Python 2.7`` in parallel to default 2.6. Make sure you've install EPEL (``epel-release`` as above). Then install current ``python27``: ``python27-devel`` and ``pytest`` .. code-block:: shell sudo rpm -ivh http://dl.fedoraproject.org/pub/epel/6/x86_64/epel-release-6-8.noarch.rpm sudo rpm -ivh https://centos6.iuscommunity.org/ius-release.rpm sudo yum install python27 python27-pip python27-devel sudo pip2.7 install pytest Please note that ``CentOS 6`` needs to keep python pointing to version 2.6 for ``yum`` to keep working, so don't create a symlink for python2.7 to python. Install newer ``Sphinx-Build`` based on ``Python 2.7``. Create a new repo ``/etc/yum.repos.d/puias6.repo`` with the following contents: :: ### Name: RPM Repository for RHEL 6 - PUIAS (used for Sphinx-Build) ### URL: http://springdale.math.ias.edu/data/puias/computational [puias-computational] name = RPM Repository for RHEL 6 - Sphinx-Build baseurl = http://springdale.math.ias.edu/data/puias/computational/$releasever/$basearch #mirrorlist = enabled = 1 protect = 0 gpgkey = gpgcheck = 0 Update rpm database & Install newer sphinx .. code-block:: shell sudo yum update sudo yum install python27-sphinx .. include:: building-libyang.rst Get FRR, compile it and install it (from Git) --------------------------------------------- **This assumes you want to build and install FRR from source and not using any packages** Add frr groups and user ^^^^^^^^^^^^^^^^^^^^^^^ .. code-block:: shell sudo groupadd -g 92 frr sudo groupadd -r -g 85 frrvty sudo useradd -u 92 -g 92 -M -r -G frrvty -s /sbin/nologin \ -c "FRR FRRouting suite" -d /var/run/frr frr Download Source, configure and compile it ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ (You may prefer different options on configure statement. These are just an example.) .. code-block:: shell git clone https://github.com/frrouting/frr.git frr cd frr ./bootstrap.sh ./configure \ --bindir=/usr/bin \ --sbindir=/usr/lib/frr \ --sysconfdir=/etc/frr \ --libdir=/usr/lib/frr \ --libexecdir=/usr/lib/frr \ --localstatedir=/var/run/frr \ --with-moduledir=/usr/lib/frr/modules \ --disable-pimd \ --enable-snmp=agentx \ --enable-multipath=64 \ --enable-user=frr \ --enable-group=frr \ --enable-vty-group=frrvty \ --disable-exampledir \ --disable-ldpd \ --enable-fpm \ --with-pkg-git-version \ --with-pkg-extra-version=-MyOwnFRRVersion make make check sudo make install Create empty FRR configuration files ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ .. code-block:: shell sudo mkdir /var/log/frr sudo mkdir /etc/frr For integrated config file: .. code-block:: shell sudo touch /etc/frr/frr.conf For individual config files: .. note:: Integrated config is preferred to individual config. .. code-block:: shell sudo touch /etc/frr/babeld.conf sudo touch /etc/frr/bfdd.conf sudo touch /etc/frr/bgpd.conf sudo touch /etc/frr/eigrpd.conf sudo touch /etc/frr/isisd.conf sudo touch /etc/frr/ldpd.conf sudo touch /etc/frr/nhrpd.conf sudo touch /etc/frr/ospf6d.conf sudo touch /etc/frr/ospfd.conf sudo touch /etc/frr/pbrd.conf sudo touch /etc/frr/pimd.conf sudo touch /etc/frr/ripd.conf sudo touch /etc/frr/ripngd.conf sudo touch /etc/frr/staticd.conf sudo touch /etc/frr/zebra.conf sudo chown -R frr:frr /etc/frr/ sudo touch /etc/frr/vtysh.conf sudo chown frr:frrvty /etc/frr/vtysh.conf sudo chmod 640 /etc/frr/*.conf Install daemon config file ^^^^^^^^^^^^^^^^^^^^^^^^^^ .. code-block:: shell sudo install -p -m 644 redhat/daemons /etc/frr/ sudo chown frr:frr /etc/frr/daemons Edit /etc/frr/daemons as needed to select the required daemons ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ Look for the section with ``watchfrr_enable=...`` and ``zebra=...`` etc. Enable the daemons as required by changing the value to ``yes`` Enable IP & IPv6 forwarding ^^^^^^^^^^^^^^^^^^^^^^^^^^^ Edit :file:`/etc/sysctl.conf` and set the following values (ignore the other settings):: # Controls IP packet forwarding net.ipv4.ip_forward = 1 net.ipv6.conf.all.forwarding=1 # Controls source route verification net.ipv4.conf.default.rp_filter = 0 Load the modified sysctl's on the system: .. code-block:: shell sudo sysctl -p /etc/sysctl.d/90-routing-sysctl.conf Add init.d startup files ^^^^^^^^^^^^^^^^^^^^^^^^ .. code-block:: shell sudo install -p -m 755 redhat/frr.init /etc/init.d/frr sudo chkconfig --add frr Enable FRR daemon at startup ^^^^^^^^^^^^^^^^^^^^^^^^^^^^ .. code-block:: shell sudo chkconfig frr on Start FRR manually (or reboot) ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ .. code-block:: shell sudo /etc/init.d/frr start frr-7.2.1/doc/developer/building-frr-for-centos7.rst0000644000000000000000000001000113610377563017220 00000000000000CentOS 7 ======================================== This document describes installation from source. If you want to build an RPM, see :ref:`packaging-redhat`. CentOS 7 restrictions: ---------------------- - MPLS is not supported on ``CentOS 7`` with default kernel. MPLS requires Linux Kernel 4.5 or higher (LDP can be built, but may have limited use without MPLS) Install required packages ------------------------- Add packages: :: sudo yum install git autoconf automake libtool make \ readline-devel texinfo net-snmp-devel groff pkgconfig \ json-c-devel pam-devel bison flex pytest c-ares-devel \ python-devel systemd-devel python-sphinx libcap-devel .. include:: building-libyang.rst Get FRR, compile it and install it (from Git) --------------------------------------------- **This assumes you want to build and install FRR from source and not using any packages** Add frr groups and user ^^^^^^^^^^^^^^^^^^^^^^^ :: sudo groupadd -g 92 frr sudo groupadd -r -g 85 frrvty sudo useradd -u 92 -g 92 -M -r -G frrvty -s /sbin/nologin \ -c "FRR FRRouting suite" -d /var/run/frr frr Download Source, configure and compile it ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ (You may prefer different options on configure statement. These are just an example.) :: git clone https://github.com/frrouting/frr.git frr cd frr ./bootstrap.sh ./configure \ --bindir=/usr/bin \ --sbindir=/usr/lib/frr \ --sysconfdir=/etc/frr \ --libdir=/usr/lib/frr \ --libexecdir=/usr/lib/frr \ --localstatedir=/var/run/frr \ --with-moduledir=/usr/lib/frr/modules \ --enable-snmp=agentx \ --enable-multipath=64 \ --enable-user=frr \ --enable-group=frr \ --enable-vty-group=frrvty \ --enable-systemd=yes \ --disable-exampledir \ --disable-ldpd \ --enable-fpm \ --with-pkg-git-version \ --with-pkg-extra-version=-MyOwnFRRVersion \ SPHINXBUILD=/usr/bin/sphinx-build make make check sudo make install Create empty FRR configuration files ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ :: sudo mkdir /var/log/frr sudo mkdir /etc/frr sudo touch /etc/frr/zebra.conf sudo touch /etc/frr/bgpd.conf sudo touch /etc/frr/ospfd.conf sudo touch /etc/frr/ospf6d.conf sudo touch /etc/frr/isisd.conf sudo touch /etc/frr/ripd.conf sudo touch /etc/frr/ripngd.conf sudo touch /etc/frr/pimd.conf sudo touch /etc/frr/nhrpd.conf sudo touch /etc/frr/eigrpd.conf sudo touch /etc/frr/babeld.conf sudo chown -R frr:frr /etc/frr/ sudo touch /etc/frr/vtysh.conf sudo chown frr:frrvty /etc/frr/vtysh.conf sudo chmod 640 /etc/frr/*.conf Install daemon config file ^^^^^^^^^^^^^^^^^^^^^^^^^^ :: sudo install -p -m 644 redhat/daemons /etc/frr/ sudo chown frr:frr /etc/frr/daemons Edit /etc/frr/daemons as needed to select the required daemons ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ Look for the section with ``watchfrr_enable=...`` and ``zebra=...`` etc. Enable the daemons as required by changing the value to ``yes`` Enable IP & IPv6 forwarding ^^^^^^^^^^^^^^^^^^^^^^^^^^^ Create a new file ``/etc/sysctl.d/90-routing-sysctl.conf`` with the following content: :: # Sysctl for routing # # Routing: We need to forward packets net.ipv4.conf.all.forwarding=1 net.ipv6.conf.all.forwarding=1 Load the modified sysctl's on the system: :: sudo sysctl -p /etc/sysctl.d/90-routing-sysctl.conf Install frr Service and redhat init files ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ :: sudo install -p -m 644 redhat/frr.service /usr/lib/systemd/system/frr.service sudo install -p -m 755 redhat/frr.init /usr/lib/frr/frr Register the systemd files ^^^^^^^^^^^^^^^^^^^^^^^^^^ :: sudo systemctl preset frr.service Enable required frr at startup ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ :: sudo systemctl enable frr Reboot or start FRR manually ^^^^^^^^^^^^^^^^^^^^^^^^^^^^ :: sudo systemctl start frr frr-7.2.1/doc/developer/building-frr-for-debian8.rst0000644000000000000000000001102013610377563017152 00000000000000Debian 8 ======================================== Debian 8 restrictions: ---------------------- - MPLS is not supported on ``Debian 8`` with default kernel. MPLS requires Linux Kernel 4.5 or higher (LDP can be built, but may have limited use without MPLS) Install required packages ------------------------- Add packages: :: sudo apt-get install git autoconf automake libtool make \ libreadline-dev texinfo libjson-c-dev pkg-config bison flex python3-pip \ libc-ares-dev python3-dev python3-sphinx build-essential libsystemd-dev \ libsnmp-dev libcap-dev Install newer pytest (>3.0) from pip :: sudo pip3 install pytest .. include:: building-libyang.rst Get FRR, compile it and install it (from Git) --------------------------------------------- **This assumes you want to build and install FRR from source and not using any packages** Add frr groups and user ^^^^^^^^^^^^^^^^^^^^^^^ :: sudo addgroup --system --gid 92 frr sudo addgroup --system --gid 85 frrvty sudo adduser --system --ingroup frr --home /var/run/frr/ \ --gecos "FRR suite" --shell /bin/false frr sudo usermod -a -G frrvty frr Download Source, configure and compile it ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ (You may prefer different options on configure statement. These are just an example.) :: git clone https://github.com/frrouting/frr.git frr cd frr ./bootstrap.sh ./configure \ --enable-exampledir=/usr/share/doc/frr/examples/ \ --localstatedir=/var/run/frr \ --sbindir=/usr/lib/frr \ --sysconfdir=/etc/frr \ --enable-multipath=64 \ --enable-user=frr \ --enable-group=frr \ --enable-vty-group=frrvty \ --enable-configfile-mask=0640 \ --enable-logfile-mask=0640 \ --enable-fpm \ --with-pkg-git-version \ --with-pkg-extra-version=-MyOwnFRRVersion make make check sudo make install Create empty FRR configuration files ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ :: sudo install -m 755 -o frr -g frr -d /var/log/frr sudo install -m 775 -o frr -g frrvty -d /etc/frr sudo install -m 640 -o frr -g frr /dev/null /etc/frr/zebra.conf sudo install -m 640 -o frr -g frr /dev/null /etc/frr/bgpd.conf sudo install -m 640 -o frr -g frr /dev/null /etc/frr/ospfd.conf sudo install -m 640 -o frr -g frr /dev/null /etc/frr/ospf6d.conf sudo install -m 640 -o frr -g frr /dev/null /etc/frr/isisd.conf sudo install -m 640 -o frr -g frr /dev/null /etc/frr/ripd.conf sudo install -m 640 -o frr -g frr /dev/null /etc/frr/ripngd.conf sudo install -m 640 -o frr -g frr /dev/null /etc/frr/pimd.conf sudo install -m 640 -o frr -g frr /dev/null /etc/frr/ldpd.conf sudo install -m 640 -o frr -g frr /dev/null /etc/frr/nhrpd.conf sudo install -m 640 -o frr -g frrvty /dev/null /etc/frr/vtysh.conf Enable IP & IPv6 forwarding ^^^^^^^^^^^^^^^^^^^^^^^^^^^ Edit ``/etc/sysctl.conf`` and uncomment the following values (ignore the other settings) :: # Uncomment the next line to enable packet forwarding for IPv4 net.ipv4.ip_forward=1 # Uncomment the next line to enable packet forwarding for IPv6 # Enabling this option disables Stateless Address Autoconfiguration # based on Router Advertisements for this host net.ipv6.conf.all.forwarding=1 **Reboot** or use ``sysctl -p`` to apply the same config to the running system Troubleshooting ^^^^^^^^^^^^^^^ **Local state directory** The local state directory must exist and have the correct permissions applied for the frrouting daemons to start. In the above ./configure example the local state directory is set to /var/run/frr (--localstatedir=/var/run/frr) Debian considers /var/run/frr to be temporary and this is removed after a reboot. When using a different local state directory you need to create the new directory and change the ownership to the frr user, for example: :: mkdir /var/opt/frr chown frr /var/opt/frr **Shared library error** If you try and start any of the frrouting daemons you may see the below error due to the frrouting shared library directory not being found: :: ./zebra: error while loading shared libraries: libfrr.so.0: cannot open shared object file: No such file or directory The fix is to add the following line to /etc/ld.so.conf which will continue to reference the library directory after the system reboots. To load the library directory path immediately run the ldconfig command after adding the line to the file eg: :: echo include /usr/local/lib >> /etc/ld.so.conf ldconfig frr-7.2.1/doc/developer/building-frr-for-debian9.rst0000644000000000000000000000744613610377563017174 00000000000000Debian 9 ======================================== Install required packages ------------------------- Add packages: :: sudo apt-get install git autoconf automake libtool make \ libreadline-dev texinfo libjson-c-dev pkg-config bison flex \ libc-ares-dev python3-dev python3-pytest python3-sphinx build-essential \ libsnmp-dev libsystemd-dev libcap-dev .. include:: building-libyang.rst Get FRR, compile it and install it (from Git) --------------------------------------------- **This assumes you want to build and install FRR from source and not using any packages** Add frr groups and user ^^^^^^^^^^^^^^^^^^^^^^^ :: sudo addgroup --system --gid 92 frr sudo addgroup --system --gid 85 frrvty sudo adduser --system --ingroup frr --home /var/opt/frr/ \ --gecos "FRR suite" --shell /bin/false frr sudo usermod -a -G frrvty frr Download Source, configure and compile it ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ (You may prefer different options on configure statement. These are just an example.) :: git clone https://github.com/frrouting/frr.git frr cd frr ./bootstrap.sh ./configure \ --enable-exampledir=/usr/share/doc/frr/examples/ \ --localstatedir=/var/opt/frr \ --sbindir=/usr/lib/frr \ --sysconfdir=/etc/frr \ --enable-multipath=64 \ --enable-user=frr \ --enable-group=frr \ --enable-vty-group=frrvty \ --enable-configfile-mask=0640 \ --enable-logfile-mask=0640 \ --enable-fpm \ --with-pkg-git-version \ --with-pkg-extra-version=-MyOwnFRRVersion make make check sudo make install Create empty FRR configuration files ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ :: sudo install -m 755 -o frr -g frr -d /var/log/frr sudo install -m 755 -o frr -g frr -d /var/opt/frr sudo install -m 775 -o frr -g frrvty -d /etc/frr sudo install -m 640 -o frr -g frr /dev/null /etc/frr/zebra.conf sudo install -m 640 -o frr -g frr /dev/null /etc/frr/bgpd.conf sudo install -m 640 -o frr -g frr /dev/null /etc/frr/ospfd.conf sudo install -m 640 -o frr -g frr /dev/null /etc/frr/ospf6d.conf sudo install -m 640 -o frr -g frr /dev/null /etc/frr/isisd.conf sudo install -m 640 -o frr -g frr /dev/null /etc/frr/ripd.conf sudo install -m 640 -o frr -g frr /dev/null /etc/frr/ripngd.conf sudo install -m 640 -o frr -g frr /dev/null /etc/frr/pimd.conf sudo install -m 640 -o frr -g frr /dev/null /etc/frr/ldpd.conf sudo install -m 640 -o frr -g frr /dev/null /etc/frr/nhrpd.conf sudo install -m 640 -o frr -g frrvty /dev/null /etc/frr/vtysh.conf Enable IP & IPv6 forwarding ^^^^^^^^^^^^^^^^^^^^^^^^^^^ Edit ``/etc/sysctl.conf`` and uncomment the following values (ignore the other settings) :: # Uncomment the next line to enable packet forwarding for IPv4 net.ipv4.ip_forward=1 # Uncomment the next line to enable packet forwarding for IPv6 # Enabling this option disables Stateless Address Autoconfiguration # based on Router Advertisements for this host net.ipv6.conf.all.forwarding=1 **Reboot** or use ``sysctl -p`` to apply the same config to the running system Troubleshooting --------------- Shared library error ^^^^^^^^^^^^^^^^^^^^ If you try and start any of the frrouting daemons you may see the below error due to the frrouting shared library directory not being found: :: ./zebra: error while loading shared libraries: libfrr.so.0: cannot open shared object file: No such file or directory The fix is to add the following line to /etc/ld.so.conf which will continue to reference the library directory after the system reboots. To load the library directory path immediately run the ldconfig command after adding the line to the file eg: :: echo include /usr/local/lib >> /etc/ld.so.conf ldconfig frr-7.2.1/doc/developer/building-frr-for-fedora.rst0000644000000000000000000000665413610377563017121 00000000000000Fedora 24+ ========== This document describes installation from source. If you want to build an RPM, see :ref:`packaging-redhat`. These instructions have been tested on Fedora 24+. Installing Dependencies ----------------------- .. code-block:: console sudo dnf install git autoconf automake libtool make \ readline-devel texinfo net-snmp-devel groff pkgconfig json-c-devel \ pam-devel python3-pytest bison flex c-ares-devel python3-devel \ python3-sphinx perl-core patch systemd-devel libcap-devel .. include:: building-libyang.rst Building & Installing FRR ------------------------- Add FRR user and groups ^^^^^^^^^^^^^^^^^^^^^^^ .. code-block:: console sudo groupadd -g 92 frr sudo groupadd -r -g 85 frrvty sudo useradd -u 92 -g 92 -M -r -G frrvty -s /sbin/nologin \ -c "FRR FRRouting suite" -d /var/run/frr frr Compile ^^^^^^^ .. include:: include-compile.rst Install FRR configuration files ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ .. code-block:: console sudo install -m 775 -o frr -g frr -d /var/log/frr sudo install -m 775 -o frr -g frrvty -d /etc/frr sudo install -m 640 -o frr -g frrvty tools/etc/frr/vtysh.conf /etc/frr/vtysh.conf sudo install -m 640 -o frr -g frr tools/etc/frr/frr.conf /etc/frr/frr.conf sudo install -m 640 -o frr -g frr tools/etc/frr/daemons.conf /etc/frr/daemons.conf sudo install -m 640 -o frr -g frr tools/etc/frr/daemons /etc/frr/daemons Tweak sysctls ^^^^^^^^^^^^^ Some sysctls need to be changed in order to enable IPv4/IPv6 forwarding and MPLS (if supported by your platform). If your platform does not support MPLS, skip the MPLS related configuration in this section. Create a new file ``/etc/sysctl.d/90-routing-sysctl.conf`` with the following content: :: # # Enable packet forwarding # net.ipv4.conf.all.forwarding=1 net.ipv6.conf.all.forwarding=1 # # Enable MPLS Label processing on all interfaces # #net.mpls.conf.eth0.input=1 #net.mpls.conf.eth1.input=1 #net.mpls.conf.eth2.input=1 #net.mpls.platform_labels=100000 .. note:: MPLS must be invidividually enabled on each interface that requires it. See the example in the config block above. Load the modifed sysctls on the system: .. code-block:: console sudo sysctl -p /etc/sysctl.d/90-routing-sysctl.conf Create a new file ``/etc/modules-load.d/mpls.conf`` with the following content: :: # Load MPLS Kernel Modules mpls-router mpls-iptunnel And load the kernel modules on the running system: .. code-block:: console sudo modprobe mpls-router mpls-iptunnel .. note:: Fedora ships with the ``firewalld`` service enabled. You may run into some issues with the iptables rules it installs by default. If you wish to just stop the service and clear `ALL` rules do these commands: .. code-block:: console sudo systemctl disable firewalld.service sudo systemctl stop firewalld.service sudo iptables -F Install service files ^^^^^^^^^^^^^^^^^^^^^ .. code-block:: console sudo install -p -m 644 redhat/frr.service /usr/lib/systemd/system/frr.service sudo install -p -m 755 redhat/frr.init /usr/lib/frr/frr sudo systemctl enable frr Enable daemons ^^^^^^^^^^^^^^ Open :file:`/etc/frr/daemons` with your text editor of choice. Look for the section with ``watchfrr_enable=...`` and ``zebra=...`` etc. Enable the daemons as required by changing the value to ``yes``. Start FRR ^^^^^^^^^ .. code-block:: frr sudo systemctl start frr frr-7.2.1/doc/developer/building-frr-for-freebsd10.rst0000644000000000000000000000671213610377563017427 00000000000000FreeBSD 10 ========================================== FreeBSD 10 restrictions: ------------------------ - MPLS is not supported on ``FreeBSD``. MPLS requires a Linux Kernel (4.5 or higher). LDP can be built, but may have limited use without MPLS Install required packages ------------------------- Add packages: (Allow the install of the package management tool if this is first package install and asked) :: pkg install git autoconf automake libtool gmake json-c pkgconf \ bison flex py36-pytest c-ares python3.6 py36-sphinx Make sure there is no /usr/bin/flex preinstalled (and use the newly installed in /usr/local/bin): (FreeBSD frequently provides a older flex as part of the base OS which takes preference in path) .. include:: building-libyang.rst :: rm -f /usr/bin/flex Get FRR, compile it and install it (from Git) --------------------------------------------- **This assumes you want to build and install FRR from source and not using any packages** Add frr group and user ^^^^^^^^^^^^^^^^^^^^^^ :: pw groupadd frr -g 101 pw groupadd frrvty -g 102 pw adduser frr -g 101 -u 101 -G 102 -c "FRR suite" \ -d /usr/local/etc/frr -s /usr/sbin/nologin (You may prefer different options on configure statement. These are just an example) :: git clone https://github.com/frrouting/frr.git frr cd frr ./bootstrap.sh export MAKE=gmake export LDFLAGS="-L/usr/local/lib" export CPPFLAGS="-I/usr/local/include" ./configure \ --sysconfdir=/usr/local/etc/frr \ --enable-pkgsrcrcdir=/usr/pkg/share/examples/rc.d \ --localstatedir=/var/run/frr \ --prefix=/usr/local \ --enable-multipath=64 \ --enable-user=frr \ --enable-group=frr \ --enable-vty-group=frrvty \ --enable-configfile-mask=0640 \ --enable-logfile-mask=0640 \ --enable-fpm \ --with-pkg-git-version \ --with-pkg-extra-version=-MyOwnFRRVersion gmake gmake check sudo gmake install Create empty FRR configuration files ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ .. code-block:: shell sudo mkdir /usr/local/etc/frr For integrated config file: .. code-block:: shell sudo touch /usr/local/etc/frr/frr.conf For individual config files: .. note:: Integrated config is preferred to individual config. .. code-block:: shell sudo touch /usr/local/etc/frr/babeld.conf sudo touch /usr/local/etc/frr/bfdd.conf sudo touch /usr/local/etc/frr/bgpd.conf sudo touch /usr/local/etc/frr/eigrpd.conf sudo touch /usr/local/etc/frr/isisd.conf sudo touch /usr/local/etc/frr/ldpd.conf sudo touch /usr/local/etc/frr/nhrpd.conf sudo touch /usr/local/etc/frr/ospf6d.conf sudo touch /usr/local/etc/frr/ospfd.conf sudo touch /usr/local/etc/frr/pbrd.conf sudo touch /usr/local/etc/frr/pimd.conf sudo touch /usr/local/etc/frr/ripd.conf sudo touch /usr/local/etc/frr/ripngd.conf sudo touch /usr/local/etc/frr/staticd.conf sudo touch /usr/local/etc/frr/zebra.conf sudo chown -R frr:frr /usr/local/etc/frr/ sudo touch /usr/local/etc/frr/vtysh.conf sudo chown frr:frrvty /usr/local/etc/frr/vtysh.conf sudo chmod 640 /usr/local/etc/frr/*.conf Enable IP & IPv6 forwarding ^^^^^^^^^^^^^^^^^^^^^^^^^^^ Add the following lines to the end of ``/etc/sysctl.conf``: :: # Routing: We need to forward packets net.inet.ip.forwarding=1 net.inet6.ip6.forwarding=1 **Reboot** or use ``sysctl`` to apply the same config to the running system. frr-7.2.1/doc/developer/building-frr-for-freebsd11.rst0000644000000000000000000000716513610377563017433 00000000000000FreeBSD 11 ========== FreeBSD 11 restrictions: ------------------------ - MPLS is not supported on ``FreeBSD``. MPLS requires a Linux Kernel (4.5 or higher). LDP can be built, but may have limited use without MPLS Install required packages ------------------------- Add packages: (Allow the install of the package management tool if this is first package install and asked) .. code-block:: shell pkg install git autoconf automake libtool gmake json-c pkgconf \ bison flex py36-pytest c-ares python3.6 py36-sphinx texinfo Make sure there is no /usr/bin/flex preinstalled (and use the newly installed in /usr/local/bin): (FreeBSD frequently provides a older flex as part of the base OS which takes preference in path) .. include:: building-libyang.rst .. code-block:: shell rm -f /usr/bin/flex Get FRR, compile it and install it (from Git) --------------------------------------------- **This assumes you want to build and install FRR from source and not using any packages** Add frr group and user ^^^^^^^^^^^^^^^^^^^^^^ .. code-block:: shell pw groupadd frr -g 101 pw groupadd frrvty -g 102 pw adduser frr -g 101 -u 101 -G 102 -c "FRR suite" \ -d /usr/local/etc/frr -s /usr/sbin/nologin Download Source, configure and compile it ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ (You may prefer different options on configure statement. These are just an example) .. code-block:: shell git clone https://github.com/frrouting/frr.git frr cd frr ./bootstrap.sh setenv MAKE gmake setenv LDFLAGS -L/usr/local/lib setenv CPPFLAGS -I/usr/local/include ln -s /usr/local/bin/sphinx-build-3.6 /usr/local/bin/sphinx-build ./configure \ --sysconfdir=/usr/local/etc/frr \ --enable-pkgsrcrcdir=/usr/pkg/share/examples/rc.d \ --localstatedir=/var/run/frr \ --prefix=/usr/local \ --enable-multipath=64 \ --enable-user=frr \ --enable-group=frr \ --enable-vty-group=frrvty \ --enable-configfile-mask=0640 \ --enable-logfile-mask=0640 \ --enable-fpm \ --with-pkg-git-version \ --with-pkg-extra-version=-MyOwnFRRVersion gmake gmake check sudo gmake install Create empty FRR configuration files ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ .. code-block:: shell sudo mkdir /usr/local/etc/frr For integrated config file: .. code-block:: shell sudo touch /usr/local/etc/frr/frr.conf For individual config files: .. note:: Integrated config is preferred to individual config. .. code-block:: shell sudo touch /usr/local/etc/frr/babeld.conf sudo touch /usr/local/etc/frr/bfdd.conf sudo touch /usr/local/etc/frr/bgpd.conf sudo touch /usr/local/etc/frr/eigrpd.conf sudo touch /usr/local/etc/frr/isisd.conf sudo touch /usr/local/etc/frr/ldpd.conf sudo touch /usr/local/etc/frr/nhrpd.conf sudo touch /usr/local/etc/frr/ospf6d.conf sudo touch /usr/local/etc/frr/ospfd.conf sudo touch /usr/local/etc/frr/pbrd.conf sudo touch /usr/local/etc/frr/pimd.conf sudo touch /usr/local/etc/frr/ripd.conf sudo touch /usr/local/etc/frr/ripngd.conf sudo touch /usr/local/etc/frr/staticd.conf sudo touch /usr/local/etc/frr/zebra.conf sudo chown -R frr:frr /usr/local/etc/frr/ sudo touch /usr/local/etc/frr/vtysh.conf sudo chown frr:frrvty /usr/local/etc/frr/vtysh.conf sudo chmod 640 /usr/local/etc/frr/*.conf Enable IP & IPv6 forwarding ^^^^^^^^^^^^^^^^^^^^^^^^^^^ Add the following lines to the end of ``/etc/sysctl.conf``: :: # Routing: We need to forward packets net.inet.ip.forwarding=1 net.inet6.ip6.forwarding=1 **Reboot** or use ``sysctl`` to apply the same config to the running system. frr-7.2.1/doc/developer/building-frr-for-freebsd9.rst0000644000000000000000000000752113610377563017356 00000000000000FreeBSD 9 ========================================= FreeBSD 9 restrictions: ----------------------- - MPLS is not supported on ``FreeBSD``. MPLS requires a Linux Kernel (4.5 or higher). LDP can be built, but may have limited use without MPLS Install required packages ------------------------- Add packages: (Allow the install of the package management tool if this is first package install and asked) :: pkg install -y git autoconf automake libtool gmake \ pkgconf texinfo json-c bison flex py36-pytest c-ares \ python3 py36-sphinx libexecinfo Make sure there is no /usr/bin/flex preinstalled (and use the newly installed in /usr/local/bin): (FreeBSD frequently provides a older flex as part of the base OS which takes preference in path) :: rm -f /usr/bin/flex For building with clang (instead of gcc), upgrade clang from 3.4 default to 3.6 *This is needed to build FreeBSD packages as well - for packages clang is default* (Clang 3.4 as shipped with FreeBSD 9 crashes during compile) :: pkg install clang36 pkg delete clang34 mv /usr/bin/clang /usr/bin/clang34 ln -s /usr/local/bin/clang36 /usr/bin/clang .. include:: building-libyang.rst Get FRR, compile it and install it (from Git) --------------------------------------------- **This assumes you want to build and install FRR from source and not using any packages** Add frr group and user ^^^^^^^^^^^^^^^^^^^^^^ :: pw groupadd frr -g 101 pw groupadd frrvty -g 102 pw adduser frr -g 101 -u 101 -G 102 -c "FRR suite" \ -d /usr/local/etc/frr -s /usr/sbin/nologin (You may prefer different options on configure statement. These are just an example) :: git clone https://github.com/frrouting/frr.git frr cd frr ./bootstrap.sh export MAKE=gmake export LDFLAGS="-L/usr/local/lib" export CPPFLAGS="-I/usr/local/include" ./configure \ --sysconfdir=/usr/local/etc/frr \ --enable-pkgsrcrcdir=/usr/pkg/share/examples/rc.d \ --localstatedir=/var/run/frr \ --prefix=/usr/local \ --enable-multipath=64 \ --enable-user=frr \ --enable-group=frr \ --enable-vty-group=frrvty \ --enable-configfile-mask=0640 \ --enable-logfile-mask=0640 \ --enable-fpm \ --with-pkg-git-version \ --with-pkg-extra-version=-MyOwnFRRVersion gmake gmake check sudo gmake install Create empty FRR configuration files ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ .. code-block:: shell sudo mkdir /usr/local/etc/frr For integrated config file: .. code-block:: shell sudo touch /usr/local/etc/frr/frr.conf For individual config files: .. note:: Integrated config is preferred to individual config. .. code-block:: shell sudo touch /usr/local/etc/frr/babeld.conf sudo touch /usr/local/etc/frr/bfdd.conf sudo touch /usr/local/etc/frr/bgpd.conf sudo touch /usr/local/etc/frr/eigrpd.conf sudo touch /usr/local/etc/frr/isisd.conf sudo touch /usr/local/etc/frr/ldpd.conf sudo touch /usr/local/etc/frr/nhrpd.conf sudo touch /usr/local/etc/frr/ospf6d.conf sudo touch /usr/local/etc/frr/ospfd.conf sudo touch /usr/local/etc/frr/pbrd.conf sudo touch /usr/local/etc/frr/pimd.conf sudo touch /usr/local/etc/frr/ripd.conf sudo touch /usr/local/etc/frr/ripngd.conf sudo touch /usr/local/etc/frr/staticd.conf sudo touch /usr/local/etc/frr/zebra.conf sudo chown -R frr:frr /usr/local/etc/frr/ sudo touch /usr/local/etc/frr/vtysh.conf sudo chown frr:frrvty /usr/local/etc/frr/vtysh.conf sudo chmod 640 /usr/local/etc/frr/*.conf Enable IP & IPv6 forwarding ^^^^^^^^^^^^^^^^^^^^^^^^^^^ Add the following lines to the end of ``/etc/sysctl.conf``: :: # Routing: We need to forward packets net.inet.ip.forwarding=1 net.inet6.ip6.forwarding=1 **Reboot** or use ``sysctl`` to apply the same config to the running system. frr-7.2.1/doc/developer/building-frr-for-netbsd6.rst0000644000000000000000000000664513610377563017226 00000000000000NetBSD 6 ======================================== NetBSD 6 restrictions: ---------------------- - MPLS is not supported on ``NetBSD``. MPLS requires a Linux Kernel (4.5 or higher). LDP can be built, but may have limited use without MPLS Install required packages ------------------------- Configure Package location: :: PKG_PATH="ftp://ftp.NetBSD.org/pub/pkgsrc/packages/NetBSD/`uname -m`/`uname -r`/All" export PKG_PATH Add packages: :: sudo pkg_add git autoconf automake libtool gmake openssl \ pkg-config json-c py36-test python36 py36-sphinx Install SSL Root Certificates (for git https access): :: sudo pkg_add mozilla-rootcerts sudo touch /etc/openssl/openssl.cnf sudo mozilla-rootcerts install .. include:: building-libyang.rst Get FRR, compile it and install it (from Git) --------------------------------------------- Add frr groups and user ^^^^^^^^^^^^^^^^^^^^^^^ :: sudo groupadd -g 92 frr sudo groupadd -g 93 frrvty sudo useradd -g 92 -u 92 -G frrvty -c "FRR suite" \ -d /nonexistent -s /sbin/nologin frr Download Source, configure and compile it ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ (You may prefer different options on configure statement. These are just an example) :: git clone https://github.com/frrouting/frr.git frr cd frr ./bootstrap.sh MAKE=gmake export LDFLAGS="-L/usr/pkg/lib -R/usr/pkg/lib" export CPPFLAGS="-I/usr/pkg/include" ./configure \ --sysconfdir=/usr/pkg/etc/frr \ --enable-exampledir=/usr/pkg/share/examples/frr \ --enable-pkgsrcrcdir=/usr/pkg/share/examples/rc.d \ --localstatedir=/var/run/frr \ --enable-multipath=64 \ --enable-user=frr \ --enable-group=frr \ --enable-vty-group=frrvty \ --enable-configfile-mask=0640 \ --enable-logfile-mask=0640 \ --enable-fpm \ --with-pkg-git-version \ --with-pkg-extra-version=-MyOwnFRRVersion gmake gmake check sudo gmake install Create empty FRR configuration files ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ :: sudo mkdir /var/log/frr sudo mkdir /usr/pkg/etc/frr sudo touch /usr/pkg/etc/frr/zebra.conf sudo touch /usr/pkg/etc/frr/bgpd.conf sudo touch /usr/pkg/etc/frr/ospfd.conf sudo touch /usr/pkg/etc/frr/ospf6d.conf sudo touch /usr/pkg/etc/frr/isisd.conf sudo touch /usr/pkg/etc/frr/ripd.conf sudo touch /usr/pkg/etc/frr/ripngd.conf sudo touch /usr/pkg/etc/frr/pimd.conf sudo chown -R frr:frr /usr/pkg/etc/frr sudo touch /usr/local/etc/frr/vtysh.conf sudo chown frr:frrvty /usr/pkg/etc/frr/*.conf sudo chmod 640 /usr/pkg/etc/frr/*.conf Enable IP & IPv6 forwarding ^^^^^^^^^^^^^^^^^^^^^^^^^^^ Add the following lines to the end of ``/etc/sysctl.conf``: :: # Routing: We need to forward packets net.inet.ip.forwarding=1 net.inet6.ip6.forwarding=1 **Reboot** or use ``sysctl`` to apply the same config to the running system Install rc.d init files ^^^^^^^^^^^^^^^^^^^^^^^ :: cp pkgsrc/*.sh /etc/rc.d/ chmod 555 /etc/rc.d/*.sh Enable FRR processes ^^^^^^^^^^^^^^^^^^^^ (Enable the required processes only) :: echo "zebra=YES" >> /etc/rc.conf echo "bgpd=YES" >> /etc/rc.conf echo "ospfd=YES" >> /etc/rc.conf echo "ospf6d=YES" >> /etc/rc.conf echo "isisd=YES" >> /etc/rc.conf echo "ripngd=YES" >> /etc/rc.conf echo "ripd=YES" >> /etc/rc.conf echo "pimd=YES" >> /etc/rc.conf frr-7.2.1/doc/developer/building-frr-for-netbsd7.rst0000644000000000000000000000636713610377563017230 00000000000000NetBSD 7 ======================================== NetBSD 7 restrictions: ---------------------- - MPLS is not supported on ``NetBSD``. MPLS requires a Linux Kernel (4.5 or higher). LDP can be built, but may have limited use without MPLS Install required packages ------------------------- :: sudo pkgin install git autoconf automake libtool gmake openssl \ pkg-config json-c python36 py36-test py36-sphinx Install SSL Root Certificates (for git https access): :: sudo pkgin install mozilla-rootcerts sudo touch /etc/openssl/openssl.cnf sudo mozilla-rootcerts install .. include:: building-libyang.rst Get FRR, compile it and install it (from Git) --------------------------------------------- Add frr groups and user ^^^^^^^^^^^^^^^^^^^^^^^ :: sudo groupadd -g 92 frr sudo groupadd -g 93 frrvty sudo useradd -g 92 -u 92 -G frrvty -c "FRR suite" \ -d /nonexistent -s /sbin/nologin frr Download Source, configure and compile it ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ (You may prefer different options on configure statement. These are just an example) :: git clone https://github.com/frrouting/frr.git frr cd frr ./bootstrap.sh MAKE=gmake export LDFLAGS="-L/usr/pkg/lib -R/usr/pkg/lib" export CPPFLAGS="-I/usr/pkg/include" ./configure \ --sysconfdir=/usr/pkg/etc/frr \ --enable-exampledir=/usr/pkg/share/examples/frr \ --enable-pkgsrcrcdir=/usr/pkg/share/examples/rc.d \ --localstatedir=/var/run/frr \ --enable-multipath=64 \ --enable-user=frr \ --enable-group=frr \ --enable-vty-group=frrvty \ --enable-configfile-mask=0640 \ --enable-logfile-mask=0640 \ --enable-fpm \ --with-pkg-git-version \ --with-pkg-extra-version=-MyOwnFRRVersion gmake gmake check sudo gmake install Create empty FRR configuration files ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ :: sudo mkdir /usr/pkg/etc/frr sudo touch /usr/pkg/etc/frr/zebra.conf sudo touch /usr/pkg/etc/frr/bgpd.conf sudo touch /usr/pkg/etc/frr/ospfd.conf sudo touch /usr/pkg/etc/frr/ospf6d.conf sudo touch /usr/pkg/etc/frr/isisd.conf sudo touch /usr/pkg/etc/frr/ripd.conf sudo touch /usr/pkg/etc/frr/ripngd.conf sudo touch /usr/pkg/etc/frr/pimd.conf sudo chown -R frr:frr /usr/pkg/etc/frr sudo touch /usr/local/etc/frr/vtysh.conf sudo chown frr:frrvty /usr/pkg/etc/frr/*.conf sudo chmod 640 /usr/pkg/etc/frr/*.conf Enable IP & IPv6 forwarding ^^^^^^^^^^^^^^^^^^^^^^^^^^^ Add the following lines to the end of ``/etc/sysctl.conf``: :: # Routing: We need to forward packets net.inet.ip.forwarding=1 net.inet6.ip6.forwarding=1 **Reboot** or use ``sysctl`` to apply the same config to the running system Install rc.d init files ^^^^^^^^^^^^^^^^^^^^^^^ :: cp pkgsrc/*.sh /etc/rc.d/ chmod 555 /etc/rc.d/*.sh Enable FRR processes ^^^^^^^^^^^^^^^^^^^^ (Enable the required processes only) :: echo "zebra=YES" >> /etc/rc.conf echo "bgpd=YES" >> /etc/rc.conf echo "ospfd=YES" >> /etc/rc.conf echo "ospf6d=YES" >> /etc/rc.conf echo "isisd=YES" >> /etc/rc.conf echo "ripngd=YES" >> /etc/rc.conf echo "ripd=YES" >> /etc/rc.conf echo "pimd=YES" >> /etc/rc.conf frr-7.2.1/doc/developer/building-frr-for-omnios.rst0000644000000000000000000000623513610377563017160 00000000000000OmniOS (OpenSolaris) ==================================================== OmniOS restrictions: -------------------- - MPLS is not supported on ``OmniOS`` or ``Solaris``. MPLS requires a Linux Kernel (4.5 or higher). LDP can be built, but may have limited use without MPLS Enable IP & IPv6 forwarding ^^^^^^^^^^^^^^^^^^^^^^^^^^^ :: routeadm -e ipv4-forwarding routeadm -e ipv6-forwarding Install required packages ------------------------- Add packages: :: pkg install \ developer/build/autoconf \ developer/build/automake \ developer/lexer/flex \ developer/parser/bison \ developer/object-file \ developer/linker \ developer/library/lint \ developer/build/gnu-make \ developer/gcc51 \ library/idnkit \ library/idnkit/header-idnkit \ system/header \ system/library/math/header-math \ git libtool pkg-config Add additional Solaris packages: :: pkgadd -d http://get.opencsw.org/now /opt/csw/bin/pkgutil -U /opt/csw/bin/pkgutil -y -i texinfo /opt/csw/bin/pkgutil -y -i perl /opt/csw/bin/pkgutil -y -i libjson_c_dev /opt/csw/bin/pkgutil -y -i python27 py_pip python27_dev Add libjson to Solaris equivalent of ld.so.conf :: crle -l /opt/csw/lib -u Add pytest: :: pip install "pytest<5" Install Sphinx::: pip install sphinx Select Python 2.7 as default (required for pytest) :: rm -f /usr/bin/python ln -s /opt/csw/bin/python2.7 /usr/bin/python Fix PATH for all users and non-interactive sessions. Edit ``/etc/default/login`` and add the following default PATH: :: PATH=/usr/gnu/bin:/usr/bin:/usr/sbin:/sbin:/opt/csw/bin Edit ``~/.profile`` and add the following default PATH: :: PATH=/usr/gnu/bin:/usr/bin:/usr/sbin:/sbin:/opt/csw/bin .. include:: building-libyang.rst Get FRR, compile it and install it (from Git) --------------------------------------------- **This assumes you want to build and install FRR from source and not using any packages** Add frr group and user ^^^^^^^^^^^^^^^^^^^^^^ :: sudo groupadd -g 93 frr sudo groupadd -g 94 frrvty sudo useradd -g 93 -u 93 -G frrvty -c "FRR suite" \ -d /nonexistent -s /bin/false frr (You may prefer different options on configure statement. These are just an example) :: git clone https://github.com/frrouting/frr.git frr cd frr ./bootstrap.sh export MAKE=gmake export LDFLAGS="-L/opt/csw/lib" export CPPFLAGS="-I/opt/csw/include" export PKG_CONFIG_PATH=/opt/csw/lib/pkgconfig ./configure \ --sysconfdir=/etc/frr \ --enable-exampledir=/usr/share/doc/frr/examples/ \ --localstatedir=/var/run/frr \ --sbindir=/usr/lib/frr \ --enable-multipath=64 \ --enable-user=frr \ --enable-group=frr \ --enable-vty-group=frrvty \ --enable-configfile-mask=0640 \ --enable-logfile-mask=0640 \ --enable-fpm \ --with-pkg-git-version \ --with-pkg-extra-version=-MyOwnFRRVersion gmake gmake check sudo gmake install Enable IP & IPv6 forwarding ^^^^^^^^^^^^^^^^^^^^^^^^^^^ :: routeadm -e ipv4-forwarding routeadm -e ipv6-forwarding frr-7.2.1/doc/developer/building-frr-for-openbsd6.rst0000644000000000000000000000774013610377563017376 00000000000000OpenBSD 6 ========================================= Install required packages ------------------------- Configure PKG\_PATH :: export PKG_PATH=http://ftp5.usa.openbsd.org/pub/OpenBSD/$(uname -r)/packages/$(machine -a)/ Add packages: :: pkg_add git autoconf-2.69p2 automake-1.15.1 libtool bison pkg_add gmake json-c py-test py-sphinx libexecinfo Select Python2.7 as default (required for pytest) :: ln -s /usr/local/bin/python2.7 /usr/local/bin/python .. include:: building-libyang.rst Get FRR, compile it and install it (from Git) --------------------------------------------- **This assumes you want to build and install FRR from source and not using any packages** Add frr group and user ^^^^^^^^^^^^^^^^^^^^^^ :: groupadd -g 525 _frr groupadd -g 526 _frrvty useradd -g 525 -u 525 -c "FRR suite" -G _frrvty \ -d /nonexistent -s /sbin/nologin _frr Download Source, configure and compile it ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ (You may prefer different options on configure statement. These are just an example) :: git clone https://github.com/frrouting/frr.git frr cd frr export AUTOCONF_VERSION="2.69" export AUTOMAKE_VERSION="1.15" ./bootstrap.sh export LDFLAGS="-L/usr/local/lib" export CPPFLAGS="-I/usr/local/include" ./configure \ --sysconfdir=/etc/frr \ --localstatedir=/var/frr \ --enable-multipath=64 \ --enable-user=_frr \ --enable-group=_frr \ --enable-vty-group=_frrvty \ --enable-configfile-mask=0640 \ --enable-logfile-mask=0640 \ --enable-fpm \ --with-pkg-git-version \ --with-pkg-extra-version=-MyOwnFRRVersion gmake gmake check doas gmake install Create empty FRR configuration files ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ :: doas mkdir /var/frr doas chown _frr:_frr /var/frr doas chmod 755 /var/frr doas mkdir /etc/frr doas touch /etc/frr/zebra.conf doas touch /etc/frr/bgpd.conf doas touch /etc/frr/ospfd.conf doas touch /etc/frr/ospf6d.conf doas touch /etc/frr/isisd.conf doas touch /etc/frr/ripd.conf doas touch /etc/frr/ripngd.conf doas touch /etc/frr/pimd.conf doas touch /etc/frr/ldpd.conf doas touch /etc/frr/nhrpd.conf doas chown -R _frr:_frr /etc/frr doas touch /etc/frr/vtysh.conf doas chown -R _frr:_frrvty /etc/frr/vtysh.conf doas chmod 750 /etc/frr doas chmod 640 /etc/frr/*.conf Enable IP & IPv6 forwarding ^^^^^^^^^^^^^^^^^^^^^^^^^^^ Add the following lines to the end of ``/etc/rc.conf``: :: net.inet6.ip6.forwarding=1 # 1=Permit forwarding of IPv6 packets net.inet6.ip6.mforwarding=1 # 1=Permit forwarding of IPv6 multicast packets net.inet6.ip6.multipath=1 # 1=Enable IPv6 multipath routing **Reboot** to apply the config to the system Enable MPLS Forwarding ^^^^^^^^^^^^^^^^^^^^^^ To enable MPLS forwarding on a given interface, use the following command: :: doas ifconfig em0 mpls Alternatively, to make MPLS forwarding persistent across reboots, add the "mpls" keyword in the hostname.\* files of the desired interfaces. Example: :: cat /etc/hostname.em0 inet 10.0.1.1 255.255.255.0 mpls Install rc.d init files ^^^^^^^^^^^^^^^^^^^^^^^ (create them in /etc/rc.d - no example are included at this time with FRR source) Example (for zebra - store as ``/etc/rc.d/frr_zebra.sh``) :: #!/bin/sh # # $OpenBSD: frr_zebra.rc,v 1.1 2013/04/18 20:29:08 sthen Exp $ daemon="/usr/local/sbin/zebra -d" . /etc/rc.d/rc.subr rc_cmd $1 Enable FRR processes ^^^^^^^^^^^^^^^^^^^^ (Enable the required processes only) :: echo "frr_zebra=YES" >> /etc/rc.conf echo "frr_bgpd=YES" >> /etc/rc.conf echo "frr_ospfd=YES" >> /etc/rc.conf echo "frr_ospf6d=YES" >> /etc/rc.conf echo "frr_isisd=YES" >> /etc/rc.conf echo "frr_ripngd=YES" >> /etc/rc.conf echo "frr_ripd=YES" >> /etc/rc.conf echo "frr_pimd=YES" >> /etc/rc.conf echo "frr_ldpd=YES" >> /etc/rc.conf frr-7.2.1/doc/developer/building-frr-for-openwrt.rst0000644000000000000000000000360213610377563017345 00000000000000OpenWRT ======= Prepare build environment ------------------------- For Debian based distributions, run: :: sudo apt-get install git build-essential libssl-dev libncurses5-dev \ unzip zlib1g-dev subversion mercurial For other environments, instructions can be found in the `official documentation `_. Get OpenWRT Sources (from Git) ------------------------------ .. note:: The OpenWRT build will fail if you run it as root. So take care to run it as a nonprivileged user. Clone the OpenWRT sources and retrieve the package feeds :: git clone https://github.com/openwrt/openwrt.git cd openwrt ./scripts/feeds update -a ./scripts/feeds install -a cd feeds/routing git fetch origin pull/319/head git read-tree --prefix=frr/ -u FETCH_HEAD:frr cd ../../package/feeds/routing/ ln -sv ../../../feeds/routing/frr . cd ../../.. Configure OpenWRT for your target and select the needed FRR packages in Network -> Routing and Redirection -> frr, exit and save :: make menuconfig Then, to compile either a complete OpenWRT image, or the FRR packages, run: :: make or make package/frr/compile It may be possible that on first build ``make package/frr/compile`` not to work and it may be needed to run a ``make`` for the entire build environment. Add ``V=s`` to get more debugging output. Work with sources ----------------- To update to a newer version, or change other options, you need to edit the ``feeds/routing/frr/Makefile``. Usage ----- Edit ``/usr/sbin/frr.init`` and add/remove the daemons name in section ``DAEMONS=`` or don't install unneeded packages For example: zebra bgpd ldpd isisd nhrpd ospfd ospf6d pimd ripd ripngd Enable the service ^^^^^^^^^^^^^^^^^^ - ``service frr enable`` Start the service ^^^^^^^^^^^^^^^^^ - ``service frr start`` frr-7.2.1/doc/developer/building-frr-for-ubuntu1404.rst0000644000000000000000000000725113610377563017506 00000000000000Ubuntu 14.04 LTS ================ This document describes installation from source. If you want to build a ``deb``, see :ref:`packaging-debian`. Installing Dependencies ----------------------- .. code-block:: console apt-get update apt-get install \ git autoconf automake libtool make libreadline-dev texinfo \ pkg-config libpam0g-dev libjson-c-dev bison flex python3-pytest \ libc-ares-dev python3-dev python3-sphinx install-info build-essential \ libsnmp-dev perl libcap-dev .. include:: building-libyang.rst Building & Installing FRR ------------------------- Add FRR user and groups ^^^^^^^^^^^^^^^^^^^^^^^ .. code-block:: console sudo groupadd -r -g 92 frr sudo groupadd -r -g 85 frrvty sudo adduser --system --ingroup frr --home /var/run/frr/ \ --gecos "FRR suite" --shell /sbin/nologin frr sudo usermod -a -G frrvty frr Compile ^^^^^^^ .. include:: include-compile.rst Install FRR configuration files ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ .. code-block:: console sudo install -m 775 -o frr -g frr -d /var/log/frr sudo install -m 775 -o frr -g frrvty -d /etc/frr sudo install -m 640 -o frr -g frrvty tools/etc/frr/vtysh.conf /etc/frr/vtysh.conf sudo install -m 640 -o frr -g frr tools/etc/frr/frr.conf /etc/frr/frr.conf sudo install -m 640 -o frr -g frr tools/etc/frr/daemons.conf /etc/frr/daemons.conf sudo install -m 640 -o frr -g frr tools/etc/frr/daemons /etc/frr/daemons Tweak sysctls ^^^^^^^^^^^^^ Some sysctls need to be changed in order to enable IPv4/IPv6 forwarding and MPLS (if supported by your platform). If your platform does not support MPLS, skip the MPLS related configuration in this section. Edit :file:`/etc/sysctl.conf` and uncomment the following values (ignore the other settings): :: # Uncomment the next line to enable packet forwarding for IPv4 net.ipv4.ip_forward=1 # Uncomment the next line to enable packet forwarding for IPv6 # Enabling this option disables Stateless Address Autoconfiguration # based on Router Advertisements for this host net.ipv6.conf.all.forwarding=1 Reboot or use ``sysctl -p`` to apply the same config to the running system. Add MPLS kernel modules """"""""""""""""""""""" .. warning:: MPLS is not supported on Ubuntu 14.04 with the default kernel. MPLS requires kernel 4.5 or higher. LDPD can be built, but may have limited use without MPLS. For an updated Ubuntu Kernel, see http://kernel.ubuntu.com/~kernel-ppa/mainline/ Ubuntu 18.04 ships with kernel 4.15. MPLS modules are present by default. To enable, add the following lines to :file:`/etc/modules-load.d/modules.conf`: :: # Load MPLS Kernel Modules mpls_router mpls_iptunnel And load the kernel modules on the running system: .. code-block:: console sudo modprobe mpls-router mpls-iptunnel Enable MPLS Forwarding """""""""""""""""""""" Edit :file:`/etc/sysctl.conf` and the following lines. Make sure to add a line equal to :file:`net.mpls.conf.eth0.input` for each interface used with MPLS. :: # Enable MPLS Label processing on all interfaces net.mpls.conf.eth0.input=1 net.mpls.conf.eth1.input=1 net.mpls.conf.eth2.input=1 net.mpls.platform_labels=100000 Install the init.d service ^^^^^^^^^^^^^^^^^^^^^^^^^^ .. code-block:: console sudo install -m 755 tools/frr /etc/init.d/frr Enable daemons ^^^^^^^^^^^^^^ Open :file:`/etc/frr/daemons` with your text editor of choice. Look for the section with ``watchfrr_enable=...`` and ``zebra=...`` etc. Enable the daemons as required by changing the value to ``yes``. Start the init.d service ^^^^^^^^^^^^^^^^^^^^^^^^ .. code-block:: console /etc/init.d/frr start Use ``/etc/init.d/frr status`` to check its status. frr-7.2.1/doc/developer/building-frr-for-ubuntu1604.rst0000644000000000000000000000725213610377563017511 00000000000000Ubuntu 16.04 LTS ================ This document describes installation from source. If you want to build a ``deb``, see :ref:`packaging-debian`. Installing Dependencies ----------------------- .. code-block:: console apt-get update apt-get install \ git autoconf automake libtool make libreadline-dev texinfo \ pkg-config libpam0g-dev libjson-c-dev bison flex python3-pytest \ libc-ares-dev python3-dev libsystemd-dev python-ipaddress python3-sphinx \ install-info build-essential libsystemd-dev libsnmp-dev perl libcap-dev .. include:: building-libyang.rst Building & Installing FRR ------------------------- Add FRR user and groups ^^^^^^^^^^^^^^^^^^^^^^^ .. code-block:: console sudo groupadd -r -g 92 frr sudo groupadd -r -g 85 frrvty sudo adduser --system --ingroup frr --home /var/run/frr/ \ --gecos "FRR suite" --shell /sbin/nologin frr sudo usermod -a -G frrvty frr Compile ^^^^^^^ .. include:: include-compile.rst Install FRR configuration files ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ .. code-block:: console sudo install -m 775 -o frr -g frr -d /var/log/frr sudo install -m 775 -o frr -g frrvty -d /etc/frr sudo install -m 640 -o frr -g frrvty tools/etc/frr/vtysh.conf /etc/frr/vtysh.conf sudo install -m 640 -o frr -g frr tools/etc/frr/frr.conf /etc/frr/frr.conf sudo install -m 640 -o frr -g frr tools/etc/frr/daemons.conf /etc/frr/daemons.conf sudo install -m 640 -o frr -g frr tools/etc/frr/daemons /etc/frr/daemons Tweak sysctls ^^^^^^^^^^^^^ Some sysctls need to be changed in order to enable IPv4/IPv6 forwarding and MPLS (if supported by your platform). If your platform does not support MPLS, skip the MPLS related configuration in this section. Edit :file:`/etc/sysctl.conf` and uncomment the following values (ignore the other settings): :: # Uncomment the next line to enable packet forwarding for IPv4 net.ipv4.ip_forward=1 # Uncomment the next line to enable packet forwarding for IPv6 # Enabling this option disables Stateless Address Autoconfiguration # based on Router Advertisements for this host net.ipv6.conf.all.forwarding=1 Reboot or use ``sysctl -p`` to apply the same config to the running system. Add MPLS kernel modules """"""""""""""""""""""" .. warning:: MPLS is not supported on Ubuntu 16.04 with the default kernel. MPLS requires kernel 4.5 or higher. LDPD can be built, but may have limited use without MPLS. For an updated Ubuntu Kernel, see http://kernel.ubuntu.com/~kernel-ppa/mainline/ Ubuntu 18.04 ships with kernel 4.15. MPLS modules are present by default. To enable, add the following lines to :file:`/etc/modules-load.d/modules.conf`: :: # Load MPLS Kernel Modules mpls_router mpls_iptunnel And load the kernel modules on the running system: .. code-block:: console sudo modprobe mpls-router mpls-iptunnel Enable MPLS Forwarding """""""""""""""""""""" Edit :file:`/etc/sysctl.conf` and the following lines. Make sure to add a line equal to :file:`net.mpls.conf.eth0.input` for each interface used with MPLS. :: # Enable MPLS Label processing on all interfaces net.mpls.conf.eth0.input=1 net.mpls.conf.eth1.input=1 net.mpls.conf.eth2.input=1 net.mpls.platform_labels=100000 Install service files ^^^^^^^^^^^^^^^^^^^^^ .. code-block:: console sudo install -m 644 tools/frr.service /etc/systemd/system/frr.service sudo systemctl enable frr Enable daemons ^^^^^^^^^^^^^^ Open :file:`/etc/frr/daemons` with your text editor of choice. Look for the section with ``watchfrr_enable=...`` and ``zebra=...`` etc. Enable the daemons as required by changing the value to ``yes``. Start FRR ^^^^^^^^^ .. code-block:: console systemctl start frr frr-7.2.1/doc/developer/building-frr-for-ubuntu1804.rst0000644000000000000000000000747413610377563017521 00000000000000Ubuntu 18.04 LTS ================ This document describes installation from source. If you want to build a ``deb``, see :ref:`packaging-debian`. Installing Dependencies ----------------------- .. code-block:: console sudo apt update sudo apt-get install \ git autoconf automake libtool make libreadline-dev texinfo \ pkg-config libpam0g-dev libjson-c-dev bison flex python3-pytest \ libc-ares-dev python3-dev libsystemd-dev python-ipaddress python3-sphinx \ install-info build-essential libsystemd-dev libsnmp-dev perl libcap-dev .. include:: building-libyang.rst Protobuf ^^^^^^^^ .. code-block:: console sudo apt-get install protobuf-c-compiler libprotobuf-c-dev ZeroMQ ^^^^^^ .. code-block:: console sudo apt-get install libzmq5 libzmq3-dev Building & Installing FRR ------------------------- Add FRR user and groups ^^^^^^^^^^^^^^^^^^^^^^^ .. code-block:: console sudo groupadd -r -g 92 frr sudo groupadd -r -g 85 frrvty sudo adduser --system --ingroup frr --home /var/run/frr/ \ --gecos "FRR suite" --shell /sbin/nologin frr sudo usermod -a -G frrvty frr Compile ^^^^^^^ .. include:: include-compile.rst Install FRR configuration files ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ .. code-block:: console sudo install -m 775 -o frr -g frr -d /var/log/frr sudo install -m 775 -o frr -g frrvty -d /etc/frr sudo install -m 640 -o frr -g frrvty tools/etc/frr/vtysh.conf /etc/frr/vtysh.conf sudo install -m 640 -o frr -g frr tools/etc/frr/frr.conf /etc/frr/frr.conf sudo install -m 640 -o frr -g frr tools/etc/frr/daemons.conf /etc/frr/daemons.conf sudo install -m 640 -o frr -g frr tools/etc/frr/daemons /etc/frr/daemons Tweak sysctls ^^^^^^^^^^^^^ Some sysctls need to be changed in order to enable IPv4/IPv6 forwarding and MPLS (if supported by your platform). If your platform does not support MPLS, skip the MPLS related configuration in this section. Edit :file:`/etc/sysctl.conf` and uncomment the following values (ignore the other settings): :: # Uncomment the next line to enable packet forwarding for IPv4 net.ipv4.ip_forward=1 # Uncomment the next line to enable packet forwarding for IPv6 # Enabling this option disables Stateless Address Autoconfiguration # based on Router Advertisements for this host net.ipv6.conf.all.forwarding=1 Reboot or use ``sysctl -p`` to apply the same config to the running system. Add MPLS kernel modules """"""""""""""""""""""" Ubuntu 18.04 ships with kernel 4.15. MPLS modules are present by default. To enable, add the following lines to :file:`/etc/modules-load.d/modules.conf`: :: # Load MPLS Kernel Modules mpls_router mpls_iptunnel And load the kernel modules on the running system: .. code-block:: console sudo modprobe mpls-router mpls-iptunnel If the above command returns an error, you may need to install the appropriate or latest linux-modules-extra--generic package. For example ``apt-get install linux-modules-extra-`uname -r`-generic`` Enable MPLS Forwarding """""""""""""""""""""" Edit :file:`/etc/sysctl.conf` and the following lines. Make sure to add a line equal to :file:`net.mpls.conf.eth0.input` for each interface used with MPLS. :: # Enable MPLS Label processing on all interfaces net.mpls.conf.eth0.input=1 net.mpls.conf.eth1.input=1 net.mpls.conf.eth2.input=1 net.mpls.platform_labels=100000 Install service files ^^^^^^^^^^^^^^^^^^^^^ .. code-block:: console sudo install -m 644 tools/frr.service /etc/systemd/system/frr.service sudo systemctl enable frr Enable daemons ^^^^^^^^^^^^^^ Open :file:`/etc/frr/daemons` with your text editor of choice. Look for the section with ``watchfrr_enable=...`` and ``zebra=...`` etc. Enable the daemons as required by changing the value to ``yes``. Start FRR ^^^^^^^^^ .. code-block:: shell systemctl start frr frr-7.2.1/doc/developer/building-libyang.rst0000644000000000000000000000415613610377563015726 00000000000000FRR depends on the relatively new ``libyang`` library to provide YANG/NETCONF support. Unfortunately, most distributions do not yet offer a ``libyang`` package from their repositories. Therefore we offer two options to install this library. **Option 1: Binary Install** The FRR project builds binary ``libyang`` packages, which we offer for download `here `_. .. warning:: ``libyang`` version 0.16.105 or newer is required to build FRR. .. note:: The ``libyang`` development packages need to be installed in addition to the libyang core package in order to build FRR successfully. Make sure to download and install those from the link above alongside the binary packages. Depending on your platform, you may also need to install the PCRE development package. Typically this is ``libpcre-dev`` or ``pcre-devel``. .. note:: For Debian-based systems, the official ``libyang`` package requires recent versions of ``swig`` (3.0.12) and ``debhelper`` (11) which are only available in Debian buster (10). However, ``libyang`` packages built on Debian buster can be installed on both Debian jessie (8) and Debian stretch (9), as well as various Ubuntu systems. The ``python3-yang`` package will not work, but the other packages (``libyang-dev`` is the one needed for FRR) will. **Option 2: Source Install** .. note:: Ensure that the `libyang build requirements `_ are met before continuing. Usually this entails installing ``cmake`` and ``libpcre-dev`` or ``pcre-devel``. .. code-block:: console git clone https://github.com/CESNET/libyang.git cd libyang mkdir build; cd build cmake -DENABLE_LYD_PRIV=ON -DCMAKE_INSTALL_PREFIX:PATH=/usr \ -D CMAKE_BUILD_TYPE:String="Release" .. make sudo make install When building ``libyang`` version ``0.16.x`` it's also necessary to pass the ``-DENABLE_CACHE=OFF`` parameter to ``cmake`` to work around a `known bug `_ in libyang. frr-7.2.1/doc/developer/building.rst0000644000000000000000000000107513610377563014300 00000000000000.. _building: ************ Building FRR ************ .. toctree:: :maxdepth: 2 building-frr-for-alpine building-frr-for-centos6 building-frr-for-centos7 building-frr-for-debian8 building-frr-for-debian9 building-frr-for-fedora building-frr-for-freebsd10 building-frr-for-freebsd11 building-frr-for-freebsd9 building-frr-for-netbsd6 building-frr-for-netbsd7 building-frr-for-omnios building-frr-for-openbsd6 building-frr-for-openwrt building-frr-for-ubuntu1404 building-frr-for-ubuntu1604 building-frr-for-ubuntu1804 frr-7.2.1/doc/developer/cli.rst0000644000000000000000000010377013610377563013257 00000000000000.. _command-line-interface: Command Line Interface ====================== FRR features a flexible modal command line interface. Often when adding new features or modifying existing code it is necessary to create or modify CLI commands. FRR has a powerful internal CLI system that does most of the heavy lifting for you. Modes ----- FRR's CLI is organized by modes. Each mode is associated with some set of functionality, e.g. EVPN, or some underlying object such as an interface. Each mode contains a set of commands that control the associated functionality or object. Users move between the modes by entering a command, which is usually different for each source and destination mode. A summary of the modes is given in the following figure. .. graphviz:: ../figures/nodes.dot .. seealso:: :ref:`cli-data-structures` Walkup ^^^^^^ FRR exhibits, for historical reasons, a peculiar behavior called 'walkup'. Suppose a user is in ``OSPF_NODE``, which contains only OSPF-specific commands, and enters the following command: :: ip route 192.168.100.0/24 10.0.2.2 This command is not defined in ``OSPF_NODE``, so the matcher will fail to match the command in that node. The matcher will then check "parent" nodes of ``OSPF_NODE``. In this case the direct parent of ``OSPF_NODE`` is ``CONFIG_NODE``, so the current node switches to ``CONFIG_NODE`` and the command is tried in that node. Since static route commands are defined in ``CONFIG_NODE`` the command succeeds. The procedure of attempting to execute unmatched commands by sequentially "walking up" to parent nodes only happens in children (direct and indirect) below ``CONFIG_NODE`` and stops at ``CONFIG_NODE``. Unfortunately, the internal representation of the various modes is not actually a graph. Instead, there is an array. The parent-child relationships are not explicitly defined in any datastructure but instead are hard-coded into the specific commands that switch nodes. For walkup, there is a function that takes a node and returns the parent of the node. This interface causes all manner of insidious problems, even for experienced developers, and needs to be fixed at some point in the future. Defining Commands ----------------- All definitions for the CLI system are exposed in ``lib/command.h``. In this header there are a set of macros used to define commands. These macros are collectively referred to as "DEFUNs", because of their syntax: :: DEFUN(command_name, command_name_cmd, "example command FOO...", "Examples\n" "CLI command\n" "Argument\n") { // ...command handler... } DEFUNs generally take four arguments which are expanded into the appropriate constructs for hooking into the CLI. In order these are: - **Function name** - the name of the handler function for the command - **Command name** - the identifier of the ``struct cmd_element`` for the command. By convention this should be the function name with ``_cmd`` appended. - **Command definition** - an expression in FRR's CLI grammar that defines the form of the command and its arguments, if any - **Doc string** - a newline-delimited string that documents each element in the command definition In the above example, ``command_name`` is the function name, ``command_name_cmd`` is the command name, ``"example..."`` is the definition and the last argument is the doc string. The block following the macro is the body of the handler function, details on which are presented later in this section. In order to make the command show up to the user it must be installed into the CLI graph. To do this, call: ``install_element(NODE, &command_name_cmd);`` This will install the command into the specified CLI node. Usually these calls are grouped together in a CLI initialization function for a set of commands, and the DEFUNs themselves are grouped into the same source file to avoid cluttering the codebase. The names of these files follow the form ``*_vty.[ch]`` by convention. Please do not scatter individual CLI commands in the middle of source files; instead expose the necessary functions in a header and place the command definition in a ``*_vty.[ch]`` file. Definition Grammar ^^^^^^^^^^^^^^^^^^ FRR uses its own grammar for defining CLI commands. The grammar draws from syntax commonly seen in \*nix manpages and should be fairly intuitive. The parser is implemented in Bison and the lexer in Flex. These may be found in ``lib/command_lex.l`` and ``lib/command_parse.y``, respectively. **ProTip**: if you define a new command and find that the parser is throwing syntax or other errors, the parser is the last place you want to look. Bison is very stable and if it detects a syntax error, 99% of the time it will be a syntax error in your definition. The formal grammar in BNF is given below. This is the grammar implemented in the Bison parser. At runtime, the Bison parser reads all of the CLI strings and builds a combined directed graph that is used to match and interpret user input. Human-friendly explanations of how to use this grammar are given a bit later in this section alongside information on the :ref:`cli-data-structures` constructed by the parser. .. productionlist:: command: `cmd_token_seq` : `cmd_token_seq` `placeholder_token` "..." cmd_token_seq: *empty* : `cmd_token_seq` `cmd_token` cmd_token: `simple_token` : `selector` simple_token: `literal_token` : `placeholder_token` literal_token: WORD `varname_token` varname_token: "$" WORD placeholder_token: `placeholder_token_real` `varname_token` placeholder_token_real: IPV4 : IPV4_PREFIX : IPV6 : IPV6_PREFIX : VARIABLE : RANGE : MAC : MAC_PREFIX selector: "<" `selector_seq_seq` ">" `varname_token` : "{" `selector_seq_seq` "}" `varname_token` : "[" `selector_seq_seq` "]" `varname_token` selector_seq_seq: `selector_seq_seq` "|" `selector_token_seq` : `selector_token_seq` selector_token_seq: `selector_token_seq` `selector_token` : `selector_token` selector_token: `selector` : `simple_token` Tokens ^^^^^^ The various capitalized tokens in the BNF above are in fact themselves placeholders, but not defined as such in the formal grammar; the grammar provides the structure, and the tokens are actually more like a type system for the strings you write in your CLI definitions. A CLI definition string is broken apart and each piece is assigned a type by the lexer based on a set of regular expressions. The parser uses the type information to verify the string and determine the structure of the CLI graph; additional metadata (such as the raw text of each token) is encoded into the graph as it is constructed by the parser, but this is merely a dumb copy job. Here is a brief summary of the various token types along with examples. +-----------------+-------------------+-------------------------------------------------------------+ | Token type | Syntax | Description | +=================+===================+=============================================================+ | ``WORD`` | ``show ip bgp`` | Matches itself. In the given example every token is a WORD. | +-----------------+-------------------+-------------------------------------------------------------+ | ``IPV4`` | ``A.B.C.D`` | Matches an IPv4 address. | +-----------------+-------------------+-------------------------------------------------------------+ | ``IPV6`` | ``X:X::X:X`` | Matches an IPv6 address. | +-----------------+-------------------+-------------------------------------------------------------+ | ``IPV4_PREFIX`` | ``A.B.C.D/M`` | Matches an IPv4 prefix in CIDR notation. | +-----------------+-------------------+-------------------------------------------------------------+ | ``IPV6_PREFIX`` | ``X:X::X:X/M`` | Matches an IPv6 prefix in CIDR notation. | +-----------------+-------------------+-------------------------------------------------------------+ | ``MAC`` | ``X:X:X:X:X:X`` | Matches a 48-bit mac address. | +-----------------+-------------------+-------------------------------------------------------------+ | ``MAC_PREFIX`` | ``X:X:X:X:X:X/M`` | Matches a 48-bit mac address with a mask. | +-----------------+-------------------+-------------------------------------------------------------+ | ``VARIABLE`` | ``FOOBAR`` | Matches anything. | +-----------------+-------------------+-------------------------------------------------------------+ | ``RANGE`` | ``(X-Y)`` | Matches numbers in the range X..Y inclusive. | +-----------------+-------------------+-------------------------------------------------------------+ When presented with user input, the parser will search over all defined commands in the current context to find a match. It is aware of the various types of user input and has a ranking system to help disambiguate commands. For instance, suppose the following commands are defined in the user's current context: :: example command FOO example command (22-49) example command A.B.C.D/X The following table demonstrates the matcher's choice for a selection of possible user input. +---------------------------------+---------------------------+--------------------------------------------------------------------------------------------------------------+ | Input | Matched command | Reason | +=================================+===========================+==============================================================================================================+ | ``example command eLi7eH4xx0r`` | example command FOO | ``eLi7eH4xx0r`` is not an integer or IPv4 prefix, | | | | but FOO is a variable and matches all input. | +---------------------------------+---------------------------+--------------------------------------------------------------------------------------------------------------+ | ``example command 42`` | example command (22-49) | ``42`` is not an IPv4 prefix. It does match both | | | | ``(22-49)`` and ``FOO``, but RANGE tokens are more specific and have a higher priority than VARIABLE tokens. | +---------------------------------+---------------------------+--------------------------------------------------------------------------------------------------------------+ | ``example command 10.3.3.0/24`` | example command A.B.C.D/X | The user entered an IPv4 prefix, which is best matched by the last command. | +---------------------------------+---------------------------+--------------------------------------------------------------------------------------------------------------+ Rules ^^^^^ There are also constructs which allow optional tokens, mutual exclusion, one-or-more selection and repetition. - ```` -- Contain sequences of tokens separated by pipes and provide mutual exclusion. User input matches at most one option. - ``[square brackets]`` -- Contains sequences of tokens that can be omitted. ``[]`` can be shortened to ``[a|b]``. - ``{curly|braces}`` -- similar to angle brackets, but instead of mutual exclusion, curly braces indicate that one or more of the pipe-separated sequences may be provided in any order. - ``VARIADICS...`` -- Any token which accepts input (anything except WORD) which occurs as the last token of a line may be followed by an ellipsis, which indicates that input matching the token may be repeated an unlimited number of times. - ``$name`` -- Specify a variable name for the preceding token. See "Variable Names" below. Some general notes: - Options are allowed at the beginning of the command. The developer is entreated to use these extremely sparingly. They are most useful for implementing the 'no' form of configuration commands. Please think carefully before using them for anything else. There is usually a better solution, even if it is just separating out the command definition into separate ones. - The developer should judiciously apply separation of concerns when defining commands. CLI definitions for two unrelated or vaguely related commands or configuration items should be defined in separate commands. Clarity is preferred over LOC (within reason). - The maximum number of space-separated tokens that can be entered is presently limited to 256. Please keep this limit in mind when implementing new CLI. Variable Names ^^^^^^^^^^^^^^ The parser tries to fill the "varname" field on each token. This can happen either manually or automatically. Manual specifications work by appending ``$name`` after the input specifier: :: foo bar$cmd WORD$name A.B.C.D$ip Note that you can also assign variable names to fixed input tokens, this can be useful if multiple commands share code. You can also use "$name" after a multiple-choice option: :: foo bar $addr [optionA|optionB]$mode The variable name is in this case assigned to the last token in each of the branches. Automatic assignment of variable names works by applying the following rules: - manual names always have priority - a ``[no]`` at the beginning receives ``no`` as varname on the ``no`` token - ``VARIABLE`` tokens whose text is not ``WORD`` or ``NAME`` receive a cleaned lowercase version of the token text as varname, e.g. ``ROUTE-MAP`` becomes ``route_map``. - other variable tokens (i.e. everything except "fixed") receive the text of the preceding fixed token as varname, if one can be found. E.g. ``ip route A.B.C.D/M INTERFACE`` assigns "route" to the ``A.B.C.D/M`` token. These rules should make it possible to avoid manual varname assignment in 90% of the cases. Doc Strings ^^^^^^^^^^^ Each token in a command definition should be documented with a brief doc string that informs a user of the meaning and/or purpose of the subsequent command tree. These strings are provided as the last parameter to DEFUN macros, concatenated together and separated by an escaped newline (``\n``). These are best explained by example. :: DEFUN (config_terminal, config_terminal_cmd, "configure terminal", "Configuration from vty interface\n" "Configuration terminal\n") The last parameter is split into two lines for readability. Two newline delimited doc strings are present, one for each token in the command. The second string documents the functionality of the ``terminal`` command in the ``configure`` subtree. Note that the first string, for ``configure`` does not contain documentation for 'terminal'. This is because the CLI is best envisioned as a tree, with tokens defining branches. An imaginary ``start`` token is the root of every command in a CLI node. Each subsequent written token descends into a subtree, so the documentation for that token ideally summarizes all the functionality contained in the subtree. A consequence of this structure is that the developer must be careful to use the same doc strings when defining multiple commands that are part of the same tree. Commands which share prefixes must share the same doc strings for those prefixes. On startup the parser will generate warnings if it notices inconsistent doc strings. Behavior is undefined; the same token may show up twice in completions, with different doc strings, or it may show up once with a random doc string. Parser warnings should be heeded and fixed to avoid confusing users. The number of doc strings provided must be equal to the amount of tokens present in the command definition, read left to right, ignoring any special constructs. In the examples below, each arrowed token needs a doc string. :: "show ip bgp" ^ ^ ^ "command [example]" ^ ^ ^ ^ DEFPY ^^^^^ ``DEFPY(...)`` is an enhanced version of ``DEFUN()`` which is preprocessed by :file:`python/clidef.py`. The python script parses the command definition string, extracts variable names and types, and generates a C wrapper function that parses the variables and passes them on. This means that in the CLI function body, you will receive additional parameters with appropriate types. This is best explained by an example. Invoking ``DEFPY`` like this: .. code-block:: c DEFPY(func, func_cmd, "[no] foo bar A.B.C.D (0-99)$num", "...help...") defines the handler function like this: .. code-block:: c func(self, vty, argc, argv, /* standard CLI arguments */ const char *no, /* unparsed "no" */ struct in_addr bar, /* parsed IP address */ const char *bar_str, /* unparsed IP address */ long num, /* parsed num */ const char *num_str) /* unparsed num */ Note that as documented in the previous section, ``bar`` is automatically applied as variable name for ``A.B.C.D``. The Python script then detects this as an IP address argument and generates code to parse it into a ``struct in_addr``, passing it in ``bar``. The raw value is passed in ``bar_str``. The range/number argument works in the same way with the explicitly given variable name. Type rules """""""""" +----------------------------+--------------------------------+--------------------------+ | Token(s) | Type | Value if omitted by user | +============================+================================+==========================+ | ``A.B.C.D`` | ``struct in_addr`` | ``0.0.0.0`` | +----------------------------+--------------------------------+--------------------------+ | ``X:X::X:X`` | ``struct in6_addr`` | ``::`` | +----------------------------+--------------------------------+--------------------------+ | ``A.B.C.D + X:X::X:X`` | ``const union sockunion *`` | ``NULL`` | +----------------------------+--------------------------------+--------------------------+ | ``A.B.C.D/M`` | ``const struct prefix_ipv4 *`` | ``NULL`` | +----------------------------+--------------------------------+--------------------------+ | ``X:X::X:X/M`` | ``const struct prefix_ipv6 *`` | ``NULL`` | +----------------------------+--------------------------------+--------------------------+ | ``A.B.C.D/M + X:X::X:X/M`` | ``const struct prefix *`` | ``NULL`` | +----------------------------+--------------------------------+--------------------------+ | ``(0-9)`` | ``long`` | ``0`` | +----------------------------+--------------------------------+--------------------------+ | ``VARIABLE`` | ``const char *`` | ``NULL`` | +----------------------------+--------------------------------+--------------------------+ | ``word`` | ``const char *`` | ``NULL`` | +----------------------------+--------------------------------+--------------------------+ | *all other* | ``const char *`` | ``NULL`` | +----------------------------+--------------------------------+--------------------------+ Note the following details: - Not all parameters are pointers, some are passed as values. - When the type is not ``const char *``, there will be an extra ``_str`` argument with type ``const char *``. - You can give a variable name not only to ``VARIABLE`` tokens but also to ``word`` tokens (e.g. constant words). This is useful if some parts of a command are optional. The type will be ``const char *``. - ``[no]`` will be passed as ``const char *no``. - Pointers will be ``NULL`` when the argument is optional and the user did not use it. - If a parameter is not a pointer, but is optional and the user didn't use it, the default value will be passed. Check the ``_str`` argument if you need to determine whether the parameter was omitted. - If the definition contains multiple parameters with the same variable name, they will be collapsed into a single function parameter. The python code will detect if the types are compatible (i.e. IPv4 + IPv6 variants) and choose a corresponding C type. - The standard DEFUN parameters (``self, vty, argc, argv``) are still present and can be used. A DEFUN can simply be **edited into a DEFPY without further changes and it will still work**; this allows easy forward migration. - A file may contain both ``DEFUN`` and ``DEFPY`` statements. Getting a parameter dump """""""""""""""""""""""" The clidef.py script can be called to get a list of DEFUNs/DEFPYs with the parameter name/type list: :: lib/clippy python/clidef.py --all-defun --show lib/plist.c > /dev/null The generated code is printed to stdout, the info dump to stderr. The ``--all-defun`` argument will make it process DEFUN blocks as well as DEFPYs, which is useful prior to converting some DEFUNs. **The dump does not list the ``_str`` arguments** to keep the output shorter. Note that the ``clidef.py`` script cannot be run with python directly, it needs to be run with *clippy* since the latter makes the CLI parser available. Include & Makefile requirements """"""""""""""""""""""""""""""" A source file that uses DEFPY needs to include the ``*_clippy.c`` file **before all DEFPY statements**: .. code-block:: c /* GPL header */ #include ... ... #ifndef VTYSH_EXTRACT_PL #include "daemon/filename_clippy.c" #endif DEFPY(...) DEFPY(...) install_element(...) This dependency needs to be marked in ``Makefile.am`` or ``subdir.am``: (there is no ordering requirement) .. code-block:: make # ... # if linked into a LTLIBRARY (.la/.so): filename.lo: filename_clippy.c # if linked into an executable or static library (.a): filename.o: filename_clippy.c Handlers ^^^^^^^^ The block that follows a CLI definition is executed when a user enters input that matches the definition. Its function signature looks like this: .. code-block:: c int (*func) (const struct cmd_element *, struct vty *, int, struct cmd_token *[]); The first argument is the command definition struct. The last argument is an ordered array of tokens that correspond to the path taken through the graph, and the argument just prior to that is the length of the array. The arrangement of the token array has changed from Quagga's CLI implementation. In the old system, missing arguments were padded with ``NULL`` so that the same parts of a command would show up at the same indices regardless of what was entered. The new system does not perform such padding and therefore it is generally *incorrect* to assume consistent indices in this array. As a simple example: Command definition: :: command [foo] User enters: :: command foo bar Array: :: [0] -> command [1] -> foo [2] -> bar User enters: :: command baz Array: :: [0] -> command [1] -> baz .. _cli-data-structures: Data Structures --------------- On startup, the CLI parser sequentially parses each command string definition and constructs a directed graph with each token forming a node. This graph is the basis of the entire CLI system. It is used to match user input in order to generate command completions and match commands to functions. There is one graph per CLI node (not the same as a graph node in the CLI graph). The CLI node struct keeps a reference to its graph (see :file:`lib/command.h`). While most of the graph maintains the form of a tree, special constructs outlined in the Rules section introduce some quirks. ``<>``, ``[]`` and ``{}`` form self-contained 'subgraphs'. Each subgraph is a tree except that all of the 'leaves' actually share a child node. This helps with minimizing graph size and debugging. As a working example, here is the graph of the following command: :: show [ip] bgp neighbors [] [json] .. figure:: ../figures/cligraph.png :align: center Graph of example CLI command ``FORK`` and ``JOIN`` nodes are plumbing nodes that don't correspond to user input. They're necessary in order to deduplicate these constructs where applicable. Options follow the same form, except that there is an edge from the ``FORK`` node to the ``JOIN`` node. Since all of the subgraphs in the example command are optional, all of them have this edge. Keywords follow the same form, except that there is an edge from ``JOIN`` to ``FORK``. Because of this the CLI graph cannot be called acyclic. There is special logic in the input matching code that keeps a stack of paths already taken through the node in order to disallow following the same path more than once. Variadics are a bit special; they have an edge back to themselves, which allows repeating the same input indefinitely. The leaves of the graph are nodes that have no out edges. These nodes are special; their data section does not contain a token, as most nodes do, or ``NULL``, as in ``FORK``/``JOIN`` nodes, but instead has a pointer to a ``cmd_element``. All paths through the graph that terminate on a leaf are guaranteed to be defined by that command. When a user enters a complete command, the command matcher tokenizes the input and executes a DFS on the CLI graph. If it is simultaneously able to exhaust all input (one input token per graph node), and then find exactly one leaf connected to the last node it reaches, then the input has matched the corresponding command and the command is executed. If it finds more than one node, then the command is ambiguous (more on this in deduplication). If it cannot exhaust all input, the command is unknown. If it exhausts all input but does not find an edge node, the command is incomplete. The parser uses an incremental strategy to build the CLI graph for a node. Each command is parsed into its own graph, and then this graph is merged into the overall graph. During this merge step, the parser makes a best-effort attempt to remove duplicate nodes. If it finds a node in the overall graph that is equal to a node in the corresponding position in the command graph, it will intelligently merge the properties from the node in the command graph into the already-existing node. Subgraphs are also checked for isomorphism and merged where possible. The definition of whether two nodes are 'equal' is based on the equality of some set of token properties; read the parser source for the most up-to-date definition of equality. When the parser is unable to deduplicate some complicated constructs, this can result in two identical paths through separate parts of the graph. If this occurs and the user enters input that matches these paths, they will receive an 'ambiguous command' error and will be unable to execute the command. Most of the time the parser can detect and warn about duplicate commands, but it will not always be able to do this. Hence care should be taken before defining a new command to ensure it is not defined elsewhere. struct cmd\_token ^^^^^^^^^^^^^^^^^ .. code-block:: c /* Command token struct. */ struct cmd_token { enum cmd_token_type type; // token type uint8_t attr; // token attributes bool allowrepeat; // matcher can match token repetitively? char *text; // token text char *desc; // token description long long min, max; // for ranges char *arg; // user input that matches this token char *varname; // variable name }; This struct is used in the CLI graph to match input against. It is also used to pass user input to command handler functions, as it is frequently useful for handlers to have access to that information. When a command is matched, the sequence of ``cmd_tokens`` that form the matching path are duplicated and placed in order into ``*argv[]``. Before this happens the ``->arg`` field is set to point at the snippet of user input that matched it. For most nontrivial commands the handler function will need to determine which of the possible matching inputs was entered. Previously this was done by looking at the first few characters of input. This is now considered an anti-pattern and should be avoided. Instead, the ``->type`` or ``->text`` fields for this logic. The ``->type`` field can be used when the possible inputs differ in type. When the possible types are the same, use the ``->text`` field. This field has the full text of the corresponding token in the definition string and using it makes for much more readable code. An example is helpful. Command definition: :: command <(1-10)|foo|BAR> In this example, the user may enter any one of: - an integer between 1 and 10 - "foo" - anything at all If the user enters "command f", then: :: argv[1]->type == WORD_TKN argv[1]->arg == "f" argv[1]->text == "foo" Range tokens have some special treatment; a token with ``->type == RANGE_TKN`` will have the ``->min`` and ``->max`` fields set to the bounding values of the range. struct cmd\_element ^^^^^^^^^^^^^^^^^^^ .. code-block:: c struct cmd_node { /* Node index. */ enum node_type node; /* Prompt character at vty interface. */ const char *prompt; /* Is this node's configuration goes to vtysh ? */ int vtysh; /* Node's configuration write function */ int (*func)(struct vty *); /* Node's command graph */ struct graph *cmdgraph; /* Vector of this node's command list. */ vector cmd_vector; /* Hashed index of command node list, for de-dupping primarily */ struct hash *cmd_hash; }; This struct corresponds to a CLI mode. The last three fields are most relevant here. cmdgraph This is a pointer to the command graph that was described in the first part of this section. It is the datastructure used for matching user input to commands. cmd_vector This is a list of all the ``struct cmd_element`` defined in the mode. cmd_hash This is a hash table of all the ``struct cmd_element`` defined in the mode. When ``install_element`` is called, it checks that the element it is given is not already present in the hash table as a safeguard against duplicate calls resulting in a command being defined twice, which renders the command ambiguous. All ``struct cmd_node`` are themselves held in a static vector defined in :file:`lib/command.c` that defines the global CLI space. Command Abbreviation & Matching Priority ---------------------------------------- It is possible for users to elide parts of tokens when the CLI matcher does not need them to make an unambiguous match. This is best explained by example. Command definitions: :: command dog cow command dog crow User input: :: c d c -> ambiguous command c d co -> match "command dog cow" The parser will look ahead and attempt to disambiguate the input based on tokens later on in the input string. Command definitions: :: show ip bgp A.B.C.D show ipv6 bgp X:X::X:X User enters: :: s i b 4.3.2.1 -> match "show ip bgp A.B.C.D" s i b ::e0 -> match "show ipv6 bgp X:X::X:X" Reading left to right, both of these commands would be ambiguous since 'i' does not explicitly select either 'ip' or 'ipv6'. However, since the user later provides a token that matches only one of the commands (an IPv4 or IPv6 address) the parser is able to look ahead and select the appropriate command. This has some implications for parsing the ``*argv[]`` that is passed to the command handler. Now consider a command definition such as: :: command 'foo' only matches the string 'foo', but 'VAR' matches any input, including 'foo'. Who wins? In situations like this the matcher will always choose the 'better' match, so 'foo' will win. Consider also: :: show foo User input: :: show ip foo ``ip`` partially matches ``ipv6`` but exactly matches ``ip``, so ``ip`` will win. Inspection & Debugging ---------------------- Permutations ^^^^^^^^^^^^ It is sometimes useful to check all the possible combinations of input that would match an arbitrary definition string. There is a tool in :file:`tools/permutations` that reads CLI definition strings on ``stdin`` and prints out all matching input permutations. It also dumps a text representation of the graph, which is more useful for debugging than anything else. It looks like this: .. code-block:: shell $ ./permutations "show [ip] bgp [ WORD]" show ip bgp view WORD show ip bgp vrf WORD show ip bgp show bgp view WORD show bgp vrf WORD show bgp This functionality is also built into VTY/VTYSH; :clicmd:`list permutations` will list all possible matching input permutations in the current CLI node. Graph Inspection ^^^^^^^^^^^^^^^^ When in the Telnet or VTYSH console, :clicmd:`show cli graph` will dump the entire command space of the current mode in the DOT graph language. This can be fed into one of the various GraphViz layout engines, such as ``dot``, ``neato``, etc. For example, to generate an image of the entire command space for the top-level mode (``ENABLE_NODE``): .. code-block:: shell sudo vtysh -c 'show cli graph' | dot -Tjpg -Grankdir=LR > graph.jpg To do the same for the BGP mode: .. code-block:: shell sudo vtysh -c 'conf t' -c 'router bgp' -c 'show cli graph' | dot -Tjpg -Grankdir=LR > bgpgraph.jpg This information is very helpful when debugging command resolution, tracking down duplicate / ambiguous commands, and debugging patches to the CLI graph builder. frr-7.2.1/doc/developer/conf.py0000644000000000000000000003044713610377563013255 00000000000000# -*- coding: utf-8 -*- # # FRR documentation build configuration file, created by # sphinx-quickstart on Tue Jan 31 16:00:52 2017. # # This file is execfile()d with the current directory set to its # containing dir. # # Note that not all possible configuration values are present in this # autogenerated file. # # All configuration values have a default; values that are commented out # serve to show the default. import sys import os import re import pygments from sphinx.highlighting import lexers # If extensions (or modules to document with autodoc) are in another directory, # add these directories to sys.path here. If the directory is relative to the # documentation root, use os.path.abspath to make it absolute, like shown here. #sys.path.insert(0, os.path.abspath('.')) # -- General configuration ------------------------------------------------ # If your documentation needs a minimal Sphinx version, state it here. needs_sphinx = '1.0' # prolog for various variable substitutions rst_prolog = '' # Add any Sphinx extension module names here, as strings. They can be # extensions coming with Sphinx (named 'sphinx.ext.*') or your custom # ones. extensions = ['sphinx.ext.todo', 'sphinx.ext.graphviz'] # Add any paths that contain templates here, relative to this directory. templates_path = ['_templates'] # The suffix(es) of source filenames. # You can specify multiple suffix as a list of string: # source_suffix = ['.rst'] source_suffix = '.rst' # The encoding of source files. #source_encoding = 'utf-8-sig' # The master toctree document. master_doc = 'index' # General information about the project. project = u'FRR' copyright = u'2017, FRR' author = u'FRR authors' # The version info for the project you're documenting, acts as replacement for # |version| and |release|, also used in various other places throughout the # built documents. # The short X.Y version. version = u'?.?' # The full version, including alpha/beta/rc tags. release = u'?.?-?' # ----------------------------------------------------------------------------- # Extract values from codebase for substitution into docs. # ----------------------------------------------------------------------------- # Various installation prefixes. Values are extracted from config.status. # Reasonable defaults are set in case that file does not exist. replace_vars = { 'AUTHORS': author, 'COPYRIGHT_YEAR': '1999-2005', 'COPYRIGHT_STR': 'Copyright (c) 1999-2005', 'PACKAGE_NAME': project.lower(), 'PACKAGE_TARNAME': project.lower(), 'PACKAGE_STRING': project.lower() + ' latest', 'PACKAGE_URL': 'https://frrouting.org/', 'PACKAGE_VERSION': 'latest', 'INSTALL_PREFIX_ETC': '/etc/frr', 'INSTALL_PREFIX_SBIN': '/usr/lib/frr', 'INSTALL_PREFIX_STATE': '/var/run/frr', 'INSTALL_PREFIX_MODULES': '/usr/lib/frr/modules', 'INSTALL_USER': 'frr', 'INSTALL_GROUP': 'frr', 'INSTALL_VTY_GROUP': 'frrvty', 'GROUP': 'frr', 'USER': 'frr', } # extract version information, installation location, other stuff we need to # use when building final documents val = re.compile('^S\["([^"]+)"\]="(.*)"$') try: with open('../../config.status', 'r') as cfgstatus: for ln in cfgstatus.readlines(): m = val.match(ln) if not m or m.group(1) not in replace_vars.keys(): continue replace_vars[m.group(1)] = m.group(2) except IOError: # if config.status doesn't exist, just ignore it pass # manually fill out some of these we can't get from config.status replace_vars['COPYRIGHT_STR'] = "Copyright (c)" replace_vars['COPYRIGHT_STR'] += ' {0}'.format(replace_vars['COPYRIGHT_YEAR']) replace_vars['COPYRIGHT_STR'] += ' {0}'.format(replace_vars['AUTHORS']) release = replace_vars['PACKAGE_VERSION'] version = release.split('-')[0] # add substitutions to prolog for key, value in replace_vars.items(): rst_prolog += '.. |{0}| replace:: {1}\n'.format(key, value) # The language for content autogenerated by Sphinx. Refer to documentation # for a list of supported languages. # # This is also used if you do content translation via gettext catalogs. # Usually you set "language" from the command line for these cases. language = None # There are two options for replacing |today|: either, you set today to some # non-false value, then it is used: #today = '' # Else, today_fmt is used as the format for a strftime call. #today_fmt = '%B %d, %Y' # List of patterns, relative to source directory, that match files and # directories to ignore when looking for source files. exclude_patterns = ['_build', 'building-libyang.rst', 'topotests-snippets.rst', 'include-compile.rst'] # The reST default role (used for this markup: `text`) to use for all # documents. #default_role = None # If true, '()' will be appended to :func: etc. cross-reference text. #add_function_parentheses = True # If true, the current module name will be prepended to all description # unit titles (such as .. function::). #add_module_names = True # If true, sectionauthor and moduleauthor directives will be shown in the # output. They are ignored by default. #show_authors = False # The name of the Pygments (syntax highlighting) style to use. pygments_style = 'sphinx' # A list of ignored prefixes for module index sorting. #modindex_common_prefix = [] # If true, keep warnings as "system message" paragraphs in the built documents. #keep_warnings = False # If true, `todo` and `todoList` produce output, else they produce nothing. todo_include_todos = True # -- Options for HTML output ---------------------------------------------- # The theme to use for HTML and HTML Help pages. See the documentation for # a list of builtin themes. html_theme = 'default' try: import sphinx_rtd_theme html_theme = 'sphinx_rtd_theme' except ImportError: pass # Theme options are theme-specific and customize the look and feel of a theme # further. For a list of options available for each theme, see the # documentation. #html_theme_options = { # 'sidebarbgcolor': '#374249' #} # Add any paths that contain custom themes here, relative to this directory. #html_theme_path = [] # The name for this set of Sphinx documents. If None, it defaults to # " v documentation". #html_title = None # A shorter title for the navigation bar. Default is the same as html_title. #html_short_title = None # The name of an image file (relative to this directory) to place at the top # of the sidebar. html_logo = '../figures/frr-icon.svg' # The name of an image file (within the static path) to use as favicon of the # docs. This file should be a Windows icon file (.ico) being 16x16 or 32x32 # pixels large. html_favicon = '../figures/frr-logo-icon.png' # Add any paths that contain custom static files (such as style sheets) here, # relative to this directory. They are copied after the builtin static files, # so a file named "default.css" will overwrite the builtin "default.css". html_static_path = ['_static'] # Add any extra paths that contain custom files (such as robots.txt or # .htaccess) here, relative to this directory. These files are copied # directly to the root of the documentation. #html_extra_path = [] # If not '', a 'Last updated on:' timestamp is inserted at every page bottom, # using the given strftime format. #html_last_updated_fmt = '%b %d, %Y' # If true, SmartyPants will be used to convert quotes and dashes to # typographically correct entities. #html_use_smartypants = True # Custom sidebar templates, maps document names to template names. #html_sidebars = {} # Additional templates that should be rendered to pages, maps page names to # template names. #html_additional_pages = {} # If false, no module index is generated. #html_domain_indices = True # If false, no index is generated. #html_use_index = True # If true, the index is split into individual pages for each letter. #html_split_index = False # If true, links to the reST sources are added to the pages. #html_show_sourcelink = True # If true, "Created using Sphinx" is shown in the HTML footer. Default is True. #html_show_sphinx = True # If true, "(C) Copyright ..." is shown in the HTML footer. Default is True. #html_show_copyright = True # If true, an OpenSearch description file will be output, and all pages will # contain a tag referring to it. The value of this option must be the # base URL from which the finished HTML is served. #html_use_opensearch = '' # This is the file name suffix for HTML files (e.g. ".xhtml"). #html_file_suffix = None # Language to be used for generating the HTML full-text search index. # Sphinx supports the following languages: # 'da', 'de', 'en', 'es', 'fi', 'fr', 'hu', 'it', 'ja' # 'nl', 'no', 'pt', 'ro', 'ru', 'sv', 'tr' #html_search_language = 'en' # A dictionary with options for the search language support, empty by default. # Now only 'ja' uses this config value #html_search_options = {'type': 'default'} # The name of a javascript file (relative to the configuration directory) that # implements a search results scorer. If empty, the default will be used. #html_search_scorer = 'scorer.js' # Output file base name for HTML help builder. htmlhelp_basename = 'FRRdoc' # -- Options for LaTeX output --------------------------------------------- latex_elements = { # The paper size ('letterpaper' or 'a4paper'). #'papersize': 'letterpaper', # The font size ('10pt', '11pt' or '12pt'). #'pointsize': '10pt', # Additional stuff for the LaTeX preamble. #'preamble': '', # Latex figure (float) alignment #'figure_align': 'htbp', } # Grouping the document tree into LaTeX files. List of tuples # (source start file, target name, title, # author, documentclass [howto, manual, or own class]). latex_documents = [ (master_doc, 'FRR.tex', u"FRR Developer's Manual", u'FRR', 'manual'), ] # The name of an image file (relative to this directory) to place at the top of # the title page. latex_logo = '../figures/frr-logo-medium.png' # For "manual" documents, if this is true, then toplevel headings are parts, # not chapters. #latex_use_parts = False # If true, show page references after internal links. #latex_show_pagerefs = False # If true, show URL addresses after external links. #latex_show_urls = False # Documents to append as an appendix to all manuals. #latex_appendices = [] # If false, no module index is generated. #latex_domain_indices = True # -- Options for manual page output --------------------------------------- # One entry per manual page. List of tuples # (source start file, name, description, authors, manual section). man_pages = [ (master_doc, 'frr', u"FRR Developer's Manual", [author], 1) ] # If true, show URL addresses after external links. #man_show_urls = False # -- Options for Texinfo output ------------------------------------------- # Grouping the document tree into Texinfo files. List of tuples # (source start file, target name, title, author, # dir menu entry, description, category) texinfo_documents = [ (master_doc, 'frr', u"FRR Developer's Manual", author, 'FRR', 'One line description of project.', 'Miscellaneous'), ] # Documents to append as an appendix to all manuals. #texinfo_appendices = [] # If false, no module index is generated. #texinfo_domain_indices = True # How to display URL addresses: 'footnote', 'no', or 'inline'. #texinfo_show_urls = 'footnote' # If true, do not generate a @detailmenu in the "Top" node's menu. #texinfo_no_detailmenu = False # contents of ../extra/frrlexer.py. # This is read here to support VPATH build. Since this section is execfile()'d # with the file location, we can safely use a relative path here to save the # contents of the lexer file for later use even if our relative path changes # due to VPATH. with open('../extra/frrlexer.py', 'rb') as lex: frrlexerpy = lex.read() # custom extensions here def setup(app): # object type for FRR CLI commands, can be extended to document parent CLI # node later on app.add_object_type('clicmd', 'clicmd') # css overrides for HTML theme app.add_stylesheet('overrides.css') # load Pygments lexer for FRR config syntax # # NB: in Pygments 2.2+ this can be done with `load_lexer_from_file`, but we # do it manually since not all of our supported build platforms have 2.2 # yet. # # frrlexer = pygments.lexers.load_lexer_from_file('../extra/frrlexer.py', lexername="FRRLexer") custom_namespace = {} exec(frrlexerpy, custom_namespace) lexers['frr'] = custom_namespace['FRRLexer']() frr-7.2.1/doc/developer/draft-zebra-00.ms0000644000000000000000000001213513610377563014727 00000000000000.pl 10.0i .po 0 .ll 7.2i .lt 7.2i .nr LL 7.2i .nr LT 7.2i .ds LF Ishiguro .ds RF FORMFEED[Page %] .ds CF .ds LH RFC DRAFT .ds RH March 1998 .ds CH .hy 0 .ad l Network Working Group K. Ishiguro Request for Comments: DRAFT Digital Magic Labs, Inc. March 1998 .sp 2 .ce Zebra Protocol Draft .sp 2 .fi .ne 4 Status of this Memo .sp .in 3 This draft is very eary beta version. .sp .in 0 .ne 4 Introduction .sp .in 3 The zebra protocol is a communication protocol between kernel routing table manager and routing protocol daemon. It is built over TCP/IP protocol suite. .sp .in 0 .ne 4 Request message formats .sp .in 3 zebra is TCP-based protocol. .sp Below is request packet format. .sp .in 0 .DS 0 1 2 3 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ | Length (2) | Command (1) | +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ .DE .sp .in 3 Length is total packet length. .sp Here is summary of command list. .sp .in 0 .DS 1 - ZEBRA_IPV4_ROUTE_ADD 2 - ZEBRA_IPV4_ROUTE_DELETE 3 - ZEBRA_IPV6_ROUTE_ADD 4 - ZEBRA_IPV6_ROUTE_DELETE 5 - ZEBRA_GET_ONE_INTERFACE 6 - ZEBRA_GET_ALL_INTERFACE 7 - ZEBRA_GET_HOSTINFO .DE .sp .in 0 .ne 4 IPv4 reply message formats .sp .in 0 .DS 0 1 2 3 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 +-+-+-+-+-+-+-+-+ | Type (1) | +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ | Gateway (4) | +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ .DE .sp .in 3 Type field specify route's origin type. .sp .in 0 .DS 1 - ZEBRA_ROUTE_RESERVE 2 - ZEBRA_ROUTE_CONNECT 3 - ZEBRA_ROUTE_STATIC 4 - ZEBRA_ROUTE_RIP 5 - ZEBRA_ROUTE_RIPNG 6 - ZEBRA_ROUTE_BGP 7 - ZEBRA_ROUTE_RADIX .DE .sp .in 3 After above message there can be variale length IPv4 prefix data. Each IPv4 prefix is encoded as a two tuple of the form .sp .in 0 .DS +----------------------+ |Subnet mask (1 octet) | +----------------------+ |IPv4 prefix (variable)| +----------------------+ .DE .sp .in 0 .ne 4 IPv6 reply message formats .sp .in 0 .DS 0 1 2 3 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 +-+-+-+-+-+-+-+-+ | Type (1) | +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ | | +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ | Gateway (16) | +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ | | +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ | | +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ .DE .sp .in 3 Type field specify route's origin type. .sp .in 0 .DS 1 - ZEBRA_ROUTE_RESERVE 2 - ZEBRA_ROUTE_CONNECT 3 - ZEBRA_ROUTE_STATIC 4 - ZEBRA_ROUTE_RIP 5 - ZEBRA_ROUTE_RIPNG 6 - ZEBRA_ROUTE_BGP 7 - ZEBRA_ROUTE_RADIX .DE .sp .in 0 .DS +----------------------+ | ifindex (4 octet) | +----------------------+ | prefixlen (1 octet)| +----------------------+ |IPv6 prefix (variable)| +----------------------+ .DE .sp .in 3 I am not sure but it seems some operation systems IPv6 implementation may need interface index when add and delete linklocal routes. .sp I have added ifindex field to specify IPv6 routes interface index. If this index is value zero, it will ignored. .sp .in 0 .ne 4 Interface information message format. .sp .in 0 .DS 0 1 2 3 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ | Interface name (20) | +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ | Index (1) | +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ | Inteface flag (4) | +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ | Inteface metric (4) | +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ | Inteface MTU (4) | +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ | Inteface Address count (4) | +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ .DE .sp .in 3 Address message format. .sp .in 0 .ne 4 Host inforamtion message format. .sp .in 0 .DS 0 1 2 3 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ |IPv4 forwarding|IPv6 forwarding| +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ .DE .sp .in 3 Host information contain IPv4/IPv6 forwarding information. frr-7.2.1/doc/developer/hooks.rst0000644000000000000000000001421613610377563013627 00000000000000.. highlight:: c Hooks ===== Libfrr provides type-safe subscribable hook points where other pieces of code can add one or more callback functions. "type-safe" in this case applies to the function pointers used for subscriptions. The implementations checks (at compile-time) whether a callback to be added has the appropriate function signature (parameters) for the hook. Example: .. code-block:: c :caption: mydaemon.h #include "hook.h" DECLARE_HOOK(some_update_event, (struct eventinfo *info), (info)) .. code-block:: c :caption: mydaemon.c #include "mydaemon.h" DEFINE_HOOK(some_update_event, (struct eventinfo *info), (info)) ... hook_call(some_update_event, info); .. code-block:: c :caption: mymodule.c #include "mydaemon.h" static int event_handler(struct eventinfo *info); ... hook_register(some_update_event, event_handler); Do not use parameter names starting with "hook", these can collide with names used by the hook code itself. Return values ------------- Callbacks to be placed on hooks always return "int" for now; hook_call will sum up the return values from each called function. (The default is 0 if no callbacks are registered.) There are no pre-defined semantics for the value, in most cases it is ignored. For success/failure indication, 0 should be success, and handlers should make sure to only return 0 or 1 (not -1 or other values). There is no built-in way to abort executing a chain after a failure of one of the callbacks. If this is needed, the hook can use an extra ``bool *aborted`` argument. Priorities ---------- Hooks support a "priority" value for ordering registered calls relative to each other. The priority is a signed integer where lower values are called earlier. There are also "Koohs", which is hooks with reverse priority ordering (for cleanup/deinit hooks, so you can use the same priority value). Recommended priority value ranges are: ======================== =================================================== Range Usage ------------------------ --------------------------------------------------- -999 ... 0 ... 999 main executable / daemon, or library -1999 ... -1000 modules registering calls that should run before the daemon's bits 1000 ... 1999 modules' calls that should run after daemon's (includes default value: 1000) ======================== =================================================== Note: the default value is 1000, based on the following 2 expectations: - most hook_register() usage will be in loadable modules - usage of hook_register() in the daemon itself may need relative ordering to itself, making an explicit value the expected case The priority value is passed as extra argument on hook_register_prio() / hook_register_arg_prio(). Whether a hook runs in reverse is determined solely by the code defining / calling the hook. (DECLARE_KOOH is actually the same thing as DECLARE_HOOK, it's just there to make it obvious.) Definition ---------- .. c:macro:: DECLARE_HOOK(name, arglist, passlist) .. c:macro:: DECLARE_KOOH(name, arglist, passlist) :param name: Name of the hook to be defined :param arglist: Function definition style parameter list in braces. :param passlist: List of the same parameters without their types. Note: the second and third macro args must be the hook function's parameter list, with the same names for each parameter. The second macro arg is with types (used for defining things), the third arg is just the names (used for passing along parameters). This macro must be placed in a header file; this header file must be included to register a callback on the hook. Examples: .. code-block:: c DECLARE_HOOK(foo, (), ()) DECLARE_HOOK(bar, (int arg), (arg)) DECLARE_HOOK(baz, (const void *x, in_addr_t y), (x, y)) .. c:macro:: DEFINE_HOOK(name, arglist, passlist) Implements an hook. Each ``DECLARE_HOOK`` must have be accompanied by exactly one ``DEFINE_HOOK``, which needs to be placed in a source file. **The hook can only be called from this source file.** This is intentional to avoid overloading and/or misusing hooks for distinct purposes. The compiled source file will include a global symbol with the name of the hook prefixed by `_hook_`. Trying to register a callback for a hook that doesn't exist will therefore result in a linker error, or a module load-time error for dynamic modules. .. c:macro:: DEFINE_KOOH(name, arglist, passlist) Same as ``DEFINE_HOOK``, but the sense of priorities / order of callbacks is reversed. This should be used for cleanup hooks. .. c:function:: int hook_call(name, ...) Calls the specified named hook. Parameters to the hook are passed right after the hook name, e.g.: .. code-block:: c hook_call(foo); hook_call(bar, 0); hook_call(baz, NULL, INADDR_ANY); Returns the sum of return values from all callbacks. The ``DEFINE_HOOK`` statement for the hook must be placed in the file before any ``hook_call`` use of the hook. Callback registration --------------------- .. c:function:: void hook_register(name, int (*callback)(...)) .. c:function:: void hook_register_prio(name, int priority, int (*callback)(...)) .. c:function:: void hook_register_arg(name, int (*callback)(void *arg, ...), void *arg) .. c:function:: void hook_register_arg_prio(name, int priority, int (*callback)(void *arg, ...), void *arg) Register a callback with an hook. If the caller needs to pass an extra argument to the callback, the _arg variant can be used and the extra parameter will be passed as first argument to the callback. There is no typechecking for this argument. The priority value is used as described above. The variants without a priority parameter use 1000 as priority value. .. c:function:: void hook_unregister(name, int (*callback)(...)) .. c:function:: void hook_unregister_arg(name, int (*callback)(void *arg, ...), void *arg) Removes a previously registered callback from a hook. Note that there is no _prio variant of these calls. The priority value is only used during registration. frr-7.2.1/doc/developer/include-compile.rst0000644000000000000000000000240613610377563015553 00000000000000Clone the FRR git repo and use the included ``configure`` script to configure FRR's build time options to your liking. The full option listing can be obtained by running ``./configure -h``. The options shown below are examples. .. note:: If your platform uses ``systemd``, please make sure to add ``--enable-systemd=yes`` to your configure options. .. code-block:: console git clone https://github.com/frrouting/frr.git frr cd frr ./bootstrap.sh ./configure \ --prefix=/usr \ --includedir=\${prefix}/include \ --enable-exampledir=\${prefix}/share/doc/frr/examples \ --bindir=\${prefix}/bin \ --sbindir=\${prefix}/lib/frr \ --libdir=\${prefix}/lib/frr \ --libexecdir=\${prefix}/lib/frr \ --localstatedir=/var/run/frr \ --sysconfdir=/etc/frr \ --with-moduledir=\${prefix}/lib/frr/modules \ --with-libyang-pluginsdir=\${prefix}/lib/frr/libyang_plugins \ --enable-configfile-mask=0640 \ --enable-logfile-mask=0640 \ --enable-snmp=agentx \ --enable-multipath=64 \ --enable-user=frr \ --enable-group=frr \ --enable-vty-group=frrvty \ --with-pkg-git-version \ --with-pkg-extra-version=-MyOwnFRRVersion make sudo make install frr-7.2.1/doc/developer/index.rst0000644000000000000000000000031413610377563013605 00000000000000FRRouting Developer's Guide =========================== .. toctree:: :maxdepth: 2 workflow building packaging process-architecture library testing bgpd ospf zebra vtysh frr-7.2.1/doc/developer/ldpd-basic-test-setup.md0000644000000000000000000004460513610377563016416 00000000000000## Topology The goal of this test is to verify that the all the basic functionality of ldpd is working as expected, be it running on Linux or OpenBSD. In addition to that, more advanced features are also tested, like LDP sessions over IPv6, MD5 authentication and pseudowire signaling. In the topology below there are 3 PE routers, 3 CE routers and one P router (not attached to any consumer site). All routers have IPv4 addresses and OSPF is used as the IGP. The three routers from the bottom of the picture, P, PE2 and PE3, are also configured for IPv6 (dual-stack) and static IPv6 routes are used to provide connectivity among them. The three CEs share the same VPLS membership. LDP is used to set up the LSPs among the PEs and to signal the pseudowires. MD5 authentication is used to protect all LDP sessions. ``` CE1 172.16.1.1/24 + | +---+---+ | PE1 | | IOS XE| | | +---+---+ | | 10.0.1.0/24 | +---+---+ | P | +------+ IOS XR+------+ | | | | | +-------+ | 10.0.2.0/24 | | 10.0.3.0/24 2001:db8:2::/64 | | 2001:db8:3::/64 | | +---+---+ +---+---+ | PE2 | | PE3 | |OpenBSD+-------------+ Linux | | | | | +---+---+ 10.0.4.0/24 +---+---+ | 2001:db8:4::/64 | + + 172.16.1.2/24 CE2 CE3 172.16.1.3/24 ``` ## Configuration #### Linux 1 - Enable IPv4/v6 forwarding: ``` # sysctl -w net.ipv4.ip_forward=1 # sysctl -w net.ipv6.conf.all.forwarding=1 ``` 2 - Enable MPLS forwarding: ``` # modprobe mpls-router # modprobe mpls-iptunnel # echo 100000 > /proc/sys/net/mpls/platform_labels # echo 1 > /proc/sys/net/mpls/conf/eth1/input # echo 1 > /proc/sys/net/mpls/conf/eth2/input ``` 3 - Set up the interfaces: ``` # ip link add name lo1 type dummy # ip link set dev lo1 up # ip addr add 4.4.4.4/32 dev lo1 # ip -6 addr add 4:4:4::4/128 dev lo1 # ip link set dev eth1 up # ip addr add 10.0.4.4/24 dev eth1 # ip -6 addr add 2001:db8:4::4/64 dev eth1 # ip link set dev eth2 up # ip addr add 10.0.3.4/24 dev eth2 # ip -6 addr add 2001:db8:3::4/64 dev eth2 ``` 4 - Set up the bridge and pseudowire interfaces: ``` # ip link add type bridge # ip link set dev bridge0 up # ip link set dev eth0 up # ip link set dev eth0 master bridge0 # ip link add name mpw0 type dummy # ip link set dev mpw0 up # ip link set dev mpw0 master bridge0 # ip link add name mpw1 type dummy # ip link set dev mpw1 up # ip link set dev mpw1 master bridge0 ``` > NOTE: MPLS support in the Linux kernel is very recent and it still doesn't support pseudowire interfaces. We are using here dummy interfaces just to show how the VPLS configuration should look like in the future. 5 - Add static IPv6 routes for the remote loopbacks: ``` # ip -6 route add 2:2:2::2/128 via 2001:db8:3::2 # ip -6 route add 3:3:3::3/128 via 2001:db8:4::3 ``` 6 - Edit /etc/frr/ospfd.conf: ``` router ospf network 4.4.4.4/32 area 0.0.0.0 network 10.0.3.4/24 area 0.0.0.0 network 10.0.4.4/24 area 0.0.0.0 ! ``` 7 - Edit /etc/frr/ldpd.conf: ``` debug mpls ldp messages recv debug mpls ldp messages sent debug mpls ldp zebra ! mpls ldp router-id 4.4.4.4 dual-stack cisco-interop neighbor 1.1.1.1 password opensourcerouting neighbor 2.2.2.2 password opensourcerouting neighbor 3.3.3.3 password opensourcerouting ! address-family ipv4 discovery transport-address 4.4.4.4 label local advertise explicit-null ! interface eth2 ! interface eth1 ! ! address-family ipv6 discovery transport-address 4:4:4::4 ttl-security disable ! interface eth2 ! interface eth1 ! ! ! l2vpn ENG type vpls bridge br0 member interface eth0 ! member pseudowire mpw0 neighbor lsr-id 1.1.1.1 pw-id 100 ! member pseudowire mpw1 neighbor lsr-id 3.3.3.3 neighbor address 3:3:3::3 pw-id 100 ! ! ``` > NOTE: We have to disable ttl-security under the ipv6 address-family in order to interoperate with the IOS-XR router. GTSM is mandatory for LDPv6 but the IOS-XR implementation is not RFC compliant in this regard. 8 - Run zebra, ospfd and ldpd. #### OpenBSD 1 - Enable IPv4/v6 forwarding: ``` # sysctl net.inet.ip.forwarding=1 # sysctl net.inet6.ip6.forwarding=1 ``` 2 - Enable MPLS forwarding: ``` # ifconfig em2 10.0.2.3/24 mpls # ifconfig em3 10.0.4.3/24 mpls ``` 3 - Set up the interfaces: ``` # ifconfig lo1 alias 3.3.3.3 netmask 255.255.255.255 # ifconfig lo1 inet6 3:3:3::3/128 # ifconfig em2 inet6 2001:db8:2::3/64 # ifconfig em3 inet6 2001:db8:4::3/64 ``` 4 - Set up the bridge and pseudowire interfaces: ``` # ifconfig bridge0 create # ifconfig bridge0 up # ifconfig em1 up # ifconfig bridge0 add em1 # ifconfig mpw0 create # ifconfig mpw0 up # ifconfig bridge0 add mpw0 # ifconfig mpw1 create # ifconfig mpw1 up # ifconfig bridge0 add mpw1 ``` 5 - Add static IPv6 routes for the remote loopbacks: ``` # route -n add 4:4:4::4/128 2001:db8:4::4 # route -n add 2:2:2::2/128 2001:db8:2::2 ``` 6 - Edit /etc/frr/ospfd.conf: ``` router ospf network 10.0.2.3/24 area 0 network 10.0.4.3/24 area 0 network 3.3.3.3/32 area 0 ! ``` 7 - Edit /etc/frr/ldpd.conf: ``` debug mpls ldp messages recv debug mpls ldp messages sent debug mpls ldp zebra ! mpls ldp router-id 3.3.3.3 dual-stack cisco-interop neighbor 1.1.1.1 password opensourcerouting neighbor 2.2.2.2 password opensourcerouting neighbor 4.4.4.4 password opensourcerouting ! address-family ipv4 discovery transport-address 3.3.3.3 label local advertise explicit-null ! interface em3 ! interface em2 ! ! address-family ipv6 discovery transport-address 3:3:3::3 ttl-security disable ! interface em3 ! interface em2 ! ! ! l2vpn ENG type vpls bridge br0 member interface em1 ! member pseudowire mpw0 neighbor lsr-id 1.1.1.1 pw-id 100 ! member pseudowire mpw1 neighbor lsr-id 4.4.4.4 neighbor address 4:4:4::4 pw-id 100 ! ! ``` 8 - Run zebra, ospfd and ldpd. #### Cisco routers CE1 (IOS): ``` interface FastEthernet0/0 ip address 172.16.1.1 255.255.255.0 ! ! ``` CE2 (IOS): ``` interface FastEthernet0/0 ip address 172.16.1.2 255.255.255.0 ! ! ``` CE3 (IOS): ``` interface FastEthernet0/0 ip address 172.16.1.3 255.255.255.0 ! ! ``` PE1 - IOS-XE (1): ``` mpls ldp neighbor 2.2.2.2 password opensourcerouting mpls ldp neighbor 3.3.3.3 password opensourcerouting mpls ldp neighbor 4.4.4.4 password opensourcerouting ! l2vpn vfi context VFI vpn id 1 member pseudowire2 member pseudowire1 ! bridge-domain 1 member GigabitEthernet1 service-instance 1 member vfi VFI ! interface Loopback1 ip address 1.1.1.1 255.255.255.255 ! interface pseudowire1 encapsulation mpls neighbor 3.3.3.3 100 ! interface pseudowire2 encapsulation mpls neighbor 4.4.4.4 100 ! interface GigabitEthernet3 ip address 10.0.1.1 255.255.255.0 mpls ip ! router ospf 1 network 0.0.0.0 255.255.255.255 area 0 ! ``` P - IOS-XR (2): ``` interface Loopback1 ipv4 address 2.2.2.2 255.255.255.255 ipv6 address 2:2:2::2/128 ! interface GigabitEthernet0/0/0/0 ipv4 address 10.0.1.2 255.255.255.0 ! interface GigabitEthernet0/0/0/1 ipv4 address 10.0.2.2 255.255.255.0 ipv6 address 2001:db8:2::2/64 ipv6 enable ! interface GigabitEthernet0/0/0/2 ipv4 address 10.0.3.2 255.255.255.0 ipv6 address 2001:db8:3::2/64 ipv6 enable ! router static address-family ipv6 unicast 3:3:3::3/128 2001:db8:2::3 4:4:4::4/128 2001:db8:3::4 ! ! router ospf 1 router-id 2.2.2.2 address-family ipv4 unicast area 0 interface Loopback1 ! interface GigabitEthernet0/0/0/0 ! interface GigabitEthernet0/0/0/1 ! interface GigabitEthernet0/0/0/2 ! ! ! mpls ldp router-id 2.2.2.2 neighbor 1.1.1.1:0 password clear opensourcerouting 3.3.3.3:0 password clear opensourcerouting 4.4.4.4:0 password clear opensourcerouting ! address-family ipv4 ! address-family ipv6 discovery transport-address 2:2:2::2 ! interface GigabitEthernet0/0/0/0 address-family ipv4 ! ! interface GigabitEthernet0/0/0/1 address-family ipv4 ! address-family ipv6 ! ! interface GigabitEthernet0/0/0/2 address-family ipv4 ! address-family ipv6 ! ! ! ``` ## Verification - Control Plane Using the CLI on the Linux box, the goal is to ensure that everything is working as expected. First, verify that all the required adjacencies and neighborships sessions were established: ``` linux# show mpls ldp discovery Local LDP Identifier: 4.4.4.4:0 Discovery Sources: Interfaces: eth1: xmit/recv LDP Id: 3.3.3.3:0, Transport address: 3.3.3.3 Hold time: 15 sec LDP Id: 3.3.3.3:0, Transport address: 3:3:3::3 Hold time: 15 sec eth2: xmit/recv LDP Id: 2.2.2.2:0, Transport address: 2.2.2.2 Hold time: 15 sec LDP Id: 2.2.2.2:0, Transport address: 2:2:2::2 Hold time: 15 sec Targeted Hellos: 4.4.4.4 -> 1.1.1.1: xmit/recv LDP Id: 1.1.1.1:0, Transport address: 1.1.1.1 Hold time: 45 sec 4:4:4::4 -> 3:3:3::3: xmit/recv LDP Id: 3.3.3.3:0, Transport address: 3:3:3::3 Hold time: 45 sec linux# show mpls ldp neighbor Peer LDP Identifier: 1.1.1.1:0 TCP connection: 4.4.4.4:40921 - 1.1.1.1:646 Session Holdtime: 180 sec State: OPERATIONAL; Downstream-Unsolicited Up time: 00:06:02 LDP Discovery Sources: IPv4: Targeted Hello: 1.1.1.1 Peer LDP Identifier: 2.2.2.2:0 TCP connection: 4:4:4::4:52286 - 2:2:2::2:646 Session Holdtime: 180 sec State: OPERATIONAL; Downstream-Unsolicited Up time: 00:06:02 LDP Discovery Sources: IPv4: Interface: eth2 IPv6: Interface: eth2 Peer LDP Identifier: 3.3.3.3:0 TCP connection: 4:4:4::4:60575 - 3:3:3::3:646 Session Holdtime: 180 sec State: OPERATIONAL; Downstream-Unsolicited Up time: 00:05:57 LDP Discovery Sources: IPv4: Interface: eth1 IPv6: Targeted Hello: 3:3:3::3 Interface: eth1 ``` Note that the neighborships with the P and PE2 routers were established over IPv6, since this is the default behavior for dual-stack LSRs, as specified in RFC 7552. If desired, the **dual-stack transport-connection prefer ipv4** command can be used to establish these sessions over IPv4 (the command should be applied an all routers). Now, verify that there's a remote label for each PE address: ``` linux# show mpls ldp binding 1.1.1.1/32 Local binding: label: 20 Remote bindings: Peer Label ----------------- --------- 1.1.1.1 imp-null 2.2.2.2 24000 3.3.3.3 20 2.2.2.2/32 Local binding: label: 21 Remote bindings: Peer Label ----------------- --------- 1.1.1.1 18 2.2.2.2 imp-null 3.3.3.3 21 3.3.3.3/32 Local binding: label: 22 Remote bindings: Peer Label ----------------- --------- 1.1.1.1 21 2.2.2.2 24003 3.3.3.3 imp-null 4.4.4.4/32 Local binding: label: imp-null Remote bindings: Peer Label ----------------- --------- 1.1.1.1 22 2.2.2.2 24001 3.3.3.3 22 10.0.1.0/24 Local binding: label: 23 Remote bindings: Peer Label ----------------- --------- 1.1.1.1 imp-null 2.2.2.2 imp-null 3.3.3.3 23 10.0.2.0/24 Local binding: label: 24 Remote bindings: Peer Label ----------------- --------- 1.1.1.1 20 2.2.2.2 imp-null 3.3.3.3 imp-null 10.0.3.0/24 Local binding: label: imp-null Remote bindings: Peer Label ----------------- --------- 1.1.1.1 19 2.2.2.2 imp-null 3.3.3.3 24 10.0.4.0/24 Local binding: label: imp-null Remote bindings: Peer Label ----------------- --------- 1.1.1.1 23 2.2.2.2 24002 3.3.3.3 imp-null 2:2:2::2/128 Local binding: label: 18 Remote bindings: Peer Label ----------------- --------- 2.2.2.2 imp-null 3.3.3.3 18 3:3:3::3/128 Local binding: label: 19 Remote bindings: Peer Label ----------------- --------- 2.2.2.2 24007 4:4:4::4/128 Local binding: label: imp-null Remote bindings: Peer Label ----------------- --------- 2.2.2.2 24006 3.3.3.3 19 2001:db8:2::/64 Local binding: label: - Remote bindings: Peer Label ----------------- --------- 2.2.2.2 imp-null 3.3.3.3 imp-null 2001:db8:3::/64 Local binding: label: imp-null Remote bindings: Peer Label ----------------- --------- 2.2.2.2 imp-null 2001:db8:4::/64 Local binding: label: imp-null Remote bindings: Peer Label ----------------- --------- 3.3.3.3 imp-null ``` Check if the pseudowires are up: ``` linux# show l2vpn atom vc Interface Peer ID VC ID Name Status --------- --------------- ---------- ---------------- ---------- mpw1 3.3.3.3 100 ENG UP mpw0 1.1.1.1 100 ENG UP ``` Check the label bindings of the pseudowires: ``` linux# show l2vpn atom binding Destination Address: 1.1.1.1, VC ID: 100 Local Label: 25 Cbit: 1, VC Type: Ethernet, GroupID: 0 MTU: 1500 Remote Label: 16 Cbit: 1, VC Type: Ethernet, GroupID: 0 MTU: 1500 Destination Address: 3.3.3.3, VC ID: 100 Local Label: 26 Cbit: 1, VC Type: Ethernet, GroupID: 0 MTU: 1500 Remote Label: 26 Cbit: 1, VC Type: Ethernet, GroupID: 0 MTU: 1500 ``` ## Verification - Data Plane Verify that all the exchanged label mappings were installed in zebra: ``` linux# show mpls table Inbound Outbound Label Type Nexthop Label -------- ------- --------------- -------- 17 LDP 2001:db8:3::2 3 19 LDP 2001:db8:3::2 24005 20 LDP 10.0.3.2 24000 21 LDP 10.0.3.2 3 22 LDP 10.0.3.2 24001 23 LDP 10.0.3.2 3 24 LDP 10.0.3.2 3 25 LDP 10.0.3.2 3 linux# show ip route ldp Codes: K - kernel route, C - connected, S - static, R - RIP, O - OSPF, I - IS-IS, B - BGP, P - PIM, A - Babel, L - LDP, > - selected route, * - FIB route L>* 1.1.1.1/32 [0/0] via 10.0.3.2, eth2 label 24000 L>* 3.3.3.3/32 [0/0] via 10.0.3.2, eth2 label 24001 ``` Verify that all the exchanged label mappings were installed in the kernel: ``` $ ip -M ro 17 via inet6 2001:db8:3::2 dev eth2 proto zebra 19 as to 24005 via inet6 2001:db8:3::2 dev eth2 proto zebra 20 as to 24000 via inet 10.0.3.2 dev eth2 proto zebra 21 via inet 10.0.3.2 dev eth2 proto zebra 22 as to 24001 via inet 10.0.3.2 dev eth2 proto zebra 23 via inet 10.0.3.2 dev eth2 proto zebra 24 via inet 10.0.3.2 dev eth2 proto zebra 25 via inet 10.0.3.2 dev eth2 proto zebra $ $ ip route | grep mpls 1.1.1.1 encap mpls 24000 via 10.0.3.2 dev eth2 proto zebra metric 20 3.3.3.3 encap mpls 24001 via 10.0.3.2 dev eth2 proto zebra metric 20 ``` Now ping PE1's loopback using lo1's address as a source address: ``` $ ping -c 5 -I 4.4.4.4 1.1.1.1 PING 1.1.1.1 (1.1.1.1) from 4.4.4.4 : 56(84) bytes of data. 64 bytes from 1.1.1.1: icmp_seq=1 ttl=253 time=3.02 ms 64 bytes from 1.1.1.1: icmp_seq=2 ttl=253 time=3.13 ms 64 bytes from 1.1.1.1: icmp_seq=3 ttl=253 time=3.19 ms 64 bytes from 1.1.1.1: icmp_seq=4 ttl=253 time=3.07 ms 64 bytes from 1.1.1.1: icmp_seq=5 ttl=253 time=3.27 ms --- 1.1.1.1 ping statistics --- 5 packets transmitted, 5 received, 0% packet loss, time 4005ms rtt min/avg/max/mdev = 3.022/3.140/3.278/0.096 ms ``` Verify that the ICMP echo request packets are leaving with the MPLS label advertised by the P router. Also, verify that the ICMP echo reply packets are arriving with an explicit-null MPLS label: ``` # tcpdump -n -i eth2 mpls and icmp tcpdump: verbose output suppressed, use -v or -vv for full protocol decode listening on eth2, link-type EN10MB (Ethernet), capture size 262144 bytes 10:01:40.758771 MPLS (label 24000, exp 0, [S], ttl 64) IP 4.4.4.4 > 1.1.1.1: ICMP echo request, id 13370, seq 1, length 64 10:01:40.761777 MPLS (label 0, exp 0, [S], ttl 254) IP 1.1.1.1 > 4.4.4.4: ICMP echo reply, id 13370, seq 1, length 64 10:01:41.760343 MPLS (label 24000, exp 0, [S], ttl 64) IP 4.4.4.4 > 1.1.1.1: ICMP echo request, id 13370, seq 2, length 64 10:01:41.763448 MPLS (label 0, exp 0, [S], ttl 254) IP 1.1.1.1 > 4.4.4.4: ICMP echo reply, id 13370, seq 2, length 64 10:01:42.761758 MPLS (label 24000, exp 0, [S], ttl 64) IP 4.4.4.4 > 1.1.1.1: ICMP echo request, id 13370, seq 3, length 64 10:01:42.764924 MPLS (label 0, exp 0, [S], ttl 254) IP 1.1.1.1 > 4.4.4.4: ICMP echo reply, id 13370, seq 3, length 64 10:01:43.763193 MPLS (label 24000, exp 0, [S], ttl 64) IP 4.4.4.4 > 1.1.1.1: ICMP echo request, id 13370, seq 4, length 64 10:01:43.766237 MPLS (label 0, exp 0, [S], ttl 254) IP 1.1.1.1 > 4.4.4.4: ICMP echo reply, id 13370, seq 4, length 64 10:01:44.764552 MPLS (label 24000, exp 0, [S], ttl 64) IP 4.4.4.4 > 1.1.1.1: ICMP echo request, id 13370, seq 5, length 64 10:01:44.767803 MPLS (label 0, exp 0, [S], ttl 254) IP 1.1.1.1 > 4.4.4.4: ICMP echo reply, id 13370, seq 5, length 64 ``` frr-7.2.1/doc/developer/library.rst0000644000000000000000000000031713610377563014145 00000000000000.. _libfrr: *************************** Library Facilities (libfrr) *************************** .. toctree:: :maxdepth: 2 memtypes rcu lists logging locking hooks cli modules frr-7.2.1/doc/developer/lists.rst0000644000000000000000000005723313610377563013650 00000000000000List implementations ==================== .. note:: The term *list* is used generically for lists, skiplists, trees and hash tables in this document. Common list interface --------------------- FRR includes a set of list-like data structure implementations with abstracted common APIs. The purpose of this is easily allow swapping out one data structure for another while also making the code easier to read and write. There is one API for unsorted lists and a similar but not identical API for sorted lists - and heaps use a middle ground of both. For unsorted lists, the following implementations exist: - single-linked list with tail pointer (e.g. STAILQ in BSD) - double-linked list - atomic single-linked list with tail pointer Being partially sorted, the oddball structure: - an 8-ary heap For sorted lists, these data structures are implemented: - single-linked list - atomic single-linked list - skiplist - red-black tree (based on OpenBSD RB_TREE) - hash table (note below) Except for hash tables, each of the sorted data structures has a variant with unique and non-unique list items. Hash tables always require unique items and mostly follow the "sorted" API but use the hash value as sorting key. Also, iterating while modifying does not work with hash tables. Conversely, the heap always has non-unique items, but iterating while modifying doesn't work either. The following sorted structures are likely to be implemented at some point in the future: - atomic skiplist - atomic hash table (note below) The APIs are all designed to be as type-safe as possible. This means that there will be a compiler warning when an item doesn't match the list, or the return value has a different type, or other similar situations. **You should never use casts with these APIs.** If a cast is neccessary in relation to these APIs, there is probably something wrong with the overall design. Only the following pieces use dynamically allocated memory: - the hash table itself is dynamically grown and shrunk - skiplists store up to 4 next pointers inline but will dynamically allocate memory to hold an item's 5th up to 16th next pointer (if they exist) - the heap uses a dynamically grown and shrunk array of items Cheat sheet ----------- Available types: :: DECLARE_LIST DECLARE_ATOMLIST DECLARE_DLIST DECLARE_HEAP DECLARE_SORTLIST_UNIQ DECLARE_SORTLIST_NONUNIQ DECLARE_ATOMLIST_UNIQ DECLARE_ATOMLIST_NONUNIQ DECLARE_SKIPLIST_UNIQ DECLARE_SKIPLIST_NONUNIQ DECLARE_RBTREE_UNIQ DECLARE_RBTREE_NONUNIQ DECLARE_HASH Functions provided: +------------------------------------+------+------+------+---------+------------+ | Function | LIST | HEAP | HASH | \*_UNIQ | \*_NONUNIQ | +====================================+======+======+======+=========+============+ | _init, _fini | yes | yes | yes | yes | yes | +------------------------------------+------+------+------+---------+------------+ | _first, _next, _next_safe | yes | yes | yes | yes | yes | +------------------------------------+------+------+------+---------+------------+ | _add_head, _add_tail, _add_after | yes | -- | -- | -- | -- | +------------------------------------+------+------+------+---------+------------+ | _add | -- | yes | yes | yes | yes | +------------------------------------+------+------+------+---------+------------+ | _del, _pop | yes | yes | yes | yes | yes | +------------------------------------+------+------+------+---------+------------+ | _find | -- | -- | yes | yes | -- | +------------------------------------+------+------+------+---------+------------+ | _find_lt, _find_gteq | -- | -- | -- | yes | yes | +------------------------------------+------+------+------+---------+------------+ | use with frr_each() macros | yes | yes | yes | yes | yes | +------------------------------------+------+------+------+---------+------------+ Datastructure type setup ------------------------ Each of the data structures has a ``PREDECL_*`` and a ``DECLARE_*`` macro to set up an "instantiation" of the list. This works somewhat similar to C++ templating, though much simpler. **In all following text, the Z prefix is replaced with a name choosen for the instance of the datastructure.** The common setup pattern will look like this: .. code-block:: c #include PREDECL_XXX(Z) struct item { int otherdata; struct Z_item mylistitem; } struct Z_head mylisthead; /* unsorted: */ DECLARE_XXX(Z, struct item, mylistitem) /* sorted, items that compare as equal cannot be added to list */ int compare_func(const struct item *a, const struct item *b); DECLARE_XXX_UNIQ(Z, struct item, mylistitem, compare_func) /* sorted, items that compare as equal can be added to list */ int compare_func(const struct item *a, const struct item *b); DECLARE_XXX_NONUNIQ(Z, struct item, mylistitem, compare_func) /* hash tables: */ int compare_func(const struct item *a, const struct item *b); uint32_t hash_func(const struct item *a); DECLARE_XXX(Z, struct item, mylistitem, compare_func, hash_func) ``XXX`` is replaced with the name of the data structure, e.g. ``SKIPLIST`` or ``ATOMLIST``. The ``DECLARE_XXX`` invocation can either occur in a `.h` file (if the list needs to be accessed from several C files) or it can be placed in a `.c` file (if the list is only accessed from that file.) The ``PREDECL_XXX`` invocation defines the ``struct Z_item`` and ``struct Z_head`` types and must therefore occur before these are used. To switch between compatible data structures, only these two lines need to be changes. To switch to a data structure with a different API, some source changes are necessary. Common iteration macros ----------------------- The following iteration macros work across all data structures: .. c:function:: frr_each(Z, &head, item) Equivalent to: .. code-block:: c for (item = Z_first(&head); item; item = Z_next(&head, item)) Note that this will fail if the list is modified while being iterated over. .. c:function:: frr_each_safe(Z, &head, item) Same as the previous, but the next element is pre-loaded into a "hidden" variable (named ``Z_safe``.) Equivalent to: .. code-block:: c for (item = Z_first(&head); item; item = next) { next = Z_next_safe(&head, item); ... } .. warning:: Iterating over hash tables while adding or removing items is not possible. The iteration position will be corrupted when the hash tables is resized while iterating. This will cause items to be skipped or iterated over twice. .. c:function:: frr_each_from(Z, &head, item, from) Iterates over the list, starting at item ``from``. This variant is "safe" as in the previous macro. Equivalent to: .. code-block:: c for (item = from; item; item = from) { from = Z_next_safe(&head, item); ... } .. note:: The ``from`` variable is written to. This is intentional - you can resume iteration after breaking out of the loop by keeping the ``from`` value persistent and reusing it for the next loop. Common API ---------- The following documentation assumes that a list has been defined using ``Z`` as the name, and ``itemtype`` being the type of the list items (e.g. ``struct item``.) .. c:function:: void Z_init(struct Z_head *) Initializes the list for use. For most implementations, this just sets some values. Hash tables are the only implementation that allocates memory in this call. .. c:function:: void Z_fini(struct Z_head *) Reverse the effects of :c:func:`Z_init()`. The list must be empty when this function is called. .. warning:: This function may ``assert()`` if the list is not empty. .. c:function:: size_t Z_count(struct Z_head *) Returns the number of items in a structure. All structures store a counter in their `Z_head` so that calling this function completes in O(1). .. note:: For atomic lists with concurrent access, the value will already be outdated by the time this function returns and can therefore only be used as an estimate. .. c:function:: itemtype *Z_first(struct Z_head *) Returns the first item in the structure, or ``NULL`` if the structure is empty. This is O(1) for all data structures except red-black trees where it is O(log n). .. c:function:: itemtype *Z_pop(struct Z_head *) Remove and return the first item in the structure, or ``NULL`` if the structure is empty. Like :c:func:`Z_first`, this is O(1) for all data structures except red-black trees where it is O(log n) again. This function can be used to build queues (with unsorted structures) or priority queues (with sorted structures.) Another common pattern is deleting all list items: .. code-block:: c while ((item = Z_pop(head))) item_free(item); .. note:: This function can - and should - be used with hash tables. It is not affected by the "modification while iterating" problem. To remove all items from a hash table, use the loop demonstrated above. .. c:function:: itemtype *Z_next(struct Z_head *, itemtype *prev) Return the item that follows after ``prev``, or ``NULL`` if ``prev`` is the last item. .. warning:: ``prev`` must not be ``NULL``! Use :c:func:`Z_next_safe()` if ``prev`` might be ``NULL``. .. c:function:: itemtype *Z_next_safe(struct Z_head *, itemtype *prev) Same as :c:func:`Z_next()`, except that ``NULL`` is returned if ``prev`` is ``NULL``. .. c:function:: itemtype *Z_del(struct Z_head *, itemtype *item) Remove ``item`` from the list and return it. .. note:: This function's behaviour is undefined if ``item`` is not actually on the list. Some structures return ``NULL`` in this case while others return ``item``. The function may also call ``assert()`` (but most don't.) .. todo:: ``Z_del_after()`` / ``Z_del_hint()``? API for unsorted structures --------------------------- Since the insertion position is not pre-defined for unsorted data, there are several functions exposed to insert data: .. note:: ``item`` must not be ``NULL`` for any of the following functions. .. c:function:: DECLARE_XXX(Z, type, field) :param listtype XXX: ``LIST``, ``DLIST`` or ``ATOMLIST`` to select a data structure implementation. :param token Z: Gives the name prefix that is used for the functions created for this instantiation. ``DECLARE_XXX(foo, ...)`` gives ``struct foo_item``, ``foo_add_head()``, ``foo_count()``, etc. Note that this must match the value given in ``PREDECL_XXX(foo)``. :param typename type: Specifies the data type of the list items, e.g. ``struct item``. Note that ``struct`` must be added here, it is not automatically added. :param token field: References a struct member of ``type`` that must be typed as ``struct foo_item``. This struct member is used to store "next" pointers or other data structure specific data. .. c:function:: void Z_add_head(struct Z_head *, itemtype *item) Insert an item at the beginning of the structure, before the first item. This is an O(1) operation for non-atomic lists. .. c:function:: void Z_add_tail(struct Z_head *, itemtype *item) Insert an item at the end of the structure, after the last item. This is also an O(1) operation for non-atomic lists. .. c:function:: void Z_add_after(struct Z_head *, itemtype *after, itemtype *item) Insert ``item`` behind ``after``. If ``after`` is ``NULL``, the item is inserted at the beginning of the list as with :c:func:`Z_add_head`. This is also an O(1) operation for non-atomic lists. A common pattern is to keep a "previous" pointer around while iterating: .. code-block:: c itemtype *prev = NULL, *item; frr_each_safe(Z, head, item) { if (something) { Z_add_after(head, prev, item); break; } prev = item; } .. todo:: maybe flip the order of ``item`` & ``after``? ``Z_add_after(head, item, after)`` API for sorted structures ------------------------- Sorted data structures do not need to have an insertion position specified, therefore the insertion calls are different from unsorted lists. Also, sorted lists can be searched for a value. .. c:function:: DECLARE_XXX_UNIQ(Z, type, field, compare_func) :param listtype XXX: One of the following: ``SORTLIST`` (single-linked sorted list), ``SKIPLIST`` (skiplist), ``RBTREE`` (RB-tree) or ``ATOMSORT`` (atomic single-linked list). :param token Z: Gives the name prefix that is used for the functions created for this instantiation. ``DECLARE_XXX(foo, ...)`` gives ``struct foo_item``, ``foo_add()``, ``foo_count()``, etc. Note that this must match the value given in ``PREDECL_XXX(foo)``. :param typename type: Specifies the data type of the list items, e.g. ``struct item``. Note that ``struct`` must be added here, it is not automatically added. :param token field: References a struct member of ``type`` that must be typed as ``struct foo_item``. This struct member is used to store "next" pointers or other data structure specific data. :param funcptr compare_func: Item comparison function, must have the following function signature: ``int function(const itemtype *, const itemtype*)``. This function may be static if the list is only used in one file. .. c:function:: DECLARE_XXX_NONUNIQ(Z, type, field, compare_func) Same as above, but allow adding multiple items to the list that compare as equal in ``compare_func``. Ordering between these items is undefined and depends on the list implementation. .. c:function:: itemtype *Z_add(struct Z_head *, itemtype *item) Insert an item at the appropriate sorted position. If another item exists in the list that compares as equal (``compare_func()`` == 0), ``item`` is not inserted into the list and the already-existing item in the list is returned. Otherwise, on successful insertion, ``NULL`` is returned. For ``_NONUNIQ`` lists, this function always returns NULL since ``item`` can always be successfully added to the list. .. c:function:: itemtype *Z_find(struct Z_head *, const itemtype *ref) Search the list for an item that compares equal to ``ref``. If no equal item is found, return ``NULL``. This function is likely used with a temporary stack-allocated value for ``ref`` like so: .. code-block:: c itemtype searchfor = { .foo = 123 }; itemtype *item = Z_find(head, &searchfor); .. note:: The ``Z_find()`` function is only available for lists that contain unique items (i.e. ``DECLARE_XXX_UNIQ``.) This is because on a list containing non-unique items, more than one item may compare as equal to the item that is searched for. .. c:function:: itemtype *Z_find_gteq(struct Z_head *, const itemtype *ref) Search the list for an item that compares greater or equal to ``ref``. See :c:func:`Z_find()` above. .. c:function:: itemtype *Z_find_lt(struct Z_head *, const itemtype *ref) Search the list for an item that compares less than ``ref``. See :c:func:`Z_find()` above. API for hash tables ------------------- .. c:function:: DECLARE_XXX(Z, type, field, compare_func, hash_func) :param listtype XXX: Only ``HASH`` is currently available. :param token Z: Gives the name prefix that is used for the functions created for this instantiation. ``DECLARE_XXX(foo, ...)`` gives ``struct foo_item``, ``foo_add()``, ``foo_count()``, etc. Note that this must match the value given in ``PREDECL_XXX(foo)``. :param typename type: Specifies the data type of the list items, e.g. ``struct item``. Note that ``struct`` must be added here, it is not automatically added. :param token field: References a struct member of ``type`` that must be typed as ``struct foo_item``. This struct member is used to store "next" pointers or other data structure specific data. :param funcptr compare_func: Item comparison function, must have the following function signature: ``int function(const itemtype *, const itemtype*)``. This function may be static if the list is only used in one file. For hash tables, this function is only used to check for equality, the ordering is ignored. :param funcptr hash_func: Hash calculation function, must have the following function signature: ``uint32_t function(const itemtype *)``. The hash value for items stored in a hash table is cached in each item, so this value need not be cached by the user code. .. warning:: Items that compare as equal cannot be inserted. Refer to the notes about sorted structures in the previous section. .. c:function:: void Z_init_size(struct Z_head *, size_t size) Same as :c:func:`Z_init()` but preset the minimum hash table to ``size``. Hash tables also support :c:func:`Z_add()` and :c:func:`Z_find()` with the same semantics as noted above. :c:func:`Z_find_gteq()` and :c:func:`Z_find_lt()` are **not** provided for hash tables. API for heaps ------------- Heaps provide the same API as the sorted data structures, except: * none of the find functions (:c:func:`Z_find()`, :c:func:`Z_find_gteq()` or :c:func:`Z_find_lt()`) are available. * iterating over the heap yields the items in semi-random order, only the first item is guaranteed to be in order and actually the "lowest" item on the heap. Being a heap, only the rebalancing performed on removing the first item (either through :c:func:`Z_pop()` or :c:func:`Z_del()`) causes the new lowest item to bubble up to the front. * all heap modifications are O(log n). However, cacheline efficiency and latency is likely quite a bit better than with other data structures. Atomic lists ------------ `atomlist.h` provides an unsorted and a sorted atomic single-linked list. Since atomic memory accesses can be considerably slower than plain memory accessses (depending on the CPU type), these lists should only be used where neccessary. The following guarantees are provided regarding concurrent access: - the operations are lock-free but not wait-free. Lock-free means that it is impossible for all threads to be blocked. Some thread will always make progress, regardless of what other threads do. (This even includes a random thread being stopped by a debugger in a random location.) Wait-free implies that the time any single thread might spend in one of the calls is bounded. This is not provided here since it is not normally relevant to practical operations. What this means is that if some thread is hammering a particular list with requests, it is possible that another thread is blocked for an extended time. The lock-free guarantee still applies since the hammering thread is making progress. - without a RCU mechanism in place, the point of contention for atomic lists is memory deallocation. As it is, **a rwlock is required for correct operation**. The *read* lock must be held for all accesses, including reading the list, adding items to the list, and removing items from the list. The *write* lock must be acquired and released before deallocating any list element. If this is not followed, an use-after-free can occur as a MT race condition when an element gets deallocated while another thread is accessing the list. .. note:: The *write* lock does not need to be held for deleting items from the list, and there should not be any instructions between the ``pthread_rwlock_wrlock`` and ``pthread_rwlock_unlock``. The write lock is used as a sequence point, not as an exclusion mechanism. - insertion operations are always safe to do with the read lock held. Added items are immediately visible after the insertion call returns and should not be touched anymore. - when removing a *particular* (pre-determined) item, the caller must ensure that no other thread is attempting to remove that same item. If this cannot be guaranteed by architecture, a separate lock might need to be added. - concurrent `pop` calls are always safe to do with only the read lock held. This does not fall under the previous rule since the `pop` call will select the next item if the first is already being removed by another thread. **Deallocation locking still applies.** Assume another thread starts reading the list, but gets task-switched by the kernel while reading the first item. `pop` will happily remove and return that item. If it is deallocated without acquiring and releasing the write lock, the other thread will later resume execution and try to access the now-deleted element. - the list count should be considered an estimate. Since there might be concurrent insertions or removals in progress, it might already be outdated by the time the call returns. No attempt is made to have it be correct even for a nanosecond. Overall, atomic lists are well-suited for MT queues; concurrent insertion, iteration and removal operations will work with the read lock held. Code snippets ^^^^^^^^^^^^^ Iteration: .. code-block:: c struct item *i; pthread_rwlock_rdlock(&itemhead_rwlock); frr_each(itemlist, &itemhead, i) { /* lock must remain held while iterating */ ... } pthread_rwlock_unlock(&itemhead_rwlock); Head removal (pop) and deallocation: .. code-block:: c struct item *i; pthread_rwlock_rdlock(&itemhead_rwlock); i = itemlist_pop(&itemhead); pthread_rwlock_unlock(&itemhead_rwlock); /* i might still be visible for another thread doing an * frr_each() (but won't be returned by another pop()) */ ... pthread_rwlock_wrlock(&itemhead_rwlock); pthread_rwlock_unlock(&itemhead_rwlock); /* i now guaranteed to be gone from the list. * note nothing between wrlock() and unlock() */ XFREE(MTYPE_ITEM, i); FAQ --- Why is the list head not ``const`` in the list APIs? The semantics that a ``const`` list head would imply are not obvious. It could mean any of the following: * the list just shouldn't be allocated/deallocated, but may be modified. This doesn't actually work since the list head needs to be modified for inserting or deleting items. * the list shouldn't be modified, but items can. This may make sense for iterating, but it's not exactly consistent - an item might be on more than one list, does it apply to all of them? If not, which one? * neither the list nor the items should be modified. This is consistent, but hard to do without creating a ``const`` copy of every single list function. Ease of use trumps this. Why is there no "is this item on a/the list" test? It's slow for several of the data structures, and the work of adding it just hasn't been done. It can certainly be added if it's needed. Why is it ``PREDECL`` + ``DECLARE`` instead of ``DECLARE`` + ``DEFINE``? The rule is that a ``DEFINE`` must be in a ``.c`` file, and linked exactly once because it defines some kind of global symbol. This is not the case for the data structure macros; they only define ``static`` symbols and it is perfectly fine to include both ``PREDECL`` and ``DECLARE`` in a header file. It is also perfectly fine to have the same ``DECLARE`` statement in 2 ``.c`` files, but only **if the macro arguments are identical.** Maybe don't do that unless you really need it. FRR lists --------- .. TODO:: document BSD lists --------- .. TODO:: refer to external docs frr-7.2.1/doc/developer/locking.rst0000644000000000000000000000431113610377563014125 00000000000000Locking ======= FRR ships two small wrappers around ``pthread_mutex_lock()`` / ``pthread_mutex_unlock``. Use ``#include "frr_pthread.h"`` to get these macros. .. c:function:: frr_with_mutex(pthread_mutex_t *mutex) Begin a C statement block that is executed with the mutex locked. Any exit from the block (``break``, ``return``, ``goto``, end of block) will cause the mutex to be unlocked:: int somefunction(int option) { frr_with_mutex(&my_mutex) { /* mutex will be locked */ if (!option) /* mutex will be unlocked before return */ return -1; if (something(option)) /* mutex will be unlocked before goto */ goto out_err; somethingelse(); /* mutex will be unlocked at end of block */ } return 0; out_err: somecleanup(); return -1; } This is a macro that internally uses a ``for`` loop. It is explicitly acceptable to use ``break`` to get out of the block. Even though a single statement works correctly, FRR coding style requires that this macro always be used with a ``{ ... }`` block. .. c:function:: frr_mutex_lock_autounlock(pthread_mutex_t *mutex) Lock mutex and unlock at the end of the current C statement block:: int somefunction(int option) { frr_mutex_lock_autounlock(&my_mutex); /* mutex will be locked */ ... if (error) /* mutex will be unlocked before return */ return -1; ... /* mutex will be unlocked before return */ return 0; } This is a macro that internally creates a variable with a destructor. When the variable goes out of scope (i.e. the block ends), the mutex is released. .. warning:: This macro should only used when :c:func:`frr_with_mutex` would result in excessively/weirdly nested code. This generally is an indicator that the code might be trying to do too many things with the lock held. Try any possible venues to reduce the amount of code covered by the lock and move to :c:func:`frr_with_mutex`. frr-7.2.1/doc/developer/logging.rst0000644000000000000000000002606413610377563014136 00000000000000Developer's Guide to Logging ============================ One of the most frequent decisions to make while writing code for FRR is what to log, what level to log it at, and when to log it. Here is a list of recommendations for these decisions. printfrr() ---------- ``printfrr()`` is FRR's modified version of ``printf()``, designed to make life easier when printing nontrivial datastructures. The following variants are available: .. c:function:: ssize_t snprintfrr(char *buf, size_t len, const char *fmt, ...) .. c:function:: ssize_t vsnprintfrr(char *buf, size_t len, const char *fmt, va_list) These correspond to ``snprintf``/``vsnprintf``. If you pass NULL for buf or 0 for len, no output is written but the return value is still calculated. The return value is always the full length of the output, unconstrained by `len`. It does **not** include the terminating ``\0`` character. A malformed format string can result in a ``-1`` return value. .. c:function:: ssize_t csnprintfrr(char *buf, size_t len, const char *fmt, ...) .. c:function:: ssize_t vcsnprintfrr(char *buf, size_t len, const char *fmt, va_list) Same as above, but the ``c`` stands for "continue" or "concatenate". The output is appended to the string instead of overwriting it. .. c:function:: char *asprintfrr(struct memtype *mt, const char *fmt, ...) .. c:function:: char *vasprintfrr(struct memtype *mt, const char *fmt, va_list) These functions allocate a dynamic buffer (using MTYPE `mt`) and print to that. If the format string is malformed, they return a copy of the format string, so the return value is always non-NULL and always dynamically allocated with `mt`. .. c:function:: char *asnprintfrr(struct memtype *mt, char *buf, size_t len, const char *fmt, ...) .. c:function:: char *vasnprintfrr(struct memtype *mt, char *buf, size_t len, const char *fmt, va_list) This variant tries to use the static buffer provided, but falls back to dynamic allocation if it is insufficient. The return value can be either `buf` or a newly allocated string using `mt`. You MUST free it like this:: char *ret = asnprintfrr(MTYPE_FOO, buf, sizeof(buf), ...); if (ret != buf) XFREE(MTYPE_FOO, ret); Extensions ^^^^^^^^^^ ``printfrr()`` format strings can be extended with suffixes after `%p` or `%d`. The following extended format specifiers are available: +-----------+--------------------------+----------------------------------------------+ | Specifier | Argument | Output | +===========+==========================+==============================================+ | ``%Lu`` | ``uint64_t`` | ``12345`` | +-----------+--------------------------+----------------------------------------------+ | ``%Ld`` | ``int64_t`` | ``-12345`` | +-----------+--------------------------+----------------------------------------------+ | ``%pI4`` | ``struct in_addr *`` | ``1.2.3.4`` | | | | | | | ``in_addr_t *`` | | +-----------+--------------------------+----------------------------------------------+ | ``%pI6`` | ``struct in6_addr *`` | ``fe80::1234`` | +-----------+--------------------------+----------------------------------------------+ | ``%pFX`` | ``struct prefix *`` | ``fe80::1234/64`` | +-----------+--------------------------+----------------------------------------------+ | ``%pSG4`` | ``struct prefix_sg *`` | ``(*,1.2.3.4)`` | +-----------+--------------------------+----------------------------------------------+ | ``%pRN`` | ``struct route_node *`` | ``192.168.1.0/24`` (dst-only node) | | | | | | | | ``2001:db8::/32 from fe80::/64`` (SADR node) | +-----------+--------------------------+----------------------------------------------+ | ``%pNHv`` | ``struct nexthop *`` | ``1.2.3.4, via eth0`` | +-----------+--------------------------+----------------------------------------------+ | ``%pNHs`` | ``struct nexthop *`` | ``1.2.3.4 if 15`` | +-----------+--------------------------+----------------------------------------------+ Printf features like field lengths can be used normally with these extensions, e.g. ``%-15pI4`` works correctly. The extension specifier after ``%p`` or ``%d`` is always an uppercase letter; by means of established pattern uppercase letters and numbers form the type identifier which may be followed by lowercase flags. You can grep the FRR source for ``printfrr_ext_autoreg`` to see all extended printers and what exactly they do. More printers are likely to be added as needed/useful, so the list above may become outdated. ``%Ld`` is not an "extension" for printfrr; it's wired directly into the main printf logic. .. note:: The ``zlog_*``/``flog_*`` and ``vty_out`` functions all use printfrr internally, so these extensions are available there. However, they are **not** available when calling ``snprintf`` directly. You need to call ``snprintfrr`` instead. AS-Safety ^^^^^^^^^ ``printfrr()`` are AS-Safe under the following conditions: * the ``[v]as[n]printfrr`` variants are not AS-Safe (allocating memory) * floating point specifiers are not AS-Safe (system printf is used for these) * the positional ``%1$d`` syntax should not be used (8 arguments are supported while AS-Safe) * extensions are only AS-Safe if their printer is AS-Safe Errors and warnings ------------------- If it is something that the user will want to look at and maybe do something, it is either an **error** or a **warning**. We're expecting that warnings and errors are in some way visible to the user (in the worst case by looking at the log after the network broke, but maybe by a syslog collector from all routers.) Therefore, anything that needs to get the user in the loop—and only these things—are warnings or errors. Note that this doesn't necessarily mean the user needs to fix something in the FRR instance. It also includes when we detect something else needs fixing, for example another router, the system we're running on, or the configuration. The common point is that the user should probably do *something*. Deciding between a warning and an error is slightly less obvious; the rule of thumb here is that an error will cause considerable fallout beyond its direct effect. Closing a BGP session due to a malformed update is an error since all routes from the peer are dropped; discarding one route because its attributes don't make sense is a warning. This also loosely corresponds to the kind of reaction we're expecting from the user. An error is likely to need immediate response while a warning might be snoozed for a bit and addressed as part of general maintenance. If a problem will self-repair (e.g. by retransmits), it should be a warning—unless the impact until that self-repair is very harsh. Examples for warnings: * a BGP update, LSA or LSP could not be processed, but operation is proceeding and the broken pieces are likely to self-fix later * some kind of controller cannot be reached, but we can work without it * another router is using some unknown or unsupported capability Examples for errors: * dropping a BGP session due to malformed data * a socket for routing protocol operation cannot be opened * desynchronization from network state because something went wrong * *everything that we as developers would really like to be notified about, i.e. some assumption in the code isn't holding up* Informational messages ---------------------- Anything that provides introspection to the user during normal operation is an **info** message. This includes all kinds of operational state transitions and events, especially if they might be interesting to the user during the course of figuring out a warning or an error. By itself, these messages should mostly be statements of fact. They might indicate the order and relationship in which things happened. Also covered are conditions that might be "operational issues" like a link failure due to an unplugged cable. If it's pretty much the point of running a routing daemon for, it's not a warning or an error, just business as usual. The user should be able to see the state of these bits from operational state output, i.e. `show interface` or `show foobar neighbors`. The log message indicating the change may have been printed weeks ago, but the state can always be viewed. (If some state change has an info message but no "show" command, maybe that command needs to be added.) Examples: * all kinds of up/down state changes * interface coming up or going down * addresses being added or deleted * peers and neighbors coming up or going down * rejection of some routes due to user-configured route maps * backwards compatibility handling because another system on the network has a different or smaller feature set .. note:: The previously used **notify** priority is replaced with *info* in all cases. We don't currently have a well-defined use case for it. Debug messages and asserts -------------------------- Everything that is only interesting on-demand, or only while developing, is a **debug** message. It might be interesting to the user for a particularly evasive issue, but in general these are details that an average user might not even be able to make sense of. Most (or all?) debug messages should be behind a `debug foobar` category switch that controls which subset of these messages is currently interesting and thus printed. If a debug message doesn't have such a guard, there should be a good explanation as to why. Conversely, debug messages are the only thing that should be guarded by these switches. Neither info nor warning or error messages should be hidden in this way. **Asserts** should only be used as pretty crashes. We are expecting that asserts remain enabled in production builds, but please try to not use asserts in a way that would cause a security problem if the assert wasn't there (i.e. don't use them for length checks.) The purpose of asserts is mainly to help development and bug hunting. If the daemon crashes, then having some more information is nice, and the assert can provide crucial hints that cut down on the time needed to track an issue. That said, if the issue can be reasonably handled and/or isn't going to crash the daemon, it shouldn't be an assert. For anything else where internal constraints are violated but we're not breaking due to it, it's an error instead (not a debug.) These require "user action" of notifying the developers. Examples: * mismatched :code:`prev`/:code:`next` pointers in lists * some field that is absolutely needed is :code:`NULL` * any other kind of data structure corruption that will cause the daemon to crash sooner or later, one way or another frr-7.2.1/doc/developer/maintainer-release-build.rst0000644000000000000000000000467213610377563017353 00000000000000Release Build Procedure for FRR Maintainers =========================================== 1. Rename branch (if needed) .. code-block:: shell git clone git@github.com:FRRouting/frr.git cd frr git checkout dev/5.0 git push origin :refs/heads/dev/5.0 git push origin dev/5.0:refs/heads/stable/5.0 2. Checkout the new stable branch: .. code-block:: shell git checkout stable/5.0 3. Update Changelog for RedHat Package: Edit :file:`redhat/frr.spec.in` and look for the ``%changelog`` section: - Change last (top of list) entry from ``%{version}`` to previous fixed version number, i.e.:: * Tue Nov 7 2017 Martin Winter - %{version} to:: * Tue Nov 7 2017 Martin Winter - 3.0.2 - Add new entry to the top of the list with ``%{version}`` tag and changelog for version. Make sure to watch the format, i.e. the day is always 2 characters, with the 1st character being a space if the day is one digit. 4. Update Changelog for Debian Packages: Edit :file:`debian/changelog-auto.in`: - Change last (top of list) entry from ``@VERSION@`` to previous fixed version number, i.e.:: frr (@VERSION@) RELEASED; urgency=medium to:: frr (3.0.2) RELEASED; urgency=medium - Add a new entry to the top of the list with a ``@VERSION@`` tag and changelog for version. 5. Change main version number: - Edit :file:`configure.ac` and change version in the ``AC_INIT`` command - Create a new entry with the version as ``%{version}`` tag 6. Test building at least a Red Hat and Ubuntu package (or create a PR to have the CI system test them) 7. Commit the changes, adding the changelog to the commit message 8. Create a git tag for the version: .. code-block:: shell git tag -a frr-5.0 -m "FRRouting Release 5.0" 9. Push the commit and tag(s) and watch for errors on CI: .. code-block:: shell git push git push --tags 10. Kick off the Release build plan on the CI system for the correct release 11. Send a Release Announcement with changes to ``announce@lists.frrouting.org`` 12. Kick off the Snapcraft build plan for the correct release 13. After CI plans succeed, release on GitHub by going to https://github.com/FRRouting/frr/releases and selecting "Draft a new release". 14. Deploy Snapcraft release (after CI system finishes the tests for snapcraft testplan) frr-7.2.1/doc/developer/memtypes.rst0000644000000000000000000001024313610377563014343 00000000000000.. highlight:: c Memtypes ======== FRR includes wrappers around ``malloc()`` and ``free()`` that count the number of objects currently allocated, for each of a defined ``MTYPE``. To this extent, there are *memory groups* and *memory types*. Each memory type must belong to a memory group, this is used just to provide some basic structure. Example: .. code-block:: c :caption: mydaemon.h DECLARE_MGROUP(MYDAEMON) DECLARE_MTYPE(MYNEIGHBOR) .. code-block:: c :caption: mydaemon.c DEFINE_MGROUP( MYDAEMON, "My daemon's memory") DEFINE_MTYPE( MYDAEMON, MYNEIGHBOR, "Neighbor entry") DEFINE_MTYPE_STATIC(MYDAEMON, MYNEIGHBORNAME, "Neighbor name") struct neigh *neighbor_new(const char *name) { struct neigh *n = XMALLOC(MYNEIGHBOR, sizeof(*n)); n->name = XSTRDUP(MYNEIGHBORNAME, name); return n; } void neighbor_free(struct neigh *n) { XFREE(MYNEIGHBORNAME, n->name); XFREE(MYNEIGHBOR, n); } Definition ---------- .. c:type:: struct memtype This is the (internal) type used for MTYPE definitions. The macros below should be used to create these, but in some cases it is useful to pass a ``struct memtype *`` pointer to some helper function. The ``MTYPE_name`` created by the macros is declared as an array, i.e. a function taking a ``struct memtype *`` argument can be called with an ``MTYPE_name`` argument (as opposed to ``&MTYPE_name``.) .. c:macro:: DECLARE_MGROUP(name) This macro forward-declares a memory group and should be placed in a ``.h`` file. It expands to an ``extern struct memgroup`` statement. .. c:macro:: DEFINE_MGROUP(mname, description) Defines/implements a memory group. Must be placed into exactly one ``.c`` file (multiple inclusion will result in a link-time symbol conflict). Contains additional logic (constructor and destructor) to register the memory group in a global list. .. c:macro:: DECLARE_MTYPE(name) Forward-declares a memory type and makes ``MTYPE_name`` available for use. Note that the ``MTYPE_`` prefix must not be included in the name, it is automatically prefixed. ``MTYPE_name`` is created as a `static const` symbol, i.e. a compile-time constant. It refers to an ``extern struct memtype _mt_name``, where `name` is replaced with the actual name. .. c:macro:: DEFINE_MTYPE(group, name, description) Define/implement a memory type, must be placed into exactly one ``.c`` file (multiple inclusion will result in a link-time symbol conflict). Like ``DEFINE_MGROUP``, this contains actual code to register the MTYPE under its group. .. c:macro:: DEFINE_MTYPE_STATIC(group, name, description) Same as ``DEFINE_MTYPE``, but the ``DEFINE_MTYPE_STATIC`` variant places the C ``static`` keyword on the definition, restricting the MTYPE's availability to the current source file. This should be appropriate in >80% of cases. .. todo:: Daemons currently have ``daemon_memory.[ch]`` files listing all of their MTYPEs. This is not how it should be, most of these types should be moved into the appropriate files where they are used. Only a few MTYPEs should remain non-static after that. Usage ----- .. c:function:: void *XMALLOC(struct memtype *mtype, size_t size) .. c:function:: void *XCALLOC(struct memtype *mtype, size_t size) .. c:function:: void *XSTRDUP(struct memtype *mtype, const char *name) Allocation wrappers for malloc/calloc/realloc/strdup, taking an extra mtype parameter. .. c:function:: void *XREALLOC(struct memtype *mtype, void *ptr, size_t size) Wrapper around realloc() with MTYPE tracking. Note that ``ptr`` may be NULL, in which case the function does the same as XMALLOC (regardless of whether the system realloc() supports this.) .. c:function:: void XFREE(struct memtype *mtype, void *ptr) Wrapper around free(), again taking an extra mtype parameter. This is actually a macro, with the following additional properties: - the macro contains ``ptr = NULL`` - if ptr is NULL, no operation is performed (as is guaranteed by system implementations.) Do not surround XFREE with ``if (ptr != NULL)`` checks. frr-7.2.1/doc/developer/modules.rst0000644000000000000000000001227013610377563014152 00000000000000Modules ======= FRR has facilities to load DSOs at startup via ``dlopen()``. These are used to implement modules, such as SNMP and FPM. Limitations ----------- - can't load, unload, or reload during runtime. This just needs some work and can probably be done in the future. - doesn't fix any of the "things need to be changed in the code in the library" issues. Most prominently, you can't add a CLI node because CLI nodes are listed in the library... - if your module crashes, the daemon crashes. Should be obvious. - **does not provide a stable API or ABI**. Your module must match a version of FRR and you may have to update it frequently to match changes. - **does not create a license boundary**. Your module will need to link libzebra and include header files from the daemons, meaning it will be GPL-encumbered. Installation ------------ Look for ``moduledir`` in ``configure.ac``, default is normally ``/usr/lib64/frr/modules`` but depends on ``--libdir`` / ``--prefix``. The daemon's name is prepended when looking for a module, e.g. "snmp" tries to find "zebra\_snmp" first when used in zebra. This is just to make it nicer for the user, with the snmp module having the same name everywhere. Modules can be packaged separately from FRR. The SNMP and FPM modules are good candidates for this because they have dependencies (net-snmp / protobuf) that are not FRR dependencies. However, any distro packages should have an "exact-match" dependency onto the FRR package. Using a module from a different FRR version will probably blow up nicely. For snapcraft (and during development), modules can be loaded with full path (e.g. -M ``$SNAP/lib/frr/modules/zebra_snmp.so``). Note that libtool puts output files in the .libs directory, so during development you have to use ``./zebra -M .libs/zebra_snmp.so``. Creating a module ----------------- ... best to look at the existing SNMP or FPM modules. Basic boilerplate: :: #include "hook.h" #include "module.h" #include "libfrr.h" #include "thread.h" static int module_late_init(struct thread_master *master) { /* Do initialization stuff here */ return 0; } static int module_init (void) { hook_register(frr_late_init, module_late_init); return 0; } FRR_MODULE_SETUP( .name = "my module", .version = "0.0", .description = "my module", .init = module_init, ) The ``frr_late_init`` hook will be called after the daemon has finished its other startup and is about to enter the main event loop; this is the best place for most initialisation. Compiler & Linker magic ----------------------- There's a ``THIS_MODULE`` (like in the Linux kernel), which uses ``visibility`` attributes to restrict it to the current module. If you get a linker error with ``_frrmod_this_module``, there is some linker SNAFU. This shouldn't be possible, though one way to get it would be to not include libzebra (which provides a fallback definition for the symbol). libzebra and the daemons each have their own ``THIS_MODULE``, as do all loadable modules. In any other libraries (e.g. ``libfrrsnmp``), ``THIS_MODULE`` will use the definition in libzebra; same applies if the main executable doesn't use ``FRR_DAEMON_INFO`` (e.g. all testcases). The deciding factor here is "what dynamic linker unit are you using the symbol from." If you're in a library function and want to know who called you, you can't use ``THIS_MODULE`` (because that'll just tell you you're in the library). Put a macro around your function that adds ``THIS_MODULE`` in the *caller's code calling your function*. The idea is to use this in the future for module unloading. Hooks already remember which module they were installed by, as groundwork for a function that removes all of a module's installed hooks. There's also the ``frr_module`` symbol in modules, pretty much a standard entry point for loadable modules. Command line parameters ----------------------- Command line parameters can be passed directly to a module by appending a colon to the module name when loading it, e.g. ``-M mymodule:myparameter``. The text after the colon will be accessible in the module's code through ``THIS_MODULE->load_args``. For example, see how the format parameter is configured in the ``zfpm_init()`` function inside ``zebra_fpm.c``. Hooks ----- Hooks are just points in the code where you can register your callback to be called. The parameter list is specific to the hook point. Since there is no stable API, the hook code has some extra type safety checks making sure you get a compiler warning when the hook parameter list doesn't match your callback. Don't ignore these warnings. Relation to MTYPE macros ------------------------ The MTYPE macros, while primarily designed to decouple MTYPEs from the library and beautify the code, also work very nicely with loadable modules -- both constructors and destructors are executed when loading/unloading modules. This means there is absolutely no change required to MTYPEs, you can just use them in a module and they will even clean up themselves when we implement module unloading and an unload happens. In fact, it's impossible to create a bug where unloading fails to de-register a MTYPE. frr-7.2.1/doc/developer/next-hop-tracking.rst0000644000000000000000000003370713610377563016054 00000000000000Next Hop Tracking ================== Next hop tracking is an optimization feature that reduces the processing time involved in the BGP bestpath algorithm by monitoring changes to the routing table. Background ----------- Recursive routes are of the form: :: p/m --> n [Ex: 1.1.0.0/16 --> 2.2.2.2] where 'n' itself is resolved through another route as follows: :: p2/m --> h, interface [Ex: 2.2.2.0/24 --> 3.3.3.3, eth0] Usually, BGP routes are recursive in nature and BGP nexthops get resolved through an IGP route. IGP usually adds its routes pointing to an interface (these are called non-recursive routes). When BGP receives a recursive route from a peer, it needs to validate the nexthop. The path is marked valid or invalid based on the reachability status of the nexthop. Nexthop validation is also important for BGP decision process as the metric to reach the nexthop is a parameter to best path selection process. As it goes with routing, this is a dynamic process. Route to the nexthop can change. The nexthop can become unreachable or reachable. In the current BGP implementation, the nexthop validation is done periodically in the scanner run. The default scanner run interval is one minute. Every minute, the scanner task walks the entire BGP table. It checks the validity of each nexthop with Zebra (the routing table manager) through a request and response message exchange between BGP and Zebra process. BGP process is blocked for that duration. The mechanism has two major drawbacks: - The scanner task runs to completion. That can potentially starve the other tasks for long periods of time, based on the BGP table size and number of nexthops. - Convergence around routing changes that affect the nexthops can be long (around a minute with the default intervals). The interval can be shortened to achieve faster reaction time, but it makes the first problem worse, with the scanner task consuming most of the CPU resources. The next-hop tracking feature makes this process event-driven. It eliminates periodic nexthop validation and introduces an asynchronous communication path between BGP and Zebra for route change notifications that can then be acted upon. Goal ---- Stating the obvious, the main goal is to remove the two limitations we discussed in the previous section. The goals, in a constructive tone, are the following: - **Fairness**: the scanner run should not consume an unjustly high amount of CPU time. This should give an overall good performance and response time to other events (route changes, session events, IO/user interface). - **Convergence**: BGP must react to nexthop changes instantly and provide sub-second convergence. This may involve diverting the routes from one nexthop to another. Overview of changes ------------------------ The changes are in both BGP and Zebra modules. The short summary is the following: - Zebra implements a registration mechanism by which clients can register for next hop notification. Consequently, it maintains a separate table, per (VRF, AF) pair, of next hops and interested client-list per next hop. - When the main routing table changes in Zebra, it evaluates the next hop table: for each next hop, it checks if the route table modifications have changed its state. If so, it notifies the interested clients. - BGP is one such client. It registers the next hops corresponding to all of its received routes/paths. It also threads the paths against each nexthop structure. - When BGP receives a next hop notification from Zebra, it walks the corresponding path list. It makes them valid or invalid depending on the next hop notification. It then re-computes best path for the corresponding destination. This may result in re-announcing those destinations to peers. Design ------ Modules ^^^^^^^ The core design introduces an "nht" (next hop tracking) module in BGP and "rnh" (recursive nexthop) module in Zebra. The "nht" module provides the following APIs: +----------------------------+--------------------------------------------------+ | Function | Action | +============================+==================================================+ | bgp_find_or_add_nexthop() | find or add a nexthop in BGP nexthop table | +----------------------------+--------------------------------------------------+ | bgp_find_nexthop() | find a nexthop in BGP nexthop table | +----------------------------+--------------------------------------------------+ | bgp_parse_nexthop_update() | parse a nexthop update message coming from zebra | +----------------------------+--------------------------------------------------+ The "rnh" module provides the following APIs: +----------------------------+----------------------------------------------------------------------------------------------------------+ | Function | Action | +============================+==========================================================================================================+ | zebra_add_rnh() | add a recursive nexthop | +----------------------------+----------------------------------------------------------------------------------------------------------+ | zebra_delete_rnh() | delete a recursive nexthop | +----------------------------+----------------------------------------------------------------------------------------------------------+ | zebra_lookup_rnh() | lookup a recursive nexthop | +----------------------------+----------------------------------------------------------------------------------------------------------+ | zebra_add_rnh_client() | register a client for nexthop notifications against a recursive nexthop | +----------------------------+----------------------------------------------------------------------------------------------------------+ | zebra_remove_rnh_client() | remove the client registration for a recursive nexthop | +----------------------------+----------------------------------------------------------------------------------------------------------+ | zebra_evaluate_rnh_table() | (re)evaluate the recursive nexthop table (most probably because the main routing table has changed). | +----------------------------+----------------------------------------------------------------------------------------------------------+ | zebra_cleanup_rnh_client() | Cleanup a client from the "rnh" module data structures (most probably because the client is going away). | +----------------------------+----------------------------------------------------------------------------------------------------------+ 4.2. Control flow The next hop registration control flow is the following: :: <==== BGP Process ====>|<==== Zebra Process ====> | receive module nht module | zserv module rnh module ---------------------------------------------------------------------- | | | bgp_update_ | | | main() | bgp_find_or_add_ | | | nexthop() | | | | | | | zserv_nexthop_ | | | register() | | | | zebra_add_rnh() | | | The next hop notification control flow is the following: :: <==== Zebra Process ====>|<==== BGP Process ====> | rib module rnh module | zebra module nht module ---------------------------------------------------------------------- | | | meta_queue_ | | | process() | zebra_evaluate_ | | | rnh_table() | | | | | | | bgp_read_nexthop_ | | | update() | | | | bgp_parse_ | | | nexthop_update() | | | zclient message format ^^^^^^^^^^^^^^^^^^^^^^ ZEBRA_NEXTHOP_REGISTER and ZEBRA_NEXTHOP_UNREGISTER messages are encoded in the following way: :: . 0 1 2 3 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ | AF | prefix len | +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ . Nexthop prefix . . . +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ . . . . +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ | AF | prefix len | +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ . Nexthop prefix . . . +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ ``ZEBRA_NEXTHOP_UPDATE`` message is encoded as follows: :: . 0 1 2 3 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ | AF | prefix len | +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ . Nexthop prefix getting resolved . . . +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ | metric | +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ | #nexthops | +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ | nexthop type | +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ . resolving Nexthop details . . . +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ . . +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ | nexthop type | +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ . resolving Nexthop details . +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ BGP data structure ^^^^^^^^^^^^^^^^^^ Legend: :: /\ struct bgp_node: a BGP destination/route/prefix \/ [ ] struct bgp_path_info: a BGP path (e.g. route received from a peer) _ (_) struct bgp_nexthop_cache: a BGP nexthop /\ NULL \/--+ ^ | : +--[ ]--[ ]--[ ]--> NULL /\ : \/--+ : | : +--[ ]--[ ]--> NULL : _ : (_)........... Zebra data structure ^^^^^^^^^^^^^^^^^^^^ RNH table:: . O / \ O O / \ O O struct rnh { uint8_t flags; struct route_entry *state; struct list *client_list; struct route_node *node; }; User interface changes ^^^^^^^^^^^^^^^^^^^^^^ :: frr# show ip nht 3.3.3.3 resolved via kernel via 11.0.0.6, swp1 Client list: bgp(fd 12) 11.0.0.10 resolved via connected is directly connected, swp2 Client list: bgp(fd 12) 11.0.0.18 resolved via connected is directly connected, swp4 Client list: bgp(fd 12) 11.11.11.11 resolved via kernel via 10.0.1.2, eth0 Client list: bgp(fd 12) frr# show ip bgp nexthop Current BGP nexthop cache: 3.3.3.3 valid [IGP metric 0], #paths 3 Last update: Wed Oct 16 04:43:49 2013 11.0.0.10 valid [IGP metric 1], #paths 1 Last update: Wed Oct 16 04:43:51 2013 11.0.0.18 valid [IGP metric 1], #paths 2 Last update: Wed Oct 16 04:43:47 2013 11.11.11.11 valid [IGP metric 0], #paths 1 Last update: Wed Oct 16 04:43:47 2013 frr# show ipv6 nht frr# show ip bgp nexthop detail frr# debug bgp nht frr# debug zebra nht 6. Sample test cases r2----r3 / \ / r1----r4 - Verify that a change in IGP cost triggers NHT + shutdown the r1-r4 and r2-r4 links + no shut the r1-r4 and r2-r4 links and wait for OSPF to come back up + We should be back to the original nexthop via r4 now - Verify that a NH becoming unreachable triggers NHT + Shutdown all links to r4 - Verify that a NH becoming reachable triggers NHT + no shut all links to r4 Future work ^^^^^^^^^^^ - route-policy for next hop validation (e.g. ignore default route) - damping for rapid next hop changes - prioritized handling of nexthop changes ((un)reachability vs. metric changes) - handling recursion loop, e.g:: 11.11.11.11/32 -> 12.12.12.12 12.12.12.12/32 -> 11.11.11.11 11.0.0.0/8 -> - better statistics frr-7.2.1/doc/developer/ospf-api.rst0000644000000000000000000003316113610377563014222 00000000000000OSPF API Documentation ====================== Disclaimer ---------- The OSPF daemon contains an API for application access to the LSA database. This API and documentation was created by Ralph Keller, originally as patch for Zebra. Unfortunately, the page containing documentation for the API is no longer online. This page is an attempt to recreate documentation for the API (with lots of help from the WayBackMachine). Ralph has kindly licensed this documentation under GPLv2+. Please preserve the acknowledgements at the bottom of this document. Introduction ------------ This page describes an API that allows external applications to access the link-state database (LSDB) of the OSPF daemon. The implementation is based on the OSPF code from FRRouting (forked from Quagga and formerly Zebra) routing protocol suite and is subject to the GNU General Public License. The OSPF API provides you with the following functionality: - Retrieval of the full or partial link-state database of the OSPF daemon. This allows applications to obtain an exact copy of the LSDB including router LSAs, network LSAs and so on. Whenever a new LSA arrives at the OSPF daemon, the API module immediately informs the application by sending a message. This way, the application is always synchronized with the LSDB of the OSPF daemon. - Origination of own opaque LSAs (of type 9, 10, or 11) which are then distributed transparently to other routers within the flooding scope and received by other applications through the OSPF API. Opaque LSAs, which are described in :rfc:`2370`, allow you to distribute application-specific information within a network using the OSPF protocol. The information contained in opaque LSAs is transparent for the routing process but it can be processed by other modules such as traffic engineering (e.g., MPLS-TE). Architecture ------------ The following picture depicts the architecture of the Quagga/Zebra protocol suite. The OSPF daemon is extended with opaque LSA capabilities and an API for external applications. The OSPF core module executes the OSPF protocol by discovering neighbors and exchanging neighbor state. The opaque module, implemented by Masahiko Endo, provides functions to exchange opaque LSAs between routers. Opaque LSAs can be generated by several modules such as the MPLS-TE module or the API server module. These modules then invoke the opaque module to flood their data to neighbors within the flooding scope. The client, which is an application potentially running on a different node than the OSPF daemon, links against the OSPF API client library. This client library establishes a socket connection with the API server module of the OSPF daemon and uses this connection to retrieve LSAs and originate opaque LSAs. .. figure:: ../figures/ospf_api_architecture.png :alt: image image The OSPF API server module works like any other internal opaque module (such as the MPLS-TE module), but listens to connections from external applications that want to communicate with the OSPF daemon. The API server module can handle multiple clients concurrently. One of the main objectives of the implementation is to make as little changes to the existing Zebra code as possible. Installation & Configuration ---------------------------- Download FRRouting and unpack it. Configure and build FRR (note that ``--enable-opaque-lsa`` also enables the ospfapi server and ospfclient). :: % sh ./configure --enable-opaque-lsa % make This should also compile the client library and sample application in ospfclient. Make sure that you have enabled opaque LSAs in your configuration. Add the ``ospf opaque-lsa`` statement to your :file:`ospfd.conf`: :: ! -*- ospf -*- ! ! OSPFd sample configuration file ! ! hostname xxxxx password xxxxx router ospf router-id 10.0.0.1 network 10.0.0.1/24 area 1 neighbor 10.0.0.2 network 10.0.1.2/24 area 1 neighbor 10.0.1.1 ospf opaque-lsa <============ add this statement! Usage ----- In the following we describe how you can use the sample application to originate opaque LSAs. The sample application first registers with the OSPF daemon the opaque type it wants to inject and then waits until the OSPF daemon is ready to accept opaque LSAs of that type. Then the client application originates an opaque LSA, waits 10 seconds and then updates the opaque LSA with new opaque data. After another 20 seconds, the client application deletes the opaque LSA from the LSDB. If the clients terminates unexpectedly, the OSPF API module will remove all the opaque LSAs that the application registered. Since the opaque LSAs are flooded to other routers, we will see the opaque LSAs in all routers according to the flooding scope of the opaque LSA. We have a very simple demo setup, just two routers connected with an ATM point-to-point link. Start the modified OSPF daemons on two adjacent routers. First run on msr2: .. code-block:: console # ./ospfd --apiserver -f /usr/local/etc/ospfd.conf And on the neighboring router msr3: .. code-block:: console # ./ospfd --apiserver -f /usr/local/etc/ospfd.conf Now the two routers form adjacency and start exchanging their databases. Looking at the OSPF daemon of msr2 (or msr3), you see this: .. code-block:: console ospfd> show ip ospf database OSPF Router with ID (10.0.0.1) Router Link States (Area 0.0.0.1) Link ID ADV Router Age Seq# CkSum Link count 10.0.0.1 10.0.0.1 55 0x80000003 0xc62f 2 10.0.0.2 10.0.0.2 55 0x80000003 0xe3e4 3 Net Link States (Area 0.0.0.1) Link ID ADV Router Age Seq# CkSum 10.0.0.2 10.0.0.2 60 0x80000001 0x5fcb Now we start the sample main application that originates an opaque LSA. .. code-block:: console # cd ospfapi/apiclient # ./main msr2 10 250 20 0.0.0.0 0.0.0.1 This originates an opaque LSA of type 10 (area local), with opaque type 250 (experimental), opaque id of 20 (chosen arbitrarily), interface address 0.0.0.0 (which is used only for opaque LSAs type 9), and area 0.0.0.1 Again looking at the OSPF database you see: .. code-block:: console ospfd> show ip ospf database OSPF Router with ID (10.0.0.1) Router Link States (Area 0.0.0.1) Link ID ADV Router Age Seq# CkSum Link count 10.0.0.1 10.0.0.1 437 0x80000003 0xc62f 2 10.0.0.2 10.0.0.2 437 0x80000003 0xe3e4 3 Net Link States (Area 0.0.0.1) Link ID ADV Router Age Seq# CkSum 10.0.0.2 10.0.0.2 442 0x80000001 0x5fcb Area-Local Opaque-LSA (Area 0.0.0.1) Opaque-Type/Id ADV Router Age Seq# CkSum 250.0.0.20 10.0.0.1 0 0x80000001 0x58a6 <=== opaque LSA You can take a closer look at this opaque LSA: .. code-block:: console ospfd> show ip ospf database opaque-area OSPF Router with ID (10.0.0.1) Area-Local Opaque-LSA (Area 0.0.0.1) LS age: 4 Options: 66 LS Type: Area-Local Opaque-LSA Link State ID: 250.0.0.20 (Area-Local Opaque-Type/ID) Advertising Router: 10.0.0.1 LS Seq Number: 80000001 Checksum: 0x58a6 Length: 24 Opaque-Type 250 (Private/Experimental) Opaque-ID 0x14 Opaque-Info: 4 octets of data Added using OSPF API: 4 octets of opaque data Opaque data: 1 0 0 0 <==== counter is 1 Note that the main application updates the opaque LSA after 10 seconds, then it looks as follows: .. code-block:: console ospfd> show ip ospf database opaque-area OSPF Router with ID (10.0.0.1) Area-Local Opaque-LSA (Area 0.0.0.1) LS age: 1 Options: 66 LS Type: Area-Local Opaque-LSA Link State ID: 250.0.0.20 (Area-Local Opaque-Type/ID) Advertising Router: 10.0.0.1 LS Seq Number: 80000002 Checksum: 0x59a3 Length: 24 Opaque-Type 250 (Private/Experimental) Opaque-ID 0x14 Opaque-Info: 4 octets of data Added using OSPF API: 4 octets of opaque data Opaque data: 2 0 0 0 <==== counter is now 2 Note that the payload of the opaque LSA has changed as you can see above. Then, again after another 20 seconds, the opaque LSA is flushed from the LSDB. Important note: ^^^^^^^^^^^^^^^ In order to originate an opaque LSA, there must be at least one active opaque-capable neighbor. Thus, you cannot originate opaque LSAs if no neighbors are present. If you try to originate when no neighbors are ready, you will receive a not ready error message. The reason for this restriction is that it might be possible that some routers have an identical opaque LSA from a previous origination in their LSDB that unfortunately could not be flushed due to a crash, and now if the router comes up again and starts originating a new opaque LSA, the new opaque LSA is considered older since it has a lower sequence number and is ignored by other routers (that consider the stalled opaque LSA as more recent). However, if the originating router first synchronizes the database before originating opaque LSAs, it will detect the older opaque LSA and can flush it first. Protocol and Message Formats ---------------------------- If you are developing your own client application and you don't want to make use of the client library (due to the GNU license restriction or whatever reason), you can implement your own client-side message handling. The OSPF API uses two connections between the client and the OSPF API server: One connection is used for a synchronous request /reply protocol and another connection is used for asynchronous notifications (e.g., LSA update, neighbor status change). Each message begins with the following header: .. figure:: ../figures/ospf_api_msghdr.png :alt: image image The message type field can take one of the following values: +-------------------------------+---------+ | Messages to OSPF daemon | Value | +===============================+=========+ | MSG\_REGISTER\_OPAQUETYPE | 1 | +-------------------------------+---------+ | MSG\_UNREGISTER\_OPAQUETYPE | 2 | +-------------------------------+---------+ | MSG\_REGISTER\_EVENT | 3 | +-------------------------------+---------+ | MSG\_SYNC\_LSDB | 4 | +-------------------------------+---------+ | MSG\_ORIGINATE\_REQUEST | 5 | +-------------------------------+---------+ | MSG\_DELETE\_REQUEST | 6 | +-------------------------------+---------+ +-----------------------------+---------+ | Messages from OSPF daemon | Value | +=============================+=========+ | MSG\_REPLY | 10 | +-----------------------------+---------+ | MSG\_READY\_NOTIFY | 11 | +-----------------------------+---------+ | MSG\_LSA\_UPDATE\_NOTIFY | 12 | +-----------------------------+---------+ | MSG\_LSA\_DELETE\_NOTIFY | 13 | +-----------------------------+---------+ | MSG\_NEW\_IF | 14 | +-----------------------------+---------+ | MSG\_DEL\_IF | 15 | +-----------------------------+---------+ | MSG\_ISM\_CHANGE | 16 | +-----------------------------+---------+ | MSG\_NSM\_CHANGE | 17 | +-----------------------------+---------+ The synchronous requests and replies have the following message formats: .. figure:: ../figures/ospf_api_msgs1.png :alt: image image The origin field allows origin-based filtering using the following origin types: +-------------------------+---------+ | Origin | Value | +=========================+=========+ | NON\_SELF\_ORIGINATED | 0 | +-------------------------+---------+ | SELF\_ORIGINATED | 1 | +-------------------------+---------+ | ANY\_ORIGIN | 2 | +-------------------------+---------+ The reply message has one of the following error codes: +--------------------------+---------+ | Error code | Value | +==========================+=========+ | API\_OK | 0 | +--------------------------+---------+ | API\_NOSUCHINTERFACE | -1 | +--------------------------+---------+ | API\_NOSUCHAREA | -2 | +--------------------------+---------+ | API\_NOSUCHLSA | -3 | +--------------------------+---------+ | API\_ILLEGALSATYPE | -4 | +--------------------------+---------+ | API\_ILLEGALOPAQUETYPE | -5 | +--------------------------+---------+ | API\_OPAQUETYPEINUSE | -6 | +--------------------------+---------+ | API\_NOMEMORY | -7 | +--------------------------+---------+ | API\_ERROR | -99 | +--------------------------+---------+ | API\_UNDEF | -100 | +--------------------------+---------+ The asynchronous notifications have the following message formats: .. figure:: ../figures/ospf_api_msgs2.png :alt: image image .. Do not delete these acknowledgements! Original Acknowledgments from Ralph Keller ------------------------------------------ I would like to thank Masahiko Endo, the author of the opaque LSA extension module, for his great support. His wonderful ASCII graphs explaining the internal workings of this code, and his invaluable input proved to be crucial in designing a useful API for accessing the link state database of the OSPF daemon. Once, he even decided to take the plane from Tokyo to Zurich so that we could actually meet and have face-to-face discussions, which was a lot of fun. Clearly, without Masahiko no API would ever be completed. I also would like to thank Daniel Bauer who wrote an opaque LSA implementation too and was willing to test the OSPF API code in one of his projects. frr-7.2.1/doc/developer/ospf-sr.rst0000644000000000000000000002644113610377563014100 00000000000000OSPF Segment Routing ==================== This is an EXPERIMENTAL support of draft `draft-ietf-ospf-segment-routing-extensions-24`. DON'T use it for production network. Supported Features ------------------ * Automatic computation of Primary and Backup Adjacency SID with Cisco experimental remote IP address * SRGB configuration * Prefix configuration for Node SID with optional NO-PHP flag (Linux kernel support both mode) * Node MSD configuration (with Linux Kernel >= 4.10 a maximum of 32 labels could be stack) * Automatic provisioning of MPLS table * Static route configuration with label stack up to 32 labels Interoperability ---------------- * Tested on various topology including point-to-point and LAN interfaces in a mix of Free Range Routing instance and Cisco IOS-XR 6.0.x * Check OSPF LSA conformity with latest wireshark release 2.5.0-rc Implementation details ---------------------- Concepts ^^^^^^^^ Segment Routing used 3 different OPAQUE LSA in OSPF to carry the various information: * **Router Information:** flood the Segment Routing capabilities of the node. This include the supported algorithms, the Segment Routing Global Block (SRGB) and the Maximum Stack Depth (MSD). * **Extended Link:** flood the Adjaceny and Lan Adjacency Segment Identifier * **Extended Prefix:** flood the Prefix Segment Identifier The implementation follows previous TE and Router Information codes. It used the OPAQUE LSA functions defined in ospf_opaque.[c,h] as well as the OSPF API. This latter is mandatory for the implementation as it provides the Callback to Segment Routing functions (see below) when an Extended Link / Prefix or Router Information LSA s are received. Overview ^^^^^^^^ Following files where modified or added: * ospd_ri.[c,h] have been modified to add the new TLVs for Segment Routing. * ospf_ext.[c,h] implement RFC7684 as base support of Extended Link and Prefix Opaque LSA. * ospf_sr.[c,h] implement the earth of Segment Routing. It adds a new Segment Routing database to manage Segment Identifiers per Link and Prefix and Segment Routing enable node, Callback functions to process incoming LSA and install MPLS FIB entry through Zebra. The figure below shows the relation between the various files: * ospf_sr.c centralized all the Segment Routing processing. It receives Opaque LSA Router Information (4.0.0.0) from ospf_ri.c and Extended Prefix (7.0.0.X) Link (8.0.0.X) from ospf_ext.c. Once received, it parse TLVs and SubTLVs and store information in SRDB (which is defined in ospf_sr.h). For each received LSA, NHLFE is computed and send to Zebra to add/remove new MPLS labels entries and FEC. New CLI configurations are also centralized in ospf_sr.c. This CLI will trigger the flooding of new LSA Router Information (4.0.0.0), Extended Prefix (7.0.0.X) and Link (8.0.0.X) by ospf_ri.c, respectively ospf_ext.c. * ospf_ri.c send back to ospf_sr.c received Router Information LSA and update Self Router Information LSA with parameters provided by ospf_sr.c i.e. SRGB and MSD. It use ospf_opaque.c functions to send/received these Opaque LSAs. * ospf_ext.c send back to ospf_sr.c received Extended Prefix and Link Opaque LSA and send self Extended Prefix and Link Opaque LSA through ospf_opaque.c functions. :: +-----------+ +-------+ | | | | | ospf_sr.c +-----+ SRDB | +-----------+ +--+ | | | +-^-------^-+ | +-------+ | | | | | | | | | | | | | | +--------+ | | | | | +---v----------+ | | | +-----v-------+ | | | | | | | | ospf_ri.c +--+ | +-------+ ospf_ext.c | | LSA 4.0.0.0 | | | LSA 7.0.0.X | | | | | LSA 8.0.0.X | +---^----------+ | | | | | +-----^-------+ | | | | | | | +--------v------------+ | | | | | | | ZEBRA: Labels + FEC | | | | | | | +---------------------+ | | | | | | +---------------+ | | | | | +---------> ospf_opaque.c <---------+ | | +---------------+ Figure 1: Overview of Segment Routing interaction Module interactions ^^^^^^^^^^^^^^^^^^^ To process incoming LSA, the code is based on the capability to call `hook()` functions when LSA are inserted or delete to / from the LSDB and the possibility to register particular treatment for Opaque LSA. The first point is provided by the OSPF API feature and the second by the Opaque implementation itself. Indeed, it is possible to register callback function for a given Opaque LSA ID (see `ospf_register_opaque_functab()` function defined in `ospf_opaque.c`). Each time a new LSA is added to the LSDB, the `new_lsa_hook()` function previously register for this LSA type is called. For Opaque LSA it is the `ospf_opaque_lsa_install_hook()`. For deletion, it is `ospf_opaque_lsa_delete_hook()`. Note that incoming LSA which is already present in the LSDB will be inserted after the old instance of this LSA remove from the LSDB. Thus, after the first time, each incoming LSA will trigger a `delete` following by an `install`. This is not very helpful to handle real LSA deletion. In fact, LSA deletion is done by Flushing LSA i.e. flood LSA after setting its age to MAX_AGE. Then, a garbage function has the role to remove all LSA with `age == MAX_AGE` in the LSDB. So, to handle LSA Flush, the best is to look to the LSA age to determine if it is an installation or a future deletion i.e. the flushed LSA is first store in the LSDB with MAX_AGE waiting for the garbage collector function. Router Information LSAs ^^^^^^^^^^^^^^^^^^^^^^^ To activate Segment Routing, new CLI command `segment-routing on` has been introduced. When this command is activated, function `ospf_router_info_update_sr()` is called to indicate to Router Information process that Segment Routing TLVs must be flood. Same function is called to modify the Segment Routing Global Block (SRGB) and Maximum Stack Depth (MSD) TLV. Only Shortest Path First (SPF) Algorithm is supported, so no possibility to modify this TLV is offer by the code. When Opaque LSA Type 4 i.e. Router Information are stored in LSDB, function `ospf_opaque_lsa_install_hook()` will call the previously registered function `ospf_router_info_lsa_update()`. In turn, the function will simply trigger `ospf_sr_ri_lsa_update()` or `ospf_sr_ri_lsa_delete` in function of the LSA age. Before, it verifies that the LSA Opaque Type is 4 (Router Information). Self Opaque LSA are not send back to the Segment Routing functions as information are already stored. Extended Link Prefix LSAs ^^^^^^^^^^^^^^^^^^^^^^^^^ Like for Router Information, Segment Routing is activate at the Extended Link/Prefix level with new `segment-routing on` command. This triggers automatically the flooding of Extended Link LSA for all ospf interfaces where adjacency is full. For Extended Prefix LSA, the new CLI command `segment-routing prefix ...` will trigger the flooding of Prefix SID TLV/SubTLVs. When Opaque LSA Type 7 i.e. Extended Prefix and Type 8 i.e. Extended Link are store in the LSDB, `ospf_ext_pref_update_lsa()` respectively `ospf_ext_link_update_lsa()` are called like for Router Information LSA. In turn, they respectively trigger `ospf_sr_ext_prefix_lsa_update()` / `ospf_sr_ext_link_lsa_update()` or `ospf_sr_ext_prefix_lsa_delete()` / `ospf_sr_ext_link_lsa_delete()` if the LSA age is equal to MAX_AGE. Zebra ^^^^^ When a new MPLS entry or new Forwarding Equivalent Class (FEC) must be added or deleted in the data plane, `add_sid_nhlfe()` respectively `del_sid_nhlfe()` are called. Once check the validity of labels, they are send to ZEBRA layer through `ZEBRA_MPLS_LABELS_ADD` command, respectively `ZEBRA_MPLS_LABELS_DELETE` command for deletion. This is completed by a new labelled route through `ZEBRA_ROUTE_ADD` command, respectively `ZEBRA_ROUTE_DELETE` command. Configuration ------------- Linux Kernel ^^^^^^^^^^^^ In order to use OSPF Segment Routing, you must setup MPLS data plane. Up to know, only Linux Kernel version >= 4.5 is supported. First, the MPLS modules aren't loaded by default, so you'll need to load them yourself: :: modprobe mpls_router modprobe mpls_gso modprobe mpls_iptunnel Then, you must activate MPLS on the interface you would used: :: sysctl -w net.mpls.conf.enp0s9.input=1 sysctl -w net.mpls.conf.lo.input=1 sysctl -w net.mpls.platform_labels=1048575 The last line fix the maximum MPLS label value. Once OSPFd start with Segment Routing, you could check that MPLS routes are enable with: :: ip -M route ip route The first command show the MPLS LFIB table while the second show the FIB table which contains route with MPLS label encapsulation. If you disable Penultimate Hop Popping with the `no-php-flag` (see below), you MUST check that RP filter is not enable for the interface you intend to use, especially the `lo` one. For that purpose, disable RP filtering with: :: systcl -w net.ipv4.conf.all.rp_filter=0 sysctl -w net.ipv4.conf.lo.rp_filter=0 OSPFd ^^^^^ Here it is a simple example of configuration to enable Segment Routing. Note that `opaque capability` and `router information` must be set to activate Opaque LSA prior to Segment Routing. :: router ospf ospf router-id 192.168.1.11 capability opaque mpls-te on mpls-te router-address 192.168.1.11 router-info area 0.0.0.0 segment-routing on segment-routing global-block 10000 19999 segment-routing node-msd 8 segment-routing prefix 192.168.1.11/32 index 1100 The first segment-routing statement enable it. The Second one set the SRGB, third line the MSD and finally, set the Prefix SID index for a given prefix. Note that only prefix of Loopback interface could be configured with a Prefix SID. It is possible to add `no-php-flag` at the end of the prefix command to disable Penultimate Hop Popping. This advertises to peers that they MUST NOT pop the MPLS label prior to sending the packet. Known limitations ----------------- * Runs only within default VRF * Only single Area is supported. ABR is not yet supported * Only SPF algorithm is supported * Extended Prefix Range is not supported * MPLS table are not flush at startup. Thus, restarting zebra process is mandatory to remove old MPLS entries in the data plane after a crash of ospfd daemon * With NO Penultimate Hop Popping, it is not possible to express a Segment Path with an Adjacency SID due to the impossibility for the Linux Kernel to perform double POP instruction. Credits ------- * Author: Anselme Sawadogo * Author: Olivier Dugeon * Copyright (C) 2016 - 2018 Orange Labs http://www.orange.com This work has been performed in the framework of the H2020-ICT-2014 project 5GEx (Grant Agreement no. 671636), which is partially funded by the European Commission. frr-7.2.1/doc/developer/ospf.rst0000644000000000000000000000012513610377563013445 00000000000000.. _ospfd: ***** OSPFD ***** .. toctree:: :maxdepth: 2 ospf-api ospf-sr frr-7.2.1/doc/developer/packaging-debian.rst0000644000000000000000000001401513610377563015645 00000000000000.. _packaging-debian: Packaging Debian ================ (Tested on Ubuntu 14.04, 16.04, 17.10, 18.04, Debian jessie, stretch and buster.) 1. Install the Debian packaging tools: .. code-block:: shell sudo apt install fakeroot debhelper devscripts 2. Checkout FRR under an **unprivileged** user account: .. code-block:: shell git clone https://github.com/frrouting/frr.git frr cd frr If you wish to build a package for a branch other than master: .. code-block:: shell git checkout 3. Install build dependencies using the `mk-build-deps` tool from the `devscripts` package: .. code-block:: shell sudo mk-build-deps --install debian/control Alternatively, you can manually install build dependencies for your platform as outlined in :ref:`building`. 4. Run ``tools/tarsource.sh -V``: .. code-block:: shell ./tools/tarsource.sh -V This script sets up the ``debian/changelog-auto`` file with proper version information. 5. (optional) Append a distribution identifier if needed (see below under :ref:`multi-dist`.) 6. Build Debian Package: .. code-block:: shell dpkg-buildpackage $options Where `$options` may contain any or all of the following items: * build profiles specified with ``-P``, e.g. ``-Ppkg.frr.nortrlib,pkg.frr.nosystemd``. Multiple values are separated by commas and there must not be a space after the ``-P``. The following build profiles are currently available: +----------------+-------------------+-----------------------------------------+ | Profile | Negation | Effect | +================+===================+=========================================+ | pkg.frr.rtrlib | pkg.frr.nortrlib | builds frr-rpki-rtrlib package (or not) | +----------------+-------------------+-----------------------------------------+ | n/a | pkg.frr.nosystemd | removes libsystemd dependency and | | | | disables unit file installation | +----------------+-------------------+-----------------------------------------+ .. note:: The ``pkg.frr.nosystemd`` option is only intended to support Ubuntu 14.04 (and should be enabled when building for that.) * the ``-uc -us`` options to disable signing the packages with your GPG key (git builds of the `master` or `stable/X.X` branches won't be signed by default since their target release is set to ``UNRELEASED``.) 7. Done! If all worked correctly, then you should end up with the Debian packages in the parent directory of where `debuild` ran. If distributed, please make sure you distribute it together with the sources (``frr_*.orig.tar.xz``, ``frr_*.debian.tar.xz`` and ``frr_*.dsc``) .. note:: A package created from `master` or `stable/X.X` is slightly different from a package created from the `debian` branch. The changelog for the former is autogenerated and sets the Debian revision to ``-0``, which causes an intentional lintian warning. The `debian` branch on the other hand has a manually maintained changelog that contains proper Debian release versioning. Furthermore, official Debian packages are built in ``3.0 (quilt)`` format with an "orig" tarball and a "debian" tarball. These tarballs are created by the ``tarsource.sh`` tool on any branch. The git repository however contains a ``3.0 (git)`` source format specifier to easily allow direct git builds. .. _multi-dist: Multi-Distribution builds ========================= You can optionally append a distribution identifier in case you want to make multiple versions of the package available in the same repository. Do the following after creating the changelog with `tarsource.sh`: .. code-block:: shell dch -l '~deb8u' 'build for Debian 8 (jessie)' dch -l '~deb9u' 'build for Debian 9 (stretch)' dch -l '~ubuntu14.04.' 'build for Ubuntu 14.04 (trusty)' dch -l '~ubuntu16.04.' 'build for Ubuntu 16.04 (xenial)' dch -l '~ubuntu18.04.' 'build for Ubuntu 18.04 (bionic)' Between building packages for specific distributions, the only difference in the package itself lies in the automatically generated shared library dependencies, e.g. libjson-c2 or libjson-c3. This means that the architecture independent packages should **not** have a suffix appended. Also, the current Debian testing/unstable releases should not have any suffix appended. For example, at the end of 2018 (i.e. ``buster``/Debian 10 is the current "testing" release), the following is a complete list of `.deb` files for Debian 8, 9 and 10 packages for FRR 6.0.1-1 with RPKI support:: frr_6.0.1-1_amd64.deb frr_6.0.1-1~deb8u1_amd64.deb frr_6.0.1-1~deb9u1_amd64.deb frr-dbg_6.0.1-1_amd64.deb frr-dbg_6.0.1-1~deb8u1_amd64.deb frr-dbg_6.0.1-1~deb9u1_amd64.deb frr-rpki-rtrlib_6.0.1-1_amd64.deb frr-rpki-rtrlib_6.0.1-1~deb8u1_amd64.deb frr-rpki-rtrlib_6.0.1-1~deb9u1_amd64.deb frr-doc_6.0.1-1_all.deb frr-pythontools_6.0.1-1_all.deb Note that there are no extra versions of the `frr-doc` and `frr-pythontools` packages (because they are for architecture ``all``, not ``amd64``), and the version for Debian 10 does **not** have a ``~deb10u1`` suffix. .. warning:: Do not use the ``-`` character in the version suffix. The last ``-`` in the version number is the separator between upstream version and Debian version. ``6.0.1-1~foobar-2`` means upstream version ``6.0.1-1~foobar``, Debian version ``2``. This is not what you want. The only allowed characters in the Debian version are ``0-9 A-Z a-z + . ~`` .. note:: The separating character for the suffix **must** be the tilde (``~``) because the tilde is ordered in version-comparison before the empty string. That means the order of the above packages is the following: ``6.0.1-1`` newer than ``6.0.1-1~deb9u1`` newer than ``6.0.1-1~deb8u1`` If you use another character (e.g. ``+``), the untagged version will be regarded as the "oldest"! frr-7.2.1/doc/developer/packaging-redhat.rst0000644000000000000000000000577713610377563015711 00000000000000.. _packaging-redhat: Packaging Red Hat ================= Tested on CentOS 6, CentOS 7 and Fedora 24. 1. On CentOS 6, refer to :ref:`building-centos6` for details on installing sufficiently up-to-date package versions to enable building FRR. Newer automake/autoconf/bison is only needed to build the RPM and is **not** needed to install the binary RPM package. 2. Install the build dependencies for your platform. Refer to the platform-specific build documentation on how to do this. 3. Install the following additional packages:: yum install rpm-build net-snmp-devel pam-devel libcap-devel If your platform uses systemd:: yum install systemd-devel If ``yum`` is not present on your system, use ``dnf`` instead. 3. Checkout FRR:: git clone https://github.com/frrouting/frr.git frr 4. Run Bootstrap and make distribution tar.gz:: cd frr ./bootstrap.sh ./configure --with-pkg-extra-version=-MyRPMVersion make dist .. note:: The only ``configure`` option respected when building RPMs is ``--with-pkg-extra-version``. 5. Create RPM directory structure and populate with sources:: mkdir rpmbuild mkdir rpmbuild/SOURCES mkdir rpmbuild/SPECS cp redhat/*.spec rpmbuild/SPECS/ cp frr*.tar.gz rpmbuild/SOURCES/ 6. Edit :file:`rpm/SPECS/frr.spec` with configuration as needed. Look at the beginning of the file and adjust the following parameters to enable or disable features as required:: ############### FRRouting (FRR) configure options ################# # with-feature options %{!?with_pam: %global with_pam 0 } %{!?with_ospfclient: %global with_ospfclient 1 } %{!?with_ospfapi: %global with_ospfapi 1 } %{!?with_irdp: %global with_irdp 1 } %{!?with_rtadv: %global with_rtadv 1 } %{!?with_ldpd: %global with_ldpd 1 } %{!?with_nhrpd: %global with_nhrpd 1 } %{!?with_eigrp: %global with_eigrpd 1 } %{!?with_shared: %global with_shared 1 } %{!?with_multipath: %global with_multipath 256 } %{!?frr_user: %global frr_user frr } %{!?vty_group: %global vty_group frrvty } %{!?with_fpm: %global with_fpm 0 } %{!?with_watchfrr: %global with_watchfrr 1 } %{!?with_bgp_vnc: %global with_bgp_vnc 0 } %{!?with_pimd: %global with_pimd 1 } %{!?with_rpki: %global with_rpki 0 } 7. Build the RPM:: rpmbuild --define "_topdir `pwd`/rpmbuild" -ba rpmbuild/SPECS/frr.spec If building with RPKI, then download and install the additional RPKI packages from https://ci1.netdef.org/browse/RPKI-RTRLIB/latestSuccessful/artifact If all works correctly, then you should end up with the RPMs under :file:`rpmbuild/RPMS` and the source RPM under :file:`rpmbuild/SRPMS`. frr-7.2.1/doc/developer/packaging.rst0000644000000000000000000000020113610377563014415 00000000000000********* Packaging ********* .. toctree:: :maxdepth: 2 maintainer-release-build packaging-debian packaging-redhat frr-7.2.1/doc/developer/rcu.rst0000644000000000000000000003004513610377563013273 00000000000000.. highlight:: c RCU === Introduction ------------ RCU (Read-Copy-Update) is, fundamentally, a paradigm of multithreaded operation (and not a set of APIs.) The core ideas are: * longer, complicated updates to structures are made only on private, "invisible" copies. Other threads, when they access the structure, see an older (but consistent) copy. * once done, the updated copy is swapped in in a single operation so that other threads see either the old or the new data but no inconsistent state between. * the old instance is only released after making sure that it is impossible any other thread might still be reading it. For more information, please search for general or Linux kernel RCU documentation; there is no way this doc can be comprehensive in explaining the interactions: * https://en.wikipedia.org/wiki/Read-copy-update * https://www.kernel.org/doc/html/latest/kernel-hacking/locking.html#avoiding-locks-read-copy-update * https://lwn.net/Articles/262464/ * http://www.rdrop.com/users/paulmck/RCU/rclock_OLS.2001.05.01c.pdf * http://lse.sourceforge.net/locking/rcupdate.html RCU, the TL;DR ^^^^^^^^^^^^^^ #. data structures are always consistent for reading. That's the "R" part. #. reading never blocks / takes a lock. #. rcu_read_lock is not a lock in the traditional sense. Think of it as a "reservation"; it notes what the *oldest* possible thing the thread might be seeing is, and which thus can't be deleted yet. #. you create some object, finish it up, and then publish it. #. publishing is an ``atomic_*`` call with ``memory_order_release``, which tells the compiler to make sure prior memory writes have completed before doing the atomic op. #. ``ATOMLIST_*`` ``add`` operations do the ``memory_order_release`` for you. #. you can't touch the object after it is published, except with atomic ops. #. because you can't touch it, if you want to change it you make a new copy, work on that, and then publish the new copy. That's the "CU" part. #. deleting the object is also an atomic op. #. other threads that started working before you published / deleted an object might not see the new object / still see the deleted object. #. because other threads may still see deleted objects, the ``free()`` needs to be delayed. That's what :c:func:`rcu_free()` is for. When (not) to use RCU ^^^^^^^^^^^^^^^^^^^^^ RCU is designed for read-heavy workloads where objects are updated relatively rarely, but frequently accessed. Do *not* indiscriminately replace locking by RCU patterns. The "copy" part of RCU implies that, while updating, several copies of a given object exist in parallel. Even after the updated copy is swapped in, the old object remains queued for freeing until all other threads are guaranteed to not be accessing it anymore, due to passing a sequence point. In addition to the increased memory usage, there may be some bursted (due to batching) malloc contention when the RCU cleanup thread does its thing and frees memory. Other useful patterns ^^^^^^^^^^^^^^^^^^^^^ In addition to the full "copy object, apply changes, atomically update" approach, there are 2 "reduced" usage cases that can be done: * atomically updating single pieces of a particular object, e.g. some flags or configuration piece * straight up read-only / immutable objects Both of these cases can be considered RCU "subsets". For example, when maintaining an atomic list of items, but these items only have a single integer value that needs to be updated, that value can be atomically updated without copying the entire object. However, the object still needs to be free'd through :c:func:`rcu_free()` since reading/updating and deleting might be happening concurrently. The same applies for immutable objects; deletion might still race with reading so they need to be free'd through RCU. FRR API ------- Before diving into detail on the provided functions, it is important to note that the FRR RCU API covers the **cleanup part of RCU, not the read-copy-update paradigm itself**. These parts are handled by standard C11 atomic operations, and by extension through the atomic data structures (ATOMLIST, ATOMSORT & co.) The ``rcu_*`` functions only make sense in conjunction with these RCU access patterns. If you're calling the RCU API but not using these, something is wrong. The other way around is not necessarily true; it is possible to use atomic ops & datastructures with other types of locking, e.g. rwlocks. .. c:function:: void rcu_read_lock() .. c:function:: void rcu_read_unlock() These functions acquire / release the RCU read-side lock. All access to RCU-guarded data must be inside a block guarded by these. Any number of threads may hold the RCU read-side lock at a given point in time, including both no threads at all and all threads. The functions implement a depth counter, i.e. can be nested. The nested calls are cheap, since they only increment/decrement the counter. Therefore, any place that uses RCU data and doesn't have a guarantee that the caller holds RCU (e.g. ``lib/`` code) should just have its own rcu_read_lock/rcu_read_unlock pair. At the "root" level (e.g. un-nested), these calls can incur the cost of one syscall (to ``futex()``). That puts them on about the same cost as a mutex lock/unlock. The ``thread_master`` code currently always holds RCU everywhere, except while doing the actual ``poll()`` syscall. This is both an optimization as well as an "easement" into getting RCU going. The current implementation contract is that any ``struct thread *`` callback is called with a RCU holding depth of 1, and that this is owned by the thread so it may (should) drop and reacquire it when doing some longer-running work. .. warning:: The RCU read-side lock must be held **continuously** for the entire time any piece of RCU data is used. This includes any access to RCU data after the initial ``atomic_load``. If the RCU read-side lock is released, any RCU-protected pointers as well as the data they refer to become invalid, as another thread may have called :c:func:`rcu_free` on them. .. c:type:: struct rcu_head .. c:type:: struct rcu_head_close .. c:type:: struct rcu_action The ``rcu_head`` structures are small (16-byte) bits that contain the queueing machinery for the RCU sweeper/cleanup mechanisms. Any piece of data that is cleaned up by RCU needs to have a matching ``rcu_head`` embedded in it. If there is more than one cleanup operation to be done (e.g. closing a file descriptor), more than one ``rcu_head`` may be embedded. .. warning:: It is not possible to reuse a ``rcu_head``. It is owned by the RCU code as soon as ``rcu_*`` is called on it. The ``_close`` variant carries an extra ``int fd`` field to store the fd to be closed. To minimize the amount of memory used for ``rcu_head``, details about the RCU operation to be performed are moved into the ``rcu_action`` structure. It contains e.g. the MTYPE for :c:func:`rcu_free` calls. The pointer to be freed is stored as an offset relative to the ``rcu_head``, which means it must be embedded as a struct field so the offset is constant. The ``rcu_action`` structure is an implementation detail. Using ``rcu_free`` or ``rcu_close`` will set it up correctly without further code needed. The ``rcu_head`` may be put in an union with other data if the other data is only used during "life" of the data, since the ``rcu_head`` is used only for the "death" of data. But note that other threads may still be reading a piece of data while a thread is working to free it. .. c:function:: void rcu_free(struct memtype *mtype, struct X *ptr, field) Free a block of memory after RCU has ensured no other thread can be accessing it anymore. The pointer remains valid for any other thread that has called :c:func:`rcu_read_lock` before the ``rcu_free`` call. .. warning:: In some other RCU implementations, the pointer remains valid to the *calling* thread if it is holding the RCU read-side lock. This is not the case in FRR, particularly when running single-threaded. Enforcing this rule also allows static analysis to find use-after-free issues. ``mtype`` is the libfrr ``MTYPE_FOO`` allocation type to pass to :c:func:`XFREE`. ``field`` must be the name of a ``struct rcu_head`` member field in ``ptr``. The offset of this field (which must be constant) is used to reduce the memory size of ``struct rcu_head``. .. note:: ``rcu_free`` (and ``rcu_close``) calls are more efficient if they are put close to each other. When freeing several RCU'd resources, try to move the calls next to each other (even if the data structures do not directly point to each other.) Having the calls bundled reduces the cost of adding the ``rcu_head`` to the RCU queue; the RCU queue is an atomic data structure whose usage will require the CPU to acquire an exclusive hold on relevant cache lines. .. c:function:: void rcu_close(struct rcu_head_close *head, int fd) Close a file descriptor after ensuring no other thread might be using it anymore. Same as :c:func:`rcu_free`, except it calls ``close`` instead of ``free``. Internals ^^^^^^^^^ .. c:type:: struct rcu_thread Per-thread state maintained by the RCU code, set up by the following functions. A pointer to a thread's own ``rcu_thread`` is saved in thread-local storage. .. c:function:: struct rcu_thread *rcu_thread_prepare(void) .. c:function:: void rcu_thread_unprepare(struct rcu_thread *rcu_thread) .. c:function:: void rcu_thread_start(struct rcu_thread *rcu_thread) Since the RCU code needs to have a list of all active threads, these functions are used by the ``frr_pthread`` code to set up threads. Teardown is automatic. It should not be necessary to call these functions. Any thread that accesses RCU-protected data needs to be registered with these functions. Threads that do not access RCU-protected data may call these functions but do not need to. Note that passing a pointer to RCU-protected data to some library which accesses that pointer makes the library "access RCU-protected data". In that case, either all of the library's threads must be registered for RCU, or the code must instead pass a (non-RCU) copy of the data to the library. .. c:function:: void rcu_shutdown(void) Stop the RCU sweeper thread and make sure all cleanup has finished. This function is called on daemon exit by the libfrr code to ensure pending RCU operations are completed. This is mostly to get a clean exit without memory leaks from queued RCU operations. It should not be necessary to call this function as libfrr handles this. FRR specifics and implementation details ---------------------------------------- The FRR RCU infrastructure has the following characteristics: * it is Epoch-based with a 32-bit wrapping counter. (This is somewhat different from other Epoch-based approaches which may be designed to only use 3 counter values, but works out to a simple implementation.) * instead of tracking CPUs as the Linux kernel does, threads are tracked. This has exactly zero semantic impact, RCU just cares about "threads of execution", which the kernel can optimize to CPUs but we can't. But it really boils down to the same thing. * there are no ``rcu_dereference`` and ``rcu_assign_pointer`` - use ``atomic_load`` and ``atomic_store`` instead. (These didn't exist when the Linux RCU code was created.) * there is no ``synchronize_rcu``; this is a design choice but may be revisited at a later point. ``synchronize_rcu`` blocks a thread until it is guaranteed that no other threads might still be accessing data structures that they may have access to at the beginning of the function call. This is a blocking design and probably not appropriate for FRR. Instead, ``rcu_call`` can be used to have the RCU sweeper thread make a callback after the same constraint is fulfilled in an asynchronous way. Most needs should be covered by ``rcu_free`` and ``rcu_close``. frr-7.2.1/doc/developer/subdir.am0000644000000000000000000000433513610377563013562 00000000000000# # doc/developer # dev_RSTFILES = \ doc/developer/bgp-typecodes.rst \ doc/developer/bgpd.rst \ doc/developer/building-frr-for-alpine.rst \ doc/developer/building-frr-for-centos6.rst \ doc/developer/building-frr-for-centos7.rst \ doc/developer/building-frr-for-debian8.rst \ doc/developer/building-frr-for-debian9.rst \ doc/developer/building-frr-for-fedora.rst \ doc/developer/building-frr-for-freebsd10.rst \ doc/developer/building-frr-for-freebsd11.rst \ doc/developer/building-frr-for-freebsd9.rst \ doc/developer/building-frr-for-netbsd6.rst \ doc/developer/building-frr-for-netbsd7.rst \ doc/developer/building-frr-for-omnios.rst \ doc/developer/building-frr-for-openbsd6.rst \ doc/developer/building-frr-for-openwrt.rst \ doc/developer/building-frr-for-ubuntu1404.rst \ doc/developer/building-frr-for-ubuntu1604.rst \ doc/developer/building-frr-for-ubuntu1804.rst \ doc/developer/building-libyang.rst \ doc/developer/building.rst \ doc/developer/cli.rst \ doc/developer/conf.py \ doc/developer/hooks.rst \ doc/developer/include-compile.rst \ doc/developer/index.rst \ doc/developer/library.rst \ doc/developer/lists.rst \ doc/developer/locking.rst \ doc/developer/logging.rst \ doc/developer/maintainer-release-build.rst \ doc/developer/memtypes.rst \ doc/developer/modules.rst \ doc/developer/next-hop-tracking.rst \ doc/developer/ospf-api.rst \ doc/developer/ospf-sr.rst \ doc/developer/ospf.rst \ doc/developer/packaging-debian.rst \ doc/developer/packaging-redhat.rst \ doc/developer/packaging.rst \ doc/developer/rcu.rst \ doc/developer/testing.rst \ doc/developer/topotests-snippets.rst \ doc/developer/topotests.rst \ doc/developer/workflow.rst \ doc/developer/zebra.rst \ # end EXTRA_DIST += \ $(dev_RSTFILES) \ doc/developer/draft-zebra-00.ms \ doc/developer/ldpd-basic-test-setup.md \ # end DEVBUILD = doc/developer/_build $(DEVBUILD)/.doctrees/environment.pickle: $(dev_RSTFILES) # # nothing built automatically for "all" target. # # # standard targets # developer-info: $(DEVBUILD)/texinfo/frr.info developer-html: $(DEVBUILD)/html/.buildinfo developer-pdf: $(DEVBUILD)/latexpdf # # hook-in for clean # .PHONY: clean-devdocs clean-local: clean-devdocs clean-devdocs: -rm -rf "$(DEVBUILD)" frr-7.2.1/doc/developer/testing.rst0000644000000000000000000000012213610377563014150 00000000000000.. _testing: ******* Testing ******* .. toctree:: :maxdepth: 2 topotests frr-7.2.1/doc/developer/topotests-snippets.rst0000644000000000000000000001471013610377563016412 00000000000000.. _topotests-snippets: Snippets -------- This document will describe common snippets of code that are frequently needed to perform some test checks. Checking for router / test failures ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ The following check uses the topogen API to check for software failure (e.g. zebra died) and/or for errors manually set by ``Topogen.set_error()``. .. code:: py # Get the topology reference tgen = get_topogen() # Check for errors in the topology if tgen.routers_have_failure(): # Skip the test with the topology errors as reason pytest.skip(tgen.errors) Checking FRR routers version ^^^^^^^^^^^^^^^^^^^^^^^^^^^^ This code snippet is usually run after the topology setup to make sure all routers instantiated in the topology have the correct software version. .. code:: py # Get the topology reference tgen = get_topogen() # Get the router list router_list = tgen.routers() # Run the check for all routers for router in router_list.values(): if router.has_version('<', '3'): # Set topology error, so the next tests are skipped tgen.set_error('unsupported version') A sample of this snippet in a test can be found `here `__. Interacting with equipment ^^^^^^^^^^^^^^^^^^^^^^^^^^ You might want to interact with the topology equipment during the tests and there are different ways to do so. Notes: 1. When using the Topogen API, all the equipment code derives from ``Topogear`` (`lib/topogen.py `__). If you feel brave you can look by yourself how the abstractions that will be mentioned here work. 2. When not using the ``Topogen`` API there is only one way to interact with the equipment, which is by calling the ``mininet`` API functions directly to spawn commands. Interacting with the Linux sandbox """""""""""""""""""""""""""""""""" Without ``Topogen``: .. code:: py global net output = net['r1'].cmd('echo "foobar"') print 'output is: {}'.format(output) With ``Topogen``: .. code:: py tgen = get_topogen() output = tgen.gears['r1'].run('echo "foobar"') print 'output is: {}'.format(output) Interacting with VTYSH """""""""""""""""""""" Without ``Topogen``: .. code:: py global net output = net['r1'].cmd('vtysh "show ip route" 2>/dev/null') print 'output is: {}'.format(output) With ``Topogen``: .. code:: py tgen = get_topogen() output = tgen.gears['r1'].vtysh_cmd("show ip route") print 'output is: {}'.format(output) ``Topogen`` also supports sending multiple lines of command: .. code:: py tgen = get_topogen() output = tgen.gears['r1'].vtysh_cmd(""" configure terminal router bgp 10 bgp router-id 10.0.255.1 neighbor 1.2.3.4 remote-as 10 ! router bgp 11 bgp router-id 10.0.255.2 ! """) print 'output is: {}'.format(output) You might also want to run multiple commands and get only the commands that failed: .. code:: py tgen = get_topogen() output = tgen.gears['r1'].vtysh_multicmd(""" configure terminal router bgp 10 bgp router-id 10.0.255.1 neighbor 1.2.3.4 remote-as 10 ! router bgp 11 bgp router-id 10.0.255.2 ! """, pretty_output=false) print 'output is: {}'.format(output) Translating vtysh JSON output into Python structures: .. code:: py tgen = get_topogen() json_output = tgen.gears['r1'].vtysh_cmd("show ip route json", isjson=True) output = json.dumps(json_output, indent=4) print 'output is: {}'.format(output) # You can also access the data structure as normal. For example: # protocol = json_output['1.1.1.1/32']['protocol'] # assert protocol == "ospf", "wrong protocol" .. note:: ``vtysh_(multi)cmd`` is only available for router types of equipment. Invoking mininet CLI ^^^^^^^^^^^^^^^^^^^^ Without ``Topogen``: .. code:: py CLI(net) With ``Topogen``: .. code:: py tgen = get_topogen() tgen.mininet_cli() Reading files ^^^^^^^^^^^^^ Loading a normal text file content in the current directory: .. code:: py # If you are using Topogen # CURDIR = CWD # # Otherwise find the directory manually: CURDIR = os.path.dirname(os.path.realpath(__file__)) file_name = '{}/r1/show_ip_route.txt'.format(CURDIR) file_content = open(file_name).read() Loading JSON from a file: .. code:: py import json file_name = '{}/r1/show_ip_route.json'.format(CURDIR) file_content = json.loads(open(file_name).read()) Comparing JSON output ^^^^^^^^^^^^^^^^^^^^^ After obtaining JSON output formatted with Python data structures, you may use it to assert a minimalist schema: .. code:: py tgen = get_topogen() json_output = tgen.gears['r1'].vtysh_cmd("show ip route json", isjson=True) expect = { '1.1.1.1/32': { 'protocol': 'ospf' } } assertmsg = "route 1.1.1.1/32 was not learned through OSPF" assert json_cmp(json_output, expect) is None, assertmsg ``json_cmp`` function description (it might be outdated, you can find the latest description in the source code at :file:`tests/topotests/lib/topotest.py` .. code:: text JSON compare function. Receives two parameters: * `d1`: json value * `d2`: json subset which we expect Returns `None` when all keys that `d1` has matches `d2`, otherwise a string containing what failed. Note: key absence can be tested by adding a key with value `None`. Pausing execution ^^^^^^^^^^^^^^^^^ Preferably, choose the ``sleep`` function that ``topotest`` provides, as it prints a notice during the test execution to help debug topology test execution time. .. code:: py # Using the topotest sleep from lib import topotest topotest.sleep(10, 'waiting 10 seconds for bla') # or just tell it the time: # topotest.sleep(10) # It will print 'Sleeping for 10 seconds'. # Or you can also use the Python sleep, but it won't show anything from time import sleep sleep(5) iproute2 Linux commands as JSON ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ ``topotest`` has two helpers implemented that parses the output of ``ip route`` commands to JSON. It might simplify your comparison needs by only needing to provide a Python dictionary. .. code:: py from lib import topotest tgen = get_topogen() routes = topotest.ip4_route(tgen.gears['r1']) expected = { '10.0.1.0/24': {}, '10.0.2.0/24': { 'dev': 'r1-eth0' } } assertmsg = "failed to find 10.0.1.0/24 and/or 10.0.2.0/24" assert json_cmp(routes, expected) is None, assertmsg frr-7.2.1/doc/developer/topotests.rst0000644000000000000000000006700613610377563014555 00000000000000.. _topotests: Topotests ========= Topotests is a suite of topology tests for FRR built on top of Mininet. Installation and Setup ---------------------- Only tested with Ubuntu 16.04 and Ubuntu 18.04 (which uses Mininet 2.2.x). Instructions are the same for all setups (i.e. ExaBGP is only used for BGP tests). Installing Mininet Infrastructure ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ .. code:: shell apt-get install mininet apt-get install python-pip apt-get install iproute pip install ipaddr pip install "pytest<5" pip install exabgp==3.4.17 (Newer 4.0 version of exabgp is not yet supported) useradd -d /var/run/exabgp/ -s /bin/false exabgp Enable Coredumps """""""""""""""" Optional, will give better output. .. code:: shell apt-get install gdb disable apport (which move core files) Set ``enabled=0`` in ``/etc/default/apport``. Next, update security limits by changing :file:`/etc/security/limits.conf` to:: # * soft core unlimited root soft core unlimited * hard core unlimited root hard core unlimited Reboot for options to take effect. FRR Installation ^^^^^^^^^^^^^^^^ FRR needs to be installed separately. It is assume to be configured like the standard Ubuntu Packages: - Binaries in :file:`/usr/lib/frr` - State Directory :file:`/var/run/frr` - Running under user ``frr``, group ``frr`` - vtygroup: ``frrvty`` - config directory: :file:`/etc/frr` - For FRR Packages, install the dbg package as well for coredump decoding No FRR config needs to be done and no FRR daemons should be run ahead of the test. They are all started as part of the test. Manual FRR build """""""""""""""" If you prefer to manually build FRR, then use the following suggested config: .. code:: shell ./configure \ --prefix=/usr \ --localstatedir=/var/run/frr \ --sbindir=/usr/lib/frr \ --sysconfdir=/etc/frr \ --enable-vtysh \ --enable-pimd \ --enable-multipath=64 \ --enable-user=frr \ --enable-group=frr \ --enable-vty-group=frrvty \ --with-pkg-extra-version=-my-manual-build And create ``frr`` user and ``frrvty`` group as follows: .. code:: shell addgroup --system --gid 92 frr addgroup --system --gid 85 frrvty adduser --system --ingroup frr --home /var/run/frr/ \ --gecos "FRRouting suite" --shell /bin/false frr usermod -G frrvty frr Executing Tests --------------- Execute all tests with output to console ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ .. code:: shell py.test -s -v --tb=no The above command must be executed from inside the topotests directory. All test\_\* scripts in subdirectories are detected and executed (unless disabled in ``pytest.ini`` file). ``--tb=no`` disables the python traceback which might be irrelevant unless the test script itself is debugged. Execute single test ^^^^^^^^^^^^^^^^^^^ .. code:: shell cd test_to_be_run ./test_to_be_run.py For example, and assuming you are inside the frr directory: .. code:: shell cd tests/topotests/bgp_l3vpn_to_bgp_vrf ./test_bgp_l3vpn_to_bgp_vrf.py For further options, refer to pytest documentation. Test will set exit code which can be used with ``git bisect``. For the simulated topology, see the description in the python file. If you need to clear the mininet setup between tests (if it isn't cleanly shutdown), then use the ``mn -c`` command to clean up the environment. StdErr log from daemos after exit ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ To enable the reporting of any messages seen on StdErr after the daemons exit, the following env variable can be set:: export TOPOTESTS_CHECK_STDERR=Yes (The value doesn't matter at this time. The check is whether the env variable exists or not.) There is no pass/fail on this reporting; the Output will be reported to the console. Collect Memory Leak Information ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ FRR processes can report unfreed memory allocations upon exit. To enable the reporting of memory leaks, define an environment variable ``TOPOTESTS_CHECK_MEMLEAK`` with the file prefix, i.e.:: export TOPOTESTS_CHECK_MEMLEAK="/home/mydir/memleak_" This will enable the check and output to console and the writing of the information to files with the given prefix (followed by testname), ie :file:`/home/mydir/memcheck_test_bgp_multiview_topo1.txt` in case of a memory leak. Running Topotests with AddressSanitizer ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ Topotests can be run with AddressSanitizer. It requires GCC 4.8 or newer. (Ubuntu 16.04 as suggested here is fine with GCC 5 as default). For more information on AddressSanitizer, see https://github.com/google/sanitizers/wiki/AddressSanitizer. The checks are done automatically in the library call of ``checkRouterRunning`` (ie at beginning of tests when there is a check for all daemons running). No changes or extra configuration for topotests is required beside compiling the suite with AddressSanitizer enabled. If a daemon crashed, then the errorlog is checked for AddressSanitizer output. If found, then this is added with context (calling test) to :file:`/tmp/AddressSanitizer.txt` in Markdown compatible format. Compiling for GCC AddressSanitizer requires to use ``gcc`` as a linker as well (instead of ``ld``). Here is a suggest way to compile frr with AddressSanitizer for ``master`` branch: .. code:: shell git clone https://github.com/FRRouting/frr.git cd frr ./bootstrap.sh export CC=gcc export CFLAGS="-O1 -g -fsanitize=address -fno-omit-frame-pointer" export LD=gcc export LDFLAGS="-g -fsanitize=address -ldl" ./configure --enable-shared=no \ --prefix=/usr/lib/frr --sysconfdir=/etc/frr \ --localstatedir=/var/run/frr \ --sbindir=/usr/lib/frr --bindir=/usr/lib/frr \ --enable-exampledir=/usr/lib/frr/examples \ --with-moduledir=/usr/lib/frr/modules \ --enable-multipath=0 --enable-rtadv \ --enable-tcp-zebra --enable-fpm --enable-pimd \ --enable-sharpd make sudo make install # Create symlink for vtysh, so topotest finds it in /usr/lib/frr sudo ln -s /usr/lib/frr/vtysh /usr/bin/ and create ``frr`` user and ``frrvty`` group as shown above. .. _topotests_docker: Running Tests with Docker ------------------------- There is a Docker image which allows to run topotests. Quickstart ^^^^^^^^^^ If you have Docker installed, you can run the topotests in Docker. The easiest way to do this, is to use the make targets from this repository. Your current user needs to have access to the Docker daemon. Alternatively you can run these commands as root. .. code:: console make topotests This command will pull the most recent topotests image from Dockerhub, compile FRR inside of it, and run the topotests. Advanced Usage ^^^^^^^^^^^^^^ Internally, the topotests make target uses a shell script to pull the image and spawn the Docker container. There are several environment variables which can be used to modify the behavior of the script, these can be listed by calling it with ``-h``: .. code:: console ./tests/topotests/docker/frr-topotests.sh -h For example, a volume is used to cache build artifacts between multiple runs of the image. If you need to force a complete recompile, you can set ``TOPOTEST_CLEAN``: .. code:: console TOPOTEST_CLEAN=1 ./tests/topotests/docker/frr-topotests.sh By default, ``frr-topotests.sh`` will build frr and run pytest. If you append arguments and the first one starts with ``/`` or ``./``, they will replace the call to pytest. If the appended arguments do not match this patttern, they will be provided to pytest as arguments. So, to run a specific test with more verbose logging: .. code:: console ./tests/topotests/docker/frr-topotests.sh -vv -s all-protocol-startup/test_all_protocol_startup.py And to compile FRR but drop into a shell instead of running pytest: .. code:: console ./tests/topotests/docker/frr-topotests.sh /bin/bash Development ^^^^^^^^^^^ The Docker image just includes all the components to run the topotests, but not the topotests themselves. So if you just want to write tests and don't want to make changes to the environment provided by the Docker image. You don't need to build your own Docker image if you do not want to. When developing new tests, there is one caveat though: The startup script of the container will run a ``git-clean`` on its copy of the FRR tree to avoid any pollution of the container with build artefacts from the host. This will also result in your newly written tests being unavailable in the container unless at least added to the index with ``git-add``. If you do want to test changes to the Docker image, you can locally build the image and run the tests without pulling from the registry using the following commands: .. code:: console make topotests-build TOPOTEST_PULL=0 make topotests .. _topotests-guidelines: Guidelines ---------- Executing Tests ^^^^^^^^^^^^^^^ To run the whole suite of tests the following commands must be executed at the top level directory of topotest: .. code:: shell $ # Change to the top level directory of topotests. $ cd path/to/topotests $ # Tests must be run as root, since Mininet requires it. $ sudo pytest In order to run a specific test, you can use the following command: .. code:: shell $ # running a specific topology $ sudo pytest ospf-topo1/ $ # or inside the test folder $ cd ospf-topo1 $ sudo pytest # to run all tests inside the directory $ sudo pytest test_ospf_topo1.py # to run a specific test $ # or outside the test folder $ cd .. $ sudo pytest ospf-topo1/test_ospf_topo1.py # to run a specific one The output of the tested daemons will be available at the temporary folder of your machine: .. code:: shell $ ls /tmp/topotest/ospf-topo1.test_ospf-topo1/r1 ... zebra.err # zebra stderr output zebra.log # zebra log file zebra.out # zebra stdout output ... You can also run memory leak tests to get reports: .. code:: shell $ # Set the environment variable to apply to a specific test... $ sudo env TOPOTESTS_CHECK_MEMLEAK="/tmp/memleak_report_" pytest ospf-topo1/test_ospf_topo1.py $ # ...or apply to all tests adding this line to the configuration file $ echo 'memleak_path = /tmp/memleak_report_' >> pytest.ini $ # You can also use your editor $ $EDITOR pytest.ini $ # After running tests you should see your files: $ ls /tmp/memleak_report_* memleak_report_test_ospf_topo1.txt Writing a New Test ^^^^^^^^^^^^^^^^^^ This section will guide you in all recommended steps to produce a standard topology test. This is the recommended test writing routine: - Write a topology (Graphviz recommended) - Obtain configuration files - Write the test itself - Create a Pull Request Topotest File Hierarchy """"""""""""""""""""""" Before starting to write any tests one must know the file hierarchy. The repository hierarchy looks like this: .. code:: shell $ cd path/to/topotest $ find ./* ... ./README.md # repository read me ./GUIDELINES.md # this file ./conftest.py # test hooks - pytest related functions ./example-test # example test folder ./example-test/__init__.py # python package marker - must always exist. ./example-test/test_template.jpg # generated topology picture - see next section ./example-test/test_template.dot # Graphviz dot file ./example-test/test_template.py # the topology plus the test ... ./ospf-topo1 # the ospf topology test ./ospf-topo1/r1 # router 1 configuration files ./ospf-topo1/r1/zebra.conf # zebra configuration file ./ospf-topo1/r1/ospfd.conf # ospf configuration file ./ospf-topo1/r1/ospfroute.txt # 'show ip ospf' output reference file # removed other for shortness sake ... ./lib # shared test/topology functions ./lib/topogen.py # topogen implementation ./lib/topotest.py # topotest implementation Guidelines for creating/editing topotest: - New topologies that don't fit the existing directories should create its own - Always remember to add the ``__init__.py`` to new folders, this makes auto complete engines and pylint happy - Router (Quagga/FRR) specific code should go on topotest.py - Generic/repeated router actions should have an abstraction in topogen.TopoRouter. - Generic/repeated non-router code should go to topotest.py - pytest related code should go to conftest.py (e.g. specialized asserts) Defining the Topology """"""""""""""""""""" The first step to write a new test is to define the topology. This step can be done in many ways, but the recommended is to use Graphviz to generate a drawing of the topology. It allows us to see the topology graphically and to see the names of equipment, links and addresses. Here is an example of Graphviz dot file that generates the template topology :file:`tests/topotests/example-test/test_template.dot` (the inlined code might get outdated, please see the linked file):: graph template { label="template"; # Routers r1 [ shape=doubleoctagon, label="r1", fillcolor="#f08080", style=filled, ]; r2 [ shape=doubleoctagon, label="r2", fillcolor="#f08080", style=filled, ]; # Switches s1 [ shape=oval, label="s1\n192.168.0.0/24", fillcolor="#d0e0d0", style=filled, ]; s2 [ shape=oval, label="s2\n192.168.1.0/24", fillcolor="#d0e0d0", style=filled, ]; # Connections r1 -- s1 [label="eth0\n.1"]; r1 -- s2 [label="eth1\n.100"]; r2 -- s2 [label="eth0\n.1"]; } Here is the produced graph: .. graphviz:: graph template { label="template"; # Routers r1 [ shape=doubleoctagon, label="r1", fillcolor="#f08080", style=filled, ]; r2 [ shape=doubleoctagon, label="r2", fillcolor="#f08080", style=filled, ]; # Switches s1 [ shape=oval, label="s1\n192.168.0.0/24", fillcolor="#d0e0d0", style=filled, ]; s2 [ shape=oval, label="s2\n192.168.1.0/24", fillcolor="#d0e0d0", style=filled, ]; # Connections r1 -- s1 [label="eth0\n.1"]; r1 -- s2 [label="eth1\n.100"]; r2 -- s2 [label="eth0\n.1"]; } Generating / Obtaining Configuration Files """""""""""""""""""""""""""""""""""""""""" In order to get the configuration files or command output for each router, we need to run the topology and execute commands in ``vtysh``. The quickest way to achieve that is writing the topology building code and running the topology. To bootstrap your test topology, do the following steps: - Copy the template test .. code:: shell $ mkdir new-topo/ $ touch new-topo/__init__.py $ cp example-test/test_template.py new-topo/test_new_topo.py - Modify the template according to your dot file Here is the template topology described in the previous section in python code: .. code:: py class TemplateTopo(Topo): "Test topology builder" def build(self, *_args, **_opts): "Build function" tgen = get_topogen(self) # Create 2 routers for routern in range(1, 3): tgen.add_router('r{}'.format(routern)) # Create a switch with just one router connected to it to simulate a # empty network. switch = tgen.add_switch('s1') switch.add_link(tgen.gears['r1']) # Create a connection between r1 and r2 switch = tgen.add_switch('s2') switch.add_link(tgen.gears['r1']) switch.add_link(tgen.gears['r2']) - Run the topology Topogen allows us to run the topology without running any tests, you can do that using the following example commands: .. code:: shell $ # Running your bootstraped topology $ sudo pytest -s --topology-only new-topo/test_new_topo.py $ # Running the test_template.py topology $ sudo pytest -s --topology-only example-test/test_template.py $ # Running the ospf_topo1.py topology $ sudo pytest -s --topology-only ospf-topo1/test_ospf_topo1.py Parameters explanation: .. program:: pytest .. option:: -s Actives input/output capture. This is required by mininet in order to show the interactive shell. .. option:: --topology-only Don't run any tests, just build the topology. After executing the commands above, you should get the following terminal output: .. code:: shell === test session starts === platform linux2 -- Python 2.7.12, pytest-3.1.2, py-1.4.34, pluggy-0.4.0 rootdir: /media/sf_src/topotests, inifile: pytest.ini collected 3 items ospf-topo1/test_ospf_topo1.py *** Starting controller *** Starting 6 switches switch1 switch2 switch3 switch4 switch5 switch6 ... r2: frr zebra started r2: frr ospfd started r3: frr zebra started r3: frr ospfd started r1: frr zebra started r1: frr ospfd started r4: frr zebra started r4: frr ospfd started *** Starting CLI: mininet> The last line shows us that we are now using the Mininet CLI (Command Line Interface), from here you can call your router ``vtysh`` or even bash. Here are some commands example: .. code:: shell mininet> r1 ping 10.0.3.1 PING 10.0.3.1 (10.0.3.1) 56(84) bytes of data. 64 bytes from 10.0.3.1: icmp_seq=1 ttl=64 time=0.576 ms 64 bytes from 10.0.3.1: icmp_seq=2 ttl=64 time=0.083 ms 64 bytes from 10.0.3.1: icmp_seq=3 ttl=64 time=0.088 ms ^C --- 10.0.3.1 ping statistics --- 3 packets transmitted, 3 received, 0% packet loss, time 1998ms rtt min/avg/max/mdev = 0.083/0.249/0.576/0.231 ms mininet> r1 ping 10.0.3.3 PING 10.0.3.3 (10.0.3.3) 56(84) bytes of data. 64 bytes from 10.0.3.3: icmp_seq=1 ttl=64 time=2.87 ms 64 bytes from 10.0.3.3: icmp_seq=2 ttl=64 time=0.080 ms 64 bytes from 10.0.3.3: icmp_seq=3 ttl=64 time=0.091 ms ^C --- 10.0.3.3 ping statistics --- 3 packets transmitted, 3 received, 0% packet loss, time 2003ms rtt min/avg/max/mdev = 0.080/1.014/2.872/1.313 ms mininet> r3 vtysh Hello, this is FRRouting (version 3.1-devrzalamena-build). Copyright 1996-2005 Kunihiro Ishiguro, et al. frr-1# show running-config Building configuration... Current configuration: ! frr version 3.1-devrzalamena-build frr defaults traditional hostname r3 no service integrated-vtysh-config ! log file zebra.log ! log file ospfd.log ! interface r3-eth0 ip address 10.0.3.1/24 ! interface r3-eth1 ip address 10.0.10.1/24 ! interface r3-eth2 ip address 172.16.0.2/24 ! router ospf ospf router-id 10.0.255.3 redistribute kernel redistribute connected redistribute static network 10.0.3.0/24 area 0 network 10.0.10.0/24 area 0 network 172.16.0.0/24 area 1 ! line vty ! end frr-1# After you successfully configured your topology, you can obtain the configuration files (per-daemon) using the following commands: .. code:: shell mininet> r3 vtysh -d ospfd Hello, this is FRRouting (version 3.1-devrzalamena-build). Copyright 1996-2005 Kunihiro Ishiguro, et al. frr-1# show running-config Building configuration... Current configuration: ! frr version 3.1-devrzalamena-build frr defaults traditional no service integrated-vtysh-config ! log file ospfd.log ! router ospf ospf router-id 10.0.255.3 redistribute kernel redistribute connected redistribute static network 10.0.3.0/24 area 0 network 10.0.10.0/24 area 0 network 172.16.0.0/24 area 1 ! line vty ! end frr-1# Writing Tests """"""""""""" Test topologies should always be bootstrapped from :file:`tests/topotests/example-test/test_template.py` because it contains important boilerplate code that can't be avoided, like: - imports: os, sys, pytest, topotest/topogen and mininet topology class - The global variable CWD (Current Working directory): which is most likely going to be used to reference the routers configuration file location Example: .. code:: py # For all registered routers, load the zebra configuration file for rname, router in router_list.iteritems(): router.load_config( TopoRouter.RD_ZEBRA, os.path.join(CWD, '{}/zebra.conf'.format(rname)) ) # os.path.join() joins the CWD string with arguments adding the necessary # slashes ('/'). Arguments must not begin with '/'. - The topology class that inherits from Mininet Topo class: .. code:: py class TemplateTopo(Topo): def build(self, *_args, **_opts): tgen = get_topogen(self) # topology build code - pytest ``setup_module()`` and ``teardown_module()`` to start the topology .. code:: py def setup_module(_m): tgen = Topogen(TemplateTopo) tgen.start_topology('debug') def teardown_module(_m): tgen = get_topogen() tgen.stop_topology() - ``__main__`` initialization code (to support running the script directly) .. code:: py if __name__ == '__main__': sys.exit(pytest.main(["-s"])) Requirements: - Test code should always be declared inside functions that begin with the ``test_`` prefix. Functions beginning with different prefixes will not be run by pytest. - Configuration files and long output commands should go into separated files inside folders named after the equipment. - Tests must be able to run without any interaction. To make sure your test conforms with this, run it without the :option:`-s` parameter. Tips: - Keep results in stack variables, so people inspecting code with ``pdb`` can easily print their values. Don't do this: .. code:: py assert foobar(router1, router2) Do this instead: .. code:: py result = foobar(router1, router2) assert result - Use ``assert`` messages to indicate where the test failed. Example: .. code:: py for router in router_list: # ... assert condition, 'Router "{}" condition failed'.format(router.name) Debugging Execution ^^^^^^^^^^^^^^^^^^^ The most effective ways to inspect topology tests are: - Run pytest with ``--pdb`` option. This option will cause a pdb shell to appear when an assertion fails Example: ``pytest -s --pdb ospf-topo1/test_ospf_topo1.py`` - Set a breakpoint in the test code with ``pdb`` Example: .. code:: py # Add the pdb import at the beginning of the file import pdb # ... # Add a breakpoint where you think the problem is def test_bla(): # ... pdb.set_trace() # ... The `Python Debugger `__ (pdb) shell allows us to run many useful operations like: - Setting breaking point on file/function/conditions (e.g. ``break``, ``condition``) - Inspecting variables (e.g. ``p`` (print), ``pp`` (pretty print)) - Running python code .. tip:: The TopoGear (equipment abstraction class) implements the ``__str__`` method that allows the user to inspect equipment information. Example of pdb usage: .. code:: shell > /media/sf_src/topotests/ospf-topo1/test_ospf_topo1.py(121)test_ospf_convergence() -> for rnum in range(1, 5): (Pdb) help Documented commands (type help ): ======================================== EOF bt cont enable jump pp run unt a c continue exit l q s until alias cl d h list quit step up args clear debug help n r tbreak w b commands disable ignore next restart u whatis break condition down j p return unalias where Miscellaneous help topics: ========================== exec pdb Undocumented commands: ====================== retval rv (Pdb) list 116 title2="Expected output") 117 118 def test_ospf_convergence(): 119 "Test OSPF daemon convergence" 120 pdb.set_trace() 121 -> for rnum in range(1, 5): 122 router = 'r{}'.format(rnum) 123 124 # Load expected results from the command 125 reffile = os.path.join(CWD, '{}/ospfroute.txt'.format(router)) 126 expected = open(reffile).read() (Pdb) step > /media/sf_src/topotests/ospf-topo1/test_ospf_topo1.py(122)test_ospf_convergence() -> router = 'r{}'.format(rnum) (Pdb) step > /media/sf_src/topotests/ospf-topo1/test_ospf_topo1.py(125)test_ospf_convergence() -> reffile = os.path.join(CWD, '{}/ospfroute.txt'.format(router)) (Pdb) print rnum 1 (Pdb) print router r1 (Pdb) tgen = get_topogen() (Pdb) pp tgen.gears[router] (Pdb) pp str(tgen.gears[router]) 'TopoGear"s1-eth0","r1-eth1"<->"s3-eth0"]> TopoRouter<>' (Pdb) l 125 120 pdb.set_trace() 121 for rnum in range(1, 5): 122 router = 'r{}'.format(rnum) 123 124 # Load expected results from the command 125 -> reffile = os.path.join(CWD, '{}/ospfroute.txt'.format(router)) 126 expected = open(reffile).read() 127 128 # Run test function until we get an result. Wait at most 60 seconds. 129 test_func = partial(compare_show_ip_ospf, router, expected) 130 result, diff = topotest.run_and_expect(test_func, '', (Pdb) router1 = tgen.gears[router] (Pdb) router1.vtysh_cmd('show ip ospf route') '============ OSPF network routing table ============\r\nN 10.0.1.0/24 [10] area: 0.0.0.0\r\n directly attached to r1-eth0\r\nN 10.0.2.0/24 [20] area: 0.0.0.0\r\n via 10.0.3.3, r1-eth1\r\nN 10.0.3.0/24 [10] area: 0.0.0.0\r\n directly attached to r1-eth1\r\nN 10.0.10.0/24 [20] area: 0.0.0.0\r\n via 10.0.3.1, r1-eth1\r\nN IA 172.16.0.0/24 [20] area: 0.0.0.0\r\n via 10.0.3.1, r1-eth1\r\nN IA 172.16.1.0/24 [30] area: 0.0.0.0\r\n via 10.0.3.1, r1-eth1\r\n\r\n============ OSPF router routing table =============\r\nR 10.0.255.2 [10] area: 0.0.0.0, ASBR\r\n via 10.0.3.3, r1-eth1\r\nR 10.0.255.3 [10] area: 0.0.0.0, ABR, ASBR\r\n via 10.0.3.1, r1-eth1\r\nR 10.0.255.4 IA [20] area: 0.0.0.0, ASBR\r\n via 10.0.3.1, r1-eth1\r\n\r\n============ OSPF external routing table ===========\r\n\r\n\r\n' (Pdb) tgen.mininet_cli() *** Starting CLI: mininet> To enable more debug messages in other Topogen subsystems (like Mininet), more logging messages can be displayed by modifying the test configuration file ``pytest.ini``: .. code:: ini [topogen] # Change the default verbosity line from 'info'... #verbosity = info # ...to 'debug' verbosity = debug Instructions for use, write or debug topologies can be found in :ref:`topotests-guidelines`. To learn/remember common code snippets see :ref:`topotests-snippets`. Before creating a new topology, make sure that there isn't one already that does what you need. If nothing is similar, then you may create a new topology, preferably, using the newest template (:file:`tests/topotests/example-test/test_template.py`). .. include:: topotests-snippets.rst License ------- All the configs and scripts are licensed under a ISC-style license. See Python scripts for details. frr-7.2.1/doc/developer/workflow.rst0000644000000000000000000013466113610377563014365 00000000000000.. _process-and-workflow: ******************* Process & Workflow ******************* .. highlight:: none FRR is a large project developed by many different groups. This section documents standards for code style & quality, commit messages, pull requests and best practices that all contributors are asked to follow. This chapter is "descriptive/post-factual" in that it documents pratices that are in use; it is not "definitive/pre-factual" in prescribing practices. This means that when a procedure changes, it is agreed upon, then put into practice, and then documented here. If this document doesn't match reality, it's the document that needs to be updated, not reality. Mailing Lists ============= The FRR development group maintains multiple mailing lists for use by the community. Italicized lists are private. +----------------------------------+--------------------------------+ | Topic | List | +==================================+================================+ | Development | dev@lists.frrouting.org | +----------------------------------+--------------------------------+ | Users & Operators | frog@lists.frrouting.org | +----------------------------------+--------------------------------+ | Announcements | announce@lists.frrouting.org | +----------------------------------+--------------------------------+ | *Security* | security@lists.frrouting.org | +----------------------------------+--------------------------------+ | *Technical Steering Committee* | tsc@lists.frrouting.org | +----------------------------------+--------------------------------+ The Development list is used to discuss and document general issues related to project development and governance. The public `Slack instance `_ and weekly technical meetings provide a higher bandwidth channel for discussions. The results of such discussions must be reflected in updates, as appropriate, to code (i.e., merges), `GitHub issues`_, and for governance or process changes, updates to the Development list and either this file or information posted at https://frrouting.org/. Development & Release Cycle =========================== Development ----------- .. figure:: ../figures/git_branches.png :align: center :scale: 55% :alt: Merging Git branches into a central trunk Rough outline of FRR development workflow The master Git for FRR resides on `GitHub`_. There is one main branch for development, ``master``. For each major release (2.0, 3.0 etc) a new release branch is created based on the master. Significant bugfixes should be backported to upcoming and existing release branches no more than 1 year old. As a general rule new features are not backported to release branches. Subsequent point releases based on a major branch are handled with git tags. Releases -------- FRR employs a ``..`` versioning scheme. ``MAJOR`` Significant new features or multiple minor features. This should mostly cover any kind of disruptive change that is visible or "risky" to operators. New features or protocols do not necessarily trigger this. (This was changed for FRR 7.x after feedback from users that the pace of major version number increments was too high.) ``MINOR`` General incremental development releases, excluding "major" changes mentioned above. Not necessarily fully backwards compatible, as smaller (but still visible) changes or deprecated feature removals may still happen. However, there shouldn't be any huge "surprises" between minor releases. ``BUGFIX`` Fixes for actual bugs and/or security issues. Fully compatible. We will pull a new development branch for the next release every 4 months. The current schedule is Feb/June/October 1. The decision for a ``MAJOR/MINOR`` release is made at the time of branch pull based on what has been received the previous 4 months. The branch name will be ``dev/MAJOR.MINOR``. At this point in time the master branch and this new branch, :file:`configure.ac`, documentation and packaging systems will be updated to reflect the next possible release name to allow for easy distinguishing. After one month the development branch will be renamed to ``stable/MAJOR.MINOR``. The branch is a stable branch. This process is not held up unless a crash or security issue has been found and needs to be addressed. Issues being fixed will not cause a delay. Bugfix releases are made as needed at 1 month intervals until the next ``MAJOR.MINOR`` release branch is pulled. Depending on the severity of the bugs, bugfix releases may occur sooner. Bugfixes are applied to the two most recent releases. However, backporting of bug fixes to older than the two most recent releases will not be prevented, if acked under the classical development workflow applying for a pull request. Security fixes are backported to all releases less than or equal to at least one year old. Security fixes may also be backported to older releases depending on severity. Long term support branches ( LTS ) ----------------------------------------- This kind of branch is not yet officially supported, and need experimentation before being effective. Previous definition of releases prevents long term support of previous releases. For instance, bug and security fixes are not applied if the stable branch is too old. Because the FRR users have a need to backport bug and security fixes after the stable branch becomes too old, there is a need to provide support on a long term basis on that stable branch. If that support is applied on that stable branch, then that branch is a long term support branch. Having a LTS branch requires extra-work and requires one person to be in charge of that maintenance branch for a certain amount of time. The amount of time will be by default set to 4 months, and can be increased. 4 months stands for the time between two releases, this time can be applied to the decision to continue with a LTS release or not. In all cases, that time period will be well-defined and published. Also, a self nomination from a person that proposes to handle the LTS branch is required. The work can be shared by multiple people. In all cases, there must be at least one person that is in charge of the maintenance branch. The person on people responsible for a maintenance branch must be a FRR maintainer. Note that they may choose to abandon support for the maintenance branch at any time. If no one takes over the responsibility of the LTS branch, then the support will be discontinued. The LTS branch duties are the following ones: - organise meetings on a (bi-)weekly or monthly basis, the handling of issues and pull requested relative to that branch. When time permits, this may be done during the regularly scheduled FRR meeting. - ensure the stability of the branch, by using and eventually adapting the checking the CI tools of FRR ( indeed, maintaining may lead to create maintenance branches for topotests or for CI). It will not be possible to backport feature requests to LTS branches. Actually, it is a false good idea to use LTS for that need. Introducing feature requests may break the paradigm where all more recent releases should also include the feature request. This would require the LTS maintainer to ensure that all more recent releases have support for this feature request. Moreover, introducing features requests may result in breaking the stability of the branch. LTS branches are first done to bring long term support for stability. Changelog --------- The changelog will be the base for the release notes. A changelog entry for your changes is usually not required and will be added based on your commit messages by the maintainers. However, you are free to include an update to the changelog with some better description. Submitting Patches and Enhancements =================================== FRR accepts patches from two sources: - Email (git format-patch) - GitHub pull request Contributors are highly encouraged to use GitHub's fork-and-PR workflow. It is easier for us to review it, test it, try it and discuss it on GitHub than it is via email, thus your patch will get more attention more quickly on GitHub. The base branch for new contributions and non-critical bug fixes should be ``master``. Please ensure your pull request is based on this branch when you submit it. GitHub Pull Requests -------------------- The preferred method of submitting changes is a GitHub pull request. Code submitted by pull request will be automatically tested by one or more CI systems. Once the automated tests succeed, other developers will review your code for quality and correctness. After any concerns are resolved, your code will be merged into the branch it was submitted against. The title of the pull request should provide a high level technical summary of the included patches. The description should provide additional details that will help the reviewer to understand the context of the included patches. Patch Submission via Mailing List --------------------------------- As an alternative submission method, a patch can be mailed to the development mailing list. Patches received on the mailing list will be picked up by Patchwork and tested against the latest development branch. The recommended way to send the patch (or series of NN patches) to the list is by using ``git send-email`` as follows (assuming they are the N most recent commit(s) in your git history):: git send-email -NN --annotate --to=dev@lists.frrouting.org If your commits do not already contain a ``Signed-off-by`` line, then use the following command to add it (after making sure you agree to the Developer Certificate of Origin as outlined above):: git send-email -NN --annotate --signoff --to=dev@lists.frrouting.org Submitting multi-commit patches as a GitHub pull request is **strongly encouraged** and increases the probability of your patch getting reviewed and merged in a timely manner. .. _license-for-contributions: License for Contributions ------------------------- FRR is under a “GPLv2 or later†license. Any code submitted must be released under the same license (preferred) or any license which allows redistribution under this GPLv2 license (eg MIT License). It is forbidden to push any code that prevents from using GPLv3 license. This becomes a community rule, as FRR produces binaries that links with Apache 2.0 libraries. Apache 2.0 and GPLv2 license are incompatible, if put together. Please see ``_ for more information. This rule guarantees the user to distribute FRR binary code without any licensing issues. Pre-submission Checklist ------------------------ - Format code (see `Code Formatting <#code-formatting>`__) - Verify and acknowledge license (see :ref:`license-for-contributions`) - Ensure you have properly signed off (see :ref:`signing-off`) - Test building with various configurations: - ``buildtest.sh`` - Verify building source distribution: - ``make dist`` (and try rebuilding from the resulting tar file) - Run unit tests: - ``make test`` - In the case of a major new feature or other significant change, document plans for continued maintenance of the feature .. _signing-off: Signing Off ----------- Code submitted to FRR must be signed off. We have the same requirements for using the signed-off-by process as the Linux kernel. In short, you must include a ``Signed-off-by`` tag in every patch. ``Signed-off-by`` is a developer's certification that they have the right to submit the patch for inclusion into the project. It is an agreement to the :ref:`Developer's Certificate of Origin `. Code without a proper ``Signed-off-by`` line cannot and will not be merged. If you are unfamiliar with this process, you should read the `official policy at kernel.org `_. You might also find `this article `_ about participating in the Linux community on the Linux Foundation website to be a helpful resource. .. _developers-certificate-of-origin: In short, when you sign off on a commit, you assert your agreement to all of the following:: Developer's Certificate of Origin 1.1 By making a contribution to this project, I certify that: (a) The contribution was created in whole or in part by me and I have the right to submit it under the open source license indicated in the file; or (b) The contribution is based upon previous work that, to the best of my knowledge, is covered under an appropriate open source license and I have the right under that license to submit that work with modifications, whether created in whole or in part by me, under the same open source license (unless I am permitted to submit under a different license), as indicated in the file; or (c) The contribution was provided directly to me by some other person who certified (a), (b) or (c) and I have not modified it. (d) I understand and agree that this project and the contribution are public and that a record of the contribution (including all personal information I submit with it, including my sign-off) is maintained indefinitely and may be redistributed consistent with this project or the open source license(s) involved. After Submitting Your Changes ----------------------------- - Watch for Continuous Integration (CI) test results - You should automatically receive an email with the test results within less than 2 hrs of the submission. If you don’t get the email, then check status on the GitHub pull request. - Please notify the development mailing list if you think something doesn't work. - If the tests failed: - In general, expect the community to ignore the submission until the tests pass. - It is up to you to fix and resubmit. - This includes fixing existing unit (“make testâ€) tests if your changes broke or changed them. - It also includes fixing distribution packages for the failing platforms (ie if new libraries are required). - Feel free to ask for help on the development list. - Go back to the submission process and repeat until the tests pass. - If the tests pass: - Wait for reviewers. Someone will review your code or be assigned to review your code. - Respond to any comments or concerns the reviewer has. Use e-mail or add a comment via github to respond or to let the reviewer know how their comment or concern is addressed. - An author must never delete or manually dismiss someone else's comments or review. (A review may be overridden by agreement in the weekly technical meeting.) - Automatically generated comments, e.g., those generated by CI systems, may be deleted by authors and others when such comments are not the most recent results from that automated comment source. - After all comments and concerns are addressed, expect your patch to be merged. - Watch out for questions on the mailing list. At this time there will be a manual code review and further (longer) tests by various community members. - Your submission is done once it is merged to the master branch. Programming Languages, Tools and Libraries ========================================== The core of FRR is written in C (gcc or clang supported) and makes use of GNU compiler extensions. A few non-essential scripts are implemented in Perl and Python. FRR requires the following tools to build distribution packages: automake, autoconf, texinfo, libtool and gawk and various libraries (i.e. libpam and libjson-c). If your contribution requires a new library or other tool, then please highlight this in your description of the change. Also make sure it’s supported by all FRR platform OSes or provide a way to build without the library (potentially without the new feature) on the other platforms. Documentation should be written in reStructuredText. Sphinx extensions may be utilized but pure ReST is preferred where possible. See :ref:`documentation`. Use of C++ ---------- While C++ is not accepted for core components of FRR, extensions, modules or other distinct components may want to use C++ and include FRR header files. There is no requirement on contributors to work to retain C++ compatibility, but fixes for C++ compatibility are welcome. This implies that the burden of work to keep C++ compatibility is placed with the people who need it, and they may provide it at their leisure to the extent it is useful to them. So, if only a subset of header files, or even parts of a header file are made available to C++, this is perfectly fine. Code Reviews ============ Code quality is paramount for any large program. Consequently we require reviews of all submitted patches by at least one person other than the submitter before the patch is merged. Because of the nature of the software, FRR's maintainer list (i.e. those with commit permissions) tends to contain employees / members of various organizations. In order to prevent conflicts of interest, we use an honor system in which submissions from an individual representing one company should be merged by someone unaffiliated with that company. Guidelines for code review -------------------------- - As a rule of thumb, the depth of the review should be proportional to the scope and / or impact of the patch. - Anyone may review a patch. - When using GitHub reviews, marking "Approve" on a code review indicates willingness to merge the PR. - For individuals with merge rights, marking "Changes requested" is equivalent to a NAK. - For a PR you marked with "Changes requested", please respond to updates in a timely manner to avoid impeding the flow of development. - Rejected or obsolete PRs are generally closed by the submitter based on requests and/or agreement captured in a PR comment. The comment may originate with a reviewer or document agreement reached on Slack, the Development mailing list, or the weekly technical meeting. Coding Practices & Style ======================== Commit messages --------------- Commit messages should be formatted in the same way as Linux kernel commit messages. The format is roughly:: dir: short summary extended summary ``dir`` should be the top level source directory under which the change was made. For example, a change in :file:`bgpd/rfapi` would be formatted as:: bgpd: short summary ... The first line should be no longer than 50 characters. Subsequent lines should be wrapped to 72 characters. You must also sign off on your commit. .. seealso:: :ref:`signing-off` Source File Header ------------------ New files must have a copyright header (see :ref:`license-for-contributions` above) added to the file. The header should be: .. code-block:: c /* * Title/Function of file * Copyright (C) YEAR Author’s Name * * This program is free software; you can redistribute it and/or modify it * under the terms of the 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; see the file COPYING; if not, write to the Free Software * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA */ #include Please copy-paste this header verbatim. In particular: - Do not replace "This program" with "FRR" - Do not change the address of the FSF Adding Copyright Claims to Existing Files ----------------------------------------- When adding copyright claims for modifications to an existing file, please add a ``Portions:`` section as shown below. If this section already exists, add your new claim at the end of the list. .. code-block:: c /* * Title/Function of file * Copyright (C) YEAR Author’s Name * Portions: * Copyright (C) 2010 Entity A .... * Copyright (C) 2016 Your name [optional brief change description] * ... */ Code Formatting --------------- FRR uses Linux kernel style except where noted below. Code which does not comply with these style guidelines will not be accepted. The project provides multiple tools to allow you to correctly style your code as painlessly as possible, primarily built around ``clang-format``. clang-format In the project root there is a :file:`.clang-format` configuration file which can be used with the ``clang-format`` source formatter tool from the LLVM project. Most of the time, this is the easiest and smartest tool to use. It can be run in a variety of ways. If you point it at a C source file or directory of source files, it will format all of them. In the LLVM source tree there are scripts that allow you to integrate it with ``git``, ``vim`` and ``emacs``, and there are third-party plugins for other editors. The ``git`` integration is particularly useful; suppose you have some changes in your git index. Then, with the integration installed, you can do the following: :: git clang-format This will format *only* the changes present in your index. If you have just made a few commits and would like to correctly style only the changes made in those commits, you can use the following syntax: :: git clang-format HEAD~X Where X is one more than the number of commits back from the tip of your branch you would like ``clang-format`` to look at (similar to specifying the target for a rebase). The ``vim`` plugin is particularly useful. It allows you to select lines in visual line mode and press a key binding to invoke ``clang-format`` on only those lines. When using ``clang-format``, it is recommended to use the latest version. Each consecutive version generally has better handling of various edge cases. You may notice on occasion that two consecutive runs of ``clang-format`` over the same code may result in changes being made on the second run. This is an unfortunate artifact of the tool. Please check with the kernel style guide if in doubt. One stylistic problem with the FRR codebase is the use of ``DEFUN`` macros for defining CLI commands. ``clang-format`` will happily format these macro invocations, but the result is often unsightly and difficult to read. Consequently, FRR takes a more relaxed position with how these are formatted. In general you should lean towards using the style exemplified in the section on :ref:`command-line-interface`. Because ``clang-format`` mangles this style, there is a Python script named ``tools/indent.py`` that wraps ``clang-format`` and handles ``DEFUN`` macros as well as some other edge cases specific to FRR. If you are submitting a new file, it is recommended to run that script over the new file, preferably after ensuring that the latest stable release of ``clang-format`` is in your ``PATH``. Documentation on ``clang-format`` and its various integrations is maintained on the LLVM website. https://clang.llvm.org/docs/ClangFormat.html checkpatch.sh In the Linux kernel source tree there is a Perl script used to check incoming patches for style errors. FRR uses an adapted version of this script for the same purpose. It can be found at :file:`tools/checkpatch.sh`. This script takes a git-formatted diff or patch file, applies it to a clean FRR tree, and inspects the result to catch potential style errors. Running this script on your patches before submission is highly recommended. The CI system runs this script as well and will comment on the PR with the results if style errors are found. It is run like this:: ./checkpatch.sh Reports are generated on ``stderr`` and the exit code indicates whether issues were found (2, 1) or not (0). Where ```` is the path to the diff or patch file and ```` is the path to your FRR source tree. The tree should be on the branch that you intend to submit the patch against. The script will make a best-effort attempt to save the state of your working tree and index before applying the patch, and to restore it when it is done, but it is still recommended that you have a clean working tree as the script does perform a hard reset on your tree during its run. The script reports two classes of issues, namely WARNINGs and ERRORs. Please pay attention to both of them. The script will generally report WARNINGs where it cannot be 100% sure that a particular issue is real. In most cases WARNINGs indicate an issue that needs to be fixed. Sometimes the script will report false positives; these will be handled in code review on a case-by-case basis. Since the script only looks at changed lines, occasionally changing one part of a line can cause the script to report a style issue already present on that line that is unrelated to the change. When convenient it is preferred that these be cleaned up inline, but this is not required. In general, a developer should heed the information reported by checkpatch. However, some flexibility is needed for cases where human judgement yields better clarity than the script. Accordingly, it may be appropriate to ignore some checkpatch.sh warnings per discussion among the submitter(s) and reviewer(s) of a change. Misreporting of errors by the script is possible. When this occurs, the exception should be handled either by patching checkpatch to correct the false error report, or by documenting the exception in this document under :ref:`style-exceptions`. If the incorrect report is likely to appear again, a checkpatch update is preferred. If the script finds one or more WARNINGs it will exit with 1. If it finds one or more ERRORs it will exit with 2. Please remember that while FRR provides these tools for your convenience, responsibility for properly formatting your code ultimately lies on the shoulders of the submitter. As such, it is recommended to double-check the results of these tools to avoid delays in merging your submission. In some cases, these tools modify or flag the format in ways that go beyond or even conflict [#tool_style_conflicts]_ with the canonical documented Linux kernel style. In these cases, the Linux kernel style takes priority; non-canonical issues flagged by the tools are not compulsory but rather are opportunities for discussion among the submitter(s) and reviewer(s) of a change. **Whitespace changes in untouched parts of the code are not acceptable in patches that change actual code.** To change/fix formatting issues, please create a separate patch that only does formatting changes and nothing else. Kernel and BSD styles are documented externally: - https://www.kernel.org/doc/html/latest/process/coding-style.html - http://man.openbsd.org/style For GNU coding style, use ``indent`` with the following invocation: :: indent -nut -nfc1 file_for_submission.c Historically, FRR used fixed-width integral types that do not exist in any standard but were defined by most platforms at some point. Officially these types are not guaranteed to exist. Therefore, please use the fixed-width integral types introduced in the C99 standard when contributing new code to FRR. If you need to convert a large amount of code to use the correct types, there is a shell script in :file:`tools/convert-fixedwidth.sh` that will do the necessary replacements. +-----------+--------------------------+ | Incorrect | Correct | +===========+==========================+ | u_int8_t | uint8_t | +-----------+--------------------------+ | u_int16_t | uint16_t | +-----------+--------------------------+ | u_int32_t | uint32_t | +-----------+--------------------------+ | u_int64_t | uint64_t | +-----------+--------------------------+ | u_char | uint8_t or unsigned char | +-----------+--------------------------+ | u_short | unsigned short | +-----------+--------------------------+ | u_int | unsigned int | +-----------+--------------------------+ | u_long | unsigned long | +-----------+--------------------------+ .. _style-exceptions: Exceptions ^^^^^^^^^^ FRR project code comes from a variety of sources, so there are some stylistic exceptions in place. They are organized here by branch. For ``master`` """""""""""""" BSD coding style applies to: - ``ldpd/`` ``babeld`` uses, approximately, the following style: - K&R style braces - Indents are 4 spaces - Function return types are on their own line For ``stable/3.0`` and ``stable/2.0`` """"""""""""""""""""""""""""""""""""" GNU coding style apply to the following parts: - ``lib/`` - ``zebra/`` - ``bgpd/`` - ``ospfd/`` - ``ospf6d/`` - ``isisd/`` - ``ripd/`` - ``ripngd/`` - ``vtysh/`` BSD coding style applies to: - ``ldpd/`` Specific Exceptions ^^^^^^^^^^^^^^^^^^^ Most of the time checkpatch errors should be corrected. Occasionally as a group maintainers will decide to ignore certain stylistic issues. Usually this is because correcting the issue is not possible without large unrelated code changes. When an exception is made, if it is unlikely to show up again and doesn't warrant an update to checkpatch, it is documented here. +------------------------------------------+---------------------------------------------------------------+ | Issue | Ignore Reason | +==========================================+===============================================================+ | DEFPY_HIDDEN, DEFPY_ATTR: complex macros | DEF* macros cannot be wrapped in parentheses without updating | | should be wrapped in parentheses | all usages of the macro, which would be highly disruptive. | +------------------------------------------+---------------------------------------------------------------+ Compile-time conditional code ----------------------------- Many users access FRR via binary packages from 3rd party sources; compile-time code puts inclusion/exclusion in the hands of the package maintainer. Please think very carefully before making code conditional at compile time, as it increases regression testing, maintenance burdens, and user confusion. In particular, please avoid gratuitous ``--enable-…`` switches to the configure script - in general, code should be of high quality and in working condition, or it shouldn’t be in FRR at all. When code must be compile-time conditional, try have the compiler make it conditional rather than the C pre-processor so that it will still be checked by the compiler, even if disabled. For example, :: if (SOME_SYMBOL) frobnicate(); is preferred to :: #ifdef SOME_SYMBOL frobnicate (); #endif /* SOME_SYMBOL */ Note that the former approach requires ensuring that ``SOME_SYMBOL`` will be defined (watch your ``AC_DEFINE``\ s). Debug-guards in code -------------------- Debugging statements are an important methodology to allow developers to fix issues found in the code after it has been released. The caveat here is that the developer must remember that people will be using the code at scale and in ways that can be unexpected for the original implementor. As such debugs **MUST** be guarded in such a way that they can be turned off. FRR has the ability to turn on/off debugs from the CLI and it is expected that the developer will use this convention to allow control of their debugs. Custom syntax-like block macros ------------------------------- FRR uses some macros that behave like the ``for`` or ``if`` C keywords. These macros follow these patterns: - loop-style macros are named ``frr_each_*`` (and ``frr_each``) - single run macros are named ``frr_with_*`` - to avoid confusion, ``frr_with_*`` macros must always use a ``{ ... }`` block even if the block only contains one statement. The ``frr_each`` constructs are assumed to be well-known enough to use normal ``for`` rules. - ``break``, ``return`` and ``goto`` all work correctly. For loop-style macros, ``continue`` works correctly too. Both the ``each`` and ``with`` keywords are inspired by other (more higher-level) programming languages that provide these constructs. There are also some older iteration macros, e.g. ``ALL_LIST_ELEMENTS`` and ``FOREACH_AFI_SAFI``. These macros in some cases do **not** fulfill the above pattern (e.g. ``break`` does not work in ``FOREACH_AFI_SAFI`` because it expands to 2 nested loops.) Static Analysis and Sanitizers ------------------------------ Clang/LLVM and GCC come with a variety of tools that can be used to help find bugs in FRR. clang-analyze This is a static analyzer that scans the source code looking for patterns that are likely to be bugs. The tool is run automatically on pull requests as part of CI and new static analysis warnings will be placed in the CI results. FRR aims for absolutely zero static analysis errors. While the project is not quite there, code that introduces new static analysis errors is very unlikely to be merged. AddressSanitizer This is an excellent tool that provides runtime instrumentation for detecting memory errors. As part of CI FRR is built with this instrumentation and run through a series of tests to look for any results. Testing your own code with this tool before submission is encouraged. You can enable it by passing:: --enable-address-sanitizer to ``configure``. ThreadSanitizer Similar to AddressSanitizer, this tool provides runtime instrumentation for detecting data races. If you are working on or around multithreaded code, extensive testing with this instrumtation enabled is *highly* recommended. You can enable it by passing:: --enable-thread-sanitizer to ``configure``. MemorySanitizer Similar to AddressSanitizer, this tool provides runtime instrumentation for detecting use of uninitialized heap memory. Testing your own code with this tool before submission is encouraged. You can enable it by passing:: --enable-memory-sanitizer to ``configure``. All of the above tools are available in the Clang/LLVM toolchain since 3.4. AddressSanitizer and ThreadSanitizer are available in recent versions of GCC, but are no longer actively maintained. MemorySanitizer is not available in GCC. .. note:: The different Sanitizers are mostly incompatible with each other. Please refer to GCC/LLVM documentation for details. Additionally, the FRR codebase is regularly scanned with Coverity. Unfortunately Coverity does not have the ability to handle scanning pull requests, but after code is merged it will send an email notifying project members with Coverity access of newly introduced defects. Executing non-installed dynamic binaries ---------------------------------------- Since FRR uses the GNU autotools build system, it inherits its shortcomings. To execute a binary directly from the build tree under a wrapper like `valgrind`, `gdb` or `strace`, use:: ./libtool --mode=execute valgrind [--valgrind-opts] zebra/zebra [--zebra-opts] While replacing valgrind/zebra as needed. The `libtool` script is found in the root of the build directory after `./configure` has completed. Its purpose is to correctly set up `LD_LIBRARY_PATH` so that libraries from the build tree are used. (On some systems, `libtool` is also available from PATH, but this is not always the case.) CLI changes ----------- CLI's are a complicated ugly beast. Additions or changes to the CLI should use a DEFUN to encapsulate one setting as much as is possible. Additionally as new DEFUN's are added to the system, documentation should be provided for the new commands. Backwards Compatibility ----------------------- As a general principle, changes to CLI and code in the lib/ directory should be made in a backwards compatible fashion. This means that changes that are purely stylistic in nature should be avoided, e.g., renaming an existing macro or library function name without any functional change. When adding new parameters to common functions, it is also good to consider if this too should be done in a backward compatible fashion, e.g., by preserving the old form in addition to adding the new form. This is not to say that minor or even major functional changes to CLI and common code should be avoided, but rather that the benefit gained from a change should be weighed against the added cost/complexity to existing code. Also, that when making such changes, it is good to preserve compatibility when possible to do so without introducing maintenance overhead/cost. It is also important to keep in mind, existing code includes code that may reside in private repositories (and is yet to be submitted) or code that has yet to be migrated from Quagga to FRR. That said, compatibility measures can (and should) be removed when either: - they become a significant burden, e.g. when data structures change and the compatibility measure would need a complex adaptation layer or becomes flat-out impossible - some measure of time (dependent on the specific case) has passed, so that the compatibility grace period is considered expired. For CLI commands, the deprecation period is 1 year. In all cases, compatibility pieces should be marked with compiler/preprocessor annotations to print warnings at compile time, pointing to the appropriate update path. A ``-Werror`` build should fail if compatibility bits are used. To avoid compilation issues in released code, such compiler/preprocessor annotations must be ignored non-development branches. For example: .. code-block:: c #if CONFDATE > 20180403 CPP_NOTICE("Use of is deprecated, please use ") #endif Preferably, the shell script :file:`tools/fixup-deprecated.py` will be updated along with making non-backwards compatible code changes, or an alternate script should be introduced, to update the code to match the change. When the script is updated, there is no need to preserve the deprecated code. Note that this does not apply to user interface changes, just internal code, macros and libraries. Miscellaneous ------------- When in doubt, follow the guidelines in the Linux kernel style guide, or ask on the development mailing list / public Slack instance. .. _documentation: Documentation ============= FRR uses Sphinx+RST as its documentation system. The document you are currently reading was generated by Sphinx from RST source in :file:`doc/developer/workflow.rst`. The documentation is structured as follows: +-----------------------+-------------------------------------------+ | Directory | Contents | +=======================+===========================================+ | :file:`doc/user` | User documentation; configuration guides; | | | protocol overviews | +-----------------------+-------------------------------------------+ | :file:`doc/developer` | Developer's documentation; API specs; | | | datastructures; architecture overviews; | | | project management procedure | +-----------------------+-------------------------------------------+ | :file:`doc/manpages` | Source for manpages | +-----------------------+-------------------------------------------+ | :file:`doc/figures` | Images and diagrams | +-----------------------+-------------------------------------------+ | :file:`doc/extra` | Miscellaneous Sphinx extensions, scripts, | | | customizations, etc. | +-----------------------+-------------------------------------------+ Each of these directories, with the exception of :file:`doc/figures` and :file:`doc/extra`, contains a Sphinx-generated Makefile and configuration script :file:`conf.py` used to set various document parameters. The makefile can be used for a variety of targets; invoke `make help` in any of these directories for a listing of available output formats. For convenience, there is a top-level :file:`Makefile.am` that has targets for PDF and HTML documentation for both developer and user documentation, respectively. That makefile is also responsible for building manual pages packed with distribution builds. Indent and styling should follow existing conventions: - 3 spaces for indents under directives - Cross references may contain only lowercase alphanumeric characters and hyphens ('-') - Lines wrapped to 80 characters where possible Characters for header levels should follow Python documentation guide: - ``#`` with overline, for parts - ``*`` with overline, for chapters - ``=``, for sections - ``-``, for subsections - ``^``, for subsubsections - ``"``, for paragraphs After you have made your changes, please make sure that you can invoke ``make latexpdf`` and ``make html`` with no warnings. The documentation is currently incomplete and needs love. If you find a broken cross-reference, figure, dead hyperlink, style issue or any other nastiness we gladly accept documentation patches. To build the docs, please ensure you have installed a recent version of `Sphinx `_. If you want to build LaTeX or PDF docs, you will also need a full LaTeX distribution installed. Code ---- FRR is a large and complex software project developed by many different people over a long period of time. Without adequate documentation, it can be exceedingly difficult to understand code segments, APIs and other interfaces. In the interest of keeping the project healthy and maintainable, you should make every effort to document your code so that other people can understand what it does without needing to closely read the code itself. Some specific guidelines that contributors should follow are: - Functions exposed in header files should have descriptive comments above their signatures in the header file. At a minimum, a function comment should contain information about the return value, parameters, and a general summary of the function's purpose. Documentation on parameter values can be omitted if it is (very) obvious what they are used for. Function comments must follow the style for multiline comments laid out in the kernel style guide. Example: .. code-block:: c /* * Determines whether or not a string is cool. * * text * the string to check for coolness * * is_clccfc * whether capslock is cruise control for cool * * Returns: * 7 if the text is cool, 0 otherwise */ int check_coolness(const char *text, bool is_clccfc); Function comments should make it clear what parameters and return values are used for. - Static functions should have descriptive comments in the same form as above if what they do is not immediately obvious. Use good engineering judgement when deciding whether a comment is necessary. If you are unsure, document your code. - Global variables, static or not, should have a comment describing their use. - **For new code in lib/, these guidelines are hard requirements.** If you make significant changes to portions of the codebase covered in the Developer's Manual, add a major subsystem or feature, or gain arcane mastery of some undocumented or poorly documented part of the codebase, please document your work so others can benefit. If you add a major feature or introduce a new API, please document the architecture and API to the best of your abilities in the Developer's Manual, using good judgement when choosing where to place it. Finally, if you come across some code that is undocumented and feel like going above and beyond, document it! We absolutely appreciate and accept patches that document previously undocumented code. User ---- If you are contributing code that adds significant user-visible functionality please document how to use it in :file:`doc/user`. Use good judgement when choosing where to place documentation. For example, instructions on how to use your implementation of a new BGP draft should go in the BGP chapter instead of being its own chapter. If you are adding a new protocol daemon, please create a new chapter. FRR Specific Markup ------------------- FRR has some customizations applied to the Sphinx markup that go a long way towards making documentation easier to use, write and maintain. CLI Commands ^^^^^^^^^^^^ When documenting CLI please use a combination of the ``.. index::`` and ``.. clicmd::`` directives. For example, the command :clicmd:`show pony` would be documented as follows: .. code-block:: rest .. index:: show pony .. clicmd:: show pony Prints an ASCII pony. Example output::: >>\. /_ )`. / _)`^)`. _.---. _ (_,' \ `^-)"" `.\ | | \ \ / | / \ /.___.'\ (\ (_ < ,"|| \ |`. \`-' \\ () )| )/ hjw |_>|> /_] // /_] /_] When documented this way, CLI commands can be cross referenced with the ``:clicmd:`` inline markup like so: .. code-block:: rest :clicmd:`show pony` This is very helpful for users who want to quickly remind themselves what a particular command does. Configuration Snippets ^^^^^^^^^^^^^^^^^^^^^^ When putting blocks of example configuration please use the ``.. code-block::`` directive and specify ``frr`` as the highlighting language, as in the following example. This will tell Sphinx to use a custom Pygments lexer to highlight FRR configuration syntax. .. code-block:: rest .. code-block:: frr ! ! Example configuration file. ! log file /tmp/log.log service integrated-vtysh-config ! ip route 1.2.3.0/24 reject ipv6 route de:ea:db:ee:ff::/64 reject ! .. _GitHub: https://github.com/frrouting/frr .. _GitHub issues: https://github.com/frrouting/frr/issues .. rubric:: Footnotes .. [#tool_style_conflicts] For example, lines over 80 characters are allowed for text strings to make it possible to search the code for them: please see `Linux kernel style (breaking long lines and strings) `_ and `Issue #1794 `_. frr-7.2.1/doc/developer/zebra.rst0000644000000000000000000003225513610377563013612 00000000000000.. _zebra: ***** Zebra ***** .. _zebra-protocol: Overview of the Zebra Protocol ============================== The Zebra protocol is used by protocol daemons to communicate with the **zebra** daemon. Each protocol daemon may request and send information to and from the **zebra** daemon such as interface states, routing state, nexthop-validation, and so on. Protocol daemons may also install routes with **zebra**. The **zebra** daemon manages which routes are installed into the forwarding table with the kernel. The Zebra protocol is a streaming protocol, with a common header. Version 0 lacks a version field and is implicitly versioned. Version 1 and all subsequent versions have a version field. Version 0 can be distinguished from all other versions by examining the 3rd byte of the header, which contains a marker value of 255 (in Quagga) or 254 (in FRR) for all versions except version 0. The marker byte corresponds to the command field in version 0, and the marker value is a reserved command in version 0. Version History --------------- - Version 0 Used by all versions of GNU Zebra and all version of Quagga up to and including Quagga 0.98. This version has no ``version`` field, and so is implicitly versioned as version 0. - Version 1 Added ``marker`` and ``version`` fields, increased ``command`` field to 16 bits. Used by Quagga versions 0.99.3 through 0.99.20. - Version 2 Used by Quagga versions 0.99.21 through 0.99.23. - Version 3 Added ``vrf_id`` field. Used by Quagga versions 0.99.23 until FRR fork. - Version 4 Change marker value to 254 to prevent people mixing and matching Quagga and FRR daemon binaries. Used by FRR versions 2.0 through 3.0.3. - Version 5 Increased VRF identifier field from 16 to 32 bits. Used by FRR versions 4.0 through 5.0.1. - Version 6 Removed the following commands: * ZEBRA_IPV4_ROUTE_ADD * ZEBRA_IPV4_ROUTE_DELETE * ZEBRA_IPV6_ROUTE_ADD * ZEBRA_IPV6_ROUTE_DELETE Used since FRR version 6.0. Zebra Protocol Definition ========================= Zebra Protocol Header Field Definitions --------------------------------------- Length Total packet length including this header. Marker Static marker. The marker value, when it exists, is 255 in all versions of Quagga. It is 254 in all versions of FRR. This is to allow version 0 headers (which do not include version explicitly) to be distinguished from versioned headers. Version Zebra protocol version number. Clients should not continue processing messages past the version field for versions they do not recognise. Command The Zebra protocol command. Current Version ^^^^^^^^^^^^^^^ :: Version 5, 6 0 1 2 3 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ | Length | Marker | Version | +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ | VRF ID | +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ | Command | +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ Past Versions ^^^^^^^^^^^^^ :: Version 0 0 1 2 3 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ | Length | Command | +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ :: Version 1, 2 0 1 2 3 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ | Length | Marker | Version | +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ | Command | +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ :: Version 3, 4 0 1 2 3 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ | Length | Marker | Version | +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ | VRF ID | Command | +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ Zebra Protocol Commands ----------------------- +------------------------------------+-------+ | Command | Value | +====================================+=======+ | ZEBRA_INTERFACE_ADD | 0 | +------------------------------------+-------+ | ZEBRA_INTERFACE_DELETE | 1 | +------------------------------------+-------+ | ZEBRA_INTERFACE_ADDRESS_ADD | 2 | +------------------------------------+-------+ | ZEBRA_INTERFACE_ADDRESS_DELETE | 3 | +------------------------------------+-------+ | ZEBRA_INTERFACE_UP | 4 | +------------------------------------+-------+ | ZEBRA_INTERFACE_DOWN | 5 | +------------------------------------+-------+ | ZEBRA_INTERFACE_SET_MASTER | 6 | +------------------------------------+-------+ | ZEBRA_ROUTE_ADD | 7 | +------------------------------------+-------+ | ZEBRA_ROUTE_DELETE | 8 | +------------------------------------+-------+ | ZEBRA_ROUTE_NOTIFY_OWNER | 9 | +------------------------------------+-------+ | ZEBRA_REDISTRIBUTE_ADD | 10 | +------------------------------------+-------+ | ZEBRA_REDISTRIBUTE_DELETE | 11 | +------------------------------------+-------+ | ZEBRA_REDISTRIBUTE_DEFAULT_ADD | 12 | +------------------------------------+-------+ | ZEBRA_REDISTRIBUTE_DEFAULT_DELETE | 13 | +------------------------------------+-------+ | ZEBRA_ROUTER_ID_ADD | 14 | +------------------------------------+-------+ | ZEBRA_ROUTER_ID_DELETE | 15 | +------------------------------------+-------+ | ZEBRA_ROUTER_ID_UPDATE | 16 | +------------------------------------+-------+ | ZEBRA_HELLO | 17 | +------------------------------------+-------+ | ZEBRA_CAPABILITIES | 18 | +------------------------------------+-------+ | ZEBRA_NEXTHOP_REGISTER | 19 | +------------------------------------+-------+ | ZEBRA_NEXTHOP_UNREGISTER | 20 | +------------------------------------+-------+ | ZEBRA_NEXTHOP_UPDATE | 21 | +------------------------------------+-------+ | ZEBRA_INTERFACE_NBR_ADDRESS_ADD | 22 | +------------------------------------+-------+ | ZEBRA_INTERFACE_NBR_ADDRESS_DELETE | 23 | +------------------------------------+-------+ | ZEBRA_INTERFACE_BFD_DEST_UPDATE | 24 | +------------------------------------+-------+ | ZEBRA_IMPORT_ROUTE_REGISTER | 25 | +------------------------------------+-------+ | ZEBRA_IMPORT_ROUTE_UNREGISTER | 26 | +------------------------------------+-------+ | ZEBRA_IMPORT_CHECK_UPDATE | 27 | +------------------------------------+-------+ | ZEBRA_BFD_DEST_REGISTER | 28 | +------------------------------------+-------+ | ZEBRA_BFD_DEST_DEREGISTER | 29 | +------------------------------------+-------+ | ZEBRA_BFD_DEST_UPDATE | 30 | +------------------------------------+-------+ | ZEBRA_BFD_DEST_REPLAY | 31 | +------------------------------------+-------+ | ZEBRA_REDISTRIBUTE_ROUTE_ADD | 32 | +------------------------------------+-------+ | ZEBRA_REDISTRIBUTE_ROUTE_DEL | 33 | +------------------------------------+-------+ | ZEBRA_VRF_UNREGISTER | 34 | +------------------------------------+-------+ | ZEBRA_VRF_ADD | 35 | +------------------------------------+-------+ | ZEBRA_VRF_DELETE | 36 | +------------------------------------+-------+ | ZEBRA_VRF_LABEL | 37 | +------------------------------------+-------+ | ZEBRA_INTERFACE_VRF_UPDATE | 38 | +------------------------------------+-------+ | ZEBRA_BFD_CLIENT_REGISTER | 39 | +------------------------------------+-------+ | ZEBRA_BFD_CLIENT_DEREGISTER | 40 | +------------------------------------+-------+ | ZEBRA_INTERFACE_ENABLE_RADV | 41 | +------------------------------------+-------+ | ZEBRA_INTERFACE_DISABLE_RADV | 42 | +------------------------------------+-------+ | ZEBRA_IPV3_NEXTHOP_LOOKUP_MRIB | 43 | +------------------------------------+-------+ | ZEBRA_INTERFACE_LINK_PARAMS | 44 | +------------------------------------+-------+ | ZEBRA_MPLS_LABELS_ADD | 45 | +------------------------------------+-------+ | ZEBRA_MPLS_LABELS_DELETE | 46 | +------------------------------------+-------+ | ZEBRA_IPMR_ROUTE_STATS | 47 | +------------------------------------+-------+ | ZEBRA_LABEL_MANAGER_CONNECT | 48 | +------------------------------------+-------+ | ZEBRA_LABEL_MANAGER_CONNECT_ASYNC | 49 | +------------------------------------+-------+ | ZEBRA_GET_LABEL_CHUNK | 50 | +------------------------------------+-------+ | ZEBRA_RELEASE_LABEL_CHUNK | 51 | +------------------------------------+-------+ | ZEBRA_FEC_REGISTER | 52 | +------------------------------------+-------+ | ZEBRA_FEC_UNREGISTER | 53 | +------------------------------------+-------+ | ZEBRA_FEC_UPDATE | 54 | +------------------------------------+-------+ | ZEBRA_ADVERTISE_DEFAULT_GW | 55 | +------------------------------------+-------+ | ZEBRA_ADVERTISE_SUBNET | 56 | +------------------------------------+-------+ | ZEBRA_ADVERTISE_ALL_VNI | 57 | +------------------------------------+-------+ | ZEBRA_LOCAL_ES_ADD | 58 | +------------------------------------+-------+ | ZEBRA_LOCAL_ES_DEL | 59 | +------------------------------------+-------+ | ZEBRA_VNI_ADD | 60 | +------------------------------------+-------+ | ZEBRA_VNI_DEL | 61 | +------------------------------------+-------+ | ZEBRA_L3VNI_ADD | 62 | +------------------------------------+-------+ | ZEBRA_L3VNI_DEL | 63 | +------------------------------------+-------+ | ZEBRA_REMOTE_VTEP_ADD | 64 | +------------------------------------+-------+ | ZEBRA_REMOTE_VTEP_DEL | 65 | +------------------------------------+-------+ | ZEBRA_MACIP_ADD | 66 | +------------------------------------+-------+ | ZEBRA_MACIP_DEL | 67 | +------------------------------------+-------+ | ZEBRA_IP_PREFIX_ROUTE_ADD | 68 | +------------------------------------+-------+ | ZEBRA_IP_PREFIX_ROUTE_DEL | 69 | +------------------------------------+-------+ | ZEBRA_REMOTE_MACIP_ADD | 70 | +------------------------------------+-------+ | ZEBRA_REMOTE_MACIP_DEL | 71 | +------------------------------------+-------+ | ZEBRA_PW_ADD | 72 | +------------------------------------+-------+ | ZEBRA_PW_DELETE | 73 | +------------------------------------+-------+ | ZEBRA_PW_SET | 74 | +------------------------------------+-------+ | ZEBRA_PW_UNSET | 75 | +------------------------------------+-------+ | ZEBRA_PW_STATUS_UPDATE | 76 | +------------------------------------+-------+ | ZEBRA_RULE_ADD | 77 | +------------------------------------+-------+ | ZEBRA_RULE_DELETE | 78 | +------------------------------------+-------+ | ZEBRA_RULE_NOTIFY_OWNER | 79 | +------------------------------------+-------+ | ZEBRA_TABLE_MANAGER_CONNECT | 80 | +------------------------------------+-------+ | ZEBRA_GET_TABLE_CHUNK | 81 | +------------------------------------+-------+ | ZEBRA_RELEASE_TABLE_CHUNK | 82 | +------------------------------------+-------+ | ZEBRA_IPSET_CREATE | 83 | +------------------------------------+-------+ | ZEBRA_IPSET_DESTROY | 84 | +------------------------------------+-------+ | ZEBRA_IPSET_ENTRY_ADD | 85 | +------------------------------------+-------+ | ZEBRA_IPSET_ENTRY_DELETE | 86 | +------------------------------------+-------+ | ZEBRA_IPSET_NOTIFY_OWNER | 87 | +------------------------------------+-------+ | ZEBRA_IPSET_ENTRY_NOTIFY_OWNER | 88 | +------------------------------------+-------+ | ZEBRA_IPTABLE_ADD | 89 | +------------------------------------+-------+ | ZEBRA_IPTABLE_DELETE | 90 | +------------------------------------+-------+ | ZEBRA_IPTABLE_NOTIFY_OWNER | 91 | +------------------------------------+-------+ | ZEBRA_VXLAN_FLOOD_CONTROL | 92 | +------------------------------------+-------+ frr-7.2.1/doc/extra/0000755000000000000000000000000013610377563011164 500000000000000frr-7.2.1/doc/extra/frrlexer.py0000644000000000000000000000301713610377563013310 00000000000000# -*- coding: utf-8 -*- # Copyright (c) 2017 Vincent Bernat # # Permission to use, copy, modify, and/or distribute this software for any # purpose with or without fee is hereby granted, provided that the above # copyright notice and this permission notice appear in all copies. # # THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES # WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF # MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR # ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES # WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN # ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF # OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. from pygments.lexer import RegexLexer, bygroups from pygments.token import Text, Comment, Keyword from pygments.token import String, Number, Name class FRRLexer(RegexLexer): name = "frr" aliases = ["frr"] tokens = { 'root': [ (r'^[ \t]*!.*?\n', Comment.Singleline), (r'"(\\\\|\\"|[^"])*"', String.Double), (r'[a-f0-9]*:[a-f0-9]*:[a-f0-9:]*(:\d+\.\d+\.\d+\.\d+)?(/\d+)?', Number), # IPv6 (r'\d+\.\d+\.\d+\.\d+(/\d+)?', Number), # IPv4 (r'^([ \t]*)(no[ \t]+)?([-\w]+)', bygroups(Text, Keyword, Name.Function)), (r'[ \t]+', Text), (r'\n', Text), (r'\d+', Number), (r'\S+', Text), ], } frr-7.2.1/doc/figures/0000755000000000000000000000000013610377563011505 500000000000000frr-7.2.1/doc/figures/cligraph.png0000644000000000000000000012057213610377563013733 00000000000000‰PNG  IHDRwÉ,8V\gAMA± üa cHRMz&€„ú€èu0ê`:˜pœºQ<bKGDÿÿÿ ½§“ pHYsZZp#¸}tIMEâ&iÿ‡€IDATxÚìÝ}\÷ÿð×¹«$¡;B1$¤¤5¹ s7aÌ–¬Ü¬¾óýbfî cóÛ˜ÙÄîø~çnsϘ)›%fb’eaÌÍ$ZJä6:Ý|~´gîë\ç\½žGº®Ïù\ïsu¼|úœë|.…B€ˆˆdE)uDDTõîDD2Äp'"’!µÔPåÌ›7ÉÉÉR—Q# >Ï=÷œÔe• ÃÝÌíÚ½ yyhÞ¶¹Ô¥Èڡ݇àííÍp'³Áp—€á>y¸ÔeÈÚïR—@T.œs'"’!†;‘ 1܉ˆdˆáND$C w""b¸ÉÈH†îDD2Äp'"’!†;‘ 1܉ˆdˆáND$C w""b¸Éý†‰‹ŽÃ+ϼÿÚþè^§;†µ†ÿ݉n6Ýà«ð5øµqÙF@Ò¹$ݶ™A3õúí¨î¨Û×Õº+‚½‚³#F·¿,ýRÚã {;ã6àë¥_ÃWá‹¡m†"#5C¯¶&| ëw´ÿhø*|ñ{üïRÿHˆª×s¯A>xˆé/N@ðÄ`XXZàÒ™KH>ŸŒÐ™¡Ðfk‘òg ~Øôœ;áù°çíüÚö¬ÛPª”øù»Ÿq7ó.lëÛêc삱ÈHÍÀŽ/v |x8~Hý¶v¶eêßò„Z£Æœ‘sðýÆï ¶:~(º6,ñ?3"9`¸× õê¡OPü~üw¬_²1ßÄàégŸÆ’o–”ø¸ÂÑùKc_³CžÅ³CžEßa}ÑίÒ¯¥#>&¾Èc&/™ øß»ÿûŒòüºtÁGß~µFy£æ!ꫨ"m,,-0vÁX£ÔC$%…BH]Uœ—·ú†õÅðÉÃ¥.EÖFxÀ¸°q˜}úÀÞÞ^êòˆd‡áNFáää„nݺé]Ç®R©ðÊ+¯H]‘,1ÜÉhBCC¡R©tß+•J |XoŸ¿¿? (( .„Z­Æ¬Y³àïïíÛ·£sçθzõj‘c,X°¡¡¡8uê†npi„‰'bèСHJJBxx8 !D±uÏœ9ááá 4nÜááᇟŸŸ^ÛE‹aúôéðððÀÁƒáì쬷ÕªU¸|ù²Ô? "ãd–233‘P¦ö7nÜDƒ „B„‡‡ Ñ¡C,„¢K—.€8~ü¸8vì˜ lllDFF†®Ÿ—^zIS§NÕmS©T€¸qã†B@œ8q¢Ø6×®]õë×Dddd©õïÝ»W~~~zÛ û4i’ ¼¼¼Dzzz‘6*•JØØØˆ#F!„èÚµ«î¹–Õ¤I“Ä /¼P­?W¢ªÂ‘{ áàà€6mÚ -- .\ÀáÇѵkWtïއƣGx{{ë¦+|}}õîqÚ¿08‘žžŽØØX¤§§ÃÑÑîîîÅÖÓ¸qcôë×[éç777ÄÄÄÀÑѱÈ~µZ)S¦`ãÆºi("9c¸× …S3ÑÑш‹‹C·nÝн{w\»v ›7o†V«E§N VWlÉ!øûûC£Ñ`Ïž=¨]»v‰íóóó@ww¦ÊP©T¸|ùr‰«6NŸ>öööxë­·ªë™ †{ RîË—/ÇÇáïﯛ__¼x1€Çóí¾¾¾ Fè…oV@TT”Þþ'mذcÇŽÅýû÷1vìXäää[KJJ öíÛèÒ¥K¥ŸÛêÕ«¡R©‚Í›7lckk‹Ù³g#** gΜ1îÉ'26©ç…¨bÊ;ç.„III€ ¬­­…V«Báîî®Û¾ÿ~]ûAƒ ¢U«VböìÙºïíííErr²®Ý?çÓ çî#""Š´™2eŠ=z´n¾½_¿~"??¿ÔÚK›s¿qã†Ø»w¯°²²*•JlذA¯¥¥¥BˆG WWWÝóåœ;ÉGî5HÓ¦MáââðóóƒF£ðxD¯V«Ñ©S']ûmÛ¶aΜ9ÈÍÍÅ’%K‹¡C‡âèÑ£º~ Y²d àÝwßÅÝ»wõö-_¾[·n…««+.\ˆÝ»wWÉ´ àÛo¿…F£Á¨Q£ðÕW_icii‰ HøS 2…%\‡F&ëöíÛ¨_¿>Ìîfæªðf»ví’º¢RqäND$C wª2í¦Ša@ا嬡O ÎŸ?_ê§Dd¶x›=2 œ$ªZ¹ÉÊhذ¡ÞŸ°çÐ9¨Ô4{xá íŽá>>üh<¨ÿ/`ä àÞÃÇûòò¾ÚL,‡uC¾ï‡Î>n³ö`ÁO­€6·`Û° ¶½úyÁ÷·ª— ¶í8f¸n¢šŠáN¥JÍ—¿\<\€çÚ¶µ€ïN™÷‹¶ß¼ù5p'« €7ü ,ÚõxÿèÏ™€s)@SG@¥¢O½ûO´ééQðç£à׿×ú:|NÿÏØ?€|(@¶RŸ%"ÓÂp§"œœœôþ¼”V0z®m ìœ|û&ø!òàêPôñÙ¹Àɀ̵€ÏSÛöŸ.øó|*°î§‚¿Oœþü¤ äóòy[ ö5ušý½DÌás5¤fü§r!H¿ó8äÛ¹uŠÖMT“1Ü©ˆÂ…µ ÿôtœê²ÿþ¯`ä^Ë¢èã{·¼šàݬ`[Úí‚?ã/=nÒµàÏzµŠîïÙ®àÏØsƒüµç þ<üĶg= ×MT“1Ü©Tu­ãﳆ½ÚB?üŒ]¬Œ.ÚÞÑöñß5_U‘kažý{ª%ö‚ ·Ô“LÃÄœ~üAá=Æp§RÝ{X0·ýþp`ÿ< uÕã9î_Η¯/ßÿ¾éï•~ïdQ E÷ŽÜoܶ|›Îõ&S;r8ßNT^çN¥J¹´Z0·ÝÔ±`þý翯lyºyùújå „ö(çe‘@ä¯ÀÍ{À­ûo¬¾3ìq[{ yàÏ´‚ÿº¶.Øîß8ý÷ ¼\;©Ï‘éáÈJe_êdeL‡>´jü_0! üý}ù°xк1”ää½= ~+èí©ßöÉ)ÿ¿Ã½[›ÇÛžå” ‘A\8ÌLqá0ããÂadN8r'"’!†;‘ 1܉ˆdˆáND$C w""b¸ÉÈH†îDD2Äp'"’!†;‘ 1܉ˆdˆáND$C\ò×ÌÍ›7vvvR—Q#ÄÅÅ¡U«VR—AT& w3eaaÐÐP©Ë¨ÄÄD8::ÂÙÙYêRÊ¥cÇŽðññ‘º ¢2á’¿dt:t@hh(&Ož,u)D²Å9w""b¸ÉÈH†îDD2Äp'"’!†;‘ 1܉ˆdˆáND$C w""b¸ÉÈH†îDD2Äp'"’!†;‘ 1܉ˆdˆáND$C w""b¸ÉÈH†îDD2Äp'"’!†;‘ 1܉ˆdˆáND$C w""b¸ÉÈH†îDD2Äp'"’!µÔ¼¥¤¤àìÙ³zÛîÝ»‡óçÏ#::Zo{¯^½ Tr¼ATB!u$_‰‰‰hß¾}©í¼½½‘ u¹D²ÁaU+///¸¹¹•ØÆÒÒ#FŒºT"Ya¸Sµ …¥¥e±ûµZ-‚ƒƒ¥.“HVîTíBBB Õj îS(ðóóC“&M¤.“HVîTíš7ooooƒû4 ¤.‘Hvîdaaa°°°(²=//C‡•º<"Ùa¸“Q#77Wo›R©DŸ>}`oo/uyD²Ãp'£prrB·nÝô®cW©Txå•W¤.H–îd4¡¡¡P©Tºï•J%,uYD²Äp'£ „B¡¨Õj <666R—E$K w2[[[@¥RA‘#GJ]‘l1ÜɨF…¼¼<ÔªU R—C$[\8L&Ƭ¬,©Ë(U^^Ôj5œ1fÌ©Ë)“çž{Ç—º ¢ra¸ËÄ×6µ­¹J]J)T°oÜÖ ›#éžÔµ”îÌÑÔ«WáNf‡á.#Â&♾C¤.£T—O@³Öí¡0ƒå}?7Dêˆ*„áNF÷TÛR—@${¦?t""¢rc¸ÉÈH†îDD2Äp'"’!†;‘ 1܉ˆdˆáND$C w""b¸ÉÈH†îDD2Äp'"’!†{ ÜJa-z_ÿ3£ÍÆ–æâž-ânÑO;࣠AHMºPl#=¬1}€Ž}¿Ã`›{™€ïþ·ÃZ(0å¹6ÈLO-¶¾Qž6Eê+üŠ\³¬Lý¶ùrþ]¿s‡ùcX .Š—úG@T­¸äo ÷üè©°°ªhéÕðÑ„ œØÿ5wÇq³|.¿ìÝŽ3Gàƒ= °wvÑëcØäÈLOÅ¿@ÄäáXõK*lêÙ9Ö®/aã’·àâæy_ïG]‡ÅÖ5dìLäh³‘–ü'b¿Û»ñìÐ0€›·_¹úÞ´ ƒFOƒ“ËSRŸn"£a¸×p/¾öêÔwÐ}ñ·8œØÿ¬¬mðî¶XÔ©gX:>Ç~ø‘k–á•ÙKõúè7r<êÔwÀ¯"q3õ*n¤$ ÷Ÿ,DÔÚ4mí…¹_EÃÖαĺ'Ìœ<ô=b¿Û{ç&žº°H»ÒúUªTÐXXbËÇsñÆG_K}º‰Œ†Ó25Üh_GÝtÇÁkuÓ-<}uÁÞ=ú€ÁéŒ;7ÓñljXܽ™[;G4zʽH›¨µpnæ†ybJ öò(­_•J¯NÁáÝqå\¢Ô§›Èh8r¯áú‡NÔM˸º{âÂÉcåîcj?€•µ fýo,­ki£T©~õ2N‰AçAUVYúüŸéØ·ásl\ò–1N)‘IàȽ†úÆ\ŒxsF¼¹ÍÛ=ž¾ Fè…oV@ÂÁ(ÐíÒÄ7 oÈX<ʺUsÆ"/7§H›×¯†R¥BÄäÄîÙ\eõ—¥ßZ6¶xiül$ŒÂµ g$=ßDÆÂp'=-ÛwÄÓ½áQÖ}Ì êŠMKñxÌ`ÄíÛ‰:õì1ð_“‹<¦}·çðŸ…_Àݧ ’~?‰}>/ÒÆçÙ˜±rTj VL‰Ã»7VI½eí÷¹‘ãáÐÈîÞ–úʘúÉ6¾>ù¹¹Ø½j þ8‹Ný‡bᎣE®”yÒÈ·–¶¯xïß-²ß»{Þ\ùmAO…C;¿ª’zËÒ¯ÆÂÃ&/úÔB!¤.‚*϶n=Œû`-žé;DêRdåÃqCйm3,[¶LêRˆÊ…#w""âÕ2$©a-E¶M|A“æK]‘Yc¸“¤¶^⬠Quà´ ‘ 1܉ˆdˆáND$C w""b¸ÉÈH†îDD2Äp'"’!†;‘ 1܉ˆdˆáND$C w""b¸ÉÈH†îDD2Äp'"’!Þ¬CF¢Ö.Gܾ]R—!+žþÛ6“º ¢rc¸ËÄÈÑ••%i ‡BÛ¶máààPá>îܹƒÔÔT´nÝZÒçR¨Ùs½Ð±cG©Ë *7…‚÷9£Jûá‡ðüóÏãâÅ‹hÚ´i…ûÙ»w/^|ñEdddÀÆÆFê§Ed¶8çNUbÉ’%xùå—+ìгgO¨T*ÄÄÄHý”ˆÌÃ*í·ß~CLL ¦M›V龬¬¬Ð«W/ìÝ»Wê§EdÖîTi‹/FŸ>}СC‡*é¯ÿþˆŒŒ”úi™5ιS¥\¹r-[¶DTTúöí[%}&''£iÓ¦8uêÚµk'õS$2K¹S¥|üñÇhÛ¶-úôéSe}ººº¢mÛ¶ˆŠŠ’úé™-†;UXff&¾üòK¼ùæ›P(UÚ÷€8ïNT wª°Ï>û õêÕðaê¼ïþýûãðáø}û¶ÔO“È,1Ü©B²³³ñé§ŸbêÔ©Ðh4UÞ·nÝP»vmDGGKýT‰ÌÃ*dýúõxðà^}õÕjé_£Ñ wïÞœw'ª †;•›ü1ƺuëVÛqúôéƒýû÷Kýt‰ÌÃÊm÷îÝøóÏ?ñÆoTëqzõê…ääd\ºtIê§LdvîTnK–,Áˆ#ШQ£j=Ž»»;š4iÂÑ;Q0Ü©\âââpäÈL:Õ(ÇëÙ³'8 õÓ&2; w*—E‹aàÀððð0Êñzö쉘˜ðƒÔDåÃå¨ÌþüóO´jÕ 111èÞ½»QŽÉ¥ˆ*†#w*³>ø>>>F v `)‚-Zp `¢rb¸S™¤§§cýúõ˜9s¦ÑÝ«W/λ•ÃÊä“O>³³3† bôc÷ìÙD^^žÔ§Èl0Ü©TYYYøüóÏ1}út¨T*£¿wïÞ¸sç¤>DfƒáN¥Z½z5„xå•W$9¾““<<<8ïNT w*Q^^–-[†7Þxµk×–¬ŽnݺáÈ‘#RŸ"³Áp§íر©©©?~¼¤uøùùáèÑ£RŸ"³Áp§}ôÑG ƒ£££¤uøùù!==IIIRŸ"³Àp§b„HHHÀúõë‘››‹•+W"++Kê‹èر#.]º„ÌÌL©K!2i ÷ìæÍ›zËèæçç#??7oÞÄôéÓѨQ#¼ûî»çå¥âéé ¥RÉ7U‰JÁp¯ÁÒÒÒŠÝ—››‹;wî`Þ¼yøú믥.UÇÊÊ nnn w¢RðCL5Xjj*”J%òóó îW©T˜;w.&Mš$u©z¼¼¼pêÔ)©Ë 2i¹×`ׯ_‡F£1¸O­Vc„ xûí·¥.³OOOŽÜ‰JÁp¯ÁÒÒÒ ^î¨Ñh0fÌ,[¶Lê òôôÄéÓ§‹ýƒˆî5ZZZZ‘û’j4cÅŠR—W¬víÚáÁƒ¸råŠÔ¥™,†{ –ššŠœœÝ÷xþùç±fÍ“üS¡fÍšÁÒÒçÏŸ—º"“eºÿ‚©Ú%''£ð3lèׯ¶nÝ*ÉM°ËC¥R¡yóæ w¢0Ük°ÔÔTÁÞµkWlÛ¶ jµy\@ÕªU+~J•¨ ÷,## …:uBTT,--¥.©ÌZµjÅ‘;Q Ìc˜&¬¬,Éo ]„¸sçìííáââ‚qãÆI]ÀÇÇ'N,µ››¶oß.u¹D&‹#÷bhµZ¬[··nÝ’º”jñðáCÔ¯_}ûö5™©˜¸¸8ÄÄÄ”©móæÍqõêUäææJ]6‘I2Õ&ìwÞ···ÔeT¹7n@¥RÁÎÎNêRt&OžŒ¤¤¤2µuuuEnn.RSSáââ"uéD&‡#÷ÊÑÑѤ‚½¼\\\ P(põêU©K!2I w2KVVVppp`¸ƒáNfËÅÅ…áNT †;™-WWW†;Q1îd¶\\\œœ,uD&‰áNf‹Ó2DÅc¸“Ùb¸áNfËÕÕ7nÜÀ£G¤.…Èä0ܫȴiÓ P(°|ùrÀ½{÷ V«¡P(tw ŠŒŒ„B¡@`` ;;sçÎEË–-aaaY«°…BkkkxyyaÇŽÛ¨ÕjÔ­[>>>xï½÷ ÕjK¬ÛÆÆF×÷?¿–-[¦ë7##°téR( ´iÓF·ðXa› &èúõ÷÷‡B¡@|||µs!píÚ5cüˆ‰Ì ýŠtëÖ ðóÏ?Ž9¢»ÆáÇõöùûû‚‚‚°páB¨ÕjÌš5 þþþؾ};:wîlpºaÁ‚ Å©S§0|øpƒK#Lœ8C‡ERRÂÃÃ1xð`ݲ¾†Ìœ9ááá 4nÜááᇟŸŸ^ÛE‹aúôéðððÀÁƒáì쬷ÕªU¸|ù²ÑÎy£F V«95Cdˆ ƒ233‘P¦ö7nÜDƒ „B„‡‡ Ñ¡C,„¢K—.€8~ü¸8vì˜ lllDFF†®Ÿ—^zIS§NÕmS©T€¸qã†B@œ8q¢Ø6×®]õë×Dddd©õïÝ»W~~~zÛ û4i’ ¼¼¼Dzzz‘6*•JØØØˆ#F!„èÚµ«î¹–Õ¤I“Ä /¼P®ŸS“&MÄúõëËÿ&’9ŽÜ«ˆƒƒÚ´iƒ´´4\¸p‡F×®]ѽ{w>|=B||dy³STx³Ž]»v•ù1ï¾û.~üñG:tHêò‰L GîdÖ,--‘-uD&‡ánæÚMÀ°OKngè¨óçÏ—ºüJ³°°`¸À{¨Ör}³´´,u‰¢šˆánæ~] ä @UC³°°`¸PC#Á<5lØPïOð™ Ôüç‹Çí §j†GSÖŽ£Ú£€àöƒâû2GZ­R—Adrî2¶åõ+ÐÅÐæ›cW?¯|¿¦$;;–––R—AdrîfÄÉÉIïÏÒ4¨$||û&ðþð‚m;〠©åïËTqäNdÃÝŒ.ÈUÖ…¹ººÖjûz=Þ~újùû2U¹Æp—±œ¼Ç׿J]MõÈÊÊB­Zµ¤.ƒÈä0ÜeìÀiàÚÍ‚¿ýÄ8=\*ÖŸ)ºuë–Þ’ÉDT€—BÊX¾Ü'u€ä¿žò ÐʹRÝš”[·n¡AƒR—Adr8r—±@?`Ö ;§`î}XgàËפ®ªjݺu vvvR—Adr8r7s§—–¼n`Á—\1܉ ãÈÌZFFç܉ `¸“ÙÊÍÍEjjj‰kßÕTœ–‘¡Ò¦jä⯿þB^^\]]¥.…ÈäpäNf+99 …7–º"“Ãp'³uõêU899ñªD0ÜÉl]½z•óíDÅ`¸“Ùºzõ*çÛ‰ŠÁp'³•œœÌ‘;Q1îd¶8-CT<†;™-†;Qñîd–>|ˆ›7o2܉ŠÁp'³tõêU!îDÅà'TK1oÞ<.Le$qqqhÕªU™Ú^½zjµÚìoðMT]î۰°@hh¨ÔeT«Ó§OâÌZÝ:vìŸ2µ½té\]]¡Vó%LdÿeÃÚÚk×®•ºŒjõÞ{ïáóÏ?ÇO?ýF#u9åòÇ uëÖR—Ad²8ç^ƒ½öÚk¸}û6¶nÝ*u)åvîÜ9¸»»K]‘Éb¸×`õë×ÇèÑ£ñÁ@!u9åÂp'*ý†›6mΞ=‹èèh©K)³ììl\¹r…Ó2D%`¸×p...:t(–,Y"u)evþüyäåå1܉JÀp'Ìœ9ÑÑÑHHHº”29wîêÕ«‡ H] ‘Éb¸Ú·o^½zaéRó¸…Óùóç9ßNT †;f̘-[¶àÊ•+R—RªS§NÁÃÃCê2ˆLÃýúõC»ví°lÙ2©K)Õ‰'Êüa'¢šŠáN:S§NÅÿû_ܼySêRŠu÷î]üùçŸ w¢R0ÜI'88öööXµj•Ô¥+!! …^^^R—BdÒÑh0qâDDDDàÑ£GR—cЯ¿þŠÖ­[£víÚR—BdÒî¤g̘1ÈÎÎÆ† ¤.Å „„NÉ•ÃôÔ©ScÆŒÁ’%KŸŸ/u9E$$$ C‡R—AdòîTĤI“pùòeDFFJ]Šž‡âܹs¹•ÊhÔ¨FŒarK$$$ ??ÞÞÞR—BdòîdЛo¾‰ØØX=zTêRtbccÑ®];Ô­[WêRˆLà jݺ5ú÷ïoRKÄÆÆ¢k×®R—AdîT¬3f`çθpá‚Ô¥~ùåtéÒEê2ˆÌÊգGtìØü±Ô¥àÂ… HKKãȨŒîT¢©S§bÍš5HKK“´ŽØØX4hÐO=õ”Ô§„È,0Ü©D/½ôš4i‚Ï?ÿ\Ò:Ž9©O‘Ù`¸S‰T*&OžŒ+VàÁƒ’ÕÁ7S‰Ê‡áN¥ú׿þ¥R‰uëÖIrü›7oâܹs|3•¨îT*kkkŒ?~ø!òòòŒ~üƒ¢víÚüd*Q90Ü©L&L˜€ëׯcçÎF?öн{wh4©O‘Ù`¸S™8::"44|ðуž={J} ˆÌŠB!¤.‚Ìßþ‰V­Z!&&Ý»w7Ê1ÓÒÒàììŒøøxNË•GîTfÍ›7Ç /¼`ÔÅöïߺuë¢}ûöR?}"³Âp§r™5k"##qæÌ½íùùùÈÌ̬Tß¿ýöÖ­[§÷©àÙgŸ…J¥’ú©™†;•Ë3Ï<ƒ®]»â£>|Xé‘{á´ÌŒ3¸¦ Q%ðRHJNNƼyóŒzÌôôtìÛ·Íš5ƒ¿¿…ûùõ×_‘’’‚Vê’ÊÊpuuÅ;ï¼#ɱ‰ª Ã]†Nž<‰:`P×hÔ•ï°ŒnÞIÇ…k¿£“dzîãbÊY4¨ßu¬ëV¸Ê¸xíw –¿:)Éñ‰ª —±wþýlk×3ê1/§žÇSέ*üxmN6,4–F­ùIk"—a÷‰µ’Ÿ¨ªpΪTe‚€¤ÁN$' w""b¸ÉÈH†îDD2Äp'"’!†;‘ 1܉ˆdˆáND$C w""b¸ÉÈH†îDD2Äp'"’!†;¡U°-†)y/@Á²»m™‹žo´„{ˆží€ !)õB‰+ü~þ—tm†ÍõG‹a œº_b ž£lÐb˜ÂàךÈe÷¿ï–¢Å0ž›Ò险•:>‘Ü0Ü©ˆ áÓ ¡V©1nÈ,ø¶öÇÞ_¶#pNg¤Þ¼Zêã7E¯ÂÕôËå:æØ!3ñúKáÔ5ÐÀ®1^)¯¿o7¿"í¿Øµï5n.Ø8ÿ œê;WêøDrÛužß.Æaÿ‰ï`meƒmïÆ¢^‚V_ˆŽ}ƒ5‘Ë0û•¥Å>^¥TÁBc‰·ÌÅGo|]æãNœ 8tò{|» ÎöM05x¡Á¶ŸìXˆµQhÝÔ _͆­c¥O$7¹“žÂé Ͼº`€ÞýõöG¥RãÕS°ûðFœ»’X-5®Š@3g7l˜£ìÆ:>‘9`¸S•ûÏàé¨gc%ߪ–þUJ®¦_Æ‘Ó1’ŸÈ0ÜIg _#ôÂ7.à`B”Þþ’ØÔ²Åø—fã`B.\;Så5.~m5TJ&G„`Oìf£ŸÈ0ÜIOû–ÑûéAÈztAsºbé¦pŒY<ûâv¢^{ükàä2õ3ò¹ñhäàŠ»nWyÏú ÀÊ» Vi0uÅHì>¼Ñ¨Ç'2 ÷îîƒÛÈËσJ©‚M-[À'S·áõÀ9ÈÍÏŪÝKpâXôï4;…³½K™úµÐXbò°ÕVwwï¬|ó[¨UL[1 ;}eÔã™:…BH]U­“'O¢C‡HX“ ÛÚõŠm·÷—íXù1~ýãüÚöÀÆù¥.]rk"—a÷‰µøíÔI©K!ªŽÜk°¿F")õüñá„õR—CDUˆ×¹×`Œ_côc¶¦(²mbÐÛ˜4_êÓA$+ w2ªK[9 Hd œ–!"’!†;‘ 1܉ˆdˆáND$C w""b¸ÉÈH†îDD2Äp'"’!†;‘ 1܉ˆdˆáND$C w""b¸ÉÈH†îDD2Ä›uÈØ¼ÿ‡Fm!ufåâµßZRWATy w²³³Chh¨Ôe+11ŽŽŽpvv–º”":4k WWW©Ë ª4…‚÷=#£êСBCC1yòd©K!’-ιÉÈH†îDD2Äp'"’!†;‘ 1܉ˆdˆáND$C w""b¸ÉÈH†îDD2Äp'"’!†;‘ 1܉ˆdˆáND$C w""b¸ÉÈH†îDD2Äp'"’!†;‘ 1܉ˆdˆáND$C w""b¸ÉÈH†îDD2Äp'"’!†;‘ ©¥.€ä-%%gÏžÕÛvïÞ=œ?ÑÑÑzÛ{õꥒ㠢ª B©‹ ùJLLDûöíKmçíí„„©Ë%’ “¨ZyyyÁÍÍ­Ä6–––1b„ﴃ ̻]hh(,--‹Ý¯Õj,u™D²Âp§j­VkpŸB¡€ŸŸš4i"u™D²Âp§j×¼ysx{{ܧÑh&u‰D²Ãp'£ ƒ……E‘íyyy:t¨ÔåÉÃŒ"88¹¹¹zÛ”J%úôé{{{©Ë#’†;…““ºuë¦w»J¥Â+¯¼"uiD²Äp'£ …J¥Ò}¯T*1xð`©Ë"’%†;M``  @­VcðàÁ°±±‘º,"Yb¸“ÑØÚÚ" *• BŒ9Rê’ˆd‹áNF5jÔ(äåå¡V­Zº"ÙâÂaf*++ ãÇ—ºŒrËË˃Z­†³³3ÆŒ#u9åæãド'J]Q©8r7SZ­ë֭í[·¤.¥\T*š6mŠæÍ›K]J¹ÅÅÅ!&&Fê2ˆÊ„#w3÷Î;ïûéOS•€öíÛ›Ýò¾“'OFRR’Ôe• ÃŒ®C‡R—@${æ5t""¢2a¸ÉÈH†îDD2Äp'"’!†;‘ 1܉ˆdˆá^CL›6 …Ë—/Ü»wjµ …‰‰‰€ÈÈH( ²³³1wî\´lÙppp@PP.\¸ ×wa? …ÖÖÖðòòÂŽ; ¶Q«Õ¨[·.|||ðÞ{ï{oÕB666º¾ÿùµlÙ2]¿€¥K—B¡P M›6HMMÕ;ö„ týúûûC¡P >>^ê Qµ`¸×ݺuüüóÏ€#GŽ //pøða½}þþþ€   ,\¸jµ³fÍ‚¿¿?¶oߎÎ;ãêÕ«Eޱ`Á„††âÔ©S>|¸Á¥&Nœˆ¡C‡")) ááá|=B||„¿¿¿n~}ñâÅÏ·ûúú(¡¾Y QQQzûŸ´aÃŒ;÷ïßÇØ±c‘““Sl-)))Ø·o K—.•~n«W¯†J¥BHH6oÞl°­­-fÏž¨¨(œ9sƸ'ŸÈؤž¢Š)B$%% €°¶¶Z­V!„»»»nûþýûuí $ˆV­Z‰Ù³gë¾···ÉÉɺvÿœO/œ»ˆˆ(ÒfÊ”)bôèѺùö~ýú‰üüüRk/mÎýÆbïÞ½ÂÊÊJ¨T*±aý6–––B!=z$\]]uÏ—sî$W¹× M›6…‹‹ ÀÏÏÀã½Z­F§Ntí·mÛ†9sæ 77K–,All,†Š£Gêú1dÉ’%€wß}wïÞÕÛ·|ùrlݺ®®®X¸p!vïÞ]%Ó2€o¿ý£FÂW_}U¤¥¥%,X áOÈ8B”p™¬Û·o£~ýúHHH0»›u˜«Â›uìÚµKêRˆJÅ‘;‘ 1Ü©RÚMÀ°O+סO ÎŸ?_ê§Gd¶x›=2 œ$ªZ¹ÉÃʬaÆzþÓ»;€†ÿjB"€Ûï»ûµ°UÐæíÀèÏ ¦tZO~Ü®pšgx0eà8ºh¥ÕADœ–¡*²ã/G[ í°9Èξ™^°쪂màTX²»äþ¶Z6º¸Q Eû#¢’qäNeæää¤÷ç“TJàe@Ò§À”ÛvÆR?Ó ÂÆ÷þüø#P«Š?Vƒz@ÂÀ·oï×ﯤ:ˆ¨ÃʬpÁ-C oõj4ù{}±‘Ýo?}µà«ðýÒ« Q} gñÇêêX[ü½¯—~%ÕADîTížüjY?Œš“÷øïÚ\©Ÿ‘ùa¸S•8pø+³àï_z¼ÝÃhçò8Ô §gþÊ¢O•ÐßiàÚMÃýQéø†*U‰Ü<ÀmbÁªWnlò Ðêï›!½Ü¥àMш(`×q ãnÉ£ø|¸OêÉEû#¢’qäNU"И5x¤-˜+ÖøòµÇûWŽFvj[YÙÀôÁÀ€ûjYß_vŽáþˆ¨d¹S¥œ^ªÿýÜ@Ãíîdÿ|õFÁ÷÷€vS þîÝÌðcæß•ŒáNF±ã—‚.=ݰPGÎ|(ɶVÁˆªÃŒÂÓpsŽ_dN¶À@Ÿ‚‘¹{#©«#’†;EoOà˜géíþ9ÍCDÃ7T‰ˆdˆáND$C w""b¸ÉÈH†îDD2Äp'"’!†;‘ 1܉ˆdˆŸP5sóæÍƒÔeÔqqqhÕª•Ôe• ÃÝLYXX 44Tê2*$11ŽŽŽpv6¯ÅÙ;vì©Ë *……w·$2Ž: 44“'O–º"Ùâœ;‘ 1܉ˆdˆáND$C w""b¸ÉÈH†îDD2Äp'"’!†;‘ 1܉ˆdˆáND$C w""b¸ÉÈH†îDD2Äp'"’!†;‘ 1܉ˆdˆáND$C w""b¸ÉÈH†îDD2Äp'"’!†;‘ 1܉ˆdˆáND$C w""b¸ÉZêHÞRRRpöìY½m÷îÝÃùóç­·½W¯^P*9Þ ª !„º’¯ÄÄD´oß¾ÔvÞÞÞHHHº\"Ùà0‰ª•——ÜÜÜJlcii‰#FH]*‘¬0ܩڅ††ÂÒÒ²ØýZ­ÁÁÁR—I$+ wªv!!!Ðjµ÷) øùù¡I“&R—I$+ wªvÍ›7‡···Á}aaaR—H$; w2а°0XXXÙž——‡¡C‡J]‘ì0ÜÉ(‚ƒƒ‘››«·M©T¢OŸ>°··—º<"Ùa¸“Q899¡[·nz×±«T*¼òÊ+R—F$K w2šÐÐP¨T*Ý÷J¥ƒ–º,"Yb¸“ÑB¡PÔj5 ©Ë"’%†;­­- R© „ÀÈ‘#¥.‰H¶îdT£FB^^jÕª…€€©Ë!’-.fâÆ¬¬,©Ë¨2yyyP«ÕpvvƘ1c¤.§J=÷Üs>|¸ÔeàÈÝämܸÉÉÉR—QeT*š6mŠæÍ›K]J•Љ‰A\\œÔeépän&Nœˆ!C†H]F•IHH@ûöíeµ¼¯œ~>$ w2º:H]‘ìÉgèDDD: w""b¸ÉÈH†îDD2Äp'"’!†;‘ 1ÜkˆiÓ¦A¡P`ùòå€{÷îA­VC¡P 11 …BÀÀ@@vv6æÎ‹–-[ … ôú.ìG¡PÀÚÚ^^^رc‡Á6jµuëÖ…Þ{ï½bï­ZÈÆÆF×÷?¿–-[¦ë7##°téR( ´iÓ©©©zÇž0a‚®_( ÄÇÇKý£!ª ÷¢[·n€ŸþpäÈäåå>¬·Ïßß„… B­VcÖ¬Yð÷÷ÇöíÛѹsg\½zµÈ1,X€ÐÐPœ:u ÇÇ­[·Š´™8q"†Ф¤$„‡‡cðàÁB[÷Ì™3Ž@ãÆŽððpøùùéµ]´h¦OŸŒG!>>666ðööÖMWøúúêÝã´ÿþ`p:#==±±±HOO‡££#ÜÝÝ‹­§qãÆèׯ 66¶ÒÏ/""nnnˆ‰‰££c‘ýjµS¦LÁÆuÓPDrÆp¯A §f¢££‡nݺ¡{÷î¸ví6oÞ ­V‹N:A­®Ø’Cð÷÷‡F£Áž={P»víÛççç€îîL•¡R©pùòeÄÄÄÛfúôé°··Ç[o½U]§˜Èd0ÜkÂp_¾|9>|ÝüúâÅ‹<žo÷õõP0B/|³¢¢¢ôö?iÆ ;v,îß¿±cÇ"''§ØZRRR°oß>@—.]*ýÜV¯^ •J…lÞ¼Ù`[[[Ìž=QQQ8sæŒqO>‘±I=/D%«ª9w!„HJJamm-´Z­BwwwÝöýû÷ëÚ4H­Zµ³gÏÖ}ooo/’““uíþ9Ÿ^8wQ¤Í”)SÄèÑ£uóíýúõùùù¥Ö^Úœû7ÄÞ½{…•••P©TbÆ zm,--…B°ô;`ÍAàÏ´‚Z;¶æº·yÜGásÕhlWðÜêZ–{Nÿ÷ p6ÈÎÔÚ7Ž.mˆô4ýsHdnî5ÈæXÀ¾ÐÔ8~ ØðsÁßÿ¯`M.Œ[üwÁ¨¶·'œìŽ~ú½`ÔnÈØUý€S]`Éî’kج?X[²‹ÖPhDZ‚ÿlm´;ÇÈξ™^°ôçÀºŸ þîæ dÜ¢OÎ?„Ôÿ¤mG F쮀¥HÍ—lëâ8Ô.§ßìxýÉ€™ÿ‚Mÿäää¤÷ç“êÔN/âÞzµ+ضÿtÁŸ¯ÿûû“ûë'Qo¿-Ú4îdŒ¶ÿéÏ4`Ë‘‚¿ïüù ðG V__v.pò s-àó”~ OR)?–IŸSlÛ\HΧ>öÉóÇnêX0¢Ÿ·µh¹ùÀ‘…ÀåO‹€KiÁ^ÛØ9øöM ñC å  aƒâÏ!‘¹`¸ËLá¢X†ÇêéQ0€æ þL»]ðçñK@á'‚—\Ù¢.˜²€+åž¾úø1# V-@£ú@O«w;À«) QÞÍôkxR¯v@“¿×+Ù]ÿ˜ñ—ÒµàÏzµ þþäþ'Ÿ»Ÿ[Áß-5€§kÁo²ÿþ¯`ä~ª„sHd.8-SƒØ>1¯]ø¦¨¡ˆÿôìlô·5²VEëo{òƒ¥eýiá. Q_CUs®¯ÿ}]kàøûÀçû€¸‹Àï×€~+øÊÀ¸¾F(Ѝ1Ü àÛüñßÝÓ=þþÄŸÀC7LjçRêBLÏtnü•Y0÷]YÎôÕ¨>ðõ¡ÇÛ=þ±êÁ¦Ø‚7RïdQ ?—Eûûçÿ=÷„øûÃo{v~Áû ¿œg¸“ùc¸€‚7%_íYp•Ëô¯€oâ€úµ æ·/¤KF}ÌSNÀË] Þ쌈v/xc³*–ŠÉÍÜ&Œô¯Ü(Ø6ä Õß7W íQ0ï¾,ˆü¸y¸u¿à7’w†•ÞÊ- íÔ‚ÿ š:Ì¿ÿ|¶`ßÓÍK<‘©ãœ;é¬ |Z0'~âÏ‚Q¬Rü»7Я½áǬŒìVðÆdV60}ðã¹ïZ¯%И5x¤¬-a/_{¼ÿË×€Å#€Ö¤t '¯à ™ýóŠ^)cˆ} ¨SAÍ1§Ãç€V ®Ú™ õO‚¨ò¸p˜‰3õ…îÞ,][,0‰Œ{@»©—/†= ¬/u…ÆÁ…ÃÈÔpZ†*eÇ/À;Û ¦2,ÔÀ‘óÀíoÞÎ"uuD5Ã*ÅÓµ`¾þø¥‚Ë l >u:7°àY"’Ã*¥·'p¬ sÜDd\|C•ˆH†îDD2Äp'"’!†;‘ 1܉ˆdˆáND$C w""b¸ÉÈH†ø U3°|ùrìÚµKê2¨¿þú+š5k&uD:¹›¸áÇÃÕÕUê2ªTbb"RSS¥.£JõêÕ ;v”º ".ùKFסC„††bòäÉR—B$[¹ÉÈH†îDD2Äp'"’!†;‘ 1܉ˆdˆáND$C w""b¸ÉÈH†îDD2Äp'"’!†;‘ 1܉ˆdˆáND$C w""b¸ÉÈH†îDD2Äp'"’!†;‘ 1܉ˆdˆáND$C w""b¸ÉÈH†îDD2Äp'"’!†;‘ ©¥.€ä-%%gÏžÕÛvïÞ=œ?ÑÑÑzÛ{õꥒ㠢ª B©‹ ùJLLDûöíKmçíí„„©Ë%’ “¨ZyyyÁÍÍ­Ä6–––1b„ﴃ ̻]hh(,--‹Ý¯Õj,u™D²Âp§j­VkpŸB¡€ŸŸš4i"u™D²Âp§j×¼ysx{{ܧÑh&u‰D²Ãp'£ ƒ……E‘íyyy:t¨ÔåÉÃŒ"88¹¹¹zÛ”J%úôé{{{©Ë#’†;…““ºuë¦w»J¥Â+¯¼"uiD²Äp'£ …J¥Ò}¯T*1xð`©Ë"’%†;M``  @­VcðàÁ°±±‘º,"Yb¸“ÑØÚÚ" *• BŒ9Rê’ˆd‹áNF5jÔ(äåå¡V­Zº"ÙâÂa21~üxdeeI]F©òòò V«áììŒ1cÆH]N™<÷Üs>|¸Ôe• Ã]&¾Þ°®m}àÐÈUêRJ¡‚}㦰nØI÷¤®¥tgŽÆ ^½z w2; w6Ïô"u¥ºü{šµn…,ïûá¸!R—@T! w2º§Úvº"Ù3ý¡•ÈH†îDD2Äp'"’!†;‘ 1܉ˆdˆáND$C w""b¸ÉÈH†îDD2Äp'"’!†;‘ 1Ük¨àVj k¡Ðûúïœq€m6¶|4oôl‰w Œ~ÚMBjÒ…bûéaé¼pìûÛÜËÌ|÷¿¥ÖB)ϵAfzj±õò´)R_áWäšeeê·°Í—ó'èú;ÌÃZ(péT¼Ô?¢jÅ%k¸çGO……U-@K¯Ž€&áÄþïШ¹;†Œ›…äs‰øeïvœ9zìI€½³‹^Ã&/@fz*~Üø"&Ǫ_RaSϮȱv}±—¼7Ìûz?ê:4(¶®!cg"G›´ä?ûÝ&Ø5hŒg‡†ܼýÊÕoô¦U4zœ\ž’út ý†{ñµ·P§¾ƒîû‹¿ÅáÄþï`emƒw·Å¢N={ÀÒñ8öÃ7ˆ\³ ¯Ì^ª×G¿‘ãQ§¾~=‰›©Wq#%©H¸ïød!¢ÖF ik/Ìý*¶vŽ%Ö8a.àä¡ïûÝ&Ø;7AðÔ…EÚ•Ö¯R¥‚ÆÂ[>ž‹7>úZêÓMd4œ–©áFû:ê¦;îX«›®háé« vðîÑ Ngܹ™Ž?NÄâîÍtØÚ9¢ÑSîEÚD­€s37ÌÛSj°—GiýªTj |u ïÞˆ+ç¥>ÝDFÑ{ ×?t¢nZÆÕÝN+wSûy¬¬m0ë{`i]»H¥J…ô«—qúH :ª²úËÒïàÿLǾ Ÿcã’·ŒqJ‰LGî5ÜÐ7æbÄ›‹0âÍEhÞîi´ðôP0B/|³F€nÿ“&~¼}CÆâQÖ}¬š3y¹9EÚ¼¶x5”*"&‡ vÏæ*«¿,ýÖ²±ÅKãg#á`®]8#éù&2†;éiÙ¾#žî=²îcNPWlZŽÅc#nßNÔ©gÿš\ä1í»=‡ÿ,üî>]ôûIìÛðy‘6>ÏÀŒ•» Rk°bêHÞ½±Jê-k¿Ï‡F®xp÷¶Ô§˜È(îTÄÔO¶!ðõ9ÈÏÍÅîUKðljXtê? w-r¥Ì“F¾µ°}Å»xxÿn‘ýÞÝðæÊo ‚xÚ(ÚùU•Ô[–~5–6yÔ§–ÈhB!uTy¶uëaÜkñLß!R—"+Ž‚Îm›aÙ²eR—BT.¹É¯–!I k¡(²-hâÛš4_êÒˆÌÃ$µõg‰ª§eˆˆdˆáND$C w""b¸ÉÈH†îDD2Äp'"’!†;‘ 1܉ˆdˆáND$C w""b¸ÉÈH†îDD2Äp'"’!†;‘ ñf2µv9âöí*÷ã„P(å~œTýV§w3QÛ¶¾îû?OÿŠÎm›I]Q¹1ÜebäˆáÈÊÊ*÷ã®_¿Ž#GŽ oß¾¨S§N•ÖtòäoÈÌÌDÏž=ËýØŒŒ œ={ݺu«¶söO=Âö‘prr‚¯¯/ìììÐì¹^èØ±£Ñj ª2‚j¤üü|±lÙ2¡ÑhĘ1cDvvv•£W¯^bÆŒzlRR’P©Tâ§Ÿ~2êyùã?DPPP*•"((H$%%õøDU…sî5н{÷0lØ0Ì™3_}õV®\ ‹*=Fnn.âââÐ¥K— =¾iÓ¦4hV¬XaÔsÓªU+lݺÑÑѸtéÚ¶m‹Y³fáÞ½{F­ƒ¨²BÞ¡¸9wî‘››‹;v ]»vÕrœ'NÀ×ש©©hذa…úˆ‰‰ÁsÏ=‡‹/¢Y³fF?WBlß¾3fÌ@NNÞ~ûmŒ=*•Ê赕Gî5ȦM›àëë 777ÄÅÅU[°@ll,ÜÜÜ*ìЫW/xxx`ÕªURœ.( á÷ßÇĉ1cÆ xyy!**J’zˆÊƒá^äææbÖ¬Yxå•W0wî\ìܹuëÖ­Öc9r]»v­t?ãÇÇÊ•++ôfqU±¶¶ÆÌ™3qîÜ9øûûcðàÁèÛ·/N:%YMD¥a¸Ë\JJ zôèÕ«Wãûï¿ÇÌ™3ryâ‘#G*<ßþ¤Q£F6oÞ\í5—ÆÙÙ+W®Ä©S§`aaŒ;iiiR—FTÃ]Æ:___(•Jœû, „K—.I]ÕP w™0æeŽ%ÑjµHLLÄ3Ï>­ZµªÖ`Ìç²È²àÊ“T]î&ÌÔ.s,MáîÆ`N—E–EáÊ“—.]Ò½ŸÂ•'©2î&Ê/s,M|||µ\ßnˆ9^YˆˆˆÀ©S§`iiÉ•'©Âî&ÈT/s,IVVÎ;g´p7×Ë"˪M›6سgWž¤ c¸›S¿Ì±$gΜA^^¼¼¼ŒvLs¾,²¬úô郓'OâÓO?åÊ“T. waê—9–æôéÓpqq©ö7SŸ$‡Ë"ËB©Tâ•W^áÊ“T. w`*«9VÆ™3g$¹ŠÇÔW‹¬J\y’ʃá.1s¹Ì±4§OŸ–¤v9]YV…+O;v wïÞåÊ“dÃ]"æv™ciNŸ> IŽ-·Ë"ËÊ××?ýôWž$Ãݵk×D—.]„£££ˆŽŽ–ºœJ»uë– âãã%9þƒ„øòË/¥>’ÑjµbåÊ•ÂÁÁA´jÕJlݺUê’Hb¹™9^æXšÓ§OC©TVÙ=SËKî—E–Wž¤b¸‰0ãËKsúôi´hѵk×–¬†špYdYpåI*Äp7s¿Ì±4R])ó¤’.‹LOO—´6)®<«·òäÝ»w¥.Œ„á^Íäp™ci¤ºR柞¼,R}ûö!  ÊoÖmN:uê„ØØX¬]»[¶lA›6m°jÕ*äååI]U7©'ýÍÙ;wDvvv±û7nÜ(j×®-^xáqûöm©Ë­6bóæÍR—!òóóE›6mÄ AƒDóæÍ…R©J¥RØØØH]šIxðàX´h‘°µµmÛ¶‘‘‘R—DÕˆá^ £GcÇŽ-²=''GÌœ9S¨Õj±hÑ"‘ŸŸ/u©Õæúõë€HLL”´ŽK—.‰7ß|SØØØ @÷¥P(Dnn®Ô§ÊdܸqCLœ8Q¨T*ѧOŸ2ýì}ºÞ¯þ*•J4hÐ@XXXˆÉ“' ­V+u‰F.zöì)i _ýu©üøq©O•IËËËëÖ­ 4...bݺuºéÄž={ F#µZ-~üñG©Ë¥2`¸—SBB‚P©TEÂÃÂÂBøûûËz~Ý—_~YŒ3Fê2ÄŠ+ þ\¥R)~øá©K4 wîܳfÍVVV¢S§NbéÒ¥B¡Pè½amm-Nž<)u©T ^ Y¹¹¹5j”Á;"iµZ;v ï½÷žÔeÕ… ТE ©ËÀ„ 0mÚ4ƒ7–V«ÕÈÌÌ”ºD³`kk‹÷ßçÎC³fÍðþûïë½Þ…ÐjµèÛ·/®^½*u¹T†{9|ðÁøã?››kpNNæÍ›‡~øAêRæòåË&î°hÑ"„††ù€˜J¥Â­[·¤.Ϭ4mÚ=zôÀíÛ·‘ŸŸ¯·/77·oßFïÞ½¹¥ c¸—Ñüùóç#''§Ô¶#FŒ¨£š{÷î!33®®®R—P(Xµj  Ýv!GîåtïÞ=„‡‡—8¹rå ˆììl©Ë%îeŸŸÐÐÐb÷k4(•JÔ©S!!!X³f 6l(uÙÕ.99L&Ü‚QúÖ­[ѹsg]Àççç3ÜËé½÷ÞÃýû÷Kl£Õjqüøq„……ñ¶&H-uæà‹/¾À‰'ôF1yyy°¶¶Æ /¼€—_~²Z3¦4W¯^…¥¥%œœœ¤.E……vïÞ®]»âüùóÈÉÉá´L9¤¤¤`Ù²eÈÍÍ…••rrrŠ]® ''Û·o×ÍÏ“é`¸—âÚµk˜1crss¡ÑhŸŸkkk"88½{÷†Z]3OãÕ«Wáââbð f©ÙÚÚâÇDÇŽqõêUܸqCê’ÌFãÆ‘œœŒS§NáÔ©S8}ú4âããqîÜ9ýM!þñûÔ¯¿þŠåË—K]—ÉØ¿?®]»µZ¦M›¢Y³fhÔ¨”Êâg´¬­­ñÙgŸI]zµ{ûí·qèÐ!8p Ô¶R½®îÝ»‡ÈÈHÔ­[ýû÷—â4U™êz]%''cÞ¼yej[ø>ËíÛ·qëÖ-ܼy<€ …={ö„‹‹‹Ô§ªÆ™8q"|||ô¶I¨äädlܸQêZMµk×`ii‰Þ½{#$$þþþhÒ¤I‰Á^“Î_JJ 7n\¦¶R—:uê _¿~&ùÛEyTçù»uëÖ­[W¦ÛóÕ©S®®®ðòò³Ï>‹ÀÀ@Œ1ƒ B×®]qëÖ-ÞæÏÈ6nܨ{ÿëI笭­±víZ©k6K»víBXX˜ÔeÅõë×Ëu÷%)_WgÏžE›6m$9vU0Æëê³Ï>C½zõ¤~ªTN»ví2¸WËP…¥¥¥¡AƒR—Q&æìDÁp§ »~ýz¸ä“È1Ü©B„HOO7›‘;QMÃp§ ÉÌÌ„V«åÈÈD1Ü©B®_¿¹™(†;UHZZT*¤.…ˆ `¸S…\¿~—Ø%"é1Ü©BÒÒÒ8ßNdÂîT!æt;QMTépW«ÕP(ÈÈÈÐû^¡PÀ­ZµÂŠ+tíýýý¡P(°{÷n½~||| P(­·}ĈºþJbcc£k÷ϯeË–¬uéÒ¥P(hÓ¦ RSSõÚL˜0¡HÍñññRýœLÎõë׫5Ü«ëu•››‹¹sçÂÕÕpuuÅš5kŠ­ƒ¯+}e=ÏçÎÓ§   "ý<ùó´¶¶†——vìØQdá9­HŸmÚ´ÁçŸn°Í“ýz|á׸qãJ<¥½>$}müó¾{;wîuëÖ-ó}ú ï[yãÆ ½ï,X æÎ+êÖ­+ˆ;w !„Xµj• ‚‚‚t}üþûï€hÒ¤‰ÈËËÓmß´i“°´´ÔõY’wÞyG„‡‡‹@4nÜX„‡‡‹ððpqäÈ‘"µ¾ÿþû€ðððׯ_/ò|4øóÏ?…BtíÚµÌ7Y.ïù3W Ó¦M+s{Sy]MŸ>]ýúõkÖ¬‹-ÿýï‹­£&¼®‘™™YjÛ²žçY³fénoii)nݺeðç»`Á1nÜ8Ý}ˆoÞ¼iðç_ž>W¬X!>øàQ»vm@üñÇž® =~êÔ©ºŸñ·ß~[âù(íõaŒ×FݺuuÿžTmá^øý¤I“1eÊ!DÁ xkÕª%¬¬¬ÄíÛ·…BÌž=[o½õ–®ßk×®‰úõë‹>úH÷C*‹½{÷ ÂÏϯØZ kòòòéééEÚ¨T*acc#FŒQî]S½K—.báÂ…eno ¯«[·n F#\\\Dvv¶ÈÍÍ-s=r~]•'ÜËržóòòDãÆE­ZµÄŒ3ñÙgŸ•øóuqqĉ' î/OŸgΜ§N7D\\\±Ç-©¦ò(îõaŒ×Fqá^msîW¯^Ebb"~úé'ïÖckk‹!C†àÑ£Gضm„ºÕî ïv$„@XXž~úiLž<¹Êk‹ˆˆ€››bbbàèèXd¿Z­Æ”)S°qãF$&&V×)2kwïÞ…­­­Ñ[™×ÕÙ³g‘““ƒüü|4kÖ ðõõÅÉ“'«¤¶šðº*ËyŽŽŽFJJ ^xáݴƺuë ö—žžŽØØX¤§§ÃÑÑîîîÛ•§Oxzz"%%AAAxæ™gÊõuS+UµÐ¯j w´oß'OžD=0fÌݾÂÕí¾úê+ÄÆÆ")) ~~~ºìºuëpøðaÌ;—.]ÒÝÂëâÅ‹eº‡iiT*._¾Œ˜˜˜bÛLŸ>öööxë­·ªë™5©Â½2¯«Gþúë/¼óÎ;X¾|9Nœ8—_~¹Jj«)¯«ÒÎsa öìÙJ¥žžž8vìþøã"}yxxÀßß{öìAíÚµ ³<}nÙ²ß|ó <<<°sçN:t¨\Ïoâĉ˜9s&fΜ OOÏ*9g’¼6þ9”¯ª_Ÿ×¯_/"##ÅéÓ§‹<¦ðW,…B! PäW¬·ß~[0øuöìÙ ýzôdmëÖ­ÓÍåoÚ´©HKKK!„}ô‘ êÕ«g¿>›’zõêüU°8¦ðºJJJ„ƒƒƒn[íÚµ…R©999%Ö#ç×Uy¦eJ;Ï…Ó6†þí>9íZxÎ6lØ ÆŽ+ooo¡Õj‹üüËÛgákfÁ‚€˜8qb±m ý«cZ¦:_’͹§ðÍEÞ9sæŒØ¶m›îËÊÊJÛ¶mwïÞ­ÐIþgm{÷îVVVB¥R‰ 6<Ñ=®®®º:¥þGh*òóó…J¥ÑÑÑe~Œ)¼®„" @÷¦Û‡~(ˆ=z”Zœ_Wå ÷’Îsá®/¾ø¢Ø¹s§Ø¹s§Ø²e‹P*•zo¸þóçÙ¥K@DDDÙ_Þ>W¬X!>ûì3ѬY3½>Ÿló䛦_}õU©û*úú0ÆkÃäÂýìÙ³º'0tèÐÛVõª…µýðÃÂÊÊJ(•J±~ýú"'Z!Ö¬Yc2ÿM…V«Dlll™c*¯«ÔÔT1dÈQ»vmagg'† &RRRJ­Gί«Š„{qç¹ðÀï¿ÿ^¯}çÎñã?Œî7oÞ„¥¥%&MšTb;¸ºº¢eË–hÖ¬F­w7uQ0•¤ûÒjµÈÏÏGóæÍѼys´k׳fÍ2xÉdaß-Z´€‹‹ Íê£ß¦"77$]òÖ­[hÒ¤ Ž?®Ûöÿ÷ ¬PÛ¾®„˜?~¹ŽSÞº¨læÌ™ƒ×^{M÷ýž={ P(pæÌݶçŸ_~ù% ;;Ó§O‡««+\\\о}{lß¾]¯Ï's¦U«VºÇ>¹ïÉœ8~ü¸ÁׇIûçùä!„¹¹¹¢nݺââÅ‹B!^}õU1hÐ Ý'hããã…³³³Ø±c‡®'³ ..N¨ÕjÝëÆÈ‰ª$ùªÞÞÞbÿþý¢wïÞbë֭Ŷ{òÄÞ½{WxxxˆÈÈHƒmÏŸ?/¬¬¬Š|œ·,}=z´ «²çYÂýöíÛ€HHH(ócªë¼Œ1BL:U´oß^lÙ²Eoßýû÷õåi[™Ç–Ö¾"jr¸gee KKKݾ¾¾bÏž=º«tŽ?.\\\„Ÿe¨U«V‘Løâ‹/„···îûfîcäDU’ô Õ„„ܹs={öÄ¿þõ/¬^½ºÄöƒFëÖ­áää„¶mÛbÀ€ÅöÛ¢E ƒç-+???½_ï¨t…síUñiáÊZ±bþûßÿ¢yóæ6l˜Þ¾—_~«V­ªPÛÊ<¶´öT>µjÕBÇŽñÓO?áÎ;ÈÊÊB@@€nÙˆƒâÙgŸœ:u Í›7/’ ]ºtÁo¿ý†¼¼¼"ýÿøãhРÚ·o_l æ˜jcdõêÕ ƒB¡ÀK/½„7Þx)))hܸ±Áö»wïF»víððáC :ü1¦L™b°­ø{i‚Šªìãk"S ÷ýû÷ÃÖÖçÎCvv6,--uûÞÿ}ØÙÙU¨me[Z{*¿gŸ}?ýôjÕª¨T*¸¹¹áÌ™38xð î=òü{îׯrss‘‘‘-[¶À¢ضæ˜Õ>rÏÎÎÆ¦M›°jÕ*4kÖ mÚ´ANNN™ä©U«žþy|ÿý÷÷wèЗ.]Â7*\ß±cǪlýˆšB­VC¥RááÇ’ÖqãÆ Lž<QQQðõõÅÛo¿­·ßÓÓS7€(OÛÊ>¶´öT~={öÄO?ý„ƒ¢G€îÝ»cÿþý8|ø°näîééi0Ž9///½‹~øá¤¥¥aݺuxõÕW‘––VìñÍ2'þ9OSÕs{[¶lÝ»w×ÛväÈѲeKÝ÷ûöí3øf†V«/¾ø¢xã7 ¶Bˆàà`Ñ¿ÝúÈ÷ïß .™™™EÚþ³ï•+W ‘˜˜XeÏ·&̹ Q°¶Ì7ß|SæöÕq^‚‚‚Äüùó…Ëùº¸¸è}¢ïðáú7ÙÊÓ¶²-­}EÔä9w! .”°²²-[¶Ôý›Žnnn¢iÓ¦zmྡºmÛ6]›ΫéÖ 1FNT%ÉÞP Ÿþy‘íÍ›7Bááá¡+ÎÞÞ^4jÔH4mÚT¸¸¸ˆaÆé­òd[!„ÈÎγgÏM›67-[¶sæÌZ­¶HÛ¾]]]E£FÄK/½¤[?ºªÔ”pwuuk×®-sûê4´oß^ï£å»wïíÚµÓ½¹9pà@±bÅŠrµ-TÑÇ–¥}EÔôpBˆ=zè µZ­°¶¶¡¡¡zí²²²ÄäÉ“E“&MDãÆE»víÄæÍ›õÚü3Ü/^¼(êÔ©#®]»f”œ¨J’_-SSÔ”ó×®];±|ùò2·¯)祺0Ü©8\~€ª”­­-îܹ#uDT †;UHýúõqûöm©Ë ¢b0Ü©BœœœJ¼º€ˆ¤Åp§ iذ!ÃÈ„1Ü©B4h€ëׯK]ƒáNÒ AŽÜ‰LÃ*¤aÆÈÈÈÐ-ÿKD¦…áNÒ Aäçç###CêRˆÈ†;UHÆ €óîD&ŠáNR¯^=XZZrÞÈD1Ü©B ¯u'2a wª°† rZ†ÈD1Ü©Âx9$‘é2x'¦¬¬,„……I]›ÉB@¡P”©mrr²ÔåMy響3Æëjüøñ%Þ¨$BܹsõêÕ3ò™¡¬¬,ƒÛ‹„»««+†.u½&C«Õ"** ÞÞÞhÖ¬Y©í]]]Ѻuk©Ë6ŠÆã§Ÿ~*S[)_WÇG£Fн­£9¨ÎוBCC+üøëׯ#>>YYY Ô»ÛU¿áÇÃÕÕµè©×"6uùùùbÙ²eB£Ñˆ1cÆTê† róå—_ŠfÍšI]F‰’’’„J¥‡’ºÙ9þ¼ J¥R‰¤¤$©K¢'pν …“&MBtt4vïÞ^½z᯿þ’º,“дiS¤¤¤¼£¼©X±b<==Ñ­[7©K‘[·naÖ¬YðôôDFFâãã±uëV4mÚTêÒè ÷2êÞ½;âãã!„€··7öïß/uI’kÚ´)rrrLö?»¬¬,¬Y³'N”ºYÈÉÉÁªU«àîîŽ;w⫯¾BLL :tè uidý ç˜_}õU`ñâÅBH]–d\]]¡T*qåÊ©K1hýúõP*•–º³÷Ýwß¡M›6xë­·0}útœ:u AAAR—E%`¸—“Z­Æ¢E‹°~ýz¼ûî»2dH½Ýœ……4h`²áþÙgŸa̘1¨U«–Ô¥˜­øøxôèÑ8p .]º„™3gVøª2†{…„„ >>/^DÇŽqúôi©K’D³fÍL2Ü÷ïß³gÏbܸqR—b–®]»†±cÇÂÏ϶¶¶8{ö,"""x©£a¸WBëÖ­ñË/¿ÀËË ;wÆ–-[¤.Éèš6mj’á¾bÅ ¼øâ‹pqq‘º³rÿþ}ÌŸ?­ZµBBB<ˆï¾û-Z´º4*'†{%Õ©S[·nÅÂ… 1jÔ(Œ;Z­V게¦yóæ¸té’Ôeè¹rå öìÙƒ7ÞxCêRÌF~~>Ö¯_–-[bõêÕøâ‹/pìØ1^edÆîU &_.éîîŽ?þøCê2ôðòÇò‰ŽŽ†··7^ýuŒ7çÏŸÇ+¯¼RæOe“ib¸W¡šx¹¤»»;RRRp÷î]©KðøòGŽÚKwöìY<ÿüóèß¿?:w/bþüù°²²’º4ª ÷*VÓ.—lݺ5„8þ¼Ô¥x|ùcHHˆÔ¥˜¬ŒŒ Lš4 žžžÈÎÎÆ¯¿þŠ•+W¢AƒR—FUˆá^ jÒå’uëÖEÆ Mfj†—?/++ ‹/F‹-tSˆ?þø#<==¥.ªýÕ”Ë%MeÞ—?&„À¶mÛàááåË—cÉ’%HLLÄ€¤.ªýš^.Ù¾}{tîÜ›7o–º¤jyŽçΓº ^þhÀÑ£GѵkW„……áå—_ƹsç0fÌ®ÜX0Ü N:زe .\ˆW^yEv—Kº»»Kî¼üQßùóç1lØ0øûû£I“&øý÷ß±hÑ"Ô©SGêÒÈHîF"çË%Û´iƒ . 77W²xùc½¼¼¸bc Çp729^.ééé‰GIvÅ /äŠTÃ]r»\²qãÆpppÀo¿ý&Éñkúå\±‘ a¸KDn—KzzzâÔ©S’»¦^þxüøqtïÞ+6’A w‰ÉårÉöíÛ#11ÑèÇ­‰—?®ØØ©S'Ô­[—+6’A w ‡Ë%½¼¼$™–©I—?®ØèææÆ©tRßÄ•3ç›qÇÇÇ "##ÃhǬ)7¿ÎËËëÖ­ 4...bݺu"??_ê²ÈÄqänBÌùrÉvíÚA­VuÞ½&\þÈ©¢î&È/—´´´D«V­Œ65#÷˹b#UÃÝD™ãå’>>>8qâ„QŽ%×˹b#U†» 3·Ë%Ÿ~úiÄÇÇåXŸ}öþóŸÿÈæòG®ØHUM!L}8H€sçÎ!00¹¹¹Ø±cÚµk'uIE>|=zô@ff&lmm«í8û÷ïG@@þüóO³¿JFíÛ·cÆŒÈÉÉÁÛo¿Ñ£Gsa/ª4ŽÜÍ„9\.éãã¥R‰„„„j=Ž\.ùD¨Õêbƒ½V­Zåî3''G(ŠRÿÓøüóÏvnßÿ}ÝóIMMÕÛwïÞ=ñöÛo +++ñÌ3ÏÈ~132} wÊÏÏ‹/jµZ´mÛV7Ò-õ¾ôÒKUz¼¼¼<ñôÓO Fc0€[´hQî>ÓÓÓK uµZ-,X`´sºiÓ&Ý–––âÕW_Bü'´råJ®ØH&‡á.c!!!W©TVùtFbb¢P©Tƒ¸"sýçÎ+6Ø57nœÑÎãŠüf¢T*ŧŸ~*Ú´i#êÖ­+/^,=zd´šˆJÃp—©7ûæ¦B¡öööâÖ­[UzÌ3fèý–@¨T*1f̘r÷uôèÑbƒ}èС"//Ï(çñôéÓ¢víÚEÎ¥F£ÎÎÎâõ×_¯¶«ˆ*ƒ×¹ËÐéÓ§ñ¯ý ùùù÷ !pïÞ=̘1£J;þ|899A©|ü²Òh4Z&àÖ­[E>†oii‰Î;ã믿Ö;Fuù믿ЧOdgg9—999HKKàAƒàààPíµ•Ã]fîÞ½‹_|±Ôe ´Z-V¯^ƒVÙ±­­­ñßÿþWoib!\]]ËÝWff&Ôjµî{ ´iÓ‘‘‘°´´4ÊyìÓ§nݺ…ÜÜÜbÛ½þúë%î'’ Ã]flmm±k×.Ì›7...P(°°°0ØV¡P ,, >¬²ã 00PwÌœœœ Ü GçÎÎÎøá‡`ccSíçP«ÕbàÀ¸téR‰ÿIæççãÊ•+X½zuµ×DT^ wòððÀüù󑜜ŒS§NaÖ¬YhÔ¨Q‘ ÏÏÏGjj*æÏŸ_¥Ç_±b…î8ùùù ÷ÌÌL! V«Q¿~}üôÓOprrªös'„À¨Q£Wb°«ÕjXYY!//sçÎEVVVµ×FT\Ͻ†ÈÏÏÇ‘#G°eËlÚ´ ·nÝ‚F£V«…R©ÄñãÇáããSeÇûâ‹/ðÚk¯A¡P ++«Ü·‡›:u*>þøcÔ­[GE›6mŒržÞ|óM|øá‡º©¥ÂÿsrrŸŸÚµk£uëÖxæ™gо}{´k×íÚµC½zõŒRQY1Üe(99óæÍ+v¿ééé¸|ù2._¾ ­V‹úõëcРAUvãe!"##qïÞ½ Ý /66—/_F@@€ÑÞ°¸_ÏÁ³”û±Ç~?ˆf ÝÐÀ®±QÎÙ#mþüë<²݇•­g~?­÷†.‘¹á«WÆÞù÷g°­]OÒNü‹§Ý»–ûqS΢ecãLű–ÁNfo¨RµªH°$؉ä„áND$C w""b¸ÉÈH†îDD2Äp'"’!†;‘ 1܉ˆdˆáND$C w""b¸ÉÈH†îDD2Äp'´ V£Å02ïe´9ÙøhË\ô|£%ÜC,ðôhLø(I©J|\á÷󿜠k3l®?Z SàÔ¥økðeƒÃ¿ÖD.3x¼ÿ}·-†)ðÜ”6HÏL­Ôñ‰ä†áNELø(ŸîXµJqCfÁ·µ?öþ²s:#õæÕR¿)z®¦_.×1Ç™‰×_ Ç ®wmj`ׯ¿Ž×_ ‡·›_‘ö_ìZ„÷¿š7lœNõ+u|"¹á HÏoã°ÿÄw°¶²Á¶wcQ¯Ž=`üÒ@üp쬉\†Ù¯,-öñ*¥ K|¼e.>zãë2wBà\À¡“ßã»ØMp¶o‚©Á ¶ýdÇB¬Š@ë¦^øjn4ìl+}|"¹áÈôN_x¶ðÕ;ôð¿8*•¯œ‚݇7âÜ•Äj©qmTš9»aü½`7Öñ‰ÌêÜOG={,ÙøVµô¯Rªp5ý2ŽœŽ‘äøDæ€áNz<[ø(¡¾q ¢ôö—Ħ–-Æ¿4¢páÚ™*¯qñk«¡Rª09"{b7ýøDæ€áNzÚ·ìˆÞOBÖ£ûšÓK7…cÌâÁØ·õêØã_'—©Ÿ‘ÏG#WÜ}p»Êk|ÖgVÎØµJƒ©+Fb÷áF=>‘9`¸×pwÜF^~TJljÙ>™º ¯ÎAn~.Ví^‚Ä¢§¡Ø±ð(œí]ÊÔ¯…Æ“‡-¨¶º»{`å›ßB­Ò`ÚŠQØyè+£ŸÈÔ)„Bê"¨jFƒ°°0©K$’†;EXX,,,ŠlÏËËÃСC¥.HvîdÁÁÁÈÍÍÕÛ¦T*ѧOØÛÛK]‘ì0ÜÉ(œœœÐ­[7½ëØU*^yå©K#’%†;Mhh(T*•î{¥R‰ÁƒK]‘,1ÜÉh¡P(jµƒ†ÔeÉÃŒÆÖÖP©TB`äÈ‘R—D$[ w2ªQ£F!//µjÕB@@€ÔåÉ“‰ñãÇ#++Kê2J•——µZ gggŒ3FêrÊä¹çžÃðáÃ¥.ƒ¨\î2ñõ†pmë‡F®R—R ì7…uÃæHº'u-¥;s4õêÕc¸“Ùa¸ËÈ€°‰x¦ï©Ë(ÕåßЬu{(Ì`yßÇ ‘º¢ a¸“Ñ=Õ¶ƒÔ%ɞ鈈¨ÜîDD2Äp'"’!†;‘ 1܉ˆdˆáND$C w""b¸ÉÈH†îDD2Äp'"’!†;‘ 1܉ˆdˆá^C·RcX …Þ×çŒäh³±å£¹x£gK„¸[`ôÓøhBR“.ÛÇHkLà…cßï0Øæ^fà»ÿ-Ű Ly® 2ÓS‹­o”§M‘ú ¿"×,+S¿…m¾œ?A×ïÜaþÖBK§â¥þU+.ù[Ã=?z*,¬jZzu|4!'ö‡FÍÝ1dÜ,$ŸKÄ/{·ãÌÑø`Oì]ôú6y2ÓSñãÆ/1y8Vý’ ›zvE޵ë‹Eظä-¸¸y`Þ×ûQסA±u ;9Úl¤%ÿ‰Øï6Á®Ac<;4 àæíW®~£7­Â ÑÓàäò”Ô§›Èhî5Ü‹¯½…:õtß_ü-'ö+k¼»-uêÙ–Žı¾AäšexeöR½>ú:õðëHÜL½Š)IEÂ}Ç' µ6M[{aîWѰµs,±®À s'}Øï6ÁÞ¹ ‚§.,Ò®´~•*4–Øòñ\¼ñÑ×RŸn"£á´L 7Ú×Q7ÝqpÇZÝtE O_]°€wþ`p:ãÎÍtüq"wo¦ÃÖΞr/Ò&jmœ›¹aÞ†˜Rƒ½ÅDFÁp§"¦~² ¯ÏA~n.v¯Z‚?NÄ¢Sÿ¡X¸ãh‘+ež4ò­%€í+ÞÅÃûw‹ì÷î€7W~[ÄÓFáÐίª¤Þ²ô«±°Ä°É ¤>µDF£B©‹ Ê³­[ã>X‹gú‘ºYùpÜtnÛ Ë–-“º¢ráȈH†xµ IjX E‘mAßFФùR—FdÖî$©­—8+HT8-CD$C w""b¸ÉÈH†îD>a7 IDATD2Äp'"’!†;‘ 1܉ˆdˆáND$C w""b¸ÉÈH†îDD2Äp'"’!†;‘ 1܉ˆdˆ7둨µË·o—ÔeÈÊŸ§Eç¶Í¤.ƒ¨Üî21rÄpdeeIZáC‡Ð¶m[888T¸;wî 55­[·–ô¹jö\/tìØQê2ˆÊM!„à}ΨÒ~øá<ÿüó¸xñ"š6mZá~öîÝ‹_|°±±‘úi™-ιS•X²d ^~ùåJ;ôìÙ*• 111R?%"³Æp§Jûí·ßƒiÓ¦Uº/+++ôêÕ {÷î•úi™5†;UÚâŋѧOtèСJúëß¿?"##¥~ZDfsîT)W®\AË–-…¾}ûVIŸÉÉÉhÚ´)N:…víÚIý‰ÌGîT)ü1Ú¶m‹>}úTYŸ®®®hÛ¶-¢¢¢¤~zDf‹áN–™™‰/¿üo¾ù& E•ö=`ÀλUÃ*ì³Ï>C½zõ0lذ*ï»ÿþ8|ø0nß¾-õÓ$2K wªììl|ú駘:u*4M•÷ß­[7Ô®]ÑÑÑR?U"³Äp§ Y¿~=}ú`ÿþýR?]"³Äp§rÛ½{7þüóO¼ñÆÕzœ^½z!99—.]’ú)™†;•Û’%K0bÄ4jÔ¨ZãîîŽ&MšpôNT w*—¸¸89rS§N5Êñzöì‰Hý´‰ÌÃÊeÑ¢E8p <<<Œr¼ž={"&&ü 5Qùpù*³?ÿü­ZµBLL ºwïn”cr)¢ŠáÈÊìƒ>€Ñ‚(XŠ E‹\˜¨œîT&éééX¿~=fΜiôc÷êÕ‹óîDåÄp§2ùä“OàììŒ!C†ýØ={öÄÁƒ‘——'õi 2 w*UVV>ÿüsLŸ>*•ÊèÇïÝ»7îܹƒ„„©O‘Ù`¸S©V¯^ !^yåIŽïääλ•ÃJ”——‡eË–á7Þ@íÚµ%«£[·n8räˆÔ§ƒÈl0Ü©D;vì@jj*Æ/i~~~8zô¨Ô§ƒÈl0Ü©D}ôÂÂÂàèè(i~~~HOOGRR’Ô§„È,0Ü©XD||<¦L™"u)pwwGýúõqìØ1©K!2 w*Ö’%Kðâ‹/¢eË–R—…BŽ;2܉ÊH-udšÎ;‡ï¿ÿ±±±R—¢ãçç‡üQê2ˆÌGîdÐâÅ‹áïïN:I]ŠŽŸŸ~ýõWdggK] ‘Éc¸Sýõ6n܈3fH]ŠžN:A«Õ"11QêRˆLý†»yóf‘mxê©§0`À©ËÓcgg‡–-[rÞ¨ î5\ß¾}ñüóÏëæÖïÝ»‡U«VáÍ7ß„Riz/N:1܉ʀë¹×pÈÌÌ„>>>ðôôÄÞ½{‘””+++©Ë+âÓO?ŲeËpá©K!2i¦74#£ÉÏÏÇíÛ·‘ŸŸ!°~ýzäææbåÊ•ÈÊÊ’ºÄ":vìˆK—.!33SêRˆLý»yó¦Þ2ºùùùÈÏÏÇÍ›71}út4jÔï¾û®Áyy©xzzB©TòMU¢R0Ük°´´´b÷åææâÎ;˜7o¾þúk©KÕ±²²‚››èüS –šš ¥R‰üü|ƒûU*æÎ‹I“&I]ª///œ:uJê2ˆLGî5ØõëסÑh îS«Õ˜0aÞ~ûm©Ë,ÂÓÓ“#w¢R0Ük°´´4ƒ—;j4Œ3Ë–-“ºDƒ<==qúôébã "†{–––V侤ÁÁÁX±b…Ôå«]»vxðà®\¹"u)D&‹á^ƒ¥¦¦"''G÷½……žþy¬Y³Æ$?ÀT¨Y³f°´´Äùóç¥.…Èd™î¿`ªvÉÉÉ(ü ›……úõ뇭[·JrìòP©ThÞ¼9è ÷,55@A°wíÚÛ¶mƒZmPµjÕŠŸR%*ýËÈÈ€B¡@§NKKK©K*³V­ZqäNTó¦I ++Kò›BW'!îܹ{{{¸¸¸`ܸqR—ðññÁĉKmçææ†íÛ·K].‘ÉâȽZ­ë֭í[·¤.¥Z<|øõë×Gß¾}Mf*&..111ejÛ¼ys\½z¹¹¹R—Md’Lã_µ {çwàíí-uUîÆP©T°³³“ºÉ“'#))©Lm]]]‘››‹ÔÔT¸¸¸H]:‘ÉáȽ†rtt4©`//( \½zUêRˆLÃÌ’••îDÅ`¸“Ùrqqa¸ƒáNfËÕÕ•áNT †;™-$''K]‘Ib¸“Ùâ´ Qñîd¶îDÅc¸“ÙruuÅ7ðèÑ#©K!29 ÷*2mÚ4( ,_¾pïÞ=¨Õj( Ý]ƒ"##¡P(ÈÎÎÆÜ¹sѲeKXXXÀÁÁAAAEÄ*ìG¡PÀÚÚ^^^رc‡Á6jµuëÖ…Þ{ï=hµÚë¶±±ÑõýϯeË–éúÍÈÈ,]º …mÚ´Ñ-ÜàÒ'NÄСC‘””„ððp }:<<„¿¿¿n~}ñâÅÏ·ûúú(¡?9òŒŠŠÒÛÿ¤ 6`ìØ±¸ÿ>ÆŽ«wÿÓJIIÁ¾}û]ºt©ôs[½z5T*BBB°yófƒmlmm1{ölDEEáÌ™3F9ç w"ÃîU¨0ÜÏ;kkkøøøÀÉÉ îîîøÿöî=¬ª2Ñãø6{“ˆŠ" &hšFåœÒÓ²¦¨uoGG ;•MæYZ3^Ž—¦‹¤é4s,«QOeE3ƒÙàÍ[6h9*jš„wÉAM‘ñ’BÀ>lÅÃÃUAÞ½_¾ŸçáöZ{­ßZýzy÷Þkeff–Z§[·nêÛ·¯Îœ9£øøø’?SSSª¤¤¤2Û¿÷Þ{õæ›oªGÚºu«æÍ›Wf—^zI>ú¨:uꤼ¼<Ýwß}%S=5ñÀhÉ’%r:1b„/^\îzcÆŒQtt´òòòêäœSî@ù(÷ZÔºuë’k‹wïÞ]N§SÒ¥BÐm·ÝV²þ'Ÿ|¢I“&©°°PÉÉÉJOO× Aƒ´aÆJ¯Qžœœ,Iš1c†N:UjÙïÿ{¥¤¤(::Z/¾ø¢–.]Z+Ó2’” O?ýTN§S#GŽÔ{ï½WfÀÀ@M›6­ÎÎ9å”ÏÏí®ä}rõX^^žš6mªŒŒ +oÖá.Þ¬cÉ’%Õ~ÎŒ3ôùçŸkݺu¦ã^…‘;|Z`` òóóMǼåîÅ:>+ù ‘Fý±æÛ*ï¨S§N5}ˆ5ær¹(w ÜCµž°uö-00°ÊK,õåîŶ¼*»%_UÈårQî@9¨ /Ñ¢E‹Rß%©ëóRƒáÒco^Zïo›¥Û'J!£<ËÚ<)õŸ)ý÷¥uŠŠ¥™ŸJ7>#þRj’(ý|†´nWé}^œöñ†4é#)r´ÔôaÏïeóx£‚‚¹\.Ó1¯ÃÈ݇䜔Î’ ¥7HÍIûIŸm–Nž‘"šxÖ{dž´è ÏÏí#¥ã§¤•ßHkvJË'Jww*½ÝÒ¥ÐFRëæÒ?÷J|)5ô‘iìüü|šŽxÊÝK„‡‡—ú^ž½G=ÅÞ0PJ}N ¿Pæ9'¥&AžŸ³r.{ÒƒÒë‰RÞY)v‚tðiJJÙroÔ@Ú1K k,Ý=]Z½CrVÇ0rÊÇ´Œ—¸x±­Ê.ºÕ)ÚSègó¥ˆÇ¤–K ¿óŒÜ\è·M{/­?,Þó=¤¡ô@\ÙåÝu³§Ø%©m„ç{ø ªóxFî@ù¹û&AÒ?_–æ­6ý^Z¾ÍóUì–~õó+Ûnã—~¾øâ­¯¼·æÇTƒ j¾!À2ŒÜ}Èésžù—Òª)RÎ|©÷Mže_ey¾ßÒîÒú^¸Òï¿”–e”]nƒÜÜÜR—LàÁÈ݇Εn'uŒ’Z‡yæß¿¼ð˜Ÿµõ|ï)%ööÌ»ÏI“Ò¶H'NK¹g<£òéCLEíÊÍÍUDD„é€×¡Ü}€ÿ…¿¯BIƒo“6ïó¼è)IZJ#{Ic.­ÿîÒM­¤k¥½G¤k\žQ'¼4Ò·Enn®n¼ñFÓ1¯C¹{)·[:vá½ë‘!žïa¥Ÿ©ú¹iBÏWevÌ*ûØ›y¾|Enn®š5kf:àu˜s÷BÛ,Ý1EúáÂÕ|bM'ò^ÇgÎ(#w/´#[ZŸ%µ “ží+ÝÁ¬C¹ •““SéµïúŠr÷B/üÂó…Êýë_ÿRQQ‘¢££MG¼Ó2ðY‡’ŸŸŸ®½öZÓQ¯C¹Ãgegg+<<œO¨å Üá³²³³™o*@¹Ãgegg3ßT€r‡Ï:tè#w ”;|Ó2@Å(wø,ʨåŸtîÜ98q‚r*@¹Ã'eggËívSî@ø„j¦L™Â…©êÈÆÕ¡C‡j­›­€€¯¿7` å^—Ë¥ÄÄDÓ1®ª;vÈårU»P¯¶nݺ©k×®ÕZwïÞ½ŠŽŽV@ÿ„òð_F‚‚‚´páBÓ1®ª—^zIóæÍÓ_|!§Ói:Îeùî»ïc:൘s¯Çžxâ ååå)%%Åt”Ë–™™©n¸Át ÀkQîõXÓ¦MõÈ#hæÌ™r»}å–Ø”;P9ʽž{öÙgµk×.­\¹Òt”jËÏÏ×Áƒ™–*A¹×sQQQ4h’““MG©¶¬¬,Qî@%(wèùçŸ×Ê•+•‘‘a:Jµdff*$$D¦£^‹r‡ºté¢>}úhÖ¬Y5ßXÈÊÊb¾¨åIÒøñãõñÇëàÁƒ¦£Té›o¾ÑÍ7ßl:àÕ(wH’î»ï>uìØQsæÌ1¥J›7o®ö‡€úŠrG‰qãÆéí·ßÖ‰'LG©Ð©S§´oß>ʨåŽC‡Uhh¨æÏŸo:J…222äçç§Î;›Žx5Ê%œN§žzê)Í;WçÏŸ7§\[¶lQLLŒ6lh: àÕ(w”2zôhåççëƒ>0¥\LÉÕ@¹£”FiôèÑJNNVqq±é8eddd(..Ît ÀëQî(ãé§ŸÖþýû•––f:J)çÎSff&#w (w”ѲeK >Üë.I‘‘¡ââbÅÆÆšŽx=Êåš0a‚ÒÓÓµaÃÓQJ¤§§«cÇŽjÒ¤‰é(€×£ÜQ®˜˜Ýÿý^uI‚ôôtÅÇÇ›ŽøÊ?~¼RSSµ{÷nÓQ$I_}õ•zôèa:à(wT¨wïÞêÖ­›^ýuÓQ´{÷n=z”‘;PM”;*5nÜ8-X°@G5š#==]ºîºëLŸÀ'Pî¨Ô€ÔªU+Í›7ÏhŽõë׫gÏž¦Oà3(wTÊáp())Io¼ñ†Îž=k,/¦—‡rG•~øaùûûkÑ¢EFöâÄ effòb*p(wT)((HcÆŒÑk¯½¦¢¢¢:ßÿÚµkÕ°aC>™ \ÊÕ2vìX9rD©©©u¾ï5kÖ¨W¯^r:¦Oà3(wTKXX˜5sæÌ:ß÷êÕ«u×]w™>€Oñs»ÝnÓ!àöíÛ§:hõêÕêÕ«WìóèÑ£ŠŒŒÔ¦M›˜–.#wT[Û¶mÕ¿ÿ:½ ØªU«Ô¤IuéÒÅôá>…rÇeyá…”––¦;w–z¼¸¸X'Ož¬Ñ¶·mÛ¦E‹•úÀÔš5ktçwÊáp˜>tÀ§Pî¸,·Þz«âãã5{ölIÒùóçõÖ[oéúë¯×ûï¿_£mçäähÔ¨QŠŒŒTÇŽ5yòd-[¶L½{÷6}Ø€Ï 0¾güøñú¨Z´hQòW€Š1rGµlÚ´I¯¼òŠRSSåt:UXXXj¹ÛíVvvvöQÙM8.~x***J£G6}:¯G¹£Jùùù;v¬¾þúë’ßËsøðáí§qãÆ•.w»ÝJIIQpp°éSx=¦eP¥ÀÀ@-_¾\;w–Ëåªp½~ø¡Fû –¿ùÿ$N§¦M›¦îÝ»›>€OàCL¨¶ãÇ+>>^PAAA™å‡Ctu—¹ú¤ÓéTll¬6lØÀ["jbäŽjkÞ¼¹¾üòKEEE•;‚/**ªñ{ÝË›rq:JII¡ØË@¹ã²„‡‡kíÚµ +÷B^5}ÇL£FJýîïï¯ùóç«M›6¦ð)”;.[«V­´~ýz…††* àÒkòþþþ5.÷’Ÿ].—úõë§áÇ›>dÀçPî¸"ÑÑÑZ·nš4iRRðN§³Æ÷ZmÚ´©$Ïÿ(BBBôî»ïš>TÀ'Qî¸bí۷ך5k$‡ÃQ+#÷ÐÐÐ’ŸSRRÔ¬Y3Ó‡ ø$Ê5Ò©S'­ZµJ×\sÎ;Wã‘ûÅi™ñãÇsM x+¤…:¤)S¦Ôé>;¦+V¨M›6êÙ³çogË–-:|ø°|ðÁ½¥²&¢££5}út#ûj ån¡­[·*..N}ã‡Éàªù«éÄ¿i÷÷ßê¶›ï¼âmì9¼KM[ªQP“+ÞFMìùþ[©A¶}³ÕÈþÚÂå,6ýÑÿVã†!uºÏý9Yº.²Ã?¿à§|¹œušùÿ[6GK7/4¶ ¶0çŽZU“b—d´Ø›Pî`!Ê,D¹€…(w°å¢ÜÀB”;Xˆr Qî`!Ê,D¹€…(w°å¢Ü¡CÔnˆŸNž>.ÉsÙÝÙOÖ]¿¾^7 ség4×ØÙƒu gw¥Ï»øûÔwÇ–¬3drOµâ§oönª4C§‘Áj7įܯisÊÝß;ŸÍR»!~º÷™uìdNö؆rGcgÖÿò¢úÕ/^Ð-1=õ÷¯þ¬“nWΉì*ŸÿáÊùÊ>¶ÿ²öùø/ž×“&ªoü0IRD³kõ䀉zrÀDŶï^fý7—¼¢—ß{Ní£nÖâ©kÞ4²FûlÃÍ:Pʶ=µjóg º&XŸÌHWH#Ï «Ç̨å_ÿU Òæè·ͪðù‡\Î@½þñdÍþõûÕÞïØ“%Ië¶þ¯>KÿP‘¡­4nè‹å®û‡¿¼¨…Ëæ*¦ug½7y¥š5«ñþÛ0rG)§/:µ»¥¤Ø%©wìý¥–WÄáÐ>øŒ–þc±2n¿*.›«6‘íõÁ”եн®öøÊµî±~Ï)$8TÉ‹sU¶ïðw(ûØ~­ß±ÚÈþ_@¹£”Nín‘ä¡_|áR’Öf,+µ¼2Á kÌ€ßjmÆ2íþ~g­g|õ‰?ÉáïPÒÜaú[úGu¾ÀPî(¥ËõÝt÷ÏúêÇóg4xR¼f}8Q£_í§SÒ(T?˜T­íŒ¸wŒZ6Ö©³yµžñήè­ñKàpjÜ#´ô‹ëtÿ€/ Üë¹SgóTT\$‡¿CÁ K’þ0î=9p’ ‹ 5i²6—®ûo¤¿¼¸A‘¡QÕڮ˨¤!Ó®Zî^± zk§ p8õì#•ºî½:Ý?àíüÜn·ÛtÔ®­[·*..N Nªqà ×ûûWÖŸÒ^×–ïÖ«ûM½µxêZÓÑ[6GK7/Ô¶o¶šŽÔ#÷zlÍ–4ÈÙ­„îõÚØÿ1@-â}îõØÌ1 ê|Ÿí†ø•yì©Áÿ¥§O5}:«Pî¨S{S˜êÓ2`!Ê,D¹€…(w°å¢ÜÀB”;Xˆr Qî`!Ê,D¹€…(w°å¢ÜÀB”;Xˆr q³‹MygŒœ.Ó1|Êžï¿•˜NÔån¡fÍš)11ÑtŒ mß¾]aaaŠŒŒ4¥Œ¸67)::Út  ÆüÜn7÷=CŠ‹‹Sbb¢’’’LG¬Åœ;Xˆr Qî`!Ê,D¹€…(w°å¢ÜÀB”;Xˆr Qî`!Ê,D¹€…(w°å¢ÜÀB”;Xˆr Qî`!Ê,D¹€…(w°å¢ÜÀB”;Xˆr Qî`!Ê,D¹€…L€Ý>¬]»v•zìôéÓÊÊÊÒÊ•+K=Þ§Oùû3ÞjƒŸÛív›{mß¾]]ºt©r½ØØXedd˜Ž Xƒa®ªÎ;«}ûö•®¨áÇ›Ž X…rÇU—˜˜¨ÀÀÀ —hèС¦cV¡ÜqÕ 6Lå.óóóS÷îÝÕªU+Ó1«Pî¸êÚ¶m«ØØØr—9N5ÊtDÀ:”;êĨQ£är¹Ê<^TT¤Aƒ™ŽX‡rG:t¨ K=æïï¯{î¹G¡¡¡¦ãÖ¡ÜQ'ÂÃÃuÇw”z»ÃáÐC=d:`%Êu&11Q‡£äwõë×Ït,ÀJ”;êÌÀåçç'I P¿~ýl:`%Êu¦qãÆJHHÃáÛíÖˆ#LG¬E¹£N9REEEjРLǬŅÃê‘Å‹kÅŠF3) @‘‘‘=z´éS¢éÓ§+::Út  ÖQîõÈÆµzEšúÜ~³± I­¯ UÛAÒéÆrüT¨?KWRRå+QîõL׎mµ0ùI£2¾Ý¯.1mäïïg,CÞ©³úð³t£ç¸š(wÔ¹¸›®3°/¨€…(w°å¢ÜÀB”;Xˆr Qî`!Ê,D¹€…(w°å¢ÜÀB”;Xˆr QîðJ†šŽø4Ê,ÄÍ:PkÖoùN¿I^¬3gÏËáð×+†«Ïí%I~í†è• õ=ó ²öçhÒØêÏ­%Ïýëò¯5qÖG ºÆ¥þ?¿õJ#¸€rG­ÈÍ;£_Mz[+MR‹°eçœPÏ!“•µj®]NIRL»–zþñþÚŸ}LwþrjI¹ù!OOœ¯¯S_RÛ¨½úÖ§¦ðy”;jÅ?6eêÈyúôœ’Ç]NeçœÐõ­[H’îï'Iº.*\ÇOžÖO…Er8´~Ëwº½kµŠ$=öwkâ¬MàÓ(wÔŠb·[1í®ÕÚÅS+\Çå¼ôÏÍÏO***–3ÀQf™Óé0}8€ÏãUÔŠž·ÄèÛÝßkùºm%mÈȪÖs{t½AÿܾWy§ÎJ’–®ÜdúpŸÇȵ¢yÓFúìç5þå÷ôÌ練°X±7µÑíot¨ò¹-ÂB4{b¢ú>öªÚFE¨mt¸éÃ|žŸÛív›º‘””¤ßnÐ’7Ÿ3Ÿ¼SgÕ4îaedd(66Öt Ö1-¢ÜÀB”;Xˆr Qî`!Ê,D¹€…(w°å¢ÜÀB”;Xˆr Qî`!Ê,D¹€…(w°å¢ÜÀBÜCµžÙ²cŸFÿ£éÆüTh:pUQîõH·nÝ”——g:†WpIJL¼^Íš53¸*¸A6Xˆ9w°å¢ÜÀBÿwµŠ Ó]%tEXtdate:create2018-03-14T15:40:30-04:00Ô¶*ì%tEXtdate:modify2018-03-14T15:40:30-04:00¥ë’P>tEXtsvg:base-urifile:///home/vagrant/frr/doc/figures/cligraph.svg”Š{IEND®B`‚frr-7.2.1/doc/figures/cligraph.svg0000644000000000000000000003334013610377563013742 00000000000000 %3 n0xd46960 START_TKN n0xd46be0 WORD_TKN " show " n0xd46960->n0xd46be0 n0xd47f80 FORK_TKN n0xd46be0->n0xd47f80 n0xd47c70 WORD_TKN " ip " n0xd47f80->n0xd47c70 n0xd484c0 JOIN_TKN n0xd47f80->n0xd484c0 n0xd47c70->n0xd484c0 n0xd47ca0 WORD_TKN " bgp " n0xd484c0->n0xd47ca0 n0xd48540 WORD_TKN " neighbors " n0xd47ca0->n0xd48540 n0xd490c0 FORK_TKN n0xd48540->n0xd490c0 n0xd48fc0 IPV4_TKN A.B.C.D n0xd490c0->n0xd48fc0 n0xd491e0 JOIN_TKN n0xd490c0->n0xd491e0 n0xd49340 IPV6_TKN X:X::X:X n0xd490c0->n0xd49340 n0xd49480 VARIABLE_TKN WORD n0xd490c0->n0xd49480 n0xd48fc0->n0xd491e0 n0xd496e0 FORK_TKN n0xd491e0->n0xd496e0 n0xd495e0 WORD_TKN " json " n0xd496e0->n0xd495e0 n0xd497c0 JOIN_TKN n0xd496e0->n0xd497c0 n0xd495e0->n0xd497c0 end0xd49900 end n0xd497c0->end0xd49900 n0xd49340->n0xd491e0 n0xd49480->n0xd491e0 frr-7.2.1/doc/figures/fig-normal-processing.dia0000644000000000000000000017005613610377563016322 00000000000000 #A4# #Best Path Selection# #Local RIB# #From Peer A# #From Peer B# #From Peer C# #From Peer D# #To Peer A# #To Peer B# #To Peer C# #To Peer D# #D# #C# #B# #A# #A# #B# #C# #D# #X# #“Out†Filter for Peer X# ## #“In†Filter for Peer X# #X# frr-7.2.1/doc/figures/fig-normal-processing.png0000644000000000000000000007240613610377563016351 00000000000000‰PNG  IHDR‡V”ü ¢sBITÛáOà pHYsÐй‹çŸ IDATxœìy\TÕûÇŸ{gc†}@%\5ÑT´4JÍ}ͰÜÒŒ’2-Ó,ñkYÚϬÌÔ2·J%ÍÂ]sTdsؘ}½÷÷ÇÅq„feæ¼ÿðuϹçžç¼ç~î9÷œç`$IÑ2r¹<55õòåË………ùùùF¯¿{÷î]ºt4hPJJ ‹Å2zý„õPWW·iÓ¦ .°Ùìÿý׈5‡………††¢vd`H•­žžž””ÄårÍ`+88xÇŽ f°…@˜‚ ¶nݺråJ‰DLG?…´Ò†‚ƒƒüñÇÑ£G›¢r„@ªŒh‘S§N%$$$Ù‰NOqsäàD§ÝJ©JuY&K­¯¤Ra–žž>räH£[A ,ËêÕ«?ûì3ðÝÕoˆ‹w Ž3X¿BR!¨Î,½û­\\ŠÚQ‡©2B7 22’ËåÎqqÙè鉛ذ¢¶v·P|ç·cbƒ„ù¸~ýúÀU*uØoÝ^0¡%’(Ê^[]p 88øöíÛ®®®&´…0 ¦~Ø":*›7oær¹ýX¬TÓK2ਫ਼žýX,.—»yófÓD ÌÇŠ+T*•_ØÓJ2`xH¿Õl(.—ûõ×_›ÖÂ4 UFèæÊ•+°ÔÕ•f.‹4€¥®®Ó„m V«333 °ç[f0‡a´€óµ£ Re„n ôfóÓ×3¡ÌQ¦Û ¬¬L*•²œèL7óXtvïæ1‡0.H•º9{ö,˜bzW+Pæ(Ó„mP\\¬V«éžf³Ètò€œœ³YD¤ÊarpZCŒÐ ¤Ê@X 8`Í7nœI­jÛÂqÜÝÝ=11±¼¼ÜDæ 0 ›8q¢‰êG „e™9sfs-Ã0ŒZ&®V"L_ ?Þ¥KM®³³³‰üÐ@Y$IR,ß¿ݺu ,HKK3…­½{÷RìpŸ¼ò"ËïyK{@ ÏFZú¯ƒûìƒ>˜;wî»ï¾›ššzèС3gÎèóãOž<$I&&&òùüÜÜܼ¼<.—»páÂVò›;Ù„_ýµwïÞ={öœ:uêéÓ§›ü:{£;²Ã瀼&ËlÚ¼_$%I‹Mm @Øì €ó·þWý÷hñÃßHÂЭˆW+XD˜G°‡ª}vpì””ªs­P(öìÙsñâÅððpX½zõÆßyçÍož>}ºJ¥JNNž={öŒ3¨ã–>•×ÔÔ¸¹¹ÁãñŽ=º~ýúƒ@FFFFFFVVVPP,\¸ð­·ÞR(ÙÙÙ:ó™L¦¶“MP©Tû÷ï§œœ:uêÆ<¸téÒVÿ#lç®SE÷wò:×dÉÏÍeyÇšnL;_©¼.—Ó1LE’E¢×]\La°aª ö]ÿÃéýÿ£3ík6Œƒÿó ÷žJ~.¨e5 7Ö‹îïté±À1äU oO…ÖÅKk¦Æ_˜——×Ò(ü Aƒ¨.—+•J###©dddä½{÷4Å¢££€N§79n Í{€««kLLÌÑ£G©­Çrss)G©Ë…B¡R©äñx-åS4N6áÌ™3UUUñññ•••~~~;wÞ·oŸª2Õ]älÒä˜T›ˆD°ÒÍí3>ÿš\^ Tve0Œk°mjŠI¨j¹ûvfiwÌ æÒã­ºËË4iµ¤¢þú'¢û;Ø=9uXÛÖô¶.^ÚXƒ0µá½ÇŸúC$‰a˜þ—7¡¥÷êcÛ¶m>>>šLooï–ò[·BMrÓþÓ”””jO8·´»ËL¡Íj€ƒ"Ç“8œË2Ù)©ô Xü¡›™b "6€TX(ªÍÁ0IªyÅGíO•Á!àºkwUC¾v¦JÄ­ÏüP”÷ƒK¯%ŽA úk³þâe ÂÔU rrrÊÍÍíÛ·/äääP£Æ%66jjjôÉo‰DòçŸþôÓOóæÍ£rŠ‹‹CCC÷íÛ÷ÑGÕë@óî²ãjó©´B­žíââˆaÓÙìSRé!‘(ÅÍ ¬A ô„Wt‚Ÿ[þèöW¢Ú[2a±ƒKH;ê©>10`8`4 0êkÌÁµŽiXcÎã€=Τ=.‰7»üqhõ,öTUÎT?.I4»üqhõ,ùTU$šŽ²Iw¤øÖËëÛ–w÷¢Ñ*:·iÜ s‹ý¬æäDµ¤€$ø™x'üIsxF(;¤„BRtXto‡Z¢{ßæÐ]ôºó5™ I…GÐ({Qx‡Œ/¹ù¯ø¨øëúD³ê¸¨„Å’’¿0º³s×)ì°¹¸ƒ§¥=27( ¢í#.ØÏàtõ¶ËsØ.I²)À™nîþXãì0B^WŸñŠ¥€°$$!¼³EZ|„UëyFcÑýõ)YS|¼:¿¢ó¬gç±F“4<óuo«`3ˆó÷¸ôxÓwl:'*Ù%P{@%xØŽùŠTDÒ¶F1*Šˆæ¶æ}/¼ód'TNä2v7ÍìÑ’P ‹”õ÷”õyÊú{Êú{„œ¯³ Ã#2±8>>^ÿ("FŠ"Ò!ï$ÑÖ-¡l »þñvB‡^Bà1Ÿå3@“ÜýVÁ˶ ?†Ó9];åD½ç9t‡KÏE-´Éµ¦Å¾%*#¬ wг<“¤šŸñ>¡h°¨OÄ”uw·6¶t©2¢­ UFX;4o÷þ_hf¸¨%•õY«:àÐÂæ  uWß% %•Äì&s»nþþþlÏ>jeÛ6h1•¢ÎäLš4ÉlF©2¢ÀòÂ]“”•Ÿçïµ ?õ™ªÅe“˜[ìz—^Z›a8Ã5,88X.¸+mÈ'Ô2ó¸%æßU)ååúÎGXH•º>|8”ªTæ4J™‹ŠÒkÞ%rÓ³·&)ÈùRYŸg>ψfˆîý$«8¯I:‡Íré”@w ¥rèìÎÝÑÁÁ¡W¯^$©æ•üeǨh$ÑÑÑæ1‡0.H•ºa2™pK¡xfI#B™ó÷×±’Ãéîþ‡3#l“„‚%™T‰Íé¡AQsM µ:€éÙ›ó\2†»ôx‹Êd¸EP)))ðèær=¢ŽH}ÅE^É_NNNK–,1µ-„)@ªŒÐM\\lihP›Ë¢`KCƒÆtshή1k5I•¨¤>{™œC ´ dµü«ïÙØ8p–»ûÀ¯4¡Ú;¥¢lj¦zM:u„ j•ôö¿¯ðJþ2Ѳ{•œ_r3õÁ àóÏ?ïÞ½»)¬ L Z¯ŒÐ@ ˆŒŒär¹s\\6zzšúõXQ[»[( ¾sç‡Ãi©dCöZqÁMÒ-v½SÈ«&öЂ$j/$É«3“î9ä;–ßí"’âÃõY«<‡þÈòDåðùü‰'ž=K‘Å\}çˆÃ#¥RÆ“‹Ë€F£¥¦¦&'£¸ñ¤Êˆ9uêTBBI’èô7·AAtã‡h-U©.Ëd©õõT* ÃÒÓÓGŽÙJyR-瞪Ù/£;z<¤ù’‡@˜ÁoDy?h’.=Þr‰\Ú¤ I¨ªÿyÉ{Äþ'‹ú`ÿþýßÿýùóç=z‹joÑ+¶ G*5jË–-¨—Ü¡AªŒhôôô¤¤$.—k[ÁÁÁ;vìHHHxfI• °æôdR%¥’ ·p¯öa4–‰D @^y±ö¿Eš!h–Ï@Ï¡?ê |!«¸àà?Tg%Aܽ{—Ï׬}xzzöìÙ“ ɇèÐ UF<¹\žššzùòåÂÂÂüü|£×ß½{÷.]º 4(%%…ÅÒWY©BMÒ¹ë×~Ý7Bµ¤¢æÔ$MpMš£÷Èßí3V3Ât UFtTø)ÒGÇ4I¸¯‚FYЄmCÊÚssµ·ÓÍ+~'Ó;Æ¢N!l4ÑQq‹þX{?ùúk«µâ9 FF³é‰$p"—!IF˜¤ÊˆŽ Fwv¸ ÙT’P ùïS›¥#ÆEVzR;œœCÀ0vÄ< úƒ°a*#:0 ÷žœ¨'+@µ·„w·´Rh*QIýµ'¡×iÎn±O³#Æ©2¢cãÜ}†CÀpMRtÿgyå% úƒ°1Hµœå]âñÞÎôøÎlq==a H•Ì-ö3š“_cŠ$øY2žE]BØ 7Ö+ëïi’œÞ+‘ôaó UFtxp¦›û€€Ñ¨$!«åg~`¢ †»BR|TRô‡&éü¢s·iôa UFØL¯h—^‹5IyÕѽôa¨ò²ŸÄ]§»„¸Å|jAvRe„à1Ÿå3@“ÜýVÁ»aAR%®»²œ|¼#2Fsðˆû£;[Ö+„=€Ta+`¸Û€Ô'a‡I5?ã}BÑ`QŸ•úkŸ¨„Eš¤k¿é®(¸4 UFØ4o÷þO–¬¨%õ×Vk´ z"~¸OÊ=¡I:…Np yÅ‚þ ì ¤Ê›‚å7„>W“”•?ü­¥Â*ÁCsø„èP(ëînmÔ$ná®}WµR0.H•¶†Kä;L(MRpk“²>¯y1BVÛpsƒýBt… îê»$¡ ’ƒí·mG†0'hw „ ¢—ÕœœH(…T’îâ=òFwÍVwŸÎõ›2Ì­Çë÷P @<~ ’u—–ÈÊÏ=ÎÆ<â6£-Ofõ•6Í9ÐUk‹JX\¯µÄü<è³F¹»8áþ ³{‡°RD÷Ö’dpî>I2Âü ¾2Âf©¿þ©¤ð &éû¹SÈ+T_ùüϯus΀‹µc*dÁsai–/_$I*j®ñÎÏRMå3={{Ûáè¥ anP_a³¸öù€¡µš¥áÆgÔZ—@/F§ûT&‡Î·Œsk‚Õò3Þ×H2Îtsø%’d„E@ªŒ°Y0Ë}àWÝ‘J’* ÿj2‹½3Ñ ÇŸ¿nŒ:Ë9ˆ° h8ð3V¨¥Õi w°æäoQ§ö ReÄ3Ëåk×®3fLXXfÂÂÂÆŒ³víZ¹\ntçéœ.®}Vj’Êúû›Læ¦ÉqEªl÷¼;É[^}U“dG$±üž7º•ÝŽæ}WF´FzzzRR—Ë5ƒ­ààà;v$$$½f~Æ é£ã:O©Iڟ寓h¯\{åèîÕ¿|Ø üÿÏòà9t`Fî®ØF;B˜¤Êˆ9uêTBBI’èô7·AAtºÑ­”ªT—e²ÔúúG*†aééé#GŽ4® R)ª95I%z¤ó쉪ÉB•«q-":N4ñ ç_<\w£9x{úwð4®M;b9õZÂñéÏ4Áð¸BR!¨Î,½û­\\j¢v„0H•º‘‘‘\.wŽ‹ËFOOSê VÔÖî ƒƒƒïܹÃẫ<©–«„Eª†|¥ _Õ¯äç©e5:K^®Y* 5ТÃcÄp¯¿<™šÏÉ4¯øLïãZÑ´#Ÿ®SBû}lô^xSH¢({muÁcµ#„ù1~×alÞ¼™Ëåöc±RM/É€¤zzÞV(²¹ÜÍ›7òÉ'í«‡ón|¦¬¿¯•è¹Å²+_ H•íŽ(NæIàD.5º$ÃãvÄöˆ é·Úä’ Òoµ„ŸÇåæÒŽÍöBèæÊ•+°ÔÕ•f.‹4€¥®®Óíg¹»ôZÂp‹Ðÿ4áË r,cßÖ$üãÙo˜Âu3ûG$a˜™Z†Ñü#’À°v„° H•ºQ(ЛÉ4§QÊeºÝÐ]BÝnòõ§CàHÐc—+-Y¶/ØtA¬ÛyM’[£tÓÚj̸P73Û#Ò•·eÎÀv„°è»2B7†@MHˆ™ízӸĆ¢¬ÏÞýVV~¾•ýIÀÿ,Ÿ«&Í6(€°$4L=Âû¨£–J*Uä«ßÈ—˜ÈÕŽLÖ±?ŠIÉ8،׎æõ•¶ í‡Çàÿó±å7¸¥2.ôzsz…° }]/k$>ÝSuó¡Ô‚þ M@ªŒ°}Ïy>ÿƒ×ð_Y>t@Ÿ–í„ÎNù]œïi’\i—ŸÿAÿõë€æabÆgR«Ú¶pwwwOLL,//7!&“uäÈ£›@t˜^}=ãwNú´$ó^ÓáJôiÙà0øÑnÿi’B•k¨ýA—™3gêŒzöÙgŸéYƒ•F’$†aÇïÒ¥‹&×ÙÙ98Ø„éh,’$)‹ïß¿¿nݺ°°°´´4S ©¯¯ß¹sç¾}ûjjjœœœŒkÅʹZte`h\[¯²ïÊÍ¡~×¾mÉ‘œëÆTÈ‚/ÖŽ1‘E„5@Ç”£|Žh>U¨Iú©šW”š=£Ld}Wn+í{^@yy¹@ €Â±cÇæå5þͽ¼¼¼¼¼ô©ÁJ„©q½r—.]""Ú°˜Äp´-ÆÄÄøùù%&&šÔPŸ>}~úé§²²²îÝ»?ó*[¢¨¶ð·k¿¼—Ô7¨Ÿ¥}±*åA•5Azq®»3x® ~UUÕ‰'JKKkkkŸ}}ÄËË+00ðÅ_ôõõµ´/ Æý¢öìëõƒ”ôÑí~^h’íS4k¦gDÁ0ì·ß~{ï½÷Ö¯_?wî\±X¼xñâC‡±X¬·Þzkýúõ8Þ8žžž¾|ùòµk×Θ1cܸq999ãÇÿí·ßèz„iôððP«·ñ©®®^¶lÙñãÇU*Õ‹/¾øý÷ßSo:-å7q²%<N§Ûá#i|ï û®ýºôࢾÁýLªÍT7Wƒ Žw¡Ór8 ÌM—Ë:•Ë::_ÿ/mãÞԎرÐÇãñrrr‡ fR[VõŠãååÞ™ÓgA,*§H^, ³¬Wˆ–0Ñóª%ñj‹S£^ÖÔÔ¸¹=ÙHÇÏÏOs¼fÍšõë×ÇÇÇ@rrrQQQvv¶H$š>}º¯¯ï;ï¼CKIIY¹reeeåŠ+Nž<ùî»ïVUU%'''%%é'ýäÉ“QQQ@’dbb¢››[nn®Z­ž4iÒÂ… :ÔR~s'›£V«óóó—.]úÞ{ïÙa:†ã´˜™Û.l½Á;Á5¹6Ïqq!$ùH¥ÊÉn)jõÖÛ/9[µmo1à´°yo{ôéÏòò±´G&AΫ®»™™¿skZZZPPP·nÝLdèܹsiiiÖóŠC½Ž¼ Ïò}sœg½Ò3»~¥B´ˆ‰žW­ˆW+XD˜UyèЧf=h·¨””Jç Åž={.^¼«W¯Þ¸q£æ‡%''OŸ>]¥R%''Ïž={ÆŒÔqKŸÊ©÷‚ x<ÞÑ£Gׯ_ðàAÈÈÈÈÈÈÈÊÊ €… ¾õÖ[ …";;[g>“ÉÔv²9=zô ¼téÒ–þú¶ õúÉ—ðÀÔÚ¼ÉóIpÿ3R锪ªßE"kVåÓ§O“$2q–ÿHÓNr´,,/ÿ‘ã õÅ>}ú´‰TùáÇiiiVõŠ£yYûKUDˆ‹È{„š´êHÃÔ'a 4ÛÝÙ#xŒØ\ ·jÏ…ÑŸW­‹—6Ö LÿÇyyy-ÂÔø^Éår¥RiddcšÈÈÈ{÷ž¬1ˆŽŽj°Zû¸%4ï®®®111G=z4äææÀÔ©S©Ë…B¡R©äñx-åS4N6'///,,¬®®îÂ… /¾øâûï¿?cÆŒV³I4¯Ÿšóô›‡88€Ð+µ¥P©Tào;ßùÅ'ü™šc ¬ðGûudý!â7;Æþ`>]§I„B!.ò²Åü»JiUç¾YÚ/s`ôçUëâ¥5SÞ¼šŒÂS“·õ¿¼¹O:ߨWŒmÛ¶ùø@­[ÚóaÜç•áâeNajƒ*3Œ9sæ$''oß¾] ¬[·nñâÅíö£%ÂÃçNºlÙ2LJ "‘H233'L˜ÐRþ3{½………$I*ŠŠŠŠŸþ900°S§Níóíù¯½ß 1 IDAT¶ï±´9®¬Œ¨'ˆ:µº‹uØÏoƒC[+ùá¿ïÞô& G±© ÀѵÍK:mø‘Õ¦ç•áâeNajÛÜM›6-\¸°oß¾L&sÁ‚&š<µk×®5kÖ¬X±¢¬¬ÌÝÝ=::züøñ­ä·Îرc©&“Ù«W¯C‡é³RËnÑÜëí®µ¦¨W«o*Ñ,«;~ÉÜ}þá¹·ãßi_<Âf6äf­ Ž\fi_¬ýµÙpñ2›0Ñ¡Õø/MN999íÞ½{÷îÝ­k鸕j›Àb±¾øâ‹/¾øBÏ|ýýG˜š@D÷•ʽBá'uuûE¢?}}½hmëø>ª+yÿðò¸ÐÁo[ìÞÎá Âp y2•L¦”É”2™J&WÊ´’Òòer•Lªlÿgì['^’P)*9ßÙ#²Ç°]ïX#þ(&""¢ù“¿%ñÒÆJ„ õO¡yëÜ ßµ»€ƒã±,V,‹åO§oª¯_UW·]¿M¸Rtéڣ̉}&Í8Ï™Ån·KD»)o(Ûxò‹&3ôÁ‹­W GÈ„EšcµB ®»ÃöˆÂi¬vWh“˜z‰E@ªÜ.¾{ÕÒ.ľk{uΞ 0Ñý=ÏÅeS}}º´m›åáNë©”jåþë¿¥çýóæ…/ö‹ch£3„YÒuh/ÿçþwjÃŇçõ¿ 샄UGàxûŒ˜œ$¡V‰% kŠþxtk#¯øHİ –ç³/~L‡~dYäye  œ½ SJ÷]ûU穾Áý¶LÞ¶eÒ6SÜâY?%옹»wPíœ:I݆ôõó÷¾žSv˘þ!zàîäþybêÊÑ«õ°y¥÷ø!†M¶ÂpÃÅÅ«o—ØÏ{.’4<(¹±Á  ;–z^Y¨¯l/¾õgó!83¼oî‰ ²K–»{wß:ù»³Î|wak¥ R“ÿ úþ’ ^¹hè½Bšò »xYf!Üù)#¼}I’$U*š£cÐK^ÑçBR­–UW:úšÚCD›x±×ؾÁÑ_ü».›{½õ’AnÁ‹‡¾mDӾݦ—ån«¯8gÄ:­K=¯¬¤ÊvAóOÓÝßïÕÖ€Š$â¾R™¯TÀR×6TÂ{!lÄà.Cö]Û»7sLÕ8q†òôý“— .Îè?{ZÌ ý_Ú~¸ôÝ‚!‹B¯¬¤Êvö‹§©ïïÝB¡æ˜ã}Y¬…θöîiÍ¢³æœ÷R¯±Û.|{æþ)Ÿƒ2•ì§Ë?¿“¶pèÛÃÃ^À Å•W|q݆ôõÛ¦þ`©Ò$A(øµu·²q÷øÿ}W'©Óäß*½™ôëœq‘‰ó‡,pslÝS$ÀŽKßéú|[°Y=ˆ?pšP*ÄŠ ölËÿykØüåõwnÀƒ7€¤´D-•0\Ý9Ý{Þød™_ühÿ^¢³]Ìì¤E8?e„áCô„B^w+Ë(þ´‚X.ú%s÷¡*…v>†aM±4ƪ1k4†áF« hŽitggçüÃæz2¼f+ÇfžWí©²í304nZŒl“…cøØÈ—‡u¾ëêÎ?nRªãˆ$‘vûÈ™§_{cBŸ×èZÞ‰äB«ä_ü»~ëämð™ÁtéÞíõ·o¬z;lþrŒNwðñ™4‡:Kc9`8ÞçÓ¯ëneUžþ»âÔ±ØÍ?›ßI„N”jåќû®îlÖ79Ûy@lçØm¾ÕÎ|sð瘜g` [z^µ¤Ê¶O¨§­mÅáÌb/Ž_šõêÖsß\)º¤ÉÉ…[Ï}}4ç°&T§R­ÔôorÊn¾ù‡e<&IQñCÇ<£ãJþüU%¹t µLJÈåj…̳ß@N·ˆËó'ªÄ"œÎB!Ç™v5BTüðÁ÷_JÃ…¾(ÅÁÛWZQöà‡/e5•:5É{àÐòô´Êsÿ(øµn½úD,þÀtÎ@žÏ?»ýâ¶²úÒ&§ºzu[4ôíþ!.^ÒÎïÔgrô4Ó¹d'ØÞóª­ UFtT‚Ý;mÿåÕ¢+[Ïý¨®D“¯ª³ÉêÒïÿÛæàÁ’ÕÉÍædÆ’é$A¨¥º 'ì­dp ì±äƒûßm$ÕjœÁôŒ˜ðÊ­ÏÞ’$TŠÐ)ó7 IÏ~¯­x³ÿ×­4 …‚Á`²m«^dÞÖÏC&Ïõ0”›vàáÎ-‘)ës¿^ë3hxpâ 5xÅõ²R$¼œ4¾ËŒ7MäËòÛßžßr·âv“|o¶wÒà·Æô|‰š9èÀx²íŠÓé£Ñ£7ÃAªŒèØ ‹éûçÍß¾ò5XMA…êLˆ£]Xª”vŸÒåöv3¶ôÝÔ+f°WÌ`휘ÿýøT ‹LYo:ÇÚDCCÃáÇgÍšåèhÌ¥>MP $¥%ž}€g¿Å¿ïV êEÅû¬Ù 4Ǫ '¥Õ@¨”­×ÙJë¹Û/n»N3۟™é<=vÖäè©ô'J¬­ÊK‡-÷w 0º?;½Ù!:¹ßÔ}ó%>÷ªvgE©V¿ûW“®Ý8~­n÷_k†F£åååmÞ¼¹ªªÊüÖŸÚí† ²W-!”Š€Q/ÝPƒ´þë³_ÎÚ5í|þYmI¦á´ñ½'î›÷ûìsµ%4ÉÁ]†Œ4¾Kû©2B7Ç€R•ÊœF)sQQQí¸ÖÍÑíýQ4ÕÙœÐq¤ iv ŽãPSS³yóæÛ·›ê ÇÕ)¨sÝÍL¨Í¾êÖ³ƒãÆí^zì$I*‘PÙÀ—”–x JÈå@ýk88ß›µgÊÎ×þ¸qHE¡ÑšjÉíÛ·7oÞ\SSc G —ÈóɳõðÔþÎÛÃÞùuîþÂG>S’À‘á¸bT‹c×T;Õåg¯ƒVƒ‚’(Î^'ªË1¼!,ê+#tÃápvîÜ™°[(<+•¦¸¹ rp0Åhv©JuY&K­¯¤Ra¶sçN‡Ó¾ªjżM§6þWpAŸÂ Pd“×aÏ·ÏVGG­V«Õj‚ ¨)š«Õj…BÑüòªªªÍ›7Ïš5«GŽ™™Éa^'3KÈ¢&S¬4ÆÄ¾“g÷ŸëâІÀj8†ã´gtl4í¨ºà@C奠^K8>ý™&ÍVH*Õ™¥w¿•‹K lGËÒ4b¡MzzzRR—Ë5ƒ­ààà;v$$$´ïòwo=ÿµP&|vQ-úcq_½û50jD”BAÑþŸÂæ›¶‹£ Í­' C©Ô½ Çñ±cÇ>\ó™™êŸYó9¶LMì„ÓŸÖQjnÖžû¿ ~~…í@íaqP_Ñ ùùù©©©—/_.,,ÌÏÏ7º‰îÝ»wéÒeРA)))íûTI¹í·×e¹°8`B™°IO¨n’Ù †Rhü•¯p~Ê–‡à8©T@ð«Ó‚^šØ¼˜f7eµLRq꘩U¹­D‹ã®Aüõ×_¥¥¥S§Ne¶q móãàéà"eÅüç{yd¹’A€øœú挈+öûÅ´£:D;BX H•Ï€Åb}üñÇ–ö¢50À´w˜'”*¤"¹P$‹äB±B$’‰„r¡X!Ê›¿ÝLw¤÷êÓSI*• P€2ôåN~+0‘o}Ö~ãàí y9·Ö&û‘æØtSK+ßM¹U¦¸qãFYYÙ‡~hÚølýÈ’!lã…´NYãê#¢ü±€#ܦ+ÚM„õ·#„•€Tak`€91œ˜N>º>.Ž_ oõ]`u2ÚÃúœ3ÀTª¬Ú$µLJstjÌY³›r—Y  îffÁžïäu¼Àѯ†N{ÃÔŽ=’$qoE›ƒƒƒ“’Œ°w¡IqH–yŒb‹àUå8ú/Oå+¾$êR"¬¤Ê»ÃŸéFϬ¤=lÀêdP¡lº‘!I¥°¡ø÷=NÁL7hÌY³›²¬¦ª.žêûÙ·’Òâ[k“­A• UŽŒŒœ5k–õ_+Y ¾JâAoŒ‹ŽWKG ”‰M—E!©2Â^àç—üõqð«þL7È|<òº¨ØtF3–Ìšƒ£kDd¯÷×Q![æ±pF§3Ý<‰æXF£ÑpÇq¼ù&Iåp¹\•®(oñññ‰‰‰Ú šÛÇù)#¼}I’$är:›Ýuö"Ï~©üß%3¨¤JEst zib@Â+m2¡b1¶Vœ\â?²›ƒ/•ƒWK™G œi,±Ú|»• Ï©2ÂÆ¡Ä¸àDÿa9ø3Ý´Ï’&ì+øv/õ]ù‰E‚È^µ$èÅ £^.ù}OóK0jù™‰·hÚ´© Q±V®lº*ÇññãÇ2ÄXþ øö7’¬8{âÁ_Åm?¨»@ÝÍÌ»›>n«*€œPn­8¹yȼLDå`<éò€1_—ÿkë„QAªŒ°MšˆqK¨»¹B–ÙœÐsVKÄ@Èåtg6 ëí±5é ³X¬Ù³g÷ìÙÓø–0ŒÓ½‡J¤{yI ~mÝ­,ÞýÛW½‚P)^e+ÂK…9éþNÀhi­ÀÑ­îEXH•6…žb¬AÝÍíÙ…Œ ÓÍ£I0çßüBí¦üÜŸ›Ù=ÑVeWW×ùóçß IÊxUÅwyÇéQ®Yfí×ëÝ5í·BÇãB™Ç‹pîaN›¹1ñ—Ž^H˜–©2ÂF¸ùÃßw~=#*¯ÕÿÒÃôpxv¹ö¢{±†5æ¬ÙMYs‰ƒ·Ÿõ¬•ÒÝ ˜?¾››ñ_ežˆn¿ao¾Û¼@üÓ„R!~TT°g[þÏ[ ZÕMÇcC™ã;åüü²´^þu…“·¹ßÒˆ& 8Ø¡ÏüG~½ jn‚³ŸŽM÷t¢îæjR—lª¯Ü£G¥K—šB’ þÀéø§{.ÿ¤>/GV­{[œÁtéÞíõ·«Î§jŽ+ƆŸ,žãT¤ÍHW›xB>ñ,*#l óë×mЪi³.~ùêÁô‘gó_wPh4ÚàÁƒ“’’L4Ê{àÐÀѯÞùßj•D¬»IŠŠ28Æx¢aŠ—BoKJ5õ…•iÓ7ˆ+ù­\„@˜¤Ê›CKžc—½ÚÒNÀ¦¾¶%†þÚk¯¾JB¦¼îèãïÿ6ÀÓ!ú3–L¿ºhê¥y¯”üùkØ[ÉÆ1FÃ~¨<“#~žº¡¸êèô ¢Š:ãÔ@´ô]a³<<žyýÛ´–ö_AÃ×úcêzµ¿ c8þÜÊ MòM÷‰]E?V=¶àë¢ôl*Gð¨:mú†—Mq ô4‘Q¢P_a›<<–q&ùBÝ‘ªy _#(T$1jË¢.cb49nMÚŒ ÂRž½BØ-H•6HIÆixÌÒ§‚N ák„686ò›…]_ŠÕäKyG§opk,èÂ>AªŒ°5šKò _¾½$Q{ò—Ù†¯ üÛŸ`[CÀiøˆÍ º É•צMßÐPRmA¯vRe„M¡S’»ÖuÌ“žáÃ×Õ—Îd%Ï»ºxÚ•“3ß™]uá¤Îb„B^wKßàa¤Z-­(YM¥fý.Âlà4|Ä—ovåÉGtQE]Úô EºWj!¦©2ÂvhQ’ ËãñÉ E½áÃ×÷þoCÄ¢”ÿ·/nûÁ>Ÿ~ãÒ-ÂÀ áñFËÀòô‰ûþá"Ú FÃ_ؘ>a°&G\ÅO›‘Z_XaA¯všƒ°Z—dðëÛÕÙÏ]\ÉÏ1Û ©nÜF‰éêÎtm/ûçHåÙÀòò‰XüÔØu“St'giEÙƒ¾”ÕTbtzèÔ¤êÿNÁã–s>{Ÿšx,*~øàû¯¥‚á _”@f,™ñ܇_XÕ̶FÇmxÃñ{¿_¤rÄÕõi3R_þ5Ž«¿e}Cب¯Œxr¹|íÚµcÆŒ ÃL@XXؘ1cÖ®]+—·{†gJ2h±²ucÄ¢”;WÝ\³¼ôØïòÚÆ9A‚¹Ü´ý}>ý::õ{º“sÙßhÊë<•ûõZ>ýlÝÛón¯ØA]ç,€è?8ú4^F’y[?~uj̦}<ܹ…ʦö`ŽZ¹¡ô8êR Çâ?½Ç䡚IMCÚŒ üü²v×Ù!ÚÂ@}eDk¤§§'%%q¹Ügm/ùùùùùùÿþûïŽ;vìØ‘ÐÖô’dèòRì¿?¬P!ª¢Ï^ý‡ðs®ó²þ+ùcOÈ”×ÇŒ¯¿sü¸$¥%j©Ä7¾ñç4?¥Ô‹ŠöY³™Út£éhŒJAƒ¤´Ä³ïðì7°ø÷ÝT¾yö`¶g0‹_?£á¹ûÎQ9Rž mFêË¿¬ðÒy‰J*ç^¼šÝüT‡hG+©2¢EN:5fÌ’$;Ñé)nnƒ‚èÆ¿aJUªË2Yj}ý#.w̘1ééé#GŽÔÿrý%üúv=Õp×NS¶˜,ϘAž1ƒ|ãGßÙ°2pÌxŒNwðñ™4‡*@c9ªFÕl~Š:h)ÈIë˜gf{Æ®áøÝ½g¨ i0mfêË¿¬ðŒn^¼ä\΃?/5WeM;b9õZÂñéÏt2þH¸BR!¨Î,½û-·]ía= l„nÁ¼yóH’œãâ’4™Í6…$@>™ÍÎ šãâB’ä¼yóž×¶I’0ìŠ ßpŸ æÊ9M¬fEéî žÑq¢â‡*±ÈÑ/ÐÑ/æè¤)ßüƒãÆí^zì$I*‘§3àé–W§ Îu73 6ûª[Ï>Fq¡/öü𙑳ŸÈ›Œ/úkæF^î£æe ÿ¹Æý﮼á©ÞšväÓuJŸ—þõ yÅ’ L'¯Wú¼ô¯O×)mmG«©2B7›7oær¹ýX¬TOO3Ü%8@ª§g?‹ËånÞ¼YŸKÚ,ÉОž©.ª/Ÿ½¾âÍ«‹¦f,™^~ò/jsF§ÀNK>¸ÿÝÆ¬ås¯¯x“û×AMy§z.[Å¿}yþ„Ì¥³êr®1Ý<¨–Iµºñ2 ëñöÊ’?÷^{/©öú•îo,5’û½Á°!«§GÍ}2 ,«ý5kcÍbíR*™âÑÙ[„RU|ê†v>ÕŽØQ!ýVfú–„á!ýV³=¢ôoGk`#tsåÊXêêJ3—EÀRW×¹ÕÕ”éÖiŸ$‘^ÉŸêÌ÷Šì3X;Gùù)Gÿ ¾ë¶hç4ßh™Ò-zÃvZÕ̶ † Z5 £ã·vüCeÈÄÇfÿoì®dŸ¨.TNéw•9<<–>qˆæRêföHÂ03µ$ £ùG$å_^ªO;BX!¨¯ŒÐMEEôf2Íi”2G™n‹K2‰û`JŸ7_Ò$åɱ9›ªnPÉ‚±bÊ.çÊø"M1êff{DšÑÓFsÏlGë©2B7999`¢oÉ-A™£L·’dk¦[·n çY]”JÊ¥€€€g–l…+&õ[8N“T¥Çç~Y™ýPª½Eej¢ðŸkš2ÔÍl¢oÉ-A™k½!¬¤ÊˆŽ’d+‡N§@åùtK;ÒÊ%7wC7gìŸ<1zq¢&©I¿þåí]§ä‰&óáñ ­ ì¤Êˆƒ‰$Ùj»w¦À(]ÆV1b†a%üRqꘕüIå¼êŠSÇ*þ>Ò?qùÀY[ 0ôûnìòñ1Ë^Õ$•bYÆ—¿k¨Èz ©i0Ð Ân¡ƒ®­gÇŽ{ìØ1ÓYÕ¶ˆa˜««ëóÏ?¿}ûvS<,ø|þçŸ~øðáââb:Þ¹s瘘˜;w²X,£ÛB˜Óõ’5Ý»Îg^›•Cu9މêïÖ­[bbbZZ/ÅJèÒwÌk)‡™Ž.$@ ä‹—XaÌÛ¯à4ZæW±Û•Zû,©& ÿ¹9 í/bVfΜ¹wïÞæùëÖ­[µj•>5X‰05~5<~üx—.]48;;݉&PI’‹Å÷ïß_·nÝ‚ ÒÒÒŒk¥®®...ÎÝÝ=55µ{÷î ׯ_?qâ„J¥²U~tþv§øç,í…A˜tàzĈ÷ïß/=ºŸéêæÑ§?ËËÇ(ÕZr^uÝÍÌ’?~Á0lÄ ưaÃzôèqâĉÒÒÒÚÚZÓÒ//¯ðÞƒ˜Ž.T²† ö€J–²ß¢q²zaÎNÝcõg Uní~^mܸ‘Rß±cÇæååQù^^^úWb „‘$‰aX^^^D„6½Ñ“æOŸ>˜˜(‹[¹ª¼ýöÛYYYçÏŸ· nÎÍO<:—³ô•€mø/¦^kBBLåV xÃÓ¯Œ+ÉÔïÒ^Ê©VÏ+íûdÏ„­ Ç fiGÌ ´{êX4.(pǪ‚ñûÔñòåˡ֊* þÎzx<£îA‹a±1›yáKg?wê~09¯ýÞ·‹Œƒ= ½‘ã,KûžWÚÜ»w¯GíøíV"LÏø®Œaؾ}ûwíÚb±xîܹÎÎÎ~ø!Ašb'OžŒŒŒtrrÚ´iSEEEtt4ƒÁ˜ptíî4 ÙJORŸ_vŠ_vÊ·Û4š¨²ÑŸW­‹—6Ö LmˆÜ„ãO}„¦¦‰éyZz ^1¶mÛæãód*¬··wKù­˜ð÷÷www?þüĉÛí§Í ýú©ÁXÚü«PI..Í%¹}˜H’3ïó_}[“|è5ûÑÑñÇ …jw0P€lÏŠœN Ü+hp¯ç×̪¸ž_x"«(=[\Åo^òá±¶…)¹ñyÕÃ},ç nÿÇö|j0¹˜[_qÁ:ÊÆ}^é/^Ö Lmx†999åææöíÛrrr¨Ñã 555M¾F·”ß 8ŽOž<ù“O>IHHpqy2¹ƒúÎßäÿÉæiþú©Ápm¾,“ÀKNNÏ,©'F—d¡TõË©G×Ô7—dÀ1ÔW¶ X ñÄ*4b\Mtrpv—‰uª>`4< x@ÿðÁ«gTÝ,(üçZѿׄeOƒéÜð±%Äü¼ª‡¿ÑèÎ=†íb96õÜ9Ø·ÛŒöùÙ1îóÊpñ2§0µA• Æœ9s’““·oß.Ö­[·xñbý/דððð©S§.[¶ Çñ!C†H$’ÌÌÌ &´”ßúÒêuëÖ 0 66699922R&“eeeíÚµëòåËÚ³Ûôd{·× øeÖŽ!Ú\¢R@7†‘_ä%É×óë9õH i±CŒF°í_¼„¯ö¡¦¨Þwô[WþÜ``Žùõëæ×¯Û §Tß.¦ä¹¡¤m¡ÍjŠ€o÷™Í%¹ÝØð#«MÏ+ÃÅËœÂÔ¶ñÆM›6-\¸°oß¾L&sÁ‚K—šd·×]»v­Y³fÅŠeeeîîîÑÑÑãÇo%¿¼½½322Ö­[·~ýz.—Ë`0"##ßxã jdÑͽަ«¤$ ÎF~0Š$‹¤ª½g¸÷žÑ¢!U¶h ôÅ•A“ÂNÈ»¤£CÖN0Ì'*Ô'*tàŠIµ÷¸…'®Á»»ô¼TP“(öHÐ_› /³ Ö—™[ ~ñlBÀ€ˆW~ûôŽ"\R"#ÉÂN\ f*ŠÈ÷aó —ä {N>j+ŸYÒãuÆs ±…è@€ÝWÇ(À‘J–Þ»œþãÙÒ?ŠHÖ} µ,f|a„>Eä»®s ¯Êú1ÑjOKaÖ}úÖÏ“ûû·ô¿ªþ@©ÌS*û)`Ë+ûWúöíÚîË%2õÞ³Ü+¹úΰE#Øv¤?^TBô¤’Aƒî "CL\Oµ ht£MΰlL)*·¶´ qóÇ:gOPrppx T‹¥Ê†H²BEì;WÊ*C|dJB¦PË„LÑšî¢9Øö†+Æcc "Ò•J8_Ö³³ nÀ¢ÃÁi„Z¦VIŒÒW¦èÐ,Ó=¯¬¤Êö‚J*¿µã§ ¿¿§³Ù¿…{„Â)ÎÎ},ß”IÇßÓ¹y~æ}þöcE:/A}e;Ä+È'ûQÇe<é…۵â,Ø€å(H¸xõ³ V‚IŸWVŽ}­²gîüz¦Éâ?‘¸7%qoŠ·x ‹5•ÍVäkUU¿‹Å*­É $@¶\¾ŒÇ«U[RùÔyär…&‰=É‹†VFÙŽ˜È«Ò$\*—¶: bj(1®ãž° ÖƒIŸWVê+ÛÍ_<þ¾ù¥§'pP$ZXSóÖ›ÉtÁq!AÜQ(jÔj `µECŸž¹YSY'£ŽI‚¤”‘Ý5gq4‚m—øáE5Ré‰êØÕÊIC¶*©­x‡N¬.w~=ãÞ-`Ô–…vx@eìì­'[÷ÌHÿõ£Æ˜ˆ¹%Â/Ï'þùû¯Ú¼á.Â6¸’[÷ã‰bêøÚ}þþÞÝ-eÇ™]¤út™T]ô‡ˆ—-¨Î$ %îìÀ õï:žÁ²Ùý¿5Øùó *ÛaA}æ¿hi/,Æóej¢qö™·+kT¿'Aä{vvÖÛûì­¦ÅF×g`“ÙÕÅU ö+[5#Ü‚k¤\¼c\¼c,gßÂØùó Ðl{ Süs–vÁbä–o6h’“†ÒiO=o'Çz¹2˜¨!Ø/S‡i’E•⌼6ìðˆ0.öü¼¢@#„ÍBäþó¥šdX ;&¬é–$,þƘ'ê+Û5Mîß/–+TD+åÓTa³\¼][Z#¥Ž›ô‡´ b»:Û˶µˆ–ÐG©*þ½Ö¶ŸcTa›Hê×Ê5ɸ!¾(Â0¢E¼]Y#µæüY©Ï¾&„ÑAªŒÐMTT”ªÌ^ƒ2G™6cW+5*³øÄç-ÑQxy€Ÿ‹cãX¹’øã¿òÖËëÃðáÃ@!©xfI#B™3J;B˜¤ÊÝøûûÀ-…œF)s”iCà5(NݨÑ$_Œõug£1jÄ3pdÑ^ôäÞ»t·öQµÔÀ:™L&ˆêîXO› ÌÞŽ©2B7qqq°¥¡Ál¡Õ[4¦ áÐÅ2åãÙ:î.Œ1±¾†:‡°⣼<Ãà$К-Ø>¨›¹âÞ’4SK"IuŽ`Œv„°H•ºY¾|yppp¶\žR[k†Ù¨@Jmm¶\¼|¹AŒ–‰®Ýçk’¯ dÒÑ}ŽÐ ǦÄ?™˜÷Hx£ ¡•òÏ„jG¢ºœâìu@š¾%‘Dqö:Q]Žáía)ÐÓ ¡‡³sçN Ãv …±¥¥E"}c.U©ŠD±¥¥»…B ÃvîÜÉá´ÿy`ß¹RÍ–U¡~N{Ø~Ü`„y.”òäÙ“G<ãiôCµƒ׎„¶fÍKû€°^ºvíºhÑ"ƒÁb±p¯«3~Ì£îÝ»ÇÆÆÎž=ûÀááá†T¥P[Èã„1aîcbÐeD›á8ÑëÅJ*'UJâ£< ùÒ±Ú² ¾2ÂvH»Rqärãð †}þz/Kíʇèè$ªwޕʧhêç3­…(4„qAß•6_¤<‘U¥IŽêçƒ$Ñn8Nôqü4É37kªør úƒ°*#l„?ÿ+—+Ç®9NôqýZ/@´Žö‹š _%…@èRe„-PR%¹œ[«IŽàˆ6gD†Mú$$ÜÍ‚†¼GB úƒ°*#l}gK5$‚¼ŸÎö7‡G˜è0÷nZsøœ/%ÐD„‰AªŒèð\{Pÿ L¤IN„cÜ´a;`Ó†jn¦GÕÒKwÑÖËÓ‚TѱQ©ÉCÊ4ÉÞ]\{uv± ?#ÔÏY;öô UFtlNeW×44NŽ¥áØäx´7ÂÈL|>@³X¹A¬<žQiY¶ ReDF Qý¥õˆÞÛËßÃÁ‚þ læè˜'±½Ò¯W× Íº—®@ªŒèÀ¹\®‰óàì@OŒC[×!LÂKýý\7U¨ˆ?.aëeB'H••2žôBΓÕP‰q~ìÇ[Ö#Æ…ÅÀ' Ð$3îÕVˆ-è†AªŒè¨ì?W¦Y¦âëÎz¡·eýAØ6ƒ{yvòq¢ŽI€ýZ[“!F©2¢C’S$¸["Ð$§ÄÑðÿoïÎã¢*÷?€Ï0 0ìûŽ ¢ˆ"^S£P3à sÉ$½je¿ÔÜ®–¤7Ókja–f›šÕõšš•Z)𹿂†; %Ûˆ û2Ã03ç÷ÇÑqB–˜å€Ÿ÷Ë?fÎcXüœƒ¦>|xÆŒ‰ÄuùûûoݺuذaMÈT­Ýý§æéÌQAýBMÐ0€‡]¾U±ñÇ[š§ ÆurÐ.{·vÕŽ››æ÷æ®ÙS«ÕüñÒ¥Kkkk‰Hdã¥e>mÿ/¾øâ™gž1ÆÊÁ04é×_eY6@(Lprhmí'4|‡¹­Tž‘ËËËó$’ØØØÃ‡:ôáb,Kßj] ÕÕÇöˆd0ŸÞ]ÃìÓ󪸧»Žßï&¸ѼRÅn=˜«R³r…Š;½|ùòU«V‘w苎^Ñöî}‘Û£¨-¬¼›rûÆ'‰døðáM}€ÿ°WYYùÒK/±,;ÍÞþ‚ŸßD;;cD2ù …íì.øùM³·gYö¥—^ª¬¬|¸Øé%ywï½cˆâûaÒ0¯IZwò.,•¿*Õ¼´ïL!w°Y®PÑüñÞ{ï1!ÑŸôZìè9а‘LD"±·[à˜Þ#’=º<Ï}*** [˜R·~ýz‰DÒÇÊ*ÑÕÕ½D@”èêÚÇÊJ"‘¬_¿¾Á«r…êûÓÎt},Ì%ÈËÖøhŽ¿»Íã=ÜzùÇ3…µu*"Ê*¬9x¡ˆ[(W¨ˆhñâÅJ¥Ò+dš³Ï㶉öYfç!‘H6lØ`ܺÀ8Êи³gÏÑ ß `ã¢}¬E÷¾"Õ2ås… ¥zÛ¡\Í íòzµJ¥JII!"ß I cáö 5ö=‚v© +,,$¢^"ïgkWWµFI¥âÈw5OŸéëáboÒV4ÅÑÖrD?OÍÓ£—Šw•h&ž#"¹BŸŸ/“ɬÄ>B‘“iZeë܃úA{T†Æ]½z•ˆŒt,¹)\u\Õ{Nå+îÏ6ìdg9¢Ÿ—)›мaQ®÷&*Uì©ë%گʪœœ•J%´v5Y“Dbozè{íRxíVAÍ…Œwæíce‰N æWW¯®¨©/*«+,• wiªw\™ˆV¦j´o¸2 ø«Á]l;yˆv7Ý@•µÊÏögIŠe²ûYÛ"¹BÉç@/"zxš˜Q£FµVíº³³s\\\Aá§“ÕÔbaaÑ©S§W_}Õ4b€AœÏ(½UX£y:i°ƒË¡À|ÄÂEº>áª{?¬«Ç­MdÊ”)ÎzÆ]&® žÓ½måŸþ¹sçΚ¥¶¶F¿ì„«‘eÙššš›7o¾óÎ;3gÎÜ¿¿1* ¬®®NKKÛ°aCTTÔùó烂‚ ^oå—È}]­ÍÝ ½)”ê½'|¢‚BýìÌØ"² žñ‹êê´-9·¨¬å¹-e z­^Z=^­]»ö­·Þ"¢¬¬¬‘#G¦§§sËÝÜÜt_ ‚‰aY–a˜ôôônݺ¼â¦<\ãÑ£GãââjjjšyWÛ+ª­­ŽŽ ùöÛo [ŸºPt5»rÌïP=Ɔaˆ¨80ÐXÍj‚{N±,{àÜîßGhÁ¬šÞÝà Gæ€/Jõž“ù¿].n~Îâ§"Ýý…Ù111öî}»þÆT­£ó»Ãˆ¨=N¨ÜºñJ[FFFXXX+þvžS 'Î0 ³sçN__ߤ¤$"ª©©™>}º­­­‹‹Ë’%KÔjµ¦Ø‘#GÂÃÃÅbñºuë £¢¢,--'Nœ¨TêtóQ•êÞ¡š»wïÆÇÇ;88ˆÅâñãÇK¥Òæ—7hd3ÄbñòåË÷ï߯c«:†Á½Ý Jd‰»3×îþó¦¤ÚHµ¸çähÿóËÍí—Ÿ?³¸øW™¬CEMý/)f éH^ “‡ø/~.Äͱ¹ëô¸¹½@wF¯š ¯æ™%˜îíÁ...vrzp-—׃‹OV¬X±zõꘘ"Z´hQvvvjjjuuõ /¼àéé¹`Á®XBBÂÒ¥Kïܹ³xñâ#GŽ,\¸°¨¨hÑ¢E3fÌhæ~Gމˆˆ "–eãâ✜œÒÒÒT*ÕsÏ=7kÖ¬={ö4µüáF6/::Z&“´X¸c°²ÄöõÜ}2?CR•!©êæoߖߡ͛foÏÕ³l™Z®Pì­©Ù[S3ÈÆf«»»£@s§÷ž.г·Žê«¡€BýíÞ™Ö}׉Û'®Hýõ)W¨H¬ß:¹Í\ …•ÈÆÓÖ%­Óh'ï'ˆ:ø¹F¯š ¯f˜%˜î¥ò“O>©]B{Û?!!aúôéD¤P(¾þúëS§N…††ѲeËÖ®]«ùÃ-Zô /(•ÊE‹M:uòäÉÜ㦕s¿ÔjµT*Ý·oßêÕ«wïÞMDçÏŸ?þü… üüüˆhÖ¬Y¯¾úªB¡HMMmt¹H$Ònd‹‰H.—·X²#ÜÛýÐÅ¢ÊZ%5›×¹>8Gš%:/—Ï–JËd/ݽ»ÇËK÷Xþýƃ‹>Ÿ}Ü[le²Æôce)˜:4 *ØùËäÜÒ*EƒW[½­ìÑåybY–U*ëÊk+þ,Éû©$ï'GÏÁ7XXÚ·¹Õ¼fðñªùðÒÆ‡`º—ÊÍW8p ÷@"‘Èd²ððpîixxxFF†¦XTT …›¢ùàèèØ·oß}ûöq·KKK#¢I“&qo¯ªªª¯¯—J¥M-÷ññÑnd‹JKK‰ÈÝÝ]Çòƒæç§f‰ ¶›¢þÖÖ?zyÅœ”ËÕÖŽëºÕ ùYèëjÓS“5Ì¢G'ûw¦…í<~ûôßg©«×õª‚¢Vh=c«ŠSo¥$TÉ<3/ìÉmÄtä«ö >^5^ÚøLz\¯,øûHî41ÝßÞ@S¿¸ŸŸ}ö™‡ÇƒÚ»»»7µ\ßzÿý÷Î;;;?r7Ôþù©a‚l _±·__Qñ]Mñü ?ÍÝñøÌÆÊâ¥g:õ vJ:’W^}oÚvWfìÝ£Â%]K~¶²è\YÁ1gß§ ±Zþ2ìx¥{xñ!˜ôHe???±Xœ––IDW¯^åöÖ?þñ"*..np4º©åz‘Éd«V­Òq—BóðÏO cgóp±x}EÅ庖/#i gCx CËåx#¢³ãªiÝwü&9›^JZs{µ•­ŸWð?óÓ7IstøT6ìxÕöð2e0é‘Ê–––Ó¦M[´hѦM›*++ßyç×^{­ÕíhJhhè¤I“æÏŸ/¢££kkkSRRÆ×Ôò/­ÎÊÊ""™LvíÚµ?üP$½þúë­kÛK¤¶îí‚ñ²9ØÒ’ˆŠTú Oó|ŒŸ›`bk‹WFö qúêHžÜ ³ˆ8ûÉOßTSzMÇòxÈÒk¼j{x™2˜ô›qsݺu³fÍŠŒŒ‰D3gΜ7ož^o×QRRÒŠ+/^œŸŸïìì5vìØf–7oäÈ‘D$:wîŸ`cccŒfw š¾nÀuÚD¤ÔóòÁAn>ípòNdW§`_»Çn•jÖ‰H!/6Ô Û;ݳ¹íáe²`bÚãeææÒx6ÐÍß>áùÒgnöFËW¨Õ]óò\,,núû븞Õÿ˘÷l;ÌÓíÞÉ“'õšE„»2걉é¿Ä²ª”=áÄ{î†.+yqÝú··ý1ê™1¦‡QþFÓ¿ž7Ø:ÿ¬¯§ûû±u´4Þð§,´wje- EŽæn_t°<æ •õ°}Qs7¡M](jôì Žñú÷áÚZ"dÝÑm"«Ì""ûÎ-–ä´ë!Ë\ã•Ù!•uõêC‹}ɨý;_©ÜVU%b˜ì:à÷À”Ê Ž‘£—®WÁ¶_æ¯ø©ü¨8v¹¸ÁÅdüþ}¡®nvqq¥ZýŽ‹‹O³³Ê@óµwîüµC ¹3w[ŒÎ,ãO` |$<üÃÓHýûõ’"b‰ªÕêë Ef}½Ñ¿_uÀeÇ­W%½”•ò¦ª¾ªSï7E6|Zx“Wü„T~$hÿð4jÿþªªŠ{`Å0Bá ‡ííCô9Ï ˆ(ûDD,«RÖÔ–gÈ*o1Œ…Ïy…L5sËŒÏdã?!•;>ÍO£öoÓ߉ »{k÷@`aeeëçÙu²g×x‡.æm• ˜f¼â3¤rÇwìr±«Í¬Qbÿhw½RùÑñ ©ÜñùºÙÄþÃÓÜ­hYÏÎŽøxÕ‘oœžA8Ó ÚßG~ž]¤2_`6€y{{Û¹öVÕW›¬F¥¢B(r;æ“Õ„T0"ÿºÊJ¥Z­’ ,L±{¶¦ì†RQYPP`‚ºÀà°7xð`"º­l8½ŽQqÕEDD˜²R£²¶¶îÑ£˪¤¹LS£4çG"ŠŠŠ2Mu`XHehœH$"¢+ …)+åªóöö6e¥Æ–@Dy—ß­«¹mìºÊ OIsˆÅâ9sæ».0¤24nÀ€D´±¢BeªUD+*4Ut“&M7nœJ)»–YXX$&&.Z´ÈP+C*C“~ýõ×aƱ, &89 ´¶ö3Â}Ÿn+•gäòÄòò<¥’a˜Ã‡:ÔàµðÁ·ß~»yóæ'Nغôª.¹lÀ5ÛÙ;Èjkž~úé7b+¹]C*Cs>þ¸X*5^\ºtèðásçö‰Œ4^]ÀFíW×Ò ²;iééûxqêÔ¸Q£†1FEÆ€T†&]ºrå­+X–  œœZ[û ßan+•gäòÄòò<©ô­+V¯\Ù«—ÁkþÐô++[?¿s<ú‰ÄÞ\¿Z­¨*¾XqçtáÍ/7}ñEEEÅÔ)S ¸~£Âqeh\mmíú>bYvš½ý?¿‰vvƈd"ò 'ÚÙ]ðó›foϲìú>ª­­5FEÀš~åÑåùÞ#’ÝÇ6’‰H 9z èµ8$úS"f÷Þ½ݺeØ*Œ© ûaÿþb©´•U¢«« z‰€(ÑÕµ•U±TúÃþýỮëWv.}–cÜžåì3Ä+dšJ¥Úúå—F­À€Êиôôt"šçèhaª-ˆæ9:jªn…’ÒÒ%Ë–QII ÷ u†ÇÅ ‹Ó~`(_a»Ã}¸ÞÝf0Œ)z–o÷W‰(33S­V·XØPýG_èo  Ç•¡q¥eeDÔK$2e¥\u\Õ­°eÛ¶ðîݹ={ôÐ,禃FØofÈs°·ßµc‡ñªn§¸×Î%Ü4Õ ENV¶¾u²Â’’ww÷æ 7Õ8YÙÙ~ùåêÕ«wïÞ%"^£GŽ 4NÛþö(@*Cã²srˆÈHÇ’›ÂUÇUÝ —._މŽ&¢KW® 4¨ÕÍxgùò5‰‰DôŸ·ß~wíÚˈ}x¡µµ5 -ìŽR(eååž­jiûÃ}¸?–Ü ¡•K]M~ÑÝ»-¦rSý‡eÙÿþï;wïfY¶k—.111D”““s09ùÐáÃ/Lš4yÒ$½Nón𡣿6¤2tUUUÿݹsà€UUUßìØÑ¿_¿Ö­§èîÝ.]ºQqqq×.]Z,?wöì¦^úùÇ›ï˜ 7Æ$°°Ò±dSýgמ=ÿÛµËÛËkÉâÅÁ]»jÊßÌÌ|ïý÷wìÜ)²´œ8a‚îMj𡣿6W†E³•Ýê•üôË//NÊ=˜†‹j%÷ŸÂ;wþ»s§Í»«ViG2…†„¼»j•µõ7;vÜ)*ju¥èo  ÛÊÐq$®^ÝàAS4‡ßŽ8±cçÎÂ;w‚»vMxýuo//"zoÕ*GGG"zwÕ*'GǶ4©™ã|7oþë7´‹}³}»›››L.ß³w'ïYYYu ›ß-4´Á:¿ßµkí¤\¼Øâæè¨ÑþóË¡C*•*näÈF÷úzyzŽ1bÏ÷ßLNæ’µÑO\³°Ñý ´a[:Žˆž=#zöÔ~мCÉÉïø¡“““‹‹ËÍÌÌM[¶pËïŒm"›çæê:9>ž{<9>~r|¼X,–Éd¯¿ùæÎ]»:½<}ú¨áÃ322ÞX²$=#£ÁÛ·lß~ùêU]vx‚Ží?©—/Ñ€þý›z÷ҥ˗u©¢Ñý ´a[]_~óÍÚ5kÂ{ôÈÍË›9gεë×[·žWfÍj°ÄÖÖvúuÍ¿ËÍÍmJ|üŽ;‰hÊýár{RRVVÖ„qã^ž>[Ò'2rɲe_~ýõÚ5k´ßžzéÒç7zyyµ®Í #nŽn?¿¦ Q¾nSy7ú¡ë ý­cC*C[ݬ¯RP `Ù-îîcmm¼šY_?´  @(<âãcóéˆÿïå—Ã{ô "___"’Éå­[ÏíüüKZuS–e=JD£FŒÐ,ì!‹¯ß¸!—˹ól9ófÏÆiuuuDdccÓTkk"’Éd&kú[džT†¶ µ´\æì¼¬´tqIÉkk/‹³CÔ±ì+ÅÅj¢/ÜÝùÉD4äþÕ/B‹6Íha¨3ZËËËË+*ˆh{Riýw©T*–eKJJ¸_œ¨>} R)?É*o]?tSþ-èoü‡TCr·°˜ãà°²¬Œˆ›ð¬ÔökÖÿý_UUÕÅÔTy]ݘѣ†™7{vTïÞ?>Özžçìž“C˜=ßø¸Ï·u_ÿ¶÷«Ç&êw íÂŒmyW×QøÐݶ.-ÏÚ¦-íØ?«Š/¾ÿî»áÝœ ®-ý 88æÃÍ‘J‹Tªmîî¾Baf}ýªÖÞ)@£¾®´0ó+îñ­”%jU+'{hÊ`0[*+•Éæ::ÆÙÚ~âæÆm®¬<ÕÚ ³ˆˆˆÍJYR//°^$ö’UÞ’\[oî&R ãªBñŸ²²H+«7œˆ(ÚÚz–ƒÍ•J+Õjs·Ú«;™ß”žôîö²‹l—~ï1w2¿®¼{ÎÜí0¤2@ZýJq±ˆa¶¸»‹îH^êì&å+•KJKÍÛ9x<æ:ˆn¥,QÕW™¹qÆTH(-ͪ¯ßÅ%Pøà¬~+†ùÜÍÍ’avWWÿT[kÆæA{¤RÖþuv¡@ êÚ#¸7û£_ø|Ç`EíœÔUæm€‘ •¡­¾«©ÙU]ý¼Ýs]JÑC$ZâäDD‹¤Ò»˜”ô‘“ºR^µÜÚ.@³P`aÕõ±µŒ@(ÍÝ_zû°›`$Heh« ¶¶ÅŸ41áÀ\GÇâÀÀ›m»/˲K/æÿÐÜ¥KZzº4çGÓ¤²4÷˪‚D"‘^oD¿j_ýª#A*Cã,…B"º¢P˜2•¯(Dä¢ÏÎÆÚÚZ"²¶¶6R“æÎž­ý”eÙï~øa{RÒw?ü`ÊÑ“ˆž‹;{þüµë×7lܸrùræþ̦[¿ü2;'gάY nôËOq£F:rDš{À5`´“wÃ3꫹wù]"š8a‚¾ïE¿j_ýª#Áqeh\XXm¬¨0ÙŒ\*¢šªõÂèyçVcfÔˆD”——gšµ«^´`ÍÅÔÔƒÉÉܳçÏøùçÇ à¶½øÏÇÇçÅ©S‰èæé™¹—•uƹ×'«–渖`ÀÃûfu„~Õ^úUG‚mehÜØ¸¸äÇS¥Ò„’’µ®®Æþù¦&J()I­«swsûз¦hÑ ‹³‹¿ûö["’Éå;vî¾[h¨îtl’§‡ÇÌW^Y¿qãÛ·Göîm)®ß¸ÑÃÝ}Áܹ&˶3z´J¥Ú–”t'3éNf’•­¯¥µ›f¦ë¶DEg‰X"ŠèÙsÁܹú®ýª=ö«© ‹Åÿš?ÿßo¿ýUUÕ1™,ÁÉi µµ1öfßV*ÏÈå‰ååyJ%Ã0ÿš?_,ëøÞÉññ;vîäˆ,-‰H©Tþûí·Ó32z†‡މɏyó»ï¿OKKK|÷]áß§üܲ}ûå«W»véb¡çT çRRˆÈßߟ{*“É^_²$+++zàÀ‘Ç———ÿrèÐK–¬]³&¬[7] èÕ¤§Ÿzê̹sçSRÖ­_/jjjV¼õ–ÝC3óܸgŸí߯ßg[¶\º|Ù‚-¯.É7àÊí\{3 õ ï9"6¶u[ÉèWí´_u HehRd¯^«V¬ØðñÇyRékR£O8ìîæ¶`îÜÈ^½tË”û£ç”øxnÉÁääôŒŒ'|ÉâŠð,»fíÚÓ¿ÿž|øðÈáõߛzéÒç7zyyé^B¡8uúôg[¶ÑÓC†p wîÚ•••5aܸ—§Oç–ô‰Œ\²lÙ—_½vÍ] èÕ$†aæÏ™3sΜ´ôt"š:yrwýwøóϪ+X–Í“Hªª y[F;;»NmÙÈC¿j¿ýª@*CsúDFnÛ¼yÏÞ½iéé…EE¯ÂÇÇÇÛÓ³{XØsãÇ[Z¶u7æñ“'‰èÙ¸8nPfæÙÑ£Oÿþûñ“'ŒžófÏÖeè|eÖ,îA}}}II‰R¥"¢ýû9’ˆX–=rô(q9½""Äbñõ7är¹••Uó´wêØ$g'§ ãÆmOJ"¢¨>}Z,Ïg Ãt h¹œ¹¡_É •¡–––/LšdîVè*;'‡ˆ´žq³³³”Ôqܹoç*Ã0NŽŽ]»v:xðÑÑÜè\^^^^QADÛ“’HkãL¥R±,[RR"‹›/àëë«o“***~Ü·{¼nÆOÖ¯×÷²Ðú˜ R:™LFD666š%ÜFC­Lֺܿ¿™WëêêˆH ”•—k/ &"¡¥e‹ôm˲|ôQiYÙÒ„„/¶m“H$_~ýõ«3fè»Ð ú˜ R:™L&—Ë5(wá©Xk<5 "bYvåÛo7zm+W{3ôµïÀ />7nÜ?îàà°ä­·~Ü¿¿¿~½""Ú¾rh ú˜ ®W†…›,Wë¢Ï¬œ" 2Fub±8(0eÙ‹©©­+ —¿nÝÚöå—!ÁÁS§L!¢^={Ž3†ˆ>ذ¡¦¶¶í뇦 _É •¡CCDûú‰eY"bY–;TÖêy$Z4vÌ"Ú¼ukþßO…SªT*•J—:’Éåï½ÿ¾¥¥å›o¼!¼‰ÚÔ)S;u*–J?ß¼¹4ý L{°¡C‰6ì·ãÇ8QQQܵëôôë7n„uë;l˜‘j:dHZFÆ¡ääYsçöëÛ×ÇÇG­RÝ.(¸~ãÆ¦?vssk±€Ž}ºiS~AÁ zkO+‰ÞX¸pþÂ…GëÿØcÑM1¹ô#ýÊ8%4© ŠP(\ýŸÿ|½cÇÉS§.]¹âêê:~ìØÉññB£ÍæÍ0̼ٳ£z÷þéàÁKW®ü~ö¬¯ÏSƒÛÚÙéR@ÇŽ?úÛoO 2ä¡I’;Mýç?·'%}üé§=œõ¼e!èý ýÊdn‡ tHÜUÍŸí &n.ÆÖ}ýѯ )méWÀÁqe¾@*ðR€/Ê|Tà ¤2_ •ø© ÀHe¾@*ðfÜìȸ™šÒ–7š‚Xi l+ðÅÿé #„)€IEND®B`‚frr-7.2.1/doc/figures/fig-normal-processing.txt0000644000000000000000000000102513610377563016371 00000000000000 _______________________________ / _________ _________ \ From Peer A --->|(A)-|Best | | |-[A]|--->To Peer A From Peer B --->|(B)-|Path |-->|Local-RIB|-[B]|--->To Peer B From Peer C --->|(C)-|Selection| | |-[C]|--->To Peer C From Peer D --->|(D)-|_________| |_________|-[D]|--->To Peer D \_______________________________/ Key: (X) - 'In' Filter applied to Peer X's announcements [X] - 'Out' Filter applied to announcements to Peer X frr-7.2.1/doc/figures/fig-rs-processing.dia0000644000000000000000000045232613610377563015461 00000000000000 #A4# #Best Path Selection# #Main Loc-RIB# #From Peer A# #From RS-Client B# #D# #C# #B# #A# #X# #“Out†Filter for Peer X# ## #“In†Filter for Peer X# #X# #From RS-Client D# #From RS-Client C# #Best Path Selection# #Loc-RIB For C# #Best Path Selection# #Loc-RIB For D# #Best Path Selection# #Loc-RIB For B# #D# #C# #B# #B# #B# #C# #C# #C# #D# #B# #D# #D# #D# #B# #C# ## #Export Policy of RS-Client X# #X# ## #Import Policy of RS-Client X# #X# #To RS-Client B# #B# #C# #To RS-Client C# #A# #To Peer A# #D# #To RS-Client D# frr-7.2.1/doc/figures/fig-rs-processing.png0000644000000000000000000023426013610377563015503 00000000000000‰PNG  IHDR]…tJsBITÛáOà pHYsÐй‹çŸ IDATxœìÝy\Tåþðï9³Á0þ Ë "(.l˜šW#S£Ô_æRJ†{YŠIiÛ ­0o–•׺®å–¦¦eš[.e*ŠÄ"« ÃḬ̂Ìz~œ`fàû~Ý×}Îò<ÏÊÇç< AQ d~äryBBµk×òòò²³³ ^¾¯¯¯··wDDD\\‡Ã1xù!„P¯C`.DfèìÙ³111"‘Èu …Â;vDFFš .„BÈœa.Dfçüùó‘‘‘Ey2™q¶¶L¦Ák)R©®56&ÔÔªTAœ={vÒ¤I¯!„êE0"ó"‘HE"Ñ>“ƒiäê4k++÷J¥B¡0--ÍÚÚÚÈ"„BæËØ¿vêœ-[¶ˆD¢`'Áø¡H€‡`G$mÙ²Åø"„Bæ s!2/ýõ¬´±a˜ªFÀJmÕ!„P¿…¹™…B#ØlSVJWWRRbÊJB!sƒã ‘y!ʽ¼L\¯SAà„Býö"„B!Ì…!„BˆÖÝ\H´2mÚ4ƒ´LŸI’´³³‹ŠŠzðà‘ªËÍÍ%bæÌ™F*!„BÈL`¹àS§Ny{{k¿´²²ê~™úÔHQT]]]VVV||üÒ¥KOžøàƒ7Ž?bccóóó“““Ï;wìØ±­[·jo‹‹‹[·n݆ Þzë­èèèÕ«W'$$9räâÅ‹ú4àܹsÇŠ¢¢¢¢ª««ÓÓÓ322D"ѲeËÚ9ߺ‘-ìÛ·oĈC† ™={ö… Z|:Ô>‚iÉó[òòD“¥ÃC2()ꧺ:c×…Bõ1Ý]¿^m®9mAìܹsáÂ… P(lmm¯^½ûöíÛ´iSjj*}Û¾}ûæÍ›§R©X,VóãÝ»w·~uKÄ•+W|}}5MEEʼn'6nÜxøðᨨ¨ëׯ‡‡‡'&&†††ÀîÝ»—,Y"“É’““užg³ÙÍÙ‚J¥òððxã7Þzë­”””ààà/¾øbåÊ•Ýùvõ7”ªAük¤F^¥=Ãq kÿÍrwÖ/ÌV*#Š‹™¡¢¨Pç´««þÏâú…!„Æfdd´5¾0""‚>‰D ô—™™™ÚÛè°Èd2[·eܸqôMhhè‰'žzê)HOO€Ù³gÓK¥R¥RYQQÑÖy77·æláâÅ‹b±xüøñ¥¥¥`À€Ä\Ø)t—¡$u³öŒ¼òš¢¨Öúk+‰²Ùlضm›³³³ö¤““S[çÛ¯…ž‰Ü<5Þ¿?//¯ùäkÔ!«A³eY»šw‚qÒ¡à°LÆ#Ékëkç×Õ½mkkÂB¡þÀDëZ{xxp¹\ºßRSSýüü ^KXX”——l†Åbµu¾¢êëë;¶sçNê¡üü|8xð Á›Ý·iG¶fØq‡WJÔêVV–1—Ç€#2Î=A!„ôg¢þB‹µ`Á‚ØØØíÛ·K$’øøø+V¼??¿Ù³g¿þúë$I>þøãõõõ7oÞœ1cF[çÛYjñäÉ“J¥²ùrÖ^^^cÇŽ=pàÀºuëºÐÙùàÈÐ.~ª¾ÎP}‡ôKä9<<ÅåÚ‘¤H¥ºÞØaa¡!ÒŒox¾ó ¦e—›Bõ^¦ÛoóæÍB¡0((èÉ'Ÿ|þùç4PoÏž=‹-Z»ví€"""¾ÿþ{z&A[çÛ²ÿþ¨¨¨ Y¿øâ‹éééôtdXھî=.Ñh~©¯Ìb…p8À&ˆ™<<œž¬?iÚVñ©‰²Œo)eçD!„ú€îÎGFzÂþB}pœÂ'ì…ÎÏGÞ'•®ª¬|ßÎîÕ‡Qþ®Bñ¯¬H2C(´Ô£—ž\|xý%É¶á Ž¶ò™K°xj B!Ô{a.4Ì…íÓ¾GîÚ:5SKJnÊå:/mwrš©ÇÞŒ-r! Ó!B¡~s¡)9:’Ò(écî€(ÛQ÷l{ E–µ»ùR5-´YØ…\˜¯TŽ*.摤€Áh~¾V£)W«'XZvqé°:–ü®QÔ¶¸Ô”}çL£ïýBõ Í;Aý¥jeíÒyÉPëÔª«€wíìòùÍÏÿ­P<ñàÁ円RµºEdl‹Ë”³²ì}uÙ{5 ‰ö¤FQ+IûBvo/oðL‡!„ú0ÓÍ;AýS]î¡‹=Žð‰=Oìé~(Ô–ÉX1½ÕËâ¡lv ›­8ª÷ì‚ÅãYê<åèk$Ûú‘Š5’´/ħ"eßR*Üd!„P„¹QëÎB&BÚŸE*ÕS––v¤ŽæÙ<üPWשѤ>é0ó˜Bõ1˜ ‘5ï,4x"¤ÑËÎâéž2ÓÊŠI EšBÑÙ’›¥ÃWu¤Ã»Ÿc:D!ÔÇà¼3ÒÇæPªñ¯‘yU§Ævm>r÷ÑóNÚúã QÊ겿¯Ëþ®ù¸CɶåùE[ùÌÅq‡!„z;ì/DÆR—{ˆe=È}„¦G²xü!ËšúYÌniê;üõ)Yæì;D!Ô«a¡écý…*IÓÚ§³O™gas¥¬.û»º{ßi”Ò—HŽ]Óz‡L®1‰Bö"céB(ìH?d¹óÔsü¡+ZöÊ«%w·ˆ”eî Tõ=ÕB„B¨k0"Ô$‹¯G:܉é!„P/‚¹¡®û'Y®+~†é!„P/‚¹¡î"Y|þИBõv˜ ‘y™0a©T¦¬”®nøðáÝ)äÑtøÈzŠÿ¤Ã¬]˜B™-̅ȼ°Ùl¸Óùe¨»ƒ®ÎÕÕµûE=L‡çùC–éH‡©ÿy˜º_B!dX˜ ‘y €­µµjSÕ¨ØZ[«­Ú HŸ?ôUç)çÚN‡Ob:D!dnpýB3ÒÇÖ/ì‰D(‰ðù›Œý ÀÚÊʽR©P(LKK³¶¶îø™ÎV¡ÔeW—ý½F)kq‰äØñü^±4›`Z¼^„B¨³°¿™kkë]»v±W* +*:,“i¬a‘JuX& +*Ú+•±k×.c„B ÙÖû—êê;Ü,þ5R–µûBõ8ì/4#Ø_¨uöìÙ˜˜‘Hd‚º„BáŽ;"##MP—F!©ËÞ[—½OWß¡=Ïo!ö"„êA˜ ÍæÂæäryBBµk×òòò²³³ ^¾¯¯¯··wDDD\\‡Ã1xùí Ó¡,{…é!„9Á\hF0ö+Emݽ½²œý˜B™ _ˆPÏ Ù6üÀ•.SÎò–-ÇVý3îPÝØS-D!Ôß`.D¨'é•ïíÁtˆBÈ0"Ôó´é°¸e:l¬”ÜùÓ!B!À\ˆ¹ Ù6Ö¯c:D!ÔS0"d^I‡L«æ—š¥Ã½˜BæB„ÌQS:œÚV:Ü„é!„Áa.DÈ|‘lÛŽÒáSu÷¾ÃtˆBÈ 0"dîþI‡þ‹Z¥ÃŠÚ; ˜BæB„z’mk=ì =Ò¡¼§ZˆB¨·Ã\ˆPo¢G:Œ¬ËþÓ!B¡.À\ˆPïÓ”§üÆóÑ‘o‚é!„P`.D¨·"9vÖÃV=L‡Üæ—ètXöëS˜Bés!B½ÛÃtx¶u:T7–?L‡û0"„êæB„ú‚féð]éðcL‡!„:„¹¡¾ƒäØY[é!„P×`.D¨¯é8žžŒé!„Pk˜ ê›þI‡~ [¦Ã†2L‡!„ZÃ\ˆÌ”\.ÿðÃ'OžîÙöô ³gÏÆÄĈD"Ô% wìØi‚ºz–F^-ËÚU—{R5´¸Ä°tæùÇpþÁàôHÛB™Ì…fs!íüùó‘‘‘Ey2™q¶¶L¦Ák)R©®56&ÔÔªTAœ={vÒ¤I¯Å iäÕ²¬u¹‡0"„js¡Á\‰$00P$-àó798{ ƒ`meå^©T(¦¥¥Y[[¹Bs¡‘Wɲva:D!ÔŽ/DæeË–-"‘(˜ÃI0~( ÁÁ!˜Ã‰D[¶l1~…æ‚äØ[_ã2å,ÏïeãS>*;=¹.çŽ;D¡~Åð¯çꎿþú VÚØ0LU#`¥MtY]u¿B§CžßÂÖ}‡ê†²Ú”²ÌÿñüqÎ4Hß!AÝ/õ1øÎ !³‚ý…ȼ( Áf›²Rº:ºê~¨£¾ÃM}‡š~úýA¡þÇš_»”ʽ¼L\¯SA`×…vÜaÎAJÝØâÃ҅翈ë=“ õMírñ_—pí—ôÜ~õ¾µcÕªU€è23Ø_ˆúÇ?}‡ƒ£ †EóKêqmʆ²_'×åÔ³ïP^v½.{ŸqZŠBÈðú~.,))‰ŽŽîéV Ô›Ö#Þ4H:”¤nVVÿm´–"„2$Zo1mÚ4£ÖÚ¼.‹rùòåæ7äååýßÿýŸ££#I’ÖÖÖÁÁÁÛ·oo§Àêêê7ß|ÓÇLJÉdZXXøùùÍ›7ÞÇ¢¶¶vïÞ½™™iðaïÚOÁf³‡þÓO?¶|„z^é0÷PûéÒ(«þZ­QJÜX„BÐ4ùÔ©SÞÞÞÚ³VVVÆ®˜®Q£ÑˆÅ⯿þzÆŒ%%%l64ÍäÉ“CBBNœ8acc#‹oܸQYYÙVQUUUááávvv ¾¾¾µµµIII§OŸV©TÎ?“(œœ>ýôÓ®µö½÷Þ[±b…‹‹‹ÎâååUSS³k×®yóæ•——s¹ÜÖ·õC×óÿzl`xÇ÷!óF§CžßBYÖκÜš;T7ˆk“ãÎYžÑÖ¸Cu]QMâ;ö_˜ªÉ!„ºˆ (Š ˆŒŒ ÓÕúh‰ÄÆÆ&;;ÛÇÇòóó½½½e2™žñôµ×^KLL¼|ùr󨕙™ÐÍ¡Ím}‹šŸ¯¯¯·²²ºw¯o×jécóNÞÚÿWþŸ/‡ÇyëÿÎ;1gšÆÊÖéÆà Z§CÉÝ-²Ìô±MÐ:žï‹€óNÐC8ï!3ÔÁøB‚ <èîî¾gϨ««‹ŽŽ¶²²²··ûí·5ö¶sçÎr¹ÜÍ›7—””„„„°X¬Y³f©TªQUUE„ƒƒý¥ƒÁ¸pá‚>€¢¨ƒ®_¿^g(l®ù{ä²²²9sæX[[s¹Ü™3gVTTh?HrròâÅ‹y<ž““ÓáÇ€N«íS¬¨¨`2™:ûû§é#fTæ¯<¼|å‘å)EÉÆ«È©  ùÿ¼ '=x°µ¶V‰¿o ´p°±ÖeÊo¼Á Z¾Y®/­MŽ/;ýt]î:ß,Kîl1Ȳõy„Bæ£)–———6ÓüŽ>ø`ãÆãÇ€ØØØüüüäääsçÎ;vlëÖ­ÚÛâââÖ­[·aÆ·Þz+::zõêÕ G޹xñb[uSÕÐÐpóæÍùóçÏ™3ÇÎÎŽ>okk»nݺçž{îÙgŸýᇤÒöF&•””TVV†‡wâ}%EQQQQÕÕÕééé"‘hÙ²eÚ«ÑÑÑ2™ìèÑ£sæÌY³f ܼy®]»öå—_ê,P­VgffÆÄĬY³¦ÿl¤Ö! –åœÐ E”l‚t¸€ÏŸÏçÏæñ†±Ùi E|uõ{ÕÕÆ«®?#-éth5x¾®tø¡ÎtHiÛßpçsûþ\7„꽚Þ#·8«íØ'bçÎ .…Bakk{õêÕØ·oߦM›RSSéÛöíÛ7oÞ<•JÅb±šïÞ½»u7[‹Ÿ}öÙC‡YX<ò æäÉ“[¶l¹|ù²¥¥eLL̆ ø|~ë••åïï¯R© Ýdhß#k®_¿ž˜˜ »wï^²d‰L&c³ÙA¼ùæ› A¤¦¦Ž1‚þV´óY{úØ{dhT6ÌÚ9£ºþŸ|$ nÿÍrÞ#Ó¯€›?r±¡á±Ø–$³==;U¾Òê,Mc…4kg}î­wÌcpLþ ¹øÏæ'½!‘yÄš°È|á{d„ÌPÓ¿Ý322¨fšßAˆD¢†††ÀÀ@úËÀÀÀÌÌLímtXd2™-ŽÛråÊ•’’’’’’ˆˆˆ´´´ÖÙ4**ê÷ßðàA||ü?üðì³ÏÒ Û±c‡vpNNÇ€êÎô ¥§§ÀìÙ³ýýýýýýßyç¥R©}•¼páBº1lý¶ÜÈÈÈP«Õååå«W¯~úé§÷ï߯Kú£R©ZôYº¹¹ÁÃ`Êår1™L’$gÍšõþûï·†¨Ñh4mt………@yyùÈfX,VûíÔçeÇÀÄbq‡wö­» µŒ”3Š×**H€u¶¶,uˆN‡ÎS~k?’„:Üþ‹ÄÝ–BÈì´÷ª·‹µ`Á‚ØØØíÛ·K$’øøø+V¤}ôÑ?þ÷ý÷ß@NNÎÌ™3çÌ™.$ÉçŸ>fÌ:¶?zôè°°°ØØØÀÀÀÆÆÆÄÄÄ={ö\»vÍVW2ðóó›={ö믿N’äã?^__óæÍ3f´³,Ž££cRR’P(¤_[7———GQ”B¡())Ù½{·»»»§®1mc?{¬ÃïÃ~%óaìoé¿~õÇï>Ò¤ˆ’SDË;wØ¡ðâb @FS¥VäpŽ ŽXÕÇú“q!žaAÂ`/‡xô~‚aáÄX¢ªÍ–—]oëSf{åZÕ$S6 !„P‡:‘ `óæÍË–- b³ÙK—.]¹r¥Aáææ¶nݺõë×/]ºt̘1`òäÉ'OžüôÓO%‰½½ý´iÓŽ?ÞÖãNNN7n܈߸q£H$b±X¯¼òJë §µgÏž>ø`íÚµÅÅÅvvv!!!Ó§Oo§…ëׯ‰‰ihhX´hQ‹KS§N¥ØlöСC9ÒþÀJ¤“6v¹úÝ1­F­¾­P„p8œNu¸’sùJÎe°çÚ C‚=C‚…!¶Â.·ªRT$W__£nè ×ÜÃ2ßÇ*=§nˆiZ…BHÎ3 ýú ¥Úþ«u̯ªúÝboAÂà/gýº:Y Óh²”ÊýRé~™,€Í>æââØÆDu…<þŸÑ­/9ó]‚…ÁÁÂÐ aˆÀZ ÃúJ–¹K’öPj}îÖPŒ åQÕJGc7 ™'œŒž-d´ï‘¿„ÿv¹Àš$Ã8œ0Ç•ÉÜ\SóNUÕönÌ(¢•IÅgÒOŸI? î¶îAÂ`ah°0ØÁ Í?4òêê›oËK¯êÿ=Ðð\ùô¢’êÓ§Oµ³Ýe¯æèèèîîþôÓOãº÷!3‡ý…f¤—®_ؼ+ôêjCÊÞÚ¿íŠî%Á¡ÕІY¿ÊÕê!"Ÿ$óô[Â.äЭI…·R‹o×):žT>ÀÞ+XìälcÙ¯ç¸èùîX§äB^Ô›‰ýá/"‚ ¢¢¢žxâ £Ö"‹Í'd·ˆ±¿!3„ý…ȸ• oíÓy©ûsMÚaM’ÐØÉ_9/„Ìy!dŽZ£Î*ËL%%Þº[œÚ¨j¹0í~UÁýª‚ãwŽ’éí8(H" áÄã´9°µ¯"9vãv4;¡óÛN €¸¸8í)Qaá¡C‡˜,æÀ¯ÚÅqt6rK{†¼¢¬êöÍì]_žã º¾:¥(9¹ðVŠ(©°ºPçýʆ›7nÜ+o¤ûHz“oÇA˜Bõ Ì…ÈX>'t^O·ÂÔì¸vÿ<ñ_ƒ'@¹¬¶Â¦MV„!ö\ü+¡^¯±±qݺu_|ñ…F£ŽÁẪpŠÒTTˆ+*Ê333:ôÕW_ÍŸ?ßP…£> s!B=†ÂÛq·ã ÿ š¥¡4Ùe÷RDII¢[©Åwêõ:)ªÕˆNÞý‰ÂËa`08D:RlmamÚ¶#„ ã7Þøæ›o‚á9b­Û ¾—+ 4’òÄ¢´­ÒŠä 0ŒyóúÝÈo¤?̅ȼL˜0á÷ß/R©<˜¦ûá,R©`øðá&«±5’ ý\üý\üg‡ÎSiTYâ z“•´wå*yëû) ò+óò+óŽÝþ‘$ÈAN>ô&+#ÛͰ°Ìüú“â_ºNœúàì‰1;bòø@Q0hÁòŠÄ?1"(**–+‡©f°ø¦©ÑÊn(A0x<žiªC½QßÿÝ€zkkë]»vEFFî•Johˆ³µ°°0Æ;å"•êZccBMM¡JEÄ®]»¬­{ÓÔ +¶U„÷˜ï1 i”Ü%'‰n¥ˆ’ *óunÄ'WÉ“ o%ÞúߟÀes‡»† C|v“•3é§'yÚ€vÙø.h”ŠºÂüÜï¶eïþrð¢U5i)pï[ ¾è¾º¡žecgí;$åý×ãŸrý×&ÏD¿ž‘HÔ(Íç;…˜¬F–…#E©þùg“ÕˆzÌ…ÈìLš4éÌ™3111…"ÑŠŠ cW' wìØ1iÒ$cWd<ÖÖã|ŸçûTÕW¥ˆ’’ “’EIE5"÷×+ê¯çÿu=ÿ/à[ðG¸…x† C:zw“•‹Yç3Åé+ŸXe{ú‘,6ŸÏ˯¥¼óÚàE«&ÓÂÙÕëùôUÇ‚ É‘ÿþ¼êNbé…_KÎÿ¶ewÏ6õK¸³2#˜ ‘9ŠŒŒÌÎÎNHH¸víZ^^^vv¶Á«ðõõõööŽˆˆˆ‹‹ëK›Øsí'ú=9ÑïI(“Š“EÉÉ¢[)¢¤RI©Îû¥Ò?r¯ü‘{l-mƒ…!Áž¡AÂO;Ï®5`¨ëÐ×¾-•”~0åC –e—?ˆÁP”¬ ‡em!á÷íSÕÉøƒü@ÝØ ‘ËÕŠF‡àǬ}ü¯-š©ª“‘Lhr’Ýw~*tºüÂDKWQ[öÑ”KÒÖW&_×9禱¼ôÆ«óp:B}æBd¦8Î{ï½×Ó­èÝœù.“‡mXTaÕýdQRraRJQrmñ•ËÊÏfœ9›q\mÜè€$ qâ9µ_Q€`(IJ“)ÎXr0æÓéŸy9 4üçéH[=[Ž¡cCÇ4?úéÿ¹ƒ ã6¯aR[[{üøñ—^zÉÒÒ(=¯ÎJ?MçÂÒßÏ8…?Qrá}éÁÙ“¥—Î(ª+m‡Žô_ñ–¼²Œî/¤;‡½ýqîwÿ•WU¸?õÜÀ9¯£m½ Á´¬¼²ˆm?Üjð|’mÛÓÍéûDg,\ÇÌ.®ÕºiÓ&:ÿåååM:5##ƒ>ïèè¨!§Nòöö¦(ª®®.+++>>~éÒ¥'OžìZ“ÚG§ØS§NÕÖÖÚØØ£ -Ì…õ/ì½Ø{M1SCiò*rSDÉI…‰·‹o×Ée:)©}pªöÁ©´ŸÀÓÎ3ˆ~×ìlǵk}3ÃÚyÞ¯*€RIɲC‹7F}, 5ægê³ FFFÆ–-[^yåÃo-˜ðtê†7}¢W ñ•³þ¯¾­Í…ŽacÜž|F)“^‹™î=oq‹ÅWÏmøª¾¨à·±˜ €°ò_õÇ2Yö>+Ÿ9¼ÁÑ$GÇŸd( Gñ/ÿ²¼€ç;Ÿ`uzzµ››[óŽ=ÿ.´ÁÛÛ[û`hh¨@ ˆŠŠêB9¢(jÿþý“&M:þüñãÇ£££Q‹VÏ Gõ’ }œ|Ÿ~á“ç6ÿºüìÿæî^6öÕQ^£ÛXX]x"õøû¿¬vû”ßÍûâ÷Ï®æ\i±³3ý*™&“K×[u&ýW#~Œ¾‹$I(//ß²eËÝ»m¾äí2kߎ£sÙ_—ªS“¬x³íþÙq›aa)¾r®øÌqШZ®¦î¿l-“kŶuÐ(u,´Þ?Y¸Žc;Œ Tu²Ìâ_#%©›5òªžnTŸÅv eÚøIÿþZüëS²Ì”ªÁ€…×ÕÕEGG[YYÙÛÛ¿ýöÛô¾Õ²··W«›Ö”-++›3g޵µ5—Ë9sfÅà ”m'âàÁƒîîî{öìi]rRRÒ½{÷¾øâ OOÏà¶ s!B€$HAÀܰÿ3ã‹Ó+Îm›ýmLÄâ`a›ÉÖy?T^Eî)‡×\;í¿OÅìþúÊ—×ó¯Õ+ꇺ6¿S©V~t&~×_ÿÃi—EçBËå»wïþí·ß(JÇ"D]G‚'&—þ~ºäÂ/®§iOSMò;¯j” ·'ŸÑý½tþ}è«ô¥ª—eíŸzRrg“¦Ñè‹*ôOü€% QÔHî~&>ýT]ö÷”ZÇ–¡]›ŸŸŸœœ|îܹcÇŽmݺUŸ§Î;Go¦JQTTTTuuuzzzFF†H$Z¶lY;çi|ðÁÆÇߺä}ûö1bÈ!³gϾpáBi©îI„†‚ï‘B-1Iæ0·áÃ܆/xl¡B¥H+¹Ko²’Qš®Ò¨ZÜLh ©Égf‰3ÝÚÏ$™ì½ZÜCµû¯ƒgÊ>œg¢ÏÐ'hs!PuæÌ™âââyóæp½Ë¸'óîà8¹Ø ‘W–Ñ'•µÕõE÷GS××€F.'ûМ}ãá¸D°ƒ)ô—”ºQvoo]î!®÷ó<¿W–Ω¥,÷P~Ò¿ ’õ“mÜ¡fæŒ#òTV¥€¦±²öö'²¬Ý¼€%Ü3º3H¡P|÷ÝwW¯^õóó€wß}wÓ¦Mo¼ñFë;ËËËmmm5MEEʼn'6nÜxøða¸qãÆ7=<<`Ù²eK–,Q(ÉÉÉ:ÏÓ»¿ÆÅÅé|A¬R©:D7`öìÙ›6m:|øðÊ•+»ü;„¹!Ô6“MO:€eCjñz“•{â,z“+3â¼{C£Ø½¾Ì­¾«Ê­ÈÑY”s¨#Ç–­% pÚ¦^Œ–›AÞ½{—nèäÔÁ =±mí/Yöµ'šeP¶­½Sø‰«8„D8GL¸»iýˆw7¤º>?ôÕÊË ¸¤Ôòºì}õyG¸gðüb\A7«(/8”FU)úÕe‰6Ð3Oü€¥U¾ªýRÝ ®MþP–µ“?d9wÀ3@te/U‘HÔÐÐØôÒ#00033SçãÆ£lllBCCOœ8ñÔSO@zz:Ìž=›Éd€T*U*•m§G:FDDè¬åâÅ‹b±xüøñ¥¥¥`À€Ä\ˆ2 –,ËÑ^öz drÙ¢”$QRŠ()MZòÄϾۀÄVQæV/v¯»Õ7Xµì\´ñ±¾¤97†Ï….N$ìÔjµZ­Öh4ôÿÓZ«Õj…BÑúq±X¼eË–—^z)  ;ÍÐNÙ<ñ}`á$h:ICÞxWçýÿÜóè1¢qœc;…*Êoµ8O©åu9ëóŽrNçùÇ0¸]\ͤAš'«L%E©+ Nôó\háöËÖOY“Õü¤º®¸&q½,óü!+,…“¡“ ì7金¢ÚZ(*##C焺ÿoÛ¶mÎÎÿô;99µu¾ýöÐ3‘›§Æû÷ïçååy{{wøYºs!B¨+xÞ˜AcÇ  µÿWsÑõ€x{"X×°­kØ>é¶ µQˆÝêÅîõenuõ¼¦Œ(ÉE͹1ä8;°o§|SJ%ù‡v^dŠ=²×¬Y£ç,–îŽÕ†††;vL:u„ Æ[ÝЀV?ïh} š%RPëç9!IÝ E( €zøÿ (úàÑK”ö¸éNèJ M÷´UBÓ^¢ÞT‚¦Í¹8”FQ—ûC}þ1ËQü€–³¼õQ‘ÿ‡­*¼û™¬òN£´À‚ïÕÙB ”Aô1ÑêLûWµgë{[«3mUÔñm–žÏ´È…4•´ úÆ›²ÌoùC_³pÿ—þƒa=<<¸\nzzzPP¤¦¦Ò/”õååå‘‘‘úœoG}}ý±cÇvîܹpáBúLAAÁÀ<¸~ýúNµJ˜ BÝeci;}ÍòßËwfý£ùy~-›_Ëöɰ™µBì^/v«/s¯¯ã5^Ö\Eè~ob—_˜È±w’¤”J>7ÇcÊÌÖ·QjucY©¥«»º±¾äü/¦É…úkg"¤F£ùùçŸ‹ŠŠfÏžM÷C˜¹ÕÿçÒüŒÿ³Ž Ëêw{Re}þцû'd•#:÷ ¥®¸’Á´rñ')¿YSr¥âþIÀN¿Rd0J£hú§šA§2™emvÕµ•,»!ü¡¯Y¸ŽÓ狵`Á‚ØØØíÛ·K$’øøø+VtªR??¿Ù³g¿þúë$I>þøãõõõ7oÞœ1cF[ç­¬Ú|yròäI¥R9sæ?wyyy;öÀëÖ­3Ò¿ q>2BÈ0ÆÅ/`ûµÙÈ“°eØF\p{î;Ÿg÷ùŒºè,μ;$ˆK^üð‹Ç¾>þíCV½—÷ývuC}ë{äUå7ߘo¼6tS‡ d¤¤¤üç?ÿ1McºÃ°³¨ûŠÒP”ºSHÄ× bÏ©$ÃÂià ¨(8ÑÔß©7’²t1’²:½êeçÉËnèsÿæÍ›…BaPPГO>ùüóÏwa0ßž={-Z´víÚDDD|ÿý÷ôJmoËþýû£¢¢Z,dýâ‹/¦§§§¦¦v¶UzÂþB„a$—$‰Hz²HÈ­ëà/ž„œؼg[$)J7~Ãx})FÝØÀ°ä¶ØÆ#wï6HZ»Øû¥¥Puû¦YmãAQI’í¤C¡PcÊ&uEAoxãmi)œÌ²œŸ"èÄrtå?mÝ&0Ù6òú’Š$k§0ý !û[*|ˆ`Z­f¢øûû·Nf\.wïÞ½{÷¶·fûyŽÃá|üñÇü±žçÛ*íçŸn}rñâÅ‹we‚ž0"„ ¯"÷ãýï:±+\„¹|¢£þ!Ê’©|Úëâ§{ŒÛ,ŠRJk ~üŽë&dÛÚC«m<-X^‘øgȦoËKÁ,·ñh'¾ôÒK½â%2üçHùäÉOCÓ[K‚â矡(jÓ¦O  ˆ‡ÿOÒÂúàÑK„ö¸éNÐQÂÃK­K ÞTK»%ЀG/µ*¡é]BMÒ‡ÅçtÒÒ#’?d9Ózˆôÿª•Òêâs–ÖƒxÀ$ÙŽž)ÍÞWQðS§r¡JM _HW©”@©Ò¥¦( }Lµ:ÓþÕfgšÚ¿íá±>÷ë|PG±”Z®’æµÛ#MX¸ç,aÛ×ÿ»ÔŸa.Du‘Z®,¿[Pš’#JLÏ¿‘Y§ïKÊÑR1Õ‹â7ÐÜxu0,,mü‡¾O÷VÑÛx4”•@ÛxL¦±·ñ`0 ƒ$I’$[h¿¤ÏˆD"•ªå´n?~|TTT‹¹“ælËåžc‚šŸÙvbl;ñrÏ4È8TÒ‚Æ:çhîùC_eÙøv­äJÑoµÜiàtí G¯¥ÙûªDg¼‚ß%ú¥¡€ ™ÚЫûkS6¨$¹º¯¤¥û$^À–mW¶¹ë·0"„:AVR%NÉ'甦äT¤j”M‘…­÷`eµ­r¢XF4£¿ÚoáôÈZqô6OÏp{ò™û?~×úÓlã±ys'Ö\·n]‹3$INŸ>ýñÇ7h£aÈ2¾i5à°p›Àº¢›é¤¢à8ÞÙ\x瑟µª¾ºø¼ƒç´6žëËÔõêóŽê¸@0,=§ðý=ì—E€¹!ÔRU‘^XšœCgÁºÒê.EõsUJääëj½ÓŠ…a¶Ï2†=‚gþüùC† Ñ¿„Ë/LìÎ*ƒå¥7^gáäBQ”F.gòxƒæ/w~Œ.yôWû@{¥R1,-=¦Ìt‹|¶Ë5ö^*Ya}á©æg,\Çñ‡¾Æ²ëÄ/e…ÒŠdÓŠõè¦)j¥DÙXY^p¢æBiúvJóÈŸɲð Ï“çÙS­êí0"„Zª/¯§ä–&çˆSrËÓ Ôr½^ª²¸§áA>õåµ™?^myÕÊbkî©Ô:‘¾+wAëmöö t?"êƒ1f̘3fjê±RR[_tß!h48?Vðã^¥¤FV3òƒ-ô”m‚ÁÌÿaWá±ý0t͇<¯AÐ<ö?6xñêÖÅŽÿá‚F©¨+ÌÏýn[öî/Ím磣4ü€% +£¼â§—-t ûռÀ©…w6Õ×Þ««Î´²ëÖ–Ù½ˆ,{Èr+ßI¿§ÛÒ×`.Dfçüùó“'O¦(ʓɌ³µ°°ð`þµH¥ºÖؘPSS(Mž<ùìÙ³“&M2x-=N^['NÉ-Mɧä–ÝÉSÖ5êóƒÍt:À%ÈGìã4ÈÊ¥ÿp3K[ȸ üfà´Ùn™0aBxx¸ *j¾²îÀ|¡é¥$½²#ýb½üú•¬íŸ6–•rÝu ê'Ylþ ?Ÿ—_Kyçµ~— ÒH¡údШOÚºÊâ8Œz>ÍHU›-Ûàw›žD††¹™‰D²páBŠ¢ðù›Œ÷çރɜÅãý·¶²r¯Tºpá´´4kë–oE{JCÕä>(¥—’IΩÉ/Õ³SÐÊÙÖ%ÈÇ%ØÇ%hS ƒÝÅ¿ÜÈ®“íSÿ}Õe$.Ñ]…,k®Ç€ªÛ7G­L¾n;d$ËÚ–7зè—#^Ï/ (J]_Çäéîƒqzlœ,?;íÓwƒ?ÚÆäê@Q²‚–µŽ×†B£Á\ˆÌË–-[D"Q0‡“`ÌP¨E$88ÜU(’E¢-[¶¼ÿþûƯÓðÒ†²;y¥)9â䜲;yr‰Ž€[#™ Ç!ž.Aƒ\‚|\‚|øîÝo ¥Ö”Þºç<Üû©ÿ¾Ú©^FdT7^«=ýÏ®€×Öemß\px/ÓŠçÿê[0äõw2·mzpöÂ;pnŒsÄ„¶ŠòzáeY~væ×Ÿ®ù°E”F£n¨gò­/‰5Þgé‹p÷hdF0"óò×_ÀJ›ö†°`¥MtY]uï@Q5ùbñízYÁ꜔F¯_-–ÖG r È´4ðŽ#•™¢ÿ9~c4ƒÃ2lɨËZÏ­æyù„|òÈÜpKW ø­:o1;› Éaë>iQ²ùLßî]„B¡ßKÙPn²•AN›6Õd5¢^s!2/%%%0´[¾ÒÕÑU›-e½¼ìN½¬`ÙíÜÆ™>O‘ ÒÞÏC;RÐÚÓ¸+œYØóÿµy‘Q«ÐŸ¢¶:ëëmˆAÈÜxxx€²´±Q®VJ&™?QWý7Eid2½þö@ýæBd^RSSÀMÚAWGWmV$…eחΩÊ*Ò¨5?`aËsÙÔ)è<›Å5Ý*<„ D«®…©S§þòË/Æ«µyL&søðáŸ}öÙøñÿ¬¡Ÿ——·víÚK—.UUUñx<ŸÅ‹/]º´­«««?úè£ãÇ0™Ì„††îÚµ‹ÃáÔÖÖîÝ»wÏž=™™™”~kv诪 [ê3Ô UyZv¯‘º²½#ÛA°K° hí 7‚ìË‚íð_—¶é®‡—cè§ðñ'ÜK<öÙn†…eæ×ŸÿzÔe|ÓZå­/ ø¿ù-vøà\‘øgȦoéåútì â³ð5_=´á«ú¢‚;Æb.DÝ÷ùçŸs¹Ü/¾ø¢ðΦÂ;›8VBí&ËÝGQEƒ˜žòÌçó¿úê«yóæªpÔ'5õž:uÊÛÛ[{ÖÊÊè;VÑ5j4±Xüõ×_Ϙ1£¤¤„Íf€F£™Z×Òz_ú¼ÿ²µ“ɶuÐ(•¦ø´¨¯³°°øì³Ï–/_þïÿûÒ¥Kµriù-–ïèèäèè0vìØO>ùÄÞÞ#€Q¯Öô·¡···¿¿¿)+ÖÖ8dÈ›ÂÂB¸ÿ~vvvJJ O'NœØNQï¿ÿ¾ÝåË—µQlìØ±o¼ñF‹ÛÖ¬YÓµÖÆÇÇÏ;·u.Ô³êþ©ê^Ñío ]ù¬Ûh“þh™ J]‘^(NÉ¡G Ê´ù›G„§“K ÄÇ%ÈÇÞ×Àät!هЇЗñO¥}²Î}òt‚É´pvõz~}ƒc¡Q5å¶Ö—胮½= èIQý´¯‹Ï÷߉¤ººÚ€% ì§@úë`|!AX³fÍÆ£££ëêêV¬Xqäȇ³dÉ’7Ò[vqöìÙU«Vååå}øá‡óæÍ›6mZjjêôéÓ8ÀìhniUUAMËêÚÙÙ1Œ .DEEuø(Š:xðàîÝ»;ü¹oþ¹¬¬ìõ×_?uê”J¥zúé§¿ùæGGGúƒ$%%mß¾ýÀ–––_ýõ¬Y³è´Jï[ß|œ¢þU÷O/þëÎŽ~Ÿi« IDAT3'ç%¸ö7v:¼£P|/•^ol,T©(ÊŠ$] ?k$‡³ÜÚšmªI m,O+P5*ôyŠiÉv6P»×ˆ¥=n÷Ùò¿.Ù£÷ÛPTU°íÀ!$üþ±}ª:=EÝØ ‘ÖÒ÷·¾Ä°°l±ÃÉd€F!×ÖÒz_ÓRÔY[[÷—PïÕ”ØÊËËmmmµg›Jýàƒ6nÜHþ‹ÍÏÏONN–Édtÿ™¶o,..nݺu¥¥¥k×®=wîÜêÕ«ÅbqlllLLLdd¤Îº)Šjll¼{÷îš5kæÌ™cg×4xÜÖÖvݺuÏ=÷Ü3Ï<3wîÜ)S¦ðùmþ¦,))©¬¬ìÔVQEEEEÙÚÚ¦§§«ÕêçŸ~Ù²eGŽ4Í@ŒŽŽ ßx¹P£ÖTe‰SrJ“rÄ)9‘¾ëÓò=]è¥d‚}ü…$ÓdËx÷e×~ÏÛÿ?J£&H’ãà<äw€ëîéÿê[YÿÝD©Õ$‹í6FðÄSôý­/y=¿ åáO8?vkíâao}ÔT A´Þ!„ú¶¦\8nܸæg›¿^‰‹‹‹ŽŽ…BñÝwß]½zÕÏÏÞ}÷ÝM›6isallìܹsU*UllìüùóçÍ›G?xð@gÅÚãgŸ}vçÎͯ~øá‡¡¡¡[¶l™3gŽ¥¥eLL̆ t¦C©T ÚL©7nܸq#11ÑÃÖ-[¶dÉ…BAnœã6¶xªõ¾ ÚK-vA¡¾¡)fdd´5¾0""‚>‰D Ú>³ÀÀÀÌÌLím!!!@¿2n~Ü–+W®øúúÀÌ™3ÓÒÒZÏ‰ŽŠŠŠŠŠ*--=pàÀ¦M›îÞ½{á‚ vìØ±hQÓ† ÙÙÙ<ª««éÁúHOO€Ù³gÓ-”J¥J¥²¢¢ÂÍÍ .\H7†ÝÑ–]¨º¿ÑvjÏ6ÞU(vJ¥<’üI ¶úyÀdÆâu ¥¡ª³‹Å)¹¥ÉÙ¥)¹µbÐo\š•ÀNäC/+è8Ä“dõýe¡BõjøEE%Ô¢(ªu˜ÓŸ““ý¶úóÏ?5jÔ–-[ÞzKÇk@°zõê)S¦{xxÌ;wÚ´iôUGGG’$é™3gÎÔ³j:ðmÛ¶ÍÙùŸ=Áœœœ:û\]];[u?Ô¼ËPËPépŸT 1|~ëPØM I½øvÓâÒewò²}ž"YLÇ!ž‚‡#M³ùB4ŸœœyEÇѸ»v–¼¢ èx#„Ì\'~›zxxp¹Üôôô   HMM¥_(wSXXØ‚ 6lØ0þ|ú/•JÅ`0š‡Nú<L¹\.—Ëm^¬Y³ÞÿýÈÈÈæïy5 ´Ê²Ú ¼¼¼­:µžºH’dg«î‡Zwju?^kl€)þf̘¶þ¹?zôè°°°ØØØÀÀÀÆÆÆÄÄÄ={ö\»v­ùd-??¿Ù³g¿þúë$I>þøãõõõ7oÞœ1cF;«6:::&%% …BúÝqªÞîór‡ß‡iTäÃéY?]K™Ûñ#faù?‡ú|̺“ï«TàÃ2X»ôÖ®ï!¤C€§KÐ z¯¾Ž"è:³íâ2cw›Mœ81++ëþÑïÙ6¶ö#G™Ã·T^QVuûæý£ßÑþrc!3ѹ·o›7o^¶lYPP›Í^ºtéÊ•+ Ò77·uëÖ­_¿~éÒ¥cÆŒ“'O>yòä§Ÿ~*‘Hìíí§M›vüøñ¶wrrºqãF||üÆE"‹Å |å•WZd¸æöìÙóÁ¬]»¶¸¸ØÎÎ.$$dúôéí´pýúõ111 Ú¡]®é¤M‡zª¢ÀÊø]³–ö|ç‘MSFœ‡dZâÊD†a¶]\Æ`ìn3Ÿ¨¨¨“'OÒ+x›‚ ¢¢¢è¿BfŽ0ø¾pH'ýú IFÓQŠcÊaïöï7û—ghçm hçιööÀ[Pîå¥ÏýÂû÷)*ÏÓ“ßíhèTPÿMI0H{_w— : Ú pS-‚ØýzS\ ®g1ƒ`2ƒd0ƒ`2H&ƒ`2&I0™äËÑó5jUôü  Ð@þá`MùýA –™I—1ÐÝfÙ»¾zùòåFMHb±øôéÓEEEíìe2ŽŽŽîîîO?ýtëM`ÕªUÐÕÕÅBF‚$‘Yøç=òN¬çÉdÞS*3”ÊQZZÜsü0—`— —Þ,+ ƒ”Ù<=ÊåÔÒã>hÿWü¸9ñP ivÊmÄäy^>}Ùܺ¸ŒÁ4Ýf...ôÊb!ÔØ_hFJŽŽ¤4M;wqDÙŽúØxu5ï¿\š³»;Eýì1íñÕÕ×[ßpû§uÎ;¡µYHÏ7Ò³¿pueå÷RiŒµõÇÝÞô“î/Ä?Ý‘V ùæTA]£^[?Ó, Ù òv¹¸Ä|º¸Œ¡ýn³~ û 2CØ_ˆŒKÕ ¿³ãŒÎKÝ_§f.÷½TúTú‚•ÕHܰ§zY¿÷¢ßW'òDåz-ëÃÕ2 vq!„™ÀµTq¥í»ØbñBpíµ?.j\7—¶åpfóx Šú?±øÇº:U³Ž Y.½¢¢R­îN¨Sœl8ëçú= W÷­'™Á†Fc7 !„þ°¿QëÎBƒïƒ÷ Àa™lYyùzc›Í'I©F“¦P”«ÕÀ»¸Q¡i±™äÜ õªÔü–ÿhN@ð‰j“µ !„>0"#jÞYhðDHcÄ׎Ž/òxd²rùŸ Šâ‘¤‹5‡Ç›Íã92—‚ ¤JªøíVÙ•»r¥¦Û¬‰Jg¢Ðd­B!¤'Ì…ÈX´…FJ„Í…[X„[àôážTRÕøëMñõŒ*uGûÄp AHf¶B¡¹KÚ¾‹v>nOn]fÔDˆz\^IÝ©›âÛ¹5­ç•2H¢ELTÊëý¸é ÀAŸ!dŽ0"c±ì1rÑÓ=Ý d,Àß’_oŠ3EÒÖWíx¬È—¤ìêœuÍž¡®z?dá3¦k%B¡ÎÀ\ˆŒÅsü°žn2 EݺWóëMqaY}ë«{‹§Ã\Âì™ "ñÞ#3Kîþ¾÷þÝ‹˜ BÈLa.DéK©Òü™^u&Q\V#o}u €;e” ØÇV»e JýÏK䡸{Î|mšv"„êÌ…¡Ž5(Ô—îTœM*«­S¶¾:dê(A€'¿Åy•ºiV²£5ûÿÙ»ó¸¨Êýàß3 þ¯£‚  ¸"(h.I„ËK½®¹Dd.mJEYi7mÁ,l3oq©[¹µx3•4SoàŠ¦è .#²ƒ Ë0ÃÌœßc#²/sfü¼_½z gy¾ÏãŸsÎsž™äû’¶¥‡”Àä  %òõ¯§Š~;W¬P6|X„a($Àab˜G/wq“çêæ -¼eSül¬ðÝÀÜá;5˜—feeÝR«}ÆûÃyK­Ö•6ZÅ.¡¸By ³è•Ö©Îó øLD?ç ¡îîŽ-½~P£a‰h^¤¤§[ÓÁÌ r!˜OOϬ¬¬s*•1sá9•JWÚhÍœ¬XñKFAæ•;ÚF‹ZZðÇt‰ qs°¶ÚNFûð`בý¹é&r!˜—ðððƒ~RQ-çE%¢O**t¥RЬ]¹Uµ/£àÂ5yãÅ©íĂȡnr[¶õwÆ×ÃzÖXŸ—/_Þén' Á¼,_¾|óæÍ§e²„ÒÒuÎÎ<ŽËi‰JKO+•‰äAÎ+,ѹ«û2 ®Ö_nðo.vsh€³… }¿!OE÷ð™Öó€\æÅÎÎ.%%%**j[eå…"ÁÁ!ÂÒ’‹kÊ·Ôê´ÚÚÄ;wnªÕ 䤤ØÙÙ¼ŠùÓhÙôìòýy¥µ÷ú¸XMsÞבÇëH¼kð¬ Ûø(`N ÁìDFF8p ..î¦L¶¬¤„ër‰$99922’ëBæF¥Ö;_zðda©\Õx¯¿·Í¤0÷~ö˜îxp ‚9ŠŠŠ’J¥‰‰‰iii¹¹¹R©Ôà%üüü"""D¢–ªí~ªkÕ‡Ï:S\¥P7ØÅ ð³Ÿæàmc’¾€ !‚™‰D«V­2u/º›òʺƒ§ f•(ë.=Ãã1a}'†¹û¸X™¤o`rÈ…„ü²Úý™…^*«ÿn: oT°sô0w{ “ô Ìr!@7w­ ú—ŒÂÓ9w?õ!¶ä?<È5r¨›ß ¹ ûúëFå/—nV6Þå`#Œ q;ÐÅÒÂ8ËD@€\ÐÝhYö´ôξŒÂ…5÷º;Š&„ºGôsÆÊ‚Ðr!@÷¡Ö°iK÷g–+ïíé.žæ>4ÀÇ @ º…Jsô\Iêé¢;Uu÷õ°æÑ¿§­ñ;]r!@×&¯QÿzºèÈÙ⥦Á.†¡¡þÃ<|=Ä&ét-È…]UI…êÀÉÂÿ](U©.F(à3#‚œ&†º{8Yš¤oÐ!t=·Š¿df\.×j®=#òÆ ty4ÄÝÑVh’¾@×…\ЕHóªöežÏ­h´!ÙX êöð`kKü½€ŽÀÏ€.€%ÊÊ­Ø—Q˜“WÕx¯³Eô0÷‡8[xÆïtÈ…fM«eÓ/—ÿ’Q˜W¢h¼×ÛÙrB˜Çð@G>KÏ@g!˜)•Z{ü|éÁ“…%rUã½þ^ÖÃ<õ¶GCA.0;5µšÃg‹.ªT¨ìbˆ‚}í&…yôñ±1Iß C.0#åUu©§ f•Öª.FÈã1¡}&†yH\­LÒ7èö ÌBa¹rfaÚÅRµ¦á£ÆÞÈþÎÑ¡n®ö"“ô È…&v½°æ—Œ‚SÒ;l£µgÄ"þ¸A®„¸Ù‰ñW8‡6`¦”JebbbZZZnn®T*5xû~~~ "‘iæá.ݬܗQpñFeã]öÖ¨·±ƒ\¬,øÆï<˜ Á¥¦¦ÆÅÅÉd2îJH¥R©TzðàÁäääää䨨(îj5À²t:çξô‚ë…5÷º9ˆ&„ºGôsb1B0.äB0;‡ŠŽŽfY¶‡@ààaié#0üÔ[juZmmâ;7e²èèèÔÔÔÈÈHƒWi@­aO\*ÛŸYXPVÛxo7ñÄ0÷a}x Ÿ@.ó"—ËcccY–]`k»ÎÙ™»3`†Ít›WJK·UVÆÆÆ^¸pÁÎÎŽ£rµ*ÍѬÒÔS…åUu÷Jl'†¹÷ïe‡<&„\æ%))I&“ ‰¹ …z<¢Dgçó*Õi™,))iõêÕ/Q©P:]tølqMmÃ¥g†÷v˜æîçimðºí…\æåĉDô¼½½Ñž¶à=oo¿°¨HWÚ€Jåª' Ÿ/U©µ v øÌð@§‰aîžN–†- ÐaÈ…`^òóó‰h……1‹êÊéJD^‰â—ÌÂôìr­¶áÚ3"!oô—G‡¹9ÙuŒ­B.ó’••ED\Ktþšü—ô‚+yU÷:ÙZ<:Ìmô‘KÏ€ùêþ¹0??ÿµ×^Ûºu«©;Ý“VËf\)ÿ%£ðV±¢ñ^/gË ¡î#‚œø<}º‹‹ dz³³:tè¦M›Zh°¼¼üå—_ö÷÷–––}ûö;w®R©$¢ŠŠŠmÛ¶Qvv6cèeáô£àóù={ö|æ™g8]ŠÌŠJ­=r¶øÕ”¿¾Üw½q(ìíiýÜ¿5 úìïŒP]ÂÝùÂ}ûöùùùé·Z[s¾j†®¢V«-,,üüóϧNšŸŸoaaADZ­6:::$$ä§Ÿ~²··/,,LOO/--m®©²²²ððpGGÇÄÄÄ€€€ŠŠŠS§Níß¿_­V׿™««ë|бޮZµjÙ²eîîîM¤W¯^UUU/^ܰaCHHHzzº¯¯oÇ u'y¥µÞÎÝóaÛ¥æÈÙâ_OÉkÔ v1Dý{ÙM óè+±1Iß:ŒaY–a˜K—.¯êýår¹½½½T*õ÷÷'¢k×®ùùùUUUµ1ž>÷Üs™™™Gmò-·ÙÙÙAAA,Ûø1€Žw¸¹í555£FêÓ§Ïwß}×*ù{³Ú»‹‹{Æ8„½×™>·l“ÿ“úÏ‹s¶t¦©‡>¡ÿ||ÅŸúÏ2 ³®É§„{¶+!éæt‹{õêL—:Àõúu"jõÏIEuÝÁSEGÏ•(T #ä1̰>ÃÜ{¸‰9ê$§Z¹ ža˜o¿ýÖÛÛ[w^uuõÂ… ­­­œœ^{í5­V«?ì×_ ‹ÅëׯÏÏÏ  …3fÌP«N¨4VVVÆ0Œ³³³îKGGG>Ÿøðá¶ €eÙo¿ýöõ×_o2ÖWÿ:rQQÑìÙ³íììÄbñ´iÓJJJô9}úô¢E‹lll\]]wîÜIDº´´pá–KˆÅâÕ«WïÝ»·-£îöÆ v½]ªHÜyeÝNéeYOc„ëõëõÿó¹q#,/oqqñ!…¢Sÿh¤°\¹5õæËÿ¾p ³°A( xcº¼Ûoñd_„Bèºî^G...vppÐoõððÐ~ë­·Þyç1cÆQ||üµk×NŸ>]UU5gÎww÷_|QwXBBÂÊ•+ ^yå•_ýuÅŠ………ñññqqqQQQMÖfY¶¶¶öüùó/½ôÒìÙ³uÛV®\ùØcýßÿýßœ9s&NœhkkÛÜòóóKKKÃÃÃÛ>f–ecbb.^¼¨Ñhþñ,Y²d×®]º½ . Þ³gϾ}û^zé¥3fddd8;;§¥¥·Úø¨Q£ ÅíÛ·{ôèÑö.uK"!/z˜ûÎcyÙ²ÊlYe Ä¶½s‡m·ÀÖ–%ªcÙr­ö’Jµ§ºzOuõX+«dWW{^g¾QTóKFá©+w´f­Dü±]¢BÜì­…¬`rwsáèÑ£ëo­5-!!A7O¦R©¶oß~üøñ¾}ûÑ›o¾¹nÝ:}.ŒŸ3gŽZ­ŽŸ?þܹsuŸoß¾Ýdá   ýç)S¦lÞ¼¹þÞ·ß~{ذaIII³g϶²²Š‹‹[»vm“é°²²’ˆô™²-ÒÓÓÓÓÓ333}||ˆhÉ’%Ï<óŒJ¥ÒÝݘ˜È0Œ§§ç§Ÿ~JDNNNº-ÄS={{{"ª­­m{º±qƒ]œ,ÔÝ„Çi:\ÿ÷d3±DéµµKKJ~W(b‹Švyxt8^ºYùKFá_7äwÙ[ ê6n‹•ÈhofàÖÝ\ØÂý…º2™L¡PèçÌ‚ƒƒ³³³õ‡…„„‘@ hð¹9ÇŽ  ¢iÓ¦]¸p¡ñ“Â111111ß|óͺuëΟ?øða†a’““Ÿ~úiÝ1R©ÔÆÆ†ˆÊËË]\\Ú8æ‹/ѬY³t=¬¬¬¬««+))ñòò"¢ØØX]g,:ôʲ²2"ruuíÀ¹Ý~ÊP¿Ås‡ ÑKË=<ÆÜ¾}¬¶ö@MÍDqû®í²,¹zg_zᵂêÆ{ÝDÑÃÜGöw °!t+íX¿wÿõ8Ý+.ìêꪻZ½aư°°¤¤¤W_}µña+V¬˜8qbPPP^^žÏœ9sôËèè²qtt<444>>>88¸¶¶633sëÖ­iiiõ¦ÑëÛ·ï¬Y³^xá7jÔ¨šššŒŒŒ©S§¶°,Ž‹‹Ë©S§$‰î²u}¹¹¹D¤P(Ο?ÿÑGYXX¼ôÒK[ˆýðt«¿ÿ”°ü¿sGÚŲÝÇ[?¥ÃÂê}nKßZR/+u )îÒa€PHD…š† Ê´ A(dˆ÷¶ŸæÑÛ‹ó=L®}ïÁ[¿~ý’%K† baa±xñâçŸÞ ðòòZ¹rå믿¾xñâ‘#GzxxDGGïÝ»÷ƒ>ËåNNN“'Oþᇚ;ÝÕÕ5==}Íš5ï¼óŽL& …ÁÁÁO=õTã §·uëÖ·Þzë•W^ÉËËstt yüñÇ[èá믿§P(ô·6êMš4‰ˆx<žŸŸßìÙ³¬¬¬Úù ÷Ò¡Û´âñˆHÝ¡¥+ùÿ²DÒÆvž\J$ä=ìüh¨»³mG<èºÚ7_Àýuä„™kSZWG_Mn£˜pϨ71–ž€r¡‘¤Ämõ˜ü= {÷ 2ÑÏibXë§èÄy¡¼R¥ûühˆÛ̱>­ž²é‹öõ­}ÔJS2 ›|îD‡»çNRkjˆh¬e;.?áiðntÈ…À-eöÀÉÂ&wqºNMžZ½¹²Ò‚aæ4›)Ô‡\Ü:r¶¸Áâ…Äq"$¢L¥riq±\«]ãääÕâúê ‡™À¡Æ“…%—JK‰ˆ%ªÒj/¨TWêêøD¯;:>cggØBÝr!p¨þd!§s„Û*+uD ÓK ˆ³³{ÒÖ¶O{ž8äBàŠ~²ÓDØöm eÈ…À•#g‹½œ­–Læð>B0 äBàŠ·‹Ut¨»©{mÅ3u Ûà‹g>ºäB B.äB B.s3nÜ8"º¥nøŠNéÊ 8ИEÌ r!˜ ":§R³¨®œ§§§1‹˜äB0/áááDôIE…ÆX5DŸTTèK<° Á¼,_¾\"‘œV*JKµÜ—Ó%”–žV*%Éòå˹/`¾ Á¼ØÙÙ¥¤¤0 ³­²2ôÖ­UUÝkxK­ÞYUzëÖ¶ÊJ†aRRRìì°à"<Ðð¾0;‘‘‘ˆ‹‹»)“-+)ẜD"INNŽŒŒäº€™C.s%•JÓÒÒrss¥R©ÁKøùùEDD$$$ˆD"ƒ·Ðå ‚™‰D«V­2u/ ¸¿ˆ @¹ˆ @¹ˆ @¹ˆ @¹ˆ @¹ˆ @¹ˆ @¹ˆ @¹ˆˆ¦î)•ÊÄÄÄ´´´ÜÜ\©Tjðöüüü"""D"‘ÁÛ€n¹ÀôRSSãââd2w%¤R©T*=xð`rrrrrrTTwµ ‹B.0±C‡EGG³,ë&¡éñÔ/œ\¼ _¥$.ž Ý’L&‹ŽŽNMMŒŒ4|èÊp!€)ÉåòØØX–e#çÑÇÐè霄B"rñ¦ÑÓéã?(r±,+—Ë9©]r!€T@¬³ IDAT)%%%Éd2ÿ!»–îÿ:2<Š]KþCH&“%%%q^ºäBS:qâÅ,%ßHy|ŠYz¯4€r!€)åçç‘ß@£Õ•Ó•ÐC.0¥¬¬,"®î)lŽ®œ®4€r!!€r!!ŠZžcê.t r!€aÔæ/ý}¡ª8ÓÔè ¼À0¬{ϪºœRòûB‘k¨mÿe®¡\T™åsß—V¶äéK#&Ó¤EÄÇßfèü$0 F`eÓ7Vžµ^Yœ©ä8F>A,KjË(;“r³¨,Ÿ®á¢<@  F7e¨U–§é0îý{ŸÏýNï=AÇ¿G.€ÎÂý…£›2¬¿EYœYòûBNï;ì?’ˆHQÅQóðA.0$ëÞ³x"§9M‡·sˆˆ$} Þ0À]ù{³Ú:S÷ŒAß!Ã0DôÝ­¶ž¨{Ù«7iµT]A•eä7žXEýF´¯³|hÆX‡/“^±î=K`ëÛ¾“ ›Âs'& *%¼¿õ‰¿ªÛ{úí«÷>WWPî9 BBQûyj‚Sµôëjé×BÇþbßiV=&ò„¶ííI{ér0@}˜ž0È…]Ïw·ˆÕ’¢šn]¦#;èë5tl½ñÙ¹´µ…`_Ë`_KÝçºò¿*Êÿ’ŸK´ô‰÷š*r %Bzx!˜€þ:ò‰¿¶u¬†Gb[ê3Œú #'Ú“DÛÞ¢ç>këéÓ²o°…Õ(7þ«¸ñ_DÜkªU¯)|+÷Žõ­UIIIµ ]ËòåËMݸr¡a„6¬²\÷Ù.ø9› Åm<1þË å•*ÝçGCÜfŽõiùx"Úäÿ¤þóâœ-íìé}úèÞ­mÇWüÙ™¦º‡ªË[äYë›ÛËÅŠ†QóiO>ÜŽSÞû¶è´T±%q¦²ð±Úú»ÔU2ù…å}*r)öjé5Žá ;Ð+V£$†ax8L¹ÀXµ¢êrJ“»¸{ŠØŽˆH¥hÇ)ª:ö¿'äÎ}©©É¯¹þSÍõï5Õy÷Áj•Ç•Çy"G«žÿgÝkªÀ> ]½bx‚Ò£O9=ô/†ßÎ;ÀD  ©úêwº÷ÔÇé;ñˆèè."¢^ý;r._ìiÛo±mÐ"eqF͵ïkó±eý´Êòê+Û«¯l: °öf%™ÀmÚÔ4ï«Ì-ÿó%§ˆ Äð;Ò90.äBƒiÝÅÅ…ÇãÙÙÙ :tÓ¦M-4X^^þòË/ûûû KK˾}ûÎ;W©TQEEŶmÛˆ(;;›a˜Žº•XXX 8ðÇìX;¸Ž ÝËV]ÏÚÙ‘sHøï¿VWWÙöîKDšZ…V©Ô¨j‡Ž°óL{zšººŠ'‘V¥äYàÅb4-«åa• ºVšëëì××­[÷ÆoQnnî¤I“.]º¤ÛîââÒöFöíÛççç§Õj ?ÿüó©S§æçç[XX‘V«ŽŽ ùé§Ÿìíí ÓÓÓKKK›kª¬¬,<<ÜÑÑ1111  ¢¢âÔ©Sû÷ïW«Õõ_TèêêúÁt`¼D´jÕªeË–¹»»79^½zݹs'%%eîܹÅÅÅb±¸½í#@·•þìV«Õ(j¶v}ž‰'"±wÀg_½üÅ:V£á -œCGzGM9·öebY­Zå;3Vhç@,ëµZ=a„ýë_ºÉN†aN:µiÓ¦o¾ùÆÊÊêóÏ?Ÿ1c†.­-X°`ë֭͵_RR"šÌŽ­Â?€ÌŸÏ¿téRRRRaa¡¡Ú¬ºžsúµ¥'_Š;÷ϵŅD¤ÈÏ;÷ÏéÏÎÉxq~ñŸÇš=“aì‚ÔU•MîdµZeiqý%º°^Ãû ‘•ß|?õY)ÓwŸÙY«®5u§º!K¡ÕìaOÑÙéçw.}~×Ò3·Nw¾Ùøøøk×®>}ú×_ýþûï?ù䓿ŽdYV¡PdddÌŸ?öìÙŽŽŽºí+W®|ì±Ç¦L™²cÇŽÊʦÿðëäçç—––†‡‡·½‡,ËÆÄÄ”——_¼xñÒ¥K2™lÉ’%ú½ .¬ªªÚ³gÏìÙ³uó‹D”––öé§Ÿ6Ù F£ÉÎÎŽ‹‹{饗ìììÚÞ=Ì‚™R*•‰‰‰iii¹¹¹R©ÔàíøùùEDD$$$´úo;#xÐÆ m¤›Þ(..NJJš;wî€:Û"Ë^úôÝ^3º-Û»#'å“à„w.nxÛ-bœ$f&±ZMs'Ö–^ß¹Õ5|Lã—ê6Z²ô»EDTTYøñ‘¶ý™2#döユوl X%}gPý/ùBK›žN’hÏ> Þñ“Z7eX^SNDgd§ÏÈ:;w¨R©¶oß~üøñ¾}ûÑ›o¾¹nݺƗt‰((èÞ/þ”)S6oÞ\ïÛo¿=lذ¤¤¤Ù³g[YYÅÅÅ­]»ÖÖÖ¶q;ºÔ¨Ï”m‘žžžžžž™™éããCDK–,yæ™gT*•îîÆèèèÄÄD†a<==uAÐÉÉIW¢Éè2räÈ矾íݨó…`ŽRSSV¯^}ðàA.BI¥Òƒ®^½: 55•‹m÷ ÚNÙK©TnÙ²åàÁƒºkOV'¯¨¹uÃyÈp"r:¢ü¯3uò;U×s¼¢bˆaˆa¾àÚŽ”£3Ç9¾$óÝYGgŽ?:+2ýÙ¹¬ZÝgÑŠÆÍ6^¨Ûà50Ü÷Þww¾üßÓ“û×ÿ¾Ð…rë=ÓÍo†K¯Ç¬‚jîd˲>¼y.Ѱ%Ì–~ÊP¯“s‡2™L¡Pß}ëLpppvvv“G;v,??????""âÂ… o牉9räÈí۷׬Y³cÇŽ)S¦èþ&''ëŸÎÉɱ±±!¢òòvü©¸xñ"Íš5+00000ð7Þ¨««+))ÑíÕuF[uéÒ%FS\\¼bÅŠ &üç?ÿi{Oôˆ…@×rèСèèh–eÉÓƒžœOƒ‘››áËÑÙs´e»L&‹ŽŽNMMŒŒ4|•6Ð×MBÓã©_8¹pðbÞ’<ºx‚vH&/´KýÛ¡X–=pà@^^Þܹs >é[?núÎŒõ«û\[\@߬Yüç±Ë›>¨-*{÷h¢«/ tæçú<½Ü°Ý3­§G-N¿~BËjõ[ª•U_glÛuú»Ébf{Âݶ#7r5æò–þó‚ã—-*¹±·ç× Ò¸ù«?e¨×á¹Ã·²,ÛÜó[®®®D´aư°°¤¤¤W_}µña+V¬˜8qbPPP^^žÏœ9s&Ož¬Û«[ÈF÷ÐÉ´iÓÚØI]àÛ¸q£[½s®®®m<½1çââ2uêÔ>}ú„……=öØcº'fÚÑB‡kpA.—ÇÆÆ²,K1“é›íõ'¡ˆÜÜ(êúf;ÅLfY666V.—sR¨EúñFΣÿ ÑÓ9 …DäâM£§ÓÇPä<2áx¡½ø|~ƒ-çÏŸOJJ*..îXƒB;{±Oϲ³DTzúO‡~ƒ…v6¾·~ÞE,ËjµÍÝ>HD®#F{?úØ…ÞT×T7}D½%º“×€±}n¼]©Vî9³kÖæiï\{³ü¦a‹Ú» "M]3¿ÔÝQã)C½ÌúøøˆÅbÝ„eeeé.(· 44tÁ‚k×®½}û¶n‹Z­n0C¯{üY:Åb±ÇßÇ›1cÆêÕ«܆¨ÕjµZ-5%44”ˆŠ‹‹×#¶òxS[.øúú*ŠÜ—ŒùB0/III2™Œ‚éÅçÈÐË~6aèÅçHš#»”””´zõjÎ+ÞO7^ÿ!»–Œ°ãصtý圑p¼²cçÏo?ä(qéßÓ¥_Oû®Æø½ëÊ4F£Ñjµºÿë4þ¬ÑhT*UãÓ “’’æÍ›Wÿ¾¨¤?;Gÿy؇)AÏ­¼¼iýõÛÖ6ϾJDý^x#{ãºÛ©?ñ-žsâÜ"Æ5×T¯™OV]“fþ~ðKo7(Ñ`I ®±Äª5jV­ÑjÔZµZ«Vk5º-j­Fsw‹Z­U7:L·E£®¿Q£Vkëîÿ²~SšªÚJ†–šøy¬Öªùëç0v^ØÂ·> BžKDVöí^gä¡F¤f¨]s‡B¡pÁ‚ñññ›6m’ËåkÖ¬Y¶lY«%Þ}÷ÝÝ»w'$$|õÕWD”““3mÚ´Ù³g‡‡‡{xxÈåò 6Œ9²þâ8õ­Y³føðá¡¡¡ñññÁÁÁµµµ™™™[·nMKKspph||ß¾}gÍšõ /ðx¼Q£FÕÔÔdddL:µ…I>—S§NI$ÝeëúrssY–U©Tùùù[¶lñööîÑ£‰©ý–!‚y9qâÑœ™ÔÚjÃãÑœ™ôæ?ï–6.]ј¥Äk8+ÄŸb–ÒGO“Ç+= äâÍôõ»u_ZØX9JœûõpíßÓ9¨‡S€OؾÕÔUʯ}·Ù8@Û¾tYs3 …"99yÒ¤IãÆkauCKWÆëõØôòyÿ¾6Xyú YÓô£š Z`x¼+ß×}ÖooËúˆ éå}=§%–%­–´,±}fù1|fõϯ«æ6MSIN]ÿ0MsÏǘˆ–Õ¹òÛïWŽ ÷5@,STHs3WÓ¿ÐùÖº™¶§Ãõë×/Y²dÈ!‹/nË£^^^+W®|ýõ×/^;88|øá‡íꉫ«ë'Ÿ|Òäš8úNÖï­H$zï½÷Þ{ï½:Vÿø_|±É§ª;ùDšr!˜—¬¬,"âêžÂ渹Ý+m\º¢ÝSØ]9ƒ7|å,EY¥toÃiHmºäâÍ’‹7/ïù_¼÷–Xú*[ëjźZi]¬ìøVr°=ÑüöÇ–®DTq)ëÜÛñžã&ð­¾JYVœñâüμ„S-äB3gÎäåå½öÚkÆéO‡±Z–X¢n}ŸKï§G>3²÷C_ÎMéX µ•×ôŸ5*yuÙ§<>–ºa×¾† À03nÝSµwªdÇηt1T¡äW()ç…Ùö>tç×}³ñ `µZM­‚o%¾º·à÷ªòR‡þƒ—½zuÛF":õÊ"¿y‹‰¨ìlÆÕí_(ËJ¼}ÌwöS\w¬U,Ëòx¼Ò¡D"‰‹‹3f—:ˆ%VË2|ƒC†_Àgø|¾@ÀÓýÇ×mð|Þßoi¸‘¯Û"àÝw–€'àóøú-×JsÿýǦæ:ãíàýTø¢ñtòuyÃg\"V«QW×Tä_Ûsóܺ’ë?ŽMŠœÛÞÈñv¦¦õíÉÿl<Öì:GH„F€\Ãðý|ÙÞ'ÖËmÃÑLÝÃ’CÿÚÊmŸX¶®²âúîíb/‰…ƒ¹„Žôzäÿêª*Óâ÷›»¨÷‚¥%™„¬ûR· KáñCCÖ~Vsëú¹·ãÍ!Q ¹088xÞ¼y]à"2ÝÍðøB[[—!¶.C,¬Üó.n¼qæ}ÿ¦qóV[§øöä×MîB"4äB0¹¬Xvì¼ÈNÌðVÛâÍ.ž*º§¶WG^ÓÔvéÏÎ%"¾¥•}`pÿ—×èž’æ[ZûUQ”ODZu]ƒS—¼ÂÎÚº†» ˆÏçóù|ÇãñЩÛ"“ÉÔjuãFÆŒÓêû^͇ìðí¾“ï{€º0³˜ˆî3ÞD=j«¿òÏŸ–j°ÑÞÊá‰Ðyž.pu×ÝNÞÅwòç¨}sóùï/ŽDhdÈ…À•rižc€qï›3©m¼zêZÕíô˲cY7]¨¸VЖSXK~Ýd_­GûV[í€áŸýGwá½ÒZíé7žõ™0Õë‘ÿ»±{{ãSÝã{ß·~ýú¶¬¿^Çã=þøã£F2h§ Y_¥ß÷à‚µ…õŒY3CæX[pûg˜/´!"­æx#sãÉB$B“@.®Üø=ëø[_{~Š×ð@S÷Å´ñÞÉ-?ó÷¬üÌ+êÚ&ÖØköDuÕÔ!¬“%w}kA]EyÍ­.ÃGkjª‰H«T ¬mˆH«Rš¤?mÑ`FP$ÍŸ?¿_¿~¦êσæjINZîÝ÷Š¢ÇM{"l¾½UkÑ\Éõ‰HìЦU*»ºú“…H„&„\\ ~âásÉöÎMôÈyZº"¥Ÿ¡¬óTP@µJ²²"êÕƒûÒŒQÏ3ÎxgùÜ÷¥•-yúÒˆÉ4iñ¹e]2ïÄ%ÙѬ›ÇÎWÞ*iåh†¯\PWñéí_ßt 稇­²ppr ›¹|sH„[ĸóë^þñWÎCGœ|eÑ€Wß5U¯ZV?ÚÛÛ?ýôÓÞÞš™Ö­àÃüý&±zNŸ×–k‹ ÒŸkéêβ,«Vó­¬|&NóŠšÒ™Î˜¿¯3¶³Ä x‚‰Á“ŽxÊÕ¦ãï(kÕµSo«Ukê*9ŠÊ\"ò l¸L]÷£Ÿ,D"49äBàŠÀJ4(.úÏÄ·Ó³÷ÎÍæ*-i4ôéFúq/‘o/zh …TVNWséøtüšò$lvAQ2Òx‰ˆ(ò bYR«¨XFÙ™”›Eeù´p ¥ˆX¶ìJÞÍcçeÇÎ矔j뚸ÑM'x Œ`ßÃíà²Ïìuè÷òÞw«ŒuQ¬é¥g¦ß‹o6ØœðNƒSš\ÚTô¯Âóòòzúé§›|kB{éWðé€áŸ}£ûPv6ã¯õ«ºw.Ì»së÷+¿=õTÄ"oŸÖO蜢«÷žná ¬­xöYèäó×uMî‡sß÷röýçäwM¹8¤›BS”ʉˆ«´ôÙFúq/yzЯQÿû¯¬ÝΧô ¸Ñ IDAT´ò¢I2Æx‰ˆ(îý{ŸÏýNï=AÇ¿7p.TÉkn¥]¼y4KvüBuAÃ;Á°õqé1z€dÌ@ïð ¡XDDW~LkpŒÏ¨þn|®Êz•!{ù`ÐÍ-X°@$âꇪë9Wþõ‘¶N%´µë»4ˆMvnÏió*._ôfû!Y­VU^Zv.ÓiPGý1— ³·/øFâØî—‰µ×ð—Z?¨ûá>{Ø\S÷ˆ Sú)4ý§%iý°—ÄbJZOî ÷zyÒãFÉà|¼Mé?’ˆHQeˆ¶X¶äâÍ›GÏß<–UtæªVÓÒŠÊK ÏÐ>=Æ”<4ÀÁ¯áÌSÉ_7êé?yøÃÄu‰·á™!>Ÿ?räÈ©S§ðÑcÝ“Ú:cv&–½ô黽f,t>Z¶wGNÊ'þ±ÏßÒjÐ WH9:óîãÃÎ!áýW¼e¨.™§‡ûFšº _g?Swî·iàVý)4=ƒ¥¥}û‰ˆ¦Ni"š·ãmÊí""Iߎ·P[^uë¿tSƒŠ’V^áeïëÑct°dô@¯á}–Í.›Wré¦þ󀑯ÏaxÝúM\7n\x¸ïÈlð¤v¼¢æÖ ç!ÉÈyèˆë»ï>~+ù¿ÔèåËcvÖÖ©ªo^»º}£t˧æöæ@è äBàVã)4=¤¥³YDD£Fv®†Äíx‘]¦/V㙯´ïD1=-]N~üãÍcç‹Ï_ky­A¡Xä5"¨Ç˜’Ñì$m¸éžeK/Ɉˆ&lùÔ¡K'·¯sp?ƒ‡Âvh uxB ÛÞ}ýŸ|îÌÏ!t'È…@›üŸìÔùK;ÕT§ÒRA>‘DÒÞ¢Mú¢÷ÂÎþR´AÒáŠ1¤ÕRuU–‘ß@zs'õѾFÙõ™í~òÓŸZ8Æ©·dô€£x ë÷hÇ÷ŠÊۥʊj†Ïýöü ™cÚ×30¡½Ø§gÙÙ —°‡JOÿéÐopëç°lÕõ¡=÷½ãA.³ OK}¬<®(Ú´62Q­’ˆÈÊŠ»Žq¤ƒãÕŸ~õÞçê Ê=GCHØž.)ò›ÜnakåÑ_2f€ä¡`O§övL§ä¯›|‘02i±o-ì"&蹕—7­¿¾s›ÀÚ&ðÙW[86ýÙ9¬V«QÔlíú<o´>v!ãÆ;r䈪&ßBìi´¢ªš|"8p Ñ*B·„\]™HDJ%)d-6uWŒê»[ÄjIQM·.Ó‘ôõ:¶‹ÞøŽì\ÚÚBq¼¸®ÒUhKDÄ0.AÉè=Æ pâÏtöµ³•·Š'm‰÷ ëÄ=¢ª(¿üy―ï·~胤É%xlzù‡¼¿©åÃÌjùs¦{cuUÙ'#æÂª² Däéi¼ŠÐ-!‚YÐ_W´4Qч;ݸI×®QpκƉŽ·†Gb[ê3Œú #'Ú“DÛÞ¢ç®Ø’“Uמ›÷´.Z9ò=Åýç>Ìq»ZGÆ{d’Ûñ6ãè."¢^]ìZz§.M¸°î ±O/—a#]ÃLjœ]‰H~å¢lïw¡má[Zeþ~Þ/{ÜÇDéŽo¼«çôù7¼í1N3“ˆX­Æ¶wŸ’Ì?BÖ}Y[ü÷“@ͬö\xüеŸÕܺ~îíxäBhRddäâââd2ÙÕŒÞ.Òv‰$9992«pCg!‡Ož>!Å¿HZ-¥¢wާϾ >þdmMÕÕ”“KååÄ0ô´ñ~lc¼DD”ü*‘FC5$»rwië)϶ˆYs5Þ%lTyÖ©’ÌÿÝØ³½×Ì'½£¿sá ]ùwÕܺ¡QÔèsaã]uò;U×s¿•¤[¢á7ñͰ¹Õž—¼ÂÎÚº:cŒº¦¨¨(©Tš˜˜˜–––››+•J ^" ÀÏÏ/"""!!»w$¹8TòŒ«93¡V&Ф‰´ÿ¿@gÏQšÄb’øÐ„(Š~” \±yÆ/úúÞg+ê=ˆ&-¢° \”2_< ‘ó°çaîc½ðþJïèÇÀÒͳ×?èà‹,µê»¹­ñ.݇ŽÝQÊDDx ´F$­Z…w‚CW‚\\ÑOžq}•ˆhÐ4€ÃöÛÀ8ãýî­v=Å'~w*[‘ª¬ÄÂÑ™ˆœCÂo|ÿµººJ÷<ЦV¡­¬ÐßxßÒÊÆ7àÖÏ»zýc˲ššjž@HDZ•R_¥#«=teÈ…À• _ÿæèïõÈ'K¸M„fãA¯i¥ÉýÏ¿Y­†áñDÎný^|“ˆÄÞ=Ÿ}õòëX†'´pé1öQÝñwõúÇ‚~/¼‘½qÝíÔŸø–bß9qnác‡Ž8ùÊ¢¯¾{·L{V{è +N}|?ý]Ú|ÐÆkZýãÿÙäv—a#]†Ý÷¾lý2„wYyú YóIý-Á ï48«…Õž±È3t?/ª°cL|a×È´ñ@÷ƒ\ÐMøûû‘²¤ÈÔiH×%///SwZ\ÐM"*8šjêŽ4¤ë–\0È…`Jf;ÅÅÝ0uCæÂøñㆹ±ç«üC?›É/©²¤(ÿÐÏ7ö|Å0ÌøñãMÝhž;ó2nܸ#GŽPQ¹¹¯jQ 8Ðxÿ¦oI¹x¯hI‘‰ÆÛ˜~Š«ç´'LÝÎé¦ÍtCæ‚¿¿LLÌÞ½{u+x›†abbb¸ Ä`(È…`^,,,ˆˆ._1j.¼|…ˆ<= ÿöÒV鯛›eÔ\˜›Ed¢ñ66~üøËW®È~üÎÂÞÁip˜Èň¿ïF¤,)*;›a„i³±cÇíß¿ÿÖ­[¥¥¥Üj#ooï &¸»»›º/Ð:äB0/ááá¤ovÐÈâå>­–¾Ù¡+mŒr÷ÓwïFE<~ëÇwžVC{7Þ-mŒzÍS“E%ë(ôë7ïŸýç­ æ6ÅÅãL›¹»»/\¸ÓÐ]áþB0/Ë—/—H$t)›6|JzGYû°,mø”.eK$’åË—s^®ÝxsÎPÊÄj9/Çj)å Ê9C¦/1Œ“WŸ"¶GŽvÈEÍ™¶oëâ.®yùå— äììl‚.…³³ó AƒÆŽkê¾4 ó…`^ìììRRR¢¢¢Ø½?SæIzr> ÄÉ5å¢":{޶l§ü†aRRRLò°¤~¼‡¾b³~§éñÔ/œ“kÊ%ytñíþŠddüñªÔÚK7+Ï]­˜ñÆ/Öön÷'`'¦ÐS\f¹ÌNddäâââd2½»Žër‰$99922’ëBÍ©?Þ/r^Θã-¯¬;w­âÜÕŠK7+Uj-YÛ7ŒøVL•%Sm„Î@« ÁEEEI¥ÒÄÄÄ´´´ÜÜ\©Tjð~~~ "‘Èàí·Kw/ËÒµ‚ês¹çr+dEŠVopd ¹ë ´ r!˜)‘H´jÕ*S÷ÂxºúxY¢ÓÒ;g¯VœË­¨R¨ÛxC¬SÌiÇ íðÜ Cäçiíåli'nÇ¿6m™r©¸ë´ æ À0m„BÝ'„º_+¨I»Xšž]ÞêÄ!."˜äB00_±¯‡x柬kGÎÿu£²ÉÃø¤¶cL¿ð2èá:2pBÀgÜD·Jj›;À)fˆû5 Í0_œ¸š_½áû«ÕµÍ^JÆEdsƒùB0¼‹7*×ï’Ö…îŽ÷-Ž#"…˜‘½_ÐäB0°39w6ü£¬»wø‘¡nÓGß÷G& Ì®#€!¥],K9xC«½· õcž1ážÊ:­€Ï¨5w·ã"2€Â|!Ìá3Å›÷_ׇB†hö8Ÿ˜pO" y}}luÛm˜;BRš¬—Р̀aü÷Ï‚þ¸­ÿ’ÇcžŒê1²¿³~ËàÞöÝÑþo>Ê9µÏ]€a¾:‹%úî÷[õC¡€Ï,™ì[?Ñ ?{"ªSÖܸpÄØ]€6À|!tŠ–e·¦Þüß…{+T‹„¼g§ôîßÓ¶Á‘.öÞΖ½<œ¿ª­6n M  ãÔöË_®¼rG¿ElÉ_þ¸o/ë&ègÜËÎX½€öA.€R©µŸý”{áú½eí­…ñÓü}\­š;edgOgK£ôÚ ¹:¢F©ÙðÃÕœ¼*ýg;‹—¦4X¿º/„B3†\í&¯Q´Gz³H¡ßâédùÒôG[¡ {„\íSZ©úpwNAY­~KO7ñŠéþ¶Vø~еáû8˜)¥R™˜˜˜–––››+•J Þ~@@€ŸŸ_DDDBB‚HÔÒ¥Oãè*ã-,W®ß%-­Té·ôñ¶yáñÞV"¾!º ¦„\æ(555..N&“qWB*•J¥Òƒ&'''''GEEqW«U]e¼7‹í‘ÊkÔú-|í–ÅøY°*@w€\fçСCÑÑÑ,Ë’§=9Ÿ"77×)*¢³çhËv™Lššiø*m ¯›„¦ÇS¿prñ6|•’<ºx‚vHoN^Õ†®Ö(5ú-a}ã&ôðCwLÿÊó"—ËcccY–¥˜ÉôÍvŠz„“PHDnnõ}³b&³,+—Ë[?ËÐôãœGÿA£§s ‰ÈÅ›FO§ÿ ÈyÔñ^¸._¿;§~(=ÀeÑ$„B€n¹ÌKRR’L&£ @zñ9b¸Ï C/>GA2™,))‰órèÆë?„b×Ãý_G†G±kɵk¼'¯ÜùäÇ«*µV¿%z˜û‚¨<#ü€!‚y9qâÑœ™Ä3ÖNæÌ¼WÚ¸tEc–ÏXmðø³ô^éVýïB馟¯©5¬~Ë´Q^3Æx#t?¸¿ÌK~~>Qß>F­Ú·Ï½ÒÆ¥+ê7ШEuåÚ2ÞÔSE;~¿¥„ Cs–<<Ø•ÃÎ€é ‚yÉÊÊ""®î)lŽ›Û½ÒÆ¥+ÊÑ=…ÍÑ•ku¼?üqû¿è¿äñ˜§¢{†9qÚ70!äBhˆ%úæ7Ùá3Åú-BoÉdßÁ½íMØ+àr!ÜG«eSÞH»X¦ßbiÁnŠ_P[ö Œ¹îQkØ/~¾v&çŽ~‹•`ùÔÞ¾Ö&ìžG®ä•Ö¶~P7Ò Æ«¬Ónø!§~(t°&Ì@(x@ WÎçV¬Û)½,«2uGŒ¤«·¦V³~—ôâJýW{Ñk³úx»X™°W`L¸Ž \7ØõÀÉÂÄW%¶SÂ=ûJl8)3ö‘û¾´° W ¤Èñ4<Ô+cÿÍ8ãåsß—V¶äéK#&Ó¤EÄïÄßæŠêºwçÜ*Qè·x;[ÆOp°v¼Qèj +"!/z˜ûÎcyÙ²ÊlY%·é0f2±,©Õ$—Óµëtè7:ô ¡·Þ n*6bÌñF>A,KjË(;“r³¨,Ÿ®é`k%rÕú]Ò¢;Jý_ñò©þ6Vøþð`Á÷}àn M^£&"nÓÒŠî}fY:ÞM¤“§hÕÛ´>‘xFš54ÚxãÞ¿÷ùÜïôÞtüûæÂü²Úõ»¥å•uú-Ûçó³´0Ö XÀlàþBàn ­þ–lYeâÎ+ÜÞ‡Ç04p%­'±˜NŸ¡´4® 5b’ñöID¤èPó7 kÞûîJýP8¸·ýò©½ LÈ…À­qƒ]íÄ §¥‘==hÚcDD¿þÆU‰¦¼·sˆˆ$};rîºÒ*…Zÿåˆ@§e1~B¾-< ð¸Õx Mót82‚ˆ(û2'7ÃÈã•]¦/V㙯tät…J£ÿ‡Ô‹P(ðX¸ÏÄ…d¶ñƾz=Jò¡;{uië{昶µ;Ì…$£ê“gòΙmþùꆭ-ºxcÂ=¸7]ýd©U?³7á‹kÛöNè~3î~Cµ#Ì…$ã䙼‰p篲4Ûtæï×—äh•ˆˆ`.$ùüv$«‹›ýSãä|ŽÐš´·ñQÛÃ\Hrñq·hO_Ñhoã%"¢¶GaéP›u“üÐHÚÛx‰ˆ¨ía.$""""€¹ˆˆˆˆ$Ì…DDDD0’µ=z4dfšµjf&€~ýú™µ(€Æ›]ïç”e!•³Èx‰ˆÈš1’u±µµ€3gÍZõÌYÞÞÞf- àŸñ¦$™µ¨TÎ"ã%""kÆ\HÖ%44Öo€Á`¦’Öo¸VÚ¼¤¢›W 7SEƒ›W^+MDDdÄ\HÖeþüù§Nã£EÙë‰">ZS§5Íüùóe/W‹4ÞäÈ{¢üIX4 îU$†¥ÆKDDÖŒ¹¬‹³³s\\œ Øü3¦NGü¯r=k˜™‰ø_1u:6ÿ,B\\œ³³ 4Ž7aæŽÀ®Mr=k˜†]›0wÖÁ‚ã%""kÆïÕ Ûºuktt´N§Ãâ¥r—Óh4±±±aaarªOõñ®œ'{9‹—ˆˆ¬s!Y£ððp­V»dÉ’ÄÄÄ””­VkòAAAÇ_°`Z­6yûMÒÞÆKDDÖ‰¹¬”Z­^¸p¡¥{a>ím¼DDd…ø|!Ì…DDDD$a.$""""€¹ˆˆˆˆ$Ì…DDDD0‘„¹ˆˆˆˆæB""""’0À\HDDDDæB""""˜ ‰ˆˆˆHÂ\HDDDDs!I˜ ‰ˆˆˆ`.$"""" s!Ì…DDDD$a.$""""€¹ˆˆˆˆ$Ì…DDDD0‘„¹ˆˆˆˆæB""""’0À\HDDDDæB""""˜ ‰ˆˆˆHÂ\HDDDDs!I˜ ‰ˆˆˆ`.$"""" s!Ì…DDDD$a.$""""€¹ˆˆˆˆ$Ì…DDDD*Kw€ˆ ¼¼|É’%‰‰‰)))Z­Öäí >|Á‚jµÚäíQÀ\HdyñññÑÑÑ:N¾Z­V«ÕnÛ¶-666666<<\¾ZDDÔJ1YXBBBDD„(Šž<ðz‡ÂÝÇôU²Ópr6}Nfú2DDÔšñùB"K*((ˆŠŠE1l–ïÆmÈ ¸ûà¶°|7¦AŨ¨¨‚‚Y*Q«Å\HdI111:.p¢Þ† ÿoGA¨·8:.&&FözDDÔª0YÒž={D΂Bi¦Š %"g]+MDDdÄ\HdIéééú™µ¨TN*MDDdÄ\HdIIII€\ÏÖG*'•&""2b.$""""€¹ˆˆˆˆ$Ì…DDDD0™JUA²¥»@DDÔ"Ì…D¦Q–þGÎÎYû-Ý""¢fâwðˆLñûä¢3qÙ;g¨=wè3ÛÖc°|µR’°c=NïC–奰s„«|‚н?Æ=•|•‰ˆ¨-ã|!‘i*{§žQʳögïœ!ÓÜ¡¾ q¯àå»ðƒ#0z2z CY öoÅ×Ôä5‰ˆ¨½à|!‘ÉHS††ò\åYûËe˜;\óâ×ÀÓsV GÈu‡2/âðvNQóq¾ÈdŒS†F¦;¼pñŸÃÞ ¯m¨ xúaìL¨í[^‡ˆˆÚ)æB"Srì>Y¡v­±ÓTépÇW0v&<4-i†ˆˆ¨nÌ…D¦T{ÊШåéðäÑìÞ5DEÑÒ} «.o¾ÅPžgé^9Ÿ;À×—uÕ#A(/ÅêS°ïÐÒLö…R"Ýv».··´-""j+8_HdƹÃÐ>ŽM¸ªìšpECG{çî~ºàèû ¦i‘ˆˆZ9æB¢VÃÖÊŠMÐÔÃa.‡¹(:ûyÎ*òMÐ(µrÌ…D öì>ês·QŸï9Ñ„”'½nrñtK«+ Â[3½Œ?–gìÉÞ>©*_ÛÒv‰ˆ¨•ãú…VÄÆ¥oùå?¤m·[W©½nmä…+:àìÕg÷ÔÃ冗¬ œiÜ~2yu{z[?fÜþãÙ¿ZÒTÛPtfuAÒ²ú޶dEÃàÁHÓbÏfôlÙzˆ…xï ±Ïûvq»ºÚaU‘.kÇC.CÞµóÓ¢¦‰ˆ¨5ã|!‘)‰U¥Egâê±ðäÊÜĹb•)ža$"¢Ö†÷‘‰L¦öd¡É¿ƒý züñ->yk^‡ÿMp耒B¤žD~S^lBk‚¦SÈë¶.½ò/ •Òβ¿wdmŸâ:â•“Ÿ©ºMDD­s!‘ÉTŸ,4y"”ØØbörÜ>;¿Æ™ý8™ˆªJØ;Á;#Ĩ‰pvor›Uι{æÊ®N6VœËÞ>Éeè2µ×ÓöŸˆˆ¬s!‘i' eJ„ÕõŠ^CMÙ ­û@1sŸ©Ì;!í1Täüù¤óMÏ:õœÙðµDDÔfðùB"Ó(>÷µsw“³«?nX’²)ç÷™†²l vŒˆˆdÂ\HdI£GfÖ¢R¹~ýú5â\¡CïY®¡ *G㮊ìÃY +sËÕ?""²æB"K²µµLð½ã&‘Êy{{7ò|;Ÿ1cÖWÿü‰¾4#{çôÒÔŸäèY s!‘%…††ؼ½™*ôؼòZéFR9ºÙPýó'¢¾cüоøRöŽ©.ƒÛùÞÑÀ…Åç6:Nnv]j«D‘a ²œ/$²¼ððp­V»hÑ¢±cÇÝø‚¦ ;vì¢E‹´Zmxxx [³qéí¶ÑÖ=ĸG¬*ÉÝ3¿ðÄŠzo‡‹†Âãù{Û¶°4ɇó…DVA­V/\¸ÐÒ½h…ÚÕmd\Á‘ÅÕ>‚"ž\UyåŒË÷§çW¦* ¿Ûí¥Øô˜˜3÷–¬SSr%"¹q¾ˆšIP¨:\Ø)ä AacÜYö÷oY;¦T¥Ö8¹"ç(€‰£:¹9+ÍÚK""j4æB"j‡€ÝF®Vع÷T¤d'L*¿ügõÓ*rް³ w5w‰ˆ¨q˜ ‰¨¥lÝx„m´qíkÜc¨,Ìùó©¢3qÆw+r®.Þ=#ÂU)TY —DDt#Ì…DdJûÎî£ÖÚwm—h(Hú oï ¢¾ÌPYTU˜"ívsVvshÌ¢6DDdnÌ…Dd‚Rí2低7/€pí ÂÒ‹ÿËÞñpyúÎêï)÷t:&p-C""ëÃ\HD¦äØcºÛ­ÿVØv4rêÊÁ7ªŸã¤Êïb_óÅ""²8æB"21uçP°6¯-Ä(V•Ö8'Ø)ɼ""¢c.$"ÓS:úºß¾ÞΧޯª¸Ùf¸Ùf˜³KDDtCÌ…D$ CyžÒÞ³úÒ†5pʈÈÚð{'Ddb•¹Ç‹Î®.½_ï7ñ]ìSTùEU8‡ˆˆÌ‰¹ˆLE,KßUt&®"ë@cÎ öt:vðÊ-rw‹ˆˆ‰¹ˆL@¬*ÉÝ=§¤‡Ã­ýÝÕŠ2é,µ¢¬›ƒ61ÅuË–-iiiÙÙÙí´\ÜÝÝ}||î¼óÎÎ;[º/DDõb.$"slœÔžÃ>þ.@LÌ|'U¾›m¦ô?aßûKê mùËxÙÙÙÙÙÙIII‘‘‘£F’µVFFÆ–-[.]º”““#k¡Æ` &j]˜ ‰ÈŠª:UuL- JNN^õÙJ¥ªûô9®ý‡¨Ý=-Ý5Y”ggæÙ§[±yóf__ßÀÀ@™ íܹsóæÍ¢h-!Ûœ˜ˆZ޹¬Tyyù’%KSRR´Z­ÉÛ >|ø‚ ÔjµÉÛoªö6^£íÛ·ë ¢æž‡½ÃÆYº/2R»{z‡«È¿raãêíÛ·Ë” “““7oÞ …²GÔÓV²Íˆ‰È$˜ ÉÅÇÇGGGët:ùJhµZ­V»mÛ¶ØØØØØØððpùjÝP{ou¼FZKdå52üÂÆÕÒå°}ûvQ»Ý?ÍzB¶y1™ s!Y„„„ˆˆQáí…™ÓÑÿfxÊ0í‘™‰#G±z­N§‹ˆˆˆ «÷c¾²2Ž×SƒžCïP¸û˜¾JvNîÁ¦`ññÖð÷ß°†™-3†) YUUU°Ê-w &"Sá÷‘ɺDEE‰¢ˆÈqX¿áwÈ xz"ü¬_‹Èq¢(FEEYä?ZÆñ†MÃòݸíYB!wÜö–ïFØ4Xp¼$«äädXeÈ–;‘©0’u‰‰‰ÑétèŒyOCd¯'˜÷4zëtº˜˜ÙËÕ"7p¢Þ† ÿoGA¨·8–/Y3æB².{ö쀩“ 0׿œ ¦NºVÚ¼¤¢‘³ Pš©¢B‰ÈY×J1’uIOO€ž=ÌZµgk¥ÍK*ÐϬE¥r/Y3¾wBÖ%)) €\ÏÖÇÓóZió’ŠÊôLa}¤r/QûÑnŸ¢V¹ˆˆÈÄÚóâSÔª1™’qñ)µ£¯oŸ9ΞCl¼M^¥¢$½ sߥŸXÛâSÔª1QÛôû¤1vEQ«ª”öö¾wÝß%üžÆ\(êõe™—í½Í{wŸÚ ãâSžÝ'ù\(ßB¶ÞîÝîqï:þü¡73ÏmˆŠŠ:~ü¸³³³Lå¨à{'$—ó9)–î‚Yµ·ñ¶ C?Y?ìÓ¯BÿýMàŒ9çÖ~ÖÈ«Ês³öÍ›.kǨ “Ÿrrí×màkæY}ªÛÀל\ûqñ)2 æB’Ë_ç÷<óͬ×Yº#fÒÞÆÛZˆCyNVîÑý®7‘ö¤mýáà‚'.xâøû¯U•Cµÿ÷ñ¾yÓü+úܺϜ[³ÀÁ·d¿©Õ’V€òŽ3­>%Jïàhpñ)2ÞG&¹L¸ù¾¯|ñÌÆY4g†Fð(K™Qw\÷££||0j$&Þ•Yÿõ6Óx)Iر§÷!K‡òRØ9ÂÕ >AèÞ㞀ÊF¾Ê­Ìï“ÆHn!¡}ž}@ÁÙ“ºÍ_þpµÒÎþô§ï¥ýï[ï1wÿÿãˆÿûAåÔ¢ û#³²÷ïYú öœZ/i('×¾æ,*•ãâSÔrÌ…$;û)ƒ^¹kÅaÝ¡Ã:™ÓRä8ˆ"*+‘~ÇŽã¬ÙÙxf¶,µêažñê«°æuįMO Ž€W²zû·bÿVÜ1¹ðš‘¶*+Š/ž?·v¥võŠÍ¿rü0€³ÿPr)U_ZbÓÑÅ9¨÷á×çzë}û]*§–î5µnÒ Pr¼hÒ©Ÿ¢–c.$IShy%yäM‡Ïν¶½o?^x¿&˜9Â,ã]óâ×ÀÓsV GÈu‡2/âðv ‡Â’Š[Kö …m‡î=g>}øÕ§{<6_P©ì<½»=øˆtT©¶Šþ‹>Ê=ºÿòöÿ¥'ü<8fµe;LDdA|¾d$M¡UßsXw虳ä}oÀ(.‘«ýúÉ=Þ Çÿ9ìðÚ†š¡€§ÆÎ„Ú¾åuš¯¼ªüí-‹ŠÊ‹,Ù‰ÚD±èB²sGn!¡E’«Š‹ì½|ì½|”ö†òòª’"·Ãz<ñliÆßUÅE • CE¹¥ûMDmGzzúŒ3,Ý‹c.$yM¸ù>—;åM‡/€7ӷܲŽwÇW0v&<4-iFF..™E3×=|äÒaK÷öΙú׬ɻ£îIýî‹O<ÀÁÇ/x΋g>[ºþŒƒ/<®ûi£¾¬ôè›Ïx>úðÂgü'EÙ8w²íäê6pØëx濫¢BÅf_^–uÙøeKZØ;gê_³§$Fß·oÞôœCI‡~Ÿ4¦,ërõö<ñà¾yÓÿŽÿ±%‰éá‡êòöÛo7²…êWÙØØ„„„üþûïÕOHIIyàÜÝÝ …³³óÀW­ZÕ@ƒyyyÿú׿U*•]Ïž=zè¡òòrùùùkÖ¬púôiAš;èæ”nÞG&yŸº«}H–;­ç/`É2(DÏ4MƒM$ëxOî€Á-주FŽþè·æ~3{Ê ‡þ˜Òb7¶GnØ^ç~÷A#ܨ¾gÐûÿ½î Aè»àù:Ö$ùùùßÿý´iÓìí-9<ô“õ Šé¿m9ûŸCWm¬û ÷ȾË6r©H+w>þ`·1%'P¬ÔÒ¥K_}õU)))wß}÷©S§¤ýîîîoä—_~ 0 Ÿ~úé}÷Ý—žžnkk À`0DDD„„„üøã;vÌÈÈØ»woNNN}Måææ†††º¸¸,Y²$(((??ÿàÁƒ[¶l©ªªªþ¡B÷ß¿yC^¸páìÙ³;wîܼÒÁ\H¦të‡Ãšz‰iÒá´(ˆ!?={ fnî×Ô6nù`h3úßT-o–º˜¦'} ~ìË™W¿®.]5®]ý\ü|]4v*»6{[ÐÈå¿}h _î_{àâ¾…w.òsíj’·OJ¥òÔ©S111>úhíÿ4OÑ…ä³ÿþÐPYaÓÁ¹ç¬vKÓÓÎþ烲¬Ë‚Jå?9ÚcØmu_)ÎA½ªŠ ë<( y9Õ—jíôUëÇ,™ÙcÂÓ¡ÌÎýo¿ßÈ›l›ðGP—.]ºtébü188¸u¤ {÷îÒ±cÇ‹/HMMÕjµ‡vttзoß1cš}ýõ×]\\~ÿýwc»õÖ[çÍ›Wã477·çŸ¾]ðÖ[oM:µö,Ýü¬‚ñNkÇîÍZ¬_§Ã¥4äç@aNŸEE…i{hZÍoy)Ø9š¦üOgœúõÔ¶ØÄÿ¼þó+3×M ÿxôÿ½çÙoŸ‰Ù±ì»#›ö§î½\pÙ šÔ¬‡“goï>Òö™ŒÓ~ñÈG¿Ñü;¡íœB¡••sìØ1´(ЧV,ÖÜ;yвX×C“ã>pò£7]ûºâË!1kܯﲬË6~î:²öÁß'Ù5县fM.ËHï=¡ úiºß9Xik³óŸ á/ý!QÔ7í÷5‰ƒ‡óºÏXþCEa©I,..ž1c†£££««ëK/½d0Üø_nn® nnnÒ...J¥rûöºï<Ô ŠâW_}õÊ+¯Üp~®ú}äÌÌÌ)S¦8;;;88ÜÿýÙÙÙÒ~A:ôøã;99yxxlܸ€”V{õêUã9ÅÆ—n ÎR›°óWD”–àülÙ†Ïþmñø`)\:Yºg¦dk‡Š2”þÅK©¨T*‡š¿ýEˆ……ûS÷wÚ©ì|]4~.~¾.~]]»ú¹tÕ¸hÕN 4>*hô‰ôãÒvYUÙÛ—î9¿{Aø+®®-íwû#åBååå«W¯;vlxxxKNª,È/¹”ê6`(·Ã.lZSYp¥èBrÿ7b ¥êü†¸‹ß}  Ïóo:uëŽê+AÖãñgk7[{I f÷ÐzJEÈìñÛŸûO~jæŽçÿ{håOƒž¹·û]C…é#ïÁ=Ý‚5VüxlmBÿÇï¼iz˜Ê¾E)ç¹çž;þü¡C‡ŠŠŠ¤9¶úæÏDQ,++;vìØóÏ??eÊ—«ÏˆwêÔéå—_¾÷Þ{Ç?uêԻC‡zÿðMOOÏÉÉ m|EQŒŒŒìÔ©ÓÉ“'õzýƒ>øÔSO}óÍ7ÒÑ3fôíÛ÷Ûo¿ýå—_žþù‰'îÛ·ÏÍÍ-11±oßëVÇlFé0’U0ÞW]1±±+«I!ÀÑ}û o¸»aÍøô3¼ú’I»i2ͯ‡iZ\<žƒ[ÚÏÆÞ¬)«*KÎÒ&gi«ïtst׸hü\ºú¹võsõóséêåì­T\ýºÃmA£>½þ ËÄ”Ý3Ö>ôbø«-íwûcÌ…DQܺukZZÚC=d’‰êª¿Ýâ?)ÊR”´]–uÿ<¬™õ×®3«Þ/˼ìàãWGW¯_ȴݳ”ÀqC¬ø1ÿB€+)—æ­:øéOƒž¹'`ì ¦Ãâ¼™)ßf(/N3èË”*ûÎöÎÝ\oòî9SP´—%ICæDþSG+ÕÍ~EEÅÚµkÿøãž={xíµ×–.]Zg.ìÕ«—qûž{îù¿ÿû¿êGß|óÍAƒÅÄÄL™2ÅÞÞ>::úí·ß®30fÊÆØ»wïÞ½{÷ïßïëë à©§žzâ‰'***¤§#""–,Y"‚··÷Š+¸ººJ%jt ¥À\H¦ôdzÕÞùÕ/ë|C"ËŠ†÷FbÍØSGgðçs{[òâ§‘|ã Œ4-öl6A.,¸P´oÑ¡¿Nü¥Ë½˜š{A—§»˜—šžÿw#ïçgçgWéØFiÓ¥£ŸkW???×®¾.šKyºê—ä•ä½øÃóÝïïvþ§‹-í}{¢TÖü–Ú±cÇ¤Ç =<<šÑ sGß®¹Gö¹¹5çÐ_z÷·qîäätéçoº=øˆ(Šú’âú÷öv[Ñyíñ÷_¸x¥Ê¡®ª- Ô6JÅÀYã~{áZVÈÓ¦ýúôJ·`Í gîõ¿cZöb©(êS¿“‘üûŽA®¾w Ûʲì’+gòÒòÒ:NQ¶›\è{KÏ~™I)Js ßùêhìÖ³Ç÷zðV…MÓâŠN§+--5ΫõíÛ÷ôéÓuž¹k×®   ÷ßÿñãÇkOÆGFFFFF^¾|yýúõK—.=vìØöíÛAˆ}ì±Ç¤s´Z­““€¼¼¼Æ¿õròäI“'OV©T +++³³³¥‡&£¢¢¤ÎH1±Í(ÝæB’WYeéW¾¨óŒ_@qt€r úè£!C†ÄÄļøâ‹µOóòòzöÙgïºë®^½z¥¥¥ùúúN:uܸqÒQi!éÍûï¿¿‘”ßÊ•+===«÷§‘—y{{7µt˜ I^ßýNúþGu²Axk<v—«ýúÉ:Þ 9¿oÄ;“1ó„އòŸßÁ¢ˆsG±ý Ly ÎnÍlßFiÓÍÍ¿››õù¥W.æ]Ôå]¼˜›z1ïâÅÜÔ¿óÓ*õ•Í…½§ýNCBoá¦B°+zN«²°àü×ÿgž ÑƦîXPZZ{÷Ýw=ºÇ í<¼j¯×ãÔ-0ä½ëÖ`³÷öðÖÇiAP(nzù=iÛ¸¿¾%j¸ËåfÕþ ¸š¢ÜÑ©¯1)n›  (‚B…  …  @þÙP\=|ý~APÆýW÷‚ PRÃW7„U?­V 5~ìóðí{ÞÝP{DÙ'/ny|¹g?ÿAs'ø¼©1¿ÕçÊH^¯T9öõ¹ÚѧÆQµ£¦sàCMm³µëvûÍnÁšœÓ×Ým(¼”ýÛ‚ÿ;´ê—Ásû|§¯¯¯ƒƒÃÉ“'  ))Iº¡Ü€Áƒ?òÈ#o¿ýöôéÓ¥»ªª*¥RYý÷—´_ ×}Þiâĉ¯¿þzxxxõû¼Òû.5rª±"€¬¬¬ðððŽÈ¨ö}-…BÑÔÒ `.$Õž<“+~¸ôzáÂ\ÔÀÔÉ&®r#foô{0èñÇ·øäi¬yþ7Á¡J ‘zùYL©ã/º-ÒѾÓMönêrm~RoÐ_.H—f/æ¦^ÌKÕåérгߦ†ãâÑËâ߃2. ôû¤1jWw(be%ͽS|ïªã/Ó¢^_–yÙÞÛG_V’žð³µ=×ÀK”ƒá§Ÿ~ºtéÒäÉ“ox§ÉŒsí/ì½\}Ï}nƒ$.þÚB=2̤óÿ{ôÃκž7ÁwDŸÆ_˜uþƒ® ›í?ÁÑ‚R!( ¥BP*… ( …BP)…ðÏNÅÕW·…R)üs¾âŸ®5¢TTß)(¯ž£P)¥kn+‚R©P^ýñúþ(ªëÛWÂõ‡zܺç=]í¡åŸ¿œ0oÕ¡•? žwß ïàÛØØ<òÈ#Ï=÷ܪU« Þzë­Ù³oüeÔÅ‹oÚ´iÁ‚ëÖ­œœ|ÿý÷O™2%44ÔËË«  à£>1bDõÅqª{ë­·†:xðàçž{®oß¾eeeû÷ïÿüóÏ;uªã%Èž={Nž|Cý¯­]\^$=¤˜š›z2ýØÁ‹o¸6M6² Û<ºeªwØêÿær;/ù§’޾ùœ÷è;•ö5?ß\ž›µoÞôFÎx™ß ×8|øpZZÚK/Yé+VÕYÕô°Ée>÷ó#˼õhü%Yû¸ú´èƒ45ªô¨Ò›°Ak“{6mÛ¬}» ž7ÁoTCÏÓ,[¶ì©§ž0`€­­í“O>ùÌ3Ïܰñ.]º¼üò˯¼òÊ“O>9bÄ//¯ˆˆˆÍ›7¿ÿþû®®®ãÆûþûïë»ÜÃÃcïÞ½o½õÖ;ï¼£Óélllúöíûè£ÖÈpÕ}þùço¼ñÆ /¼––æââ2a„zøÊ+¯DGG—––mlvéú0’\Œ“gò&¿ÊÒlÓ™i¼€^CÑk¨|Í7‡£Ú)Ø«W°W¯ÒÊÒ™ë¦5fÁÂNpñ|÷¦0C÷œüƒDƒA_Vª´wø;~óå[+òr:õé<ûÅskV8øÂãÓž{dß¹µŸ•çfûŒ½×Ê£fè[ÃDQT( ¤CFmÎ.5OÛ…F Û&üWµ¼è»þ7<“jÈ:~áÑ1^Ï¿Ï'´Wpppí»«kÖ¬‘>=WŸÚW½üòË/¿ü²´Ý©S§>ø Ióððøøã?þ¸Ž§2Œ¬Þ[µZýî»ï¾ûî» t¬úùóæÍ«oµJ7 s!Éåû£ßusó_4îY’õhoã­Ïòß>L»r©ÞÃ" / ïv‹àë'%¦YöþŠbeaþ…Mkºhl;¹p<¢Ëã+‹ £'<ôx÷Gfeïß²ô?Ò‚,$ xû“’K޾ùœ5äB ä¾}ûN›6­UÜDÄ_òŽŒ  þ#'â·;Ï>û, ¢(D¢Áƒ(ŠEDˆƒh6¤=¢Q¼ú£(íEƒhÜu(Š¢ÃÕ†¯nˆÕN6üsR-÷ô†²œ‚†×+ÐÜÚ7dN¤WHÖ½ÐÈ_ƒ¾ €RUs›Iå V¨øyc.$¹ø»LÔŽšnoã­Ó.íÎ_ŽÿT{¿BPÜìÛTÐíSF=T‘_1#ÆLó[{ç<@igß1¸oŸ½%=¤´³ÏØõkif:CUÍh‚ŸzAP©l;¹*›ÿnÍ )•J¥R©P( Eí ãÒNWUUU»‘‘#GFFF6õ¡rKŸsŒúHõß}sÀ¦—Íý(pSí]¶éðª_ê>&~#û z:Òóæ&¡R¡´3èËôU%J›&ßì«Ïã§cE½A4 zƒ¨7ˆQÔ ƒX¥—®tôÚ9ƒ¡êŸ=zÃÕsŒ'üs¨úNQ/µ£ÿg§( †*}mƒþê×õçú¦ ÿôPÔ z½t¾¾¼2/ùï†R¸ t}sÈœñžýLôUPª†¹ä2Ìß4k¯·ím¼µee/ùõº»!6J›šQA·ßx['ûN*ò4g—†~ò¥ô|¡‘h0zuŽï÷u¹c|ꦵµ/T*rßó\¶lYãO6ÞÕ2R(&L¸ås?AÛ>•æ_›PÇAè6¦ÈœH¾Ýš×²Úѧ´à\IþÙî&»É P)¡ª¹æeëòÇëërµiuBÀØAgw Ö˜¹Wís!™€qñ¶·¤ÅÕ*õnÃFp«Sƒ_Ì3¿Êü¼’K©îCoÓ—0”—«*Ê-ݵzÕ˜T«ÕÓ§OïÝ»·¥úÓÞýï–Ê’ëþõ‚ÿ!!OG¶0tpXZp.W·Å„¹°µ+LË>µqWíý ¥"pü°Oséîmþ^µ+Ì…Dd›m<‘~lLÏ;FæjocoéÕͶ“«Gè¨ýóq î9|ô±¥¯ ]¾Îmà°/<~Ó‹‹-Ý»ºUÏ…;v|ì±Ç||Z´¬‰´‚ðÏ—T¼FÝÙõi½X/ÿ¾-mË÷•…ù¢^¯°µírG¤&rRKúcÍJ³ Ž±Ãø£ î2;Òµ‡ V–ñð¿?3囌sÜ»ÞãèÚ÷Æ´Wl6T^÷Ô„ÂFÕsÂðOÞíìçYßUdBÌ…DÔRÑ qñû߬_ŸH¶u/=#½ç½Vc_ßïÔ¸¤Îå -Åø)¼.]º<öØcM]¬NÆ|š*mË÷i[¿ïýÜN]»¨ÈË)ÏkÂÒ•­ÎáÿRUZ@P*ï:pöxΚ?A IDATW9¹ÝìÑmBÖ…ïOýÕmàkn~w ‚ñwX”{,óÜFM¿gmÔíåã@ù2Î~¿Ûø£ÊÎ6øÁ[û?~—“w{ù°Ì…d]FýÛo¿!3žfü«af&€~ýZüi¹¦“Æ›w“­k{cÙi€IÇ«|¼R>Ò|a¯^½yäµZ-S•¢ Égÿý¡¡²Â¦ƒsÏY qºÞ?-ÿÌñ›_ûçiHQLýþË^O¿$…B¶.n¶.ÍýÀŽÕ+ɺrò« ¥"èžÐ³ÆwìÖÙä%º zCõÙ©›Ïí}!õÈ»Ž.½•6ô•…%WNW–傦Ÿu-´.«Ë0è lÔ½§Ž¾ùѶó­íÖ‚¹¬ËÕ7Μ5k.t‰gÀƒ™ç¿-Ê>T¹O4T*UŽvÎþÝ&¸ûO°Q·ÙØ]Cž6-ù—½¶ìûNë7#ÜÎźMn?˜ ɺ„††nÛ¶ ë7`Äp˜gƒë7H¥ÍQîzÒx7¯Ä p˜ç¬AÍ+¯–6G=j±Ñ£G›üV7µ+ òK.¥º  Àmà° ›®.¬?±á¯µmCŸ»_©®ûûÔ¦ÕÁcPAf(dÍ’Öü:xÓÃl;XéÓÉíDëXøŠÚùóçk4œ:V ÁUdMCñÑ œ:­ÑhæÏ·Àýi¼É‡÷*Ä|ðÌDâ^EòaXj¼Ô –LðׇBÛN®6:^9~ØRÝ13ó„B’ܶhúÀÙã -޹¬‹³³s\\œ Øü3¦NGü¯Òæ—™‰ø_1u:6ÿ,B\\œ³³³,…doÂ:Ì]›®>ügrÙiص sG a,8^²N6Î|»æÙ çÐ_z÷¯ûÕÇ»²îï^š’ÅÇKÖHz=ýò™UË.l\£rt žób}'jÆOTÚ;$Ç­(ÏÍT*µ‹»ækÿZ 5s!Y£ððp­V»dÉ’ÄÄÄ””­VkòAAAÇ_°`|ïx6R{¯ÙTäçùtÉM/¿géŽX—:—àqêòÞªžAèrÇø.wŒ—©oDdYÌ…d¥ÔjõÂ… -Ý óioãm¡ÌÝ;R¿ûB_V*êõJ;»®÷Më|ÛµO3T”7þ.§¨×—e^¶÷ö)˺¼wÎCÖ³~!µ.ÒâS%é¶æ{å¿¢$]*m¶ŠÔV1QësúÓ÷¼µ¢C÷ž*ò󪊋ZÞfynÖ¾yÓGnØ®vó ý÷7-oÚ'iñ©¢Üã®fÌ…E¹Ç¥‰Z‚¹ˆZ%QõcY¶]l;ºHÛi[¸üÛjwÏàÙ×=$WãÊÁ±4=íì>(˺,¨Tþ“£3ÿLpð…Ǧ=™ôö¿¤ùÂ:W{¾é¥wÏ­ý¬<7Ûgì½þS5ç¨ÉúI‹O¥ŸŽuñ¹½Ú÷Kd$ŠúôÓ±àâSd |ý‡ˆZŸàY Ž/}õÈó/ý¼©<'KÚYpö¤nó×ý}²äß*Ç´ÿ}k<¿ÎC'?zÓµÿ¡+¾³Æ}ððî̲ô?ö^]®^&ЧV,ÖÜ;yвX×C“ã>–vgü‘0àíOú½üÞ¥_8­H5I‹Oå&]8ô–yVŸºpè­¢Ü$.>E&ÁùB"j}ä–¼¤ƒÙûÿLývm·I3}"&Hëêýo €’K©úÒ’Î#Ã¥ókª,¸Rt!¹ÿ1Ò}‚²Ž? ë[í9ø©•ʶ“›¡²Ò£¥VEZ|*<<<ó܆üË»}ûÌqö"dz†%é™û.ø¤¼øŸ"Sa.$¢VIa«v4ÜmÐðÎ#ÇïeŸˆ ‚JeçéÝíÁG¤”j;CÕÕÜVû´!6kítA¥€öûºê‹OÛWï¢?¦ÂŧȄx™ˆZŸ¬=;«JŠ¥íŠÜl[7n!¡E’«Š‹ì½|ì½|”öÆók²qîäätéço Š¢ÁPUT¨PÙ0T”¯jìjÏDµH‹O-Z´hìØ±AAAr” ;vì¢E‹´Zmxx¸%¨â|!µ>™‰¿¥|ù_Ñ  µ›gïy¯pðñ žóâ™Ï–Šz½ÂÆÖmð¯Qc¥ókêöà#½ç¾zzåÒ¿ãTÚ9øOö å6pØ¿éÅÅWË4zµg¢Ú¸øµFÌ…DÔúôynQûÝp4¢úã2„µÙ{ûxëãê{ú.x§ÆU ¬ölçáÅ5‰¨á}d""""˜ ‰ˆÚŒÀÀ@åÙ™–îHMR—ºtérÃ3‰È²˜ ‰ˆÚ•Jàòïñ–îHMR—¸Š ‘õc.$"K²Ú).9HÔ†,‡1cÆ‚úíºô„Ÿ­ä—´<;3=áçÔo× ‚0fÌKw‡ˆn€ï‘%§¸ºÞÿ°¥û";iÚL²###7oÞ,­àm=AˆŒŒ”/‘©0‘‰‰"DQ4ˆ0ˆ¢(ý¿Q4ˆ°wvE%Ô""„Ñ÷e\ùZ÷ÃW¶;¹ö¢v÷´t÷eQž™{dŸ¦ÍFÕ«W¯-[¶\ºt)''G¾BäîîîããsçwvîÜÙÒ}!¢c.$"Ø}"gýŽKå•Cƒ_™¼p€Súj»|ß5£Ïú%[Û—Ì3mÖ¹sç3fÈZ‚ˆÚ*æB"2}ܺz:|öóùôܲ&]h‹²>s5 XÏ—ÜÜÜ|}}9mFDV޹ˆLÃ×Ã~áÃÁ_îÐýy¼±ÙN€è§8­D§¸ˆˆ¬ßG&+U^^þæ›oFDDôèÑCA="""Þ|óÍòòò÷F~mc¼jEÔØ®OÜíoo«lÌù„ùúCDDMÂùB²FñññÑÑÑ:N¾Z­V«ÕnÛ¶-66666Ö²_ocãìâïåðÙÏçS3J8ÍI¸â)È8d""j*æB²: ¢(ÂÛ 3§£ÿÍð”áÕÌL9ŠÕku:]DDD|||XX˜é«4‚q¼ž<ðz‡ÂÝÇôU²Ópr6}3Œ×`Ï\***­jà*ü§eê5ï#“u)((ˆŠŠE‘ã°~-Âï%ðôDøX¿‘ãDQŒŠŠ*(°À MãxæaùnÜö€,¡€»n{Ëw#lä¯A÷œÌ}yõÉÕÛRs *8S£8£BC'‘ù1’u‰‰‰ÑétèŒyOCd¯'˜÷4zëtº˜ ¬“"7p¢Þ† ÿoGA¨·8&¯(bÿ™¼×>?õß-2¯ÜàFA×AÈ3au""2 æB².{ö쀩“ 0׿œ ¦NºVÚ¼¤¢‘³ hÔ{& P"rÖµÒ-'‡“¯¼¾îTíEjlTŠ §çg¥óR\0Ii""2->_HÖ%==zö0kÕž=®•6/©h@?³•Ê™d¼Iç ¾ßýwí÷KTJá–¾nã‡zï?›§M+2î¯(-üý˗Ǽö¯–—&""“c.$ë’””@®g ëãéy­´yIEez¦°>R¹Ž÷ÔÅÂïvÿ}îïâû axo×ÈaÞîm¥«ÝýÍÛ…¹·¤.ɇ¹ˆšælZÑ÷þ}æRQý AìrO¨wguµ×NÕÏ}uR‚y:IDDÍÀ\HD•’^üýîô©5_d€.÷÷îâfWóÐ?¹ÐÇÝ~òhßGÌÐK""j.æB"º±ÔÌ’v§Mɯ±_nîÞqˆ.û:/T[•âÉqþ¶*¾èFDdÕ˜ I.i9e>µfÚ°¶:Þ´ìÒÓi¯ˆµõíæN÷ðÖthdSÕ3ÂýZÚ!""2 æB’‹ÚF1¨óÆ]i§u…§u…ò¦¥ÈqETU¡ ç/ av`PÞxNfš½3ÏxõUXó:â×€¦'GÀF+YH=‰ý[±+î˜Þ¢\˜SXñӞ˻Oäè 5¡¿—ã„Þ}»97©Á¿bí¹G""²Ì…$#i ­ ¤ €¼éðÙ¹×¶EÇŽcñ8ˆ…obÙ(Ì4kh†ñ®yñkàé‡9+Ð#äºC™qx{óCá•¢Ê_ö]þ=)»J_3ºùyÚß;¼ËÍÝ;šoö•ˆˆ,¹ddœB3î1ÇÜ¡  ßMˆY†GŸÀ¡ÃHLÄ-#d)T‹Üã½pñŸÃÞ ¯m€‡¦æQO?ŒÙœf Jªþ·ïòΣÙU†‡|ÜìîÞ%¤G'&B"¢ö€¹äU} ÍÈéÐÛ ÷ß‹uëñë³åBÈ<Þ_ÀØ™u„Âæ).«Ú²?cûá¬òÊš‰°³‹úžáÞC{ºšñM""²0æB’Wí)4#ÙÓáˆáX·§Ï˜¾åúÉ:Þ“{`pD û¶vN}F>ôÂO”Vèkrïh9Ì{xoW…¹î¿‘•`.lkVþt8ãóžzÚ¸¹ïƒC-©„•Æí¨¦7%c:ôÓ@nN#OŸ¹ì`3úßT-o–º˜ #^ì<¢F(tí`;n˜×­}Ý”L„DDís!YcZòêrùÜAÓ4jgúšóaÖ yã-/;GtàøÎµ¾Á×n¯wt´7Ôkd?w•’‰ˆ¨ýb.¤¶«¤:4m]kfk‡Š2”þ±«Ö+=ù@zòïÀAìUwé|{~¤Žˆˆ˜ É*ï«.˜d¢ÉBuÀ?w“­LóÆë¡AšO£ç`ôáЖOã6l»#Ä“‰ˆˆ$Ì…mÁ¬ñþ€“.YxmE“'“W·¤ú­3nÿñì_µOغ?£Î÷0$2¾w²ç/r£ó®Zý|ˆIV]–o¼Áƒ‘¦ÅžÍ¦É…™©Iwõ2ACDDÔV0’¼Ê+ [dÔyHÞuj2³ðݰ±Á]cei¿²ŽwÔdl_„/pÛƒè×ìfˆˆˆêÆ\HòúíHVÅü w"pü/Eq1f? ¹ªÔEÖñ Äȉø}#Þ™Œ™ï t<”ÿüEœ;Ší_`ÊKpvka""j§˜ IFµ'ÏäJ„.QDI ’Ï!õ" <…î3q¡™a¼ÑïÁ Çßâ“§±æuøß‡()DêIägA0åES•""¢v‡¹dT}òLÞ9ÂÍ?_ݰµEoL¸÷F¢«Ÿ,µêg†ñÚØbörÜ>;¿Æ™ý8™ˆªJØ;Á;#Ĩ‰pv7mA""jG˜ I.ÆÉ3yáÎ_ei¶éÌ4^@¯¡è5T¾æ‰ˆ¨b.$¹üv$«‹›ýSãäMHÖ£½—ˆˆÚæB’‹»}ÄàΖî…ù´·ñQÛÃõlI.7ù·4F{/µ=Ì…DDDD0‘„¹ˆˆˆˆæB²6£G€ÌL³VÍÌЯŸ>-'7»ÞÏ)ËB*g‘ñ‘5c.$ëbkk gΚµê™³¼½½ÍZÀ?ãMI2kQ©œEÆKDDÖŒ¹¬Khh(¬ßƒÁL% ¬ßp­´yIE7¯„Ao¦Š=6¯¼VšˆˆÈˆ¹¬Ëüùó5 NÆG+ в×E|´§Nk4šùóçË^®i¼É‡÷*Dù“°h@Ü«H> K—ˆˆ¬s!Ygg縸8A°ùgLŽø_åzÖ03ñ¿bêtlþY„¸¸8gg ,@hoÂ:Ì]›äzÖ0; »6aî$¬ƒÇKDDÖŒß;!«¶uëÖèèhN‡ÅKå.§ÑhbccÃÂÂä.TŸêã]9Oör/Y-æB²FáááZ­vÉ’%‰‰‰)))Z­Öä%‚‚‚†¾`ÁµZmòö›¤½—ˆˆ¬s!Y)µZ½páBK÷Â|ÚÛx‰ˆÈ ñùB""""˜ ‰ˆˆˆHÂ\HDDDDs!I˜ ‰ˆˆˆ`.$"""" s!Ì…DDDD$a.$""""€¹ˆˆˆˆ$Ì…DDDD0‘„¹ˆˆˆˆæB""""’0À\HDDDDæB""""˜ ‰ˆˆˆHÂ\HDDDDs!I˜ ‰ˆˆˆ`.$"""" s!Ì…DDDD$a.$""""€¹ˆˆˆˆ$Ì…DDDD0‘„¹ˆˆˆˆæB""""’0À\HDDDDæB""""˜ ‰ˆˆˆHÂ\HDDDDs!I˜ ‰ˆˆˆT–î@yyù’%KSRR´Z­ÉÛ >|ø‚ ÔjµÉÛ'"¢6€¹Èòâã㣣£u:|%´Z­V«Ý¶m[llllllxx¸|µˆˆ¨•b.$²°„„„ˆˆQ=5xà9ô…»é«d§áälú:.""">>>,,Ìôeˆˆ¨5ãó…D–TPP%ŠbØ4,ßÛ%p÷Ám`ùn„Mƒ(ŠQQQ²T""¢V‹¹È’bbbt:]àD½ Aþߎ‚Qo#pt:]LLŒìõˆˆ¨Ua.$²¤={öˆœ…ÒLJDκVšˆˆÈˆ¹È’***ô3kQ©œTšˆˆÈˆ¹È’~ûí7@®g ë#•“J1À\HDDDDæB""""˜ ‰L¥üò–îQ‹0™Fe~rÎÎYû-Ý""¢fâwðˆLñûä¢3qÙ;g¨=wè3ÛÖc°U&û^÷£nÞè޷܇þ£!rÔ$"¢ö‚¹È4•½SϨ‚¤eåYûËeN‡aC¡¯Bat§±ûìþ7݆y«àè,GA""j˜ ‰LFš24”ç5F¿wm[qf>‡c»ó8^YoŽïéQ›Äÿ€™Œ4eX}OyÖþì3d}îP<¯m€½Žÿ‰ñ2Õ!"¢¶¹È”»OV¨]kì4C:ôôCDüù½Lˆˆ¨íc.$2¥ÚS†Fr§ÃAcàÜQ9Ú&"¢vAEÑÒ} «rþx’kàµÆçAðõ¥Æ^(½\çù¥E˜ • ¾8ߨv” é¶Ûu¹½±µ‰ˆ¨­ã|!‘çCû8šªMµ=èõ=q´wîî§ Ž¾Ñ`ª>Q«Æ\HÔF”€S§Füp˜ËÃa.ŠÎ~žóÇㆊ|9»FDD­s!‘¨=»úÜmÔç{N›ªÍ¿“À'ðÆg* Â[3½Œ?–gìÉÞ>©*_kªžQ+Åõ ­ˆÛ­«ÌVkUàLãö“É«[ÒÔ­3nÿñì_-iªm(:³º iY}Gå[ïúPÜtëÏ4(Ä{^ˆ}Þ·‹›´§ªH—µã!—!ïÚùŒ1yLjˆ¨µà|!‘)‰U¥EgâêÅuÄ'*'?Óv›ˆˆ¬s!‘ÉTŸ,”ï9B _\ݰQ£sWŒ‰ðéð jfkUι{æÊr¤=Uç²·OrºLí5Âý%"¢Ö¹È4Œ“…²&ÂÆ¯€Ý$¶î=ÆlÌM|¦2ï„´ÇPQóç“Î7=ëÔsfÃ×Q›Áç ‰L£øÜ×6ÎÝe}ŽPVJ/÷Ñë컎¿¶K4$-ËÛû‚¨/·\¿ˆˆÈ|˜ ‰LÃÎûÖVš¥ÚeÈ{Î7ÿ µ?J/þ’ýÛÃú’t vŒˆˆÌƒ¹È4TÎXQº5pê1ÃíÖ+l;÷TæÌJ˜X‘uÀ‚½"""3`.$¢šÔ‡»Ù êxíMCynήG‹Ïm°`¯ˆˆHnÌ…DT•“Æãö/«þD4TåzóÊÁ×+ÚQÃ\HDuTŽ®Ã—wè3»úã†%)›r~Ÿi(˶`LjˆH&Ì…D–4zôhÙif-*•ëׯ_#Î:ôžåú‘ r4îªÈ>œ•0±2÷¸\ý#"" a.$²$[[[)If-*•óönì'óì|ÆxŒY_ýó'úÒŒìÓKS’£{DDd)Ì…D– `óJôfªhÐcóÊk¥Iåè>fCõÏŸˆúò¼}/æ]Ñ\]'""™1YÒüùó5MòaĽ Ñ {9Ñ€¸W‘|fþüùMºVaëìv˪Ÿ?)>»6ç' ù&í&Ys!‘%9;;ÇÅÅ ‚°sG`×&¹ž5ÌNîM˜; ë B\\œ³³s“[ÎýžwºTPªûÊ3öd%L¬Ì×6âz±É‰ˆÈŒø}d" Ûºuktt´N§[9Oör&666,,¬Ù-ØûÝ­êàŸ›øŒñ#(úâKÙ;¦º ^lç{GŸÛè8¹Ùu©­Eþ…ÈZp¾ÈòÂÃõZí¢E‹ÆŽtã š.((hìØ±‹-Òjµááá-lÍÆ¥·GØF[÷㱪$wÏüÂ+ê½. äïmÛÂÒDD$ÎYµZ½páBK÷¢ jW·‘qGWûŠXxrUå•3.CÞlœjœ_U˜b¨(xün·ÿgï¾ãk:ÿ8€ν7{ïMDB[ì±bÖ^µ)J©=ZTQ›ªÒR”jUªFð³•ØY"Cö’y³î=¿?Ž^‘å&¹çÞ›äû~ý^¿×Éϸžæ~óœg,Ýçëë«äÒõTÑA®„¾Q!!¤’ȨÅ*ã–_1 ÙÉÜØ+IÿU˜Yìæü”'†w563*µ”„BäFq!!¤Jt>1ërP m.;S˜ž|iD^üÍ¢·å§< ­ÉLð2Uv !„ȇâBBHUiš7·èyBÃÔ]vFZ™rsfVðÙäü”w‹wOô62…*(%!„¡¸¢B+ó®‡têxŠ•f<ý>íî"V’+-È*Ì çN› uåYÔ†Bˆ²Q\HQ F¨eÒfƒQ³Å`Þ GMþߨ¼¸«Eç)7ÐÆÐZ†„¢~(.$„(’žëx³N?4dg Þ¾|ûà«¢÷è‹ÒmuŠOL!„¢rBLËÊÓ¢ç £÷ 1²…âb÷4ÔªÜBBù8Š !Š'Ô³7ï~TÛ®Ì]UÌ4Ì4”Y$B!Eq!!„Ò¼4¡ŽeÑ¥ ‹¡.CBQ7´ß !DÁ R³BŠßø—¹'ÀV'R_”žUhTÎ=„B”‰âBBˆ¢°¹q׳‚ä'Ý—çnlýgÞvä»X„BäDq!!DØÂœÔ[³óP‘huC3ZåIµù+!„ùQ\HQF¤kÖå€4?£ õi~Ê“üÔ')O¥™å?%d õŸ?Ïh©œBB)Å…„…hjYwԲ03"?åq~ê“‚”'¯Jnè¬÷"(³™„¥ßE„¢zô»˜ÂF 2t:éÖ€-ÈÊO \4s@KWÝNæZ‚\î.-A®£nèípÓsçÎÅÄÄ$''«´Ð|177·³³ëÓ§•••ªËB!e¢¸¢ Œ†¾–e»í&ðõ¯/J7ÓLäþgÇ|·é‰DZ“wÆKNNNNN~úô©O×®]yÍ+!!áܹsoÞ¼IIIá5#yP@LHõBq!!D² ² "s\ÂÂÂöîÙ-ÔÕ?ÛÔ£–¹¥ª‹Æ‹¼äÄÔÇ¡vøùùÙÛÛ;;;ó”ÑÕ«WýüüXV]‚leÄ„ª£¸¨©¼¼¼7Þ¾};<<<44Tá黸¸899µoß~ñâÅZZZ O¿¢j[}e._¾,‘²ÇÚôì¯ê²ðHËÜÒ¦gÿüô·'^¾|™§¸0,,ÌÏÏ¡ë¤9jd+- &„(Å…DùûûO™2%::š¿,BCCCCC/\¸°ÿþýû÷{yyñ—×GÕ¶ú•‘‘Àº‹º”‡WÖ]¼"NäªÌ‡Ë—/³,ë8tœúÙÊ ˆ !ŠBq!Q;—.]òööfY6Öøt<<šÁ’‡nÄD<~‚ƒ‡¢££½½½ýýý{ö,s3_^Éêké€a ÐÈævŠÏ%9/þÅÉï¡òú @‹¨A! IDATz¶”€«&We>B-ƒl¾bBˆ¢ÐþÈD½dddLš4‰eYøôÇÑCðêÅKPÀÒ^½pô|ú³,;iÒ$•|iÉêÛs¶ÝBça¼…ÌíÐy¶ÝBÏqPa} ¯Â –A6ß1!DQ(.$êÅ××7::n 1o†÷üóæÀ­att´¯¯/ïÙ•ÀÕ×¹9&­ÃÿŽŒ“ÖÁ¹9TU_B!êŒâB¢^þý÷_=e5N£G¼ÏZ¹¸L}fA TRŽ!|f½ÏšB‘¡¸¨—üü|hàªÔ\¸¾ÏZ¹¸Lš*5S.;•Ô—Bˆ:£y'D½\¹r_c Ëbiù>kåâ2åiLaY¸ìTR_Bj¡ôôô·oß*0AkkkµZmŠÔ$B!Šöõ×__½z5=Ï:3é¾S67·077ëÔ©Ó† LMM˜2!B!Š”››»lÙ²mÛ¶I¥RZzŒ–®­¢gYirrBrrRPPÐo¿ý¶sçÎñãÇ+*qB(.$„ÔL×Fôж°bY–-,êèØ÷jë5PžY‰$71^ÇF¹o÷I 2oÞ¼~øa„uš-2±í¦mà¨à XiFÒ½7Û3“N˜0A(Ž3FÁYÚŠæ¾Üy]»¦»Ö¶úV mwm·ë˜ç¿;OœýêÐ9ŸÊKM ˜G0¤’.^¼¸oß>F jÜã˜MƒO`†–mu?bï>Àܹsãã㟠©•(.$|y>÷÷YÞõœÒܾ/ÙtíõÁzº°³C×.>"¥6oåÔw¤ý?jhÁÌõ=Ðq<º)c!ðêåÚˆÜYKÏÆ_| #äE´ßo­·jëíÚsö›ýbýOwøé/‘¾X@ý ³’ïÝj¹iŸ KNª©‚‚‚ÇŒUý‘ÊÉÑÚelzüÍ{÷î)';RãQ\Hø¢­¡3ªÕØÝ×w<Š~ø(šçèЧ?Xˆ‹Ç³@„„"9s?ã%¯2(³¾=Ç‚e!)Df¢ƒpë/Üú M:cÞ^èò‘auÕåøeiA~vÔëW‡v‡Üá:uþÛÀGB~ôó&R"ÎÑ021tiôhõçÖ]zÛtï+Ò7Pu©I5 -‘D¨¡¤†¤gÒ˜a„úúúÊÉŽÔxq]hi9iø–¾øüýqÀ=,Z†‹—”B‰õ²áý1Ë"8»æáÙuøNÃò£ÊØO¯,’ ¡†Ê²/@CÓ ~çOç~óH•åøÏÝÙ£ïÌykÒÀÈ?u¾€®]†³—ïÙtoþÄ‹¦Eÿ}B’+~²fáý…S­š[oÄ$ CcMcS³íî«Ç¼“üü|–e+ýxnR¼leUR¸;{ôÏFÝž2$`Þø”‡w¸K×FôÈMŠ/zÿÓ? ˜7>ÖÿtUr$¤BÆŽË”fݺur¦Pô) –-[^»v­è áááÆ 377†††-Z´Ø»wo9 ¦¥¥}ùå—ÎÎÎ"‘H[[»AƒcÆŒÉËËžžþË/¿ b=$\V ¡PX·nÝéÓ§GGGW4zLø%uWò/oZ_G`ãfLùT1 V²ë[D«Þ8µ¯ž(<áŠéâÜmë•ï?ÿý³Q­ÆNn?U…¯•»¿\êyóVÌ[u(z¦Õw?~pø/þ†¿‚UHzzú©S§Æ§£££Âb´ÝyX6îʹ}[<÷ž(ý õqÀóÍ«ä\*R±,‚¢3ÝêÐxSeJÙ´¬3CÍJ<»iÓ¦+VïׯßË—/¹óæææò'òÏ?ÿ899I¥Ò„„„]»v 2$..NSS€T*õöönÙ²åéÓ§ŒŒîÞ½›’’RVR©©©žžž&&&7ntqqIOOðàÁ¹sç ‹î^haañÝwßU¢¾V­ZõÙgŸYYY•ZGGǬ¬¬/^lݺµeË–wïÞ­W¯žü‰S\H©Ó–v}D1ÑÒ¸I`¥ÈÌBz:¸Âw3š5­h¿o[‰òWÑ¡m}x› ïý§6œzäSÓ:uMê:˜Ö­cRÇÞÄA[¤]Åbtvé²íÊ)+=rïÐý¨€U}¾®cZ·ŠiÖfB¡ðåË—¾¾¾“'O.õk ²"ÂB~Ø"-È×00l0k±¶…•8.&dß÷¹IñŒHToä‹vK’a ]Ü ³2K½ÈJ¥ùi)E—ªÖ÷CÞžº7¸½ E‡|˜×cÄy’ž6õmõ*ô¬­­­­íûíd6lX‰899q6jÔ¨eË–FFFQQQÎÎÎ"##CCC=z¤§§ÀÝݽGòúàW¯^mbbríÚ5YØ©S§yóæ»ÍÌÌláÂ…•(*€µk׎=ºÔ_²Š´iÓføðá;v\ºtéo¿ý&âô™¨Ù›V£ú•šO71HO€Ì,… ?_±%T¬ªÖ·4Z: ‘È{¿A½ „—_^Ø{ßê3Ë?=<Îk{·a?üâ¹¾ÿÛüçã“÷"ïÆgÄKYi…Ša¡oÙȦ1wœ4ù× =ù“Eåß„Ör@RR’¯¯ï³gÏ"˾ܱÞaÐÈV›÷›6ov`;€[טz´i»ãHß_Ì[·/ëÁܤøˆ?[xv)yñÚˆ×Gõº3kdnB\£ù«PN5з­UD|öw¿‡n<òFÍFÍÖ8>í¬ŸGf|s,xËaa±ÙŠJ6;;{âĉzzz¦¦¦K—.åv&,_jj*Ã0fffÜ&&&B¡ðòåÒß?òì±cÇ–/_^´k°TEß#'&&Ž5ÊÐÐPWWwèСÉÉÉÜy†a>|8mÚ4}}} ‹'Nà¢U77·ŽSÔÕÕ]½zµŸŸ_aa¡<…çP!©®^„”…8¯#pîöü€ þø~LŒU]2åg€¾|5éŠDºÅÿógÁ&d&$d&Ü‹ ÔiÛ›8Ô1©coR§®iÝ:&uLô´Ê[£«K·çqÜqnaî÷—7ýûúÖb¯å¦º¦©þ‹ äåå|x@@€™™ÙíÛ·ÝÝÝ?šxÇŽÅbqlll:uä,Å…D-ÈÞ«î.ïfeÅ èéÁ½1ÜÃÜ ¿üŠ]{°b©B‹©0 ¨o ±a`ç,×ͺ–ò¾/Î-Ì K K -zÒLÏÜÁÄ¡ŽIÝ:¦uë˜Ö©cR×ÚÐF(rW;»tÝõáËÛá·&³Äk…œ™Y\€eÙóçÏÇÄÄŒ3棽UtvK½“ê˜Äç&Åã¿ÁšIw®ïý.71^×®”ï˜bK)¶xªÒ¿­õ­ç)) àeTæË¨`wGÃÁlêY+&:¼{­è¡–¦Ž•žiSóºŒm:Õ¶yÍ>ž6÷CÒ¸–ø<2ãydFãº=mœí*¹:c~~þ¡C‡nܸѠA+W®Ü´iS©q¡›ÛûˆþôÓOE¯®Y³¦U«V¾¾¾£FÒÑÑ™2eʺuëJ333ÈbJyܽ{÷îÝ»÷îݳ··0sæÌéÓ§çççs£½½½7nÜÈ0ŒÍŽ;˜ššrY”žÊÈÍÍ•¿<’ªºñÅòo8vÿH©ó08¼ÌÃäƒ_~Å¿)X17Ü­ÊÄOÔðð4é$×ÍY_?¼óüNtjTdjDtZtTZd\z¬œoS²“S²“‹N:ÖjØÙÕ1­[ǤNÓºö&oÒ>˜—–“¶ä¯…õ‡:¾þ;JþJ¡PXì̳gϸᆕHPÃÐH×¾nêãó6RÞ1nä¡ah¬_ÏåÍ™ß?™À²¬$'»¬Å½-ÚuÎzøÝÊëw‹tK ŒŠ, T3˜iz62½ø~’×›ÕÌÉhP{›ºVº ÉŲþ°,Ëæ½ÍIM‰:“uÆÈª½Kû­J[[Ø™i·v5 N“y™ù<2³Q]ƒž6.£££Åb±¬_ÍÝÝ=((¨Ô;¯_¿îââ`èС%»ä}|||||âãã=ºiÓ¦gÏž]¾|™a˜ýû÷O:•»'44”[`<--MþY//^¼0räH‘H 33³   99™49iÒ$®0\˜XQ©©©*ô»‚âB¯Üñ±û¿–z‰ÇAôô OC US_ %BCÝäÞ|+?£ ¹}‹¢å)ļ}•••••‘›.Oj’‚ÈÔˆÈÔˆrîaÁÚ´·2v6LCª jõ;e‰D"‘H¤R)÷ÿœ’lj$¿´‘² ¾¾¾ãÆ+ÚÉQŽ»³GËŽ[}Àmβཛ#Nü"ÒÓo8{ €FŸ¯Ú½)Öÿ´P[·Þè)–í»••”ãˆO³^‡íÚà¾pM±,X©T"ÎrKÕýÛZß~‘*•~ðGã“ðô§áéÎÆƒÚÛ8XTuªx½–_ù‰ÍLzø*`qzÂíÛsÝ:ÿ¤Ê¥ê•ÎÇÓæÞ]†2/"3_DfºÕ1ØÞƵ"ÑaÑîv,Ë–5ÃÂÂÂÚÚÀÖ­[Û´iãëë»dÉ’’·Y[[ñÅ}ûöuss‹‰‰±··=ztÿþý¹«ÜB6ܤ“¡C‡ÊYH.àÛ½{·¥¥eÑòÈùxùnݺåääT¡þKŠ ¿N=ù“Ûÿ£(~÷ÄpÞœëó•~ÙTRßûØõ9r21~5Lm*ŸŽ†PÃѬž£Ù+¤‹ßF¥EE§EE¥FF¥EE¥FƦÇH *‹Ž¥ÎUé¥FLW¦!£N¯É 23^ÿö“r^€Ê?QC£ô…~Äbñþýûûõë×­[·r†j[X—\¯Gßѹå†V_Ó±±o¾v»<)0A“eïöÛ‘/kI b#ŽëMŒJŒJ‹ŒN‹NÉN–?M)¤ì“x6¶µ€Ç…®è¡ej€-(à0h”}ßRþ‚g%’ÜÄx;InNÜ¥3ê60®œé“R©ôï¿ÿ~óæÍÈ‘#+÷ŽIÉ<¼¦ÅK?ø^lÝÿs¿]}£¢) Üyû ômë&=«ðgÙ‡´ôì­]ÆÅ¼Ü›ù·œqáäïyÛDJpÑaCƒím|,:ÔÐИ0a‚ öîÝ›‘‘±víÚÏ>ûøþ¨ëׯ?yòäâÅ‹> ,,lèС£Fòôô´¶¶ÎÈÈØºuk‡Š.ŽSÔÚµkÛ¶mÛºuë ¸»»çææÞ»wï矾}û¶±q)4h0räÈÏ?ÿ\ tìØ1''' `È!ܲ8¥277ðàƒƒCÉ}±ÃÃÈÅâgÏžmÙ²ESS³¢«áP\HxT´óŒß>3¿3ïuuѰ†E玼äU6¥Õ÷ÒÁ§†¬ê¢÷§ð;>²*…P ´3¶·3¶÷¬÷~uèì¼,nbdj䋸g¢|tmšd$]’^°ha–ô°Ìb«ÈcÍ6m ké/Ÿ>Y³À¦[¡Nñay©IóÆËÙã¥|]VãÑ£G111K—ªé«¢˜šû>”e”v?ø­ia¼¢Ò4±ëórovª"'ªq‚¢3ƒŽg6t0ÞÅαÜQž›7ož9sfóæÍ555g̘1wîÜ&nkk»lÙ²å˗Ϙ1£C‡ÖÖÖÞÞÞ~~~ß}÷]FF†©©iÿþýO*sk) ‹»wï®]»ö›o¾‰ŽŽÖÐÐpwwŸÅ…„/²Î3~#«yI¶â”SßßÔµcEOK¿¡µ[Ck7qøÓÃãäY°Ð&vŒýݘûJ(ž~=V*•䊅:º±þ~ñWÏç§¥7öhøÙ’W¿ìð`Ñ4§q3¤>xuhO^j²]ïAõFMVBÙÊDz¬@ (':tpp˜2…‡ŽbE«ÁA¡ Ã@ PØÐmC'ù¹IŠJ°†ÑѺØéY½Ÿ˜ß°aÃ’oWuuuùånë¹²”|jÙ²eË–-㎿ÿþû •ÍÂÂbûöíÛ·—26CVÈ¢¥ÕÒÒúöÛo¿ýöÛr Vôþyóæ•:«Z!S').$|9õäOG³z_÷ÿ†Çq„ꤶշ,Û®l‰y[vôÊ"3*«½cG;Æ^úrÄüˆe 2Ó#NÒµuÐ46`Þºƒm¯Y™·§ v3­þ„YÉ÷nµÜ´[%áÆ¥æëv漉x²f:ąʉ ÝÝÝÇW-^"³`_Üçååýß †®\¹FðÅ_°,X–e–Ë‚ûîàýI–eÁ¢ÄA±{м»M îwJYnÜÜ@ʾûb•¾+¤`Á"¿ÍÎ-oe`‘ißȬ[ëO27*èµ°¬¼KÕ3%ŽÞßeŠßPl<ªlä\±¶ä°UîÎÒ2*-ß’}¬œBÉG>mMaϽ[ZéjŸ­OªŽâB—võJ( …B@ JÈ~äÎDGG—º]A—.]|||JÉ®ŽXöÑ…Æ{°GÙ½3ÛŒôÛ¢¢2ÉëÛßBBcJßïD$d:¹›÷mkef àè\Z˜@¤)ï¢??-¨!‘n:]ú^‹:šÂÍ-z·²ÔÓ¦è…/ôɾÔ3sRu”ª¶Õ·¤ä¬ä?x¢!Ôháв«K÷ŽÎuŒä§¢Ì"µÝy„_(ÃJ¥W̶ï3Ķ׀ȓ‡J>ˆDïË oÞ¼Yþ›eï³dÁàÁƒ;vTöÚÚéydF©A¡¦HЩ‰Yß6Ö&ú¥Ï¯"qF8ƒÚõ‹%äMV©A¡¶¦°Gs oŠùGŸ/!DX°ë/¬å;Ôiµql×Å¥k§Núå|éi9o"ÍÛv–ädæå‰ôôHóóT]´2ëÔÒÒ?~|£FTUžÚæ¯[qÅÎhŠ]›™÷ime¤ÇKDÈy{€‘uÛU×P§ÿ-þikk {xXône©¯C‹2ЧLQ€“O<{Ö£A¯..ÝÚÕóÔÑPÔZn ¦iljáÙõÞü f-Û[¶ïölÓò¶Û›µhwÑ´&KÖ«ºt¥+M:ÕÎή* r+ø0ÿí¤bݵOÝaãäy07)þîì1Zæ–J¥’B]û~CÍÛÈ·ÇNõôôuÆ«¸lÙZ‚î½[Y–Ø^\±òsâãÃŽšõ†ðš‘Z ‰Ézõ¾³PKCУ¹…w++Š•‰>kBHUIY©ƒI³³.ʶHV¥/=Ã0æ­,vÎ}ñ7Å)u9hU‘m…gkk;uêÔR—@«(Ù >•Ðn×1¬D’òðNðî’¼<«N=«^$5ÄÝŠåŽu4…Ý›[ôn©Œ^«ÌäGáK$™u=–hêTòŸ©::}û]g!E„*DŸ8Q/ݺu»rå QdG Þ%&hÚ´éGoT8®¾É10¯RPÅ$Ç ­¯€´«ç©¨ÔH1\¡››Û„ ´´´>zådE„…ü°EZ¯a`Ø`Öb€½;{LÝ¡ãÒƒ›­,e4$#š·î?rrôéßjj\øäUzDBŽ®–°g Ë^-,øÙöúÁWÀ²’Â윷AâŒW #th2ßÚuÿB”„Þ#µÓ³gÏóçÏO™2%::ë7ñƒƒÃþýû{öTÙÀù¢õÝ]ÊŽ— ¦òúuÄ0ns–ïÝq⑞~ÃÙKʹ÷îìÑ,˲‰¾£s“Eß87,çæÚŠÿw„ðƒâB¢Ž¼¼¼BCC7nÜxûöíðððÐÐP…gáâââääÔ¾}ûÅ‹ó7ÇSNµ­¾J“Ÿž¼kc“eT]õRê<úŽÎ-7ì-ÿ6µZ¾G=988h8ˆ“”–cAn2Ãú÷ï§´IÍFq!QSZZZ«V­Ru)”§¶Õ·Šoý/òÏ_%¹bV"jk×2Ϊs¯’·IóóRŸÜ“3MV"ÉMŒ×±±ãVo¦ˆT‚½½= âssó$™B %ä˜öœe¥YY¥oßLHEQ\H©~‚vmh¾v‡AýòÓÓ ³𥘗š0o|—ã—µÌ,=ø½ê ’ZHCCÃÃÃãÎ; aÇlݦ)!ÇøÐ_´nÝZ y‘Ú€âBBHµÄJ ¹M#M#î8æü_ñWÎÐ2·løÙƒäŠ]éê‰ãbBö}Ÿ›ψDõFNI¼y ÀƒEÓœÆÍxºîK®¿°ÔÕž›,ýöÕ¡=y©Év½Õ5Y™µ&êoíÚµ^^^ožï0²òÔ3mÂk^1/v§Çß455¥åˆ¢Ð|dBHõÓpÖâÀM+5ÿÍ™“y)ïÆre„¼ˆöûÍãë­-7þ ÒÕ‹9û‡ìþR/½ØºÆÔ£MÛGÚøþbÞº}ý ³´Ü´OÇÚöÝc,ûrÇz‡A#[mÞoÚ¼mØíÜé„—š¯ÛÙtÙ†7ÿP·")®gÏžÓ¦Mc¥…Ï/Š >˜›¡øò£/€œ7‘qŽU/îþ’— 2ÞfE„y|åË-ÑÇKùeXÖjÏ g.bD"Mc3iA2jKª›­[·êêênÛ¶-êɦ¨'›´ôÀJ•8ËJóÅ Ü”gƒ;wŽ=ZQ‰Bq!!¤Zhj™µjoÖª½U—Þ–ÙyfD"mKÇOÞ-§'ÔÒ–¾‹ÛJ^âØJ­ÎˆD@k“2hkkoÙ²eÖ¬Y_ýõÕ«WÓó¬2“î+0}ss ss³N:mذÁÔÔT)Bq!!¤úIú÷ªI³Ö"]=ù©Éš&fÌZzFþùkav7E’+–f¦s÷—¼$ÔÖѯçòæÌL`YV’“-iæçÉr‘­ölÞ¦“œ«="ãìì|øðaiii LÙÚÚºö¬6E”ŒâBBHõ“xûJø‘Y©„´Ì,Í[ @×®NÃÙK‚÷lb%†¦YëÖ]{s÷—¼äøÉ„FŸ¯Ú½)Öÿ´P[·Þè)–ž]ÍZ´»¿hZ“%ëßeS‘Õž )‹¡¡!m/Dª Š !ÕOã_—zÞ¼UóVŠž‘-CXò’Ž}óµÛ‹žq_üM±§ÊYí™y&„Ô<4™B!„BHakk /9QÕ)Ž+W>>~~~Ü ÞêƒagggU„òBŒ•²,˲)+•²R–•JY‰”;c$Ôa†ÉÌ)À²}ÛuËxðæÏ#šFƦm´Ì-U]|^ä%'¦>PB·Y×®]ÝÜÜÎ;÷æÍ›””þ2’“¹¹¹]Ÿ>}¬¬¬T]BÈÇ1•[îŸTw{?•Ï;¨Â’š!øÏ›·Ö-ç±iEŸM0,øúñÑÚð»ˆë6ëÚµ«ª ¢.æÏŸÊî:CáõB ÁŽæê^ú|OÚ«¸ =Èhp_ÜÛA}º¸ø@Ýf„jâBBˆb˜5tú×ê›_ :yCÞgLAï:¬–ÐÊÊjâĉ<ŽBˆh>2QSyyykÖ¬ñöövuuexàêêêíí½fÍš¼¼¼—†5£¾"­®&õÜ:CS_Gžû ÛZK­õø+!„ ¡ñ…µ”š/ô÷÷Ÿ2eJtt´òrppØ¿¿———ò*KÍ«oFT⏻“#˹Gê ŸïS ¯!jÆ¢nè=2Q;—.]òööfY6Öøt<<šÁ’‡9ª‰‰xüEGG{{{ûûû÷ìÙSñ¹ÈAV_K [€Fž0·S|.É1xñ/N~%ÔW*‘ÆÞ ÎMÍ*çVWTЫ.…„¢V¨¿°–RÛþÂŒŒ ww÷èèhøôÇü¹`xX¾ÛáwÆÁÁ!00Pùû1ÈêÛs&†ç‘¬?-Ç¥Ã੾¬Dú÷;N§G–»¨2ƒüNÒ:ŠÍT;Ô_Hˆº¡ñ…D½øúúFGGí!æÍá=(À0˜7n £££}}U°0W_çæ˜´Ž÷ #À¤upn…×—•²¯ÎÞ;ÑwÅÿþø‘ (lnIA!!„¨!Š ‰zù÷ß`ô”Õ8Œñ>kåâ2õ™PI9 „ð™õ>k`Ùˆ‹Oú¬¾8ww±Ej„Z6­\ŠÝþ:7©°µb²&„¢P4¾¨—üü|hàªÔ\¸¾ÏZ¹¸Lš*5S.;…Ô7êêÓ{[O%F;/Ð5Ö©åg^»w?Tv^,Íÿ)ñÚ j†rB)Å…D½\¹r/MÊaiù>kåâ2åc¢I9¸ìªXߘÛ/|ÿLxôªØyPà2¨}«9 ìÍ0öûþšt;¥ ¼ù(„BTˆâBBHÅÄݹ·åÏØ€àbç¡À¹_ÛVs9¾ßÒC |6Õõẟ•SHB!•@q!!D^‰OÂ|ÿ|sóyñ Sß»U«Ï™8Û¿$x7yÈÔÕ®ýòQX7‘÷RB©,Š !—ü<òÞÖS‘Wž¿À0ŽÝ›µž?Ĭ¡C© "Í^ÛfŠ´5ù.'!„ª ¸ð%êÚ³:]š¨ºÊSSë›soë©×¢Ä"s›´ž7زi½òž0:¬câ¢ÜA”„B*ŽâB—Ô7÷m5w mÛ†ª.‹2Ô¼ú¾ ¿¿ý¯WgXiñˆÐÎÓ­õ¼ÁÖ-‹¯AS’@(¨ß¯ÛðÎEOr«BQ7¾¸íþdÿy¿1mÛ6ä=Z Å™³xú ññÈ̓ŽÌÍáX `ø'ÐPF;WN}GÚð£†ÌlP߇À£›ÂψNz°ãtÈéY‰´Ø%ë–.mæ±m'oí ìÍ»®ÿôã÷BQ´^-¥œ}ðÿxîÎÆܱœÑÃ…6W/Ê›‡D‚»ñ—Ôs„s}hh 5 ¯Â‘”gNA_ÿãétí…*ïÇUéúþöFÞ,¸¸°çX°,$…ÈLCt£ IgÌÛ =ùv¶ãÒ)Y߬ؔ»þþ㦴PRì’eS§Öó;tr—·¬„Bªê/$<âºÐÄ)bïù R|_ÚÎÝøË6ÖX±}p)6w ÒPX^£Œú¦lx̲À®yxv¾Ó°üh%÷ÓËN|ûhÏ™—ǯIò ‹]2w«ÓjÞ`ÇîÍ”±3!!„Õ¡}ðD:Zͦx={7ÈoÌF¿1cï) ƒÐ0œòƒ®.|7 ØÚ`ð@hk) #ùð^ßÒ0 ¶ÅÊãÐÑGàMÜ÷¯p ┌Ûë;Ö}qàáËÅ‚B;¯Ÿ óûʱ‡…„RãQ\Høå>¶»ŽYñW› ‹–þ9CÂÚêc·* ¿õ-›exO€›§*ðT^zöÝïNí¶èé …¹l‹gTϺ‡ïôáÿ¬uònE!!„Ô~•ìB“Q@´ôø)tìPÙÒ)¿õ-W«ÞðªÄ ƒ¥Òhö7õ8ÒåËG?üS“Wô’½y·“GœÿÆe@;F@!!„Ô"4¾|0Eùª4/>J_Q¹¢öÔŸ¨„‚¿q‡¶õàm‚\7O²êì®kŸŸ%.zR߯´Å¬ ?é$ X0B!ÕÅ…D-È¢%Wëq¼¼å怎ãI%ë[.-ŸF\º‹oÝu߯y£kaÜbf?·‘]…šô;Bj/ú Õ™–òò COWÕEQ=q6èËusˆ8>Dïªc­cjà1­oã1ÝE:´I!„Ôvµ {¯:YRǬ­…ׯáÞ˜·¢ñ¢’õ-WlØ9Ë{ÿéÔ¿o9Ðt¢E„„B8ÖRü­e]RÑÕžKªÒH»&Âÿ®*$.œùêg…,óÎc}Ëõð4é$ïýá¹I-föWx1!„T_~Šóžì?_ê%DH}½qæ,þ>ƒÞ½ÐÀµòé(¿õ-[J,.„†&ºä#yB!µÅ…„_¿þÛÿ£(…EHÜàí…óþX°ŸÏF÷®þ7‘–eŒ¿ÿÁ´)06ªjFrã·¾e¹]Ÿ#'ãWÃÔ†§L!„Ô|•ì3 ,[Œ~}qî<žâñBWöèãïÞ0‘o‚®"(£¾€K¿¾;ÐЂU]ôþ^ãaçÂGV„BjŠ _dg|¿E€fMЬ éËA9õýí ©B!Å…„?¿þÏÄÙ¶×ö™üF„j£¶Õ—BHÍCq!áKÝ®M=¦öQu)”§¶Õ—BHÍ#PuHeâb§ê"(Um«/!„š‡âBB!„P\H!„B8B!„€âB¢nºu뉉JÍ51@Ó¦M•š)€ÿꛣÔL¹ìTR_B!êŒâB¢^455 8D©¹‡°±QÁr\}ß*5S.;•Ô—Bˆ:£¸¨OOO8zR©’²”Jqôøû¬•‹ËÔo7¤%å(•Ào÷û¬ !„†eYU—÷222ÜÝÝ£££áÓóç‚aøÍeá»~g ùÍ®Y}{ŽÃäoÀðü—+ÅOËqé0TU_B!êŒâB¢v.]ºäååŲ,l¬ñéxx4ƒ¥¥â³ILÄã'8xqñ Ãøûû÷ìÙSñ¹ÈAV_K [€Fž0ça%Ää¼ø'¿Gb4T[_B!j‹âB¢Žüýý§L™­„¼öïßïå奄¼ÊRÛêK!D=Q\HÔT^^ÞÆoß¾ªðô]\\œœœÚ·o¿xñb---…§_Qµ­¾„BÔÅ…„B! ùÈ„B!„Cq!!„B(.$„B!Š !„B@q!!„BáP\H!„BŠ !„B‡âBB!„P\H!„B8B!„€âBB!„¡¸B!„B!„Å…„B! ¸B!„p(.$„B!Å…„B!„#ªô“ Ã(°¤æaYVÉ9R›$å£6IÔ µI¢n¨¿B!„Ué/äœóóSH9HMÒÇÇG…¹S›$%Q›$ê†Ú$Q7\›¤þBB!„P\H!„B8B!„€âBB!„©ê¼B!•PPPðû¼xù2.!!66VáéÛÚÚÚXY5rsûdèP …§Ojj“Bˆò=|ôhëŽIÉÉüeûàÑ£óþþóæÌiѼ9y‘€Ú$áP\H!JõèÉ“_}Ų,l¬ñéxx4ƒ¥¥â³ILÄã'8x().~ÅW_}³fMófÍŸ ©dmÒÒà‘'ÌíŸKr ^ü‹“ß#1:™Ú¤Ú¢ñ…„¢<999¾Û¶±, Ÿþ8z^½x XZ«Ž‚O–e}·mËÉÉá%#RÍÉÚdÏqØv ‡ñ0·CçaØv =ÇÚ¤Ú¢¸B”生_Rr2ÜbÞ(aG2†Á¼9pk˜”œ|Š–2&¥áÚ¤ssLZ†ÿ €`Ò:87µIõDq!!„(ÏË—/`ô”õëW Àèï³&äC\Ãð™PI9 „ð™õ>k¢V(.$„å)(,€®J͵ëû¬ ù×0œš*5S.;j“jˆâBBQž§ÏžàkLaY,-ßgMȇ¸†ÁӘ²pÙQ›TCB!„€âBB!„¡¸B!„B!„Å…„Bjµ>>>}||T] BÞQmƒTñ>x´K7Q7üµÉ–-ZˆÅb÷Ƈ l`` À” ©„ò¿xúz{Ï™5Ki…©œ’U …zºº>ýûwîØ±*Éž£%—•®ºò5£Aª2.¤]º‰ºáµM>{›/Žñòåé¿ÿþtüxŸþý%lwAªµˆHL‰‚¬Z†îÝŠ_ŒÂôY°¶ÆÞ]ÐÖª\}½½K=ïÞ¨QåT”üüü´·o­äXЇ«˲R©4;;;üõëç/^<ñBRXØ­k×Jd-PÚ’ãÕЛ,õFA>æîBûůƄbY_X8à› ¥£Šòñ¦ö4H•Å…²]ºëˆD‹ÛkkÛ‹_˜7……·ss7¾}•L»t“µI-={ûƳ -ÛhêÚ(0}©4?3é~zü͸àƒ{ü1==}üر LŸÔ@Žu1m2ví…ïv4m s³÷—òó±æHY¬^Qé €Úv 6 òõ‘«˲'O:ðóÏ'OªÜ×ð?ýU‰§j {WŒZŠC_ã§epk«÷— ò°m¤R|¾§¦…¨M R5Évéž``pÏÞ~¸¾>A!{‘h¸¾þ={û ´K7)‡¬MZÖáÑ÷‚¹ã@Å…M#«öuš-rí¸ `NüñGØ«WŠÍ‚Ô@C‡ YSdfaÓ÷`Ù÷ç÷ü€Wá˜=õUU4õÄ0Lÿ¾}DEE©º,5SŸÉhÔÙ騻àƒ&yx-¢^büWph ºÂ©Ÿj× UÓ_ÈíÒÝBKk£™™"S°ÑÌìY~þÃääS~~cFŽä?ORÍpmRß´©c‹•|oobÛÝÚuB|ÈÏûܰn¯y‘jOÀ`é"Lš†€{øûøô€[·qÊ;b@?^3øèÑòÕ« ÷íÚeddÄz´}×®bPƒ”©hƒTM!·Uv3MMefÊeWé]º÷ýô7{ßO?5iÜXvž¿‰Bå|q?r¤ºOÝR+\ÃÐ7uWNv"Mc-=»z´gûvkkkÍ›5;r쀱ÿ}Ë)??ÿÆÍ›»÷íЫ{wî¤üe(Õ•k׮ݸakc³éÛoÍLM‹^jááa``ðèñ㌌ CCCîä…K—ô¬Ñq!€.Ãqï<\Äîy“‰/Bψ—¼Î]¸°gÇK ‹Ð°°¹_|ñ¿«WmllöìØaei;eƌdz,[´MRƒ¬tƒTM\Èm•ÍӘ²pÙUz—îGwéØÀ£'O*7t”³võêõ7øzÕªo7múèý¥Îäz›?:GIþùS„k SX‘–i^vLBbâGã²Ú˲¿=zìÄ –eë×ïÒ¥ €ˆˆˆs.œ÷÷=r䘑#+4å¹Xƒ¡¶ªF_~‰Sø&O„{ñ¿*çMLLÉ“©©©²c}}ý93g®Y¿~מ= píÆ!ƒÕ­S§Ø#³g̰±¶Ð×ÛÛÿÒ¥'ÿý¦åº.ùøpM‘a˜AܼuëêõëžçΚÅ}W”,´-((HIIá:]<Ûµë߯_EËP*ÿ‹L?¾Øw0‘HÔ±}ûs.ܸu‹K*77÷ÚõëîÛÙÚV¢.ÕÃ`Ú&,ìà{0üK4hÍW^ çÏ·´°PßÉ €T*]8o÷ëÂÎÖVGGG,hél¢Yé©âõ «‘ÌÌÌ_kïé™™™yøÈ‘vE+THBbbýúõ$%%9ׯÿÑûË™-øÑ9JòÏŸ"Ê'Ê;‡´¬¶wü÷ß?ncm½tÑ"ggÙýÁ!!¾ûîȱcšÇ “¿HÅ µUõbb‚‘Ÿà‡ýк•¢R•ç3÷l×®s§N×oÜX·aƒ‰‰I©C´eã·ìíp½×^GDp¬[Wv'wüúõëb)´lÑ¢åG‘Жac##ggçžÝºuêØQöG‘üe(7?¬Y“&¥^íÚ¹ó¹ dßè·nß‹Å5û%²Œ‘ÌÀÑõЬ+5puåda6lð~n‹ ´¿~©AVºAR\XÜ¿%€pùþñJuæìYîëóÌÙ³ŸÍ˜¡‚‘¯dÛ‹‹ÿõØ1o×­+ÖÍÖÀÕõÛuëfÍ™søÈ‘Î:Y[Y¡R¨­ª—·é8ùç»ão7aßnhU~yšŠroÔèú,ËèÛ·üÁUZZZ ÿ±#‹èè¼_¶„ëB΋U¶†¶U,CvN==½R¯º7nlfjúüÅ‹äädssó —.ikkwªì ÆÕKF Îîw¼{¾=ÍÒ'N¨5ÈŠ6HZ½S^¿ùfã7ß=(‹lLý•kצ̘ÑoРy ÆÅÇsW7¬[×ÈÍ À·ëÖ¹—+V!å Þ –]ânKNN ÎÍ=täÈäéÓû 4dĈ_}\2M±Xüõºuý ªJñˆ¢”ÚöΞ?/‘H|úõ+õÝ«µ•Uÿ¾} %’s.pgJm-²“¥6j«j„e±aRRñÕJXZ 2 ?PZæ‡533ðϹsÙÙÙåÜÌ}çÉvôá¾ürsse7p‹…éê(o»*–ûÎÈÈ(õª@ èÜ©˲×nÞŒ‹{عcG2&–Ö$,‹=óñ6óöÂÌ1¡ømƒªËTjmÊ«i“&M›4)zP¾ó.|·e‹±±±©©ipHÈÞ}û¸ó²µŒø  073+9J,/\²äØñãNõêMž8±Ÿ>AAA_.]ú2(¨Øã²yXü•ȯԶ÷ðñcžíÚ•õwéÑãÇòdQjƒ¡¶ªFþ8…;5];cé"0 Nþ‰‡rýãVÝÁC‡²²²Ö¬\ÙÁÓ3%5õÇå…¤‘‘êý÷ŽŒ›ÿYdå¶ðˆõêÕ“'k¶èúx•UÅ24pqpïÁƒ²nà&^»~ýâ¥K¼zö¬Ji«‹s?áÑÿà3 íúcÖV0 ÎîÇó[ª.V Ô +Ú é=2_>¼iýz÷Æ#£¢fÌžý,0°r锜-¨§§·uóæòŸ277;jT±ùS~þ9<<|Ø!“'NäδhÞ|éʕڴ~}ÑÇ‹ÎÃ"ê‰Û»™:Sª:uꈑo‹çRLEQ[åKH(öþˆ† 0i"4÷Àð¡8~6áà(ãu’¢Ÿ÷÷ï×§““Óg3f< ¼pñbçNZxx”¼™eÙ³.èØ¡w¦[—./^¾ô;s¦«+7õò¯Ó§pk–ƒ›L••UõÍÄ+]ŽwïÞ?þùða· dKäääîõŸ‹³³µuhXXl\œ­-×Ë^³½~†#ëP¿† Û£ß4œù»çã»ËÐU›íß©AV¢Aª{\\PÐ=66Ÿe÷YX .ñë/¤  gll‘袭­Žšm5;mòdîÕ›q‘ã )9[аR’eÙ‹—/à^ç4kÚTWW7ðùóÜÜÜ¢+jVzQš¼¼<|8B¥îÅXqƒf>ŠÚ*/Äb¬ùXµ ÿýÆžü)îãu¶íIJÅUI~ÇîÝe]š9}º€avîÙc``ÀíÙhbb2sêÔM[¶lÛ¹sïŽE›ßÆÍ›­­¬Â_¿¸¿¾“Sï^½¸óÞ^^ÿ»zõʵkééé.ÎÎÏ_¾ |þÜ­aCo/¯ò Ö´I“»›}}[4o>pÀ€ªÔ±Òeàtlß¾{×®ÿ»zuæÜ¹MÜÝMLL’““ƒ‚ƒ·oÙÂM`¦k—.ÇŽÏÎÎþdèпïyn6¶Í‚†æî†HãÝÉ‹ðä*¢ƒqp>Û¦Òò Y…©îqa •&&+SS¥¤xjk[ ߯9œÇ²S“’¤Àêèþßz""a•ÖIVÔ Í·oß¾MOpà矋.<*‘HX–MIIáâWN¥çaÕxâŒWþC¤Ò|çvß›Õé[ÊՋôôìÜ{ù`¤­¥%ÎÍ‹ÅeM(9´™oÔVy±uÞÄ`ù]fBS+–búgð¿„ŽíÑY®n†R=¾¬KÓ§L9wñâ«ððÙ3gÊBü®]º\»yón@ÀÁC‡fMŸ.»™[zÃÈȨ¯·÷Äqã44ÞÅ "‘蛯¿>ò‹µ:çIDATtäÈõ7=ybff6tðà1£F‰>¶NÙÌiÓ233ï?|˜›—WůáJ—Ã0ÌÂùó6hpöüùg ÃXYZvéÔÉÔÄDvO×Î?Î0LÏ"ûLÔT–#þ5>Û«÷j¡¡…Ù;°¬/nüV½Ñ¶øoGe£Yé©îq!€i††çrrnçæ~žœü›••ì[bujê‹üüïÌÌÜ”»>¶œÔíOF®{I ¤½}[ô¼«‹ ÑÿÍòéÖwhúEäã ¿6°h¥©ó~·T’vgÀºxúò°³³ {õ*úÍÙ ÅDFG¨Ž‹¨Q[}ïâe\¸ˆÞ½Ð«Ä2õ0y"~ØÍ[áÞ%V2û(yâøýú è÷ÁV{ Ã|µbE…RÓÑÑ™>eÊô)S*T+KËï7n,¿xòÿ)R~Š¥S2Y†aJ~Eq¶UË–Üìœìæ)\?‰ÎÃÐiHñKuaÄ"]ý‹Ñ 5Œ?²0k)J~òòœ9ùÛoò$%C ²|Õ .;ÍÍ;ÇÆþO,>”™9ÁÀÀ¹œœŸ23ûëêN¨ò›þZ‚[úœeÙ5«V•µ #‘‡µË¸´˜ËI÷^ß[Ñ óÀ»?¢žlÊy\¯åj#%£¹‡GØ«W·ïÜ)+.ü÷Îî¶²RPÈj>P[}¯WR"B™Q#0j„KCÊäwæ ¯Z°laÇÁè8¸Ì«>³àSæ2¦DyªÒ «Ç|d‘h½©)€U©©……±……Ÿ''Û‹D¾ææêÕ)§–¸¯]]ÝzŽŽ,ËÞøPÕ%ªæS›o…"½·ñ7_àÎ¥Å\N;jjß˲¾’¾§ûöî- ÿþ矄ÿÖk-*>!៳g…BaŸÞ½‹ž/ºÿæ©Ó§KMY…ñ"µUR¥§§_»qÃÐÀ me÷; DªØ «G\`¤¾~o]Ý–ý,)iFrr†Túƒ……ñÇ6ת帱eYYY܃ðÃþýÅæ¨J$ù¶ë&-=»ºÍ—ˆ|²17+*_~o¹¦®M½Vëd݇|³¶¶;fŒX,^¶r%·ü½LpHÈÒ•+ʹãÇŒ‘-jÍ CäÖÓ—H$ÇOžüå×_‹¥Y¬Á(µÕ꫉»{w%m,®žÎùûtëÚU£V rPWÔ «Ø «Á{døš™uÌË È˰Ôظ×ú¯¦ŠÍŸêÙ½û‹  ó.Ìœ3§M«V¶¶¶R‰äMllàóç{wì077Wuy«‹zƒÓb.¥Å^ X F )Èrí¸[¤i¨Ì2 :477÷øï¿Ï™?¿«k)ËFFF†½zÅ0̘Q£>:Tvs«-®ß¼¹|õj§zõ¢¢£322–,\øÍ‡Ãe8ᮢ¨­V_ÅVª…F~òÉÈO>Qu)È;Ô «Ø «M\ÀB(œmh¸&- @7%β¬¾ŠÍŸbfî¬Y-=<Μ;÷èÉ“[ÿþ«££cgkÛ£[7=}}U¶ÚaêµZ›ya@fòCöîs Ì•=1–a˜ cǶo×ÎïÌ™§¡aaÀÊÒ²¯·÷€~ýŠn¾ `æôéùùù?~ÔÈÍmÂØ±E7}wâ&ÜUµUBQL¥Çqóm+·2·íU’£c…žJ‘HºÄÆ&H$\54.ÛÚjWpίED·š) ÷ï«üajUo“m‡¿¬ÐSqA?E=Ý À½ç =Óï‚SÔ‹+ã2“î÷í·UÜbŽÈC}Úä»Mÿ®^TrIеè·Ÿ:Q·6ùÛ%#íj“ê„k Õf| ÌNNNH~²°°‰B Ö¥¥©ºP¤V+ÈK ù…;~°T*©äÒå¤Vy·“ai³…x”˜ø>kB>Ä5ŒäâËÒó‹ËŽÚ¤ª6qᾌŒKbñ##=½ææ ðCFÆÊn"BH•±áK r“\<}5u­Å¯¢ŸùªºH¤Ðà–® Qj®Á!ï³&äC\êÔL¹ì¨Mª¡ê>ÍÏÿ:-­¹–Öccµµg˜“œœ!•ªºt¤6Š9ü6îºMÃÿ·wçaM]ÛÀ×a$¨L‘Q¦Ëà8`«"(ÔJeÄ÷D,ÚZÛ: HEKoµµ_õÖE¸Š("µŸ×k¯Šµ µÎEyL"ʨ¢‚€ pÞç6 C’È„¬ßÇxr†¸ôìì³×^+ ,æÙNù€¨-=öæå-e· ©º±T­Ò…ýßÕÕÿ:ù×¥êŽ Œ´Ð¥¨\ÿ.>¤øëÒH¥ ‚~akW×ÊW¯qhäHÆŸ ¿Ò×Ë`<åñ¢_¿VnóÐÔÚPTu—޳…S8Œ0šj:z9<Ήæw6+¹qHµøùd³¡¸bãAsËHb㡸d$›@MmD¨;*&Ëò!ùk åÿm…ì‚䯡,0&UÓ ènzýúIgç.k¡g-‚H`³5 âç––smmJljø¼¶²ìH55†Ý´Ý„ÚW‡åÎÔµç¶ÕVä}¯Üæ!Çb±Ö‡‡iç`I(\Ì’×\×/áb, …´sA¬UJ[ #33bưõë¯Ý¸!—&!•$ˆÉß~‚p7¸vJ^s ëžÂµSî¿ý“*KÕíŸjm=ÙÒò¿Ã†-êµ8…#ƒ­§÷]CÃuuSÌÍÔÕ•ÒB4ÔTä}×ÞRi;u‡ö0KÁF5u-»©; ~[TW™¦oîe0j®[ˆTܤ ¾ÿöÛØøøWÏkaûNy_n$›6iÂñ»]½~}ßþýÔ便¥3ÝÝåÝ0Y¡’(1­u 1ù²ºî@„Ü/‡1©ÊT½_¤£¤£#êÕ0]Ý0]]E¶ qu•éu¿°­ýÙV=°ôÆX8ETÝß]žûÍp¶‹¦6®½ŒDr™4éHbâNŸ.*.~þâųîe]dÂÌÌÌÔØxÜØ±‹.¤SöàÜ… ¾fÍ{õ"æçx÷zê§¥¥emeåïë;ËãÇKÏž??›–vïþý/_vtt0 }=½Q£F9ØÛH(¢<*+˸x± °: S[ÛÐÐÐÒÒÒÁÞ~á‚}e-(þ–\U]½6"¢³³ó˨(3z¼Z]]ild·g– —cÀ˜Ä˜¤¨z¿!•¶òe[‰\ðÙtÌ Ó1+Ù4xijj.Y¼XÙ­øË“òrð˜9S[[ÛÜÌL̛̞$Ir¹ÜòÊʇ¥¥;cb:;;çΞ-ا¨¤ä«Í›;::,,,¦»ºj1-­­Ïž?Ï¿{÷N^ÞlOO‰÷`>Ÿððaªg`me5ÝÕUSC£±±ñIEÅÙÙdgûx{÷yV“C}T.—ÛÐØhldÔç«–…†:räŸ NŽŽ†Âþc×.’$£7nTåN!cR|{†HLb¿!„´µµ€¶¶¶Ä=ÃV¯þãÙ´´Ä¤¤S©©Â÷à¤ääŽŽŽ—/  „ pÚÛóòó‡Ñ(Z“˜”tîÂcã_|1vÌá—jkksîÜéó çÏž•xriùØñž~~Ù·o?((ˆ‹ûî›oo9éèÑòŠŠµ«Võ¨?„èÀ˜C~19òNB)!e)˜7w.<}Ú-UáQYøûøô8!S[ÛÍÕuÄðáâÏùøÉ“ôóç™LæÛ¶õ¸€‰‰‰ßüù*5üFÄL&37/ï×ÌLjcöíÛéçÏ»¹ºR£Y¨0&û§ß1‰ã…ddffdeñùüE ¢I¯è-†1‰úÓÞ~üĉ«×¯××׳ =fÎ Y¼˜‰I;>!1‘ÚÍÛÏO‡Å:õïÓ?3UÉM‹ÁÞÈb±Þ¼ySQYé`oßÖf\¼þ>>¢“‰Ñ{.§½ý?§O_½v­öÅ --­qcÇ. ®.8äòÕ«ÇOœx^[kog·iÃS“’‡×GE ïöSr2›ÝÇìac#£ÏW®Üw89yÒĉš{ãâŒFŽŒ ëGÏf(À˜T͘ÄñBq¨d¨‡¥¥e—–*¶>R¬îîŒ> z;’騾¾žÇ}ÓÚP¨k<]—ëâ·sš©«Q__òÄÀ˜„!“2ôkffqIÉ 7·è ‚ IrûÎ7nÞ̼xq¾·÷h‡ÑÇOœ€¥Þo$êêêªý:ûÖ­”cÇ444>\²DøÕeK—¾zõêúÍ›ñ$&%¹Lœè:mšëÔ©Ã%=­£Ô¾x£ÌÍ¥|£}8qòä“'O‚W,_Nmq™4)zóæ£ÇŽíܾ]xÏ£?ý´sûv'GÇʪªÏ×®¥úl6{ip0͇ ˆðµk?_»¶¨¸BCBÆa=0&U6&•Ó/ïì|ÿÁƒo”k#Öðx`cmMÿúÉPý#H¡êêêjmm}R^^XTTXTÄçñf½÷žÌ/'Ê[“L7ö¶¶EÅÅugÓ/¬«L'I¾¥¥ £û“‰0&‡NLÊЕk×`Ÿõðˆ ˆ¾¾7nÞ¼ríÚ|ooiÏÖã+„‰±ñ£„72ŒèßÏÏO¿páN^Þ­œœ[99ñ³<]±ÂÉÑÌÍÍ CJ ¯§˜œ’“]\Öü·Æ¤ÊƤrú…T©ì{\®"û…÷¸\0æ±ýd¨þé‘BE’ä©ÔÔä””S©©Š¼&ÓøùøddeÕU¦Zúê™ö ’­ŽÖšª»ÿiŽÆäPŠI*¯¨áOŒú½¼¼¼g ™4·´455Ípw§n]=1ÙÅe²‹K[[[n^ÞÍ?þȾ};ëÒ¥[99;¶m£¾¢¿xùrù'Ÿ1|øÉãÇ€Á`p¹\‡CsI9Q›š 9%„&Tñù|’$ëëëÍ…†<ÿŒpTIhjj:ûË/Ôï»ccÿ¹w¯´ßý†ŒI•IåÌ/¤JeÇ55)ªH7ðâšš _Uº6e˜ ê DUU•b®(|é!žLgfföQh(<¼ñyåÝ¼Ž¹\†ìª«LéÏçqÜ\]{?!¥ cr(Ĥ q8è>ÔA}µhãpúq¶Ã ÔO\LŒ¶¶ö©3gJ>³?‹Åšéî½qãIIï¾óNssóÁÇ©—4ÔÕG™› ~LMM©íÆÆÆPQYÙæ £ÆxÔÔÔ„8ØÛ;;9it_Zyàÿ¬H’ŒÙ·ïuCÃW›6d³«««;6Às¾­0&U6&•3^àç—yñb^]ݦúú††òîœvlª¯ÏëèªJ·`\Z8JLþ”ðQgNžÜ““›»ß>éF5H¡50%f-ÑLk’Ø$L¦ó÷õåóùGRRjKSjKS´tÌ5µÙ‚ ȧ¦Æhz‘ @Àxg爰0iÏ€19ÔbRV˜L&‡Ãioo܆©gÖÀŠ…†„:rdwlìþØX‰õõõõׯ[·$4”šê†††‡zïé8n\uuõÕë×8?oĈ@’äw[¶Èo”]à—ôôÿËÍ]8ÃÍmĈÑ_}6-mÚ”)Æ—÷¥ŒI•IåŒ ªtÿØÜünMÍÏ--Ôä?™«áñ~niy·¦æÇæfšUºz'C‰ÏŸv(9ùîýûv¶¶êRŽýÞÊÉ ê³–è§5Ñi&Ó.XpøÀÉ..jjjêdcKý½æW¹²úáóÚÆ;;µcÛ¶~|°“C0&e‚z@V)4èû¤¢lllxf?{;»§OŸ¦Ð£n„š’f½?{6\ÈÈ Öœë7‹ecmM’dn^Þ@ÎC¡V?¥ìñã#G:ØÛ‡.] œýý &6¶µ­màWË`LªlL*-YP¥»ª®nM]¼/G³J·°Þù>âó§„ÍËÏOˆ‹311¡9.—{ýƇÀOOj£Ä¬%úiMtš„Ét`fföý·ß’$YU]ÝÜÜ,Ã36ÌÊÒr ]“C3&n–‡GQqqÚ¹s£¨8¡æõ{&ƒ€ººúº5kÖEFžMOŸîêêìäDmß¼uëûsæ¼ãâ"<"B’ä™ÔT 1ù}ÌèÑs¼¼².]ŠÞ¼yÍgŸÍœ1Cðå$ÉÒG.dd|¼l™®®®ÄøûïÙ·/1)ÉÆÚZ8U‹Çç4¿&Qƒ[---¢rW9íí?ìÚ¥©©ùeT” èEèÒ¥wòó+*+7¬_OçBCÆ¤ÊÆ¤2שQÁ*ÝâÑÏŸZ·z5ðÊU«¨_:;;ëëë©1×iÓ|æÏYKZZZôÓšh6 “é(AXYZ*»’aL":æÍûû•+—¯^mjj²·³+,..(,;f Ub€ìlm,8š›O=̽s'÷Î uuS–ŽN'—[ZVV]]=’Í^ùñÇO¶zuWWץ˗wîÙs0)ÉÎÖV‡Åjmk+//ohl$â£eËè4o¶§gQIIFf檰°)ï¼cffÖÅç×<{VPXx0>^b*e¼³ó휜Ý{÷ºLšäïÛG…ôý>}ö,*2ÒT(¤ FTddxdä¥Ë—§Mê>]k “*“J^¿PÕªt‹G?ŠæÝ«æÏ*=AèéêÚÙÙÍž5k†»;u—˜µÄb±è§5Ñl&Ó .“ˆ m[·;~üÚõëù÷î.  UÎUZKƒƒoܼùâåËÃÉÉëÖ¬€Û¶Ý/((}ô¨¦¦æVN—Ëe0£ÌÍ—,^¼ÀחΊqšššÖ¯Ÿ7wnfVVQqñýx<“Éen>ÛËkŽ—— bÝêÕ“'N<÷ë¯ù÷îÝÌÎf2™æff^³féО°êÓO›››sóòÚ;:z߃/_¹ré÷ß½<=={åìÿÍÆ&ôÓSRâ÷ïw;V_Êuìßb“*“¸®µd›?b+^C÷¬%áíT  MM‰;HÛáÄ¥ÃGŽP‰KŸ eì#Uƒ1‰hb2™Ÿ}ò‰˜Nü_½ø}´µµS’’„·Œwvïì,m#{srtìsÁ1­êÝH‚ ÜÝÜÜÝÜhž¡÷c#£˜;D>ë½÷Ĭâ´(0pQ` ¨W‡2ŒIÕŒIìJANùS¢HÌZ¢®.ô&L¦t0&BÉÖG–‚üò§ú$1kI¶iM˜L7aL"„’!ìJa–‡¤;Ge†Ë0J”HLJzÚ=)‡Ççóù|:;Ð$*qÉÚÊêU]]Bbâß’ŒI„B2„Ï‘¥ ×ü©>IÌZ’IZ`2Ý …1)Ÿw‰BCö ¥ ïü©Þ$f-É$­ “é/ŒIŒI„’!BübÙâŽ$ —.„†ªÊY¿C«ß0&‘(“HÕ`L"UCÅ$Î/D!„BØ/D!„Bì"„B!Ì;A!y»“£ì& Ô Æ$û…!$_ß~ÿ½²›€P7“Hì"„¼øöªgraL"ñ°_ˆBò’†« ƒ1‰Äü„B!€ýB„B!DÁ~!B!„x<„DQV}'„DQ|L"„ÐàòÿUÍ—±º,ãIEND®B`‚frr-7.2.1/doc/figures/fig-rs-processing.txt0000644000000000000000000000430013610377563015524 00000000000000From Peer A | From RS-Client B | | From RS-Client C | | | From RS-Client D | | | | | | | | Main / Normal RIB | | | | ________________________________ | | | | / _________ _________ \ | | | +--->|(D)-|Best | | Main | | | | +--|--->|(C)-|Path |-->|Local-RIB|->[A]|--->To Peer A | +--|--|--->|(B)-|Selection| | | | +--|--|--|--->|(A)-|_________| |_________| | | | | | \________________________________/ | | | | | | | | ________________________________ | | | | / _________ _________ \ | | | +--->*D*->|{B}-|Best | |RS-Client| | | | +--|--->*C*->|{B}-|Path |-->|Local-RIB|->[B]|--->To RS-Client B | | | | | |Selection| | for B | | +--|--|--|-------->|{B}-|_________| |_________| | | | | | \________________________________/ | | | | | | | | ________________________________ | | | | / _________ _________ \ | | | +--->*D*->|{C}-|Best | |RS-Client| | | | | | | |Path |-->|Local-RIB|->[C]|--->To RS-Client C | +--|--|--->*B*->|{C}-|Selection| | for C | | +--|--|--|-------->|{C}-|_________| |_________| | | | | \________________________________/ | | | | | | ________________________________ | | | / _________ _________ \ | | | | |Best | |RS-Client| | | | +------>*C*->|{D}-|Path |-->|Local-RIB|->[D]|--->To RS-Client D | +--------->*B*->|{D}-|Selection| | for D | | +----------------->|{D}-|_________| |_________| | \________________________________/ Key: (X) - 'In' Filter applied to Peer X's announcements before considering announcement for the normal main Local-RIB [X] - 'Out' Filter applied to announcements to Peer X *X* - 'Export' Filter of RS-Client X, to apply X's policies before its routes may be considered for other RS-Clients RIBs. {X} - 'Import' Filter of RS-Client X, to apply X's policies on routes before allowing them into X's RIB. frr-7.2.1/doc/figures/fig-vnc-commercial-route-reflector.dia0000644000000000000000000006725713610377563020706 00000000000000 #Letter# ## #NVE 4 VN 172.16.4.1# #NVE 5 VN 172.16.130.1# #NVE 6 VN 172.16.132.1# #NVE 7 VN 172.16.6.1# #NVE 8 VN 172.16.8.1# #NVE 9 VN 172.16.134.1# ## #NVA 3 192.168.1.102# #NVA 2 192.168.1.101# #Commercial Router Route Reflector 192.168.1.104# frr-7.2.1/doc/figures/fig-vnc-commercial-route-reflector.png0000644000000000000000000014345013610377563020723 00000000000000‰PNG  IHDRØ%jEbKGDÿÿÿ ½§“ IDATxœìÝyTUõ÷ñÏaP™J¹N™`™)â¢9„Zá¬9&Ý3mº]5g­4‡Ôr(õf˜3vM½ŽYš&8„Dމ12)p8Ï=ðäS¦2í¼_k¹V³ÏoÎ^¸ëã÷··Éb±XÃÅÅÅéüùó:þ¼~ùåÅÆÆ*!!á¶?éééJOO—$¥¥¥)33S’äêê*ÙØØÈÕÕUNNNrwwW•*Uäáá!www=ôÐCªQ£†jÖ¬©GyDåÊ•3òë”& 6€âuíÚ5PÛ¶m NVú%&&jÆŒú裔™™©jÕªiÚ´i4hÛp@‰DÁÊ”ŒŒ M:UsæÌ¡Ü1Ø_•œK—.UãÆ Np(Ø@™±oß>+**JåʕӘ1c4aÂ9;;­LÛ»w¯FŽ©S§NÉÎÎN¯¾úª¦L™"GGG££Ü 6Pê%''ëÍ7ßÔ²eËd±XÔ¤I-_¾\>>>FGÃÿ•™™©÷Þ{OÓ§O×­[·äíí­%K–¨]»vFG¸+ 6Pªíß¿_ýû÷×… äää¤iÓ¦iôèÑlµRgÏžUpp°8 “ɤ‘#Gêý÷ßW… ŒŽpGl TÊÊÊÒ¤I“ôÞ{ïÉl6«eË–Zµj•jÖ¬it4ÜENNŽ.\¨·ÞzK7oÞ”BCC™8V‹‚ ”:¿üò‹zõê¥ï¿ÿ^öööš4i’Þzë-ÙÚÚ ÷áÔ©SêÓ§"""T¡B½ÿþû5j”ѱþ„‚ ”*áááêׯŸU§N}þùçzê©§ŒŽ…|ºyó¦þõ¯iþüù²X,êÛ·¯–-[Æ€U¡`¥‚ÅbÑôéÓõî»ï*''GݺuÓŠ+äââbt4‚M›6iÀ€ºqã†|}}µaÃÕªUËèX’(Ø@)ššª_|Q›6m’­­­¦OŸ®7ß|S&“Éèh(DQQQ Ò©S§äææ¦5kÖ¨C‡FÇ `%[ll¬:wî¬cÇŽÉÃÃC_|ñ…Úµkgt,‘ÔÔT :T_~ù¥ììì´xñb ”q<Ÿ”X§NRÓ¦MuìØ1Õ®][‡¦\+åœõÅ_h„ ÊÎÎÖË/¿¬ýë_âߌ€‘˜`%ÒÞ½{õüóÏ+99Y-Z´ÐW_}%www£c¡­\¹R/¿ü²²²²Ô§O­\¹RåÊ•3:(ƒ(Ø@‰³cÇuïÞ]éééêÕ«—V®\© * øæ›oÔ­[7%''«sçÎZ·n?  ØQ°€eãÆêÝ»·233õÊ+¯hñâÅ<Ì Œûá‡Ô¡CÅÇÇ«mÛ¶Ú´i“œœœŒŽÊîÁJŒ/¾øB={öTff¦ÆG¹IR£Fô¿ÿýOÕªUÓ7ß|£:(99ÙèX  a‚ ”6lP¯^½d6›5aÂM™2ÅèH°2?ÿü³Ú¶m« .¨eË– —£££Ñ±@@Á¬Þ¶mÛ¤ÌÌLMš4Iï¾û®Ñ‘`¥.\¸ -ZèòåËzæ™g´uëV|аj»wïVçΕ‘‘¡×_]|ðÑ‘`墣£Õ²eKÅÆÆªk×®Z¿~½ìííŽJ1 6`µNž<©fÍš)==]#GŽÔ‚ ¸çîIDD„¯~ýú)$$„ŸPdxȰJ—/_V`` ÒÓÓÕ¯_?Ê5ÜmݺUŽŽŽ Õ{ï½gt$PŠ1Á¬Nrr²Z¶l™7…´cÇ|Ù¼y³‚‚‚”““£õë×ÏèH b‚ X³Ù¬îÝ»+""B>>> £\C¾uíÚU}ô‘,‹† ¢ÿýïFG¥°*o¿ý¶víÚ¥ªU«jûöíruu5:J¸aÆéí·ßVff¦zöì©Ë—/ ”2lVcýúõêÙ³§ìííµgÏùûû ¥DNNŽ:wî¬;v¨Y³fÚ»w/“‘ Ð0Á¬ÂéÓ§5dÈY,Í›7r …ÊÆÆF¡¡¡òööÖáÇ5fÌ£#€R„ 6`¸ôôt=ùä“:{ö¬ ¤+V ¥Ô‰'Ô¼ys¥§§+44T}ûö5:(˜`†{ýõ×uöìY5jÔH‹/6:J±Ç\ü±¤ßïÍvþüyc€R 6`¨Í›7ëù矗ƒƒƒŽ;¦zõê eÀ‹/¾¨ÐÐPµhÑB{÷î•­­­Ñ‘@ Æ0Lll¬†*‹Å¢Ù³gS®¡Ø,^¼X5jÔÐ4cÆ £ã€Ž 6`˜çŸ^›6mR×®]µiÓ&£ã Œ9pà€Z·n-“ɤ£GªaÆFG%lÀëÖ­Ó¦M›ôÀhùòåFÇAÔ¢E ½ýöÛÊÎÎÖСCe6›ŽJ( 6Pì®_¿®Ñ£GK’æÍ›§*UªœeÕ„ T¯^==zTóæÍ3:(¡Ø" ŠÝ!C´bÅ jëÖ­FÇAwàÀµjÕJŽŽŽ:yò¤¼½½ŽJ&Ø@±:tèV®\)}üñÇFÇÔ¢E ½òÊ+JKKÓ˜1cŒŽJ &Ø@±±X,jÒ¤‰Ž=ªùóççmŒvãÆ Õ­[W±±±Ú¹s§Ú·oot$P‚0ÁŠÍþó=zTõë××ðáÃŽä©X±¢fΜ)I;v¬²³³ NJ 6P,RSSõÎ;ïH’æÎ+;;;ƒ·0`€7n¬S§Né“O>1:(A(Ø@±˜;w®~ýõW²ýVÉÆÆFsçΕÉdÒ”)S”’’bt$PBP°€"wýúu}øá‡²³³ÓìÙ³ŽÜQ‹-Ô£GÅÅÅiÁ‚FÇ%(rsæÌQRR’^|ñEÕ«WÏè8Àßš©:uêèØ±cŠŠŠ2:°2l Ð¤¥¥iÛ¶mrrrR`` Ñq€BÕ«W/IÒ—_~ip`m(Ø@¡Ùºu«ÒÓÓÕ¹sg9::(T={ö”$­]»Öà$ÀÚP°€B“[<ä@iÒ A=ú裊ŒŒÔ™3gŒŽ¬(Ú±c‡Ô©S'£ãE"((H’fp`M(Ø@¡Ø»w¯222Ôºuk988(;v”$…‡‡œX 6P(r ‡Ü(š5k&777>|XIIIFÇV‚‚ Š;vHÛCQªÙÚÚê™gž‘ÙlÖÿû_£ã+AÁ ,&&FçΓ···j×®mt H±Müÿ(Ø@íÝ»W’ÔºukCsÅ¡U«V’¤}ûöœX 6P`‡’$ùûûœ(zµjÕ’§§§¢££kt`(Ø@>>rttT£FtêÔ©|F’RRRôÒK/ÉÃÃCnnn8p ÒÓÓo[344Tµk×VÕªU%Iiii>|¸ªV­*ggg=öØcúñÇÿ4öÉ'Ÿ¨~ýúrqq‘›››ºwï®k×®Ý÷õtuuÕK/½t[î{¹~œPË}-99Y­Zµ’$¹¹¹ÉÙÙY«V­Ê÷µ0ÈEÁòÍÚ ¶_ýU'OžTïÞ½ÿö¸¾}ûÊÖÖVW¯^Udd¤8 ñãÇßv̺uëtàÀ]¿~] 6TçεnÝ:íÛ·OIIIjذ¡þùÏè3ƒVbb¢bbbtéÒ%]¹rEo¾ùæmkîØ±C?üðƒ~ýõWIÒ AƒtæÌ?~\©©©úꫯT¹rå?}Ç|P[¶lQJJŠ¢¢¢”œœü§¼÷"11QK—.UÓ¦Mïëúý'''íÛ·OÒÿ›8p`¾¯…Ñr· ÇÅÅ)--Íè8ÀH€|zþùç-’,7n4:ŠÅb±XNœ8a‘dÉÈȸã1W¯^µH²DEEå½¶fÍ‹§§§Åb±X¾ÿþ{‹$Ë•+WòÞß¿ÿ_¾fgg—ïÏÄÅÅYL&“åܹsyïoß¾ÝâááqÛš¿üòKÞû¿ýö›E’%22òOß+÷ø¬¬¬¿üÞ»wï¶8::ÞõØÜ÷\]]-ÎÎÎI–5jX~ýõ×ûº~\û¯ýÕûù¹Ö¢AƒI–ˆˆ££1ÁòíÊ•+’~Ÿ–²UªT‘ôûÆ;‰•ôû¶Ñ\<òÈŸ¶O>ðÀyÿ]¡B…¿|-;;û¶­÷ó™+W®Èb±¨qãÆrss“›››z÷î­ÔÔTåäää}ÆËË+ï¿s'·jÕªuÇï—+,,L~~~ª\¹²\]]ÕµkW¥§§ßÓC$)>>^7nÜзß~«””­X±BÒ½_¿û‘Ÿka-ªW¯.IV3UŒAÁò-11Q’þr‹¢ªW¯.___}ñÅw<ÆÓÓS’tñâż×.^¼Xì÷õÊÍ¥¤¤$%%%)99Y²±ùë_ÑrËœŸþùo×¾zõªzöì©×_]W¯^Urr²6oÞ,I²X,÷œÑd2©iÓ¦š9s¦f̘¡øøø»^¿òåËK’RSSóÞÿcùf2™þtžü\ k‘[êÆÅÅœɺcV-·TÈ-¬ÁG}¤3fhÑ¢EJII‘ô{ôöÛokçÎòôôT@@€Æ¯ôôt]»vM3gÎT¿~ýŠ5gÕªUÕ¥K½öÚkyEell¬víÚuÇÏT©REAAA=z´®^½*I:wîœ.]ºtÛq2›ÍruuUùòåuùòeM:5ßY¬jÕªiîܹw½~µk×–«««/^,³Ù¬K—.iúôéyk¹»»Kº½$Ìϵ°¹ß'!!Áà$ÀHl _²²²tãÆ ÙÛÛ«bÅŠFÇÉÓªU+íÙ³G»víRÍš5åä䤧Ÿ~Zjܸ±$)44T·nÝ’§§§êׯ/???M›6­Ø³~þùçrrrRݺuåââ¢V­Z)""âo?³råJy{{Ë××W...zá…òJ©\ÞÞÞš5k–úõë'uëÖM={öÌwN;;;Mž|¸ªV­*ggg=öØc:tèп߽\³ÐÐPÕ®][U«V½§ïmrŸšû4QP6Q°€|)­7wOLLÔÒ¥KÕ´iÓ¼×úöí+[[[]½zU‘‘‘:pà€Æ×µœœœò&År§Ï(Iø _çŠWXX˜¢¢¢T±bEIÒ¸qã4`À-\¸ð¾3æw½{ͧõë×+22RÕ«W—$Õ®][’tíÚµ?­w¯×lÚ´i%~òëúõë’¤J•*œ‰‚ ä‹»»»$)!!Áà$…#>>^¶¶¶úî»ï¨+VèwÞQll¬$éá‡Î;ö‘GùËbé^]¹rE‹E7Î{Íb±(33S999w¼Ÿ×2æw½{Í“;aV«V­{ú~÷zͼ¼¼îi=k–ûóŸû÷”Mlùboo/WWWeee•š˜L&5mÚT3gÎÔŒ3/OOOIÒÅ‹óŽ»xñbÞ}ÃÊ—//éöI¾?I&“éOçÉ]3**JIIIJJJRrr²222îZ†ý]Æü¬w/yr§Ö~þùç¿Ìs§õîtÍJ 6 Q°€ðððôûdUi2xð`U«VMsçΕ§§§4~üx¥§§ëÚµkš9s¦úõë'é÷­’®®®Z¼x±Ìf³.]º¤éÓ§ç­•[¼ü±œªZµªºté¢×^{M‰‰‰’~ŸúÚµkW¾2t½»}¾J•* ÒèÑ£uõêUIÒ¹sçtéÒ¥¿ü~w»f¥IîÏ~îßP6Q°€|«\¹²$å•2¥…&Ož¬… *))I¡¡¡ºuë–<==U¿~}ùùùiÚ´i’¤ *($$DŸ~ú©*V¬¨=z襗^Ê[«F6l˜š6m*777…„„H’>ÿüs999©nݺrqqQ«V­‘ïŒ]ïnŸ_¹r¥¼½½åëë+½ð JLL¼ã÷û»kVšP°I2Y,‹Ñ!@ɤ7*,,L/¼ð‚Ñq€b× A:uJ‘‘‘zì±ÇŒŽ ÂÈ·š5kJ’~ù僓Æ8þ¼¤ß'@ÙEÁò-·TÈ-€²$..NiiiªR¥ŠœœœŒŽ DÁò‚ eYîäfî$'(»(Ø@¾Q°¡,‹‰‰‘$y{{œ‚ ä[:udgg§¨¨(egg(VgΜ‘$=úè£'F£`ùV¾|yÕ©SG·nÝRTT”Ñq€b!Iòññ18 0(Ür!·l(é®\¹¢gŸ}V•+W–ÉdúÓdÞ¹sçÔ¶m[9;;ëÁÔÌ™3o{ܸqzôÑGåää¤Ê•+«OŸ>ŠÍ÷ù$iÕªUzôÑGåâ⢇zH_}õUÞ{ûö퓟ŸŸœœœT±bE=ûì³ŠŽŽ.Ðùîçøôôt 8Pnnnzà4a„¿\';;[O<ñÄ=³¤ `¹(Ø@4lØP’ôã?œ¤pØØØè…^Ђ þô^NNŽºté¢G}T ŠˆˆÐ®]»ôŸÿü'ïggg­]»V©©©ŠŠŠÒÍ›7Õ«W¯|O’BBBôÁhÆ JIIÑ?üwÍoܸ¡Î;ë…^PRR’._¾¬*Uª¨{÷îù>ßýÿÆoèòå˺té’Ž=ª-[¶ìOÇÍ›7Onnn÷tÎ’ --M111rrrâl@&‹Åb1:(¹¶oß®ÀÀ,zÈ IDAT@=ûì³Ú¶m›Ñq ÍÑ£GõÔSO)++Kvvv’¤Ÿ~úIõêÕS||¼ÜÝÝ%I»víÒ¤I“tàÀ¿\gÿþýjÓ¦Í]§¶þê|Òï’øôÓOÕ¶mÛ?}æôéÓzì±Ç”šš*'''IÒÁƒÕºukeeeåë|÷s|vv¶*Uª¤-[¶¨uëÖ’¤?üPëÖ­ÓáÇó>{áÂuèÐAŸ|ò‰Ú´isÏç´fGŽ‘ŸŸŸš6mªo¿ýÖè8À`L°€ñóó“ÉdÒ·ß~«²òïvüž‹E'Ož¼ã±»víÒ“O>™¯ó\¼xQ.\ÐñãÇU³fMyxx¨_¿~º~ýº¤ß2ѨQ#ÍŸ?_·nÝÒõë×õñÇ«OŸ>ù:ßýúå—_”ššš7Q'I?þ¸"##o;näÈ‘š:uªœ‹%Wq8xð $©yóæ'Ö€‚ HåÊ•U¯^=%&&êìÙ³FÇ)Rµk×VíÚµ5qâDedd(>>^3gÎTjjê_–‹›6mÒ‡~¨E‹åë|‰‰‰’~¿ïÛéÓ§uöìYýúë¯úç?ÿ)I²³³ÓÆõùçŸçÝóíÚµkZºtiþ¿ä}HMM•¤¼é9é÷-²¼ëׯWVV–zôèQ,™Š ø# 6P`þþþ’¤C‡œ¤hÙØØhóæÍŠŠŠRõêÕÕ¦MµhÑB•*U’ÉdºíØ 6hРAÚ¸q£7nœ¯óåN|MŸ>]òððÐ;ï¼£­[·J’233õÌ3Ϩ_¿~JMMUbb¢ªV­ú·÷|+L¹ùÒÒÒò^KMM•³³³L&“RRRôÖ[oiáÂ…Å’§8Q°€?¢`Ö¢E Iºã}ÈJ“zõêi×®]º~ýº"""äìì¬V­ZÝvÌŠ+4tèPmÞ¼YíÚµË÷¹jÖ¬©Š+ÞVÞ™L¦¼é°¨¨(EEEi̘1ªP¡‚*Uª¤aÆåpE­fÍšrvv¾m‹ì‰'Ô AI¿ß³îâÅ‹òó󓇇GÞµðôôÔÆ‹%cQˆŽŽÖµk×T«V-U­ZÕè8À P°€Ë-˜öíÛgp’ÂqóæMeffJ’nݺ¥›7oæ½wæÌ™¼‰­={öhîܹ?~|Þû ,Ð믿®;wªeË–:Ÿ­­­† ¢ &äÝcmÖ¬YêÒ¥‹$éᇖ“““.\¨¬¬,¥¤¤hÉ’%òññÉ÷÷»ŸãíììÔ¿M›6M)))ºté’,X !C†Húý ³¿üò‹Nœ8¡'NhõêÕ’¤cÇŽ©cÇŽ÷tm¬Ñž={$ImÚ´18 °l À¼½½U¯^=ÅÄÄ”Šû°988ämýsvv–ƒƒCÞ{›6mÒ#<"½õÖ[Z½zõm1xõÕWuãÆ ÈÙÙ9ïÙl–$?~ü¶ÿ¿ÛùfÍš%³Ù¬|P5jÔ‡‡‡–,Y"IªX±¢6nܨuëÖÉÕÕUÕªUÓÕ«WµfÍš¼Ïßïùî÷øÙ³g«ZµjòòòR£FÔ¿K’ìííååå•÷ç$=øàƒ·­QÒü÷¿ÿ•$=óÌ3'ÖÂd)+ûEêÕW_Õ‚ 4þ|=Úè8@‘ÈÎΖ‡‡‡ÒÒÒ/WWW£#+À(’¤mÛ¶œ(:ß~û­’““åççG¹òP°€BѲeK988hÿþýÊÈÈ0:P$¶oß.IzöÙg N¬ (zöÙg•‘‘‘WB¥MîÓO_xდkBÁ M¯^½$I_~ù¥ÁI€ÂwòäI={V¾¾¾ªW¯žÑq€¡`…&00PNNNÚ¶m›ÒÒÒŒŽªuëÖI’zôèap`m(Ø@¡qttT`` ÒÓÓyØJÜÉÌÜIM€\l PõíÛW’´zõjƒ“…çûï¿×¹sçôÔSO©víÚFÇV†‚ ªÀÀ@yzzjÛ¶mŠ5:P(>ûì3IÒ!C N¬(Tvvv\’4wî\ƒ“ù·wï^9rDO<ñ„š5kft`¥(Ø@‘xþùçU«V-}ýõ×úñÇŽä˼yó$IcÆŒ18 °fl HØØØhôèѲX,úðÃŽÜ·Ó§OkË–-zøá‡y¸ø[l È ø ÂÃõ}ûv£ã’““£W_}U’4iÒ$999œ”l X899iÖ¬Y’¤qãÆ)++ËàDÀí>ûì3?~\ 4àÉ¡à¾P°€bÓ¯_?ùùùéìÙ³ú裌ŽäIJJÒøñã%IóçÏ—­­­Á‰@IBÁŠÉdÒܹse2™ôî»ïêÂ… FG$Io½õ–®]»¦çŸ^FÇ% (V~~~:t¨RSSõÊ+¯о}û´lÙ29;;kÞ¼yFÇ%(vï¿ÿ¾ªW¯®ððp…††eØÍ›7,‹Å¢©S§ê‘G1:((Ø@±sss˻ۘ1ctíÚ5ƒ¡¬š2eŠ¢¢¢Ô¤I5Êè8 „2Y,‹Ñ!@ÙÔ­[7………©sçÎÚ¼y³L&“Ñ‘P†ìÛ·O²±±ÑÑ£Gåëëkt$PB1Á ³xñbU­ZU[·nÕâÅ‹Žƒ2$))I ÙlÖ¤I“(×@0Á µmÛ6uéÒE*TÐÑ£GU¿~}£#¡ èÓ§Ö¬Y£§Ÿ~Zß|ólmmŽJ0&Ø€¡5bÄedd¨OŸ>JOO7:J¹Ï>ûLkÖ¬Q¥J•´jÕ*Ê5P`L°Ãeddè©§žÒ©S§ôâ‹/*$$ÄèH(¥~øáµhÑBZ³fzõêet$P 0Á çàà  6ÈÕÕUŸþyÞF” nݺ)##C£F¢\…† 6`5ÂÂÂÔ½{wÙÛÛë›o¾Q‹-ŒŽ„RÂl6+00P;wîTóæÍµ{÷n•+WÎèX ”`‚ X   ½ýöÛÊÌÌTPP¢££Ž„RbܸqÚ¹s§<==µvíZÊ5P¨˜`VÅl6«K—.Ú±c‡êׯ¯C‡ÉÕÕÕèX(Á>þøc >\åʕӮ]»Ô²eK£#€R†‚ XäädµlÙR ÐŽ;˜8B¾lÞ¼YAAAÊÉÉQHHˆúõëgt$P ±EXWWWmß¾]^^^Ú½{·† "þM÷ëÈ‘#êÓ§Ìf³f̘A¹Š °J^^^Ú¾}»\]]ªaÆQ²ážEDDèÙgŸUzzº^yå½ýöÛFG¥°Z>>>úꫯäèè¨%K–è7Þ0:J€sçΩ]»vJLLÔ /¼ …  ”rlÀªµnÝZëׯW¹rå4gÎMš4ÉèH°b.\P@@€~ûí7uèÐAkÖ¬‘­­­Ñ±@)g;‰ßR€•«]»¶ÜÝݵcÇíÝ»WÙÙÙ 0:¬Ì¹sç K—.©FÚ·oŸ*T¨`t,P0Á¬^\\œæÎ+‹Å"MŸ>]¯½ö÷dCžÈÈHµjÕJ.\$]¹rE§OŸ68(+(Ø€UËÌÌÔóÏ?¯èèh5mÚT_|ñ…Ê•+§yóæé•W^‘Ùl6:" vôèQµnÝZ±±±j×®F¥¬¬, 0@FÇe°Z‹EC† Ñ¡C‡äåå¥ 6¨gÏžÚ²e‹µtéRõîÝ›¥ ûú믠„„=÷ÜsÚºu«Þÿ}Õ¯__gΜÑ;ï¼ctDPP°«5qâD…††ÊÕÕUÛ·o׃>(Ijß¾½vìØ¡J•*iýúõjÛ¶­âââ N‹â¶|ùr*%%E/¾ø¢Ö­[§òåË«B… ‘½½½æÏŸ¯Ý»w”rlÀ*…††júôé²µµÕêÕ«åããsÛû­ZµÒÁƒU³fM>|XÍš5STT”AiQœ,‹Þyç+;;[ãÇ×þóÙÛÛçÓ¨Q#Mœ8Q‹EƒVRR’‰@ig²pw``eöìÙ£Ž;*33S‹/Ö°aÃîxlll¬ž{î9}÷Ýwª\¹²BCCÕ±cÇbL‹âtãÆ Q… îy ‹Å¢N:içÎ ÔÖ­[‹01(‹xÈ0œÅbÑ!C´wï^yyyiÓ¦Mù*×$©mÛ¶:vì˜üýý£§Ÿ~Z“&MRvvv!§FQûñÇÕ¸qc}üñÇrppÐ’%K´råÊû*×$Éd2é³Ï>SåÊ•µmÛ6-[¶¬ˆ€²Š‚ nÚ´i •‹‹‹¶oß.//¯­÷ðÃëÿûŸ&Ož,Išy{{ëСCòññÑìÙ³™f³bjÞ¼¹Æ§ÌÌL7Nß~û­êׯ_àµ{÷î­Þ½{+55U Ùl.„ÄlÀ@Ô!Cd±XôÑG©k×®…~ŽfÍšéĉ1b„nݺ¥7ÞxCM›6Õ‰' ý\È¿[·niâĉjܸ±¾ýö[Õ®][»wïÖìÙ³U¾|ùB;Ï¢E‹ôàƒêàÁƒš3gN¡­ Ê6r -ÅÅÅiøðáyÛ÷ŠÒÁƒ¬3gÎÈÎÎN£GÖ„ äææVäçÆ…‡‡k̘1úé§Ÿdgg§7ÞxC&LƒƒC‘œoçÎêÔ©“ìííõý÷ßË××·HÎÊ 6Pì®_¿®&Mš(::Z]»vUXX˜lmm‹åÜ™™™š1c†Þÿ}eddÈÃÃC“'OÖË/¿,;;»bÉ€ß9sFcÇŽUxx¸$ÉÏÏOŸ|ò‰6lXäç9r¤-Z$___}÷Ýw…:%Ê 6P¬233Õ©S'íÞ½[>>>:|ø°œœœŠ=ÇÅ‹5vìXmذA’äëë«÷ß_:t(ö,eM||¼fΜ©… *33SÕªUÓ¬Y³Ô¿™L¦bÉ––¦F)**Jo¾ù¦Þ{ï½b9/((Ø@±zñÅ*///>|¸ÀO -¨½{÷jìØ±:~ü¸$©E‹š:uªZ·nmh®Òèúõëš3gŽæÏŸ¯ÔÔT988hìØ±zûí·åìì\ìyŽ9¢æÍ›K’öìÙ£–-[{P:ðPlfÍš¥ÐÐP9::jË–-†—k’Ôºuk=zT«W¯VݺuuàÀµiÓFíÚµÓ¾}ûŒŽW*$%%iÊ”)òööÖôéÓ•™™©þóŸŠŠŠÒ´iÓ )×$©iÓ¦zçwd6›5hÐ ¥¤¤’”|L°€bªþýûËÆÆFaaaEòÄЂÊÎÎVhh¨¦L™¢˜˜IR“&Môúë¯+((¨ØîWZ\¸pAóæÍÓ§Ÿ~ª””ÙÙÙ©ÿþš0a‚jÖ¬it}ZO>ù¤nÞ¼©Í›7«K—.FG% (RÑÑÑò÷÷W\\œ† ¦Å‹éžY,}ýõך={¶víÚ%IrppP÷îÝ5tèPµjÕÊà„Ö#>>^!!!Z¶l™Îœ9#Izà4bÄ >\'ü{sçÎÕØ±cåéé©“'OªJ•*FG%(2ÉÉÉòóóÓÙ³gÕ¹sg}õÕW%v›edd¤–,Y¢Ï?ÿ\III’¤:uê¨GêÕ«—||| NXüRRR´eË­]»Váááºuë–$©yóæ V¯^½T¡BƒSÞ‹Å¢¶mÛjÏž= Ê{º,À½ `E"33S:uÒîÝ»åãã£ýû÷ËÕÕÕèX–‘‘¡õë×kùòå·=¡~ýúêÑ£‡ž{î9=þøã2™L¦,:‰‰‰úú믵nÝ:íØ±C’$õïß_ÁÁÁzôÑG N™?.\PÆ •œœ¬U«ViÀ€FG%(C† ÑŠ+äåå¥Ã‡[ÅC [TT”Ö­[§/¿ü2ï^m’äé驎;ªcÇŽj×®ÜÝÝ LY0f³Y?üðƒÂÃõcÇ}÷Ýw2›Í’$uéÒE={öTÇŽU¾|yƒÓܪU«4hÐ ¹ººêäÉ“zøá‡ŽJ 6PèfÍš¥ýë_rttÔž={Ô¤I£#¹Ó§OkݺuÚ´i“Nœ8¡Ü_±lllT¿~}ùûû«E‹jÞ¼¹¼½½ N{giiiúþûïµÿ~:tH‡VrrrÞû•+WVûöíÕ£GuêÔI¦-ݺuSXX˜Ú´i£o¾ù¦ÔN#€ÂCÁ Õš5kÔ·o_ÙØØ(,,L]»v5:R±‹Uxx¸ÂÃõk×.%$$Üö¾‡‡‡6l¨ ÈÇÇG 6TíÚµ‹u mvv¶.]º¤ÈÈHEFFêÇTdd¤~úé'eggçgkk«F©cÇŽêÔ©“š4iRbï£w¯âââäëë«ØØXÍ;WcÆŒ1:°rl Ð9rDJOO×| ×_ÝèH†ËÉÉÑéÓ§uèÐ!8p@TLLÌ_ëîî®5jäý©V­šÜÝÝåîî.¹»»«\¹ryEœƒƒCÞC’’’d±XtëÖ-¥§§+%%E Š‹‹S||¼táÂ?^çϟץK—”••õ§ NNNzê©§Ô²eKùûû«Y³f¥âÞy÷kË–-êÚµ«*T¨ cÇŽ©~ýúFGVŒ‚ Šèèhùûû+..NÆ ÓâÅ‹Ždµâããó&Æ"""ôã?êܹs·mÅ,jvvvz衇ԠA5hÐ o¢®nݺ²³³+¶Ö,88XË—/דO>©Ã‡ËÞÞÞèHÀJQ°€KNNVË–-¡öíÛkûöí¥~aQHHHÈ›0;þ¼®^½ª„„%$$äM¡effæqºyó¦$ÉÍÍM&“IåË——£££\\\äîî®*UªäM¿=òÈ#yÓq=ô…Ñ]¤¤¤èñÇWLLŒ&L˜ )S¦ X) 6P ™™™êÔ©“vïÞ-íß¿¿Ln)Dé´ÿ~µiÓF’tðàA5mÚÔàDÀÙ”l£FÒîÝ»åéé©íÛ·S®¡TiÙ²¥ÆŽ+³Ù¬(--ÍèHÀ Q°€|›5k––.]*GGG}õÕWòòò2:Pè¦N*EEEé­·Þ2:°Blù¦îÝ»Ëd2iãÆêÚµ«Ñ‘€"sòäI=õÔSÊÊÊRxx¸Ú·oot$`E˜`÷íÈ‘#êß¿¿,‹¦OŸN¹†RÏ××WS¦L‘ÅbÑ!Ctýúu£#+¸//^TãÆ§aÆiñâÅFGŠ…ÙlÖÓO?­ƒªwïÞúâ‹/ŒŽ¬¸gÉÉÉjÙ²¥""" ;wÊÎÎÎèX@±‰‰‰QÆ •ššª5kÖ¨W¯^FGV€-¢àž˜Íf)""B>>> £\C™ãíí­?üP’4|øpýúë¯'Ö€‚ Ü“Q£Fi÷îݪR¥Š¶lÙ"WWW£#†V`` 5dȱ!P°€»š5k–>þøc9::jË–-zä‘GŒŽjùòåòððÐÎ;õñÇŒ{°€¿µyóf)''GëÖ­S·nÝŒŽX… 6¨{÷îrrrÒñãÇU»vm£#ƒ0ÁîèÈ‘#êÓ§Ìf³f̘A¹üA·nÝÔ¿¥¥¥©ÿþ2›ÍFGa‚ ü¥Ë—/«I“&ºzõª‚ƒƒµtéR£#V'))I¾¾¾ºté’¦NªñãÇ €‚ ø?ìÝ{\Î÷ÿ?ðÇÕU¡"¤±iŽÉ±TTÊaä8g9&ÇÃ0à ÛæÔÆÆ´‘Ã$gQÌ!Ç¢UJ†eH3[sˆ„êêõûãóuý´B¨^×áq¿Ý®ÛæºÞ½ß«.y^Ïëù~¿ˆˆ(Ÿôôt´lÙ*• žžžˆˆˆ€©©©ìXDZ)22íÚµƒ‰‰ NŸ> gggÙ‘ˆˆˆ¨„ñQ"""ÊC­VcÀ€P©ThÔ¨BBBØ\#z OOOLœ8YYY2džÅàÁƒ‘••%;!6؈ˆˆôœZ­†··7’’’`kk‹;vÀÔÔTv,"ƒ³dÉØÚÚâܹs˜={¶ì8DDDT„xŠ(‘ž?~<¾ÿþ{X[[#::š+†ItæÌ´hÑpüøq4oÞ\r""""* œ` (? IDAT#""ÒcøþûïajjŠÝ»w³¹F$Y³fÍðÙgŸA­VcèСÈÈȉˆˆˆŠ'؈ˆˆôTXX¼¼¼››‹   ®J¤%²²²Ð¬Y3$$$`Ô¨QX³fìHDDDô–Ø`#""ÒC*• -Z´Àƒ0gÎ^ï‰HËœ?...xúô)~þùgtîÜYv$"""z l°é™ÔÔT¸»»#55>>> ⊡DZèÛo¿ÅÔ©SQ¥Jœ?VVV²#ÑbƒˆˆHdff¢U«VˆG«V­pèÐ!®J¤¥rssѶm[;v }úôÁÎ;eG"""¢7Ä‘žP«ÕðòòBXXlmm kkkÙ±ˆè%®_¿ŽÆãÁƒ  AƒdG"""¢7ÀUD‰ˆˆôħŸ~а°0X[[#""‚Í5"P£F ,_¾0aÂܼySr""""zl°退,[¶ ¦¦¦Ø¶mlmmeG"¢B>|8zöì‰û÷ïcøðáà &DDDº‡ 6""" &@¡P`ýúõðôô”‰ˆ^Ó?þˆÊ•+ãÈ‘#X¹r¥ì8DDDôšx 6"""¦R©Ð²eK¤§§ãóÏ?ÇüùóeG"¢7Šž={¢L™2ˆGýúõeG"""¢BbƒˆˆHGݺu nnnHMM…‚‚‚ P(dÇ"¢·àëë‹ 6ÀÅÅQQQ011‘‰ˆˆˆ §ˆé ÌÌLxyy!55îîîX¿~=›kDz`ÅŠ¨Q£bcc±`ÁÙqˆˆˆ¨8ÁFDD¤cÔj5¼¼¼[[[DGGsÅP"=rüøqxzzÂÈÈQQQpuu•‰ˆˆˆ^lDDD:æóÏ?GXX,--Á摞ùàƒðÉ'Ÿ ''C† ÁãÇeG"""¢W`ƒˆˆH‡à믿†©©)BBB`kk+;ƒ  Q£F¸|ù2>ûì3ÙqˆˆˆèxŠ(‘Ž8rä:vìµZM›6aðàÁ²#Q1JLL„››²³³qðàA´k×Nv$"""zN°é•J…Þ½{C­Vcúôél®GGGÌž=B >÷îÝ“‰ˆˆˆ^€lDDDZ.-- Mš4ÁÍ›7áãム  ®Jd Ôj5Zµj…èèhøøø`óæÍ²#QØ`#""Òb™™™ðôôDLL ÜÜÜ 333Ù±ˆ¨%''ÃÉÉ رcúöí+;ýO%""ÒRB 4111°µµExx8›kDÈÖÖK–,Œ3ýõ—äDDDDô_l°i©™3gbÏž=°´´Dhh(¬­­eG""Iüüüðá‡âîÝ»1bx ‘vaƒˆˆH ýøãð÷÷‡©©)BBBРAÙ‘ˆH"…BuëÖ¡bÅŠˆˆˆÀš5kdG"""¢çðlDDDZ&22~ø!²²²ˆ#FÈŽDDZbçÎèׯÌÍÍ‘˜˜[[[Ù‘ˆˆˆœ`#""Ò**• ^^^ÈÊÊÂôéÓÙ\#¢<úöí ø@v$"""ƒÄ 6"""I"##1fÌÀòåËÙ\#¢×æââ‚Ï?ÿ¹¹¹6l>|(;‘Aâ‘—.]B³fÍžžŽéÓ§cÑ¢E²#‘ŽÊÎÎFóæÍ‹áÇcýúõ²#6؈ˆˆJXZZ<<<œœŒ=z`Ïž=P(²c‘ûí·ßФI<~ü{÷îE=dG"""2(œýôS¨Õj 2=’‰ˆˆHïp‚ˆˆ¨ˆ=z:uBVVV¯^ÍC‰HkdeeÁÕÕçÎØ1c ;‘^aƒˆˆ¨$''ÃÃÃiii˜4i–-[&;Q*• ...ÈÊÊÂþýûÑ©S'Ù‘ˆˆˆôO%""zKiiiøðÑ––†îÝ»cÉ’%²#åcooùóçkb¹{÷®ìHDDDzƒlDDDo!++ ;vıcÇàì쌓'OrÅP"ÒZjµžžž8qâúõë‡íÛ·ËŽDDD¤8ÁFDDô†žM;v 666 esˆ´šR©ÄÆQ¶lYìØ±[¶l‘‰ˆˆH/°ÁFDDô†æÏŸàà`”-[û÷ï‡ìHDD¯T³fM,_¾0~üx¤¦¦JNDDD¤ûxŠ(ÑÆàÁƒadd„tïÞ]v$"¢×Ò½{w„‡‡£}ûöøå—_ P(dG"""ÒYœ`#""zMQQQðõõ…+W®dsˆtÒÚµkammC‡aÕªU²ãé4N°½†äädxxx -- ãÆÃ÷ß/;ÑÛ³g¼¼¼`ff†³gÏ¢nݺ²#é$6؈ˆˆ éÞ½{puuErr2ºwïŽ(•JÙ±ˆˆÞʰaÃðÓO?ÁÕÕQQQ066–‰ˆˆHçðQ""¢BÈÊÊBŸ>}œœ {{{lÙ²…Í5"Ò ß}÷ªW¯Ž_ý .”‡ˆˆH'q‚ˆˆ¨ „àà`ØØØàôéÓ\1”ˆôÊÑ£GÑ®];(•JDGG£iÓ¦²#éN°½‚¿¿?‚ƒƒaff†ððp6׈Hï´iÓü1²³³1dÈ<~üXv$""" 6""¢—ÆàÁƒadd„®JDzëÉ“'hÚ´).\¸€?þË—/—‰ˆˆHg°ÁFDDô111hݺ5ž>>زe‹ì¤åx 6""¢ÿHOOG·nÝ––†®]»båÊ•²#•˜3fÀÍÍ 7oÞää.Q!±ÁFDDôœ¬¬,xyyáÒ¥K°··ÇæÍ›¡T*eÇ""*1J¥AAA077GPPvïÞ-;‘V†‚7ÞòÜ-Z0R(à·WÝT*•ä_eDD%c̘1ˆŒŒ„ öïßKKKÙ‘ˆˆJ\:uðÍ7ßFÛ·oKNDDD¤Ý8ÁFDDôüýý±aØ™™a÷îݰ±±‘‰ˆHš±cÇ¢cÇŽ¸sçFŽ);‘VÓ4ØdÔñ¦7{{{™¯O"¢³}ûvÌœ9J¥[·n…«««ìHDDR) ¬_¿+Vľ}û°víZÙ‘ˆˆˆ´'؈ˆÈàÅÄÄÀ××Bøûû£{÷î²#i…÷Þ{O³ŠòäÉ“ñÇHNDDD¤Ø`#""ƒvõêUtëÖ ™™™;v,¦N*;‘Véß¿? €ŒŒ 2jµZv$"""­Ã¬ôôtôêÕ iiièСV®\);‘VZ½z5ªV­Š¨¨(,Y²Dv"""­Ã¤¬¬,xyyA¥RÁÞÞ;vì€R©”‹ˆH+U¨Pëׯ‡B¡À¬Y³””$;‘Vaƒˆˆ Òĉ‰Ê•+cÿþý°´´”‰ˆH«uèÐãÆCVV „§OŸÊŽDDD¤5Ø`#""ƒãïïüfff …ìHDD:á믿†T*¾üòKÙqˆˆˆ´lDDdPBBB0sæL( lݺnnn²#é ssslÚ´ J¥K—.ÅÉ“'eG"""Ò l°‘Áˆ‰‰ÁàÁƒ!„ÀÂ… ѽ{wÙ‘ˆˆtŽ››fΜ µZ¡C‡âáDz#IÇ„””tëÖ ™™™;v,¦OŸ.;‘ÎúòË/ѤI\»v “'O–‡ˆˆH:6؈ˆH不§£k×®HKKƒ§§'V¬X!;‘N311Á¦M›Pºti"<<\v$"""©Ø`#""½¦V«áåå•J{{{„„„ÀÄÄDv,""× A,Z´àç燴´4ɉˆˆˆäaƒˆˆôÚ„  kkk„……ÁÒÒRv$""½ññÇ£M›6¸}û6ÆŒ#;‘4l°‘Þò÷÷G@@ÌÌÌŽ5jÈŽDD¤W 6nÜKKK„„„à§Ÿ~’‰ˆˆH 6؈ˆH/………á‹/¾€B¡À¦M›àææ&;‘^ªV­¾ûî;ÿ›h»qã†äDDDD% 6""Ò;111ðöö†Z­ÆÂ… Ñ»woÙ‘ˆˆôÚ!Càåå…ôôt >BÙ‘ˆˆˆJlDD¤WRSSÑ«W/dffbÔ¨Q˜>}ºìHDDá‡~@•*UpôèQ,_¾\v""¢ÅéôôttîÜýõ<==±jÕ*Ù‘ˆˆ †µµ5Ö¬Y˜9s&.^¼(9QÉaƒˆˆô‚Z­Æ€ R©Ð¨Q#„„„ÀÔÔTv,""ƒÒ­[7Œ9Ož<ÁàÁƒ‘-;Q‰`ƒˆˆô„ pàÀX[[cÏž=°´´”‰ˆÈ -]ºµjÕÂÙ³g1oÞ<ÙqˆˆˆJlDD¤ó–.]Š€€˜™™!<<¶¶¶²#¬²eËbãÆP*•X¸p!bbbdG"""qqqP(puuͳͳûsrrÐ¥KŒ1"ß× !`kk‹eË–i¶·°°Ès6lØ ýßmMMMQ®\¹âxšÒ°ÁFDD:-,, Ó¦MƒB¡@`` ÜÜÜdG""2x-[¶Ä”)S V«1dÈ“‡ˆˆþÏܹs1sæLdffæ{¬k×®([¶,¶nÝšçþÀÀ@ôêÕ VVVo}ü?þø‘‘‘ðóó{ë}i½i°Éu|&''NNNšãQñIMME¯^½™™‰¡C‡búôé²#ÑsJ•*…   ˜ššbõêÕøå—_dG"""#FŒ@ÅŠñõ×_ç{ÌØØÇG`` æ¾{÷îaÏž=5jT‘?00ÎÎÎpvv.’ýi ½i°=#cÔñ™åË—£|ùòo•Ÿˆˆ^-==;wFjj*<==±fÍÙ‘ˆˆ¨˜7o„1bîÞ½+;‘ÁS*•X¶l–,Y‚ÔÔÔ|1±±±8þ<`óæÍ¨Zµ*Ú´i“g»J•*¡|ùòšÛªU«^y윜lذAï¦×=l°Éu¼qã1{öì7Þ½Ú³ëù¨T*ØÙÙ!$$¦¦¦²cÑ L™2Í›7ÇŸþ‰>úHv""жm[´mÛ¶À³@jÕª…¶mÛj¦ØÖ­[‡‘#GB¡PäÙîßÿÅýû÷5·ñãÇ¿ò¸áááÈÈÈлë¯zØ`“5ê8~üxÌ›7oµ""z¹ & ,, ÖÖÖØ·o,--eG""¢—P*•Ø´i,,,°mÛ6lÛ¶Mv$""°dÉìܹ¿þúk¾Çüüü°yófœ:u .\(Ôe³ cÍš5ðööÖËÞ‰Þ5ØdŒ:îÚµ ÙÙÙèÛ·oÑ>""ÊcÕªU€©©)ÂÃÃakk+;B­Zµ°téRÀG}„?ÿüSr""¢ÂINNÆ7dÇ(vvv7n\gâõèÑFFF2dºuë†*Uª¼õñRRRpðàA½<=ÐÃP²£Ž>ÄgŸ}V¨s‰ˆèÍ………aÒ¤IP(X¿~=ÜÜÜdG""¢×0jÔ(téÒwïÞň#ò,LFD¤ RSS±wï^|þùçèØ±#*V¬ˆ:uê **Jv´b3{ö웚šbèС¸víFŽYà×–/_>Ïâ:txé±Ö­[‡Æ£iÓ¦E’]ÛËP\–,Y{{{xxxä{ÌÏÏcÇŽEŸ>}ÞzÔñòåËHIIA³fÍ@³zh•*U°víZôêÕë÷MDÚ%-- ׯ_Çõë×qíÚ5ܾ}wîÜÉsËÌÌÔ\òÑ£GÈÊÊXZZÂÈÈFFF°´´„¹¹9¬¬¬`mmJ•*ÁÊÊ ï¿ÿ>jÔ¨š5k¢zõê¼®Øs’’’àíí µZ… ÂÇÇGv$""z°··Ç/¿ü‚€€Œ7Nv$­Çúƒ¨xüû￈E\\œæ¿ýõW¾íÞ}÷]éÅämÓ¦Mó5ÓÊ—/ÿý·Àí/^ŒÅ‹j?…1wî\Ì;÷µ¿NWèmƒíU£Ž}ôQ‘Œ:6nÜ×®]Óü9)) ]ºtA||<Þyç7Þ¯¶9sæ ªV­ŠŠ+ÊŽBTìþþûo$%%!)) *• *• —/_Æ£GÞxŸéééšÿ¿sçÎ+·722‚ 6l888ÀÞÞõêÕƒ‰‰ÉçÐE©©©èÒ¥ 233áããSàt2é†*Uªà‡~@Ÿ>}0mÚ4´oßuêÔ‘K+°þ *><@||¼¦™‹ëׯçÛ®B… pqqAÓ¦M5ÿµ±±¶lÙRòÁI§èmƒ øß¨cPPP¾ûŸ:.Y²ä…§v–/_>ÏŸ=<»wïj·´´D‡ЧOtíÚUói¶.š0aV­ZkkkDGGóE""=‰víÚÁØØgΜ³³³ìHEŽõQÑÉÍÍÅ•+WòL¦%&&âñãÇy¶S*•hРAžfZãÆßêú‚l°Ñ˰ÁF…ò|ƒÍÞÞ¾ÀmRRR˜˜ˆÄÄDMã­ wSSS4jÔŽŽŽšI7XXX÷Ó uùòelÛ¶ ;vìÀÅ‹5÷ÛØØh>qm×®,--%¦|;B$&&j>ŽŽÖ|bgnnŽnݺ¡oß¾èÒ¥ J•*%9má=»ðµ©©)>Œ–-[ÊŽDDDÅà“O>ÁòåËÑ AÄÇÇ£téÒ²#½5Öº[v¹~ýºf*-..ñññxðàAžm lmmó\7ÍÉÉ©È'?Ù`£—aƒ ¥0 ¶‚ܽ{gÏžÅÙ³g5Í·Ë—/ç;ïÝÈÈvvvprrÒLº5iÒ„×t 7öøñclß¾k×®Ett´æþF¡_¿~èÑ£$&,^ééé8xð víÚ…ŸþY³¢XÅŠ1xð`Œ95’œòåÂÂÂàåå…ÜÜ\qÅP""=öøñc4mÚ/^Ä'Ÿ|‚¥K—ÊŽôFXè~ýArýõ×_yVóŒ-peË÷ß?ß"ÿ½~zq`ƒ^æYƒ ø_ƒMÄÞÞ^IIIo½¯‡Ѝ¨(ñý÷ß ___áìì,LMMų×àó·jÕª‰îÝ»‹¹sçŠÐÐPqãÆ"x6¤ÏÎ;'Æ',--5¯£ºuëŠÙ³g‹ .ÈŽ'EFF†Øºu«ðòò¥J•Ò|_ÜÝÝÅúõëEff¦ìˆù$%%i~†sæÌ‘‡ˆˆJ@ll¬011 …BDFFÊŽóZXä§‹õ•¬»wïŠ_~ùE,X°@ôìÙSØØØøžðwÞ;w³gÏáááâöíÛÒ28PÁÁÁŲÿÎ; __ß|÷çææŠÚµk‹¥K—ŠØØX@¸¸¸ˆÜÜ\Í6ÏîÏÎÎ.pß©©©âÃ?*T(p;ssó<7Q¶lYÍã“'OõêÕfff¢B… bÀ€⯿þzásyÕñ„bãÆ¢^½zÂÂÂBØØØˆ={ö¼ôûó:ÛæøEmÑ¢EÏ^·l°Ñ‹eƒ­ YYY"!!A¬[·NL˜0A´hÑBXXXø ÖÚÚZ´oß^L›6MlݺU\¾|Y¨ÕêbÉEº!77WìÛ·O´k×Nó:)S¦Œ6l˜ˆŠŠ’O«Ü¹sG,_¾\4jÔHó½ªT©’˜5k–øûï¿eÇBqóæMMåãã“§p ""ý6wî\@T¯^]Ü¿_vœ—býQxºPPñzøð¡8~ü¸øöÛoÅ€„­­mïõ,--…§§§øì³ÏÄ®]»Äõë×eGÏ£¸l{öìâáÇyî?räˆ(Uª”ø÷ß5´J•*‰Ÿ~úI³Í«l·nÝkÖ¬AAA…j8µmÛVŒ=ZóçY³f‰¤¤$‘››+ÒÒÒDÏž=E«V­^øõ¯:Þ¦M›DÆ 5BüóÏ?â?þxáþ^wû×}¾E 6*”ân°D­V‹Ë—/‹íÛ·‹iÓ¦‰öíÛ kkë[XXˆæÍ›‹ &ˆuëÖ‰øøx‘••UbYIާOŸŠÀÀ@Ñ AÍkÁÖÖV¬ZµJë‹rm- $LLLQºtiáçç'._¾,-Ó£GDÓ¦MѪU+ñôéSiYˆˆ¨äegg WWW@ :Tvœ±þx;ÚXPÑzúô©øõ×_ÅêÕ«ÅðáÃEÆ …R©Ì÷®L™2ÂÝÝ]Lœ8QlÚ´IüöÛoZ?8QÜ ¶ììlñî»ïŠ5kÖä¹ßÛÛ[ 0@ñÿißÿ½¨ZµªxôèQžû_ÕH*ÌvW¯^ …BÉOÉ IDATÄÇÇ¿p›'N¥RùÊçô¢ãU¯^]>|ø•_ÿ¦Û¿êøÅ¡Älu|ù¨£ÿûËäèèøÊ¾ŒQG ¶III¡¡¡bîܹ¢gÏž¢Zµj6ÝLMM…³³³ðõõ«V­QQQù> Ý”-Ö¬Y#ªW¯®ùy·lÙRìÙ³GëÿQÖF)))bêÔ©¢\¹r€022ƒ¿ÿþ{‰æÈÉÉÝ»w×¼QùçŸJôøDD¤._¾,ÌÌÌ";Žë¢UPý1hÐ qåÊÙÑè5ää䈤¤$±~ýz1vìXáââRàåLLL„“““ðóók×® %ò>¶¨wƒM!fΜ)\]]5¾{÷®(]º´8räˆâÿ÷GžRNNŽøé§ŸDíÚµ5?ÓŽ;Š3gÎÈŽ¦ÒÓÓÅÂ… EÅŠall,FŒQb£ù“'OÖüPÒÍ=""Ò.+W®Ô\Dæõ–„`ýQÜ ª?|}}ŵk×dG£ÿÈÍÍW®\ÁÁÁâ“O>-Z´æææ¾çjР2dˆX¹r¥8}ú´xüø±ìøE¢$lϦÇT*•Bˆï¾ûNÔ®][3„ô|äðáÃÂÌÌLܼy³ÈlÙÙÙ¢J•*âÇ|á>öîÝ+,,,Dllì+ŸOAÇKHH„ŸŸŸÈÌÌiii¢uëÖ¢oß¾îãu·Õñ‹K‰5Ø8êørׯ_uëÖG-ô¿$_(ÚÞ`+Èó‹)Œ1¢P‹)Ì™3‡‹)h©ÈÈÈ<§b´iÓFœ:uJv,½tÿþ}1{ölÍ…šMMMÅÔ©S‹utõêÕšc=ûtŽˆˆ Wnn®hß¾½ ºuë&-ë’sÿþ}1gΜ<õÇ”)SDzzºìh+%%EìÞ½[̘1C´k×N”/_¾À÷RµjÕýû÷‹/ÇŽ<½Ø”DƒM!Úµk'>þøc!„7‹-Ò<öß>@·nÝ„O‘õMBBB Žzf×®]¢|ùòâСC…z.ï÷ßDZZšæ¾ƒŠ2eʸ×ÝþUÇ/.%z 6Ž:¾X×®]ÅŽ;^ë‡ÏÛëËÊʉ‰‰šÅZ¶lùÂÅ*Uª¤YLaË–-\LA’ßÿ]ôêÕKósqrrz£sïéõݹsGL™2EÓ˜~÷ÝwźuëŠüïÁþýû…R© …BlÞ¼¹H÷MDDº+55UsI”ÀÀÀ=6ëyJªþ ¼þþûo±oß>1gÎѵkWQ¹råß#½÷Þ{¢GbÞ¼yâÀâßÿ•½D•TƒmÇŽÂÊÊJœ}*¬­­EÍš5E¯^½ò6lçÏŸ/Öæµ>)Ééžo°qÔ1¯¬¬,qóæMÍmß¾}€¸~ýºÈÌÌ|ãç[ÔØ`{±çS9r$SxC'NœÐŒ§›››‹eË–ñt -vñâEÑ¢E @( 1iÒ¤BÿÊÉÉÝ»w„­­­øçŸŠ9-麈ˆ¡P(D©R¥Š´eý¡[~ûí·<õÇ„ ôfuÊ×¥V«Å… ÄO?ý$Æ/š5k&J—.ïý‡±±±ppp#FŒ"..NdeeÉŽ¯³Ø`£—)ñ›u,h/ûhè#l¯§ ÅÊ–-[àÏÍÊÊÊ SÈÊÊ3gÎÔL¶lÙ²D®±BoO­V‹åË—kŠ9{{ûBýŽøì³Ïaii©9=žˆˆèUÆŒ#‡·ž|fý¡»ÔjµX±bÅk׺îêÕ«bÛ¶mbêÔ©âƒ>(ð½…B¡uëÖ>>>bùòåâÔ©S<¶ˆ±ÁF/ó¬Á¦ÐtÙ„Ñ988@¥R!)) ööö²ãè¤ÜÜ\$''#11gÏžÅÙ³g‘˜˜ˆ´´´|ÛZXXÀÁÁÎÎÎprr‚££#5jSSS ɋϵk×пÄÆÆÂÄÄsæÌÁgŸ}¥R);½•J¨T*”.]‹/Æøñã Ü6 ãÆƒ©©)"""àééYÂi‰ˆHW=zôŽŽŽHNNÆôéÓ±hÑ¢7ÚëýpáÂx{{kêo¾ù&L«Hܺu ±±±ˆ‹‹Óü÷Î;ù¶«^½:š6m ¸¸¸ I“&°´´”Øpøøø`Ë–-ÆÀeÇ!-ãïï3f€ 6z)6ØŠÏŸþ‰øøx$&&"11ñññHIIÉ·©©)5jGGG8::¢I“&ppp€………„ÔoïÀðññÁÝ»wagg‡Í›7ÃÅÅEv,zCOž<ÁŒ3°bÅ !0pà@¬]»fffšmŽ9‚Ž;B­VcÓ¦M>>O3í?þÈ·¥¥%š4iWWWÍtÚûï¿/!1½ 6؈´HÅŠѶm[´mÛVsߣGpîÜ9$&&joçÏŸÇ•+WpåÊlݺU³mµjÕ4 7ggg8::¢Zµj2žJ4hBCC¡T*áïïiÓ¦A¡PÈŽFE¤GˆE¯^½pîÜ94mÚk×®ÅäÉ“‘žž,\¸PvL""Òa³fÍÂþýû‘€É“'cÍš5/Ýžõ‡þ{Vxyy!)) ...ضm:vì(-ÓÓ§Oqîܹ<Í´K—.A­VçÙÎÌÌ NNNy&Óìììøú$Òal°i9sssxxxÀÃÃCs_vv6.^¼ˆ³gÏ"!!AsšiJJ RRR¦Ù¶bÅŠhÒ¤‰æšnÎÎΨS§ŒŒŒJ$ÿí۷ѵkWÄÇÇ£R¥Jغu+ÚµkW"Ǧ’egg‡˜˜øúúbçÎðöö†»»;Þ{ï=¬Y³†#½SSS¡iÓ¦ D=Ð¥K—·eýa8ìììpæÌŒ9Û·oG×®]±zõjŒ5ªØ““ƒ‹/æi¦%%%!;;;Ïv&&&pttÌsªgƒ `lÌ·ãDú„£‰t‰‰ 7nŒÆcøðáò.¦ ¹¾[ZZ:„C‡i¾þÙb ÏOºÇb .\@çΑ’’‚:uê`ÿþýZ9¶OEÇÂÂÛ·oGݺu1þ|œ}™™‰þýûcãÆ(]ºt±=GÒNÇÇûï¿Þ½{cË–-xøð!vìØÁ×½###lܸؽ{7‚‚‚0xð`Ö6l˜¦þغu+>|ˆ;w¾ôµð÷ßç9Í3..ÿüóO¾ílllò4Ó\\\P¡B…â|:D¤CØ`#2@-¦™™©™rKLLD|||žÅ¶mÛ¦ÙöÙb Ï&Ýž_LaÏž=0`²²²0f̬^½š×Þ2`íÚµÃÑ£GѱcG„‡‡£[·nØ»w/ÌÍÍeG#""V½zu,_¾¾¾¾˜8q"ž "¢7²oß>Œ;BÖôBµk×ÖÔ©©©hÑ¢öíÛÇúƒˆŠÛôDôV"##Ñ·o_dggcêÔ©,n饪W¯ŽÈÈHT®\D¿~ý““#;é˜gõGVVëz¥êÕ«ãèÑ£¨R¥ >¬©]‰^— o¼å¹Í˜1lDô’’’Э[7<~üãÇÇ7ß|#;é€:uêàСC°²²Bhh(|}}eG"""ÂúƒÞ„­­-<ˆJ•*!,, ÇÏw-6"¢·¡ ð— ÈÁÁ*• III°··—‡´Hjj*ÜÝÝ‘šš A¡à…©ðbbbàéé‰ÌÌL,Z´Ó§O—‰ˆˆ´ëz[¬?ˆ¨¸hlD/Ã=/==-[¶„J¥‚§§'"""x z#aaaðòòBnn.‚‚‚àãã#;i)ÖTTXQqà)¢DôZÔj5úôé•J{{{„„„°¸¥7Ö½{w¬\¹Bøúúâĉ²#‘býAEé¿õÇñãÇeG""= <7”ˆ^çŸ~Š%K– råʈ‹‹ƒìH¤f̘T®\ñññ¨ZµªìHDD¤EXPqxV¼óÎ;ˆçëŠˆÞ lDTh»víB¿~ý`bb‚£GÂÃÃCv$Òjµ]»vÅàáá£Gr2ˆˆ°þ â“››‹®]»"""îîî8vìë"zcøfffP©T¨Y³¦ìHDDT X69uêZµj333$%%¡V­Z²#‘–ã)¢D”Gtt46n܈²eËj–+'’©eË–=z4=z„I“&ÉŽCDDÅ€õi›-Z`̘1¬?ˆ¨Ð8ÁFDB¸ºº"..+V¬ÀĉeG"¤§§£^½z¸}û6<ˆöíÛËŽDDDE„õi« nݺ¸}û6~ùåtèÐAv$"Òbœ`#"M›6!.. 4À¸qãdÇ!Ò°´´Ä‚ “'OFNNŽäDDDTTX¶*W®-Z€õ½'؈‘‘ºuëâÖ­[ü„Ž´Rnn.ÜÜ܇U«Vá£>’‰ˆˆÞëÒvÏ×+W®ÄøñãeG""-Å 6",[¶ ·nÝB—.]XÜ’V222ÂÒ¥K¡P(ðÕW_!##Cv$""zK¬?HÛaÙ²ešúãáDz#‘–RΙ3gŽìD$×½{÷0`Àäää`ïÞ½¨T©’ìHDª^½:Ο?ØØXXXX eË–²#ÑbýAº¢Zµj¸xñ"bccannŽV­ZÉŽDDZˆlD„o¿ý÷ïßÇ AƒP¯^=Ùqˆ^ꫯ¾‚R©Ä’%Kžž.;½!Ö¤KæÎ ¥R‰o¿ý–õˆlD.-- >>>B`×®]¨P¡‚ìHD/emmäädÄÄÄ T©RhݺµìHDDôšX®©T©®^½Š˜˜˜˜˜ M›6²#‘–á‘[±b>|___Ô¬YSv¢B™5kŒ±bÅ Ü¿_v""zM¬?H±þ ¢—á‘ËÈÈ€··7rss±{÷n”+WNv$¢B©X±"nܸӧO£R¥Jððð‰ˆˆ ‰õ骊+"%%gΜaýADùp‚È€mذ÷ïßÇ€`cc#;Ñk™:u*ŒŒŒ°råJäääÈŽCDD…ÄúƒtÙ´iÓ4õGVV–ì8D¤EØ`#2P¹¹¹øî»ï P(ðé§ŸÊŽCôÚêׯnݺ!%%[¶l‘‡ˆˆ õé:;;;Mý±mÛ6ÙqˆH‹°ÁFd BBBœœŒ: Q£F²ã½‘I“&–/_.9 ëÒ“'OÀúƒˆòbƒÈ@ýðÀO>ùDr¢7׺uk¸ºº"!!§OŸ–‡ˆˆ^õéƒV­Z¡Y³fHHH@TT”ì8D¤%Ø`#2@W¯^Edd$j×®:ÈŽCôVF  ”œ„ˆˆ^†õé???Àºuë$'!"mÁ‘ „#GŽ„B¡‡è­ôïßåÊ•ÃöíÛñàÁÙqˆˆèX>éׯÊ•+‡;v°þ "€B!d‡ ¢’“jÕªáÎ;HIIA•*UdG"zkcÇŽÅ?ü€€€Œ3Fv""ú֤Ƈ€€¬^½cÇŽ•‡ŠÑ£G¸j,½lDfïÞ½èÕ«zöì‰={öÈŽCT$bccáêê üú믲ãѰþ }¦M›¢I“&ˆ‹‹“‡Š‘W­§W2–€ˆJÖ³(9 QÑqqqA:u‹ßÿuêÔ‘‰ˆˆžÃúƒôQ“&M`gg‡øøx\¹rvvv²#Q ¨P¡‚ì¤eîÝ»0~ö?D/S®\9(•JÙ1è-=zôûö탹¹9ºté";Q‘êß¿?æÏŸíÛ·ã‹/¾‡ˆˆþëÒgýû÷Ǽyó°}ûv|ùå—²ãP1 政¿?f̘ãŠ+ÊÎB: )) ööö²cÐ[úù矑™™‰þýûÃÌÌLv¢"Õ¯_?ÌŸ?;vì`ƒˆH‹°þ }Ö¯_?Ì›7;vì`ƒÈÀiNå˜#„ŽúeÇŽþWé{{{Ô«W*• —.]B½zõdG"""°þ ýÖ¨Q#Ô¯_çÏŸÇo¿ý†úõëËŽDD’hlwïÞ•™ƒ´”ƒƒT*•ìT?~Œˆˆ”)S~ø¡ì8DÅ¢W¯^X´höìÙƒ3fÈŽCDdðX!ðòò‚ ‚Ï?ÿ\v"’ÄHv"*ÇŽÃãÇѺuk”)SFv¢bѹsgÀþýû%'!""€õ†N:8 9 ÉÄ‘xöþ³€H5kÖ –––8sæ ÒÓÓeÇ!"2x¬?ȸ»»£|ùò8}ú4îß¿/;I‘ˆˆˆžžAzÍØØ:t@NN<(;‘ÁcýA†@©T¢}ûöP«Õ8tèì8D$ lDà?þÀï¿ÿŽZµj¡N:²ã«öíÛ \""ÉX!ái¢DÄ‘8vì uëÖRs•„6mÚŽ=*9 ‘acýA†¤U«V€'NHNBD²°ÁFd¢££’“?[[[T®\ÉÉÉøûï¿eÇ!"2X¬?ÈØÚÚ¢J•*HNNÆíÛ·eÇ!" Ø`#2ÿ½;‹ªìÿþÖaQÃ]IQIÒ\PÀ­ÜRÌrß×Ìľ©OjôTj" ¹<ššŠæ–’¦¥ä¾€ ®( nh&«,² Ü¿?ú1ó€²ÏfÞ¯ëâºÆ™3ç~Ÿià|úœûœsöìY€‡‡‡ÄIˆ4£ð»^øÝ'""ÍcýAú†õ‘~cƒHÇ%%%!&&¶¶¶hÑ¢…Ôqˆ4‚.‘´X>býA¤ßØ`#Òqç΃;w†L&“:‘F°À%"’ëÒG…§CžMDú… 6"wõêU@Û¶m%NB¤9­[·†nܸ‚‚©ãéÖ¤Xé76؈t\TTÀÅÅEâ$Dš£P(àèèˆÌÌLÄÅÅI‡ˆHï°þ }ÄúƒH¿±ÁF¤ãXà’¾*üÎþ‘æ°þ }ÅúƒH±ÁF¤Ãrrr 899I‡H£XàIƒõé3ÖDú‹ 6" ¥R '''Èår©ãiT«V­·nÝ’8 ‘~aýAúÌÙÙ-q"Ò46؈tØýû÷Mš4‘4‘ ¿÷…¿DD¤¬?HŸ5mÚpïÞ=‰“‘¦±ÁF¤ÃXà’>{ýõ×þù§ÄIˆˆô ëÒgl°é/6؈t \ÒgöööP(HHH@ff¦ÔqˆˆôëÒg¬?ˆôlD:¬ðöà¤d IDATŽŽŽ'!’O%"Ò<Ö¤ï8‹H?±ÁF¤Ã?~ ¨_¿¾ÄIˆ¤Q¯^=À“'O$NBD¤?X¾cýAÚ*222™ ®®®B{^©T¢ÿþ˜4iR±÷ !ЬY3ª–·°°Pû?~üKÇ>uê:wî …B+++ôë×wïÞ­ŽÍ” lD:,99`cc#q’š§ªv>…>ûì3Èd2üöÛo¥Žíëë‹V­ZA¡PÀÆÆ#FŒÀÓ§O«fÃôŒ 11Qâ$DDúƒõGÅIUäää {÷îppp€……¬­­áé鉈ˆˆªÛ8=booHHH8 QÉîÝ»‡_óööÆ®]»‘‘¡öüñãDZcǪžKMMEFF†êgÓ¦M%®3--  €——RSS{{{ :´Ê¶I°ÁF¤Ã wê…;y*¿ªØùäææbóæÍhÓ¦ Ö¯__ꘪõÆÆÆ";;Æ «üÆè!¸DDšÇú£ò4]Èår¬\¹<@FFžûöíƒ?üð~ýõ×Rg£ùùùÁÅÅ2™ vvv˜={6Ξ=[u¥G gOΦ "¢êÅú£jhºþ044„‹‹ LLL2™ r¹œ×Ñ« 6ØHÛMš4 666Xºti±×är9&L˜ Ö˜OIIAhh(¼½½+4ž““Úµk‡+V '')))X³f FŒQámÐF:Ó`+¤é©ŽE}g›Ô wè9Â@ÿU;ŸuëÖaذaèØ±#œœœÊô÷¤¨#GŽ }ûöÞ}Æ‘f±þ¨RÕLJ¥¥% """pòäÉ*Ù}Ãi;CCCÂßßñññÅ^Ÿ4i.^¼ˆ7n¶nÝŠúõë£gÏžjËÙÙÙ¡V­ZªŸ•+W–8ž\.Ghh(¶nݪº ÎßÿuëÖUýÆIHçlšžêH¤­ É'©Ù*»ó‰‹‹ÃÑ£G1zôhÀèÑ£±~ýzµ™¶¯²oß>`ÕªUU´Eú….‘f±þ¨RÕ;vì@zz:bbb˜˜ˆ9sæTñ–é‡Úµkø§ñI¤­<==áé鉹sç{ÍÑÑžžžªFþ† ðÑGA&“©-—˜˜ˆÔÔTÕÏŒ3J+77}úôÁ¨Q£‘‘ääd¼öÚk:71IçlšžêXTãÆaooñãÇ#--­Òë#ªŒ¬¬,€™™™ÄIj¾Êì|6lØGGGtîÜ0jÔ(ÄÅÅáøñ㥎»gÏŒ?¡¡¡èСCnQõ`ýQu¤ª?€Nçúæ›o°qãÆ*Ú"ÒFþþþؽ{7.\¸Pì5lݺgΜÁÍ›7_yÙ¬ÒÄÆÆ"66³fÍ‚©©)j×®©S§âÀ•H¯}t®Á¦é©ŽÐ´iSܺu ?Fdd$nß¾)S¦T톑¤*²óQ*•øñÇñèÑ#ØÙÙÁÎÎíÚµüðïïÇÄG}„ýû÷£wïÞU¾=úB¡P233%NBDDT~š®?ŠÊÏχ‘‘Q•l‡¾±´´¤§§Kœ„èÕœœœ0mÚ4,Z´¨Økƒ ‚ÆŽ‹ÂÁÁ¡Âã4jÔ …+W®D^^ÒÓÓ±víZ¸¸¸T&¾Ö‘K :=Ú3kÖ,µ×Ší zåTG¹¼l­­­ê4¤ÆÃÏÏC† ©š!ª çÏŸ¬­­%N¢JÛùLŸ>½ØÎçÀxöì.\¸€:uꨖ?tèf̘¤¤¤OM†ŸŸ> WW×êÛ(=Pøw\©TJœ„ˆH?°þ¨Zšª?®]»†„„tëÖ ÆÆÆ¸}û6¾øâ Œ5ªz7PGü3¥   Ä×óóó+tÆ“©©©ÊTÑÓUŸ?þÒíx•ôôô Õ_™™™ÈÍÍ-÷û²²²]î÷åää”xy©ÿuõêÕr¯»¦X´hQ‰×±766ƸqãàïïÿÒ GµjÕRû·»»;Š-gee…ÐÐPÌ›7~~~000€››vìØQ5¡%t²Áüs´ÇÅÅîîîÅ^óññÁÔ©S1tèÐJOu|™²^_‰j–¼¼<ÕµEŠ>V*•ª#TEw˜E¨ŠÎ¢;ÇÿÝQÝù}œššªú^ÝÑ}œ––†üü|ÕóTµÊ»óùá‡ðþû龜?~<þýïcË–-øôÓO‹­ï“O>\.G¯^½Ôžþü9 «hkôƒ±±1T¨P#""Òš¨?òòò0wî\ÄÄÄ@:uê`äÈ‘X¸paõl”Ž+ÎÈÈ@^^õ#E¿xñ999Ôb}œ­º>HÑÇE^äææªN)+ú˜ôGUì|˜››—ºÜŽ;tzU m°š™êGÅðáÑ™™ sss <Ë—/¯üh‘¯¾ú 666šmõ²™W5‘\.W]S¡èã¢;É¢ T§HÝ!þïαðNC€úίèckkkÕtó¢‹îôŠîÈ®_¿Ž=zTñ'@T³6ß+R¨UDáüþýûëÜEÜõÕõë×Ù`£RéLƒMÊ©Ž_ý5¾þúër½§¦ùù矫|/k YXX¨.¨ZôqÑ£E›››ÃÄÄ€ú‘‹¢MMMUSµ‹¥(úØØØXuAô¢ŒŒjä­æmllðTQÒo…³^y‘f""Í(<°ÈúƒôYá„‚ÂâD¤t¦ÁFÕkáÂ…¨_¿>õÙV/›yU–ÙVDDÕ­ð´ï²Lý'"""ª …×f.<Û…ˆôlT&C‡Õ¹[èêºÂ{…SÔ‰ˆˆˆªë""ÒWœ³J¤£ Ok-¼Ó)‘>JJJØÚÚJœ„ˆH?°þ úﵩ‹žíCDº 6"UØP(l0é£äädÿ½&!U/ÖD<ÀGå׿Lš4©ØóB4kÖ ˆŒŒ„L&ƒ«««Úuã ŸW*•%®ûñãÇèׯlllJ\ÎÂÂBíÇØØXí.·¾¾¾hÕª lll0bÄ<}úô¥ÛRÚx°yóf´jÕ –––hذ!~ùå——®ïÔ©Sèܹ3 ¬¬¬Ð¯_?ܽ{·RãW6؈t”‘‘¬­­‘——§S.íæ;wàéé Ô¯_‹/V{]Ó;ˆêÞ!”¶ü‹/0nÜ8ÔªU uêÔÁ‚ *5^M׈H³X°þJ¯?ÊûyÔ4¬?¨¼¼½½±k×®b³?ŽøøxŒ;Võܽ{÷RæuÀËË ÁÁÁ%¾ž‘‘¡öÓ­[7Œ9Rõº………*[ll,²³³1lذ ‚eË–aÏž=HOOÇå˗ѦM›—MKKÀàåå…ÔÔTÄÇÇÃÞÞC‡­ðøÕ‰ 6"fgg/½›nMôª?˜8p Zµj…¤¤$DEEáÈ‘#زe‹jMî 4±C(mùÏ>û ñññxôè"##‚~ø¡ÂãÕ4…ßýÂß""ª~¬?X”V”÷ó¨iXPy 0–––ؾ}»Úóëׯ‡———Z³ÖÏÏóçÏWÝÌ«4uëÖ…··7Z¶lYê²qqq8vì|||ÔÆsqqL&ƒfÏž³gÏVx¼ `ÅŠpvvØÛÛ£iÓ¦%.ôôt̘1FFF°²²‚nÞ¼Y%Û[QQýúõ'N,ö|AAxýõ×E@@€¸xñ¢ :vì( TË>Ÿ——WâºãããEß¾}EíÚµK\N¡P¨ý KKKÕë³gÏ-[¶æææ¢víÚbøðá⯿þzé¶”6žBlÚ´I´lÙRXXXˆ ˆÐÐÐ ­/;;[tëÖM¼öÚkB¡P+++Ñ«W/®Z&33SŒ;VX[[ {{{ñÅ_¼t¬²æ//@\¿~½Òë"ÍëØ±£ .\¸ u”*WÒßÛ·o "11QõÜü!<<<^ºžS§N CCà '„7GŽ)ñ=7oÞDFF†ê¹3gι\^áñʳ|^^ž°°°ÇW=·|ùrѹsçJWSôéÓGaaaRG!"Ò¬?X”µþ(TÖÏ£¦xûí·qøða©£P9r¤ ¶mÛVmcÌŸ?_¸ººªþœœ,LMMÅÑ£G…ÿý}ËÎÎ...báÂ…jÏ—ö{[–åæÍ›'Ú·oÿÊõ,\¸P-gyÆ{ðà –-[&š4i"lmmÅÈ‘#Errr‰ëÈËËíÚµß|óÈÎÎÉÉÉbÔ¨Qb̘1¿º,^¼X•žÁÆ©Œ%Oe,m}r¹+W®Äƒ‘‘'OžÀÃÃT-SÚ‘Ÿòæ'ýÓ Aÿtþõ‰(rM!®_¿þÒe9‚öíÛWhœ‡âÁƒ¸rå š6m ;;;Œ5Jua['''´k×+V¬@NNRRR°fÍŒ1¢Bã•×½{÷‘‘¡öwªmÛ¶¸qã†FÆ×Ož<Ô«WOâ$DDúƒõëòÖ•ù<´ÑãÇõë×—8 Õ$“&MÂÅ‹U¿+[·nEýúõѳgOµå ÿ*ý;«T*ñã?ªÍ^û_ûöíC@@V­ZU¡1 ¯|çÎDGGãöíÛxòä &Ož\âòr¹¡¡¡Øºu«ê”ò¿ÿþëÖ­«ÐøÕ­Ò 6Ne,y*cië344„‹‹ LLL2™ r¹ŽŽŽþùroÙ² ,€¥¥%5j„™3gbãÆÎOú§ðûyïÞ=‰“hFóæÍѼys,\¸YYYHLLÄâÅ‹‘‘‘¡Vôªî„Ô;„ …Bõœ……ÅK?]tÿþ}@“&M$ÍAD¤OX°þÊ^TöóÐF¬?¨"áéé‰õë×6lØ€>ú2™¬Ø²žžžðôôÄܹs«lü_ýj“–ŠÚ³gÆÐÐPtèСBcÞiú›o¾™™ììì0þ|8p ÄåsssѧOŒ5 HNNÆk¯½¦µ§”WºÁ&—Ë1aÂÕ—øç¶Ä¡¡¡ðööV[vÒ¤I°±±ÁÒ¥K+;l1ëׯG»víЮ]»—.SGŠ*jøðá°´´„B¡@DDNž< €3O¨jîÔ wòºÎÀÀû÷ïGll,êÕ«‡ž={¢K—.¨]»v±“&vRï óeffªžËÈÈ€……E‰;j]óôéSdffÂÁÁA­È'"¢êÅúƒõP¶ú£*>m“€ÌÌLØÛÛ³þ róññÁÖ­[qæÌܼyãÇé²þþþؽ{7.\¸P%c¯[·#FŒPýõã?â£>ÂþýûÑ»wï Ñ´iSXYY©ý-Éd/=ø‹ØØXÌš5 ¦¦¦¨]»6¦NúÒ†œÔªä&œÊXq;vì@zz:bbb˜˜ˆ9sæà̪úVà@Ë–-qäȤ¤¤ ** èÖ­›Ú2šÚAH½ChÚ´),,,ÔNQ¹zõ*Z·n­‘ñ¥Æ£ÇDDÒ`ýÁú£,õGU}Ú¦pææ«Ît"z™AƒÁÀÀcÇŽÅÀáààðÒeœœ0mÚ4,Z´¨Ôõfgg#77““ƒììlµ×>|ˆ°°°{*ÁÁÁ˜3g>Œ®]»–i;^6ž¡¡!&Nœˆ ¨Na_²d‰Ú¥²ŠjÔ¨ V®\‰¼¼<¤§§cíÚµpqq©ÔöV—*i°q*cå999á›o¾Qªï3O¨jèjûª?˜·nÝRýÞ?~øâ‹/T¯kr¡©ÂË–—Ëå3f ¾þúk¤§§ãÑ£GÆÄ‰+5^MÁ‘4X°þ(­þ¨ÈçQSÄÅÅ€êÒ?DåallŒqãÆáÞ½{øè£J]~Ñ¢Eeš€cffÿôÌÌÌÔ^ß°aÚ´iSb¿ä“O>AZZzõê ÕO~~>àÊ•+jÿ.m¼%K– ??õë×G“&M`gg‡µkת^/º>+++„††b÷îݰ¶¶Fݺuñ×_aÇŽ%._Öí­N•º‹h¡]»v [[[qúôi!—ËÕîÖù¿woˆ‰‰ÆÆÆbÕªUUr·‹wß}Wx{{—øÚÆE­ZµÄ©S§Ê¼-%§T*…••U±»™ššVh}%ùí·ßTwA-¼ûΉ'T¯—v÷òŽW¼‹hÍ–-är¹011Ñ©»CþÝ*úShñâÅÂÖÖVXXXˆŽ;ŠcÇŽ{¯\./vb¥R)„âòåËjÿ.m¼ììláãã#lmm…•••6l˜Ú]pÂÂÂDûöí…™™™P(¢wïÞâÖ­[ª×Ë;^y—ÏÌÌcÆŒVVVÂÎήĻ¿êý5ÙÂ… áçç'u""½ÂúƒõGiõGiŸGMÆúC7iâ.¢TsÞE´Êl999ÂÞÞ^4mÚTxyy©½VRÃgÖ¬YÂÎήÔFPVV–8{ö¬êVÓYYYj¯?xð@ˆ‹/{ïŠ+„8þ|™·ãUãÍš5KL:Uu{XOOOñÁTh}W¯^üñ‡ÈÉÉBqëÖ-Ñ®];1eÊÕ{§N*z÷î-ÒÒÒÄÇEãÆÅºuë*œ¿"Ø`«ùœqóæM©£i”——— öîÝ+u""½ÃúƒôëÝĽJaƒ­JN8•±¼ëËËËÃܹsakk ôë×}ûöÅŠ+Tïõ÷÷GݺuÑ A´k×cÆŒQ»q„¶M…$íTx:@TT”ÄIˆ4«ð;_Ú)1DDTõX¾býA¤¿d(œÆÆ çS Þ|óMDEEáúõëÜIÔP‹/Æüùó1oÞ<|ûí·RÇ!ÒˆôôtX[[ÃÂÂÏŸ?çµ+‰ˆ4Œõé£ÌÌLXYYÁÌÌ iii00¨²ù,$±Q£Fá§Ÿ~¶mÛ^zíwÒ_K–,Á¼yóªæ&D¤½Ú´i¸víšÄIˆ4'** B¸¸¸°¹FD$Ö¤nܸ‚‚´nÝšÍ5"=Äßz"×¹sgÈd2œ;wŽ3UIoœ;wÀ?ß""Ò<֤Ξ= ªKö‘~aƒHÇÙØØ eË–HNNÆíÛ·¥ŽC¤gΜtéÒEâ$DDú‰õé#6؈ôlDzÀÝÝ.q"Í`KD$=Ö¤oXé76؈ô@á,žÂY=DºìÎ;xöìš7oŽ:uêH‡ˆHo±þ }r÷î]üý÷ßhÖ¬^{í5©ã‘Ø`#Òݺuœ:uJâ$DÕïĉ€=zHšƒˆHß±þ }rüøq@Ïž=%NBDRaƒH8::¢eË–ˆ‹‹ãuPHç>|ðÎ;ïHœ„ˆH¿±þ }òÇúôé#q"’ lDzâí·ß„……Iœ„¨úäååáÈ‘#022BïÞ½¥ŽCD¤÷X>P*• ƒ\.W}ç‰Hÿ°ÁF¤'ú÷ï8xð ÄIˆªÏ¹sçðüùstîÜÖÖÖRÇ!"Ò{¬?H°þ "€ 6"½ÑµkW˜™™áôéÓÈÊÊ’:Qµøí·ßýúõ“8 ¬?H?°þ "€ 6"½aff†~ýú!++KUéšÐÐP€———ÄIˆˆ`ýAúõ€\êD¤9Æ Þ={°sçN¼ÿþûRÇ!ªR×®]CLL Ú´iƒ-ZH‡ˆˆþ?֤ˮ_¿ŽÛ·oãÍ7ßDË–-¥ŽCÕ,%%)))RÇ -Sø`ƒHôïß …Dff& …Ô‘ˆªÌ®]»~ø¡ÄIˆˆ¨(Ö¤ËvïÞ øàƒ$NBš0cÆ Ì˜1CꤥØ`#Ò#æææèß¿?víÚ…ƒ²A:… 6""íÄúƒtÙÎ;ü3S“t—B¡@íÚµ¥ŽAZN@@rr²ÄQH½ù曈Çõë×áââ"uªûöíÃàÁƒ1hÐ üòË/RÇ!ª.\@§NàêêŠóçÏK‡ˆˆþëÒE/^„««+:vìˆ .H‡ˆ$¦šÁfcc#e"ÒþýûÃÁÁÄÓ§Oáàà u$¢JÛ°a`Ò¤I'!"¢’°þ ]´qãFÀĉ%NBDÚ@V»vm!uÒ~gΜ³³³Ô1¨ŠÌŸ?‹/Æ·ß~‹yóæI‡¨R222P¯^=!ðäÉXZZJ‰ˆˆJÀúƒtIff&êÕ«‡üü|¬?H—üøã˜8q"&L˜ šÉFDúÍ@êD¤yŽŽŽèÕ«âââ&u¢JùÏþðöö–8 ½ ëÒ%k×®Àúƒˆþ‹ 6"=5mÚ4@`` ÄIˆ*îĉ¸xñ"Þzë-tîÜYê8DDT Ö¤ Nœ8óçÏã­·Þ‚›››ÔqˆHK°ÁF¤§ŒfÍš!,, ×®]“:Q…þÚìÙ³%NBDDeÁúƒtAPP`„ '!"m‘ž200ÀÌ™3!„@@@€ÔqˆÊ-::¿þú+5j„áÇK‡ˆˆÊ€õÕt…õGýúõñÿ÷x÷ÝwqìØ1©c‘`ƒHM˜0¶¶¶Ø±c>|(u¢rY¶l„øøã!—Ë¥ŽCDDeÄúƒj2àwÞ¡¡!> OOOtèÐ;vì€R©”:"IÄðË/¿üRêD$ ccc¤§§ãøñãÈÌÌÄÀ¥ŽDT&wîÜÁäÉ“aee…Ÿ~ú ÆÆÆRG""¢2býA5ÕÝ»wáãã+++:tŸ|ò ¬­­;wî`Ïž=زe à7Þ€‰‰‰Ä‰‰H“dB!u"’NRRñâÅ ÄÄÄÀÑÑQêHD¥=z4¶mÛ???,\¸Pê8DDTN¬?¨&3f ¶nÝŠ/¿ü‹-R=Ÿ““ƒ-[¶`ùò刉‰ØØØ`Ê”)øøãáàà Ud"Ò Î`#ÒsæææÈÊʉ'šš ///©#½ÒÍ›71}útÔ®];vìàÑa"¢ˆõÕ4ÑÑј6mj×®íÛ·ÃÔÔTõš\.Gûöí1mÚ4´oß?FLL NŸ>•+WâþýûhÖ¬ìíí%Ü"ªn¼aöìÙ¨]»6¶mÛ†[·nI‡è•-Z„‚‚üßÿý¬¬¬¤ŽCDDÄúƒj’… ¢  sæÌµµu‰Ëà½÷ÞéS§pþüy 2yyyذaZ·nâôéÓNNDšÂ¡V­ZðõõE~~>fÏž-u¢—:uêöìÙƒ:uê`ÆŒRÇ!"¢J`ýA5ÅéÓ§UõÇÌ™3ËôWWWìÙ³111˜:u*ÌÌÌpàÀtëÖ :uÂîÝ»‘ŸŸ_ÍɉH“x 6"dff¢E‹xüø1<ˆ~ýúI‰HMAAÚ·o«W¯bõêÕ˜:uªÔ‘ˆˆ¨’X¶+((@‡påÊ•JÕ‰‰‰X½z5V®\‰„„€££#fÏž &ÀÜܼ*c‘Ø`#"•­[·b̘1hÙ²%®_¿###©#©¬_¿ÞÞÞpqqÁ•+W`hh(u$""ª¬?H›Ö­[·ÆÕ«W+]¼xñ›7oF@@îÞ½ °³³Ã´iÓ0}útÔ©S§*b‘Ø`#"!ÜÝÝqîÜ9,_¾œ§kÖHMM…““pìØ1ôìÙSêHDDTEX¶JMMEË–-ñ÷ßãèÑ£èÕ«W•­»  ¿üò –/_Žððp€©©)ÆŽ ___899UÙXD¤l°‘šsçÎÁÝÝ …7nÜ@ãÆ¥ŽDüðÃöîÝ‹`ÿþýÉdRG"=ròäIxzzÂÀÀ—.]‚‹‹‹Ô‘ˆˆHX”N:…^½zÁÀÀ‘‘‘xóÍ7¥Ž„üü|ìݻ˖-ÃÅ‹æææ?~<|}}áèè(qB"žÄMD/µzõj¼öÚk8pàV¯^-uÒ#)))7nòóóáççÇæ‘aýARIMMÅØ±c‘ŸŸ/¿üR+šk`hhˆ>ø.\À±cÇпdeeaõêÕprr‡~ˆ .H“Hïq½ÒÁƒ1pà@˜šš"22ÎÎÎRG"=0|øpìܹÝ»wÇÑ£Gyj(‘žaýAR1bvìØQ#êèèh`ëÖ­ÈÉÉtíÚsæÌÁÀ9ó“Hl°Q©>þøc¬\¹o¾ù&"""`nn.u$Òa6lÀG}„ÚµkãÚµkhذ¡Ô‘ˆˆH¬?H“6n܈I“&¡víÚ¸råJ¹kù_ý…+V`íÚµHMM8;;cöìÙ=z4LLL$NH¤?Ø`#¢Reee¡cÇŽ¸yó&F©#‘Žºtéºté‚ììlìܹ~ø¡Ô‘ˆˆH"¬?HS._¾Œ.]º ++ ;vìÀ°aäŽTnéé騰a‚‚‚ðàÁ€ƒƒ>þøcL™2666'$Ò}l°Q™ÄÄÄ S§Nxþü9‚ƒƒññÇK‰tLbb":tè€à“O>APPÔ‘ˆˆHb¬?¨º%%%¡C‡¸ÿ>>þøcK©R”J%vïÞï¾ûW¯^( x{{cÖ¬Y5ffQMÄ•ÙÞ½{1tèPáèÑ£èÒ¥‹Ô‘HGäçç£_¿~ C—.]pôèQK‹ˆˆ´ëª.ùùùèß¿?> ;vL§ê#GŽÀßßaaaB@.—cèС˜3gÚ·o/u<"ûˆQ™ 2sçÎEnn.† ‚»wïJ‰tÄ'Ÿ|‚°°0Ô­[»víÒ©â–ˆˆ*‡õU___>|:Yôî݇µk×0nÜ8Èd2ìØ±:t@¯^½pèÐ!p¾ QÕá 6"*—üü| 8¿ÿþ;œkkk©cQ ¶fÍL›6 ÆÆÆœ™@DD%býAU­hýqäÈtíÚUêHÕîÑ£Gƺuë––pqq¯¯/FŒ¡s F"McƒˆÊíùóçèÚµ+¢¢¢Ð«W/üþûïÜ!S…ìß¿C† AAABBB0jÔ(©#‘–býAUEßë´´4¬[·ÁÁÁxôè ^½z˜9s&¦L™Âæu ž={†ŒŒ ©c–cƒˆ*$>>nnnˆÇ¨Q£™L&u,ªAΟ?^½záÅ‹X¼x1æÎ+u$""Òr¬?¨²XüWnn.¶oߎåË—#** `eeÌœ9 6”8¡ö5j~úé'©c–cƒˆ*,** ]»vÅóçÏ1yòd¬Y³†E.•ITTºw”L:«W¯–:Õ¬?¨¢¢¢¢Ð£G$''cÊ”)X³fÔ‘´‚aaað÷÷Ç‘#GFFF6l|}}Ѷm[‰J¯hƒÍÑÑQâ4¤mâââ²?ÿü“ 6*Uƒ 8ŸJtâÄ ôïß/^¼€¯¯/üýý¥ŽDZ.66]ºtABB† ‚]»vÁÐÐPêXDDTƒ°þ òºsçºté‚gÏžÁËË »wïfýQ‚«W¯béÒ¥øù矡T*ï¼óæÌ™ƒÞ½{KœN:… ¶mÛ¶aäÈ‘RÇ!-³dÉÌ›7ò×_]ê,T\¿~...RÇ -Ô£GüüóÏ|mÛ¶…¯¯/† ###©£iyáNs¤’Nu$ú_±±±ˆˆˆ@xx8NŸ>¼¼<€ŸŸ”J%¾þúk‰’¶¹sçz÷îøøxtïÞ{öìáÌX""ª°¾}ûbûöíøðÃYÐKݹs}úôA||<ºu놽{÷²þ(ƒÆ#00‹-ÂÚµk„«W¯b̘1˜?>>ýôS|ôÑG°´´”:*‘ÖP5ØþüóO)s–zóÍ7U¼$ý•••…ÈÈH„‡‡#<<HHHP[ÆÔÔMš4Á;wðÍ7ß 33¼& øçš'o¿ý6ž>}Š.]º`ß¾}077—:ÕpC† Á¶mÛ0zôhÖTÌ7ЧOUý±ÿ~ÖåT«V-|þùç˜5k¶nÝŠ€€DGGcöìÙøê«¯0yòdÌœ9õêÕ“:*‘ä ¤@DÚçñãÇØ½{7>ýôStîÜÖÖÖèÖ­æÎ‹ýû÷#!!ðòò‚¿¿?Ξ=‹ÔÔTܺuK5+)((S¦LA~~¾Ô›C‹ŒŒDÏž=ñôéS¼ýöÛ8|ø0oÿNDDUfذaøùçŸYšÈÈHôèÑOŸ>EïÞ½YT’‰‰ &Mš„7nàÀèÙ³'RSS±téR4mÚãÇÇÍ›7¥ŽI$)yé‹‘.S*•¸víšjfÚÙ³gñðáCµe ѦMxxxÀÍÍ îîî/=­|РAøõ×_áåå…uëÖ!99[¶l™™™&6‡´ÌáÇñÁ ==ƒ ÂÎ;abb"u,""Ò1¬?¨¨°°0 :”õG5Édèß¿?ú÷ï .Àßß{÷îÅæÍ›±eËôë×¾¾¾èÙ³§ÔQ‰4Ž 6"=“œœŒˆˆU3íâÅ‹ÈÌÌT[ÆÚÚnnnªfZ§NÊu}…·ß~¿ÿþ;ŒŸþ?ƾ}û`oo_Õ›CZì‡~À´iÓ T*1vìXlذr9w;DDT=X¬_¿S§N…R©ÄèÑ£±qãF^¿š¸ººb×®]ˆ‹‹CPP6n܈ƒâàÁƒhß¾=æÌ™ƒ¡C‡²þ#½ÁSD‰t\ll,6oތɓ'ÃÙÙvvv0`¾ùæœ8q™™™prr¸qã°víZDEE!99¿ÿþ;.\ˆÞ½{Wèâ¥ÝºuÃÙ³gÑ´iSDDDÀÍÍ ±±±Õ°…¤m„˜7o||| T*±`ÁlÚ´‰ÅU;ÖúKùóçÃÛÛJ¥_|ñ¶lÙÂæš8::"88>ÄW_}…:uêàÒ¥K1bœœœðý÷ß; O¤‹ø;D:¤¬7#èØ±#ÜÝÝáîî77·j;²ÛªU+„‡‡cРA¸páÜÜܰmÛ6¼ûî»Õ2I/-- ãÇGhh(ŒŒŒ°nÝ:Œ?^êXDD¤GX蟴´4L˜0{÷î…‘‘Ö®]‹ &HKïØØØ`Á‚øì³Ï°eË &&3gÎÄ—_~‰)S¦àã?†ƒƒƒÔQ‰ªg°Õ`½Á©S§°dɼ÷Þ{Õ~Ú„ƒƒŽ;†÷ßÉÉÉèß¿?¾þúk!ªu\Ò¼[·n¡S§N …­­-~ÿýw6׈ˆH¬?ôGaý±wï^ØØØà·ß~csMb¦¦¦ðññAtt4öíÛ$''ãÛo¿E“&Màí혘©cU96؈j¥R‰K—.áûï¿ÇÈ‘#Ѹqc4hÐ~ø!‚‚‚pþüy M›6˜6mBBBðçŸ⯿þÂÞ½{áëë wwwI.ðªP(°{÷n,Y²2™ ,À Aƒðüùsg¡ê±k×.¸ººâöíÛxë­·pñâExzzJ‹ˆˆôëÝ÷óÏ?«Õ‘‘‘èÝ»·Ô±èÿ300À{g3gÎàìÙ³2dòòò°~ýz8;;cРA8}ú´Ô1‰ª O%ÒRš¸&Éd2|þùçèС†Ž_ýíÚµCHHÜÝÝ¥ŽGôâÅ Ì™3ÿùÏ „À¸qãðŸÿü¦¦¦RG#""bý¡£²²²àëëËú£)¼<ÍÝ»w€Í›7cÿþýØ¿?\]]ñÙgŸaÈ!00à ª¹Ø`#Ò±±±ˆˆˆ@xx8NŸ>Û·o;ÁÉÉIÕLsww‡³³sÛ yzzª.zŽîÝ»ã_ÿú¾øâ ^¿†¹zõ*F…èèh˜››#00>>>RÇ"""*†õ‡î¸víFŽÉú£†jÖ¬V¯^/¿ü«V­ÂêÕ«qáÂ|ðÁhÖ¬fÏžqãÆÁÜÜ\ê¨DåV³þÏœHGdeeáôéÓXºt) „:uê E‹?~<Ö­[‡[·nÁÄÄ]»vÅ矎}ûöáÙ³gˆ‰‰Á¦M›àããƒÖ­[׸æZ¡FáäÉ“ðóóøùù¡{÷îøóÏ?%NFeQPP€åË—£sçÎˆŽŽV’Áâ–ˆˆ´ëš­  èÔ©¢££Ñ¶m[\¼x‘õG U§NøùùáÁƒX½z5š5k†»wïbÚ´ihܸ1-Z„gÏžI“¨\jæÿÕ05áfš&—˱páBœ:u ŽŽŽ‡‹‹ üýý¡T*¥ŽG/Ì™3¹¹¹˜3g"""ЪU+©£•ŠõGÍTXøúú"77¾¾¾8wŽF•dnnŽ©S§"&&FuM½ÄÄD|õÕWhÒ¤ ¦M›†»wïJ“¨LØ`#ªb5ùfRpssÃÕ«W1}útäääà³Ï>C§NpõêU©£Q999X°`:tè€sçÎÁÉÉ ÇŽòeËôæ»JDDºƒõGÍ““ƒ… ªêæÍ›ãرcð÷÷gý¡c ðþûïãüùó8uêÞ{ï=äää`Íš5hÑ¢Þÿ}„‡‡K“è•xÁ¢JÒµ›HÁÒÒ+W®Äˆ#àííË—/£cÇŽ˜9s&,X€ZµjIQ¯:t³fÍBLL är9æÍ›‡ ÀÌÌLêhDDDÆúC»±þÐ_]»vE×®]„„„`ï޽ػw/ÜÝÝñùçŸcÀ€5ör9¤»ø$*§ØØXlÞ¼“'O†³³3ììì0`À|óÍ78qâ233áää„qãÆaíÚµˆŠŠBrr2~ÿýw,\¸½{÷fsí%<<ÄìÙ³±gÏÿ|¿ûî;¼óÎ;'Ó}‰‰‰X¼x1¾ÿþ{äåå¡nݺøî»ï0jÔ(Èd2©ãUÖÒ)¬?V®\‰ÜÜ\Ô­[K–,Á˜1cX 337nD`` îÝ»àŸ›%̘1Ó§O‡Mµ=jÔ(üôÓOضmFŽYmãPÍ´dÉÌ›7 6z5}k°=~üX­™vùòeäåå©-ãàà777xxxÀÍÍ íÛ·ç5 ªÑ‰'0{öl\¹rÐ¥KüûßÿF=¤ ¦ƒRRRàïïàà`dddÀÌÌ ³gÏÆÜ¹saaa!u<"""aý¡9)))X¾|9V¬XÁúƒÊD©Tâ矆¿¿?.]ºP(˜8q"fÍšGGÇ*“ 6z6بLt¹Á¦T*qíÚ5U3íìÙ³xøð¡Ú2†††hݺµª™æîî^-°éÕ °sçNøùù!&&àé鉅 ¢[·n§«ùRSS±bÅ âùóç066Æ„ ðÅ_ AƒRÇ#""’ëꕚšŠàà`"55•õUÈñãDZ|ùrüöÛoBÀÐÐC† Áœ9sàêêZeã°ÁF¯Â•‰.5Øx3‚šO©TbÛ¶møê«¯puuÅœ9s0dÈJœ°fyðà‚‚‚°aä§§C.—c̘1X¸p!š4i"u<"""­Àú£j½¬þX°`š6m*u<ª¡nÞ¼‰eË–aûöíÈÍÍôìÙ¾¾¾èׯ_¥O3fƒ^¥°ÁüÓ`D%qqqÄõë×¥ŽRn111bÓ¦MÂÇÇG´jÕJÈd2Qø}/üqrrãÆk×®QQQ"??_êØT¹¹¹bÓ¦Mªï'áèè(‚‚‚DJJŠÔñ´Þ™3gÄðáÃ…\.„¹¹¹˜:uª¸sçŽÔшˆˆ´ëÊaýAšðøñcñù矋Zµj©~OÅúõëEvvv…×;räH@lÛ¶­ ÓjÞÅ‹ѱcGQPPPìù¼¼<ѯ_?1qâÄbï-((¯¿þºP-¯P(Ô~Æ÷Ò±cccE¯^½„B¡õêÕß~ûmul¢$/^\ø}cƒ^®¦4Ø^¼x!N:%–,Y"Þ{ï=aoo_¬™fjj*ºví*>ÿüs±oß>ñìÙ3©cS%ˆC‡‰Þ½{«þ;›™™‰1cƈ“'OJO«$$$ˆ€€ÑªU+ÕgU§Náçç'¤ŽGDDTc°þ(;Ö$•´´4 6l¨úî988ˆ%K–T¨!®k 6;;;±yóæbÏçåå‰ÐÐPaaa!ÒÓÓÕÞ{ôèQabb"Õ–/‹üü|Ñ¢E 1}út‘-’’’D¯^½Ô2Ôdl°Q™hkƒ->>^ìÚµKÌš5KtêÔIk¨988///áïï/Ξ=[©#¤ý¢¢¢ÄŒ3ÔŽV999‰ýë_Z÷ýÕ”´´4±mÛ61hÐ abb¢ú\<<<ĦM›DVV–Ô‰ˆˆj4Öűþ m’››+BBBDÛ¶mUßEKKK1kÖ,qÿþý2¯G×l«V­õë×™™™jÏçå剼¼¼Øòeqûöm@$&&ªžûã?„‡‡Gm™´t®Á&åTG!„Ø´i“hÙ²¥°°° 4¡¡¡U½‰’І[^^žˆŒŒÁÁÁbĈ¢Q£FÅši†††¢M›6bÚ´i"$$Düù矒å%i½xñBlÙ²EtëÖMí;âìì,-Z$._¾¬ö7B×$%%‰íÛ·‹!C†333ÕöÛÙÙ‰O?ýTDGGK‘ˆˆHç°þ`ýAÚï?þï¼óŽêû)—ËÅðáÃÅ•+WJ}¯®5ز³³…‹‹‹X¸p¡Úó… ³ùóç WWWÕû’““…©©©8zôh‰Ë—¦°ÁVtæjXX˜°´´¬ªM“”Î6Ø4=ÕQ!¶lÙ"Þxã qóæM!„Ïž=qqqU³a“¢Á–””$8 þõ¯‰=z…BQ¬¡fmm-Þ}÷]áçç'þøã‘––¦±|TsÄÄĈ¯¿þZíZ)…³Ç/vìØ¡v¥&R*•âÂ… ⫯¾nnnÂÐÐPíèÜÈ‘#Å/¿üœDDDÂúƒõi·+W®ˆÑ£G«Õ»woqèС—6Âu­Á–——'Ž9"ÌÍÍÅ£GŠõAþüóO!“ÉDTT”Bˆàà`ñú믫>ŸÂå­­­Õ~¾ÿþûÇÍÏÏÍ›7S§N/^¼ ¢gÏžB&“éÄÁ‡Â›ÎÜE422;vĪU«ðí·ß"66æææªçóòò5‚ŸŸ¼½½Uï9r$„ؾ}»Úòr¹¼Lc7iÒ6l€§§gµl›”4qÑØØXDDD <<gΜÁ­[·Š}œœTwötww‡³³3 ª%é¦èèhìÞ½ûöíÃÕ«WUß18;;ÃÝÝ]ºt‡‡%Nûr™™™¸xñ"NŸ>ððpDDDàùóçª×mllðöÛoãƒ>@ß¾}aff&aZ"""ýÆúƒH{=zôÁÁÁX·nÒÒÒ...ðõõň#`ll¬ZVWî"ú¿ýŽ÷Þ{VVV˜5kV±>HŸ>}ðÆo ((mÛ¶ÅðáÃ1wîÜ×S·o߯Œ3péÒ%4hÐ^^^Xµj’’’ªm{5¥ð.¢:×`ËÎÎFÇŽáåå??¿bÿáÿõ¯áÈ‘#8þ< %%õêÕÃÁƒÑ«W¯rQ>|ˆÆcÙ²eXµjÒÓÓñÎ;ï`åÊ•¨]»vuovµ«ê[VV"##®Ú9'$$¨-cjjŠŽ;ªšinnn°··¯ôØD…ž>}ŠC‡áСC8räH±?êvvvhÓ¦ Z·n ´iÓÍ›7‡µµµÆ2*•J>>°²²ÒÙ[ll,\\\ˆéÓ§«õAvïÞ©S§â—_~AÏž=ñèÑ#888”¸žŠøî»ïÐÐÐ*Û>©èlƒ-//'OžÄ{g˜˜<}úTí?|\\š5k†ëׯ£uëÖøþûï±bŠܹs2™LµžÿÝ‘}ýõט1cF±q¯^½Š·Þz >>> Bff&>øàØÛÛc×®]šÚüjSÙÛãÇÕši—/_VÍ&,äàà777xxxÀÍÍ íÛ·‡‰‰IUmÑ+ ::Z5ƒòìÙ³ˆ‹‹+qY[[[4iÒDõS·n]ØÚÚÂÖÖvvv°µµ…±±±ê™LMM©©©B ''/^¼@zz:’’’€ÄÄD$%%áÁƒ¸ÿ>îß¿Gû]…BŽ;¢k×®ª´& o"""ª<ÖDÚ'77Û·oÇòå˰²²‚îÞ½‹_~ùEçlðé§ŸbëÖ­HLLT{>77 4€……Ú¶m‹½{÷¾r=¥¹uë5j…BãÇcäÈ‘8pàÚ·o_õªa:Ý`ÓäTÇ»wï¢yóæHHH€à?þÀ AƒðâÅ‹êÛ` )OƒM©TâÚµkªfÚÙ³gñðáCµe ѺukU3ÍÝÝ]«§Ã“~JLLT±ŠŠÂµk×pçεS!ª›\.GÆ Ѻuk´nÝZuD»E‹>JDDDDÚ‹õ‘vB ,, K—.ÅñãÇüsZwAAN6ØRSSѬY3$%%ëƒ|öÙgð÷÷ÇÁƒѯ_¿bëQ(jëwwwGXXX‰c/Y²þþþÈÉÉA«V­°téRôìÙ³¶Rót¾Á¦©©Žùùù°±±A\\lmmGŽÁÀ‘••U}¬!¯j°%''#""BÕL»xñ"233Õ–±¶¶†›››ª™Ö©S'XZZjrˆªLRR’êïýû÷ñ×_!)) IIIª£À¹¹¹ªB8++ ÙÙÙ€ZµjA&“ÁÄÄæææ°´´„­­-ìííUGŸ7n¬::ݰaCI¹¹DDD¤XIçÒ¥Kð÷÷ÇÎ;!„¨ñ 6ª… 6= áää„iÓ¦aÑ¢EÅ^4h¦OŸŽ±cÇbàÀªæZEbâĉX°`ñâÅ ,Y²¬L|­Ä›¾+<C¦1QÍÀúƒH:íÛ·ÇöíÛ‘••…}ûöI‡´œÎ6Ø`Ñ¢E )ö¼±±1Ƭ\¹²Ä÷ÖªUKíߥMuœ9s&êׯ¼¼<ôíÛkÖ¬©üh‘îÝ»#%%Eí9¸ººòfDDDDDD¤³þ÷TH¢’èLƒ­C‡ÅfSÕªU ‰‰‰%.¿lÙ2,[¶¬Lë)‰‰ Ö®]‹µk×–ë}5IJJ oF@DDDDDDDTi°Qõúí·ßзo_©ci^‹Ê¤AƒRG """""""ÒJl°UlDDDDDDDDÕ¤ÿþ˜4iR±ç…hÖ¬ ™LWWWµëÂ>¯T*K\÷ãÇѯ_?ØØØ”¸œ………Ú±±1¬¬¬T¯ûúú¢U«VP(°±±Áˆ#ðôéÓ—nKiãÀæÍ›ÑªU+XZZ¢aÆøå—_*´¾œœtïÞ°°°€µµ5<==Ql=J¥o½õÖ+?«²æ¯(6؈ˆˆˆˆˆˆˆª‰··7víÚ…ŒŒ µç?ŽøøxŒ;VõܽFPà•IDAT{÷RæuÀËË ÁÁÁ%¾ž‘‘¡öÓ­[7Œ9Rõº………*[ll,²³³1lذ ‚eË–aÏž=HOOÇå˗ѦM› ­O.—cåÊ•xðà222ðäÉxxx`àÀÅ– B­Zµ^:NYóWlDDDDDDDDÕdÀ€°´´ÄöíÛÕž_¿~=¼¼¼`kk«zÎÏÏóçÏÇ‹/Ê´îºuëÂÛÛ-[¶,uÙ¸¸8;v >>>j㹸¸@&“ÁÎγgÏÆÙ³g+<Þ‚ °bÅ 8;;ìííÑ´iÓ ­ÏÐÐ...011Èd2Èår8::ª-÷àÁ¬_¿‹-zùÆ—1eTºÁÆ©ŽÕ7Õ±:ò‘æÈårL˜0ëׯW=—’’‚ÐÐPx{{«-;iÒ$ØØØ`éÒ¥Užcýúõh×®Úµk÷ÒeŽ9‚öíÛWhý>ăpåÊ4mÚvvv5jRRR*0|øpXZZB¡P ""'OžT{}ÆŒø÷¿ÿ ‹JSY•n°qªcõMu¬êüDDDDDDD¤y“&MÂÅ‹qãÆ ÀÖ­[Q¿~}ôìÙSm9CCCÂßßñññU6¾R©Ä?þ¨6{ííÛ·XµjU…ÆHNNܹsÑÑѸ}û6ž>®ºººWÿrjjJG@@eeeŸ¾#''G)))a¡¡Åb {T˜QÁ`P ’¤ÃÃCžžª¨¨H6›-4{FF†ü~¿iwFË”—Puü¼÷ªŽOÄNuuµâââT__¯ªª*edd¼¹7//O­­­Q=¸ÿææFwww’ž÷~ss¶~zzª•••W3“‘‘utthyyY%%%Qýoݯ¦¦&y½^ÝÞÞêüü\>ŸïÕ·~FsÞÎÎŽVWWCkêíí•Ûí–$èääDÛÛÛÚÞÞÖìì¬$issSååå¾Ï(S6ªŽÆ=¯:>eÆü vÕÐР““y<žˆûûúú¢jY­Výþý[ÒCvaµZÃÖ'&&TPPðjžÐÖÖ¦‹‹ •––†½D2 J’¶¶¶Â¾tŸÏçS0Tff¦²³³e³Ù466ZÿÈy÷÷÷êêêRZZš’““UYY©ŠŠ K’”••úJOO—$eff†ÎøèüFü2å=T[ZZTSSUÕÑáp¨¸¸Ø”»#UÛÛÛ¢Nc_óÝUÇGfÍbkppPƒƒƒ/~ît:_ä ©©©:;;‹xf¤\¢¿¿_ýýýŸúlaaá‹—Z¾÷™¤¤$……jŸ=ÏétjccãÝùžï~ÖGç7”›DÕñ£çEª:š=?¾†iUGs«Ž_1?Ìg‘ôGúºŠþmùùùÚÛÛÓîî®G¬Çà[¹ÝnÍÎÎjffæÍg¿ãÿåóùÔÝÝm^ƒ ø°°°°°°°°°°üŠõ?Ýññ±Žc=~˜Ç¿ 6€¼^¯¼^o¬ÇÀEÀð†ôôtÙíöXÎ"é$ÅxüDùùùº¾¾Öîî®G¬ÇøqB ¶ÜÜÜXÎü“þŠóËÙXIEND®B`‚frr-7.2.1/doc/figures/fig-vnc-commercial-route-reflector.txt0000644000000000000000000000000013610377563020735 00000000000000frr-7.2.1/doc/figures/fig-vnc-frr-route-reflector.dia0000644000000000000000000006520713610377563017355 00000000000000 #Letter# ## #NVE 4 VN 172.16.4.1# #NVE 5 VN 172.16.130.1# #NVE 6 VN 172.16.132.1# #NVE 7 VN 172.16.6.1# #NVE 8 VN 172.16.8.1# #NVE 9 VN 172.16.134.1# #BGP Route Reflector 1 192.168.1.100# #NVA 2 192.168.1.101# #NVA 3 192.168.1.102# frr-7.2.1/doc/figures/fig-vnc-frr-route-reflector.png0000644000000000000000000014147213610377563017403 00000000000000‰PNG  IHDRØ%jEbKGDÿÿÿ ½§“ IDATxœìÝyT•õÞþñkš Š ¤–•rÔÔDËÌÁÍ µDsž“JKãhÓñ¨©iZ9çCi‡0GÌ}œr̲IœB2:áA”A†ÍþýÑžãÓ 2}7ð~­åîû澯½×v-Öåçû½-6›Í&§ .èÂ… úù矫øøø[þ¤¦¦*55U’”’’¢ŒŒ I’»»»äàà www¹ººÊÃÃC^^^òôô”‡‡‡xàÕ¨QC5kÖÔC=¤²eËš|»%†…‚  h]¹rE'NœÐ‰'©ÈÈHýøãJII)² ª^½ºyä5lØP 6”êÖ­«2eÊY€’€‚  Y­Vú¨:v쨎;Ê××WNNNE– 8 `('OžÔÚµkµiÓ&8q"÷uGGG5hÐ@-[¶”¯¯¯üýýõàƒLú×ÒÒÒtüøq:tH‡ÖÑ£G•{ÜÝÝ]íÛ·WÏž=Õ¥K—Üi:€ÒŒ‚ ~üñG­^½Zk×®ÕéÓ§s_¯^½zîÄW»víäîîn0eþØl6}ÿý÷¹yGŽQVV–¤ß–¨víÚUÏ>û¬uÏ=÷N `À]HKKÓš5k´lÙ29r$÷õ ¨W¯^zúé§Õ°aCƒ WRR’víÚ¥õë×këÖ­¹O4­\¹²¨áÇ«Aƒ†S- 6€;pâÄ -Y²Daaa¹{ª=üðÃêÓ§zõê¥úõëNXôRRR´eË­[·NÛ¶mSzzº$©E‹ VŸ>}äììl8%@á£`ø6›M;vìМ9s´{÷nI¿=e³wïÞ –¯¯¯á„ö#!!A¡¡¡Z¾|¹Nž<)IòôôÔÈ‘#õÒK/éÞ{ï5œ ðP°ü ÕìÙ³s÷V«U«–BBB4`À€b½§ZQ8zô¨-Z¤5kÖ(33SåʕӠAƒ4vìXÕ©SÇt<€GÁðÿeeeiÅŠš6mš.^¼(Iò÷÷ט1cÔ­[7988NX¼\¾|YóçÏ×Ò¥Kuýúu988¨_¿~š8q¢j×®m:@¡`¥žÕjUXX˜¦L™¢Ÿ~úI’Ô¡CMžøàedd¨Zµjš:uª† Â2\P,Q°€R%--Mo¿ý¶fÍšE¹cØ•œK—.U“&M '¸;l Ô8pà€‚ƒƒ¥²eË*$$D&L›››éh¥ÚþýûõòË/ëÔ©SrrrÒ+¯¼¢)S¦ÈÅÅÅt4€;BÁJ¼¤¤$½þúëZ¶l™l6›š6mªåË—ËÇÇÇt4üz÷Ýw5mÚ4¥§§ËÛÛ[K–,Q»víLG¸- 6P¢>> câØ- 6PâüüóÏêÝ»·¾þúk•)SF“&MÒo¼!GGGÓÑpN:¥¾}û*22RåÊ•Ó{ï½§Q£F™Žð;l D‰ˆˆPÿþý• :uêèÓO?ÕOûLíÚµ3 …$99YÇך5käää¤E‹)88Øt,PÊñ|zPl:uJÍš5Ó7ß|£ÚµkëèÑ£”k%œ›››>ûì3M˜0AYYYzþùçõüCüŸ10‰ 6P,íß¿_Ï<󌒒’äçç§Ï?ÿ\¦c¡­\¹RÏ?ÿ¼233Õ·o_­\¹ReË–5 ”Bl ØÙ±c‡zöì©ÔÔTõîÝ[+W®T¹råLÇ‚{öìQ=”””¤.]ºhݺu|@‘£`ÅÊÆÕ§OeddèÅ_Ô¢E‹x˜A)÷í·ßªC‡ºzõªÚ¶m«M›6ÉÕÕÕt,Pа(6>ûì3õêÕK;v,å$I7Ö_|¡jÕªiÏž=êСƒ’’’LÇ¥l Xذaƒz÷î-«Õª &hÊ”)¦#ÁÎüôÓOjÛ¶­.^¼(EDDÈÅÅÅt,P P°»·mÛ6)##C“&MÒ[o½e:ìÔÅ‹åç秘˜=õÔSÚºu+>…Ž‚ ص½{÷ªK—.JKKÓ«¯¾ª÷ßßt$عèèhùûû+66VݺuÓúõëU¦LÓ±@ FÁìÖ‰'Ô¢E ¥¦¦êå—_ÖüùóÙs w$22Rºzõªú÷ï¯ÐÐP¾; Ðð`—bbb¨ÔÔTõïߟr wÅÇÇG[·n•‹‹‹ÂÂÂôî»ïšŽJ0&Ø€ÝIJJ’¿¿îÒŽ;ØG y²yóf);;[¡¡¡êß¿¿éH b‚ ثժž={*22R>>> §\CžuëÖM|ðl6›† ¦/¾øÂt$PQ°»òæ›oj÷îݪR¥Š¶oß.wwwÓ‘PÌ1Bo¾ù¦222Ô«W/ÅÄĘŽJ–ˆ»±~ýzõêÕKeʔѾ}ûäëëk:JˆììluéÒE;vìP‹-´ÿ~&#@a‚ Ø…Ó§Okذa²Ùlš;w.å ”ƒƒƒÂÂÂäíí­£G*$$Ät$P‚0ÁŒKMMÕã?®³gÏjÈ!Z±b…éH(¡¾ÿþ{µlÙR©©© S¿~ýLG%lÀ¸W_}UgÏžUãÆµhÑ"ÓqP‚=úè£Z¼x±¤ßöf»pá‚Ù@ D`‚ µyóf=óÌ3rvvÖ7ß|£ºu뚎„R`À€ “ŸŸŸöïß/GGGÓ‘@1Æ0&66VÇ—ÍfÓÌ™3)×Pd-Z¤5jèСCzçwLÇÅlÀ˜gžyF›6mR·nÝ´iÓ&ÓqPÊ:tH­[·–ÅbÑñãÇÕ¨Q#Ó‘@1Å0bݺuÚ´i“î½÷^-_¾Üt”B~~~zóÍ7•••¥áÇËjµšŽŠ) 6Pä®]»¦Ñ£GK’æÎ+///ÉPZM˜0AuëÖÕñãÇ5wî\Óq@1ÅQPä† ¦+V(00P[·n5¥Ü¡C‡ÔªU+¹¸¸èĉòöö6 3L°€"uäÈ­\¹RåË—×âÅ‹MÇäçç§_|Q))) 1CL°€"c³ÙÔ´iS?~\óæÍË]& ˜výúu=üðÊÕÎ;Õ¾}{Ó‘@1Â(2ÿú׿tüøqÕ¯__#GŽ4ÈU¡BMŸ>]’4fÌeeeNŠ 6P$’““5nÜ8IÒœ9säääd8p«Aƒ©I“&:uê”>üðCÓq@1BÁŠÄœ9sôïÿ[,¿ƒ]rppМ9sd±X4eÊݸqÃt$PLP°€BwíÚ5Íž=[NNNš9s¦é8ÀŸòóóÓ³Ï>«¸¸8ÍŸ?ßtPLP°€B7kÖ,%&&jÀ€ª[·®é8À_šúÈp`¯(Ø@[¾|¹l6›†.‹Åb:/½zõR… ´víZ]¿~Ýt`‡(Ø@ÊÌÌÔÊ•+U¦L 2Ät ß\]]Õ¿¥¤¤(,,Ìt`‡(Ø@Ú¶m›bcc¨ªU«šŽˆçž{NËDÀ£`jÕªU’¤~ýúNœÇ\uêÔÑ7ß|£¨¨(Óq€¡`&%%EÛ¶m“«««MÇ TïÞ½%IkÖ¬1œØ 6P`¶nݪÔÔTuéÒE...¦ãªW¯^’¤µk×Nì (09ÅCN”$ 4P½zõtòäI9sÆt`G(Ø@HKKÓŽ;äìì¬N:™ŽŠ   IRxx¸á$ÀžP°€±ÿ~¥¥¥©uëÖrvv6(;v”$EDDNì (9…CN”D-Z´PÅŠuôèQ%&&šŽì(;vì$–‡¢DsttÔSO=%«ÕªÿùŸÿ1Ø 6oçϟ׹sçäíí­Úµk›Ž*–‰€ÿ‹‚ äÛþýû%I­[·6š( ­Zµ’$8pÀp`/(Ø@¾9rD’äëëk8 PøjÕª¥ªU«*::Z±±±¦ã;@ÁòíðáÃ’¤–-[NœïzÎw”nl _âããõã?ÊÃÃC?ü°é8@‘ `ÿ‚ äË—_~)›Í¦æÍ›Ëb±˜Ž‰œåÐ9Ë£@éFÁòåûï¿—$=ú裆“E§AƒrppÐÉ“'•m:0Œ‚ äKdd¤$ÉÇÇÇp è¸ººÊÛÛ[))):þ¼é8À0 6/l(­r¾ó9ÿ@éEÁò,==]QQQºçž{T§NÓq€"EÁrP°€<‹ŠŠRVV–êÔ©#'''Óq€"U¿~}IÒéÓ§ '¦Q°€<»pá‚$©FFs&Ô¬YS’ôóÏ?NL£`yFÁ†ÒŒ‚ ä `yFÁ†ÒÌËËK®®®Š‹‹SJJŠé8À 6gçÏŸ—$y{{N˜Á(Ø@>üòË/’¤ûï¿ßpÀŒûî»O’ôïÿÛp`ȳ„„IRåÊ•‹ä~Ç—Åb‘›››ÜÜÜäêêª6mÚèäÉ“·œ©Þ½{«jÕªrss“———:wî¬mÛ¶ýáu*V¬¨.]º(::úŽîëî;êìÙ³ú¾²²²ò}üd´Z­êÙ³§Ê—//777¹¸¸ä;×gËïuþÈ/¿ü¢Î;«råÊ…v¿âåå%IŠ‹‹+ÒûûBÁò,§TÈ)ŠJbb¢’““§Zµj©W¯^¹Ç:¤V­Z©eË–:s挒““õÓO?é¹çžÓöíÛÿð:?ÿü³*UªtËuþê¾—.]Ò}÷ݧ޽{ÊûËüd‘$ݸqCÏ=÷œ<==U±bE «U«¦àà`Õ­[÷Ž>—‚FÁ$ 6G9…BNÁ`Brr²BCCåïï/é·}°Nœ8qÛI´ÿ+>>^~ø¡7n|Gç'$$héÒ¥jÖ¬Yîkýúõ“£££~ýõW}ú(99YÙÙÙw1¯×»Ó<9fµjÕº£÷w§ŸYõêÕïèz&Q°€bËÙÙYýû÷Ï`»ï¾ûÔ°aÃ?\ÞXP,‹š5k¦éÓ§ëwÞÑÕ«WUµjUIÒ¥K—rÏ»téRî¾a÷Üs¤ÿú“nê²X,¿»OÎ5£¢¢”˜˜¨ÄÄD%%%)--Mý+Ü_eÌËõî$OÎÔÚO?ýô‡yþìzö™9Óu7nÜ0œ˜DÁò$))IÒo›é›’žž®Õ«Wç.E”¤… júôéš7ož$ý6m÷ÕW_轇ªjÕªiΜ9ªZµª4~üx¥¦¦êÊ•+š>}ºú÷ï/é·¥’îîîZ´h‘¬V«._¾¬iÓ¦å^+g¯ÿ.§ªT©¢®]»êïÿ{îûˆÕîÝ»ó”1¿×»ÝÏ{yy)((H£GÖ¯¿þ*I:wîœ._¾ü‡ïïvŸÙݸyófîSFÓÓÓuóæÍ»¾F^唓w2J. 6Pìä<ÒÓÓSgÏžÕš5krùùùéàÁƒ:räˆêÕ«§òåË롇ÒÖ­[µqãF99Ì´NNNšR… ôì³Ïê¹çžË½V54bÄ5kÖL+VThh¨$éÓO?•«««~øa•/_^­ZµRdddž3æ÷z·ûù•+WÊÛÛ[ 6TùòåÕ½{w%$$üéûû«Ïìn8;;«eË–’~Û°(—-çÜ+gÉ4(,¶ÿÞˆà:tHþþþ¹…PíÝ»WmÛ¶U@@€öìÙc:0„ 6 L>ìØ 6'9{¯åìÅ”F9{¯ÝɃ"@ÉÅo@å<=4çi¢ t¢`yÂæîÀo(Ø@ž¸¹¹I’’““ '̹víš$©R¥J†““(Ø@žxxxH’âãã 'ÌÉùþçü{¥È“2eÊÈÝÝ]™™™%êA¿üò‹:wî¬Ê•+Ëb±(++ë–ãçÎSÛ¶måææ¦ûï¿_Ó§O¿åøØ±cU¯^=¹ººªråÊêÛ·¯bccó|?Iúä“OT¯^=•/_^<ð€>ÿüóÜcPóæÍåêêª *¨sçÎŠŽŽÎ×ýîæüÔÔT ­GyDÉÉÉruu•$>|X­[·Vfffžîw7çgee©R¥JÚ²e‹Z·n-Iš={¶Ö­[§£GÞöxIСCíÚµK;wîTûöíMdž0Áò¬zõê’¤˜˜ÃIŠÖÿÿ¤ÍfÓ‰'þôÜÝ»wëñÇÏÓ}.]º¤‹/ê»ï¾SÍš5åéé©þýûçn¬_§N5nÜXóæÍSzzº®]»¦Å‹«oß¾yºßÝúù矕œœœ;Q'I>ú¨Nž­³gÏêßÿþ·^xáI’“““6nܨO?ý4wÏ·+W®héÒ¥y“w!ç ²9ÓsÒoKds>Û/ .\¸ é·ICPzQ°€<Ë)rJ†’ÎÁÁA›7oVTT”î»ï>µiÓF~~~ªT©’,Ë-çnذAC† ÑÆÕ¤I“<ÝÏÍÍM’4mÚ49;;ËÓÓSãÆÓÖ­[%Izê©§Ô¿%''+!!AUªTùË=ß RN¾”””Ü×’““åææ&‹ÅrÛãÅ]\\œRRRäååuK‰J 6g¥­`“¤ºuëj÷îݺvíš"##åææ¦V­ZÝrΊ+4|øpmÞ¼YíÚµËó½jÖ¬© *ÜRFY,–Ü鯨¨(EEE)$$DåÊ•S¥J•4bĈÜ®°Õ¬YSnnn·,‘ýþûïÕ Aƒ;:^ÜåLnæLr€Ò‹‚ äYI-ØnÞ¼©ŒŒ IRzzºnÞ¼™{ìÌ™3¹YûöíÓœ9s4~üøÜãóçÏ׫¯¾ª;wÊßß?_÷sttÔ°aÃ4a„Ü=Öf̘¡®]»J’|ðA¹ººjÁ‚ÊÌÌÔ7´dÉùøøäùýÝÍùNNN8p ¦Nª7nèòåËš?¾† vGÇ‹»óçÏK’¼½½ '¦Q°€<«S§ŽœœœuÛ§d'ÎÎÎjÙ²¥¤ß–A:;;çÛ´i“zè!•/_^o¼ñ†V­ZuËC ^yå]¿~]rssËýcµZ%Iß}÷Ý-¿Ýýf̘!«Õªûï¿_5jÔ§§§–,Y"IªP¡‚6nܨuëÖÉÝÝ]ÕªUÓ¯¿þªÕ«WçþüÝÞïnÏŸ9s¦ªU«¦êÕ««qãÆ8p ‚ƒƒïøxqvæÌIR½zõ '¦Yl%e‡Y`Ä#<¢Ó§OëÔ©Sª_¿¾é8@‘ ÒÆ®îÝ»›Ž b‚ äKÎrÄÈÈHÃI€¢•ó¿Ý’\PòQ°€|iÔ¨‘$é‡~0œ(:))):þ¼\]]Ùƒ P°€ü¡`CitòäIegg«AƒrÜ~Òã IDATpàWjJ;~ùÒ¼ysY,}ùå—bkW”‡–¤Ü‡?€Ò‚ äKåÊ•U·n]%$$èìÙ³¦ãE‚‚ ü7 6o¾¾¾’¤#GŽN 6ðß(Ø@¾ùùùI’:d8 Pø¢££uåÊÕªUKUªT1Ø 6o­Zµ’$8pÀp ðíÛ·O’Ô¦MÃI€½ `ùæíí­ºuëêüùóìÆïþç$IO=õ”á$À^P°€Ѿ}{IÒ®]» ' OVV–víÚ%''§Üï<(’¤mÛ¶Nž/¿üRIIIjÞ¼¹ÜÝÝMÇv‚‚ 9;;ëàÁƒJKK3(Û·o—$uîÜÙp`O(Ø@pvvVçΕ–––[B%ÍÆ%IÝ»w7œØ 6P`z÷î-IZ³fá$@Á;qâ„Ξ=«† ªnݺ¦ã;BÁ L`` \]]µmÛ6¥¤¤˜Ž¨uëÖI’ž}öYÃI€½¡`ÆÅÅEJMMåa(qr&3s&5rP°€Õ¯_?IÒªU« ' Î×_­sçÎé‰'žPíÚµMÇv†‚ ¨ÀÀ@U­ZUÛ¶mSll¬é8@øøã%IÆ 3œØ# 6P œœœ4tèPeeeiÅŠ¦ãù–’’¢U«VÉÕÕ5wBà¿Q°€7|øpY,-_¾\6›Ít _Ö®]«ëׯ«W¯^ªP¡‚é8ÀQ°€çíí­€€?^»ví2È—%K–H’‚ƒƒ 'öŠ‚ Š‘#GJ’æÌ™c8 wû÷ï×±cÇôØc©E‹¦ã;EÁ Å3Ï<£Zµji×®]úá‡LÇòdîܹ’¤ÃI€=£`…ÂÁÁA£G–ÍfÓìÙ³MÇîÚéÓ§µeË=øàƒ<Üü% 6Ph†*­^½Z—.]2¸+3gÎTvv¶F%'''Óq€£`…ÆÍÍM#FŒPFF†¦Nj:pÇ¢££ªŠ+êùçŸ7Ø9 6P¨BBBT¡B­X±BçÏŸ7¸#“'OVVVVî÷à¯P°€Båáá¡W^yEYYYš}Z«V­RåÊ•y¸¸#l Ð3F•*URXX˜Îœ9c:ð—&Nœ¨ììl½úê«rww7l ÐU¬XQcÇŽ•ÕjÕ˜1cLÇþÔÁƒµaÃÝ{ï½=z´é8 ˜ `E"$$D÷ß¿"""´}ûvÓq€ßÉÎÎÖ+¯¼"Iš4i’\]] 'Å(®®®š1c†$iìØ±ÊÌÌ4œ¸ÕǬï¾ûN 4àÉ¡à®P°€"Ó¿5oÞ\gÏžÕ|`:+11QãÇ—$Í›7OŽŽŽ†€â„‚ ‹Å¢9sæÈb±è­·ÞÒÅ‹MG$Io¼ñ†®\¹¢gžyF¦ã€b†‚ ©æÍ›køðáJNNÖ‹/¾h: hÙ²erssÓܹsMÇÅ(rï½÷žî»ï>EDD(,,Ìt”b7oÞTpp°l6›Þ~ûm=ôÐC¦#€bˆ‚ ¹Š+æîÁ¢+W®N„ÒjÊ”)ŠŠŠRÓ¦M5jÔ(Óq@1e±Ùl6Ó!@éÔ£G…‡‡«K—.Ú¼y³,‹éH(E8 €€988èøñãjذ¡éH ˜b‚ ³hÑ"U©RE[·nÕ¢E‹LÇA)’˜˜¨AƒÉjµjÒ¤I”k _˜`FmÛ¶M]»vU¹råtüøqÕ¯_ßt$”}ûöÕêÕ«õä“OjÏž=rtt4 cL°£õÒK/)--M}ûöUjjªéH(á>þøc­^½Z•*UÒ'Ÿ|B¹ò 6`\ZZšžxâ :uJ Phh¨éH(¡¾ýö[ùùù)--M«W¯VïÞ½MG%lÀ8gggmذAîîîúôÓOsŸ0 ¤øøxõèÑCiii5jå(0L°»®ž={ªL™2Ú³güüüLGB aµZ¨;wªeË–Ú»w¯Ê–-k:(!˜`v#((Ho¾ù¦222¤èèhÓ‘PBŒ;V;wîTÕªUµvíZÊ5P ˜`vÅjµªk×®Ú±c‡êׯ¯#GŽÈÝÝÝt,c‹/ÖÈ‘#U¶lYíÞ½[þþþ¦#€†‚ ؤ¤$ùûû+22RÚ±cGȓ͛7+((HÙÙÙ UÿþýMG%KD€Ýqww×öíÛU½zuíÝ»WÆ ÿ'ˆ»uìØ1õíÛWV«Uï¼óå(4lÀ.U¯^]Û·o—»»»ÂÂÂ4bÄJ6ܱÈÈHuîÜY©©©zñÅõæ›ošŽJ0 6`·|||ôùçŸËÅÅEK–,Ñk¯½f:ŠsçΩ]»vJHHP÷îݵ`ÁÓ‘@ GÁìZëÖ­µ~ýz•-[V³fÍÒ¤I“LG‚»xñ¢ôŸÿüG:tÐêÕ«åèèh:(á'ñ[*°sµk×–‡‡‡vìØ¡ýû÷+++K¦cÁΜ;wNº|ù²jÔ¨¡¨\¹r¦c€R€ 6`÷âââ4gÎÙl6988hÚ´iúûßÿΞlÈuòäIµjÕJ/^”$ýòË/:}ú´áT ´ `v-##CÏ<󌢣£Õ¬Y3}öÙg*[¶¬æÎ«_|QV«ÕtDvüøqµnÝZ±±±j×®F¥ÌÌL 4Hiii¦ã€R€‚ Ø-›Í¦aÆéÈ‘#ª^½º6lØ ^½ziË–-rqqÑÒ¥KÕ§OJ”Rl×®] P||¼ž~úimݺUï½÷žêׯ¯3gÎhܸq¦#€R€‚ Ø­‰'*,,LîîîÚ¾}»î¿ÿ~IRûöíµcÇUªTIëׯWÛ¶mg8-ŠÚòå˨7nhÀ€Z·nî¹ç•+WN¡¡¡*S¦ŒæÍ›§½{÷šŽ J8 6`—ÂÂÂ4mÚ49::jÕªUòññ¹åx«V­tøðaÕ¬YSGU‹-e(-Š’ÍfÓ¸q㬬¬,?^ÿú׿T¦L™Üs7n¬‰'Êf³ièСJLL4˜”t»;³oß>uìØQZ´h‘FŒñ§çÆÆÆêé§ŸÖW_}¥Ê•++,,L;v,´(Jׯ_×СC®2eÊhÉ’%:tèžkµZÕ²eK;vLƒ Ò'Ÿ|RÄi@iAÁìJtt´|}}§Í™3ç¶?“’’¢ÁƒkÆ rppÐäÉ“õÏþS‹¥£¨œ9sFAAA:{ö¬*W®¬5kÖ¨]»vù3çÎÓc=¦””­_¿^=zô(¢´ 4¡`v#..N¾¾¾ŠŽŽV·nÝ.GGÇ;úY›Í¦÷Þ{Oÿüç?eµZÕµkW…††ÊÝݽS£(¬_¿^C‡Urr²{ì1mذA5kÖ¼£Ÿ]´h‘^zé%yzz*22RU«V-ä´ ´¡`v!##C:tÐþýûÕ¸qcºzõª¼½½*__ßBHŒ¢––¦±cÇêÃ?”ÍfÓàÁƒõᇪ\¹rw| ›Í¦N:içÎ ÔÖ­[ 11(xÈ0Îf³iذaÚ¿¿ªW¯®M›6å©\“¤¶mÛê›o¾‘¯¯¯ÎŸ?¯'Ÿ|R“&MRVVV§Faûá‡Ô¤I-^¼XÎÎÎZ²d‰V®\yWåš$Y,}üñǪ\¹²¶mÛ¦eË–RbPZQ°ã¦Nª°°0•/_^Û·oWõêÕóu½|P_|ñ…&Ož,Iš>>r]'''Mœ8Q···Ž9"Íœ9“i6;©–-[jìØ±ÊÈÈÐØ±cõå—_ª~ýúù¾vŸ>}Ô§O%''kРA²Z­€‚ tøða 6L6›M|ðºuëVà÷hÑ¢…¾ÿþ{½ôÒKJOO×k¯½¦fÍšéûï¿/ð{!ïÒÓÓ5qâD5iÒD_~ù¥j×®­½{÷jæÌ™ºçž{ ì> .Ôý÷߯ÇkÖ¬Yv]Pºñ`Dtt´|}}§‘#Gæ.ß+L‡Vpp°Îœ9#'''=Z&LPÅŠ ýÞøs Ñ?þ('''½öÚkš0a‚œ å~;wîT§NT¦L}ýõ×jذa¡Ü”l È]»vMM›6Utt´ºuë¦ððp9::ɽ322ôÎ;ïè½÷ÞSZZš<==5yòd=ÿüórrr*’ øÍ™3g4fÌEDDH’š7o®?üP5*ô{¿üòËZ¸p¡6l¨¯¾úª@§ä@éCÁŠTFF†:uꤽ{÷ÊÇÇGG•««k‘ç¸té’ÆŒ£ 6H’6l¨÷Þ{O:t(ò,¥ÍÕ«W5}út-X°@ªV­šf̘¡Êb±I†””5nÜXQQQzýõ×õî»ïÉ}@ÉDÁŠÔ€¦êÕ«ëèÑ£ù~bh~íß¿_cÆŒÑwß}'IòóóÓÛo¿­Ö­[ÍU]»vM³fÍÒ¼y󔜜,ggg3Fo¾ù¦ÜÜÜŠ<ϱcÇÔ²eKIÒ¾}ûäïï_ä@ÉÀC@‘™1c†ÂÂÂäââ¢-[¶/×$©uëÖ:~ü¸V­Z¥‡~X‡R›6mÔ®];8pÀt¼!11QS¦L‘···¦M›¦ŒŒ ½ðÂ ŠŠŠÒÔ©S”k’Ô¬Y37NV«UC† Ñ7ŒäÅl H„……iàÀrppPxxx¡<14¿²²²¦)S¦èüù󒤦M›êÕW_UPPP‘íWR\¼xQsçÎÕG}¤7nÈÉÉIÔ„ T³fMÓñ$I™™™jÑ¢…¾ùæ >\Ë–-3 Cl Ð;vL­[·ÖÍ›75oÞ<=Út¤¿”™™©U«ViÖ¬YŠŒŒ”$y{{kôèÑå>]àNP°€B‘‘‘¡N:iïÞ½òññÑÁƒåîîn:V¾¥¥¥iýúõZ¾|ù-A¨_¿¾ž}öY=ýôÓzôÑGe±X ¦,< Úµk—Ö­[§;v(--M’äéé©*88XõêÕ3œ2o.^¼¨F)))IŸ|ò‰ d:(&(Ø@¡6l˜V¬X¡êÕ«ëèÑ£vñÄЂ¥uëÖiÍš5¹{µIRÕªUÕ±cGuìØQíÚµ“‡‡‡Á”ùcµZõí·ß*""B;vìÐW_}%«Õ*I*_¾¼ºvíª^½z©cÇŽºçž{ §Í¿O>ùDC† ‘»»»Nœ8¡|Ðt$P P°€7cÆ ýãÿ‹‹‹öíÛ§¦M›šŽTèNŸ>­uëÖiÓ¦Múþûï•ó+–ƒƒƒêׯ/___ùùù©eË–òöö6œöÏ¥¤¤è믿ÖÁƒuäÈ=zTIII¹Ç+W®¬öíÛëÙgŸU§Näììl0máèÑ£‡ÂÃÃÕ¦MíÙ³§ÄN#€‚CÁ ÔêÕ«Õ¯_?988(<<\ݺu3©ÈÅÆÆ*""BÚ½{·âãão9îéé©F©AƒòññQ£FT»ví"]B›••¥Ë—/ëäÉ“:yò¤~øá­#GŽèСC:|ø°ÎŸ?ÿ‡çzxx¨F¹ªU«&yxxÈÓÓS*[¶lnçììœûÄÄDÙl6¥§§+55U7nÜP||¼âââtõêUÅÇÇëâÅ‹ºpá‚.\¸ Ë—/+33ów\]]õÄOÈßß_¾¾¾jÑ¢E‰Ø;ïnmÙ²EݺuS¹råôÍ7ߨ~ýú¦#;FÁ Dtt´|}}§#FhÑ¢E¦#Ù­«W¯æNŒEFFê‡~йsçnYŠYØœœœôÀ¨AƒjРAîDÝÃ?,''§"ËaÏ‚ƒƒµ|ùr=þøã:zô¨Ê”)c:°Sl ß’’’äïï¯ÈÈHµoß^Û·o/ñË C|||î„Ù… ô믿*>>^ñññ¹Sh¹E\ZZšnÞ¼)IªX±¢,‹î¹ç¹¸¸¨|ùòòðð——WîôÛC=”;÷ÀPÝÆ7ôè£êüùóš0a‚¦L™b:°Sl _222Ô©S'íÝ»W>>>:xð`©\Rˆ’éàÁƒjÓ¦$éðáÃjÖ¬™áDÀ9˜Š·Q£FiïÞ½ªZµª¶oßN¹†Åßß_cÆŒ‘ÕjÕ Aƒ”’’b:°Cl Ïf̘¡¥K—ÊÅÅEŸþ¹ªW¯n:PàÞ~ûmùøø(**Jo¼ñ†é8À±DäIxx¸zöì)‹Å¢7ª[·n¦#…æÄ‰zâ‰'”™™©ˆˆµoßÞt$`G˜`wíØ±c8p l6›¦M›F¹†¯aÆš2eŠl6›† ¦k×®™Žìlà®\ºtIMš4Q\\œFŒ¡E‹™Ž «Õª'Ÿ|R‡VŸ>}ôÙgŸ™Žì¸cIIIò÷÷Wdd¤´sçN999™Ž™óçÏ«Q£FJNNÖêÕ«Õ»woÓ‘€`‰(¸#V«UAAAŠŒŒ”ÂÃÃ)×Pêx{{köìÙ’¤‘#Gêßÿþ·áDÀP°€;2jÔ(íÝ»W^^^Ú²e‹ÜÝÝMGŒV`` 4lØ0± P°€Ûš1c†/^,mÙ²E=ôéH€QË—/—§§§vîܩŋ›Ž c6ð—6oÞ¬   eggkݺuêÑ£‡éH€]ذaƒzöì)WWW}÷Ýwª]»¶éHÀ&ØÀŸ:vì˜úöí+«ÕªwÞy‡r ø/=zôÐÀ•’’¢ÊjµšŽ a‚ ü¡˜˜5mÚT¿þú«‚ƒƒµtéRÓ‘»“˜˜¨† êòåËzûí·5~üxÓ‘€làw’’’äïï¯ÈÈHhÇŽ*[¶¬éX€]Ú»w¯Úµk§2eÊèèÑ£jܸ±éH ˆ±DÜÂjµªOŸ>ŠŒŒTƒ N¹ü…€€=Z4hnÞ¼i:(blà£FRDD„¼¼¼´qãF¹»»›ŽؽéÓ§«^½z:uê”Æg:(b,¹fÏž­±cÇÊÅÅE{÷îU³fÍLGŠãÇË××WV«U»wïV›6mLGE„ 6 IÚ¼y³^ýuY,-_¾œr ¸KMš4Ñøñã•­¡C‡êúõë¦#€"BÁtüøqõíÛWV«Uï¼óŽúöík:P,7NM›6ÕÅ‹5zôhÓq@a‰(¥\LLŒZ´h¡˜˜ 2Â=÷ÜçÅÏ?ÿŒ^½zÉŽDDDDeÀ‘ŽIJJBûöí‘””///øûûsÅP"-ôõ×_cæÌ™¨]»6.^¼KKKÙ‘ˆˆˆ¨”Ø`#""Ò!YYYèܹ3¢££Ñ¹sg;vŒ+†i©¼¼l°Ur‡ÆÔ©S¡P(°e˸»»ËŽDD/éûï¿G­ZµpâÄ ¬]»Vv"""zI¼Q%N:!==Ÿ~ú)/^,;•Rpp0ú÷ïjÕª!::Íš5“‰ˆˆˆJˆ 6""¢JêîÝ»hÛ¶-’’’àåå( Ù±ˆ¨ ¼½½±uëV¸¸¸ ,, FFF²#Q ðQ""¢J(++ žžžHJJBûöí±eË6׈tÀš5kP¿~}DFFbÉ’%²ãQ q‚ˆˆ¨’Q©TðôôDHHlmmÎC‰tÈéÓ§áîî„……ÁÕÕUv$"""*'؈ˆˆ*™O?ý!!!077ÇáÇÙ\#Ò1o¾ù&>úè#äææbÔ¨Qxüø±ìHDDDT 6؈ˆˆ*‘ 6àË/¿„±±1‚‚‚`kk+;U€%K– eË–¸zõ*>ùäÙqˆˆˆ¨˜››#88ÖÖÖ²#‘$>>>xçwššŠ±cÇ‚'¡i6؈ˆˆ´Ð÷ß___#((Í›7—‰ˆ$R(ؼy3,,,pøðalܸQv$"""z¯ÁFDD¤eBCCñÎ;ï ;;~~~;v¬ìHD¤%~úé' <¦¦¦ˆ…­­­ìHDDDN°i•øøxxzz";;³gÏfsˆ x÷Ýwáåå…ÌÌL¼÷Þ{P©T²#8ÁFDD¤5’““Ѿ}{\¿~ï¾û.vïÞ …B!;i™ÀÁÁIIIX²d æÎ+;‘ÞcƒˆˆH deeÁÝÝhÛ¶-BCC¹b(=×ñãÇÑ£G!""ŽŽŽ²#é5ž"JDD$™ÞÞÞˆˆˆ@ƒ pàÀ6׈腺uë†)S¦ ;;#GŽÄÓ§OeG"""Òkl°I6wî\ìÞ½æææ†µµµìHDT |ùå—hÒ¤ .^¼ˆO?ýTv"""½ÆSD‰ˆˆ$Úºu+¼½½allŒÃ‡ÃÝÝ]v$"ªD"##áææ†¼¼<„††âÍ7ß”‰ˆˆH/q‚ˆˆH’ÐÐPLœ8°zõj6׈襹¸¸àÓO?E^^FGÉŽDDD¤—8ÁFDD$Á•+WЮ];¤§§cöìÙX¶l™ìHDTIåää C‡ˆŒŒÄ˜1c°eËÙ‘ˆˆˆôlDDD–œœ 777$$$ _¿~Ø·o …ìXDT‰ýñÇhݺ5?~Œýû÷£_¿~²#éž"JDD¤AOž<‡‡жm[ìØ±ƒÍ5"*³fÍšÁ××àããƒÿýWr""""ý‘†!0nÜ8DDDÀÆÆ!!!011‘‹ˆtÄÔ©SѵkWüû￘0a‚ì8DDDz… 6""" ™;w.`nnŽC‡áÕW_•‰ˆtˆB¡ÀÖ­[Q³fMìß¿[·n•‰ˆˆHoðlDDDðã?bäÈ‘P*•8|ø0ºwï.;é¨üŸ75jÔÀï¿ÿŽúõëËŽDDD¤ó8ÁFDDTÁBCC1vìXÀÚµkÙ\#¢ 5bÄ 4>Ę1c——';‘Îcƒˆˆ¨%$$`àÀÈÎÎÆÇŒI“&ÉŽDDzà»ï¾CíÚµqêÔ)¬ZµJv"""ÇSD‰ˆˆ*Hrr2ÜÜÜ€¾}û"((J¥Rv,"Ò‡BŸ>}P¥JDFF¢eË–²#é,N°U€ììlôïß hÛ¶-vîÜÉæiT¯^½0nÜ8>...ÈÎÎÆ¡C‡ðöÛoËŽDDD¤3xŠ(Q%''ãwÞArr2úöí‹+VÈŽDDTˆ½½=/^¬^ˆ%55Uv$"""Á 6""¢2ÈÎÎFÏž=qêÔ)8;;ãÌ™3\1”ˆ´–J¥‚»»;~ýõW <»wï–‰ˆˆH'p‚ˆˆ¨”ò§@N:³¹FDZM©TbÛ¶m¨^½:±cÇÙ‘ˆˆˆtlDDD¥´xñb zõê8tèllldG""*Vƒ °zõjÀ”)S””$9QåÇSD‰ˆˆJ! #GŽ„‚‚‚зo_Ù‘ˆˆ^Jß¾}qàÀtïÞ¿üò  …ìHDDD•'؈ˆˆ^RXX¼½½!„ÀÚµkÙ\#¢JiÓ¦M°¶¶Æ±cǰnÝ:Ùqˆˆˆ*5N°½„„„¸¹¹!99“'OÆ·ß~+;Q©íÛ·žžž011Á… ФIÙ‘ˆˆˆ*%6؈ˆˆJèÁƒpuuEBBúöí‹   (•JÙ±ˆˆÊdôèÑøá‡àêꊰ°0ÊŽDDDTéðQ""¢ÈÎÎÆ Aƒ{{{ìØ±ƒÍ5"Ò ß|ó êÕ«‡ß~û K—.•‡ˆˆ¨RâQ Œ1°±±Á¹sç¸b(锓'O¢[·nP*•G›6mdG"""ªT8ÁFDDT ___ÀÄÄ`sˆtÎ[o½…?ü9995j?~,;Q¥Â 6""¢ÀÈ‘#a``€   ®JD:ëÉ“'hÓ¦ .]º„?ü«W¯–‰ˆˆ¨Ò`ƒˆˆè9"""Ð¥K•‰ˆˆHk°ÁFDDzÇ××ßÿ=LLL Ù‘ˆˆ*…/¿üvvvˆÇçŸ.;‘Ö`ƒˆˆôJPPæÎ …B;w¢mÛ¶²#U¦¦¦Ø¾};”J%V®\‰3gÎÈŽDDD¤Ø`#""½‘#GB¥K—¢oß¾²#U:mÛ¶Åܹs¡R©ðÞ{ïáÑ£G²#IÇé…ÄÄDxxx ++ “&MÂìÙ³eG""ª´>ÿüs´nÝ7oÞÄôéÓeÇ!""’Ž 6""ÒyéééèÓ§’““áîîŽ5kÖÈŽDDT©aûöí¨Zµ*üüüpàÀÙ‘ˆˆˆ¤bƒˆˆtšJ¥‚§§'âããaoo   ÉŽEDTé5oÞË–-øøø 99Yr""""yØ`#""6uêT„††ÂÚÚ!!!077—‰ˆHg|øá‡xë­·pïÞ=Lœ8Qv"""iØ`#""åëë‹ 6ÀÄÄ@ýúõeG""Ò) …Û¶mƒ¹¹9‚‚‚ðÃ?ÈŽDDD$lDD¤“BBBðÙgŸA¡P`ûöíhÛ¶­ìHDD:©nݺøæ›oüo¢íöíÛ’ilDD¤s"""0lØ0¨T*,]º”‰ˆH§5 žžžHOOǘ1c „‰ˆˆH£Ø`#""’””„ ++ ãÇÇìÙ³eG""Ò ß}÷j×®“'ObõêÕ²ãilDD¤3ÒÓÓÑ«W/üý÷ßpwwǺuëdG""ÒÖÖÖØ¸q#`îܹ¸|ù²äDDDDšÃé•J…¡C‡">>-[¶DPPŒeÇ""Ò+7nž»]FF:wîŒáÇ—Ëñh 6؈ˆ¨ÒŠŠŠÂ°aàR©°téR 6Lv$""ú?_|ñpíÚ5|òÉ'²ãÑÿY¸p!æÎ‹¬¬¬BõéÓÕ«WÇÎ; Üïçç‡ÀÒÒ²Ìû¿qãBCCáããSæçÒ&:Ó`“9ê˜/77NNNêýQÅIJJ€••…÷Þ{³gÏ–‰ˆˆžQ¥JøûûÃØØëׯÇ/¿ü";;v,,,,ðå—_zÌÐÐcÆŒŸŸŸú¾`ß¾}?~|¹ìßÏÏÎÎÎpvv.—çÓ:Ó`Ë'cÔ1ßêÕ«Q³fÍ2å'"¢â¥§§£W¯^HJJ‚»»;6nÜ(;ÁÁÁ‹-‚cÇŽEjjªìHDDzO©TbÕªUX±b’’’ =>vìXDFFââÅ‹€üuêÔÁ[o½U`;+++Ô¬YS}[·n]±ûÎÍÍÅÖ­[unz ÐÁ›¬QÇÛ·oÃÏÏóçÏ/õsQñò¯ç;;;ÁØØXv,""zŽ3f C‡øë¯¿ðþûïËŽCDDºv튮]»yHÆ ѵkWõÛæÍ›1nÜ8(ŠÛÝ¿iiiêÛ”)SŠÝï‘‘¡s×_t°Á&kÔqÊ”)X´hÌÌÌÊôþúë/ɉˆˆJ&!!·oß–£BØÙÙaòäÉEž‰×¯_?`Ô¨Qððð@íڵ˼¿ÄÄD=zT'Ot°ÁhvÔñÑ£Gøä“OJt®1•^HH¦M›…B-[¶ mÛ¶²#ÑK?~4h€zõêñºbψ‹‹Ã°aàR©°téRxyyÉŽDDD¥àçç{{{üòË/ذa&Ož,;’ÖcýAT1îß¿ÈÈHDEE©ÿû÷ßÚîµ×^ƒNLÞ¶iÓ¦P3­fÍš¸ÿ~‘Û/_¾Ë—//Ñó”ÄÂ… ±pá—þºÊBglÅ:¾ÿþûå2êØªU+ܼySý縸8ôîÝÑÑÑxõÕWKý¼Úæüùó¨S§,,,dG!ªpÿüóâââ‡øøxÄÇÇãêÕ«ÈÌÌ,õs¦§§«ÿ?%%¥Øí `ccƒ-ZÀÁÁ°··GÓ¦MaddTê•QRRz÷,xyy9LDD•CíÚµñÝwßaРA˜5kºwïŽÆËŽ¥XUœ‡"::ZÝL‹ŒŒÄ­[· m÷Ê+¯ÀÅÅmÚ´QÿׯÆ^^^رc‡æƒS¥¢³ 6࣎þþþ…îÏu\±bÅsOí¬Y³f?»¹¹áèÑ£…¶322‚úÏ÷îÝÔ©S††ºó×ëããÔ­[NNNpvv†££#Q·n]ÙñˆJM¥RáâÅ‹ Cxx8Μ9ƒÄÄÄ"·µ¶¶FýúõÕŸòÖ®]–––n&&&011˜ššª?NOOG^^òòòžžŽÌÌL¤¤¤ 99÷ïßGJJ îܹ£þtúöíÛHLLDbb">¬ÎP­Z5´iÓ;vD‡о}{n|gee¡wïÞHJJ‚»»;¶lÙRè”~""ª\ˆQ£Faûöí9r$ T*eÇÒ(ÖDçñãLj-0víÚ5äååØÎÔÔÎÎÎpqqQ7Óx}_* €×@ "988 >>NNNøóÏ?‘‘‘Qhkkk8::ªoNNN°µµ…N^ât@RRŽ9‚#GŽàøñã>Ý€ZµjøÔÖÞÞMš4©©©Æ2æåå!)) —.]*ðiö•+W““£ÞN¡PÀÑÑo¿ý6Þ~ûm¸¹¹éLs_¥RÁÓÓ!!!°µµÅùóçaii);•ƒ´´4´jÕ ‰‰‰X´h>ûì3Ù‘*ë¢ò—››‹‹/˜L‹W_º)Ÿ±±±úÚ_ùÍ´æÍ›—¸¹Ÿ?Á€áÇWÄ¡P%æëë‹9sæ°ÁF/–ß`‹‹‹C‹-€˜˜\¸p111ˆErrr¡¯333C«V­Ô“nNNNhÙ²%GËIš‹/"00ÁÁÁˆ‹‹S߯T*ѲeKtèÐnnnèÔ©“VOe>~üQQQ8{ö,ÂÂÂpîÜ9¤¦¦ª777G=0hÐ ôéÓGýive4uêT¬[·ÖÖÖç'ŠDD:&44ݺuƒ¡¡!Ο?gggÙ‘Êë¢ò“——‡k×®˜L‹ÅãÇ l§T*ѼyóÍ´V­Z•éú‚l°Ñ‹°ÁF%òlƒÍÞÞ¾Èm‹ØØXuã­¨wccc´lÙŽŽŽêI7˜™™Uôažºzõ*víÚ…ÀÀ@\¾|Y}¿ú×nݺÁÜÜ\bʲB 66Vý‰xxx¸ú;SSSxxxàÝwßEïÞ½Q¥JÉiK.ÿÂ×ÆÆÆ8~ü8:uê$;U€>ú«W¯FóæÍªU«ÊŽTf¬?*oýAÚåÖ­[ê©´¨¨(DGGãáǶQ(°µµ-pÝ4''§rŸüdƒ^„ 6*‘’4ØŠ’ššŠ .àÂ… êæÛÕ«W ÷n``;;;899©'ÝZ·nÍk:P©=~ü»wïÆ¦M›®¾¿eË–úè#¬\¹Rv¤RaýQùë’ëï¿ÿ.°šgddd‘+[¾ñÆ…!øïõÓ+lô"ù 6à 6AT{{{@ÄÅÅ•ù¹=z$ÂÂÂÄ·ß~+¼½½…³³³066ù¯ÁgouëÖ}ûö .ÁÁÁâöíÛåp4¤Ë~ÿýw1yòdann®~5iÒDÌŸ?_\ºtIv<)222ÄÎ;…§§§¨R¥Šúï¥}ûöbË–-"++KvÄBâââÔßà ȎCDD)ŒŒŒ„B¡¡¡¡²ã¼Ö…UÆúƒ4+55UüòË/bÉ’%¢ÿþÂÆÆ¦È÷„¯¾úªèÕ«—˜?¾8pà€¸wïž´ÌÇD@@@…<¯^½„··w¡ûóòòD£FÄÊ•+Edd¤ \\\D^^žz›üûsrrŠ|$ñÎ;ïˆW^y¥ÈíLMM ÜŒŒŒDõêÕÕOŸ>]4mÚT˜˜˜ˆW^yE :Tüý÷ßÏ=–âö'„Û¶mM›6fffÂÆÆFìÛ·ï…?/³}Iö_Þ–-[–ÿºeƒž¯<lEÉÎÎ111bóæÍbêÔ©¢cÇŽÂÌ̬ȰÖÖÖ¢{÷îbÖ¬YbçÎâêÕ«B¥RUH.ªòòòÄÁƒE·nÝÔ¯“jÕª‰Ñ£G‹°°0Ùñ´JJJŠX½zµhÙ²¥úïÊÊÊJÌ›7OüóÏ?²ã !„¸s玺Àòòò*P8‘n[¸p¡ êÕ«'ÒÒÒdÇy!Ö%WêªX=§OŸ_ýµ:t¨°µµ-ò½ž¹¹¹pwwŸ|ò‰Ø³g¸uë–ìèTtƒmß¾}ÂÌÌL~~~¢yóæêׂ­­­X·nÖåÚ <<\Œ1B ¢jÕªÂÇÇG\½zUZ¦ÌÌLѦM@tîÜY<}úTZ""Ò¼œœáêê*ˆ÷Þ{Ovœ"±þ(m¬?¨|=}úTüöÛobýúõb̘1¢E‹B©TzW­Z5Ѿ}{ñÁˆíÛ·‹?þøCë'*ºÁ–““#^{í5±qãÆ÷6L :Tñÿiß~û­¨S§ŽÈÌÌ,pq¤’lwýúu¡P(Dttôs·ùõ×_…R©,ö˜ž·¿zõê‰ãÇûõ¥Ý¾¸ýW5Ø8êøâQG!þ÷ÉÑѱØo¾ŒQG ¶çILLÁÁÁbáÂ…¢ÿþ¢nݺE6ÝŒ…³³³ðööëÖ­aaa…>  Ê)''GlܸQÔ«WOýýîÔ©“Ø·oŸÖÿRÖF‰‰‰bæÌ™¢F€000#GŽþù§Fs俿о}ûªß¨üûï¿Ý?i‡«W¯ @ÉŽ£Æú£|UŒ1B\»vMv4z ¹¹¹"..NlÙ²ELš4I¸¸¸yù###áää$|||ĦM›DLLŒFÞÇ–·Šn° !Äܹs…«««úÏ©©©¢jÕªâĉBˆÿßyò䉰··óæÍ+py4ØæÌ™#Z·nýÂç™7o^œ/³¿Û·o bùòå¢~ýúÂÒÒR >\¤¦¦ù/»}qû¯(k°qÔñù£‹ù–/_.ºtéRì1ÈuÔ¦[QRRRÄñãÇÅ—_~)† &š5kVä§$¢I“&bذaâË/¿G)))²ãS åææŠ~øA4jÔHý=íÙ³§8þ¼ìh:!==],]ºTXXXÂÐÐPŒ;Vc£ùÓ§OWÿÐtsˆˆ´ËÚµkÕ—‘y½%!XT´¢êoooqóæMÙÑè?òòòĵk×D@@€øè£DÇŽ…©©i‘﹚7o.F%Ö®]+Î;'?~,;~¹ÐDƒ-z,>>^!Ä7ß|#5j¤Bz¶?rüøqabb"îܹSn ¶œœQ»vmñý÷ß?÷9öïß/ÌÌÌDddd±ÇSÔþbbbáãã#²²²Drr²èÒ¥‹x÷Ýw‹|Ž—Ý¾¸ýW5Ø8êøb·nÝMš4'Ož,ñ7_“/mo°åÙÅÆŽ[¢Å,XÀÅ´ThhhS1Þzë-qöìYÙ±tRZZš˜?¾úBÍÆÆÆbæÌ™:º~ýzõ¾ò?#""ý•——'ºwï.i9XhNZZšX°`AúcÆŒ"==]v4½•˜˜(öîÝ+æÌ™#ºuë&jÖ¬Yä{©† Š!C†ˆåË—‹S§N‰‡ÊŽ^a4Ñ`Bˆnݺ‰?üP!D«V­Ä²eËÔý·àáá!¼¼¼Ê­oTäpT¾={öˆš5kŠcÇŽ•èXŠÚߟþ)ˆäädõ}GÕªU+ò9^vûâö_Q4z 6Ž:>_Ÿ>}D``àK}óÙ`{yÙÙÙ"66V½˜B§Nž»˜‚•••z1…;vp1IþüóýçTã IDATO1`Àõ÷ÅÉÉ©TçÞÓËKII3fÌP7¦_{í5±yóærÿwpèÐ!¡T*…B¡?þøc¹>7U^IIIêK¢øùùit߬?äÑTýAýóÏ?âàÁƒbÁ‚¢OŸ>¢V­ZE¾Gzýõ×E¿~ýÄ¢E‹Ä‘#GÄýû÷eG×(M5Ø…¥¥¥8sæŒ044,p «ÿö®^½*ŒÅ·ß~[.}“·ß~[Œ?¾ÈǶlÙ"jÖ¬)~ýõ×KQûËÍÍ5jÔ(ðú9v온Zµj‘Ïñ²Û·ÿŠ¢ÑG‹öÓO?‰ž={–èŠÛEÑ•[Q^v177·‹)ð"ì#++KÌ™3‡Å•øï› ggçýŒ,‰¸¸8õ'ÕsçÎ-—ç$""ݱcÇ@T¯^½D—\)+ÖÚ£¨&gyÕú.--Mœ8qBøúúЏ®à³7KKKѳgOñÙgŸ‰ýû÷‹¿þúKvté4Õ`{úô©°¶¶ 4 (ðXQ}€iÓ¦ ++«bû?aaa€ÈÈÈ(têîíÛ·…A‘ÿÖÖ¬Y#,,,DDDD‰ãEû›6mš˜4i’xòä‰HMM]»v}aßäe·/Éñ–7¯"ÊQÇ‚>|(6l¨¾ÞlÚãÎ;/½˜ÂÚµk¹˜B98}ú´°³³SÿýΚ5‹§ZàĉêÓd ÅÌ™3Õ§ò—Æ_ý%llláååU`q""¢|ƒÀÿÈÍÍ­°ý°þÐN'Ož-Z´P×3fÌ(Sý¡o²²²Dxx¸X½zµðòòMš4 …¢ÈA‚Î;‹éÓ§‹;wŠëׯˎ®•4Õ`Bˆ™3g âàÁƒî/ªðàÁaiiYl ¨÷³Ïš7ožprrzî×ZD2ÿçò…  ü¹¸ý=yòDøøøKKKQ£F 1dÈgþý÷ù^vû’oyÓxƒ£Ž…ŸÃÐÐPXZZ KKKõ$‡¥¥e±«&±Á¦yù‹)|õÕWÅ.¦`ggW`1}Ÿ.´´4áãã£þ¥ïêêª÷¯9móôéS±páBQ¥Jü9e&33S´mÛVíÚµã$(=WJJŠxýõ×ñÕW_•ûó³þÐ~OŸ>_|ñEú£¤Cú$;;[\¸pA|ÿý÷bܸqÂÑÑQz¯R¥Jáêê*Þÿ}±uëVqñâÅ m^ëM6بòÑxƒ£Žegg‹;wî¨oÄ­[·DVVV©·¼±Áö|Ï.¦0nÜ8.¦PJ¿þú«z<ÝÔÔT¬ZµŠ§ch±Ë—/‹Ž; B¡PˆiÓ¦•øçPnn®èÛ·¯ lmmÅ¿ÿþ[Ái‰ˆ¨²;|ø°P(¢J•*åZ²þ¨\þøãõÇÔ©SufuÊ—¥R©Ä¥K—Ä?ü ¦L™"Úµk'ªV­Zèý‡¡¡¡pppcÇŽ6lQQQ";;[vüJ‹ 6z7Ø„à¨cQÏ÷¢¿mudƒí嵘BõêÕ‹ü¾YZZêõb ÙÙÙbîܹêIÀN:iä+Tv*•J¬^½Z]ÌÙÛÛ—ègÄ'Ÿ|"sssõéñDDDÅ™8q¢ Ê<ùÌú£òR©TbÍš5/]Tvׯ_»ví3gÎo¾ùf‘ï- …hÒ¤‰ðòò«W¯gÏžåé´åŒ 6z‘ü›BÝeDÿåàà€øøxÄÅÅÁÞÞ^vœJ)// ˆÅ… páÂÄÆÆ"99¹Ð¶fffppp€³³3œœœàè舖-[ÂØØXBòŠsóæM 2‘‘‘022‚ ðÉ'Ÿ@©TÊŽF/!>>^^^ˆGÕªU±|ùrL™2¥Èm7l؀ɓ'ÃØØ‡†»»»†ÓQe•™™ GGG$$$`öìÙX¶lY©ž‡õ‡n¸té† ¦®?¾úê+L:Uv¬rq÷î]DFF"**Jýß”””BÛÕ«WmÚ´‹‹ \\\кuk˜››KH¬?¼¼¼°cÇ`øðá²ã–ñõõÅœ9sÀ½l篿þBtt4bcc‹èèh$&&ÚÎØØ-[¶„££#Ѻuk888ÀÌÌLBê²;rä¼¼¼šš ;;;üøãpqq‘‹JéÉ“'˜3gÖ¬Y!†ŽM›6ÁÄÄD½Í‰'гgO¨T*lß¾#GŽ”˜˜ˆˆ*£óçÏ£cÇŽ€Ó§O£C‡/õõ¬?tKIêm—’’R ‘‰»wïÚ®V­ZpqqQ7ÔÚ´iƒW_}UBbýƽH~ƒÍPv"}U§NÔ©S}ûöUß—ššŠ˜˜ÄÄĨ'Ý®]»¦ž|Ëg``[[[´nÝŽŽŽprr‚““¬¬¬dJ‰!°dÉÌŸ?yyy8p ¶nÝŠêÕ«ËŽFePµjU¬Zµ ]ºtÁ¨Q£°cÇ\ºt {÷îE£FB¥RaöìÙl®Q©´k׳gÏÆ’%KðÞ{ï!66¶D6²þÐMEÕ/^ÄÞ½{akk+;^!ˆŽŽ.ÐL»qãF¡íÌÍÍѺuk¸ººª§ÓÞxã ‰‰¨4Ø`#Ò"èÚµ+ºvíª¾/33¿ÿþ;bccÕ·‹/âÚµk¸vívîܩ޶nݺꆛ³³3Q·n]‡R@FFFŒàà`(•JøúúbÖ¬YP(²£Q9éׯ"##1`ÀüþûïhÓ¦ 6mÚ„éÓ§#==^^^Xºt©ì˜DDT‰Í›7‡BLL ¦OŸŽ7¾p{Öº/¿þðôôD\\\\\°k×.ôìÙSZ¦§OŸâ÷ß/ÐL»rå T*UíLLLàääT`2ÍÎÎŽ¯O¢JŒ 6"-gjj 777¸¹¹©ïËÉÉÁåË—qáÂÄÄĨO3MLLDbb"BBBÔÛZXX uëÖêkº9;;£qãÆ000ÐHþ{÷î¡OŸ>ˆŽŽ†••vî܉nݺidߤYvvvˆˆˆ€··7~úé' 6 íÛ·Ç믿Ž7²`$"¢2166†¿¿?Ú´i???ôë×½{÷.r[ÖúÃÎÎçϟǸqã°{÷nôéÓë×¯Çøñã+|ß¹¹¹¸|ùrfZ\\rrr lgddGGǧz6oÞ††|;N¤Kø/š¨222B«V­ÐªU+Œ3@ÁÅbbbÔ×wKNNƱcÇpìØ1õ×ç/¦ðì¤[E,¦péÒ%ôêÕ ‰‰‰hܸ1:¤•cûT~ÌḬ̀{÷n4iÒ‹/Æ™3g0sæLT«VMv4""Ò-Z´À’%K0cÆ Œ7/^„¥¥emXè333ìܹvvvX´h|||pãÆ ,]º´Ü>àBàÚµk®›ƒ¬¬¬Û)•J´hÑ¢@3­U«V¨R¥J¹ä "íÅ‘Ž000€ììì0xð`õýÿ]LáÂ… ¸}û6ÂÃîÞÎÈÈööö嶘©S§Ð¿¤§§£cÇŽØ¿¡˜t“B¡À¢E‹Ð°aCL˜0+V¬ÀÝ»w±uëV[—ˆˆ4oÚ´i8pàN:… &`Ïž=êÇXè/…B/¾ø 6„|}}qûömlÛ¶­TõÇíÛ·ÕÍ´ÈÈHDGG#==½Ðv¶¶¶Nótvv®´‹‘QÙ°ÁF¤ãÊc1gggõB %YLáðáÃ4h²²²0dÈlÛ¶ U«V­°c$í4f̼ñÆ8p vìØG!00¯""*lÛ¶ Ø»w/üýý1räHÖ=z´ºþعs'=z„Ÿ~úé…¯…þù§ÀižQQQø÷ß mgccS ™æââ‚W^y¥"‡ˆ*6؈ôPQ‹)dee©§Übcc]`1…]»v©·Í_L!ÒíÙÅöíÛ‡¡C‡";;'NÄúõëyí-=Ö­[7œ} SSS¤¥¥h¤EFFâÎ;…žÃÒÒ®®®j¯½öš„£!¢Ê‚ 6"ð¿•Œž·˜Â³“n¿ÿþûsS¨S§._¾ •J…3f`ùòå,n ÎÎÎ8uêºwïŽãÇãí·ßÆÏ?ÿ sssÙшˆ¨3f BBB°ÿ~L˜0BÖ¤æììŒÓ§O£[·n8qâš6mŠªU«âúõëBضzõêpvv.0™Ö°aCIɉ¨²bƒˆžëÙÅF  ðb ù+™&''#55ðùçŸã‹/¾˜œ´M‹-púôitïÞgÏž…‡‡Ž9Ùшˆ¨óðð@pp0„¬?¨æÍ›ãÌ™3èÚµ+nß¾ ¨Zµ* L¦5mÚ’ÓQeǽ”¢S8xð  €œœ,X°óçÏ—œ’´QãÆqúôitìØgΜÁ€pàÀ.|@DD¥rðàALš4 BÖô\5R×IIIèØ±#<ÈúƒˆÊÛôDT&¡¡¡x÷Ýw‘““ƒ™3g²¸¥ªW¯BCCQ«V-=zƒFnn®ìXDDTÉä×ÙÙÙ¬?¨XõêÕÃÉ“'Q»vm?~\]»½,///( Þx+p›3g6؈¨ âââàááÇcÊ”)øê«¯dG¢J qãÆ8vì,-- oooÙ‘ˆˆ¨aýA¥akk‹£GÂÊÊ !!!3fL¡k±•…€À.T$ÄÇÇ#..ööö²ãIJJBûöí‘””///øûûC¡à…©ä"""àî¬,,[¶ ³gÏ–‰ˆˆ´ë*+ÖDTQÔ 6¢aƒž•žžŽN:!>>îîî8|ø0¯cA¥OOOäååÁßß^^^²#‘–býAå…õUž"JD/E¥RaРAˆ‡½½=‚‚‚XÜR©õíÛk×®…ÞÞÞøõ×_eG"""-ÄúƒÊÓëÓ§OËŽDD:@!xn(½„?þ+V¬@­ZµÙ‘HÌ™3¾¾¾¨U«¢££Q§NÙ‘ˆˆH‹°þ Š_¼úê«ˆŽŽæëŠˆÊ„ 6"*±={ö`ðàÁ022ÂÉ“'áææ&;é•J…>}úàÈ‘#pssÃÉ“'9™@DDXPÅÉËËCŸ>}pøða´oß§NbýAD¥ÆSD‰¨D._¾ ooo!°zõj·T®”J%РA„‡‡cÚ´i²#‘`ýAÉÀÀhذ!Î;ÇúƒˆÊ„lDT¬¬¬,´nÝW®\ÁèÑ£±uëVÙ‘HGÅÆÆ¢C‡ÈÊÊB@@†.;IÂúƒ4…õ•N°Q±fΜ‰+W®ÀÙÙëׯ—‡t˜££#6lØxÿý÷qûömɉˆˆHÖ¤)ÏÖ“&M­[·ä"¢J‰lDôB!!!èß¿?ªU«†èèh4mÚTv$Ò^^^رc:vìˆS§NA©TÊŽDDDÄúƒd1bXQ©p‚ˆžëÞ½{7n„X±b‹[Ò˜õë×£~ýú8{ö,|}}eÇ!"" býA²<[,]ºTv"ªd8ÁFDÏÕ¿£oß¾–‡ôÌÙ³gÑ¥K 22­Zµ’‰ˆˆ4€õÉ”_( DEE±þ ¢ãé§Ÿ~Bpp0^}õUøùùÉŽCz¨cÇŽ˜5krrr0nÜ8¨T*Ù‘ˆˆ¨‚±þ Ù:vìˆÙ³g#77—õ½N°Q!<@óæÍqïÞ=ìØ±Æ “‰ôÔÓ§Oáèèˆ+W®`ÅŠ˜1c†ìHDDTAX¶`ýAD¥Áâíí­[·¢wïÞøùçŸeÇ!=wæÌ¼ùæ›011A||<4h ;UÖ¤MΞ=‹Î;ÃÄÄqqqhذ¡ìHD¤åxŠ(ŽmÛ¶¡zõêêåʉdêÔ©&L˜€ÌÌLL›6Mv""ª¬?HÛtìØ'NdýAD%Æ 6"RBÀÕÕQQQX³f >øàÙ‘ˆéééhÚ´)îÝ»‡£G¢{÷î²#Q9aýAÚêáÇhÒ¤ îÝ»‡_~ù=zô‰ˆ´'؈Hmûö툊ŠBóæÍ1yòdÙqˆÔÌÍͱdÉÀôéÓ‘››+9•Ö¤­jÔ¨eË–`ýADÅã222ФIܽ{—ŸÐ‘VÊËËCÛ¶m…uëÖáý÷ß—‰ˆˆÊˆõi»gëµk×bÊ”)²#‘–âV­Z…»wï¢wïÞ,nI+`åÊ•P(øâ‹/‘‘!;•ëÒvXµj•ºþxôè‘ìHD¤¥” ,X ;ÉõàÁ :¹¹¹Ø¿?¬¬¬dG"*R½zõpñâEDFFÂÌÌ :u’‰ˆˆJ‰õUuëÖÅåË— SSStîÜYv$"ÒBœ`#"|ýõ×HKKÈ#дiSÙqˆ^è‹/¾€R©ÄŠ+žž.;•ëªL.\¥R‰¯¿þšõ‰lDz.99^^^B`Ïž=xå•WdG"z!kkk$$$ ""UªTA—.]dG""¢—Äúƒ*+++\¿~022Â[o½%;iN°é¹5kÖàÑ£GðööFƒ dÇ!*‘yóæÁÐÐkÖ¬AZZšì8DDô’XPeÄúƒˆ^„lDz,##Æ C^^öîÝ‹5jÈŽDT"¸}û6Î;+++¸¹¹ÉŽDDD%Äúƒ*+ $&&âüùó¬?ˆ¨N°é±­[·"-- C‡…ì8D/eæÌ™000ÀÚµk‘››+;•ëªÌfÍš¥®?²³³eÇ!"-‘žÊËËÃ7ß|…B?þXv¢—Ö¬Y3xxx 11;v쇈ˆJ€õUvvvvêúc×®]²ã‘aƒHO!!!=zô@Ë–-eÇ!*•iÓ¦V¯^-9 •ëÒÓ§OÀúƒˆ bƒHO}÷Ýw€>úHr¢ÒëÒ¥ \]]ƒsçÎÉŽCDDÅ`ýAº sçÎh×®bbb&;i 6؈ôÐõë׊F¡G²ã•É„ ~~~’“Ñ‹°þ ]âããؼy³ä$D¤-Ø`#ÒC~~~B`ܸqP(²ã•É!CP£F ìÞ½>”‡ˆˆžƒõé’Áƒ£F dýAD…BÈADš“““ƒºuë"%%‰‰‰¨]»¶ìHDe6iÒ$|÷Ýwذa&Nœ(;ýëÒE“'OƆ °~ýzLš4Ivª@™™™\5–ŠÅ‘žÙ¿? €þýûcß¾}²ã•‹ÈÈH¸ººÂÅÅ¿ýö›ì8DDô¬?HEGG£M›6hݺ5¢¢¢dÇ¡ äååÅUë©X†²‘fåÿb>|¸ä$DåÇÅÅ7Fdd$þüóO4nÜXv$""zëÒE­[·†¢££qíÚ5ØÙÙÉŽDðÊ+¯ÈŽ@ZæÁƒÃüÿ!z‘5j@©TÊŽAe”™™‰ƒÂÔÔ½{÷–‡¨\ 2‹/ÆîÝ»ñÙgŸÉŽCDDÿ‡õé²!C†`Ñ¢Eؽ{7>ÿüsÙq¨‚ðƒ*Ä××sæÌ¡………ì,T ÄÅÅÁÞÞ^v *£ŸþYYY2dLLLdÇ!*WƒÆâŋȑaýAºlðàÁX´hÙ`#ÒsêSD9æHEá„£n ð¿B€HרÛÛ£iÓ¦ˆÇ•+WдiSÙ‘ˆˆ¬?H·µlÙÍš5ÃÅ‹ñÇ Y³f²#‘$ê[jjªÌ¤¥/;•ƒÇãðáèV­ÞyçÙqˆ*Ä€°lÙ2ìÛ·sæÌ‘‡ˆHï±þ }àéé‰%K– ((Ÿ~ú©ì8D$‰ìD¤§NÂãÇÑ¥KT«VMv¢ Ñ«W/À¡C‡$'!""€õ釷ß~päÈÉIˆH&6؈ôDþ/üü€Hµk׿ææ8þ<ÒÓÓeÇ!"Ò{¬?H´oß5kÖĹsç––&;I‘ž8|ø0ðô Òi†††èÑ£rssqôèQÙqˆˆôëÒJ¥Ý»w‡J¥Â±cÇdÇ!"IØ`#Ò7nÜÀŸþ‰† ¢qãÆ²ãU¨îÝ» \""ÉX>ái¢DÄ‘8uê K—.RsiÂ[o½8yò¤ä$DDúõé“Î;~ýõWÉIˆH6؈ô@xx8ÀÍÍMr¢Šgkk‹Zµj!!!ÿüóì8DDz‹õé[[[Ô®] ¸wïžì8D$lDz ,, СCÉIˆ4#ÿµžÿÚ'""ÍcýAú†õ‘~cƒHÇ¥¤¤àêÕ«°´´D“&MdÇ!Ò¸DDr±þ }ÄúƒH¿±ÁF¤ãΟ?!Úµk…B!;‘F°À%"’‹õé£üÓ¡óO&"ýÂ‘Ž‹8::JNB¤9-[¶„.^¼ˆ¼¼<ÙqˆˆôëÒG¬?ˆôlD:.>>`oo/9 ‘昚š¢aÆÈÌÌÄ7dÇ!"Ò;¬?H±þ Òol°é8¸¤¯ò_óùÿˆˆHsX¾býA¤¿Ø`#ÒaOŸ>ŵk×P¥JØÙÙÉŽC¤Q,p‰ˆä`ýAúŒõ‘þbƒH‡]»v ¹¹¹°³³ƒ¡¡¡ì8DÕ¬Y3Àü!9 ‘~aýAú¬yóæ€Ë—/KNBD𯑻uë ~ýúRsÉÿºÏÿw@DDšÁúƒôYƒ 7oÞ”œ„ˆ4 6"Æ—ôY£Fׯ_—œ„ˆH¿°þ }Æ‘þbƒH‡±À%}fmm SSS$''#33Sv""½Áúƒôë"ýÅ‘Ë_¼aÆ’“ÉÁÓD‰ˆ4õé;N±é'6؈tØ_ý¨S§Žä$Dr¼þú뀻wïJNBD¤?X¾cýAÚ*** …®®®Bº?77½{÷ÆØ±c }­¶¶¶Xµj•z{33³·Ñ£G?wß¿þú+ÚµkSSSÔ¨Q½zõBBBBE¦4l°é°ÔÔT€………ä$•OyýòÉ÷ñÇC¡PàСCÅî{ÆŒhÖ¬LMMaaaaÆáÞ½{ås`zÆÊÊ pÿþ}ÉIˆˆôëÒ“U<}úo¾ù&j×® 333˜››£k×®8wî\ùœ±¶¶$''KNBT´›7oÂßß¿ÈÇÆÀÀ@ddd¸ÿäÉ“HJJ¨Q£Ô÷¥¥¥!##C}Û¶m[‘ÏùðáCôéÓ @ZZ’’’`mmAƒ•Û1i6؈tXþ/õü_òôòÊã—Ovv6~øá´jÕ ~~~ÅîÓÌÌLý¼×®]Ó'O0dȲŒbKD¤y¬?ÊNÓõ‡¡¡!Ö­[‡Û·o###wïÞE‡àááQ>¤g,--)))’“máÂ…˜;w.²²² =Ö§OT¯^;wî,p¿ŸŸ  ~}¿Œ¤¤$L™2¡¡¡°··WâÕ ÖÊ#Uýüs:×Ò¥K±yóf%m ÕEسg.\¸Pâ5lß¾gÏžÅ7^yÙ¬òÄÇÇ#>>óçχžž4h€Ù³gãСCÕH_÷¨\ƒ­¶§:@ëÖ­qóæM<~üQQQ¸uëfÍš¥Ü #"IUe瓟Ÿï¿ÿ>„¥¥%,--Ñ¥KÀwß}÷Êñ¾ÿþ{̘1„‹‹‹Ò·G]233%NBDDTyµ]WPPmmm¥l‡º166¤§§Kœ„èÕlllàéé __߯ >ššš˜4i† ++«*Ó¢E bÍš5ÈËËCzz:6lØ;;»êįsdR¨ ÅöÌŸ?_áµâG{V­ZõÊ©Ž2YÅ> ùiH-[¶„ŸŸFŽ©œ!ª¢/^LMM%N¢ÊÛùÌ™3§ÄÎçСCxöì.\¸€FÉ—?räæÎ‹çÏŸ—zjzpp0üüüpôèQ888ÔÜF©¢¿ãùùù'!"R¬?”«¶ê«W¯"11}úôŽŽnݺ…Ï?ÿnnn5»*JSóŸy,………¥¾^PPP¥3ž„HMM­R¦ªž®úâÅ‹2·ãUÒÓÓ«Teff"77·ÒïËÊÊBvvv¥ß—““Sêå¥þ-::ºÒë®/|}}K½Ž½ŽŽ&OžŒÀÀÀ2'™™™)üÛÉÉ aaa%–311Ahh(-Z???hjjÂÑÑ»víRÎFÔ*Ù`þ9Úcgg''§¯yxx`öìÙ5jTµ§:–¥¢×W¢ú%//O~m‘âóóóåG¨Šï0‹?.,,”ÅwŽÿÞQßùœšš*ÿ^ßÑœ––†‚‚ùó¤\•Ýù|÷Ýwxÿý÷åG‹L™2ÿùϰuëV|üñÇ%Ö÷ÑGA&“aÀ€ Ï¿xñZZZJÚõ ££U*Ôˆˆˆê‚Ú¨?òòò°páBÄÅÅAFaüøñðññ©™RqE§H>|¸ÄD")ÙÛÛ—èU˜™™!))©ÔåP¡õ”gàÀ8p`¥ÞSߨlƒ­*G{ªêĉèС6lˆÀ××#FŒ¨Ö:ÕAñfPñÇÅvœ‘‘¼¼<ŠG6Š?~ùò%rrr(Å(þ8;;[~}⋽ÈÍÍ•ŸRVü1©eì|>\ê²2™ ÷ïß/sl6艈ˆÔ“Tõ‡½½=¢¢¢ª˜ªBKK &&&•~Ÿ††F‰CeffV¥fŸ‰‰I•ðWøŒ°â åH+C__zzz•~Ÿ®®. Ê]n×®]*=‹”Cel@íLu€cÇŽaìØ±ÈÌÌ„FŒ+VTê/¿üæææUšmUÖÌ«úH&“ɯ©PüqñdñÇšššòS$Šïÿ½s,ºÓ ¸ó+þØÔÔT>ݼøãâ;½â;²k×®¡_¿~Jþˆê—¢æ{U 5"""¢ª(:€?tèP•»ˆ»ººvílT.•i°I9Õñ«¯¾ÂW_}U©÷Ô7{÷îUú:Ëj É/¨Züqñ£Å@WW€â‘‹âõôôäSµ‹¥(þXGGG~Aôâµµµëå­æÍÍÍðTQRoE³^y‘f"¢ÚQt`‘õ©³¢ EĉH=¨Lƒj–š6m @q¶UY3¯*2ÛŠˆ¨¦ö]‘©ÿDDDDÊPtm梳]ˆH=°ÁF2jÔ(•»…®ª+š±W4Eˆˆˆ¨¦±þ ""uÅ9«D*ªè´Ö¢;©£çÏŸ,,,$NBD¤XýïÚÔÅÏö!"ÕÇ‘Š*j(5ˆÔQrr2€ÿ]“ˆˆjë"à£Ê:t(¦OŸ^ây!Ú´iƒ•+W"** pppP¸n|Ñóùùù¥®ûñãÇ2dÌÍÍK]ÎÈÈHáGGGGá.·^^^hß¾= annŽqãÆáéÓ§enKyãÀ–-[о}{£yóæøùçŸË\ßéÓ§Ñ£GÂÄÄC† Á;wª5~MaƒHEikkÃÔÔyyy*u¡áòþ`Þ¾}ÎÎÎ022BÓ¦M±lÙ2…×k{QÓ;„ò–ùò%&Ož 3334jÔK–,©Öxõ \"¢ÚÅúƒõP~ýQÙÏ£¾aýA•åîîŽÝ»w—˜ý{âÄ ?~¼üu###y¶øøxdggc̘1UoÛ¶mÀ¾}ûžžŽË—/£S§N¥.›––†wß}®®®HMMÅ£GаaCŒ5ªÊã×$6؈T˜¥¥%”y7ÝúèU0 1lØ0´oßÏŸ?GLL ÂÃñuëVù2µ¹ƒ¨ByËòÉ'xôè>|ˆ¨¨(lÛ¶ ß}÷]•Ç«oоûE¿ DDTóX°þ(¯þ¨ìçQß°þ Êz÷ÝwallŒ;w*<¿qãF¸ºº*4kýüü°xñbùͼÊÓ¸qc¸»»£]»vå.›€ãÇÃÃÃCa<;;;hhhÀÒÒ ,À¹sçª<Þ’%K°zõjØÚÚ6lˆÖ­[—ºì£GžžŽ¹sçB[[&&&ðððÀ7”²½5AU5dÈ1mÚ´ÏŠ×_]‰‹/ ¢[·n¢°°P¾LÑóyyy¥®ûÑ£GbðàÁ¢Aƒ¥.ghh¨ð£­­-Œå¯/X°@´k×Nˆ ˆ±cÇŠ¿þú«Ìm)o}Äk¯½& …‰‰‰0`€ˆˆˆ/“™™)&Mš$LMMEÆ ÅçŸ^æXÍ_Yvvv€¸víZµ×Eµ¯[·n€¸pá‚ÔQ”®´¿·nÝDRR’ü¹ßÿ]ôìÙ³Ìõœ>}ZhiiUiºèñãÇ€¦M›Jœ„ê“éÓ§ãâÅ‹òß•íÛ·£iÓ¦èß¿¿ÂrZZZX¹r%•úw6??ßÿ½Âìµ;pà‚‚‚°víÚ*Qt}äÛ·o#66·nÝ“'O0sæÌR——Éd ÅöíÛå§”ÿý÷ß ©Òø5­Ú 6Ne,}*cyëÓÒÒ‚tuuÉd°¶¶ðÏ—{ëÖ­X²d ŒÑ¢E Ì›7›7o®r~R?EßÏ»wïJœ¤v´mÛmÛ¶…²²²””„eË–!##C¡è-RÓ;©wE> åÏ•ùy¨¢{÷îZµj%i""uÂúƒõPñú£ºŸG]ÄúƒªÂÚÚÎÎÎØ¸q#`Ó¦M˜1c444J,ëìì ggg,\¸PiãÿòË/ÈÈÈP˜´Tܾ}û0eÊ„††ÂÞÞ¾JcÝizéÒ¥Ð×ׇ¥¥%/^ŒC‡•º|nn.777ddd 99¯½öZ=¥¼Ú 6™L†©S§Ê¿À?·% …»»»Â²Ó§O‡¹¹9–/_^ÝaKظq#ºté‚.]º”¹LM)ªª±cÇÂØØ†††ˆŒŒÄ©S§pæ )GÑN½h'¯ê455qðàAÄÇÇ£I“&èß¿?zõê… ”Ø1ÕÆBêBQ¾ÌÌLùs022*uG­jž>}ŠÌÌLXYY)ùDDT³X°þ*V(ãó¨k‘™™‰† ²þ JóððÀöíÛqöìYܸqS¦L)sÙÀÀ@ìÙ³.\PÊØ!!!7nœüw¸¸ï¿ÿ3fÌÀÁƒáââRå1Z·n …¿eüG||<æÏŸ===4hгgÏ.³!'5¥Üä€S«n×®]HOOG\\’’’àíí €3OH9Ô­À€víÚ!<<)))ˆ‰‰‘‘úô飰Lmí ¤Þ!´nÝFFF §¨DGG£C‡µ2¾Ôxô˜ˆH¬?XT¤þPÖçQ×ÍÜ|Õ™NDe>|84551iÒ$ 6 VVVe.kccOOOøúú–»Þììläæærrr­ðúƒVjO%88ÞÞÞ8zô(z÷î]¡í(k<---L›6 K–,‘ŸÂîïï¯p©¬âZ´hCCC¬Y³yyyHOOdž `ggW­í­)Ji°q*cõÙØØ`éÒ¥òS@Õ}æ )‡ª¸¯úƒyóæMùï͉'°råJ|þùçò×ksQ[;„²–—Éd˜8q"¾úê+¤§§ãáÇÆ´iÓª5^}Á‘4X°þ(¯þ¨ÊçQ_$$$€üÒ?D•¡££ƒÉ“'ãîÝ»˜1cF¹ËûúúVh޾¾>zöì àŸ^ƒ¾¾¾Âë›6mB§NJí—|ôÑGHKKÀ`dd$ÿ)((\¹rEáßåçïï‚‚4mÚ­Zµ‚¥¥%6lØ ½øúLLLŠ={öÀÔÔ7Æ_ý…]»v•º|E··&Uë.¢EvïÞ-,,,Ä™3g„L&S¸[ç¿ïÞ'tttÄÚµk•r·‹Aƒ ww÷R_Û¼y³033§OŸ®ð¶”6^~~¾011)qw ==½*­¯4¿þú«ü.¨Ewß9yò¤üõòî¾SÙñ*‚w­ß²³³…L&ººº*uwÈ¢¿[ÅŠ,[¶LXXX###Ñ­[7qüøñï•Éd%îBœŸŸ/„âòåË ÿ.o¼ììlááá!,,,„‰‰‰3fŒÂ]pÂÂÂD×®]…¾¾¾044...âæÍ›ò×+;^e—ÏÌÌ'N&&&ÂÒÒ²Ô»¿êýõ™ üüü¤ŽBD¤VX°þ(¯þ(ïó¨ÏX¨¦Ú¸‹(Õ_EwUZƒ-''G4lØP´nÝZ¸ºº*¼VZÃgþüùÂÒÒ²ÜFPVV–8wîœüVÓYYY ¯ß¿_hjjŠ‹/–xïêÕ«…¹¹¹8þ|…·ãUãÍŸ?_Ìž=[~{Xggg1zôè*­/::Züþûï"''G!ÄÍ›7E—.]ĬY³äï={¶pqqiiiâÁƒ¢eË–"$$¤Êù«‚ ¶úÏÖÖV7nÜ: Q­ruuÄþýû¥ŽBD¤vXºbý¡šØ`£W)j°)åQ€S+»¾¼¼<,\¸022Â!C0xð`¬^½ZþÞÀÀ@4nÜÍš5C—.]0qâD…GÔµ©T7#q¢ÚUô/ï”""R>Ö¤®X©/ Mcã…ó©;vDLL ®]»ÆD=µlÙ2,^¼‹-Â×_-u¢Z‘žžSSSáÅ‹¼v%Q-cýAê(33&&&Ð××GZZ45•6Ÿ…$æææ†ü;vì(óÚ菉üýý±hÑ"åÜ䀈ê®N:®^½*q¢Ú!ìììØ\#"’ëRGׯ_Gaa!:tèÀæ‘âo=‘ŠëÑ£444ðÇp¦*©?þøÀ?ß""ª}¬?H;wä—ì!"õ‘Š377G»v휜Œ[·nI‡¨Vœ={ЫW/‰“©'֤ި`#Rol°©'''@DD„ÄIˆj \""é±þ uÃúƒH½±ÁF¤ŠfñÍê!Re·o߯³gÏжm[4jÔHê8DDj‹õ©“;wîàï¿ÿF›6mðÚk¯I‡ˆ$À‘èÓ§àôéÓ'!ªy'Ožôë×OÒDDêŽõ©“'Nú÷ï/q"’ lDjÀÚÚíÚµCBB¯ƒB*ïèÑ£€wÞyGâ$DDêõ©“ßÿ0pà@‰“‘TØ`#Ro¿ý6 ,,Lâ$D5'//áááÐÖÖ†‹‹‹ÔqˆˆÔëRùùù ƒL&“ç‰Hý°ÁF¤&† 8|ø°ÄIˆjÎü/^ G055•:‘ÚcýAê€õl°©Þ½{C__gΜAVV–Ôqˆjį¿þ 2dˆÄIˆˆ`ýAêõl°© }}} 2YYYò"€HÕ„††\]]%NBDDëR¬?ˆdR ¢Ú3fÌìÛ·?ýôÞÿ}©ã)ÕÕ«W‡N:á7Þ:ý¬?H•]»v ·nÝBÇŽÑ®];©ãP KIIAJJŠÔ1¨Ž)úN°ÁF¤F† CCC>|™™™044”:‘ÒìÞ½ðÁHœ„ˆˆŠcýAªlÏž=€Ñ£GKœ„jÃܹs1wî\©cPÅ‘100ÀСC±{÷n>|˜R)l°ÕM¬?H•ýôÓOþ™©IªËÐÐ 4:Õq$''K…ꢎ;âÑ£G¸víìì줎CJpàÀŒ1ÇÇÏ?ÿ,u"¥¸páºwïœ?^ê8DDô/¬?H]¼xèÖ­.\¸ u"’˜|›¹¹¹”9ˆ¨– :VVV8|ø0ž>} +++©#UÛ¦M›Ó§O—8 •†õ©¢Í›7¦M›&q"ª 44h ¤AußÙ³gakk+u R’Å‹cÙ²eøú믱hÑ"©ãUKFFš4i!ž:„>}ú {÷îØ³g j89Õ&^ƒˆ™™™xã7ðøñc>|C† ‘:‘‚ÂÂBtíÚÑÑÑX·nfÏž-u$""ª&ÖT×ÂÞÞW®\©Vý‘””„uëÖaÍš5HLLX[[cÁ‚˜:u* ”›ˆ$ÀÉmß¾'ND»vípíÚ5hkkK‰HnãÆpww‡®\¹---©#‘°þ º¬¨þèС¢££«]¼|ù[¶lAPPîܹ°´´„§§'æÌ™ƒF)#6I€ 6"’BÀÉÉ üñV¬XÁÓ5¨ÎHMM… qüøqôïß_êHDD¤$¬?¨®JMME»víð÷ßãØ±c0`€ÒÖ]XXˆŸþ+V¬@DD@OO“&M‚——lll”6Õ6؈HÁü'''âúõëhÙ²¥Ô‘ˆàááï¾û#FŒ@hh¨ÔqˆˆHÉXP]4sæL„„„ÔxýqöìYàСC(,,„¦¦&FŒ///899ÕØ¸D¤\l°Q EÍŒAƒá·ß~“:©¹S§N¡ÿþüŸ.""Çúƒê’Ó§O£_¿~µZÄÅÅ!(([·nEvv6ÀÉÉ ^^^1b45yB¢ºŒ 6"*!55o¾ù&ž,--qýúu¼öÚkRG"5´páB,_¾Ý»wǹsçxc""Çúƒê‚Å‹cÙ²eppp@DD„dõGff&6oÞŒU«V!!!ШQ#Ì™3žžž°´´”$•Ž 6"*Óûï¿ýû÷ãÝwßÅÁƒ¡¡¡!u$R#§N‚³³3455qéÒ%ØÙÙI‰ˆˆjë’ÒéÓ§1`Àhjj"** ;v”: °ÿ~àâÅ‹L™2^^^°¶¶–8!Oâ&¢2­[·¯½ö:„uëÖI‡ÔHJJ &OžŒ‚‚øùù±¹FD¤FXTRSS1iÒ$à‹/¾¨Í5ÐÒÒÂèÑ£qáÂ?~C‡EVVÖ­[|ðÁ¸pá‚Ô1‰Ôg°Ñ+>|Æ ƒžž¢¢¢`kk+u$RcÇŽÅO?ý„¾}ûâØ±c<5”ˆHͰþ )Œ7»víªõGll,‚‚‚°}ûväääz÷î ooo 6Œ3?‰$À•ëÃ?Äš5kбcGDFFÂÀÀ@êH¤Â6mÚ„3f Aƒ¸zõ*š7o.u$""’ëªM›7oÆôéÓÑ A\¹r¥Þܵü¯¿þÂêÕ«±a䦦lmm±`ÁL˜0ººº'$Rl°Q¹²²²Ð­[7ܸq&LÀ¶mÛ¤ŽD*êÒ¥KèÕ«²³³ñÓO?áƒ>:I„õÕ–Ë—/£W¯^ÈÊÊ®]»0fÌ©#UZzz:6mÚ„U«Váþýû+++|øá‡˜5kÌÍÍ%NH¤úØ`#¢ ‰‹‹C÷îÝñâÅ ãÃ?”:©˜¤¤$ØÛÛãþýûøè£°jÕ*©#‘ÄXPM{þü9ìííqïÞ=|øá‡–:RµäççcÏž=øæ›o 044„»»;æÏŸ_ofæÕGl°Q…íß¿£F‚¶¶6Ž;†^½zI‰TDAA† ‚°°0ôêÕ ÇŽƒŽŽŽÔ±ˆˆ¨`ýA5¥  C‡ÅÑ£GѳgO?~\¥êððp",, BÈd2Œ5 ÞÞÞèÚµ«ÔñˆTï"JD6räH,\¸¹¹¹9r$îܹ#u$R}ôÂÂÂиqcìÞ½[¥Š[""ªÖTS¼¼¼pôèQXYY©dýáââ‚#GŽàêÏ&¥ IDATÕ«˜>‘‘‘ˆˆˆÀ™3g——ðóóC~~>¾úê+‰R]sûöm¸¸¸àÑ£GèÛ·/öíÛÇ™±DDTeƒÆÎ;ñÁ°þ 2ݾ}Ä£GЧOìß¿ŸõG´lÙ+W®„¯¯/6lØ€U«V!::'NÄâÅ‹ññÇcÆŒ066–:*Q!o°ýùçŸRæ :ªcÇŽò ^’úÊÊÊBTT"""ÈÈH$&&*,£§§‡V­ZáöíÛXºt)233Äk¢€®yòöÛoãéÓ§èÕ«8©cQ=7räHìØ±&L`ýA%\¿~”×dýQIffføôÓO1þ|lß¾AAAˆÅ‚ ðå—_bæÌ™˜7oš4i"uT"ÉiJ€ˆêžÇcÏž=øøãÑ£G˜šš¢OŸ>X¸p!<ˆÄÄDXYYÁÕÕ8wîRSSqóæMù¬¤U«VaÖ¬Y(((zsHbQQQèß¿?ž>}Š·ß~Gåí߉ˆHiÆŒƒ½{÷²þ QQQèׯž>} ÖÕ¤««‹éÓ§ãúõë8tèú÷ïÔÔT,_¾­[·Æ”)SpãÆ ©cIJVþ"D¤ÊòóóqõêUùÌ´sçÎáÁƒ Ëhii¡S§NèÙ³'áääTæiåÇÇ/¿üWWW„„„ 99[·n…¾¾~mlÕ1GÅèÑ£‘žžŽáÇ㧟~‚®®®Ô±ˆˆHŰþ âÂÂÂ0jÔ(Ö5@CCC‡ÅСCqáÂbÿþýزe ¶nÝŠ!C†ÀËË ýû÷—:*Q­cƒHÍ$''#22RÞL»xñ"233–155…£££¼™Ö½{÷J]_áí·ßÆo¿ý†#F`ïÞ½xüø18€† *{s¨ûî»ïàéé‰üü|Lš4 ›6m‚LÆÝÕ Ö7nÄìÙ³‘ŸŸ &`óæÍ¼  qppÀîÝ»‘€U«VaóæÍ8|ø0>Œ®]»ÂÛÛ£FbýGjƒ§ˆ©¸øøxlÙ²3g΄­­-,--ñî»ïbéÒ¥8yò$233accƒÉ“'cÆ ˆ‰‰Arr2~ûí7øøøÀÅÅ¥J/íÓ§Î;‡Ö­[#22ŽŽŽˆ¯-¤ºFE‹ÁÃÃùùùX²d ~øáWDDTãX¨/!/^ wwwäççãóÏ?ÇÖ­[Ù\«ÖÖÖƃðå—_¢Q£F¸téÆ|ûí·%è©"þß‘ ©èͺuë'''899ÁÑѱƎì¶oß>|8.\¸GGGìØ±ƒ ª‘ñHziii˜2e BCC¡­­L™2EêXDD¤FX¨Ÿ´´4L:û÷¶66lØ€©S§JK혛›cÉ’%øä“O°uëV!..óæÍÃ_|Y³fáÃ?„•••ÔQ‰jg°ÕcU½ÁéÓ§áïï÷Þ{¯ÆO›°²²ÂñãÇñþûï#99C‡ÅW_}!DŽKµïæÍ›èÞ½;BCCaaaß~ûÍ5""’ëõQTìß¿æææøõ×_Ù\“˜žž<<<‹ gÏžHNNÆ×_V­ZÁÝÝqqqRÇ$R:6؈ê‰üü|\ºt ß~û-Æ–-[¢Y³føàƒ°jÕ*œ?………èÔ©<==±mÛ6üùçŸøë¯¿°ÿ~xyyÁÉÉI’ ¼bÏž=ð÷÷‡††–,Y‚áÇãÅ‹µž…jÆîÝ»áàà€[·ná­·ÞÂÅ‹áìì,u,""Rc¬?TßÞ½{ꨨ(¸¸¸H‹þ?MMM¼÷Þ{8{ö,Î;‡‘#G"//7n„­­-†Ž3gÎH“HixŠ(QU7#¨MøôÓOaoo±cÇâ—_~A—.]°mÛ6899IªèåË—ðööÆÿû_!0yòdü÷¿ÿ…žžžÔшˆˆX¨¨¬¬,xyy±þ¨GŠ.OsçÎaË–-8xð <|òÉ'9r$459ˆê/6ØˆêˆøøxDFF"""gΜÁ­[·JœÆ`cc#o¦999ÁÖÖ¶Þ턜å=ˆˆ@ß¾}ñÙgŸáóÏ?çEðë™èèh¸¹¹!66X¹r%<<<¤ŽEDDTëÕqõêUŒ?žõG=Õ¦M¬[·_|ñÖ®]‹uëÖáÂ… =z4Ú´iƒ `òäÉ000:*Q¥Õ¯ÿ3'RYYY8sæ –/_ŽáÇ£Q£Fxã70eÊ„„„àæÍ›ÐÕÕEïÞ½ñé§ŸâÀxöìâââðÃ?ÀÃÃ:t¨w͵"-Z´À©S§àççðóóCß¾}ñçŸJœŒ*¢°°+V¬@=+?%ƒÅ-Õe¬?ê·ÂÂB¡{÷îˆEçÎqñâEÖõT£Fàçç‡û÷ïcݺuhÓ¦ îܹOOO´lÙ¾¾¾xöì™Ô1‰*¥~þß9Q=SnFPÛd2|||púôiX[[#""vvv D~~¾Ôñ¨ 111èÙ³'¼½½‘›› oooDFF¢}ûöRG#""*ëú©¨þðòòBnn.¼¼¼ðÇÀÖÖVêhTM˜={6âââä×ÔKJJ—_~‰V­ZÁÓÓwîÜ‘:&Q…°ÁF¤dõùfRpttDtt4æÌ™ƒœœ|òÉ'èÞ½;¢££¥ŽFÅäää`É’%°··Çü?~jó]%""ÕÁú£~ÈÉɼþhÛ¶-Ž?ŽÀÀ@Ö*FSSï¿ÿ>Ο?Ó§Oã½÷ÞCNNÖ¯_7Þxï¿ÿ>"""¤ŽIôJ¼àQ5©Úͤ`llŒ5kÖ`ܸqpwwÇåË—Ñ­[7Ì›7K–,™™™ÔÕÚ‘#G0þ|ÄÅÅA&“aÑ¢EX²d ôõõ¥ŽFDDTe¬?ê6Öê«wïÞèÝ»7âââˆmÛ¶aÿþýØ¿?œœœðé§ŸâÝwß­·—Ë!ÕÅo$Q%ÅÇÇcË–-˜9s&lmmaii‰wß}K—.ÅÉ“'‘™™ Lž<6l@LL ’““ñÛo¿ÁÇÇ...l®•¡gÏžˆŽŽ†¯¯/´µµ„¶mÛbݺu®^½*o¦;w}¾#¶¶¶Â××W\¾|Yáo„ªyþü¹Ø¹s§9r¤Ð××—o¿¥¥¥øøãEll¬Ô‰ˆˆTëÖT÷ýþûïâwÞ‘?e2™;v¬¸råJ¹ïUµ[vv¶°³³>>> Ï5Ì/^,äïKNNzzzâØ±c¥._ž¢[ñ™«aaaÂØØXY›&)•m°ÕöTG!„غu«xóÍ7Å7„B<{öL$$$(gÃ$&EƒíùóçâСCâ³Ï>ýúõ†††%j¦¦¦bРAÂÏÏOüþûï"--­ÖòQý'¾úê+…k¥Ínœ2eŠØµk—ÂQ”ú(??_\¸pA|ùå—ÂÑÑQhii)?~¼øùçŸ9ƒ“ˆˆ¨–°þ`ýAuÛ•+WÄ„ ΂rqqGŽ)³®j ¶¼¼<. ÄÇKôAþüóO¡¡¡!bbb„B‹×_]þù-ojjªðóí·ß–:nAAhÛ¶­˜={¶xùò¥HLLýû÷*qð¡¨Á¦2wŠŠB·nݰvíZ|ýõ׈‡üù¼¼<@‹-àççwwwù{Ç!vîÜ©°¼L&«ÐØ­ZµÂ¦M›àìì\#Û&¥Ú¸‹h||<"##³gÏâæÍ›%¾666ò;{:99ÁÖÖššš5’‡TSll,öìÙƒ ::ZþÓÔÔ„­­-œœœÐ«W/ôìÙÖÖÖ§-[ff&.^¼ˆ3gÎ ""‘‘‘xñâ…üusss¼ýöÛ=z4 }}} Ó©7ÖDu×ÇŒ¤¥¥ìììàåå…qãÆAGGG¾¬ªÜEôßýŽ÷Þ{&&&˜?~‰>ÈÀñæ›obÕªUèܹ3ÆŽ‹… –ºžŠ¸uëæÎ‹K—.¡Y³fpuuÅÚµkñüùóÛÞÚRtQ•k°egg£[·npuu…ŸŸ_‰ÿðŸ}öÂÃÃqþüy@JJ š4i‚ÇcÀ€•þ¢}Š#GŽàÈ‘#/ñGÝÒÒ:uB‡`gg‡N:¡mÛ¶055­µŒùùùxøð!®_¿ŽëׯãêÕ«¸~ý:ââ⟟/_NKK ]ºtÁ Aƒ0xð`888@KK«ÖrQŰþ ª›ÒÒÒ°~ýzãÉ“'€æÍ›cÞ¼yððð€‰‰‰Ê6Øâããagg‡•+WbΜ9 }={ö`öìÙøùçŸÑ¿<|øVVV¥®§*¾ùæDFF"44TiÛ'•m°åååáÔ©Sxï½÷‡§OŸ*ü‡OHH@›6mpíÚ5tèÐß~û-V¯^Û·oCCCC¾žïȾúê+Ì;·Ä¸ÑÑÑxë­·àááU«V!33£GFÆ ±{÷îÚÚüSÝÛãÇši—/_–Ï&,beeGGGôìÙŽŽŽèÚµ+tuu•µ D¯TXXˆØØXù ÊsçÎ!!!¡Ôe-,,ЪU+ùOãÆaaa XZZÂÂÂ:::ò¿úúúÐÓÓ¤¦¦Bœœ¼|ùéééxþü9‘””„çÏŸãþýû¸wïîÝ»‡‡–ø]CCCtëÖ ½{÷–7 k³ð&""¢êcýAT÷äææbçÎX±bbbb&&&ðððÀ;wðóÏ?«\ƒ >þøclß¾III Ïçææ¢Y³f022BçαÿþW®§<7oÞD‹-`hhˆ'N`üøñ8tèºvíªü ­e*Ý`«Í©ŽwîÜAÛ¶m‘˜˜KKKÀï¿ÿŽáÇãåË—5·Áµ¤2 ¶üü|\½zUÞL;wî|8æÌ™ƒI“&aذaòæZUhiiaÚ´iX²d V®\‰—/_Âß߯ «Nü:‰7# uWt:†*Lc&""¢úõ‘tºvíŠ;w"++ :Õq*Û`___lÛ¶­Äó:::˜ ZOytuu±aÃlذ¡Rï«ORRRx3"""""""¢R¨LƒjÖ¯¿þŠÁƒKƒˆˆˆˆˆˆˆ¨Îáű¨Bš5k&u"""""""¢:‰ 6""""""""¢j`ƒˆˆˆˆˆˆˆ¨† :Ó§O/ñ¼mÚ´ÁÊ•+ 888(\¾èùüüüR×ýøñc 2æææ¥.gdd¤ð£££ùë^^^hß¾= annŽqãÆáéÓ§enKyãÀ–-[о}{£yóæøù矫´¾œœôíÛVVV022‚©©)œYb=ùùùxë­·^ùYU4U±ÁFDDDDDDDTCÜÝݱ{÷nddd(<âÄ ?~¼üu###y¶øøxdggc̘1UoÛ¶mÀ¾}ûžžŽË—/£S§NUZŸL&Ú5kpÿþ}dddàÉ“'èÙ³'† VbÙU«VÁÌ̬Ìq*š¿:Ø`#"""""""ª!ï¾û.Œ±sçN…ç7nÜWWWXXXÈŸóóóÃâÅ‹ñòåË ­»qãÆpwwG»víÊ]6!!LJ‡‡‡ÂxvvvÐÐЀ¥¥%,X€sçÎUy¼%K–`õêÕ°µµ4lØ­[·®Òú´´´`gg]]]€††d2¬­­–»ÿ>6nÜ__ß²7¾‚ù«£Ú 6Nu¬¹©Ž5‘ŸˆˆˆˆˆˆˆjL&ÃÔ©S±qãFùs))) …»»»Â²Ó§O‡¹¹9–/_®ô7nD—.]Ð¥K—2— G×®]«´þàþýû¸rå Z·n KKK¸¹¹!%%¥ª‘cÇŽ…±±1 ‰S§N)¼>wî\üç?ÿ‘‘QµÆ©®j7Ø8Õ±æ¦:*;?Õ¾éÓ§ãâÅ‹¸~ý:`ûöíhÚ´)ú÷ﯰœ––V®\‰ÀÀ@zöì àŸÞ…¾¾¾Âë›6mB§NJí'|ôÑGHKKÀn"YPP¸råŠÂ¿ËÏßßhÚ´)ZµjKKKlذAþzeÖ———‡… ÂÂÂFFF2dŒÕ«W´µµÑ¬Y3ùO£FM›6•¯£²ù«C¦”µàŸ©Ž³gÏÆ¨Q£*4ÕÑÎÎNNNJ»¼©Ž ,ÀÁƒ+Ü-MmOu,¢¬üDDDDDDD$­€€”xÞÞÞ¾DÁÌÌ III害¼¾„ŸŸüüüªôÞ·Þz«ÄM-_õ]]]lذA¡©VÕõÙÛÛ#**ê•ùþ½ü¿×UÙüÕ¡”l§:Vv}åMuTv~""""""""ªJk°qª£r§:ÖD~""""""""R> ¨¹)rT¿uìØ111¸víìì줎CDDDDDDT«ÜÜÜðã?bÇŽe^ûÔ—¿¿?-Z¤¼lDDDDDDDDDêˆ 6""""""""¢j`ƒˆˆˆˆˆˆˆˆ¨Ø`#""""""""ª6؈ˆˆˆˆˆˆˆˆª 6""""""""¢j`ƒˆˆˆˆˆˆˆˆ¨Ø`#""""""""ª6؈ˆˆˆˆþ_»vlà Qt,¹ €:¼&ɹ.ˆíg–‚=KïUðƒF pƒ n0°À Ïê€ÑõÞÓ{¯Î`0×MØ~h­¥µVÁ  l_LÓ”yž«3Ü#É+IŽã(NaDÛ¶å<Ïìûžu]«s†óù`[–¥²þÒMh|ÿîFÉIEND®B`‚frr-7.2.1/doc/figures/fig-vnc-frr-route-reflector.txt0000644000000000000000000000000013610377563017413 00000000000000frr-7.2.1/doc/figures/fig-vnc-gw-rr.dia0000644000000000000000000012132113610377563014471 00000000000000 #Letter# ## #NVE 1 VN 172.16.1.1# #NVE 2 VN 172.16.2.1# #NVE 3 VN 172.16.3.1# #NVE 4 VN 172.16.4.1# #CE 1 172.16.1.2# #CE 2 172.16.2.2# #CE 3 172.16.3.2# #CE 4 172.16.4.2# #VNC Gateway 1 192.168.1.101# #NVA 1 (NVA) 192.168.1.103# #NVA 2 (NVA) 192.168.1.104# #VNC Gateway 2 192.168.1.102# #RR 192.168.1.105# frr-7.2.1/doc/figures/fig-vnc-gw-rr.png0000644000000000000000000023420413610377563014525 00000000000000‰PNG  IHDRuìm+‹œbKGDÿÿÿ ½§“ IDATxœìÝwTTçÚ6ðk@A`1X¢`Åvj/ Æ ˉåD£Q£oNbblŸ%šD%zÔXã±ÅD•®‚¢ bDDEEE¥I‡çû×y%6ÊÌìÙ3×o-Ö:gÊÞ×08¹÷=Ͼ·B!@DDDDDDDD²b$u""""""""*;6uˆˆˆˆˆˆˆˆdˆM""""""""bS‡ˆˆˆˆˆˆˆH†ØÔ!"""""""’!6uˆˆˆˆˆˆˆˆdˆM""""""""bS‡ˆˆˆˆˆˆˆH†ØÔ!"""""""’!6uˆˆˆˆˆˆˆˆdˆM""""""""bS‡ˆˆˆˆˆˆˆH†ØÔ!"""""""’!6uˆˆˆˆˆˆˆˆdˆM""""""""bS‡ˆˆˆˆˆˆˆH†ØÔ!"""""""’!6uˆˆˆˆˆˆˆˆdˆM""""""""bS‡ˆˆˆˆˆˆˆH†ØÔ!"""""""’!6uˆˆˆˆˆˆˆˆdˆM""""""""bS‡ˆˆˆˆˆˆˆH†ØÔ!"""""""’!6uˆˆˆˆˆˆˆˆdˆM""""""""bS‡ˆˆˆˆˆˆˆH†ØÔ!"""""""’!6uˆˆˆˆˆˆˆˆdˆM""""""""bS‡ˆˆˆˆˆˆˆH†ØÔ!"""""""’!6uˆˆˆˆˆˆˆˆdˆM""""""""bS‡ˆˆˆˆˆˆˆH†ØÔ!"""""""’!6uˆˆˆˆˆˆˆˆdˆM""""""""bS‡ˆˆˆˆˆˆˆH†ØÔ!"""""""’!6uˆˆˆˆˆˆˆˆdˆM""""""""bS‡ˆˆˆˆˆˆˆH†ØÔ!"""""""’!6uˆˆˆˆˆˆˆˆdˆM""""""""bS‡ˆˆˆˆˆˆˆH†ØÔ!"""""""’!6uˆˆˆˆˆˆˆˆdˆM""""""""bS‡ˆˆˆˆˆˆˆH†ØÔ!"""""""’!6uˆˆˆˆˆˆˆˆdˆM""""""""bS‡ˆˆˆˆˆˆˆH†ØÔ!"""""""’!6uˆˆˆˆˆˆˆˆdˆM""""""""bS‡ˆˆˆˆˆˆˆH†ØÔ!"""""""’!6uˆˆˆˆˆˆˆˆdˆM""""""""bS‡ˆˆˆˆˆˆˆH†ØÔ!"""""""’!6uˆˆˆˆˆˆˆˆdˆM""""""""bS‡ˆˆˆˆˆˆˆH†ØÔ!"""""""’!6uˆˆˆˆˆˆˆˆdˆM""""""""bS‡ˆˆˆˆˆˆˆH†ØÔ!"""""""’!6uˆˆˆˆˆˆˆˆdˆM""""""""bS‡ˆˆˆˆˆˆˆH†*I@ ééé(,,„ P(`ll KKK‰“•ŽÞ5u233‹ØØX$&&"11·nÝBJJ žÏÈÈõë×G‡àááooo4oÞ\µ""""""""m‘]S'##8räŽ9‚äää÷+•J´hÑ-Z´(±Ò¦zõêªU8oS¼šçñãÇ%VúÄÆÆâÊ•+ÈÌÌ,ñx{{{ôêÕ ¾¾¾ðóóãJ""""""¢rਔ²“ES'77À®]»päÈdggxñ;;;ÃËË îîîèСêׯ##ÍÌ.**BBB"""†àà`\¹rEõgff??? 6 }ûö…©©©FrÉ G¥h†N7u®]»†õë×cË–-xúô) jÕªðóóC¯^½Ð«W/ØÛÛKš199GŽÁüÀÀ@ÕJžêÕ«cĈ?~<œœœ$ÍHDDDDDD¤-•¢=:×ÔB 00Ë–-É'¦¦¦èß¿?†Š^½zÁÌÌL┯—ÀÀ@ìܹ‡Bnn. zô课ú ;v”:"‘ÚqTŠ4t¦©STT„íÛ·cñâŸzõ* Q£F˜0aF‰–MJJ ¶mÛ†•+WâÖ­[ooo|õÕWðõõ•8QÅpTŠô$oê!°gÏÌ™3×®]xyyáþçЧO½éÚRPP€íÛ·ã»ï¾S½¾¶mÛâ믿Fÿþý b9éŽJÑ’6u®^½Š‰'âÌ™3Ì;ݺu“*’ÆaïÞ½X´h.^¼àEójÅŠhݺµÄ鈈ˆˆˆˆˆÞŒ£Rt“$MÌÌLÌŸ?Ë—/G^^ðã?bÀ€ÚŽ¢uB8p_|ñâããadd„1cÆ`É’%°¶¶–:‘ G¥è6­7uöïß©S§âÎ;011ÁôéÓ1kÖ,íèiJ^^V®\‰9sæ 33öööXµj $u4""""""2p•"Q)Zkêdffbâĉضm k×®X½z5š4i¢Ýë¬;wî`„  Œ=«W¯†¹¹¹ÄɈˆˆˆˆˆÈqTŠ|F¥h¥©‹Áƒ#..–––øé§Ÿ éÝÊÊÖ­[1iÒ$ddd iӦؽ{7Z´h!u,""""""2•"¿Q)oê¬[·Ó¦MCVVZµj…Ý»w£Q£FšÜ¥lݸqC† Att4ÌÍͱzõjŒ=ZêXDDDDDD¤ç8*å¹JÑXS'''cÇŽÅöíÛ“&MÂ÷߯W׃ׄœœL™2ëÖ­Œ9ëÖ­ãˆˆˆˆÔŽ£R^O.£R4ÒÔIOOÇ|€3gÎÀÒÒ6lÀàÁƒÕ½½¶{÷nŒ7éééèܹ38KKK©c‘žà¨”wÓõQ)joê$''Ã××ÑÑѨ]»6Ž;†fÍš©s#66=zôÀƒЦMâ½÷Þ“:ÉG¥”ž.JQë5Èâããáéé‰èèh899!44” hÑ¢ÂÂÂиqc\¸p^^^HHH:ÉTNN>úè#üóŸÿDVV&Mš„ˆˆ6tÞ¢Q£FÇøñã‘••…üã5jrss¥Ž¦¾•:111èÞ½;>|777>|¶¶¶êØ´Á{üø1z÷îóçÏÃÞÞþù§N-÷"""""""ÝÇQ)§k£RÔÒÔ‰‡——>|ˆž={bïÞ½°°°PG>ú_™™™4hŽ;†:uêàÔ©Shذ¡Ô±ˆˆˆˆˆˆH8*E}tiTJ…›:?†‡‡âããÑ¥KüñÇ011QW>zI^^|}}qòäI4lØaaa°³³“:é°øøxôìÙ prr‘#GP¯^=©cÉZbb"zö쉿þú 6ÄÑ£GQ¿~}­ç¨ÐL´´4ôèÑñññpssÃÁƒÙÐÑ ìÛ·íÛ·G||<úöí‹ôôt©c‘ŽŠ‰‰QÍgussCHH:jààà€Õñ¹§§'bccµž£Ü+u¸jD:\Eú.==………(þxR(066–ô\U"""Òo¬?HqTŠæI=*¥ÜM1cÆ`ãÆ¨S§BCCQ·n]ug£·ˆ‡êÕ«SSS ¿2"""ÒU¬?ÈÐq1€öH¹è¥\MmÛ¶aäÈ‘P*• ƒ³³³&²Ñ;ÄÄÄ C‡ÈÊÊÂÿû_øûûK‰ñññˆŽŽFLL bbbpùòe$$$ ,5–––066†B¡!PXXX¦Ó ŒŒàè舖-[ÂÙÙÎÎÎhÕª4hPæ×DDDDºõQIiiièÔ©¢££áææ†'Np…ކ¥¥¥¡{÷î8þ<ÜÜÜpìØ1­¬ô+sS'..íÚµÃóçÏñË/¿ @SÙ¨¶lÙ‚Ñ£G£jÕªˆŒŒDãÆ¥ŽD$77QQQ Ahh(ÂÃÃñøñãWW¥JÕ·XÅ?µjÕR}ËUüWi?ôÒÓÓUß®Ûvÿþ}Õ·p ¸}û6rss_y®<<<àíí www´k׎ßXÉ뢷ã¨éH±:ªLM¬¬,tèÐ111;v,Ö¯_¯ÉlTJ£FÂÖ­[ѪU+„‡‡£J•*RG"=–˜˜ˆÀÀ@>|§OŸFVVV‰û6lˆV­Z©¾rqqAýúõUßziKQQnݺ…K—.©¾µ‹ŽŽÆÍ›7K,u’HDD<<<0tèPÄÅÅÁÃÃÇÇ©S§XP©QÇŽ„?ÿü:tÀ•+W0dÈxxx ""BêxDDDZÅúC;XÐgŸ}†œœLš4‰ hÓ¦ &L˜€¬¬,L™2EmÛ}ãJwwwDDD`ÿþýèß¿¿ÚvHÚ³gÏ 2„«u Ð;wðå—_bçÎB Y³fXºt)üüü¤Žf~ÿýw̘1ׯ_‡B¡@@@–,Y‚š5kJˆˆHcXH‹õ‡aùã?àçç‡ZµjáÚµk°´´”:•Bjj*š6mŠääd;v Ý»w¯ð6_»R'44hÕªúõëWá4>üðC4kÖ ìÖˆ‚‚|÷ÝwprrÂŽ;`ee…•+WâÒ¥K,¨´¨OŸ>¸|ù2¾ÿþ{XYYá—_~AãÆñÃ?   @êxDDDjÅúC7°þ0B|ûí·€%K–°¡##ÖÖÖX¼x1¨ÞÊzmSgéÒ¥€iÓ¦ñ|LS(øâ‹/?þø£ÄiHÓŠÏÑüòË/‘››‹ &àÆ˜4i*Uª$u<ƒcbb‚éÓ§ãÆøä“O••…3fÀÝÝ—.]’:‘Z°þÐ-¬? G¥È›ºG¥¼rúÕÝ»wQ¿~}ØÙÙ!11&&&Þ I'//xüø1Q»vm©#‘šåää`Μ9ªoaš7oŽõë×ÃÝÝ]êhô’Œ7qqq¨T©þçþ³gφ©©©ÔшˆˆÊŒõ‡<°þÐO•"ê•òÊJ7¢  ãÇgCG˜˜˜`ìØ±(((ÀÆ¥ŽCjWWW|÷Ýw022ÂìÙ³qáÂT:ÈËË ÑÑј5kŒŒŒ°hÑ"¸ººâÊ•+RG#""*ÖòÁúCÿpTŠ~P稔M¢¢"üüóÏ022˜1c*´aÒü1ŒŒŒ°qãFI‡Ô@åË—ÃÕÕ111pvvFdd$æÌ™Ãf¬355żyópîÜ94oÞ—/_F»ví°råJ¼af=‘Î`ý!O¬?ô G¥èuŽJ)qúÕ±cÇгgOôèÑG­XJÒ)]»vÅÉ“'qâÄ téÒEê8T?ƈ#pôèQ( L™2‹-B•*U¤ŽFe““ƒ/¾ø«V­‚½{÷Æ–-[`kk+u4""¢W°þЬ?ä£Rô‹ºF¥”X©³k×.ÀСC+žtJñ{Zü“}º\ÛP5uŽ;èÑ£‡Z‘îéÕ«àÈ‘#'¡ÒÊÌÌDß¾}±f͘™™aûöíX¾|9*W®,u4R³Ê•+cÕªUغu+ªT©‚•+W¢ÿþÈÌÌ”:Ö†ƒõ‡|pTŠþªè¨ÕéWM›6E\\®]»'''õ%$8;;ãòåËRÇ¡w¸ÿ>z÷îèèhÔ¨Q„›››Ô±H ÂÃÃñÁ %%mÛ¶Å¡C‡P³fM©c‘`ýa¸Xè.ŽJÑo•b¼8Ÿ2..µk×fCG9;;£Fˆ‰‰Á£G¤ŽCoqíÚ5¸¹¹!::NNNgAe@ÜÝÝF!** :tÀõë×¥ŽEDDzŽõ‡acý¡»8*E¿UtTŠð¢+ ¼ø‡LúÍÓÓ*qz“èèhtêÔ IIIðññAhh(êׯ/u,Ò²  ""žžž¸sç:vìÈvDD¤1¬?`ý¡«8*EÿUdTŠ„„„¼¼¼Ô‹tQñ{\üž“n9wîºté‚GÁÏÏGŽa7Þ€ÙØØàرcèÙ³'>|ˆÎ;ãüùóRÇ"""=Ãúƒ^ÆúC÷>|лwo‰“¦øúúþøã2?×xñA®®®jŒEº¨}ûöÀfŒnݺáÙ³g8p öïß333©c‘ÄÌÍÍqðàAôïßOŸ>E·nÝØ”%""µaýA¯ÃúCwpTŠa¨È¨#!bcc¡P(ТE E$]ѲeK( \ºt ÿ;#›tÀ¹sçзo_ddd »w‰‰Ô±HG˜˜˜`Ïž=@zz:úôéÃÆ,UëzÖº£R GyG¥ݼyhРªV­ª‰l¤C,--áàà€ôôt$&&J‡\¾|½zõBZZ†Ž-[¶ÀØØXêX¤c*Uª„-[¶ÀßßiiiðõõELLŒÔ±ˆˆH¦XPi°þG¥ŽòŽJ1ºv퀗4'ÃЬY3ÀÕ«W%NBqqqª%Ï ÀÖ­[YPÑcëÖ­èׯžRÎìů¡Fï|lõêÕamm­úYµj•¦ãéœêÕ«RRR$Nbx¶nÝŠ•+W–ø–ƒ¨"6lˆ½{÷ÂÄÄË—/ÇÎ;¥ŽDDD:†õ©ëíë¨MŸïܹ¸~ý:RRRôª±X®¦ÎÓ§O666ˆ¤Å ©G½ó±)))HMMUýLš4IÓñt›:Ò¸xñ"&L˜X½z5ÜÜÜ$NDúÂÛÛ?ýô`̘1¼Ô(©°þ Maý¡yr•¢­ãóÆcÁ‚ظqc¹³êšòŒJ1ÊÉÉT©RE#¡´¡V­ZpqqÁŽ;¤Ž" fff€ììl‰“ާOŸbРAÈÊÊ„ ÞyÕ5¢²3f ÆŒƒ¬¬, 8iiiRG"""‰±þ Mcý¡Yr•¢ÍãóÂÂBT®\YãûÑ–òŒJ1ÊÏÏÙÿ"V®\‰… bõêÕÈÈÈð¢³ùå—_âèÑ£ÚvNNòòò¼¸ŒZq#L®Š/[É¡fÚ!„À¨Q£pëÖ-xxx`ùòåRG"=õÓO?¡}ûöˆÇ?þñ©ã‘„X¶°þÐ9JÑÄñù¥K—püøqÕ±y\\¾ùæ¨-·ÔÊsVQñeèä>ýÞÇǧNÂñãÇáèè tìØÙÙÙh×®êqÖÖÖP*•ªŸ=z¼sÛfffðôô(•JÕJ¹R*•€ÌÌL‰“†µk×â÷ßG5°{÷n^:”4ÆÄÄ{÷î…­­-öïߟþYêHDD$Ö¤-¬?4GΣR4q|žŸŸ/¿ü¶¶¶P*•ðó󃯯/þýïkã%iEyš: xÑÍ'áP(ð}×´¸¸8´mÛÙÙÙ8xð úôé#u$2û÷ïÇÀ¡T*…ÆK‰ˆˆ´ˆõIõ‡úY[[#-- ©©©°²²’:iAZZ¬­­aee…ÔÔÔR=ǨøšñÅK¢HÿŸëÊÍÊËËÃG}„¬¬,|òÉ',¨Hk €1cÆ 33}ôŠO³%""ýÇúƒ¤ÂúCýôeT •^yF¥ŠŠŠ4ŠÈP-\¸QQQ\©Ô IDATprrÂ÷ß/u20ÿþ÷¿Ñ¸qcœ?K–,‘:i ë’ëõÒ—Q)Tzå•¢¨U«–¸ÿ>îÝ»‡Zµji*餤$¼ÿþû¨S§îÞ½+u½tåÊ´iÓEEE/qÞ(‘¶œ;w¨\¹2¢££Ñ¤I©#‘±þ ]ÀúC}82Ã0•õ}7*ž¤]‘býAº†õ‡zXXXž?.qÒ–âUYÅ«´JÃÈÁÁ˜˜¨HowïÞ=øùùÁÆÆ …%îùzõJ¥&&&°´´TÝ?}út4mÚ°±±¿¿?’““˽?زe š6mŠªU«âý÷ßÇ*´½—•5¯¦¿×Åï=©Ï×_ôôtŒ9;w–: G>|8ž={†Ù³gK‡ˆˆÔŒõé"Ö§íQ)r>>ÏÍÍEÇŽaoo¥R +++tíÚáááoÜ¿®Ÿ¿¬<£R$mêaÀ€X±bÅkïÏÌÌ,ñãããƒáÇ«îW*•ؽ{7233ñ×_!''C‡-÷þ¶mÛ†¥K—bïÞ½ÈÈÈÀ… вeËroïïÊšWSØÔÑŒèèhlÚ´ J¥‹/–:Q ‹/†¹¹9þóŸÿ 66Vê8DD¤&¬?H—±þ¨mJ‘óñy¥J•°jÕ*ܾ}™™™¸ÿ><==Ñ·oß7î_WŽÏ_V®Q)GD=„TΟ?/ˆüüü7>ææÍ›B¡Pˆ¨¨¨7>&((H—{õêÕÇ/}ðwlï]J›Wݺví*”ëµÒ›uêÔI .”: ÑkÍ;WÝ»w—: © ëÒu¬?Êoذa€Ø±c‡V÷+÷ãs!„xþü¹˜;w®hß¾}©Ÿ#Õñù˶oß.ˆáÇ—ú9F...€Ë—/«©·¤6l@›6mЦM›7>æøñãhÛ¶m¹¶çÎܾ}/^„££#ªW¯Ž€€<{ö¬¼‘ß©"y+"&&àìì¬õ}ë«ÀÀ@œ>}ŽŽŽ˜6mšÔqˆ^kÆŒ¨[·.þüóO;vLê8DDTA¬?HX”Ÿ”gÕ¼‹®Ÿ6 U«V………ÂÃÃqæÌ™RïSªãó—•ç¬#{{{ØÙÙ!99Yg/k^PP€M›6aüøño|Ìo¿ý†ü«W¯.×>ž>} ¸qã®^½Š¸¸8Ü¿ÿüç?˵½w©hÞòJNNÆ£G`ooÏAÉj"„À·ß~ X´hªT©"q"¢×377Ç‚ €ç¶Éë’ Öå§«M]>>ß¹s'222pýúu¤¤¤`ÆŒ¥ÚŸTÇçW®¦èújC‡!33³Äùz/Û»w/Fýû÷£]»våÚ‡R©,X°fff¨^½:fΜ‰ßÿ½Ü¹ßDyË«ø=.~Ï©â~ûí7DEEÁÅÅC† ‘:Ñ[ >Í›7GDD„F>߈ˆH;Xœ°þ(GGGÀ­[·$NR’ŽÏ7nŒ `ãÆï|¬”Çç—¨_¿~©ŸcîîîðÖÉÐRZ·nüýýUoìË6mÚ„±cÇâàÁƒèÖ­[¹÷áèèKKK( Õm …BˆroóuÔ•·¼Šßãâ÷œ*¦¨¨HõüyóJüýé"###ÕßììÙ³ÕþGDDšÇúƒä†õGùèêâ ¹Ÿ¢råÊo}ŒÔÇçWžQ)Fàáá Ó@¬·ËÉÉA^^€—!ËÉÉ)qÿ;wpìØ±×.íZ±bf̘£GÂÛÛ»Bû366ÆÇŒY³f!77Ïž=ÃâÅ‹ß:-»4ù+šWÝBCC.\À'Ÿ|‚íÛ·#))I’,úàСC¸|ù2Úµk‡>ø@ê8D¥òᇢU«V¸pá:$u""*#Ö$G¬?ÊNŠQ)r=>¿téŽ?®ÚV\\¾ùæ¼qߺp|þ²rJBˆÔÔTall,¬¬¬DAAf8¿€W~^öí·ßŠÖ­[¿ñ¹•*U%~Š_Ã… Jüÿwí/''GŒ?^ØÚÚ KKK1tèPñôéSÕýeÝÞßÿ®¼š–——'”J¥¨\¹²°²²*‘¹AƒbôèÑbóæÍ"!!A+yô»»» >,u¢2Ù·oŸ :uê$u""*#Ö$W¬?ÊNÛW.–ëñùùóçEÛ¶m…R©ÂÑÑQ|ýõ×"77÷µ/M^m+ï•ÉB¼X¿äááððp„……ñÔ=oooxyyaéÒ¥ BPP‚ƒƒ‘žž^â±uêÔA§Nàããooo899I”ZwEDDÀÝÝÍš5Cll,—>“¬ÁÙÙW¯^Åùóç%?˜ˆˆJ‡õÉë²),,Ä¿þõ/üðØ?>¾ùæ©#‘Í;sæÌÁìÙ³1gΜR?Ϩøøúúþøãµ‡#ÝðóóC‡ðÅ_à÷ßÇÓ§O…eË–¡_¿~°µµERR~ùåŒ?M›6EÍš51tèP¬^½±±±<À²e˼¸L# *’###L:°téR‰ÓQi±þ 9cýñv¹¹¹Æ‚ àëë‹jÕª!""€4£RH»ŠG¥xzz–éyª•:QQQh×®Ú¶m‹ÈÈHõ'$ɵmÛ.\@TTÚ´ióÆÇ !påÊœ>}ÁÁÁ8sæ >|Xâ1¶¶¶ðööFÇŽѱcG¸¸¸ÀØØXÓ/Ag$&&¢Q£F°³³Cbb"LLL¤ŽDTfÙÙÙpttÄ“'OpóæMÔ­[WêHDDô¬?H°þø?ééé EHH‚ƒƒqîÜ9äææ–xŒ««+¢¢¢ T*ñäɃ:æ2$ùùù°±±Ann.ž>}úÚ!Ôo¢Z©Ó¦M888àÂ… ªk£“þ¸yó&.\¸€ ¼µ¡¼˜*Þ¢E Lš4 »víBrr2®]»†uëÖ! uêÔÁ“'OpàÀL›6 mÚ´­­-úô郥K—"""ZzeÒX·n 0qâDT$[ffføôÓOQPP€uëÖI‡ˆˆÞõéC®?’““ñ믿bÊ”)hÓ¦ lllàç燅 "88ùùùpqqÁ¤I“°sçNÜ¿gÏž…««+ÒÒÒpîÜ9©_iÈÙ³g‘™™ 77·25t€—VêÀ¿þõ/,Y²ß}÷¾øâ µ%é,Z´3gÎÄW_}…… Vx{  ™3g„„„„÷+•JxxxÀÛÛ:uBûöíajjZáýê‚‚‚¼ÿþûHIIÁÝ»waoo/u$¢r»wïðÞ{ï!11•*U’:½ëÒ'†R$$$ 88ÁÁÁ Áõë×KÜobb‚víÚÁÛÛÞÞÞðôô„µµõ+Û™?>¾ýö[Ìš5 óæÍÓV|Ò¢™3gbÑ¢EX¸p!¾úê«2=·DSçÂ… hÛ¶-Ú´iƒ¨¨(µ%é´lÙ—/_Æ¥K—àââ¢öí'%%©}AAA¸té U÷WªT ­[·FÇŽUzÕªU“$ëÛ$%%¡^½z¨U«nß¾ ##£w?‰HÇ ^½zxôènß¾ZµjI‰ˆˆ^Âúƒô‘ÜêçÏŸ#<<\u:ÕÙ³g‘••Uâ1uëÖ…êt*'''µ]¥Ž£RôWEG¥¼ÒÔIKKC:u „@bb"ªW¯®¶°¤}©©©xÿý÷¼8wÕÒÒRâD/¤§§#88XuºVddd‰áÊFFFpqqQ­äñññ„‰_X¶l>ÿüsL›6 ?þø£ÔqˆÔfêÔ©ø÷¿ÿåË—cÊ”)RÇ!"¢—°þ }¥ËõÇ“'OŠ3gÎ $$.\(q¼¢P(дiSx{{ÃËË >>>½’G¥è¯ŠŽJy¥©“&MÂêÕ«1sæL,X°@-AIÿïÿý?Ìš5 “'OÆŠ+¤ŽóFÏŸ?GXX˜jøòß/çWü¡Y¼’ÇÇÇG’n~‡pöìYDDDÀÍÍMëû'Ò”°°0xzzÂÃá¡¡RÇ!"¢—°þ }¥KõÇÝ»wKÌùzõj‰ñÅgx{{«ÆGh{G¥èuŒJymSçÎ;hÔ¨ÌÍÍqëÖ­×Nà&Ý— àáǸ~ý:6l(u¤RËÉÉÁÙ³gqæÌœ9s¯,olÔ¨|||Tžzõêi4Sbb"êׯzõê!!!AmK)‰tõêÕCRRnß¾­ZáGDDÒbýAúLÊúãÚµk APP‚ƒƒqûöí÷›™™ÁÍÍM5ÂÝݽ̗šV7ŽJÑ?ê•òÚrëÖ­‹>ú©©©üc‘±µk×âÁƒøðÃeÕЀ*Uª cÇŽøöÛoqâÄ ¤¦¦"44 .„¯¯/ªV­Š7nàçŸÆÈ‘#áààŒ5 ?ÿü3nܸ¡öL{öìƒfAEzG¡P`ðàÁB`Ïž=RÇ!"¢ÿÅúƒô™¶ê‚‚DFFbùòå8p jÔ¨fÍšaüøñøå—_pûömX[[£OŸ>øî»ïŠ´´4œ:u sçÎE÷îÝ%oèÀðááT*±uëV¤¤¤H‡*(55¿üò ”J%†^îí¼v¥ܸqM›6EµjÕ˜˜ ‹rï„´ïùóçhР=z„ .èÝò¼‚‚\¼xÁÁÁ8}úôk§ÍתU«ÄJž¦M›V¨òôôDXXBCCáááQÑ—@¤sBBBTç…K‡ˆˆÀúƒôŸ&êììlœ?^5'<<%S«V­ópZ´h!‹!ä•¢?Ô5*åMàE'pÇŽX¸p!¾úê«rï„´¯øäÃ?4ˆoÝ‹ŠŠ«ººVpp0=zTâ1vvvªy<;v„³³s©?¸Ÿ>}Š5jÀÊÊ =‚±±±&^‘¤ P½zu<þ)))°²²’:‘AcýA†@õGZZBBBTóp"##KÌç€Æ«8^^^hРº^‚VqTŠ~P稔·6ubbbЪU+(•J\¿~öööåÞiϽ{÷àää„ììl\ºt Í›7—:’Ö !píÚ5ÕյΜ9ƒû÷ï—xLµjÕàåå¥ZÉÓºukTªTéµÛÛ¹s'üýý1lØ0ìØ±C/HC† Áž={°{÷n ФIÄÅÅI“Hã6lˆ›7o">>^¶ç›ÉÝõë×áääÄúƒ FqýѺuk\¼x±Ä}5jÔ€———êTªV­ZüŒ)ŽJ‘'MŒJ)Õ”ØÉ“'£]»v¸~ý:æÌ™Sá’fÌš5 ñññpwwÇĉ¥Ž#;5kÖİaÃðÓO?!66>ÄÞ½{Ñ£Gà'È`xzzBCC%NBDd¸Â°þ ÃQ\ØØØÀÁÁ#FŒÀúõëqíÚ5U]>uêT´mÛÖà:àììŒ?þéééøú믥ŽC¥4sæLdffbܸqj›}[ª¦Ž±±16lØ,Y²GUËÎI}>Œü&&&X·n,.ǧëììì0pà@äççø¿ÿÐé;6uˆˆ¤WüÌúƒ Eñߺ££#nݺ…­[·bìØ±prr’8™îš?>,--±iÓ&µ]ž4çÔ©Sضm¬­­1oÞ<µm·ÔGþ-[¶Ä‚ „Àˆ#^¹’IçîÝ»5j„Xºt)Z´h!u$½Â¢Š ›:DDÒcýA†¦øo=<<\â$òaoo¹sçBqãÆ!''GêHôÙÙÙ?~<„˜?¾Zgß–j¦N1!úõë‡C‡ÁÇÇ'OžäÒ7‰ S§N Eÿþý±oß>( ©cé´´4T«V –––xöì·d„¨V­ÒÓÓñìÙ3½¾z‘.býA†ˆõGù¢C‡ˆŒŒÄ¿þõ/,^¼XêHô3fÌÀ?üwww„„„¨õÌš2mI¡P`óæÍ¨[·.‚‚‚0{ölµ¡òùæ›o lÚ´‰ÿÑW³èèh!ЪU+þnÉ`( 8;;C˜˜©ãÖdˆX”G¥è>MJ)óÖlll°cÇT®\‹-ÂÁƒÕˆJoÏž=X²d LLL°k×.X[[KIïÿÅÙÙYâ$DÚUü7Ï¢ŠˆHûX¡býQ>•¢»´1*¥\-",Z´EEEð÷÷ÇÉ“'Õ‹ÞáäÉ“øè£ „À’%Kàêê*u$½Ä¢Š ‹*""é°þ CÅú£ü¦OŸŽ¾}ûâñãÇð÷÷Gaa¡Ô‘ ^AAüýýñäÉôïß“'OÖÈ~ʽîgúôé˜0a²²²0pà@þÃÓ¢˜˜ 8yyy˜2e ¦L™"u$½Å¢Š ‹*""é°þ CÅú£ü8*E÷hkTJ™%ÿ]aa!FŒ;v N:8uê6l¨Î|ô7ñññðððÀãÇ€mÛ¶ñ\k ²¶¶FZZRSS9¬ Ê“'OP½zuØÚÚ"%%Eê8DD…õ*Ö†N:¡°°û÷ïÇ| u$ƒ´gÏ :•+WFpp°FϬ©Ð„ccclÞ¼]ºtARR|}}ñðáCue£¿INN†¯¯/?~Œ.]º`ãÆlèhгgÏTWŸ`AE†ÆÖÖU«VÅ“'O‘‘!u""ƒÁúƒ ëŠã¨éi{TJ…Ç.›˜˜`ß¾}puuE||<¼¼¼ Žlô’øøxxzz">>nnn8tèLLL¤Ž¥×’æ ’Jñß~ñ¿""Ò<ÖdèXTG¥HGŠQ)j¹––••>ŒöíÛ«šÑÑÑêØ4ˆŠŠR5ËÜÜÜpøða˜››KKﱨ"CW¿~}`£žˆH‹X¡cý¡+W®„¿¿?ÒÒÒàçç‡øøx©#é½øøxtíÚiiiÀ²eË´²_µ] ½zõê8yò$zôèäädtìØ§NR׿ Ö‰'йsg<|ø¾¾¾8qâlmm¥Ženݺptt”8 ‘4øM‘ö±þ CÇúC=8*E»¤•¢¶¦(•J:tÇGzz:|}}ñ믿ªseçÎðóóCFFFŒß~û RÇ2÷ïßÔªUKâ$DÒ¨]»6àÞ½{'!"2¬?ÈбþPŽJÑ©G¥¨µ©¼øÃùå—_0uêTäææbÈ!øüóÏ‘ŸŸ¯î]é­üü||þùç>|8òòò0}útlÙ²•+W–:šAyúô)ÀÆÆFâ$åÓ»woŒ3æ•Û…hذ!–-[†ÈÈH( ¸ººâå áß^PP ºíâÅ‹8p ììì`aaLž<ù­W&ˆ‰‰ÁСCaoo¥R ;;;øùùáðáÃ¥z ¯Ë¡ËîÝ»???ØØØÈ*÷›ÿíÿ[ ""ÍcýÁú£¬¦OŸŽ¦M›ÂÂÂ666ð÷÷Grr²Ô±Êõ‡zqTŠf騵7u@¡P`Ù²eXµjLLL°lÙ2øøøàÎ;šØ^¹uë<==±lÙ2˜˜˜`õêÕøþûïy•+ Ƚ¨7nvïÞÌÌÌ·Ÿ:u III9r¤ê¶[·naÛ¶moÜVPP:uê„.]º !!ÏŸ?GPP”J%¢¢¢^ûœøøøÀÓÓ×®]Cff&nÞ¼‰1cÆ 00P=/RÇaÀ€X±b…ÔQÔ¢øTÏ'OžHœ„ˆÈp°þø?¬?JG©Tª~çýõrrr0tèP©c•ëõã¨ÍЙQ)BÃΟ?/acc#öï߯é]ÊÖþýû…µµµ 5j$.^¼(u$ƒæîî.ˆ°°0©£”K~~¾¨Y³¦X·n]‰ÛýýýŰaÄ/þ}«W¯µk×ÏŸ?/q{~~¾BñÝwß•iÿ...béÒ¥ï|Üš5kDÓ¦M…R©VVVbРA"99Ydff 333@XXX ±yóf!„éééâã?¶¶¶ÂÊÊJŒ9R•}×®]ÂÅÅEµýÉ“'‹J•*‰ôôt!„Û·oíÛ·ë¾…bÅŠÂÕÕµDÖ°°0QµjU‘™™ùÖ×ô÷ߟ\ ÂËËKê(DDƒõëòÖÅ‚‚‚„±±q©«‹XhNnn®>|¸ LMMÅž={¤Ž$[;vì&&&€1b„ÈËË“,‹Æ›:B‘––&,…B!&Mš¤ú€#!222ħŸ~* … ,ÒÒÒ¤Žeð7n,ˆëׯK¥ÜfΜY¢0xúô©¨R¥Š8qâ„âÿЧœœáìì,¾ýöÛ·ççç‹{÷î âöíÛ¥ÞoYžsðàA/„âáÇ¢[·n¢_¿~¯äxÙ AƒDÿþýEZZšHOO]»vŸ~ú©BˆG ###ñèÑ#!„Íš5 6‡BññÇ‹/¿üòû~úô©033±±±ªýŽ3FŒ;ö¯I_š:×®]„“““ÔQˆˆ ëÖå­?Š}ûí·¯4†ä„õ‡f‰©S§ªŽÍ§M›&iCBnòòòÄ´iÓTÇîÓ§OEEE’fÒJS§ØÊ•+Uïš5kŠ;wjs÷:)00PÔ«WOfffbÍš5RG¢ÿU·nÝ2ºææÍ›B¡Pˆ˜˜!Ä‹o4h úày¹h9~ü¸077wïÞ-q{tt´ ²³³K½ß×=gïÞ½ÂÊÊJXZZ SSSQPPðÚçž]Œ;VlÛ¶MˆK—.‰-Zˆ7nSSS‘••õÎ} ñbùr5D^^žØ´i“hÖ¬Y©^¿¾4u>|(ˆ5jH…ˆÈ`°þ`ýQÞúã×_ÖÖÖolÉëíᨔÒÓåQ)Zoêûõ×_UßD˜˜˜ˆ™3gŠÔÔT©âhͳgÏÄÌ™3U+–ªW¯.6lØ ù’-z•••• û¿ËÝ»w [[[,*Uª$ƒR©”*šFddd`Ù²eX¶lRSSabb‚éÓ§ã›o¾µµµÔñè5òóó@ö—’ïׯ>ýôSŒ9}ûö…½½ýÛ¸qcLœ8³gÏ.qûÊ•+Ñ·o_(•JŒ9U«VÅ;wðÓO?¡sçÎèÙ³ç+ÛZ½z5úôéƒ*Uª`Ĉ°±±Avv6Î;§zLvv6 aeeSSS$%%aþüùªû‹§Çß¼yMš4¼÷Þ{èÛ·/¦M›†•+WÂÆÆÉÉɈE·nÝ>>>HJJ¾}û0oÞ<( tîÜ?üð¾øâ‹Rí»Ø¸qã0oÞ<\½z{÷î}çï;''yyyøÿìÝy\Íéû?ðש´(¥)šBdZF²Ë–¥²ÔF²}È4ccƘ±Œ1>™åƒAŒfF³d²K)Ld™±¤E%m’ ©TTZ®ß¾ŸmÎé}–ëùxœ?tÞûUçt\ç~ßïë€ŠŠ TUUASS³Áï“EjjÏþ‹¨¬¬DzzºÀicL9ÔþÂõ×­?¶lÙ‚µk×"<<ýúõ«÷XyP[ÈË–òòNWWÁÁÁضm>ýôSlÛ¶ !!!Ø´i“\ï¢& 'Nœ€nß¾ ---lܸï¿ÿ¾Ð±^$ô¬Qjj*yyy‘ªªªxé×çŸ^gF_^ݽ{—–/_.^ª¥¦¦F3gΤôôt¡£±àÿ.SË–-#tüøñ:_ÙŠ’ÂÂBñÒïç¿~õêUzûí·ÉÐÐZ·nMæææ´hÑ¢z—‡ÇÅÅÑäÉ“©}ûö¤££CíÚµ#WW×:K;7lØ@íÚµ#êׯíØ±£ÎØ>>>âeÎDôìŒÂ¼yóÈÈȈtttÈÒÒ’6nÜXgì!C†P÷îÝÅÿö÷÷a7‘†Æ&"ÊÏÏ' š4iR£~×µ¯›çoòìe?ßøÆ7¾ñMú7EÀõGËÔÀ³Ïµ»uÕÞ^Õ?H(Òß<áV)ÏÈ[«dDrr26lØ€ß~û OŸ>…††&Mš„¹sçbøðá‰DBGl"©S§ðË/¿àÀâŸeúôéøôÓOѽ{w¡#²F¨}½ÉП@uu5LMMWWW¡ã´¸Ú¿ƒ.]ºœ„1Æ”CíÊH®?”×\‡ )$$K—.EVVÔÕÕ±lÙ2|úé§ÐÓÓ:šTá»ï¾Ã¦M›PVV###lذsæÌ‘é¹™šÔ©•““ƒ-[¶àçŸÆÃ‡˜2e <<<лwo¾Ü•+WŒýû÷#33`ddooo,\¸°Þe§Löèêꢤ¤ÅÅÅhÓ¦Ðq˜@°~ýz¤¤¤@EEEè8-ª¸¸zzzÐÕÕÅ£G„ŽÃcJëpýÁõ‡ðJKKÅ­R*++¡¯¯¯T­R.\(7­RdrR§VEEBBBðóÏ?㯿þÏÔš››ÃÅÅ...prrì?¼’’œ>}aaa OäˆD"Œ9ÿùÏ0qâDhhh’½ž¶mÛâÑ£G(**RøYiör†††ÐÒÒB`` œœœ„ŽÓâ=z„¶mÛBOOEEEBÇaŒ1¥ÀõãúƒëY’––†µk×â×_Euu5 ðþûï+Ä¢…ÚÅ$;wîDQQÔÔÔàéé‰5kÖÀÂÂBèx&Ó“:ÏËÈÈÀüýû÷ãÚµk⯫ªª¢W¯^4h;;;tïÞ]âÍå*++qóæMÄÅÅáÒ¥KˆŽŽF\\ª««ÅÇôéÓ“'O†‡‡ÌÍÍ%:>ky:tÀýû÷qïÞ=´oß^è8Œµ¸ÜÜ\˜˜˜ cÇŽÈÉÉ:cŒ)®?˜²ãúC6q«Ù%7“:ÏËÌ̯Ž9}ú4JJJêܯ®®Ž=z K—.077‡…… Å7mmmhiiÕùž²²2<~ü>ß222žžŽŒŒ ¤¤¤ˆw#¨Õ¦M899‰W ñDŽbyã7••…Û·oÃÌÌLè8Œµ¸¬¬,¼ñÆ033ÃíÛ·…ŽÃcJë¦ì¸þmÜ*EöÈå¤Î󪫫qýúu\¸p/^D\\RRRÄÛQKJ«V­Ð£GØÙÙaÀ€ptt„TUU%:“ÖÖÖHLLÄ7ðæ›o ‡±;;;ØÚÚ"..Nè8Œ1¦¸þ`ÊŽëùÀ­Rd‡šÐ^—ªª*ààà€… ž>}Š›7o"==™™™ÈÈȨ³çñãÇ(++«ó8ZZZÐÖÖ®³¢ÇÂÂ]ºt……ºwïuuu!~D&víÚAvv6îܹƒ+W® ((?ýôS³Ç“7µ×I œ„1Æ”×\4T4õ÷!o¸þoæææxÿý÷qèÐ!âÊ•+زe Þ{ï=X[[ƒˆÇã‡~À’%Kàåå…±cÇ¢ÿþ°±±A×®]ëÜlllпŒ;^^^X²d ~øá=z "ØØØ`Ú´iزe ®]»†ÂÂB:tï¿ÿ¾â­Ìù7¡g•“U+V¬ /œ-R—/_~áÌPrr2 üü|ñ×"""hðàÁ¯|œ³gÏ’ªªj³Æ#"zã7(22ò¥ßsãÆ @¥¥¥â¯EGG“ššZ³ÇkÊñ•••¤££CgΜíÿû 0àµÇ“ëÖ­#/œ!dŒ1&=\pýÑØú£Vcò‚ëÅVQQAqqqtèÐ!ñŠ›éÓ§“««+õë׬­­©K—.unÖÖÖÔ¯_?ruu¥éÓ§‹Wø9r„âãã©¢¢BèKPrßS‡1i155ܽ{Wà$-‹žëNDõ6¨‹ŒŒ„ƒƒC³Æ©ÝÙ#&&sçÎEII œ±mÛ6èëë£{÷îèÝ»7~øá|üñÇxòä vìØ÷Þ{¯Yã5UFFJKKÅgî W¯^X³fM‹Œ/ j_ûµ Œ1Ƥë®?šZ¼ÎïCqý¡ØÔÕÕakk [[[¡£( ¾üбW¨]¦WÛ)]ÑYZZÂÒÒ«W¯FYYòóóáëë‹ÒÒÒ:…V­Ã‡cãÆðóókÖxµËÊoݺ…ÄÄD$''#''óçϨ©©áàÁƒØ»w/´µµa``€{÷îÁßß¿ù?d”––´µµÅ_ÓÑÑyåïCeddx¶M%cŒ±–Áõ×@ãë×ý}È"®?kžÔaìºtéHOO8IËPQQÁ‘#G’’Œ1ŽŽŽÐ×ׇH$ªslHHfÍš…ƒ¢OŸ>ÍOGG°~ýzhiiÁÈÈŸþ9Ž;àÙ.v£G†§§'JKKQPP€:´Ø5ãµù?~,þZii)ttt^ø}(ªÚ 2cŒÉ®?¸þWHâ÷!‹¸þ`¬ixR‡±WP¶3e`ee…ÈÈH">>::::thc0wî\9r£FjöXÐÕÕ­S ˆD"ñY¨””¤¤¤`É’%ÐÔÔ„¾¾>|||ÄE—´YXX@GG§Îòïë×¯ÃÆÆ¦EÆ!33"‘ˆ‹*ÆkA\pýјúCR¿YÃõcMÇ“:Œ½‚––:v숲²2äææ GbÊËËñôéS@EEÊËËÅ÷%%%‰Ï 9s›6mÂÊ•+Å÷oٲ˖-Cxx8† òZ㩪ªbΜ9Xµj***PXXˆ 6`„ 333hkkcÛ¶m¨¬¬DII vîÜÙàõ·õý|M9^MM ^^^øúë¯QRR‚;wî`Ë–-˜3gÎk'/rrrPQQhhh‡1Æ”×\4T4ç÷!/¸þ`¬„éÏ̘|prr"têÔ)¡£H €nµ|}}ÉÐÐttt¨oß¾túôé¾WMM´µµëܪªªˆˆèÚµkuþÝÐxåååäííM†††¤««KS¦L¡‚‚ñý'Ož$ÒÒÒ"mmm5j%%%‰ïoêxM=þñãÇäååEºººdddôÒ]êû~yvòäI@£G: cŒ)®?¸þ¨¯þhè÷!ϸþ`¬éDDJÒñ“±fX²d ~øálÞ¼‹/:c-fãÆøøã±téRüïÿ:cŒ)®?˜²âúƒ±¦ã˯«‡Ô»­&cŠ(>>x»IÆ×LYqýÁXÓñ¤cõxë­·±±±'a¬eÕ~¨ý`Ác¬åpýÁ”׌5_~ÅX=ÊÊÊ ««‹V­Z¡¸¸jjjBGbLêž>} ]]]Š‹‹¹Q!cŒµ0®?˜2âúƒ±æá•:ŒÕCKK ööö(++CLLŒÐqk×®]CEEz÷îÍcŒ €ë¦Œ¸þ`¬y”~R§¸¸………(((@AA Q\\,t,&C ¸pá‚ÀIkçÏŸ lذ‰‰‰KKKøøø`æÌ™0008aÓäçç#(([·nEFF`È!X±b\]]NÇküøñ8~ü8Ž;†qãÆ ‡1‰;zô(ÜÜÜ0aÂ9rDè8Œ1ÆÀõS|\(ŽúZ¥ØÚÚJ´UJ}^Õ*¥vêCÑZ¥Hç·ØLD„àà`ØØØ`ÆŒHLL„££#>Œääd|ôÑGr7¡<»üê£>BJJ vïÞž={âܹs;v,úô郃BçÖØ¿¼óÎ;€œ„1é¨}m×¾ÖcŒ ë¦è¸þIIIXºt)LLL0uêT}àíí¢¢"¡#²—Ð××ÇèÑ£QXXˆˆˆ¡ã0&Q'OžDQQÆŒÃcŒÉ®?˜"ãúCþÔÔÔ ((666?~ضmúõë‡ââbìß¿_èxŒIDpp0JJJ0yòd.¨cLéêêÂÃÃë¦PjëáÇóçÅ­Rä£UJ‹Nêøûû£ÿþHNNF¯^½påÊxzz¶d¹0cÆ \½z½zõBRRú÷ïÝ»w KiTTT`ÿþý7nÌÌÌðÙgŸ!11íÛ·ÇG}„÷ßðÓO? œ”1ÉøùçŸsçÎ8 cŒ±W™7o®?˜â¨­?rrr0hÐ ¼ùæ›Ø´iîß¿/p2p«yj•Ò"—_•——cîܹطo`Á‚øþûïå~?xi+//ÇâÅ‹áïïàÙd¿¿?ÿÞ¤äâŋسgöïß‚‚Ï:¸;3gÎÄØ±cÅ;NX[[#117nÜÀ›o¾)dlÆ^KBBlmmaccƒøøx¡ã0Æ«×LQ<_Œ?¸wï@]]îîî˜={6œ¥ºõ5{·Jy9Yn•"õ¿ââb¸¸¸`ß¾}ÐÕÕEpp0¶nÝÊ ©©‰;wâ?þ€®®.áêêŠââb¡£)ŒÚË«zöì‰AƒaçÎ(((€ƒƒ¶nÝŠœœ8pîîîu¶­]Ñðã? 1‰¨} ÿç?ÿ8 cŒ±†pýÁÅóõ‡¯¯/îܹƒC‡aܸq¨®®Æþýû1vìXXXX`õêÕÈÊÊ8±ràV)¯&Ë­R¤ºR'//®®®¸~ý:LMMqòäI>«ÐL 3f rssÑ»wo„††¢C‡BÇ’Keee8pàöíÛ‡“'O¢ºº`llŒ÷Þ{³gφ­­m½QTT„Î;x6k«¯¯/õÜŒIZ~~>ÌÌÌ ªªŠ;wîðV¢Œ1&ã¸þ`Š ¡ú#''@ZZ€g}Nœœœàíí 777^ þþþøè£ðäÉôêÕ ÁÁÁ°´´:–Lºuë&OžŒëׯ£uëÖðóóìY³Ë#µ•:©©©>>¤§§'®óutthÖ¬Ytá ¦Ul¹¹¹Ô«W/@¦¦¦tãÆ ¡#É­øøxêØ±# Þ½{S^^^‹Ž/Ñ•:<À Aƒšš '''œ8q‚ÏHÉÓ§OáêêŠÓ§O£[·n¸páÚµk't,ÁΟ?   üñÇxôè€g»W¹¹¹aÖ¬Ypvv†ªªªDÇýûï¿1`À˜™™!%%…¯ïer¡¬¬ –––ÈÉÉÁÕ«Waoo/t$ÆcMÀõ“G’®?ÊÊÊðçŸâçŸƹsçPû±ÖÊÊ óæÍÃôéÓѾ}{IDW8©©©pvvFzz:¬¬¬†7ÞxCèXr-33ÎÎÎHIIA·nÝŽ.]º´Ìà’š***ÏôõïߟWè´€¢¢"êÛ·¯øwþèÑ#¡#µ¸Û·oÓW_}E]»vÏÔ ÒÎ;©  @ê\]] mݺUêc1& 7n$4nÜ8¡£0Æk&ggg®?˜\©­?&L˜ ñÇNII¡åË—“‰‰‰øó@«V­èÝwߥãÇSUU•ÄÇ”Wqqqâ+kú÷ïOùùùBGR÷ïß>766¦øøøW"+uxÕˆp”quÔãÇqèÐ!ìÞ½§OŸFMM €gMÕ¼¼¼0kÖ,XYYµXžË—/£ÿþèØ±#RSS¡¥¥Õbc3ÖT?F×®]qÿþ}ôíÛ:uÂÚµk¹7cŒÉ‰„„¬Y³YYY¸zõ*×L.<_\»v ½zõ’Ê8••• Å®]»püøqñ.·fff˜9s&æÌ™sss©Œ-RSSáèèˆ{÷îÁÙÙ!!!ÐÖÖ:–B)--ŤI“pòäItêÔ gΜA·nݤ;¨$f†æÌ™#îïrûömI<$k‚[·n‰¯áóöö:ŽTÔÔÔPTTÍš5‹tttÄ3ðZZZäééI'NœtÃܬƒ IDATÞÝÝзß~+XÆcýúõ€\]]IKK‹ŠŠ M›6nÞ¼)t<Æc¯póæMš6mš¸_ ––=šë&jëwß}·ÅƼ{÷.ùúúÖYѯ¢¢B£F¢ßÿÊËË[,‹,¸ÿ>uëÖ““UTTIaUTT““ nݺÑýû÷¥:ÞkOꊛSÅÅÅI"k†¸¸8jݺ5 _ýUè8ÓÐåUEEEBG$"¢ëׯ“ŠŠ éêêJý–±æÊÍÍ%RUU¥„„ÊÉÉ¡… ’ºº: 555š={6edd•1ÆØÿÉÈÈ Ù³g“šš uuuZ¸p!åääpýÁä¿ë–VSSCýõyzzŠOh Z¼x±R|†åV)-¯%[¥¼Ö¤NRRikkÚ»w¯¤2±fÚ½{7 6mÚÈõ÷’’  ¡C‡’H$¿ñš™™Ñ_|!ÑÝ«$iæÌ™€æÍ›'tÆ^ªö5:wîÜ:_¿}û6Í›7¯ÎÊÎÎ()cŒ±ììlòññ©3ñ>oÞ¼VÅsýÁdÝ«ê!ÒÖ­[©wïÞuN÷ë×O¦NKRK¯aÿ_K­Žjö¤ÎãÇÉÖÖVfþ@Ù33fÌ Ô«W/*++:N£5tyUDDUWW ³^ÏŸ…ˆ‰‰:cu\¾|¹Á³¹©©©äååUgiÿ’%KèÞ½{-œ–1Æ”×½{÷hÉ’%u.‘õòò¢ÔÔÔ—Ïõ“e©?„råÊúðÃ_º5ú¹s焎'1Ü*EX-Ñ*¥Ù“:sçÎ%dkkK?–d&ö?~L={ö$ôþûï §A©©©´jÕ*²°°¿™ŠD":t¨\Ζÿ÷¿ÿ%4tèPª©©:cDDT]]M ôý÷ß7x|bb"yxxˆ'wtttè³Ï>£‡¶@ZÆSN>¤Ï>ûL|rK$‘‡‡%&&6ø½\0YÔÔúC(Ož<¡½{÷¾p•€••}ûí·r}r‹[¥Èi·Ji֤Ή'ikkËì¥0Ê,!!Aü¢‰ˆˆ:Π껼jÕªU¯<%ÊÊÊÄTþþþBÇaŒˆˆüüüYZZ6iÙgll,¹¹¹‰ÿNõôôèË/¿”ê5ÁŒ1¦l=zD_~ù¥xµ€H$"777ŠmôcpýÁdQsë!¥¦¦¾tkô‰'ÊÝÖèÜ*E¶H³UJ“'uÊÊÊÈÒÒ’ÐO?ý$Ñ0LrvìØA¨G2ÑÙ½ººš"""ÈËË«ÎåU:::4}út¹¸¼ª±j'=Û¶mKwïÞ:SrwîÜ!]]]‰DÍžäýçŸÈÙÙ¹NcA___*))‘pZÆS%%%äëëKâ÷Wgggú矚õx\0Y"‰úCHUUUtèÐ!rww÷¬½„iÕªU”––&tÄzq«Ù$­V)MžÔùòË/ 4ˆ—wʰêêjêß¿? õë× –£öò*33³.¯ PØ…žžž€Þyç¡£0%çææFhÖ¬Y¯ýXçΣaƉÿ–Û·oO›6m¢'OžH )cŒ)‡'OžÐ¦M›¨}ûöâ÷ÓaÆI¤‡×LVH²þÚÝ»wiÆ ↷Ïoþ믿ÊÄ ôãV)²IZ­Rš4©“ššJššš¤¦¦Fׯ_—X&W¯^%UUUjݺ5¥§§·Ø¸EEEäïïÿÂåUæææryUc=xð€ŒŒŒ ‡)©ß~ûM<ù"É~8âkä©©)mß¾]n–V3Ƙ***hûöídjj*~ÿ0`€DW1pýÁd´ê¡Õnì2}úô¶F_¸p¡Ìô¬áV)²M­Rš4©3vìX@K–,‘ÈàLú,X@h„ R§öò*OOÏ:orµ䣢¢”neWmc2Þšµ¸ÌÌLÒ××'ôûï¿KeŒ£G’½½}‰Û]»vQee¥TÆcŒ1yTYYI»ví"sssñû¥½½==zT*ãqýÁ„Ôõ‡,(**"???rpp¨³5zŸ>}Ýì…[¥ÈI·Jiô¤Nhh( n’)G ÉØØ˜ÐÉ“'%þøIII´råJ¥»¼ª±Þ}÷]@#GŽT˜žALöUUU‰/“š2eŠTǪ©©¡ýû÷“µµµø= {÷î´oß>~Í3Æ”Zuu5íÛ·ºwï.~´¶¶¦ýû÷KýD×L-YÈ’k׮т Ä“Y¨uëÖâÛ-‰[¥ÈI·JiÔ¤NMM õéÓ‡;gË©ÚNÛ ÈãÑÎ;iàÀuf¦»víJ_}õeffJdEððáCñ2ëo¿ýVè8LI¬_¿žÐo¼A………-2fuu5‰Ï  ᢂ1¦Tjjj($$„lllÄ––Ôb,\0!QÈ’Ú­ÑGŒñÂÖè6lúÖèÜ*E¾H²UJ£&u:DÈÎÎŽgûåPuuµø,ú±cÇšõUUUÆ—W5éS§HEE…ÔÕÕéÒ¥KBÇa .::šZµjEªªª-~vˆèå—8884û½‡1ÆäɱcÇê\Ž!äe©\°–$tý!kRSSéóÏ?aktwww©mέRä¤Z¥4jR§¶!æÁƒ_k0&œàààf­ÖIJJ¢åË—×yCªíö¾wï^¥¿¼ª±>ýôSñ6ˆ÷ïß:SP¹¹¹â¿Õ•+W š¥%‚2Ƙ¬Õò\°– Kõ‡¬©ªª¢£G’»»;µjÕJüabbB+W®”Ø2Ü*E>IªUJƒ“:ÑÑÑâ½Ôy%†üª©©¡7ß|“ÐÅ‹ë=¶°°°ÞË«nß¾ÝB©Çó×1B*³óL¹=}ú”† BhôèÑ2ó“æÖ½Œ1&´sçΉÿ¯ÝígÓ¦MôäÉ¡£×LúdµþE999´aÆ:}¶D"9’öîÝKeeeÍz\n•"ß$Ñ*¥ÁIwww@{öìiö L6Ô¾`<<<^¸¯ªªŠŽ?N¤¡¡!~£ÑÓÓ#ooo:{ö,O꽦{÷îQ§N-]ºTè8LÁ,Y²„™™™Lž-))!___200¿¿8;;Ó?ÿü#t4Æk²þù‡œëliìëë+“+˜¹þ`Ò$ëõ‡,ªÝ}ÆŒ/l¾`Á‚&÷ÃáV)òM­RêÔÉÊÊ"555êØ±£àËGÙ뫨¨ Ž;’šššx‹Ë†.¯’•3MŠââÅ‹¤®®Nè—_~:SÛ·o'¤¡¡A—/_:N½ŠŠŠhÍš5¤««+>CåææF±±±BGcŒ±ÅÆÆ’›››¸ ª®®.­Y³F°í‹‹ë& òTȪ¢¢"Ú±cÇ [£;88ÐŽ;õÞ­Rä_s[¥ÔªwR§vK´5kÖ4ëÁ™ìYµj ?üP¼LïùÎìëׯçË«¤Ìßߟºº::uJè8LÎ………‘ªª*‰D"¹Zr›ŸŸOË—/'mmmñdòäÉ“)))IèhŒ1ö‚¤¤$š§IIIÐÒÒ8r‰Dصkœœœ#FàÞ½{BÇbr"''£FBvv6œœœ°k×.ˆD"¡c½SSSlß¾7oÞÄìÙ³ADð÷÷G·nݰhÑ"äææ ‘1¦rss±hÑ"tëÖ þþþ "Ìž=7oÞÄöíÛajj*tÄ×Âõ{ŠXȺ®]»bݺuÈÊÊÂñãÇáîî8|ø0üýýQUUooo¨«« œ”½.uuuÌ;UUUصkW“¾WDDôï/VVVÂØØ%%%ÈË˃ÄÂ2áåçç£cÇŽhÛ¶-rss¡¦¦&t$¥õèÑ#899áÚµk°µµÅéÓ§add$t,&çµÏqc¼ôò«ž={"99III°²²’\B&3âããagg[[[ÄÅÅ ‡ÈÊʨQ£pëÖ-X[[#22ÆÆÆBÇb2 77£FBbb"zô興¥=+sá¬^½§NðlåáòåËñá‡r0ÆXƒÊÊÊàçç‡o¾ùùùù€‘#G⫯¾Â AƒN' ®?Ø«pý!›¸UŠbkN«”Vêܽ{ÉÉÉ055å fkk‹öíÛ#>>÷ïß:`ff†³gÏÂÚÚ7nÜÀСC‘••%t,&°ÌÌL 2‰‰‰°µµÅ_ý¥ÔÕ Aƒ‰Ó§OcðàÁÈÏÏÇ'Ÿ|‚®]»bëÖ­xúô©Ðc2èéӧغu+ºvíŠO>ùùùùÈÈH¥Ð¸þ`/Çõ‡ìâV)Š­9­R^˜Ô¹xñ"`àÀ’MÇdÎàÁƒçÏŸ8 «ellŒ¨¨(ôîÝ·nÝÂàÁƒ+t,&˜˜ <iiièÛ·/þúë/>{úFŒèèh„……¡oß¾âk,--ñÓO?¡ªªJ舌1PUU…Ÿ~ú –––âôúöí‹°°0DGGcĈBG” \°çqý!Û¸UŠâkj«”&u¢££ŽŽŽŒÅdQís\ûœ3Ù`hhˆÓ§OcøðáÈÎÎÆ!C¸÷‘ ÅСC‘““ƒ‘#G"22’ÏÆ¼„³³3þþûo>|vvvÈÊÊ‚··7zôèÀÀ@TWW ‘1&€êêj¢GðööFVVìììpøðaüý÷ßpvv:¢Ìáúƒ\ȃãÇÆ'p&-®®®€'N4êø&uþùç@¿~ý$‹É¢¾}û._¾,pöozzz‡——JJJ0aÂøûû ‹µ;vÀÍÍ ¥¥¥˜5kBCC¡««+t,™%‰àææ†˜˜üñǰ²²Bzz:fΜ £¦¦F蘌±PSSƒàà`ØØØ`æÌ™HOO‡••þøãÄÄÄÀÍÍ "‘Hè˜2‹ëåÆõ‡ìãV)Ê¡©­RêLê ‰ä²ëLL Þyç´k×ÚÚÚ077ÇÂ… ‘ŸŸ+W®@$AGG§ÎmÖ¬Yõ>æÇŒž={B[[xï½÷——×2?”½õÖ[‰DˆÅKúe3©««cÏž=X½z5ªªª0þ|,Z´•••BGcRòôéS|øá‡øàƒPSSƒµk×b×®]PWW:š\PQQÁäÉ“‘€={ö K—.HNNÆ”)S`oo#GŽð{c ŠˆpäÈØÛÛcÊ”)HNNF—.]°gÏ$$$`òäÉPQy馯ì_¸þP>\Èn•¢<šÒ*¥Îÿniii())A×®]ѦM餓’³gÏbøðáprrBzz:?~Œ³gÏBGGW¯^WTT„ÒÒRñm÷îÝõ>®ŽŽ‚ƒƒQZZŠ”””——+ÌÖqººº077Gqq1233…ŽÃ^B$aíڵسg455±uëV899!77WèhLÂîÞ½‹áÇcûöíÐÒÒBPPV¯^Íg”›AUU3fÌ@rr2vî܉Î;#..îîîèß¿?ÂÃÃ…ŽÈ“ ððpôïßîî‹CçαsçN$''cÆŒPUU:¢ÜáúCypý!_¸UŠòhR«zΑ#GM˜0ä}óÍ7¯¼ÿòåË€*++_kœ³gÏ’ªªêk=†,7n cÇŽ …5àÊ•+ôÆoêØ±#={VèHLBΜ9C:t daaA111BGR(åååôÃ?±±1 äèèHýõ—ÐÑc¯á¯¿þ"GGGñßµ±±1ýðÃT^^.t4…Âõ‡ââúCþ 8Ð… „ŽÂ¤ììÙ³€† Òà±uVêddd,,,$2»ÔRrrr‡©S§J}¬ÈÈH888H}œ–bnn¼RG888àêÕ«=z4rssáä䄯¾úŠÁʱªª*¬^½£F½{÷àêêŠ+W® W¯^BGS(X´hÒÒÒðÍ7ßÀÈÈÑÑÑ>|8FK—. ‘1Ö—.]ÂèÑ£1|øpDGGÃÈÈß|ó ÒÒÒ°hÑ"hhhQ¡pý¡x¸þO$ç­RXÓ4¥UJIôôt@—.]¤—N ŠŠŠÄ· 4jŒÌš5 DŸ>}^+¯,©]•U»J‹É>UUU|ùå—ˆŠŠ‚¹¹9Ο?·Þz BGc{{{\ºt ]»vŹsçðÅ_pÏ¢££ƒ•+W"##+W®„ŽŽŽ;Lš4 BGdŒ='!!“&M‚ƒƒŽ;öÒ¿a&}\È?®?ä[RR gÏž'a-åÍ7ß$&&Ö{\¿à‡x6/OLLL`gg‡ß~ûM*€¹sçâÈ‘#5j”TÆJís]ûÜ3ù1hÐ ÄÆÆbúôé(..ÆÌ™3áââÂgÍdXzz:ÆŒƒ9s戟³˜˜ 0@èhJéù³üË–-ƒ¦¦&8€·Þz žžžHII:"cJ-%%žžžxë­·pàÀhjjbÙ²euVÛ±–Çõ‡üáúC1Èk«”—‘Ʈյªªª`oo‘H„ªª*éþ RÖØ«jêLêÔ^ÆT»òEžlݺÿýïáç燒’ÏV}öÙg¯µÓÉ–-[°lÙ2„‡‡cÈ!’Š+3ŒŒŒ<[ÁÄä®®.‚‚‚pðàA˜˜˜ <<666ؼy3jjj„ŽÇþOUUþ÷¿ÿÁÖÖ055ÅáDZ{÷n^>+ŒŒŒðÝwß!-- .„šš~ýõWX[[cΜ9üA…±–™™‰9sæÀÚÚ¿þú+ÔÔÔ°páB¤¥¥á»ï¾×.L8\È®?‹¼¶Jù7iíZ]kóæÍ 3éßèV)ÏwM®í~ž——'¥ÎÒuõêUzûí·ÉÐÐZ·nMæææ´hÑ"ÊÏÏï~¥­­]ç6zôèz©©©½ð}UUU-ôSIWnn®xÇ&ßŠŠŠÈÛÛ›D" {{{îŒ/Μ9Cööö€TTTèƒ> G ‹ÕãöíÛ4oÞ>>”-t4ÆZvv6ùøøººº¸þš7oݾ}[èh¬\È&®?Ïĉ 8p@è(¯Eš»VgffR=èÌ™3ÙùZhþù' I“&Õ{\I===@EEER ÇdGQQ ===¡£0 ‰ŠŠ¢7ß|“H$¢÷Þ{ bܾ}›Þ}÷]ñV»vvvtîÜ9¡c±&HMM%///RQQ!¤¥¥EK–,¡{÷î 1…rïÞ=Z²d iii‰?€zyyQjjªÐÑXpý!¸þP\C‡%%t”f»{÷.¨÷½áu&uÆOÁÁÁ¯õ²¤vrjøðáõWçò«ÊÊJ@«V­š¹@ˆÉUUUàm)ÈСC ???â·ß~ƒ••V®\‰¢¢"¡ã)¼ÂÂB|ñŰ²²ÂŸþ ###øùùáêÕ«ptt:k‚®]»"00 ððð@yy96oÞŒ®]»bÅŠ(((:"cr­  +V¬@×®]±yóf”——ÃÃà  D×®]…ŽÈš€ëaqý¡øä¹UJ-iîZý矢²²É* Ý*åùüߌ.S.ü¼+®ÂÂBúðéU«V€ôõõiݺuTRR"t4…óèÑ#Z»v-µmÛV|ÉÎÇL………BGcKnnnâK ôôôèË/¿äåìŒ5Ñ£GèË/¿¯‰DäææF±±±BGcÂõGËáúCyÈ{«"é­Ô)..¦.]ºÐ­[·šý²¨±­RxR‡ñó®ÒÒÒhÆŒ¤ªªJÈÈȈ6lØÀF%àÑ£GäëëK†††â³fÍ¢ôôt¡£1)ùçŸÈÙÙYüÞi``@¾¾¾üa…±”””¯¯/ˆÿ~œéŸþ:“®?¤‡ëå£(­RìììhÆ ¯¼¿92—/_&555244$CCCñïÊÐÐP®{5¶UJOòmÚ´!T\\,ÕpLvpOå’œœLS§N÷ÑÕÕ¥eË–QVV–ÐÑäNVV-[¶ŒtuuÅ= ¦M›F7oÞ:k!çΣaƉ?œ¶oßž6mÚDOž<:c2åÉ“'´iÓ&jß¾½øïeذaÜçC‰pý!9\(¯Ö­[züø±ÐQ^KTTéêêÒ¶mÛÄó·oߦåË—SXXX³&už>}JwîÜߎ?N(33S®ë²’’@:::õÇ’•Oê(§äädòöö&MMM@­Zµ"OOOŠŽŽ:šÌ;wîyzzŠ—”kjj’··7%''  $""‚  þ°jjjJÛ·o§ŠŠ ¡£1&¨ŠŠ Ú¾};™ššŠÿ> @BGcáú£ù¸þ`Štu…4v­~ž¢\~EÔ¸ç]ôLMM‘““ƒ»wïÂÄĤ¡¾=Ldgg£sçÎèÔ©îܹ#tÖÂîß¿???lß¾]Ü€«gÏž˜7ofΜ ʆüü|ÁßßÉÉÉž5.ûàƒðá‡6ªÙS|ÇŽÃêÕ«077ÇêÕ«áåå555Ó1Örªªª„¯¾ú ™™™{{{|õÕW?~¼°á˜Làú£q¸þ`ωD€ç>¾3%Шçýùž·Þz‹Ðõë×¥6Óô¼ììlruu%}}ý—Τý{v®U«VÔ¦MñýK—.%+++jݺ5éëëÓÔ©S)77·ÙãíÞ½›¬¬¬HGG‡:uêD|åã5uü¦ß®]»FÈÞÞ^ÐLXOž<¡€€ú¨N#KMMMrww§€€Á'"¥)77—ÈÍÍ444Ä?¿‘‘}ôÑG”””$tD&'ª««)((ˆ,--ů# á·LáÔÔÔPHHÙØØˆ_ï–––Ä“™¬Ñ¸þàúƒÕ[¥(ŸfMêL:•Ðo¿ý&ÕpÿÖ˜kÞÒÒÒH$ÑÕ«W_yÌÙ³gIUUµÙã½ñÆÙøàÍ¿¹ÇKþ}ûM›6MÐLöTTTÐÑ£GÉËËKÜŒÿ·õ¬½½=­X±‚¢¢¢èéÓ§BGm¶òòrŠŠŠ¢+V½½½x«êÚ7Ï3fÐñãǹ7 k¶ÊÊJÚµk™››‹_[tìØ1¡£1&ÇŽ#ñëÛÜÜœvíÚ¥} ˜0¸þàúƒ½œ‰‰  »wï …µ;wîêÔ©S½ÇÕ¹ÈßÜÜÄ×?Ë’Ÿþ½{÷FïÞ½_yLdd$šõøYYY¸}û6bbb0wî\”””ÀÙÙÛ¶mƒ¾¾~££©ã¿N^I©}®kŸ{Æj©««cüøñ?~<ÊËËŽC‡!,, 111ˆ‰‰¯¯/´´´Ð§O8::bðàÁ8p Ì^ ÿðáC\ºt çÎÃ… pùòe”——‹ï766†‹‹ &Nœggghhh˜–)555Ìž=žžžøå—_°~ýz\½zãÇÇ€°nÝ:Œ5J蘌5Ydd$V­Z…K—.xÖ—ñ‹/¾Àþ󨫫 œŽÉ3®?¸þ`/×®];äääàÁƒÜÿVIr1©SUU…€€¬]»ö•Ç>|7nÄ™3gš5FAAàÖ­[HLLÄãÇáááùóç#88¸Áïoêø¯›WRxR‡5†¦¦&ÜÝÝáîî"Âõë׆°°0\¼xçÎùsçÄÇwîܶ¶¶°µµ…zöì ´mÛ¶Eò"33‰‰‰ˆG\\^h®¡¡¡C‡ÂÅÅ...èÕ«—¸c’¤®®Ìš5 ;w¯/.]º„Ñ£Gcذaøúë¯áèè(tLÆ•+W"** о}{¬X±óçχ–––À阢áúƒ±ÿÏÐÐÀ³IB¦jŸëÚçþUêLêXXX222¤«yŽ=ŠÒÒRL›6í¥÷‡„„`îܹ8xð úôéÓ¬1tttëׯ‡––´´´ðùçŸÃÝݽÁïmêø’È+)ééé€.]ºšƒÉ‘H{{{ØÛÛcÅŠ(++Õ+WóçÏãâÅ‹¸sçîܹƒÐÐÐ:ß«¯¯sss˜››£S§N044ßÚµk'^§¥¥MMÍ:ß[^^޲²2ÏÞàþ}ËÎÎFff&222PTTôÒ솆†0`† ‚Aƒ¡oß¾/ŒÃ˜4iiiaÉ’%˜;w.¶mÛ†ï¾ûQQQ2dœ±nÝ:ôíÛW蘌½àòåËXµjÂÃÃøä“O°`Áq Ř4qýÁ”]íng÷ïß8 k)µÏuƒ;Ý=-Vnn. ccc)^ö¢†z길¸Ð¼yó^zß®]»¨mÛ¶töìÙׯªªŠtuu)??_üµˆˆÒÔÔ¬÷±š:~sòJSûöí Ý»wOè(LdeeÑñãÇiÆ 4mÚ4²··§¶mÛŠ¯—öM__ŸìííÉÓÓ“6lØ@¡¡¡”••%ô¯…±Ñš5kÄ}#D"¹¹¹Qll¬ÐÑ#"¢ØØXrss÷üÐÕÕ¥5kÖp£N&“¸þ`Šì³Ï>#äëëÛâcËû®Õµ*++©W¯^ öó••]«×¯_OèóÏ?¯÷¸:+uŒÑ®];äååáÁƒ ^»% åååxúô) ¢¢UUUuf®³²²pòäIüý÷ß/|ï–-[°víZ„‡‡£_¿~¯5žªª*æÌ™ƒU«VaÓ¦Mxòä 6lØ€ &¼ò±š:~sòJS^^îß¿ccã†gÿk‚Î;£sçÎ;vl¯×.KÎÌÌDvvv3]<@aa! ¬¬ åååuV’ijjŠ—ö?†­öÖ©S'˜››·è2kÆ^—žž¾üòK,\¸ß}÷¶mÛ†#GŽàرcx÷Ýw±víZXYY “)¡ääd¬Y³þù'jjj ­­ à“O>ip8cBáúƒ)2![¥¨¨¨`âĉ˜6m¼¼¼^¸¿´´´Î¿G…nݺ‰ÿ­££ƒàà`ØØØàáǘ7o¦L™"¾”·©ãá»ï¾CHHÞ|óMïÏû-""üp«{tùÔê™3gþèç\>zÉ’%7tïŸZŸŸÏ—_~Ihhè~͆ ¨¬¬dâĉu¾ÞÍæ½QuÙ*åªC777öìÙCuuuƒ…cUVV²wï^š5k¦•:""vÊ××—¿ýío|úé§L:•ªª*^{í5ºvíJDDEEEFGUTTDDD]»våµ×^£²²’©S§òé§Ÿò·¿ýíÚÏH£³÷Õ:×sjõ“O>Y¯§Vûøøðûßÿž­[·^õóÏŸ?Ï‚ HNN®óµê#ïºüw|ùïü§ü`¨ãííÍ]wÝÅÙ³gùè£ê?Ø…ýë_”––r÷Ýwë(R;×£GÒÒÒ8xð ãǧ¼¼œW^y….]º`2™~ô]‘ï+))Ád2Ñ¥K^yåÊËË?~<$--=zQDD~Dco•RWË–-ã±Ç»êûË+VðÔSO±yófxà¾F—.]hÕª...µsqqùÁ6.—}þùç?~œAƒáããS{íöíÛ³iÓ¦½N}å½QuÙ*åC€‡z€÷Þ{¯c‰=y÷Ýw~p:€ˆˆØ¯Þ½{óöÛo“••ÅèÑ£)--%66–.]º{]'?HÓtµï•Ñ£G“••ÅÛo¿MïÞ½Ž(""×`äV)ß?Eº¼¼üŠß¿|jõÕ½úóŸÿ̼yóÈÈÈ`ذa7u½ïžZ}éÒ%þûßÿþä©Õ}ûö娱cää䓓êU«ÈÎÎfÔ¨QWýšÉ[ßê²UŠ‹í*#­ììlȀؿý'à 0€?þ˜ìììŸÜÄJÄH—'ð?6yiêöìÙCtt4|ð>>>,X°€Y³fÕÁ+M[YYK–,!>>¾öq½û￟˜˜˜Ú7"r%õ±WgÏž¥mÛ¶xyyqæÌ™F=pç»+c.ûî#f³™-[¶\õ#ÜÝÝiÞ¼ù?{ö,nnn8p€aÆÕþóµ®wéÒ%fÏžÍÛo¿Mee%=ôK—.­=äèj¯wÙþýû ¤²²ww÷«~þµò6´ÊÊJÚ´iÃ¥K—(..¾æ“5WêØl6üüüøâ‹/8zô¨ž«v2GŽ¡[·ntíڕÇGäG©T‰\Ÿ;wb2™jïêtèÐÈÈHBCCñðð08¡¢¢‚””âââj7Ö¾çž{°Z­Ü{ï½§±oêbφ ÂÞ½{Ù³göFuR™™™ 6Œ¡C‡²{÷îk~þU¿rqqaÒ¤IØl6Ö­[Wï!ÅX—ÿN'Mšdp©÷Þ{/™™™lÛ¶ÀÀÀ+N4zýõשªª2:¢4’ªª*^ýuºwï^{RZ`` Û¶m#33S§­Rœ_]·J¹êP`òäɬ]»¶b‰=Y³f S¦L18‰ˆˆÔ§‘#Gò¯ý‹¿ÿýïpüøqfΜIÏž=Y¹r¥NµtbÕÕÕ¬\¹’ž={2sæLŽ?N@@ÿûßù׿þÅÈ‘#Ž(""õàòýËoüÅùddd\÷Ïî«>~uYÿþýÉÉÉáÀôëׯ~Š¡.?C8pà@²²²ŒŽ#ò“´üYäÆÕÔÔ°aÃÌf3ùùùøûûc±X ÂÕõGïëˆÑß³HýSÿ{¦­RœÛl•ò“?é/ï\½lÙ²›O'vaùòå̘1Ãà$""Ò\]]™4iyyy¼ùæ›øùù‘ŸŸÏäÉ“éß¿?›7oÖf³Ùؼy3ýû÷gòäÉäççãççÇ›o¾I^^“&MÒ@GDÄ i«çv#[¥üäJ³gÏò‹_ü›ÍFaa!>>>7ŸR SRRB§N8yò$­Zµ28‘ÈOÓ2‘úSYYÉŠ+ˆåĉbµZõhŽƒÉÈÈÀd2Õ®¸íÔ©QQQL›6fÍšœNÄñ©ˆ½ûøã0`wÞy'ÙÙÙFÇ‘zÔ·o_rss9xð ×õ5?y ÇÛÛ›'žx‚ .˜˜X/!Å8ÉÉÉ”––2mÚ4 tDDš˜fÍš1sæL:Ä«¯¾JûöíÉÊÊbÔ¨Q 6Œ]»vQ®a×®] 6ŒQ£F‘••EûöíyõÕW9tè3gÎÔ@GD¤‰¸óÎ;éׯü1999FÇ‘z²ÿ~rss8pàutàC€ùóçãááÁ_þòJJJn*¤§¬¬Œ¿üå/¸ºº2{öl£ãˆˆˆAš7oÎìÙ³9räñññøøø™™Éˆ#øÍo~þ}ûŒŽ(ß³oß>~ó›ß0bÄ233ñññ!>>ž#GŽ0{ölš7ontDidÚ*ÅùÜèV)×êtîܙǜ’’þò—¿ÜX:1Ü_ÿúWN:EPPݺu3:Žˆˆ¬eË–ÌŸ?ŸcÇŽaµZiݺ5Û·ogðàÁŒ;–±É;pàcÇŽeðàÁlß¾Ö­[cµZ9zô(óçϧeË–FGƒL:///V®\IQQ‘Ñqä&•””ðÖ[oáååÅÔ©Sëôµ×µƒÞï~÷;ÜÜÜHLLäÂ… 7RŒsáÂâããqqq!22Òè8""bG¼¼¼ˆŠŠâرcDEEáååÅÖ­[0`&L //ÏèˆMN^^&L`À€lݺõG·Þz«ÑEDÄ`Ú*ŹÜÌV)×5ÔéÞ½;“&M¢¨¨ˆ?ÿùÏ7RŒ“˜˜È×_Í„ t4½ˆˆ\ÕåU ÇŽcÞ¼y´hÑ‚7Ò·o_‚ƒƒ)((0:¢Ó+(( 88˜¾}û²qãFZ´hÁ¼yó®XM%""r™¶Jq7»UÊuŸu‰««+/¿ü2_}õU/$Æ8yò$ñññ¸¹¹ñÒK/GDDìœ 9r„çŸwwwV­ZE¯^½˜>}:………FGt:………LŸ>^½z±jÕ*ÜÝÝyþùç9rä :}TDD®J[¥8‡›Ý*å'4ÿ¾§Ÿ~š7ÞxƒéÓ§×nâ#öí‰'ž`åÊ•<óÌ3,]ºÔè8"u¢#EEŒwüøqbccY±bUUUxxx0cÆ .\HÇŽŽçÐNž<É¢E‹X¾|9¸»»3mÚ4¢¢¢èܹ³ÑñDš,õq$‡âŽ;îàg?û………ÜrË-FG’:¸pá]»våôéÓ|üñÇ7ôdÍu¯Ô°Z­´jÕŠ+V°{÷î:_L×Î;IMM¥uëÖÄÄÄGDDPçÎY¶lùùù„„„PUUÅÒ¥KéÞ½;áááœ>}ÚèˆçôéÓ„‡‡Ó½{w–.]JUU!!!äçç³lÙ2 tDDäºi«ÇV[¥Ôi¥@RRáááôìÙ“œœZ´hqC–†UVVF@@‡æµ×^#,,ÌèH"u¦;e"öçßÿþ7f³™ 6`³Ùðòò",,ŒˆˆÚ´ict<»V\\LBBBífˆ...a±X¸ãŽ;ŒŽ'"ÿKýCÍ'Ÿ|B¿~ýðòòâóÏ?§}ûöFG’ëpòäIüýý)++ãàÁƒôêÕë†^§N+užþyÈ矮=Zì˜Édâðáà <˜çž{Îè8""â$î¸ãÖ­[GNN<ò.\àå—_ÆÏϋŹs猎hwÎ;‡ÅbÁÏÏ—_~™ .ðÈ#““úuë4Б›Ò§O¦OŸÎ¹sçX¸p¡Ñqä:ýþ÷¿§´´”§Ÿ~ú†:p+u<È]wÝEee%ï½÷#G޼áRÿÒÓÓ;v,Íš5#;;›Þ½{Iä†èN™ˆýËÊÊÂd2‘‘‘@›6mˆˆˆ ,, ///ƒÓ«´´”ääd(..`äÈ‘X­V N'"?FýCÑW_}EÏž=9þ<»víbذaFG’Ÿ°sçNî¿ÿ~¼½½)((à¶Ûn»áתóJ€¾}û²hÑ"l6!!!|ùå—7@ê׉'xâ‰'°Ùl$$$h #"" *00mÛ¶±{÷n†Nqq1‘‘‘tíÚ•¤¤$ÊÊÊŒŽØèÊÊÊHJJ¢k×®DFFR\\ÌðáÃÙ½{7Û¶mÓ@GDDê]ûöí±X,Øl6ž~úiÊËËŽ$?¢¬¬Œ™3gb³Ù°Z­75Ð\©ßN®ûÛß²eË~ýë_³cÇÜÜÜn*ŒÜœªª*FŒÁ‡~È£>ÊÆkï4ˆ8"Ý)q<Û·oÇd2±oß>:vìÈÂ… ™1c§kX,_¾œE‹qòäI „Õjå08ˆ\/õqTÕÕÕ 4ˆýû÷³`Á^~ùe£#ÉUÌ›7Å‹3xð`233qu½¡µ6µnx¨ßnø×¿Ž?ÎÂ… ‰½©0rs~÷»ß¯¯/ uëÖFG¹)*U"ŽkëÖ­DGGsàÀ|}}‰ŽŽ&$$wwwƒÓÕ¯ªª*RSS‰‰‰¡°°€þýûØ1cŒ '"u¦þ!ŽL[¥Ø·†Ø*å¦FBmÚ´aõêÕ4kÖŒ¸¸86oÞ|Ó䯬_¿ž?þñxxx°víZ tDDÄPcÆŒ!;;›õë×Ó«W/ ™>}:½zõbÕªUÔÔÔñ¦ÕÔÔ°jÕ*zõêÅôéÓ),,¤W¯^¬_¿žììl tDD¤Ñi«ûÕP[¥ÜÜ:`È!ÄÅÅQSSÃc=ÆŽ;ê#—ÔÁŽ;xüñDZÙlüñä®»î2:’ˆˆHí‘ݹ¹¹¤¦¦Ò½{w ¦oß¾lܸÑ!ï„Ûl66nÜHß¾} ¦  €îÝ»“ššJnn.AAAzüYDD 3wî\ÆŽË7ß|Ãc=FuuµÑ‘š¼ªª*{ì1Μ9ã>ÊóÏ?_o¯}S_}×sÏ=ÇÒ¥Kñööf÷îÝôéÓ§>^V®á“O>aذaœ={–^x¤¤$£#‰Ô-q.W{LiÀ€X,Fml¸ë”žžŽÙl&;;pîÇÊDš*õqÚ*ž4äV)õ6Ô©®®&$$„Õ«Wó‹_ü‚;wÒ­[·úxiù‡fÈ!|óÍ7“ššª;ƒâTTªDœ“#n(Ü”7€ijÔ?ÄYìÙ³‡#FP]]ͦM›xä‘GŒŽÔ$­_¿žÉ“'Ó¬Y3vïÞ]ïOÖÔÛP¾-i=ô;vì [·ndffrûí·××ËËw|õÕW 6ŒÃ‡sß}÷ñÞ{ï©TŠÓQ©qneee¤¤¤ÇéÓ§>|8±±± :ÔàtßÊÌÌ$**Š]»vЮ];"## ÅÓÓÓàt"ÒÔ?Ä™,^¼˜yóæÑ²eK¶lÙÂ}÷Ýgt¤&eÇŽ<ôÐCTTT””Ä /¼PïרסÀÙ³gyðÁùè£èÖ­øùùÕç%š¼Ã‡3räHŽ=ÊÝwßÍŽ;hÙ²¥Ñ±DêJ•HÓPZZJrr2 0räH¬V+†dÊÊÊÂd2‘‘‘|{8DDDaaaxyy’ID‡ú‡8m•bŒÆÚ*¥Þ‡:EEE<üðÃdeeѾ}{Þ{ï=úõëWß—i’²³³=z4_ý5wß}7ééé´mÛÖèX" B¥J¤i9{ö,‰‰‰$&&rîÜ9\\\;v,V«•€€€FÉ››‹ÉdbË–-Øl6ZµjExx8áááx{{7J1–ú‡8m•Òøs«”êÀ·wÝ&L˜Àûï¿O«V­xçw¸÷Þ{âRMÆ|À¸qã8þ<=ôëׯç–[n1:–HƒQ©išÎœ9CBBÉÉÉ\¸pWWW‚‚‚°X,øûû7È5óóó1›Ílذššn¹åˆˆˆÐÍ‘&FýCœ‘¶Ji<½UJƒ uàÛoœiÓ¦±jÕ*š7oÎ[o½EPPPC]Ω­Y³†'žx‚ŠŠ BBBX¾|9Íš53:–HƒR©iÚ¾þúkâââHII¡¼¼777‚ƒƒ1›Íõöh÷Ñ£G±X,¤¥¥Q]]M‹- %22ReW¤‰Rÿg¥­Rž[¥¸6ä‹{xxðÖ[o1gÎ.]ºÄ¤I“xñÅ©¬¬lÈË:•ÊÊJ^|ñE¦NJEEsçÎåÍ7ßÔ@GDDœÞí·ßNRR‡æÙgŸÅÍÍ•+WâïïOhh('Nœ¸á×>qâ¡¡¡øûû³råJÜÜÜxöÙg9|ø0III興ˆÓñöö&==ÀÀ@>Ì=÷ÜCNNŽÑ±œFvv6C‡­褧§7ÊÞ· ºRç»–,YÂܹs¹téƒ bíÚµtîܹ1.í°Ž;ÆäÉ“ÉÊÊ¢yóæüéOâ¹çž3:–H£Ñ2ù®ÂÂBbbbHMM¥ªªŠæÍ›3sæL"##éСÃu½Æ©S§ˆ‹‹cÙ²e\ºt wwwBBBˆŽŽÆ××·aÿ"âÔ?ÄÙi«”úgäV)6ÔØ¿?“&MâØ±c´iÓ†åË—óè£6ÖåÊ;ï¼Ã´iÓ())¡{÷î¬[·N›MK“£R%"WSPP€ÅbaÍš5ÔÔÔàééɬY³X°`>>>Wýš¢¢"âããY²d eee¸ºº2eÊÌf3=zôhä?ˆØ3õi ´UJý1z«”}üêûHNN'N¤¸¸˜ñãÇóüóÏsþüùÆŒa×JKK cüøñ”””0qâDöïß¯ŽˆˆÈÿêÑ£iiiû,_|ñžžžüéOâ™gž1:–ˆat§LD®Gvv6f³™ôôtZ·nÍóÏ?Àk¯½V;ä=z4‹…–UDìŸú‡45Ú*¥îìi«Æ:ðíÎÐ/¼ðï¾û.÷ß?K–,¡gÏžFE2ÄáÇ™?>›6m`Ĉ¤¤¤èî¡4y*U"R{öì!**Š;w^ññ{ï½—ØØX† bP2q$êÒi«”ëgo[¥4êãWß×­[7ÒÓÓÙ°a;wæƒ> €… röìY#£5Š’’.\H@@›6mÂÇLJ7Þxƒ;vh #""Rdgg“ŸŸÿƒßËÏÏ';;›ŠŠ ’‰ˆˆØ?m•rmöºUŠ¡CË&L˜À§Ÿ~Êüùó±Ùlüá K—.üá ´´ÔèxõîüùóÄÄÄÔþ«««™;w.‡bÆŒµwDDDä§UUUñúë¯Ó½{wfϞͩS§ dÛ¶mlÛ¶ÀÀ@N:ÅìÙ³éÞ½;¯¿þ:UUUFDZ;­Zµbݺu¼öÚk´hÑ‚äädzöìÉÚµkŽf¸÷Þ{Þ½{³dÉZ´hÁÒ¥KY·n­›ÜÝ IDATZµ2:š±_]Í‘#G°X,¬ZµŠêêjÚ´iÃ3Ï<ÃóÏ?OûöíŽwS¾üòKþüç?“’’BII îîîc6›éÒ¥‹ÑñD쎖?‹È©®®&-- ‹ÅÂÑ£GÀjµ2vìØ+þÿ±eËL&¹¹¹øùùa6› ÆÍÍͰ?ƒˆØ'õm•r™#l•bwCËòóóyùå—Y½z54oÞœ &ðÔSO1bćYÍb³ÙøàƒX¾|97n¬ý³<þøãÌŸ?ß®¾DìJ•ˆ|_MM 6lÀl6×>jåïïÅb!((W׫/B¾Ñ¯‘¦GýCäÿ¼ýöÛ¼øâ‹?~æÍ›Çüùóñöö6:Zƒ*))!!!ÄÄDÊÊÊðññáå—_fúôév7‹°Û¡Îe—W·¼ñÆœ9s€.]º0yòd&NœÈwÞip«ۿ?ëÖ­cýúõàããÃÌ™3bÕ‘HcP©‘ËêkÅÍõ®ð‘¦KýCäJ¥¥¥X­V©¬¬äg?ûóæÍcöìÙxyy¯^?žÄÄD)))ÁÃÃ矞¨¨(Z·nmt¼«²û¡Îe—.]âí·ßæ7ÞàŸÿügíÿd}}}5j£Fâ¾ûîãÖ[o5$ßùóçÙ±cGí3ü—9...Üÿý̘1ƒqãÆÑ¼ysCò‰8"•*ÈÈÈÀd2‘••@§NˆŠŠbÚ´i4kÖì†^³²²’+Vˉ' Äjµ2räÈzË."ŽGýCäê´UŠ}r˜¡Îw;vŒµkײ~ýz>þøãÚ»¹¹Ñ¯_?† ÂàÁƒ  G7\ø~Lee%Ÿþ9¹¹¹ìÛ·ÌÌLrss©®®®ýœ2iÒ$&Nœˆ¯¯o½^_¤©P©iÚvíÚETT™™™´oßžÈÈHBCCëí&É¥K—HII!..ޝ¾ú €¡C‡ËðáÃëå"âXÔ?D~š¶J±/9Ôù®ÂÂÂÚÕ1;vìøÁ‘kôìÙ???|}}éÒ¥ mÛ¶­ýuË-·àééyÅ×”••qáÂΜ9SûëØ±c=z”cÇŽQPPðƒcQo½õVî»ï¾ÚUCäˆÜ<•*‘¦iß¾}˜L&¶oß|ûørDDaaa´lÙ²A®yñâE’““IHH ¨¨€x«ÕÊ Aƒäš"bŸÔ?D®¶J±?Ôù®êêjrrrسg{÷î%77—‚‚*++ëõ:Íš5£gÏž0hÐ †J@@€NЩg*U"MËˆŽŽfëÖ­´nÝš¹sçò /4ÚãÕçÏŸçÕW_eñâÅ”””0fÌbbbèß¿£dc©ˆÔ¶J1–S u®¦¢¢‚Ï?ÿœ£GRXXȱcÇ®XsáÂÊÊÊ®øOOOn¹å–+VôtéÒ???ºtéB=ððð0èO$Òt¨T‰4 yyy˜Íf6mÚ„ÍfÃËË‹9sæ0wî\Ã6%,))añâÅ$%%QZZŠ‹‹ ãÆÃb±Ð»woC2‰HãPÿ¹qÚ*¥ñ9ýPGD—J•ˆs+((Àb±°fÍjjjðôôdÖ¬Y,X°£ãPTTD||UUU‘ššJLL ………ôïߟ˜˜ÆŒcl8;µuëV¢££9p྾¾DGG‚»»»ÁéDä»Ô?DÄ‘h¨#"vK¥JľÔÔÔ°fÍ, ôêÕ‹—^z‰ &Ôþ7+Wg³Ùxûí·y饗øôÓOèÑ£f³™)S¦àêêjpBõq,ꈈÝR©±6›M›6a6›ÉËË {÷îDGG3uêT #ꨦ¦†U«VáC‡èÝ»7‹…qãÆi8&b0õq$ꈈÝR©1^zz:f³™ììl@ Õ§«=Æ6`À, £G66œH¦þ!"ŽDC±[*U"ÆÙ¾};ÑÑÑìݻп éjN<˜˜˜m8-bõq$ꈈÝR©i|:ŠÛ8:^Ä>¨ˆˆ#ÑPGDì–J•HãÉÊÊÂd2‘‘‘@›6mˆˆˆ ,, ///ƒÓ5-¥¥¥$''“@qq1#GŽÄjµhp:ç§þ!"ŽDC±[*U" /77“ÉÄ–-[°Ùl´jÕŠððpÂÃÃñöö6:^“vöìYILLäܹs¸¸¸0vìX¬V+FÇqZê"âH4Ô»¥R%Òpòóó1›Ílذššn¹åˆˆˆ mÛ¶FÇ“ï8sæ $''sáÂ\]] Âb±àïïot<§£þ!"ŽDC±[*U"õïèÑ£X,ÒÒÒ¨®®¦E‹„††Éí·ßnt<ù _ý5qqq¤¤¤P^^Ž››ÁÁÁ˜ÍfüüüŒŽ'â4Ô?DÄ‘h¨#"vK¥J¤þœ8q‚ØØXV¬XAee%̘1ƒ… Ò±cG£ãIœ>>,X°€Y³fáééip:û¥þ!"ŽDC±[*U"?®¢¢‚””âââ8uê÷ÜsV«•{ï½×àtbOvî܉ÉdâÃ? C‡DFFЇ‡‡ÁéDìú‡ˆ8 uDÄn©T‰üPUU+V¬ 66–ãÇHLL £F28سmÛ¶MVV;w&**ŠiÓ¦áîînp:û¡þ!"ŽDC±[*U"ÿ§ººš´´4, G «ÕÊØ±ckÿ{ù)6›-[¶`2™ÈÍÍÀÏϳÙLpp0nnn'1žú‡ˆ8 uDÄn©T‰@MM 6lÀl6“ŸŸ€¿¿?‹…   \]] N(ŽHßW"?NýCD‰†:"b·Tª¤)ÓŠ i Z&òCê"âH4Ô»¥R%MUFF&“©vï“N:Õî}Ò¬Y3ƒÓ‰3ª¬¬¬Ý«éĉÀ·{5Y­VFŽip:‘Æ¥þ!"ŽDC±;çΣººš6mÚP\\Œ››­Zµ28™HÃÚµkQQQdffо}ûÚSŠš7onp:i .]ºT{ªÚW_}ÀСC‰eøðá§iXê"âˆ4Ô‘W^^Naa῾üòKŠŠŠ8sæ EEEqîܹëz½V­ZáããSû«mÛ¶üüç?Ç××___üüüøå/©7Áâ0öíÛ‡ÉdbûöíøøøAXX-[¶484E/^$99™„„ŠŠŠxà°Z­ 4Èàt"×GýCDš uD¤^>|˜œœ>ùä>ùärss9zôh–0·jÕ 77·+–?WWW_wépuu¥K—.ôíÛ—>}úЧOúõëG×®]ëügi( ::š­[·кukæÎË /¼À­·Þjp:8þ<¯¾ú*‹/¦¤¤€1cÆCÿþý N'òÔ?D¤©ÒPGDnØ¥K—ÈÎÎ&33“?ü½{÷òÍ7ßüàóZ´hQ{ëò¯Ÿÿüçµw¹.ßñºÞåÍçΫ½»vùnÛ—_~Y{îèÑ£|ñÅ\ºté_{Ûm·1dȆ ÆàÁƒ8p 7ýïB¤.òòò0›ÍlÚ´ ›Í†——sæÌaîܹ´nÝÚèx"?PRRÂâÅ‹IJJ¢´´Æ‡Åb¡wïÞFÇ“&FýCDäÿh¨#"uRXXÈ»ï¾Kzz:ÿüç?¹xñâ¿ß­[7úõëW{w* ??¿F?A¥¦¦†cÇŽqðàÁÚ»v9999räŠÏóòòâþûïgÔ¨QŒ5 __ßFÍ)MKAA‹…5kÖPSSƒ§§'³fÍbÁ‚øøøOäšŠŠŠˆgÉ’%”••áêêÊ”)S0›ÍôèÑÃèxâÄÔ?DD®NC¹¦œœÖ®]Ë;ï¼C~~~íÇ›7o΀:t(÷Üsƒæ¶Ûn30éµ}óÍ7ìÙ³‡Ý»w³wï^öïßOEEEíïûûû3~üx&MšDß¾} L*Τ°°˜˜RSS©ªªÂÃÃÐÐP"##éСƒÑñDêìÔ©SÄÅÅ‘’’BEEîîî„„„­7§RoÔ?DD®MC¹ªÏ>ûŒÕ«W³nÝ: j?îëëËÃ?ÌèÑ£1b„ÃoâZZZÊ|À¶mÛØ¶m………µ¿çïïÏĉ™:u*þþþÆ…‡uòäI-ZÄòåËkßøN›6¨¨(:wîlt<‘›vüøqbccY±bEíÀrÆŒ,\¸Ž;Oú‡ú‡ˆÔ†:"RëâÅ‹¬[·Ž×_={öÔ~¼_¿~Lž<™G}ÔéËE~~>7ndݺu]»Šáò#*ÁÁÁ˜Ífm–)NéÈ‘#X,ÒÒÒj-¼¼­]»vFÇ;§þ¡þ!"7NC!//¥K—’––ÆÙ³gøÕ¯~Åc=ƤI“šì> ùùù¬_¿žU«VÕ.ûööö&88˜gŸ}V›ƒÊ“@rrríf²AAAX,î¸ã£ã‰4¸ÿûߘÍf6lØP» xXX´iÓÆèxbgÔ?®NýCDêBC‘&Êf³ñþû˜Èûï¿Íf£eË–Lš4‰§Ÿ~š!C†Ñ®|øá‡¼ñƬ]»–²²2\\\xðÁ™7o<ð€ÑñÄ`çÎ#11‘ÄÄDΞ=‹‹‹ cÇŽÅjµ`t<‘F—››‹ÉdbË–-Øl6¼½½ '<<üºOç¤þQ7ê"r-êˆ41UUU¤¥¥±xñb>ùäüüü˜3gÿó?ÿƒ···Á íÛÙ³gY¹r%III=zøvyø‹/¾Èc=†»»»Á ¥1•––’œœÌ+¯¼Â™3g9r$V«•ÀÀ@ƒÓ‰/++ “ÉDFFmÚ´!""‚°°0¼¼¼ N'Iýãæ¨ˆÈÑPG¤‰¨©©!-- ‹ÅR{¬æ!CxñÅyôÑGqss38¡c©®®fãÆ¼òÊ+|ôÑGôèÑ“ÉÄÔ©Squu58¡4¤²²2RRRˆ‹‹ãôéÓ >œØØX†jp:û“™™ITT»ví ]»vDFFª}BœœúGýRÿ‘ïÓPGÄÉÙl6Ö¯_Ùl®}.{ĈÄÄÄ0lØ0ƒÓ9‡]»va2™Ø½{7½zõÂl6„‹‹‹Áé¤>UTT°|ùr-ZÄÉ“'4hV«UËàE®ÃöíÛ1™LìÛ·€Ž;²páBf̘‡‡‡Áé¤>©4<õ uDœÚ¾}û¯-ÏC† !&&†ûï¿ßàdÎéûoV Dbb"ƒ 28™Ü¬ªª*RSS‰‰‰©=v¶ÿþÄÄÄ0fÌcÉ8 ­[·Í€o«ŽŽŽ&$$D‘8õÆ¥þ!Ò´i¨#ℎ?Îï~÷;Ö¬YƒÍfãW¿ú <üðÃFGk¶nÝʼyóøüóÏqqq!88˜?þñtèÐÁèhRG555¬Y³‹ÅBAAðíЗ^z‰ &èN¨ÈM°Ùl¼ýöÛ¼ôÒK|úé§À·‘˜Íf¦L™¢ÇHú‡±Ô?Dš&ý´q"UUUÄÇÇãïïÏêÕ«ñööæµ×^ãàÁƒ*Th̘1äææòÊ+¯àííÍ[o½E=X¼x1UUUFÇ“ë`³Ùظq#}ûö%88˜‚‚ºwïNjj*¹¹¹ZÚ.R\\\ "77—ÔÔTºwïNAAÁÁÁôíÛ—7¢{ŽAýÃ>¨ˆ4MZ©#â$>þøcžzê)8€««+¡¡¡ÄÄÄàããct´&­¨¨“ÉIJe˨©©aàÀ¼ñÆôíÛ×èhò#ÒÓÓ1›Ídggz,D¤±\í1Ç`±X=z´±áäG©Ø'õ‘¦CCW^^ÎK/½T{¦W¯^¼þúë <Øèhò™™™<ýôÓäççãîîNDDf³™æÍ›Mþ×öíÛ‰ŽŽfïÞ½€6p1ÊÕ6$ÁÃÃÈÈH~ÿûßë ¨ºté‹-">>žŠŠ Xµj½zõ2:Z“¦£–EìSYY)))ÄÅÅqúôi†Nll,C‡58]Ó¦þáXÔ?Dœ›†:"Èf³ñꫯIyy9}úô!-->}úM®ÃÁƒ æÓO?¥E‹üñ$,,L{´4²¬¬,L&´iÓ†ˆˆÂÂÂðòò28ˆ\VZZJrr2 0räH¬V+§kZÔ?›ú‡ˆsÒPGÄÁ|óÍ7„„„‘‘‹‹ /¼ðqqq´hÑÂèhRåååÌŸ?Ÿäädl6£GæÍ7ߤmÛ¶FGsz¹¹¹˜L&¶lÙ‚Íf£U«V„‡‡Ž···ÑñDäGœ={–ÄÄD9wî...Œ;«ÕJ@@€Ññœžú‡sPÿq>êˆ8ýû÷3aÂŽ?N‡øÿïÿñàƒKn¶mÛxòÉ'ùúë¯éÒ¥ o¿ý6ýû÷7:–SÊÏÏÇl6³aÃjjj¸å–[ #""BeVÄœ9s†„„’““¹pá®®®a±Xð÷÷7:žSRÿp>ê"ÎCC±|ùrÂÂÂ(//ç׿þ5k×®¥}ûöFÇ’zpòäI&MšÄž={hÑ¢ýë_yâ‰'ŒŽå4Ž=ŠÅb!--êêjZ´hAhh(‘‘‘Ü~ûíFÇ‘ôõ×_GJJ åå帹¹ŒÙlÆÏÏÏèxNCýÃy©ˆ8W£ˆÈO«®®æ¹çžã©§ž¢¼¼œ^xíÛ·«P9‘Ž;òÏþ“Y³fQ^^ΓO>ÉìÙ³©®®6:šC;qâ¡¡¡øûû³råJÜÜÜxöÙg9tèIIIèˆ8¸Ûo¿¤¤$>̳Ï>‹››+W®ÄßߟÐÐPNœ8atD‡¦þáüÔ?DœƒVêˆØ±ÒÒR&MšÄ{g§§'o¼ñS§N5:–4 ÔÔTfΜIyy9cÆŒaõêÕÚ´·ŽN:E\\Ë–-ãÒ¥K¸»»Btt4¾¾¾FÇ‘RXXHLL ©©©TUUѼysfΜIdd$:t0:žCQÿhzÔ?D—†:"vêË/¿dôèÑäääЮ];6oÞÌÝwßmt,i{÷îå‘G¡¨¨ˆ°e˽!¹EEEÄÇdzdÉÊÊÊpuueÊ”)˜Ífzôèat<i$X,Ö¬YCMM žžžÌš5‹ àããct<»§þÑt©ˆ8& uDìпÿýo|ðAþóŸÿàïïOzzºöhbŽ9ÂC=Ä¡C‡èܹ3ï¿ÿ>={ö4:–]*))añâÅ$%%QZZŠ‹‹ ãÆÃb±Ð»wo£ã‰ˆAòòò0›ÍlÚ´ ›Í†——sæÌaîܹ´nÝÚèxvIýCÔ?D†:"v&''‡‘#Grúôi~ýë_³iÓ&Ú´ict,1@qq1<ò~ø!·ß~;ï¿ÿ¾ŽíýŽÒÒR’’’X¼x1%%%Œ=‹Å€ N'"ö";;³ÙLzz:­[·fîܹ̙3G—|‡ú‡\¦þ!âX4Ô±#}ô£Fâ¿ÿý/?ü06lÀÓÓÓèXb ‹/2~üx222hÓ¦ Û¶m#00ÐèX†*++cÉ’%ÄÇÇSTTÀý÷ßOLL C† 18ˆØ«={öÍ|€ ,`Ö¬YMþg­ú‡|Ÿú‡ˆãÐPGÄNìÞ½›Ñ£GsþüyÆÏêÕ«ñðð0:–ØŠŠ &OžÌ;ï¼C«V­HOOgèСFÇjt¤¤¤Ç©S§¸çž{°Z­Ü{ï½§G±sçNL&~ø!:t 22’ÐÐÐ&ùsWýC~Œú‡ˆcÐPGÄ|ôÑG<øàƒœ={–àà`Þ|óMÜÜÜŒŽ%v¤ªªŠ'Ÿ|’´´4¼½½ùÇ?þÑdî˜UUU±bÅ bcc9~ü8ÄÄÄ0jÔ(ƒÓ‰ˆ£Ú¶mÑÑÑdeeйsg¢¢¢˜6mîîî§kêr-M¹ˆ8 uD –››Ëˆ#øïÿËÔ©SY¹r¥ •\Uuu5!!!¬^½š¶mÛ²sçNúôéct¬S]]MZZ‹…£G€ÕjeìØ±¸¸¸œPDÍfcË–-˜L&rssðóóÃl6ìÔ?Õ?äz5µþ!âh4Ô1P~~>¿þõ¯ùæ›o7nëׯW¡’ŸTUUEPPÿûß¹ýöÛÙ½{7Ý»w7:V½ª©©aÆ ˜Ífòóóð÷÷Çb±„«««Á EÄÙ4µÿï¨H]5…þ!â¨4Ô1ÈþóÌþózè!Þyç=Ã.×¥¢¢‚±cÇòþûïÓµkWöîÝËm·Ýft¬›Ö”ˆ}h +Õ?äF9kÿqtêˆàâÅ‹Ü{ï½|ôÑG 2„üã´lÙÒèXâ@.^¼È}÷ÝÇ¿þõ/î¾ûnvìØáÐßC˜L¦Ú½-:uêT»·E³fÍ N'"MMeeeí^^'Nœ¾ÝËËjµ2räHƒÓÝ8õ¹YÎÖ?Dœ†:"¬ººšñãdzyófºuëÆž={t—CnÈ7ß|Ã!C8|ø0<ò7nt¸Õ,»ví"**ŠÌÌLÚ·o_{ MóæÍ N'"MÝ¥K—jOÝûꫯ:t(±±± >Üàtu£þ!õÅú‡ˆ3q®„E@xx8›7oæ¶Ûnã½÷ÞS¡’öÝï¡Í›73oÞ<£#]·}ûöñ›ßü†#F™™‰ñññ9r„Ù³gk #"v¡yóæÌž=›#GŽ™™™Œ1‚ßüæ7ìÛ·Ïèˆ×MýCê‹#÷g¤•:"håÊ•<ñÄ´lÙ’;vp÷ÝwIœÀîÝ»yਨ¨`õêÕL™2ÅèH?êÀDGG³uëVZ·nÍܹsyá…¸õÖ[ N'"òÓΟ?Ï«¯¾ÊâÅ‹)))`̘1ÄÄÄпƒÓý8õiŽÔ?Dœ™†:"äÀ :”‹/²bÅ ž|òI£#‰Y¾|9O=õ-[¶dß¾}vwÔh^^f³™M›6a³ÙðòòbΜ9Ì;—Ö­[OD¤NJJJX¼x1III”––âââ¸qã°X,ôîÝÛèxWPÿ†dïýC¤)ÐPG¤MEÔç IDAT3pà@Ž;ƳÏ>Ë_þò£#‰zê©§X¾|9ݺucÿþýx{{‰‚‚, kÖ¬¡¦¦OOOf͚ł ðññ1:žˆÈM)**">>ž%K–PVV†««+S¦LÁl6Ó£G£ã©H£°Çþ!Ò”h¨#ÒÀl6<ò[·neÈ!ìܹSG‡Jƒ¨¨¨`èСdee1nÜ86nÜhX–ÂÂBbbbHMM¥ªª BCC‰ŒŒ¤C‡†åi§N"..Ž””***pww'$$„èèh|}} ɤþ!Åžú‡HS¤¡ŽH[ºt)Ï=÷íÚµãã?¦cÇŽFG'vâÄ ú÷ïÏ™3gxã7˜1cF£^ÿäÉ“,Z´ˆåË—×¾±™6mQQQtîܹQ³ˆˆ4¶ãÇËŠ+jÚ3fÌ`áÂ…þó_ýC“ÑýC¤)ÓPG¤åçç3`ÀÊÊÊØ¼y3cÆŒ1:’4›6mbüøñxyy‘Ý(œ>}ºö.õåG‚ƒƒ1›ÍtíÚµÁ¯/"bOŽ9‚Åb!--­öÑÓ˫۵k×à×Wÿ#Ñ?DDGš‹4˜ŠŠ üq.^¼È3Ï<£B%fܸq̘1ƒÒÒRüq*++ìZÅÅÅDFFÒµkW’’’(//gâĉäåå±råJ tD¤IêÚµ++W®$//‰'R^^NRR]»v%22’âââ»¶ú‡¥1û‡ˆü­Ôi /½ô‹²³³iÙ²¥Ñ‘¤ ¹páwÞy'ÄÆÆ²páÂz}ýsçΑ˜˜Hbb"gÏžÅÅÅ…±cÇbµZ ¨×k‰ˆ8ºÜÜ\L&[¶lÁf³áííMxx8ááá´jÕª^¯¥þ!Fjèþ!"?¤¡ŽHøôÓO¹óÎ;©©©aïÞ½ 8ÐèHÒ}ôÑG 2„fÍš‘““CÏž=oú5KKKINNæ•W^áÌ™3Œ9«ÕJ``àM¿¾ˆˆ3ËÊÊÂd2‘‘‘@Û¶m™7oaaaxyyÝô뫈=hˆþ!"?N_‰Ô³ššž~úi***xñÅU¨Ä0wÝu³gϦ¼¼œ§Ÿ~š›™á—••]ñèÀ™3g>|8»wïfÛ¶m興\‡ÀÀ@¶mÛÆîÝ»>|8gΜ¹âÖ²²²~mõ±õÙ?DäÚ´RG¤ž½öÚkÌž=›nݺ‘››‹§§§Ñ‘¤ »pá}úôáØ±c,]º”gžy¦N__QQÁòåËY´h'Ož`РAX­Vxà†ˆ,"Òdlß¾“Éľ}ûèØ±# .dÆŒu>~\ýCìÉÍö¹~êˆÔ£Ó§OÓ½{wΟ?Ï|À½÷Þkt$Þÿ}FŽÉÏ~ö3:DÛ¶m¯ù5UUU¤¦¦Caa!ýû÷'&&F›nŠˆÔ³­[·Íðõõ%::šÜÝݯùõêbn¤ˆHÝéñ+‘z´páBÎ;ÇÿüÏÿ¨P‰ÝxðÁ™:u*ÿýï1›Í?ù¹555¬ZµŠ^½z1}út éÕ«ëׯ';;[‘0f̲³³Y¿~=½zõ¢°°éÓ§Ó«W/V­ZEMMÍO~½ú‡Ø£ºô¹qZ©#ROrrr8p žžž:tˆöíÛI¤Ö‰'ð÷÷§¢¢‚лwï+~ßf³±iÓ&Ìf3yyytïÞèèh¦NŠ««îˆˆ4†ËÃõ˜˜:@ïÞ½±X,Œ7—+>_ýCìÙµú‡ˆÜ<µt‘zNuu5¿ÿýïU¨ÄîtêÔ‰ PUUÅ‹/¾xÅ不§È„ ÈËËÃ××—¿ýío|öÙg<þøã興4"WWWüq>ûì3þö·¿áëëK^^&L 00ôôô+>_ýCìÙOõ©Z©#RÞ}÷]FM—.]øì³ÏhÑ¢…Ñ‘D~àâÅ‹ÜqÇ?~œŒŒ \]]‰ŽŽfïÞ½ÀÍmÐ)"" ãjÖ<˜˜˜***Ô?Äî}¿<øàƒFGq*êˆÜ$›ÍF`` ÙÙÙ¬Y³†É“'IäG½õÖ[„„„пrss©®®¦]»vDFFªÓRDDìTYY)))ÄÅÅqúôiÜÜܸãŽ;ÈËËSÿ»w¹ 4¨öf’ˆÔ uDnÒ;ï¼Ã¸qã ''çϺ‹Ø“ššøôÓO;v,C† !,, ///££‰ˆÈu(--%99™ÌÌLÒÓÓÕ?Ä!|·lÙ²E/ˆÔ# uDnBMMM튇wÞy‡ßþö·FG¹¦õë×3iÒ$î¼óNöï߯7""FýC‘ú‡HÃÐî—"7aË–-äææ2pà@yä£ãˆ\—   úõëÇÇÌ–-[ŒŽ#""u¤þ!ŽHýC¤ah¨#râãã°X,ºÛ ÃÅÅ…èèh N#""u¥þ!ŽHýC¤aèñ+‘´oß>̯~õ+òòòTªÄ¡ÔÔÔЧO>ûì3²²²8p Ñ‘DDä:¨ˆ#Sÿ©Z©#rƒ.ßa˜7ož •8WWWæÌ™@BB‚ÁiDDäz©ˆ#Sÿ©W¬Ô9zô¨‘YÄAüâ¿ÀÃÃÃè†*,,¤{÷îÜvÛm6ùâ˜ÊÊÊèÒ¥ gΜáÈ‘#tîÜÙèH""òÔ?Ĩ\?½?—ëáþÝèÚµ«Q9ÄäææÒ§O£cjÙ²eTUUñÜsÏ©P‰ÃòôôdÖ¬YDGG³lÙ2bccŽ$""?AýCœúÇõÓûs¹W¬Ô¹¼„ÓÏÏϰ@b¿.OŠ›úP§ªªŠN:QTTĉ'hß¾½Ñ‘DnØÉ“'ñõõåöÛo§°°ww÷k‘ˆˆ4:õq&ê×GïÏå§\~~ÕÿzŽ9Ò¨aÄ1ðÉ'ŸÃp[·n嫯¾âÑGU¡‡×±cG~øa6oÞ̻ᆱ£qEDì”ú‡8õºÑûs¹šËïϵQ²H½þúë<ýôÓ'©—¿—/o‹ˆˆýQÿg£þ!R?®úø•N9—«¹< lÊ_ýç?ÿá—¿ü%?ÿùÏùâ‹/puÕ\T_UU¿üå/9}ú4_|ñ?ÿùÏŽ$""ß¡þ!ÎHýãÚôþ\~ŠVêˆÜ€õë×SSSÃĉU¨Äi¸»»3qâDªªªX¿~½ÑqDDä{Ô?Ä©ˆÔýT©ƒµk×0yòdƒ“ˆÔ¯I“&°nÝ:ƒ“ˆˆÈ÷©ˆ³Rÿ¹yêˆ\§ÂÂB>úè#|}}¹ë®»ŒŽ#R¯L§NØ»w/ÿŸ½;‹ªìß~Œ(22P(E‚¹P¢âRä’!jŠY¦Rnoæ’¦f™{o¹Û›fji½fš¯äЂл) ˆ²( Ïï31Ê3g–ûs]s]5çpžïà÷ósžsëÖ-©Ë!"¢ÿÇüA¦Œùƒ¨úØÔ!ª àà`!0hÐ Íõ­D¦B&“aРABðh""ÂüA¦Œùƒ¨úØÔ!ª ;wúöí+q%DºÑ¯_?@XX˜Ä•‘ó™:æ¢êaS‡¨ÒÓÓqêÔ)888àÕW_•º"ðöö†Nž<‰û÷ïK]‘Ùcþ sÀüAT=lêUÀPXXˆ=zÀÒÒRêrˆtB.—£GP©T8pà€Ôå™=æ2ÌDÕæQìÛ·гgO‰+!Ò­=zC‘`þ sÁüATulê•C}ûöA&“1T‘ÉëÕ«`ïÞ½WBDdÞ˜?Èœ0U›:Då¸zõ*îÞ½‹¦M›âÙgŸ•º"rqqAãÆqûöm\¿~]êrˆˆÌ󙿢ªcS‡¨'NœtèÐAâJˆô£cÇŽ€ãÇK\ ‘ùbþ sÃüAT5lê•Cý‡Eý‡†ÈÔ1TIùƒÌ óQÕ°©CT†*27 UDDÒcþ sÃüAT5lê•áþýû¸rå ìììЬY3©Ë!Ò‹—^z vvv¸xñ"îß¿/u9DDf‡ùƒÌóQÕ°©CT†3gÎ@Ö­[C&“I]‘^Èd2xzzB¸¸8©Ë!"2;ÌdŽ˜?ˆª†M¢2¨ÿ xzzJ\ ‘~©ßó UDDúÇüAæŠùƒ¨òØÔ!*C™+†*""é0¹bþ ª<6uˆÊÀPEæŠ¡ŠˆH:Ìd®˜?ˆ*M¢2\¼xÀã…ۈ̉‡‡àÒ¥KWBDd~˜?È\1UžI6u¢££!“ÉðÊ+¯@ñÔó*• ½{÷ÆØ±cŸúZ!^|ñE¬\¹R³¿B¡ÐzŒ=ºÔ±oß¾ ???888hÆ"㔑‘û÷ï£~ýú°³³“º"½R*•¨W¯ÒÒÒ-u9DDfƒùƒÌó‡i’j~žŸŸ.]ºÀÙÙ …vvvðõõEdd¤®^ª$L²©£mÛ¶•¸müøñ BNNŽÖóHJJÂÈ‘#5Ïeff"''Góغuk©cZXX _¿~X³fM¼’NBB Q£F’ÖA$õ{_ý³@DDºÇüAæŽùÃté{~.—˱nÝ:$&&"''ÉÉÉèØ±#úôéSc¯É˜tSgÁ‚˜3g>|øÔ¶·Þz õêÕÃÿû_­ç7mÚ„~ýúA©TViÌ `üøñhÞ¼y•¾ž C™;wwwÀ7$®„ˆÈ|0¹cþ0]úžŸ[ZZÂÓÓµk×Èd2ÈårÍ{ÌT˜tSgìØ±pppÀòåËŸÚ&—Ë1fÌlÚ´Ió\FFÂÂÂ0~üx}–I*>>àææ&q%DÒà'eDDúÇüAæŽùÃtI5?2dêÕ«DFFâðáÃÕ:ž¡1馎¥¥%V®\‰/¿üIIIOm;v,¢¢¢pþüyÀöíÛáââ­ýaoo¯y¬[·N/õ“´’““ 6”¸"i¸¸¸x¼Véó™;æÓ%Õü|ÇŽÈÎÎÆ•+WpïÞ=̘1£æ^”0馸úúÂ×׳gÏ~j›»»;|}}5ÝÀï¾ûãÆƒL&ÓÚïÞ½{ÈÌÌÔ<&Ož¬—ÚIZééé‰+ѽšZ¼LmæÌ™Édøý÷ßË»*‹‹¿ûî»øæ›oô^û¤I“´ö5uê÷¾úgˆˆtùC?ùcúôéððð€ 0tèP¤¤¤”ù5ÌúÁüaÚ¤œŸ7mÚ‹/ÆæÍ›«ÿB ˆÉ7uàË/¿Dpp0þüóϧ¶bûöí8vì.\¸Pæ­È¼˜S¨R«‰ÅË=z„ï¿ÿ­ZµÒ:}²4•]\<..‡¸qãô^ûܹs±dÉdeeU¨Vc§¾v9--MâJˆˆÌó‡6]å…B¡9îÕ«W‘——ÿR÷gþÐæÓ'åü¼°°µjÕªÑcJÍ,š:M›6Åĉñé§Ÿ>µíwÞ……Fމ>}úÀÙÙ¹ÚãåååáÑ£GßF-//¯ÚÇ$ýûûï¿Ï<óŒÄ•èOM,^ö믿Bo¿ý¿ýö[¹ŸzUvqñ5kÖ r¹\ïµ7lد¼òJ©áÍÔ8::xüiéó‡6]å ÀÓÓ2™ ŽŽŽ˜6mŽ?^êþÌúÃüaúô5??{ö,ÂÃÃ5sóË—/cÞ¼y¨ò1 ‘Y4uàÓO?Õ:5RÍÊÊ £FB||üSw5{{{( Í£GeŽemmŽ;xü)€µµuõ_éúÓªÞ ÍÕÄâe7n„¿¿?Ú·o¦M›–z‹ÁªB`×®]èÞ½»dµwëÖ ¿þúkͼ ÇPED¤ÌÚô•?ÂÃÃѶmÛ·1èó‡yÐÇü¼  ³gφR©„B¡€ŸŸzõê…Õ«W×èk‘œ(€xâ)" OOO@ìܹSDGG‹ØØXqýúuÍãÞ½{"==]¤§§‹ÜÜ\©Ë­6WWW@$&&J]ŠÎEEE ¢  @„‡‡‹ºuëŠ[·ni=/„ׯ_2™LÄÅÅ !„X³fhܸ±(**ÒÚ)„bÉ’%ZÛ+ZCinÞ¼)ˆ»wïJVû¾}û„R©,ÿ›jáêê*u)DDfƒùC¿ùC!vîÜ) …ˆŠŠ*q;ó‡~™rþÈÏÏ×Ì—ÒÓÓÅ74s© .ˆèèh-¢¢¢ÄÁƒ9?§2©ççÚçUÀÌ™3qíÚµ ï/—ËQ¯^=ÍÿÛÙÙÁÂâñIbuëÖEíÚµKÜÏÞÞ^³(VñýjÕª…BQâ~666°²²*q¿úõëkþ»ø~VVV°±±yj¿hö5'Å/›:uªÖ¶â‹—­Zµê©Å˾ûî;¸»»ÃÛÛ€¹sç"""]»v­vm[[[Éj·µµÕÔaêêÔ©¼„”ˆH˜?ô›?BBB0nÜ8„……¡]»v%îÃü¡_O朜x|æEñ5Šîß¿¢¢"Íþ¹¹¹šmÅ¿_>D~~>€Çkª_Ÿ(;;[s£ŽŠ_ÌÌLÍ~<Ð\âSÖñ‰tMª´ÆÃÖÖ*• ÙÙÙšç‹ÿÒ+þ‹S¥RiýR5–?Hê?ÜO^;m¾üòKxzz¢C‡Om Ä„ 0pà@­ÅËT*¶lÙ‚´´4Íi³jß~ûm4uÔ ·¬¬,Í|}מ••¥Õ 4eêFªúg™ˆˆtO=ùcþЦ‹ü±eËL›6 »víBçÎKÝùC¿Šç'ïzdìžüйø‡ÝuêÔÑZ¶£~ýú×{d|Ìï¯UÛ_|OOÏ ïÿdó'33SsýdñæÏ“ñâûï~?¹_ñ&Qñý=z¤ù´ëÉýŠwü‹ïW¼ë®®ËÔVG¯ˆò/›4iÒS‹—íÞ½ÿý7þüóO­Å÷íۇɓ'#--­Ôõž\\\¥R•šž{î9899áâÅ‹¥. ©ëÚ/^¼//¯Ç65ê EAAnܸ!q5DDæAý÷ùC[Mç5kÖ`Á‚Ø¿?^yå•2ëbþÐ/uþP78 …æçáÉ3ûmmmaii àq3¨nݺšmÅ›`ÅÏú·°°€f[ñã—Õt©èñ---µÎê*~üÊ2µ¦éHñk²Àkö¨ êköÎ;'u)zaN?%­g“‘‘!”Je‰ëÜ̘1C{öìÑ<ççç'üÔ± „«««X±bE©ã«¿×Å¥;v¬˜;w®dµ÷îÝ[¬]»¶ÔúLMIÿ6|ðÁ|èþa¤Ì„\.666Z•JUâþÌúeN?eá÷Ê¢žŸË„øgÉiu'P”° 5QË–-‡sçÎUêLcÅŸÃtöìY¼ýöÛ¸~ýºÞOM¿sç<==qãÆR¯«75êŸwww‰+!"2ê3#™? ó‡~1‡?Æï•E=?çåWD¥¨W¯²³³‘­uš'I«U«VèÚµ+6mÚ„÷ß_¯c/Z´sæÌ1›@¥^äÏÖÖׯ_—¸""ó`kkËüa€˜?ô§xþ ¢òYès°Þ½{cìØ±O=/„À‹/¾ˆ•+W"::2™ ¯¼òŠVGRý|i+‡ß¾}~~~ppp(q¿â÷±W(°²²ÒúE1}útxxxÀÆÆ:t(RRRJ}-åßÿ=<<^u÷§ÊãÈÜ=|ø´$""Ýbþ sÇü¡?œŸ—õí Ÿì\™‹û÷ï€Öm?‰ˆH·˜?ÈÜ1èççeÏχ ‚zõêÁÆÆ‘‘‘8|øp•Æ×5½6u€Ço†¨¨(œ?°}ûv¸¸¸ÀÇÇGk?KKK¬\¹_~ù%’’’jl|•J…-[¶ 00°Ô}~ýõW¬X±ëׯ¯Òééé€k×®áâÅ‹¸|ù2’““ñÞ{ïUéx$ '''@jjªÄ•IãÞ½{GGG‰+!"2Ìdî˜?ô‹óóÒíØ±ÙÙÙ¸rå îÝ»‡3fTi|]Ó{SÇÝݾ¾¾šnàwß}‡qãÆ•¸–¯¯/|}}1{öìÿ·ß~CNNŽÖõzÅ…„„`ôèÑ C»víª4†ú–Å‹ÃÚÚŽŽŽ˜3gvïÞ]åºIÿ”J% --MâJjNy x]»v ¾¾¾P(pqqÁÒ¥Kµ¶ë{Á²#GŽÀÛÛ666°µµ…ŸŸþúë¯jW™ý>|ˆQ£FÁÞÞÏ<ó >ùäͶª, flÔ$®„ˆÈ|00”•?ŠS©Tðòò2¹›¦0èççåkÚ´)/^ŒÍ›7Wi|]Ó{S±}ûv;v .\ÀèÑ£KÝ÷Ë/¿Dpp0þüóÏ{ãÆ:t¨æ¶¸-[¶`ܸqصkºuëVå1ÜÜÜ`kk«õƒ “É´V 'çþC¢þÃb ÊZÀ«¨¨}úô‡‡ÒÒÒ‡ððpüðÚ}ô¹`YVVÞzë-ôë×™™™HJJ‚““Xåñ*»ÿÌ™3‘””„[·n!::Û¶m÷ß~  j ¨õ„B=Á ""Ýcþ`þ(+·jÕ*ØÛÛWhLcÂü¡œŸ—¯°°µjÕªr :U|¡èx!&µüü|áää$ÜÜÜD¿~ý´¶•´°ÐÔ©S…££c¹ åææŠãÇ "''GäææjmOLL"**ꩯ]½zµppp§Nªðë(k¼©S§Š &ˆ¼¼<‘žž.|}}Å Aƒª|¼šØ¿ºÌm¡ä?þXK–,‘º”WÒÏÙåË—qïÞ=Ís;v,õ8º\°ìÂ… š÷¶Ú±cÇ„\.¯òx•Ù¿  @( ¡y¾ÞÞÞ%£2 ¨‹…  bÞ¼yR—BDd6˜?˜?*’?D³fÍDDD„ÞeÕæp~.ÍüüÌ™3âàÁƒ"??_!Ä¥K—D›6mÄûï¿_åñuA’…’Õ¬¬¬0jÔ(ÄÇÇcܸqåîÿé§ŸV¨‹fmmŽ;xÜÑ·¶¶ÖÚþÝwß¡U«V%ž¶õÁ ++ ]»v…B¡Ð< ±±±Zÿ_ÞxË–-Caa!\\\ШQ#8::bÆ ší•=^e÷§êsqqðøYsRügMsçΕº¯.,kÚ´)Ú´iƒÕ«W#??øÏþƒ¡C‡Vi¼ÊŠGNNŽÖ­[·n­¹ÞXÍXP« õ{_ý³@DDºÇüÁüQ‘ü1yòd,\¸°Ä³Œó‡þq~®}¼‚‚Ìž=J¥ …~~~èÕ«V¯^]âþ}½:S¼Ó=uÉ8™Û™:»wïDïÞ½¥.¥Æ•Ôq/,,Mš4&L>©©©ÂÇÇGÈd2QTTôÔ1vîÜ) E‰õŠŒ+ˆÀÀ@Íxo¼ñ†VÇ<11QxxxKKK@tëÖ­Bïšø¤,&&FÐtè…âÔ©S@‰ß+W®ˆvíÚ‰‰'VhLcðæ›o bß¾}R—BDd6˜?˜?ÊËÁÁÁâÍ7߬Ҙƀù㜟SY$=S‡È¸»»nܸ!q%úaaa]»váêÕ«hذ!|||ЩS'Ô¯_ÿ©…Òô±`Ù£Gн{w ''éééxöÙg˼†¾&©ë{ðàæ¹œœ(ŠŽ3ôÔª"!!ШQ#Ië "2'ÌÌ@éù#;;³fͺuëôR˜?ˆ*‡M¢R¨ÿ¨ÿ°˜ƒæÍ›#<<ˆ‹‹ƒB¡À믿®µ¾,»zõ*®^½Š©S§¢N:¨_¿>&L˜ ·»È¹¹¹A¡Phþ}æÌ´hÑ¢Ô¯1èÔ*I„„Èd2†*""=bþ`þ(+\¹r7oÞ„··75ß ggg„……é¥F]bþ ª<6uˆJamm  77w¿'28 IDATîÜ‘ºœ“——‡Gx|[î¼¼<ͶK—.i>ŠˆˆÀÊ•+1oÞ<Íö5kÖ`ƌؿ?:wî\­ñ,--ñî»ïâ“O>Ñ\³¾lÙ2ÍÝ£\]]accƒuëÖ¡  ÙÙÙØ°a<==«üú*³¿\.Lj#°hÑ"dggãÖ­[X³f Þ}÷]ÀÙ³g®ùÚË—/cÞ¼y¨Ð÷ÅÐ%''#?? 6DíÚµ¥.‡ˆÈl00”•?Zµj…øøxœ9sgΜÁO?ý8}ú4zöìY¡ï!cþ ª‚â×d×ìQÌmM!„èÚµ« þ÷¿ÿI]JQÿœ¨-]ºT(•J¡P(DûöíÅ¡C‡žúZ¹\.lll´*•Jñø:ðâÿ_Þxyyy"00P(•Jakk+üýýEzzºfûDÛ¶m…µµµ°±±ݺu—.]Òl¯ìx•ÝÿÁƒbĈÂÖÖV8::jÝ…!**J´mÛV( acc#ÜÜÜÄܹsµ®7fD÷îÝ¥.…ˆÈì00”–?ždjkê0hãüœÊ¢žŸË„øgÙjõiˆ¢÷k'óѲeKÄÅÅáܹså~Za*¦NŠÕ«WcÕªUøàƒ¤.‡HoV¬XéÓ§cÚ´iøê«¯¤.‡ˆÈ¬0¹bþÐÆù9•E=?çåWDehÙ²%”y[M"SfÓÀ%"2$Ìd®˜?ˆ*M¢2´jÕ ÀãõSˆÌ‰z"¡žX‘þ0¹bþ ª<6uˆÊðÒK/A.—ãâÅ‹P©TR—C¤=Â… `ee…—_~YêrˆˆÌó™#梪aS‡¨ ÖÖÖðòòBnn.bcc¥.‡H/bbbŸŸ6mÚðÎDD`þ sÄüAT5lê•£C‡€'NH\ ‘~?~бcG‰+!"2_Ìdn˜?ˆª†M¢rtêÔ pìØ1‰+!Ò†*""é1¹aþ ª6uˆÊÁOÊÈܨ'ê é󙿢ªaS‡¨ 6DÓ¦M‘œœŒ«W¯J]‘N]¸p©©©xùå—áää$u9DDf‹ùƒÌ óQÕ±©CT={öìÛ·OâJˆtKýW¿ç‰ˆH:Ìd.˜?ˆªN^Ò“7nÜÐwdÌù}áçç‡5kÖà÷ßÇ¿þõ/©Ë!Ò†*""ÃÁüAæ‚ù£læ<£Ò©ß2!„P?)“É$+ˆŒÇ¹sçàéé)uz•›› ¥R HKKƒµµµÄÕ¼œœ8::¢V­Z¸wïo'JD$1æ2Ì¥ãüœ*BëLwww©ê #bee%u zgmm®]»bÏž=8tèz÷î-uID5.""ùùùèÑ£‘`þ sÀüQ:ÎÏ©"´š:ׯ_—ª"ƒ×¿ìÙ³¡¡¡ Ud’BCC<~¯‘a`þ S§ÎÏ=÷8ÔªUK⪠ççTZ—_Qé222ðì³ÏB¡PàîÝ»ücC&%??ÎÎÎÈÍÍEJJ ìíí¥.‰ˆˆÀüA¦­xþpppÀ;wààà€wÞy @·nÝxöQ9x÷+¢ ª_¿>ºwŒ âââpùòe,Y²mÚ´AVV¶oߎ~ýúÁÉÉ þþþƃ$®œÈpðò+¢JÈÊÊBƒ —ËqçÎÔ­[Wê’ˆª-'' 6„ÉÉɨW¯žÔ%Q1ÌdŠ*’?nܸ„„„àÏ?ÿ„zêjmmž={bàÀxë­·`kk«ïò‰ ÏÔ!ª[[[ 4YYY–º¢„ììl <˜ ""ÄüA¦¨"ùÃÝÝ3gÎÄÉ“'‘€•+W¢sçÎÈÏÏGXXðÌ3Ïà­·ÞÂ÷ߌŒ =¿ "éñL¢J:~ü8:uꄎ;âØ±cR—CTm:t@dd$Nœ8×^{Mêrˆˆ¨Ìdjª“?’““†ÐÐP>|………€ZµjÁÇÇD¿~ýàè訋҉ ›:DUðòË/ãâÅ‹¸pá^zé%©Ë!ª²óçÏÃÓÓ-Z´@\\œÔåQ˜?ÈTÔdþHMMEXXBBB‚‚€¥¥%ºté‚þýûcÀ€pvv®‰Ò‰ /¿"ª‚qãÆ¾ùæ‰+!ªõ{xìØ±WBDDåaþ SQ“ùÃÉÉ Ø¿?RRR°yófôîÝr¹‡ÂäÉ“áââ‚×_«V­BRRRµÇ$2$„††jÝ]©Tâí·ßÆ Aƒàëë[å›Ìp~Neá™:DÅá矆——Þyçœà/_2EEE˜2e ŠŠŠ0þ|³ TDDæ€ùƒ •9çÌ›7111øë¯¿°lÙ2´oß999øùçŸ1hÐ 899aàÀøé§Ÿ-uÉdÄx¦U˜)œ©“““ƒ7âË/¿Ä;w<þ¥;gÎ 2r¹¼ZÇÏËËÃK/½„øøxlܸãǯ‰²‰ªå믿ƤI“ФIœ?¾Ê×u‘abþ CÄüñ´›7oâ—_~AHHNž<‰¢¢"@:uУGôïßo¿ý6êׯ€ós*›z~ΦU˜17u233±nÝ:¬Zµ iii///Ì;ýúõƒ…EÍ´¶oß>ôêÕ ööö¸pá6lXcÇ&ª¬¤¤$¼üòËÈÎÎÆЭ[7©K"""`þ CÂüQ¾ääd„„„ $$ÇŽCaa!ÀÊÊ ¾¾¾èß¿¿¦AËù9•„—_‘YHMMÅܹsñ /à“O>AZZ:tè€ßÿ§OŸÆ€j´¡={öD@@2331eÊ”=6QeMš4 YYY5j‘ cþ CÂüQ¾† bÊ”)øã?œœŒo¾ùݺuCQQöîÝ‹ÀÀ@©K$#Á3u¨ÂŒéLÛ·oãË/¿ÄÆñðáC@·nÝ0wî\¼ñÆ:ÿÞ½{ðððÀ½{÷„Aƒé|L¢'íØ±C‡Å3Ï<ƒK—.ÁÁÁAê’ˆˆH‡˜?È0TOZZvíÚ…³gÏbõêÕ8?§’ñL2Iñññxï½÷àîîŽU«V!77o¿ý6"##qðàA½4tÀÑÑ+V¬¼ÿþû¸}û¶^Æ%RKLLÄĉkÖ¬a ""2Ì$5æêS*•3f V­Z%u)d$ØÔ!“péÒ%Œ1M›6ÅÆ¡R©àïïØØXüúë¯ðööÖ{M#FŒÀÀ‘žžŽQ£FiB#ÒµÂÂBŒ5 ð÷÷‡¿¿¿Ô%‘ž0T˜?ˆ¤Á¦µØØX 8-Z´ÀöíÛ!“É0zôh\¼x;vì@«V­$­oÆ pqqÁÿþ÷?|õÕW’ÖBæcùòå8|ø0^xá|óÍ7R—CDDzÆüAR`þ ’›:d”Nœ8Þ½{£mÛ¶ ••&Nœˆ«W¯bË–-hÖ¬™Ô%ðÃ?ÀÂÂóæÍéS§¤.‰LÜñãÇñÙgŸÁÒÒ?üðìíí¥.‰ˆˆôŒùƒôùƒH:lêQ ‡:vìˆßÿ666˜6mnܸõë×£Q£FR—ø”®]»bÆŒxôèˆÔÔT©K"•’’‚Áƒ£  C‡EçÎ¥.‰ˆˆ$ŸŸ°°0tèÐùƒt®xþøøãñúë¯K]‘YaS‡ ž»víÂk¯½†îÝ»ã?þ€½½=æÍ›‡„„|õÕWhРÔe–iÉ’%èÒ¥ ’’’àïïÂÂB©K"SPP€Áƒ#99­[·Æ?þˆwÞy™™™R—FDDztýúutêÔ ëÖ­CLL ^{í5æÒ™âù£{÷îøì³Ï¤.‰Èì°©C«¨¨?ÿü3¼¼¼ðÎ;ïàäÉ“prrÂâÅ‹‘˜˜ˆ… B©TJ]f…XZZ"((Ï=÷"""ðÑGI]™˜>úG…««+fÍšüöÛohÓ¦ ¢££¥.ˆˆô 88Xó{ßÍÍ ‡ÆÎ;™?HgŠçü–––R—DdvØÔ!ƒSPP€­[·â¥—^Â!CpöìY¸¸¸`åÊ•HHHÀœ9s`kk+u™•öÌ3Ï 88VVVX±b6oÞ,uId"þóŸÿ`ÕªU¨]»6BBB0dÈœ>}íÚµC||<:uꄯ¿þZê2‰ˆHGòóó1eÊ <YYY4hbbbЮ];æÒ™'󇓓“Ô%™%6uÈ`äååá믿FÓ¦M1fÌ\¹rnnnذanܸ©S§¢nݺR—Y-ÞÞÞX·n`„ 8tèÄ‘±Û¿?¦L™™L†ï¾ûíÚµ¼ð 8vì&OžŒüü|Lš4 ÇGNNŽÄQMR7ï×­[‡ÚµkcíÚµ ÒZ¨–ùƒjZiùƒˆôM’\NNV¬XwwwLš4 ðððÀ¶mÛpõêUÂÊÊJê2kÌøñã1{ölwîÜ——~ùåœ?LJ\.—ºLX²d pÿþ}øùù!))Iê’ÈÈ$&&ÂÏÏ÷ïßG@@–,YRê¾þþþˆŠŠB‹-pùòe¼öÚkøñÇõX-Õ¤üü|üë_ÿBÿþý‘™™‰¾}ûj.·* óUWeòé›:¤w©©©˜;w.^xá|òÉ'HKKC‡°gÏœ>} €……i¿5e26oÞŒ®]»")) >>>¸{÷®Ôe‘‘HNNF·nÝ””„®]»bóæÍÉde~MóæÍqêÔ)Œ5 999>|8Þ{ï=äååé©j""ª êË­Ö®]«Y''44Tër«Ò0PuT%‘î™äÌ9::2™ ¯¼ò „O=¯R©Ð»woŒ;ö©¯BàÅ_ÄÊ•+5û+ ­ÇèÑ£K{úôéððð€ 0tèP¤¤¤èâeÛ·oãÃ?D£F°dÉdee¡[·nˆˆˆÀñãÇáççgV¬¬¬Š6mÚ௿þB÷îÝqïÞ=©Ë"—ššŠ7ß|ýõÚ·oÐÐÐ _žX·n]lݺ6l@:u°qãFtìØñññ:®šˆˆjBI—[}øá‡•ÊOÌTÕÉDRÎÏÕT*¼¼¼4㙓lê¨ÅÇÇcÛ¶m%n?~<‚‚‚žZ44""III9r¤æ¹ÌÌLäääh[·n-uL…B¡9îÕ«W‘——ÿy=Æ*>>ï½÷7nŒU«V!77o¿ý6"##qðàA¼ñÆR—(;;;ìÛ·-Z´@\\œætV¢’Ü¿½zõÂùóçáéé‰ßÿvvv•>N`` "##ѤIÄÄÄ M›6عs§*&"¢šPPP€iÓ¦Uúr«Ò0PeÔTþ ’b~®¶jÕª Ñh”D1ÄO¥¨¨(@¬_¿^¸¸¸ˆh=_PP Dƒ ÄÆµ¾vèСbÈ!Oí_UGŽ–––U1ÄÓÓSçΫÐþ/^Çr¹\Âßß_œ9sFÇ•Ÿäädñâ‹/ ¢mÛ¶âÞ½{R—D&55U´iÓF/¾ø¢¸sçNµ™™™)úöí+™L&¦OŸ^­ßwDDTóâãã…··· jÕª%V¬X!ŠŠŠjäØÌT]äª8ÎÏkf~ž š5k&"""ª=¿7$êù¹IŸ©3vìX888`ùòåOm“Ëå3f 6mÚ¤y.##aaa?~|Õ޶mÛÖØñŒAll,ˆ-Z`ûöíÉd=z4.^¼ˆ;v U«VR—hp4h€ÿýïhÒ¤ NŸ>.]ºð²=Ò¸sçºt邘˜4kÖ ‡‚³³sµkgg‡ÐÐP¬X±r¹_}õºvíŠÛ·o×@ÕDDT];wî„——Nž< WWW9r¤Ò—[•…ùƒÊ¢«üAæKªùùäÉ“±páB(ŠjÇ`ïôÀÄ:"<<\Ô­[Wܺuë©ÎÞõë×…L&qqqB!Ö¬Y#7n¬ùôC½¿ÖcíÚµªcçÎB¡Pˆ¨¨(ݼP=+ïLãÇ ???!“ÉQ§N1a¯ßBØ;wÄË/¿,ˆ&MšˆÄÄD©K"‰ÅÇÇ‹Æ ÂÓÓSgŸ?~\¸¸¸ÂÉÉI8p@'ãQù=z$>üðCM¦êÛ·¯HKKÓÙxÌô$}å*ççÕŸŸ‹7ß|ó©:Lz~nòM!„èÓ§(ñ±[·nâƒ>BѪU+±téÒRS¿üò‹°··¬æ«1¥5u<(|||4ï…B!¦M›&’““%ªÔ¸Ý»wOsšësÏ=ÇËÕÌXLLŒhذ¡ Ú·o¯Ó@/„ÿý·èÑ£‡ ,--ÅçŸ. u:&iKLLÔÙåVeaþ 5}ç*ççÕ›Ÿgee wwwqíÚµ*}½¡3«¦Î•+W„•••X¿~ýSÿˆAAAB©TŠ£G ¹\®Õ…®ê?úæÍ›…½½½8räHͼ Q¼©STT$~ýõWMè ìííżyóx=v ÈÌÌo¼ñ† êÕ«'öîÝ+uI¤g{öì …B¾¾¾âþýûz·°°P,X°@XXX¢Gâï¿ÿÖËØDDæn÷îÝB©T ÂÕÕUDFFêu|æ’*PÉ8?¯Þü<**JÈår¡T*…R©vvv€P*•"44´f_¤̪©#„S§NŽŽŽO=ŸŸŸ/œœœ„›››èׯ_¹Ç)ÏêÕ«…ƒƒƒ8uêTõ_ˆQ¿i¾øâ ѪU+ÍûÅÉÉI,^¼˜¿ôkX~~¾1b„ är¹Ø°aƒÔ%‘ž|ýõ×ÂÒÒR£Gùùùz¯aÿþýÂÉÉIÏ?ÿ¼Þ'DDæäÑ£GbÚ´išË­z÷î-Ù‡dÌæËòiãü¼zóóG‰[·ni{öìDBB‚xøðaͼ8 ™]S'##CóÉÇ“o‚3fbÏž=%ÇÆÆFëѽ{÷RÇVÿ|òkT*U;H hÞ4ÿÿpqq+W®Ô¬^N5¯¨¨HÌŸ?_ó=Ÿ2eŠxôè‘Ôe‘Žäç狉'jîFµ`Á½œr_š¤¤$ѹsg½_@DdNž¼Üjùòå’ÿ®eþ0/†–?蜟W~^^ÆL=?— !„zÑdõJúÅž"ÒhÙ²%âââàââ‚ùóçcÔ¨Q¨]»¶Ôe™…~øï½÷òòòЩS'¡AƒR—E5èöíÛ4h"##ammo¿ýR—…‚‚Ìš5 «V­‚ýû÷Ç–-[`kk+uiDDFoÏž=5jÒÒÒàêꊟþÞÞÞR—¥Áüaú 5ÐcœŸSYÔós“¾¥9éÆîÝ»ȆŽ9ÇŽà /¼€cÇŽ¡mÛ¶8zô¨ÔeQ ùã?жm[DFFÂÍÍ 'Nœ0˜@U«V-¬X±¡¡¡°··Ghh(Úµk‡3gÎH]‘ÑR©T˜={6úô郴´4ôîÝ111ÕИ?L!ç"ª86u¨Ò,--¥.Á,µmÛ§OŸF÷îÝqçÎtíÚŸþ9 ¥.ªH¥RaþüùèÖ­îÞ½‹^½z!::­[·–º´§ôíÛWSÛµk×ðÚk¯aÓ¦MR—EDdtnÞ¼‰®]»bùòå°´´Ä²eËðÛo¿A©TJ]Z‰˜?L1å"*›:DFD©TbïÞ½øôÓOŸ~ú)ºté‚„„i £J»qã:w B&“aÑ¢Eؽ{7¤.­T7Fdd$‘——‡ñãÇc̘1xøð¡Ô¥…={ö M›68zô(\\\pèÐ!Ìš5Ks‰…¡bþ0Ƙ?ˆ¨llêKKK|öÙg8|ø05j„ãÇ£U«Vøá‡¤.*hË–-ðòòÂÉ“'Ѹqc=zsçÎ………áÿJ®S§6lØ€mÛ¶¡nݺغu+^}õU\¾|YêÒˆˆ VI—[9s;w–º´ cþ0~Æœ?ˆ¨tü &2R:tÀÙ³g1|øpdeeaÔ¨QèÙ³'?53`7nÜ@=ðî»ïjþÍbcc n …Š>|8N:…æÍ›ãüùóxõÕW,uYDDçöíÛ%^nåèè(uiUÂüa|L)ÑÓØÔ!2b¶¶¶Ø¶mÂÂÂаaCìß¿-Z´ÀªU«PTT$uyôÿT*¾úê+xzzâàÁƒpqqÁ¯¿þŠ­[·¢^½zR—We-Z´@TT† †¬¬, <S¦LA~~¾Ô¥„ÀËËËè.·*ó‡q0ÕüADÚôÚÔéÝ»7ÆŽûÔóB¼øâ‹X¹r%¢££!“ÉðÊ+¯hݺMý¼J¥*ñØ·o߆ŸŸJÜO¡Ph=¬¬¬´nÉ;}útxxxÀÆÆ:t(RRRJ}-åßÿ=<<úöí‹‹/"00>ć~ˆvíÚ!22RêÒÌÞüW^y3fÌ@^^&Nœˆ‹/âí·ß–º´¡P(ðã?bíÚµ¨]»6Ö­[‡×_7oÞ”º4""ɨT*|üñÇèÕ«RSSÑ£GÄÆÆÕåVÁüa¸L=PÍàü¼ôù¹šJ¥‚——W¹sn)ççzmêŒ?AAAÈÉÉÑz>""III9r¤æ¹øøxlÛ¶­ÂǶ°°@¿~ý°fÍš·çääh=^ýu 6L³]¡Phj»zõ*òòòàïï_åñ¶mÛ†ÿûß Avv6bbbЪU«RWÓã“ù±³³Ã† ðÇॗ^Bll,:vìˆaÆq‚-›7obРAðññAll,Z¶l‰Ã‡cýúõZ°LÅäÉ“qìØ1¸¹¹áÏ?ÿD›6m°gÏ©Ë""Ò;õåVË–-ƒ……-Z„½{÷ÂÉÉIêÒt‚ùð˜[þ êáü¼ôù¹ÚªU«`oo_î~’ÎÏE1ÄOÕ¨‚‚Ñ A±qãF­ç‡*† "„"**Jëׯ...âÁƒZÏ”9FEö»~ýºÉdâôéÓ¥îsäÈaiiYîk*m¼^xA„‡‡—ûõº_<==qîÜ9EÕSPP Ö¯_/amm-æÎ+222¤.Í䥧§‹9sækkk@8::Šõë×ëågÔ¤¥¥‰Þ½{ B&“‰?þX¨T*©Ë""Ò‹ýû÷ '''@¸¸¸ˆ#GŽH]’^1HÇÜó‡©âüüRÌÏD³fÍDDDD…çÜRÌÏõz¦Ž\.ǘ1c°iÓ&Ís Ãøñãµö;v,°|ùò¯cÓ¦MhÓ¦ Ú´iSê>áááhÛ¶m•ŽóæM$&&"66nnnpttD@@222*|ŒêŒO$—Ë1qâD\»v “&M‚J¥ÂâÅ‹áîîŽE‹=Õ§êËÊÊÂçŸwww,Y²………˜>}:®]»†‰'B.—K]¢^888à·ß~òeË`ii‰¥K—¢{÷îež.KDdì 1þ|“¿Üª<ÌúÇüAÕÁùyÙóóÉ“'cáÂ…P(UWoŠwz ãN ÿtáâââ„B¬Y³F4nÜX !´;[ááá¢nݺâÖ­[5Ö ,((ÎÎÎbÆ ¥cçÎB¡Pˆ¨¨¨r_OIãÅÆÆ "00P<|øP¤¦¦Š7ÞxC 4¨ÜãÕÄøºÂ3uŒ×õë×ÅÈ‘#…¥¥¥æÓ›eË–‰û÷ïK]šÑ»ÿ¾Xºt©P*•€ËåbôèÑâÆR—&¹Ã‡ ggg@8;;‹ˆˆ©K""ªq·oß>>>€°´´Ÿþ¹(,,”º,ƒÀü¡;ÌæóóǤ˜Ÿ‹7ß|³B¯¡¼ñuE=?×{SG!ºuë&>øà!„­ZµK—.Õl{ò›Ð§OPcošÐÐP¡P(Dvvv‰Ûùåaoo/>>øé§ŸLö?˜?èiœŸSYÔós6u¨ÂØÔ1/=„_ýYYYÿžhݺ5zö쉞={âµ×^C­Zµ$®¶jòóóqêÔ)ìÛ·ûöíÙ3g4¿ÿìììðÎ;ïÀßßݺuƒ•••ÄÕšžàà`Œ7YYYhÞ¼9‚ƒƒÑ¢E ©Ë""Ò’’’‚aÆ!""–––˜7o>ùäXZZJ]šIbþ`þ p~NeaS‡*Mó•——‡ýû÷cçÎØ·oRRR4Û¬­­Ñ®];têÔ ;vÄk¯½ «-]ZZNž<‰£GâĉˆŠŠB^^žf»³³3zöì‰~ýúáÍ7ßDíÚµ%¬Ö<\½z ÀùóçQ·n]üç?ÿÁÈ‘#¥.‹ˆaÆ!%%ÎÎÎøé§Ÿàãã#uYfƒùƒÌççT6u¨ÒØÔ!àñï‡3gÎh>]ŠŒŒDAAÖ>Ï?ÿ<<==áé鉖-[ÂÃÃnnn°··×KHHHÀÅ‹5ïÙóçÏãÖ­[ZûÕ®]¯¾úªæS¿Ö­[k~’þ<|ø“&MÂÖ­[X½z5êÔ©#maDd¶ŠŠŠ°xñb,X°€—[æ2GœŸSYØÔ¡JcS‡J’››‹èèh;v ÇGdd¤æZø'Õ¯_5B£FðÜsÏA©TjNNN¨_¿>€ÇŸ¾=9¡ÏËËCnn.€ÇŸx=ùHJJBBBâãã‘™™YâøJ¥ÞÞÞèܹ3:tè€öíÛ³q`@¶lÙ‚‰'"//­[·FPPš4i"uYDdfRRR0jÔ(8p˜;w.>ýôS^ne`˜?Èp~NeaS‡*Mª¨[·n!..Nó~¹téR™a§¦©ÃÛK/½¤ù´®E‹xþùçõ2>UÝ™3g0xð`\»v öööزe úöí+uYDd&Š_nåää„íÛ·£GR—EÄüA¦†ós* ›:TilêPu©OKNHH@RR’Ö']©©©ÈÈÈðøÓ·âךï€amm ZŸ°©Ï=÷5j¤×Ó¬I7²²²0vìXüòË/Éd˜:u*–/_n´ b‘áãåV¦ùƒŒççT6u¨ÒØÔ!"}B`ÕªU˜5k СCÁÅÅEêÒˆÈĤ¦¦bøðá¼ÜŠˆ ççTõüÜBêBˆˆˆž$“Éðá‡âÈ‘#puuʼn'àåå…H]™£Gj~·899aïÞ½øüóÏÙÐ!""£Á¦,oooœ>}=zô@jj*zõê…Ï>û EEER—FDF¬¨¨ .D×®]qûömtîܱ±±\?‡ˆˆŒ›:DDdÐ5ŸžËd2,X°½zõBjjªÔ¥‘R7ˆçÏŸ¢¢"Ì™3‡âåDDd”ØÔ!""ƒgaaO>ùDs‰Äàåå…cÇŽI]‘’.·Z¼x1är¹Ô¥U ›:DDd4ºvíŠØØXtîÜ·oßF×®]±råJ. HDeB`ùòå¼ÜŠˆˆL›:DDdT\\\pèÐ!Ìœ9*• Ó¦MCÿþý‘™™)uiDd€RSSñÖ[oaöìÙ(,,ĬY³x¹™ 6uˆˆÈèÈår|ñÅ …½½=vî܉víÚáÌ™3R—FDD}¹Õï¿ÿ¥R‰ß~û Ë–-ãåVDDd2d¢Ø9ë2™ pýúuÉ "ÃÕ²eKø@ÅÅÅFGª”ÒçPݸn5þ| Ê ¥÷ÀÙÙY_ýµþ÷ÿW&“I“'OVÏž=•™™it4 JX¼x±:uê¤3gÎÈÇǧì¹T ü°~À}Ú½{·^~ùeedd¨yóæZ³fŽ"''GÇ×Úµk%I‘‘‘š3gÓ9T&u¸OAAA:|ø°uáÂuíÚU3fÌ¿7­)]·Z»v­µjÕ*-Z´ˆB€ B©@9hÖ¬™âââôÎ;器¸XãÇ×óÏ?¯ììl££•âVëVFÇÀª±~@9ûæ›o4pà@eggËÃÃC±±±zâ‰'ŒŽT³Ù¬#FhõêÕ’X· 2QêPRSS¦øøxÕ©SGsæÌQdd¤Ñ±€r•˜˜¨°°0%''ËÁÁAŸ~ú)Ó9T"Ö¯¨Ú»w¯F­üü| >\ Ùl6:P./^¬Ž;*99Y^^^:tè…•ŒI*ØêÕ«5bÄ™Íf=öØcZ¿~½Ú´ict,àžÜjÝ***JõêÕ38¶‡R€Jœœ¬ÐÐP%%%±¦‚j+))I¡¡¡¬[PE°~@%hÓ¦8 ˆˆ™Íf 0@#GŽTAAÑÑ€;²|ùrùùù±n@B©@%qppЪU«´hÑ"Õ©SGŸ~ú©”ššjt4àw™Íf½òÊ+2dˆòòò4xð`ýíoc…€*€õ+ pôèQ½ð JMMUÆ µbÅ =÷ÜsFǪ¶rrrT\\¬ÒkL&“ìììäèèhp²êíß×­æÍ›§Áƒ ü ¥ÉÎÎÖ!C´qãF™L&?^}ô‘jÖ¬it´*Ål6+))IIIIJKKSZZšRSSuéÒ%eee)++ë¶ïwvv–³³³\]]åîî.wwwyxxÈËËKíÚµ“ƒƒC%}%ÕËòåË5zôhåååÉËËK±±±LçPÅPê` ‹Å¢¨¨(½óÎ;º~ýºµfÍ5oÞÜèh†°X,JLLÔÞ½{uàÀ}º6lht¼2¬[`}ª÷ü36ÄÓÓSаaÔ——§!C†hÈ!ÊËË34׆ Ô®];MŸ>]’ôî»ïêĉ6QèH¿~ {¿~ýtüøqÍœ9SõêÕÓ’%KÔ¶m[­_¿ÞèxÊÏÏ×ðáÃ5`À™ÍfEDDèСC:X&u¨†–/_®Ñ£G+//OÞÞÞZ·n<==+5ƒÙlÖ¨Q£´råJIR·nÝ´`ÁµnݺRsT5ééé9r¤¶nÝ*é×_°`êÕ«WéYN:¥—^zI ªS§ŽæÌ™ÃºV„R€j*))I¡¡¡JNN–£££–.]ªÐÐPC®ýÉ'Ÿ(""¢R®]]|ñÅ3fŒrssÕ¶m[ÅÄÄTêtÌëVUù9LàÞ±~@5åå奿ÿýï UNNŽÂÂÂ4nÜ8Uèu/^,???%''ËÇÇGñññ:·ðÊ+¯èðáÃòññÑÉ“'åçç§åË—WøuoµnO¡€¢Ô kРbbb4oÞ<ÙÛÛkîܹzúé§•žž^î×ÊÏÏ×€4|øpåååi̘1:xð`¥¯}U'¥ÏAŠŒŒ,{Ò AƒTPPP!×;sæŒ:uê¤Å‹«N:Z´h‘V­Z%GGÇ ¹0ëWX‰C‡éÅ_TzzºœõÅ_¨W¯^år=÷Üsúþûï+}ÕËZÄÄÄèµ×^SNNŽ‚‚‚´qãÆr-[bcc5lذ*ÿ±÷ üPê`E²²²4hÐ mÙ²E5jÔЄ 4qâDÙÙÙÝó9322¢„„5oÞ\;vìÐc=VŽ©mGRR’zô衟~úI:tÐÖ­[Õ¤I“û:g~~¾Þxã -Z´H’ª¥K—2€  ÔÀÊX,M:Uï¿ÿ¾Š‹‹¤/¿üRM›6½ës¥¤¤¨gÏž:wîœÚ´i£íÛ·ë‘G©€Ô¶#--M={öÔéÓ§ÕªU+}ûí·jÑ¢Å=ëÌ™3 +ût«™3gjôèÑåœTU”:X©Ý»wëå—_VFF†š5k¦µk×*00ðŽßŸ˜˜¨gŸ}V/^”ŸŸŸ¶lÙ"ggç Ll;233Õ»wo:tHM›6Õwß}wןŒÅºàAÉX©   =zTAAAúñÇÕµkWM›6Mwòûœ”””²B§gÏžÚµk…N9ruuU\\œzôèQ¶Þ–’’rGï-((ÐØ±c¦œœ…††òéVØ(&u°rׯ_×{ï½§éÓ§Ëb±¨OŸ>úâ‹/Ô°aÃ[Ÿ™™©Î;+%%E]»vÕ¶mÛT»víJNm ¢¸¸8µjÕJû÷ï—««ë’¢þýû+>>^öööš9s¦ÆŒS‰‰@UB©€Ø²e‹ ¤¬¬,yxx(&&F¾¾¾7såÊuéÒE òóóÓ®]»T¿~}ƒÛ†+W®èÙgŸÕ¡C‡äçç§;vÜò!Ç7®[ýÞý¶…õ+lDïÞ½uøðaùúú*55Uúä“OÊ^/,,ÔóÏ?¯„„µjÕJ›7o¦Ð©NNNÚ²e‹Zµj¥¿ýíoêׯŸ Ë^¿ÕºÕÑ£G)t¥¶ä‘GÑÞ½{5fÌhôèÑ0`€Ìf³FŽ©¸¸8=ôÐCÚµk×m×€P¾\]]µmÛ6=øàƒŠ‹‹Óرc%©¬|›?¾ìíí5oÞ<ÅÄÄÈÉÉÉàÄ *`ý ­aÆÉl6ë™gžÑ÷ß/íß¿_íÛ·7:žMJLL”¿¿¿òòò4eÊM›6MÙÙÙ¬[€[¢ÔÀ†%''á¼]!ÍIDATkèСJHHP^^žV­Z¥ˆˆ£cÙ´+VhðàÁjР<<<Ô¢E -[¶ìwl lëWØ0777åææ*//OÆ £Ð© ¤W^yE¹¹¹²X,úòË/)tÀ-Qê`ÃÆ§ÄÄDµoß^sæÌ1:þeáÂ…jÛ¶­õæ›oTQ¬_`£¶o߮կ__ñññjӦёpƒãÇ«cÇŽÊËËÓwß}§îÝ» T1Lê`ƒòóóõúë¯K’fÏžM¡Sµk×NþóŸ%©ìÓÊnD©€ š6mšÎœ9£Î;kèСFÇÁŒ”ŸŸŸN:UVð”bý söìYyyyéúõëŠ×ã?nt$ÜÆ‘#GÔ±cGÙÛÛ+))IFGU“:ؘ×_]ùùù3f …N5СC9Ryyy7nœÑq@¤6dÛ¶mêÕ«—š5k¦“'OÊÑÑÑèH¸ÙÙÙjÛ¶­222´cÇ=ûì³FGU“:؋Ţ>ø@’4}út j¤aÆš:uª$•ÝC&u°›6mRß¾}åíí­£GªF ~·S”””ÈÛÛ[Ç×7ß|£Þ½{ ŒŸæ°¥““&M¢Ð©†jÔ¨¡‰'J’>úè#ƒÓ€ª€IlÀ¾}û 9rD&“ÉèH¸‹E^^^:qâ„8 £#ñk:lÀŒ3$Iÿó?ÿC¡S™L&?^’4kÖ,ƒÓ£1©€•;þ¼Z´h!WWW¥¥¥©víÚFGÂ}(,,”»»»233•––¦æÍ› „I¬Ü矮ëׯ+22’BÇ Ô®][Æ Óõë×õù矈I¬XII‰ÜÝÝuáÂ¥¥¥éá‡6:ÊAZZšZ¶l)777={–_`£ø +¶sçN?^Ý»w§Ð±"îîîêÒ¥‹ÒÒÒô—¿üÅè8À ”:X±èèhIRxx¸ÁIPÞJïié=¶‡õ+¬TQQ‘š6mªÜÜ\edd¨qãÆFGB9ºté’|ðA5lØP?ýô“jÖ¬it$PɘÔÀJíÞ½[—/_VPP…ŽrqqQ`` .]º¤={ö€R+µcÇIR= N‚Š,IÚ¾}»ÁI€(u°R[¶l‘$õîÝÛà$¨(!!!’¤mÛ¶œgê`….\¸ ‡zHÍ›7×?ü`tT &MšèçŸÖÅ‹õÀT"&u°B$uêÔÉà$¨hO=õ”$iß¾}'•R+´wï^IR@@€ÁIPÑJïqé=¶ƒR+ô÷¿ÿ]’Ô±cGƒ“ ¢=ùä“’¤C‡œT6ž©€•±X,rrr’ÙlÖ•+WÔ A£#¡åää¨aÆjР²³³e2™ŒŽ* “:X™³gÏ*77W-[¶¤Ð±ŽŽŽrwwWNNŽÒÒÒŒŽ*¥VæäÉ“’¤¶mÛœ•å±Ç“$8qÂà$ 2Qê`eRSS%I')ñññ2™Lrppƒƒƒœœœ¬äää»:ÆÚ¸»»K“:ØJ¬Ì¹sç$I-Z´08IÅÉÎΖÙlVzzºš5k¦ððð{:ÆZPê`›(u°2ééé’$777ƒ“T<''' :TÇ¿¯cª»Ò©¬Ò)-`(u°2YYY’$gggƒ“T¼Ë—/kñâÅòóó»¯cª»Ò{]zï€m¨itP¾233%I®®®'©8...*..–Ùl–»»»öïßOÇX IÒ¥K— N*“:X™Ë—/K’7nlp’ŠséÒ%åääèàÁƒÊÍÍÕ²eËîékA©€m¢ÔÀÊäççK’êÔ©cp’Še2™äçç§)S¦èÿþïÿnYhÜÉ1Ö nݺ’¤k×®œT&J¬LQQ‘$©V­Z'©C† у>¨¨¨¨û:¦:³³³“$œT&J¬L^^ž$©^½z'©5kÖÔ¤I“4þ|eggßó1Õ™ƒƒƒ$Él6œT&“Åb±”“É$Iâñ¶…û€íaR+Ó AIRnn®ÁIPY®\¹"Irrr28 ¨L”:X™5~ýß{II‰ÁIP‘(u°2õë×—$]½zÕà$¨,¥SY¥SZÀ6Pê`e\]]%I™™™'Ae)½×¥÷ØJ¬Œ³³³$)++Ëà$¨,¥÷ºôÞÛ@©€•yà$I?ÿü³ÁIPYJïué½¶R+ãîî.IJKK»ïs]¸pA½zõRãÆe2™týúõ›^?s挺uë&5oÞ\S¦L¹éõ·ÞzKmÛ¶UýúõÕ¸qcõïß_÷|=IZ±b…Ú¶m« èá‡ÖÆË^ûë_ÿ*Õ¯__ŽŽŽêÕ«—RRRîëzws|^^ž ¤† êÐûï¿Óëñññ2™Lrpp(û}ÛkÞ‰Ò{]zï€m ÔÀÊ”g©S£F õë×OsçÎýÍk%%%êÓ§Ú¶m«¬¬,%&&jçÎúâ‹/ÊŽqppPLLŒÌf³NŸ>­üü|…‡‡ßÓõ$iåÊ•š1c†Ö¯_¯ÜÜ\9rD?þ¸$)''GøÃÔ¯_?eggë‡~«««^|ñÅ{¾ÞÝÿÇ?þQ?üðƒÎŸ?¯øøx­\¹RK–,ùÍqÙÙÙ2›Í2›Í·ý~Ü)Jl“Éb±XŒÊÏŽ;Ô³gOõèÑCß~ûm¹œ3>>^O>ù¤ŠŠŠT³fMIÒ©S§Ô¦M]ºt©ìY.;wîÔ‡~¨½{÷Þò<{öìQPPМˆ¹Õõ¤_K‹Ï>ûLݺuûÍ{Nœ8¡víÚÉl6—}ؾ}ûÔ¥KÝÓõîæøëׯ«Q£FÚ¼y³ºté"Iš5k–bccuàÀ{ºÎêÞ½»víÚ¥;wÞò{¬“:XoooIÒ±cÇ*åz7þ~Èb±Üöº;wîÔOª:hΜ9*((Ð/¿ü¢… ªÿþ÷t½»•šš*³Ù\69$I>>>JJJúͱ<òˆ\]]5xð`åääÜ÷µ%IíÛ·¿ïs€êƒR+Ó´iS¹ºº*##£B?ÖÜÓÓSžžžúàƒtíÚ5]ºtIS¦L‘ÙlÖ­7mÚ¤Y³fiÁ‚÷t½Ë—/Kúõ9>'NœPrr²~üñG >\’T³fMmذA«V­*{†ÏÅ‹µxñâ{ÿ"ï‚Ùl–¤²)!é×õ³¿:yò¤.\¸ øøx%''kĈ÷uÝŒŒ ýüóÏjÚ´)JÀÆPê`…*cZ§Fúúë¯uúôi5kÖLAAA P£Fd2™n:výúõ­Ó§Oë7ÞP:uÔ¨Q#9²¬ô©hrpp¸©HKHH——×mßw¿7,½Ç¥÷ØJ¬PçÎ%Iû÷ï¿ïsåçç«°°P’TPP üüü²×Nž}úH’ÜÜÜT¿~}ÍŸ?_EEEÊÍÍÕ¢E‹þãsfn÷õÝÍñ5kÖÔÀõÑG)77WçÏŸ×ܹsõꫯ–½w÷îÝe+qéééš8q¢úöí{Gß—ß³oß>IÒSO=u_çÕŸ~€ºr劜åàà ¬¬,ÙÙÙÝó¹þ}•JúÿÓ%S§NÕÌ™3UPP ¶mÛjÚ´i ºé½5kÖ”½½ýoòÙÙÙéèÑ£ ,ûïÿt½‚‚½þúëZ¿~½ŠŠŠ¢… ªQ£F’¤ï¾ûNï¾û®Nœ8¡5j¨S§Nš7o^ÙÊÓÝ^ïnÏËËÓˆ#´iÓ&Õ®][#FŒÐäɓˎ{ï½÷´dÉ]½zUõêÕSß¾}õç?ÿY 4øí7þ©qãÆ*((ÐåË—ËVÀ€m ÔÀJuîÜYÐþýûYͱR{÷îU`` ´gÏ£ã€JÆúV*$$D’´mÛ6ƒ“ ¢lݺU’Ô«W/ƒ“#Pê`¥Jÿ¡_úXŸo¿ýV’Ô³gOƒ“#°~€•²X,jÑ¢…þùÏêܹsrww7:ÊÑÙ³gÕªU+µlÙR)))FÇ`R+e2™&‹Å¢˜˜£ã œ•ÞÓ°°0ƒ“£Pê`ÅÂÃÃ%IÑÑÑ'Ay[»v­$饗^28 0 ¥V¬C‡òññÑ‘#G”`t”“øøx;vL¾¾¾òöö6:0¥V.22R’´xñbƒ“ ¼|öÙg’¤¡C‡œ‰%`å®\¹¢‡zH‹Eiiirqq1:îCvv¶~øaIÒ… äèèhp"`&u°rNNN4h®^½ª¨¨(£ãà>ÍŸ?_f³YC† ¡ÐÀÆ1©€ HOO—§§§êÕ«§ÔÔT5lØÐèH¸×®]SË–-uñâE:uJ­Zµ2:0“:Ø777 0@ÙÙÙúä“OŒŽƒ{ôé§Ÿê§Ÿ~Ò‹/¾H¡˜ÔÀVœ9sFmÛ¶U£F”––¦úõë wáêÕ«jÙ²¥~þùg9rD>>>FGcRáéé©°°0]ºtIsçÎ5:îRTT”.^¼¨^xBHbR›’˜˜(988èÔ©SjÚ´©Ñ‘p.\¸ 6mÚèÚµkúÇ?þ¡víÚ TLê`CÚ·o¯W_}U999š0a‚Ñqp‡þô§?Él6ëµ×^£Ðe˜ÔÀÆddd¨uëÖÊÍÍÕ÷߯ÀÀ@£#á6vïÞ­nݺÉÉÉI§OŸ–«««Ñ‘@Á¤6¦iÓ¦š4i’,‹^{í5åçç ¿ãÚµkŠŒŒ”ÅbÑäÉ“)tÀM(u°AcÇŽ•¯¯¯N:¥?üÐè8øï¿ÿ¾RRRÔ©S'5Êè8 Šaý õüC;vTQQ‘¶mÛ¦ž={ 7زe‹úôé£ZµjéðáÃòòò2:¨b˜ÔÀF=þøãúøãe±X4pà@ýøãFG¿œ?^ƒ ’ÅbÑŒ3(tÀ-1©€ ³X,úïÿþomÞ¼YO?ý´âââdgggt,›výúuuéÒEûöíSß¾}õÕW_Éd2 TALê`ÃL&“–/_.777ýõ¯ÕĉŽdóÞ{ï=íÛ·OîîîZ¶l…ø]Lêíß¿_]ºtQqq±6lØ çž{ÎèH6)66VáááªU«–öìÙ£Ž; TaLêuîÜYS¦LQII‰ú÷ﯸ¸8£#Ùœ¸¸8 0@‹EÓ§O§Ðÿ“: Ì¨Q£´páB999iÏž=jß¾½Ñ‘lBbb¢uåÊ7N³gÏ6:¨(u@™ââb 8PkÖ¬ÑC=¤Ý»w«U«VFDzj)))êܹ³233¡•+WòpG(uÀM ¢¸¸8µjÕJ{÷îU“&MŒŽe•222¨””uíÚUÛ¶mSíÚµŽª ž©nR»vm}õÕWêØ±£RRR sçÎËꤤ¤è©§žRJJŠüüü´yóf pW(uÀo899iË–-zòÉ'Ëʇ„„£cYÇ—•e~~~Ú²e‹êÕ«gt,PÍPê€[rqqQ\\œzô行Œ =óÌ3Ú½{·Ñ±ª½]»v)((H/^THHˆvíÚ%ggg£c€jˆRü.mÞ¼Y/¿ü²rrr¢uëÖ«ÚZ»v­zõê¥ÜÜ\ 8P›6mRýúõŽª)Jp[µk×ÖªU«ôÆo¨  @aaazóÍ7UTTdt´j£¨¨Ho¾ù¦^~ùeê­·ÞÒŠ+T«V-££€jŒO¿wlÁ‚zë­·TPP EGGËÍÍÍèXUZjjªÂÃÃuèÐ!ÙÛÛkÖ¬Y5j”ѱ€ Ôw%>>^aaaJMMUãÆõÙgŸ©oß¾FǪ’6nܨ!C†(;;[žžžŠ‰‰‘ѱ€•`ý Ü___%$$(44T—/_ÖóÏ?¯±cÇ*77×èhU†ÙlÖ˜1côüóÏ+;;[¡¡¡Š§ÐåŠRÜ5GGGÅÄÄhÞ¼yªS§ŽæÏŸ¯Ö­[+::Úèh†Û¶m›¼¼¼´`ÁÕ©SG .TLLŒŽ¬ ëWྤ¤¤hܸqÚºu«$©[·nZ°`Z·nmp²Ê•’’¢ñãÇkÆ ’¤.]ºhÑ¢EzôÑG N¬“:ྴjÕJ[¶lѺuëäææ¦]»vÉÛÛ[&LЕ+WŒŽWá²³³5aÂy{{kÆ rqqÑÒ¥KG¡*“: Ü˜ÍfMžûì3}õÕWe_Ë€4~üxÖ¬€!(u@…+nYºt©²²²$I Whh¨:tè`pÂ[‹WLLŒbcc•––&IrqqQdd¤UL€êRTš‚‚­_¿^K—.Õ_þò•þâîî®àà`«k×®jР!ùrss§íÛ·kûöíeEŽÉdR·nÝ4tèPõë×Oööö†ä¸¥0Djjª¢££«#GŽ”ý½|||Ô¹sguêÔIÞÞÞzôÑGU«V­r½~QQ‘N:¥cÇŽéàÁƒÚ»w¯Ž;¦âââ²c|}}¦ÐÐP¹»»—ëõî¥0\ZZZÙtL\\œrssoz½víÚjݺµZ´h!wwwyxxÈÙÙ¹ìOýúõU·nÝ›ÞsíÚ5]½zUYYYeRSSuîÜ9¥¦¦êôéÓ*,,¼é= 4P×®]˦†(r@UF©ª”ââb%$$hÿþý:pà€Ž;¦Ó§O«¨¨¨\¯S«V-µnÝZÞÞÞò÷÷W@@€¼½½eggW®×¨(ÿx7ÃÂgÄqIEND®B`‚frr-7.2.1/doc/figures/fig-vnc-gw-rr.txt0000644000000000000000000000000013610377563014541 00000000000000frr-7.2.1/doc/figures/fig-vnc-gw.dia0000644000000000000000000011236413610377563014057 00000000000000 #Letter# ## #NVE 1 VN 172.16.1.1# #NVE 2 VN 172.16.2.1# #NVE 3 VN 172.16.3.1# #NVE 4 VN 172.16.4.1# #CE 1 172.16.1.2# #CE 2 172.16.2.2# #CE 3 172.16.3.2# #CE 4 172.16.4.2# #VNC Gateway 1 192.168.1.101# #NVA 1 (NVA) 192.168.1.103# #NVA 2 (NVA) 192.168.1.104# #VNC Gateway 2 192.168.1.102# frr-7.2.1/doc/figures/fig-vnc-gw.png0000644000000000000000000020346013610377563014104 00000000000000‰PNG  IHDRuKi2ªÊbKGDÿÿÿ ½§“ IDATxœìÝwTTçÚ6ðk@A`1X¢`Åvj/ Æ ˉåD£Q£oNbblŸ%šD%zÔXã±ÅD•®‚¢ bDDEEE¥I‡çû×y%6ÊÌìÙ3×o-Ö:gÊÞ×08¹÷=Ͼ·B!@DDDDDDDD²b$u""""""""*;6uˆˆˆˆˆˆˆˆdˆM""""""""bS‡ˆˆˆˆˆˆˆH†ØÔ!"""""""’!6uˆˆˆˆˆˆˆˆdˆM""""""""bS‡ˆˆˆˆˆˆˆH†ØÔ!"""""""’!6uˆˆˆˆˆˆˆˆdˆM""""""""bS‡ˆˆˆˆˆˆˆH†ØÔ!"""""""’!6uˆˆˆˆˆˆˆˆdˆM""""""""bS‡ˆˆˆˆˆˆˆH†ØÔ!"""""""’!6uˆˆˆˆˆˆˆˆdˆM""""""""bS‡ˆˆˆˆˆˆˆH†ØÔ!"""""""’!6uˆˆˆˆˆˆˆˆdˆM""""""""bS‡ˆˆˆˆˆˆˆH†ØÔ!"""""""’!6uˆˆˆˆˆˆˆˆdˆM""""""""bS‡ˆˆˆˆˆˆˆH†ØÔ!"""""""’!6uˆˆˆˆˆˆˆˆdˆM""""""""bS‡ˆˆˆˆˆˆˆH†ØÔ!"""""""’!6uˆˆˆˆˆˆˆˆdˆM""""""""bS‡ˆˆˆˆˆˆˆH†ØÔ!"""""""’!6uˆˆˆˆˆˆˆˆdˆM""""""""bS‡ˆˆˆˆˆˆˆH†ØÔ!"""""""’!6uˆˆˆˆˆˆˆˆdˆM""""""""bS‡ˆˆˆˆˆˆˆH†ØÔ!"""""""’!6uˆˆˆˆˆˆˆˆdˆM""""""""bS‡ˆˆˆˆˆˆˆH†ØÔ!"""""""’!6uˆˆˆˆˆˆˆˆdˆM""""""""bS‡ˆˆˆˆˆˆˆH†ØÔ!"""""""’!6uˆˆˆˆˆˆˆˆdˆM""""""""bS‡ˆˆˆˆˆˆˆH†ØÔ!"""""""’!6uˆˆˆˆˆˆˆˆdˆM""""""""bS‡ˆˆˆˆˆˆˆH†ØÔ!"""""""’!6uˆˆˆˆˆˆˆˆdˆM""""""""bS‡ˆˆˆˆˆˆˆH†ØÔ!"""""""’!6uˆˆˆˆˆˆˆˆdˆM""""""""bS‡ˆˆˆˆˆˆˆH†ØÔ!"""""""’!6uˆˆˆˆˆˆˆˆdˆM""""""""bS‡ˆˆˆˆˆˆˆH†ØÔ!"""""""’!6uˆˆˆˆˆˆˆˆdˆM""""""""bS‡ˆˆˆˆˆˆˆH†*I@ ééé(,,„ P(`ll KKK‰“•ŽÞ5u233‹ØØX$&&"11·nÝBJJ žÏÈÈõë×G‡àááooo4oÞ\µ""""""""m‘]S'##8räŽ9‚äää÷+•J´hÑ-Z´(±Ò¦zõêªU8oS¼šçñãÇ%VúÄÆÆâÊ•+ÈÌÌ,ñx{{{ôêÕ ¾¾¾ðóóãJ""""""¢rਔ²“ES'77À®]»päÈdggxñ;;;ÃËË îîîèСêׯ##ÍÌ.**BBB"""†àà`\¹rEõgff??? 6 }ûö…©©©FrÉ G¥h†N7u®]»†õë×cË–-xúô) jÕªðóóC¯^½Ð«W/ØÛÛKš199GŽÁüÀÀ@ÕJžêÕ«cĈ?~<œœœ$ÍHDDDDDD¤-•¢=:×ÔB 00Ë–-É'¦¦¦èß¿?†Š^½zÁÌÌL┯—ÀÀ@ìܹ‡Bnn. zô课ú ;v”:"‘ÚqTŠ4t¦©STT„íÛ·cñâŸzõ* Q£F˜0aF‰–MJJ ¶mÛ†•+WâÖ­[ooo|õÕWðõõ•8QÅpTŠô$oê!°gÏÌ™3×®]xyyáþçЧO½éÚRPP€íÛ·ã»ï¾S½¾¶mÛâ믿Fÿþý b9éŽJÑ’6u®^½Š‰'âÌ™3Ì;ݺu“*’ÆaïÞ½X´h.^¼àEójÅŠhݺµÄ鈈ˆˆˆˆˆÞŒ£Rt“$MÌÌLÌŸ?Ë—/G^^ðã?bÀ€ÚŽ¢uB8p_|ñâããadd„1cÆ`É’%°¶¶–:‘ G¥è6­7uöïß©S§âÎ;011ÁôéÓ1kÖ,íèiJ^^V®\‰9sæ 33öööXµj $u4""""""2p•"Q)Zkêdffbâĉضm k×®X½z5š4i¢Ýë¬;wî`„  Œ=«W¯†¹¹¹ÄɈˆˆˆˆˆÈqTŠ|F¥h¥©‹Áƒ#..–––øé§Ÿ éÝÊÊÖ­[1iÒ$ddd iӦؽ{7Z´h!u,""""""2•"¿Q)oê¬[·Ó¦MCVVZµj…Ý»w£Q£FšÜ¥lݸqC† Att4ÌÍͱzõjŒ=ZêXDDDDDD¤ç8*å¹JÑXS'''cÇŽÅöíÛ“&MÂ÷߯W׃ׄœœL™2ëÖ­Œ9ëÖ­ãˆˆˆˆÔŽ£R^O.£R4ÒÔIOOÇ|€3gÎÀÒÒ6lÀàÁƒÕ½½¶{÷nŒ7éééèܹ38KKK©c‘žà¨”wÓõQ)joê$''Ã××ÑÑѨ]»6Ž;†fÍš©s#66=zôÀƒЦMâ½÷Þ“:ÉG¥”ž.JQë5Èâããáéé‰èèh899!44” hÑ¢ÂÂÂиqc\¸p^^^HHH:ÉTNN>úè#üóŸÿDVV&Mš„ˆˆ6tÞ¢Q£FÇøñã‘••…üã5jrss¥Ž¦¾•:111èÞ½;>|777>|¶¶¶êØ´Á{üø1z÷îóçÏÃÞÞþù§N-÷"""""""ÝÇQ)§k£RÔÒÔ‰‡——>|ˆž={bïÞ½°°°PG>ú_™™™4hŽ;†:uêàÔ©Shذ¡Ô±ˆˆˆˆˆˆH8*E}tiTJ…›:?†‡‡âããÑ¥KüñÇ011QW>zI^^|}}qòäI4lØaaa°³³“:é°øøxôìÙ prr‘#GP¯^=©cÉZbb"zö쉿þú 6ÄÑ£GQ¿~}­ç¨ÐL´´4ôèÑñññpssÃÁƒÙÐÑ ìÛ·íÛ·G||<úöí‹ôôt©c‘ŽŠ‰‰QÍgussCHH:jààà€Õñ¹§§'bccµž£Ü+u¸jD:\Eú.==………(þxR(066–ô\U"""Òo¬?HqTŠæI=*¥ÜM1cÆ`ãÆ¨S§BCCQ·n]ug£·ˆ‡êÕ«SSS ¿2"""ÒU¬?ÈÐq1€öH¹è¥\MmÛ¶aäÈ‘P*• ƒ³³³&²Ñ;ÄÄÄ C‡ÈÊÊÂÿû_øûûK‰ñññˆŽŽFLL bbbpùòe$$$ ,5–––066†B¡!PXXX¦Ó ŒŒàè舖-[ÂÙÙÎÎÎhÕª4hPæ×DDDDºõQIiiièÔ©¢££áææ†'Np…ކ¥¥¥¡{÷î8þ<ÜÜÜpìØ1­¬ô+sS'..íÚµÃóçÏñË/¿ @SÙ¨¶lÙ‚Ñ£G£jÕªˆŒŒDãÆ¥ŽD$77QQQ Ahh(ÂÃÃñøñãWW¥JÕ·XÅ?µjÕR}ËUüWi?ôÒÓÓUß®Ûvÿþ}Õ·p ¸}û6rss_y®<<<àíí www´k׎ßXÉ뢷ã¨éH±:ªLM¬¬,tèÐ111;v,Ö¯_¯ÉlTJ£FÂÖ­[ѪU+„‡‡£J•*RG"=–˜˜ˆÀÀ@>|§OŸFVVV‰û6lˆV­Z©¾rqqAýúõUßziKQQnݺ…K—.©¾µ‹ŽŽÆÍ›7K,u’HDD<<<0tèPÄÅÅÁÃÃÇÇ©S§XP©QÇŽ„?ÿü:tÀ•+W0dÈxxx ""BêxDDDZÅúC;XÐgŸ}†œœLš4‰ hÓ¦ &L˜€¬¬,L™2EmÛ}ãJwwwDDD`ÿþýèß¿¿ÚvHÚ³gÏ 2„«u Ð;wðå—_bçÎB Y³fXºt)üüü¤Žf~ÿýw̘1ׯ_‡B¡@@@–,Y‚š5kJˆˆHcXH‹õ‡aùã?àçç‡ZµjáÚµk°´´”:•Bjj*š6mŠääd;v Ý»w¯ð6_»R'44hÕªúõëWá4>üðC4kÖ ìÖˆ‚‚|÷ÝwprrÂŽ;`ee…•+WâÒ¥K,¨´¨OŸ>¸|ù2¾ÿþ{XYYá—_~AãÆñÃ?   @êxDDDjÅúC7°þ0B|ûí·€%K–°¡##ÖÖÖX¼x1¨ÞÊzmSgéÒ¥€iÓ¦ñ|LS(øâ‹/?þø£ÄiHÓŠÏÑüòË/‘››‹ &àÆ˜4i*Uª$u<ƒcbb‚éÓ§ãÆøä“O••…3fÀÝÝ—.]’:‘Z°þÐ-¬? G¥È›ºG¥¼rúÕÝ»wQ¿~}ØÙÙ!11&&&Þ I'//xüø1Q»vm©#‘šåää`Μ9ªoaš7oŽõë×ÃÝÝ]êhô’Œ7qqq¨T©þçþ³gφ©©©ÔшˆˆÊŒõ‡<°þÐO•"ê•òÊJ7¢  ãÇgCG˜˜˜`ìØ±(((ÀÆ¥ŽCjWWW|÷Ýw022ÂìÙ³qáÂT:ÈËË ÑÑј5kŒŒŒ°hÑ"¸ººâÊ•+RG#""*ÖòÁúCÿpTŠ~P稔M¢¢"üüóÏ022˜1c*´aÒü1ŒŒŒ°qãFI‡Ô@åË—ÃÕÕ111pvvFdd$æÌ™Ãf¬355żyópîÜ94oÞ—/_F»ví°råJ¼af=‘Î`ý!O¬?ô G¥èuŽJ)qúÕ±cÇгgOôèÑG­XJÒ)]»vÅÉ“'qâÄ téÒEê8T?ƈ#pôèQ( L™2‹-B•*U¤ŽFe““ƒ/¾ø«V­‚½{÷Æ–-[`kk+u4""¢W°þЬ?ä£Rô‹ºF¥”X©³k×.ÀСC+žtJñ{Zü“}º\ÛP5uŽ;èÑ£‡Z‘îéÕ«àÈ‘#'¡ÒÊÌÌDß¾}±f͘™™aûöíX¾|9*W®,u4R³Ê•+cÕªUغu+ªT©‚•+W¢ÿþÈÌÌ”:Ö†ƒõ‡|pTŠþªè¨ÕéWM›6E\\®]»'''õ%$8;;ãòåËRÇ¡w¸ÿ>z÷îèèhÔ¨Q„›››Ô±H ÂÃÃñÁ %%mÛ¶Å¡C‡P³fM©c‘`ýa¸Xè.ŽJÑo•b¼8Ÿ2..µk×fCG9;;£Fˆ‰‰Á£G¤ŽCoqíÚ5¸¹¹!::NNNgAe@ÜÝÝF!** :tÀõë×¥ŽEDDzŽõ‡acý¡»8*E¿UtTŠð¢+ ¼ø‡LúÍÓÓ*qz“èèhtêÔ IIIðññAhh(êׯ/u,Ò²  ""žžž¸sç:vìÈvDD¤1¬?`ý¡«8*EÿUdTŠ„„„¼¼¼Ô‹tQñ{\üž“n9wîºté‚GÁÏÏGŽa7Þ€ÙØØàرcèÙ³'>|ˆÎ;ãüùóRÇ"""=Ãúƒ^ÆúC÷>|лwo‰“¦øúúþøã2?×xñA®®®jŒEº¨}ûöÀfŒnݺáÙ³g8p öïß333©c‘ÄÌÍÍqðàAôïßOŸ>E·nÝØ”%""µaýA¯ÃúCwpTŠa¨È¨#!bcc¡P(ТE E$]ѲeK( \ºt ÿ;#›tÀ¹sçзo_ddd »w‰‰Ô±HG˜˜˜`Ïž=@zz:úôéÃÆ,UëzÖº£R GyG¥ݼyhРªV­ª‰l¤C,--áàà€ôôt$&&J‡\¾|½zõBZZ†Ž-[¶ÀØØXêX¤c*Uª„-[¶ÀßßiiiðõõELLŒÔ±ˆˆH¦XPi°þG¥ŽòŽJ1ºv퀗4'ÃЬY3ÀÕ«W%NBqqqª%Ï ÀÖ­[YPÑcëÖ­èׯžRÎìů¡Fï|lõêÕamm­úYµj•¦ãéœêÕ«RRR$Nbx¶nÝŠ•+W–ø–ƒ¨"6lˆ½{÷ÂÄÄË—/ÇÎ;¥ŽDDD:†õ©ëíë¨MŸïܹ¸~ý:RRRôª±X®¦ÎÓ§O666ˆ¤Å ©G½ó±)))HMMUýLš4IÓñt›:Ò¸xñ"&L˜X½z5ÜÜÜ$NDúÂÛÛ?ýô`̘1¼Ô(©°þ Maý¡yr•¢­ãóÆcÁ‚ظqc¹³êšòŒJ1ÊÉÉT©RE#¡´¡V­ZpqqÁŽ;¤Ž" fff€ììl‰“ާOŸbРAÈÊÊ„ ÞyÕ5¢²3f ÆŒƒ¬¬, 8iiiRG"""‰±þ Mcý¡Yr•¢ÍãóÂÂBT®\YãûÑ–òŒJ1ÊÏÏÙÿ"V®\‰… bõêÕÈÈÈð¢³ùå—_âèÑ£ÚvNNòòò¼¸ŒZq#L®Š/[É¡fÚ!„À¨Q£pëÖ-xxx`ùòåRG"=õÓO?¡}ûöˆÇ?þñ©ã‘„X¶°þÐ9JÑÄñù¥K—püøqÕ±y\\¾ùæ¨-·ÔÊsVQñeèä>ýÞÇǧNÂñãÇáèè tìØÙÙÙh×®êqÖÖÖP*•ªŸ=z¼sÛfffðôô(•JÕJ¹R*•€ÌÌL‰“†µk×â÷ßG5°{÷n^:”4ÆÄÄ{÷î…­­-öïߟþYêHDD$Ö¤-¬?4GΣR4q|žŸŸ/¿ü¶¶¶P*•ðó󃯯/þýïkã%iEyš: xÑÍ'áP(ð}×´¸¸8´mÛÙÙÙ8xð úôé#u$2û÷ïÇÀ¡T*…ÆK‰ˆˆ´ˆõIõ‡úY[[#-- ©©©°²²’:iAZZ¬­­aee…ÔÔÔR=ǨøšñÅK¢HÿŸëÊÍÊËËÃG}„¬¬,|òÉ',¨Hk €1cÆ 33}ôŠO³%""ýÇúƒ¤ÂúCýôeT •^yF¥ŠŠŠ4ŠÈP-\¸QQQ\©Ô IDATprrÂ÷ß/u20ÿþ÷¿Ñ¸qcœ?K–,‘:i ë’ëõÒ—Q)Tzå•¢¨U«–¸ÿ>îÝ»‡Zµji*餤$¼ÿþû¨S§îÞ½+u½tåÊ´iÓEEE/qÞ(‘¶œ;w¨\¹2¢££Ñ¤I©#‘±þ ]ÀúC}82Ã0•õ}7*ž¤]‘býAº†õ‡zXXXž?.qÒ–âUYÅ«´JÃÈÁÁ˜˜¨HowïÞ=øùùÁÆÆ …%îùzõJ¥&&&°´´TÝ?}út4mÚ°±±¿¿?’““˽?زe š6mŠªU«âý÷ßÇ*´½—•5¯¦¿×Åï=©Ï×_ôôtŒ9;w–: G>|8ž={†Ù³gK‡ˆˆÔŒõé"Ö§íQ)r>>ÏÍÍEÇŽaoo¥R +++tíÚáááoÜ¿®Ÿ¿¬<£R$mêaÀ€X±bÅkïÏÌÌ,ñãããƒáÇ«îW*•ؽ{7233ñ×_!''C‡-÷þ¶mÛ†¥K—bïÞ½ÈÈÈÀ… вeËroïïÊšWSØÔÑŒèèhlÚ´ J¥‹/–:Q ‹/†¹¹9þóŸÿ 66Vê8DD¤&¬?H—±þ¨mJ‘óñy¥J•°jÕ*ܾ}™™™¸ÿ><==Ñ·oß7î_WŽÏ_V®Q)GD=„TΟ?/ˆüüü7>ææÍ›B¡Pˆ¨¨¨7>&((H—{õêÕÇ/}ðwlï]J›Wݺví*”ëµÒ›uêÔI .”: ÑkÍ;WÝ»w—: © ëÒu¬?Êoذa€Ø±c‡V÷+÷ãs!„xþü¹˜;w®hß¾}©Ÿ#Õñù˶oß.ˆáÇ—ú9F...€Ë—/«©·¤6l@›6mЦM›7>æøñãhÛ¶m¹¶çÎܾ}/^„££#ªW¯Ž€€<{ö¬¼‘ß©"y+"&&àìì¬õ}ë«ÀÀ@œ>}ŽŽŽ˜6mšÔqˆ^kÆŒ¨[·.þüóO;vLê8DDTA¬?HX”Ÿ”gÕ¼‹®Ÿ6 U«V………ÂÃÃqæÌ™RïSªãó—•ç¬#{{{ØÙÙ!99Yg/k^PP€M›6aüøño|Ìo¿ý†ü«W¯.×>ž>} ¸qã®^½Š¸¸8Ü¿ÿüç?˵½w©hÞòJNNÆ£G`ooÏAÉj"„À·ß~ X´hªT©"q"¢×377Ç‚ €ç¶Éë’ Öå§«M]>>ß¹s'222pýúu¤¤¤`ÆŒ¥ÚŸTÇçW®¦èújC‡!33³Äùz/Û»w/Fýû÷£]»våÚ‡R©,X°fff¨^½:fΜ‰ßÿ½Ü¹ßDyË«ø=.~Ï©â~ûí7DEEÁÅÅC† ‘:Ñ[ >Í›7GDD„F>߈ˆH;Xœ°þ(GGGÀ­[·$NR’ŽÏ7nŒ `ãÆï|¬”Çç—¨_¿~©ŸcîîîðÖÉÐRZ·nüýýUoìË6mÚ„±cÇâàÁƒèÖ­[¹÷áèèKKK( Õm …BˆroóuÔ•·¼Šßãâ÷œ*¦¨¨HõüyóJüýé"###ÕßììÙ³ÕþGDDšÇúƒä†õGùèêâ ¹Ÿ¢råÊo}ŒÔÇçWžQ)Fàáá Ó@¬·ËÉÉA^^€—!ËÉÉ)qÿ;wpìØ±×.íZ±bf̘£GÂÛÛ»Bû366ÆÇŒY³f!77Ïž=ÃâÅ‹ß:-»4ù+šWÝBCC.\À'Ÿ|‚íÛ·#))I’,úàСC¸|ù2Úµk‡>ø@ê8D¥òᇢU«V¸pá:$u""*#Ö$G¬?ÊNŠQ)r=>¿téŽ?®ÚV\\¾ùæ¼qߺp|þ²rJBˆÔÔTall,¬¬¬DAAf8¿€W~^öí·ßŠÖ­[¿ñ¹•*U%~Š_Ã… Jüÿwí/''GŒ?^ØÚÚ KKK1tèPñôéSÕýeÝÞßÿ®¼š–——'”J¥¨\¹²°²²*‘¹AƒbôèÑbóæÍ"!!A+yô»»» >,u¢2Ù·oŸ :uê$u""*#Ö$W¬?ÊNÛW.–ëñùùóçEÛ¶m…R©ÂÑÑQ|ýõ×"77÷µ/M^m+ï•ÉB¼X¿äááððp„……ñÔ=oooxyyaéÒ¥ BPP‚ƒƒ‘žž^â±uêÔA§Nàããooo899I”ZwEDDÀÝÝÍš5Cll,—>“¬ÁÙÙW¯^Åùóç%?˜ˆˆJ‡õÉë²),,Ä¿þõ/üðØ?>¾ùæ©#‘Í;sæÌÁìÙ³1gΜR?Ϩøøúúþøãµ‡#ÝðóóC‡ðÅ_à÷ßÇÓ§O…eË–¡_¿~°µµERR~ùåŒ?M›6EÍš51tèP¬^½±±±<À²e˼¸L# *’###L:°téR‰ÓQi±þ 9cýñv¹¹¹Æ‚ àëë‹jÕª!""€4£RH»ŠG¥xzz–éyª•:QQQh×®Ú¶m‹ÈÈHõ'$ɵmÛ.\@TTÚ´ióÆÇ !påÊœ>}ÁÁÁ8sæ >|Xâ1¶¶¶ðööFÇŽѱcG¸¸¸ÀØØXÓ/Ag$&&¢Q£F°³³Cbb"LLL¤ŽDTfÙÙÙpttÄ“'OpóæMÔ­[WêHDDô¬?H°þø?ééé EHH‚ƒƒqîÜ9äææ–xŒ««+¢¢¢ T*ñäɃ:æ2$ùùù°±±Ann.ž>}úÚ!Ôo¢Z©Ó¦M888àÂ… ªk£“þ¸yó&.\¸€ ¼µ¡¼˜*Þ¢E Lš4 »víBrr2®]»†uëÖ! uêÔÁ“'OpàÀL›6 mÚ´­­-úô郥K—"""ZzeÒX·n 0qâDT$[ffføôÓOQPP€uëÖI‡ˆˆÞõéC®?’““ñ믿bÊ”)hÓ¦ lllàç燅 "88ùùùpqqÁ¤I“°sçNÜ¿gÏž…««+ÒÒÒpîÜ9©_iÈÙ³g‘™™ 77·25t€—VêÀ¿þõ/,Y²ß}÷¾øâ µ%é,Z´3gÎÄW_}…… Vx{  ™3g„„„„÷+•JxxxÀÛÛ:uBûöíajjZáýê‚‚‚¼ÿþûHIIÁÝ»waoo/u$¢r»wïðÞ{ï!11•*U’:½ëÒ'†R$$$ 88ÁÁÁ Áõë×KÜobb‚víÚÁÛÛÞÞÞðôô„µµõ+Û™?>¾ýö[Ìš5 óæÍÓV|Ò¢™3gbÑ¢EX¸p!¾úê«2=·DSçÂ… hÛ¶-Ú´iƒ¨¨(µ%é´lÙ—/_Æ¥K—àââ¢öí'%%©}AAA¸té U÷WªT ­[·FÇŽUzÕªU“$ëÛ$%%¡^½z¨U«nß¾ ##£w?‰HÇ ^½zxôènß¾ZµjI‰ˆˆ^Âúƒô‘ÜêçÏŸ#<<\u:ÕÙ³g‘••Uâ1uëÖ…êt*'''µ]¥Ž£RôWEG¥¼ÒÔIKKC:u „@bb"ªW¯®¶°¤}©©©xÿý÷¼8wÕÒÒRâD/¤§§#88XuºVddd‰áÊFFFpqqQ­äñññ„‰_X¶l>ÿüsL›6 ?þø£ÔqˆÔfêÔ©ø÷¿ÿåË—cÊ”)RÇ!"¢—°þ }¥ËõÇ“'OŠ3gÎ $$.\(q¼¢P(дiSx{{ÃËË >>>½’G¥è¯ŠŽJy¥©“&MÂêÕ«1sæL,X°@-AIÿïÿý?Ìš5 “'OÆŠ+¤ŽóFÏŸ?GXX˜jøòß/çWü¡Y¼’ÇÇÇG’n~‡pöìYDDDÀÍÍMëû'Ò”°°0xzzÂÃá¡¡RÇ!"¢—°þ }¥KõÇÝ»wKÌùzõj‰ñÅgx{{«ÆGh{G¥èuŒJymSçÎ;hÔ¨ÌÍÍqëÖ­×Nà&Ý— àáǸ~ý:6l(u¤RËÉÉÁÙ³gqæÌœ9s¯,olÔ¨|||Tžzõêi4Sbb"êׯzõê!!!AmK)‰tõêÕCRRnß¾­ZáGDDÒbýAúLÊúãÚµk APP‚ƒƒqûöí÷›™™ÁÍÍM5ÂÝݽ̗šV7ŽJÑ?ê•òÚrëÖ­‹>ú©©©üc‘±µk×âÁƒøðÃeÕЀ*Uª cÇŽøöÛoqâÄ ¤¦¦"44 .„¯¯/ªV­Š7nàçŸÆÈ‘#áààŒ5 ?ÿü3nܸ¡öL{öìƒfAEzG¡P`ðàÁB`Ïž=RÇ!"¢ÿÅúƒô™¶ê‚‚DFFbùòå8p jÔ¨fÍšaüøñøå—_pûömX[[£OŸ>øî»ïŠ´´4œ:u sçÎE÷îÝ%oèÀðááT*±uëV¤¤¤H‡*(55¿üò ”J%†^îí¼v¥ܸqM›6EµjÕ˜˜ ‹rï„´ïùóçhР=z„ .èÝò¼‚‚\¼xÁÁÁ8}úôk§ÍתU«ÄJž¦M›V¨òôôDXXBCCáááQÑ—@¤sBBBTç…K‡ˆˆÀúƒôŸ&êììlœ?^5'<<%S«V­ópZ´h!‹!ä•¢?Ô5*åMàE'pÇŽX¸p!¾úê«rï„´¯øäÃ?4ˆoÝ‹ŠŠ«ººVpp0=zTâ1vvvªy<;v„³³s©?¸Ÿ>}Š5jÀÊÊ =‚±±±&^‘¤ P½zu<þ)))°²²’:‘AcýA†@õGZZBBBTóp"##KÌç€Æ«8^^^hРº^‚VqTŠ~P稔·6ubbbЪU+(•J\¿~öööåÞiϽ{÷àää„ììl\ºt Í›7—:’Ö !píÚ5ÕյΜ9ƒû÷ï—xLµjÕàåå¥ZÉÓºukTªTéµÛÛ¹s'üýý1lØ0ìØ±C/HC† Áž={°{÷n ФIÄÅÅI“Hã6lˆ›7o">>^¶ç›ÉÝõë×áääÄúƒ FqýѺuk\¼x±Ä}5jÔ€———êTªV­ZüŒ)ŽJ‘'MŒJ)Õ”ØÉ“'£]»v¸~ý:æÌ™Sá’fÌš5 ñññpwwÇĉ¥Ž#;5kÖİaÃðÓO?!66>ÄÞ½{Ñ£Gà'È`xzzBCC%NBDd¸Â°þ ÃQ\ØØØÀÁÁ#FŒÀúõëqíÚ5U]>uêT´mÛÖà:àììŒ?þéééøú믥ŽC¥4sæLdffbܸqj›}[ª¦Ž±±16lØ,Y²GUËÎI}>Œü&&&X·n,.ǧëììì0pà@äççø¿ÿÐé;6uˆˆ¤WüÌúƒ Eñߺ££#nݺ…­[·bìØ±prr’8™îš?>,--±iÓ&µ]ž4çÔ©Sضm¬­­1oÞ<µm·ÔGþ-[¶Ä‚ „Àˆ#^¹’IçîÝ»5j„Xºt)Z´h!u$½Â¢Š ›:DDÒcýA†¦øo=<<\â$òaoo¹sçBqãÆ!''GêHôÙÙÙ?~<„˜?¾Zgß–j¦N1!úõë‡C‡ÁÇÇ'OžäÒ7‰ S§N Eÿþý±oß>( ©cé´´4T«V –––xöì·d„¨V­ÒÓÓñìÙ3½¾z‘.býA†ˆõGù¢C‡ˆŒŒÄ¿þõ/,^¼XêHô3fÌÀ?üwww„„„¨õÌš2mI¡P`óæÍ¨[·.‚‚‚0{ölµ¡òùæ›o lÚ´‰ÿÑW³èèh!ЪU+þnÉ`( 8;;C˜˜©ãÖdˆX”G¥è>MJ)óÖlll°cÇT®\‹-ÂÁƒÕˆJoÏž=X²d LLL°k×.X[[KIïÿÅÙÙYâ$DÚUü7Ï¢ŠˆHûX¡býQ>•¢»´1*¥\-",Z´EEEð÷÷ÇÉ“'Õ‹ÞáäÉ“øè£ „À’%Kàêê*u$½Ä¢Š ‹*""é°þ CÅú£ü¦OŸŽ¾}ûâñãÇð÷÷Gaa¡Ô‘ ^AAüýýñäÉôïß“'OÖÈ~ʽîgúôé˜0a²²²0pà@þÃÓ¢˜˜ 8yyy˜2e ¦L™"u$½Å¢Š ‹*""é°þ CÅú£ü8*E÷hkTJ™%ÿ]aa!FŒ;v N:8uê6l¨Î|ô7ñññðððÀãÇ€mÛ¶ñ\k ²¶¶FZZRSS9¬ Ê“'OP½zuØÚÚ"%%Eê8DD…õ*Ö†N:¡°°û÷ïÇ| u$ƒ´gÏ :•+WFpp°FϬ©Ð„ccclÞ¼]ºtARR|}}ñðáCue£¿INN†¯¯/?~Œ.]º`ãÆlèhгgÏTWŸ`AE†ÆÖÖU«VÅ“'O‘‘!u""ƒÁúƒ ëŠã¨éi{TJ…Ç.›˜˜`ß¾}puuE||<¼¼¼ Žlô’øøxxzz">>nnn8tèLLL¤Ž¥×’æ ’Jñß~ñ¿""Ò<ÖdèXTG¥HGŠQ)j¹––••>ŒöíÛ«šÑÑÑêØ4ˆŠŠR5ËÜÜÜpøða˜››KKﱨ"CW¿~}`£žˆH‹X¡cý¡+W®„¿¿?ÒÒÒàçç‡øøx©#é½øøxtíÚiiiÀ²eË´²_µ] ½zõê8yò$zôèäädtìØ§NR׿ Ö‰'йsg<|ø¾¾¾8qâlmm¥Ženݺptt”8 ‘4øM‘ö±þ CÇúC=8*E»¤•¢¶¦(•J:tÇGzz:|}}ñ믿ªseçÎðóóCFFFŒß~û RÇ2÷ïßÔªUKâ$DÒ¨]»6àÞ½{'!"2¬?ÈбþPŽJÑ©G¥¨µ©¼øÃùå—_0uêTäææbÈ!øüóÏ‘ŸŸ¯î]é­üü||þùç>|8òòò0}útlÙ²•+W–:šAyúô)ÀÆÆFâ$åÓ»woŒ3æ•Û…hذ!–-[†ÈÈH( ¸ººâå áß^PP ºíâÅ‹8p ììì`aaLž<ù­W&ˆ‰‰ÁСCaoo¥R ;;;øùùáðáÃ¥z ¯Ë¡ËîÝ»???ØØØÈ*÷›ÿíÿ[ ""ÍcýÁú£¬¦OŸŽ¦M›ÂÂÂ666ð÷÷Grr²Ô±Êõ‡zqTŠf騵7u@¡P`Ù²eXµjLLL°lÙ2øøøàÎ;šØ^¹uë<==±lÙ2˜˜˜`õêÕøþûïy•+ Ƚ¨7nvïÞÌÌÌ·Ÿ:u III9r¤ê¶[·naÛ¶moÜVPP:uê„.]º !!ÏŸ?GPP”J%¢¢¢^ûœøøøÀÓÓ×®]Cff&nÞ¼‰1cÆ 00P=/RÇaÀ€X±b…ÔQÔ¢øTÏ'OžHœ„ˆÈp°þø?¬?JG©Tª~çýõrrr0tèP©c•ëõã¨ÍЙQ)BÃΟ?/acc#öï߯é]ÊÖþýû…µµµ 5j$.^¼(u$ƒæîî.ˆ°°0©£”K~~¾¨Y³¦X·n]‰ÛýýýŰaÄ/þ}«W¯µk×ÏŸ?/q{~~¾BñÝwß•iÿ...béÒ¥ï|Üš5kDÓ¦M…R©VVVbРA"99Ydff 333@XXX ±yóf!„éééâã?¶¶¶ÂÊÊJŒ9R•}×®]ÂÅÅEµýÉ“'‹J•*‰ôôt!„Û·oíÛ·ë¾…bÅŠÂÕÕµDÖ°°0QµjU‘™™ùÖ×ô÷ߟ\ ÂËËKê(DDƒõëòÖÅ‚‚‚„±±q©«‹XhNnn®>|¸ LMMÅž={¤Ž$[;vì&&&€1b„ÈËË“,‹Æ›:B‘––&,…B!&Mš¤ú€#!222ħŸ~* … ,ÒÒÒ¤Žeð7n,ˆëׯK¥ÜfΜY¢0xúô©¨R¥Š8qâ„âÿЧœœáìì,¾ýöÛ·ççç‹{÷î âöíÛ¥ÞoYžsðàA/„âáÇ¢[·n¢_¿~¯äxÙ AƒDÿþýEZZšHOO]»vŸ~ú©BˆG ###ñèÑ#!„Íš5 6‡BññÇ‹/¿üòû~úô©033±±±ªýŽ3FŒ;ö¯I_š:×®]„“““ÔQˆˆ ëÖå­?Š}ûí·¯4†ä„õ‡f‰©S§ªŽÍ§M›&iCBnòòòÄ´iÓTÇîÓ§OEEE’fÒJS§ØÊ•+Uïš5kŠ;wjs÷:)00PÔ«WOfffbÍš5RG¢ÿU·nÝ2ºææÍ›B¡Pˆ˜˜!Ä‹o4h úày¹h9~ü¸077wïÞ-q{tt´ ²³³K½ß×=gïÞ½ÂÊÊJXZZ SSSQPPðÚçž]Œ;VlÛ¶MˆK—.‰-Zˆ7nSSS‘••õÎ} ñbùr5D^^žØ´i“hÖ¬Y©^¿¾4u>|(ˆ5jH…ˆÈ`°þ`ýQÞúã×_ÖÖÖolÉëíᨔÒÓåQ)Zoêûõ×_UßD˜˜˜ˆ™3gŠÔÔT©âhͳgÏÄÌ™3U+–ªW¯.6lØ ù’-z•••• û¿ËÝ»w [[[,*Uª$ƒR©”*šFddd`Ù²eX¶lRSSabb‚éÓ§ã›o¾µµµÔñè5òóó@ö—’ïׯ>ýôSŒ9}ûö…½½ýÛ¸qcLœ8³gÏ.qûÊ•+Ñ·o_(•JŒ9U«VÅ;wðÓO?¡sçÎèÙ³ç+ÛZ½z5úôéƒ*Uª`Ĉ°±±Avv6Î;§zLvv6 aeeSSS$%%aþüùªû‹§Çß¼yMš4¼÷Þ{èÛ·/¦M›†•+WÂÆÆÉÉɈE·nÝ>>>HJJ¾}û0oÞ<( tîÜ?üð¾øâ‹Rí»Ø¸qã0oÞ<\½z{÷î}çï;''yyyøÿìÝy\Íéû?ðש´(¥)šBdZF²Ë–¥²ÔF²}È4ccƘ±Œ1>™åƒAŒfF³d²K)Ld™±¤E%m’ ©TTZ®ß¾ŸmÎé}–ëùxœ?tÞûUçt\ç~ßïë€ŠŠ TUUASS³Áï“EjjÏþ‹¨¬¬DzzºÀicL9ÔþÂõ×­?¶lÙ‚µk×"<<ýúõ«÷XyP[ÈË–òòNWWÁÁÁضm>ýôSlÛ¶ !!!Ø´i“\ï¢& 'Nœ€nß¾ ---lܸï¿ÿ¾Ð±^$ô¬Qjj*yyy‘ªªªxé×çŸ^gF_^ݽ{—–/_.^ª¥¦¦F3gΤôôt¡£±àÿ.SË–-#tüøñ:_ÙŠ’ÂÂBñÒïç¿~õêUzûí·ÉÐÐZ·nMæææ´hÑ¢z—‡ÇÅÅÑäÉ“©}ûö¤££CíÚµ#WW×:K;7lØ@íÚµ#êׯíØ±£ÎØ>>>âeÎDôìŒÂ¼yóÈÈȈtttÈÒÒ’6nÜXgì!C†P÷îÝÅÿö÷÷a7‘†Æ&"ÊÏÏ' š4iR£~×µ¯›çoòìe?ßøÆ7¾ñMú7EÀõGËÔÀ³Ïµ»uÕÞ^Õ?H(Òß<áV)ÏÈ[«dDrr26lØ€ß~û OŸ>…††&Mš„¹sçbøðá‰DBGl"©S§ðË/¿àÀâŸeúôéøôÓOѽ{w¡#²F¨}½ÉП@uu5LMMWWW¡ã´¸Ú¿ƒ.]ºœ„1Æ”CíÊH®?”×\‡ )$$K—.EVVÔÕÕ±lÙ2|úé§ÐÓÓ:šTá»ï¾Ã¦M›PVV###lذsæÌ‘é¹™šÔ©•““ƒ-[¶àçŸÆÃ‡˜2e <<<лwo¾Ü•+WŒýû÷#33`ddooo,\¸°Þe§Löèêꢤ¤ÅÅÅhÓ¦Ðq˜@°~ýz¤¤¤@EEEè8-ª¸¸zzzÐÕÕÅ£G„ŽÃcJëpýÁõ‡ðJKKÅ­R*++¡¯¯¯T­R.\(7­RdrR§VEEBBBðóÏ?㯿þÏÔš››ÃÅÅ...prrì?¼’’œ>}aaa OäˆD"Œ9ÿùÏ0qâDhhh’½ž¶mÛâÑ£G(**RøYiör†††ÐÒÒB`` œœœ„ŽÓâ=z„¶mÛBOOEEEBÇaŒ1¥ÀõãúƒëY’––†µk×â×_Euu5 ðþûï+Ä¢…ÚÅ$;wîDQQÔÔÔàéé‰5kÖÀÂÂBèx&Ó“:ÏËÈÈÀüýû÷ãÚµk⯫ªª¢W¯^4h;;;tïÞ]âÍå*++qóæMÄÅÅáÒ¥KˆŽŽF\\ª««ÅÇôéÓ“'O†‡‡ÌÍÍ%:>ky:tÀýû÷qïÞ=´oß^è8Œµ¸ÜÜ\˜˜˜ cÇŽÈÉÉ:cŒ)®?˜²ãúC6q«Ù%7“:ÏËÌ̯Ž9}ú4JJJêܯ®®Ž=z K—.077‡…… Å7mmmhiiÕùž²²2<~ü>ß222žžŽŒŒ ¤¤¤ˆw#¨Õ¦M899‰W ñDŽbyã7••…Û·oÃÌÌLè8Œµ¸¬¬,¼ñÆ033ÃíÛ·…ŽÃcJë¦ì¸þmÜ*EöÈå¤Î󪫫qýúu\¸p/^D\\RRRÄÛQKJ«V­Ð£GØÙÙaÀ€ptt„TUU%:“ÖÖÖHLLÄ7ðæ›o ‡±;;;ØÚÚ"..Nè8Œ1¦¸þ`ÊŽëùÀ­Rd‡šÐ^—ªª*ààà€… ž>}Š›7o"==™™™ÈÈȨ³çñãÇ(++«ó8ZZZÐÖÖ®³¢ÇÂÂ]ºt……ºwïuuu!~D&víÚAvv6îܹƒ+W® ((?ýôS³Ç“7µ×I œ„1Æ”×\4T4õ÷!o¸þoæææxÿý÷qèÐ!âÊ•+زe Þ{ï=X[[ƒˆÇã‡~À’%Kàåå…±cÇ¢ÿþ°±±A×®]ëÜlllпŒ;^^^X²d ~øá=z "ØØØ`Ú´iزe ®]»†ÂÂB:tï¿ÿ¾â­Ìù7¡g•“U+V¬ /œ-R—/_~áÌPrr2 üü|ñ×"""hðàÁ¯|œ³gÏ’ªªj³Æ#"zã7(22ò¥ßsãÆ @¥¥¥â¯EGG“ššZ³ÇkÊñ•••¤££CgΜíÿû 0àµÇ“ëÖ­#/œ!dŒ1&=\pýÑØú£Vcò‚ëÅVQQAqqqtèÐ!ñŠ›éÓ§“««+õë׬­­©K—.unÖÖÖÔ¯_?ruu¥éÓ§‹Wø9r„âãã©¢¢BèKPrßS‡1i155ܽ{Wà$-‹žëNDõ6¨‹ŒŒ„ƒƒC³Æ©ÝÙ#&&sçÎEII œ±mÛ6èëë£{÷îèÝ»7~øá|üñÇxòä vìØ÷Þ{¯Yã5UFFJKKÅgî W¯^X³fM‹Œ/ j_ûµ Œ1Ƥë®?šZ¼ÎïCqý¡ØÔÕÕakk [[[¡£( ¾üбW¨]¦WÛ)]ÑYZZÂÒÒ«W¯FYYòóóáëë‹ÒÒÒ:…V­Ã‡cãÆðóókÖxµËÊoݺ…ÄÄD$''#''óçϨ©©áàÁƒØ»w/´µµa``€{÷îÁßß¿ù?d”––´µµÅ_ÓÑÑyåïCeddx¶M%cŒ±–Áõ×@ãë×ý}È"®?kžÔaìºtéHOO8IËPQQÁ‘#G’’Œ1ŽŽŽÐ×ׇH$ªslHHfÍš…ƒ¢OŸ>ÍOGG°~ýzhiiÁÈÈŸþ9Ž;àÙ.v£G†§§'JKKQPP€:´Ø5ãµù?~,þZii)ttt^ø}(ªÚ 2cŒÉ®?¸þWHâ÷!‹¸þ`¬ixR‡±WP¶3e`ee…ÈÈH">>::::thc0wî\9r£FjöXÐÕÕ­S ˆD"ñY¨””¤¤¤`É’%ÐÔÔ„¾¾>|||ÄE—´YXX@GG§Îòïë×¯ÃÆÆ¦EÆ!33"‘ˆ‹*ÆkA\pýјúCR¿YÃõcMÇ“:Œ½‚––:v숲²2äææ GbÊËËñôéS@EEÊËËÅ÷%%%‰Ï 9s›6mÂÊ•+Å÷oٲ˖-Cxx8† òZ㩪ªbΜ9Xµj***PXXˆ 6`„ 333hkkcÛ¶m¨¬¬DII vîÜÙàõ·õý|M9^MM ^^^øúë¯QRR‚;wî`Ë–-˜3gÎk'/rrrPQQhhh‡1Æ”×\4T4ç÷!/¸þ`¬„éÏ̘|prr"têÔ)¡£H €nµ|}}ÉÐÐttt¨oß¾túôé¾WMM´µµëܪªªˆˆèÚµkuþÝÐxåååäííM†††¤««KS¦L¡‚‚ñý'Ož$ÒÒÒ"mmm5j%%%‰ïoêxM=þñãÇäååEºººdddôÒ]êû~yvòäI@£G: cŒ)®?¸þ¨¯þhè÷!ϸþ`¬éDDJÒñ“±fX²d ~øálÞ¼‹/:c-fãÆøøã±téRüïÿ:cŒ)®?˜²âúƒ±¦ã˯«‡Ô»­&cŠ(>>x»IÆ×LYqýÁXÓñ¤cõxë­·±±±'a¬eÕ~¨ý`Ác¬åpýÁ”׌5_~ÅX=ÊÊÊ ««‹V­Z¡¸¸jjjBGbLêž>} ]]]Š‹‹¹Q!cŒµ0®?˜2âúƒ±æá•:ŒÕCKK ööö(++CLLŒÐqk×®]CEEz÷îÍcŒ €ë¦Œ¸þ`¬y”~R§¸¸………(((@AA Q\\,t,&C ¸pá‚ÀIkçÏŸ lذ‰‰‰KKKøøø`æÌ™0008aÓäçç#(([·nEFF`È!X±b\]]NÇküøñ8~ü8Ž;†qãÆ ‡1‰;zô(ÜÜÜ0aÂ9rDè8Œ1ÆÀõS|\(ŽúZ¥ØÚÚJ´UJ}^Õ*¥vêCÑZ¥Hç·ØLD„àà`ØØØ`ÆŒHLL„££#>Œääd|ôÑGr7¡<»üê£>BJJ vïÞž={âܹs;v,úô郃BçÖØ¿¼óÎ;€œ„1é¨}m×¾ÖcŒ ë¦è¸þIIIXºt)LLL0uêT}àíí¢¢"¡#²—Ð××ÇèÑ£QXXˆˆˆ¡ã0&Q'OžDQQÆŒÃcŒÉ®?˜"ãúCþÔÔÔ ((666?~ضmúõë‡ââbìß¿_èxŒIDpp0JJJ0yòd.¨cLéêêÂÃÃë¦PjëáÇóçÅ­Rä£UJ‹Nêøûû£ÿþHNNF¯^½påÊxzz¶d¹0cÆ \½z½zõBRRú÷ïÝ»w KiTTT`ÿþý7nÌÌÌðÙgŸ!11íÛ·ÇG}„÷ßðÓO? œ”1ÉøùçŸsçÎ8 cŒ±W™7o®?˜â¨­?rrr0hÐ ¼ùæ›Ø´iîß¿/p2p«yj•Ò"—_•——cîܹطo`Á‚øþûïå~?xi+//ÇâÅ‹áïïàÙd¿¿?ÿÞ¤äâŋسgöïß‚‚Ï:¸;3gÎÄØ±cÅ;NX[[#117nÜÀ›o¾)dlÆ^KBBlmmaccƒøøx¡ã0Æ«×LQ<_Œ?¸wï@]]îîî˜={6œ¥ºõ5{·Jy9Yn•"õ¿ââb¸¸¸`ß¾}ÐÕÕEpp0¶nÝÊ ©©‰;wâ?þ€®®.áêêŠââb¡£)ŒÚË«zöì‰AƒaçÎ(((€ƒƒ¶nÝŠœœ8pîîîu¶­]Ñðã? 1‰¨} ÿç?ÿ8 cŒ±†pýÁÅóõ‡¯¯/îܹƒC‡aܸq¨®®Æþýû1vìXXXX`õêÕÈÊÊ8±ràV)¯&Ë­R¤ºR'//®®®¸~ý:LMMqòäI>«ÐL 3f rssÑ»wo„††¢C‡BÇ’Keee8pàöíÛ‡“'O¢ºº`llŒ÷Þ{³gφ­­m½QTT„Î;x6k«¯¯/õÜŒIZ~~>ÌÌÌ ªªŠ;wîðV¢Œ1&ã¸þ`Š ¡ú#''@ZZ€g}Nœœœàíí 777^ þþþøè£ðäÉôêÕ ÁÁÁ°´´:–Lºuë&OžŒëׯ£uëÖðóóìY³Ë#µ•:©©©>>¤§§'®óutthÖ¬Ytá ¦Ul¹¹¹Ô«W/@¦¦¦tãÆ ¡#É­øøxêØ±# Þ½{S^^^‹Ž/Ñ•:<À Aƒšš '''œ8q‚ÏHÉÓ§OáêêŠÓ§O£[·n¸páÚµk't,ÁΟ?   üñÇxôè€g»W¹¹¹aÖ¬Ypvv†ªªªDÇýûï¿1`À˜™™!%%…¯ïer¡¬¬ –––ÈÉÉÁÕ«Waoo/t$ÆcMÀõ“G’®?ÊÊÊðçŸâçŸƹsçPû±ÖÊÊ óæÍÃôéÓѾ}{IDW8©©©pvvFzz:¬¬¬†7ÞxCèXr-33ÎÎÎHIIA·nÝŽ.]º´Ìà’š***ÏôõïߟWè´€¢¢"êÛ·¯øwþèÑ#¡#µ¸Û·oÓW_}E]»vÏÔ ÒÎ;©  @ê\]] mݺUêc1& 7n$4nÜ8¡£0Æk&ggg®?˜\©­?&L˜ ñÇNII¡åË—“‰‰‰øó@«V­èÝwߥãÇSUU•ÄÇ”Wqqqâ+kú÷ïOùùùBGR÷ïß>766¦øøøW"+uxÕˆp”quÔãÇqèÐ!ìÞ½§OŸFMM €gMÕ¼¼¼0kÖ,XYYµXžË—/£ÿþèØ±#RSS¡¥¥Õbc3ÖT?F×®]qÿþ}ôíÛ:uÂÚµk¹7cŒÉ‰„„¬Y³YYY¸zõ*×L.<_\»v ½zõ’Ê8••• Å®]»püøqñ.·fff˜9s&æÌ™sss©Œ-RSSáèèˆ{÷îÁÙÙ!!!ÐÖÖ:–B)--ŤI“pòäItêÔ gΜA·nݤ;¨$f†æÌ™#îïrûömI<$k‚[·n‰¯áóöö:ŽTÔÔÔPTTÍš5‹tttÄ3ðZZZäééI'NœtÃܬƒ IDATÞÝÝзß~+XÆcýúõ€\]]IKK‹ŠŠ M›6nÞ¼)t<Æc¯póæMš6mš¸_ ––=šë&jëwß}·ÅƼ{÷.ùúúÖYѯ¢¢B£F¢ßÿÊËË[,‹,¸ÿ>uëÖ““UTTIaUTT““ nݺÑýû÷¥:ÞkOꊛSÅÅÅI"k†¸¸8jݺ5 _ýUè8ÓÐåUEEEBG$"¢ëׯ“ŠŠ éêêJý–±æÊÍÍ%RUU¥„„ÊÉÉ¡… ’ºº: 555š={6edd•1ÆØÿÉÈÈ Ù³g“šš uuuZ¸p!åääpýÁä¿ë–VSSCýõyzzŠOh Z¼x±R|†åV)-¯%[¥¼Ö¤NRRikkÚ»w¯¤2±fÚ½{7 6mÚÈõ÷’’  ¡C‡’H$¿ñš™™Ñ_|!ÑÝ«$iæÌ™€æÍ›'tÆ^ªö5:wîÜ:_¿}û6Í›7¯ÎÊÎÎ()cŒ±ììlòññ©3ñ>oÞ¼VÅsýÁdÝ«ê!ÒÖ­[©wïÞuN÷ë×O¦NKRK¯aÿ_K­Žjö¤ÎãÇÉÖÖVfþ@Ù33fÌ Ô«W/*++:N£5tyUDDUWW ³^ÏŸ…ˆ‰‰:cu\¾|¹Á³¹©©©äååUgiÿ’%KèÞ½{-œ–1Æ”×½{÷hÉ’%u.‘õòò¢ÔÔÔ—Ïõ“e©?„råÊúðÃ_º5ú¹s焎'1Ü*EX-Ñ*¥Ù“:sçÎ%dkkK?–d&ö?~L={ö$ôþûï §A©©©´jÕ*²°°¿™ŠD":t¨\Ζÿ÷¿ÿ%4tèPª©©:cDDT]]M ôý÷ß7x|bb"yxxˆ'wtttè³Ï>£‡¶@ZÆSN>¤Ï>ûL|rK$‘‡‡%&&6ø½\0YÔÔúC(Ož<¡½{÷¾p•€••}ûí·r}r‹[¥Èi·Ji֤Ή'ikkËì¥0Ê,!!Aü¢‰ˆˆ:Π껼jÕªU¯<%ÊÊÊÄTþþþBÇaŒˆˆüüüYZZ6iÙgll,¹¹¹‰ÿNõôôèË/¿”ê5ÁŒ1¦l=zD_~ù¥xµ€H$"777ŠmôcpýÁdQsë!¥¦¦¾tkô‰'ÊÝÖèÜ*E¶H³UJ“'uÊÊÊÈÒÒ’ÐO?ý$Ñ0LrvìØA¨G2ÑÙ½ººš"""ÈËË«ÎåU:::4}út¹¸¼ª±j'=Û¶mKwïÞ:SrwîÜ!]]]‰DÍžäýçŸÈÙÙ¹NcA___*))‘pZÆS%%%äëëKâ÷Wgggú矚õx\0Y"‰úCHUUUtèÐ!rww÷¬½„iÕªU”––&tÄzq«Ù$­V)MžÔùòË/ 4ˆ—wʰêêjêß¿? õë× –£öò*33³.¯ PØ…žžž€Þyç¡£0%çææFhÖ¬Y¯ýXçΣaƉÿ–Û·oO›6m¢'OžH )cŒ)‡'OžÐ¦M›¨}ûöâ÷ÓaÆI¤‡×LVH²þÚÝ»wiÆ ↷Ïoþ믿ÊÄ ôãV)²IZ­Rš4©“ššJššš¤¦¦Fׯ_—X&W¯^%UUUjݺ5¥§§·Ø¸EEEäïïÿÂåUæææryUc=xð€ŒŒŒ ‡)©ß~ûM<ù"É~8âkä©©)mß¾]n–V3Ƙ***hûöídjj*~ÿ0`€DW1pýÁd´ê¡Õnì2}úô¶F_¸p¡Ìô¬áV)²M­Rš4©3vìX@K–,‘ÈàLú,X@h„ R§öò*OOÏ:orµ䣢¢”neWmc2Þšµ¸ÌÌLÒ××'ôûï¿KeŒ£G’½½}‰Û]»vQee¥TÆcŒ1yTYYI»ví"sssñû¥½½==zT*ãqýÁ„Ôõ‡,(**"???rpp¨³5zŸ>}Ýì…[¥ÈI·Jiô¤Nhh( n’)G ÉØØ˜ÐÉ“'%þøIII´råJ¥»¼ª±Þ}÷]@#GŽT˜žALöUUU‰/“š2eŠTǪ©©¡ýû÷“µµµø= {÷î´oß>~Í3Æ”Zuu5íÛ·ºwï.~´¶¶¦ýû÷KýD×L-YÈ’k׮т Ä“Y¨uëÖâÛ-‰[¥ÈI·JiÔ¤NMM õéÓ‡;gË©ÚNÛ ÈãÑÎ;iàÀuf¦»víJ_}õeffJdEððáCñ2ëo¿ýVè8LI¬_¿žÐo¼A………-2fuu5‰Ï  ᢂ1¦Tjjj($$„lllÄ––Ôb,\0!QÈ’Ú­ÑGŒñÂÖè6lúÖèÜ*E¾H²UJ£&u:DÈÎÎŽgûåPuuµø,ú±cÇšõUUUÆ—W5éS§HEE…ÔÕÕéÒ¥KBÇa .::šZµjEªªª-~vˆèå—8884û½‡1ÆäɱcÇê\Ž!äe©\°–$tý!kRSSéóÏ?aktwww©mέRä¤Z¥4jR§¶!æÁƒ_k0&œàààf­ÖIJJ¢åË—×yCªíö¾wï^¥¿¼ª±>ýôSñ6ˆ÷ïß:SP¹¹¹â¿Õ•+W š¥%‚2Ƙ¬Õò\°– Kõ‡¬©ªª¢£G’»»;µjÕJüabbB+W®”Ø2Ü*E>IªUJƒ“:ÑÑÑâ½Ôy%†üª©©¡7ß|“ÐÅ‹ë=¶°°°ÞË«nß¾ÝB©Çó×1B*³óL¹=}ú”† BhôèÑ2ó“æÖ½Œ1&´sçΉÿ¯ÝígÓ¦MôäÉ¡£×LúdµþE999´aÆ:}¶D"9’öîÝKeeeÍz\n•"ß$Ñ*¥ÁIwww@{öìiö L6Ô¾`<<<^¸¯ªªŠŽ?N¤¡¡!~£ÑÓÓ#ooo:{ö,O꽦{÷îQ§N-]ºTè8LÁ,Y²„™™™Lž-))!___200¿¿8;;Ó?ÿü#t4Æk²þù‡œëliìëë+“+˜¹þ`Ò$ëõ‡,ªÝ}ÆŒ/l¾`Á‚&÷ÃáV)òM­RêÔÉÊÊ"555êØ±£àËGÙ뫨¨ Ž;’šššx‹Ë†.¯’•3MŠââÅ‹¤®®Nè—_~:SÛ·o'¤¡¡A—/_:N½ŠŠŠhÍš5¤««+>CåææF±±±BGcŒ±ÅÆÆ’›››¸ ª®®.­Y³F°í‹‹ë& òTȪ¢¢"Ú±cÇ [£;88ÐŽ;õÞ­Rä_s[¥ÔªwR§vK´5kÖ4ëÁ™ìYµj ?üP¼LïùÎìëׯçË«¤Ìßߟºº::uJè8LÎ………‘ªª*‰D"¹Zr›ŸŸOË—/'mmmñdòäÉ“)))IèhŒ1ö‚¤¤$š§IIIÐÒÒ8r‰Dصkœœœ#FàÞ½{BÇbr"''£FBvv6œœœ°k×.ˆD"¡c½SSSlß¾7oÞÄìÙ³ADð÷÷G·nݰhÑ"äææ ‘1¦rss±hÑ"tëÖ þþþ "Ìž=7oÞÄöíÛajj*tÄ×Âõ{ŠXȺ®]»bݺuÈÊÊÂñãÇáîî8|ø0üýýQUUooo¨«« œ”½.uuuÌ;UUUصkW“¾WDDôï/VVVÂØØ%%%ÈË˃ÄÂ2áåçç£cÇŽhÛ¶-rss¡¦¦&t$¥õèÑ#899áÚµk°µµÅéÓ§add$t,&çµÏqc¼ôò«ž={"99III°²²’\B&3âããagg[[[ÄÅÅ ‡ÈÊʨQ£pëÖ-X[[#22ÆÆÆBÇb2 77£FBbb"zô興¥=+sá¬^½§NðlåáòåËñá‡r0ÆXƒÊÊÊàçç‡o¾ùùùù€‘#G⫯¾Â AƒN' ®?Ø«pý!›¸UŠbkN«”Vêܽ{ÉÉÉ055å fkk‹öíÛ#>>÷ïß:`ff†³gÏÂÚÚ7nÜÀСC‘••%t,&°ÌÌL 2‰‰‰°µµÅ_ý¥ÔÕ Aƒ‰Ó§OcðàÁÈÏÏÇ'Ÿ|‚®]»bëÖ­xúô©Ðc2èéӧغu+ºvíŠO>ùùùùÈÈH¥Ð¸þ`/Çõ‡ìâV)Š­9­R^˜Ô¹xñ"`àÀ’MÇdÎàÁƒçÏŸ8 «ellŒ¨¨(ôîÝ·nÝÂàÁƒ+t,&˜˜ <iiièÛ·/þúë/>{úFŒèèh„……¡oß¾âk,--ñÓO?¡ªªJ舌1PUU…Ÿ~ú –––âôúöí‹°°0DGGcĈBG” \°çqý!Û¸UŠâkj«”&u¢££ŽŽŽŒÅdQís\ûœ3Ù`hhˆÓ§OcøðáÈÎÎÆ!C¸÷‘ ÅСC‘““ƒ‘#G"22’ÏÆ¼„³³3þþûo>|vvvÈÊÊ‚··7zôèÀÀ@TWW ‘1&€êêj¢GðööFVVìììpøðaüý÷ßpvv:¢Ìáúƒ\ȃãÇÆ'p&-®®®€'N4êø&uþùç@¿~ý$‹É¢¾}û._¾,pöozzz‡——JJJ0aÂøûû ‹µ;vÀÍÍ ¥¥¥˜5kBCC¡««+t,™%‰àææ†˜˜üñǰ²²Bzz:fΜ £¦¦F蘌±PSSƒàà`ØØØ`æÌ™HOO‡••þøãÄÄÄÀÍÍ "‘Hè˜2‹ëåÆõ‡ìãV)Ê¡©­RêLê ‰ä²ëLL Þyç´k×ÚÚÚ077ÇÂ… ‘ŸŸ+W®@$AGG§ÎmÖ¬Yõ>æÇŒž={B[[xï½÷——×2?”½õÖ[‰DˆÅKúe3©««cÏž=X½z5ªªª0þ|,Z´•••BGcRòôéS|øá‡øàƒPSSƒµk×b×®]PWW:š\PQQÁäÉ“‘€={ö K—.HNNÆ”)S`oo#GŽð{c ŠˆpäÈØÛÛcÊ”)HNNF—.]°gÏ$$$`òäÉPQy馯ì_¸þP>\Èn•¢<šÒ*¥Îÿniii())A×®]ѦM餓’³gÏbøðáprrBzz:?~Œ³gÏBGGW¯^WTT„ÒÒRñm÷îÝõ>®ŽŽ‚ƒƒQZZŠ”””——+ÌÖqººº077Gqq1233…ŽÃ^B$aíڵسg455±uëV899!77WèhLÂîÞ½‹áÇcûöíÐÒÒBPPV¯^Íg”›AUU3fÌ@rr2vî܉Î;#..îîîèß¿?ÂÃÃ…ŽÈ“ ððpôïßîî‹CçαsçN$''cÆŒPUU:¢ÜáúCypý!_¸UŠòhR«zΑ#GM˜0ä}óÍ7¯¼ÿòåË€*++_kœ³gÏ’ªªêk=†,7n cÇŽ …5àÊ•+ôÆoêØ±#={VèHLBΜ9C:t daaA111BGR(åååôÃ?±±1 äèèHýõ—ÐÑc¯á¯¿þ"GGGñßµ±±1ýðÃT^^.t4…Âõ‡ââúCþ 8Ð… „ŽÂ¤ììÙ³€† Òà±uVêddd,,,$2»ÔRrrr‡©S§J}¬ÈÈH888H}œ–bnn¼RG888àêÕ«=z4rssáä䄯¾úŠÁʱªª*¬^½£F½{÷àêêŠ+W® W¯^BGS(X´hÒÒÒðÍ7ßÀÈÈÑÑÑ>|8FK—. ‘1Ö—.]ÂèÑ£1|øpDGGÃÈÈß|ó ÒÒÒ°hÑ"hhhQ¡pý¡x¸þO$ç­RXÓ4¥UJIôôt@—.]¤—N ŠŠŠÄ· 4jŒÌš5 DŸ>}^+¯,©]•U»J‹É>UUU|ùå—ˆŠŠ‚¹¹9Ο?·Þz BGc{{{\ºt ]»vŹsçðÅ_pÏ¢££ƒ•+W"##+W®„ŽŽŽ;Lš4 BGdŒ='!!“&M‚ƒƒŽ;öÒ¿a&}\È?®?ä[RR gÏž'a-åÍ7ß$&&Ö{\¿à‡x6/OLLL`gg‡ß~ûM*€¹sçâÈ‘#5j”TÆJís]ûÜ3ù1hÐ ÄÆÆbúôé(..ÆÌ™3áââÂgÍdXzz:ÆŒƒ9s戟³˜˜ 0@èhJéù³üË–-ƒ¦¦&8€·Þz žžžHII:"cJ-%%žžžxë­·pàÀhjjbÙ²euVÛ±–Çõ‡üáúC1Èk«”—‘Ʈյªªª`oo‘H„ªª*éþ RÖØ«jêLêÔ^ÆT»òEžlݺÿýïáç燒’ÏV}öÙg¯µÓÉ–-[°lÙ2„‡‡cÈ!’Š+3ŒŒŒ<[ÁÄä®®.‚‚‚pðàA˜˜˜ <<666ؼy3jjj„ŽÇþOUUþ÷¿ÿÁÖÖ055ÅáDZ{÷n^>+ŒŒŒðÝwß!-- .„šš~ýõWX[[cΜ9üA…±–™™‰9sæÀÚÚ¿þú+ÔÔÔ°páB¤¥¥á»ï¾×.L8\È®?‹¼¶Jù7iíZ]kóæÍ 3éßèV)ÏwM®í~ž——'¥ÎÒuõêUzûí·ÉÐÐZ·nMæææ´hÑ"ÊÏÏï~¥­­]ç6zôèz©©©½ð}UUU-ôSIWnn®xÇ&ßŠŠŠÈÛÛ›D" {{{îŒ/Μ9Cööö€TTTèƒ> G ‹ÕãöíÛ4oÞ>>”-t4ÆZvv6ùøøººº¸þš7oݾ}[èh¬\È&®?Ïĉ 8p@è(¯Eš»VgffR=èÌ™3ÙùZhþù' I“&Õ{\I===@EEER ÇdGQQ ===¡£0 ‰ŠŠ¢7ß|“H$¢÷Þ{ bܾ}›Þ}÷]ñV»vvvtîÜ9¡c±&HMM%///RQQ!¤¥¥EK–,¡{÷î 1…rïÞ=Z²d iii‰?€zyyQjjªÐÑXpý!¸þP\C‡%%t”f»{÷.¨÷½áu&uÆOÁÁÁ¯õ²¤vrjøðáõWçò«ÊÊJ@«V­š¹@ˆÉUUUàm)ÈСC ???â·ß~ƒ••V®\‰¢¢"¡ã)¼ÂÂB|ñŰ²²ÂŸþ ###øùùáêÕ«ptt:k‚®]»"00 ððð@yy96oÞŒ®]»bÅŠ(((:"cr­  +V¬@×®]±yóf”——ÃÃà  D×®]…ŽÈš€ëaqý¡øä¹UJ-iîZý矢²²É* Ý*åùüߌ.S.ü¼+®ÂÂBúðéU«V€ôõõiݺuTRR"t4…óèÑ#Z»v-µmÛV|ÉÎÇL………BGcKnnnâK ôôôèË/¿äåìŒ5Ñ£GèË/¿¯‰DäææF±±±BGcÂõGËáúCyÈ{«"é­Ô)..¦.]ºÐ­[·šý²¨±­RxR‡ñó®ÒÒÒhÆŒ¤ªªJÈÈȈ6lØÀF%àÑ£GäëëK†††â³fÍ¢ôôt¡£1)ùçŸÈÙÙYüÞi``@¾¾¾üa…±”””¯¯/ˆÿ~œéŸþ:“®?¤‡ëå£(­RìììhÆ ¯¼¿92—/_&555244$CCCñïÊÐÐP®{5¶UJOòmÚ´!T\\,ÕpLvpOå’œœLS§N÷ÑÕÕ¥eË–QVV–ÐÑäNVV-[¶ŒtuuÅ= ¦M›F7oÞ:k!çΣaƉ?œ¶oßž6mÚDOž<:c2åÉ“'´iÓ&jß¾½øïeذaÜçC‰pý!9\(¯Ö­[züø±ÐQ^KTTéêêÒ¶mÛÄó·oߦåË—SXXX³&už>}JwîÜߎ?N(33S®ë²’’@:::õÇ’•Oê(§äädòöö&MMM@­Zµ"OOOŠŽŽ:šÌ;wîyzzŠ—”kjj’··7%''  $""‚  þ°jjjJÛ·o§ŠŠ ¡£1&¨ŠŠ Ú¾};™ššŠÿ> @BGcáú£ù¸þ`Štu…4v­~ž¢\~EÔ¸ç]ôLMM‘““ƒ»wïÂÄĤ¡¾=Ldgg£sçÎèÔ©îܹ#tÖÂîß¿???lß¾]Ü€«gÏž˜7ofΜ ʆüü|ÁßßÉÉÉž5.ûàƒðá‡6ªÙS|ÇŽÃêÕ«077ÇêÕ«áåå555Ó1Örªªª„¯¾ú ™™™{{{|õÕW?~¼°á˜Làú£q¸þ`ωD€ç>¾3%Шçýùž·Þz‹Ðõë×¥6Óô¼ììlruu%}}ý—Τý{v®U«VÔ¦MñýK—.%+++jݺ5éëëÓÔ©S)77·ÙãíÞ½›¬¬¬HGG‡:uêD|åã5uü¦ß®]»FÈÞÞ^ÐLXOž<¡€€ú¨N#KMMMrww§€€Á'"¥)77—ÈÍÍ444Ä?¿‘‘}ôÑG”””$tD&'ª««)((ˆ,--ů# á·LáÔÔÔPHHÙØØˆ_ï–––Ä“™¬Ñ¸þàúƒÕ[¥(ŸfMêL:•Ðo¿ý&ÕpÿÖ˜kÞÒÒÒH$ÑÕ«W_yÌÙ³gIUUµÙã½ñÆÙøàÍ¿¹ÇKþ}ûM›6MÐLöTTTÐÑ£GÉËËKÜŒÿ·õ¬½½=­X±‚¢¢¢èéÓ§BGm¶òòrŠŠŠ¢+V½½½x«êÚ7Ï3fÐñãǹ7 k¶ÊÊJÚµk™››‹_[tìØ1¡£1&ÇŽ#ñëÛÜÜœvíÚ¥} ˜0¸þàúƒ½œ‰‰  »wï …µ;wîêÔ©S½ÇÕ¹ÈßÜÜÄ×?Ë’Ÿþ½{÷FïÞ½_yLdd$šõøYYY¸}û6bbb0wî\”””ÀÙÙÛ¶mƒ¾¾~££©ã¿N^I©}®kŸ{Æj©««cüøñ?~<ÊËËŽC‡!,, 111ˆ‰‰¯¯/´´´Ð§O8::bðàÁ8p Ì^ ÿðáC\ºt çÎÃ… pùòe”——‹ï766†‹‹ &Nœggghhh˜–)555Ìž=žžžøå—_°~ýz\½zãÇÇ€°nÝ:Œ5J蘌5Ydd$V­Z…K—.xÖ—ñ‹/¾Àþ󨫫 œŽÉ3®?¸þ`/×®];äääàÁƒÜÿVIr1©SUU…€€¬]»ö•Ç>|7nÄ™3gš5FAAàÖ­[HLLÄãÇáááùóç#88¸Áïoêø¯›WRxR‡5†¦¦&ÜÝÝáîî"Âõë׆°°0\¼xçÎùsçÄÇwîܶ¶¶°µµ…zöì ´mÛ¶Eò"33‰‰‰ˆG\\^h®¡¡¡C‡ÂÅÅ...èÕ«—¸c’¤®®Ìš5 ;w¯/.]º„Ñ£Gcذaøúë¯áèè(tLÆ•+W"** о}{¬X±óçχ–––À阢áúƒ±ÿÏÐÐÀ³IB¦jŸëÚçþUêLêXXX222¤«yŽ=ŠÒÒRL›6í¥÷‡„„`îܹ8xð úôéÓ¬1tttëׯ‡––´´´ðùçŸÃÝݽÁïmêø’È+)ééé€.]ºšƒÉ‘H{{{ØÛÛcÅŠ(++Õ+WóçÏãâÅ‹¸sçîܹƒÐÐÐ:ß«¯¯sss˜››£S§N044ßÚµk'^§¥¥MMÍ:ß[^^޲²2ÏÞàþ}ËÎÎFff&222PTTôÒ솆†0`† ‚Aƒ¡oß¾/ŒÃ˜4iiiaÉ’%˜;w.¶mÛ†ï¾ûQQQ2dœ±nÝ:ôíÛW蘌½àòåËXµjÂÃÃøä“O°`Áq Ř4qýÁ”]íng÷ïß8 k)µÏuƒ;Ý=-Vnn. ccc)^ö¢†z길¸Ð¼yó^zß®]»¨mÛ¶töìÙׯªªŠtuu)??_üµˆˆÒÔÔ¬÷±š:~sòJSûöí Ý»wOè(LdeeÑñãÇiÆ 4mÚ4²··§¶mÛŠ¯—öM__ŸìííÉÓÓ“6lØ@¡¡¡”••%ô¯…±Ñš5kÄ}#D"¹¹¹Qll¬ÐÑ#"¢ØØXrss÷üÐÕÕ¥5kÖp£N&“¸þ`Šì³Ï>#äëëÛâcËû®Õµ*++©W¯^ öó••]«×¯_OèóÏ?¯÷¸:+uŒÑ®];äååáÁƒ ^»% åååxúô) ¢¢UUUuf®³²²pòäIüý÷ß/|ï–-[°víZ„‡‡£_¿~¯5žªª*æÌ™ƒU«VaÓ¦Mxòä 6lØ€ &¼ò±š:~sòJS^^îß¿ccã†gÿk‚Î;£sçÎ;vl¯×.KÎÌÌDvvv3]<@aa! ¬¬ åååuV’ijjŠ—ö?†­öÖ©S'˜››·è2kÆ^—žž¾üòK,\¸ß}÷¶mÛ†#GŽàرcx÷Ýw±víZXYY “)¡ääd¬Y³þù'jjj ­­ à“O>ip8cBáúƒ)2![¥¨¨¨`âĉ˜6m¼¼¼^¸¿´´´Î¿G…nݺ‰ÿ­££ƒàà`ØØØàáǘ7o¦L™"¾”·©ãá»ï¾CHHÞ|óMïÏû-""üp«{tùÔê™3gþèç\>zÉ’%7tïŸZŸŸÏ—_~Ihhè~͆ ¨¬¬dâĉu¾ÞÍæ½QuÙ*åªC777öìÙCuuuƒ…cUVV²wï^š5k¦•:""vÊ××—¿ýío|úé§L:•ªª*^{í5ºvíJDDEEEFGUTTDDD]»våµ×^£²²’©S§òé§Ÿò·¿ýíÚÏH£³÷Õ:×sjõ“O>Y¯§Vûøøðûßÿž­[·^õóÏŸ?Ï‚ HNN®óµê#ïºüw|ùïü§ü`¨ãííÍ]wÝÅÙ³gùè£ê?Ø…ýë_”––r÷Ýwë(R;×£GÒÒÒ8xð ãǧ¼¼œW^y….]º`2™~ô]‘ï+))Ád2Ñ¥K^yåÊËË?~<$--=zQDD~Dco•RWË–-ã±Ç»êûË+VðÔSO±yófxà¾F—.]hÕª...µsqqùÁ6.—}þùç?~œAƒáããS{íöíÛ³iÓ¦½N}å½QuÙ*åC€‡z€÷Þ{¯c‰=y÷Ýw~p:€ˆˆØ¯Þ½{óöÛo“••ÅèÑ£)--%66–.]º{]'?HÓtµï•Ñ£G“••ÅÛo¿MïÞ½Ž(""×`äV)ß?Eº¼¼üŠß¿|jõÕ½úóŸÿ̼yóÈÈÈ`ذa7u½ïžZ}éÒ%þûßÿþä©Õ}ûö娱cää䓓êU«ÈÎÎfÔ¨QWýšÉ[ßê²UŠ‹í*#­ììlȀؿý'à 0€?þ˜ìììŸÜÄJÄH—'ð?6yiêöìÙCtt4|ð>>>,X°€Y³fÕÁ+M[YYK–,!>>¾öq½û￟˜˜˜Ú7"r%õ±WgÏž¥mÛ¶xyyqæÌ™F=pç»+c.ûî#f³™-[¶\õ#ÜÝÝiÞ¼ù?{ö,nnn8p€aÆÕþóµ®wéÒ%fÏžÍÛo¿Mee%=ôK—.­=äèj¯wÙþýû ¤²²ww÷«~þµò6´ÊÊJÚ´iÃ¥K—(..¾æ“5WêØl6üüüøâ‹/8zô¨ž«v2GŽ¡[·ntíڕÇGäG©T‰\Ÿ;wb2™jïêtèÐÈÈHBCCñðð08¡¢¢‚””âââj7Ö¾çž{°Z­Ü{ï½§±oêbφ ÂÞ½{Ù³göFuR™™™ 6Œ¡C‡²{÷îk~þU¿rqqaÒ¤IØl6Ö­[Wï!ÅX—ÿN'Mšdp©÷Þ{/™™™lÛ¶ÀÀÀ+N4zýõשªª2:¢4’ªª*^ýuºwï^{RZ`` Û¶m#33S§­Rœ_]·J¹êP`òäɬ]»¶b‰=Y³f S¦L18‰ˆˆÔ§‘#Gò¯ý‹¿ÿýïpüøqfΜIÏž=Y¹r¥NµtbÕÕÕ¬\¹’ž={2sæLŽ?N@@ÿûßù׿þÅÈ‘#Ž(""õàòýËoüÅùddd\÷Ïî«>~uYÿþýÉÉÉáÀôëׯ~Š¡.?C8pà@²²²ŒŽ#ò“´üYäÆÕÔÔ°aÃÌf3ùùùøûûc±X ÂÕõGïëˆÑß³HýSÿ{¦­RœÛl•ò“?é/ï\½lÙ²›O'vaùòå̘1Ãà$""Ò\]]™4iyyy¼ùæ›øùù‘ŸŸÏäÉ“éß¿?›7oÖf³Ùؼy3ýû÷gòäÉäççãççÇ›o¾I^^“&MÒ@GDÄ i«çv#[¥üäJ³gÏò‹_ü›ÍFaa!>>>7ŸR SRRB§N8yò$­Zµ28‘ÈOÓ2‘úSYYÉŠ+ˆåĉbµZõhŽƒÉÈÈÀd2Õ®¸íÔ©QQQL›6fÍšœNÄñ©ˆ½ûøã0`wÞy'ÙÙÙFÇ‘zÔ·o_rss9xð ×õ5?y ÇÛÛ›'žx‚ .˜˜X/!Å8ÉÉÉ”––2mÚ4 tDDš˜fÍš1sæL:Ä«¯¾JûöíÉÊÊbÔ¨Q 6Œ]»vQ®a×®] 6ŒQ£F‘••EûöíyõÕW9tè3gÎÔ@GD¤‰¸óÎ;éׯü1999FÇ‘z²ÿ~rss8pàutàC€ùóçãááÁ_þòJJJn*¤§¬¬Œ¿üå/¸ºº2{öl£ãˆˆˆAš7oÎìÙ³9räñññøøø™™Éˆ#øÍo~þ}ûŒŽ(ß³oß>~ó›ß0bÄ233ñññ!>>ž#GŽ0{ölš7ontDidÚ*ÅùÜèV)×êtîܙǜ’’þò—¿ÜX:1Ü_ÿúWN:EPPݺu3:Žˆˆ¬eË–ÌŸ?ŸcÇŽaµZiݺ5Û·ogðàÁŒ;–±É;pàcÇŽeðàÁlß¾Ö­[cµZ9zô(óçϧeË–FGƒL:///V®\IQQ‘Ñqä&•””ðÖ[oáååÅÔ©Sëôµ×µƒÞï~÷;ÜÜÜHLLäÂ… 7RŒsáÂâããqqq!22Òè8""bG¼¼¼ˆŠŠâرcDEEáååÅÖ­[0`&L //ÏèˆMN^^&L`À€lݺõG·Þz«ÑEDÄ`Ú*ŹÜÌV)×5ÔéÞ½;“&M¢¨¨ˆ?ÿùÏ7RŒ“˜˜È×_Í„ t4½ˆˆ\ÕåU ÇŽcÞ¼y´hÑ‚7Ò·o_‚ƒƒ)((0:¢Ó+(( 88˜¾}û²qãFZ´hÁ¼yó®XM%""r™¶Jq7»UÊuŸu‰««+/¿ü2_}õU/$Æ8yò$ñññ¸¹¹ñÒK/GDDìœ 9r„çŸwwwV­ZE¯^½˜>}:………FGt:………LŸ>^½z±jÕ*ÜÝÝyþùç9rä :}TDD®J[¥8‡›Ý*å'4ÿ¾§Ÿ~š7ÞxƒéÓ§×nâ#öí‰'ž`åÊ•<óÌ3,]ºÔè8"u¢#EEŒwüøqbccY±bUUUxxx0cÆ .\HÇŽŽçÐNž<É¢E‹X¾|9¸»»3mÚ4¢¢¢èܹ³ÑñDš,õq$‡âŽ;îàg?û………ÜrË-FG’:¸pá]»våôéÓ|üñÇ7ôdÍu¯Ô°Z­´jÕŠ+V°{÷î:_L×Î;IMM¥uëÖÄÄÄGDDPçÎY¶lùùù„„„PUUÅÒ¥KéÞ½;áááœ>}ÚèˆçôéÓ„‡‡Ó½{w–.]JUU!!!äçç³lÙ2 tDDäºi«ÇV[¥Ôi¥@RRáááôìÙ“œœZ´hqC–†UVVF@@‡æµ×^#,,ÌèH"u¦;e"öçßÿþ7f³™ 6`³Ùðòò",,ŒˆˆÚ´ict<»V\\LBBBífˆ...a±X¸ãŽ;ŒŽ'"ÿKýCÍ'Ÿ|B¿~ýðòòâóÏ?§}ûöFG’ëpòäIüýý)++ãàÁƒôêÕë†^§N+užþyÈ矮=Zì˜Édâðáà <˜çž{Îè8""â$î¸ãÖ­[GNN<ò.\àå—_ÆÏϋŹs猎hwÎ;‡ÅbÁÏÏ—_~™ .ðÈ#““úuë4Б›Ò§O¦OŸÎ¹sçX¸p¡Ñqä:ýþ÷¿§´´”§Ÿ~ú†:p+u<È]wÝEee%ï½÷#G޼áRÿÒÓÓ;v,Íš5#;;›Þ½{Iä†èN™ˆýËÊÊÂd2‘‘‘@›6mˆˆˆ ,, ///ƒÓ«´´”ääd(..`äÈ‘X­V N'"?FýCÑW_}EÏž=9þ<»víbذaFG’Ÿ°sçNî¿ÿ~¼½½)((à¶Ûn»áתóJ€¾}û²hÑ"l6!!!|ùå—7@ê׉'xâ‰'°Ùl$$$h #"" *00mÛ¶±{÷n†Nqq1‘‘‘tíÚ•¤¤$ÊÊÊŒŽØèÊÊÊHJJ¢k×®DFFR\\ÌðáÃÙ½{7Û¶mÓ@GDDê]ûöí±X,Øl6ž~úiÊËËŽ$?¢¬¬Œ™3gb³Ù°Z­75Ð\©ßN®ûÛß²eË~ýë_³cÇÜÜÜn*ŒÜœªª*FŒÁ‡~È£>ÊÆkï4ˆ8"Ý)q<Û·oÇd2±oß>:vìÈÂ… ™1c§kX,_¾œE‹qòäI „Õjå08ˆ\/õqTÕÕÕ 4ˆýû÷³`Á^~ùe£#ÉUÌ›7Å‹3xð`233qu½¡µ6µnx¨ßnø×¿Ž?ÎÂ… ‰½©0rs~÷»ß¯¯/ uëÖFG¹)*U"ŽkëÖ­DGGsàÀ|}}‰ŽŽ&$$wwwƒÓÕ¯ªª*RSS‰‰‰¡°°€þýûØ1cŒ '"u¦þ!ŽL[¥Ø·†Ø*å¦FBmÚ´aõêÕ4kÖŒ¸¸86oÞ|Ó䯬_¿ž?þñxxx°víZ tDDÄPcÆŒ!;;›õë×Ó«W/ ™>}:½zõbÕªUÔÔÔñ¦ÕÔÔ°jÕ*zõêÅôéÓ),,¤W¯^¬_¿žììl tDD¤Ñi«ûÕP[¥ÜÜ:`È!ÄÅÅQSSÃc=ÆŽ;ê#—ÔÁŽ;xüñDZÙlüñä®»î2:’ˆˆHí‘ݹ¹¹¤¦¦Ò½{w ¦oß¾lܸÑ!ï„Ûl66nÜHß¾} ¦  €îÝ»“ššJnn.AAAzüYDD 3wî\ÆŽË7ß|Ãc=FuuµÑ‘š¼ªª*{ì1Μ9ã>ÊóÏ?_o¯}S_}×sÏ=ÇÒ¥Kñööf÷îÝôéÓ§>^V®á“O>aذaœ={–^x¤¤$£#‰Ô-q.W{LiÀ€X,Fml¸ë”žžŽÙl&;;pîÇÊDš*õqÚ*ž4äV)õ6Ô©®®&$$„Õ«Wó‹_ü‚;wÒ­[·úxiù‡fÈ!|óÍ7“ššª;ƒâTTªDœ“#n(Ü”7€ijÔ?ÄYìÙ³‡#FP]]ͦM›xä‘GŒŽÔ$­_¿žÉ“'Ó¬Y3vïÞ]ïOÖÔÛP¾-i=ô;vì [·ndffrûí·××ËËw|õÕW 6ŒÃ‡sß}÷ñÞ{ï©TŠÓQ©qneee¤¤¤ÇéÓ§>|8±±± :ÔàtßÊÌÌ$**Š]»vЮ];"## ÅÓÓÓàt"ÒÔ?Ä™,^¼˜yóæÑ²eK¶lÙÂ}÷Ýgt¤&eÇŽ<ôÐCTTT””Ä /¼PïרסÀÙ³gyðÁùè£èÖ­øùùÕç%š¼Ã‡3räHŽ=ÊÝwßÍŽ;hÙ²¥Ñ±DêJ•HÓPZZJrr2 0räH¬V+†dÊÊÊÂd2‘‘‘|{8DDDaaaxyy’ID‡ú‡8m•bŒÆÚ*¥Þ‡:EEE<üðÃdeeѾ}{Þ{ï=úõëWß—i’²³³=z4_ý5wß}7ééé´mÛÖèX" B¥J¤i9{ö,‰‰‰$&&rîÜ9\\\;v,V«•€€€FÉ››‹ÉdbË–-Øl6ZµjExx8áááx{{7J1–ú‡8m•Òøs«”êÀ·wÝ&L˜Àûï¿O«V­xçw¸÷Þ{âRMÆ|À¸qã8þ<=ôëׯç–[n1:–HƒQ©išÎœ9CBBÉÉÉ\¸pWWW‚‚‚°X,øûû7È5óóó1›Ílذššn¹åˆˆˆÐÍ‘&FýCœ‘¶Ji<½UJƒ uàÛoœiÓ¦±jÕ*š7oÎ[o½EPPPC]Ω­Y³†'žx‚ŠŠ BBBX¾|9Íš53:–HƒR©iÚ¾þúkâââHII¡¼¼777‚ƒƒ1›Íõöh÷Ñ£G±X,¤¥¥Q]]M‹- %22ReW¤‰Rÿg¥­Rž[¥¸6ä‹{xxðÖ[o1gÎ.]ºÄ¤I“xñÅ©¬¬lÈË:•ÊÊJ^|ñE¦NJEEsçÎåÍ7ßÔ@GDDœÞí·ßNRR‡æÙgŸÅÍÍ•+WâïïOhh('Nœ¸á×>qâ¡¡¡øûû³råJÜÜÜxöÙg9|ø0III興ˆÓñöö&==ÀÀ@>Ì=÷ÜCNNŽÑ±œFvv6C‡­褧§7ÊÞ· ºRç»–,YÂܹs¹téƒ bíÚµtîܹ1.í°Ž;ÆäÉ“ÉÊÊ¢yóæüéOâ¹çž3:–H£Ñ2ù®ÂÂBbbbHMM¥ªªŠæÍ›3sæL"##éСÃu½Æ©S§ˆ‹‹cÙ²e\ºt wwwBBBˆŽŽÆ××·aÿ"âÔ?ÄÙi«”úgäV)6ÔØ¿?“&MâØ±c´iÓ†åË—óè£6ÖåÊ;ï¼Ã´iÓ())¡{÷î¬[·N›MK“£R%"WSPP€ÅbaÍš5ÔÔÔàééɬY³X°`>>>Wýš¢¢"âããY²d eee¸ºº2eÊÌf3=zôhä?ˆØ3õi ´UJý1z«”}üêûHNN'N¤¸¸˜ñãÇóüóÏsþüùÆŒa×JKK cüøñ”””0qâDöïß¯ŽˆˆÈÿêÑ£iiiû,_|ñžžžüéOâ™gž1:–ˆat§LD®Gvv6f³™ôôtZ·nÍóÏ?Àk¯½V;ä=z4‹…–UDìŸú‡45Ú*¥îìi«Æ:ðíÎÐ/¼ðï¾û.÷ß?K–,¡gÏžFE2ÄáÇ™?>›6m`Ĉ¤¤¤èî¡4y*U"R{öì!**Š;w^ññ{ï½—ØØX† bP2q$êÒi«”ëgo[¥4êãWß×­[7ÒÓÓÙ°a;wæƒ> €… röìY#£5Š’’.\H@@›6mÂÇLJ7Þxƒ;vh #""Rdgg“ŸŸÿƒßËÏÏ';;›ŠŠ ’‰ˆˆØ?m•rmöºUŠ¡CË&L˜À§Ÿ~Êüùó±Ùlüá K—.üá ´´ÔèxõîüùóÄÄÄÔþ«««™;w.‡bÆŒµwDDDä§UUUñúë¯Ó½{wfϞͩS§ dÛ¶mlÛ¶ÀÀ@N:ÅìÙ³éÞ½;¯¿þ:UUUFDZ;­Zµbݺu¼öÚk´hÑ‚äädzöìÉÚµkŽf¸÷Þ{Þ½{³dÉZ´hÁÒ¥KY·n­›ÜÝ IDATZµ2:š±_]Í‘#G°X,¬ZµŠêêjÚ´iÃ3Ï<ÃóÏ?OûöíŽwS¾üòKþüç?“’’BII îîîc6›éÒ¥‹ÑñD쎖?‹È©®®&-- ‹ÅÂÑ£GÀjµ2vìØ+þÿ±eËL&¹¹¹øùùa6› ÆÍÍͰ?ƒˆØ'õm•r™#l•bwCËòóóyùå—Y½z54oÞœ &ðÔSO1bćYÍb³ÙøàƒX¾|97n¬ý³<þøãÌŸ?ß®¾DìJ•ˆ|_MM 6lÀl6×>jåïïÅb!((W׫/B¾Ñ¯‘¦GýCäÿ¼ýöÛ¼øâ‹?~æÍ›Çüùóñöö6:Zƒ*))!!!ÄÄDÊÊÊðññáå—_fúôév7‹°Û¡Îe—W·¼ñÆœ9s€.]º0yòd&NœÈwÞip«ۿ?ëÖ­cýúõàããÃÌ™3bÕ‘HcP©‘ËêkÅÍõ®ð‘¦KýCäJ¥¥¥X­V©¬¬äg?ûóæÍcöìÙxyy¯^?žÄÄD)))ÁÃÃ矞¨¨(Z·nmt¼«²û¡Îe—.]âí·ßæ7ÞàŸÿügíÿd}}}5j£Fâ¾ûîãÖ[o5$ßùóçÙ±cGí3ü—9...Üÿý̘1ƒqãÆÑ¼ysCò‰8"•*ÈÈÈÀd2‘••@§NˆŠŠbÚ´i4kÖì†^³²²’+Vˉ' Äjµ2räÈzË."ŽGýCäê´UŠ}r˜¡Îw;vŒµkײ~ýz>þøãÚ»¹¹Ñ¯_?† ÂàÁƒ  G7\ø~Lee%Ÿþ9¹¹¹ìÛ·ÌÌLrss©®®®ýœ2iÒ$&Nœˆ¯¯o½^_¤©P©iÚvíÚETT™™™´oßžÈÈHBCCëí&É¥K—HII!..ޝ¾ú €¡C‡ËðáÃëå"âXÔ?D~š¶J±/9Ôù®ÂÂÂÚÕ1;vìøÁ‘kôìÙ???|}}éÒ¥ mÛ¶­ýuË-·àééyÅ×”••qáÂΜ9SûëØ±c=z”cÇŽQPPðƒcQo½õVî»ï¾ÚUCäˆÜ<•*‘¦iß¾}˜L&¶oß|ûørDDaaa´lÙ²A®yñâE’““IHH ¨¨€x«ÕÊ Aƒäš"bŸÔ?D®¶J±?Ôù®êêjrrrسg{÷î%77—‚‚*++ëõ:Íš5£gÏž0hÐ †J@@€NЩg*U"MËˆŽŽfëÖ­´nÝš¹sçò /4ÚãÕçÏŸçÕW_eñâÅ”””0fÌbbbèß¿£dc©ˆÔ¶J1–S u®¦¢¢‚Ï?ÿœ£GRXXȱcÇ®XsáÂÊÊÊ®øOOOn¹å–+VôtéÒ???ºtéB=ððð0èO$Òt¨T‰4 yyy˜Íf6mÚ„ÍfÃËË‹9sæ0wî\Ã6%,))añâÅ$%%QZZŠ‹‹ ãÆÃb±Ð»woC2‰HãPÿ¹qÚ*¥ñ9ýPGD—J•ˆs+((Àb±°fÍjjjðôôdÖ¬Y,X°£ãPTTD||UUU‘ššJLL ………ôïߟ˜˜ÆŒcl8;µuëV¢££9p྾¾DGG‚»»»ÁéDä»Ô?DÄ‘h¨#"vK¥JľÔÔÔ°fÍ, ôêÕ‹—^z‰ &Ôþ7+Wg³Ùxûí·y饗øôÓOèÑ£f³™)S¦àêêjpBõq,ꈈÝR©±6›M›6a6›ÉËË {÷îDGG3uêT #ꨦ¦†U«VáC‡èÝ»7‹…qãÆi8&b0õq$ꈈÝR©1^zz:f³™ììl@ Õ§«=Æ6`À, £G66œH¦þ!"ŽDC±[*U"ÆÙ¾};ÑÑÑìݻп éjN<˜˜˜m8-bõq$ꈈÝR©i|:ŠÛ8:^Ä>¨ˆˆ#ÑPGDì–J•HãÉÊÊÂd2‘‘‘@›6mˆˆˆ ,, ///ƒÓ5-¥¥¥$''“@qq1#GŽÄjµhp:ç§þ!"ŽDC±[*U" /77“ÉÄ–-[°Ùl´jÕŠððpÂÃÃñöö6:^“vöìYILLäܹs¸¸¸0vìX¬V+FÇqZê"âH4Ô»¥R%Òpòóó1›Ílذššn¹åˆˆˆ mÛ¶FÇ“ï8sæ $''sáÂ\]] Âb±àïïot<§£þ!"ŽDC±[*U"õïèÑ£X,ÒÒÒ¨®®¦E‹„††Éí·ßnt<ù _ý5qqq¤¤¤P^^Ž››ÁÁÁ˜ÍfüüüŒŽ'â4Ô?DÄ‘h¨#"vK¥J¤þœ8q‚ØØXV¬XAee%̘1ƒ… Ò±cG£ãIœ>>,X°€Y³fáééip:û¥þ!"ŽDC±[*U"?®¢¢‚””âââ8uê÷ÜsV«•{ï½×àtbOvî܉ÉdâÃ? C‡DFFЇ‡‡ÁéDìú‡ˆ8 uDÄn©T‰üPUU+V¬ 66–ãÇHLL £F28سmÛ¶MVV;w&**ŠiÓ¦áîînp:û¡þ!"ŽDC±[*U"ÿ§ººš´´4, G «ÕÊØ±ckÿ{ù)6›-[¶`2™ÈÍÍÀÏϳÙLpp0nnn'1žú‡ˆ8 uDÄn©T‰@MM 6lÀl6“ŸŸ€¿¿?‹…   \]] N(ŽHßW"?NýCD‰†:"b·Tª¤)ÓŠ i Z&òCê"âH4Ô»¥R%MUFF&“©vï“N:Õî}Ò¬Y3ƒÓ‰3ª¬¬¬Ý«éĉÀ·{5Y­VFŽip:‘Æ¥þ!"ŽDC±;çΣººš6mÚP\\Œ››­Zµ28™HÃÚµkQQQdffо}ûÚSŠš7onp:i .]ºT{ªÚW_}ÀСC‰eøðá§iXê"âˆ4Ô‘W^^Naa῾üòKŠŠŠ8sæ EEEqîܹëz½V­ZáããSû«mÛ¶üüç?Ç××___üüüøå/©7Áâ0öíÛ‡ÉdbûöíøøøAXX-[¶484E/^$99™„„ŠŠŠxà°Z­ 4Èàt"×GýCDš uD¤^>|˜œœ>ùä>ùärss9zôh–0·jÕ 77·+–?WWW_wépuu¥K—.ôíÛ—>}úЧOúõëG×®]ëügi( ::š­[·кukæÎË /¼À­·Þjp:8þ<¯¾ú*‹/¦¤¤€1cÆCÿþý N'òÔ?D¤©ÒPGDnØ¥K—ÈÎÎ&33“?ü½{÷òÍ7ßüàóZ´hQ{ëò¯Ÿÿüçµw¹.ßñºÞåÍçΫ½»vùnÛ—_~Y{îèÑ£|ñÅ\ºté_{Ûm·1dȆ ÆàÁƒ8p 7ýïB¤.òòò0›ÍlÚ´ ›Í†——sæÌaîܹ´nÝÚèx"?PRRÂâÅ‹IJJ¢´´Æ‡Åb¡wïÞFÇ“&FýCDäÿh¨#"uRXXÈ»ï¾Kzz:ÿüç?¹xñâ¿ß­[7úõëW{w* ??¿F?A¥¦¦†cÇŽqðàÁÚ»v9999räŠÏóòòâþûïgÔ¨QŒ5 __ßFÍ)MKAA‹…5kÖPSSƒ§§'³fÍbÁ‚øøøOäšŠŠŠˆgÉ’%”••áêêÊ”)S0›ÍôèÑÃèxâÄÔ?DD®NC¹¦œœÖ®]Ë;ï¼C~~~íÇ›7o΀:t(÷Üsƒæ¶Ûn30éµ}óÍ7ìÙ³‡Ý»w³wï^öïßOEEEíïûûû3~üx&MšDß¾} L*Τ°°˜˜RSS©ªªÂÃÃÐÐP"##éСƒÑñDêìÔ©SÄÅÅ‘’’BEEîîî„„„­7§RoÔ?DD®MC¹ªÏ>ûŒÕ«W³nÝ: j?îëëËÃ?ÌèÑ£1b„ÃoâZZZÊ|À¶mÛØ¶m………µ¿çïïÏĉ™:u*þþþÆ…‡uòäI-ZÄòåËkßøN›6¨¨(:wîlt<‘›vüøqbccY±bEíÀrÆŒ,\¸Ž;Oú‡ú‡ˆÔ†:"RëâÅ‹¬[·Ž×_={öÔ~¼_¿~Lž<™G}ÔéËE~~>7ndݺu]»Šáò#*ÁÁÁ˜Ífm–)NéÈ‘#X,ÒÒÒj-¼¼­]»vFÇ;§þ¡þ!"7NC!//¥K—’––ÆÙ³gøÕ¯~Åc=ƤI“šì> ùùù¬_¿žU«VÕ.ûööö&88˜gŸ}V›ƒÊ“@rrríf²AAAX,î¸ã£ã‰4¸ÿûߘÍf6lØP» xXX´iÓÆèxbgÔ?®NýCDêBC‘&Êf³ñþû˜Èûï¿Íf£eË–Lš4‰§Ÿ~š!C†Ñ®|øá‡¼ñƬ]»–²²2\\\xðÁ™7o<ð€ÑñÄ`çÎ#11‘ÄÄDΞ=‹‹‹ cÇŽÅjµ`t<‘F—››‹ÉdbË–-Øl6¼½½ '<<üºOç¤þQ7ê"r-êˆ41UUU¤¥¥±xñb>ùäüüü˜3gÿó?ÿƒ···Á íÛÙ³gY¹r%III=zøvyø‹/¾Èc=†»»»Á ¥1•––’œœÌ+¯¼Â™3g9r$V«•ÀÀ@ƒÓ‰/++ “ÉDFFmÚ´!""‚°°0¼¼¼ N'Iýãæ¨ˆÈÑPG¤‰¨©©!-- ‹ÅR{¬æ!CxñÅyôÑGqss38¡c©®®fãÆ¼òÊ+|ôÑGôèÑ“ÉÄÔ©Squu58¡4¤²²2RRRˆ‹‹ãôéÓ >œØØX†jp:û“™™ITT»ví ]»vDFFª}BœœúGýRÿ‘ïÓPGÄÉÙl6Ö¯_Ùl®}.{ĈÄÄÄ0lØ0ƒÓ9‡]»va2™Ø½{7½zõÂl6„‹‹‹Áé¤>UTT°|ùr-ZÄÉ“'4hV«UËàE®ÃöíÛ1™LìÛ·€Ž;²páBf̘‡‡‡Áé¤>©4<õ uDœÚ¾}û¯-ÏC† !&&†ûï¿ßàdÎéûoV Dbb"ƒ 28™Ü¬ªª*RSS‰‰‰©=v¶ÿþÄÄÄ0fÌcÉ8 ­[·Í€o«ŽŽŽ&$$D‘8õÆ¥þ!Ò´i¨#ℎ?Îï~÷;Ö¬YƒÍfãW¿ú <üðÃFGk¶nÝʼyóøüóÏqqq!88˜?þñtèÐÁèhRG555¬Y³‹ÅBAAðíЗ^z‰ &èN¨ÈM°Ùl¼ýöÛ¼ôÒK|úé§À·‘˜Íf¦L™¢ÇHú‡±Ô?Dš&ý´q"UUUÄÇÇãïïÏêÕ«ñööæµ×^ãàÁƒ*Th̘1äææòÊ+¯àííÍ[o½E=X¼x1UUUFÇ“ë`³Ùظq#}ûö%88˜‚‚ºwïNjj*¹¹¹ZÚ.R\\\ "77—ÔÔTºwïNAAÁÁÁôíÛ—7¢{ŽAýÃ>¨ˆ4MZ©#â$>þøcžzê)8€««+¡¡¡ÄÄÄàããct´&­¨¨“ÉIJe˨©©aàÀ¼ñÆôíÛ×èhò#ÒÓÓ1›Ídggz,D¤±\í1Ç`±X=z´±áäG©Ø'õ‘¦CCW^^ÎK/½T{¦W¯^¼þúë <Øèhò™™™<ýôÓäççãîîNDDf³™æÍ›Mþ×öíÛ‰ŽŽfïÞ½€6p1ÊÕ6$ÁÃÃÈÈH~ÿûßë ¨ºté‹-">>žŠŠ Xµj½zõ2:Z“¦£–EìSYY)))ÄÅÅqúôi†Nll,C‡58]Ó¦þáXÔ?Dœ›†:"Èf³ñꫯIyy9}úô!-->}úM®ÃÁƒ æÓO?¥E‹üñ$,,L{´4²¬¬,L&´iÓ†ˆˆÂÂÂðòò28ˆ\VZZJrr2 0räH¬V+§kZÔ?›ú‡ˆsÒPGÄÁ|óÍ7„„„‘‘‹‹ /¼ðqqq´hÑÂèhRåååÌŸ?Ÿäädl6£GæÍ7ߤmÛ¶FGsz¹¹¹˜L&¶lÙ‚Íf£U«V„‡‡Ž···ÑñDäGœ={–ÄÄD9wî...Œ;«ÕJ@@€Ññœžú‡sPÿq>êˆ8ýû÷3aÂŽ?N‡øÿïÿñàƒKn¶mÛxòÉ'ùúë¯éÒ¥ o¿ý6ýû÷7:–SÊÏÏÇl6³aÃjjj¸å–[ #""BeVÄœ9s†„„’““¹pá®®®a±Xð÷÷7:žSRÿp>ê"ÎCC±|ùrÂÂÂ(//ç׿þ5k×®¥}ûöFÇ’zpòäI&MšÄž={hÑ¢ýë_yâ‰'ŒŽå4Ž=ŠÅb!--êêjZ´hAhh(‘‘‘Ü~ûíFÇ‘ôõ×_GJJ åå帹¹ŒÙlÆÏÏÏèxNCýÃy©ˆ8W£ˆÈO«®®æ¹çžã©§ž¢¼¼œ^xíÛ·«P9‘Ž;òÏþ“Y³fQ^^ΓO>ÉìÙ³©®®6:šC;qâ¡¡¡øûû³råJÜÜÜxöÙg9tèIIIèˆ8¸Ûo¿¤¤$>̳Ï>‹››+W®ÄßߟÐÐPNœ8atD‡¦þáüÔ?DœƒVêˆØ±ÒÒR&MšÄ{g§§'o¼ñS§N5:–4 ÔÔTfΜIyy9cÆŒaõêÕÚ´·ŽN:E\\Ë–-ãÒ¥K¸»»Btt4¾¾¾FÇ‘RXXHLL ©©©TUUѼysfΜIdd$:t0:žCQÿhzÔ?D—†:"vêË/¿dôèÑäääЮ];6oÞÌÝwßmt,i{÷îå‘G¡¨¨ˆ°e˽!¹EEEÄÇdzdÉÊÊÊpuueÊ”)˜Ífzôèat<i$X,Ö¬YCMM žžžÌš5‹ àããct<»§þÑt©ˆ8& uDìпÿýo|ðAþóŸÿàïïOzzºöhbŽ9ÂC=Ä¡C‡èܹ3ï¿ÿ>={ö4:–]*))añâÅ$%%QZZŠ‹‹ ãÆÃb±Ð»wo£ã‰ˆAòòò0›ÍlÚ´ ›Í†——sæÌaîܹ´nÝÚèxvIýCÔ?D†:"v&''‡‘#Grúôi~ýë_³iÓ&Ú´ict,1@qq1<ò~ø!·ß~;ï¿ÿ¾ŽíýŽÒÒR’’’X¼x1%%%Œ=‹Å€ N'"ö";;³ÙLzz:­[·fîܹ̙3G—|‡ú‡\¦þ!âX4Ô±#}ô£Fâ¿ÿý/?ü06lÀÓÓÓèXb ‹/2~üx222hÓ¦ Û¶m#00ÐèX†*++cÉ’%ÄÇÇSTTÀý÷ßOLL C† 18ˆØ«={öÍ|€ ,`Ö¬YMþg­ú‡|Ÿú‡ˆãÐPGÄNìÞ½›Ñ£GsþüyÆÏêÕ«ñðð0:–ØŠŠ &OžÌ;ï¼C«V­HOOgèСFÇjt¤¤¤Ç©S§¸çž{°Z­Ü{ï½§G±sçNL&~ø!:t 22’ÐÐÐ&ùsWýC~Œú‡ˆcÐPGÄ|ôÑG<øàƒœ={–àà`Þ|óMÜÜÜŒŽ%v¤ªªŠ'Ÿ|’´´4¼½½ùÇ?þÑdî˜UUU±bÅ bcc9~ü8ÄÄÄ0jÔ(ƒÓ‰ˆ£Ú¶mÑÑÑdeeйsg¢¢¢˜6mîîî§kêr-M¹ˆ8 uD –››Ëˆ#øïÿËÔ©SY¹r¥ •\Uuu5!!!¬^½š¶mÛ²sçNúôéct¬S]]MZZ‹…£G€ÕjeìØ±¸¸¸œPDÍfcË–-˜L&rssðóóÃl6ìÔ?Õ?äz5µþ!âh4Ô1P~~>¿þõ¯ùæ›o7nëׯW¡’ŸTUUEPPÿûß¹ýöÛÙ½{7Ý»w7:V½ª©©aÆ ˜Ífòóóð÷÷Çb±„«««Á EÄÙ4µÿï¨H]5…þ!â¨4Ô1ÈþóÌþózè!Þyç=Ã.×¥¢¢‚±cÇòþûïÓµkWöîÝËm·Ýft¬›Ö”ˆ}h +Õ?äF9kÿqtêˆàâÅ‹Ü{ï½|ôÑG 2„üã´lÙÒèXâ@.^¼È}÷ÝÇ¿þõ/î¾ûnvìØáÐßC˜L¦Ú½-:uêT»·E³fÍ N'"MMeeeí^^'Nœ¾ÝËËjµ2räHƒÓÝ8õ¹YÎÖ?Dœ†:"¬ººšñãdzyófºuëÆž={t—CnÈ7ß|Ã!C8|ø0<ò7nt¸Õ,»ví"**ŠÌÌLÚ·o_{ MóæÍ N'"MÝ¥K—jOÝûꫯ:t(±±± >Üàtu£þ!õÅú‡ˆ3q®„E@xx8›7oæ¶Ûnã½÷ÞS¡’öÝï¡Í›73oÞ<£#]·}ûöñ›ßü†#F™™‰ñññ9r„Ù³gk #"v¡yóæÌž=›#GŽ™™™Œ1‚ßüæ7ìÛ·Ïèˆ×MýCê‹#÷g¤•:"håÊ•<ñÄ´lÙ’;vp÷ÝwIœÀîÝ»yਨ¨`õêÕL™2ÅèH?êÀDGG³uëVZ·nÍܹsyá…¸õÖ[ N'"òÓΟ?Ï«¯¾ÊâÅ‹)))`̘1ÄÄÄпƒÓý8õiŽÔ?Dœ™†:"äÀ :”‹/²bÅ ž|òI£#‰Y¾|9O=õ-[¶dß¾}vwÔh^^f³™M›6a³ÙðòòbΜ9Ì;—Ö­[OD¤NJJJX¼x1III”––âââ¸qã°X,ôîÝÛèxWPÿ†dïýC¤)ÐPG¤MEÔç IDAT3pà@Ž;ƳÏ>Ë_þò£#‰zê©§X¾|9ݺucÿþýx{{‰‚‚, kÖ¬¡¦¦OOOf͚ł ðññ1:žˆÈM)**">>ž%K–PVV†««+S¦LÁl6Ó£G£ã©H£°Çþ!Ò”h¨#ÒÀl6<ò[·neÈ!ìܹSG‡Jƒ¨¨¨`èСdee1nÜ86nÜhX–ÂÂBbbbHMM¥ªª BCC‰ŒŒ¤C‡†åi§N"..Ž””***pww'$$„èèh|}} ɤþ!Åžú‡HS¤¡ŽH[ºt)Ï=÷íÚµãã?¦cÇŽFG'vâÄ ú÷ïÏ™3gxã7˜1cF£^ÿäÉ“,Z´ˆåË—×¾±™6mQQQtîܹQ³ˆˆ4¶ãÇËŠ+jÚ3fÌ`áÂ…þó_ýC“ÑýC¤)ÓPG¤åçç3`ÀÊÊÊØ¼y3cÆŒ1:’4›6mbüøñxyy‘Ý(œ>}ºö.õåG‚ƒƒ1›ÍtíÚµÁ¯/"bOŽ9‚Åb!--­öÑÓ˫۵k×à×Wÿ#Ñ?DDGš‹4˜ŠŠ üq.^¼È3Ï<£B%fܸq̘1ƒÒÒRüq*++ìZÅÅÅDFFÒµkW’’’(//gâĉäåå±råJ tD¤IêÚµ++W®$//‰'R^^NRR]»v%22’âââ»¶ú‡¥1û‡ˆü­Ôi /½ô‹²³³iÙ²¥Ñ‘¤ ¹páwÞy'ÄÆÆ²páÂz}ýsçΑ˜˜Hbb"gÏžÅÅÅ…±cÇbµZ ¨×k‰ˆ8ºÜÜ\L&[¶lÁf³áííMxx8ááá´jÕª^¯¥þ!Fjèþ!"?¤¡ŽHøôÓO¹óÎ;©©©aïÞ½ 8ÐèHÒ}ôÑG 2„fÍš‘““CÏž=oú5KKKINNæ•W^áÌ™3Œ9«ÕJ``àM¿¾ˆˆ3ËÊÊÂd2‘‘‘@Û¶m™7oaaaxyyÝô뫈=hˆþ!"?N_‰Ô³ššž~úi***xñÅU¨Ä0wÝu³gϦ¼¼œ§Ÿ~š›™á—••]ñèÀ™3g>|8»wïfÛ¶m興\‡ÀÀ@¶mÛÆîÝ»>|8gΜ¹âÖ²²²~mõ±õÙ?DäÚ´RG¤ž½öÚkÌž=›nݺ‘››‹§§§Ñ‘¤ »pá}úôáØ±c,]º”gžy¦N__QQÁòåËY´h'Ož`РAX­Vxà†ˆ,"Òdlß¾“Éľ}ûèØ±# .dÆŒu>~\ýCìÉÍö¹~êˆÔ£Ó§OÓ½{wΟ?Ï|À½÷Þkt$Þÿ}FŽÉÏ~ö3:DÛ¶m¯ù5UUU¤¦¦Caa!ýû÷'&&F›nŠˆÔ³­[·Íðõõ%::šÜÝݯùõêbn¤ˆHÝéñ+‘z´páBÎ;ÇÿüÏÿ¨P‰ÝxðÁ™:u*ÿýï1›Í?ù¹555¬ZµŠ^½z1}út éÕ«ëׯ';;[‘0f̲³³Y¿~=½zõ¢°°éÓ§Ó«W/V­ZEMMÍO~½ú‡Ø£ºô¹qZ©#ROrrr8p žžž:tˆöíÛI¤Ö‰'ð÷÷§¢¢‚лwï+~ßf³±iÓ&Ìf3yyytïÞèèh¦NŠ««îˆˆ4†ËÃõ˜˜:@ïÞ½±X,Œ7—+>_ýCìÙµú‡ˆÜ<µt‘zNuu5¿ÿýïU¨ÄîtêÔ‰ PUUÅ‹/¾xÅ不§È„ ÈËËÃ××—¿ýío|öÙg<þøã興4"WWWüq>ûì3þö·¿áëëK^^&L 00ôôô+>_ýCìÙOõ©Z©#RÞ}÷]FM—.]øì³ÏhÑ¢…Ñ‘D~àâÅ‹ÜqÇ?~œŒŒ \]]‰ŽŽfïÞ½ÀÍmÐ)"" ãjÖ<˜˜˜***Ô?Äî}¿<øàƒFGq*êˆÜ$›ÍF`` ÙÙÙ¬Y³†É“'IäG½õÖ[„„„пrss©®®¦]»vDFFªÓRDDìTYY)))ÄÅÅqúôiÜÜܸãŽ;ÈËËSÿ»w¹ 4¨öf’ˆÔ uDnÒ;ï¼Ã¸qã ''çϺ‹Ø“ššøôÓO;v,C† !,, ///££‰ˆÈu(--%99™ÌÌLÒÓÓÕ?Ä!|·lÙ²E/ˆÔ# uDnBMMM튇wÞy‡ßþö·FG¹¦õë×3iÒ$î¼óNöï߯7""FýC‘ú‡HÃÐî—"7aË–-äææ2pà@yä£ãˆ\—   úõëÇÇÌ–-[ŒŽ#""u¤þ!ŽHýC¤ah¨#râãã°X,ºÛ ÃÅÅ…èèh N#""u¥þ!ŽHýC¤aèñ+‘´oß>̯~õ+òòòTªÄ¡ÔÔÔЧO>ûì3²²²8p Ñ‘DDä:¨ˆ#Sÿ©Z©#rƒ.ßa˜7ož •8WWWæÌ™@BB‚ÁiDDäz©ˆ#Sÿ©W¬Ô9zô¨‘YÄAüâ¿ÀÃÃÃè†*,,¤{÷îÜvÛm6ùâ˜ÊÊÊèÒ¥ gΜáÈ‘#tîÜÙèH""òÔ?Ĩ\?½?—ëáþÝèÚµ«Q9ÄäææÒ§O£cjÙ²eTUUñÜsÏ©P‰ÃòôôdÖ¬YDGG³lÙ2bccŽ$""?AýCœúÇõÓûs¹W¬Ô¹¼„ÓÏÏϰ@b¿.OŠ›úP§ªªŠN:QTTĉ'hß¾½Ñ‘DnØÉ“'ñõõåöÛo§°°ww÷k‘ˆˆ4:õq&ê×GïÏå§\~~ÕÿzŽ9Ò¨aÄ1ðÉ'ŸÃp[·n嫯¾âÑGU¡‡×±cG~øa6oÞ̻ᆱ£qEDì”ú‡8õºÑûs¹šËïϵQ²H½þúë<ýôÓ'©—¿—/o‹ˆˆýQÿg£þ!R?®úø•N9—«¹< lÊ_ýç?ÿá—¿ü%?ÿùÏùâ‹/puÕ\T_UU¿üå/9}ú4_|ñ?ÿùÏŽ$""ß¡þ!ÎHýãÚôþ\~ŠVêˆÜ€õë×SSSÃĉU¨Äi¸»»3qâDªªªX¿~½ÑqDDä{Ô?Ä©ˆÔýT©ƒµk×0yòdƒ“ˆÔ¯I“&°nÝ:ƒ“ˆˆÈ÷©ˆ³Rÿ¹yêˆ\§ÂÂB>úè#|}}¹ë®»ŒŽ#R¯L§NØ»w/ÿ¿½{‹ª\ß~Œ(22`(J€¥a"â))h†fá!4ò¬”‡vnµr«imÓjo5i§™fúÛ»4D- •DKEEEQQ9‰¨qP@ïï¿31ÊfÖîÏuÍuåZ‹õ>CÜï3k½sãÆ ©Ë!"¢ÿÃüAÆŒùƒ¨ñØÔ!ª£;wB±cǪïo%22™ cÇŽ…‚—@éæ2fÌDǦQíÞ½ðꫯJ\ ‘vÂÃÃ%®„ˆˆT˜?ÈØ15›:Du““ƒS§NÁÎÎÏ=÷œÔåi…··7lllpòäIܽ{WêrˆˆL󙿢ÆaS‡¨<ˆòòr :æææR—C¤r¹C‡EYY<(u9DD&ùƒLóQã°©CT€aÆI\ ‘v :ªˆˆôó™ æ¢†cS‡¨BDDD@&“1T‘Ñ{饗¿üò‹Ä•™6æ2%ÌD ǦQ-qçÎtêÔ O<ñ„Ôåi•““:v숛7oâúõëR—CDd²˜?È”05›:Dµ8qâ oß¾WB¤>>>€ãÇK\ ‘ébþ SÃüAÔ0lêÕBõ‡Eõ‡†ÈØ1TIùƒL óQð©CT †*25 UDDÒcþ SÃüAÔ0lêÕàîÝ»¸rå lllйsg©Ë!Ò‰.]ºÀÆÆ ¸{÷®Ô噿2EÌD æQ Ξ= !ºwï™L&u9D:!“Éàáá!âãã¥.‡ˆÈä0)bþ j6uˆj úƒâáá!q%Dº¥zÍ3Téó™*æ¢úcS‡¨ Udªªˆˆ¤ÃüA¦Šùƒ¨þØÔ!ªC™*†*""é0©bþ ª?6uˆjàáÂmD¦ÄÝÝpéÒ%‰+!"2=Ìdª˜?ˆêÏ(›:±±±ÉdèÓ§„m/++ÃðáÃ1mڴǾV§žz !!!êã …ÆcòäÉÕŽ}óæMøûûÃÎÎN=¦ÜÜ\ܽ{­[·†Ôåé”R©D«V­‚‚©Ë!"2Ìdʘ?Œ“Tóó’’ 8ŽŽŽP(°±±¯¯/¢££µõT%a”M•äädlÛ¶­Ê}3fÌÀŽ;PXX¨±=** ééé˜8q¢z[^^ Õo¿ý¶Ú1ÍÌÌ€uëÖ5És 餤¤\\\$­ƒH*ª×¾êgˆˆ´ùƒLó‡ñÒõü\.—cýúõHMMEaa!222àãプ_~¹Éž“>0ê¦ÎG}„E‹áþýûí1bZµj…ÿýïÛ7mÚ„€€(•ÊÙ¶m[̘1Ï<óLƒ¾žôC™:777@RR’Ä•™æ2uÌÆK×óssssxxx yóæ™L¹\®~ £nêL›6 vvvøì³ÏÛ'—Ë1eÊlÚ´I½-77ááá˜1c†.Ë$=•œœ puu•¸"ið2""Ýcþ SÇüa¼¤šŸ7­Zµ‚••¢££qôèÑFOßuSÇÜÜ!!!XµjÒÓÓÛ?mÚ4ÄÄÄàÂ… €íÛ·ÃÉÉ ƒ Ò8ÎÞÞ¶¶¶êÇúõëuR?I+##Ю];‰+!’†““€‡k…‘n0©cþ0^RÍÏ¿ÿþ{àÊ•+ÈÊÊ‚ šîIé£nꀯ¯/|}}±páÂÇö¹¹¹Á××WÝ üæ›o0}útÈd2ã²²²——§~Ì™3G'µ“´rrrvvvW¢}Mµx™Ê»ï¾ ™L†ýû÷×:vCŸ:u*¾úê+×>{ölc굯úY ""ícþÐMþ˜?>ÜÝÝaee;;;Œ?·oß®ñk˜?tƒùøI9?ïÔ©V¬XÍ›77þ‰è£oêÀªU«°sçNüñÇí ÆöíÛqìØ1\¼x±ÆO¶"ÓbJ¡J¥)/{ðà¶nÝ OOOË'«SßÅÅãããqøðaLŸ>]çµ/^¼+W®D~~~j5tª{—³³³%®„ˆÈt0hÒVþP(êó&&&¢¸¸ÕÏü¡;ÌÆOÊùyyy9š5k֤甚I4u:uê„Y³faÙ²eí{å•W`ff†‰'âå—_†££c£Ç+..ƃ<üµâââFŸ“tïÏ?ÿ´iÓFâJt§)/Û³g„øúë¯ñÓO?Õú®W}_·n‚‚‚ —Ëu^{»víЧOŸjÛ±±··ððÝ""Ò æMÚÊ}ô<<< “É`ooyóæáøñãÕÏü¡;ÌÆOWóósçÎ!22R=7¿|ù2–,Y‚   ŸS™DS–-[¦qi¤Š……&Mš„äääÇ:ï*¶¶¶P(êÇСCkËÒÒ>>>¾ `iiÙø'@:§zw ¡Ÿ„fˆšbñ²7"00½{÷F§NªýˆÁ†B`ïÞ½2dˆdµûùùaÏž=Mó„ôC‘î1hÒUþˆŒŒDÏž=«ÜÇü¡[̦AóóÒÒR,\¸J¥ …þþþx饗°víÚ&}.’•l"RóððÄîÝ»Ell¬ˆ‹‹ׯ_W?²²²DNNŽÈÉÉEEER—ÛhÎÎ΀HMM•º­‹‰‰Dii©ˆŒŒ-[¶7nÜÐØ.„ׯ_2™LÄÇÇ !„X·nèØ±£¨¨¨ÐØ-„båÊ•ûëZCuÒÒÒqçÎÉjˆˆJ¥²öoªHMM„³³³Ô¥™ æÝæ!„ؽ{·P("&&¦Êý̺eÌù£¤¤D=_ÊÉÉIIIê¹ÔÅ‹Ell¬ˆ111âСCœŸSTósÍë‰êàÝwßÅÕ«Wë|¼\.G«V­Ôÿ¶±±™ÙËÄZ¶l‰æÍ›Wyœ­­­zQ¬ÊÇ5kÖ …¢Ê㬬¬`aaQåq­[·Vÿwåã,,,`eeõØq÷îÝSkJ*/^6wî\}•/[³fÍc‹—}óÍ7pssƒ··7 ((‹/FTTÜèÚrssÖÖÖ’Õnmm­®ÃصhÑx )‘1è6„……aúôéG¯^½ª<†ùC·Í………(--ððÊ‹Êkݽ{ê㋊ŠÔû*¿îß¿’’×T©¼>QAAúƒ:êz~!òòòÔÇÝ»wO}‹OMç'Ò6u¨Þ:vìkkk”••¡  @½½ò/½Ê¿8ËÊÊ4~©Ê$ÕîGï6«V­‚‡‡úöíûؾàà`Ìœ9cÆŒÑX¼¬¬¬ [¶lAvv¶ú²Y•¯¿þºIš:ª†[~~¾ú¾®kÏÏÏ×h3U#Uõ³LDDÚ§šü1hÒFþزe æÍ›‡½{÷¢ÿþÕÇü¡[•óÇ£Ÿzdè}Ó¹ò›Ý-Z´ÐX¶£uëÖˆŒŒÔydxLï¯5Ú¿þõ/xxxÔùøG›?yyyêû'+7íŒW>®r÷ûÑã*7‰*÷àÁõ»]W¹ã_ù¸Ê]wU]ƶ:z]Ô¶xÙìÙ³[¼ìçŸÆŸþ‰?þøCcqLjˆÌ™3ÙÙÙÕ®ðèââeeeU†¦öíÛÃÁÁ Õ. ©íÚàååU娯F5¡(--ERR’ÄÕ™ÕßCæMM?Ö­[‡>ú@Ÿ>}j¬‹ùC·TùCÕàT(ꟇG¯ì·¶¶†¹¹9€‡Í –-[ª÷Un‚U¾êßÌÌ 666ê}•Ï_SÓ¥®ç777׸ª«òùëËØšZ¤%•ïÉïÙ£¨îÙ;þ¼Ô¥è„)ý·™™°nݺ*÷j< €×_]½_¡P¨kKLLDqq1<Þ¶mÛðïÿaaa(((À™3gàééÙàó5öxª?~ò™ºû÷ï€Æ¢€DD¤]Ìdê˜?t‡óóªççr¹ëׯGjj* ‘‘‘¼üòË _«*/´-/ÄTZZ*Ú¶m+6nܨ±}üøñbܸqBˆ¿ NNNâÞ½{Û]tìQu9îúõëB&“‰Ó§OW{Ìo¿ý&ÌÍÍk}NÕ÷ä“OŠÈÈÈZ¿¾®çkªãÃÔJvvvDjjªÔ¥I"55UÎÎÎR—BDd2˜?ÈÔ1ü…óó¿H5?Bˆ{÷î‰>úHôîÝ»Áãkƒj~®Ó+uär9¦L™‚M›6©·åææ"<<3fÌÐ8vÚ´i°³³ÃgŸ}ÖäulÚ´ =zô@=ª=&22={ölÐùÓÒÒššŠ¸¸8¸ººÂÞÞAAA©MúOõq†v®‰LÅÝ»w@ãc?‰ˆH»˜?ÈÔ1èçç5ÏÏLJV­ZÁÊÊ ÑÑÑ8zôhƒÆ×66u€‡/†˜˜\¸p°}ûv899aРAÇ™››#$$«V­Bzzz“_VV†-[¶ 88¸ÚcöìÙƒÕ«W#44´Acäää®^½Š„„\¾|xóÍ7t>’†ƒƒ 33SâJˆ¤‘••°··—¸""ÓÁüA¦ŽùC·8?¯Þ÷ß‚‚\¹rYYYX°`AƒÆ×67uÜÜÜàëë«î~óÍ7˜>}z• aùúúÂ×× .l²ñúé'jܯWYXX&OžŒððpôêÕ«Ac¨ÞaY±b,--aooE‹áçŸnpݤ{J¥-q%M§¶¼®^½ ___( 899á“O>ÑØ¯ëË~ûí7x{{ÃÊÊ ÖÖÖð÷÷ǵk×5^}Ž¿ÿ>&Mš[[[´iÓ|ðz_CP34ª?€vvvWBDd:˜?˜?jÊ••••ÁËËËè>4…ùC·8?¯]§N°bÅ lÞ¼¹Aãk›Î›:ŒíÛ·ãØ±c¸xñ"&Ož\í±«V­ÂÎ;ñÇ4ÉØ7nÄøñãÕÿc+Û²e ¦OŸŽ½{÷ÂÏϯÁc¸ººÂÚÚZãA&“i¬NúOõ‡Dõ‡ÅÔ´€WEE^~ùe¸»»#;;ñññˆŒŒÄwß}§>F— –åççcĈ@^^ÒÓÓáàà€1cÆ4x¼úÿî»ï"==7nÜ@ll,¶mÛ†¯¿þ@ÃP34ª …j‚ADDÚÇüÁüQSþ¨lÍš5°µµ­Ó˜†„ùC÷8?¯]yy9š5kÖà´ªòB;ÐòBL*%%%ÂÁÁA¸ººŠ€€}U-,4wî\aoo_ë‚CEEEâøñã€(,,EEEûSSS…™™™ˆ‰‰yìk×®]+ìììÄ©S§êüàý÷ßÇúõëuR˜?ˆê‡M¢j¨þ¨þ°˜‚gžy‘‘‘ÈÍÍE||<   qŒ®,KLLDbb"æÎ‹-Z uëÖ˜9s¦Î>EÎÕÕ …Bãòï³gÏ¢k×®Õ~^/ VOB¤¤¤@&“1TéóóGMùãÊ•+HKKƒ··7ìííÕß GGG„‡‡ë¤Fmbþ ª?6uˆªaii‰¶mÛ¢¨¨·nÝ’ºœ&CÁÙû IDATS\\Œxø±ÜÅÅÅê}—.]R¿3…,Y²D½ݺuX°`8€þýû7j}Æ «Ó÷FŸ15@å{²À{ö¨¦¶¦ŽB >L¦KD¤O˜?ÈT1Õ›:D5ðôôðpý"S¢šH¨&DD¤;Ìdª˜?ˆêM¢téÒr¹ (++“º"xðà.^¼ <ûì³R—CDdr˜?È15 ›:D5°´´„——ŠŠŠ'u9D:qæÌ””” Güä "" 0)bþ j6uˆjÑ·o_À‰'$®„H7Ž?ðññ‘¸""ÓÅüA¦†ùƒ¨aØÔ!ªE¿~ýÇŽ“¸"Ý`¨""’󙿢†aS‡¨|§ŒLj¡šP‘î1©aþ j6uˆjÑ®];têÔ HLL”º"­ºxñ"233ñì³ÏÂÁÁAêrˆˆL󙿢†cS‡¨† ˆˆˆ¸"íR½ÆU¯y""’ó™ æ¢†“Wµ1))I×u0å×…¿¿?Ö­[‡ýû÷ãoû›Ôåi C‘þ`þ SÁüQ3Sž‡QõT¯ ™B¨6Êd2É "ÃqþüyxxxH]†NA©T²³³aii)qEDM¯°°öööhÖ¬²²²øq¢DDcþ SÀüQ=ÎÏ©.4®Ôqss“ª2 R— s–––Ú¶m ¹\Ž[·n¡eË–R—DÔh………h×®„ÈÈÈ@«V­¤.‰ˆˆ*aþ cT—ü‘””„°°0„……á?þ€jêjii‰aÆa̘11b¬­­u]>‘Þà•:Dõ`mm±cÇ"??;w¢&±cÇàµ×^cC‡ˆH11ªKþpssûヒ“'O"%%!!!èß¿?JJJŽ   ´iÓ#FŒÀÖ­[‘››«ãgA$=^©CTOÇG¿~ýàããƒcÇŽI]Q£õíÛÑÑÑ8qâžþy©Ë!"¢*0±iLþÈÈÈ@xx8víÚ…£G¢¼¼ЬY3 4cÆŒA@@ìííµQ:‘^aS‡¨ž}öY$$$àâÅ‹èÒ¥‹Ôå5Ø… àáá®]»">>^êrˆˆ¨Ìd,š2dff"<<aaaˆŠŠBii)ÀÜÜĨQ£0zôh8::6EéDz‡·_5ÀôéÓ_}õ•Ä•5Žê5|sæÌ““ €5kÖ ==½Ñcé^©CÔyyyèС -- ­[·–¸"¢úËÊÊ‚³³3ÌÍÍqãÆ ~”9‘žcþ c «ü‘ŸŸ½{÷",, P$ºL&ÃsÏ=‡Q£Fa̘1puuÕÊøDºÂ+uˆÀÖÖS¦LAaa!BCC¥.‡¨A¾úê+aêÔ©lèæ2ºÊÖÖÖxã7ŽÌÌLüðÃ;v,Z¶l‰“'Oâ½÷Þƒ››zõê…O>ùW®\ÑZ-DÚÄ+uˆèÚµkèܹ3žxâ ¤¤¤ÀÂÂBꒈꬨ¨ÎÎÎÈËËÃõë×áìì,uIDDTÌdÈô!áÀعs'~þùgäçç«÷uëÖ £GƨQ£ÐµkW×FÔM¤¤$)k!Ѿ}{ÉÉɘ9s&8€ 6 88Xê’ˆê,44sæÌÁk¯½†~øAêrˆˆ¨±cÇæ28ú–?JJJ‰;wbïÞ½‰îîî®nðxyyIRççTM™L&e-d Ο?©ËÌ¥K—°råJ|ÿý÷èÞ½;bccáììŒÄÄD4oÞ\êòˆjUTT„§Ÿ~8}ú´dA…ˆˆæÔ©Sðööfþ ƒ¢ïù£´´QQQøñÇެ¬,õ¾Ž;b̘1=z4zõꥳy3ççTU6uÜÜÜ$+ˆô—ªSlªM¸¸8¬X±ááᨨ¨@³fÍðÆo ##À_|9sæH]&Q­BBB0oÞ<¼üòËØ»w¯ÔåQøûûã—_~aþ ƒaHù£¼¼GÅ®]»†Û·o«÷9;;«<ÞÞÞ03ÓÞ2µœŸSMTóó*›:\f‡ªÒ­[7ÄÇÇ›\Sçĉøøã!Z´h©S§âÝwß…‹‹ bbbðÜsÏ¡mÛ¶¸ví,--¥.™¨Z÷îÝCÇŽñçŸâÌ™3èÞ½»Ô%Q0!1äüQQQãÇc×®]øñÇ5>½]»v=z4F~ýúÁÜܼIÇæüœj¢šŸóÓ¯ˆª‰AƒÁÇÇ¿üò ¬¬¬0oÞ<$%%!44...€Þ½{cäÈ‘ÈÈÈÀúõë¥-š¨k×®Å;w0zôhƒ TDD¤‰ùƒ ‰!ç333ôïß!!!HKKCtt4Þ}÷]¸ºº"##_|ñ^xá´k×o½õ"##QVV&uÙdBx¥Õ™)\©#„ÀO?ý„O>ù'OžððãCçÌ™ƒ¹sçB©TVùuçÎC= P(píÚ5888è²l¢:¹}û6ž~úiáܹsxöÙg¥.‰ˆˆùƒ 1çÓ§O#,, »víÒøHt¥R‰‘#GbìØ±ðõõmð‡Ìp~N5á•:D•TTTà‡~€——^yåœûì3uwÝÉÉ ,@pp0Z¶lYçsU~"66Öà.-%ã‹çž{ŽïææÒg¦š?.]º¤¾‚'..N½½U«Vð÷÷ǘ1càïï_ë\ƒósª ¯Ô!“V\\Œ/¿ü:uÂÔ©SqåʸººbÆ HJJÂܹsëÕÐGGG,Z´åååxçwøË—ôFEEÞ~ûmTTT`éÒ¥&¨ˆˆLóé+SÎîîîX²d Μ9ƒk×®áÓO?EïÞ½QXXˆ~øcÇŽ…ƒƒÆŒƒÿþ÷¿(((ºd2`¼R‡êÌ®Ô),,ÄÆ±jÕ*ܺu ÀÃ_º‹-¸qã —ËuþââbtéÒÉÉÉØ¸q#f̘Ñe5Ê—_~‰Ù³gãé§ŸÆ… |_7é'æÒGÌKKKÃ?þˆ°°0œ@vv6úöí‹ýû÷ãôéÓ=zt“6t`ذa B^^Þ~ûí&=7Q}Íž=ùùù˜4i‘cþ }ÂüQ»víÚáí·ßÆ‘#G‘‘¯¾ú ~~~¨¨¨À/¿ü‚àà`©K$Á+u¨Î éJ›7obÕªUظq#îß¿ðóóÃâÅ‹ñ /h}ü¬¬,¸»»#++ ;vìÀرcµ>&Ñ£¾ÿþ{Œ?mÚ´Á¥K—`gg'uIDD¤E̤˜?';;{÷îŹsç°víZœŸSÕx¥¥ääd¼ùæ›pssÚ5kPTT„‘#G"::‡ÒICìíí±zõjÀ[o½…›7oêd\"•ÔÔTÌš5 °nÝ:*""ÀüARcþh<¥R‰)S¦`Íš5R—B‚M2 —.]„ ЩS'lܸeee D\\öìÙooo×4aÂŒ3999˜4i’z!4"m+//ǤI“››‹ÀÀ@J]éóI…ùƒHlêA‹‹‹Ã˜1cеkWlß¾2™ “'OFBB¾ÿþ{xzzJZ߆ àää„_ýŸþ¹¤µéøì³ÏpôèQ<ùä“øê«¯¤.‡ˆˆtŒùƒ¤ÀüA$ 6uÈ 8qÇGÏž= Ìš5 ‰‰‰Ø²e :wî,u‰;;;|÷Ýw033Ã’%KpêÔ)©K"#wüøq|øá‡077Çwß}[[[©K"""cþ ]cþ ’›:dP"##1hÐ øøø`ÿþý°²²Â¼yó””„ÐÐP¸¸¸H]âcŒ àÁƒ3f 233¥.‰ŒÔíÛ·ñÚk¯¡´´ÿøÇ?0`À©K"""‰0®0I‹MÒ{BìÝ»Ï?ÿ<† ‚#GŽÀÖÖK–,AJJ >ÿüs´mÛVê2k´råJ 8ééé Dyy¹Ô%‘‘)--Åk¯½†ŒŒ 2~ø¡Ô%‘Ę?HÛ˜?ˆ¤Ç¦é­ŠŠ üðÃðòòÂ+¯¼‚“'OÂÁÁ+V¬@jj*–/_¥R)u™ubnnŽ;v }ûöˆŠŠÂ{ï½'uIddÞ{ï=üþûïpvvÆÿûÿæææR—DDDcþ mcþ ’›:¤wJKKñí·ß¢K—.7nÎ;'''„„„ %%‹-‚µµµÔeÖ[›6m°sçNXXX`õêÕØ¼y³Ô%‘‘øÏþƒ5kÖ yóæ ƒƒƒƒÔ%‘ž`þ maþ ÒlêÞ(..Æ—_~‰N:aÊ”)¸rå \]]±aÃ$%%aîܹhÙ²¥Ôe6Š··7Ö¯_˜9s&>,qEdè8€·ß~2™ ß|ó zõê%uIDD¤g˜?¨©1é6uHr………X½z5ÜÜÜ0{öl¤¤¤ÀÝÝÛ¶mCbb"‚ƒƒaaa!u™MfÆŒX¸p!>^ê’È@ÅÇÇ«×Hxÿý÷$uIDD¤§˜?¨©0é6uH2yyyøøãáââ‚ùóçãÖ­[ðòòÂ?þˆ .à7Þ€\.—ºL­X¹r%‚‚‚p÷î]øûû#==]ê’ÈÀ¤¦¦ÂßßwïÞEPPV®\)uIDD¤ç˜?¨±˜?ˆô›:¤s™™™X¼x1ž|òI|ðÁÈÎÎFß¾}±oß>œ>}£G†™™q¿4e26oÞŒÁƒ#==ƒ Â;w¤.‹ DFFüüüžžŽÁƒcóæÍÉdR—EDDzŽùƒƒùƒH?åÌ9662™ }úôâ±íeee>|8¦M›öØ× !ðÔSO!$$D}¼B¡ÐxLž<¹Ú±çÏŸwwwXYYÁÎÎãÇÇíÛ·µñ4 ÎÍ›7ñ÷¿ÿ...X¹r%òóóáç燨¨(?~þþþ&õ‡Á»víB=píÚ5 2YYYR—Ez.33/¾ø"®]»†Þ½{c×®]Fu{"ió5ó5†”ós•²²2xyy©Ç3&FÙÔQINNƶmÛªÜ7cÆ ìØ±………Û£¢¢žžŽ‰'ª·ååå¡°°PýøöÛo«S¡P¨Ï›˜˜ˆââb6Éó1TÉÉÉxóÍ7ѱcG¬Y³EEE9r$¢££qèÐ!¼ð R—(DDD k×®ˆW_ÎJT•»wï⥗^Â… àááýû÷ÃÆÆF겈ˆÈÀ0P}0PS‘b~®²fÍØÚÚ6ª~}eÔM>ú‹-Âýû÷Û7bÄ´jÕ ÿûßÿ4¶oÚ´ P*• ÓÃÃ2™ ööö˜7oŽ?Þ sºK—.a„ èÔ©6n܈ÒÒR"..{öì···Ô%êŸ>B`ïÞ½xþùç1dÈDEEÁÖÖK–,AJJ >ÿüs´mÛVê2 ’R©ÄáÇñ / ==ýû÷GDD„Ôe‘Žíß¿ @FF|}} ;;;©Ë"""#ÅüAó醮æçW®\AZZ¼½½aoo¯ž›;::"<<¼ÁçÕ;¢â‘M)&&F¥¥¥êmsçÎöööm/))ÂÕÕUÔzžÚ¬]»VØÙÙ‰S§N5þ‰è@üë_ÿžžžê׋ƒƒƒX±b…¸{÷®Ô%•’’1aÂ@Èår±aéK"ùòË/…¹¹¹ &Ož,JJJ¤.‰ˆˆLó‡ébþÐ?œŸ7n~þàÁqãÆ õcß¾}€HII÷ïßoš''!ÕüÜ$®Ô€eË–AñØv Lš4 ÉÉɘ>}z•_kkk …B¡~ :´ÚqÞyçäççcðàÁ_S^^ÞdÏEjï½÷Î;'''„„„ %%‹-‚µµµÔ¥ lݺK—.EYYÞ|óMüíoCii©Ô¥‘–ú›7o†………Ô¥‘‰`þ0=Ì$]ÌÏ›5k†öíÛ«mÚ´899ÁÒҲ鞌Äd¢ÒwRu¯ZUß\¢nݺ!>>NNNXºt)&Mš„æÍ›K]–Iøî»ïðæ›o¢¸¸ýúõÃŽ;x{›‘¹yó&ÆŽ‹èèhXZZâ믿FPPÔe‘ cþ0~Ìúósª‰j~n2WêPÓùùçŸ̆ŽMœ8ÇŽÓO>‰cÇŽ¡gÏžøý÷ߥ.‹šÈ‘#GгgODGGÃÕÕ'Nœ` """É17æ"ãÀ¦Õ›¹¹¹Ô%˜¤ž={âôéÓ2dnݺ…ÁƒãŸÿü§QÝÚgjÊÊʰtéRøùùáÎ;x饗‹îÝ»K]æcÄüAd\ØÔ!2 J¥¿üò –-[àὨDJJŠ´…Q½%%%¡ÿþX¾|9d2>þøcüüóÏü„ ""Ò;ÌƃùƒÈø°©Cd`ÌÍÍñá‡âèÑ£pqqÁñãÇáéé‰ï¾ûNêÒ¨Ž¶lÙ///œæ"ãÄŸ`"Õ·o_œ;wo¼ñòóó1iÒ$ 6Œïšé±¤¤$ :S§NUÿ?‹‹‹ƒ···Ô¥Õ ó‡áaþ 2nlê0kkklÛ¶ áááh×®8€®]»bÍš5¨¨¨º<ú?eeeøüóÏáááC‡ÁÉÉ {öìÁ·ß~‹V­ZI]Q½0æ"Ó Ó¦ÎðáÃ1mڴǶ !ðÔSO!$$±±±ÉdèÓ§ÆG·©¶—••Uyî›7oÂßßvvvUWùsì ,,,`mm­Þ?þ|¸»»ÃÊÊ vvv?~~üx1nÜ8!„111€ NNNâÞ½{ÛKKKk£.Ç]¿~]Èd2qúôéjùí·ß„¹¹y­Ï©ºñž|òIYë×kk|mðððÄùóçµ>5Nii© ööö€°´´‹/¹¹¹R—fôrrrÄ¢E‹„¥¥¥ ìííEhh¨N~F‰ˆˆ¤Äü!æãÄùù_¤˜Ÿ§¤¤ˆÎ;‹¨¨¨:Ϲ¥˜ŸëôJ¹\Ž)S¦`Ó¦Mêm¹¹¹ÇŒ34Ž6mìììðÙgŸ5y›6mB=УGj‰ŒŒDÏž=tþ´´4¤¦¦"..®®®°··GPPrssë|ŽÆŒO$—Ë1kÖ,\½z³gÏFYYV¬X777|üñÇuã©ñòóóñÏþnnnX¹r%ÊËË1þ|\½z³fÍ‚\.—ºD"""­bþÐ=æj ÎÏkžŸÏ™3Ë—/‡B¡hи:S¹Ó-w…ø« /„bݺu¢cÇŽ¢¢¢B¡ÙÙŠŒŒ-[¶7nÜh²N`ii©ptt6l¨ö»wï …BÄÄÄÔú|ª/..NÁÁÁâþýû"33S¼ð bìØ±µž¯)Æ×^©c¸®_¿.&Nœ(ÌÍÍÕïÞ|úé§âîÝ»R—fðîÞ½+>ùä¡T*!—ËÅäÉ“ERR’Ô¥IŠùC{˜?LççI1?ß¹s§xñ_7ôå…IDATÅëôj_[Tós7u„ÂÏÏO¼óÎ;B!<==Å'Ÿ|¢Þ÷è7áå—_AAAMö¢Ùµk—P(¢   Êý?þø£°µµ‡ªÓs©j¼«W¯ "33S½íàÁƒÂÒÒ²Öó5ÅøÚ¦Žá»|ù²7nœ033„µµµX°`HKK“º4ƒ“––&,X ¬­­^öhf&^ýuqåÊ©K#""Ò+ÌM‡ùôp~.Íü€‡¿'ºwïŽaÆaذaxþùçѬY3‰«m˜’’œ:u ˆˆˆÀÙ³gÕ¿ÿlllðÊ+¯ 00~~~°°°¸Z"""ãÇüÁüAáüœj¦Õ›:¦«¸¸Àîݻ۷o«÷YZZ¢W¯^èׯ|||ðüóÏÃÎÎNÂj«—“'Oâ÷ßlj'ƒââbõ~GGG 6 xñÅѼys «%""2mÌdê8?§š°©CõƦ?œ={VýîRtt4JKK5ŽéС<<<àáánݺÁÝÝ®®®°µµÕI¹¹¹HIIABB‚ú5{áÂܸqCã¸æÍ›ã¹çžS¿ë×½{wõïA"""ÒÌdŠ8?§š°©CõƦU¥¨¨±±±8vìŽ?Žèèhõ½ðjݺ5\\\àââ‚öíÛC©Tªhݺ5€‡ï¾µhÑBãk‹‹‹QTTàá;^>ÒÓÓ‘’’‚äädäååU9¾R©„··7ú÷ï¾}û¢wïÞCDDDúùƒLççT6u¨ÞØÔ¡ººqãâããÕ¯—K—.Õvšš*¼uéÒEýn]×®]Ñ¡CŒODDDºÇüAƆósª‰j~.—º"2>:t@‡àïﯱ]uYrJJ ÒÓÓ5ÞéÊÌÌDnn.€‡ï¾U¾×xø ––– ñ›êѾ}{¸¸¸èô2k"""ÒÌDdŠØÔ!"iݺ5Z·n ///©K!"""ÁüADÆÌLꈈˆˆˆˆˆˆ¨þØÔ!"""""""2@lê 6uˆˆˆˆˆˆˆˆ ›:DDDDDDDDˆM""""""""Ħ‘’Wµ1))I×uà낈ˆˆˆˆH·8£ª¨^U6u:vì¨ÓbˆˆˆˆˆˆˆèqœŸSMþ?Ó΋d?X¹IEND®B`‚frr-7.2.1/doc/figures/fig-vnc-gw.txt0000644000000000000000000000000013610377563014120 00000000000000frr-7.2.1/doc/figures/fig-vnc-mesh.dia0000644000000000000000000011256413610377563014400 00000000000000 #Letter# ## ## ## #NVA 1 192.168.1.100# #NVA 2 192.168.1.101# #NVA 3 192.168.1.102# #NVE 7 VN 172.16.6.1# #NVE 8 VN 172.16.8.1# #NVE 9 VN 172.16.134.1# #NVE 4 VN 172.16.4.1# #NVE 5 VN 172.16.130.1# #NVE 6 VN 172.16.132.1# #NVE 1 VN 172.16.0.1# #NVE 2 VN 172.16.2.1# #NVE 3 VN 172.16.128.1# frr-7.2.1/doc/figures/fig-vnc-mesh.png0000644000000000000000000016714413610377563014433 00000000000000‰PNG  IHDRØÔÙ¤=bKGDÿÿÿ ½§“ IDATxœìÝy\Uuâÿñ÷å"ʢ▚æ6Ž™¥9-S~A ®’‚æ ¹"X”‰iå”k錚KRZ¹¤¥âVâ†(‰ˆ¡ŽY“¡™ã8.©i¹¡"x¹¿?fâ—£¦Éò¹ÀëùxÜGuιçó>ôø<€7Ÿ{ŽÅáp8¥XLLŒBCCMÇ€“kÚ´©RSSMÇ(ñöîÝ«fÍš™Žb --M+V4£Ä«[·®~øáÓ1àäÖ¯_¯   Ó1À(Ó€âÌÕtp!!!Z²d‰ép2¬¨2ƒƒ¸ooo]¼xÑtŒRçØ±cªS§Žép2:tP\\œéàXÁäl@>P°ù@Áäl@>P°ù@Áäl@>P°ù@Áäl@>P°ù@Áäl@>P°ù@ÁälP‚|õÕW²X,úóŸÿ,‡ÃqÃök×®)((Haaa7¼×áp¨aÆš1cFÞñ^^^×½ú÷ï˱Ož<©ÀÀ@U®\9o, 439‡®x@žžžª\¹²z÷î­Ó§OÆeÅ‚©ùxõêUµnÝZ5jÔ———*V¬(›Í¦;wÖ¥ ¡`€èÈ‘#Z´hÑM÷…‡‡kÅŠJOO¿n{RR’Nœ8¡¾}ûæmKKKSzzzÞkáÂ…·ÓÅÅEÁÁÁŠŽŽ.kJ óÑËË+ï¼TVV–zöìY ×gE=]]]õÞ{ï騱cJOO×?þ(uìØ±À® à(Ø ?~¼^ýueffÞ°¯C‡*_¾¼–.]zÝöyóæ)88XUªT¹«1kÖ¬©ððp5nÜø®Þ”T&æãøñãÕ´iSY,U­ZU/¿ü²¶oß~WçJ’¢žV«UM›6UÙ²e%I‹E®®®jРÁÝ]ÀiQ°@ ¦Ê•+kòäÉ7ìsuuÕ€4oÞ¼¼m.\Pll¬ÂÃË2&P*8Ã|ܼy³}ôÑ;P\™š½zõRùòååéé©;w*999_ç8 6(¬V«f̘¡©S§êĉ7ì ÓîÝ»µoß>IÒâÅ‹U«V-ùùù]w\ÕªUåíí÷zï½÷Š$?P’˜žkÖ¬ÑôéÓ5kÖ¬ü_ PÌ™šË–-ÓåË—õÏþSgÏžÕˆ# î¢N‚ J(›Í&›Í¦‘#GÞ°¯Aƒ²Ùly¥Ÿ?¾ $‹ÅrÝqgÏžUZZZÞëÅ_,’ì@Icj>~öÙgêß¿¿bccõØcÌÅÅœÉï5ÒĉõÑGåÿBN…‚ J°©S§jåÊ•úòË/oءŋ+%%Eß}÷Ýo>‘@þõ|\°` ¤µkתM›6ù>P’˜üþh·ÛU¦L™='À< 6(Á5j¤ÈÈH;ö†};w–‹‹‹úöí«Ž;ªFù/++KÙÙÙ’¤«W¯*+++ßçJŠ¢œÑÑÑ1b„6mÚ¤–-[æë\@ITTóñÛo¿ÕæÍ›ó¾78p@£FRhhè]Ÿàœ(Ø „;v¬Ç ÛÝÜÜÔ¯_?9rDƒ ºé{½½½ååå•÷ øÍ±ÜÝÝåãã#Iòòò’»»{þ/(AŠj>:T—.]’¿¿ÿuï±Ûív-@qWó1''G#GŽT•*Uäåå¥ÀÀ@µoß^3gÎ,Ðk˜gqÜì» ”"111 UHHˆ–,Yb:œÌÞ½{Õ¬Y35mÚT©©©¦ã”x|½q;ÞÞÞºxñ¢ÒÒÒT±bEÓqJ¼ºuëê‡~бcÇT§NÓqàd:tè ¸¸8­_¿^AAA¦ã€Q¬`ò‚ È 6 (Ø€| `ò‚ È 6(AAA »a»ÃáPÆ 5cÆ }õÕW²X,úóŸÿ,‡Ã‘wÌ/Û¯]»vÓsŸ|¸xàyzzªråÊêÝ»·NŸ>}Ëk¹Ýx’ôñÇëPùòåuß}÷iõêÕ·<_ff¦úõë'oooÝsÏ==zô-½ÓñßÂ|¼õ|,ŒñQº1ßn=ßnw¾ÛåÛ¶m›ž|òIyzzªB… Ô¡C‡ò•p÷(Ø „‡‡kÅŠJOO¿n{RR’Nœ8¡¾}ûæm;räˆ-ZtÇçvqqQpp°¢££oº?==ýºW«V­’·ßËË+/ÛÁƒ•••¥ž={Þõx‹-ÒÛo¿­Ï>ûL—/_Ö?þñ=üð÷<ß+¯¼¢'Nèøñãúꫯ´hÑ"Í;÷®Çn‡ùxëùXÐãÌ·[Ï·Ûï·ò]ºtI:tPpp°ÒÒÒtâÄ U«VMݺu»ëñùä€RnÉ’%IŽB#''ÇQ³fMÇœ9s®ÛÞ»woG¯^½‡Ã±{÷n‡$ǬY³µjÕrddd\·=''ç7Ǹ“ãþýï;,‹ã믿¾å1Û¶msX­ÖÛ^Ó­Æ«[·®cóæÍ·}¿ÃñŸ¯‹———#)))oÛ´iÓO>ùä]_ÐRSS’M›6-ÔqðEñõf>Þ¹üŽ_*V¬èäHKK+ô±àpÔ©SÇ!ÉqìØ±»z?óíöîô:ï»ï¾sHr¤§§çíOIIq¸ººØxw"((È!ɱ~ýú|Ÿ Š;V°@puuÕ€4oÞ¼¼m.\Pll¬ÂÃï;6,,L•+WÖäÉ“ <ǼyóôÈ#è‘G¹å1›7oÖ£>zWçÿá‡tìØ1}óÍ7ª_¿¾ªV­ªÐÐP]¸pá¦Ç9rDééé×ý…¿yóæÚ·oß]Ü æãÍçcAHÌ·ß3ßnç×ù5j¤GyD3gÎÔÕ«Wuá½ÿþûêÝ»wø}(Ø ˆ„……i÷îÝyåÑâÅ‹U«V-ùùù]wœÕjÕŒ34uêT8q¢ÀÆ¿víš,X ˆˆˆ[³fÍMŸ>]³fͺ«1Ο?/Iú׿þ¥ýû÷ëÀúñÇ5xðà›ÿËG†<==ó¶yyy)==ýºûðùx{ùøó-ÿþ7Ÿ«««bccµxñâ¼{´ýôÓOš3gNŒøý(Ø ˆ4hÐ@6›-ï¯øóçÏ× Aƒd±Xn8Öf³Éf³iäÈ‘6þºu딞ž~Ýýg~í³Ï>Sÿþý«Ç{ì®Æðòò’$Mœ8QîîîªZµª^ýu­_¿þ7ÏÈÈÈÛ–žž.//¯›~]€‚Â|üm1>ð æ[þÜ,_vv¶Ú¶m«ÐÐP¥§§ëüùóª^½úoÞCP¸(Ø EDDhñâÅJIIÑwß}§þýûßòØ©S§jåÊ•úòË/ dì9sæ¨wïÞy¿üÚ‚ 4hÐ ­]»VmÚ´¹ë1êׯ¯ *\÷K“Åb¹åj´úõëËËËK©©©yÛöìÙ£‡zè®3wŠùxs5>ðkÌ·»s«|ÔÁƒ¥råÊ©R¥Jzþùç ¤ÐÜ 6(B;w–‹‹‹úöí«Ž;ªF·<¶Q£FŠŒŒÔرco{Þ¬¬,eggK’®^½ª¬¬¬ëöÿðÃJHH¸éÇc¢££5bÄmÚ´I-[¶¼£ë¸ÕxV«UÔèÑ£óî 3iÒ$uìØñ¦çquuUŸ>}4aÂ]¾|YÇWtt´˜¯ëîóñF9>ðk̷ߟÿ·òÕ©SGžžžzï½÷”““£Ë—/ëÃ?TÓ¦Mïz<@>™}ƘWOýµ#F8$9âââ®Û~³§z]¸pÁQ¥J•Û>íKÒ ¯_3fŒãOúÓ-ßëêêêðôô¼îuíÚ5‡Ãápüãÿ¸î¿o7^VV–#""ÂQ¥JG… ={ötœ?>oÿÿž/##ÃѧOG… U«VuŒ5êº|¿wü‚ÆSD‹VQ½™×Ÿ¯ Ç/ êpwwwxzz:Ú´iãøþûïïz¼»ÁSDàÿ³8ÜE@é£ÐÐP…„„hÉ’%¦ãÀÉìÝ»WÍš5SÓ¦M¯û(+ _oÜŽ···.^¼¨´´4U¬XÑtœ¯nݺyOȬS§Žé8p2:tP\\œÖ¯_¯   ÓqÀ(>" äl@>P°ù@Áäl@>P°ù@Áäl@>P°ù@Á䮦€³ˆ‰‰QLLŒé$íÝ»W‹Åt ÿU·n]Ópj¬`òÁâp8¦Cðk‡C­[·Ö_|¡aÆiúôé¦#F\¼xQÞÞÞªX±¢ÒÒÒLÇJ¥'Nèé§ŸÖþýûU·n]mܸQ76 àdXÁp:‹E#GŽ”›››f̘¡×^{Ít$@)tàÀùúújÿþýz衇´}ûvÊ5ÀMQ°œR`` V¬X!777M™2E£G6 (rnnn’¤ììlÃI€Òg×®]òõõÕ±cÇäëë«mÛ¶©V­Z¦cœÀiuîÜYK—.U™2e4aÂ7Ît$ H¹»»K’®\¹b8 PºÄÇÇËf³éܹsêÔ©“T©R%Ó±NŒ‚ àÔºvíª%K–Èjµjüøñš0a‚éH€lÑ¢Eêܹ³2224pà@­Zµ*¯ìàV(ØN¯{÷îZ´h‘¬V«F­)S¦˜Ž(¦M›¦~ýú)''G¯½öšæÍ›'«Õj: à)¢€bã“O>ÑÀe·Û5}út 6Ìt$ ÐY,Iÿyº.€Âáp8ôÊ+¯hÚ´i²X,š>}º¢¢¢LÇ#®¦p§úöí+»Ý®°°0½üòËrqqÑСCMÇc999 Ó¢E‹äææ¦ ($$Ät,@1à 6@±3oÞ}´bÅ Ó‘€S®\9IRVV–á$@ñ¶~ýzµiÓFçÏŸW§N´qãFy{{›Ž(!(Ø%Â믿®qãÆÉn·+44TŸ}ö™éH'±páB+33SaaaZµj•ÜÝÝMÇ” l€cìØ±5j”®]»¦­Y³Æt$€a“'OÖÀuíÚ5½ñÆš;w®¬V«éX€†‚ P¢¼õÖ[zíµ×”­=zhÆ ¦# p86l˜FŽ)‹Å¢èèhM˜0!ïɼ$ 6@‰3iÒ$½üòËÊÎÎVpp°6nÜh: åää¨oß¾zçwäææ¦˜˜ 2Ät,@ FÁ(‘¦M›¦¡C‡*;;[]»vÕçŸn: ddd¨sçÎZ¼x±Ê—/¯ 6¨gÏž¦cJ8 6@‰5cÆ ½ð ºr劺t颤¤$Ó‘…èܹs²ÙlŠWõêÕ•””$›Íf:  `”X‹Eï¾û®"""”™™©:hÛ¶m¦c Á±cÇäëë«]»v©AƒJIIÑ£>j: ” `”h‹E|ð¨ÌÌLiûöí¦c о}ûäã㣨Y³fJIIQÆ MÇ”"l€Ïb±hΜ9êׯŸÒÓÓ¤¿ÿýï¦cw¤\¹r’¤¬¬,ÃIç”’’¢V­ZéäÉ“jݺµ’““U³fMÓ±¥  T°Z­š?¾BCCuñâEµk×N»wï6 ¸­²eËJ’®^½j8 à|Ö®]«€€]¸p!ï©ÑÞÞÞ¦cJ! 6@©aµZõñÇ«Gºxñ¢ôüÃt,À]˜?¾ºvíª+W®(""B+W®Ì[ñ @Q£`”*V«UK–,Ñ3Ï<£´´4èÛo¿5 ð;Lš4Iááá²Ûí3fŒ>üðCY­VÓ±¥ ÔquuULLŒºté¢sçΩmÛ¶Ú·oŸéX€Ûp86l˜þò—¿ÈÅÅE³fÍÒøñãMÇ€‚ P:¹¹¹iùòå Ò™3gäïï¯ýû÷›Ž¸…ììl=ûì³zçwT¶lY-[¶L‘‘‘¦c ‰‚ Pй¹¹iÕªUj׮Μ9#›Í¦þóŸ¦cþGzzº:v쨘˜U¨PAñññêÖ­›éXä¡`”j¿”l:}ú´l6›þõ¯™Žø¯_V'$$¨zõêJJJ’ŸŸŸéX\‡‚ Pê¹»»+66Vþþþ:yò¤üýýõïÿÛt,(õŽ=*___íÞ½[ 4PJJŠyäÓ±¸’<<<´nÝ:µjÕJ'Nœ¿¿¿Ž=j:”Z©©©ú¿ÿû?ª””5hÐÀt,ò‚ €ß©víÚÚ²e‹êÕ«§;w*((H¦c¡„òðð$effNäOrr²žzê)>}Z6›MIIIª^½ºéX 6îB:u”””¤:uê(%%E (eÊ”‘$åääNÜ½ØØXµk×NiiiêÙ³§6lØ òåË›Ž@¡`à.Õ«WO[¶lQíÚµµmÛ6uìØ‘’ þÇœ9sÔ½{weeeiÈ!Љ‰‘›››éX( 6òáøƒ¶lÙ’wo¶àà`eee™ŽNáÍ7ßÔàÁƒe·ÛõÖ[o)::Z..ü (yøî@>ýñÔ–-[T£F %$$(88XÙÙÙ¦c€1v»]/¼ð‚ÆŽ+«Õª?üP£F2 €BCÁ@¸ÿþû•˜˜¨jÕªiãÆêÚµ+%€RéêÕ«êÕ«—fÏž­råÊiåÊ•Šˆˆ0 €BEÁ@iÒ¤I^ɧ^½zQ²(U.]º¤öíÛëÓO?•···6nܨàà`Ó±(tl ¦M›*!!AUªTQll¬BCCuíÚ5Ó± Ð>}Z­[·VRR’jÖ¬©äädµnÝÚt,ЬyóæJHH···>ýôS…††Ên·›Ž…æðáÃjÙ²¥öìÙ£FiÇŽjÖ¬™éX 6 Á#<¢„„U¬XQ+V¬P¿~ý(Ù”H{öì‘:¤Ç\)))ªW¯žéX) 6 Éã?®øøxU¨PAK–,Ñ Aƒ”››k:ŠOOOIRFF†á$À’’’Ôºuk>}ZÚ²e‹ªU«f:EŽ‚ €BÔ¢E mذA^^^Z¸p¡ÂÃÃåp8LÇB1âêê*IÜËNçÓO?UûöíuéÒ%…„„hݺuòòò2 #(Ø(d>>>Š‹‹“‡‡‡>úè#=÷Üs”lŠµÙ³g«W¯^ºzõª† ¢E‹ÉÍÍÍt,Œ¡` ´jÕJëÖ­“‡‡‡æÌ™£—^z‰’ @±4vìX½ð ÊÍÍÕ„ -~­”n|' ˆøûûkõêÕrww×{ï½§aÆ™ŽwÌn·kðàÁzóÍ7eµZ5wî\½ñƦcà(Ø(BmÛ¶ÕªU«äææ¦™3gjøðá¦#Àmeee©{÷îš3gŽÜÝݵjÕ*………™Ž€Ó ` ˆµk×.¯d›>}ºþò—¿˜Ž·téÒ%*66V•*URBB‚:uêd:N…‚ ‚‚‚´|ùr¹¹¹iÒ¤I3fŒéHpƒÓ§O«uëÖJJJR­Zµ´mÛ6ùúúšŽ€Ó¡`À.]º(&&F®®®zë­·4~üxÓ‘ Ï¡C‡äãã£={ö¨qãÆÚ¾}»zè!Ó±pJlôÌ3ÏhÉ’%²Z­7nœþú׿šŽúúë¯åëë«Ã‡ë‰'žPJJŠêÖ­k:N‹‚ Ãzôè¡?þXV«Uo¼ñ†¦Nj:œˆ———$)==Ýp”‰‰‰òóóÓO?ý¤€€mÞ¼YUªT1 §FÁ€ ÕüùóeµZõÊ+¯hÆŒ¦#ÁIX­VI’Ýn7œ¥Áòå˨˗/+$$DëÖ­Ë+yÀ­Q°à$úõë§9sæÈb±høð኎Ž6 @)òî»ï*$$DÙÙÙŠŠŠÒâŋ忿f:ÅNdàÀúàƒ$IQQQyÿ…ÅáphÔ¨Qz饗äp84iÒ$͘1C‹Åt4Š WÓÀõ"""tíÚ5½øâ‹ŠŒŒ”««« d:€Èn·ëùçŸ×ܹsåêꪹsçªÿþ¦cPìP°à„"##•““£¨¨(EDDÈjµjÀ€¦c(A²²²¢ØØXyxxhùòåêСƒéXKl8©¡C‡Ên·køðá —«««úôéc:€ --M]ºtQrr²*W®¬õë׫E‹¦cPlQ°àÄ^~ùeåäähäÈ‘0`€Ê”)£^½z™Ž ;uê”Úµk§ÔÔTÝwß}Ú¸q£š4ib:Å9ÀɽöÚkzë­·d·Ûõì³ÏjåÊ•¦#(¦<¨ÿû¿ÿSjjª7n¬””Ê5 ÅÀ¨Q£4vìXÙív…††jÕªU¦#(fvïÞ-___=zTO<ñ„RRRT§NÓ±((Ø(&Ƨ7ÞxC999êÝ»·Ö®]k:Š@ùòå%I—/_6œÅYBB‚üýýuæÌµoß^‰‰‰ªR¥ŠéX”l#&LЫ¯¾ªììluïÞ]6l0 …ÌÅå??®åææN‚â*&&F;vTzzºúôé£5kÖÈÓÓÓt,J 6Йɓ'kذaÊÎÎV×®]µiÓ&Ó‘8©èèh=ûì³ÊÎÎÖðáÃõñÇ«L™2¦cPâP°P M›6M/½ô’®^½ª®]»jóæÍ¦#p"‡C£FÒСC%IS§NÕÔ©Se±X ' d¢` ²X,zçw©ÌÌLuîÜY[·n,ðñó IDAT5 €°Ûí ×ĉU¦L}üñÇ>|¸éX”hlS‹Eï½÷ž ¤ÌÌLé‹/¾0 €AW®\Q×®]5þ|yzzjÍš5êÓ§éX”xlc‹EsæÌÑ€òJ¶;v˜ŽÀ€ .( @k×®UåÊ•µyófµoßÞt,J 6Š9‹Å¢¹sçªoß¾º|ù²µk×.Ó±¡“'OªU«VJIIÑ}÷ݧ/¾øBO>ù¤éX”l”V«U}ô‘z÷î­‹/êé§ŸÖW_}e:€"pàÀùøøhß¾}jÒ¤‰¶oß®&Mš˜Ž@©BÁ@ aµZµhÑ"uïÞ]/^T@@€¾ùæÓ±¢]»vÉ××WÇŽS‹-ôÅ_è¾ûî3 €R‡‚ €ÄjµjÉ’%zæ™gòîÇôí·ßšŽ…|¨P¡‚$éÒ¥K†“ÀÙlܸQ6›MçÎS‡´yófU®\Ùt,J% 6J˜2eÊ(&&F;wÖÙ³gÕ¶m[íÛ·Ït,Ü%‹Å"Ir8†“À™ü2Ç322Ô¿ÅÆÆÊÃÃÃt,J- 6J 777­X±B:sæŒl6›¾ÿþ{Ó±€wÞyGÏ>û¬²³³õÚk¯é£>’«««éX”jl”PnnnZµj•ž~úiýüóϲÙl:xð éXî’ÃáÐÈ‘#5lØ0IÒŒ34iÒ¤¼UŽÀ 6J°²eË*66VmÛ¶Õ©S§äïï¯C‡™Žàwºv효ɓ'«L™2Z¼x±¢¢¢LÇÿEÁ@ çîî®Õ«Wë©§žÒÉ“'åçç§Ã‡›Žàeff*88X .”§§§Ö¬Y£Ó±À¯P°P xxx(..N-[¶Ô‰'äïï¯cÇŽ™Žà6Ο?¯6mÚhýúõªR¥ŠÕ¾}{Ó±Àÿ ` ”ø¥dóññѱcÇäïï¯ãÇ›ŽàŽ?®–-[jçΪ[·®RRRôÄO˜Žn‚‚ €R¤|ùòŠ‹‹Ó“O>©Ã‡Ëßß_'Nœ0 Àÿøþûïåãã£ýû÷롇ÒöíÛÕ¸qcÓ±À-P°PÊT¬XQ7nÔc=¦C‡Éf³éÔ©S¦cø¯]»v©eË–:~ü¸|}}µmÛ6ÕªUËt,ð(Ø(…*V¬¨„„=òÈ#:xð l6›~úé'Ó±p+V”$]¼xÑp…øøxÙl6;wN:uRBB‚*Uªd:¸ 6J©J•*)!!A?ü°¾ÿþ{Ùl69sÆt, ÔZ´h‘:wŒ ………iÕªUrww7 Ü 6J±*UªèóÏ?×C=¤ï¾ûNmÚ´ÑÙ³gMÇJiÓ¦©_¿~ÊÉÉÑ믿®¹sçÊjµšŽî¥\µjÕ”˜˜¨x@©©© Ð… LÇJ‡Ã¡#FhĈ’¤3fhâĉ²X,†“€ßƒ‚ èž{îÑ–-[tÿý÷ë›o¾QÛ¶m¹çPÈrrrÔ¯_?M›6MnnnZ¼x±¢¢¢LÇw‚ H’jÔ¨¡ÄÄD5lØP_ýµ(Ù€B’‘‘¡Î;kÑ¢EòòòÒºuëb:¸Kl O­Zµ”””¤ èË/¿T`` ._¾l:P¢œ;wN6›MñññªV­š¶lÙ¢€€Ó±@>P°€ëÔ®][IIIªW¯žvìØ¡ÀÀ@edd˜Ž”ÇWË–-µk×.Õ«WO)))züñÇMÇùDÁnP§NmÙ²E÷ÝwŸRRR¤ÌÌLÓ±€bmÿþýòññÑ÷߯fÍšiÇŽjÔ¨‘éX P°€›ª_¿¾¶lÙ¢Úµk+99Y;w¦d3à—§I:ÃI;wîTË–-uüøqµnÝZÉÉɪY³¦éX €P°€[jذ¡uï½÷jóæÍêÚµ«²²²LÇ*U*T¨ Iºté’á$¸[ëׯW›6mtþüykãÆòöö6  6ð›5j¤ÄÄDU¯^]›6mR×®]•m:P,,\¸PÁÁÁÊÌÌTXX˜V®\©råÊ™Ž ¸­Æ+11QÕªUS||¼ºuëFÉÜÆäÉ“5pà@]»vMo¼ñ†æÎ+«Õj:(làŽ<øàƒÚ¼y³ªV­ªuëÖ©wïÞÊÉÉ1 p:‡CÆ ÓÈ‘#e±X­ &äÝO”<w̿Þ={d³ÙtþüyuëÖMË–-cUN!úûßÿ®øøxùøø( ÀtÜFvv¶  ˜˜¹¹¹é“O>QÏž=MÇ…Œ‚ ün_ýµl6›.^¼¨ž={jÉ’%”l…àòåË:sæŒÎž=«sçÎéܹsyOr½pá ǻ»»«\¹rrqqQ•*Unx•)S¦¨/¡TÉÈÈP·nÝ´qãF•/_^±±±²Ùl¦c€"@ÁîÊ—_~©€€]¼xQ}úôÑ‚ (Ùî‘#G”ššªƒêèÑ£y¯#GŽèÊ•+6ŽÅbQÍš5U¿~}Õ«WOõêÕSýúõÕ´iS=øàƒòôô,°±J£sçÎ)((H»víRõêÕ§G}Ôt,PD(ØÀ]Û¹s§Úµk§K—.©ÿþš?¾\\¸Åë­¬o¿ýV{öìQJJŠvïÞ}]éæææ&___µk×NAAAjÒ¤‰ÁÄÎ!%%E:uÒ… Ôºuk­Y³F+V4 @Á Dbb¢:uê¤ÌÌL 2D3gÎ,%[ff¦Ö¯_¯+VhÆ ×Ý7­iÓ¦jÛ¶­Z·n­-Z¨Zµj“þ>×®]Ó·ß~«””%%%)11Qéééyû›4i¢=z¨gÏžjܸ±Á¤f¬]»V½zõÒ•+W¬˜˜•+WÎt,`(0 êܹ³²²²¥3f˜ŽT(rss•˜˜¨>úHëÖ­Ë[éåéé©§Ÿ~ZíÛ·W»víT»vmÃI Nvv¶RRR´qãFÅÅÅiÿþýyûš5k¦ÐÐPõïß_÷ÜsÁ”EcþüùüðC=ÿüó’¤÷ß_ƒ.²±‡.\¨áÇëÂ… ªX±¢F­!C†8Íê©ÒæWvîÜY³fÍR­ZµŠ|XmÚ´ÑÀuáÂõîÝ[ßÿ½†N¹fПþô'%%%iñâźçž{´fÍ=øàƒzÿý÷U”ž;w®ºwﮬ¬,EFFjÙ²e”kàŽQ°€"÷â‹/júôér8ŠˆˆÐÂ… m,‡Ã¡™3gªiÓ¦Ú²e‹j×®­õë×+&&Æ)o®_Z…††jÿþýêÛ·¯.^¼¨ÈÈH=õÔS:zôh¡=qâD ûlžÿÔ©Sêß¿¿d±X©¿ýío*_¾|Žƒ‚• ÁƒëèÑ£ªP¡‚Þÿ}…„„ø8¹¹¹ŠŠŠÒ»ï¾+«ÕªÙ³g+""¢ÀÇ%0jÒ¤IúË_þ"«Õª%K–¨gÏžrÞÕ«W+<<\gÏžUÍš5µpáBȹQø.]º¤^xA‹/–$…„„höìÙªX±bœ?;;[}ûöÕòåËU®\9ÅÄÄð p×(Ø€qo¾ù¦ÆŽ+«ÕªeË–©[·nw}®k×®iĈš9s¦$©K—.š;w®ªV­ZPqQ„bbb©‹/ªAƒúì³ÏÔ¼yó|óòåË Vbb¢*T¨ µkתuëÖ””FlÀ)Œ;Vo¾ù¦ÜÜÜ´lÙ²»ZMôÓO?©GÚ¶m›Ê–-«™3géSJQ8Ž;¦^½zéïÿ»<<<ôÁ¨OŸ>wu®Ÿ~úIAAAúúë¯U£F ÅÇÇç»°°Ž7nœé~~~ÊÉÉÑÖ­[«æÍ›ëþûï¿ã÷ïܹSmڴѾ}ûT§NmܸQ:u*ÄÄ(*ÞÞÞêÓ§Î;§;w*66VçÏŸW›6m~×Ã>,}÷Ýwjذ¡’’’ôÀbrPZ°‚ 8•W^yES§N•›››V¯^­öíÛßö=K—.Õ€tõêUùûûkÙ²eªV­Z¤EQ[°`"##•••%›Í¦O?ýTÞÞÞ·}ßž={Ô¾}{>}Z>ú¨âââT½zõ"H JÓ~íí·ßVTT”²³³ÕµkW%$$üæñýë_ª«W¯jèСJHH \+Á  ””Ý{ï½JLL”¯¯¯Ž;ö›ïIJJRëÖ­uúôiÙl6%%%Q®€Å 6àt‡†ªwß}WZ»v­l6ÛuÇäääèùçŸ×üùóeµZ5cÆ 2ÄPbµãÇ+((H{÷îU5´nÝ:=öØc7«eee©gÏžúä“Oäææf 1(É(Ø€Sr8ŠŒŒÔ| mذ!ïIW¯^U÷îݵnÝ:yzz*&&†û­•B—.]R÷îÝ• ///­]»V~~~yûçÌ™£ÈÈHÙív 2Dï¼óŽ\\ø(xü„œ’ÅbÑìÙ³¦ÌÌL)%%E™™™êر£Ö­[§ªU«jëÖ­”k¥T… ´~ýzõíÛWééé R\\œ$éÍ7ßÔàÁƒ•››« &(::šr V°§–››«AƒiÁ‚*_¾¼êׯ¯ÔÔTÕ¨QC›7oÖƒ>h:" s8zñÅ5{öl¹¹¹ÉÏÏO›6m’ÕjÕ‡~¨°°0Ó@ GÁœžÝn׳Ï>«eË–I’êÔ©£Ï?ÿ\52œ ÎÂáphäÈ‘š2eŠ,‹\]]µ|ùr›ŽJÖɧwõêUýøã’¤ hÛ¶m”k¸ŽÅbÑäÉ“5fÌýò÷cWWWé@iÁ 6àÔ²³³Õ©S'mÚ´Iµk×Ö¶mÛT¿~}Ó±àÄþò—¿hÒ¤I*W®œÖ¯_Ãh pZv»]ݺuÓêÕ«uÏ=÷è‹/¾`åîÈK/½¤wß}WÚ²e‹žxâ Ó‘@ ÆGD€Ó2dˆV¯^­*UªhóæÍ”k¸c3gÎÔÀóž:{èÐ!Ó‘@ Æ 6à”Þ~ûm½úꫬ@Â]³ÛíêÚµ«Ö®]«† jÇŽªV­šéX ¢`NgíÚµêÚµ«rssµtéRõìÙÓt$S™™™zê©§´{÷nùúú*11Qnnn¦c€†ˆ§òí·ßªwïÞ²Ûíúë_ÿJ¹†|ñððЪU«T»vm¥¤¤hðàÁ¦#€ˆlÀi\¸pA=ö˜>¬ððpÍ™3Çt$”{÷î•._¾¬Ù³gëùçŸ7 ” lÀ)ØívuìØQñññjÑ¢…¶nÝÊGùP >ýôSõèÑCeÊ”ÑÖ­[Õ¢E Ó‘@ ÁGD€SxóÍ7¯{î¹G+V¬ \CëÖ­›†®ììlõèÑC?ÿü³éH „`0.99Y6›M‹E‰‰‰jÕª•éH(¡ìv»Ú¶m«¤¤$iݺu²X,¦c€bŽlÀ¨´´4õë×Ov»]o½õå •ÕjÕÒ¥KU½zuÅÅÅiÖ¬Y¦#€€lÀ¨^½ziùòåjݺµeµZMGB)¯   •+WN_}õ•š4ib:(ÆXÁŒY²d‰–/_®J•*éã?¦\C‘iß¾½^xá]¹rE½{÷VNNŽéH £`FüüóÏ:t¨$éý÷ßWݺu 'Bi3eÊ=øàƒJMMÕäÉ“MÇÅF„„„héÒ¥êÞ½»V¬Xa:J©¯¾úJO>ù¤\]]µgÏ5nÜØt$P ±‚ ¹¸¸8-]ºT•*URtt´é8(Å{ì1EEEéêÕ« {wƒl H¥§§ëЉ'4þ| 8Ðt$”rjÖ¬™>¬÷ß_Ï=÷œéH ˜a(RS¦Lщ'äïﯘŽÈÓÓS|ð$iôèÑJKK3œ7¬`Eæ‡~Ðý÷ß/»Ý®ÔÔTîw§Ò£G­\¹RQQQš1c†é8 a(2¯¼òв²²ôüóÏS®ÁéL™2EåʕӬY³tàÀÓq@1BÁŠÄŽ;´råJU©REãÆ3¸A½zõ4bÄåääèÕW_5#|D ???mݺUï½÷ž^xáÓq€›JOOWãÆuòäIíØ±C-Z´0 ¬`….11Q[·nÕþð <Øtà–¼¼¼4zôhIÊû'Àí°‚ :íØ±CŸ|ò‰úôéc:ð›rrrtÿý÷ëÈ‘#JJJÒSO=e:pr¬`…*>>^;vìÐ< Óq€Û*S¦ŒÆŽ+I3fŒá4 8 `…jÊ”)’¤±cÇÊjµNÜ™ÐÐP5jÔH_|ñ…vîÜi:prl Ð|óÍ7Úºu«š4i¢îÝ»›ŽÜ1WWW½ñÆ’¤É“'Nœ(4“&M’$EEEÉÅ…;P¼ôêÕK5kÖÔºuëtèÐ!Óq€ã']P(~øá­ZµJU«VÕ³Ï>k:𻹹¹iذaÊÍÍÕ´iÓLÇNŒ‚ ŠY³féÚµk2dˆÜÝÝMÇîJxx¸¼¼¼ôÉ'Ÿ(--Ítà¤(Ø@ËÎÎÖÂ… åææ¦çž{Îtà®y{{«_¿~ÊÌÌÔâÅ‹MÇNŠ‚ ¸µk×êçŸV§NtÏ=÷˜ŽäKDD„$iîܹ†“gEÁ Ü/EDxx¸á$@þ5kÖLþ󟕚šª/¿üÒtà„(Ø@:räˆ6oÞ¬úõë«M›6¦ãâ—²˜Ulàf(Ø@úä“O”››«¾}ûÊÅ…5P2ôìÙSåÊ•ÓòåË•••e:p2üÔ ÔŠ+$I=zô0œ(8åË—W`` ._¾¬ 6˜Žœ þ{÷ŸsýøüymLfLX¡Ñh)e”Ó˜C 9eåÔÌa(„òQNÕÇ·ƒ¬øÊ"3B† £Xr^”ÃP2¬,§9›áÚ®]¿?ún¿o'fÛëÚ®ÇývsûÜ>»®íýXŸºÝº=?¯÷õ ×8p@‡R5ôØc™ÎrU×®]%IË–-3\ È5œ^CaÖ¡C¹»»+&&Fiii¦s€a`¹† …Y‰%Ô®];]»vÛDÀ-Ø@®8zô¨>¬‡zH<òˆé O<óÌ3’¤Õ«W.Ž„ äŠ5kÖH’Ú·oo¸È;O?ý´,‹¾úê+ÙívÓ9ÀA0°€\ñÕW_Iú}€ « *èÉ'ŸÔ©S§´gÏÓ9ÀA0°€»výúuÅÅÅ©xñâ 4䩬9kT``wmëÖ­º~ýº7n¬âÅ‹›Îòø¿ØÀ]Û¾}»$) Àp ÷üýýU¤HÅÇÇ+##Ãtp là®mÛ¶M’Ô¨Q#Ã%@ÞswwWíÚµ•––¦~øÁtp là®Øl6ÅÇÇËÕÕUþþþ¦s€|‘5&gËÀ¹1°€»ràÀ]¹rE5jÔP©R¥Lçù‚ ü76pWvïÞ-Iª[·®á ÿÔ®][’¸EHb`w)!!A’äççg¸È?>>>òôôTRR’._¾l:ÆÀîÊþýû%I5kÖ4\ä‹Å"???Ùíöì‘8/6pWöíÛ'IªU«–á eÚd` l Ç~ûí7]¸pA<ð€Ê”)c:ÈW l ȱ£GJ’zè!Ã%@þ«Zµª$騱c†K€i l Ç’’’$ýþ©R¥Š$éøñã†K€i l ÇØàÌ|||äâ⢤¤$effšÎ1°€Ë:¹“u’p&nnnªX±¢¬V«Nž(Iúå—_ —“Ø@Ž9sF’tÿý÷.̸ï¾û$IgÏž5\Lb`9vþüyIRÙ²e —<»wï–ÅbQýúõe·ÛÿðõŒŒ µoß^ýû÷ÿÃ÷ÚívùúújêÔ©Ù_{íµ×d±X´víÚ¼öo¿ý¦víÚ©L™2Ù×BÎdý½ŸõÏpN l Ç.\¸ I*S¦Œá’‚ëøñ㊈ˆøÓר¥K—*55õ–¯óÍ7JNNVïÞ½%IV«UóçÏW­Zµ4gΜ¼¦‹‹‹:wî¬éÓ§ßý/à䨀ÄÀrèÊ•+²Z­*Uª”ÜÜÜLçXo½õ–Ƨ´´´?¼öÌ3ϨdÉ’Z¼xñ-_Ÿ3gŽ:wîœ=ì+±" IDATî¬ZµJv»]Ÿ~ú©bbbtúô鿽f… 4pà@=ú裹÷‹8)///IRJJŠá`È‘K—.I’J—.m¸¤`ëß¿¿Ê”)£÷ßÿ¯)RDýúõ»åTÚÅ‹µbÅ 80ûk³gÏV÷îÝU¯^=U«VMŸþy~¤CÿÿôfÖiNàœØ@Ž\¹rE’TªT)Ã%›«««¦NªÉ“'+99ù¯÷ïß_»víÒ$I .Ô< æÍ›K’Ž;¦7ªW¯^’¤^½ziΜ9·|®òN±bÅ$I7oÞ4\Lb`9’5àX,Ã%_‹-Ô¢E 3æ¯U­ZU-Z´È>Å6wî\ 0 û¯ûܹsUµjU5hÐ@’¬cÇŽé›o¾É¿_À‰-ZT’”žžn¸˜TÄt(˜²NìdàÁÝ™ýôSæGºSsww—¤?ý =à<8Ár䯒¤{î¹ÇpIáP­Z5 2DãÇÿÃk;v”‹‹‹z÷î­:¨|ùò’¤Õ«WëìÙ³Ú±c‡öîÝ›ýgöìÙZ±bÅß>ÙòƲZ­’~K³þ÷ÄÉ:IÈ-¹8761~üø?jÜÜÜÔ§O?~\ Èþú§Ÿ~ªgŸ}Vµk×–··wöŸ¾}ûêþûïׂ þòZÅ‹W£F$I*^¼xîÿBN ë3³>“8'n0 nݺÓJ—.­sçÎýéû'Mš¤I“&Ýòµ5kÖüé{‹)¢_~ùåo¯Ï‰«ÜÁÃ>€Ä 6CY·†rk!œûÈ¡¬‡d=ìpFY7ÈzØpN l Gøpw@JOO—$-ZÔp 0‰ äîüÿœY':€sb`9RºtiIÒ¥K— —æ\¸pA’T¦LÃ%À$6#¥J•’›››®\¹"«Õj:0"%%E’äååe¸˜ÄÀr,ëÔNÖ)žÂà·ß~S»víT¦LY,eddÜòú‘#GÔ¢E yxxèÐĉoy}äÈ‘ª^½ºJ”(¡2eʨgÏž:}útޝ'IóçÏWõêÕU²dIUªTI+W®Ì~móæÍjРJ”(¡R¥J©]»vJLL¼«ëÝÉûÓÒÒÔ§O•.]Z÷ÝwŸÞ|óÍ;z½ ;wîœ$©\¹r†K€I l DzF…¬‘¡0pqqQçÎ5}úô?¼–™™©:¨zõê:þ¼´aÃ-X° û=Zºt©RSSõóÏ?ëÆêÞ½{Ž®'Iš4i’–/_®«W¯ê‡~P­Zµ$ýþùwÏ<óŒ:wî¬K—.)99Y^^^zî¹çr|½;}ÿk¯½¦ääd8qB»wïVDD„>ýôÓÛ~½ ãQ I;þ9Ô¢E ÅÅÅiãÆ 4“«vïÞ­zõê)==]EŠ‘$>|X>ú¨Î;§²eËJ’6lØ ÿùŸÿÑÖ­[ÿôçlÙ²EÍ›7ÿÇ“bv=IòññÑܹsÕ¢E‹?|Ï¡C‡ôøã+55U%J”$mÛ¶MÍš5Ë~ºå^ïNÞŸ‘‘¡{ï½W111jÖ¬™$iÊ”)Z¶l™vìØñ¯5ÒöíÛµmÛ6˜Î†p‚ äXåÊ•%I¿þú«á’üõßÿÿ¤Ýn×þýûÿò½6lP:urt_ýU¿üò‹öìÙ£*Uª¨\¹r ÖÅ‹%IÕªUSíÚµ5mÚ4ݼyS/^Txx¸zö왣ëÝ©ãÇ+555ûD$=ñÄ:pàÀm½^dý½ŸõÏpN l ǪT©"é÷!Å<üðÃzøá‡õïÿ[ׯ_×¹sç4qâD¥¦¦êÏn Xµj•¦L™¢?þ8G×˺ýðÈ‘#:tè~úé'='ý~‹lÖ_z½ »yó¦Nž<©bÅŠ©bÅŠ¦s€A l Ç|||$IIIIF;ò‹‹‹‹¢££õóÏ?«bÅŠjÞ¼¹7n¬{ï½W‹å–÷._¾\}ûöÕŠ+T·nÝ]ÏÃÃC’4aÂ/^\åʕӸqã´zõjI’ÕjU«V­¬ÔÔT]¸pA÷ßÿß~æ[nÊê»víZö×RSSåáá!‹Åò¯tIIIÊÌÌ”\\ø×jœÿ&rÌÙ6IzôÑGµaÃ]¼xQ òððPÓ¦Moyϼyó4`ÀEGG«eË–9¾V•*UTªT©[Æ(‹Å’}úëçŸÖÏ?ÿ¬W^yE÷Üsî½÷^ <8{€ËkUªT‘‡‡Ç-·ÈîÝ»W5jÔ¸­× º¬¿ï³þ9΋ äØC=$I:zô¨á’ÜuãÆ Y­VI¿ßxãÆì×~üñÇìYß|ó¦Nª7Þx#ûõéÓ§ëÕW_ÕúõëÕ¤I“»ºž«««BCCõæ›ofÆZXX˜:tè é÷Ïý*Q¢„>úè#¥§§ëêÕ«š5k–üüürüûÝÉû‹)¢½ûzõªNœ8¡éÓ§+44ô¶^/èŽ;&IªZµªá`ȱx@åÊ•Óo¿ý¦sçΙÎÉ5Å‹W£F$ý~dñâų_[µj•|ðA•,YR£GÖ¢E‹nyˆÁË/¿¬+W®(00PÙl6›$iÏž=·ü÷º^XX˜l6›xàùøø¨\¹rš5k–$©T©RZ±b…–-[&OOOU¨PA§NÒ_|‘ýýwz½;}ÿäÉ“U¡By{{«víÚ ÑÀoûõ‚lïÞ½’~ppn{aø„Y`L‹-§7*00Ðto´cÇmß¾] 64 â¸+Y·#&$$.òÝn×d±X ÍgÊ€œc`w… Î())IW¯^•J–,i:ÆÀîJݺu%I»wï6\äŸï¿ÿ^’nùü=à¼ØÀ]©Q£†<==uàÀ]¾|Ùt/¶mÛ&IÙÎ ÜWWWùûûËf³)>>Þt/ØÀc`w-kdÈ€ÂìÚµkÚ³gJ”(¡'Ÿ|Òtp là®5nÜX’´uëVÃ%@Þ‹WFF†üýýU¤HÓ9À0°€» âÅ‹kûöíº~ýºé O}ýõ×’¤V­Z.Ž‚ ܵ{î¹Gºqã†âââLçy*66V’Ô¶m[Ã%ÀQ0°€\‘56d@atòäIíß¿_+VTÍš5MçÁÀrE›6m$IëÖ­3\äõë×Ën·«uëÖ²X,¦s€ƒ``¹â¡‡RõêÕuôèQýøã¦s€<±fÍIRûöí —GÂÀrM×®]%IË–-3\ä¾ÔÔTÅÆÆÊÃÃÏ_·``¹¦[·n’¤¥K—.r_LLŒÒÒÒÔ¡C¹»»›Î„ äšÇ\5jÔÐÁƒuàÀÓ9@®Ê޳†d€, l WqŠ …Ñ•+W«R¥Jq{(ø6«BBBäâ⢈ˆeffšÎrÅ_|¡›7oªG*V¬˜éà`Ø@®òññQ«V­”””¤¯¿þÚt+æÌ™#I0`€áàˆØ@®8p $éÓO?5\ܽýû÷k×®]ªY³¦êÕ«g:8 6ë:tè ûî»O111:{ö¬éà®Ìž=[’ô /.ŽŠ ä:777õë×OV«U3gÎ4䨥K—4þ|¹»»+88ØtpP l O 2DEŠÑǬëׯ›ÎrdÖ¬YJMMUß¾}UºtiÓ9ÀA1°€SRR’ž~úiÆ5p[Ø@žkÖ¬™uìØ1…‡‡›ÎþRjjªÞzë-IÊþO€ÂÀòÅ„ d±XôöÛoëüùó¦s€?5aÂ>}ZAAA|ö¸m|È7ÁÁÁZ´h‘†ª3f˜Înqüøq=öØc²Ùl:xð ~øaÓI €àÈ7'N”»»»>ùä¨M›6iΜ9¦s¥¦¦jðàÁ’~?eéééi¸4 l _eÝ"*I£FÒÉ“' ÁÙ½ùæ›JJJRÓ¦Mj:@<äÑ«W/EFFªK—.Z¾|¹é8©]»v©aÆ*Z´¨öíÛ§jÕª™N'Ø€~ø¡Ê•+§¨¨(-Z´ÈtœPZZšúöí+›Í¦7Þxƒq ä0¢\¹rš1c†$饗^RRR’Ù 8W_}U‡Ò“O>©Q£F™Î·ˆ£‚ƒƒµhÑ"5nÜX›6m’«««é$8˜˜uìØQÅ‹×îÝ»U½zuÓI ã0jæÌ™òññÑÖ­[f:NàôéÓêß¿¿ìv»&OžÌ¸î'Ø€q[·nU³fÍ$IëׯW‹-Ì¡ÐÊÈÈP‹-´yófiÕªU¦“@!À 6`\ãÆ5~üxÙl6õìÙS'Nœ0„BjÔ¨QÚ¼y³*Uª¤¹sçšÎ…'Ø€C°Ûí ÒêÕ«U¯^=mÙ²EÅŠ3…BdéÒ¥êÞ½»ÜÜÜ´uëVÕ«WÏt($8Á‚ÅbÑÂ… åëë«]»v饗^2„B$!!A¡¡¡’¤>úˆq ä*6à0<==%wwwÍ;—‡ W$''«]»vºvíšBCC5pà@ÓI áQàp¢££Õ¥Keff*""BÁÁÁ¦“P@]¾|YMš4QBB‚+777ÓY áp8AAAš1c†ìv»BCCµuëVÓI(€l6›z÷î­„„=üðÃŠŠŠb\y‚ 8¤ÁƒkÈ!²Z­êСƒöïßo: ˆÝnWÿþý-///­]»Vžžž¦³@!ÅÀÖôéÓÕ¥K]ºtI-[¶ÔO?ýd: İaÃ4þ|yxx(&&F¾¾¾¦“@!ÆÀ–«««/^¬¶mÛ*%%E­ZµÒÑ£GMgÁÁ=Zü±ÜÝݵjÕ*ùûû›N…phnnnúòË/Õ¼ys%''ëé§ŸVRR’é,8¨ñãÇëƒ>PÑ¢EµtéRšNN€ 8ùdöëV«U 4О={ô /hÖ¬Yk@aÇÀŽÕjUË–-µeËÕ«WO›6m’»»ûŸ¾÷ƒ>И1cd·Û5tèPM:UEŠÉçbä§ï¾ûN;vÔéÓ§åçç§5kÖ¨R¥JxßT¯^=ݼyS«W¯V»ví ÔgÀ-¢À¡Øív…††jË–-òööVTTÔ_Žk’4jÔ(-Y²D÷Üs>úè#µlÙRgΜÉÇbä§9sæ¨iÓ¦:}ú´ž~úimݺõOÇ5IªQ£†Þ}÷]Ùívõïß_çÏŸÏçZà,Ø€Cyë­·)OOO­]»VÞÞÞÿø=]»vÕ·ß~«J•*éÛo¿U:u´cÇŽ|¨E~¹qㆠ êæÍ›zå•W´fÍ•*Uêo¿oĈjÖ¬™NŸ>­AƒåS-p6Ü" Fdd¤BBBäââ¢+V¨C‡wôý)))êÑ£‡âââäææ¦É“'kèС²X,yTŒüpôèQõèÑC»wï–‡‡‡>ýôSõèÑã¶¿?))IµjÕÒ•+W¡^½zåa-pFœ`aóæÍ •Ýn׌3îx\“$///}õÕWzíµ×”žž®áÇë™gžá–Ñlþüùª]»¶vïÞ­‡~X;v츣qM’|||ôá‡J’† ¦'NäE*pbœ`Æ%&&* @)))6l˜¦OŸ~×?síÚµ Õ™3gtÿý÷ë³Ï>ãCî K—.iРAZ²d‰$©oß¾š>}ºJ–,™ãŸÙ©S'­ZµJ-Z´Ð×_ÍÉFkØ€QçÎSÆ •˜˜¨   EEEÉÕÕ5W~öÙ³gª5kÖÈb±hàÀúàƒäéé™+?ycíÚµ4hNœ8¡Ò¥KkÖ¬YêÖ­Û]ÿܳgÏÊÏÏOgÏžÕ´iÓ4|øð\¨``Y­VµmÛVqqqªU«–¶oßþ·O Í »Ý®™3gjôèѺvíš*V¬¨?þX:uÊÕëàî={V#FŒÐ¢E‹$IÍ›7×矮ʕ+çÚ5V­Z¥N:©xñâúþûïU½zõ\ûÙÀyñlÀ»Ý®ÐÐPÅÅÅÉÛÛ[«W¯ÎõqM’,‹^zé%8p@­[·ÖÉ“'Õ¹sg=ûì³:uêT®_wÎn·kþüùzì±Ç´hÑ"Ý{z3gŽ6nܘ«ãš$uìØQýúõÓõë×Õ§OeddäêÏΉ 1qâDEFFÊÝÝ]k×®•··wž^ÏÇÇGëÖ­SDD„Ê–-«¨¨(U«VM&LÐõë×óôÚøk;wîT@@€úöí«óçÏë¹çžÓ¡C‡Ô¿ÿ<ûŒ´iÓ¦ÉÇÇG»víһロ'×Î…[D@¾‹ŒŒTHHˆ\\\¥   |½þ¹sç4~üxÍž=[ª\¹²&Mš”+Ÿó…ÛsêÔ)=Z .”Ýn×#<¢?üPmÚ´É—ëûí· ”‹‹‹¶oß®zõêåËu@áÄ 6¯vîÜ©ÐÐPÙívM›6-ßÇ5I*W®œ>þøc}÷ÝwjÖ¬™~ýõWuïÞ]5Ò† ò½Ç™œ?^cÆŒQµjÕ!OOOMž}Z3fÌPxx¸.^¼¨"EЍ_¿~zýõ×õàƒšÎÓÞ½{åïï¯ôôt}õÕWjÙ²¥é$P1°€}ZŸþ¹æÌ™££GJ’Š/®¶mÛª{÷îzæ™gäîîn¸2$$$hÉ’%ZºtiögÖ+VL;vÔÀ(—‚õÀúY³fiРA*S¦Œ8  *˜NÈñññ TZZš¦L™¢#F˜NÊ5v»]qqqš7ož¢££uõêUI’»»»Zµj¥6mÚ¨M›6òññ1š‹nܸ¡Í›7kݺuZ»v­>œýÚO<¡Þ½{«W¯^òòò2Xywìv»Ú·o¯ØØXµk×N«W¯æáà¶0°€\—˜˜¨€€¥¤¤hðàÁš9s¦é¤ý‡÷T®\Y5jÔÐ#<"U©REU«V•J”(‘k-™™™:yò¤Ž?žýçØ±c:xð <¨7nÜòþ¢E‹ªN:jÔ¨‘Õ¬Y3§ù|9IêÕ«—"## Í›7;ÍH r† äšhîܹªX±¢âããåíím:É¡;vL[·nU||¼8 ýû÷ëÒ¥Kù~•-[VåÊ•S¹råT¶lYyxxH’J•*õ‡ÑçÚµk²Z­ÊÈÈÐùóçÿðÇf³ýéu\]]åë뫚5kê‰'žPãÆU¯^=/^<÷~ùæâÅ‹ªY³¦’““5aÂ7Îtp` l W„……iìØ±rwwW\\œüýýM'¿þú«8 Ã‡+)))ûdYRR’®]»–k×qqqQÅŠU¥J•ì?U«VÕã?®Ç\÷ÜsO®]«°Ø°aƒž~úi-ZTñññzâ‰'L'ÅÀîÚ²eËÔ½{w¹¸¸(**JAAA¦“ …ÔÔT?^çÎÓ¹sçtþüy¥¦¦J’®\¹ò‡i%J”›››Š)¢²eËþá·9Þ¹áÇkÆŒªQ£†vïÞ­bÅŠ™Nˆ Ü•øøx*--Maaa=z´é$ פ¥¥©víÚ:|ø°Fީɓ'›Nˆ äØñãÇåïﯔ” JKKSHHˆ222L'€\ÆÀ€“Wxx¸ÜÜÜ´råJùúúšNœÆôéÓõàƒê»ï¾Ó{ï½g:ä2nÀ DGG«K—.ÊÌÌTDDO  øæ›oÔ²eK¹ººjûöíª[·®é$K8Á@!— àà`Ùl6½ýöÛŒk€!Í›7×Ë/¿¬ôôtõîÝ[ׯ_7r 'Ø(Ä’““Õ°aC%''+88X²X,¦³§uãÆ Õ©SG‡ÒË/¿¬?üÐtÈ lRiiijÔ¨‘öîÝ«æÍ›kݺurss38½~øA 4PFF†6lØ ÀÀ@ÓIà.q‹(…ÍfSÏž=µwï^ùúújÉ’%Œk€ƒ¨]»¶þýïËn·«oß¾ºté’é$p—Ø(„þõ¯)::Z^^^Š•———é$ÿeìØ±ò÷÷׉'ôòË/›Îw‰[D(dÂÃÃ5dȹ¹¹iýúõjÖ¬™é$âÈ‘#zòÉ'uíÚ5}ùå—zöÙgM'€â…ÈêÕ«5lØ0Y,}öÙgŒk€{øá‡õÁH’ ¤Ó§O.9ÅÀ@!‘ ^½zÉf³é7ÞPpp°é$ÿ`ðàÁjݺµÎ;§˜Î9Ä-¢ÉÉÉjذ¡’““¬ˆˆY,ÓYnÃÉ“'åçç§ .höìÙ8p é$p‡Ø(àÒÒÒÔ¼ys}÷Ýwjܸ±6nÜÈCfÉ’%êÑ£‡<<<´oß>U­ZÕt¸Ü" @f³ÙÔ³gO}÷ÝwòõõUTTãPuïÞ]=zôPjjªz÷î-›Íf: Ü6 °1cÆ(::ZeÊ”Qll¬¼¼¼L'È¡™3gêжmÛ4yòdÓ9àp‹(Txx¸† "777ÅÆÆ*00Ðt€»´~ýzµmÛVE‹Õ®]»T³fMÓIà6p‚ €諯¾Ò°aÃd±XôÙgŸ1®…DëÖ­5dÈY­VõêÕK7oÞ4nLBB‚ºuë&›Í¦Ñ£G+88Øt€\ôþûï«ZµjJHHпÿýoÓ9à6p‹(È™3gT·n]%''+88X²X,¦³ä²øøx5jÔH’ôÍ7ߨI“&†‹ÀßáDZZš:vì¨äädùûûkîܹŒk@!åïï¯qãÆÉf³©oß¾ºzõªé$ð7Ø(ìv»zöì©øøxùúú*&&FÅŠ3 ½ù曪S§ŽŽ;¦ýë_¦sÀß`` 7nœ¢££åéé©Õ«WËËËËt€ÿüsyzz***JóçÏ7þN°à€RRRT¿~}%%%©GZ´hO œÜüùóÕ·o_yzzjß¾}zðÁM'€ÿÅÀ€ƒIKKS`` âããåïﯸ¸8¹»»›Îàž}öYEEE©yóæÚ¸q#Ã;‚[Dp v»]}úôQ||¼|}}ø Û'Ÿ|¢òåËë›o¾Ñ´iÓLç€ÿÅÀ€7nœ¾üòËìÏZòòò2ÀxyyiöìÙ’¤±cÇêСC†‹€ÄÀ€Ã˜3gŽÂÂÂäææ¦¨¨(ùùù™Nà€:tè èÆêÝ»·ÒÓÓM'àôØpqqqz饗$I3fÌP`` á"ŽlÊ”)ªZµª¾ÿþ{½óÎ;¦spz<äÃ<¨FéòåË3fŒ&Nœh: @°eË5oÞ\’´mÛ6ùûû.Àyq‚ ƒRRRÔ©S']¾|Y]ºtÑ{ï½g: @ѤI9R6›M½{÷Öµk×L'à´8Á€!iii T||¼üýýÇCÜ‘›7oª~ýúÚ¿¿^zé%}ôÑG¦“pJ l`·ÛÕ«W/-Z´H•+WÖîÝ»yb(€Ù¿¿êÕ«§ôôtÅÆÆªuëÖ¦“p:Ü" €ãÆÓ¢E‹äéé©Õ«W3®ȱš5kêí·ß–ÝnWÿþýuáÂÓI8N°Ï,X >}úÈÕÕU_}õO p×l6›žzê)mÛ¶M=zôÐâÅ‹M'àTØÈGqqqjÛ¶­¬V«fΜ©Áƒ›NPH;vLµjÕRjjª¾øâ uïÞÝtNƒ[DÈ'GŽQ—.]dµZ5fÌÆ5¹ªjÕªš2eŠ$iÈ!:yò¤á"œ'ØÈ))) Pbb¢‚‚‚%WWWÓY ¡gžyFkÖ¬QëÖ­+‹Åb: €B €>^ÞÞÞZ±bã€U©RÅtŸÁÀ°Ûí U\\œ¼½½µzõjÆ5ÒóÏ?¯nݺéêÕ«êÓ§l6›é$ ,6îÀĉ)www­]»VÞÞÞ¦“ ÇÂÃÃU¡BmÙ²ES§N5@Å-¢Ü¦ÈÈH…„„ÈÅÅEQQQ 2wmݺuj×®ÜÜÜ´k×.ùùù™N Àá·açÎ •Ýn×´iÓ×mÚ´Ñ‹/¾¨›7o*$$DV«Õtÿ ëI¡V«UƒÖK/½d: rÕäÉ“åëë«}ûöiüøñ¦s(p¸E€¿qùòeÕ«WOGŽQPP¢¢¢äêêj: rÝÎ;Õ¸qcIÒ·ß~«F. ààÁjµªK—.:räˆüüüÁ¸ ÐjРƌ#›Í¦>}ú(55Õt'Øø }úôÑ‚ äíí­;vðÄP…žÕjUƒ ´gÏ 8P³gÏ6Æ}ñÅÚ»w¯é 886þDXX˜ÆŽ+wwwmÞ¼YuêÔ1ùâàÁƒª[·®nÞ¼©˜˜µoßÞt¬E‹™Î€ƒ+b:G³hÑ"7N®®®Z¼x1ã§òøãëÝwßÕ«¯¾ªèÀ*[¶¬é,0®fÍšzþùçMgÀÁ„……éÒ¥K*2fÌÓ-(^yå•/_Þtä¹øøx 8Pv»]“&MRPPé$Èw#FŒÐêÕ«µiÓ&½øâ‹úòË/M'€q£Gf`ÃØív;VEÞÿ}Ó-(‚ƒƒØz‰‰‰êСƒÒÒÒ4xð`1Âtáââ¢yóæ©V­ZZ¾|¹.\¨^½z™ÎÀaeß"f²ŠŽœÅåË—Õ©S'¥¤¤¨mÛ¶š1c†é$0ÊÇÇG~ø¡BCC5lØ0=õÔSªT©’é,RöÀ6zôh“pP‘‘‘JHH0yÊjµªK—.:xð üüü´xñb¹ººšÎãúõë§èèh­\¹RýúõÓ×_-‹Åb: ‡ãb:Ó^zé%ÅÅÅ©bÅŠZ»v­<==M'€Ã˜5k–î¿ÿ~mܸ‘Ó½ü6€S Óœ9säî¨(y{{›N‡rß}÷iÖ¬Y’~ÿøüÑpއ à´¾üòK7N®®®Z¼x±üýýM'€CêØ±£BCCuýúuõéÓGéé馓p( l§¯>}úÈn·k„ 2íÃ?”víÚ¥ &˜ÎÀ¡0°œNRR’:tè ´´4 <˜ýÀm(Y²¤æÏŸ/M˜0Aß}÷é$À©\¾|YAAAJIIQ‹-øÀn¸M›6Õˆ#”‘‘¡Þ½{+--Ít à4¬V«ºt预„ùùùiùòåruu5Ê„ T£F >|XcÆŒ1€C``8W^yEqqqòòòÒš5käééi: œbÅŠ)""Bnnnú裴aÃÓIÇÀp aaa —»»»bbbT©R%ÓIP`=ñÄ?~¼ìv»úõë§‹/šNÀ(6@¡·jÕ*7N‹E .”¿¿¿é$(ðF­€€%''kذa¦s0Š P¨ÅÇÇëù矗Ýn×{ï½§Î;›N€BÁÕÕUóçÏ—‡‡‡"##µlÙ2ÓIÃÀ(´’““¤´´4 4ˆã€\æëë«É“'K’ ¤S§N.À 6@¡tùòeµk×NgÏžU`` ¦M›f: ¥^xAíÚµÓ… Ô¿ÙívÓIä;6@¡c³ÙÔµkW%$$ÈÏÏOQQQrss3…’ÅbÑœ9sT¶lYÅÆÆjöìÙ¦“Èw l€Bgذaúúë¯åå奕+WÊÓÓÓtj*TPxx¸$iäÈ‘JLL4\@þb`*“&MRxx¸ÜÝÝ£ªU«šN§ÐµkWëÚµkêÝ»·l6›é$ò  ÐˆŽŽÖرce±X4oÞ<ùûû›N§2cÆ y{{kÇŽzÿý÷MçoØ…B||¼zöì)›Í¦÷Þ{Oݺu3NçÞ{ïÕ¼yód±XôÖ[oiïÞ½¦“È l€/99YÏ=÷œÒÒÒª1cƘN§Õ²eK :TV«U!!!ºyó¦é$€¤Ý»wËb±¨~ýú·<ñ9ëëjß¾½ú÷ïÿ‡ïµÛíòõõýìÝwXWû7ðï‚”QÀ%X"Œí1Q4‰5ê `/“øÓt5–fAc4–Ø=–{¯±DAA@£F5Q¥I?ï¾;@Ú²³åû¹®½²ÎœsÏfÙ¹÷ž3s0gΩ½J¥Òx 2¤Ä¾ÿÝÖÒÒöööU±›²aˆˆ Ú“'OÐ¥K$%%¡C‡ÒM¶‰ˆH>3fÌ@ãÆ/¾øBîpˆˆ¨„„¬Y³¦Øu#GŽÄ¦M›žž®±üÈ‘#HJJ Aƒ¤e?FzzºôXµjU‰}n—žžŽvíÚ!88X+û£/X`#""ƒ•ŸŸÄÅÅ¡iÓ¦øõ×_aii)wXDD&ÏÚÚkÖ¬R©Äœ9spìØ1¹C""¢ÿ/,, 'NDfff‘uݺuƒ6lØ ±|Ù²eèÕ«+Ýÿ7pøða„††Vz[úÄh lruTËË˃ÔU­1cÆ`×®]pvvÆöíÛQ½zu¹C""¢ÿ/ _|ñ 0dȤ¥¥É>|8jÖ¬Yìd4J¥C‡Ųeˤe)))ˆˆˆÀÈ‘#µÒÿ²eËàëë ___­lO_MMMŽ¡Žj?üð*?•Íܹs±hÑ"X[[cÇŽpss“;$""ú—I“&! ‰‰‰;v¬ÜásssÌ™3³fÍBRRR‘õÇGdd$âããk×®E½zõо}{vNNNpppóçÏ/µï¼¼<¬\¹ÒèF¯FX`“k¨ãÍ›7±lÙ2L:µÂÛ "¢²Ù¾};>þøc( ,]º¯¾úªÜ!Q1”J%V¯^ kkk¬\¹Û¶m“;$""ˆÀÀÀb'kذ!¥QlË—/Lj# P(4Ú%''ãñãÇÒãƒ>(µß;v ==Ýèî¿aM®¡Ž|ð¾þúk¨TªJm‡ˆˆž/::AAAÈÏÏÇwß}‡¹C""¢çhÒ¤ ¦OŸ Åýû÷eŽˆˆˆ`Ö¬Yؼy3Ξ=[d]hh(Ö®]‹'NàâÅ‹eºmVY,Y²AAAFY;1º›C·lÙ‚ÜÜ\ôéÓG»;CDD’’’УGdffbÀ€Åžq#""ý3fÌâþýûFyY‘!rwwÇ{ï½Wì•xo¿ý6ÌÌÌ0hÐ tïÞuêÔ©t·nÝÂþýûö8`t6@·CÓÒÒðù矗éZc""ª¸ôôttéÒIIIèС–/_.wHDDTF …+W®„ƒƒ¶mÛ†•+WÊQ‰233qòäIÌ; ÀÎ;å©ÊL:Uc¢H5KKK < 1bD±¯uppИò7Þxn_Ë—/‡——üýýµ»¾QÊ@U™5k<<<кuë"ëBCC1zôh¼óÎ;•êxåÊܺu -[¶iöÐ:uê`éÒ¥èÕ«W…·MDú--- <@rr2>|ˆ‡J÷LII)ÒÞÚÚVVV033ƒ££c‘‡………®wÁ`äçç#$$qqqpssÃÖ­[aii)wXDDT/½ô~üñG 8cÇŽEûöíáââ"wX‡ù‘våææ">>‘‘‘8{ö,"##qéÒ%é·½1ñ÷÷/RLspp@rrr±íÃÃÃ^¦í”EXXÂÂÂÊý:Ca´¶Ò†:¾ÿþûZêèåå…„„éß±±±èÚµ+Î;‡ZµjUx»D¤‹«W¯"11Qz$$$àéÓ§ZëG¡P nݺpuu…‹‹ \\\àêê ¼òÊ+°µµÕZ_†h̘1ؾ};œ±gÏÎØLDd  €mÛ¶aË–-:t(:33£¼¨¦R˜U‚‚\¾|QQQˆŒŒDTTbbb••¥ÑN©TÂÓÓð÷÷ǶmÛ°wï^™¢&Ca´6àÙPÇ5kÖY®ê8kÖ¬/íü÷·Ö­[cÿþýEÚYXXàÅ_”þý÷ßêÕ«¥ÒxÞÞÁƒ£mÛ¶ðöö†š7oγ]dt®^½ŠÓ§OãÌ™3ˆE\\RSSKloggggg899Igmll5jÔ(ÒþéÓ§ÈÊÊBAAtƹðãîÝ»¸{÷.Nž<©ñ:3334lØ^^^ðööFÛ¶m`2Iï¢E‹°hÑ"XZZâ·ß~ƒ›››Ü!Q%üôÓO8qâŽ=Š9sæàã?–;$Y1ÿ ª:7nÜ i‘‘‘8þ<ÒÒÒ4Ú( 4nÜþþþRAÍÇÇGú»€ßÿ]ס“R*4¼ŒŸ§§'âââŠ,·´´DóæÍáíí ???x{{ÃÓÓÓ(g!ã‡àرc8}ú4>>øã?ŒöÞšÌ?ˆ´ïáÇ…´ÈÈHܽ{·H»ÚµkK£ÒÔÿ­È­œX`£çaÊD]`‹…‡‡‡´<==±±±ˆ‰‰Á¹sçƒøøxäääÙFýúõ5Fºy{{£~ýúºÜ 2a™™™Ø¹s'6mÚ„Ý»wkÜ·ÄÃÃ:uÂk¯½†V­ZÁÙÙYÆHË'//.\À‰'päÈ:téééÒúfÍš¡oß¾èׯš4i"c¤“””„V­Z!)) !!!X³fM‘Ùž‰ˆÈ°¥§§ÃÛÛׯ_Çĉñí·ßÊ’Ö0ÿ0ÌüƒôSZZΟ?¯QL»qãF‘v…´€€¼ôÒKZ‰6zؾ'’Ä IDAT¨LJ*°G=ûJtt´4Ò-&&Fã «æìì,ÝÏÍÇǾ¾¾pssãMnI+ pèÐ!¬X±;vìδÚÚÚâÍ7ßDçÎñÖ[oiÜ?ÑÐåääàĉØ»w/víÚ…K—.Ië<==‚!C†Ää+™™™hÛ¶-¢££Ñ¾}{ìÝ»×hG5™ºS§N¡]»vžÝã¨U«V2GTqÌ? ;ÿ ý˜˜DFFJµË—/¹zÊÆÆ>>>ð÷÷G‹-77·*;!Ë=ºÀ<+° ¢âxxx"66¶B¯ÏÏÏW®\7nŸ}ö™èÔ©“pvvêÏ]á‡J¥mÚ´cƌ˗/çÏŸ999ZÞ#2fwîÜß|óhذ¡ô¹²±±}ûö[¶l™™™r‡¨3/^S§NMš4‘Þ ñÎ;{÷Šüü|¹C,V^^žèÑ£‡ ÜÜÜÄýû÷剈ˆªØøñã¥ïýôôt¹Ã)7æÿc¨ùÉ#77W\¸pA,[¶L¼ûî»ÂÏÏOXXXùhaa!üüüÄ»ï¾+–-[&.\¸ rssukpp° Ö­[W%ÛïÒ¥‹6lX‘å¢Q£Fâûï¿‘‘‘€Rõò’Þ“¤¤$ѹsgQ£FbÛÙÚÚj<,,,„´þ£>Mš4666¢F¢ÿþâÞ½{%îKiý !ĪU«D“&M„J¥/¾ø¢ˆˆˆxîûSžöeé_Û¦M›¦þ¼²ÀF%«l­$·nÝÛ¶maaaâí·ßõë×/¶èfii)|}}ŰaÃÄüùóÅÉ“'EZZšVc!ÃwüøqÑ»wo¡T*aff&:uê$6lØ`‰º¶]¸pA|öÙg¢V­ZÒß–‹‹‹Ož<‘;< cÇŽ„³³³øë¯¿ä‡ˆˆt ;;[xyy bÔ¨Qr‡SfÌ?žÏòªzâòåËbíÚµbìØ±¢M›6ÂÆÆ¦Èï?sssñÊ+¯ˆ!C†ˆùóç‹3gΈ¬¬,¹Ã¯ò[DD„P©TE~ë:tHT«VM$''K…4'''ñóÏ?KmJ+°Ý½{W,Y²D¬Y³¦L§ÀÀ@ïâ)S¦ˆØØXQPP •Ô_ƒ ÄÁƒK}}EÛ—ÖUÐYCŸ?ÔQˆgLÞÞÞ¥þÏ—c¨£. lÅIKK'Ož ,Ǿ¾¾ÂÒÒ²Ø/çúõë‹=zˆ°°0±mÛ6qóæMYb¦ª•-~úé'áââ"ý¿÷öökÖ¬á%Ååpùòe*¬¬¬¡T*EHHˆ¸|ù²,ñìܹS˜›› …B!Ö®]+K DD$¯ððp@Ô­[WïNž2ÿÐuþamm-åÁÁÁ²å¤ÿý·Ø±c‡˜:uªèÒ¥‹Æ¨ÅÂ_|QôìÙS|ûí·bß¾}âÑ£Gr‡^fU]`Bˆ‰'Š-ZHÿ~ôè‘°²²‡Bü¯>’••%<<<Ä”)S4–k£À6aÂáçç÷ÜíL™2E#ÎòôwóæM@„‡‡ áèè(‚ƒƒKü,”·}iýWØ8Ա䡋jáááâõ×_/uäê(w­8999"::Z,_¾\Œ3Füç?ÿ*•ªØ/q'''Ñ©S'ñÙgŸ‰ 6ˆ+W®ðÞ*77W¬X±B¸ººJÿß|óMqàÀ¹C3h÷ïßaaaÂÉÉIJt\¦ï.m‰•ÎjNž}ú»ò¶/­ÿª¢³‡:>_bb¢hܸ±8räH™ÿçëòƒ¢¶â¨'Søå—_Ê4™BëÖ­¥ÉÎ;Ç3z¬  @¬_¿^¸»»Kÿ;vì(NŸ>-whF%55U|õÕWÂÁÁAÏn&ZåÉÐíÛ·Å‹/¾(ˆQÌDDdznܸ!ìììªü‡li˜èFjjªøúë¯užPÙ¤§§‹ßÿ]|ÿý÷"88X¼üòËB¡Pù}egg'^{í5ñé§ŸŠM›6‰„„¹C×:]Ø„¢cÇŽbìØ±B!¼¼¼Ä´iÓ¤uÿ®tïÞ]„„„h­nò믿;8JmË–-ÂÁÁ¡Ì'Šëﯿþă¤eû÷ïÖÖÖÅn£¼íK뿪èôlêX²nݺ‰M›6•ë> lewûömi2…ž={–i2…ü‘“)è‰óçÏK£;ˆÿüç?âØ±cr‡eÔRRRĤI“¤Q¡vvvbÖ¬Y";;[ë}eddH÷°iÓ¦M•ôADD†gùò倨Q£†¸}û¶Îûgþ¡{)))bòäÉRþ¡R©Dxx8sÊÉÉQQQbÑ¢EbøðáÂÓÓSšÀ£ðÃÊÊJ´lÙR|ðÁâçŸ/^4‰+„tU`Û´i“ptt¿ÿþ»P*•·°úwàÊ•+ÂÒÒR,X°@+u“·ÞzKŒ9²Øu+V¬âøñãeÞ—âúËËËööö"99YZvàÀaeeUì6ÊÛ¾´þ«ŠN lêX<õÍ>˲¥õ_U ½ÀVœâ&S077/rð0337Ö˜L¡ð6U{÷î‰aÆI7Âuss¿þú«Üa™”»wïùP–{J–U^^žèÑ£‡´íû÷ïkmÛDDdøÔLjN:élt3óùUuþAÏäå剸øx±råJñþûï‹-ZˆjÕªù=¤T*…···1b„X¼x±8þ¼É^ù£«[vv¶pvv®®®¢W¯^늫Œ7NºÔúyõ§OŸŠ“'O "==]<}úTcýÍ›7…™™Y±õ¹s犚5kŠ3gΔy?ž×߸qãÄèÑ£EVV–xôè‘ |nݤ¼í˲¿Ú¦óYD9ÔQSjjªhذ¡øë¯¿Ê´¥õ_UŒ±ÀVœŠL¦ðå—_r2-+((+V¬&ò¨^½z•ž¢²9þ¼xíµ×¤ÏÏž=ERRR¥·ûÉ'ŸH£Ô߃DDDjÿý·t»yóæUi_Ì?ôÏ¿G¾ýöÛZÉ?LÕ_ý%6lØ >úè#Ñ®]»bï_mff&š4i"(æÎ+N:%233å]oèªÀ&Äÿòä]»vi,/®’’"K­÷»¶°)S¦Ÿ_«T*‹L"™——'„xö÷Zøß¥õ—••%BCC…£££°··ýúõÓ¸òïßÛ+oû²ì¯¶é¼ÀÆ¡ŽE·¡T*…£££ptt”nðíèèXê™2Øt#''GÄÄÄ”i2GGGi2…õë×s2… ¸~ýºèСƒôžñþz¢  @¬Y³FšªzõêbÑ¢EQ°páBéÒlõ­ˆˆˆþ-""B666U6Ë$óý¶víZücáÂ…¼_k)nß¾-"""ÄĉÅo¼!Žÿýpqq}úô3g·Ož<‘;t½¦ËØ8ÔQSNNޏ}û¶ôصk— Ÿ{¦@×CM¹ÀVœÂ“)|þùçåžLgB‹*((?üðƒ°±±À³é»wîÜ)wXTŒääd1pà@é3Þ®];‘˜˜X®mìÛ·Oº${íÚµU)‹Áƒ ¢E‹Z=¹ÌüÃp$''‹AƒiäÆxýŠxðàØ½{·øê«¯D÷îÝEݺu‹ý]R·n]ѽ{wñÕW_‰Ý»wk\uEeÃ=Î lBp¨cqÛ{Þ{ CY`+›Â“)ôêÕ‹“)”ÑÝ»wÅo¼!…B!Þÿ}‘šš*wXTн{÷ élrYØØXi´îøñã«8J""2Ož< 4Ä—_~©•m2ÿ0Lûöí“ò{{{“+t|XÌœ9SôéÓGz/þýpppo¼ñ†˜8q¢ˆˆˆe¢cÄ=ºÀ¦ªlB€èß<==‡ØØXxxxÈŽAyô袣£qþüyDGG#&&W¯^E~~¾F;333¸¹¹ÁÏÏÞÞÞðññ¯¯/eŠ\7~ûí7Œ9ÉÉɨ[·.V­Z…7ÞxCî°¨ŒRSS1zôh¬_¿ŒE‹ÁÞÞ¾Øöÿüóüýý‘””„¬Y³ …B—!‘:rä:vìsssœ>>€¿¿?àææÆ« „„„`ýúõX·n‚ƒƒå‡ôÌôéÓ1a°ÀFÏÅ›veddàÂ… ˆ‰‰‘ŠoñññÈÉÉ)Ò¶~ýú7oooÔ¯__†¨µ+//Ÿ|ò æÎ èÙ³'–.] '''™#£ŠX¿~=Þ{ï=êü£ÿþøã?ЦMüôÓO8p Ü¡=WAA®^½ŠÈÈH© ƒ§OŸj´377‡‡‡‡F1ÍËË –––2ENDeÁ‘Ì,,,àåå/// :À³ƒïµkפ‘nçÎCLL ''.\ЙvéÒ%äççk´³¶¶†···ÆÈ4wwwƒ™xŒˆª lD&B[“)øøøÀ××·ÄÉ233ѳgO8pNNNسgO¥Ï’aª^½:víÚ…áÇcÍš5èÒ¥ ¶nÝŠÎ;˸wß}Û¶mþ}û0bÄüòË/èÕ«ó‚½½=vî܉#F`õêÕèÚµ+6oÞŒ®]»j´ËÏÏÇ¥K—4Ši.\@NNŽF; xzz" @*¨5oÞJ%J‘&~+™°çM¦Px¤ÛùóçqóæÍR'Shܸ1ÂÂÂpêÔ)Ô©SÄ+¯¼"Ç®‘ž°°°ÀÏ?ÿ •J…E‹¡gÏžX¿~½Æå}䈈 Ø /¼€Ù³gcèС¸víóÒ P(0þ|¨T*Ìœ9cÆŒB£MÆ 5.óôõõ…L‘¡cˆÊ¤¸É233ƒ³gÏbæÌ™¸wï6lˆÃ‡󦮤A¡P`æÌ™°¶¶ÆW_}…ÀÚÚݺu“;4""2P™™™Xµj0ÿ b) ̘1VVVøê«¯`ff†   8þþþptt”;D"2",°Q…ÙØØÀßß_}õîÝ»‡_|drK% Cvv6f̘¾}ûbçÎèСƒÜa‘ÉÉÉAïÞ½qìØ1æTª°°0äää`úôéØºu+†ÊâUÈøñã+w¤gf̘€6"ª„üü|ôë×ûöíC­ZµpèÐ!¸ººÊé¹éÓ§###óçÏG÷îÝqøða¼úê«r‡EDD‚ùUÄ´iÓ‘‘ü=zô`þArûöm©˜Bôo,°Q…3¿ýöqðàAÞó„ÊlÞ¼yÈÈÈÀÊ•+ѽ{wœ:u nnnr‡EDD€ùUÔܹs‘‘‘+V0ÿ réß¿?<==åƒôœ€€Ï?ÿ\æPH©«ó±±±ððð9Ò'áááøì³Ï`ccÃ3€T!ùùùèÝ»7¶oß777œ:u ÎÎÎr‡EDDzŒùUó"ª*RèyX`£Â¶oߎ޽{£  6l@¿~ýä‰ Tff&þóŸÿàüùóxíµ×°ÿ~XZZÊé!æ¤-™™™xýõ׉¶mÛâСCÌ?ˆ¨ÒÓ§OgJ5lØ0žÙ!À… кukdffbÚ´i?~¼Ü!‘KJJB«V­””„¡C‡bÅŠr‡DDDz†ùi[áücÈ!X¹r¥Ü!‘S!X`#¢2III¿¿?nܸ‘#GbÉ’%r‡DF"..­[·Fzz:~úé'Œ5JHO0ÿ ª‡6mÚ -- .Äèѣ剈  lDT&ùùùèÞ½;öìÙƒV­ZáèÑ£JOZµyófôë×8zô(Zµj%wHDD$3æTÕ¶lÙ‚¾}û2ÿ ¢J3“;"2 _}õöìÙƒZµjaÓ¦MLnIëúôéƒ?ü999èÛ·/îß¿/wHDD$3æTÕÞyç|üñÇÌ?ˆ¨Ò8‚ˆJuìØ1B¡PàСCh×®Ü!‘‘ÊÏÏG§NpäÈtëÖ Û·o‡B¡;,""’óÒ•ÂùG×®]±cÇæDTnÁFDÏõøñc <ùùùøú믙ÜR•277dž P»vmìܹ .”;$""’óÒ¥ÂùÇ®]»°`Á¹C""ÄlDô\ýû÷Ç/¿ü‚×^{ ‡‚¹¹¹Ü!‘ عs'zôè+++DEE¡Y³fr‡DDD:Äüƒä°gÏtíÚ•ùUG°Q‰Ö­[‡_~ù5jÔÀÏ?ÿÌä–t¦[·nxÿý÷ñôéS„„„ 77WHG˜\:wî,åAAAÌ?ˆ¨\X`#¢bÝ¿cÇŽ,Z´ 49"253gÎD³f̓3fÈéó’ÛÌ™3ñÊ+¯ 66–ù• /%¢bcÆ èÓ§6mÚ$w8d¢¢¢¢Ð²eK(•JÄÄÄ I“&r‡DDDUˆùéæDTÁFDEìÚµ 6l@50oÞ<¹Ã!æïï±cÇ";;¡¡¡à9!""ãÅüƒô…¿¿?ƇììlŒ9’ù• G°‘†ôôt4mÚIIIX¾|9† &wHdâ222àáá„„,^¼¡¡¡r‡DDDZÆüƒôMFF<==qãÆ ,Z´ï¾û®Ü!‘žã6"Ò0sæL$%%¡C‡:t¨ÜáÁÖÖ?ýô`Ò¤Ixüø±Ì‘¶1ÿ }S8ÿ˜úè#äææâÓO?•;""Òæ¤ï\\\ðÉ'Ÿ 77Ÿ}ö™Üá‘ã$Dhß¾=Ž=Šùóçãý÷ß—;¢b¥§§£qãÆ¸{÷.NŸ>–-[ÊUó2éééhÒ¤ îܹƒS§N¡U«Vr‡DDzˆ#؈‡ÂÑ£GѨQ#Œ5JîpˆJ¤R©0eÊÏn8LDD†‹ù •J%åÌ?ˆ¨$ÁFDhÓ¦ N:…Õ«WcàÀr‡Cô\999hÒ¤ pôèQ¼öÚkr‡DDDÀüƒ Inn.7nŒ„„9r¯¿þºÜ!‘žá6"·gÏœ:u M›6Epp°Üá•ÊÒÒR:{¬ÍFDD†…ù L:ó"* lD&næÌ™€©S§ÂÜÜ\æhˆÊfàÀpwwÇñãÇñÇÈ•ó2D!!!pwwÇï¿ÿŽÓ§OËé؈LXtt4Ž=ŠfÍš¡OŸ>r‡CTfJ¥'Nð¿iDDd˜¡R*•øâ‹/3fÌ9"Ò7,°™°éÓ§Æ33~a Bݺu±mÛ6\»vMîpˆˆ¨Œ˜!ëß¿?êÖ­‹;v0ÿ " <¢™¨[·ná×_…““  w8Dåfii‰±cÇ¢  ?üðƒÜáQ0ÿ Cgii‰?ü˜={¶Üá‘aÈD-X°yyy3f ¬­­å‡¨BF•J…U«VáñãÇr‡CDD¥`þAÆ`äÈ‘P©TX½z5ó"’°ÀFd‚rrr°jÕ*XZZâÝwß•;¢ sppÀ Aƒ‘‘õë×Ë=ó2ó#ÂûýR©8‚ÈÄlÚ´ зo_™#!Ò{{{tîÜسgzõê%wHDDTó2FvvvèÒ¥ ~ýõWìÞ½½{÷–;$ªbžžž–; Ò3Ó§OÇãÇ¡?~¼Ü±7nêÔ©#wTIñññ¸téš7oŽf͚ɑVõéÓؼy3 lDDz„ù³>}úà×_ÅæÍ›Y`3Ÿþ9 lT„&L€rÆŒrÇB $$„6#À³Çd̺wïìØ±™™™°±±‘;$""ó2nÌ?ˆHMºDtúôérÆAzŠ# \2f*• ;wÆÖ­[±gÏü÷¿ÿ•;$""ó2n¶¶¶èÒ¥ ¶lÙ‚Ý»wãwÞ‘;$"’‰T`ûüóÏ匃ôÔºuë'w¤ׯ_Ç•+WШQ#4nÜXîpˆªD·nݰuëVìܹ“6""=ÀüƒLA·nݰeËìܹ“6"f&wD¤»vítíÚUæHˆªÎo¼…Býû÷ƒ“dÉù™æD°ÀFd2öïßàY@d¬^xáxyyáîÝ»¸pá‚Üá™<æd êÖ­ Ü»wÑÑÑr‡CD2aÈ<}ú‡†µµ5:tè w8DUê­·ÞìÝ»WæHˆˆLó2%ê"²º¨LD¦‡6"pâÄ <}úmÛ¶…µµµÜáU©N:8 s$DD¦ù™؈ˆ6"pêÔ)@ëÖ­eŽ„¨êµlÙJ¥üñòòò䇈Èd1ÿ SòꫯB©TâÌ™3Ì?ˆL lD&àäÉ“€6mÚÈ QÕ³±±··7233#w8DD&‹ù™øúú"33çÏŸ—;"’ lDF.??gΜ¹¹9^}õU¹Ã!Ò õ9õ;""Ò-ædŠ˜™6؈Œ\||jŸ~ú) vïÞ]jßü1š6m [[[Ô¬YAAAøû￵³c&FýÙWÿ-QÕcþQqråÙÙÙxíµ×P§N¨T*T¯^8}ú´öv΄0ÿ }—€5kÖ»näȑشiÒÓÓ5–9rIII4h´ìñãÇHOO—«V­*v›©©©èÖ­zõê…Ç#)) ÎÎÎxçw´¶Oú€6"#öèÑ#@Íš5eŽÄpiãà“““ƒŸþ^^^X¶lY©}ªT*i»W¯^EVVúõëWù1ALp‰ˆtùGåé:ÿP*•˜?>nÞ¼‰ôôtܽ{mÚ´A÷îݵ³C&†ùé»°°0Lœ8™™™EÖuëÖ vvvذaƒÆòeË–¡W¯^:y’””„´´4|ðÁ°°°€½½=BCCqñâÅ ïƒ>2š›œC‹{Í/¿üR»ITf©©©ÈÉɽ½=,--åÇ`iãà³mÛ6!°téRìØ±£ÔÑhaaaððð€B¡€““>úè#œ|8jÖ¬‰3fY§T*1tèPÂ|JJ """0räÈ õçîî___Ì;ÙÙÙHIIÁ¢E‹Tá}ÐGFS`SÓõPÇ ¿†£MHn?888ȉaÓÆÁgÉ’%èׯàîî^¦ï“Â<??¿ ïƒ)SžP¦ "¢ªÅüC;äÊ?ú÷ï;;;ØÚÚâôéÓ8vì˜VöÇÔ0ÿ }gnnŽ9sæ`Ö¬YHJJ*²~øðሌŒD||<`íÚµ¨W¯Ú·o¯ÑÎÉÉ ÒcþüùÅö§T*µk×J·Áù矰dÉíŒ®À¦ë¡ŽDú*55`oo/s$†­²Ÿ7nàСC0``À€X¶l™ÆHÛçÙ¶m¾ÿþ{,X°@K{dZ¬¬¬YYY2GBDd˜h‡\ùÇÆ‘––†+W® 99Ÿ|ò‰–÷Ì4¨GfggË QɈñãÇY×°aCJ…üåË—cĈP(í’““ñøñcéñÁÛWNN:uꄤ§§ãÑ£G¨]»¶Ñ L2º›®‡:Ö A8;;cÈ!RrA$uõï/A*¿Ê|–/_ކ ¢eË–€ܸqGŽ)µß­[·bÈ!ˆˆˆ€¿¿¿÷Èt˜››òóóeŽ„ˆÈ40ÿйòàÙå\ß~û-V¬X¡¥½1-€ÜÜ\™#!z¾Y³faóæÍ8{öl‘u¡¡¡X»v-Nœ8‹/>÷¶Y¥¹zõ*®^½ŠqãÆÁÊÊ 5jÔÀèÑ£±sçÎJD¯Œ®À¦ë¡ŽàêêŠ?ÿüwîÜATT._¾Œwß}W»;FTNê3fê3hT99øäååaåÊ•¸}û6œœœàää___ÀÒ¥KŸÛßÊ•+1bÄlß¾;vÔúþ˜ •JEn @DDUƒù‡vé:ÿ(,??_*QùØØØ@±WU‘áÈÉÉAJJ RRR““#w8UÂÝÝï½÷¦NZdÝÛo¿ 333 4Ý»wG:u*ÜOýúõakk‹ùóç#77iiiX¼x1<<<*¾ÞQÊ@U(|¶gܸqë Ÿíùᇞ;ÔQ©,ÛÛãèè(]^Ú A„……¡wïÞÚÙ2OŸ>•.cËÊÊÂÓ§O‹<ÏÎΖÔ…Ÿçää ##À³3eéééHLLð¿Kä¨rJ;ø¼ÿþûE>;wîÄýû÷qöìYÔªUKj¿wï^|ðÁxøða±—¦Ï›7aaaØ·oZ´hQu;eB._¾\ì""Ò.õ næÚ¡«üãÂ… xðàÚµkKKK\¾|“&MBHHHÕî ‘Rÿ¶¬HþQPP€'Ož”»O!„tÄòzüøq™o_RXjjj…®HOO¯Ðè¾ÌÌÌ ]v[ø÷5uêÔbïcoii‰ÁƒcÖ¬Y%8ú÷ý6[·nýû÷igooˆˆL˜0aaa033C«V­°qãFí섞0Êðìl‡‡Z·n]d]hh(FwÞy§ÒCKR‘/(Ҏ—hRRR¤ç…$…Ÿ?yòEž>€¤¥¥!//¯È󋌌 éLGáç… =H<Ê{ðYºt)þûßÿJgÕ† ‚¯¿þ«W¯Æ‡~Xd{cÇŽ…R©D‡4–?yòDºä‘ÊÆÎÎpýúõbo@DD¤ït‘äææbüøñ¸rå „¨U«‚ƒƒ1eÊ”ªÙ)#§¾!óÃfii [[[š¿ç ™¿¿‘Z…ƒƒ’““‹mŽððð2m§4:uB§NÊõCc´¶Šœí©¨#GŽ yóæpvvÆ­[·0uêTôìÙ³RÛÔ7©©©¸sçŽT *<Úª¤‘WemUÜÈ+àÙÐö´´´"Ïóóó¥ûÛ~^Ñ3=úÄÊÊ ÖÖÖEžW«VMf^øK¾ðs éR8õó¤¤$¬[·N×»a4´qðÙµkW±m•J%nÞ¼Ybß,Ðkú»£I“&Ur2…ˆˆ4%&&â§Ÿ~’; ƒ%Wþáï﨨¨ DLÅQÿFiܸ1†Z®×š™™¡zõêê·Fz]õêÕafVþ»GÙÛÛWèä¯J¥ªÐåÇ666ºü¼ðo«Š Áúõë+µ 2~F[`t3Ô:„þýû###666èÙ³'fÏž]ùÐ#mÛ¶•;„r)|p)üÜÁÁA²]øyáƒJáEáçvvvÒeÃ…Ÿ>@ØÚÚÂÒÒ²ÈóÂkkk鲉Âϵ-** ëÖ­ãì‰D5j„Ï?ÿ\î0ˆˆŒ^TT~úé'ædÒÔER777æD&Äh lruüæ›oðÍ7ß”ë5†ÆÞÞ*•J*•e´UI#¯Šm<;«¦¾œ«ðssssi˜uá3:…Ÿ+Š"EQSÇéÁ‰þ7¹ú{†ˆˆªó¢ÿMn þ-DD¦Áh lTµNœ8at3|;õè<^nH¦L}ßDÞ»ŽˆH7˜Aº'3ga%2-å¿Ðšˆ ‚zÔŸúD¦H}‰g³#"Ò æDÿÁY‘û…‘ábÈH©/™­ètÝDÆàÑ£G€š5kÊ ‘i`þAÄüƒÈT±ÀFd¤ìííaii‰ÔÔT£˜Rš¨"îß¿¨U«–Ì‘™æDÀƒÎÎÎ2GB†¢k×®>|x‘åB¸¹¹aΜ9ˆŠŠ‚B¡@‹-4.ÃW/ÏËË+vÛwîÜA—.]P³fÍbÛ©T*‡¥¥¥4>þøc4mÚ¶¶¶¨Y³&‚‚‚ð÷ß—¸/¥õ?ÿü3š6m ;;;¼ôÒKøí·ßJÜÞñãÇѲeKØÚÚÂÞÞ]ºtÁµk×*ÕUaȈ©Ïš©Ï¢ƒÒ¾0ÿúë/B¥R¡^½z˜6mšÆz] ªú€PZûÌÌL <¨U«&Ož\©þ Ï éóæ¥åå}? z¢='''™#!C1räHlÚ´Iš KíÈ‘#HJJ Aƒ¤e X³fM™·mff†^½zaÞ¼yÅ®OOO×x´k×ÁÁÁÒz•J%ÅvõêUdee¡_¿~îoÍš5ÇÖ­[‘––†óçÏÃË˫ض©©©èÖ­zõê…Ç#)) ÎÎÎxçw*ÜUbȈ©ê%ͦkˆž÷…YPP€îÝ»£iÓ¦xøð!âââpðàA¬^½Zj£Ë„.¥µÿôÓO‘””„Û·o#** kÖ¬ÁÒ¥K+ÜŸ¡yøð!ÀÑÑQæHˆˆLóæ¥åå}? OðQyuëÖ vvvذaƒÆòeË–¡W¯^¹lXX&Nœ(ÍV[šºuëbäÈ‘hÒ¤I©moܸÇ#44T£?( 899á£>ÂÉ“'+ÜßäÉ“1wî\4kÖ À³‘ž®®®Å¶MJJBZZ>øàXXXÀÞÞ¡¡¡¸xñ¢Vö·*¢¢ºté"† VdyAAhÔ¨‘øþûïEdd¤ DAAÔF½<77·Øm'%%‰Î;‹5jÛÎÖÖVãaaa!ììì¤õ}ô‘hÒ¤‰°±±5jÔýû÷÷îÝ+q_JëO!V­Z%š4i"T*•xñÅEDDD…¶—••%Úµk'j×®-lmm…½½½èСƒ8uê”Ô&##C 4HT¯^]8;;‹I“&•ØWYã//@ÄÆÆVz[¤{:tÄ¡C‡äEëŠûþ¸|ù² ’““¥emÚ´)q;ÇæææêO!4h ¸yó& Aƒ2GBDd:˜0ÿ(oþQ™÷C©?ûê¿¢²>|8"##¥¿•µk×¢^½zhß¾½F;sssÌ™3³fÍBRR’ÖúÏËËÃÊ•+5F¯ýÛ¶mÛðý÷ßcÁ‚êC=ºó¯¿þÂ¥K—pùòeܽ{£F*¶½R©DDDÖ®]+]RþÏ?ÿ`É’%꿪UºÀÆ¡ŒÅe,m{æææððð¦nV(P*•hذ!€gîÕ«Wcòäɰ³³Cýúõñÿ÷X±bE…ã'Ó£þ|&$$ȉn¼üòËxùå—1eÊ<}úÉÉɘ6mÒÓÓ5’^µª>@È}@PŸø°µµ•–©Tªßc“{÷î¡Zµj¨[·®Üá™ æÌ?€²ç•}?ôMvv6îÞ½‹jÕªá…^;2 6D`` –-[X¾|9FŒ…BQ¤m`` 1~üx­õ¿cǤ§§k Z*lëÖ­2d"""àïï_¡>T*àÛo¿…µµ5œœœ0qâDìܹ³Øö999èÔ©BBBžžŽG¡víÚz{Iy¥ lJ¥C‡•>’’‚ˆˆŒ9R£íðáÃQ³fM̘1£²Ý±lÙ2øúúÂ××·Ä6Uy¦¨¢ú÷ï;;;ØÚÚâôéÓ8vìŽ\]]aoo¯ñ] P(J<ùõêU\½zãÆƒ••jԨѣG—X“›Vþâ9”±â6n܈´´4\¹rÉÉÉøä“Opä i‡©%¸Ð¤I}ºÆ­² «_¿>lmm1þ|äææ"-- ‹/†‡‡G¥ö·ªh¥ÀÆ¡Œ•çîîŽo¿ýVºÔÔGžv4jÔpýúu™#Ñ®ç}aþùçŸÒßÍ‘#G0gÎLš4IZ¯Ë„®%µW*•8p ¾ù椥¥áöíÛ˜7o† V©þ ŵk×nnn2GBDdZ˜0ÿ(-ÿ¨Èûa(nܸÒ­ˆÊÃÒÒƒFBBFŒQjû©S§–i޵µ5Ú´iàY­ÁÚÚZcýòåËáååUl½dìØ±HMME‡ R©¤G~~> ::Zãߥõ7}útäçç£^½zpqq““/^,­/¼={{{DDD`óæÍ¨^½:êÖ­‹{÷îaãÆÅ¶/ëþV¥JÍ"ª¶iÓ&áèè(~ÿýw¡T*5fëü÷ì W®\–––bÁ‚Z™íâ­·Þ#GŽ,vÝŠ+„ƒƒƒ8~üx™÷¥¸þòòò„½½}‘Ù¬¬¬*´½âìÞ½[šU=ûÎÑ£G¥õ¥Í¾SÞþÊ‚³ˆ>'''@ÕXóæMaff&"##‹¼vîܹ¢fÍšâÌ™3eÞçõ7nÜ81zôhizØÀÀ@ѧOŸ m/&&F8p@dgg !„øóÏ?…¯¯¯x÷Ýw¥×Ž=ZtìØQ¤¦¦Š[·n‰ ˆ%K–T8þŠ`ÍðuèÐA¦w&2­ZµÄ©S§ä…ˆÈä0ÿ SÅüÃ8±ÀFÏ£.°ií®‹ÊX¾íåææbüøñptt„J¥B—.]йsgÌ;Wzí¬Y³P·n]¼øâ‹ðõõÅÀ5&ŽÐ·¡¤ŸÔ—ÄÅÅÉ ‘î!…Ba2÷œ#"Ò'Ì?È1ÿ 2mJmn,<<áááE–ûûû)¦988 99¹Ôm–V„ CXXX…^ëãã#M&P–×T«V ‹/Ö(ªUt{þþþˆŠŠzn|666X½zu‰ëË?™&&¸dŠ‘––WWWØÙÙÉ‘ÉaþA¦ˆù‘iã¼ÁDFN=º³´‚.‘1QÞ+:± Uó2EçÎøùùÉ É6"#×¼ysT¯^ñññxòä‰ÜáéÄÉ“'@ºdžˆˆt‹ù™"æD¦6"#gnnŽW_}ùùù8sæŒÜáéĉ'mÛ¶•9""ÓÄüƒL lD¦6" >È«úDÆ,==.\€J¥‚———Üá™,ædJ222 [[[øøøÈÉ€6" Å£ÕCdÌþøíÝy\Nyÿ?ð×U©´iˆé¶„P¤ÂXR„–¡ÉÒè¶UÆu+cŒ²o·™1]‘]M,eìF™0ˆB5Rö%ÊXn²L¥Òv~Ì·ë7É’¶sÕõz>×ã1Î9×ù¼Ž¹ª·wŸs>gÏ¢¨¨½{÷†ŠJµ®åCDD€õ)’sçΡ¨¨¬?ˆlD ÀÊÊ 6D\\òòòÄŽCT£Ž;°³³9 ‘bcýAŠ$&&ðé§ŸŠœ„ˆÄ‘PWW‡­­-òóóqüøq±ãÕ¨_ý0xð`‘“)6Ö¤H¢¢¢Ÿ}ö™ÈIˆH,l°)ˆÒö¥?ü‰ê£àÒ¥KhÞ¼9LMMÅŽCD¤ðX"xðàRRRмys˜››‹‡ˆD‘‚pppDGG‹œ„¨æDGGC888@"‘ˆ‡ˆHá±þ EpäÈ‚€Aƒ±þ R`l°)ˆvíÚ¡S§N¸}û6®^½*v¢ÁÛC‰ˆä ëR¬?ˆ`ƒH¡Œ9°k×.‘“U¿ììlDEEA[[›Ï?!"’#¬?¨>ËÉÉATT´´´X)8®L¤@F…o¾ù‘‘‘X¼x±ØqˆªÕÁƒ‘——WWW4lØPì8DDôXP}vðàAäææÂÅÅbÇ¡6wî\¤¤¤ˆƒäÌòå˰ÁF¤P:wî SSS\ºt —.]âCà©^‰ŒŒð÷?䈈H~°þ úŒõ‡b¹w™Bô:6؈̨Q£péÒ%DFF²À¥z#++ ÑÑÑÐÑÑ‘=P›ˆˆä몲²²ÞZÏ3†+ÄÒ{I0gΑ£<*íΧ¤¤ÀÌÌLä4TîÞ½‹víÚÁÀÀ»ˆ;P IDAT·o߆’ÅHuߦM›0uêTüç?ÿÁÆÅŽCDD¯aýAõQpp0<==ááá   ±ã‘Èd 6¢waƒ­~qppÀ‘#GAƒ‰‡¨Êzöì‰ÄÄD$$$ GbÇ!"¢7`ýAõM¯^½€óçÏ£gÏžbÇ!"‘©øùù‰ê}}}±#P5š2e Ž9‚ü‘.ÕyÉÉÉHLLD×®]Ù\#"’c¬?¨>IIIABBÌÍÍÙ\#"€ o %RXAAÖ­[%%%|õÕWbÇ!"¢ `ýAu]AAV¯^ %%%äææâüùóbG""9Á‘›5kÀßß%%%"§!ú0áááxøð!œÑ¾}{±ãQ±þ ºlÇŽxøð!z÷î-[¶ÀÂÂýúõÃøy&Rpl°)°nݺaÀ€¸yó&"""ÄŽCTaEEEøá‡>>>"§!"¢Áúƒêª¢¢",[¶ °`ÁÌ›7ºººˆ…““:wîŒÐÐP¼zõJä¤D$‰ ‚Ø!ˆH}ºØqˆÞ(++ ÆÆÆxôèΞ= ±#Q°þ º '':tÀ£GÞ½{Wè}©©©ÀŽ;PPP°±±!‘Hj26Õ26؈pöìYXYY¡qãÆ¸~ý:š4i"v$¢ræÍ›???899aß¾}bÇ!"¢*býAuAiý1lØ0ìß¿ÿƒßÿþ}"88YYY333øøøÀÅŪªªÕ™ˆD üßÿþ÷¿b‡ "ñµlÙ7nÜÀùóç‘›› GGG±#•‘––†/¾ø***Ø»w/ÿFDT°þ ywçÎ|ñÅH$Ø¿¥ê 8ÞÞÞhÒ¤ ®]»†›7obß¾}ؼy3ŠŠŠ`jj uuõ¸"ª-œÁFD2èÔ© œœŒÎ;‹‰HføðáØ·o||| •JÅŽCDDÕ„õɳÿûߨ³gOµÖ………ˆˆˆÀŠ+šš ÐÖÖ†‡‡¾úê+´jÕªZÆ!¢ÚÅEˆHÆÀÀ³gÏFQQ¦M›ößI^DEEaß¾}hÖ¬/^,v""ªF¬?H^EGGcÏž=hÚ´)-ZTmçmРÆŽ‹‹/"::öööÈÎÎF@@Úµk‡/¾øÉÉÉÕ6ÕÎ`#¢2rssabb‚ôôtcÊ”)bG"—““SSS¤§§#$$îîîbG""¢jÆúƒäMNNÌÌÌp÷îÝZ©?’““€;w¢¨¨`ooÙ³gãÓO?­Ñ±‰¨z°ÁFDåDGGã³Ï>ƒ®®.._¾ŒæÍ›‹‰ØŒ3°zõj 0ÇçŠ[DDõë’'_ý5ѯ_?œ8q¢Öêôôt"44ÙÙÙ€®]»ÂÇÇ£GFƒ j%}86ØˆèÆŽ‹mÛ¶ÁÙÙ»wï;)¨sçΡOŸ>hР.^¼###±#Q býAò !!–––¢Ö/^¼@PPÖ¬YƒZµj…éÓ§ÃÃÃ:::µž‰ˆÞ 6"z£ÌÌLtêÔ ™™™Ø¶m\]]ÅŽD &77=zôÀÕ«Wñý÷ßcÞ¼ybG""¢ÆúƒÄ–››‹ž={âÊ•+øî»ï°`ÁQó <<¸rå @WWžžž˜>}:gzÉ6؈è­vîÜ èêêâÂ… hӦؑHxyyaãÆøä“OpöìYÞAD¤ X˜JënݺáܹsrS‚€Ã‡# ¿ÿþ;@UU...ðññ™™™È ‰ˆ 6"z'777lß¾}ûöʼn' ¬¬,v$R€““444””„Ž;ЉˆˆjëÃÁƒáä䄆 "11:u;Ò%%%aùò娳gŠ‹‹!‘Hààà€Y³fÁÆÆFìxD KIìD$ß6lØ€6mÚàôéÓðóó;)€‡bòäÉ€€€6׈ˆëªm=‚»»;A€T*•ÛætïÞ‘‘‘¸qã¾üòKhhh ** ¶¶¶èÑ£G™•H‰¨öp½×éÓ§1`ÀÀ‘#G`gg'n ª· agg‡ØØX 6 û÷ï;‰„õÕ–¢¢"ØÙÙáÔ©Su²þxöì6mÚ„Õ«WãñãÇ€Ö­[cæÌ™pww‡¦¦¦È ‰g°Ñ{õíÛK–,Aqq1\\\pïÞ=±#Q=5{ölÄÆÆÂÀÀ¡¡¡bÇ!""±þ Ú2{ölœ:u ­Zµª“õGãÆ1þ|¤§§#((ÆÆÆHOOÇW_},X°=;&Q½ÇlDT!‚ `ذa8tèzöì‰ØØX¨©©‰‹ê‘Ò‡Z«©©áôéÓèÑ£‡Ø‘ˆˆHd¬?¨¦EFFbôèÑPUUÅéӧѳgO±#UYII :©TŠØØX€ššÆŽ ¹¾ý•¨.cƒˆ*쯿þB=pëÖ-¸»»#$$DìHTO¤¦¦¢wïÞÈÍÍEHHÜÝÝÅŽDDDr‚õÕ”ÔÔTXZZâåË—Æ”)SÄŽTíâãã!•J±oß>”””@II C† ¯¯/¬­­ÅŽGT¯°ÁFDäŸ~øsçÎ;Õqþù',--ñçŸòNDDôF¬?¨ºý³þ˜4iR¼5ôCܺu +W®DXXrss½zõ‚¯¯/œ¹R/Q5`ƒˆ>Øàì쌒’lݺnnnbG¢:꯿þ‚µµ5RSSakk‹¨¨(¨ªªŠ‹ˆˆäëª.Š\dffbݺuX¿~=233íÚµƒÆ ‘Õ]l°Q¥lܸ^^^PUUűcÇзo_±#QS\\ŒaÆáðáÃèÔ©âããѨQ#±c‘cýAUU\\ ggg8p:t@BB‚BÖ¹¹¹ ÃÊ•+qëÖ-€žž¼¼¼àíífÍš‰œP¾„††âüùóbÇ 9ÇUš··76lØ]]]œgDDDÿÄúC±„„„`Ú´i²úc×®]ÐÑÑ;–BÒÖÖÆŒ3àí툈 99 ,€ŸŸŸlA„Ö­[‹•¨Ú±ÁFTUÇbµiäÈ‘hݺ5FŒ“'O¢{÷îØµk,--EÉCÕ/??ÞÞÞØ¼y3`æÌ™X¾|9g ‘hXÔùùù˜6mBCC3fÌ€¿¿?ë9РAŒ;cÇŽÅÑ£G!•JñÛo¿!00ë֭È#àëë‹îÝ»‹•¨Úð;‘œ«©Åj[¯^½””„1cÆàøñã0`¤R)¦M›Æ‡ŸÖq·o߯èÑ£‘””---„„„`ôèÑbÇ"""býQݾ}cÆŒAbb"´´´ðã?b̘1bÇ¢7°··‡½½=RRRàïìܹ;w½=|}}1pà@~MRÇEˆäLm.FPÛš6mŠß~û ³fÍBaa!¦OŸŽ!C†àÿûŸØÑ¨’¶lÙ‚nݺ!)) FFF8{ö,›kDD$WXÔ?aaaøä“O˜˜ˆ: >>žÍµ:ÀÜÜ[·nÅíÛ·áëë =zèÒ¥ ÂÂÂPPP vL¢JcƒHDEEEHJJÂÚµkáêêŠÖ­[£eË–5jqîÜ9””” K—.ðòò’ý@zøð!öìÙXYYAMMMìK©0eee¬X±‡ÂnjÇ£K—.8|ø°ØÑè¼xñ£GÆÄ‰‘‰'"11;w;Q9¬?ê‡/^`̘1˜0a²²²0aÂ$%%ÁÔÔTìhôZµjÜ»wþþþhÞ¼9RSS1a´mÛR©YYYbÇ$ú`l°Õ¢gÏžá×_ÅÂ… acc]]]ôèÑÓ§OÇŽ;‘‘FÁÁÁK—.ELL ž?Žääd¬_¿cÇŽ­Ö•>Åäè舔” <ÿûßÿ0dÈxzz⯿þ;½Ç¯¿þ sssDFFâ£>BDD6oÞÌÅ ˆˆHî±þ¨»> sssDDD@WWøé§ŸXÔa:::ðõõÅ;w°e˘™™áÁƒ˜5kZµj…Y³fáÞ½{bÇ$ª06؈jÐ7OOO˜˜˜@OOC† Á²eËpâÄ ¼|ùFFF?~<‚‚‚ššŠgÏž!** ‹/†½½}½.š5k†ƒbݺuÐÐÐ@pp0LLL°oß>±£Ñ<~ü®®®2dîÝ»[[[\¼x£F;Q…±þ¨[?~ 777 <÷î݃ ëzFUUãÇÇÅ‹ {{{deeA*•¢]»vøâ‹/’’"vL¢÷bƒ¨šäåå!66Ë—/‡““š5kcccL˜0ÁÁÁ¸zõ*ÔÔÔ`mm9sæ`ÿþýxüø1®_¿Ž-[¶ÀÃæ¦¦¢­ô)‰Dooo\ºt ƒ ƒ0|øpüûßÿÆÃ‡ÅŽGA@XX:uê„;và£>Bhh(Ž=ŠV­Z‰ˆˆèƒ±þ¥õ‡‰‰ ¶oߎ>ú!!!8vì ÄŽG5@"‘`РAˆ‰‰Abb"ÆŒAŽ®]»ÂÁÁG;&Ñ[)ֿ䉪Q}^Œ@ mÚ´Att4¶nÝŠ&Mš`Ïž=022²eË——'v<…uöìYXYYa„ xöìF…+W®`Ò¤I\鉈ˆê<ÖòéŸõÇÓ§O1bÄ\¹rîîî¬?D÷îݱcÇܺu 3fÌ€––Ž9‚O?ýݺuCxx8 ÅŽITlD ˆ‹ˆeìØ±¸ví¼¼¼ŸŸ… ¢cÇŽˆŒŒ;šByøð!ÆŽ +++œ={&&&ˆŠŠBDDôõõÅŽGDDT­Xȇ‡bܸq²úÃØØQQQصkëÕºuk¬Zµ X¶lôõõ‘œœŒ/¾ø:t@`` ²³³ÅŽI€¿§à½ÎÜÜ©©©HII™™™ØqjͳgÏøøxœ9s xùòe™c5jKKKXZZÂÊÊ õúyib¸páfΜ‰'N¬¬¬°téRØÛÛ‹¬{úô)üýý±nÝ:¼|ùºººX¸p!¾üòK¨ªªŠˆˆ¨Æ±þ¨}¥õÇúõë‘““ÃúƒÞêÕ«WÇÊ•+qåÊ€®®.<==1}út4oÞ¼FÆussÃöíÛ±mÛ6¸ººÖÈTwùùùaÞ¼yl°Ñ»)JƒíƈG\\bccqíÚµr_FFF²fš••LLLîyibÙ»w/,X€«W¯ú÷ïo¿ýÖÖÖ"'«?^¼xU«VaÕªUÈÎΆªª*<<<°dÉèé鉈ˆ¨Ö±þ¨y/^¼@`` V­Z…¬¬,ÖTa‚ àðáÃX±bN:àïÅÜÜÜàëë “j 6z6بBêcƒ-//‰‰‰ˆ‹‹C\\âããñäÉ“2Ǩ««£gÏž²fš¥¥%Ÿ—&²’’lÛ¶ ß~û-nÞ¼ °µµÅìÙ³1pà@>£’=z„µk×bãÆxþü9TTT0qâD,\¸&""…Çú£f¼­þX°`Z·n-v<ªcΟ?©TŠ={ö ¸¸‰ŽŽŽðññMµŒÁ½ lT!õ¡Ávÿþý2Í´?þø£Ü1õõõaii‰>}úÀÒÒÝ»wçóÒäTQQÂÂÂàçç‡[·nÌÌÌàããÞFPAW¯^E@@ÂÃÃñêÕ+¨ªªÂÕÕ‹-‚¡¡¡Øñˆˆˆä ëêqíÚ5H¥ÒrõÇÂ… Ñ®];±ãQ—––†ÀÀ@lÞ¼YöxŸîÝ»cÖ¬Y1b”••+}n6Øè]JlÀß 6èMÌÌÌBJJŠØQ*¤°°PHLLÖ¬Y#¸¸¸Bég¼ô¥¬¬,téÒEðòò¶nÝ*ܾ}[ìØT ÅÅÅÂîÝ»+++Ùÿ[}}}aîܹ­[·ÄŽ'—òó󅈈ÁÞÞ^PRR:::¬Y³„û÷ˆHî±þøpo«?|}}…ŒŒ ±ãQ=ôäÉaéÒ¥B³fÍd_§†††Âš5k„œœœJÓÕÕU lÛ¶­šÓÖ®„„€Ð³gO¡¤¤¤ÜöÂÂBÁÑÑQ˜4iR¹÷–””íÚµV®\);^SS³ÌküøñoûÆ‚­­­ ©©)4oÞ\øþûïkâEñÃ?”~ÖØ`£·“÷ÛÓ§O…C‡ ,  hjj–k¨5jÔHppp–.]*ÄÄÄYYYbǦjvæÌaĈBƒ ‚D"ììì„ððpþÿáÂ… Â×_-4mÚ´L‘!•J…¿þúKìxDDDuëwcýAbËËË‚‚‚cccÙg°qãÆÂüùó…‡~йê[ƒMOOO +·½°°PØ»w¯ ¥¥%dgg—yï±cÇ555!33³ÌñQ\\, ÞÞÞB~~¾ðôéSÁÖÖ¶L†ºŒ 6ªyk°]¿~]زe‹àáá!˜˜˜‰¤\CÍÈÈH?~¼$¤¦¦ ÅÅÅbǦZòðáCá‡~Úµk'û<4lØPpvv"""„—/_б֤¤¤ ,:tè û»PSSF%ÄÄÄð낈ˆ¨š°þøÿX<*..öîÝ[f橺ººàáá!\»v­Bç¨o ¶õë× -Z´}úgì°°Pø×¿þ%—y¯‹‹‹0f̘rÇWĵk×Bff¦l[LLŒÐ§OŸjº2qÕ»›˜SA¶lÙ"tìØQÐÒÒZ¶l)ìÝ»·º/Qb6Ørss…S§N ~~~°aÃÊüöëŸß­­­…9sæû÷ï?~\ë9Iþ”””GÜÜÜmmmÙçECCCprr6nÜ(ܹsGì˜Õ*//O8räˆðõ×_—ù-¡k×®ÂÊ•+ùõADDTƒX°þ ùwæÌÁÙÙYv»²’’’0lØ0áÔ©Sï|_}k°åçç fffÂâÅ‹Ël/m˜ÍŸ?_èÕ«—ì}Ïž=ÔÕÕ…cÇŽ½ñø÷)m°=yòD¶í·ß~´µµ«ëÒDUolµ=ÕQáçŸ:wî,\¾|YAxüø±––V=&²Úl°ýùçŸBdd¤0cÆ ÁÂÂB6ÝþŸ/}}}aøðá‚T*Μ9#äçç×x.ªÛrss…Ý»w £F444Ê|ž:vì(|õÕWBddäO[AA/H¥RÁÑѱܵ™™™ ß}÷pãÆ ±£)Ö¬?H¾Ý¼ySðôôÔÕÕeŸ_ a÷îÝoœiYßl………ÂÑ£G áÞ½{åú ·oß$‰šš*‚ ¬Y³Fh×®l2Séñ5*óZ»víÇ-..:tè L:UÈÍÍžúè£*_O}ñìÙ3ÄÇÇËši ²¥“K5jÔ–––²fš……´µµEJLõ‘††œœœàääàïoòGŽÁ™3g‹´´4¤¥¥á矖½ÇÀÀ¦¦¦066F›6mжm[¢M›6ÐÔÔ¬¶l%%%xðàîܹ#{¥¥¥áòå˸|ù2òóóËß AôîÝ}úô­­-  +ˆˆˆH~°þ ’_Íš5ÃÒ¥K1g΄……! ·nÝ‚——/^ oooL›6Mì˜5F*•ÂÌÌ VVVåöyxx`êÔ©1b._¾Œ &Ti¬Ž;âèÑ£²?¯X±ýúõ«Ò9åM½›ÁVXXˆ“'Obذa¸~ý:=zT¦³š––†öíÛ#%%¦¦¦X»v-V¯^›7oB"‘ÈÎÓ¨Q£2çÿî»ïÞø…•œœŒnݺÁÃÃxùò%Fމ¦M›"22²¶.¿ÆTvÛ7¸¸8œ>}W¯^-÷322’5Ó¬¬¬`bb%%¥ê¾¢ KKKÃéÓ§qîÜ9\ºt )))xñâÅ[×ÒÒB“&M §§===4iÒZZZ(++—9þåË—(((@QQž>}ZîU\\üÆq”••Ѿ}{˜››£k×®èÛ·/zö쉆 VßÅ‘(XÉââbìÙ³R©çÏŸðw“¼E‹¸yóf½›Á_ý5ÂÃÑ™™Yf{AAZ¶l ---tíÚ{öìyçyÞçêÕ«000€¦¦&~ÿýw¸ººâСCèÞ½{õ_h-«·3ØÀÎÎvvv˜;w.f̘QfŸ¡¡!ììì‚ÀÀ@„††bòäÉH$eŽËÌ̬Ð¥ô‡Ù²eËаaC4lØóçÏ—ý†Jäåå!11qqq²[>Ÿè}uɰaÃ0xð`.F@DDDDDDDôšzÓ`£šõÝwß}Ð*¢DDDDDDDDŠ‚Óˆˆˆˆˆˆˆˆˆª€ 6""""""""¢*`ƒˆˆˆˆˆˆˆ¨† <îîîå¶ ‚€öíÛcÕªUHLL„D"A¯^½Ê<¾t{QQÑÏ}ÿþ}8::¢qãÆo<®tåáÒ—ªª*tttdû}||ЩS'hjj¢qãÆpqqÁ£GÞz-ïÂÂÂЩS'hkk£U«VØ·o_¥Î÷êÕ+ôïßúúúÐÒÒB£F`gg‡øøørç)**B·nÝÞùwUÑü•ÅQ ™2e "##‘““Sfûï¿ÿŽ?ÿüãÆ“m»sç¶nÝZás+))aøðáX³fÍ÷çää”yõë×®®®²ýZZZ²l7nÜ@~~>F]éñ¶nÝ ìÞ½ÙÙÙøã?Ð¥K—JOEEëÖ­Czz:rrrðàÁôéÓC‡-wl`` tuuß:NEóWlDDDDDDDD5dÈ!ÐÖÖÆŽ;Êl ÁðáÃѤIÙ¶¥K—bþüùÈÍͭйÿõ¯aÊ”)èØ±ã{MKKÃñãÇáááQf<333H$èééaæÌ™8sæL¥Ç[´hV¯^ @Ó¦MѶmÛJOYYfffPSSH$¨¨¨ÀÐаÌqééé Á’%KÞ~ñÌ_Un°qªcÍMu¬‰üDDDDDDDT{TTT0qâD„„„ȶ=þ{÷îÅ”)SÊëîîŽÆcùòåÕž#$$Ÿ|ò >ùä“·sôèQtïÞ½RçÏÈÈ@zz:.\¸€¶mÛBOOnnnxþüye#ÆŒmmmhjj">>'Ož,³Ú´iøöÛo¡¥¥U¥qªªÊ 6Nu¬¹©ŽÕŸˆˆˆˆˆˆˆjŸ»»;péÒ%@xx8Z´h›2Ç)++cÕªUJ¥øóÏ?«mü¢¢"üôÓOef¯½nÿþýX¹r%Ö¯__©1ž={¸yó&®\¹‚k×®áÁƒðôô¬ÔùJíܹÙÙÙ¸~ý:233áëë+Û÷Ë/¿ °°#GެÒÕ¡Ê 6Nu¬¹©ŽÕŸˆˆˆˆˆˆˆjŸ¡¡!ìììd³ØBCC1yòdH$’rÇÚÙÙÁÎÎsçέ¶ñ<ˆœœœ2“’þi÷îݘ0aöîÝ‹=zTjŒÒdË–-CÆ ¡§§‡ùóçãСC•ÎýOFFFX¶l6oÞ ÈÎÎÆœ9s°nݺj9UU¹ÁÆ©Ž5;ÕñŸª’ŸˆˆˆˆˆˆˆÄãááððpœ>}—/_Æ„ Þz¬T*Å®]»pþüùj;88...o¼ò§Ÿ~ÂäÉ“qàÀØÛÛWzŒ¶mÛBGG§LÓP"‘”yTXU£Aƒ€ëׯ###½{÷†žžž,»¾¾>öîÝ[mcVTµ,rÀ©Ž•÷®©ŽÿTÕüDDDDDDD$'''())aܸq:t(ôõõßz¬‘‘¼¼¼*ôàþüü|øûyïùùùeögddà·ß~{cÏdÍš5ðõõÅ‘#G`mm]¡ëxÛxÊÊʘ4i-Z„W¯^áùóçðóó{㪟9ßÅ‹qôèQÙ¾k×®aáÂ…psstéÒwîÜArr2’““±}ûv@RR>x¼ªª–§:VÝëSÿ©:ò‘xTUU1~üxܹs“'O~ïñK–,©Ð쯆 ¢OŸ>þî]4lذÌþÐÐPtéÒåý„¯¾ú YYY°µµ-³ˆdqq1àÂ… eþü¾ñüüüP\\Œ-Z M›6ÐÓÓCPPlÿ‡œ¯°°sçÎE“&M ¥¥GGG|öÙgX½z5 AƒhÙ²¥ìÕ¬Y3@‹-dçøÐüU¡R-gÁßS§NŠ#FThª£™™¬¬¬ªeì÷Muœ9s&8Pánì›ÔöTÇRÕ•ŸˆˆˆˆˆˆˆÄåïïÿrÛ{ôèQ®¿ ««‹ÌÌÌ÷žó}}‰¥K—béÒ¥•zo·nÝÊ-jù®÷¨©©!((¨LS­²çëѣߙïõã_?ׇ毊j™Ápªã‡žï}S«;?ÕŒjk°qªcõNu¬‰üDDDDDDDDTý$ æ¦ÈQÝfnnŽÔÔT¤¤¤ÀÌÌLì8DDDDDDDµÊÍÍ Û·oǶmÛÞúìwR\~~~˜7o^õÍ`#""""""""RDl°UlDDDDDDDDDUÀQ°ÁFDDDDDDDDTl°UlDDDDDDDDDUÀQ°ÁFDDDDDDDDTl°UlDDDDDDDDDU "v""""""""yçé鉓'OŠƒäLpp06؈ˆˆˆˆˆˆˆÞ+''GÖL!zlDDDDDDDDoáîîŽþýû‹ƒäœ¬Áæéé)f’S©©©bG """"""­­-lmmÅŽArN@;É¿””˜™™‰ƒˆˆˆˆˆˆˆHîü?¬«ØõÒ`uIEND®B`‚frr-7.2.1/doc/figures/fig-vnc-mesh.txt0000644000000000000000000000000013610377563014437 00000000000000frr-7.2.1/doc/figures/fig-vnc-redundant-route-reflectors.dia0000644000000000000000000007467513610377563020744 00000000000000 #Letter# ## #NVE 4 VN 172.16.4.1# #NVE 5 VN 172.16.130.1# #NVE 6 VN 172.16.132.1# #NVE 7 VN 172.16.6.1# #NVE 8 VN 172.16.8.1# #NVE 9 VN 172.16.134.1# #BGP Route Reflector 1 192.168.1.100# #Commercial Router Route Reflector 192.168.1.104# #NVA 2 192.168.1.101# #NVA 3 192.168.1.102# frr-7.2.1/doc/figures/fig-vnc-redundant-route-reflectors.png0000644000000000000000000022313113610377563020752 00000000000000‰PNG  IHDRØ‘†1âbKGDÿÿÿ ½§“ IDATxœìÝwxTeþþñw „ô ]: èÂÒ‘& E)‚HYA,° ì²QPøŠFWVQQVé½+ „E) -TCh&„”IOf~ð›Y³´’œdæ~]W®Kæœ9ç>Çaòð9Oq²X,DDäŽ222øí·ß¸pá.\àâÅ‹\¿~ßÿëׯsýúuRRRHLLÄl6c6›ILL téÒxzzàé鉻»;åË—ÏõS¹rem?F^®ˆˆˆ)))\¸póçÏsáÂ.]ºDll¬­ýqýúuÒÒÒHHH 33“””<<55•ŒŒ ’’’rµAâââ¸~ý:—.]ÊÕ±öÀÿ#ggg4h@«V­hÓ¦ ­[·¦V­Z…xwDDJ&ØDÄadgg³wï^¶lÙ–-[8|øp®ži¥J•²=µ ¡qãÆ4lتU«âääTd9SRR8uê”í ¶õiöï¿ÿžk¿²eËÒ¥KºuëF·nݨT©R‘e‘¼3™L|ÿý÷¶6È… rm÷ôô¤Q£F¶^ëÁÁÁ4hРȧˆ·õæ?räDDD”””k¿jÕªÙÚ;wÖÐRT`;—’’ÂúõëY¹r%Û·oÏõd¶|ùò´nÝšV­ZѪU+š6mŠ»»»iïîܹsìÝ»—}ûö±gÏ"""0›Í899ѤIž|òIú÷ïOƒ N+""âØbccY±bkÖ¬a×®]¤§§Û¶U¯^Ö­[Ó²eKÚ¶mKpp0...¦½3³ÙÌñãÇÙ»w/áááìÙ³‡sçÎÙ¶»ººÒªU+úôéCß¾}©ZµªiEDŒ£›ˆØ´´46nÜȲeËØ¸q#©©©À€Ö'®Mš4)ÒžiíÆlÛ¶Íö4üêÕ«¶m5¢ÿþ 0€:uê˜RDDÄqÄÇdzråJ–-[ÆÎ;mÃ>½¼¼èÔ©“­ hlÐckìØ±ÃöÓÙÙ™Ö­[Ó¿úõëGÅŠ N*"RtT`»ÁgŸ}Æ’%K¸qãpsÈE¯^½xæ™gxüñÇívƒÅbáðáì[·ŽeË–qüøqÛ¶¶mÛ2räHúõëW¬{艈ˆ”D‹…ü‘ °jÕ*Û¼gôíÛ—§Ÿ~švíÚQ¦Lƒ“ë«V­bÅŠ\ºt ¸9õF=5j]»v-¶=ôDD Š l"R¢¥¦¦òïÿ›Ï?ÿœŸþwwwzôèAÿþýéÑ£§,z‘‘‘,[¶Œï¾ûލ¨(|}}2dùË_68¡ˆˆHÉËÂ… Y°`gΜn®ÂùÌ3Ïп:tèàpE%³ÙÌž={X¶lË—/çÚµkÀÍ!±/¼ð£FÒR±[*°‰H‰tõêU>ùäæÏŸO\\ÁÁÁüå/aðàÁ”-[Öà„ÅÇîÝ»ùüóÏY¾|¹m•ÔÎ;3qâDºtéR¢‡ÉŠˆˆµS§NÆ¢E‹HOOÇÉɉöíÛ3jÔ(úô郛››Ñ‹…¬¬,6nÜÈ‚ غu+999”*UŠ0aš4ibtD‘¥›ˆ”(Ç',,Œo¾ù†ŒŒ [CmìØ±´hÑÂèxÅZbb"‹-bîܹ¶'íÁÁÁL˜0RºtiƒŠˆˆ_?þø#ï½÷›7oÆl6ãããèQ£=z4AAAFÇ+Ö¢££ùüóÏs=µ>ìëÚµ«ÁéDD † l"R"œ:uŠ·Þz‹o¿ý³ÙŒŸŸ/¾ø"/¿ü2ÕªU3:^‰b6›Y³f ï¿ÿ>{÷î 00iÓ¦1dÈ\]] N(""R|„‡‡ÊO?ýÜîøÊ+¯ð—¿üƒÓ•,©©©|ýõׄ……Ùö5kÖŒ3f¨Ð&"%ž l"R¬?ž7ß|“%K–¿¿?'N䥗^ÂÛÛÛèx%Þž={˜>}:;vì víÚL›6Aƒáììlp:ã8p€ÐÐP¶oßÜü9uêT¨‡QÈl6³jÕ*Þ|óM"##hݺ53fÌ C‡§ÉØD¤XJHH`æÌ™Ì›7ÌÌLüüü?~<ãÇWa­ìÞ½›ÐÐPvíÚÀ#}:§N [·n„……Q¿~}ƒÓ‰ˆÜØD¤XÉÉÉáÓO?å7Þ ..wwwÆÏë¯¿ŽŸŸŸÑñìÞŽ;˜4i‡ wïÞ¼û[FDDìžÉdböìÙ„……‘––†¿¿?Ó§OgÔ¨Qš§´egg³hÑ"BCC¹rå ¥K—æ•W^!44Tí?)1T`‘bcÿþýŒ3&WqgΜ9<üðÃ's,f³™ J\\eÊ”aÒ¤IL™2E+£‰ˆˆ]Z¾|9¯½öšŠ;»]‘óÝwßeذaZõ\DŠ=ØDÄp)))„††2wî\Ìf37æÃ?ÔðDƒY‡é~ôÑGdggS¯^=,X@›6mŒŽ&""R .]ºÄرcY»v-]»våÃ?¤^½z'slÑÑÑLœ8‘åË—ЩS'>ûì3jÕªep2‘;SMD µuëVFÍ… pww'44”I“&iŽ“bäÈ‘#Œ9’ƒâììÌèÑ£™5k–VN‘Ëb±ðÙgŸ1yòd©P¡}ôÏ>û¬ÑÑä6lØÀ˜1cˆ‰‰ÁÝÝ·Þz‹ñãÇãââbt4‘[¨À&"†HKKãõ×_ç“O>Áb±Ð®];,X@:uŒŽ&·‘ÍG}Ä´iÓHMM¥V­Z,^¼˜V­ZMDDä¾\½z•^x-[¶0tèPÂÂÂ(_¾¼ÁÉäv’’’˜2e óçÏÇl6Óºuk¾ùæŽ&"’‹ l"Rä>ÌàÁƒ9~ü8îîî¼û;Vsk”gÏžeèСìÝ»WWW¦L™ÂÔ©SÕãPDDJ„uëÖ1räHbcc©T©_~ù%O<ñ„ѱ$vïÞÍСC¹pá>>>ÌŸ?ŸAƒKDÄÆÙè"â8, |ð-Z´àøñã4iÒ„ƒòòË/«¸VB<üðÃüôÓO¼ù曼õÖ[<öØcDGGœLDDäÎÒÓÓ3f O=õ±±±<ùä“=zTŵ¤mÛ¶9r„çž{ޤ¤$ÌsÏ=‡Éd2:šˆ l"RDL&#FŒ`Ù²e8;;ó׿þ•™3gjUÊìçŸfðàÁœ;wþýïÓ¹sg£c‰ˆˆäÍ3Ï<ÃÁƒñðð ,,Œ_|ÑèXò–.]ÊK/½Dbb" 4`ÕªUÔ­[×èX"âàT`‘BEïÞ½9~ü8~~~,Y²„îÝ»K @bb"C‡eݺu¸¸¸0sæL&Ož¬‰""R,|ÿý÷ 8ØØXj׮ͪU«hÔ¨‘ѱ¤œ={–gžy†#GŽàããâE‹xê©§ŒŽ%"LCDE¤PmÞ¼™fÍšqüøqBBB8xð ŠkvÄ××—5kÖ0sæL, ÿûßéׯ©©©FG÷ÁеkWbccéÕ«PqÍŽ<üðÃìÝ»—Aƒ‘””DïÞ½™6mê?""FQMD ÍüùóéÕ«III 4ˆ}ûöñðÃK ˜““ÿøÇ?ظq#åÊ•cåÊ•tèÐk×®MDDPNNcÇŽå¯ý+‹…·Þz‹µk×âççgt4)`,Y²„>úWWWf̘ÁsÏ=GFF†ÑÑDÄiˆ¨ˆ8³ÙÌäÉ“yï½÷prrbúôéL:UÃ@TTÝ»wçìÙ³Ô¬Y“7R¿~}£c‰ˆˆƒ0™L<ûì³lܸwww-ZDß¾}Ž%E`Û¶môëפ¤$ÚµkÇêÕ«)W®œÑ±DĨÀ&"*33“çž{ŽåË—Sºti>ÿüs† bt,)B±±±<õÔSìÛ·²e˲víZÚ¶mkt,±s¿ÿþ;O<ñ¿þú+¬]»––-[KŠPDD=zôàâŋԭ[—­[·R£F £c‰ˆƒPMD LZZÏ<ó ›7o¦lÙ²¬ZµŠöíÛK ––ÆÐ¡CY±b¬^½š.]ºKDDìÔåË—éÔ©'Ož¤N:lÚ´IÓR8¨Ë—/Ó³gO:DõêÕÙ¾};uêÔ1:–ˆ8ØD¤@˜L&ž|òIvîÜI… ضm76:–(''‡1cư`ÁÜÜÜøî»ï´º—ˆˆ¸ .бcGΟ?O“&Mغu+*T0:–())‰=zN¥J•ؾ}»¸‘B§EDä%&&Ò¥KvîÜI•*Uøé§ŸT\\\\øôÓOyíµ×ÈÈÈ _¿~|÷ÝwFÇ;rúôiÚµkÇùóçùóŸÿÌ?ü âšàããÃÖ­[yüñǹzõ*íÛ·ç—_~1:–ˆØ9ØD䤦¦Ò£GöíÛG`` »wï¦^½zFÇ’bÂÉɉ?üüãdee1xð`V¯^mt,±Öžk/^¤]»vìØ±ƒ²eËKŠ Ö¯_Ï“O>Éõë×éÖ­FÇ;¦!¢"’oiiiôìÙ“~øêÕ«ÎC=dt,)¦Þxã Þzë-J—.ÍÚµkéÖ­›Ñ‘DD¤„Љ‰¡C‡œ9s†víÚ±yóf<<<ŒŽ%ÅPVVýúõcíÚµT¬X‘ððp‚‚‚ŒŽ%"vH6É—ÌÌLúôéÃÆmÃBÕX‘{™0aaaaxxx°iÓ&{ì1£#‰ˆH Ëc=Ɖ'hÞ¼9[·nÅ×××èXRŒeffÒ³gO¶oßNµjÕ×ê¢"RàT`‘ûf±X2dK–,! €ü‘ KJ‹ÅÂèÑ£ùì³Ïðõõe÷îÝKDDJˆÔÔT:vìÈþýû áÇÔ°PÉ“ÔÔTºuëÆîÝ» bïÞ½KDìˆæ`‘û6eÊ–,Y‚¯¯/Û¶mSqMòÌÉɉùóç3xð`éÞ½;111FÇ‘ ''‡²ÿ~‚‚‚4çšÜëœlMš4áÌ™3ôë×ÌÌL£c‰ˆQMDîË¢E‹˜={6...¬\¹’&MšIJggg¾üòK:vìHLL Ý»w'))ÉèX""R̽òÊ+¬[·Ž€€6oÞ¬ÞGrß|}}Y¿~=ÕªUã§Ÿ~bøðáh@—ˆØD$Ï~øáFÀ¼yóèÔ©“Á‰¤¤*]º4«V­"((ˆˆˆ† BNNŽÑ±DD¤˜ cþüù¶^Hš÷Uò«ZµjlÚ´ ___–,YÂÔ©SŽ$"vBs°‰HžDGGÓ´iSbccùÛßþƬY³ŒŽ$vàÌ™3´jÕŠØØXBCC™1c†Ñ‘DD¤˜Ù¹s'?þ8f³™¥K—òì³ÏIìÀ?üÀOFG‘N6¹§ôôtÚ´iÃ/¿ü“O>Éš5kprr2:–؉ݻwÓ©S'²³³YµjO?ý´Ñ‘DD¤˜ˆ‰‰áÑG%66–7ÞxƒéÓ§IìȼyóxõÕWñõõeÿþýÔ­[×èH"R‚©À&"÷4bľüòKj×®Íþó|}}Ž$væÃ?düøñøúúràÀêÔ©ct$1Xff&mÛ¶åÀtïÞ 6蟸!C†ðÍ7ßP¿~}öïß···Ñ‘D¤„Òl"rW .äË/¿ÄÓÓ“Õ«W«¸&…bܸq 4ˆÄÄDúöíKzzºÑ‘DDÄ`ãÇçÀ±dÉפP|úé§4iÒ„'Nðâ‹/GDJ0õ`‘;:sæ <ò&“‰o¿ý–IìXjj*Í›7'22’×^{?üÐèH""b 6ðä“OR¦L~þùgBBBŒŽ$vìܹsüéO"!!E‹1dÈ£#‰H ¤›ˆÜVvv6mÚ´aÿþýŒ1‚Ï?ÿÜèHâ"##iÖ¬lܸ‘'žxÂèH""RÄ®]»FãÆ¹víŸ|ò cÆŒ1:’8€ï¾ûŽgŸ}Ž9B`` Ñ‘D¤„ÑQ¹­3f°ÿ~‚‚‚Ô“HŠL£Fø¿ÿû?, Ç'66ÖèH""R„, #GŽäÚµkôìÙ“Ñ£GIÄ€2dIII 4ˆœœ£#‰H £l"r‹_~ù…æÍ›ãääDxx8Í›77:’8‹ÅB=ؼy3ýúõcÙ²eFG‘"òÅ_0räH*V¬È‘#G¨X±¢Ñ‘Ä$%%ñÈ#pîÜ9fÏžÍäÉ“Ž$"%ˆ l"’KVVþóŸ9|ø0Ó§Oç7Þ0:’8 «W¯Ò¨Q#®_¿Îš5kxê©§ŒŽ$""…ìÊ•+4lØ7n°víZž|òI£#‰Ú»w/mÛ¶ÅÍÍ£Gdt$)!4DTDryï½÷8|ø05âïÿ»ÑqÄAUªT‰°°0ÆŽKbb¢Á‰DD¤°½òÊ+ܸqƒAƒ©¸&†iÕªcÇŽ%--¿üå/¨?Šˆä•z°‰ˆMTT7&++‹={öhh¨®k×®lÛ¶Ñ£G3þ|£ãˆˆH!Y³f ½{÷ÆßߟãÇ`t$q`&“‰† Í‚ 9r¤Ñ‘D¤PMDl¬ÅŒqãÆñÁG„óçÏLZZû÷ï§iÓ¦FG‘–ššJýúõ‰ŽŽfÑ¢E 2ÄèH"lÚ´‰=zP¾|y¢¢¢(W®œÑ‘D¤˜ÓQ`Æ lÛ¶êÕ«3cÆ £ãˆP³fM¦OŸŽÙlfܸq¦!"b‡æÌ™Ctt4?þ¸ŠkRltïÞþýûsýúuÞ|óM£ãˆH  l"BVV5"**Š%K–0hÐ £#‰ØdffÒ¨Q#NŸ>ÍÒ¥K8p Ñ‘DD¤€\¼x‘zõê‘™™É¡C‡hÔ¨‘Ñ‘Dl¢££©_¿>™™™=z”úõëIDŠ1õ`æÍ›GTT-[¶TñBŠÒ¥KóÞ{ïð·¿ýÔÔTƒ‰ˆHA±~¯¿øâ‹*®I±S½zu&L˜@vv6ãÇ7:ŽˆsêÁ&âà©Y³& ìß¿ŸfÍšI䶺téÂöíÛ™={6“'O6:Žˆˆ< _ý•¦M›âççÇéÓ§)_¾¼Ñ‘Dn‘ššJ:u¸téÛ·o§sçÎFG‘bJ=ØD\XX7nÜ`ðàÁ*®I±öþûïãììÌ»ï¾KRR’ÑqDDä½ñÆX,¦M›¦âš[¼óÎ;L›6Íà4"Rœ©›ˆ‹§fÍš¤¦¦ròäI~øa£#‰ÜÕsÏ=Ç’%Kxë­·˜:uªÑqDD$Ÿ~þùgZ¶lIÕªU9sæ eÊ”1:’È™Íf5jĉ'ظq#Ý»w7:’ˆCêÁ&âÀæÌ™CRRÆ SqMJ„iÓ¦áêêÊûï¿Ï7ŒŽ#""ùôÆoªâš{ÎÎÎLŸ>¸ÙQ¹õ`qP7nÜ zõêdffE5ŒŽ$’'ÇgáÂ…L›67ß|Óè8""rŸ¬½×9uê¥K—6:’È=Y,š4iÂÑ£GY·n½zõ2:’ˆ3êÁ&â þùÏb2™1b„ŠkR¢L:WWW>ùäÒÒÒŒŽ#""÷ɺ2thh¨ŠkRb899Ùz±………FDŠ%õ`q@™™™ríÚ5N:EPPÑ‘Dî‹u.¶ùóç3zôh£ãˆˆH9s†ºuëR±bE.\¸ ›”(f³™àà`Ž?δ@˜ˆä¢l"èÛo¿åÊ•+<óÌ3*®I‰4aÂàæÊ¢f³Ùà4""’WsçÎÅl63vìXפÄqvvfܸqÀ{bŠˆX©›ˆjԨǎcß¾}´hÑÂè8"ùÒ±cGvîÜÉš5kxê©§ŒŽ#""÷ÀC=ÀÅ‹ñóó38‘ÈýKKK£V­ZÄÅÅqöìYªW¯nt$)&ÔƒMÄÁìÞ½›cÇŽÑ¢E פD›4iŸ|ò‰ÁIDD$/¾úê+L&/¼ð‚ŠkRb¹»»3vìX²³³ù׿þet)FÔƒMÄÁ :”Å‹³hÑ"† bt‘|3›ÍñÛo¿qæÌjÖ¬it$¹ kúÈÈH6lht‘|»|ù25jÔ B… üöÛo¸ººIDŠõ`q ¬X±???úöíkt‘âìì̈#0›Í|ñÅFÇ‘»Ø³gÇŽ£uëÖ*®I‰W¥JzöìÉåË—Ù°aƒÑqD¤˜PMÄ|óÍ7¤¥¥ñÜsÏáîînt‘6lØ0\]]ùꫯÈÎÎ6:ŽˆˆÜõAȈ# N"R0FŽ ÀçŸnp).4DTÄ4iÒ„#GŽpøða7nlt‘Ñ«W/6lØÀúõëéÙ³§ÑqDDä$%%Q¹re\]]¹rå FGy`999råÊ.\¸@µjÕŒŽ$"S6qüøqŽ9Bƒ T\»2hÐ –.]jp¹µk×’ššÊSO=¥âšØ ú÷ïONNË–-3:Žˆ*°‰8ë/þþýûœD¤`õìÙwww6lØ@ZZšÑqDDä¨ "öÊú™VMD@6‡ñÝwß0`Àƒ“ˆ,oooºwïNrr2›6m2:ŽˆˆüABBÛ¶mÃÏÏ.]ºG¤@ýùÏ&00pá£㈈ÁT`qGåäÉ“„„„P¯^=£ãˆ8káØZH‘âaõêÕdffÒ»woJ—.mt‘åääDÿþý±X,êÅ&"*°‰8‚5kÖðôÓOœD¤p<ñĸ¹¹±yóf222ŒŽ#""ÿßêÕ«èÝ»·ÁID ‡õ³mý¬‹ˆãRMÄX‡ÍuïÞÝà$"…ÃËË‹¶mÛb2™Ø½{·ÑqDDÈÈÈ`çθ¹¹Ñ¡C£ãˆŠfÍšQ®\9þóŸÿot1 l"vîúõëüç?ÿ¡|ùò4kÖÌè8"…¦[·nlÙ²Åà$""ðÓO?a2™xì±Çðòò2:ŽH¡pqq¡k×®äää°uëV£ãˆˆT`±s[·nÅl6ÓµkWœõW^ì— l""Å˶mÛ´¸Ø½®]»¨À&âàô¯m;§á¡â(6lHõêÕ9vìÑÑÑFÇqxjƒˆ£èÖ­NNNlÙ²‹Åbt1ˆ l"vÎ:UÛ¶m N"RøÚµkÀ®]» N""âØbcc9qâÔ¯_ßè8"…ªbÅŠÔ­[—k×®qêÔ)£ãˆˆAT`±c111DGGS­Z5ªW¯nt‘BצMÂÃà N""âØöìÙ@ëÖ­ N"R4¬Ÿuëg_D l"vÌZd°Dì·""ÅÃÞ½{hÕª•ÁIDІò‰ˆ l"vL[q4 4 lÙ²?~œ7nGDÄa©›8=äØDìØ¾}ûhÙ²¥ÁIDІ³³3þóŸ1›Í8pÀè8"")++‹_ý•R¥Jñè£G¤HÔ®]NŸ>M\\œÑqDÄ*°‰Ø©œœŽ;†‹‹ 64:ŽH‘iܸ1GŽ18‰ˆˆc:yò$éééÔ«W2eÊG¤È„„„pôèQƒ“ˆˆT`±SgΜ!--   ÜÝÝŽ#RdT`1VDDÁÁÁ')ZÖϼõ8ØDìÔáÇhÒ¤‰ÁIDŠ–·""Ʋ>à°>ðqjƒˆ86ØD씞‹£ªW¯¥J•âäÉ“deeGDÄáX‡ÇY‡Ë'‡¢OŸ>àééI`` ¯¼òŠ]Ï™uèÐ!¼¼¼ÈÉɹç¾ÄÉɉììì;nóòòÂËË ___ºuëÆÉ“' $çÝÎ]R¨½ˆcSMÄN8q€úõëœD¤h•*UŠ   ²²²8sæŒÑqDDÎñ7w— IDATãÇ›+;'»ví¢}ûötìØ‘sçΑ’’®]»ðòòâ—_~1:^¾effÞuû#<‚ÉdÂÅÅ¥@Η€Éd"::š*Uª0`À€9nA¸×½(lÖv·µ."ŽE6;uþüyjÖ¬ip‘¢À…  Í!"âh²²²¸té¥J•¢jÕªFÇÉå•W^áÿø/¿ü2ÞÞÞT¯^Y³fѵkW~ÿýwúô郯¯/þþþŒ;–ŒŒ à¿=¬Ö®]KÆ ñôô¤iÓ¦DFF²víZ‚ƒƒñððàÑG娱cù~@rr2#FŒÀßß???žþyRSSssÉ’%Ô®]›Š+’’ÂK/½DÅŠñòò¢aÆ9rä–žaÿú׿hРÞÞÞøùùÑ·o_®]»vß÷Ó××—#FäÊ—û÷ÇjÖ×i×®~~~xyyñõ×_çû^ÅÓÓ“€€RRRˆ54‹ˆ=ØD씵°`-4ˆ8’ZµjpîÜ9ƒ“ˆˆ8–‹/’““ÃC=T`=¦ ÂåË—9zô(Ï>ûì]÷4h...\¹r…ÈÈHÂÃà ͵ÏòåË çÆ4nܘž={²|ùrvíÚEBB7æÅ_| ÷¼ð ÄÇÇsîÜ9.^¼È¥K—˜4iR®cnÞ¼™_ý•Ë—/0lØ0Nœ8Á¡C‡0™L¬Y³†råÊÝrU«Veýúõ$''Ebbâ-yó">>žÏ>ûŒæÍ›ß×ý»OOOvíÚü·‡ÜóÏ?Ÿï{a$ëÃmëÃnq ±; ÀâëëktCÌ™3ÇX&Nœht‡òý÷ß[KÇŽŽ’ËáÇ-€%--íŽû\¹rÅX¢¢¢l¯}ûí·–J•*Y,‹å?ÿù°\ºtɶ}÷îÝ·}ÍÕÕ5ß899YNŸ>mÛ¾iÓ&‹¿¿®cž?Þ¶ý÷ß·–ÈÈÈ[®ËºVVÖm¯û‡~°xxxÜs_ë6___‹———°Z._¾|_÷ïÇþãk·ÛžŸ{a´gŸ}ÖXþýïEDŠ˜k‘VóD¤H¨÷š8: 1Fq¢" ¸9„±zõê·ÝçêÕ«¹¶×¨Qã–á“*T°ýw™2enûZvvv®¡÷óžK—.a±XhÚ´©m»Åb!33³Ùl{­Zµj¶ÿ¶öÜ ºý øƒU«Vñî»ïENNf³™ÔÔÔ<-‚‡‹‹  G,\¸)S¦äùþÝüÜ £© "â¸4DTÄYYUªT18‰ˆ1¬ 혘ƒ“ˆˆ8–âÚ©R¥ !!!üûßÿ¾ã>•*U ::ÚöZttt‘ÏëeÍEBB $&&’––†³óíÿùf½ßgÏž½ë±¯\¹Bÿþý™8q"W®\!11‘uëÖ7 WyåääDóæÍ™5kï¼óqqq÷¼nnn˜L&Ûö?ßœœœn9O~î…Ѭÿ/ŠÃpU)ZÅó[IDˆuRUëÓÚÂö¿Ë¶{zzÒ¡C"##síÁ€¨T©^^^н{w6nÜxÛãøùùѳgÏ;®Y–‹/ˆŒ999ôíÛooo¼¼¼ððð(eì âúîäÒ¥KtïÞråÊÚ9î¦|ùò\¿~½HÏ+"âè¬ß»ÖïáâdÞ¼y¼óÎ;üóŸÿ$99¸YúÛßþÆÖ­[©T©;v$44”ÔÔT®]»Æ¬Y³¼úê«\¹r€Ó§OsñâÅ\û¥¥¥‘““ƒ¯¯/nnnÄÄÄ0cÆŒ|g}á…¨\¹2|ðÁ=ï_íÚµñõõå“O>!''‡‹/òöÛoÛŽeýÌü±H˜Ÿ{a4kû[‹ˆ8ØDìQ[뤴±±±Ñ¿Û¶ððpÚµkGëÖ­9qâ&“‰³gÏ2bÄ6mÚtÛãœ?ž²eËæ:ÎÝÎ[—‹·zŒÇŽcåÊ•œ={“Éd›¸8ÈÌ̼íëÎÎÎôîÝ›¹sçq¢›¬“:[ã""R4Šs­]»vìܹ“;vP³fM<==yì±ÇHKK³ A\²d TªT‰ ТE fΜYäY¿ùæ<==©[·.ÞÞÞ´k׎ˆˆˆ»¾ç«¯¾¢V­Z„„„àííMïÞ½où=X«V-fÏžÍàÁƒñööæ™gž¹g;ën\]]yóÍ7ùøãIHH¸ëý+S¦ ‹/æ‹/¾ÀÇLJ~ýú1bÄÛ±3f Í›7ÇÏÏÅ‹çû^IùD˜¡3À‰H¡ µ–3fÉùn7)í?þh›¬×b±XBBB,sæÌ¹ïㄇ‡[œïkÛŸ¯]»féÝ»·ÅÇÇÇR¾|yËK/½dIOO¿ãû­¯%$$XÜÝÝ-€ÅÓÓÓâééiùꫯ,‹Å’””d>|¸¥|ùò___ËСC-)))ùÎx·ãmÞ¼9WŽgŸ}ö–cÞ+Éd²Œ3ÆR¡B‹§§§¥Aƒ–={öÜñúòrϾùæKPPÅÏÏïŽÿ?ïtýEÅÅÅÅX²³³‹üÜ""Žªk×®À²eË££ˆâàÁƒÀò§?ýÉè("RÄÔƒMÄýþûï@îÉs‹’ÉdbñâÅ´mÛ¸9ÅÑ£Gïû éõë×ù׿þÅ£>š§ý‹Órñ÷“ñnÇëÖ­[®·›;æ^y† Ɖ'8tè&“‰5kÖðÐCÝñúòrÏ6oÞ̯¿þZ¬çQ/6‘¢gýε~‹8àæb"âX´Š¨ˆ²NëååU¤çµ6(’““©T©?ýôðß9(þXð[µjÇÇb±‘‘AJJÊ-Ç)S¦ mÚ´aÙ²e÷>>L˜0¡C‡òñÇßwÆü/¯ybccY±b‘‘‘¶Iwk×® pÛU½òzÏfΜ‰··w^n™aÜÝÝ›ó͈ˆHÑHMMÀÃÃÃà$"Æðóón>ÀÇ¢l"vȺ̺‹‹K‘ž7..ÎÖªGŒ=øïd¯Öžu}úô!!!ï¿ÿžŒŒŒ\+WYsõêUV¬XAÍš5ïyÞ¤¤$~þùg’““Y¸p!@¡/ïç營ŸÏ>û,&“)×rñy͘ßãå5µ‡YPPPž®/¯÷̺Jgqæëë @bb¢ÁIDD‡uñ€âþFDD¤ ©À&b‡ŒêÁfåîîÎàÁƒm=تT©BHHÈm‡7”’°\üÝ2æwùù{½ßÚkí+rååúîtÏDDDDäά# ’’’ N""EM6)p|ûí·¶¡ˆÿüç?™5k}ô‘m~–´´48P ç. ËÅÿ1ãƒï^ï OŸ>¼úê«\¹r€Ó§OsñâÅÛ^ß½îÙýHOO·­2š‘‘Azzú}ãAxzzä~,""…Ëúkýq4Ö˜!"ŽA6;dT6???¼¼¼ð÷÷çäÉ“|÷Ýw¶mmÚ´a÷îÝìÝ»—úõëãííM5ذa«W¯ÆÕµ`¦„, ËÅÿoÆ=Þ½ÞÿÕW_Q«V-BBBðöö¦wïÞÄÇÇßñúîvÏ»;­[·n~­s¢ëg*;;»HÏ+"âȬ߹õ{]¤$²ÎAh“PDƒ“E¥u»Ó¡C~üñGvîÜIûöíŽ#bˆ¶mÛÎîÝ»iÓ¦ÑqDD‚ŸŸ‰‰‰$$$ØæÂ,‰}úàëë‹¿¿?cÇŽ%##¸ÙƒËÉÉ)W¡ÑúZbb"íÚµþ;½Æ×_ Ü\…uĈøûûãççÇóÏ?Ÿça†·Ëx·ãmÙ²…-ZP«V-xË1ï•'%%…—^z‰Š+âååEÆ Ù»wï¯//÷lÉ’%Ô®]»Ä.°d v»Å¤Dľ©À&b‡¬s¯ýquLÉŸøøx>ûì3š7on{mРA¸¸¸påÊ"## '44ôžÇòôô´õ³ö>{þùç› !ÅÇÇsîÜ9.^¼È¥K—˜4iR¾3ÞíxݺuË•ãv«½ß+ϰaÃ8qâ‡Âd2±fÍzè¡;^_^îÙæÍ›ùõ×_¹|ùrž®»¸±®j]MTD‡ÆÍˆØ!àæ¼"Ž*11 DÏ$"RÒx{{7{>Ùrrr0™L²wï^àæŠÝßÿ=QQQxxxàááÁ”)S7nsæÌÉ×¹âââXµjQQQ¶âÌ„ :t(üñ}gÌïñòš'66–+VI•*Ul+È_»ví–ãåõžÍœ9Óö9)IT`±Cöָɴ´4€"_½TDÄ‘ÙÛê‰qqq¸¸¸pàÀzôèÁÂ… ™2e W¯^ zõê¶}kÔ¨qÛÂR^]ºt ‹ÅBÓ¦Mm¯Y,2331›Í8;ß~ðÑ2æ÷xyÍcía”§ëËë=«V­ZžŽW\%$$7‡ÇŠˆcÑQ;@ll¬ÁIDŒa±XˆÇÉɉråÊGDÄaX¿sããã NRpœœœhÞ¼9³fÍâwÞ!..ŽJ•*mÛ/::Ú6o˜››{ºŽ?’n7?—õ˜QQQ$$$@bb"iii÷,†Ý-c~Ž——<Ö^kgÏž½mž;ïN÷Ì^ÄÅÅ7{ŠˆcQ6;dÛK—.1jÔ(~þùgnܸq˲ï§OŸfôèÑìß¿___^~ùeþþ÷¿Û¶O˜0M›6››]»våƒ>°5öî÷|_ý5³gÏ&&&???æÍ›ÇÓO? À®]»˜4i¸¸¸Ð¦MæÎ{ǧ¼y9ßý쟚šÊ˜1cX»v-¥K—æÅ_dÆŒyÞ^ÒÅÇÇ“““CùòåqqqáäÉ“L›6­PÎåææVh«„¹ººÚ0ggçB>[¶lÙB;¶¯¯ožþq˜^^^”*UªPŽíááaûGwA+S¦L¡õÖ,Uª”mnÏ‚æââ¢yŠìL… €›“ÙÛ›^xwß}—>ø€·ß~›Ž;ÊÂ… INNfÖ¬Y <¸9TÒ××—O>ù„É“'sùòeÞ~ûm۱ʗ/Ü,NÕ­[€Š+Ò«W/Æϼyó(W®W¯^%22’Î;ç+ãƒï^yèÓ§¯¾ú*‹/¦råÊœ>}š2eÊÜöú*Uªt×{f/¬ío=àq<*°‰Ø!k£æúõë')8ÎÎÎôîÝ›Aƒ1dÈ\ÛÌf3½zõ¢sçÎlÚ´‰””úõëGÕªU:t(póÍË–-£Q£F\¿~Q£F1`À~úé§û>ÀâÅ‹™3g+W®¤AƒÄÆÆÚžR'%%ѳgOþñ°{÷nÒÒÒxå•WèÛ·/‡Î×ùîwÿ×_˜˜.^¼È7h×®Õ«WgÔ¨QyÚ^Òýoã6..ŽåË—IDŠ˜““S¡Ñòóó+´U½½½ïúåAxzzRºtéB9ö#ÊG}Ä„ HMMeþüù 80_ç»_çÏŸÇd2ѸqcÛkMš4á7ÞÈÓv{üw¢dúõëgd$)b‹Å6ÙxaHHH °žS'''ßóP~¥¤¤™™Y(ÇöôôäĉÀÍ© D‘µýmm‹ˆãPMÄpáÂCs•ÚµkS»vm¦M›Æûï¿OJJ ³fÍÂd2a±Xn³víZÂÂÂòÝ£É:ìåôéÓ?~Ü6$õÅ_dÙ²e¸ºº²zõjºuëÆ´iÓÈÉÉ¡sçά_¿þ¯5/¬CU­½çàæYëý¸×öÂòT”Ο?@Íš5 N""Fqrr*Ô¹ óØ%™µ—æ¹sç N"b ëg¿V­Z'‘¢¦UDE쯯/eË–%11±P‡iÎÎά[·Ž¨¨(ªT©B‡hÓ¦ eË–½¥X´råJ† ÆêÕ«s-;?¬“}¿ýöÛ¸»»ãïïÏ”)Sذaps8Õã?ÎàÁƒ1™LÄÇÇS±bE ð`zŸùþ8¤Ëd2áåå…““Ó=·ÛØDDŒáhùDþ—õ³oý» "ŽC6;åh Üzõê±cÇnܸADD^^^´k×.×> .däÈ‘¬[·.Ï“ûÞNÍš5ñññÉUŒrrr² ŠŠŠ"**ŠqãÆQ¦LÊ–-˘1cl¸ÂV³fM¼¼¼r ‘=|ø05ÊÓv{ Æ­ˆˆ1ªU«†««+111…6ÌU¤8ÓC>Ç¥›ˆ²þR·þ’·ééé¶yc222HOO·m;qâ„­GÖÎ;ùàƒ µmŸ;w.'NdëÖ­´mÛöÎçââÂðáÙ:u*ܸqƒÙ³gÓ«W/ªW¯Ž§§'ü1YYY$''óé§Ÿœï뻟ý]]]2d3gÎ$99™‹/2wî\†ž§íö@6c¸ººR­Z5²³³móaŠ8 “ÉDll,^^^GDŠ˜ l"vªAƒÀÍ-í…»»;­[·nƒtww·m[»v-5jÔÀÛۛɓ'³téÒ\‹¼öÚk$%%ѱcG¼¼¼l?999:t(ןïu¾Ù³g“““CÕªU ÄßߟO?ýV¯^ÍòåËñõõ¥råÊ\¹r…o¿ýÖöþû=ßýîÿÞ{ïQ¹reªU«Æ£>Ê!C5jTž·—dœ9s777‚‚‚ŒŽ#"âpêׯ`[ð@ÄQXÛÝÖv¸ˆ8ØDì”u…È#GŽœ¤àX,–[~¬þö·¿Grr2 C‡·¼7++ “É”ëÇÅÅ€Gy$ןïu>777>ýôSâââHLLäÛo¿Í5áõã?ÎÁƒIMMÅd2±}ûvêÕ«gÛ~¿ç»ßý=<Û{ûí7ºvíÊ¿þõ/:tèçsg:uâ‡~àûï¿§cÇŽFÇ‘"¦l"vÊÅÅ…† ’““ñcÇŒŽ#Rd¬ÿ˜ 18‰ˆˆcªW¯eÊ”áäÉ“÷\°Çžü±ß‚Åb¹kqqÇŽ¹æŠ½ÑÑÑüöÛo:tˆš5kâïïÏàÁƒ¹qãuêÔáÑGå£>²-Æ4þ|˜¯óݯóçÏc2™l=êš4iBddd®ý^~ùef̘——W‘ä* jƒˆ86ØDì˜uü={öœD¤h˜Íföïß³³3Í›77:ŽˆˆC*UªúÓŸÈÊÊâ—_~1:N¡«]»6µk×fÚ´i¤¥¥ǬY³0™LÜn°ÐÚµk ãŸÿüg¾ÎÜœ÷íøñãœýôS|||X½z5Ë—/Ç××—Ê•+såʾýö[Ûûï÷|÷»ÿ{ï½GåÊ•©V­>ú(C† aÔ¨QÀÍ9úªU«fû©P¡U«V-±«Þ_¹r…¨¨(*W®L:uŒŽ#"q²ÜnæM±K—.eðàÁ 4ˆ%K–G¤ÐDDDBppð]Wn‘¢1qâDÞÿ}Þ{ï=&L˜`t‘Bóõ×_3lØ0žþy¾úê+£ãˆˆAÔƒMÄÎuëÖ ¶lÙ’ë©£ˆ½Ù¶m]ºt18‰ˆˆ<ñÄlÞ¼Ùà$"…Ëú·~æEÄ1©À&bçÊ•+GÓ¦M‰çàÁƒFÇ)4›6m {÷î'¸9•——ááá˜L&£ãˆŠœœ¶oߎ‹‹ ?þ¸ÑqDÄ@*°‰8€=z°qãFƒ“ˆ“ÉÄž={ðòòÒê]""Å„››;w&##ƒ;vG¤Pìß¿Ÿøøxš7oN¹r匎#"RMÄôîÝ D/}.r77n$##ƒ=zàææftùÿž~úiÖ¬Ycp‘Âam_[ÛÛ"â¸T`q5¢~ýúDFFrâÄ £ãˆ¸eË–пƒ“ˆˆÈ=õÔS¸¹,ÊmŽ IDAT¹±víZ222ŒŽ#R , Ë—/ÇÉɉ~ýúGD ¦›ˆƒ0`ß}÷ÁID Vrr2›6mÂÛÛ[󯉈3~~~téÒ…„„Ûb4"öbÿþýüöÛo4oÞœ5jGD ¦›ˆƒPMìÕºuëHOOçÉ'Ÿ¤L™2FÇ‘ÿ¡6ˆØ+õ ‘?r²X,£CˆHÑxä‘G8|ø0‡¢I“&FÇ)½zõbÆ ¬_¿žž={GDDþGRRUªTÁÅÅ…Ë—/ãééit$‘–M`` ×®]ãÂ… T­ZÕèH"b0õ`q £F`Á‚')111lÞ¼™jÕªñÄOGDDnÃÇLJþýû“””dëñ#RÒmÙ²…K—.Ñ­[7×DPMÄ¡ 4www–.]JZZšÑqDØÂ… ÉÉÉá…^ÀÅÅÅè8""rzÈ'öÆúY¶~¶EDT`q ~~~ôëׄ„–/_nt‘b6›ùòË/qvvføðáFÇ‘»hÙ²%5bß¾}DFFGä\¾|™M›6Q¥J-°$"6*°‰8ëS¶ùóçœDäÁlݺ• .йsgŽ#""÷0bÄ>ûì3ƒ“ˆ<˜/¿üìÝw\—õâÿÿˆˆ¨ˆ"æŠ4­\L 'n 5gns¤–i®´<.´²ÌUš•[Ô\ä,nQ,KMM3÷@DD@ÆïÏ÷ðûô9§r/Æó~»ù—®Ç9ÇsÎužï×ûº“’’BïÞ½±²²2#"9„^r ’¹ººEhh(uêÔ1#òTš4i¾}ûظq#mÛ¶5#""ÿ 66–矀ßÿ’%K.yr>¤bÅŠÄÄÄpþüyœœœL'‰H¡l"ùÐÈ‘#˜6mšá‘§ξ}û¨R¥ ­[·6#""ÁÞÞž>}úϼyóLçˆ<•+VpóæMÚ·o¯qMDþD'ØDò¡äääŒ×ŠŸ9s†*Uª˜Ny"]»veõêÕÌŸ?_ÉEÎ;GÕªU)]º4/^ÄÚÚÚt’ÈcKKK£zõêœ9s†ãÇãááa:IDr`ɇ¬­­:t(iii̘1ÃtŽÈùí·ß ¤T©RôèÑÃtŽˆˆ<*UªÐ¡C®]»Æ²eËLçˆ<‘7ræÌ7n¬qMDþƒ6‘|jРA-Z”Å‹sáÂÓ9"Íßߟ””†JáÂ…M爈È1bŸ|ò ÉÉɆkDOZZ“&M`Ô¨QfcD$GÒÀ&’OÙÛÛ3tèP’““ùøãMçˆ<–3gÎðý÷ßS¢D † b:GDDžBíÚµyíµ×øý÷ßY°`é‘DzvíZ¢££yõÕWiÕª•éÉô 6‘|ìîÝ»TªT‰ðË/¿ðÒK/™Nù[ÿ~öÚ”)S7nœéyJaaaÔ©S‡råÊqöìYH–-55•š5krúôi¶mÛ†¯¯¯é$Ét‚M$+Q¢Ç'%%…‰'šÎù[ÑÑѬ]»–R¥Jñþûï›Î‘gàåå…ŸŸW®\á›o¾1#ò·8}ú4õêÕÓ¸&"I'ØDò¹¸¸8*UªÄÝ»w9tèuêÔ1$ò_5kÖŒ={öðùçŸ3zôhÓ9""òŒ"""ððð xñâüúë¯8::šNùñññT­Z•+W®°gÏš4ib:IDr(`Éçììì˜0aééé 6 mî’mܸ‘={öàääÄСCM爈H&pss£GÄÆÆê$½äXŸ}öW®\áõ×_׸&"K'ØD„GáââÂéÓ§Y¶l½zõ2$’!))‰5jpþüyÖ®]K§NL'‰ˆH&¹rå U«VåáÇ„‡‡ãììl:I$Ã… ¨^½:©©©DEEQµjUÓI"’ƒé›ˆP°`AfΜ Àرc¹ÿ¾á"‘ÿ߬Y³8þ<ÞÞÞtìØÑtŽˆˆd¢òåË3zôhRSSyÿý÷u’^r”Ñ£G“˜˜È{ï½§qMDþ‘N°‰H†V­Z±mÛ6Þ{ï=æÌ™c:G„óçÏãììLRRGÅÃÃÃt’ˆˆd²ÄÄDªV­Êï¿ÿÎÒ¥KéÝ»·é$6oÞLÛ¶mqttä×_ÅÞÞÞt’ˆäp:Á&"æÌ™CáÂ…™7o‡2#ù\zz:äáǼûî»×DDò(¾úê+FŒÁÍ›7 I~wÿþ} À_|¡qMD‹6ÉP¹re&NœHZZ 99Ùt’äcK—.e÷îÝT¨PO?ýÔtŽˆˆd¡6mÚйsgbbbô21îÃ?äòåË4oÞ\Ï&‘Ǧ¯ˆŠÈŸ¤¤¤àååExx8'NdÒ¤I¦“$ºví5kÖ$&&†-[¶àççg:IDD²Ø7¨^½:111lÚ´‰6mÚ˜N’|(88˜ÆcccCTT•+W6$"¹„N°‰ÈŸXYY±páB¬¬¬øøã 5$ùLzz:o½õ111¼ùæ›×DDò‰çž{ŽéÓ§п®_¿n¸Hò›{÷îÑ«W/ÒÒÒð÷÷׸&"OD›ˆüÆGjj*=zôÐ[E%[Íž=›   Ê–-Ë×_m:GDD²Ñ[o½E›6m¸uë}ûöÕ[E%[½ûî»\¼x‘ 0|øpÓ9"’Ëè+¢"ò_¥¦¦âííMhh(½zõbÙ²e¦“$ˆŒŒ¤víÚ$''³cÇZ´ha:IDD²Ù­[·pqqáúõë|ùå—z&›d‹•+WÒ£Gìí퉈ˆà…^0$"¹ŒN°‰ÈU @V¬X¬\¹Òt’äqñññtíÚ•¤¤$†®qMD$Ÿ:xð /¿ü2Œ3†ððpÓI’Ç={–wß}€¯¿þZ㚈< l"ò—^|ñÅŒ¯è 0€ÈÈHÃE’W¥§§Ó·o_N:…»»»Þ*"’%&&2xð`Ú·oÏhÕª‰‰‰tèИ˜Óy’G%$$о}{âââèÙ³'ݺu3$"¹”6ù[=zô`À€$$$СCbccM'I4kÖ,Ö­[‡½½=ëÖ­£P¡B¦“DD$:u ///æÍ›G¡B…øòË/ùᇨW¯.\ {÷¦šÎ”<¨_¿~œùDÏ„•L³sçNF………¼ôÒK¦“D$—Ó[DEä±íÛ·×^{äädæÍ›Ç;ï¼c:Ir±[·nQ¯^=Î;G›6mذa––úÜGD$?8rä]»våÂ… ØÙÙ1oÞ<ºwïþ—þ›o¾áÝwßÅÚÚš]»váííµ’×DGGãííͽ{÷?~<“'O6$"y€6y"+W®¤gÏžXZZ²~ýzÚ´ic:Ir¡äädš6mJHH^^^ìÝ»W_ËÉÒÒÒ˜>}:ãÆãÑ£Gxzzòý÷ß?Öé¡÷Þ{¯¿þGGG:D•*U²¡XòšË—/S·n]._¾L÷îÝY¾|9¦³D$ÐÀ&"OlòäÉLœ8‘bÅŠ±wï^jÕªe:Ir‘ÔÔTzöìɪU«¨P¡¡¡¡T¨PÁt–ˆˆd±ëׯӻwo‚‚‚°°°`øðáL:kkëÇúǧ¦¦Ò¾}{6oÞL•*U ¡téÒY\-yɽ{÷ðöö&::š¦M›²mÛ¶Çþû'"òO4°‰ÈKOO§gÏž¬\¹GGGvïÞ³³³é,ÉÒÓÓ0` .¤xñâëH>D¯^½¸q㎎Ž,[¶Œ–-[>ñÏIHH iÓ¦9rgggöîÝ‹ƒƒCK^“@‹-8tèU«VåðáÃ/^Üt–ˆä!z؈<1 /^L›6m¸u믽öçÎ3%¹Àˆ#X¸p!¶¶¶lÙ²E㚈H÷èÑ#ÆŒƒ¯¯/7nÜ Y³fDFF>Õ¸düïG5ˆŽŽ¦eË–Ü»w/“«%¯IJJ¢M›6:t'''vîÜ©qMD26y*ÖÖÖ¬[·ŽæÍ›síÚ5š6mÊ¥K—LgI6aÂfÏžµµ56lЪEDò¸ .РA¦M›Føä“O ¢lÙ²Ïôs ¢J•*=z”Ö­[“IÕ’×$''Ó¹sgvïÞMÙ²eÙ½{·M!"YB›ˆ<5kkk6mÚDƒ øã?hÔ¨çÏŸ7%9ÐØ±c™2e ]ºt¡y󿆋DD$+­^½777ÂÂÂxá…Ø¿?cÇŽÍ´·E—+WŽ]»vQ¡B‚ƒƒñõõ%...S~¶ä‰‰‰tìØ‘Í›7ãèèÈŽ;ôr É2ØD䙨ÚÚòã?R¯^=.^¼ˆ··7§N2%9Dzz:C‡eêÔ©(P hÞ¼¹N<ŠˆäA ôïߟ®]»G‡ˆˆˆ ^½z™~­^x½{÷âääDpp0Íš5#&&&Ó¯#¹Óƒhݺ5[¶l¡T©Rlß¾]¦‘,¥MDžYñâÅ ¢iÓ¦\»v† n:K KMM¥_¿~Ì™36lØÀÖ­[)S¦ {÷îÅÕÕ•Õ«W›Î‘LE­ZµX´h… æÛo¿%00{{û,»f•*U8pà•+WæØ±c4nܘ7ndÙõ$wˆÅÇLJ]»vQ¶lYöíÛ‡‡‡‡é,Éã4°‰H¦(R¤?ýô¯¿þ:·oߦI“&ìÞ½Ût–’@§NX²d EŠaË–-´nÝ___¢¢¢hÛ¶-±±±tíÚ•ž={êÕ""¹Ü¼yó¨]»6§OŸ¦F„……1pàÀl¹ö /¼@pp0ÕªU#::š pöìÙl¹¶ä<—/_¦Q£F:t(ãïF5Lg‰H> MD2 ëׯ§k׮ܻw–-[²téRÓY’ÍnܸA“&Mذa%K–$((èOÏ\sttdãÆÌŸ?Ÿ"EаbÅ \]] 6X-""O#&&†víÚ1xð`0`aaaÔ¬Y3[;Ê–-ËxõÕW9wîuëÖåàÁƒÙÚ æEDDP§N¢¢¢¨^½zÆéF‘ì MD2•µµ5+W®äÃ?äÑ£GôéÓ‡ &žžn:M²Á©S§¨[·.aaaT®\™ÐÐп|îÎÛo¿Í‰'ðòòâ÷ß§I“&üë_ÿâÑ£GÙ\-""O#88www6n܈½½=ëÖ­ã»ï¾ÃÖÖÖHO©R¥Ø»w/mÛ¶åÎ;´hÑ‚5kÖi‘ì·mÛ66lÈ•+WhÒ¤ ÄÉÉÉt–ˆä#&Mš4Ét„ˆä-4oÞœòå˳mÛ6öíÛÇéÓ§ñõõÅÚÚÚtžd‘EøúõëÔ­[—]»výã­ƒƒ}úô!==ƒràÀ¶nÝJ£F(UªT6•‹ˆÈ“HMMå“O>¡o߾ܽ{—ºuë²sçÎ,y‘Á“²¶¶¦S§NÄÆÆÂ?ü@ZZ5ÂÂÂÂtžd‘™3gÒ¯_?éÕ«k×®¥hÑ¢¦³D$Ÿ±H×±ÉB;vì K—.ÄÆÆR£F Ö¯_ÏË/¿l:K2QZZ“'OfÊ”)¤¥¥ñæ›o²dÉ .üD?'$$„ž={ráÂlmm™9sf¶=¿GDDÏ•+WèÑ£ûöíÃÒÒ’1cÆ0yòd¬¬¬L§ý‡9sæ0bÄRRRhÙ²%+V¬ dÉ’¦³$ÅÇÇÓ¿Ö¬Yƒ……þþþŒ7Ncªˆ¡MD²Ü¹sçèСQQQØÙÙ@Û¶mMgI&ˆ‰‰¡gÏžlݺ+++>ûì3FŒñÔ7¶qqq :”eË–àççÇ¢E‹(]ºtff‹ˆÈSزe }ûöåöíÛ”)S†åË—ÿé›9ÑþýûyóÍ7¹qã•*Ubýúõ¸¹¹™Î’LpæÌ:tèÀÏ?ÿŒ½½=Ë—/ÇÏÏÏt–ˆäcz›ˆd¹*UªJ·n݈‹‹£]»v 6ŒÄÄDÓiò ‚ƒƒñðð`ëÖ­”.]š;w2räÈgúÔØÎÎŽ¥K—²fÍJ”(Á?þˆ‹‹ [·nÍÄryIII 6Œ¶mÛrûöíŒ7Bçôq  Q£F?~œºuëráÂêÖ­ËܹsõlØ\nÙ²e¼úê«üüóϸ¸¸pôèQk"bœ6ɶ¶¶¬\¹’¹sçR¨P!¾üòK¼¼¼ˆŽŽ6&O(%%…ñãÇÓ¤I~ÿýw6lȉ'hܸq¦]£sçÎDEEѬY3nܸŸŸƒ&!!!Ó®!""ÿì×_¥nݺ|ùå—XYY1}út¶nÝŠ£££é´ÇV¾|yöîÝËСCIJJbÈ!øùùqãÆ Óiò„bccéÒ¥ o½õ÷ïß§oß¾„††R¥JÓi""úЍˆd¿èèhºwïNtt4666L:•¡C‡bi©Í?§;{ö,=zô ,, +++üýý3f  Ȓ륥¥1sæLÆGRRÕªUcÅŠxxxdÉõDDäÿÀàÁƒ‰§råʬ^½OOOÓYÏdÛ¶môéÓ‡7nPºti.\HëÖ­MgÉcØ·o½{÷æÒ¥K”(Q‚o¿ý–Î;›ÎÉ ÿ7+"ÙÎÙÙ™°°0† FRRǧaÆœ:uÊtšü…””>ûì3\]] £J•*„„„0vìØ,×,--5jGŽ¡Fœ:uŠºuëòÙgŸ‘–––e×ÉÏîß¿OÏž=éÝ»7ñññtëÖ'Näúq  eË–DEEáççÇÍ›7iÓ¦ Ý»wçæÍ›¦Óä/ÄÆÆ2pà@š6mÊ¥K—hÔ¨×D$ÇÑÀ&"FØØØ0kÖ,víÚÅ‹/¾HHHnnnLž<™äädÓyò¿;v OOO>úè#’’’:t(xyye[ƒ««+ÇŽcÈ!ú(ãF[DD2ϱcǨU«+V¬ hÑ¢,^¼˜•+Wbggg:-Ó”.]šÍ›7³páBJ”(Á÷ßOõêÕ 0&ÿÇúõë©Q£óçϧH‘"Ìš5‹={öàääd:MDä?è+¢"bÜÇ™0a³gÏ&%%…jÕª1sæL|}}M§åkwïÞeܸqÌŸ?Ÿ””œ™?>uêÔ1ÚµcÇúôéõk×°··ç믿¦[·nF›DDr»ôôtfÏžñaŠ››«V­¢jÕª¦Ó²Ôõë×2d´hтٳgS½zuÃeùÛ¹sç5j›6mÀ××—o¾ù†Š+š ù:Á&"Æ.\˜/¾ø‚#GŽàááÁ©S§hÙ²%íÛ·çܹs¦óò””æÍ›ÇË/¿Ì¼yó(X° S¦LáØ±cÆÇ5€×^{¨¨(Þxã bccéÞ½;Ý»wçÞ½{¦ÓDDr¥[·náççLj#HNNfÈ!>|8ÏkeÊ”aݺulذ'''vî܉««+ƒæöíÛ¦óòû÷ï3jÔ(jÔ¨Á¦M›([¶,+V¬`Û¶m×D$ÇÓ 6ÉQÒÒÒXºt)ãÆãÚµkX[[3dÈ>úè#Lçåy?ýô£Gæ—_~Á‚.]ºðÙgŸåدb,X°€#FÏ /¼@@@ 64%"’kìÙ³‡ž={rõêUX´hmÛ¶5eÄÇ™9s&Ÿ|ò >ÄÞÞžñãÇóî»ïbccc:/OKIIaÉ’%Lœ8ñO÷'N¤X±b¦óDD‹6É‘âãã™:u*Ó§O'99™bÅŠ1|øp†޽½½é¼üðCV¯^Mzz:*T`ܸqôéÓkkkÓyyJjj*ßÿ=þþþœ?€víÚ1mÚ4ªT©b¸NDäÉh`‘íÒ¥K|üñÇ,Y²„””J”(Áˆ#xï½÷4´e‚0aÂöïß@ÕªUñ÷÷§cÇŽXZæž§¤¤¤àïïÏgŸ}FJJJƺóÃ×›DDžÔ¥K—èÚµ+‡¢@Œ?žqãÆeé[¡s£ãÇ3qâD~úé'*V¬ÈøñãéÑ£‡†¶g”ššÊºuëð÷÷çôéÓ4iÒ„É“'Ó AÃu""OG›ˆä ¿ýöþþþ¬\¹’ÔÔTŠ+F¿~ý6l/¼ð‚é¼\%55•õë×3}út¨\¹2'N¤{÷î¹jXû¿:DÏž=ùí·ß°µµeúôé 4 Ói""9Âúõëéׯ±±±<ÿüó¬X±B_­ÿ‡füøñìÚµ €råÊ1tèP¨ûžÐƒX¼x1³gÏæ·ß~ ~ýúøûûÓ¬Y3Ãu""ÏF›ˆä*¿þú+3fÌ €ÄÄD¬¬¬èر#C‡¥nݺ¦ór´˜˜–/_ÎW_}•qSëææÆÈ‘#éÒ¥ VVV† 3G\\C‡eÙ²eøùù±hÑ"J—.m¸LDÄœÄÄD†ηß~ @Û¶mY´h‘žoú:ÄŒ3ظq#iiiö 4ˆW^yÅt^ŽvéÒ%¾ýö[¾ûî;bbb°°°ÀÇLJQ£FѼysÓy""™B›ˆäJ7oÞä믿fÞ¼yoùªQ£ýû÷§W¯^”,YÒpaΞžÎX°`ëׯçáÇùæ¦600CéÒ¥Y´h~~~¦³DD²ÝÏ?ÿL—.]8yò$666L›6÷Þ{O§{ŸÒ¹sç˜9s&Ë–-#!! ¼½½0`íÛ·§pá¦s„G±iÓ&-ZDPPiiiX[[ÓµkWFމ³³³éD‘L¥MDrµ‡²fÍæÏŸOhh(… ¢U«Vtîܙ֭[S¤HÕÙ/22’µkײzõêŒÓj%K–¤{÷î 85j.Ì—/_æ­·Þb÷îÝXXX0hÐ ¦OŸŽ­­­é4‘l±`Á† FBBU«VeÕªU¸¹¹™ÎÊn߾͒%KX´hgΜ D‰´k׎Î;Ó¬Y³L·nݸpávvv|óÍ7tëÖÍtV¾p÷î]6lØÀÚµkÙ½{7)))ØÚÚÒ¸qcZ¶lÉk¯½ÆK/½d¸ôÙ\¼x‘íÛ·³}ûvvïÞM||<–––4lØÎ;Ó±cG —Šˆd l"’§=|øŸ~ú‰ÀÀ@vîÜ™q²  xñâÔ¯_ŸzõêQ¿~}j×®£Ox?žƒrèÐ!‚ƒƒ9}ú4ÿþ¯pKKKjÕªEëÖ­éܹ³¶üùðÃùꫯHOO§aÆèM´"’'¤¥¥1mÚ4ÆOJJ žžž¬^½šÊ•+›NË—îܹÃúõë äÀ$&&fü^¹rå2îA¼½½quuͱ_'MMMåäÉ“„„„pðàABBB¸téRÆï[[[Ó AÚµkGÇŽ)S¦ŒÁZs4°‰H¾‘ššJXXÛ¶mcûöí?~œ´´´Œß/X° ¯¼ò 5kÖÄÕÕ•š5kR³fMžþy (m÷îÝã×_%22’èèhNžýôS ,h:M€„„öíÛǶmÛØ±cgÏžýÓïÛÚÚR½zõŒûì¢âW IDATgggªU«F¹rå²µóæÍ›œ:uŠèèh¢¢¢2îCþ}Bíß*V¬ˆ¯¯/¾¾¾4kÖŒ¢E‹fk§ˆHN¤MDò­{÷î¡C‡ áÈ‘#ÏMùß ,ÈóÏ?O¥J•¨T©NNN888ààà€££#+VŒ"EŠ`mm……öööÀÿœ˜ú÷ϼwïIIIܹsçO¿®]»ÆÅ‹¹pá.\àîÝ»ÿµ·råÊ4hÐ ãÓîªU«ê pÏàöíÛ 0€ 6Э[7¾þúëŒïDDr‹;vЫW/nÞ¼IéÒ¥Y¶l¾¾¾¦³äo\½z5ã$88˜ÈÈÈŒ¯“þo666¼øâ‹T¬X‘Š+R¾|yJ•*•ñËÁÁÂ… cggG°²²¢X±b)†ÿùäùßÏu+^¼8… Ê8ýöï_eË–¥bÅŠ'äJ”(aòŸn¾tîÜ9zôèÁ‘#G°´´dôèÑøûûcmmm:MDä¿:þ<]»våèÑ£XYY1yòdÆŒ£·ä1‰‰‰üöÛo\¼x‘‹/råÊnß¾ñëÎ;<|ø¸¸8RSSIIIáþýûÿq²ÞÆÆæO÷ŽŽŽ”+W.ãþ£bÅŠzˬˆÈ3ÐÀ&""¤¤¤0eÊ>ýôSRRRððð`ÅŠT«VÍtšˆÈŸ¬ZµŠAƒGÅŠYµjuêÔ1%""’¯é#.ÀÊÊ öïßÏ‹/¾È‰'ðôôä›o¾AŸE‰HNðàÁúõëG·n݈‹‹£S§N„‡‡k\NŸ>MçΙ0a‚é‘|K›ˆÈ3:}ú4:ubøðá¦S$Ô«WˆˆúôéCBBï¾û.~~~ܸqÃtšˆäc‘‘‘xzz²xñblmmùî»ïX»v­^Ì"Àÿ¼¸gݺuìÝ»×tŠˆH¾¥MDäݾ}›ÀÀ@Ž;f:E2I±bÅX¼x1ëÖ­£dÉ’lݺ~üñGÓi"’Ϥ§§3wî\êÔ©ÃéÓ§©Y³&aaa 0Àtšˆˆˆü/ØDDDþBÇŽ‰ŠŠ¢yóæÜ¼y“6mÚ0hÐ L§‰H>pçÎÚµkÇ!CHLLdРA„……Q£F Ói"""òh`ùåË—'((ˆ3f`mmÍwß}‡‡‡Ç7&"yXpp0îîîlÚ´ {{{ùæ›o2ÞL-"""9‹6‘`aaÁˆ# ÃÙÙ™3gÎP·n]¦NJjjªé<ÉCRSSñ÷÷§I“&üñÇÔ«WÈÈH:tè`:MDDDþ†6‘ÇäââBXXÆ #%%…±cÇÒ¤I~ÿýwÓi"’\¾|™fÍš1iÒ$ÒÓÓ;v,û÷ïÇÉÉÉtšˆˆˆü l"""OÀÆÆ†Y³f±cÇÊ•+Gpp0®®®¬\¹Òtšˆäb›7oÆÍÍýû÷S¶lYvîÜÉ'Ÿ|‚•••é4y ØDDDžB‹-ˆŠŠ¢}ûöÜ»w=zеkWbccM§‰H.’””ÄСCyã7¸sç­Zµ"22’¦M›šN‘' MDDä)988ðÃ?°hÑ"Š+ÆêÕ«quueß¾}¦ÓD$8sæ uêÔaΜ9,X3fðã?âèèh:MDDDž6‘gÔ·o_ÂÃéS§—.]¢Y³fŒ3†äädÓi"’C-]ºOOO"""x饗 aĈXXX˜N‘§ MDD$T®\™àà`&Nœˆ¥¥%Ó¦M£nݺœ:uÊtšˆä qqqtïÞ>}úO=8vìžžž¦ÓDDDäh`yFE‹ >>Þp‰˜feeŤI“¦råÊœ8q‚Zµjñõ×_“žžn:OD ;vìµjÕâûï¿§hÑ¢,]º”åË—cggg:MDDDž‘6‘gT @RSS —HNQ§NÂÃÃéÓ§>ä½÷ÞÃÏÏ7n˜NÒÓÓ™1cõë×çܹs¸»»süøqz÷îm:MDDD2‰6‘,P¬X1/^Ì?ü€ƒƒ[·nÅÙٙ͛7›N‘ltëÖ-^ýuFÅ£G:t(¡¡¡¼üò˦ÓDDD$i`ÉBíÛ·'**ŠæÍ›sëÖ-Ú¶mË AƒHHH0&"Yl÷îݸ¸¸°mÛ6Ø´i_~ù%… 2&"""™L›ˆˆH+W®AAAÌš5 ¾ûî;ÜÝÝ9zô¨é4É)))Œ;®_¿NãÆ‰ŒŒ¤uëÖ¦ÓDDD$‹h`É 6Œ°°0œùõ×_©_¿>Ÿ~ú©žß'’‡üþûï4lØ©S§baa¿¿?»ví¢|ùò¦ÓDDD$ i`ÉFÎÎ΄……1bÄRRRø×¿þEãÆ¹xñ¢é4yF¸¹¹Š““{÷îe„ /ѼK›ˆˆH6³±±aÆŒQ®\9<ˆ««++V¬0&"O!!!AƒÑ©S'bccyã78qâÞÞÞ¦ÓDDD$›h`1¤yóæDEEѱcGâââèÙ³']ºtáîÝ»¦ÓDä1}úNݺuùã?hÞ¼9cÆŒ!))Étšˆü±±±têÔ‰Aƒ‘@ß¾}9~ü8...¦ÓDDDÄ l"""9DåÊ•9pàþþþXZZ2mÚ4êÔ©Ã/¿üb:MDþŸÐÐPÜÜÜ ÄÎÎŽU«V±hÑ"lmmM§‰ˆˆˆAØDDDr+++&L˜@pp0•+W&""OOO¾þúkÒÓÓMç‰ä[iii|úé§4lØßÿ///ÂÃÃéÒ¥‹é4É4°‰ˆ<£B… è«|’©êÔ©CDDýúõãáǼ÷Þ{´jÕŠëׯ›NÉw®_¿Žÿú׿HMMåƒ>ààÁƒ¼øâ‹¦ÓDDD$‡ÐÀ&"òŒlllHLL4\"yMÑ¢EY¸p!?üðlß¾6oÞl:M$ߨ¶m...ìÞ½›çž{ŽíÛ·3mÚ4 ,h:MDDDr l"""9\ûöí‰ŽŽÆÇLJ[·nѶm[ ÀƒL§‰äYÉÉÉŒ5Š×_[·nÑ¢E """ðññ1&"""96‘\ lÙ²lß¾Y³facc ðððàèÑ£¦ÓDòœsçÎQ¿~}f̘••Ÿ}öÛ·o§L™2¦ÓDDD$‡ÒÀ&""’KXXX0lØ0Ž=Š‹‹ ¿þú+õë×çã?&55ÕtžHžðý÷ßS«V-Ž;F¥J•f̘1XZê¶YDDDþšîDDDr™š5kƈ#HIIaüøñ4jÔˆ‹/šNɵâããéÓ§Ý»w'..ŽÎ;NíÚµM§‰ˆˆH. MDD$*T¨3fÌ ((ˆ *‚««+¦ÓDrˆˆ<==Yºt)¶¶¶,X°€5kÖP¼xqÓi"""’Kh`ÉÅš7oNdd$;v$..ŽÞ½{óæ›or÷î]Ói"9^zz:_}õuêÔáÌ™38;;sôèQú÷ïo:MDDDr l"""¹\É’%Y·n‹/ÆÎÎŽµk×âêêÊž={L§‰äXwîÜá7Þàý÷ß'))‰wÞy‡°°0ªW¯n:MDDDr! l"""yDŸ>}§^½züñÇ´hÑ‚>ø€¤¤$Ói"9ÊþýûqsscóæÍ”(Q‚õë×3oÞQ¶lY¶oßÎW_}… ,ÀÝݰ°0Ói"™jñâÅxzzÉK/½DHHÆ Ë8q,"""’Ù4°‰ˆˆä# 2„cÇŽáââÂÙ³g©_¿>ü1©©©¦óDžI\\ݺu£_¿~U¨P!¾øâ ‚‚‚¨P¡!!!¸»»³lÙ2Ói"-==éÓ§S¿~}Ο?‡‡'Nœ gÏž¦ÓDDD$ÑÀ&""’Ï5kÖŒÈÈH:wîL\\o½õ;w&&&ÆtšÈߺyó&-[¶äƒ> %%…aÆqèÐ!^zé%Ói"""’Ïh`J–,Éš5kXºt)vvv¬[·WWWvïÞm:Mä¿Úµk®®®ìرGGG6oÞ̬Y³(T¨é4ɇ4°‰ˆˆH†Þ½{Nýúõ¹|ù2>>>Œ5Ф¤$Ói"¤¤¤ðÑGñÚk¯qýúuš4iBxx8~~~¦ÓDDD$ÓÀ&"""òâ‹/²ÿ~¦L™‚¥¥%3fÌÀËË‹ŸþÙtšäs.\ÀÛÛ›Ï>û KKK¦L™ÂÎ;)_¾¼é4Éç4°‰ˆˆÈ(P ãÆ#$$„—^z‰¨¨(<==ùꫯHOO7'ùÐÚµkñððàðáÃ899±wï^ÆGL§‰ˆˆˆh`‘¿æååExx8o¿ý6‰‰‰¼ÿþûøúúríÚ5Ói’O$$$0`ÀÞ|óMbcci×®4hÐÀtšˆˆˆH l"""ò·Š)ÂüùóÙ°a¥J•"((6nÜh:Mò¸“'OâååÅ‚ °±±aÞ¼y¬_¿ž%J˜Nù l"""òXÞxã ¢££ñõõåöíÛ´k׎þýûo:Mò o¿ý–W_}•Ÿþ™jÕªÆ;ï¼c:KDDDä¿ÒÀ&"òŒŠ)Àƒ —ˆd½2eʰuëV¾úê+lllX´hîîî„……™N“<"66–:ðÎ;Hÿþý9vìÎÎΦÓDDDDþ’6‘gdee@JJŠá‘ìaaaÁ!C8~ü8nnnœ;wŽúõë3yòdýç@žIHH®®®¬_¿žâÅ‹³zõj,X€­­­é4‘¿¥MDDDžJõêÕ9|ø0£F"--‰'Ò¨Q#~ûí7Ói’ˤ¥¥ñÉ'ŸÐ¸qc.]ºDíÚµ çÍ7ß4&"""òX4°‰ˆˆÈS+T¨_|ñ;wîäùçŸçСC¸»»³dÉÓi’K\½z•-Z0nÜ8ÒÒÒ=z4ÁÁÁTªTÉtšˆˆˆÈcÓÀ&"""ϬiÓ¦DFFÒ¹sgâââèÛ·/:u"&&Ætšä`[·nÅÍÍ={öP¦L¶mÛÆçŸNÁ‚M§‰ˆˆˆ< l"""’)J”(Áš5kX¶lvvvâêêÊ®]»L§I“””Ĉ#ðóóãÖ­[øøøé4‘§¢MDDD2U¯^½ˆŒŒ¤~ýú\¾|FŽIRR’é4ÉΞ=Kƒ ˜5kVVVL›6íÛ·óÜsÏ™NyjØDDD$ÓU¬X‘ýû÷3eʬ¬¬˜9s&^^^œÀÂÂÂtšˆˆˆÈ3ÑÀ&"""Y¢@Œ7Ž^~ùe¢¢¢xõÕW™={6ééé¦ó$ÅÇÇÓ»wozöìÉýû÷éÒ¥ xyy™NÉØDDD$K½úꫜ8q‚·ß~›ÄÄD†ޝ¯/×®]3&ÙàĉÔªU‹€€lmmY¸p!«V­ÂÎÎÎtšˆˆˆH¦ÑÀ&"""Y®H‘"ÌŸ?ŸM›6áèèHPPÎÎά_¿Þtšd‘ôôt¾üòKêիǯ¿þŠ‹‹ ǧ_¿~¦ÓDDDD26É6mÚ´!** ___îܹC‡èß¿?ñññ¦Ó$ݾ}›¶mÛ2lØ0’’’4\"’»T¯^Ç3zôhÒÒÒ˜8q" 6äüùó¦Óä \ºt‰¦M›2eÊ&L˜Àž={prr2\&"""’=4°‰ˆˆˆQ… âóÏ?g×®]<ÿü󄆆âîîÎ’%KL§Écظq#îîîS¾|yvíÚ…¿¿?VVV¦ÓDDDD²6Éš4iBdd$]ºtáþýûôíÛ—N:qçÎÓiò_$&&2xð`ÚµkGLL ­[·&""‚Æ›NÉvØDDD$Ç(Q¢«V­bùòåØÙÙˆ‹‹ »ví2&ÿË©S§¨]»6óæÍ£P¡BÌž=›M›6QªT)Ói""""Fh`‘§GDFFâííÍÕ«WñññaäÈ‘$&&šNË÷-Z„§§'QQQ¼üòË„††òþûïcaaa:MDDDÄ l"""’#U¬X‘½{÷òÉ'Ÿ`eeÅÌ™3ñòò"::ÚtZ¾G×®]éß¿? ôîÝ›ãÇãîîn:MDDDÄ8 l"""’c(P€±cÇÂ+¯¼Btt4^^^Ìž=›ôôtÓyùFXXnnn¬^½šbÅŠ±|ùr–.]JÑ¢EM§‰ˆˆˆäØDDD$Ç{õÕW9qâ$11‘áÇãããÃÕ«WM§åiéééL›6 páÂ<==9~ü8=zô0&"""’£h`‘\ÁÖÖ–o¿ý–M›6QºtivíÚ…‹‹ ?üðƒé´<鯸úú2fÌRRR1b䥗^2&"""’ãh`‘\¥M›6DEEѪU+îܹCÇŽéÛ·/÷ïß7–gáææFPPŽŽŽüøã̘1ƒB… ™NÉ‘4°‰ˆˆH®óÜsÏñã?2wî\ .Ì’%KpwwçðáæÓrµG1fÌZ¶lÉõë×iÚ´)´jÕÊtšˆˆˆHަMDDDr% ÌñãÇñððàüùóx{{3qâDRRRLçå:.\ÀÛÛ›iÓ¦aiiÉÇÌÎ;)W®œé4‘O›ˆH&Ò[ E²_µjÕ eôèѤ¥¥1yòd¼½½9þ¼é´\cÍš5¸¹¹qäÈœœœØ¿?ÿú׿°´Ô­¢ˆˆˆÈãÐ]“ˆH&(^¼8qqq†KDò'kkk>ÿüsvïÞ““‡ÆÝÝE‹™NËÑxûí·éÒ¥ qqqtèÐÈÈHêÕ«g:MDDD$WÑÀ&"""yFãÆ‰ŒŒ¤K—.Ü¿ŸþýûÓ¡Cîܹc:-Ç‰ŽŽÆÓÓ“… bccÃ7ß|C`` ööö¦ÓDDDDr l"""’§ØÛÛ³jÕ*V¬XAñâÅY¿~=...™NË1æÍ›‡——§N¢F=z”Aƒ™Îɵ4°‰ˆˆHžÔ½{w"##ñööæêÕ«øúú2|øpM§s÷î]Ú·oÏàÁƒILLäí·ß&,,Œš5kšNÉÕ4°‰ˆˆHžõ /°wï^>ýôS¬¬¬˜={6^^^DEE™NËvÄÍÍ 6`ooÏÚµk™?>¶¶¶¦ÓDDDDr= l"""’§(P€>úˆÐÐP^y墣£ñòòbæÌ™ùâÍ¿©©©|üñÇ4iÒ„K—.Q§NNœ8A§NL§‰ˆˆˆäØDDD$_¨U«'Nœ`àÀ$''3räH|||¸råŠé´,såÊZ´hÁøñãIKKãÃ?$88˜J•*™NÉS4°‰ˆˆH¾akkË·ß~ËæÍ›)]º4»víÂÅÅ…ÀÀ@Ói™îÇÄÍͽ{÷R¦LvìØÁÔ©S±²²2&"""’çh`‘|ÇÏϨ¨(ZµjELL :u¢o߾ܿßtÚ3KJJbøðá´iÓ†Û·oãëëKdd$Í›77&"""’gi`‘|é¹çžãÇdÞ¼yØÚÚ²dÉÜÜÜ8tèé´§vöìYêÕ«ÇìÙ³±²²â‹/¾`ëÖ­”.]ÚtšˆˆˆHž¦MDDDò- ÞyçŽ;†‡‡¿ýö5bâĉ¤¤¤˜Î{"xxxpâÄ *W®LHH£FÂÂÂÂtšˆˆˆHž§MDDDò½jÕªʇ~HZZ“'O¦Aƒœ;wÎtÚ?ºÿ>½zõ¢wïÞÄÇÇÓµkWNœ8Á«¯¾j:MDDD$ßÐÀ&"""X[[3uêTöìÙƒ““GŽÁÝÝE‹™NûKǧV­Z,_¾œ"Eаxñb¾ÿþ{ìììL§‰ˆˆˆä+ØDD2­­- †KDäY5jÔˆÈÈHºuëF||<ýû÷§}ûöܾ}ÛtZ†ôôtfÏžMýúõ9{ö,®®®;vŒ>}ú˜NÉ—4°‰ˆd‚‚ ðèÑ#Ã%"’ìííY¹r%+W®¤xñâlذ‚‚‚L§qëÖ-Z·nÍðáÃINNæ½÷ÞãðáÃT­ZÕtšˆˆˆH¾¥MDDDä/tëÖÈÈH6lȵk×ðõõeذa$&&éÙ»w/nnnüôÓO888°aÃæÌ™ƒ‘ùØDDDDþÆ /¼ÀÞ½{™:u*VVV|ùå—xzz•m )))Œ?žæÍ›sõêU6lHxx8m۶ͶùkØDDDDþ¥¥%~ø!¡¡¡T­Z•Ÿþ///fΜIZZZ–^ûÒ¥K4nܘ?þ &NœÈž={xþùç³ôº""""òø4°‰ˆˆˆ<¦ZµjqüøqÞyç’““9r$>>>\¾|9K®·~ýzÜÜÜ ¡B… ìÞ½›I“&Q @,¹žˆˆˆˆ< l""""OÀÖÖ–yóæ±yófJ—.ÍîÝ»quu%000Ó®‘˜˜È»ï¾K‡¸{÷.mÚ´!""‚FeÚ5DDDD$óh`y ~~~DGGãççGLL :uâ­·Þ"..î™~îÿÇÞ}GEq>^¿KQÀKÔ¨,,-`GP±DňÔ¨1ˆ-öKÔ»FcÃ^ƒ½!A *‚K[{ì틊Š"ʲÏûG~î"**0”û9gONvggîÂÊÎÞyæ™óçÏÃÉÉ Ë–-ƒ¡¡!/^Œ={öÀÂÂ"Ÿ’Q~cÁFDDDô*V¬ˆ}ûöá÷߇±±1Ö¯_…BÈÈÈZ_`` œœœpöìYÔ¯_QQQøî»ï “Éò99å'lDDDDA&“aðàÁˆ‹‹ƒƒƒ®]»†–-[bòäÉP«ÕyZÇÓ§OѳgOøùù!==ýúõƒR©„]§'"""¢üÀ‚ˆˆˆ(Ô¯_§NÂøñã¡Ñh0cÆ 4mÚW®\yëó¢¢¢ P(333lÞ¼k×®…‰‰I!%'"""¢Å‚ˆˆˆ(ŸèëëcöìÙ8vìjÖ¬‰èèh( ¬Zµêµe5 æÎ‹-ZàúõëpttDll,|}}%HNDDDDƒ{„ û IDATQ>kÞ¼9áëë‹´´4 8^^^xøð!àÞ½{ðôôÄøñã¡V«1fÌDFF¢nݺ''"""¢Á‚ˆ(èëë²²²$NBDEE¹rå°yóflÙ²æææØ³gär9fÍš;;;>|VVV8xð ´Gˆˆˆˆ¨øaÁFD”Œééé'!¢¢ÆÇlj‰‰hÑ¢’’’0iÒ$Ü¿nnnP©Thß¾½Ô‰ˆˆˆè#±`#"""*`jµ9î{ðàîÝ»'Q"""""ÊO,؈ˆˆˆ жmÛ P(kkk¬]»Ÿ}öÎ;@£ÑH“ˆˆˆˆ> 6"""¢žžŽÀÇÇ©©©èÞ½;âããѯ_?(•J 2/_¾Ä÷ß¶mÛâöíÛRG&"""¢Ä‚ˆˆˆ(Ÿ%&&ÂÁÁkÖ¬±±1–/_ŽíÛ·ÃÜÜÀ?ó6.]º@¥J•pôèQØÚÚ"((HâäDDDDô!X°壥K—ÂÅÅ/^DãÆqúôiøûûçºl‡ R©Ð©S'¤¤¤ gÏžèׯRSS 95} lDDDDùàÑ£Gðòò°aÙ™ DGG£qãÆo}^ÅŠ±ÿ~,_¾ÆÆÆX¿~=lmmQHɉˆˆˆèc±`#"""úHááá°··Çž={`nnŽíÛ·cùòå(S¦Lž×áï︸8888àÆhÙ²%&Ož µZ]€É‰ˆˆˆ(?°`#"""ú@ÙÙÙøùçŸáææ†[·nÁÕÕ èÞ½û­¯~ýú8uê&Nœ˜1cš4i‚¿ÿþ;?cQ>cÁFDDDôîÞ½ wwwL:BLœ8'Nœ@Íš5?j½úúú˜9s&ÂÂÂP³fMÄÄÄÀÞÞ«V­Ê§äDDDD”ßX°½§ýû÷ÃÖÖÇG•*UŠ™3gBOO/߶Ѽys$&&¢wïÞxþü9ˆ®]»"999ß¶ADDDDùƒQ½xñ#FŒ@—.]ðèÑ#´o߉‰‰pww/í•+W7nÄÖ­[ann޽{÷B.—#$$¤@¶GDDDD†Q\ºt ®®®X¼x1ôõõ€ƒÂÊʪÀ·ýÕW_!11nnn¸wï:tè€ï¾û¾m""""z7lDDù \¹r€§OŸJœ„ˆ ÂúõëáèèˆøøxÔ­[3f d2Y¡e¨Q£Ž9‚¹sçB__K–,££# -åŽÑ¤¦¦¢OŸ>èׯÒÒÒЫW/ÄÆÆÂÑÑQ’<:::7nN:…† âüùópqqÁüùó¡Ñh$ÉDDDDD,؈ˆˆˆr¥T*áèèˆM›6ÁÄÄk׮ŦM›`ff&u4ØÛÛC©TbèСxùò%Æܾ}[êhDDDD¥ 6"""¢B`Á‚hÚ´)._¾ ;;;(•Jôë×Oêh9”)SK–,ÁP©R%„……ÁÖÖAAARG#"""*uX°ýŸäädtìØcÆŒAVV†ލ¨(Ô¯__êhoÔ¡Cœ9s;wFJJ zöì‰>}ú 55UêhDDDD¥ 6""""ýõlmmqèÐ!XXX`Ïž=X´h ¥ŽöNVVVØ»w/–/_ccclÚ´ ¶¶¶ˆˆˆ:Q©À‚ˆˆˆJ5µZI“&¡M›6HJJBË–-‘˜˜ˆÎ;Kí½ùûû#>>NNN¸qãZ¶l‰I“&!++KêhDDDD% 6"""*µnÞ¼‰–-[bÖ¬YÉdøé§ŸpôèQT«VMêh¬^½zˆˆˆÀĉ³fÍBÓ¦Mñ÷ßKœŒˆˆˆ¨äbÁFDDD¥ÒÎ;agg‡ÈÈHT¯^aaa˜:u*tuu¥ŽöÑôõõ1sæL;v ÖÖÖˆ‰‰½½=V¬X!u4"""¢‰•*4hºwïŽ'Ož k×®ˆGóæÍ¥Ž–ïš5k†ÄÄDôîÝÏŸ?Ç AƒÐ¥K$''Kˆˆˆ¨DaÁFDDD¥Æ¹sçàììŒ+VÀÈÈ¿ýövíÚ ©£333lܸÛ¶mCùòå±oß>ÈårKˆˆˆ¨Ä`ÁFDDD¥ÂÊ•+áì쌳gÏ¢AƒˆŠŠÂ°aà “ɤŽV(zöì‰ÄÄD¸¹¹áÞ½{èÔ©† †ŒŒ ©£{,؈ˆˆ¨D{òä ¼½½áïïôôt|óÍ7P*•°µµ•:Z¡«^½:Ž9‚yóæÁÀÀK—.…££#âã㥎FDDDT¬±`#"ÊeÊ”Ž!*b¢¢¢ P(°cǘ™™aË–-X½z5Ê–-+u4Éèèèàûï¿GTT6lˆóçÏÃÅÅóæÍƒF£‘:Q±Ä‚ˆ(^¾|)q"FƒÙ³g£yóæ¸q㜜œ©£vvvP*•6l²²²ðÃ?ÀÃ÷oß–:Q±Ã‚ˆˆˆJ”{÷î¡]»v˜8q"²³³1vìXDDD víÚRG+rÊ”)ƒß~û ÁÁÁ¨\¹2 —˱mÛ6©£+,؈ˆˆ¨Ä \.Ç‘#GP±bE:tóçχ¾¾¾ÔÑŠ4OOO¨T*téÒOž<úôéƒÔÔT©£ ,؈ˆˆ¨ØËÊÊÂØ±cÑ¡C$''£M›6HLLD»ví¤ŽVlXYYaÏž=X¹r%Ê–-‹M›6A.—#<<\êhDDDDE 6"""*Ö®^½Š&Mšà—_~®®.fÏžT®\YêhÅ’ŸŸâââàää„›7oÂÍÍ “&MBVV–ÔшˆˆˆŠ,lDDDTlmÙ²öööP*•¨U«ÂÃÃ1~üxèèpçcÔ«W‘‘‘øñdzfÍ‚««+.]º$q2"""¢¢‰{ŸDDDT줥¥á›o¾A¯^½ššŠ=z .....RG+1ôôô0}út?~ÖÖÖˆ…½½=V¬X!u4"""¢"‡+ prrÂÚµkallŒ•+Wâ?þ€¹¹¹ÔÑJ¤¦M›"11}ûöEzz: „/¾ø<:Q‘Á‚ˆˆˆŠ!~ûí7¸ººââÅ‹hܸ1bbbàçç'u´ÏÌÌ ëÖ­ÃüòåËãÀËå–:Q‘À‚ˆˆˆŠ¼GÁËË Ã‡Gff& „˜˜4lØPêh¥J=˜˜wwwÜ¿:uÂСC‘žž.u4""""I±`#""¢"íĉ°³³ÃÞ½{annŽ;wbÙ²e022’:Z©T½zu„††bþüù000Àï¿ÿGGGÄÅÅIˆˆˆH2,؈ˆˆ¨HÊÎÎÆO?ý„Ö­[ãÎ;Ú¹Àºuë&u´ROGGcÇŽETT5j„ .ÀÕÕsçÎ…F£‘:Q¡cÁFD”ôôôjµZâ$D%Ã;wкukL›6 BLš4 ÇŽC5¤ŽFÿbgg¥R‰ï¾ûYYY?~}:Nœ8Úµk#66öööX¶l„RÇ#"""Ê7,؈ˆˆ¨Ð!€&MšàêÕ«°··Gll,¾þúk©£QjÒ¤ âããÑ·o_¤§§cÈ!èܹ3ù䄆†" X¶l+u4"""¢‚ˆˆˆ Ä7мysÌž=:::øùçŸqäÈT«VMêhTèèè`̘1ˆŽŽF£FpḺºbΜ9Ðh4RÇ#"""z/,؈ˆˆ(ßíØ± …QQQ¨Q£ÂÂÂ0yòdèêêJй\¥R‰áÇC­Vc„ hݺ5nÞ¼)u4"""¢}*u4"""¢·bÁFD”d2@!q"i,_¾ÎÎÎ8wî>ûì3œ>}C† ‘:C–––ؽ{7abb‚-[¶@.—ãøñãRG#"""z#lDDDôÁžûLêhDÄÂÂ;wîD`` LMM±mÛ6ØÚÚâØ±cRG#""¢ŠQ>044¼|ùRâ$DïçØ±c°µµÅþýûQ¡BìÚµ K—.Õ–ÆDÅÙ€ܺu îîîøá‡ø·šˆˆˆò 6""¢R(;;S§N…‡‡îÞ½‹fÍš!>>^^^RG#ÊWuêÔAxx8¦L™Ì›7®®®¸pá‚Ôшˆˆ¨aÁFDDTÊܺu nnnøùçŸ?þø#ÂÂÂP£F ‰“ ===L›6 ááá¨S§âââààà€¥K—B!u<"""*X°•"{öì½½=ÂÃÃQ­Z5>|Ó§O‡žžžÔш œ‹‹ âããÑ¿ddd`ذaèÔ©îß¿/u4"""*æX°•™™™6l¼¼¼ðèÑ#têÔ pss“:Q¡255Åš5k°cÇXXX 88r¹ûöí“:c,؈ˆˆJ¸‹/ÂÅÅK—.…¡¡!~ýõWìÛ·–––RG#’Ì—_~ •JRG#*ŒŒŒ°`Á„††¢jÕª‡­­-6mÚ$u4"""*âX°•÷ïßGûöí1nÜ8¨ÕjŒ5 øôÓO¥ŽFTìxxx@¥RáË/¿Djj*úô餤¤HˆˆˆŠ(lDDDÅÜáÇagg‡?ÿüVVVØ¿?,XCCC©£[رcÖ¬YSSSlÛ¶ ¶¶¶ “:A,؈ˆò®®.pBl*TYYY?~<<==qïÞ=¸¹¹!!!;v”:Q‰Ñ¿ÄÇÇÃÅÅ·o߆‡‡~øá¼|ùRêhDDDT„°`#"ʯ&OKK“8 •ׯ_G‹-0wî\èèè`ÆŒ8räªV­*u4¢§N:Ç´iÓ ££ƒyóæáóÏ?Çùó祎FDDDE 6""¢b&(( …QQQ¨Q£Ž;†I“&AG‡ëDEOOS¦LAxx8êÔ©ƒ„„8::béÒ¥BHˆˆˆ$Æ=q""¢b"==~~~èÙ³'ž>}Šnݺ!!!M›6•:Q©áââ‚øøx 06l:vìˆ{÷îIˆˆˆ$Ä‚ˆˆ¨8sæ œœœ###üþûïØ¹s'Ê—//u4¢RÇÔÔعs',,,pèÐ!ÈårìÛ·OêhDDD$lDDDEܲeËàììŒóçÏ£aƈ‰‰ÁàÁƒ¥ŽETêuëÖ *• mÛ¶Err2ºté<þ\êhDDDTÈX°Q)))øòË/1dÈdffÂÏÏ111hܸ±ÔшèÿT­Z!!!X¸p!ŒŒŒ°råJØÛÛ#&&FêhDDDTˆX°A°³³Ã®]»P®\9üñÇX¹r%Œ¥ŽFDÿ!“É0räHDGGC.—ãï¿ÿFÓ¦M1sæLdggKˆˆˆ  6""¢"D£Ñ`ÆŒhÕªnݺ¥P½GRG#¢w°±±Att4F µZü-[¶Ä7¤ŽFDDDLOêDDDôÿýïèÝ»7 ££ƒñãÇã矆¾¾¾Ôш( ñË/¿ }ûöèß¿?"""`kk‹%K– OŸ>RÇ#"¢°xñbœ}:päÈlÞ¼ 6”:}$í)¢AAARæ "Š#‰òÆÔÔðìÙ3‰“PQ—––†¡C‡bÆ ,_¾fff'#¢‚¦§§‡)S¦ÀÓÓ½zõBBB1wî\ 6Œ£W‰ˆˆŠ1mÁÆaŽ”œ9sFêDEžŽŽ@£ÑHœ„в¸¸8|õÕW¸|ù2ŒñÛo¿á›o¾‘:2gggÄÇÇcäÈ‘X½z5†Žàà`¬]»•+W–:}©•tBüúë¯hÒ¤ ._¾ [[[ÄÆÆ²\#*ÅLLLˆÝ»wÃÒÒ!!!°±±Áž={¤ŽFDDD€QJNNFçÎ1jÔ(¼|ùC‡ETT4h u4"*ºví •J…¶mÛâáÇðòòÂÀñüùs©£Ñ{`ÁFDDT@ P(pàÀXXX`×®]X²d ŒŒŒ¤ŽFDEH•*U‚E‹ÁÈÈ«V­‚B¡@tt´Ôшˆˆ(X°å3µZÉ“'£M›6¸{÷.š7oޏ¸8tíÚUêhDTDÉd2 >J¥r¹—/_FÓ¦M1cÆ dggKˆˆˆÞQ>ºuëÜÜÜ0cÆ À”)S†5jHœŒˆŠƒF!::cÆŒF£ÁäɓѲeK\»vMêhDDDô,؈ˆˆòÉîÝ»agg‡“'O¢Zµj8zô(¦M›]]]©£Q1bhhˆ€€„††â“O>ADD Ö¯_/u4"""zlDDD)33C‡E·nÝ’’‚Î;#11-[¶”:cîîîHLL„··7RSSѯ_?ôìÙ?–:ý 6""¢páÂ8;;ã÷߇¡¡!-Z„={öÀÂÂBêhDTT¨PAAAX·nÌÌÌ[[[=zTêhDDDô/,؈ˆˆ>P`` qæÌÔ¯_§NÂðáÃ!“ɤŽFD%Lß¾}&MšàÎ;hÛ¶-¾ÿþ{¼xñBêhDDDlDDDïíéÓ§øê«¯àçç‡ôôtôíÛJ¥ …BêhDT‚Õ®]'NœÀôéÓ¡££ƒ€€8;;ãܹsRG#""*õX°½‡Ó§OC¡Pà?þ€™™6mÚ„uëÖÁÄÄDêhDT èêêâÇDDDêÖ­ •JGGGüöÛoBHˆˆ¨ÔbÁFD”ŒŒŒü3Ù=•LóæÍCóæÍqýúu8::B©T¢W¯^RG#¢RÈÙÙ ðóóCff&†OOO$%%Iˆˆ¨TbÁFD” €sá”P÷îÝCûöíñÃ?@­VcôèшˆˆÀ§Ÿ~*u4"*ÅÊ–-‹•+Wb÷îݰ´´Dhh(är9öìÙ#u4""¢R‡Ñ[„††ÂÎΡ¡¡°²²ÂÁƒñË/¿ÀÀÀ@êhDD€®]»B¥RÁÓÓ>„——üüü––&u4""¢RƒQ.²²²ðÃ?ÀÓÓ÷ï߇»»;Ѿ}{©£½¦J•*ÆâÅ‹add„ÀÀ@( DGGKˆˆ¨T`ÁFDDôׯ_G³fÍ0oÞ<èêêbæÌ™ E•*U¤ŽFDôF2™ ß}÷”J%ìììpåÊ4mÚÓ§O‡Z­–:Q‰Æ‚ˆˆè_¶mÛ;;;DGG£fÍš8~ü8&Nœ~dQñШQ#DEEaìØ±Ðh4˜2e Z¶l‰k×®Iˆˆ¨Äâ·"""éééøöÛoáããƒÔÔT|ùå—HHH@“&M¤ŽFDôÞ 1þ|„††¢zõꈌŒ„B¡Àºu뤎FDDT"±`#"¢RO¥RÁÁÁ«W¯F™2e°|ùrìØ±æææRG#"ú(¯æìÑ£RSSÑ¿x{{ãñãÇRG#"¢B¦T*!“Éàìì !Äk÷«ÕjtìØ xí¹BÔ­[ .Ô.ob€-ñù IDATb’ãÖ¯_¿7nû¿ËÀÌ̬ ^¦dX°Q©öûï¿ãóÏ?ÇÅ‹ѸqcDGGÃßß_êXDDù¦|ùòøã?°nÝ:˜™™aÇŽ°µµÅÑ£G¥ŽFDD¸~ý:6nܘëc~~~ zíJÔaaa¸sç¾þúkí}Ož>>%²;)q›CwìØ¬¬,x{{çï‹!¢bÃÔÔðìÙ3‰“п½xñ#GŽD—.]ððáCxzzB¥RÁÃÃCêhDDEJçΡR©àé鉇âË/¿Ä·ß~ûÚÔ*T45hÐAAAøù知ŽBT"eeeI¡@Ô«WC† ÉõL¼.]º@GG_ý5¾øâ T®\ù£·wëÖ-„††–ÈÓCX°…;ÔñÙ³gøá‡òt®1ž¿ÿþ®®®X´hôôô€àà`XYYIˆ¨Hª\¹2‚ƒƒ±dÉ”)S«W¯†B¡@TT”ÔÑè,--áííýÚ "z8uê/^Œ¯¿þŸ}övìØ!u¬3uêÔ\çß400@ß¾}qýúu|ûí·¹>×ÜÜ<ÇÅ!Û¶mûÖm­^½¶¶¶pttÌ—ìE €Pì'4U*•prrBVVôôôð÷ßÃÆÆ .ÄСCµ÷ÀöíÛ1xð`ìÙ³nnn¸}û¶¶ýïzò²]WWW”+WÀ?ö=}úXµj¼¼¼ îE¹\Ž3gÎ@¥RÁÆÆFê8D’KJJ­[·ððáCDrr2ž>} xþü9^¾|™ãyzzzÚQöfff°´´Ôî‡XXX Zµj¨^½:ôõõ ý5YYYP©Tˆ‰‰All,”J%Ξ= µZc9h4lÞ¼¹DÎFgΜ9˜0aJì'仆::4_†:ÚÚÚâúõëÚÿW©TèØ±#bccQ±bÅ^oQÃy¥¨4yòä Μ9ƒ3gÎàìÙ³¸~ý:nܸ7n 33³À¶[¥JX[[ÃÚÚõêÕƒ lmmQ»vmíoôvÏž=Ã!C°iÓ&€¯¯/–-[333‰“/ 6DTT¦L™‚€€L:þù'6lØ€:uêH¯ÄºwïΜ9ƒÄÄDœ?^»rçÎ×¾ðç]]]T«V ÖÖÖ¨U«>ûì3ÈårØØØà“O>)mI!;;/^DLL ”J%bbb˜˜ˆ/^äXNOOr¹NNNptt„““°mÛ6‰’SqQb 6àŸ¡Ž7n|íþWCÞxjç«Ñ&¯4iÒ¡¡¡¯-§¯¯ŸãƒçÞ½{€jÕª•¨#|Mš4´ÿmذ!vQ±÷âÅ ÄÆÆâäÉ“ˆŒŒDBBnÞ¼ùÆå«T©‚5jä8úûꦣ£###”)S&Çs„xòä ==];ú-99=ƒpíÚ5$%%!)) §NÊñü²eË¢qãÆpvvF“&MмysT«V-ÿÅœR©„¯¯/._¾ ,^¼ýû÷—:Q±ehhˆ¹sçÂÓÓ}ûöEdd$ -ZÄ¿¯ùàéÓ§8uê”ö–€äää\—ÕÓÓƒµµ5ªU«¦Ýï°´´„•••öLš²eËÂÀÀ ÇóÔjµv~ØÔÔT<|ø0Ç(ü»wïâöíÛ¸uënݺ…'Näx~…  —ËÑ´iS¸ººÂÕÕ*T(€ŸQþBàÊ•+9Ê´øøx<þ<Çr:::hРœœœàààGGG(Š×ÎHáÁnÊ‹sŠ(ŒW§ˆæÆÀÀr¹ …öööP(Ë寕 DEIvv6¢££qèÐ!=z±±±¯µ277‡ lllиqcÔªUK;²¬ OãLJJÒŽ”ûû￵G°¯]»öÚ(Ò5j E‹ðôôDÛ¶mKõ¼bBüú믘0a^¼x;;;lݺ 4:Q‰‘’’‚Áƒã?þtïÞË—/‡………ÄÉŠôôt;v !!!8qâΞ=‹ìììËT®\Y;‚½aÆÚ}O>ù¤ÀÞgggãîÝ»¸qã®_¿Ž .@¥RáÌ™3¯]4N&“¡AƒhÕªÚµkww÷y%@*~nÞ¼ ¥R™ãöê÷¿Y[[ÃÉÉI;:ÍÁÁ!Og:ôêÕ [¶lá)¢”«W§ˆ²`£·zU°…‡‡kGúÄÇÇ#..W®\yíK¿žž4h -ÜìííaggÇÓ³HR)))Ø¿?‚ƒƒqøða<~üXû˜¡¡!ЬY3íHÍš5kJ˜öuÏŸ?ÇÙ³gÈÈH„‡‡ãîÝ»ÚÇuttàààOOOtîܹTÍ5–œœŒ~ýú!882™ Æ Ãüùóahh(u4"¢iÓ¦M:t(RSSQµjU¬_¿žWf~‹Û·ocïÞ½8tèþúë¯SM”+WN;*ÌÕÕvvvEî€ÙãÇ¡R©¡i÷ïý(4kÖ žžžèÖ­O¦Bqÿþý#Ó”J%×åÊ–-›ã¦¯¯/LMMµ=Z4hÐ@‹òåË‹¯¾úJ$%%½ñµ¼k{B±nÝ:Ñ Aabb">ùä±{÷î·þ|Þgù¼l?¿Íž=ûÕ{–½Y~l¹ÉÎΗ.][¶lãÆîîî9vLþ}377nnnb̘1bÓ¦MâüùóüBMB!NŸ>-¼½½…¾¾¾ tttD›6mDPPÈÌÌ”:^‘páÂ1nÜ8Q±bEí¿©:uêˆÅ‹‹´´4©ã½—;w sss@T¯^]?~\êHDD$„xþü¹ð÷÷×~Îxxxˆ»wïJ«Àdgg‹½{÷ 777ík644¾¾¾âèÑ£Åú@V~Ñh4"22Rôïß?ÇõÏ?ÿ\lݺµP¾ðRÑñòåK'V¬X!¾ýö[¡P(´ûïÿ¾GGG1hÐ (‹Ì{¥  ¶Ý»w ñìÙ³÷=zTЇj‹4KKK±~ýzí2ï*Øþ÷¿ÿ‰•+WŠ7æ©prwwþþþÚÿŸ2eŠP©TB£ÑˆäädѵkWÑ¢E‹7>ÿ]ÛÛ°aƒhÔ¨‘8wîœBˆˆk×®½q}ï»üû¾ÞüÀ‚ò¤  ¶7¹~ýºØµk—øñÇEÇŽEåÊ•s-ÝÊ–-+š4i"†*V¯^-âââÄË—/ %#IëÕNmóæÍµï‡Ê•+‹I“&½õmi÷òåK±}ûvѶm[!“ÉQ¡BñÃ?ù/AbРAÚßw—.]ÄÇ¥ŽEDDÿ±wï^aee% ±sçN©#嫌Œ ±bÅ Q¿~}ígRýúõÅÂ… Å£G¤ŽWd=yòD,_¾\ØÚÚjn5kÖ .©©©RÇ£|¦V«ÅÙ³gÅúõëŰaÄ‹‹‹022zíûœ®®®°±±ýû÷¿ÿþ»ˆŽŽ.ÒgtÁ–••%ªT©"V®\™ã~ñÕW_ !þ‘¶téRQ­Z5ñüùó÷¿«HÊËrW¯^2™LÄÆÆ¾q™'N]]Ýw¾¦7m¯fÍšâÈ‘#ï|þ‡.ÿ®í„B+Ø8ÔñíC…øçÓ«S¹ÞöË—b¨cal¹IJJ3fÌ^^^9Nüï{{{áçç'~ÿýw¥ý£CÅ_vv¶Ø²e‹hРöwÞ°aCÈÑjïI¥R‰¾}û í¿qûöm©£½æìÙ³¢qãÆÚS/^\êN·!"*NîÝ»':tè ý¬îß¿±/QÒÓÓE@@@ŽÑànnnâÀ­öžŽ9":vì¨=ØW¾|y1mÚ´bÿ)Í._¾,¶nÝ*F-Z´h‘ëT@2™LÔ¯__ôêÕK,\¸P„‡‡»3) º`Bˆ‰' gggíÿ?~üX‰£G !þ?’™™)lllÄ”)SrÜŸÛ„ „ƒƒÃ[×3eÊ”9ßg{7oÞÄüùó…µµµ°°°¾¾¾âñãǹ®ã}—×ö J¡lêøî5óçÏ­Zµzçkb¨cQ(ØróèÑ#qäÈ1oÞ<áãã#êׯŸë9ûzzz¢qãÆ¢OŸ>báÂ…âØ±câéÓ§Rǧ÷ ÑhÄÎ;µ% ѬY3qàÀ-éÿûŸ?~¼vGÈÈÈH >ü­ ÓÊ•+…±±± 4h âã㥎DDDy ÑhÄ’%KD™2e´Sœ:uJêXï-33S,^¼XT©RE;…··7çËçÎ}ûözzzÚsæÌ)v¥KisëÖ-±sçN1aÂѶm[íÀÿÞjÖ¬)¾üòK1gÎqôèQñäÉ©£´Â(Ø^;sæŒBˆÅ‹‹:uêh¿óü»9räˆ066·oßη‚-++KT®\Y¬X±âëØ³g011111ï|=¹m/>>^ééé"99Y´jÕJx{{纎÷]þ]Û/(…V°q¨ãÛݸqCÔ¯__„……åù—_˜o”¢Z°åæÙ³g"<<\,^¼Xôë×OØÚÚæzn¿L&uëÖ=zô³gÏ¡¡¡âÁƒRǧ\„…… {{{íïÎÙÙY„„„H«ÄINNcÇŽÕ~266“&MzíÀHaIIIÞÞÞ9F?p‡›ˆ¨ø9wîœös\OOOL:µȨ̀ô6FlÚ´IT¯^]»ïØ¥K‘˜˜(u´çÒ¥KÂ××W{ ¼R¥JbéÒ¥Åâ}RÒÝ¿_œ§×’Ûö._¾,äxŸ„††Š2eÊ于÷]þ]Û/(…:‡:¾Y§NDPPÐ{ýòY°å]FF†ˆ‰‰+V¬þþþÂÙÙ9×9ˆ5jˆÎ;‹iÓ¦‰½{÷Š;wîH¿Ôº|ù²ðòòÒþnlmmž}û¤ŽUâ%%%‰áÇkO­R¥ŠX½zu¡žþrêÔ)íiàfff¾CDDëÅ‹bܸqÚÅÅÅE\¹rEêXotêÔ)áââ¢Ýi×®]žFjÐÇ9sæLŽ}?¹\.¤ŽUj¤¤¤ˆÃ‡‹Y³f‰nݺiËåÿÞ,,,D»víĤI“ÄîÝ»‹äô"¥° ¶   aaa!ÂÃÃ…žž^Ž3KþÛ\ºtIˆ¥K—æKoâéé)üüür}lÍš5ÂÜÜ\œ8q"ϯ%·í©Õjaff–c>åÇ ##£\×ñ¾Ë¿kû¥P 6uÌÝöíÛE»víòôÞµý‚RÜ ¶Üdee •J%Ö­['FŒ!š7o.LMMsý±²²žžžbüøñbûöíâÊ•+<-±=þ\Lš4IjO_¶lç7)d—/_íÛ·Ï1rðôéÓºÍììl1{ölí©"ŽŽŽEú ½Ÿ¿þúKÔ¨QC¦¦¦bõêÕRGÊ!))Iøøøhçkذ¡8tèÔ±J°°0¡P(´û ^^^âúõëRÇ*Qž={&Ž?.~ùåáëë+>ýôSíûþß7SSSѲeK1vìX±mÛ6qõêU©£Kª° ¶/^+++Q«V-áåå•ã±Üz€‘#G KKËwö"""Biii"###Çã7oÞ:::¹ö!‹-*Tx¯ïoÛÞÈ‘#ÅàÁƒEff¦xüø±pwwkoò¾Ëçåõæ·B¿Š(‡:攚š*j×®-._¾œ§×ð®í”’X°å&;;[\ºtIlݺUŒ7Nxxxˆ *äZº™››‹V­Z‰Ñ£G‹7ŠóçÏ µZ-õK(öŽ9"j×®-€&Ý3fŒHII‘:V©vèÐ!ѰaCü3ï̈# ä´Ñ¤¤$ááá¡= gìØ±¼*0Q ”’’"¾úê+í>U·nÝ$¿*´F£ÂÜÜ\»Ÿ·páBž¢(¡ììl±zõjQ¹re@˜˜˜ˆ… ò€ëÈÌ̧OŸK–,ýúõ5ººº¯}¿)S¦ŒpuuÇ6l.\àÏû? «`Bˆ±cÇ âàÁƒ9îÏ­HIIïìrû^ûoS¦L …âÏÕÓÓ{í"’¯¾ÇÅÅåøÿwm/33S 8PXXX333ѳgÏgþýw}ï»|^^o~+ô‚C__‡žžž°°°¢\¹rÚa·»vízïí”ÒR°½É7Ä®]»ÄäÉ“EÇŽEÕªUsýÇjll,š4i"† "V¯^-bccYäÑãÇEÿþýµGΚ5k&Ο?/u,ú?YYYbÖ¬YÚùÙ¬­­Eppp¾­?$$D{U¶Š+r´Q)°qãFí¾oÕªUEhh¨$9._¾,ÜÜÜ´ûs¾¾¾œ—·IIIÔî#:;;—Úï$y‘••%âããŪU«ÄÀ…B¡Èu>j}}}áàà üýýŪU«D||< å<(Ì‚ŠŸB/Ø8Ô1§—/_ŠÛ·okoÄ7Dzzú¿ÞüVÚ ¶Ü$%%‰àà`1cÆ ñå—_jç‹úïÍÀÀ@ØÛÛ‹o¿ýV,]ºTœ:uJ{úÇ¡C‡´G'MMMÅ’%Kx´¬ˆºté’öjÇÀ?HMMýàõ½|ùRŒ;V»ÓìááQd®^JDDïÆ¢yóæÚÑË#GŽ,ðýÚWþ{•Ó5j¼6R„ŠŽãÇ‹zõêiË¡™3g–ú³G²³³Å¹sçĆ ÄðáÃ…«««öýüï›®®®hÔ¨‘èׯŸX²d‰8}ú´ÈÌÌ”:~±Ä‚ަР6!8Ô1·õ½ígP†:²`Ë›””qäÈ |||ÄgŸ}¦Ì7·¹Þ½{‹ ˆ°°°qÙê÷•‘‘!¾ûî;m¹Ò®]»R5Ijq¥ÑhÄŠ+´sÖ­[÷ƒæf»råŠprrÒþ ž5k‹U"¢RH­V‹™3gjGÙØØØø>ç½{÷D‡´ÅÞàÁƒ?ꀎôôt1~üxíé-Z´7oÞ”:V¡¹zõªØ¶m›;v¬hÙ²e®óGËd2ñé§Ÿ ___±`ÁqâÄ É®_±`£·yU°É´-› ú/¹\Ž3gÎ@¥RÁÆÆFê8ÅJZZT*bcc‘€ØØXœ?YYY9–“Éd¨]»6ìííaoo…B{{{XYYI”¼`©T*øúúâܹs022¼yó0lØ0Èd2©£Q]½z½{÷FTTôôô0uêTL˜0ºººï|îÖ­[1hÐ ¤¦¦ÂÚÚ[·n…‹‹K!¤&"¢¢*66½zõÂ¥K—`hhˆY³faÔ¨Qù¾opàÀ 0<@¥J•°fÍtèÐ!_·A+<<}úôÁÍ›7annŽe˖᫯¾’:V¾ºsç”J%bbb T*‹G½¶\Íš5áààGGG899ÁÑÑæææ$.zõê…-[¶`óæÍðõõ•:1sæÌÁ„ À‚ÞŠ[þzñâΜ9ƒøøxmñ–˜˜ˆÌÌÌ×–ýä“Ornöööøä“O$H6lØ€Aƒ!##r¹[¶lA£F¤ŽE@­VcÚ´i˜={6²³³Ñ¦Mlݺ¹.ÿüùs|÷ÝwX»v-ÀÛÛ+W®äŽ ÒÓÓ1zôh¬X±àááõë×£jÕª½îììlL™2³gφ:uÂêÕ«Q±bÅ^7¾'Ož`ðàÁضm`èСX¸p!ôõõ%Nöþð{a X‚QQc‰€(V„X0öÞ{Áú‹Æõµ_lÑhD#ÑhlQQ,!D‰ˆ"‚¢€Q± ˆ-Y‘>¿?üî¾5´eÏ–ûs]\ÁÝaç² ‡gž9Ç××0eʬY³eË–œŒJêìÙ³2d=z„ÚµkãàÁƒhÖ¬Y¾m®^½Š¡C‡âÆ077ÇÚµkáåå%(1i³7»Ì*Uª„Í›7càÀÅ~½¿ÿþF™2e°zõjü¿ÿ÷ÿ´~ìDÛ¾};&OžŒŒŒ ¸¸¸`ÿþý°µµë½RSSqéÒ%DDD¨Ši omW±bEUMùñᇠHL)œ IDATobþ lT(,°‰!Inݺ¥*¸EEEáòåËïl·²²BÓ¦MUE·¦M›¢aÆ…º]O?~Œâܹs033Ã÷ßQ£F‰ŽEjôðáC }:rss1zôhøùùéäü\T8•+WF`` æÎ‹5kÖ`ĈˆÇÂ… Õ¾¯œœüù矪BZDD¢££ßZĬL™2pttD‹-àììŒ-ZÀÞÞžïC"=Ç‘Ž«Zµ*ºv튮]»ª{þüù[nqqq¸|ù2._¾Œü`llŒúõë« nÊÿ–tâùmÛ¶aâĉÈÎÎÆÐ¡C±}ûvηf ÆŽ‹š5kbàÀØ»w/’’’pøðaѱˆˆHG5jÔaaaX¼x1V­Z…¥K—"88;wî|ëV:I’0{öl¬Y³2™ _}õU©YHûã›o¾A:u0}út,Z´wïÞÅæÍ›‹]ÔÊËËC\\"""T DEE!===ßvFFFøøãóu¦999ÁÌÌL‡FD:„6"=T¡BtìØ;vT=¦P(¯ðvíÚ5\¿~ׯ_Ç®]»¼¾b\§N4mÚ4_áÍÆÆ¦Pû^»v-fÍšI’0þ|,[¶Œ ˜N:!44=zô@hh(:tè€àà`T«VMt4""ÒA¦¦¦X¾|9ºu놑#GâÂ… hÚ´)¾ýö[Œ7Àë•BLJŸ~ú eË–Õøm‚¤¦NŠZµjaذaضmž>}Š}ûöêBï½{÷ò-@péÒ¥w.:V¯^½|Å´fÍšA.——Æá‘ŽaÈ@Èår¸¸¸ä›»$++ 111ùŠn111¸sçîܹƒ¨¶µµµ}«Ó­fÍšùö±lÙ2,\¸2™ ¾¾¾˜2eŠÆŽ´‹½½=.\¸€N:!&&íÛ·Gppð[ï""¢Âj×®®^½Š©S§bÏž=?~<‚‚‚ðÝwßaæÌ™Ø·oär9Ž9’ï"#–ž={âÔ©SèÖ­Ž9‚Þ½{ãСCù¦Eyøða¾Û<###ß¹˜Ø‡~¨ºÍSYT«X±¢&‡ˆt lDÌÔÔÎÎÎpvvV=–““ƒ7n份T¹˜ÂƒpôèQÕ¶666ª‚Û;wàïïccclݺ•+…ªW¯ŽtéÒE5wΉ'P·n]ÑшˆHGU¨P»wïF=0eÊ ((°²²BPPP¡B ýÕ¢E œ>};wFpp0ÜÜÜн{wÄÄÄ "">|ëkªT©’¯3­E‹¨Zµª€ôD¤«X`#¢|LLL`oo{{{Œ1Àë9Mîܹ£*¸]ºt W®\Arr2~ÿýwüþûï^ìvìØ!C†ˆ<Ò"ÖÖÖ8uêºwïŽóçÏ£}ûöøã?`gg':é0´lÙ­ZµÂÓ§OammãÇ£Y³f¢£‘–ppp@HH>ýôSÕ<ÄJ*TÈWLkÞ¼9»ì‰¨ÄX`#¢Éd2Ô«WõêÕÃàÁƒU'&&bþüùسgLMM±oß¾|+œ€••‚ƒƒÑ£G„„„ sçÎ8}ú4jÔ¨!:é¨ÜÜ\ÌŸ?OŸ>… NŸ>Æ‹ŽEZ¦~ýúøã?Ю];$$$ qãÆØ»w/7nÌ9‚‰HíX`#¢b;xð öìÙcccìܹ“Å5z/ 9r]ºtAxx8:wîŒB/žADD¤”——‡1cÆàÀª‹8,®ÑûÔªU 'Nœ@ûöíqíÚ5ÌŸ?„©©©èh¤ƒ<==qøðaÑ1HËøûû`ˆŠiË–-˜5kd2¶nÝš¯³è]¬¬¬ðÛo¿¡}ûöˆŽŽFçÎqæÌXYY‰ŽFDD:dÚ´iعs',--qüøq899‰ŽDZ®^½z8~ü8:tè€ÀÀ@Œ;;wîd‹²˜BôO2ðzŽ%¢rttDLL ¢££áàà :i åÊLYYY\-”Š,99...¸}û6ºwGÂØØXt,""ÒË—/ÇüùóannŽ   ´k×Nt$Ò!QQQèСRSS1oÞ<øøøˆŽD:"<<‰‰‰¢c–SØ $8 i#euž6RЉ‰››&T"·o߆‹‹ ’““1yòdlܸQt$""Òr‡€TŸ÷éÓGp"ÒEo^(Þ¸q#&Ož,:é Uèß°ÀF””„6mÚ )) žžžl­§ GÇŽ‘žžÌ›7Ot$""ÒRüAê´{÷nŒ1FFF D×®]EG""= Û¿? lT Î;sž$—••…öíÛ#,, ;vÄo¿ýÆÉa©ÄŽ=ŠþýûC’$¢[·n¢#‘–yøð!œñøñcv=“Ú,X°>>>°²²BDD>úè#Ñ‘ˆHÇÉ$N¾FD…0iÒ$lÞ¼uêÔÁÅ‹Q¹reÑ‘HO¬^½sæÌAÅŠ‰:uꈎDDDZ"++ ;vĹsçÐ¥Küú믜·“ÔB’$ 6 ûö탽½=ÂÃÃann.:é0؈¨@Û¶mÃØ±cannŽóçÏ£I“&¢#‘‘$ ƒÆàää„sçÎq€KDD^¯êëë‹Úµk#22’øH­ÒÓÓѪU+ÄÆÆbèСøùçŸEG""f$:i·Ë—/«V ݼy3‹k¤v2™ ?þø#5j„+W®`Ò¤I¢#‘صk|}}annŽ€€×Hí”ï-+++ìÝ»ëׯ‰ˆt;؈è½ÒÓÓáììŒ7n`úôéøöÛoEG"=vãÆ ´jÕ /^¼ÀîÝ»ááá!: r÷î]899!-- Û¶mÃèÑ£EG"=vôèQôíÛ¦¦¦¸xñ"EG""Ä6"z¯Ù³gãÆpvvÆÊ•+EÇ!=×°aClÚ´ 0uêT$$$NDDD"äææbøðáHKKÃèÑ£Y\£R×»wo|þùçÈÌÌ„§§'^½z%:é ؈èñý÷ßÃÜÜ»ví⊡¤ðððÀóçÏ1|øpäææŠŽDDD¶lÙ2„……¡N:¼e4æ¿ÿý/‹¹s犎CD:ˆ·ˆÑ[RRRðñÇ#997nÄäÉ“EG"òüùs4mÚ÷î݃æÍ›':iHDD\\\ IΞ=‹6mÚˆŽDäÏ?ÿDóæÍ‘‘‘cÇŽ¡sç΢#‘a½eÆŒHNNFÏž=9áþøc¬X±’$aÒ¤Ixùò¥èHD¤CX`#¢|Ž;†Ý»w£B… ؼy3d2™èHd€\]]1mÚ4ddd`âĉ`³5‘þ[¹r%¢££áè舅 ŠŽCjÚ´ipssC||<-Z$:éÞ"JD* …öööHHH€ŸŸ&L˜ :0¾‰ˆ Ç7àä䄜œ„……¡E‹¢#‘»yó&œœœÍ÷#;؈HåË/¿DBBÚ·oñãÇ‹ŽCN.—ãûï¿Ì;ÉÉÉ‚Qi™>À€DG"R9}ú4:vìܺu VVV¢#‘ã-¢D„àà`¢zõê˜?¾è8Dù|ðÁ˜?>rssñù矋ŽCDDj´víZÄÇÇ£}ûö,®‘ÖéСú÷ïääd|õÕW¢ã‘–c‘ËËËC“&M‹íÛ·cÔ¨Q¢#½å͇Ç£OŸ>¢#Q ýõ×_¨[·.^½z…ÈÈH899‰ŽDô–øøx4jÔ’$áúõë¨S§ŽèHD¤¥ØÁFdàöîÝ‹ØØX4oÞ#FŒ‡èÌḬ̀bÅ ÀâÅ‹‘——'8•ÔÊ•+¡P(0fÌ×HkÙÙÙaÆŒÈÊÊÂÒ¥KEÇ!"-Æ6"–““ƒÆ#..ÇGçÎEG"z/I’ТE \ºt ûöíÃàÁƒEG""¢bzøð!êÕ«I’pûömØÚÚŠŽDô^ÏŸ?‡ bccÑ AÑ‘ˆH ±ƒÈ€íÚµ qqqpsscq´žL&SÍòå—_"77Wp"""*.¼zõ “&Mbq´^… 0kÖ,äääÀÛÛ[t"ÒRì`#2P999¨_¿>âããqæÌ´k×Nt$¢BqqqAXXvíÚOOOÑqˆˆ¨ˆîß¿>úÆÆÆ¸{÷.ªV­*:QÒÒÒ`gg‡gÏž!665‰ˆ´ ;؈ T@@âããáîîÎâéeÛ7ß|^#""Ò=ëׯGff&¦M›Æâé KKKÌ;yyyXµj•è8D¤…ØÁFd Z¶l‰ˆˆœ>}íÛ·‡¨HÚ´iƒ .àØ±cèÒ¥‹è8DDTH …~ø!233q÷î]T«VMt$¢BS¾ÓÓÓqïÞ=T¯^]t$"Ò"ì`#2@gΜADDš6mÊâé¤Ï?ÿ°víZÁIˆˆ¨(üüüðüùs >œÅ5Ò9r¹“&MBVV|}}EÇ!"-Ã6"Ô·o_9r;vìÀˆ#DÇ!*²ÜÜ\4lØwîÜATTš4i": ''uëÖÅýû÷‹?þXt$¢"{ôèj×® sssÜ¿r¹\t$"Òì`#20ñññøå—_P³fM 6Lt¢b166ÆgŸ}I’°qãFÑqˆˆ¨~ùå$&&¢[·n,®‘Ϊ^½:†ŠçÏŸc÷îÝ¢ã‘aÈÀlݺyyyðòò‚‰‰‰è8DÅ6fÌÈårìÙ³ …Bt""*À?ü˜6mšà$D%óÙgŸøß{šˆ`È äää`ûöí011ÁèÑ£EÇ!*¹\Ž¡C‡B¡P`ïÞ½¢ãÑ¿HHHÀñãÇQ«V-.NC:ÏÙÙÍš5Ã¥K—pùòeÑqˆHK°ÀFd@‚‚‚ðàÁtïÞ¶¶¶¢ã•˜——€×“f‘öÚ¶mòòò0fÌñOÒ}ãÇlÙ²Ep"Ò\ä€È€ôêÕ øå—_гgOÑqˆÔ¢I“&ˆŽŽÆÕ«Wáèè(:ýCnn.j×®GáÞ½{¨Q£†èHD%–šš [[[˜˜˜àÁƒ°°°‰J‘N:%:i9NÀDd RRRpìØ1X[[£k×®¢ã©ÍÈ‘#1{ölìØ±«W¯‡ˆˆþáÌ™3HJJ‚»»;‹k¤7¬¬¬Ð»woüüóÏ8zô(Ós±±±8qâ„è¤åX`#2DNN ÀÅ H¯ 8sæÌÁ°jÕ*Èd2Ñ‘ˆˆè ûöí 2Dp"õ4h~þùgøûû³Àf ÆŒÑ1HË,_¾'Ož„ɧŸ~*: é???ØÙÙ‰ŽA%°ÿ~ÀàÁƒ'!R¯Zµj¡U«V¸páÂÃÃѺukÑ‘ˆˆèÿdggãСC(S¦ úõë':‘ZuëÖ åË—Ço¿ý†/^ |ùò¢#Q)ëÔ©:uê$:i™ÈÈÈ×6¶9Ra( Ѩžÿüs‡¨TÈår´mÛ …ç·ˆbHÏ…„„@¡P ]»vËå¢ã•š®]»Ž;&8 @PP°{ôžò=®|Ï‘abHÏ:wî,8 Qéruu…\.Ghh(f!"ÒÊ Ê DúJ9ÎVŽ»‰È0±ÀF¤ç”+sžEÒweÊ”››²³³qöìYÑqˆˆ Ú«W¯†råÊ¡M›6¢ã•*T­Z111xòä‰è8D$ lDzìÅ‹ˆEùòåaoo/:Q©suu„†† NBDdØ"""••…-ZÀÔÔTt¢R×¶m[à>^p""ÃÃ1ªªU«ÂÒÒOžø@t"SvM(»(ˆˆH3x  lD†‹6"=”˜˜ˆ¼¼<Ô¬YFFü1'ÃÃÁ-‘,°‘¡ã„Èpñ/o"=ÄÁ-º?ü¸È‘†)Ï»Êó0‘¡©U« !!Ap"Ò4؈ôÐÓ§O•*UœD÷DFFB&“¡eË–$é­ÇsrrУGŒ7î­¯•$ õêÕÃÚµkUÍ™32™ AAAî{Ö¬YhÔ¨,,,P©R% 6 ?VϘ*Uªþúë/ÁIˆˆ ‹ò¼«<Sቃdff¢]»v¨V­är9¬¬¬àî°0õœ±¶¶¤¤¤NBô6ug”ÛËåò|£G~ï¾ÿøã´nÝ(_¾<ºwïŽÛ·o—Æa Ñâà¶äâãã±sçÎw>7aÂìß¿ …"ßã§OŸFRRFŽ àõ\x?ýôš4i‚-[¶¸O¹\®zݸ¸8ddd`È!%?T¹reÀßÿ-8 ‘aQžw•ça*:MALLL°aÃ$$$@¡PàáÇhÛ¶-zõ꥞20ƒ.PÇyž?…B¡úؾ}û;_óÅ‹èÙ³'úõë‡çÏŸ#)) 6668p ÚŽI°ÀF¤‡8¸-9ooo,X°éééo=׳gOXZZâçŸÎ÷ø–-[Я_?Õ÷ýÈ‘#$ ?üð~ù嗻Ѽ½½áàà™LkkkÌœ9çÎSßAe÷¦²›“ˆˆ4ƒ]ô%§é1ˆ±±1P¶lY€L&ƒ‰‰‰jÁ *@rr²à$Dï§ŽóLQ$%%!-- Ó¦MC™2eP¾|yxyyáÚµkÅ>m¤76‘­Žïúš}ûö•Æa ·%7nÜ8TªT +V¬xë9Œ3&ßágÏžáСC˜0a‚ê1??? 2-Z´@ýúõß{Eç}Nœ8ggçbƒ!355Eùòå‘••…/^ˆŽCDd0ØE_r¢Æ C‡…¥¥%,,,†µ¡áE>Òê8ÏEýúõѬY3¬[·™™™xöì6mÚ„aÆû´‘ÞØ”4Ýêø¦7¿†·u‘H,°•œ±±1Ö®]‹Õ«W#))é­çLJˆˆÄÆÆvíÚ[[[tèÐp÷î]œ[¶lÉwàß9rkÖ¬¯¯¯šŽÈðT¨PÀës3i†rœ-—Ë'Ñ]¢Æ {÷îEZZnÞ¼‰””Ìž=[ÍGfx‹(é‚’žg”¬­­Q¡BÕdž Þ¹?:t»víRÍ7ýäÉøùù©ÿàÒ»›¦[‰´Q^^ÀÈHï~Ä5ÊÝÝîîî˜7oÞ[ÏÕ©Sîîîª+;[·nÅøñã!“ÉTÿ®S§Z·n ðôôÄÝ»wqúôé÷{ðàAŒ=‡BóæÍÕxD†ÅÌÌ ‘‘!8 ‘áHKKXZZ N¢ÛDA€×&Ë–-Ã?þ¨¦£1,ÆÆÆ€ÜÜ\ÁIˆþ]IÎ3J)))xþü¹êcÚ´iïÜWVV>ýôSxzzB¡PàéÓ§¨ZµªÞ5&éÝ_ßšnu|S­Zµ`ccƒÑ£Gó–$Šƒ[õY½z5üýýqñâÅ·žóòò®]»Šk×®©n%ÏÉÉÁ¶mÛpÿþ}X[[ÃÚÚÍš5üðÃÿº¿mÛ¶aüøñ8zô(:uê¤öã1$ʹd233'!"2¼È§>šƒ¼)77eÊ”QËqsssxgѶ)Îy¦8âââ‡3fÀÌÌ +VÄäÉ“X‚ôÚÇDtuS¶:öîÝûE³qãÆÁÇDZ±±°··ÿ×VÇ7}ýõ×ï­ÆÚÙÙáúõëhذ!0dÈLš4 {öìQß·êS¿~}L™2K–,yë¹>}ú`êÔ©9r$zõê…jÕªñ×_áâÅ‹ùæ 9vì¦M›†¿ÿþû³ëׯ‡··7Ž?Ž–-[–ÞA“׿ârrr'!"2YYY^Ï…I%£©1ÈÕ«W‘œœŒO>ù¦¦¦¸qã.\OOÏÒ=@=¥,Lfgg N¢½^¼xQ¬¿ôôôb]8ÍÌÌ,VÁ377WÕ8SÐbeºª8ç™â¨Y³&,,,°aÃÌœ9ؼy3J_ëè] Èßê8cÆŒ|ϽÙêøí·ßþk«£ò³‚T®\Yõ‹ªV­ZðööFÿþýÕs0¤ó …êliž––†œœDFFø_•Ì’%KÞ9·£©©)F…Õ«Wç›oà‡~À€TWŒ•F¯¾ú ;vìÀçŸþÖëMŸ>&&&èØ±c¾ÇSSSU·PáYXX^¾|)8 ‘áxõê \¹r‚“èMŒA²³³1oÞ<ܼy’$¡J•*ðððÀâÅ‹Kç  €©©)²²²àîî^ä Þ"‹HEõìÙ³b}i—¢žgÞ¤œóXÉÅÅÁÁÁomW¾|y:tóçχ··7ŒŒŒ£r›( IDATЦMìÝ»W=¡%d$…žx[[EFF¢E‹ÈÎΆ‰‰ âââààà€µk×bêÔ©ªÇÀßß“'OÆáÇѡCÜ¿_UýçëÇñãÇѯ_?½h vttDLL ¢££µ¢ºüòåKÕ•QQŸµè%ÒÙ³gáêê*:‘nnn åÏ‘U¨P©©©xþü9¬¬¬DÇ!Bùs@ïfiiY¬¿µÍÍÍ‹Õ@P¶lYÕ­»EallŒòåËbccñøñcìÞ½E~-ÒoË—/Çüùóõ³ƒ Ð\«#ðzR{{{ØØØ 11K–,Aß¾}KôšÚæÂ… ˆW]MyóÊJi|þ¾â–.²°°PÝ&Q’Ïår¹ªå¼ Ï}||ŹɠñVi"""AY\ûý÷ß‹üµ"‹HEU±bÅb}.òôôäPT ½-°šiu€“'ObèСxùò%ÌÍÍÑ·o_|óÍ7%?-âåå%:€ü¿pJãsuÃÞü\„Ÿ~ú QQQ:ß™JT\새H󬬬ššŠÔÔTv°‘Áã‚UD†Eo lÍ›7«˜P¡B¤¤¤¼sûU«VaÕªU…z‚|ýõ×øú믋ô5º¦uëÖ°±±™™€×ój¨ãó¢À¨p”ßߌŒ ÁIˆˆˆˆˆ ‡rîWå\°Dd8ô¦ÀF¥ËÏÏO+æ`£ÂQ$‹3A*‘¾`‘æ)7P.v@dh”«—w>o"Ò]œ˜†H±ƒèfvÀi ld踒.‘ábHUªT ðôéSÁIˆÄQ¾ÿ•?DDTú*W® øûï¿'!C9E‘µµµà$D¤i,°é¡*Uªþúë/ÁIÔçÁƒèÞ½;*Uª™L¦j¿WºuëÜÝÝ!—Ëakk Ÿ|ÏÏš5 5‚……*Uª„aÆáñãÇÅÞðz1‰FÁÒÒ~ø!>¬zî?þ@ëÖ­aaaòåË£{÷î¸}ûv‰öW”íÓÓÓ1jÔ(T¨PUªTÁ¢E‹J´?]£P(™™ ¹\Î6"" ÒÇ‹|ƒmû‚Æ Eý~èšääd€à$¤KzôèqãÆ½õ¸$I¨W¯Ö®]‹ÈÈHÈd2´lÙ2ß¼ñÊÇß÷³[ÐϬ\.Ï÷ajjšoeYMŸÃþ©´ÏiêÄ‘ÒÇÁ­‘‘úõë‡õë׿õ\^^zõê…Fáï¿ÿFLL Nœ8;v¨¶‘ËåØ¿? âââ‘‘!C†k°sçN¬Zµ DZZ._¾Œ&Mš^¼xž={¢_¿~xþü9’’’`ccƒ{EÝ~Μ9HJJÂýû÷‰;wâ‡~(öþt²¸¬,6‘f(»v޷И.â¤hÛ4)ê÷C×°ƒžŠc„ ªŸ‹7>}III9r¤ê±øøxìܹ³Ð¯]ÐϬB¡È÷ñÉ'ŸÀÃÃCõ¼&Ïaÿ¤‰sšºI$¢wqppHÑÑÑ¢£P:tH õíÛWtµ‹ˆˆHÙÙÙªÇnܸ!RRRTýþûïRÛ¶mßû:üñ‡dll\¬ýI’$ÕªUK:qâÄ;¿æÚµkI¡P¨ •LLLн¿¢lŸ-ÉåréôéӪǾùæ©uëÖ%ÞŸ®PWóæÍEG!"2(‹-’HK—.Eí8)xû¢ŒA” ûýЛ7o–H^^^¢£yxxH¤Ý»w—ÊëgggKÕ«W—üüüò=>lØ0ièС’$ýïgÎ××W²µµ•^¾|™ïñ‚~v ³Ý;w$™L&]ºté½Û”æ9ìŸ4yN+  €Tâ6¶2¾¿•ñß^/33íÚµCµjÕ —Ëaeewww„……©¶)¨½º8ùÉ0T«V ôªÝ¾0Þ<¿H’„èèè÷n{âÄ 8;;k?‰‰‰HHH@TTììì`mm OOO<{ö P¿~}4kÖ ëÖ­Cff&ž={†M›6aذaÅÚ_QÅÇÇC¡Pä»ää䄨ØXì_úè#|ôÑGX¼x1^½z…””øøø@¡Päð*9rkÖ¬¯¯o±ö§lý¿uëþüóOܸq>Äĉ¼þù>tèvíÚ¥*ð?yò~~~Å?È"Pž-,,TÉåò÷~?ô‘ò½¯üY ""Íà„c ðc’~?´Ç T\ãÆCDD„ª ½k×.ØÚÚ¢C‡ù¶366ÆÚµk±zõj$%%©mÿ999ضm¼¼¼Þ»MiŸÃþIô9­¨J\`ëÙ³',--ñóÏ?ç{|Ë–-èׯŸj%!ðööÆ‚ žž^¨×®^½:&L˜€† ¸íÝ»wqêÔ©|oooo888@&“ÁÚÚ3gÎĹs犽¿E‹aݺuøøã¼ž¸ÒÎήX¯gll ÕäÛ2™ &&&¨S§€×oî;v`Ñ¢E°´´DÍš5ñÙgŸáÇ,v~2ÕªUC¹råðøñcÕRáúÌÈÈGE\\>øàtèЮ®®¨X±"d2Y¾m<ˆÑ£GãСChÞ¼y±ö'—ËË–-C¹rå`mm  00••…O?ýžžžP(xúô)ªV­ª±ùE”ù^¾|©zL¡P@.—¿õýÐWñññð¯çh""R?C+°q òî|…ƒ¨ãû¡X`£âªS§ÜÝÝU]l[·nÅøñãß9~www‡»»;æÍ›§¶ýÿòË/P(ùš–Þ¤‰sØ?‰>§U‰ lle|w+ca :–––°°°@XXBBBð/*9Cà6lØ'NœÀ³gϹ\ŽO>ù$ß6Û¶mÃøñãqôèQtêÔ©Øû²³³Cùòåóý²“Édª+³qqqˆ‹‹ÃŒ3`ff†Š+bòäÉïýÅ¡nvvvËåùnO¹rå ìíí5²mp÷î]P]´ ""Í0´ñÀ1È?óf ¢®ï‡6âE>* ///ìÚµ ¡¡¡¸víFýÞmW¯^ \¼xQ-ûöóóðaÃTE°7iêöO¢ÏiE¥–UDÙÊX|{÷îEZZnÞ¼‰””Ìž=oñ¢’ÓÇnFF²²²¼žÇ0##CõÜõë×UWKOŸ>µk×báÂ…ªçׯ_Ù³gãøñãpss+ÑþŒ1vìX,Z´H5ÀòåËU·x׬Yذa²³³‘––†Í›7ÃÁÁ¡ØÇW”íMLL0bÄ|ýõ×HKKÃýû÷±~ýzŒ;¶DûÓ%¼zLD$†……lllðòåK$''‹Ž£6ƒnûÂŒAŠóýÐÙÙÙHJJB™2eP£F ÑqHõéÓFFF9r$zõ꥚[û]êׯ)S¦`É’%¾nA?㉉‰~gME“ç°ÒÔ9MÔ²Šh§N¤éÓ§K’$IMš4‘|||TÏýsõ†^½zIžžžj[í" @’ËåRZZÚ;Ÿ?pà€T¡Bé÷ß/Ô±¼k·nÝ’HÉÉɪǂƒƒ¥råÊëõÞåøñã’™™™$I’'ž>}ªzþäÉ“’\.WÛþ ƒ«ˆê®éÓ§K¤o¿ýVtµQž¯ÞüPòññ‘*W®,Éår©E‹Ò©S§ÞúZÉÂÂ"ßGNNŽ$I’tùòå|ÿ.h’———T¹re©|ùòÒ!Còý¼KÎÎÎR¹rå$ ©S§NÒõë×UÏuEÝþåË—Òˆ#¤òåËKÖÖÖÒÂ… ‹ôýÔeyyy’¥¥¥$“ɤ/^ˆŽCDdp\]]%ÒÙ³gEGQŽAÔ7)èû¡Ë”«Ê6hÐ@tR³Ò^EôM³gÏ–H¿þúk¾Çßõwþ³gϤʕ+ø÷AãþÅ‹KM›6}ï×jòöÏ×S÷9­4(WU[mÿþýRåÊ•¥³gÏJ&&&Ò£GTÏýópóæMÉÔÔTòõõUK­k×®Ò„ ÞùÜ?þ(U¨PAúã? },ïÚ_NNŽT¾|ù·–àVÄJ’_)((H²´´”$éK\Ÿ9sFõ|AK\u…Á›îÚ²e‹@7nœè(Du÷î] €dgg': ‘Aš«[·.îÞ½‹;wîp%s=ãéé‰={ö`÷îÝððð‡´ÌòåË1þ|õu°‘öiÑ¢ ""Bp"ÍQ¾ß•ï""Ò,KKK4jÔiii¸~ýºè8D‘œœŒøøxØØØÀÎÎNt"€6"=¦¼]øÜ¹s‚“iFRRP£F ÔªUKt""ƒåââ8þ¼à$DšqþüyH’vm(؈ô·dhBCC®®®‚“6^ä#CÃ1±ÀF¤Çìííaee…ØØX¤¦¦ŠŽCTê”È)ÿ°#""1x‘ Ç DÄ‘322‚‹‹ òòò8À%ƒÀ«ÇDDÚ¡~ýú¨Zµ*âââðäÉÑqˆJÕ«W¯péÒ%”+WÎÎ΢ã‘ ,°é¹®]»Ž;&8 Qézôè®^½ŠêÕ«£I“&¢ã¼.]ºŽ?.8 Qé:uê²²²Ð±cG˜ššŠŽCD‚°ÀF¤ç8¸%C I’йsgN.LD¤8!C èܹ³à$D$ lDz®Aƒ¨[·.nÞ¼‰;wCTj”]šÊ®M""«k×®066ƱcÇ››+:Q© tïÞ]p"‰6"À+Ȥïrss ccc^=&"Ò•*UBóæÍñôéSDFFŠŽCT*îܹƒÛ·o£nݺ¨W¯žè8D$ lD OŸ>€#GŽNBT:BCCñôéS¸ºº¢R¥J¢ãÑÿá„ôÝÑ£G½{÷œ„ˆDcÈtìØÖÖÖ8uêRRRDÇ!R»ýû÷,8 ½Iy^Vž§‰ô;}ûC† œ„ˆD3€ˆJŸ‰‰ ú÷ï???ÀËËKt$"µÉÍÍE@@ŒÑ¿Ñqˆˆè uëÖEóæÍ‰ÈÈH4oÞ\t$"µIHHÀÅ‹Q«V-´lÙRtÒOOOlÛ¶Mt Ò2'NœÀ‘Á-:Q‘ÄÄÄ 88µkׯŋ€¶mÛ¢mÛ¶@^^žèˆD$ lDjÆŒÉdøæ›ožž.:Q¡­_¿OŸ>ÅСCñÁˆŽCDDE4kÖ,€···à$DEóå—_B’$Ìœ9×®]ÃâÅ‹ammóçÏcÀ€hР6mÚı5‘’I’$‰ADb 8ÄŠ+0wî\Ñqˆ ”šš ;;;¼xñ111hÔ¨‘èHDDTD999hÔ¨nß¾'NÀÝÝ]t$¢EEEÁÙÙUªTÁÝ»wannHOOǶmÛ°fÍܽ{`mm)S¦`êÔ©¨R¥ŠÈØD¤Aì`#2`_~ù%ŒŒŒ°råJ¤¥¥‰ŽCT µk×âÙ³gððð`qˆHG™˜˜`É’%€E‹ NCT8‹/†$I˜?¾ª¸æææ˜:u*âââ°ÿ~´lÙ)))Xºt)jÕª…‰'âæÍ›“‘¦°ƒÈÀyxxàçŸÆÒ¥K9È%­öôéSØÙÙ!==ׯ_G½zõDG""¢bÊÍÍ…ƒƒ®_¿Ž   tëÖMt$¢÷ GëÖ­akk‹Û·oÃÌÌì_·?{ö,V¯^ÀÀ@äååÁÈȽ{÷Æœ9sàâ⢡ÔD¤iì`#2pK–,‰‰ V¯^Ç‹ŽCô^ÞÞÞxñâFÍ⑎366Æ—_~ ˜;w.rrrÄ"zI’0{ölÀþóŸ‹kàææ†#GŽàÏ?ÿ„——LMMqøða.ˆ@¤çØÁFD˜0a¶lÙ‚±cÇbëÖ­¢ã½åúõëptt„©©)®_¿Žš5kŠŽDDD%”——‡V­Z!226lÀÔ©SEG"zËÞ½{1lØ0|ôÑGˆ…©©i‘_㯿þ‚¯¯/6n܈””@½zõ0sæLŒ5*ß-§D¤»X`#"]ºtApp0-Z„¥K—ŠŽCDDjröìY´kו*U­[·P±bEÑ‘ˆTÒÓÓѨQ#$&&âðáÃèÓ§O‰_ï§Ÿ~Â7ß|ƒ;wîà‚Dú„·ˆªV­Š… "//3gÎëî¤M~ýõW£F˜7ožè8DD¤Fnnn4hþþûoÕ-£DÚbõêÕHLLħŸ~ZââðzA„É“'ãæÍ›\H±ƒˆ™™™pppÀ­[·°uëVŒ;Vt$"¼|ùˆÇîÝ»ááá!:©Ybb"6lˆììl„‡‡£Y³f¢#áÖ­[pttDNN¢¢¢`oo_*ûyׂ={öÄܹsѶmÛRÙ'•v° lÙ²X³f `Μ9xòä‰àDD¯ሇ››† &:•‚š5kbÞ¼yÈÉÉÁøñã¹à 'I&NœˆŒŒ L›6­ÔŠkÀ»D8zô(\]]¹ ‘Žaå3tèPìÛ·ƒƾ}ûDÇ!‰Ö­[ÃÄÄW®\AÆ EG""¢R’••…fÍšáÚµkX¾|9¾øâ ёȀmÙ²&L@­Zµ ¹\®±}sA"ÝÅåó×_¡Q£Fxúô)Ž9‚Þ½{‹ŽD(;;-[¶Ä•+WðÕW_aáÂ…¢#Q) ƒ««+Ê–-‹èèhÔ«WOt$2@=BãÆñìÙ3üúë¯èÞ½»ÊÖ¬YƒÛ·oà‚DÚŽ·ˆQ>UªTQÝ*:qâDÞ*JBx{{ãÊ•+°··g‘hÓ¦ ¦NŠW¯^aĈ¼U”4N’$Œ7Ïž=ƒ‡‡‡°âA.ˆ@¤ØÁFDo‘$ ={öDPPzö쉣GB&“‰ŽE"$$îîî022¹sçТE Ñ‘ˆˆHC œœœpçÎ,^¼ÞÞÞ¢#‘Y¿~=¦OŸŽjÕª!::666¢#åó¾fÏž 777Ññˆ  lDôNOžŒ+VàâÅ‹333Œ93gÎDƒ '$Òo,°QîÝ»‡-Z %%Ë–- DG"=òèÑ#8;;ãÑ£GX°`–-[&:i‰“'O¢K—.^O]¡Íób‘î9þ<:tè€ììløûûcÀ€¢#© D Ò<؈¨P‚ƒƒUK• wïÞ‚‘>HOOGÇŽŽÎ;#((ˆó®Q>+W®Ä_|Š+ââÅ‹¨W¯žèH¤’’’кukxzz"//;wî„§§§èHDD¤#®_¿Ž6mÚ 55ãLJŸŸW7§BSvÏ›››ãÔ©ShÕª•èHZ "ÌÓÓ{ö샴 lDT,IIIpqqÁýû÷Ñ©S'¢lÙ²¢c‘–ûå—_0pà@deeÁÇÇóæÍ‰ˆˆtÌÙ³gѵkW¤§§cÆŒX»v­èH¤–-[†… ÂÔÔ@¯^½DGÒ:ïZ¡eË–˜;w.úõëgÐ "( l3g΄‡‡‡è8¤e–/_Ž@æììÌhïÞ½Û>Mïwûöm|òÉ'xôèz÷î ˜ššŠŽEZêÔ©SèÑ£2220oÞ<øøøˆŽDDD:*88½zõBVV.\ˆ¯¾úJt$ÒbëׯÇôéÓallŒÝ»wcÈ!¢#i5.ˆð6em÷îÝ,°Ñ[–/_ŽùóçÃäÒ¥K¢³â­„¸ IDATxõê•è¤Erss‹sçÎÁÙÙ¿ýöŽ=ŠaÆáçŸf‘ÞrêÔ)ôêÕ ˜6m‹kDDT";wÆþýû1hÐ |ýõ×011Á’%KDÇ"-´qãF̘12™ ~~~,®B•*Uàíí/¾ø"ß‚S¦LÁâÅ‹¹ Ñ{˜(?‰ŒŒ™ƒ´TóæÍEG -––†ððpœ?aaa Cjjj¾mLLL€¾}ûâàÁƒ(W®œ ´¤m~ýõW 8;v,Ö¯_/:é>}ú`ÇŽ>|8¾üòK( ¬\¹’s²‘ÊÊ•+ñÅ_@&“aýúõ;v¬èH:ÅÜÜ“'OÆÄ‰qøða|óÍ78þ<–.]Š•+WrA¢PØœEæ -åàà€˜˜Ñ1HÃqîÜ9„……áüùó¸zõ*rrròmS»vm¸ºº¢M›6hÛ¶-rssÑ­[7üöÛoèÞ½;~ùåÈårAG@Úbß¾}9r$²²²0eÊlذø‘Ú :¦¦¦6lV¯^ …B7òw aáÂ…X¶lŒ±qãFxyy‰Ž¤³ŒŒŒÐ¿ôïß¡¡¡Xµjáçç‡-[¶pA¢ÿcRð&D¤ÏrrrpõêUUwÚ¹s瘘˜o´lÙ...ª[[Û·^+$$îîî8sæŒjákkkM i???L™2¹¹¹œsˆˆJMÿþýqøða 0ßÿÿöî;*ªkmø3T$Œ6D ˆ "bnì\QbÅØîµp—]c¢#ˆ1b,`Œ(«‰7¶D)Œ€¨ˆŠXcAADFØßYÌ— Òåù­5xfÏÙÏ!£9¼ç=g¯^¼¼>'Nœ@nn®Úccc¸¹¹©ºÓºté}}ý7îÛÖÖ±±±èÙ³'Nœ8üòË/hݺuUÕ@BÌž=ÁÁÁ€E‹aΜ9§""¢º¬wïÞøùçŸ1`ÀlÚ´ ·nÝÂŽ;иqc©£Q5zòä |||°gÏ4hÐ[·n…———ԱꤶmÛbÍš5øòË/U "œ>NNNˆE‹-ÇâI¢Y³fˆÅG}„;wî {÷î—:U‘¨¨(¸ºº"33®®®HHH`qMB% "ÄÅÅ©îj),,ÄÚµkagg///;vLê˜DU‚lDµHzzºZwÚùóçQ\\¬6ÆÖÖ®®®ªî4[[[ÉVÒÒÔÔDXX:vìˆÏ>û áááHJJ¶mÛðî»ïJ’‰*_xx8¦N ¥R OOOlÙ²Mš4‘:Õc†††Ø·oæÌ™ƒ¯¿þ“&M‰'°zõj>ªŽP*•˜:uªªxúÙgŸaùòåÐÕÕ•8•puuÅîÝ»¹ Õ,°ÕP………HLLT=;-!!wîÜQ£«««¶º§B¡¨‘…‘#GB.—cРAˆ‹‹ƒ££#¢¢¢Ð³gO©£Q<~üãÇGtt4d2f̘ŋó$‰ˆˆjMMMÃÙÙ£GFTTRRR°eË´k×NêxT™™™6lŽ?Ž `õêÕ9r¤Ô±è^· ‚ ¹ Õ ,°ÕYYYªÎ´øøx$&&âéÓ§jcÌÍÍáææ¦êNstt¬5Wé:wîŒÄÄDøúúbÿþýøÇ?þ©S§"((¨Öý¿¸¸8øúú"33FFFX¿~=¼½½¥ŽEDDTŠ··7ììì0xð`$''ÃÉÉ !!!øì³Ï$ëò§òÛ¼y3&L˜€ÇÃÆÆ?ýô:uê$u,*.ˆ@uŸÁF$‘ .`ýúõð÷÷‡ÌÍÍáåå…%K–àØ±c(((@ûöí€ 6àâÅ‹¸{÷.vî܉éÓ§ÃÕÕµÖ¦LMMñóÏ?#44:::X¶lœqîÜ9©£Q=þ ,@÷îÝ‘™™ 777$%%±¸FDD5š­­-N:…ñãÇãéÓ§˜0a €{÷îIÊ(''>>>ðõõÅãÇáëë‹3gΰ¸V •,ˆpñâEÕ"$YYYøâ‹/ТE Œ7/^”:&Ñ[c¨àرcX²d ¼¼¼`nn;;;øûûcýúõ¸pá4h€îÝ»cΜ9Ø»w/²²²ššŠ5kÖ`äÈ‘hÓ¦Ô‡Q)d2¦M›†“'O¢}ûöHII““‚‚‚ðüùs©ãÑk$%%A¡Pà‹/¾|ñÅøý÷ßaee%m0""¢2ÐÓÓêU«sssìÝ»r¹ÑÑÑRG£7Ø»w/ìíí ###DGG#** 5’:UÀ‹ " 8 "P­ÆQ¸{÷.víÚ…ÀÀ@¸¹¹ÁØØîîî˜9s&bbbpÿþ}4mÚC† AXXŽ?Žœœ9r‹-Bß¾}abb"õaT)$&&bêÔ©(,,Äœ9sàèèˆÄÄD©£Ñ ž>}ŠY³f¡K—.HLL„­­-Ž;†yóæñykDDTëôë×)))ðòò½{÷àããƒ~ýúáúõëRG£ܽ{C‡EÿþýqóæMxzz"99Æ “:U2WWWìܹçÏŸG@@tttwwwtíÚÛ·oGQQ‘Ô1‰^‹6¢ B 55k׮ŨQ£Ð¶m[XXXÀÛÛ¡¡¡ˆ‡R©„ƒƒ&L˜€¨¨(\¹r·o߯¶mÛ0eÊtíÚÚÚÚRJµkР–-[†„„Èår¤¤¤ÀÅÅS§NÅ£G¤ŽGöïßCCCóçÏGRR\\\¤ŽFDDTnï¼óvïÞíÛ·ÃÂÂûö탽½=V¬XÁŽú ¸¸°³³Ã?þ¬_¿@‹-¤ŽGU¨dA„k×®aþüù033S-ˆÐ¦M¬\¹ùùùRÇ$z)üU$ z‘ƒƒΞ=‹””Èår©ãÔùùù8yò¤jeÏ„„dgg«Ñ×ׇB¡P­ì©P(`dd$QâÚA©TbÉ’%X¼x1 ФIcÔ¨QÐÐീêvùòeLŸ>111…Bµk×ÂÞÞ^âdDDD•ëáÇøÏþƒõë×C;;;„††¢W¯^RG«—~ûí7L›6 gΜ :Ë—/Ç;ï¼#q2’B~~¾Ú‚`ffVí "øøø ::›7oÆðáëeNª=‚ƒƒ1kÖ,v°½ÉíÛ·±}ûvL:...066†‡‡æÎ‹}ûö!;;Íš5ðaðbÅ $&&âÑ£G8pà.\ˆ^½z±¸VÚÚÚ˜;w.Ξ=‹âþýûð÷÷‡““~ûí7©ãÕ=B`` Ú·o˜˜4mÚ‘‘‘ˆeqˆˆê¤Æ#""ñññpqqÁùóçÑ»woôîÝiiiRÇ«7®_¿ŽáÇÃÃÃgΜ>Œ-[¶°¸VqAªMX`#ú›ââb$''cÕªUøôÓOѲeKXZZâã?ÆòåËqâÄ £sçΘ»ví CCC‰×/ÏŸ?Ç÷ßÅ‹ãÚµk€þýûãË/¿D‡$NW7<}ú«V­Â’%Kpÿþ}hhhÀÇÇ ,@Ë–-¥ŽGDD$‰¼¼<„††",, 999ÐÑÑÁèÑ£1wî\4kÖLêxu½{÷°dÉ|÷Ýwxúô)ôõõ1iÒ$Ìœ9ÆÆÆRÇ£ZàÒ¥K ÁÆQPPpvvF`` ¼½½+u1.Þ"J¯Sr‹( lôZu­ÀvãÆ ÄÆÆ"!!ñññHNN.õ [+++tëÖ …nnn°··çJ‰+,,ĺuë„Û·oC&“¡OŸ>˜>}:¯l–SVVV­Z…ððpÜ»wÿüs´k×NêxDDD5ÂÇ‚+V //ºººðõõÅ´iÓ`gg'u¼Z)##Ë—/Gdd$òóó¡§§‡€€Ìš5‹·‚R¹Ü»wáááXµj²²²666˜2e üüü ¯¯_á9X`£×aʤ6ØŠŠŠ””¤êN‹‹‹+µüº––:wî WWWÕËÒÒR¢Äô&OŸ>źuë°lÙ2UG›££#1hРz¹ëÛº|ù2¾ùæ¬_¿ùùùÐÒÒÂàÁƒ1kÖ,888Hˆˆ¨FÊÊÊBhh(V¯^G©.öM›6 =zô:^­pâÄ „††bçÎ(**‚¾¾>üüü0cÆ žS¥¨ÊX`£×))°ØÑËÈår@¤¤¤HårrrÄ/¿ü"æÏŸ/zöì) EÉ÷»äell,úöí+-Z$Ž9"òòò¤ŽMå T*Å–-[„£££ê¿­………˜1c†¸|ù²Ôñjœ‚‚±uëVѳgO¡¡¡!}}}1yòd‘‘‘!u<""¢ZãñãÇ",,L´hÑBubkk+BBBÄÝ»w¥ŽWãáëë+ôôôT?''') ¤ŽHõH\\œðööV]dÖÐÐ G}«ýÔµ›™™™øá‡JmW*•b×®]ÂÀÀ@äææª}öСCBWWWdee©/‹¢¢"Ѷm[1qâDQPP ]ìܹSüù矒f¦êõ矊àà`ѲeKÕwBGGGôíÛWüðÃâáÇRG¬rÅÅÅâĉ"00Píʺ®®®øä“OÄXd&""ªd.\Ó§Oæææªÿ÷6jÔHøúúŠ˜˜˜zQPR*•bÿþýÂßß_˜˜˜¨~FFFbÒ¤IâÌ™3RG¤zîÒ¥Kbܸq¢Aƒªï§³³³Ø¶m›xþüù?_× láááÂÒÒRúHDDDð1T+BlÙ²Em¼––V™æ¶²²Bdd$<==«äؤT«ˆ"11Qµ²gBBîܹ£6FWWÎÎΪ•= š4iR)óSýôüùsÄÇÇcÿþýØ¿?’’’ÔþÝ322‚B¡@ÇŽáàà{{{ØÚÚJº2é½{÷’’¢ú;xâÄ ¤¥¥©å611Á‡~ˆ^½z¡W¯^°°°,/•–——‡C‡©ÎA233ÕÞ·°°€››:tè{{{888ÀÆÆ2™LšÀ233qöìY¤¦¦")) ÇÇõë×ÕÆ4kÖLuþѳgOI”–¨üŠ‹‹±{÷n„††">>РAŒ1Ó¦MCÛ¶mÔUD_¬w 05”)SJÕA>üðC´oßË—/GÇŽ1tèPÌœ9ó¥û)‹´´4Lš4 §OŸF³fÍ0pà@„‡‡ãÁƒUv¼Õ¥dѲý$j¬^½K–,ÁÂ… ÕÞÓÒÒÂèÑ£¡*°=|ø»ví¾}ûÊ5ßõë×qíÚ5œ9scÆŒAnn.>úè#¬\¹7®ðñÔYYYˆW½ñôéSµ1Mš4A·nÝàææ…BGGGèêêJ”˜ê"---¸»»ÃÝÝAAA¸sç<ˆ„„ÄÆÆ"55Uuâ[BGGmÚ´AË–-aeekkkXYY¡yóæ033ƒ™™ôôôÊ•GàÁƒ¸{÷.222™™‰«W¯âêÕ«¸xñ"îÝ»Wês&&&èÖ­\]]áîîggghjj–ûçBDDDUËÀÀ^^^ðòòð×/™GEll,âââ‘‘;v`ÇŽjŸ±µµU{X[[ÃÚÚÍš5ƒ©©)LMM¡££S®€½½}ù~ D5ˆ††¼½½áííøøx„††b÷îÝX»v-"""Я_?J³Ê„„„@.—ÃÕÕµÔ{?~<ŒsçÎaÔ¨QšËÖÖTýù믿†»»{…öYÓÔ¹›¦¦&ÂÂÂ0`Àµ.µþþþøê«¯šš {{{lÚ´ –––ðððPgff¦öçE‹aÒ¤I¥ö— ¸téΟ?'Ož`È!7n¶mÛV‰GV{\¸pA­;íÅŽ™L;;;U‘@¡P M›6&¦úȾ¾¾ðõõäää !!IIIHIIAjj*ÒÒÒššŠÔÔÔWî§aƪ]MMMèèè@__À_ßu!Š‹‹‘““௫Ø%'µoê677‡ƒƒär9еkWØÚÚJzE›ˆˆˆ*ÆÖÖ¶¶¶ܹsqqqHNNFjj*RRR‘‘ÄÄD$&&¾r?†††ªsà¯s’’ Ô%ç ………xòä €¿Îuîß¿¯:'y+++ÈårØÛÛ£cÇŽpqqAóæÍ+zèD5ZÉÝS—/_Ʋe˰aÃÄÄÄ &&Fõ÷¬®iÓ¦ &L˜€ ”zÏËË 'NĈ#пÿ ß)sáÂ4oÞúúú8rä°wïÞ í³¦©s6ðôô„§§'fΜ‰)S¦¨½gccOOODDD`ùò刌ŒÄ˜1cJýš••U¦VGÀâÅ‹¡§§===Ìž=[u…ª®+((À©S§TÝi ¸ÿ¾Ú===8;;£[·nP(P(011‘(1ÑË©ns(QXXˆôôt\¹rEÕ]–™™‰ëׯ#++ YYYÈÏÏG~~>nܸñVóÉd2˜™™ÁÔÔï¼ólllÔ®P·mÛæææ•}˜DDDTÃXXX`РA4hj[^^ÒÒÒTç%î7oÞT]¨ËÍÍEnnn©[NßD[[[U˜³´´„••¬¬¬Tç"íÚµC£F*ù(‰jV­ZaÕªUøüóÏŽU«V!++KêXUfÁ‚ˆŠŠ*µ]GG#GŽDHHV®\ùÒÏ«ýÙÕÕ¿þúëKÇîÙ³!!!xöìÚµk‡èèh8::Vüj:÷ ¶’{€ÓÓÓ!—ˆ‰'ªÝ¼}ûvŒ?»w‡nܸ¡ªÆ¾í½ÄEEE011AFF†ªª}ðàAôïß¿ÔmµÑ‹Ï`»{÷.âããUÝi§OŸÆ³gÏÔ>caa÷ß_ÕÖ¹sgIŸcET•òóóU'ºEEEjWŠ…ÉdÐÐÐP=—ÄÀÀ@uRËN4"""*¯ÜÜ\Õ9ð×9IÉyyÉ9Èß;댌ФI>+è-åçç£{÷î8uêT­U:û ¶ÕÕꨩ© ???Ì›7aaaÈÏÏGpp0ú÷ï_‘ø5Îܹs‘––†ôôtµíËåxÿý÷¡P(àêê ‰RU¿† ¢aÆxï½÷¤ŽBDDDõˆ¡¡! aee%u¢:­aÆhݺ5N:%uªáêl ¨¾VÇàà`üë_ÿ‚¥¥%”J%z÷îï¾û®âPƒÄÄÄôõõáââ¢ZŒ@¡Pð*Õku¦ÀæääTê6WccãWÞ+½téR,]º´Lûy]]]¬Y³kÖ¬y«ÏÕ&3gÎÄàÁƒÑ¡C‡2/ÃKDDDDDDDT°RBe2|øpÈår©cÕ8R """"""""ªÍX`#""""""""ª؈ˆˆˆˆˆˆˆªHß¾}áïï_j»­ZµBXX!“Éàìì¬ö\ø’íÏŸ?é¾oݺ…>}úÀÄÄä¥ã Ô^:::hÔ¨‘êýéÓ§£]»vÐ×ׇ‰‰ † †;wî¼òXÞ4üðÃh×® ñÞ{ïa÷îÝåÚß³gÏðÁÀÂÂ022‚§§'JíçùóçèÔ©ÓkVeÍ_^,°U‘±cÇbÛ¶mÈËËSÛ~äÈܼy#FŒPm»zõ*¢¢¢Ê¼o 8+V¬xéûyyyj/www >\õ¾*[zz: ðÉ'Ÿ”{¾¨¨(,]º;vì@nn.þøãtèС\ûÓÒÒÂÊ•+qíÚ5äååáöíÛpssCÿþýK]¾|9Œ_9OYóW lDDDDDDDDU¤_¿~044Ä–-[Ô¶GDD`àÀ055Um[¸p!fÏžüüü2í»iÓ¦;v,lmmß86##‡F@@€Ú|r¹2™ fff˜6mâââÊ=ß¼yóðÍ7ßÀÎÎФIX[[—kšššËåÐÕÕÈd2hiiÁÆÆFmܵk× ¼úà˘¿"*\`c«cÕµ:Äp ¢IDATVE~"""""""ª>ZZZ=z4"""TÛ>|ˆ]»vaìØ±jcýýýabb‚%K–TzŽˆˆtîÜ;w~嘃ÂÑѱ\û¿~ý:®]»†3gÎÀÚÚfffðññÁÇË0tèPB__ øý÷ßÕÞŸ4i¾üòKThžŠªp­ŽU×êXÙù‰ˆˆˆˆˆˆ¨úùûûãÔ©SHMMlÚ´ –––ðððP§©©‰°°0„„„àæÍ›•6ÿóçÏñý÷ß«u¯½hÏž=X¶lÂÃÃË5Gvv6àÒ¥K8þ<ÒÒÒpûömŒ7®\û+±uëVäææââÅ‹ÈÊÊB`` ê½Ÿ~ú J¥C† ©Ð•¡Â6¶:V]«ceç'""""""¢êgccOOOU[dd$ÆŒ™LVj¬§§'<==1sæÌJ›ÿ¿ÿý/òòòÔš’þnÇŽ5jvíÚ''§rÍQÒA¶xñbèééÁÌÌ ³gÏÆÞ½{ËûïÚ´iƒÅ‹cýúõ€ÜÜ\̘1+W®¬”ýWT… llu¬ÚVÇ¿«H~"""""""’N@@6mÚ„ØØXœ;w£FzåØlß¾'Ož¬”¹×®]‹aƽô6Êï¿ÿcÆŒALL zöìYî9¬­­Ñ¨Q#µ¢¡L&S{TXEA[[pñâE\¿~...033Se·°°À®]»*m⪔EØêX~¯kuü»Šæ'"""""""éxyyACC#FŒ@ÿþýaaañʱmÚ´Á„ Êôàþ‚‚øëyïjï_¿~¿þúëKk&+V¬@`` þ÷¿ÿáý÷ß/Óq¼j>MMMøùùaÞ¼yxöì>|ˆààà—®úY–ý%''ãàÁƒª÷ÒÒÒ0wî\øøø:t耫W¯")) IIIˆŽŽœ>}½zõzëù*ªR llu¬¸[ÿ®2ò‘tttt0räH\½zcÆŒyãø ”©ûKOOnnnþª]èé驽‰:¼´žðïÿ?F=Ô‘,**œ9sFíÏoš/88EEE°´´„••ÌḬ̀fÍÕûo³?¥R‰™3gÂÔÔèÓ§z÷îo¾ù ­­fÍš©^æææKKKÕ>Þ6EhUÊ^ðW«ãøñã1xðà2µ:Êår¸ººVÊÜojuœ6mbbbÊ\}™ênu,QYù‰ˆˆˆˆˆˆHZK—.ÅÒ¥KKmwrr*U_066FVVÖ÷ù¦ºÄÂ… ±páÂr}¶S§N¥µ|Ýgtuu±f͵¢Zy÷çää„ÄÄÄ׿{qü‹ûzÛüQ)l[ßvoju¬ìüDDDDDDDDT5*­ÀÆVÇÊmu¬ŠüDDDDDDDDTùdPu-rT»988àìÙ³HII\.—:QµòññAtt46oÞüÊg¿SýŒY³fU^Q}ÄQ°ÀFDDDDDDDDT,°U lDDDDDDDDDÀQ°ÀFDDDDDDDDT,°U lDDDDDDDDDÀQ°ÀFDDDDDDDDTZR """""""ªé|||°lÙ2©cP súôi,°•II1…èE2„G¡šÈÁÁgÏžEJJ är¹ÔqˆˆˆˆˆˆˆªÕÕ«W‘-u ªáTlNNNRæ êìÙ³RG """"""’Œµµ5¬­­¥ŽA5œªÀÆ6G""""""""¢·÷½|Â1ËnàIEND®B`‚frr-7.2.1/doc/figures/fig-vnc-redundant-route-reflectors.txt0000644000000000000000000000000013610377563020771 00000000000000frr-7.2.1/doc/figures/fig_topologies_full.dia0000644000000000000000000004363013610377563016145 00000000000000 #A4# #RF2# #RF4# #RF3# #RF1# frr-7.2.1/doc/figures/fig_topologies_full.png0000644000000000000000000002045113610377563016170 00000000000000‰PNG  IHDRÞÞÝ)àsBITÛáOà pHYsÐй‹çŸ IDATxœíPgþÇ?›AÐ 2,§ 7sP¦¨œµéÔ™ÖÚN¤ˆmVë9zŽõ¬Ž uŽŽ:w8Úûë0SF«-\ýŠ”V-ïz¤*Ò±TÐk¥"Ü8õ=F"‰ !ÙçûǃKØìn6ÉþLö5ù6Ïóìgó~?ϳûì³Ï!›‘‘«ÕÚÑÑÑÛÛÛßßß××çt:Ýn·ËåJHHHNNNKKKMMÍËËËÍÍ-((HJJ §’!‹¾D8Ö¼uëÖáÇ[ZZÚÛÛù—£Óéžxâ‰%K–”””ää䄼w ±‘WßP¬I’dSSÓÁƒ[[[ñ½^_PP°hÑ¢ÌÌÌÌÌÌììl“Éd4M&“Íf¼}ûöÕ«W»»»;;;ÛÛÛI’‚ Ìfóºu늋‹cbbB> aQо(H’¬««›1cÎWVVÖÚÚJ’$ÿB\.Wssóš5k¨p³²²¼^oPÁhŽ¢ô š?þ8ÞYzzzmm­Ãájg4GMMÍܹsq™yyy}}}á¨JÓ——5Ýn÷Úµk ‚€iÓ¦ÕÕÕU¸ñx<‡JOOÇGEE…Óéªp >(SßÀÖ¼víÚc=±±±Û·o³&±át:·oß®×ë`Þ¼y?ÿü³{ÑðG±ú°æ… ¦L™‚O.\¸ Pœ¬\¾|yΜ9œœüÕW_‰½; %ëËeÍ'NLš4 ž{î9»Ý.h¬Øl¶%K–€N§ûä“O¤Ùit¢p}Y­ÙÜܬÓé`ýúõ_;{½ÞU«Vá.æÄ‰Rî:zP¾¾ÌÖ|²³³e?yW#jÔw̚ǀ9sæ(vfÚèè(>_nhh;õ¡F}Ǭùä“OÀ{ï½'_lyÿý÷à©§ž’;õ¡F}!ÔÓÓ‰‰‰ ;¼wï^||Ä"¾øBЈ¤%ÔàUªï¸5‹‹‹©DsçÎ%X0™LË–-kjj€0–ÆÔ©Sýóz<žÎÎοÿýﯼò >y§X¾|9œ>}:èã¶Záé§áòå 3*‡Ë—áé§Áj 6M_YÄ>räÈòåËõ«_FƒÁ‘‘ñÆo\¼xÑ·ä ú 1iÒ¤‘‘ªiåù ÜâÅ‹].cãÌóW›2eŠo®;w>óÌ3ñññ¾i| ÇÄÄ ›ÍÆ·ŸhkCf3@))Hª©_¢`·£”€ÌfÔÖÆ3“¿¾Ò‹{çÎÉ“'3¦Ñét JöÕ×€‡àóóócccýs^ºt‰ú›$I»ÝÞßßæÌüÒ_|±gÏž;v°—‘‘qòäIŽèñ¼,Š?ÿùÏ^¯×`0þðÃÃÃôô“'O.,,<þüÙ³gñíW.¬Vصk¼™)/‡‰¦WññP^[·‚Õ V+˜Í°c˜ÍÜ™8ô•L\·Ûm·Ûó›ß¼ñÆø±L‡Ãñù矗—— mذáÑG-**‚‰úBUU¼õÖ[¾Õ‚ªXlu±±±'˜={6GÅÊÉÉáY¹1»wïnkkÃcÂl1lÙ²ªªª¸ ¢ZJê£ö&C5œÔ'P ꯯ôâÞ½{÷ôéÓþwò>ýôS\ÔÒ¥K©”¾ðòË/ƒßHlÀèI’œ>}:èõzA¢÷‡-†ºº:X±bs6SâOuuÈ‘(‹êj†£c7¨¿¾Jãõzñô¼äädj#¥¯îÆ€oñ‡ |Fˆg¤J ~xôÊ•+ô/ð…ã…BJ ¬_/Ep°~=¤¤Ð7²{úJ&®N§KII€ÁÁAj#¥¯îæÍ›ššT¡ׯ_ü0ž”¤¥¥À/¿ü2¾‰Ã”µŸeú‚Ï8aúBÐW2q=Ï­[· ŧ²ë‹/h¼Ümþèè諯¾Š?~\¤6Ÿ-lʤ¤$„Ø»oíó ‹÷×W âb¨AÀ²²2} xøÊ`0p!tÿþýëׯ···8pGmÞ¼ù¥—^âÈåõz‡††Ø¾5! '$$@¡ÓÚ _´ðà*þ·Ok }e×ëõâËÿ˜˜˜·ß~›ÚŽõu»Ý´‰R‡¾RSS?úè#ŽÙ“ÜÙ1Û¶mã¨Rl•ÛårÀóqqZ{ɧí|Öd¢é«qBúÓŸpÊšš}tèlœ={–{ÇáG¯uèaš2`‡.£¸ï¾û.Nö—¿ü…M_ÈÊÊ€Ÿ~ú‰Ñw'‚§£@NN÷ŒT*÷ArÀf;¾>ÈÌÌßР3r„a?ò3%Æ__yÅ%IrçΠÓéhí%†Ò,XV«Õ÷k6[$I݃ٸq£HÑsÇpîÜ9((( gà0hdŒ·cüGÝYL‰ñ×WFqI’Ä#êñññŸþ9cJ_¾Vǃ!¢¶¶Ÿ¨îß¿ÿË/¿ä“KXðˆ&µ4Þ8f3´µA[ý»¨©‘"8 ¨©úFöc篯Øâ"„Þ|óÍwß}wòäÉÿüç?ŸþyÆd”¾ºÜÜ\øî»ïxî`æÌ™{÷îůZµÊf³ vàÐYO˜ØDÚ·у‡öí›°…£B@PúŠ*neeemm-=ztþüùlÉÆõ=sæ ù6ªÜC_^¯wáÂ…8ÁÊ•+…móÆ€{¨“'O.‚ÖÅGÀ§ïY&¿ùGþúÊ".u_þøwJJß“âØò÷ôôPw±×ûÉšÚ¤8þ¦ÄpLŠcË"¸¸wîÜy衇@¯×߸qƒ#¥¯¾€*,,€ææf*EÀèB{öìÁi¦OŸ~çÎ0£Ç8}À1€ïF< kþüùA‹Ðƒªºá¬®Ê”4}¥wÛ¶m8KFFÆ,à”§N¢ô*ß¹<|¢Å«ç€Åb¡Ð†fM¼j-Ë–-€]»vUì8--!fT—/‡–¦¯ôâ®\¹’[V*˜+VPú2?¶Æ'z„Ð÷ßO™‰2~hÑcZ3..N{l-XhúJ/.OkÒ[Óö Ô¨¯¶DBT F}µ…e¢5ê«-Ç-¨N_mÃhAuújK¿FêÒW[0;ºP‘¾Úk¢ 髽œ%êP‹¾Ú+­¢Uè«ì '¶lQ÷t!eb·7oÞ¬#™õ áE€ù_¯¹i@&²XP}=â?Nƒ› Õ×#‹™LàÄ–-ª|}*Fæ—ÛløGûh ‰Žÿ1m6µ¾t#ó«Ü-†ç³4òÑ‘ÔÇbÁ©dÖ—ÀÖD¹Ýîµk×Ó¦M«««ð®ŒÇã9tè¾O¥×ë+**œNçø×õõ\¶jõ‡Û‘Ô§¾žÊ!§¾ìð²&¦££ßé€ôôôÚÚÚ0k˜Ãᨩ©¡žÌËËëëë£'¢õéšGÙàéHŸÞœV€<ú²„5B$IÖÕÕ͘1ƒšØ[VVÖÚÚT%s¹\ÍÍÍkÖ¬‰‰‰Áådeeá•p™30öél£qÌ£üª¦êq:Çi4ñ+=èÍiÈ£/ â½47I’MMMlmmÅ[ôz}AA^ 9333;;Ûd2F“Éd³Ùoß¾}õêÕîîîÎÎÎööv¼Af³yݺuÅÅÅÔa0ÐЯ½Æ7¸œ(-…ÒRÈÍ ö¸ÔJw746Bc#ôôðÍR_eel_J­/A™ÆÍ›7wïÞ½`Á|šÂNWTTTUUuåÊ^»áÓ§çä ÊJÔÕÎ᨞®.TY‰rrBèÍ‘H_Bi5ý±Z­½½½ýýý}}}N§ÓítºÜî„ÄÄäää´´´ÔÔÔ¼¼¼ÜÜÜ‚‚‚¤¤¤àv°t)|öÃv‚€¥KaçÎ(j#ùÐÝ ;w§Ÿ£¸ ½« =ª¹sþ¾öÕë¯mƒêëÇk‰0/f*Øç Ú"íBvþïÿÆÝ z=€×;öodûR|}µV3 hm'eJˆt_J‚fÍðÀæ++›àK½^óeøèä@ý0vg‘z#!š5ÃãèQxýõ M&x½ðúëpô¨L1Eš5ÃûÒ÷2_ ÁƒkvÍa Y3Th¾4 ¡Àðàô]sgxh—A!áïKßëÿñÎW^‘#J•#ÒP~$ß x¿Gö•j$@»Q©8xÞ‡ŒxwjÖTAÝlwjÖT!ÌÛˆ`wjÖT !Ï'ŠTwjÖTaÎs‹HwjÖ”Aæ_Fž;5kÊŒ€ó‚#Ìš5åDðùê‘äNÍš²!ÒsãNÍšò êó=‘áNÍš2 ÁsgàNÍšR#Ùójw§fMI‘ø9]U»S³¦tÈòü¸zÝ©YS"d\×@¥îÔ¬)²¯·¡FwjÖÙ}‰Q;5kŠ‹B|‰Q—;5kŠˆ¢|‰Q‘;5kŠ…}‰Q‹;5kŠ‚b}‰Q…;5k Â}‰Q¾;5k Œ*|‰Q¸;5k ‰Š|‰Q²;5k †ê|‰Q¬;U`ÍË—Qu5r8ü fÝá@ÕÕèòåpwÍ•ú#¯;üQ.}…°¼ÙŒRRPu5²Û} f ÝnGÕÕ(%™Íì—'ªö%F^wʤ¯Ölk Ñ÷h¡SAã-mmì—àKŒŒî”I_NÌæñ@ñø†î4€tMfÄø#£;åÐW kR‹ÏGš&3Â|‰‘Ërè+Üå•oÅâøHÓdF¤/1r¹Sr}…³&ÏŠ%A“Á¾ÄÈâNÉõtP*`Å ¿JùbЈx_bBpgÀŸ. èëƒ Ö X±Â¯Rõõ¨«‹õÛ(ñ%&(wvu¡úúp÷(¾>=”ÏQ±©R ª¬dþ*ª|‰áïÎÊJd±°G±õõAhkrT¬ð«”͆L&”“ÃðUúÃÓ99ÈdB6[¸»U߉ˆp”±b R¥êëÇJ£õéQëKL@wvu}~ŸŽÄÔw""X“±b R¥,–±Ò|ûô(÷%†Û••cÛéÓÅÓw"âL¡U,AªîÍqTŸ®ù’‚Ã99céÓ‘8úú!Ž5iK*EõæTŸ®ù’£;©Þ\À>] }ým2%U±„ªRToŽ?ÅÅš/ðwgqñ„ßM>‰ ¯¢Y“ªX‚T)ßÞBó%34wúþPöéÂêË„˜SÐÍfÁª­7÷ýh¾ô‡æNÚG> ª/¼>uddÄjµvttôööö÷÷÷õõ9N·Óér»z(999---555///77·   )))¸462o'xñE˜7/üCˆ(æÍƒ_„O?„¾ml„²² ÊcÔ·Ðét»Ý‚èËH8¾¾yóæîÝ»,X@ÿ=êtº¢¢¢ªªª+W®ðÚoîÿÉÉA••\÷0£®.TY9~=ÎöáݧK¤/ b¬Xœ$ÙÔÔtðàÁÖÖV¼E¯×,Z´(333333;;Ûd2F“Éd³Ùoß¾}õêÕîîîÎÎÎööv’$€ ³Ù¼nݺââ☘Öý54Àk¯ñ .'JK¡´rsƒ=.µÒÝ ÐØ==|³Ô×s4œRëËFPF&I²®®nÆŒ8o\\\YYYkk+I’ü q¹\ÍÍÍkÖ¬¡ÂÍÊÊjhhðz½Ìh׿Ü£Y,¨¾9AšZq:Q}=²XÑįÄr.¾,aÍŽŽŽÇï,==½¶¶ÖÞ<+‡ÃQSS3wî\\f^^^__=ŸÞwRØ‘‚\~ª›mÌ£<.¿J}ÙáeM·Û½víZ|Â1mÚ´ººº ª7çСCééé¸ã¨¨¨pú6x׿š#áéQŸët9õe'°5¯]»öØc@llìöíÛìIl8ÎíÛ·ëõz˜7oÞÏ?ÿ<öco®9’Ü}Чˬ/;¬yáÂ…)S¦àÓ… .'+—/_ž3g$''õÕWôÞ\sdh0zÔdB6›ÌúrÂeÍ'NLš4 ž{î9»ïãñbb³Ù–,Y:î“M›4G ÉDžØ²Ef}?ù„#%«5›››u:¬_¿>Øk«0ñz½«V­€XƒáÄ–-Hª_-Š°Û›7oÖ„ÌúÆÆž8q‚-³5Ï;g2™pÜ¢E€ŠŠ 0 [~`Q…¾ ÖìííMLL”7nÌêÕ« ))©¿¿_ÞH" µèK·æÈÈH~~>X,Ç#E€ìx½Þ^xòóóïß¿/o0‘Šô¥[³¼¼fÏž=<<,U„\ܽ{_ÓmÚ´IîX"é;Áš_ý5Aƒá›o¾‘0¼\ºtI¯×ëõúo¿ýVîXÔºô·¦×ëÅ÷©ÞyçÉà ÀÖ­[ñ.ïRDªÓwÜšGŽ€™3gŠt? \.¾Óõá‡Ê‹ZQ¾cÖôx<<òˆ’µ?|ø0dggË~ò®FÔ¨ï˜5?sæÌ‘xô•?£££ø|¹¡¡AîXÔ‡õ³æ“O> ï½÷ž|±æý÷߀§žzJî@Ô‡õ„POO$&&*|ìðÞ½{ñññA\»vMîXÔ„JõÕ@ss3,]ºßìW, %%%¡úúú‹øÏHZB ^¥úŽ[³¸¸˜J4wî\‚“É”‘‘±lÙ²¦¦&ü#lÙiL:•ÊrìØ±5kÖ<úè£S¦LÁ;ÊÊÊzùå—O:…<À´|ùr8}útÐÇmµÂÓOCKKЕCK <ý4X­Áæ£é+‹¸Œ|ðÁ8%5~‚¾CCCALš4idd„jZsrrøóâÅ‹].cãÌ';L™2…O–åË—ã ·ááᘘƒÁ`ã?A®­ml©‰”uOb²ÛÇ^4a6ó_˜À__YÄõ§»»›jÅs,b嫯ÁçççÇÆÆú—~éÒ%êo’$ív{ÿ™3gðSH_|ñÅž={vìØÁ\FFÆÉ“'9¢Çóî0?üpiié³Ï>›——7uêÔÁÁÁ–––íÛ· ;vláÂ…o¾ùæäÉ“ ÏŸ?öìY|û• «víofÊË!>>@%åå°u+X­`µ‚Ù ;v€Ù̉C_)Å¥a·ÛKKKN'm»¯¾PUUo½õ–¯£©ŠÅfùÆkÌž=›£bå0.ÓÊã€eSS.Êü`¡ˆ-[¶@UUWYTKI}ÔÞdb¨†“újAýõ•E\_H’üÝï~³fÍJMM¥Eé«ëîîütJJJ¦OŸýýýAeä?8BcñâÅøü>/éa{æŸSúŸ“©½ÉÄà†Ó¶ã}@úŠ!®/uuuÿøÇ?bbbŽ;–@û–ÒWwãÆ À·‰øCD||<à©âqÿþ}üǬY³ð8ô+W®Ð“rˆ”’ë׋¦„¬_))ôìÇ‚¾¢Š{ñâÅ7À¾}û üPúênÞ¼ ¸]åÏÀÀÀõë×!øæ6Xð¿ÿýïñiiiðË/¿Œ' ÔrDH“‰ño8)˜~‡ôOÜ{÷î•––ŽŒŒ”””üñdL3®ïäÉ“€vÁË}:2::úꫯâÇãt!äõz:d4àí·ß¦æ¤`S&%%!ÄtN©}&žƒúë+—¸$I–––@VVÖÐÐo0¾EQúðð•Á`É8„Ðýû÷¯_¿ÞÞÞ~àÀ‹/ÀæÍ›_zé%Ž\^¯whhˆí[£ÑHþïÿûÙgŸ9Žk×®ýë_ÿº~ýúoûÛ]»v=óÌ3T|vâv:Cä‹\Å“££H_iÄ­©©illŒmllÄO32¦¯Û qqq@›(pè+55õ£>â˜=ɳmÛ6Z.Ú`ĬY³þö·¿ýïÿóMãr¹ !.Nk/ù´qF#M_YĽpὪ­­õwšo«9¦oBB€³gϲÅŽ5Μ9sòäÉšššÒÒR\׎ú,îªuèQÝ¿ÿ—_~ɯ¡a6›§M›íííx Ѥ&ø&…¶6hkc¸w7055"G*55ðàîÃ8ìÇÎ__‘Äå˜&Â¥¯.77¾ûî;ž9gΜ¹wï^ü÷ªU«l6[P;j‹ÝnÇ[pè¬'Ll"íÛ‡˜‘J‚ÃûöMØÂQ! (}ÅwÞ¼ywÙÁifÏžÿÝ¿ÿ¸¾gΜ€¢¢"ßF˜{èËëõ.\¸'X¹reøm>‡_Ùeddà-¸‡:yòdàÌ´.>Î8}Ï2ùÍ?ò×W9â2Eé`R[‰===Ô],Æõ¾Bˆ~ttÔ#U‰7lØ€´Iq‚NŠcË"†¸lЊòÕB………ÐÜÜLe=BhÏž=8ÍôéÓïܹ~ô3fÌØ°aéS§nݺ5<<|éÒ¥òòr‚ ࡇ‹…ž:u æÏŸÏ¿Ø1°AUÝpVWeJ š¾²ˆË­(_}ŠcÅŠT>ÑŽŽâÕsÀb±ÐÆ«BˆXxøá‡;::pš+VÀ®]»ø;––3*Pƒ§é+‹¸lЊòÕ—ù±5>Ñ#„¾ÿþ{j&Û|fô{÷î]¾|ù¯ýk<í%11±¨¨è¯ý+u¿U{l-4húÊ".¾EÑôÕö Ô¨¯¶DBT F}µ…e¢5ê«-Ç-¨N_mÃhAuújK¿FêÒW[0;ºP‘¾Úk¢ 髽œ%êP‹¾Ú+­¢Uè«ìr¾(N#”¯¯²_ŸÊùzM0Q¸¾Ê~é´†È(YßÖD ~•»† (VßÀÖD¹Ýîµk×âY½Ó¦M«««ð®ŒÇã9tè¾O¥×ë+**œN§P…kðA™úò²&¦££ßé€ôôôÚÚÚ0k˜Ãᨩ©¡žÌËËëëë §@pPš¾AX!D’d]]ÝŒ3ðÎâââÊÊÊZ[[ƒªd.—«¹¹yÍš5111¸œ¬¬,¼nPÁhŽ¢ô%ï5[Î/ÀIDAT¥¹)H’ljj:xð`kk+Þ¢×ë -Z”™™™™™™m2™ŒF£Éd²Ùlƒƒƒ·oß¾zõjwwwggg{{;~6™ ³Ù¼nݺââbê04dG!ú†bMŠ[·n>|¸¥¥¥½½9:î‰'žX²dIII Ï%ï5dA^}ò&ÅÈȈÕjíèèèíííïïïëës:n·Ûår%$$$''§¥¥¥¦¦æåååææ$%%…¿S ÉEßÿ]Žy,Þo‡(IEND®B`‚frr-7.2.1/doc/figures/fig_topologies_full.txt0000644000000000000000000000010613610377563016216 00000000000000(RF1)--(RF2) | \ / | | \/ | | /\ | | / \ | (RF3)--(RF4) frr-7.2.1/doc/figures/fig_topologies_rs.dia0000644000000000000000000004162513610377563015631 00000000000000 #A4# #RS# #RF2# #RF4# #RF3# #RF1# frr-7.2.1/doc/figures/fig_topologies_rs.png0000644000000000000000000002341213610377563015652 00000000000000‰PNG  IHDRÞÞÝ)àsBITÛáOà pHYsÐй‹çŸ IDATxœíiTTGÚÇÿ½ÙȦ²$€‚,¢ Ñ F='NŒÑ9ˆÄd—à”1ñ&2‰Y4=&šxrBDÜ2\"&Îà‚‘Œ¢Aƒ¢, .¸°JÓtßz?v7M¯ÐÛí¾¿ÓºëÖ­ûÜú?u»n­ûì”)SÂÃÃ{|usc]}{âš Ãì߿˖-ùùù4D DGGOœ81((((((,,ÌÉÉI,;99566Þ¿ÿîÝ»7nÜ()))...,,dÇ›0aBrrr||¼H$êñ=p˜[Ñ—Ã0YYY>>>ô\ggç™3gæçç3 cx"‰$777))Ianppð®]»är¹QÆp˜›Ò××,**5j½X@@@fffkk«QS£µµ5##cèС4ÍÈÈÈÊÊÊÞ$ÈÑlM_ƒ\S*•Ο?ŸÇãðððÈÊÊ2ªéF&“eggÐ?ŽeË–µµµ™*qC°M}õ»fEEÅÈ‘#ôéÓ'--­—%Immmiii@DDÄ­[·ÌqŽîج¾z\óüùóýúõ£Õ…óçÏ›ÈN­\¹r%44€——×É“'Í}9[ÖW—kæååõíÛÀ‹/¾ØÒÒbR#µÒØØ8eÊ|>ÿÀ–¹¨cbãújuÍÜÜ\>Ÿ`áÂ…~w–ËåsçÎ¥1yyy–¼´ã`ûújvÍÓ§O;99Q»Íf¡–-[@(rÿì&‡újpÍòòrwwwëÚM™7oOOϪª*ëZbO°E_u×looŠŠ'“É,a värùÔ©SDEE=zôÈºÆØ,ÒWÝ5—.] $$¤¹¹ÙRêâáÇô.55ÕÚ¶Ø,Ò·‹kž={–Çã …ÂsçÎYÐ<=\¾|Y ‚_~ùÅÚ¶°vé«tM¹\Nû©V¬Xaqóôðî»ïÒž.öR8¬ÓWéšÛ¶màïïo¦þ€Þ ‘HhO×öíÛ­m [a¾®)“É dËÚçää ³zå°QßN×Ü·o€ÐÐP›™ÖÑÑAëË»ví²¶-ìƒúvºæsÏ=`Ó¦MÖ³M?ß|ó €ñãÇ[ÛöÁF}A)++àîînãm‡MMM...<¯¢¢ÂÚ¶° –êË›› `Ú´i´³ßfqssKHH „ìܹÓÚ¶° ¶êK‰‰‰››«ð_³Äbq```BB¾}ûtT\ ´¦_¿~ÝÏíèè¸páÂÆ_}õUÕC‡k¢‚ê¨ékq›ššrrrf̘ܧO@øÆo\¼xQ5eU}ÑÐÐÀãñúöíÛÞÞ®ˆaàD¸É“'K$SYOùøãŸþyÕ8ªš››E"‘P(lll4TǦ»¾–·¶¶ÖÕÕUc>Ÿ¿yófEʪú i|TTTŸ>}ºŸyùòeÅw†aZZZªªªŽ?Ng!ýðÃkÖ¬ù裴7xðàC‡é°žŽËR°jÕ*¹\. cbbJKK›››Õ⻺ºÆÄÄœ9sæÔ©S´û•C7:ôµ˜¸R©´¥¥å©§žJLL¤Ó2[[[9²téÒ†††E‹=ýôÓ£GFW}‘žž`ñâŪÅBQ°´•Ž{÷Ò!!!: Vxx¸œÕ«Wÿ÷¿ÿ¥mÂÚlxçw¤§§•²ÃÒ]_Ë‹ûðáÃcÇŽuïÉ;xð MjÚ´iŠ@…¾Â’’tvˆá$$$x{{×××WUUu¢nÒÒÒôÆ¡3ôè['‡^z ¯ÉÅíß¿ÿ¤I“º‡ÿõ¯uwwojj:sæŒ"P¡/ÿöíÛh7‘áðx}ºŽ³äryCCƒ¶£b±¸ÀÔt©Tj쉎‰!úZE\¹\N_ÿE"ÑòåËáJ}¨ ”ÒÛôåçç·cÇ£'uŸNyï½÷t¼Öi{‘”H$ô {;ttºëk âBÞÿ}3##C5\¡¯6>Éd2C®§`÷îÝtÄ€å¡[a9ºÓ}- îúõëi«Ö§Ÿ~úüCõRßàà`ׯ_Wõ\EÁzØ:@xx¸î©Šhº‹Ž´=5+++õ8e‡¢»¾Ö—a˜?þŸÏW{^RúbìØ± Tks †a}0)))f²^· §OŸÝ㔊îúZQ\†ah‹º‹‹Ë‘#G4ÆQè˧ïê´±@/</33“VT7nÜxâÄ CÎ2-´ES±4‡n ××ÜâBÞzë­/¾øÂÕÕõÇ|饗4FSèË1b€ .xÿuëÖÑïsçÎmll4…ÙF@MçVÚ6£ô5«¸|ðAff&€Ý»wÇÆÆj‹¦Ð—ÿç?ÿÀÿþ÷?ï‘””4nÜ87oÞ\¼xqoM6’¢¢"<Îq½«¯™ÄÝ·oßêÕ«,X°€.Ç¥ ¥¾ºÅi«4”••)z±4®÷E™¼®É Š3ƒâ´brqkkk @ ܾ}[GÌ.ƒâúõë]TTtôèÑiÓ¦XÂÂÂ>þøcÚRº`Á‚1cƨö5õÚ¦E¡mÅj±±±tÕ½ô@_“‹»~ýú‡4hÐñãÇ5ÆILLpòäI¥¾„5kÖ˜3gŽÂyõ,BHGG]=@\\œZ - 7ö©IW­ÕÁË/¿ à“O>1*YGM_Ë‹ûÆoèu_sΜ9 }5O[3ÄzBÈÅ‹δuëÖÞXOÑëšÎÎÎÜ´5cQÓ×òâèšjÓָɾõå–Hpب/·°ŒCÀF}¹å¸ÖéË-bè(°N_néW‚]úr f;,Ò—ÛfÀ±`‘¾Üæ,[ôå¶´rDX¡¯mo(æmÛfÉK;¹ÙÙ|ÏÊúö`#@Šõ·×œ4‰ˆÅ$5•Ô×[æêA}=IM%bqÞäɬÜ>•båM‰7l ˆ»;YµŠX*ûì––²jqwïÌÕ غé4Åš[¹=Ú™‰ôããC22ˆTjì©”ddŸ.ùyô(±®¾:Ñïš„©T:þ|ÀÃÃ#++Ë„½22™,;;›öS ‚eË–µµµu«¨è’•ôJvï&\·0 Ù½›„†jÈÉÇc ­¦¯N rMJQQíé™™ÙËÖÚÚš‘‘¡˜YYYÙ%†LFÄb y ‘#É?öæêÁ?’‘#5g XLº¶kZA_áš„†a²²²|||{gΜ™ŸŸoT!“H$¹¹¹III"‘ˆ¦LWÂÕ{øpÍ9K?ù 1 ‰•œ?Oþò]Y7|x÷“¬ ¯vxÄॹ0 ³ÿþ-[¶äççÓ@M×B srr‹ÅNNN÷ïß¿{÷î7JJJŠ‹‹ é¼7a„äääøøxÅm¨“€tYÃãáÕW±jBBŒ½û¤¼+V`ÏèVvútì߯ñˆEõÕQެÆ;wV¯^=vìXZM1>Ÿ?zôèôôôk×®é¿FZ𮢝øˆDdáBRSÓ›Ûa=55dáB"”ciizÓ³„¾ÚéÉS³;íííEEEåååUUU•••mmmR©T"‘¸¹¹yyy 8ÐÏÏ/22rĈÑÑÑžžž†&½m ìêŠ%K°t)m¾eS>ÿ6 ¥ÅÐSrr`À”ŠõÕŽi\ÓŒüü3FևǃŸÂÆðpŒ‡gž±ˆq6Cq1NBY®_GYîÜÑóoàìYh_Bðy×|ðÚŠ`d$Þ{aaˆˆ€³³eͲm=ÂÕ«¸~k×â×_5ǹ–5Ë8øú£Xx{k>ôë¯ 1r$ç—ê8;cäH…ZýÒÛÛÆý,pMj+o©®$‘œŒ[·,l;¸u ÉÉÊŸjËo°a13¶¹¦·7„XÜùóÁ$&âñ40 ñàAçO±vùóá\Ó4„…)¿ðbc±zµ2äÄ lØ`y£lš  º:æêÕˆÅ(CT³ÔféMË“…8x°³).(ˆÐåÎär2qb—>·K—¬m¥ÍpéR—Þ݉ í†io'AAZÛJý°Á5¯^íÌÐ;•·n¥ú1lЀÓÖFþô'e¶xxÕ1>;wv†_½j= … ®ÙÞN„BIÔ:a÷îíÒ½¡sùqG!%¥KžìÝÛå¨\N"#‰PHTÖRµYØàš„ÐP:¸PÄD¥ <9vÌâ–ÙÇŽO™!‰‰â=JBC-nYO`‰k~þ¹æð¦&¬Ã×WëTÿÛ|ÖYm7R_O|}•YLšš4ÇÔ–™6K\SÇxª³g‰P¨”$>^CœæfâãClo­£im%>>DãêññÊL ÉÙ³Z±ÕÅâÔ`Cã¾v;ccñÏ*ææâÛoÕã¬_»wqã†Yl³$7nàî]¬_¯þí·ÈÍUþüç?uõëÈL›ÂÚeÃtt˜å3ÃÕ•¨nWWGÜÜ@ö챞‰&bÏ77RW§ ¼~¸º*o?&†ttXÏD“Á’¤¡;wÂÕµógK fφbWÆO?Es3”•YÇ<Bo¡¹Ÿ~Ú"“aölåX8WWìÜ }û4³»pM!!øòKåÏ¢"¬\ UUøúëÎ@»qM_ª*X¹EEÊ_~i?£ý­ýØ6)ª¯),$³f)CF²¶}½fÔ(åíÌšE ‰@ çµØüxM£¸w‘‘¸s§ó§¿?îÜQþpw‡Å·-41ýú¡©©ó;Ÿ??åÀ+??üú+¼¼¬ešÉ±—?tŠ—¶n…b"Ë­[]%55¡¦Æ*v™†š¥_`¥_òxغ՞üö暆G|¼Ö£¬®nê0>>Ç[ÐKÀæW¹ÖÖΩ0Š91eezæm••a ™grt¸æ8p®®WΑ¢_\\,h¢)a³k64 #99Êv"½ØëS“ÒÒ‚ @÷— ‘˜ˆ?f¯k²ù}à@de¡¤ññ0p¢ôõëf¶Éœh<‡øx”” + šÙ&3ÂfפDDàÀœ=‹ñãõG¶ï§&€ñãqö,@D„ù 2/öÕxtô(ÒÒPR¢5‚PˆÖVôécA›L„T ]U—#ðÙgxé% Úd^ØÿÔT套pñ"¶oÇàÁš#Èd(/·¨I¦¢¼\«_ŒíÛqñ¢=ù%ìÍ5ðù˜=×®aÃÍí|,­nj4ÛË 6àÚ5̞͚ñDco÷Ó‰XŒÔTTT`Å õWT–V7ÕÌvqÁЍ¨@jªrê³}a§®IqwÇÊ•(/ÇÂ…P,¢Çv׉°p!Ê˱r¥}¯:f×®IññÁæÍ(-Å«¯‚Çc±kÒ•DKK±y3¯ÎjÇØ×º^.\à=^šuóç1r¤µ­°暀Q«˜Úަ›;*{»dfoqê P×ä`'œkrØ(œkrØ(œkrØ(œkrØ(œkrØ(ÚxdC‡-ÓÒu$‹}||FõÚk¯ÅÇÇóµ­øã?rrr ÊÊÊîÝ»×ÑÑ!‹ àïïñÌ3Ï<ÿüóO=õ”Ùn‚ÍXs¦±50ü®Ã [ð|òäɉDc _~ùe}cCcbbLh³=Á=5õsùòeÅw†aZZZªªªŽ?N·ýá‡Ö¬YóÑG©uàÀÔÔTB¡011ñÅ_ ‰D---·oß¾zõêÙ³gOŸ>mÑ;aÖ.–Æð»V<5µEØ»w/ÒýhLL =š››«-…G;w΄6ÛÚ‡nÈ]+êšÚ"Bž|òÉúúz@ ë6ÝÉÉ©½½Ý××÷öíÛ½ìi4Üf{‚{Cï9<ÏÅÅ€““S÷£tãe¹\ni³ìÎ5{N]]]uu5€‘šÆªÑÀºººM›6YÚ2»€sÍ"“ÉRSSé£1%%¥{„´´4ú%%%eúôé………ŽöÜ[¬Yѵ†ßu÷× úz^ZZš••õôÓOÓ£K–,aFc _|ñ…j-3 àí·ßþñÇÛÜÅA•²¶–¦®© ??¿;vhóKÊÏ?ÿ<¡Û*Kýúõ›7oÞ… Ln³=áx7l:×ìʶmÛhxxxx«‘¾Ü¾}{Ó¦M±*ÛSשîÐ;›í Ç»aã]S-œa˜©S§ÒC)=Ý}ðçŸ:t(MdÁ‚¦²Ùžp¼îµkBnÞ¼éææFþôÓO=³¤²²’¾$ùúúêŽé˜®É5õÿuëÖÑïsçÎmìÑñAAAƒP[[k:ÓìÎ5{HRRÒ¸qãܼysñâÅ=HaêÓ^öµ»©à\³‡ðùü¬¬,ÚE¹mÛ¶\Õ}ø/¼ðÂÞ½{ÛÚÚ´¥°cÇŽ˜4i’YMe)Üð­èÞ`íڵ˗/àíí}åÊ•'žxBíBÎÎÎãÇ6l˜¯¯¯‹‹‹L&ûý÷ß:´}ûvBˆ››[qqqˆÎ}¨sx‡ÃU® ¿k½ƒâ!QQQ4Z\\œjó»···ÞÌ äÅiƒ{jjŧ&€K—.5ŠŽ0Úºukbb" gæÊ•+………ÅÅÅ¥¥¥üñǽ{÷ÚÛÛE"‘OTTT\\Ük¯½Ö·o_ÚlOp®ÉØhsïá^ƒ8lÎ59lÎ59lÎ59lÎ59lÎ59lÎ59l]½Ã1— föÔûLùóñö(DD€ÕŽx<õ†wÕüì3œ,Z¤ùôæf’“cVMK\sî\²j•†ð¼¼Î?kú™=[k ÇŽ™Ï:‹¢ãFfÏVfGòò4ÄYµŠÌk>ëLKzƒž}ee¨¨€‡‡2ðÎDFâ޽Ο!!(.ÆãÕÿ‘æf<ó ÊË;zyá×_»Œò|ðC† <œ3àØÐxD®^EC¯ì ƒÙ³•~)á»ïÚ/¸¹á»ï uþ¼w³g+gžƒY¯^+žGÖ~l@uu矔‹ ©©é üì³.U«5k¬j¢-±fM—œùì³ÎðšââÒX]mU ‚ ®yì˜2£ß~›BÎ#"‘2pâD{xû6r9™8Q™9"¡kr¿ý¶2 5o6ü¡—–*¿ó ~û 3g¢££3ÄÛ»Kí S¬$ßÑ™3ñÛoøæeÕ,µUØ èÕ«Êïí툋SŽåñÍÊihfÅ×ÙÙʱ,ˆ‹ë2ŒU5Km6¸¦ZW¹½hoÉÑ…©S±h‘ò§Úpwî©i´å£¿?ââÐÔdYkXBSââ´ŽOeƒkÚ|»¦Žù× ž|Æ!"B9÷àÉ'-bœÍP[«œ‚rõ*JKõ¯›`óóñM37¨½½½   ¨¨¨¼¼¼ªªª²²²­­M*•J$777//¯úùùEFFŽ1"::ÚÓÓÓФ )ßµµ¨­Åÿ cÆ =Ýá\óÚ5¬XÂB#N)-5Ü5ͨ¯zózçÎÕ«W;Ö¨å*ù|þèÑ£ÓÓÓ¯]»¦ÿ7vi¥Óñ‰Š²‡ao½áÈehvmܨ7=K諞ü¡3 ³ÿþ-[¶äççÓ@=qâÄ      °°0'''±XìääÔØØxÿþý»wïÞ¸q£¤¤¤¸¸¸°°a<o„ ÉÉÉñññ"E†ÿø¾þZAC‡båJ¼ü2»çW˜B°o>ü׮鉙œŒÌLG,ª¯Î{1†a²²²|||è¹ÎÎÎ3gÎÌÏÏgŒg%‘Hrss“’’æïÚµK®±Ù|Ü8]Eð`²u+‘ÉŒº ûG&#[·’ÁƒueݸqÝϳ‚¾Ú1Â5‹ŠŠFE/™™ÙÚÚjÔÅÔhmmÍÈÈ:t(M322²²²R=’··æœõõ%›6‘ööÞ`ç´·“M›ˆ¯¯æ ôöV‹n}µckJ¥ÒùóçÓ ‡‡‡GVV–QÅH72™,;;; €þq,[¶¬M1 ¶®NCžzz’µkIïrÍhm%k×OO 9YWG£XM_èwÍŠŠŠ‘#GèÓ§OZZZ/K’6ÚÚÚÒÒÒ€ˆˆˆ[·nBHAA—¬ts#~HÌa€ÓÐ@>ü¸¹uÉÏ‚b]}u¢Ç5ÏŸ?߯_?Z]8þ¼‰ìÔÊ•+WBCCxyyÿÀK/‘ˆ$' ‡¡ÜºE’“‰H”7uª•õ=p@GL­®™››Ëçó,\¸ÐØw«^"—ËçÎ  P˜·u«%/í8änÙÂçñ¬¬oŸ>yg‰B´¹æéÓ§œœ¨Ýf³PË–-  õ>ù9Œ…újpÍòòrwwwëÚM™7oOOÏ*;˜Bn3°E_u×looŠŠ'³vS¶\.Ÿ:u*€¨¨¨GY×û€Eúª»æÒ¥K„„„4k›÷mY>|HßéRSS­m‹=À"}»¸æÙ³gy<žP(**ª¦mä._¾¬øÎ0LKKKUUÕñãÇé,¤~øaÍš5}ô‘6ã|èÐ!ÖóU–Ñòõõ1cƤI“"##û÷ïÿþý£G¦¥¥544|ÿý÷ãÆ{ë­·\]]cbbΜ9sêÔ©©Ü’2 C_Kn ÃA¡IDATŠ«FKKËŒ3ÚÚÚÔÂUõEzz:€Å‹«z´¢`isù½{÷Ò!!!: –¢4‚ÆËýû÷Ó¤&L˜@CÞyçéé醧ìÈt××*âªÂ0Ì믿 00ÐÏÏO-)…¾ü’’tvˆá$$$x{{¨ªª2êDЉ#jLž<™~©{¼Ý9¡Gß:9ôÒ}Í!®*YYYß}÷H$úþûïݺ-$­Ð—ûömÚ¶ŒÕÇsqq@G¤šGÑ/ô 5ýšÞ%8=Ð׬â^ºt)%%ÀçŸÝ=‚B_þ;wøé]òª+uuuÕÕÕ0þqk,»ví¢_þþ÷¿Ó/P__oÖëÚ =Ð×|â655͘1£½½=!!áí·ßÖG©¯««+µ^ÝÕ‘ŽŽŽ×^{FØ·oŸ9ª#„¹\ž-‹,_¾\Ñ~DÒÓÓ³Ç);Ýõµ–¸ Ã̘1@pppÃãÛÔÕ¤ú éò4B¡ž%ã!=ª®®.,,ܼyó¥K—,Y²dúôé:Î’Ëå ÚŽŠÅbµFàßÿýßÿþwkkkEEÅO?ýT]]=f̘O>ùäùçŸWÄ¡µ©TªÛ`Š!úZFÜŒŒŒ½{÷öéÓgïÞ½t†±F”ú:;;P(¥·éËÏÏoÇŽ:FOê>òÞ{ï©¥Ö¸~ýú{÷î©Æ‘H$ôŒ*²Kw}­"îùóçiëUf×Ý]»?5úòiã“L&3äz vïÞ=kÖ,£Ö¶3„˜˜˜ãÇ:t(##cÆŒ·oßþ¿ÿû¿   ={ö(â477ÐØ ËÑèkrqf̘!•Jÿö·¿-X°@wd¥¾ÁÁÁ®_¿ÞÝ—<ì Ž <<\÷ˆTE4 ·FJKKé‹9ŸÏ?sæ ¬¬¬Ô›”‡îúZX\†ahÅ ,,¬©©Iíh÷§¦B_Œ;@AAA÷Э¦Ì0Œ¢&%%ÅTÖëà?ÿùMjòäÉ4äôéÓ¢íf;T3Ó]_ ‹ûÛo¿øp0oÞ<…¾|ú®N ôÂãñ233iEuãÆ'Nœ0üª=c„  ¯M[4KãqèÆp}Í$.£º¡(ôå1À…  <ÓßßÝã½"çÎÛØØhÔ……Çãõïß@KK ¡¦8FÃ(}Í!nDDÄCíÐ8!!!ôçÆ•ú?~ÀèÑ£Uº›¾ärù¸qãh„7Þx£÷Ï|´¶¶Ò7žÁƒÓúuèС^¦ì t××vÄÕ˜”B_=ƒâ´¥XVV¦èÅÒ¸ÞW¬ïèèè¨(Ä‹-"Ü 8ãÑ1(NÛ)æWjI©ê«g(±ŽD׬YCãx{{×ÖÖöÞzŸE‹>|¸¦¦¦¹¹ùòåËK—.¥M  ‹…rC‰{€¶¡Ä:N1¹¸ÚPKªËPb…sæÌQœ`ˆõtõqqqj-´=°Zðõõ-**¢qæÌ™à“O>1>>ôbÎÎÎ3gÎÌÏÏ7ªI$’ÜÜܤ¤$‘HDÓ ¦+áe ‡É±)}yÄॹ0 ³ÿþ-[¶äççÓ@=qâÄ      °°0'''±XìääÔØØxÿþý»wïÞ¸q£¤¤¤¸¸¸°°ÎMæñx&LHNNŽW܇ձ}{âš jjjrrrŽ=ZXXhx:|>ÿÙgŸ2eJBB7Ü–±®¾½rMíííEEEåååUUU•••mmmR©T"‘¸¹¹yyy 8ÐÏÏ/22rĈÑÑÑžžž½¿(‡Å°Š¾ÿTÛ@8T5IEND®B`‚frr-7.2.1/doc/figures/fig_topologies_rs.txt0000644000000000000000000000006513610377563015704 00000000000000(RF1) (RF2) \ / [RS] / \ (RF3) (RF4) frr-7.2.1/doc/figures/frr-icon.svg0000644000000000000000000000553213610377563013672 00000000000000 image/svg+xmlfrr-7.2.1/doc/figures/frr-logo-icon.png0000644000000000000000000000311113610377563014604 00000000000000‰PNG  IHDR szzôbKGDÿÿÿ ½§“ pHYsgŸÒRtIMEâ87󜙚ÖIDATXÃÅ—{lSuÇ¿çwoËÖÈ«½Ê|"°­ÛÜ*‰SŒQÿBM0hŒF‰H$dm÷·Ñv"ÁD"F$|‰F|·å5ØÚ á%‰r;D§Ì=ØÚ{Ç?zG¶®ÅÍÍøû«¹=¿ßùœÇïœóF°tOþš¶ê²©Ã%F$Í<_ƺ/F}…sÿŠ lÝç^?4¡¶ªÒÙÒè{,0~Q“°g-rÖèûO¢Õ¥Ùˆ÷50ËÖ§g@Ê.¨¶O´úæ“ÿ™¢¾âElÆ÷œTl—¿¥a Lüøñç ­*)”ߦ¬DÇ!”gAâ.Å–e€ mQ·¨ åy€l¬Ö+ò¶+ u¯û^HóÖ‚‘Y Wäý `êàD¥>ÊjW õͫג|ïm`'¡<í „ÞÑ=yÆrI †âƒCÀr¯åÆuVBm0-I¹ )ýÊ£¾¢‡tOþi½"Ï– à¬oŽ@±`–æÛú†;3A¶q~ñ3ƒB {Ý¥`V@dhÖݺÏ} ¤±bˆ«T[‘ˆtYMé›ñÏÀœ3P¦´lÞ²ò{îÎÍì8HiôuÖk–^ØæÇçs¹ûj Æ‚äú@B©qm<¹PYìÐ+òÛÁ<»¿= Pîgæ÷º»»»~ô{ÆãV»;qŽº À‚ä$\ €¼3!Ë«’Œïeà50¥YpÊ»NDÖ~ttt¨à¬=t n0r@8²›ÀŒß6/ÍÀeHÜŸn˨û’NîÔ¡®Ä]—5é/77wâ>UUs¾ÞûÕå=¼`̨úî/žu cf‰3ó†ù-þ½/ü‡›­*)½Z!úèƒùðÁCßüá@G’o&ÈÔ=ç¬x¾+r¦_êÿW½fåö+´=;lCgõ2°›0Ò2­,.g#¦Z7íú¨×ý'ˆ®›ôÄf#e)ž´jglÈ$Ík­„:=R6ÝÖOÃ:+‡¥l¿°áމé'"¢?’æ´ÕÌ·9kŸ'g†m½¯è9°ÌQ‡P2²I±Í&¡Vh1{;;ÚÖßž›ཤ/ÙlÆÍE(OK¹·ð%6ão$ŽS:ýÍ=,å ´kÁð|-!6 {š™¶%÷ 6=àò·ìQ u¼KÇéÞÂ<ÝSpˆ¥ñмÔh9õß67t¾_ÞUätJÅ1é8€XRŠu_áÚDÓ‰øH±/oãòH#–óê"[f®}ráå³ÙŒ5‚Ä—.ËÞHfT7š¤Úˇ˜h¯ê^wCÂǾ¢ŒìLX PF«–Õ[´†ÈWý‘Ÿu¯»Îì¹t$>Ö‚áûG4êž‚=`ù`ŠÙ¼Dë´@èPRÜ—°4>O¸Ñv³æo9õ-gȵ@¨ö_Mź'ÿ$˜g¥ÙÚIBÙDŽì:gu“´Úúûæ£ Ò‚áÂQ?L”‰Óç‚ÄÙ4·|³¹ +gÕ¾@;Xºu{Ǩfø¾5µ`øfÅŸ®Ö šêŽÄÉ–q§U…–G+oË“§™U û¸[jt)—«¾ùH¬O0ÄÂÑš2ǘ¼ uGOi ‘r­!bƒ¢>G˜)ƒáZ8f ~ù­Q¿ ÿÍŠVÞæd£ï „ºR ´nM%÷7*oâªÊDIEND®B`‚frr-7.2.1/doc/figures/frr-logo-medium.png0000644000000000000000000003151113610377563015141 00000000000000‰PNG  IHDRÖÝÇ$ŒíbKGDùC» pHYsgŸÒRtIMEâ; ÷Þ¶Þ IDATxÚí]w|Eûf÷îr—^/É ˆ"B ½‰ ‚T)"ˆ È«(à‹RÒ $^Eìˆtˆ*ŠÒ…@@¥ÉÝå.½_ÙÝùýƒå‡$·»—Û»Ì÷óáóòÊÍìì³Ïwž23Ïð‚aqÿX]L"’ ¸("~àêÊ2-ŸO$A@ˆ%*`ֶ°¸?¨Ø‘JDB@ˆ%žå*]l'̘®Í5¤ ®!ȈDÆ`¾vb-P o‹%šgH`XoÊq‚ƒ€KdØJoôÒÅuÅÅëž#®!!x—­>î*½5KIÈEˆE fÜÅV߀Wˆ0±D»æÖ.yTæ¬$2!Ä" lµ t1˜5]{ʘþ!˜¨-8ô9SSò"‘!Á¿ûy<šP»Ñ¸ü‰­D~„XwäÍ»-Srs’>! ”n$Œ‹à6°b`Æ ºØNØZzóE}|"P7ÙÒ$ÀbaÁìÄÀ–Þ܈Æð.‘*±X„X-Ößâ.Àï3ÇDª„Xb«!î*×.&—šÐߘ9œÈ—«…Z, Òo͹G™ª¢D„X-4í ÆÒLÝ{…K‡¤!bµ@“åȼ¶Ò˜ Oê…ÊÞŸGäMˆåþ(Z=ñE\Wæðç`k èb"0S|}º>1ŠžËHôTîÎÔèºÏÑÅD`«.og«k¶1XMW6c›y@ñºçÉGq‰œà®Ð'FÍ =ÞcÊu¤ÆYP&4ú ¹.6´™ÈÇ!Är TîLßñ‰€ ­”áÍ~ó°µ&Âõç?¤òÅøô}®ƒïؘËä‹WPÚîÞÚ©À”ê¦^§_ՕŶºìz …¤5ù` UG7è“{?P•“M> !–ĵbLý_luÏf Ãu—¿;ÌVÍ–¼!§hÀ–ªuq§ùŒšÆô!D“‰+( B…,Ü ÆŒaÏcùˆ©,Ä@ÑҔƷ]@DËRúÔ°µe_ÆœÌ'484ñÛLj*K -jnɆ™4k ¹rŠqÉàO™²›’ þ3”£<®¶lpŒˆYàÑe¨îŸMªr²Ág©rM,–“QºivW[ñµSLñ5Z*Šòô¿ÄÕ–_‡ÜÕ ÷Lnøï»ÒÁo\"ÑPB, jÃLº«ÇçÇf‰½yÖ“ôGQOn+Ð Z»ìÜSDý±\úÄ I? †ä^ñœ¹jió¯Aa@@©ü.ræªMFîd„­xÝs<ç#¢y„X®…Ší©à71L+ÇM`J®mǬ 7Û{bÜp¾j»Ì7Ìšph*Q1B,·€1{¼®-ÛÈVžÔ\q$“%h]²pÏ4ݼ¶ ]sh!–Ë'% pæz0f<6—)»µ¶yÜ> ”ʟóM“vr2Q%·#Ö­¤þ ¹·ïM¦ä†ƒwJ €ñ.MfÞ3!3Q!‚;Áew^TîÍÓòQs‘¥ÔÂ”Þ u©0DË€R(·h3ó"¬ÃB*·³Xúøn Š¥2|µ”««zÍq„â€Rùí¼YóQ‚¦Â%w^(Ûõm_w~ÏeŒYÇì‘ÅP*ÿrU»~KüŸ_Iv¹¸¯+XþyÒ.®-8TO*GJéS§zpHzxêOR$UñÚg†—nšÝ•¨.±X‚aXܸšjUᲡ[ØŠÂ1ˆ؈V° mç÷ƒç|ð€ãÎD>ëXs~o+EX‡õÖ‚ûþÍró,¨:<² &õ%Ħ¶HŽ;R‹q!@rÕ»š%§^øÙ91c\äcE{`«‹¡æç]·ëZ þácÐDk ±„£äí™#ÌWúBtR!Hæ±G“~æÉŠí©pʱ.Üúi<ûƒz œÒç1l«“†/1Çæ8`«‹ yHŒÕ .`êÀ´ºßŽ~!n‚åámÑfæ!Zé7ÀobŠCÆoÌqûï–ë'‡˜V޵éb"0WWùf™/1Ç $Äj¦øbk}’B™ÆÕ–$ŠO!”gІð´J€ÐÄoE¯—^¶ùÕ?]Ø’k¦Áº˜ ˆ:d3ý*k!V³¡xÍÓ  i//\:d`.Q< Ä@)}¯j3óªý€—ù–ßÏ )Z=ñädú–­2-#1–ó`Ì ß`yùOï[ÙJ•Q@y¾žôÃ+þS2D뺡ª€>1j8¶Õíg«K0[SJL±Xˆ¥Rú¥ð’×ÜøÙÊTŠ÷rÞX›y©ZwŸ-jBeÃÌúdC]UkÓò1º˜Œmæý¾W‘€«)Ð'ö޵Ém¥×­\]…˜–êíð´€ÿ´U¢ÄRÅkž®¶b)ûÉŠÚó{oØJnd5"±ô‰=ÉUrÄ1VÌXD §°Í3bø|Jå'š•*ÝøRý_䪦å#±UŸ÷½Íø‹¯³+ãë_I…-UVÌÚÄ1R /¬YrRpQÜÙÇÃëÞ¢ìqÇ-׎‡EƒdK¥´l‹U˜6°*X&&©(¯Ýš%'E}\‰‘.¶Óøš¼ý×­Æ+adá–@²Ä2-(,JF™Kl¢‘ÊÓ-g­/F_†Åýêÿ7¥ïD}zG0ÞᎄÒÅDíwb¯ŸÊ˱ጳ֊àû!Pµí“M)¼çi3óDIP(Â;Fè“zbÎ\ù¹;ÇPÚ¬|0$÷Æú¤žƒtÑõ²æ~¯hø·Êœ•„®B¬ªœlžýÔ^:hekKÅè’Ófæ!¤ô^wP©tq»e¢t±&˜=š‡ëIïöqg©l­ýu¢èãÙ]tGlH4¦úÄç½~Sòî,Â)ËgÔ|ÐÅD,a+ôÂ- EcmV> 0}àþ<´]ºÞÊÆÛ[l…BÀÕ”ìÁÖªº˜lZ=ñšå—#·¯’¬Ü‘F#5bé⺤@‚PK€dX›‘+xÜ…iëÇù”審s€9¢ Jl†Km0 Óº˜ŽØ”ýd¼ùú™žD6"–1ãñt`­ñBIEyø`ÍÒ³¢Œ9,ùRzcÀÜç$Û×8ÑlÆ_–ÚŒWNèâºb}BwB0gKÛ Ê>šÿ ­ôF J©Tþ8<í¸(ã5­?ZýæÌÕDìâ…€³f,'uÑaCJßQ¶ËÈš^sËôÆhPµîF׿,zôƒRúàðÅǵáÖF]LÄÓ6Ã¥=dׄ`’g®ÜgÚ4‰Ó'õm;ù hsK½hX‹¯ >ɇ^83¡úÜžr¾¹ÝC„¬†‚îšÇ:pÆÌá£ë­× ±ø"dá^¿{XA‰„ `Ø¢^Uû– ‹q٣ϘožÙ†h9Qt'º‡LÙï{ŠVM8Y{òjIûE%–>®K:[]"(®’´ú_í¥oÏøŒŽæÝ‡aq¿g˜rÝLj’S%ûe-,è ¾À)ïëךËENée›_ˆ‹Ðk˜JÓkÁs>ÄBHÅÕ–}L2w‡.&<ú߀Ð$ó°6Ç3ë~=|ÃÒw"@åîeî=›ˆf­ºc!§€iïà+aI?<(ˆà‹ûMåjË>rwR©ÚÚ8ó-ÑKL£r1Æ÷aÆâåÈü†Ì?Ö"HåH„§Ÿé¬YzÖ[›•Q¿:he1å†7ŒK‡rêèÐ'õ&Äú' —=åŸDGË$ó3ÚŒó‚¾`ñÚ©me7[©­px±BÏCÐfå!äá{Ü{)Ù*#袰fÉ ÐÅv"®àÿ¯úaÎÌ·ºJå¿ZÐj~ð¼­¼âªòOcï¯>»ó7Îþ]„ pâÒ!ªco8k…c+ }sâN^63éb"@›•Oˆ`Hî•ÂYjRøöƒžû4KNá›0 O=º˜ŽØ6S ™‚ÃŒí†&óÂC!KÉ[3 è•Í’Ÿ>©Çl­ v¿Ú¬|·"/¬ÌY >#ç#}LGŽÿšm¿Eà’ 3!hÖ&Ð'öÀ¹Ú§ÙÝ;9`޹¦ÍÌ»Oêc-ßþS2@ŸÐí:f¬÷ŠM.}|7Ð,ûÙå¿)/VøŽZ†ø.Kx“ cPµëÏ;ß4k˜VŽ©rRÝ™ñŽV³ìrRü¥D7¢Ûzu?OÌ]-ú¸.X³ìg(\2¸åY¬â7ŸÚ/¬Wí…/Nð%’)>DMפŸáWéb#ŸÌ} .é"„®j3/´3eõü.¯Dú„î¿`Ær¿(3½ÒÂSD%ï¼ A/¾Ý²\Aã²Ç0S¡ç)9´¹¼ Q´fÒHë­Ü}®W4R(/k–œîP±}1øM\ì6q¡>®3’ù·úÎVzýa1ú“«ÛßP/ØÕ¦Å¸‚…©À˜9|>S~‹ßÎ̇¶óz^„ZQŸã°êòr\ŽT´ìŠ6+y÷|ª¸©4¹X®n÷Hýú—ðOc3]i­‹‰pé;–í–‚!©æ¬üb$WíѤŸ~’ï`ˆb¦¢Ð…¤Ké´™î)Ûü*ÌX -úÄ(Œmfa`ËBÚM]”³Ë­-–>¹÷[ÈYj1O%!¤Ò't›ÊTb×* ¾_Eyx·€E*MúDyx [ÄFbŠ~Û Pwš[[,Aû½NyßWƒþ³Á.rR€W§a¡UÇ?)tjJè’6+¯#€>1ê¶™»ò¢½‚!,ùä–Ä2¤ÌXakMŸëK-Ͳs¼…£Oî±Eâ•‚ÀQñÝ*l>÷5&´ºíiœÆŒ5JˆSˆ(úiMFî6·´Xú¤ó‰­0аëCæïšÃëÃÄw{³VI§Ö-/Ñ,;Lhô/sr¯œ¥†ÿY,Žï^“µæË?èC¿sb™²Çg®‰fËofòÙäJ{–†% ²·]ÃV]tG,é£õ2U¾vééN„>MÌ=1p­ò‡°ÅÇx)‚.º£/ ¤lŒ Ú¬|Ñ677š¼PÏß ÀYøÕ°À¨4ÏÀ‚^Ù ¦c,R&•ò¾~h/ÿÈæ|fÙg ^º˜ˆ—Kh,9…„”l`ëʱ!¹7¯«š(ÏÀØõ§îüscÖÈWÊ·Ä9ÞbUîÉ[ñï̇x]@{þ–|¤½ýAo <ýG°åº©Ñfå£ò­‰àÿtºÃŸeÌ|"˜)ûp`®þ¨MV¾Kî<ÖÅtÂÍ·½>®‹ÝVËÚªU@+šàvœeHì%>±|ÇÄSzCÍk9-uü×<ƒ»N’ÚŒí5qnÕ¾7ıR;듦7Æé¢;bóÕÇZêuBÚŒ\¬Í¼Ð“ï„‚Y›ÝV«â³Žù¤Qöy;<ý¤c\A¾I ÀÜ{å[“ÜÂZÑ>¡y6ã¯ë|F/Ü—>©'øŽO„Òwg ·ÿZLêɯyB?ñ µè¢;¿Üߎ$Æä¥ žöv\c.([S,<$ÿç({ÿ5¨½r8X ¯øŠÛtáA€ÀqÆÆ»%”¬(„:j3rEËNŠY£ÃU×±îèzñ\Ûj8ml÷óâº`àîvu$WÍѤŸáÒü?‹0}5È|B¦ðêLéóŸv‘—L)R!ZÚÌ ‰EªâõÓ‚uÑ]¾ðÍœ¹sDï³ló«çÓ–«-…Šm‹íªRöÑÖöQcö³6…÷º£+Ȕ߲›æ@ÔÆîºwåŸFƒ1kä8l­‘̺•øÿDaê pËõ“ERwýf½< zõéí5eê3ƨž=ð_ÿ̘ùÂw‡öèÝG¼êµ3Ö‚6óB_~ÚKCÍÏ»íÚ§ðÜJP¶éY×Xv8f•hĪؖú¸Îñ<§yùïgvOiþÏ,J¡xV*»,(•_^é&qff]B·p¶¶Xï Š oo zõéY–­¾råŠúŸÿž››;XááQÓ«OoÜ®];¥˜Ï6­‡€—ÕÌî±·IÐìf5þM„©ãßz÷{*(ï ~Ó0·¡xý4^M­…WÆKA¹Ì£6|ñ‘3× 'U\—p`¬zWH£¯[¿Î?ªg–eÙF˲,\¾\×@ÿb=_½`Ÿõé>qflP±-ÍîÒq´WP# úÄsD![išÄƒTàÕ}Â7Á³?à¸FŒ“Œ ¸ô¬(·lè{„Çè¥N¨¡€÷6o¶kU!f‹åÒ ÁƒÍS¸¯‹É=y¹ƒÕ§·v(Z;Õ®flMÉþFã,ÆŒD!VñšÉÉíþ·`ßò’í|€ä*iÖCèbѪ ‚»1f ×€µVò¤8ôÍ!èÑ«'ïØ¶ªªúâÊ+ô®[k¤@*ÐfæE„¼¾ƒ¿•Љ€òy2e7t®²FÕ«w¯jŒùçŒ(Š‚³ç~.Øôî&QÆÃ7‰Á”\³»MÐì÷_i,Îb*ùWû[Ï”Â{ 7УuË'.©øzõ@)ö´g  JüÅkžmV>Ôä}YãJ;)8Œ»¾— ZxÞV(Z5Áå ȱV·âE¹[¸rO&`ÆbÿN Іº_yWî´Ï.PrÅ—ÂŒásË60‰ÏncJé½›çGU© ÚÌâ"øÞ}|·pkaK‘jذa•¢Y|ZœÊľccÁ£U7†ʃÛDúŽ·o+g©-nÌh°å¿cAÄâjJymáÌ5và(Ûü*èc#F;;A añ_ó¶V¦•ãÀüÓ3=¸ŠKK8±úêÉsíóNÖîǃøèSa´{ R4Í.ÿxbÑÞÁöÇW€A“yáe{[ÌX ŠÐcœo®¨‹Bš«ì‚Ê£0-}C-E!Q/´BÛż"Gîh,3(ºw¬ÿ³oð'[ÍgG/ÄS©¬Æ+=­š¥ç"¹€)ýBmÅ¿º$úõí»C¬¾Îœ=û¥¸®œkøMJÙÛX²‰­*~Œ·+XþqÌ(>ëW@ÑïòyhɆŸàõ÷¨°©Œ5¸r\tðàA8sêt€«­ØÇITíúþÖ\“åáí8bÑÞ!©|fÿ©Ë?â_=êL¥Â¬íbå¾¼Ú¦ ¬·Vn€'FŽ !ÞÕSîÑjáìé3××½¹NÔqù>±èõæJ!¹²ÄaÄb«‹ìo¨ðtY… š’=ÌwôB^mÃ’­ô1¸±öñ%œ:y*ì&—‡‡ìÙ½Ç!Ú/»ç’æ’fm1É€K ½y‹_PWËëØraÆã“=dæÐ) ²ë°[|›—nšÄÖ”‚;áÌ©Ó~¾¾M~©öíÛ—üxôê?p€Ë¿;WWñ[#Ôƒªc¶µ›X%_šÛœ:¶\çÜ2ÉóÞiQ´jÔ]þN•¾=ômЙS§‘\&û×›===áÌ©ÓhË'ŸO›1Ž9 nŒ,f•ÝÄ2ÿv,Èî Æ óåfÈ´íœ)'Ú+wÛ×w Êà®:ôú‚ùpü§ãʇ ¢B4Ð@cŒé‡~˜:òÃa4hðÃðÁæ÷¡¥€cì'¿ô$PÇ3xlª«ZæL!…%á½…©ô—Bê~9îZ pÕÊúâÅÙ+³1üã8ïÙÓõåÖÿƒ”Í ”žþ“–ˆ×%B@©üíŽA)JÅÇô @ñºŒ˜3Wº¤ÒUîÉ„º_ŽÊ[jM×pÛ`¯oÿéžvíí£hy£^N Æî šD¿\?9ÙóÇ5d ÞnœïØX‡´Óíu<¬?hÍKÏÃê‘5 SF”¼ýBÓ ôiߘñ8Õîý~2å½=dØÎ,’)0€ý9€êœUm+otš+…kIÅgñà7™Ÿ7j+¾J´¾9¼ƒã[6~»Å(jÎlËS¶í3Ò”=þKõü¯üP¾%ü§dôÒÅD໑!d¶?—0}u³¥k/}×Ë™ É•¼IU±%Á·úç@\AÇÃrý´ bŠóõ_ÈÕíÇ.º7,áÐæ?%t1 ÍÊGw%Ÿ!4§ÀlÅ¿9Q11¨Úæ]¸¦úÜB*Wòû)°ý²qÌXCJŸý™6+ÿ6¹Äü¾-HS<¿2E1 š-öë›3ÕÅ»1ÇŒÕÇw¹+¹ôñ]êï@k<—Aˆ%&<îí~/‘B3ù,!jֺݘãžÔÅü{·šeçAŸÜ´ËÎ!Jþç²íBˆåÐm5#Rh.asŽèsŒ»+¹ÒŽƒ1}„§ŸF”Êÿë)ß,ibUæ¬t²!l¿(S~‹(|3 ({ü÷<2²“ö×Ì¿ÛB¿ÓÊq¾ø¢½ƒmL¹þLŶéËwÔ ½ƒÔ΋e…ãê*‰Ö;Û’Áj¼¬pä‘¶\¿Ò”=î®·w«ì‚’·_€°¤ž=ï÷Tªt‰€äÊp³ˆæJ~O¥¢å}ý›ñÊ0Ó£ïZD3èå÷ üãE0m ¯ÆÍK,ZîGˆEp÷Ä…­Yžc+¾ê]¸tÈ]}N>Õ™œB,ÌØÊœ÷Å8¢µFUN6èb#7ç3ÙJ#Rú:$ k^bÙêÊ]•XH®$Úï@øŒšˆ’õnîçræJÐ'tÇ.K¬Ê½YÀÖ”9W,#¨½, Ñ~GêǾc0cqŽn0ÐÅFºæ–&ß1Î.|+Ln´¯z:QÇ pÙ£¨êèæ8§Vƈ¹_DôM@ÅöT0ÿzô" Ç ,þ }¤0–rUl_LˆåhøML!BpŠß|éb#OHiLºèŽ˜öÕÜ'ä’÷f&–7²b ¥ï¾ò!ßæÊvl$³(>‚ç~Œã^’BPñÍêß%Vü¿ÉHòÄ’‡v@N«ÙŽÔýv¬ßæA/m4‘µ0qaÌ|éã:Ÿ”äÉ„ ¶à›ÐTcÖ$ibùô˜pÈ©ª@RSž„ ""4v?Æ×SÊc´êr?ÌM-L€$K,Õ ©EΜõ‘\©6‰¡ûă>1êª+œscÊ~ÿˆÃøY}b’$±œ l3 29”ÒϘ#'E@ÑÊq_c[][—Ѻò³Ïéb#‘$‰E{¹¬2¨£snñ¾iàï.–éÊ£|ï“…´]£ÐD4wå0ÌØ>Ì=¯‹‰@’#g©žáÌjHŒÊÒ^®~àQB .`B÷KüµCèœ×Bæ}ö6ë"ÒdäÓ”Ò?Œ)½èÖá®/ í8²½Ó$G,ÌXjùQ9ÆÂÛâTí[À2—ˆ;ÈÅÿ›Œô)ýbÆÒœì1ùoÿŸB\xêF¿Gç•¿¾ã>mÖEüÌj9¥ð·øËÕè7M4l6-⻎³¹…kÊž0ÅfÈÿ(ÚI.mVž gëã"1朻¦¥ÍÊw9—Ôš÷ÝýEÍýEˆµÒ.¿Øä÷.ÿxÑí£5ß¾§¨øö­DçÒÊIDAT óÕSµÞ]G]µ_­/â/À³—µ½¨ŽÎ‰„ÅRÏß±Åy¤à üÓ¸…Bz ¼CHvOÂB©0í<Òž&=Oå5ä«&ý”!àñyêèœ íò‹È§ßt’yhÍ7òýd­ëþt#›[ÉÕŽÆeaIX,€ú-#àÜ@¾gİNÏgóýƠ鈹tàjKŸÐ cÆÊ»½, 5„ÆîwÈ;—¾û þç-¨ü,ѳúü¾kñ¯5ʶýËØŠ[õd»Ë·¦= ,å(rªÅµvneDEð%U½÷€€ö ‰"6¨i0$÷¶ !`Bc÷£¢Õ“2¾RøNN¯Õdœ×yGŽ)‹ÿ i³."ÏN#}Q­dÁ÷¶¡}Co©lmè“zb§Z¬ÊéPuü“‰€¨mÎûÔ”í|ôÒÆ©|{0Ÿù•lÅ¢ò®b± )}Œœ¹JH!Lyx=žvò)½WÙ{¯Ô^>äíѺ[Szë<[] €¨ú¢ŸÎrË?\ؽæBλ/¼—\ù´WPdXòÞé"cæð.LÙïç±þ•T%œ¹*PHòà¶ ^”ã“HÉ;/Õ]ý‘½'#·Ü)® ÿó+Î:C+ŠB*€ÐØç)…ñõîìþ±BIƒzQ*^÷œýqü_qK7Ín–wzñí’{2rËcÈ[­‘@@]`×%eÿF¹&"'ù‡\ã»bÎR-H·0Ç`e›ÏùÈ®¶U9Ù hõ¦.º#ÖÇweê®üpÕÔ[_}pÃí-TeïÏsì´í ÁWî\•'>›€¶;×h!Ðfæ –.®KOà˜“-Ù,Zýòî÷\ÇÒqy"t‡‘Ìã9ÍÒ³¼ã*]l'0¾£Œ-·aÖö;åèžr$Èmˆõsíô HîqԻǾOÆó×£ÂÔ¾g©j±ÄÒ'÷ý [*Å› Ц‡Æ~•ÄK§º#° 3– ý–ö ‡°øo"G§:2µÓ3ÖþBHP¸dO;î¼CœÎ&UbÔ-ñHÕ Bc¿JªÜÉï‚ne«.k°ÍÜ(©€cAÚ~PÑš§Á­ˆU´b Ø*ôO:ŸYéã»-Ý4›÷̾¸þ"{£º¶¤}„E«Ÿ>¤‹‰à°Í¬E=¼!4ö*Z= |Çó2X`¾vrN“6PtlàÌõGBæmu/b…,Ü Úÿ|–#ÁÓ?pæzÁ„žûÉy¤ðì n|K]ћϠ?׬† CÄ '­€ð´¨0u„¼ö9¯> i.6I§1W¬p¤œœê ¢»±·W²kµ@ŸÐíW}bÁJ¢I?sÜŽ\»ÒÀ¦Ï?®‹í„1kk#ZˆNËA³ìg¤Oìa)ößA`Ìx é㻯æjÊjÊǦ¼Cxö|‚u[b™V> leá8)(!f¬íüÌ*Rbá„,èÞ§Ü‚PÛ믰©>³ûp=¡˜^bÁ@rh—Cº„(ФŸ¶»}å¾ lõÌZš”?—ù߃“g;Zn’È,é¢Â’¨€ô—-)BP¹'|ÇÆ‚!µÿP®¶ükGÈÙÑYASöx¤ž¿ëã»üŒ9¶«#.ƒ£½‚ ,ù02¤ôðTþ÷!èb"ئº€u oÚ?ìA.xîÇU%IÔ¼@rÕk’˜ž1úøn‡ — ¤´¾cc¡rß O9vú¯(têŽïêd3^¾ ‹é„1Ë8‚TX®~ Àç‘9”1c˜ Réã»â¦é1Æ´oXÌýé§N*ÉËãÞ¨RIUcÖ:PyÿW…öã;zaƒe9ŽÞý0ÇH’D•;Óÿœùã:çëã»âÒ]‰ Â!:Æšª^°û!Ë•Ã84î ï® ©ý¹¦Þ§Eû¨? Küny³ ©|àÂ¥C1[Y(•á`mV¾¨“NÉúi]Ì7NKäB]A]LòhÛë€åÚÉpDË#›ãÂ7Ì1 n7Uóå§‚õ%}p[ez°Iò¤d4"An1^bš«\¸$ˆeÊ\UÑX¶¦d—Tª Q /sø’“*±û5f ÇLùï’ˆ±šóÀ)’y€W·Ñ¾~SoO1¤öÿ‚«-Ѥs,xu2¨&ï«Ríҳͧ?RPbõü–rdP2ÉXPÎZ£ÔÅDìµ§Hc£Èc!4îBÝWw›ƒT˜Ã”Êïe¿Áÿ•ÕœÿR Rík2©0‡eAmâý§d6+©$C¬Ûƒ‘{<ÒZÿM©ü÷˜VŽEÕ‹ö€&#÷xÀ¨DO™¿Üx-c‡>Ø7|ñ8³‰Õ,9%Ìý[òð)®¶|TSŸ”,%4ö@†StYJß!|É©íR+ˆÉVŽV„´M*ÿàuQÇU}⳺и¯òðíéväâXZ6Ù·ÏT™zÁî>££…‘*mP-[]Ô£©¿—ùk¾Ñf^Xâ,H®Ä4¥PM”Ú4^“0UùÄ•»Å›üÔ‹öÕ[¯´ã§µYPt7 ¦”> ¼¯O„vÙù­ P‰’êÕ'FqlM‰ª©)Ú7 Ô±_ +ß„X V+íÔ)ÅZ (Ùúúeß'ã ro¦Cú×fäžÐf]D€¨^€«-}`(Ÿ!á©Ç‘­è·‹õvóEéÆ@ýâ/¶™›~‡7x´î.+^3 ûOÉpš8$§Àº„î@+¼G°5Å9R¬“®ÉÌó©ÜžRí÷TšCŸcHØSî¯ÙbÑçµ»#ÅÈ Šqs@{ K9ú…Xï®ï‚h¿VÿcÊn̵'Ƀä@…´—Ae!–ô½s=/©)®véYK9ü%í&É"úØNUˆRN*^÷¼CÇçÞáTð¼­÷ÿA Þ²ÀÖ·crId%¥ò}\»ü“Tõ¾œâ3¦ôš}¤¢= .½îtRI’X¦•ã@to”TVüd+¶Ôî.\òˆÃÈøÒŸ¥Áåêö'Ccö#mV>B Ï>P¿{ÞI>HîÙO›u…/þékÑ\í·fÔGIèÎak­]‘Âä>!2°aVÈö(·vÿáª,€7¤:>D{콺ììØ.šiàë –ÍW„==ÙüßÏÄó’z®Ã6ól{×÷hÏ@ g­bÃy9iQ«ª¯“r$ÌZÆÜßõ{haÒÓ*©.Î6$÷.ÅÖZ{I…å­,Š6ýˆB’"•ä‰8cU,°õ³RN‘aÖö°>¡(Ý4§E\JÇYkÁ¸lhÐ~‚ÿû6$õÄœ¥ÚÞ›61`œ¨Ž= s©Mó¥ô¼WøÆ¬'0SzSâ£D@yî O><ÆÝ]Á†ü¥ðð”G­&_\Sü¿)`)¾Ú,UךªŠ˜c€ö L Oýi©”e+ù;ˆKÞ~,ÆßÒ/Љ«)mHîUPöÑBpk œÍV£j×w]¾àÿnmÚ‰ëÈÃ[ßT2«Úõ"uR¹ŒÅ0½1z†­è·M®q0¤ôݧIýQòÖK‰…LZˆ¢/j2r#'/º×aÆ¢ü7JÑÞ!¨Ú+¿})ã úJ¹ ±Ô‹öm¦ý÷ºÆh1`sÅhCro\}ðÍ!º„î’œ Š×< H¡ê!èM9¦cÙóú é£ô½¹@yuºãÇPt|XÒ÷È·í@ÆUôÕe,VŶ`«Ë”æ_×5Ç¡<ñ8†uH›•÷haÚ@$ô"G 0mfkJ„½#cöSuQøÂ›ÆñðU¶¦¸íŸÞ&¹¶sÿW?ýÑÕ÷|ð‘É„\"LTS2€)ùõ¨<¸­Ì²\;õÒi´—){!–‹“k«êÁÁÏ`–!ìêfgæzQŽ8¶–?µ(·ŽªVÏßAˆåêzqã¯Î#£\®„˜Dá÷Øü‡„$†°µ¦ :Iˆåx>û,’)d„ÂQýÓ‡¿ËƒÛÌâ²ÕE=ˆªõ‰=±\˜µ±rš¦‘̃°CÂRŽbõœ@ ˜§L…ÑèѶ§–ËÕc„¬|`åž\@¯é2YÀ= ¸õ Í ÏŒó(ZÈTæ_Žl#Är„§‡ZÃy64ö §&_¯{!…—° Ð1×§dãžvwY¡–¦…é$³†Ta3¯s¡j?h[àÌ·&9M†©Ìlm™‡rÇ2A^í. œõ.±Xn/$~—æÕsb¢I^C@̥̕ ‰·d>!%îJªI,€ÚÜýÅá“×Ëh¿°Ä5´Uû–ƒÇ½½& 郫«ÀƬG ±Üšô3PQp€ ‹?4ö×DK¿´š´à3:¬ºs_ËÕœBLɵþº˜Ž…Kb¹ Ž\„ųBÏdJæ§ +ÊMŸœ–Çê»{†:!.!¢%~^ð!ÄrC´…K84þk„TqÄzÙI°¬üà?gòËc°6(ûfU!–¢¡À¿fñ±¬ðÅ)™¿f`ŽX¯&À”ùx!¤l34f {HÛ‰Ëmó½‰84îë1 õ¯ ¹ €,*ß¡q_cmfÞ~yð¿Ì Qˆ)ÿý¢Ì7LSñY8dº‹ÎY«±!¥Ï!B¬–lÁ2rWi³ò‘Ì7ìuy`«r"€ºë'¾”«Ûm²+ƒ««bZ>²½.! bµ`ÈÕíÖ¨ch2ó( èù€üqkF˳æég°zÁžIHЮ [ÉËÊÖQ÷bµ`¾ôN½> „µ¹«´™yHuÿ€é@Ñ1¸E’L›•Œ¼zômB,‚¿“mÖ»h3r—k³."ÕƒžD-¬?ÞÞ2Â1ÓòQ#4^Ðûbx¼øÍg¸Ú»“t»“ ‹îøíÖ•­0NûËeM‚”Óíw‚>©g-¶Öªø“‹ŽÁ!ªû{Ïþ€X,‚‡w¯I«Ãâ¿®ÍÊGA–…à€Ð"Jáy[™Ü&æZrÊQ´UH¼%ó .rR‹%aT¹¶Cå‘wFbŽY@«'¸š’¡œµ¶ÁbíœùÖWy—ò^G}Áx¦üæv!íúuX·Ã±„+å¿ÜUºi6Î\ïRïb\:äSil-Ä%¤}C[c–¹žrbÜŽ-*DóVXZÞý§ûŽ|½„ÄX ±e—1S„Ì瘵AõÑÍ’¶±š5ß’µ~KH ³¶¦7F?ze¨tß“¸‚Îq c#ksžüÙ…A·î¢x>›‹€ ú¸.sü¯[¢<ŠÂSŽª¥ønÄ$p L+Ç! Ð!.!W[lZ>êsâ ü†”>79sU+þ.!@ËïÓfä^“Ò{ýÃã Žd[ÔIEND®B`‚frr-7.2.1/doc/figures/frr-logo-small.png0000644000000000000000000001403713610377563014775 00000000000000‰PNG  IHDRkoë.>bKGDÿÿÿ ½§“ pHYsgŸÒRtIMEâ7'i³•1¬IDATxÚí]w|TUöÿÞ÷¦—dÒ éÒA¥(°‚(bCT@VWD–(‚ J]@ÚRDDW`PV©*HY~Ò‘Šh"žL’Éô™÷ÎïRæ½™dfÂ9ŸOþ€÷Þ÷î÷žzÏ9D„Hý+Yóö€H~¿êþcD„H¥‹Ã“IÝð>$Œü‚ãq»ÑoÇ<çûN{Š/Ô€ñoHòº;©#9o¬ë¦xQ]4Ì«GSɪ±ïÞ®`E¶Îz=…8Cܵ¯ >*‰£70¦Ñ`5œ):ˇ\„`)@î„»És1£F FXR  %ògõ"Ûî5`EV ù—×D¡lóL*^<(¿¬.ð­8®óGó&u"±¼¨¬fµêAt”!g|krÝ[Vu‘;óJ>5ÿ°b Uå>Fƒ¢¥ƒÈòõ´¦5¦{É~`=J¿˜H àÁ~ w aäzVYÀkÀ’_"¬?ü¥kÇgˆ Éx$xA‚ŒS‚S둘þ-㣓jÀ †l{V¡dõ⌠¦xU€q;ÀéLPÄÝErÃ÷”‰ &)ë´†ºqÇšF(È•±EË_!¦TWœÆeÖç4 :42ty ¨Â85`ù!¡4EKþLBÉE‰È„@.ð†X¨›tI7vyy¦"õNÜ®¤¨Ž±î\Ž’õ‰×ÇHôBÓ¸óá¨#ïQÖi… 3g‰ÖbÌ}ŠD«90½$ `*-t÷<Ñ=ºÏ»Ûkà©&ÎrfìDáÂþÄG%d,p*= ¼Â ݆VÍà¸vH·L¥«á,T²rô û±-ãX€þ’±ëPfì9²j xÝðæœ7ÿ\™Ûö®úTô8 Œ½Éïïg5œ%3qs'¡$þ€"¯Úfݶƾ´ä‘Êr’·0î_¢ìë©$X‹Ài ¿;Ò¦1€×ÀÔ†1(9ùN+ò&w&y1F"xc<â^ù”)ë_Ž8øµ&K²á<¹æUcˆ3Ä€ñ*N}ià!a)DÎø6Äbä<зí;ÔÔêÇŽíþe?Ì«þJ‚¥L¡xÛ€r°„â șؖø¨yåÈ+ÿÚ*¦ªÓ:0çùÔn-HL€¡"Æwû2°s6r&Þ#oñ‘>: Iãw¤ìÝ™‡P8÷)bZ#ØeWCA%ZÍÈ™x·?‡iïê-϶ÌìE¢Û†  ª™`J ±i°í_»Ë¶-„ò"Du{õÃC¶ò±iV¥—PÑ‚þ$ºlò@ ^È´m‘7 ÎîENzs]V ‚rycé{®<$§ÏøÄº¾¯»ì¸8ºžl8ªÊ ™R‘ôÖöjÝ&‡¹“ï#9QN‚qÏÏa?Q›[ƒî_ÀýÛi+ê¡7$ºth"0… I£7±ê ˜Ö€ÔiÇSHç(2^‰¢åCˆœåÕ VÙÆi’Eäv ª×(ÉË6Lkê-Î )7i›w_òÁaÆ'Õ¿¡¦~ʇSJ»fœ!…óž›%uôœ?Ž‚yOùΘ%¦'ßaºûžõ=¯N+²ÿv'qú˜½ é¹YL׺g•@Wô<çÍ$§¿ŒÇ{5û´‰Ê‹ ýv5ÈÆxðñwT,óǯìtžÛÛÕ·á£@ÊÔ£’bȼü/Ûœ?ïy(4% ù­]Œ3U¢¨@ ØKQ4¿¹/CíÅ…a™¢µ¹ï¶%).Ê Q{a“2ý/޼ƒ¸+ö쀔é'X¥Ä 9m°ÛÒUj¥F=>^r@wÖa8Ní|(48y:ãTÀ@‘× ËÖyÈÓˆò?èJ‚%L­ ›8ä 1ˆyn“ŠzðÆD”®›0Xêù¨‡5ºzyHàU`•¿(QJ„‘ @o?i]õŸIŠZ(òºPkÆ)('•ožmÌy«%Yw/û}ó²z¢$º¶} ªÝÆ+eYøôSxÝ>¯êïíŽÜö«Œû¡¯ÆU ,ÛÞÕùRæiT¯QÒ\õóÿÁs:åFâȯ˜dhëÊ{íe¸ðF-*ÿa…%,¾\ÿê§JrZ%¸/e›g¶÷Éy‰õ ˆ«suXïÇ53ËuR›Š¢Ãc——%±ìXúsÐF"bú~À”µ›û½µlô¦¹“:oŒ¿¡Ö!”jDõ©” •o_²O’3;ô»zñ+TðøYð—Ár\ßOÊÐ6ëv"N(̓ëÌ-VZ?𮓳—C`ÿ±Ì¶wuFeʆü‘×ëżùóqgÓ&ԸɴxÉbx½Þ€ž5öé•ò­˜Fשݾç´i7Çñǽ* G7Å–ýІ5>å½(@×îéFRXw,éÅTú õ”¦çgÎôw_ñÒ3;†„R/íÛ·­ïjCŸ¯üœŒF#¢¢¢°ü“O¨U›ÖtæÌ™¬ Ú–=·K9ÊöÛøº¦¨ÕŒS]ʼnžìŒ]~Áòæÿ Ñe•åÐÞý¨äå;–n jòH„éé÷ý²Iéš7ßpeyþ LZíõ–£N§CïÇ èÃ4­{v—² íû¾X)õœº~»ì+ÿí<µ»¥_°Ƕ€©|›ºÚÝOI=ìùå˜&¸?^ý½ýeï±lšYË~dóüPµdéRÒïoŠŽÆìçøý@]Û>Ý ©á9|‹IUƒöi׺,ä²Ëƒåúå@¦d¨§UfRÛŽl蔹.zõÄxY®Š/¢|ÇÒ‹á0ÉW¬XáwÐÿîÞm d,u6¾'X¥…3c§ÏkŠÄú€ø‡ndJ <¹§ý€u懺>çÒe‡¦ùƒ’;o\T¬Km€¶u/Ù{ògô )®–.‡ß{\.W@ciZu÷½è8<9§|Æ’T)M@WøbŒWÀ›sV,Áœ’Xµ¼!œDÁh+…à( BW¢zŒå*Ë7*ñ¹ªJÏô}º³¿{z衸À¢ ð½ÉJí¥>“(ù¤ú Ï‹ãá-Ë•L¼åÜY‡ÁId-©ë¶•´NœÇ¿D¨è²BßùºÊΈÄS}žÚãr»¥9ÏáÀÀƒÌ¹\ õ@‚os_° ‘VÚÊ+q9,]%Áò䞎•j¿£¼£Í’ÆEöÉÇ‚ÉýÓ6é’-wݺã#\ßÅ3´Ô¼ysŒ1R’»_þ:KN ,>©ˆ¯ ß ³4ÿnI®2ÐävHZ¼ ¡èü _«—/”)%_Γ÷óš*¯z"hšu“M$wžÜQ-A¾—_z Ï?÷kÛ¾i4Æátà§ÃG™RxT†ÓA¢èsóŸÜÒº‘)T ¯ëJ‹Pru(¼æó>Y”^óGn8¹U¢·ê_Æxðñu$/Ûö¬ªH¼¼‰H,Ë“,z÷g¡tÝ„RLqõÔ($u¤"eÊáJ¯(ÑRˆ`ü+µÇÞÒœÄH­‘’4ñ³OÉΉýÐW+E§µCì ¹#®üÿ”wö°ì·Z§3``jýa¿±ÁÊ`)øªW¸jê¶[+;~îÙõ¸ÉH´™ý¦8On£xéà«LoBÚÜóL´— `šƒ%Z ƒÊoP$7 »J3uºÙÀòæœÈN€+óPÝÂY×Ä9ió/2ÑV ElmGhÁrYÁ‚SJµ¬ÂÊro6¬à8¾uQ ÷zŠ2‘÷^Çë¬Ý´ù˜2©!B oPÆn±{Ä’\ˆö²Ê=ã´ ÷͖צ¬{WˆÁ r²É_yÍM¦íÈ0uå÷ô„ì1ö'«§1‚‚)6ðºd­UÒÍÕ²|Û¢*;ðL©Föè>°Œñ¦2Ä“wn•,X;ö¿itÕÁÿ‚;8µ Ò"{l#‚Çz°xSRPzË•y Ÿ¼fÚz³€U¶q:…Bl3…9ã[‘h) 1g®Ú‡©´B¶Ê×äjw)füjÓU?®ÊB^‰¼÷;“7ÿ× `Á4Ê'bñEi1Ø -Èa‰x°J×¼IRþ&oˆÇ«!º¬ Ñ‹€+0y f÷&Oæá@ݨc•å SjaÏokì>¼s$U0³'IY€$xaêó.Kžrˆ¥}˜ÅR'dIéß²˜gg1xÜ6ù‚sŽGáâäÊØ…+w6ª –2¥ñSUÐ ®;3ånÑ·ï·‡<®ˆʺm¼2†jtÈR7ÿc+i àcR¡mÓ©³Ï²´¹™,yÒ>–4îÓg#§­"»ìJ9ÅŸ #Ç‘-—«²,SÖj¾û×U9ìä8½«®ÜuEêP×nwÞ™‚‰`?ø,ÛH¢ÍŒØÔó«÷µQ€6 Úi¨Õ¡/«x¶¢£ ®S»P²öm‚BóêÑãq0]û§«ÞÐX´– gÂ]Tå­ ýø¦ï$ÝèÄ›}ù>Fr‡r‚´¹çY(rÞˆ’§ËZÆ®¯0cÏ!úI‚h3C°šÁk£ª.9C x)˜˜Ê·/”])ŠZM »§OzDX~»?óƒª~ûãr@¯xíËÂ9Oåë饛3Îer#pÑIÁµ /]7a€ýÐW+«nz7xÓ´è.{ßÅáÉÄU²!”œUøáSäÉ= ¹L+Æx¤LûIº{AÆN¯xµ¢{@"D—ió~ øƒÚáÓÝõè*¶Ô;šƒÇž±}€ã§­ð—‹¨¾³R§a¢­ Á6?.ûÏûrÒ›QÙwsI(ÉxMÕ ÝñäI?2¦PJå:³%+GÒUg-3U6fcûL…$…Èøà°¸` KƆyå(ò\8€Û‡´yYLÛâá)Áü.¹MÁ¸€{¡,ñC—³ø¡Ÿ´’ýDpþŠ?B×f:sJâ_[Yå4〥nÜ ªäà7 ¯DáÂgÉ_Tþ’®‹8{BòÛ»˜"¶v`Í«J¢u½¶çÒæþÆT :ÈGfŽlFñ'CéÚì/ÑVŠä‰ÿ J„,9/ºÏ;ìÊ4à`â†yS»’TÚubØ”‚Äq[YRúwLÛ¢ÇÑfH$‚J@Rúw,î/Ÿ6ò·u_²zÜ8ó¿Æ\ÇQ‚¥©S²`ϧ éùYEKgº³Õ ÅXŒã0êk¦H¬[9Ý$xáþí(ìû×1=+ße³tÍø!ö#–ùÔKõÚ2õ™Øì“ˆäL}†ü)]I°\'RK!jM=V¹N9áæ,ˆ{a~=¡¼0D‹ZDÁìÞ•?~W@U¿-üåß!>ÑÔ?P€cÿzä¼Ù‚„ò¢ë€"»µþ~:$@…,¦‹FtÏ1i!k{À8-{‰J>{cExt‘W²•y([ÿ^/C”ï\F>czÚh¤Î:ÃBÙt,,Çü½7y‹ ¡î púľ¼Œ©j…®`_(ÍCî{ˆ“(b'¯ q/.ašæÝ$E1¹íÈþ[º\ñB"Ôõï=÷Ê'­B}¬JXÀíeÈÛ(°Ã9+ƒ™àºî=Žøa+u¡*_µl˜ÞкwåÏr–`êLù^RåÛ¡|Û?H° áõ5¿ƒz ÛѷΟ¾Añç#BÒ|Ë—¡ntß©˜ó›qºào*Zø,¹/JûwÊÔfH±V¾þùëiM Ý_;Å…ñ̯°žS\Ñ}zÓ¢pµ '¯ ʤ†Ðµ~”®z÷ir;=¦¡tSKÁ SÿéL×öIÜH ûqí… ú‘'ç4Âz¤‰ A€"¡.TuZMÐ4}`JÅ)C÷uwÙóê±W‡‡®4eòA毶ì¦ fõ&oñùj:Ú‚A§‹Eò¤½•RñæÏ^_é<µ{€T|’ÓÇ yâ7ììãj)/L³‰ñ¦T„²“™Ìúx$¹‘¡ØÁ Ê¥0‹V3Ê·-Â- CÒ¸­Laª…H/?M»‘In¨2Ë–Yäùíè- Öïnbú7Åb,rOQW¦Ü }‡~#¤tSëPôÑ‹tkƒuiåŽZÏôw?Ù?Ø-•p’éÙé x™†)$xPòÏ‘‹ny° ú™÷ׯ½°€ p4Ÿä¢±VvGÚþÓæaΓßßú`€¦åÃHr„qÚ舋\T"¢{§3) –)50:œ Š·>X@Å^Tò»{˜¡óà8Ñ^q€UšLsM^‰‚Ÿ Û¬KÕ{¬9eâÿoLDÄœù;Å ýXF¼ùç`ßû¯Û,àê"éí,fÀvÙ¹bâ‡,—Nãx”¬O‚ùâíÖ%Ò¶ê‰Ôé'˜©Ï{ìRšñ&MËîÐ4ë¶UڜףpástÛu‰tžAꌓ,vÀ¦ˆ­Sq@À 4Dâ^ZòH:)G´—ÀòõÔ–·%X—Wu«žH·…¥ŒßÉ ¯§¢“XµGHxc»ò ‘kɲó£cî¬#aáÕÈ 5yrNÃqì8G®¬Càt&Täšÿñ-¼!Iv„Ñ’§vÝ<[:P#lÕmÖµäþå\™àÍ=;ßyø wþ(cj#ùƒƒa™´¼É÷“h—Hyc êzí*¶õkÀ Ph¹í`A4\–#Á|y“;“¨ú$¯ ±ƒ0mUNÔ»™uV•Wa˜€>6 QŽcR–*S¨aþìu º`ãv+ìч^ƒ2Yºá3SkQ8ÿª+b¢+$¹ Dðg¡üÛСòÿ”ž‘Ùp¼ IEND®B`‚frr-7.2.1/doc/figures/frr-logo.png0000644000000000000000000006360613610377563013675 00000000000000‰PNG  IHDR«¹0ýbKGDÿÿÿ ½§“ pHYsgŸÒRtIMEâ8LLøŒ IDATxÚìgxTÕÖÇמ^Ó zO@MAQ•ª‚ÒTP¼Š×‚ ¢€^P@ºHÞK€PB %„Ôé}NÙﯾêE¥Ìœsffý>ñð䙽ÎÚå¿×.kJ) ˆð~ÆDƒÎ@ä¶¡ ¡ ö (ÿ¬;õåDg ‚b…H"ÞkÓÒ1Ôµk>PžGŸ ‚b…HL«ùíß¶]³¨uÉËk©ËŠŽAÅ ‘ôwbà)8ЧröÊ–ä¡sA±B¤Zýosc­ÅP9g õžØˆþAÅ ‘€V¹yÄÅùÁüÃÛÔ¾nr'ÊúÑQ‚ X!ÒŠ¬~óØš}æ¹CÎZоBÅ ‡?ïYÝ _É9Eå¬~”)ÈF‡!‚b…ˆXÝ‚Xðn T.I]{^ZGÅ X­n£¹Q ¶í3©í›WçðúAP¬D¨Èêö››;o÷Óì”-+@"Š‚HÆ\_>C½§7£3Å A‚ %wÑÜX/˜W¿I›¦Õ¢,ƒÎD+ „Ü}ssd}[hžÿ\1o«@‡"Š‚£µ‘€üŒïÚ©”ÊYý(så$úAP¬$à±UÀ~‰sUAå‚áÔ}à[t+‚ X!H¥Š¸¹Q¬[¦Qëò×ߣ>:AP¬$ÚBHP~׳íýªÙƒ([qŒ (Vr·¡UðšSu *¿@}gw ŸÅ AîB«‚Yý¹ùÝ`Z9ž:6ÏÔÇ¢ÃÅ Aî@L‚,V¿â8¸Øm^0ú,ï¨B§#Š‚Ünh%\só˨šÕ²…gÐï‚b… ·¡UEV¿Â:* bþêÍú (V"axÌ›¦PÛwoäýô‚ X!È­„Wâ49×Ϙ¾L¹ª"¬A±BÐ*—S~*f ¾ó{±"Å AþN­ä¢O}0};–º¶”ç°>Å An""FV¿Ç¶w.µ.³—wZ°RÅ AþXID¬<—?X9»e¯ŸÇŠA+ù½ZI«Éq¶R¨œ7ˆz³×aÝ Š‚ü*VrÉ™D9ÌëߥöÞëE/Ö‚ X!¯UDº¶9O¬Ýhš3”òæb¬(A±B"[­¤Ýäü¥ç¡bvÊäea]!Š‚b%]x *—>O]»æåy¬3A±B"N«¤¼ø'l»fQëÒqËy· +AP¬lrÒÅ“¿oPÕì”»‘‡U‡ 8r ‘Z…^“c-ס|Î ê=±ëAP¬ˆÐªZü£bùÀüÃ$êX?µeýX‘‚b…„3TÚMΑ½ê¤yî07o-ÃÊD+›œtñ•œÕVÌzš2ǰ:G$ ÙeÀ?Á»-P¹ø9êÚ»(¥X±‚b…„ li>P¿+|>ˆR°mŸImË^›E=N¬` æDg…HPÇs{x~Þ îS¨¿,|+ãkCܳÿ!òjõ°ÒÅ ü^ðåî÷©M«=ûŒ,D©Ø¾Í=ݰ Š"U˜k9à9¾¶‹ëÌÖÔ¹ËbÆÏÖ6vŸPr6 A±B¤õ¹Àwz+8³×Pi.:俨Ó2 c}VG„Î@+D,¸yàÎ^ÛÊyzÃIêw£Cn‚ÜqgeÝVè A±B‹¢X|g¶óÈj“¿øç8ôÈ­ô0Ät{ƒèx} (VHPEÊeOöZp^A9g:äÐet{'ú©÷§µ (VH a+®‚'ke+ÇÉõ'ÅçÞïUb=ˆyö ¢HªƒÎ@+äna.eƒóàò%ž‹{‡¡7ÜáT:ˆú#¢Êè‚Î@+äv¡”“wì»à~”DÝÿœÒðØX·#Šr+"ÅóàÏÙ Ž=ó©¿ì:D@4uÚŠøi¦ÌÎ@+ä¦p,øÎlÇÞùÔ_yý! c2Ä þœ(j5Gg ÈßõtA¤ERøNmûîy”µ\C‡™Æ cÈIYòè¤2C yT"K q ÓÇ€L2®º"Šò‹HQ þó{À±}FRDU ”ñµ)Ò&ÊãjîWħ‚<:dÆ8 Æ J : AP¬[)Èû¶/_É9¬ï;€¨  ªÞø’²zƒþЏ´SŠ„š OY\*Š‚ X!w-R×ÎcÛ²½W޶EoÜb‡ˆ«ªê§*«7zGQ½(«7›6F"Š"xs1Ø7>Å}~û$ôÆ_#ªêZ-ßQ×j9UY3äÕêf˜@+$ÈP\{#k ¥‹ù=29¨«7eUµ[ÖV¥µ(QÖj²˜jèA±B)žßñ `Ûñå\ft‘ºæ=%š÷¥ªêµejSî/!Š" Ì¥l°ýôYX?«(ãkƒ¦a‡¦ªúí/¨ê¶¢5`A+DLxk86~:Í•»cbÄO*hÝÿŽºÁ}SU Ú<¶6 A±B$Ç‚;kØv΢”ñDÜçËõ  kÖ¹£ºYç,e½Ö@*l‚b…H ¶è,XÖM¦Lyd-ù)ãj¶Y¥¦Ùì<-ˆL†AP¬©A]Vpl›ÕÉy|;H(]ËÞD“ÑäÕêa#@+DÊøNmËOŸPÞm ûo•ébAߢ{SMËž”iXù‚ XIÞVöõS¸óöŒ ï¨}“Îoj[õøTÕ°àûN‚ X…ÞÀúÓ'”÷:ÂöU5[8 mûE©Ó;ãsAP¬B*š²–m݇«=ùûû…ã÷¥ ­žÈÔµë{J^£1V8‚ (V¡†çØ:°nþŒR_øESªjAß¾?ÑÜÓ ˆ£(AP¬B/šršÁþÃûa·7Eä еèÕ_ßîé5ø .‚ (V!Œÿâa°|?‰rÎʰù&™& ŒíÝ}€D%`%#‚bªPÆÎm_Ôrd}[6 )&Œ‡M›'€¨uXÉ‚ X…2lYXVý‹2åÃâ{T5ÒÁØi8Q§wÆcç‚ X…î¬U`Ûò¥œ?ä¿E[÷Þ,}çQUõðbAP¬Âêu‚í‡&¹s¶N õoÑÔiwÊøÈ‹™Ê:™X±‚ X… li>X–§Œ©0¤¿C]«õ%ã#/7PÕk•z'Æ D‰?"Š•ñßæ “)°¾Ð©´V%Q]_JU6h‡úwbäqg½¼¥8s pÖqŒ¹dg)Ϙ¯ƒ¦V«5±#¾êžB+éà÷‚mÃG}\'×­ ÕOPUoQ½BTîÃúü½(ù\À–_®ü°å—â˜Ò‚Õþò‚.œ³ƒ (V¡o.Ó7¯†ì›S c2D=ú Q·êñïFñŽ*`¯çsý<øoä-aÊ †±Öblä‚bâÕåc`^>žòkÈÙN”Zˆê4Šè Š¼½Êø€-ÍöÚ9ð_?3ÞWtf ‚ X…Þ¬ïÀ¼ùc <b*EÀÐúé]_ÜI'(ã¶ð,ø.eƒ÷r¶‰¹q.ކZÝ!Šr˃Ë€cÃÇ!ùН¶^‡]Æžº*ª7 ÿzâ9àJòÀw)üGy‹Nv‡ûn‚b…ü#¼£ ,Ë_/ð¨R•nL†è^ÿ"êæ]ü~LàÏ;¾ ¦y®™Îïƒ!ŠrSØÒ|0-}‰r¶ÒÐ1šÈ ªÃx}×1f¢Ö‡e½p¦kà;¿<ç÷ø®¬”bcE«ÈÄŸLË_¥Ôï ›Õ5[VÄ<9)9=d‹sÁw~¸Ïï¢LE>6PA±B¼Ç7€yý»!sB¦†˜ÇÆu›'Ãê(:WUÞÓ[ÁuzeÍEØ0Å ù玹`ßóUȬ+éïé=ÊØóõ…2C\XøŸwZÀwv¸No´û¯Ÿ1b‹D+äwP–ûºû¸N…FF ¹>bž|ï—g;BÞ÷~ðçîÏ©Ÿ¸ó÷<^Ž (VÈMK¯¬Ë^;ä¹r¤C(Ø«ËèöNÔãoO•bCÚ+$NS"‚b…Bu—DwKtG…Lº$æÊI°oŸrÙèA±B¡ºˆÚñ¦U“Bß×rÀ¹}öNÏå¬.صA± ÄÀZø³¤…J™Ôâ†|Nä µ%ïKÞ\ ŽŸfNså] A«ÁžªE£$+Tú¦|ÕÿÃ7¥þæõ¹Àµg8-¦”c±7!‚b(¸yP¹äi !Óå¢}ø9 „HW¤x|'7mû’¿† ŠUè UU!T-M©OzÏ›Ë4Fˆ0(K;_.Sø3Ø7~‚'üA± ¼µ ªÒõóÆØmBê²ïw<6 XIq 5-E9[‰älS§4÷Ä›¥“$ë?ï‰ `Ýü)å=6ì1!Sœ UKFS¹.T5ïYªJM®¨Ù”©éêÏÊ (V¡=“ô8Á¼h eLW%g›®I—ÙQÏ|·uФtJ®‚¸g¦uúÃ’Œ¦\{ƒmÏWx{òÇÉŸÏîs[§¸ÏmDšú÷mÕ5¬»ªÙC ÓE£ƒ«;Ásd Ø.b$%TJ-ÄûЍêIoû‡3]ëw“Ê}×O'a—@þY¹xðêæ-8DÉz9h<°T×â±áÊô‡Aªû¯Š•äð_8–S$õ¢ŸLñ#¾&Ê´ ÉùË›½,?},éD¾ˆ”u‹ûí ùц{ztÔ´~"KŠmA±’ lI.˜Vާ@yÉØ$×'@üÈùDQ½¡¤|Å;L`_ûÁwÞž‘Ø €—ß Žc«9Ž­eR0´éKÔ™½p™A±úÃàk-Ó’—(e¼ÒªèH5ŸÈjIÊWLA6˜¿{C’¤‘ð€©(ËæO(lÿŒ™O´ÓÝ7à˜<_ŒA"\¬(ãó²W%•§N_âF- ò˜êÒñσ{ÏB°ížMRlýHða½àÈþ.Û‘ýhëuØ¥ï8¨«²ñý’Î}‰ X ûÚÉ#ýÊU§Ljñ£æ)e¥à°®~k‹·àP7löˆx.guñ\΢ʤ†`|h$Q·xˆLŽŽAn Y¨€{ßRR¾:eB]É [t*g÷£(Tˆ`*òÁ¼z"­œÞ›zŽ­Ê2è$¼ÅÊñ0X·ÏÌz–2®ÄZ()¡òZ •_?K9[)¶vDR°æ"°¬û7­ø¬;õ[€o¢!á(V\U!˜WMÌÞ‹"¶&Ä^DdÑÒ¸ªD/ØV½5ÆòÓG”â%_DÊ}ÙV –uïÒÊ™ORßÙ@q?¹ÙŠFS¯ÌKÇQÞ+w©äÑ)?z!‘ÅT“†ìU`ZöªÉ_üs6q$T`LWÁ´ò5ªNÉ`£º½ªTÖo‡NAB;²²¯<‘©º" µ7&CÂè…D*ïq7ò â«g( ªøJÎ)*>G­ß¼:‹7£CÐ+ÏáÕà:·eš$"*}Ä^Däñ5¥ÑÉÏï…Š¹ƒq Üv-ŸÑ›º¶¼3¬ X…lq.X7,‰m¢ÒAüˆ9DžX[¾qí]¦oÇJêR4‚Ü-”óƒmï\Z9½õÝA±’>¼Ûæ(•À‰!"“CüàYD‘ÒT|Çp,Øx¯—MB§"$àÍÜ^¦•ã©u鸼µ ‚b%ÑÙ¥`ÿþ½¬åº$ì‰}ú#¢jØ^|¿0^°~;~óÄÚØ”‘HÀ·gdùÌ'¨çÈ <A±’žƒËÁ}açx)ØÓí ¢iÙCüHÓãó 0-iP¿,&Só×# ¸ª"tŠ•DÂÿy`Û6]K\Q‡&ë: ß'öJ0=œJîd_щúÿéK½ÙëÐ(V"Ï /XV½)‰K­úŒîoê»O¨]¨L×À4wõ—á³óB/˜×¿K­K_™Ã;ñ+‘pnù¼‰¿ò²èv¨Ó2 £úOù”ÈÄuw#ªæ ¡RÙ»C©àÎÛ=¦òó>”ÉËBg X ‹ÿâapY‘+¶òèêûìŒ:D¡Õ¶$*ç œ« [-‚Ül2窂ʥÏSçÖÙ€)ÆP¬wZÀòý$ñ÷©H:[ôÄ´lIT-|Žò^;¶Xùìû¿¦–ÏgóœØ¡X»±­ý`–RŒï÷1‘×h, ¡òذµ"w?ÿJË€¸§¦CËÇ‡É IaûÞ«GÛVþ§e®œÂJˆÔ2{oóZñ£ªèÎ/}×P¨€ mØiM숯úKÍ.®ü2ø.oîÞ³Þ¢@Ãìî’L±½&í½ý°†úDKRÇ^ ÖÍŸˆ.TºfNÕuy^\_Ü@¡B‚<¹è’ëîg›S—|ùYà=»k†'ïxïKñX6L¦lÅ妯žo\¹+#«»ÇúÍ«³ÜvÓUµ&÷â2"SiÅ‹¨Jó¡jþp*Œ¬Äã=ðŸßmô^>Ô Âà)m½»¢}ÖU¦‹Â‰buçøÎlÓª ¢CTHze ‘ǧ‰7H˜‹¡rÎÊ9+°u¢XIC¸¬åà9¹\Ç×RÖÚOv(ãë@üss‰,.eˆ!‰¼ÓÖ SDW͸§>W¨&0-|… ‘Ö “ úΣ qâ’øÜ"¢kÒu&’ߘ®Båœg)w/Õ£XÝŽMŸŽáÜQm0Þ;¸ºyWÑʧ^'˜¿H3æ:C¤ ‘É@Ù Ä ý|Bµ7¶’¨ŽÃâ‰ÊrßÁ9+¡bÞ0ê¿|+5”ÚŸØË€þÜýPµì%QP×H‡¸—±.þRÖ–E/f{¯m‹M2œ¦‚rP&ÖuÆÃÔîÿF}Ïca÷‰ÔëÏÑÀqp1å\¡•îˆÈ×ÿ3"æ$ ±âý¨œñål%â'#$û^´5lÊs`[1qŠûüöIØC¹'P%7Uí–™ªšÍO)«7YR];ó‰p³N/xŽ­ûÅ”³—…T½Å=õÑdöÂ6Œbõ×8·Îûþù¢FUñƒguúâ•oÿñ£ çÑ•g±)†XÇ‘ÉA–yN]¿}sU­ HmDkˆx¿PÆޣ߃mï×”yiÿvˆë;…hÚ< Åêa+®BåOˆšQ=ªãÐdCÏ7D;ÍàÍúÌ›¦à ¿!‚2¡>hÝ—ª®ß¾DY·5µòW¢åq‚ëà2p\L)ã Ázr2Ѵ냕‡bõG, Fïõ\>ü hORCH»Š¥Z”òýùG¡jÉhv uZf¡6½KMúC€ÇoÞVŽ­_Œtý¼qA(ØûÄ{DÛþi¬8«_ûN‘É!éåÕ¢åýc+®BÕœ”÷:°JMÝöÇt-ºµS7é$*ˆ6_x¬?¦þ9’·5þéOˆ:³'VZ¤‹õ¹ rzoÊ:ÊEû蘮¯]gq^ƒçÝ60}9¨K eRеìM´-»,¦:$}ŸçÁ{ø;°nÿœRÆ#áQQ ÏÎ"ª¦b¥E²X96ÏÔ:.v‹õÁêš-+âÆ,M&2¹ð…s,˜½í½‚GÔ%ÑøU:зzQ4¡òžÞ (Tâ¢kÒufâóßÄ×Öí½ýP¨DFݪ$½ºžhê¶?&Eû¨ß ¦¥/QÎr++’"+¶è,TÌ(Ú¡ MƒŽ[cGÌíNDÈiÆV\ÊÙý¥½N® \®}«'»êî²K‘T"EQ ¼‡V€uëgTŠÏÑ+“B˜eïÑE€XQJÁ1¯~SR{½–“ibFDQ³VT8EVÔ熊iR±Þª2Þ÷lmcï7¿}K/˜fõ§þÊËØÂ‚ˆ2¹ÄôœH” Ú¡3¶¬ÌK_¢¬U:'òäÑÕ!qìj"3Äa HP÷¬‰ˆq†¹” ެeÅØ´‚ƒ¾E¯“&l$ÚöOƒ(YHá#™¨Dˆ~1ÑÖë°K*6y®éàÞ³+'ÄʵxM”Ò4è¸UŒ7ªxÌß¿ƒûTÁ˜aÇÖ„„áóIô3Ï•1¹l¤A4ˆñUW}‹¤b“m÷—”-§èBZ¬x· ì—ˆ3hD÷|½»E;7NÄÙJ±U˜¨ŽC““^[OTîCgDtˆ¥€¨þÍÔgöé+ {(æU)õ8±nBU¬<—õ‰“ïËØþ™¦òäú‚—ë?·œ§\ŽM*€ÑTL Hµ”z¾Q* :"“CÔS¬3´&S ö°Öb°oøh$ÖLŠuYÁžõ(Q•LcC—1„.—wšÁ¼þ}\þ †Ö}{'¾²Ž¨êµFg ,B ê‰I§ í6—‚=®Ÿ7.ðÙŽjbå>¼¨_œ@¢;¿Dˆ>Fðr›gâE:õv‘Ú ƒ¾ QO}° s±!‡ññ·ÎZ÷í-[¬¦PÞ‰c@Ȉï÷€ãÈrQ" e|mÐÜÛ_ðr™KÙ¸ü Ô5Ò!ù•ï‰*£ :¹¥ËØçÝMR8tÁ¹-àØô鬕+ïñA¬#ºÇë„(”‚–IX׈˘%ß;¸A܋ˈ,.ܺ`ÉäÝoêLMýû7‰m‹ë̦9þÜýX)A"pyˆ8–Š2pkê´?&ÆÔ®½‹1b+º«¨ø§§u‹GÑÈ!W@ì³Ó{›æ §þÒ\QM±®ÿ€&ÖÝ(èOêuplà&H1»|À,òÙœ­D”ˆzt¬ài ØŠ«àØÿ5FUw3ÆDW‡ø!³‰"¥1:¹»V­‡Øá_ÓœA¢¦fbàÜõµÑØs‚`Ç¡=ÇÖƒuË´€E1Œ'º‡GH®Ž² H)ûþÅ¢ ÜÚ¬SÔn!h™”R°¯ûð, àl&ÒP§e&¾¼*"„Šò<°%yÀ–æcÅsò•ñÃæ¢ÔŠj‡ãð2;W.\º5m»> ÓDì÷œ'ÖR¡å\¬üS~Q”0>ò²àý§·€·ðXw†¾Õ“ýãF/¨Ι(8K x¯˲×f”OîH+f?Eýaå[°ªÕƒ¸þÓˆ¨FðØ7|"XeµŒ÷Ø7³æ"`®—\ÝdÐuhåF1Œ×5î·mï¶IªNïZ¬ÜGV×¥1¦w{G^CØýj¯û¾ùUÝ®NÉU0ð ¢½¯X|ï´€çÐJ¨ü¼/­˜ýµ˜OY+&Ú—Ö܈@L¿‡Ë£«‹7‘±ƒ÷è‰÷=°¬ô”óƒÿôÖð+êsóÄúB1f鯮/LºXÇÎ9¤ôjiH Ð@°¹D•Ñ9ä?…)ül+&N,ÿèAjùé#*Ö>-r‹Ã„>⟙N@ħdì{æRê&Ñ­¢zCÐÔi,P¿ç<þƒ¤&æw%VÞ“?õ ŸqX×ì±wäÉõ„%•æƒóÄ÷ûp¸ÁB©…Ä_‡ô‰”eÀ{b#Tý§?­œ7˜ºÎm™†î…Ð\©v ˆé2N´ugÎm÷AáÜî{&`Í_–lI^è‹¥œ‡W‰¢¼†NĪ~š±(®Þ²P© øÜB¢¬›’öó~Ï/K}Ó£æÞ¦þÒóX©!ŠöÁa ®Ù²B´èêÀ"Ê;Í‚”¥jÚ ä†Ä€ýž÷ÌŽÐ+öÒq`ª. n°¦N»SŠšÍ-ÓŸ<—³0aÝ- •F.$Bß ”H¹÷,†ŠO¡–Ÿ>¢¬£+4ÔÛ£L1ý§$ƒBœgf(ã.º’+ÀкoÀ"I÷¹­’™¡ß±X¹²¿Ÿ$†ÁúNߪ;vÍÁÝó[E¡†„as‰2-=¤Ì¦,Þ¬ï rZ7jÝ1“býðBžP ⺽.Úr ãÈJÊ»íÂD’mŸØo±–ëÀçJ¢ïH¬¨Ë ž ;§m¬2©!¨u8ª: ¾k§R°»ßÚ 6~ð!·ôç;»*§÷¢æMS(çªÂŠ SÔ÷öm9úà=²F˜A=.´uïÍ ÔïyÏî ]±òü¼ÄH5d|`(úŽŽcç\\º%¥"×ÿS¢n|ȘÌÝÈó¼gM+ÇãÑóˆ˜LÉ ºÏ¿“Å:hÏú†ò~aNk3{lVï>+¥À;+÷‰õ‚¯0&úžîÂFUƒïúé$ìæÿLl¯wB&s:õ:Á±á“ú峟¦˜6+²PToQGˆ²È»-à;¶^˜(2ýa ÊÀìѱÖb`¯‹Àè¶ÅŠ»‘b¤á7t*ø{UŽ]UÝ Q‡Å‡Ê…__Ψœñ8uY^€§;#]—Ñ 0&‹R¶óð Jy>øQ¤ZúôÇê÷¼gwˆþfÈm‹•ûäá3V(Ô ió„ EbTu‹Ë M»ÎÔwo–ºÔeëò7&™–Ã~ŽL¥…¨Ç^%ºbÌEÀæ&ºjñèŠ@ý–'wRbEY?¸Nm*ÚH}ó}eºha£ªÝ__Åný÷¨Rï1Çøx‘É$m§/ï T|þ$uçl‚µ†¨[õu qN¬:¯ä»ª~{iŒYÓUàÍâîëÞÖ(Ã\8¼Ç*¸‘úvO­tös-|×NÖÆ.ý×È ‰÷ìÌxPi$k#e|àX?µ•iéÊ9+±Òß „@T¯7D‰®<ùûq¦kÁÿF…téôؤO ˆ0 bå>³Mð»UÊ䯠¨Õ\Ð2Ý—MÄîüw­Fqƒ?'²hé®’rU…`új0ud¯:‰†Ütl©“ šEÉÖê>úƒ /DjÓÙ0±º˜5+$ÄŠúÜàÎÛ+ø2Š¡}?Ag?œµ\9Û¦aWþkâz¼E”µï‘¬}¾3Û¡bV?ê/»€•…ü-QŒí.F¹îÓ›Ü ÀõEý6¨—“=—²ÆRV¼­«[+ÿ…¬WØP]©MKaÛ’;k•(½ø/зz²¿¦ÃIÚFyœ[giÕJýn¬,äŸóšÍ@×øaÁ䜕à+þ²Q¨@Û¨Óûé_Œ˜¢ÓÒ+Ï™í3[özh  v>8}£Ü_ L¨ Æ'Þ^#EÛ¨Ç Öo^Ybßï!·‡¡óó£Ä(×sr£ Û*Ú&~¨ßò_oßJq«'ÿx¡Óµ{j¿åyoês`ï½Ù M®€¸Ÿ™J+9Ûxk˜–¼„ïK»Ý×®]ƒ’’py\ÀúYPkÕ i5Ó fÍš T*Cî»5›¦N»SÞ«Ù­«ÜÝS¢Üö©2]Tp'™:nŒÌ?D Ý_å`Ê-‰•ÿÂ~ œ_PÔÉ@‘ÒT¸™9σ#k9ÎÊÿ‚˜ÇÞ B¿Ì|+°¥ù`^<ïN‰üü|ؾ};É>êÎÏÏÿÛ™ŠF£{Z´Xqÿ÷~¤Ë#2ß©ï4,Ó{5[ÐþO9?ørvƒ¶í“A-GfˆuJë+9w×{ýe€÷8@¦5 ^G·´ è>³m‰àQÕ==Uoöòq`-×¹É2BÃWh:”œ]Ì•SP9w U8xð 1¼`ààAô›o—Ñ*¯× G³³}öÙtÚ­{7:ùÃÉ®] >¥jÔ”I /×›³[;Wš†÷,äe¯Ÿ¥ŽþQ¬¨ÏÞK‡ m˜æaó̹O¬Èÿ6MD=õÞ`¡ÿã ïâa¨\<ŠŠñRu8SXX/ŒyaËkÆÓœœœúw< qlÜ´i_¿þýè¬Y³Àí–öV0! ÷¼‘{/D=Áoê†{­Û_xFšbå/8*x†uu­Ö—ä±5„ Ç]Vpç€$ˆéù/"J””M¾œ=`Zö"Ö‡@6l؃žDOœ<Ù-`³pŽƒeË¿¥C†¡—.]’ô÷kZvØ1ï[{8|½eZ‹€%¶e O’¤XùòvÚ(]Ë ÝœÞ"Ê“'RGÛ°ÓMëÞ’²Éw~/˜V¾F±¾Ïó0}Æô¸§N¡>_pö¦ ‹Š`؈áôÈÑ#’õÑÀвwG¡Ëõæì þ©@¹4i™»bïõŸ;€ýïoÅŠòÿ–T*uÿÅÃ`^ùžÃ ÔÌžR˜úÑÔß­^m ú ìõÂk¯½F<(YhÛõͺLÏÅ}S(ülêºm2°RÆl™ðQòߊ[œ B¿œªmðÀR¢¬<öz.øËòpÔú±¾Jd1Õ$cså$T};#ª3wî\ذq£`Ë:,ÇÁ¿ÞþÍÉÉ‘¤?)MA™P_Ð2)ãöZð-òo¦Hø}«¿+_ÞÁ Òµì1\ÐY͉õ­ùªjA}ïÓ’±‡-+€ªo^Æ=ªsàÀX¼t‰à« >ŸÞ|ë_ÔjµJÒ/ú–=?há/8|!Nm $@¯$3×Ï ’”Xys÷ Ú‰\ª&7£apù þ‰è'Þ!D¤§¿ÿ o-ó¢(^Ö,6› &8Y´åïòòr˜1cF?)úFÓò1ÁËô ú2,QªAU½™' âZš·\hÉþn:¨¦Þ}+ˆZ'ÜlæÒQà½8þCË'K%I-õ:Á´øE¼GæÌÓÄj³‰jÃÖíÛVŸ8qBr¾‘Å¥‚*õAõ—œ‰ãÝö —£ªÕ¢I@"«ŠúÅ_Š•ÿòqág4M; ²<ï¹}pØúýÌK Æn¯®‚-”çÁöݤ9LE>VL€¹qãüøã¹R°eÞ×ó²¥è#]Ó‡ã…mð˜+ÁneÍô¢ÀôO¸Ê"A]ô—bå»|\ð\Ýø~áÚË€çüε8tý?ÆûG• [Ü;æ€;o÷¬•À³rÕÊ8Ž—ÆË?Ÿ9ÓöÌÙ3’ó‘ºÙ‚—ÉQ» EJ“€ý[V ±ò^:,è@®ª‘Bž>c/Ã%Àß!×Å‚þg%a‹ÿÜn°í›‡× ‚1 2 lÛ¶Í$%›6oÞœ!¹þ\±i¶ûëgÏý»j(s9Xèãë7+®ª8{™ †h›>,è ÷¹í¸ø;¢C„|Žå¯àÍÅ`^û U8qòˆ½WõgvïÞ}–ç¥÷†œ¶I§ÚB–ç->×$Øû@D&UµÆÙó—^4gìMÅŠ¹$ü¦§ZàS€žó»p ð×¥˜Tдÿ¨:eý`Yþ:ň7ˆb%Á 6» $g—ªna7eX¯ ÑŠ:¥q@²ùËò‡‰.VÞËÙ“„4BU )Â=?ñË _0><š…øï9·Í6únä`…‘óçÏo‘¢].\œMʺ™'pf®}%äIõŽâw8[ PF¸×ãÿG¬(¥à½|tФkò`;!Ëó^<Ðß& šV½D·ƒ¹r ‡–à "È^-ì&I»Š %g“L ªêÍ„ ®ÊòÛ» Eb€ýg)O¬¸òKÀ»-B‡ÛÇ„,ÏsñP. õÀÑ£*êu‚eÍÛ¸Odxž‡*³I’¶™ªLÒ¼ \73YÈòüe—‚~uD‘T;pmJL±b® ÿ°–²nkÁÊâL×€5òË @m[ñÏ™8·þ§k-Æ ö$Í㑬mn·»¹íR¤¦WºÂP~1è‰ ItrÀžBá-7D«ëçº*TI Afîþÿ¢tŸ(c‡¡TQm`‹Î‚#{¦¼Šp(¥íRÖv÷:€·W ! Œ¯˜þ+¦XùŠ~Þ)h˜]¿]m!Ëóåg-À¡€È i'rTű`Yû>.ÿ „V«•¬m^’™,dq5A¦vW~%øclÍ™éÂæâ‰¢ˆïqü’óI@Tu[ ¶&GY?x.‰Ã€®Eï¾2Cœ¨6¸­L§$àÀ+“ARR’$mKLL\'ÉI! ªÖDÐ=u®*øC¢">ej ~‡±ÜxS±b¯Ÿ¼1¨êd VSô³ G-¥Œþ¾¢¼Ã¶Ýs1ª˜ÚµjIRÒj§IÖgÊjõ† *VÖà'dŦäb0g¹&ØŒ÷b%Äÿ?Uõ¦ äC‹þ‹GP×lY¡Hm*ª ®s:P¿+C`222úJÑ®ô¦é’õ™"±ž —À8KIЗÖäщ°9—Y°ìë+ÿµ³‚¾Q¢®×.JÈò|…'¯âp o÷t²˜åse—Áq|Í!¬ áiÓºälŠ‹ƒºuëJÖgòdamc¬%ï}à Ür0u óˆæŪøŒ ¯?ªÒš –W‡²~𗜫éƒQj@ÑYTœ;¾šWÅàž{xIÙÔµk×xBˆd}¦ˆOv2g¹a ¾XîuÎ%ÌݽßÄŠ³W'ðe`eªp‰$¸’< œ?â+]F·þD­­|¶$\¹;&"ÎÀ«P@ž=$¥ ½zö2KÙg$*€È„«œ•@Y&¸¿!pbEGVœÐo“èbA'ÜŒÅ_tmf¯5b–ïØñÕ¬qÐ( IØÒ¾]»5’öj„LŠèjÂênf|¢PQ&€ã=ÂdñÿM¬ØRaß&Q§6tÐd OO‹ôAJÊ:­E+Ÿ-+ÏŽÃP.Ä%11  ‰èjÌ c‡‚Ï1)‚²BäºÀn£aJý¿X•å º_¥LMï/dyÞk§#~éI—ñH‘ÉD+ß½ÿ›A€H‚‘ÏýÎUß>}š6kÖ,$ü%7&.T¬ÜÁÏé,×ÅäŸPOúü6rùnä zPUS¸£Ó¼¹8geÄPÚô.¢=Å[ËÀyfÓr@$^¯‡?˜Ld"l¨V Æw!TüEô1[…, Y]t@²Q¯€‘e`+…ݳ’§'V~’óJnfhHyšx¯‡»|Às¨"33^÷ªàje0`Ú´iD¯×‡NÿÑÆ:ˆð®à‹•LmHŠ+Êx¹,à*¯p Q“A•(Xyìˆt´é]2ÅZ¤,®ßãYu 2pÐ@úìÁK£ÑÀç3f’zõê…”Ÿdº(a ô?C>Që"À”ñÖ¤¸ aïʪR3f YS–ñËOš¦O‰U¶ÿüž_nº#’dìØ±ðâ˜1A¬˜èh˜7g.iÙ²eÈùˆh ‚–ÇsLÐË©´Y†å_máĪ꺠¡Hª7NÐÁ²,/²7öjPÖo€peŸ ˆ¤1|Ìøl:‰2ç>jzzú¥eß~KÒÓÓC´ û‚ @¬ˆR±¢Œ·`bŘ® úVD _ªüGÕwÛ³—A$£©ÝvQªE)›·–÷ÊѶ€HžN:ÁêïV“®]»¾¨ßÔjµðʸqdáü jT«²¾!rŸÛ ß^ ¾‰pœ k¤2¶ªh†°b%\®-®ürÄBš÷ö«lï™í¨!Dbb"|<õ£¹K/!<ðÀ§wzZP¯×ÃÐ!CȆõ?’g? ‰\B¾ãY¡¶@V€l;2y`¢@J©\kºZ[Èz'Ô®ÎËðp…ºxûô&@ß$H¬X#+Eb]Á2æRŸ8WUD÷1eZsQÊåÝ6ðhð¬OÐòü¥y}h°ß| ¡ÄŠ·– º§£Hîpg.ŽìFˆ o†ýæÊIÀ‘pA¨Ìâ¿â:¹n­c݇‚™Yˆg}¹T'Wòú¢Œ5—L²dq­w ½'¹¨*©ˆõТÿÊñZ€ áY¹­‚—é<¾fŸ}Õ[ƒ¶éwdÙ…Èä‚$È–±–bá.Èc„»È[#û2°ºF£ab•í½t´‡8$|"+[1ÊuÛ2Ͳìµ%”ñ~|ô{³ì¢P (TIõ?UÄ¥¥¢Œ†Ø îr k+ÓFr“'ÕûF”Ží²S÷ÛðsY»‰U¶çâÞatñKÍc‡þ'39 ñi `Á]ëj£ éÑCf¾¶¡»µll$w0e5q2[3%x /X{Ù81Ë÷^Íne^0Ú7bŽŽèsŸMßõ ŒØ,¸«Íe¡’üʺÙÊÇDrS$‹$V×Ïã膄Wde)5Šmƒ¯ä¬¶jþs”ÚwÂÙÐýˆyäµ»ºˆ)Ó³/ÖbÅ9*kGjç"J ØQÊf®çÌÁá (Ë笄-LùE¨œ7”r–ûMÝÃÏAlïß±`ad±Šà§ì±i Væ yADG´H˜‰•½\Rö°æ"¨š;„²|‡P{_ˆ{úcw‚I†buwð~PƱL›²T¥òk¹.°•ÒkÏœ½ Ló†PîFàroj2{Aü ÏÉí&¸%zA^µ_±¢þ2­<¶úLQ:QU!ŽnHXÁU\‘¦]n TÎNÙ¢³ûMuzgˆ:å­gÌ—Q¬îN¬ÜŽˆî`Š˜çÄ(—­,ÂÑ +˜Š+¤jïu@å”)Ücܪ†í!ñ¹E„¨oíL‰Ìbuw•hôÈJœÙ^„_ÄFÂP¬Ê/­ôÄœñBÕÒç©/gOà&»µ[@ÒóKˆ\û÷¢1‚P¯‡odå‰ìÈŠ4Ûù_±*è,÷H˜#<þÒÜÉÛɱ`Zñ*õÚ¸ oÆÿÂ2¢0&ÿõß’ûÆ0+gDw2™.J¬Èj H˜À•_†`¤: ’²‚iÍ›ÔsxMà"¬¤:ÿâ2¢ˆ»yªOyTÂ1«»­7Ÿ+²ÅJ-Nç¶WŒÄ! Bñ‚»eãdêÞ·4pVl Ä¿°”(“þ¯˜Å¥¾bu—ðŒ/¢;Ñ‹#V¼ÇÑù‘ð‚-ÎéŠv[·M§Î-ÿ œ`E%BÂó‹‰:%ã)౩[Q¬î6² •Ð=(4p;GOYy¬€ á‚÷ÊñC¡j»ýÀjÿñ£ ÊóšÇ@ܨJMv§~Ÿ| c±òDì _®6ˆãsžÞcà x[0UWBúœGWžu|ÿî €½ ¬1@숯2µZ  ˆKE±ºû)>“¹‘•Z”b ëÇ üWއÅw8Oÿ¸Ü²â4@ý“(5;äóáúŒîo¯È„odÅsQ‘ÚÉd¾ö‡™(Çà‡„X]>Ö+\¾Å“»s¼uÉØÔçÌÊõÌÇŸÊŒ (Vw­þ”Ü~‘"+@±BÂf²ËƒçÂþáôMžËY]Ì‹Æ\åt•Èä‚&ËÆÈ*…Z¡I¬X@p€»žœ«*ì¾Ëwídmóüç(ï ½Ü©á»gE©2bÅŠÈÄI7/“ã(‡„ÞÜýaûmþÒ\0ÍFùK¾b%“¹#µ£QŽg T±ó$ÌðœßMÃùû˜ª+P5oåL¡óœOøîYyÄ&¤¬H{G +$ôaK/Su)ü¿Ózªæ¥\Ùe+‘#«ˆ}y‘râ!—ad…„ÞS[‘ò­œ³*¿BÙH+Æb%Ü7BX‘RMÉ@T:íÐèñ<¸Îlލc­¼Ç• †SæÊI+1 MI¤v8ž//¢L‹#ºó¼+dzGÞ›lÔï†ÊÅ£©ÿÂ+ÁÅJ¤Üx’+¯(gX®‹ U\Ù?LŠ\¥öAÕ·c©ïÌv++¡¦IT.zŽúó¢XÝ.òø”Èží8+€³Š“ïL— ÊxÌfHh0e|`[÷!.ÿÕÇ^0}óõåìF±º­‹® “Gtãa‹ÎŠV¶¶Y‚Ý‘ ®]ó1]EG[°8L+^£Þ“}Ú.¬Å ä PƤFtÃa®Ÿ‹«l Þ F$4i³X ZT¥ÏìÓר¶GUõf‘1¦<˜¿‹znÅUî>S$Ö]ʘ‹†Ej'õ¹`変<-äúà\U8Z"âá÷‚eÍÛ(/Μ9º:D=1iQªÁ@(ã®ì0Åç)Éíæ»ž³…©È±ì &–RÞç"ú‡F XýÊj ‡{.îX±ò—œM¢>7µNð²‰Lú–=’퇾ÁD¡ˆhØ·ÌÌ`L…¢•ÝåeB”êÿïJ5(j6EÍf Ø ü^`Kó-Î_Éù~Ìõ³«ýUWhèo±Ù¶Ï¤Ôã$†îãP¬þö«×èŽJyØ«§AÙ¸ƒ(åkZô¨°úGLDœÉÚ¹]à<ºR´[eB]Pgöüç?Ti@Q«9(j5 ÀXC}n`o\¶øøKrGú¯Ÿ[ª{nöó)ïwd{¿uŠÈîl÷)üŪZýˆï°¾KGâ”;˜Eñͦ Œ¯ƒÛˆàðæb0ýðoQC“¨GÆr‡{TD­eLPÖÉ-ÀBXH=N`nä[œ LqÎD_qδPÉÄá<ºê$øÜâžúàß¾ô„½XÉj‘ÉAìW.ÅÄ“Ødídž¾M_bÝ%f IDAT6 #­(°~°¬xƒóò¯*õ³*£K@“h  ª×TõZ| Ÿònp%yàÿeì=ßõsïs¶IÖ‹óô†¥¼×Ý?Ë_|n,ëÆÖ±ûÂÎñt‰»yÌÏ»ÞÎ^º,®ªzÈÏSçÏ;(ZÙ2Cè2ºc®@D˜•„C+ÁybíF1m0´yº«¢f3QûœªÑ} ï< b†~1.iÒnRýí}$aèÝy,Ñ6zh©\/ÞJžËY]Ì‹^(æÝö[<( ÿÕ×Þ%`Û>#¢—¡´ ;­‰ñU±Êg‹ÎBÅÜ]1Œ'º‡GÄU„KÙP¹hó¸Lc„¤ ?™1^òþâ­eÀ”ü÷GqÎZ_ɹ>¼Û"\ Q­ Ä=7ï–|¥ˆ„¬LiñØ{éP?êqö'Zƒ(å+j5uZf¡ïÚÉÚ8¤"Á€+¿¦å¯R±ï+EwB ‹©ê˜j nöèúp–àŠ/€ÿ—C{}%9ò^{PÊ÷—]Ó×ÃiüÈùDS ÅJQ3#â;2å9ðåMËî¢Ù`|pxß²“xÐ B„PU‹ÇPÞ+n6uUõf ½·_HûR›òØøïá‡(¥À›‹ÿ{1Wá/>Ÿå+ÉiKýÎÀDÃUW jÞ?j!‘ǧE¶XÉ´FP&7ŽèCÞœÝS4-»¿#Z„Û¤¨ë¿ò2Ž®Hà&b'˜—¾D9[©¸†ÈäûôäNŽeKBÈãk‚<¾&¨[<Ê@;ÊóÀ›®Sœ lInœïzN¶ÿFN}Êxï¨ Özªæ¥ñ#çEµ7·#ö¬ë§¶rd¯:ÉšÈUüÎ~"ÓE³Áwf;˜VMˆÈè ÷¬‚Qù=`Y4¦ÀWtBô •Ñ!úG_ŠÜIÏ_y˜â À”äÖò_Ï9æ+ÍMÖwE4$Œ˜Onv8E)ŽT¥µ8Ù«"{ÊùÁvhÚõ¯2º‚rF¹HÚ3ãÛ7¯ì•‚P©ë®sdx%29È“ëƒ<¹>h2{@2p,°—-ÎÉù æzN¶¿,WûW×Xx * §ñCçÿÞ%‹<±RÔn޽\'7kÚõM¯AË ê‘—ˆéÛ±¸w…ܹP± X—¿¾ÄsùðƒâÒ¢ŸžBn÷’kD W€¢z#PToš6ŸeàÊ/ÿ²Vr¾ƒïzÎ!yÀ7P¿ª–ä ›ˆ˜ÇØÅspX6ÖÅêû(ãûú)ý\§Ö¯–V‘AÂÈÅÿsYU¸yPþeÿ€[òèÐgtI6ô|£"TÚ…,Ò:Ñ@“Öê @)¸ö-í&S4‚¦nûcX)‘ gº¦9ÏRÉ ÄvS¡ŽËït‚ÍÙJ@Y£iE(µ Y$vMÃŽMqXøïÒ©õ[8k©èvB ¦ß”v2m VJ„â;»*þÓúKs%g›±Ý€vÚûŹƒì:ð-øoäô7U5ÒA%â (V·ˆºY'~…çÀ½iI4Ƙj×ï#‚•aMÐïÇú©­L+ÇÓ@=;H´ \aìý/Q¢~¶â*Øvðåñ¨ã !¡ÕÕ"R¬äÉõA•XG‰_£«c«s9‹4Žó«š<QŒBÁŠ˜k9`úÏÓTª/"¨ª7ƒ˜AÓ‹rñ—cÁ¶úm&дZªª×6äÚŠ,R;‰¶ywÿ åXpí˜;H*ö} ÷¯Â½Í± 8w̅ʹ)c*”æ¤6º:Ä ›MˆZ/Jù®= ÁWr. *Iä ˆêýÆðPl3+VšæqÄø}tuúÇåléE‰Œ ˆ4½"¶&VLÂ…ªYý©}ÏW¢?Aÿ—MP ñÏ}MdÑI¢Eœ¶=s¿ü÷À¨¿}ÅJŠ—ÿÇÖY’9Eô1?ôKBT:¬˜p‰¦¼Nplø¤~ÅÜ”©È—î ¨1BÂÈD‘TW?yœ`YõFÀ…\]´…îÕ Y$wmón¸ø;<ùûû1ÒIî!¯Vâ}A@&ÇÊ e‘¢¼'7AÅô^Ôqdy”m%J-$ ÿšÈk4ÍÛº&±–ëÿݘ‰L¥E± E4]q$ùÖ SÊ2’±GÕè>ˆ{r2N*Böúy0Íl7ÿ圕Ò6V¡†øa_E-ñjõýÜç¶N øÄ¼ÑCKÕÍC{¼‹h±’W«ʤ8¢ü¦êx²VJkRÑæqˆîú VÁ™®mÕ›ã+¾êOý×ÏH?µ¾B C¾"bž’c‹sÁ²é£€ïS•¢Ÿœ4<ÔÛ”,Ò;•¾õ“8þyb×—”·–K«ž:Â#í¡ RöJ°ÿ8µUùŒ^ÔufóŒP°™¨t8b>Q5l/š ÔeóòW)åÿÎ\ì£ãI8dcx±Ò´ì÷DþØq86Ϙ(5» Ý_ã}ÏÖÆ’¼µ §Õ*ÿô1ê<ºêd¨äÞüå0Å"¢¬ÛJ¼þÆs`]ùæNÖø7ªÔ5[V¨ïím,âÅJfŒm“.Sq¸ù#®s[¦ùs÷IÎ.C¯‰E†öÏdb I$’ª*ûÚÉÊ?}”:[J™óeºXHµ„(Ó2DµÃ¹å £çrV—€ÿ°B ÑOON&²ðæeØÝômú¼ƒ^ø_,ë&SÞm—”M„ˆzbÒ)cÇáQXCâÁË’±Kʧ÷ ÎãköÑ{Å@“ /,#Š”Æ¢Úá9¶‡–¥“Å>6(’ê„M›S`·P4l c2Hé%RIÌšàøizŸè~“×IÍ6cÏ ™ZO‚‘7 ¹9¼Ç¾3ÛÀyä;Ê”_ ÙïPÕH‡¸á_™QÜG ™+§ÀúãûAi¿Úº÷fi:<Ví#+ 29è[÷ÅÍû›à:µn-“—%͈¸ë Ûû]«.¨ƒêÕÓ`[ónŸ²©QË„´Pi>¸"þùÅ¢ WY¦oÇÑ`D¤Dm„¨~“;’0ëYýÚˆÛôÛÞ9舛`þášøÊDfŒ—^½Ý×ä1I¤jåëX/VV€`Ë À÷óvpÙLƒqAU mû=õøÛûEIJû;¨½ L‹^ ¼Ç”ß{ò="©vm#«_— Ú†®@OÜdè¬Û÷ï®–ê«Òª¦Bâè¥DnHÄʺÓ”R`KrÁµsTÎìC+¾x’ÚöÍ ¡’É!¦×$Õç]ñ…ÊëÓ’)k-Ž ·é÷ úžÇ²Fܳö»ÜQ •‹žC‡ü±=Þ"b=@w+ðÖr°|ûšÛWrV’9e¤ö¬=õ8¹z|µòäî9Ž{¶r],Ä þ‚(늀”2>°.yùçÊ‘Áø}eRH|y•&,Ç\ü}e7hÊäFÊkòÁĺí3ªª—)jÞ´ÿkï¼Ã£ªº5¾öÌ™Þ'“ BSª@hÒ)RHh A>¤ˆ ÐTPŠ¢¡$AEPQ?/PPi“d’ÌdÚ¾x¿çÞ+¨€™ì33ïïo`ëÝg¿{í³ÏÚ¹€¶&ýþú’K“ËggA±kTÉ%yäýr;yN,ñ]8f’k×óJ©¸«7#ÛØUòØ ¨øÍ9kÃeT$iÈ>êÙ¨5*"l^…©Ë½x[ÿg+Ã`€ Þ˜ÅCåÅòÝ*PiÈ’º8Û–¼˜‘¤hœ3/åPñ¾—~oÅFel›2È1e³,ŒŠ‡BT¼ý_SÊO|2%\¿a²)«E÷-0«? iÕ—”ñ'\¿RqƼ,’÷D§kŸLζ2•£!D‹!˜ZOö´ÌL©&û½/0…Å;ó2†ûµÑ÷ÝØÿfõè÷RÁÆiðGLÌLR“¡Çxr>²‡Ú$§@ÅH˜­”dî:A?óm¦jÔ^V¡Kò¨à•ñÜ{ö«°–;†VƒÒõ½&ŦüxP]UžÓ»—¼5wE¤]¡4Ç“%uq¶sz&Ó5ê– %剶^»Ÿfd3ãÝ3L£——Q]9EùkÆpߥð¾¿ÕÕ¿ý ó°'^e1Ú¸fõw+™P]]'eÇ÷Î)É\”‰-¼¤š·’mÂÚ”øI¯3M¤³PS&‹ ƒì×3Ûä MåøÑ«ïô7”÷Ò=…¢¢V2ß1‰9ÞôIIŽÕ„÷P¼6‡¼îð¦ÂV›âî[˘ÖÛs1žŠ¿ÇØ}Âa´î¹~Ü_nÉqï~>¢O.¨´'[úºÎiÛ˜¾y¿Äð¨TÙóÖzȘjïfÆ~3ˆéd8Aä~穆…;òpo{+ñ7áeÆÌ誃'ð:«+K—ûÐÕâF ë³ åî]+êFzW©v3²Žyfiµ¹{™ùŽÉL¡³BÜ0¡kÐéÓ„™Ìœ¶äM…µš,cäeEäÚ0uŸûË-9aŸwô6r¤¯gJG] B×õëFß}•~IÁ²|$ãz ë‹7ÎR dòØáH)¬°V#c¿édè5‰ù~ÜGå_gïóœ>Ø*WŽI{Mé¡Jl-ë8çSÁ–™a?ED¤Ðš(~âz¦L@oK˜Õ ´F²Ü5î\ˆû®nİe|˃¾A¦äÇwEÃA¦Ò¦U_Ò´êÛÃìºHÞÃ{¨üû=ÜŸ›±omî»Ì½î$Õ»Mö±zþkíZÊy0þ1¦ÖSÜøu²½ŠfhÚ !Õo’?÷g$ã(ý&ëÝPYÉó–QËg3UôÜ·£´Õ$CÏt2ôLgÁ˧ÉsôCòüð1Çøø«™˜‘®I¯çMwÜ7[ª+ÿþÂÜ[J%o/›îŽÿkTFŠÿ “b¬;Åuå7ß¾“)ã$$íf̾NÒYûØÕ‰ÌÝï}‚®‹äûésòž8°ÍûË¡Tî÷‘©[ôŒ-³b ²öʹ톒BoŽ ÍB!òxŠö®äTE¹+ô¶ßßQaëïÏs„Ü8æ³Sð¡ðÍáÏÿýkÿ¯?ÄÆjPR‘ª^+2ôLnT‚f~b}ÄU¨ð¹ÖMÇ =Ó1`VâдO&mb‡ÃÈDåàùùÓѹ+‡rß±OŒ(¤ì»w×{ì‡äL$[ŸYa_h*NrLÞÌ4·õø0+±0…‚,©O&15ö +­È*wQþ›ò’m F‡ f%”¶d4Û•Lé‘··ä­Jæ'>C2*ã79È–ö”ðqÊ+ÜT¼mÁQÑŸ-ü¾Ð\œÄÔúJÿ·G&Ù'½šˆ»¨`V²CÛv0é›ô~™¨\‚Å—¨`Ó^´å‘ùÁ’<$ä¢nÜ‘Ì]'¨DÇá=ûU ϧ›e°Ð¬IÖ•¸Ð”4dOYÊÌCæf’ ®²8yU¹„J )oå,+D2Â1`5&²öÉ´†S`­uÓ•MÀO…kïΈm{ÅJŠŸšÁ¤ZM…çĵqúFÏÉ}ãþÉ¿¡r4$Û˜gБføŽJù¯?€Ä†MÍ–ËÀ¹úH¸ I¶kþYÊ]=¬Ê¿3ºz‚¯Oñ3¶3R‹½>&X’Gy+‡ð§ø¦þ¾¡MrŠiȼl…Z‡Á°4 ê¦ÝÉØvXd"|T\<ªË}y4/Θ;K/ê#¥£YÎþþÊŸ†Ü»Ÿm/<æx² yü†óÁT:²_Î,©‹aT0«ÈÃ8hîû*'¶ÂMÙ÷»Ÿ»òÜ@^öá’ÃǦ‘†®ýPÒ7ë³TtîC[ùŽï_±ßÖ‡ -úϽî?_§ÍEçCÙL›4ƒ)Ì`0ŒrÏPÞ‹iUÚ3¦W^: ™ºMdºÎ#+Ü뇗QÞªp_[ÙìÿP6S˜Âó‘»r(–þùa¦P’¥× ¦ë>Žp]*«ˆGrÖ'[ò"g¯"Bžb*þð9ž·¢÷|ö&q’r=+Vƒ•liË„Ó`Y!g.Ú(‹|¤üùs«r6&çÛ˜þÎ 0*˜Uô mÝŸLF&!U;é¹v/ç¹+úýÞkÛƒ‹ªaYg÷œÜ7ÎûÅVáùP7éFÆvÃ{ÿ±š2ß99flÅE‰"ج‚m¬½—û.ýˆdˆäj#™:ŽdúÎ#Iaq"!6N~*X3šû.¼%¡!çŒLrÖ›o)å­Jæ¢ßH]«U¡uØÂ8I‡YE=¡Â teõpÎ+Ð:HØ`WJ¤¿mPоcj¶ªNs$ärÏPî Ã9Än¡ª«7¥¸io2ÑÕúÏ|K‹'HÛy¶ü`V±CʼnϨ`óTNȹpÔÕ›‘áöLÓª/cüÏÁíäzw±ðAjê:^oº{N'˜•ʼAE{V é2A¡5‘¾Õ€$}뇥º-‘ÿ¡2º9TŽôMLÝ -0+”d>ѯô›Ì=È„¼PÙë’¾Í`¦kÓŸöZ1‹»€òV Þ6Li©Nñf1…ÞŒ ³‚YU5<à'׫“Ž{Ï~ÝÙ'šš-Úæ½Ušf=Hr&Æd|? üÍS…O†ýçZF?ý4F%Ì Y±r-uQþšQ<à:dȽâr4$}ó^LÓ¤Iµš)¥ØÙx{i›ÒÿÊøVtöa˘¶í F˜AðÊ)Ê]3†s¾Š˜Fc"mƒŽÏkÝ>[ݸ#)ãêDyy奼Fpþ)±yW)á¡LëÛ³0+ n.øù lšÂE_Bn¥¹ië¶™«®ÛêiUÝV$Õhu•Wàâ Ê[“&|Œjê$µß¿!GÈaV@Þïާ­@ˆh@Ò’¦fóSêšMzIÕSU¿… õ#þx|Ù¾TüásÂǨ¥× fè5 ã fDáùü-r½· bDåSÆÈÐfhŠeøâìHý/ðPˆ\ë&õžýª…Ð@JrNy‹Iµ›a\Åè (t]F‘¥Ç4½F8§» -¢ýV¡ ˈ¥-Z“Ø@BAreÌá¼¢ã fDaè3ŒíS»#@Ž(­ÕÉz—V6þÂsä~ï™^Pfb2¿¡é]ø¦Èm«~dh5(]t¥_ïø¨â‡C˜S(Éïçýü-³0,®¡÷R×/XÑÏñàåÓfä`XÚF]ÞG6€¬Æ¦¤"ëˆåŒ$­Ð8xÐG…s9ø Ì ˆ6,Û½«û£ÂrCr&’­ÿñÇÙ¯œ ²V;¡Ì ȡ»*Eß¼ßdÈ ]§TÒÝÒc“è8J>ß|ÅŸs‚À¬€pÃ’Ôdµb©±ãÈ$dÈ Ë°…÷)õ6áq¸¶Ï㼬‚À¬€pÃR(Èp ÈuÓ;ÈÔaDÑq”ÿøá|ÏW;!Ì Èmç7âY†û}€\0Ü=û+U\¢ð8Šv-ãÁÜ ³²AÓª/9î[Ç„wÀˆjÙF¬`$xÅýrm}´„‚ˆ³rAÕ¨9¦¾ÅTöºHŽT»)Yz> |‹Úwþ{Sé'ë!Ì Èj‚p&’cÚ›L›Øñ+dˆFßc¹Øk‘mø«ׯTòΊd(³2D×u Å}™)´f$CÛvé›÷}Bte‡³³*Ž~A`V@ލníLñî`tƱ‹eè‚E’I|Û>×Î'x¨è Y9¢´Õ$ûÔ×Z4Q0ƒ•l©Ë…o†<ÅT¼}Á縬fä:YHj2™Øžö4c*-ª¾ÊoÔÌ]Æ&ˆŽÃsæËΞo@˜3ÚÖýÉùÀ6¦Žo€d€*ÇÐgF®ÊÙXxÅ{ŸçK'!Ì ÈeBŠ{ ƒ’’S P¥¾JC¶‘ËSJBãàÁ¹2åÜï…(0+ ëIC£'ËðÅÙŽÑ«™B×:€ØAª~ YúÌYcn•îYÕŠÀ¬@ nÑ“ÚÉtïØŽl€ªB×e ië‹ï´âþrKŽïäA³Qe™d¿&Í6èqF_€*s YS—tÃ7€®óy¨ÔQ`V bV»R)áÁL¦©ÙmªAø'k5² ](|;0XšG%Y‹^€"0+A(ãëQÜÔ7TÖ¾£ÊaGs[2¶½—ïIDAT%¼é2÷{©(cž—5¬@„¢I@ÎYï2cë!c TõÛ¹[ºð÷WIe½ A`V b‰ÑFæ´%oÆOØ€î ,zO!uõfÂã(Þÿ ÷ÿr‚À¬@D¯€µ'ÇCYÌ:p>Sè,H¨4˜¤"ëÈåŒ$Ø@8'×¶¹œ{J! Ì D4J‰ôGRÂû™éö1ˆaÊArÖ'{¿GÄ_ÖXô¹ß]>ŠÀ¬@4¬„ V2 ~ôTÂCÙLÛ°ë.dTšNi¤kÔ-[t¥GÞÙäýî}³QSh%4$ûÄ—9Æ®aª„[ðÏAŒ‘uøâ9œ@-z{1]‚(0+M¨›ÜAŽw0ûˆg˜*®nް̲%/Y£×MÅ[çå¡DY¨šd Ò¶êGñ³Þfö”%L{‚ÈDÓüN2¶Ö_tÞ³_µ(ß¿ ‚À¬@T¢”HÛnÅ?ü³ úSÈ ¸aLy_²×GÉG«yàâq³Q[iIjÒuJ£„9{˜mÀc¨´ÀìiË™è§<$WÆcœ|¸¬f¢µ–t]F‘ó‘=Ìž¶‚©nENÀu!ÕmIæSÄ_Ö˜ŠÜ»ŸkE`V PJ¤m}7ÅÏÌdŽûÖ1mb‡ÃH ø;Œ=ÓIS³¥GtîCßú~:A`V ¦Š­[:‘}òkIÎiÛ˜¾yß'ðq1ø«EŽuär=S‰¿¶Æ•¹€‡ÜùÐfb ©v3²ŽyvQµ¹1KÏéL2á0¸†_9ê’mÀ<ñ—5–Rqæ¢Pfbu ZÈÐ{29ÝË£W3]ƒNŸ"+àÿ¢íBú[ï|Utž“ûÆyn‡ 0+ÛKh‰Ô-z’-}]„‡ßc¦.÷™Ñ4üó°'Ò•»ð8\{žæÜ3f‘ÒQLf»æïcqc^`úf}–2¥„ÄÄò„e´“mØáÛðRQÆ<Î~ˆ³àw˜¤&Mó;ÉzÏs æ`ö¡‹™¶^»Ÿ™ØDݤ™:ŒL‡ïÒT¶w-©ªy€sŽ,€ˆ$èú*޼Oeß¿ÏýWNÈ:V]ã;¶ÛƯIƒj•CÈç¡‚Õ©Ü_ð‹ðX雘ºA[ˆ‚Ê €k£´Õ ý(~f&«6çf»ûQ¦­×þ1†äDûÄ¥Ö‘mÄ F ¥ðX\ÛçñPy DYpÙ^‹t]Çýþ -«Í?ÀìÖ2}“^/’¤Er¢©vS²ô|@üqöâKäÞ¹d /ØÑÏKg¾&ÿ©CvOΗþ+'…„mÀðÀCA*|ù¾ ¿®):–¸áO1MÒˆ³àŸrç“ïô×ä;ýU/ÏÏ? _„YEº¦…èʪdÎ}åb'Sµ‘œ3³˜ÒV¢À¬¨\‚¿’ïô7ä?¬WŹï>òçæÀ¬"ï×ïPaÖ|á“™¦nÛSöɯ5b2x—mà£Ó(ãê.®éÚ'LDŒ{J)pá8ùÏ#߯߯¯øõÈÄ`¹ ‰’9ÚvƒÉðÓþ§ËŽïúî¨âÜ7 Ë÷m CÏtˆ‚Ê €ª%Txü—r(xù4ùsOOñ]þym ïñP••ŒàeE”»2™KsÅ¢P’sÊ[LªÝ ¢À¬L0@Áü³¸r†ü—OS0ï—9 O\çuתÄ`VUƒïäAÊß8Iø¤¦Š«GŽÛÓè! Ì ™Vb>q×or]¦€ë" /J ƒ- ï>É©Üï<ÕÐýå–ÑqÛ¥v7§<¾ŠÀ¬à*¸ßK/¤q_Þiá±ÄÝó"Ó4ëQ*| ˆ®¸JKÖ+˜Nä¹²çÁ’<ˆ³€«‘jÞJæÞ ïn*wQñŽíÁÌ ®‰þޱ$‡îüÞœÏûy¿È€ 0+¸¦P’%mIS¦6 ¥èýgyðòiˆ³€«QÚj’}ðáÛ<è#×Ö¹œ|fW£I@úýˆŽÃwù•~𢠊Üd¥Œ€h'T^Lù+“yÀ}Ex,ñ60U£ö•üa¢Ó[È–ºL·rº¶Ï㼬¢À¬àjT:¹ËØÑqÜW¨8ûÉ%PäÆÀ6 fàþ Êq$÷çþ,<{ÊR¦m7¢ ²€?¬ÎU²\ΘRüíH®]Ky°àN™™3s¸|Ï{Þw (Jå¸Á"Xàœâ³Â¼ˆó­«PþÉÀÑU¬£¢Ô9LmW@iù½¦@¿¤”)<ŠðϤ”¥(u]¥ª|‹-Ý}–Àʈë©nCøa†{€4­†'"ÌFøóWà¬äžÀ8n ÓbÈ:øóÃf'"¼ì_=ÍT”ä ¢«T á}œØ¦uÀ·Å× Æò'„oVa¹Ãu>ï,§!¼4Cx8È Èïï‚ð–îX^År†Ú&@w`0ÂD 7 5ÒnEQ”æï·‹°8Ã$„çæDøtOÎZg X Ï lBXã¯÷†ã„„µDÝëp  †›ýñ!Àµ~ÿ5ÔnE©jé*Uå7àK`–XÞº¾ áj„ 7¶øŠåz )ÂÓËþ€³pKc脃t°üÝ_é‘jAÒZ¤(ÕˆŠ®Ru„÷†»A”è ÷°tÅrHÄ•æÀ›XöÄrÂãÀ‘Å®‡Òü€¥‹ïÑK[`JD#Ÿ¢Ô9Tt•ªðÐÈG]MŹNÃðÎçj€t„¯¼?·k|Y«}¾B +П€@€p0 a>Â* Mõ6LQ¥îî5ð; Âl `¸Ó_KNFXƒðð Ïb˜„Þ3–"ä#lÀð$®÷ÀMù¦ùãs½ "ßǞ5ÓlEQEQEQEQEQEQEQEQE©Ï¤Ôv”q³Œm­åº(Š¢4( Âàz|ÂÏÅsè–Ì¥æâ™×p‹ŸCa¯WÛû9® ”‹–ãF»)Š¢4.@ø Èz!",ÄMV3 á[„m¸!Á üà3”OrD·)ÂjàšÚ£(5ŠVÃM^3Èöš`y˜<†e ‹ÀNÀ•¸!À‘óâGxa Â à€¨;0×Ï{DµÈÄpŸŸ‡w&nT@!ÂD„+pÓ>*Š¢Ôkv÷ÖèÉþ¸·tó0LB¸IÄÃsÚŽBÈAX \ì°áC ÷ø™Ç¾J,]a†Ç¼…\€³˜K[ºÂL„ Àßýd;Û€ý=ø:ZýCQ¥zá­kĹã^÷âöë.)NSÚ½Ð'ÌÝ€ƒ>õå™÷ÂhŸö/´7SZt{øý;qÞôóÇÿñùšûr®¨Æç (•FÝ J"„gõÚqn–±´ÂÒá ÜÒ:±&ÿ á8„eÞ±sŒ4áYÊú¿{F]wsëZ®õ–òB¼»¿^lÁÐ.¡–)J ¡¢«$Âfÿwºÿû|„o€Óp“/&(ž`<=:3ÎÊ=ËÅX:‹b¤ OLѵQ××`¹ÊÛÕK[,çúëM€¥^ ŠRgÐ`ƒ’Ÿù¿÷‰á{ÀÎ!BÀf„ÓëçÀ7/n7Ü|»Í0C@w`ˆOSòòîÂÒá| `rT¾þár,?a8 KËyÀ3¸)ø4©-WE©ák¿¢o˜A~M´_r>ÎŽ¸~#ÂV¿.Ú.¾gB À_ü\¸û#ýœ¼· üˆ°8Ç—ÑÚ§ ûi÷@x !Ï[Úc#î÷'„õ¸ÉÓEQê=—ù É뢨„e>ø¦(ŠÒ hâ{'Œ­0eÍs&Â@fmWDQ%™´v©íJÄ`'b÷ˆPEQEQEQEQEQ’ÃhqEQ”Âõ¿ZÛÕPEi ôô³Ž½VÛQEiøn÷³’mÇ VEQª‰¦~ÚÇð´5·Ôv…EQ2Ã#æßµ?à¶TEQ’Ž0;Jt-PSE©Â4µi@MQ%é”Т7 ¨)Š¢$™Ò´èMjŠ¢(I%:€½i@MQ%iÄ  i@MQ¥Z(+€¦5EQ”¤SvMjŠ¢(I¦üšÔEQ’JE4 ¨)Š¢$xhPSEI ñÐ4 ¦(ŠReâ i@MQ¥Š$@Ó€š¢(J•H4€¦5EQ”JS™šÔEQ*EehPSEI˜ÊÐ4 ¦(Š’ U  i@MQ%!ª@Ó€š¢(JÜ$#€¦5¥Ò¤¶+ 4H26Ów¸bi SNÞ ożbÕ¯«(Š’(*°h?¬í *Jubj»Š¢( ]EQ”DEWQ¥QÑUE©ATtEQj]EQ”DEWQ¥QÑUE©ATtEQj]EQ”DEWQ¥QÑUE©ATtEQj]EQ”DEWQ¥QÑUE©ATtEQj]EQ”DEWQ¥QÑUE©ATtEQj]EQ”DEWQ¥QÑUE©ATtEQj]EQ”DEWQ¥QÑUE©ATtEQj&µ]E©Çá<,~ à)à=m' $à/Qy20ü€kº“ánn¶TòD C#Ž7ððfùÒ0ÜJÀUÀ@W`RTš6n:·ž€—€+(_ñ¨¥«(•ãF„çX†å!¾B˜Œò× ÐÊï÷Ã0Õï·Àòû„ïf¹hGʱìKÀ"¿å <Œ® _*– ý~_ ‡ÇH“†%DÀââò¡ Â|`Pü©f„Y@ÏÚ®FY¨¥«(‰³ÂuXö¾ñçfbùa:–§€ ܤ=±ô:…>}s ?°ø°@kœXoði¢S|ž`qDY¥–cÉ.>¶ä œ‹e‚¯Ã/@¿>N„ñ@þOC„Sxß_O÷õÌEåÝÈ;?8-úÕ§ÉšF§ý€<_žõç›-pÏ0è€ÜžÀ`[‚m«vTt%Q çS±Å‚f.–Þ8!íŒ0˾—Ý1\FÀ=@s„©¿`9á-Æ—`È à¯¾¼, ­ ø3ÂdœÐvò°l£Æ™@‘/ãy,—ùãç°\ ¬¨ôó°´Â²Îíï-þE@/à,§E&b9ø'ö°ô.ÄГ€+}bèEÀÀ>/Ÿ=€_±œ„³°ßŽ0ò€‰Xúí®Æò5ð]¥ÛUM¨èVÿNð>vºþÀm¥Sê–ÞØb«`_à„ˆã)”Xc›±ü áv/žíÖXþe°'ð06Žû¾ Üœwp®Œb¤;0Â÷ÚËéXÎN¨åax×>ñ/™ß€GîÃr-ð‚08øË¡XöÃù¥ÏñnòîÆrð `æçã¾0R°çSþè Ü \ä_$uNpAE7éX÷ðàzÜÛ<¦ß<ÞtJD(BÈôØÍ1´Àr6–…À×å”°‰’Ïî Äÿÿpºÿ»ËT ý#êÉ6~óûßÿ¾ŒóD|âÖc¹h¬s€?ùÔ  ‹Øâ@àóPFíK0À¡¸àd¸¼B ûð Ðá5,/Ópî„:Šnò¹ 8†’ JUÓ54z¦§§SXXH=èÔ©#[¶laÅŠoغu+ù[ó;µ ¾ÏæÚ!`1ˆ3øè½pV%äÇy§fQÇÛ#ö‹(±¦K#|ŽåŽJÞ#Jût…C€#8¡\V\7Ë£X¾Áp¥¿äJ·'V N£–çµ|‹e%°K_àd Ça¹ËHà¥J´§FQë*É„v^MFº†DjjêŸ323&Þ|ÓÍü´n_~ñsfÏáý÷ÞççŸ~â½wßåø!ÇwLÏH_ôMÆ=³²²v…BwŽ=z`2ÊóLö.Ž:?çw¬,ÛqA%‡]ç!ÅW\àª2Ý´\`  -°G%ʈæ7\P¬gQÿŒ³Ê§c8hIÀ,'Pbè‹ –…ë«ÝEÀ\@-\Þ>í 7Ïp–»1¥^„uµt•!-#íÎn]»=kVjÇŽc¦Ùwß}yõ•W̤I“v½äÒKäåæ„³rbøðáÍÚ´i3YkÄ3§j-(Å:,棾À²'°aJ ûs+иx­œrßÃ2C®ÇC¤Zˆp>¾'D[à‰„kîºwÝeº·P+@+)s%ÂáÜåZ„g¼+¡–.À߀Àóc@‰Åÿ>–aø\ËÄõjËu“^²3–Ý€[vX&aØØ€å,ÎõåmÅpµï×üs•Û—d¤¶+ÐP±ð,0h.åt[‰7]=çä];î:eÉgŸ¥í´ÓNqe˜8qb0îŠË׿nÎíEœÏeÔ¨Q½Œ1£Eä"\À*’ã³³³g%Ví Éãü™Kq=:à¢é[Ž8Ÿ*Àé8Ëíu\À'äI‰:Þ8gá}‰³7ÝpßP\רÙÄvÁ´Æ}ž—%6g1wÀð¶}ݺ«|»š±cW²&¸AßFoåïnC7àpœïú Jº¶5ŽZ³VzQÎöÂùƒ¿>R)é2ÖÕ—— ̤ä÷Ðg1`.°ÚŸßç žîÛV§PÑ­&Tt‹IÉh•±ê¹iÓ;wÜq§Ž`ð±Çæ½;ÿÝ î-+M¤U‹ó‘—õ›®ÑUª‚°1Bt ê^¨!¬³†6³SŸúÍ¡»vØ53QÁ¸åæ›ÓN:ùä+b‰nV­R?˜K#ì&©¢[}|Žë:îSè?© ]ƒ"µEê‰#Î>;­2yHQQÑ.¸Oáu Xµ±x" U4oRó,©æòßÌÎξ¬šï‘*ºÕ„¸¾‘ÿŒ8ÎÅùŸÊM×ÐHKKëÛ»wï”Êä5ÆÐ­[·,‡4h@­ÚØÑ;¥A#"Kk»Ѩè*ՋмY³Êtu¤¦¦Òq׎GˆÈ1¨Aih?]¥Z)((X¹fMå ­Y³ºÉ+¯¼rvvöÞAô²q½¥^¢½”êfØ‘¿;ò±¹o¿“™hÆo¾ù†ýößc^n^["F_…B¡V"r¶µv,°_ýôSÆ_¸yÓæËK7a„EÙÙÙY@'ÉÂu¬W”:K¥¢ÊŠ’EEElømÉß}÷]ûO8¡‰1å¿ë—/_ÎÑÇ“—›“{‘µ6®ù-ZT°ÈñÈ𪈀zŽâMZ´hQôü·Š¢Ô,¬±`£¶UÖ ç¬(ïk¶.Ï’U;´Êl9Сƒ¶|þùRkm°ÃVTTh³³µ™­2óš6m:ªâ"Ë' µÊÊÊ …B¡OC¡Pc¢ÔaÔ§[ÖÍÍÙ xПJ²p3)í,e-•âò¾-n¥„”&Mšü»i³¦WõîÝ›¡§žJçÎÙ²e Ÿ-YÂK/½HaaQ^ÎæœÃÙqI•>|x³éÓ§7ÄaÖŠÒ0ð–îQç^öo'|ž…ÙX¸ÞzwM´¥[Nºž¶°ØÂ< —Dä9Ê—ó‰…iŒ¸v¶…Y>´pƒ­_ý­ XkŒ±iéi¶y‹æ6|áÃÚ® ¢(µ€Ýõúùí ?XøÜ‚±p–à§,íkgXgíä{úó!`žÀïýµÇ(mùÌjÝë oۨċ¥–÷"Îì œq<…’ ˆ6cùÂíüh´Âòw,ŸâÏœç}gw)s€QÄrsYÄp; XvÃr–3kd9Áµ¯ –¾¸ß¢iá^,W`y 0ÞÂüˆ¥?–ý±lÅYÿ±&ˆ*p7–ë°LÄ»~| X,§ú'}Îj¿¸Ë•ÀwIksQÑÍâÅÏÒ¿…›g׈4ˆ³@Sgq½Ò_üÌúÉŸÈ·P„›?·O¿,| 7û¾¼â|Pl‘èjεPˆÐšX+Ê £±,ĉBYl>Ø÷ßñÿ÷v,Ïa8(f —€oýþW¸XÄŠ8ïQ1sp¢+À÷cq®ŸpVô>¸— @!†ýèŽð’\€íÜÉÿ*¾»+ïEÜWÄ,/ûòV–QNBE7AÄM(þ î³(5â’õ´ÇMÉø*î n:ÆŠØ„Ú.¾œàFÜÞéɪ¿’"œqf‰ß¸4Ž â¼S´OÒFì”%ZÂr,ÙqÞ#žà\4Ïùû‡9Üo3pÏ`9áºZÅò †Ó£òXJ·'’p» îÿÁrœq–o±¬~ñVö ŽÇr–¯[F{/ÄGº……~[œ¼.%g$Mqϵn¾Û[üùr'÷ƒœk]ž{¿á‚ J%ÉÊÊ:% } …²’Xì“À^ì(°WQ5?âvJB ;tÅ:©øŠ0”À}]%H@Éoj'`J”ÍFœ±Q€›`ÎP˜Ža0К€Ï°œDØ¿ C(1úÊjwðÎ —w$.Èv<†[€ÈÂr7†c’ЖjG-ÝŠyŸÒ¾¡ï€‰Àþx50/Àâ–ß¹¸çϽgñ¶¶ù´_E”÷6%Ÿš—á~€À-ñ~ð0îßi~Õ ÜœÈc%‚qãÆeÜ\h­un!ÿw’øËéSQÿ‡eO„•Àäö[ÎoŸ…{±–Å<,³0äcéAi˰a$Â,Ýq_Y®¹ó±>€ð<–þ”Ä*k÷‘Xþ‹åj„§^ÃÒKkà Üïù2„>Á²/%+f¿‹å6 NØëÛr ³3±´Ç² pÐËÓ:¿b9ƒ€³|®-þDÀ_«Ü¾$S_–xQr-´°\™‚ÇŽ;¤°°ðF9ˆ¨¯8kí_Æ[Y+KK\—¥pÙ§¸A3y8Aéˆ[Ùœ•³p]¥Â/ò”¨ã>8¿èÜê!Mq/ôn¸…OOÄ_ß!¶›¢5îóüç2ê,¾Î€7ýñFœUÙ| WÆ/QyÃÝà¾:ßÊß7܆N¸î]¿áâa7HS\7¹4àm„ï±trp_†ƒp–òW¸—ʯ>ß®8÷Å&_^‘?ßηÅó)ØÞ8_ðËDu÷¬ ¨è*5MRE7Òª¥tORT“è*UAØ!ºu/(õ’°U[PP°ƒU«ÔæRbµ6ÔÒUjšJ[ºñZµ±°ÖZ(£ƒ•R“ä‹H¬tu13;;;™AÔ*£–®RçI†U+"‚ök® ¤QƒÃ×E¤Î-ÚªŸe5€…¡µ]‡úJ(jYXXx¾ˆì‹þ^•€ZºÕŒuBq¯…Ÿ>¨íúÔ7²³³·à§=zôù"ò郺ƔzŠŠnõsÐ7¯‚Šn˜0aÂ$`R(ÚUDî´ÖžA|sÌ""c¢gßRj˜¢¢¢õéZk·ÔÔ½âE­…jƺñò§ãúmv×w±1“Ô.cñZ¿ÚeL©+¨¬±®£üÉþ°~%yL˜0aÒøñã÷:‰È$ ÎY6Љºª—Q”Œ5çbx Œ´JÈÎÎþ7åßïÕ÷«ÔeÔÒ­&|mtÔé}¬›[W©FÔúU”Fˆ…!¶ô‚–áí‰Ú®[-S++GÜtÓM©§R¥Þâ—ã‰%º[Âk¢5Rêÿr=ŠRÔ½P DТрš¢4bTt«‡èZ4¡r®)ŠÒ€QÑM2eТрš¢4RTt“OxZE¨µ«(Ý䯘žÝÈjŠÒ(QÑM"Трš¢4BTt“KE´hÔÅ ( Ý$g- ¨)J#CE7yÄ@‹F­]EiD¨è&ÊŠ§Ô¥¡¢›  E£5EiD¨è&‡DhѨ‹AQ *ºU¤’´h4 ¦(ݪSÙZ4jíÖ?NÂð Âÿ0L†D\káÁyZaŸð ßÒåg`˜±= ‹#_¦xÚÑÁÄþ=¶‹*{†û€£âjƒ¨è&ƒd‰¥Ôê†#ý±\‚¥/°ÆŸ{Ëç¯aÉ6ð,îÿØ^À®@_àWŸ¾%p`·í¸ßS °Ö§‰>nüΟ›Ĭ° ËôâcËv„aXú«<5|œÏá^*áûí…0„€¹þÌNÀÁþó"Ò Î–¼t¾ÚÍ#êÑ>ê¸-0ÈæF”—Š{Í€wqÏv7Ÿ_ÜsÛš`Ûªµt«FUhѨ‹¡>`ð,%‚æ,mqbØá ÂPœ€žîÓ¥"LÅ0á?&øó#1Œ(ïBL„…êò„nöÖ\zœõí€,&â^á2ŸöŽ«œ²°t (ȃa†p+Â;„-ta:Â#ÎC˜‡0÷òáêˆÏÆpßï‡ð †á7"Ìljm:Â'£1œðÐ 8hálœ˜×9ÔÒ­$I  E³…C>Hr¹J2±ôÄò^Ä™Î,> ˜lóG9XîD¸øÎ k…åÏX>öæÄyßXî Â,ÜïïÞéÁð¨ßßË1XNM°•e#LE°¾üÝVxë„{°üË«€xa=øçú8KpÂŒ8îu–«°¼àË› \, ±œék2'²ãø;;¾ë*º•§ 0«Œk=6Á±?à^ËÝ€þ¸ïBà[ÊþÖ1©µT’° ¡MÄv¿ùk£ÙÀ×å”°øÜïo¢øgQ!/ù¿,/`Q‡H~%`‘ßß\¬Šóc™ŽõRç>ç¯ÁYòë€þ~‡s€¥9†}èŠðŠ\€×p.•ò0ÀÁŽòåµÀ°7Ó4„ùX^^Æ qGE·’|dŸ´ßÖ­žþ¡ €~{îIçn])ÈÏgùòå¬^ûYË•ùŸwŠðd– IDATW©|ŒDX¶°Äo¥ÜeÛ»#Í˹f˜W„åÅ–gÕîQ¥}ºp NdŸ à͈ës°¬ÁÄdcœ‹¬“Æ—në,k XöŽÅp–÷°ü˜Z‰öÔ(êÓM"-[¶¼¡u›Öïûç¿öúá—_xkáBž|þ¦¼ö:‹¿^ÁâeËÈ=÷öOky0¨¶ëÛ¸ä’K3fÌ×\rÉ¡I,öI 7pEÄ9®ºV¡Üí@눺î]`Î$`A%ïÑÊï·ÇÒªÊ&`gœKåsœ®Ìæ`ìJÀ'XN¦DPO¢Äè+«ÝÛOpq“pygS0Ü ¼JÀåXîÃpdÚRí¨¥›$Ò2ÒnïÞ­Ûe³ÞœÕ²cÇØ‚.]ºðÐcsÚÙ#š>|VÎæœÁP©ÿ8J9Œ92µY³f§¡ ‹ATì?ŒŸ_° E˜ ŒBøËÞŸOưßrqÁ«Ë)å”;Ë[ÀÒ•Ò‘÷B„±tà ÒS ×Ü2áa„—±ì |‘pÑßGcù/–«žAxËû“›ïEÀ%#|Š¥WDûæa¹ C>–DZÀ–«¦! ÇÒK:ðG  –l„¿`9‰ øë#Ã\ó%×)¤¶+Ð@²K‡]ž[ºdIÚÎ;ïW†™3g2ü¬³6äåæurªµvu‹•sý#,W¦à¬¬¬=ƒ ¸@DÆàºEr|vvvY>øÊ’Šû¬n‹s/|…³Ø ü–AI_ÝÁ¸Oîw€Ìˆó&*]O N ¿Æù{·â¬ÓàXÜïå=b»R}ž¼×†óÁ¾ãȨ̈ÄÞˆ³FSpþàHŒOó[Œ{¦ú¼à,辞ÿ£ÄÕŠë±0a-–.>]w`°øg†ï¿³//Ï—ö·ŽðõzXïÏ÷Ä…PÞ6—ójñ¾ˆe ¥ßÂa–ËI†5&ÌÂ2XQå²"K5ÜNÀŸ’îP çMx x%Þ:d¶Êüfò¤I=N>9±.»çÞÖg¼tÏ–-[þšPÆúMRE7ҪŠ[YT‡è*UAØ!ºçÓµ¼02êÚ@\Ð}aRîd™@IÇðdÐ ø+–áIH7áu–0á>àâ8ëÑ¿M›6;%*¸7üõ†&Åd¡_ “••µç˜1cnoÖ¬ÙZ`å ®R7™‹s;4*Â>Ýg€àÌxg¢†Ïa‹Joà\ ~¤MXŒGâÜ—ð(î`†N|LÂ}^dPÒ5¦50CGæ/øóá^=0FÀgÀØ¡cLÂ[¸Q\å_:ÃïG°üË6„˱<^AùcŽ6lXjEébÑ·o_ÒÓÓ›çæäö¦žtw©M"­Zkí`‘„ÞUSB¡Ð¶Š“)5ÌÊj.ÿµìììQÕ|„‹îÜ0ºá¸ÎÅ‚å ,çûë}ÞÄr7¿úþvço#\Žë¬ü!P€0á ÞC¸Ks`<ÂUX>r>@x›€nðÃ)ÿ 0ao!܆e;ðXT½s°ôö« ƒu|馴Þ8}A™­2÷è½ûî••Ö·{÷¦\wÀBÝ2©ÀW/:¯E#DDêÜ¿{IïËd„‘XÂ9´ü¨Ã5üŠ-Á­WbyÛ¿€å~Üp¿½ |åœÓ<’Ó øƒÏûÂ",·ùë+ ø›¿¶;†¾etO&Küß)¸ëXŽ'£ˆ4MI‰·oûŽ´LIiþG˜x²“~»ÔÁhkm …šcŽ´ÖVVp¥ÎÙeìyà~ ‹ï 7µxÔ‰e/„À |ÚtJwg ~Ù†å/>P²Ë°Ãôv½Ft YAÉd |q-vçïêa??}ä|O¦œÜœß¿: ’}ž¿[½š..Z|90Ú:wŠŠ¯';;»xx" õ±ÖŽ‘ðOE©wDŠî&„W±œíÇ3Ÿq-ËP<‹PÓ¨¼‘#lîðãÃ!\ ò¢Kîã:7‡iг†ÃÌŠ†Vûz—ÉåÀ‹‰d,ÚV4wÆ‹3ÆÝ|óM­*N]šµkײîçŸ#f¡%*¾e’ýpýÈ‘#oгÇB1"2"%%åÕZA¥Î±eË–üÚ®C4¥GLF뇷¸ø¼åMïzxØŽáqV7E•×á ,Ý·±´Bvè¦õ–¿àw«Ký½’Õmdn²çÙq§3\LDzŠçÍÃõ»¬ˆyß~ûíö… Ò¿ÿ„*úà½÷0\$Ö•°øŽ²ðp§ÀÏ Þ€™8qb>0˜¯õk­ýí¡‡ZWc•T”2ˆþ$~Xç»wEòBÂ?Wg[àNm-%–î*„–!|îgºÎ_[Cx˜ å‹V ŒÂŽ.n$`CÄ}££ÙÆŽsÆpZBé,m°œæG¾„·[˹o$…ù[ó¯»xÔÅyñ©‡%K–0ááG¸aK¹Ó}¦W+-ÜgÝœ¬JÙÙÙ_?þúmÛ¶uÎ"Þ»¥–H´h œ; ¢È~*®ìOå¤IÁ̉ÝR‘ÌÖ™3Ž:òÈã¦N™Ú"5µüdË–-cÈGpÇú_8Ë–5çGL¶PÿÝÕ6"-LÖ¯ŽP”FjF«Œçúôí“3þLÖFŽ™ …†‡B¡ÙYYYÇ&«\EQê)))ç¶ËHÿmïî]í—^bï¾û.ûÜb/v¦Ý9#ÝÉH·ÿƒ9.°°¼ Â[_Å·ÆD7’›nºIgÔS”†J ûì=`ÿج©½¡IŠö»±œ`¡i#ßZ]EQ0ά@$§G¥oLâ«¢«4jô“« P(nnÔ½pCªË[ê¥<Â]ÍVÔñU”F‰ŠnBÅWQ>*ºu_Ei¸¨èÖaT|¥á¡¢[PñU”†ƒŠn=BÅWQê?*ºõ_E©¿¨èÖcT|¥þ¡¢ÛPñU”úƒŠnBÅWQê>*º _E©»¨è6`T|¥î¡¢ÛPñM:»b˜±Mþ´NRùÇàÖ«ˆNQõo&©`¸÷»IÃ0±œ”ûC|žEÕç~J–Áª*Í0<¤²Ê£†{bœï ;¬L“*ºߤ‘e(Óýö:‘¯Å‘÷&`\izb80ÎzœQðöJyã# ·*wSl9b#Ü |€å`uq}`3»@Ÿ î¶'Âü Ò¤`9#ÞêW ,ÇÇ8ÿ5Bˆä½`•dèÔŽµE-M)Ù¦vì°%ê\{ ìâÛCs.þÜNžÄp/%Ï*88·À Ý@Y+žöAÈ+§ž;€^¸õãºá£=q?p"rª¯o×Rm…  5ÂÆ2îs4†‡ŠÜ¿á°R)„7pëý¹#8884âÜÙË!rlùº‡WoáÛœœŒ³:›á–ëGÉd]|»ÃìíÛoßYÀQ”žg­gøgÐወüÝ€ž~LD[F-ÝFŒZ¾I¥3`­@/„¥NÅp$§¸ÿÐ}°ì‡e p Ð a†a!„÷t,#Üá/XÃ+Q§¡30ÜŠá(¿ìsŽC¸a²O×Ó×w(†#>ö@x„Š\†sxµÜÐü‹J˜p«oÛx ÿR†í0œísM@¸Ç?ÃÀ`_^„g0Žp-Â4`»ÿÒèëïñ¨niã~§'"|ˆáh„[^÷õk†0 a ÂyQõÿÂÛ@sü ¹å>¥f©/–n45dù6K·ã~{a¦x…ìcq/!‡að¿p­ß¿±ê´áà`Œ·²ÂFÑ5ŒQ>Â7¥6Ãþú(Äò~AÐËüÑî?øýÁÀõÈ.NçÄæ(ʳt…e@ûˆãfùgó„¯p‹Õ6÷ílêSé_6û |æ÷Bø:"ÝYÞWÝÂQäÏï‚°É×{"Îu“‚ðÂ*œU{lñïJø’°ïÙ¥ûg-§úrÃ.”°¥û;„D»F„ïp_) Ó¤2™”ŠùøX‡ûöéLÉ/¨."Îw÷”…gs€€Ý+QTØòmëÿêÅe”Zîý.,‹üþl`=…á ,À%;”`€õìJ [Oc>ÄøãÊþ*ÍÇF¹,…ÅûÂWØâ£ૈý¿?'¢¾|}ÿ¯ŒûÅ¢#ðK©3ÂRø£ç€¹¸¯¸8ÃXNrc”yÂ;m™FÀ4œK¤XÑãË}á\,K~Ær†>¼Žû]î†û÷gÏB8€7€Š­c×.aî¿ðÚ¨úýŒûºù‚QÑM>öhÓêÖŸ ¶1pÿýèØ­…ù,ÿê+¾^µŠó«·æ7¯°”Z¤šÅw¿ŒŒ ¶oßÎ@×®]ÉËËãË/¿dõšÕXk»l-hü–´UÛ)û‹å2„?`y€€»1äE_$)QÇ- 8eAœõ°”ÿ¬bß¹4cÆay€{Ê©oü÷ø'¶ÑtEøá)^ÞðA¸hLT¹)8?n¡ß‚yfGcù–1ƒ¥/Îr—'y$â¸ȸV„åHïâø‡YDæKì))IGZ¶lyKÛvmó&Llݺe‡%Øüñ{Å¥Y¶]ËÛ€#j»Âñ’ ·Ãv°×¦6/ì¸óN…O=õ”-(Èßáù|ÿýwväE#·§¥§ýJÉçc•=zôÀP(ôøèÑ£OFyžX´„ Š„™„Ý ¥Ý b¸="ß,\à,H “…ááwª(6 Ããå¿E‰_´ÂOþü ÀȈú¾NØÝŸ{á "]I±i% Cx3âøü—R¤{a Â7@ª?¾Àûl[Dµ¹%BNĽçùOÿƒp–êZ„ï «°àhêïw νù¢‹ ¤uDø•Hß¶°š°ÿ=AÔÒM™wõèÑ#4ëÍ7[î²Ë.1ÓtèÐ{z˜S‡ ozÚé§¿ž³9çxཚ­iâ$Ãò½¼E*ÿ·Ï>M–ΜIÛ¶mc¦éÒ¥ O<þ¸væ™mÏ>gÄÛy9y‡Ÿ$ZßqãÆenÛ¶m„µöR`cÌ”DË©4–WnF8ËÞ€E8Ë4rF`ù„€!¼Ðgå¶fP"ØñÐaCŒ:ì›@}_E¸ a_ßÀ×qZ\ù…¹X/Ç‘ú}àïçn‰¥)®‡ÄI8—ÆnÀŸÛæ cìݵåu„¿‹q–ëf„÷°Þ*µ\0 á,,{ËW) ’Åâ,7"<Šå`Ü æb»E*D*N¢ÄÁI;uœºä³ÏÒÚµkW†Ù³gsÚé§ÿ¶%oKð€z‚u®é¸ÅwŠ·vïÆûŸ~Jff|±‡3^´¿¿à÷ëóróºöVÀèÑ£ûcBÀy¸OÑHŽÏÎΞ×Í+&×µ¨¬.Tàz„t>òpÖâ<œ[â8àsàœ f îsù]œP4÷÷[ÓÑÇ‘õ(ënÂý;5ñ÷Ç×9÷5>o¸ áú~Œ“#}}À=ÿí¸—B¬6Â0Š€Q÷)¶•Q·v¸®`+qÏa /÷+àÜ èmŸv_\0ì#œU¢ê}ÜÔçßìÓÙÑm°30rùç&\·¹°«&ìΗcü}6—úô”Ѿrÿy“NPÕ ßb2‹»*SpTY÷ð$}$É_‡¨Øÿ—ìt‘˜ÌV™+§N™ÒuÈ!§Žà‚ /ÜúÂŒÈËÉ».¡Œu/¾àž[Xi =ÒÓxjæL;ì°„ÊzÚiy³Þœu[~~þ?ËJ˪-ƒdŠ®ð –‘¸rCÆ ¼†e8U°tñ…t äÍ”î#ûãÌïªÒ÷±2 e{“žˆð–î”OvºXÜ£gÙß~óMFÅIK³lÙ2úõï¿!/7o'ê±S¾<Ëwð÷½÷âý¥K.wÑ¢E3xðÚM7u޾VU ÝêeÜ€„9%¬çô³+JXM€ÀÜ(•ððÁq]!‚Û¸CgæSýùcq])†‹€ù@ÃÞ¬ÄE7áFÆ„½‰O³Ë€Gp¦GܧůÎÖp/±¬Náqßðò}ÒÉNƒ&Mš7lØ™-*N¹#{ì±Móróú_V¦Œº@y>ß9ÍšrÒYgUªÜ<ÜÈ®ÎÀš¬Úë(ò|(*ªTE”úÌ+ÙÙÙ‰øÈ+b…ß*Mœ“2Â9Åc¶ ÃZ"|€0…€y×cÙ¸ÃP,ƒ> à ÿZð,†Àý\€á ï^X‚a2–LÆp:–“° tC¸á-¦!ŒÅpAqTµëGçHæ}²ÓÅ =#½w¯ÝzU: ¹W÷îÍ{ü¸î€ê±è†‰%¾ß§¦î~J¯^ä,£<ºvíºm{ÇíÇvØa âµjwÀZ[©H³R¿‘:÷ïîÄ"`2Â"ÜÚbBÉØâó>&àïXûh ó³ _øÑ6nˆã#À ÞaÇ .ºû(d` Ï#¬¤ÄjiA@Pˆ%¸£zšÖÚ;p£§¥^RòYl™„0!€g‹ÏlnñgÂ]MÂÂÙ-ä#,»á¦h;á]l©ñ÷›ØqJ´6”t™ª´xÕEEEï¼ð WÜró- Ã^»v-?­_9¥R3œøŽ´0‘`ùnÉÝ2{Æ‹/\píµ×$ü|¾øâ òóó·«Æ¿¸~ܸq7œŠ{VÇG×G9ÓóN¢uPê7¹¹¹eu[«5"}‘SÛ°lƒRÃÝÞ@˜„e<.ª‰ÞQa –K€×xá Ju\Šëëv08 ×1ú#³UÝpÁ¸IIJóW­únÛ‚ 8p`BïûÏ¿ÁŽcA)ß ,ŒîÇ~×f/Y²”¥K—²Ï>û$”ñ®»ïÊ·Ø'‰êÙqÿý÷à†àN3fLoàb¹×ÿ2&ÖÚ܇~¸®-V‘ÎÈŸýHç(ýù¶Ëƒ¸™‚V!üË¥¬ÆuXvX®FxaÂ,—ù|›#Ë9ÿÅÍô4–óqnˆ\¤x2€<ÜìEåñ ¥Ý»!œŸÄtñP”¿5ÿš‹GÊݺ5®>ü,^¼˜‰ÙãùëÖüò’¥âfMú¶»¶n+ØvÃE_”WXÿ£}÷Ýw™:uÚ¶¼œ¼[ËK7~üøåãÇ¿¾yóæ]ps¤Î¡w¿S”H„ø#ÇmâHS©(t]%³Uæ´!' ÉËËËÝaNèíóÏ—Ú®;µ³3Dà žŠ¯d¶Êœyæ°3·äço­ðù,\ø±mݦu®KbÂŒ3¦÷˜1cn…B?‡B!ë·ã’Ü&EQj™f™­2ŸÙ­×n9sæÌŽ)&ùöžÿüÇLN\pë»ø¶ÈlùRŸ¾}¶ÌŸ?/æóÙ²%ÏÞzë­AËô–9)))§Wõ†ãÆk …†‡B¡ÙYYY•pEI6:÷Bò9³}fÆ£­ÓÓÛ 2„Ž={RX°eŸ.fö;s9T„ësrgâ¦:ü®+TeÙFý ¸‰1æ†-[ÜÒ¶m[Ž?þxztïÎÖü­|üñBæÍ›GJJÊÆÜœÜþ¸y ’zoÔí (  g.ûØ¿6I±7‹Ø§Àþµr„ca¸…¯ª¸RC}²|K­Ѥi+"õmåEQê6Áåz™ø6„åz¥Òè”u@œÀWÔk£,Â]ÍÔ EiH¨èÖ!T|¥á£¢[QñU”†‹ŠnFÅWQ*ºõ_Ei8¨èÖ#T|¥þ£¢[QñU”ú‹Šn=FÅWQê*º _E©?¨è6 T|¥î£¢ÛQñU”º‹ŠnFÅWQê*ºßjA€¯ ,Fxq½=†cäk…a|Âw3 @wJ‹EɱÝŽÀ©Àé@zu»ÓQç.E˜ç÷àVó&⸉‰ni«Ùð4†;"ÎtÎñumq¾ p<0÷R:·.kJOæ}Ü÷r;-ª¼ Ü—Û> À~ßã¬û²žQ­¢î…FŒº*‰a8³ÀOQWcéŽ[Ñ£=Â$ 9ÂÑÀN8ë %Âd û \!’#0\QÞÙF Ïa8 áR„…8±Œ§¾=€_Æý@Ÿˆ2ÿ[ê8qKw‚â•>Ž@xÃ!cÞZ‚ð2ÂMŽBxáE %p:Æ/vë8 ÃX¿?á ‡"ŒAøçfi…ð†!FXtŹJNZW¡]ÕF“Š“( ˜náyœåUÙe„Ââ;ÒÖ¯e„ÃÒËüˆ3‡b¸ ø(à`‹?ÊÁr'ÂíÀ¿€ö@–?bY< Ìó¾Ob½*¼†{ÖÿŽ‘îH ÓüþÎXúa9!‘&–‹s_XÔác,ùkwae¶?ž œ¬:ùº'D¸\ʻ׿±ŒÅòš?~ gÅþGÀÅ>å"œÈ>Œ#àïÔÑßžŠn5±X€ûWo tÅ}k¦Öf¥*@Å7N„|„ ŠÏüBÀ"íÀ4àërJø Xî÷s‰ÿ‹óâ=Ë«EÔ!’ïŠ]X¸ŸâÏqÞ£b,wP"º½[q?ñµÀþF*Zvư'^÷‚ 0Š÷Ë"è‡aÎU–]1ô%`§/ð"ðY²šX¨è&ŸCwkÝêŽMEE6`]vëÉ–Ü<ž^¶ŒO¾ü’sŒáê-[ë²öV·øöÏÈÈÀZËÀÓ½{6mÚÄ—_~ÉŠ+@èž¿%'à—dµ'é,@8ø N|–ùÍ£„mqÞ)úw’±o€ü˜¹„Uظã-âLÉ[P,÷s€¡8ÿìtÀ05âút,k0œCi½Ê^·t»¦…åýl²?p(Âñ3±\;ø³•ŒIËH»s§wÊ}ú駃mÛ vXb|ýúŸíuW]aÛ¥µÜF‰¯Î“Œ€[ØËS›uݵCáÔ©SlQQáÏgݺí%—fmOKOû 盫2YYYGdeeM3fÌ‘É(ÏÓáGàJ„Ã`ø'Bƒûì^寊ð®ßî½Ðá¿ †Gü¾ üÃmþèJzF4A˜ûÌŽ&V ­W°«¡B>p0U ¤ Ó+üþ‡¸@_¸ Óq¿õS–ã>üÀu·³¸`ØX OG”÷†ûüþ|àìâ+†g|ýOŠêz÷÷â.z—Tm•m¥>ž™þ@¿þýrׯÿy1‰ÞæÏŸg3[eæÉ‚j§*â;¦Eª|è!vãÆß*|>³f½i32Órq‘õ„9rd묬¬P(Z …¬ß’Ý¥é@„¯¾Exa†‡0d³£èî° 'šå‰î~1<‰0Ç÷ƒ ‹îs}¿àÏޚƨWù¢ BX!a6ª*º†Û"ü³V#¼€°áU_OA˜áŸÙ ó|W³  o÷Ó³0<[,ºÐá{Ÿ÷=ßîfþ9~çŸÓ„5ÀþY-ðAº:Ð-˼Wchç.'/]²$­uëø¦ï¼ó§œzꦼܼ8_½Áºÿtq»&‰ðŸ^=ywñ'¤§Ç׋çÕW_³#Îñk^n^w /ž<£GîgŒ Áÿ³wÞñQë~Þ³„@ME¥…*`A¬¨RõÚ°TH¸êÏrõŠÝxm`½zõª‘k¯ b/t)‚iz)I6¡$g~Ìlr²ÙdKØ„y>Ÿ…SæÌyÏd÷{fÞyg†Ë)©Q8=;;{|D7úÅÐX ¬G7 ÑÍëT`‡I{"ºÜf£C¶Ç t‡]åh©(@G*øÑ]yÀÒ£[Ï\SQ™…±š‰vìŠÌ=¶‰&ü ë“&øûšˆvSl7û €cÍ3Í÷¤àhtôÁ\„ßQ´@GV‡vÓ¬G»"÷OE—sŽÉ/àO®OI¹Î3ç1Ïv40 ]Vq…˜'¢x íw àCø Åô§²wúŕĖ ‡çqK…›ÄšîH¶âàò"°0RR¤®ûpìÍúõëá%šk‡^»sìØ^ÈÍÉýGTÆ JÿD¶Ñ IDAT0/E7³;„J³hŸœÄû'Ò­[·¨ò4xPþŸ9jçÎÿ*/Í!C&&&^¨”º=H¡<ªCt-•AØîÝýÝLP|pEйîè7Òì*¹“â~à·*ÉKÓ‡GPœ^éZ#LÅe.? L"ò-Ý:ð ÔhàÎ;î¬çºîÕÔЇ@‘è§N”ç;hÙ®mÔ‚ p÷]w'Õ­Wwx¨sC‡=6##㥺uënTJ½DÅ‚k‰G´ \C­#Лø.0Ý´ÑÂè0ƒ¢È¤ébbSqyøÖ|‹Ãm&>q'ׇâò=ðºéÒŒ’Ñ$ápƒI3xÝd8]{:‡î¸,ž¡ì&áTØ'‘¥Óéyb A¸—»ÃäO:uú4(¸)íÚµ£Aƒ>ž¿°,–<⊢&×MàÌÁUty¹tíÚGœèJ루ՆâÓŒŒŒÐV–}É…Õœÿ'ÙÙÙ—Tó=¢" º›Ñ•’ Ñ"ç ¸U/+p¹]»¼ÅM[–¡GÙL@ñ&Î^˜¸BÑ”’N€mæXXÄG$vï@‚P÷ÈVð†ª%“tx‡/¯ã[P™òRSSÒ‰ï±%KDxC?ÆG­q„ð>^BEG„›æ™Ï”Çt:íFqÂx„8 þ ºg;¤ÔÈ‘µèþ–CR#yö–¿'Ò?è$"ì1ÏÝ‘»bõš5EáS†fíºõ´Ð-Ž+€eµM|ÙµkÆÚµkUøÔ¡Ù°aC¼yóÞÎÎÎ>ÝuÝV"rºwÛb©‘x›ÅyŸ¢¸Ä¸¼sdnGq/èýMF‡qŒ©¼‰âEtãmoubmCûèÔEϨiî}¿›°…· ÜÆ Kaaáäqã>ºùá‡J Ÿº4ë֭㯿þâð’C hñ½XÁ{À¿VD›o<‘Ÿ—?þ£q]uûí·G]>K—.e÷îÝù ã]G½5xðà'6lØKD2Ð-'_Ù "ç*¥‚[]–ZÎÖ­[㮣®´/Òå-ôÌH›€%ÅÇutíf’=8¼‚Ëbàá üAøE{`..#Ü”f¼ð‘hA¹øŽk–Ð-âGœÎe ÂhO \AZ€™ë×oØ5sæLN9%ºATÏ<þ+jà}@|/2ÃgÝ"¨‰LüñÇ¥î¢E‹èÒ¥KT>öøã®r_ >>vìØ"ôðÓ‰C‡mîóù.SJ]Oé—y)”R»²³³ƒãN-–½Nðï}°ÅƒŽ¿ˆ°ÒŒ Ù€nö?†Ü¾ÌëQ<Š°Ø¤½Å-&Ý2t÷O(þiF¤lF8 U<}ÝfÜR£ü´L>ðcб£q89ÊtÓ͈—æ9?!Ò™Ÿ ° ¿à–k®½ÆŸŸùozîܹ¼õêkÜUzø¼!0wÁ¯5Øí°s÷®Ýw ¹zˆ×®]_ôí·ßòá‡îöçú©(ÝèÑ£7¼ôÒK£¶mÛÖZ)ÕíKŽÙÝc±Ä>B= F(;"(ñÖ³X—Ò“$GLZƒ´·ûôíãÏÍÍ ;Ìuáªy“Fê3‘hç0Ø]CÅWÒ¦}zîyçäççûÖÏìÙ³Tƒ† ò€^±ÜlèСÍ333Gddd¬«ÆaÀ‹e“š–újËV-s¿øâób’ŸïW#zH˜’쎉^pkºø&¦5HÓ®}»ü‰'„,ŸÜÜuÏ=÷¸I)I¹ÀY•½áàÁƒ}Æ 뛑‘1&33³O<ƒÅRijäH¨8çìCÒRG×MLlÚ§OZtèÀÎü|~]´ˆÉ3gÒÏq¸#Ïÿõ1z²åÐãìce5¯Ã펔ԔG“““éÛ·íÚ¶#'7‡ 0{ö÷ø|¾mþ<Wj®Ûb±ìm \êePY F‚zÔ– 5Òˆ‚süPÉ5ÊjRÍ·6¬‘f±Xâ å”û™øZѵìר…)ã%ðz:¼s1f5«Uƒ,,–Ú„Ý8НÅRû±¢‡XñµXj/Vtã+¾KíÊn  Å·mUÙh±X"Ên ¢Äw¹_‹eïbE·bÅ×b©¹XÑ­ÁXñµXjVtkV|-–šƒÝZ„_‹%þ±¢[ ±âk±Ä/Vtk1V|-–øÃŠî~€G|ÊoUáW™EXD\FÉt©ãðVˆëâ0&ê» ¢—— Çe<Ÿ±Àp¯#—†0ÎlŸÜ"ÍAyOÀám`PÄÏa±TÑÎ2¶ìsªxV³hÄ·vÌ2æ0á'`Ð8apIѸÃlŸ‚0Ýl„°9êû [€F¤¼Ç¼úšÏåk€a®k‚°ÕlgâðRˆ4ÍÐÏ“&Bð·lÛ;è%ÄÛ×fXö"ñ.ºö‘øÖÑ펰 8 èx„µèe¨êÝÑË?Ý„°8ÑmŒ®_lÒ¤SzˆvɾÝæÀ`sMƒrl»‡×ƒŽ G˜f¶¼+3oòŠFtK·ÞÄá1Ï‘VÀt Ø»$Wº¥u9p pºÞ‚Ò"¼ßÒä7¨ï9ÞÐä5]V ÿëLúHZ{ë^Øp­Û!!¼l :³E+`Z\ßp8-'˜tIoé×#¼iŽ_ŒÃÕžü.Â)^´„pè‰p9Â|"«ù‚C`‡Éã?èšy ÏgKíGƒ¢ .?›ýÞÓp8áJóM„¯nÃáX„‰fØ$`N©Õ·ÏÇá:³Ýa:G \Š0H",ÄáT:›òh  $™EgS+ñ\ÕFðI,µøLÁh^¬ËÄ÷b¥—z@`eÕY'(Z¡ŠkާàpeñžËÓ”¬íÇåE„tà9à ÅÍÀ ïzj¡áî;Åh„OÑ+E ‘®g±ßXÑEggDõŒ¡ÝÊìµC˜ŽâUsî W£˜lö?G×Fuí^q<Š" ŸÉ'ܽC‘‰âk³ÿº»ÈÅ%Ó¤œn1¼ÜË“ÀoUð´UŽÝjbðú¯^ý î $ïK£Â`Å7B„„¦¸ÅG¶à2ßœû0øµ‚¶R²¦]>‘·8KDJñ%Ý=6xY‹[ìÂÊæExð(FQ"ºéÀãè¯øzà(..2iÆ¡#.‡"|c` PæN> +¡}ç h†Ãa¸¼",Cø—åUöŒÕˆݪ§wë† žÊw]zuïΡmÚ°§ €—,áÒÅ‹¹ÀçãŸþüH–§ßgT³øžœššŠã8tïÞ6mZ“››ÇâÅ‹Yºt)>Ÿ¯M¾?ÿâ´–€Ë,„«»Ðeõ³ùø€û#ÈaO„wª´ŸàÙ®CImº4ÂTÄýÁ÷ˆ„IPJîÝÐu"\²¡X\Añ'p ¥íw(aÜ€M pM~»=ùmrP œ€¢?ÂG(îþÃóXj(¾”´”g>äà¼1cÞW……{Ê,1¾}û6uï#Tãä¤=À™ûÚàH©Š·Ý †×¯WÔ¶E³ÂO>ùX–)Ÿ-[þT7ßr“›œ’¼8­²vgee9%؇^éü<4@؈ÿ(©¥úpxÁú-MÔèè…f;8zá`„MfûV²Í¶ƒ0‡G@GZ 2"á;ô -˜Pi%h;ËÛ·DØ œHe:ÒtXÚÍfû;àBÏ3|ôÎBXIIçÖ&¯Tàï&ôLç CÑž1{SÑî ½çð>ppïz¬¸‡çLªåTÎOm© ¤6H}¹ÛIÝò¶ný«Œ˜fÏž¥6jèG‡ÜÔ*#¾W%ÕWgö:Måäì[>S§NQ©i©yèÞí¨:thÓÌÌÌ+222”ùô¯ââ8Ê4m×›˜Õ58<à ”ÝÃv E³"Ñ=a;ï!|‹Ã;ÑýÍt>GXŽð¡[ª‹.üa¯›ü¾£²¢ëðb|®pÂ:„/æ£cëLa97ÁÝ[Æ"L1ÑϘüŽFXk:áæšçN@Ç ¯D˜Žð±‰XèbÊê;„/ÐÑq‡˜ç}¶çœaŠ‹€©Up§å(Ë*W >žÇex¥Ò9¼‰Ë×Pü¶ ÅqÀ1”.£ƒÒ[·zmÑÂEÉiii!N—eÆŒœ~Æ9ùþü6T¥¿m/ ô.b·Ã«"9´W¯^!./ŸÌáÃw¾óî;Ùy9y7Euaœ`Äw0p/px¨4»€öÉI|8e Ç|Tù_tñÅùŸöù“ùùù÷•—fèСM}>ߥÔ0*U«ѵTa»Gt÷Í„÷€“C=g"Œ¡DpÅá9^¥t³øF =/ítžÂáà”4ºQÒy#qxF‰ø÷@ûn0¾›»([cHFx¶ùi:б}˜¦Ù%žã½B× BqÒ!‡’­àŒ¸ýözÊUWQCã¥MœïûèÚ^È8ß)@ëí£\€»î¼3ÉWÇ7,ø¸×Wë8Îz¥ÔHj{lpmDxÈ;k AüÝ#yð4:àùç™óÇ ŒÃå.t˜Æ+(®>C\hB6¶#ÌÄe4ð5Â( g®Eñ ‡0 x—÷nGh‡Ëà„P<ËóÏ¢Ø<dwŠ~èÑ'ã(ŸHÓð .ËF¢h¼|…â+@7sJ`:}¼ ¦H°6mÚаaCÇŸçï,%x ¢h‡É‰uùÛ  +º¼\ºtéBBB4ôˆ¤uZí¦M›†‰H´"ûUFF† ŸÌ²—¹¡:3WJ{ùå—Wç=¢¥Ä¯xáFO£k½ùÀ\nÁåa0x(ê Gñ™Ùx™’ÔÉÀ<?SV¨?árŸ¹v1:¾îNs~1'ºâZW‰s#ÂXTñ}ý¡x1Üe© R;´mÓ6Üd"åÒ©eËz‡oØØm| Ý¡Äw}½z]m[ž÷'<Í›·ØÝ²E«'œpÂ)ÀùJ©P-ŸH¨‘­ Kåp'æßfuáý"~ tÚšaŽ%኷ Ì3Ÿ;(=Än‰ù7Šë>AXŠÃMÀ¶ {¶AJ9Ã×£]‡˜°’á‚§«—…ž½À°(W)¥b¯@ÕQ*áxÑ ŸmsFq„wxñRŸ3¯2åS6k‹¥fã]¿i‚_‚â\ÞóœÛ†â&Ǚω(¼Uö€_¦ð>Š(.A‡Ç¼Ii¶M<û‰èؽífoÔkCq g»)Ú喼ܼ«×¬. Ÿ24kÖ¯§¹.·+€¥µM|WìÚ5síÚµ1«î† .\øqvvö…………­Dä(ŲXj¥ãü\ÞFx²QÒÜU|†p;Š™Àn„7PÌFå×a%ŠNÀb\¾AÈJó ŠûÐÁË?¡§¿›NÕD5€îäKA×Ü#O' 3n’­÷!Œ#©(,,œôÑGãnyäáG"‹ó°fͶoÝJç’CÁ#¸”Ї“Æ=þ‚ñ|øá#F”7+V¹,Y²„={öäk^yå•MÀ¨¬¬¬Ç=áaˆ`d¥RêÌfÍšMˆÖKg_UâÊ%øË:Xˆâý ãÙí€U€BøÅS@ þ6·Ez& ÝÀfCMºe( €Õ(n2ÁÔIÀטë7ãâ­5þŽ[a¡åS:àh2{‘¦[ƒâg„oÑA×ßàò`©Ô.¿Q2Ÿ—ï6nܘ?mÚ´´=zT`jYž9’K]7”³Ñ+¾¯¬Ž*óøaÒòåËÝùóçsì±ÇFuáÈQ# Š ‹Ê ëÌÊÊrÑßÕ‰×\sÍ¡ W(¥†Cñô~e‘¢¬¬¬½ç®²XÊ!Z™˜O$o:„÷É:æµ7‰Ú&ŸÏwQ«ôV£-\˜’’Ùž³fÍb@¿~üèÏ/3)kö k¾ÿ’’‰Rj >Ÿoh§Îÿ=wÎÜäzõB½·Ê2qâDÎ0`›6Ž3hpD¨Ú¯ÓµXj©i©¯žÚãÔ¼íÛ·…æ:wîuh£†ê«Ø' oÞ¢ø"µAêgüí ^^nØò™>}šJkæNå^×\sÍ¡fðêjl±Xö1uRÒR^hÖüмqã> 9¡KnnŽú×½÷ª““ÜEªb¥†š$¾uÓ¦½Ù*½Uþ_|®\·¨LùlÛ¶UÝöÏÛÜä”äô’0•bðàÁ¾ÌÌÌs233?ËÌÌìSÏ`±T‚SõôoѰÁ+…Ь÷i§Ñ¼];våçóë’%̘7s‡Ûýù_ / §ŒÎÑYš€Û¡&ÍWûÔ´Ô'èÙ³'íÚµ%77—… ±`Á|u|åçåw¡ì¼‹ÅW‚zÔHPÿ5Ôö 5Òˆ™µk~%j½±.¹¯¨ k¤Y,–xBE¹0å~&¾Vt-û5vhd  ‚ˆ 7ý_yì_ DZ,5+ºqD5Šoì“X,–*ÅŠnB|«jit+¾Ë>ÆŠnãßc©œøÖÅŠ¯ÅXÑ­T“ø¾¤ât )‹¥6cE·Q…n‡º@°ÒНŲw±¢[ñÎW‹ß…a.)+¾Ë^ÆŠn Æ#¾·ƒ_‹%α¢[ ¨FñmVU6Z,ÝZDñ]cVñ]eÅ×b©Z¬èÖB<â{ p!z…ŽX°âk±T1Vtk1F|LJcÅ×b‰ ¬èîXñ­|@ÂT„_¦×P2]êÁ8¼⺆8Œ‰ún‡è5ýÂqÂÏçàF¯#—f¦8 ¸9Dš‚òž€ðpiÄÏa±TÑÎ2¶ìs VzDef5ÛeÄ÷Ð(n_;fsxáG´@µÎDø ‡LŠÆèEWNA˜n¶BØõý„-@£RÞƒ0½ðj_àB„•À]a®k‚°ÕlgâðRˆ4Íz‚ù@þW!ägGò{a9pؾ6£<®¢j©}ˆ^n¬‚ÀƒÄö% Ô|‡(x =™ú¦*34~é‰âlí¡X¨V£X| <Š^ uºŒŽ'P²|üh¡rЫa§£“uð‚÷S3ѵìÏ=÷.ðЉÅûŠF—£x8øÈ1g~‰òù'á]GPèƒÐ—ÏÍ‘v@ôÚvCñµuѼ Ðå”ÌA¿´“Œ]-‚öÛ …ÞoòË3Ç›çõ¯Ñùw’“ÐáÒÆ Ö½°ãq;tF»~sIy»¢©ùÖ<"¼CYÑûEK`'ºFû€SJt’^š" ExÓ¿‡«=ù]„õÅ{Â8pÂhщÄÞÎÀ6“Ç€Žž<Ÿ-µ=uPtÀe¹Ùï0‡ ÌA»E„ñÃqh‡ð §hq€Ãß=yžÃuf»ÂdZâp¶Yi< h„°‡.æ^sÐS™ $áp2ú%wØš®¥*k¾‰hñ½Jé¥ãkgÍWÑÅ·ž#§àpeñžËÓ”Ôîü¸¼ˆ<¤ ¸];~aF„÷ý/Š×>B—õ£!Òõ,ö+Ú¢HGqz4X!Ú}¡ÐúÑa’Ç®ÇP\U\>Ú§|9°HFq" ˜€0)‚{B1 ų?¸™ƒËÍÆ–ùèïßëÀ¸< üVeÏ\…XÑ­&V3Ñß´D Ð Ý®ŠW¬øFˆàG8ØÓÀÞ„kšóÂýÀà× rØ ¬6ÛD¾Va‰H)¾Á¡‡Ç/¿à’m¶÷ ËìˆðáQ܉þ®º•ô Ú¥°8‡+t®)šáÐ8˜`à[c[Eø€.8\ 2ùµÄ¡.ÿü¿ |ŽË8`Y•=c5bE·ê9½EÃϸ"ô>­'‡¶iËî‚&þø#CæÎå,Ça„??y_Y!Ä÷! C Y‹o–@Ï”Ôë&Ò³gOÚ´iM^^‹-fÁ‚p|¾¶ùyùÍ Uö@UËwàX|V›OàÙr'6êí'z¶ëRž¿2ا[1I¦ó2ŸŸî<ààDtÁ/*N­ø¸§”ýuТŠÀs+ÀÅåe`·'¿-@Š®(NGxѲÐRK©“Ú õ¥æ-šå}üñ¸KŒçææ¨ï¿O5NNÚãóùÎß×GŠ'ÚáçX#vº:©~áa­[íùê«/Ë”R®Ú¾}›qÇíU¶{VV–3lذ¾c222zUAQHCX‡Ã£”T\px¡݉Ô)î4;ÅãBŽ^8)nÜŠÃh³íC˜eî¡£J"#M”Çe!l»‡×˵\GQœcöZ#ìB f4Ñ ¥û‚´+ãOþ»|ÆqpÂjJ{ט¼R¿ãðnqnÚ‡ûŒÉo0Äsî cÿ€ Ð»,þc®YNåüÔ–š@jZêk§ö85oÇŽí!Åû™7o®jÔ¸QúËXcˆU|]P—%ÕWçöë«òòrÖόÓUZƒ´<àÔXìÌÈÈ8$33sDFFÆêŒŒ e>ý«¸8:#,Bøa:ÂzFáð^G‹Ïâ0ù56iþ:ç]Ñn…_ÐÍïXhƒŽ0™ ìŠ1Ѝ‰Ž˜öm·C‡t-EØ‚¢:´¬p”IÇ\Ïä·ƒÒám æX‚Ž’2œ‡ö™Çƒ0ESJ lˆ°EGJâ +Cgtm¬‚¼Zg#ð°ÓÔö¿þkÒ Gè‡b çÚ‡u<쳟–/O+¯ËcÍš5~Ä9ùþüFPN?t ÀSó}hï=÷ðı]™2o~Ôù.Y²„Sºwÿ#7'·©÷xµÚ2ˆÈß¶nÝ:!\:ËÞcì‡cŸjz`Ó»zôè±3|êØ8üðÃUVVV\ý¶ô—UñÂ%(#ºÂ@ï™4 fâòðÂ}é¸ÜŽC?½Ñbý3Ï;Q<‰pÂó(ãp.s€¹Æ_õ.ÿE„ð Š“f"ŒÁåQ„›qx —KÊXí;í+mYº_ãr°á~EÀ£(nÅ+ÔG›¼º:uêô¾à‚ bŠFHOO§I“&äûó;?Æ’G<à‰vøÝ“}F|§&ÖåŒAƒcÊ÷È#$!!!í›[¨ÕnÚ´i¸ˆ¤G“—Rê«F"EkÙ[d Í=/DµñÛo¿CWúâ†@ áô% í'ê 7ç.G˜ˆ2¡0Šèµ¹F ÌÂå>s®Š1À,\¢k¯^ÚÇàÒØËT„èa’:7—[Ña'/ „£Q Àá9\~†â äUžÞýÎߣ8‡ÿŸîËM§ÝÛÀ9Ùf¶¦#žBq’¹o7³rÖç™kµÏN@oàù(ÒÝ‹Ck\®1eðÂ\®55ßoÐ#züAé°ŸSÚµo÷寿üuGÚÊ•+9µK6øóC)ÒLà^ ¸=j.gt=¦ë{?ÌŸõèE‹qj¿åæä–š@'¨#í|tèP8lô‚%.ðþÖ·!<ð2%‚ °Ž_›æn(3ÜõÅ)×#ü aÂ8C€]f¢Ànn2Aàÿ2þÔ<`z¡Áû¡˜Bé8æ8„ŠÛª(ÝhV#|‚âmç GDmFèŽ0¢ø£]0^fmÞ¼9oòäÉaL-ËS>ÂEEåUO&+¯ {ԙǓùå—Â9sæD}á#>š_TXôrðñ¬¬,÷å—_ž˜}¡ëº-DäôtKÜéD–ŠÐ*½å›‹-NNK‹¬Â;}út.üÛßXêϧqd÷˜‰ž» Ò1õqƒÏ绪]ûvÏý0~JRRdCý¿úê+_xá_þ<:̉Aí×Öt-–ÚDjƒÔ—NìvbÞÖ­…æúÝw3Õ! ÓÔÄØæ1˜¡JÆÎ×Ò¤½Û»O/NÎŽ°å3yò$•š–št‹å^C‡mj†¯¨ÆaÀ‹eã$§$?Ýôà¦yï½÷®*,ÜSFL¶mÛªî¾ýŸê ä$÷ËÊ-“£̬aâ['µAêèfÍ›å÷‘***,S>þù‡ºáÿnp“S’·=+{ì¬,'##£FFÆU<áÅ3Ö½PõôLoØàuQQ«^ÝO¡Y»vìöçóÓ’%Ì[²„ŽÃ?ó ¾è¨'Ÿ¾ƒÊ¯-5 ×çR3BªnHMKý㧜ҶmÛ’““ÃâÅ‹Y¶lŽÏù³À_p$ðû¾6Ôb±Ô \ê]POzÔ òB,L©àdŸU²Ö«,03€Åû‹´v,Li±XâÃjÀ ŽV0F[Iñ]çâkEײ_c¦ŒŠ^²+Z”cutA/³HÁ•Êþ-–¸Âþ ã EF|¦râ{$z‰œ€øÆfôrÕV|-–½ˆÝŠÀ*L*/¾­Ñâû«‚›$V•‹¥,Vtk8«ø¶§râ›ü½¼º_‹¥š°¢[KXcÄ·ð,±/­Ý -¾¿ñlÍt‹ÅVtkknB»žE¯‚ -)-¾õ«ÊF‹eÆŠn-E`]‰o ´ø®Q0НÅR9¬èÖrÖñMF1fu0’ñl6r‹ÅR +ºû ¿‹žJ2-¾ù_Q.^ñÍRõÚpµ€–žÕ±ç!LÇá¿èVA8êÞO~>pWy¥Ùø|Áµ‘¡Wõ>HE˜ZAÊîÀ@sÍë[æ |ü-‚»ùÿ}JD˜A^•%ݬ|Lg`Ø^¸¿%b™elØx ‚‘ ü•œÕìO#¾‘.-DÓLÚÖ(:âƒKg`p(zÝÀ§À_ž{œü |NèÉJÛQšÖ@*|‹^Ñû"sì]`‹I›œaìÌ3Ç7Þ%uÂT©t«ƒq+tiÔGwèþeòœ‚ÃùæÙnÁá ÀÁ¡3º¯à“îc„«c÷ “_Â+èZïß>v!<‰ž£„'<Ï×À¤).Cø]s¿a:zÓ„WÞ¡cýƒ^E¿L¾F\a™Xö.5Á½P šÑÜVI·CŽq_4 ºEmq/!L0ŸiùÀ?ÌùnàùQ:üÝš(í^p Œð¤{]Û†”ª½Þd|ÆÁt,§ x-¤âÔÂwÀµf/a³Ù>=£]ÀŽ—ÿ3×LF×¶Ëw/«À³¨µð=Â"S66?l= ‡‡)©ðõB˜m¶½î…Ó‚Ü5gàð Zh%/‚Æ9Æî—€Û€ºk~FÇ­Ÿð­±m5p²ÇÖ©À%@=“oOÙ.C î/@³ g^MÙïvDX÷B5± ½fú&ôk¾%ºmuÀ¾4*D×F²<ü- ±|¹R͵×)ø/ð˜ÀÖ2©ñ»ÆEÅìA1Êl»èfû³?h‚ÿP‡âDá«T ¼Z¼ïr£Ù†°ÐãLØIù¥”"¹\+…µž| €µ!ò Øû€±·ðc¹y–å `[Ð}?Æ-nÚÿ…~~—·€qèŠâ¤2×jº ÌD[ÿ5._£N»¡ØßZò._" G1 ˜‡ðŠ^8tÄåKôwòP(yÌ óH\Æû¦SBKSÓoHÙÁF …8”íbE·ê9÷дÔÿ&Ö«GŸ>}hÞ¾=[óý|¿h1Ÿ9“¾ŽÃíyþÔ}md8r€QF0¯CwŒ4®øªÄ÷z¯´„¼m))¤¤¤Ð·oÚ¶iËŽœ-ZȬY³ñÕñµ÷çúÓ5Uõ,Õ„ L,çÜ]gáòðYåLE¿Ý´ М’aÜ{ªÊи ácï¿q¸7Ê©ó‹Ð5×’™ï\–º|CøÅø<„nú?"Ý ®g¿>ÚG½]F¡,œ¼ôE1 Å& è„ž$j·±³ŽÙHÄ-Žä)ôÈCq*£žÀeˆç\BPZË> !5-õµV­[å~õÕ—e–WÊUùù~õØ£¨Æ)É{|>߅᳌¤˜øÜ¿bq7€º<©¾êÒ¾šØºÆo¨k±Ú½p»Ù~Ü4‰õžnþ lôB&/„¸SÇ0=ù×âðŠÇ®IèÀÁˆYqYø·C]„ièØîHÝ ‹ð†ËiÑ ia`)—ü³}¤q)€®én¤¤oàzcgpôBB®çÞãþ@ûv@øa¥çülJþ6©+Ð.…zH©Ú¬7z¡±qÅôòä³;Phß’Ö íÝ>}ûøsssB Š÷³hÑBÕä€&~ŸÏwî¾¶;ZŒøÞ¤à·H×589I <ëL•Ÿï[>ß?[5hØ ˆI(‡Þ,33sDFFÆÚŒŒ e>ý«°Ú"Å*¡¸á7„fàð®ù7nCøÓÚa6:–u :É\‰ÃSžü†àðdˆû´GØò£ïu9ϧÖH=ÍÞAÆW 0ÀcïL½-ŒMÝÑ5Ò5!ŸÖái¼"+LDG„¢ ÂJ„¯&ãð¼ñÉ^ 4Gðãð“6 aƒñ ¯Ew°Õ3b >ÂzÏþõÆßê[f•ݱk¦ ¬3~bÐñ¿›=éÚ{|Í—"ÌB×’Ûûˆc@Ì¿3Q¼¥ªùu6£8ª YXŽb ”ûupx—K«(]EœŒŽ9üwð ŸÏwE»öm_øaþÉII‘ Ôš;w.=O;-¯ ¿ -Ú?T£P E»©(í ŽÃ›GÁ”ï¿'11²É˾ýö[Î:ûìþ<+"ˆ¨¢›W MÑå²Ýl=³Ýе-ÐMó_÷¢}Á„²÷'"›8© #¢ø}Õ:ëÑ!kéhßìf´ !™ŸòhßéÏÄ>š2˜º@Gôïns˜´¡¸ m÷k±Ü\¿ o#\t®àG÷UÅ5”8ñ+‹Gãð_ãô¯lºŠHú"==æÉZ´h±§ëÑ]/kÛ¶í©®ëöÃŽ¢´Ôp¼_àÏЋ†Ã@ïŸQ´CxÐô:®D¥²c»Q\ðÂz qÏV”žHâ7´¿FwÆH)¿ïÞjrçãpÂTÓózX¤*W¹nìo§°(a<£à3¥'©ìxcFߔʔOQQ;vìØ¤”ZKì«aX,qƒwpDÂG—¢€7& ¶ ¸Ї6¢ô(¥@@w"0Åa@k„;^GqZ©¼t@€$t}`¢ê¯×£ÃFþDq°‡×# ÏËÍ[±rÕªBbh²zÝ:škŸòÙÀY ¾²¤üILjy¿¬ZµúoÄXC]¿~}ÝíÛ¶Oœ;wî«C† ‘˜˜x¡RêàÈ(³ tbYö#”Rq÷¢.-.o™X¾_ÌG£øáó€|„·PL‚R±„ ƒˆW è ,Ãå„Ai¾Dñ/à8`!÷£˜HÕõNž‹ž-ë­ˆÓ)ÒPLE»TGчÒÃË¥°°pÂ|p먑#£îH[±bþ;èTr¨Ö‰o¿àë>{íÝwßuù,X°€¢¢¢íèÐ"^{íµíèÅ7³‡z¬ã8ÀåD6¡ú9{9dÌb Ipíc*0U¦ãêU„Éó~5£8ž£OrLºßPÜjŸÿD¸e&úÐÆ~`5Šk^GØ„¢#ªx4ÌZÜRNêàý`r‘2!m- L”BÅé üÂ/8܃âi„,ôLP—UåØòýŸ[þÌ™0aBf†æ‰‡âŠÂÂâñ«àUµó£îm¦®X±r÷¬Y³¢¾ð¡‡Êß³{OÈyeG=?;;;s÷îÝÍD$“Zâ·Ô~jæT#ñǹÍ[4gÉâÅÉ F6£á”)S¸üœsXêÏdDE ®ùú|¾ËÛ´mýâóHNII‰èšÏ>û\]ré%[üyþt"\å"LíwoްXBb{‚«†O·ïØþjß~ýü[¶l ›xúôé\rþù¼™àBéšoëp+**zkóæß?<ó¬³ü;v„Ÿ®wüøñ\rÙ%ùþ<ÿYD±¬­ýZ,û’œš<ê€È{ã7ÜÝ»w•™SàÏ?ÿPÿøÇÍ»HNÚ3¡rsÕº5P|})i)Ï|ÈÁùï½÷®*,ÜS¦|~ûm“ÊÈÌ(JNIÞœR÷”aÆõÌÈÈx{ذa=Ã'·Xªë^¨zNjظ᳻wí>⤓NÚÕ¾}»ºyyyjٲ廗.]Z¯nbÝwswäÞ¥ôäÊã¤.†šèvžš–ú‚RŠN8¶mÛ²}ûv–-[ÆÊ•+AøcgþÎΔ^¶Æb±XÂÒ8=ÿ5¦ BAw“*¹JCMªùÖ†•#,KMg?_+º‹%~ØÄ׊®Åb‰?j±øZѵX,ñK-_+º‹%þ©EâkE×b±ÔjøZѵX,5,¾Vt-KÍ¥Н]‹ÅRó©AâkE×b±Ôj€øZѵX,µ8_+º‹¥ö‡âkE×b±Ô~âH|­èZ,–ý‡8ßÚ º-æy>³pxhÁµõ 1Û\çwEWzÏWá/‡—ÑçT„©¤ì‰¶^²ç3ôŒ{áð¡×-¬ˆD„äUYÒƆ8~0|/ÜßRÛØ‡â[D·ÂNôs œ„ÃË7GµÃ(àŸaò†Cȵá‚èˆPà±#ð©Ê5õ.Z ¶—“Ƈ0…ÀËDÿ o÷Ø3ÌØy|˜{‰°(LšúþH¯Íß³,zñÞCbÍ8¦eÃ-5@݉}2õZ·zq¸xŸÕe9Â6ôqÐ8 ½VÛ`ÐEGrpé ,Z¡kˆ»€?=÷8 8 X|¨°v”¦5 4N&ËËÐ5Ê7€ßMÚvÆÞ“.°’èF¯Ô}Â\;=ÇVyìš^}»70]Û¿-櫱À ?Ð8øHÍÑe8דÿÁæœxÈEצ?4eÒÕ<û4“þ,`º|OD—ëÀûè%¡|À߀€«M>^N26ÎCñÃq¹?L¹„Ä®‘¶Ÿ#0Côë§¢l1fSs×p«"ŽGÿ(·G›&p à"ŒNF‹@†èÚÔLãÐap о8\ lC Á¦Þcq8Pè½?NDxǤ; ½ZvPhìí€p?px…wq„ËפH:¡E„i8ô¶#\‹ÃS€àÐHD¯Ô-Ÿ! D—áûÀÅ&¿„l`Â)»G‹-Ozž¯!Âè¿ÏU&/pè‹0 ¨kò|á Ï*àšËÌý6˜ýoP ª°L,ñ‹‚&J)ソàv¨-î…"„ æ3 !½JèZQÉRû{ÿÏl—¸ôñÛ<éþ œŽnŽ{ku7âðßvtDp¶}¾Èk&§ÖÂzµÙKGØl¶»¡k;F7šk&£k…廄ÕèÚt`ÿ{„E¦l¦ üa܉@"Qâ†é…0Ûl{Ý ½zÒõÇá´{A¡E 1BޱûtÙ&"¬AXtÎ5÷aº¦°uºæ_ÏäÛÓS¶Ë€K~† !V•zæ(°î…jBéæÓ‹ÀÁ»CœonV ìR0R kïZY–½ív¨S§E…E¨-ç¸fŠQÅÛºÙþ‡ÙŸœ‹ÃHÇ 8XX&E`tñ¾ËÍfkÂ"O‘ì¢|_q©9‡FXïÉg'°Þ³Èspp½Ñ¬¤| °-è¾ã2Ýìm~"àqÜŽCWÇ”¹VsÂ,Ïc<.ãÑ®‰]&¿ÒÏáò% (fs~GqpùÝÒhŠ×M!ÌD8·Ø-ñdžV#€&”uíü 4¶†- ¬èV1J×pÎF7…+Húº'ôB“þ~_ Åoý}Juˆïx žãœY?©>Mš4¡ÿþ´NO§`góçÏgêÔoñù|‡ååæµ~­ÂÇ ØR•ÊîË9w/B\FáðP9wÞEéïHs Ðl–M^m܃ЗG‡qx Ê’*DûDKlvYBèòé„0E.·!<"]pÙ$éhp¡ÿ–“×pè‡Ë·(~Ãa ŠÎèÁÀË+ÁlÔÇ%×ó{<ùå èãOâr¹çœ7¨°¢[õ´AAþ@û¦Ê t¹÷¾«t-ðJàâDtT•øÀÙC’“Î>:½UѲ_æä“O.“¨  €gžy&í¡G^°3ç•EEEUÆöÁƒ×mÔ¨Ñy@†ˆ<öÒK/M{QU …â5t-¿ŠÐK%)ô¿Ó.Çe :àCG}7hâøŽ(rè‚âõ {×FaÃàP`]i;KÑ•Еo->ÐÏ4ŽèZæ_èÚwWV·˜‹â:ô÷tŠÑeˆD˜‹þ­½Œö¯D/ŠmÀ\î2®†~Pì®iJI«!*lGZ#ð˜èÚëÌ ’€ÞMf?ðÌa(ÕMe:Ü\àÒä$|ýûóÝü|¡ ~ýúÜqÇ2ýÛiÉi ÒÞD÷fGMfffûaÆlÔ¨Ñ` ÐW)Uq(WtA¹áS x á!„OMçÕ „ë–¸üŽ0„Ë(Œÿs)Z´>G× ò=9ï{íØ‰°²ÌG‹`ðuy”Ôä\¬xáA½«®CGVä¡k€ªÜgÖ~Ñnž#¹„p©¦‡šûLǡи-®­ÿz Šÿ üˆ0á&÷…°£ô¾âsÿ¥&¿ŸÐƒó×!܉ðÂO¦ƒmªÉÇëæ(ã+†í(nFx]ÃmüBø¨ŽTåÑâAÁ»C°OWA[tÐH;•nší>o‡FMÍ÷?ŽÃØ®G3iÖ,"Êÿ»ï¾£_ÿþ9ùþütBûüJá­Õ¢_ÁßíÓ³³³ÇGtóª¡ú%ú Zà: k»{€#Ð/Ú¿LÚÖèy$5ÅêÂkoÚõµšÈšÐGàpnqtA8Ðå±-ü‡¢…ý´Ÿ4ÙØ:„¬9ÚÝTžG‹Ò÷;|·B0ø r5Ö½°oü¡Q ¡Eb˾1'zÀýèp €Gê×cÂk¯E,¸'Ÿ|2_rqÂûï¿·?×[yé233Û»®{­ˆ\C Ü*>ØFéôOžíàNªÕÕoNX*²7?¢…º%‘½8ö k¢6y¶7¥ÝA4®’È("ºçó’€ÐeFßÅ€­éVÁ5]S›=ÝdY l– ôTÐø¸Uà©}ft%(¯æû ð|·?+zWõòåË9áÄÿÊËÍ;OÇIµÚ2¸®;°°°0Ö8dK¦NzpnnnósÎ9gÞ¾¶ÅKݺuwggg‡rÍÄJ:ºU° Ö lMwï‘Ì&ôWððOß ››~àí}h_¥(¯ÃmZb]ú_[y§N¨W¯^½¼Ü¼t`uejµŽã|X·nݘ찄§ÿ˜ÜïÕŽˆŒ.¨Â,טOÌXÑ­>>E÷™ýÀ(JB¡îA;üC’’!™5–`ñÝX¯^ïZ† ∈-Zì9ö˜c‡´lÙ²‡Rª§ˆØÖ™¥Fc£ª áÿÙ;ïð¨Šõæ$aÒ@”*¡7± \+VlØ *VÔ°Áµ׫ȯíZ¯W” W±¡ÅþS± R¥W©Ò“Mß3¿?f6{²Ùd7›MØ$óyžópÊœ9s–Íwßyçw˜$à~¡EW@>~] +àD êÅL¬°ñE;,Ž‹›åõzCßP %%%ìÞ½{b%ÁGð †z…±t µÊ†¢Â9k×®=~॔lÚ´©É¾½û¦Í›7ïÍQ£FÝS\\|…”ò&«¯ IDAT”¼:x¨Û †@JYɪ…éªj›38òˆ)K/N¯îóçÏç̳ÎúsßÞ}ü™™™ý,Ër£æÍ‡“»¢®CÆ † ÷‚¡¶™¾nݺ¢™3«Ÿwúѱc=E…E¯»öÚk¯-ÈÎÎÎr¹\m…YËm`0Ä ÆÒ5Ô:qqq—gthÿúâE‹“SSSú磦Êë†_·#/7¯aÎü aýK×K×Pëx½ÞvîÚ5鬳ÏöìÞ:)ÓçŸÎ5×]ãÉËÍ;—jLµ4Ö¯ÁP‘p¤„~[`rãÊîí*}É” >¬Ô´Ôç>äà‚‰'Ê¢¢B)¥]nÛ°a½¼öºk½É)É»€c£ñЬ¬¬²²²ÞÈÌÌ :cÎ`¨kŒ{¡¤šÞ®âiT Jç TîOCynHMK}½´´”¾}û’‘‘Çãá÷ßçÏ?ÿD"·õ¢ª„2ƒ¡á!áO »$œ©·avH(‘*u£³l\Àñ2H·8°œã|¥I B\«! aåƒ!bŒO·jŠ|«·I¨\·ñ@š„&ž—*IH„e¼¦U•“*ï嬆 ù^÷ ©„[¥ÊºT,a½„aú|‚„K•±>_Â'Rå5 †ú‹¶t÷Jpëm´„< ŸéëwëµÀþ.áF©u²¾Vfé†(·ZÂ* Ã%¼%ÕúbHè¬ïùRÂ¥~J|[H¥ËÝ©ïóHøøÀ}RÕÆXº†FM}ìžÖ%éÀxÇñ>T2P‰Ê7ãÏH¿8WVô“–ûÃQîrÔJ¤mPë7ÊH¿u½'*¯é?QYêót}Û€%ºü2à –Py[ C cD·j¶ã_u´+jѺ'3QBÙLƒÊ±°%ÔNËÙŽr77¢Þ”-ý!`‰Tײ€Çô饨Ì]>öÕJx[ Ë3 1Œݪ±…?±ó<©ráú"6ùþ Õª¾iB¹$œ-‡Ýÿ åv¸¬ËtG­*p†.û.{Ê ŽwÔw4ÊG\o  C´OwKÀ¹í¾s®Ö¾Õ$Œ“P*Õ’ë>Ý å´¶H×9Aÿ+%dI8YûtçKxAÂB]¶‡„¡ºÜ'þ«£)þW÷ŸPÄŸ®¡Qc,ÝÊGEÿì#@G Þ‘ÊUpÊ2½xI—›‚Î,_Y9¡Ä÷Jà 8µ2j¾€Ÿ$ †Ç¡–s¹W(·Ä ©|Á× \/ÖÆ`0 †ú±t §k0 uˆ]ƒÁ`¨CŒè CbD×`0ê#ºƒÁP‡Ñ „Ãeùœº‡É*2ÕR,ýìNuù\C•$"XŠ`7‚߀{ñ‡a¶E¨<úœoš÷Aª¿‚…à;ÔD™P܈`¾c›‰Å£º½U‘Žà{½1ð eZÔ=ÁÀßÃ~º!J2úêVêÉÎm£¬Ã$åõsß­«gÖ" #dLð ‚Y¨8êTàXó±xA—ðMóèà'½ßÁ¶ž·“ Yì‚ð‚€Îz€`)VÙtòÊhÀ·¬GV¹œ#>E QSâ;Ý€óuÛ. ë=êÁ¨œ%1‰™ÔdP^žEåB(Cªžƒ%ôRß’DËÍHHPä|< …Êçö}†:c p,’î@®>7ÉÀ›¨$F^T‚¤D,!i \ÌÒå; „Jo ¦}÷Aõ¢æé2G.`®>n \‹²â&£íWD‹d­>Z‹äàz}<•CdGÀqÐïZ%¬ÅŸ\i‚σ­²ç¡¦ºŸƒú»yG¿@ p5Êýe¸|ŒÈ4Ý€Þ¨-ßqà< exørŒtD}†IÀçÀýÜfúü8dzcã^R_^] õÙ @§kÜ-U×r*GîéV¢òÝJxSB¼„ƒuÙw$ÌD][&•倄CtšÆ\TÊÆ‰RýÑùh.á;}ß ‡Öág`ða1Áûø×Çj$'Å(7Â㨿±Ttf].Á‹À§!xGŸˆUöãî;¾¨ìHð„E où(ʼn ­kÁý(±ò]½š¹­’…Í|}|±þl °è‰`ÊBO@0‹€` ‚ñ(·Ç¬r–ò,.×û"˜bÑÁ|à`à?£þ><ÚeÓ %ìqX¤£.cé†G²„iz¿ ê?z„>ND}©î¾Fe&ûe9\‹²†‡stÙ‹€±À|à6à.TWô?À¹º®–¨D7@Y—õ àQ”¥‘‰ò¥Ýý×5T‰¤-’g"¸Ëqý^üV>6ï"8˜ˆúMBr °É4-á<÷)`26`‘Œ$ ªQg¹óµ8´@‡d`µÞ±*D™åtDð!’ôµ!¹ ˜«Ûù’kPl-®’Ya½·à1$Ùº¾t$×£zû§"$KQ=Ìǰ™€ßš)ŒèVŸ|”¥0ÕñqŸ€7tž\_—§ p¾ÞÒQvŠ€'µ;â ½Þ|#à%]ÏnT–2ÿ'à_R w& ÀÒMh’€×ëÅöÖ£TÀ‚ýud/^¢Ï¢þÏ«êÖîFåX("üµ ý"e3‹Ó+É ü ’Gô~1*gGq˜Ïr£øžÜÕåXtGðDÙuI',¶ ¾ÿÓñ§à›ChY*¶³9ÐØŒ`/’ö¨÷i‚¤ö­H']ιBŠ2P7ñ»ê’PŸÓ¢éËŽFt µMrÓä¦[æÏ›—Ö«W¯Ð¥Œ¼é¦Âwß{7;oÞm••q»Ý=¥”Ã…™T½}m‰®!RÊ‹n£Áˆ®¡¶¹øäSNzcÆôáLa-ÇêÕ«ésôÑ{ê).*ìÚøñãŸ0aÂýÅÅÅmQqÐßÖ°C`,ÝZDªTsËðÎ Ê‘4&.Éè1qÉâÅÉéééaÝ0yòdycf涼ܼÎT=頌֯±t 1±tkçür€t@íý{÷¾5àŒ3<ýõWÈÂS§~,¯¿ñO^nÞ9„)¸`¬_ƒ¡Q£—ØÙd©ŸAºm‘œšüÄA-*ÈÎ/óó=RJ»Ü¶zõ*yŰ˽É)É;ˆÒrHn·»¯Ûí~uäÈ‘ý£QŸÁPSŒ{¡–p  ÒXÔ|\›š–:±¤¤„Ã?œ2ÈËËcùòìØ±¯íÝZ\X܃F65Ô`0Ô ?±r¥o#PóÑ0¦4"Äøtk=€VYwÖn¨Ãæ †ˆní8€HcP3=Ft£ŒžvMˆbmˆ0A·Á`¨ßÑ>Cñ/FYîÐE CCÈnô WLë 5ƒ¡QcD7Š„@ Ä ¨ #ºÑ%ÔZ f@Í`hdÑa bÔ †F†ÝèîZ f@Í`hDÑ‘ЧP3Ft£@5Ð1jC#ˆnt¨îZ f@Í`h$Ñ­! bÔ †F‚ÝšéZ f@­~‘ŒA° ÁJà! A_?Á ½Ÿ´Õû-"ʤ&X„³ìÆ-Ö8¶ùX<4 q_3 õþ0,þ¤Lë€º× ø¸›ØJÛÿÿƒ¡¡QE Çên%åcCHí(|àGT²uÐÁ/X¼¤Ë¤Wêýþ~Òû-l‹à‰; o·|tÖÛ‰añDˆûZ Ø­÷³°¤Ì¡$ÐÕQÿYvW„õuà ÇnFeK·Ôp-3 V8èƒä|àW X„d²L„¥.›ˆÅ  5p‘£Ž.(Ëøà}®pŒ£ÌQÀ±ŽãÖÀ½úžÊ ¹ÀZ½ýŒä¿HNÕW‡-¥Ãa­c›†àK,ú9®Å?{êNnFýí EY¤‡':Êõ8>ƒz÷VŽó]‡µU~‚>wÐ µ\S‹j¾W`D·fÔt-3 V°¸Á€'àÊ:$'¡Dø WRC*‚g€µXôAð®>?‹!Žr±B­Ê•bÑF»Ú…ÕZ‹S€ÍºŽûŽŽ:ï:…QOe¤ é‹]ÖC¹ ÁDl¶cÑÁ\”ø5A0‹c€½ÞCr½ Àâ2G°¸\ï_¤ß{­ÌC y+3l¶!ø %Ö1ùªмÀ,½mMJÄåõÒ¡¸„¨Ÿæ*ð ¨}ÕÆ¢‹¤-’g"¸Ëqý^`—>*ÄæS'SQ‚‘„ä&`63üæs¦`éHF ,ÀÀrç#˜¯Ú )BrnµÞ±*«Gm¼‡dо6É•ÀÝΖH®v»°¹Z·qNX®$Á£H®fëúZ Ìö/H~E-ñô5°˜Œÿÿ ¦0¢9ÍQݼ2lY–uâ§I‰—ezhâ^H·Œ Š‹‹Y²x1O~ñÝJJøgnÞòSàùJêÝ]ÉyC¬ Ø‹ =vÙ™%HžÒמE ¬Võ¿ ÊüºÅÕxòì²=›™X p´ÁÉ/HÑû` PRçTä({r' ø°èŠà)|îI',v \(3Ëœ.0ŸÐïžôDð/”-’ŽXäcó"ð;‚?|‹ÍÇ(;'æ1¢!¶ÙŽSMÓÒÓr:uêxêçÙ9æ˜ ÷x½^Þš8‘Kï¼³Ûþ¢ø¢¢¢quÕÞÆÎ˜1câÇŒS•Êl~DðêG·Ø¢·$Âëò{ÃzŽEJÀ™Ô€ýà?Ђ]H„õŒòu†ËZü¢«üÆÊ½ (Ö–®_ä%EÀXåü»‰Taà{ïR H[Êõ!¹häl£‘ ü;‚÷©SŒO7:ˆ´ô´Ï:ë¬ÓçΙ›Lpâââ¸þ†øeḷx:..®¦ñ½†*¸ãŽ;’Ün÷P·Û=mË–-¢Xõ$”ou<~ÑJÅâu*ÍŠ £ÞBüþÕ$$ƒË]µ¸Eï¥#¸›oª×l@ù›;êý>@Ïê( eÉzéÀ0`Pˆàà$`:’Ëð‡ÎÝŒ<¤YöÞ.D™[ßWéú | ®Ö€·|ŒUî/fµ-fVŸHHHÙ¥Kç“ß{÷ݤ&Mš„,ß¹sg¾6-Ù•èzShˆ"#FŒè=bĈ'=Ïf”oïÌ(?¢ÉYHZ#Ø©ãUWaó ‚çQV^)°Q—ߎÅ+(aÚà¨Ëyü1’“ÌA0ÁgØeÖì* EǯÖbÌ÷¿%DÁ‘L@ð*‚Æ"x%ö^`½.µØäîR”e[›¹XüM×+‚ü¡Ûúð ð‚‰~Gð‡ŽvÈÓ5|‰`>‚Ù¾ÂÖîÉm†!X®ß{¶.?(F°Á"$`—¹ì6!˜„ i‹9b) ¹¾Ò$9%yûO3f4;ú裫uã?úGɸq¯|°wÏÞÆdñ£G´+c.’ãª[éwÜ‘äñx.@M2 &²çdggGb†¢9*êO”(UÆÁ(w^¨Ý”‹bJ i 䣄1RÒPíÞª` hƒò'¶3R· Á^$íQ` (+xNW‚ŸÖúü¾ ç-”{LJ õn \WNâÝ1X¸°Ë añ*6Ë ,à;r,žÄf!Êòˆ&£€·¨øŸá#‰El>¢rg{ *(¼mˆrœ}tߣ'/\° œÙB娾};;uÊ/,(lF4:b›¨Šîˆ#z×!ÜT=y ¶D×)åE·ÑàHûI*hÙ÷k¤ØÕ¶:‚bóÀö¨Ô¥ˆÎDð”W &ºq¦!X¡»~9:Ü$ð/X¹ëQá'U7¢Iü)ƒ.É`­Zµ¢mÛ6¥k׬ë ,ФޯHVm,Ë:äæ›o6®œbâĉÿ»à‚ Z¶hÑ¢Ym=C‘ÿòË/ÇTè˜OtB™îg_ésQþ›eú8ŽÅ¡Øüà(×Õ:ø5’y%‡c³xÕò‹¨îÀ0,ŽÄf9ð6Jì[¡œû»°¸»,B @,ÞAÒÕ•¨ŒSƒ±9Hlw!+ˆn°rw"C‹njJj—öíØ7ž‘‘!×®Y×#º!ñYµ'”U[Û¶ß±íàñU†ÃUW]pGiit‚J‚!„˜ \\kˆŸèÚz†È0l-¦—b3I_w!øÁ÷Øüà$¯/`1 I_k±Y€Å³H:c3ÁP}°ÅÕØÌ–cñ’ŽØLBp)0H‡tAð2‚ùØLE0Al²*´ÜÖs½EAöÓ Á,d™àÿ„šzi¹ xmoaIIäžáÉO¾ Ž~V p»Ý Bˆë·”²_È †Ço¡Ù¼ƒd*~Υ畿¯¯^ ,Çæ6àU$Cuœ¢Íz¦É$Ç#y˜ˆäzì >ÜöH† LÐb{þ)|ͱ|€äQ$GÔàíZS>H}Êš• BÞþ¼•+W­ŠXuׯ_fJ¸0Òz*ÙÙÙ%RʶmKŒÎ02ªƒsrÄ2ÔÌ•óQßë°8ÉѦé²5QÅ$JÇt>É^î@0 »B¶¢îºnŸ…ZŒJÒ5á` þQÛêÌÖ©ˆM!V¹nh¢®[FT.Ø#lû›œ)9<÷ì³ÕN%·lÙ2d~>ÝÕaàS©ðžðYuëk¨dgg/î5jÔ#EEEƒP¾Ü3¨^ôÍN‚G6ÁBß(åg¤IÞEpõàAð6ÏÜï í(rœûɧÀñ:¾n’ Çõ<*΂IÅ/ÂÑ ñØ0ž 퉴\0îß·ûÇœ:dÈÐ¥<ùÈ#ÜPTáwňo%¼ôÒKE¨)§9n·»§”r¸"“ð²I]e¢ ±@àÐ$` ’ P_n…ÍwH.FY{€!>$˜%(Ø œ ÌÄæŸ¨yèN+ð7”•|²>>åZ˜…÷•2ïj½ÿJÄÔ¨µÅµºÝÕ) ¹ßþ‘î¬,Ïöíág|òñ'üôÕWÜY¹?Ø'¾?J8=ìŠ ÙÙÙË'L˜p¿Ëåj‡Jã÷-aôL †M`î…->’­ŽóÓ‘|€ÊŽ¿HÐ" ÊÏæ×’üÁT`r+$÷¡,â¨@é|$ÃõL˜ÝÀÁH2õ~!僜ƒ±žòÖq/· yØ…d´ž.¸I¡nO°rWR.¾óäyž9åÔSïþvÚ´äöí«ŽLúìÓÏp_s5_zòI]÷©À÷Æò N ­_ƒ!æq‡Ú—–ˆšß]U8—…š5Μôš’FxÓmÃ-”¤¤¤»ÒÒÓ<Ï>óŒwïÞ=RJ»Ü¶jÕJyý—ËöÉMåÜÈW˜¨ïnµ¾rĨQ£\¾œ n·Ûv»ÝÒívŸÆ 5ÅLŽ>½ŽHO{gcQQßÞ]»’Ñ¡ù«×¬aÏîÝd–”pWqÉæt5å±&Ÿ}µ|kepeŒ1¢·Â-¥ü`„ ?G«^ƒÁCH¸Äò'ïƒüä"^¿µš#áp “%Ø5\[­¾Y¾ a4ƒÁKH¸$„Pæ8Ê66ñ5¢khÔ˜ÔŽK…}?%Æ‘ŽÀû¢ê‹ø #º1‚_ƒ¡q`D7Æ0âk04lŒèÆ(F| ††‰ÝLj¯Áа0¢[O0âk04 ŒèÖ3Œø õ#ºõ#¾CýĈn=Lj¯ÁP¿0¢Û@0âk0ÔŒè60Œø ±ÝŠ_ƒ!61¢ÛÀ1âk0ÄFt F|£J'kÛ Ÿ‡‡qïA@³e.Çâ±0êêÐß6;Œ{ÃÃbjµî4‹ª(y>p‚‚´ç†0žÖµHBU$"XF]5¥ ‚`kêuî­ƒçªCuR;À6¨”’ !µcw@g½õÀâEk •˜Þâ)àžõÀâÕ0ÚÑA¾£¾­C÷†Ë…¨„ûÍì­¤L‚øÖBTÿ‡·8Ú3Aþu+ã‹C”IBà ·ñ5 '‚߃^QË‘EüK·‘b,ß#µz[Íh j *€~X<†Åó(+ '’£ôÕçÇâQà!ÔWNÎÃâYà&*®g¬¾mƒ¾Öe¥^ŒÅ¿³€ƒ‡´øwrÔÓ‹±º½ÔŠKLœ«¬nw´çcà;Ôw”¥'/·£VÃâRÔÚv—ërMÛ°x8'à™1Xü µÄVÀÿs: ÿçpÐJ?»Q=Pˆ]tÕŸA ç' y‹›«ú@ªÂˆn#LjoÔ8 µë_Àq¾ÀæOl–!xäž>¾Æf'àEð ʪÉ…XœŽÍl·@¥䂊–nk}í$=±ùÁTm¥­Z x[—;Á—ØlÆf)‚qÀºö;Q]êʱ¸›iU”8µê÷&@ ø‹úÝN×â[±VÁwXôÆæw/#ôµ&ž–‡è•»‹ÜüM·û ¯ëÏç`¯ùÀß<ƒÍr,2ôÒQMQÖúŒÇv,´«¸Áà}ü’!U~&†ºe/\6 äË’7%7•OtÉç@.!÷BeÔÛ¡¡¸¼æëm•î>_£¯÷–•¶˜ŒÒû~÷‚Å»À­ŽzïEYh#Ìtœ¿‹W‚´£'»‚O×â }ýF_”•ü„²æÚ#Ø®÷Îu´÷ àïúžïӨʽ ؤ;Žçè¶ÌG°G‹}þ˜¶<›³Ëƒ e=\&Õ ÐhàR"[@ÓgùVº€¦%,’š&QZZJQQQÚ}(Fr™ãx P¨÷—¡Ü ¯!鉤° B ¯%!ù IDAT’Àÿ:Î<­ÿí…àGŸÃÙm¤I—€zý¶;Ž‹¡LhuúÚ;I/ÝÞªÍiÖ¡ä9àÿôQ.ªP ÌÇâi$G¡\¹Aê< Á|GÛgë- (B¹-|擄ùÁ}H~Ö½†íHNâ6_¢~–8Ú9‹Øx…Ž6tF0 hƒZ)Üù£³SŸÿ‹jbD7z$§7Kÿ¸[·®'LȞܧOŸ ^|å&½7‰Ì¿ßzŠ+¿ðö¢¢¢@;â6Ä7ži‘pj|B<Ý»wgð A´iÓ†¢¢"/YÂ'Ÿ|LIIiïÜý¹})ÿå¯1C‡m’““SºdØø|©±‹äplÆ °x®§Mì8î_¸½Qki(,Er”nï|,ž©¦“©5ˆ̧HïcówàQ”Eùrr ÅqœôB feŸÍtà=,b3Ø‚à:$½€Pî$¢\A`‘†Í.}¿õ£àc'’3±ø'ð’ÁŽk‰º>ÃÂJKO›6ìÊaÅÅERJ»ÊmÆõ²}Fû¼„„„pÂhb‚šºö‚<;%Yžqâ ÞeË–ý\JKKä„ Ù2-=-/!!áÆš¶Ùív§gee¹Ýn÷"·Û}v4>Mw5Õ•ÎÒGÍ,n|î…ûôµG°˜Œ[qéîð9TŒ^Ȫ½,z¡3Ê º‹ÿ9Úõp¦>j]æ^ä ë|í]€à ×½0wß½piв*’á§²’ÿÔ÷ƒr/øÂÁºëöùBÈþÅ;TŒ^hZæ^P5~ªÛÙ HG°Qæ‹õ}¾è‘VþD 6&"pv·œÑ ÉÖá ‰S~éÍ(WIµ1iQ !!á–=zœ0ñÍ7B–ÏÈÈàÛiÓ’š$¼DtÃ{j𠏕—¤$ÓåòËùzÆ ë°Ã Z...ŽÌÌLæÏ›—Üü æ/ÆÅÅ ¤­™™™ýÜn÷x`³”ròâ‹rPí¤½]÷ØÀú ï+øßȾb3TÖõ-¾ÁBlŠ®ÀH`Ð^ǯDò‚…~G0 ›‡Pß;çg/˵Kò!°XŽryÌD2Éq}$‚«¬D°ÉKÀœ õ–ê}’[ÜŽŠ’è‰ê F䋤»h(+9%yûϳf¥yä‘ÕºqôèÑ%ÿùïKSöíÙwe-µ­Ö¨ŽÛáÉøx¦÷?/¾ÿË ïw~ñâÅœp≞‚ü‚.øý•âv»Ó…—K)o&¸Èž“,ؽ¶h‚|Ù¡v£Ä®-°¿¦£D-˜o³®h4ÇÿY;ÛŠnžF–Y‚áÐõCRŠ ³QB†êºû>·x”Ï8äw š‚òÑVå/¯ŒÑ¨H†ˆÄ}£u,\àÕlTsͱxÊ X CðGû°xÁ¹ØÌBpªîÖƒÅÕ¨Ào°xÁØüŠ` ‚ôsº x‹[°ùÁiX<´å6—i+¼ èuEO³ð þ,”¼"’K°¸XÏ~¹ _8×:$Cê`ñ¼¶· &1¦–Ç“zô‘ Ä¿8à¶ÅåâÐCCå0©œvíÚ•žp ·ºÝîß,ËššêY-Á5b œ®Í»:Ô# ežܯ¯^‰` ¶µÌCðRûk±u!ÉËHþ|€ä{$«HÎCÒÈG2I‡c¡¯§as#P„d7ðdÄogÑ5àcÊ/¨èƒÜ‰Í5úÝ¿Au¤òQXòöç­X±rE U[Ü•²vݺ¸Wa,p¾„ÇÏEäSqcŸåÛ%>þ»AEEÁ¦À†Eaa!¹ûs’R !Z¢Få †z‹srÄ(wÂ(§úoøBJ,z#ù›E%Z‡ æ,ƒ]g’±Æw#˜†]!¾°;ʯë‹s,£÷ ª}¦cÍ‚ÙmúÏÀÿóÌž=›øøøm¨‘nôŒ² '++«›mÛ7 !®'<ëwx‡Œ A z8[»ü1h6Ó\‚é=ÀPÙ·7£f½ÌÃæi” :Åý7TܯËy,jöÈ<¢Càj½ÿ*h»#×#™¤¨™@>¯eq‡3ê)÷ïÛŸ5ò¦›<[¶l »‘Sr¦0÷Ûo¹­¤Ò¨Ÿøþ,ÉSê³¶mÛ¶ï믿®öŒyÄãÉó*=~üøU&L¸Ïž=íQ>äo©Ç?N†ÆC ènCð *#36n&’7,×ۭȲTs»p&»Ü`‚ß,Dr'ªK¿eÍ ¹QVßÇH®G‰y!ÊÅà#ð8ë)o÷B”ÅïFò€žió»Žvx>H¹ýÞEð9*;Ò]ºMNò«hËtOžç_'|²gݺÊ'*ù˜’3…[®ÎGž|ퟩ’ãÿ“0[Â…õpÀÍ»ßþ‘ï¿Þ³sg°IUÁùßÿÞ°ç糤¤$Øô×2rrrг³³s²³³ÏBôR>Eù8Oƒ¡^Ox]¹Ô¬›P uZR7"’„ÊŠxü‰Ž«Ëåº%9%ÙóÄã—îÜùW…ÜË–-•W^4XvJn*ˆ¯ÁÐ(1¢#ñ5Ftc #¾CÃÆˆnŒbÄ×`h˜ÑqŒø #ºõ#¾CÃÀˆn=ȯÁP¿1¢[O1âk0ÔOŒèÖsŒø õ #º #¾CýÀˆnȯÁÛÑm ñ5b#º #¾uB“ÝCýÁˆn#ÁˆoÔiƒ`>/‚"ýï/ø×ÿ;Q¶†`+üK.uGPaa¼Ša”üíØŠ,%tÿ.|kG½†`a2GÔm#(D0ƒÐ Ô%ƒ¡Üš‡1…ÝF†ߨàw É™:)å ¢l­¿ÕHžÒû!ø ìÞÈ*žðþ^››tÑÛ@GŸ…¸Ï¿bs"ÁsÇI]w7$·ý—Ã{:@0õý44ª“O÷@sòù6„|ºOh«0К:Á”µÛ˜Œ`©.?8 ¸Á/fýôý™À(G}7@`—#˜àg*_¬ô[«ν‰([mûU ãÚ«ÀÑ@7ý €wôJ êÿ.ðGC° Á\Ç™»u/à'à8Çùîº}s€aÀ›@ jØ»å®îuß¡ë› œè81‚¹÷ésÿÖ½ŽyÀaAÞÁЩO¢ë£Å·þ‹®àw‡E[Ç— ³×bàI”èJ›Pâ½Á}ÏWf9êøR‹2º+Ÿ¼…`–H§ùݦ6"X¯ëÉÇ¢«êø"Ýn ±úx‚v«¼Šà'¥Àáúsð¹:^CàÑÏk|ˆàWG9ZH^Òõ~Ô?XýP®/jó×ôþPàA½ÿÐ-È;"ÛàŠÏA>'¯KI–#“›ÊÇ… bXt}Ôø6ÑÝ Luœy\‹ˆÚà:ü¢ 0Q¶böaZ¸|‹n§EB‰.<îhà mùò­Ã—[\æw…Sô}5Ýò>]‰`¹£¾bàRÇñjà”ïrÔ5LU‹®ò_ã¨o9ð5p;åýÜ#ñý©{*ë pbÉù݈s¹\·õˆ³ëÛ«g ÂéíÛSPPÀšË–“Cêþ\îÏÍ;ä@7´2ü\+á ààJü¾¾êàóùþ"•X|.@: ÄÇÇãJt!mIA~²üåXƃ ƒ£¹o"µH ²C€UÜo+õ~AåñÿXK~ApZ%å¶"¹Rïï‡r‚Vs$=€R}t ‚7Pß“¯€‰úºK ûÁPNÈ?Þ ñ¤¦@ãQn_}^$ÙÀ­Ö;‘|rW©éÍÒ§÷?©Þ²eKƒ.-nÛ^™“3Y¶iѼ4))é¾ÐUx¢iùî!.—k\RÓ$yÜñÇÊÇ\Nœø¦7îeyË­7Ë–­ZÊ”ôåý€Ñ@Œ5ÊÅú^%¸O·•¶ï¦jK×9xyd¥–®r#8-ÝÁŽû>G°$HÛ‚ùtýT´t ©™{ÛPÝÿx}=Íqõ`Ôçô ‚ßçÛTaéNÓ–®¥ÛÔÒqí ]_3]w{”›¦˜¦ÛÓ–®!:Ä¥7KŸ>üúë KKK‚ ®sûóÏM²c§Žy #tÃÃ¥¦â» äi)ÉòܧzW®\ôsñzKå[o½%›5oæq¹\7×´ÍÇo–••åv»Ý¿¹Ýî³£ñ9hRäk¿¬o°æ0[«Ý\G¹ÊD÷#êýv¨P4§Ow*Ê »îZ?¤m¡DwðŠ>¡…¯¦¢»tt„àoïPÜdê÷Ðí¯‘ˆoÈSR’åm7”^oiÈÏfíÚ5²u›ÖyqqqW†nQE233û¹Ýîñn·;ßívK½EStA‰ç&íÓô¢Œ^G Nj g¯.{’Í5(qËsÔÓÓ!ÈÇh?l¡ö  |( U°H?Ë.ãŠ|@Õƒ|ÿÆ7(§Ú¿5ÕA‹(A›äÞ#µØº%§"ج÷G°A©Ùå|þæËñ¤¦ß¹H·ákà{}O?{Ëêó·«‚ú\±þ ÖŸÕ6Ýγªø)¶²¶HlšÜtûÜ9sÒz÷î]­ÇŽ[úìóÏ}´oϾËk©mµ†„^„éó},!¹§žÌÇ_ƒe…¾téRŽ=î8OA~A7`k¨òÇoær¹.“RŽB–rNvvö7AÎ×”f@k”Ö®¢\+”Xm®¢ŒžÀZ 8ȵvÀ>('ÜÕ¥©®gU êET;óÎ7ÕÛN6’vøÿ»ÂjÔ×H üçÚe̬Æï{Ž”èZ<4Å.#ïbó+ðLŸdñ6sQùÑäATwiO%×›cq;Л)ø»>Õá,.JtÓ×Î?æØcÞ;gNzu+ݱc:v,(,(lFð?°˜GªîõÃÀe ÞßtišÄÜ¥KéÔ©Sµê¾ûž{г'dOÌݗ뮬Lfff?˲ܨî¤*ª«-Ñ5DJEÑmø,Ý|†¤ þ?þÛ‘ô¥ê‘Øpù° X…º’󼃤#ÁÿãâñiÛÌFð’[P£êár ‚qHîlO"q£»}M›<ñЃÿ¸ïá‡ލ×е[·}kV¯A§]Ö*³|ß&~Ÿ|ÿ}e·Vʆ 8¬wïýùžüæ8¬È0¬ÚŠí“Ò ü\íFjO?ýôÅþýû?ТE‹@ë5j$$$ì7nܦڪ?|¾™ÙÀ^à(›.x°¿àdaq(6?â_9 eÚæ³€X†Í:”º5Òèôx ‹Ã±Y‰ù,BuyŽþÂb6Û€uÛÊ#xèDÕao€Ä2 ^âBðwdPÑíŵ@sl£üs¥®Eò/|á(’4,®ÅV¢›’’Ò¹]»v»i22Ú³fõš¶ÔsÑ­,Ôlv¢‹Ó ЍÎ:––jå{ò»«œV­”²*«¶bû„ÈŽ¨†ZcðàÁ@¹™lQÇëõN.®ÍgT_wP"x‹+W.A2I5E0‹¦ØÌBðT·,#x‹ `7/"8›o°è‡ÅKºÜÅÀ±zÿçc3‹þ>ÑÏé€àY,nÀæ3Ãâù -— Eò7ªŽsì(gÝü ôR®5‚Ÿ±Ù†Í÷nnÕÏy g<¡E{® o©×STTýü%e俥<dž1}¶^˜Ûa‹Ë%Ûµkq}mÛ¶+=餓nw»Ý+,Ëš¸©Ú`0Ä4~+Ñæ PNn‰ä\üó¡¯B°[‡¨H"˜‰ä @ͱ¹Y_ëƒäà#l~ ¢ÈuÔIBÚØLA8©¯'c㊑äBYÒêcÑŠò¾Þݨ Aù@} É  Å_Ò‹žºCë NÄâA$W#9ÙwcîþÜËW,/&Âô~kׯ‹› |¢C¤šJ9%pA}Ägùv‰o;¸¨èŒHë)(Ì»vîú¢wïÞ¥”7Ñk¥ÁP÷8»æ+Q3F.D¹ÊbÑ É©eqpJ´Zà³8lG×X2Á«À½¦aó߀gvE¹-|j)°5r¹…ò£¶%ÔÍg’ôsEm+ŽÅkz¶M[Ê–Žš3 ÉÑPSˆmÛ_åäLyè…ç_h"DõŒÕE‹‘PXDux0ø­!‰ïæüüy+V®8fµÙ¶Í¦›\y¹žãÇÿrèСÏ4kÖìt!„Õe¬N©Ùˆ¿¡~²ñ@7 òþPÉ»†!ð`ó~Ùy›ý(¿ë£úŒ…šuâs€;GÞç é Žà*m;û—û¨8“§¹>¯ž=6"9Éq܉àÿ 7 ‰Í(ÿöúêk¼®-Ü`±‹‹=ùž“'çôºüò˪¥º?ô# *DÇ4(ñ-,(üâý÷?¸yì£cÓB—.ϬY³ˆOØ lÈÉÉñ¢bW¿9rd;)åÕÕ°~G˜èC,âó>p&’”OÊò5‚kPñˆ7#¢ìDMõ,Áæ%ÔŒ§EòJtÏÑÇýQVn¨¬MáÒ•à ÔÀœ2&-2‘LR®=*5ÝLÔ\ïóËÚ,d<*v°ŸÞº8¸ïþ¬›o¹©`Ó¦ðIߟ4‰_§OgTi¥a„>ñ/aP=öùþ¼cÇŽ=_~ùeµo|xôh'ÏÔ§ÿꫯn?~üS{öìé,¥< õ}47°ÁPgŠîï"øøËq~6’WQyA× ŽÔ>\›M8ºÛHîFð‚•æ b³Ø "¹Á8TZ»÷\ƒŠPÈÙ±H% ®ö+åÝ]ebºÉ]¨9ìk´^Rn"’s,Fð#’Ÿ‘\ŠŠ~ð"¸Áø²Í"púîÌ‚üÂÑ'|²gÕªÐñæ“Þ{ÛGŒ`jž'œ¡¾(_ób Cë¡øÚû÷íϺþ†<;vì]Z3~üx{áÂ…ÛKJJªŒ:ÈÉÉñN˜0áÛìììË,Ëê „¸ŸìR ‘b©a”¨³PN]ÑÀExËw8'8$SÍ•52›&7õŒyøáÒ­[·T˜Þº`Á|yéyçÊ®ÉMåo‘'ŽYRÅ7%%åÑî=ºå­_¿.ä4àììñÞä”ä½ô(ÂeèСq#FŒ8ÓívOv»Ý¥µ4 Ø`0Äú¥¥Îl‘”(èØAžR9 __Ù¾ÅA²[jŠ|:.NæÃZ %5ÌÚUïÄ×årÝžœ’\0vìXù×_;*d`ûå—Ùòô§{SÓR×¥Ô#GŽìèv»9rä±Ñ¨Ï`0Ä .)¹äç ¿¹¦¼`æHè(a|#ß‹“S’ebR¢ìÔ¹“<éä“dŸ£ûÈfÍ›ÉÔ´TiÅY0«ë †ê «±\O#ßú¿r„ÁPÌjÀë…JØ È&ò¬H¾h‡ú:àf04 ŒèÆF| †ÆÝȯÁа1¢£ñ5&Ftc#¾CÈn=Áˆ¯ÁÐ00¢[Ï0âk0ÔoŒèÖSŒø õ#ºõ#¾CýˆnÁˆ¯ÁP?0¢ÛÀ0âk0Ä6Ft(F| †ØÄˆnLjoÐô@7ÀP0¢ÛHpˆowŒøFƒfXüÁnÛìÁâüËÃw@°Xï§à_ûà0VC©ˆ`5á%â¿K·É·­ÃâuÊ'èÆAÖêýáX[&©m@Ý»¬ÄâI"Xx´é $èFT†ÝF†€uF|kŒ…à3$í‘…$ÉHzcñª.³Ƀzÿ(¯•ÝZƒÑŒð>ã$ß é¢·HzañÏ÷ ü¢î"¸õnÍ‘tsÔ’«kÂ~“ÚF0èp ›QFt)F|kÄ ’Ëßj¤"Žä”•‡Z¥¸)W‡×9êèƒÅ X<‡_ ŽNq”98ÕqÜ ‹§±x8¼Šö{ô¶ÉëHŽÓ×®Ú8Ê^´ ë­ýìql Lâ·ãú¹XŒÓp'ÇùVX<ŠÅ‹úÝnD%¬ïœá(×8Óq|/cñå—pê§?Ãlà|}î #€–Õ|¯:Áˆn#LjoXœ«o- ¸²Éy¨•£›!¸ðb³EŸÛ¬Ë¥cq?6_ÉÞÖçOÃ*[%Ûw<°ìHð6˰Ù`&Ð9ŒÖºœ`®ã ½£Î›ËWŸ–HNÀæ}|#‚'°ù‚ŸQ"Ÿ„`6†Í/!xeU÷ÇâBG':ޝEð 6Ó±Ù‡`–no;_bó6? øj×m@±^0·¸ïUkÄèbë€, O÷7Ù÷Ã'¾¿I L £×Ò@ÒÉ2Ç™Áv\¿ Ø®Š€€s€oQÖWlnvaóGØ«eHFŸ h‡àFlþ¤ÜÅNÒG­€UØ ÿC ØY¶§~\^Cò¥>ó’ @>‚ÎH®B­.¾›Ûu—:|ÞU=ëH®@­ú ‚H®fyÀÇÀ.$¿£V%ß ä_¡V9ŒèÖÀµ˜åj¶ÄDšx½t,,âœÒRŽ%¶»u)¾.— W¢ o©—ÂÂB¼^oM›_7v!èˆ]vf’,}í5 ¿èc—ÞJªñä…e{6s°8«’rÓÜ­÷sQ‚=$Ý ìí;Ó€ñ(¡í€à-|ÿ×’6XìG­>×ñ øÐ–hÐÁëeÏ“´Ââslf!øX ,Dò1”ùÓc#ºÑ%>11ñÞ#,ñpÿ£ûpÆà‹è×®-%%%¬øýwÜ9“aç.îËÍku ŠÚß}ðd›ÄÄÒsÌ1 4ˆ6mÚPTTÄ’ß~còäÉìÛ·ïȼܼ“Ÿ¢õ.×\sMòÛo¿í‰V}Ø|‹à à Ø©·4 cX5„ƒUaÀí `‹Þ?˜ÊÄT‹,‹DE$ƒz{ð¿ÃàTÄÀB I”; )¸«œ#%ªUµ©ÈGr ʪHB"xlnnNAð’æÀ£¼¡žÒ,-=möéNõ¬\¹¢ÜòâÎíÓO?‘í9¸´iÓ¦èW ¢±€æºÔ{ÇËÿ•ÒöVø·þD*IDATllÛ+'Oþ@Ô¢¹'))éΚ¶933³ŸÛíïv»×Gá#p’€`‚©¨2P>ÆÏìE e ð=·¿ög´D°ÍQWkD™þ]× ÐÁ ,žT—Þb"ª£ÔZûhÏÒ¶‡t¹à(ëp¸>:5¨ÕÁn}> ‹ñAî>$°³f1 ¸G×?ãx‡å¨ÁÁcìBÅ‹ƒÅcº®T Á7(wE*‚ßô`X¼ÅãúZ:‚¥¨A¶Ë,¦ԞÑmøÊ ìÅ])?šé£=Ñ »è€úen¨Ä§¥§ýìÎrz½¥• ®oÛ¶m«ìÖ½kžËåºí@7¼ºDK|åGH9ùƒ â»qãùÿíÝ{|Õõ}Çñ×÷„\Î I¸hG.¤¤¶•BÕ®  X/«u[<ö°(8Ùª­$Ø‘:Q.•°¹MÖVÙ&æ¨e€í¬:\+bõuÝPT LZnBr’ü¾ûã÷ËýrrN’sIÞÏÇãÇ#9ùý~ùþ΃¼Ï÷|¿ŸïïäÍ &''ßnûŠ‹‹3‹ŠŠ wZo;úȰÆÇz uÎz5®·âö~§áãKÞ¾ù^ïFÜzØ­íÎcØâ}a†ƒv‹€…^ÆÇJ¯&ø­ÁÖÑ÷C”‡ÍÂpÃ|lÄÇ*ÜJˆ,/n„.ÆŠÝŒt![€§½¯s1¼ŽáSïÅå¾6û-Æð†J¯Æ¹ 7t/Àð.†CÞî‚–á‘1^õ®û÷@sg%à ^­p%†m¸ã×xÂŽ;Xár,³:ü俱¬‡._ñÂãc-Û¡¥V±¶`ù.ÝŸ]áQÜ¢î_à°‚Èfç³1¬õf¦;IMM]g Î`Kës—ᵩ«ëNÃnèxÝ)Þ9Ïux< 8qëÁ¨Þƒ%ZÞ^ä`8„%è^B÷ÉêXb©ëñq–B¯ÝŸt±Ï °Ì¶cxËKÀ?‡ñ{Ò½2›&o¬ª£4ºÿø®;3ÆÖE<ôÐ?5®üÉÊÍgNŸùNXÆë¾],ÁíåE¾*é©upÛmíZö£5¬^½zÃÙ3g¿×Õ!ÅÅÅ™ s­µ €?ìá캩Ρ;$ïßÿÁò­=Ñ…þÛRp|†¿ÇÓنý¸OT1PŽa!–MÀ^Ïr"pËÝÀ‡ÀÝ@ð2î[­GqßÒ|„e1°˜€[p8að{, ¡Ë²’Eøø–EX>Oס{†bù†÷ý ÷c™Üž·cX€;õ qËO>‡E@–¼nBw攩SÖ¿½}{اN"7/¯®þ\ý(:¿R'”>õ|ÇŒÿ;ö‹ ÊËË?á+Õu5u#iÓà ѫíÜ6kÏ$%%ýQXm’·mÛ¶YS§NýMZZZ¤µá!566žY³fÍá:$Ü?Ë ·`½Ð5|Ë:oŸ< ÿáÕÊíð¥8Ü‚‹±Üƒeð&†g°¼»ÊåN ?Åò-||‡jÜå“›±ü¸˜áe,Y^H/ÂòU|¬ôjþ:z0ÞxW×ò½|³=¸+„:áa,×Ç0<Ž¿Áá>à8Ë€?iW‡ÙFJZÊô™3fdöÐŽn=š±cÇ6U;,YÜ)pòóóÉÊÌ¢®¦î¢âââO{Ù«íJ–ã8»Bï&ÑtõÕWÜï8½+æˆDRRÒ ¸cÔq£y@ü—¸ƒÿÙ¸ƒÑ“q‹ŽnÁ°x8Ã?bùó–c ÏÏã–¯¤à#wLêÇØNu„‘¸˜'€'pÇp¦z??Ž»*ªÁ[­3ªW6ŠöcVÕÞù:®”úË×€½¸%,uáüÞô@zANNNÄ«¯.Î;æÙøºaHÄ:®pk µ0bÌ(*êöÇvîܹ?«¯¯?f­}’ðW$®4‡îI`+p3îºò-4³øÈÃr-†_zÛ¸«ARpZÊbÀrîr¿öâŽóµ•»V½íâ ×ûºí„XßV19œÆ˜o–…;fÝñ¼)øxÃno²,¬%‘Žãëêz37Òµºê`Ò»µ†»-üÕ` ßëFfm=pݵ0¬›No7½ÜfŸ}vÚ>|x°è¿Z[‘iýKp‡î‚XžhyÜᨷäï‘ à´Î¶ ±¯{Ë¿| Ãy+EšÅ-óÑ:«ùE ²ÿ.©E–Ùm¾¿w\¹£¿Æ-¸¾w¢ï.íí/©®ª.Û»o_=Í/Ba:päHs5ýDà`‰u'û6˜¾ÍPÇ…wêêv<=mÚ7\»vÿÖ­ƒFo/D/·©©‰ŠŠÊÔ²àþWÞyçõ………KŒ1s¬µ?.ëmŒ1M¸s 2ÄXk{»H$jÚv?^Â}kßÞ:j×&,;qWìÄÇ?ÕÞÛÿö +0| ‡5¸·‰;Mû™½À'øx‡uÀïñ·Ëûáz¦áã»8¯ÿ†ÛÛ~ Ã2,ÿÚÅ~ç° Çí _äU*|ˆ; ²·í8Ζçž{îoì±TŸ/¼¾;wîÄþ|Ç»– ªðm8×ð›gŸ}ö‡>ð@O®†eKi ß½Ü7ß|“ä”ä Üa'JKKÏâþ-m3™v+n•IOΖ––jXBâBÛ”¨Ãr—·f»íLz–™æaØkéõ:¼JÛ Ë<,1¼ˆaŽW×êà°· ÁzcbØ€/a¹·vö¨WÜì–ï±õnDÛqÛʽ¯½vßêMþ ZÎßv¿'1T`ø5>n÷nÏ—Mû[Ëê¡-œ;wîЦMφ=ò`Ér kj»ûqsø¾çݵ+žoÙГ·Oœ8q|óæÍîwðäjØ»,èñÀ’+‚5Õ5?éêgkÖ¬ÙYZZZäcŠèºÊE$î ÞÛïE×Ô#GüöÝ]»ÒÇ׫~þôÓÝ» Ã799yA =PWRRb­l÷ü455Úßýî {å´+›223Ðþ"¶`Á‚éóçϼ?Î%"ñ)7kDÖ:º¿*7/·êò+&Ÿ¹ô²K?Ë‘U›5"ëHjjê" ÅB¾…Ç,œbá;+°i4››—k'Mþº0q‚ž1ÜffeZŸÏwÝrTD"Œ®¦ÓZ‹ÜÎ ßË1ضÞ}Š‚ˆH_ ¡ðUèŠHüá«Ð‘ø3ˆÃW¡+"ñk†¯BWDâß _…®ˆ$ŽA¾ ]I< ¾ ]I\ ¾ ]I| ¾ ]< |º"2øÄqø*tEdðŠÃðUèŠÈàGá«Ð‘¡#ÂW¡+"CO ÃW¡+"CW ÂW¡+"ÅðU芈4‹Bø*tED:Àðm ]Ào3GdÚŒÌ ;,y˜BW†ëH|³Ü©}8ÕžðÓÚ5Ž1yÕUW1kæLrss©©©a_Y7nääÉ“õÁêà·×ûåDDQ_{¾Ÿ‚2<ÝΞ5é¨(o÷ñëm·_|Á޾`tÐï÷/‹õ5‹ˆÄ\$á[ëîò¥Kº Û¶ÛÑ£•6ÿóùÁäääù±¾^‘¸Nø–¤$Û›fþ™uœ¦^…®µŽýè£ý6¨ÆÆúZEDↅqJ-4t¸§ÁŽømyùǽÜæí¾å÷5dŽÈ|&Ö×("wºëù®{ã ׇ¸Ö:¶²²Â¦ùÓªa±¾>‘þâ‹udp0Pnà.àËÀã@=ÀÿúÓ¸vÆÌˆÎ™››ËÈ‘#-ð…~k¨HŒ)t¥_u ßc))NvvvÄçËÉÉirú«}"±¦Ð•Ѿ;’’¶ÖÖÖF|ž`M Øo ‰1…® ¨O‚5;ö•íkŒäئ¦&*+*Ó€û¹Y""ƒÖÔqãªÂ)kÞ^{í·v䨑e±¾‘Db2³2÷?ÿü¯ÂÝ)S§T'%%}?Ö "’h®5zT°§å¿·Gyä|fVæ^î£àED/¿ßoÞØ¼`YÙ¾»råÊóéÃÓO¢Õh""‘KNN¾=¨»wñ½öÈ‘Ãí‚öüùûê«[í¤É“š223ö À•AJ·v”hû¶?àÿ5@FFcÆŒ¡¶¶–cÇŽ2,9™êªêÃÖ±_šbÜN‘AazˆOŽø(Ö ,FaØ"t`i¬*"’辈¡,Dà¶n>JÑnDD"2Ãñ^nëö ëÆ‹ˆ$’9ê"Üæí}ÜÏi‘–cpú¸ÍÛQ`B¬/F¤?è†72p s韲Äàš~8HÌ)tED¢H¡+"E ]‘(R芈D‘BWD$Šº""Q¤Ð‰"…®ˆH)tED¢H¡+"E ]‘(R芈D‘BWD$Šº""Q¤Ð‰"…®ˆH)tED¢H¡+"E ]‘(R芈D‘BWb­1Ö ‰&…®ÄÒX.Â28ëÆˆDðX7@†¬W°Ü TåX*0¼\ãv‰ (õt%ú ¥Xfàn³·°\ ìQ«DDœá ¶ÍÖ, qÔ( Û:g;£Ðb‘Ö>tƒÀ¬^™ŠŸ+tEDÂѺǀIa ÷cpº""½á†îû@~Î2C ]‘|< ï‡3] ÜÜ特ÿ™1Ç^:Iý÷IEND®B`‚frr-7.2.1/doc/figures/git_branches.svg0000644000000000000000000005415213610377563014605 00000000000000 image/svg+xml 1.0ReleaseBranch Master(Stable) 1.1ReleaseBranch Version 1.0.a1 Version 1.1.a1 Version 1.1.a2 Version 1.1.b1 Patch Email (Patchwork) Github Pull Request Github Pull Request Patch Email (Patchwork) Patch Email (Patchwork) Github Pull Request Github Pull Request Github Pull Request Patch Email (Patchwork) Github Pull Request Github Pull Request Github Pull Request Github Pull Request Github Pull Request Github Pull Request Version 1.0.a2 Version 1.0.b1 Version 1.0.0 Version 1.1.0 Version 1.1.1 Version 1.1.2 frr-7.2.1/doc/figures/ospf_api_architecture.png0000644000000000000000000005014413610377563016501 00000000000000‰PNG  IHDR½‡2O}Ù pHYsÄÄ•+ IDATxœìÝH[é¾/þ÷þÒ Ý!sÉÀR°°r±AË’b†-\Ë8˜-8‡Zpµ´Ð^F™n¶¢Ê9j9#TδðÔ2ù‚C#“€‚ƒ n¥YÜ w‡I`vN¡ß?žt¹\‰1j4‰¾_ ƒyò¬g=Yi²>y~þáÇ """*ÁÿSé QÍ`Ü@DDD¥bÜ@DDD¥bÜ@DDD¥bÜ@DDD¥bܰ'·Ûm/ÄívU<¿Ûív:G«ìÅb1»Ý>==]$O2™t»Ý ·ÛL& f—k||üxê{HÓÓÓv»=‹Uº"DD§ã†=Åb±P(”Ÿîñx>ÿüó¥¥¥˱Ûí>,’áÍ›7Ñhô0U<‚l6 …2™Ì^’ÉäÕ«WƒÁ Õju¹\.—Ëjµz<ž«W¯¼ ƒAç+}p™L& e³ÙJW„ˆè”8Wé T5Y–ÅíP+|ýõ×]]]¥’J¥$I*’Áçó¾ŠÇfxxXQ”—/_:5q~~þÖ­[ƒƒƒº:OOO+Šâr¹†‡‡ççç{{{O¼¾DDt7˜Ãá$)•JéÒÃá°ø£¥¥¥”rÂá°Á`°X,{=  H†½$“IQ7I’êëët¬V,³ÙlÚ @ooïÌÌL~ëˆ×ë•eùîÝ»ÇçóŒt}ÔÌ{½Š"×G½%¾DDth6›M–åRžÚØØ°ÙldY`³Ù^¾|)ÒEŠxjllìDz, ˆü^¿~ÝÝÝ­-mnnNþ@ww÷ï¿ÿ.ž’e¹»»[[“ßÿ]( Èjz¾ ¢JE^ãÆÆÆ¾êõë×Ĺ\.—,Ëj… ¾"›ÍV¤Ø±±1mfí«×óñãÇêÕ×G{¸ö Øl6—Ë ”WADD¥`ܰ§½â†ííJܹÕXáõë×âÀׯ_øx«wÊþóŸ>|Poiccc⎨=Ñóçϸ\.që}þü¹È,žwe]eÔû´¨˜ <~üXUðÕí7ˆšˆHåñãÇâå¤^¾|©;©ˆ*özEOªf€‡©‘ÄÆÆÆÆÆ† ÔW166¦þûï¿‹g7•ã†=‰;–m7õ³š-ÿö?ÿùOY–Õ{§î6)âC¨'R£ü`EÜJçææ>|¼+?~üX[š(\„/º(AUèì7ˆÓiÙ‹òÅ-\«à TÎÍÍxþü¹šòøñã±±1íÐVXRhë)þÖ50hÏžß#êϸˆ¨\8Ÿ¢T©T* I’ôòåKí`É`0(†;„?RE’¤ü•*I’êêêòÓÅ »ÝÖ0›Í²,‹qˆ‡C–e¯×+òEQúúúø|>EQ>ûì3í±V«UQ”C»t8Á`ðõë×?îîîÆÇ¹$óóójžùùyµ‚ËåREÍÓÖÖàÁƒƒƒƒbPBÿÐÐPÁ+011¡½n±XluuU—§µµµ`mÅ•×=ÛÙÙyWLDDûà¸Èbtó)Ün·ÇãYXXÐ …B—/_Î?ö §Ó=OþlFuF†Ëåòx<Éd²¾¾~aaA–e1Q̨¼sçN~±E&[–B¬ÜÐßß`~~~ttttt´££C WAÉÌÌÌÌÌŒö¨™™Q1‹Å2777:::99999)˲Ýnÿæ›oöï9??ïóùÄÐKEQzE˜¢êìì>P DDT㆘ÅbG–å¡¡!5Ýf³=zô¨\gèééÑ% ñ‡ˆ¦¦¦ÄOs»Ý®Í677×ÔÔ¤;öÌÒÒÒÄÄÄÈÈHþ| ·nÝòù|ýýýÉd2æ—/Ër(ŠÅb"8èííííí ~¿uuÕãñƒÁ_~ù%¢Äøøøðð°Ïh6›ÛÚÚ²Ùl~LVÄ«W¯´u>bÌDDD:ŒæéÓ§W¯^õx<ׯ_W1§R)Ý”¿@ `4Z¸$I²,§ÓéüÒÔ¸¡¾¾Þjµú|>Ññã?Št³Ù,þÐûöí[EQZ ¡P(¿eE%ê355•¿Æ>.óðÝw߉Hkuuµ··×ápˆlÓÓÓwîÜñx<ÚØK1™¶§ô¥9ÅÕ‹ÇãÚÄün"": Žo8˜úúzÑ?88(RÄCí‚Í@àóÏ?9DáV«5 ª«ÿüóÏ———Õ§Ó©(Êèè¨,Ëê »··W–e]gAooïåË—ó—šØ—Åb±ÙlG·u2™œ™™‘e¹££€ÏçÓÖA{^õö¿¼¼|ëÖ-ííÿÂ… öŠ«tkd•Þ£FTÚ•°ý~‰‡Q)ØÞp`CCC~¿qqqzzZ ñóûýwîÜùõ×_eYÎd2ân:11!òK’$F;öôôˆQELLL\»víÆN§Óh4*Šâñxl6›öÀÞÞÞÑÑQEQÄÄKÕ·ß~{ëÖ-»Ý.®¯¯/..º\®"+Gy<žü;ëìì¬Åb™½víÚ;w¦¦¦Ô{y*•Renn®¾¾^ŒÊTç:êØívvô÷÷OMM}ýõ×â©×§à¥°Z­‹‹‹n·ûîÝ»«««b¨¸ªÅ¯›öê]½zUÔŠAQÙ1nØ“Åb1™LŸšu»ÝËËË7oÞ¬«« ƒããã¢Û€ÝnS;ï=ztïÞ=õX«ÕúÉ'Ÿìu"‹ÅòË/¿ «“ ÆÆÆòÛóNçêêêýû÷µ‰½½½.\xôè‘V)IÒÜÜÜ^K> íË‚/?‹ûý~µÅÂn·ÿøã¢Áï÷Ûl¶o¾ù¦àáß|óM,ûõ×_è^‘¸>òù|n·; ŠY*CCCN§Sìˆ!ê¬k¨Ð^O‹ÅòÓO? Š+àt:{zzîÝ»§öòÑýáÇ•®ÕŽo ""¢R1n ""¢R1n ""¢R1n ""¢R1n ""¢R1n ""¢R1n ""¢R1n(Õøø¸nÑå½ñññ·oß–ëÔe,Šˆˆè(7”Êãñˆe÷µºº:<<|¸ ¥ò9Î~ø¡,E×™.ÕÓ§OK\®øîÝ»º=-'/..¶¶¶½(""¢£cÜPªÒ〺ºº² DDDÕæìöS$“I·ÛmùÈívk÷_v:ÓÓÓj†X,æt:Ýn·šaiiÉn·‹gNg8¶Ûíbû¥ééiõïX,f·ÛÃá°ö\ãããÚšÌÏÏ«E‰ÒÔrĆXG-Àøøx~æ½§Ó™jQ±¥¥%5çøø¸Ýn×nx=88èt:¸Ýn·Û­{ÉÅÏ««¤¶Xqm÷º º:ëê)ê£}JTO}JÔY¥};t‡‹÷ÃGˆˆàÙôûï¿Ë²,ËòÀÀÀÆÆÆÀÀ€xøûï¿‹ ²,Ûl6ñ›Í¦¦ˆgŸ?Àf³ÍÍÍÍÍ͉œ666>|ø 6{ollÇŽ‰](çææDQ?àr¹^¾|)j"òøðáåË—âaww÷ØØØ?ÿùÏ>ˆ¢].×ÆÆ†zê—/_|™j=?~üòåKq¬ú*dYv¹\jfñ´)"ƒ8‹¸\cccºrò‰×(^ÔãÇÅÃçÏŸ«Å sss¢4õ¼¯_¿—K<ûøñcQ±×¯_k ÛØØ—ZûÖˆªj+£};Ô âpQ¸,ËâÚѾÎhÜ îUÚÛíË—/Å­N<·õfóawÜ *q3Û+nÐf!K‘ûœˆ´‡i+©>̯˜Ž(\{SuQKww·zj5R‹§9u±Žzµ×G577§«ä?ÿùOmÉù×VÔDm.—KÀ}øýˆEá?VŸW‰qƒø[û¾ollˆx¨à$""3ÚO m6›ÃáPS‡Íf ƒjŠ$I‡)„ÃáP(ÔÙÙ©Mìéé)r:mæúúz]M~ùå—«½°° Ú´‰N§3•Ji;Y´õt:uuujâÐÐ,Ë333â@EQDþòò2—Ë¥åõzeYîííjÿ÷ªäÌÌŒ,ËCCCjJ]]Óé …BápX¤X­Víµ½w(>ŸÀììì/¿ü¢»J*ŸÏ'Ër¿šÒßß_¤2:G–eíûÞÒÒ"I’85íë쎋´X,ù)©Tjß777±;ÐÖÖvèšd³Ùñññõõõt:J¥ŠLàŒÅbŠ¢\½zU—®(J*•*x¯µZ­{•ÖÑÑ!Ëò³gφ††|>Ÿ$Iׯ_^^^îíí]]]-rìAéŠÒÍ12™Œš2==ý믿Æb1Ý;’N§ó —$©ôš(Š¢{ëE)=ò ":ãÎnÜ`2™Žr¸ö&wƒƒƒ“““²,K’ÔÖÖfµZ£Ñèäää^ùóÛÔô‚ù/\¸°WQõõõ’$ùýþ¡¡¡h4êr¹,‹,Ë+++©Tª¯¯ï¯¼‘ë,--=xð@b±Xzzz í[·] Î^ˆˆJqvã†ÕÕU]Jñ *Ñ´°ººªmîÎ/­ÉdÒçóuwwkÛÉu“ ´L&S*•Òvxûö­¶'BGWOÎÎNLJE-(V«5 Šútttèå¨ò›m‰„ö¡®MEa¢Õabb€¶«BíÝÀÇ+ºÒ#I’t‰ˆH댎o°Z­©TJ{C ‡Ã©Tª”–y‹Åb³Ùü~¿6±Ä¥$uD¯„®Ñ^W²–‘  ,®]»–ßç ¥¥E–e]iÓÓÓŠ¢¨£1®_¿®(ÊÈȈ,ËbÀ˜Äèõz%IÚkAq===Š¢èÖäöûýê)ƒAíìG1nCœZÚSk—ËW@[x (¾4§ö ØíöP(¤šL&/_¾l·Ûú2‰ˆÎ¨J̬ 1ÙO 877'ª“ýò')œ‡©N­,>s¯ê,1ÂccCÌ,PW'_¨ó0µ³ó'1êˆÙ6›MLòË]º»»ÅCqFìž³°ïäFm%_¾|©›Ž!ÊWk¥{ â\"óëׯÅYí5¥=~üX}ã´/J×*N]ðzªï»Z7uŽ(wFã†>lll¨÷{Y–»»»Õ áÇ"E›_—òüùsuU—Ë¥<966¦Î3Óüòãµ(õΧ¥›(æ%j'.Šö宣–]ûº âÆœ%hgBjgl Ú×XzÃ7uíYDBjÝKï‹zAº»»Åц;êJ÷¬jˆÃ_¾|©­êëׯ‹ÔˆˆŠûÇŽ¥£F$“Éü†ñCÞØØ8Ü Ó¢Çä@ÇèX,–ÍfO~õëp8l0tÝ(‹E’¤`0(.~ÁZí[aí±jºSy[Åáùu#"¢âÎzÜp8‹Åjµj3:Îh4ZâÈÊ3®àm¾ª $"¢½œÝùGa·Û=ÛíÍÝëëë‹‹‹¢gˆˆècÜp³³³²,{<ñW’¤çÏŸwuuUº^µÁjµ~òÉ'Õ\ í…ýDDDTª3º~ã"""*Õ¹wïÞ­­­UºÇËh4žüDªu¯^½*eŸ³š–Íf C¥kqŒýÙÿí·ßâñx¹«sÖYó¾ˆ7oÞD"‘²WæŒkll<ôê›™m0ž/oªÊZ"ûþïÿ=â µN]Tûè‹Uœú㯘Oó=u+Ý~ÿ¾Y2Vº"ÇèП}Ë%‹Áh8wž#ÇË&¾ÿ÷ÿõï_}õÕAüºÿëåÀ²I:ÒN„¤•ÍdÍ’yÙ¿|¸ÃÏmooßlØiÝ.oµªÊEŸa{ût¾Àp8|ãÆ —Ë¥Û«Ik||Üãñ¨›8ȲlµZ'&& .y$¿ýöÛÞÞÞcªs­Ø~ÿ>ÐQž]O«“;ˆí÷çþæ8ͯñПýíííïÿö½ù¢¹ìU:³ºú½¸9pÓév–½JgÖz`}adáЇs|CÍ+¾«“XÈÒjµŠ666œNçâââµkגɤ.óüü<I’fffޱÆDDT³Ø wÊy<›Í¦]Ú²¥¥¥¡¡áÎ;SSSbÓj•ÚÚÚ&''c±×`&""Æ §_þí¿¿¿yy9Nkc±X(¸ÿ¾Ïçûî»ïfgguÎÏÏ' F£±¿¿ÿX«MDDUˆqÃé €n$³¶B }}}õõõ·{°Ûíb0©Tjjjê§Ÿ~b›Ñ™Rþ¸!ühù´Ø³ùòó¿}å äOPÇØæ¾ýöÛÑÑÑÏ?ÿ\–e»ÝÞÞÞÞÑÑQpx¹Ïç³Ùl"èëë»uëÖôô´Ú¨0>>žJ¥ž>}*fµ‰ñ˜ƒƒƒùñË û_{þ{Ïæ“êPŸ7Õ©øG¬ŒÄ‰ „e)Ém¤ÞøØæ+XHøŠ׊e°üwdÞ€Ù€Ž . Œ2ß“W1€×× M\^,| l€³m;)?lbøWŒ}†!®¼p½½½mmmƒƒƒÑhÔãñx<Y–%IÔn¨1??¯(Êýû÷Õ£FGG½^¯7èF_¶´´üøãFãižÂW^×üP²èn€¯£À³î B…–Š xteW”pãgˆõOE?R?­²aÏsÝXÎU{£;WCñ±-H6àÛÏÐû±}J”o“Üo_ç2ãú¢t_DtbÊ7øâ P²øn³öÂyd\»R”,‚‰\ÀÁ³X,¢U ûý~¿ß …B¡ÐÜÜœ:ÙÒçóɲ|óæMõ(±ç§ÚÁát:=Ï7¬VkGG‡Óé<Ü.gÓô&”,d¢i$·÷ü­<`…n!¡nüŒ_œû…­d1۹߫’ÛHe ÒÝ€ÖÝ“í×ÓXŒãÖ >9‡®‹8»} ¡ºpÏ ã9XMÁÇdé÷{~ÉÑñ)gÜHBÉbì3x¶Lì™M2hBÿ7~†/Žû-l<.------CCC@à믿qC2™ŒF£Š¢\¾|YwÈ‚ˆºººž?>11F§¦¦$Izôèâ,…7žûµ}kžW{6¡õÈúÞ‡¡¸ƒðlax½2÷HÙ¾x¸ÁËePò¢‡VS×8½‰;k˜ˆ n$sAƒ¶‘¦åSܼ„ËÏàÙÂ7M%usQ•sý†GQ¸~®F(YLoàØ–O! dsÝ¥Tn·Ûb±ä¯Óàp8œN§¢(bI±*ÔÀÀ€k7Y–µ£#»ºº‚Áà/¿ü277g·ÛC¡Ð7NôõÔ¦X¡¬&ôZ àÙ:Øáws…T„d€d@4]à)o<÷l‰ú› öl¢(h5@ßt îœ XþûJ#¢²([{CrÑ4l,F¸.Á³oýMå*ž£½½]ŒiÈ_M2‹É²,Z Ä ÝZÂðððøøøÐÐÐøøx&“™˜˜¨¯¯ïííííí5™L“““áp˜MÅ}· ΰ›áÙ*Üì_µúqkE_gÑIqß oüO-–ù^/µL´±O“¨2ÊÖÞà‹AÉ¢§êÏÃjB(…€þ‡îžÞ¾G* Ù©®\5"ôööÚl6qï×¶:Œ/..Úív@@Qñ·Žhrðx<Eñù|ê&Òé´byü¯£¶ ¹›îM|ñîO¨dk¼h&ÑÕy* à`? D?æAOm“°‡Å‹ÁÕ=gcÑI*[{ƒ7ÙçÇ_$Î,Ʊ ÀQhCÝç3ƒ™­Ühsn8Ѩ Ks&=ztïÞ½ááamEQl6›XÖiaa€vD¤ª¾¾Þjµ...±±±«W¯Þ¸qC]¿Atmp‹¬âæcP²;õÅFGnæuFx•ÜxapTŠÕ¤ïªðÅaÝ{›¡Ì¶þ3¾šÂT€~Lô¾fí\Çb“QLFsLzà´ð»‚¨2Ê7¨Ã—ÔOr¯£¿Jži“ OQ£"dYÛë)---Á`p~~~ee%‹°X,?þø£:B”°×äˆ{÷îµ¶¶f³ÙúúúX,6==íõz8ÎÎÎNN©Ø—ø™þæw¹«ÿb*\ ™ýÖJĀʂñ÷‰?Ô®ŠXJß~¶g~qÏçj<ð´j‹¾Ä2˜y…Õ4RY„R¥0Å}+{B‰* ÅT¡b™šY¶Híª=/'ñH„R˜Þ,0ŽA,hQúêDT.G‡)¦qˆ‘ëÒÎèq¢3E úéÛcú€Ý |\Ú¡Vˆ® _N40ü±¤Â‹I,—üOk¯%7­TÛ¸¨«yé[Q~‘gÕ“^0`AA0±3šAlsÅÞO¢Š8êmyߺ.îôïZŒ˜IÑßt¢“³@&“q:'wJªp8üêÕ+§Óù§?ýé˜N¡ýg¿ígç@{VÌ2'?­ù§ÖÕ¼ôméßbU‰rîkU‹@}}ý矾´´TéêÐI‡Ãÿú¯ÿúßþÛûꫯþö·¿½{÷®Ò5""ªg´ <{öÌçó¥R©J×…*c{{Ûëõz½ÞóçÏ;Î/¾øâX[ ˆˆN‡³70\ | ˆˆJw&â†÷ï199¹o¸à÷û¹áÂY°×?]qµ""ª g"n8wKKK>Ÿ/“ÉÛvÐÜÜ<88xÂu£“ç÷û·¶¶öz¶»»»««ËétмˆˆHëLÄ ‚Óét:³³³>Ÿ¯`Q__Ïö†³à·ß~ËOTÃ…O>ùää«DDT+ÎPÜ Ú7€ ³ƒáќŸA¥ ŒÆÙ*€ŽŒáÑáœé¸A%ˆJׂNˆÛív»Ý•®QM*gÜà"–Á¬=·ô¬;ˆ7ÛDz¶]rõçË_,Q,wpŸ<cn•Iç2Ò{ïx§f#¢ªRθ!–A(…ìíÏxþDî_{› ß¶¢Nós.ã“ó¸Ûˆ{kHea7ãÍ6PhÏIçò®ôâŸ#ñ1™µcæ|qHŒ|Æý$‰¨l¶æà*>‘»£KDÓøüæc…3Ç2¹oU!ü\õÁ³’©,î¬Á®Ù2û_¥0ó Ÿ¿@z;—g2º+Q5˜áËŸw>©,n­èÇÚ—0ü+€\·Úd—Ÿ!¦Y($šF,“ ¼Ù†Å€Å¸þHb1ËǾ¹}?G™÷¥ðÝ&&£JÁÌ­6ˆ¨|ÐÞHb2 ›„§¹é Ém\õaô׿´òÝø^~±óÓÇ„g ƒ«;- &£˜kÏøö=®ùJ!„£ýMèo‚Å ÉÀq‘TI£¿ê;æíK&vfú8—Jáñ•–€ùX.¶ÐþÓ ¥`“ð‹õçËÀp¾8|ñ]¨GQè»”{Xâçȳ•;{øÕÞc8.œ~·iWó U‰|.´î̬?W#ü‰ý'FÎÇ d1°»ScÖŽ`¾ø®ï»î†/ͺsè4#”ÂjŠ ­TÕ]°OGÓ°I»ºz-XIÁ³…Xf×üÑ•ÜQ"Q4ãi?P¢(ñléŸ#õì-Ÿ–ýµ–“’͵Êäë4W{å‰Î¦Ä ± dº.îJjÁPËþÇ®¤ÀjBø»Ò%B)„ÿ±óÑjÚ•¡Ó¼ç× Q¥XMXŒÃâ…³ pÔïºÃ-ÿJv³þ_»é<ûmç##ô·Æ¾FÜZ/–»ë‹@á¾5÷l韣Ns9^ç~tÕ?9p lÀÓ?~ŠAQu:À§ü(‹1ˆnÝ[+‡/¨zø:à\F4É'4P+ IDAT(&£ »Æ'²àÙÊB8^ F…7ž‹f¶ vÚ-ªístãçÜ8eaì³’~Eè0> ª-'Ú8׎¦B]­üâ šãë@r¾–Sˆ¦±Çb|×°ƒ+zäÊûmˆa7ç†JHea5é3TÏçÈjÚµ˜Š‘È΀|ÐÅàmÝP†@ nÊûŒ?h3!”ò¾Út}½D5!¹ Ã9ÔŸÏÕ0¸ŠÉ(8êÑ&@ú½þ_ûÛ÷Ⱦ߿%ÿ¦ ϦÂh0@ÉâÇö§ªíst«ÈQ•;À<ÌN3”,<¯v%.(%5Æv6€/¾+QL—°xs?­ˆj‚˜ ÙØ•(šÄ ŽzÈxû~WžÞ®úöœ·¬‡¯¦áC6ìŠÈù9"¢Š;@Ü0ÔÙ€á_1½™K™Þ„g 6iÿÉŽzt7`1w0÷íËàšJΆoR•Êb>†@ò`G•E˧ XŒïL Ë0pì˜p5BÉâš?÷¯4¹ w‹qH†’&-»J!”‚³aWzy?GUb<¼çºÀ‹ˆªÁÁ:$Ÿþ÷Öpg SQP²ÅVž×CÉ<[&rÇʸwM+…³“QÜZ9À©‰ÊëÑÜ[Ãð¯¹Æ6ñyì³ÙFC-ÈlÃÇç/rû6èÃ⺔+ù~Þ0Ãr}ŽªD‘y˜àTL¢ªt°¸¡åS»Hb5m’~yö¿v>çùûZù:Ë`ùïȼ‡ñœ–]¿Z>ÅF·~ÔX~âDzdø'4ÓŒ(Ÿîƒ`6 ã‚þçþDî·`ùï¹é×/ê‡ ì5ÿ@ýùܳ›ŠŽî6¡Ó¼ÿèË“!>¿†?~¶ÈP'¢js˜ÐŽúº¯Å‚µ,Æb¸ ~Mä'¶|Ê/ª¼½>ªúóÅz%Šÿ.þl‘ÏQݹêút©LUÕ“ˆJt°ý)ˆˆˆè,cÜ@DDD¥bÜ@DDD¥bÜ@DDD¥bÜ@DDD¥bÜ@DDD¥bÜ@DDD¥:—Éd–R•®ÈqJeް8a×+]ƒãNcûýûÓýÓ™ìû÷\­š¨œÎ?¾A†½µÒ9N/÷÷¥Ãœê€ó¼çÞŸò×ø8wŽ¢r:wþüy{+FnWº"ÇiÁ_›þP¥*]:š‹1Tº D§ Ç7Q©7Q©7Q©7Q©8Ò˜¨%3ð¬ÂÍ=´˜p³ ŽÆŠÖ‰ˆˆqQ÷Â’†lÊ¥„xÖÐÝŒéÔ+Z9":Û7U·ž5È&̹Ð{%—ØÂ£#ˆ&ðËC"ªŽo ª"ókð¬Á&cãÛ €£¾Ûp]’F¿·rõ#¢3í DUd&#]¨+´VÙ¬ A‹¶rcÞnã‡î:݆gëq´6ÀÕV A"–³0”È®·À"éó¶°úÖã踄þöÜûŽ\eÆý0Թ낉ókH¼ÉU¦à‰ˆ¨v1n ª±B dS±ñÎfLþ ÿf.’Âð"ŒÂÔ2HFxBð„ðm×®æ ç,FvFKxÖ0¼ˆ±î]÷{µD2bjS˰Ëð¬¡óZÀòB„üDûdîUˆ&0¼ˆ?câúá. UÆ DÕb5Rѱ =­˜ü9—S%îñ³.ˆ¥píÜòà‚1[Œû±ëJ.€À¾^€'´s¿Ÿ^Éõütuçñv×~€gíÀ/A jD’ÌàÆ &Fçèo?piDT…8¾¨Z$Þ@§uÿœ©Ì®‡’q'&°Høñ&< äRÖãM;8!¡¤ŽçR¦–!›ð´/×%Qw?ÝÝiŸ(Q` !®+;áH½1WŽh!¢S€í D5Oj8!›Mäúòv­‹¥v=Lf@2îQwV3”ôªáßgˮĺóŒ)ˆ¥8Ðè4`Ü@T-ÌŸÀz|ÿœº¾ŒÎK2hÛ$´«H¥2úh@¤ä÷´6`1RB½?ZÀƒgxðlWº8]vûEQÕbÜ@T-Úì´ä]ßÉY¢ù5Œ.åV‘’ŒpÙÐv—R]ÓbœÍ0Ú¼ZfcÑ©À¸¨ZX$Ød„,EÐÕ\8/M»Wã¹)*mcÃè¼¼¿{šÆÒΟ’±ðP†L¶Ôš B :›¸6ÑiÆq‘DUd°<Û±¨åö@I£»Yc^~µëa` JV3„ã¹>Ý!ÚÀ¢Þ˜ë×HînéÛ¯“"–ÚÕåÑ~ VõÙì“°Oê '¢Ÿ¨Št5ç…¼1ƒyÍ4ÈÀœOrë+L÷èZŒ`z%÷w2ƒ‡KpÏ|lKHeðV3¼@ÄZ}öÜIÕlùyÄ0I·gçDî…]z¯À&ó¶SQNHÉÀµ±‰N öSU—Y>»ˆ©eÜòäºðqhá^ûZÙdÜñbjyg8äœ+×ÀPoÌ-uy47ò1•d„ë ÿ*RiH&´5áÛ>Ôi6Âq?€Ù‘lVî®\;G,÷Ri( ØÝ0áû¾´×JDTe6Û·.ßÊO7Ëæf{óÈìHþS¥ÀÂÄB$Ñå—­ò÷»¿ ½?xŸ ?)x^³lîû¶¯Kí`¦B†o '”ÂÍå_@qµoÝîê+~xÁ7«†”7x— ½Ps‚Päåço°»ŠÀÖœ$1¹_?=Ú ,b‰\6XH&,®`qEßEBDt:˜es—kç”Íd¾À ϋ웬îî258µ0¹`–Í_¸¾èºÙuÞx@|3î›ñ­,®¸íîÙମðöîöK»ؽZµ²¸ò—[1|bpð[µ(Ý[#¨Pº µlißûpñæîõfÕ„’â†TÚÊѲâ|€PÐÿq}1BÂ=íÅÆßrí"Ïĺ°4 8 »jóšíb’LêTáþÄý/-_®,®ÄcqµI<°AÃØÓ1m[wSKSWo×C÷Þ3ã3º¢.µ^Ò¥ðN{'ïL.L,0nØWþÕ03>ódø‰÷‘·xÜPððû÷Ývw$Yš_ªÅ&ŸRçaÊf½;,™FT­y'hÐÛ×Eôs5gît^ôvA6çÂ"*¯q?,Ã÷àçX† oÚIåâp:,?[VS&Üÿþ~Áò{c÷̲yɳ”ÿT¾žþ³lNó[õ°®t^ D•ÃÞÖÙ •H•³N'åó0“iä »9˜åU( Ø›¡,búž-C ËòÃÉĸè¸(üp¥³>„êMú >ÞŸÄcñH(b–Í{µ˜êM~zÄtÜJŠ$B¤’GDhåyÏ‹#•CDtºy§½/‰Z±};“ŒéÚgwMR™e³IÚùM§ÒfÙ E8ì;[OpК\˜\X˜\È?ð ×Gü‘Ž: DTÙlkî¹ßÓ£©·‡„’ØkžíØÓ±2Vò$•7XÐݎŸ"?ºr@I`ìöþå8Z!›ŒàmvW”ÐûQe×bÕDä"¤Àu²„õ8#΀ˆL¾†ÑvqçölŸDHMF§Öã˜ü«ñ]kQ‹<ÝÍ;…è»P|@C8Ž3àl†Ñ+öó)<¿®C¶ãž-&ɤkZȾÍ>ì}¸²¸òÐý°øZÙ*gÒñ·ø¬Ë  JšÚš FCÇõŽ„(…Y6?ÿ¸£A<qDB‘¦¶¦R‚¡Ù¶ó0šŒ—Z/If©F[„’×™Áå¯àyX=h³ÀjÞåÜœÉÃVW†ŸàÚ=ŒÜ†£É4†aq¶æ JƒSè´^§’¨Ö…̹ЛË0#èn†ïcŒn½€[¬þ–‹¦Wrq†v_ ·ž5ŒûsÍùyzZs·ü£¸÷ J/ïïD0¢ØÏ7’¡Î02?òÕå¯"Á\d'úÂE¿¸–¶AÂmwçÇ ]®.¶+”Eƒ¥a68ë¶»&ޤߔÒIa–Í5ºHC¥Îì3`ãoènG*;“¸| —oáÎ$Ri Ü<À: C}¸‰TŸÿ,_⪞°5x%†Á›0¹€¯Gv Q­M;AÉ÷4£âš$ðGs½ëMsî*dÖÙ´Ós‘Ÿ§¥¡@WÅÄR¹ m¯„(VI#°u¤ÂÏ2±\qBIÄcq]½]fÙ¬D•4÷®¨‰§fÙüÂó«îû|Æ`f¾ï‘Lcy573Â,¡£M?¬Ald¥rÌß×jâ>îßÜ)äz‡~ÊåìÃûgê»xýÿby—äÒ_Q-‘ j4uj±’1÷Ô^y:›0ùóak ,Œ t+@ÄÒvšCè D|`–Íj‡B³½ù…çÅxÿxí.Q| ˜êM=÷{&ïLz§¼ÎŽümAN½Ä B½iŸÝFV÷µ*^HÁN·üDKC–¢":M †”4ÂñÜÆüŸJ®ã+{ZqÇ«Ï£Ž¬TË )ð¿Úi¢Ç‹µR´4@6!šØé .!¤àÂ}Žo(F Ñ?èQ­ŽÖ}·SÐÓ_Ò´—â}}C}Ú‰*ùWû´va0n :%†:á³7Û`<ð¬A6áß?~›õ·Ã»žËs×Íf‚ú˜ í" /"“EO+¼ëðE ‹­ßðýu|ù7fಡóR®X1á“AÑ)ø¨J™ ú‡ùà eÓ®lÁÜZ"2Mp]Á¿÷ìê)ÐåénÆH¾Ö,'èhÄœ £K˜ü“?çV¥Äîþ]eºšñü6&–á ax1wê?Xª’ˆjã¢jË[‚Ö—·$kKCl¢Bìp½×šÐ³.$3He`8ŸÛKBWNïô^Ñ¢ö˜_™®ft5çŠ-rj"ªuŒˆN¡}oÛõÆ}v½*¥ÃKD5­Ôõ"‰ˆˆˆ7Q©Ê7Äâ°| Ë—˜_Ú?3Õ¢²Å ßÍBI@IÀ(W‘DDDT]Ê6.2­&#¢ ’iýfWDDDt ”§½a~ JmMp: $0ühŸü±øþe†7ËQ3"""*ŸòÄ ¢o¢Ï‰Þ.Èf#òX¾Äø ¦½°|‰ÿþ/°| çƒ]Dx–/XÇà,_âò-X¾„ûaY*HDDDeP†~ŠdQ¶æÜ®Ööfx^`~©ÀNÙþU„"¸‰ž?Ãû3|\»‡Ÿíl‡­$ðð RiÜïÁ% Kð¼@,ƒ/ßNtTçÎáÿVéJÐQeß¿_é:*eˆ<>( Üÿ¸þ7nx^`ÆW nEðxbã–&Ø›ñåÿÄà#h÷þH¥ñt -Màh…éL.`|šÝCˆNÂû÷ø°ñ•®ÉÅ/ÿrîW·#*§2ôSx– ›¡nfi€­¡HA Úlº°5çÆQªì͹ A˜¸Ù çvU£Æ u( H&„7wþ³˜໼Î{Þ.ómMPH%wRÚóö†•85ƒˆˆ¨:µoa B\¾¥*t¤|AŸb4èSšŽX#""":.G‚Èf¸ö9íÝÕ1‘Éê³HÙ>bˆˆˆè¸)n˜öBIÀõEA‹f ¡¼Ë»â†Õ¼%V7!›w hð‡àØÝU‘J³«‚ˆˆ¨*i|ƒwnæ56¹…BÖwuëE`•wè à­¦b|JmG©&•Çáã†X¡d³¾y@åt@¨¾Åø ›ŸÁף͘¸§?ðÚ=Ì/!¼ ÷C ?­™“0‰ˆˆªÂáû)¾›-<²AÕç„/€`dg» [3ÚšàYÂðènÇĽEŸ§±¿ãÖ_@6ÃõfG]G"""*§ÃÇ ûÞÎ- ˆ=×'NÜÇý›H%a¨ÓG ‚Ñß÷ˆÅ‘} ©žûcU‘ ¬¤VoÚ?(RQe•g_+""": 7Q©N®Ÿâé uÅ2´4acòÅ“ªÐÉÅ ÚÅŽ’‡ˆˆˆ*…;ÌÕ˜d:ãñ­úW£â¡ÅlºÙÕæhmÜ+ÿ´weyýU:“™Û[/õv]Ñfx›ÝþÁ(xìõŽKƒ¤+-“}·×¹îö8ê çK-DTs7Õ’ñ¿g)¤$Ò²97+)QÁ—HDU„í D5,™ÎD•„­YÖvLÜ¿ÞÝÞ,þö-G”DÚÕeSïýfG\²ÙŒ(ÉtFMT iij0¿´¦$ÒNGsþ¾@äX_U3¶7Õ°­ßRâî®K÷}[üá]^Ðyå’.ƒ½Yö¼XK%3õ¦ÜÖ-mVm†•õW¬ò…ðf\›.™Œ¡ˆÞŒ‹ð‚ˆÎÆ D5l5ò£ÁP$l6åßãå ÿÚ«½nÿ±DÀ­¿xÊQM":=7Õ0£áO¥dK¦wÚ´ÌÒ'Åœû««©AÊOgcљŸ¨†µY(OéÒÝ=òi¨¯Ób6…"ÊÖo)]ܰþ* `L+¹©!Q"Äâ)ËÞGÑ©Çq‘D5¬¥©AŒp|›ÝV“éL0¢øW£Ú[/XXZÕ%FSì¿PuÚšøamâÛìöµ{?X¾Ö¨$¢3…qQmsuÙ”DúÚ½bñ€Xö>|(cUˆ¨š½{÷îÚµkâïT*•ÉdÅÃñññÃ}­ÓÉûꫯ’É$€L&Ç›››EúÍ›7ÝnwE«væŒ//çF3¬¬¬´··‹¿[ZZ¾ÿþûŠUë81n :£fggC¡Ðììl¥+B‡FFFÓÜ›^Cþð‡3qKå¸H"""*ã"""*ã"""*ã"""*ã"""*ã"""*ã"""*ã"""*ã"""*ã"""*ÕiÛÆ†ˆŠx÷îÝÚÚšø{kk+™LªK766Ö××W®jt껇3™Œú°¡¡áâÅ‹•«×Y$Þõ¡ú^Æ––– Uêx‰Å´‰HU__ŸJ¥òÓÿ÷ÿþß—.]:ùúÐ!8Ž•••üôÿüÏÿüꫯN¼:gÚÇGGGóÓoß¾===}òõ9ì§ :[œNg~bcc#ƒ†rýúõüÄóçÏ|séXõôôL/øŒˆÎ–‚_g§ø;îT*8Î?ýéO'_™3îÒ¥Kêfô*I’GEês7-‡C’$]â^¿™¨:Õ××···ë¿øâ‹JÔ… „ݧ»á‡ã"‰ óù|áp¸Òµ8f³Y;ÄÁd2y½Þ Ö‡áüùóÚ‡ç΋F£>¬T}βt:­KÉf³Uþ^8ÎCÛdÜ@TØ¿üË¿|Ñ÷m¥kq,ÎI—€_Õ‡Fsc¸À@Iªjï?½üêC“¹q3ÍïóJ1Œ¦l&=œ;oxs¾áM¦ÒÉÄràÁj0p¸ÃùïŒhO]·G*]…ã -g3¹/¶›”8(²öÄ7ÃJdEüÝÕ÷ÍgœIQI/fr³*®ü›UþÕ±µX]8| 9¾è,jnÏõ¿J jTkg®[ýܹóÍŽÓÜ¡^ýZ;{4ŸòQÆŒˆÎ"õ«í³ŽSþwŠ©±Bs»óÜyΤ¨$©á’ÔÐÀ`”[OíL qÑYÔØê0%ìþDµÅhª—›Û´88“¢òD®¶äbŒˆÎ¨æv';)j]kçuvRT ‚ŸúN p\$Ñ™ÕÚyÝø©©Òµ #iv8cë!vRT©á’ÜÜ~ê;)À¸èÌjlu˜ê*] :£©Þyo¬Òµ ×Èl¥«pØOAtv™ÌÜ;±æñM¬gä½`Ü@DDD¥b?Ñ=yàL(Ñ"ÆžÇJ,jøKËòUã"¢;oøÄhÒo “N¥ŠÉ,Ÿ|•ˆNã"¢+8.“NNº¯èè¹â5":!ß@DT3ƒ7Ò åÊ®öžþüg3éä¾%:O*În:!lo "*ÏC· ÉͶü¦ÏC· ª;zîçñÍð³©{™t €Ñ$uÜlvt©Ïi1ËÖ‡sifTd˜ X[šúf´#ÍçõûÇó‰Æ DDG·â^{á1™å»~Ò=5é¶+‘Е/\Ò@tÕï¼óT\{wO'”™áfÙjmëZò<ùŸ_ºþ:w¥«WÍ“P¢b$¦Ñ$™Ìþ™ñÅ'Ãr³ÍÖåy¢«þŸ&0t ãøˆèH¶ÖÞÉ;&³Ü7öô¼¡Nû”h„è¾=ÖÙ7$R:û†&ÝöŸ&íÎ>©Á¢æ”›íjCÅ¥+3Ã7–fFµqƒèy¶³o„–r‘ŠzÒ6§kÒ}5ð1n ãÃñ DD‡—I'F¿ÐÕ÷mCS‹îY%4™e5hºnúfÔ“YÖ.ûØÐÔÒìp¦ÊÖz@{`§ûñ‡úÆžvõ}«TŒ¦ú²¼(¢"ØÞ@DtxêXHmÛ€ŠÇİÿ̸6=›Íˆo®ju·ü ²Ào‘Uu¿“YÖ¶Ohhj‘J|3œŠo¾I%¢«~Î¥ãƸˆèŠŒ…°ý6 PBKÝS&³l0îl*–¿„ÔдïÙSñ˜ïÑ`B‰¦Š(Ó,[4ÐqcÜ@DtEÆBjÉÍ61÷á@RñÍ}óüpïšhê¸ùíÆOÍ¢5B¬>It|7X‘±ª†¦“YS+µ2éddÙר֡ö;äçù»p±¹m¯ ¬x§µ#%‰N ÇELñ±ZfÙšN(+Þim¢ïѰwòNxù™š’N(Ú1™t2ð™Ì²:¸!ß»l€á“6Ñ?3.ú,ˆŽÛˆˆfÕçÃ¾í´­›g¥KÏÐtB‰z'ïüöêWÇõ»ÛÛ™Õ¥…µ¹Ù¦da2Ë‹O†³ÙLëŸ{RñÍ¥™ÑtBqýu®HÚœ®Ð’'ð]­b0Dà٢ߤ¼/–H‡qщÛs~ÿ‚J Š4šêï>úÉ÷hP‰×^xÄÍíÝ·¿÷i3›ekWß·K3£bÕ&“Y¾ý¿žk׋Ìg4Õ‹C<¹…ƒ"ïÿÇËÍ?ðm­Š´UÅ>|øPé:U£?üáÿ±ÁO•M|3  x¿F)y b¬—¸x•hk=°º0² 쟵¶7„R¢E ‡>„è(8.’ˆˆˆJŸˆˆˆJŸˆˆˆJŸˆˆˆJŸˆˆˆJŸˆˆˆJŸˆˆˆJŸˆˆˆJŸˆˆˆJŸˆˆˆJÅu¦‰öôÃ×܈ˆN›l6#™Œ‡>œqQa™L¦Òµ 3'›Í®¯¯;ŒYé544úXî‡IDTEfggŸ={æ÷û+]¢Â8¾ˆ¨Š<{öleeåÍ›7•®QaŒˆˆªÅ›7oVVV¶··}>_¥ëBTã"¢jáóù¶··<{ö¬Òu!*ŒqQµPÃvUPÕbÜ@DTD'…ø›]Tµ7Uµ“B`WU'Æ DDUA(°«‚ªã"¢ÊÓvR쪠êĸˆ¨òt»*¨ 1n "ª¼‚!»*¨ q "¢ {÷î€ööv©T*“É466Ч"‘÷ª ªÂý)ˆˆªÈììl(š­tEˆ c?•Šq•Šq•Šq•Šó)¨6LOOœ¨ÖÒÒòý÷ß‹¿Ãáðƒ˜‡yj:Õj­ªú0óhóÜ»wó)¨6$ÿÿöî´‰÷ø ßÁ¡C†:'´ƒ`!‚.ÝtÈС…Œ] µ ¸2³¸5С:Üà"$ƒP¨Å:q8H¡B3(´`†‚ÝüÇ/ä—T½þ3ùÊë5HóøÜåÉ”7ÏçsO¾|ÙÛÛÏårÓÓÓéßß¾}ûôé“9æü§ç4›ÍôyŠY9æôιwïžÜ0B<‡ÉˆÓßd%7YÉ @Vž§²ïß¿øð!ý{ooïË—/[[[éË;wîܼysxKƒ~ú"†orr²ÝnŽ¿ÿ¾P(üñåÀO©S ßÜÜÜààÄÄ„ÐÀ¨‘†ïñãǃƒ§† .¹`ø …ÂÄÄDßà©a†Kn }» ŠŒ&¹`$ôí.(R0šä€‘ÐWªP¤`4É £¢»Ç HÁÈ’FEwA‘‚‘%7ŒŠn©B‘‚‘%7Œ¹¹9E F™ß§!vq~ŸNw÷îÝ8އ½ €Ë·´´´¶¶v¾kí7Àéâ8ÞÞÞö*.Y³ÙŒ¢èÜ—ëo²’€¬ä +¹ÈJn²’€¬ä +¹ÈJn²’€¬ä +¹ÈJn²’€¬ä +¹ÈJn²’€¬ä€¬jµÚüü|­Vü¯J¥2?????ppðç6(]̰WÁ_Hn8ƒ$I+•J£Ñ‚ \.ONNþñEÁŸ#7\HÂ0,—ËSSSÃ^\-¹àü2††£££_ßçg~[õøíárý3ìüW¥¡!ŸÏ/--jµZ½^ï¾, KKKÝ—+++ív»\.¯®®&I†áÚÚÚáááêêj¹\þøñc½^OÇs¹Üà[T*•8Ž»/‹Åâììì|Jø?rÀytCÃúúú¯' … Z­VE­V«;¿ÓéAP­Vƒ ÈçóAŒ&IR­V;N¡PÈår­VkggguuõåË—Ý›/..ÆqüàÁƒ[·nA°»»[­V¿~ýÚ›Kà*È gÖm„ ÃðÔ õz½ÑhÜ¿¿R©ô]EQwc I’\.·±±166v||ÜÙétz é…õzýáÇÁÿvJ¥R:¡T*-..FQôèÑ#™\)ý g“f‚|>†ašç¼{÷.‚………ÞÁ•••0 ·¶¶zgffÆÆÆ‚ HÿMåóùÞªÄíÛ·ƒžV†8ŽÃ0솆Tú^oÞ¼¹Ø‡ƒß°ßp6I’¤å‰f³¹¼¼üúõ뾯ù Úív†;;;;;;}—§å‰®´BÑ'­>œ:çàà mzè;F"½m«Õ:×g‚¬ä€³éö4LOO/,,lnnV«ÕÁ.‡$Iz›"/ËÉÉÉÏnžvP^ú;B/¹àlÒ>ÇT©TÚÝÝã¸R©¬¬¬ôN ð·“ñrý¢®”þ€ yöìYÚèлËå’$<}¡V«]°”055†a_±#‚£££(ŠFä”kþbrÀ…LNN‹Å jµZ÷k{ff&‚W¯^õÎŒ¢hssóÔŸ·8“‰‰‰$Iúú1766ªÕê`;\.u €‹šÝßßo4/^¼H˳³³[[[Fãøøxvvöúõëoß¾¢( þ‡,ÎáéÓ§ív»Z­îïï‹Å“““î#}YÀ¥“Îàg6ô‚B&‚ 3 )tJ…i–ãCä >4’Ò˦¬‡z©!hЂBÃQšr•“`|âÃÈÉÅ<¸lŠHÁƒ#l`…8Â2 ¬ ½ßî×ibR«¦qìÿ÷”ùæ›™oÆøÏ̾™ï›÷ïßBMG£Ñv·¢µÊå²Åbiw+Zèääääääzïã×Àëõ~ƒQ}ÒÌÌŒ,¯ Bo»ÒB‘Èÿ†ÃÿÙîV´ÐÚZ®§§ûzÿ¯½Ÿ>ê꺃Q}ÚÌÌ €òâÅp»ÒBß|ó_ïßÿO»[ÑB‚°àtö^ï?âµçr¹ßn´»!„>Fm„2ŒÚ!d&µBÈL0j#„™`ÔF!3Á¨Bf‚Q!„Ì£6B™ Fmd•ʉ¢¶»Èd®ë×£62¥¥¬Û½ÒîV\¾dò],ö¶Ý­¸¶®ë׿f»€Ð§9ß¶» -‰¤½Þþv·™ žk£/GQ]±ˋŲ¢ªªNKœÎÞéiWý"Æ:gÜй‘V5š4žÚª&­=£F;E6Z,–/²ò«@UuãÖL OÝÙ³„æÇêâ£/ϵÑå(Ë áð°Çó”d³OŸ®íìLZ­–dò]$’¦•_½¾¢ø†ãº%i_ÓJß“¤ï}¾„,k¤ÃXâñ'ÇÌÍe‰œªÎ’ò©© IÚ¯© ××wu½Lf¥R~«õBo”ž›ËìîvuÝ¢­Z^~²¶–§“[[?­ŸÚr0î»Ýn+•ª.—}zÚÅq!M+%¹D"·¼üäéÓ5º*²ÝL¦ Ë“äåó‡t§Gx¾‡TÅ7…Âù,l<î½ÈζEý¦R~·{E×ËšVêïgeyŠÅ2)$KÙí6Iúž|6~•e-•òÓCdÜPý±R”C·{%ŒF·€~[½×çÚèrX­»Ý&I -Y\ÌÚí6«Õ’ÍŒ%‚ÁAUUÕYA`'&6ÈùN©T•¤}¯·oïùü¼‹½•emggRUg÷öÂ05%Õlˆü‹./?¡u|¾5:7Ý~ñbXUgS)¿®—C¡ÚÅÏassÈæÆòôé™ÜÙ™¤-lÔòb±‰¤%ûÞÕu+—û=²«ê,Ëv{½ýª:ëtöÀË—ºÑD"çpôÐC4:ÚGÖÀqÝã㤎(¾)•ªd£©”_–µ©©‹ïïfÜAòWX “?ýÔõ2IýëÝÝä ¤RþBሔ'“ï~ ‡‡É_DUKšVªßJ“c¥i¥õõ]2kuÕ»¹¹ŸL¾ûrûÿù0j£K#Š|¡pD/? …#Qä`q1ÛßÏH99Ç!§6Äô´‹ç{8Ž)—r>ÕÙy+•òÇb£5[!Qž„9RG×ËôßLX2‹ç{ìv›ªžòü¹X¶{vV$›s¹ìt¬V‹(Þ+•ªÐ¨å‰DžÖ'X¶ûÔ­ˆâ=zªN®ÓƒÁA:—½ÙYQ×ËÙìªê››û/^ “‹ žï ‡‡é)§éäù†±0Œ…L:½ c!ǶTª–JÕJå„TÛÚúÔYY‘?¾G®ð¬VK<þ¤~åŸ4E®öâ-')¦µµ¼ÓÙ+ËZ8üaÐúÛ`T­ž°l·1`€Ëe7þn]'ÃÃ÷<葤ýíí_‰\(´¹ºê¥7Qškr¬4Í·k`ÔF—Éï~L§÷u½L¯UÆR*U@’Éw$i[CUõƒÝãyHþc±·Ñè¶ñŽ?Ï÷°lw>_{:yêoÀ—Ô¨åGOMÖ‚ÞO«'Š|$’N§÷áË"“)УGކÃñ­ÅrëÙ³’Ëu‡ UÕŽÊ·.yß®E9,—OG$1âó%"‘´Çóa,»»‡´šªêõym‡£ç:+Ìk£ËÄqL?;?¿Í0ÚÂïr9¦žÓéý±±Í­¬È$w $‹k²v»m}}—Öñùðq¡-µœä¦I#ɇš˜R.Wég©çç·—) ëz’û«Ng/Ï÷ô÷³ãã6:5%Í̤ášßX\ÌÒÉãã’Êðûzÿ°R91Þš¦®Ù±ÂsmtÉFGûž=[ýúÃ]DçáÛ·¿Œ%VVdÈå4¯÷á©×¶óó#ùü!É]€®—‰B’¾„R‡œ·†ÃÃìÞwqZnµZÂááH$Íq!°Ûmýý%Ž~”¤}Ú©QØDâ]ý˜¼n÷ ÝßTÊO GÜîã¡ ³®²³ªÙYça¡ð‘Hšt¯öÔ«™ët¬pŒvôiŸ;Fû©7ÄTUßÞ>€ÁÁ^zN:KÐIºx&ó‹ÍÖ58ØKÂq¥r¢iºq´Ž1ú+Ê!Ë2ô²·R9ÑõrÍÊi4F{ͦë'›¨o¹±m·8Žá¸×ÛO3ä$š®°¦g:üq7•òKÒ~GÇ­ú_»lö Ÿÿ·ÃñmÍÛWvŒöšï@ý$ÃXèß´Ñ΋e]/3Œ¥Z=ùÓŸþ¶·÷œç{ê¿6§.^óu­Ùâ•BÆhǨ>ís£¶5ŠÚDÜ{{aÈ3ËËOEXAX ÏàK€]Å7}}=»»‡››û““nw£Z루],···«ÕªÃáày¾]mº J¥¢iZGGÇq5³TU­V«,Ëvvv¶¥mè+¤ª³±ØÛrùW–e=ºÓ¨³Úà྾Ú ™ík}MÌéìÝÙ™$O3õõõŒ;›gЮߣv±X …Âï$D£Q†aÍ»—––B¡˲ªªÖÌÒ4mvvvzzº-mC_'údã9êœeÙ¯œÕjùª.Pn@¥R(•JËË˪ªªªº³³n·»X,¶»…ç¤iZ6›5–ÔL"„Ý€¿þõ¯J¥œN')µZ­©T B¡±¶¢(Š¢Ô­ X,gÕL֬ĸcP3i\¼ÑoF“f,Ëö÷÷¯­}ÔssmmM–ek*7Z›ªª§¶¡Qy“U59d©úË„:ÕMeY«ÕjœaµZ·¶¶hj8‹E£Q:÷Õ«WÃÃÃ077·»»ÛÕÕ%Ë2™µ¼¼¼¶¶F'ÉJEq»ÝÁ`®ÄëõÚl¶H$B&Ãá°Çãº\ ñxœ|æ8. ®¯¯ëº äR©šfS~¿Ÿ®œeùÕ«W´aL&uèN‹E·ÛM¶v»]’¤&å055E?€(Šóóóô3ÝQ%I¢1Ú¸Ã0ñx¼>BF7TUÕ4­þ hI§ÓÏž= ƒ$"ÂÄÄ =››› ªêÞÞÃ0OŸ>%“$Í255Eªiš¶¾¾¾³³CÖ …VVVè$žn·»T*‘òT*%˲Ïç£MŠF£/^¼ ³t]¯¹0E YòeZ266V³Sä :tww“òT*U(b±X“òd2)IÍ/ƒÁ……r||>_¡PH¥Rªª.//#ûÜÜ]jooT>ó!ô•ºQ­VÀåj–ËŸŸŸüøq  “ää÷åË—d’eÙÙÙYèìì$ë!¬V«(Š¥Ò‡çwý~?95þá‡`qq‘L>yòû +EÉår/^¼ å<χÃaY–i:B’ÆáyÞn·7I,tvvÚívš$Y[[#qœZ\\ìïï¯Ù)r)P*•J¥R¥R!ÚÚÚ"Õ•ß½{7 Òü’Ãár`eYƒä¦®Óé ƒ´‰DBE²Tgg'ùÂä;B¨¹› ð,›ÉdštÑu½&¬3 cŒ˜ÒËG‡Ý½{·yµL&ò, dMÓt]'›0^ôõõe2hLEz /ËòÖÖ–q.ÉZ‚`,Ìçó055511qÿþ}†a\.×Èșۨœçy†ab±X¹\Îd24…¢(Цi$ˆ‡ƒü0ß!I’È MÓòù¼q÷B¨ÆM 5M«ŸçóùX–5o'9ljDèÙk}ÊØn·÷õ}Ô–ü~ ?xð@’¤íííD" …VWW=O£òt:=11Á0 Çq£££cccÍÛV­V5Móz½Æß!—Ëe ñ!Tï&‚ Ër¥R1>{R©Tè;†avwwk–ìî>}HŽ‹p8¡PHUUaËå2ttœò~á³ I’šô0 S*•Œ¿IÉd’MEQÊår  ŸÏ‰D<O£òùùyãIÚ]„çy–eóù<½Ž¡gÖDZ,[ó£˜L&\\\4¶´ªIy8£)rrMN±ãñ¸(Šn·›Ì"=ÿÈçx<.‚ñ¨†ÃáFwÚ«\.¯¯¿M$rínH Y,·nßþïv·¢…ªÕßôëýG¼ö~ûí·ú?z矢(äþžÃᨿ'¦ªêöö6ˆ¢Hƒ yéÍÔOêºN"¦¢(ÆžÍ'³Ùl>Ÿ¯i†¢(ÆWˆWnTÓUU;::hƒkVBwjpp°fU§¶¡QyÍÁ!·jé ÉûOxž'ÝÞ7rÉ1·ÙlƒƒƒW3dÀôô´®gŸ<Áœ;Bí´½ýËáa'¾©µµ8Žóz½4yíóùTU5>éc _ÛZºúðM­_‚×ë …Bä9&Ò)ðÕ«WínBÈÄ0j·Öôô´Ãá 79].—×ë½²i„)`Ôn9§Ó‰Î „. ŽÑŽBf‚Q!„Ìä7ÕÕÏkþëÏU©T.w…!ôº>Ÿ¯þAGEQîß¿‰qvii‰>l‚Bè|0C‚BfòyQ»ÉÐ_—;øV±XÄA¹B¨ÞY{þ5¬ Î<øVÍ›¬ ¾E†‹F£š¦½~ýšŽZ€BhÔÖu}nnÎ8ƒ¼"• ƒuÑêóù&&&}Î;†ÃáÛ·oc7g„ªñá\»É¸0õƒuqFçççÏ>ø–ñuwõƒo d³YR"y÷B¡¿Gm†aj^iDúÏMë:ûà[£££t¤sh:øÖ©C#ôµÉfòùÀÈÏqŒqV2ùîèèØfëòxˋŲ$í—Ë¿z½ŽŽŽ[º^& *Ê!Ï÷Z,ÿ<¢‹YÆI–e:;oU*'dq²-‡ã[§³—nšN¢/ï¬yíFƒuáà[µ‚(¾)ŽÆ¡ÐæääŸççG@QÝî`‹®—WVäTÊoµZ ™|‰¤É"‰DÎn·•JUYž$‹+ËY*ÝVÕY²¶û÷_îí=§Ûí^ñzû§§]KKÙL¦@Û mÎÎ>&%º^…6ÿùÏï}áÂàŒQ»É`]8øB—NQ …£IŽçæ2‰DŽDíññ A`ãq/©) Àº$}‘HšÎ"‘šDpд’ °4R»Ý+±Ø[ã÷©r9íõëQRMß„B›««^rv/ óóÛµÛâL=ÿš ÖÕ|ð­õõõJ¥•Je}}Ý8 ßB¨‘ŽŽ[šV …$UÕ`zÚEn.§‡§˜š,ŽŠÅr6{³³¿Êó=v»Í¸Nº9­.—ýd3X¶›Fö¾¾–í¦ —Ë~‘Dq¦sí&ƒu5|K„û÷ï“ñºL:øB_Ç1³³‰\"ñ7–íöùsÇ1ùü!<}ºf¬¬i%]/“ 897'úúzŒ)‹åýLÏÁ‘Ý€Sfäy~ooæ7âñøóçÏÉ [´o5x<‡ÃQ?ø!Ë2¯kdd„$¾³N|+•JáÝH„¦§]ÓÓ®lö “ùY’öeyigg²£ãËv//?1†`àùÐÑ×à&†7¬aÈ‘T;µfMyMãÛ¥kVÈó|MI}„¾BÙìÁÚZ>÷:½Ngo8<|ÿ~d{ûÀáèÑ´RµzBûo¤Óû²¬²,38ØûìÙz6{@gO´Ñu‚ï!AèʱÙ,‰Ä»Xì-™\[{§i%‡£‡ã˜ÇïMLl,¶ªêªZêì¼EfÍ̤‹Å2Äbos9í“âù–í^ZÊ@±XöùšVjᎡˀcÙ tåp³ºêDÒÑè6)Y]õ’ž×’ô½(¾¡©m»ÝF:ÐY À0–þþ™F–í6®¿»»ƒ~‡‡#‘4Ç…@ï=~|Ön!Æ• / ÇhGŸ†c´· éCRóˆ Qót U,–«ÕŽcadyò,j´6t¥àí]u§Æk¢Q5v#9; Ù&‚ym„2<×FèzŠÇŸ´» ¨%0j#t=5É® Sà B™ Fm„2ŒÚ!d&µBÈL0j#„™`ÔF!3Á¨Bf‚Q!„Ì£6B™ ¾ó}Z<ÿË_þÒîV „ cÔF!3Á B™ Fm„2ŒÚ!d&µBÈL0j#„™`ÔF!3Á¨Bf‚Q!„Ì£6B™ Fm„2ŒÚ!d&µBÈL0j#„™`ÔF—àøø8‹¢Øî† dV>Ÿ/‹‹ÅOÖÄ7µ¢ó;>>–$)Nonn’ü:!t>ß~ûíáá!C6›ÝØØ$I×õS+à× ¡óit®m±XDQ¦wû1j£óh¾ñë„ÐùÔDíú`MaÔFR¾ñë„Ðù¨Ý$XSµÑå á;‹µ»!™Òôôt__ßYzÀ¨Bf‚ÏF"„™`ÔF!3Á¨Bf‚Q!„Ì£6B™ Fm„Ì!™Lr˜››3Κššj4‹E±Q¯2ã¬b±˜N§/·åèrÝlwBŸ–N§ÇÆÆ^¿~íp8òùü³gÏ,K ŸÏ'Ëò«W¯l6ÛÏ?ÿ‰DÊåòüü|ÍJ¥R£•wuuÑÏ^¯wxx¸E;‚.£6B&0??ïõzI˜æyþ_ÿú×úúz (‹²,‡Ãagyž/ ’$ÕGí&âñx«ÚZ3$™€,ËÿûßëË­Vk86¦>,K“õÐ\ŠÏ磅>ŸLŠ¢¨iZ"‘Àa‰®2<×FÈ:;;@Q”L&CR"¤Üãñ«e2»Ý~êr¹Çq©TêèèhbbBEI’@UURaff¦P(ˆ¢8::ÚÂ=AƒQ!ÓPÅív€Ýnïíí­¯àóùt]o”ñ`Y–Ìây¾££ã»ï¾SU•ã8Zçy°X,亚0C‚ið<¯ªêÞÞ ÕÌ%·%Ãá°1ÏÁN'˲­k-jŒÚ™Lgg'y·b2™¤…‚ ȲœJ¥j&F}}}_¢}¨Å0j#d>ŸÏØšŒX­Vɤ °µµÕ<³‘ÉdjJl6Û%7µFm„L@UUc¶šÒ9ouuÕjµ&“É\.Dz,‰ÝDww7éRƒa˜¡¡!—Ëe\C}D"±»»{êÐU€Q!ày~gg'f2Žã~úé'§Ó ·oßž=ËFGGïܹS­VãñxWW]™E«Åãq¼EyÅáX6!d&˜×F!3Á¨Bf‚Q!„Ì£6B™Éÿ½ƒ…×O¹ªIEND®B`‚frr-7.2.1/doc/figures/ospf_api_msgs1.png0000644000000000000000000010771613610377563015061 00000000000000‰PNG  IHDRtìëÒo pHYsÄÄ•+ IDATxœìÝ_H#iþ/þg‡¹˜…6ä¢iè>T ÛÁÈ¿,™ _¿f•®… d #)°¿ŒÍ¸Ä¥mˆËÌr”¯al6ÃHÑ6†SÍdé\øÅjŒ,Á@/[ÅYa„|H.æâ@ÿ.žµ¶:QÛ?mü÷~1 ¦ê©§žJ´?yþÿâíÛ·ºè£‹.Àƒè Ðm_tà „¿¾|yÑ¥8_üñÿûÿï¢KqŽl½½­Fã¢Kgµ¾¾îõz/ºpˆ¾pzÛÛÛßÿßÿë¼{÷¢ r^V—–йÜ_*•‹.È9ú÷Û·ÿóÿïAüÃ}•ÍŠâ?þñDß«åcBˆ¦i­VËzÔf³qwƬ5M#„œ=ŸÃ4›ÍgÏžÍÌÌ´WUÕívw¹0m`Y¶§§§ ÷ºvTõ.ËÚÇv5­¼¶f³Ùú<ž;}ˆ­fóºÞ×ñ¹\NBDQð[|úé§Çe³Ù³d-Š¢(ŠG§Éf³©Têtù¯®®V:*%ªª ¨ªzŠÂ|(~¿ÿÙ³gG§I$…¼±¢~îwlWÓDžŸº?—L¦ãñß|ú©Èó»šÖ–lá·¿HF"ç_X€àŸ£®†††4‹¿ÿýï ÃÄãñZ­vê¬@ 8:M<oœ¶Ïimmmddätמ«P(äñxŽH ªj4íZy®´åH„’ÙØø^Ó¾×´?¯¯× c®ã‹Ôv©ôùƒŠ,_DNìà1ÏÇ-//ëº.¿ûÏ™ªª‡ÕØTUm Õ““““““æËZ­¦ªªÖQk90«Ã’Yï^­VAxonGßèÀÇÑ4­íY+’¦i9ÌÌÌX;`hš÷~¡ïÏÉ:ËssèÕªÇç³;ôå ×;‹Õ ÃZý-d³„©…óç ±«i;‡üuÔkµU­ô!î¨êaWvÉÑvTµ³m.›CG]Ñ*©Íf£/³Ùl<7Ï.--ŽŽÒŸ#‘ˆ¤yž/•Jù|ÞívÓÐHO‰¢X*•h†a2™ ÇqÇéº.I’$I4¶Y³2“BAà8N–e]×ð€Öàm==_ÏͧIò ÍÐÖÓ³Ï× ÃúFÀ¥òÏ–ç¡¡¡ååeBH£Ñ˜%„,,,˜•-Ú(Êó¼õÊr¹L1 Ã9ŽcY¶í¡P¨X,ÒæVA|>_ç¼4Z“–e™fKÑšMÌ0Œ5±ag™Üf-äàà`±X4_öõõ½·H4ke´-*‰LOO 0 ãóù¾üòËÎ’ÐÜè{n½‹Ùl–çÆôziP©×jk²¼–ËÅ=Z~õŠ6«*²lg˜t"a¦ßÞïãèŽr±h·ürBn³¬Y'ËZ[ÂÍS}n·ƒar©T«Ñ(‹f«5!¤nËßÔ ×ëìø›jC»‡YÞ±ü®îéúv¹Œ‰¼—Ó¿ú}Í9²¯^½°öÔB\.×àà õÊÞÞÞcÞÃáp”J¥l6ûúõkY–C¡P[-¹Õjéº …¬qÑçó8xxmmy÷ß»ópD‘¬ñø£££Ÿ}ö™,Ëkkk’$E£ÑçÏŸßí•lµZ,˶Õã}>˲ú»ý7PEQ¶Ëå‰ý)Ýv‡#099úðáWk/^LÌ̲Ù=]·3Lyÿ«a¶77 Ùl÷{OD)’ÓÓv†qrÜH `³Ù~?>~êÜZ­Öž®ÿ:ºmù]õø|ýG޽€ tÀ¨«žžž¥¥¥±±±H$B;Y†©×ëÖu-²Ù,‹ ÃX'ÝZû}­ÿö·¿ƒAxR©T2™¬ÕjÖ1S´Ò̲lÛ]>ûì³Îʲ|Ø\&·ÛͲl.—k[pÃ0Œ“ö]¤h4ªišYýíœyLQUµÑh˜c¿EQŒÇãmÑ×ãñ<~üØçó™Ö4moo‹uBŒü.í÷x¬8뢊,÷ eÞ­ìŠ<ßͱW}OÛL'ëÂ{º¾£ªæ2 ?ê:­(¯.,°.—9~Ê:ìÙÎ0o,¿N»š¶÷¾ïaw8Îɲ·YvÂò»ZÈfïôç—ÁÁ3ŽFGGC¡Ðââ"ív˜˜ØÜÜ4ß( ããã´þ711ñòåKzªÙl¸¢E:žžž¦ÝŸd¿Ø ½æ|_žç%I2[\#‘H<o[„‹ªV«GÌô¥‰­K…ˆ¢¨ëú)ú‰+’×ëE‘>T6›}yÐrÇSSS´=Ÿúé§Ÿ¬Uö½½=BˆÛíššš2ߟH$ÒÖ}cÑ~Üø£GJ¡`œÅ=]ï÷x굚^­Žt| ¶77»Öå9äóíéº9ÌjGUYöZ¾ê½Ø_HdGU·K%Úªlmj¶¦!„zµjÆck7¶“ãê†Ñj6 !­f³léìèçù‚$™W%#‘t<þóA>p:³¢8‹lðá:ãèÛo¿-•J³³³¥R) ¾~ýz||ÿðüù ×KûzG:š4““¹d²°ºÚ.ÏA¯÷ÏŸ§ãñµ\ŽR7 Öå2çBö4mŒãì S7Œ~ž§ÕÓ‰Xì÷ããâþX 'Ç9Y¶Z.÷¹Ý£Á`åõë¨ßO/±v*‹OŸN•J_ ÐS^A0«Ýs™ŒÈóô*ZŒ‰XÌ~ª)yp =Ì¢†êoß¾=l dºþ³µEtmm222Ò–˜ƒb¦Õj}úé§[[[n·»-[UU‹Å¢Ó鱯TMÍ»˜Ìš•¢(oÞ¼é<ÜYøµµµF£át:Û{ÛÖan6›†a˜³x;׈>¬äÖÂÓYC´§¼-EQÊå²Ç㱆úŽY×Ó>0Ùå¹0ð|këçFc»\¶ž"û‹WBFÁîpÐéFf‚]M[{ñ¢ßãù¤·—fb>N!›mµZô*k³6½ãÿ)§Ó32rÌÐûï·oÏg³œõ^ô«R¦»cúŽiV…¡¡®-¦ Ä/Þ¾}{–ë­Kjý>Ýã,hu¥išvÿþý••3Rr'‚¥Æs|¨è{iq#3úžå{ÀÉä—3ú¶ ¸k5›¹gÏß|cëé©(ŠþæÍˆ ,G£{šæä¸©ùyúUƒ¶ÿö¿š·e’K¥hûDŸÇó06¿ÔkµÕdr§\îµÛGEÑk™ÝG§e7êõ‡‘ÈêÂAô…çà~ß㛚š¢+>& A?~|ÆÕ¯Žã\.×£G"‘H$¡mËápø¢ËpMl—ËÖžˆèúwÑè?tžÊ%“âðð-»Ýãóm—J¿Ÿ&[Ëåh|íÌdVsɤÇçóø|;å²85€³8ëþ¾^¯wccC’$BÈàààÔÔÔ Ùc’ì¢#§Âáð{[ÂáºË²_ÏÏß}ßlÝ.drÍìéú~ÿ=­¤²}}¿kkï´]*MÄbt$¼ %&'뵚ÝáXŽD¬ƒÏ™;wrɤ BY~ò§?Ñštà›o¾8÷ƒ›ä¬Ñ—âp8:7Ù½ ‚—{F)\8[OÏÄ™ÿ4>H&׌“eÍöaïèè{W#!„Ø&›óf¸Õ«U¯ ˜#\I&×d™.qo6bÛzzúy¯àúÑà’[Èç““Iú.u²,ÝÓbGU÷t]‘åÎÛ­ŽOo³,¢/|@ˆ¾pýÙZßÝQÕÿÊåV™;w““N–5[¤­:·ªìŒÇgqÖQWçmwgçW™{SÖk5ssŽ>·;¼°Ð?4ôæ‡!v†©¼~m^RQ”1Ž«(Šgd„¼ƒÛêÇgtõ¢o­V+XV>‚k ^«)×ý3í¬KÁF¾ürO×gEqGUs©TÚ²rÎQWÛ››éDbGU“‘ˆ¹×\ôÝìlEQvT5HlonÒõÈF"•$z#¥Pˆ?zdg˜A¯×îpx!²ÙUÅ*WðÁ]½è;<<¼sª/Âpi‰ÃÃúµþLÓ‰Ä1ãPw8î?¿ÿ~OÓ¢~emm>ŸÿüÁƒ›bëíe].kbÖåêÙ$õõü|A’¢~ÿOõú|>o¦Ì”JNŽ‹?zõûËÅ¢9dÚ;:jÞ(9=ÝÏóæŒÞðÂÂh(”ŽÇ£~ÿ-»=;oäÛpNÐï —‘wtÔºð…9J909xw‚ß’u?ì™ëq멹ƒv1U[nÐ?뾉D‚Û—Ú_/^Ó4ATUåyžã8žç­M¾ô¬yU²ÁªõT*•E‘æ™J¥¬kqhšÆó¼¹0–õ*AèöºmAÐunz¯( ÏóÖd…BæOïH×Áà8®m ˜.Ê´ ìézA’¦¡¢("Ï×-Ÿ©R(L ¡«%¤RÉHdŒãÆ8®­%0HÐãc—»dŸi.•*HÒž®‹<ŸK¥¦!‰XL ‚R(ìjÚ´ 쨪Èóc'ò|[kü¬(Ò4»0àêúˆ"Š¢$I±X,ŸÏ‡ÃáÇÓPÚjµ^¾|é÷û'&&666|>ßØØ˜²ÿgÿþý[·nåóù|> …¢Ñ¨¹­Ðýû÷ëõ:=•ËåJ¥ÝȨÑhT«UóÞ­Vkss“îbT«Õ¬Öëuÿþú5V³³³,Ë ‚0;;ëõz Ã, â,,,ܺu‹¢iZ2™¬×ë+++4®Ó4‘H¤ía€/Vhv–Î ÍÎz½uÃ-Ÿéê‚íÖ-BÈž¦å’ÉŸêõÌÆFleeOÓ¦÷?Ód$R¤‰Xl>Ÿ„Ë_ª<úð¡Wœ,^^}øðÞà uüNEQôjµï³Ïš­Öë—/£~¿01‘ÙØðø|¿3£¬Èó{š[Y™Ïçi ê.f¿\ekš&I’¹ë»Ûín4’$™ h˜ÛÍÌÌT*•ååe¯×«ªªËåÊì·ä¸ÝnI’èÂO4˜½zõŠn3Ïç‡÷u;B4%„˜¾zõj`` •Jµ­!E·èíí¥?ÐiQé¾sssfbš›Ãᘛ›ûâ‹/4M³Ùl²,Çb1ëÃ&“I¬TuèE¶Þ^úÝ)6÷Õkµºa|mùLiã¡Ýá ssÿñÅ»šf³ÙY6'ô¹Ý­F#—L.Ígjëé±õö’ý'B¡‚$²YZàÂê*ërÙŽšaBFC!z|bfæM¥’[^ôz ÙlÝ02tôP_&3-Ë‘ÈFá\Y¯­­±,k]¶ÉçóE£Qº{˲¡PÈ<588Hëšn·[–eðÊåòî?¸mmas‡‡ÃaÝÔö0´ýÙÚ|MÉårGÇŇJ’Dw¹§3×¹ä÷÷n£Y–}ñâ…ÇãÑu½Z­š7j4º®_ò­„n”чÿ*I»šv‡ãh%ØÜ ßò™z½N–¥»íéº^­šËó¶¶=í/»ÃÁº\Š,Ó(KW@¤§œ,+XþÜî ÒeŠõj•"¿»£niF€+çãÆû¦wî°K ‚@›‘†ñx<ìþboõzýå ûÚ-»…3 sÄ&¾Ýå>N/,,H’dý¢À´ø\¹\6ÿo¢«ÊÁe0èõö Éétxa¡ I£–ÏôöAŸév¹LÙy÷3íê¹ÄŸ)ÊBö§!Y—z8p[@úteË_!¤mÜ/\-;NBH­V;0ÊêºNk–ô¥ª‰DµZÍçóf•ÑÜdÐãñÈï6ˆÇØ„a»Ýn½ð°"µ Éd2B¬Ñ·Ò±+œÓéìëë‹F£™Læ½q.ÐH K&ÿ- „Xë‚o:>SÆé¼Ó×÷]4:›Éܹ:Ÿéh0˜ŽÇs©TemÍZ¡ßÓuZé§/ÍÅ•<>_A’¬{ÛѺYfø°>!„XÇ.={öŒeY3¬¾xñÂ<%Ë2mÑ¥±ÍL£(ŠÙòìóùt]7gYOõööBÌQÊÖš®Ïç«V«êþZçµZmxx¸­!ú@t8ÕÜÜœËå²FëjµÚl6éÏ´+zddÄív³,ûÇ?þÑL&Š""ñe3"„inŽvˆšÇõjµµÿ™ÒqUž‘‘>·ÛɲËgJÇw·È'ÖÏók¹œ^­Ž>|h=¾fùsSd™ÆfÚºn]¯#â÷O߀­<®±G,×ueÙJ¥R­V—––Ì’$ÑSÅb‘a:”Iű±1QY–m4²,³,K£¬×ë …B=¢qÑZ!™Lúý~ŸÏW©TêõºÙ><33S,ý~¿ ½½½’$1 sàÖI ÃH’T©Th·Ãår½|ùòùóçm)ïß¿O¿ Ðae46Ó‡ýé§ŸÍSö=…“²3LA’ÞT*tí}ýòå:>š©û÷=>ߺþWIúÃóç46OÄb¿oýôÓ½ÁAóÔ<Æáú=žï¢Q‘çG:L|úô7Ÿ~êdÙ¶mí ’ô£®ßfÙr±hg:ÊlÐëýu(ôûññÊë×ôTÝ0f™½ WÂG„`0¸¾¾n·Û‹Å"Çq£–¹ç+++ôT (í·}ŽŽ~ÿý÷„b±xçÎMÓ–––ÌPšÉdb±˜¦itÞ‘yÜáplllx<žb±822R*•B¡y¶T*…ÃaMÓ*•J8.YÚÙ¬2™L(4 ¶ #„0 ‰Dh}}}Ý_fcã µ´@§_¼}ûö°sªª lmmq<0Çq¡PèüöáÖ­[KU€6¿áC¹}÷nj}Ýy÷îÏyZl·nY'yž’éîgºº´TÌåþÒÑß|v»š6uÿ¾9‰ˆ²£ªãÏ·¶ºŸ“ã~?>nà‚$m—Ëfʵ\n-—#„üC×éU?ÕëŸo»TŠøý"ÏÿT¯÷y<Û¥Ò´ \ÌÃÀ%€1Ïǵ$Ëô‡=M“ÓéÑ`ðèô{ºî„ðÂ!„q:?>þäO LNB˜;wrÉäy.-Ô}ŽÅãó™?÷y<Ǽêßúþ>BˆkÿB×±s€k ÑàÄl½½]¸Ú}º Ñà¬Z†ùsÝ0.°$pU úÂVQ”]M»èRœÀŽªŽqÜŽª^tAºê˜O=ÆqéD¢;Eú°X—K‘åB6[QÌ#‚cBô…«jGUÿã‹/š­ÖEädötý¢‹Ðm=6›azÞ·ër]æÎÔ¶âÙz{G^’å~žOÇãñGn³l ¦§zl¶þ¡!óÁ;_².Ww.Ì8€óu‡ã2¥Ò{“™“y.§¶âÑYC¦¹L¦ó’¶ï|yÉÎê¾p%íjZrjŠ’œššÅiAHF"ÖÓ‚  »š6-;ª*òülj<¯ Öd³¢8Æqô”¹\QçUE±¾´6¢š9ŒqœµÕ±¢(4ýÇM Â-äÉHdZZÍæ|gº‰¾Qæ3Ò…œ!³¢˜K¥è©YQÜÕ4‘çÍw H˜—²YsʼniA KD™ï¿™ùm‘8¢/\Iw8N˜˜ „âÓ§÷K5¢¢(zµÚ÷ÙgÍVëõË—Q¿_˜˜Èllx|¾ß™QVäù=M‹­¬ÌçóNŽ‹?zD#„õªù|žôˆ¾Œ­¬Bhà'„L ‚™ÃD,¶]*Ñ\¯Õâõy<óùü|>ߨ×ç:ºgEQ‘åÐ쬭§çÜ߯s°«iS÷ïÛnÝ2ŸQ¦ß$ö4®#1 y¡ÙjmonÒ>‚t"ñ]4‡çóù{ƒƒéx\¯Vi†zµJÇ.™ï¿Wæóù~žÿ.µ.åp  å®*ºvÁ¾¾;'„BI*d³tí¡Âê*ërÙŽšaBFC!z|bfæM¥’[^ôz ÙlÝ02v‡ƒÒ—ÉL Âr$b6ÂazÕ×ssÿñÅÿùý÷ÞÑQBˆ11‘ŽÇ !4Ò|=7G7Çís»åtzOÓ!ô¾ÿô¹Ý„ÙL¦¼¶f-ü¬(îiÚ|>O\EË‘ˆaÌ×åW¯¾HÇãt]'biªµ6$éá“'´Ù¶Ïín5Ê!­¯æûß—Él—JÛåò5Þ„n D_¸ìwJÿ½Þ.•&b1zÊɲB(d¦¼78X$B­rÉ’dÍǬ‡ËRDŸôöBìN'}I£>!ÄÖÓCÌŽªîîì{{uð3 !„ÆÔ¨ßϺ\^AðŒŒX» “SSÛ››xþüê†^Bˆ^­ŽZÞX[Oavö—;¦ïC›]MÛÓusí'BÈ¿‡E_9†û¢ IDATëRPæp¥!úÂ5áZ%¥kß[ॵÛ64N”‹EëÁ“ŽAMF"4xئméÁ¿lm¥ãñr9§ãñ~ž·Ìùu(”ŽÇ½‚pE›OçÊ P8?ˆ¾pMŒƒéx<—JUÖÖúyÞ<¾§ë»švgrˆ¹*‚Çç+H’u j½V;0N¦¢(«‹‹æ¢ùd?¢S?·Zfl.•Z|üxôáCZ//ßeÙ¯~ûÛÇÊ^?žpö”ƒaœ,»»³cVúwwvΡ\WF]ÁõÑÏók¹œ^­Ž>|h=¾öâ…ù³"Ë46÷{<{ºnÝ¥5â÷ŸhÇ7ýÍ'ËŽì_Òj6ÍEŽ Ù¬8ýÔɲmÃs ’ô£®ßfÙr±hôz ý~|¼òú5=U7ŒÙ“ÔDG!—LFü~ºõMA’ì Cðh0(§Óæ)zßA¯×:þÈ;:úëP(9==øùçW±ýy.“y~êþ}ÏGOÙ¦ml§©……©û÷Ç8޾WèÐ… Ñ®°ðÒ’¾³c®@t‡ãœ,k DÅVV6‹År±8XÃÃ\&3øùç•ׯËÅ¢ÇçB!Úò|—e¿žŸ¿»_[í|Ioaw8–_½’Óér±Øçñd66Z­ÖÚ‹­fÓÖÓ“)•h38!ļo[VSóó²$½ùá‡+:š×úŒ±˜Ù×ÞV¶>õŽû^Ór©T«Ñè÷xŒ¤½õ„ÑP¨ßã!ïͽw¯;пxûöíE—®ªÛwï¦Ö×wï^tAþ‰Î@5'BvTu|`àùÖÖ•]|®þýöíùl¶káZFE‘ÎÝ"ûÃÖ¾?ÏźÕRéw¿ùÍúÿ÷é.¯(Êv¹Lé÷xÌw‰ö2Øl6Y’ÌãõZmM–é·Š¶÷sWÓh÷ãtZÇš—´?©YQ††D,1}¥ î ×ý×­\,öóü‰FNA—ÙnÝJNO·~úéN_ßår«‹‹_ÏÏ_t¡%ò¼Ù0p`JÛ­[ýCCáååÎSEù/¾øu(4¸<ž>·Û Ò´afWÓÊkk•µµÅÇÝ]s…¸öuÕh4!¶ý³l6ߟ@YZZÝï­‰D"òþøžçK¥R>Ÿw»Ý‚ Bè)Qͺ5Ã0™L†ã8Žãt]—$Élqµfe&#„‚Àqœ,˺®?xð@îXV„êþ ½<Ïg2úÇqKG Çq´9]UÕ©©)cÿûl(š™™!„¨ªê÷ûÃáp2™$„Xo×YZ+kÉiy!‰D¢R©Ð·ê,öaŸt_A’~ ™áðˆM–î}öÙv©t—eÍfêd$bëíís»sËËN–=pE³t"aëí LNÞá¸Àä$þè{sük­+ÕBQ”ÙÙY–ei¿¯¢(ãããápXÓ4MÓxžŸžž®Õj„l6»¸¸‹Å4MÛØØÐ4MßoW©×ëõz’J¥J¥=»µµE‰D"„MÓX– …B4˜% Y–WVVÌdæúz½.Ër(ÚÚÚZèøí¡^¯Óüóù|©T¢ùksz*•"„Ð'òûýÇÑljÅbÑh´°¿ä®ë¹\Žæöüùó—/_f³ÙÎÒZe³Y³äš¦…ÃáÅÅE3ÙË—/ëõºùÖɲLß:Q«Õj>Ÿ×4meeåèrè2;Ãìi]ÌkWÓ¾›=,%m1žºŸ®n¦ æÖ·9ŽìïòÔj6gEÑly~S©¬årô’z­¶S.Ÿt™q¸Òþ}777,=zd·Û_½zEÏ.// ™-É´6Ik‡étúÁƒ4¤9ŽÌA_ñh5šV4{zzòù< „m$IÁëõšÉ ÃP,ß7gffÜnw[S°¦i/_¾œ››£-ºn·;‹ÑŠ&­|g÷—Ìår´~Iïn5 †B!kPŸ˜˜ ¹ƒA–e÷ööŽ~ûúúÂá°w†ŸÇã!„˜ÃÈY–ߟSñôéS]×é[Q*•Âá0msöz½ÂIÖ8€ó^^®ÆWc7'ŠÂÄ„“eÿ+—;01ÝZã¸äô´WèȲð‚a¢~ÿÇ}50p›e?ð€ÎVZ’å^»^"÷ÚíK‡l÷×Ò¿F]-//BÆìì,!daaÁŒs´u´­i´\.B Ãðù|æAŽãØw׳%„„B¡b±è÷û !‚ ø|>oÇÔ~Z”e¹lY§^×õr¹L3‡¬HGÓϾûµT×uÚCìr¹dYƒ´{•FÜ~øA×uëãï©èÛßBîˆûZ¹Ýn†aR©T£Ñ(‹FÇ ³¯×|TUÕuÝcÙ't¶¨ÀEés»¿×´]M#„Ð]:Ì1:‡m÷¹Ý™R©^«Õ £m„óaÇÉþÈæ,&¸QþÕïküyõêÕÀÀ€µ§–âr¹­Wöî/ï÷^‡£T*e³Ùׯ_˲¼¸¸ …ÚjÉ­VK×õP(d Þ>ŸÏóî®mZ­˲ÖoôBšÏÔÔÔ£G!Éd’aú}â§Ÿ~ê¼ä, …Âôô4Í?Øl¶ñññ•9\ ;'wiw8\.ã°ãBïÍtÀ¨«žžž¥¥¥±±±H$BÛc†©×ë3–ïzÙl–ÆE†a*•ŠyÜÚïk=ø·¿ý- ÒêT*•L&kµšuô/­²,Ûv—Ï>ûìèðx<?öù|æ·MÓööözzz!´ÞœJ¥dY‡Ã4Áàà`µZµÞ¨P(8÷÷N?………Zɦ/UËJú‡q»Ý,Ë–Ëe³ØÖJ?\oï08:: …i·ëÄÄÄææ¦ÙZ(ÆÇÇi´˜˜˜0Ç%5›ÍM§ÓÓÓÓÍýÈh7°zûû­ò¹èRœ£=]ÿC,vU–ìê¬; Z—Ô û}ºX¬ø†¸l; ÜLØað*:k›!T,Âàà`¥Ryùòå“'O>HÉ®«ƒG]Ÿ×ëÝØØ “‘×××;×¢«0^ÆápÌ\…ýÂ.‰³Ö}à¤}º Ñ÷44M£ SœB7¢¯ªªt2ë•ÈŸã¸D"qØYQ9ŽûôÓO‡‡‡9Ž£[R‰D‚{Ïóæafæm ޏ\W]Z¥¨sñçÈf³1 Ó…5èαXŒn‚T,%Iª×ëÖu²è2–T¥Rýúµ5u¿ŠF£FŠÜ(×a@Žãº³5}©T2—Ø$û+^I’dÝ1¢møw"‘ˆF£>4WàlK#Ë2¢/Àò!DQžçi[¨ t¥ªD"Ѷß{$¡ ­‚ ¨ªJÛ`9Žk[`ÅŸ?b1ê™™–eéÞÉ:þFbGU[û[˜tžêŸÏçóõzF5ÇóòåKŲöº,Ë.—‹R­Vý~?˲ù|>K’dFSWVVòùü­[·îß¿o¹x<.B>Ÿçy>Òœ[­ÖË—/é)EQ”$)‹ÑÌ?~lf~Ø}[­Öææ&Ý ivvvy_ 0 ãÖ­[„Z­vÿþý[·n™é÷ûOúf ‚°¸¸(B*•2¿X¼wef†aªÕªù²Ñh¨û²Ù¬$IÖÆj€Ë&ê÷çÚ¶kGUÇ:päô`õ1Ý(Ð(’Édèn^¯—eÙÕÕUÚdJG™®.—‹6ŸºÝî\.W,gff²Ù¬a´.˜ÉdAøá‡èÖ¹áp˜^žÉdJ¥R¹\6cÃá0]\Ó4I’ž?NSºÝîF£!I’ÙT{à}­Ïc…SSS.—‹ö¹F£QzkzêÕ«W©TêDë’/,,ôööJ’T­V?~̲,Ïóß~ûmÛþ m|>Ÿ$IæËÅÅÅÅÅEóåÐТ/\f£¡P¿Çsœ”õZ-â÷oonz|¾ó.ÀU÷ W~¿Ÿ¶Ûl63 …B!³?U–es=Bˆ9nˆâÛÿKÛÛÛ#–{éU£££ôgå˜î!h2O­­­±,kÆxš¹®ëæxæï{ ZZsû³M›¢[ðžbKÝ™™MÓ666ž?îr¹J¥ÒÀÀ@óÈv¶J¥b}ùäÉ“­}ëëë„ááaÌ_‚ãØÕ´UÝ=h“z­Övüˆ”;ªz`£ñ&ffßÝ7lGU뿱éDBîµÛ,{Ìœn²![[[‚ Ôëõx<><|øðè¼F[X=©÷®?Q ðîòK«øE †aŽÞ²°M³Ù4¿8Ž`0(Ë2½Å³#ÛÙêõºõméííuïóz½4kå ÓŽªŠoô^«««~¿¿­lïíô¥;Ã1b À”œšrrÜ÷šö½¦e66ꆱl™kžK&'b±?¯¯&'Ó‰„"˱••ï5í/[[„¹ýïÓ³¢Ø¨×34Ÿ~žOÇãÇ/C!›]]\œˆÅhvÊeëÙ¹LfÔÒjð¡H’T~÷—Òu=:ëIÓSt(R½^o4¹\޶˜Z³¢Ã–O”'õQ6›µ¶| ‚ÀZŽA¨V«¹\î8}“G×u3ÜÖjµd2ùã?¿4###äÝŠà³gÏX–=þžótS,k»ÄçóU«U³òZ«Õ†‡‡O´Ò;=99iý¾“Ífu]§_:Õj5:¶ëˆÞåT*¥ëºçxýjpcSóóôg»Ãag˜F½nžíçùÑ`¶$É+ôg[OÏB>_7ŒŠ¢B?ÿüë¹9ûþ¾Û'l"–ÓéÏ< !Öîp|=7÷!ž à=òùü7ß|sö|Ün÷ÖÖÖñ£ E'Ë”J¥™™™L&ÓYg+—ËmÕÎcú8 ¦Ói¿ßO»Qi“¬YÍ ƒñxÜ0ŒãD_¯× …èv¿½½½4«`0xüU¨G,×ueÙJ¥R­V—––Žy¹¦iããã,˦Óét:mÏd2333Åb‘voÓ‘S ö5“$IÅbÑ|i·ÛeY¦e‹ÇãfKòææ¦u°®ëæ— úy0 cmoËÜ0 ]×C¡Ð{Ûà† LNVE–¤u}OÓ¶77û‡†Ì³f¥Ý±Š,[+¦{º¾].z½£Á஦¥‰V£±S.×OøOFÝ0¬Ã©½^tñÞ(µZM–åF£ÑÛÛ+‚Ùn§ªªÛíÎf³­VëáÇtª¢(år¹-%•Ífé ¡/¿üòDÝÍÙétÒU:‹çt:ƒïk†i6›†apG sØ%fð¢Ïx`>´ÃTUU–eé³›%ñx<ÖÛi&©TÊf³ƒÁ !¥R)•JÑ¡Î@ ­¢Æ0ŒÝn·¾}¡PÈZWóx<æ¤ÕL&“Íf_¿~].—ͬX–ŸŸ·V©À½{÷< oß¾M»f=O*•2o}Ø}­™Ìï×Ú¡í1ÍQÖŽøž GFF$IªT*õzÝãñÌÍÍ™o®Çãi»{Û[ß™yooïÈÈÈ)~ÿà¦y¾nv†ñø|£~7;{`²V«µ§ë¿…¬õZÏG-'#‘ÕÅÅþ¡¡>G˜˜Ð«UÝ´p<Ùl6ÓK ÃH&“KKKtP­ßïçyžÑ¥q…çyÃ0hbkJMÓîß¿OöÿMŽF£Ož}jž²Ûí¥R©P(Bzzz4M{oE‹eY3 ðÔêø39):hÉÌÖív ‚`ÉD§Ñ7çÐuž{zzhûê«$@Œƒr:ôȾßÔÖÏóÛ‡¬m>—Ɉ<õûiâºaLÄbt¤U?ÏÒ [7 ¯ ¬..îjÚãõ}Œƒ•ׯiÎuÃèçyôûÞ6›-‰˜#Œt]²Œ<0;5MÓu½­W×us5ˆT*E‡ Ÿb¤’aÖe8Ž3{-DQcY–a˜Î.Ô£}¨¹'•JE×õ#:­K µËBg#8\ˆL©TQ”ír¹ßã¡íÏft>Ÿ¿ûnÌ”J;ªúŠEÆéôŒŒ˜ƒœç2™/¿ù†§C—ÿ-°¿o޾5ÿ¹LfWÓÖ^¼ ÅØQվޡ(åk`xx˜aŸÏçóùÜn÷asll6˲áp¸m “t Áó«_ýJ’¤µÔÝkGÓ´µµµµµµÇïîîv÷š[·n ³Êzö8¸ ½^ëšSfØëŒôàqŽ˜¦óëË;göv‹÷æ W ]bâèe(š`ww׬zÖjµh4úôéSÃ0^¾|i.$LN¾¼’Ëå²®.¬(Š9y7‘HôööNNNr799yÒåå³Ï>+•JæàgBH$¡+,u&Fô¸ÑZÍæ›"L`œ rïÞ=ÚžLc*ýtØÊ†¡P(Þ¹s‡®Ž099Y­Véx(–e_¿~M£o¡P8ióêÔÔÔ_|A×ç¯Õj³–‘ÿ´Ÿ”v?×jµr¹L÷êÚ˜\(>ÿüóÉÉÉd2yÿþ}úM…>ãaóhu@ö×rH&“t‡VºJÁa·333Ož<¡‰êõº¹æA,+•J4“L&‹Å!Ç_Ñëõ>þœæ<<<Ì~_Y–ívûðð0=EièÌ=yŸˆÝn·þl¾4³¢ 1MOOÓ%‡éÃÒ’LOO ‚`VÖÛîþ‹·oßž´4Ôí»wSëëλw/º 7Ú¬( CCm[­Ÿ‡Ã8,1Ã0Õ‡ÿ Å8Q Ï]cøè’ åŽåDí°ÄgŽGäpB/!Äáp¼÷ë¢/\MÓ«µÖ€|= úÀ…á8îf®*è é’4wÆ<t¢/@·!úÀ¥Íf‰!DQ”„e®T*%B$9¿[7›M:s·k}àâ5›Mº!!¤\.›K@g³ÙÇ×ëõz½~~wïéé¹uëVâ„ÛnžF]ÀÅûíoËó<&ûÍ7ߘÛííí •ÙÔ뚟Ÿ…BjË££!ú™ˆÿó’ñ[p‘~nµïܹèRœI­V+•J¯^½j;®iÝŒAUUëîÖ/^¼ „8NëVôtÑ+EQÊå2Ý€Á<Þ¶Ò–¦i Ãôôô8—ËFéªÔç +MÂéݾ}ûëÿõ¿ì]ùž‡‘‰‰@  +MžŸT*•L&5M£/‰„$It!Zë=p×^z–î÷@×ÞØØp8ªªúý~žçéYzŠf.B½^7kÒµZmxxxeeÅëõB²Ùl<7‹q®Pk3øøã>ë<\¬ÂêêEá¬r¹Ü{g2™D"Q,;[žUU•$i}}Îf³900 IÝÕ@×užçi¥Á˜nŽD7JÒ4ã8²¿½wïNÇ£ëzw‹Æ¨+¸`†aœt"·Û½µµåµlzÝæáÇfJ²¿°×ëeY6NÓS¹\Î:Ô™ã8–eËåòIË ˆ¾p%5 Qyž§»êºn=KwÞ¥¬» ‚@«¼š¦mnnNLLtf{ž¥þ'D_¸zEùâ‹/!@ ŸÏkšvÌÚs8¦—§Óé¡¡!ÚÝ}è÷€«gyy™eÙSŒOv8 ì®®–J%‰Û8ÎQÀ÷@Ý.˜Ëå*‹'º„ÖYUU%„4›MQÛZž011A‡qµ ¢VUU×uÇs¢’œ¢/\0Aè¼ Ùíö΃ Ãøý~Úé˲ìƒÌÞÖ Ý–ìr¹Úò,‹]k‹Æ|_8½Ûwï¦Ö×1ãàbÍŠ¢04t¥çûB8Ž3çÝ_­V3 ㄼÏó³ŠÏ ê¾pñB¡ÐòòòI¯r8§½©TŠa˜¶Ð«(ŠaÝ ½Ñ.ƒ™™™z½®(ʹÞEQŽã?~ܹcÒòòòÒÒÒ¹ÞÝ ÑŽ’N$*ýƒØj6Ó‰D«Ù4T%H¤‰Ý®,Ô×O&“iµZçz ¯×›Ïçÿþ÷¿ŽŽZ7›Í‘‘‘¶ƒç 3Žà(I"„ vôÆýC׿‹Fÿ?Ÿ¯Ïí&„L ‚^­Ú†^ÒÏós]Yª®Žãº0âéÀ–êžžž®µ9Sˆ¾p”ù|þîû1È¥Rzµ:ŸÏÓH\Q”ÿøâ‹ÁÏ?µì9Vˆ¾WC½V[“åV£aëísk©Uís» Ùl«Õ}øÐÖÓC©(Êv¹Ü–’*d³ÆÞ!däË/žAsfœÎ;}}æÁ7?üÀº\}ûUŠA¯×ɲô.p D_€+ Í¦ãqÚ®[7Œ\2^ZòŽŽB¢~?Ïo—J„›Í6 Š<_7 šØšrWÓ¦îß'„ÐSßE£Ÿ< /,£IŽ IDAT}ë¨ß? MÌÌBÌœÍü©¶Fæz­F±YVÙ€6ˆ¾W@:7C !DäùÕ…ïþ‘íR)³±A븳¢HùËÖ­ÏŠbrzš¦\ŽDX—kI–éUÉHD‘å÷Fß•!‘¨†Ù¼<+ŠÛ››¦\ŽF !îö¢\-ó p,¿ze†ÞN^Kóòv©ÔçñÐÐK‰|û-!¤ÍBfR©¹löÔe(‹ýá#òüÉl6›“eá°ëÝeâé e:)h4ê÷xîþêW²$ÑÙD¬Ÿÿç©X,q!„u¹èËŠ¢³vÐëýÃóçéxœæ‡Í/t¾SüÑ#kzë mhƒáô°Ã`—Ñ…5ŽŸØÁ0Õ‡?§b@\oÔ}®ŒżÃŸ=p"ôœ¢/À¶«is‡Ô™zíö¥ö À1!úÜhw8.|ò-ÍàŒ}n:´$tft¢/@·!út¢/@·!út¢/@·!út¢/@·!útÖº‚Ó»sçÎØÿø] Ó^tàd°Ã @·¡å Û}º Ñ Û}º Ñ Û}º Ñ Û}º Ñ Û}º Ñ Û}º Ñ Û}º Ñ Û}º Ñ Û}º Ñ Û}º Ñàf!•JÑŸS©” ž€s…è Ðm¿xûöíE—àfAÝ Û}º Ñ Û}º Ñ Û}º Ñ Û}º Ñ Û}º Ñ Û}º Ñ Û}º Ñ Û}º Ñ Û}º Ñ Û}º Ñ Û}º Ñ Û}º Ñ Û}º Ñ Û}º Ñ Û}º Ñ Û>¾èÀ¦(Š¢(] €›î“O> ‡Ã¿üå//º pˆ¾pzAQäÁf³]tAn´Ò‹ È¢xÑ@ô…3 |óóîÝ‹.ÀVÛÛ»è"À‰¡ß Û}º Ñ Û}º Ñ Û}º Ñ Û}º Ñ Û}®§U½è"À¡}®ƒiA˜ú³R(ŒqÜøÀ€yä¤9ÿœÖy¸l·n™?¯.,°.×|>—eŸC£^?Å)8D_€ë`.“±¾¼78Øçv_Taà½Ðò pÌŠâ¬(B¦¡nIy~WÓ!éDbŒãèÓ‚@šWÑãÉH¤3ÃOM ‚R(ˆ¶²Ò¨×çöw„íR)¼´4ŸÏÿT¯oonš¹qJ¯V“ÓÓNŽó ‚gd¤Ë pm|LI¥R„ÉÉÉSÔj5Y–×ÖÖnݺõÙgŸ ‚àp8ÚÒ4›ÍgÏžõõõŽŽž½L4·o¾ù¦§§çˆdGûø÷:ú.W mp¶õöÒ~ÔõÑPh4¤g·}¾‚$Bv5íõË—ÿùý÷ÞÑQBH_&³]*Ñ4Gœ¢X—«­¡Nê#BH.—Ëårž. ÃÃÃÉd²^¯kš–L&‡‡‡ …B[²gÏžI’4==ýAʤëz4ÕuýèdGû˜DQ|öìÙYr¸ Eáyþ¢K—Ô\&313³£ªEI'åb‘/¯­9YÖkùºìÝÕ|Ä)êÞààùàš{Ϩ«……žç3–﹂ LOO·Õq%Iây¾T*)ŠâõzÏX&·Û½µµå~ߘ‘Ì™¿}—J%ö$ƒB/§r¹lÆE—.©B6›ŽÇ !v†qr\¯Ý^7 BH«ÑhKiëí¥?q >”÷ôû†ÑŸ¦¦¦†Ñ,7EÑuýéÓ§.—kyyùè kµZ*•J$Š¢˜›Í¦¦iµZ­í¸õ‰D"›ÍBÔƒÖ i2Ú"m¥iZ"‘0s°^Òh4¬f³Ù¶dm,êe½5-’¦iÍf“fÒöm/Í[ßgëqóFÍf³ÑhÐhæVéx¼Ÿç¿×´L©4—ɘÕÖ~‡R¯ÕÌ”?î·6q >”÷Ô}†)‹ǬÑz½ÞÒ»@«««CCCÇ ‚ÇkµZgÇ0•H$$Ib†"I’Ëå’e™òìÙ³b±H1 £X,.// ˜Õ_žç Ã`Æ0Œt:mF>Ÿw»Ý¢(BJ¥’ªª~¿ŸV¾i²d2iÆ-Qéqš<ߨØp8sssº®Ë²¬iš,Ë4úÈôFù|¾óAÚŠZ*•²Ùl<ï|(zkI’†††!´‘ÜçóÍÌÌІz³„ªªZŸ×Zàh4úäÉ“………Îwa˜R©´ºº*˲®ëSSS@àŒ½àpÍìjÚž®OÄbæ‘7• ýaÐë%„È’413Ci5›Û¥’aŽ>Ê{ê¾Ëˈa|ñÅ4¸¦R©šå1U*•!$ B’ÉäY)ŠFÃáp©T*•Jù|¾Z­Fö'3lnnMûÿÛ»¿Ð6² ²°œššr¹\¬–‹%™¾ªF#wT"‘`ç›Íf>|ÈVùýþuÝÀуär9VÛëáÇ÷îÝc-Ýl6‹ÅØòåååf³©(Êääd0äy¾\.#z¡K¿ 9éxt¹\µZíÖ­[ׯ_gé°NZqwP†ËåÒZ~]J¥ÏóZBØívQµµU]#K¥’ËåÒ®ÏÏÏt¥öæÍ›ZÉDÄúcÙ%ä#/EW«Õõõu­"ŠD"µZmïW®ª² ï:)¶o`w¬éäääq®1§ÓiŽã´ó N§seeE[Ëú¨Ýn7û.rdð‰õûGý~í5ë=&¢L¹ì +¥ÒkUý~n޽ít:Dä þòì½Úظ‰|?7§•pت`P+Þ󱾼 ,EE¹{÷n4Õn.J§ÓDÄúl‰¨ÙlªªšËå´ÔÑT*îÝoÐ}ÇÊÑl6=öö >í®ÒôÚÙÙ‘$IQ64iߦs¥R!¢‰‰ ýBUU›Íæ!G$¢ UU÷MÁ½çË£Á>@}Z…§§§‡‡‡yž·Ùl’$}”û»àbðëz>üïö‚„ffX²öV{=âvè¾›v½Þw•~wxo‡¥o*•Z[[Ó·eAÈd2årykkËëõ*в¾¾~ûöm}òe³ÙB¡°7}{B–寿þ: úý~‡Ãa·Û÷MJ“ÉÄóüòòrׂ#Ç]_¹rÅétî;ÖÌl6·ÞkvÜ`0øÃ?tUˆ¼^¯×ë­V«¥R©T*ݸqãÉ“'`€óè°ô½víÚ­[·º²úµétšçy6&H/îíõx<¬KVsä½Dd³ÙJ¥ÒÌî×íb±xœ½4KKK<Ïyo’ÃáPUµÓéh}ÔÅb‘Ýtø\_|ñE×f‘H¤¯¯Ïn·ŽŽv]?è¾ Ý 6›MQ}êK’$Š"Çqñx< Ùív»Ý>33Ã:ù‘¾çÑ?¯ûVßEDn·ÛétÆãñH$Âʲ<66FDÁ`ˆ …ÂÞyØ  »wïî]NDÒî,w©T*›Í†ÃáÃë755µ¾¾.IRµZ-‹'Ѓ}`§Ón·%Iê oUU«Õª ß|óÍôô4ߤ(Êôô´¢(GNƒÅ®øŽ±+ÄÅbQë*`«´óÕš…½vÒìììÞóeo#‘H6›5™L—/_. Ú§*Ër³Ùüâ‹/h·Ë½X,âŽ#€óâ_ˆÈl67›Mß»Xb•ËeÖÆž˜˜0›ÍOŸ>µX,l¼•~¤c±Xl6[×]Ilùêê*k ‚L&çç禫 Pr»ÝOžŸ ìŠé7ß|SÚîGÅr¹Ì.Z ›Í611!ÂØØ˜þơñ!Öׯ_azzZE­¥®?ß®“º}ûv2™dÇòûýúUl˜4Û«P(<|øµÈõ¥MLLˆ¢È>=6ämzzúÌÛð‰øìíÛ·ÇÙNQ”N§säuÐãh4Ífó˜E)Šb2™´¡O]·ÆžÆ«Õê{œæ!‡ÐNA„`08£´rȱØ]O{¯RŸè\NÛÕÔ³gÖ^Wà“6+I¢Ó©õ™Á¹pÜçû~Ä›[,Ëá‰õîÞ½Ëî¬eÀ÷ïßçyþ¤Ùs¢#¾_°rˆC>ºCŽuÐ^':8›Ž›¾½2??ïóù†‡‡ÙTD´¸¸ØëJ|³ž¾‹…Í%ÉÞž‘×÷³ººzê/s®CWs1Î>Ü3MÀG‡ô0ÒÀhçãº/œMÿètf¾ûîÒ%üôR]U]¿þu¯k'ƒ¿›ðþ.]º4èp°‡@@¯tvvðkxî }áý]úüsÿ?`®+€ÞjÔëŸþy¯k'ƒë¾FCú é `4¤/€Ñ¾FCú é `4¤/€Ñ¾FCúÀquÚí­jußUÛŠ²­(×àüBúÀqåïßú|û®š“¤9I2¸>çæy€ãr8z]€ é p^±NàA»}ßUúåÛŠÒîtÚ’ˆ,g¶XŽ<âˆÛ=âvë—R2é pþ$#¹PÐÞºE1¼°@DéDâÕÆ=üØÊóOe«ZMNMµšM¶¥7 Ḭ́×Å\.k…ð6Û¢®Ì}¥‰b6ûDQˆH_²™ã>æé|pÝàœ)ærr¡[^~¢(Oůܻ§xzþø±éÊ•‡/_Æ–—‰(êóYmŠÅD£r±HD­F#ûÃa¶*¶¼¬ÖjùTêøÕHNMÑO/_>Q”Q¿s}ýÎàÂBÛàœéô‡ÃZ°Íá ¢v§ÃÞZy~j~žu#³4ËdØ*o °ñüùÊ‚ÛëýÜdòƒþÉI¶Š•ÖÙÙ9f¶ªÕÍõõÿïï7]¾LDþÉɵ|þc À§é pÎ ÚíŽË§RJ©¤õ*k´+¸¯þö·ºªJ.—¶JÛØtùrhf¦˜Ë5ëõW;­V]U_‡¿–JVžïm‰Ãã©”JïyJŸ¤/À9#‹Ééi3ÇYaÔï7™L?Žï»eçÍ+Ï;<ž½«Z†tý:ñ6Ûµ‘‘!‡#>1qºõ¤/À9³²° !uÐôDtmdD­Õ´aVD$‹f«•ˆ–¢Q"úéåKÖu|R_z<¢ÑmEÑš¿l´F]œ3]]Íîß?hK1$¢ÙÝI0ZFrzzßíó©Ô‰zžíö!§3s÷.{»U­ªµÚñw´}ΙP,öãø¸v5×*Vž¯U*{oº5[,¡X,ß3ǵšM3DZAXÒ;SåòwÃÃìf¡>³yÈéúí·ý‚Ðj4Íæáófì-¿˜Ëu:QQüÜdj5›úqX`˜YIN 3}ž+H_xH_€³é{¡çþ‰ÝÈ´ïª!‡£kšIxou`4´}àŸ´Ù¯àT¡í `4¤/€Ñ¾FCú é `4¤/€Ñ¾FCú é `4ÌuDú÷§Kø)è¥t:#ýý½®œ þnÂøå—ðÿh¶Xz]€OZ6‘°Z­½®œ Ò>À¥Kƒž0Ð[Å••^WN ×}Œ†ô0ÒÀhH_£!}Œ†ô0ÒÀhH_£!}Œ†ô0ÒÀhH_£!}Œ†ô0ÒÀhH_£!}Œ†ô0Ò>]v{«Z=dƒV£QÌåò©Ô!›mU«ÛŠrø?ʇ;íòi[QŽü(>ÜVµÚi·?ViGþ œ¤/|ºò÷ïG}¾ƒÖN‹¢týz:Ï'“QŸï† ÈÅb×6­F#êóMr”­ju|xøôþÄŸvùÇ7'Is’tÚG‰ú|ùû÷?Vi‡ÿ œ¤/À>Ò‰„Z«Å–—Ÿ(ÊEɼxÁÛlÉééV£¡ßl%™4såS©ÕÎ%¤/À>*¥’™ãFÜnöÖl±Ì¤RD´V(è7“ …A‡cÈåZËçSìAÝÔÛŠrPãu«ZݪV»R_[µïrm¯ƒVT=Vƒz*óÊŸ¨„÷îµ>üd÷]Õj4ÎBo|Ê.õºgQŸÙ¬Öj­FÃl±°%f‹åÉ»ñ°!ËuUC!"ú?¿úÕ†,ki½×ç󬇳®ª_}óÍânŠoU«É©©V³ÉÞzƒÁÐÌ {]ÌåÒñ¸Vo³i{%#y÷µ[õêÚ+¼¸èöz‰hZ¯ ‚\(tU`ßj˜9n6“é"º!þpx-ŸgkÍ·°ºÊ>ý^¬`_ìÐ[•жexiiÐn?ä¸ìõø´(š®\™Ëdô%çS©|2¹÷d»>"ö)…´¢ÔZ½r¹ª6À©BÛ`S D$]¿.¹\éDbßvR~iiÈéì„~Ar:óKK‡¸U©d^¼x¢(ÿñðáóÇ‹¹[õù¬‚Àú·C±Øƒh”]]n5éxܳU±åeµVcýÛÅ\nåÞ½P,ƺķ*í(²üãø¸¶×Ë¥õ–ï´Zr¡à ¾|9µ›CšäÔ”VÌ‹­fs)ùç™&“ßÏÍ=Q”ùÕÕV³¹j{ÑO/_>Q”Q¿s}}ßsg‡õûYùVA`;rÜ›á0;S¶Y«ÑPk5ïÍ›úbåbñÞ­[]'ËÐÅ\N.´ þpxåÞ=¶jV’ÔZm~u•}ª›åò!ÿ×NÒ`ý‚yñ‚5+‹Ùìøðð A躸«Öj£~?{=ê÷³¶òAŠ¡k/z+Ï7ëuÚ½Z¬µç¼ÀoƒÁ•…"úÜdòƒþÉI¶Šµª;;;DTH§¿úæo @Df‹åû¹9í(ì ¶+yE×: ÍÌ Úí¬Q«7ê÷OÍϳ×f‹ÅÌq;­–¶vÈåb´Ûy›­®(D´U­n®¯Ïf2¦Ë—‰È?99ätò‘jµššŸo5›²|ÈqÙk­ñÊ:ü»ºV¾úæ›®“ÍܽKDýƒƒþpXÛÞæpQ»Ó!¢ÍrÙ³–÷ˆÛÝÕs`ô<ìÏl±h}•¬-uïÖ-ÚMœÍíít"ADºª²Y­ß¸Kÿàà?KÞí¤}õ·¿ÕUUÒu~j}°¦Ë—C33Å\®Y¯¿ÚØØiµêªªmãðx´]FÜn+ϳ׬CUz·7UkÒ9쟜ÜåB6ûZU늲¹¾®Ò«»åѵ‘‘J©DD-•¬<¯r‡ÇÃVí¥9ö-d³Rq»9® i]èkùüÞ˜ìúØ ²oƒv»…ãò©Tgg§R*iŸêVµZWUÆÌoü~ùÝNxc }ºuÚíüýûþ~`­:"òÞ@@r¹Öòy–¾l˜•¾××ÊóŃÓwÿ½ycåù®aZ†tý:ñ6Ûµ‘‘!‡#>1qœ2Ùöú%¦¾¾#÷’\®V³iæ8‡Çã½yóÁììñÎสÃ!Çõéx¼˜Ë :­f“õE“\,&§§Íg„Q¿ßd2ý8>þ¡çðQ!}ºý£Ó)f³DtP”n+Êæúú<|Ⱥ™b.÷ãøx1—Ó/<ܵ‘µVÓE.ÍV+±k«?½|©}И9îÕÆ†¾2Z³˜õÜê déux5йܿúú_ž=Óºj£ò_z<¢ÑmEÑš¿úZu©”JZ­XÿüÃqäq‡\®B:­ÖjfŽÓ†¿iº>¦Ïl&¢•…ý 5í²ý ÝnåùZ¥Âzž‰¨¦ûþ`$\÷èf¶Xx›­˜Íê/ôæS©ÍõuÖNÍܽkåù®”etOÔ)ƒD4»;CE«ÑHNO?Úo*‰|*¥E¬ ©µš–(ìJ§¶js}]¬$‹?Žo0`6dYëª=Ä Ý>ätjGߪVµÄ{µšM­Âì‹ÅÞñá{ë½y³ÕlÊ…XÞÅáñè?–å^I"]>£ÿHÙíal¶¬N»}Ì[Å>:´}á“VWÕïAb÷ü, Ó¢xïÖ-ý -7oßf ¸Íryß;UÜ¢( úû”g¶XB±X:¿!fŽc}°lôtçÎT¹üÝð0»XÛg69¬©ç 6ž?ú|l—!—K»îËVý8>^H§‰hs}ý·Áà‘mqo PH§ãÚ…á!—ë8ƒÃKKl0­&õƒïÙÕ*LDó««Ç9®–ÐûžBhf浪²b‰¨ÕlÞ¼}›ÝqŠÅ~×®[AkòÎe2’ËÅ>ØV³Éþ—y¦Ýgoß¾íu༺:0zöÌ:0Ð늼§N»ýónƒR3ÀóZgï¶¢lU*Íz³Z££Z¦nU«úÍôö]µU­j]¬X3ÇiÛ´ÊÚZ³^ÿÒãÑo¦-ýöÛ~Ah5fSÛ`[QÖ=r8FÜî½åWÖÖˆÈ1:ªu ³ûmöŽvÖlÈòf¥Â Ô×¹ëŒ:ív«ÙÔ—SÌå:Ψ(~n2u­bX .¬®® &“©+J:®¶¯Uô·ùvÕG;ÙQQÔééZÞuúì ìƒí:ây4+I¢Ó)þ4Ÿð!}áý÷ôc°ôÍœüÎÚN»ýÝðplyùiL€¾çzžàÌé´ÛScc­f“·Ù½p!!}àtis’Ÿéòe6ÀíDwpœ#H_8]ÚtT'‚Ü…‹ w é `4¤/Àé:wÏ‘=S~ï‡þœq¸î pŠÒ‰ÄkU|÷©´þµÞf›ZX`w¸nU«ìéÂ]ô–‹Å•……V³YWU+ÏëwßKÿ`Ý!—K?®ö`]3ÇÝŒD´îÒ»ÏÜÕšˆf%‰Í³¡ú/ÍI’ö¤a€‹é pZZF1›Í¼xqÚÚiµXhiKj•J>™œÓP_WÕ›·ow=í`hw èd$²rïÞoƒAVÈ?vvÌÎN-=}º7ùò©Ô½[·nÿùÏ6‡ƒm)¹\ìv^–¯áÅE³ÕúèþýäôôÀ¯ÍJ`ùÊVmom¥ãñÎÎ{Šûö íõù2/^°©3ÄPhN’Þã^a€3é pZ–¢Ñ!—똳N~8ý„Mƒv;wõênÜÐ?õá7~ÿ¾“:mÈòʽ{ßÏÏë£×ž>ýnxx)YÜ3c>™ü­îÙÃ47÷»¯¿f3FÉ…B(cíÝÁLæ† ¬=zš™i5›åò?WÙíj­& ,}ÕZ­{¯B•¯=éèøÏ®8¾§e³\Ž-/koé­Õ÷»~?7§Í/±­(K‘ëUæm¶™TêøYΞ•ÔétŽÜ²¸²båù®;|L—/‡M&ÓÞíõ'EDŸëÚÓO¸LËf´Ö?£÷ g²çè¹E±N#}á‚Á¨+€SÁ4¤å¨\,æ“ÉP,6¿º^\Ü,—µGI.W]QbËËó««VAˆOL°¡F­FcjlÌtåÊüêêüêêN«ÙïÚíAÝ¿oåùÑ=¥ßk³\Öžs çöz÷gjÄíÖ/gÒ·ª;íöVµÊN=ljˆ¼€O´ IDAT€~úëJ©ÄÛlÿ{ Qd Ü­jujlÌÌqú[„ã÷·šMö\B€ m_€S±ñü¹>ÒÔ­-ÒžÕ£ ªb.×j6µËœƒ™Ì´(²þ^ö$>­‰¼ôôéwÃÃùTjßÉ+6××oì¹@ŠÅômå®Wú'àù àƒs¹ÿÊfÿãáCýÂüýûìÉu¼ÏJR«ÙœÝ=51’ …t<ÎÞúÃaýÆ,×þŸÿ1¬ÀH_€SÑõ¬½!‡£˜ÍÞ„!—Ë{ó¦6˜õ*²YýÆl!+!HèW­åóû¦¯•ç½Á uvväB·Ùær¹®G-…b±þÁAííe]¯òë÷º±§˜Ëý8>~óöí®náÐÌLhff[Q¦ÆÆ¦E±ëÊ1ëfÅbl4kâk]ñ²Ÿ˜èììtõ„oV*˜ð.¤/€FÜO3wïn–Ëìo(ó[• UJ%ýƬK–= W¿ÊÌqÖî½1sœW¿ñû£>ßÂï?÷îNýƒƒûŽº2sÜN«µwù†,“®ó¼Ë¬$±VïAWdû=½XÿÀcÉåj5›ó««ZMØ7­ª#n÷ËUÌf1Ó$\lH_ƒ˜L&–1­Fc)MÇãÞ@Àáñ³Yý5ZV™9®ÏlÖ7õ1vˆA»Ý >ˆF¯}ñÅqæXõûóÉäÞ‹++›åò¾©XûõOžèoçí´Û³€~hwõj]U;Ž™ˆv5¸ï]LzWy~·ÁE‡QW§bÐá`WfV’¤ë×Ùk³Å2òÕWìõÃQWU6D‹‰ø|Ó¢HDG­Õ´™§Z†týzWGôAB33CN'ËÔ#7f ñùô³ ºÞÝ1SzÉHd³\ž_]ÕG/ý£ÓQk5}/z~iiÈédY«=å·+z9«µ®ª¬Íèd«Õ—Ï‘'pŽ í p*œ¬k¶JwîL•ËÓ¢xmd„]šr¹ˆhÄíþm0øãøøÆóçWy¾R*iÑB33•R)êó¹EÑÔ×WÌfõÝËGšÍd¦ÆÆ““{oØÝk~u5êóIׯ›9nÐáØªT6××ûîtTL«Ñ`畜šê:ëjþq|üµª^åùWj­6¿ºJDÅ\ns}ÝÊó,ƒÖ²÷r¡Ÿ˜`§©ÿ˜ÊÚ½{73À€ô8ìr©6MD¿ d^¼(d³•RÉ*ì¢/Ûr.“ùê«çÏ+¥’ÃãƒA­ç6S.çS©µ5"ò‡Ãu#ïûÝ~Að‡ÃN»=ÀóßÏÏðüAµ´ÛŸ( ;ÖV¥býmÇzŸ›Lû6ˆÙoo À]½Z\Y©”Jƒ‡Ö Í]½úýüüAG_,йœ\(ì´ZÇÿÃúñbÏŸé2àbøìíÛ·½®œWWRÏžYz]‘3*H¼ÚØ8NÓqCbËËð|ˆYINi÷r8pÝà´„ffvZ-ýM8©t"1är!záâAúœ¢›‘H^÷ð8‘V£Q)•"úS¯+ðñáº/À)r{½]ƒáøÌ žnÚ¾FCú é `4¤/€Ñ¾FCú é `4¤/€Ñ¾FCú é `4¤/€Ñ¾FCú é `4¤/€Ñ¾FCúíR¯+çÛV¥ÒØÞîu->i;F¯«'†ô…÷ç÷ûKét¯käp8z]8™ÏÞ¾}Ûë:|ZpÝÀhH_£!}Œ†ô0ÒÀhH_£!}Œ†ô0ÒÀhH_£!}Œ†ô0ÒÀhH_£!}Œ†ô0ÒÀhH_£!}Œ†ô0ÒÀhH_£!}Œ†ô0ÒÀhH_£!}Œ†ô0ÒÀhH_£!}Œ†ô0ÒÀhH_£!}Œv©×€sL–eY–{]  p8|åÊ•^×Né ï/ I.Q4™L½®À'­\,ö÷÷K’ÔëŠÀ }áƒøøÁ:0ÐëZ|Òõz¯«'†ë¾FCú é `4¤/€Ñ¾FCú é `4¤/€Ñ¾Fû"REQ”C6R%•Jår¹C6«V«FãDÇ®V«ívûðmÚívµZ=Q±pJ:íöÖ¿Ûв}èŸÐû"’$é  BEq¹\cccÉd2ÿêW¿r¹\{38—Ë û|¾ÛçóÝ¿ÿðmîß¿œbeY¾sœ& |Û€3+ÿ~ô€ßÇ9Iš»¿ƒÆ8¢ç9‰Ñ‹/XûøÙ³gÍfsoÎ¥Óéo¾ùf}}ýð6ôé™íÕ¡?¢jµF{] € 9Þ`°×µ¸ŽHßZ­æñx, {ëv»c±X³ÙÔG]£Ñ`‘ìt:ïÞ½{ä!«ÕêAIY­Vlù5cörr =EQô¥R¾¢(ïÑ0=h/¶ü¤Ýõš­jõ ~à®åÛŠrÈ–[Õjëx?‡#nwhfæ˜%À!Ž~Æ‘ªªú·@ è—$“I"òz½¯_¿f¯’Ëåâñ8{ír¹ZED‹‹‹^¯wo ¢(Öj5­„L&‘ «««v»=‰ ¶Çq™LF„®¢ªÕªÏçEñÞ½{<Ï?}úT„}ËgOMM5›MV ÃáP¥P(°BØAÙ–‚ ƒÁ™™™®½ˆH[Þh4|>Ÿ¶Üf³iE‘ÏçcKù$á—ŒDdÝOˆ[à D”N$^mlÑóÇ­<ÿDQ¶ªÕäÔTk÷‡Í jñYÌåÒº_:Þf[<ê§.H³Ù'ŠBDú’Í÷1OàpDÛ7‹e³YK©Tjߦd¡PE‘ˆØS©Ô¾E5x<Î.³r´\—ey||<³U.—kzzzo£PÅV«ÅºÁWWWËå2ë/ N§Óét*Šb·Û‰D¡PX^^VååË—DtÐ%aUU+•ʳgÏÙ9î[>MMMÑË—/E …B…B¡Õji…ôéù|>AØIÅb±h4Z,‰hrrÒl6³å«««µZ-•JÙíöÕÕU"Z]]EôÂ!йœ\(Ä–—Ÿ(ÊEñ‡Ã+÷îi#žž?~lºråáË—±åe"Šú|VA`[†b±Ѩ\,Q«ÑHÇãþp˜­Š-/«µZþ€_Þ}%§¦ˆè§—/Ÿ(ʨ߿¹¾~ ç pa‘¾@àÙ³g.—«V«%“I6êJ–emY–UU …BDd±Xl6[>Ÿß·¨l6KDZk2“Éð<Ï^/--9ÎÉÉImí6©5Š¢<~üxnnŽuƒÛíöX,¶oJe³YQÝn7]¾|yuuµÙlêë¬ …Ün·×ë=¤üjµº¾¾žÉd._¾Ì>îßôÙ·í|@0\XX ¢V«ÕjµØxo»ÝþôéSíÜŽÔ?8è‡GÜnöÖæpQ»Óao­ìºšp¹XLNO›9Î*£~¿Édúq||ß-;oÞXyÞ±ßa«Ñ®_'"Þf»622äpÄ'&N·Þ sXú¾~ý::}”²ÞW†uÌ®®®jƒ¢‰H’¤r¹Üh4ô ‰Èãñ°ÎgÖä8®ÕjÍèÆRær¹®¼t8·nÝòx•:QÏó Ý>ätfv)¶ªUu÷—ŽãÛ¾ëëë]÷ä°»hVWW§¦¦nܸ¡ï€}øð¡Ûí–ey}}ýáÇ]% ‚àt:³ÙìÌ»÷Z,–X,ǵaYÎÝa@àùóçãããétšU& îm .--ù|>v¹”µ›Ù a"òûý·nÝbwe2—ËåóùX¤5›ÍX,Öõa_‡”ÏV ‚ ­ÒNÊårE£ÑR©Ôl69ŽÓNJ;_m/vïÓ¾¥±Ùívžç§§§3™ †=ÃAB±ØããÚÕ\« Xy¾V© ê@0f‹%‹¥ãñ‚`æ¸V³iæ86Kºsgª\þnx˜Ý,Ôg69'j¿†—–¢>ŸVòËU?ÿ3Þæ³·oß*в·i¨ÊT­V+•ÊÎÎŽ¾ºÝn«ªjßó O»óo´ªP(˜L¦@  ( ÇqZ×±¢(kkkD4::ª}Ø{v5º«?œUR_m6ZÊjµŽŽŽ½]#¶/Ÿˆr¹\§ÓE‘ݘ[.—µš?zôˆíÒuRFcmm­^¯ëû´9ûèL&ÓޔϠ«©gϬ½®È'g[Q*ì—EÍ »Ý¨_:íöϪÚíF£²¶Ö¬×¿ôxô«´å£ß~Û/­F£Ñlîp½½ås¹N§3*ŠŸ›L­f³ÿ<üÜ^<³’$:c¶ÝOÇgoß¾íuÎ6’YKßOÒà,@úžGGßqŸv#Ó¾«†Ž‘ÞËAú¾¿ßßë*À9†ô}˜š .*?~¶ qÄL“ðÑ!}Œ†ô0ÒÀhH_£!}Œ†ô0ÒÀhH_£a®+ø Ò¿ÿ;]º¸?E¿üBDù‰è—_úÌæKŸÞëzÀû««ªC÷X8.ôŸ8e¿üòKø4ãÙÉçÔ?zTY[‹-/÷º"§(>1aåyûîÓ‚á~d5VþY Ýn2™~÷õ×Ò;ý‚pg é g]hf&43Ój4*kkr¡°Y.7<üÓË—,€×òyÞfÛªVÙÆfŽ+›¾kkfŽÓ÷„_)f³ìµ•çÅ`pï*6:l[QÚÎ_K¥×ªªm£Öj^Ý.Þ@ YV³Ùg6뇎[y~íÑ##? 8>¤/œ]vûgUeýÌf‹Åx­j5êóåïßÍÌlÈòæúº•ç£>Ÿ¶W]U{µÓj¾Á¾÷(o+Êœ$µšM"âm¶«ÖBݪVëªjæ¸J©ôÏãrœ©¯ïCŠ€Óƒô…³«¸²’O&µN]FÑ·¸²båù'ºK¡DtCXï®1•´ Âæn'ó^uUÝV­ûWA¶‰‘ւߪVWîÝc«Ì§o ç2ö ÝnåùQ¿_Õ~X8œ5ugר(Qbr²Ónk ‹¹\]U9«•ˆ6Ëe·nÐ/ãE#Ç^|õkmkK*¥o³io×=Ò^Ë…ÂËEDj­fíêõ_umV‡Ç³Y.k§\Èfë»aÜg6³æ2ójcC{ÍÛlkù¼ö¶˜ËIׯkòðáò©¦sm_8ŠÙ¬¾×´Ïl^,ÌK(KÇãß ›9Ž­Ú\_ÿm0è X ³ÑÎz7Ãa¹PXI&à ÔÜl<Ÿ˜p‹¢©¯¯R*µšÍ…ÕUý©½VÕ«<_)•ÌdžS±±ÜWÌfS_ßkU­+Š•ç··¶íöÐÌL¥Tšsx<¯UUß°öJÒnܘÅk##ú‹ˆ¦¦ÆÆ$—ËáñtvvXÌëû à±/7˜Î>¤/ôž~Q÷ª@À1:ZÈf_mlì´ZƒÇ÷ssZ¯ò÷óó{Çôš-8Üu—ð©šËdйܯóç[•JW÷/Å–—×K¥J©¤_^Xàúû7ÖÖˆÈ-Šs™L>•2]¹ÂÖfÊå|*µ–Ï:±ååß}ý5[îözÿòìYqeåÕÆ† ¹E1ÿ>[Õ/™/ Ùl¥T² B(Ó¦(€3è³·oßöºp^]H={fÌSzbeq±”Ïÿ¤ëà=‘­ju|xøáË—Òý(…⻑ßÖž² pÇlU«ìm§Ýn5›ý‚PÌåšõ:gµjß3Øáÿ¼Êþn!ۊ® 9]Ã6dy³R1õõŠ¢þby«ÑX+:;;b0ñùˆ(sð5þš•$Ñé”p‡÷¹‚¶/œ9ùû÷‹Ù¬6žNÿ$ÿ¾¾×}s}]. "š“$Ò¤¾t"QÌfÙõ öBÛLr¹ZÍ&[•O&Ë‹n¯—ˆŠ¹\:×v!"íòÀ‡Ã¨+€ÓrÙdr:/›L=/ä‚Ù\_õû3år¦\¾ýç??üxûÝqï{³ÙP,Æv‰-/·šMmòN"úéåK¶jÈåJîv¤ãñ!—‹-Ÿ×]Èø(¾§¥_2åòÎ6õQ ¹`¬<¯]AE+ϳ™8WH§Ù ð·ûÉîm`›åò Ã¡ >üéODTÌåØ ö©ùy¶|Ðn×cøpH_8ÇŽyOsxq±ÕlŽß„iQdÏ´ØV”ºªÊ…{4Å Aønx¸®ªÍz}³Ré*üÚÈÈ)|špÝ.>·×ëöz·ªÕ¿–J•Ré7nüñÉ“Á/¾°ò¼?¶9úx¾¸²Ò«ªÂ'm_8ëþñ^÷iwuÚíd$²­(ìvêL¹<ätÊ…kÚ6··ívöÏÂqîßo5›ŽÑѽ“¨|”s`¾p&9jæ¼»ð'øq 9uUesKm+ʃÙÙãì5èp´šM™r±( l¹éòe¹PÈܽËÞnÈr«Ù¼öÅDä WîÝcê´Û‰ÉÉÍr¹_úá«o¾y0;Ë&McO²:……Ozž¡÷Ø Møi<ùT*ŸL>AÛˆÛ}óöí|2™O&‰È'ü ¯%>1ADfŽó‡Ãlw"š_]MNMÝØ¼æE6n+43ÓÙÙÑdæ8mxób¡0-ŠÒõël¹[_ãÿ |)roizž¡ÇØ#i_ml¤‰mEÑ?¡–ˆ´%ùTjC–åbQr¹$—«˜Ëé7ÛåYI’\®YI:k}¼²Ì‡N$6d9H°·vjv;HtÚmíDºQÌå¦EQr¹’‘ˆþ±Û{Ìðe¶X K³ÅrPƒvû¾Ã§) àC }á 1™LÅlVŸ¬K‘‹®µ|þÁììÊ‚ :éx<‰°mй› ÙáñÑÔØ˜~°ÌYóZU“ºi·åÞ­[ÿfµþ¬ªÅlvjlŒˆÄP¨óætýºÀ³’”ŽÇ¯ ‚ÃãÙªT¾þtàBBúB±^Äk##¡™³ÅÂÛl…tZ[«ÖjÚ3Ù³ƒ¼@xa!‹É… §t<þýüü\&š™™Ëd†\®cÒ1ƈÛÍn ÍÌŒ¸ÝÒ;úÁ´…tzÈéd­«ºªZa.“ñ‹…‚™ã–¢Q"ÚåÿÊfcËËá…6d×Ìq ¿ÿ}O >Òη(¶šM«¬¬Í¡ÏÛlZß [¸V(lU«ìq¿[Õ*û7òÕW›ëëg­ÿYÓ/CN§v;©\(ŒúýÚZéÎí5{Ð/mV*Vžÿ¼¯O;G« lžÉéþà˜0ê Îo ŽÇ× ÿäd!fÏ¢göN6ÔÙÙaÏ¥OÇãúåÇœz°WFý~6–µ€Gw÷Vžß÷c¥Tª«jÔç3²’pª¾pæ¸Eq-ŸÅV³9›ÉhË_«j×–¦¾>þÚ5+Ïg^¼8挃gr2ŸL²GëôDÔj4öž»7,\$èy†3G …6××—¢Q3ÇéÛ‚ú¾V¹X¬«ªcttà׿&¢µÝyˆ(HH.×”ÄÛlr¡°Y.kWµ‰¨®ª•µ5ím¥Tb3û³ÿ²'0Ó¢8‹‡¹œghû™À¦ñc#°Ø•ÑÍrÙë·©«ê¬$}ûÃÛ[[éxü·Á Ëf·(Þ»u«³³ó¥ÇS«TŠÙ¬[µ§ÖœœÕZWÕd$âôxØsÝýSS¿ûúk+ÏkWµÖ…Þ?8Èæ;dMo PH§£>_(ëüï|þùãÇ|ò¤'çÒz/‹ÒivgãðxŠÙ쨮]HDCNç³™]þôƒÚ” á…S__¥TbOMׯ:#¼€\(lU*\?Kß·ÛÊóú«ÚŒ?.¤Ó­f“·Ù–ž>Õšþ™r™ÝtDDfŽûã“'ì ðpN!}¡÷¼@Wðµªv]eØ´D{KØ;ÿÑY³¨ë'"6¨[?™±9ÚôL]æt—Àà¼CúÂÙ²U­nomm–˱åå^×åTtÚíŸUõÑýû¼Í†I”>YH_8[²ssj­6är±ZMŸÙÜ«*}\?ïÞ;´ôôi×*+Ï÷¢FÐH_8[ºzh\~î ÚíûÞ;tÐr¸>{ûöm¯ëçÕ•û·V«×µúÏÿüÏÈîÌçp. í ïÏd2åþïÿµ ôº"Ÿ´YI2_”K3Ÿ̶`4¤/€Ñ¾FCú é `4¤/€Ñ¾FCú é `4¤/€Ñ¾ðÑL‹b>•:é*€OÒ>µVëììœtÀ'OY€I8&´}΢b.7-Š’Ë•ŒD:í6[Øi·Ó‰D§Ýž•$Éåš•¤V£¡ß+ŸJI.[µ!ËÚòV£‘ŒDØr­"Úåt"¡m¦_¥ßkZåbñ8ÕN'úã²úL‹âVµú¾ŸÀÅ„ô8s¦E1_‡Ç³U©|7<ÌRögU-f³SccD$†B7o¤ë×µ–\®µ|Þáñ8<žÎ›7¿ûúk„­FCº~}«RqxˆFÙªmE‘®_­(çª üáÆ }N¤˜ÍnV*Z}òÉ$Û=êóÕUõ#LçzžΖb.÷üñã¿<{6âvQhfFr¹““‹…ÕUuÈåšËdˆÈH.×R4:—ÉlÈr«Ù̼xa¶XX97a³Rq»—¢Q"Ê”Ëzž[PIDATl¹©¯ïA4zd5–"Þfc%"®¿?ŸLŠÁ Vþ‘gÑj6çWWív"rz<¿ûúë“~Ú¾gËÆóçCN'‹^Æáñ¨µšöVºsG¿j³\&¢·û‰¢°hÜV}Gñf¹ìEí­ ZyþÈj¨µÚUAتVÙ?›ÃADk»a|œ³0s‹^V½ãàÓ¶/ÀÙR?tà’•çûaßUéDBëF6sœ¾§×Ô×§½>Nãu«Z­«ª\(ÈïÆíñ-ï= 3Çs_€OÒàlé3›wZ­C6h5{´˜Ë=ˆFoÿùÏŽÑQÏ7t!}Ò[}ív+χb1o p¢5VA8ükÀ'=ÏgËÈèh«ÙÔf~µ±¡5ëªZY[ÓVUJ%Þf#"¹Pr:ý““,zõ»¹\ú&l1—;hÔöÖ–öÚÌqÏŸko7dù† èÇ3q_}¥?‹V£Ñj6¹/À§é p¶ø''Íñù6dy«Z•¤ç?7§mŽÇ‹¹[Õj6§ˆhdtts}=ŸJmU«r±ñùh·É;5?OD³’´U­s¹t<®5úí·uUe«ò©”~ÕÍH俲Y¶J.ãfŽÓ_>œ7ÐÎbC–Y}@ƒô8s2å²UâQŸ¯óæ6þ™ñ‡Ã…tš­Zzú”5vý““7oßÎ'“QŸ¯˜É|?7çÅ׊BDf‹eééÓΛ7QŸ¯NûÃa­¨~Aøã“'uE‰ú|kkó««_}óÍe“‰ˆÜ^¯¶*9==äri£¦ÁÛlÚ5fí,âb(4äré/?|â>{ûöm¯ëçÕÕÔ³gÖ^WäS±U­Ž?|ùRKÜ«BàL™•$Ñé”$©×@ÛÀhó pž|”»f?¤ÜïDDÚÜp$¤/À¹1h·øc >°ODÚV úêwõêÊ Ã^XèÕ©äI’¤(Š~©ÙlÜýsö~ؼL&ó!…ÀÅVÌå~ÿm0èðx^«êÔØXlyyÄí&¢b6[)•Zͦ™ãˆh-Ÿ_#bo;;;v{jlŒˆå“ɵ|>S.Ñf¥RÌf‰ÈÌq¯ßýÁ8#.‘¢(Íf3 jK766nݺ•ÏçËåò{­à%??š™aog%éÁììˆî§îÉîOÑ¢V³ùÓË—¦Ë—Ù–ú·_zŸ/•J]ýÕªªf³Y-}E¹uëÖßÿþ÷Ó9#87†+Ïg^¼0[,ï±{ggç£W Àÿ›¾Íf“å«f}}= ‚@D‰D¢Ùl¾|ùòòåËDäñx|>_.— D¤ªêÇÙkQ¯_¿®­bîܹ“ÍfµNìt:ít:…Ý¡­ðÉøõ¯‰h­PІ+§‰J©´ôô©Ö¥|Þf“ mpŽ˜-·(Þ»u+HlU«ùTª˜Í:GF/M-,Ѭ$mU«²ÌÆ?‡b±S¯4œ éDâ¾ÁÃyöÏQWZwq.—Ñ®à6›M"òù|ú=Íf³¶oW¡­V«k‰ßïO&“D$Ë2‰h£…L}}•R©˜Íš9Î jãŸy›Me×úîŸÚ~AXzút)‰ú|D4är-¬®²Ø6õõñ6›'pbû\÷ ÏŸ?F£‡ƒõs'ÂAwî²lÖÓ‚Y399™L&s¹ÜóçÏm6›å½®óÁ…š™ í©GD‹ïö Ìíùñë„®mÿä¤ÿÃnU8mûÏ4ù§?ý‰çùÙÙYöV„®T.—+‘Hho{Ñn·›ÍæèèèÞ2m6[¡P(—Ëhøœ/ÛŠ"¹\Ûº;ø§E1ŸJ±UÓ¢¸U­N‹â A¸!éÝ¿ ùTjZ÷ËÞUH2aÛëwÑ d˧E±µû·…v{›ÙrŒ¹ƒónÿô½|ùòâââúúz$!¢ùùy"r¹\²,W«UQõ³s¨ªêóùdY–eyllŒã¸}'Éšššzüø1édÀÙ×ît6××õw‚©µË¿v§óüñã¨ÏçÅùÕÕ!—ëA4º!ËDÔÙÙQkµ} IF"r¡[^ž_]õ‡Ã¢Ñb.GD­FcjlÌtåÊüêêüêêN«Ù½à•N$D£þpx~uõÚȈ¼_·À9r‰ˆö~ìõzƒÁ`¥Ri·Û‹åéÓ§‘Hdbb‚ˆl6ÛÓ§OµÞcžçý~?[år¹´ê®bÝn7Ïó]#«àð‡ÃÞ@€ˆ3™Íry³R9ônþ­J…·ÙØ6ƒv; :D´’î*ÃÒÓ§ß ³)ÌŠÙìÍÛ·Ù5…A»ýµªn~ÀL|=w‰žŠY¿\„ÂÁ_6'''÷¶w»Še½ÓwîÜyïºÀÙds8´×æ=Ã0÷rx<¢QÉåt8~£{BF]QˆHßMDkù¼ct´®ª¿ÑÝ+á½yé çšÏ8j·ÛªªÞ¿ßf³á6_ÍÌðƒƒÅLF.Vîݳòüüêê ÝÞj6‰¨R*i[š9Î*{§?û3Á9gDú² ÃDôôéSgßÈW_±¹¸·eN’’SS™rÙÌq}f³~({«Ñ`S¡Yy~{k‹uSÑöÖVOª ð±ì?êêøfffŽ|–‘ÝnWEQ4|áãê´ÛéD¢Ón÷º"Üe“‰t·!ËuU=r/v»¶6hù¯º­är-üþ÷ìu¿ h7s;<µVÓžºÑj4¤ë×YG4›ÝL+£®à¼3¢í pJ~VÕÑè—Ö$‚ÓÐ/CNg:Wkµ7­Öf¹låù#÷Å|2ñùÏ«VKÛK …~'¢«<ÿZUÿ+›ý‡‰(43S)•ØjS_›ƒ…Ý>µ°0566-Š×FFXi§yƧîCÛ¾ð)È”Ëþpx«R¹b6g^¼ð‡ÃC ðü÷óóº0õûÙ*³Å’yñbÐᨔJ#££™rÙ ²-½À_ž=»b6WJ¥+fó_ž=óîÞˆÈôZQ^mløÃáÌîЪ~AȼxqUXiKOŸzu$8w>{ûöm¯ëçÕÕÔ³gÖ)d[QÖ=""1Ô?ìH[>úí·ýï^³ØåÍJ…³ZûLJ‡¾|©µ}Ù*S_ߨ(¾ß£“º þÿׯ?°€S5+I¢Ó)IR¯+'€žgè¥YIÚ,—Ù=*Ål6‹±6~ùƒhôæíÛÚãŒ$—«Õlš9ŽýW_š¶ŠˆòÉdxqÑ­{ª4ÀÙô…ž)ær›å2»Õ„ˆÒ‰D:÷éDB¿¼˜Ëý8>>är¹½Þt"Ñj6µU³’´¹¾ÎJ›•$"úéåKö¬…YIJNO#}àlÂu_è™B:mæ8­Ó8433¿ºJD•RiÈåÒ–{!§³˜Éì]%é&oÙ,—õO'ŒüéODÄæ/8kÐö…^rx<ú·,V[Íf×r"bc\»Võ F»­(uU• ý(uUmÖë§Wy€÷†ô…^úXOª1™LVž÷‡Ãú)‰hà7Æ=ÏÐ3}fóV¥¢½m57a«Z5sœ~®A"j5›lBÞfÓ¯Ò¦}`Û›ÛÛƒv;ûgá¸G÷ï·ö<|à,@úBÏø§¦6××Ùcb‰h)e—oF"úål8»Ä«ß¥Õh<Ø}5yƒÁ•{÷تN»˜œÜ,—û1½œIèy†žq»ÿãáÃt<žO&‰ÈÌq³™ ¹½Þ®åáÅE–£]»øÃa­uš™éììä“Im/6† à Âlðþ>ÊlD´­(D´·zÐr"ÚªVš]r«Zµp܇OµA˜mΠ̶q¡í ½wPÿð!ýƇLìŒ9ŸàìÃu_£!}Œ†žgø ùû÷M&S¯kqZõú/ÿø‡~d5À¤V«ätöºp2uïO–eY–{]‹SôË/¿t:¾¾¾^WàápøÊ•+½®œÀÿGG†|ÃüNÂIEND®B`‚frr-7.2.1/doc/figures/ospf_api_msgs2.png0000644000000000000000000010533013610377563015050 00000000000000‰PNG  IHDRïD¯0ž pHYsÄÄ•+ IDATxœìÝohi¾'ú_ÏmÎiî•ïuîjÏ–XÒܪ=‹Ì±sÛ0b]>­pÌAf”“š%ÍÁ8Ã8ÏÚCm]{ÎØD cÑ£ìF "2ŒÀý¢‚ÕD/82rF<’”XY©U*í‰õ·*‰ddÙÎqD”‘eÞåZU"*‹á‰‰AQ,kšS2²¬$ÉýÊ/ƒ;¹;ªV­&VV’²šE±V­²"UQôÝÝCC,¡´¹ÿMV*/Ÿ;w÷Ñ£¡¡–£:æ£ÀÚ~@D²,˲œ5YYYMµµ^G>Ÿ—eù ž°#UUEQd¯u]‡Ãº®Q¥R™ŸŸ×4íÉ“'å'ÈÜÚZ­Zýéùóa^’:¶:wr¹1¿Ÿˆ|SSD´‹e ªz;öÏͱ\Ð }w7 ±Ò²®7Ÿ$±§µCCw=êøà7ŸÍŠ"knQðÓOS\nÀí¶í·TBŸNû¼#:÷iéëëóz½áp¸X,íÿUišöÅ_Ñ¥K—A0ïŸJ¥Êår—"·ÛmlÑ4ˆÌ»U*•f³Ùr`£Ñ¨V«‚ °38Ω©)óíõi4õzˆŠÅ"ÏóF¼Ñh”J%¶Ýf³5›MŽãÌy$MÓ8ŽC›½gù¦¦X»{OÓ’Ÿ}v;6:#*‰…&&Øžµjµ¬ë™TÊ÷ü_ËÛSÊçYrÃ`NåäysÈ6=­×—‚Á²¦ÕªU"2àµjÕíõ»„íÙÓ´²®«Šbþ)ëzµ\~•[‚“éÀŠº®óßž¦íloO^¿nŽ’YVåÈ¢ù«)¨êOÏŸÿQ 0æ÷»Üî¡¡ Ï·f^ Ì韛s™šMDô!Fx¾K¾æ…BÁü,T×uY–¯_¿ÎÞ¦R©\.·±±Á‚{*•º|ù²×ëõx<¡PÈårA6 )ŠÂâr(â8ŽÍF£Á";ÍÌÌÄb±x<>33CDš¦moo'“ÉöÊmooÿæ7¿a»Åãñ«W¯jš&B$i¯(Š333õz]–evÑâþãþ™™·Û=11Á¶«ªzþüyv*"b•G(ïAüÙ³7¯^mih7 ãµ’H8y~îùv݇÷4íÌkÄÇÃs{½™ç}kJs—u½ ªFóüq¡ðýSʵ5'Ï/wú³ç]®|6;½ß_€õØéxé§õ:{ÁžV÷öŒ„O­RY ‡ƒŸ~jÃ/ÎwÆ÷yóÝÝ]y_8~òäÉÝ»wÆr"‘à8Îh§OMMŒŒ¬¯¯Q<?èaéîîîôô4{Ý××ç÷û"—Ë•N§“ŒŒþÇãy~fÿ¯S’$žç›Í&e³YQ[êÓñû #ÇÃó|"‘`oÓé´´Ÿu…ãÒ¬×KÅ¢ù?"öxGF++±Pˆm,¨êìø8I©Š2¸ÿÐÛ NžO~öÙÑԜՄ=„$¢t<þ¥,ûçæŒn/-±'·éxüÁ½{“¡Úï_Øl4–‚A#dûggw¶·Óñ8Õ*•ÛKKÆ©Ýî²®³¢=M3ùõ›7YQ³ÑˆÌÌìärGó}öŽHÇãó½(¾o›Öw°R©LLLÔjµ±±1c§jµªëº9àêûy‡#³Ðl<~$¢b±¨ëúÀÀ€±ÅívÇö;ÌÎÎ^¹r¥R©8EQæLú1§¹«Õª×”XdjµÚ¡î˜ˆˆ$Ib¿!ºü2€£Ô’ó%"Ö/™Ë-ƒª¢¬ß¼IDNžç]®¥dÒîp°G|¾ÉÉ–SÙÞåÚy¾ãÛcw8nllÄfgTÉÏnÜ0ÚÈD4àvGGÙë_ݽËÒýsÑh)Ÿï§û}À'/²&ù°Çó«»w++éXŒˆ<’´³½Ívöx&¯_OÇb¬È?7WÛÿŸnza¡Y¯EvŽ»±±q·ÿîhÖëúîîq×¢›Ö¼¹ÃáØØØ5òL ¸víšyO›ÍFD’$íîî·ÛýÑG±Ö½Qz–Öe™=1ýé¹¹9EQTUÍf³ý2€#³izˆÒŽ¥#ö4­Ñl==Èôh´ÝêÑvkJærµJ¥R­škÈü•ß?–ŠÅ–¢.‡°[c‡”ŠEöMÆÌE£æ³™¿6Œ"Çq{èÆ‚:޹¹¹íím#“îr¹4M2¹uëÖ×_­iÚ½{÷<Ãá¨ï'òAàyÞH§‘ù5I’$ËòúúºËåzÙJsg~@JD¬÷ËáÏàp88ŽcOMÍ) èYg¡=êõ»ÃÑ¥†‹^áÃY ”7D$b~FbŒ³eElKPç%ɈÂt5“™—¤ (.ƒ-CvÓñxPƒ¢ÈRU†‚ª.ƒAQL´ ±ìAGöÏÌÌ\¼xQ–eÖódvvv{{;¸Ÿ …B²,Ûl6²V¼OûÃyZ8óö©©©©©©ö?l{±X4Æã˜ÇøD£Ñh4j AjowéL²°°Ð2©ËaêÓRmãõÐÐPÇÛ|…_‡÷F¢0By»¿ÚOÚúúZFouä„/eù”Ý>âõ{<Æ“•|6kç8ó;Ç©Šâ«U«æE<<’tž‚vwPnz¨ë_[ÇRUU«Õêaz³¼B}#s‡næï‚ådò”ÝÎ:G±žQ, ³NAFç"¦ßn¯T«e]ÿÀ4*팩‡^o:žÕ*AÐuýúõëovšÆCRUõÊ•+º®onnýÕàX°>?{š–ßÚbùñUE±sœSÚ‡qÕ*'Ïã³È4V«gO4ßØØ µèßdz±±ÁžâKà0ŒújÓµï•JÆëyI:-sÑèA8#Õ½=6¾Á)-C‚¢èöz§ì—Y_7ÆñfÖ×_ñ6ŽÊñDóãŠã½S0ˤRÍfÓoéÎE‰Hä‡^/2à‡404ääùÛKK´¼\ýö[eävwc—.݇—‚ÁK×®íæóiÓÜȧAUÞå:30°W*©Šâ‘$"š½q#8:ÅŸ-/Ðß///תU6¾w2úÅ… §yþ‡^ï²Ù#öʰö¿ÄÊJ³çƾŽR±x;>îZœ0k÷ï÷Ûí+W®(‰Äd(4(Šlnµ>›mpd¤Ï4>Ñ)¬èŒ üzs³¬i቉ÂÖÖO.^d{ÎE£¾@@I$ÂJ"á Øô>v‡Ã¸Ëž¯Ý¿Ï:ì{|¾_onæ³ÙðÄÄãBáÆÆFwkÁ*Ïp°¹M:6rKÅbŸÍfÉIº‘ö±vFÌczu”ÎBòùf²9ëíñùÌ+.™ÏÀú ¾ðBf]ÎÖƒÍá˜]„²®gd9#Ë‹wî¬\¹²vÿ¾È‘H>›Mær,ïYÊçY';ÇÍ­­q- Ó¼Ø9n)™ì˜ÎÖ“#¢ðÄïr±ŸüæÉ æ%ÉvêÔr2yAüsséXÌÎqµjÕèwÁN›5feñ¼Ëic¶©iNž÷›šÆž8™§?ÌÈò€ÛMDõZMU”1¿SÓ65Í)±ý±‰HDU”Å;w65-ɶ¼?n¹ ± °nll¬*ÊäÜ™VªU*úî®1wX:û~y¹‡뵚15cxbÂ)ìÞ§o‡Ãj&sw½ Ñz‹G’ŒÇM{šFD“¦A Æ“ÒÙ7jÕ*œ‘e$±o[__tcÃ(êAv‡ƒOao·…ˆŒŽƒ¢È^Ûizš}lò#Ÿà›šúQ °Þ6«;¼ãiÞ297§* [á!ùÙgvÓt€ÓÈa¶q'Ÿÿð£ˆHU”R>o”–u}'Ÿï¸êf/¦§û‹în¥Óæû2Oð{f` ¬ë¥bññW_•u=hšÉ½fš}€A4‡ÞÂf'gý|wr¹éÅE£È¼\œ¡Ùl–uýGÀiÓœf•Ÿm}}¬¯aûו/è厕ÐãÍáÕ=}úôëçóݯ?{–ˆþ±\þGScûðÔK¥ï¾ûî_”…ìöÓ´ý­j6›•r¹g‡ÎÂa|],~÷ôé{Ïž=;îšÀI%œ=[¯×é}ë¶ ¾ûî;¢÷-|ƒDß=}úþûï[ùñ]ðÝwN§ÿ„ðê8Ž»#ù p¼ ªº¾¼ŒÑCV€h`ˆæV€h`ˆæV€h`ˆæV€h`ˆæV€hpJozB€ˆæoÞAHD"쵚É\„ËçÎÍKÒáÏ0/Iíߥè¤ërkæ:Â<-oïrÙúûÙëõh”w¹nll|Èó‡?C½V{…¢“ÎvêÔqWáC4xóØzž†³Ãý¼.RïÀb§¯ÑàÍ›—¤á±1ÿÌ̼$ÕªÕŒ,ç³Ù¥dòŒ ,ƒ;¹ÛmPÍñË(ò´e:íiÚZ(䑤ÄÊ ­Ý¿æù…FÙl-'ÞåZˆÇÙjvéxüñW_²ÛUE±s\r¿>†D$’‘eöšw¹f£Qvæ¥`ðìǶ¶ôÝ]Vùƒ.ADj&³²%¹í7 y|¾îŸÛR0Hû1Ý|fÿÜ\÷7xôÝÝf½ND¥%;Çy$inmíŒ ÌKRYÓïܹ±±1½¸¸“˱øEDó’´“ËÍ­®ÞØØxR«ílog;¨¨Ñl>¸w/±²â‘$$µ‡òÙñqÛ©S766nllÔkµàèh³Ñ ¢f½¾“Ë•òy_ ÀB2c¡|zqñÆÆÆâ;õZmy¿’eMKÇbDä <’T«TZ.š˜`{Tõ.¸½^V䄨ü|­Réþ¹•5­¬iìõìøx½VcuØJ§ËºþÒÿ ï´ÍÞ"–`±õ÷ ±Hú³åe6#üÀÐ’H°àµ§iîÝûõæ&k½$“Fû½K㟛ë¸òõZ(dç8£í¿vÿþOÎK¬¬ÌE£DTÖõö¶<ó­®ûcEé¯×h§3FÉÜ”6.‘ŽÇý33úãÇ? þ¸ZÞµk_Êr¥Z5/uÝE:g'´õõчÁÑÑÃø.C48"¶¾>KÅâ^©T-—kÕªãˆ(¿µåäys"Â#I¥|¾{ã:`)Q}wט¯nç8ã@'Ïw å´KÅâÓz}'ŸÏg³æRVa†}µt5ÙJ§ý33ì fOÓÍæ²Ùo_²e]ØÚ²s åDdw8Ì×…ŽÍŽN,R…ˆì7`ŠÂ,-cft‰éRô–dR)–ˆ·sœSúív–ûnǶ›Ã=;„ˆö4m9d;ð.×é¾9bá~;o¢9À)¨êúÍ›×ó#1b´”ÝîŒ,×*#a4f»½Ð˶ˆ™ÄÊŠùñl"a"ÛÙ9®ßn7wà1ê¹ Ño=bíëR±¸~óæáë0àv«Ï÷ :è x pDôÇ1¶<.ÚÙíõê»»Æ0×Z¥e‰}w×)Fªäϧk^hÄë-ëzAUÙÛ‚ªâ)è ¡mpDÆ$)‹…&&X7’Œ,Û9ÎèÓ‹‹¿¼|ù[]?ÍóùlÖœ&îRÔÅr2ÅÙñq·×Û¬×YgÄŽÏKÍÎÂàÈHbe¥Z.“)‹bþqðÇŠ-,ä³ÙðÄ„G’lýýìŽØ“O$­ß¼yÊn·õ÷«ëeMsòü^©tÈ~÷ÃÏ•+WXLUQœ/3öêÝô?ýÝßýÝq×N*ùîÝ¿øË¿ü×~xÜé9ÍfsÐífŸÌ??}ú ýë?üŸûúÜ^ï?ý÷ÿþÿ¨ê©ù/—îÜq{½ïÿÉŸüùààŸüéŸþù_üŹ¿üËÿ÷ïÿþþÛ»ôïÿý¿ýë¿>ÅqÃÃDtPÑÿb³}`³‰^ïŸüéŸv¬ÆßƒÿüôéÍåþ¿ú§÷ÓŸ†>ÿœmÿß}÷¿Ùíÿ—(tT³ÙÌg³ôÞ{ûþÃÌòr³Ù<-ýÿâ_üóÓ§ÿÇÀ«•±3½ÿþãB¡öÿð×û·ÿ÷íÛl»{lìý³?cÛÿ­×úüszÿýÿý_ý«ÿüÏ»|næó{þæolýýÿõ¿ü—ÿñìÙÂüýögìc|éŒwÀ?ìíýýþÏï={öì¸k'•èñL.-±T—‚ª®//#ÓG§Ë\’˜üà5!šÀÑ‘——ê}Èf>8âúX ¢9–ùÈà BE+@4°Ds+@4°Ds+@4°Ds+@4°Ds+À¬[ðêD§ÿÃNçqWàV)—ëß|ƒh¯NUUu=8FÑÀ 7°Ds+@4°Ds+@4°Ds+@4°Ds+@4°Ds+@4°Ds+@4°Ds+@4°Ds+@4°Ds+@4°Ds+@4°Ds+@4°Ds+@4°Ds+@4°Ds+@4°Ds+@4°Ds+@4°Ds+@4°‚÷»p‚­®®*™Ìq×(09ùÞ³gÏŽ»pR‰ϰÏ÷ÑÐÐqWàöu±XÈdÐ6‡×òÑÐаÇsܵx×2äͬÑÀ ͬÑÀ ͬÑÀ ͬÑÀ ͬÑÀ Òñx:7¿—¤X(tø3T5‰¼lôDs+ØJ§·Òiö:“Jݼzµ^«=©Õ†|>#Ë/[½ó´XÁR2i¼®–˃##É\îëGÑÀRö4­Y¯Q©Xüçm}}D”I¥ªå2]ºtFÌû³¢A·»ýT‹JÅâÀÐP&•j6›¾ÉIv~èï‘$Iccc333÷H¥RŠ¢ìîî‘Ëå ƒ>Ÿ¯eMÓ‚Á ÛíŽF£/[ƒx<¾µµ¥(ÊËW¾çhš&í Éd’½ …Bìïß¿/<ÿÿÀ± Q2—K~öÙN.GD±ÙÙ1¿ß=66;>NDvŽ#¢ÛáðäõësÑ(íišQ”‘e¶Ó¥(<11(Šì6›Í75u”· ]ü€ˆvwwëõzÇâH$rùòåS§N@ @D.\ˆ´=ùì³ÏªÕê«Eäz½Î¾*Nºx<>>>Î^7›ÍjµÚl6‰HÓ´›7oJ’Êám[N&}€ã’¹œff-â]®MMKærÉ\nòúuuÿÿÓµPÈÎq¬è·™OÒ¥ˆˆvr¹äÇ›š†PÞS^i‘eùúõëæw0”eyaaÁ¼[.— ²,§R©©wõØü844¤i{Ýl6yž…_-¯o!ÿÀfëX¤ïîN/.²×¶¾¾1¿?‹½°ˆˆ<’dw8Þf­áU¼¸OK¿ùí7@£Ñ0¶¤R)" ¢(&‰îg‹Çã¢(Š¢ÈŽ2°Ô„(ŠÁ`°R©Û+•J(b‡„B!£(«ªªªª$I¢(¶ü\PU•-‰hšf.5N(IRæ€UÐTUÇã•J¥c•Ìwß寧j¡P ¢H$¢ªj£ÑˆD"FCUÕt:ͶÇãñH$ÒrQv#Ý?4€Wfw82ëëAQ ŠâA0æ¥b±¬ëgŒ=]ûùñ.EŒíù˜=âÑÜårɲÌb"Ûâp8úL>Eq¹\‡crrr{{»KlE1N{½^·Û½²² Ùv]×ÇÇÇív»×ëÍår£££l{¥RÕ4Íëõz½Þ|>o¥Ó饥¥¥¥¥ááa·Û‡³e2™óçÏ‘×ë- ãããò~ç*MÓŒ ‚Ð1kDDù|>‹ŽŽUš˜˜`EFø ¯×‹ÅDQl?ƒ®ëápX×õöíóóóÆ[MÓ®^½êt:úÄ^Ó¼$¥c1·×û³ååäǾ@€mï; ÁÞ½zÙ 2-Š¢H’$Ër8æyÞårI’dÎ¥T*•ÝÝÝÅÅE"òx<<ϯ¯¯{:--‰DªÕê£GØ7ËåZYY1Ú¼«««ìáêÀÀÀ… ŠÅâÐÐ,ËÇéx¯×{îÜ9VDDæ³±ª²Ñh4$÷;lƒÁÜ~W­P(är¹Œ=Ïœ9‹Å€£íg£®ë›››íUúùÏn¾®×똘ˆÇã333ù|~ww—å ŠÅ";ÇãéïïW…m×4M–eUUÙG”H$FFFL‡·dOÓÜ»÷«»wws?xFœ<ÿ»tz`Y×ßíwWïR½ìÅ™EQ>|x÷î]Qwww/_¾,I’QÊš½ÆI’rôrÍf³.—˾SSSš¦±0Êó¼ÑOÆçóñ<Ï^/,,g+‹ù|Þ|BŽãŒ³y½^ö¢R©T«ÕÉÉIc7óëÝÝ]AŠûÜn70Ûç! IDAT™¾ÌªR.—“$ɸîÐÐÇqéCÿ­ ‚022²¾¾ÎÞ*Šâ÷ûy,ÀËbq¹ðà{«f2ªé¯Ý¨ŠRPU"*¨ê!‹ gª¿¹ÃᘚšbMòx<~õêUÖ%"Y–u]?w³®ëFi‹ááá—­Ÿ¹eÍ™ºIµ0’ûÕjU×us®ßx],u]W¥%|ÔŸç…×z5~¿?‹KI™¿^Y¿ÝÞñíôâbbeå‚ ïr±·Uöx¦šõúÊ•+lOÿÜœñ¨³Kô¬nÑ<‰È²ldÌ™™™™X,Æ" ªªº®ß½{wÀôÀdvv6NwŒæ-yd#grÊWWW?þøc‡ÃQ,Í_Ùl6z>@¯‡††xž_\\|Í^7/ý[°0•J=xð€=ox³0«¦6Ê´©Ë™ojÊ75U*Ç:¢˜»ÎE£sÑ(DD~Óÿ¶m> wtË´\ºtI×õÐó÷°Œ0kŸ®¯¯ó y¥Ré…÷Ó’Ê`•4^s÷`ÿW'«ƒ /եĜv'¢F£Q­VÝÆÑ½ð$,ióR¼š¡¡.} nTu)‚^ó}Û¼P(´ôîXXX!ܼy3ŸÏ³Ät½^W…ã8Öô¦VVVÖÖÖZž…Þ¸qctt4 ^»v­^¯/--]¼x±û@—Ë•Ëå2™ŒÓédýLèMãP(táÂ"ây^×uóWˆQtíÚµr¹Ÿ,>f2Žã¦§§ÛO*IR­V3÷I'"‡Ãqÿþý'OžLLL\¹rÅív³Fn¿Ëå2ïér¹X„õ}œŸŸŸ˜˜øê«¯>|xñâEö,Tó7Íf3Nâóù~ÿûß?yò$›Íò<¿ººjìæóù6775M›˜˜˜ŸŸE±ã3Û.UÁ|‚ <|ø=ššbÕ{üø±Ífa‡˜ëưÎ?»6¼š÷ž={vÜuxÃ"‘È¥K—ŒXß1û¼X?z ÌÙ"z<“KKÃ/óãÞ¸‚ª®//[p~óB¡`ŒÞÌd2²,÷N+¸Ñh‹Åp8ìr¹Nz(€žbÁq£Ñh(2FJ’Ô;s¤èºÎ†•Þ¿ÿ¸ë–bÁh.BÏίkž à ²`¦à„h`ˆæVðÇh …ÞàDÛñxܘû»»–néGFUÕŽÓá¶`ó•¿‘+6 þ€·äûh®ªj>Ÿ©Q‘Ý¥ÓéÃÌ, oݺõ¦.úRòù¼1ïylÌT÷}X˜6fÁ=H__ß©S§óð²¾æKKKGu¾mMŸ{²èº~ïÞ½ÃìyãÆ Y–[V2x}ïQ*•ªV«Æœ!¬ 1¶¥ÑhèºÎ&;d³²†|ËD‰•JEQ”z½Ø_ߤ½¨¿¿_’$6‹kÌÖëuódŠÆÉÝ:Ò4í‹/¾ "ó°O6–Íf“eÙívwü©‘J¥ÊårÇy²séŽ×eÓ•J%›Ífldr:æÉX‡Ëå ‡ÃÆb'ZµZýéùóÇ] þ£Þ{öì™$I§N2â 9i´šÍ#ãA$IQc†cxz*•ZYYaSW«U"â8ŽíÓ^ÄV’$éÞ½{lI#ÖC\Åjµjìi,HÔ‚Í”ËvÛÞÞ6Ö¡ŽD"Ùl–k\Ý iÚøø8«Û¡Z­½¿º4›zŒ­ÔñºlEÓíím¶ŠP2™,‹lˆq¡ ãë}ÖèuŽ‘ý½à#ûwww?ùä“C¦(Ê£G’ɤ¦i.—˘/wee……ø\.·±±a>dee…ÍÕ•Ëå4Mã8Ž_EQxž,”³…==zÄöEѼ„¦!‰°K°ÝîÞ½{óæMcéäíím¿ß¯iZû`ËP(Äqœ¦i¹\îÑ£Gæ¢Ã\:•Jµ_—Í©»¶¶FDkkkìqvvVEv!µÍ?bÜn·®ë/L²¼”°yÌËMtg^Gmvvvww·R©°^7nÜ`Û‡††Ì³Þ¿Á4}þAr¹œÛí6NþùçŸQ*•jÙ-›ÍŠ¢hdf¦¦¦FFFŒ<ϳÐi^‡šÙÝÝ5ž ôõõ™—p;Ì¥‰Çq-×5OžÎ‹Åíímóòu¡Pˆ}Jì­ <Ï¿ðÉ*ÀKyé‘ýæ Øß߯ëzµZe±ÉœkfI"²Ùl¡P(ŸÏ³4‹®ë###-§Õ4­}™7]×ËårËžÕjÕXÔP«ÕºW»ýKËív³ Óyi¶Fyª¬–¥”öQ\Ù_‚ËØ³Z­š?Ÿ×\À ÅKGóŽa¨ûâ £££Çy½^¯×;44ÔqFC›ÍÆóüÜÜ\ËóIcyå×Ħ?¨è—×®]ë~Zv¶;wî´|&Ý×ÌxM?è昖ðm´¸‰(ŸÏóŸÙï.¡f2Æ™ t£8nh›œ0U­U«É‡íû‚/ÂN>ÏZÖe]ÿõæ¦gÿ1Xbeåg7n­é¥`ðöÒÒp.×ý$‡±þ(XÞŸ¾t^’7?^ˆæ'̰Çc$¯÷4í›ç' rò¼ÊKÅbY×9§³´?Ÿþð'Ÿ|)Ë{šÖý$/T«TjÕêÏ–—-Dóã†hpò$"#³aç¸r§É™‰èÙ,%LÓQ‘“çÍæáOÒQ¥Z-ëú¦‰BÏzxKÍN˜L*u;¾þ›ß¸ÇÆØ#Í ŒOt»"¢-Ó¤o‰H$(ŠÍFãð'éÈîpØ9.cZ{+Ó¶1´ÍN˜á±±›W¯¦ãq—Û]+—×£Q"jvjÛ$ݼzµY¯ÿÐëÝÍç3²ì‘$[_ßáOrÉPè.œæùz½Èfw,1-艆hpÂøgfª{{éXŒb1ÞåúÙòòv6Ëú#ÚúûyÓ’¼D4ÚúûóÙlF–íç Xÿ–.'éÂ|~Ï÷ëÍÍõh4#˼ËuccC6=…£÷Þ³gÏŽ»pRaF\€^ÀfÄEÞÀ i€?2w[lÁ»\«Ï¯+ =ÑþhzaaÚ´j œ È´X¢9€ šX¢9€ šX¢9€ šX¢9€ šX¢9€`Exu ÂÞË,?o‰kpó´À«s:ŸÞ¹cáq‹¹Ü/~üãßÿã?wEºÁŒ¸pâT5‰oÓñø¼$ÅB¡c¬ÀqA4‡l'Ÿ7¦oͤR7¯^­×jOjµã­À±@¦,¢Z.ŽŒ$±:%¼«Í¡‡Ô*•-EiÖëœÓ雚2íiÚÖ_ÑØ¥Kl™ù–R¶Bq©Xüçm}}GVg€L ôŠ‚ªGG·Òé|6›XY¹ µJ…-ƒ³ããùl6ŸÍþøßü›öÌxò³ÏTE©U«±ÙÙÌúú‘×àø¡m½âöÒÒ (.'“DÔl4~rîÜ–¢øgf2©ÔN.wccc`hˆˆ2©Ô//_ñzÍ}i–“ÉD$’Ïf‘iwÚæÐ+úíö\NÍdˆÈÖ×·©iþ™"R ;DZPND¾©©Á‘4ÀZ m½b6]qá‚“çí7æ÷³h^«V˺~Á”+/cÈ@Dsèg!™ËíiZ~k«°µuóêÕêÞÞ\4JD? .]»fÞ¹Ïf;¦jô(Dsè‰HÄÖß9#þ™™X(¤*Ê\4Ê»\eM32-D´ z$©½g À» ysè …­tšõc©U*¥|žw¹ˆÈ?;»³½½ ²Ýb¡Ð—²lCÛüݶ§iAQÜÓ´ã®HAÛzŪ¢ÌKRpt”½å]®UE!¢açWwï²>‹¬èWwïvœ¦ßn?²ÚÂñj4›;ÛÛfó¸+ÒCÍ¡‡°ð]*Íy"òMMù¦¦XCÌœ`™^X˜^X0^aMz2-ÐsZB¹áŒ W~\‘HAU·éx<Q³Ñ`Ÿ%"‘ (ÎK’±+j6O²§iKÁ ;$“J™¯•I¥æ%)(бPÈ|x­R‰…BAQ\ ŸÖëoí^O*Dsx±Œ,ïäóÆÛ­tz+&¢ot=#ËAQüV×Ý^o½V[¹r…=üøF×o‡Ãߘº“'ÙÓ´Ùñq"r{½§á——/sa.ƒ‰••Ó‚àözKùüOÎc½V©GGKù¼Ûë%¢•+WŽîæOdZൔuÝ#I¬/©ÿÚµŸœ;§Èr÷Ä×Ö_Ø9ŽûeØ4;UýR–ÿÓïÏž‹L/,E1úóŸ/'“ká0c}‘ÈípøíÝÔI„h¯ë¯ü~öÂÖ×gç¸îÏ9¬ŸÒð'Ÿø¦¦Ø7íäóNžÿ ¿¿T,²-NAØÉåˆh'—óH’q)0&CÑŽšojªÙl¦c±\.±²b縹µµ¡¡|6[ÖõðÄDÇ£lýýÆk»ÃqT•=1Íàøgfü33µJ%¿µ¥$቉MMp»kÕêæ½È›xòÙž‚Âqj6Æoê.JÅ¢¹o s0­U«‡<Êè|bÌoLD±Ph^’ˆÈîpø¦¦&C!¶/fþ“˜—$6plPUE1¶gR)L×ÓÑŽSúÖ­ƒ~V›…'&Ò·n½òUj•Š1”^ ïr©Š’I¥ ªzÈs`hÈÉó·—– ªšI¥B¦èAQ|pï^")‹U]FíÇ"»ã™TªT,ÆB¡÷î±tùìD´ –ŠÅL*•XYyKwzr!ÓÇiÐí>Ìn¾@à{v¤Èòæ==«ŠÂú‘/8ûñÇ¿úŠˆúl¶Á‘ó$hNA0Ük÷﯅B+W®Ø9n2R…y|¾_on®G£ìI¦1¯=%s9ãBvŽûõæ¦Çç#"»ÃÁΞ˜°sÜ yy“¯™½÷ìٳ㮜T¢Ç3¹´Ôq=S*ûl¶ŽC~Ú|2{šÖh6@d¨U*•jÕÁqíOÃØïtó‘HF–[²±‡¹P1—ûÅüûüÇî•8^U]_^FÛÞ 6"{m縥d’ÅôyI:-ª¢”uý“‹Ïq¶T,ÆfgYBÖÎqNAh>yÂÆú__ 0½°ˆD ì ìäFOç–‹E,”³ÒÙIÌ""¶ñˆ>€·ysxóXT]¼sgSÓ~ûè-ïgZ뵚ª(¾@àî£G³ûQøû£fg‰è·mjÚ˜ßÿ¥,×kµö“?¸wïI­¶©i›šÆžŒ±Çk™Tʸ覦ùçæÖoÞÜÓ´é…_ àäùMMcQ;<1á¶Ûôââíp˜-xp¢!šÃ›Çâ5ËÀØúú¢µjÕ<ÇôÂÂÀÐ9S*w¶·—’I[_ùgf>¹x±ãÉ<ψQðÓO˺^©V‰èÌÀ€nÎHû¸Ün"jŸcÍ.bdi}SS? ÖŸÿ^8‰i7¬T,–uý‡^¯±…¥¶«åò÷o;üC6ëäys|?;<œÏf;^ÂÈ•Ÿ'ϳ×CCŽKÇãÍz=ŸÍÔ…îñW_•u=(ŠÆ–Ãw¶èeˆæ`j&›Ÿg ÷1¿ßf³ýòòåöÝšOž8yÞmú²°DsxÃX/ãÝ|¾¥»ˆyXv»A·ûv8¼§iFóüq¡ðR×]F.èù(fg‡‡õÝ]ócO5“±;/u-€„¼9¼y¼Ëµ•N£7Ù`“1Ó”Ií†=žÁ‘‘å`•I¥Ü»÷RmI˜|Ñ6ÚˆY Œ*›2{~¾}g€ÑÞ<Ö@þɹsAQ¼ ;¹Üôââ §Iš[[cG]%‘øQ ðR^\|pï^PÙDÄ~"ÑØ¥Ke]ÿɹs‰HÄîpL/.îär!(ŠÁÑÑ–©YN(Œ‚W×}ôP©XüC6Ë9¾©)ccËjpÍFã]7çdŒ±?ó’T¯ÕØ|Ö¥bñCž·õõuÜßx»§iù­-"“$»Ãa¾+ÄNBDl²§j¹üC¯·Ë"Œ‚£‡àíj”-ãBm}}l¶Íâ;Æwƒ¾»kÌgmœÇØß|óÉÍç?è5íOöôJ·УÍ¡'œÞåZ¹r…EpUQì797wÜõ81Í¡W¬*J&•bÝÒýssþ™™ã®ÀI‚h¯îéÓ§¿û⋯1Aùá}ðÁDôÝÓ§ë««o𴯦º·÷´7jÐEYן>}Ч ðꄳg¿ûv$?ñ¾ûŽÞG£zÚÓzÝf³áÏ^ÇqÝgÄ€#Àú´ ¿9€ šX¢9€ šX¢9€ šX¢9€ šX¢9€ šX¢9€ šX¢9€ šX¢9€ šX¢9€ šX¢9€ šÃ»+ÏKÒA¥µJ% Eñ‚ E1 5öÝæ%©ËIŒ}ÒñøëV÷øÎHKÁàR0x²®²§iAQÜÓ´7uÂc„UžáÝÕ¬×õÝ݃Jƒ££vŽs{½lOUQTEùí£G¶¾>cŸL*¥ïî–u½ ª]V»ÖwwÏ¿ÙÊåù©|$1ñÍ^¥Ñlîlo7šÍ7xÎã‚hÐkê®Ý¿oÄîɹ¹àèhbee.5vS…w¹ì—Y_ïÍŽ¢9@ÍzˆÌÍp»Ã៛3ïS«TôÝÝéÅE"J¬¬Ô*»ÃÑ圙TJI$ˆHšžöMM™·«ŠR¯ÕÜîéÅEóEÓñøV:MDNAðMNš¿0XQ¿ÝXZj¹qÔ˜ßa ªª?~Ì>½¸Ýæï$c‡Ìú:kùšLÇãüÙ³D”^[«×jn¯wza¡½ò“¡ÐA7Î.í[ …ÚÏÐñº{š¶õÅæÝÚ·¼ò§W«TÖc±R>϶Tíys€Ýî²®ÏKRAUþ™#Æ‘"ËD䛚b¡™½=HF–UEq{½NAøååËFš{)L¬¬œ·×[ÊçrŠâV:íözÝ^oóÉ“Ÿž?oT&(ŠéXÌíõž„ðÄDY× ™JÇbFN'ŸOÇb±ùy"ú¶-Y¡f2?=žˆÜ^ï€Ûm>p+¾½´t{iéìðð€Û};6ÒÖ±Pè——/³Ê¯G£µjµã½³KÏŽŸóûóÙlP»_×f³ed9“J'Y … -gž—¤–O¯V©tÿôj•Jpt´”ϳÚÊ•+]þÕN´Í:öx~u÷nbeå§çÏ;yÞÎqn÷äÜœ¹õ‘åÁý¨4(ŠYno9ì·ª(ìuYÓ¶ÒiÿÌLAU¿”åÿôûß³fãôÂBP£?ÿùr2YPÕZµš|øÐ¸âAØÉç‡=žL*U«Voll ш×Ë"%"‘Zµj$÷èõ†'&2©û¾)ëú¯77=>_{õ2Éäåd’½µõ÷gL_Næs‘ª(DT«TTE¹þ›ß°o8ÿµk?9wî Û/ëú¯îÞeÕ“¤àè(«ÕA×µ;¼Ë¥$ÆãgÐëœJ=¸w¯åÓ‹Ì̬*J—Oo-&¢d.g|b·Ãც}² mЙojjSÓî>zä ˆHU”à訚ɰ҂ª–uÝøî‘$ö,ô ³±–`Ëë|ÞÉóô÷—ŠEöŸSvr9"öx65£=M3®KD…ìÇB9ÛÓÉóìu>›µsÜ7ºÎÎFDvŽS÷¿Eœ<ß1”Ѫ¢!µT,~kj쳓¡|d¿òù­-"2~¬ØúúŒï¶vNž7â²Ýá0jÕåºIªU«¬­ÍéæôûGFÌÙ'·×Ëkwùôvr9©’ŸÞI‡¶9@7CCCCÓ µJ%41›Ÿg1³¾Nm¿ÓÓkk/õ,4ŸÍ–u=<1ѱ4‰ d;Çé”öNvŽc/X®£å„ývû k²§ik¡P—>†úûÙ‹j¹ÜRtšçêpbÔÐP¯Õº_×75•XYÙRÿÌŒ’H´UtïÜrЧGD¶ý[ ¢î:NDs€.‚G’Ì í‡Ûë5ÄN.7yýú_ùýÆ¿K§UEyá³P³·»V­nvŠJ™Têv8|ý7¿qV%Vä„.AÓ)Fk÷ð–ƒA"2²7æPxÎélÙÂwÔžRgß1ݯ둤­tzL’jÕêRÛMõÛíì+¡]—O¯{=O4dZ:à].šÍ<ûá?97ÇZîì¿É¹9"ZÅ^ê*DÄR"̼$±gŒª¢ ŽŒøgfX02×dø“OŒ+2b¥‘¨1E1‰t¯F©XÜÙÞ–¦§ìMK¦¥#÷ØíŒ‘ÒéȨp³Ñ¨U«Ãcc/¼®4=½³½½Û9îŒ)3ÃccæÏˆ ìG@—OoPÍõ̤RåCÜ쉀¶9¼ëZ‚Ý Û=ìñÌF£³ããÁÑÑAQ<ÍóDô¸PxpïÞ¯îÞ%"%‘°s\Kœ=¸S¥½óßA|SSJ"ž˜˜^\<30ð»túÁ½{¿ÞÜ$¢á±±›W¯¦ãq—Û]+—×£QÚoT²£B?[^&¢Û¦г7nGGƒ¢ø³ååúûåååZµ*Ý«104ääy%‘830@D¿K§[¾:²;IJ¬¬Ñ™/nÝê²sY×ͶsK¸w¿îAÙÉåZú†2þ™™­tšöƒþþ/nÝbE»zì#Z /]»¶W*±ú[¢9¼»lýývŽËg³-‡=ž3‚|øp-.k 1¼ËźO°†Þ˜)ÇbðƒëÑhû¸PÞå2çjmýýÎý–f2—c‰ÈÎqFŸÿÌLuo/‹Q,Æ»\?[^ÞÎfž…ì(–µŸ^\,y²Ú5‡U]_^FÛzQ©XÜ+•vr¹Å;wŽ».'¢9ô"yyYßÝEüø;$Dsx»¦ºŒ’=²+/ =¬ÑÀ ͬys€·k^’–S)ó¼Ûo\:o­nëï7µQ³ÑHwÝÆJo ªºÍ²Ùb[ŠÚeR©j¹lë–T¬ˆs:[æÉê~»‹–£–‚ÁÙ7¬4›ÊÛƒhð%"Û©So5”ÑV:]«VÍ3[ÕªÕ›W¯“Í~£ë·Ã᎓²½§iËÁ q’|6{;6ÏUkÆ& 3vNÇbÓ‹‹FŠ"+ªU«‰•cZÚ.GíiÚìø8±£”D˜±vø“OØ$·oðã²*Ds€·¥V©dd9ùðá\ËÎqÉç‡Å/ƒŽþÿ£ IDAT_ÊrðÓOº1¹U;6û•yBðL*õËË—Oó|{w”ÈÌ £‘HbeÅ=6fw8Ø$3F‘1áx÷£ÖB!£þÍFã'çÎÅB!6A›ÆÀ˜¢º@ÞàmY ‡y—ë¹.R©D$’ŽÇ[æóªU*éx<‰´ÏÞ¥¨»K×®ÑÖ_¼pÏt<Î&)4WÕ75õÉÅ‹íËý[ ÎxûC¯·¬ë•j•ˆNó¼y΃·Û˜æ°ËQúîî€ÛͶÛúúìWÊç=Çü~¶t‡¶9ÀÛ²“ËËå4Ùñqsžanuõû©ÒS©ÄÊ Ûž‘eÞå2 ]Š^h¯T"¢Áý(ÙÅV:Ýq’ƒ®Õò#€]ˆMubnÈ7 ¶öæ ÅR>ßl4l}}¥b±V­úL3…IR:ÛÓ´ö‚¢9À[Áæ¹eÓÆQúÖ-óTæó’”I&=>_­RI¬¬øçæØœ‚¥b1<1‘ˆDØúµ_®V­š'ƒlÖëlVXó“ÌߥÓ0M1fëïgg®U«]&äz!%‘øäâEs¨5²ð¼ËuÐdëæ£–“ÉyIúɹs,o>(Šæ{d¿:.ñ fˆæo‹›FîÂÖß_Öõt<ÎúrÍ^¶6´±ÛÀÐG’Ø£]ŠÚ/WÖuc‡²®ŽŒ_sú‚žŸŒðô«6{Ù3ϵû÷ÍíçözÙ7ÊR0ØÐ[ŽJD"úî®G’lýýßêúN.×’(·s\Çœ˜!šÿÌÌ㯾JÇbéXŒ­Ížò=.ʺ~¡S0íRÔnpdÄHe,ƒ;¹wútË>skkŸ‚¶ä©‰5À‰(ùðaK§[_ûÊá]®_^¾l~Û~{VlþîaS·<ö›í CºA/û¾m.IÇqÁ`ð ý4M“$IA$Ib‰éH$b>$ ¶¼íÞÞgç,‹Æ™ý%IÒu]–eI’Ø–ùaâ¦Q’$e2QÙöÙÙY]×EQd51WÛ|~£†Æ™Ó2ÆvQUU}ÁGЫö4íÁ½{Ó‹‹Ó ÃÝá`Ý]ˆèŒ 8yþwû]Ô‰ÈxÝ¥zÙ÷Ѽ¿¿yyù |K¥R?uêÔÆÆÆÆÆF­V›˜˜ "·ÛËåXd'¢\.×òÖݩۓ¡ÙlÞ»wobbB’¤ QÃá0‹žKKK<ÏK’´´´DD¡PH–e–?™››»zõªÐwwwçççYD–$izzšçùµµµO?ý”ˆÌÕþÿöî0´,Oþé÷mv惼¨YÝ¥Ä*l²TeÒ ³–· #Py¢0þ 0ò¤bÐ,VÀnÚ!ì!‚¶°vGZ«Aa,ph…@"2¬@ [Á"¸¾tåZÍ &2¯Ívè*hC‹•`ÅF`}h–€ßg\S‘mEqK®v{½n¯w»R13 ˆ¢!>‹ÍÅbô† Bˆç`„Ô¡i _UûUû|óp8|åÊ•¶à®×ë„B¡ na†ŽßAˆD"„:|nµZñxœ²³³³¸¸øFN±T*©ÿWåp8 ]\«f1Ã0v»eYº½ÙlùÉA_EûN !V«õ¤çÐh"Ÿ µóÐWÚÓ\­·hGÖ Ã˜L&mµ¡V«Ñ­×ëD"tbøØØMsúôMM,q¹\Á`0•J½ê|›h4º³³“Ëålÿ"Õ·`4éG”*‘H|øá‡.—+Nk¿š¨ï Ÿ±†b,cF[mp¹\;;;ê-?µZmttT¿3 C1›Í4pc±ØÏÚl6–e?ÿüsu sòÒËå29¨¡B$IR+-v»]QµÖ_«Õâñø÷ßO·g4w[Ћ´oê½¼%GßÙOë-êÓ………B¡@sÍh4¦Ói†ajsçæÍ›·oߦOyžO§ÓŸïÆ?fY¶P(Ôëõ”fu:€þôÿþó?ÿs«Õ²Ûí.\P·^¸pá¯ÿú¯?üðCµÞâ÷ûßÿýr¹üŸÿùŸÿøÿxïÞ=uçK—. †ÉÉÉ¿ú«¿"„üå_þåÅ‹oܸñ£ýèðë ƒÁàr¹~ô£iÓÖ~øÁf³Ñ3áyþ/þâ/þæoþ†çù¿ÿû¿ÿÙÏ~ößÿýß’$ýä'?ù—ù—k×®ÑCÚNÞ`0üÏÿü=íŸüä'ÿðÿðôéÓ?þñ<ϯ®®^¸páùóç´õ—¿ü¥Ñhüÿø?þñG}G¿üå/ÿîïþnww÷ü£ËåZYYùÛ¿ýÛ7ùŸ\GÒüýÏ~ö7š9púþsw÷ÿû¿ÿ÷½ýýý^Ÿ œU¼Ó9 t1Þž²$­†Ãøí!=@šèÒ@æz€4Ф9€ ÍôóÍáä.pÜnw¿ o•uhèè;ûºa±X>»wôîФ9€ Íôi Hs=@šèÒ@æz€4Ф9€ Í [ÙDb^^µ NÒºÕj6•Wm‚Ó4€nM-,¬Ér¯Ïކ5Τl"±žÍB,çžœ¤+Y–%IùææÜ¹ÕXlÐnŸ‹ÅèÆüêjU–-çÿì³ó×¹“Ê’´U*M-,¨OiÏv—ë-½MèÆægŸç׳Y»Ëew¹ZÏž}råJY’![¥R6ÏÏB¾—eBH>“ùäÊB ÜÙ«Wéž:é`«TʧÓô±”Ï«=S.«Û¡W068cÊ’Ô¨×S&³™n縭R‰Ž¬«Šò»µ5§ÛM›’‘ȧKKêh:ä÷ß …FŠÅÎtc5û…ÏN¥èÓyA@ݼ·æg̈ө¯weù»§Oµ­–U£|»R©* c±lW*:öòåß§Ó»²Ü¹“—jÔjzýÓpXÝâDš÷ÒàìIF£jeÃÄ0Õc~Ïï…!$‰h7ZXv¯Õ꾓#Õêõª¢üØhT·œ|•woÒàŒÉg2÷‚ÁÛ_|a£—4Ç56µ†ìv ËjË)'èäHf†±°ìͦºEûzWAÎI‡ÏÌ MáF­vÜž.]"„¬‹¢º%úy¾µ·×}'G2™Í&†É¯®ª[´¡'068cFÆÆîܼ™M$¬v{£Z]Å!­£†Æ&³Ù)wnÞl5›?u¹vJ¥|:íÃÀ@÷g2øÍøø9–ý©Ëõ‡Ba«X|SoNipÆxffê»»ÙxœÄã¬Õúi8¼Y(Ðùˆ£‘µZµ;ÏÅb£±T(äÓiø}>:¿¥C'hûwºÝ¿[[[Åòé4kµ.åriÍEQ8}ïíïï÷úà¬âÎÉP¨û9mð6”%i5FÝ@Pi€?ÓN[lÃZ­Ëš ªÐoæðgS ê£p¶ Ò Hs=@šèÒ@æz€4Ф9€ Íôi ¸^ËÓƒŸ(€^¡†XCNnyyYÌç{}@·i ¨›èÒ@æz€4Ф9€ Íôi Hs=@šèÒ@æz€4Ф9€ Íôi Hs=@šèÒ@æz€4Ф9€ Íôi Hs=@šèÒ@æz€4Ф9€ Íôi Hs=@šèÒ@æzð~¯OÎ0I’$IêõYá½ýýý^ŸœU¼Ói¼pÁl±ôúDÞiµjµùÝw›ÃkqONŽ8½> €wZY’VÃaÔÍôi Hs=@šèÒ@æz€4Ф9€ Íôi »²¼+ËÚ§ÙD¢ü*«è´öö¶+•Wm‚þ;ûô ì÷BRÅ"!¤,IŸ\¹baYÃŒ‹]ö½{7ŸN¯i>ºi‚þñ>!¤òâ§.Ã0f³¹ËãeY&„p÷ÆÏŒR©TX–xS’$}óÍ7“““oõUÞ¶1G}¼U*YXáû®yŸ211¡(Šº‰eY†a€Ûí~éñ~¿ŸR|Ùç&“iµZ333¯tr>Ÿoaa¡ón’$­®®¦R©Wêœ"ÂÎÎ!Ä`0x½ÞW= x^üã21Œöi£V«Õë„A›­í@ÚtxûK›Z­Öù·3Œƒ“ùS¥åöíÛžƒÏöf³¹²²2>>¾¶¶ÖM w#‰ø|¾7ÒÕa¡Pèdîìì,.."ÇAæ²,Šó‚ ììTeœãÜ>ßÔÂÝ¢î9µ¸è>ø7¯mâù¶lç8ÏÜ\6¯*Êí/¾ð¼â Þž?¥¹Ñh´i>N'Ïó±X¬-ÍiMÆvÔgµJ–åV«Õyµ7ƒÁðªUšnΡsç•JE=|ppPÛDOþÈkµZ½^?\†:n;Àij6ôÁ²(&£ÑR¡@kè!¿¿Ùh¤66Lf3}šŒDhš‡ü~egg)—´ÙÊ’™žV{ëÐDÉÆãS‹‹Ì¹sX ¹¯{tjjêÆjðe2™H$¢¶.//¶W*•ÙÙÙz½NŸªEŽãEI§ÓétšÖÙ€(Št7†aR©ÔK3㸹¹¹l6Kûg&—Ë™ÍfA677é¹\Îf³×¹ lj¢¨(ʵk×vvvE™˜˜ „ȲÜvòmg¥ÖdèãX,vx;Ïó'¨ö¼U#—/»''MCs,»uPÝ*=ss´2ât:A:øÃéÐDây7¾ÑöŸcg(ÚívBH©T"„H’tãÆ¹¹9Y–eYæy~~~¾V«µ211ÁqÝgqq1 æóyBˆ,Ë,Ëú|>åÑhTÅû÷ï˲üäÉrP|©x<‡eYÎårõz= BDQt8‡C–e›ÍÖ¡óF£!Š¢Ïç{òäI,£g•ËåèYÍÎΪ'¿±±Q¯×=P„F£±±±!Ëòýû÷EQL$mÛs¹\±XTèn¯÷Y,Éh4øy>ŸNÓíÛ•JUQ¬v»ºçÏj­š¨s,ûöO^Ù±iÎq˲Íf“²²²âp8Ôk˜tǵûÓ€S§^¯×çó©cX­t:-‚Óé$„ Ðhîæç%yž§GÙl6«Õ*uÉþ¥/,,Øl¶Ã_<ÏÒÒ}l6›†i4„Y–>|‡i!ÅétÎÍÍ †¶í6›mqqQÔŒ_úA<øøâÅR¡@¦¦œ‚Ðë3‚·åØJK¥RQÅh4Bh1ñ" ¶«¾þúkEQ´û¨U -:¢EQ{¸¢(¥RÉù²«ŒŒŒ …WíœyñB¿ÖÌÌŒ$IétZQY–777!¤Õj±,«=7ú©–ÉdÈ¡ °Š¢h‹ò½Õ¨Õ$Qütiiê`bX2¥m6 Ëî”Jꔕƒ¿šMÐÏŽMóíímBÈØØ}jµZGFF´;РW={öŒeY—ËÕùõZ­–¢(>ŸOÍ.—Ë®ùZwb¯Ó9Ïóôb¦ËåšœœTcš~;9òµ¿_—ËÅâK(ô«F­VÒŒ†x~=›uONZ{{ëÙl7MзŽMsZ¦ ZvÐÎûÎd2m922²³³£Ý'ŸÏ[ýš;-à°,ÛÖÛG}ôšïäu:Ïd2›››_}õÕáïF£±mÄFE¹uëÖÍ›7].—º]–åjµŠ» ˜Ìæ!ž¿ ÒoÔëNAX½sgW–Ïs\8•òóü¯†‡M C›ÔKš oµ×ÍkµZ¥Rñûý>T¯éMMMmnnÒÚ!$ŸÏ߸q£­ÒB§“«—kµÚüüüÝ»wÕÔA.ÏóétZ½5D"‘V«õFÞÏé\’$µLd³Ù‡úFöööÒé4˲tûìììÞÞžúZ'žùðšB©TèકçÖ-õq8•zðä‰Ý妦Ödy.{ðä‰zoQªX\¼ßîr­„x Ð}eIJF£¯Úýi ëÙìz6Kç3™;7o6gF÷=l•JùtúU› ¼OI$„™™u«$I…B¡T*¹\.»ÝîÔ,Ä‘H$šÍfÛF*Bnݺ500pä‹Õj5Q×××?øàƒ>úHûЇÏºJ¥ÔÇõjuÈáHayËwÌû„l6K4IÊó|½^g†R(‚Áàµk×DQ¤­ÙlvssÓáp_ü·"IR0$„¸\.ÛQ‹êE£Ñt:Ma¦Ñh‹Åx<žËåèÎmç'³+Ë­f“²]©\`YÃÀ!$ŸÉÔ«UBÈØõëç9N»?m²ÛwudÓv¥2h³å3™V«åžœ43tƒÓ×¾†b&“©×ëjÈBòùüøøx"‘ÐFm½^¯Õjf³Yݲººêp8677|™D" ¿øâ µ“½½½«W¯NLLȲü&ßÀ;)ì÷BRÅbêóÏ·ŠEBH|vvÌã±Í^½J11 !ä^08yûö\,FÙ•eµ)ŸNÓ¨MÁ‰‰!ž§/a0Ü^ïi¾Mè =Í«Õ*!D;¸v»Ý>Ÿï믿V·8Žz½.Š¢6ߋŢÏç;.Íãñ¸ÏçÓî?00‡§§§óù¼ÛíV·WÉÉd2ôÜ®_¿Î .*•ŠÍf«ÕjtÔïóù´0´°Ól6}>ŸÁ`¨×ëœfTB;´X,^üs §RÉh´T(ÐJ˼ °VëòÁwëx ‰"Mó•@ÀÄ0t·ÖÞoªC!d«XLml˜4kÐÚ¯‚Z,EQ¢/^¿N¥R)MUŽ"Böà’ !D’$BˆËå:ò5*•Š¢(“““mÛN§,Ëj”Ó´Íf³…BáÊ•+ƒËñ²,s‰D …B¡P¸xñ¢Ú411FGG …B:¥gBÉ©Œÿó IDATd2£££´·ÑÑÑ«W¯úý~õ|Ô#‘ÏóµZ­«ÿZgÍB"ÎdŽlRvv„©)úØ000æñtÓDq ¢¼µ§¹×ëõù|Á`ã8žç€šZSSS´ØBŸ®®®ò<Ük BˆÅbyéÙär¹b±H‡ùj¥>X­VY–iÓíÛ·Õ&Bˆ(ŠOž<)‹´b³²²B·Ó˜¦‡är¹z½®2;;Ëó<í…z=è•Élί®úyÞÏóã'üílW*UE9?8¨îi=¨wh¢ FãÛ?qxeGÌPL¥Rß~û­Ïç#„ˆ¢xåÊš}Ú}hÉBMÕb±øÙgŸux–e¹¯½Æ0ŒZáÑä‰Dæ˜Á!Du ÏóFƒ|WXZZ¢Ûm6›úaS©T677µý ÏA—æ!Û]®OÃáÔÆ†Ûç£Û †ãéÐýìèùæÇ-,,СëƒêõºZ¦P©ÅÂÚn·+ŠR©Tº?-£æÃßl6Ó±?ÏóÇiæm{²,K”J%zàqMÓÓÓÜùùyEQ´ƒw}Ø•åÇN-.N-,Œ8&³™Nw!„œç8 ËþM½T}Ü¡ úY{š ‚ÐÜQFj/‡ÃN-¶¬®® ‚Ðá5.]ºÄ²,Ñ6ÇE_v™ ñxÜår…Ãá ßÁàâd ˲÷ïßÏiòdÚ\¿~]Q”|>¯n¡µ{rðE¡ÕjÙT«Õl6»··÷Ònú–Ñd2šLÚ§ôÁÔââV±8Îqã—O¥¦ !4¦§œ‚™žç¸Èô´gnN=¼Cô­öŠ@`||œçù©©©ÁÁABH¡P¸sçÎíÛ·,øÒšx,»zõ*ívll¬Õj}ùå—Á`Ðçó¾§T‹ã8–e?~Lçæóy±‹aÇq×®]›ŸŸ'„X,–»wïª÷C©MƒNª™ŸŸ·Z­ÇÝ¿ p&,kþ.¦ÔÇn¯×íõnW*f†¡Q´3Äçb±¹XŒÞDñh¦×´†DúÕÿC1™L¦ƒOr·Û½¶¶F‰D"ÃÃÃÃÃÃétzii)‹Ñ´;Ó!¹¶ô¡–§Ûp÷èÑ#“ɉDFGG/^¼H»U'>j»m³¸¸X,i;•J-..’ƒbýaj'¢(ò˜Íf:†^z­×ë<Ïã!Ð1}¦9!$‹ÍÍÍщ•o£^ÐWt›æ„³ÙÜ}qàLÃoèÒ@æz€4Ф9€ Íôi ÖièìÃÁÁ§;;½> —¬V=ß=o›Édúׯ¾Âª[½õòU·à¬@šèÒ@æz€4Ф9€ Íôi Hs=@šèÒº•M$æáU›àt Í [­fS9fµMp:æÐ­©……5YîõYÀѰ†"À™”M$Ö³YBˆ…ãÜ““t%˲$)ß|Ü;·‹ Úís±ݘ_]­Ê²…ãüŸ}vžã:wÒAY’¶J¥©…õ)íÙîr½¥· ÝÃØàìñóüz6kw¹ì.WëÙ³O®\)K!d«TÊÆãñùyBÈ÷²LÉg2Ÿ\¹B¡;{õ*ݳC'l•Jùtš>–òyµçoÊeu;ô ÆægLY’õzjcÃd6Ó-ã·U*Ñ‘uUQ~·¶æt»iS2ùtiIM‡üþ{¡ÐH±Ø¹“n¬Æb¿ðù©}:/¨›÷ÒàŒq:Õâõ®,÷ô©¶Õ²j”oW*UEa,–íJåOÇ^¾üûtzW–;wòRZ­Q¯«[œHó^Cšœ=ÉhT­l˜¦ª(Gîö‡B’ŒD´-,»×jußÉ‘jõzUQ~l4ª[ξÊ;€7ipÆä3™{Áàí/¾°ÑKšãš ›ZCv»…eµå”tr$3ÃXXö‡fSÝ¢} =« gŒ$ŠC‡gf†¦p£V;nÏ —.BÖEQÝ’ŒFý<ßÚÛ뾓#™ÌfÃäWWÕ-ÚÇЛœ1#ccwnÞÌ&V»½Q­®Æb„ÖQCc“Ùì„;7o¶šÍŸº\;¥R>v ‚a` ûNŽ3üf|üËþÔåúC¡°U,¾©7'ƒ48c<33õÝÝlßÒ¡“´ý;Ýîß­­­ÆbùtšµZ—r¹´æ¢(œ¾÷ö÷÷{}pVñNçd(Ôýœ6xÊ’´£n ¨´ÀŸi§-¶a­ÖeÍUè7Hsø³©…õÆQ8[PiФ9€ Íôi Hs=@šèÒ@æz€4ÐÜ '÷Ã?üŸ/¿|zð+eÐUEyþü9ÖP„“ûðÃxþ\ûsb:óC«µ«(—††z}"üÐl ŒÍáäL £ïq+Åâo>þøßÊå^Ÿ@'tE\¤9ô…l"AñĮ̀[Ê’´Y(l—Jv—kÈn×~f$£QBˆàóµýÜeko/{÷.!ëFÁ;WA¡/¬g³ëÙ¬úÔÏó‘ééíR‰R*>¹re^ÔÖ|:}/\ÇÛ:ÉÞ½{/”%éðnc¶Ø"‰¢SS[x× Í¡…S©ÿö[·ÏG‘Dñ“+Wü<¯­«BÆXèÿu{½½8Y€¾€4‡>užã¦RÅâš,ÿöÁƒF½öûµ;˜ÌfÃÐb‹˜Lñ|Π/ Í¡ïÌ ‚vú !Äíõº}¾F½Þ¶§Zl‘DÑ=9yz§ÐæÐwš†vî9Õj6ïI‹-ùL†¢ã[Rº4‡¾3lmnúy>ŸÉlW*Û•J2]½sçðENZlIF"(³¼k¶+•qŽÛÆŠoHsè F“Éh2ÑÇN·ûwkk„d$rcxøÆðp>þtii.;|à˜ÇCA™åTU”^ŸBÁ:-Ж_¼±Óév;ÝnBÈv¥2`0œç8mëšfr‹gfF»ºËÔÂiwÆæÐ×m¶¶(‡žh»4òûC~?!dW–ça»R™„qŽç8õ¶¯]Yn›Wªí$›HÐýÇ9.ôâl¥ßO·ûy^{«”Ïûyžnß)•ÞÞ›=£æðrÊÎŽöBtU–«²LÙkµ?|œ˜p ÂR.7Äó÷‚AÁ{­ÖÖææ^«u¸)ŸÏÆãS‹‹K¹ÜÜòòV±¨ºŸç«²¼xÿþR.gá¸Èô4ý<(KÒoÆÇ-·”Ë SSÙCk®*-ðºN‹ênZV]L ó½fJb£V;ýsîs¸ ]Qôµ·wx•…à BÈîö6}Z–$u†xÈï÷ŽÒÇ&³yäòeúxÈn¯* ½¹— LLП²»\[ÅbkonÓiÌ7oƒ±9ô‹d4úS—Kû#G*KR~uU»Ö9œ§ ¬Þ¹CùÀd’D±›âøyŽr8’‘ˆ²³ó¬ÑØ*-,K›üŸ}6[,Π‡##­fSEz7ïˆÓù ŸïŸnÜ(?~|ŽeK…B£^¥R„©……R¡0{õªÝåú^Qª²¬öÆæÐ¶+•{Á`7{Þ …ª/. §`.û탭gÏž5‹÷ïÓ»p/°ì§KK4Á:æñ \M‹ž¹¹íRé“)µ±á™›£Mç9.µ±ñáÈH©PxÖhL-.ªÏáTê·BJ…‚ÝåJml¨7¤ŠÅ1§T(|`2­º†±9œªf£Aiíím¶•GŽ\†eÌã;Xÿ–.{K|%ÏñÌÌXív¬÷ú†±9œªÉ@ Q¯ÿjxØÏóêòL‡yff¾{ú4†ü~?ÏÓêÊ+q ÂïÓéqŽ£+=™»˜ƒp¦!ÍáT9Ýn:·Áh2åÓé/^ÔN.Vùy>2=]*αì§áðÃñª/4‹Ñ߉n6ÙxÜ?:ŠJ èÒNÕv¥òÝÓ§ž™™eQ\“å_ø|‡WÛÈg2[››‹÷ï§ŠÅ©……“ýD\Y’L C'úßžÿü ¼€~…4‡SŸÍ®¬¨O[ÏžiïCiT«‡)KÒqw2KUQÔ%°¿¼{WmŠLOg5O !ç07t WAáTÍ­¬'&Æ9ÎÄ04£él–A›Í²ñùù|*µ,Šb2™žVƒ~ˆç·&˜y€—°ÙlHs=@Ý@æz€4Ф9€ Íôi Hs=@šèÒ@æz€4Ф9€ Íôi Hs=@šèÒ@æz€4Ф9€ Íôi Hs=@šèÒ@æz€4Ф9€ Íôi Hs=@šèÒ@æz€4Ѓ÷{}p†‰¢X©Tz}@œNç{ûûû½> 8«x§Óxá‚Ùbéõ‰¼ÓjÕjó»ï06‡×âžœq:{}ï´²$­†Ã¨›èÒ@æz€4Ф9€ Íôi Hs=@šèÒ@veyW–µO³‰DY’ºï¡µ··}̪;š àÎ~=ûý„T±H)KÒ'W®XXÖÄ0#Åb—=dïÞͧÓkš„nš ¼O©T* ØÍfmÃÞÞž¢(6›M»±R© »Ý~éÒ%íþ²,·Z-–eÚ^€®±×ÖOY–××× ƒÝnç8®ó9Àacúx«T²°,Â÷]ó>!dbb‚òäÉmß½{7NËÿ 2™L$¡Óé4!„çùT*E·øýþÍÍÍk×®‰¢¨í½V«MLL(ŠòäÉ“#CY’¤P(T¯×éSEQ´´Ç33£}jbíÓF­V«× !ƒ‡þ iÓáí/mjµZç5c/è¹?ÕÍEùõ¯Ýa¿H$Âó¼|`qq±X,í>;;;mGÅãñ}J’tåÊŽã666h·_}õÕÎÎÏó'z/ï®yA˜ú ŸNomnŽs\2¥[ü££Á‰‰àÄÄ8Çå3íQ´iœã¾W”¶lç¸l"áýøâÅl"qZï^îOiîp8Òét>Ÿ?r§J¥¢(Ê­[·Ô-^¯—çyíHÜápB2š(„QA8îµC¡ÃáH¥RjÑÆét...nnn¶õS«Õ*•J­Vkën?îdY>²‰uä!ǽ@Ÿk6ÍFƒ²,ŠnŸoÈáX“å©……ßßl4Rk²¼&ËC<Ÿ<ø’òû•¥\nM–ïßßÒTØ;4B²ñøÔââ¿~õUÛè­?]u¹\&“i~~þòåˇk߃R*•´ÕµÌ¢¢ùîõzéSZ!q¹\wîÜ9ü²,onn>xð m»×ëm«žûýþâÁ?&Ab±˜úXûm`qq‘¾´ Çɲ¬¶.//»ÝîÃG ‚ Š¢ZÉÑ6iëHg×ÈåËîÉIÓÁ€é˪ѼU,zææh!eÄét ‚t0>ëÐDây÷ÁŸ9ô?ÏPL$„#ë-Çù|¾›7orç÷û3™Ì‘£×ÉÉIm¼&“I«Õj4|áõõuBÈààà‘/§>V…Bë0ôÓ‚¾´ßïo4j‰†çyµ¬ßh4DQ“eùÉ“' è~¿gg'—Ëɲ|ÿþ}íw AÔs¹Üá:ÀYäözÿ—Å’ŒF〟çóé4ݾ]©TÅj·«{þüà:j‡&ê˾ý‡Wöç47›ÍsssÇÕ[R©Ôƒ¬Vk±X¼qãÆèè¨ m™ît:‰¦Ø"Šâìììq/Ül6Y–}é|–e—––ècú€^2½|ùr8VK4ì¡^333„Ç£^e-‹sssôENçÜÜÝ.ËòÇÕm6ÛââbÛ]€³(||ñb©P „SSÎã+ŸpÖ½p÷ÐÌÌ̵k׿çç÷ööïêõzi]âÛo¿¥E :FK-¦Óò…óøß³X,Š¢tóÁjdkçDz½^‹ÅFÏóéƒÅh.èÛ†´úo׌8ÔÇ¥R‰ …øÉd²ËÓè[ZMÅO—–RÅâ\,æöz ß•m6 Ëî”JêÎêãMÐÏÚï=²Þ’H$š‹×ÇÅb±åååÍÍͶ¼S‹-Éd²óÔš¤ÛÛÛ‡›A8îz¬*\¼x±P(B¦¦¦:\kí,ïzÑÒÒÒá!?ÀÙÕ¨Õè âùõl¶µ·Giíí­g³Ý4AßjOsµÞRÐüý믿><×ÐrÔ/µ«ÅQ''';¼0Çq‡ãp5C’¤‡>{ö¬Ã±µZMÅ¥¥¥b±‹Å¼^ïqÕy-›ÍƲlI3ÊPÛívEQ\.×ÂëׯÛíöÄΓÙ<Äó÷‚A?ÏûyÞ?::h·W…®N¥!¿öóü¯†‡5_[;4Aß:âÎþ™™™õõõ‡ª#ÓÉÉÉt:Mç“ÐK”µZmvvöÈ·zA²C™… ããã~¿ii‰VQ$Išžžv8ÞW¹b^«Õ´Ÿ=ð<Çív»Íf«T*êG”Ífs8³³³=¢ F±ë»¢z+¤™‚å¹ukìúuú8œJ]¿uë…c±Ð‰(?÷xÔ{‹RÅbY’¶J¥±ë×ÏsœöjçqMK¹Ü|gíKG¯Ó’H$´³SœNçƒ"‘ÈŋՈg&—Ë>–F¿Ïç{ék»ÝnÚíèè(ÝÒv/èqÌf3ÏóÁ`†x½^áÎ;²,soNK¥R‚ ¨å~:C‘>^YY™˜˜f†^5=òÝô'ím™†ƒækå Í¦½Ÿ³íÞΧsä`àÕMÓ‘·†B?xo¿R©^b….½rä:-„—Ë¥m¢×<Õ$­T*ÚÖ¶§‡I’D‹ׯ_ï¼N‹¶+z2‹…äÕwÑv2‡O@}k‰D"kW gb·Û_úÅ!¼Ó9 à¿@O•%i5~o¿×grzèÄù……úÔï÷˲Œrʉ!ÍúMówkE\ŸÏ EaY¶P(Ôëõååå^ŸÀðn¥ù‚Ýn§U—ËåóùÚÖ8£Þ­4'„8NÔÄ@ðKrz€4Ф9€ Íôi Hs=@šèÁ»ug?¼YƒƒƒõFƒ¼ÿÎݵÐ_ž?gL&üÂÉ †IŸïÕè©§•JéË/‘æprïÿøÇ—l6¬ºÐså|us=@šèÒ@æz€4Ф9€ Íôi Hs=@šèDko/¶ööÞl·Éh´,I¯Ú§i ß)ʽ`ð;Ey³ÝæÓé­RéU›àô!Íô«nœ1Û•Ê ÍV–¤­RÉ`4zffÚv MCv»º Zko¯Q¯ 1V·7jµuQl5›£qLLf³Úî,¯ù¥ÁhtON¶u~dÓ‘ýÃ)Cšœ%Û•Jpbbˆç·ŠEÃ4êõl<¾&ËêñÙÙF½nb˜|:mb˜T±HÉÞ½[*!z½T(Œ‹ùL&‰˜†nÌÆãsËËN·›òûŸN9„õlVûêÇ5îÿþS@TZΘª¢BÖd9U,.år„l"¡¶M&µ©Q¯'£Qº}kssÌãY“å•G!ÉHÄíó¥ŠÅT±¸&Ë&†YÅ!eIú}:ýÛhÓ˜ÇS=(Äwh:Ü?œ>¤9ÀÙ£V9m6BH«ÙT›|¡Úä„|:MŸZX–Öd „•G¦÷¼Y(XXÖíõÒ§ž™ ˾´épÿpúPi8{~l4ªiµ„²°ì æ§  šÝÚ †x °]*5êuBHUQhýd»TÒv¨í¿CôŒÍt¥Q«u³›tt»T²»\K¹Üš,Ó('„M¦ãéÐýi UEùîéSõé7åò‘ÃgZgårS ƒ/þ¬ëÈØ­«Ô§š  Ítå^(D‡çÙDâñÇ“Àá}Ø?¬*ʺ(Ò§ÉhTÍeZûùýôiÈïW/uvh‚~€48c´×É‹ ËŽy<þÑÑqŽËÆã¿}ð€N:l3âtþÂçËÆãã7Îqß+ŠÛçS})—«Ê2m"„\¾vM=°CôÜ{ûûû½>8«x§s2­"}ˆÞaô:»íʲÁ`ÐÞRÔMôDY’VÃaÌiСn¢¼ónç9îMÐC¨´èÒ@æz€4Ф9€ Íôi Hs=@šèÒ@æz€4Ь¡'wãv±Â5@° a E89‹ÅòÙýûX ·èЏ¨´èÒ@æz€4Ф9€ Íôi Hs=@šèÒ@æïíJå ö–M$æáȦßòûßàkÁKa€wEÈï¯ÊrªX|S¶šMegçȦª,¿©W.alð®xã ;µ°°†ÔîHs€~×ÚÛKF£»²6VU”|&£nùC¡`aÙA›ý64# ZIDAT>Õ&éqW#M S~üX}Z–¤qŽÓN†yÕS"„hO©mÔ§cs€3æ<ÇýÂçKF"„óƒƒ(.-ÑVÖj•D‘µZ™sçò««Ú-·U,&£QÏ­[“ÀoÆÇ !×oÝjT«ñùyÜø»Mf³SÔSúòîÝ×z‡p"Hs€~7`0 9ƒº%œJ%£Q1™lÔë&†ùíƒjÍdYC~? V·Ï÷áG}óõ×´ÉÿÙgaY. cׯ;Ýîß­­­ÆbÁ‰ BÈχS)º›…ãº<1ƒÑÈZ­ôñ\,f0éë:Á37§¾.œŽ÷ö÷÷{}pVñNçd(tâ¼eIZ ‡Q7ÐTZàXó‚pܺZnŸO;çziÇZÆÔ”³•=@šèÒ@æz€4Ф9€ Íôi Hs=À½ prÏŸ?ú*¿^oÃÓJåùóçXCNîÃÁÁF£AÞ×ï˜àùó~øáÇš¥húÑóç&“I¿‡ðö™L¦¹•¯ˆ[)óñÇÿûûï{}"Ðq‘æÐ3eIÚ*•¯Ã—ŒF‡ìvõC¢Q«­‹by}ÝðÁ~ôј ˜ÌfÚÔÚÛËÞ½K^ü{mç£Qý!y}ÃUPè™­Ré^0òûÛ¶çÓé­R‰>–òyÿèh6o6UYÎÆãþÑQ)Ÿ§­ß)ʽ`ð^0xø-³++÷‚Áõlöm¿ €>4‡û}:­¦óa«±ØϯÉrªXL‹k²ÌZ­ñùyí>–mûÌF­¦ììXXöm4@ÿAšC/YXöòµkñùùÖÞÞ‘;4êõs/†²gvÖÄ0»²¬nâù­bQ»Ïº(šÆÄ0oãœúÒzl6#„Ä~ýë#[M S*´…”§3U,ž×ü±{r’¢Ýg=›óxÞÖô%¤9ôØyŽóÌÍWo™[YiÔëŸ\¹2Îqó‚M$µZÛ>?6Y«U-¶4jµF½>&oýÔú ÒzÏ33s\½eÐf[“åß>x0ÄóÊÎΛ7ý££Éh´m7§ ¨Å–uQd­VuÞ À;i}a!‘ Ç×[Ü^o8•Z“åÿöÛ!ž¿ ¶ äÝ^/9(¶¬g³N Ìá݃4‡¾`2›i½%ŸÉ¨³‰Äü‹¹|žã©”…e•íí¶h±…–Yh¸¼SæÐ/h½%‰T…na?üðñÇÚ|'„7û…[ÄtšµZßú¹ô¤9ôZoQ8CG2‰Û•Êv¥R–¤Ù«W !‚Ï×v,çÓi”YÞ»²ìçyíDU@šC¡õí]?©bqˆç%Q¼1<|cx82=m4™V=R/rjw¦£rµÌb4™Œ&Ó)ž>œž½Vkkss¯Õêõ‰ô¬Ó=3µ°px}ÏÌLÛÊ*áTв+Ë{­Ö Í¦m¢3^Ô§Ë¢¨mm{  o›ÃÙpžãÚ¢NS2ÕÞŸ•M$²‰!¤µ·G'Œ&£Q?ÏÏ ‚ºmÒ^çÐv²+Ë!¿ŸÒvi$ŸÉÌ ‚Ÿçã€öðF­ü<òûh6ßÚ{=«æðrÚ¥Ð!ëÙ,]Ñì;EɧÓ~žÿ^Qì.W³ÑˆLOÓ;¼èšhß\ÓÖv²+Ëôú‡Ýå:Çqÿtã†zAÈïOF"ç8Îîrm—J¿¦Þ¨Õü££Û¥’Ýå"„D¦§OïÍŸ¨´Àk©*ŠSæb1BˆçÖ­_ ‹éôášÖú—_š†ÖШV³I)KÒïÓéýê+ºòÔ‚Ÿçc¿þu8•Z  !©ƒ{Ä’Ñè½`ðí½©³i¯ëç«âºY쌱X¶67C~ÿÈåËn¯—~B¶J% ËþØhÜ>ø…B ÇÑ»|·ŠEíl%Áç˧ÓoømœqHs8mn¯·Õjeãñ­b1‰˜fneeÐf+ UE NLy”ÁhTcå†ÃæÐtòR£V+­¯‹ÉdpbbM–íöF½¾vÌ,ò®|v„« Ðm˜6êõ.R'Ÿh¿ŒtÍ“Ùìöz'º½c@-³Bæþ:½í@ÝžÏdªšë«@æÐ?’ѨöÏø8eI:üãsð¶±V«$ŠùL¦ûÿþƒ6›…eï…BeIÊg2Mýdˆç?|Hÿ?^–¤ÕXÌÄ04ÙM œ˜Èg2Û•J<xüð!-—Ï.-BB~ÿv¥’Ïd’‘È[z§gÒúÂv¥Òå…{¡P÷sŸºeQâùd$™ž>Dzž¹9 ÇB †!‡cÀ`P÷´pœZà^yôÈh2E¦§Ådr2âyÚät»·¶V*‚‘éi Ç©“UèÝ¿ÉH$81±]*ýnmÍévBLfóÊ£G­gÏ‚b2¹”Ë]¾vMûºðÞþþ~¯ÏÎ*Þéœ …èd²WBoì43Œz-k»R¹1<üàÉí-Bt·ƒAûKC~ž'š™jÑÁþ‰o;ª‹¿ùøã¯þë¿Nv8Àé(KÒj8Œ« pªµZ`bB­º²Vë²(nW*tCpbBÝŸUw31L(•:Ïqó‚°µ¹I縥\î…B>V/šÑ~–r¹A›­,IÚLÔ Ñz…J œªèÌŒÑdZ“å5Y^Êå”l"1h³-år„¥\Ž.®ŸµpÝ-µ±Ñ¨×WB¿ï;CÇš,9âV¯ŒE¦§‚@{ðÌÍ­Þ¹ƒõö@ß06‡SÕl4!­½=ÃÀÀ Í¶òè‘¶Š¢óxÔö4™Í&†¡¾’g‡xff¬vû‘/ ›Ã©š õú¯†‡ý<øç=Už™™ïž>MF£ta&Z]y%NAø}:­þ6´¹‹Î4¤9œ*§ÛÚØðÌÍM¦|:ýñÅ‹mëçQ~žLO— …s,ûi8<äp¼ê ÍÅbÿþí·nŸ¯Ùhdãqÿè(*- oHs8UÛ•ÊwOŸzff–EqM–áóž8œÏd¶67ïßO‹S '˜3C)K’‰a¦RÅâ¿=yBI}þùxý i§*>;›]YQŸ¶ž=Ó.ÒÔ¨VR–¤ãîæñܹy“ÎPt{½êžz}ˆçÕ–›Z\LF"¥BÒ¨×é8½oà´ ÍáTÑß~+KÒV©4d·k«(©Z½NïîK‹mû¨7ýÓ *äàž uϱë×ÏsÜv¥B·»½^ûØXi}½^­þÔåÂïîá^P8¹ß zVà^P8è½ ¨›èÒ@P7‡×ò´‹5lÏ.e{ûùóçÚߪèCôÏus8¹ååe1ŸïõY¼EÏŸ?oµZF͘ô'ßääÿÓ´µå3PÛIEND®B`‚frr-7.2.1/doc/manpages/0000755000000000000000000000000013610377563011634 500000000000000frr-7.2.1/doc/manpages/Makefile0000644000000000000000000000033513610377563013215 00000000000000all: ALWAYS @$(MAKE) -s -C ../.. doc/manpages/man.stamp help: ALWAYS @$(MAKE) -s -C ../.. doc/help %: ALWAYS @$(MAKE) -s -C ../.. doc/manpages/_build/$@ Makefile: #nothing ALWAYS: .PHONY: ALWAYS makefiles .SUFFIXES: frr-7.2.1/doc/manpages/bfd-options.rst0000644000000000000000000000037713610377563014541 00000000000000BFD SOCKET ---------- The following option controls the BFD daemon control socket location. .. option:: --bfdctl bfd-control-socket Opens the BFD daemon control socket located at the pointed location. (default: |INSTALL_PREFIX_STATE|/bfdd.sock) frr-7.2.1/doc/manpages/common-options.rst0000644000000000000000000001552313610377563015275 00000000000000HELP AND VERSION ---------------- .. option:: -h, --help Print a short description of the daemon's command line options. .. option:: -v, --version Print version and build information for the daemon. Both of these options inhibit normal operation and will immediately exit. PROCESS CONTROL --------------- These options control background operation: .. option:: -d, --daemon Launches the process in background/daemon mode, forking and detaching from the terminal. The parent process will delay its exit until the daemon/child has finished its initialization and has entered its main loop. This is important for zebra startup because the other daemons will attempt to connect to zebra. A return from zebra -d guarantees its readiness to accept these connections. .. option:: -t, --terminal Opens an interactive VTY session on the terminal, allowing for both state and configuration operations. Note that the terminal starts operating after startup has completed and the configuration file has been loaded. The process will exit when end of file is detected on the terminal. It is possible to daemonize a process started with -t (but without -d) by sending SIGQUIT to the process (normally mapped to a ^\ keypress.) The combination of :option:`--daemon` and :option:`--terminal` will delay the daemon from going into background until the terminal session ends (by end of file.) If the process receives SIGINT (e.g. a ^C keypress) in this mode, it will exit instead of daemonizing. It is safe to suspend (SIGTSTP / ^Z) the terminal session opened by the previous two options; this will only stop the terminal but not the protocol daemon itself (which runs in a separate second process.) CONFIGURATION AND PATHS ----------------------- The following options control configuration and file system locations for frr processes: .. option:: -f, --config_file config-file Specify a configuration file to be used instead of the default /etc/frr/.conf file. Note that the daemon will attempt to write to this file if the write file command is issued on its VTY interface or through vtysh. .. option:: -C, --dryrun Load the configuration file and check its validity, then exit. .. option:: -i, --pid_file pid-file Output a pid file to a location other than the default /var/run/frr/.pid. .. option:: -z, --socket zclient-path Override the path of the ZAPI socket used to communicate between zebra and the various protocol daemons. The default is /var/run/frr/zserv.api. The value of this option must be the same across all daemons. .. option:: -N, --pathspace pathspace Insert pathspace into all default paths, changing the defaults to: /etc/frr/pathspace/.conf /var/run/frr/pathspace/.pid /var/run/frr/pathspace/.vty /var/run/frr/pathspace/zserv.api ´.´ and ´/´ characters will not be accepted in pathspace, but the empty string will be accepted. Note that this only changes the respective defaults, it has no effect on the respective path if the -f, -i, -z or --vty_socket options are used. The purpose of this option is to easily group all file system related bits together for running multiple fully-separate "logical routers" on a system, particularly with Linux network namespaces. Groups of daemons running with distinct pathspace values will be completely unaware of each other and not interact in any way. This option does not do any system setup (like network namespaces.) This must be done by the user, for example by running: ip netns exec namespace -N namespace PROCESS CREDENTIALS ------------------- .. option:: -u, --user user (default: frr) .. option:: -g, --group group (default: frr) Change the user/group which the daemon will switch to. .. option:: -S, --skip_runas Skip setting the process effective user and group. Note that there is an additional group, frrvty, which controls group ownership of the VTY sockets. The name of this group cannot currently be changed, and user must be a member of this group. VTY SETUP --------- These following options control the daemon's VTY (interactive command line) interface. The interface is available over TCP, using the telnet protocol, as well as through the vtysh frontend. .. option:: -A, --vty_addr vty-addr Specify an IP/IPv6 address to bind the TCP VTY interface to. It is generally recommended to specify ::1 or 127.0.0.1. For reasons of backwards compatibility, the default is to listen on all interfaces. .. option:: -P, --vty_port vty-port Override the daemon's default TCP VTY port (each daemon has a different default value upwards of 2600, listed below.) Specifying 0 disables the TCP VTY interface. Default ports are::: zebra 2601 ripd 2602 ripngd 2603 ospfd 2604 bgpd 2605 ospf6d 2606 isisd 2608 babeld 2609 nhrpd 2610 pimd 2611 ldpd 2612 eigrpd 2613 pbrd 2615 staticd 2616 bfdd 2617 fabricd 2618 vrrpd 2619 Port 2607 is used for ospfd's Opaque LSA API. .. option:: --vty_socket vty-path Overrides the directory used for the .vty sockets. vtysh connects to these sockets in order to access each daemon's VTY. Default: /var/run/frr[/] NB: Unlike the other options, this option specifies a directory, not a full path. This option is primarily used by the SNAP packaging system, its semantics may change. It should not be necessary in most other scenarios. MODULE LOADING -------------- frr supports optional dynamically loadable modules, although these can only be loaded at startup. The set of available modules may vary across distributions and packages, and modules may be available for installation as separate packages. .. option:: -M, --module module[:options] Load a module named module, optionally passing options to it. If there is a ´/´ character in module, the value is assumed to be a pathname to a module. If there is no ´/´ character, the module directory (see next option) is searched first for a module named "_.so", then for ".so". This allows for a module to exist in variations appropriate for particular daemons, e.g. zebra_snmp and bgp_snmp, with the correct one selected by -M snmp. The meaning of options is specific to the module being loaded. Most modules currently ignore it. Modules are loaded in the order as listed on the command line. This is not generally relevant. .. option:: --moduledir module-path Look for modules in the module-path directory instead of the default /usr/lib/frr/modules. (This path is not affected by the -N option.) The list of loaded modules can be inspected at runtime with the show modules VTY command. frr-7.2.1/doc/manpages/conf.py0000644000000000000000000003067113610377563013062 00000000000000# -*- coding: utf-8 -*- # # FRR documentation build configuration file, created by # sphinx-quickstart on Tue Jan 31 16:00:52 2017. # # This file is execfile()d with the current directory set to its # containing dir. # # Note that not all possible configuration values are present in this # autogenerated file. # # All configuration values have a default; values that are commented out # serve to show the default. import sys import os import re # If extensions (or modules to document with autodoc) are in another directory, # add these directories to sys.path here. If the directory is relative to the # documentation root, use os.path.abspath to make it absolute, like shown here. #sys.path.insert(0, os.path.abspath('.')) # -- General configuration ------------------------------------------------ # If your documentation needs a minimal Sphinx version, state it here. needs_sphinx = '1.0' # prolog for various variable substitutions rst_prolog = '' # Add any Sphinx extension module names here, as strings. They can be # extensions coming with Sphinx (named 'sphinx.ext.*') or your custom # ones. extensions = ['sphinx.ext.todo'] # Add any paths that contain templates here, relative to this directory. templates_path = ['_templates'] # The suffix(es) of source filenames. # You can specify multiple suffix as a list of string: # source_suffix = ['.rst'] source_suffix = '.rst' # The encoding of source files. #source_encoding = 'utf-8-sig' # The master toctree document. master_doc = 'index' # General information about the project. project = u'FRR' copyright = u'2017, FRR' author = u'FRR authors' # The version info for the project you're documenting, acts as replacement for # |version| and |release|, also used in various other places throughout the # built documents. # The short X.Y version. version = u'?.?' # The full version, including alpha/beta/rc tags. release = u'?.?-?' # ----------------------------------------------------------------------------- # Extract values from codebase for substitution into docs. # ----------------------------------------------------------------------------- # Various installation prefixes. Values are extracted from config.status. # Reasonable defaults are set in case that file does not exist. replace_vars = { 'AUTHORS': author, 'COPYRIGHT_YEAR': '1999-2005', 'COPYRIGHT_STR': 'Copyright (c) 1999-2005', 'PACKAGE_NAME': project.lower(), 'PACKAGE_TARNAME': project.lower(), 'PACKAGE_STRING': project.lower() + ' latest', 'PACKAGE_URL': 'https://frrouting.org/', 'PACKAGE_VERSION': 'latest', 'INSTALL_PREFIX_ETC': '/etc/frr', 'INSTALL_PREFIX_SBIN': '/usr/lib/frr', 'INSTALL_PREFIX_STATE': '/var/run/frr', 'INSTALL_PREFIX_MODULES': '/usr/lib/frr/modules', 'INSTALL_USER': 'frr', 'INSTALL_GROUP': 'frr', 'INSTALL_VTY_GROUP': 'frrvty', 'GROUP': 'frr', 'USER': 'frr', } # extract version information, installation location, other stuff we need to # use when building final documents val = re.compile('^S\["([^"]+)"\]="(.*)"$') try: with open('../../config.status', 'r') as cfgstatus: for ln in cfgstatus.readlines(): m = val.match(ln) if not m or m.group(1) not in replace_vars.keys(): continue replace_vars[m.group(1)] = m.group(2) except IOError: # if config.status doesn't exist, just ignore it pass # manually fill out some of these we can't get from config.status replace_vars['COPYRIGHT_STR'] = "Copyright (c)" replace_vars['COPYRIGHT_STR'] += ' {0}'.format(replace_vars['COPYRIGHT_YEAR']) replace_vars['COPYRIGHT_STR'] += ' {0}'.format(replace_vars['AUTHORS']) release = replace_vars['PACKAGE_VERSION'] version = release.split('-')[0] # add substitutions to prolog for key, value in replace_vars.items(): rst_prolog += '.. |{0}| replace:: {1}\n'.format(key, value) # The language for content autogenerated by Sphinx. Refer to documentation # for a list of supported languages. # # This is also used if you do content translation via gettext catalogs. # Usually you set "language" from the command line for these cases. language = None # There are two options for replacing |today|: either, you set today to some # non-false value, then it is used: #today = '' # Else, today_fmt is used as the format for a strftime call. #today_fmt = '%B %d, %Y' # List of patterns, relative to source directory, that match files and # directories to ignore when looking for source files. exclude_patterns = ['_build', 'common-options.rst', 'epilogue.rst', 'defines.rst', 'bfd-options.rst'] # The reST default role (used for this markup: `text`) to use for all # documents. #default_role = None # If true, '()' will be appended to :func: etc. cross-reference text. #add_function_parentheses = True # If true, the current module name will be prepended to all description # unit titles (such as .. function::). #add_module_names = True # If true, sectionauthor and moduleauthor directives will be shown in the # output. They are ignored by default. #show_authors = False # The name of the Pygments (syntax highlighting) style to use. pygments_style = 'sphinx' # A list of ignored prefixes for module index sorting. #modindex_common_prefix = [] # If true, keep warnings as "system message" paragraphs in the built documents. #keep_warnings = False # If true, `todo` and `todoList` produce output, else they produce nothing. todo_include_todos = True # -- Options for HTML output ---------------------------------------------- # The theme to use for HTML and HTML Help pages. See the documentation for # a list of builtin themes. html_theme = 'default' # Theme options are theme-specific and customize the look and feel of a theme # further. For a list of options available for each theme, see the # documentation. #html_theme_options = {} # Add any paths that contain custom themes here, relative to this directory. #html_theme_path = [] # The name for this set of Sphinx documents. If None, it defaults to # " v documentation". #html_title = None # A shorter title for the navigation bar. Default is the same as html_title. #html_short_title = None # The name of an image file (relative to this directory) to place at the top # of the sidebar. #html_logo = None # The name of an image file (within the static path) to use as favicon of the # docs. This file should be a Windows icon file (.ico) being 16x16 or 32x32 # pixels large. #html_favicon = None # Add any paths that contain custom static files (such as style sheets) here, # relative to this directory. They are copied after the builtin static files, # so a file named "default.css" will overwrite the builtin "default.css". html_static_path = ['_static'] # Add any extra paths that contain custom files (such as robots.txt or # .htaccess) here, relative to this directory. These files are copied # directly to the root of the documentation. #html_extra_path = [] # If not '', a 'Last updated on:' timestamp is inserted at every page bottom, # using the given strftime format. #html_last_updated_fmt = '%b %d, %Y' # If true, SmartyPants will be used to convert quotes and dashes to # typographically correct entities. #html_use_smartypants = True # Custom sidebar templates, maps document names to template names. #html_sidebars = {} # Additional templates that should be rendered to pages, maps page names to # template names. #html_additional_pages = {} # If false, no module index is generated. #html_domain_indices = True # If false, no index is generated. #html_use_index = True # If true, the index is split into individual pages for each letter. #html_split_index = False # If true, links to the reST sources are added to the pages. #html_show_sourcelink = True # If true, "Created using Sphinx" is shown in the HTML footer. Default is True. #html_show_sphinx = True # If true, "(C) Copyright ..." is shown in the HTML footer. Default is True. #html_show_copyright = True # If true, an OpenSearch description file will be output, and all pages will # contain a tag referring to it. The value of this option must be the # base URL from which the finished HTML is served. #html_use_opensearch = '' # This is the file name suffix for HTML files (e.g. ".xhtml"). #html_file_suffix = None # Language to be used for generating the HTML full-text search index. # Sphinx supports the following languages: # 'da', 'de', 'en', 'es', 'fi', 'fr', 'hu', 'it', 'ja' # 'nl', 'no', 'pt', 'ro', 'ru', 'sv', 'tr' #html_search_language = 'en' # A dictionary with options for the search language support, empty by default. # Now only 'ja' uses this config value #html_search_options = {'type': 'default'} # The name of a javascript file (relative to the configuration directory) that # implements a search results scorer. If empty, the default will be used. #html_search_scorer = 'scorer.js' # Output file base name for HTML help builder. htmlhelp_basename = 'FRRdoc' # -- Options for LaTeX output --------------------------------------------- latex_elements = { # The paper size ('letterpaper' or 'a4paper'). #'papersize': 'letterpaper', # The font size ('10pt', '11pt' or '12pt'). #'pointsize': '10pt', # Additional stuff for the LaTeX preamble. #'preamble': '', # Latex figure (float) alignment #'figure_align': 'htbp', } # Grouping the document tree into LaTeX files. List of tuples # (source start file, target name, title, # author, documentclass [howto, manual, or own class]). latex_documents = [ (master_doc, 'FRR.tex', u'FRR User Manual', u'FRR', 'manual'), ] # The name of an image file (relative to this directory) to place at the top of # the title page. #latex_logo = None # For "manual" documents, if this is true, then toplevel headings are parts, # not chapters. #latex_use_parts = False # If true, show page references after internal links. #latex_show_pagerefs = False # If true, show URL addresses after external links. #latex_show_urls = False # Documents to append as an appendix to all manuals. #latex_appendices = [] # If false, no module index is generated. #latex_domain_indices = True # -- Options for manual page output --------------------------------------- # One entry per manual page. List of tuples # (source start file, name, description, authors, manual section). # If true, show URL addresses after external links. #man_show_urls = False fwfrr = "{0} routing engine for use with FRRouting." man_pages = [ ('frr-bfdd', 'frr-bfdd', fwfrr.format("a bfd"), [], 8), ('frr-bgpd', 'frr-bgpd', fwfrr.format("a BGPv4, BGPv4+, BGPv4-"), [], 8), ('frr-eigrpd', 'frr-eigrpd', fwfrr.format("an EIGRP"), [], 8), ('frr-fabricd', 'frr-fabricd', fwfrr.format("an OpenFabric"), [], 8), ('frr-isisd', 'frr-isisd', fwfrr.format("an IS-IS"), [], 8), ('frr-ldpd', 'frr-ldpd', fwfrr.format("an LDP"), [], 8), ('frr-nhrpd', 'frr-nhrpd', fwfrr.format("a Next Hop Routing Protocol"), [], 8), ('frr-ospf6d', 'frr-ospf6d', fwfrr.format("an OSPFv3"), [], 8), ('frr-ospfclient', 'frr-ospfclient', 'an example ospf-api client', [], 8), ('frr-ospfd', 'frr-ospfd', fwfrr.format("an OSPFv2"), [], 8), ('frr-pbrd', 'frr-pbrd', fwfrr.format("a PBR"), [], 8), ('frr-pimd', 'frr-pimd', fwfrr.format("a PIM"), [], 8), ('frr-ripd', 'frr-ripd', fwfrr.format("a RIP"), [], 8), ('frr-ripngd', 'frr-ripngd', fwfrr.format("a RIPNG"), [], 8), ('frr-sharpd', 'frr-sharpd', fwfrr.format("a SHARP"), [], 8), ('frr-staticd', 'frr-staticd', fwfrr.format("a static route manager"), [], 8), ('frr-vrrpd', 'frr-vrrpd', fwfrr.format("a VRRP"), [], 8), ('frr-watchfrr', 'frr-watchfrr', 'a program to monitor the status of FRRouting daemons', [], 8), ('frr-zebra', 'frr-zebra', 'a routing manager for use with associated FRRouting components.', [], 8), ('frr', 'frr', 'a systemd interaction script', [], 1), ('mtracebis', 'mtracebis', "a multicast trace client", [], 8), ('vtysh', 'vtysh', 'an integrated shell for FRRouting.', [], 1), ] # -- Options for Texinfo output ------------------------------------------- # Grouping the document tree into Texinfo files. List of tuples # (source start file, target name, title, author, # dir menu entry, description, category) # Documents to append as an appendix to all manuals. #texinfo_appendices = [] # If false, no module index is generated. #texinfo_domain_indices = True # How to display URL addresses: 'footnote', 'no', or 'inline'. #texinfo_show_urls = 'footnote' # If true, do not generate a @detailmenu in the "Top" node's menu. #texinfo_no_detailmenu = False # custom extensions here frr-7.2.1/doc/manpages/defines.rst0000644000000000000000000000106113610377563013721 00000000000000.. |synopsis-options| replace:: [-d|-t|-dt] [-C] [-f config-file] [-i pid-file] [-z zclient-path] [-u user] [-g group] [-A vty-addr] [-P vty-port] [-M module[:options]] [-N pathspace] [--vty_socket vty-path] [--moduledir module-path] .. |synopsis-options-hv| replace:: [-h] [-v] .. |seealso-programs| replace:: frr-zebra(8), vtysh(1), frr-ripd(8), frr-ripngd(8), frr-ospfd(8), frr-ospf6d(8), frr-bgpd(8), frr-isisd(8), frr-babeld(8), frr-nhrpd(8), frr-pimd(8), frr-pbrd(8), frr-ldpd(8), frr-eigrpd(8), frr-staticd(8), frr-fabricd(8), frr-vrrpd(8), mtracebis(8) frr-7.2.1/doc/manpages/epilogue.rst0000644000000000000000000000113713610377563014121 00000000000000WARNING ======= This man page is intended to be a quick reference for command line options. The definitive document is the info file |PACKAGE_STRING| or the documentation available on the project website at |PACKAGE_URL|. DIAGNOSTICS =========== The daemon may log to standard output, to a VTY, to a log file, or through syslog to the system logs. FRR supports many debugging options, see the Info file, web docs or source for details. SEE ALSO ======== |seealso-programs| |PACKAGE_URL| BUGS ==== FRR eats bugs for breakfast. If you have food for the maintainers, please email . frr-7.2.1/doc/manpages/frr-bfdd.rst0000644000000000000000000000140413610377563013773 00000000000000**** BFDD **** .. include:: defines.rst .. |DAEMON| replace:: bfdd SYNOPSIS ======== |DAEMON| |synopsis-options-hv| |DAEMON| |synopsis-options| DESCRIPTION =========== |DAEMON| is a communication failure detection component that works with the FRRouting routing engine. OPTIONS ======= OPTIONS available for the |DAEMON| command: .. include:: common-options.rst .. include:: bfd-options.rst FILES ===== |INSTALL_PREFIX_SBIN|/|DAEMON| The default location of the |DAEMON| binary. |INSTALL_PREFIX_ETC|/|DAEMON|.conf The default location of the |DAEMON| config file. $(PWD)/|DAEMON|.log If the |DAEMON| process is configured to output logs to a file, then you will find this file in the directory where you started |DAEMON|. .. include:: epilogue.rst frr-7.2.1/doc/manpages/frr-bgpd.rst0000644000000000000000000000153213610377563014012 00000000000000**** BGPD **** .. include:: defines.rst .. |DAEMON| replace:: bgpd SYNOPSIS ======== |DAEMON| |synopsis-options-hv| |DAEMON| |synopsis-options| DESCRIPTION =========== |DAEMON| is a routing component that works with the FRRouting routing engine. OPTIONS ======= OPTIONS available for the |DAEMON| command: .. include:: common-options.rst LABEL MANAGER ------------- .. option:: -I, --int_num Set zclient id. This is required when using Zebra label manager in proxy mode. FILES ===== |INSTALL_PREFIX_SBIN|/|DAEMON| The default location of the |DAEMON| binary. |INSTALL_PREFIX_ETC|/|DAEMON|.conf The default location of the |DAEMON| config file. $(PWD)/|DAEMON|.log If the |DAEMON| process is configured to output logs to a file, then you will find this file in the directory where you started |DAEMON|. .. include:: epilogue.rst frr-7.2.1/doc/manpages/frr-eigrpd.rst0000644000000000000000000000132713610377563014352 00000000000000****** EIGRPD ****** .. include:: defines.rst .. |DAEMON| replace:: eigrpd SYNOPSIS ======== |DAEMON| |synopsis-options-hv| |DAEMON| |synopsis-options| DESCRIPTION =========== |DAEMON| is a routing component that works with the FRRouting routing engine. OPTIONS ======= OPTIONS available for the |DAEMON| command: .. include:: common-options.rst FILES ===== |INSTALL_PREFIX_SBIN|/|DAEMON| The default location of the |DAEMON| binary. |INSTALL_PREFIX_ETC|/|DAEMON|.conf The default location of the |DAEMON| config file. $(PWD)/|DAEMON|.log If the |DAEMON| process is configured to output logs to a file, then you will find this file in the directory where you started |DAEMON|. .. include:: epilogue.rst frr-7.2.1/doc/manpages/frr-fabricd.rst0000644000000000000000000000133313610377563014467 00000000000000******* FABRICD ******* .. include:: defines.rst .. |DAEMON| replace:: fabricd SYNOPSIS ======== |DAEMON| |synopsis-options-hv| |DAEMON| |synopsis-options| DESCRIPTION =========== |DAEMON| is a routing component that works with the FRRouting routing engine. OPTIONS ======= OPTIONS available for the |DAEMON| command: .. include:: common-options.rst FILES ===== |INSTALL_PREFIX_SBIN|/|DAEMON| The default location of the |DAEMON| binary. |INSTALL_PREFIX_ETC|/|DAEMON|.conf The default location of the |DAEMON| config file. $(PWD)/|DAEMON|.log If the |DAEMON| process is configured to output logs to a file, then you will find this file in the directory where you started |DAEMON|. .. include:: epilogue.rst frr-7.2.1/doc/manpages/frr-isisd.rst0000644000000000000000000000132313610377563014207 00000000000000***** ISISD ***** .. include:: defines.rst .. |DAEMON| replace:: isisd SYNOPSIS ======== |DAEMON| |synopsis-options-hv| |DAEMON| |synopsis-options| DESCRIPTION =========== |DAEMON| is a routing component that works with the FRRouting routing engine. OPTIONS ======= OPTIONS available for the |DAEMON| command: .. include:: common-options.rst FILES ===== |INSTALL_PREFIX_SBIN|/|DAEMON| The default location of the |DAEMON| binary. |INSTALL_PREFIX_ETC|/|DAEMON|.conf The default location of the |DAEMON| config file. $(PWD)/|DAEMON|.log If the |DAEMON| process is configured to output logs to a file, then you will find this file in the directory where you started |DAEMON|. .. include:: epilogue.rst frr-7.2.1/doc/manpages/frr-ldpd.rst0000644000000000000000000000131713610377563014022 00000000000000**** LDPD **** .. include:: defines.rst .. |DAEMON| replace:: ldpd SYNOPSIS ======== |DAEMON| |synopsis-options-hv| |DAEMON| |synopsis-options| DESCRIPTION =========== |DAEMON| is a routing component that works with the FRRouting routing engine. OPTIONS ======= OPTIONS available for the |DAEMON| command: .. include:: common-options.rst FILES ===== |INSTALL_PREFIX_SBIN|/|DAEMON| The default location of the |DAEMON| binary. |INSTALL_PREFIX_ETC|/|DAEMON|.conf The default location of the |DAEMON| config file. $(PWD)/|DAEMON|.log If the |DAEMON| process is configured to output logs to a file, then you will find this file in the directory where you started |DAEMON|. .. include:: epilogue.rst frr-7.2.1/doc/manpages/frr-nhrpd.rst0000644000000000000000000000132313610377563014207 00000000000000***** NHRPD ***** .. include:: defines.rst .. |DAEMON| replace:: nhrpd SYNOPSIS ======== |DAEMON| |synopsis-options-hv| |DAEMON| |synopsis-options| DESCRIPTION =========== |DAEMON| is a routing component that works with the FRRouting routing engine. OPTIONS ======= OPTIONS available for the |DAEMON| command: .. include:: common-options.rst FILES ===== |INSTALL_PREFIX_SBIN|/|DAEMON| The default location of the |DAEMON| binary. |INSTALL_PREFIX_ETC|/|DAEMON|.conf The default location of the |DAEMON| config file. $(PWD)/|DAEMON|.log If the |DAEMON| process is configured to output logs to a file, then you will find this file in the directory where you started |DAEMON|. .. include:: epilogue.rst frr-7.2.1/doc/manpages/frr-ospf6d.rst0000644000000000000000000000133013610377563014273 00000000000000****** OSPF6D ****** .. include:: defines.rst .. |DAEMON| replace:: ospf6d SYNOPSIS ======== |DAEMON| |synopsis-options-hv| |DAEMON| |synopsis-options| DESCRIPTION =========== |DAEMON| is a routing component that works with the FRRouting routing engine. OPTIONS ======= OPTIONS available for the |DAEMON| command: .. include:: common-options.rst FILES ===== |INSTALL_PREFIX_SBIN|/|DAEMON| The default location of the |DAEMON| binary. |INSTALL_PREFIX_ETC|/|DAEMON|.conf The default location of the |DAEMON| config file. $(PWD)/|DAEMON|.log If the |DAEMON| process is configured to output logs to a file, then you will find this file in the directory where you started |DAEMON|. .. include:: epilogue.rst frr-7.2.1/doc/manpages/frr-ospfclient.rst0000644000000000000000000000154613610377563015251 00000000000000********** OSPFCLIENT ********** .. include:: defines.rst SYNOPSIS ======== ospfclient DESCRIPTION =========== ospfclient is an example ospf-api client to test the ospfd daemon. OPTIONS ======= .. option:: ospfd A router where the API-enabled OSPF daemon is running. .. option:: lsatype The value has to be either "9", "10", or "11", depending on the flooding scope. .. option:: opaquetype The value has to be in the range of 0-255 (for example, experimental applications might use opaquetype larger than 128). .. option:: opaqueid Arbitrary application instance (24 bits). .. option:: ifaddr Interface IP address for type 9, otherwise it will be ignored. .. option:: areaid Area in the IP address format for type 10, otherwise it will be ignored. .. include:: epilogue.rst frr-7.2.1/doc/manpages/frr-ospfd.rst0000644000000000000000000000142113610377563014206 00000000000000***** OSPFD ***** .. include:: defines.rst .. |DAEMON| replace:: ospfd SYNOPSIS ======== |DAEMON| |synopsis-options-hv| |DAEMON| |synopsis-options| DESCRIPTION =========== |DAEMON| is a routing component that works with the FRRouting routing engine. OPTIONS ======= OPTIONS available for the |DAEMON| command: .. include:: common-options.rst .. option:: -a, --apiserver Enable the OSPF API server. FILES ===== |INSTALL_PREFIX_SBIN|/|DAEMON| The default location of the |DAEMON| binary. |INSTALL_PREFIX_ETC|/|DAEMON|.conf The default location of the |DAEMON| config file. $(PWD)/|DAEMON|.log If the |DAEMON| process is configured to output logs to a file, then you will find this file in the directory where you started |DAEMON|. .. include:: epilogue.rst frr-7.2.1/doc/manpages/frr-pbrd.rst0000644000000000000000000000130513610377563014023 00000000000000**** PBRD **** .. include:: defines.rst .. |DAEMON| replace:: pbrd SYNOPSIS ======== |DAEMON| |synopsis-options-hv| |DAEMON| |synopsis-options| DESCRIPTION =========== |DAEMON| is a routing component that works with the FRRouting engine. OPTIONS ======= OPTIONS available for the |DAEMON| command: .. include:: common-options.rst FILES ===== |INSTALL_PREFIX_SBIN|/|DAEMON| The default location of the |DAEMON| binary. |INSTALL_PREFIX_ETC|/|DAEMON|.conf The default location of the |DAEMON| config file. $(PWD)/|DAEMON|.log If the |DAEMON| process is configured to output logs to a file, then you will find this file in the directory where you started |DAEMON|. .. include:: epilogue.rst frr-7.2.1/doc/manpages/frr-pimd.rst0000644000000000000000000000131713610377563014030 00000000000000**** PIMD **** .. include:: defines.rst .. |DAEMON| replace:: pimd SYNOPSIS ======== |DAEMON| |synopsis-options-hv| |DAEMON| |synopsis-options| DESCRIPTION =========== |DAEMON| is a routing component that works with the FRRouting routing engine. OPTIONS ======= OPTIONS available for the |DAEMON| command: .. include:: common-options.rst FILES ===== |INSTALL_PREFIX_SBIN|/|DAEMON| The default location of the |DAEMON| binary. |INSTALL_PREFIX_ETC|/|DAEMON|.conf The default location of the |DAEMON| config file. $(PWD)/|DAEMON|.log If the |DAEMON| process is configured to output logs to a file, then you will find this file in the directory where you started |DAEMON|. .. include:: epilogue.rst frr-7.2.1/doc/manpages/frr-ripd.rst0000644000000000000000000000131713610377563014035 00000000000000**** RIPD **** .. include:: defines.rst .. |DAEMON| replace:: ripd SYNOPSIS ======== |DAEMON| |synopsis-options-hv| |DAEMON| |synopsis-options| DESCRIPTION =========== |DAEMON| is a routing component that works with the FRRouting routing engine. OPTIONS ======= OPTIONS available for the |DAEMON| command: .. include:: common-options.rst FILES ===== |INSTALL_PREFIX_SBIN|/|DAEMON| The default location of the |DAEMON| binary. |INSTALL_PREFIX_ETC|/|DAEMON|.conf The default location of the |DAEMON| config file. $(PWD)/|DAEMON|.log If the |DAEMON| process is configured to output logs to a file, then you will find this file in the directory where you started |DAEMON|. .. include:: epilogue.rst frr-7.2.1/doc/manpages/frr-ripngd.rst0000644000000000000000000000132713610377563014363 00000000000000****** RIPNGD ****** .. include:: defines.rst .. |DAEMON| replace:: ripngd SYNOPSIS ======== |DAEMON| |synopsis-options-hv| |DAEMON| |synopsis-options| DESCRIPTION =========== |DAEMON| is a routing component that works with the FRRouting routing engine. OPTIONS ======= OPTIONS available for the |DAEMON| command: .. include:: common-options.rst FILES ===== |INSTALL_PREFIX_SBIN|/|DAEMON| The default location of the |DAEMON| binary. |INSTALL_PREFIX_ETC|/|DAEMON|.conf The default location of the |DAEMON| config file. $(PWD)/|DAEMON|.log If the |DAEMON| process is configured to output logs to a file, then you will find this file in the directory where you started |DAEMON|. .. include:: epilogue.rst frr-7.2.1/doc/manpages/frr-sharpd.rst0000644000000000000000000000131513610377563014356 00000000000000****** SHARPD ****** .. include:: defines.rst .. |DAEMON| replace:: sharpd SYNOPSIS ======== |DAEMON| |synopsis-options-hv| |DAEMON| |synopsis-options| DESCRIPTION =========== |DAEMON| is a routing component that works with the FRRouting engine. OPTIONS ======= OPTIONS available for the |DAEMON| command: .. include:: common-options.rst FILES ===== |INSTALL_PREFIX_SBIN|/|DAEMON| The default location of the |DAEMON| binary. |INSTALL_PREFIX_ETC|/|DAEMON|.conf The default location of the |DAEMON| config file. $(PWD)/|DAEMON|.log If the |DAEMON| process is configured to output logs to a file, then you will find this file in the directory where you started |DAEMON|. .. include:: epilogue.rst frr-7.2.1/doc/manpages/frr-staticd.rst0000644000000000000000000000132113610377563014525 00000000000000******* STATICD ******* .. include:: defines.rst .. |DAEMON| replace:: staticd SYNOPSIS ======== |DAEMON| |synopsis-options-hv| |DAEMON| |synopsis-options| DESCRIPTION =========== |DAEMON| is a routing component that works with the FRRouting engine. OPTIONS ======= OPTIONS available for the |DAEMON| command: .. include:: common-options.rst FILES ===== |INSTALL_PREFIX_SBIN|/|DAEMON| The default location of the |DAEMON| binary. |INSTALL_PREFIX_ETC|/|DAEMON|.conf The default location of the |DAEMON| config file. $(PWD)/|DAEMON|.log If the |DAEMON| process is configured to output logs to a file, then you will find this file in the directory where you started |DAEMON|. .. include:: epilogue.rst frr-7.2.1/doc/manpages/frr-vrrpd.rst0000644000000000000000000000147013610377563014234 00000000000000***** VRRPD ***** .. include:: defines.rst .. |DAEMON| replace:: vrrpd SYNOPSIS ======== |DAEMON| |synopsis-options-hv| |DAEMON| |synopsis-options| DESCRIPTION =========== |DAEMON| is a routing component that works with the FRRouting routing engine. It implements the Virtual Router Redundancy Protocol. Support for both VRRPv2 and VRRPv3 is present. OPTIONS ======= OPTIONS available for the |DAEMON| command: .. include:: common-options.rst FILES ===== |INSTALL_PREFIX_SBIN|/|DAEMON| The default location of the |DAEMON| binary. |INSTALL_PREFIX_ETC|/|DAEMON|.conf The default location of the |DAEMON| config file. $(PWD)/|DAEMON|.log If the |DAEMON| process is configured to output logs to a file, then you will find this file in the directory where you started |DAEMON|. .. include:: epilogue.rst frr-7.2.1/doc/manpages/frr-watchfrr.rst0000644000000000000000000001132713610377563014721 00000000000000******** WATCHFRR ******** .. include:: defines.rst .. |DAEMON| replace:: watchfrr SYNOPSIS ======== |DAEMON| |synopsis-options-hv| |DAEMON| [option...] ... DESCRIPTION =========== |DAEMON| is a watchdog program that monitors the status of supplied frr daemons and tries to restart them in case they become unresponsive or shut down. To determine whether a daemon is running, it tries to connect to the daemon's VTY UNIX stream socket, and send echo commands to ensure the daemon responds. When the daemon crashes, EOF is received from the socket, so that |DAEMON| can react immediately. In order to avoid restarting the daemons in quick succession, you can supply the -m and -M options to set the minimum and maximum delay between the restart commands. The minimum restart delay is recalculated each time a restart is attempted. If the time since the last restart attempt exceeds twice the value of -M, the restart delay is set to the value of -m, otherwise the interval is doubled (but capped at the value of -M). OPTIONS ======= .. option:: --dry Run |DAEMON| in "dry-run" mode, only monitoring the specified daemons but not performing any start/stop/restart actions. .. option:: -d, --daemon Run in daemon mode. When supplied, error messages are sent to Syslog instead of standard output (stdout). .. option:: -S , --statedir Set the VTY socket directory (the default value is "/var/run/frr"). .. option:: -l , --loglevel Set the logging level (the default value is "6"). The value should range from 0 (LOG_EMERG) to 7 (LOG_DEBUG), but higher number can be supplied if extra debugging messages are required. .. option:: --min-restart-interval Set the minimum number of seconds to wait between invocations of the daemon restart commands (the default value is "60"). .. option:: --max-restart-interval Set the maximum number of seconds to wait between invocations of the daemon restart commands (the default value is "600"). .. option:: -i , --interval Set the status polling interval in seconds (the default value is "5"). .. option:: -t , --timeout Set the unresponsiveness timeout in seconds (the default value is "10"). .. option:: -T , --restart-timeout Set the restart (kill) timeout in seconds (the default value is "20"). If any background jobs are still running after this period has elapsed, they will be killed. .. option:: -p , --pid-file Set the process identifier filename (the default value is "/var/run/frr/|DAEMON|.pid"). .. option:: -b , --blank-string When the supplied string is found in any of the command line option arguments (i.e., -r, -s, or -k), replace it with a space. This is an ugly hack to circumvent problems with passing the command line arguments containing embedded spaces. .. option:: -v, --version Display the version information and exit. .. option:: -h, --help Display the usage information and exit. The following 3 options specify scripts that |DAEMON| uses to perform start/stop/restart actions. Reasonable default values are built into watchfrr, so the use of these options should no longer be necessary: .. option:: -s command, --start-command command Supply a Bourne shell command to start a single daemon. The command string should contain the '%s' placeholder to be substituted with the daemon name. .. option:: -k command, --kill-command command Supply a Bourne shell command to stop a single daemon. The command string should contain the '%s' placeholder to be substituted with the daemon name. .. option:: -r command, --restart command Supply a Bourne shell command to restart a single daemon. The command string should contain the '%s' placeholder to be substituted with the daemon name. PREVIOUS OPTIONS ================ Prior versions of |DAEMON| supported some additional options that no longer exist::: -a, -A, -e, -R, -z The ``-a``, ``-A`` and ``-R`` options were used to select alternate monitoring modes that offered different patterns of restarting daemons. The "correct" mode (phased restart) is now the default. The -e and -z options used to disable some monitoring aspects, |DAEMON| now always has all monitoring features enabled. Removing these options should result in correct operation, if it does not please file a bug report. FILES ===== |INSTALL_PREFIX_SBIN|/|DAEMON| The default location of the |DAEMON| binary. |INSTALL_PREFIX_ETC|/|DAEMON|.conf The default location of the |DAEMON| config file. $(PWD)/|DAEMON|.log If the |DAEMON| process is configured to output logs to a file, then you will find this file in the directory where you started |DAEMON|. .. include:: epilogue.rst frr-7.2.1/doc/manpages/frr-zebra.rst0000644000000000000000000000347613610377563014212 00000000000000***** ZEBRA ***** .. include:: defines.rst .. |DAEMON| replace:: zebra SYNOPSIS ======== |DAEMON| |synopsis-options-hv| |DAEMON| |synopsis-options| DESCRIPTION =========== |DAEMON| is a routing manager that implements the zebra route engine. zebra supports all protocol daemons in the FRRouting suite. OPTIONS ======= OPTIONS available for the |DAEMON| command: .. include:: common-options.rst .. option:: -b, --batch Runs in batch mode, zebra parses its config and exits. .. option:: -k, --keep_kernel On startup, don't delete self inserted routes. .. option:: -s, --nl-bufsize Set netlink receive buffer size. There are cases where zebra daemon can't handle flood of netlink messages from kernel. If you ever see "recvmsg overrun" messages in zebra log, you are in trouble. Solution is to increase receive buffer of netlink socket. Note that kernel < 2.6.14 doesn't allow increasing it over maximum value defined in /proc/sys/net/core/rmem_max. If you want to do it, you have to increase maximum before starting zebra. Note that this affects Linux only. .. option:: -n, --vrfwnetns Enable namespace VRF backend. By default, the VRF backend relies on VRF-lite support from the Linux kernel. This option permits discovering Linux named network namespaces and mapping it to FRR VRF contexts. ROUTES ------ .. option:: -r, --retain When the program terminates, do not flush routes installed by zebra from the kernel. FILES ===== |INSTALL_PREFIX_SBIN|/|DAEMON| The default location of the |DAEMON| binary. |INSTALL_PREFIX_ETC|/|DAEMON|.conf The default location of the |DAEMON| config file. $(PWD)/|DAEMON|.log If the |DAEMON| process is configured to output logs to a file, then you will find this file in the directory where you started |DAEMON|. .. include:: epilogue.rst frr-7.2.1/doc/manpages/frr.rst0000644000000000000000000000101613610377563013075 00000000000000*** FRR *** .. include:: defines.rst SYNOPSIS ======== frr [ start ] frr [ stop ] frr [ reload ] frr [ restart ] frr [ status ] DESCRIPTION =========== frr is a systemd interaction script for the FRRouting routing engine. OPTIONS ======= Options available for the frr command: start Start enabled FRR daemons stop Stop enabled FRR daemons reload Reload modified configuration files restart Stop all running daemons and then restart them status Status of all the daemon .. include:: epilogue.rst frr-7.2.1/doc/manpages/index.rst0000644000000000000000000000102213610377563013410 00000000000000.. FRR documentation master file, created by sphinx-quickstart on Wed Jan 31 12:00:55 2018. You can adapt this file completely to your liking, but it should at least contain the root `toctree` directive. .. toctree:: :maxdepth: 2 frr frr-bfdd frr-bgpd frr-eigrpd frr-isisd frr-fabricd frr-ldpd frr-nhrpd frr-ospf6d frr-ospfclient frr-ospfd frr-pimd frr-pbrd frr-ripd frr-ripngd frr-sharpd frr-staticd frr-watchfrr frr-zebra frr-vrrpd mtracebis vtysh frr-7.2.1/doc/manpages/mtracebis.rst0000644000000000000000000000221213610377563014254 00000000000000********* MTRACEBIS ********* .. include:: defines.rst .. |PROGRAM| replace:: mtracebis SYNOPSIS ======== |PROGRAM| |synopsis-options-hv| |PROGRAM| [] DESCRIPTION =========== |PROGRAM| is a program for initiating multicast traceroute, or "mtrace", queries. It can initiate two types of mtrace queries: weak and group. Weak tests whether the interfaces towards the source are multicast enabled and is initiated by supplying only the multicast source address. Group tests whether there is multicast routing protocol state for particular multicast group and is initiated by supplying mutlicast source and group. The first query sent is a full query, capable of crossing the network all the way to the source. If this fails, hop-by-hop queries are initiated. Hop-by-hop queries start by requesting only a response from the nearest router. Following that, next query is extended to the next two routers, and so on... until a set of routers is tested for connectivity. FILES ===== |INSTALL_PREFIX_SBIN|/|PROGRAM| The default location of the |PROGRAM| binary. .. include:: epilogue.rst AUTHORS ======= Mladen Sablic frr-7.2.1/doc/manpages/subdir.am0000644000000000000000000000300413610377563013360 00000000000000# # doc/manpages # man_RSTFILES = \ doc/manpages/bfd-options.rst \ doc/manpages/common-options.rst \ doc/manpages/conf.py \ doc/manpages/defines.rst \ doc/manpages/epilogue.rst \ doc/manpages/frr-bfdd.rst \ doc/manpages/frr-bgpd.rst \ doc/manpages/frr-eigrpd.rst \ doc/manpages/frr-fabricd.rst \ doc/manpages/frr-isisd.rst \ doc/manpages/frr-ldpd.rst \ doc/manpages/frr-nhrpd.rst \ doc/manpages/frr-ospf6d.rst \ doc/manpages/frr-ospfclient.rst \ doc/manpages/frr-ospfd.rst \ doc/manpages/frr-pbrd.rst \ doc/manpages/frr-pimd.rst \ doc/manpages/frr-ripd.rst \ doc/manpages/frr-ripngd.rst \ doc/manpages/frr-sharpd.rst \ doc/manpages/frr-staticd.rst \ doc/manpages/frr-vrrpd.rst \ doc/manpages/frr-watchfrr.rst \ doc/manpages/frr-zebra.rst \ doc/manpages/frr.rst \ doc/manpages/index.rst \ doc/manpages/mtracebis.rst \ doc/manpages/vtysh.rst \ # end EXTRA_DIST += $(man_RSTFILES) MANPARENT = doc/manpages/_build MANBUILD = $(MANPARENT)/man doc/manpages/_build/.doctrees/environment.pickle: $(man_RSTFILES) # # automake integration # rstman1dir = $(mandir)/man1 rstman8dir = $(mandir)/man8 rstman1_DATA = rstman8_DATA = if DOC rstman1_DATA += $(man1) rstman8_DATA += $(man8) endif # DOC man1 = $(MANBUILD)/frr.1 man8 = # dependency $(man8) $(man1): $(MANBUILD)/man.stamp # # hook-ins for clean / doc # (install is handled by automake _DATA) # clean-local: clean-manpages .PHONY: clean-manpages clean-manpages: -rm -rf $(MANPARENT) doc: doc-man .PHONY: doc-man doc-man: $(rstman8_DATA) $(rstman1_DATA) frr-7.2.1/doc/manpages/vtysh.rst0000644000000000000000000000667313610377563013477 00000000000000***** VTYSH ***** .. include:: defines.rst .. |DAEMON| replace:: vtysh SYNOPSIS ======== vtysh [ -b ] vtysh [ -E ] [ -d *daemon* ] [ -c *command* ] DESCRIPTION =========== vtysh is an integrated shell for the FRRouting suite of protocol daemons. OPTIONS ======= OPTIONS available for the vtysh command: .. option:: -b, --boot Execute boot startup configuration. It makes sense only if integrated config file is in use (not default in FRRouting). See Info file frr for more info. .. option:: -c, --command command Specify command to be executed under batch mode. It behaves like -c option in any other shell - command is executed and vtysh exits. It's useful for gathering info from FRRouting daemons or reconfiguring daemons from inside shell scripts, etc. Note that multiple commands may be executed by using more than one -c option and/or embedding linefeed characters inside the command string. .. option:: -d, --daemon daemon_name Specify which daemon to connect to. By default, vtysh attempts to connect to all FRRouting daemons running on the system. With this flag, one can specify a single daemon to connect to instead. For example, specifying '-d ospfd' will connect only to ospfd. This can be particularly useful inside scripts with -c where the command is targeted for a single daemon. .. option:: -e, --execute command Alias for -c. It's here only for compatibility with Zebra routing software and older FRR versions. This will be removed in future. .. option:: -E, --echo When the -c option is being used, this flag will cause the standard vtysh prompt and command to be echoed prior to displaying the results. This is particularly useful to separate the results when executing multiple commands. .. option:: -C, --dryrun When the -C option is being used, this flag will check the config for syntatic validity. .. option:: -m, --markfile Mark the input file with context ends, useful for cleanup of a config file that has a lot of extraneous space and end markers .. option:: -n, --noerror When executing cli that does not invoke a vtysh shell, if an error ocurrs ignore it for purposes of return codes from vtysh. .. option:: -u, --user Restrict access to configuration commands by preventing use of the "enable" command. This option provides the same limited "security" as password-protected telnet access. *This security should not be relied on in production environments.* Caveat emptor: VTYSH was never designed to be a privilege broker and is not built using secure coding practices. No guarantees of security are provided for this option and under no circumstances should this option be used to provide any semblance of secure read-only access to FRR. .. option:: -h, --help Display a usage message on standard output and exit. ENVIRONMENT VARIABLES ===================== VTYSH_PAGER This should be the name of the pager to use. Default is more. FILES ===== |INSTALL_PREFIX_SBIN|/vtysh The default location of the vtysh binary. |INSTALL_PREFIX_ETC|/vtysh.conf The default location of the vtysh config file. |INSTALL_PREFIX_ETC|/frr.conf The default location of the integrated FRRouting routing engine config file if integrated config file is in use. ${HOME}/.history_frr Location of history of commands entered via cli $(PWD)/|DAEMON|.log If the |DAEMON| process is configured to output logs to a file, then you will find this file in the directory where you started |DAEMON|. .. include:: epilogue.rst frr-7.2.1/doc/mpls/0000755000000000000000000000000013610377563011014 500000000000000frr-7.2.1/doc/mpls/ChangeLog.opaque.txt0000644000000000000000000002067413610377563014626 00000000000000----- * ----- * ----- * ----- * ----- * ----- * ----- * ----- * ----- * ----- Changes 2013.07.01 1. Feature enhancements 1.1 Update ospf_te.[c,h] in conformance to RFC3630 and clean the code. Add new directive to enable MPLS-TE per interface instead of globally 1.2 Add support for RFC4970 "Router Information" and RFC5088 "PCE Capabilities announcement". 1.3 Incorporate the mpls documentation into the main stream doc. ----- * ----- * ----- * ----- * ----- * ----- * ----- * ----- * ----- * ----- Changes 2001.12.03 1. Bug fixes 1.1 Though a new member "oi" has added to "struct ospf_lsa" to control flooding scope of type-9 Opaque-LSAs, the value was always NULL because no one set it. 1.2 In the function "show_ip_ospf_database_summary()" and "show_lsa_ detail_adv_router()", VTY output for type-11 Opaque-LSAs did not work properly. 1.3 URL for the opaque-type assignment reference has changed. 1.4 In the file "ospf_mpls_te.c", printf formats have changed to avoid compiler warning messages; "%lu" -> "%u", "%lx" -> "%x". Note that this hack depends on OS, compiler and their versions. 1.5 One of attached documentation "opaque_lsa.txt" has changed to reflect the latest coding. 2. Feature enhancements 2.1 Knowing that it is an ugly hack, an "officially unallocated" opaque-type value 0 has newly introduced as a "wildcard", which matches to all opaque-type. This value must not be flooded to the network, of course. 2.2 The Opaque-core module makes use of newly introduced hooks to dispatch every LSDB change (LSA installation and deletion) to preregistered opaque users. Therefore, by providing appropriate callback functions as new parameters of "ospf_register_opaque_functab()", an opaque user can refer to every LSA instance to be installed into, or to be deleted from, the LSDB. ----- * ----- * ----- * ----- * ----- * ----- * ----- * ----- * ----- * ----- Changes 2001.10.31 1. Bug fixes 1.1 Since each LSA has their own lifetime, they will remain in a routing domain (being stored in LSDB of each router), until their age naturally reach to MaxAge or explicitly being flushed by the originated router. Therefore, if a router restarted with a short downtime, it is possible that previously flooded self-originated LSAs might received if the NSM status is not less than Exchange. There were some problems in the way of handling self-originated Opaque-LSAs if they are contained in a received LSUpd message, but not installed to the local LSDB yet. Regardless of some conditions to start originating Opaque-LSAs (there should be at least one opaque-capable full-state neighbor), the function "ospf_flood()" will be called to flood and install this brand-new looking LSA. As the result, when the NSM of an opaque-capable neighbor gets full, internal state inconsistency happens; a user of Opaque-LSA such as MPLS-TE can refer to self-originated LSAs in the local LSDB, but cannot modify their contents... Above problems have fixed with a policy "flush it from the whole routing domain and keep silent until the flushing completed". By using this sweeping technique, we can be free from confusion caused by self-originated LSAs received via network. 1.2 The function "ospf_opaque_type_name()" contained massive ifdefs corresponding to each "opaque-type". These unnecessary ifdefs are removed completely. 1.3 In the function "ospf_delete_opaque_functab()", there was an improper loop control that causes illegal memory access. Original coding was "next = nextnode (node)". 1.4 The function "ospf_mpls_te_ism_change()" could not handle the case when the ISM changes from Waiting to DR/BDR/Other. So, there was a case that even if one of an ISM become operational and MPLS-TE module has started, the corresponding Opaque-LSA cannot be originated. 1.5 The function "ospf_opaque_lsa_reoriginate_schedule()" did not allow to be called multiple times, simply because handling module for the given "lsa-type & opaque-type" already exists. But this assumption seems to be wrong. Change the policy to allow this function to be called multiple times and let the caller to decide what should do when the corresponding callback function "(* functab->lsa_originator)()" is called. 2. Feature enhancements 2.1 The global bitmap "opaque" has introduced instead of former flag "OpaqueCapable", to store complex conditions to handle Opaque-LSAs. 2.2 The MPLS-TE module now referes to "draft-katz-yeung-ospf-traffic -06.txt", no significant changes with 05 version, though. ----- * ----- * ----- * ----- * ----- * ----- * ----- * ----- * ----- * ----- Changes 2001.08.03 1. Bug fixes 1.1 Even if the ospfd started with opaque capability enabled, when the ospfd receives an unknown opaque-type (unregistered by the function "ospf_register_opaque_functab()" beforehand), the LSA was discarded. As the result, only the opaque-LSAs that have commonly registered by opaque-capable ospf routers can be flooded in a routing domain. This behavior has fixed so that arbitrary opaque-type LSAs can be flooded among opaque-capable ospf routers. If the ospfd has opaque-LSA capability but disabled at runtime, received opaque-LSAs can be accepted and registered to LSDB as is, but not be flooded to the network; those opaque LSAs will remain in LSDB until explicitly flushed by incoming LSUpd messages with MaxAge, or their age naturally reaches to MaxAge. 1.2 The function "ospf_register_opaque_functab()" did not check if the entry corresponding to the given "lsa-type, opaque-type" combination already exists or not. This problem has fixed not to allow multiple registration. 1.3 Since type-11 (AS external) LSAs will be flooded beyond areas, there is little relationship between "struct lsa" and "struct area". More specifically, the pointer address "lsa->area" can be NULL if the lsa-type is 11, thus an illegal memory access will happen. This problem has fixed. 1.4 When self-originated opaque-LSAs are received via network and if the corresponding opaque-type functions are not available (they have already deleted) at that time, those LSAs were dropped due to "unknown opaque-type" error. After the problem 1.1 has fixed, those "self-originated" LSAs were registered to LSDB and then flooded to the network, even if the processing functions did not exist... After all, this problem has fixed so that those LSAs should explicitly be flushed from the routing domain immediately, if the processing functions cannot find at that time. 1.5 Some typo have fixed. --- EXAMPLE --- static int opaque_lsa_originate_callback (list funclist, void *lsa_type_dependent) ^^^^^ --- EXAMPLE --- 2. Feature enhancements 2.1 According to the description of rfc2328 in section 10.8, any change in the router's optional capabilities should trigger the option re-negotiation procedures with neighbors. --- EXCERPT --- If for some reason the router's optional capabilities change, the Database Exchange procedure should be restarted by reverting to neighbor state ExStart. --- EXCERPT --- For the opaque-capability changes, this feature has implemented. More specifically, if "ospf opaque-lsa" or "no ospf opaque-lsa" VTY command is given at runtime, all self-originated LSAs will be flushed immediately and then all neighbor status will be forced to ExStart by generating SeqNumberMismatch events. 2.1 When we change opaque-capability dynamically (ON -> OFF -> ON), there was no trigger at "OFF->ON" timing to reactivate opaque LSA handling modules (such as MPLS-TE) that have once forcibly stopped at "ON->OFF" timing. Now this dynamic reactivation feature has added. 2.2 The MPLS-TE module now referes to "draft-katz-yeung-ospf-traffic -05.txt", no significant changes with 04 version, though. ----- * ----- * ----- * ----- * ----- * ----- * ----- * ----- * ----- * ----- Changes 2001.03.28 Initial release of Opaque-LSA/MPLS-TE extensions for the zebra/ospfd. frr-7.2.1/doc/mpls/cli_summary.txt0000644000000000000000000000525513610377563014030 00000000000000Summary of CLI commands, expanded for Opaque-LSA/MPLS-TE. --------------------------------------------------------- router> show ip ospf database (asbr-summary|external|max-age|network|router|self-originate|summary|opaque-link|opaque-area|opaque-external) show ip ospf database (asbr-summary|external|network|router|summary|opaque-link|opaque-area|opaque-external) (self-originate|) show ip ospf database (asbr-summary|external|network|router|summary|opaque-link|opaque-area|opaque-external) A.B.C.D show ip ospf database (asbr-summary|external|network|router|summary|opaque-link|opaque-area|opaque-external) A.B.C.D (self-originate|) show ip ospf database (asbr-summary|external|network|router|summary|opaque-link|opaque-area|opaque-external) A.B.C.D adv-router A.B.C.D show ip ospf database (asbr-summary|external|network|router|summary|opaque-link|opaque-area|opaque-external) adv-router A.B.C.D --> Add database items: opaque-link, opaque-area, opaque-external show mpls-te interface [INTERFACE] --> Show current MPLS-TE link-TLV parameters. If [INTERFACE] is omitted, all interfaces will be displayed. show mpls-te router --> Show current MPLS-TE Router-TLV parameters. router> enable router# router# configure terminal router(config)# interface [INTERFACE] router(config-if)# mpls-te link max-bw BANDWIDTH --> Set MPLS-TE link-TLV parameter: Maximum Bandwidth (Bytes/sec). In integer or floating point format (1000, or 1.0e3) mpls-te link max-rsv-bw BANDWIDTH --> Set MPLS-TE link-TLV parameter: Maximum Reservable Bandwidth (Bytes/sec). In integer or floating point format (1000, or 1.0e3) mpls-te link metric <0-4294967295> --> Set MPLS-TE link-TLV parameter: MPLS-TE metric. mpls-te link rsc-clsclr BITPATTERN --> Set MPLS-TE link-TLV parameter: Resource Class/Color. In 32-bit hexadecimal format, with leading "0x" (0x0 - 0xffffffff) mpls-te link unrsv-bw <0-7> BANDWIDTH --> Set MPLS-TE link-TLV parameter: Unreserved Bandwidth (Bytes/sec). In integer or floating point format (1000, or 1.0e3) router(config-if)# exit router(config)# router ospf router(config-router)# mpls-te --> Enable MPLS-TE functionality. Note that master-switch "ospf opaque-lsa" must also be specified. mpls-te on --> Alias of "mpls-te" command. mpls-te router-address A.B.C.D --> Set MPLS-TE Router-TLV parameter: Router Address. no mpls-te --> Disable MPLS-TE functionality. no ospf opaque-lsa --> Disable Opaque-LSAs capability. This node behaves Opaque-incapable node. ospf opaque-lsa --> Enable Opaque-LSAs capability. This is the master-switch to make this node Opaque-capable. router# exit frr-7.2.1/doc/mpls/opaque_lsa.txt0000644000000000000000000005505013610377563013633 000000000000001. List of "opaque-type dependent" callback functions per LSA-type. (N = 9,10,11) | | struct | list struct struct +-> +-------+ listnode listnode | head |-----> +------+ +------ | tail | | next |--------------------> | next | count | /--| prev |<---------------------| prev +-------+ | data |----+ | |///////| +------+ | +-------+ | | struct | ospf_opaque_tabent | +----------------------+ <--+ | opaque_type | +----------------------+ | (Callback functions) | +----------------------+ 2. Self-originated Opaque-LSAs per LSA-type. 2.1 Type-11 (AS-external) Opaque-LSAs struct ospf +---> +-------------------+ | |///////////////////| | +-------------------+ | | opaque | | +-------------------+ | |///////////////////| | +-------------------+ | | opaque_lsa_self |---+ | +-------------------+ | | |///////////////////| | | +-------------------+ | | | ......|.............................|....................................... : | | Almost common for type-9,10,11 LSA : : | +-----------------------+ : : | | : : | | struct : : | | list struct struct : : | +-> +-------+ listnode listnode : : | | head |-----> +------+ +------ : : | | tail | | next |--------------------> | next : : | | count | /--| prev |<---------------------| prev : : | +-------+ | data |---+ | : : | |///////| +------+ | : : | +-------+ | : : | | : : | struct | : : | opaque_info_per_type | : : | +-------------------+ <--------+ : : | | opaque_type | <------------+ : : | +-------------------+ | : : | | status | | : : | +-------------------+ | : : | | t_opaque_lsa_self | | : : | +-------------------+ | : : +-----| owner | | struct : : +-------------------+ | ospf_opaque_tabent : : | functab |-------------------> +---------------- : : +-------------------+ | | opaque_type : : | id_list |---+ | |(Callback Funcs) : : +-------------------+ | | | : : | | : : +-----------------------+ | : : | | : : | struct | : : | list struct | struct : : +-> +-------+ listnode | listnode : : | head |-----> +------+ | +------ : : | tail | | next |--------------------> | next : : | count | /--| prev |<---------------------| prev : : +-------+ | data |---+ | | : : |///////| +------+ | | : : +-------+ | | : : | | : : struct | | : : opaque_info_per_id | | : : +-------------------+ <--------+ | : : | opaque_id | | : : +-------------------+ | : : | t_opaque_lsa_self | | : : +-------------------+ | : : | opqctl_type |--------------+ : : +-------------------+ : : | lsa |---+ : : +-------------------+ | : : | : : struct | : : ospf_lsa | : : +-------------+ <-------+ : : |/////////////| struct : : +-------------+ lsa_header : : | data |--------------> +-------- : : +-------------+ | : : |/////////////| : : +-------------+ : : +--------| area | : : | +-------------+ : : --- |/////////////| : : +-------------+ : : +-----| oi | : : | +-------------+ : : --- : :..........................................................................: 2.2 Type-10 (area-local) Opaque-LSAs struct ospf +---------+ <-----------+ |/////////| | +---------+ | | struct | ospf_area | +--+---> +-----------------+ | | | | top |-----+ | | +-----------------+ | | |/////////////////| struct | | +-----------------+ ospf_lsa | | | router_lsa_self |-----------> +--------- | | +-----------------+ | | | | opaque_lsa_self |-----+ | | | +-----------------+ | | | |/////////////////| | | | +-----------------+ | | | | ...|..|.............................|....................................... : | | | Almost common for type-9,10,11 LSA : : | | +-----------------------+ : : | | | : : | | | struct : : | | | list struct struct : : | | +-> +-------+ listnode listnode : : | | | head |-----> +------+ +------ : : | | | tail | | next |--------------------> | next : : | | | count | /--| prev |<---------------------| prev : : | | +-------+ | data |---+ | : : | | |///////| +------+ | : : | | +-------+ | : : | | | : : | | struct | : : | | opaque_info_per_type | : : | | +-------------------+ <--------+ : : | | | opaque_type | <------------+ : : | | +-------------------+ | : : | | | status | | : : | | +-------------------+ | : : | | | t_opaque_lsa_self | | : : | | +-------------------+ | : : | +-----| owner | | struct : : | +-------------------+ | ospf_opaque_tabent : : | | functab |-------------------> +---------------- : : | +-------------------+ | | opaque_type : : | | id_list |---+ | |(Callback Funcs) : : | +-------------------+ | | | : : | | | : : | +-----------------------+ | : : | | | : : | | struct | : : | | list struct | struct : : | +-> +-------+ listnode | listnode : : | | head |-----> +------+ | +------ : : | | tail | | next |--------------------> | next : : | | count | /--| prev |<---------------------| prev : : | +-------+ | data |---+ | | : : | |///////| +------+ | | : : | +-------+ | | : : | | | : : | struct | | : : | opaque_info_per_id | | : : | +-------------------+ <--------+ | : : | | opaque_id | | : : | +-------------------+ | : : | | t_opaque_lsa_self | | : : | +-------------------+ | : : | | opqctl_type |--------------+ : : | +-------------------+ : : | | lsa |---+ : : | +-------------------+ | : : | | : : | struct | : : | ospf_lsa | : : | +-------------+ <-------+ : : | |/////////////| struct : : | +-------------+ lsa_header : : | | data |--------------> +-------- : : | +-------------+ | : : | |/////////////| : : | +-------------+ : : +--------| area | : : +-------------+ : : |/////////////| : : +-------------+ : : +-----| oi | : : | +-------------+ : : --- : :..........................................................................: 2.3 Type-9 (link-local) Opaque-LSAs struct ospf_area +------> +---------+ <---------+ | |/////////| | | +---------+ | | | | struct | | ospf_interface | | +-+-> +-----------------+ | | | | |/////////////////| | | | | +-----------------+ | | | | | area |---+ | | | +-----------------+ | | | |/////////////////| struct | | | +-----------------+ ospf_lsa | | | |network_lsa_self |-----------> +--------- | | | +-----------------+ | | | | | opaque_lsa_self |-----+ | | | | +-----------------+ | | | | |/////////////////| | | | | +-----------------+ | | | | | ...|..|.|...........................|....................................... : | | | | Almost common for type-9,10,11 LSA : : | | | +-----------------------+ : : | | | | : : | | | | struct : : | | | | list struct struct : : | | | +-> +-------+ listnode listnode : : | | | | head |-----> +------+ +------ : : | | | | tail | | next |--------------------> | next : : | | | | count | /--| prev |<---------------------| prev : : | | | +-------+ | data |---+ | : : | | | |///////| +------+ | : : | | | +-------+ | : : | | | | : : | | | struct | : : | | | opaque_info_per_type | : : | | | +-------------------+ <--------+ : : | | | | opaque_type | <------------+ : : | | | +-------------------+ | : : | | | | status | | : : | | | +-------------------+ | : : | | | | t_opaque_lsa_self | | : : | | | +-------------------+ | : : | | +---| owner | | struct : : | | +-------------------+ | ospf_opaque_tabent : : | | | functab |-------------------> +---------------- : : | | +-------------------+ | | opaque_type : : | | | id_list |---+ | |(Callback Funcs) : : | | +-------------------+ | | | : : | | | | : : | | +-----------------------+ | : : | | | | : : | | | struct | : : | | | list struct | struct : : | | +-> +-------+ listnode | listnode : : | | | head |-----> +------+ | +------ : : | | | tail | | next |--------------------> | next : : | | | count | /--| prev |<---------------------| prev : : | | +-------+ | data |---+ | | : : | | |///////| +------+ | | : : | | +-------+ | | : : | | | | : : | | struct | | : : | | opaque_info_per_id | | : : | | +-------------------+ <--------+ | : : | | | opaque_id | | : : | | +-------------------+ | : : | | | t_opaque_lsa_self | | : : | | +-------------------+ | : : | | | opqctl_type |--------------+ : : | | +-------------------+ : : | | | lsa |---+ : : | | +-------------------+ | : : | | | : : | | struct | : : | | ospf_lsa | : : | | +-------------+ <-------+ : : | | |/////////////| struct : : | | +-------------+ lsa_header : : | | | data |--------------> +-------- : : | | +-------------+ | : : | | |/////////////| : : | | +-------------+ : : +--|-----| area | : : | +-------------+ : : | |/////////////| : : | +-------------+ : : +-----| oi | : : +-------------+ : :..........................................................................: 3. Internal structures for MPLS-TE parameter management. struct ospf_mpls_te +-------------+ | status | +-------------+ | iflist |---+ +-------------+ | |(Router-TLV) | | +-------------+ | | +---------------------+ | | struct | list struct struct +---> +-------+ listnode listnode | head |-----> +------+ +------ | tail | | next |--------------------> | next | count | /--| prev |<---------------------| prev +-------+ | data |---+ | |///////| +------+ | +-------+ | | +--------------------------------+ | | struct | ospf_mpls_te_linkparms +-> +----------------+ | instance | struct +----------------+ interface | ifp |--------------------> +----------+ +----------------+ +----> |//////////| | area |----+ | +----------+ +----------------+ | | | info |-----+ | flags | | | +----------+ | +----------------+ | | |//////////| | | (Link-TLV) | | | +----------+ | +----------------+ | | | | (Link-SubTLVs) | | | struct | +----------------+ | | ospf_if_info | | | +----------+ <---+ | | |//////////| struct | | +----------+ ospf_area | | | oifs |-----+ +-> +--------------+ <----+ | +----------+ | | |//////////////| | | | +--------------+ | struct | | | route_table | | struct | +-----------+ <--+ | ospf_interface | | route_top | - - - - -. | +--------------+ <----+ | +-----------+ . | |//////////////| | | . | +--------------+ | | struct . | | ifp |------|----------+ route_node . | +--------------+ | +-----------+ < - - - - | |//////////////| | |///////////| | +--------------+ | +-----------+ +---| area | +-----------------| info | +--------------+ +-----------+ |//////////////| |///////////| +--------------+ +-----------+ frr-7.2.1/doc/mpls/ospfd.conf0000644000000000000000000000307013610377563012716 00000000000000! ! Zebra configuration saved from vty ! 2001/03/16 22:07:53 ! hostname HOSTNAME password PASSWORD log file /var/log/ospfd.log ! debug ospf ism debug ospf nsm debug ospf lsa debug ospf zebra debug ospf event debug ospf packet all detail ! ! interface fxp0 ip ospf hello-interval 60 ip ospf dead-interval 240 mpls-te on mpls-te link metric 999 mpls-te link max-bw 1.25e+06 mpls-te link max-rsv-bw 1.25e+06 mpls-te link unrsv-bw 0 1.25e+06 mpls-te link unrsv-bw 1 1.25e+06 mpls-te link unrsv-bw 2 1.25e+06 mpls-te link unrsv-bw 3 1.25e+06 mpls-te link unrsv-bw 4 1.25e+06 mpls-te link unrsv-bw 5 1.25e+06 mpls-te link unrsv-bw 6 1.25e+06 mpls-te link unrsv-bw 7 1.25e+06 mpls-te link rsc-clsclr 0xab ! interface de1 ip ospf hello-interval 60 ip ospf dead-interval 240 mpls-te link metric 111 mpls-te link max-bw 1.25e+06 mpls-te link max-rsv-bw 1.25e+06 mpls-te link unrsv-bw 0 1.25e+06 mpls-te link unrsv-bw 1 1.25e+06 mpls-te link unrsv-bw 2 1.25e+06 mpls-te link unrsv-bw 3 1.25e+06 mpls-te link unrsv-bw 4 1.25e+06 mpls-te link unrsv-bw 5 1.25e+06 mpls-te link unrsv-bw 6 1.25e+06 mpls-te link unrsv-bw 7 1.25e+06 mpls-te link rsc-clsclr 0xcd ! interface de0 mpls-te link metric 0 mpls-te link rsc-clsclr 0x0 ! interface lp0 ip ospf network point-to-point ! interface tun0 ip ospf network point-to-point ! interface sl0 ip ospf network point-to-point ! interface ppp0 ip ospf network point-to-point ! interface lo0 ! router ospf compatible rfc1583 network 192.168.0.0/16 area 1 ospf opaque-lsa mpls-te mpls-te router-address 1.2.3.4 ! line vty ! frr-7.2.1/doc/subdir.am0000644000000000000000000001464213610377563011577 00000000000000# # doc # # You can set these variables from the command line. SPHINXOPTS ?= PAPER ?= # Internal variables. PAPEROPT_a4 = -D latex_paper_size=a4 PAPEROPT_letter = -D latex_paper_size=letter ALLSPHINXOPTS = $(PAPEROPT_$(PAPER)) $(SPHINXOPTS) # the i18n builder cannot share the environment and doctrees with the others I18NSPHINXOPTS = $(PAPEROPT_$(PAPER)) $(SPHINXOPTS) ### AM_V_SPHINX = $(am__v_SPHINX_$(V)) am__v_SPHINX_ = $(am__v_SPHINX_$(AM_DEFAULT_VERBOSITY)) am__v_SPHINX_0 = @echo " SPHINX " $@; am__v_SPHINX_1 = AM_V_MAKEINFO = $(am__v_MAKEINFO_$(V)) am__v_MAKEINFO_ = $(am__v_MAKEINFO_$(AM_DEFAULT_VERBOSITY)) am__v_MAKEINFO_0 = @echo " MAKEINFO" $@; am__v_MAKEINFO_1 = # # real-file sphinx targets that work for dependencies # doc/%/_build/.doctrees/environment.pickle: $(AM_V_SPHINX) ( \ subdoc="$@"; subdoc="$${subdoc#doc/}"; subdoc="doc/$${subdoc%%/*}"; \ $(PYTHON) $(PYSPHINX) -a -q -b text -d "$${subdoc}/_build/.doctrees" \ $(ALLSPHINXOPTS) "$(top_srcdir)/$${subdoc}" "$${subdoc}/_build/text" \ ) doc/%/_build/html/.buildinfo: doc/%/_build/.doctrees/environment.pickle $(AM_V_SPHINX) ( \ subdoc="$@"; subdoc="$${subdoc#doc/}"; subdoc="doc/$${subdoc%%/*}"; \ $(PYTHON) $(PYSPHINX) -q -b html -d "$${subdoc}/_build/.doctrees" \ $(ALLSPHINXOPTS) "$(top_srcdir)/$${subdoc}" "$${subdoc}/_build/html" \ ) .PRECIOUS: doc/%/_build/texinfo/frr.texi doc/%/_build/texinfo/frr.texi: doc/%/_build/.doctrees/environment.pickle $(AM_V_SPHINX) ( \ subdoc="$@"; subdoc="$${subdoc#doc/}"; subdoc="doc/$${subdoc%%/*}"; \ $(PYTHON) $(PYSPHINX) -q -b texinfo -d "$${subdoc}/_build/.doctrees" \ $(ALLSPHINXOPTS) "$(top_srcdir)/$${subdoc}" "$${subdoc}/_build/texinfo" \ ) doc/%/_build/texinfo/frr.info: doc/%/_build/texinfo/frr.texi $(AM_V_MAKEINFO)$(MAKEINFO) --no-split -o '$@' '$<' doc/%/_build/man/man.stamp: doc/%/_build/.doctrees/environment.pickle $(AM_V_SPHINX) ( \ subdoc="$@"; subdoc="$${subdoc#doc/}"; subdoc="doc/$${subdoc%%/*}"; \ $(MKDIR_P) "$${subdoc}/_build/man"; touch $@.tmp; \ $(PYTHON) $(PYSPHINX) -a -q -b man -d "$${subdoc}/_build/.doctrees" \ $(ALLSPHINXOPTS) "$(top_srcdir)/$${subdoc}" "$${subdoc}/_build/man" && \ mv -f $@.tmp $@ \ ) # # auxiliary sphinx targets (output name = directory, # deps will not work very well) # SPHINXTARGETS = \ html dirhtml singlehtml pickle json \ htmlhelp qthelp applehelp devhelp \ epub latex text man texinfo gettext \ changes linkcheck doctest coverage \ xml pseudoxml \ # end M_SPHINXTARGETS = $(addprefix doc/%/_build/,$(SPHINXTARGETS)) .PRECIOUS: $(M_SPHINXTARGETS) $(M_SPHINXTARGETS): doc/%/_build/.doctrees/environment.pickle $(AM_V_SPHINX) ( \ target="$@"; \ builder="$${target##*/}"; \ subdoc="$${target#doc/}"; subdoc="doc/$${subdoc%%/*}"; \ rm -rf "$@"; \ $(PYTHON) $(PYSPHINX) -q -b $${builder} -d $${subdoc}/_build/.doctrees \ $(ALLSPHINXOPTS) $(top_srcdir)/$${subdoc} $@ \ ) .PHONY: doc/%/_build/latexpdf doc/%/_build/latexpdf: doc/%/_build/latex @make -C $< all-pdf # If you want to build the developer's docs in other formats, try the # following: # # $ cd developer # $ make help # dist tarballs want doc sources EXTRA_DIST += \ doc/mpls/ChangeLog.opaque.txt \ doc/mpls/ospfd.conf \ doc/mpls/cli_summary.txt \ doc/mpls/opaque_lsa.txt \ doc/figures/cligraph.png \ doc/figures/cligraph.svg \ doc/figures/fig-normal-processing.dia \ doc/figures/fig-normal-processing.png \ doc/figures/fig-normal-processing.txt \ doc/figures/fig-rs-processing.dia \ doc/figures/fig-rs-processing.png \ doc/figures/fig-rs-processing.txt \ doc/figures/fig_topologies_full.dia \ doc/figures/fig_topologies_full.png \ doc/figures/fig_topologies_full.txt \ doc/figures/fig_topologies_rs.dia \ doc/figures/fig_topologies_rs.png \ doc/figures/fig_topologies_rs.txt \ doc/figures/fig-vnc-commercial-route-reflector.dia \ doc/figures/fig-vnc-commercial-route-reflector.png \ doc/figures/fig-vnc-commercial-route-reflector.txt \ doc/figures/fig-vnc-frr-route-reflector.dia \ doc/figures/fig-vnc-frr-route-reflector.png \ doc/figures/fig-vnc-frr-route-reflector.txt \ doc/figures/fig-vnc-gw.dia \ doc/figures/fig-vnc-gw.png \ doc/figures/fig-vnc-gw-rr.dia \ doc/figures/fig-vnc-gw-rr.png \ doc/figures/fig-vnc-gw-rr.txt \ doc/figures/fig-vnc-gw.txt \ doc/figures/fig-vnc-mesh.dia \ doc/figures/fig-vnc-mesh.png \ doc/figures/fig-vnc-mesh.txt \ doc/figures/fig-vnc-redundant-route-reflectors.dia \ doc/figures/fig-vnc-redundant-route-reflectors.png \ doc/figures/fig-vnc-redundant-route-reflectors.txt \ doc/figures/frr-icon.svg \ doc/figures/frr-logo-icon.png \ doc/figures/frr-logo-medium.png \ doc/figures/frr-logo.png \ doc/figures/frr-logo-small.png \ doc/figures/git_branches.png \ doc/figures/git_branches.svg \ doc/figures/ospf_api_architecture.png \ doc/figures/ospf_api_msghdr.png \ doc/figures/ospf_api_msgs1.png \ doc/figures/ospf_api_msgs2.png \ doc/extra/frrlexer.py \ # end .PHONY: doc/help doc/help: @echo "Please use \`make doc/{user,manpages,developer}/' where is one of" @echo " html to make standalone HTML files" @echo " dirhtml to make HTML files named index.html in directories" @echo " singlehtml to make a single large HTML file" @echo " pickle to make pickle files" @echo " json to make JSON files" @echo " htmlhelp to make HTML files and a HTML help project" @echo " qthelp to make HTML files and a qthelp project" @echo " applehelp to make an Apple Help Book" @echo " devhelp to make HTML files and a Devhelp project" @echo " epub to make an epub" @echo " latex to make LaTeX files, you can set PAPER=a4 or PAPER=letter" @echo " latexpdf to make LaTeX files and run them through pdflatex" @echo " latexpdfja to make LaTeX files and run them through platex/dvipdfmx" @echo " text to make text files" @echo " man to make manual pages" @echo " texinfo to make Texinfo files" @echo " info to make Texinfo files and run them through makeinfo" @echo " gettext to make PO message catalogs" @echo " changes to make an overview of all changed/added/deprecated items" @echo " xml to make Docutils-native XML files" @echo " pseudoxml to make pseudoxml-XML files for display purposes" @echo " linkcheck to check all external links for integrity" @echo " doctest to run all doctests embedded in the documentation (if enabled)" @echo " coverage to run coverage check of the documentation (if enabled)" frr-7.2.1/doc/user/0000755000000000000000000000000013610377563011017 500000000000000frr-7.2.1/doc/user/Makefile0000644000000000000000000000051213610377563012375 00000000000000all: ALWAYS @$(MAKE) -s -C ../.. doc-user help: ALWAYS @$(MAKE) -s -C ../.. doc/help pdf: ALWAYS @$(MAKE) -s -C ../.. doc/user/_build/latexpdf info: ALWAYS @$(MAKE) -s -C ../.. doc/user/_build/texinfo/frr.info %: ALWAYS @$(MAKE) -s -C ../.. doc/user/_build/$@ Makefile: #nothing ALWAYS: .PHONY: ALWAYS makefiles .SUFFIXES: frr-7.2.1/doc/user/Useful_Sysctl_Settings.md0000644000000000000000000000356613610377563015757 00000000000000# Useful Sysctl Settings Sysctl on Linux systems can tweak many useful behaviors. When it comes to a routing protocol suite like FRRouting there are numerous values depending on your use case that make sense to optimize. The below sysctl values provide a logical set of defaults which can be further optimized. ``` # /etc/sysctl.d/99frr_defaults.conf # Place this file at the location above and reload the device. # or run the sysctl -p /etc/sysctl.d/99frr_defaults.conf # Enables IPv4/IPv6 Routing net.ipv4.ip_forward = 1 net.ipv6.conf.all.forwarding=1 # Routing net.ipv6.route.max_size=131072 net.ipv4.conf.all.ignore_routes_with_linkdown=1 net.ipv6.conf.all.ignore_routes_with_linkdown=1 # Best Settings for Peering w/ BGP Unnumbered # and OSPF Neighbors net.ipv4.conf.all.rp_filter = 0 net.ipv4.conf.default.rp_filter = 0 net.ipv4.conf.lo.rp_filter = 0 net.ipv4.conf.all.forwarding = 1 net.ipv4.conf.default.forwarding = 1 net.ipv4.conf.default.arp_announce = 2 net.ipv4.conf.default.arp_notify = 1 net.ipv4.conf.default.arp_ignore=1 net.ipv4.conf.all.arp_announce = 2 net.ipv4.conf.all.arp_notify = 1 net.ipv4.conf.all.arp_ignore=1 net.ipv4.icmp_errors_use_inbound_ifaddr=1 # Miscellaneous Settings # Keep ipv6 permanent addresses on an admin down net.ipv6.conf.all.keep_addr_on_down=1 # igmp net.ipv4.igmp_max_memberships=1000 net.ipv4.neigh.default.mcast_solicit = 10 # MLD net.ipv6.mld_max_msf=512 # Garbage Collection Settings for ARP and Neighbors net.ipv4.neigh.default.gc_thresh2=7168 net.ipv4.neigh.default.gc_thresh3=8192 net.ipv4.neigh.default.base_reachable_time_ms=14400000 net.ipv6.neigh.default.gc_thresh2=3584 net.ipv6.neigh.default.gc_thresh3=4096 net.ipv6.neigh.default.base_reachable_time_ms=14400000 # Use neigh information on selection of nexthop for multipath hops net.ipv4.fib_multipath_use_neigh=1 # Allows Apps to Work with VRF net.ipv4.tcp_l3mdev_accept=1 ``` frr-7.2.1/doc/user/babeld.rst0000644000000000000000000002126013610377563012703 00000000000000.. _babel: ***** Babel ***** Babel is an interior gateway protocol that is suitable both for wired networks and for wireless mesh networks. Babel has been described as 'RIP on speed' -- it is based on the same principles as RIP, but includes a number of refinements that make it react much faster to topology changes without ever counting to infinity, and allow it to perform reliable link quality estimation on wireless links. Babel is a double-stack routing protocol, meaning that a single Babel instance is able to perform routing for both IPv4 and IPv6. FRR implements Babel as described in :rfc:`6126`. .. _configuring-babeld: Configuring babeld ================== The *babeld* daemon can be invoked with any of the common options (:ref:`common-invocation-options`). The *zebra* daemon must be running before *babeld* is invoked. Also, if *zebra* is restarted then *babeld* must be too. Configuration of *babeld* is done in its configuration file :file:`babeld.conf`. .. _babel-configuration: Babel configuration =================== .. index:: single: router babel single: no router babel .. clicmd:: [no] router babel Enable or disable Babel routing. .. index:: single: babel resend-delay (20-655340) single: no babel resend-delay [(20-655340)] .. clicmd:: [no] babel resend-delay (20-655340) Specifies the time after which important messages are resent when avoiding a black-hole. The default is 2000 ms. .. index:: single: babel diversity single: no babel diversity .. clicmd:: [no] babel diversity Enable or disable routing using radio frequency diversity. This is highly recommended in networks with many wireless nodes. If you enable this, you will probably want to set `babel diversity-factor` and `babel channel` below. .. index:: babel diversity-factor (1-256) .. clicmd:: babel diversity-factor (1-256) Sets the multiplicative factor used for diversity routing, in units of 1/256; lower values cause diversity to play a more important role in route selection. The default it 256, which means that diversity plays no role in route selection; you will probably want to set that to 128 or less on nodes with multiple independent radios. .. index:: single: network IFNAME single: no network IFNAME .. clicmd:: no network IFNAME Enable or disable Babel on the given interface. .. index:: babel .. clicmd:: babel Specifies whether this interface is wireless, which disables a number of optimisations that are only correct on wired interfaces. Specifying `wireless` (the default) is always correct, but may cause slower convergence and extra routing traffic. .. index:: single: babel split-horizon single: no babel split-horizon .. clicmd:: [no] babel split-horizon Specifies whether to perform split-horizon on the interface. Specifying ``no babel split-horizon`` is always correct, while ``babel split-horizon`` is an optimisation that should only be used on symmetric and transitive (wired) networks. The default is ``babel split-horizon`` on wired interfaces, and ``no babel split-horizon`` on wireless interfaces. This flag is reset when the wired/wireless status of an interface is changed. .. index:: babel hello-interval (20-655340) .. clicmd:: babel hello-interval (20-655340) Specifies the time in milliseconds between two scheduled hellos. On wired links, Babel notices a link failure within two hello intervals; on wireless links, the link quality value is reestimated at every hello interval. The default is 4000 ms. .. index:: babel update-interval (20-655340) .. clicmd:: babel update-interval (20-655340) Specifies the time in milliseconds between two scheduled updates. Since Babel makes extensive use of triggered updates, this can be set to fairly high values on links with little packet loss. The default is 20000 ms. .. index:: single: babel channel (1-254) single: babel channel interfering single: babel channel noninterfering .. clicmd:: babel channel (1-254) .. clicmd:: babel channel interfering .. clicmd:: babel channel noninterfering Set the channel number that diversity routing uses for this interface (see `babel diversity` above). Noninterfering interfaces are assumed to only interfere with themselves, interfering interfaces are assumed to interfere with all other channels except noninterfering channels, and interfaces with a channel number interfere with interfering interfaces and interfaces with the same channel number. The default is ``babel channel interfering`` for wireless interfaces, and ``babel channel noninterfering`` for wired interfaces. This is reset when the wired/wireless status of an interface is changed. .. index:: babel rxcost (1-65534) .. clicmd:: babel rxcost (1-65534) Specifies the base receive cost for this interface. For wireless interfaces, it specifies the multiplier used for computing the ETX reception cost (default 256); for wired interfaces, it specifies the cost that will be advertised to neighbours. This value is reset when the wired/wireless attribute of the interface is changed. .. note:: Do not use this command unless you know what you are doing; in most networks, acting directly on the cost using route maps is a better technique. .. index:: babel rtt-decay (1-256) .. clicmd:: babel rtt-decay (1-256) This specifies the decay factor for the exponential moving average of RTT samples, in units of 1/256. Higher values discard old samples faster. The default is 42. .. index:: babel rtt-min (1-65535) .. clicmd:: babel rtt-min (1-65535) This specifies the minimum RTT, in milliseconds, starting from which we increase the cost to a neighbour. The additional cost is linear in (rtt - rtt-min). The default is 100 ms. .. index:: babel rtt-max (1-65535) .. clicmd:: babel rtt-max (1-65535) This specifies the maximum RTT, in milliseconds, above which we don't increase the cost to a neighbour. The default is 120 ms. .. index:: babel max-rtt-penalty (0-65535) .. clicmd:: babel max-rtt-penalty (0-65535) This specifies the maximum cost added to a neighbour because of RTT, i.e. when the RTT is higher or equal than rtt-max. The default is 0, which effectively disables the use of a RTT-based cost. .. index:: single: babel enable-timestamps single: no babel enable-timestamps .. clicmd:: [no] babel enable-timestamps Enable or disable sending timestamps with each Hello and IHU message in order to compute RTT values. The default is `no babel enable-timestamps`. .. index:: babel resend-delay (20-655340) .. clicmd:: babel resend-delay (20-655340) Specifies the time in milliseconds after which an 'important' request or update will be resent. The default is 2000 ms. You probably don't want to tweak this value. .. index:: babel smoothing-half-life (0-65534) .. clicmd:: babel smoothing-half-life (0-65534) Specifies the time constant, in seconds, of the smoothing algorithm used for implementing hysteresis. Larger values reduce route oscillation at the cost of very slightly increasing convergence time. The value 0 disables hysteresis, and is suitable for wired networks. The default is 4 s. .. _babel-redistribution: Babel redistribution ==================== .. index:: single: redistribute KIND single: no redistribute KIND .. clicmd:: [no] redistribute KIND Specify which kind of routes should be redistributed into Babel. .. _show-babel-information: Show Babel information ====================== These commands dump various parts of *babeld*'s internal state. .. index:: show babel route .. clicmd:: show babel route .. index:: show babel route A.B.C.D .. clicmd:: show babel route A.B.C.D .. index:: show babel route X:X::X:X .. clicmd:: show babel route X:X::X:X .. index:: show babel route A.B.C.D/M .. clicmd:: show babel route A.B.C.D/M .. index:: show babel route X:X::X:X/M .. clicmd:: show babel route X:X::X:X/M .. index:: show babel interface .. clicmd:: show babel interface .. index:: show babel interface IFNAME .. clicmd:: show babel interface IFNAME .. index:: show babel neighbor .. clicmd:: show babel neighbor .. index:: show babel parameters .. clicmd:: show babel parameters Babel debugging commands ======================== .. index:: simple: debug babel KIND simple: no debug babel KIND .. clicmd:: [no] debug babel KIND Enable or disable debugging messages of a given kind. ``KIND`` can be one of: - ``common`` - ``filter`` - ``timeout`` - ``interface`` - ``route`` - ``all`` .. note:: If you have compiled with the ``NO_DEBUG`` flag, then these commands aren't available. frr-7.2.1/doc/user/basic.rst0000644000000000000000000005702013610377563012556 00000000000000.. _basic-commands: ************** Basic Commands ************** The following sections discuss commands common to all the routing daemons. .. _config-commands: Config Commands =============== .. index:: Configuration files for running the software .. index:: Files for running configurations .. index:: Modifying the herd's behavior .. index:: Getting the herd running In a config file, you can write the debugging options, a vty's password, routing daemon configurations, a log file name, and so forth. This information forms the initial command set for a routing beast as it is starting. Config files are generally found in |INSTALL_PREFIX_ETC|. Each of the daemons has its own config file. The daemon name plus ``.conf`` is the default config file name. For example, zebra's default config file name is :file:`zebra.conf`. You can specify a config file using the :option:`-f` or :option:`--config_file` options when starting the daemon. .. _basic-config-commands: Basic Config Commands --------------------- .. index:: hostname HOSTNAME .. clicmd:: hostname HOSTNAME Set hostname of the router. .. index:: single: no password PASSWORD single: password PASSWORD .. clicmd:: [no] password PASSWORD Set password for vty interface. The ``no`` form of the command deletes the password. If there is no password, a vty won't accept connections. .. index:: single: no enable password PASSWORD single: enable password PASSWORD .. clicmd:: [no] enable password PASSWORD Set enable password. The ``no`` form of the command deletes the enable password. .. index:: single: no log trap [LEVEL] single: log trap LEVEL .. clicmd:: [no] log trap LEVEL These commands are deprecated and are present only for historical compatibility. The log trap command sets the current logging level for all enabled logging destinations, and it sets the default for all future logging commands that do not specify a level. The normal default logging level is debugging. The ``no`` form of the command resets the default level for future logging commands to debugging, but it does not change the logging level of existing logging destinations. .. index:: single: no log stdout [LEVEL] single: log stdout [LEVEL] .. clicmd:: [no] log stdout LEVEL Enable logging output to stdout. If the optional second argument specifying the logging level is not present, the default logging level (typically debugging) will be used. The ``no`` form of the command disables logging to stdout. The ``LEVEL`` argument must have one of these values: emergencies, alerts, critical, errors, warnings, notifications, informational, or debugging. Note that the existing code logs its most important messages with severity ``errors``. .. index:: single: no log file [FILENAME [LEVEL]] single: log file FILENAME [LEVEL] .. clicmd:: [no] log file [FILENAME [LEVEL]] If you want to log into a file, please specify ``filename`` as in this example: :: log file /var/log/frr/bgpd.log informational If the optional second argument specifying the logging level is not present, the default logging level (typically debugging, but can be changed using the deprecated ``log trap`` command) will be used. The ``no`` form of the command disables logging to a file. .. note:: If you do not configure any file logging, and a daemon crashes due to a signal or an assertion failure, it will attempt to save the crash information in a file named :file:`/var/tmp/frr..crashlog`. For security reasons, this will not happen if the file exists already, so it is important to delete the file after reporting the crash information. .. index:: single: no log syslog [LEVEL] single: log syslog [LEVEL] .. clicmd:: [no] log syslog [LEVEL] Enable logging output to syslog. If the optional second argument specifying the logging level is not present, the default logging level (typically debugging, but can be changed using the deprecated ``log trap`` command) will be used. The ``no`` form of the command disables logging to syslog. .. index:: single: no log monitor [LEVEL] single: log monitor [LEVEL] .. clicmd:: [no] log monitor [LEVEL] Enable logging output to vty terminals that have enabled logging using the ``terminal monitor`` command. By default, monitor logging is enabled at the debugging level, but this command (or the deprecated ``log trap`` command) can be used to change the monitor logging level. If the optional second argument specifying the logging level is not present, the default logging level (typically debugging) will be used. The ``no`` form of the command disables logging to terminal monitors. .. index:: single: no log facility [FACILITY] single: log facility [FACILITY] .. clicmd:: [no] log facility [FACILITY] This command changes the facility used in syslog messages. The default facility is ``daemon``. The ``no`` form of the command resets the facility to the default ``daemon`` facility. .. index:: single: no log record-priority single: log record-priority .. clicmd:: [no] log record-priority To include the severity in all messages logged to a file, to stdout, or to a terminal monitor (i.e. anything except syslog), use the ``log record-priority`` global configuration command. To disable this option, use the ``no`` form of the command. By default, the severity level is not included in logged messages. Note: some versions of syslogd (including Solaris) can be configured to include the facility and level in the messages emitted. .. index:: single: log timestamp precision (0-6) single: [no] log timestamp precision (0-6) .. clicmd:: [no] log timestamp precision [(0-6)] This command sets the precision of log message timestamps to the given number of digits after the decimal point. Currently, the value must be in the range 0 to 6 (i.e. the maximum precision is microseconds). To restore the default behavior (1-second accuracy), use the ``no`` form of the command, or set the precision explicitly to 0. :: log timestamp precision 3 In this example, the precision is set to provide timestamps with millisecond accuracy. .. index:: [no] log commands .. clicmd:: [no] log commands This command enables the logging of all commands typed by a user to all enabled log destinations. The note that logging includes full command lines, including passwords. If the daemon startup option `--command-log-always` is used to start the daemon then this command is turned on by default and cannot be turned off and the [no] form of the command is dissallowed. .. index:: single: no log-filter WORD [DAEMON] single: log-filter WORD [DAEMON] .. clicmd:: [no] log-filter WORD [DAEMON] This command forces logs to be filtered on a specific string. A log message will only be printed if it matches on one of the filters in the log-filter table. Can be daemon independent. .. note:: Log filters help when you need to turn on debugs that cause significant load on the system (enabling certain debugs can bring FRR to a halt). Log filters prevent this but you should still expect a small performance hit due to filtering each of all those logs. .. index:: log-filter clear [DAEMON] .. clicmd:: log-filter clear [DAEMON] This command clears all current filters in the log-filter table. Can be daemon independent. .. index:: service password-encryption .. clicmd:: service password-encryption Encrypt password. .. index:: service advanced-vty .. clicmd:: service advanced-vty Enable advanced mode VTY. .. index:: service terminal-length (0-512) .. clicmd:: service terminal-length (0-512) Set system wide line configuration. This configuration command applies to all VTY interfaces. .. index:: line vty .. clicmd:: line vty Enter vty configuration mode. .. index:: banner motd default .. clicmd:: banner motd default Set default motd string. .. index:: no banner motd .. clicmd:: no banner motd No motd banner string will be printed. .. index:: exec-timeout MINUTE [SECOND] .. clicmd:: exec-timeout MINUTE [SECOND] Set VTY connection timeout value. When only one argument is specified it is used for timeout value in minutes. Optional second argument is used for timeout value in seconds. Default timeout value is 10 minutes. When timeout value is zero, it means no timeout. .. index:: no exec-timeout .. clicmd:: no exec-timeout Do not perform timeout at all. This command is as same as ``exec-timeout 0 0``. .. index:: access-class ACCESS-LIST .. clicmd:: access-class ACCESS-LIST Restrict vty connections with an access list. .. _sample-config-file: Sample Config File ------------------ Below is a sample configuration file for the zebra daemon. .. code-block:: frr ! ! Zebra configuration file ! hostname Router password zebra enable password zebra ! log stdout ! ! ``!`` and ``#`` are comment characters. If the first character of the word is one of the comment characters then from the rest of the line forward will be ignored as a comment. .. code-block:: frr password zebra!password If a comment character is not the first character of the word, it's a normal character. So in the above example ``!`` will not be regarded as a comment and the password is set to ``zebra!password``. .. _terminal-mode-commands: Terminal Mode Commands ====================== .. index:: write terminal .. clicmd:: write terminal Displays the current configuration to the vty interface. .. index:: write file .. clicmd:: write file Write current configuration to configuration file. .. index:: configure [terminal] .. clicmd:: configure [terminal] Change to configuration mode. This command is the first step to configuration. .. index:: terminal length (0-512) .. clicmd:: terminal length (0-512) Set terminal display length to ``(0-512)``. If length is 0, no display control is performed. .. index:: who .. clicmd:: who Show a list of currently connected vty sessions. .. index:: list .. clicmd:: list List all available commands. .. index:: show version .. clicmd:: show version Show the current version of |PACKAGE_NAME| and its build host information. .. index:: show logging .. clicmd:: show logging Shows the current configuration of the logging system. This includes the status of all logging destinations. .. index:: show log-filter .. clicmd:: show log-filter Shows the current log filters applied to each daemon. .. index:: show memory .. clicmd:: show memory Show information on how much memory is used for which specific things in |PACKAGE_NAME|. Output may vary depending on system capabilities but will generally look something like this: :: frr# show memory System allocator statistics: Total heap allocated: 1584 KiB Holding block headers: 0 bytes Used small blocks: 0 bytes Used ordinary blocks: 1484 KiB Free small blocks: 2096 bytes Free ordinary blocks: 100 KiB Ordinary blocks: 2 Small blocks: 60 Holding blocks: 0 (see system documentation for 'mallinfo' for meaning) --- qmem libfrr --- Buffer : 3 24 72 Buffer data : 1 4120 4120 Host config : 3 (variably sized) 72 Command Tokens : 3427 72 247160 Command Token Text : 2555 (variably sized) 83720 Command Token Help : 2555 (variably sized) 61720 Command Argument : 2 (variably sized) 48 Command Argument Name : 641 (variably sized) 15672 [...] --- qmem Label Manager --- --- qmem zebra --- ZEBRA VRF : 1 912 920 Route Entry : 11 80 968 Static route : 1 192 200 RIB destination : 8 48 448 RIB table info : 4 16 96 Nexthop tracking object : 1 200 200 Zebra Name Space : 1 312 312 --- qmem Table Manager --- To understand system allocator statistics, refer to your system's :manpage:`mallinfo(3)` man page. Below these statistics, statistics on individual memory allocation types in |PACKAGE_NAME| (so-called `MTYPEs`) is printed: * the first column of numbers is the current count of allocations made for the type (the number decreases when items are freed.) * the second column is the size of each item. This is only available if allocations on a type are always made with the same size. * the third column is the total amount of memory allocated for the particular type, including padding applied by malloc. This means that the number may be larger than the first column multiplied by the second. Overhead incurred by malloc's bookkeeping is not included in this, and the column may be missing if system support is not available. When executing this command from ``vtysh``, each of the daemons' memory usage is printed sequentially. .. index:: logmsg LEVEL MESSAGE .. clicmd:: logmsg LEVEL MESSAGE Send a message to all logging destinations that are enabled for messages of the given severity. .. index:: find COMMAND... .. clicmd:: find COMMAND... This command performs a simple substring search across all defined commands in all modes. As an example, suppose you're in enable mode and can't remember where the command to turn OSPF segment routing on is: :: frr# find segment-routing on (ospf) segment-routing on The CLI mode is displayed next to each command. In this example, :clicmd:`segment-routing on` is under the `router ospf` mode. Similarly, suppose you want a listing of all commands that contain "l2vpn": :: frr# find l2vpn (view) show [ip] bgp l2vpn evpn [json] (view) show [ip] bgp l2vpn evpn all [json] (view) show [ip] bgp l2vpn evpn all neighbors A.B.C.D advertised-routes [json] (view) show [ip] bgp l2vpn evpn all neighbors A.B.C.D routes [json] (view) show [ip] bgp l2vpn evpn all overlay ... .. _common-show-commands: .. index:: show thread cpu .. clicmd:: show thread cpu [r|w|t|e|x] This command displays system run statistics for all the different event types. If no options is specified all different run types are displayed together. Additionally you can ask to look at (r)ead, (w)rite, (t)imer, (e)vent and e(x)ecute thread event types. .. index:: show thread poll .. clicmd:: show thread poll This command displays FRR's poll data. It allows a glimpse into how we are setting each individual fd for the poll command at that point in time. .. _common-invocation-options: Common Invocation Options ========================= These options apply to all |PACKAGE_NAME| daemons. .. option:: -d, --daemon Run in daemon mode. .. option:: -f, --config_file Set configuration file name. .. option:: -h, --help Display this help and exit. .. option:: -i, --pid_file Upon startup the process identifier of the daemon is written to a file, typically in :file:`/var/run`. This file can be used by the init system to implement commands such as ``.../init.d/zebra status``, ``.../init.d/zebra restart`` or ``.../init.d/zebra stop``. The file name is an run-time option rather than a configure-time option so that multiple routing daemons can be run simultaneously. This is useful when using |PACKAGE_NAME| to implement a routing looking glass. One machine can be used to collect differing routing views from differing points in the network. .. option:: -A, --vty_addr

Set the VTY local address to bind to. If set, the VTY socket will only be bound to this address. .. option:: -P, --vty_port Set the VTY TCP port number. If set to 0 then the TCP VTY sockets will not be opened. .. option:: -u Set the user and group to run as. .. option:: -N Set the namespace that the daemon will run in. A "/" will be added to all files that use the statedir. If you have "/var/run/frr" as the default statedir then it will become "/var/run/frr/". .. option:: -v, --version Print program version. .. option:: --command-log-always Cause the daemon to always log commands entered to the specified log file. This also makes the `no log commands` command dissallowed. Enabling this is suggested if you have need to track what the operator is doing on this router. .. option:: --log When initializing the daemon, setup the log to go to either stdout, syslog or to a file. These values will be displayed as part of a show run. Additionally they can be overridden at runtime if desired via the normal log commands. .. option:: --log-level When initializing the daemon, allow the specification of a default log level at startup from one of the specified levels. .. option:: --tcli Enable the transactional CLI mode. .. _loadable-module-support: Loadable Module Support ======================= FRR supports loading extension modules at startup. Loading, reloading or unloading modules at runtime is not supported (yet). To load a module, use the following command line option at daemon startup: .. option:: -M, --module Load the specified module, optionally passing options to it. If the module name contains a slash (/), it is assumed to be a full pathname to a file to be loaded. If it does not contain a slash, the |INSTALL_PREFIX_MODULES| directory is searched for a module of the given name; first with the daemon name prepended (e.g. ``zebra_mod`` for ``mod``), then without the daemon name prepended. This option is available on all daemons, though some daemons may not have any modules available to be loaded. The SNMP Module --------------- If SNMP is enabled during compile-time and installed as part of the package, the ``snmp`` module can be loaded for the *Zebra*, *bgpd*, *ospfd*, *ospf6d* and *ripd* daemons. The module ignores any options passed to it. Refer to :ref:`snmp-support` for information on its usage. The FPM Module -------------- If FPM is enabled during compile-time and installed as part of the package, the ``fpm`` module can be loaded for the *zebra* daemon. This provides the Forwarding Plane Manager ("FPM") API. The module expects its argument to be either ``Netlink`` or ``protobuf``, specifying the encapsulation to use. ``Netlink`` is the default, and ``protobuf`` may not be available if the module was built without protobuf support. Refer to :ref:`zebra-fib-push-interface` for more information. .. _virtual-terminal-interfaces: Virtual Terminal Interfaces =========================== VTY -- Virtual Terminal [aka TeletYpe] Interface is a command line interface (CLI) for user interaction with the routing daemon. .. _vty-overview: VTY Overview ------------ VTY stands for Virtual TeletYpe interface. It means you can connect to the daemon via the telnet protocol. To enable a VTY interface, you have to setup a VTY password. If there is no VTY password, one cannot connect to the VTY interface at all. :: % telnet localhost 2601 Trying 127.0.0.1... Connected to localhost. Escape character is '^]'. Hello, this is |PACKAGE_NAME| (version |PACKAGE_VERSION|) |COPYRIGHT_STR| User Access Verification Password: XXXXX Router> ? enable . . . Turn on privileged commands exit . . . Exit current mode and down to previous mode help . . . Description of the interactive help system list . . . Print command list show . . . Show system inform wh. . . Display who is on a vty Router> enable Password: XXXXX Router# configure terminal Router(config)# interface eth0 Router(config-if)# ip address 10.0.0.1/8 Router(config-if)# ^Z Router# .. _vty-modes: VTY Modes --------- There are three basic VTY modes: There are commands that may be restricted to specific VTY modes. .. _vty-view-mode: VTY View Mode ^^^^^^^^^^^^^ This mode is for read-only access to the CLI. One may exit the mode by leaving the system, or by entering `enable` mode. .. _vty-enable-mode: VTY Enable Mode ^^^^^^^^^^^^^^^ This mode is for read-write access to the CLI. One may exit the mode by leaving the system, or by escaping to view mode. .. _vty-other-modes: VTY Other Modes ^^^^^^^^^^^^^^^ This page is for describing other modes. .. _vty-cli-commands: VTY CLI Commands ---------------- Commands that you may use at the command-line are described in the following three subsubsections. .. _cli-movement-commands: CLI Movement Commands ^^^^^^^^^^^^^^^^^^^^^ These commands are used for moving the CLI cursor. The :kbd:`C` character means press the Control Key. :kbd:`C-f` / :kbd:`LEFT` Move forward one character. :kbd:`C-b` / :kbd:`RIGHT` Move backward one character. :kbd:`M-f` Move forward one word. :kbd:`M-b` Move backward one word. :kbd:`C-a` Move to the beginning of the line. :kbd:`C-e` Move to the end of the line. .. _cli-editing-commands: CLI Editing Commands ^^^^^^^^^^^^^^^^^^^^ These commands are used for editing text on a line. The :kbd:`C` character means press the Control Key. :kbd:`C-h` / :kbd:`DEL` Delete the character before point. :kbd:`C-d` Delete the character after point. :kbd:`M-d` Forward kill word. :kbd:`C-w` Backward kill word. :kbd:`C-k` Kill to the end of the line. :kbd:`C-u` Kill line from the beginning, erasing input. :kbd:`C-t` Transpose character. CLI Advanced Commands ^^^^^^^^^^^^^^^^^^^^^ There are several additional CLI commands for command line completions, insta-help, and VTY session management. :kbd:`C-c` Interrupt current input and moves to the next line. :kbd:`C-z` End current configuration session and move to top node. :kbd:`C-n` / :kbd:`DOWN` Move down to next line in the history buffer. :kbd:`C-p` / :kbd:`UP` Move up to previous line in the history buffer. :kbd:`TAB` Use command line completion by typing :kbd:`TAB`. :kbd:`?` You can use command line help by typing ``help`` at the beginning of the line. Typing :kbd:`?` at any point in the line will show possible completions. Pipe Actions ^^^^^^^^^^^^ VTY supports optional modifiers at the end of commands that perform postprocessing on command output or modify the action of commands. These do not show up in the :kbd:`?` or :kbd:`TAB` suggestion lists. ``... | include REGEX`` Filters the output of the preceding command, including only lines which match the POSIX Extended Regular Expression ``REGEX``. Do not put the regex in quotes. Examples: :: frr# show ip bgp sum json | include remoteAs "remoteAs":0, "remoteAs":455, "remoteAs":99, :: frr# show run | include neigh.*[0-9]{2}\.0\.[2-4]\.[0-9]* neighbor 10.0.2.106 remote-as 99 neighbor 10.0.2.107 remote-as 99 neighbor 10.0.2.108 remote-as 99 neighbor 10.0.2.109 remote-as 99 neighbor 10.0.2.110 remote-as 99 neighbor 10.0.3.111 remote-as 111 frr-7.2.1/doc/user/bfd.rst0000644000000000000000000003251113610377563012226 00000000000000.. _bfd: ********************************** Bidirectional Forwarding Detection ********************************** :abbr:`BFD (Bidirectional Forwarding Detection)` stands for Bidirectional Forwarding Detection and it is described and extended by the following RFCs: * :rfc:`5880` * :rfc:`5881` * :rfc:`5883` Currently, there are two implementations of the BFD commands in FRR: * :abbr:`PTM (Prescriptive Topology Manager)`: an external daemon which implements BFD; * ``bfdd``: a BFD implementation that is able to talk with remote peers; This document will focus on the later implementation: *bfdd*. .. _bfd-starting: Starting BFD ============ *bfdd* default configuration file is :file:`bfdd.conf`. *bfdd* searches the current directory first then |INSTALL_PREFIX_ETC|/bfdd.conf. All of *bfdd*'s command must be configured in :file:`bfdd.conf`. *bfdd* specific invocation options are described below. Common options may also be specified (:ref:`common-invocation-options`). .. program:: bfdd .. option:: --bfdctl Set the BFD daemon control socket location. If using a non-default socket location:: /usr/lib/frr/bfdd --bfdctl /tmp/bfdd.sock The default UNIX socket location is: #define BFDD_CONTROL_SOCKET "|INSTALL_PREFIX_STATE|/bfdd.sock" This option overrides the location addition that the -N option provides to the bfdd.sock .. _bfd-commands: BFDd Commands ============= .. index:: bfd .. clicmd:: bfd Opens the BFD daemon configuration node. .. index:: peer [{multihop|local-address |interface IFNAME|vrf NAME}] .. clicmd:: peer [{multihop|local-address |interface IFNAME|vrf NAME}] Creates and configures a new BFD peer to listen and talk to. `multihop` tells the BFD daemon that we should expect packets with TTL less than 254 (because it will take more than one hop) and to listen on the multihop port (4784). When using multi-hop mode `echo-mode` will not work (see :rfc:`5883` section 3). `local-address` provides a local address that we should bind our peer listener to and the address we should use to send the packets. This option is mandatory for IPv6. `interface` selects which interface we should use. `vrf` selects which domain we want to use. .. index:: no peer $peer [{multihop|local-address $local|interface IFNAME$ifname|vrf NAME$vrf_name}] .. clicmd:: no peer $peer [{multihop|local-address $local|interface IFNAME$ifname|vrf NAME$vrf_name}] Stops and removes the selected peer. .. index:: show bfd [vrf NAME] peers [json] .. clicmd:: show bfd [vrf NAME] peers [json] Show all configured BFD peers information and current status. .. index:: show bfd [vrf NAME$vrf_name] peer $peer [{multihop|local-address $local|interface IFNAME$ifname}]> [json] .. clicmd:: show bfd [vrf NAME$vrf_name] peer $peer [{multihop|local-address $local|interface IFNAME$ifname}]> [json] Show status for a specific BFD peer. .. _bfd-peer-config: Peer Configurations ------------------- .. index:: detect-multiplier (2-255) .. clicmd:: detect-multiplier (2-255) Configures the detection multiplier to determine packet loss. The remote transmission interval will be multiplied by this value to determine the connection loss detection timer. The default value is 3. Example: when the local system has `detect-multiplier 3` and the remote system has `transmission interval 300`, the local system will detect failures only after 900 milliseconds without receiving packets. .. index:: receive-interval (10-60000) .. clicmd:: receive-interval (10-60000) Configures the minimum interval that this system is capable of receiving control packets. The default value is 300 milliseconds. .. index:: transmit-interval (10-60000) .. clicmd:: transmit-interval (10-60000) The minimum transmission interval (less jitter) that this system wants to use to send BFD control packets. .. index:: echo-interval (10-60000) .. clicmd:: echo-interval (10-60000) Configures the minimal echo receive transmission interval that this system is capable of handling. .. index:: [no] echo-mode .. clicmd:: [no] echo-mode Enables or disables the echo transmission mode. This mode is disabled by default. It is recommended that the transmission interval of control packets to be increased after enabling echo-mode to reduce bandwidth usage. For example: `transmission-interval 2000`. Echo mode is not supported on multi-hop setups (see :rfc:`5883` section 3). .. index:: [no] shutdown .. clicmd:: [no] shutdown Enables or disables the peer. When the peer is disabled an 'administrative down' message is sent to the remote peer. .. index:: label WORD .. clicmd:: label WORD Labels a peer with the provided word. This word can be referenced later on other daemons to refer to a specific peer. .. _bfd-bgp-peer-config: BGP BFD Configuration --------------------- The following commands are available inside the BGP configuration node. .. index:: neighbor bfd .. clicmd:: neighbor bfd Listen for BFD events registered on the same target as this BGP neighbor. When BFD peer goes down it immediately asks BGP to shutdown the connection with its neighbor and, when it goes back up, notify BGP to try to connect to it. .. index:: no neighbor bfd .. clicmd:: no neighbor bfd Removes any notification registration for this neighbor. .. index:: neighbor bfd check-control-plane-failure .. clicmd:: neighbor bfd check-control-plane-failure Allow to write CBIT independence in BFD outgoing packets. Also allow to read both C-BIT value of BFD and lookup BGP peer status. This command is useful when a BFD down event is caught, while the BGP peer requested that local BGP keeps the remote BGP entries as staled if such issue is detected. This is the case when graceful restart is enabled, and it is wished to ignore the BD event while waiting for the remote router to restart. .. index:: no neighbor bfd check-control-plane-failure .. clicmd:: no neighbor bfd check-control-plane-failure Disallow to write CBIT independence in BFD outgoing packets. Also disallow to ignore BFD down notification. This is the default behaviour. .. _bfd-ospf-peer-config: OSPF BFD Configuration ---------------------- The following commands are available inside the interface configuration node. .. index:: ip ospf bfd .. clicmd:: ip ospf bfd Listen for BFD events on peers created on the interface. Every time a new neighbor is found a BFD peer is created to monitor the link status for fast convergence. .. index:: no ip ospf bfd .. clicmd:: no ip ospf bfd Removes any notification registration for this interface peers. .. _bfd-ospf6-peer-config: OSPF6 BFD Configuration ----------------------- The following commands are available inside the interface configuration node. .. index:: ipv6 ospf6 bfd .. clicmd:: ipv6 ospf6 bfd Listen for BFD events on peers created on the interface. Every time a new neighbor is found a BFD peer is created to monitor the link status for fast convergence. .. index:: no ipv6 ospf6 bfd .. clicmd:: no ipv6 ospf6 bfd Removes any notification registration for this interface peers. .. _bfd-pim-peer-config: PIM BFD Configuration --------------------- The following commands are available inside the interface configuration node. .. index:: ip pim bfd .. clicmd:: ip pim bfd Listen for BFD events on peers created on the interface. Every time a new neighbor is found a BFD peer is created to monitor the link status for fast convergence. .. index:: no ip pim bfd .. clicmd:: no ip pim bfd Removes any notification registration for this interface peers. .. _bfd-configuration: Configuration ============= Before applying ``bfdd`` rules to integrated daemons (like BGPd), we must create the corresponding peers inside the ``bfd`` configuration node. Here is an example of BFD configuration: :: bfd peer 192.168.0.1 label home-peer no shutdown ! ! router bgp 65530 neighbor 192.168.0.1 remote-as 65531 neighbor 192.168.0.1 bfd neighbor 192.168.0.2 remote-as 65530 neighbor 192.168.0.2 bfd neighbor 192.168.0.3 remote-as 65532 neighbor 192.168.0.3 bfd ! Peers can be identified by its address (use ``multihop`` when you need to specify a multi hop peer) or can be specified manually by a label. Here are the available peer configurations: :: bfd ! configure a peer on an specific interface peer 192.168.0.1 interface eth0 no shutdown ! ! configure a multihop peer peer 192.168.0.2 multihop local-address 192.168.0.3 shutdown ! ! configure a peer in a different vrf peer 192.168.0.3 vrf foo shutdown ! ! configure a peer with every option possible peer 192.168.0.4 label peer-label detect-multiplier 50 receive-interval 60000 transmit-interval 3000 shutdown ! ! configure a peer on an interface from a separate vrf peer 192.168.0.5 interface eth1 vrf vrf2 no shutdown ! ! remove a peer no peer 192.168.0.3 vrf foo .. _bfd-status: Status ====== You can inspect the current BFD peer status with the following commands: :: frr# show bfd peers BFD Peers: peer 192.168.0.1 ID: 1 Remote ID: 1 Status: up Uptime: 1 minute(s), 51 second(s) Diagnostics: ok Remote diagnostics: ok Local timers: Receive interval: 300ms Transmission interval: 300ms Echo transmission interval: disabled Remote timers: Receive interval: 300ms Transmission interval: 300ms Echo transmission interval: 50ms peer 192.168.1.1 label: router3-peer ID: 2 Remote ID: 2 Status: up Uptime: 1 minute(s), 53 second(s) Diagnostics: ok Remote diagnostics: ok Local timers: Receive interval: 300ms Transmission interval: 300ms Echo transmission interval: disabled Remote timers: Receive interval: 300ms Transmission interval: 300ms Echo transmission interval: 50ms frr# show bfd peer 192.168.1.1 BFD Peer: peer 192.168.1.1 label: router3-peer ID: 2 Remote ID: 2 Status: up Uptime: 3 minute(s), 4 second(s) Diagnostics: ok Remote diagnostics: ok Local timers: Receive interval: 300ms Transmission interval: 300ms Echo transmission interval: disabled Remote timers: Receive interval: 300ms Transmission interval: 300ms Echo transmission interval: 50ms frr# show bfd peer 192.168.0.1 json {"multihop":false,"peer":"192.168.0.1","id":1,"remote-id":1,"status":"up","uptime":161,"diagnostic":"ok","remote-diagnostic":"ok","receive-interval":300,"transmit-interval":300,"echo-interval":50,"remote-receive-interval":300,"remote-transmit-interval":300,"remote-echo-interval":50} You can also inspect peer session counters with the following commands: :: frr# show bfd peers counters BFD Peers: peer 192.168.2.1 interface r2-eth2 Control packet input: 28 packets Control packet output: 28 packets Echo packet input: 0 packets Echo packet output: 0 packets Session up events: 1 Session down events: 0 Zebra notifications: 2 peer 192.168.0.1 Control packet input: 54 packets Control packet output: 103 packets Echo packet input: 965 packets Echo packet output: 966 packets Session up events: 1 Session down events: 0 Zebra notifications: 4 frr# show bfd peer 192.168.0.1 counters peer 192.168.0.1 Control packet input: 126 packets Control packet output: 247 packets Echo packet input: 2409 packets Echo packet output: 2410 packets Session up events: 1 Session down events: 0 Zebra notifications: 4 frr# show bfd peer 192.168.0.1 counters json {"multihop":false,"peer":"192.168.0.1","control-packet-input":348,"control-packet-output":685,"echo-packet-input":6815,"echo-packet-output":6816,"session-up":1,"session-down":0,"zebra-notifications":4} frr-7.2.1/doc/user/bgp.rst0000644000000000000000000031351013610377563012244 00000000000000.. _bgp: *** BGP *** :abbr:`BGP` stands for Border Gateway Protocol. The latest BGP version is 4. BGP-4 is one of the Exterior Gateway Protocols and the de facto standard interdomain routing protocol. BGP-4 is described in :rfc:`1771` and updated by :rfc:`4271`. :rfc:`2858` adds multiprotocol support to BGP-4. .. _starting-bgp: Starting BGP ============ The default configuration file of *bgpd* is :file:`bgpd.conf`. *bgpd* searches the current directory first, followed by |INSTALL_PREFIX_ETC|/bgpd.conf. All of *bgpd*'s commands must be configured in :file:`bgpd.conf` when the integrated config is not being used. *bgpd* specific invocation options are described below. Common options may also be specified (:ref:`common-invocation-options`). .. program:: bgpd .. option:: -p, --bgp_port Set the bgp protocol's port number. When port number is 0, that means do not listen bgp port. .. option:: -l, --listenon Specify a specific IP address for bgpd to listen on, rather than its default of ``0.0.0.0`` / ``::``. This can be useful to constrain bgpd to an internal address, or to run multiple bgpd processes on one host. .. _bgp-basic-concepts: Basic Concepts ============== .. _bgp-autonomous-systems: Autonomous Systems ------------------ From :rfc:`1930`: An AS is a connected group of one or more IP prefixes run by one or more network operators which has a SINGLE and CLEARLY DEFINED routing policy. Each AS has an identifying number associated with it called an :abbr:`ASN (Autonomous System Number)`. This is a two octet value ranging in value from 1 to 65535. The AS numbers 64512 through 65535 are defined as private AS numbers. Private AS numbers must not be advertised on the global Internet. The :abbr:`ASN (Autonomous System Number)` is one of the essential elements of BGP. BGP is a distance vector routing protocol, and the AS-Path framework provides distance vector metric and loop detection to BGP. .. seealso:: :rfc:`1930` .. _bgp-address-families: Address Families ---------------- Multiprotocol extensions enable BGP to carry routing information for multiple network layer protocols. BGP supports an Address Family Identifier (AFI) for IPv4 and IPv6. Support is also provided for multiple sets of per-AFI information via the BGP Subsequent Address Family Identifier (SAFI). FRR supports SAFIs for unicast information, labeled information (:rfc:`3107` and :rfc:`8277`), and Layer 3 VPN information (:rfc:`4364` and :rfc:`4659`). .. _bgp-route-selection: Route Selection --------------- The route selection process used by FRR's BGP implementation uses the following decision criterion, starting at the top of the list and going towards the bottom until one of the factors can be used. 1. **Weight check** Prefer higher local weight routes to lower routes. 2. **Local preference check** Prefer higher local preference routes to lower. 3. **Local route check** Prefer local routes (statics, aggregates, redistributed) to received routes. 4. **AS path length check** Prefer shortest hop-count AS_PATHs. 5. **Origin check** Prefer the lowest origin type route. That is, prefer IGP origin routes to EGP, to Incomplete routes. 6. **MED check** Where routes with a MED were received from the same AS, prefer the route with the lowest MED. :ref:`bgp-med`. 7. **External check** Prefer the route received from an external, eBGP peer over routes received from other types of peers. 8. **IGP cost check** Prefer the route with the lower IGP cost. 9. **Multi-path check** If multi-pathing is enabled, then check whether the routes not yet distinguished in preference may be considered equal. If :clicmd:`bgp bestpath as-path multipath-relax` is set, all such routes are considered equal, otherwise routes received via iBGP with identical AS_PATHs or routes received from eBGP neighbours in the same AS are considered equal. 10. **Already-selected external check** Where both routes were received from eBGP peers, then prefer the route which is already selected. Note that this check is not applied if :clicmd:`bgp bestpath compare-routerid` is configured. This check can prevent some cases of oscillation. 11. **Router-ID check** Prefer the route with the lowest `router-ID`. If the route has an `ORIGINATOR_ID` attribute, through iBGP reflection, then that router ID is used, otherwise the `router-ID` of the peer the route was received from is used. 12. **Cluster-List length check** The route with the shortest cluster-list length is used. The cluster-list reflects the iBGP reflection path the route has taken. 13. **Peer address** Prefer the route received from the peer with the higher transport layer address, as a last-resort tie-breaker. .. _bgp-capability-negotiation: Capability Negotiation ---------------------- When adding IPv6 routing information exchange feature to BGP. There were some proposals. :abbr:`IETF (Internet Engineering Task Force)` :abbr:`IDR (Inter Domain Routing)` adopted a proposal called Multiprotocol Extension for BGP. The specification is described in :rfc:`2283`. The protocol does not define new protocols. It defines new attributes to existing BGP. When it is used exchanging IPv6 routing information it is called BGP-4+. When it is used for exchanging multicast routing information it is called MBGP. *bgpd* supports Multiprotocol Extension for BGP. So if a remote peer supports the protocol, *bgpd* can exchange IPv6 and/or multicast routing information. Traditional BGP did not have the feature to detect a remote peer's capabilities, e.g. whether it can handle prefix types other than IPv4 unicast routes. This was a big problem using Multiprotocol Extension for BGP in an operational network. :rfc:`2842` adopted a feature called Capability Negotiation. *bgpd* use this Capability Negotiation to detect the remote peer's capabilities. If a peer is only configured as an IPv4 unicast neighbor, *bgpd* does not send these Capability Negotiation packets (at least not unless other optional BGP features require capability negotiation). By default, FRR will bring up peering with minimal common capability for the both sides. For example, if the local router has unicast and multicast capabilities and the remote router only has unicast capability the local router will establish the connection with unicast only capability. When there are no common capabilities, FRR sends Unsupported Capability error and then resets the connection. .. _bgp-router-configuration: BGP Router Configuration ======================== ASN and Router ID ----------------- First of all you must configure BGP router with the :clicmd:`router bgp ASN` command. The AS number is an identifier for the autonomous system. The BGP protocol uses the AS number for detecting whether the BGP connection is internal or external. .. index:: router bgp ASN .. clicmd:: router bgp ASN Enable a BGP protocol process with the specified ASN. After this statement you can input any `BGP Commands`. .. index:: no router bgp ASN .. clicmd:: no router bgp ASN Destroy a BGP protocol process with the specified ASN. .. index:: bgp router-id A.B.C.D .. clicmd:: bgp router-id A.B.C.D This command specifies the router-ID. If *bgpd* connects to *zebra* it gets interface and address information. In that case default router ID value is selected as the largest IP Address of the interfaces. When `router zebra` is not enabled *bgpd* can't get interface information so `router-id` is set to 0.0.0.0. So please set router-id by hand. .. _bgp-multiple-autonomous-systems: Multiple Autonomous Systems --------------------------- FRR's BGP implementation is capable of running multiple autonomous systems at once. Each configured AS corresponds to a :ref:`zebra-vrf`. In the past, to get the same functionality the network administrator had to run a new *bgpd* process; using VRFs allows multiple autonomous systems to be handled in a single process. When using multiple autonomous systems, all router config blocks after the first one must specify a VRF to be the target of BGP's route selection. This VRF must be unique within respect to all other VRFs being used for the same purpose, i.e. two different autonomous systems cannot use the same VRF. However, the same AS can be used with different VRFs. .. note:: The separated nature of VRFs makes it possible to peer a single *bgpd* process to itself, on one machine. Note that this can be done fully within BGP without a corresponding VRF in the kernel or Zebra, which enables some practical use cases such as :ref:`route reflectors ` and route servers. Configuration of additional autonomous systems, or of a router that targets a specific VRF, is accomplished with the following command: .. index:: router bgp ASN vrf VRFNAME .. clicmd:: router bgp ASN vrf VRFNAME ``VRFNAME`` is matched against VRFs configured in the kernel. When ``vrf VRFNAME`` is not specified, the BGP protocol process belongs to the default VRF. An example configuration with multiple autonomous systems might look like this: .. code-block:: frr router bgp 1 neighbor 10.0.0.1 remote-as 20 neighbor 10.0.0.2 remote-as 30 ! router bgp 2 vrf blue neighbor 10.0.0.3 remote-as 40 neighbor 10.0.0.4 remote-as 50 ! router bgp 3 vrf red neighbor 10.0.0.5 remote-as 60 neighbor 10.0.0.6 remote-as 70 ... .. seealso:: :ref:`bgp-vrf-route-leaking` .. seealso:: :ref:`zebra-vrf` .. _bgp-views: Views ----- In addition to supporting multiple autonomous systems, FRR's BGP implementation also supports *views*. BGP views are almost the same as normal BGP processes, except that routes selected by BGP are not installed into the kernel routing table. Each BGP view provides an independent set of routing information which is only distributed via BGP. Multiple views can be supported, and BGP view information is always independent from other routing protocols and Zebra/kernel routes. BGP views use the core instance (i.e., default VRF) for communication with peers. .. index:: router bgp AS-NUMBER view NAME .. clicmd:: router bgp AS-NUMBER view NAME Make a new BGP view. You can use an arbitrary word for the ``NAME``. Routes selected by the view are not installed into the kernel routing table. With this command, you can setup Route Server like below. .. code-block:: frr ! router bgp 1 view 1 neighbor 10.0.0.1 remote-as 2 neighbor 10.0.0.2 remote-as 3 ! router bgp 2 view 2 neighbor 10.0.0.3 remote-as 4 neighbor 10.0.0.4 remote-as 5 .. index:: show [ip] bgp view NAME .. clicmd:: show [ip] bgp view NAME Display the routing table of BGP view ``NAME``. Route Selection --------------- .. index:: bgp bestpath as-path confed .. clicmd:: bgp bestpath as-path confed This command specifies that the length of confederation path sets and sequences should should be taken into account during the BGP best path decision process. .. index:: bgp bestpath as-path multipath-relax .. clicmd:: bgp bestpath as-path multipath-relax This command specifies that BGP decision process should consider paths of equal AS_PATH length candidates for multipath computation. Without the knob, the entire AS_PATH must match for multipath computation. .. clicmd:: bgp bestpath compare-routerid Ensure that when comparing routes where both are equal on most metrics, including local-pref, AS_PATH length, IGP cost, MED, that the tie is broken based on router-ID. If this option is enabled, then the already-selected check, where already selected eBGP routes are preferred, is skipped. If a route has an `ORIGINATOR_ID` attribute because it has been reflected, that `ORIGINATOR_ID` will be used. Otherwise, the router-ID of the peer the route was received from will be used. The advantage of this is that the route-selection (at this point) will be more deterministic. The disadvantage is that a few or even one lowest-ID router may attract all traffic to otherwise-equal paths because of this check. It may increase the possibility of MED or IGP oscillation, unless other measures were taken to avoid these. The exact behaviour will be sensitive to the iBGP and reflection topology. .. _bgp-distance: Administrative Distance Metrics ------------------------------- .. index:: distance bgp (1-255) (1-255) (1-255) .. clicmd:: distance bgp (1-255) (1-255) (1-255) This command change distance value of BGP. The arguments are the distance values for for external routes, internal routes and local routes respectively. .. index:: distance (1-255) A.B.C.D/M .. clicmd:: distance (1-255) A.B.C.D/M .. index:: distance (1-255) A.B.C.D/M WORD .. clicmd:: distance (1-255) A.B.C.D/M WORD Sets the administrative distance for a particular route. .. _bgp-requires-policy: Require policy on EBGP ------------------------------- .. index:: [no] bgp ebgp-requires-policy .. clicmd:: [no] bgp ebgp-requires-policy This command requires incoming and outgoing filters to be applied for eBGP sessions. Without the incoming filter, no routes will be accepted. Without the outgoing filter, no routes will be announced. .. _bgp-route-flap-dampening: Route Flap Dampening -------------------- .. clicmd:: bgp dampening (1-45) (1-20000) (1-20000) (1-255) This command enables BGP route-flap dampening and specifies dampening parameters. half-life Half-life time for the penalty reuse-threshold Value to start reusing a route suppress-threshold Value to start suppressing a route max-suppress Maximum duration to suppress a stable route The route-flap damping algorithm is compatible with :rfc:`2439`. The use of this command is not recommended nowadays. At the moment, route-flap dampening is not working per VRF and is working only for IPv4 unicast and multicast. .. seealso:: https://www.ripe.net/publications/docs/ripe-378 .. _bgp-med: Multi-Exit Discriminator ------------------------ The BGP :abbr:`MED (Multi-Exit Discriminator)` attribute has properties which can cause subtle convergence problems in BGP. These properties and problems have proven to be hard to understand, at least historically, and may still not be widely understood. The following attempts to collect together and present what is known about MED, to help operators and FRR users in designing and configuring their networks. The BGP :abbr:`MED` attribute is intended to allow one AS to indicate its preferences for its ingress points to another AS. The MED attribute will not be propagated on to another AS by the receiving AS - it is 'non-transitive' in the BGP sense. E.g., if AS X and AS Y have 2 different BGP peering points, then AS X might set a MED of 100 on routes advertised at one and a MED of 200 at the other. When AS Y selects between otherwise equal routes to or via AS X, AS Y should prefer to take the path via the lower MED peering of 100 with AS X. Setting the MED allows an AS to influence the routing taken to it within another, neighbouring AS. In this use of MED it is not really meaningful to compare the MED value on routes where the next AS on the paths differs. E.g., if AS Y also had a route for some destination via AS Z in addition to the routes from AS X, and AS Z had also set a MED, it wouldn't make sense for AS Y to compare AS Z's MED values to those of AS X. The MED values have been set by different administrators, with different frames of reference. The default behaviour of BGP therefore is to not compare MED values across routes received from different neighbouring ASes. In FRR this is done by comparing the neighbouring, left-most AS in the received AS_PATHs of the routes and only comparing MED if those are the same. Unfortunately, this behaviour of MED, of sometimes being compared across routes and sometimes not, depending on the properties of those other routes, means MED can cause the order of preference over all the routes to be undefined. That is, given routes A, B, and C, if A is preferred to B, and B is preferred to C, then a well-defined order should mean the preference is transitive (in the sense of orders [#med-transitivity-rant]_) and that A would be preferred to C. However, when MED is involved this need not be the case. With MED it is possible that C is actually preferred over A. So A is preferred to B, B is preferred to C, but C is preferred to A. This can be true even where BGP defines a deterministic 'most preferred' route out of the full set of A,B,C. With MED, for any given set of routes there may be a deterministically preferred route, but there need not be any way to arrange them into any order of preference. With unmodified MED, the order of preference of routes literally becomes undefined. That MED can induce non-transitive preferences over routes can cause issues. Firstly, it may be perceived to cause routing table churn locally at speakers; secondly, and more seriously, it may cause routing instability in iBGP topologies, where sets of speakers continually oscillate between different paths. The first issue arises from how speakers often implement routing decisions. Though BGP defines a selection process that will deterministically select the same route as best at any given speaker, even with MED, that process requires evaluating all routes together. For performance and ease of implementation reasons, many implementations evaluate route preferences in a pair-wise fashion instead. Given there is no well-defined order when MED is involved, the best route that will be chosen becomes subject to implementation details, such as the order the routes are stored in. That may be (locally) non-deterministic, e.g.: it may be the order the routes were received in. This indeterminism may be considered undesirable, though it need not cause problems. It may mean additional routing churn is perceived, as sometimes more updates may be produced than at other times in reaction to some event . This first issue can be fixed with a more deterministic route selection that ensures routes are ordered by the neighbouring AS during selection. :clicmd:`bgp deterministic-med`. This may reduce the number of updates as routes are received, and may in some cases reduce routing churn. Though, it could equally deterministically produce the largest possible set of updates in response to the most common sequence of received updates. A deterministic order of evaluation tends to imply an additional overhead of sorting over any set of n routes to a destination. The implementation of deterministic MED in FRR scales significantly worse than most sorting algorithms at present, with the number of paths to a given destination. That number is often low enough to not cause any issues, but where there are many paths, the deterministic comparison may quickly become increasingly expensive in terms of CPU. Deterministic local evaluation can *not* fix the second, more major, issue of MED however. Which is that the non-transitive preference of routes MED can cause may lead to routing instability or oscillation across multiple speakers in iBGP topologies. This can occur with full-mesh iBGP, but is particularly problematic in non-full-mesh iBGP topologies that further reduce the routing information known to each speaker. This has primarily been documented with iBGP :ref:`route-reflection ` topologies. However, any route-hiding technologies potentially could also exacerbate oscillation with MED. This second issue occurs where speakers each have only a subset of routes, and there are cycles in the preferences between different combinations of routes - as the undefined order of preference of MED allows - and the routes are distributed in a way that causes the BGP speakers to 'chase' those cycles. This can occur even if all speakers use a deterministic order of evaluation in route selection. E.g., speaker 4 in AS A might receive a route from speaker 2 in AS X, and from speaker 3 in AS Y; while speaker 5 in AS A might receive that route from speaker 1 in AS Y. AS Y might set a MED of 200 at speaker 1, and 100 at speaker 3. I.e, using ASN:ID:MED to label the speakers: :: . /---------------\\ X:2------|--A:4-------A:5--|-Y:1:200 Y:3:100--|-/ | \\---------------/ Assuming all other metrics are equal (AS_PATH, ORIGIN, 0 IGP costs), then based on the RFC4271 decision process speaker 4 will choose X:2 over Y:3:100, based on the lower ID of 2. Speaker 4 advertises X:2 to speaker 5. Speaker 5 will continue to prefer Y:1:200 based on the ID, and advertise this to speaker 4. Speaker 4 will now have the full set of routes, and the Y:1:200 it receives from 5 will beat X:2, but when speaker 4 compares Y:1:200 to Y:3:100 the MED check now becomes active as the ASes match, and now Y:3:100 is preferred. Speaker 4 therefore now advertises Y:3:100 to 5, which will also agrees that Y:3:100 is preferred to Y:1:200, and so withdraws the latter route from 4. Speaker 4 now has only X:2 and Y:3:100, and X:2 beats Y:3:100, and so speaker 4 implicitly updates its route to speaker 5 to X:2. Speaker 5 sees that Y:1:200 beats X:2 based on the ID, and advertises Y:1:200 to speaker 4, and the cycle continues. The root cause is the lack of a clear order of preference caused by how MED sometimes is and sometimes is not compared, leading to this cycle in the preferences between the routes: :: . /---> X:2 ---beats---> Y:3:100 --\\ | | | | \\---beats--- Y:1:200 <---beats---/ This particular type of oscillation in full-mesh iBGP topologies can be avoided by speakers preferring already selected, external routes rather than choosing to update to new a route based on a post-MED metric (e.g. router-ID), at the cost of a non-deterministic selection process. FRR implements this, as do many other implementations, so long as it is not overridden by setting :clicmd:`bgp bestpath compare-routerid`, and see also :ref:`bgp-route-selection`. However, more complex and insidious cycles of oscillation are possible with iBGP route-reflection, which are not so easily avoided. These have been documented in various places. See, e.g.: - [bgp-route-osci-cond]_ - [stable-flexible-ibgp]_ - [ibgp-correctness]_ for concrete examples and further references. There is as of this writing *no* known way to use MED for its original purpose; *and* reduce routing information in iBGP topologies; *and* be sure to avoid the instability problems of MED due the non-transitive routing preferences it can induce; in general on arbitrary networks. There may be iBGP topology specific ways to reduce the instability risks, even while using MED, e.g.: by constraining the reflection topology and by tuning IGP costs between route-reflector clusters, see :rfc:`3345` for details. In the near future, the Add-Path extension to BGP may also solve MED oscillation while still allowing MED to be used as intended, by distributing "best-paths per neighbour AS". This would be at the cost of distributing at least as many routes to all speakers as a full-mesh iBGP would, if not more, while also imposing similar CPU overheads as the "Deterministic MED" feature at each Add-Path reflector. More generally, the instability problems that MED can introduce on more complex, non-full-mesh, iBGP topologies may be avoided either by: - Setting :clicmd:`bgp always-compare-med`, however this allows MED to be compared across values set by different neighbour ASes, which may not produce coherent desirable results, of itself. - Effectively ignoring MED by setting MED to the same value (e.g.: 0) using :clicmd:`set metric METRIC` on all received routes, in combination with setting :clicmd:`bgp always-compare-med` on all speakers. This is the simplest and most performant way to avoid MED oscillation issues, where an AS is happy not to allow neighbours to inject this problematic metric. As MED is evaluated after the AS_PATH length check, another possible use for MED is for intra-AS steering of routes with equal AS_PATH length, as an extension of the last case above. As MED is evaluated before IGP metric, this can allow cold-potato routing to be implemented to send traffic to preferred hand-offs with neighbours, rather than the closest hand-off according to the IGP metric. Note that even if action is taken to address the MED non-transitivity issues, other oscillations may still be possible. E.g., on IGP cost if iBGP and IGP topologies are at cross-purposes with each other - see the Flavel and Roughan paper above for an example. Hence the guideline that the iBGP topology should follow the IGP topology. .. index:: bgp deterministic-med .. clicmd:: bgp deterministic-med Carry out route-selection in way that produces deterministic answers locally, even in the face of MED and the lack of a well-defined order of preference it can induce on routes. Without this option the preferred route with MED may be determined largely by the order that routes were received in. Setting this option will have a performance cost that may be noticeable when there are many routes for each destination. Currently in FRR it is implemented in a way that scales poorly as the number of routes per destination increases. The default is that this option is not set. Note that there are other sources of indeterminism in the route selection process, specifically, the preference for older and already selected routes from eBGP peers, :ref:`bgp-route-selection`. .. index:: bgp always-compare-med .. clicmd:: bgp always-compare-med Always compare the MED on routes, even when they were received from different neighbouring ASes. Setting this option makes the order of preference of routes more defined, and should eliminate MED induced oscillations. If using this option, it may also be desirable to use :clicmd:`set metric METRIC` to set MED to 0 on routes received from external neighbours. This option can be used, together with :clicmd:`set metric METRIC` to use MED as an intra-AS metric to steer equal-length AS_PATH routes to, e.g., desired exit points. .. _bgp-network: Networks -------- .. index:: network A.B.C.D/M .. clicmd:: network A.B.C.D/M This command adds the announcement network. .. code-block:: frr router bgp 1 address-family ipv4 unicast network 10.0.0.0/8 exit-address-family This configuration example says that network 10.0.0.0/8 will be announced to all neighbors. Some vendors' routers don't advertise routes if they aren't present in their IGP routing tables; `bgpd` doesn't care about IGP routes when announcing its routes. .. index:: no network A.B.C.D/M .. clicmd:: no network A.B.C.D/M .. _bgp-route-aggregation: Route Aggregation ----------------- .. _bgp-route-aggregation-ipv4: Route Aggregation-IPv4 Address Family ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ .. index:: aggregate-address A.B.C.D/M .. clicmd:: aggregate-address A.B.C.D/M This command specifies an aggregate address. .. index:: aggregate-address A.B.C.D/M route-map NAME .. clicmd:: aggregate-address A.B.C.D/M route-map NAME Apply a route-map for an aggregated prefix. .. index:: aggregate-address A.B.C.D/M as-set .. clicmd:: aggregate-address A.B.C.D/M as-set This command specifies an aggregate address. Resulting routes include AS set. .. index:: aggregate-address A.B.C.D/M summary-only .. clicmd:: aggregate-address A.B.C.D/M summary-only This command specifies an aggregate address. Aggregated routes will not be announce. .. index:: no aggregate-address A.B.C.D/M .. clicmd:: no aggregate-address A.B.C.D/M This command removes an aggregate address. This configuration example setup the aggregate-address under ipv4 address-family. .. code-block:: frr router bgp 1 address-family ipv4 unicast aggregate-address 10.0.0.0/8 aggregate-address 20.0.0.0/8 as-set aggregate-address 40.0.0.0/8 summary-only aggregate-address 50.0.0.0/8 route-map aggr-rmap exit-address-family .. _bgp-route-aggregation-ipv6: Route Aggregation-IPv6 Address Family ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ .. index:: aggregate-address X:X::X:X/M .. clicmd:: aggregate-address X:X::X:X/M This command specifies an aggregate address. .. index:: aggregate-address X:X::X:X/M route-map NAME .. clicmd:: aggregate-address X:X::X:X/M route-map NAME Apply a route-map for an aggregated prefix. .. index:: aggregate-address X:X::X:X/M as-set .. clicmd:: aggregate-address X:X::X:X/M as-set This command specifies an aggregate address. Resulting routes include AS set. .. index:: aggregate-address X:X::X:X/M summary-only .. clicmd:: aggregate-address X:X::X:X/M summary-only This command specifies an aggregate address. Aggregated routes will not be announce. .. index:: no aggregate-address X:X::X:X/M .. clicmd:: no aggregate-address X:X::X:X/M This command removes an aggregate address. This configuration example setup the aggregate-address under ipv6 address-family. .. code-block:: frr router bgp 1 address-family ipv6 unicast aggregate-address 10::0/64 aggregate-address 20::0/64 as-set aggregate-address 40::0/64 summary-only aggregate-address 50::0/64 route-map aggr-rmap exit-address-family .. _bgp-redistribute-to-bgp: Redistribution -------------- .. index:: redistribute kernel .. clicmd:: redistribute kernel Redistribute kernel route to BGP process. .. index:: redistribute static .. clicmd:: redistribute static Redistribute static route to BGP process. .. index:: redistribute connected .. clicmd:: redistribute connected Redistribute connected route to BGP process. .. index:: redistribute rip .. clicmd:: redistribute rip Redistribute RIP route to BGP process. .. index:: redistribute ospf .. clicmd:: redistribute ospf Redistribute OSPF route to BGP process. .. index:: redistribute vnc .. clicmd:: redistribute vnc Redistribute VNC routes to BGP process. .. index:: redistribute vnc-direct .. clicmd:: redistribute vnc-direct Redistribute VNC direct (not via zebra) routes to BGP process. .. index:: update-delay MAX-DELAY .. clicmd:: update-delay MAX-DELAY .. index:: update-delay MAX-DELAY ESTABLISH-WAIT .. clicmd:: update-delay MAX-DELAY ESTABLISH-WAIT This feature is used to enable read-only mode on BGP process restart or when BGP process is cleared using 'clear ip bgp \*'. When applicable, read-only mode would begin as soon as the first peer reaches Established status and a timer for max-delay seconds is started. During this mode BGP doesn't run any best-path or generate any updates to its peers. This mode continues until: 1. All the configured peers, except the shutdown peers, have sent explicit EOR (End-Of-RIB) or an implicit-EOR. The first keep-alive after BGP has reached Established is considered an implicit-EOR. If the establish-wait optional value is given, then BGP will wait for peers to reach established from the beginning of the update-delay till the establish-wait period is over, i.e. the minimum set of established peers for which EOR is expected would be peers established during the establish-wait window, not necessarily all the configured neighbors. 2. max-delay period is over. On hitting any of the above two conditions, BGP resumes the decision process and generates updates to its peers. Default max-delay is 0, i.e. the feature is off by default. .. index:: table-map ROUTE-MAP-NAME .. clicmd:: table-map ROUTE-MAP-NAME This feature is used to apply a route-map on route updates from BGP to Zebra. All the applicable match operations are allowed, such as match on prefix, next-hop, communities, etc. Set operations for this attach-point are limited to metric and next-hop only. Any operation of this feature does not affect BGPs internal RIB. Supported for ipv4 and ipv6 address families. It works on multi-paths as well, however, metric setting is based on the best-path only. .. _bgp-peers: Peers ----- .. _bgp-defining-peers: Defining Peers ^^^^^^^^^^^^^^ .. index:: neighbor PEER remote-as ASN .. clicmd:: neighbor PEER remote-as ASN Creates a new neighbor whose remote-as is ASN. PEER can be an IPv4 address or an IPv6 address or an interface to use for the connection. .. code-block:: frr router bgp 1 neighbor 10.0.0.1 remote-as 2 In this case my router, in AS-1, is trying to peer with AS-2 at 10.0.0.1. This command must be the first command used when configuring a neighbor. If the remote-as is not specified, *bgpd* will complain like this: :: can't find neighbor 10.0.0.1 .. index:: neighbor PEER remote-as internal .. clicmd:: neighbor PEER remote-as internal Create a peer as you would when you specify an ASN, except that if the peers ASN is different than mine as specified under the :clicmd:`router bgp ASN` command the connection will be denied. .. index:: neighbor PEER remote-as external .. clicmd:: neighbor PEER remote-as external Create a peer as you would when you specify an ASN, except that if the peers ASN is the same as mine as specified under the :clicmd:`router bgp ASN` command the connection will be denied. .. index:: [no] bgp listen range peer-group PGNAME .. clicmd:: [no] bgp listen range peer-group PGNAME Accept connections from any peers in the specified prefix. Configuration from the specified peer-group is used to configure these peers. .. note:: When using BGP listen ranges, if the associated peer group has TCP MD5 authentication configured, your kernel must support this on prefixes. On Linux, this support was added in kernel version 4.14. If your kernel does not support this feature you will get a warning in the log file, and the listen range will only accept connections from peers without MD5 configured. Additionally, we have observed that when using this option at scale (several hundred peers) the kernel may hit its option memory limit. In this situation you will see error messages like: ``bgpd: sockopt_tcp_signature: setsockopt(23): Cannot allocate memory`` In this case you need to increase the value of the sysctl ``net.core.optmem_max`` to allow the kernel to allocate the necessary option memory. .. _bgp-configuring-peers: Configuring Peers ^^^^^^^^^^^^^^^^^ .. index:: [no] neighbor PEER shutdown .. clicmd:: [no] neighbor PEER shutdown Shutdown the peer. We can delete the neighbor's configuration by ``no neighbor PEER remote-as ASN`` but all configuration of the neighbor will be deleted. When you want to preserve the configuration, but want to drop the BGP peer, use this syntax. .. index:: [no] neighbor PEER disable-connected-check .. clicmd:: [no] neighbor PEER disable-connected-check Allow peerings between directly connected eBGP peers using loopback addresses. .. index:: [no] neighbor PEER ebgp-multihop .. clicmd:: [no] neighbor PEER ebgp-multihop .. index:: [no] neighbor PEER description ... .. clicmd:: [no] neighbor PEER description ... Set description of the peer. .. index:: [no] neighbor PEER version VERSION .. clicmd:: [no] neighbor PEER version VERSION Set up the neighbor's BGP version. `version` can be `4`, `4+` or `4-`. BGP version `4` is the default value used for BGP peering. BGP version `4+` means that the neighbor supports Multiprotocol Extensions for BGP-4. BGP version `4-` is similar but the neighbor speaks the old Internet-Draft revision 00's Multiprotocol Extensions for BGP-4. Some routing software is still using this version. .. index:: [no] neighbor PEER interface IFNAME .. clicmd:: [no] neighbor PEER interface IFNAME When you connect to a BGP peer over an IPv6 link-local address, you have to specify the IFNAME of the interface used for the connection. To specify IPv4 session addresses, see the ``neighbor PEER update-source`` command below. This command is deprecated and may be removed in a future release. Its use should be avoided. .. index:: [no] neighbor PEER next-hop-self [all] .. clicmd:: [no] neighbor PEER next-hop-self [all] This command specifies an announced route's nexthop as being equivalent to the address of the bgp router if it is learned via eBGP. If the optional keyword `all` is specified the modification is done also for routes learned via iBGP. .. index:: [no] neighbor PEER update-source .. clicmd:: [no] neighbor PEER update-source Specify the IPv4 source address to use for the :abbr:`BGP` session to this neighbour, may be specified as either an IPv4 address directly or as an interface name (in which case the *zebra* daemon MUST be running in order for *bgpd* to be able to retrieve interface state). .. code-block:: frr router bgp 64555 neighbor foo update-source 192.168.0.1 neighbor bar update-source lo0 .. index:: [no] neighbor PEER default-originate .. clicmd:: [no] neighbor PEER default-originate *bgpd*'s default is to not announce the default route (0.0.0.0/0) even if it is in routing table. When you want to announce default routes to the peer, use this command. .. index:: neighbor PEER port PORT .. clicmd:: neighbor PEER port PORT .. index:: neighbor PEER send-community .. clicmd:: neighbor PEER send-community .. index:: [no] neighbor PEER weight WEIGHT .. clicmd:: [no] neighbor PEER weight WEIGHT This command specifies a default `weight` value for the neighbor's routes. .. index:: [no] neighbor PEER maximum-prefix NUMBER .. clicmd:: [no] neighbor PEER maximum-prefix NUMBER Sets a maximum number of prefixes we can receive from a given peer. If this number is exceeded, the BGP session will be destroyed. In practice, it is generally preferable to use a prefix-list to limit what prefixes are received from the peer instead of using this knob. Tearing down the BGP session when a limit is exceeded is far more destructive than merely rejecting undesired prefixes. The prefix-list method is also much more granular and offers much smarter matching criterion than number of received prefixes, making it more suited to implementing policy. .. index:: [no] neighbor PEER local-as AS-NUMBER [no-prepend] [replace-as] .. clicmd:: [no] neighbor PEER local-as AS-NUMBER [no-prepend] [replace-as] Specify an alternate AS for this BGP process when interacting with the specified peer. With no modifiers, the specified local-as is prepended to the received AS_PATH when receiving routing updates from the peer, and prepended to the outgoing AS_PATH (after the process local AS) when transmitting local routes to the peer. If the no-prepend attribute is specified, then the supplied local-as is not prepended to the received AS_PATH. If the replace-as attribute is specified, then only the supplied local-as is prepended to the AS_PATH when transmitting local-route updates to this peer. Note that replace-as can only be specified if no-prepend is. This command is only allowed for eBGP peers. .. index:: [no] neighbor PEER ttl-security hops NUMBER .. clicmd:: [no] neighbor PEER ttl-security hops NUMBER This command enforces Generalized TTL Security Mechanism (GTSM), as specified in RFC 5082. With this command, only neighbors that are the specified number of hops away will be allowed to become neighbors. This command is mutually exclusive with *ebgp-multihop*. .. index:: [no] neighbor PEER capability extended-nexthop .. clicmd:: [no] neighbor PEER capability extended-nexthop Allow bgp to negotiate the extended-nexthop capability with it's peer. If you are peering over a v6 LL address then this capability is turned on automatically. If you are peering over a v6 Global Address then turning on this command will allow BGP to install v4 routes with v6 nexthops if you do not have v4 configured on interfaces. .. index:: [no] bgp fast-external-failover .. clicmd:: [no] bgp fast-external-failover This command causes bgp to not take down ebgp peers immediately when a link flaps. `bgp fast-external-failover` is the default and will not be displayed as part of a `show run`. The no form of the command turns off this ability. .. index:: [no] bgp default ipv4-unicast .. clicmd:: [no] bgp default ipv4-unicast This command allows the user to specify that v4 peering is turned on by default or not. This command defaults to on and is not displayed. The `no bgp default ipv4-unicast` form of the command is displayed. .. _bgp-peer-filtering: Peer Filtering ^^^^^^^^^^^^^^ .. index:: neighbor PEER distribute-list NAME [in|out] .. clicmd:: neighbor PEER distribute-list NAME [in|out] This command specifies a distribute-list for the peer. `direct` is ``in`` or ``out``. .. index:: neighbor PEER prefix-list NAME [in|out] .. clicmd:: neighbor PEER prefix-list NAME [in|out] .. index:: neighbor PEER filter-list NAME [in|out] .. clicmd:: neighbor PEER filter-list NAME [in|out] .. index:: neighbor PEER route-map NAME [in|out] .. clicmd:: neighbor PEER route-map NAME [in|out] Apply a route-map on the neighbor. `direct` must be `in` or `out`. .. index:: bgp route-reflector allow-outbound-policy .. clicmd:: bgp route-reflector allow-outbound-policy By default, attribute modification via route-map policy out is not reflected on reflected routes. This option allows the modifications to be reflected as well. Once enabled, it affects all reflected routes. .. _bgp-peer-group: Peer Groups ^^^^^^^^^^^ Peer groups are used to help improve scaling by generating the same update information to all members of a peer group. Note that this means that the routes generated by a member of a peer group will be sent back to that originating peer with the originator identifier attribute set to indicated the originating peer. All peers not associated with a specific peer group are treated as belonging to a default peer group, and will share updates. .. index:: neighbor WORD peer-group .. clicmd:: neighbor WORD peer-group This command defines a new peer group. .. index:: neighbor PEER peer-group PGNAME .. clicmd:: neighbor PEER peer-group PGNAME This command bind specific peer to peer group WORD. .. index:: neighbor PEER solo .. clicmd:: neighbor PEER solo This command is used to indicate that routes advertised by the peer should not be reflected back to the peer. This command only is only meaningful when there is a single peer defined in the peer-group. Capability Negotiation ^^^^^^^^^^^^^^^^^^^^^^ .. index:: neighbor PEER strict-capability-match .. clicmd:: neighbor PEER strict-capability-match .. index:: no neighbor PEER strict-capability-match .. clicmd:: no neighbor PEER strict-capability-match Strictly compares remote capabilities and local capabilities. If capabilities are different, send Unsupported Capability error then reset connection. You may want to disable sending Capability Negotiation OPEN message optional parameter to the peer when remote peer does not implement Capability Negotiation. Please use *dont-capability-negotiate* command to disable the feature. .. index:: neighbor PEER dont-capability-negotiate .. clicmd:: neighbor PEER dont-capability-negotiate .. index:: no neighbor PEER dont-capability-negotiate .. clicmd:: no neighbor PEER dont-capability-negotiate Suppress sending Capability Negotiation as OPEN message optional parameter to the peer. This command only affects the peer is configured other than IPv4 unicast configuration. When remote peer does not have capability negotiation feature, remote peer will not send any capabilities at all. In that case, bgp configures the peer with configured capabilities. You may prefer locally configured capabilities more than the negotiated capabilities even though remote peer sends capabilities. If the peer is configured by *override-capability*, *bgpd* ignores received capabilities then override negotiated capabilities with configured values. .. index:: neighbor PEER override-capability .. clicmd:: neighbor PEER override-capability .. index:: no neighbor PEER override-capability .. clicmd:: no neighbor PEER override-capability Override the result of Capability Negotiation with local configuration. Ignore remote peer's capability value. .. _bgp-as-path-access-lists: AS Path Access Lists -------------------- AS path access list is user defined AS path. .. index:: ip as-path access-list WORD permit|deny LINE .. clicmd:: ip as-path access-list WORD permit|deny LINE This command defines a new AS path access list. .. index:: no ip as-path access-list WORD .. clicmd:: no ip as-path access-list WORD .. index:: no ip as-path access-list WORD permit|deny LINE .. clicmd:: no ip as-path access-list WORD permit|deny LINE .. _bgp-using-as-path-in-route-map: Using AS Path in Route Map -------------------------- .. index:: [no] match as-path WORD .. clicmd:: [no] match as-path WORD For a given as-path, WORD, match it on the BGP as-path given for the prefix and if it matches do normal route-map actions. The no form of the command removes this match from the route-map. .. index:: [no] set as-path prepend AS-PATH .. clicmd:: [no] set as-path prepend AS-PATH Prepend the given string of AS numbers to the AS_PATH of the BGP path's NLRI. The no form of this command removes this set operation from the route-map. .. index:: [no] set as-path prepend last-as NUM .. clicmd:: [no] set as-path prepend last-as NUM Prepend the existing last AS number (the leftmost ASN) to the AS_PATH. The no form of this command removes this set operation from the route-map. .. _bgp-communities-attribute: Communities Attribute --------------------- The BGP communities attribute is widely used for implementing policy routing. Network operators can manipulate BGP communities attribute based on their network policy. BGP communities attribute is defined in :rfc:`1997` and :rfc:`1998`. It is an optional transitive attribute, therefore local policy can travel through different autonomous system. The communities attribute is a set of communities values. Each community value is 4 octet long. The following format is used to define the community value. ``AS:VAL`` This format represents 4 octet communities value. ``AS`` is high order 2 octet in digit format. ``VAL`` is low order 2 octet in digit format. This format is useful to define AS oriented policy value. For example, ``7675:80`` can be used when AS 7675 wants to pass local policy value 80 to neighboring peer. ``internet`` ``internet`` represents well-known communities value 0. ``graceful-shutdown`` ``graceful-shutdown`` represents well-known communities value ``GRACEFUL_SHUTDOWN`` ``0xFFFF0000`` ``65535:0``. :rfc:`8326` implements the purpose Graceful BGP Session Shutdown to reduce the amount of lost traffic when taking BGP sessions down for maintenance. The use of the community needs to be supported from your peers side to actually have any effect. ``accept-own`` ``accept-own`` represents well-known communities value ``ACCEPT_OWN`` ``0xFFFF0001`` ``65535:1``. :rfc:`7611` implements a way to signal to a router to accept routes with a local nexthop address. This can be the case when doing policing and having traffic having a nexthop located in another VRF but still local interface to the router. It is recommended to read the RFC for full details. ``route-filter-translated-v4`` ``route-filter-translated-v4`` represents well-known communities value ``ROUTE_FILTER_TRANSLATED_v4`` ``0xFFFF0002`` ``65535:2``. ``route-filter-v4`` ``route-filter-v4`` represents well-known communities value ``ROUTE_FILTER_v4`` ``0xFFFF0003`` ``65535:3``. ``route-filter-translated-v6`` ``route-filter-translated-v6`` represents well-known communities value ``ROUTE_FILTER_TRANSLATED_v6`` ``0xFFFF0004`` ``65535:4``. ``route-filter-v6`` ``route-filter-v6`` represents well-known communities value ``ROUTE_FILTER_v6`` ``0xFFFF0005`` ``65535:5``. ``llgr-stale`` ``llgr-stale`` represents well-known communities value ``LLGR_STALE`` ``0xFFFF0006`` ``65535:6``. Assigned and intended only for use with routers supporting the Long-lived Graceful Restart Capability as described in [Draft-IETF-uttaro-idr-bgp-persistence]_. Routers receiving routes with this community may (depending on implementation) choose allow to reject or modify routes on the presence or absence of this community. ``no-llgr`` ``no-llgr`` represents well-known communities value ``NO_LLGR`` ``0xFFFF0007`` ``65535:7``. Assigned and intended only for use with routers supporting the Long-lived Graceful Restart Capability as described in [Draft-IETF-uttaro-idr-bgp-persistence]_. Routers receiving routes with this community may (depending on implementation) choose allow to reject or modify routes on the presence or absence of this community. ``accept-own-nexthop`` ``accept-own-nexthop`` represents well-known communities value ``accept-own-nexthop`` ``0xFFFF0008`` ``65535:8``. [Draft-IETF-agrewal-idr-accept-own-nexthop]_ describes how to tag and label VPN routes to be able to send traffic between VRFs via an internal layer 2 domain on the same PE device. Refer to [Draft-IETF-agrewal-idr-accept-own-nexthop]_ for full details. ``blackhole`` ``blackhole`` represents well-known communities value ``BLACKHOLE`` ``0xFFFF029A`` ``65535:666``. :rfc:`7999` documents sending prefixes to EBGP peers and upstream for the purpose of blackholing traffic. Prefixes tagged with the this community should normally not be re-advertised from neighbors of the originating network. It is recommended upon receiving prefixes tagged with this community to add ``NO_EXPORT`` and ``NO_ADVERTISE``. ``no-export`` ``no-export`` represents well-known communities value ``NO_EXPORT`` ``0xFFFFFF01``. All routes carry this value must not be advertised to outside a BGP confederation boundary. If neighboring BGP peer is part of BGP confederation, the peer is considered as inside a BGP confederation boundary, so the route will be announced to the peer. ``no-advertise`` ``no-advertise`` represents well-known communities value ``NO_ADVERTISE`` ``0xFFFFFF02``. All routes carry this value must not be advertise to other BGP peers. ``local-AS`` ``local-AS`` represents well-known communities value ``NO_EXPORT_SUBCONFED`` ``0xFFFFFF03``. All routes carry this value must not be advertised to external BGP peers. Even if the neighboring router is part of confederation, it is considered as external BGP peer, so the route will not be announced to the peer. ``no-peer`` ``no-peer`` represents well-known communities value ``NOPEER`` ``0xFFFFFF04`` ``65535:65284``. :rfc:`3765` is used to communicate to another network how the originating network want the prefix propagated. When the communities attribute is received duplicate community values in the attribute are ignored and value is sorted in numerical order. .. [Draft-IETF-uttaro-idr-bgp-persistence] .. [Draft-IETF-agrewal-idr-accept-own-nexthop] .. _bgp-community-lists: Community Lists ^^^^^^^^^^^^^^^ Community lists are user defined lists of community attribute values. These lists can be used for matching or manipulating the communities attribute in UPDATE messages. There are two types of community list: standard This type accepts an explicit value for the attribute. expanded This type accepts a regular expression. Because the regex must be interpreted on each use expanded community lists are slower than standard lists. .. index:: ip community-list standard NAME permit|deny COMMUNITY .. clicmd:: ip community-list standard NAME permit|deny COMMUNITY This command defines a new standard community list. ``COMMUNITY`` is communities value. The ``COMMUNITY`` is compiled into community structure. We can define multiple community list under same name. In that case match will happen user defined order. Once the community list matches to communities attribute in BGP updates it return permit or deny by the community list definition. When there is no matched entry, deny will be returned. When ``COMMUNITY`` is empty it matches to any routes. .. index:: ip community-list expanded NAME permit|deny COMMUNITY .. clicmd:: ip community-list expanded NAME permit|deny COMMUNITY This command defines a new expanded community list. ``COMMUNITY`` is a string expression of communities attribute. ``COMMUNITY`` can be a regular expression (:ref:`bgp-regular-expressions`) to match the communities attribute in BGP updates. The expanded community is only used to filter, not `set` actions. .. deprecated:: 5.0 It is recommended to use the more explicit versions of this command. .. index:: ip community-list NAME permit|deny COMMUNITY .. clicmd:: ip community-list NAME permit|deny COMMUNITY When the community list type is not specified, the community list type is automatically detected. If ``COMMUNITY`` can be compiled into communities attribute, the community list is defined as a standard community list. Otherwise it is defined as an expanded community list. This feature is left for backward compatibility. Use of this feature is not recommended. .. index:: no ip community-list [standard|expanded] NAME .. clicmd:: no ip community-list [standard|expanded] NAME Deletes the community list specified by ``NAME``. All community lists share the same namespace, so it's not necessary to specify ``standard`` or ``expanded``; these modifiers are purely aesthetic. .. index:: show ip community-list [NAME] .. clicmd:: show ip community-list [NAME] Displays community list information. When ``NAME`` is specified the specified community list's information is shown. :: # show ip community-list Named Community standard list CLIST permit 7675:80 7675:100 no-export deny internet Named Community expanded list EXPAND permit : # show ip community-list CLIST Named Community standard list CLIST permit 7675:80 7675:100 no-export deny internet .. _bgp-numbered-community-lists: Numbered Community Lists ^^^^^^^^^^^^^^^^^^^^^^^^ When number is used for BGP community list name, the number has special meanings. Community list number in the range from 1 and 99 is standard community list. Community list number in the range from 100 to 199 is expanded community list. These community lists are called as numbered community lists. On the other hand normal community lists is called as named community lists. .. index:: ip community-list (1-99) permit|deny COMMUNITY .. clicmd:: ip community-list (1-99) permit|deny COMMUNITY This command defines a new community list. The argument to (1-99) defines the list identifier. .. index:: ip community-list (100-199) permit|deny COMMUNITY .. clicmd:: ip community-list (100-199) permit|deny COMMUNITY This command defines a new expanded community list. The argument to (100-199) defines the list identifier. .. _bgp-using-communities-in-route-map: Using Communities in Route Maps ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ In :ref:`route-map` we can match on or set the BGP communities attribute. Using this feature network operator can implement their network policy based on BGP communities attribute. The ollowing commands can be used in route maps: .. index:: match community WORD exact-match [exact-match] .. clicmd:: match community WORD exact-match [exact-match] This command perform match to BGP updates using community list WORD. When the one of BGP communities value match to the one of communities value in community list, it is match. When `exact-match` keyword is specified, match happen only when BGP updates have completely same communities value specified in the community list. .. index:: set community additive .. clicmd:: set community additive This command sets the community value in BGP updates. If the attribute is already configured, the newly provided value replaces the old one unless the ``additive`` keyword is specified, in which case the new value is appended to the existing value. If ``none`` is specified as the community value, the communities attribute is not sent. It is not possible to set an expanded community list. .. index:: set comm-list WORD delete .. clicmd:: set comm-list WORD delete This command remove communities value from BGP communities attribute. The ``word`` is community list name. When BGP route's communities value matches to the community list ``word``, the communities value is removed. When all of communities value is removed eventually, the BGP update's communities attribute is completely removed. .. _bgp-communities-example: Example Configuration ^^^^^^^^^^^^^^^^^^^^^ The following configuration is exemplary of the most typical usage of BGP communities attribute. In the example, AS 7675 provides an upstream Internet connection to AS 100. When the following configuration exists in AS 7675, the network operator of AS 100 can set local preference in AS 7675 network by setting BGP communities attribute to the updates. .. code-block:: frr router bgp 7675 neighbor 192.168.0.1 remote-as 100 address-family ipv4 unicast neighbor 192.168.0.1 route-map RMAP in exit-address-family ! ip community-list 70 permit 7675:70 ip community-list 70 deny ip community-list 80 permit 7675:80 ip community-list 80 deny ip community-list 90 permit 7675:90 ip community-list 90 deny ! route-map RMAP permit 10 match community 70 set local-preference 70 ! route-map RMAP permit 20 match community 80 set local-preference 80 ! route-map RMAP permit 30 match community 90 set local-preference 90 The following configuration announces ``10.0.0.0/8`` from AS 100 to AS 7675. The route has communities value ``7675:80`` so when above configuration exists in AS 7675, the announced routes' local preference value will be set to 80. .. code-block:: frr router bgp 100 network 10.0.0.0/8 neighbor 192.168.0.2 remote-as 7675 address-family ipv4 unicast neighbor 192.168.0.2 route-map RMAP out exit-address-family ! ip prefix-list PLIST permit 10.0.0.0/8 ! route-map RMAP permit 10 match ip address prefix-list PLIST set community 7675:80 The following configuration is an example of BGP route filtering using communities attribute. This configuration only permit BGP routes which has BGP communities value ``0:80`` or ``0:90``. The network operator can set special internal communities value at BGP border router, then limit the BGP route announcements into the internal network. .. code-block:: frr router bgp 7675 neighbor 192.168.0.1 remote-as 100 address-family ipv4 unicast neighbor 192.168.0.1 route-map RMAP in exit-address-family ! ip community-list 1 permit 0:80 0:90 ! route-map RMAP permit in match community 1 The following example filters BGP routes which have a community value of ``1:1``. When there is no match community-list returns ``deny``. To avoid filtering all routes, a ``permit`` line is set at the end of the community-list. .. code-block:: frr router bgp 7675 neighbor 192.168.0.1 remote-as 100 address-family ipv4 unicast neighbor 192.168.0.1 route-map RMAP in exit-address-family ! ip community-list standard FILTER deny 1:1 ip community-list standard FILTER permit ! route-map RMAP permit 10 match community FILTER The communities value keyword ``internet`` has special meanings in standard community lists. In the below example ``internet`` matches all BGP routes even if the route does not have communities attribute at all. So community list ``INTERNET`` is the same as ``FILTER`` in the previous example. .. code-block:: frr ip community-list standard INTERNET deny 1:1 ip community-list standard INTERNET permit internet The following configuration is an example of communities value deletion. With this configuration the community values ``100:1`` and ``100:2`` are removed from BGP updates. For communities value deletion, only ``permit`` community-list is used. ``deny`` community-list is ignored. .. code-block:: frr router bgp 7675 neighbor 192.168.0.1 remote-as 100 address-family ipv4 unicast neighbor 192.168.0.1 route-map RMAP in exit-address-family ! ip community-list standard DEL permit 100:1 100:2 ! route-map RMAP permit 10 set comm-list DEL delete .. _bgp-extended-communities-attribute: Extended Communities Attribute ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ BGP extended communities attribute is introduced with MPLS VPN/BGP technology. MPLS VPN/BGP expands capability of network infrastructure to provide VPN functionality. At the same time it requires a new framework for policy routing. With BGP Extended Communities Attribute we can use Route Target or Site of Origin for implementing network policy for MPLS VPN/BGP. BGP Extended Communities Attribute is similar to BGP Communities Attribute. It is an optional transitive attribute. BGP Extended Communities Attribute can carry multiple Extended Community value. Each Extended Community value is eight octet length. BGP Extended Communities Attribute provides an extended range compared with BGP Communities Attribute. Adding to that there is a type field in each value to provides community space structure. There are two format to define Extended Community value. One is AS based format the other is IP address based format. ``AS:VAL`` This is a format to define AS based Extended Community value. ``AS`` part is 2 octets Global Administrator subfield in Extended Community value. ``VAL`` part is 4 octets Local Administrator subfield. ``7675:100`` represents AS 7675 policy value 100. ``IP-Address:VAL`` This is a format to define IP address based Extended Community value. ``IP-Address`` part is 4 octets Global Administrator subfield. ``VAL`` part is 2 octets Local Administrator subfield. .. _bgp-extended-community-lists: Extended Community Lists ^^^^^^^^^^^^^^^^^^^^^^^^ .. index:: ip extcommunity-list standard NAME permit|deny EXTCOMMUNITY .. clicmd:: ip extcommunity-list standard NAME permit|deny EXTCOMMUNITY This command defines a new standard extcommunity-list. `extcommunity` is extended communities value. The `extcommunity` is compiled into extended community structure. We can define multiple extcommunity-list under same name. In that case match will happen user defined order. Once the extcommunity-list matches to extended communities attribute in BGP updates it return permit or deny based upon the extcommunity-list definition. When there is no matched entry, deny will be returned. When `extcommunity` is empty it matches to any routes. .. index:: ip extcommunity-list expanded NAME permit|deny LINE .. clicmd:: ip extcommunity-list expanded NAME permit|deny LINE This command defines a new expanded extcommunity-list. `line` is a string expression of extended communities attribute. `line` can be a regular expression (:ref:`bgp-regular-expressions`) to match an extended communities attribute in BGP updates. .. index:: no ip extcommunity-list NAME .. clicmd:: no ip extcommunity-list NAME .. index:: no ip extcommunity-list standard NAME .. clicmd:: no ip extcommunity-list standard NAME .. index:: no ip extcommunity-list expanded NAME .. clicmd:: no ip extcommunity-list expanded NAME These commands delete extended community lists specified by `name`. All of extended community lists shares a single name space. So extended community lists can be removed simply specifying the name. .. index:: show ip extcommunity-list .. clicmd:: show ip extcommunity-list .. index:: show ip extcommunity-list NAME .. clicmd:: show ip extcommunity-list NAME This command displays current extcommunity-list information. When `name` is specified the community list's information is shown.:: # show ip extcommunity-list .. _bgp-extended-communities-in-route-map: BGP Extended Communities in Route Map """"""""""""""""""""""""""""""""""""" .. index:: match extcommunity WORD .. clicmd:: match extcommunity WORD .. index:: set extcommunity rt EXTCOMMUNITY .. clicmd:: set extcommunity rt EXTCOMMUNITY This command set Route Target value. .. index:: set extcommunity soo EXTCOMMUNITY .. clicmd:: set extcommunity soo EXTCOMMUNITY This command set Site of Origin value. Note that the extended expanded community is only used for `match` rule, not for `set` actions. .. _bgp-large-communities-attribute: Large Communities Attribute ^^^^^^^^^^^^^^^^^^^^^^^^^^^ The BGP Large Communities attribute was introduced in Feb 2017 with :rfc:`8092`. The BGP Large Communities Attribute is similar to the BGP Communities Attribute except that it has 3 components instead of two and each of which are 4 octets in length. Large Communities bring additional functionality and convenience over traditional communities, specifically the fact that the ``GLOBAL`` part below is now 4 octets wide allowing seamless use in networks using 4-byte ASNs. ``GLOBAL:LOCAL1:LOCAL2`` This is the format to define Large Community values. Referencing :rfc:`8195` the values are commonly referred to as follows: - The ``GLOBAL`` part is a 4 octet Global Administrator field, commonly used as the operators AS number. - The ``LOCAL1`` part is a 4 octet Local Data Part 1 subfield referred to as a function. - The ``LOCAL2`` part is a 4 octet Local Data Part 2 field and referred to as the parameter subfield. As an example, ``65551:1:10`` represents AS 65551 function 1 and parameter 10. The referenced RFC above gives some guidelines on recommended usage. .. _bgp-large-community-lists: Large Community Lists """"""""""""""""""""" Two types of large community lists are supported, namely `standard` and `expanded`. .. index:: ip large-community-list standard NAME permit|deny LARGE-COMMUNITY .. clicmd:: ip large-community-list standard NAME permit|deny LARGE-COMMUNITY This command defines a new standard large-community-list. `large-community` is the Large Community value. We can add multiple large communities under same name. In that case the match will happen in the user defined order. Once the large-community-list matches the Large Communities attribute in BGP updates it will return permit or deny based upon the large-community-list definition. When there is no matched entry, a deny will be returned. When `large-community` is empty it matches any routes. .. index:: ip large-community-list expanded NAME permit|deny LINE .. clicmd:: ip large-community-list expanded NAME permit|deny LINE This command defines a new expanded large-community-list. Where `line` is a string matching expression, it will be compared to the entire Large Communities attribute as a string, with each large-community in order from lowest to highest. `line` can also be a regular expression which matches this Large Community attribute. .. index:: no ip large-community-list NAME .. clicmd:: no ip large-community-list NAME .. index:: no ip large-community-list standard NAME .. clicmd:: no ip large-community-list standard NAME .. index:: no ip large-community-list expanded NAME .. clicmd:: no ip large-community-list expanded NAME These commands delete Large Community lists specified by `name`. All Large Community lists share a single namespace. This means Large Community lists can be removed by simply specifying the name. .. index:: show ip large-community-list .. clicmd:: show ip large-community-list .. index:: show ip large-community-list NAME .. clicmd:: show ip large-community-list NAME This command display current large-community-list information. When `name` is specified the community list information is shown. .. index:: show ip bgp large-community-info .. clicmd:: show ip bgp large-community-info This command displays the current large communities in use. .. _bgp-large-communities-in-route-map: Large Communities in Route Map """""""""""""""""""""""""""""" .. index:: match large-community LINE [exact-match] .. clicmd:: match large-community LINE [exact-match] Where `line` can be a simple string to match, or a regular expression. It is very important to note that this match occurs on the entire large-community string as a whole, where each large-community is ordered from lowest to highest. When `exact-match` keyword is specified, match happen only when BGP updates have completely same large communities value specified in the large community list. .. index:: set large-community LARGE-COMMUNITY .. clicmd:: set large-community LARGE-COMMUNITY .. index:: set large-community LARGE-COMMUNITY LARGE-COMMUNITY .. clicmd:: set large-community LARGE-COMMUNITY LARGE-COMMUNITY .. index:: set large-community LARGE-COMMUNITY additive .. clicmd:: set large-community LARGE-COMMUNITY additive These commands are used for setting large-community values. The first command will overwrite any large-communities currently present. The second specifies two large-communities, which overwrites the current large-community list. The third will add a large-community value without overwriting other values. Multiple large-community values can be specified. Note that the large expanded community is only used for `match` rule, not for `set` actions. .. _bgp-l3vpn-vrfs: L3VPN VRFs ---------- *bgpd* supports :abbr:`L3VPN (Layer 3 Virtual Private Networks)` :abbr:`VRFs (Virtual Routing and Forwarding)` for IPv4 :rfc:`4364` and IPv6 :rfc:`4659`. L3VPN routes, and their associated VRF MPLS labels, can be distributed to VPN SAFI neighbors in the *default*, i.e., non VRF, BGP instance. VRF MPLS labels are reached using *core* MPLS labels which are distributed using LDP or BGP labeled unicast. *bgpd* also supports inter-VRF route leaking. .. _bgp-vrf-route-leaking: VRF Route Leaking ----------------- BGP routes may be leaked (i.e. copied) between a unicast VRF RIB and the VPN SAFI RIB of the default VRF for use in MPLS-based L3VPNs. Unicast routes may also be leaked between any VRFs (including the unicast RIB of the default BGP instanced). A shortcut syntax is also available for specifying leaking from one VRF to another VRF using the default instance's VPN RIB as the intemediary. A common application of the VRF-VRF feature is to connect a customer's private routing domain to a provider's VPN service. Leaking is configured from the point of view of an individual VRF: ``import`` refers to routes leaked from VPN to a unicast VRF, whereas ``export`` refers to routes leaked from a unicast VRF to VPN. Required parameters ^^^^^^^^^^^^^^^^^^^ Routes exported from a unicast VRF to the VPN RIB must be augmented by two parameters: - an :abbr:`RD (Route Distinguisher)` - an :abbr:`RTLIST (Route-target List)` Configuration for these exported routes must, at a minimum, specify these two parameters. Routes imported from the VPN RIB to a unicast VRF are selected according to their RTLISTs. Routes whose RTLIST contains at least one route-target in common with the configured import RTLIST are leaked. Configuration for these imported routes must specify an RTLIST to be matched. The RD, which carries no semantic value, is intended to make the route unique in the VPN RIB among all routes of its prefix that originate from all the customers and sites that are attached to the provider's VPN service. Accordingly, each site of each customer is typically assigned an RD that is unique across the entire provider network. The RTLIST is a set of route-target extended community values whose purpose is to specify route-leaking policy. Typically, a customer is assigned a single route-target value for import and export to be used at all customer sites. This configuration specifies a simple topology wherein a customer has a single routing domain which is shared across all its sites. More complex routing topologies are possible through use of additional route-targets to augment the leaking of sets of routes in various ways. When using the shortcut syntax for vrf-to-vrf leaking, the RD and RT are auto-derived. General configuration ^^^^^^^^^^^^^^^^^^^^^ Configuration of route leaking between a unicast VRF RIB and the VPN SAFI RIB of the default VRF is accomplished via commands in the context of a VRF address-family: .. index:: rd vpn export AS:NN|IP:nn .. clicmd:: rd vpn export AS:NN|IP:nn Specifies the route distinguisher to be added to a route exported from the current unicast VRF to VPN. .. index:: no rd vpn export [AS:NN|IP:nn] .. clicmd:: no rd vpn export [AS:NN|IP:nn] Deletes any previously-configured export route distinguisher. .. index:: rt vpn import|export|both RTLIST... .. clicmd:: rt vpn import|export|both RTLIST... Specifies the route-target list to be attached to a route (export) or the route-target list to match against (import) when exporting/importing between the current unicast VRF and VPN. The RTLIST is a space-separated list of route-targets, which are BGP extended community values as described in :ref:`bgp-extended-communities-attribute`. .. index:: no rt vpn import|export|both [RTLIST...] .. clicmd:: no rt vpn import|export|both [RTLIST...] Deletes any previously-configured import or export route-target list. .. index:: label vpn export (0..1048575)|auto .. clicmd:: label vpn export (0..1048575)|auto Enables an MPLS label to be attached to a route exported from the current unicast VRF to VPN. If the value specified is ``auto``, the label value is automatically assigned from a pool maintained by the Zebra daemon. If Zebra is not running, or if this command is not configured, automatic label assignment will not complete, which will block corresponding route export. .. index:: no label vpn export [(0..1048575)|auto] .. clicmd:: no label vpn export [(0..1048575)|auto] Deletes any previously-configured export label. .. index:: nexthop vpn export A.B.C.D|X:X::X:X .. clicmd:: nexthop vpn export A.B.C.D|X:X::X:X Specifies an optional nexthop value to be assigned to a route exported from the current unicast VRF to VPN. If left unspecified, the nexthop will be set to 0.0.0.0 or 0:0::0:0 (self). .. index:: no nexthop vpn export [A.B.C.D|X:X::X:X] .. clicmd:: no nexthop vpn export [A.B.C.D|X:X::X:X] Deletes any previously-configured export nexthop. .. index:: route-map vpn import|export MAP .. clicmd:: route-map vpn import|export MAP Specifies an optional route-map to be applied to routes imported or exported between the current unicast VRF and VPN. .. index:: no route-map vpn import|export [MAP] .. clicmd:: no route-map vpn import|export [MAP] Deletes any previously-configured import or export route-map. .. index:: import|export vpn .. clicmd:: import|export vpn Enables import or export of routes between the current unicast VRF and VPN. .. index:: no import|export vpn .. clicmd:: no import|export vpn Disables import or export of routes between the current unicast VRF and VPN. .. index:: import vrf VRFNAME .. clicmd:: import vrf VRFNAME Shortcut syntax for specifying automatic leaking from vrf VRFNAME to the current VRF using the VPN RIB as intermediary. The RD and RT are auto derived and should not be specified explicitly for either the source or destination VRF's. This shortcut syntax mode is not compatible with the explicit `import vpn` and `export vpn` statements for the two VRF's involved. The CLI will disallow attempts to configure incompatible leaking modes. .. index:: no import vrf VRFNAME .. clicmd:: no import vrf VRFNAME Disables automatic leaking from vrf VRFNAME to the current VRF using the VPN RIB as intermediary. .. _bgp-cisco-compatibility: Cisco Compatibility ------------------- FRR has commands that change some configuration syntax and default behavior to behave more closely to Cisco conventions. These are deprecated and will be removed in a future version of FRR. .. deprecated:: 5.0 Please transition to using the FRR specific syntax for your configuration. .. index:: bgp config-type cisco .. clicmd:: bgp config-type cisco Cisco compatible BGP configuration output. When this configuration line is specified: - ``no synchronization`` is displayed. This command does nothing and is for display purposes only. - ``no auto-summary`` is displayed. - The ``network`` and ``aggregate-address`` arguments are displayed as: :: A.B.C.D M.M.M.M FRR: network 10.0.0.0/8 Cisco: network 10.0.0.0 FRR: aggregate-address 192.168.0.0/24 Cisco: aggregate-address 192.168.0.0 255.255.255.0 Community attribute handling is also different. If no configuration is specified community attribute and extended community attribute are sent to the neighbor. If a user manually disables the feature, the community attribute is not sent to the neighbor. When ``bgp config-type cisco`` is specified, the community attribute is not sent to the neighbor by default. To send the community attribute user has to specify :clicmd:`neighbor A.B.C.D send-community` like so: .. code-block:: frr ! router bgp 1 neighbor 10.0.0.1 remote-as 1 address-family ipv4 unicast no neighbor 10.0.0.1 send-community exit-address-family ! router bgp 1 neighbor 10.0.0.1 remote-as 1 address-family ipv4 unicast neighbor 10.0.0.1 send-community exit-address-family ! .. deprecated:: 5.0 Please transition to using the FRR specific syntax for your configuration. .. index:: bgp config-type zebra .. clicmd:: bgp config-type zebra FRR style BGP configuration. This is the default. .. _bgp-debugging: Debugging --------- .. index:: show debug .. clicmd:: show debug Show all enabled debugs. .. index:: [no] debug bgp neighbor-events .. clicmd:: [no] debug bgp neighbor-events Enable or disable debugging for neighbor events. This provides general information on BGP events such as peer connection / disconnection, session establishment / teardown, and capability negotiation. .. index:: [no] debug bgp updates .. clicmd:: [no] debug bgp updates Enable or disable debugging for BGP updates. This provides information on BGP UPDATE messages transmitted and received between local and remote instances. .. index:: [no] debug bgp keepalives .. clicmd:: [no] debug bgp keepalives Enable or disable debugging for BGP keepalives. This provides information on BGP KEEPALIVE messages transmitted and received between local and remote instances. .. index:: [no] debug bgp bestpath .. clicmd:: [no] debug bgp bestpath Enable or disable debugging for bestpath selection on the specified prefix. .. index:: [no] debug bgp nht .. clicmd:: [no] debug bgp nht Enable or disable debugging of BGP nexthop tracking. .. index:: [no] debug bgp update-groups .. clicmd:: [no] debug bgp update-groups Enable or disable debugging of dynamic update groups. This provides general information on group creation, deletion, join and prune events. .. index:: [no] debug bgp zebra .. clicmd:: [no] debug bgp zebra Enable or disable debugging of communications between *bgpd* and *zebra*. Dumping Messages and Routing Tables ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ .. index:: dump bgp all PATH [INTERVAL] .. clicmd:: dump bgp all PATH [INTERVAL] .. index:: dump bgp all-et PATH [INTERVAL] .. clicmd:: dump bgp all-et PATH [INTERVAL] .. index:: no dump bgp all [PATH] [INTERVAL] .. clicmd:: no dump bgp all [PATH] [INTERVAL] Dump all BGP packet and events to `path` file. If `interval` is set, a new file will be created for echo `interval` of seconds. The path `path` can be set with date and time formatting (strftime). The type ‘all-et’ enables support for Extended Timestamp Header (:ref:`packet-binary-dump-format`). .. index:: dump bgp updates PATH [INTERVAL] .. clicmd:: dump bgp updates PATH [INTERVAL] .. index:: dump bgp updates-et PATH [INTERVAL] .. clicmd:: dump bgp updates-et PATH [INTERVAL] .. index:: no dump bgp updates [PATH] [INTERVAL] .. clicmd:: no dump bgp updates [PATH] [INTERVAL] Dump only BGP updates messages to `path` file. If `interval` is set, a new file will be created for echo `interval` of seconds. The path `path` can be set with date and time formatting (strftime). The type ‘updates-et’ enables support for Extended Timestamp Header (:ref:`packet-binary-dump-format`). .. index:: dump bgp routes-mrt PATH .. clicmd:: dump bgp routes-mrt PATH .. index:: dump bgp routes-mrt PATH INTERVAL .. clicmd:: dump bgp routes-mrt PATH INTERVAL .. index:: no dump bgp route-mrt [PATH] [INTERVAL] .. clicmd:: no dump bgp route-mrt [PATH] [INTERVAL] Dump whole BGP routing table to `path`. This is heavy process. The path `path` can be set with date and time formatting (strftime). If `interval` is set, a new file will be created for echo `interval` of seconds. Note: the interval variable can also be set using hours and minutes: 04h20m00. .. _bgp-other-commands: Other BGP Commands ------------------ .. index:: clear bgp \* .. clicmd:: clear bgp \* Clear all peers. .. index:: clear bgp ipv4|ipv6 \* .. clicmd:: clear bgp ipv4|ipv6 \* Clear all peers with this address-family activated. .. index:: clear bgp ipv4|ipv6 unicast \* .. clicmd:: clear bgp ipv4|ipv6 unicast \* Clear all peers with this address-family and sub-address-family activated. .. index:: clear bgp ipv4|ipv6 PEER .. clicmd:: clear bgp ipv4|ipv6 PEER Clear peers with address of X.X.X.X and this address-family activated. .. index:: clear bgp ipv4|ipv6 unicast PEER .. clicmd:: clear bgp ipv4|ipv6 unicast PEER Clear peer with address of X.X.X.X and this address-family and sub-address-family activated. .. index:: clear bgp ipv4|ipv6 PEER soft|in|out .. clicmd:: clear bgp ipv4|ipv6 PEER soft|in|out Clear peer using soft reconfiguration in this address-family. .. index:: clear bgp ipv4|ipv6 unicast PEER soft|in|out .. clicmd:: clear bgp ipv4|ipv6 unicast PEER soft|in|out Clear peer using soft reconfiguration in this address-family and sub-address-family. .. _bgp-displaying-bgp-information: Displaying BGP Information ========================== The following four commands display the IPv6 and IPv4 routing tables, depending on whether or not the ``ip`` keyword is used. Actually, :clicmd:`show ip bgp` command was used on older `Quagga` routing daemon project, while :clicmd:`show bgp` command is the new format. The choice has been done to keep old format with IPv4 routing table, while new format displays IPv6 routing table. .. index:: show ip bgp .. clicmd:: show ip bgp .. index:: show ip bgp A.B.C.D .. clicmd:: show ip bgp A.B.C.D .. index:: show bgp .. clicmd:: show bgp .. index:: show bgp X:X::X:X .. clicmd:: show bgp X:X::X:X These commands display BGP routes. When no route is specified, the default is to display all BGP routes. :: BGP table version is 0, local router ID is 10.1.1.1 Status codes: s suppressed, d damped, h history, * valid, > best, i - internal Origin codes: i - IGP, e - EGP, ? - incomplete Network Next Hop Metric LocPrf Weight Path \*> 1.1.1.1/32 0.0.0.0 0 32768 i Total number of prefixes 1 Some other commands provide additional options for filtering the output. .. index:: show [ip] bgp regexp LINE .. clicmd:: show [ip] bgp regexp LINE This command displays BGP routes using AS path regular expression (:ref:`bgp-regular-expressions`). .. index:: show [ip] bgp summary .. clicmd:: show [ip] bgp summary Show a bgp peer summary for the specified address family. The old command structure :clicmd:`show ip bgp` may be removed in the future and should no longer be used. In order to reach the other BGP routing tables other than the IPv6 routing table given by :clicmd:`show bgp`, the new command structure is extended with :clicmd:`show bgp [afi] [safi]`. .. index:: show bgp [afi] [safi] .. clicmd:: show bgp [afi] [safi] .. index:: show bgp .. clicmd:: show bgp These commands display BGP routes for the specific routing table indicated by the selected afi and the selected safi. If no afi and no safi value is given, the command falls back to the default IPv6 routing table .. index:: show bgp [afi] [safi] summary .. clicmd:: show bgp [afi] [safi] summary Show a bgp peer summary for the specified address family, and subsequent address-family. .. index:: show bgp [afi] [safi] summary failed [json] .. clicmd:: show bgp [afi] [safi] summary failed [json] Show a bgp peer summary for peers that are not succesfully exchanging routes for the specified address family, and subsequent address-family. .. index:: show bgp [afi] [safi] neighbor [PEER] .. clicmd:: show bgp [afi] [safi] neighbor [PEER] This command shows information on a specific BGP peer of the relevant afi and safi selected. .. index:: show bgp [afi] [safi] dampening dampened-paths .. clicmd:: show bgp [afi] [safi] dampening dampened-paths Display paths suppressed due to dampening of the selected afi and safi selected. .. index:: show bgp [afi] [safi] dampening flap-statistics .. clicmd:: show bgp [afi] [safi] dampening flap-statistics Display flap statistics of routes of the selected afi and safi selected. .. _bgp-display-routes-by-community: Displaying Routes by Community Attribute ---------------------------------------- The following commands allow displaying routes based on their community attribute. .. index:: show [ip] bgp community .. clicmd:: show [ip] bgp community .. index:: show [ip] bgp community COMMUNITY .. clicmd:: show [ip] bgp community COMMUNITY .. index:: show [ip] bgp community COMMUNITY exact-match .. clicmd:: show [ip] bgp community COMMUNITY exact-match These commands display BGP routes which have the community attribute. attribute. When ``COMMUNITY`` is specified, BGP routes that match that community are displayed. When `exact-match` is specified, it display only routes that have an exact match. .. index:: show [ip] bgp community-list WORD .. clicmd:: show [ip] bgp community-list WORD .. index:: show [ip] bgp community-list WORD exact-match .. clicmd:: show [ip] bgp community-list WORD exact-match These commands display BGP routes for the address family specified that match the specified community list. When `exact-match` is specified, it displays only routes that have an exact match. .. _bgp-display-routes-by-lcommunity: Displaying Routes by Large Community Attribute ---------------------------------------------- The following commands allow displaying routes based on their large community attribute. .. index:: show [ip] bgp large-community .. clicmd:: show [ip] bgp large-community .. index:: show [ip] bgp large-community LARGE-COMMUNITY .. clicmd:: show [ip] bgp large-community LARGE-COMMUNITY .. index:: show [ip] bgp large-community LARGE-COMMUNITY exact-match .. clicmd:: show [ip] bgp large-community LARGE-COMMUNITY exact-match .. index:: show [ip] bgp large-community LARGE-COMMUNITY json .. clicmd:: show [ip] bgp large-community LARGE-COMMUNITY json These commands display BGP routes which have the large community attribute. attribute. When ``LARGE-COMMUNITY`` is specified, BGP routes that match that large community are displayed. When `exact-match` is specified, it display only routes that have an exact match. When `json` is specified, it display routes in json format. .. index:: show [ip] bgp large-community-list WORD .. clicmd:: show [ip] bgp large-community-list WORD .. index:: show [ip] bgp large-community-list WORD exact-match .. clicmd:: show [ip] bgp large-community-list WORD exact-match .. index:: show [ip] bgp large-community-list WORD json .. clicmd:: show [ip] bgp large-community-list WORD json These commands display BGP routes for the address family specified that match the specified large community list. When `exact-match` is specified, it displays only routes that have an exact match. When `json` is specified, it display routes in json format. .. _bgp-display-routes-by-as-path: Displaying Routes by AS Path ---------------------------- .. index:: show bgp ipv4|ipv6 regexp LINE .. clicmd:: show bgp ipv4|ipv6 regexp LINE This commands displays BGP routes that matches a regular expression `line` (:ref:`bgp-regular-expressions`). .. index:: show [ip] bgp ipv4 vpn .. clicmd:: show [ip] bgp ipv4 vpn .. index:: show [ip] bgp ipv6 vpn .. clicmd:: show [ip] bgp ipv6 vpn Print active IPV4 or IPV6 routes advertised via the VPN SAFI. .. index:: show bgp ipv4 vpn summary .. clicmd:: show bgp ipv4 vpn summary .. index:: show bgp ipv6 vpn summary .. clicmd:: show bgp ipv6 vpn summary Print a summary of neighbor connections for the specified AFI/SAFI combination. .. _bgp-route-reflector: Route Reflector =============== BGP routers connected inside the same AS through BGP belong to an internal BGP session, or IBGP. In order to prevent routing table loops, IBGP does not advertise IBGP-learned routes to other routers in the same session. As such, IBGP requires a full mesh of all peers. For large networks, this quickly becomes unscalable. Introducing route reflectors removes the need for the full-mesh. When route reflectors are configured, these will reflect the routes announced by the peers configured as clients. A route reflector client is configured with: .. index:: neighbor PEER route-reflector-client .. clicmd:: neighbor PEER route-reflector-client .. index:: no neighbor PEER route-reflector-client .. clicmd:: no neighbor PEER route-reflector-client To avoid single points of failure, multiple route reflectors can be configured. A cluster is a collection of route reflectors and their clients, and is used by route reflectors to avoid looping. .. index:: bgp cluster-id A.B.C.D .. clicmd:: bgp cluster-id A.B.C.D .. _routing-policy: Routing Policy ============== You can set different routing policy for a peer. For example, you can set different filter for a peer. .. code-block:: frr ! router bgp 1 view 1 neighbor 10.0.0.1 remote-as 2 address-family ipv4 unicast neighbor 10.0.0.1 distribute-list 1 in exit-address-family ! router bgp 1 view 2 neighbor 10.0.0.1 remote-as 2 address-family ipv4 unicast neighbor 10.0.0.1 distribute-list 2 in exit-address-family This means BGP update from a peer 10.0.0.1 goes to both BGP view 1 and view 2. When the update is inserted into view 1, distribute-list 1 is applied. On the other hand, when the update is inserted into view 2, distribute-list 2 is applied. .. _bgp-regular-expressions: BGP Regular Expressions ======================= BGP regular expressions are based on :t:`POSIX 1003.2` regular expressions. The following description is just a quick subset of the POSIX regular expressions. .\* Matches any single character. \* Matches 0 or more occurrences of pattern. \+ Matches 1 or more occurrences of pattern. ? Match 0 or 1 occurrences of pattern. ^ Matches the beginning of the line. $ Matches the end of the line. _ The ``_`` character has special meanings in BGP regular expressions. It matches to space and comma , and AS set delimiter ``{`` and ``}`` and AS confederation delimiter ``(`` and ``)``. And it also matches to the beginning of the line and the end of the line. So ``_`` can be used for AS value boundaries match. This character technically evaluates to ``(^|[,{}()]|$)``. .. _bgp-configuration-examples: Miscellaneous Configuration Examples ==================================== Example of a session to an upstream, advertising only one prefix to it. .. code-block:: frr router bgp 64512 bgp router-id 10.236.87.1 neighbor upstream peer-group neighbor upstream remote-as 64515 neighbor upstream capability dynamic neighbor 10.1.1.1 peer-group upstream neighbor 10.1.1.1 description ACME ISP address-family ipv4 unicast network 10.236.87.0/24 neighbor upstream prefix-list pl-allowed-adv out exit-address-family ! ip prefix-list pl-allowed-adv seq 5 permit 82.195.133.0/25 ip prefix-list pl-allowed-adv seq 10 deny any A more complex example including upstream, peer and customer sessions advertising global prefixes and NO_EXPORT prefixes and providing actions for customer routes based on community values. Extensive use is made of route-maps and the 'call' feature to support selective advertising of prefixes. This example is intended as guidance only, it has NOT been tested and almost certainly contains silly mistakes, if not serious flaws. .. code-block:: frr router bgp 64512 bgp router-id 10.236.87.1 neighbor upstream capability dynamic neighbor cust capability dynamic neighbor peer capability dynamic neighbor 10.1.1.1 remote-as 64515 neighbor 10.1.1.1 peer-group upstream neighbor 10.2.1.1 remote-as 64516 neighbor 10.2.1.1 peer-group upstream neighbor 10.3.1.1 remote-as 64517 neighbor 10.3.1.1 peer-group cust-default neighbor 10.3.1.1 description customer1 neighbor 10.4.1.1 remote-as 64518 neighbor 10.4.1.1 peer-group cust neighbor 10.4.1.1 description customer2 neighbor 10.5.1.1 remote-as 64519 neighbor 10.5.1.1 peer-group peer neighbor 10.5.1.1 description peer AS 1 neighbor 10.6.1.1 remote-as 64520 neighbor 10.6.1.1 peer-group peer neighbor 10.6.1.1 description peer AS 2 address-family ipv4 unicast network 10.123.456.0/24 network 10.123.456.128/25 route-map rm-no-export neighbor upstream route-map rm-upstream-out out neighbor cust route-map rm-cust-in in neighbor cust route-map rm-cust-out out neighbor cust send-community both neighbor peer route-map rm-peer-in in neighbor peer route-map rm-peer-out out neighbor peer send-community both neighbor 10.3.1.1 prefix-list pl-cust1-network in neighbor 10.4.1.1 prefix-list pl-cust2-network in neighbor 10.5.1.1 prefix-list pl-peer1-network in neighbor 10.6.1.1 prefix-list pl-peer2-network in exit-address-family ! ip prefix-list pl-default permit 0.0.0.0/0 ! ip prefix-list pl-upstream-peers permit 10.1.1.1/32 ip prefix-list pl-upstream-peers permit 10.2.1.1/32 ! ip prefix-list pl-cust1-network permit 10.3.1.0/24 ip prefix-list pl-cust1-network permit 10.3.2.0/24 ! ip prefix-list pl-cust2-network permit 10.4.1.0/24 ! ip prefix-list pl-peer1-network permit 10.5.1.0/24 ip prefix-list pl-peer1-network permit 10.5.2.0/24 ip prefix-list pl-peer1-network permit 192.168.0.0/24 ! ip prefix-list pl-peer2-network permit 10.6.1.0/24 ip prefix-list pl-peer2-network permit 10.6.2.0/24 ip prefix-list pl-peer2-network permit 192.168.1.0/24 ip prefix-list pl-peer2-network permit 192.168.2.0/24 ip prefix-list pl-peer2-network permit 172.16.1/24 ! ip as-path access-list asp-own-as permit ^$ ip as-path access-list asp-own-as permit _64512_ ! ! ################################################################# ! Match communities we provide actions for, on routes receives from ! customers. Communities values of :X, with X, have actions: ! ! 100 - blackhole the prefix ! 200 - set no_export ! 300 - advertise only to other customers ! 400 - advertise only to upstreams ! 500 - set no_export when advertising to upstreams ! 2X00 - set local_preference to X00 ! ! blackhole the prefix of the route ip community-list standard cm-blackhole permit 64512:100 ! ! set no-export community before advertising ip community-list standard cm-set-no-export permit 64512:200 ! ! advertise only to other customers ip community-list standard cm-cust-only permit 64512:300 ! ! advertise only to upstreams ip community-list standard cm-upstream-only permit 64512:400 ! ! advertise to upstreams with no-export ip community-list standard cm-upstream-noexport permit 64512:500 ! ! set local-pref to least significant 3 digits of the community ip community-list standard cm-prefmod-100 permit 64512:2100 ip community-list standard cm-prefmod-200 permit 64512:2200 ip community-list standard cm-prefmod-300 permit 64512:2300 ip community-list standard cm-prefmod-400 permit 64512:2400 ip community-list expanded cme-prefmod-range permit 64512:2... ! ! Informational communities ! ! 3000 - learned from upstream ! 3100 - learned from customer ! 3200 - learned from peer ! ip community-list standard cm-learnt-upstream permit 64512:3000 ip community-list standard cm-learnt-cust permit 64512:3100 ip community-list standard cm-learnt-peer permit 64512:3200 ! ! ################################################################### ! Utility route-maps ! ! These utility route-maps generally should not used to permit/deny ! routes, i.e. they do not have meaning as filters, and hence probably ! should be used with 'on-match next'. These all finish with an empty ! permit entry so as not interfere with processing in the caller. ! route-map rm-no-export permit 10 set community additive no-export route-map rm-no-export permit 20 ! route-map rm-blackhole permit 10 description blackhole, up-pref and ensure it cannot escape this AS set ip next-hop 127.0.0.1 set local-preference 10 set community additive no-export route-map rm-blackhole permit 20 ! ! Set local-pref as requested route-map rm-prefmod permit 10 match community cm-prefmod-100 set local-preference 100 route-map rm-prefmod permit 20 match community cm-prefmod-200 set local-preference 200 route-map rm-prefmod permit 30 match community cm-prefmod-300 set local-preference 300 route-map rm-prefmod permit 40 match community cm-prefmod-400 set local-preference 400 route-map rm-prefmod permit 50 ! ! Community actions to take on receipt of route. route-map rm-community-in permit 10 description check for blackholing, no point continuing if it matches. match community cm-blackhole call rm-blackhole route-map rm-community-in permit 20 match community cm-set-no-export call rm-no-export on-match next route-map rm-community-in permit 30 match community cme-prefmod-range call rm-prefmod route-map rm-community-in permit 40 ! ! ##################################################################### ! Community actions to take when advertising a route. ! These are filtering route-maps, ! ! Deny customer routes to upstream with cust-only set. route-map rm-community-filt-to-upstream deny 10 match community cm-learnt-cust match community cm-cust-only route-map rm-community-filt-to-upstream permit 20 ! ! Deny customer routes to other customers with upstream-only set. route-map rm-community-filt-to-cust deny 10 match community cm-learnt-cust match community cm-upstream-only route-map rm-community-filt-to-cust permit 20 ! ! ################################################################### ! The top-level route-maps applied to sessions. Further entries could ! be added obviously.. ! ! Customers route-map rm-cust-in permit 10 call rm-community-in on-match next route-map rm-cust-in permit 20 set community additive 64512:3100 route-map rm-cust-in permit 30 ! route-map rm-cust-out permit 10 call rm-community-filt-to-cust on-match next route-map rm-cust-out permit 20 ! ! Upstream transit ASes route-map rm-upstream-out permit 10 description filter customer prefixes which are marked cust-only call rm-community-filt-to-upstream on-match next route-map rm-upstream-out permit 20 description only customer routes are provided to upstreams/peers match community cm-learnt-cust ! ! Peer ASes ! outbound policy is same as for upstream route-map rm-peer-out permit 10 call rm-upstream-out ! route-map rm-peer-in permit 10 set community additive 64512:3200 Example of how to set up a 6-Bone connection. .. code-block:: frr ! bgpd configuration ! ================== ! ! MP-BGP configuration ! router bgp 7675 bgp router-id 10.0.0.1 neighbor 3ffe:1cfa:0:2:2a0:c9ff:fe9e:f56 remote-as `as-number` ! address-family ipv6 network 3ffe:506::/32 neighbor 3ffe:1cfa:0:2:2a0:c9ff:fe9e:f56 activate neighbor 3ffe:1cfa:0:2:2a0:c9ff:fe9e:f56 route-map set-nexthop out neighbor 3ffe:1cfa:0:2:2c0:4fff:fe68:a231 remote-as `as-number` neighbor 3ffe:1cfa:0:2:2c0:4fff:fe68:a231 route-map set-nexthop out exit-address-family ! ipv6 access-list all permit any ! ! Set output nexthop address. ! route-map set-nexthop permit 10 match ipv6 address all set ipv6 nexthop global 3ffe:1cfa:0:2:2c0:4fff:fe68:a225 set ipv6 nexthop local fe80::2c0:4fff:fe68:a225 ! log file bgpd.log ! .. include:: routeserver.rst .. include:: rpki.rst .. include:: flowspec.rst .. [#med-transitivity-rant] For some set of objects to have an order, there *must* be some binary ordering relation that is defined for *every* combination of those objects, and that relation *must* be transitive. I.e.:, if the relation operator is <, and if a < b and b < c then that relation must carry over and it *must* be that a < c for the objects to have an order. The ordering relation may allow for equality, i.e. a < b and b < a may both be true and imply that a and b are equal in the order and not distinguished by it, in which case the set has a partial order. Otherwise, if there is an order, all the objects have a distinct place in the order and the set has a total order) .. [bgp-route-osci-cond] McPherson, D. and Gill, V. and Walton, D., "Border Gateway Protocol (BGP) Persistent Route Oscillation Condition", IETF RFC3345 .. [stable-flexible-ibgp] Flavel, A. and M. Roughan, "Stable and flexible iBGP", ACM SIGCOMM 2009 .. [ibgp-correctness] Griffin, T. and G. Wilfong, "On the correctness of IBGP configuration", ACM SIGCOMM 2002 frr-7.2.1/doc/user/bmp.rst0000644000000000000000000001505613610377563012256 00000000000000.. _bmp: *** BMP *** :abbr:`BMP` (BGP Monitoring Protocol, :rfc:`7854`) is used to send monitoring data from BGP routers to network management entities. Implementation characteristics ============================== The `BMP` implementation in FRR has the following properties: - only the :rfc:`7854` features are currently implemented. This means protocol version 3 without any extensions. It is not possible to use an older draft protocol version of BMP. - the following statistics codes are implemented: - 0: count of prefixes rejected - 2: count of duplicate prefix withdrawals - 3: count of **prefixes** with loop in cluster id - 4: count of **prefixes** with loop in AS-path - 5: count of **prefixes** with loop in originator - 11: count of updates subjected to :rfc:`7607` "treat as withdrawal" handling due to errors - 65531: *experimental* count of prefixes rejected due to invalid next-hop Note that stat items 3, 4 and 5 are specified to count updates, but FRR implements them as prefix-based counters. - **route mirroring** is fully implemented, however BGP OPEN messages are not currently included in route mirroring messages. Their contents can be extracted from the "peer up" notification for sessions that established successfully. OPEN messages for failed sessions cannot currently be mirrored. - **route monitoring** is available for IPv4 and IPv6 AFIs, unicast and multicast SAFIs. Other SAFIs (VPN, Labeled-Unicast, Flowspec, etc.) are not currently supported. - monitoring peers that have BGP **add-path** enabled on the session will result in somewhat unpredictable behaviour. Currently, the outcome is: - route mirroring functions as intended, messages are copied verbatim - the add-path ID is never included in route monitoring messages - if multiple paths were received from a peer, an unpredictable path is picked and sent on the BMP session. The selection will differ for pre-policy and post-policy monitoring sessions. - as long as any path is present, something will be advertised on BMP sessions. Only after the last path is gone a withdrawal will be sent on BMP sessions. - updates to additional paths will trigger BMP route monitoring messages. There is no guarantee on consistency regarding which path is sent in these messages. - monitoring peers with :rfc:`5549` extended next-hops has not been tested. Starting BMP ============ BMP is implemented as a loadable module. This means that to use BMP, ``bgpd`` must be started with the ``-M bmp`` option. It is not possible to enable BMP if ``bgpd`` was started without this option. Configuring BMP =============== All of FRR's BMP configuration options are located inside the :clicmd:`router bgp ASN` block. Configure BGP first before proceeding to BMP setup. There is one option that applies to the BGP instance as a whole: .. index:: bmp mirror buffer-limit(0-4294967294) .. clicmd:: [no] bmp mirror buffer-limit(0-4294967294) This sets the maximum amount of memory used for buffering BGP messages (updates, keepalives, ...) for sending in BMP Route Mirroring. The buffer is for the entire BGP instance; if multiple BMP targets are configured they reference the same buffer and do not consume additional memory. Queue overhead is included in accounting this memory, so the actual space available for BGP messages is slightly less than the value configured here. If the buffer fills up, the oldest messages are removed from the buffer and any BMP sessions where the now-removed messages were still pending have their **entire** queue flushed and a "Mirroring Messages Lost" BMP message is sent. BMP Route Monitoring is not affected by this option. All other configuration is managed per targets: .. index:: bmp targets NAME .. clicmd:: [no] bmp targets NAME Create/delete a targets group. As implied by the plural name, targets may cover multiple outbound active BMP sessions as well as inbound passive listeners. If BMP sessions have the same configuration, putting them in the same ``bmp targets`` will reduce overhead. BMP session configuration ------------------------- Inside a ``bmp targets`` block, the following commands control session establishment: .. index:: bmp connect HOSTNAME port (1-65535) {min-retry MSEC|max-retry MSEC} .. clicmd:: [no] bmp connect HOSTNAME port (1-65535) {min-retry MSEC|max-retry MSEC} Add/remove an active outbound BMP session. HOSTNAME is resolved via DNS, if multiple addresses are returned they are tried in nondeterministic order. Only one connection will be established even if multiple addresses are returned. ``min-retry`` and ``max-retry`` specify (in milliseconds) bounds for exponential backoff. .. warning:: ``ip access-list`` and ``ipv6 access-list`` are checked for outbound connections resulting from ``bmp connect`` statements. .. index:: bmp listener port (1-65535) .. clicmd:: [no] bmp listener port (1-65535) Accept incoming BMP sessions on the specified address and port. You can use ``0.0.0.0`` and ``::`` to listen on all IPv4/IPv6 addresses. .. clicmd:: [no] ip access-list NAME .. clicmd:: [no] ipv6 access-list NAME Restrict BMP sessions to the addresses allowed by the respective access lists. The access lists are checked for both passive and active BMP sessions. Changes do not affect currently established sessions. BMP data feed configuration --------------------------- The following commands configure what BMP messages are sent on sessions associated with a particular ``bmp targets``: .. index:: bmp stats [interval (100-86400000)] .. clicmd:: [no] bmp stats [interval (100-86400000)] Send BMP Statistics (counter) messages at the specified interval (in milliseconds.) .. index:: bmp monitor AFI SAFI .. clicmd:: [no] bmp monitor AFI SAFI Perform Route Monitoring for the specified AFI and SAFI. Only IPv4 and IPv6 are currently valid for AFI, and only unicast and multicast are valid for SAFI. Other AFI/SAFI combinations may be added in the future. All BGP neighbors are included in Route Monitoring. Options to select a subset of BGP sessions may be added in the future. .. index:: bmp mirror .. clicmd:: [no] bmp mirror Perform Route Mirroring for all BGP neighbors. Since this provides a direct feed of BGP messages, there are no AFI/SAFI options to be configured. All BGP neighbors are included in Route Mirroring. Options to select a subset of BGP sessions may be added in the future. frr-7.2.1/doc/user/bugs.rst0000644000000000000000000000430413610377563012432 00000000000000.. index:: Bug Reports .. index:: Reporting bugs .. _bug-reports: ************** Reporting Bugs ************** This file describes the procedure for reporting FRRouting bugs. You are asked to follow this format when submitting bug reports. Bugs submitted with woefully incomplete information will receive little attention and are likely to be closed. If you hit a suspected bug in an older version, you may be asked to test with a later version in your environment. Often you may be asked for additional information to help solve the bug. Bugs may be closed after 30 days of non-response to requests to reconfirm or supply additional information. Please report bugs on the project GitHub issue tracker at https://github.com/frrouting/frr/issues Report Format & Requested Information ===================================== When reporting a bug, please provide the following information. #. Your FRR version if it is a release build, or the commit hash if you built from source. #. If you compiled from source, please provide your ``./configure`` line, including all option flags. #. A full list of the FRR daemons you run. #. Your platform name and version, e.g. ``Ubuntu 18.04``. #. Problem description. - Provide as much information as possible. - Copy and paste relevant commands and their output to describe your network setup. - Topology diagrams are helpful when reporting bugs involving more than one box. - Platform routing tables and interface configurations are useful if you are reporting a routing issue. *Please be sure to review the provided information and censor any sensitive material.* #. All FRR configuration files you use. Again, please be sure to censor any sensitive information. For sensitive v4 / v6 addresses, we ask that you censor the inner octets; e.g., ``192.XXX.XXX.32/24``. #. If you are reporting a crash and have a core file, please supply a stack trace using GDB: :: $ gdb exec_file core_file (gdb) bt . #. Run all FRR daemons with full debugging on and send *only* the portion of logs which are relevant to your problem. #. Patches, workarounds, and fixes are always welcome. .. seealso:: :ref:`basic-config-commands` frr-7.2.1/doc/user/conf.py0000644000000000000000000003050513610377563012241 00000000000000# -*- coding: utf-8 -*- # # FRR documentation build configuration file, created by # sphinx-quickstart on Tue Jan 31 16:00:52 2017. # # This file is execfile()d with the current directory set to its # containing dir. # # Note that not all possible configuration values are present in this # autogenerated file. # # All configuration values have a default; values that are commented out # serve to show the default. import sys import os import re import pygments from sphinx.highlighting import lexers # If extensions (or modules to document with autodoc) are in another directory, # add these directories to sys.path here. If the directory is relative to the # documentation root, use os.path.abspath to make it absolute, like shown here. #sys.path.insert(0, os.path.abspath('.')) # -- General configuration ------------------------------------------------ # If your documentation needs a minimal Sphinx version, state it here. needs_sphinx = '1.0' # prolog for various variable substitutions rst_prolog = '' # Add any Sphinx extension module names here, as strings. They can be # extensions coming with Sphinx (named 'sphinx.ext.*') or your custom # ones. extensions = ['sphinx.ext.todo'] # Add any paths that contain templates here, relative to this directory. templates_path = ['_templates'] # The suffix(es) of source filenames. # You can specify multiple suffix as a list of string: # source_suffix = ['.rst'] source_suffix = '.rst' # The encoding of source files. #source_encoding = 'utf-8-sig' # The master toctree document. master_doc = 'index' # General information about the project. project = u'FRR' copyright = u'2017, FRR' author = u'FRR authors' # The version info for the project you're documenting, acts as replacement for # |version| and |release|, also used in various other places throughout the # built documents. # The short X.Y version. version = u'?.?' # The full version, including alpha/beta/rc tags. release = u'?.?-?' # ----------------------------------------------------------------------------- # Extract values from codebase for substitution into docs. # ----------------------------------------------------------------------------- # Various installation prefixes. Values are extracted from config.status. # Reasonable defaults are set in case that file does not exist. replace_vars = { 'AUTHORS': author, 'COPYRIGHT_YEAR': '1999-2005', 'COPYRIGHT_STR': 'Copyright (c) 1999-2005', 'PACKAGE_NAME': project.lower(), 'PACKAGE_TARNAME': project.lower(), 'PACKAGE_STRING': project.lower() + ' latest', 'PACKAGE_URL': 'https://frrouting.org/', 'PACKAGE_VERSION': 'latest', 'INSTALL_PREFIX_ETC': '/etc/frr', 'INSTALL_PREFIX_SBIN': '/usr/lib/frr', 'INSTALL_PREFIX_STATE': '/var/run/frr', 'INSTALL_PREFIX_MODULES': '/usr/lib/frr/modules', 'INSTALL_USER': 'frr', 'INSTALL_GROUP': 'frr', 'INSTALL_VTY_GROUP': 'frrvty', 'GROUP': 'frr', 'USER': 'frr', } # extract version information, installation location, other stuff we need to # use when building final documents val = re.compile('^S\["([^"]+)"\]="(.*)"$') try: with open('../../config.status', 'r') as cfgstatus: for ln in cfgstatus.readlines(): m = val.match(ln) if not m or m.group(1) not in replace_vars.keys(): continue replace_vars[m.group(1)] = m.group(2) except IOError: # if config.status doesn't exist, just ignore it pass # manually fill out some of these we can't get from config.status replace_vars['COPYRIGHT_STR'] = "Copyright (c)" replace_vars['COPYRIGHT_STR'] += ' {0}'.format(replace_vars['COPYRIGHT_YEAR']) replace_vars['COPYRIGHT_STR'] += ' {0}'.format(replace_vars['AUTHORS']) release = replace_vars['PACKAGE_VERSION'] version = release.split('-')[0] # add substitutions to prolog for key, value in replace_vars.items(): rst_prolog += '.. |{0}| replace:: {1}\n'.format(key, value) # The language for content autogenerated by Sphinx. Refer to documentation # for a list of supported languages. # # This is also used if you do content translation via gettext catalogs. # Usually you set "language" from the command line for these cases. language = None # There are two options for replacing |today|: either, you set today to some # non-false value, then it is used: #today = '' # Else, today_fmt is used as the format for a strftime call. #today_fmt = '%B %d, %Y' # List of patterns, relative to source directory, that match files and # directories to ignore when looking for source files. exclude_patterns = ['_build', 'rpki.rst', 'routeserver.rst', 'ospf_fundamentals.rst', 'flowspec.rst', 'snmptrap.rst'] # The reST default role (used for this markup: `text`) to use for all # documents. #default_role = None # If true, '()' will be appended to :func: etc. cross-reference text. #add_function_parentheses = True # If true, the current module name will be prepended to all description # unit titles (such as .. function::). #add_module_names = True # If true, sectionauthor and moduleauthor directives will be shown in the # output. They are ignored by default. #show_authors = False # The name of the Pygments (syntax highlighting) style to use. pygments_style = 'sphinx' # A list of ignored prefixes for module index sorting. #modindex_common_prefix = [] # If true, keep warnings as "system message" paragraphs in the built documents. #keep_warnings = False # If true, `todo` and `todoList` produce output, else they produce nothing. todo_include_todos = True # -- Options for HTML output ---------------------------------------------- # The theme to use for HTML and HTML Help pages. See the documentation for # a list of builtin themes. html_theme = 'default' try: import sphinx_rtd_theme html_theme = 'sphinx_rtd_theme' except ImportError: pass # Theme options are theme-specific and customize the look and feel of a theme # further. For a list of options available for each theme, see the # documentation. #html_theme_options = { # 'sidebarbgcolor': '#374249' #} # Add any paths that contain custom themes here, relative to this directory. #html_theme_path = [] # The name for this set of Sphinx documents. If None, it defaults to # " v documentation". #html_title = None # A shorter title for the navigation bar. Default is the same as html_title. #html_short_title = None # The name of an image file (relative to this directory) to place at the top # of the sidebar. html_logo = '../figures/frr-icon.svg' # The name of an image file (within the static path) to use as favicon of the # docs. This file should be a Windows icon file (.ico) being 16x16 or 32x32 # pixels large. html_favicon = '../figures/frr-logo-icon.png' # Add any paths that contain custom static files (such as style sheets) here, # relative to this directory. They are copied after the builtin static files, # so a file named "default.css" will overwrite the builtin "default.css". html_static_path = ['_static'] # Add any extra paths that contain custom files (such as robots.txt or # .htaccess) here, relative to this directory. These files are copied # directly to the root of the documentation. #html_extra_path = [] # If not '', a 'Last updated on:' timestamp is inserted at every page bottom, # using the given strftime format. #html_last_updated_fmt = '%b %d, %Y' # If true, SmartyPants will be used to convert quotes and dashes to # typographically correct entities. #html_use_smartypants = True # Custom sidebar templates, maps document names to template names. #html_sidebars = {} # Additional templates that should be rendered to pages, maps page names to # template names. #html_additional_pages = {} # If false, no module index is generated. #html_domain_indices = True # If false, no index is generated. #html_use_index = True # If true, the index is split into individual pages for each letter. #html_split_index = False # If true, links to the reST sources are added to the pages. #html_show_sourcelink = True # If true, "Created using Sphinx" is shown in the HTML footer. Default is True. #html_show_sphinx = True # If true, "(C) Copyright ..." is shown in the HTML footer. Default is True. #html_show_copyright = True # If true, an OpenSearch description file will be output, and all pages will # contain a tag referring to it. The value of this option must be the # base URL from which the finished HTML is served. #html_use_opensearch = '' # This is the file name suffix for HTML files (e.g. ".xhtml"). #html_file_suffix = None # Language to be used for generating the HTML full-text search index. # Sphinx supports the following languages: # 'da', 'de', 'en', 'es', 'fi', 'fr', 'hu', 'it', 'ja' # 'nl', 'no', 'pt', 'ro', 'ru', 'sv', 'tr' #html_search_language = 'en' # A dictionary with options for the search language support, empty by default. # Now only 'ja' uses this config value #html_search_options = {'type': 'default'} # The name of a javascript file (relative to the configuration directory) that # implements a search results scorer. If empty, the default will be used. #html_search_scorer = 'scorer.js' # Output file base name for HTML help builder. htmlhelp_basename = 'FRRdoc' # -- Options for LaTeX output --------------------------------------------- latex_elements = { # The paper size ('letterpaper' or 'a4paper'). #'papersize': 'letterpaper', # The font size ('10pt', '11pt' or '12pt'). #'pointsize': '10pt', # Additional stuff for the LaTeX preamble. #'preamble': '', # Latex figure (float) alignment #'figure_align': 'htbp', } # Grouping the document tree into LaTeX files. List of tuples # (source start file, target name, title, # author, documentclass [howto, manual, or own class]). latex_documents = [ (master_doc, 'FRR.tex', u'FRR User Manual', u'FRR', 'manual'), ] # The name of an image file (relative to this directory) to place at the top of # the title page. latex_logo = '../figures/frr-logo-medium.png' # For "manual" documents, if this is true, then toplevel headings are parts, # not chapters. #latex_use_parts = False # If true, show page references after internal links. #latex_show_pagerefs = False # If true, show URL addresses after external links. #latex_show_urls = False # Documents to append as an appendix to all manuals. #latex_appendices = [] # If false, no module index is generated. #latex_domain_indices = True # -- Options for manual page output --------------------------------------- # One entry per manual page. List of tuples # (source start file, name, description, authors, manual section). man_pages = [ (master_doc, 'frr', u'FRR User Manual', [author], 1) ] # If true, show URL addresses after external links. #man_show_urls = False # -- Options for Texinfo output ------------------------------------------- # Grouping the document tree into Texinfo files. List of tuples # (source start file, target name, title, author, # dir menu entry, description, category) texinfo_documents = [ (master_doc, 'frr', u'FRR User Manual', author, 'FRR', 'One line description of project.', 'Miscellaneous'), ] # Documents to append as an appendix to all manuals. #texinfo_appendices = [] # If false, no module index is generated. #texinfo_domain_indices = True # How to display URL addresses: 'footnote', 'no', or 'inline'. #texinfo_show_urls = 'footnote' # If true, do not generate a @detailmenu in the "Top" node's menu. #texinfo_no_detailmenu = False # contents of ../extra/frrlexer.py. # This is read here to support VPATH build. Since this section is execfile()'d # with the file location, we can safely use a relative path here to save the # contents of the lexer file for later use even if our relative path changes # due to VPATH. with open('../extra/frrlexer.py', 'rb') as lex: frrlexerpy = lex.read() # custom extensions here def setup(app): # object type for FRR CLI commands, can be extended to document parent CLI # node later on app.add_object_type('clicmd', 'clicmd') # css overrides for HTML theme app.add_stylesheet('overrides.css') app.add_javascript('overrides.js') # load Pygments lexer for FRR config syntax # # NB: in Pygments 2.2+ this can be done with `load_lexer_from_file`, but we # do it manually since not all of our supported build platforms have 2.2 # yet. # # frrlexer = pygments.lexers.load_lexer_from_file('../extra/frrlexer.py', lexername="FRRLexer") custom_namespace = {} exec(frrlexerpy, custom_namespace) lexers['frr'] = custom_namespace['FRRLexer']() frr-7.2.1/doc/user/eigrpd.rst0000644000000000000000000001772713610377563012761 00000000000000.. _eigrp: ***** EIGRP ***** .. glossary:: DUAL The *Diffusing Update ALgorithm*, a :term:`Bellman-Ford` based routing algorithm used by EIGRP. EIGRP -- Routing Information Protocol is widely deployed interior gateway routing protocol. EIGRP was developed in the 1990's. EIGRP is a :term:`distance-vector` protocol and is based on the :term:`DUAL` algorithms. As a distance-vector protocol, the EIGRP router send updates to its neighbors as networks change, thus allowing the convergence to a known topology. *eigrpd* supports EIGRP as described in RFC7868 .. _starting-and-stopping-eigrpd: Starting and Stopping eigrpd ============================ The default configuration file name of *eigrpd*'s is :file:`eigrpd.conf`. When invocation *eigrpd* searches directory |INSTALL_PREFIX_ETC|. If :file:`eigrpd.conf` is not there next search current directory. If an integrated config is specified configuration is written into :file:`frr.conf`. The EIGRP protocol requires interface information maintained by *zebra* daemon. So running *zebra* is mandatory to run *eigrpd*. Thus minimum sequence for running EIGRP is: :: # zebra -d # eigrpd -d Please note that *zebra* must be invoked before *eigrpd*. To stop *eigrpd*, please use :: kill `cat /var/run/eigrpd.pid` Certain signals have special meanings to *eigrpd*. +------------------+-----------------------------------------------------------+ | Signal | Meaning | +==================+===========================================================+ | SIGHUP & SIGUSR1 | Rotate the log file | +------------------+-----------------------------------------------------------+ | SIGINT & SIGTERM | Sweep all installed EIGRP routes and gracefully terminate | +------------------+-----------------------------------------------------------+ *eigrpd* invocation options. Common options that can be specified (:ref:`common-invocation-options`). .. program:: eigrpd .. _eigrp-configuration: EIGRP Configuration =================== .. index:: router eigrp (1-65535) [vrf NAME] .. clicmd:: router eigrp (1-65535) [vrf NAME] The `router eigrp` command is necessary to enable EIGRP. To disable EIGRP, use the `no router eigrp (1-65535)` command. EIGRP must be enabled before carrying out any of the EIGRP commands. Specify vrf NAME if you want eigrp to work within the specified vrf. .. index:: no router eigrp (1-65535) [vrf NAME] .. clicmd:: no router eigrp (1-65535) [vrf NAME] Disable EIGRP. .. index:: network NETWORK .. clicmd:: network NETWORK .. index:: no network NETWORK .. clicmd:: no network NETWORK Set the EIGRP enable interface by `network`. The interfaces which have addresses matching with `network` are enabled. This group of commands either enables or disables EIGRP interfaces between certain numbers of a specified network address. For example, if the network for 10.0.0.0/24 is EIGRP enabled, this would result in all the addresses from 10.0.0.0 to 10.0.0.255 being enabled for EIGRP. The `no network` command will disable EIGRP for the specified network. Below is very simple EIGRP configuration. Interface `eth0` and interface which address match to `10.0.0.0/8` are EIGRP enabled. .. code-block:: frr ! router eigrp 1 network 10.0.0.0/8 ! .. index:: passive-interface (IFNAME|default) .. clicmd:: passive-interface (IFNAME|default) .. index:: no passive-interface IFNAME .. clicmd:: no passive-interface IFNAME This command sets the specified interface to passive mode. On passive mode interface, all receiving packets are ignored and eigrpd does not send either multicast or unicast EIGRP packets except to EIGRP neighbors specified with `neighbor` command. The interface may be specified as `default` to make eigrpd default to passive on all interfaces. The default is to be passive on all interfaces. .. _how-to-announce-eigrp-route: How to Announce EIGRP route =========================== .. index:: redistribute kernel .. clicmd:: redistribute kernel .. index:: redistribute kernel metric (1-4294967295) (0-4294967295) (0-255) (1-255) (1-65535) .. clicmd:: redistribute kernel metric (1-4294967295) (0-4294967295) (0-255) (1-255) (1-65535) .. index:: no redistribute kernel .. clicmd:: no redistribute kernel `redistribute kernel` redistributes routing information from kernel route entries into the EIGRP tables. `no redistribute kernel` disables the routes. .. index:: redistribute static .. clicmd:: redistribute static .. index:: redistribute static metric (1-4294967295) (0-4294967295) (0-255) (1-255) (1-65535) .. clicmd:: redistribute static metric (1-4294967295) (0-4294967295) (0-255) (1-255) (1-65535) .. index:: no redistribute static .. clicmd:: no redistribute static `redistribute static` redistributes routing information from static route entries into the EIGRP tables. `no redistribute static` disables the routes. .. index:: redistribute connected .. clicmd:: redistribute connected .. index:: redistribute connected metric (1-4294967295) (0-4294967295) (0-255) (1-255) (1-65535) .. clicmd:: redistribute connected metric (1-4294967295) (0-4294967295) (0-255) (1-255) (1-65535) .. index:: no redistribute connected .. clicmd:: no redistribute connected Redistribute connected routes into the EIGRP tables. `no redistribute connected` disables the connected routes in the EIGRP tables. This command redistribute connected of the interface which EIGRP disabled. The connected route on EIGRP enabled interface is announced by default. .. index:: redistribute ospf .. clicmd:: redistribute ospf .. index:: redistribute ospf metric (1-4294967295) (0-4294967295) (0-255) (1-255) (1-65535) .. clicmd:: redistribute ospf metric (1-4294967295) (0-4294967295) (0-255) (1-255) (1-65535) .. index:: no redistribute ospf .. clicmd:: no redistribute ospf `redistribute ospf` redistributes routing information from ospf route entries into the EIGRP tables. `no redistribute ospf` disables the routes. .. index:: redistribute bgp .. clicmd:: redistribute bgp .. index:: redistribute bgp metric (1-4294967295) (0-4294967295) (0-255) (1-255) (1-65535) .. clicmd:: redistribute bgp metric (1-4294967295) (0-4294967295) (0-255) (1-255) (1-65535) .. index:: no redistribute bgp .. clicmd:: no redistribute bgp `redistribute bgp` redistributes routing information from bgp route entries into the EIGRP tables. `no redistribute bgp` disables the routes. .. _show-eigrp-information: Show EIGRP Information ====================== .. index:: show ip eigrp [vrf NAME] topology .. clicmd:: show ip eigrp [vrf NAME] topology Display current EIGRP status. :: eigrpd> **show ip eigrp topology** # show ip eigrp topo EIGRP Topology Table for AS(4)/ID(0.0.0.0) Codes: P - Passive, A - Active, U - Update, Q - Query, R - Reply r - reply Status, s - sia Status P 10.0.2.0/24, 1 successors, FD is 256256, serno: 0 via Connected, enp0s3 .. index:: show ip eigrp [vrf NAME] interface .. clicmd:: show ip eigrp [vrf NAME] interface Display the list of interfaces associated with a particular eigrp instance. ..index:: show ip eigrp [vrf NAME] neighbor ..clicmd:: show ip eigrp [vrf NAME] neighbor Display the list of neighbors that have been established within a particular eigrp instance. EIGRP Debug Commands ==================== Debug for EIGRP protocol. .. index:: debug eigrp packets .. clicmd:: debug eigrp packets Debug eigrp packets ``debug eigrp`` will show EIGRP packets that are sent and received. .. index:: debug eigrp transmit .. clicmd:: debug eigrp transmit Debug eigrp transmit events ``debug eigrp transmit`` will display detailed information about the EIGRP transmit events. .. index:: show debugging eigrp .. clicmd:: show debugging eigrp Display *eigrpd*'s debugging option. ``show debugging eigrp`` will show all information currently set for eigrpd debug. frr-7.2.1/doc/user/fabricd.rst0000644000000000000000000002442113610377563013066 00000000000000.. _fabricd: ********** OpenFabric ********** OpenFabric, specified in :t:`draft-white-openfabric-06.txt`, is a routing protocol derived from IS-IS, providing link-state routing with efficient flooding for topologies like spine-leaf networks. FRR implements OpenFabric in a daemon called *fabricd* .. _configuring-fabricd: Configuring fabricd =================== There are no *fabricd* specific options. Common options can be specified (:ref:`common-invocation-options`) to *fabricd*. *fabricd* needs to acquire interface information from *zebra* in order to function. Therefore *zebra* must be running before invoking *fabricd*. Also, if *zebra* is restarted then *fabricd* must be too. Like other daemons, *fabricd* configuration is done in an OpenFabric specific configuration file :file:`fabricd.conf`. .. _openfabric-router: OpenFabric router ================= To enable the OpenFabric routing protocol, an OpenFabric router needs to be created in the configuration: .. index:: router openfabric WORD .. clicmd:: router openfabric WORD .. index:: no router openfabric WORD .. clicmd:: no router openfabric WORD Enable or disable the OpenFabric process by specifying the OpenFabric domain with 'WORD'. .. index:: net XX.XXXX. ... .XXX.XX .. clicmd:: net XX.XXXX. ... .XXX.XX .. index:: no net XX.XXXX. ... .XXX.XX .. clicmd:: no net XX.XXXX. ... .XXX.XX Set/Unset network entity title (NET) provided in ISO format. .. index:: domain-password [clear | md5] .. clicmd:: domain-password [clear | md5] .. index:: no domain-password .. clicmd:: no domain-password Configure the authentication password for a domain, as clear text or md5 one. .. index:: log-adjacency-changes .. clicmd:: log-adjacency-changes .. index:: no log-adjacency-changes .. clicmd:: no log-adjacency-changes Log changes in adjacency state. .. index:: set-overload-bit .. clicmd:: set-overload-bit .. index:: no set-overload-bit .. clicmd:: no set-overload-bit Set overload bit to avoid any transit traffic. .. index:: purge-originator .. clicmd:: purge-originator .. index:: no purge-originator .. clicmd:: no purge-originator Enable or disable :rfc:`6232` purge originator identification. .. index:: fabric-tier (0-14) .. clicmd:: fabric-tier (0-14) .. index:: no fabric-tier .. clicmd:: no fabric-tier Configure a static tier number to advertise as location in the fabric .. _openfabric-timer: OpenFabric Timer ================ .. index:: lsp-gen-interval (1-120) .. clicmd:: lsp-gen-interval (1-120) .. index:: no lsp-gen-interval .. clicmd:: no lsp-gen-interval Set minimum interval in seconds between regenerating same LSP. .. index:: lsp-refresh-interval (1-65235) .. clicmd:: lsp-refresh-interval (1-65235) .. index:: no lsp-refresh-interval .. clicmd:: no lsp-refresh-interval Set LSP refresh interval in seconds. .. index:: max-lsp-lifetime (360-65535) .. clicmd:: max-lsp-lifetime (360-65535) .. index:: no max-lsp-lifetime .. clicmd:: no max-lsp-lifetime Set LSP maximum LSP lifetime in seconds. .. index:: spf-interval (1-120) .. clicmd:: spf-interval (1-120) .. index:: no spf-interval .. clicmd:: no spf-interval Set minimum interval between consecutive SPF calculations in seconds. .. _openfabric-interface: OpenFabric interface ==================== .. index:: ip router openfabric WORD .. clicmd:: ip router openfabric WORD .. index:: no ip router openfabric WORD .. clicmd:: no ip router openfabric WORD .. _ip-router-openfabric-word: Activate OpenFabric on this interface. Note that the name of OpenFabric instance must be the same as the one used to configure the routing process (see command :clicmd:`router openfabric WORD`). .. index:: openfabric csnp-interval (1-600) .. clicmd:: openfabric csnp-interval (1-600) .. index:: no openfabric csnp-interval .. clicmd:: no openfabric csnp-interval Set CSNP interval in seconds. .. index:: openfabric hello-interval (1-600) .. clicmd:: openfabric hello-interval (1-600) .. index:: no openfabric hello-interval .. clicmd:: no openfabric hello-interval Set Hello interval in seconds. .. index:: openfabric hello-multiplier (2-100) .. clicmd:: openfabric hello-multiplier (2-100) .. index:: no openfabric hello-multiplier .. clicmd:: no openfabric hello-multiplier Set multiplier for Hello holding time. .. index:: openfabric metric (0-16777215) .. clicmd:: openfabric metric (0-16777215) .. index:: no openfabric metric .. clicmd:: no openfabric metric Set interface metric value. .. index:: openfabric passive .. clicmd:: openfabric passive .. index:: no openfabric passive .. clicmd:: no openfabric passive Configure the passive mode for this interface. .. index:: openfabric password [clear | md5] .. clicmd:: openfabric password [clear | md5] .. index:: no openfabric password .. clicmd:: no openfabric password Configure the authentication password (clear or encoded text) for the interface. .. index:: openfabric psnp-interval (1-120) .. clicmd:: openfabric psnp-interval (1-120) .. index:: no openfabric psnp-interval .. clicmd:: no openfabric psnp-interval Set PSNP interval in seconds. .. _showing-openfabric-information: Showing OpenFabric information ============================== .. index:: show openfabric summary .. clicmd:: show openfabric summary Show summary information about OpenFabric. .. index:: show openfabric hostname .. clicmd:: show openfabric hostname Show which hostnames are associated with which OpenFabric system ids. .. index:: show openfabric interface .. clicmd:: show openfabric interface .. index:: show openfabric interface detail .. clicmd:: show openfabric interface detail .. index:: show openfabric interface .. clicmd:: show openfabric interface Show state and configuration of specified OpenFabric interface, or all interfaces if no interface is given with or without details. .. index:: show openfabric neighbor .. clicmd:: show openfabric neighbor .. index:: show openfabric neighbor .. clicmd:: show openfabric neighbor .. index:: show openfabric neighbor detail .. clicmd:: show openfabric neighbor detail Show state and information of specified OpenFabric neighbor, or all neighbors if no system id is given with or without details. .. index:: show openfabric database .. clicmd:: show openfabric database .. index:: show openfabric database [detail] .. clicmd:: show openfabric database [detail] .. index:: show openfabric database [detail] .. clicmd:: show openfabric database [detail] .. index:: show openfabric database detail .. clicmd:: show openfabric database detail Show the OpenFabric database globally, for a specific LSP id without or with details. .. index:: show openfabric topology .. clicmd:: show openfabric topology Show calculated OpenFabric paths and associated topology information. .. _debugging-openfabric: Debugging OpenFabric ==================== .. index:: debug openfabric adj-packets .. clicmd:: debug openfabric adj-packets .. index:: no debug openfabric adj-packets .. clicmd:: no debug openfabric adj-packets OpenFabric Adjacency related packets. .. index:: debug openfabric checksum-errors .. clicmd:: debug openfabric checksum-errors .. index:: no debug openfabric checksum-errors .. clicmd:: no debug openfabric checksum-errors OpenFabric LSP checksum errors. .. index:: debug openfabric events .. clicmd:: debug openfabric events .. index:: no debug openfabric events .. clicmd:: no debug openfabric events OpenFabric Events. .. index:: debug openfabric local-updates .. clicmd:: debug openfabric local-updates .. index:: no debug openfabric local-updates .. clicmd:: no debug openfabric local-updates OpenFabric local update packets. .. index:: debug openfabric lsp-gen .. clicmd:: debug openfabric lsp-gen .. index:: no debug openfabric lsp-gen .. clicmd:: no debug openfabric lsp-gen Generation of own LSPs. .. index:: debug openfabric lsp-sched .. clicmd:: debug openfabric lsp-sched .. index:: no debug openfabric lsp-sched .. clicmd:: no debug openfabric lsp-sched Debug scheduling of generation of own LSPs. .. index:: debug openfabric packet-dump .. clicmd:: debug openfabric packet-dump .. index:: no debug openfabric packet-dump .. clicmd:: no debug openfabric packet-dump OpenFabric packet dump. .. index:: debug openfabric protocol-errors .. clicmd:: debug openfabric protocol-errors .. index:: no debug openfabric protocol-errors .. clicmd:: no debug openfabric protocol-errors OpenFabric LSP protocol errors. .. index:: debug openfabric route-events .. clicmd:: debug openfabric route-events .. index:: no debug openfabric route-events .. clicmd:: no debug openfabric route-events OpenFabric Route related events. .. index:: debug openfabric snp-packets .. clicmd:: debug openfabric snp-packets .. index:: no debug openfabric snp-packets .. clicmd:: no debug openfabric snp-packets OpenFabric CSNP/PSNP packets. .. index:: debug openfabric spf-events .. clicmd:: debug openfabric spf-events .. index:: debug openfabric spf-statistics .. clicmd:: debug openfabric spf-statistics .. index:: debug openfabric spf-triggers .. clicmd:: debug openfabric spf-triggers .. index:: no debug openfabric spf-events .. clicmd:: no debug openfabric spf-events .. index:: no debug openfabric spf-statistics .. clicmd:: no debug openfabric spf-statistics .. index:: no debug openfabric spf-triggers .. clicmd:: no debug openfabric spf-triggers OpenFabric Shortest Path First Events, Timing and Statistic Data and triggering events. .. index:: debug openfabric update-packets .. clicmd:: debug openfabric update-packets .. index:: no debug openfabric update-packets .. clicmd:: no debug openfabric update-packets Update related packets. .. index:: show debugging openfabric .. clicmd:: show debugging openfabric Print which OpenFabric debug levels are active. OpenFabric configuration example ================================ A simple example: .. code-block:: frr ! interface lo ip address 192.0.2.1/32 ip router openfabric 1 ipv6 address 2001:db8::1/128 ipv6 router openfabric 1 ! interface eth0 ip router openfabric 1 ipv6 router openfabric 1 ! interface eth1 ip router openfabric 1 ipv6 router openfabric 1 ! router openfabric 1 net 49.0000.0000.0001.00 frr-7.2.1/doc/user/filter.rst0000644000000000000000000001564713610377563012773 00000000000000********* Filtering ********* FRR provides many very flexible filtering features. Filtering is used for both input and output of the routing information. Once filtering is defined, it can be applied in any direction. IP Access List ============== .. index:: access-list NAME [seq (1-4294967295)] permit IPV4-NETWORK .. clicmd:: access-list NAME [seq (1-4294967295)] permit IPV4-NETWORK .. index:: access-list NAME [seq (1-4294967295)] deny IPV4-NETWORK .. clicmd:: access-list NAME [seq (1-4294967295)] deny IPV4-NETWORK seq seq `number` can be set either automatically or manually. In the case that sequential numbers are set manually, the user may pick any number less than 4294967295. In the case that sequential number are set automatically, the sequential number will increase by a unit of five (5) per list. If a list with no specified sequential number is created after a list with a specified sequential number, the list will automatically pick the next multiple of five (5) as the list number. For example, if a list with number 2 already exists and a new list with no specified number is created, the next list will be numbered 5. If lists 2 and 7 already exist and a new list with no specified number is created, the new list will be numbered 10. Basic filtering is done by `access-list` as shown in the following example. .. code-block:: frr access-list filter deny 10.0.0.0/9 access-list filter permit 10.0.0.0/8 access-list filter seq 13 permit 10.0.0.0/7 IP Prefix List ============== *ip prefix-list* provides the most powerful prefix based filtering mechanism. In addition to *access-list* functionality, *ip prefix-list* has prefix length range specification and sequential number specification. You can add or delete prefix based filters to arbitrary points of prefix-list using sequential number specification. If no ip prefix-list is specified, it acts as permit. If *ip prefix-list* is defined, and no match is found, default deny is applied. .. index:: ip prefix-list NAME (permit|deny) PREFIX [le LEN] [ge LEN] .. clicmd:: ip prefix-list NAME (permit|deny) PREFIX [le LEN] [ge LEN] .. index:: ip prefix-list NAME seq NUMBER (permit|deny) PREFIX [le LEN] [ge LEN] .. clicmd:: ip prefix-list NAME seq NUMBER (permit|deny) PREFIX [le LEN] [ge LEN] You can create *ip prefix-list* using above commands. seq seq `number` can be set either automatically or manually. In the case that sequential numbers are set manually, the user may pick any number less than 4294967295. In the case that sequential number are set automatically, the sequential number will increase by a unit of five (5) per list. If a list with no specified sequential number is created after a list with a specified sequential number, the list will automatically pick the next multiple of five (5) as the list number. For example, if a list with number 2 already exists and a new list with no specified number is created, the next list will be numbered 5. If lists 2 and 7 already exist and a new list with no specified number is created, the new list will be numbered 10. le Specifies prefix length. The prefix list will be applied if the prefix length is less than or equal to the le prefix length. ge Specifies prefix length. The prefix list will be applied if the prefix length is greater than or equal to the ge prefix length. Less than or equal to prefix numbers and greater than or equal to prefix numbers can be used together. The order of the le and ge commands does not matter. If a prefix list with a different sequential number but with the exact same rules as a previous list is created, an error will result. However, in the case that the sequential number and the rules are exactly similar, no error will result. If a list with the same sequential number as a previous list is created, the new list will overwrite the old list. Matching of IP Prefix is performed from the smaller sequential number to the larger. The matching will stop once any rule has been applied. In the case of no le or ge command, the prefix length must match exactly the length specified in the prefix list. .. index:: no ip prefix-list NAME .. clicmd:: no ip prefix-list NAME .. _ip-prefix-list-description: ip prefix-list description -------------------------- .. index:: ip prefix-list NAME description DESC .. clicmd:: ip prefix-list NAME description DESC Descriptions may be added to prefix lists. This command adds a description to the prefix list. .. index:: no ip prefix-list NAME description [DESC] .. clicmd:: no ip prefix-list NAME description [DESC] Deletes the description from a prefix list. It is possible to use the command without the full description. .. _ip-prefix-list-sequential-number-control: ip prefix-list sequential number control ---------------------------------------- .. index:: ip prefix-list sequence-number .. clicmd:: ip prefix-list sequence-number With this command, the IP prefix list sequential number is displayed. This is the default behavior. .. index:: no ip prefix-list sequence-number .. clicmd:: no ip prefix-list sequence-number With this command, the IP prefix list sequential number is not displayed. .. _showing-ip-prefix-list: Showing ip prefix-list ---------------------- .. index:: show ip prefix-list .. clicmd:: show ip prefix-list Display all IP prefix lists. .. index:: show ip prefix-list NAME .. clicmd:: show ip prefix-list NAME Show IP prefix list can be used with a prefix list name. .. index:: show ip prefix-list NAME seq NUM .. clicmd:: show ip prefix-list NAME seq NUM Show IP prefix list can be used with a prefix list name and sequential number. .. index:: show ip prefix-list NAME A.B.C.D/M .. clicmd:: show ip prefix-list NAME A.B.C.D/M If the command longer is used, all prefix lists with prefix lengths equal to or longer than the specified length will be displayed. If the command first match is used, the first prefix length match will be displayed. .. index:: show ip prefix-list NAME A.B.C.D/M longer .. clicmd:: show ip prefix-list NAME A.B.C.D/M longer .. index:: show ip prefix-list NAME A.B.C.D/M first-match .. clicmd:: show ip prefix-list NAME A.B.C.D/M first-match .. index:: show ip prefix-list summary .. clicmd:: show ip prefix-list summary .. index:: show ip prefix-list summary NAME .. clicmd:: show ip prefix-list summary NAME .. index:: show ip prefix-list detail .. clicmd:: show ip prefix-list detail .. index:: show ip prefix-list detail NAME .. clicmd:: show ip prefix-list detail NAME Clear counter of ip prefix-list ------------------------------- .. index:: clear ip prefix-list [NAME [A.B.C.D/M]] .. clicmd:: clear ip prefix-list [NAME [A.B.C.D/M]] Clears the counters of all IP prefix lists. Clear IP Prefix List can be used with a specified NAME or NAME and prefix. frr-7.2.1/doc/user/flowspec.rst0000644000000000000000000003364713610377563013330 00000000000000.. _flowspec: Flowspec ======== .. _features-of-the-current-implementation-flowspec: Overview --------- Flowspec introduces a new :abbr:`NLRI (Network Layer Reachability Information)` encoding format that is used to distribute traffic rule flow specifications. Basically, instead of simply relying on destination IP address for IP prefixes, the IP prefix is replaced by a n-tuple consisting of a rule. That rule can be a more or less complex combination of the following: - Network source/destination (can be one or the other, or both). - Layer 4 information for UDP/TCP: source port, destination port, or any port. - Layer 4 information for ICMP type and ICMP code. - Layer 4 information for TCP Flags. - Layer 3 information: DSCP value, Protocol type, packet length, fragmentation. - Misc layer 4 TCP flags. A combination of the above rules is applied for traffic filtering. This is encoded as part of specific BGP extended communities and the action can range from the obvious rerouting (to nexthop or to separate VRF) to shaping, or discard. The following IETF drafts and RFCs have been used to implement FRR Flowspec: - :rfc:`5575` - [Draft-IETF-IDR-Flowspec-redirect-IP]_ .. _design-principles-flowspec: Design Principles ----------------- FRR implements the Flowspec client side, that is to say that BGP is able to receive Flowspec entries, but is not able to act as manager and send Flowspec entries. Linux provides the following mechanisms to implement policy based routing: - Filtering the traffic with ``Netfilter``. ``Netfilter`` provides a set of tools like ``ipset`` and ``iptables`` that are powerful enough to be able to filter such Flowspec filter rule. - using non standard routing tables via ``iproute2`` (via the ``ip rule`` command provided by ``iproute2``). ``iproute2`` is already used by FRR's :ref:`pbr` daemon which provides basic policy based routing based on IP source and destination criterion. Below example is an illustration of what Flowspec will inject in the underlying system: .. code-block:: shell # linux shell ipset create match0x102 hash:net,net counters ipset add match0x102 32.0.0.0/16,40.0.0.0/16 iptables -N match0x102 -t mangle iptables -A match0x102 -t mangle -j MARK --set-mark 102 iptables -A match0x102 -t mangle -j ACCEPT iptables -i ntfp3 -t mangle -I PREROUTING -m set --match-set match0x102 src,dst -g match0x102 ip rule add fwmark 102 lookup 102 ip route add 40.0.0.0/16 via 44.0.0.2 table 102 For handling an incoming Flowspec entry, the following workflow is applied: - Incoming Flowspec entries are handled by *bgpd*, stored in the BGP RIB. - Flowspec entry is installed according to its complexity. It will be installed if one of the following filtering action is seen on the BGP extended community: either redirect IP, or redirect VRF, in conjunction with rate option, for redirecting traffic. Or rate option set to 0, for discarding traffic. According to the degree of complexity of the Flowspec entry, it will be installed in *zebra* RIB. For more information about what is supported in the FRR implementation as rule, see :ref:`flowspec-known-issues` chapter. Flowspec entry is split in several parts before being sent to *zebra*. - *zebra* daemon receives the policy routing configuration Policy Based Routing entities necessary to policy route the traffic in the underlying system, are received by *zebra*. Two filtering contexts will be created or appended in ``Netfilter``: ``ipset`` and ``iptable`` context. The former is used to define an IP filter based on multiple criterium. For instance, an ipset ``net:net`` is based on two ip addresses, while ``net,port,net`` is based on two ip addresses and one port (for ICMP, UDP, or TCP). The way the filtering is used (for example, is src port or dst port used?) is defined by the latter filtering context. ``iptable`` command will reference the ``ipset`` context and will tell how to filter and what to do. In our case, a marker will be set to indicate ``iproute2`` where to forward the traffic to. Sometimes, for dropping action, there is no need to add a marker; the ``iptable`` will tell to drop all packets matching the ``ipset`` entry. Configuration Guide ------------------- In order to configure an IPv4 Flowspec engine, use the following configuration. As of today, it is only possible to configure Flowspec on the default VRF. .. code-block:: frr router bgp neighbor remote-as address-family ipv4 flowspec neighbor activate exit exit You can see Flowspec entries, by using one of the following show commands: .. index:: show bgp ipv4 flowspec [detail | A.B.C.D] .. clicmd:: show bgp ipv4 flowspec [detail | A.B.C.D] Per-interface configuration ^^^^^^^^^^^^^^^^^^^^^^^^^^^ One nice feature to use is the ability to apply Flowspec to a specific interface, instead of applying it to the whole machine. Despite the following IETF draft [Draft-IETF-IDR-Flowspec-Interface-Set]_ is not implemented, it is possible to manually limit Flowspec application to some incoming interfaces. Actually, not using it can result to some unexpected behaviour like accounting twice the traffic, or slow down the traffic (filtering costs). To limit Flowspec to one specific interface, use the following command, under `flowspec address-family` node. .. index:: [no] local-install .. clicmd:: [no] local-install By default, Flowspec is activated on all interfaces. Installing it to a named interface will result in allowing only this interface. Conversely, enabling any interface will flush all previously configured interfaces. VRF redirection ^^^^^^^^^^^^^^^ Another nice feature to configure is the ability to redirect traffic to a separate VRF. This feature does not go against the ability to configure Flowspec only on default VRF. Actually, when you receive incoming BGP flowspec entries on that default VRF, you can redirect traffic to an other VRF. As a reminder, BGP flowspec entries have a BGP extended community that contains a Route Target. Finding out a local VRF based on Route Target consists in the following: - A configuration of each VRF must be done, with its Route Target set Each VRF is being configured within a BGP VRF instance with its own Route Target list. Route Target accepted format matches the following: ``A.B.C.D:U16``, or ``U16:U32``, ``U32:U16``. - The first VRF with the matching Route Target will be selected to route traffic to. Use the following command under ipv4 unicast address-family node .. index:: [no] rt redirect import RTLIST... .. clicmd:: [no] rt redirect import RTLIST... In order to illustrate, if the Route Target configured in the Flowspec entry is ``E.F.G.H:II``, then a BGP VRF instance with the same Route Target will be set set. That VRF will then be selected. The below full configuration example depicts how Route Targets are configured and how VRFs and cross VRF configuration is done. Note that the VRF are mapped on Linux Network Namespaces. For data traffic to cross VRF boundaries, virtual ethernet interfaces are created with private IP addressing scheme. .. code-block:: frr router bgp neighbor remote-as address-family ipv4 flowspec neighbor A.B.C.D activate exit exit router bgp vrf vrf2 address-family ipv4 unicast rt redirect import exit exit Flowspec monitoring & troubleshooting ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ You can monitor policy-routing objects by using one of the following commands. Those command rely on the filtering contexts configured from BGP, and get the statistics information retrieved from the underlying system. In other words, those statistics are retrieved from ``Netfilter``. .. index:: show pbr ipset IPSETNAME | iptable .. clicmd:: show pbr ipset IPSETNAME | iptable ``IPSETNAME`` is the policy routing object name created by ``ipset``. About rule contexts, it is possible to know which rule has been configured to policy-route some specific traffic. The :clicmd:`show pbr iptable` command displays for forwarded traffic, which table is used. Then it is easy to use that table identifier to dump the routing table that the forwarded traffic will match. .. code-block:: frr .. index:: show ip route table TABLEID .. clicmd:: show ip route table TABLEID ``TABLEID`` is the table number identifier referencing the non standard routing table used in this example. .. index:: [no] debug bgp flowspec .. clicmd:: [no] debug bgp flowspec You can troubleshoot Flowspec, or BGP policy based routing. For instance, if you encounter some issues when decoding a Flowspec entry, you should enable :clicmd:`debug bgp flowspec`. .. index:: [no] debug bgp pbr [error] .. clicmd:: [no] debug bgp pbr [error] If you fail to apply the flowspec entry into *zebra*, there should be some relationship with policy routing mechanism. Here, :clicmd:`debug bgp pbr error` could help. To get information about policy routing contexts created/removed, only use :clicmd:`debug bgp pbr` command. Ensuring that a Flowspec entry has been correctly installed and that incoming traffic is policy-routed correctly can be checked as demonstrated below. First of all, you must check whether the Flowspec entry has been installed or not. .. code-block:: frr CLI# show bgp ipv4 flowspec 5.5.5.2/32 BGP flowspec entry: (flags 0x418) Destination Address 5.5.5.2/32 IP Protocol = 17 Destination Port >= 50 , <= 90 FS:redirect VRF RT:255.255.255.255:255 received for 18:41:37 installed in PBR (match0x271ce00) This means that the Flowspec entry has been installed in an ``iptable`` named ``match0x271ce00``. Once you have confirmation it is installed, you can check whether you find the associate entry by executing following command. You can also check whether incoming traffic has been matched by looking at counter line. .. code-block:: frr CLI# show pbr ipset match0x271ce00 IPset match0x271ce00 type net,port to 5.5.5.0/24:proto 6:80-120 (8) pkts 1000, bytes 1000000 to 5.5.5.2:proto 17:50-90 (5) pkts 1692918, bytes 157441374 As you can see, the entry is present. note that an ``iptable`` entry can be used to host several Flowspec entries. In order to know where the matching traffic is redirected to, you have to look at the policy routing rules. The policy-routing is done by forwarding traffic to a routing table number. That routing table number is reached by using a ``iptable``. The relationship between the routing table number and the incoming traffic is a ``MARKER`` that is set by the IPtable referencing the IPSet. In Flowspec case, ``iptable`` referencing the ``ipset`` context have the same name. So it is easy to know which routing table is used by issuing following command: .. code-block:: frr CLI# show pbr iptable IPtable match0x271ce00 action redirect (5) pkts 1700000, bytes 158000000 table 257, fwmark 257 ... As you can see, by using following Linux commands, the MARKER ``0x101`` is present in both ``iptable`` and ``ip rule`` contexts. .. code-block:: shell # iptables -t mangle --list match0x271ce00 -v Chain match0x271ce00 (1 references) pkts bytes target prot opt in out source destination 1700K 158M MARK all -- any any anywhere anywhere MARK set 0x101 1700K 158M ACCEPT all -- any any anywhere anywhere # ip rule list 0:from all lookup local 0:from all fwmark 0x101 lookup 257 32766:from all lookup main 32767:from all lookup default This allows us to see where the traffic is forwarded to. .. _flowspec-known-issues: Limitations / Known Issues -------------------------- As you can see, Flowspec is rich and can be very complex. As of today, not all Flowspec rules will be able to be converted into Policy Based Routing actions. - The ``Netfilter`` driver is not integrated into FRR yet. Not having this piece of code prevents from injecting flowspec entries into the underlying system. - There are some limitations around filtering contexts If I take example of UDP ports, or TCP ports in Flowspec, the information can be a range of ports, or a unique value. This case is handled. However, complexity can be increased, if the flow is a combination of a list of range of ports and an enumerate of unique values. Here this case is not handled. Similarly, it is not possible to create a filter for both src port and dst port. For instance, filter on src port from [1-1000] and dst port = 80. The same kind of complexity is not possible for packet length, ICMP type, ICMP code. There are some other known issues: - The validation procedure depicted in :rfc:`5575` is not available. This validation procedure has not been implemented, as this feature was not used in the existing setups you shared with us. - The filtering action shaper value, if positive, is not used to apply shaping. If value is positive, the traffic is redirected to the wished destination, without any other action configured by Flowspec. It is recommended to configure Quality of Service if needed, more globally on a per interface basis. - Upon an unexpected crash or other event, *zebra* may not have time to flush PBR contexts. That is to say ``ipset``, ``iptable`` and ``ip rule`` contexts. This is also a consequence due to the fact that ip rule / ipset / iptables are not discovered at startup (not able to read appropriate contexts coming from Flowspec). Appendix -------- More information with a public presentation that explains the design of Flowspec inside FRRouting. [Presentation]_ .. [Draft-IETF-IDR-Flowspec-redirect-IP] .. [Draft-IETF-IDR-Flowspec-Interface-Set] .. [Presentation] frr-7.2.1/doc/user/glossary.rst0000644000000000000000000000410713610377563013336 00000000000000******** Glossary ******** .. glossary:: distance-vector A distance-vector routing protocol in data networks determines the best route for data packets based on distance. Distance-vector routing protocols measure the distance by the number of routers a packet has to pass. Some distance-vector protocols also take into account network latency and other factors that influence traffic on a given route. To determine the best route across a network, routers on which a distance-vector protocol is implemented exchange information with one another, usually routing tables plus hop counts for destination networks and possibly other traffic information. Distance-vector routing protocols also require that a router informs its neighbours of network topology changes periodically. [distance-vector-rp]_ link-state Link-state algorithms (also known as shortest path first algorithms) flood routing information to all nodes in the internetwork. Each router, however, sends only the portion of the routing table that describes the state of its own links. In link-state algorithms, each router builds a picture of the entire network in its routing tables. Distance vector algorithms (also known as Bellman-Ford algorithms) call for each router to send all or some portion of its routing table, but only to its neighbors. In essence, link-state algorithms send small updates everywhere, while distance vector algorithms send larger updates only to neighboring routers. Distance vector algorithms know only about their neighbors. [link-state-rp]_ Bellman-Ford The Bellman–Ford algorithm is an algorithm that computes shortest paths from a single source vertex to all of the other vertices in a weighted digraph. [bellman-ford]_ .. [distance-vector-rp] https://en.wikipedia.org/wiki/Distance-vector_routing_protocol .. [link-state-rp] https://en.wikipedia.org/wiki/Link-state_routing_protocol .. [bellman-ford] https://en.wikipedia.org/wiki/Bellman-Ford_algorithm frr-7.2.1/doc/user/index.rst0000644000000000000000000000262113610377563012601 00000000000000FRRouting User Guide ==================== ############ Introduction ############ .. _introduction: .. toctree:: :maxdepth: 2 overview installation setup ###### Basics ###### .. _basics: .. toctree:: :maxdepth: 2 basic vtysh filter routemap ipv6 kernel snmp .. modules ######### Protocols ######### .. _protocols: .. toctree:: :maxdepth: 2 zebra bfd bgp babeld fabricd ldpd eigrpd isisd nhrpd ospfd ospf6d pim pbr ripd ripngd sharp static vnc vrrp bmp ######## Appendix ######## .. _appendix: .. toctree:: :maxdepth: 2 bugs packet-dumps glossary ################ Copyright notice ################ Copyright (c) 1996-2018 Kunihiro Ishiguro, et al. Permission is granted to make and distribute verbatim copies of this manual provided the copyright notice and this permission notice are preserved on all copies. Permission is granted to copy and distribute modified versions of this manual under the conditions for verbatim copying, provided that the entire resulting derived work is distributed under the terms of a permission notice identical to this one. Permission is granted to copy and distribute translations of this manual into another language, under the above conditions for modified versions, except that this permission notice may be stated in a translation approved by Kunihiro Ishiguro. frr-7.2.1/doc/user/installation.rst0000644000000000000000000004133513610377563014200 00000000000000.. _installation: Installation ============ .. index:: How to install FRR .. index:: Installation .. index:: Installing FRR .. index:: Building the system .. index:: Making FRR This section covers the basics of building, installing and setting up FRR. From Packages ------------- The project publishes packages for Red Hat, Centos, Debian and Ubuntu on the `GitHub releases `_. page. External contributors offer packages for many other platforms including \*BSD, Alpine, Gentoo, Docker, and others. There is currently no documentation on how to use those but we hope to add it soon. From Snapcraft -------------- In addition to traditional packages the project also builds and publishes universal Snap images, available at https://snapcraft.io/frr. From Source ----------- Building FRR from source is the best way to ensure you have the latest features and bug fixes. Details for each supported platform, including dependency package listings, permissions, and other gotchas, are in the developer's documentation. This section provides a brief overview on the process. Getting the Source ^^^^^^^^^^^^^^^^^^ FRR's source is available on the project `GitHub page `_. .. code-block:: shell git clone https://github.com/FRRouting/frr.git When building from Git there are several branches to choose from. The ``master`` branch is the primary development branch. It should be considered unstable. Each release has its own branch named ``stable/X.X``, where ``X.X`` is the release version. In addition, release tarballs are published on the GitHub releases page `here `_. Configuration ^^^^^^^^^^^^^ .. index:: Configuration options .. index:: Options for configuring .. index:: Build options .. index:: Distribution configuration .. index:: Options to `./configure` FRR has an excellent configure script which automatically detects most host configurations. There are several additional configure options to customize the build to include or exclude specific features and dependencies. First, update the build system. Change into your FRR source directory and issue: .. code-block:: shell ./bootstrap.sh This will install any missing build scripts and update the Autotools configuration. Once this is done you can move on to choosing your configuration options from the list below. .. _frr-configuration: .. program:: configure .. option:: --enable-tcmalloc Enable the alternate malloc library. In some cases this is faster and more efficient, in some cases it is not. .. option:: --disable-doc Do not build any documentation, including this one. .. option:: --enable-doc-html From the documentation build html docs as well in addition to the normal output. .. option:: --disable-zebra Do not build zebra daemon. This generally only be useful in a scenario where you are building bgp as a standalone server. .. option:: --disable-ripd Do not build ripd. .. option:: --disable-ripngd Do not build ripngd. .. option:: --disable-ospfd Do not build ospfd. .. option:: --disable-ospf6d Do not build ospf6d. .. option:: --disable-bgpd Do not build bgpd. .. option:: --disable-ldpd Do not build ldpd. .. option:: --disable-nhrpd Do not build nhrpd. .. option:: --disable-eigrpd Do not build eigrpd. .. option:: --disable-babeld Do not build babeld. .. option:: --disable-watchfrr Do not build watchfrr. Watchfrr is used to integrate daemons into startup/shutdown software available on your machine. This is needed for systemd integration, if you disable watchfrr you cannot have any systemd integration. .. option:: --enable-systemd Build watchfrr with systemd integration, this will allow FRR to communicate with systemd to tell systemd if FRR has come up properly. .. option:: --disable-pimd Turn off building of pimd. On some BSD platforms pimd will not build properly due to lack of kernel support. .. option:: --disable-vrrpd Turn off building of vrrpd. Linux is required for vrrpd support; other platforms are not supported. .. option:: --disable-pbrd Turn off building of pbrd. This daemon currently requires linux in order to function properly. .. option:: --enable-sharpd Turn on building of sharpd. This daemon facilitates testing of FRR and can also be used as a quick and easy route generator. .. option:: --disable-staticd Do not build staticd. This daemon is necessary if you want static routes. .. option:: --disable-bfdd Do not build bfdd. .. option:: --disable-bgp-announce Make *bgpd* which does not make bgp announcements at all. This feature is good for using *bgpd* as a BGP announcement listener. .. option:: --disable-bgp-vnc Turn off bgpd's ability to use VNC. .. option:: --enable-datacenter Enable system defaults to work as if in a Data Center. See defaults.h for what is changed by this configure option. .. option:: --enable-snmp Enable SNMP support. By default, SNMP support is disabled. .. option:: --disable-ospfapi Disable support for OSPF-API, an API to interface directly with ospfd. OSPF-API is enabled if --enable-opaque-lsa is set. .. option:: --disable-ospfclient Disable building of the example OSPF-API client. .. option:: --disable-isisd Do not build isisd. .. option:: --disable-fabricd Do not build fabricd. .. option:: --enable-isis-topology Enable IS-IS topology generator. .. option:: --enable-realms Enable the support of Linux Realms. Convert tag values from 1-255 into a realm value when inserting into the Linux kernel. Then routing policy can be assigned to the realm. See the tc man page. .. option:: --disable-rtadv Disable support IPV6 router advertisement in zebra. .. option:: --enable-gcc-rdynamic Pass the ``-rdynamic`` option to the linker driver. This is in most cases necessary for getting usable backtraces. This option defaults to on if the compiler is detected as gcc, but giving an explicit enable/disable is suggested. .. option:: --disable-backtrace Controls backtrace support for the crash handlers. This is autodetected by default. Using the switch will enforce the requested behaviour, failing with an error if support is requested but not available. On BSD systems, this needs libexecinfo, while on glibc support for this is part of libc itself. .. option:: --enable-dev-build Turn on some options for compiling FRR within a development environment in mind. Specifically turn on -g3 -O0 for compiling options and add inclusion of grammar sandbox. .. option:: --enable-fuzzing Turn on some compile options to allow you to run fuzzing tools against the system. This flag is intended as a developer only tool and should not be used for normal operations. .. option:: --disable-snmp Build without SNMP support. .. option:: --disable-vtysh Build without VTYSH. .. option:: --enable-fpm Build with FPM module support. .. option:: --enable-numeric-version Alpine Linux does not allow non-numeric characters in the version string. With this option, we provide a way to strip out these characters for APK dev package builds. .. option:: --enable-multipath=X Compile FRR with up to X way ECMP supported. This number can be from 0-999. For backwards compatibility with older configure options when setting X = 0, we will build FRR with 64 way ECMP. This is needed because there are hardcoded arrays that FRR builds towards, so we need to know how big to make these arrays at build time. Additionally if this parameter is not passed in FRR will default to 16 ECMP. .. option:: --enable-shell-access Turn on the ability of FRR to access some shell options( telnet/ssh/bash/etc. ) from vtysh itself. This option is considered extremely unsecure and should only be considered for usage if you really really know what you are doing. .. option:: --enable-gcov Code coverage reports from gcov require adjustments to the C and LD flags. With this option, gcov instrumentation is added to the build and coverage reports are created during execution. The check-coverage make target is also created to ease report uploading to codecov.io. The upload requires the COMMIT (git hash) and TOKEN (codecov upload token) environment variables be set. .. option:: --enable-config-rollbacks Build with configuration rollback support. Requires SQLite3. .. option:: --enable-confd= Build the ConfD northbound plugin. Look for the libconfd libs and headers in `dir`. .. option:: --enable-sysrepo Build the Sysrepo northbound plugin. You may specify any combination of the above options to the configure script. By default, the executables are placed in :file:`/usr/local/sbin` and the configuration files in :file:`/usr/local/etc`. The :file:`/usr/local/` installation prefix and other directories may be changed using the following options to the configuration script. .. option:: --prefix Install architecture-independent files in `prefix` [/usr/local]. .. option:: --sysconfdir Look for configuration files in `dir` [`prefix`/etc]. Note that sample configuration files will be installed here. .. option:: --localstatedir Configure zebra to use `dir` for local state files, such as pid files and unix sockets. .. option:: --with-yangmodelsdir Look for YANG modules in `dir` [`prefix`/share/yang]. Note that the FRR YANG modules will be installed here. Python dependency, documentation and tests ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ FRR's documentation and basic unit tests heavily use code written in Python. Additionally, FRR ships Python extensions written in C which are used during its build process. To this extent, FRR needs the following: * an installation of CPython, preferably version 3.2 or newer (2.7 works but is end of life and will stop working at some point.) * development files (mostly headers) for that version of CPython * an installation of `sphinx` for that version of CPython, to build the documentation * an installation of `pytest` for that version of CPython, to run the unit tests The `sphinx` and `pytest` dependencies can be avoided by not building documentation / not running ``make check``, but the CPython dependency is a hard dependency of the FRR build process (for the `clippy` tool.) .. _least-privilege-support: Least-Privilege Support """"""""""""""""""""""" .. index:: FRR Least-Privileges .. index:: FRR Privileges Additionally, you may configure zebra to drop its elevated privileges shortly after startup and switch to another user. The configure script will automatically try to configure this support. There are three configure options to control the behaviour of FRR daemons. .. option:: --enable-user Switch to user `user shortly after startup, and run as user `user` in normal operation. .. option:: --enable-group Switch real and effective group to `group` shortly after startup. .. option:: --enable-vty-group Create Unix Vty sockets (for use with vtysh) with group ownership set to `group`. This allows one to create a separate group which is restricted to accessing only the vty sockets, hence allowing one to delegate this group to individual users, or to run vtysh setgid to this group. The default user and group which will be configured is 'frr' if no user or group is specified. Note that this user or group requires write access to the local state directory (see :option:`--localstatedir`) and requires at least read access, and write access if you wish to allow daemons to write out their configuration, to the configuration directory (see :option:`--sysconfdir`). On systems which have the 'libcap' capabilities manipulation library (currently only Linux), FRR will retain only minimal capabilities required and will only raise these capabilities for brief periods. On systems without libcap, FRR will run as the user specified and only raise its UID to 0 for brief periods. Linux Notes """"""""""" .. index:: Building on Linux boxes .. index:: Linux configurations There are several options available only to GNU/Linux systems. If you use GNU/Linux, make sure that the current kernel configuration is what you want. FRR will run with any kernel configuration but some recommendations do exist. :makevar:`CONFIG_NETLINK` Kernel/User Netlink socket. This enables an advanced interface between the Linux kernel and *zebra* (:ref:`kernel-interface`). :makevar:`CONFIG_RTNETLINK` This makes it possible to receive Netlink routing messages. If you specify this option, *zebra* can detect routing information updates directly from the kernel (:ref:`kernel-interface`). :makevar:`CONFIG_IP_MULTICAST` This option enables IP multicast and should be specified when you use *ripd* (:ref:`rip`) or *ospfd* (:ref:`ospfv2`) because these protocols use multicast. Linux sysctl settings and kernel modules ```````````````````````````````````````` There are several kernel parameters that impact overall operation of FRR when using Linux as a router. Generally these parameters should be set in a sysctl related configuration file, e.g., :file:`/etc/sysctl.conf` on Ubuntu based systems and a new file :file:`/etc/sysctl.d/90-routing-sysctl.conf` on Centos based systems. Additional kernel modules are also needed to support MPLS forwarding. :makevar:`IPv4 and IPv6 forwarding` The following are set to enable IP forwarding in the kernel: .. code-block:: shell net.ipv4.conf.all.forwarding=1 net.ipv6.conf.all.forwarding=1 :makevar:`MPLS forwarding` Basic MPLS support was introduced in the kernel in version 4.1 and additional capability was introduced in 4.3 and 4.5. For some general information on Linux MPLS support, see https://www.netdevconf.org/1.1/proceedings/slides/prabhu-mpls-tutorial.pdf. The following modules should be loaded to support MPLS forwarding, and are generally added to a configuration file such as :file:`/etc/modules-load.d/modules.conf`: .. code-block:: shell # Load MPLS Kernel Modules mpls_router mpls_iptunnel The following is an example to enable MPLS forwarding in the kernel, typically by editing :file:`/etc/sysctl.conf`: .. code-block:: shell # Enable MPLS Label processing on all interfaces net.mpls.conf.eth0.input=1 net.mpls.conf.eth1.input=1 net.mpls.conf.eth2.input=1 net.mpls.platform_labels=100000 Make sure to add a line equal to :file:`net.mpls.conf..input` for each interface *''* used with MPLS and to set labels to an appropriate value. :makevar:`VRF forwarding` General information on Linux VRF support can be found in https://www.kernel.org/doc/Documentation/networking/vrf.txt. Kernel support for VRFs was introduced in 4.3 and improved upon through 4.13, which is the version most used in FRR testing (as of June 2018). Additional background on using Linux VRFs and kernel specific features can be found in http://schd.ws/hosted_files/ossna2017/fe/vrf-tutorial-oss.pdf. The following impacts how BGP TCP sockets are managed across VRFs: .. code-block:: shell net.ipv4.tcp_l3mdev_accept=0 With this setting a BGP TCP socket is opened per VRF. This setting ensures that other TCP services, such as SSH, provided for non-VRF purposes are blocked from VRF associated Linux interfaces. .. code-block:: shell net.ipv4.tcp_l3mdev_accept=1 With this setting a single BGP TCP socket is shared across the system. This setting exposes any TCP service running on the system, e.g., SSH, to all VRFs. Generally this setting is not used in environments where VRFs are used to support multiple administrative groups. **Important note** as of June 2018, Kernel versions 4.14-4.18 have a known bug where VRF-specific TCP sockets are not properly handled. When running these kernel versions, if unable to establish any VRF BGP adjacencies, either downgrade to 4.13 or set 'net.ipv4.tcp_l3mdev_accept=1'. The fix for this issue is planned to be included in future kernel versions. So upgrading your kernel may also address this issue. Building ^^^^^^^^ Once you have chosen your configure options, run the configure script and pass the options you chose: .. code-block:: shell ./configure \ --prefix=/usr \ --enable-exampledir=/usr/share/doc/frr/examples/ \ --localstatedir=/var/run/frr \ --sbindir=/usr/lib/frr \ --sysconfdir=/etc/frr \ --enable-pimd \ --enable-watchfrr \ ... After configuring the software, you are ready to build and install it in your system. .. code-block:: shell make && sudo make install If everything finishes successfully, FRR should be installed. You should now skip to the section on :ref:`basic-setup`. frr-7.2.1/doc/user/ipv6.rst0000644000000000000000000002170513610377563012362 00000000000000.. _ipv6-support: ************ IPv6 Support ************ FRR fully supports IPv6 routing. As described so far, FRR supports RIPng, OSPFv3, and BGP-4+. You can give IPv6 addresses to an interface and configure static IPv6 routing information. FRR IPv6 also provides automatic address configuration via a feature called ``address auto configuration``. To do it, the router must send router advertisement messages to the all nodes that exist on the network. Previous versions of FRR could be built without IPv6 support. This is no longer possible. Router Advertisement ==================== .. index:: no ipv6 nd suppress-ra .. clicmd:: no ipv6 nd suppress-ra Send router advertisement messages. .. index:: ipv6 nd suppress-ra .. clicmd:: ipv6 nd suppress-ra Don't send router advertisement messages. .. index:: ipv6 nd prefix ipv6prefix [valid-lifetime] [preferred-lifetime] [off-link] [no-autoconfig] [router-address] .. clicmd:: ipv6 nd prefix ipv6prefix [valid-lifetime] [preferred-lifetime] [off-link] [no-autoconfig] [router-address] Configuring the IPv6 prefix to include in router advertisements. Several prefix specific optional parameters and flags may follow: - ``valid-lifetime``: the length of time in seconds during what the prefix is valid for the purpose of on-link determination. Value ``infinite`` represents infinity (i.e. a value of all one bits (``0xffffffff``)). Range: ``(0-4294967295)`` Default: ``2592000`` - ``preferred-lifetime``: the length of time in seconds during what addresses generated from the prefix remain preferred. Value ``infinite`` represents infinity. Range: ``(0-4294967295)`` Default: ``604800`` - ``off-link``: indicates that advertisement makes no statement about on-link or off-link properties of the prefix. Default: not set, i.e. this prefix can be used for on-link determination. - ``no-autoconfig``: indicates to hosts on the local link that the specified prefix cannot be used for IPv6 autoconfiguration. Default: not set, i.e. prefix can be used for autoconfiguration. - ``router-address``: indicates to hosts on the local link that the specified prefix contains a complete IP address by setting R flag. Default: not set, i.e. hosts do not assume a complete IP address is placed. .. index:: single: no ipv6 nd ra-interval [(1-1800)] single: no ipv6 nd ra-interval [(1-1800)] .. clicmd:: [no] ipv6 nd ra-interval [(1-1800)] The maximum time allowed between sending unsolicited multicast router advertisements from the interface, in seconds. Default: ``600`` .. index:: ipv6 nd ra-interval msec (70-1800000) .. index:: single: no ipv6 nd ra-interval [msec (70-1800000)] single: ipv6 nd ra-interval msec (70-1800000) .. clicmd:: [no] ipv6 nd ra-interval [msec (70-1800000)] The maximum time allowed between sending unsolicited multicast router advertisements from the interface, in milliseconds. Default: ``600000`` .. index:: single: ipv6 nd ra-lifetime (0-9000) single: no ipv6 nd ra-lifetime [(0-9000)] .. clicmd:: [no] ipv6 nd ra-lifetime [(0-9000)] The value to be placed in the Router Lifetime field of router advertisements sent from the interface, in seconds. Indicates the usefulness of the router as a default router on this interface. Setting the value to zero indicates that the router should not be considered a default router on this interface. Must be either zero or between value specified with ``ipv6 nd ra-interval`` (or default) and 9000 seconds. Default: ``1800`` .. index:: single: no ipv6 nd reachable-time [(1-3600000)] single: ipv6 nd reachable-time (1-3600000) .. clicmd:: [no] ipv6 nd reachable-time [(1-3600000)] The value to be placed in the Reachable Time field in the Router Advertisement messages sent by the router, in milliseconds. The configured time enables the router to detect unavailable neighbors. The value zero means unspecified (by this router). Default: ``0`` .. index:: single: ipv6 nd managed-config-flag single: no ipv6 nd managed-config-flag .. clicmd:: [no] ipv6 nd managed-config-flag Set/unset flag in IPv6 router advertisements which indicates to hosts that they should use managed (stateful) protocol for addresses autoconfiguration in addition to any addresses autoconfigured using stateless address autoconfiguration. Default: not set .. index:: single: ipv6 nd other-config-flag single: no ipv6 nd other-config-flag .. clicmd:: [no] ipv6 nd other-config-flag Set/unset flag in IPv6 router advertisements which indicates to hosts that they should use administered (stateful) protocol to obtain autoconfiguration information other than addresses. Default: not set .. index:: single: ipv6 nd home-agent-config-flag single: no ipv6 nd home-agent-config-flag .. clicmd:: [no] ipv6 nd home-agent-config-flag Set/unset flag in IPv6 router advertisements which indicates to hosts that the router acts as a Home Agent and includes a Home Agent Option. Default: not set .. index:: ipv6 nd home-agent-preference (0-65535) .. index:: single: no ipv6 nd home-agent-preference [(0-65535)] single: ipv6 nd home-agent-preference (0-65535) .. clicmd:: [no] ipv6 nd home-agent-preference [(0-65535)] The value to be placed in Home Agent Option, when Home Agent config flag is set, which indicates to hosts Home Agent preference. The default value of 0 stands for the lowest preference possible. Default: ``0`` .. index:: single: ipv6 nd home-agent-lifetime (0-65520) single: no ipv6 nd home-agent-lifetime (0-65520) .. clicmd:: [no] ipv6 nd home-agent-lifetime [(0-65520)] The value to be placed in Home Agent Option, when Home Agent config flag is set, which indicates to hosts Home Agent Lifetime. The default value of 0 means to place the current Router Lifetime value. Default: ``0`` .. index:: single: ipv6 nd adv-interval-option single: no ipv6 nd adv-interval-option .. clicmd:: [no] ipv6 nd adv-interval-option Include an Advertisement Interval option which indicates to hosts the maximum time, in milliseconds, between successive unsolicited Router Advertisements. Default: not set .. index:: single: ipv6 nd router-preference (high|medium|low) single: no ipv6 nd router-preference (high|medium|low) .. clicmd:: [no] ipv6 nd router-preference [(high|medium|low)] Set default router preference in IPv6 router advertisements per RFC4191. Default: medium .. index:: single: ipv6 nd mtu (1-65535) single: no ipv6 nd mtu [(1-65535)] .. clicmd:: [no] ipv6 nd mtu [(1-65535)] Include an MTU (type 5) option in each RA packet to assist the attached hosts in proper interface configuration. The announced value is not verified to be consistent with router interface MTU. Default: don't advertise any MTU option. .. index:: single: ipv6 nd rdnss ipv6address [lifetime] single: no ipv6 nd rdnss ipv6address [lifetime] .. clicmd:: [no] ipv6 nd rdnss ipv6address [lifetime] Recursive DNS server address to advertise using the RDNSS (type 25) option described in RFC8106. Can be specified more than once to advertise multiple addresses. Note that hosts may choose to limit the number of RDNSS addresses to track. Optional parameter: - ``lifetime``: the maximum time in seconds over which the specified address may be used for domain name resolution. Value ``infinite`` represents infinity (i.e. a value of all one bits (``0xffffffff``)). A value of 0 indicates that the address must no longer be used. Range: ``(0-4294967295)`` Default: ``3 * ra-interval`` Default: do not emit RDNSS option .. index:: single: ipv6 nd dnssl domain-name-suffix [lifetime] single: no ipv6 nd dnssl domain-name-suffix [lifetime] .. clicmd:: [no] ipv6 nd dnssl domain-name-suffix [lifetime] Advertise DNS search list using the DNSSL (type 31) option described in RFC8106. Specify more than once to advertise multiple domain name suffixes. Host implementations may limit the number of honored search list entries. Optional parameter: - ``lifetime``: the maximum time in seconds over which the specified domain suffix may be used in the course of name resolution. Value ``infinite`` represents infinity (i.e. a value of all one bits (``0xffffffff``)). A value of 0 indicates that the name suffix must no longer be used. Range: ``(0-4294967295)`` Default: ``3 * ra-interval`` Default: do not emit DNSSL option Router Advertisement Configuration Example ========================================== A small example: .. code-block:: frr interface eth0 no ipv6 nd suppress-ra ipv6 nd prefix 2001:0DB8:5009::/64 .. seealso:: - :rfc:`2462` (IPv6 Stateless Address Autoconfiguration) - :rfc:`4861` (Neighbor Discovery for IP Version 6 (IPv6)) - :rfc:`6275` (Mobility Support in IPv6) - :rfc:`4191` (Default Router Preferences and More-Specific Routes) - :rfc:`8106` (IPv6 Router Advertisement Options for DNS Configuration) frr-7.2.1/doc/user/isisd.rst0000644000000000000000000004126713610377563012616 00000000000000.. _isis: **** ISIS **** :abbr:`ISIS (Intermediate System to Intermediate System)` is a routing protocol which is described in :t:`ISO10589`, :rfc:`1195`, :rfc:`5308`. ISIS is an :abbr:`IGP (Interior Gateway Protocol)`. Compared with :abbr:`RIP`, :abbr:`ISIS` can provide scalable network support and faster convergence times like :abbr:`OSPF`. ISIS is widely used in large networks such as :abbr:`ISP (Internet Service Provider)` and carrier backbone networks. .. _configuring-isisd: Configuring isisd ================= There are no *isisd* specific options. Common options can be specified (:ref:`common-invocation-options`) to *isisd*. *isisd* needs to acquire interface information from *zebra* in order to function. Therefore *zebra* must be running before invoking *isisd*. Also, if *zebra* is restarted then *isisd* must be too. Like other daemons, *isisd* configuration is done in :abbr:`ISIS` specific configuration file :file:`isisd.conf`. .. _isis-router: ISIS router =========== To start the ISIS process you have to specify the ISIS router. As of this writing, *isisd* does not support multiple ISIS processes. .. index:: [no] router isis WORD .. clicmd:: [no] router isis WORD Enable or disable the ISIS process by specifying the ISIS domain with 'WORD'. *isisd* does not yet support multiple ISIS processes but you must specify the name of ISIS process. The ISIS process name 'WORD' is then used for interface (see command :clicmd:`ip router isis WORD`). .. index:: net XX.XXXX. ... .XXX.XX .. clicmd:: net XX.XXXX. ... .XXX.XX .. index:: no net XX.XXXX. ... .XXX.XX .. clicmd:: no net XX.XXXX. ... .XXX.XX Set/Unset network entity title (NET) provided in ISO format. .. index:: hostname dynamic .. clicmd:: hostname dynamic .. index:: no hostname dynamic .. clicmd:: no hostname dynamic Enable support for dynamic hostname. .. index:: area-password [clear | md5] .. clicmd:: area-password [clear | md5] .. index:: domain-password [clear | md5] .. clicmd:: domain-password [clear | md5] .. index:: no area-password .. clicmd:: no area-password .. index:: no domain-password .. clicmd:: no domain-password Configure the authentication password for an area, respectively a domain, as clear text or md5 one. .. index:: log-adjacency-changes .. clicmd:: log-adjacency-changes .. index:: no log-adjacency-changes .. clicmd:: no log-adjacency-changes Log changes in adjacency state. .. index:: metric-style [narrow | transition | wide] .. clicmd:: metric-style [narrow | transition | wide] .. index:: no metric-style .. clicmd:: no metric-style Set old-style (ISO 10589) or new-style packet formats: - narrow Use old style of TLVs with narrow metric - transition Send and accept both styles of TLVs during transition - wide Use new style of TLVs to carry wider metric .. index:: set-overload-bit .. clicmd:: set-overload-bit .. index:: no set-overload-bit .. clicmd:: no set-overload-bit Set overload bit to avoid any transit traffic. .. index:: purge-originator .. clicmd:: purge-originator .. index:: no purge-originator .. clicmd:: no purge-originator Enable or disable :rfc:`6232` purge originator identification. .. _isis-timer: ISIS Timer ========== .. index:: lsp-gen-interval (1-120) .. clicmd:: lsp-gen-interval (1-120) .. index:: lsp-gen-interval [level-1 | level-2] (1-120) .. clicmd:: lsp-gen-interval [level-1 | level-2] (1-120) .. index:: no lsp-gen-interval .. clicmd:: no lsp-gen-interval .. index:: no lsp-gen-interval [level-1 | level-2] .. clicmd:: no lsp-gen-interval [level-1 | level-2] Set minimum interval in seconds between regenerating same LSP, globally, for an area (level-1) or a domain (level-2). .. index:: lsp-refresh-interval [level-1 | level-2] (1-65235) .. clicmd:: lsp-refresh-interval [level-1 | level-2] (1-65235) .. index:: no lsp-refresh-interval [level-1 | level-2] .. clicmd:: no lsp-refresh-interval [level-1 | level-2] Set LSP refresh interval in seconds, globally, for an area (level-1) or a domain (level-2). .. index:: max-lsp-lifetime (360-65535) .. clicmd:: max-lsp-lifetime (360-65535) .. index:: max-lsp-lifetime [level-1 | level-2] (360-65535) .. clicmd:: max-lsp-lifetime [level-1 | level-2] (360-65535) .. index:: no max-lsp-lifetime .. clicmd:: no max-lsp-lifetime .. index:: no max-lsp-lifetime [level-1 | level-2] .. clicmd:: no max-lsp-lifetime [level-1 | level-2] Set LSP maximum LSP lifetime in seconds, globally, for an area (level-1) or a domain (level-2). .. index:: spf-interval (1-120) .. clicmd:: spf-interval (1-120) .. index:: spf-interval [level-1 | level-2] (1-120) .. clicmd:: spf-interval [level-1 | level-2] (1-120) .. index:: no spf-interval .. clicmd:: no spf-interval .. index:: no spf-interval [level-1 | level-2] .. clicmd:: no spf-interval [level-1 | level-2] Set minimum interval between consecutive SPF calculations in seconds. .. _isis-region: ISIS region =========== .. index:: is-type [level-1 | level-1-2 | level-2-only] .. clicmd:: is-type [level-1 | level-1-2 | level-2-only] .. index:: no is-type .. clicmd:: no is-type Define the ISIS router behavior: - level-1 Act as a station router only - level-1-2 Act as both a station router and an area router - level-2-only Act as an area router only .. _isis-interface: ISIS interface ============== .. _ip-router-isis-word: .. index:: [no] router isis WORD .. clicmd:: [no] router isis WORD Activate ISIS adjacency on this interface. Note that the name of ISIS instance must be the same as the one used to configure the ISIS process (see command :clicmd:`router isis WORD`). To enable IPv4, issue ``ip router isis WORD``; to enable IPv6, issue ``ipv6 router isis WORD``. .. index:: isis circuit-type [level-1 | level-1-2 | level-2] .. clicmd:: isis circuit-type [level-1 | level-1-2 | level-2] .. index:: no isis circuit-type .. clicmd:: no isis circuit-type Configure circuit type for interface: - level-1 Level-1 only adjacencies are formed - level-1-2 Level-1-2 adjacencies are formed - level-2-only Level-2 only adjacencies are formed .. index:: isis csnp-interval (1-600) .. clicmd:: isis csnp-interval (1-600) .. index:: isis csnp-interval (1-600) [level-1 | level-2] .. clicmd:: isis csnp-interval (1-600) [level-1 | level-2] .. index:: no isis csnp-interval .. clicmd:: no isis csnp-interval .. index:: no isis csnp-interval [level-1 | level-2] .. clicmd:: no isis csnp-interval [level-1 | level-2] Set CSNP interval in seconds globally, for an area (level-1) or a domain (level-2). .. index:: isis hello padding .. clicmd:: isis hello padding Add padding to IS-IS hello packets. .. index:: isis hello-interval (1-600) .. clicmd:: isis hello-interval (1-600) .. index:: isis hello-interval (1-600) [level-1 | level-2] .. clicmd:: isis hello-interval (1-600) [level-1 | level-2] .. index:: no isis hello-interval .. clicmd:: no isis hello-interval .. index:: no isis hello-interval [level-1 | level-2] .. clicmd:: no isis hello-interval [level-1 | level-2] Set Hello interval in seconds globally, for an area (level-1) or a domain (level-2). .. index:: isis hello-multiplier (2-100) .. clicmd:: isis hello-multiplier (2-100) .. index:: isis hello-multiplier (2-100) [level-1 | level-2] .. clicmd:: isis hello-multiplier (2-100) [level-1 | level-2] .. index:: no isis hello-multiplier .. clicmd:: no isis hello-multiplier .. index:: no isis hello-multiplier [level-1 | level-2] .. clicmd:: no isis hello-multiplier [level-1 | level-2] Set multiplier for Hello holding time globally, for an area (level-1) or a domain (level-2). .. index:: isis metric [(0-255) | (0-16777215)] .. clicmd:: isis metric [(0-255) | (0-16777215)] .. index:: isis metric [(0-255) | (0-16777215)] [level-1 | level-2] .. clicmd:: isis metric [(0-255) | (0-16777215)] [level-1 | level-2] .. index:: no isis metric .. clicmd:: no isis metric .. index:: no isis metric [level-1 | level-2] .. clicmd:: no isis metric [level-1 | level-2] Set default metric value globally, for an area (level-1) or a domain (level-2). Max value depend if metric support narrow or wide value (see command :clicmd:`metric-style [narrow | transition | wide]`). .. index:: isis network point-to-point .. clicmd:: isis network point-to-point .. index:: no isis network point-to-point .. clicmd:: no isis network point-to-point Set network type to 'Point-to-Point' (broadcast by default). .. index:: isis passive .. clicmd:: isis passive .. index:: no isis passive .. clicmd:: no isis passive Configure the passive mode for this interface. .. index:: isis password [clear | md5] .. clicmd:: isis password [clear | md5] .. index:: no isis password .. clicmd:: no isis password Configure the authentication password (clear or encoded text) for the interface. .. index:: isis priority (0-127) .. clicmd:: isis priority (0-127) .. index:: isis priority (0-127) [level-1 | level-2] .. clicmd:: isis priority (0-127) [level-1 | level-2] .. index:: no isis priority .. clicmd:: no isis priority .. index:: no isis priority [level-1 | level-2] .. clicmd:: no isis priority [level-1 | level-2] Set priority for Designated Router election, globally, for the area (level-1) or the domain (level-2). .. index:: isis psnp-interval (1-120) .. clicmd:: isis psnp-interval (1-120) .. index:: isis psnp-interval (1-120) [level-1 | level-2] .. clicmd:: isis psnp-interval (1-120) [level-1 | level-2] .. index:: no isis psnp-interval .. clicmd:: no isis psnp-interval .. index:: no isis psnp-interval [level-1 | level-2] .. clicmd:: no isis psnp-interval [level-1 | level-2] Set PSNP interval in seconds globally, for an area (level-1) or a domain (level-2). .. index:: isis three-way-handshake .. clicmd:: isis three-way-handshake .. index:: no isis three-way-handshake .. clicmd:: no isis three-way-handshake Enable or disable :rfc:`5303` Three-Way Handshake for P2P adjacencies. Three-Way Handshake is enabled by default. .. _showing-isis-information: Showing ISIS information ======================== .. index:: show isis summary .. clicmd:: show isis summary Show summary information about ISIS. .. index:: show isis hostname .. clicmd:: show isis hostname Show information about ISIS node. .. index:: show isis interface .. clicmd:: show isis interface .. index:: show isis interface detail .. clicmd:: show isis interface detail .. index:: show isis interface .. clicmd:: show isis interface Show state and configuration of ISIS specified interface, or all interfaces if no interface is given with or without details. .. index:: show isis neighbor .. clicmd:: show isis neighbor .. index:: show isis neighbor .. clicmd:: show isis neighbor .. index:: show isis neighbor detail .. clicmd:: show isis neighbor detail Show state and information of ISIS specified neighbor, or all neighbors if no system id is given with or without details. .. index:: show isis database .. clicmd:: show isis database .. index:: show isis database [detail] .. clicmd:: show isis database [detail] .. index:: show isis database [detail] .. clicmd:: show isis database [detail] .. index:: show isis database detail .. clicmd:: show isis database detail Show the ISIS database globally, for a specific LSP id without or with details. .. index:: show isis topology .. clicmd:: show isis topology .. index:: show isis topology [level-1|level-2] .. clicmd:: show isis topology [level-1|level-2] Show topology IS-IS paths to Intermediate Systems, globally, in area (level-1) or domain (level-2). .. index:: show ip route isis .. clicmd:: show ip route isis Show the ISIS routing table, as determined by the most recent SPF calculation. .. _isis-traffic-engineering: Traffic Engineering =================== .. note:: At this time, FRR offers partial support for some of the routing protocol extensions that can be used with MPLS-TE. FRR does not support a complete RSVP-TE solution currently. .. index:: mpls-te on .. clicmd:: mpls-te on .. index:: no mpls-te .. clicmd:: no mpls-te Enable Traffic Engineering LSP flooding. .. index:: mpls-te router-address .. clicmd:: mpls-te router-address .. index:: no mpls-te router-address .. clicmd:: no mpls-te router-address Configure stable IP address for MPLS-TE. .. index:: show isis mpls-te interface .. clicmd:: show isis mpls-te interface .. index:: show isis mpls-te interface INTERFACE .. clicmd:: show isis mpls-te interface INTERFACE Show MPLS Traffic Engineering parameters for all or specified interface. .. index:: show isis mpls-te router .. clicmd:: show isis mpls-te router Show Traffic Engineering router parameters. .. seealso:: :ref:`ospf-traffic-engineering` .. _debugging-isis: Debugging ISIS ============== .. index:: debug isis adj-packets .. clicmd:: debug isis adj-packets .. index:: no debug isis adj-packets .. clicmd:: no debug isis adj-packets IS-IS Adjacency related packets. .. index:: debug isis checksum-errors .. clicmd:: debug isis checksum-errors .. index:: no debug isis checksum-errors .. clicmd:: no debug isis checksum-errors IS-IS LSP checksum errors. .. index:: debug isis events .. clicmd:: debug isis events .. index:: no debug isis events .. clicmd:: no debug isis events IS-IS Events. .. index:: debug isis local-updates .. clicmd:: debug isis local-updates .. index:: no debug isis local-updates .. clicmd:: no debug isis local-updates IS-IS local update packets. .. index:: debug isis packet-dump .. clicmd:: debug isis packet-dump .. index:: no debug isis packet-dump .. clicmd:: no debug isis packet-dump IS-IS packet dump. .. index:: debug isis protocol-errors .. clicmd:: debug isis protocol-errors .. index:: no debug isis protocol-errors .. clicmd:: no debug isis protocol-errors IS-IS LSP protocol errors. .. index:: debug isis route-events .. clicmd:: debug isis route-events .. index:: no debug isis route-events .. clicmd:: no debug isis route-events IS-IS Route related events. .. index:: debug isis snp-packets .. clicmd:: debug isis snp-packets .. index:: no debug isis snp-packets .. clicmd:: no debug isis snp-packets IS-IS CSNP/PSNP packets. .. index:: debug isis spf-events .. clicmd:: debug isis spf-events .. index:: debug isis spf-statistics .. clicmd:: debug isis spf-statistics .. index:: debug isis spf-triggers .. clicmd:: debug isis spf-triggers .. index:: no debug isis spf-events .. clicmd:: no debug isis spf-events .. index:: no debug isis spf-statistics .. clicmd:: no debug isis spf-statistics .. index:: no debug isis spf-triggers .. clicmd:: no debug isis spf-triggers IS-IS Shortest Path First Events, Timing and Statistic Data and triggering events. .. index:: debug isis update-packets .. clicmd:: debug isis update-packets .. index:: no debug isis update-packets .. clicmd:: no debug isis update-packets Update related packets. .. index:: show debugging isis .. clicmd:: show debugging isis Print which ISIS debug level is activate. ISIS Configuration Examples =========================== A simple example, with MD5 authentication enabled: .. code-block:: frr ! interface eth0 ip router isis FOO isis network point-to-point isis circuit-type level-2-only ! router isis FOO net 47.0023.0000.0000.0000.0000.0000.0000.1900.0004.00 metric-style wide is-type level-2-only A Traffic Engineering configuration, with Inter-ASv2 support. First, the :file:`zebra.conf` part: .. code-block:: frr hostname HOSTNAME password PASSWORD log file /var/log/zebra.log ! interface eth0 ip address 10.2.2.2/24 link-params max-bw 1.25e+07 max-rsv-bw 1.25e+06 unrsv-bw 0 1.25e+06 unrsv-bw 1 1.25e+06 unrsv-bw 2 1.25e+06 unrsv-bw 3 1.25e+06 unrsv-bw 4 1.25e+06 unrsv-bw 5 1.25e+06 unrsv-bw 6 1.25e+06 unrsv-bw 7 1.25e+06 admin-grp 0xab ! interface eth1 ip address 10.1.1.1/24 link-params enable metric 100 max-bw 1.25e+07 max-rsv-bw 1.25e+06 unrsv-bw 0 1.25e+06 unrsv-bw 1 1.25e+06 unrsv-bw 2 1.25e+06 unrsv-bw 3 1.25e+06 unrsv-bw 4 1.25e+06 unrsv-bw 5 1.25e+06 unrsv-bw 6 1.25e+06 unrsv-bw 7 1.25e+06 neighbor 10.1.1.2 as 65000 Then the :file:`isisd.conf` itself: .. code-block:: frr hostname HOSTNAME password PASSWORD log file /var/log/isisd.log ! ! interface eth0 ip router isis FOO ! interface eth1 ip router isis FOO ! ! router isis FOO isis net 47.0023.0000.0000.0000.0000.0000.0000.1900.0004.00 mpls-te on mpls-te router-address 10.1.1.1 ! line vty frr-7.2.1/doc/user/kernel.rst0000644000000000000000000000355213610377563012756 00000000000000.. _kernel-interface: **************** Kernel Interface **************** There are several different methods for reading kernel routing table information, updating kernel routing tables, and for looking up interfaces. - ioctl This method is a very traditional way for reading or writing kernel information. `ioctl` can be used for looking up interfaces and for modifying interface addresses, flags, mtu settings and other types of information. Also, `ioctl` can insert and delete kernel routing table entries. It will soon be available on almost any platform which zebra supports, but it is a little bit ugly thus far, so if a better method is supported by the kernel, zebra will use that. - sysctl This is a program that can lookup kernel information using MIB (Management Information Base) syntax. Normally, it only provides a way of getting information from the kernel. So one would usually want to change kernel information using another method such as `ioctl`. - proc filesystem This is a special filesystem mount that provides an easy way of getting kernel information. - routing socket / Netlink On recent Linux kernels (2.0.x and 2.2.x), there is a kernel/user communication support called `Netlink`. It makes asynchronous communication between kernel and FRR possible, similar to a routing socket on BSD systems. Before you use this feature, be sure to select (in kernel configuration) the kernel/Netlink support option 'Kernel/User network link driver' and 'Routing messages'. Today, the :file:`/dev/route` special device file is obsolete. Netlink communication is done by reading/writing over Netlink socket. After the kernel configuration, please reconfigure and rebuild FRR. You can use Netlink as a dynamic routing update channel between FRR and the kernel. frr-7.2.1/doc/user/ldpd.rst0000644000000000000000000002735513610377563012430 00000000000000.. _ldp: *** LDP *** The *ldpd* daemon is a standardised protocol that permits exchanging MPLS label information between MPLS devices. The LDP protocol creates peering between devices, so as to exchange that label information. This information is stored in MPLS table of *zebra*, and it injects that MPLS information in the underlying system (Linux kernel or OpenBSD system for instance). *ldpd* provides necessary options to create a Layer 2 VPN across MPLS network. For instance, it is possible to interconnect several sites that share the same broadcast domain. FRR implements LDP as described in :rfc:`5036`; other LDP standard are the following ones: :rfc:`6720`, :rfc:`6667`, :rfc:`5919`, :rfc:`5561`, :rfc:`7552`, :rfc:`4447`. Because MPLS is already available, FRR also supports :rfc:`3031`. Running Ldpd ============ The *ldpd* daemon can be invoked with any of the common options (:ref:`common-invocation-options`). ..option:: --ctl_socket This option allows you to override the path to the ldpd.sock file used to control this daemon. If specified this option overrides the -N option path addition. The *zebra* daemon must be running before *ldpd* is invoked. Configuration of *ldpd* is done in its configuration file :file:`ldpd.conf`. .. _understanding-ldp: Understanding LDP principles ============================ Let's first introduce some definitions that permit understand better the LDP protocol: - `LSR` : Labeled Switch Router. Networking devices handling labels used to forward traffic between and through them. - `LER` : Labeled Edge Router. A Labeled edge router is located at the edge of an MPLS network, generally between an IP network and an MPLS network. ``LDP`` aims at sharing label information across devices. It tries to establish peering with remote LDP capable devices, first by discovering using UDP port 646 , then by peering using TCP port 646. Once the TCP session is established, the label information is shared, through label advertisements. There are different methods to send label advertisement modes. The implementation actually supports the following : Liberal Label Retention + Downstream Unsolicited + Independent Control. The other advertising modes are depicted below, and compared with the current implementation. - Liberal label retention versus conservative mode In liberal mode, every label sent by every LSR is stored in the MPLS table. In conservative mode, only the label that was sent by the best next hop (determined by the IGP metric) for that particular FEC is stored in the MPLS table. - Independent LSP Control versus ordered LSP Control MPLS has two ways of binding labels to FEC’s; either through ordered LSP control, or independent LSP control. Ordered LSP control only binds a label to a FEC if it is the egress LSR, or the router received a label binding for a FEC from the next hop router. In this mode, an MPLS router will create a label binding for each FEC and distribute it to its neighbors so long as he has a entry in the RIB for the destination. In the other mode, label bindings are made without any dependencies on another router advertising a label for a particular FEC. Each router makes it own independent decision to create a label for each FEC. By default IOS uses Independent LSP Control, while Juniper implements the Ordered Control. Both modes are interoperable, the difference is that Ordered Control prevent blackholing during the LDP convergence process, at cost of slowing down the convergence itself - unsolicited downstream versus downstream on demand Downstream on demand label distribution is where an LSR must explicitly request that a label be sent from its downstream router for a particular FEC. Unsolicited label distribution is where a label is sent from the downstream router without the original router requesting it. .. _configuring-ldpd: .. _ldp-configuration: LDP Configuration =================== .. index:: [no] mpls ldp .. clicmd:: [no] mpls ldp Enable or disable LDP daemon .. index:: [no] router-id A.B.C.D .. clicmd:: [no] router-id A.B.C.D The following command located under MPLS router node configures the MPLS router-id of the local device. .. index:: [no] address-family [ipv4 | ipv6] .. clicmd:: [no] address-family [ipv4 | ipv6] Configure LDP for IPv4 or IPv6 address-family. Located under MPLS route node, this subnode permits configuring the LDP neighbors. .. index:: [no] interface IFACE .. clicmd:: [no] interface IFACE Located under MPLS address-family node, use this command to enable or disable LDP discovery per interface. IFACE stands for the interface name where LDP is enabled. By default it is disabled. Once this command executed, the address-family interface node is configured. .. index:: [no] discovery transport-address A.B.C.D | A:B::C:D .. clicmd:: [no] discovery transport-address A.B.C.D | A:B::C:D Located under mpls address-family interface node, use this command to set the IPv4 or IPv6 transport-address used by the LDP protocol to talk on this interface. .. index:: [no] neighbor A.B.C.D password PASSWORD .. clicmd:: [no] neighbor A.B.C.D password PASSWORD The following command located under MPLS router node configures the router of a LDP device. This device, if found, will have to comply with the configured password. PASSWORD is a clear text password wit its digest sent through the network. .. index:: [no] neighbor A.B.C.D holdtime HOLDTIME .. clicmd:: [no] neighbor A.B.C.D holdtime HOLDTIME The following command located under MPLS router node configures the holdtime value in seconds of the LDP neighbor ID. Configuring it triggers a keepalive mechanism. That value can be configured between 15 and 65535 seconds. After this time of non response, the LDP established session will be considered as set to down. By default, no holdtime is configured for the LDP devices. .. index:: [no] discovery hello holdtime HOLDTIME .. clicmd:: [no] discovery hello holdtime HOLDTIME .. index:: [no] discovery hello interval INTERVAL .. clicmd:: [no] discovery hello interval INTERVAL INTERVAL value ranges from 1 to 65535 seconds. Default value is 5 seconds. This is the value between each hello timer message sent. HOLDTIME value ranges from 1 to 65535 seconds. Default value is 15 seconds. That value is added as a TLV in the LDP messages. .. index:: [no] dual-stack transport-connection prefer ipv4 .. clicmd:: [no] dual-stack transport-connection prefer ipv4 When *ldpd* is configured for dual-stack operation, the transport connection preference is IPv6 by default (as specified by :rfc:`7552`). On such circumstances, *ldpd* will refuse to establish TCP connections over IPv4. You can use above command to change the transport connection preference to IPv4. In this case, it will be possible to distribute label mappings for IPv6 FECs over TCPv4 connections. .. _show-ldp-information: Show LDP Information ==================== These commands dump various parts of *ldpd*. .. index:: show mpls ldp neighbor [A.B.C.D] .. clicmd:: show mpls ldp neighbor [A.B.C.D] This command dumps the various neighbors discovered. Below example shows that local machine has an operation neighbor with ID set to 1.1.1.1. :: west-vm# show mpls ldp neighbor AF ID State Remote Address Uptime ipv4 1.1.1.1 OPERATIONAL 1.1.1.1 00:01:37 west-vm# .. index:: show mpls ldp neighbor [A.B.C.D] capabilities .. clicmd:: show mpls ldp neighbor [A.B.C.D] capabilities .. index:: show mpls ldp neighbor [A.B.C.D] detail .. clicmd:: show mpls ldp neighbor [A.B.C.D] detail Above commands dump other neighbor information. .. index:: show mpls ldp discovery [detail] .. clicmd:: show mpls ldp discovery [detail] .. index:: show mpls ldp ipv4 discovery [detail] .. clicmd:: show mpls ldp ipv4 discovery [detail] .. index:: show mpls ldp ipv6 discovery [detail] .. clicmd:: show mpls ldp ipv6 discovery [detail] Above commands dump discovery information. .. index:: show mpls ldp ipv4 interface .. clicmd:: show mpls ldp ipv4 interface .. index:: show mpls ldp ipv6 interface .. clicmd:: show mpls ldp ipv6 interface Above command dumps the IPv4 or IPv6 interface per where LDP is enabled. Below output illustrates what is dumped for IPv4. :: west-vm# show mpls ldp ipv4 interface AF Interface State Uptime Hello Timers ac ipv4 eth1 ACTIVE 00:08:35 5/15 0 ipv4 eth3 ACTIVE 00:08:35 5/15 1 .. index:: show mpls ldp ipv4|ipv6 binding .. clicmd:: show mpls ldp ipv4|ipv6 binding Above command dumps the binding obtained through MPLS exchanges with LDP. :: west-vm# show mpls ldp ipv4 binding AF Destination Nexthop Local Label Remote Label In Use ipv4 1.1.1.1/32 1.1.1.1 16 imp-null yes ipv4 2.2.2.2/32 1.1.1.1 imp-null 16 no ipv4 10.0.2.0/24 1.1.1.1 imp-null imp-null no ipv4 10.115.0.0/24 1.1.1.1 imp-null 17 no ipv4 10.135.0.0/24 1.1.1.1 imp-null imp-null no ipv4 10.200.0.0/24 1.1.1.1 17 imp-null yes west-vm# LDP debugging commands ======================== .. index:: simple: debug mpls ldp KIND simple: no debug mpls ldp KIND .. clicmd:: [no] debug mpls ldp KIND Enable or disable debugging messages of a given kind. ``KIND`` can be one of: - ``discovery`` - ``errors`` - ``event`` - ``labels`` - ``messages`` - ``zebra`` LDP Example Configuration ========================= Below configuration gives a typical MPLS configuration of a device located in a MPLS backbone. LDP is enabled on two interfaces and will attempt to peer with two neighbors with router-id set to either 1.1.1.1 or 3.3.3.3. .. code-block:: frr mpls ldp router-id 2.2.2.2 neighbor 1.1.1.1 password test neighbor 3.3.3.3 password test ! address-family ipv4 discovery transport-address 2.2.2.2 ! interface eth1 ! interface eth3 ! exit-address-family ! Deploying LDP across a backbone generally is done in a full mesh configuration topology. LDP is typically deployed with an IGP like OSPF, that helps discover the remote IPs. Below example is an OSPF configuration extract that goes with LDP configuration .. code-block:: frr router ospf ospf router-id 2.2.2.2 network 0.0.0.0/0 area 0 ! Below output shows the routing entry on the LER side. The OSPF routing entry (10.200.0.0) is associated with Label entry (17), and shows that MPLS push action that traffic to that destination will be applied. :: north-vm# show ip route Codes: K - kernel route, C - connected, S - static, R - RIP, O - OSPF, I - IS-IS, B - BGP, E - EIGRP, N - NHRP, T - Table, v - VNC, V - VNC-Direct, A - Babel, D - SHARP, F - PBR, > - selected route, * - FIB route O>* 1.1.1.1/32 [110/120] via 10.115.0.1, eth2, label 16, 00:00:15 O>* 2.2.2.2/32 [110/20] via 10.115.0.1, eth2, label implicit-null, 00:00:15 O 3.3.3.3/32 [110/10] via 0.0.0.0, loopback1 onlink, 00:01:19 C>* 3.3.3.3/32 is directly connected, loopback1, 00:01:29 O>* 10.0.2.0/24 [110/11] via 10.115.0.1, eth2, label implicit-null, 00:00:15 O 10.100.0.0/24 [110/10] is directly connected, eth1, 00:00:32 C>* 10.100.0.0/24 is directly connected, eth1, 00:00:32 O 10.115.0.0/24 [110/10] is directly connected, eth2, 00:00:25 C>* 10.115.0.0/24 is directly connected, eth2, 00:00:32 O>* 10.135.0.0/24 [110/110] via 10.115.0.1, eth2, label implicit-null, 00:00:15 O>* 10.200.0.0/24 [110/210] via 10.115.0.1, eth2, label 17, 00:00:15 north-vm# frr-7.2.1/doc/user/nhrpd.rst0000644000000000000000000001056713610377563012615 00000000000000.. _nhrp: **** NHRP **** *nhrpd* is an implementation of the :abbr:NHRP `(Next Hop Routing Protocol)`. NHRP is described in :rfc`2332`. NHRP is used to improve the efficiency of routing computer network traffic over :abbr:`NBMA (Non-Broadcast, Multiple Access)` networks. NHRP provides an ARP-like solution that allows a system to dynamically learn the NBMA address of the other systems that are part of that network, allowing these systems to directly communicate without requiring traffic to use an intermediate hop. Cisco Dynamic Multipoint VPN (DMVPN) is based on NHRP, and |PACKAGE_NAME| nhrpd implements this scenario. .. _routing-design: Routing Design ============== nhrpd never handles routing of prefixes itself. You need to run some real routing protocol (e.g. BGP) to advertise routes over the tunnels. What nhrpd does it establishes 'shortcut routes' that optimizes the routing protocol to avoid going through extra nodes in NBMA GRE mesh. nhrpd does route NHRP domain addresses individually using per-host prefixes. This is similar to Cisco FlexVPN; but in contrast to opennhrp which uses a generic subnet route. To create NBMA GRE tunnel you might use the following (Linux terminal commands)::: ip tunnel add gre1 mode gre key 42 ttl 64 ip addr add 10.255.255.2/32 dev gre1 ip link set gre1 up Note that the IP-address is assigned as host prefix to gre1. nhrpd will automatically create additional host routes pointing to gre1 when a connection with these hosts is established. The gre1 subnet prefix should be announced by routing protocol from the hub nodes (e.g. BGP 'network' announce). This allows the routing protocol to decide which is the closest hub and determine the relay hub on prefix basis when direct tunnel is not established. nhrpd will redistribute directly connected neighbors to zebra. Within hub nodes, these routes should be internally redistributed using some routing protocol (e.g. iBGP) to allow hubs to be able to relay all traffic. This can be achieved in hubs with the following bgp configuration (network command defines the GRE subnet): .. code-block:: frr router bgp 65555 address-family ipv4 unicast network 172.16.0.0/16 redistribute nhrp exit-address-family .. _configuring-nhrp: Configuring NHRP ================ FIXME .. _hub-functionality: Hub Functionality ================= In addition to routing nhrp redistributed host prefixes, the hub nodes are also responsible to send NHRP Traffic Indication messages that trigger creation of the shortcut tunnels. nhrpd sends Traffic Indication messages based on network traffic captured using NFLOG. Typically you want to send Traffic Indications for network traffic that is routed from gre1 back to gre1 in rate limited manner. This can be achieved with the following iptables rule. .. code-block:: shell iptables -A FORWARD -i gre1 -o gre1 \\ -m hashlimit --hashlimit-upto 4/minute --hashlimit-burst 1 \\ --hashlimit-mode srcip,dstip --hashlimit-srcmask 24 --hashlimit-dstmask 24 \\ --hashlimit-name loglimit-0 -j NFLOG --nflog-group 1 --nflog-range 128 You can fine tune the src/dstmask according to the prefix lengths you announce internal, add additional IP range matches, or rate limitation if needed. However, the above should be good in most cases. This kernel NFLOG target's nflog-group is configured in global nhrp config with: .. code-block:: frr nhrp nflog-group 1 To start sending these traffic notices out from hubs, use the nhrp per-interface directive: .. code-block:: frr interface gre1 ip nhrp redirect .. _integration-with-ike: Integration with IKE ==================== nhrpd needs tight integration with IKE daemon for various reasons. Currently only strongSwan is supported as IKE daemon. nhrpd connects to strongSwan using VICI protocol based on UNIX socket (hardcoded now as /var/run/charon.vici). strongSwan currently needs few patches applied. Please check out the `http://git.alpinelinux.org/cgit/user/tteras/strongswan/log/?h=tteras-release,release `_ and `http://git.alpinelinux.org/cgit/user/tteras/strongswan/log/?h=tteras,working tree `_ git repositories for the patches. .. _nhrp-events: NHRP Events =========== FIXME Configuration Example ===================== FIXME frr-7.2.1/doc/user/ospf6d.rst0000644000000000000000000001357413610377563012704 00000000000000.. _ospfv3: ****** OSPFv3 ****** *ospf6d* is a daemon support OSPF version 3 for IPv6 network. OSPF for IPv6 is described in :rfc:`2740`. .. _ospf6-router: OSPF6 router ============ .. index:: router ospf6 .. clicmd:: router ospf6 .. index:: ospf6 router-id A.B.C.D .. clicmd:: ospf6 router-id A.B.C.D Set router's Router-ID. .. index:: interface IFNAME area AREA .. clicmd:: interface IFNAME area AREA Bind interface to specified area, and start sending OSPF packets. `area` can be specified as 0. .. index:: timers throttle spf DELAY INITIAL-HOLDTIME MAX-HOLDTIME .. clicmd:: timers throttle spf DELAY INITIAL-HOLDTIME MAX-HOLDTIME .. index:: no timers throttle spf .. clicmd:: no timers throttle spf This command sets the initial `delay`, the `initial-holdtime` and the `maximum-holdtime` between when SPF is calculated and the event which triggered the calculation. The times are specified in milliseconds and must be in the range of 0 to 600000 milliseconds. The `delay` specifies the minimum amount of time to delay SPF calculation (hence it affects how long SPF calculation is delayed after an event which occurs outside of the holdtime of any previous SPF calculation, and also serves as a minimum holdtime). Consecutive SPF calculations will always be separated by at least 'hold-time' milliseconds. The hold-time is adaptive and initially is set to the `initial-holdtime` configured with the above command. Events which occur within the holdtime of the previous SPF calculation will cause the holdtime to be increased by `initial-holdtime`, bounded by the `maximum-holdtime` configured with this command. If the adaptive hold-time elapses without any SPF-triggering event occurring then the current holdtime is reset to the `initial-holdtime`. .. code-block:: frr router ospf6 timers throttle spf 200 400 10000 In this example, the `delay` is set to 200ms, the initial holdtime is set to 400ms and the `maximum holdtime` to 10s. Hence there will always be at least 200ms between an event which requires SPF calculation and the actual SPF calculation. Further consecutive SPF calculations will always be separated by between 400ms to 10s, the hold-time increasing by 400ms each time an SPF-triggering event occurs within the hold-time of the previous SPF calculation. .. index:: auto-cost reference-bandwidth COST .. clicmd:: auto-cost reference-bandwidth COST .. index:: no auto-cost reference-bandwidth .. clicmd:: no auto-cost reference-bandwidth This sets the reference bandwidth for cost calculations, where this bandwidth is considered equivalent to an OSPF cost of 1, specified in Mbits/s. The default is 100Mbit/s (i.e. a link of bandwidth 100Mbit/s or higher will have a cost of 1. Cost of lower bandwidth links will be scaled with reference to this cost). This configuration setting MUST be consistent across all routers within the OSPF domain. .. _ospf6-area: OSPF6 area ========== Area support for OSPFv3 is not yet implemented. .. _ospf6-interface: OSPF6 interface =============== .. index:: ipv6 ospf6 cost COST .. clicmd:: ipv6 ospf6 cost COST Sets interface's output cost. Default value depends on the interface bandwidth and on the auto-cost reference bandwidth. .. index:: ipv6 ospf6 hello-interval HELLOINTERVAL .. clicmd:: ipv6 ospf6 hello-interval HELLOINTERVAL Sets interface's Hello Interval. Default 10 .. index:: ipv6 ospf6 dead-interval DEADINTERVAL .. clicmd:: ipv6 ospf6 dead-interval DEADINTERVAL Sets interface's Router Dead Interval. Default value is 40. .. index:: ipv6 ospf6 retransmit-interval RETRANSMITINTERVAL .. clicmd:: ipv6 ospf6 retransmit-interval RETRANSMITINTERVAL Sets interface's Rxmt Interval. Default value is 5. .. index:: ipv6 ospf6 priority PRIORITY .. clicmd:: ipv6 ospf6 priority PRIORITY Sets interface's Router Priority. Default value is 1. .. index:: ipv6 ospf6 transmit-delay TRANSMITDELAY .. clicmd:: ipv6 ospf6 transmit-delay TRANSMITDELAY Sets interface's Inf-Trans-Delay. Default value is 1. .. index:: ipv6 ospf6 network (broadcast|point-to-point) .. clicmd:: ipv6 ospf6 network (broadcast|point-to-point) Set explicitly network type for specified interface. .. _redistribute-routes-to-ospf6: Redistribute routes to OSPF6 ============================ .. index:: redistribute static .. clicmd:: redistribute static .. index:: redistribute connected .. clicmd:: redistribute connected .. index:: redistribute ripng .. clicmd:: redistribute ripng .. _showing-ospf6-information: Showing OSPF6 information ========================= .. index:: show ipv6 ospf6 [INSTANCE_ID] .. clicmd:: show ipv6 ospf6 [INSTANCE_ID] INSTANCE_ID is an optional OSPF instance ID. To see router ID and OSPF instance ID, simply type "show ipv6 ospf6 ". .. index:: show ipv6 ospf6 database .. clicmd:: show ipv6 ospf6 database This command shows LSA database summary. You can specify the type of LSA. .. index:: show ipv6 ospf6 interface .. clicmd:: show ipv6 ospf6 interface To see OSPF interface configuration like costs. .. index:: show ipv6 ospf6 neighbor .. clicmd:: show ipv6 ospf6 neighbor Shows state and chosen (Backup) DR of neighbor. .. index:: show ipv6 ospf6 request-list A.B.C.D .. clicmd:: show ipv6 ospf6 request-list A.B.C.D Shows requestlist of neighbor. .. index:: show ipv6 route ospf6 .. clicmd:: show ipv6 route ospf6 This command shows internal routing table. .. index:: show ipv6 ospf6 zebra .. clicmd:: show ipv6 ospf6 zebra Shows state about what is being redistributed between zebra and OSPF6 OSPF6 Configuration Examples ============================ Example of ospf6d configured on one interface and area: .. code-block:: frr interface eth0 ipv6 ospf6 instance-id 0 ! router ospf6 ospf6 router-id 212.17.55.53 area 0.0.0.0 range 2001:770:105:2::/64 interface eth0 area 0.0.0.0 ! frr-7.2.1/doc/user/ospf_fundamentals.rst0000644000000000000000000005105713610377563015211 00000000000000.. _ospf-fundamentals: OSPF Fundamentals ================= .. index:: Link-state routing protocol .. index:: Distance-vector routing protocol :abbr:`OSPF` is, mostly, a link-state routing protocol. In contrast to :term:`distance-vector` protocols, such as :abbr:`RIP` or :abbr:`BGP`, where routers describe available `paths` (i.e. routes) to each other, in :term:`link-state` protocols routers instead describe the state of their links to their immediate neighbouring routers. .. index:: Link State Announcement .. index:: Link State Advertisement .. index:: LSA flooding .. index:: Link State Database Each router describes their link-state information in a message known as an :abbr:`LSA (Link State Advertisement)`, which is then propagated through to all other routers in a link-state routing domain, by a process called `flooding`. Each router thus builds up an :abbr:`LSDB (Link State Database)` of all the link-state messages. From this collection of LSAs in the LSDB, each router can then calculate the shortest path to any other router, based on some common metric, by using an algorithm such as `Edgar Djikstra's `_ :abbr:`SPF (Shortest Path First)` algorithm. .. index:: Link-state routing protocol advantages By describing connectivity of a network in this way, in terms of routers and links rather than in terms of the paths through a network, a link-state protocol can use less bandwidth and converge more quickly than other protocols. A link-state protocol need distribute only one link-state message throughout the link-state domain when a link on any single given router changes state, in order for all routers to reconverge on the best paths through the network. In contrast, distance vector protocols can require a progression of different path update messages from a series of different routers in order to converge. .. index:: Link-state routing protocol disadvantages The disadvantage to a link-state protocol is that the process of computing the best paths can be relatively intensive when compared to distance-vector protocols, in which near to no computation need be done other than (potentially) select between multiple routes. This overhead is mostly negligible for modern embedded CPUs, even for networks with thousands of nodes. The primary scaling overhead lies more in coping with the ever greater frequency of LSA updates as the size of a link-state area increases, in managing the :abbr:`LSDB` and required flooding. This section aims to give a distilled, but accurate, description of the more important workings of :abbr:`OSPF` which an administrator may need to know to be able best configure and trouble-shoot :abbr:`OSPF`. OSPF Mechanisms --------------- :abbr:`OSPF` defines a range of mechanisms, concerned with detecting, describing and propagating state through a network. These mechanisms will nearly all be covered in greater detail further on. They may be broadly classed as: .. index:: OSPF Hello Protocol The Hello Protocol ^^^^^^^^^^^^^^^^^^ The OSPF Hello protocol allows OSPF to quickly detect changes in two-way reachability between routers on a link. OSPF can additionally avail of other sources of reachability information, such as link-state information provided by hardware, or through dedicated reachability protocols such as :abbr:`BFD (Bidirectional Forwarding Detection)`. OSPF also uses the Hello protocol to propagate certain state between routers sharing a link, for example: - Hello protocol configured state, such as the dead-interval. - Router priority, for DR/BDR election. - DR/BDR election results. - Any optional capabilities supported by each router. The Hello protocol is comparatively trivial and will not be explored in greater detail than here. .. index:: OSPF LSA overview .. _ospf-lsas: LSAs ^^^^ At the heart of :abbr:`OSPF` are :abbr:`LSA (Link State Advertisement)` messages. Despite the name, some :abbr:`LSA` s do not, strictly speaking, describe link-state information. Common :abbr:`LSA` s describe information such as: - Routers, in terms of their links. - Networks, in terms of attached routers. - Routes, external to a link-state domain: External Routes Routes entirely external to :abbr:`OSPF`. Routers originating such routes are known as :abbr:`ASBR (Autonomous-System Border Router)` routers. Summary Routes Routes which summarise routing information relating to OSPF areas external to the OSPF link-state area at hand, originated by :abbr:`ABR (Area Boundary Router)` routers. .. _ospf-lsa-flooding: LSA Flooding """""""""""" OSPF defines several related mechanisms, used to manage synchronisation of :abbr:`LSDB` s between neighbours as neighbours form adjacencies and the propagation, or `flooding` of new or updated :abbr:`LSA` s. .. index:: OSPF Areas overview .. _ospf-areas: Areas ^^^^^ OSPF provides for the protocol to be broken up into multiple smaller and independent link-state areas. Each area must be connected to a common backbone area by an :abbr:`ABR (Area Boundary Router)`. These :abbr:`ABR` routers are responsible for summarising the link-state routing information of an area into `Summary LSAs`, possibly in a condensed (i.e. aggregated) form, and then originating these summaries into all other areas the :abbr:`ABR` is connected to. Note that only summaries and external routes are passed between areas. As these describe *paths*, rather than any router link-states, routing between areas hence is by :term:`distance-vector`, **not** link-state. OSPF LSAs --------- The core objects in OSPF are :abbr:`LSA` s. Everything else in OSPF revolves around detecting what to describe in LSAs, when to update them, how to flood them throughout a network and how to calculate routes from them. There are a variety of different :abbr:`LSA` s, for purposes such as describing actual link-state information, describing paths (i.e. routes), describing bandwidth usage of links for :abbr:`TE (Traffic Engineering)` purposes, and even arbitrary data by way of *Opaque* :abbr:`LSA` s. LSA Header ^^^^^^^^^^ All LSAs share a common header with the following information: - Type Different types of :abbr:`LSA` s describe different things in :abbr:`OSPF`. Types include: - Router LSA - Network LSA - Network Summary LSA - Router Summary LSA - AS-External LSA The specifics of the different types of LSA are examined below. - Advertising Router The Router ID of the router originating the LSA. .. seealso:: :clicmd:`ospf router-id A.B.C.D`. - LSA ID The ID of the LSA, which is typically derived in some way from the information the LSA describes, e.g. a Router LSA uses the Router ID as the LSA ID, a Network LSA will have the IP address of the :abbr:`DR` as its LSA ID. The combination of the Type, ID and Advertising Router ID must uniquely identify the :abbr:`LSA`. There can however be multiple instances of an LSA with the same Type, LSA ID and Advertising Router ID, see :ref:`sequence number `. - Age A number to allow stale :abbr:`LSA` s to, eventually, be purged by routers from their :abbr:`LSDB` s. The value nominally is one of seconds. An age of 3600, i.e. 1 hour, is called the `MaxAge`. MaxAge LSAs are ignored in routing calculations. LSAs must be periodically refreshed by their Advertising Router before reaching MaxAge if they are to remain valid. Routers may deliberately flood LSAs with the age artificially set to 3600 to indicate an LSA is no longer valid. This is called `flushing` of an LSA. It is not abnormal to see stale LSAs in the LSDB, this can occur where a router has shutdown without flushing its LSA(s), e.g. where it has become disconnected from the network. Such LSAs do little harm. .. _ospf-lsa-sequence-number: - Sequence Number A number used to distinguish newer instances of an LSA from older instances. Link-State LSAs ^^^^^^^^^^^^^^^ Of all the various kinds of :abbr:`LSA` s, just two types comprise the actual link-state part of :abbr:`OSPF`, Router :abbr:`LSA` s and Network :abbr:`LSA` s. These LSA types are absolutely core to the protocol. Instances of these LSAs are specific to the link-state area in which they are originated. Routes calculated from these two LSA types are called `intra-area routes`. - Router LSA Each OSPF Router must originate a router :abbr:`LSA` to describe itself. In it, the router lists each of its :abbr:`OSPF` enabled interfaces, for the given link-state area, in terms of: Cost The output cost of that interface, scaled inversely to some commonly known reference value, :clicmd:`auto-cost reference-bandwidth (1-4294967`. Link Type Transit Network A link to a multi-access network, on which the router has at least one Full adjacency with another router. :abbr:`PtP (Point-to-Point)` A link to a single remote router, with a Full adjacency. No :abbr:`DR (Designated Router)` is elected on such links; no network LSA is originated for such a link. Stub A link with no adjacent neighbours, or a host route. - Link ID and Data These values depend on the Link Type: +----------------+-----------------------------------+------------------------------------------+ | Link Type | Link ID | Link Data | +================+===================================+==========================================+ | Transit | Link IP address of the :abbr:`DR` | Interface IP address | +----------------+-----------------------------------+------------------------------------------+ | Point-to-Point | Router ID of the remote router | Local interface IP address, or the | | | | :abbr:`ifindex (MIB-II interface index)` | | | | for unnumbered links | +----------------+-----------------------------------+------------------------------------------+ | Stub | IP address | Subnet Mask | +----------------+-----------------------------------+------------------------------------------+ Links on a router may be listed multiple times in the Router LSA, e.g. a :abbr:`PtP` interface on which OSPF is enabled must *always* be described by a Stub link in the Router :abbr:`LSA`, in addition to being listed as PtP link in the Router :abbr:`LSA` if the adjacency with the remote router is Full. Stub links may also be used as a way to describe links on which OSPF is *not* spoken, known as `passive interfaces`, see :clicmd:`passive-interface INTERFACE`. - Network LSA On multi-access links (e.g. ethernets, certain kinds of ATM and X.25 configurations), routers elect a :abbr:`DR`. The :abbr:`DR` is responsible for originating a Network :abbr:`LSA`, which helps reduce the information needed to describe multi-access networks with multiple routers attached. The :abbr:`DR` also acts as a hub for the flooding of :abbr:`LSA` s on that link, thus reducing flooding overheads. The contents of the Network LSA describes the: - Subnet Mask As the :abbr:`LSA` ID of a Network LSA must be the IP address of the :abbr:`DR`, the Subnet Mask together with the :abbr:`LSA` ID gives you the network address. - Attached Routers Each router fully-adjacent with the :abbr:`DR` is listed in the LSA, by their Router-ID. This allows the corresponding Router :abbr:`LSA` s to be easily retrieved from the :abbr:`LSDB`. Summary of Link State LSAs: +-------------+----------------------------+--------------------------------------------+ | LSA Type | LSA ID | LSA Data Describes | +=============+============================+============================================+ | Router LSA | Router ID | The :abbr:`OSPF` enabled links of the | | | | router, within a specific link-state area. | +-------------+----------------------------+--------------------------------------------+ | Network LSA | The IP address of the | The subnet mask of the network and the | | | :abbr:`DR` for the network | Router IDs of all routers on the network | +-------------+----------------------------+--------------------------------------------+ With an LSDB composed of just these two types of :abbr:`LSA`, it is possible to construct a directed graph of the connectivity between all routers and networks in a given OSPF link-state area. So, not surprisingly, when OSPF routers build updated routing tables, the first stage of :abbr:`SPF` calculation concerns itself only with these two LSA types. .. _ospf-link-state-lsa-examples: Link-State LSA Examples ^^^^^^^^^^^^^^^^^^^^^^^ The example below shows two :abbr:`LSA` s, both originated by the same router (Router ID 192.168.0.49) and with the same :abbr:`LSA` ID (192.168.0.49), but of different LSA types. The first LSA being the router LSA describing 192.168.0.49's links: 2 links to multi-access networks with fully-adjacent neighbours (i.e. Transit links) and 1 being a Stub link (no adjacent neighbours). The second LSA being a Network LSA, for which 192.168.0.49 is the :abbr:`DR`, listing the Router IDs of 4 routers on that network which are fully adjacent with 192.168.0.49. :: # show ip ospf database router 192.168.0.49 OSPF Router with ID (192.168.0.53) Router Link States (Area 0.0.0.0) LS age: 38 Options: 0x2 : *|-|-|-|-|-|E|* LS Flags: 0x6 Flags: 0x2 : ASBR LS Type: router-LSA Link State ID: 192.168.0.49 Advertising Router: 192.168.0.49 LS Seq Number: 80000f90 Checksum: 0x518b Length: 60 Number of Links: 3 Link connected to: a Transit Network (Link ID) Designated Router address: 192.168.1.3 (Link Data) Router Interface address: 192.168.1.3 Number of TOS metrics: 0 TOS 0 Metric: 10 Link connected to: a Transit Network (Link ID) Designated Router address: 192.168.0.49 (Link Data) Router Interface address: 192.168.0.49 Number of TOS metrics: 0 TOS 0 Metric: 10 Link connected to: Stub Network (Link ID) Net: 192.168.3.190 (Link Data) Network Mask: 255.255.255.255 Number of TOS metrics: 0 TOS 0 Metric: 39063 # show ip ospf database network 192.168.0.49 OSPF Router with ID (192.168.0.53) Net Link States (Area 0.0.0.0) LS age: 285 Options: 0x2 : *|-|-|-|-|-|E|* LS Flags: 0x6 LS Type: network-LSA Link State ID: 192.168.0.49 (address of Designated Router) Advertising Router: 192.168.0.49 LS Seq Number: 80000074 Checksum: 0x0103 Length: 40 Network Mask: /29 Attached Router: 192.168.0.49 Attached Router: 192.168.0.52 Attached Router: 192.168.0.53 Attached Router: 192.168.0.54 Note that from one LSA, you can find the other. E.g. Given the Network-LSA you have a list of Router IDs on that network, from which you can then look up, in the local :abbr:`LSDB`, the matching Router LSA. From that Router-LSA you may (potentially) find links to other Transit networks and Routers IDs which can be used to lookup the corresponding Router or Network LSA. And in that fashion, one can find all the Routers and Networks reachable from that starting :abbr:`LSA`. Given the Router LSA instead, you have the IP address of the :abbr:`DR` of any attached transit links. Network LSAs will have that IP as their LSA ID, so you can then look up that Network LSA and from that find all the attached routers on that link, leading potentially to more links and Network and Router LSAs, etc. etc. From just the above two :abbr:`LSA` s, one can already see the following partial topology: :: ------------------------ Network: ...... | Designated Router IP: 192.168.1.3 | IP: 192.168.1.3 (transit link) (cost: 10) Router ID: 192.168.0.49(stub)---------- IP: 192.168.3.190/32 (cost: 10) (cost: 39063) (transit link) IP: 192.168.0.49 | | ------------------------------ Network: 192.168.0.48/29 | | | Designated Router IP: 192.168.0.49 | | | | | Router ID: 192.168.0.54 | | | Router ID: 192.168.0.53 | Router ID: 192.168.0.52 Note the Router IDs, though they look like IP addresses and often are IP addresses, are not strictly speaking IP addresses, nor need they be reachable addresses (though, OSPF will calculate routes to Router IDs). External LSAs ^^^^^^^^^^^^^ External, or "Type 5", :abbr:`LSA` s describe routing information which is entirely external to :abbr:`OSPF`, and is "injected" into :abbr:`OSPF`. Such routing information may have come from another routing protocol, such as RIP or BGP, they may represent static routes or they may represent a default route. An :abbr:`OSPF` router which originates External :abbr:`LSA` s is known as an :abbr:`ASBR (AS Boundary Router)`. Unlike the link-state :abbr:`LSA` s, and most other :abbr:`LSA` s, which are flooded only within the area in which they originate, External :abbr:`LSA` s are flooded through-out the :abbr:`OSPF` network to all areas capable of carrying External :abbr:`LSA` s (:ref:`ospf-areas`). Routes internal to OSPF (intra-area or inter-area) are always preferred over external routes. The External :abbr:`LSA` describes the following: IP Network number The IP Network number of the route is described by the :abbr:`LSA` ID field. IP Network Mask The body of the External LSA describes the IP Network Mask of the route. This, together with the :abbr:`LSA` ID, describes the prefix of the IP route concerned. Metric The cost of the External Route. This cost may be an OSPF cost (also known as a "Type 1" metric), i.e. equivalent to the normal OSPF costs, or an externally derived cost ("Type 2" metric) which is not comparable to OSPF costs and always considered larger than any OSPF cost. Where there are both Type 1 and 2 External routes for a route, the Type 1 is always preferred. Forwarding Address The address of the router to forward packets to for the route. This may be, and usually is, left as 0 to specify that the ASBR originating the External :abbr:`LSA` should be used. There must be an internal OSPF route to the forwarding address, for the forwarding address to be usable. Tag An arbitrary 4-bytes of data, not interpreted by OSPF, which may carry whatever information about the route which OSPF speakers desire. AS External LSA Example ^^^^^^^^^^^^^^^^^^^^^^^ To illustrate, below is an example of an External :abbr:`LSA` in the :abbr:`LSDB` of an OSPF router. It describes a route to the IP prefix of 192.168.165.0/24, originated by the ASBR with Router-ID 192.168.0.49. The metric of 20 is external to OSPF. The forwarding address is 0, so the route should forward to the originating ASBR if selected. :: # show ip ospf database external 192.168.165.0 LS age: 995 Options: 0x2 : *|-|-|-|-|-|E|* LS Flags: 0x9 LS Type: AS-external-LSA Link State ID: 192.168.165.0 (External Network Number) Advertising Router: 192.168.0.49 LS Seq Number: 800001d8 Checksum: 0xea27 Length: 36 Network Mask: /24 Metric Type: 2 (Larger than any link state path) TOS: 0 Metric: 20 Forward Address: 0.0.0.0 External Route Tag: 0 We can add this to our partial topology from above, which now looks like::: --------------------- Network: ...... | Designated Router IP: 192.168.1.3 | IP: 192.168.1.3 /---- External route: 192.168.165.0/24 (transit link) / Cost: 20 (External metric) (cost: 10) / Router ID: 192.168.0.49(stub)---------- IP: 192.168.3.190/32 (cost: 10) (cost: 39063) (transit link) IP: 192.168.0.49 | | ------------------------------ Network: 192.168.0.48/29 | | | Designated Router IP: 192.168.0.49 | | | | | Router ID: 192.168.0.54 | | | Router ID: 192.168.0.53 | Router ID: 192.168.0.52 Summary LSAs ^^^^^^^^^^^^ Summary LSAs are created by :abbr:`ABR` s to summarise the destinations available within one area to other areas. These LSAs may describe IP networks, potentially in aggregated form, or :abbr:`ASBR` routers. frr-7.2.1/doc/user/ospfd.rst0000644000000000000000000013535613610377563012621 00000000000000.. _ospfv2: ****** OSPFv2 ****** :abbr:`OSPF (Open Shortest Path First)` version 2 is a routing protocol which is described in :rfc:`2328`. OSPF is an :abbr:`IGP (Interior Gateway Protocol)`. Compared with :abbr:`RIP`, :abbr:`OSPF` can provide scalable network support and faster convergence times. OSPF is widely used in large networks such as :abbr:`ISP (Internet Service Provider)` backbone and enterprise networks. .. include:: ospf_fundamentals.rst .. _configuring-ospfd: Configuring OSPF ================ *ospfd* accepts all :ref:`common-invocation-options`. .. option:: -n, --instance Specify the instance number for this invocation of *ospfd*. .. option:: -a, --apiserver Enable the OSPF API server. This is required to use ``ospfclient``. *ospfd* must acquire interface information from *zebra* in order to function. Therefore *zebra* must be running before invoking *ospfd*. Also, if *zebra* is restarted then *ospfd* must be too. Like other daemons, *ospfd* configuration is done in :abbr:`OSPF` specific configuration file :file:`ospfd.conf` when the integrated config is not used. .. _ospf-multi-instance: Multi-instance Support ---------------------- OSPF supports multiple instances. Each instance is identified by a positive nonzero integer that must be provided when adding configuration items specific to that instance. Enabling instances is done with :file:`/etc/frr/daemons` in the following manner: :: ... ospfd=yes ospfd_instances=1,5,6 ... The ``ospfd_instances`` variable controls which instances are started and what their IDs are. In this example, after starting FRR you should see the following processes: .. code-block:: shell # ps -ef | grep "ospfd" frr 11816 1 0 17:30 ? 00:00:00 /usr/lib/frr/ospfd --daemon -A 127.0.0.1 -n 1 frr 11822 1 0 17:30 ? 00:00:00 /usr/lib/frr/ospfd --daemon -A 127.0.0.1 -n 2 frr 11828 1 0 17:30 ? 00:00:00 /usr/lib/frr/ospfd --daemon -A 127.0.0.1 -n 3 The instance number should be specified in the config when addressing a particular instance: .. code-block:: frr router ospf 5 ospf router-id 1.2.3.4 area 0.0.0.0 authentication message-digest ... .. _ospf-router: Routers ------- To start OSPF process you have to specify the OSPF router. .. index:: router ospf [(1-65535)] vrf NAME .. clicmd:: router ospf [(1-65535)] vrf NAME .. index:: no router ospf [(1-65535)] vrf NAME .. clicmd:: no router ospf [(1-65535)] vrf NAME Enable or disable the OSPF process. .. index:: ospf router-id A.B.C.D .. clicmd:: ospf router-id A.B.C.D .. index:: no ospf router-id [A.B.C.D] .. clicmd:: no ospf router-id [A.B.C.D] This sets the router-ID of the OSPF process. The router-ID may be an IP address of the router, but need not be - it can be any arbitrary 32bit number. However it MUST be unique within the entire OSPF domain to the OSPF speaker - bad things will happen if multiple OSPF speakers are configured with the same router-ID! If one is not specified then *ospfd* will obtain a router-ID automatically from *zebra*. .. index:: ospf abr-type TYPE .. clicmd:: ospf abr-type TYPE .. index:: no ospf abr-type TYPE .. clicmd:: no ospf abr-type TYPE `type` can be cisco|ibm|shortcut|standard. The "Cisco" and "IBM" types are equivalent. The OSPF standard for ABR behaviour does not allow an ABR to consider routes through non-backbone areas when its links to the backbone are down, even when there are other ABRs in attached non-backbone areas which still can reach the backbone - this restriction exists primarily to ensure routing-loops are avoided. With the "Cisco" or "IBM" ABR type, the default in this release of FRR, this restriction is lifted, allowing an ABR to consider summaries learned from other ABRs through non-backbone areas, and hence route via non-backbone areas as a last resort when, and only when, backbone links are down. Note that areas with fully-adjacent virtual-links are considered to be "transit capable" and can always be used to route backbone traffic, and hence are unaffected by this setting (:clicmd:`area A.B.C.D virtual-link A.B.C.D`). More information regarding the behaviour controlled by this command can be found in :rfc:`3509`, and :t:`draft-ietf-ospf-shortcut-abr-02.txt`. Quote: "Though the definition of the :abbr:`ABR (Area Border Router)` in the OSPF specification does not require a router with multiple attached areas to have a backbone connection, it is actually necessary to provide successful routing to the inter-area and external destinations. If this requirement is not met, all traffic destined for the areas not connected to such an ABR or out of the OSPF domain, is dropped. This document describes alternative ABR behaviors implemented in Cisco and IBM routers." .. index:: ospf rfc1583compatibility .. clicmd:: ospf rfc1583compatibility .. index:: no ospf rfc1583compatibility .. clicmd:: no ospf rfc1583compatibility :rfc:`2328`, the successor to :rfc:`1583`, suggests according to section G.2 (changes) in section 16.4 a change to the path preference algorithm that prevents possible routing loops that were possible in the old version of OSPFv2. More specifically it demands that inter-area paths and intra-area backbone path are now of equal preference but still both preferred to external paths. This command should NOT be set normally. .. index:: log-adjacency-changes [detail] .. clicmd:: log-adjacency-changes [detail] .. index:: no log-adjacency-changes [detail] .. clicmd:: no log-adjacency-changes [detail] Configures ospfd to log changes in adjacency. With the optional detail argument, all changes in adjacency status are shown. Without detail, only changes to full or regressions are shown. .. index:: passive-interface INTERFACE .. clicmd:: passive-interface INTERFACE .. index:: no passive-interface INTERFACE .. clicmd:: no passive-interface INTERFACE Do not speak OSPF interface on the given interface, but do advertise the interface as a stub link in the router-:abbr:`LSA (Link State Advertisement)` for this router. This allows one to advertise addresses on such connected interfaces without having to originate AS-External/Type-5 LSAs (which have global flooding scope) - as would occur if connected addresses were redistributed into OSPF (:ref:`redistribute-routes-to-ospf`). This is the only way to advertise non-OSPF links into stub areas. .. index:: timers throttle spf DELAY INITIAL-HOLDTIME MAX-HOLDTIME .. clicmd:: timers throttle spf DELAY INITIAL-HOLDTIME MAX-HOLDTIME .. index:: no timers throttle spf .. clicmd:: no timers throttle spf This command sets the initial `delay`, the `initial-holdtime` and the `maximum-holdtime` between when SPF is calculated and the event which triggered the calculation. The times are specified in milliseconds and must be in the range of 0 to 600000 milliseconds. The `delay` specifies the minimum amount of time to delay SPF calculation (hence it affects how long SPF calculation is delayed after an event which occurs outside of the holdtime of any previous SPF calculation, and also serves as a minimum holdtime). Consecutive SPF calculations will always be separated by at least 'hold-time' milliseconds. The hold-time is adaptive and initially is set to the `initial-holdtime` configured with the above command. Events which occur within the holdtime of the previous SPF calculation will cause the holdtime to be increased by `initial-holdtime`, bounded by the `maximum-holdtime` configured with this command. If the adaptive hold-time elapses without any SPF-triggering event occurring then the current holdtime is reset to the `initial-holdtime`. The current holdtime can be viewed with :clicmd:`show ip ospf`, where it is expressed as a multiplier of the `initial-holdtime`. .. code-block:: frr router ospf timers throttle spf 200 400 10000 In this example, the `delay` is set to 200ms, the initial holdtime is set to 400ms and the `maximum holdtime` to 10s. Hence there will always be at least 200ms between an event which requires SPF calculation and the actual SPF calculation. Further consecutive SPF calculations will always be separated by between 400ms to 10s, the hold-time increasing by 400ms each time an SPF-triggering event occurs within the hold-time of the previous SPF calculation. This command supersedes the *timers spf* command in previous FRR releases. .. index:: max-metric router-lsa [on-startup|on-shutdown] (5-86400) .. clicmd:: max-metric router-lsa [on-startup|on-shutdown] (5-86400) .. index:: max-metric router-lsa administrative .. clicmd:: max-metric router-lsa administrative .. index:: no max-metric router-lsa [on-startup|on-shutdown|administrative] .. clicmd:: no max-metric router-lsa [on-startup|on-shutdown|administrative] This enables :rfc:`3137` support, where the OSPF process describes its transit links in its router-LSA as having infinite distance so that other routers will avoid calculating transit paths through the router while still being able to reach networks through the router. This support may be enabled administratively (and indefinitely) or conditionally. Conditional enabling of max-metric router-lsas can be for a period of seconds after startup and/or for a period of seconds prior to shutdown. Enabling this for a period after startup allows OSPF to converge fully first without affecting any existing routes used by other routers, while still allowing any connected stub links and/or redistributed routes to be reachable. Enabling this for a period of time in advance of shutdown allows the router to gracefully excuse itself from the OSPF domain. Enabling this feature administratively allows for administrative intervention for whatever reason, for an indefinite period of time. Note that if the configuration is written to file, this administrative form of the stub-router command will also be written to file. If *ospfd* is restarted later, the command will then take effect until manually deconfigured. Configured state of this feature as well as current status, such as the number of second remaining till on-startup or on-shutdown ends, can be viewed with the :clicmd:`show ip ospf` command. .. index:: auto-cost reference-bandwidth (1-4294967) .. clicmd:: auto-cost reference-bandwidth (1-4294967) .. index:: no auto-cost reference-bandwidth .. clicmd:: no auto-cost reference-bandwidth This sets the reference bandwidth for cost calculations, where this bandwidth is considered equivalent to an OSPF cost of 1, specified in Mbits/s. The default is 100Mbit/s (i.e. a link of bandwidth 100Mbit/s or higher will have a cost of 1. Cost of lower bandwidth links will be scaled with reference to this cost). This configuration setting MUST be consistent across all routers within the OSPF domain. .. index:: network A.B.C.D/M area A.B.C.D .. clicmd:: network A.B.C.D/M area A.B.C.D .. index:: network A.B.C.D/M area (0-4294967295) .. clicmd:: network A.B.C.D/M area (0-4294967295) .. index:: no network A.B.C.D/M area A.B.C.D .. clicmd:: no network A.B.C.D/M area A.B.C.D .. index:: no network A.B.C.D/M area (0-4294967295) .. clicmd:: no network A.B.C.D/M area (0-4294967295) This command specifies the OSPF enabled interface(s). If the interface has an address from range 192.168.1.0/24 then the command below enables ospf on this interface so router can provide network information to the other ospf routers via this interface. .. code-block:: frr router ospf network 192.168.1.0/24 area 0.0.0.0 Prefix length in interface must be equal or bigger (i.e. smaller network) than prefix length in network statement. For example statement above doesn't enable ospf on interface with address 192.168.1.1/23, but it does on interface with address 192.168.1.129/25. Note that the behavior when there is a peer address defined on an interface changed after release 0.99.7. Currently, if a peer prefix has been configured, then we test whether the prefix in the network command contains the destination prefix. Otherwise, we test whether the network command prefix contains the local address prefix of the interface. In some cases it may be more convenient to enable OSPF on a per interface/subnet basis (:clicmd:`ip ospf area AREA [ADDR]`). .. _ospf-area: Areas ----- .. index:: area A.B.C.D range A.B.C.D/M .. clicmd:: area A.B.C.D range A.B.C.D/M .. index:: area (0-4294967295) range A.B.C.D/M .. clicmd:: area (0-4294967295) range A.B.C.D/M .. index:: no area A.B.C.D range A.B.C.D/M .. clicmd:: no area A.B.C.D range A.B.C.D/M .. index:: no area (0-4294967295) range A.B.C.D/M .. clicmd:: no area (0-4294967295) range A.B.C.D/M Summarize intra area paths from specified area into one Type-3 summary-LSA announced to other areas. This command can be used only in ABR and ONLY router-LSAs (Type-1) and network-LSAs (Type-2) (i.e. LSAs with scope area) can be summarized. Type-5 AS-external-LSAs can't be summarized - their scope is AS. Summarizing Type-7 AS-external-LSAs isn't supported yet by FRR. .. code-block:: frr router ospf network 192.168.1.0/24 area 0.0.0.0 network 10.0.0.0/8 area 0.0.0.10 area 0.0.0.10 range 10.0.0.0/8 With configuration above one Type-3 Summary-LSA with routing info 10.0.0.0/8 is announced into backbone area if area 0.0.0.10 contains at least one intra-area network (i.e. described with router or network LSA) from this range. .. index:: area A.B.C.D range IPV4_PREFIX not-advertise .. clicmd:: area A.B.C.D range IPV4_PREFIX not-advertise .. index:: no area A.B.C.D range IPV4_PREFIX not-advertise .. clicmd:: no area A.B.C.D range IPV4_PREFIX not-advertise Instead of summarizing intra area paths filter them - i.e. intra area paths from this range are not advertised into other areas. This command makes sense in ABR only. .. index:: area A.B.C.D range IPV4_PREFIX substitute IPV4_PREFIX .. clicmd:: area A.B.C.D range IPV4_PREFIX substitute IPV4_PREFIX .. index:: no area A.B.C.D range IPV4_PREFIX substitute IPV4_PREFIX .. clicmd:: no area A.B.C.D range IPV4_PREFIX substitute IPV4_PREFIX Substitute summarized prefix with another prefix. .. code-block:: frr router ospf network 192.168.1.0/24 area 0.0.0.0 network 10.0.0.0/8 area 0.0.0.10 area 0.0.0.10 range 10.0.0.0/8 substitute 11.0.0.0/8 One Type-3 summary-LSA with routing info 11.0.0.0/8 is announced into backbone area if area 0.0.0.10 contains at least one intra-area network (i.e. described with router-LSA or network-LSA) from range 10.0.0.0/8. This command makes sense in ABR only. .. index:: area A.B.C.D virtual-link A.B.C.D .. clicmd:: area A.B.C.D virtual-link A.B.C.D .. index:: area (0-4294967295) virtual-link A.B.C.D .. clicmd:: area (0-4294967295) virtual-link A.B.C.D .. index:: no area A.B.C.D virtual-link A.B.C.D .. clicmd:: no area A.B.C.D virtual-link A.B.C.D .. index:: no area (0-4294967295) virtual-link A.B.C.D .. clicmd:: no area (0-4294967295) virtual-link A.B.C.D .. index:: area A.B.C.D shortcut .. clicmd:: area A.B.C.D shortcut .. index:: area (0-4294967295) shortcut .. clicmd:: area (0-4294967295) shortcut .. index:: no area A.B.C.D shortcut .. clicmd:: no area A.B.C.D shortcut .. index:: no area (0-4294967295) shortcut .. clicmd:: no area (0-4294967295) shortcut Configure the area as Shortcut capable. See :rfc:`3509`. This requires that the 'abr-type' be set to 'shortcut'. .. index:: area A.B.C.D stub .. clicmd:: area A.B.C.D stub .. index:: area (0-4294967295) stub .. clicmd:: area (0-4294967295) stub .. index:: no area A.B.C.D stub .. clicmd:: no area A.B.C.D stub .. index:: no area (0-4294967295) stub .. clicmd:: no area (0-4294967295) stub Configure the area to be a stub area. That is, an area where no router originates routes external to OSPF and hence an area where all external routes are via the ABR(s). Hence, ABRs for such an area do not need to pass AS-External LSAs (type-5s) or ASBR-Summary LSAs (type-4) into the area. They need only pass Network-Summary (type-3) LSAs into such an area, along with a default-route summary. .. index:: area A.B.C.D stub no-summary .. clicmd:: area A.B.C.D stub no-summary .. index:: area (0-4294967295) stub no-summary .. clicmd:: area (0-4294967295) stub no-summary .. index:: no area A.B.C.D stub no-summary .. clicmd:: no area A.B.C.D stub no-summary .. index:: no area (0-4294967295) stub no-summary .. clicmd:: no area (0-4294967295) stub no-summary Prevents an *ospfd* ABR from injecting inter-area summaries into the specified stub area. .. index:: area A.B.C.D default-cost (0-16777215) .. clicmd:: area A.B.C.D default-cost (0-16777215) .. index:: no area A.B.C.D default-cost (0-16777215) .. clicmd:: no area A.B.C.D default-cost (0-16777215) Set the cost of default-summary LSAs announced to stubby areas. .. index:: area A.B.C.D export-list NAME .. clicmd:: area A.B.C.D export-list NAME .. index:: area (0-4294967295) export-list NAME .. clicmd:: area (0-4294967295) export-list NAME .. index:: no area A.B.C.D export-list NAME .. clicmd:: no area A.B.C.D export-list NAME .. index:: no area (0-4294967295) export-list NAME .. clicmd:: no area (0-4294967295) export-list NAME Filter Type-3 summary-LSAs announced to other areas originated from intra- area paths from specified area. .. code-block:: frr router ospf network 192.168.1.0/24 area 0.0.0.0 network 10.0.0.0/8 area 0.0.0.10 area 0.0.0.10 export-list foo ! access-list foo permit 10.10.0.0/16 access-list foo deny any With example above any intra-area paths from area 0.0.0.10 and from range 10.10.0.0/16 (for example 10.10.1.0/24 and 10.10.2.128/30) are announced into other areas as Type-3 summary-LSA's, but any others (for example 10.11.0.0/16 or 10.128.30.16/30) aren't. This command is only relevant if the router is an ABR for the specified area. .. index:: area A.B.C.D import-list NAME .. clicmd:: area A.B.C.D import-list NAME .. index:: area (0-4294967295) import-list NAME .. clicmd:: area (0-4294967295) import-list NAME .. index:: no area A.B.C.D import-list NAME .. clicmd:: no area A.B.C.D import-list NAME .. index:: no area (0-4294967295) import-list NAME .. clicmd:: no area (0-4294967295) import-list NAME Same as export-list, but it applies to paths announced into specified area as Type-3 summary-LSAs. .. index:: area A.B.C.D filter-list prefix NAME in .. clicmd:: area A.B.C.D filter-list prefix NAME in .. index:: area A.B.C.D filter-list prefix NAME out .. clicmd:: area A.B.C.D filter-list prefix NAME out .. index:: area (0-4294967295) filter-list prefix NAME in .. clicmd:: area (0-4294967295) filter-list prefix NAME in .. index:: area (0-4294967295) filter-list prefix NAME out .. clicmd:: area (0-4294967295) filter-list prefix NAME out .. index:: no area A.B.C.D filter-list prefix NAME in .. clicmd:: no area A.B.C.D filter-list prefix NAME in .. index:: no area A.B.C.D filter-list prefix NAME out .. clicmd:: no area A.B.C.D filter-list prefix NAME out .. index:: no area (0-4294967295) filter-list prefix NAME in .. clicmd:: no area (0-4294967295) filter-list prefix NAME in .. index:: no area (0-4294967295) filter-list prefix NAME out .. clicmd:: no area (0-4294967295) filter-list prefix NAME out Filtering Type-3 summary-LSAs to/from area using prefix lists. This command makes sense in ABR only. .. index:: area A.B.C.D authentication .. clicmd:: area A.B.C.D authentication .. index:: area (0-4294967295) authentication .. clicmd:: area (0-4294967295) authentication .. index:: no area A.B.C.D authentication .. clicmd:: no area A.B.C.D authentication .. index:: no area (0-4294967295) authentication .. clicmd:: no area (0-4294967295) authentication Specify that simple password authentication should be used for the given area. .. index:: area A.B.C.D authentication message-digest .. clicmd:: area A.B.C.D authentication message-digest .. index:: area (0-4294967295) authentication message-digest .. clicmd:: area (0-4294967295) authentication message-digest Specify that OSPF packets must be authenticated with MD5 HMACs within the given area. Keying material must also be configured on a per-interface basis (:clicmd:`ip ospf message-digest-key`). MD5 authentication may also be configured on a per-interface basis (:clicmd:`ip ospf authentication message-digest`). Such per-interface settings will override any per-area authentication setting. .. _ospf-interface: Interfaces ---------- .. index:: ip ospf area AREA [ADDR] .. clicmd:: ip ospf area AREA [ADDR] .. index:: no ip ospf area [ADDR] .. clicmd:: no ip ospf area [ADDR] Enable OSPF on the interface, optionally restricted to just the IP address given by `ADDR`, putting it in the `AREA` area. Per interface area settings take precedence to network commands (:clicmd:`network A.B.C.D/M area A.B.C.D`). If you have a lot of interfaces, and/or a lot of subnets, then enabling OSPF via this command may result in a slight performance improvement. .. index:: ip ospf authentication-key AUTH_KEY .. clicmd:: ip ospf authentication-key AUTH_KEY .. index:: no ip ospf authentication-key .. clicmd:: no ip ospf authentication-key Set OSPF authentication key to a simple password. After setting `AUTH_KEY`, all OSPF packets are authenticated. `AUTH_KEY` has length up to 8 chars. Simple text password authentication is insecure and deprecated in favour of MD5 HMAC authentication. .. index:: ip ospf authentication message-digest .. clicmd:: ip ospf authentication message-digest Specify that MD5 HMAC authentication must be used on this interface. MD5 keying material must also be configured. Overrides any authentication enabled on a per-area basis (:clicmd:`area A.B.C.D authentication message-digest`) Note that OSPF MD5 authentication requires that time never go backwards (correct time is NOT important, only that it never goes backwards), even across resets, if ospfd is to be able to promptly reestablish adjacencies with its neighbours after restarts/reboots. The host should have system time be set at boot from an external or non-volatile source (e.g. battery backed clock, NTP, etc.) or else the system clock should be periodically saved to non-volatile storage and restored at boot if MD5 authentication is to be expected to work reliably. .. index:: ip ospf message-digest-key KEYID md5 KEY .. clicmd:: ip ospf message-digest-key KEYID md5 KEY .. index:: no ip ospf message-digest-key .. clicmd:: no ip ospf message-digest-key Set OSPF authentication key to a cryptographic password. The cryptographic algorithm is MD5. KEYID identifies secret key used to create the message digest. This ID is part of the protocol and must be consistent across routers on a link. KEY is the actual message digest key, of up to 16 chars (larger strings will be truncated), and is associated with the given KEYID. .. index:: ip ospf cost (1-65535) .. clicmd:: ip ospf cost (1-65535) .. index:: no ip ospf cost .. clicmd:: no ip ospf cost Set link cost for the specified interface. The cost value is set to router-LSA's metric field and used for SPF calculation. .. index:: ip ospf dead-interval (1-65535) .. clicmd:: ip ospf dead-interval (1-65535) .. index:: ip ospf dead-interval minimal hello-multiplier (2-20) .. clicmd:: ip ospf dead-interval minimal hello-multiplier (2-20) .. index:: no ip ospf dead-interval .. clicmd:: no ip ospf dead-interval Set number of seconds for RouterDeadInterval timer value used for Wait Timer and Inactivity Timer. This value must be the same for all routers attached to a common network. The default value is 40 seconds. If 'minimal' is specified instead, then the dead-interval is set to 1 second and one must specify a hello-multiplier. The hello-multiplier specifies how many Hellos to send per second, from 2 (every 500ms) to 20 (every 50ms). Thus one can have 1s convergence time for OSPF. If this form is specified, then the hello-interval advertised in Hello packets is set to 0 and the hello-interval on received Hello packets is not checked, thus the hello-multiplier need NOT be the same across multiple routers on a common link. .. index:: ip ospf hello-interval (1-65535) .. clicmd:: ip ospf hello-interval (1-65535) .. index:: no ip ospf hello-interval .. clicmd:: no ip ospf hello-interval Set number of seconds for HelloInterval timer value. Setting this value, Hello packet will be sent every timer value seconds on the specified interface. This value must be the same for all routers attached to a common network. The default value is 10 seconds. This command has no effect if :clicmd:`ip ospf dead-interval minimal hello-multiplier (2-20)` is also specified for the interface. .. index:: ip ospf network (broadcast|non-broadcast|point-to-multipoint|point-to-point) .. clicmd:: ip ospf network (broadcast|non-broadcast|point-to-multipoint|point-to-point) .. index:: no ip ospf network .. clicmd:: no ip ospf network Set explicitly network type for specified interface. .. index:: ip ospf priority (0-255) .. clicmd:: ip ospf priority (0-255) .. index:: no ip ospf priority .. clicmd:: no ip ospf priority Set RouterPriority integer value. The router with the highest priority will be more eligible to become Designated Router. Setting the value to 0, makes the router ineligible to become Designated Router. The default value is 1. .. index:: ip ospf retransmit-interval (1-65535) .. clicmd:: ip ospf retransmit-interval (1-65535) .. index:: no ip ospf retransmit interval .. clicmd:: no ip ospf retransmit interval Set number of seconds for RxmtInterval timer value. This value is used when retransmitting Database Description and Link State Request packets. The default value is 5 seconds. .. index:: ip ospf transmit-delay .. clicmd:: ip ospf transmit-delay .. index:: no ip ospf transmit-delay .. clicmd:: no ip ospf transmit-delay Set number of seconds for InfTransDelay value. LSAs' age should be incremented by this value when transmitting. The default value is 1 second. .. index:: ip ospf area (A.B.C.D|(0-4294967295)) .. clicmd:: ip ospf area (A.B.C.D|(0-4294967295)) .. index:: no ip ospf area .. clicmd:: no ip ospf area Enable ospf on an interface and set associated area. .. _redistribute-routes-to-ospf: Redistribution -------------- .. index:: redistribute (kernel|connected|static|rip|bgp) .. clicmd:: redistribute (kernel|connected|static|rip|bgp) .. index:: redistribute (kernel|connected|static|rip|bgp) ROUTE-MAP .. clicmd:: redistribute (kernel|connected|static|rip|bgp) ROUTE-MAP .. index:: redistribute (kernel|connected|static|rip|bgp) metric-type (1|2) .. clicmd:: redistribute (kernel|connected|static|rip|bgp) metric-type (1|2) .. index:: redistribute (kernel|connected|static|rip|bgp) metric-type (1|2) route-map WORD .. clicmd:: redistribute (kernel|connected|static|rip|bgp) metric-type (1|2) route-map WORD .. index:: redistribute (kernel|connected|static|rip|bgp) metric (0-16777214) .. clicmd:: redistribute (kernel|connected|static|rip|bgp) metric (0-16777214) .. index:: redistribute (kernel|connected|static|rip|bgp) metric (0-16777214) route-map WORD .. clicmd:: redistribute (kernel|connected|static|rip|bgp) metric (0-16777214) route-map WORD .. index:: redistribute (kernel|connected|static|rip|bgp) metric-type (1|2) metric (0-16777214) .. clicmd:: redistribute (kernel|connected|static|rip|bgp) metric-type (1|2) metric (0-16777214) .. index:: redistribute (kernel|connected|static|rip|bgp) metric-type (1|2) metric (0-16777214) route-map WORD .. clicmd:: redistribute (kernel|connected|static|rip|bgp) metric-type (1|2) metric (0-16777214) route-map WORD .. index:: no redistribute (kernel|connected|static|rip|bgp) .. clicmd:: no redistribute (kernel|connected|static|rip|bgp) .. _ospf-redistribute: Redistribute routes of the specified protocol or kind into OSPF, with the metric type and metric set if specified, filtering the routes using the given route-map if specified. Redistributed routes may also be filtered with distribute-lists, see :ref:`ospf distribute-list configuration `. Redistributed routes are distributed as into OSPF as Type-5 External LSAs into links to areas that accept external routes, Type-7 External LSAs for NSSA areas and are not redistributed at all into Stub areas, where external routes are not permitted. Note that for connected routes, one may instead use the `passive-interface` configuration. .. seealso:: clicmd:`passive-interface INTERFACE`. .. index:: default-information originate .. clicmd:: default-information originate .. index:: default-information originate metric (0-16777214) .. clicmd:: default-information originate metric (0-16777214) .. index:: default-information originate metric (0-16777214) metric-type (1|2) .. clicmd:: default-information originate metric (0-16777214) metric-type (1|2) .. index:: default-information originate metric (0-16777214) metric-type (1|2) route-map WORD .. clicmd:: default-information originate metric (0-16777214) metric-type (1|2) route-map WORD .. index:: default-information originate always .. clicmd:: default-information originate always .. index:: default-information originate always metric (0-16777214) .. clicmd:: default-information originate always metric (0-16777214) .. index:: default-information originate always metric (0-16777214) metric-type (1|2) .. clicmd:: default-information originate always metric (0-16777214) metric-type (1|2) .. index:: default-information originate always metric (0-16777214) metric-type (1|2) route-map WORD .. clicmd:: default-information originate always metric (0-16777214) metric-type (1|2) route-map WORD .. index:: no default-information originate .. clicmd:: no default-information originate Originate an AS-External (type-5) LSA describing a default route into all external-routing capable areas, of the specified metric and metric type. If the 'always' keyword is given then the default is always advertised, even when there is no default present in the routing table. .. index:: distribute-list NAME out (kernel|connected|static|rip|ospf .. clicmd:: distribute-list NAME out (kernel|connected|static|rip|ospf .. index:: no distribute-list NAME out (kernel|connected|static|rip|ospf .. clicmd:: no distribute-list NAME out (kernel|connected|static|rip|ospf .. _ospf-distribute-list: Apply the access-list filter, NAME, to redistributed routes of the given type before allowing the routes to redistributed into OSPF (:ref:`ospf redistribution `). .. index:: default-metric (0-16777214) .. clicmd:: default-metric (0-16777214) .. index:: no default-metric .. clicmd:: no default-metric .. index:: distance (1-255) .. clicmd:: distance (1-255) .. index:: no distance (1-255) .. clicmd:: no distance (1-255) .. index:: distance ospf (intra-area|inter-area|external) (1-255) .. clicmd:: distance ospf (intra-area|inter-area|external) (1-255) .. index:: no distance ospf .. clicmd:: no distance ospf .. index:: router zebra .. clicmd:: router zebra .. index:: no router zebra .. clicmd:: no router zebra .. _showing-ospf-information: Showing Information =================== .. _show-ip-ospf: .. index:: show ip ospf .. clicmd:: show ip ospf Show information on a variety of general OSPF and area state and configuration information. .. index:: show ip ospf interface [INTERFACE] .. clicmd:: show ip ospf interface [INTERFACE] Show state and configuration of OSPF the specified interface, or all interfaces if no interface is given. .. index:: show ip ospf neighbor .. clicmd:: show ip ospf neighbor .. index:: show ip ospf neighbor INTERFACE .. clicmd:: show ip ospf neighbor INTERFACE .. index:: show ip ospf neighbor detail .. clicmd:: show ip ospf neighbor detail .. index:: show ip ospf neighbor INTERFACE detail .. clicmd:: show ip ospf neighbor INTERFACE detail .. index:: show ip ospf database .. clicmd:: show ip ospf database .. index:: show ip ospf database (asbr-summary|external|network|router|summary) .. clicmd:: show ip ospf database (asbr-summary|external|network|router|summary) .. index:: show ip ospf database (asbr-summary|external|network|router|summary) LINK-STATE-ID .. clicmd:: show ip ospf database (asbr-summary|external|network|router|summary) LINK-STATE-ID .. index:: show ip ospf database (asbr-summary|external|network|router|summary) LINK-STATE-ID adv-router ADV-ROUTER .. clicmd:: show ip ospf database (asbr-summary|external|network|router|summary) LINK-STATE-ID adv-router ADV-ROUTER .. index:: show ip ospf database (asbr-summary|external|network|router|summary) adv-router ADV-ROUTER .. clicmd:: show ip ospf database (asbr-summary|external|network|router|summary) adv-router ADV-ROUTER .. index:: show ip ospf database (asbr-summary|external|network|router|summary) LINK-STATE-ID self-originate .. clicmd:: show ip ospf database (asbr-summary|external|network|router|summary) LINK-STATE-ID self-originate .. index:: show ip ospf database (asbr-summary|external|network|router|summary) self-originate .. clicmd:: show ip ospf database (asbr-summary|external|network|router|summary) self-originate .. index:: show ip ospf database max-age .. clicmd:: show ip ospf database max-age .. index:: show ip ospf database self-originate .. clicmd:: show ip ospf database self-originate .. index:: show ip ospf route .. clicmd:: show ip ospf route Show the OSPF routing table, as determined by the most recent SPF calculation. .. _opaque-lsa: Opaque LSA ========== .. index:: ospf opaque-lsa .. clicmd:: ospf opaque-lsa .. index:: capability opaque .. clicmd:: capability opaque .. index:: no ospf opaque-lsa .. clicmd:: no ospf opaque-lsa .. index:: no capability opaque .. clicmd:: no capability opaque *ospfd* supports Opaque LSA (:rfc:`2370`) as partial support for MPLS Traffic Engineering LSAs. The opaque-lsa capability must be enabled in the configuration. An alternate command could be "mpls-te on" (:ref:`ospf-traffic-engineering`). Note that FRR offers only partial support for some of the routing protocol extensions that are used with MPLS-TE; it does not support a complete RSVP-TE solution. .. index:: show ip ospf database (opaque-link|opaque-area|opaque-external) .. clicmd:: show ip ospf database (opaque-link|opaque-area|opaque-external) .. index:: show ip ospf database (opaque-link|opaque-area|opaque-external) LINK-STATE-ID .. clicmd:: show ip ospf database (opaque-link|opaque-area|opaque-external) LINK-STATE-ID .. index:: show ip ospf database (opaque-link|opaque-area|opaque-external) LINK-STATE-ID adv-router ADV-ROUTER .. clicmd:: show ip ospf database (opaque-link|opaque-area|opaque-external) LINK-STATE-ID adv-router ADV-ROUTER .. index:: show ip ospf database (opaque-link|opaque-area|opaque-external) adv-router ADV-ROUTER .. clicmd:: show ip ospf database (opaque-link|opaque-area|opaque-external) adv-router ADV-ROUTER .. index:: show ip ospf database (opaque-link|opaque-area|opaque-external) LINK-STATE-ID self-originate .. clicmd:: show ip ospf database (opaque-link|opaque-area|opaque-external) LINK-STATE-ID self-originate .. index:: show ip ospf database (opaque-link|opaque-area|opaque-external) self-originate .. clicmd:: show ip ospf database (opaque-link|opaque-area|opaque-external) self-originate Show Opaque LSA from the database. .. _ospf-traffic-engineering: Traffic Engineering =================== .. note:: At this time, FRR offers partial support for some of the routing protocol extensions that can be used with MPLS-TE. FRR does not support a complete RSVP-TE solution currently. .. index:: mpls-te on .. clicmd:: mpls-te on .. index:: no mpls-te .. clicmd:: no mpls-te Enable Traffic Engineering LSA flooding. .. index:: mpls-te router-address .. clicmd:: mpls-te router-address Configure stable IP address for MPLS-TE. This IP address is then advertise in Opaque LSA Type-10 TLV=1 (TE) option 1 (Router-Address). .. index:: mpls-te inter-as area |as .. clicmd:: mpls-te inter-as area |as .. index:: no mpls-te inter-as .. clicmd:: no mpls-te inter-as Enable :rfc:`5392` support - Inter-AS TE v2 - to flood Traffic Engineering parameters of Inter-AS link. 2 modes are supported: AREA and AS; LSA are flood in AREA with Opaque Type-10, respectively in AS with Opaque Type-11. In all case, Opaque-LSA TLV=6. .. index:: show ip ospf mpls-te interface .. clicmd:: show ip ospf mpls-te interface .. index:: show ip ospf mpls-te interface INTERFACE .. clicmd:: show ip ospf mpls-te interface INTERFACE Show MPLS Traffic Engineering parameters for all or specified interface. .. index:: show ip ospf mpls-te router .. clicmd:: show ip ospf mpls-te router Show Traffic Engineering router parameters. .. _router-information: Router Information ================== .. index:: router-info [as | area] .. clicmd:: router-info [as | area] .. index:: no router-info .. clicmd:: no router-info Enable Router Information (:rfc:`4970`) LSA advertisement with AS scope (default) or Area scope flooding when area is specified. Old syntax `router-info area ` is always supported but mark as deprecated as the area ID is no more necessary. Indeed, router information support multi-area and detect automatically the areas. .. index:: pce address .. clicmd:: pce address .. index:: no pce address .. clicmd:: no pce address .. index:: pce domain as (0-65535) .. clicmd:: pce domain as (0-65535) .. index:: no pce domain as (0-65535) .. clicmd:: no pce domain as (0-65535) .. index:: pce neighbor as (0-65535) .. clicmd:: pce neighbor as (0-65535) .. index:: no pce neighbor as (0-65535) .. clicmd:: no pce neighbor as (0-65535) .. index:: pce flag BITPATTERN .. clicmd:: pce flag BITPATTERN .. index:: no pce flag .. clicmd:: no pce flag .. index:: pce scope BITPATTERN .. clicmd:: pce scope BITPATTERN .. index:: no pce scope .. clicmd:: no pce scope The commands are conform to :rfc:`5088` and allow OSPF router announce Path Computation Element (PCE) capabilities through the Router Information (RI) LSA. Router Information must be enable prior to this. The command set/unset respectively the PCE IP address, Autonomous System (AS) numbers of controlled domains, neighbor ASs, flag and scope. For flag and scope, please refer to :rfc`5088` for the BITPATTERN recognition. Multiple 'pce neighbor' command could be specified in order to specify all PCE neighbours. .. index:: show ip ospf router-info .. clicmd:: show ip ospf router-info Show Router Capabilities flag. .. index:: show ip ospf router-info pce .. clicmd:: show ip ospf router-info pce Show Router Capabilities PCE parameters. .. _debugging-ospf: Segment Routing =============== This is an EXPERIMENTAL support of Segment Routing as per draft `draft-ietf-ospf-segment-routing-extensions-24.txt` for MPLS dataplane. .. index:: [no] segment-routing on .. clicmd:: [no] segment-routing on Enable Segment Routing. Even if this also activate routing information support, it is preferable to also activate routing information, and set accordingly the Area or AS flooding. .. index:: [no] segment-routing global-block (0-1048575) (0-1048575) .. clicmd:: [no] segment-routing global-block (0-1048575) (0-1048575) Fix the Segment Routing Global Block i.e. the label range used by MPLS to store label in the MPLS FIB. .. index:: [no] segment-routing node-msd (1-16) .. clicmd:: [no] segment-routing node-msd (1-16) Fix the Maximum Stack Depth supported by the router. The value depend of the MPLS dataplane. E.g. for Linux kernel, since version 4.13 it is 32. .. index:: [no] segment-routing prefix A.B.C.D/M index (0-65535) [no-php-flag] .. clicmd:: [no] segment-routing prefix A.B.C.D/M index (0-65535) [no-php-flag] Set the Segment Routing index for the specified prefix. Note that, only prefix with /32 corresponding to a loopback interface are currently supported. The 'no-php-flag' means NO Penultimate Hop Popping that allows SR node to request to its neighbor to not pop the label. .. index:: show ip ospf database segment-routing [json] .. clicmd:: show ip ospf database segment-routing [json] Show Segment Routing Data Base, all SR nodes, specific advertised router or self router. Optional JSON output can be obtained by appending 'json' to the end of the command. Debugging OSPF ============== .. index:: debug ospf packet (hello|dd|ls-request|ls-update|ls-ack|all) (send|recv) [detail] .. clicmd:: debug ospf packet (hello|dd|ls-request|ls-update|ls-ack|all) (send|recv) [detail] .. index:: no debug ospf packet (hello|dd|ls-request|ls-update|ls-ack|all) (send|recv) [detail] .. clicmd:: no debug ospf packet (hello|dd|ls-request|ls-update|ls-ack|all) (send|recv) [detail] Dump Packet for debugging .. index:: debug ospf ism .. clicmd:: debug ospf ism .. index:: debug ospf ism (status|events|timers) .. clicmd:: debug ospf ism (status|events|timers) .. index:: no debug ospf ism .. clicmd:: no debug ospf ism .. index:: no debug ospf ism (status|events|timers) .. clicmd:: no debug ospf ism (status|events|timers) Show debug information of Interface State Machine .. index:: debug ospf nsm .. clicmd:: debug ospf nsm .. index:: debug ospf nsm (status|events|timers) .. clicmd:: debug ospf nsm (status|events|timers) .. index:: no debug ospf nsm .. clicmd:: no debug ospf nsm .. index:: no debug ospf nsm (status|events|timers) .. clicmd:: no debug ospf nsm (status|events|timers) Show debug information of Network State Machine .. index:: debug ospf event .. clicmd:: debug ospf event .. index:: no debug ospf event .. clicmd:: no debug ospf event Show debug information of OSPF event .. index:: debug ospf nssa .. clicmd:: debug ospf nssa .. index:: no debug ospf nssa .. clicmd:: no debug ospf nssa Show debug information about Not So Stub Area .. index:: debug ospf lsa .. clicmd:: debug ospf lsa .. index:: debug ospf lsa (generate|flooding|refresh) .. clicmd:: debug ospf lsa (generate|flooding|refresh) .. index:: no debug ospf lsa .. clicmd:: no debug ospf lsa .. index:: no debug ospf lsa (generate|flooding|refresh) .. clicmd:: no debug ospf lsa (generate|flooding|refresh) Show debug detail of Link State messages .. index:: debug ospf te .. clicmd:: debug ospf te .. index:: no debug ospf te .. clicmd:: no debug ospf te Show debug information about Traffic Engineering LSA .. index:: debug ospf zebra .. clicmd:: debug ospf zebra .. index:: debug ospf zebra (interface|redistribute) .. clicmd:: debug ospf zebra (interface|redistribute) .. index:: no debug ospf zebra .. clicmd:: no debug ospf zebra .. index:: no debug ospf zebra (interface|redistribute) .. clicmd:: no debug ospf zebra (interface|redistribute) Show debug information of ZEBRA API .. index:: show debugging ospf .. clicmd:: show debugging ospf OSPF Configuration Examples =========================== A simple example, with MD5 authentication enabled: .. code-block:: frr ! interface bge0 ip ospf authentication message-digest ip ospf message-digest-key 1 md5 ABCDEFGHIJK ! router ospf network 192.168.0.0/16 area 0.0.0.1 area 0.0.0.1 authentication message-digest An :abbr:`ABR` router, with MD5 authentication and performing summarisation of networks between the areas: .. code-block:: frr ! password ABCDEF log file /var/log/frr/ospfd.log service advanced-vty ! interface eth0 ip ospf authentication message-digest ip ospf message-digest-key 1 md5 ABCDEFGHIJK ! interface ppp0 ! interface br0 ip ospf authentication message-digest ip ospf message-digest-key 2 md5 XYZ12345 ! router ospf ospf router-id 192.168.0.1 redistribute connected passive interface ppp0 network 192.168.0.0/24 area 0.0.0.0 network 10.0.0.0/16 area 0.0.0.0 network 192.168.1.0/24 area 0.0.0.1 area 0.0.0.0 authentication message-digest area 0.0.0.0 range 10.0.0.0/16 area 0.0.0.0 range 192.168.0.0/24 area 0.0.0.1 authentication message-digest area 0.0.0.1 range 10.2.0.0/16 ! A Traffic Engineering configuration, with Inter-ASv2 support. First, the :file:`zebra.conf` part: .. code-block:: frr interface eth0 ip address 198.168.1.1/24 link-params enable admin-grp 0xa1 metric 100 max-bw 1.25e+07 max-rsv-bw 1.25e+06 unrsv-bw 0 1.25e+06 unrsv-bw 1 1.25e+06 unrsv-bw 2 1.25e+06 unrsv-bw 3 1.25e+06 unrsv-bw 4 1.25e+06 unrsv-bw 5 1.25e+06 unrsv-bw 6 1.25e+06 unrsv-bw 7 1.25e+06 ! interface eth1 ip address 192.168.2.1/24 link-params enable metric 10 max-bw 1.25e+07 max-rsv-bw 1.25e+06 unrsv-bw 0 1.25e+06 unrsv-bw 1 1.25e+06 unrsv-bw 2 1.25e+06 unrsv-bw 3 1.25e+06 unrsv-bw 4 1.25e+06 unrsv-bw 5 1.25e+06 unrsv-bw 6 1.25e+06 unrsv-bw 7 1.25e+06 neighbor 192.168.2.2 as 65000 hostname HOSTNAME password PASSWORD log file /var/log/zebra.log ! interface eth0 ip address 198.168.1.1/24 link-params enable admin-grp 0xa1 metric 100 max-bw 1.25e+07 max-rsv-bw 1.25e+06 unrsv-bw 0 1.25e+06 unrsv-bw 1 1.25e+06 unrsv-bw 2 1.25e+06 unrsv-bw 3 1.25e+06 unrsv-bw 4 1.25e+06 unrsv-bw 5 1.25e+06 unrsv-bw 6 1.25e+06 unrsv-bw 7 1.25e+06 ! interface eth1 ip address 192.168.2.1/24 link-params enable metric 10 max-bw 1.25e+07 max-rsv-bw 1.25e+06 unrsv-bw 0 1.25e+06 unrsv-bw 1 1.25e+06 unrsv-bw 2 1.25e+06 unrsv-bw 3 1.25e+06 unrsv-bw 4 1.25e+06 unrsv-bw 5 1.25e+06 unrsv-bw 6 1.25e+06 unrsv-bw 7 1.25e+06 neighbor 192.168.2.2 as 65000 Then the :file:`ospfd.conf` itself: .. code-block:: frr hostname HOSTNAME password PASSWORD log file /var/log/ospfd.log ! ! interface eth0 ip ospf hello-interval 60 ip ospf dead-interval 240 ! interface eth1 ip ospf hello-interval 60 ip ospf dead-interval 240 ! ! router ospf ospf router-id 192.168.1.1 network 192.168.0.0/16 area 1 ospf opaque-lsa mpls-te mpls-te router-address 192.168.1.1 mpls-te inter-as area 1 ! line vty A router information example with PCE advertisement: .. code-block:: frr ! router ospf ospf router-id 192.168.1.1 network 192.168.0.0/16 area 1 capability opaque mpls-te mpls-te router-address 192.168.1.1 router-info area 0.0.0.1 pce address 192.168.1.1 pce flag 0x80 pce domain as 65400 pce neighbor as 65500 pce neighbor as 65200 pce scope 0x80 ! frr-7.2.1/doc/user/overview.rst0000644000000000000000000005052613610377563013347 00000000000000.. _overview: ******** Overview ******** `FRR`_ is a routing software package that provides TCP/IP based routing services with routing protocols support such as BGP, RIP, OSPF, IS-IS and more (see :ref:`supported-protocols`). FRR also supports special BGP Route Reflector and Route Server behavior. In addition to traditional IPv4 routing protocols, FRR also supports IPv6 routing protocols. With an SNMP daemon that supports the AgentX protocol, FRR provides routing protocol MIB read-only access (:ref:`snmp-support`). FRR uses an advanced software architecture to provide you with a high quality, multi server routing engine. FRR has an interactive user interface for each routing protocol and supports common client commands. Due to this design, you can add new protocol daemons to FRR easily. You can use FRR library as your program's client user interface. FRR is distributed under the GNU General Public License. FRR is a fork of `Quagga `_. .. _about-frr: About FRR ========= Today, TCP/IP networks are covering all of the world. The Internet has been deployed in many countries, companies, and to the home. When you connect to the Internet your packet will pass many routers which have TCP/IP routing functionality. A system with FRR installed acts as a dedicated router. With FRR, your machine exchanges routing information with other routers using routing protocols. FRR uses this information to update the kernel routing table so that the right data goes to the right place. You can dynamically change the configuration and you may view routing table information from the FRR terminal interface. Adding to routing protocol support, FRR can setup interface's flags, interface's address, static routes and so on. If you have a small network, or a stub network, or xDSL connection, configuring the FRR routing software is very easy. The only thing you have to do is to set up the interfaces and put a few commands about static routes and/or default routes. If the network is rather large, or if the network structure changes frequently, you will want to take advantage of FRR's dynamic routing protocol support for protocols such as RIP, OSPF, IS-IS or BGP. Traditionally, UNIX based router configuration is done by *ifconfig* and *route* commands. Status of routing table is displayed by *netstat* utility. Almost of these commands work only if the user has root privileges. FRR has a different system administration method. There are two user modes in FRR. One is normal mode, the other is enable mode. Normal mode user can only view system status, enable mode user can change system configuration. This UNIX account independent feature will be great help to the router administrator. Currently, FRR supports common unicast routing protocols, that is BGP, OSPF, RIP and IS-IS. Upcoming for MPLS support, an implementation of LDP is currently being prepared for merging. Implementations of BFD and PIM-SSM (IPv4) also exist, but are not actively being worked on. The ultimate goal of the FRR project is making a production-grade, high quality, featureful and free IP routing software suite. System Architecture =================== .. index:: System architecture .. index:: Software architecture .. index:: Software internals Traditional routing software is made as a one process program which provides all of the routing protocol functionalities. FRR takes a different approach. FRR is a suite of daemons that work together to build the routing table. There is a daemon for each major supported protocol as well as a middleman daemon (*Zebra*) which serves as the broker between these daemons and the kernel. This architecture allows for high resiliency, since an error, crash or exploit in one protocol daemon will generally not affect the others. It is also flexible and extensible since the modularity makes it easy to implement new protocols and tie them into the suite. An illustration of the large scale architecture is given below. :: +----+ +----+ +-----+ +----+ +----+ +----+ +-----+ |bgpd| |ripd| |ospfd| |ldpd| |pbrd| |pimd| |.....| +----+ +----+ +-----+ +----+ +----+ +----+ +-----+ | | | | | | | +----v-------v--------v-------v-------v-------v--------v | | | Zebra | | | +------------------------------------------------------+ | | | | | | +------v------+ +---------v--------+ +------v------+ | | | | | | | *NIX Kernel | | Remote dataplane | | ........... | | | | | | | +-------------+ +------------------+ +-------------+ The multi-process architecture brings extensibility, modularity and maintainability. All of the FRR daemons can be managed through a single integrated user interface shell called *vtysh*. *vtysh* connects to each daemon through a UNIX domain socket and then works as a proxy for user input. In addition to a unified frontend, *vtysh* also provides the ability to configure all the daemons using a single configuration file through the integrated configuration mode avoiding the problem of having to maintain a separate configuration file for each daemon. Supported Platforms =================== .. index:: Supported platforms .. index:: FRR on other systems .. index:: Compatibility with other systems .. index:: Operating systems that support FRR Currently FRR supports GNU/Linux and BSD. Porting FRR to other platforms is not too difficult as platform dependent code should be mostly limited to the *Zebra* daemon. Protocol daemons are largely platform independent. Please let us know if you can get FRR to run on a platform which is not listed below: - GNU/Linux - FreeBSD - NetBSD - OpenBSD Versions of these platforms that are older than around 2 years from the point of their original release (in case of GNU/Linux, this is since the kernel's release on https://kernel.org/) may need some work. Similarly, the following platforms may work with some effort: - Solaris - MacOS Recent versions of the following compilers are well tested: - GNU's GCC - LLVM's Clang - Intel's ICC .. _supported-protocols: Supported Protocols vs. Platform ================================ The following table lists all protocols cross-refrenced to all operating systems that have at least CI build tests. Note that for features, only features with system dependencies are included here. .. role:: mark .. comment - the :mark:`X` pieces mesh with a little bit of JavaScript and CSS in _static/overrides.{js,css} respectively. The JS code looks at the presence of the 'Y' 'N' '≥' '†' or 'CP' strings. This seemed to be the best / least intrusive way of getting a nice table in HTML. The table will look somewhat shoddy on other sphinx targets like PDF or info (but should still be readable.) +-----------------------------------+----------------+--------------+------------+------------+------------+ | Daemon / Feature | Linux | OpenBSD | FreeBSD | NetBSD | Solaris | +===================================+================+==============+============+============+============+ | **FRR Core** | | | | | | +-----------------------------------+----------------+--------------+------------+------------+------------+ | `zebra` | :mark:`Y` | :mark:`Y` | :mark:`Y` | :mark:`Y` | :mark:`Y` | +-----------------------------------+----------------+--------------+------------+------------+------------+ | VRF | :mark:`≥4.8` | :mark:`N` | :mark:`N` | :mark:`N` | :mark:`N` | +-----------------------------------+----------------+--------------+------------+------------+------------+ | MPLS | :mark:`≥4.5` | :mark:`Y` | :mark:`N` | :mark:`N` | :mark:`N` | +-----------------------------------+----------------+--------------+------------+------------+------------+ | `pbrd` (Policy Routing) | :mark:`Y` | :mark:`N` | :mark:`N` | :mark:`N` | :mark:`N` | +-----------------------------------+----------------+--------------+------------+------------+------------+ | **WAN / Carrier protocols** | | | | | | +-----------------------------------+----------------+--------------+------------+------------+------------+ | `bgpd` (BGP) | :mark:`Y` | :mark:`Y` | :mark:`Y` | :mark:`Y` | :mark:`Y` | +-----------------------------------+----------------+--------------+------------+------------+------------+ | VRF / L3VPN | :mark:`≥4.8` | :mark:`CP` | :mark:`CP` | :mark:`CP` | :mark:`CP` | | | :mark:`†4.3` | | | | | +-----------------------------------+----------------+--------------+------------+------------+------------+ | EVPN | :mark:`≥4.18` | :mark:`CP` | :mark:`CP` | :mark:`CP` | :mark:`CP` | | | :mark:`†4.9` | | | | | +-----------------------------------+----------------+--------------+------------+------------+------------+ | VNC (Virtual Network Control) | :mark:`CP` | :mark:`CP` | :mark:`CP` | :mark:`CP` | :mark:`CP` | +-----------------------------------+----------------+--------------+------------+------------+------------+ | Flowspec | :mark:`CP` | :mark:`CP` | :mark:`CP` | :mark:`CP` | :mark:`CP` | +-----------------------------------+----------------+--------------+------------+------------+------------+ | `ldpd` (LDP) | :mark:`≥4.5` | :mark:`Y` | :mark:`N` | :mark:`N` | :mark:`N` | +-----------------------------------+----------------+--------------+------------+------------+------------+ | VPWS / PW | :mark:`N` | :mark:`≥5.8` | :mark:`N` | :mark:`N` | :mark:`N` | +-----------------------------------+----------------+--------------+------------+------------+------------+ | VPLS | :mark:`N` | :mark:`≥5.8` | :mark:`N` | :mark:`N` | :mark:`N` | +-----------------------------------+----------------+--------------+------------+------------+------------+ | `nhrpd` (NHRP) | :mark:`Y` | :mark:`N` | :mark:`N` | :mark:`N` | :mark:`N` | +-----------------------------------+----------------+--------------+------------+------------+------------+ | **Link-State Routing** | | | | | | +-----------------------------------+----------------+--------------+------------+------------+------------+ | `ospfd` (OSPFv2) | :mark:`Y` | :mark:`Y` | :mark:`Y` | :mark:`Y` | :mark:`Y` | +-----------------------------------+----------------+--------------+------------+------------+------------+ | Segment Routing | :mark:`≥4.12` | :mark:`N` | :mark:`N` | :mark:`N` | :mark:`N` | +-----------------------------------+----------------+--------------+------------+------------+------------+ | `ospf6d` (OSPFv3) | :mark:`Y` | :mark:`Y` | :mark:`Y` | :mark:`Y` | :mark:`Y` | +-----------------------------------+----------------+--------------+------------+------------+------------+ | `isisd` (IS-IS) | :mark:`Y` | :mark:`Y` | :mark:`Y` | :mark:`Y` | :mark:`Y` | +-----------------------------------+----------------+--------------+------------+------------+------------+ | **Distance-Vector Routing** | | | | | | +-----------------------------------+----------------+--------------+------------+------------+------------+ | `ripd` (RIPv2) | :mark:`Y` | :mark:`Y` | :mark:`Y` | :mark:`Y` | :mark:`Y` | +-----------------------------------+----------------+--------------+------------+------------+------------+ | `ripngd` (RIPng) | :mark:`Y` | :mark:`Y` | :mark:`Y` | :mark:`Y` | :mark:`Y` | +-----------------------------------+----------------+--------------+------------+------------+------------+ | `babeld` (BABEL) | :mark:`Y` | :mark:`Y` | :mark:`Y` | :mark:`Y` | :mark:`Y` | +-----------------------------------+----------------+--------------+------------+------------+------------+ | `eigrpd` (EIGRP) | :mark:`Y` | :mark:`Y` | :mark:`Y` | :mark:`Y` | :mark:`Y` | +-----------------------------------+----------------+--------------+------------+------------+------------+ | **Multicast Routing** | | | | | | +-----------------------------------+----------------+--------------+------------+------------+------------+ | `pimd` (PIM) | :mark:`≥4.18` | :mark:`N` | :mark:`Y` | :mark:`Y` | :mark:`Y` | +-----------------------------------+----------------+--------------+------------+------------+------------+ | SSM (Source Specific) | :mark:`Y` | :mark:`N` | :mark:`Y` | :mark:`Y` | :mark:`Y` | +-----------------------------------+----------------+--------------+------------+------------+------------+ | ASM (Any Source) | :mark:`Y` | :mark:`N` | :mark:`N` | :mark:`N` | :mark:`N` | +-----------------------------------+----------------+--------------+------------+------------+------------+ | EVPN BUM Forwarding | :mark:`≥5.0` | :mark:`N` | :mark:`N` | :mark:`N` | :mark:`N` | +-----------------------------------+----------------+--------------+------------+------------+------------+ The indicators have the following semantics: * :mark:`Y` - daemon/feature fully functional * :mark:`≥X.X` - fully functional with kernel version X.X or newer * :mark:`†X.X` - restricted functionality or impaired performance with kernel version X.X or newer * :mark:`CP` - control plane only (i.e. BGP route server / route reflector) * :mark:`N` - daemon/feature not supported by operating system Known Kernel Issues: ==================== - Linux v6 Route Replacement - Linux kernels before 4.11 can cause issues with v6 route deletion when you have ecmp routes installed into the kernel. This especially becomes apparent if the route is being transformed from one ecmp path to another. .. _supported-rfcs: Supported RFCs -------------- FRR implements the following RFCs: .. note:: This list is incomplete. - :rfc:`1058` :t:`Routing Information Protocol. C.L. Hedrick. Jun-01-1988.` - :rfc:`2082` :t:`RIP-2 MD5 Authentication. F. Baker, R. Atkinson. January 1997.` - :rfc:`2453` :t:`RIP Version 2. G. Malkin. November 1998.` - :rfc:`2080` :t:`RIPng for IPv6. G. Malkin, R. Minnear. January 1997.` - :rfc:`2328` :t:`OSPF Version 2. J. Moy. April 1998.` - :rfc:`2370` :t:`The OSPF Opaque LSA Option R. Coltun. July 1998.` - :rfc:`3101` :t:`The OSPF Not-So-Stubby Area (NSSA) Option P. Murphy. January 2003.` - :rfc:`2740` :t:`OSPF for IPv6. R. Coltun, D. Ferguson, J. Moy. December 1999.` - :rfc:`1771` :t:`A Border Gateway Protocol 4 (BGP-4). Y. Rekhter & T. Li. March 1995.` - :rfc:`1965` :t:`Autonomous System Confederations for BGP. P. Traina. June 1996.` - :rfc:`1997` :t:`BGP Communities Attribute. R. Chandra, P. Traina & T. Li. August 1996.` - :rfc:`2545` :t:`Use of BGP-4 Multiprotocol Extensions for IPv6 Inter-Domain Routing. P. Marques, F. Dupont. March 1999.` - :rfc:`2796` :t:`BGP Route Reflection An alternative to full mesh IBGP. T. Bates & R. Chandrasekeran. June 1996.` - :rfc:`2858` :t:`Multiprotocol Extensions for BGP-4. T. Bates, Y. Rekhter, R. Chandra, D. Katz. June 2000.` - :rfc:`2842` :t:`Capabilities Advertisement with BGP-4. R. Chandra, J. Scudder. May 2000.` - :rfc:`3137` :t:`OSPF Stub Router Advertisement, A. Retana, L. Nguyen, R. White, A. Zinin, D. McPherson. June 2001` - :rfc:`4447` :t:`Pseudowire Setup and Maintenance Using the Label Distribution Protocol (LDP), L. Martini, E. Rosen, N. El-Aawar, T. Smith, and G. Heron. April 2006.` - :rfc:`4762` :t:`Virtual Private LAN Service (VPLS) Using Label Distribution Protocol (LDP) Signaling, M. Lasserre and V. Kompella. January 2007.` - :rfc:`5036` :t:`LDP Specification, L. Andersson, I. Minei, and B. Thomas. October 2007.` - :rfc:`5561` :t:`LDP Capabilities, B. Thomas, K. Raza, S. Aggarwal, R. Aggarwal, and JL. Le Roux. July 2009.` - :rfc:`5918` :t:`Label Distribution Protocol (LDP) 'Typed Wildcard' Forward Equivalence Class (FEC), R. Asati, I. Minei, and B. Thomas. August 2010.` - :rfc:`5919` :t:`Signaling LDP Label Advertisement Completion, R. Asati, P. Mohapatra, E. Chen, and B. Thomas. August 2010.` - :rfc:`6667` :t:`LDP 'Typed Wildcard' Forwarding Equivalence Class (FEC) for PWid and Generalized PWid FEC Elements, K. Raza, S. Boutros, and C. Pignataro. July 2012.` - :rfc:`6720` :t:`The Generalized TTL Security Mechanism (GTSM) for the Label Distribution Protocol (LDP), C. Pignataro and R. Asati. August 2012.` - :rfc:`7552` :t:`Updates to LDP for IPv6, R. Asati, C. Pignataro, K. Raza, V. Manral, and R. Papneja. June 2015.` - :rfc:`5880` :t:`Bidirectional Forwarding Detection (BFD), D. Katz, D. Ward. June 2010` - :rfc:`5881` :t:`Bidirectional Forwarding Detection (BFD) for IPv4 and IPv6 (Single Hop), D. Katz, D. Ward. June 2010` - :rfc:`5883` :t:`Bidirectional Forwarding Detection (BFD) for Multihop Paths, D. Katz, D. Ward. June 2010` **When SNMP support is enabled, the following RFCs are also supported:** - :rfc:`1227` :t:`SNMP MUX protocol and MIB. M.T. Rose. May-01-1991.` - :rfc:`1657` :t:`Definitions of Managed Objects for the Fourth Version of the Border Gateway Protocol (BGP-4) using SMIv2. S. Willis, J. Burruss, J. Chu, Editor. July 1994.` - :rfc:`1724` :t:`RIP Version 2 MIB Extension. G. Malkin & F. Baker. November 1994.` - :rfc:`1850` :t:`OSPF Version 2 Management Information Base. F. Baker, R. Coltun. November 1995.` - :rfc:`2741` :t:`Agent Extensibility (AgentX) Protocol. M. Daniele, B. Wijnen. January 2000.` How to get FRR ============== The official FRR website is located at |PACKAGE_URL| and contains further information, as well as links to additional resources. Several distributions provide packages for FRR. Check your distribution's repositories to find out if a suitable version is available. Mailing Lists ============= .. index:: How to get in touch with FRR .. index:: Contact information .. index:: Mailing lists Italicized lists are private. +--------------------------------+------------------------------+ | Topic | List | +================================+==============================+ | Development | dev@lists.frrouting.org | +--------------------------------+------------------------------+ | Users & Operators | frog@lists.frrouting.org | +--------------------------------+------------------------------+ | Announcements | announce@lists.frrouting.org | +--------------------------------+------------------------------+ | *Security* | security@lists.frrouting.org | +--------------------------------+------------------------------+ | *Technical Steering Committee* | tsc@lists.frrouting.org | +--------------------------------+------------------------------+ The Development list is used to discuss and document general issues related to project development and governance. The public `Slack`_ instance and weekly technical meetings provide a higher bandwidth channel for discussions. The results of such discussions are reflected in updates, as appropriate, to code (i.e., merges), `GitHub issues`_ tracked issues, and for governance or process changes, updates to the Development list and either this file or information posted at `FRR`_. Bug Reports =========== For information on reporting bugs, please see :ref:`bug-reports`. .. _frr: |package-url| .. _github: https://github.com/frrouting/frr/ .. _github issues: https://github.com/frrouting/frr/issues .. _slack: https://frrouting.slack.com/ frr-7.2.1/doc/user/packet-dumps.rst0000644000000000000000000002767113610377563014103 00000000000000.. _packet-binary-dump-format: Packet Binary Dump Format ========================= FRR can dump routing protocol packets into a file with a binary format. It seems to be better that we share the MRT's header format for backward compatibility with MRT's dump logs. We should also define the binary format excluding the header, because we must support both IP v4 and v6 addresses as socket addresses and / or routing entries. In the last meeting, we discussed to have a version field in the header. But Masaki told us that we can define new 'type' value rather than having a 'version' field, and it seems to be better because we don't need to change header format. Here is the common header format. This is same as that of MRT.:: 0 1 2 3 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ | Time | +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ | Type | Subtype | +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ | Length | +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ If 'type' is PROTOCOL_BGP4MP_ET, the common header format will contain an additional microsecond field (RFC6396 2011).:: 0 1 2 3 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ | Time | +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ | Type | Subtype | +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ | Length | +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ | Microsecond | +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ If 'type' is PROTOCOL_BGP4MP, 'subtype' is BGP4MP_STATE_CHANGE, and Address Family == IP (version 4):: 0 1 2 3 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ | Source AS number | Destination AS number | +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ | Interface Index | Address Family | +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ | Source IP address | +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ | Destination IP address | +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ | Old State | New State | +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ Where State is the value defined in RFC1771. If 'type' is PROTOCOL_BGP4MP, 'subtype' is BGP4MP_STATE_CHANGE, and Address Family == IP version 6:: 0 1 2 3 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ | Source AS number | Destination AS number | +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ | Interface Index | Address Family | +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ | Source IP address | +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ | Source IP address (Cont'd) | +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ | Source IP address (Cont'd) | +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ | Source IP address (Cont'd) | +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ | Destination IP address | +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ | Destination IP address (Cont'd) | +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ | Destination IP address (Cont'd) | +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ | Destination IP address (Cont'd) | +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ | Old State | New State | +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ If 'type' is PROTOCOL_BGP4MP, 'subtype' is BGP4MP_MESSAGE, and Address Family == IP (version 4):: 0 1 2 3 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ | Source AS number | Destination AS number | +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ | Interface Index | Address Family | +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ | Source IP address | +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ | Destination IP address | +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ | BGP Message Packet | | | +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ Where BGP Message Packet is the whole contents of the BGP4 message including header portion. If 'type' is PROTOCOL_BGP4MP, 'subtype' is BGP4MP_MESSAGE, and Address Family == IP version 6:: 0 1 2 3 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ | Source AS number | Destination AS number | +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ | Interface Index | Address Family | +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ | Source IP address | +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ | Source IP address (Cont'd) | +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ | Source IP address (Cont'd) | +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ | Source IP address (Cont'd) | +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ | Destination IP address | +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ | Destination IP address (Cont'd) | +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ | Destination IP address (Cont'd) | +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ | Destination IP address (Cont'd) | +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ | BGP Message Packet | | | +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ If 'type' is PROTOCOL_BGP4MP, 'subtype' is BGP4MP_ENTRY, and Address Family == IP (version 4):: 0 1 2 3 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ | View # | Status | +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ | Time Last Change | +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ | Address Family | SAFI | Next-Hop-Len | +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ | Next Hop Address | +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ | Prefix Length | Address Prefix [variable] | +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ | Attribute Length | +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ | BGP Attribute [variable length] | +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ If 'type' is PROTOCOL_BGP4MP, 'subtype' is BGP4MP_ENTRY, and Address Family == IP version 6:: 0 1 2 3 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ | View # | Status | +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ | Time Last Change | +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ | Address Family | SAFI | Next-Hop-Len | +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ | Next Hop Address | +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ | Next Hop Address (Cont'd) | +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ | Next Hop Address (Cont'd) | +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ | Next Hop Address (Cont'd) | +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ | Prefix Length | Address Prefix [variable] | +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ | Address Prefix (cont'd) [variable] | +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ | Attribute Length | +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ | BGP Attribute [variable length] | +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ BGP4 Attribute must not contain MP_UNREACH_NLRI. If BGP Attribute has MP_REACH_NLRI field, it must has zero length NLRI, e.g., MP_REACH_NLRI has only Address Family, SAFI and next-hop values. If 'type' is PROTOCOL_BGP4MP and 'subtype' is BGP4MP_SNAPSHOT:: 0 1 2 3 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ | View # | File Name [variable] | +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ The file specified in "File Name" contains all routing entries, which are in the format of ``subtype == BGP4MP_ENTRY``. :: Constants: /\* type value \*/ #define MSG_PROTOCOL_BGP4MP 16 #define MSG_PROTOCOL_BGP4MP_ET 17 /\* subtype value \*/ #define BGP4MP_STATE_CHANGE 0 #define BGP4MP_MESSAGE 1 #define BGP4MP_ENTRY 2 #define BGP4MP_SNAPSHOT 3 frr-7.2.1/doc/user/pbr.rst0000644000000000000000000001115713610377563012261 00000000000000.. _pbr: *** PBR *** :abbr:`PBR` is Policy Based Routing. This implementation supports a very simple interface to allow admins to influence routing on their router. At this time you can only match on destination and source prefixes for an incoming interface. At this point in time, this implementation will only work on Linux. .. _starting-pbr: Starting PBR ============ Default configuration file for *pbrd* is :file:`pbrd.conf`. The typical location of :file:`pbrd.conf` is |INSTALL_PREFIX_ETC|/pbrd.conf. If the user is using integrated config, then :file:`pbrd.conf` need not be present and the :file:`frr.conf` is read instead. .. program:: pbrd :abbr:`PBR` supports all the common FRR daemon start options which are documented elsewhere. .. _nexthop-groups: Nexthop Groups ============== Nexthop groups are a way to encapsulate ECMP information together. It's a listing of ECMP nexthops used to forward packets for when a pbr-map is matched. .. clicmd:: nexthop-group NAME Create a nexthop-group with an associated NAME. This will put you into a sub-mode where you can specify individual nexthops. To exit this mode type exit or end as per normal conventions for leaving a sub-mode. .. clicmd:: nexthop [A.B.C.D|X:X::X:XX] [interface] [nexthop-vrf NAME] Create a v4 or v6 nexthop. All normal rules for creating nexthops that you are used to are allowed here. The syntax was intentionally kept the same as creating nexthops as you would for static routes. .. clicmd:: [no] pbr table range (10000-4294966272) (10000-4294966272) Set or unset the range used to assign numeric table ID's to new nexthop-group tables. Existing tables will not be modified to fit in this range, so it is recommended to configure this before adding nexthop groups. .. seealso:: :ref:`pbr-details` Showing Nexthop Group Information --------------------------------- .. clicmd:: show pbr nexthop-groups [NAME] Display information on a PBR nexthop-group. If ``NAME`` is omitted, all nexthop groups are shown. .. _pbr-maps: PBR Maps ======== PBR maps are a way to group policies that we would like to apply to individual interfaces. These policies when applied are matched against incoming packets. If matched the nexthop-group or nexthop is used to forward the packets to the end destination. .. clicmd:: pbr-map NAME seq (1-700) Create a pbr-map with NAME and sequence number specified. This command puts you into a new submode for pbr-map specification. To exit this mode type exit or end as per normal conventions for leaving a sub-mode. .. clicmd:: match src-ip PREFIX When a incoming packet matches the source prefix specified, take the packet and forward according to the nexthops specified. This command accepts both v4 and v6 prefixes. This command is used in conjunction of the :clicmd:`match dst-ip PREFIX` command for matching. .. clicmd:: match dst-ip PREFIX When a incoming packet matches the destination prefix specified, take the packet and forward according to the nexthops specified. This command accepts both v4 and v6 prefixes. This command is used in conjunction of the :clicmd:`match src-ip PREFIX` command for matching. .. clicmd:: match mark (1-4294967295) Select the mark to match. This is a linux only command and if attempted on another platform it will be denied. This mark translates to the underlying `ip rule .... fwmark XXXX` command. .. clicmd:: set nexthop-group NAME Use the nexthop-group NAME as the place to forward packets when the match commands have matched a packet. .. clicmd:: set nexthop [A.B.C.D|X:X::X:XX] [interface] [nexthop-vrf NAME] Use this individual nexthop as the place to forward packets when the match commands have matched a packet. .. _pbr-policy: PBR Policy ========== After you have specified a PBR map, in order for it to be turned on, you must apply the PBR map to an interface. This policy application to an interface causes the policy to be installed into the kernel. .. index:: pbr-policy .. clicmd:: pbr-policy NAME This command is available under interface sub-mode. This turns on the PBR map NAME and allows it to work properly. .. _pbr-details: PBR Details =========== Under the covers a PBR map is translated into two separate constructs in the Linux kernel. .. index:: PBR Rules The PBR map specified creates a `ip rule ...` that is inserted into the Linux kernel that points to a table to use for forwarding once the rule matches. .. index:: PBR Tables The creation of a nexthop or nexthop-group is translated to a default route in a table with the nexthops specified as the nexthops for the default route. frr-7.2.1/doc/user/pim.rst0000644000000000000000000004615413610377563012270 00000000000000.. _pim: *** PIM *** PIM -- Protocol Independent Multicast *pimd* supports pim-sm as well as igmp v2 and v3. pim is vrf aware and can work within the context of vrf's in order to do S,G mrouting. Additionally PIM can be used in the EVPN underlay network for optimizing forwarding of overlay BUM traffic. .. _starting-and-stopping-pimd: Starting and Stopping pimd ========================== The default configuration file name of *pimd*'s is :file:`pimd.conf`. When invoked *pimd* searches directory |INSTALL_PREFIX_ETC|. If :file:`pimd.conf` is not there then next search current directory. *pimd* requires zebra for proper operation. Additionally *pimd* depends on routing properly setup and working in the network that it is working on. :: # zebra -d # pimd -d Please note that *zebra* must be invoked before *pimd*. To stop *pimd* please use:: kill `cat /var/run/pimd.pid` Certain signals have special meanings to *pimd*. +---------+---------------------------------------------------------------------+ | Signal | Meaning | +=========+=====================================================================+ | SIGUSR1 | Rotate the *pimd* logfile | +---------+---------------------------------------------------------------------+ | SIGINT | *pimd* sweeps all installed PIM mroutes then terminates gracefully. | | SIGTERM | | +---------+---------------------------------------------------------------------+ *pimd* invocation options. Common options that can be specified (:ref:`common-invocation-options`). .. index:: ip pim rp A.B.C.D A.B.C.D/M .. clicmd:: ip pim rp A.B.C.D A.B.C.D/M In order to use pim, it is necessary to configure a RP for join messages to be sent to. Currently the only methodology to do this is via static rp commands. All routers in the pim network must agree on these values. The first ip address is the RP's address and the second value is the matching prefix of group ranges covered. This command is vrf aware, to configure for a vrf, enter the vrf submode. .. index:: ip pim spt-switchover infinity-and-beyond .. clicmd:: ip pim spt-switchover infinity-and-beyond On the last hop router if it is desired to not switch over to the SPT tree. Configure this command. This command is vrf aware, to configure for a vrf, enter the vrf submode. .. index:: ip pim ecmp .. clicmd:: ip pim ecmp If pim has the a choice of ECMP nexthops for a particular RPF, pim will cause S,G flows to be spread out amongst the nexthops. If this command is not specified then the first nexthop found will be used. This command is vrf aware, to configure for a vrf, enter the vrf submode. .. index:: ip pim ecmp rebalance .. clicmd:: ip pim ecmp rebalance If pim is using ECMP and an interface goes down, cause pim to rebalance all S,G flows across the remaining nexthops. If this command is not configured pim only modifies those S,G flows that were using the interface that went down. This command is vrf aware, to configure for a vrf, enter the vrf submode. .. index:: ip pim join-prune-interval (60-600) .. clicmd:: ip pim join-prune-interval (60-600) Modify the join/prune interval that pim uses to the new value. Time is specified in seconds. This command is vrf aware, to configure for a vrf, enter the vrf submode. .. index:: ip pim keep-alive-timer (31-60000) .. clicmd:: ip pim keep-alive-timer (31-60000) Modify the time out value for a S,G flow from 31-60000 seconds. 31 seconds is chosen for a lower bound because some hardware platforms cannot see data flowing in better than 30 second chunks. This command is vrf aware, to configure for a vrf, enter the vrf submode. .. index:: ip pim packets (1-100) .. clicmd:: ip pim packets (1-100) When processing packets from a neighbor process the number of packets incoming at one time before moving on to the next task. The default value is 3 packets. This command is only useful at scale when you can possibly have a large number of pim control packets flowing. This command is vrf aware, to configure for a vrf, enter the vrf submode. .. index:: ip pim register-suppress-time (5-60000) .. clicmd:: ip pim register-suppress-time (5-60000) Modify the time that pim will register suppress a FHR will send register notifications to the kernel. This command is vrf aware, to configure for a vrf, enter the vrf submode. .. index:: ip pim send-v6-secondary .. clicmd:: ip pim send-v6-secondary When sending pim hello packets tell pim to send any v6 secondary addresses on the interface. This information is used to allow pim to use v6 nexthops in it's decision for RPF lookup. This command is vrf aware, to configure for a vrf, enter the vrf submode. .. index:: ip pim ssm prefix-list WORD .. clicmd:: ip pim ssm prefix-list WORD Specify a range of group addresses via a prefix-list that forces pim to never do SM over. This command is vrf aware, to configure for a vrf, enter the vrf submode. .. index:: ip multicast rpf-lookup-mode WORD .. clicmd:: ip multicast rpf-lookup-mode WORD Modify how PIM does RPF lookups in the zebra routing table. You can use these choices: longer-prefix Lookup the RPF in both tables using the longer prefix as a match lower-distance Lookup the RPF in both tables using the lower distance as a match mrib-only Lookup in the Multicast RIB only mrib-then-urib Lookup in the Multicast RIB then the Unicast Rib, returning first found. This is the default value for lookup if this command is not entered urib-only Lookup in the Unicast Rib only. .. index:: ip igmp generate-query-once [version (2-3)] .. clicmd:: ip igmp generate-query-once [version (2-3)] Generate IGMP query (v2/v3) on user requirement. This will not depend on the existing IGMP general query timer.If no version is provided in the cli, it will be considered as default v2 query.This is a hidden command. .. _pim-interface-configuration: PIM Interface Configuration =========================== PIM interface commands allow you to configure an interface as either a Receiver or a interface that you would like to form pim neighbors on. If the interface is in a vrf, enter the interface command with the vrf keyword at the end. .. index:: ip pim bfd .. clicmd:: ip pim bfd Turns on BFD support for PIM for this interface. .. index:: ip pim bsm .. clicmd:: ip pim bsm Tell pim that we would like to use this interface to process bootstrap messages. This is enabled by default. 'no' form of this command is used to restrict bsm messages on this interface. .. index:: ip pim unicast-bsm .. clicmd:: ip pim unicast-bsm Tell pim that we would like to allow interface to process unicast bootstrap messages. This is enabled by default. 'no' form of this command is used to restrict processing of unicast bsm messages on this interface. .. index:: ip pim drpriority (1-4294967295) .. clicmd:: ip pim drpriority (1-4294967295) Set the DR Priority for the interface. This command is useful to allow the user to influence what node becomes the DR for a lan segment. .. index:: ip pim hello (1-180) (1-180) .. clicmd:: ip pim hello (1-180) (1-180) Set the pim hello and hold interval for a interface. .. index:: ip pim sm .. clicmd:: ip pim sm Tell pim that we would like to use this interface to form pim neighbors over. Please note we will *not* accept igmp reports over this interface with this command. .. index:: ip igmp .. clicmd:: ip igmp Tell pim to receive IGMP reports and Query on this interface. The default version is v3. This command is useful on the LHR. .. index:: ip igmp join A.B.C.D A.B.C.D .. clicmd:: ip igmp join A.B.C.D A.B.C.D Join multicast source-group on an interface. .. index:: ip igmp query-interval (1-1800) .. clicmd:: ip igmp query-interval (1-1800) Set the IGMP query interval that PIM will use. .. index:: ip igmp query-max-response-time (10-250) .. clicmd:: ip igmp query-max-response-time (10-250) Set the IGMP query response timeout value. If an report is not returned in the specified time we will assume the S,G or \*,G has timed out. .. index:: ip igmp version (2-3) .. clicmd:: ip igmp version (2-3) Set the IGMP version used on this interface. The default value is 3. .. index:: ip multicast boundary oil WORD .. clicmd:: ip multicast boundary oil WORD Set a pim multicast boundary, based upon the WORD prefix-list. If a pim join or IGMP report is received on this interface and the Group is denied by the prefix-list, PIM will ignore the join or report. .. index:: ip igmp last-member-query-count (1-7) .. clicmd:: ip igmp last-member-query-count (1-7) Set the IGMP last member query count. The default value is 2. 'no' form of this command is used to to configure back to the default value. .. index:: ip igmp last-member-query-interval (1-255) .. clicmd:: ip igmp last-member-query-interval (1-255) Set the IGMP last member query interval in deciseconds. The default value is 10 deciseconds. 'no' form of this command is used to to configure back to the default value. .. _pim-multicast-rib-insertion: PIM Multicast RIB insertion: ============================ In order to influence Multicast RPF lookup, it is possible to insert into zebra routes for the Multicast RIB. These routes are only used for RPF lookup and will not be used by zebra for insertion into the kernel *or* for normal rib processing. As such it is possible to create weird states with these commands. Use with caution. Most of the time this will not be necessary. .. index:: ip mroute A.B.C.D/M A.B.C.D (1-255) .. clicmd:: ip mroute A.B.C.D/M A.B.C.D (1-255) Insert into the Multicast Rib Route A.B.C.D/M with specified nexthop. The distance can be specified as well if desired. .. index:: ip mroute A.B.C.D/M INTERFACE (1-255) .. clicmd:: ip mroute A.B.C.D/M INTERFACE (1-255) Insert into the Multicast Rib Route A.B.C.D/M using the specified INTERFACE. The distance can be specified as well if desired. .. _show-pim-information: Show PIM Information ==================== All PIM show commands are vrf aware and typically allow you to insert a specified vrf command if information is desired about a specific vrf. If no vrf is specified then the default vrf is assumed. Finally the special keyword 'all' allows you to look at all vrfs for the command. Naming a vrf 'all' will cause great confusion. .. index:: show ip igmp interface .. clicmd:: show ip igmp interface Display IGMP interface information. .. index:: show ip igmp join .. clicmd:: show ip igmp join Display IGMP static join information. .. index:: show ip igmp groups .. clicmd:: show ip igmp groups Display IGMP groups information. .. index:: show ip igmp groups retransmissions .. clicmd:: show ip igmp groups retransmissions Display IGMP group retransmission information. .. index:: show ip igmp sources .. clicmd:: show ip igmp sources Display IGMP sources information. .. index:: show ip igmp sources retransmissions .. clicmd:: show ip igmp sources retransmissions Display IGMP source retransmission information. .. index:: show ip igmp statistics .. clicmd:: show ip igmp statistics Display IGMP statistics information. .. index:: show ip multicast .. clicmd:: show ip multicast Display various information about the interfaces used in this pim instance. .. index:: show ip mroute [vrf NAME] [A.B.C.D [A.B.C.D]] [fill] [json] .. clicmd:: show ip mroute [vrf NAME] [A.B.C.D [A.B.C.D]] [fill] [json] Display information about installed into the kernel S,G mroutes. If one address is specified we assume it is the Group we are interested in displaying data on. If the second address is specified then it is Source Group. The keyword `fill` says to fill in all assumed data for test/data gathering purposes. .. index:: show ip mroute count .. clicmd:: show ip mroute count Display information about installed into the kernel S,G mroutes and in addition display data about packet flow for the mroutes. .. index:: show ip mroute summary .. clicmd:: show ip mroute summary Display total number of S,G mroutes and number of S,G mroutes installed into the kernel. .. index:: show ip pim assert .. clicmd:: show ip pim assert Display information about asserts in the PIM system for S,G mroutes. .. index:: show ip pim assert-internal .. clicmd:: show ip pim assert-internal Display internal assert state for S,G mroutes .. index:: show ip pim assert-metric .. clicmd:: show ip pim assert-metric Display metric information about assert state for S,G mroutes .. index:: show ip pim assert-winner-metric .. clicmd:: show ip pim assert-winner-metric Display winner metric for assert state for S,G mroutes .. index:: show ip pim group-type .. clicmd:: show ip pim group-type Display SSM group ranges. .. index:: show ip pim interface .. clicmd:: show ip pim interface Display information about interfaces PIM is using. .. index:: show ip pim [vrf NAME] join [A.B.C.D [A.B.C.D]] [json] .. clicmd:: show ip pim join Display information about PIM joins received. If one address is specified then we assume it is the Group we are interested in displaying data on. If the second address is specified then it is Source Group. .. index:: show ip pim local-membership .. clicmd:: show ip pim local-membership Display information about PIM interface local-membership. .. index:: show ip pim neighbor .. clicmd:: show ip pim neighbor Display information about PIM neighbors. .. index:: show ip pim nexthop .. clicmd:: show ip pim nexthop Display information about pim nexthops that are being used. .. index:: show ip pim nexthop-lookup .. clicmd:: show ip pim nexthop-lookup Display information about a S,G pair and how the RPF would be chosen. This is especially useful if there are ECMP's available from the RPF lookup. .. index:: show ip pim rp-info .. clicmd:: show ip pim rp-info Display information about RP's that are configured on this router. .. index:: show ip pim rpf .. clicmd:: show ip pim rpf Display information about currently being used S,G's and their RPF lookup information. Additionally display some statistics about what has been happening on the router. .. index:: show ip pim secondary .. clicmd:: show ip pim secondary Display information about an interface and all the secondary addresses associated with it. .. index:: show ip pim state .. clicmd:: show ip pim state Display information about known S,G's and incoming interface as well as the OIL and how they were chosen. .. index:: show ip pim [vrf NAME] upstream [A.B.C.D [A.B.C.D]] [json] .. clicmd:: show ip pim upstream Display upstream information about a S,G mroute. Allow the user to specify sub Source and Groups that we are only interested in. .. index:: show ip pim upstream-join-desired .. clicmd:: show ip pim upstream-join-desired Display upstream information for S,G's and if we desire to join the multicast tree .. index:: show ip pim upstream-rpf .. clicmd:: show ip pim upstream-rpf Display upstream information for S,G's and the RPF data associated with them. .. index:: show ip pim bsr .. clicmd:: show ip pim bsr Display current bsr, its uptime and last received bsm age. .. index:: show ip pim bsrp-info .. clicmd:: show ip pim bsrp-info Display group-to-rp mappings received from E-BSR. .. index:: show ip pim bsm-database .. clicmd:: show ip pim bsm-database Display all fragments ofstored bootstrap message in user readable format. .. index:: show ip rpf .. clicmd:: show ip rpf Display the multicast RIB created in zebra. .. index:: mtrace A.B.C.D [A.B.C.D] .. clicmd:: mtrace A.B.C.D [A.B.C.D] Display multicast traceroute towards source, optionally for particular group. PIM Debug Commands ================== The debugging subsystem for PIM behaves in accordance with how FRR handles debugging. You can specify debugging at the enable CLI mode as well as the configure CLI mode. If you specify debug commands in the configuration cli mode, the debug commands can be persistent across restarts of the FRR pimd if the config was written out. .. index:: debug igmp .. clicmd:: debug igmp This turns on debugging for IGMP protocol activity. .. index:: debug mtrace .. clicmd:: debug mtrace This turns on debugging for mtrace protocol activity. .. index:: debug mroute .. clicmd:: debug mroute This turns on debugging for PIM interaction with kernel MFC cache. .. index:: debug pim events .. clicmd:: debug pim events This turns on debugging for PIM system events. Especially timers. .. index:: debug pim nht .. clicmd:: debug pim nht This turns on debugging for PIM nexthop tracking. It will display information about RPF lookups and information about when a nexthop changes. .. index:: debug pim packet-dump .. clicmd:: debug pim packet-dump This turns on an extraordinary amount of data. Each pim packet sent and received is dumped for debugging purposes. This should be considered a developer only command. .. index:: debug pim packets .. clicmd:: debug pim packets This turns on information about packet generation for sending and about packet handling from a received packet. .. index:: debug pim trace .. clicmd:: debug pim trace This traces pim code and how it is running. .. index:: debug pim bsm .. clicmd:: debug pim bsm This turns on debugging for BSR message processing. .. index:: debug pim zebra .. clicmd:: debug pim zebra This gathers data about events from zebra that come up through the ZAPI. PIM Clear Commands ================== Clear commands reset various variables. .. index:: clear ip interfaces .. clicmd:: clear ip interfaces Reset interfaces. .. index:: clear ip igmp interfaces .. clicmd:: clear ip igmp interfaces Reset IGMP interfaces. .. index:: clear ip mroute .. clicmd:: clear ip mroute Reset multicast routes. .. index:: clear ip mroute [vrf NAME] count .. clicmd:: clear ip mroute [vrf NAME] count When this command is issued, reset the counts of data shown for packet count, byte count and wrong interface to 0 and start count up from this spot. .. index:: clear ip pim interfaces .. clicmd:: clear ip pim interfaces Reset PIM interfaces. .. index:: clear ip pim oil .. clicmd:: clear ip pim oil Rescan PIM OIL (output interface list). PIM EVPN configuration ====================== To use PIM in the underlay for overlay BUM forwarding associate a multicast group with the L2 VNI. The actual configuration is based on your distribution. Here is an ifupdown2 example:: auto vx-10100 iface vx-10100 vxlan-id 10100 bridge-access 100 vxlan-local-tunnelip 27.0.0.11 vxlan-mcastgrp 239.1.1.100 .. note:: PIM will see the ``vxlan-mcastgrp`` configuration and auto configure state to properly forward BUM traffic. PIM also needs to be configured in the underlay to allow the BUM MDT to be setup. This is existing PIM configuration: - Enable pim on the underlay L3 interface via the "ip pim" command. - Configure RPs for the BUM multicast group range. - Ensure the PIM is enabled on the lo of the VTEPs and the RP. frr-7.2.1/doc/user/ripd.rst0000644000000000000000000005616613610377563012445 00000000000000.. _rip: *** RIP *** RIP -- Routing Information Protocol is widely deployed interior gateway protocol. RIP was developed in the 1970s at Xerox Labs as part of the XNS routing protocol. RIP is a :term:`distance-vector` protocol and is based on the :term:`Bellman-Ford` algorithms. As a distance-vector protocol, RIP router send updates to its neighbors periodically, thus allowing the convergence to a known topology. In each update, the distance to any given network will be broadcast to its neighboring router. *ripd* supports RIP version 2 as described in RFC2453 and RIP version 1 as described in RFC1058. .. _starting-and-stopping-ripd: Starting and Stopping ripd ========================== The default configuration file name of *ripd*'s is :file:`ripd.conf`. When invocation *ripd* searches directory |INSTALL_PREFIX_ETC|. If :file:`ripd.conf` is not there next search current directory. RIP uses UDP port 520 to send and receive RIP packets. So the user must have the capability to bind the port, generally this means that the user must have superuser privileges. RIP protocol requires interface information maintained by *zebra* daemon. So running *zebra* is mandatory to run *ripd*. Thus minimum sequence for running RIP is like below: :: # zebra -d # ripd -d Please note that *zebra* must be invoked before *ripd*. To stop *ripd*. Please use:: kill `cat /var/run/ripd.pid` Certain signals have special meanings to *ripd*. +-------------+------------------------------------------------------+ | Signal | Action | +=============+======================================================+ | ``SIGHUP`` | Reload configuration file :file:`ripd.conf`. | | | All configurations are reset. All routes learned | | | so far are cleared and removed from routing table. | +-------------+------------------------------------------------------+ | ``SIGUSR1`` | Rotate the *ripd* logfile. | +-------------+------------------------------------------------------+ | ``SIGINT`` | | | ``SIGTERM`` | Sweep all installed routes and gracefully terminate. | +-------------+------------------------------------------------------+ *ripd* invocation options. Common options that can be specified (:ref:`common-invocation-options`). .. _rip-netmask: RIP netmask ----------- The netmask features of *ripd* support both version 1 and version 2 of RIP. Version 1 of RIP originally contained no netmask information. In RIP version 1, network classes were originally used to determine the size of the netmask. Class A networks use 8 bits of mask, Class B networks use 16 bits of masks, while Class C networks use 24 bits of mask. Today, the most widely used method of a network mask is assigned to the packet on the basis of the interface that received the packet. Version 2 of RIP supports a variable length subnet mask (VLSM). By extending the subnet mask, the mask can be divided and reused. Each subnet can be used for different purposes such as large to middle size LANs and WAN links. FRR *ripd* does not support the non-sequential netmasks that are included in RIP Version 2. In a case of similar information with the same prefix and metric, the old information will be suppressed. Ripd does not currently support equal cost multipath routing. .. _rip-configuration: RIP Configuration ================= .. index:: router rip .. clicmd:: router rip The `router rip` command is necessary to enable RIP. To disable RIP, use the `no router rip` command. RIP must be enabled before carrying out any of the RIP commands. .. index:: no router rip .. clicmd:: no router rip Disable RIP. .. index:: network NETWORK .. clicmd:: network NETWORK .. index:: no network NETWORK .. clicmd:: no network NETWORK Set the RIP enable interface by NETWORK. The interfaces which have addresses matching with NETWORK are enabled. This group of commands either enables or disables RIP interfaces between certain numbers of a specified network address. For example, if the network for 10.0.0.0/24 is RIP enabled, this would result in all the addresses from 10.0.0.0 to 10.0.0.255 being enabled for RIP. The `no network` command will disable RIP for the specified network. .. index:: network IFNAME .. clicmd:: network IFNAME .. index:: no network IFNAME .. clicmd:: no network IFNAME Set a RIP enabled interface by IFNAME. Both the sending and receiving of RIP packets will be enabled on the port specified in the `network ifname` command. The `no network ifname` command will disable RIP on the specified interface. .. index:: neighbor A.B.C.D .. clicmd:: neighbor A.B.C.D .. index:: no neighbor A.B.C.D .. clicmd:: no neighbor A.B.C.D Specify RIP neighbor. When a neighbor doesn't understand multicast, this command is used to specify neighbors. In some cases, not all routers will be able to understand multicasting, where packets are sent to a network or a group of addresses. In a situation where a neighbor cannot process multicast packets, it is necessary to establish a direct link between routers. The neighbor command allows the network administrator to specify a router as a RIP neighbor. The `no neighbor a.b.c.d` command will disable the RIP neighbor. Below is very simple RIP configuration. Interface `eth0` and interface which address match to `10.0.0.0/8` are RIP enabled. .. code-block:: frr ! router rip network 10.0.0.0/8 network eth0 ! .. index:: passive-interface (IFNAME|default) .. clicmd:: passive-interface (IFNAME|default) .. index:: no passive-interface IFNAME .. clicmd:: no passive-interface IFNAME This command sets the specified interface to passive mode. On passive mode interface, all receiving packets are processed as normal and ripd does not send either multicast or unicast RIP packets except to RIP neighbors specified with `neighbor` command. The interface may be specified as `default` to make ripd default to passive on all interfaces. The default is to be passive on all interfaces. .. index:: ip split-horizon .. clicmd:: ip split-horizon .. index:: no ip split-horizon .. clicmd:: no ip split-horizon Control split-horizon on the interface. Default is `ip split-horizon`. If you don't perform split-horizon on the interface, please specify `no ip split-horizon`. .. _rip-version-control: RIP Version Control =================== RIP can be configured to send either Version 1 or Version 2 packets. The default is to send RIPv2 while accepting both RIPv1 and RIPv2 (and replying with packets of the appropriate version for REQUESTS / triggered updates). The version to receive and send can be specified globally, and further overridden on a per-interface basis if needs be for send and receive separately (see below). It is important to note that RIPv1 cannot be authenticated. Further, if RIPv1 is enabled then RIP will reply to REQUEST packets, sending the state of its RIP routing table to any remote routers that ask on demand. For a more detailed discussion on the security implications of RIPv1 see :ref:`rip-authentication`. .. index:: version VERSION .. clicmd:: version VERSION Set RIP version to accept for reads and send. ``VERSION`` can be either 1 or 1. Disabling RIPv1 by specifying version 2 is STRONGLY encouraged, :ref:`rip-authentication`. This may become the default in a future release. Default: Send Version 2, and accept either version. .. index:: no version .. clicmd:: no version Reset the global version setting back to the default. .. index:: ip rip send version VERSION .. clicmd:: ip rip send version VERSION VERSION can be ``1``, ``2``, or ``1 2``. This interface command overrides the global rip version setting, and selects which version of RIP to send packets with, for this interface specifically. Choice of RIP Version 1, RIP Version 2, or both versions. In the latter case, where ``1 2`` is specified, packets will be both broadcast and multicast. Default: Send packets according to the global version (version 2) .. index:: ip rip receive version VERSION .. clicmd:: ip rip receive version VERSION VERSION can be ``1``, ``2``, or ``1 2``. This interface command overrides the global rip version setting, and selects which versions of RIP packets will be accepted on this interface. Choice of RIP Version 1, RIP Version 2, or both. Default: Accept packets according to the global setting (both 1 and 2). .. _how-to-announce-rip-route: How to Announce RIP route ========================= .. index:: redistribute kernel .. clicmd:: redistribute kernel .. index:: redistribute kernel metric (0-16) .. clicmd:: redistribute kernel metric (0-16) .. index:: redistribute kernel route-map ROUTE-MAP .. clicmd:: redistribute kernel route-map ROUTE-MAP .. index:: no redistribute kernel .. clicmd:: no redistribute kernel `redistribute kernel` redistributes routing information from kernel route entries into the RIP tables. `no redistribute kernel` disables the routes. .. index:: redistribute static .. clicmd:: redistribute static .. index:: redistribute static metric (0-16) .. clicmd:: redistribute static metric (0-16) .. index:: redistribute static route-map ROUTE-MAP .. clicmd:: redistribute static route-map ROUTE-MAP .. index:: no redistribute static .. clicmd:: no redistribute static `redistribute static` redistributes routing information from static route entries into the RIP tables. `no redistribute static` disables the routes. .. index:: redistribute connected .. clicmd:: redistribute connected .. index:: redistribute connected metric (0-16) .. clicmd:: redistribute connected metric (0-16) .. index:: redistribute connected route-map ROUTE-MAP .. clicmd:: redistribute connected route-map ROUTE-MAP .. index:: no redistribute connected .. clicmd:: no redistribute connected Redistribute connected routes into the RIP tables. `no redistribute connected` disables the connected routes in the RIP tables. This command redistribute connected of the interface which RIP disabled. The connected route on RIP enabled interface is announced by default. .. index:: redistribute ospf .. clicmd:: redistribute ospf .. index:: redistribute ospf metric (0-16) .. clicmd:: redistribute ospf metric (0-16) .. index:: redistribute ospf route-map ROUTE-MAP .. clicmd:: redistribute ospf route-map ROUTE-MAP .. index:: no redistribute ospf .. clicmd:: no redistribute ospf `redistribute ospf` redistributes routing information from ospf route entries into the RIP tables. `no redistribute ospf` disables the routes. .. index:: redistribute bgp .. clicmd:: redistribute bgp .. index:: redistribute bgp metric (0-16) .. clicmd:: redistribute bgp metric (0-16) .. index:: redistribute bgp route-map ROUTE-MAP .. clicmd:: redistribute bgp route-map ROUTE-MAP .. index:: no redistribute bgp .. clicmd:: no redistribute bgp `redistribute bgp` redistributes routing information from bgp route entries into the RIP tables. `no redistribute bgp` disables the routes. If you want to specify RIP only static routes: .. index:: default-information originate .. clicmd:: default-information originate .. index:: route A.B.C.D/M .. clicmd:: route A.B.C.D/M .. index:: no route A.B.C.D/M .. clicmd:: no route A.B.C.D/M This command is specific to FRR. The `route` command makes a static route only inside RIP. This command should be used only by advanced users who are particularly knowledgeable about the RIP protocol. In most cases, we recommend creating a static route in FRR and redistributing it in RIP using `redistribute static`. .. _filtering-rip-routes: Filtering RIP Routes ==================== RIP routes can be filtered by a distribute-list. .. index:: distribute-list ACCESS_LIST DIRECT IFNAME .. clicmd:: distribute-list ACCESS_LIST DIRECT IFNAME You can apply access lists to the interface with a `distribute-list` command. ACCESS_LIST is the access list name. DIRECT is ``in`` or ``out``. If DIRECT is ``in`` the access list is applied to input packets. The `distribute-list` command can be used to filter the RIP path. `distribute-list` can apply access-lists to a chosen interface. First, one should specify the access-list. Next, the name of the access-list is used in the distribute-list command. For example, in the following configuration ``eth0`` will permit only the paths that match the route 10.0.0.0/8 .. code-block:: frr ! router rip distribute-list private in eth0 ! access-list private permit 10 10.0.0.0/8 access-list private deny any ! `distribute-list` can be applied to both incoming and outgoing data. .. index:: distribute-list prefix PREFIX_LIST (in|out) IFNAME .. clicmd:: distribute-list prefix PREFIX_LIST (in|out) IFNAME You can apply prefix lists to the interface with a `distribute-list` command. PREFIX_LIST is the prefix list name. Next is the direction of ``in`` or ``out``. If DIRECT is ``in`` the access list is applied to input packets. .. _rip-metric-manipulation: RIP Metric Manipulation ======================= RIP metric is a value for distance for the network. Usually *ripd* increment the metric when the network information is received. Redistributed routes' metric is set to 1. .. index:: default-metric (1-16) .. clicmd:: default-metric (1-16) .. index:: no default-metric (1-16) .. clicmd:: no default-metric (1-16) This command modifies the default metric value for redistributed routes. The default value is 1. This command does not affect connected route even if it is redistributed by *redistribute connected*. To modify connected route's metric value, please use ``redistribute connected metric`` or *route-map*. *offset-list* also affects connected routes. .. index:: offset-list ACCESS-LIST (in|out) .. clicmd:: offset-list ACCESS-LIST (in|out) .. index:: offset-list ACCESS-LIST (in|out) IFNAME .. clicmd:: offset-list ACCESS-LIST (in|out) IFNAME .. _rip-distance: RIP distance ============ Distance value is used in zebra daemon. Default RIP distance is 120. .. index:: distance (1-255) .. clicmd:: distance (1-255) .. index:: no distance (1-255) .. clicmd:: no distance (1-255) Set default RIP distance to specified value. .. index:: distance (1-255) A.B.C.D/M .. clicmd:: distance (1-255) A.B.C.D/M .. index:: no distance (1-255) A.B.C.D/M .. clicmd:: no distance (1-255) A.B.C.D/M Set default RIP distance to specified value when the route's source IP address matches the specified prefix. .. index:: distance (1-255) A.B.C.D/M ACCESS-LIST .. clicmd:: distance (1-255) A.B.C.D/M ACCESS-LIST .. index:: no distance (1-255) A.B.C.D/M ACCESS-LIST .. clicmd:: no distance (1-255) A.B.C.D/M ACCESS-LIST Set default RIP distance to specified value when the route's source IP address matches the specified prefix and the specified access-list. .. _rip-route-map: RIP route-map ============= Usage of *ripd*'s route-map support. Optional argument route-map MAP_NAME can be added to each `redistribute` statement. .. code-block:: frr redistribute static [route-map MAP_NAME] redistribute connected [route-map MAP_NAME] ..... Cisco applies route-map _before_ routes will exported to rip route table. In current FRR's test implementation, *ripd* applies route-map after routes are listed in the route table and before routes will be announced to an interface (something like output filter). I think it is not so clear, but it is draft and it may be changed at future. Route-map statement (:ref:`route-map`) is needed to use route-map functionality. .. index:: match interface WORD .. clicmd:: match interface WORD This command match to incoming interface. Notation of this match is different from Cisco. Cisco uses a list of interfaces - NAME1 NAME2 ... NAMEN. Ripd allows only one name (maybe will change in the future). Next - Cisco means interface which includes next-hop of routes (it is somewhat similar to "ip next-hop" statement). Ripd means interface where this route will be sent. This difference is because "next-hop" of same routes which sends to different interfaces must be different. Maybe it'd be better to made new matches - say "match interface-out NAME" or something like that. .. index:: match ip address WORD .. clicmd:: match ip address WORD .. index:: match ip address prefix-list WORD .. clicmd:: match ip address prefix-list WORD Match if route destination is permitted by access-list. .. index:: match ip next-hop WORD .. clicmd:: match ip next-hop WORD .. index:: match ip next-hop prefix-list WORD .. clicmd:: match ip next-hop prefix-list WORD Match if route next-hop (meaning next-hop listed in the rip route-table as displayed by "show ip rip") is permitted by access-list. .. index:: match metric (0-4294967295) .. clicmd:: match metric (0-4294967295) This command match to the metric value of RIP updates. For other protocol compatibility metric range is shown as (0-4294967295). But for RIP protocol only the value range (0-16) make sense. .. index:: set ip next-hop A.B.C.D .. clicmd:: set ip next-hop A.B.C.D This command set next hop value in RIPv2 protocol. This command does not affect RIPv1 because there is no next hop field in the packet. .. index:: set metric (0-4294967295) .. clicmd:: set metric (0-4294967295) Set a metric for matched route when sending announcement. The metric value range is very large for compatibility with other protocols. For RIP, valid metric values are from 1 to 16. .. _rip-authentication: RIP Authentication ================== RIPv2 allows packets to be authenticated via either an insecure plain text password, included with the packet, or via a more secure MD5 based :abbr:`HMAC (keyed-Hashing for Message AuthentiCation)`, RIPv1 can not be authenticated at all, thus when authentication is configured `ripd` will discard routing updates received via RIPv1 packets. However, unless RIPv1 reception is disabled entirely, :ref:`rip-version-control`, RIPv1 REQUEST packets which are received, which query the router for routing information, will still be honoured by `ripd`, and `ripd` WILL reply to such packets. This allows `ripd` to honour such REQUESTs (which sometimes is used by old equipment and very simple devices to bootstrap their default route), while still providing security for route updates which are received. In short: Enabling authentication prevents routes being updated by unauthenticated remote routers, but still can allow routes (I.e. the entire RIP routing table) to be queried remotely, potentially by anyone on the internet, via RIPv1. To prevent such unauthenticated querying of routes disable RIPv1, :ref:`rip-version-control`. .. index:: ip rip authentication mode md5 .. clicmd:: ip rip authentication mode md5 .. index:: no ip rip authentication mode md5 .. clicmd:: no ip rip authentication mode md5 Set the interface with RIPv2 MD5 authentication. .. index:: ip rip authentication mode text .. clicmd:: ip rip authentication mode text .. index:: no ip rip authentication mode text .. clicmd:: no ip rip authentication mode text Set the interface with RIPv2 simple password authentication. .. index:: ip rip authentication string STRING .. clicmd:: ip rip authentication string STRING .. index:: no ip rip authentication string STRING .. clicmd:: no ip rip authentication string STRING RIP version 2 has simple text authentication. This command sets authentication string. The string must be shorter than 16 characters. .. index:: ip rip authentication key-chain KEY-CHAIN .. clicmd:: ip rip authentication key-chain KEY-CHAIN .. index:: no ip rip authentication key-chain KEY-CHAIN .. clicmd:: no ip rip authentication key-chain KEY-CHAIN Specify Keyed MD5 chain. .. code-block:: frr ! key chain test key 1 key-string test ! interface eth1 ip rip authentication mode md5 ip rip authentication key-chain test ! .. _rip-timers: RIP Timers ========== .. index:: timers basic UPDATE TIMEOUT GARBAGE .. clicmd:: timers basic UPDATE TIMEOUT GARBAGE RIP protocol has several timers. User can configure those timers' values by `timers basic` command. The default settings for the timers are as follows: - The update timer is 30 seconds. Every update timer seconds, the RIP process is awakened to send an unsolicited Response message containing the complete routing table to all neighboring RIP routers. - The timeout timer is 180 seconds. Upon expiration of the timeout, the route is no longer valid; however, it is retained in the routing table for a short time so that neighbors can be notified that the route has been dropped. - The garbage collect timer is 120 seconds. Upon expiration of the garbage-collection timer, the route is finally removed from the routing table. The ``timers basic`` command allows the the default values of the timers listed above to be changed. .. index:: no timers basic .. clicmd:: no timers basic The `no timers basic` command will reset the timers to the default settings listed above. .. _show-rip-information: Show RIP Information ==================== To display RIP routes. .. index:: show ip rip .. clicmd:: show ip rip Show RIP routes. The command displays all RIP routes. For routes that are received through RIP, this command will display the time the packet was sent and the tag information. This command will also display this information for routes redistributed into RIP. .. index:: show ip rip status .. clicmd:: show ip rip status The command displays current RIP status. It includes RIP timer, filtering, version, RIP enabled interface and RIP peer information. :: ripd> **show ip rip status** Routing Protocol is "rip" Sending updates every 30 seconds with +/-50%, next due in 35 seconds Timeout after 180 seconds, garbage collect after 120 seconds Outgoing update filter list for all interface is not set Incoming update filter list for all interface is not set Default redistribution metric is 1 Redistributing: kernel connected Default version control: send version 2, receive version 2 Interface Send Recv Routing for Networks: eth0 eth1 1.1.1.1 203.181.89.241 Routing Information Sources: Gateway BadPackets BadRoutes Distance Last Update RIP Debug Commands ================== Debug for RIP protocol. .. index:: debug rip events .. clicmd:: debug rip events Shows RIP events. Sending and receiving packets, timers, and changes in interfaces are events shown with *ripd*. .. index:: debug rip packet .. clicmd:: debug rip packet Shows display detailed information about the RIP packets. The origin and port number of the packet as well as a packet dump is shown. .. index:: debug rip zebra .. clicmd:: debug rip zebra This command will show the communication between *ripd* and *zebra*. The main information will include addition and deletion of paths to the kernel and the sending and receiving of interface information. .. index:: show debugging rip .. clicmd:: show debugging rip Shows all information currently set for ripd debug. frr-7.2.1/doc/user/ripngd.rst0000644000000000000000000000361013610377563012754 00000000000000.. _ripng: ***** RIPng ***** *ripngd* supports the RIPng protocol as described in :rfc:`2080`. It's an IPv6 reincarnation of the RIP protocol. .. _invoking-ripngd: Invoking ripngd =============== There are no `ripngd` specific invocation options. Common options can be specified (:ref:`common-invocation-options`). .. _ripngd-configuration: ripngd Configuration ==================== Currently ripngd supports the following commands: .. index:: router ripng .. clicmd:: router ripng Enable RIPng. .. index:: flush_timer TIME .. clicmd:: flush_timer TIME Set flush timer. .. index:: network NETWORK .. clicmd:: network NETWORK Set RIPng enabled interface by NETWORK. .. index:: network IFNAME .. clicmd:: network IFNAME Set RIPng enabled interface by IFNAME. .. index:: route NETWORK .. clicmd:: route NETWORK Set RIPng static routing announcement of NETWORK. .. index:: router zebra .. clicmd:: router zebra This command is the default and does not appear in the configuration. With this statement, RIPng routes go to the *zebra* daemon. .. _ripngd-terminal-mode-commands: ripngd Terminal Mode Commands ============================= .. index:: show ip ripng .. clicmd:: show ip ripng .. index:: show debugging ripng .. clicmd:: show debugging ripng .. index:: debug ripng events .. clicmd:: debug ripng events .. index:: debug ripng packet .. clicmd:: debug ripng packet .. index:: debug ripng zebra .. clicmd:: debug ripng zebra ripngd Filtering Commands ========================= .. index:: distribute-list ACCESS_LIST (in|out) IFNAME .. clicmd:: distribute-list ACCESS_LIST (in|out) IFNAME You can apply an access-list to the interface using the `distribute-list` command. ACCESS_LIST is an access-list name. `direct` is ``in`` or ``out``. If `direct` is ``in``, the access-list is applied only to incoming packets.:: distribute-list local-only out sit1 frr-7.2.1/doc/user/routemap.rst0000644000000000000000000002473513610377563013340 00000000000000.. _route-map: ********** Route Maps ********** Route maps provide a means to both filter and/or apply actions to route, hence allowing policy to be applied to routes. Route maps are an ordered list of route map entries. Each entry may specify up to four distinct sets of clauses: .. glossary:: Matching Conditions A route-map entry may, optionally, specify one or more conditions which must be matched if the entry is to be considered further, as governed by the Match Policy. If a route-map entry does not explicitly specify any matching conditions, then it always matches. Set Actions A route-map entry may, optionally, specify one or more Set Actions to set or modify attributes of the route. Matching Policy This specifies the policy implied if the :term:`Matching Conditions` are met or not met, and which actions of the route-map are to be taken, if any. The two possibilities are: - :dfn:`permit`: If the entry matches, then carry out the :term:`Set Actions`. Then finish processing the route-map, permitting the route, unless an :term:`Exit Policy` action indicates otherwise. - :dfn:`deny`: If the entry matches, then finish processing the route-map and deny the route (return `deny`). The `Matching Policy` is specified as part of the command which defines the ordered entry in the route-map. See below. Call Action Call to another route-map, after any :term:`Set Actions` have been carried out. If the route-map called returns `deny` then processing of the route-map finishes and the route is denied, regardless of the :term:`Matching Policy` or the :term:`Exit Policy`. If the called route-map returns `permit`, then :term:`Matching Policy` and :term:`Exit Policy` govern further behaviour, as normal. Exit Policy An entry may, optionally, specify an alternative :dfn:`Exit Policy` to take if the entry matched, rather than the normal policy of exiting the route-map and permitting the route. The two possibilities are: - :dfn:`next`: Continue on with processing of the route-map entries. - :dfn:`goto N`: Jump ahead to the first route-map entry whose order in the route-map is >= N. Jumping to a previous entry is not permitted. The default action of a route-map, if no entries match, is to deny. I.e. a route-map essentially has as its last entry an empty *deny* entry, which matches all routes. To change this behaviour, one must specify an empty *permit* entry as the last entry in the route-map. To summarise the above: +--------+--------+----------+ | | Match | No Match | +========+========+==========+ | Permit | action | cont | +--------+--------+----------+ | Deny | deny | cont | +--------+--------+----------+ action - Apply *set* statements - If *call* is present, call given route-map. If that returns a ``deny``, finish processing and return ``deny``. - If *Exit Policy* is *next*, goto next route-map entry - If *Exit Policy* is *goto*, goto first entry whose order in the list is >= the given order. - Finish processing the route-map and permit the route. deny The route is denied by the route-map (return ``deny``). cont goto next route-map entry .. _route-map-show-command: .. index:: show route-map [WORD] .. clicmd:: show route-map [WORD] Display data about each daemons knowledge of individual route-maps. If WORD is supplied narrow choice to that particular route-map. .. _route-map-clear-counter-command: .. index:: clear route-map counter [WORD] .. clicmd:: clear route-map counter [WORD] Clear counters that are being stored about the route-map utilization so that subsuquent show commands will indicate since the last clear. If WORD is specified clear just that particular route-map's counters. .. _route-map-command: Route Map Command ================= .. index:: route-map ROUTE-MAP-NAME (permit|deny) ORDER .. clicmd:: route-map ROUTE-MAP-NAME (permit|deny) ORDER Configure the `order`'th entry in `route-map-name` with ``Match Policy`` of either *permit* or *deny*. .. _route-map-match-command: Route Map Match Command ======================= .. index:: match ip address ACCESS_LIST .. clicmd:: match ip address ACCESS_LIST Matches the specified `access_list` .. index:: match ip address prefix-list PREFIX_LIST .. clicmd:: match ip address prefix-list PREFIX_LIST Matches the specified `PREFIX_LIST` .. index:: match ip address prefix-len 0-32 .. clicmd:: match ip address prefix-len 0-32 Matches the specified `prefix-len`. This is a Zebra specific command. .. index:: match ipv6 address ACCESS_LIST .. clicmd:: match ipv6 address ACCESS_LIST Matches the specified `access_list` .. index:: match ipv6 address prefix-list PREFIX_LIST .. clicmd:: match ipv6 address prefix-list PREFIX_LIST Matches the specified `PREFIX_LIST` .. index:: match ipv6 address prefix-len 0-128 .. clicmd:: match ipv6 address prefix-len 0-128 Matches the specified `prefix-len`. This is a Zebra specific command. .. index:: match ip next-hop IPV4_ADDR .. clicmd:: match ip next-hop IPV4_ADDR Matches the specified `ipv4_addr`. .. index:: match as-path AS_PATH .. clicmd:: match as-path AS_PATH Matches the specified `as_path`. .. index:: match metric METRIC .. clicmd:: match metric METRIC Matches the specified `metric`. .. index:: match tag TAG .. clicmd:: match tag TAG Matches the specified tag value associated with the route. This tag value can be in the range of (1-4294967295). .. index:: match local-preference METRIC .. clicmd:: match local-preference METRIC Matches the specified `local-preference`. .. index:: match community COMMUNITY_LIST .. clicmd:: match community COMMUNITY_LIST Matches the specified `community_list` .. index:: match peer IPV4_ADDR .. clicmd:: match peer IPV4_ADDR This is a BGP specific match command. Matches the peer ip address if the neighbor was specified in this manner. .. index:: match peer IPV6_ADDR .. clicmd:: match peer IPV6_ADDR This is a BGP specific match command. Matches the peer ipv6 address if the neighbor was specified in this manner. .. index:: match peer INTERFACE_NAME .. clicmd:: match peer INTERFACE_NAME This is a BGP specific match command. Matches the peer interface name specified if the neighbor was specified in this manner. .. index:: match source-protocol PROTOCOL_NAME .. clicmd:: match source-protocol PROTOCOL_NAME This is a ZEBRA specific match command. Matches the originating protocol specified. .. index:: match source-instance NUMBER .. clicmd:: match source-instance NUMBER This is a ZEBRA specific match command. The number is a range from (0-255). Matches the originating protocols instance specified. .. _route-map-set-command: Route Map Set Command ===================== .. program:: configure .. index:: set tag TAG .. clicmd:: set tag TAG Set a tag on the matched route. This tag value can be from (1-4294967295). Additionally if you have compiled with the :option:`--enable-realms` configure option. Tag values from (1-255) are sent to the Linux kernel as a realm value. Then route policy can be applied. See the tc man page. .. index:: set ip next-hop IPV4_ADDRESS .. clicmd:: set ip next-hop IPV4_ADDRESS Set the BGP nexthop address to the specified IPV4_ADDRESS. For both incoming and outgoing route-maps. .. index:: set ip next-hop peer-address .. clicmd:: set ip next-hop peer-address Set the BGP nexthop address to the address of the peer. For an incoming route-map this means the ip address of our peer is used. For an outgoing route-map this means the ip address of our self is used to establish the peering with our neighbor. .. index:: set ip next-hop unchanged .. clicmd:: set ip next-hop unchanged Set the route-map as unchanged. Pass the route-map through without changing it's value. .. index:: set ipv6 next-hop peer-address .. clicmd:: set ipv6 next-hop peer-address Set the BGP nexthop address to the address of the peer. For an incoming route-map this means the ipv6 address of our peer is used. For an outgoing route-map this means the ip address of our self is used to establish the peering with our neighbor. .. index:: set ipv6 next-hop prefer-global .. clicmd:: set ipv6 next-hop prefer-global For Incoming and Import Route-maps if we receive a v6 global and v6 LL address for the route, then prefer to use the global address as the nexthop. .. index:: set ipv6 next-hop global IPV6_ADDRESS .. clicmd:: set ipv6 next-hop global IPV6_ADDRESS Set the next-hop to the specified IPV6_ADDRESS for both incoming and outgoing route-maps. .. index:: set local-preference LOCAL_PREF .. clicmd:: set local-preference LOCAL_PREF Set the BGP local preference to `local_pref`. .. index:: set weight WEIGHT .. clicmd:: set weight WEIGHT Set the route's weight. .. index:: set metric METRIC .. clicmd:: set metric METRIC Set the BGP attribute MED. .. index:: set as-path prepend AS_PATH .. clicmd:: set as-path prepend AS_PATH Set the BGP AS path to prepend. .. index:: set community COMMUNITY .. clicmd:: set community COMMUNITY Set the BGP community attribute. .. index:: set ipv6 next-hop local IPV6_ADDRESS .. clicmd:: set ipv6 next-hop local IPV6_ADDRESS Set the BGP-4+ link local IPv6 nexthop address. .. index:: set origin ORIGIN .. clicmd:: set origin ORIGIN Set BGP route origin. .. _route-map-call-command: Route Map Call Command ====================== .. index:: call NAME .. clicmd:: call NAME Call route-map `name`. If it returns deny, deny the route and finish processing the route-map. .. _route-map-exit-action-command: Route Map Exit Action Command ============================= .. index:: on-match next .. clicmd:: on-match next .. index:: continue .. clicmd:: continue Proceed on to the next entry in the route-map. .. index:: on-match goto N .. clicmd:: on-match goto N .. index:: continue N .. clicmd:: continue N Proceed processing the route-map at the first entry whose order is >= N Route Map Examples ================== A simple example of a route-map: .. code-block:: frr route-map test permit 10 match ip address 10 set local-preference 200 This means that if a route matches ip access-list number 10 it's local-preference value is set to 200. See :ref:`bgp-configuration-examples` for examples of more sophisticated usage of route-maps, including of the ``call`` action. frr-7.2.1/doc/user/routeserver.rst0000644000000000000000000005216313610377563014065 00000000000000.. _configuring-frr-as-a-route-server: Configuring FRR as a Route Server ================================= The purpose of a Route Server is to centralize the peerings between BGP speakers. For example if we have an exchange point scenario with four BGP speakers, each of which maintaining a BGP peering with the other three (:ref:`fig-topologies-full`), we can convert it into a centralized scenario where each of the four establishes a single BGP peering against the Route Server (:ref:`fig-topologies-rs`). We will first describe briefly the Route Server model implemented by FRR. We will explain the commands that have been added for configuring that model. And finally we will show a full example of FRR configured as Route Server. .. _description-of-the-route-server-model: Description of the Route Server model ------------------------------------- First we are going to describe the normal processing that BGP announcements suffer inside a standard BGP speaker, as shown in :ref:`fig-normal-processing`, it consists of three steps: - When an announcement is received from some peer, the `In` filters configured for that peer are applied to the announcement. These filters can reject the announcement, accept it unmodified, or accept it with some of its attributes modified. - The announcements that pass the `In` filters go into the Best Path Selection process, where they are compared to other announcements referred to the same destination that have been received from different peers (in case such other announcements exist). For each different destination, the announcement which is selected as the best is inserted into the BGP speaker's Loc-RIB. - The routes which are inserted in the Loc-RIB are considered for announcement to all the peers (except the one from which the route came). This is done by passing the routes in the Loc-RIB through the `Out` filters corresponding to each peer. These filters can reject the route, accept it unmodified, or accept it with some of its attributes modified. Those routes which are accepted by the `Out` filters of a peer are announced to that peer. .. _fig-normal-processing: .. figure:: ../figures/fig-normal-processing.png :alt: Normal announcement processing :align: center Announcement processing inside a 'normal' BGP speaker .. _fig-topologies-full: .. figure:: ../figures/fig_topologies_full.png :alt: Full Mesh BGP Topology :align: center Full Mesh .. _fig-topologies-rs: .. figure:: ../figures/fig_topologies_rs.png :alt: Route Server BGP Topology :align: center Route server and clients Of course we want that the routing tables obtained in each of the routers are the same when using the route server than when not. But as a consequence of having a single BGP peering (against the route server), the BGP speakers can no longer distinguish from/to which peer each announce comes/goes. .. _filter-delegation: This means that the routers connected to the route server are not able to apply by themselves the same input/output filters as in the full mesh scenario, so they have to delegate those functions to the route server. Even more, the 'best path' selection must be also performed inside the route server on behalf of its clients. The reason is that if, after applying the filters of the announcer and the (potential) receiver, the route server decides to send to some client two or more different announcements referred to the same destination, the client will only retain the last one, considering it as an implicit withdrawal of the previous announcements for the same destination. This is the expected behavior of a BGP speaker as defined in :rfc:`1771`, and even though there are some proposals of mechanisms that permit multiple paths for the same destination to be sent through a single BGP peering, none are currently supported by most existing BGP implementations. As a consequence a route server must maintain additional information and perform additional tasks for a RS-client that those necessary for common BGP peerings. Essentially a route server must: .. _route-server-tasks: - Maintain a separated Routing Information Base (Loc-RIB) for each peer configured as RS-client, containing the routes selected as a result of the 'Best Path Selection' process that is performed on behalf of that RS-client. - Whenever it receives an announcement from a RS-client, it must consider it for the Loc-RIBs of the other RS-clients. - This means that for each of them the route server must pass the announcement through the appropriate `Out` filter of the announcer. - Then through the appropriate `In` filter of the potential receiver. - Only if the announcement is accepted by both filters it will be passed to the 'Best Path Selection' process. - Finally, it might go into the Loc-RIB of the receiver. When we talk about the 'appropriate' filter, both the announcer and the receiver of the route must be taken into account. Suppose that the route server receives an announcement from client A, and the route server is considering it for the Loc-RIB of client B. The filters that should be applied are the same that would be used in the full mesh scenario, i.e., first the `Out` filter of router A for announcements going to router B, and then the `In` filter of router B for announcements coming from router A. We call 'Export Policy' of a RS-client to the set of `Out` filters that the client would use if there was no route server. The same applies for the 'Import Policy' of a RS-client and the set of `In` filters of the client if there was no route server. It is also common to demand from a route server that it does not modify some BGP attributes (next-hop, as-path and MED) that are usually modified by standard BGP speakers before announcing a route. The announcement processing model implemented by FRR is shown in :ref:`fig-rs-processing`. The figure shows a mixture of RS-clients (B, C and D) with normal BGP peers (A). There are some details that worth additional comments: - Announcements coming from a normal BGP peer are also considered for the Loc-RIBs of all the RS-clients. But logically they do not pass through any export policy. - Those peers that are configured as RS-clients do not receive any announce from the `Main` Loc-RIB. - Apart from import and export policies, `In` and `Out` filters can also be set for RS-clients. `In` filters might be useful when the route server has also normal BGP peers. On the other hand, `Out` filters for RS-clients are probably unnecessary, but we decided not to remove them as they do not hurt anybody (they can always be left empty). .. _fig-rs-processing: .. figure:: ../figures/fig-rs-processing.png :align: center :alt: Route Server Processing Model Announcement processing model implemented by the Route Server .. _commands-for-configuring-a-route-server: Commands for configuring a Route Server --------------------------------------- Now we will describe the commands that have been added to frr in order to support the route server features. .. index:: neighbor PEER-GROUP route-server-client .. clicmd:: neighbor PEER-GROUP route-server-client .. index:: neighbor A.B.C.D route-server-client .. clicmd:: neighbor A.B.C.D route-server-client .. index:: neighbor X:X::X:X route-server-client .. clicmd:: neighbor X:X::X:X route-server-client This command configures the peer given by `peer`, `A.B.C.D` or `X:X::X:X` as an RS-client. Actually this command is not new, it already existed in standard FRR. It enables the transparent mode for the specified peer. This means that some BGP attributes (as-path, next-hop and MED) of the routes announced to that peer are not modified. With the route server patch, this command, apart from setting the transparent mode, creates a new Loc-RIB dedicated to the specified peer (those named `Loc-RIB for X` in :ref:`fig-rs-processing`.). Starting from that moment, every announcement received by the route server will be also considered for the new Loc-RIB. .. index:: neigbor A.B.C.D|X.X::X.X|peer-group route-map WORD import|export .. clicmd:: neigbor A.B.C.D|X.X::X.X|peer-group route-map WORD import|export This set of commands can be used to specify the route-map that represents the Import or Export policy of a peer which is configured as a RS-client (with the previous command). .. index:: match peer A.B.C.D|X:X::X:X .. clicmd:: match peer A.B.C.D|X:X::X:X This is a new *match* statement for use in route-maps, enabling them to describe import/export policies. As we said before, an import/export policy represents a set of input/output filters of the RS-client. This statement makes possible that a single route-map represents the full set of filters that a BGP speaker would use for its different peers in a non-RS scenario. The *match peer* statement has different semantics whether it is used inside an import or an export route-map. In the first case the statement matches if the address of the peer who sends the announce is the same that the address specified by {A.B.C.D|X:X::X:X}. For export route-maps it matches when {A.B.C.D|X:X::X:X} is the address of the RS-Client into whose Loc-RIB the announce is going to be inserted (how the same export policy is applied before different Loc-RIBs is shown in :ref:`fig-rs-processing`.). .. index:: call WORD .. clicmd:: call WORD This command (also used inside a route-map) jumps into a different route-map, whose name is specified by `WORD`. When the called route-map finishes, depending on its result the original route-map continues or not. Apart from being useful for making import/export route-maps easier to write, this command can also be used inside any normal (in or out) route-map. .. _example-of-route-server-configuration: Example of Route Server Configuration ------------------------------------- Finally we are going to show how to configure a FRR daemon to act as a Route Server. For this purpose we are going to present a scenario without route server, and then we will show how to use the configurations of the BGP routers to generate the configuration of the route server. All the configuration files shown in this section have been taken from scenarios which were tested using the VNUML tool `http://www.dit.upm.es/vnuml,VNUML `_. .. _configuration-of-the-bgp-routers-without-route-server: Configuration of the BGP routers without Route Server ----------------------------------------------------- We will suppose that our initial scenario is an exchange point with three BGP capable routers, named RA, RB and RC. Each of the BGP speakers generates some routes (with the `network` command), and establishes BGP peerings against the other two routers. These peerings have In and Out route-maps configured, named like 'PEER-X-IN' or 'PEER-X-OUT'. For example the configuration file for router RA could be the following: .. code-block:: frr #Configuration for router 'RA' ! hostname RA password **** ! router bgp 65001 no bgp default ipv4-unicast neighbor 2001:0DB8::B remote-as 65002 neighbor 2001:0DB8::C remote-as 65003 ! address-family ipv6 network 2001:0DB8:AAAA:1::/64 network 2001:0DB8:AAAA:2::/64 network 2001:0DB8:0000:1::/64 network 2001:0DB8:0000:2::/64 neighbor 2001:0DB8::B activate neighbor 2001:0DB8::B soft-reconfiguration inbound neighbor 2001:0DB8::B route-map PEER-B-IN in neighbor 2001:0DB8::B route-map PEER-B-OUT out neighbor 2001:0DB8::C activate neighbor 2001:0DB8::C soft-reconfiguration inbound neighbor 2001:0DB8::C route-map PEER-C-IN in neighbor 2001:0DB8::C route-map PEER-C-OUT out exit-address-family ! ipv6 prefix-list COMMON-PREFIXES seq 5 permit 2001:0DB8:0000::/48 ge 64 le 64 ipv6 prefix-list COMMON-PREFIXES seq 10 deny any ! ipv6 prefix-list PEER-A-PREFIXES seq 5 permit 2001:0DB8:AAAA::/48 ge 64 le 64 ipv6 prefix-list PEER-A-PREFIXES seq 10 deny any ! ipv6 prefix-list PEER-B-PREFIXES seq 5 permit 2001:0DB8:BBBB::/48 ge 64 le 64 ipv6 prefix-list PEER-B-PREFIXES seq 10 deny any ! ipv6 prefix-list PEER-C-PREFIXES seq 5 permit 2001:0DB8:CCCC::/48 ge 64 le 64 ipv6 prefix-list PEER-C-PREFIXES seq 10 deny any ! route-map PEER-B-IN permit 10 match ipv6 address prefix-list COMMON-PREFIXES set metric 100 route-map PEER-B-IN permit 20 match ipv6 address prefix-list PEER-B-PREFIXES set community 65001:11111 ! route-map PEER-C-IN permit 10 match ipv6 address prefix-list COMMON-PREFIXES set metric 200 route-map PEER-C-IN permit 20 match ipv6 address prefix-list PEER-C-PREFIXES set community 65001:22222 ! route-map PEER-B-OUT permit 10 match ipv6 address prefix-list PEER-A-PREFIXES ! route-map PEER-C-OUT permit 10 match ipv6 address prefix-list PEER-A-PREFIXES ! line vty ! .. _configuration-of-the-bgp-routers-with-route-server: Configuration of the BGP routers with Route Server -------------------------------------------------- To convert the initial scenario into one with route server, first we must modify the configuration of routers RA, RB and RC. Now they must not peer between them, but only with the route server. For example, RA's configuration would turn into: .. code-block:: frr # Configuration for router 'RA' ! hostname RA password **** ! router bgp 65001 no bgp default ipv4-unicast neighbor 2001:0DB8::FFFF remote-as 65000 ! address-family ipv6 network 2001:0DB8:AAAA:1::/64 network 2001:0DB8:AAAA:2::/64 network 2001:0DB8:0000:1::/64 network 2001:0DB8:0000:2::/64 neighbor 2001:0DB8::FFFF activate neighbor 2001:0DB8::FFFF soft-reconfiguration inbound exit-address-family ! line vty ! Which is logically much simpler than its initial configuration, as it now maintains only one BGP peering and all the filters (route-maps) have disappeared. .. _configuration-of-the-route-server-itself: Configuration of the Route Server itself ---------------------------------------- As we said when we described the functions of a route server (:ref:`description-of-the-route-server-model`), it is in charge of all the route filtering. To achieve that, the In and Out filters from the RA, RB and RC configurations must be converted into Import and Export policies in the route server. This is a fragment of the route server configuration (we only show the policies for client RA): .. code-block:: frr # Configuration for Route Server ('RS') ! hostname RS password ix ! router bgp 65000 view RS no bgp default ipv4-unicast neighbor 2001:0DB8::A remote-as 65001 neighbor 2001:0DB8::B remote-as 65002 neighbor 2001:0DB8::C remote-as 65003 ! address-family ipv6 neighbor 2001:0DB8::A activate neighbor 2001:0DB8::A route-server-client neighbor 2001:0DB8::A route-map RSCLIENT-A-IMPORT import neighbor 2001:0DB8::A route-map RSCLIENT-A-EXPORT export neighbor 2001:0DB8::A soft-reconfiguration inbound neighbor 2001:0DB8::B activate neighbor 2001:0DB8::B route-server-client neighbor 2001:0DB8::B route-map RSCLIENT-B-IMPORT import neighbor 2001:0DB8::B route-map RSCLIENT-B-EXPORT export neighbor 2001:0DB8::B soft-reconfiguration inbound neighbor 2001:0DB8::C activate neighbor 2001:0DB8::C route-server-client neighbor 2001:0DB8::C route-map RSCLIENT-C-IMPORT import neighbor 2001:0DB8::C route-map RSCLIENT-C-EXPORT export neighbor 2001:0DB8::C soft-reconfiguration inbound exit-address-family ! ipv6 prefix-list COMMON-PREFIXES seq 5 permit 2001:0DB8:0000::/48 ge 64 le 64 ipv6 prefix-list COMMON-PREFIXES seq 10 deny any ! ipv6 prefix-list PEER-A-PREFIXES seq 5 permit 2001:0DB8:AAAA::/48 ge 64 le 64 ipv6 prefix-list PEER-A-PREFIXES seq 10 deny any ! ipv6 prefix-list PEER-B-PREFIXES seq 5 permit 2001:0DB8:BBBB::/48 ge 64 le 64 ipv6 prefix-list PEER-B-PREFIXES seq 10 deny any ! ipv6 prefix-list PEER-C-PREFIXES seq 5 permit 2001:0DB8:CCCC::/48 ge 64 le 64 ipv6 prefix-list PEER-C-PREFIXES seq 10 deny any ! route-map RSCLIENT-A-IMPORT permit 10 match peer 2001:0DB8::B call A-IMPORT-FROM-B route-map RSCLIENT-A-IMPORT permit 20 match peer 2001:0DB8::C call A-IMPORT-FROM-C ! route-map A-IMPORT-FROM-B permit 10 match ipv6 address prefix-list COMMON-PREFIXES set metric 100 route-map A-IMPORT-FROM-B permit 20 match ipv6 address prefix-list PEER-B-PREFIXES set community 65001:11111 ! route-map A-IMPORT-FROM-C permit 10 match ipv6 address prefix-list COMMON-PREFIXES set metric 200 route-map A-IMPORT-FROM-C permit 20 match ipv6 address prefix-list PEER-C-PREFIXES set community 65001:22222 ! route-map RSCLIENT-A-EXPORT permit 10 match peer 2001:0DB8::B match ipv6 address prefix-list PEER-A-PREFIXES route-map RSCLIENT-A-EXPORT permit 20 match peer 2001:0DB8::C match ipv6 address prefix-list PEER-A-PREFIXES ! ... ... ... If you compare the initial configuration of RA with the route server configuration above, you can see how easy it is to generate the Import and Export policies for RA from the In and Out route-maps of RA's original configuration. When there was no route server, RA maintained two peerings, one with RB and another with RC. Each of this peerings had an In route-map configured. To build the Import route-map for client RA in the route server, simply add route-map entries following this scheme: :: route-map permit 10 match peer call route-map permit 20 match peer call This is exactly the process that has been followed to generate the route-map RSCLIENT-A-IMPORT. The route-maps that are called inside it (A-IMPORT-FROM-B and A-IMPORT-FROM-C) are exactly the same than the In route-maps from the original configuration of RA (PEER-B-IN and PEER-C-IN), only the name is different. The same could have been done to create the Export policy for RA (route-map RSCLIENT-A-EXPORT), but in this case the original Out route-maps where so simple that we decided not to use the `call WORD` commands, and we integrated all in a single route-map (RSCLIENT-A-EXPORT). The Import and Export policies for RB and RC are not shown, but the process would be identical. Further considerations about Import and Export route-maps --------------------------------------------------------- The current version of the route server patch only allows to specify a route-map for import and export policies, while in a standard BGP speaker apart from route-maps there are other tools for performing input and output filtering (access-lists, community-lists, ...). But this does not represent any limitation, as all kinds of filters can be included in import/export route-maps. For example suppose that in the non-route-server scenario peer RA had the following filters configured for input from peer B: .. code-block:: frr neighbor 2001:0DB8::B prefix-list LIST-1 in neighbor 2001:0DB8::B filter-list LIST-2 in neighbor 2001:0DB8::B route-map PEER-B-IN in ... ... route-map PEER-B-IN permit 10 match ipv6 address prefix-list COMMON-PREFIXES set local-preference 100 route-map PEER-B-IN permit 20 match ipv6 address prefix-list PEER-B-PREFIXES set community 65001:11111 It is possible to write a single route-map which is equivalent to the three filters (the community-list, the prefix-list and the route-map). That route-map can then be used inside the Import policy in the route server. Lets see how to do it: .. code-block:: frr neighbor 2001:0DB8::A route-map RSCLIENT-A-IMPORT import ... ! ... route-map RSCLIENT-A-IMPORT permit 10 match peer 2001:0DB8::B call A-IMPORT-FROM-B ... ... ! route-map A-IMPORT-FROM-B permit 1 match ipv6 address prefix-list LIST-1 match as-path LIST-2 on-match goto 10 route-map A-IMPORT-FROM-B deny 2 route-map A-IMPORT-FROM-B permit 10 match ipv6 address prefix-list COMMON-PREFIXES set local-preference 100 route-map A-IMPORT-FROM-B permit 20 match ipv6 address prefix-list PEER-B-PREFIXES set community 65001:11111 ! ... ... The route-map A-IMPORT-FROM-B is equivalent to the three filters (LIST-1, LIST-2 and PEER-B-IN). The first entry of route-map A-IMPORT-FROM-B (sequence number 1) matches if and only if both the prefix-list LIST-1 and the filter-list LIST-2 match. If that happens, due to the 'on-match goto 10' statement the next route-map entry to be processed will be number 10, and as of that point route-map A-IMPORT-FROM-B is identical to PEER-B-IN. If the first entry does not match, `on-match goto 10`' will be ignored and the next processed entry will be number 2, which will deny the route. Thus, the result is the same that with the three original filters, i.e., if either LIST-1 or LIST-2 rejects the route, it does not reach the route-map PEER-B-IN. In case both LIST-1 and LIST-2 accept the route, it passes to PEER-B-IN, which can reject, accept or modify the route. frr-7.2.1/doc/user/rpki.rst0000644000000000000000000002054613610377563012445 00000000000000.. _prefix-origin-validation-using-rpki: Prefix Origin Validation Using RPKI =================================== Prefix Origin Validation allows BGP routers to verify if the origin AS of an IP prefix is legitimate to announce this IP prefix. The required attestation objects are stored in the Resource Public Key Infrastructure (:abbr:`RPKI`). However, RPKI-enabled routers do not store cryptographic data itself but only validation information. The validation of the cryptographic data (so called Route Origin Authorization, or short :abbr:`ROA`, objects) will be performed by trusted cache servers. The RPKI/RTR protocol defines a standard mechanism to maintain the exchange of the prefix/origin AS mapping between the cache server and routers. In combination with a BGP Prefix Origin Validation scheme a router is able to verify received BGP updates without suffering from cryptographic complexity. The RPKI/RTR protocol is defined in :rfc:`6810` and the validation scheme in :rfc:`6811`. The current version of Prefix Origin Validation in FRR implements both RFCs. For a more detailed but still easy-to-read background, we suggest: - [Securing-BGP]_ - [Resource-Certification]_ .. _features-of-the-current-implementation: Features of the Current Implementation -------------------------------------- In a nutshell, the current implementation provides the following features - The BGP router can connect to one or more RPKI cache servers to receive validated prefix to origin AS mappings. Advanced failover can be implemented by server sockets with different preference values. - If no connection to an RPKI cache server can be established after a pre-defined timeout, the router will process routes without prefix origin validation. It still will try to establish a connection to an RPKI cache server in the background. - By default, enabling RPKI does not change best path selection. In particular, invalid prefixes will still be considered during best path selection. However, the router can be configured to ignore all invalid prefixes. - Route maps can be configured to match a specific RPKI validation state. This allows the creation of local policies, which handle BGP routes based on the outcome of the Prefix Origin Validation. - Updates from the RPKI cache servers are directly applied and path selection is updated accordingly. (Soft reconfiguration **must** be enabled for this to work). .. _enabling-rpki: Enabling RPKI ------------- .. index:: rpki .. clicmd:: rpki This command enables the RPKI configuration mode. Most commands that start with *rpki* can only be used in this mode. When it is used in a telnet session, leaving of this mode cause rpki to be initialized. Executing this command alone does not activate prefix validation. You need to configure at least one reachable cache server. See section :ref:`configuring-rpki-rtr-cache-servers` for configuring a cache server. .. index:: RPKI and daemons When first installing FRR with RPKI support from the pre-packaged binaries. Remember to add ``-M rpki`` to the variable ``bgpd_options`` in :file:`/etc/frr/daemons` , like so:: bgpd_options=" -A 127.0.0.1 -M rpki" instead of the default setting:: bgpd_options=" -A 127.0.0.1" Otherwise you will encounter an error when trying to enter RPKI configuration mode due to the ``rpki`` module not being loaded when the BGP daemon is initialized. Examples of the error:: router(config)# debug rpki % [BGP] Unknown command: debug rpki router(config)# rpki % [BGP] Unknown command: rpki Note that the RPKI commands will be available in vtysh when running ``find rpki`` regardless of whether the module is loaded. .. _configuring-rpki-rtr-cache-servers: Configuring RPKI/RTR Cache Servers ---------------------------------- The following commands are independent of a specific cache server. .. index:: rpki polling_period (1-3600) .. clicmd:: rpki polling_period (1-3600) .. index:: no rpki polling_period .. clicmd:: no rpki polling_period Set the number of seconds the router waits until the router asks the cache again for updated data. The default value is 300 seconds. The following commands configure one or multiple cache servers. .. index:: rpki cache (A.B.C.D|WORD) PORT [SSH_USERNAME] [SSH_PRIVKEY_PATH] [SSH_PUBKEY_PATH] [KNOWN_HOSTS_PATH] PREFERENCE .. clicmd:: rpki cache (A.B.C.D|WORD) PORT [SSH_USERNAME] [SSH_PRIVKEY_PATH] [SSH_PUBKEY_PATH] [KNOWN_HOSTS_PATH] PREFERENCE .. index:: no rpki cache (A.B.C.D|WORD) [PORT] PREFERENCE .. clicmd:: no rpki cache (A.B.C.D|WORD) [PORT] PREFERENCE Add a cache server to the socket. By default, the connection between router and cache server is based on plain TCP. Protecting the connection between router and cache server by SSH is optional. Deleting a socket removes the associated cache server and terminates the existing connection. A.B.C.D|WORD Address of the cache server. PORT Port number to connect to the cache server SSH_USERNAME SSH username to establish an SSH connection to the cache server. SSH_PRIVKEY_PATH Local path that includes the private key file of the router. SSH_PUBKEY_PATH Local path that includes the public key file of the router. KNOWN_HOSTS_PATH Local path that includes the known hosts file. The default value depends on the configuration of the operating system environment, usually :file:`~/.ssh/known_hosts`. .. _validating-bgp-updates: Validating BGP Updates ---------------------- .. index:: match rpki notfound|invalid|valid .. clicmd:: match rpki notfound|invalid|valid .. index:: no match rpki notfound|invalid|valid .. clicmd:: no match rpki notfound|invalid|valid Create a clause for a route map to match prefixes with the specified RPKI state. In the following example, the router prefers valid routes over invalid prefixes because invalid routes have a lower local preference. .. code-block:: frr ! Allow for invalid routes in route selection process route bgp 60001 ! ! Set local preference of invalid prefixes to 10 route-map rpki permit 10 match rpki invalid set local-preference 10 ! ! Set local preference of valid prefixes to 500 route-map rpki permit 500 match rpki valid set local-preference 500 .. _debugging: Debugging --------- .. index:: debug rpki .. clicmd:: debug rpki .. index:: no debug rpki .. clicmd:: no debug rpki Enable or disable debugging output for RPKI. .. _displaying-rpki: Displaying RPKI --------------- .. index:: show rpki prefix-table .. clicmd:: show rpki prefix-table Display all validated prefix to origin AS mappings/records which have been received from the cache servers and stored in the router. Based on this data, the router validates BGP Updates. .. index:: show rpki cache-connection .. clicmd:: show rpki cache-connection Display all configured cache servers, whether active or not. RPKI Configuration Example -------------------------- .. code-block:: frr hostname bgpd1 password zebra ! log stdout debug bgp updates debug bgp keepalives debug rpki ! rpki rpki polling_period 1000 rpki timeout 10 ! SSH Example: rpki cache example.com 22 rtr-ssh ./ssh_key/id_rsa ./ssh_key/id_rsa.pub preference 1 ! TCP Example: rpki cache rpki-validator.realmv6.org 8282 preference 2 exit ! router bgp 60001 bgp router-id 141.22.28.223 network 192.168.0.0/16 neighbor 123.123.123.0 remote-as 60002 neighbor 123.123.123.0 route-map rpki in ! address-family ipv6 neighbor 123.123.123.0 activate neighbor 123.123.123.0 route-map rpki in exit-address-family ! route-map rpki permit 10 match rpki invalid set local-preference 10 ! route-map rpki permit 20 match rpki notfound set local-preference 20 ! route-map rpki permit 30 match rpki valid set local-preference 30 ! route-map rpki permit 40 ! .. [Securing-BGP] Geoff Huston, Randy Bush: Securing BGP, In: The Internet Protocol Journal, Volume 14, No. 2, 2011. .. [Resource-Certification] Geoff Huston: Resource Certification, In: The Internet Protocol Journal, Volume 12, No.1, 2009. frr-7.2.1/doc/user/setup.rst0000644000000000000000000001554313610377563012641 00000000000000.. _basic-setup: Basic Setup ============ After installing FRR, some basic configuration must be completed before it is ready to use. Daemons Configuration File -------------------------- After a fresh install, starting FRR will do nothing. This is because daemons must be explicitly enabled by editing a file in your configuration directory. This file is usually located at :file:`/etc/frr/daemons` and determines which daemons are activated when issuing a service start / stop command via init or systemd. The file initially looks like this: :: zebra=no bgpd=no ospfd=no ospf6d=no ripd=no ripngd=no isisd=no pimd=no ldpd=no nhrpd=no eigrpd=no babeld=no sharpd=no staticd=no pbrd=no bfdd=no fabricd=no # # If this option is set the /etc/init.d/frr script automatically loads # the config via "vtysh -b" when the servers are started. # Check /etc/pam.d/frr if you intend to use "vtysh"! # vtysh_enable=yes zebra_options=" -s 90000000 --daemon -A 127.0.0.1" bgpd_options=" --daemon -A 127.0.0.1" ospfd_options=" --daemon -A 127.0.0.1" ospf6d_options=" --daemon -A ::1" ripd_options=" --daemon -A 127.0.0.1" ripngd_options=" --daemon -A ::1" isisd_options=" --daemon -A 127.0.0.1" pimd_options=" --daemon -A 127.0.0.1" ldpd_options=" --daemon -A 127.0.0.1" nhrpd_options=" --daemon -A 127.0.0.1" eigrpd_options=" --daemon -A 127.0.0.1" babeld_options=" --daemon -A 127.0.0.1" sharpd_options=" --daemon -A 127.0.0.1" staticd_options=" --daemon -A 127.0.0.1" pbrd_options=" --daemon -A 127.0.0.1" bfdd_options=" --daemon -A 127.0.0.1" fabricd_options=" --daemon -A 127.0.0.1" #MAX_FDS=1024 # The list of daemons to watch is automatically generated by the init script. #watchfrr_options="" # for debugging purposes, you can specify a "wrap" command to start instead # of starting the daemon directly, e.g. to use valgrind on ospfd: # ospfd_wrap="/usr/bin/valgrind" # or you can use "all_wrap" for all daemons, e.g. to use perf record: # all_wrap="/usr/bin/perf record --call-graph -" # the normal daemon command is added to this at the end. Breaking this file down: :: bgpd=yes To enable a particular daemon, simply change the corresponding 'no' to 'yes'. Subsequent service restarts should start the daemon. :: vtysh_enable=yes As the comment says, this causes :ref:`VTYSH ` to apply configuration when starting the daemons. This is useful for a variety of reasons touched on in the VTYSH documentation and should generally be enabled. :: MAX_FDS=1024 This allows the operator to control the number of open file descriptors each daemon is allowed to start with. The current assumed value on most operating systems is 1024. If the operator plans to run bgp with several thousands of peers than this is where we would modify FRR to allow this to happen. :: zebra_options=" -s 90000000 --daemon -A 127.0.0.1" bgpd_options=" --daemon -A 127.0.0.1" ... The next set of lines controls what options are passed to daemons when started from the service script. Usually daemons will have ``--daemon`` and ``-A
`` specified in order to daemonize and listen for VTY commands on a particular address. The remaining file content regarding `watchfrr_options` and `*_wrap` settings should not normally be needed; refer to the comments in case they are. Services -------- FRR daemons have their own terminal interface or VTY. After installation, it's a good idea to setup each daemon's port number to connect to them. To do this add the following entries to :file:`/etc/services`. :: zebrasrv 2600/tcp # zebra service zebra 2601/tcp # zebra vty ripd 2602/tcp # RIPd vty ripngd 2603/tcp # RIPngd vty ospfd 2604/tcp # OSPFd vty bgpd 2605/tcp # BGPd vty ospf6d 2606/tcp # OSPF6d vty ospfapi 2607/tcp # ospfapi isisd 2608/tcp # ISISd vty babeld 2609/tcp # BABELd vty nhrpd 2610/tcp # nhrpd vty pimd 2611/tcp # PIMd vty ldpd 2612/tcp # LDPd vty eigprd 2613/tcp # EIGRPd vty bfdd 2617/tcp # bfdd vty fabricd 2618/tcp # fabricd vty vrrpd 2619/tcp # vrrpd vty If you use a FreeBSD newer than 2.2.8, the above entries are already added to :file:`/etc/services` so there is no need to add it. If you specify a port number when starting the daemon, these entries may not be needed. You may need to make changes to the config files in |INSTALL_PREFIX_ETC|. Systemd ------- Although not installed when installing from source, FRR provides a service file for use with ``systemd``. It is located in :file:`tools/frr.service` in the Git repository. If ``systemctl status frr.service`` indicates that the FRR service is not found, copy the service file from the Git repository into your preferred location. A good place is usually ``/etc/systemd/system/``. After issuing a ``systemctl daemon-reload``, you should be able to start the FRR service via ``systemctl start frr``. If this fails, or no daemons are started. check the ``journalctl`` logs for an indication of what went wrong. Operations ---------- This section covers a few common operational tasks and how to perform them. Restarting ^^^^^^^^^^ Restarting kills all running FRR daemons and starts them again. Any unsaved configuration will be lost. .. code-block:: console service frr restart .. note:: Alternatively, you can invoke the init script directly:: /etc/init.d/frr restart Or, if using systemd:: systemctl restart frr Reloading ^^^^^^^^^ Reloading applies the differential between on-disk configuration and the current effective configuration of running FRR processes. This includes starting daemons that were previously stopped and any changes made to individual or unified daemon configuration files. .. code-block:: console service frr reload .. note:: Alternatively, you can invoke the init script directly:: /etc/init.d/frr reload Or, if using systemd:: systemctl reload frr Starting a new daemon ^^^^^^^^^^^^^^^^^^^^^ Suppose *bgpd* and *zebra* are running, and you wish to start *pimd*. In ``/etc/frr/daemons`` make the following change: .. code-block:: diff - pimd=no + pimd=yes Then perform a reload. Currently there is no way to stop or restart an individual daemon. This is because FRR's monitoring program cannot currently distinguish between a crashed / killed daemon versus one that has been intentionally stopped or restarted. The closest that can be achieved is to remove all configuration for the daemon, and set its line in ``/etc/frr/daemons`` to ``=no``. Once this is done, the daemon will be stopped the next time FRR is restarted. frr-7.2.1/doc/user/sharp.rst0000644000000000000000000000673213610377563012616 00000000000000.. _sharp: ***** SHARP ***** :abbr:`SHARP (Super Happy Advanced Routing Process)` is a daemon that provides miscellaneous functionality used for testing FRR and creating proof-of-concept labs. .. _starting-sharp: Starting SHARP ============== Default configuration file for *sharpd* is :file:`sharpd.conf`. The typical location of :file:`sharpd.conf` is |INSTALL_PREFIX_ETC|/sharpd.conf. If the user is using integrated config, then :file:`sharpd.conf` need not be present and the :file:`frr.conf` is read instead. .. program:: sharpd :abbr:`SHARP` supports all the common FRR daemon start options which are documented elsewhere. .. _using-sharp: Using SHARP =========== All sharp commands are under the enable node and preceded by the ``sharp`` keyword. At present, no sharp commands will be preserved in the config. .. index:: sharp install .. clicmd:: sharp install routes A.B.C.D |nexthop-group NAME> (1-1000000) [instance (0-255)] [repeat (2-1000)] Install up to 1,000,000 (one million) /32 routes starting at ``A.B.C.D`` with specified nexthop ``E.F.G.H`` or ``X:X::X:X``. The nexthop is a ``NEXTHOP_TYPE_IPV4`` or ``NEXTHOP_TYPE_IPV6`` and must be reachable to be installed into the kernel. Alternatively a nexthop-group NAME can be specified and used as the nexthops. The routes are installed into zebra as ``ZEBRA_ROUTE_SHARP`` and can be used as part of a normal route redistribution. Route installation time is noted in the debug log. When zebra successfully installs a route into the kernel and SHARP receives success notifications for all routes this is logged as well. Instance (0-255) if specified causes the routes to be installed in a different instance. If repeat is used then we will install/uninstall the routes the number of times specified. .. index:: sharp remove .. clicmd:: sharp remove routes A.B.C.D (1-1000000) Remove up to 1,000,000 (one million) /32 routes starting at ``A.B.C.D``. The routes are removed from zebra. Route deletion start is noted in the debug log and when all routes have been successfully deleted the debug log will be updated with this information as well. .. index:: sharp data route .. clicmd:: sharp data route Allow end user doing route install and deletion to get timing information from the vty or vtysh instead of having to read the log file. This command is informational only and you should look at sharp_vty.c for explanation of the output as that it may change. .. index:: sharp label .. clicmd:: sharp label vrf NAME label (0-1000000) Install a label into the kernel that causes the specified vrf NAME table to be used for pop and forward operations when the specified label is seen. .. index:: sharp watch .. clicmd:: [no] sharp watch |import [connected] Instruct zebra to monitor and notify sharp when the specified nexthop is changed. The notification from zebra is written into the debug log. The nexthop or import choice chooses the type of nexthop we are asking zebra to watch for us. This choice affects zebra's decision on what matches. Connected tells zebra whether or not that we want the route matched against to be a static or connected route. The no form of the command obviously turns this watching off. .. index:: sharp data nexthop .. clicmd:: sharp data nexthop Allow end user to dump associated data with the nexthop tracking that may have been turned on. frr-7.2.1/doc/user/snmp.rst0000644000000000000000000000533613610377563012455 00000000000000.. _snmp-support: ************ SNMP Support ************ :abbr:`SNMP (Simple Network Managing Protocol)` is a widely implemented feature for collecting network information from router and/or host. FRR itself does not support SNMP agent (server daemon) functionality but is able to connect to a SNMP agent using the the AgentX protocol (:rfc:`2741`) and make the routing protocol MIBs available through it. Note that SNMP Support needs to be enabled at compile-time and loaded as module on daemon startup. Refer to :ref:`loadable-module-support` on the latter. .. _getting-and-installing-an-snmp-agent: Getting and installing an SNMP agent ==================================== The supported SNMP agent is AgentX. We recommend to use the latest version of `net-snmp` which was formerly known as `ucd-snmp`. It is free and open software and available at `http://www.net-snmp.org/ `_ and as binary package for most Linux distributions. .. _agentx-configuration: AgentX configuration ==================== .. program:: configure To enable AgentX protocol support, FRR must have been build with the :option:`--enable-snmp` or `--enable-snmp=agentx` option. Both the master SNMP agent (snmpd) and each of the FRR daemons must be configured. In :file:`/etc/snmp/snmpd.conf`, the ``master agentx`` directive should be added. In each of the FRR daemons, ``agentx`` command will enable AgentX support. :file:`/etc/snmp/zebra.conf`: :: # # example access restrictions setup # com2sec readonly default public group MyROGroup v1 readonly view all included .1 80 access MyROGroup "" any noauth exact all none none # # enable master agent for AgentX subagents # master agentx :file:`/etc/frr/ospfd.conf:` .. code-block:: frr ! ... the rest of ospfd.conf has been omitted for clarity ... ! agentx ! Upon successful connection, you should get something like this in the log of each FRR daemons: :: 2012/05/25 11:39:08 ZEBRA: snmp[info]: NET-SNMP version 5.4.3 AgentX subagent connected Then, you can use the following command to check everything works as expected: :: # snmpwalk -c public -v1 localhost .1.3.6.1.2.1.14.1.1 OSPF-MIB::ospfRouterId.0 = IpAddress: 192.168.42.109 [...] The AgentX protocol can be transported over a Unix socket or using TCP or UDP. It usually defaults to a Unix socket and depends on how NetSNMP was built. If need to configure FRR to use another transport, you can configure it through :file:`/etc/snmp/frr.conf`: :: [snmpd] # Use a remote master agent agentXSocket tcp:192.168.15.12:705 Here is the syntax for using AgentX: .. index:: agentx .. clicmd:: agentx .. index:: no agentx .. clicmd:: no agentx .. include:: snmptrap.rst frr-7.2.1/doc/user/snmptrap.rst0000644000000000000000000001325313610377563013341 00000000000000Handling SNMP Traps =================== To handle snmp traps make sure your snmp setup of frr works correctly as described in the frr documentation in :ref:`snmp-support`. The BGP4 mib will send traps on peer up/down events. These should be visible in your snmp logs with a message similar to: :: snmpd[13733]: Got trap from peer on fd 14 To react on these traps they should be handled by a trapsink. Configure your trapsink by adding the following lines to :file:`/etc/snmpd/snmpd.conf`: :: # send traps to the snmptrapd on localhost trapsink localhost This will send all traps to an snmptrapd running on localhost. You can of course also use a dedicated management station to catch traps. Configure the snmptrapd daemon by adding the following line to :file:`/etc/snmpd/snmptrapd.conf`: :: traphandle .1.3.6.1.4.1.3317.1.2.2 /etc/snmp/snmptrap_handle.sh This will use the bash script :file:`/etc/snmp/snmptrap_handle.sh` to handle the BGP4 traps. To add traps for other protocol daemons, lookup their appropriate OID from their mib. (For additional information about which traps are supported by your mib, lookup the mib on `http://www.oidview.com/mibs/detail.html `_). Make sure *snmptrapd* is started. The snmptrap_handle.sh script I personally use for handling BGP4 traps is below. You can of course do all sorts of things when handling traps, like sound a siren, have your display flash, etc., be creative ;). .. code-block:: shell #!/bin/bash # routers name ROUTER=`hostname -s` #email address use to sent out notification EMAILADDR="john@doe.com" #email address used (allongside above) where warnings should be sent EMAILADDR_WARN="sms-john@doe.com" # type of notification TYPE="Notice" # local snmp community for getting AS belonging to peer COMMUNITY="" # if a peer address is in $WARN_PEERS a warning should be sent WARN_PEERS="192.0.2.1" # get stdin INPUT=`cat -` # get some vars from stdin uptime=`echo $INPUT | cut -d' ' -f5` peer=`echo $INPUT | cut -d' ' -f8 | sed -e 's/SNMPv2-SMI::mib-2.15.3.1.14.//g'` peerstate=`echo $INPUT | cut -d' ' -f13` errorcode=`echo $INPUT | cut -d' ' -f9 | sed -e 's/\\"//g'` suberrorcode=`echo $INPUT | cut -d' ' -f10 | sed -e 's/\\"//g'` remoteas=`snmpget -v2c -c $COMMUNITY localhost SNMPv2-SMI::mib-2.15.3.1.9.$peer | cut -d' ' -f4` WHOISINFO=`whois -h whois.ripe.net " -r AS$remoteas" | egrep '(as-name|descr)'` asname=`echo "$WHOISINFO" | grep "^as-name:" | sed -e 's/^as-name://g' -e 's/ //g' -e 's/^ //g' | uniq` asdescr=`echo "$WHOISINFO" | grep "^descr:" | sed -e 's/^descr://g' -e 's/ //g' -e 's/^ //g' | uniq` # if peer address is in $WARN_PEER, the email should also # be sent to $EMAILADDR_WARN for ip in $WARN_PEERS; do if [ "x$ip" == "x$peer" ]; then EMAILADDR="$EMAILADDR,$EMAILADDR_WARN" TYPE="WARNING" break fi done # convert peer state case "$peerstate" in 1) peerstate="Idle" ;; 2) peerstate="Connect" ;; 3) peerstate="Active" ;; 4) peerstate="Opensent" ;; 5) peerstate="Openconfirm" ;; 6) peerstate="Established" ;; *) peerstate="Unknown" ;; esac # get textual messages for errors case "$errorcode" in 00) error="No error" suberror="" ;; 01) error="Message Header Error" case "$suberrorcode" in 01) suberror="Connection Not Synchronized" ;; 02) suberror="Bad Message Length" ;; 03) suberror="Bad Message Type" ;; *) suberror="Unknown" ;; esac ;; 02) error="OPEN Message Error" case "$suberrorcode" in 01) suberror="Unsupported Version Number" ;; 02) suberror="Bad Peer AS" ;; 03) suberror="Bad BGP Identifier" ;; 04) suberror="Unsupported Optional Parameter" ;; 05) suberror="Authentication Failure" ;; 06) suberror="Unacceptable Hold Time" ;; *) suberror="Unknown" ;; esac ;; 03) error="UPDATE Message Error" case "$suberrorcode" in 01) suberror="Malformed Attribute List" ;; 02) suberror="Unrecognized Well-known Attribute" ;; 03) suberror="Missing Well-known Attribute" ;; 04) suberror="Attribute Flags Error" ;; 05) suberror="Attribute Length Error" ;; 06) suberror="Invalid ORIGIN Attribute" ;; 07) suberror="AS Routing Loop" ;; 08) suberror="Invalid NEXT_HOP Attribute" ;; 09) suberror="Optional Attribute Error" ;; 10) suberror="Invalid Network Field" ;; 11) suberror="Malformed AS_PATH" ;; *) suberror="Unknown" ;; esac ;; 04) error="Hold Timer Expired" suberror="" ;; 05) error="Finite State Machine Error" suberror="" ;; 06) error="Cease" case "$suberrorcode" in 01) suberror="Maximum Number of Prefixes Reached" ;; 02) suberror="Administratively Shutdown" ;; 03) suberror="Peer Unconfigured" ;; 04) suberror="Administratively Reset" ;; 05) suberror="Connection Rejected" ;; 06) suberror="Other Configuration Change" ;; 07) suberror="Connection collision resolution" ;; 08) suberror="Out of Resource" ;; 09) suberror="MAX" ;; *) suberror="Unknown" ;; esac ;; *) error="Unknown" suberror="" ;; esac # create textual message from errorcodes if [ "x$suberror" == "x" ]; then NOTIFY="$errorcode ($error)" else NOTIFY="$errorcode/$suberrorcode ($error/$suberror)" fi # form a decent subject SUBJECT="$TYPE: $ROUTER [bgp] $peer is $peerstate: $NOTIFY" # create the email body MAIL=`cat << EOF BGP notification on router $ROUTER. Peer: $peer AS: $remoteas New state: $peerstate Notification: $NOTIFY Info: $asname $asdescr Snmpd uptime: $uptime EOF` # mail the notification echo "$MAIL" | mail -s "$SUBJECT" $EMAILADDR frr-7.2.1/doc/user/static.rst0000644000000000000000000001075213610377563012765 00000000000000.. _static: ****** STATIC ****** :abbr:`STATIC` is a daemon that handles the installation and deletion of static routes. .. _starting-static: Starting STATIC =============== Default configuration file for *staticd* is :file:`staticd.conf`. The typical location of :file:`staticd.conf` is |INSTALL_PREFIX_ETC|/staticd.conf. If the user is using integrated config, then :file:`staticd.conf` need not be present and the :file:`frr.conf` is read instead. If the user has not fully upgraded to using the staticd.conf and still has a non-integrated config with zebra.conf holding the static routes, *staticd* will read in the :file:`zebrad.conf` as a backup. .. program:: staticd :abbr:`STATIC` supports all the common FRR daemon start options which are documented elsewhere. .. _static-route-commands: Static Route Commands ===================== Static routing is a very fundamental feature of routing technology. It defines a static prefix and gateway. .. index:: ip route NETWORK GATEWAY table TABLENO nexthop-vrf VRFNAME DISTANCE vrf VRFNAME .. clicmd:: ip route NETWORK GATEWAY table TABLENO nexthop-vrf VRFNAME DISTANCE vrf VRFNAME .. index:: ipv6 route NETWORK from SRCPREFIX GATEWAY table TABLENO nexthop-vrf VRFNAME DISTANCE vrf VRFNAME .. clicmd:: ipv6 route NETWORK from SRCPREFIX GATEWAY table TABLENO nexthop-vrf VRFNAME DISTANCE vrf VRFNAME NETWORK is destination prefix with a valid v4 or v6 network based upon initial form of the command. GATEWAY is gateway for the prefix it currently must match the v4 or v6 route type specified at the start of the command. GATEWAY can also be treated as an interface name. If the interface name is ``null0`` then zebra installs a blackhole route. TABLENO is an optional parameter for namespaces that allows you to create the route in a specified table associated with the vrf namespace. table will be rejected if you are not using namespace based vrfs. ``nexthop-vrf`` allows you to create a leaked route with a nexthop in the specified VRFNAME vrf VRFNAME allows you to create the route in a specified vrf. ``nexthop-vrf`` cannot be currently used with namespace based vrfs currently as well. The v6 variant allows the installation of a static source-specific route with the SRCPREFIX sub command. These routes are currently supported on Linux operating systems only, and perform AND matching on packet's destination and source addresses in the kernel's forwarding path. Note that destination longest-prefix match is "more important" than source LPM, e.g. ``2001:db8:1::/64 from 2001:db8::/48`` will win over ``2001:db8::/48 from 2001:db8:1::/64`` if both match. .. _multiple-route-command: Multiple nexthop static route ============================= To create multiple nexthops to the same NETWORK, just reenter the same network statement with different nexthop information. .. code-block:: frr ip route 10.0.0.1/32 10.0.0.2 ip route 10.0.0.1/32 10.0.0.3 ip route 10.0.0.1/32 eth0 If there is no route to 10.0.0.2 and 10.0.0.3, and interface eth0 is reachable, then the last route is installed into the kernel. If zebra has been compiled with multipath support, and both 10.0.0.2 and 10.0.0.3 are reachable, zebra will install a multipath route via both nexthops, if the platform supports this. :: router> show ip route S> 10.0.0.1/32 [1/0] via 10.0.0.2 inactive via 10.0.0.3 inactive * is directly connected, eth0 .. code-block:: frr ip route 10.0.0.0/8 10.0.0.2 ip route 10.0.0.0/8 10.0.0.3 ip route 10.0.0.0/8 null0 255 This will install a multihop route via the specified next-hops if they are reachable, as well as a high-distance blackhole route, which can be useful to prevent traffic destined for a prefix to match less-specific routes (e.g. default) should the specified gateways not be reachable. E.g.: :: router> show ip route 10.0.0.0/8 Routing entry for 10.0.0.0/8 Known via "static", distance 1, metric 0 10.0.0.2 inactive 10.0.0.3 inactive Routing entry for 10.0.0.0/8 Known via "static", distance 255, metric 0 directly connected, Null0 Also, if the user wants to configure a static route for a specific VRF, then a specific VRF configuration mode is available. After entering into that mode with :clicmd:`vrf VRF` the user can enter the same route command as before, but this time, the route command will apply to the VRF. .. code-block:: frr # case with VRF configure vrf r1-cust1 ip route 10.0.0.0/24 10.0.0.2 exit-vrf frr-7.2.1/doc/user/subdir.am0000644000000000000000000000531013610377563012545 00000000000000# # doc/user # user_RSTFILES = \ doc/user/babeld.rst \ doc/user/ldpd.rst \ doc/user/basic.rst \ doc/user/bgp.rst \ doc/user/bmp.rst \ doc/user/bugs.rst \ doc/user/conf.py \ doc/user/eigrpd.rst \ doc/user/fabricd.rst \ doc/user/filter.rst \ doc/user/glossary.rst \ doc/user/index.rst \ doc/user/installation.rst \ doc/user/ipv6.rst \ doc/user/isisd.rst \ doc/user/kernel.rst \ doc/user/nhrpd.rst \ doc/user/ospf6d.rst \ doc/user/ospfd.rst \ doc/user/ospf_fundamentals.rst \ doc/user/overview.rst \ doc/user/packet-dumps.rst \ doc/user/pim.rst \ doc/user/ripd.rst \ doc/user/pbr.rst \ doc/user/ripngd.rst \ doc/user/routemap.rst \ doc/user/routeserver.rst \ doc/user/rpki.rst \ doc/user/setup.rst \ doc/user/sharp.rst \ doc/user/snmp.rst \ doc/user/snmptrap.rst \ doc/user/static.rst \ doc/user/vnc.rst \ doc/user/vrrp.rst \ doc/user/vtysh.rst \ doc/user/zebra.rst \ doc/user/bfd.rst \ doc/user/flowspec.rst \ # end EXTRA_DIST += \ $(user_RSTFILES) \ doc/user/Useful_Sysctl_Settings.md \ # end USERBUILD = doc/user/_build $(USERBUILD)/.doctrees/environment.pickle: $(user_RSTFILES) # # automake integration (things that should be built in "all") # if DOC nodist_noinst_DATA += $(USERBUILD)/texinfo/frr.info endif if DOC_HTML nodist_noinst_DATA += $(USERBUILD)/html/.buildinfo endif # # standard targets # .PHONY: info html pdf info: $(USERBUILD)/texinfo/frr.info html: $(USERBUILD)/html/.buildinfo pdf: $(USERBUILD)/latexpdf # # hook-ins for clean / install / doc # .PHONY: clean-userdocs clean-local: clean-userdocs clean-userdocs: -rm -rf "$(USERBUILD)" # INSTALL_INFO=install-info .PHONY: install-info uninstall-info install-html uninstall-html install-info: $(USERBUILD)/texinfo/frr.info $(MKDIR_P) "$(DESTDIR)$(infodir)" $(INSTALL_DATA) "$<" "$(DESTDIR)$(infodir)" [ -z "${DESTDIR}" ] && $(INSTALL_INFO) --info-dir="$(DESTDIR)$(infodir)" "$<" || true uninstall-info: $(USERBUILD)/texinfo/frr.info -rm -f "$(DESTDIR)$(infodir)/$<" [ -z "${DESTDIR}" ] && $(INSTALL_INFO) --delete --info-dir="$(DESTDIR)$(infodir)" "$<" || true install-html: $(USERBUILD)/html/.buildinfo $(MKDIR_P) "$(DESTDIR)$(htmldir)" cp -r "$(USERBUILD)/html" "$(DESTDIR)$(htmldir)" uninstall-html: -rm -rf "$(DESTDIR)$(htmldir)/html" .PHONY: install-data-local uninstall-local if DOC DOC_INFO=info TARGET_INSTALL_INFO=install-info TARGET_UNINSTALL_INFO=uninstall-info endif if DOC_HTML DOC_HTML=html TARGET_INSTALL_HTML=install-html TARGET_UNINSTALL_HTML=uninstall-html endif # leave the comments in, this was causing weird reordering issues in automake install-data-local: $(TARGET_INSTALL_INFO) $(TARGET_INSTALL_HTML) # uninstall-local: $(TARGET_UNINSTALL_INFO) $(TARGET_UNINSTALL_HTML) # doc: $(DOC_INFO) $(DOC_HTML) frr-7.2.1/doc/user/vnc.rst0000644000000000000000000015112213610377563012261 00000000000000.. _vnc-and-vnc-gw: ************** VNC and VNC-GW ************** This chapter describes how to use :abbr:`VNC (Virtual Network Control)` services, including :abbr:`NVA (Network Virtualization Authority)` and :abbr:`VNC-GW (VNC Gateway)` functions. Background information on NVAs, :abbr:`NVE (Network Virtualization Edge)` s, :abbr:`UN (Underlay Network)` s, and :abbr:`VN (Virtual Network)` is available from the `IETF `_. :abbr:`VNC-GW (VNC Gateway)` s support the import/export of routing information between VNC and :abbr:`CE (customer edge)` routers operating within a VN. Both IP/Layer 3 (L3) VNs, and IP with Ethernet/Layer 2 (L2) VNs are supported. BGP, with IP VPNs and Tunnel Encapsulation, is used to distribute VN information between NVAs. BGP based IP VPN support is defined in :rfc:`4364`, and :rfc:`4659`. Encapsulation information is provided via the Tunnel Encapsulation Attribute, :rfc:`5512`. The protocol that is used to communicate routing and Ethernet / Layer 2 (L2) forwarding information between NVAs and NVEs is referred to as the Remote Forwarder Protocol (RFP). `OpenFlow` is an example RFP. Specific RFP implementations may choose to implement either a `hard-state` or `soft-state` prefix and address registration model. To support a `soft-state` refresh model, a `lifetime` in seconds is associated with all registrations and responses. The chapter also provides sample configurations for basic example scenarios. .. _configuring-vnc: Configuring VNC =============== Virtual Network Control (:abbr:`VNC`) service configuration commands appear in the `router bgp` section of the BGPD configuration file (:ref:`bgp-configuration-examples`). The commands are broken down into the following areas: - :dfn:`General VNC` configuration applies to general VNC operation and is primarily used to control the method used to advertise tunnel information. - :dfn:`Remote Forwarder Protocol (RFP)` configuration relates to the protocol used between NVAs and NVEs. - :dfn:`VNC Defaults` provides default parameters for registered NVEs. - :dfn:`VNC NVE Group` provides for configuration of a specific set of registered NVEs and overrides default parameters. - :dfn:`Redistribution` and :dfn:`Export` control VNC-GW operation, i.e., the import/export of routing information between VNC and customer edge routers (:abbr:`CE` s) operating within a VN. .. _general-vnc-configuration: General VNC Configuration ------------------------- .. _rfp-related-configuration: RFP Related Configuration ------------------------- The protocol that is used to communicate routing and Ethernet / L2 forwarding information between NVAs and NVEs is referred to as the Remote Forwarder Protocol (RFP). Currently, only a simple example RFP is included in FRR. Developers may use this example as a starting point to integrate FRR with an RFP of their choosing, e.g., `OpenFlow`. The example code includes the following sample configuration: .. clicmd:: rfp example-config-value VALUE This is a simple example configuration parameter included as part of the RFP example code. VALUE must be in the range of 0 to 4294967295. .. _vnc-defaults-configuration: VNC Defaults Configuration -------------------------- The VNC Defaults section allows the user to specify default values for configuration parameters for all registered NVEs. Default values are overridden by :ref:`vnc-nve-group-configuration`. .. clicmd:: vnc defaults Enter VNC configuration mode for specifying VNC default behaviors. Use `exit-vnc` to leave VNC configuration mode. `vnc defaults` is optional. .. code-block:: frr vnc defaults ... various VNC defaults exit-vnc These are the statements that can appear between ``vnc defaults`` and ``exit-vnc``. Documentation for these statements is given in :ref:`vnc-nve-group-configuration`. - :clicmd:`rt import RT-LIST` - :clicmd:`rt export RT-LIST` - :clicmd:`rt both RT-LIST` - :clicmd:`rd ROUTE-DISTINGUISHER` - :clicmd:`l2rd NVE-ID-VALUE` - :clicmd:`response-lifetime LIFETIME|infinite` - :clicmd:`export bgp|zebra route-map MAP-NAME` - :clicmd:`export bgp|zebra no route-map` .. index:: exit-vnc .. clicmd:: exit-vnc Exit VNC configuration mode. .. _vnc-nve-group-configuration: VNC NVE Group Configuration --------------------------- A NVE Group corresponds to a specific set of NVEs. A Client NVE is assigned to an NVE Group based on whether there is a match for either its virtual or underlay network address against the VN and/or UN address prefixes specified in the NVE Group definition. When an NVE Group definition specifies both VN and UN address prefixes, then an NVE must match both prefixes in order to be assigned to the NVE Group. In the event that multiple NVE Groups match based on VN and/or UN addresses, the NVE is assigned to the first NVE Group listed in the configuration. If an NVE is not assigned to an NVE Group, its messages will be ignored. Configuration values specified for an NVE group apply to all member NVEs and override configuration values specified in the VNC Defaults section. **At least one `nve-group` is mandatory for useful VNC operation.** .. index:: vnc nve-group NAME .. clicmd:: vnc nve-group NAME Enter VNC configuration mode for defining the NVE group `name`. Use `exit` or `exit-vnc` to exit group configuration mode. .. code-block:: frr vnc nve-group group1 ... configuration commands exit-vnc .. index:: no vnc nve-group NAME .. clicmd:: no vnc nve-group NAME Delete the NVE group named `name`. The following statements are valid in an NVE group definition: .. index:: l2rd NVE-ID-VALUE .. clicmd:: l2rd NVE-ID-VALUE Set the value used to distinguish NVEs connected to the same physical Ethernet segment (i.e., at the same location) [#]_. The nve-id subfield may be specified as either a literal value in the range 1-255, or it may be specified as `auto:vn`, which means to use the least-significant octet of the originating NVE's VN address. .. index:: prefix vn|un A.B.C.D/M|X:X::X:X/M .. clicmd:: prefix vn|un A.B.C.D/M|X:X::X:X/M Specify the matching prefix for this NVE group by either virtual-network address (`vn`) or underlay-network address (`un`). Either or both virtual-network and underlay-network prefixes may be specified. Subsequent virtual-network or underlay-network values within a `vnc nve-group` `exit-vnc` block override their respective previous values. These prefixes are used only for determining assignments of NVEs to NVE Groups. .. index:: rd ROUTE-DISTINGUISHER .. clicmd:: rd ROUTE-DISTINGUISHER Specify the route distinguisher for routes advertised via BGP VPNs. The route distinguisher must be in one of these forms: - ``IPv4-address:two-byte-integer`` - ``four-byte-autonomous-system-number:two-byte-integer`` - ``two-byte-autonomous-system-number:four-byte-integer`` - ``auto:vn:two-byte-integer`` Routes originated by NVEs in the NVE group will use the group's specified `route-distinguisher` when they are advertised via BGP. If the `auto` form is specified, it means that a matching NVE has its RD set to ``rd_type=IP=1:IPv4-address=VN-address:two-byte-integer``, for IPv4 VN addresses and ``rd_type=IP=1:IPv4-address=Last-four-bytes-of-VN-address:two-byte-integer``, for IPv6 VN addresses. If the NVE group definition does not specify a `route-distinguisher`, then the default `route-distinguisher` is used. If neither a group nor a default `route-distinguisher` is configured, then the advertised RD is set to ``two-byte-autonomous-system-number=0:four-byte-integer=0``. .. index:: response-lifetime LIFETIME|infinite .. clicmd:: response-lifetime LIFETIME|infinite Specify the response lifetime, in seconds, to be included in RFP response messages sent to NVEs. If the value 'infinite' is given, an infinite lifetime will be used. Note that this parameter is not the same as the lifetime supplied by NVEs in RFP registration messages. This parameter does not affect the lifetime value attached to routes sent by this server via BGP. If the NVE group definition does not specify a `response-lifetime`, the default `response-lifetime` will be used. If neither a group nor a default `response-lifetime` is configured, the value 3600 will be used. The maximum response lifetime is 2147483647. .. index:: rt export RT-LIST .. clicmd:: rt export RT-LIST .. index:: rt import RT-LIST .. clicmd:: rt import RT-LIST .. index:: rt both RT-LIST .. clicmd:: rt both RT-LIST Specify route target import and export lists. `rt-list` is a space-separated list of route targets, each element of which is in one of the following forms: - ``IPv4-address:two-byte-integer`` - ``four-byte-autonomous-system-number:two-byte-integer`` - ``two-byte-autonomous-system-number:four-byte-integer`` The first form, `rt export`, specifies an `export rt-list`. The `export rt-list` will be attached to routes originated by NVEs in the NVE group when they are advertised via BGP. If the NVE group definition does not specify an `export rt-list`, then the default `export rt-list` is used. If neither a group nor a default `export rt-list` is configured, then no RT list will be sent; in turn, these routes will probably not be processed by receiving NVAs. The second form, `rt import` specifies an `import rt-list`, which is a filter for incoming routes. In order to be made available to NVEs in the group, incoming BGP VPN routes must have RT lists that have at least one route target in common with the group's `import rt-list`. If the NVE group definition does not specify an import filter, then the default `import rt-list` is used. If neither a group nor a default `import rt-list` is configured, there can be no RT intersections when receiving BGP routes and therefore no incoming BGP routes will be processed for the group. The third, `rt both`, is a shorthand way of specifying both lists simultaneously, and is equivalent to `rt export `rt-list`` followed by `rt import `rt-list``. .. index:: export bgp|zebra route-map MAP-NAME .. clicmd:: export bgp|zebra route-map MAP-NAME Specify that the named route-map should be applied to routes being exported to bgp or zebra. This parameter is used in conjunction with :ref:`configuring-export-of-routes-to-other-routing-protocols`. This item is optional. .. index:: export bgp|zebra no route-map .. clicmd:: export bgp|zebra no route-map Specify that no route-map should be applied to routes being exported to bgp or zebra. This parameter is used in conjunction with :ref:`configuring-export-of-routes-to-other-routing-protocols`. This item is optional. .. index:: export bgp|zebra ipv4|ipv6 prefix-list LIST-NAME .. clicmd:: export bgp|zebra ipv4|ipv6 prefix-list LIST-NAME Specify that the named prefix-list filter should be applied to routes being exported to bgp or zebra. Prefix-lists for ipv4 and ipv6 are independent of each other. This parameter is used in conjunction with :ref:`configuring-export-of-routes-to-other-routing-protocols`. This item is optional. .. index:: export bgp|zebra no ipv4|ipv6 prefix-list .. clicmd:: export bgp|zebra no ipv4|ipv6 prefix-list Specify that no prefix-list filter should be applied to routes being exported to bgp or zebra. This parameter is used in conjunction with :ref:`configuring-export-of-routes-to-other-routing-protocols`. This item is optional. .. _vnc-l2-group-configuration: VNC L2 Group Configuration -------------------------- The route targets advertised with prefixes and addresses registered by an NVE are determined based on the NVE's associated VNC NVE Group Configuration, :ref:`vnc-nve-group-configuration`. Layer 2 (L2) Groups are used to override the route targets for an NVE's Ethernet registrations based on the Logical Network Identifier and label value. A Logical Network Identifier is used to uniquely identify a logical Ethernet segment and is conceptually similar to the Ethernet Segment Identifier defined in :rfc:`7432`. Both the Logical Network Identifier and Label are passed to VNC via RFP prefix and address registration. Note that a corresponding NVE group configuration must be present, and that other NVE associated configuration information, notably RD, is not impacted by L2 Group Configuration. .. index:: vnc l2-group NAME .. clicmd:: vnc l2-group NAME Enter VNC configuration mode for defining the L2 group `name`. Use `exit` or `exit-vnc` to exit group configuration mode. .. code-block:: frr vnc l2-group group1 ... configuration commands exit-vnc .. index:: no vnc l2-group NAME .. clicmd:: no vnc l2-group NAME Delete the L2 group named `name`. The following statements are valid in a L2 group definition: .. index:: logical-network-id VALUE .. clicmd:: logical-network-id VALUE Define the Logical Network Identifier with a value in the range of 0-4294967295 that identifies the logical Ethernet segment. .. index:: labels LABEL-LIST .. clicmd:: labels LABEL-LIST .. index:: no labels LABEL-LIST .. clicmd:: no labels LABEL-LIST Add or remove labels associated with the group. `label-list` is a space separated list of label values in the range of 0-1048575. .. index:: rt import RT-TARGET .. clicmd:: rt import RT-TARGET .. index:: rt export RT-TARGET .. clicmd:: rt export RT-TARGET .. index:: rt both RT-TARGET .. clicmd:: rt both RT-TARGET Specify the route target import and export value associated with the group. A complete definition of these parameters is given above, :ref:`vnc-nve-group-configuration`. .. _configuring-redistribution-of-routes-from-other-routing-protocols: Configuring Redistribution of Routes from Other Routing Protocols ----------------------------------------------------------------- Routes from other protocols (including BGP) can be provided to VNC (both for RFP and for redistribution via BGP) from three sources: the zebra kernel routing process; directly from the main (default) unicast BGP RIB; or directly from a designated BGP unicast exterior routing RIB instance. The protocol named in the `vnc redistribute` command indicates the route source: `bgp-direct` routes come directly from the main (default) unicast BGP RIB and are available for RFP and are redistributed via BGP; `bgp-direct-to-nve-groups` routes come directly from a designated BGP unicast routing RIB and are made available only to RFP; and routes from other protocols come from the zebra kernel routing process. Note that the zebra process does not need to be active if only `bgp-direct` or `bgp-direct-to-nve-groups` routes are used. zebra routes ^^^^^^^^^^^^ Routes originating from protocols other than BGP must be obtained via the zebra routing process. Redistribution of these routes into VNC does not support policy mechanisms such as prefix-lists or route-maps. bgp-direct routes ^^^^^^^^^^^^^^^^^ `bgp-direct` redistribution supports policy via prefix lists and route-maps. This policy is applied to incoming original unicast routes before the redistribution translations (described below) are performed. Redistribution of `bgp-direct` routes is performed in one of three possible modes: `plain`, `nve-group`, or `resolve-nve`. The default mode is `plain`. These modes indicate the kind of translations applied to routes before they are added to the VNC RIB. In `plain` mode, the route's next hop is unchanged and the RD is set based on the next hop. For `bgp-direct` redistribution, the following translations are performed: - The VN address is set to the original unicast route's next hop address. - The UN address is NOT set. (VN->UN mapping will occur via ENCAP route or attribute, based on `vnc advertise-un-method` setting, generated by the RFP registration of the actual NVE) - The RD is set to as if auto:vn:0 were specified (i.e., `rd_type=IP=1`:`IPv4-address=VN-address`:`two-byte-integer=0`) - The RT list is included in the extended community list copied from the original unicast route (i.e., it must be set in the original unicast route). In `nve-group` mode, routes are registered with VNC as if they came from an NVE in the nve-group designated in the `vnc redistribute nve-group` command. The following translations are performed: - The next hop/VN address is set to the VN prefix configured for the redistribute nve-group. - The UN address is set to the UN prefix configured for the redistribute nve-group. - The RD is set to the RD configured for the redistribute nve-group. - The RT list is set to the RT list configured for the redistribute nve-group. If `bgp-direct` routes are being redistributed, any extended communities present in the original unicast route will also be included. In `resolve-nve` mode, the next hop of the original BGP route is typically the address of an NVE connected router (CE) connected by one or more NVEs. Each of the connected NVEs will register, via RFP, a VNC host route to the CE. This mode may be though of as a mechanism to proxy RFP registrations of BGP unicast routes on behalf of registering NVEs. Multiple copies of the BGP route, one per matching NVE host route, will be added to VNC. In other words, for a given BGP unicast route, each instance of a RFP-registered host route to the unicast route's next hop will result in an instance of an imported VNC route. Each such imported VNC route will have a prefix equal to the original BGP unicast route's prefix, and a next hop equal to the next hop of the matching RFP-registered host route. If there is no RFP-registered host route to the next hop of the BGP unicast route, no corresponding VNC route will be imported. The following translations are applied: - The Next Hop is set to the next hop of the NVE route (i.e., the VN address of the NVE). - The extended community list in the new route is set to the union of: - Any extended communities in the original BGP route - Any extended communities in the NVE route - An added route-origin extended community with the next hop of the original BGP route is added to the new route. The value of the local administrator field defaults 5226 but may be configured by the user via the `roo-ec-local-admin` parameter. - The Tunnel Encapsulation attribute is set to the value of the Tunnel Encapsulation attribute of the NVE route, if any. bgp-direct-to-nve-groups routes ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ Unicast routes from the main or a designated instance of BGP may be redistributed to VNC as bgp-direct-to-nve-groups routes. These routes are NOT announced via BGP, but they are made available for local RFP lookup in response to queries from NVEs. A non-main/default BGP instance is configured using the `router bgp AS view NAME` command as described elsewhere in this document. In order for a route in the unicast BGP RIB to be made available to a querying NVE, there must already be, available to that NVE, an (interior) VNC route matching the next hop address of the unicast route. When the unicast route is provided to the NVE, its next hop is replaced by the next hop of the corresponding NVE. If there are multiple longest-prefix-match VNC routes, the unicast route will be replicated for each. There is currently no policy (prefix-list or route-map) support for `bgp-direct-to-nve-groups` routes. Redistribution Command Syntax ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ .. index:: vnc redistribute ipv4|ipv6 bgp|bgp-direct|ipv6 bgp-direct-to-nve-groups|connected|kernel|ospf|rip|static .. clicmd:: vnc redistribute ipv4|ipv6 bgp|bgp-direct|ipv6 bgp-direct-to-nve-groups|connected|kernel|ospf|rip|static .. index:: vnc redistribute ipv4|ipv6 bgp-direct-to-nve-groups view VIEWNAME .. clicmd:: vnc redistribute ipv4|ipv6 bgp-direct-to-nve-groups view VIEWNAME .. index:: no vnc redistribute ipv4|ipv6 bgp|bgp-direct|bgp-direct-to-nve-groups|connected|kernel|ospf|rip|static .. clicmd:: no vnc redistribute ipv4|ipv6 bgp|bgp-direct|bgp-direct-to-nve-groups|connected|kernel|ospf|rip|static Import (or do not import) prefixes from another routing protocols. Specify both the address family to import (`ipv4` or `ipv6`) and the protocol (`bgp`, `bgp-direct`, `bgp-direct-to-nve-groups`, `connected`, `kernel`, `ospf`, `rip`, or `static`). Repeat this statement as needed for each combination of address family and routing protocol. Prefixes from protocol `bgp-direct` are imported from unicast BGP in the same bgpd process. Prefixes from all other protocols (including `bgp`) are imported via the `zebra` kernel routing process. .. index:: vnc redistribute mode plain|nve-group|resolve-nve .. clicmd:: vnc redistribute mode plain|nve-group|resolve-nve Redistribute routes from other protocols into VNC using the specified mode. Not all combinations of modes and protocols are supported. .. index:: vnc redistribute nve-group GROUP-NAME .. clicmd:: vnc redistribute nve-group GROUP-NAME .. index:: no vnc redistribute nve-group GROUP-NAME .. clicmd:: no vnc redistribute nve-group GROUP-NAME When using `nve-group` mode, assign (or do not assign) the NVE group `group-name` to routes redistributed from another routing protocol. `group-name` must be configured using `vnc nve-group`. The VN and UN prefixes of the nve-group must both be configured, and each prefix must be specified as a full-length (/32 for IPv4, /128 for IPv6) prefix. .. index:: vnc redistribute lifetime LIFETIME|infinite .. clicmd:: vnc redistribute lifetime LIFETIME|infinite Assign a registration lifetime, either `lifetime` seconds or `infinite`, to prefixes redistributed from other routing protocols as if they had been received via RFP registration messages from an NVE. `lifetime` can be any integer between 1 and 4294967295, inclusive. .. index:: vnc redistribute resolve-nve roo-ec-local-admin 0-65536 .. clicmd:: vnc redistribute resolve-nve roo-ec-local-admin 0-65536 Assign a value to the local-administrator subfield used in the Route Origin extended community that is assigned to routes exported under the `resolve-nve` mode. The default value is `5226`. The following four `prefix-list` and `route-map` commands may be specified in the context of an nve-group or not. If they are specified in the context of an nve-group, they apply only if the redistribution mode is `nve-group`, and then only for routes being redistributed from `bgp-direct`. If they are specified outside the context of an nve-group, then they apply only for redistribution modes `plain` and `resolve-nve`, and then only for routes being redistributed from `bgp-direct`. .. index:: vnc redistribute bgp-direct (ipv4|ipv6) prefix-list LIST-NAME .. clicmd:: vnc redistribute bgp-direct (ipv4|ipv6) prefix-list LIST-NAME When redistributing `bgp-direct` routes, specifies that the named prefix-list should be applied. .. index:: vnc redistribute bgp-direct no (ipv4|ipv6) prefix-list .. clicmd:: vnc redistribute bgp-direct no (ipv4|ipv6) prefix-list When redistributing `bgp-direct` routes, specifies that no prefix-list should be applied. .. index:: vnc redistribute bgp-direct route-map MAP-NAME .. clicmd:: vnc redistribute bgp-direct route-map MAP-NAME When redistributing `bgp-direct` routes, specifies that the named route-map should be applied. .. index:: vnc redistribute bgp-direct no route-map .. clicmd:: vnc redistribute bgp-direct no route-map When redistributing `bgp-direct` routes, specifies that no route-map should be applied. .. _configuring-export-of-routes-to-other-routing-protocols: Configuring Export of Routes to Other Routing Protocols ------------------------------------------------------- Routes from VNC (both for RFP and for redistribution via BGP) can be provided to other protocols, either via zebra or directly to BGP. It is important to note that when exporting routes to other protocols, the downstream protocol must also be configured to import the routes. For example, when VNC routes are exported to unicast BGP, the BGP configuration must include a corresponding `redistribute vnc-direct` statement. .. index:: export bgp|zebra mode none|group-nve|registering-nve|ce .. clicmd:: export bgp|zebra mode none|group-nve|registering-nve|ce Specify how routes should be exported to bgp or zebra. If the mode is `none`, routes are not exported. If the mode is `group-nve`, routes are exported according to nve-group or vrf-policy group configuration (:ref:`vnc-nve-group-configuration`): if a group is configured to allow export, then each prefix visible to the group is exported with next hops set to the currently-registered NVEs. If the mode is `registering-nve`, then all VNC routes are exported with their original next hops. If the mode is `ce`, only VNC routes that have an NVE connected CE Router encoded in a Route Origin Extended Community are exported. This extended community must have an administrative value that matches the configured `roo-ec-local-admin` value. The next hop of the exported route is set to the encoded NVE connected CE Router. The default for both bgp and zebra is mode `none`. .. index:: vnc export bgp|zebra group-nve group GROUP-NAME .. clicmd:: vnc export bgp|zebra group-nve group GROUP-NAME .. index:: vnc export bgp|zebra group-nve no group GROUP-NAME .. clicmd:: vnc export bgp|zebra group-nve no group GROUP-NAME When export mode is `group-nve`, export (or do not export) prefixes from the specified nve-group or vrf-policy group to unicast BGP or to zebra. Repeat this statement as needed for each nve-group to be exported. Each VNC prefix that is exported will result in N exported routes to the prefix, each with a next hop corresponding to one of the N NVEs currently associated with the nve-group. Some commands have a special meaning under certain export modes. :clicmd:`export bgp|zebra ipv4|ipv6 prefix-list LIST-NAME` When export mode is `ce` or `registering-nve`, specifies that the named prefix-list should be applied to routes being exported to bgp or zebra. Prefix-lists for ipv4 and ipv6 are independent of each other. :clicmd:`export bgp|zebra no ipv4|ipv6 prefix-list` When export mode is `ce` or `registering-nve`, specifies that no prefix-list should be applied to routes being exported to bgp or zebra. :clicmd:`export bgp|zebra route-map MAP-NAME` When export mode is `ce` or `registering-nve`, specifies that the named route-map should be applied to routes being exported to bgp or zebra. :clicmd:`export bgp|zebra no route-map` When export mode is `ce` or `registering-nve`, specifies that no route-map should be applied to routes being exported to bgp or zebra. When the export mode is `group-nve`, policy for exported routes is specified per-NVE-group or vrf-policy group inside a `nve-group` `RFG-NAME` block via the following commands(:ref:`vnc-nve-group-configuration`): :clicmd:`export bgp|zebra route-map MAP-NAME` This command is valid inside a `nve-group` `RFG-NAME` block. It specifies that the named route-map should be applied to routes being exported to bgp or zebra. :clicmd:`export bgp|zebra no route-map` This command is valid inside a `nve-group` `RFG-NAME` block. It specifies that no route-map should be applied to routes being exported to bgp or zebra. :clicmd:`export bgp|zebra ipv4|ipv6 prefix-list LIST-NAME` This command is valid inside a `nve-group` `RFG-NAME` block. It specifies that the named prefix-list filter should be applied to routes being exported to bgp or zebra. Prefix-lists for ipv4 and ipv6 are independent of each other. :clicmd:`export bgp|zebra no ipv4|ipv6 prefix-list` This command is valid inside a `nve-group` `RFG-NAME` block. It specifies that no prefix-list filter should be applied to routes being exported to bgp or zebra. .. _manual-address-control: Manual Address Control ====================== The commands in this section can be used to augment normal dynamic VNC. The `add vnc` commands can be used to manually add IP prefix or Ethernet MAC address forwarding information. The `clear vnc` commands can be used to remove manually and dynamically added information. .. clicmd:: add vnc prefix (A.B.C.D/M|X:X::X:X/M) vn (A.B.C.D|X:X::X:X) un (A.B.C.D|X:X::X:X) [cost (0-255)] [lifetime (infinite|(1-4294967295))] [local-next-hop (A.B.C.D|X:X::X:X) [local-cost (0-255)]] Register an IP prefix on behalf of the NVE identified by the VN and UN addresses. The `cost` parameter provides the administrative preference of the forwarding information for remote advertisement. If omitted, it defaults to 255 (lowest preference). The `lifetime` parameter identifies the period, in seconds, that the information remains valid. If omitted, it defaults to `infinite`. The optional `local-next-hop` parameter is used to configure a nexthop to be used by an NVE to reach the prefix via a locally connected CE router. This information remains local to the NVA, i.e., not passed to other NVAs, and is only passed to registered NVEs. When specified, it is also possible to provide a `local-cost` parameter to provide a forwarding preference. If omitted, it defaults to 255 (lowest preference). .. clicmd:: add vnc mac xx:xx:xx:xx:xx:xx virtual-network-identifier (1-4294967295) vn (A.B.C.D|X:X::X:X) un (A.B.C.D|X:X::X:X) [prefix (A.B.C.D/M|X:X::X:X/M)] [cost (0-255)] [lifetime (infinite|(1-4294967295))] Register a MAC address for a logical Ethernet (L2VPN) on behalf of the NVE identified by the VN and UN addresses. The optional `prefix` parameter is to support enable IP address mediation for the given prefix. The `cost` parameter provides the administrative preference of the forwarding information. If omitted, it defaults to 255. The `lifetime` parameter identifies the period, in seconds, that the information remains valid. If omitted, it defaults to `infinite`. .. clicmd:: clear vnc prefix (\*|A.B.C.D/M|X:X::X:X/M) (\*|[(vn|un) (A.B.C.D|X:X::X:X|\*) [(un|vn) (A.B.C.D|X:X::X:X|\*)] [mac xx:xx:xx:xx:xx:xx] [local-next-hop (A.B.C.D|X:X::X:X)]) Delete the information identified by prefix, VN address, and UN address. Any or all of these parameters may be wildcarded to (potentially) match more than one registration. The optional `mac` parameter specifies a layer-2 MAC address that must match the registration(s) to be deleted. The optional `local-next-hop` parameter is used to delete specific local nexthop information. .. index:: clear vnc mac (\\*|xx:xx:xx:xx:xx:xx) virtual-network-identifier (\\*|(1-4294967295)) (\\*|[(vn|un) (A.B.C.D|X:X::X:X|\\*) [(un|vn) (A.B.C.D|X:X::X:X|\*)] [prefix (\\*|A.B.C.D/M|X:X::X:X/M)]) .. clicmd:: clear vnc mac (\*|xx:xx:xx:xx:xx:xx) virtual-network-identifier (\*|(1-4294967295)) (\*|[(vn|un) (A.B.C.D|X:X::X:X|\*) [(un|vn) (A.B.C.D|X:X::X:X|\*)] [prefix (\*|A.B.C.D/M|X:X::X:X/M)]) Delete mac forwarding information. Any or all of these parameters may be wildcarded to (potentially) match more than one registration. The default value for the `prefix` parameter is the wildcard value `*`. .. index:: clear vnc nve (\*|((vn|un) (A.B.C.D|X:X::X:X) [(un|vn) (A.B.C.D|X:X::X:X)])) .. clicmd:: clear vnc nve (\*|((vn|un) (A.B.C.D|X:X::X:X) [(un|vn) (A.B.C.D|X:X::X:X)])) Delete prefixes associated with the NVE specified by the given VN and UN addresses. It is permissible to specify only one of VN or UN, in which case any matching registration will be deleted. It is also permissible to specify `*` in lieu of any VN or UN address, in which case all registrations will match. .. _other-vnc-related-commands: Other VNC-Related Commands ========================== Note: VNC-Related configuration can be obtained via the `show running-configuration` command when in `enable` mode. The following commands are used to clear and display Virtual Network Control related information: .. index:: clear vnc counters .. clicmd:: clear vnc counters Reset the counter values stored by the NVA. Counter values can be seen using the `show vnc` commands listed above. This command is only available in `enable` mode. .. index:: show vnc summary .. clicmd:: show vnc summary Print counter values and other general information about the NVA. Counter values can be reset using the `clear vnc counters` command listed below. .. index:: show vnc nves .. clicmd:: show vnc nves .. index:: show vnc nves vn|un ADDRESS .. clicmd:: show vnc nves vn|un ADDRESS Display the NVA's current clients. Specifying `address` limits the output to the NVEs whose addresses match `address`. The time since the NVA last communicated with the NVE, per-NVE summary counters and each NVE's addresses will be displayed. .. index:: show vnc queries .. clicmd:: show vnc queries .. index:: show vnc queries PREFIX .. clicmd:: show vnc queries PREFIX Display active Query information. Queries remain valid for the default Response Lifetime (:ref:`vnc-defaults-configuration`) or NVE-group Response Lifetime (:ref:`vnc-nve-group-configuration`). Specifying `prefix` limits the output to Query Targets that fall within `prefix`. Query information is provided for each querying NVE, and includes the Query Target and the time remaining before the information is removed. .. index:: show vnc registrations [all|local|remote|holddown|imported] .. clicmd:: show vnc registrations [all|local|remote|holddown|imported] .. index:: show vnc registrations [all|local|remote|holddown|imported] PREFIX .. clicmd:: show vnc registrations [all|local|remote|holddown|imported] PREFIX Display local, remote, holddown, and/or imported registration information. Local registrations are routes received via RFP, which are present in the NVA Registrations Cache. Remote registrations are routes received via BGP (VPN SAFIs), which are present in the NVE-group import tables. Holddown registrations are local and remote routes that have been withdrawn but whose holddown timeouts have not yet elapsed. Imported information represents routes that are imported into NVA and are made available to querying NVEs. Depending on configuration, imported routes may also be advertised via BGP. Specifying `prefix` limits the output to the registered prefixes that fall within `prefix`. Registration information includes the registered prefix, the registering NVE addresses, the registered administrative cost, the registration lifetime and the time since the information was registered or, in the case of Holddown registrations, the amount of time remaining before the information is removed. .. index:: show vnc responses [active|removed] .. clicmd:: show vnc responses [active|removed] .. index:: show vnc responses [active|removed] PREFIX .. clicmd:: show vnc responses [active|removed] PREFIX Display all, active and/or removed response information which are present in the NVA Responses Cache. Responses remain valid for the default Response Lifetime (:ref:`vnc-defaults-configuration`) or NVE-group Response Lifetime (:ref:`vnc-nve-group-configuration`.) When Removal Responses are enabled (:ref:`general-vnc-configuration`), such responses are listed for the Response Lifetime. Specifying `prefix` limits the output to the addresses that fall within `prefix`. Response information is provided for each querying NVE, and includes the response prefix, the prefix-associated registering NVE addresses, the administrative cost, the provided response lifetime and the time remaining before the information is to be removed or will become inactive. .. index:: show memory vnc .. clicmd:: show memory vnc Print the number of memory items allocated by the NVA. .. _example-vnc-and-vnc-gw-configurations: Example VNC and VNC-GW Configurations ===================================== .. _vnc-mesh-nva-config: Mesh NVA Configuration ---------------------- This example includes three NVAs, nine NVEs, and two NVE groups. Note that while not shown, a single physical device may support multiple logical NVEs. :ref:`vnc-fig-vnc-mesh` shows ``code NVA-1`` (192.168.1.100), ``NVA 2`` (192.168.1.101), and ``NVA 3`` (192.168.1.102), which are connected in a full mesh. Each is a member of the autonomous system 64512. Each NVA provides VNC services to three NVE clients in the 172.16.0.0/16 virtual-network address range. The 172.16.0.0/16 address range is partitioned into two NVE groups, ``group1`` (172.16.0.0/17) and ``group2`` (172.16.128.0/17). Each NVE belongs to either NVE group ``group1`` or NVE group ``group2``. The NVEs ``NVE 1``, ``NVE 2``, ``NVE 4``, ``NVE 7``, and ``NVE 8`` are members of the NVE group ``group1``. The NVEs ``NVE 3``, ``NVE 5``, ``NVE 6``, and ``NVE 9`` are members of the NVE group ``group2``. Each NVA advertises NVE underlay-network IP addresses using the Tunnel Encapsulation Attribute. .. _vnc-fig-vnc-mesh: .. figure:: ../figures/fig-vnc-mesh.png :align: center :alt: Three-way Mesh A three-way full mesh with three NVEs per NVA. :file:`bgpd.conf` for ``NVA 1`` (192.168.1.100): .. code-block:: frr router bgp 64512 bgp router-id 192.168.1.100 neighbor 192.168.1.101 remote-as 64512 neighbor 192.168.1.102 remote-as 64512 address-family ipv4 vpn neighbor 192.168.1.101 activate neighbor 192.168.1.102 activate exit-address-family vnc defaults rd 64512:1 response-lifetime 200 rt both 1000:1 1000:2 exit-vnc vnc nve-group group1 prefix vn 172.16.0.0/17 rt both 1000:1 exit-vnc vnc nve-group group2 prefix vn 172.16.128.0/17 rt both 1000:2 exit-vnc exit :file:`bgpd.conf` for ``NVA 2`` (192.168.1.101): .. code-block:: frr router bgp 64512 bgp router-id 192.168.1.101 neighbor 192.168.1.100 remote-as 64512 neighbor 192.168.1.102 remote-as 64512 address-family ipv4 vpn neighbor 192.168.1.100 activate neighbor 192.168.1.102 activate exit-address-family vnc nve-group group1 prefix vn 172.16.0.0/17 rd 64512:1 response-lifetime 200 rt both 1000:1 1000:2 exit-vnc exit :file:`bgpd.conf` for ``NVA 3`` (192.168.1.102): .. code-block:: frr router bgp 64512 bgp router-id 192.168.1.102 neighbor 192.168.1.101 remote-as 64512 neighbor 192.168.1.102 remote-as 64512 address-family ipv4 vpn neighbor 192.168.1.100 activate neighbor 192.168.1.101 activate exit-address-family vnc defaults rd 64512:1 response-lifetime 200 rt both 1000:1 1000:2 exit-vnc vnc nve-group group1 prefix vn 172.16.128.0/17 exit-vnc exit Mesh NVA and VNC-GW Configuration --------------------------------- This example includes two NVAs, each with two associated NVEs, and two VNC-GWs, each supporting two CE routers physically attached to the four NVEs. Note that this example is showing a more complex configuration where VNC-GW is separated from normal NVA functions; it is equally possible to simplify the configuration and combine NVA and VNC-GW functions in a single FRR instance. .. _vnc-fig-vnc-gw: .. figure:: ../figures/fig-vnc-gw.png :align: center :alt: FRR VNC Gateway Meshed NVEs and VNC-GWs As shown in :ref:`vnc-fig-vnc-gw`, NVAs and VNC-GWs are connected in a full iBGP mesh. The VNC-GWs each have two CEs configured as route-reflector clients. Each client provides BGP updates with unicast routes that the VNC-GW reflects to the other client. The VNC-GW also imports these unicast routes into VPN routes to be shared with the other VNC-GW and the two NVAs. This route importation is controlled with the ``vnc redistribute`` statements shown in the configuration. Similarly, registrations sent by NVEs via RFP to the NVAs are exported by the VNC-GWs to the route-reflector clients as unicast routes. RFP registrations exported this way have a next-hop address of the CE behind the connected (registering) NVE. Exporting VNC routes as IPv4 unicast is enabled with the ``vnc export`` command below. The configuration for ``VNC-GW 1`` is shown below. .. code-block:: frr router bgp 64512 bgp router-id 192.168.1.101 bgp cluster-id 1.2.3.4 neighbor 192.168.1.102 remote-as 64512 neighbor 192.168.1.103 remote-as 64512 neighbor 192.168.1.104 remote-as 64512 neighbor 172.16.1.2 remote-as 64512 neighbor 172.16.2.2 remote-as 64512 ! address-family ipv4 unicast redistribute vnc-direct no neighbor 192.168.1.102 activate no neighbor 192.168.1.103 activate no neighbor 192.168.1.104 activate neighbor 172.16.1.2 route-reflector-client neighbor 172.16.2.2 route-reflector-client exit-address-family ! address-family ipv4 vpn neighbor 192.168.1.102 activate neighbor 192.168.1.103 activate neighbor 192.168.1.104 activate exit-address-family vnc export bgp mode ce vnc redistribute mode resolve-nve vnc redistribute ipv4 bgp-direct exit Note that in the VNC-GW configuration, the neighboring VNC-GW and NVAs each have a statement disabling the IPv4 unicast address family. IPv4 unicast is on by default and this prevents the other VNC-GW and NVAs from learning unicast routes advertised by the route-reflector clients. Configuration for ``NVA 2``: .. code-block:: frr router bgp 64512 bgp router-id 192.168.1.104 neighbor 192.168.1.101 remote-as 64512 neighbor 192.168.1.102 remote-as 64512 neighbor 192.168.1.103 remote-as 64512 ! address-family ipv4 unicast no neighbor 192.168.1.101 activate no neighbor 192.168.1.102 activate no neighbor 192.168.1.103 activate exit-address-family ! address-family ipv4 vpn neighbor 192.168.1.101 activate neighbor 192.168.1.102 activate neighbor 192.168.1.103 activate exit-address-family ! vnc defaults response-lifetime 3600 exit-vnc vnc nve-group nve1 prefix vn 172.16.1.1/32 response-lifetime 3600 rt both 1000:1 1000:2 exit-vnc vnc nve-group nve2 prefix vn 172.16.2.1/32 response-lifetime 3600 rt both 1000:1 1000:2 exit-vnc exit .. TBD make this its own example: .. .. @float Figure,fig:fig-vnc-gw-rr .. @center @image{fig-vnc-gw-rr,400pt,,FRR VNC Gateway with RR} .. @end float .. An NVA can also import unicast routes from BGP without advertising the .. imported routes as VPN routes. Such imported routes, while not .. distributed to other NVAs or VNC-GWs, are are available to NVEs via .. RFP query messages sent to the NVA. @ref{fig:fig-vnc-gw-rr} .. shows an example topology where unicast routes are imported into NVAs .. from a Route Reflector. (@pxref{Route Reflector} for route reflector .. configuration details.) The following three lines can be added to the .. ``NVA 1`` and ``NVA 2`` configurations to import routes into VNC .. for local VNC use: .. .. @verbatim .. neighbor 192.168.1.105 remote-as 64512 .. vnc redistribute mode plain .. vnc redistribute ipv4 bgp-direct-to-nve-groups .. @end verbatim .. _vnc-with-frr-route-reflector-config: VNC with FRR Route Reflector Configuration ------------------------------------------ A route reflector eliminates the need for a fully meshed NVA network by acting as the hub between NVAs. :ref:`vnc-fig-vnc-frr-route-reflector` shows BGP route reflector ``BGP Route Reflector 1`` (192.168.1.100) as a route reflector for NVAs ``NVA 2``(192.168.1.101) and ``NVA 3`` (192.168.1.102). .. _vnc-fig-vnc-frr-route-reflector: .. figure:: ../figures/fig-vnc-frr-route-reflector.png :align: center :alt: FRR Route Reflector Two NVAs and a BGP Route Reflector ``NVA 2`` and ``NVA 3`` advertise NVE underlay-network IP addresses using the Tunnel Encapsulation Attribute. ``BGP Route Reflector 1`` ``reflects'' advertisements from ``NVA 2`` to ``NVA 3`` and vice versa. As in the example of :ref:`vnc-mesh-nva-config`, there are two NVE groups. The 172.16.0.0/16 address range is partitioned into two NVE groups, ``group1`` (172.16.0.0/17) and ``group2`` (172.16.128.0/17). The NVE ``NVE 4``, ``NVE 7``, and ``NVE 8`` are members of the NVE group ``group1``. The NVEs ``NVE 5``, ``NVE 6``, and ``NVE 9`` are members of the NVE group ``group2``. :file:`bgpd.conf` for ``BGP Route Reflector 1`` on 192.168.1.100: .. code-block:: frr router bgp 64512 bgp router-id 192.168.1.100 neighbor 192.168.1.101 remote-as 64512 neighbor 192.168.1.101 port 7179 neighbor 192.168.1.101 description iBGP-client-192-168-1-101 neighbor 192.168.1.102 remote-as 64512 neighbor 192.168.1.102 port 7179 neighbor 192.168.1.102 description iBGP-client-192-168-1-102 address-family ipv4 unicast neighbor 192.168.1.101 route-reflector-client neighbor 192.168.1.102 route-reflector-client exit-address-family address-family ipv4 vpn neighbor 192.168.1.101 activate neighbor 192.168.1.102 activate neighbor 192.168.1.101 route-reflector-client neighbor 192.168.1.102 route-reflector-client exit-address-family exit :file:`bgpd.conf` for ``NVA 2`` on 192.168.1.101: .. code-block:: frr router bgp 64512 bgp router-id 192.168.1.101 neighbor 192.168.1.100 remote-as 64512 address-family ipv4 vpn neighbor 192.168.1.100 activate exit-address-family vnc nve-group group1 prefix vn 172.16.0.0/17 rd 64512:1 response-lifetime 200 rt both 1000:1 1000:2 exit-vnc exit :file:`bgpd.conf` for ``NVA 2`` on 192.168.1.102: .. code-block:: frr router bgp 64512 bgp router-id 192.168.1.102 neighbor 192.168.1.100 remote-as 64512 address-family ipv4 vpn neighbor 192.168.1.100 activate exit-address-family vnc defaults rd 64512:1 response-lifetime 200 rt both 1000:1 1000:2 exit-vnc vnc nve-group group1 prefix vn 172.16.128.0/17 exit-vnc exit While not shown, an NVA can also be configured as a route reflector. .. _vnc-with-commercial-route-reflector-config: VNC with Commercial Route Reflector Configuration ------------------------------------------------- This example is identical to :ref:`vnc-with-frr-route-reflector-config` with the exception that the route reflector is a commercial router. Only the VNC-relevant configuration is provided. .. figure:: ../figures/fig-vnc-commercial-route-reflector.png :align: center :alt: Commercial Route Reflector Two NVAs with a commercial route reflector :file:`bgpd.conf` for BGP route reflector ``Commercial Router`` on 192.168.1.104::: version 8.5R1.13; routing-options { rib inet.0 { static { route 172.16.0.0/16 next-hop 192.168.1.104; } } autonomous-system 64512; resolution { rib inet.3 { resolution-ribs inet.0; } rib bgp.l3vpn.0 { resolution-ribs inet.0; } } } protocols { bgp { advertise-inactive; family inet { labeled-unicast; } group 1 { type internal; advertise-inactive; advertise-peer-as; import h; family inet { unicast; } family inet-vpn { unicast; } cluster 192.168.1.104; neighbor 192.168.1.101; neighbor 192.168.1.102; } } } policy-options { policy-statement h { from protocol bgp; then { as-path-prepend 64512; accept; } } } :file:`bgpd.conf` for ``NVA 2`` on 192.168.1.101: .. code-block:: frr router bgp 64512 bgp router-id 192.168.1.101 neighbor 192.168.1.100 remote-as 64512 address-family ipv4 vpn neighbor 192.168.1.100 activate exit-address-family vnc nve-group group1 prefix vn 172.16.0.0/17 rd 64512:1 response-lifetime 200 rt both 1000:1 1000:2 exit-vnc exit :file:`bgpd.conf` for ``NVA 3`` on 192.168.1.102: .. code-block:: frr router bgp 64512 bgp router-id 192.168.1.102 neighbor 192.168.1.100 remote-as 64512 address-family ipv4 vpn neighbor 192.168.1.100 activate exit-address-family vnc defaults rd 64512:1 response-lifetime 200 rt both 1000:1 1000:2 exit-vnc vnc nve-group group1 prefix vn 172.16.128.0/17 exit-vnc exit VNC with Redundant Route Reflectors Configuration ------------------------------------------------- This example combines the previous two (:ref:`vnc-with-frr-route-reflector-config` and :ref:`vnc-with-commercial-route-reflector-config`) into a redundant route reflector configuration. BGP route reflectors ``BGP Route Reflector 1`` and ``Commercial Router`` are the route reflectors for NVAs ``NVA 2`` and ``NVA 3``. The two NVAs have connections to both route reflectors. .. figure:: ../figures/fig-vnc-redundant-route-reflectors.png :align: center :alt: Redundant Route Reflectors FRR-based NVA with redundant route reflectors :file:`bgpd.conf` for ``BPGD Route Reflector 1`` on 192.168.1.100: .. code-block:: frr router bgp 64512 bgp router-id 192.168.1.100 bgp cluster-id 192.168.1.100 neighbor 192.168.1.104 remote-as 64512 neighbor 192.168.1.101 remote-as 64512 neighbor 192.168.1.101 description iBGP-client-192-168-1-101 neighbor 192.168.1.101 route-reflector-client neighbor 192.168.1.102 remote-as 64512 neighbor 192.168.1.102 description iBGP-client-192-168-1-102 neighbor 192.168.1.102 route-reflector-client address-family ipv4 vpn neighbor 192.168.1.101 activate neighbor 192.168.1.102 activate neighbor 192.168.1.104 activate neighbor 192.168.1.101 route-reflector-client neighbor 192.168.1.102 route-reflector-client exit-address-family exit :file:`bgpd.conf` for ``NVA 2`` on 192.168.1.101: .. code-block:: frr router bgp 64512 bgp router-id 192.168.1.101 neighbor 192.168.1.100 remote-as 64512 neighbor 192.168.1.104 remote-as 64512 address-family ipv4 vpn neighbor 192.168.1.100 activate neighbor 192.168.1.104 activate exit-address-family vnc nve-group group1 prefix vn 172.16.0.0/17 rd 64512:1 response-lifetime 200 rt both 1000:1 1000:2 exit-vnc exit :file:`bgpd.conf` for ``NVA 3`` on 192.168.1.102: .. code-block:: frr router bgp 64512 bgp router-id 192.168.1.102 neighbor 192.168.1.100 remote-as 64512 neighbor 192.168.1.104 remote-as 64512 address-family ipv4 vpn neighbor 192.168.1.100 activate neighbor 192.168.1.104 activate exit-address-family vnc defaults rd 64512:1 response-lifetime 200 rt both 1000:1 1000:2 exit-vnc vnc nve-group group1 prefix vn 172.16.128.0/17 exit-vnc exit :file:`bgpd.conf` for the Commercial Router route reflector on 192.168.1.104::: routing-options { rib inet.0 { static { route 172.16.0.0/16 next-hop 192.168.1.104; } } autonomous-system 64512; resolution { rib inet.3 { resolution-ribs inet.0; } rib bgp.l3vpn.0 { resolution-ribs inet.0; } } } protocols { bgp { advertise-inactive; family inet { labeled-unicast; } group 1 { type internal; advertise-inactive; advertise-peer-as; import h; family inet { unicast; } family inet-vpn { unicast; } cluster 192.168.1.104; neighbor 192.168.1.101; neighbor 192.168.1.102; } group 2 { type internal; advertise-inactive; advertise-peer-as; import h; family inet { unicast; } family inet-vpn { unicast; } neighbor 192.168.1.100; } } } policy-options { policy-statement h { from protocol bgp; then { as-path-prepend 64512; accept; } } } .. [#] The nve-id is carried in the route distinguisher. It is the second octet of the eight-octet route distinguisher generated for Ethernet / L2 advertisements. The first octet is a constant 0xFF, and the third through eighth octets are set to the L2 ethernet address being advertised. frr-7.2.1/doc/user/vrrp.rst0000644000000000000000000005343513610377563012474 00000000000000.. _vrrp: **** VRRP **** :abbr:`VRRP` stands for Virtual Router Redundancy Protocol. This protocol is used to allow multiple backup routers on the same segment to take over operation of each others' IP addresses if the primary router fails. This is typically used to provide fault-tolerant gateways to hosts on the segment. FRR implements VRRPv2 (:rfc:`3768`) and VRRPv3 (:rfc:`5798`). For VRRPv2, no authentication methods are supported; these are deprecated in the VRRPv2 specification as they do not provide any additional security over the base protocol. .. note:: - VRRP is supported on Linux 5.1+ - VRRP does not implement Accept_Mode .. _vrrp-starting: Starting VRRP ============= The configuration file for *vrrpd* is :file:`vrrpd.conf`. The typical location of :file:`vrrpd.conf` is |INSTALL_PREFIX_ETC|/vrrpd.conf. If using integrated config, then :file:`vrrpd.conf` need not be present and :file:`frr.conf` is read instead. .. program:: vrrpd :abbr:`VRRP` supports all the common FRR daemon start options which are documented elsewhere. .. _vrrp-protocol-overview: Protocol Overview ================= From :rfc:`5798`: VRRP specifies an election protocol that dynamically assigns responsibility for a virtual router to one of the VRRP routers on a LAN. The VRRP router controlling the IPv4 or IPv6 address(es) associated with a virtual router is called the Master, and it forwards packets sent to these IPv4 or IPv6 addresses. VRRP Master routers are configured with virtual IPv4 or IPv6 addresses, and VRRP Backup routers infer the address family of the virtual addresses being carried based on the transport protocol. Within a VRRP router, the virtual routers in each of the IPv4 and IPv6 address families are a domain unto themselves and do not overlap. The election process provides dynamic failover in the forwarding responsibility should the Master become unavailable. For IPv4, the advantage gained from using VRRP is a higher-availability default path without requiring configuration of dynamic routing or router discovery protocols on every end-host. For IPv6, the advantage gained from using VRRP for IPv6 is a quicker switchover to Backup routers than can be obtained with standard IPv6 Neighbor Discovery mechanisms. VRRP accomplishes these goals primarily by using a virtual MAC address shared between the physical routers participating in a VRRP virtual router. This reduces churn in the neighbor tables of hosts and downstream switches and makes router failover theoretically transparent to these devices. FRR implements the election protocol and handles changing the operating system interface configuration in response to protocol state changes. As a consequence of the shared virtual MAC requirement, VRRP is currently supported only on Linux, as Linux is the only operating system that provides the necessary features in its network stack to make implementing this protocol feasible. When a VRRP router is acting as the Master router, FRR allows the interface(s) with the backed-up IP addresses to remain up and functional. When the router transitions to Backup state, these interfaces are set into ``protodown`` mode. This is an interface mode that is functionally equivalent to ``NO-CARRIER``. Physical drivers typically use this state indication to drop traffic on an interface. In the case of VRRP, the interfaces in question are macvlan devices, which are virtual interfaces. Since the IP addresses managed by VRRP are on these interfaces, this has the same effect as removing these addresses from the interface, but is implemented as a state flag. .. _vrrp-configuration: Configuring VRRP ================ VRRP is configured on a per-interface basis, with some global defaults accessible outside the interface context. .. _vrrp-system-configuration: System Configuration -------------------- FRR's VRRP implementation uses Linux macvlan devices to to implement the shared virtual MAC feature of the protocol. Currently, it does not create those system interfaces - they must be configured outside of FRR before VRRP can be enabled on them. Each interface on which VRRP will be enabled must have at least one macvlan device configured with the virtual MAC and placed in the proper operation mode. The addresses backed up by VRRP are assigned to these interfaces. Suppose you have an interface ``eth0`` with the following configuration: .. code-block:: console $ ip link show eth0 2: eth0: mtu 1500 qdisc fq_codel state UP group default qlen 1000 link/ether 02:17:45:00:aa:aa brd ff:ff:ff:ff:ff:ff inet 10.0.2.15/24 brd 10.0.2.255 scope global dynamic eth0 valid_lft 72532sec preferred_lft 72532sec inet 10.0.2.16/24 brd 10.0.2.255 scope global dynamic eth0 valid_lft 72532sec preferred_lft 72532sec inet6 fe80::17:45ff:fe00:aaaa/64 scope link valid_lft forever preferred_lft forever Suppose the address you want to back up is ``10.0.2.16``, and will be managed by the virtual router with id ``5``. A macvlan device with the appropriate MAC address must be created before VRRP can begin to operate. If you are using ``ifupdown2``, the configuration is as follows: .. code-block:: console iface eth0 ... vrrp 5 10.0.2.16/24 2001:0db8::0370:7334/64 Applying this configuration with ``ifreload -a`` will create the appropriate macvlan device. If you are using ``iproute2``, the equivalent configuration is: .. code-block:: console ip link add vrrp4-2-1 link eth0 addrgenmode random type macvlan mode bridge ip link set dev vrrp4-2-1 address 00:00:5e:00:01:05 ip addr add 10.0.2.16/24 dev vrrp4-2-1 ip link set dev vrrp4-2-1 up ip link add vrrp6-2-1 link eth0 addrgenmode random type macvlan mode bridge ip link set dev vrrp4-2-1 address 00:00:5e:00:02:05 ip addr add 2001:db8::370:7334/64 dev vrrp6-2-1 ip link set dev vrrp6-2-1 up In either case, the created interfaces will look like this: .. code-block:: console $ ip addr show vrrp4-2-1 5: vrrp4-2-1@eth0: mtu 1500 qdisc noqueue state UP group default qlen 1000 link/ether 00:00:5e:00:01:05 brd ff:ff:ff:ff:ff:ff inet 10.0.2.16/24 scope global vrrp4-2-1 valid_lft forever preferred_lft forever inet6 fe80::dc56:d11a:e69d:ea72/64 scope link stable-privacy valid_lft forever preferred_lft forever $ ip addr show vrrp6-2-1 8: vrrp6-2-1@eth0: mtu 1500 qdisc noqueue state UP group default qlen 1000 link/ether 00:00:5e:00:02:05 brd ff:ff:ff:ff:ff:ff inet6 2001:db8::370:7334/64 scope global valid_lft forever preferred_lft forever inet6 fe80::f8b7:c9dd:a1e8:9844/64 scope link stable-privacy valid_lft forever preferred_lft forever Using ``vrrp4-2-1`` as an example, a few things to note about this interface: - It is slaved to ``eth0``; any packets transmitted on this interface will egress via ``eth0`` - Its MAC address is set to the VRRP IPv4 virtual MAC specified by the RFC for :abbr:`VRID (Virtual Router ID)` ``5`` - The link local address on the interface is not derived from the interface MAC First to note is that packets transmitted on this interface will egress via ``eth0``, but with their Ethernet source MAC set to the VRRP virtual MAC. This is how FRR's VRRP implementation accomplishes the virtual MAC requirement on real hardware. Ingress traffic is a more complicated matter. Macvlan devices have multiple operating modes that change how ingress traffic is handled. Of relevance to FRR's implementation are the ``bridge`` and ``private`` modes. In ``private`` mode, any ingress traffic on ``eth0`` (in our example) with a source MAC address equal to the MAC address on any of ``eth0``'s macvlan devices will be placed *only* on that macvlan device. This curious behavior is undesirable, since FRR's implementation of VRRP needs to be able to receive advertisements from neighbors while in Backup mode - i.e., while its macvlan devices are in ``protodown on``. If the macvlan devices are instead set to ``bridge`` mode, all ingress traffic shows up on all interfaces - including ``eth0`` - regardless of source MAC or any other factor. Consequently, macvlans used by FRR for VRRP must be set to ``bridge`` mode or the protocol will not function correctly. As for the MAC address assigned to this interface, the last byte of the address holds the :abbr:`VRID (Virtual Router Identifier)`, in this case ``0x05``. The second to last byte is ``0x01``, as specified by the RFC for IPv4 operation. The IPv6 MAC address is be identical except that the second to last byte is defined to be ``0x02``. Two things to note from this arrangement: 1. There can only be up to 255 unique Virtual Routers on an interface (only 1 byte is available for the VRID) 2. IPv4 and IPv6 addresses must be assigned to different macvlan devices, because they have different MAC addresses Finally, take note of the generated IPv6 link local address on the interface. For interfaces on which VRRP will operate in IPv6 mode, this link local *cannot* be derived using the usual EUI-64 method. This is because VRRP advertisements are sent from the link local address of this interface, and VRRP uses the source address of received advertisements as part of its election algorithm. If the IPv6 link local of a router is equivalent to the IPv6 link local in a received advertisement, this can cause both routers to assume the Master role (very bad). ``ifupdown`` knows to set the ``addrgenmode`` of the interface properly, but when using ``iproute2`` to create the macvlan devices, you must be careful to manually specify ``addrgenmode random``. A brief note on the Backup state ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ It is worth noting here that an alternate choice for the implementation of the Backup state, such as removing all the IP addresses assigned to the macvlan device or deleting their local routes instead of setting the device into ``protodown on``, would allow the protocol to function regardless of whether the macvlan device(s) are set to ``private`` or ``bridge`` mode. Indeed, the strange behavior of the kernel macvlan driver in ``private`` mode, whereby it performs what may be thought of as a sort of interface-level layer 2 "NAT" based on source MAC, can be traced back to a patch clearly designed to accommodate a VRRP implementation from a different vendor. However, the ``protodown`` based implementation allows for a configuration model in which FRR does not dynamically manage the addresses assigned on a system, but instead just manages interface state. Such a scenario was in mind when this protocol implementation was initially built, which is why the other choices are not currently present. Since support for placing macvlan devices into ``protodown`` was not added to Linux until version 5.1, this also explains the relatively restrictive kernel versioning requirement. In the future other methods of implementing Backup state may be added along with a configuration knob to choose between them. .. _vrrp-interface-configuration: Interface Configuration ----------------------- Continuing with the example from the previous section, we assume the macvlan interfaces have been properly configured with the proper MAC addresses and the IPvX addresses assigned. In FRR, a possible VRRPv3 configuration for this interface is: .. code-block:: frr interface eth0 vrrp 5 version 3 vrrp 5 priority 200 vrrp 5 advertisement-interval 1500 vrrp 5 ip 10.0.2.16 vrrp 5 ipv6 2001:0db8::0370:7334 VRRP will activate as soon as the first IPvX address configuration line is encountered. If you do not want this behavior, use the :clicmd:`vrrp (1-255) shutdown` command, and apply the ``no`` form when you are ready to activate VRRP. At this point executing ``show vrrp`` will display the following: .. code-block:: console ubuntu-bionic# show vrrp Virtual Router ID 5 Protocol Version 3 Autoconfigured Yes Shutdown No Interface eth0 VRRP interface (v4) vrrp4-2-5 VRRP interface (v6) vrrp6-2-5 Primary IP (v4) 10.0.2.15 Primary IP (v6) fe80::9b91:7155:bf6a:d386 Virtual MAC (v4) 00:00:5e:00:01:05 Virtual MAC (v6) 00:00:5e:00:02:05 Status (v4) Master Status (v6) Master Priority 200 Effective Priority (v4) 200 Effective Priority (v6) 200 Preempt Mode Yes Accept Mode Yes Advertisement Interval 1500 ms Master Advertisement Interval (v4) 1000 ms Master Advertisement Interval (v6) 1000 ms Advertisements Tx (v4) 14 Advertisements Tx (v6) 14 Advertisements Rx (v4) 0 Advertisements Rx (v6) 0 Gratuitous ARP Tx (v4) 1 Neigh. Adverts Tx (v6) 1 State transitions (v4) 2 State transitions (v6) 2 Skew Time (v4) 210 ms Skew Time (v6) 210 ms Master Down Interval (v4) 3210 ms Master Down Interval (v6) 3210 ms IPv4 Addresses 1 .................................. 10.0.2.16 IPv6 Addresses 1 .................................. 2001:db8::370:7334 At this point, VRRP has sent gratuitous ARP requests for the IPv4 address, Unsolicited Neighbor Advertisements for the IPv6 address, and has asked Zebra to send Router Advertisements on its behalf. It is also transmitting VRRPv3 advertisements on the macvlan interfaces. The Primary IP fields are of some interest, as the behavior may be counterintuitive. These fields show the source address used for VRRP advertisements. Although VRRPv3 advertisements are always transmitted on the macvlan interfaces, in the IPv4 case the source address is set to the primary IPv4 address on the base interface, ``eth0`` in this case. This is a protocol requirement, and IPv4 VRRP will not function unless the base interface has an IPv4 address assigned. In the IPv6 case the link local of the macvlan interface is used. If any misconfiguration errors are detected, VRRP for the misconfigured address family will not come up and the configuration issue will be logged to FRR's configured logging destination. Per the RFC, IPv4 and IPv6 virtual routers are independent of each other. For instance, it is possible for the IPv4 router to be in Backup state while the IPv6 router is in Master state; or for either to be completely inoperative while the other is operative, etc. Instances sharing the same base interface and VRID are shown together in the show output for conceptual convenience. To complete your VRRP deployment, configure other routers on the segment with the exact same system and FRR configuration as shown above. Provided each router receives the others' VRRP advertisements, the Master election protocol will run, one Master will be elected, and the other routers will place their macvlan interfaces into ``protodown on`` until Master fails or priority values are changed to favor another router. Switching the protocol version to VRRPv2 is accomplished simply by changing ``version 3`` to ``version 2`` in the VRID configuration line. Note that VRRPv2 does not support IPv6, so any IPv6 configuration will be rejected by FRR when using VRRPv2. .. note:: All VRRP routers initially start in Backup state, and wait for the calculated Master Down Interval to pass before they assume Master status. This prevents downstream neighbor table churn if another router is already Master with higher priority, meaning this box will ultimately assume Backup status once the first advertisement is received. However, if the calculated Master Down Interval is high and this router is configured such that it will ultimately assume Master status, then it will take a while for this to happen. This is a known issue. All interface configuration commands are documented below. .. index:: [no] vrrp (1-255) [version (2-3)] .. clicmd:: [no] vrrp (1-255) [version (2-3)] Create a VRRP router with the specified VRID on the interface. Optionally specify the protocol version. If the protocol version is not specified, the default is VRRPv3. .. index:: [no] vrrp (1-255) advertisement-interval (10-40950) .. clicmd:: [no] vrrp (1-255) advertisement-interval (10-40950) Set the advertisement interval. This is the interval at which VRRP advertisements will be sent. Values are given in milliseconds, but must be multiples of 10, as VRRP itself uses centiseconds. .. index:: [no] vrrp (1-255) ip A.B.C.D .. clicmd:: [no] vrrp (1-255) ip A.B.C.D Add an IPv4 address to the router. This address must already be configured on the appropriate macvlan device. Adding an IP address to the router will implicitly activate the router; see :clicmd:`[no] vrrp (1-255) shutdown` to override this behavior. .. index:: [no] vrrp (1-255) ipv6 X:X::X:X .. clicmd:: [no] vrrp (1-255) ipv6 X:X::X:X Add an IPv6 address to the router. This address must already be configured on the appropriate macvlan device. Adding an IP address to the router will implicitly activate the router; see :clicmd:`[no] vrrp (1-255) shutdown` to override this behavior. This command will fail if the protocol version is set to VRRPv2, as VRRPv2 does not support IPv6. .. index:: [no] vrrp (1-255) preempt .. clicmd:: [no] vrrp (1-255) preempt Toggle preempt mode. When enabled, preemption allows Backup routers with higher priority to take over Master status from the existing Master. Enabled by default. .. index:: [no] vrrp (1-255) priority (1-254) .. clicmd:: [no] vrrp (1-255) priority (1-254) Set the router priority. The router with the highest priority is elected as the Master. If all routers in the VRRP virtual router are configured with the same priority, the router with the highest primary IP address is elected as the Master. Priority value 255 is reserved for the acting Master router. .. index:: [no] vrrp (1-255) shutdown .. clicmd:: [no] vrrp (1-255) shutdown Place the router into administrative shutdown. VRRP will not activate for this router until this command is removed with the ``no`` form. .. _vrrp-global-configuration: Global Configuration -------------------- Show commands, global defaults and debugging configuration commands. .. index:: show vrrp [interface INTERFACE] [(1-255)] [json] .. clicmd:: show vrrp [interface INTERFACE] [(1-255)] [json] Shows VRRP status for some or all configured VRRP routers. Specifying an interface will only show routers configured on that interface. Specifying a VRID will only show routers with that VRID. Specifying ``json`` will dump each router state in a JSON array. .. index:: debug vrrp [{protocol|autoconfigure|packets|sockets|ndisc|arp|zebra}] .. clicmd:: debug vrrp [{protocol|autoconfigure|packets|sockets|ndisc|arp|zebra}] Toggle debugging logs for some or all components of VRRP. protocol Logs state changes, election protocol decisions, and interface status changes. autoconfigure Logs actions taken by the autoconfiguration procedures. See :ref:`vrrp-autoconfiguration`. packets Logs details of ingress and egress packets. Includes packet decodes and hex dumps. sockets Logs details of socket configuration and initialization. ndisc Logs actions taken by the Neighbor Discovery component of VRRP. arp Logs actions taken by the ARP component of VRRP. zebra Logs communications with Zebra. .. index:: [no] vrrp default .. clicmd:: [no] vrrp default Configure defaults for new VRRP routers. These values will not affect already configured VRRP routers, but will be applied to newly configured ones. .. _vrrp-autoconfiguration: Autoconfiguration ----------------- In light of the complicated configuration required on the base system before VRRP can be enabled, FRR has the ability to automatically configure VRRP sessions by inspecting the interfaces present on the system. Since it is quite unlikely that macvlan devices with VRRP virtual MACs will exist on systems not using VRRP, this can be a convenient shortcut to automatically generate FRR configuration. After configuring the interfaces as described in :ref:`vrrp-system-configuration`, and configuring any defaults you may want, execute the following command: .. index:: [no] vrrp autoconfigure [version (2-3)] .. clicmd:: [no] vrrp autoconfigure [version (2-3)] Generates VRRP configuration based on the interface configuration on the base system. Any existing interfaces that are configured properly for VRRP - i.e. have the correct MAC address, link local address (when required), IPv4 and IPv6 addresses - are used to create a VRRP router on their parent interfaces, with VRRP IPvX addresses taken from the addresses assigned to the macvlan devices. The generated configuration appears in the output of ``show run``, which can then be modified as needed and written to the config file. The ``version`` parameter controls the protocol version; if using VRRPv2, keep in mind that IPv6 is not supported and will not be configured. The following configuration is then generated for you: .. code-block:: frr interface eth0 vrrp 5 vrrp 5 ip 10.0.2.16 vrrp 5 ipv6 2001:db8::370:7334 VRRP is automatically activated. Global defaults, if set, are applied. You can then edit this configuration with **vtysh** as needed, and commit it by writing to the configuration file. frr-7.2.1/doc/user/vtysh.rst0000644000000000000000000001762413610377563012660 00000000000000.. _vty-shell: ********* VTY shell ********* .. program:: configure *vtysh* provides a combined frontend to all FRR daemons in a single combined session. It is enabled by default at build time, but can be disabled through the :option:`--disable-vtysh` option to the configure script. *vtysh* has a configuration file, :file:`vtysh.conf`. The location of that file cannot be changed from |INSTALL_PREFIX_ETC| since it contains options controlling authentication behavior. This file will also not be written by configuration-save commands, it is intended to be updated manually by an administrator with an external editor. .. warning:: This also means the ``hostname`` and ``banner motd`` commands (which both do have effect for vtysh) need to be manually updated in :file:`vtysh.conf`. Pager usage =========== *vtysh* can call an external paging program (e.g. *more* or *less*) to paginate long output from commands. This feature used to be enabled by default but is now controlled by the ``VTYSH_PAGER`` environment variable and the :clicmd:`terminal paginate` command: .. envvar:: VTYSH_PAGER If set, the ``VTYSH_PAGER`` environment variable causes *vtysh* to pipe output from commands through the given command. Note that this happens regardless of the length of the output. As such, standard pager behavior (particularly waiting at the end of output) tends to be annoying to the user. Using ``less -EFX`` is recommended for a better user experience. If this environment variable is unset, *vtysh* defaults to not using any pager. This variable should be set by the user according to their preferences, in their :file:`~/.profile` file. .. index:: [no] terminal paginate .. clicmd:: [no] terminal paginate Enables/disables vtysh output pagination. This command is intended to be placed in :file:`vtysh.conf` to set a system-wide default. If this is enabled but ``VTYSH_PAGER`` is not set, the system default pager (likely ``more`` or ``/usr/bin/pager``) will be used. Permissions and setup requirements ================================== *vtysh* connects to running daemons through Unix sockets located in |INSTALL_PREFIX_STATE|. Running vtysh thus requires access to that directory, plus membership in the |INSTALL_VTY_GROUP| group (which is the group that the daemons will change ownership of their sockets to). To restrict access to FRR configuration, make sure no unauthorized users are members of the |INSTALL_VTY_GROUP| group. .. warning:: VTYSH implements a CLI option ``-u, --user`` that disallows entering the characters "en" on the command line, which ideally restricts access to configuration commands. However, VTYSH was never designed to be a privilege broker and is not built using secure coding practices. No guarantees of security are provided for this option and under no circumstances should this option be used to provide any semblance of security or read-only access to FRR. PAM support (experimental) -------------------------- vtysh has working (but rather useless) PAM support. It will perform an "authenticate" PAM call using |PACKAGE_NAME| as service name. No other (accounting, session, password change) calls will be performed by vtysh. Users using vtysh still need to have appropriate access to the daemons' VTY sockets, usually by being member of the |INSTALL_VTY_GROUP| group. If they have this membership, PAM support is useless since they can connect to daemons and issue commands using some other tool. Alternatively, the *vtysh* binary could be made SGID (set group ID) to the |INSTALL_VTY_GROUP| group. .. warning:: No security guarantees are made for this configuration. .. index:: username USERNAME nopassword .. clicmd:: username USERNAME nopassword If PAM support is enabled at build-time, this command allows disabling the use of PAM on a per-user basis. If vtysh finds that an user is trying to use vtysh and a "nopassword" entry is found, no calls to PAM will be made at all. .. _integrated-configuration-mode: Integrated configuration mode ============================= Integrated configuration mode uses a single configuration file, :file:`frr.conf`, for all daemons. This replaces the individual files like :file:`zebra.conf` or :file:`bgpd.conf`. :file:`frr.conf` is located in |INSTALL_PREFIX_ETC|. All daemons check for the existence of this file at startup, and if it exists will not load their individual configuration files. Instead, ``vtysh -b`` must be invoked to process :file:`frr.conf` and apply its settings to the individual daemons. .. warning:: *vtysh -b* must also be executed after restarting any daemon. Configuration saving, file ownership and permissions ---------------------------------------------------- The :file:`frr.conf` file is not written by any of the daemons; instead *vtysh* contains the necessary logic to collect configuration from all of the daemons, combine it and write it out. .. warning:: Daemons must be running for *vtysh* to be able to collect their configuration. Any configuration from non-running daemons is permanently lost after doing a configuration save. Since the *vtysh* command may be running as ordinary user on the system, configuration writes will be tried through *watchfrr*, using the ``write integrated`` command internally. Since *watchfrr* is running as superuser, *vtysh* is able to ensure correct ownership and permissions on :file:`frr.conf`. If *watchfrr* is not running or the configuration write fails, *vtysh* will attempt to directly write to the file. This is likely to fail if running as unprivileged user; alternatively it may leave the file with incorrect owner or permissions. Writing the configuration can be triggered directly by invoking *vtysh -w*. This may be useful for scripting. Note this command should be run as either the superuser or the FRR user. We recommend you do not mix the use of the two types of files. Further, it is better not to use the integrated :file:`frr.conf` file, as any syntax error in it can lead to /all/ of your daemons being unable to start up. Per daemon files are more robust as impact of errors in configuration are limited to the daemon in whose file the error is made. .. index:: service integrated-vtysh-config .. clicmd:: service integrated-vtysh-config .. index:: no service integrated-vtysh-config .. clicmd:: no service integrated-vtysh-config Control whether integrated :file:`frr.conf` file is written when 'write file' is issued. These commands need to be placed in :file:`vtysh.conf` to have any effect. Note that since :file:`vtysh.conf` is not written by FRR itself, they therefore need to be manually placed in that file. This command has 3 states: service integrated-vtysh-config *vtysh* will always write :file:`frr.conf`. no service integrated-vtysh-config *vtysh* will never write :file:`frr.conf`; instead it will ask daemons to write their individual configuration files. Neither option present (default) *vtysh* will check whether :file:`frr.conf` exists. If it does, configuration writes will update that file. Otherwise, writes are performed through the individual daemons. This command is primarily intended for packaging/distribution purposes, to preset one of the two operating modes and ensure consistent operation across installations. .. index:: write integrated .. clicmd:: write integrated Unconditionally (regardless of ``service integrated-vtysh-config`` setting) write out integrated :file:`frr.conf` file through *watchfrr*. If *watchfrr* is not running, this command is unavailable. .. warning:: Configuration changes made while some daemon is not running will be invisible to that daemon. The daemon will start up with its saved configuration (either in its individual configuration file, or in :file:`frr.conf`). This is particularly troublesome for route-maps and prefix lists, which would otherwise be synchronized between daemons. frr-7.2.1/doc/user/zebra.rst0000644000000000000000000007161613610377563012607 00000000000000.. _zebra: ***** Zebra ***** *zebra* is an IP routing manager. It provides kernel routing table updates, interface lookups, and redistribution of routes between different routing protocols. .. _invoking-zebra: Invoking zebra ============== Besides the common invocation options (:ref:`common-invocation-options`), the *zebra* specific invocation options are listed below. .. program:: zebra .. option:: -b, --batch Runs in batch mode. *zebra* parses configuration file and terminates immediately. .. option:: -K TIME, --graceful_restart TIME If this option is specified, the graceful restart time is TIME seconds. Zebra, when started, will read in routes. Those routes that Zebra identifies that it was the originator of will be swept in TIME seconds. If no time is specified then we will sweep those routes immediately. .. option:: -r, --retain When program terminates, do not flush routes installed by *zebra* from the kernel. .. option:: -e X, --ecmp X Run zebra with a limited ecmp ability compared to what it is compiled to. If you are running zebra on hardware limited functionality you can force zebra to limit the maximum ecmp allowed to X. This number is bounded by what you compiled FRR with as the maximum number. .. option:: -n, --vrfwnetns When *Zebra* starts with this option, the VRF backend is based on Linux network namespaces. That implies that all network namespaces discovered by ZEBRA will create an associated VRF. The other daemons will operate on the VRF VRF defined by *Zebra*, as usual. .. seealso:: :ref:`zebra-vrf` .. option:: -o, --vrfdefaultname When *Zebra* starts with this option, the default VRF name is changed to the parameter. .. seealso:: :ref:`zebra-vrf` .. option:: -z , --socket If this option is supplied on the cli, the path to the zebra control socket(zapi), is used. This option overrides a -N option if handed to it on the cli. .. option:: --v6-rr-semantics The linux kernel is receiving the ability to use the same route replacement semantics for v6 that v4 uses. If you are using a kernel that supports this functionality then run *Zebra* with this option and we will use Route Replace Semantics instead of delete than add. .. _interface-commands: Configuration Addresses behaviour ================================= At startup, *Zebra* will first discover the underlying networking objects from the operating system. This includes interfaces, addresses of interfaces, static routes, etc. Then, it will read the configuration file, including its own interface addresses, static routes, etc. All this information comprises the operational context from *Zebra*. But configuration context from *Zebra* will remain the same as the one from :file:`zebra.conf` config file. As an example, executing the following :clicmd:`show running-config` will reflect what was in :file:`zebra.conf`. In a similar way, networking objects that are configured outside of the *Zebra* like *iproute2* will not impact the configuration context from *Zebra*. This behaviour permits you to continue saving your own config file, and decide what is really to be pushed on the config file, and what is dependent on the underlying system. Note that inversely, from *Zebra*, you will not be able to delete networking objects that were previously configured outside of *Zebra*. Interface Commands ================== .. _standard-commands: Standard Commands ----------------- .. index:: interface IFNAME .. clicmd:: interface IFNAME .. index:: interface IFNAME vrf VRF .. clicmd:: interface IFNAME vrf VRF .. index:: shutdown .. clicmd:: shutdown .. index:: no shutdown .. clicmd:: no shutdown Up or down the current interface. .. index:: ip address ADDRESS/PREFIX .. clicmd:: ip address ADDRESS/PREFIX .. index:: ipv6 address ADDRESS/PREFIX .. clicmd:: ipv6 address ADDRESS/PREFIX .. index:: no ip address ADDRESS/PREFIX .. clicmd:: no ip address ADDRESS/PREFIX .. index:: no ipv6 address ADDRESS/PREFIX .. clicmd:: no ipv6 address ADDRESS/PREFIX Set the IPv4 or IPv6 address/prefix for the interface. .. index:: ip address LOCAL-ADDR peer PEER-ADDR/PREFIX .. clicmd:: ip address LOCAL-ADDR peer PEER-ADDR/PREFIX .. index:: no ip address LOCAL-ADDR peer PEER-ADDR/PREFIX .. clicmd:: no ip address LOCAL-ADDR peer PEER-ADDR/PREFIX Configure an IPv4 Point-to-Point address on the interface. (The concept of PtP addressing does not exist for IPv6.) `local-addr` has no subnet mask since the local side in PtP addressing is always a single (/32) address. `peer-addr/prefix` can be an arbitrary subnet behind the other end of the link (or even on the link in Point-to-Multipoint setups), though generally /32s are used. .. index:: description DESCRIPTION ... .. clicmd:: description DESCRIPTION ... Set description for the interface. .. index:: multicast .. clicmd:: multicast .. index:: no multicast .. clicmd:: no multicast Enable or disables multicast flag for the interface. .. index:: bandwidth (1-10000000) .. clicmd:: bandwidth (1-10000000) .. index:: no bandwidth (1-10000000) .. clicmd:: no bandwidth (1-10000000) Set bandwidth value of the interface in kilobits/sec. This is for calculating OSPF cost. This command does not affect the actual device configuration. .. index:: link-detect .. clicmd:: link-detect .. index:: no link-detect .. clicmd:: no link-detect Enable/disable link-detect on platforms which support this. Currently only Linux and Solaris, and only where network interface drivers support reporting link-state via the ``IFF_RUNNING`` flag. In FRR, link-detect is on by default. .. _link-parameters-commands: Link Parameters Commands ------------------------ .. note:: At this time, FRR offers partial support for some of the routing protocol extensions that can be used with MPLS-TE. FRR does not support a complete RSVP-TE solution currently. .. index:: link-params .. clicmd:: link-params .. index:: no link-param .. clicmd:: no link-param Enter into the link parameters sub node. At least 'enable' must be set to activate the link parameters, and consequently routing information that could be used as part of Traffic Engineering on this interface. MPLS-TE must be enable at the OSPF (:ref:`ospf-traffic-engineering`) or ISIS (:ref:`isis-traffic-engineering`) router level in complement to this. Disable link parameters for this interface. Under link parameter statement, the following commands set the different TE values: .. index:: link-params [enable] .. clicmd:: link-params [enable] Enable link parameters for this interface. .. index:: link-params [metric (0-4294967295)] .. clicmd:: link-params [metric (0-4294967295)] .. index:: link-params max-bw BANDWIDTH .. clicmd:: link-params max-bw BANDWIDTH .. index:: link-params max-rsv-bw BANDWIDTH .. clicmd:: link-params max-rsv-bw BANDWIDTH .. index:: link-params unrsv-bw (0-7) BANDWIDTH .. clicmd:: link-params unrsv-bw (0-7) BANDWIDTH .. index:: link-params admin-grp BANDWIDTH .. clicmd:: link-params admin-grp BANDWIDTH These commands specifies the Traffic Engineering parameters of the interface in conformity to RFC3630 (OSPF) or RFC5305 (ISIS). There are respectively the TE Metric (different from the OSPF or ISIS metric), Maximum Bandwidth (interface speed by default), Maximum Reservable Bandwidth, Unreserved Bandwidth for each 0-7 priority and Admin Group (ISIS) or Resource Class/Color (OSPF). Note that BANDIWDTH is specified in IEEE floating point format and express in Bytes/second. .. index:: link-param delay (0-16777215) [min (0-16777215) | max (0-16777215)] .. clicmd:: link-param delay (0-16777215) [min (0-16777215) | max (0-16777215)] .. index:: link-param delay-variation (0-16777215) .. clicmd:: link-param delay-variation (0-16777215) .. index:: link-param packet-loss PERCENTAGE .. clicmd:: link-param packet-loss PERCENTAGE .. index:: link-param res-bw BANDWIDTH .. clicmd:: link-param res-bw BANDWIDTH .. index:: link-param ava-bw BANDWIDTH .. clicmd:: link-param ava-bw BANDWIDTH .. index:: link-param use-bw BANDWIDTH .. clicmd:: link-param use-bw BANDWIDTH These command specifies additional Traffic Engineering parameters of the interface in conformity to draft-ietf-ospf-te-metrics-extension-05.txt and draft-ietf-isis-te-metrics-extension-03.txt. There are respectively the delay, jitter, loss, available bandwidth, reservable bandwidth and utilized bandwidth. Note that BANDWIDTH is specified in IEEE floating point format and express in Bytes/second. Delays and delay variation are express in micro-second (µs). Loss is specified in PERCENTAGE ranging from 0 to 50.331642% by step of 0.000003. .. index:: link-param neighbor as (0-65535) .. clicmd:: link-param neighbor as (0-65535) .. index:: link-param no neighbor .. clicmd:: link-param no neighbor Specifies the remote ASBR IP address and Autonomous System (AS) number for InterASv2 link in OSPF (RFC5392). Note that this option is not yet supported for ISIS (RFC5316). .. index:: ip nht resolve-via-default .. clicmd:: ip nht resolve-via-default Allows nexthop tracking to resolve via the default route. This is useful when e.g. you want to allow BGP to peer across the default route. .. _zebra-vrf: Virtual Routing and Forwarding ============================== FRR supports :abbr:`VRF (Virtual Routing and Forwarding)`. VRF is a way to separate networking contexts on the same machine. Those networking contexts are associated with separate interfaces, thus making it possible to associate one interface with a specific VRF. VRF can be used, for example, when instantiating per enterprise networking services, without having to instantiate the physical host machine or the routing management daemons for each enterprise. As a result, interfaces are separate for each set of VRF, and routing daemons can have their own context for each VRF. This conceptual view introduces the *Default VRF* case. If the user does not configure any specific VRF, then by default, FRR uses the *Default VRF*. Configuring VRF networking contexts can be done in various ways on FRR. The VRF interfaces can be configured by entering in interface configuration mode :clicmd:`interface IFNAME vrf VRF`. A VRF backend mode is chosen when running *Zebra*. If no option is chosen, then the *Linux VRF* implementation as references in https://www.kernel.org/doc/Documentation/networking/vrf.txt will be mapped over the *Zebra* VRF. The routing table associated to that VRF is a Linux table identifier located in the same *Linux network namespace* where *Zebra* started. If the :option:`-n` option is chosen, then the *Linux network namespace* will be mapped over the *Zebra* VRF. That implies that *Zebra* is able to configure several *Linux network namespaces*. The routing table associated to that VRF is the whole routing tables located in that namespace. For instance, this mode matches OpenStack Network Namespaces. It matches also OpenFastPath. The default behavior remains Linux VRF which is supported by the Linux kernel community, see https://www.kernel.org/doc/Documentation/networking/vrf.txt. Because of that difference, there are some subtle differences when running some commands in relationship to VRF. Here is an extract of some of those commands: .. index:: vrf VRF .. clicmd:: vrf VRF This command is available on configuration mode. By default, above command permits accessing the VRF configuration mode. This mode is available for both VRFs. It is to be noted that *Zebra* does not create Linux VRF. The network administrator can however decide to provision this command in configuration file to provide more clarity about the intended configuration. .. index:: netns NAMESPACE .. clicmd:: netns NAMESPACE This command is based on VRF configuration mode. This command is available when *Zebra* is run in :option:`-n` mode. This command reflects which *Linux network namespace* is to be mapped with *Zebra* VRF. It is to be noted that *Zebra* creates and detects added/suppressed VRFs from the Linux environment (in fact, those managed with iproute2). The network administrator can however decide to provision this command in configuration file to provide more clarity about the intended configuration. .. index:: show ip route vrf VRF .. clicmd:: show ip route vrf VRF The show command permits dumping the routing table associated to the VRF. If *Zebra* is launched with default settings, this will be the ``TABLENO`` of the VRF configured on the kernel, thanks to information provided in https://www.kernel.org/doc/Documentation/networking/vrf.txt. If *Zebra* is launched with :option:`-n` option, this will be the default routing table of the *Linux network namespace* ``VRF``. .. index:: show ip route vrf VRF table TABLENO .. clicmd:: show ip route vrf VRF table TABLENO The show command is only available with :option:`-n` option. This command will dump the routing table ``TABLENO`` of the *Linux network namespace* ``VRF``. .. index:: show ip route vrf VRF tables .. clicmd:: show ip route vrf VRF tables This command will dump the routing tables within the vrf scope. If `vrf all` is executed, all routing tables will be dumped. By using the :option:`-n` option, the *Linux network namespace* will be mapped over the *Zebra* VRF. One nice feature that is possible by handling *Linux network namespace* is the ability to name default VRF. At startup, *Zebra* discovers the available *Linux network namespace* by parsing folder `/var/run/netns`. Each file stands for a *Linux network namespace*, but not all *Linux network namespaces* are available under that folder. This is the case for default VRF. It is possible to name the default VRF, by creating a file, by executing following commands. .. code-block:: shell touch /var/run/netns/vrf0 mount --bind /proc/self/ns/net /var/run/netns/vrf0 Above command illustrates what happens when the default VRF is visible under `var/run/netns/`. Here, the default VRF file is `vrf0`. At startup, FRR detects the presence of that file. It detects that the file statistics information matches the same file statistics information as `/proc/self/ns/net` ( through stat() function). As statistics information matches, then `vrf0` stands for the new default namespace name. Consequently, the VRF naming `Default` will be overridden by the new discovered namespace name `vrf0`. For those who don't use VRF backend with *Linux network namespace*, it is possible to statically configure and recompile FRR. It is possible to choose an alternate name for default VRF. Then, the default VRF naming will automatically be updated with the new name. To illustrate, if you want to recompile with `global` value, use the following command: .. code-block:: shell ./configure --with-defaultvrfname=global .. _zebra-mpls: MPLS Commands ============= You can configure static mpls entries in zebra. Basically, handling MPLS consists of popping, swapping or pushing labels to IP packets. MPLS Acronyms ------------- :abbr:`LSR (Labeled Switch Router)` Networking devices handling labels used to forward traffic between and through them. :abbr:`LER (Labeled Edge Router)` A Labeled edge router is located at the edge of an MPLS network, generally between an IP network and an MPLS network. MPLS Push Action ---------------- The push action is generally used for LER devices, which want to encapsulate all traffic for a wished destination into an MPLS label. This action is stored in routing entry, and can be configured like a route: .. index:: [no] ip route NETWORK MASK GATEWAY|INTERFACE label LABEL .. clicmd:: [no] ip route NETWORK MASK GATEWAY|INTERFACE label LABEL NETWORK and MASK stand for the IP prefix entry to be added as static route entry. GATEWAY is the gateway IP address to reach, in order to reach the prefix. INTERFACE is the interface behind which the prefix is located. LABEL is the MPLS label to use to reach the prefix abovementioned. You can check that the static entry is stored in the zebra RIB database, by looking at the presence of the entry. :: zebra(configure)# ip route 1.1.1.1/32 10.0.1.1 label 777 zebra# show ip route Codes: K - kernel route, C - connected, S - static, R - RIP, O - OSPF, I - IS-IS, B - BGP, E - EIGRP, N - NHRP, T - Table, v - VNC, V - VNC-Direct, A - Babel, D - SHARP, F - PBR, > - selected route, * - FIB route S>* 1.1.1.1/32 [1/0] via 10.0.1.1, r2-eth0, label 777, 00:39:42 MPLS Swap and Pop Action ------------------------ The swap action is generally used for LSR devices, which swap a packet with a label, with an other label. The Pop action is used on LER devices, at the termination of the MPLS traffic; this is used to remove MPLS header. .. index:: [no] mpls lsp INCOMING_LABEL GATEWAY OUTGOING_LABEL|explicit-null|implicit-null .. clicmd:: [no] mpls lsp INCOMING_LABEL GATEWAY OUTGOING_LABEL|explicit-null|implicit-null INCOMING_LABEL and OUTGOING_LABEL are MPLS labels with values ranging from 16 to 1048575. GATEWAY is the gateway IP address where to send MPLS packet. The outgoing label can either be a value or have an explicit-null label header. This specific header can be read by IP devices. The incoming label can also be removed; in that case the implicit-null keyword is used, and the outgoing packet emitted is an IP packet without MPLS header. You can check that the MPLS actions are stored in the zebra MPLS table, by looking at the presence of the entry. .. index:: show mpls table .. clicmd:: show mpls table :: zebra(configure)# mpls lsp 18 10.125.0.2 implicit-null zebra(configure)# mpls lsp 19 10.125.0.2 20 zebra(configure)# mpls lsp 21 10.125.0.2 explicit-null zebra# show mpls table Inbound Outbound Label Type Nexthop Label -------- ------- --------------- -------- 18 Static 10.125.0.2 implicit-null 19 Static 10.125.0.2 20 21 Static 10.125.0.2 IPv4 Explicit Null .. _multicast-rib-commands: Multicast RIB Commands ====================== The Multicast RIB provides a separate table of unicast destinations which is used for Multicast Reverse Path Forwarding decisions. It is used with a multicast source's IP address, hence contains not multicast group addresses but unicast addresses. This table is fully separate from the default unicast table. However, RPF lookup can include the unicast table. WARNING: RPF lookup results are non-responsive in this version of FRR, i.e. multicast routing does not actively react to changes in underlying unicast topology! .. index:: ip multicast rpf-lookup-mode MODE .. clicmd:: ip multicast rpf-lookup-mode MODE .. index:: no ip multicast rpf-lookup-mode [MODE] .. clicmd:: no ip multicast rpf-lookup-mode [MODE] MODE sets the method used to perform RPF lookups. Supported modes: urib-only Performs the lookup on the Unicast RIB. The Multicast RIB is never used. mrib-only Performs the lookup on the Multicast RIB. The Unicast RIB is never used. mrib-then-urib Tries to perform the lookup on the Multicast RIB. If any route is found, that route is used. Otherwise, the Unicast RIB is tried. lower-distance Performs a lookup on the Multicast RIB and Unicast RIB each. The result with the lower administrative distance is used; if they're equal, the Multicast RIB takes precedence. longer-prefix Performs a lookup on the Multicast RIB and Unicast RIB each. The result with the longer prefix length is used; if they're equal, the Multicast RIB takes precedence. The `mrib-then-urib` setting is the default behavior if nothing is configured. If this is the desired behavior, it should be explicitly configured to make the configuration immune against possible changes in what the default behavior is. .. warning:: Unreachable routes do not receive special treatment and do not cause fallback to a second lookup. .. index:: show ip rpf ADDR .. clicmd:: show ip rpf ADDR Performs a Multicast RPF lookup, as configured with ``ip multicast rpf-lookup-mode MODE``. ADDR specifies the multicast source address to look up. :: > show ip rpf 192.0.2.1 Routing entry for 192.0.2.0/24 using Unicast RIB Known via "kernel", distance 0, metric 0, best * 198.51.100.1, via eth0 Indicates that a multicast source lookup for 192.0.2.1 would use an Unicast RIB entry for 192.0.2.0/24 with a gateway of 198.51.100.1. .. index:: show ip rpf .. clicmd:: show ip rpf Prints the entire Multicast RIB. Note that this is independent of the configured RPF lookup mode, the Multicast RIB may be printed yet not used at all. .. index:: ip mroute PREFIX NEXTHOP [DISTANCE] .. clicmd:: ip mroute PREFIX NEXTHOP [DISTANCE] .. index:: no ip mroute PREFIX NEXTHOP [DISTANCE] .. clicmd:: no ip mroute PREFIX NEXTHOP [DISTANCE] Adds a static route entry to the Multicast RIB. This performs exactly as the ``ip route`` command, except that it inserts the route in the Multicast RIB instead of the Unicast RIB. .. _zebra-route-filtering: zebra Route Filtering ===================== Zebra supports :dfn:`prefix-list` s and :ref:`route-map` s to match routes received from other FRR components. The permit/deny facilities provided by these commands can be used to filter which routes zebra will install in the kernel. .. index:: ip protocol PROTOCOL route-map ROUTEMAP .. clicmd:: ip protocol PROTOCOL route-map ROUTEMAP Apply a route-map filter to routes for the specified protocol. PROTOCOL can be **any** or one of - system, - kernel, - connected, - static, - rip, - ripng, - ospf, - ospf6, - isis, - bgp, - hsls. .. index:: set src ADDRESS .. clicmd:: set src ADDRESS Within a route-map, set the preferred source address for matching routes when installing in the kernel. The following creates a prefix-list that matches all addresses, a route-map that sets the preferred source address, and applies the route-map to all *rip* routes. .. code-block:: frr ip prefix-list ANY permit 0.0.0.0/0 le 32 route-map RM1 permit 10 match ip address prefix-list ANY set src 10.0.0.1 ip protocol rip route-map RM1 .. _zebra-fib-push-interface: zebra FIB push interface ======================== Zebra supports a 'FIB push' interface that allows an external component to learn the forwarding information computed by the FRR routing suite. This is a loadable module that needs to be enabled at startup as described in :ref:`loadable-module-support`. In FRR, the Routing Information Base (RIB) resides inside zebra. Routing protocols communicate their best routes to zebra, and zebra computes the best route across protocols for each prefix. This latter information makes up the Forwarding Information Base (FIB). Zebra feeds the FIB to the kernel, which allows the IP stack in the kernel to forward packets according to the routes computed by FRR. The kernel FIB is updated in an OS-specific way. For example, the `Netlink` interface is used on Linux, and route sockets are used on FreeBSD. The FIB push interface aims to provide a cross-platform mechanism to support scenarios where the router has a forwarding path that is distinct from the kernel, commonly a hardware-based fast path. In these cases, the FIB needs to be maintained reliably in the fast path as well. We refer to the component that programs the forwarding plane (directly or indirectly) as the Forwarding Plane Manager or FPM. The FIB push interface comprises of a TCP connection between zebra and the FPM. The connection is initiated by zebra -- that is, the FPM acts as the TCP server. .. program:: configure The relevant zebra code kicks in when zebra is configured with the :option:`--enable-fpm` flag. Zebra periodically attempts to connect to the well-known FPM port. Once the connection is up, zebra starts sending messages containing routes over the socket to the FPM. Zebra sends a complete copy of the forwarding table to the FPM, including routes that it may have picked up from the kernel. The existing interaction of zebra with the kernel remains unchanged -- that is, the kernel continues to receive FIB updates as before. The encapsulation header for the messages exchanged with the FPM is defined by the file :file:`fpm/fpm.h` in the frr tree. The routes themselves are encoded in Netlink or protobuf format, with Netlink being the default. Protobuf is one of a number of new serialization formats wherein the message schema is expressed in a purpose-built language. Code for encoding/decoding to/from the wire format is generated from the schema. Protobuf messages can be extended easily while maintaining backward-compatibility with older code. Protobuf has the following advantages over Netlink: - Code for serialization/deserialization is generated automatically. This reduces the likelihood of bugs, allows third-party programs to be integrated quickly, and makes it easy to add fields. - The message format is not tied to an OS (Linux), and can be evolved independently. As mentioned before, zebra encodes routes sent to the FPM in Netlink format by default. The format can be controlled via the FPM module's load-time option to zebra, which currently takes the values `Netlink` and `protobuf`. The zebra FPM interface uses replace semantics. That is, if a 'route add' message for a prefix is followed by another 'route add' message, the information in the second message is complete by itself, and replaces the information sent in the first message. If the connection to the FPM goes down for some reason, zebra sends the FPM a complete copy of the forwarding table(s) when it reconnects. .. _zebra-dplane: Dataplane Commands ================== The zebra dataplane subsystem provides a framework for FIB programming. Zebra uses the dataplane to program the local kernel as it makes changes to objects such as IP routes, MPLS LSPs, and interface IP addresses. The dataplane runs in its own pthread, in order to off-load work from the main zebra pthread. .. index:: show zebra dplane [detailed] .. clicmd:: show zebra dplane [detailed] Display statistics about the updates and events passing through the dataplane subsystem. .. index:: show zebra dplane providers .. clicmd:: show zebra dplane providers Display information about the running dataplane plugins that are providing updates to a FIB. By default, the local kernel plugin is present. .. index:: zebra dplane limit [NUMBER] .. clicmd:: zebra dplane limit [NUMBER] Configure the limit on the number of pending updates that are waiting to be processed by the dataplane pthread. zebra Terminal Mode Commands ============================ .. index:: show ip route .. clicmd:: show ip route Display current routes which zebra holds in its database. :: Router# show ip route Codes: K - kernel route, C - connected, S - static, R - RIP, B - BGP * - FIB route. K* 0.0.0.0/0 203.181.89.241 S 0.0.0.0/0 203.181.89.1 C* 127.0.0.0/8 lo C* 203.181.89.240/28 eth0 .. index:: show ipv6 route .. clicmd:: show ipv6 route .. index:: show interface .. clicmd:: show interface .. index:: show ip prefix-list [NAME] .. clicmd:: show ip prefix-list [NAME] .. index:: show route-map [NAME] .. clicmd:: show route-map [NAME] .. index:: show ip protocol .. clicmd:: show ip protocol .. index:: show ipforward .. clicmd:: show ipforward Display whether the host's IP forwarding function is enabled or not. Almost any UNIX kernel can be configured with IP forwarding disabled. If so, the box can't work as a router. .. index:: show ipv6forward .. clicmd:: show ipv6forward Display whether the host's IP v6 forwarding is enabled or not. .. index:: show zebra .. clicmd:: show zebra Display various statistics related to the installation and deletion of routes, neighbor updates, and LSP's into the kernel. .. index:: show zebra client [summary] .. clicmd:: show zebra client [summary] Display statistics about clients that are connected to zebra. This is useful for debugging and seeing how much data is being passed between zebra and it's clients. If the summary form of the command is choosen a table is displayed with shortened information. .. index:: show zebra router table summary .. clicmd:: show zebra router table summary Display summarized data about tables created, their afi/safi/tableid and how many routes each table contains. Please note this is the total number of route nodes in the table. Which will be higher than the actual number of routes that are held. .. index:: show zebra fpm stats .. clicmd:: show zebra fpm stats Display statistics related to the zebra code that interacts with the optional Forwarding Plane Manager (FPM) component. .. index:: clear zebra fpm stats .. clicmd:: clear zebra fpm stats Reset statistics related to the zebra code that interacts with the optional Forwarding Plane Manager (FPM) component. frr-7.2.1/eigrpd/0000755000000000000000000000000013610377563010546 500000000000000frr-7.2.1/eigrpd/Makefile0000644000000000000000000000022513610377563012125 00000000000000all: ALWAYS @$(MAKE) -s -C .. eigrpd/eigrpd %: ALWAYS @$(MAKE) -s -C .. eigrpd/$@ Makefile: #nothing ALWAYS: .PHONY: ALWAYS makefiles .SUFFIXES: frr-7.2.1/eigrpd/eigrp_cli.c0000644000000000000000000006347013610377563012601 00000000000000/* * EIGRP daemon CLI implementation. * * Copyright (C) 2019 Network Device Education Foundation, Inc. ("NetDEF") * Rafael Zalamena * * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation; either version 2 of the License, or * (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program; if not, write to the Free Software * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA * 02110-1301 USA. */ #include #include "lib/command.h" #include "lib/log.h" #include "lib/northbound_cli.h" #include "eigrp_structs.h" #include "eigrpd.h" #include "eigrp_zebra.h" #ifndef VTYSH_EXTRACT_PL #include "eigrpd/eigrp_cli_clippy.c" #endif /* VTYSH_EXTRACT_PL */ /* * XPath: /frr-eigrpd:eigrpd/instance */ DEFPY_NOSH( router_eigrp, router_eigrp_cmd, "router eigrp (1-65535)$as [vrf NAME]", ROUTER_STR EIGRP_STR AS_STR VRF_CMD_HELP_STR) { char xpath[XPATH_MAXLEN]; int rv; snprintf(xpath, sizeof(xpath), "/frr-eigrpd:eigrpd/instance[asn='%s'][vrf='%s']", as_str, vrf ? vrf : VRF_DEFAULT_NAME); nb_cli_enqueue_change(vty, xpath, NB_OP_CREATE, NULL); rv = nb_cli_apply_changes(vty, NULL); if (rv == CMD_SUCCESS) VTY_PUSH_XPATH(EIGRP_NODE, xpath); return rv; } DEFPY( no_router_eigrp, no_router_eigrp_cmd, "no router eigrp (1-65535)$as [vrf NAME]", NO_STR ROUTER_STR EIGRP_STR AS_STR VRF_CMD_HELP_STR) { char xpath[XPATH_MAXLEN]; snprintf(xpath, sizeof(xpath), "/frr-eigrpd:eigrpd/instance[asn='%s'][vrf='%s']", as_str, vrf ? vrf : VRF_DEFAULT_NAME); nb_cli_enqueue_change(vty, xpath, NB_OP_DESTROY, NULL); return nb_cli_apply_changes(vty, NULL); } void eigrp_cli_show_header(struct vty *vty, struct lyd_node *dnode, bool show_defaults) { const char *asn = yang_dnode_get_string(dnode, "./asn"); const char *vrf = yang_dnode_get_string(dnode, "./vrf"); vty_out(vty, "router eigrp %s", asn); if (strcmp(vrf, VRF_DEFAULT_NAME)) vty_out(vty, " vrf %s", vrf); vty_out(vty, "\n"); } void eigrp_cli_show_end_header(struct vty *vty, struct lyd_node *dnode) { vty_out(vty, "!\n"); } /* * XPath: /frr-eigrpd:eigrpd/instance/router-id */ DEFPY( eigrp_router_id, eigrp_router_id_cmd, "eigrp router-id A.B.C.D$addr", EIGRP_STR "Router ID for this EIGRP process\n" "EIGRP Router-ID in IP address format\n") { nb_cli_enqueue_change(vty, "./router-id", NB_OP_MODIFY, addr_str); return nb_cli_apply_changes(vty, NULL); } DEFPY( no_eigrp_router_id, no_eigrp_router_id_cmd, "no eigrp router-id [A.B.C.D]", NO_STR EIGRP_STR "Router ID for this EIGRP process\n" "EIGRP Router-ID in IP address format\n") { nb_cli_enqueue_change(vty, "./router-id", NB_OP_DESTROY, NULL); return nb_cli_apply_changes(vty, NULL); } void eigrp_cli_show_router_id(struct vty *vty, struct lyd_node *dnode, bool show_defaults) { const char *router_id = yang_dnode_get_string(dnode, NULL); vty_out(vty, " eigrp router-id %s\n", router_id); } /* * XPath: /frr-eigrpd:eigrpd/instance/passive-interface */ DEFPY( eigrp_passive_interface, eigrp_passive_interface_cmd, "[no] passive-interface IFNAME", NO_STR "Suppress routing updates on an interface\n" "Interface to suppress on\n") { if (no) nb_cli_enqueue_change(vty, "./passive-interface", NB_OP_DESTROY, ifname); else nb_cli_enqueue_change(vty, "./passive-interface", NB_OP_CREATE, ifname); return nb_cli_apply_changes(vty, NULL); } void eigrp_cli_show_passive_interface(struct vty *vty, struct lyd_node *dnode, bool show_defaults) { const char *ifname = yang_dnode_get_string(dnode, NULL); vty_out(vty, " passive-interface %s\n", ifname); } /* * XPath: /frr-eigrpd:eigrpd/instance/active-time */ DEFPY( eigrp_timers_active, eigrp_timers_active_cmd, "timers active-time <(1-65535)$timer|disabled$disabled>", "Adjust routing timers\n" "Time limit for active state\n" "Active state time limit in seconds\n" "Disable time limit for active state\n") { if (disabled) nb_cli_enqueue_change(vty, "./active-time", NB_OP_MODIFY, "0"); else nb_cli_enqueue_change(vty, "./active-time", NB_OP_MODIFY, timer_str); return nb_cli_apply_changes(vty, NULL); } DEFPY( no_eigrp_timers_active, no_eigrp_timers_active_cmd, "no timers active-time [<(1-65535)|disabled>]", NO_STR "Adjust routing timers\n" "Time limit for active state\n" "Active state time limit in seconds\n" "Disable time limit for active state\n") { nb_cli_enqueue_change(vty, "./active-time", NB_OP_DESTROY, NULL); return nb_cli_apply_changes(vty, NULL); } void eigrp_cli_show_active_time(struct vty *vty, struct lyd_node *dnode, bool show_defaults) { const char *timer = yang_dnode_get_string(dnode, NULL); vty_out(vty, " timers active-time %s\n", timer); } /* * XPath: /frr-eigrpd:eigrpd/instance/variance */ DEFPY( eigrp_variance, eigrp_variance_cmd, "variance (1-128)$variance", "Control load balancing variance\n" "Metric variance multiplier\n") { nb_cli_enqueue_change(vty, "./variance", NB_OP_MODIFY, variance_str); return nb_cli_apply_changes(vty, NULL); } DEFPY( no_eigrp_variance, no_eigrp_variance_cmd, "no variance [(1-128)]", NO_STR "Control load balancing variance\n" "Metric variance multiplier\n") { nb_cli_enqueue_change(vty, "./variance", NB_OP_DESTROY, NULL); return nb_cli_apply_changes(vty, NULL); } void eigrp_cli_show_variance(struct vty *vty, struct lyd_node *dnode, bool show_defaults) { const char *variance = yang_dnode_get_string(dnode, NULL); vty_out(vty, " variance %s\n", variance); } /* * XPath: /frr-eigrpd:eigrpd/instance/maximum-paths */ DEFPY( eigrp_maximum_paths, eigrp_maximum_paths_cmd, "maximum-paths (1-32)$maximum_paths", "Forward packets over multiple paths\n" "Number of paths\n") { nb_cli_enqueue_change(vty, "./maximum-paths", NB_OP_MODIFY, maximum_paths_str); return nb_cli_apply_changes(vty, NULL); } DEFPY( no_eigrp_maximum_paths, no_eigrp_maximum_paths_cmd, "no maximum-paths [(1-32)]", NO_STR "Forward packets over multiple paths\n" "Number of paths\n") { nb_cli_enqueue_change(vty, "./maximum-paths", NB_OP_DESTROY, NULL); return nb_cli_apply_changes(vty, NULL); } void eigrp_cli_show_maximum_paths(struct vty *vty, struct lyd_node *dnode, bool show_defaults) { const char *maximum_paths = yang_dnode_get_string(dnode, NULL); vty_out(vty, " maximum-paths %s\n", maximum_paths); } /* * XPath: /frr-eigrpd:eigrpd/instance/metric-weights/K1 * XPath: /frr-eigrpd:eigrpd/instance/metric-weights/K2 * XPath: /frr-eigrpd:eigrpd/instance/metric-weights/K3 * XPath: /frr-eigrpd:eigrpd/instance/metric-weights/K4 * XPath: /frr-eigrpd:eigrpd/instance/metric-weights/K5 * XPath: /frr-eigrpd:eigrpd/instance/metric-weights/K6 */ DEFPY( eigrp_metric_weights, eigrp_metric_weights_cmd, "metric weights (0-255)$k1 (0-255)$k2 (0-255)$k3 (0-255)$k4 (0-255)$k5 [(0-255)$k6]", "Modify metrics and parameters for advertisement\n" "Modify metric coefficients\n" "K1\n" "K2\n" "K3\n" "K4\n" "K5\n" "K6\n") { nb_cli_enqueue_change(vty, "./metric-weights/K1", NB_OP_MODIFY, k1_str); nb_cli_enqueue_change(vty, "./metric-weights/K2", NB_OP_MODIFY, k2_str); nb_cli_enqueue_change(vty, "./metric-weights/K3", NB_OP_MODIFY, k3_str); nb_cli_enqueue_change(vty, "./metric-weights/K4", NB_OP_MODIFY, k4_str); nb_cli_enqueue_change(vty, "./metric-weights/K5", NB_OP_MODIFY, k5_str); if (k6) nb_cli_enqueue_change(vty, "./metric-weights/K6", NB_OP_MODIFY, k6_str); return nb_cli_apply_changes(vty, NULL); } DEFPY( no_eigrp_metric_weights, no_eigrp_metric_weights_cmd, "no metric weights [(0-255) (0-255) (0-255) (0-255) (0-255) (0-255)]", NO_STR "Modify metrics and parameters for advertisement\n" "Modify metric coefficients\n" "K1\n" "K2\n" "K3\n" "K4\n" "K5\n" "K6\n") { nb_cli_enqueue_change(vty, "./metric-weights/K1", NB_OP_DESTROY, NULL); nb_cli_enqueue_change(vty, "./metric-weights/K2", NB_OP_DESTROY, NULL); nb_cli_enqueue_change(vty, "./metric-weights/K3", NB_OP_DESTROY, NULL); nb_cli_enqueue_change(vty, "./metric-weights/K4", NB_OP_DESTROY, NULL); nb_cli_enqueue_change(vty, "./metric-weights/K5", NB_OP_DESTROY, NULL); nb_cli_enqueue_change(vty, "./metric-weights/K6", NB_OP_DESTROY, NULL); return nb_cli_apply_changes(vty, NULL); } void eigrp_cli_show_metrics(struct vty *vty, struct lyd_node *dnode, bool show_defaults) { const char *k1, *k2, *k3, *k4, *k5, *k6; k1 = yang_dnode_exists(dnode, "./K1") ? yang_dnode_get_string(dnode, "./K1") : "0"; k2 = yang_dnode_exists(dnode, "./K2") ? yang_dnode_get_string(dnode, "./K2") : "0"; k3 = yang_dnode_exists(dnode, "./K3") ? yang_dnode_get_string(dnode, "./K3") : "0"; k4 = yang_dnode_exists(dnode, "./K4") ? yang_dnode_get_string(dnode, "./K4") : "0"; k5 = yang_dnode_exists(dnode, "./K5") ? yang_dnode_get_string(dnode, "./K5") : "0"; k6 = yang_dnode_exists(dnode, "./K6") ? yang_dnode_get_string(dnode, "./K6") : "0"; vty_out(vty, " metric weights %s %s %s %s %s", k1, k2, k3, k4, k5); if (k6) vty_out(vty, " %s", k6); vty_out(vty, "\n"); } /* * XPath: /frr-eigrpd:eigrpd/instance/network */ DEFPY( eigrp_network, eigrp_network_cmd, "[no] network A.B.C.D/M$prefix", NO_STR "Enable routing on an IP network\n" "EIGRP network prefix\n") { if (no) nb_cli_enqueue_change(vty, "./network", NB_OP_DESTROY, prefix_str); else nb_cli_enqueue_change(vty, "./network", NB_OP_CREATE, prefix_str); return nb_cli_apply_changes(vty, NULL); } void eigrp_cli_show_network(struct vty *vty, struct lyd_node *dnode, bool show_defaults) { const char *prefix = yang_dnode_get_string(dnode, NULL); vty_out(vty, " network %s\n", prefix); } /* * XPath: /frr-eigrpd:eigrpd/instance/neighbor */ DEFPY( eigrp_neighbor, eigrp_neighbor_cmd, "[no] neighbor A.B.C.D$addr", NO_STR "Specify a neighbor router\n" "Neighbor address\n") { if (no) nb_cli_enqueue_change(vty, "./neighbor", NB_OP_DESTROY, addr_str); else nb_cli_enqueue_change(vty, "./neighbor", NB_OP_CREATE, addr_str); return nb_cli_apply_changes(vty, NULL); } void eigrp_cli_show_neighbor(struct vty *vty, struct lyd_node *dnode, bool show_defaults) { const char *prefix = yang_dnode_get_string(dnode, NULL); vty_out(vty, " neighbor %s\n", prefix); } /* * XPath: /frr-eigrpd:eigrpd/instance/redistribute * XPath: /frr-eigrpd:eigrpd/instance/redistribute/route-map * XPath: /frr-eigrpd:eigrpd/instance/redistribute/metrics/bandwidth * XPath: /frr-eigrpd:eigrpd/instance/redistribute/metrics/delay * XPath: /frr-eigrpd:eigrpd/instance/redistribute/metrics/reliability * XPath: /frr-eigrpd:eigrpd/instance/redistribute/metrics/load * XPath: /frr-eigrpd:eigrpd/instance/redistribute/metrics/mtu */ DEFPY( eigrp_redistribute_source_metric, eigrp_redistribute_source_metric_cmd, "[no] redistribute " FRR_REDIST_STR_EIGRPD "$proto [metric (1-4294967295)$bw (0-4294967295)$delay (0-255)$rlbt (1-255)$load (1-65535)$mtu]", NO_STR REDIST_STR FRR_REDIST_HELP_STR_EIGRPD "Metric for redistributed routes\n" "Bandwidth metric in Kbits per second\n" "EIGRP delay metric, in 10 microsecond units\n" "EIGRP reliability metric where 255 is 100% reliable2 ?\n" "EIGRP Effective bandwidth metric (Loading) where 255 is 100% loaded\n" "EIGRP MTU of the path\n") { char xpath[XPATH_MAXLEN], xpath_metric[XPATH_MAXLEN + 64]; snprintf(xpath, sizeof(xpath), "./redistribute[protocol='%s']", proto); if (no) { nb_cli_enqueue_change(vty, xpath, NB_OP_DESTROY, NULL); return nb_cli_apply_changes(vty, NULL); } nb_cli_enqueue_change(vty, xpath, NB_OP_CREATE, NULL); if (bw == 0 || delay == 0 || rlbt == 0 || load == 0 || mtu == 0) return nb_cli_apply_changes(vty, NULL); snprintf(xpath_metric, sizeof(xpath_metric), "%s/metrics/bandwidth", xpath); nb_cli_enqueue_change(vty, xpath_metric, NB_OP_MODIFY, bw_str); snprintf(xpath_metric, sizeof(xpath_metric), "%s/metrics/delay", xpath); nb_cli_enqueue_change(vty, xpath_metric, NB_OP_MODIFY, delay_str); snprintf(xpath_metric, sizeof(xpath_metric), "%s/metrics/reliability", xpath); nb_cli_enqueue_change(vty, xpath_metric, NB_OP_MODIFY, rlbt_str); snprintf(xpath_metric, sizeof(xpath_metric), "%s/metrics/load", xpath); nb_cli_enqueue_change(vty, xpath_metric, NB_OP_MODIFY, load_str); snprintf(xpath_metric, sizeof(xpath_metric), "%s/metrics/mtu", xpath); nb_cli_enqueue_change(vty, xpath_metric, NB_OP_MODIFY, mtu_str); return nb_cli_apply_changes(vty, NULL); } void eigrp_cli_show_redistribute(struct vty *vty, struct lyd_node *dnode, bool show_defaults) { const char *proto = yang_dnode_get_string(dnode, "./protocol"); const char *bw, *delay, *load, *mtu, *rlbt; bw = yang_dnode_exists(dnode, "./metrics/bandwidth") ? yang_dnode_get_string(dnode, "./metrics/bandwidth") : NULL; delay = yang_dnode_exists(dnode, "./metrics/delay") ? yang_dnode_get_string(dnode, "./metrics/delay") : NULL; rlbt = yang_dnode_exists(dnode, "./metrics/reliability") ? yang_dnode_get_string(dnode, "./metrics/reliability") : NULL; load = yang_dnode_exists(dnode, "./metrics/load") ? yang_dnode_get_string(dnode, "./metrics/load") : NULL; mtu = yang_dnode_exists(dnode, "./metrics/mtu") ? yang_dnode_get_string(dnode, "./metrics/mtu") : NULL; vty_out(vty, " redistribute %s", proto); if (bw || rlbt || delay || load || mtu) vty_out(vty, " metric %s %s %s %s %s", bw, delay, rlbt, load, mtu); vty_out(vty, "\n"); } /* * XPath: /frr-interface:lib/interface/frr-eigrpd:eigrp/delay */ DEFPY( eigrp_if_delay, eigrp_if_delay_cmd, "delay (1-16777215)$delay", "Specify interface throughput delay\n" "Throughput delay (tens of microseconds)\n") { nb_cli_enqueue_change(vty, "./frr-eigrpd:eigrp/delay", NB_OP_MODIFY, delay_str); return nb_cli_apply_changes(vty, NULL); } DEFPY( no_eigrp_if_delay, no_eigrp_if_delay_cmd, "no delay [(1-16777215)]", NO_STR "Specify interface throughput delay\n" "Throughput delay (tens of microseconds)\n") { nb_cli_enqueue_change(vty, "./frr-eigrpd:eigrp/delay", NB_OP_DESTROY, NULL); return nb_cli_apply_changes(vty, NULL); } void eigrp_cli_show_delay(struct vty *vty, struct lyd_node *dnode, bool show_defaults) { const char *delay = yang_dnode_get_string(dnode, NULL); vty_out(vty, " delay %s\n", delay); } /* * XPath: /frr-interface:lib/interface/frr-eigrpd:eigrp/bandwidth */ DEFPY( eigrp_if_bandwidth, eigrp_if_bandwidth_cmd, "eigrp bandwidth (1-10000000)$bw", EIGRP_STR "Set bandwidth informational parameter\n" "Bandwidth in kilobits\n") { nb_cli_enqueue_change(vty, "./frr-eigrpd:eigrp/bandwidth", NB_OP_MODIFY, bw_str); return nb_cli_apply_changes(vty, NULL); } DEFPY( no_eigrp_if_bandwidth, no_eigrp_if_bandwidth_cmd, "no eigrp bandwidth [(1-10000000)]", NO_STR EIGRP_STR "Set bandwidth informational parameter\n" "Bandwidth in kilobits\n") { nb_cli_enqueue_change(vty, "./frr-eigrpd:eigrp/bandwidth", NB_OP_DESTROY, NULL); return nb_cli_apply_changes(vty, NULL); } void eigrp_cli_show_bandwidth(struct vty *vty, struct lyd_node *dnode, bool show_defaults) { const char *bandwidth = yang_dnode_get_string(dnode, NULL); vty_out(vty, " eigrp bandwidth %s\n", bandwidth); } /* * XPath: /frr-interface:lib/interface/frr-eigrpd:eigrp/hello-interval */ DEFPY( eigrp_if_ip_hellointerval, eigrp_if_ip_hellointerval_cmd, "ip hello-interval eigrp (1-65535)$hello", "Interface Internet Protocol config commands\n" "Configures EIGRP hello interval\n" EIGRP_STR "Seconds between hello transmissions\n") { nb_cli_enqueue_change(vty, "./frr-eigrpd:eigrp/hello-interval", NB_OP_MODIFY, hello_str); return nb_cli_apply_changes(vty, NULL); } DEFPY( no_eigrp_if_ip_hellointerval, no_eigrp_if_ip_hellointerval_cmd, "no ip hello-interval eigrp [(1-65535)]", NO_STR "Interface Internet Protocol config commands\n" "Configures EIGRP hello interval\n" EIGRP_STR "Seconds between hello transmissions\n") { nb_cli_enqueue_change(vty, "./frr-eigrpd:eigrp/hello-interval", NB_OP_DESTROY, NULL); return nb_cli_apply_changes(vty, NULL); } void eigrp_cli_show_hello_interval(struct vty *vty, struct lyd_node *dnode, bool show_defaults) { const char *hello = yang_dnode_get_string(dnode, NULL); vty_out(vty, " ip hello-interval eigrp %s\n", hello); } /* * XPath: /frr-interface:lib/interface/frr-eigrpd:eigrp/hold-time */ DEFPY( eigrp_if_ip_holdinterval, eigrp_if_ip_holdinterval_cmd, "ip hold-time eigrp (1-65535)$hold", "Interface Internet Protocol config commands\n" "Configures EIGRP IPv4 hold time\n" EIGRP_STR "Seconds before neighbor is considered down\n") { nb_cli_enqueue_change(vty, "./frr-eigrpd:eigrp/hold-time", NB_OP_MODIFY, hold_str); return nb_cli_apply_changes(vty, NULL); } DEFPY( no_eigrp_if_ip_holdinterval, no_eigrp_if_ip_holdinterval_cmd, "no ip hold-time eigrp [(1-65535)]", NO_STR "Interface Internet Protocol config commands\n" "Configures EIGRP IPv4 hold time\n" EIGRP_STR "Seconds before neighbor is considered down\n") { nb_cli_enqueue_change(vty, "./frr-eigrpd:eigrp/hold-time", NB_OP_DESTROY, NULL); return nb_cli_apply_changes(vty, NULL); } void eigrp_cli_show_hold_time(struct vty *vty, struct lyd_node *dnode, bool show_defaults) { const char *holdtime = yang_dnode_get_string(dnode, NULL); vty_out(vty, " ip hold-time eigrp %s\n", holdtime); } /* * XPath: /frr-interface:lib/interface/frr-eigrpd:eigrp/split-horizon */ /* NOT implemented. */ /* * XPath: /frr-interface:lib/interface/frr-eigrpd:eigrp/instance * XPath: /frr-interface:lib/interface/frr-eigrpd:eigrp/instance/summarize-addresses */ DEFPY( eigrp_ip_summary_address, eigrp_ip_summary_address_cmd, "ip summary-address eigrp (1-65535)$as A.B.C.D/M$prefix", "Interface Internet Protocol config commands\n" "Perform address summarization\n" EIGRP_STR AS_STR "Summary /, e.g. 192.168.0.0/16\n") { char xpath[XPATH_MAXLEN], xpath_auth[XPATH_MAXLEN + 64]; snprintf(xpath, sizeof(xpath), "./frr-eigrpd:eigrp/instance[asn='%s']", as_str); nb_cli_enqueue_change(vty, xpath, NB_OP_CREATE, NULL); snprintf(xpath_auth, sizeof(xpath_auth), "%s/summarize-address", xpath); nb_cli_enqueue_change(vty, xpath_auth, NB_OP_CREATE, prefix_str); return nb_cli_apply_changes(vty, NULL); } DEFPY( no_eigrp_ip_summary_address, no_eigrp_ip_summary_address_cmd, "no ip summary-address eigrp (1-65535)$as A.B.C.D/M$prefix", NO_STR "Interface Internet Protocol config commands\n" "Perform address summarization\n" EIGRP_STR AS_STR "Summary /, e.g. 192.168.0.0/16\n") { char xpath[XPATH_MAXLEN], xpath_auth[XPATH_MAXLEN + 64]; snprintf(xpath, sizeof(xpath), "./frr-eigrpd:eigrp/instance[asn='%s']", as_str); nb_cli_enqueue_change(vty, xpath, NB_OP_CREATE, NULL); snprintf(xpath_auth, sizeof(xpath_auth), "%s/summarize-address", xpath); nb_cli_enqueue_change(vty, xpath_auth, NB_OP_DESTROY, prefix_str); return nb_cli_apply_changes(vty, NULL); } void eigrp_cli_show_summarize_address(struct vty *vty, struct lyd_node *dnode, bool show_defaults) { const struct eigrp_interface *eif = nb_running_get_entry(dnode, NULL, true); const char *summarize_address = yang_dnode_get_string(dnode, NULL); vty_out(vty, " ip summary-address eigrp %d %s\n", eif->eigrp->AS, summarize_address); } /* * XPath: /frr-interface:lib/interface/frr-eigrpd:eigrp/instance * XPath: /frr-interface:lib/interface/frr-eigrpd:eigrp/instance/authentication */ DEFPY( eigrp_authentication_mode, eigrp_authentication_mode_cmd, "ip authentication mode eigrp (1-65535)$as $crypt", "Interface Internet Protocol config commands\n" "Authentication subcommands\n" "Mode\n" EIGRP_STR AS_STR "Keyed message digest\n" "HMAC SHA256 algorithm \n") { char xpath[XPATH_MAXLEN], xpath_auth[XPATH_MAXLEN + 64]; snprintf(xpath, sizeof(xpath), "./frr-eigrpd:eigrp/instance[asn='%s']", as_str); nb_cli_enqueue_change(vty, xpath, NB_OP_CREATE, NULL); snprintf(xpath_auth, sizeof(xpath_auth), "%s/authentication", xpath); nb_cli_enqueue_change(vty, xpath_auth, NB_OP_MODIFY, crypt); return nb_cli_apply_changes(vty, NULL); } DEFPY( no_eigrp_authentication_mode, no_eigrp_authentication_mode_cmd, "no ip authentication mode eigrp (1-65535)$as []", NO_STR "Interface Internet Protocol config commands\n" "Authentication subcommands\n" "Mode\n" EIGRP_STR AS_STR "Keyed message digest\n" "HMAC SHA256 algorithm \n") { char xpath[XPATH_MAXLEN], xpath_auth[XPATH_MAXLEN + 64]; snprintf(xpath, sizeof(xpath), "./frr-eigrpd:eigrp/instance[asn='%s']", as_str); nb_cli_enqueue_change(vty, xpath, NB_OP_CREATE, NULL); snprintf(xpath_auth, sizeof(xpath_auth), "%s/authentication", xpath); nb_cli_enqueue_change(vty, xpath_auth, NB_OP_MODIFY, "none"); return nb_cli_apply_changes(vty, NULL); } void eigrp_cli_show_authentication(struct vty *vty, struct lyd_node *dnode, bool show_defaults) { const struct eigrp_interface *eif = nb_running_get_entry(dnode, NULL, true); const char *crypt = yang_dnode_get_string(dnode, NULL); vty_out(vty, " ip authentication mode eigrp %d %s\n", eif->eigrp->AS, crypt); } /* * XPath: /frr-interface:lib/interface/frr-eigrpd:eigrp/instance * XPath: /frr-interface:lib/interface/frr-eigrpd:eigrp/instance/keychain */ DEFPY( eigrp_authentication_keychain, eigrp_authentication_keychain_cmd, "ip authentication key-chain eigrp (1-65535)$as WORD$name", "Interface Internet Protocol config commands\n" "Authentication subcommands\n" "Key-chain\n" EIGRP_STR AS_STR "Name of key-chain\n") { char xpath[XPATH_MAXLEN], xpath_auth[XPATH_MAXLEN + 64]; snprintf(xpath, sizeof(xpath), "./frr-eigrpd:eigrp/instance[asn='%s']", as_str); nb_cli_enqueue_change(vty, xpath, NB_OP_CREATE, NULL); snprintf(xpath_auth, sizeof(xpath_auth), "%s/keychain", xpath); nb_cli_enqueue_change(vty, xpath_auth, NB_OP_MODIFY, name); return nb_cli_apply_changes(vty, NULL); } DEFPY( no_eigrp_authentication_keychain, no_eigrp_authentication_keychain_cmd, "no ip authentication key-chain eigrp (1-65535)$as [WORD]", NO_STR "Interface Internet Protocol config commands\n" "Authentication subcommands\n" "Key-chain\n" EIGRP_STR AS_STR "Name of key-chain\n") { char xpath[XPATH_MAXLEN], xpath_auth[XPATH_MAXLEN + 64]; snprintf(xpath, sizeof(xpath), "./frr-eigrpd:eigrp/instance[asn='%s']", as_str); snprintf(xpath_auth, sizeof(xpath_auth), "%s/keychain", xpath); nb_cli_enqueue_change(vty, xpath_auth, NB_OP_DESTROY, NULL); return nb_cli_apply_changes(vty, NULL); } void eigrp_cli_show_keychain(struct vty *vty, struct lyd_node *dnode, bool show_defaults) { const struct eigrp_interface *eif = nb_running_get_entry(dnode, NULL, true); const char *keychain = yang_dnode_get_string(dnode, NULL); vty_out(vty, " ip authentication key-chain eigrp %d %s\n", eif->eigrp->AS, keychain); } /* * CLI installation procedures. */ static struct cmd_node eigrp_node = {EIGRP_NODE, "%s(config-router)# ", 1}; static int eigrp_config_write(struct vty *vty) { struct lyd_node *dnode; int written = 0; dnode = yang_dnode_get(running_config->dnode, "/frr-eigrpd:eigrpd"); if (dnode) { nb_cli_show_dnode_cmds(vty, dnode, false); written = 1; } return written; } static struct cmd_node eigrp_interface_node = {INTERFACE_NODE, "%s(config-if)# ", 1}; static int eigrp_write_interface(struct vty *vty) { struct lyd_node *dnode; struct interface *ifp; struct vrf *vrf; int written = 0; RB_FOREACH(vrf, vrf_name_head, &vrfs_by_name) { FOR_ALL_INTERFACES(vrf, ifp) { dnode = yang_dnode_get( running_config->dnode, "/frr-interface:lib/interface[name='%s'][vrf='%s']", ifp->name, vrf->name); if (dnode == NULL) continue; written = 1; nb_cli_show_dnode_cmds(vty, dnode, false); } } return written; } void eigrp_cli_init(void) { install_element(CONFIG_NODE, &router_eigrp_cmd); install_element(CONFIG_NODE, &no_router_eigrp_cmd); install_node(&eigrp_node, eigrp_config_write); install_default(EIGRP_NODE); install_element(EIGRP_NODE, &eigrp_router_id_cmd); install_element(EIGRP_NODE, &no_eigrp_router_id_cmd); install_element(EIGRP_NODE, &eigrp_passive_interface_cmd); install_element(EIGRP_NODE, &eigrp_timers_active_cmd); install_element(EIGRP_NODE, &no_eigrp_timers_active_cmd); install_element(EIGRP_NODE, &eigrp_variance_cmd); install_element(EIGRP_NODE, &no_eigrp_variance_cmd); install_element(EIGRP_NODE, &eigrp_maximum_paths_cmd); install_element(EIGRP_NODE, &no_eigrp_maximum_paths_cmd); install_element(EIGRP_NODE, &eigrp_metric_weights_cmd); install_element(EIGRP_NODE, &no_eigrp_metric_weights_cmd); install_element(EIGRP_NODE, &eigrp_network_cmd); install_element(EIGRP_NODE, &eigrp_neighbor_cmd); install_element(EIGRP_NODE, &eigrp_redistribute_source_metric_cmd); install_node(&eigrp_interface_node, eigrp_write_interface); if_cmd_init(); install_element(INTERFACE_NODE, &eigrp_if_delay_cmd); install_element(INTERFACE_NODE, &no_eigrp_if_delay_cmd); install_element(INTERFACE_NODE, &eigrp_if_bandwidth_cmd); install_element(INTERFACE_NODE, &no_eigrp_if_bandwidth_cmd); install_element(INTERFACE_NODE, &eigrp_if_ip_hellointerval_cmd); install_element(INTERFACE_NODE, &no_eigrp_if_ip_hellointerval_cmd); install_element(INTERFACE_NODE, &eigrp_if_ip_holdinterval_cmd); install_element(INTERFACE_NODE, &no_eigrp_if_ip_holdinterval_cmd); install_element(INTERFACE_NODE, &eigrp_ip_summary_address_cmd); install_element(INTERFACE_NODE, &no_eigrp_ip_summary_address_cmd); install_element(INTERFACE_NODE, &eigrp_authentication_mode_cmd); install_element(INTERFACE_NODE, &no_eigrp_authentication_mode_cmd); install_element(INTERFACE_NODE, &eigrp_authentication_keychain_cmd); install_element(INTERFACE_NODE, &no_eigrp_authentication_keychain_cmd); } frr-7.2.1/eigrpd/eigrp_const.h0000644000000000000000000004121113610377563013152 00000000000000/* * EIGRP Definition of Constants. * Copyright (C) 2013-2016 * Authors: * Donnie Savage * Jan Janovic * Matej Perina * Peter Orsag * Peter Paluch * Frantisek Gazo * Tomas Hvorkovy * Martin Kontsek * Lukas Koribsky * * This file is part of GNU Zebra. * * GNU Zebra is free software; you can redistribute it and/or modify it * under the terms of the GNU General Public License as published by the * Free Software Foundation; either version 2, or (at your option) any * later version. * * GNU Zebra 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; see the file COPYING; if not, write to the Free Software * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA */ #ifndef _ZEBRA_EIGRP_CONST_H_ #define _ZEBRA_EIGRP_CONST_H_ #define EIGRP_NEIGHBOR_DOWN 0 #define EIGRP_NEIGHBOR_PENDING 1 #define EIGRP_NEIGHBOR_UP 2 #define EIGRP_NEIGHBOR_STATE_MAX 3 /*Packet requiring ack will be retransmitted again after this time*/ #define EIGRP_PACKET_RETRANS_TIME 2 /* in seconds */ #define EIGRP_PACKET_RETRANS_MAX 16 /* number of retrans attempts */ #define PLAINTEXT_LENGTH 81 /*Metric variance multiplier*/ #define EIGRP_VARIANCE_DEFAULT 1 #define EIGRP_MAX_PATHS_DEFAULT 4 /* Return values of functions involved in packet verification */ #define MSG_OK 0 #define MSG_NG 1 #define EIGRP_HEADER_VERSION 2 /* Default protocol, port number. */ #ifndef IPPROTO_EIGRPIGP #define IPPROTO_EIGRPIGP 88 #endif /* IPPROTO_EIGRPIGP */ #define EIGRP_AUTH_MD5_TLV_SIZE 40 #define EIGRP_AUTH_SHA256_TLV_SIZE 56 /*Cisco routers use only first 44 bytes of basic hello for their MD5 * calculations*/ #define EIGRP_MD5_BASIC_COMPUTE 44 #define EIGRP_MD5_UPDATE_INIT_COMPUTE 40 #define EIGRP_AUTH_BASIC_HELLO_FLAG 0x01 #define EIGRP_AUTH_TID_HELLO_FLAG 0x02 #define EIGRP_AUTH_UPDATE_INIT_FLAG 0x04 #define EIGRP_AUTH_UPDATE_FLAG 0x08 #define EIGRP_AUTH_EXTRA_SALT_FLAG 0x10 #define EIGRP_NEXT_SEQUENCE_TLV_SIZE 8 /* IP TTL for EIGRP protocol. */ #define EIGRP_IP_TTL 1 /* VTY port number. */ #define EIGRP_VTY_PORT 2613 /* Default configuration file name for eigrp. */ #define EIGRP_DEFAULT_CONFIG "eigrpd.conf" #define EIGRP_HELLO_INTERVAL_DEFAULT 5 #define EIGRP_HOLD_INTERVAL_DEFAULT 15 #define EIGRP_BANDWIDTH_DEFAULT 100000 #define EIGRP_DELAY_DEFAULT 10 #define EIGRP_RELIABILITY_DEFAULT 255 #define EIGRP_LOAD_DEFAULT 1 #define EIGRP_MULTICAST_ADDRESS 0xe000000A /*224.0.0.10*/ #define EIGRP_MAX_METRIC 0xffffffffU /*4294967295*/ enum metric_change { METRIC_DECREASE, METRIC_SAME, METRIC_INCREASE }; #define DEFAULT_ROUTE ZEBRA_ROUTE_MAX #define DEFAULT_ROUTE_TYPE(T) ((T) == DEFAULT_ROUTE) #define INTERFACE_DOWN_BY_ZEBRA 1 #define INTERFACE_DOWN_BY_VTY 2 #define INTERFACE_DOWN_BY_FINAL 3 #define EIGRP_HELLO_NORMAL 0x00 #define EIGRP_HELLO_GRACEFUL_SHUTDOWN 0x01 #define EIGRP_HELLO_ADD_SEQUENCE 0x02 #define EIGRP_HELLO_GRACEFUL_SHUTDOWN_NBR 0x04 /* EIGRP Network Type. */ #define EIGRP_IFTYPE_NONE 0 #define EIGRP_IFTYPE_POINTOPOINT 1 #define EIGRP_IFTYPE_BROADCAST 2 #define EIGRP_IFTYPE_NBMA 3 #define EIGRP_IFTYPE_POINTOMULTIPOINT 4 #define EIGRP_IFTYPE_LOOPBACK 5 #define EIGRP_IFTYPE_MAX 6 #define EIGRP_IF_ACTIVE 0 #define EIGRP_IF_PASSIVE 1 /* EIGRP TT destination type */ #define EIGRP_TOPOLOGY_TYPE_CONNECTED 0 // Connected network #define EIGRP_TOPOLOGY_TYPE_REMOTE 1 // Remote internal network #define EIGRP_TOPOLOGY_TYPE_REMOTE_EXTERNAL 2 // Remote external network /*EIGRP TT entry flags*/ #define EIGRP_NEXTHOP_ENTRY_SUCCESSOR_FLAG (1 << 0) #define EIGRP_NEXTHOP_ENTRY_FSUCCESSOR_FLAG (1 << 1) #define EIGRP_NEXTHOP_ENTRY_INTABLE_FLAG (1 << 2) #define EIGRP_NEXTHOP_ENTRY_EXTERNAL_FLAG (1 << 3) /*EIGRP FSM state count, event count*/ #define EIGRP_FSM_STATE_MAX 5 #define EIGRP_FSM_EVENT_MAX 16 /*EEGRP FSM states*/ enum eigrp_fsm_states { EIGRP_FSM_STATE_PASSIVE, EIGRP_FSM_STATE_ACTIVE_0, EIGRP_FSM_STATE_ACTIVE_1, EIGRP_FSM_STATE_ACTIVE_2, EIGRP_FSM_STATE_ACTIVE_3, }; /*EIGRP FSM events return values*/ #define EIGRP_FSM_NEED_UPDATE 1 #define EIGRP_FSM_NEED_QUERY 2 /*EIGRP FSM events*/ enum eigrp_fsm_events { /* * Input event other than query from succ, * FC is not satisfied */ EIGRP_FSM_EVENT_NQ_FCN, /* last reply, FD is reset */ EIGRP_FSM_EVENT_LR, /* Query from succ, FC not satisfied */ EIGRP_FSM_EVENT_Q_FCN, /* last reply, FC satisifed with current value of FDij */ EIGRP_FSM_EVENT_LR_FCS, /* distance increase while in a active state */ EIGRP_FSM_EVENT_DINC, /* Query from succ while in active state */ EIGRP_FSM_EVENT_QACT, /* last reply, FC not satisfied */ EIGRP_FSM_EVENT_LR_FCN, /* * state not changed * usually by receiving not last reply */ EIGRP_FSM_KEEP_STATE, }; /** * External routes originate from some other protocol - these are them */ #define NULL_PROTID 0 /*!< unknown protocol */ #define IGRP_PROTID 1 /*!< IGRP.. whos your daddy! */ #define EIGRP_PROTID 2 /*!< EIGRP - Just flat out the best */ #define STATIC_PROTID 3 /*!< Staticly configured source */ #define RIP_PROTID 4 /*!< Routing Information Protocol */ #define HELLO_PROTID 5 /*!< Hello? RFC-891 you there? */ #define OSPF_PROTID 6 /*!< OSPF - Open Shortest Path First */ #define ISIS_PROTID 7 /*!< Intermediate System To Intermediate System */ #define EGP_PROTID 8 /*!< Exterior Gateway Protocol */ #define BGP_PROTID 9 /*!< Border Gateway Protocol */ #define IDRP_PROTID 10 /*!< InterDomain Routing Protocol */ #define CONN_PROTID 11 /*!< Connected source */ /* * metric k-value defaults */ #define EIGRP_K1_DEFAULT 1 //!< unweighed inverse bandwidth #define EIGRP_K2_DEFAULT 0 //!< no loading term #define EIGRP_K3_DEFAULT 1 //!< unweighted delay #define EIGRP_K4_DEFAULT 0 //!< no reliability term #define EIGRP_K5_DEFAULT 0 //!< no reliability term #define EIGRP_K6_DEFAULT 0 //!< do not add in extended metrics /* * EIGRP Fixed header */ #define EIGRP_HEADER_LEN 20U #define EIGRP_PACKET_MAX_LEN 65535U /* includes IP Header size. */ #define EIGRP_TLV_HDR_LENGTH 4 /** * EIGRP Packet Opcodes */ #define EIGRP_OPC_UPDATE 1 /*!< packet containing routing information */ #define EIGRP_OPC_REQUEST 2 /*!< sent to request one or more routes */ #define EIGRP_OPC_QUERY 3 /*!< sent when a routing is in active start */ #define EIGRP_OPC_REPLY 4 /*!< sent in response to a query */ #define EIGRP_OPC_HELLO 5 /*!< sent to maintain a peering session */ #define EIGRP_OPC_IPXSAP 6 /*!< IPX SAP information */ #define EIGRP_OPC_PROBE 7 /*!< for test purposes */ #define EIGRP_OPC_ACK 8 /*!< acknowledge */ #define EIGRP_OPC_SIAQUERY 10 /*!< QUERY - with relaxed restrictions */ #define EIGRP_OPC_SIAREPLY 11 /*!< REPLY - may contain old routing information */ /** * EIGRP TLV Range definitions * PDM TLV Range * General 0x0000 * IPv4 0x0100 ** TLVs for one and all * ATALK 0x0200 ** legacy * IPX 0x0300 ** discontinued * IPv6 0x0400 ** legacy * Multiprotocol 0x0600 ** wide metrics * MultiTopology 0x00f0 ** deprecated */ #define EIGRP_TLV_RANGEMASK 0xfff0 /*!< should be 0xff00 - opps */ #define EIGRP_TLV_GENERAL 0x0000 /** * 1.2 TLV Definitions ** legacy * These are considered legacyu and are only used for backward compability with * older Cisco Routers. They should not be your first choice for packet codings */ #define EIGRP_TLV_IPv4 0x0100 /*!< Classic IPv4 TLV encoding */ #define EIGRP_TLV_ATALK 0x0200 /*!< Classic Appletalk TLV encoding*/ #define EIGRP_TLV_IPX 0x0300 /*!< Classic IPX TLV encoding */ #define EIGRP_TLV_IPv6 0x0400 /*!< Classic IPv6 TLV encoding */ /** * 2.0 Multi-Protocol TLV Definitions * These are the current packet formats and should be used for packets */ #define EIGRP_TLV_MP 0x0600 /*!< Non-PDM specific encoding */ /** * TLV type definitions. Generic (protocol-independent) TLV types are * defined here. Protocol-specific ones are defined elsewhere. */ #define EIGRP_TLV_PARAMETER (EIGRP_TLV_GENERAL | 0x0001) /*!< eigrp parameters */ #define EIGRP_TLV_PARAMETER_LEN (12U) #define EIGRP_TLV_AUTH (EIGRP_TLV_GENERAL | 0x0002) /*!< authentication */ #define EIGRP_TLV_SEQ (EIGRP_TLV_GENERAL | 0x0003) /*!< sequenced packet */ #define EIGRP_TLV_SEQ_BASE_LEN (5U) #define EIGRP_TLV_SW_VERSION (EIGRP_TLV_GENERAL | 0x0004) /*!< software version */ #define EIGRP_TLV_SW_VERSION_LEN (8U) #define EIGRP_TLV_NEXT_MCAST_SEQ (EIGRP_TLV_GENERAL | 0x0005) /*!< sequence number */ #define EIGRP_TLV_PEER_TERMINATION (EIGRP_TLV_GENERAL | 0x0007) /*!< peer termination */ #define EIGRP_TLV_PEER_TERMINATION_LEN (9U) #define EIGRP_TLV_PEER_TIDLIST (EIGRP_TLV_GENERAL | 0x0008) /*!< peer sub-topology list */ /* Older cisco routers send TIDLIST value wrong, adding for backwards * compatabily */ #define EIGRP_TLV_PEER_MTRLIST (EIGRP_TLV_GENERAL | 0x00f5) /** * Route Based TLVs */ #define EIGRP_TLV_REQUEST 0x0001 #define EIGRP_TLV_INTERNAL 0x0002 #define EIGRP_TLV_EXTERNAL 0x0003 #define EIGRP_TLV_COMMUNITY 0x0004 #define EIGRP_TLV_TYPEMASK 0x000f #define EIGRP_TLV_IPv4_REQ (EIGRP_TLV_IPv4 | EIGRP_TLV_REQUEST) #define EIGRP_TLV_IPv4_INT (EIGRP_TLV_IPv4 | EIGRP_TLV_INTERNAL) #define EIGRP_TLV_IPv4_EXT (EIGRP_TLV_IPv4 | EIGRP_TLV_EXTERNAL) #define EIGRP_TLV_IPv4_COM (EIGRP_TLV_IPv4 | EIGRP_TLV_COMMUNITY) #define EIGRP_TLV_IPV4_SIZE_GRT_24_BIT 0x001D #define EIGRP_TLV_IPV4_SIZE_GRT_16_BIT 0x001C #define EIGRP_TLV_IPV4_SIZE_GRT_8_BIT 0x001B #define EIGRP_TLV_IPV4_SIZE_GRT_0_BIT 0x001A #define EIGRP_TLV_MAX_IPV4_BYTE EIGRP_TLV_IPV4_SIZE_GRT_24_BIT /* max number of TLV IPv4 prefixes in packet */ #define EIGRP_TLV_MAX_IPv4 25 /** * * extdata flag field definitions */ #define EIGRP_OPAQUE_EXT 0x01 /*!< Route is external */ #define EIGRP_OPAQUE_CD 0x02 /*!< Candidate default route */ /** * Address-Family types are taken from: * http://www.iana.org/assignments/address-family-numbers * to provide a standards based exchange of AFI information between * EIGRP routers. */ #define EIGRP_AF_IPv4 1 /*!< IPv4 (IP version 4) */ #define EIGRP_AF_IPv6 2 /*!< IPv6 (IP version 6) */ #define EIGRP_AF_IPX 11 /*!< IPX */ #define EIGRP_AF_ATALK 12 /*!< Appletalk */ #define EIGRP_SF_COMMON 16384 /*!< Cisco Service Family */ #define EIGRP_SF_IPv4 16385 /*!< Cisco IPv4 Service Family */ #define EIGRP_SF_IPv6 16386 /*!< Cisco IPv6 Service Family */ /** * Authentication types supported by EIGRP */ #define EIGRP_AUTH_TYPE_NONE 0 #define EIGRP_AUTH_TYPE_TEXT 1 #define EIGRP_AUTH_TYPE_MD5 2 #define EIGRP_AUTH_TYPE_MD5_LEN 16 #define EIGRP_AUTH_TYPE_SHA256 3 #define EIGRP_AUTH_TYPE_SHA256_LEN 32 /** * opaque flag field definitions */ #define EIGRP_OPAQUE_SRCWD 0x01 /*!< Route Source Withdraw */ #define EIGRP_OPAQUE_ACTIVE 0x04 /*!< Route is currently in active state */ #define EIGRP_OPAQUE_REPL 0x08 /*!< Route is replicated from different tableid */ /** * pak flag bit field definitions - 0 (none)-7 source priority */ #define EIGRP_PRIV_DEFAULT 0x00 /* 0 (none)-7 source priority */ #define EIGRP_PRIV_LOW 0x01 #define EIGRP_PRIV_MEDIUM 0x04 #define EIGRP_PRIV_HIGH 0x07 /* * Init bit definition. First unicast transmitted Update has this * bit set in the flags field of the fixed header. It tells the neighbor * to down-load his topology table. */ #define EIGRP_INIT_FLAG 0x01 /* * CR bit (Conditionally Received) definition in flags field on header. Any * packets with the CR-bit set can be accepted by an EIGRP speaker if and * only if a previous Hello was received with the SEQUENCE_TYPE TLV present. * * This allows multicasts to be transmitted in order and reliably at the * same time as unicasts are transmitted. */ #define EIGRP_CR_FLAG 0x02 /* * RS bit. The Restart flag is set in the hello and the init * update packets during the nsf signaling period. A nsf-aware * router looks at the RS flag to detect if a peer is restarting * and maintain the adjacency. A restarting router looks at * this flag to determine if the peer is helping out with the restart. */ #define EIGRP_RS_FLAG 0x04 /* * EOT bit. The End-of-Table flag marks the end of the start-up updates * sent to a new peer. A nsf restarting router looks at this flag to * determine if it has finished receiving the start-up updates from all * peers. A nsf-aware router waits for this flag before cleaning up * the stale routes from the restarting peer. */ #define EIGRP_EOT_FLAG 0x08 /** * EIGRP Virtual Router ID * * Define values to deal with EIGRP virtual router ids. Virtual * router IDs are stored in the upper short of the EIGRP fixed packet * header. The lower short of the packet header continues to be used * as asystem number. * * Virtual Router IDs are PDM-independent. All PDMs will use * VRID_BASE to indicate the 'base' or 'legacy' EIGRP instance. * All PDMs need to initialize their vrid to VRID_BASE for compatibility * with legacy routers. * Once IPv6 supports 'MTR Multicast', it will use the same VRID as * IPv4. No current plans to support VRIDs on IPX. :) * Initial usage of VRID is to signal usage of Multicast topology for * MTR. * * VRID_MCAST is a well known constant, other VRIDs will be determined * programmatic... * * With the addition of SAF the VRID space has been divided into two * segments 0x0000-0x7fff is for EIGRP and vNets, 0x8000-0xffff is * for saf and its associated vNets. */ #define EIGRP_VRID_MASK 0x8001 #define EIGRP_VRID_AF_BASE 0x0000 #define EIGRP_VRID_MCAST_BASE 0x0001 #define EIGRP_VRID_SF_BASE 0x8000 /* Extended Attributes for a destination */ #define EIGRP_ATTR_HDRLEN (2) #define EIGRP_ATTR_MAXDATA (512) #define EIGRP_ATTR_NOOP 0 /*!< No-Op used as offset padding */ #define EIGRP_ATTR_SCALED 1 /*!< Scaled metric values */ #define EIGRP_ATTR_TAG 2 /*!< Tag assigned by Admin for dest */ #define EIGRP_ATTR_COMM 3 /*!< Community attribute for dest */ #define EIGRP_ATTR_JITTER 4 /*!< Variation in path delay */ #define EIGRP_ATTR_QENERGY 5 /*!< Non-Active energy usage along path */ #define EIGRP_ATTR_ENERGY 6 /*!< Active energy usage along path */ /* * Begin EIGRP-BGP interoperability communities */ #define EIGRP_EXTCOMM_SOO_ASFMT 0x0003 /* Site-of-Origin, BGP AS format */ #define EIGRP_EXTCOMM_SOO_ADRFMT 0x0103 /* Site-of-Origin, BGP/EIGRP addr format */ /* * EIGRP Specific communities */ #define EIGRP_EXTCOMM_EIGRP 0x8800 /* EIGRP route information appended*/ #define EIGRP_EXTCOMM_DAD 0x8801 /* EIGRP AS + Delay */ #define EIGRP_EXTCOMM_VRHB 0x8802 /* EIGRP Vector: Reliability + Hop + BW */ #define EIGRP_EXTCOMM_SRLM 0x8803 /* EIGRP System: Reserve +Load + MTU */ #define EIGRP_EXTCOMM_SAR 0x8804 /* EIGRP System: Remote AS + Remote ID */ #define EIGRP_EXTCOMM_RPM 0x8805 /* EIGRP Remote: Protocol + Metric */ #define EIGRP_EXTCOMM_VRR 0x8806 /* EIGRP Vecmet: Rsvd + (internal) Routerid */ /* * EIGRP Filter constants */ #define EIGRP_FILTER_IN 0 #define EIGRP_FILTER_OUT 1 #define EIGRP_FILTER_MAX 2 /* * EIGRP Filter constants */ #define EIGRP_HSROLE_DEFAULT EIGRP_HSROLE_SPOKE #define EIGRP_HSROLE_HUB 0x01 #define EIGRP_HSROLE_SPOKE 0x02 #endif /* _ZEBRA_EIGRP_CONST_H_ */ frr-7.2.1/eigrpd/eigrp_dump.c0000644000000000000000000004250713610377563012775 00000000000000/* * EIGRP Dump Functions and Debugging. * Copyright (C) 2013-2014 * Authors: * Donnie Savage * Jan Janovic * Matej Perina * Peter Orsag * Peter Paluch * * This file is part of GNU Zebra. * * GNU Zebra is free software; you can redistribute it and/or modify it * under the terms of the GNU General Public License as published by the * Free Software Foundation; either version 2, or (at your option) any * later version. * * GNU Zebra 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; see the file COPYING; if not, write to the Free Software * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA */ #include #include "linklist.h" #include "thread.h" #include "prefix.h" #include "command.h" #include "stream.h" #include "log.h" #include "sockopt.h" #include "table.h" #include "keychain.h" #include "eigrpd/eigrp_structs.h" #include "eigrpd/eigrpd.h" #include "eigrpd/eigrp_interface.h" #include "eigrpd/eigrp_neighbor.h" #include "eigrpd/eigrp_packet.h" #include "eigrpd/eigrp_zebra.h" #include "eigrpd/eigrp_vty.h" #include "eigrpd/eigrp_network.h" #include "eigrpd/eigrp_dump.h" #include "eigrpd/eigrp_topology.h" /* Enable debug option variables -- valid only session. */ unsigned long term_debug_eigrp = 0; unsigned long term_debug_eigrp_nei = 0; unsigned long term_debug_eigrp_packet[11] = {0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0}; unsigned long term_debug_eigrp_zebra = 6; unsigned long term_debug_eigrp_transmit = 0; /* Configuration debug option variables. */ unsigned long conf_debug_eigrp = 0; unsigned long conf_debug_eigrp_nei = 0; unsigned long conf_debug_eigrp_packet[11] = {0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0}; unsigned long conf_debug_eigrp_zebra = 0; unsigned long conf_debug_eigrp_transmit = 0; static int config_write_debug(struct vty *vty) { int write = 0; int i; const char *type_str[] = {"update", "request", "query", "reply", "hello", "", "probe", "ack", "", "SIA query", "SIA reply", "stub", "all"}; const char *detail_str[] = { "", " send", " recv", "", " detail", " send detail", " recv detail", " detail"}; /* debug eigrp event. */ /* debug eigrp packet */ for (i = 0; i < 10; i++) { if (conf_debug_eigrp_packet[i] == 0 && term_debug_eigrp_packet[i] == 0) continue; vty_out(vty, "debug eigrp packet %s%s\n", type_str[i], detail_str[conf_debug_eigrp_packet[i]]); write = 1; } return write; } static int eigrp_neighbor_packet_queue_sum(struct eigrp_interface *ei) { struct eigrp_neighbor *nbr; struct listnode *node, *nnode; int sum; sum = 0; for (ALL_LIST_ELEMENTS(ei->nbrs, node, nnode, nbr)) { sum += nbr->retrans_queue->count; } return sum; } /* * Expects header to be in host order */ void eigrp_ip_header_dump(struct ip *iph) { /* IP Header dump. */ zlog_debug("ip_v %u", iph->ip_v); zlog_debug("ip_hl %u", iph->ip_hl); zlog_debug("ip_tos %u", iph->ip_tos); zlog_debug("ip_len %u", iph->ip_len); zlog_debug("ip_id %u", (uint32_t)iph->ip_id); zlog_debug("ip_off %u", (uint32_t)iph->ip_off); zlog_debug("ip_ttl %u", iph->ip_ttl); zlog_debug("ip_p %u", iph->ip_p); zlog_debug("ip_sum 0x%x", (uint32_t)iph->ip_sum); zlog_debug("ip_src %s", inet_ntoa(iph->ip_src)); zlog_debug("ip_dst %s", inet_ntoa(iph->ip_dst)); } /* * Expects header to be in host order */ void eigrp_header_dump(struct eigrp_header *eigrph) { /* EIGRP Header dump. */ zlog_debug("eigrp_version %u", eigrph->version); zlog_debug("eigrp_opcode %u", eigrph->opcode); zlog_debug("eigrp_checksum 0x%x", ntohs(eigrph->checksum)); zlog_debug("eigrp_flags 0x%x", ntohl(eigrph->flags)); zlog_debug("eigrp_sequence %u", ntohl(eigrph->sequence)); zlog_debug("eigrp_ack %u", ntohl(eigrph->ack)); zlog_debug("eigrp_vrid %u", ntohs(eigrph->vrid)); zlog_debug("eigrp_AS %u", ntohs(eigrph->ASNumber)); } const char *eigrp_if_name_string(struct eigrp_interface *ei) { static char buf[EIGRP_IF_STRING_MAXLEN] = ""; if (!ei) return "inactive"; snprintf(buf, EIGRP_IF_STRING_MAXLEN, "%s", ei->ifp->name); return buf; } const char *eigrp_topology_ip_string(struct eigrp_prefix_entry *tn) { static char buf[EIGRP_IF_STRING_MAXLEN] = ""; uint32_t ifaddr; ifaddr = ntohl(tn->destination->u.prefix4.s_addr); snprintf(buf, EIGRP_IF_STRING_MAXLEN, "%u.%u.%u.%u", (ifaddr >> 24) & 0xff, (ifaddr >> 16) & 0xff, (ifaddr >> 8) & 0xff, ifaddr & 0xff); return buf; } const char *eigrp_if_ip_string(struct eigrp_interface *ei) { static char buf[EIGRP_IF_STRING_MAXLEN] = ""; uint32_t ifaddr; if (!ei) return "inactive"; ifaddr = ntohl(ei->address.u.prefix4.s_addr); snprintf(buf, EIGRP_IF_STRING_MAXLEN, "%u.%u.%u.%u", (ifaddr >> 24) & 0xff, (ifaddr >> 16) & 0xff, (ifaddr >> 8) & 0xff, ifaddr & 0xff); return buf; } const char *eigrp_neigh_ip_string(struct eigrp_neighbor *nbr) { static char buf[EIGRP_IF_STRING_MAXLEN] = ""; uint32_t ifaddr; ifaddr = ntohl(nbr->src.s_addr); snprintf(buf, EIGRP_IF_STRING_MAXLEN, "%u.%u.%u.%u", (ifaddr >> 24) & 0xff, (ifaddr >> 16) & 0xff, (ifaddr >> 8) & 0xff, ifaddr & 0xff); return buf; } void show_ip_eigrp_interface_header(struct vty *vty, struct eigrp *eigrp) { vty_out(vty, "\nEIGRP interfaces for AS(%d)\n\n %-10s %-10s %-10s %-6s %-12s %-7s %-14s %-12s %-8s %-8s %-8s\n %-39s %-12s %-7s %-14s %-12s %-8s\n", eigrp->AS, "Interface", "Bandwidth", "Delay", "Peers", "Xmit Queue", "Mean", "Pacing Time", "Multicast", "Pending", "Hello", "Holdtime", "", "Un/Reliable", "SRTT", "Un/Reliable", "Flow Timer", "Routes"); } void show_ip_eigrp_interface_sub(struct vty *vty, struct eigrp *eigrp, struct eigrp_interface *ei) { vty_out(vty, "%-11s ", eigrp_if_name_string(ei)); vty_out(vty, "%-11u", ei->params.bandwidth); vty_out(vty, "%-11u", ei->params.delay); vty_out(vty, "%-7u", ei->nbrs->count); vty_out(vty, "%u %c %-10u", 0, '/', eigrp_neighbor_packet_queue_sum(ei)); vty_out(vty, "%-7u %-14u %-12u %-8u", 0, 0, 0, 0); vty_out(vty, "%-8u %-8u \n", ei->params.v_hello, ei->params.v_wait); } void show_ip_eigrp_interface_detail(struct vty *vty, struct eigrp *eigrp, struct eigrp_interface *ei) { vty_out(vty, "%-2s %s %d %-3s \n", "", "Hello interval is ", 0, " sec"); vty_out(vty, "%-2s %s %s \n", "", "Next xmit serial", ""); vty_out(vty, "%-2s %s %d %s %d %s %d %s %d \n", "", "Un/reliable mcasts: ", 0, "/", 0, "Un/reliable ucasts: ", 0, "/", 0); vty_out(vty, "%-2s %s %d %s %d %s %d \n", "", "Mcast exceptions: ", 0, " CR packets: ", 0, " ACKs suppressed: ", 0); vty_out(vty, "%-2s %s %d %s %d \n", "", "Retransmissions sent: ", 0, "Out-of-sequence rcvd: ", 0); vty_out(vty, "%-2s %s %s %s \n", "", "Authentication mode is ", "not", "set"); vty_out(vty, "%-2s %s \n", "", "Use multicast"); } void show_ip_eigrp_neighbor_header(struct vty *vty, struct eigrp *eigrp) { vty_out(vty, "\nEIGRP neighbors for AS(%d)\n\n%-3s %-17s %-20s %-6s %-8s %-6s %-5s %-5s %-5s\n %-41s %-6s %-8s %-6s %-4s %-6s %-5s \n", eigrp->AS, "H", "Address", "Interface", "Hold", "Uptime", "SRTT", "RTO", "Q", "Seq", "", "(sec)", "", "(ms)", "", "Cnt", "Num"); } void show_ip_eigrp_neighbor_sub(struct vty *vty, struct eigrp_neighbor *nbr, int detail) { vty_out(vty, "%-3u %-17s %-21s", 0, eigrp_neigh_ip_string(nbr), eigrp_if_name_string(nbr->ei)); if (nbr->t_holddown) vty_out(vty, "%-7lu", thread_timer_remain_second(nbr->t_holddown)); else vty_out(vty, "- "); vty_out(vty, "%-8u %-6u %-5u", 0, 0, EIGRP_PACKET_RETRANS_TIME); vty_out(vty, "%-7lu", nbr->retrans_queue->count); vty_out(vty, "%u\n", nbr->recv_sequence_number); if (detail) { vty_out(vty, " Version %u.%u/%u.%u", nbr->os_rel_major, nbr->os_rel_minor, nbr->tlv_rel_major, nbr->tlv_rel_minor); vty_out(vty, ", Retrans: %lu, Retries: %lu", nbr->retrans_queue->count, 0UL); vty_out(vty, ", %s\n", eigrp_nbr_state_str(nbr)); } } /* * Print standard header for show EIGRP topology output */ void show_ip_eigrp_topology_header(struct vty *vty, struct eigrp *eigrp) { vty_out(vty, "\nEIGRP Topology Table for AS(%d)/ID(%s)\n\n", eigrp->AS, inet_ntoa(eigrp->router_id)); vty_out(vty, "Codes: P - Passive, A - Active, U - Update, Q - Query, " "R - Reply\n r - reply Status, s - sia Status\n\n"); } void show_ip_eigrp_prefix_entry(struct vty *vty, struct eigrp_prefix_entry *tn) { struct list *successors = eigrp_topology_get_successor(tn); char buffer[PREFIX_STRLEN]; vty_out(vty, "%-3c", (tn->state > 0) ? 'A' : 'P'); vty_out(vty, "%s, ", prefix2str(tn->destination, buffer, PREFIX_STRLEN)); vty_out(vty, "%u successors, ", (successors) ? successors->count : 0); vty_out(vty, "FD is %u, serno: %" PRIu64 " \n", tn->fdistance, tn->serno); if (successors) list_delete(&successors); } void show_ip_eigrp_nexthop_entry(struct vty *vty, struct eigrp *eigrp, struct eigrp_nexthop_entry *te, bool *first) { if (te->reported_distance == EIGRP_MAX_METRIC) return; if (*first) { show_ip_eigrp_prefix_entry(vty, te->prefix); *first = false; } if (te->adv_router == eigrp->neighbor_self) vty_out(vty, "%-7s%s, %s\n", " ", "via Connected", eigrp_if_name_string(te->ei)); else { vty_out(vty, "%-7s%s%s (%u/%u), %s\n", " ", "via ", inet_ntoa(te->adv_router->src), te->distance, te->reported_distance, eigrp_if_name_string(te->ei)); } } DEFUN_NOSH (show_debugging_eigrp, show_debugging_eigrp_cmd, "show debugging [eigrp]", SHOW_STR DEBUG_STR EIGRP_STR) { int i; vty_out(vty, "EIGRP debugging status:\n"); /* Show debug status for events. */ if (IS_DEBUG_EIGRP(event, EVENT)) vty_out(vty, " EIGRP event debugging is on\n"); /* Show debug status for EIGRP Packets. */ for (i = 0; i < 11; i++) { if (i == 8) continue; if (IS_DEBUG_EIGRP_PACKET(i, SEND) && IS_DEBUG_EIGRP_PACKET(i, RECV)) { vty_out(vty, " EIGRP packet %s%s debugging is on\n", lookup_msg(eigrp_packet_type_str, i + 1, NULL), IS_DEBUG_EIGRP_PACKET(i, PACKET_DETAIL) ? " detail" : ""); } else { if (IS_DEBUG_EIGRP_PACKET(i, SEND)) vty_out(vty, " EIGRP packet %s send%s debugging is on\n", lookup_msg(eigrp_packet_type_str, i + 1, NULL), IS_DEBUG_EIGRP_PACKET(i, PACKET_DETAIL) ? " detail" : ""); if (IS_DEBUG_EIGRP_PACKET(i, RECV)) vty_out(vty, " EIGRP packet %s receive%s debugging is on\n", lookup_msg(eigrp_packet_type_str, i + 1, NULL), IS_DEBUG_EIGRP_PACKET(i, PACKET_DETAIL) ? " detail" : ""); } } return CMD_SUCCESS; } /* [no] debug eigrp packet (hello|dd|ls-request|ls-update|ls-ack|all) [send|recv [detail]] */ DEFUN (debug_eigrp_transmit, debug_eigrp_transmit_cmd, "debug eigrp transmit [detail]", DEBUG_STR EIGRP_STR "EIGRP transmission events\n" "packet sent\n" "packet received\n" "all packets\n" "Detailed Information\n") { int flag = 0; int idx = 2; /* send or recv. */ if (argv_find(argv, argc, "send", &idx)) flag = EIGRP_DEBUG_SEND; else if (argv_find(argv, argc, "recv", &idx)) flag = EIGRP_DEBUG_RECV; else if (argv_find(argv, argc, "all", &idx)) flag = EIGRP_DEBUG_SEND_RECV; /* detail option */ if (argv_find(argv, argc, "detail", &idx)) flag = EIGRP_DEBUG_PACKET_DETAIL; if (vty->node == CONFIG_NODE) DEBUG_TRANSMIT_ON(0, flag); else TERM_DEBUG_TRANSMIT_ON(0, flag); return CMD_SUCCESS; } DEFUN (no_debug_eigrp_transmit, no_debug_eigrp_transmit_cmd, "no debug eigrp transmit [detail]", NO_STR UNDEBUG_STR EIGRP_STR "EIGRP transmission events\n" "packet sent\n" "packet received\n" "all packets\n" "Detailed Information\n") { int flag = 0; int idx = 3; /* send or recv. */ if (argv_find(argv, argc, "send", &idx)) flag = EIGRP_DEBUG_SEND; else if (argv_find(argv, argc, "recv", &idx)) flag = EIGRP_DEBUG_RECV; else if (argv_find(argv, argc, "all", &idx)) flag = EIGRP_DEBUG_SEND_RECV; /* detail option */ if (argv_find(argv, argc, "detail", &idx)) flag = EIGRP_DEBUG_PACKET_DETAIL; if (vty->node == CONFIG_NODE) DEBUG_TRANSMIT_OFF(0, flag); else TERM_DEBUG_TRANSMIT_OFF(0, flag); return CMD_SUCCESS; } DEFUN (debug_eigrp_packets, debug_eigrp_packets_all_cmd, "debug eigrp packets [send|receive] [detail]", DEBUG_STR EIGRP_STR "EIGRP packets\n" "EIGRP SIA-Query packets\n" "EIGRP SIA-Reply packets\n" "EIGRP ack packets\n" "EIGRP hello packets\n" "EIGRP probe packets\n" "EIGRP query packets\n" "EIGRP reply packets\n" "EIGRP request packets\n" "EIGRP retransmissions\n" "EIGRP stub packets\n" "Display all EIGRP packets except Hellos\n" "EIGRP update packets\n" "Display all EIGRP packets\n" "Send Packets\n" "Receive Packets\n" "Detail Information\n") { int type = 0; int flag = 0; int i; int idx = 0; /* Check packet type. */ if (argv_find(argv, argc, "hello", &idx)) type = EIGRP_DEBUG_HELLO; if (argv_find(argv, argc, "update", &idx)) type = EIGRP_DEBUG_UPDATE; if (argv_find(argv, argc, "query", &idx)) type = EIGRP_DEBUG_QUERY; if (argv_find(argv, argc, "ack", &idx)) type = EIGRP_DEBUG_ACK; if (argv_find(argv, argc, "probe", &idx)) type = EIGRP_DEBUG_PROBE; if (argv_find(argv, argc, "stub", &idx)) type = EIGRP_DEBUG_STUB; if (argv_find(argv, argc, "reply", &idx)) type = EIGRP_DEBUG_REPLY; if (argv_find(argv, argc, "request", &idx)) type = EIGRP_DEBUG_REQUEST; if (argv_find(argv, argc, "siaquery", &idx)) type = EIGRP_DEBUG_SIAQUERY; if (argv_find(argv, argc, "siareply", &idx)) type = EIGRP_DEBUG_SIAREPLY; if (argv_find(argv, argc, "all", &idx)) type = EIGRP_DEBUG_PACKETS_ALL; /* All packet types, both send and recv. */ flag = EIGRP_DEBUG_SEND_RECV; /* send or recv. */ if (argv_find(argv, argc, "s", &idx)) flag = EIGRP_DEBUG_SEND; else if (argv_find(argv, argc, "r", &idx)) flag = EIGRP_DEBUG_RECV; /* detail. */ if (argv_find(argv, argc, "detail", &idx)) flag |= EIGRP_DEBUG_PACKET_DETAIL; for (i = 0; i < 11; i++) if (type & (0x01 << i)) { if (vty->node == CONFIG_NODE) DEBUG_PACKET_ON(i, flag); else TERM_DEBUG_PACKET_ON(i, flag); } return CMD_SUCCESS; } DEFUN (no_debug_eigrp_packets, no_debug_eigrp_packets_all_cmd, "no debug eigrp packets [send|receive] [detail]", NO_STR UNDEBUG_STR EIGRP_STR "EIGRP packets\n" "EIGRP SIA-Query packets\n" "EIGRP SIA-Reply packets\n" "EIGRP ack packets\n" "EIGRP hello packets\n" "EIGRP probe packets\n" "EIGRP query packets\n" "EIGRP reply packets\n" "EIGRP request packets\n" "EIGRP retransmissions\n" "EIGRP stub packets\n" "Display all EIGRP packets except Hellos\n" "EIGRP update packets\n" "Display all EIGRP packets\n" "Send Packets\n" "Receive Packets\n" "Detailed Information\n") { int type = 0; int flag = 0; int i; int idx = 0; /* Check packet type. */ if (argv_find(argv, argc, "hello", &idx)) type = EIGRP_DEBUG_HELLO; if (argv_find(argv, argc, "update", &idx)) type = EIGRP_DEBUG_UPDATE; if (argv_find(argv, argc, "query", &idx)) type = EIGRP_DEBUG_QUERY; if (argv_find(argv, argc, "ack", &idx)) type = EIGRP_DEBUG_ACK; if (argv_find(argv, argc, "probe", &idx)) type = EIGRP_DEBUG_PROBE; if (argv_find(argv, argc, "stub", &idx)) type = EIGRP_DEBUG_STUB; if (argv_find(argv, argc, "reply", &idx)) type = EIGRP_DEBUG_REPLY; if (argv_find(argv, argc, "request", &idx)) type = EIGRP_DEBUG_REQUEST; if (argv_find(argv, argc, "siaquery", &idx)) type = EIGRP_DEBUG_SIAQUERY; if (argv_find(argv, argc, "siareply", &idx)) type = EIGRP_DEBUG_SIAREPLY; /* Default, both send and recv. */ flag = EIGRP_DEBUG_SEND_RECV; /* send or recv. */ if (argv_find(argv, argc, "send", &idx)) flag = EIGRP_DEBUG_SEND; else if (argv_find(argv, argc, "reply", &idx)) flag = EIGRP_DEBUG_RECV; /* detail. */ if (argv_find(argv, argc, "detail", &idx)) flag |= EIGRP_DEBUG_PACKET_DETAIL; for (i = 0; i < 11; i++) if (type & (0x01 << i)) { if (vty->node == CONFIG_NODE) DEBUG_PACKET_OFF(i, flag); else TERM_DEBUG_PACKET_OFF(i, flag); } return CMD_SUCCESS; } /* Debug node. */ static struct cmd_node eigrp_debug_node = { DEBUG_NODE, "", 1 /* VTYSH */ }; /* Initialize debug commands. */ void eigrp_debug_init(void) { install_node(&eigrp_debug_node, config_write_debug); install_element(ENABLE_NODE, &show_debugging_eigrp_cmd); install_element(ENABLE_NODE, &debug_eigrp_packets_all_cmd); install_element(ENABLE_NODE, &no_debug_eigrp_packets_all_cmd); install_element(ENABLE_NODE, &debug_eigrp_transmit_cmd); install_element(ENABLE_NODE, &no_debug_eigrp_transmit_cmd); install_element(CONFIG_NODE, &show_debugging_eigrp_cmd); install_element(CONFIG_NODE, &debug_eigrp_packets_all_cmd); install_element(CONFIG_NODE, &no_debug_eigrp_packets_all_cmd); install_element(CONFIG_NODE, &debug_eigrp_transmit_cmd); install_element(CONFIG_NODE, &no_debug_eigrp_transmit_cmd); } frr-7.2.1/eigrpd/eigrp_dump.h0000644000000000000000000001640213610377563012775 00000000000000/* * EIGRP Dump Functions and Debbuging. * Copyright (C) 2013-2014 * Authors: * Donnie Savage * Jan Janovic * Matej Perina * Peter Orsag * Peter Paluch * * This file is part of GNU Zebra. * * GNU Zebra is free software; you can redistribute it and/or modify it * under the terms of the GNU General Public License as published by the * Free Software Foundation; either version 2, or (at your option) any * later version. * * GNU Zebra 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; see the file COPYING; if not, write to the Free Software * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA */ #ifndef _ZEBRA_EIGRPD_DUMP_H_ #define _ZEBRA_EIGRPD_DUMP_H_ #define EIGRP_TIME_DUMP_SIZE 16 /* general debug flags */ extern unsigned long term_debug_eigrp; #define EIGRP_DEBUG_EVENT 0x01 #define EIGRP_DEBUG_DETAIL 0x02 #define EIGRP_DEBUG_TIMERS 0x04 /* neighbor debug flags */ extern unsigned long term_debug_eigrp_nei; #define EIGRP_DEBUG_NEI 0x01 /* packet debug flags */ extern unsigned long term_debug_eigrp_packet[]; #define EIGRP_DEBUG_UPDATE 0x01 #define EIGRP_DEBUG_REQUEST 0x02 #define EIGRP_DEBUG_QUERY 0x04 #define EIGRP_DEBUG_REPLY 0x08 #define EIGRP_DEBUG_HELLO 0x10 #define EIGRP_DEBUG_PROBE 0x40 #define EIGRP_DEBUG_ACK 0x80 #define EIGRP_DEBUG_SIAQUERY 0x200 #define EIGRP_DEBUG_SIAREPLY 0x400 #define EIGRP_DEBUG_STUB 0x800 #define EIGRP_DEBUG_PACKETS_ALL 0xfff extern unsigned long term_debug_eigrp_transmit; #define EIGRP_DEBUG_SEND 0x01 #define EIGRP_DEBUG_RECV 0x02 #define EIGRP_DEBUG_SEND_RECV 0x03 #define EIGRP_DEBUG_PACKET_DETAIL 0x04 /* zebra debug flags */ extern unsigned long term_debug_eigrp_zebra; #define EIGRP_DEBUG_ZEBRA_INTERFACE 0x01 #define EIGRP_DEBUG_ZEBRA_REDISTRIBUTE 0x02 #define EIGRP_DEBUG_ZEBRA 0x03 /* Macro for setting debug option. */ #define CONF_DEBUG_NEI_ON(a, b) conf_debug_eigrp_nei[a] |= (b) #define CONF_DEBUG_NEI_OFF(a, b) conf_debug_eigrp_nei[a] &= ~(b) #define TERM_DEBUG_NEI_ON(a, b) term_debug_eigrp_nei[a] |= (b) #define TERM_DEBUG_NEI_OFF(a, b) term_debug_eigrp_nei[a] &= ~(b) #define DEBUG_NEI_ON(a, b) \ do { \ CONF_DEBUG_NEI_ON(a, b); \ TERM_DEBUG_NEI_ON(a, b); \ } while (0) #define DEBUG_NEI_OFF(a, b) \ do { \ CONF_DEBUG_NEI_OFF(a, b); \ TERM_DEBUG_NEI_OFF(a, b); \ } while (0) #define CONF_DEBUG_PACKET_ON(a, b) conf_debug_eigrp_packet[a] |= (b) #define CONF_DEBUG_PACKET_OFF(a, b) conf_debug_eigrp_packet[a] &= ~(b) #define TERM_DEBUG_PACKET_ON(a, b) term_debug_eigrp_packet[a] |= (b) #define TERM_DEBUG_PACKET_OFF(a, b) term_debug_eigrp_packet[a] &= ~(b) #define DEBUG_PACKET_ON(a, b) \ do { \ CONF_DEBUG_PACKET_ON(a, b); \ TERM_DEBUG_PACKET_ON(a, b); \ } while (0) #define DEBUG_PACKET_OFF(a, b) \ do { \ CONF_DEBUG_PACKET_OFF(a, b); \ TERM_DEBUG_PACKET_OFF(a, b); \ } while (0) #define CONF_DEBUG_TRANSMIT_ON(a, b) conf_debug_eigrp_transmit |= (b) #define CONF_DEBUG_TRANSMIT_OFF(a, b) conf_debug_eigrp_transmit &= ~(b) #define TERM_DEBUG_TRANSMIT_ON(a, b) term_debug_eigrp_transmit |= (b) #define TERM_DEBUG_TRANSMIT_OFF(a, b) term_debug_eigrp_transmit &= ~(b) #define DEBUG_TRANSMIT_ON(a, b) \ do { \ CONF_DEBUG_TRANSMIT_ON(a, b); \ TERM_DEBUG_TRANSMIT_ON(a, b); \ } while (0) #define DEBUG_TRANSMIT_OFF(a, b) \ do { \ CONF_DEBUG_TRANSMIT_OFF(a, b); \ TERM_DEBUG_TRANSMIT_OFF(a, b); \ } while (0) #define CONF_DEBUG_ON(a, b) conf_debug_eigrp_ ## a |= (EIGRP_DEBUG_ ## b) #define CONF_DEBUG_OFF(a, b) conf_debug_eigrp_ ## a &= ~(EIGRP_DEBUG_ ## b) #define TERM_DEBUG_ON(a, b) term_debug_eigrp_ ## a |= (EIGRP_DEBUG_ ## b) #define TERM_DEBUG_OFF(a, b) term_debug_eigrp_ ## a &= ~(EIGRP_DEBUG_ ## b) #define DEBUG_ON(a, b) \ do { \ CONF_DEBUG_ON(a, b); \ TERM_DEBUG_ON(a, b); \ } while (0) #define DEBUG_OFF(a, b) \ do { \ CONF_DEBUG_OFF(a, b); \ TERM_DEBUG_OFF(a, b); \ } while (0) /* Macro for checking debug option. */ #define IS_DEBUG_EIGRP_PACKET(a, b) \ (term_debug_eigrp_packet[a] & EIGRP_DEBUG_##b) #define IS_DEBUG_EIGRP_TRANSMIT(a, b) \ (term_debug_eigrp_transmit & EIGRP_DEBUG_##b) #define IS_DEBUG_EIGRP_NEI(a, b) (term_debug_eigrp_nei & EIGRP_DEBUG_##b) #define IS_DEBUG_EIGRP(a, b) (term_debug_eigrp & EIGRP_DEBUG_##b) #define IS_DEBUG_EIGRP_EVENT IS_DEBUG_EIGRP(event, EVENT) /* Prototypes. */ extern const char *eigrp_if_name_string(struct eigrp_interface *); extern const char *eigrp_if_ip_string(struct eigrp_interface *); extern const char *eigrp_neigh_ip_string(struct eigrp_neighbor *); extern const char *eigrp_topology_ip_string(struct eigrp_prefix_entry *); extern void eigrp_ip_header_dump(struct ip *); extern void eigrp_header_dump(struct eigrp_header *); extern void show_ip_eigrp_interface_header(struct vty *, struct eigrp *); extern void show_ip_eigrp_neighbor_header(struct vty *, struct eigrp *); extern void show_ip_eigrp_topology_header(struct vty *, struct eigrp *); extern void show_ip_eigrp_interface_detail(struct vty *, struct eigrp *, struct eigrp_interface *); extern void show_ip_eigrp_interface_sub(struct vty *, struct eigrp *, struct eigrp_interface *); extern void show_ip_eigrp_neighbor_sub(struct vty *, struct eigrp_neighbor *, int); extern void show_ip_eigrp_prefix_entry(struct vty *, struct eigrp_prefix_entry *); extern void show_ip_eigrp_nexthop_entry(struct vty *vty, struct eigrp *eigrp, struct eigrp_nexthop_entry *ne, bool *first); extern void eigrp_debug_init(void); #endif /* _ZEBRA_EIGRPD_DUMP_H_ */ frr-7.2.1/eigrpd/eigrp_errors.c0000644000000000000000000000304113610377563013332 00000000000000/* * EIGRP-specific error messages. * Copyright (C) 2018 Cumulus Networks, Inc. * Donald Sharp * * This program is free software; you can redistribute it and/or modify it * under the terms of the 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; see the file COPYING; if not, write to the Free Software * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA */ #include #include "lib/ferr.h" #include "eigrp_errors.h" /* clang-format off */ static struct log_ref ferr_eigrp_err[] = { { .code = EC_EIGRP_PACKET, .title = "EIGRP Packet Error", .description = "EIGRP has a packet that does not correctly decode or encode", .suggestion = "Gather log files from both sides of the neighbor relationship and open an issue" }, { .code = EC_EIGRP_CONFIG, .title = "EIGRP Configuration Error", .description = "EIGRP has detected a configuration error", .suggestion = "Correct the configuration issue, if it still persists open an Issue" }, { .code = END_FERR, } }; /* clang-format on */ void eigrp_error_init(void) { log_ref_add(ferr_eigrp_err); } frr-7.2.1/eigrpd/eigrp_errors.h0000644000000000000000000000204113610377563013336 00000000000000/* * EIGRP-specific error messages. * Copyright (C) 2018 Cumulus Networks, Inc. * Donald Sharp * * This program is free software; you can redistribute it and/or modify it * under the terms of the 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; see the file COPYING; if not, write to the Free Software * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA */ #ifndef __EIGRP_ERRORS_H__ #define __EIGRP_ERRORS_H__ #include "lib/ferr.h" enum eigrp_log_refs { EC_EIGRP_PACKET = EIGRP_FERR_START, EC_EIGRP_CONFIG, }; extern void eigrp_error_init(void); #endif frr-7.2.1/eigrpd/eigrp_filter.c0000644000000000000000000002211713610377563013310 00000000000000/* * EIGRP Filter Functions. * Copyright (C) 2013-2015 * Authors: * Donnie Savage * Jan Janovic * Matej Perina * Peter Orsag * Peter Paluch * Frantisek Gazo * Tomas Hvorkovy * Martin Kontsek * Lukas Koribsky * * * This file is part of GNU Zebra. * * GNU Zebra is free software; you can redistribute it and/or modify it * under the terms of the GNU General Public License as published by the * Free Software Foundation; either version 2, or (at your option) any * later version. * * GNU Zebra 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; see the file COPYING; if not, write to the Free Software * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA */ #include #include "if.h" #include "command.h" #include "prefix.h" #include "table.h" #include "thread.h" #include "memory.h" #include "log.h" #include "stream.h" #include "filter.h" #include "sockunion.h" #include "sockopt.h" #include "routemap.h" #include "if_rmap.h" #include "plist.h" #include "distribute.h" #include "md5.h" #include "keychain.h" #include "privs.h" #include "vrf.h" #include "eigrpd/eigrp_structs.h" #include "eigrpd/eigrpd.h" #include "eigrpd/eigrp_const.h" #include "eigrpd/eigrp_filter.h" #include "eigrpd/eigrp_packet.h" #include "eigrpd/eigrp_memory.h" /* * Distribute-list update functions. */ void eigrp_distribute_update(struct distribute_ctx *ctx, struct distribute *dist) { struct eigrp *e = eigrp_lookup(ctx->vrf->vrf_id); struct interface *ifp; struct eigrp_interface *ei = NULL; struct access_list *alist; struct prefix_list *plist; // struct route_map *routemap; /* if no interface address is present, set list to eigrp process struct */ /* Check if distribute-list was set for process or interface */ if (!dist->ifname) { /* access list IN for whole process */ if (dist->list[DISTRIBUTE_V4_IN]) { alist = access_list_lookup( AFI_IP, dist->list[DISTRIBUTE_V4_IN]); if (alist) e->list[EIGRP_FILTER_IN] = alist; else e->list[EIGRP_FILTER_IN] = NULL; } else { e->list[EIGRP_FILTER_IN] = NULL; } /* access list OUT for whole process */ if (dist->list[DISTRIBUTE_V4_OUT]) { alist = access_list_lookup( AFI_IP, dist->list[DISTRIBUTE_V4_OUT]); if (alist) e->list[EIGRP_FILTER_OUT] = alist; else e->list[EIGRP_FILTER_OUT] = NULL; } else { e->list[EIGRP_FILTER_OUT] = NULL; } /* PREFIX_LIST IN for process */ if (dist->prefix[DISTRIBUTE_V4_IN]) { plist = prefix_list_lookup( AFI_IP, dist->prefix[DISTRIBUTE_V4_IN]); if (plist) { e->prefix[EIGRP_FILTER_IN] = plist; } else e->prefix[EIGRP_FILTER_IN] = NULL; } else e->prefix[EIGRP_FILTER_IN] = NULL; /* PREFIX_LIST OUT for process */ if (dist->prefix[DISTRIBUTE_V4_OUT]) { plist = prefix_list_lookup( AFI_IP, dist->prefix[DISTRIBUTE_V4_OUT]); if (plist) { e->prefix[EIGRP_FILTER_OUT] = plist; } else e->prefix[EIGRP_FILTER_OUT] = NULL; } else e->prefix[EIGRP_FILTER_OUT] = NULL; // This is commented out, because the distribute.[ch] code // changes looked poorly written from first glance // commit was 133bdf2d // TODO: DBS #if 0 /* route-map IN for whole process */ if (dist->route[DISTRIBUTE_V4_IN]) { routemap = route_map_lookup_by_name (dist->route[DISTRIBUTE_V4_IN]); if (routemap) e->routemap[EIGRP_FILTER_IN] = routemap; else e->routemap[EIGRP_FILTER_IN] = NULL; } else { e->routemap[EIGRP_FILTER_IN] = NULL; } /* route-map OUT for whole process */ if (dist->route[DISTRIBUTE_V4_OUT]) { routemap = route_map_lookup_by_name (dist->route[DISTRIBUTE_V4_OUT]); if (routemap) e->routemap[EIGRP_FILTER_OUT] = routemap; else e->routemap[EIGRP_FILTER_OUT] = NULL; } else { e->routemap[EIGRP_FILTER_OUT] = NULL; } #endif // TODO: check Graceful restart after 10sec /* check if there is already GR scheduled */ if (e->t_distribute != NULL) { /* if is, cancel schedule */ thread_cancel(e->t_distribute); } /* schedule Graceful restart for whole process in 10sec */ e->t_distribute = NULL; thread_add_timer(master, eigrp_distribute_timer_process, e, (10), &e->t_distribute); return; } ifp = if_lookup_by_name(dist->ifname, e->vrf_id); if (ifp == NULL) return; /*struct eigrp_if_info * info = ifp->info; ei = info->eigrp_interface;*/ struct listnode *node, *nnode; struct eigrp_interface *ei2; /* Find proper interface */ for (ALL_LIST_ELEMENTS(e->eiflist, node, nnode, ei2)) { if (strcmp(ei2->ifp->name, ifp->name) == 0) { ei = ei2; break; } } assert(ei != NULL); /* Access-list for interface in */ if (dist->list[DISTRIBUTE_V4_IN]) { alist = access_list_lookup(AFI_IP, dist->list[DISTRIBUTE_V4_IN]); if (alist) { ei->list[EIGRP_FILTER_IN] = alist; } else ei->list[EIGRP_FILTER_IN] = NULL; } else { ei->list[EIGRP_FILTER_IN] = NULL; } /* Access-list for interface in */ if (dist->list[DISTRIBUTE_V4_OUT]) { alist = access_list_lookup(AFI_IP, dist->list[DISTRIBUTE_V4_OUT]); if (alist) ei->list[EIGRP_FILTER_OUT] = alist; else ei->list[EIGRP_FILTER_OUT] = NULL; } else ei->list[EIGRP_FILTER_OUT] = NULL; /* Prefix-list for interface in */ if (dist->prefix[DISTRIBUTE_V4_IN]) { plist = prefix_list_lookup(AFI_IP, dist->prefix[DISTRIBUTE_V4_IN]); if (plist) ei->prefix[EIGRP_FILTER_IN] = plist; else ei->prefix[EIGRP_FILTER_IN] = NULL; } else ei->prefix[EIGRP_FILTER_IN] = NULL; /* Prefix-list for interface out */ if (dist->prefix[DISTRIBUTE_V4_OUT]) { plist = prefix_list_lookup(AFI_IP, dist->prefix[DISTRIBUTE_V4_OUT]); if (plist) ei->prefix[EIGRP_FILTER_OUT] = plist; else ei->prefix[EIGRP_FILTER_OUT] = NULL; } else ei->prefix[EIGRP_FILTER_OUT] = NULL; #if 0 /* route-map IN for whole process */ if (dist->route[DISTRIBUTE_V4_IN]) { zlog_info("route[DISTRIBUTE_V4_IN]); if (routemap) ei->routemap[EIGRP_FILTER_IN] = routemap; else ei->routemap[EIGRP_FILTER_IN] = NULL; } else { ei->routemap[EIGRP_FILTER_IN] = NULL; } /* route-map OUT for whole process */ if (dist->route[DISTRIBUTE_V4_OUT]) { routemap = route_map_lookup_by_name (dist->route[DISTRIBUTE_V4_OUT]); if (routemap) ei->routemap[EIGRP_FILTER_OUT] = routemap; else ei->routemap[EIGRP_FILTER_OUT] = NULL; } else { ei->routemap[EIGRP_FILTER_OUT] = NULL; } #endif // TODO: check Graceful restart after 10sec /* check if there is already GR scheduled */ if (ei->t_distribute != NULL) { /* if is, cancel schedule */ thread_cancel(ei->t_distribute); } /* schedule Graceful restart for interface in 10sec */ e->t_distribute = NULL; thread_add_timer(master, eigrp_distribute_timer_interface, ei, 10, &e->t_distribute); } /* * Function called by prefix-list and access-list update */ void eigrp_distribute_update_interface(struct interface *ifp) { struct distribute *dist; struct eigrp *eigrp; eigrp = eigrp_lookup(ifp->vrf_id); if (!eigrp) return; dist = distribute_lookup(eigrp->distribute_ctx, ifp->name); if (dist) eigrp_distribute_update(eigrp->distribute_ctx, dist); } /* Update all interface's distribute list. * Function used in hook for prefix-list */ void eigrp_distribute_update_all(struct prefix_list *notused) { struct vrf *vrf; struct interface *ifp; RB_FOREACH (vrf, vrf_name_head, &vrfs_by_name) { FOR_ALL_INTERFACES (vrf, ifp) eigrp_distribute_update_interface(ifp); } } /* * Function used in hook for acces-list */ void eigrp_distribute_update_all_wrapper(struct access_list *notused) { eigrp_distribute_update_all(NULL); } /* * @fn eigrp_distribute_timer_process * * @param[in] thread current execution thread timer is associated with * * @return int always returns 0 * * @par * Called when 10sec waiting time expire and * executes Graceful restart for whole process */ int eigrp_distribute_timer_process(struct thread *thread) { struct eigrp *eigrp; eigrp = THREAD_ARG(thread); eigrp->t_distribute = NULL; /* execute GR for whole process */ eigrp_update_send_process_GR(eigrp, EIGRP_GR_FILTER, NULL); return 0; } /* * @fn eigrp_distribute_timer_interface * * @param[in] thread current execution thread timer is associated with * * @return int always returns 0 * * @par * Called when 10sec waiting time expire and * executes Graceful restart for interface */ int eigrp_distribute_timer_interface(struct thread *thread) { struct eigrp_interface *ei; ei = THREAD_ARG(thread); ei->t_distribute = NULL; /* execute GR for interface */ eigrp_update_send_interface_GR(ei, EIGRP_GR_FILTER, NULL); return 0; } frr-7.2.1/eigrpd/eigrp_filter.h0000644000000000000000000000301213610377563013306 00000000000000/* * EIGRP Filter Functions. * Copyright (C) 2013-2016 * Authors: * Donnie Savage * Jan Janovic * Matej Perina * Peter Orsag * Peter Paluch * Frantisek Gazo * Tomas Hvorkovy * Martin Kontsek * Lukas Koribsky * * * This file is part of GNU Zebra. * * GNU Zebra is free software; you can redistribute it and/or modify it * under the terms of the GNU General Public License as published by the * Free Software Foundation; either version 2, or (at your option) any * later version. * * GNU Zebra 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; see the file COPYING; if not, write to the Free Software * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA */ #ifndef EIGRPD_EIGRP_FILTER_H_ #define EIGRPD_EIGRP_FILTER_H_ extern void eigrp_distribute_update(struct distribute_ctx *ctx, struct distribute *dist); extern void eigrp_distribute_update_interface(struct interface *ifp); extern void eigrp_distribute_update_all(struct prefix_list *plist); extern void eigrp_distribute_update_all_wrapper(struct access_list *alist); extern int eigrp_distribute_timer_process(struct thread *thread); extern int eigrp_distribute_timer_interface(struct thread *thread); #endif /* EIGRPD_EIGRP_FILTER_H_ */ frr-7.2.1/eigrpd/eigrp_fsm.c0000644000000000000000000004666513610377563012626 00000000000000/* * EIGRPd Finite State Machine (DUAL). * Copyright (C) 2013-2014 * Authors: * Donnie Savage * Jan Janovic * Matej Perina * Peter Orsag * Peter Paluch * * This file is part of GNU Zebra. * * GNU Zebra is free software; you can redistribute it and/or modify it * under the terms of the GNU General Public License as published by the * Free Software Foundation; either version 2, or (at your option) any * later version. * * GNU Zebra 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; see the file COPYING; if not, write to the Free Software * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA * * This file contains functions for executing logic of finite state machine * * +------------ + * | (7) | * | v * +=====================================+ * | | * | Passive | * | | * +=====================================+ * ^ | ^ ^ ^ | * (3)| | (1)| | (1)| | * | (0)| | (3)| | (2)| * | | | | | +---------------+ * | | | | | \ * +--------+ | | | +-----------------+ \ * / / / | \ \ * / / / +----+ \ \ * | | | | | | * | v | | | v * +===========+ (6) +===========+ +===========+ (6) +===========+ * | |------->| | (5) | |-------->| | * | | (4) | |------>| | (4) | | * | ACTIVE 0 |<-------| ACTIVE 1 | | ACTIVE 2 |<--------| ACTIVE 3 * | * +--| | +--| | +--| | +--| | * | +===========+ | +===========+ | +===========+ | * +===========+ * | ^ |(5) | ^ | ^ ^ | ^ * | | +---------|------|------------|----+ | | | * +-------+ +------+ +---------+ +---------+ * (7) (7) (7) (7) * * 0- input event other than query from successor, FC not satisfied * 1- last reply, FD is reset * 2- query from successor, FC not satisfied * 3- last reply, FC satisfied with current value of FDij * 4- distance increase while in active state * 5- query from successor while in active state * 6- last reply, FC not satisfied with current value of FDij * 7- state not changed, usually by receiving not last reply */ #include #include #include "prefix.h" #include "table.h" #include "memory.h" #include "log.h" #include "linklist.h" #include "vty.h" #include "eigrpd/eigrp_structs.h" #include "eigrpd/eigrpd.h" #include "eigrpd/eigrp_interface.h" #include "eigrpd/eigrp_neighbor.h" #include "eigrpd/eigrp_packet.h" #include "eigrpd/eigrp_zebra.h" #include "eigrpd/eigrp_vty.h" #include "eigrpd/eigrp_network.h" #include "eigrpd/eigrp_dump.h" #include "eigrpd/eigrp_topology.h" #include "eigrpd/eigrp_fsm.h" /* * Prototypes */ int eigrp_fsm_event_keep_state(struct eigrp_fsm_action_message *); int eigrp_fsm_event_nq_fcn(struct eigrp_fsm_action_message *); int eigrp_fsm_event_q_fcn(struct eigrp_fsm_action_message *); int eigrp_fsm_event_lr(struct eigrp_fsm_action_message *); int eigrp_fsm_event_dinc(struct eigrp_fsm_action_message *); int eigrp_fsm_event_lr_fcs(struct eigrp_fsm_action_message *); int eigrp_fsm_event_lr_fcn(struct eigrp_fsm_action_message *); int eigrp_fsm_event_qact(struct eigrp_fsm_action_message *); //--------------------------------------------------------------------- /* * NSM - field of fields of struct containing one function each. * Which function is used depends on actual state of FSM and occurred * event(arrow in diagram). Usage: * NSM[actual/starting state][occurred event].func * Functions are should be executed within separate thread. */ struct { int (*func)(struct eigrp_fsm_action_message *); } NSM[EIGRP_FSM_STATE_MAX][EIGRP_FSM_EVENT_MAX] = { { // PASSIVE STATE {eigrp_fsm_event_nq_fcn}, /* Event 0 */ {eigrp_fsm_event_keep_state}, /* Event 1 */ {eigrp_fsm_event_q_fcn}, /* Event 2 */ {eigrp_fsm_event_keep_state}, /* Event 3 */ {eigrp_fsm_event_keep_state}, /* Event 4 */ {eigrp_fsm_event_keep_state}, /* Event 5 */ {eigrp_fsm_event_keep_state}, /* Event 6 */ {eigrp_fsm_event_keep_state}, /* Event 7 */ }, { // Active 0 state {eigrp_fsm_event_keep_state}, /* Event 0 */ {eigrp_fsm_event_keep_state}, /* Event 1 */ {eigrp_fsm_event_keep_state}, /* Event 2 */ {eigrp_fsm_event_lr_fcs}, /* Event 3 */ {eigrp_fsm_event_keep_state}, /* Event 4 */ {eigrp_fsm_event_qact}, /* Event 5 */ {eigrp_fsm_event_lr_fcn}, /* Event 6 */ {eigrp_fsm_event_keep_state}, /* Event 7 */ }, { // Active 1 state {eigrp_fsm_event_keep_state}, /* Event 0 */ {eigrp_fsm_event_lr}, /* Event 1 */ {eigrp_fsm_event_keep_state}, /* Event 2 */ {eigrp_fsm_event_keep_state}, /* Event 3 */ {eigrp_fsm_event_dinc}, /* Event 4 */ {eigrp_fsm_event_qact}, /* Event 5 */ {eigrp_fsm_event_keep_state}, /* Event 6 */ {eigrp_fsm_event_keep_state}, /* Event 7 */ }, { // Active 2 state {eigrp_fsm_event_keep_state}, /* Event 0 */ {eigrp_fsm_event_keep_state}, /* Event 1 */ {eigrp_fsm_event_keep_state}, /* Event 2 */ {eigrp_fsm_event_lr_fcs}, /* Event 3 */ {eigrp_fsm_event_keep_state}, /* Event 4 */ {eigrp_fsm_event_keep_state}, /* Event 5 */ {eigrp_fsm_event_lr_fcn}, /* Event 6 */ {eigrp_fsm_event_keep_state}, /* Event 7 */ }, { // Active 3 state {eigrp_fsm_event_keep_state}, /* Event 0 */ {eigrp_fsm_event_lr}, /* Event 1 */ {eigrp_fsm_event_keep_state}, /* Event 2 */ {eigrp_fsm_event_keep_state}, /* Event 3 */ {eigrp_fsm_event_dinc}, /* Event 4 */ {eigrp_fsm_event_keep_state}, /* Event 5 */ {eigrp_fsm_event_keep_state}, /* Event 6 */ {eigrp_fsm_event_keep_state}, /* Event 7 */ }, }; static const char *packet_type2str(uint8_t packet_type) { if (packet_type == EIGRP_OPC_UPDATE) return "Update"; if (packet_type == EIGRP_OPC_REQUEST) return "Request"; if (packet_type == EIGRP_OPC_QUERY) return "Query"; if (packet_type == EIGRP_OPC_REPLY) return "Reply"; if (packet_type == EIGRP_OPC_HELLO) return "Hello"; if (packet_type == EIGRP_OPC_IPXSAP) return "IPXSAP"; if (packet_type == EIGRP_OPC_ACK) return "Ack"; if (packet_type == EIGRP_OPC_SIAQUERY) return "SIA Query"; if (packet_type == EIGRP_OPC_SIAREPLY) return "SIA Reply"; return "Unknown"; } static const char *prefix_state2str(enum eigrp_fsm_states state) { switch (state) { case EIGRP_FSM_STATE_PASSIVE: return "Passive"; case EIGRP_FSM_STATE_ACTIVE_0: return "Active oij0"; case EIGRP_FSM_STATE_ACTIVE_1: return "Active oij1"; case EIGRP_FSM_STATE_ACTIVE_2: return "Active oij2"; case EIGRP_FSM_STATE_ACTIVE_3: return "Active oij3"; } return "Unknown"; } static const char *fsm_state2str(enum eigrp_fsm_events event) { switch (event) { case EIGRP_FSM_KEEP_STATE: return "Keep State Event"; case EIGRP_FSM_EVENT_NQ_FCN: return "Non Query Event Feasability not satisfied"; case EIGRP_FSM_EVENT_LR: return "Last Reply Event"; case EIGRP_FSM_EVENT_Q_FCN: return "Query Event Feasability not satisfied"; case EIGRP_FSM_EVENT_LR_FCS: return "Last Reply Event Feasability satisfied"; case EIGRP_FSM_EVENT_DINC: return "Distance Increase Event"; case EIGRP_FSM_EVENT_QACT: return "Query from Successor while in active state"; case EIGRP_FSM_EVENT_LR_FCN: return "Last Reply Event, Feasibility not satisfied"; } return "Unknown"; } static const char *change2str(enum metric_change change) { switch (change) { case METRIC_DECREASE: return "Decrease"; case METRIC_SAME: return "Same"; case METRIC_INCREASE: return "Increase"; } return "Unknown"; } /* * Main function in which are make decisions which event occurred. * msg - argument of type struct eigrp_fsm_action_message contain * details about what happen * * Return number of occurred event (arrow in diagram). * */ static enum eigrp_fsm_events eigrp_get_fsm_event(struct eigrp_fsm_action_message *msg) { // Loading base information from message // struct eigrp *eigrp = msg->eigrp; struct eigrp_prefix_entry *prefix = msg->prefix; struct eigrp_nexthop_entry *entry = msg->entry; uint8_t actual_state = prefix->state; enum metric_change change; if (entry == NULL) { entry = eigrp_nexthop_entry_new(); entry->adv_router = msg->adv_router; entry->ei = msg->adv_router->ei; entry->prefix = prefix; msg->entry = entry; } /* * Calculate resultant metrics and insert to correct position * in entries list */ change = eigrp_topology_update_distance(msg); /* Store for display later */ msg->change = change; switch (actual_state) { case EIGRP_FSM_STATE_PASSIVE: { struct eigrp_nexthop_entry *head = listnode_head(prefix->entries); if (head->reported_distance < prefix->fdistance) { return EIGRP_FSM_KEEP_STATE; } /* * if best entry doesn't satisfy feasibility condition it means * move to active state * dependently if it was query from successor */ if (msg->packet_type == EIGRP_OPC_QUERY) { return EIGRP_FSM_EVENT_Q_FCN; } else { return EIGRP_FSM_EVENT_NQ_FCN; } break; } case EIGRP_FSM_STATE_ACTIVE_0: { if (msg->packet_type == EIGRP_OPC_REPLY) { struct eigrp_nexthop_entry *head = listnode_head(prefix->entries); listnode_delete(prefix->rij, entry->adv_router); if (prefix->rij->count) return EIGRP_FSM_KEEP_STATE; zlog_info("All reply received"); if (head->reported_distance < prefix->fdistance) { return EIGRP_FSM_EVENT_LR_FCS; } return EIGRP_FSM_EVENT_LR_FCN; } else if (msg->packet_type == EIGRP_OPC_QUERY && (entry->flags & EIGRP_NEXTHOP_ENTRY_SUCCESSOR_FLAG)) { return EIGRP_FSM_EVENT_QACT; } return EIGRP_FSM_KEEP_STATE; break; } case EIGRP_FSM_STATE_ACTIVE_1: { if (msg->packet_type == EIGRP_OPC_QUERY && (entry->flags & EIGRP_NEXTHOP_ENTRY_SUCCESSOR_FLAG)) { return EIGRP_FSM_EVENT_QACT; } else if (msg->packet_type == EIGRP_OPC_REPLY) { listnode_delete(prefix->rij, entry->adv_router); if (change == METRIC_INCREASE && (entry->flags & EIGRP_NEXTHOP_ENTRY_SUCCESSOR_FLAG)) { return EIGRP_FSM_EVENT_DINC; } else if (prefix->rij->count) { return EIGRP_FSM_KEEP_STATE; } else { zlog_info("All reply received"); return EIGRP_FSM_EVENT_LR; } } else if (msg->packet_type == EIGRP_OPC_UPDATE && change == METRIC_INCREASE && (entry->flags & EIGRP_NEXTHOP_ENTRY_SUCCESSOR_FLAG)) { return EIGRP_FSM_EVENT_DINC; } return EIGRP_FSM_KEEP_STATE; break; } case EIGRP_FSM_STATE_ACTIVE_2: { if (msg->packet_type == EIGRP_OPC_REPLY) { struct eigrp_nexthop_entry *head = listnode_head(prefix->entries); listnode_delete(prefix->rij, entry->adv_router); if (prefix->rij->count) { return EIGRP_FSM_KEEP_STATE; } else { zlog_info("All reply received"); if (head->reported_distance < prefix->fdistance) { return EIGRP_FSM_EVENT_LR_FCS; } return EIGRP_FSM_EVENT_LR_FCN; } } return EIGRP_FSM_KEEP_STATE; break; } case EIGRP_FSM_STATE_ACTIVE_3: { if (msg->packet_type == EIGRP_OPC_REPLY) { listnode_delete(prefix->rij, entry->adv_router); if (change == METRIC_INCREASE && (entry->flags & EIGRP_NEXTHOP_ENTRY_SUCCESSOR_FLAG)) { return EIGRP_FSM_EVENT_DINC; } else if (prefix->rij->count) { return EIGRP_FSM_KEEP_STATE; } else { zlog_info("All reply received"); return EIGRP_FSM_EVENT_LR; } } else if (msg->packet_type == EIGRP_OPC_UPDATE && change == METRIC_INCREASE && (entry->flags & EIGRP_NEXTHOP_ENTRY_SUCCESSOR_FLAG)) { return EIGRP_FSM_EVENT_DINC; } return EIGRP_FSM_KEEP_STATE; break; } } return EIGRP_FSM_KEEP_STATE; } /* * Function made to execute in separate thread. * Load argument from thread and execute proper NSM function */ int eigrp_fsm_event(struct eigrp_fsm_action_message *msg) { enum eigrp_fsm_events event = eigrp_get_fsm_event(msg); zlog_info( "EIGRP AS: %d State: %s Event: %s Network: %s Packet Type: %s Reply RIJ Count: %d change: %s", msg->eigrp->AS, prefix_state2str(msg->prefix->state), fsm_state2str(event), eigrp_topology_ip_string(msg->prefix), packet_type2str(msg->packet_type), msg->prefix->rij->count, change2str(msg->change)); (*(NSM[msg->prefix->state][event].func))(msg); return 1; } /* * Function of event 0. * */ int eigrp_fsm_event_nq_fcn(struct eigrp_fsm_action_message *msg) { struct eigrp *eigrp = msg->eigrp; struct eigrp_prefix_entry *prefix = msg->prefix; struct list *successors = eigrp_topology_get_successor(prefix); struct eigrp_nexthop_entry *ne; assert(successors); // If this is NULL we have shit the bed, fun huh? ne = listnode_head(successors); prefix->state = EIGRP_FSM_STATE_ACTIVE_1; prefix->rdistance = prefix->distance = prefix->fdistance = ne->distance; prefix->reported_metric = ne->total_metric; if (eigrp_nbr_count_get(eigrp)) { prefix->req_action |= EIGRP_FSM_NEED_QUERY; listnode_add(eigrp->topology_changes_internalIPV4, prefix); } else { eigrp_fsm_event_lr(msg); // in the case that there are no more // neighbors left } list_delete(&successors); return 1; } int eigrp_fsm_event_q_fcn(struct eigrp_fsm_action_message *msg) { struct eigrp *eigrp = msg->eigrp; struct eigrp_prefix_entry *prefix = msg->prefix; struct list *successors = eigrp_topology_get_successor(prefix); struct eigrp_nexthop_entry *ne; assert(successors); // If this is NULL somebody poked us in the eye. ne = listnode_head(successors); prefix->state = EIGRP_FSM_STATE_ACTIVE_3; prefix->rdistance = prefix->distance = prefix->fdistance = ne->distance; prefix->reported_metric = ne->total_metric; if (eigrp_nbr_count_get(eigrp)) { prefix->req_action |= EIGRP_FSM_NEED_QUERY; listnode_add(eigrp->topology_changes_internalIPV4, prefix); } else { eigrp_fsm_event_lr(msg); // in the case that there are no more // neighbors left } list_delete(&successors); return 1; } int eigrp_fsm_event_keep_state(struct eigrp_fsm_action_message *msg) { struct eigrp *eigrp = msg->eigrp; struct eigrp_prefix_entry *prefix = msg->prefix; struct eigrp_nexthop_entry *ne = listnode_head(prefix->entries); if (prefix->state == EIGRP_FSM_STATE_PASSIVE) { if (!eigrp_metrics_is_same(prefix->reported_metric, ne->total_metric)) { prefix->rdistance = prefix->fdistance = prefix->distance = ne->distance; prefix->reported_metric = ne->total_metric; if (msg->packet_type == EIGRP_OPC_QUERY) eigrp_send_reply(msg->adv_router, prefix); prefix->req_action |= EIGRP_FSM_NEED_UPDATE; listnode_add(eigrp->topology_changes_internalIPV4, prefix); } eigrp_topology_update_node_flags(eigrp, prefix); eigrp_update_routing_table(eigrp, prefix); } if (msg->packet_type == EIGRP_OPC_QUERY) eigrp_send_reply(msg->adv_router, prefix); return 1; } int eigrp_fsm_event_lr(struct eigrp_fsm_action_message *msg) { struct eigrp *eigrp = msg->eigrp; struct eigrp_prefix_entry *prefix = msg->prefix; struct eigrp_nexthop_entry *ne = listnode_head(prefix->entries); prefix->fdistance = prefix->distance = prefix->rdistance = ne->distance; prefix->reported_metric = ne->total_metric; if (prefix->state == EIGRP_FSM_STATE_ACTIVE_3) { struct list *successors = eigrp_topology_get_successor(prefix); assert(successors); // It's like Napolean and Waterloo ne = listnode_head(successors); eigrp_send_reply(ne->adv_router, prefix); list_delete(&successors); } prefix->state = EIGRP_FSM_STATE_PASSIVE; prefix->req_action |= EIGRP_FSM_NEED_UPDATE; listnode_add(eigrp->topology_changes_internalIPV4, prefix); eigrp_topology_update_node_flags(eigrp, prefix); eigrp_update_routing_table(eigrp, prefix); eigrp_update_topology_table_prefix(eigrp, eigrp->topology_table, prefix); return 1; } int eigrp_fsm_event_dinc(struct eigrp_fsm_action_message *msg) { struct list *successors = eigrp_topology_get_successor(msg->prefix); struct eigrp_nexthop_entry *ne; assert(successors); // Trump and his big hands ne = listnode_head(successors); msg->prefix->state = msg->prefix->state == EIGRP_FSM_STATE_ACTIVE_1 ? EIGRP_FSM_STATE_ACTIVE_0 : EIGRP_FSM_STATE_ACTIVE_2; msg->prefix->distance = ne->distance; if (!msg->prefix->rij->count) (*(NSM[msg->prefix->state][eigrp_get_fsm_event(msg)].func))( msg); list_delete(&successors); return 1; } int eigrp_fsm_event_lr_fcs(struct eigrp_fsm_action_message *msg) { struct eigrp *eigrp = msg->eigrp; struct eigrp_prefix_entry *prefix = msg->prefix; struct eigrp_nexthop_entry *ne = listnode_head(prefix->entries); prefix->state = EIGRP_FSM_STATE_PASSIVE; prefix->distance = prefix->rdistance = ne->distance; prefix->reported_metric = ne->total_metric; prefix->fdistance = prefix->fdistance > prefix->distance ? prefix->distance : prefix->fdistance; if (prefix->state == EIGRP_FSM_STATE_ACTIVE_2) { struct list *successors = eigrp_topology_get_successor(prefix); assert(successors); // Having a spoon and all you need is a // knife ne = listnode_head(successors); eigrp_send_reply(ne->adv_router, prefix); list_delete(&successors); } prefix->req_action |= EIGRP_FSM_NEED_UPDATE; listnode_add(eigrp->topology_changes_internalIPV4, prefix); eigrp_topology_update_node_flags(eigrp, prefix); eigrp_update_routing_table(eigrp, prefix); eigrp_update_topology_table_prefix(eigrp, eigrp->topology_table, prefix); return 1; } int eigrp_fsm_event_lr_fcn(struct eigrp_fsm_action_message *msg) { struct eigrp *eigrp = msg->eigrp; struct eigrp_prefix_entry *prefix = msg->prefix; struct eigrp_nexthop_entry *best_successor; struct list *successors = eigrp_topology_get_successor(prefix); assert(successors); // Routing without a stack prefix->state = prefix->state == EIGRP_FSM_STATE_ACTIVE_0 ? EIGRP_FSM_STATE_ACTIVE_1 : EIGRP_FSM_STATE_ACTIVE_3; best_successor = listnode_head(successors); prefix->rdistance = prefix->distance = best_successor->distance; prefix->reported_metric = best_successor->total_metric; if (eigrp_nbr_count_get(eigrp)) { prefix->req_action |= EIGRP_FSM_NEED_QUERY; listnode_add(eigrp->topology_changes_internalIPV4, prefix); } else { eigrp_fsm_event_lr(msg); // in the case that there are no more // neighbors left } list_delete(&successors); return 1; } int eigrp_fsm_event_qact(struct eigrp_fsm_action_message *msg) { struct list *successors = eigrp_topology_get_successor(msg->prefix); struct eigrp_nexthop_entry *ne; assert(successors); // Cats and no Dogs ne = listnode_head(successors); msg->prefix->state = EIGRP_FSM_STATE_ACTIVE_2; msg->prefix->distance = ne->distance; list_delete(&successors); return 1; } frr-7.2.1/eigrpd/eigrp_fsm.h0000644000000000000000000000207413610377563012615 00000000000000/* * EIGRP Finite State Machine (DUAL). * Copyright (C) 2013-2014 * Authors: * Donnie Savage * Jan Janovic * Matej Perina * Peter Orsag * Peter Paluch * * This file is part of GNU Zebra. * * GNU Zebra is free software; you can redistribute it and/or modify it * under the terms of the GNU General Public License as published by the * Free Software Foundation; either version 2, or (at your option) any * later version. * * GNU Zebra 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; see the file COPYING; if not, write to the Free Software * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA */ #ifndef _ZEBRA_EIGRP_FSM_H #define _ZEBRA_EIGRP_FSM_H extern int eigrp_fsm_event(struct eigrp_fsm_action_message *msg); #endif /* _ZEBRA_EIGRP_DUAL_H */ frr-7.2.1/eigrpd/eigrp_hello.c0000644000000000000000000005075713610377563013141 00000000000000/* * EIGRP Sending and Receiving EIGRP Hello Packets. * Copyright (C) 2013-2016 * Authors: * Donnie Savage * Jan Janovic * Matej Perina * Peter Orsag * Peter Paluch * Frantisek Gazo * Tomas Hvorkovy * Martin Kontsek * Lukas Koribsky * * This file is part of GNU Zebra. * * GNU Zebra is free software; you can redistribute it and/or modify it * under the terms of the GNU General Public License as published by the * Free Software Foundation; either version 2, or (at your option) any * later version. * * GNU Zebra 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; see the file COPYING; if not, write to the Free Software * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA */ #include #include "thread.h" #include "memory.h" #include "linklist.h" #include "prefix.h" #include "if.h" #include "table.h" #include "sockunion.h" #include "stream.h" #include "log.h" #include "sockopt.h" #include "checksum.h" #include "vty.h" #include "md5.h" #include "eigrpd/eigrp_structs.h" #include "eigrpd/eigrpd.h" #include "eigrpd/eigrp_interface.h" #include "eigrpd/eigrp_neighbor.h" #include "eigrpd/eigrp_packet.h" #include "eigrpd/eigrp_zebra.h" #include "eigrpd/eigrp_vty.h" #include "eigrpd/eigrp_dump.h" #include "eigrpd/eigrp_macros.h" #include "eigrpd/eigrp_errors.h" /* Packet Type String. */ static const struct message eigrp_general_tlv_type_str[] = { {EIGRP_TLV_PARAMETER, "PARAMETER"}, {EIGRP_TLV_AUTH, "AUTH"}, {EIGRP_TLV_SEQ, "SEQ"}, {EIGRP_TLV_SW_VERSION, "SW_VERSION"}, {EIGRP_TLV_NEXT_MCAST_SEQ, "NEXT_MCAST_SEQ"}, {EIGRP_TLV_PEER_TERMINATION, "PEER_TERMINATION"}, {EIGRP_TLV_PEER_MTRLIST, "PEER_MTRLIST"}, {EIGRP_TLV_PEER_TIDLIST, "PEER_TIDLIST"}, {0}}; /* * @fn eigrp_hello_timer * * @param[in] thread current execution thread timer is associated with * * @return int always returns 0 * * @par * Called once per "hello" time interval, default 5 seconds * Sends hello packet via multicast for all interfaces eigrp * is configured for */ int eigrp_hello_timer(struct thread *thread) { struct eigrp_interface *ei; ei = THREAD_ARG(thread); ei->t_hello = NULL; if (IS_DEBUG_EIGRP(0, TIMERS)) zlog_debug("Start Hello Timer (%s) Expire [%u]", IF_NAME(ei), ei->params.v_hello); /* Sending hello packet. */ eigrp_hello_send(ei, EIGRP_HELLO_NORMAL, NULL); /* Hello timer set. */ ei->t_hello = NULL; thread_add_timer(master, eigrp_hello_timer, ei, ei->params.v_hello, &ei->t_hello); return 0; } /** * @fn eigrp_hello_parameter_decode * * @param[in] nbr neighbor the ACK should be sent to * @param[in] param pointer packet TLV is stored to * * @return uint16_t number of bytes added to packet stream * * @par * Encode Parameter TLV, used to convey metric weights and the hold time. * * @usage * Note the addition of K6 for the new extended metrics, and does not apply to * older TLV packet formats. */ static struct eigrp_neighbor * eigrp_hello_parameter_decode(struct eigrp_neighbor *nbr, struct eigrp_tlv_hdr_type *tlv) { struct eigrp *eigrp = nbr->ei->eigrp; struct TLV_Parameter_Type *param = (struct TLV_Parameter_Type *)tlv; /* copy over the values passed in by the neighbor */ nbr->K1 = param->K1; nbr->K2 = param->K2; nbr->K3 = param->K3; nbr->K4 = param->K4; nbr->K5 = param->K5; nbr->K6 = param->K6; nbr->v_holddown = ntohs(param->hold_time); /* * Check K1-K5 have the correct values to be able to become neighbors * K6 does not have to match */ if ((eigrp->k_values[0] == nbr->K1) && (eigrp->k_values[1] == nbr->K2) && (eigrp->k_values[2] == nbr->K3) && (eigrp->k_values[3] == nbr->K4) && (eigrp->k_values[4] == nbr->K5)) { if (eigrp_nbr_state_get(nbr) == EIGRP_NEIGHBOR_DOWN) { zlog_info("Neighbor %s (%s) is pending: new adjacency", inet_ntoa(nbr->src), ifindex2ifname(nbr->ei->ifp->ifindex, eigrp->vrf_id)); /* Expedited hello sent */ eigrp_hello_send(nbr->ei, EIGRP_HELLO_NORMAL, NULL); // if(ntohl(nbr->ei->address->u.prefix4.s_addr) > // ntohl(nbr->src.s_addr)) eigrp_update_send_init(nbr); eigrp_nbr_state_set(nbr, EIGRP_NEIGHBOR_PENDING); } } else { if (eigrp_nbr_state_get(nbr) != EIGRP_NEIGHBOR_DOWN) { if ((param->K1 & param->K2 & param->K3 & param->K4 & param->K5) == 255) { zlog_info( "Neighbor %s (%s) is down: Interface PEER-TERMINATION received", inet_ntoa(nbr->src), ifindex2ifname(nbr->ei->ifp->ifindex, eigrp->vrf_id)); eigrp_nbr_delete(nbr); return NULL; } else { zlog_info( "Neighbor %s (%s) going down: Kvalue mismatch", inet_ntoa(nbr->src), ifindex2ifname(nbr->ei->ifp->ifindex, eigrp->vrf_id)); eigrp_nbr_state_set(nbr, EIGRP_NEIGHBOR_DOWN); } } } return nbr; } static uint8_t eigrp_hello_authentication_decode(struct stream *s, struct eigrp_tlv_hdr_type *tlv_header, struct eigrp_neighbor *nbr) { struct TLV_MD5_Authentication_Type *md5; md5 = (struct TLV_MD5_Authentication_Type *)tlv_header; if (md5->auth_type == EIGRP_AUTH_TYPE_MD5) return eigrp_check_md5_digest(s, md5, nbr, EIGRP_AUTH_BASIC_HELLO_FLAG); else if (md5->auth_type == EIGRP_AUTH_TYPE_SHA256) return eigrp_check_sha256_digest( s, (struct TLV_SHA256_Authentication_Type *)tlv_header, nbr, EIGRP_AUTH_BASIC_HELLO_FLAG); return 0; } /** * @fn eigrp_sw_version_decode * * @param[in] nbr neighbor the ACK shoudl be sent to * @param[in] param pointer to TLV software version information * * @return void * * @par * Read the software version in the specified location. * This consists of two bytes of OS version, and two bytes of EIGRP * revision number. */ static void eigrp_sw_version_decode(struct eigrp_neighbor *nbr, struct eigrp_tlv_hdr_type *tlv) { struct TLV_Software_Type *version = (struct TLV_Software_Type *)tlv; nbr->os_rel_major = version->vender_major; nbr->os_rel_minor = version->vender_minor; nbr->tlv_rel_major = version->eigrp_major; nbr->tlv_rel_minor = version->eigrp_minor; return; } /** * @fn eigrp_peer_termination_decode * * @param[in] nbr neighbor the ACK shoudl be sent to * @param[in] tlv pointer to TLV software version information * * @return void * * @par * Read the address in the TLV and match to out address. If * a match is found, move the sending neighbor to the down state. If * out address is not in the TLV, then ignore the peer termination */ static void eigrp_peer_termination_decode(struct eigrp_neighbor *nbr, struct eigrp_tlv_hdr_type *tlv) { struct eigrp *eigrp = nbr->ei->eigrp; struct TLV_Peer_Termination_type *param = (struct TLV_Peer_Termination_type *)tlv; uint32_t my_ip = nbr->ei->address.u.prefix4.s_addr; uint32_t received_ip = param->neighbor_ip; if (my_ip == received_ip) { zlog_info("Neighbor %s (%s) is down: Peer Termination received", inet_ntoa(nbr->src), ifindex2ifname(nbr->ei->ifp->ifindex, eigrp->vrf_id)); /* set neighbor to DOWN */ nbr->state = EIGRP_NEIGHBOR_DOWN; /* delete neighbor */ eigrp_nbr_delete(nbr); } } /** * @fn eigrp_peer_termination_encode * * @param[in,out] s packet stream TLV is stored to * @param[in] nbr_addr pointer to neighbor address for Peer * Termination TLV * * @return uint16_t number of bytes added to packet stream * * @par * Function used to encode Peer Termination TLV to Hello packet. */ static uint16_t eigrp_peer_termination_encode(struct stream *s, struct in_addr *nbr_addr) { uint16_t length = EIGRP_TLV_PEER_TERMINATION_LEN; /* fill in type and length */ stream_putw(s, EIGRP_TLV_PEER_TERMINATION); stream_putw(s, length); /* fill in unknown field 0x04 */ stream_putc(s, 0x04); /* finally neighbor IP address */ stream_put_ipv4(s, nbr_addr->s_addr); return (length); } /* * @fn eigrp_hello_receive * * @param[in] eigrp eigrp routing process * @param[in] iph pointer to ip header * @param[in] eigrph pointer to eigrp header * @param[in] s input ip stream * @param[in] ei eigrp interface packet arrived on * @param[in] size size of eigrp packet * * @return void * * @par * This is the main worker function for processing hello packets. It * will validate the peer associated with the src ip address of the ip * header, and then decode each of the general TLVs which the packet * may contain. * * @usage * Not all TLVs are current decoder. This is a work in progress.. */ void eigrp_hello_receive(struct eigrp *eigrp, struct ip *iph, struct eigrp_header *eigrph, struct stream *s, struct eigrp_interface *ei, int size) { struct eigrp_tlv_hdr_type *tlv_header; struct eigrp_neighbor *nbr; uint16_t type; uint16_t length; /* get neighbor struct */ nbr = eigrp_nbr_get(ei, eigrph, iph); /* neighbor must be valid, eigrp_nbr_get creates if none existed */ assert(nbr); if (IS_DEBUG_EIGRP_PACKET(eigrph->opcode - 1, RECV)) zlog_debug("Processing Hello size[%u] int(%s) nbr(%s)", size, ifindex2ifname(nbr->ei->ifp->ifindex, eigrp->vrf_id), inet_ntoa(nbr->src)); size -= EIGRP_HEADER_LEN; if (size < 0) return; tlv_header = (struct eigrp_tlv_hdr_type *)eigrph->tlv; do { type = ntohs(tlv_header->type); length = ntohs(tlv_header->length); if ((length > 0) && (length <= size)) { if (IS_DEBUG_EIGRP_PACKET(0, RECV)) zlog_debug( " General TLV(%s)", lookup_msg(eigrp_general_tlv_type_str, type, NULL)); // determine what General TLV is being processed switch (type) { case EIGRP_TLV_PARAMETER: nbr = eigrp_hello_parameter_decode(nbr, tlv_header); if (!nbr) return; break; case EIGRP_TLV_AUTH: { if (eigrp_hello_authentication_decode( s, tlv_header, nbr) == 0) return; else break; break; } case EIGRP_TLV_SEQ: break; case EIGRP_TLV_SW_VERSION: eigrp_sw_version_decode(nbr, tlv_header); break; case EIGRP_TLV_NEXT_MCAST_SEQ: break; case EIGRP_TLV_PEER_TERMINATION: eigrp_peer_termination_decode(nbr, tlv_header); return; break; case EIGRP_TLV_PEER_MTRLIST: case EIGRP_TLV_PEER_TIDLIST: break; default: break; } } tlv_header = (struct eigrp_tlv_hdr_type *)(((char *)tlv_header) + length); size -= length; } while (size > 0); /*If received packet is hello with Parameter TLV*/ if (ntohl(eigrph->ack) == 0) { /* increment statistics. */ ei->hello_in++; if (nbr) eigrp_nbr_state_update(nbr); } if (IS_DEBUG_EIGRP_PACKET(0, RECV)) zlog_debug("Hello Packet received from %s", inet_ntoa(nbr->src)); } uint32_t FRR_MAJOR; uint32_t FRR_MINOR; void eigrp_sw_version_initialize(void) { char ver_string[] = VERSION; char *dash = strstr(ver_string, "-"); int ret; if (dash) dash[0] = '\0'; ret = sscanf(ver_string, "%" SCNu32 ".%" SCNu32, &FRR_MAJOR, &FRR_MINOR); if (ret != 2) flog_err(EC_EIGRP_PACKET, "Did not Properly parse %s, please fix VERSION string", VERSION); } /** * @fn eigrp_sw_version_encode * * @param[in,out] s packet stream TLV is stored to * * @return uint16_t number of bytes added to packet stream * * @par * Store the software version in the specified location. * This consists of two bytes of OS version, and two bytes of EIGRP * revision number. */ static uint16_t eigrp_sw_version_encode(struct stream *s) { uint16_t length = EIGRP_TLV_SW_VERSION_LEN; // setup the tlv fields stream_putw(s, EIGRP_TLV_SW_VERSION); stream_putw(s, length); stream_putc(s, FRR_MAJOR); //!< major os version stream_putc(s, FRR_MINOR); //!< minor os version /* and the core eigrp version */ stream_putc(s, EIGRP_MAJOR_VERSION); stream_putc(s, EIGRP_MINOR_VERSION); return (length); } /** * @fn eigrp_tidlist_encode * * @param[in,out] s packet stream TLV is stored to * * @return void * * @par * If doing mutli-topology, then store the supported TID list. * This is currently a place holder function */ static uint16_t eigrp_tidlist_encode(struct stream *s) { // uint16_t length = EIGRP_TLV_SW_VERSION_LEN; return 0; } /** * @fn eigrp_sequence_encode * * @param[in,out] s packet stream TLV is stored to * * @return uint16_t number of bytes added to packet stream * * @par * Part of conditional receive process * */ static uint16_t eigrp_sequence_encode(struct eigrp *eigrp, struct stream *s) { uint16_t length = EIGRP_TLV_SEQ_BASE_LEN; struct eigrp_interface *ei; struct listnode *node, *node2, *nnode2; struct eigrp_neighbor *nbr; size_t backup_end, size_end; int found; // add in the parameters TLV backup_end = stream_get_endp(s); stream_putw(s, EIGRP_TLV_SEQ); size_end = s->endp; stream_putw(s, 0x0000); stream_putc(s, IPV4_MAX_BYTELEN); found = 0; for (ALL_LIST_ELEMENTS_RO(eigrp->eiflist, node, ei)) { for (ALL_LIST_ELEMENTS(ei->nbrs, node2, nnode2, nbr)) { if (nbr->multicast_queue->count > 0) { length += (uint16_t)stream_put_ipv4( s, nbr->src.s_addr); found = 1; } } } if (found == 0) { stream_set_endp(s, backup_end); return 0; } backup_end = stream_get_endp(s); stream_set_endp(s, size_end); stream_putw(s, length); stream_set_endp(s, backup_end); return length; } /** * @fn eigrp_sequence_encode * * @param[in,out] s packet stream TLV is stored to * * @return uint16_t number of bytes added to packet stream * * @par * Part of conditional receive process * */ static uint16_t eigrp_next_sequence_encode(struct eigrp *eigrp, struct stream *s) { uint16_t length = EIGRP_NEXT_SEQUENCE_TLV_SIZE; // add in the parameters TLV stream_putw(s, EIGRP_TLV_NEXT_MCAST_SEQ); stream_putw(s, EIGRP_NEXT_SEQUENCE_TLV_SIZE); stream_putl(s, eigrp->sequence_number + 1); return length; } /** * @fn eigrp_hello_parameter_encode * * @param[in] ei pointer to interface hello packet came in on * @param[in,out] s packet stream TLV is stored to * * @return uint16_t number of bytes added to packet stream * * @par * Encode Parameter TLV, used to convey metric weights and the hold time. * * @usage * Note the addition of K6 for the new extended metrics, and does not apply to * older TLV packet formats. */ static uint16_t eigrp_hello_parameter_encode(struct eigrp_interface *ei, struct stream *s, uint8_t flags) { // add in the parameters TLV stream_putw(s, EIGRP_TLV_PARAMETER); stream_putw(s, EIGRP_TLV_PARAMETER_LEN); // if graceful shutdown is needed to be announced, send all 255 in K // values if (flags & EIGRP_HELLO_GRACEFUL_SHUTDOWN) { stream_putc(s, 0xff); /* K1 */ stream_putc(s, 0xff); /* K2 */ stream_putc(s, 0xff); /* K3 */ stream_putc(s, 0xff); /* K4 */ stream_putc(s, 0xff); /* K5 */ stream_putc(s, 0xff); /* K6 */ } else // set k values { stream_putc(s, ei->eigrp->k_values[0]); /* K1 */ stream_putc(s, ei->eigrp->k_values[1]); /* K2 */ stream_putc(s, ei->eigrp->k_values[2]); /* K3 */ stream_putc(s, ei->eigrp->k_values[3]); /* K4 */ stream_putc(s, ei->eigrp->k_values[4]); /* K5 */ stream_putc(s, ei->eigrp->k_values[5]); /* K6 */ } // and set hold time value.. stream_putw(s, ei->params.v_wait); return EIGRP_TLV_PARAMETER_LEN; } /** * @fn eigrp_hello_encode * * @param[in] ei pointer to interface hello packet came in on * @param[in] s packet stream TLV is stored to * @param[in] ack if non-zero, neigbors sequence packet to ack * @param[in] flags type of hello packet * @param[in] nbr_addr pointer to neighbor address for Peer * Termination TLV * * @return eigrp_packet pointer initialize hello packet * * @par * Allocate an EIGRP hello packet, and add in the the approperate TLVs * */ static struct eigrp_packet *eigrp_hello_encode(struct eigrp_interface *ei, in_addr_t addr, uint32_t ack, uint8_t flags, struct in_addr *nbr_addr) { struct eigrp_packet *ep; uint16_t length = EIGRP_HEADER_LEN; // allocate a new packet to be sent ep = eigrp_packet_new(EIGRP_PACKET_MTU(ei->ifp->mtu), NULL); if (ep) { // encode common header feilds eigrp_packet_header_init(EIGRP_OPC_HELLO, ei->eigrp, ep->s, 0, 0, ack); // encode Authentication TLV if ((ei->params.auth_type == EIGRP_AUTH_TYPE_MD5) && (ei->params.auth_keychain != NULL)) { length += eigrp_add_authTLV_MD5_to_stream(ep->s, ei); } else if ((ei->params.auth_type == EIGRP_AUTH_TYPE_SHA256) && (ei->params.auth_keychain != NULL)) { length += eigrp_add_authTLV_SHA256_to_stream(ep->s, ei); } /* encode appropriate parameters to Hello packet */ if (flags & EIGRP_HELLO_GRACEFUL_SHUTDOWN) length += eigrp_hello_parameter_encode( ei, ep->s, EIGRP_HELLO_GRACEFUL_SHUTDOWN); else length += eigrp_hello_parameter_encode( ei, ep->s, EIGRP_HELLO_NORMAL); // figure out the version of code we're running length += eigrp_sw_version_encode(ep->s); if (flags & EIGRP_HELLO_ADD_SEQUENCE) { length += eigrp_sequence_encode(ei->eigrp, ep->s); length += eigrp_next_sequence_encode(ei->eigrp, ep->s); } // add in the TID list if doing multi-topology length += eigrp_tidlist_encode(ep->s); /* encode Peer Termination TLV if needed */ if (flags & EIGRP_HELLO_GRACEFUL_SHUTDOWN_NBR) length += eigrp_peer_termination_encode(ep->s, nbr_addr); // Set packet length ep->length = length; // set soruce address for the hello packet ep->dst.s_addr = addr; if ((ei->params.auth_type == EIGRP_AUTH_TYPE_MD5) && (ei->params.auth_keychain != NULL)) { eigrp_make_md5_digest(ei, ep->s, EIGRP_AUTH_BASIC_HELLO_FLAG); } else if ((ei->params.auth_type == EIGRP_AUTH_TYPE_SHA256) && (ei->params.auth_keychain != NULL)) { eigrp_make_sha256_digest(ei, ep->s, EIGRP_AUTH_BASIC_HELLO_FLAG); } // EIGRP Checksum eigrp_packet_checksum(ei, ep->s, length); } return (ep); } /** * @fn eigrp_hello_send * * @param[in] nbr neighbor the ACK should be sent to * * @return void * * @par * Send (unicast) a hello packet with the destination address * associated with the neighbor. The eigrp header ACK feild will be * updated to the neighbor's sequence number to acknolodge any * outstanding packets */ void eigrp_hello_send_ack(struct eigrp_neighbor *nbr) { struct eigrp_packet *ep; /* if packet succesfully created, add it to the interface queue */ ep = eigrp_hello_encode(nbr->ei, nbr->src.s_addr, nbr->recv_sequence_number, EIGRP_HELLO_NORMAL, NULL); if (ep) { if (IS_DEBUG_EIGRP_PACKET(0, SEND)) zlog_debug("Queueing [Hello] Ack Seq [%u] nbr [%s]", nbr->recv_sequence_number, inet_ntoa(nbr->src)); /* Add packet to the top of the interface output queue*/ eigrp_fifo_push(nbr->ei->obuf, ep); /* Hook thread to write packet. */ if (nbr->ei->on_write_q == 0) { listnode_add(nbr->ei->eigrp->oi_write_q, nbr->ei); nbr->ei->on_write_q = 1; } thread_add_write(master, eigrp_write, nbr->ei->eigrp, nbr->ei->eigrp->fd, &nbr->ei->eigrp->t_write); } } /** * @fn eigrp_hello_send * * @param[in] ei pointer to interface hello should be sent * @param[in] flags type of hello packet * @param[in] nbr_addr pointer to neighbor address for Peer * Termination TLV * * @return void * * @par * Build and enqueue a generic (multicast) periodic hello packet for * sending. If no packets are currently queues, the packet will be * sent immadiatly */ void eigrp_hello_send(struct eigrp_interface *ei, uint8_t flags, struct in_addr *nbr_addr) { struct eigrp_packet *ep = NULL; /* If this is passive interface, do not send EIGRP Hello. if ((EIGRP_IF_PASSIVE_STATUS (ei) == EIGRP_IF_PASSIVE) || (ei->type != EIGRP_IFTYPE_NBMA)) return; */ if (IS_DEBUG_EIGRP_PACKET(0, SEND)) zlog_debug("Queueing [Hello] Interface(%s)", IF_NAME(ei)); /* if packet was succesfully created, then add it to the interface queue */ ep = eigrp_hello_encode(ei, htonl(EIGRP_MULTICAST_ADDRESS), 0, flags, nbr_addr); if (ep) { // Add packet to the top of the interface output queue eigrp_fifo_push(ei->obuf, ep); /* Hook thread to write packet. */ if (ei->on_write_q == 0) { listnode_add(ei->eigrp->oi_write_q, ei); ei->on_write_q = 1; } if (ei->eigrp->t_write == NULL) { if (flags & EIGRP_HELLO_GRACEFUL_SHUTDOWN) { thread_execute(master, eigrp_write, ei->eigrp, ei->eigrp->fd); } else { thread_add_write(master, eigrp_write, ei->eigrp, ei->eigrp->fd, &ei->eigrp->t_write); } } } } frr-7.2.1/eigrpd/eigrp_interface.c0000644000000000000000000002415513610377563013767 00000000000000/* * EIGRP Interface Functions. * Copyright (C) 2013-2016 * Authors: * Donnie Savage * Jan Janovic * Matej Perina * Peter Orsag * Peter Paluch * Frantisek Gazo * Tomas Hvorkovy * Martin Kontsek * Lukas Koribsky * * This file is part of GNU Zebra. * * GNU Zebra is free software; you can redistribute it and/or modify it * under the terms of the GNU General Public License as published by the * Free Software Foundation; either version 2, or (at your option) any * later version. * * GNU Zebra 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; see the file COPYING; if not, write to the Free Software * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA */ #include #include "thread.h" #include "linklist.h" #include "prefix.h" #include "if.h" #include "table.h" #include "memory.h" #include "command.h" #include "stream.h" #include "log.h" #include "keychain.h" #include "vrf.h" #include "eigrpd/eigrp_structs.h" #include "eigrpd/eigrpd.h" #include "eigrpd/eigrp_interface.h" #include "eigrpd/eigrp_neighbor.h" #include "eigrpd/eigrp_packet.h" #include "eigrpd/eigrp_zebra.h" #include "eigrpd/eigrp_vty.h" #include "eigrpd/eigrp_network.h" #include "eigrpd/eigrp_topology.h" #include "eigrpd/eigrp_memory.h" #include "eigrpd/eigrp_fsm.h" struct eigrp_interface *eigrp_if_new(struct eigrp *eigrp, struct interface *ifp, struct prefix *p) { struct eigrp_interface *ei = ifp->info; int i; if (ei) return ei; ei = XCALLOC(MTYPE_EIGRP_IF, sizeof(struct eigrp_interface)); /* Set zebra interface pointer. */ ei->ifp = ifp; prefix_copy(&ei->address, p); ifp->info = ei; listnode_add(eigrp->eiflist, ei); ei->type = EIGRP_IFTYPE_BROADCAST; /* Initialize neighbor list. */ ei->nbrs = list_new(); ei->crypt_seqnum = time(NULL); /* Initialize lists */ for (i = 0; i < EIGRP_FILTER_MAX; i++) { ei->list[i] = NULL; ei->prefix[i] = NULL; ei->routemap[i] = NULL; } ei->eigrp = eigrp; ei->params.v_hello = EIGRP_HELLO_INTERVAL_DEFAULT; ei->params.v_wait = EIGRP_HOLD_INTERVAL_DEFAULT; ei->params.bandwidth = EIGRP_BANDWIDTH_DEFAULT; ei->params.delay = EIGRP_DELAY_DEFAULT; ei->params.reliability = EIGRP_RELIABILITY_DEFAULT; ei->params.load = EIGRP_LOAD_DEFAULT; ei->params.auth_type = EIGRP_AUTH_TYPE_NONE; ei->params.auth_keychain = NULL; return ei; } int eigrp_if_delete_hook(struct interface *ifp) { struct eigrp_interface *ei = ifp->info; struct eigrp *eigrp; if (!ei) return 0; list_delete(&ei->nbrs); eigrp = ei->eigrp; listnode_delete(eigrp->eiflist, ei); eigrp_fifo_free(ei->obuf); XFREE(MTYPE_EIGRP_IF_INFO, ifp->info); ifp->info = NULL; return 0; } struct list *eigrp_iflist; void eigrp_if_init(void) { /* Initialize Zebra interface data structure. */ // hook_register_prio(if_add, 0, eigrp_if_new); hook_register_prio(if_del, 0, eigrp_if_delete_hook); } void eigrp_del_if_params(struct eigrp_if_params *eip) { if (eip->auth_keychain) free(eip->auth_keychain); } int eigrp_if_up(struct eigrp_interface *ei) { struct eigrp_prefix_entry *pe; struct eigrp_nexthop_entry *ne; struct eigrp_metrics metric; struct eigrp_interface *ei2; struct listnode *node, *nnode; struct eigrp *eigrp; if (ei == NULL) return 0; eigrp = ei->eigrp; eigrp_adjust_sndbuflen(eigrp, ei->ifp->mtu); eigrp_if_stream_set(ei); /* Set multicast memberships appropriately for new state. */ eigrp_if_set_multicast(ei); thread_add_event(master, eigrp_hello_timer, ei, (1), NULL); /*Prepare metrics*/ metric.bandwidth = eigrp_bandwidth_to_scaled(ei->params.bandwidth); metric.delay = eigrp_delay_to_scaled(ei->params.delay); metric.load = ei->params.load; metric.reliability = ei->params.reliability; metric.mtu[0] = 0xDC; metric.mtu[1] = 0x05; metric.mtu[2] = 0x00; metric.hop_count = 0; metric.flags = 0; metric.tag = 0; /*Add connected entry to topology table*/ ne = eigrp_nexthop_entry_new(); ne->ei = ei; ne->reported_metric = metric; ne->total_metric = metric; ne->distance = eigrp_calculate_metrics(eigrp, metric); ne->reported_distance = 0; ne->adv_router = eigrp->neighbor_self; ne->flags = EIGRP_NEXTHOP_ENTRY_SUCCESSOR_FLAG; struct prefix dest_addr; dest_addr = ei->address; apply_mask(&dest_addr); pe = eigrp_topology_table_lookup_ipv4(eigrp->topology_table, &dest_addr); if (pe == NULL) { pe = eigrp_prefix_entry_new(); pe->serno = eigrp->serno; pe->destination = (struct prefix *)prefix_ipv4_new(); prefix_copy(pe->destination, &dest_addr); pe->af = AF_INET; pe->nt = EIGRP_TOPOLOGY_TYPE_CONNECTED; ne->prefix = pe; pe->reported_metric = metric; pe->state = EIGRP_FSM_STATE_PASSIVE; pe->fdistance = eigrp_calculate_metrics(eigrp, metric); pe->req_action |= EIGRP_FSM_NEED_UPDATE; eigrp_prefix_entry_add(eigrp->topology_table, pe); listnode_add(eigrp->topology_changes_internalIPV4, pe); eigrp_nexthop_entry_add(eigrp, pe, ne); for (ALL_LIST_ELEMENTS(eigrp->eiflist, node, nnode, ei2)) { eigrp_update_send(ei2); } pe->req_action &= ~EIGRP_FSM_NEED_UPDATE; listnode_delete(eigrp->topology_changes_internalIPV4, pe); } else { struct eigrp_fsm_action_message msg; ne->prefix = pe; eigrp_nexthop_entry_add(eigrp, pe, ne); msg.packet_type = EIGRP_OPC_UPDATE; msg.eigrp = eigrp; msg.data_type = EIGRP_CONNECTED; msg.adv_router = NULL; msg.entry = ne; msg.prefix = pe; eigrp_fsm_event(&msg); } return 1; } int eigrp_if_down(struct eigrp_interface *ei) { struct listnode *node, *nnode; struct eigrp_neighbor *nbr; if (ei == NULL) return 0; /* Shutdown packet reception and sending */ if (ei->t_hello) THREAD_OFF(ei->t_hello); eigrp_if_stream_unset(ei); /*Set infinite metrics to routes learned by this interface and start * query process*/ for (ALL_LIST_ELEMENTS(ei->nbrs, node, nnode, nbr)) { eigrp_nbr_delete(nbr); } return 1; } void eigrp_if_stream_set(struct eigrp_interface *ei) { /* set output fifo queue. */ if (ei->obuf == NULL) ei->obuf = eigrp_fifo_new(); } void eigrp_if_stream_unset(struct eigrp_interface *ei) { struct eigrp *eigrp = ei->eigrp; if (ei->on_write_q) { listnode_delete(eigrp->oi_write_q, ei); if (list_isempty(eigrp->oi_write_q)) thread_cancel(eigrp->t_write); ei->on_write_q = 0; } } bool eigrp_if_is_passive(struct eigrp_interface *ei) { if (ei->params.passive_interface == EIGRP_IF_ACTIVE) return false; if (ei->eigrp->passive_interface_default == EIGRP_IF_ACTIVE) return false; return true; } void eigrp_if_set_multicast(struct eigrp_interface *ei) { if (!eigrp_if_is_passive(ei)) { /* The interface should belong to the EIGRP-all-routers group. */ if (!ei->member_allrouters && (eigrp_if_add_allspfrouters(ei->eigrp, &ei->address, ei->ifp->ifindex) >= 0)) /* Set the flag only if the system call to join * succeeded. */ ei->member_allrouters = true; } else { /* The interface should NOT belong to the EIGRP-all-routers * group. */ if (ei->member_allrouters) { /* Only actually drop if this is the last reference */ eigrp_if_drop_allspfrouters(ei->eigrp, &ei->address, ei->ifp->ifindex); /* Unset the flag regardless of whether the system call to leave the group succeeded, since it's much safer to assume that we are not a member. */ ei->member_allrouters = false; } } } uint8_t eigrp_default_iftype(struct interface *ifp) { if (if_is_pointopoint(ifp)) return EIGRP_IFTYPE_POINTOPOINT; else if (if_is_loopback(ifp)) return EIGRP_IFTYPE_LOOPBACK; else return EIGRP_IFTYPE_BROADCAST; } void eigrp_if_free(struct eigrp_interface *ei, int source) { struct prefix dest_addr; struct eigrp_prefix_entry *pe; struct eigrp *eigrp = ei->eigrp; if (source == INTERFACE_DOWN_BY_VTY) { THREAD_OFF(ei->t_hello); eigrp_hello_send(ei, EIGRP_HELLO_GRACEFUL_SHUTDOWN, NULL); } dest_addr = ei->address; apply_mask(&dest_addr); pe = eigrp_topology_table_lookup_ipv4(eigrp->topology_table, &dest_addr); if (pe) eigrp_prefix_entry_delete(eigrp, eigrp->topology_table, pe); eigrp_if_down(ei); listnode_delete(ei->eigrp->eiflist, ei); } /* Simulate down/up on the interface. This is needed, for example, when the MTU changes. */ void eigrp_if_reset(struct interface *ifp) { struct eigrp_interface *ei = ifp->info; if (!ei) return; eigrp_if_down(ei); eigrp_if_up(ei); } struct eigrp_interface *eigrp_if_lookup_by_local_addr(struct eigrp *eigrp, struct interface *ifp, struct in_addr address) { struct listnode *node; struct eigrp_interface *ei; for (ALL_LIST_ELEMENTS_RO(eigrp->eiflist, node, ei)) { if (ifp && ei->ifp != ifp) continue; if (IPV4_ADDR_SAME(&address, &ei->address.u.prefix4)) return ei; } return NULL; } /** * @fn eigrp_if_lookup_by_name * * @param[in] eigrp EIGRP process * @param[in] if_name Name of the interface * * @return struct eigrp_interface * * * @par * Function is used for lookup interface by name. */ struct eigrp_interface *eigrp_if_lookup_by_name(struct eigrp *eigrp, const char *if_name) { struct eigrp_interface *ei; struct listnode *node; /* iterate over all eigrp interfaces */ for (ALL_LIST_ELEMENTS_RO(eigrp->eiflist, node, ei)) { /* compare int name with eigrp interface's name */ if (strcmp(ei->ifp->name, if_name) == 0) { return ei; } } return NULL; } uint32_t eigrp_bandwidth_to_scaled(uint32_t bandwidth) { uint64_t temp_bandwidth = (256ull * 10000000) / bandwidth; temp_bandwidth = temp_bandwidth < EIGRP_MAX_METRIC ? temp_bandwidth : EIGRP_MAX_METRIC; return (uint32_t)temp_bandwidth; } uint32_t eigrp_scaled_to_bandwidth(uint32_t scaled) { uint64_t temp_scaled = scaled * (256ull * 10000000); temp_scaled = temp_scaled < EIGRP_MAX_METRIC ? temp_scaled : EIGRP_MAX_METRIC; return (uint32_t)temp_scaled; } uint32_t eigrp_delay_to_scaled(uint32_t delay) { return delay * 256; } uint32_t eigrp_scaled_to_delay(uint32_t scaled) { return scaled / 256; } frr-7.2.1/eigrpd/eigrp_interface.h0000644000000000000000000000456413610377563013776 00000000000000/* * EIGRP Interface Functions. * Copyright (C) 2013-2016 * Authors: * Donnie Savage * Jan Janovic * Matej Perina * Peter Orsag * Peter Paluch * Frantisek Gazo * Tomas Hvorkovy * Martin Kontsek * Lukas Koribsky * * This file is part of GNU Zebra. * * GNU Zebra is free software; you can redistribute it and/or modify it * under the terms of the GNU General Public License as published by the * Free Software Foundation; either version 2, or (at your option) any * later version. * * GNU Zebra 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; see the file COPYING; if not, write to the Free Software * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA */ #ifndef _ZEBRA_EIGRP_INTERFACE_H_ #define _ZEBRA_EIGRP_INTERFACE_H_ /*Prototypes*/ extern void eigrp_if_init(void); extern int eigrp_if_new_hook(struct interface *); extern int eigrp_if_delete_hook(struct interface *); extern bool eigrp_if_is_passive(struct eigrp_interface *ei); extern void eigrp_del_if_params(struct eigrp_if_params *); extern struct eigrp_interface *eigrp_if_new(struct eigrp *, struct interface *, struct prefix *); extern int eigrp_if_up(struct eigrp_interface *); extern void eigrp_if_stream_set(struct eigrp_interface *); extern void eigrp_if_set_multicast(struct eigrp_interface *); extern uint8_t eigrp_default_iftype(struct interface *); extern void eigrp_if_free(struct eigrp_interface *, int); extern int eigrp_if_down(struct eigrp_interface *); extern void eigrp_if_stream_unset(struct eigrp_interface *); extern struct eigrp_interface *eigrp_if_lookup_by_local_addr(struct eigrp *, struct interface *, struct in_addr); extern struct eigrp_interface *eigrp_if_lookup_by_name(struct eigrp *, const char *); /* Simulate down/up on the interface. */ extern void eigrp_if_reset(struct interface *); extern uint32_t eigrp_bandwidth_to_scaled(uint32_t); extern uint32_t eigrp_scaled_to_bandwidth(uint32_t); extern uint32_t eigrp_delay_to_scaled(uint32_t); extern uint32_t eigrp_scaled_to_delay(uint32_t); #endif /* ZEBRA_EIGRP_INTERFACE_H_ */ frr-7.2.1/eigrpd/eigrp_macros.h0000644000000000000000000000273013610377563013313 00000000000000/* * EIGRP Macros Definition. * Copyright (C) 2013-2014 * Authors: * Donnie Savage * Jan Janovic * Matej Perina * Peter Orsag * Peter Paluch * * This file is part of GNU Zebra. * * GNU Zebra is free software; you can redistribute it and/or modify it * under the terms of the GNU General Public License as published by the * Free Software Foundation; either version 2, or (at your option) any * later version. * * GNU Zebra 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; see the file COPYING; if not, write to the Free Software * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA */ #ifndef _ZEBRA_EIGRP_MACROS_H_ #define _ZEBRA_EIGRP_MACROS_H_ //-------------------------------------------------------------------------- #define EIGRP_IF_STRING_MAXLEN 40 #define IF_NAME(I) eigrp_if_name_string ((I)) //-------------------------------------------------------------------------- #define EIGRP_PACKET_MTU(mtu) ((mtu) - (sizeof(struct ip))) /* Topology Macros */ /* FSM macros*/ #define EIGRP_FSM_EVENT_SCHEDULE(I, E) \ thread_add_event(master, eigrp_fsm_event, (I), (E)) #endif /* _ZEBRA_EIGRP_MACROS_H_ */ frr-7.2.1/eigrpd/eigrp_main.c0000644000000000000000000001201713610377563012745 00000000000000/* * EIGRP Main Routine. * Copyright (C) 2013-2015 * Authors: * Donnie Savage * Jan Janovic * Matej Perina * Peter Orsag * Peter Paluch * Frantisek Gazo * Tomas Hvorkovy * Martin Kontsek * Lukas Koribsky * * This file is part of GNU Zebra. * * GNU Zebra is free software; you can redistribute it and/or modify it * under the terms of the GNU General Public License as published by the * Free Software Foundation; either version 2, or (at your option) any * later version. * * GNU Zebra 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; see the file COPYING; if not, write to the Free Software * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA */ #include #include #include "getopt.h" #include "thread.h" #include "prefix.h" #include "linklist.h" #include "if.h" #include "vector.h" #include "vty.h" #include "command.h" #include "filter.h" #include "plist.h" #include "stream.h" #include "log.h" #include "memory.h" #include "privs.h" #include "sigevent.h" #include "zclient.h" #include "keychain.h" #include "distribute.h" #include "libfrr.h" #include "routemap.h" //#include "if_rmap.h" #include "eigrpd/eigrp_structs.h" #include "eigrpd/eigrpd.h" #include "eigrpd/eigrp_dump.h" #include "eigrpd/eigrp_interface.h" #include "eigrpd/eigrp_neighbor.h" #include "eigrpd/eigrp_packet.h" #include "eigrpd/eigrp_vty.h" #include "eigrpd/eigrp_zebra.h" #include "eigrpd/eigrp_network.h" #include "eigrpd/eigrp_snmp.h" #include "eigrpd/eigrp_filter.h" #include "eigrpd/eigrp_errors.h" #include "eigrpd/eigrp_vrf.h" //#include "eigrpd/eigrp_routemap.h" /* eigprd privileges */ zebra_capabilities_t _caps_p[] = { ZCAP_NET_RAW, ZCAP_BIND, ZCAP_NET_ADMIN, }; struct zebra_privs_t eigrpd_privs = { #if defined(FRR_USER) && defined(FRR_GROUP) .user = FRR_USER, .group = FRR_GROUP, #endif #if defined(VTY_GROUP) .vty_group = VTY_GROUP, #endif .caps_p = _caps_p, .cap_num_p = array_size(_caps_p), .cap_num_i = 0}; /* EIGRPd options. */ struct option longopts[] = {{0}}; /* Master of threads. */ struct thread_master *master; /* Forward declaration of daemon info structure. */ static struct frr_daemon_info eigrpd_di; /* SIGHUP handler. */ static void sighup(void) { zlog_info("SIGHUP received"); /* Reload config file. */ vty_read_config(NULL, eigrpd_di.config_file, config_default); } /* SIGINT / SIGTERM handler. */ static void sigint(void) { zlog_notice("Terminating on signal"); eigrp_terminate(); exit(0); } /* SIGUSR1 handler. */ static void sigusr1(void) { zlog_rotate(); } struct quagga_signal_t eigrp_signals[] = { { .signal = SIGHUP, .handler = &sighup, }, { .signal = SIGUSR1, .handler = &sigusr1, }, { .signal = SIGINT, .handler = &sigint, }, { .signal = SIGTERM, .handler = &sigint, }, }; static const struct frr_yang_module_info *eigrpd_yang_modules[] = { &frr_eigrpd_info, &frr_interface_info, }; FRR_DAEMON_INFO(eigrpd, EIGRP, .vty_port = EIGRP_VTY_PORT, .proghelp = "Implementation of the EIGRP routing protocol.", .signals = eigrp_signals, .n_signals = array_size(eigrp_signals), .privs = &eigrpd_privs, .yang_modules = eigrpd_yang_modules, .n_yang_modules = array_size(eigrpd_yang_modules), ) /* EIGRPd main routine. */ int main(int argc, char **argv, char **envp) { frr_preinit(&eigrpd_di, argc, argv); frr_opt_add("", longopts, ""); while (1) { int opt; opt = frr_getopt(argc, argv, NULL); if (opt == EOF) break; switch (opt) { case 0: break; default: frr_help_exit(1); break; } } eigrp_sw_version_initialize(); /* EIGRP master init. */ eigrp_master_init(); eigrp_om->master = frr_init(); master = eigrp_om->master; eigrp_error_init(); eigrp_vrf_init(); vrf_init(NULL, NULL, NULL, NULL, NULL); /*EIGRPd init*/ eigrp_if_init(); eigrp_zebra_init(); eigrp_debug_init(); /* Get configuration file. */ /* EIGRP VTY inits */ eigrp_vty_init(); keychain_init(); eigrp_vty_show_init(); eigrp_cli_init(); #ifdef HAVE_SNMP eigrp_snmp_init(); #endif /* HAVE_SNMP */ /* Access list install. */ access_list_init(); access_list_add_hook(eigrp_distribute_update_all_wrapper); access_list_delete_hook(eigrp_distribute_update_all_wrapper); /* Prefix list initialize.*/ prefix_list_init(); prefix_list_add_hook(eigrp_distribute_update_all); prefix_list_delete_hook(eigrp_distribute_update_all); /* * XXX: This is just to get the CLI installed to suppress VTYSH errors. * Routemaps in EIGRP are not yet functional. */ route_map_init(); /*eigrp_route_map_init(); route_map_add_hook (eigrp_rmap_update); route_map_delete_hook (eigrp_rmap_update);*/ /*if_rmap_init (EIGRP_NODE); */ /* Distribute list install. */ distribute_list_init(EIGRP_NODE); frr_config_fork(); frr_run(master); /* Not reached. */ return (0); } frr-7.2.1/eigrpd/eigrp_memory.c0000644000000000000000000000326513610377563013336 00000000000000/* eigrpd memory type definitions * * Copyright (C) 2017 Donald Sharp * * This file is part of FRR * * FRR is free software; you can redistribute it and/or modify it * under the terms of the GNU General Public License as published by the * Free Software Foundation; either version 2, or (at your option) any * later version. * * FRR 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; see the file COPYING; if not, write to the Free Software * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA */ #ifdef HAVE_CONFIG_H #include "config.h" #endif #include "eigrp_memory.h" DEFINE_MGROUP(EIGRPD, "eigrpd") DEFINE_MTYPE(EIGRPD, EIGRP_TOP, "EIGRP structure") DEFINE_MTYPE(EIGRPD, EIGRP_IF, "EIGRP interface") DEFINE_MTYPE(EIGRPD, EIGRP_NEIGHBOR, "EIGRP neighbor") DEFINE_MTYPE(EIGRPD, EIGRP_IF_PARAMS, "EIGRP Interface Parameters") DEFINE_MTYPE(EIGRPD, EIGRP_IF_INFO, "EIGRP Interface Information") DEFINE_MTYPE(EIGRPD, EIGRP_FIFO, "EIGRP FIFO") DEFINE_MTYPE(EIGRPD, EIGRP_PACKET, "EIGRP Packet") DEFINE_MTYPE(EIGRPD, EIGRP_IPV4_INT_TLV, "EIGRP IPv4 TLV") DEFINE_MTYPE(EIGRPD, EIGRP_SEQ_TLV, "EIGRP SEQ TLV") DEFINE_MTYPE(EIGRPD, EIGRP_AUTH_TLV, "EIGRP AUTH TLV") DEFINE_MTYPE(EIGRPD, EIGRP_AUTH_SHA256_TLV, "EIGRP SHA TLV") DEFINE_MTYPE(EIGRPD, EIGRP_PREFIX_ENTRY, "EIGRP Prefix") DEFINE_MTYPE(EIGRPD, EIGRP_NEXTHOP_ENTRY, "EIGRP Nexthop Entry") DEFINE_MTYPE(EIGRPD, EIGRP_FSM_MSG, "EIGRP FSM Message") frr-7.2.1/eigrpd/eigrp_memory.h0000644000000000000000000000253313610377563013340 00000000000000/* eigrpd memory type declarations * * Copyright (C) 2017 Donald Sharp * * This file is part of FRR. * * FRR is free software; you can redistribute it and/or modify it * under the terms of the GNU General Public License as published by the * Free Software Foundation; either version 2, or (at your option) any * later version. * * FRR 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; see the file COPYING; if not, write to the Free Software * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA */ #ifndef _FRR_EIGRP_MEMORY_H #define _FRR_EIGRP_MEMORY_H #include "memory.h" DECLARE_MGROUP(EIGRPD) DECLARE_MTYPE(EIGRP_TOP) DECLARE_MTYPE(EIGRP_IF) DECLARE_MTYPE(EIGRP_NEIGHBOR) DECLARE_MTYPE(EIGRP_IF_PARAMS) DECLARE_MTYPE(EIGRP_IF_INFO) DECLARE_MTYPE(EIGRP_FIFO) DECLARE_MTYPE(EIGRP_PACKET) DECLARE_MTYPE(EIGRP_IPV4_INT_TLV) DECLARE_MTYPE(EIGRP_SEQ_TLV) DECLARE_MTYPE(EIGRP_AUTH_TLV) DECLARE_MTYPE(EIGRP_AUTH_SHA256_TLV) DECLARE_MTYPE(EIGRP_PREFIX_ENTRY) DECLARE_MTYPE(EIGRP_NEXTHOP_ENTRY) DECLARE_MTYPE(EIGRP_FSM_MSG) #endif /* _FRR_EIGRP_MEMORY_H */ frr-7.2.1/eigrpd/eigrp_neighbor.c0000644000000000000000000002121313610377563013614 00000000000000/* * EIGRP Neighbor Handling. * Copyright (C) 2013-2016 * Authors: * Donnie Savage * Jan Janovic * Matej Perina * Peter Orsag * Peter Paluch * Frantisek Gazo * Tomas Hvorkovy * Martin Kontsek * Lukas Koribsky * * This file is part of GNU Zebra. * * GNU Zebra is free software; you can redistribute it and/or modify it * under the terms of the GNU General Public License as published by the * Free Software Foundation; either version 2, or (at your option) any * later version. * * GNU Zebra 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; see the file COPYING; if not, write to the Free Software * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA */ #include #include "linklist.h" #include "prefix.h" #include "memory.h" #include "command.h" #include "thread.h" #include "stream.h" #include "table.h" #include "log.h" #include "keychain.h" #include "vty.h" #include "eigrpd/eigrp_structs.h" #include "eigrpd/eigrpd.h" #include "eigrpd/eigrp_interface.h" #include "eigrpd/eigrp_neighbor.h" #include "eigrpd/eigrp_dump.h" #include "eigrpd/eigrp_packet.h" #include "eigrpd/eigrp_zebra.h" #include "eigrpd/eigrp_vty.h" #include "eigrpd/eigrp_network.h" #include "eigrpd/eigrp_topology.h" #include "eigrpd/eigrp_memory.h" #include "eigrpd/eigrp_errors.h" struct eigrp_neighbor *eigrp_nbr_new(struct eigrp_interface *ei) { struct eigrp_neighbor *nbr; /* Allcate new neighbor. */ nbr = XCALLOC(MTYPE_EIGRP_NEIGHBOR, sizeof(struct eigrp_neighbor)); /* Relate neighbor to the interface. */ nbr->ei = ei; /* Set default values. */ eigrp_nbr_state_set(nbr, EIGRP_NEIGHBOR_DOWN); return nbr; } /** *@fn void dissect_eigrp_sw_version (tvbuff_t *tvb, proto_tree *tree, * proto_item *ti) * * @par * Create a new neighbor structure and initalize it. */ static struct eigrp_neighbor *eigrp_nbr_add(struct eigrp_interface *ei, struct eigrp_header *eigrph, struct ip *iph) { struct eigrp_neighbor *nbr; nbr = eigrp_nbr_new(ei); nbr->src = iph->ip_src; // if (IS_DEBUG_EIGRP_EVENT) // zlog_debug("NSM[%s:%s]: start", IF_NAME (nbr->oi), // inet_ntoa (nbr->router_id)); return nbr; } struct eigrp_neighbor *eigrp_nbr_get(struct eigrp_interface *ei, struct eigrp_header *eigrph, struct ip *iph) { struct eigrp_neighbor *nbr; struct listnode *node, *nnode; for (ALL_LIST_ELEMENTS(ei->nbrs, node, nnode, nbr)) { if (iph->ip_src.s_addr == nbr->src.s_addr) { return nbr; } } nbr = eigrp_nbr_add(ei, eigrph, iph); listnode_add(ei->nbrs, nbr); return nbr; } /** * @fn eigrp_nbr_lookup_by_addr * * @param[in] ei EIGRP interface * @param[in] nbr_addr Address of neighbor * * @return void * * @par * Function is used for neighbor lookup by address * in specified interface. */ struct eigrp_neighbor *eigrp_nbr_lookup_by_addr(struct eigrp_interface *ei, struct in_addr *addr) { struct eigrp_neighbor *nbr; struct listnode *node, *nnode; for (ALL_LIST_ELEMENTS(ei->nbrs, node, nnode, nbr)) { if (addr->s_addr == nbr->src.s_addr) { return nbr; } } return NULL; } /** * @fn eigrp_nbr_lookup_by_addr_process * * @param[in] eigrp EIGRP process * @param[in] nbr_addr Address of neighbor * * @return void * * @par * Function is used for neighbor lookup by address * in whole EIGRP process. */ struct eigrp_neighbor *eigrp_nbr_lookup_by_addr_process(struct eigrp *eigrp, struct in_addr nbr_addr) { struct eigrp_interface *ei; struct listnode *node, *node2, *nnode2; struct eigrp_neighbor *nbr; /* iterate over all eigrp interfaces */ for (ALL_LIST_ELEMENTS_RO(eigrp->eiflist, node, ei)) { /* iterate over all neighbors on eigrp interface */ for (ALL_LIST_ELEMENTS(ei->nbrs, node2, nnode2, nbr)) { /* compare if neighbor address is same as arg address */ if (nbr->src.s_addr == nbr_addr.s_addr) { return nbr; } } } return NULL; } /* Delete specified EIGRP neighbor from interface. */ void eigrp_nbr_delete(struct eigrp_neighbor *nbr) { eigrp_nbr_state_set(nbr, EIGRP_NEIGHBOR_DOWN); if (nbr->ei) eigrp_topology_neighbor_down(nbr->ei->eigrp, nbr); /* Cancel all events. */ /* Thread lookup cost would be negligible. */ thread_cancel_event(master, nbr); eigrp_fifo_free(nbr->multicast_queue); eigrp_fifo_free(nbr->retrans_queue); THREAD_OFF(nbr->t_holddown); if (nbr->ei) listnode_delete(nbr->ei->nbrs, nbr); XFREE(MTYPE_EIGRP_NEIGHBOR, nbr); } int holddown_timer_expired(struct thread *thread) { struct eigrp_neighbor *nbr = THREAD_ARG(thread); struct eigrp *eigrp = nbr->ei->eigrp; zlog_info("Neighbor %s (%s) is down: holding time expired", inet_ntoa(nbr->src), ifindex2ifname(nbr->ei->ifp->ifindex, eigrp->vrf_id)); nbr->state = EIGRP_NEIGHBOR_DOWN; eigrp_nbr_delete(nbr); return 0; } uint8_t eigrp_nbr_state_get(struct eigrp_neighbor *nbr) { return (nbr->state); } void eigrp_nbr_state_set(struct eigrp_neighbor *nbr, uint8_t state) { nbr->state = state; if (eigrp_nbr_state_get(nbr) == EIGRP_NEIGHBOR_DOWN) { // reset all the seq/ack counters nbr->recv_sequence_number = 0; nbr->init_sequence_number = 0; nbr->retrans_counter = 0; // Kvalues nbr->K1 = EIGRP_K1_DEFAULT; nbr->K2 = EIGRP_K2_DEFAULT; nbr->K3 = EIGRP_K3_DEFAULT; nbr->K4 = EIGRP_K4_DEFAULT; nbr->K5 = EIGRP_K5_DEFAULT; nbr->K6 = EIGRP_K6_DEFAULT; // hold time.. nbr->v_holddown = EIGRP_HOLD_INTERVAL_DEFAULT; THREAD_OFF(nbr->t_holddown); /* out with the old */ if (nbr->multicast_queue) eigrp_fifo_free(nbr->multicast_queue); if (nbr->retrans_queue) eigrp_fifo_free(nbr->retrans_queue); /* in with the new */ nbr->retrans_queue = eigrp_fifo_new(); nbr->multicast_queue = eigrp_fifo_new(); nbr->crypt_seqnum = 0; } } const char *eigrp_nbr_state_str(struct eigrp_neighbor *nbr) { const char *state; switch (nbr->state) { case EIGRP_NEIGHBOR_DOWN: state = "Down"; break; case EIGRP_NEIGHBOR_PENDING: state = "Waiting for Init"; break; case EIGRP_NEIGHBOR_UP: state = "Up"; break; default: state = "Unknown"; break; } return (state); } void eigrp_nbr_state_update(struct eigrp_neighbor *nbr) { switch (nbr->state) { case EIGRP_NEIGHBOR_DOWN: { /*Start Hold Down Timer for neighbor*/ // THREAD_OFF(nbr->t_holddown); // THREAD_TIMER_ON(master, nbr->t_holddown, // holddown_timer_expired, // nbr, nbr->v_holddown); break; } case EIGRP_NEIGHBOR_PENDING: { /*Reset Hold Down Timer for neighbor*/ THREAD_OFF(nbr->t_holddown); thread_add_timer(master, holddown_timer_expired, nbr, nbr->v_holddown, &nbr->t_holddown); break; } case EIGRP_NEIGHBOR_UP: { /*Reset Hold Down Timer for neighbor*/ THREAD_OFF(nbr->t_holddown); thread_add_timer(master, holddown_timer_expired, nbr, nbr->v_holddown, &nbr->t_holddown); break; } } } int eigrp_nbr_count_get(struct eigrp *eigrp) { struct eigrp_interface *iface; struct listnode *node, *node2, *nnode2; struct eigrp_neighbor *nbr; uint32_t counter; counter = 0; for (ALL_LIST_ELEMENTS_RO(eigrp->eiflist, node, iface)) { for (ALL_LIST_ELEMENTS(iface->nbrs, node2, nnode2, nbr)) { if (nbr->state == EIGRP_NEIGHBOR_UP) { counter++; } } } return counter; } /** * @fn eigrp_nbr_hard_restart * * @param[in] nbr Neighbor who would receive hard restart * @param[in] vty Virtual terminal for log output * @return void * * @par * Function used for executing hard restart for neighbor: * Send Hello packet with Peer Termination TLV with * neighbor's address, set it's state to DOWN and delete the neighbor */ void eigrp_nbr_hard_restart(struct eigrp_neighbor *nbr, struct vty *vty) { struct eigrp *eigrp = nbr->ei->eigrp; zlog_debug("Neighbor %s (%s) is down: manually cleared", inet_ntoa(nbr->src), ifindex2ifname(nbr->ei->ifp->ifindex, eigrp->vrf_id)); if (vty != NULL) { vty_time_print(vty, 0); vty_out(vty, "Neighbor %s (%s) is down: manually cleared\n", inet_ntoa(nbr->src), ifindex2ifname(nbr->ei->ifp->ifindex, eigrp->vrf_id)); } /* send Hello with Peer Termination TLV */ eigrp_hello_send(nbr->ei, EIGRP_HELLO_GRACEFUL_SHUTDOWN_NBR, &(nbr->src)); /* set neighbor to DOWN */ nbr->state = EIGRP_NEIGHBOR_DOWN; /* delete neighbor */ eigrp_nbr_delete(nbr); } int eigrp_nbr_split_horizon_check(struct eigrp_nexthop_entry *ne, struct eigrp_interface *ei) { if (ne->distance == EIGRP_MAX_METRIC) return 0; return (ne->ei == ei); } frr-7.2.1/eigrpd/eigrp_neighbor.h0000644000000000000000000000435513610377563013631 00000000000000/* * EIGRP Neighbor Handling. * Copyright (C) 2013-2016 * Authors: * Donnie Savage * Jan Janovic * Matej Perina * Peter Orsag * Peter Paluch * Frantisek Gazo * Tomas Hvorkovy * Martin Kontsek * Lukas Koribsky * * This file is part of GNU Zebra. * * GNU Zebra is free software; you can redistribute it and/or modify it * under the terms of the GNU General Public License as published by the * Free Software Foundation; either version 2, or (at your option) any * later version. * * GNU Zebra 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; see the file COPYING; if not, write to the Free Software * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA */ #ifndef _ZEBRA_EIGRP_NEIGHBOR_H #define _ZEBRA_EIGRP_NEIGHBOR_H /* Prototypes */ extern struct eigrp_neighbor *eigrp_nbr_get(struct eigrp_interface *ei, struct eigrp_header *, struct ip *addr); extern struct eigrp_neighbor *eigrp_nbr_new(struct eigrp_interface *ei); extern void eigrp_nbr_delete(struct eigrp_neighbor *neigh); extern int holddown_timer_expired(struct thread *thread); extern int eigrp_neighborship_check(struct eigrp_neighbor *neigh, struct TLV_Parameter_Type *tlv); extern void eigrp_nbr_state_update(struct eigrp_neighbor *neigh); extern void eigrp_nbr_state_set(struct eigrp_neighbor *neigh, uint8_t state); extern uint8_t eigrp_nbr_state_get(struct eigrp_neighbor *neigh); extern int eigrp_nbr_count_get(struct eigrp *eigrp); extern const char *eigrp_nbr_state_str(struct eigrp_neighbor *neigh); extern struct eigrp_neighbor * eigrp_nbr_lookup_by_addr(struct eigrp_interface *ei, struct in_addr *addr); extern struct eigrp_neighbor * eigrp_nbr_lookup_by_addr_process(struct eigrp *eigrp, struct in_addr addr); extern void eigrp_nbr_hard_restart(struct eigrp_neighbor *nbr, struct vty *vty); extern int eigrp_nbr_split_horizon_check(struct eigrp_nexthop_entry *ne, struct eigrp_interface *ei); #endif /* _ZEBRA_EIGRP_NEIGHBOR_H */ frr-7.2.1/eigrpd/eigrp_network.c0000644000000000000000000002711613610377563013520 00000000000000/* * EIGRP Network Related Functions. * Copyright (C) 2013-2014 * Authors: * Donnie Savage * Jan Janovic * Matej Perina * Peter Orsag * Peter Paluch * * This file is part of GNU Zebra. * * GNU Zebra is free software; you can redistribute it and/or modify it * under the terms of the GNU General Public License as published by the * Free Software Foundation; either version 2, or (at your option) any * later version. * * GNU Zebra 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; see the file COPYING; if not, write to the Free Software * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA */ #include #include "thread.h" #include "linklist.h" #include "prefix.h" #include "if.h" #include "sockunion.h" #include "log.h" #include "sockopt.h" #include "privs.h" #include "table.h" #include "vty.h" #include "lib_errors.h" #include "eigrpd/eigrp_structs.h" #include "eigrpd/eigrpd.h" #include "eigrpd/eigrp_interface.h" #include "eigrpd/eigrp_neighbor.h" #include "eigrpd/eigrp_packet.h" #include "eigrpd/eigrp_zebra.h" #include "eigrpd/eigrp_vty.h" #include "eigrpd/eigrp_network.h" static int eigrp_network_match_iface(const struct prefix *connected_prefix, const struct prefix *prefix); static void eigrp_network_run_interface(struct eigrp *, struct prefix *, struct interface *); int eigrp_sock_init(struct vrf *vrf) { int eigrp_sock; int ret; #ifdef IP_HDRINCL int hincl = 1; #endif frr_with_privs(&eigrpd_privs) { eigrp_sock = vrf_socket( AF_INET, SOCK_RAW, IPPROTO_EIGRPIGP, vrf->vrf_id, vrf->vrf_id != VRF_DEFAULT ? vrf->name : NULL); if (eigrp_sock < 0) { zlog_err("eigrp_read_sock_init: socket: %s", safe_strerror(errno)); exit(1); } #ifdef IP_HDRINCL /* we will include IP header with packet */ ret = setsockopt(eigrp_sock, IPPROTO_IP, IP_HDRINCL, &hincl, sizeof(hincl)); if (ret < 0) { zlog_warn("Can't set IP_HDRINCL option for fd %d: %s", eigrp_sock, safe_strerror(errno)); } #elif defined(IPTOS_PREC_INTERNETCONTROL) #warning "IP_HDRINCL not available on this system" #warning "using IPTOS_PREC_INTERNETCONTROL" ret = setsockopt_ipv4_tos(eigrp_sock, IPTOS_PREC_INTERNETCONTROL); if (ret < 0) { zlog_warn("can't set sockopt IP_TOS %d to socket %d: %s", tos, eigrp_sock, safe_strerror(errno)); close(eigrp_sock); /* Prevent sd leak. */ return ret; } #else /* !IPTOS_PREC_INTERNETCONTROL */ #warning "IP_HDRINCL not available, nor is IPTOS_PREC_INTERNETCONTROL" zlog_warn("IP_HDRINCL option not available"); #endif /* IP_HDRINCL */ ret = setsockopt_ifindex(AF_INET, eigrp_sock, 1); if (ret < 0) zlog_warn("Can't set pktinfo option for fd %d", eigrp_sock); } return eigrp_sock; } void eigrp_adjust_sndbuflen(struct eigrp *eigrp, unsigned int buflen) { int newbuflen; /* Check if any work has to be done at all. */ if (eigrp->maxsndbuflen >= buflen) return; /* Now we try to set SO_SNDBUF to what our caller has requested * (the MTU of a newly added interface). However, if the OS has * truncated the actual buffer size to somewhat less size, try * to detect it and update our records appropriately. The OS * may allocate more buffer space, than requested, this isn't * a error. */ setsockopt_so_sendbuf(eigrp->fd, buflen); newbuflen = getsockopt_so_sendbuf(eigrp->fd); if (newbuflen < 0 || newbuflen < (int)buflen) zlog_warn("%s: tried to set SO_SNDBUF to %u, but got %d", __func__, buflen, newbuflen); if (newbuflen >= 0) eigrp->maxsndbuflen = (unsigned int)newbuflen; else zlog_warn("%s: failed to get SO_SNDBUF", __func__); } int eigrp_if_ipmulticast(struct eigrp *top, struct prefix *p, unsigned int ifindex) { uint8_t val; int ret, len; val = 0; len = sizeof(val); /* Prevent receiving self-origined multicast packets. */ ret = setsockopt(top->fd, IPPROTO_IP, IP_MULTICAST_LOOP, (void *)&val, len); if (ret < 0) zlog_warn( "can't setsockopt IP_MULTICAST_LOOP (0) for fd %d: %s", top->fd, safe_strerror(errno)); /* Explicitly set multicast ttl to 1 -- endo. */ val = 1; ret = setsockopt(top->fd, IPPROTO_IP, IP_MULTICAST_TTL, (void *)&val, len); if (ret < 0) zlog_warn("can't setsockopt IP_MULTICAST_TTL (1) for fd %d: %s", top->fd, safe_strerror(errno)); ret = setsockopt_ipv4_multicast_if(top->fd, p->u.prefix4, ifindex); if (ret < 0) zlog_warn( "can't setsockopt IP_MULTICAST_IF (fd %d, addr %s, " "ifindex %u): %s", top->fd, inet_ntoa(p->u.prefix4), ifindex, safe_strerror(errno)); return ret; } /* Join to the EIGRP multicast group. */ int eigrp_if_add_allspfrouters(struct eigrp *top, struct prefix *p, unsigned int ifindex) { int ret; ret = setsockopt_ipv4_multicast( top->fd, IP_ADD_MEMBERSHIP, p->u.prefix4, htonl(EIGRP_MULTICAST_ADDRESS), ifindex); if (ret < 0) zlog_warn( "can't setsockopt IP_ADD_MEMBERSHIP (fd %d, addr %s, " "ifindex %u, AllSPFRouters): %s; perhaps a kernel limit " "on # of multicast group memberships has been exceeded?", top->fd, inet_ntoa(p->u.prefix4), ifindex, safe_strerror(errno)); else zlog_debug("interface %s [%u] join EIGRP Multicast group.", inet_ntoa(p->u.prefix4), ifindex); return ret; } int eigrp_if_drop_allspfrouters(struct eigrp *top, struct prefix *p, unsigned int ifindex) { int ret; ret = setsockopt_ipv4_multicast( top->fd, IP_DROP_MEMBERSHIP, p->u.prefix4, htonl(EIGRP_MULTICAST_ADDRESS), ifindex); if (ret < 0) zlog_warn( "can't setsockopt IP_DROP_MEMBERSHIP (fd %d, addr %s, " "ifindex %u, AllSPFRouters): %s", top->fd, inet_ntoa(p->u.prefix4), ifindex, safe_strerror(errno)); else zlog_debug("interface %s [%u] leave EIGRP Multicast group.", inet_ntoa(p->u.prefix4), ifindex); return ret; } int eigrp_network_set(struct eigrp *eigrp, struct prefix *p) { struct vrf *vrf = vrf_lookup_by_id(eigrp->vrf_id); struct route_node *rn; struct interface *ifp; rn = route_node_get(eigrp->networks, (struct prefix *)p); if (rn->info) { /* There is already same network statement. */ route_unlock_node(rn); return 0; } struct prefix *pref = prefix_new(); PREFIX_COPY_IPV4(pref, p); rn->info = (void *)pref; /* Schedule Router ID Update. */ if (eigrp->router_id.s_addr == 0) eigrp_router_id_update(eigrp); /* Run network config now. */ /* Get target interface. */ FOR_ALL_INTERFACES (vrf, ifp) { zlog_debug("Setting up %s", ifp->name); eigrp_network_run_interface(eigrp, p, ifp); } return 1; } /* Check whether interface matches given network * returns: 1, true. 0, false */ static int eigrp_network_match_iface(const struct prefix *co_prefix, const struct prefix *net) { /* new approach: more elegant and conceptually clean */ return prefix_match_network_statement(net, co_prefix); } static void eigrp_network_run_interface(struct eigrp *eigrp, struct prefix *p, struct interface *ifp) { struct eigrp_interface *ei; struct listnode *cnode; struct connected *co; /* if interface prefix is match specified prefix, then create socket and join multicast group. */ for (ALL_LIST_ELEMENTS_RO(ifp->connected, cnode, co)) { if (CHECK_FLAG(co->flags, ZEBRA_IFA_SECONDARY)) continue; if (p->family == co->address->family && !ifp->info && eigrp_network_match_iface(co->address, p)) { ei = eigrp_if_new(eigrp, ifp, co->address); /* Relate eigrp interface to eigrp instance. */ ei->eigrp = eigrp; /* if router_id is not configured, dont bring up * interfaces. * eigrp_router_id_update() will call eigrp_if_update * whenever r-id is configured instead. */ if (if_is_operative(ifp)) eigrp_if_up(ei); } } } void eigrp_if_update(struct interface *ifp) { struct listnode *node, *nnode; struct route_node *rn; struct eigrp *eigrp; /* * In the event there are multiple eigrp autonymnous systems running, * we need to check eac one and add the interface as approperate */ for (ALL_LIST_ELEMENTS(eigrp_om->eigrp, node, nnode, eigrp)) { if (ifp->vrf_id != eigrp->vrf_id) continue; /* EIGRP must be on and Router-ID must be configured. */ if (eigrp->router_id.s_addr == 0) continue; /* Run each network for this interface. */ for (rn = route_top(eigrp->networks); rn; rn = route_next(rn)) if (rn->info != NULL) { eigrp_network_run_interface(eigrp, &rn->p, ifp); } } } int eigrp_network_unset(struct eigrp *eigrp, struct prefix *p) { struct route_node *rn; struct listnode *node, *nnode; struct eigrp_interface *ei; struct prefix *pref; rn = route_node_lookup(eigrp->networks, p); if (rn == NULL) return 0; pref = rn->info; route_unlock_node(rn); if (!IPV4_ADDR_SAME(&pref->u.prefix4, &p->u.prefix4)) return 0; prefix_ipv4_free(rn->info); rn->info = NULL; route_unlock_node(rn); /* initial reference */ /* Find interfaces that not configured already. */ for (ALL_LIST_ELEMENTS(eigrp->eiflist, node, nnode, ei)) { bool found = false; for (rn = route_top(eigrp->networks); rn; rn = route_next(rn)) { if (rn->info == NULL) continue; if (eigrp_network_match_iface(&ei->address, &rn->p)) { found = true; route_unlock_node(rn); break; } } if (!found) { eigrp_if_free(ei, INTERFACE_DOWN_BY_VTY); } } return 1; } uint32_t eigrp_calculate_metrics(struct eigrp *eigrp, struct eigrp_metrics metric) { uint64_t temp_metric; temp_metric = 0; if (metric.delay == EIGRP_MAX_METRIC) return EIGRP_MAX_METRIC; // EIGRP Metric = // {K1*BW+[(K2*BW)/(256-load)]+(K3*delay)}*{K5/(reliability+K4)} if (eigrp->k_values[0]) temp_metric += (eigrp->k_values[0] * metric.bandwidth); if (eigrp->k_values[1]) temp_metric += ((eigrp->k_values[1] * metric.bandwidth) / (256 - metric.load)); if (eigrp->k_values[2]) temp_metric += (eigrp->k_values[2] * metric.delay); if (eigrp->k_values[3] && !eigrp->k_values[4]) temp_metric *= eigrp->k_values[3]; if (!eigrp->k_values[3] && eigrp->k_values[4]) temp_metric *= (eigrp->k_values[4] / metric.reliability); if (eigrp->k_values[3] && eigrp->k_values[4]) temp_metric *= ((eigrp->k_values[4] / metric.reliability) + eigrp->k_values[3]); if (temp_metric <= EIGRP_MAX_METRIC) return (uint32_t)temp_metric; else return EIGRP_MAX_METRIC; } uint32_t eigrp_calculate_total_metrics(struct eigrp *eigrp, struct eigrp_nexthop_entry *entry) { struct eigrp_interface *ei = entry->ei; entry->total_metric = entry->reported_metric; uint64_t temp_delay = (uint64_t)entry->total_metric.delay + (uint64_t)eigrp_delay_to_scaled(ei->params.delay); entry->total_metric.delay = temp_delay > EIGRP_MAX_METRIC ? EIGRP_MAX_METRIC : (uint32_t)temp_delay; uint32_t bw = eigrp_bandwidth_to_scaled(ei->params.bandwidth); entry->total_metric.bandwidth = entry->total_metric.bandwidth > bw ? bw : entry->total_metric.bandwidth; return eigrp_calculate_metrics(eigrp, entry->total_metric); } uint8_t eigrp_metrics_is_same(struct eigrp_metrics metric1, struct eigrp_metrics metric2) { if ((metric1.bandwidth == metric2.bandwidth) && (metric1.delay == metric2.delay) && (metric1.hop_count == metric2.hop_count) && (metric1.load == metric2.load) && (metric1.reliability == metric2.reliability) && (metric1.mtu[0] == metric2.mtu[0]) && (metric1.mtu[1] == metric2.mtu[1]) && (metric1.mtu[2] == metric2.mtu[2])) return 1; return 0; // if different } void eigrp_external_routes_refresh(struct eigrp *eigrp, int type) { } frr-7.2.1/eigrpd/eigrp_network.h0000644000000000000000000000373313610377563013524 00000000000000/* * EIGRP Network Related Functions. * Copyright (C) 2013-2014 * Authors: * Donnie Savage * Jan Janovic * Matej Perina * Peter Orsag * Peter Paluch * * This file is part of GNU Zebra. * * GNU Zebra is free software; you can redistribute it and/or modify it * under the terms of the GNU General Public License as published by the * Free Software Foundation; either version 2, or (at your option) any * later version. * * GNU Zebra 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; see the file COPYING; if not, write to the Free Software * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA */ #ifndef _ZEBRA_EIGRP_NETWORK_H #define _ZEBRA_EIGRP_NETWORK_H /* Prototypes */ extern int eigrp_sock_init(struct vrf *vrf); extern int eigrp_if_ipmulticast(struct eigrp *, struct prefix *, unsigned int); extern int eigrp_network_set(struct eigrp *eigrp, struct prefix *p); extern int eigrp_network_unset(struct eigrp *eigrp, struct prefix *p); extern int eigrp_hello_timer(struct thread *); extern void eigrp_if_update(struct interface *); extern int eigrp_if_add_allspfrouters(struct eigrp *, struct prefix *, unsigned int); extern int eigrp_if_drop_allspfrouters(struct eigrp *top, struct prefix *p, unsigned int ifindex); extern void eigrp_adjust_sndbuflen(struct eigrp *, unsigned int); extern uint32_t eigrp_calculate_metrics(struct eigrp *, struct eigrp_metrics); extern uint32_t eigrp_calculate_total_metrics(struct eigrp *, struct eigrp_nexthop_entry *); extern uint8_t eigrp_metrics_is_same(struct eigrp_metrics, struct eigrp_metrics); extern void eigrp_external_routes_refresh(struct eigrp *, int); #endif /* EIGRP_NETWORK_H_ */ frr-7.2.1/eigrpd/eigrp_northbound.c0000644000000000000000000010611613610377563014207 00000000000000/* * EIGRP daemon northbound implementation. * * Copyright (C) 2019 Network Device Education Foundation, Inc. ("NetDEF") * Rafael Zalamena * * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation; either version 2 of the License, or * (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program; if not, write to the Free Software * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA * 02110-1301 USA. */ #include #include "lib/keychain.h" #include "lib/log.h" #include "lib/northbound.h" #include "lib/table.h" #include "lib/vrf.h" #include "lib/zclient.h" #include "eigrp_structs.h" #include "eigrpd.h" #include "eigrp_interface.h" #include "eigrp_network.h" #include "eigrp_zebra.h" /* Helper functions. */ static void redistribute_get_metrics(const struct lyd_node *dnode, struct eigrp_metrics *em) { memset(em, 0, sizeof(*em)); if (yang_dnode_exists(dnode, "./bandwidth")) em->bandwidth = yang_dnode_get_uint32(dnode, "./bandwidth"); if (yang_dnode_exists(dnode, "./delay")) em->delay = yang_dnode_get_uint32(dnode, "./delay"); #if 0 /* TODO: How does MTU work? */ if (yang_dnode_exists(dnode, "./mtu")) em->mtu[0] = yang_dnode_get_uint32(dnode, "./mtu"); #endif if (yang_dnode_exists(dnode, "./load")) em->load = yang_dnode_get_uint32(dnode, "./load"); if (yang_dnode_exists(dnode, "./reliability")) em->reliability = yang_dnode_get_uint32(dnode, "./reliability"); } static struct eigrp_interface *eigrp_interface_lookup(const struct eigrp *eigrp, const char *ifname) { struct eigrp_interface *eif; struct listnode *ln; for (ALL_LIST_ELEMENTS_RO(eigrp->eiflist, ln, eif)) { if (strcmp(ifname, eif->ifp->name)) continue; return eif; } return NULL; } /* * XPath: /frr-eigrpd:eigrpd/instance */ static int eigrpd_instance_create(enum nb_event event, const struct lyd_node *dnode, union nb_resource *resource) { struct eigrp *eigrp; const char *vrf; vrf_id_t vrfid; switch (event) { case NB_EV_VALIDATE: /* NOTHING */ break; case NB_EV_PREPARE: vrf = yang_dnode_get_string(dnode, "./vrf"); vrfid = vrf_name_to_id(vrf); eigrp = eigrp_get(yang_dnode_get_uint16(dnode, "./asn"), vrfid); resource->ptr = eigrp; break; case NB_EV_ABORT: eigrp_finish_final(resource->ptr); break; case NB_EV_APPLY: nb_running_set_entry(dnode, resource->ptr); break; } return NB_OK; } static int eigrpd_instance_destroy(enum nb_event event, const struct lyd_node *dnode) { struct eigrp *eigrp; switch (event) { case NB_EV_VALIDATE: case NB_EV_PREPARE: case NB_EV_ABORT: /* NOTHING */ break; case NB_EV_APPLY: eigrp = nb_running_unset_entry(dnode); eigrp_finish_final(eigrp); break; } return NB_OK; } /* * XPath: /frr-eigrpd:eigrpd/instance/router-id */ static int eigrpd_instance_router_id_modify(enum nb_event event, const struct lyd_node *dnode, union nb_resource *resource) { struct eigrp *eigrp; switch (event) { case NB_EV_VALIDATE: case NB_EV_PREPARE: case NB_EV_ABORT: /* NOTHING */ break; case NB_EV_APPLY: eigrp = nb_running_get_entry(dnode, NULL, true); yang_dnode_get_ipv4(&eigrp->router_id_static, dnode, NULL); break; } return NB_OK; } static int eigrpd_instance_router_id_destroy(enum nb_event event, const struct lyd_node *dnode) { struct eigrp *eigrp; switch (event) { case NB_EV_VALIDATE: case NB_EV_PREPARE: case NB_EV_ABORT: /* NOTHING */ break; case NB_EV_APPLY: eigrp = nb_running_get_entry(dnode, NULL, true); eigrp->router_id_static.s_addr = 0; break; } return NB_OK; } /* * XPath: /frr-eigrpd:eigrpd/instance/passive-interface */ static int eigrpd_instance_passive_interface_create(enum nb_event event, const struct lyd_node *dnode, union nb_resource *resource) { struct eigrp_interface *eif; struct eigrp *eigrp; const char *ifname; switch (event) { case NB_EV_VALIDATE: eigrp = nb_running_get_entry(dnode, NULL, false); if (eigrp == NULL) { /* * XXX: we can't verify if the interface exists * and is active until EIGRP is up. */ break; } ifname = yang_dnode_get_string(dnode, NULL); eif = eigrp_interface_lookup(eigrp, ifname); if (eif == NULL) return NB_ERR_INCONSISTENCY; break; case NB_EV_PREPARE: case NB_EV_ABORT: /* NOTHING */ break; case NB_EV_APPLY: eigrp = nb_running_get_entry(dnode, NULL, true); ifname = yang_dnode_get_string(dnode, NULL); eif = eigrp_interface_lookup(eigrp, ifname); if (eif == NULL) return NB_ERR_INCONSISTENCY; eif->params.passive_interface = EIGRP_IF_PASSIVE; break; } return NB_OK; } static int eigrpd_instance_passive_interface_destroy(enum nb_event event, const struct lyd_node *dnode) { struct eigrp_interface *eif; struct eigrp *eigrp; const char *ifname; switch (event) { case NB_EV_VALIDATE: case NB_EV_PREPARE: case NB_EV_ABORT: /* NOTHING */ break; case NB_EV_APPLY: eigrp = nb_running_get_entry(dnode, NULL, true); ifname = yang_dnode_get_string(dnode, NULL); eif = eigrp_interface_lookup(eigrp, ifname); if (eif == NULL) break; eif->params.passive_interface = EIGRP_IF_ACTIVE; break; } return NB_OK; } /* * XPath: /frr-eigrpd:eigrpd/instance/active-time */ static int eigrpd_instance_active_time_modify(enum nb_event event, const struct lyd_node *dnode, union nb_resource *resource) { switch (event) { case NB_EV_VALIDATE: /* TODO: Not implemented. */ return NB_ERR_INCONSISTENCY; case NB_EV_PREPARE: case NB_EV_ABORT: case NB_EV_APPLY: /* NOTHING */ break; } return NB_OK; } /* * XPath: /frr-eigrpd:eigrpd/instance/variance */ static int eigrpd_instance_variance_modify(enum nb_event event, const struct lyd_node *dnode, union nb_resource *resource) { struct eigrp *eigrp; switch (event) { case NB_EV_VALIDATE: case NB_EV_PREPARE: case NB_EV_ABORT: /* NOTHING */ break; case NB_EV_APPLY: eigrp = nb_running_get_entry(dnode, NULL, true); eigrp->variance = yang_dnode_get_uint8(dnode, NULL); break; } return NB_OK; } static int eigrpd_instance_variance_destroy(enum nb_event event, const struct lyd_node *dnode) { struct eigrp *eigrp; switch (event) { case NB_EV_VALIDATE: case NB_EV_PREPARE: case NB_EV_ABORT: /* NOTHING */ break; case NB_EV_APPLY: eigrp = nb_running_get_entry(dnode, NULL, true); eigrp->variance = EIGRP_VARIANCE_DEFAULT; break; } return NB_OK; } /* * XPath: /frr-eigrpd:eigrpd/instance/maximum-paths */ static int eigrpd_instance_maximum_paths_modify(enum nb_event event, const struct lyd_node *dnode, union nb_resource *resource) { struct eigrp *eigrp; switch (event) { case NB_EV_VALIDATE: case NB_EV_PREPARE: case NB_EV_ABORT: /* NOTHING */ break; case NB_EV_APPLY: eigrp = nb_running_get_entry(dnode, NULL, true); eigrp->max_paths = yang_dnode_get_uint8(dnode, NULL); break; } return NB_OK; } static int eigrpd_instance_maximum_paths_destroy(enum nb_event event, const struct lyd_node *dnode) { struct eigrp *eigrp; switch (event) { case NB_EV_VALIDATE: case NB_EV_PREPARE: case NB_EV_ABORT: /* NOTHING */ break; case NB_EV_APPLY: eigrp = nb_running_get_entry(dnode, NULL, true); eigrp->max_paths = EIGRP_MAX_PATHS_DEFAULT; break; } return NB_OK; } /* * XPath: /frr-eigrpd:eigrpd/instance/metric-weights/K1 */ static int eigrpd_instance_metric_weights_K1_modify(enum nb_event event, const struct lyd_node *dnode, union nb_resource *resource) { struct eigrp *eigrp; switch (event) { case NB_EV_VALIDATE: case NB_EV_PREPARE: case NB_EV_ABORT: /* NOTHING */ break; case NB_EV_APPLY: eigrp = nb_running_get_entry(dnode, NULL, true); eigrp->k_values[0] = yang_dnode_get_uint8(dnode, NULL); break; } return NB_OK; } static int eigrpd_instance_metric_weights_K1_destroy(enum nb_event event, const struct lyd_node *dnode) { struct eigrp *eigrp; switch (event) { case NB_EV_VALIDATE: case NB_EV_PREPARE: case NB_EV_ABORT: /* NOTHING */ break; case NB_EV_APPLY: eigrp = nb_running_get_entry(dnode, NULL, true); eigrp->k_values[0] = EIGRP_K1_DEFAULT; break; } return NB_OK; } /* * XPath: /frr-eigrpd:eigrpd/instance/metric-weights/K2 */ static int eigrpd_instance_metric_weights_K2_modify(enum nb_event event, const struct lyd_node *dnode, union nb_resource *resource) { struct eigrp *eigrp; switch (event) { case NB_EV_VALIDATE: case NB_EV_PREPARE: case NB_EV_ABORT: /* NOTHING */ break; case NB_EV_APPLY: eigrp = nb_running_get_entry(dnode, NULL, true); eigrp->k_values[1] = yang_dnode_get_uint8(dnode, NULL); break; } return NB_OK; } static int eigrpd_instance_metric_weights_K2_destroy(enum nb_event event, const struct lyd_node *dnode) { struct eigrp *eigrp; switch (event) { case NB_EV_VALIDATE: case NB_EV_PREPARE: case NB_EV_ABORT: /* NOTHING */ break; case NB_EV_APPLY: eigrp = nb_running_get_entry(dnode, NULL, true); eigrp->k_values[1] = EIGRP_K2_DEFAULT; break; } return NB_OK; } /* * XPath: /frr-eigrpd:eigrpd/instance/metric-weights/K3 */ static int eigrpd_instance_metric_weights_K3_modify(enum nb_event event, const struct lyd_node *dnode, union nb_resource *resource) { struct eigrp *eigrp; switch (event) { case NB_EV_VALIDATE: case NB_EV_PREPARE: case NB_EV_ABORT: /* NOTHING */ break; case NB_EV_APPLY: eigrp = nb_running_get_entry(dnode, NULL, true); eigrp->k_values[2] = yang_dnode_get_uint8(dnode, NULL); break; } return NB_OK; } static int eigrpd_instance_metric_weights_K3_destroy(enum nb_event event, const struct lyd_node *dnode) { struct eigrp *eigrp; switch (event) { case NB_EV_VALIDATE: case NB_EV_PREPARE: case NB_EV_ABORT: /* NOTHING */ break; case NB_EV_APPLY: eigrp = nb_running_get_entry(dnode, NULL, true); eigrp->k_values[2] = EIGRP_K3_DEFAULT; break; } return NB_OK; } /* * XPath: /frr-eigrpd:eigrpd/instance/metric-weights/K4 */ static int eigrpd_instance_metric_weights_K4_modify(enum nb_event event, const struct lyd_node *dnode, union nb_resource *resource) { struct eigrp *eigrp; switch (event) { case NB_EV_VALIDATE: case NB_EV_PREPARE: case NB_EV_ABORT: /* NOTHING */ break; case NB_EV_APPLY: eigrp = nb_running_get_entry(dnode, NULL, true); eigrp->k_values[3] = yang_dnode_get_uint8(dnode, NULL); break; } return NB_OK; } static int eigrpd_instance_metric_weights_K4_destroy(enum nb_event event, const struct lyd_node *dnode) { struct eigrp *eigrp; switch (event) { case NB_EV_VALIDATE: case NB_EV_PREPARE: case NB_EV_ABORT: /* NOTHING */ break; case NB_EV_APPLY: eigrp = nb_running_get_entry(dnode, NULL, true); eigrp->k_values[3] = EIGRP_K4_DEFAULT; break; } return NB_OK; } /* * XPath: /frr-eigrpd:eigrpd/instance/metric-weights/K5 */ static int eigrpd_instance_metric_weights_K5_modify(enum nb_event event, const struct lyd_node *dnode, union nb_resource *resource) { struct eigrp *eigrp; switch (event) { case NB_EV_VALIDATE: case NB_EV_PREPARE: case NB_EV_ABORT: /* NOTHING */ break; case NB_EV_APPLY: eigrp = nb_running_get_entry(dnode, NULL, true); eigrp->k_values[4] = yang_dnode_get_uint8(dnode, NULL); break; } return NB_OK; } static int eigrpd_instance_metric_weights_K5_destroy(enum nb_event event, const struct lyd_node *dnode) { struct eigrp *eigrp; switch (event) { case NB_EV_VALIDATE: case NB_EV_PREPARE: case NB_EV_ABORT: /* NOTHING */ break; case NB_EV_APPLY: eigrp = nb_running_get_entry(dnode, NULL, true); eigrp->k_values[4] = EIGRP_K5_DEFAULT; break; } return NB_OK; } /* * XPath: /frr-eigrpd:eigrpd/instance/metric-weights/K6 */ static int eigrpd_instance_metric_weights_K6_modify(enum nb_event event, const struct lyd_node *dnode, union nb_resource *resource) { struct eigrp *eigrp; switch (event) { case NB_EV_VALIDATE: case NB_EV_PREPARE: case NB_EV_ABORT: /* NOTHING */ break; case NB_EV_APPLY: eigrp = nb_running_get_entry(dnode, NULL, true); eigrp->k_values[5] = yang_dnode_get_uint8(dnode, NULL); break; } return NB_OK; } static int eigrpd_instance_metric_weights_K6_destroy(enum nb_event event, const struct lyd_node *dnode) { struct eigrp *eigrp; switch (event) { case NB_EV_VALIDATE: case NB_EV_PREPARE: case NB_EV_ABORT: /* NOTHING */ break; case NB_EV_APPLY: eigrp = nb_running_get_entry(dnode, NULL, true); eigrp->k_values[5] = EIGRP_K6_DEFAULT; break; } return NB_OK; } /* * XPath: /frr-eigrpd:eigrpd/instance/network */ static int eigrpd_instance_network_create(enum nb_event event, const struct lyd_node *dnode, union nb_resource *resource) { struct route_node *rnode; struct prefix prefix; struct eigrp *eigrp; int exists; yang_dnode_get_ipv4p(&prefix, dnode, NULL); switch (event) { case NB_EV_VALIDATE: eigrp = nb_running_get_entry(dnode, NULL, false); /* If entry doesn't exist it means the list is empty. */ if (eigrp == NULL) break; rnode = route_node_get(eigrp->networks, &prefix); exists = (rnode->info != NULL); route_unlock_node(rnode); if (exists) return NB_ERR_INCONSISTENCY; break; case NB_EV_PREPARE: case NB_EV_ABORT: /* NOTHING */ break; case NB_EV_APPLY: eigrp = nb_running_get_entry(dnode, NULL, true); if (eigrp_network_set(eigrp, &prefix) == 0) return NB_ERR_INCONSISTENCY; break; } return NB_OK; } static int eigrpd_instance_network_destroy(enum nb_event event, const struct lyd_node *dnode) { struct route_node *rnode; struct prefix prefix; struct eigrp *eigrp; int exists = 0; yang_dnode_get_ipv4p(&prefix, dnode, NULL); switch (event) { case NB_EV_VALIDATE: eigrp = nb_running_get_entry(dnode, NULL, false); /* If entry doesn't exist it means the list is empty. */ if (eigrp == NULL) break; rnode = route_node_get(eigrp->networks, &prefix); exists = (rnode->info != NULL); route_unlock_node(rnode); if (exists == 0) return NB_ERR_INCONSISTENCY; break; case NB_EV_PREPARE: case NB_EV_ABORT: /* NOTHING */ break; case NB_EV_APPLY: eigrp = nb_running_get_entry(dnode, NULL, true); eigrp_network_unset(eigrp, &prefix); break; } return NB_OK; } /* * XPath: /frr-eigrpd:eigrpd/instance/neighbor */ static int eigrpd_instance_neighbor_create(enum nb_event event, const struct lyd_node *dnode, union nb_resource *resource) { switch (event) { case NB_EV_VALIDATE: /* TODO: Not implemented. */ return NB_ERR_INCONSISTENCY; case NB_EV_PREPARE: case NB_EV_ABORT: case NB_EV_APPLY: /* NOTHING */ break; } return NB_OK; } static int eigrpd_instance_neighbor_destroy(enum nb_event event, const struct lyd_node *dnode) { switch (event) { case NB_EV_VALIDATE: /* TODO: Not implemented. */ return NB_ERR_INCONSISTENCY; case NB_EV_PREPARE: case NB_EV_ABORT: case NB_EV_APPLY: /* NOTHING */ break; } return NB_OK; } /* * XPath: /frr-eigrpd:eigrpd/instance/redistribute */ static int eigrpd_instance_redistribute_create(enum nb_event event, const struct lyd_node *dnode, union nb_resource *resource) { struct eigrp_metrics metrics; const char *vrfname; struct eigrp *eigrp; uint32_t proto; vrf_id_t vrfid; switch (event) { case NB_EV_VALIDATE: proto = yang_dnode_get_enum(dnode, "./protocol"); vrfname = yang_dnode_get_string(dnode, "../vrf"); vrfid = vrf_name_to_id(vrfname); if (vrf_bitmap_check(zclient->redist[AFI_IP][proto], vrfid)) return NB_ERR_INCONSISTENCY; break; case NB_EV_PREPARE: case NB_EV_ABORT: /* NOTHING */ break; case NB_EV_APPLY: eigrp = nb_running_get_entry(dnode, NULL, true); proto = yang_dnode_get_enum(dnode, "./protocol"); redistribute_get_metrics(dnode, &metrics); eigrp_redistribute_set(eigrp, proto, metrics); break; } return NB_OK; } static int eigrpd_instance_redistribute_destroy(enum nb_event event, const struct lyd_node *dnode) { struct eigrp *eigrp; uint32_t proto; switch (event) { case NB_EV_VALIDATE: case NB_EV_PREPARE: case NB_EV_ABORT: /* NOTHING */ break; case NB_EV_APPLY: eigrp = nb_running_get_entry(dnode, NULL, true); proto = yang_dnode_get_enum(dnode, "./protocol"); eigrp_redistribute_unset(eigrp, proto); break; } return NB_OK; } /* * XPath: /frr-eigrpd:eigrpd/instance/redistribute/route-map */ static int eigrpd_instance_redistribute_route_map_modify(enum nb_event event, const struct lyd_node *dnode, union nb_resource *resource) { switch (event) { case NB_EV_VALIDATE: /* TODO: Not implemented. */ return NB_ERR_INCONSISTENCY; case NB_EV_PREPARE: case NB_EV_ABORT: case NB_EV_APPLY: /* NOTHING */ break; } return NB_OK; } static int eigrpd_instance_redistribute_route_map_destroy(enum nb_event event, const struct lyd_node *dnode) { switch (event) { case NB_EV_VALIDATE: /* TODO: Not implemented. */ return NB_ERR_INCONSISTENCY; case NB_EV_PREPARE: case NB_EV_ABORT: case NB_EV_APPLY: /* NOTHING */ break; } return NB_OK; } /* * XPath: /frr-eigrpd:eigrpd/instance/redistribute/metrics/bandwidth */ static int eigrpd_instance_redistribute_metrics_bandwidth_modify( enum nb_event event, const struct lyd_node *dnode, union nb_resource *resource) { struct eigrp_metrics metrics; struct eigrp *eigrp; uint32_t proto; switch (event) { case NB_EV_VALIDATE: case NB_EV_PREPARE: case NB_EV_ABORT: /* NOTHING */ break; case NB_EV_APPLY: eigrp = nb_running_get_entry(dnode, NULL, true); proto = yang_dnode_get_enum(dnode, "../../protocol"); redistribute_get_metrics(dnode, &metrics); eigrp_redistribute_set(eigrp, proto, metrics); break; } return NB_OK; } static int eigrpd_instance_redistribute_metrics_bandwidth_destroy( enum nb_event event, const struct lyd_node *dnode) { struct eigrp_metrics metrics; struct eigrp *eigrp; uint32_t proto; switch (event) { case NB_EV_VALIDATE: case NB_EV_PREPARE: case NB_EV_ABORT: /* NOTHING */ break; case NB_EV_APPLY: eigrp = nb_running_get_entry(dnode, NULL, true); proto = yang_dnode_get_enum(dnode, "../../protocol"); redistribute_get_metrics(dnode, &metrics); eigrp_redistribute_set(eigrp, proto, metrics); break; } return NB_OK; } /* * XPath: /frr-eigrpd:eigrpd/instance/redistribute/metrics/delay */ static int eigrpd_instance_redistribute_metrics_delay_modify(enum nb_event event, const struct lyd_node *dnode, union nb_resource *resource) { return eigrpd_instance_redistribute_metrics_bandwidth_modify(event, dnode, resource); } static int eigrpd_instance_redistribute_metrics_delay_destroy(enum nb_event event, const struct lyd_node *dnode) { return eigrpd_instance_redistribute_metrics_bandwidth_destroy(event, dnode); } /* * XPath: /frr-eigrpd:eigrpd/instance/redistribute/metrics/reliability */ static int eigrpd_instance_redistribute_metrics_reliability_modify( enum nb_event event, const struct lyd_node *dnode, union nb_resource *resource) { return eigrpd_instance_redistribute_metrics_bandwidth_modify(event, dnode, resource); } static int eigrpd_instance_redistribute_metrics_reliability_destroy( enum nb_event event, const struct lyd_node *dnode) { return eigrpd_instance_redistribute_metrics_bandwidth_destroy(event, dnode); } /* * XPath: /frr-eigrpd:eigrpd/instance/redistribute/metrics/load */ static int eigrpd_instance_redistribute_metrics_load_modify(enum nb_event event, const struct lyd_node *dnode, union nb_resource *resource) { return eigrpd_instance_redistribute_metrics_bandwidth_modify(event, dnode, resource); } static int eigrpd_instance_redistribute_metrics_load_destroy(enum nb_event event, const struct lyd_node *dnode) { return eigrpd_instance_redistribute_metrics_bandwidth_destroy(event, dnode); } /* * XPath: /frr-eigrpd:eigrpd/instance/redistribute/metrics/mtu */ static int eigrpd_instance_redistribute_metrics_mtu_modify(enum nb_event event, const struct lyd_node *dnode, union nb_resource *resource) { return eigrpd_instance_redistribute_metrics_bandwidth_modify(event, dnode, resource); } static int eigrpd_instance_redistribute_metrics_mtu_destroy(enum nb_event event, const struct lyd_node *dnode) { return eigrpd_instance_redistribute_metrics_bandwidth_destroy(event, dnode); } /* * XPath: /frr-interface:lib/interface/frr-eigrpd:eigrp/delay */ static int lib_interface_eigrp_delay_modify(enum nb_event event, const struct lyd_node *dnode, union nb_resource *resource) { struct eigrp_interface *ei; struct interface *ifp; switch (event) { case NB_EV_VALIDATE: ifp = nb_running_get_entry(dnode, NULL, false); if (ifp == NULL) { /* * XXX: we can't verify if the interface exists * and is active until EIGRP is up. */ break; } ei = ifp->info; if (ei == NULL) return NB_ERR_INCONSISTENCY; break; case NB_EV_PREPARE: case NB_EV_ABORT: /* NOTHING */ break; case NB_EV_APPLY: ifp = nb_running_get_entry(dnode, NULL, true); ei = ifp->info; if (ei == NULL) return NB_ERR_INCONSISTENCY; ei->params.delay = yang_dnode_get_uint32(dnode, NULL); eigrp_if_reset(ifp); break; } return NB_OK; } /* * XPath: /frr-interface:lib/interface/frr-eigrpd:eigrp/bandwidth */ static int lib_interface_eigrp_bandwidth_modify(enum nb_event event, const struct lyd_node *dnode, union nb_resource *resource) { struct interface *ifp; struct eigrp_interface *ei; switch (event) { case NB_EV_VALIDATE: ifp = nb_running_get_entry(dnode, NULL, false); if (ifp == NULL) { /* * XXX: we can't verify if the interface exists * and is active until EIGRP is up. */ break; } ei = ifp->info; if (ei == NULL) return NB_ERR_INCONSISTENCY; break; case NB_EV_PREPARE: case NB_EV_ABORT: /* NOTHING */ break; case NB_EV_APPLY: ifp = nb_running_get_entry(dnode, NULL, true); ei = ifp->info; if (ei == NULL) return NB_ERR_INCONSISTENCY; ei->params.bandwidth = yang_dnode_get_uint32(dnode, NULL); eigrp_if_reset(ifp); break; } return NB_OK; } /* * XPath: /frr-interface:lib/interface/frr-eigrpd:eigrp/hello-interval */ static int lib_interface_eigrp_hello_interval_modify(enum nb_event event, const struct lyd_node *dnode, union nb_resource *resource) { struct interface *ifp; struct eigrp_interface *ei; switch (event) { case NB_EV_VALIDATE: ifp = nb_running_get_entry(dnode, NULL, false); if (ifp == NULL) { /* * XXX: we can't verify if the interface exists * and is active until EIGRP is up. */ break; } ei = ifp->info; if (ei == NULL) return NB_ERR_INCONSISTENCY; break; case NB_EV_PREPARE: case NB_EV_ABORT: /* NOTHING */ break; case NB_EV_APPLY: ifp = nb_running_get_entry(dnode, NULL, true); ei = ifp->info; if (ei == NULL) return NB_ERR_INCONSISTENCY; ei->params.v_hello = yang_dnode_get_uint16(dnode, NULL); break; } return NB_OK; } /* * XPath: /frr-interface:lib/interface/frr-eigrpd:eigrp/hold-time */ static int lib_interface_eigrp_hold_time_modify(enum nb_event event, const struct lyd_node *dnode, union nb_resource *resource) { struct interface *ifp; struct eigrp_interface *ei; switch (event) { case NB_EV_VALIDATE: ifp = nb_running_get_entry(dnode, NULL, false); if (ifp == NULL) { /* * XXX: we can't verify if the interface exists * and is active until EIGRP is up. */ break; } ei = ifp->info; if (ei == NULL) return NB_ERR_INCONSISTENCY; break; case NB_EV_PREPARE: case NB_EV_ABORT: /* NOTHING */ break; case NB_EV_APPLY: ifp = nb_running_get_entry(dnode, NULL, true); ei = ifp->info; if (ei == NULL) return NB_ERR_INCONSISTENCY; ei->params.v_wait = yang_dnode_get_uint16(dnode, NULL); break; } return NB_OK; } /* * XPath: /frr-interface:lib/interface/frr-eigrpd:eigrp/split-horizon */ static int lib_interface_eigrp_split_horizon_modify(enum nb_event event, const struct lyd_node *dnode, union nb_resource *resource) { switch (event) { case NB_EV_VALIDATE: /* TODO: Not implemented. */ return NB_ERR_INCONSISTENCY; case NB_EV_PREPARE: case NB_EV_ABORT: case NB_EV_APPLY: /* NOTHING */ break; } return NB_OK; } /* * XPath: /frr-interface:lib/interface/frr-eigrpd:eigrp/instance */ static int lib_interface_eigrp_instance_create(enum nb_event event, const struct lyd_node *dnode, union nb_resource *resource) { struct eigrp_interface *eif; struct interface *ifp; struct eigrp *eigrp; switch (event) { case NB_EV_VALIDATE: ifp = nb_running_get_entry(dnode, NULL, false); if (ifp == NULL) { /* * XXX: we can't verify if the interface exists * and is active until EIGRP is up. */ break; } eigrp = eigrp_get(yang_dnode_get_uint16(dnode, "./asn"), ifp->vrf_id); eif = eigrp_interface_lookup(eigrp, ifp->name); if (eif == NULL) return NB_ERR_INCONSISTENCY; break; case NB_EV_PREPARE: case NB_EV_ABORT: /* NOTHING */ break; case NB_EV_APPLY: ifp = nb_running_get_entry(dnode, NULL, true); eigrp = eigrp_get(yang_dnode_get_uint16(dnode, "./asn"), ifp->vrf_id); eif = eigrp_interface_lookup(eigrp, ifp->name); if (eif == NULL) return NB_ERR_INCONSISTENCY; nb_running_set_entry(dnode, eif); break; } return NB_OK; } static int lib_interface_eigrp_instance_destroy(enum nb_event event, const struct lyd_node *dnode) { switch (event) { case NB_EV_VALIDATE: case NB_EV_PREPARE: case NB_EV_ABORT: /* NOTHING */ break; case NB_EV_APPLY: nb_running_unset_entry(dnode); break; } return NB_OK; } /* * XPath: * /frr-interface:lib/interface/frr-eigrpd:eigrp/instance/summarize-addresses */ static int lib_interface_eigrp_instance_summarize_addresses_create( enum nb_event event, const struct lyd_node *dnode, union nb_resource *resource) { switch (event) { case NB_EV_VALIDATE: /* TODO: Not implemented. */ return NB_ERR_INCONSISTENCY; case NB_EV_PREPARE: case NB_EV_ABORT: case NB_EV_APPLY: /* NOTHING */ break; } return NB_OK; } static int lib_interface_eigrp_instance_summarize_addresses_destroy( enum nb_event event, const struct lyd_node *dnode) { switch (event) { case NB_EV_VALIDATE: /* TODO: Not implemented. */ return NB_ERR_INCONSISTENCY; case NB_EV_PREPARE: case NB_EV_ABORT: case NB_EV_APPLY: /* NOTHING */ break; } return NB_OK; } /* * XPath: /frr-interface:lib/interface/frr-eigrpd:eigrp/instance/authentication */ static int lib_interface_eigrp_instance_authentication_modify(enum nb_event event, const struct lyd_node *dnode, union nb_resource *resource) { struct eigrp_interface *eif; switch (event) { case NB_EV_VALIDATE: case NB_EV_PREPARE: case NB_EV_ABORT: /* NOTHING */ break; case NB_EV_APPLY: eif = nb_running_get_entry(dnode, NULL, true); eif->params.auth_type = yang_dnode_get_enum(dnode, NULL); break; } return NB_OK; } /* * XPath: /frr-interface:lib/interface/frr-eigrpd:eigrp/instance/keychain */ static int lib_interface_eigrp_instance_keychain_modify(enum nb_event event, const struct lyd_node *dnode, union nb_resource *resource) { struct eigrp_interface *eif; struct keychain *keychain; switch (event) { case NB_EV_VALIDATE: keychain = keychain_lookup(yang_dnode_get_string(dnode, NULL)); if (keychain == NULL) return NB_ERR_INCONSISTENCY; break; case NB_EV_PREPARE: resource->ptr = strdup(yang_dnode_get_string(dnode, NULL)); if (resource->ptr == NULL) return NB_ERR_RESOURCE; break; case NB_EV_ABORT: free(resource->ptr); resource->ptr = NULL; break; case NB_EV_APPLY: eif = nb_running_get_entry(dnode, NULL, true); if (eif->params.auth_keychain) free(eif->params.auth_keychain); eif->params.auth_keychain = resource->ptr; break; } return NB_OK; } static int lib_interface_eigrp_instance_keychain_destroy(enum nb_event event, const struct lyd_node *dnode) { struct eigrp_interface *eif; switch (event) { case NB_EV_VALIDATE: case NB_EV_PREPARE: case NB_EV_ABORT: /* NOTHING */ break; case NB_EV_APPLY: eif = nb_running_get_entry(dnode, NULL, true); if (eif->params.auth_keychain) free(eif->params.auth_keychain); eif->params.auth_keychain = NULL; break; } return NB_OK; } /* clang-format off */ const struct frr_yang_module_info frr_eigrpd_info = { .name = "frr-eigrpd", .nodes = { { .xpath = "/frr-eigrpd:eigrpd/instance", .cbs = { .create = eigrpd_instance_create, .destroy = eigrpd_instance_destroy, .cli_show = eigrp_cli_show_header, .cli_show_end = eigrp_cli_show_end_header, } }, { .xpath = "/frr-eigrpd:eigrpd/instance/router-id", .cbs = { .modify = eigrpd_instance_router_id_modify, .destroy = eigrpd_instance_router_id_destroy, .cli_show = eigrp_cli_show_router_id, } }, { .xpath = "/frr-eigrpd:eigrpd/instance/passive-interface", .cbs = { .create = eigrpd_instance_passive_interface_create, .destroy = eigrpd_instance_passive_interface_destroy, .cli_show = eigrp_cli_show_passive_interface, } }, { .xpath = "/frr-eigrpd:eigrpd/instance/active-time", .cbs = { .modify = eigrpd_instance_active_time_modify, .cli_show = eigrp_cli_show_active_time, } }, { .xpath = "/frr-eigrpd:eigrpd/instance/variance", .cbs = { .modify = eigrpd_instance_variance_modify, .destroy = eigrpd_instance_variance_destroy, .cli_show = eigrp_cli_show_variance, } }, { .xpath = "/frr-eigrpd:eigrpd/instance/maximum-paths", .cbs = { .modify = eigrpd_instance_maximum_paths_modify, .destroy = eigrpd_instance_maximum_paths_destroy, .cli_show = eigrp_cli_show_maximum_paths, } }, { .xpath = "/frr-eigrpd:eigrpd/instance/metric-weights", .cbs = { .cli_show = eigrp_cli_show_metrics, } }, { .xpath = "/frr-eigrpd:eigrpd/instance/metric-weights/K1", .cbs = { .modify = eigrpd_instance_metric_weights_K1_modify, .destroy = eigrpd_instance_metric_weights_K1_destroy, } }, { .xpath = "/frr-eigrpd:eigrpd/instance/metric-weights/K2", .cbs = { .modify = eigrpd_instance_metric_weights_K2_modify, .destroy = eigrpd_instance_metric_weights_K2_destroy, } }, { .xpath = "/frr-eigrpd:eigrpd/instance/metric-weights/K3", .cbs = { .modify = eigrpd_instance_metric_weights_K3_modify, .destroy = eigrpd_instance_metric_weights_K3_destroy, } }, { .xpath = "/frr-eigrpd:eigrpd/instance/metric-weights/K4", .cbs = { .modify = eigrpd_instance_metric_weights_K4_modify, .destroy = eigrpd_instance_metric_weights_K4_destroy, } }, { .xpath = "/frr-eigrpd:eigrpd/instance/metric-weights/K5", .cbs = { .modify = eigrpd_instance_metric_weights_K5_modify, .destroy = eigrpd_instance_metric_weights_K5_destroy, } }, { .xpath = "/frr-eigrpd:eigrpd/instance/metric-weights/K6", .cbs = { .modify = eigrpd_instance_metric_weights_K6_modify, .destroy = eigrpd_instance_metric_weights_K6_destroy, } }, { .xpath = "/frr-eigrpd:eigrpd/instance/network", .cbs = { .create = eigrpd_instance_network_create, .destroy = eigrpd_instance_network_destroy, .cli_show = eigrp_cli_show_network, } }, { .xpath = "/frr-eigrpd:eigrpd/instance/neighbor", .cbs = { .create = eigrpd_instance_neighbor_create, .destroy = eigrpd_instance_neighbor_destroy, .cli_show = eigrp_cli_show_neighbor, } }, { .xpath = "/frr-eigrpd:eigrpd/instance/redistribute", .cbs = { .create = eigrpd_instance_redistribute_create, .destroy = eigrpd_instance_redistribute_destroy, .cli_show = eigrp_cli_show_redistribute, } }, { .xpath = "/frr-eigrpd:eigrpd/instance/redistribute/route-map", .cbs = { .modify = eigrpd_instance_redistribute_route_map_modify, .destroy = eigrpd_instance_redistribute_route_map_destroy, } }, { .xpath = "/frr-eigrpd:eigrpd/instance/redistribute/metrics/bandwidth", .cbs = { .modify = eigrpd_instance_redistribute_metrics_bandwidth_modify, .destroy = eigrpd_instance_redistribute_metrics_bandwidth_destroy, } }, { .xpath = "/frr-eigrpd:eigrpd/instance/redistribute/metrics/delay", .cbs = { .modify = eigrpd_instance_redistribute_metrics_delay_modify, .destroy = eigrpd_instance_redistribute_metrics_delay_destroy, } }, { .xpath = "/frr-eigrpd:eigrpd/instance/redistribute/metrics/reliability", .cbs = { .modify = eigrpd_instance_redistribute_metrics_reliability_modify, .destroy = eigrpd_instance_redistribute_metrics_reliability_destroy, } }, { .xpath = "/frr-eigrpd:eigrpd/instance/redistribute/metrics/load", .cbs = { .modify = eigrpd_instance_redistribute_metrics_load_modify, .destroy = eigrpd_instance_redistribute_metrics_load_destroy, } }, { .xpath = "/frr-eigrpd:eigrpd/instance/redistribute/metrics/mtu", .cbs = { .modify = eigrpd_instance_redistribute_metrics_mtu_modify, .destroy = eigrpd_instance_redistribute_metrics_mtu_destroy, } }, { .xpath = "/frr-interface:lib/interface/frr-eigrpd:eigrp/delay", .cbs = { .modify = lib_interface_eigrp_delay_modify, .cli_show = eigrp_cli_show_delay, } }, { .xpath = "/frr-interface:lib/interface/frr-eigrpd:eigrp/bandwidth", .cbs = { .modify = lib_interface_eigrp_bandwidth_modify, .cli_show = eigrp_cli_show_bandwidth, } }, { .xpath = "/frr-interface:lib/interface/frr-eigrpd:eigrp/hello-interval", .cbs = { .modify = lib_interface_eigrp_hello_interval_modify, .cli_show = eigrp_cli_show_hello_interval, } }, { .xpath = "/frr-interface:lib/interface/frr-eigrpd:eigrp/hold-time", .cbs = { .modify = lib_interface_eigrp_hold_time_modify, .cli_show = eigrp_cli_show_hold_time, } }, { .xpath = "/frr-interface:lib/interface/frr-eigrpd:eigrp/split-horizon", .cbs = { .modify = lib_interface_eigrp_split_horizon_modify, } }, { .xpath = "/frr-interface:lib/interface/frr-eigrpd:eigrp/instance", .cbs = { .create = lib_interface_eigrp_instance_create, .destroy = lib_interface_eigrp_instance_destroy, } }, { .xpath = "/frr-interface:lib/interface/frr-eigrpd:eigrp/instance/summarize-addresses", .cbs = { .create = lib_interface_eigrp_instance_summarize_addresses_create, .destroy = lib_interface_eigrp_instance_summarize_addresses_destroy, .cli_show = eigrp_cli_show_summarize_address, } }, { .xpath = "/frr-interface:lib/interface/frr-eigrpd:eigrp/instance/authentication", .cbs = { .modify = lib_interface_eigrp_instance_authentication_modify, .cli_show = eigrp_cli_show_authentication, } }, { .xpath = "/frr-interface:lib/interface/frr-eigrpd:eigrp/instance/keychain", .cbs = { .modify = lib_interface_eigrp_instance_keychain_modify, .destroy = lib_interface_eigrp_instance_keychain_destroy, .cli_show = eigrp_cli_show_keychain, } }, { .xpath = NULL, }, } }; frr-7.2.1/eigrpd/eigrp_packet.c0000644000000000000000000010650513610377563013276 00000000000000/* * EIGRP General Sending and Receiving of EIGRP Packets. * Copyright (C) 2013-2014 * Authors: * Donnie Savage * Jan Janovic * Matej Perina * Peter Orsag * Peter Paluch * * This file is part of GNU Zebra. * * GNU Zebra is free software; you can redistribute it and/or modify it * under the terms of the GNU General Public License as published by the * Free Software Foundation; either version 2, or (at your option) any * later version. * * GNU Zebra 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; see the file COPYING; if not, write to the Free Software * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA */ #include #include "thread.h" #include "memory.h" #include "linklist.h" #include "vty.h" #include "keychain.h" #include "prefix.h" #include "if.h" #include "table.h" #include "sockunion.h" #include "stream.h" #include "log.h" #include "sockopt.h" #include "checksum.h" #include "md5.h" #include "sha256.h" #include "lib_errors.h" #include "eigrpd/eigrp_structs.h" #include "eigrpd/eigrpd.h" #include "eigrpd/eigrp_interface.h" #include "eigrpd/eigrp_neighbor.h" #include "eigrpd/eigrp_packet.h" #include "eigrpd/eigrp_zebra.h" #include "eigrpd/eigrp_vty.h" #include "eigrpd/eigrp_dump.h" #include "eigrpd/eigrp_macros.h" #include "eigrpd/eigrp_network.h" #include "eigrpd/eigrp_topology.h" #include "eigrpd/eigrp_fsm.h" #include "eigrpd/eigrp_memory.h" #include "eigrpd/eigrp_errors.h" /* Packet Type String. */ const struct message eigrp_packet_type_str[] = { {EIGRP_OPC_UPDATE, "Update"}, {EIGRP_OPC_REQUEST, "Request"}, {EIGRP_OPC_QUERY, "Query"}, {EIGRP_OPC_REPLY, "Reply"}, {EIGRP_OPC_HELLO, "Hello"}, {EIGRP_OPC_IPXSAP, "IPX-SAP"}, {EIGRP_OPC_PROBE, "Probe"}, {EIGRP_OPC_ACK, "Ack"}, {EIGRP_OPC_SIAQUERY, "SIAQuery"}, {EIGRP_OPC_SIAREPLY, "SIAReply"}, {0}}; static unsigned char zeropad[16] = {0}; /* Forward function reference*/ static struct stream *eigrp_recv_packet(struct eigrp *eigrp, int fd, struct interface **ifp, struct stream *s); static int eigrp_verify_header(struct stream *s, struct eigrp_interface *ei, struct ip *addr, struct eigrp_header *header); static int eigrp_check_network_mask(struct eigrp_interface *ei, struct in_addr mask); static int eigrp_retrans_count_exceeded(struct eigrp_packet *ep, struct eigrp_neighbor *nbr) { return 1; } int eigrp_make_md5_digest(struct eigrp_interface *ei, struct stream *s, uint8_t flags) { struct key *key = NULL; struct keychain *keychain; unsigned char digest[EIGRP_AUTH_TYPE_MD5_LEN]; MD5_CTX ctx; uint8_t *ibuf; size_t backup_get, backup_end; struct TLV_MD5_Authentication_Type *auth_TLV; ibuf = s->data; backup_end = s->endp; backup_get = s->getp; auth_TLV = eigrp_authTLV_MD5_new(); stream_set_getp(s, EIGRP_HEADER_LEN); stream_get(auth_TLV, s, EIGRP_AUTH_MD5_TLV_SIZE); stream_set_getp(s, backup_get); keychain = keychain_lookup(ei->params.auth_keychain); if (keychain) key = key_lookup_for_send(keychain); else { eigrp_authTLV_MD5_free(auth_TLV); return EIGRP_AUTH_TYPE_NONE; } memset(&ctx, 0, sizeof(ctx)); MD5Init(&ctx); /* Generate a digest. Each situation needs different handling */ if (flags & EIGRP_AUTH_BASIC_HELLO_FLAG) { MD5Update(&ctx, ibuf, EIGRP_MD5_BASIC_COMPUTE); MD5Update(&ctx, key->string, strlen(key->string)); if (strlen(key->string) < 16) MD5Update(&ctx, zeropad, 16 - strlen(key->string)); } else if (flags & EIGRP_AUTH_UPDATE_INIT_FLAG) { MD5Update(&ctx, ibuf, EIGRP_MD5_UPDATE_INIT_COMPUTE); } else if (flags & EIGRP_AUTH_UPDATE_FLAG) { MD5Update(&ctx, ibuf, EIGRP_MD5_BASIC_COMPUTE); MD5Update(&ctx, key->string, strlen(key->string)); if (strlen(key->string) < 16) MD5Update(&ctx, zeropad, 16 - strlen(key->string)); if (backup_end > (EIGRP_HEADER_LEN + EIGRP_AUTH_MD5_TLV_SIZE)) { MD5Update(&ctx, ibuf + (EIGRP_HEADER_LEN + EIGRP_AUTH_MD5_TLV_SIZE), backup_end - 20 - (EIGRP_HEADER_LEN + EIGRP_AUTH_MD5_TLV_SIZE)); } } MD5Final(digest, &ctx); /* Append md5 digest to the end of the stream. */ memcpy(auth_TLV->digest, digest, EIGRP_AUTH_TYPE_MD5_LEN); stream_set_endp(s, EIGRP_HEADER_LEN); stream_put(s, auth_TLV, EIGRP_AUTH_MD5_TLV_SIZE); stream_set_endp(s, backup_end); eigrp_authTLV_MD5_free(auth_TLV); return EIGRP_AUTH_TYPE_MD5_LEN; } int eigrp_check_md5_digest(struct stream *s, struct TLV_MD5_Authentication_Type *authTLV, struct eigrp_neighbor *nbr, uint8_t flags) { MD5_CTX ctx; unsigned char digest[EIGRP_AUTH_TYPE_MD5_LEN]; unsigned char orig[EIGRP_AUTH_TYPE_MD5_LEN]; struct key *key = NULL; struct keychain *keychain; uint8_t *ibuf; size_t backup_end; struct TLV_MD5_Authentication_Type *auth_TLV; struct eigrp_header *eigrph; if (ntohl(nbr->crypt_seqnum) > ntohl(authTLV->key_sequence)) { zlog_warn( "interface %s: eigrp_check_md5 bad sequence %d (expect %d)", IF_NAME(nbr->ei), ntohl(authTLV->key_sequence), ntohl(nbr->crypt_seqnum)); return 0; } eigrph = (struct eigrp_header *)s->data; eigrph->checksum = 0; auth_TLV = (struct TLV_MD5_Authentication_Type *)(s->data + EIGRP_HEADER_LEN); memcpy(orig, auth_TLV->digest, EIGRP_AUTH_TYPE_MD5_LEN); memset(digest, 0, EIGRP_AUTH_TYPE_MD5_LEN); memset(auth_TLV->digest, 0, EIGRP_AUTH_TYPE_MD5_LEN); ibuf = s->data; backup_end = s->endp; keychain = keychain_lookup(nbr->ei->params.auth_keychain); if (keychain) key = key_lookup_for_send(keychain); if (!key) { zlog_warn( "Interface %s: Expected key value not found in config", nbr->ei->ifp->name); return 0; } memset(&ctx, 0, sizeof(ctx)); MD5Init(&ctx); /* Generate a digest. Each situation needs different handling */ if (flags & EIGRP_AUTH_BASIC_HELLO_FLAG) { MD5Update(&ctx, ibuf, EIGRP_MD5_BASIC_COMPUTE); MD5Update(&ctx, key->string, strlen(key->string)); if (strlen(key->string) < 16) MD5Update(&ctx, zeropad, 16 - strlen(key->string)); } else if (flags & EIGRP_AUTH_UPDATE_INIT_FLAG) { MD5Update(&ctx, ibuf, EIGRP_MD5_UPDATE_INIT_COMPUTE); } else if (flags & EIGRP_AUTH_UPDATE_FLAG) { MD5Update(&ctx, ibuf, EIGRP_MD5_BASIC_COMPUTE); MD5Update(&ctx, key->string, strlen(key->string)); if (strlen(key->string) < 16) MD5Update(&ctx, zeropad, 16 - strlen(key->string)); if (backup_end > (EIGRP_HEADER_LEN + EIGRP_AUTH_MD5_TLV_SIZE)) { MD5Update(&ctx, ibuf + (EIGRP_HEADER_LEN + EIGRP_AUTH_MD5_TLV_SIZE), backup_end - 20 - (EIGRP_HEADER_LEN + EIGRP_AUTH_MD5_TLV_SIZE)); } } MD5Final(digest, &ctx); /* compare the two */ if (memcmp(orig, digest, EIGRP_AUTH_TYPE_MD5_LEN) != 0) { zlog_warn("interface %s: eigrp_check_md5 checksum mismatch", IF_NAME(nbr->ei)); return 0; } /* save neighbor's crypt_seqnum */ nbr->crypt_seqnum = authTLV->key_sequence; return 1; } int eigrp_make_sha256_digest(struct eigrp_interface *ei, struct stream *s, uint8_t flags) { struct key *key = NULL; struct keychain *keychain; char source_ip[PREFIX_STRLEN]; unsigned char digest[EIGRP_AUTH_TYPE_SHA256_LEN]; unsigned char buffer[1 + PLAINTEXT_LENGTH + 45 + 1] = {0}; HMAC_SHA256_CTX ctx; void *ibuf; size_t backup_get, backup_end; struct TLV_SHA256_Authentication_Type *auth_TLV; ibuf = s->data; backup_end = s->endp; backup_get = s->getp; auth_TLV = eigrp_authTLV_SHA256_new(); stream_set_getp(s, EIGRP_HEADER_LEN); stream_get(auth_TLV, s, EIGRP_AUTH_SHA256_TLV_SIZE); stream_set_getp(s, backup_get); keychain = keychain_lookup(ei->params.auth_keychain); if (keychain) key = key_lookup_for_send(keychain); if (!key) { zlog_warn( "Interface %s: Expected key value not found in config", ei->ifp->name); eigrp_authTLV_SHA256_free(auth_TLV); return 0; } inet_ntop(AF_INET, &ei->address.u.prefix4, source_ip, PREFIX_STRLEN); memset(&ctx, 0, sizeof(ctx)); buffer[0] = '\n'; memcpy(buffer + 1, key, strlen(key->string)); memcpy(buffer + 1 + strlen(key->string), source_ip, strlen(source_ip)); HMAC__SHA256_Init(&ctx, buffer, 1 + strlen(key->string) + strlen(source_ip)); HMAC__SHA256_Update(&ctx, ibuf, strlen(ibuf)); HMAC__SHA256_Final(digest, &ctx); /* Put hmac-sha256 digest to it's place */ memcpy(auth_TLV->digest, digest, EIGRP_AUTH_TYPE_SHA256_LEN); stream_set_endp(s, EIGRP_HEADER_LEN); stream_put(s, auth_TLV, EIGRP_AUTH_SHA256_TLV_SIZE); stream_set_endp(s, backup_end); eigrp_authTLV_SHA256_free(auth_TLV); return EIGRP_AUTH_TYPE_SHA256_LEN; } int eigrp_check_sha256_digest(struct stream *s, struct TLV_SHA256_Authentication_Type *authTLV, struct eigrp_neighbor *nbr, uint8_t flags) { return 1; } int eigrp_write(struct thread *thread) { struct eigrp *eigrp = THREAD_ARG(thread); struct eigrp_header *eigrph; struct eigrp_interface *ei; struct eigrp_packet *ep; struct sockaddr_in sa_dst; struct ip iph; struct msghdr msg; struct iovec iov[2]; uint32_t seqno, ack; int ret; int flags = 0; struct listnode *node; #ifdef WANT_EIGRP_WRITE_FRAGMENT static uint16_t ipid = 0; #endif /* WANT_EIGRP_WRITE_FRAGMENT */ #define EIGRP_WRITE_IPHL_SHIFT 2 eigrp->t_write = NULL; node = listhead(eigrp->oi_write_q); assert(node); ei = listgetdata(node); assert(ei); #ifdef WANT_EIGRP_WRITE_FRAGMENT /* seed ipid static with low order bits of time */ if (ipid == 0) ipid = (time(NULL) & 0xffff); #endif /* WANT_EIGRP_WRITE_FRAGMENT */ /* Get one packet from queue. */ ep = eigrp_fifo_next(ei->obuf); if (!ep) { flog_err(EC_LIB_DEVELOPMENT, "%s: Interface %s no packet on queue?", __PRETTY_FUNCTION__, ei->ifp->name); goto out; } if (ep->length < EIGRP_HEADER_LEN) { flog_err(EC_EIGRP_PACKET, "%s: Packet just has a header?", __PRETTY_FUNCTION__); eigrp_header_dump((struct eigrp_header *)ep->s->data); eigrp_packet_delete(ei); goto out; } if (ep->dst.s_addr == htonl(EIGRP_MULTICAST_ADDRESS)) eigrp_if_ipmulticast(eigrp, &ei->address, ei->ifp->ifindex); memset(&iph, 0, sizeof(struct ip)); memset(&sa_dst, 0, sizeof(sa_dst)); /* * We build and schedule packets to go out * in the future. In the mean time we may * process some update packets from the * neighbor, thus making it necessary * to update the ack we are using for * this outgoing packet. */ eigrph = (struct eigrp_header *)STREAM_DATA(ep->s); seqno = ntohl(eigrph->sequence); ack = ntohl(eigrph->ack); if (ep->nbr && (ack != ep->nbr->recv_sequence_number)) { eigrph->ack = htonl(ep->nbr->recv_sequence_number); ack = ep->nbr->recv_sequence_number; eigrph->checksum = 0; eigrp_packet_checksum(ei, ep->s, ep->length); } sa_dst.sin_family = AF_INET; #ifdef HAVE_STRUCT_SOCKADDR_IN_SIN_LEN sa_dst.sin_len = sizeof(sa_dst); #endif /* HAVE_STRUCT_SOCKADDR_IN_SIN_LEN */ sa_dst.sin_addr = ep->dst; sa_dst.sin_port = htons(0); /* Set DONTROUTE flag if dst is unicast. */ if (!IN_MULTICAST(htonl(ep->dst.s_addr))) flags = MSG_DONTROUTE; iph.ip_hl = sizeof(struct ip) >> EIGRP_WRITE_IPHL_SHIFT; /* it'd be very strange for header to not be 4byte-word aligned but.. */ if (sizeof(struct ip) > (unsigned int)(iph.ip_hl << EIGRP_WRITE_IPHL_SHIFT)) iph.ip_hl++; /* we presume sizeof struct ip cant overflow ip_hl.. */ iph.ip_v = IPVERSION; iph.ip_tos = IPTOS_PREC_INTERNETCONTROL; iph.ip_len = (iph.ip_hl << EIGRP_WRITE_IPHL_SHIFT) + ep->length; #if defined(__DragonFly__) /* * DragonFly's raw socket expects ip_len/ip_off in network byte order. */ iph.ip_len = htons(iph.ip_len); #endif iph.ip_off = 0; iph.ip_ttl = EIGRP_IP_TTL; iph.ip_p = IPPROTO_EIGRPIGP; iph.ip_sum = 0; iph.ip_src.s_addr = ei->address.u.prefix4.s_addr; iph.ip_dst.s_addr = ep->dst.s_addr; memset(&msg, 0, sizeof(msg)); msg.msg_name = (caddr_t)&sa_dst; msg.msg_namelen = sizeof(sa_dst); msg.msg_iov = iov; msg.msg_iovlen = 2; iov[0].iov_base = (char *)&iph; iov[0].iov_len = iph.ip_hl << EIGRP_WRITE_IPHL_SHIFT; iov[1].iov_base = stream_pnt(ep->s); iov[1].iov_len = ep->length; /* send final fragment (could be first) */ sockopt_iphdrincl_swab_htosys(&iph); ret = sendmsg(eigrp->fd, &msg, flags); sockopt_iphdrincl_swab_systoh(&iph); if (IS_DEBUG_EIGRP_TRANSMIT(0, SEND)) { eigrph = (struct eigrp_header *)STREAM_DATA(ep->s); zlog_debug( "Sending [%s][%d/%d] to [%s] via [%s] ret [%d].", lookup_msg(eigrp_packet_type_str, eigrph->opcode, NULL), seqno, ack, inet_ntoa(ep->dst), IF_NAME(ei), ret); } if (ret < 0) zlog_warn( "*** sendmsg in eigrp_write failed to %s, " "id %d, off %d, len %d, interface %s, mtu %u: %s", inet_ntoa(iph.ip_dst), iph.ip_id, iph.ip_off, iph.ip_len, ei->ifp->name, ei->ifp->mtu, safe_strerror(errno)); /* Now delete packet from queue. */ eigrp_packet_delete(ei); out: if (eigrp_fifo_next(ei->obuf) == NULL) { ei->on_write_q = 0; list_delete_node(eigrp->oi_write_q, node); } /* If packets still remain in queue, call write thread. */ if (!list_isempty(eigrp->oi_write_q)) { eigrp->t_write = NULL; thread_add_write(master, eigrp_write, eigrp, eigrp->fd, &eigrp->t_write); } return 0; } /* Starting point of packet process function. */ int eigrp_read(struct thread *thread) { int ret; struct stream *ibuf; struct eigrp *eigrp; struct eigrp_interface *ei; struct ip *iph; struct eigrp_header *eigrph; struct interface *ifp; struct eigrp_neighbor *nbr; struct in_addr srcaddr; uint16_t opcode = 0; uint16_t length = 0; /* first of all get interface pointer. */ eigrp = THREAD_ARG(thread); /* prepare for next packet. */ eigrp->t_read = NULL; thread_add_read(master, eigrp_read, eigrp, eigrp->fd, &eigrp->t_read); stream_reset(eigrp->ibuf); if (!(ibuf = eigrp_recv_packet(eigrp, eigrp->fd, &ifp, eigrp->ibuf))) { /* This raw packet is known to be at least as big as its IP * header. */ return -1; } /* Note that there should not be alignment problems with this assignment because this is at the beginning of the stream data buffer. */ iph = (struct ip *)STREAM_DATA(ibuf); // Substract IPv4 header size from EIGRP Packet itself if (iph->ip_v == 4) length = (iph->ip_len) - 20U; srcaddr = iph->ip_src; /* IP Header dump. */ if (IS_DEBUG_EIGRP_TRANSMIT(0, RECV) && IS_DEBUG_EIGRP_TRANSMIT(0, PACKET_DETAIL)) eigrp_ip_header_dump(iph); /* Note that sockopt_iphdrincl_swab_systoh was called in * eigrp_recv_packet. */ if (ifp == NULL) { struct connected *c; /* Handle cases where the platform does not support retrieving the ifindex, and also platforms (such as Solaris 8) that claim to support ifindex retrieval but do not. */ c = if_lookup_address((void *)&srcaddr, AF_INET, eigrp->vrf_id); if (c == NULL) return 0; ifp = c->ifp; } /* associate packet with eigrp interface */ ei = ifp->info; /* eigrp_verify_header() relies on a valid "ei" and thus can be called only after the checks below are passed. These checks in turn access the fields of unverified "eigrph" structure for their own purposes and must remain very accurate in doing this. */ if (!ei) return 0; /* Self-originated packet should be discarded silently. */ if (eigrp_if_lookup_by_local_addr(eigrp, NULL, iph->ip_src) || (IPV4_ADDR_SAME(&srcaddr, &ei->address.u.prefix4))) { if (IS_DEBUG_EIGRP_TRANSMIT(0, RECV)) zlog_debug( "eigrp_read[%s]: Dropping self-originated packet", inet_ntoa(srcaddr)); return 0; } /* Advance from IP header to EIGRP header (iph->ip_hl has been verified by eigrp_recv_packet() to be correct). */ stream_forward_getp(ibuf, (iph->ip_hl * 4)); eigrph = (struct eigrp_header *)stream_pnt(ibuf); if (IS_DEBUG_EIGRP_TRANSMIT(0, RECV) && IS_DEBUG_EIGRP_TRANSMIT(0, PACKET_DETAIL)) eigrp_header_dump(eigrph); // if (MSG_OK != eigrp_packet_examin(eigrph, stream_get_endp(ibuf) - // stream_get_getp(ibuf))) // return -1; /* If incoming interface is passive one, ignore it. */ if (eigrp_if_is_passive(ei)) { char buf[3][INET_ADDRSTRLEN]; if (IS_DEBUG_EIGRP_TRANSMIT(0, RECV)) zlog_debug( "ignoring packet from router %s sent to %s, " "received on a passive interface, %s", inet_ntop(AF_INET, &eigrph->vrid, buf[0], sizeof(buf[0])), inet_ntop(AF_INET, &iph->ip_dst, buf[1], sizeof(buf[1])), inet_ntop(AF_INET, &ei->address.u.prefix4, buf[2], sizeof(buf[2]))); if (iph->ip_dst.s_addr == htonl(EIGRP_MULTICAST_ADDRESS)) { eigrp_if_set_multicast(ei); } return 0; } /* else it must be a local eigrp interface, check it was received on * correct link */ else if (ei->ifp != ifp) { if (IS_DEBUG_EIGRP_TRANSMIT(0, RECV)) zlog_warn("Packet from [%s] received on wrong link %s", inet_ntoa(iph->ip_src), ifp->name); return 0; } /* Verify more EIGRP header fields. */ ret = eigrp_verify_header(ibuf, ei, iph, eigrph); if (ret < 0) { if (IS_DEBUG_EIGRP_TRANSMIT(0, RECV)) zlog_debug( "eigrp_read[%s]: Header check failed, dropping.", inet_ntoa(iph->ip_src)); return ret; } /* calcualte the eigrp packet length, and move the pounter to the start of the eigrp TLVs */ opcode = eigrph->opcode; if (IS_DEBUG_EIGRP_TRANSMIT(0, RECV)) { char src[PREFIX_STRLEN], dst[PREFIX_STRLEN]; strlcpy(src, inet_ntoa(iph->ip_src), sizeof(src)); strlcpy(dst, inet_ntoa(iph->ip_dst), sizeof(dst)); zlog_debug( "Received [%s][%d/%d] length [%u] via [%s] src [%s] dst [%s]", lookup_msg(eigrp_packet_type_str, opcode, NULL), ntohl(eigrph->sequence), ntohl(eigrph->ack), length, IF_NAME(ei), src, dst); } /* Read rest of the packet and call each sort of packet routine. */ stream_forward_getp(ibuf, EIGRP_HEADER_LEN); /* New testing block of code for handling Acks */ if (ntohl(eigrph->ack) != 0) { struct eigrp_packet *ep = NULL; nbr = eigrp_nbr_get(ei, eigrph, iph); // neighbor must be valid, eigrp_nbr_get creates if none existed assert(nbr); ep = eigrp_fifo_next(nbr->retrans_queue); if ((ep) && (ntohl(eigrph->ack) == ep->sequence_number)) { ep = eigrp_fifo_pop(nbr->retrans_queue); eigrp_packet_free(ep); if ((nbr->state == EIGRP_NEIGHBOR_PENDING) && (ntohl(eigrph->ack) == nbr->init_sequence_number)) { eigrp_nbr_state_set(nbr, EIGRP_NEIGHBOR_UP); zlog_info("Neighbor(%s) adjacency became full", inet_ntoa(nbr->src)); nbr->init_sequence_number = 0; nbr->recv_sequence_number = ntohl(eigrph->sequence); eigrp_update_send_EOT(nbr); } else eigrp_send_packet_reliably(nbr); } ep = eigrp_fifo_next(nbr->multicast_queue); if (ep) { if (ntohl(eigrph->ack) == ep->sequence_number) { ep = eigrp_fifo_pop(nbr->multicast_queue); eigrp_packet_free(ep); if (nbr->multicast_queue->count > 0) { eigrp_send_packet_reliably(nbr); } } } } switch (opcode) { case EIGRP_OPC_HELLO: eigrp_hello_receive(eigrp, iph, eigrph, ibuf, ei, length); break; case EIGRP_OPC_PROBE: // eigrp_probe_receive(eigrp, iph, eigrph, ibuf, ei, // length); break; case EIGRP_OPC_QUERY: eigrp_query_receive(eigrp, iph, eigrph, ibuf, ei, length); break; case EIGRP_OPC_REPLY: eigrp_reply_receive(eigrp, iph, eigrph, ibuf, ei, length); break; case EIGRP_OPC_REQUEST: // eigrp_request_receive(eigrp, iph, eigrph, ibuf, ei, // length); break; case EIGRP_OPC_SIAQUERY: eigrp_query_receive(eigrp, iph, eigrph, ibuf, ei, length); break; case EIGRP_OPC_SIAREPLY: eigrp_reply_receive(eigrp, iph, eigrph, ibuf, ei, length); break; case EIGRP_OPC_UPDATE: eigrp_update_receive(eigrp, iph, eigrph, ibuf, ei, length); break; default: zlog_warn( "interface %s: EIGRP packet header type %d unsupported", IF_NAME(ei), opcode); break; } return 0; } static struct stream *eigrp_recv_packet(struct eigrp *eigrp, int fd, struct interface **ifp, struct stream *ibuf) { int ret; struct ip *iph; uint16_t ip_len; unsigned int ifindex = 0; struct iovec iov; /* Header and data both require alignment. */ char buff[CMSG_SPACE(SOPT_SIZE_CMSG_IFINDEX_IPV4())]; struct msghdr msgh; memset(&msgh, 0, sizeof(struct msghdr)); msgh.msg_iov = &iov; msgh.msg_iovlen = 1; msgh.msg_control = (caddr_t)buff; msgh.msg_controllen = sizeof(buff); ret = stream_recvmsg(ibuf, fd, &msgh, 0, (EIGRP_PACKET_MAX_LEN + 1)); if (ret < 0) { zlog_warn("stream_recvmsg failed: %s", safe_strerror(errno)); return NULL; } if ((unsigned int)ret < sizeof(*iph)) /* ret must be > 0 now */ { zlog_warn( "eigrp_recv_packet: discarding runt packet of length %d " "(ip header size is %u)", ret, (unsigned int)sizeof(*iph)); return NULL; } /* Note that there should not be alignment problems with this assignment because this is at the beginning of the stream data buffer. */ iph = (struct ip *)STREAM_DATA(ibuf); sockopt_iphdrincl_swab_systoh(iph); ip_len = iph->ip_len; #if !defined(GNU_LINUX) && (OpenBSD < 200311) && (__FreeBSD_version < 1000000) /* * Kernel network code touches incoming IP header parameters, * before protocol specific processing. * * 1) Convert byteorder to host representation. * --> ip_len, ip_id, ip_off * * 2) Adjust ip_len to strip IP header size! * --> If user process receives entire IP packet via RAW * socket, it must consider adding IP header size to * the "ip_len" field of "ip" structure. * * For more details, see . */ ip_len = ip_len + (iph->ip_hl << 2); #endif #if defined(__DragonFly__) /* * in DragonFly's raw socket, ip_len/ip_off are read * in network byte order. * As OpenBSD < 200311 adjust ip_len to strip IP header size! */ ip_len = ntohs(iph->ip_len) + (iph->ip_hl << 2); #endif ifindex = getsockopt_ifindex(AF_INET, &msgh); *ifp = if_lookup_by_index(ifindex, eigrp->vrf_id); if (ret != ip_len) { zlog_warn( "eigrp_recv_packet read length mismatch: ip_len is %d, " "but recvmsg returned %d", ip_len, ret); return NULL; } return ibuf; } struct eigrp_fifo *eigrp_fifo_new(void) { struct eigrp_fifo *new; new = XCALLOC(MTYPE_EIGRP_FIFO, sizeof(struct eigrp_fifo)); return new; } /* Free eigrp packet fifo. */ void eigrp_fifo_free(struct eigrp_fifo *fifo) { struct eigrp_packet *ep; struct eigrp_packet *next; for (ep = fifo->head; ep; ep = next) { next = ep->next; eigrp_packet_free(ep); } fifo->head = fifo->tail = NULL; fifo->count = 0; XFREE(MTYPE_EIGRP_FIFO, fifo); } /* Free eigrp fifo entries without destroying fifo itself*/ void eigrp_fifo_reset(struct eigrp_fifo *fifo) { struct eigrp_packet *ep; struct eigrp_packet *next; for (ep = fifo->head; ep; ep = next) { next = ep->next; eigrp_packet_free(ep); } fifo->head = fifo->tail = NULL; fifo->count = 0; } struct eigrp_packet *eigrp_packet_new(size_t size, struct eigrp_neighbor *nbr) { struct eigrp_packet *new; new = XCALLOC(MTYPE_EIGRP_PACKET, sizeof(struct eigrp_packet)); new->s = stream_new(size); new->retrans_counter = 0; new->nbr = nbr; return new; } void eigrp_send_packet_reliably(struct eigrp_neighbor *nbr) { struct eigrp_packet *ep; ep = eigrp_fifo_next(nbr->retrans_queue); if (ep) { struct eigrp_packet *duplicate; duplicate = eigrp_packet_duplicate(ep, nbr); /* Add packet to the top of the interface output queue*/ eigrp_fifo_push(nbr->ei->obuf, duplicate); /*Start retransmission timer*/ thread_add_timer(master, eigrp_unack_packet_retrans, nbr, EIGRP_PACKET_RETRANS_TIME, &ep->t_retrans_timer); /*Increment sequence number counter*/ nbr->ei->eigrp->sequence_number++; /* Hook thread to write packet. */ if (nbr->ei->on_write_q == 0) { listnode_add(nbr->ei->eigrp->oi_write_q, nbr->ei); nbr->ei->on_write_q = 1; } thread_add_write(master, eigrp_write, nbr->ei->eigrp, nbr->ei->eigrp->fd, &nbr->ei->eigrp->t_write); } } /* Calculate EIGRP checksum */ void eigrp_packet_checksum(struct eigrp_interface *ei, struct stream *s, uint16_t length) { struct eigrp_header *eigrph; eigrph = (struct eigrp_header *)STREAM_DATA(s); /* Calculate checksum. */ eigrph->checksum = in_cksum(eigrph, length); } /* Make EIGRP header. */ void eigrp_packet_header_init(int type, struct eigrp *eigrp, struct stream *s, uint32_t flags, uint32_t sequence, uint32_t ack) { struct eigrp_header *eigrph; stream_reset(s); eigrph = (struct eigrp_header *)STREAM_DATA(s); eigrph->version = (uint8_t)EIGRP_HEADER_VERSION; eigrph->opcode = (uint8_t)type; eigrph->checksum = 0; eigrph->vrid = htons(eigrp->vrid); eigrph->ASNumber = htons(eigrp->AS); eigrph->ack = htonl(ack); eigrph->sequence = htonl(sequence); // if(flags == EIGRP_INIT_FLAG) // eigrph->sequence = htonl(3); eigrph->flags = htonl(flags); if (IS_DEBUG_EIGRP_TRANSMIT(0, PACKET_DETAIL)) zlog_debug("Packet Header Init Seq [%u] Ack [%u]", htonl(eigrph->sequence), htonl(eigrph->ack)); stream_forward_endp(s, EIGRP_HEADER_LEN); } /* Add new packet to head of fifo. */ void eigrp_fifo_push(struct eigrp_fifo *fifo, struct eigrp_packet *ep) { ep->next = fifo->head; ep->previous = NULL; if (fifo->tail == NULL) fifo->tail = ep; if (fifo->count != 0) fifo->head->previous = ep; fifo->head = ep; fifo->count++; } /* Return last fifo entry. */ struct eigrp_packet *eigrp_fifo_next(struct eigrp_fifo *fifo) { return fifo->tail; } void eigrp_packet_delete(struct eigrp_interface *ei) { struct eigrp_packet *ep; ep = eigrp_fifo_pop(ei->obuf); if (ep) eigrp_packet_free(ep); } void eigrp_packet_free(struct eigrp_packet *ep) { if (ep->s) stream_free(ep->s); THREAD_OFF(ep->t_retrans_timer); XFREE(MTYPE_EIGRP_PACKET, ep); } /* EIGRP Header verification. */ static int eigrp_verify_header(struct stream *ibuf, struct eigrp_interface *ei, struct ip *iph, struct eigrp_header *eigrph) { /* Check network mask, Silently discarded. */ if (!eigrp_check_network_mask(ei, iph->ip_src)) { zlog_warn( "interface %s: eigrp_read network address is not same [%s]", IF_NAME(ei), inet_ntoa(iph->ip_src)); return -1; } // // /* Check authentication. The function handles logging actions, where // required. */ // if (! eigrp_check_auth(ei, eigrph)) // return -1; return 0; } /* Unbound socket will accept any Raw IP packets if proto is matched. To prevent it, compare src IP address and i/f address with masking i/f network mask. */ static int eigrp_check_network_mask(struct eigrp_interface *ei, struct in_addr ip_src) { struct in_addr mask, me, him; if (ei->type == EIGRP_IFTYPE_POINTOPOINT) return 1; masklen2ip(ei->address.prefixlen, &mask); me.s_addr = ei->address.u.prefix4.s_addr & mask.s_addr; him.s_addr = ip_src.s_addr & mask.s_addr; if (IPV4_ADDR_SAME(&me, &him)) return 1; return 0; } int eigrp_unack_packet_retrans(struct thread *thread) { struct eigrp_neighbor *nbr; nbr = (struct eigrp_neighbor *)THREAD_ARG(thread); struct eigrp_packet *ep; ep = eigrp_fifo_next(nbr->retrans_queue); if (ep) { struct eigrp_packet *duplicate; duplicate = eigrp_packet_duplicate(ep, nbr); /* Add packet to the top of the interface output queue*/ eigrp_fifo_push(nbr->ei->obuf, duplicate); ep->retrans_counter++; if (ep->retrans_counter == EIGRP_PACKET_RETRANS_MAX) return eigrp_retrans_count_exceeded(ep, nbr); /*Start retransmission timer*/ ep->t_retrans_timer = NULL; thread_add_timer(master, eigrp_unack_packet_retrans, nbr, EIGRP_PACKET_RETRANS_TIME, &ep->t_retrans_timer); /* Hook thread to write packet. */ if (nbr->ei->on_write_q == 0) { listnode_add(nbr->ei->eigrp->oi_write_q, nbr->ei); nbr->ei->on_write_q = 1; } thread_add_write(master, eigrp_write, nbr->ei->eigrp, nbr->ei->eigrp->fd, &nbr->ei->eigrp->t_write); } return 0; } int eigrp_unack_multicast_packet_retrans(struct thread *thread) { struct eigrp_neighbor *nbr; nbr = (struct eigrp_neighbor *)THREAD_ARG(thread); struct eigrp_packet *ep; ep = eigrp_fifo_next(nbr->multicast_queue); if (ep) { struct eigrp_packet *duplicate; duplicate = eigrp_packet_duplicate(ep, nbr); /* Add packet to the top of the interface output queue*/ eigrp_fifo_push(nbr->ei->obuf, duplicate); ep->retrans_counter++; if (ep->retrans_counter == EIGRP_PACKET_RETRANS_MAX) return eigrp_retrans_count_exceeded(ep, nbr); /*Start retransmission timer*/ ep->t_retrans_timer = NULL; thread_add_timer(master, eigrp_unack_multicast_packet_retrans, nbr, EIGRP_PACKET_RETRANS_TIME, &ep->t_retrans_timer); /* Hook thread to write packet. */ if (nbr->ei->on_write_q == 0) { listnode_add(nbr->ei->eigrp->oi_write_q, nbr->ei); nbr->ei->on_write_q = 1; } thread_add_write(master, eigrp_write, nbr->ei->eigrp, nbr->ei->eigrp->fd, &nbr->ei->eigrp->t_write); } return 0; } /* Get packet from tail of fifo. */ struct eigrp_packet *eigrp_fifo_pop(struct eigrp_fifo *fifo) { struct eigrp_packet *ep = NULL; ep = fifo->tail; if (ep) { fifo->tail = ep->previous; if (fifo->tail == NULL) fifo->head = NULL; else fifo->tail->next = NULL; fifo->count--; } return ep; } struct eigrp_packet *eigrp_packet_duplicate(struct eigrp_packet *old, struct eigrp_neighbor *nbr) { struct eigrp_packet *new; new = eigrp_packet_new(EIGRP_PACKET_MTU(nbr->ei->ifp->mtu), nbr); new->length = old->length; new->retrans_counter = old->retrans_counter; new->dst = old->dst; new->sequence_number = old->sequence_number; stream_copy(new->s, old->s); return new; } static struct TLV_IPv4_Internal_type *eigrp_IPv4_InternalTLV_new(void) { struct TLV_IPv4_Internal_type *new; new = XCALLOC(MTYPE_EIGRP_IPV4_INT_TLV, sizeof(struct TLV_IPv4_Internal_type)); return new; } struct TLV_IPv4_Internal_type *eigrp_read_ipv4_tlv(struct stream *s) { struct TLV_IPv4_Internal_type *tlv; uint32_t destination_tmp; tlv = eigrp_IPv4_InternalTLV_new(); tlv->type = stream_getw(s); tlv->length = stream_getw(s); tlv->forward.s_addr = stream_getl(s); tlv->metric.delay = stream_getl(s); tlv->metric.bandwidth = stream_getl(s); tlv->metric.mtu[0] = stream_getc(s); tlv->metric.mtu[1] = stream_getc(s); tlv->metric.mtu[2] = stream_getc(s); tlv->metric.hop_count = stream_getc(s); tlv->metric.reliability = stream_getc(s); tlv->metric.load = stream_getc(s); tlv->metric.tag = stream_getc(s); tlv->metric.flags = stream_getc(s); tlv->prefix_length = stream_getc(s); destination_tmp = stream_getc(s) << 24; if (tlv->prefix_length > 8) destination_tmp |= stream_getc(s) << 16; if (tlv->prefix_length > 16) destination_tmp |= stream_getc(s) << 8; if (tlv->prefix_length > 24) destination_tmp |= stream_getc(s); tlv->destination.s_addr = htonl(destination_tmp); return tlv; } uint16_t eigrp_add_internalTLV_to_stream(struct stream *s, struct eigrp_prefix_entry *pe) { uint16_t length; stream_putw(s, EIGRP_TLV_IPv4_INT); switch (pe->destination->prefixlen) { case 0: case 1: case 2: case 3: case 4: case 5: case 6: case 7: case 8: length = EIGRP_TLV_IPV4_SIZE_GRT_0_BIT; stream_putw(s, length); break; case 9: case 10: case 11: case 12: case 13: case 14: case 15: case 16: length = EIGRP_TLV_IPV4_SIZE_GRT_8_BIT; stream_putw(s, length); break; case 17: case 18: case 19: case 20: case 21: case 22: case 23: case 24: length = EIGRP_TLV_IPV4_SIZE_GRT_16_BIT; stream_putw(s, length); break; case 25: case 26: case 27: case 28: case 29: case 30: case 31: case 32: length = EIGRP_TLV_IPV4_SIZE_GRT_24_BIT; stream_putw(s, length); break; default: flog_err(EC_LIB_DEVELOPMENT, "%s: Unexpected prefix length: %d", __PRETTY_FUNCTION__, pe->destination->prefixlen); return 0; } stream_putl(s, 0x00000000); /*Metric*/ stream_putl(s, pe->reported_metric.delay); stream_putl(s, pe->reported_metric.bandwidth); stream_putc(s, pe->reported_metric.mtu[2]); stream_putc(s, pe->reported_metric.mtu[1]); stream_putc(s, pe->reported_metric.mtu[0]); stream_putc(s, pe->reported_metric.hop_count); stream_putc(s, pe->reported_metric.reliability); stream_putc(s, pe->reported_metric.load); stream_putc(s, pe->reported_metric.tag); stream_putc(s, pe->reported_metric.flags); stream_putc(s, pe->destination->prefixlen); stream_putc(s, (ntohl(pe->destination->u.prefix4.s_addr) >> 24) & 0xFF); if (pe->destination->prefixlen > 8) stream_putc(s, (ntohl(pe->destination->u.prefix4.s_addr) >> 16) & 0xFF); if (pe->destination->prefixlen > 16) stream_putc(s, (ntohl(pe->destination->u.prefix4.s_addr) >> 8) & 0xFF); if (pe->destination->prefixlen > 24) stream_putc(s, ntohl(pe->destination->u.prefix4.s_addr) & 0xFF); return length; } uint16_t eigrp_add_authTLV_MD5_to_stream(struct stream *s, struct eigrp_interface *ei) { struct key *key; struct keychain *keychain; struct TLV_MD5_Authentication_Type *authTLV; authTLV = eigrp_authTLV_MD5_new(); authTLV->type = htons(EIGRP_TLV_AUTH); authTLV->length = htons(EIGRP_AUTH_MD5_TLV_SIZE); authTLV->auth_type = htons(EIGRP_AUTH_TYPE_MD5); authTLV->auth_length = htons(EIGRP_AUTH_TYPE_MD5_LEN); authTLV->key_sequence = 0; memset(authTLV->Nullpad, 0, sizeof(authTLV->Nullpad)); keychain = keychain_lookup(ei->params.auth_keychain); if (keychain) key = key_lookup_for_send(keychain); else { free(ei->params.auth_keychain); ei->params.auth_keychain = NULL; eigrp_authTLV_MD5_free(authTLV); return 0; } if (key) { authTLV->key_id = htonl(key->index); memset(authTLV->digest, 0, EIGRP_AUTH_TYPE_MD5_LEN); stream_put(s, authTLV, sizeof(struct TLV_MD5_Authentication_Type)); eigrp_authTLV_MD5_free(authTLV); return EIGRP_AUTH_MD5_TLV_SIZE; } eigrp_authTLV_MD5_free(authTLV); return 0; } uint16_t eigrp_add_authTLV_SHA256_to_stream(struct stream *s, struct eigrp_interface *ei) { struct key *key; struct keychain *keychain; struct TLV_SHA256_Authentication_Type *authTLV; authTLV = eigrp_authTLV_SHA256_new(); authTLV->type = htons(EIGRP_TLV_AUTH); authTLV->length = htons(EIGRP_AUTH_SHA256_TLV_SIZE); authTLV->auth_type = htons(EIGRP_AUTH_TYPE_SHA256); authTLV->auth_length = htons(EIGRP_AUTH_TYPE_SHA256_LEN); authTLV->key_sequence = 0; memset(authTLV->Nullpad, 0, sizeof(authTLV->Nullpad)); keychain = keychain_lookup(ei->params.auth_keychain); if (keychain) key = key_lookup_for_send(keychain); else { free(ei->params.auth_keychain); ei->params.auth_keychain = NULL; eigrp_authTLV_SHA256_free(authTLV); return 0; } if (key) { authTLV->key_id = 0; memset(authTLV->digest, 0, EIGRP_AUTH_TYPE_SHA256_LEN); stream_put(s, authTLV, sizeof(struct TLV_SHA256_Authentication_Type)); eigrp_authTLV_SHA256_free(authTLV); return EIGRP_AUTH_SHA256_TLV_SIZE; } eigrp_authTLV_SHA256_free(authTLV); return 0; } struct TLV_MD5_Authentication_Type *eigrp_authTLV_MD5_new(void) { struct TLV_MD5_Authentication_Type *new; new = XCALLOC(MTYPE_EIGRP_AUTH_TLV, sizeof(struct TLV_MD5_Authentication_Type)); return new; } void eigrp_authTLV_MD5_free(struct TLV_MD5_Authentication_Type *authTLV) { XFREE(MTYPE_EIGRP_AUTH_TLV, authTLV); } struct TLV_SHA256_Authentication_Type *eigrp_authTLV_SHA256_new(void) { struct TLV_SHA256_Authentication_Type *new; new = XCALLOC(MTYPE_EIGRP_AUTH_SHA256_TLV, sizeof(struct TLV_SHA256_Authentication_Type)); return new; } void eigrp_authTLV_SHA256_free(struct TLV_SHA256_Authentication_Type *authTLV) { XFREE(MTYPE_EIGRP_AUTH_SHA256_TLV, authTLV); } void eigrp_IPv4_InternalTLV_free( struct TLV_IPv4_Internal_type *IPv4_InternalTLV) { XFREE(MTYPE_EIGRP_IPV4_INT_TLV, IPv4_InternalTLV); } struct TLV_Sequence_Type *eigrp_SequenceTLV_new(void) { struct TLV_Sequence_Type *new; new = XCALLOC(MTYPE_EIGRP_SEQ_TLV, sizeof(struct TLV_Sequence_Type)); return new; } frr-7.2.1/eigrpd/eigrp_packet.h0000644000000000000000000001421013610377563013272 00000000000000/* * EIGRP General Sending and Receiving of EIGRP Packets. * Copyright (C) 2013-2016 * Authors: * Donnie Savage * Jan Janovic * Matej Perina * Peter Orsag * Peter Paluch * Frantisek Gazo * Tomas Hvorkovy * Martin Kontsek * Lukas Koribsky * * This file is part of GNU Zebra. * * GNU Zebra is free software; you can redistribute it and/or modify it * under the terms of the GNU General Public License as published by the * Free Software Foundation; either version 2, or (at your option) any * later version. * * GNU Zebra 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; see the file COPYING; if not, write to the Free Software * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA */ #ifndef _ZEBRA_EIGRP_PACKET_H #define _ZEBRA_EIGRP_PACKET_H /*Prototypes*/ extern int eigrp_read(struct thread *); extern int eigrp_write(struct thread *); extern struct eigrp_packet *eigrp_packet_new(size_t, struct eigrp_neighbor *); extern struct eigrp_packet *eigrp_packet_duplicate(struct eigrp_packet *, struct eigrp_neighbor *); extern void eigrp_packet_free(struct eigrp_packet *); extern void eigrp_packet_delete(struct eigrp_interface *); extern void eigrp_packet_header_init(int, struct eigrp *, struct stream *, uint32_t, uint32_t, uint32_t); extern void eigrp_packet_checksum(struct eigrp_interface *, struct stream *, uint16_t); extern struct eigrp_fifo *eigrp_fifo_new(void); extern struct eigrp_packet *eigrp_fifo_next(struct eigrp_fifo *); extern struct eigrp_packet *eigrp_fifo_pop(struct eigrp_fifo *); extern void eigrp_fifo_push(struct eigrp_fifo *, struct eigrp_packet *); extern void eigrp_fifo_free(struct eigrp_fifo *); extern void eigrp_fifo_reset(struct eigrp_fifo *); extern void eigrp_send_packet_reliably(struct eigrp_neighbor *); extern struct TLV_IPv4_Internal_type *eigrp_read_ipv4_tlv(struct stream *); extern uint16_t eigrp_add_internalTLV_to_stream(struct stream *, struct eigrp_prefix_entry *); extern uint16_t eigrp_add_authTLV_MD5_to_stream(struct stream *, struct eigrp_interface *); extern uint16_t eigrp_add_authTLV_SHA256_to_stream(struct stream *, struct eigrp_interface *); extern int eigrp_unack_packet_retrans(struct thread *); extern int eigrp_unack_multicast_packet_retrans(struct thread *); /* * untill there is reason to have their own header, these externs are found in * eigrp_hello.c */ extern void eigrp_sw_version_initialize(void); extern void eigrp_hello_send(struct eigrp_interface *, uint8_t, struct in_addr *); extern void eigrp_hello_send_ack(struct eigrp_neighbor *); extern void eigrp_hello_receive(struct eigrp *, struct ip *, struct eigrp_header *, struct stream *, struct eigrp_interface *, int); extern int eigrp_hello_timer(struct thread *); /* * These externs are found in eigrp_update.c */ extern bool eigrp_update_prefix_apply(struct eigrp *eigrp, struct eigrp_interface *ei, int in, struct prefix *prefix); extern void eigrp_update_send(struct eigrp_interface *); extern void eigrp_update_receive(struct eigrp *, struct ip *, struct eigrp_header *, struct stream *, struct eigrp_interface *, int); extern void eigrp_update_send_all(struct eigrp *, struct eigrp_interface *); extern void eigrp_update_send_init(struct eigrp_neighbor *); extern void eigrp_update_send_EOT(struct eigrp_neighbor *); extern int eigrp_update_send_GR_thread(struct thread *); extern void eigrp_update_send_GR(struct eigrp_neighbor *, enum GR_type, struct vty *); extern void eigrp_update_send_interface_GR(struct eigrp_interface *, enum GR_type, struct vty *); extern void eigrp_update_send_process_GR(struct eigrp *, enum GR_type, struct vty *); /* * These externs are found in eigrp_query.c */ extern void eigrp_send_query(struct eigrp_interface *); extern void eigrp_query_receive(struct eigrp *, struct ip *, struct eigrp_header *, struct stream *, struct eigrp_interface *, int); extern uint32_t eigrp_query_send_all(struct eigrp *); /* * These externs are found in eigrp_reply.c */ extern void eigrp_send_reply(struct eigrp_neighbor *, struct eigrp_prefix_entry *); extern void eigrp_reply_receive(struct eigrp *, struct ip *, struct eigrp_header *, struct stream *, struct eigrp_interface *, int); /* * These externs are found in eigrp_siaquery.c */ extern void eigrp_send_siaquery(struct eigrp_neighbor *, struct eigrp_prefix_entry *); extern void eigrp_siaquery_receive(struct eigrp *, struct ip *, struct eigrp_header *, struct stream *, struct eigrp_interface *, int); /* * These externs are found in eigrp_siareply.c */ extern void eigrp_send_siareply(struct eigrp_neighbor *, struct eigrp_prefix_entry *); extern void eigrp_siareply_receive(struct eigrp *, struct ip *, struct eigrp_header *, struct stream *, struct eigrp_interface *, int); extern struct TLV_MD5_Authentication_Type *eigrp_authTLV_MD5_new(void); extern void eigrp_authTLV_MD5_free(struct TLV_MD5_Authentication_Type *); extern struct TLV_SHA256_Authentication_Type *eigrp_authTLV_SHA256_new(void); extern void eigrp_authTLV_SHA256_free(struct TLV_SHA256_Authentication_Type *); extern int eigrp_make_md5_digest(struct eigrp_interface *, struct stream *, uint8_t); extern int eigrp_check_md5_digest(struct stream *, struct TLV_MD5_Authentication_Type *, struct eigrp_neighbor *, uint8_t); extern int eigrp_make_sha256_digest(struct eigrp_interface *, struct stream *, uint8_t); extern int eigrp_check_sha256_digest(struct stream *, struct TLV_SHA256_Authentication_Type *, struct eigrp_neighbor *, uint8_t); extern void eigrp_IPv4_InternalTLV_free(struct TLV_IPv4_Internal_type *); extern struct TLV_Sequence_Type *eigrp_SequenceTLV_new(void); extern const struct message eigrp_packet_type_str[]; extern const size_t eigrp_packet_type_str_max; #endif /* _ZEBRA_EIGRP_PACKET_H */ frr-7.2.1/eigrpd/eigrp_query.c0000644000000000000000000001563313610377563013175 00000000000000/* * EIGRP Sending and Receiving EIGRP Query Packets. * Copyright (C) 2013-2014 * Authors: * Donnie Savage * Jan Janovic * Matej Perina * Peter Orsag * Peter Paluch * * This file is part of GNU Zebra. * * GNU Zebra is free software; you can redistribute it and/or modify it * under the terms of the GNU General Public License as published by the * Free Software Foundation; either version 2, or (at your option) any * later version. * * GNU Zebra 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; see the file COPYING; if not, write to the Free Software * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA */ #include #include "thread.h" #include "memory.h" #include "linklist.h" #include "prefix.h" #include "if.h" #include "table.h" #include "sockunion.h" #include "stream.h" #include "log.h" #include "sockopt.h" #include "checksum.h" #include "md5.h" #include "vty.h" #include "eigrpd/eigrp_structs.h" #include "eigrpd/eigrpd.h" #include "eigrpd/eigrp_interface.h" #include "eigrpd/eigrp_neighbor.h" #include "eigrpd/eigrp_packet.h" #include "eigrpd/eigrp_zebra.h" #include "eigrpd/eigrp_vty.h" #include "eigrpd/eigrp_dump.h" #include "eigrpd/eigrp_macros.h" #include "eigrpd/eigrp_topology.h" #include "eigrpd/eigrp_fsm.h" #include "eigrpd/eigrp_memory.h" uint32_t eigrp_query_send_all(struct eigrp *eigrp) { struct eigrp_interface *iface; struct listnode *node, *node2, *nnode2; struct eigrp_prefix_entry *pe; uint32_t counter; if (eigrp == NULL) { zlog_debug("EIGRP Routing Process not enabled"); return 0; } counter = 0; for (ALL_LIST_ELEMENTS_RO(eigrp->eiflist, node, iface)) { eigrp_send_query(iface); counter++; } for (ALL_LIST_ELEMENTS(eigrp->topology_changes_internalIPV4, node2, nnode2, pe)) { if (pe->req_action & EIGRP_FSM_NEED_QUERY) { pe->req_action &= ~EIGRP_FSM_NEED_QUERY; listnode_delete(eigrp->topology_changes_internalIPV4, pe); } } return counter; } /*EIGRP QUERY read function*/ void eigrp_query_receive(struct eigrp *eigrp, struct ip *iph, struct eigrp_header *eigrph, struct stream *s, struct eigrp_interface *ei, int size) { struct eigrp_neighbor *nbr; struct TLV_IPv4_Internal_type *tlv; struct prefix dest_addr; uint16_t type; uint16_t length; /* increment statistics. */ ei->query_in++; /* get neighbor struct */ nbr = eigrp_nbr_get(ei, eigrph, iph); /* neighbor must be valid, eigrp_nbr_get creates if none existed */ assert(nbr); nbr->recv_sequence_number = ntohl(eigrph->sequence); while (s->endp > s->getp) { type = stream_getw(s); switch (type) { case EIGRP_TLV_IPv4_INT: stream_set_getp(s, s->getp - sizeof(uint16_t)); tlv = eigrp_read_ipv4_tlv(s); dest_addr.family = AF_INET; dest_addr.u.prefix4 = tlv->destination; dest_addr.prefixlen = tlv->prefix_length; struct eigrp_prefix_entry *dest = eigrp_topology_table_lookup_ipv4( eigrp->topology_table, &dest_addr); /* If the destination exists (it should, but one never * know)*/ if (dest != NULL) { struct eigrp_fsm_action_message msg; struct eigrp_nexthop_entry *entry = eigrp_prefix_entry_lookup(dest->entries, nbr); msg.packet_type = EIGRP_OPC_QUERY; msg.eigrp = eigrp; msg.data_type = EIGRP_INT; msg.adv_router = nbr; msg.metrics = tlv->metric; msg.entry = entry; msg.prefix = dest; eigrp_fsm_event(&msg); } eigrp_IPv4_InternalTLV_free(tlv); break; case EIGRP_TLV_IPv4_EXT: /* DVS: processing of external routes needs packet and fsm work. * for now, lets just not creash the box */ default: length = stream_getw(s); // -2 for type, -2 for len for (length -= 4; length; length--) { (void)stream_getc(s); } } } eigrp_hello_send_ack(nbr); eigrp_query_send_all(eigrp); eigrp_update_send_all(eigrp, nbr->ei); } void eigrp_send_query(struct eigrp_interface *ei) { struct eigrp_packet *ep = NULL; uint16_t length = EIGRP_HEADER_LEN; struct listnode *node, *nnode, *node2, *nnode2; struct eigrp_neighbor *nbr; struct eigrp_prefix_entry *pe; bool has_tlv = false; bool new_packet = true; uint16_t eigrp_mtu = EIGRP_PACKET_MTU(ei->ifp->mtu); for (ALL_LIST_ELEMENTS(ei->eigrp->topology_changes_internalIPV4, node, nnode, pe)) { if (!(pe->req_action & EIGRP_FSM_NEED_QUERY)) continue; if (new_packet) { ep = eigrp_packet_new(eigrp_mtu, NULL); /* Prepare EIGRP INIT UPDATE header */ eigrp_packet_header_init(EIGRP_OPC_QUERY, ei->eigrp, ep->s, 0, ei->eigrp->sequence_number, 0); // encode Authentication TLV, if needed if ((ei->params.auth_type == EIGRP_AUTH_TYPE_MD5) && (ei->params.auth_keychain != NULL)) { length += eigrp_add_authTLV_MD5_to_stream(ep->s, ei); } new_packet = false; } length += eigrp_add_internalTLV_to_stream(ep->s, pe); has_tlv = true; for (ALL_LIST_ELEMENTS(ei->nbrs, node2, nnode2, nbr)) { if (nbr->state == EIGRP_NEIGHBOR_UP) listnode_add(pe->rij, nbr); } if (length + EIGRP_TLV_MAX_IPV4_BYTE > eigrp_mtu) { if ((ei->params.auth_type == EIGRP_AUTH_TYPE_MD5) && ei->params.auth_keychain != NULL) { eigrp_make_md5_digest(ei, ep->s, EIGRP_AUTH_UPDATE_FLAG); } eigrp_packet_checksum(ei, ep->s, length); ep->length = length; ep->dst.s_addr = htonl(EIGRP_MULTICAST_ADDRESS); ep->sequence_number = ei->eigrp->sequence_number; ei->eigrp->sequence_number++; for (ALL_LIST_ELEMENTS(ei->nbrs, node2, nnode2, nbr)) { struct eigrp_packet *dup; if (nbr->state != EIGRP_NEIGHBOR_UP) continue; dup = eigrp_packet_duplicate(ep, nbr); /*Put packet to retransmission queue*/ eigrp_fifo_push(nbr->retrans_queue, dup); if (nbr->retrans_queue->count == 1) eigrp_send_packet_reliably(nbr); } has_tlv = false; length = 0; eigrp_packet_free(ep); ep = NULL; new_packet = true; } } if (!has_tlv) return; if ((ei->params.auth_type == EIGRP_AUTH_TYPE_MD5) && ei->params.auth_keychain != NULL) eigrp_make_md5_digest(ei, ep->s, EIGRP_AUTH_UPDATE_FLAG); /* EIGRP Checksum */ eigrp_packet_checksum(ei, ep->s, length); ep->length = length; ep->dst.s_addr = htonl(EIGRP_MULTICAST_ADDRESS); /*This ack number we await from neighbor*/ ep->sequence_number = ei->eigrp->sequence_number; ei->eigrp->sequence_number++; for (ALL_LIST_ELEMENTS(ei->nbrs, node2, nnode2, nbr)) { struct eigrp_packet *dup; if (nbr->state != EIGRP_NEIGHBOR_UP) continue; dup = eigrp_packet_duplicate(ep, nbr); /*Put packet to retransmission queue*/ eigrp_fifo_push(nbr->retrans_queue, dup); if (nbr->retrans_queue->count == 1) eigrp_send_packet_reliably(nbr); } eigrp_packet_free(ep); } frr-7.2.1/eigrpd/eigrp_reply.c0000644000000000000000000001232213610377563013153 00000000000000/* * Eigrp Sending and Receiving EIGRP Reply Packets. * Copyright (C) 2013-2016 * Authors: * Donnie Savage * Jan Janovic * Matej Perina * Peter Orsag * Peter Paluch * Frantisek Gazo * Tomas Hvorkovy * Martin Kontsek * Lukas Koribsky * * This file is part of GNU Zebra. * * GNU Zebra is free software; you can redistribute it and/or modify it * under the terms of the GNU General Public License as published by the * Free Software Foundation; either version 2, or (at your option) any * later version. * * GNU Zebra 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; see the file COPYING; if not, write to the Free Software * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA */ #include #include "thread.h" #include "memory.h" #include "linklist.h" #include "prefix.h" #include "if.h" #include "table.h" #include "sockunion.h" #include "stream.h" #include "log.h" #include "sockopt.h" #include "checksum.h" #include "md5.h" #include "vty.h" #include "keychain.h" #include "plist.h" #include "eigrpd/eigrp_structs.h" #include "eigrpd/eigrpd.h" #include "eigrpd/eigrp_interface.h" #include "eigrpd/eigrp_neighbor.h" #include "eigrpd/eigrp_packet.h" #include "eigrpd/eigrp_zebra.h" #include "eigrpd/eigrp_vty.h" #include "eigrpd/eigrp_dump.h" #include "eigrpd/eigrp_macros.h" #include "eigrpd/eigrp_topology.h" #include "eigrpd/eigrp_fsm.h" #include "eigrpd/eigrp_memory.h" #include "eigrpd/eigrp_errors.h" void eigrp_send_reply(struct eigrp_neighbor *nbr, struct eigrp_prefix_entry *pe) { struct eigrp_packet *ep; uint16_t length = EIGRP_HEADER_LEN; struct eigrp_interface *ei = nbr->ei; struct eigrp *eigrp = ei->eigrp; struct eigrp_prefix_entry *pe2; // TODO: Work in progress /* Filtering */ /* get list from eigrp process */ pe2 = XCALLOC(MTYPE_EIGRP_PREFIX_ENTRY, sizeof(struct eigrp_prefix_entry)); memcpy(pe2, pe, sizeof(struct eigrp_prefix_entry)); if (eigrp_update_prefix_apply(eigrp, ei, EIGRP_FILTER_OUT, pe2->destination)) { zlog_info("REPLY SEND: Setting Metric to max"); pe2->reported_metric.delay = EIGRP_MAX_METRIC; } /* * End of filtering */ ep = eigrp_packet_new(EIGRP_PACKET_MTU(ei->ifp->mtu), nbr); /* Prepare EIGRP INIT UPDATE header */ eigrp_packet_header_init(EIGRP_OPC_REPLY, eigrp, ep->s, 0, eigrp->sequence_number, 0); // encode Authentication TLV, if needed if (ei->params.auth_type == EIGRP_AUTH_TYPE_MD5 && (ei->params.auth_keychain != NULL)) { length += eigrp_add_authTLV_MD5_to_stream(ep->s, ei); } length += eigrp_add_internalTLV_to_stream(ep->s, pe2); if ((ei->params.auth_type == EIGRP_AUTH_TYPE_MD5) && (ei->params.auth_keychain != NULL)) { eigrp_make_md5_digest(ei, ep->s, EIGRP_AUTH_UPDATE_FLAG); } /* EIGRP Checksum */ eigrp_packet_checksum(ei, ep->s, length); ep->length = length; ep->dst.s_addr = nbr->src.s_addr; /*This ack number we await from neighbor*/ ep->sequence_number = eigrp->sequence_number; /*Put packet to retransmission queue*/ eigrp_fifo_push(nbr->retrans_queue, ep); if (nbr->retrans_queue->count == 1) { eigrp_send_packet_reliably(nbr); } XFREE(MTYPE_EIGRP_PREFIX_ENTRY, pe2); } /*EIGRP REPLY read function*/ void eigrp_reply_receive(struct eigrp *eigrp, struct ip *iph, struct eigrp_header *eigrph, struct stream *s, struct eigrp_interface *ei, int size) { struct eigrp_neighbor *nbr; struct TLV_IPv4_Internal_type *tlv; uint16_t type; /* increment statistics. */ ei->reply_in++; /* get neighbor struct */ nbr = eigrp_nbr_get(ei, eigrph, iph); /* neighbor must be valid, eigrp_nbr_get creates if none existed */ assert(nbr); nbr->recv_sequence_number = ntohl(eigrph->sequence); while (s->endp > s->getp) { type = stream_getw(s); if (type != EIGRP_TLV_IPv4_INT) continue; struct prefix dest_addr; stream_set_getp(s, s->getp - sizeof(uint16_t)); tlv = eigrp_read_ipv4_tlv(s); dest_addr.family = AF_INET; dest_addr.u.prefix4 = tlv->destination; dest_addr.prefixlen = tlv->prefix_length; struct eigrp_prefix_entry *dest = eigrp_topology_table_lookup_ipv4(eigrp->topology_table, &dest_addr); /* * Destination must exists */ if (!dest) { char buf[PREFIX_STRLEN]; flog_err( EC_EIGRP_PACKET, "%s: Received prefix %s which we do not know about", __PRETTY_FUNCTION__, prefix2str(&dest_addr, buf, sizeof(buf))); eigrp_IPv4_InternalTLV_free(tlv); continue; } struct eigrp_fsm_action_message msg; struct eigrp_nexthop_entry *entry = eigrp_prefix_entry_lookup(dest->entries, nbr); if (eigrp_update_prefix_apply(eigrp, ei, EIGRP_FILTER_IN, &dest_addr)) { tlv->metric.delay = EIGRP_MAX_METRIC; } /* * End of filtering */ msg.packet_type = EIGRP_OPC_REPLY; msg.eigrp = eigrp; msg.data_type = EIGRP_INT; msg.adv_router = nbr; msg.metrics = tlv->metric; msg.entry = entry; msg.prefix = dest; eigrp_fsm_event(&msg); eigrp_IPv4_InternalTLV_free(tlv); } eigrp_hello_send_ack(nbr); } frr-7.2.1/eigrpd/eigrp_siaquery.c0000644000000000000000000001051213610377563013661 00000000000000/* * EIGRP Sending and Receiving EIGRP SIA-Query Packets. * Copyright (C) 2013-2014 * Authors: * Donnie Savage * Jan Janovic * Matej Perina * Peter Orsag * Peter Paluch * * This file is part of GNU Zebra. * * GNU Zebra is free software; you can redistribute it and/or modify it * under the terms of the GNU General Public License as published by the * Free Software Foundation; either version 2, or (at your option) any * later version. * * GNU Zebra 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; see the file COPYING; if not, write to the Free Software * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA */ #include #include "thread.h" #include "memory.h" #include "linklist.h" #include "prefix.h" #include "if.h" #include "table.h" #include "sockunion.h" #include "stream.h" #include "log.h" #include "sockopt.h" #include "checksum.h" #include "md5.h" #include "vty.h" #include "eigrpd/eigrp_structs.h" #include "eigrpd/eigrpd.h" #include "eigrpd/eigrp_interface.h" #include "eigrpd/eigrp_neighbor.h" #include "eigrpd/eigrp_packet.h" #include "eigrpd/eigrp_zebra.h" #include "eigrpd/eigrp_vty.h" #include "eigrpd/eigrp_dump.h" #include "eigrpd/eigrp_macros.h" #include "eigrpd/eigrp_topology.h" #include "eigrpd/eigrp_fsm.h" #include "eigrpd/eigrp_memory.h" /*EIGRP SIA-QUERY read function*/ void eigrp_siaquery_receive(struct eigrp *eigrp, struct ip *iph, struct eigrp_header *eigrph, struct stream *s, struct eigrp_interface *ei, int size) { struct eigrp_neighbor *nbr; struct TLV_IPv4_Internal_type *tlv; uint16_t type; /* increment statistics. */ ei->siaQuery_in++; /* get neighbor struct */ nbr = eigrp_nbr_get(ei, eigrph, iph); /* neighbor must be valid, eigrp_nbr_get creates if none existed */ assert(nbr); nbr->recv_sequence_number = ntohl(eigrph->sequence); while (s->endp > s->getp) { type = stream_getw(s); if (type == EIGRP_TLV_IPv4_INT) { struct prefix dest_addr; stream_set_getp(s, s->getp - sizeof(uint16_t)); tlv = eigrp_read_ipv4_tlv(s); dest_addr.family = AFI_IP; dest_addr.u.prefix4 = tlv->destination; dest_addr.prefixlen = tlv->prefix_length; struct eigrp_prefix_entry *dest = eigrp_topology_table_lookup_ipv4( eigrp->topology_table, &dest_addr); /* If the destination exists (it should, but one never * know)*/ if (dest != NULL) { struct eigrp_fsm_action_message msg; struct eigrp_nexthop_entry *entry = eigrp_prefix_entry_lookup(dest->entries, nbr); msg.packet_type = EIGRP_OPC_SIAQUERY; msg.eigrp = eigrp; msg.data_type = EIGRP_INT; msg.adv_router = nbr; msg.metrics = tlv->metric; msg.entry = entry; msg.prefix = dest; eigrp_fsm_event(&msg); } eigrp_IPv4_InternalTLV_free(tlv); } } eigrp_hello_send_ack(nbr); } void eigrp_send_siaquery(struct eigrp_neighbor *nbr, struct eigrp_prefix_entry *pe) { struct eigrp_packet *ep; uint16_t length = EIGRP_HEADER_LEN; ep = eigrp_packet_new(EIGRP_PACKET_MTU(nbr->ei->ifp->mtu), nbr); /* Prepare EIGRP INIT UPDATE header */ eigrp_packet_header_init(EIGRP_OPC_SIAQUERY, nbr->ei->eigrp, ep->s, 0, nbr->ei->eigrp->sequence_number, 0); // encode Authentication TLV, if needed if ((nbr->ei->params.auth_type == EIGRP_AUTH_TYPE_MD5) && (nbr->ei->params.auth_keychain != NULL)) { length += eigrp_add_authTLV_MD5_to_stream(ep->s, nbr->ei); } length += eigrp_add_internalTLV_to_stream(ep->s, pe); if ((nbr->ei->params.auth_type == EIGRP_AUTH_TYPE_MD5) && (nbr->ei->params.auth_keychain != NULL)) { eigrp_make_md5_digest(nbr->ei, ep->s, EIGRP_AUTH_UPDATE_FLAG); } /* EIGRP Checksum */ eigrp_packet_checksum(nbr->ei, ep->s, length); ep->length = length; ep->dst.s_addr = nbr->src.s_addr; /*This ack number we await from neighbor*/ ep->sequence_number = nbr->ei->eigrp->sequence_number; if (nbr->state == EIGRP_NEIGHBOR_UP) { /*Put packet to retransmission queue*/ eigrp_fifo_push(nbr->retrans_queue, ep); if (nbr->retrans_queue->count == 1) { eigrp_send_packet_reliably(nbr); } } else eigrp_packet_free(ep); } frr-7.2.1/eigrpd/eigrp_siareply.c0000644000000000000000000001050513610377563013651 00000000000000/* * EIGRP Sending and Receiving EIGRP SIA-Reply Packets. * Copyright (C) 2013-2014 * Authors: * Donnie Savage * Jan Janovic * Matej Perina * Peter Orsag * Peter Paluch * * This file is part of GNU Zebra. * * GNU Zebra is free software; you can redistribute it and/or modify it * under the terms of the GNU General Public License as published by the * Free Software Foundation; either version 2, or (at your option) any * later version. * * GNU Zebra 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; see the file COPYING; if not, write to the Free Software * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA */ #include #include "thread.h" #include "memory.h" #include "linklist.h" #include "prefix.h" #include "if.h" #include "table.h" #include "sockunion.h" #include "stream.h" #include "log.h" #include "sockopt.h" #include "checksum.h" #include "md5.h" #include "vty.h" #include "eigrpd/eigrp_structs.h" #include "eigrpd/eigrpd.h" #include "eigrpd/eigrp_interface.h" #include "eigrpd/eigrp_neighbor.h" #include "eigrpd/eigrp_packet.h" #include "eigrpd/eigrp_zebra.h" #include "eigrpd/eigrp_vty.h" #include "eigrpd/eigrp_dump.h" #include "eigrpd/eigrp_macros.h" #include "eigrpd/eigrp_topology.h" #include "eigrpd/eigrp_fsm.h" #include "eigrpd/eigrp_memory.h" /*EIGRP SIA-REPLY read function*/ void eigrp_siareply_receive(struct eigrp *eigrp, struct ip *iph, struct eigrp_header *eigrph, struct stream *s, struct eigrp_interface *ei, int size) { struct eigrp_neighbor *nbr; struct TLV_IPv4_Internal_type *tlv; uint16_t type; /* increment statistics. */ ei->siaReply_in++; /* get neighbor struct */ nbr = eigrp_nbr_get(ei, eigrph, iph); /* neighbor must be valid, eigrp_nbr_get creates if none existed */ assert(nbr); nbr->recv_sequence_number = ntohl(eigrph->sequence); while (s->endp > s->getp) { type = stream_getw(s); if (type == EIGRP_TLV_IPv4_INT) { struct prefix dest_addr; stream_set_getp(s, s->getp - sizeof(uint16_t)); tlv = eigrp_read_ipv4_tlv(s); dest_addr.family = AFI_IP; dest_addr.u.prefix4 = tlv->destination; dest_addr.prefixlen = tlv->prefix_length; struct eigrp_prefix_entry *dest = eigrp_topology_table_lookup_ipv4( eigrp->topology_table, &dest_addr); /* If the destination exists (it should, but one never * know)*/ if (dest != NULL) { struct eigrp_fsm_action_message msg; struct eigrp_nexthop_entry *entry = eigrp_prefix_entry_lookup(dest->entries, nbr); msg.packet_type = EIGRP_OPC_SIAQUERY; msg.eigrp = eigrp; msg.data_type = EIGRP_INT; msg.adv_router = nbr; msg.metrics = tlv->metric; msg.entry = entry; msg.prefix = dest; eigrp_fsm_event(&msg); } eigrp_IPv4_InternalTLV_free(tlv); } } eigrp_hello_send_ack(nbr); } void eigrp_send_siareply(struct eigrp_neighbor *nbr, struct eigrp_prefix_entry *pe) { struct eigrp_packet *ep; uint16_t length = EIGRP_HEADER_LEN; ep = eigrp_packet_new(EIGRP_PACKET_MTU(nbr->ei->ifp->mtu), nbr); /* Prepare EIGRP INIT UPDATE header */ eigrp_packet_header_init(EIGRP_OPC_SIAREPLY, nbr->ei->eigrp, ep->s, 0, nbr->ei->eigrp->sequence_number, 0); // encode Authentication TLV, if needed if (nbr->ei->params.auth_type == EIGRP_AUTH_TYPE_MD5 && nbr->ei->params.auth_keychain != NULL) { length += eigrp_add_authTLV_MD5_to_stream(ep->s, nbr->ei); } length += eigrp_add_internalTLV_to_stream(ep->s, pe); if ((nbr->ei->params.auth_type == EIGRP_AUTH_TYPE_MD5) && (nbr->ei->params.auth_keychain != NULL)) { eigrp_make_md5_digest(nbr->ei, ep->s, EIGRP_AUTH_UPDATE_FLAG); } /* EIGRP Checksum */ eigrp_packet_checksum(nbr->ei, ep->s, length); ep->length = length; ep->dst.s_addr = nbr->src.s_addr; /*This ack number we await from neighbor*/ ep->sequence_number = nbr->ei->eigrp->sequence_number; if (nbr->state == EIGRP_NEIGHBOR_UP) { /*Put packet to retransmission queue*/ eigrp_fifo_push(nbr->retrans_queue, ep); if (nbr->retrans_queue->count == 1) { eigrp_send_packet_reliably(nbr); } } else eigrp_packet_free(ep); } frr-7.2.1/eigrpd/eigrp_snmp.c0000644000000000000000000010255313610377563013003 00000000000000/* * EIGRP SNMP Support. * Copyright (C) 2013-2014 * Authors: * Donnie Savage * Jan Janovic * Matej Perina * Peter Orsag * Peter Paluch * * This file is part of GNU Zebra. * * GNU Zebra is free software; you can redistribute it and/or modify it * under the terms of the GNU General Public License as published by the * Free Software Foundation; either version 2, or (at your option) any * later version. * * GNU Zebra 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; see the file COPYING; if not, write to the Free Software * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA */ #include #ifdef HAVE_SNMP #include #include #include "thread.h" #include "memory.h" #include "linklist.h" #include "prefix.h" #include "if.h" #include "table.h" #include "sockunion.h" #include "stream.h" #include "log.h" #include "sockopt.h" #include "checksum.h" #include "md5.h" #include "keychain.h" #include "smux.h" #include "eigrpd/eigrp_structs.h" #include "eigrpd/eigrpd.h" #include "eigrpd/eigrp_interface.h" #include "eigrpd/eigrp_neighbor.h" #include "eigrpd/eigrp_packet.h" #include "eigrpd/eigrp_zebra.h" #include "eigrpd/eigrp_vty.h" #include "eigrpd/eigrp_dump.h" #include "eigrpd/eigrp_network.h" #include "eigrpd/eigrp_topology.h" #include "eigrpd/eigrp_fsm.h" #include "eigrpd/eigrp_snmp.h" struct list *eigrp_snmp_iflist; /* Declare static local variables for convenience. */ SNMP_LOCAL_VARIABLES /* EIGRP-MIB - 1.3.6.1.4.1.9.9.449.1*/ #define EIGRPMIB 1,3,6,1,4,1,9,9,449,1 /* EIGRP-MIB instances. */ oid eigrp_oid[] = {EIGRPMIB}; /* EIGRP VPN entry */ #define EIGRPVPNID 1 #define EIGRPVPNNAME 2 /* EIGRP Traffic statistics entry */ #define EIGRPASNUMBER 1 #define EIGRPNBRCOUNT 2 #define EIGRPHELLOSSENT 3 #define EIGRPHELLOSRCVD 4 #define EIGRPUPDATESSENT 5 #define EIGRPUPDATESRCVD 6 #define EIGRPQUERIESSENT 7 #define EIGRPQUERIESRCVD 8 #define EIGRPREPLIESSENT 9 #define EIGRPREPLIESRCVD 10 #define EIGRPACKSSENT 11 #define EIGRPACKSRCVD 12 #define EIGRPINPUTQHIGHMARK 13 #define EIGRPINPUTQDROPS 14 #define EIGRPSIAQUERIESSENT 15 #define EIGRPSIAQUERIESRCVD 16 #define EIGRPASROUTERIDTYPE 17 #define EIGRPASROUTERID 18 #define EIGRPTOPOROUTES 19 #define EIGRPHEADSERIAL 20 #define EIGRPNEXTSERIAL 21 #define EIGRPXMITPENDREPLIES 22 #define EIGRPXMITDUMMIES 23 /* EIGRP topology entry */ #define EIGRPDESTNETTYPE 1 #define EIGRPDESTNET 2 #define EIGRPDESTNETPREFIXLEN 4 #define EIGRPACTIVE 5 #define EIGRPSTUCKINACTIVE 6 #define EIGRPDESTSUCCESSORS 7 #define EIGRPFDISTANCE 8 #define EIGRPROUTEORIGINTYPE 9 #define EIGRPROUTEORIGINADDRTYPE 10 #define EIGRPROUTEORIGINADDR 11 #define EIGRPNEXTHOPADDRESSTYPE 12 #define EIGRPNEXTHOPADDRESS 13 #define EIGRPNEXTHOPINTERFACE 14 #define EIGRPDISTANCE 15 #define EIGRPREPORTDISTANCE 16 /* EIGRP peer entry */ #define EIGRPHANDLE 1 #define EIGRPPEERADDRTYPE 2 #define EIGRPPEERADDR 3 #define EIGRPPEERIFINDEX 4 #define EIGRPHOLDTIME 5 #define EIGRPUPTIME 6 #define EIGRPSRTT 7 #define EIGRPRTO 8 #define EIGRPPKTSENQUEUED 9 #define EIGRPLASTSEQ 10 #define EIGRPVERSION 11 #define EIGRPRETRANS 12 #define EIGRPRETRIES 13 /* EIGRP interface entry */ #define EIGRPPEERCOUNT 3 #define EIGRPXMITRELIABLEQ 4 #define EIGRPXMITUNRELIABLEQ 5 #define EIGRPMEANSRTT 6 #define EIGRPPACINGRELIABLE 7 #define EIGRPPACINGUNRELIABLE 8 #define EIGRPMFLOWTIMER 9 #define EIGRPPENDINGROUTES 10 #define EIGRPHELLOINTERVAL 11 #define EIGRPXMITNEXTSERIAL 12 #define EIGRPUMCASTS 13 #define EIGRPRMCASTS 14 #define EIGRPUUCASTS 15 #define EIGRPRUCASTS 16 #define EIGRPMCASTEXCEPTS 17 #define EIGRPCRPKTS 18 #define EIGRPACKSSUPPRESSED 19 #define EIGRPRETRANSSENT 20 #define EIGRPOOSRCVD 21 #define EIGRPAUTHMODE 22 #define EIGRPAUTHKEYCHAIN 23 /* SNMP value hack. */ #define COUNTER ASN_COUNTER #define INTEGER ASN_INTEGER #define GAUGE ASN_GAUGE #define TIMETICKS ASN_TIMETICKS #define IPADDRESS ASN_IPADDRESS #define STRING ASN_OCTET_STR #define IPADDRESSPREFIXLEN ASN_INTEGER #define IPADDRESSTYPE ASN_INTEGER #define INTERFACEINDEXORZERO ASN_INTEGER #define UINTEGER ASN_UNSIGNED /* Hook functions. */ static uint8_t *eigrpVpnEntry(struct variable *, oid *, size_t *, int, size_t *, WriteMethod **); static uint8_t *eigrpTraffStatsEntry(struct variable *, oid *, size_t *, int, size_t *, WriteMethod **); static uint8_t *eigrpTopologyEntry(struct variable *, oid *, size_t *, int, size_t *, WriteMethod **); static uint8_t *eigrpPeerEntry(struct variable *, oid *, size_t *, int, size_t *, WriteMethod **); static uint8_t *eigrpInterfaceEntry(struct variable *, oid *, size_t *, int, size_t *, WriteMethod **); struct variable eigrp_variables[] = { /* EIGRP vpn variables */ {EIGRPVPNID, INTEGER, NOACCESS, eigrpVpnEntry, 4, {1, 1, 1, 1}}, {EIGRPVPNNAME, STRING, RONLY, eigrpVpnEntry, 4, {1, 1, 1, 2}}, /* EIGRP traffic stats variables */ {EIGRPASNUMBER, UINTEGER, NOACCESS, eigrpTraffStatsEntry, 4, {2, 1, 1, 1}}, {EIGRPNBRCOUNT, UINTEGER, RONLY, eigrpTraffStatsEntry, 4, {2, 1, 1, 2}}, {EIGRPHELLOSSENT, COUNTER, RONLY, eigrpTraffStatsEntry, 4, {2, 1, 1, 3}}, {EIGRPHELLOSRCVD, COUNTER, RONLY, eigrpTraffStatsEntry, 4, {2, 1, 1, 4}}, {EIGRPUPDATESSENT, COUNTER, RONLY, eigrpTraffStatsEntry, 4, {2, 1, 1, 5}}, {EIGRPUPDATESRCVD, COUNTER, RONLY, eigrpTraffStatsEntry, 4, {2, 1, 1, 6}}, {EIGRPQUERIESSENT, COUNTER, RONLY, eigrpTraffStatsEntry, 4, {2, 1, 1, 7}}, {EIGRPQUERIESRCVD, COUNTER, RONLY, eigrpTraffStatsEntry, 4, {2, 1, 1, 8}}, {EIGRPREPLIESSENT, COUNTER, RONLY, eigrpTraffStatsEntry, 4, {2, 1, 1, 9}}, {EIGRPREPLIESRCVD, COUNTER, RONLY, eigrpTraffStatsEntry, 4, {2, 1, 1, 10}}, {EIGRPACKSSENT, COUNTER, RONLY, eigrpTraffStatsEntry, 4, {2, 1, 1, 11}}, {EIGRPACKSRCVD, COUNTER, RONLY, eigrpTraffStatsEntry, 4, {2, 1, 1, 12}}, {EIGRPINPUTQHIGHMARK, INTEGER, RONLY, eigrpTraffStatsEntry, 4, {2, 1, 1, 13}}, {EIGRPINPUTQDROPS, COUNTER, RONLY, eigrpTraffStatsEntry, 4, {2, 1, 1, 14}}, {EIGRPSIAQUERIESSENT, COUNTER, RONLY, eigrpTraffStatsEntry, 4, {2, 1, 1, 15}}, {EIGRPSIAQUERIESRCVD, COUNTER, RONLY, eigrpTraffStatsEntry, 4, {2, 1, 1, 16}}, {EIGRPASROUTERIDTYPE, IPADDRESSTYPE, RONLY, eigrpTraffStatsEntry, 4, {2, 1, 1, 17}}, {EIGRPASROUTERID, IPADDRESS, RONLY, eigrpTraffStatsEntry, 4, {2, 1, 1, 18}}, {EIGRPTOPOROUTES, COUNTER, RONLY, eigrpTraffStatsEntry, 4, {2, 1, 1, 19}}, {EIGRPHEADSERIAL, COUNTER, RONLY, eigrpTraffStatsEntry, 4, {2, 1, 1, 20}}, {EIGRPNEXTSERIAL, COUNTER, RONLY, eigrpTraffStatsEntry, 4, {2, 1, 1, 21}}, {EIGRPXMITPENDREPLIES, INTEGER, RONLY, eigrpTraffStatsEntry, 4, {2, 1, 1, 22}}, {EIGRPXMITDUMMIES, COUNTER, RONLY, eigrpTraffStatsEntry, 4, {2, 1, 1, 23}}, /* EIGRP topology variables */ {EIGRPDESTNETTYPE, IPADDRESSTYPE, NOACCESS, eigrpTopologyEntry, 4, {3, 1, 1, 1}}, {EIGRPDESTNET, IPADDRESSPREFIXLEN, NOACCESS, eigrpTopologyEntry, 4, {3, 1, 1, 2}}, {EIGRPDESTNETPREFIXLEN, IPADDRESSTYPE, NOACCESS, eigrpTopologyEntry, 4, {3, 1, 1, 4}}, {EIGRPACTIVE, INTEGER, RONLY, eigrpTopologyEntry, 4, {3, 1, 1, 5}}, {EIGRPSTUCKINACTIVE, INTEGER, RONLY, eigrpTopologyEntry, 4, {3, 1, 1, 6}}, {EIGRPDESTSUCCESSORS, INTEGER, RONLY, eigrpTopologyEntry, 4, {3, 1, 1, 7}}, {EIGRPFDISTANCE, INTEGER, RONLY, eigrpTopologyEntry, 4, {3, 1, 1, 8}}, {EIGRPROUTEORIGINTYPE, STRING, RONLY, eigrpTopologyEntry, 4, {3, 1, 1, 9}}, {EIGRPROUTEORIGINADDRTYPE, IPADDRESSTYPE, RONLY, eigrpTopologyEntry, 4, {3, 1, 1, 10}}, {EIGRPROUTEORIGINADDR, IPADDRESS, RONLY, eigrpTopologyEntry, 4, {3, 1, 1, 11}}, {EIGRPNEXTHOPADDRESSTYPE, IPADDRESSTYPE, RONLY, eigrpTopologyEntry, 4, {3, 1, 1, 12}}, {EIGRPNEXTHOPADDRESS, IPADDRESS, RONLY, eigrpTopologyEntry, 4, {3, 1, 1, 13}}, {EIGRPNEXTHOPINTERFACE, STRING, RONLY, eigrpTopologyEntry, 4, {3, 1, 1, 14}}, {EIGRPDISTANCE, INTEGER, RONLY, eigrpTopologyEntry, 4, {3, 1, 1, 15}}, {EIGRPREPORTDISTANCE, INTEGER, RONLY, eigrpTopologyEntry, 4, {3, 1, 1, 16}}, /* EIGRP peer variables */ {EIGRPHANDLE, INTEGER, NOACCESS, eigrpPeerEntry, 4, {4, 1, 1, 1}}, {EIGRPPEERADDRTYPE, IPADDRESSTYPE, RONLY, eigrpPeerEntry, 4, {4, 1, 1, 2}}, {EIGRPPEERADDR, IPADDRESS, RONLY, eigrpPeerEntry, 4, {4, 1, 1, 3}}, {EIGRPPEERIFINDEX, INTERFACEINDEXORZERO, RONLY, eigrpPeerEntry, 4, {4, 1, 1, 4}}, {EIGRPHOLDTIME, INTEGER, RONLY, eigrpPeerEntry, 4, {4, 1, 1, 5}}, {EIGRPUPTIME, STRING, RONLY, eigrpPeerEntry, 4, {4, 1, 1, 6}}, {EIGRPSRTT, INTEGER, RONLY, eigrpPeerEntry, 4, {4, 1, 1, 7}}, {EIGRPRTO, INTEGER, RONLY, eigrpPeerEntry, 4, {4, 1, 1, 8}}, {EIGRPPKTSENQUEUED, INTEGER, RONLY, eigrpPeerEntry, 4, {4, 1, 1, 9}}, {EIGRPLASTSEQ, INTEGER, RONLY, eigrpPeerEntry, 4, {4, 1, 1, 10}}, {EIGRPVERSION, STRING, RONLY, eigrpPeerEntry, 4, {4, 1, 1, 11}}, {EIGRPRETRANS, COUNTER, RONLY, eigrpPeerEntry, 4, {4, 1, 1, 12}}, {EIGRPRETRIES, INTEGER, RONLY, eigrpPeerEntry, 4, {4, 1, 1, 13}}, /* EIGRP interface variables */ {EIGRPPEERCOUNT, GAUGE, RONLY, eigrpInterfaceEntry, 4, {5, 1, 1, 3}}, {EIGRPXMITRELIABLEQ, GAUGE, RONLY, eigrpInterfaceEntry, 4, {5, 1, 1, 4}}, {EIGRPXMITUNRELIABLEQ, GAUGE, RONLY, eigrpInterfaceEntry, 4, {5, 1, 1, 5}}, {EIGRPMEANSRTT, INTEGER, RONLY, eigrpInterfaceEntry, 4, {5, 1, 1, 6}}, {EIGRPPACINGRELIABLE, INTEGER, RONLY, eigrpInterfaceEntry, 4, {5, 1, 1, 7}}, {EIGRPPACINGUNRELIABLE, INTEGER, RONLY, eigrpInterfaceEntry, 4, {5, 1, 1, 8}}, {EIGRPMFLOWTIMER, INTEGER, RONLY, eigrpInterfaceEntry, 4, {5, 1, 1, 9}}, {EIGRPPENDINGROUTES, GAUGE, RONLY, eigrpInterfaceEntry, 4, {5, 1, 1, 10}}, {EIGRPHELLOINTERVAL, INTEGER, RONLY, eigrpInterfaceEntry, 4, {5, 1, 1, 11}}, {EIGRPXMITNEXTSERIAL, COUNTER, RONLY, eigrpInterfaceEntry, 4, {5, 1, 1, 12}}, {EIGRPUMCASTS, COUNTER, RONLY, eigrpInterfaceEntry, 4, {5, 1, 1, 13}}, {EIGRPRMCASTS, COUNTER, RONLY, eigrpInterfaceEntry, 4, {5, 1, 1, 14}}, {EIGRPUUCASTS, COUNTER, RONLY, eigrpInterfaceEntry, 4, {5, 1, 1, 15}}, {EIGRPRUCASTS, COUNTER, RONLY, eigrpInterfaceEntry, 4, {5, 1, 1, 16}}, {EIGRPMCASTEXCEPTS, COUNTER, RONLY, eigrpInterfaceEntry, 4, {5, 1, 1, 17}}, {EIGRPCRPKTS, COUNTER, RONLY, eigrpInterfaceEntry, 4, {5, 1, 1, 18}}, {EIGRPACKSSUPPRESSED, COUNTER, RONLY, eigrpInterfaceEntry, 4, {5, 1, 1, 19}}, {EIGRPRETRANSSENT, COUNTER, RONLY, eigrpInterfaceEntry, 4, {5, 1, 1, 20}}, {EIGRPOOSRCVD, COUNTER, RONLY, eigrpInterfaceEntry, 4, {5, 1, 1, 21}}, {EIGRPAUTHMODE, INTEGER, RONLY, eigrpInterfaceEntry, 4, {5, 1, 1, 22}}, {EIGRPAUTHKEYCHAIN, STRING, RONLY, eigrpInterfaceEntry, 4, {5, 1, 1, 23}}}; static struct eigrp_neighbor *eigrp_snmp_nbr_lookup(struct eigrp *eigrp, struct in_addr *nbr_addr, unsigned int *ifindex) { struct listnode *node, *nnode, *node2, *nnode2; struct eigrp_interface *ei; struct eigrp_neighbor *nbr; for (ALL_LIST_ELEMENTS(eigrp->eiflist, node, nnode, ei)) { for (ALL_LIST_ELEMENTS(ei->nbrs, node2, nnode2, nbr)) { if (IPV4_ADDR_SAME(&nbr->src, nbr_addr)) { return nbr; } } } return NULL; } static struct eigrp_neighbor * eigrp_snmp_nbr_lookup_next(struct in_addr *nbr_addr, unsigned int *ifindex, int first) { struct listnode *node, *nnode, *node2, *nnode2; struct eigrp_interface *ei; struct eigrp_neighbor *nbr; struct eigrp_neighbor *min = NULL; struct eigrp *eigrp; eigrp = eigrp_lookup(); for (ALL_LIST_ELEMENTS(eigrp->eiflist, node, nnode, ei)) { for (ALL_LIST_ELEMENTS(ei->nbrs, node2, nnode2, nbr)) { if (first) { if (!min) min = nbr; else if (ntohl(nbr->src.s_addr) < ntohl(min->src.s_addr)) min = nbr; } else if (ntohl(nbr->src.s_addr) > ntohl(nbr_addr->s_addr)) { if (!min) min = nbr; else if (ntohl(nbr->src.s_addr) < ntohl(min->src.s_addr)) min = nbr; } } } if (min) { *nbr_addr = min->src; *ifindex = 0; return min; } return NULL; } static struct eigrp_neighbor *eigrpNbrLookup(struct variable *v, oid *name, size_t *length, struct in_addr *nbr_addr, unsigned int *ifindex, int exact) { unsigned int len; int first; struct eigrp_neighbor *nbr; struct eigrp *eigrp; eigrp = eigrp_lookup(); if (!eigrp) return NULL; if (exact) { if (*length != v->namelen + IN_ADDR_SIZE + 1) return NULL; oid2in_addr(name + v->namelen, IN_ADDR_SIZE, nbr_addr); *ifindex = name[v->namelen + IN_ADDR_SIZE]; return eigrp_snmp_nbr_lookup(eigrp, nbr_addr, ifindex); } else { first = 0; len = *length - v->namelen; if (len == 0) first = 1; if (len > IN_ADDR_SIZE) len = IN_ADDR_SIZE; oid2in_addr(name + v->namelen, len, nbr_addr); len = *length - v->namelen - IN_ADDR_SIZE; if (len >= 1) *ifindex = name[v->namelen + IN_ADDR_SIZE]; nbr = eigrp_snmp_nbr_lookup_next(nbr_addr, ifindex, first); if (nbr) { *length = v->namelen + IN_ADDR_SIZE + 1; oid_copy_addr(name + v->namelen, nbr_addr, IN_ADDR_SIZE); name[v->namelen + IN_ADDR_SIZE] = *ifindex; return nbr; } } return NULL; } static uint8_t *eigrpVpnEntry(struct variable *v, oid *name, size_t *length, int exact, size_t *var_len, WriteMethod **write_method) { struct eigrp *eigrp; eigrp = eigrp_lookup(); /* Check whether the instance identifier is valid */ if (smux_header_generic(v, name, length, exact, var_len, write_method) == MATCH_FAILED) return NULL; /* Return the current value of the variable */ switch (v->magic) { case EIGRPVPNID: /* 1 */ /* The unique VPN identifier */ if (eigrp) { return SNMP_INTEGER(1); } else return SNMP_INTEGER(0); break; case EIGRPVPNNAME: /* 2 */ /* The name given to the VPN */ if (eigrp) { return SNMP_INTEGER(1); } else return SNMP_INTEGER(0); break; default: return NULL; } return NULL; } static uint32_t eigrp_neighbor_count(struct eigrp *eigrp) { uint32_t count; struct eigrp_interface *ei; struct listnode *node, *node2, *nnode2; struct eigrp_neighbor *nbr; if (eigrp == NULL) { return 0; } count = 0; for (ALL_LIST_ELEMENTS_RO(eigrp->eiflist, node, ei)) { for (ALL_LIST_ELEMENTS(ei->nbrs, node2, nnode2, nbr)) { if (nbr->state == EIGRP_NEIGHBOR_UP) count++; } } return count; } static uint8_t *eigrpTraffStatsEntry(struct variable *v, oid *name, size_t *length, int exact, size_t *var_len, WriteMethod **write_method) { struct eigrp *eigrp; struct eigrp_interface *ei; struct listnode *node, *nnode; int counter; eigrp = eigrp_lookup(); /* Check whether the instance identifier is valid */ if (smux_header_generic(v, name, length, exact, var_len, write_method) == MATCH_FAILED) return NULL; /* Return the current value of the variable */ switch (v->magic) { case EIGRPASNUMBER: /* 1 */ /* AS-number of this EIGRP instance. */ if (eigrp) return SNMP_INTEGER(eigrp->AS); else return SNMP_INTEGER(0); break; case EIGRPNBRCOUNT: /* 2 */ /* Neighbor count of this EIGRP instance */ if (eigrp) return SNMP_INTEGER(eigrp_neighbor_count(eigrp)); else return SNMP_INTEGER(0); break; case EIGRPHELLOSSENT: /* 3 */ /* Hello packets output count */ if (eigrp) { counter = 0; for (ALL_LIST_ELEMENTS(eigrp->eiflist, node, nnode, ei)) { counter += ei->hello_out; } return SNMP_INTEGER(counter); } else return SNMP_INTEGER(0); break; case EIGRPHELLOSRCVD: /* 4 */ /* Hello packets input count */ if (eigrp) { counter = 0; for (ALL_LIST_ELEMENTS(eigrp->eiflist, node, nnode, ei)) { counter += ei->hello_in; } return SNMP_INTEGER(counter); } else return SNMP_INTEGER(0); break; case EIGRPUPDATESSENT: /* 5 */ /* Update packets output count */ if (eigrp) { counter = 0; for (ALL_LIST_ELEMENTS(eigrp->eiflist, node, nnode, ei)) { counter += ei->update_out; } return SNMP_INTEGER(counter); } else return SNMP_INTEGER(0); break; case EIGRPUPDATESRCVD: /* 6 */ /* Update packets input count */ if (eigrp) { counter = 0; for (ALL_LIST_ELEMENTS(eigrp->eiflist, node, nnode, ei)) { counter += ei->update_in; } return SNMP_INTEGER(counter); } else return SNMP_INTEGER(0); break; case EIGRPQUERIESSENT: /* 7 */ /* Querry packets output count */ if (eigrp) { counter = 0; for (ALL_LIST_ELEMENTS(eigrp->eiflist, node, nnode, ei)) { counter += ei->query_out; } return SNMP_INTEGER(counter); } else return SNMP_INTEGER(0); break; case EIGRPQUERIESRCVD: /* 8 */ /* Querry packets input count */ if (eigrp) { counter = 0; for (ALL_LIST_ELEMENTS(eigrp->eiflist, node, nnode, ei)) { counter += ei->query_in; } return SNMP_INTEGER(counter); } else return SNMP_INTEGER(0); break; case EIGRPREPLIESSENT: /* 9 */ /* Reply packets output count */ if (eigrp) { counter = 0; for (ALL_LIST_ELEMENTS(eigrp->eiflist, node, nnode, ei)) { counter += ei->reply_out; } return SNMP_INTEGER(counter); } else return SNMP_INTEGER(0); break; case EIGRPREPLIESRCVD: /* 10 */ /* Reply packets input count */ if (eigrp) { counter = 0; for (ALL_LIST_ELEMENTS(eigrp->eiflist, node, nnode, ei)) { counter += ei->reply_in; } return SNMP_INTEGER(counter); } else return SNMP_INTEGER(0); break; case EIGRPACKSSENT: /* 11 */ /* Acknowledgement packets output count */ if (eigrp) { counter = 0; for (ALL_LIST_ELEMENTS(eigrp->eiflist, node, nnode, ei)) { counter += ei->ack_out; } return SNMP_INTEGER(counter); } else return SNMP_INTEGER(0); break; case EIGRPACKSRCVD: /* 12 */ /* Acknowledgement packets input count */ if (eigrp) { counter = 0; for (ALL_LIST_ELEMENTS(eigrp->eiflist, node, nnode, ei)) { counter += ei->ack_in; } return SNMP_INTEGER(counter); } else return SNMP_INTEGER(0); break; case EIGRPINPUTQHIGHMARK: /* 13 */ /* The highest number of EIGRP packets in the input queue */ if (eigrp) { return SNMP_INTEGER(1); } else return SNMP_INTEGER(0); break; case EIGRPINPUTQDROPS: /* 14 */ /* The number of EIGRP packets dropped from the input queue */ if (eigrp) { return SNMP_INTEGER(1); } else return SNMP_INTEGER(0); break; case EIGRPSIAQUERIESSENT: /* 15 */ /* SIA querry packets output count */ if (eigrp) { counter = 0; for (ALL_LIST_ELEMENTS(eigrp->eiflist, node, nnode, ei)) { counter += ei->siaQuery_out; } return SNMP_INTEGER(counter); } else return SNMP_INTEGER(0); break; case EIGRPSIAQUERIESRCVD: /* 16 */ /* SIA querry packets input count */ if (eigrp) { counter = 0; for (ALL_LIST_ELEMENTS(eigrp->eiflist, node, nnode, ei)) { counter += ei->siaQuery_in; } return SNMP_INTEGER(counter); } else return SNMP_INTEGER(0); break; case EIGRPASROUTERIDTYPE: /* 17 */ /* Whether the router ID is set manually or automatically */ if (eigrp) if (eigrp->router_id_static != 0) return SNMP_INTEGER(1); else return SNMP_INTEGER(1); else return SNMP_INTEGER(0); break; case EIGRPASROUTERID: /* 18 */ /* Router ID for this EIGRP AS */ if (eigrp) if (eigrp->router_id_static != 0) return SNMP_INTEGER(eigrp->router_id_static); else return SNMP_INTEGER(eigrp->router_id); else return SNMP_INTEGER(0); break; case EIGRPTOPOROUTES: /* 19 */ /* The total number of EIGRP derived routes currently existing in the topology table for the AS */ if (eigrp) { return SNMP_INTEGER(1); } else return SNMP_INTEGER(0); break; case EIGRPHEADSERIAL: /* 20 */ /* The serial number of the first route in the internal sequence for an AS*/ if (eigrp) { return SNMP_INTEGER(1); } else return SNMP_INTEGER(0); break; case EIGRPNEXTSERIAL: /* 21 */ /* The serial number that would be assigned to the next new or changed route in the topology table for the AS*/ if (eigrp) { return SNMP_INTEGER(1); } else return SNMP_INTEGER(0); break; case EIGRPXMITPENDREPLIES: /* 22 */ /* Total number of outstanding replies expected to queries that have been sent to peers in the current AS*/ if (eigrp) { return SNMP_INTEGER(1); } else return SNMP_INTEGER(0); break; case EIGRPXMITDUMMIES: /* 23 */ /* Total number of currently existing dummies associated with * the AS*/ if (eigrp) { return SNMP_INTEGER(1); } else return SNMP_INTEGER(0); break; default: return NULL; } return NULL; } static uint8_t *eigrpTopologyEntry(struct variable *v, oid *name, size_t *length, int exact, size_t *var_len, WriteMethod **write_method) { struct eigrp *eigrp; eigrp = eigrp_lookup(); /* Check whether the instance identifier is valid */ if (smux_header_generic(v, name, length, exact, var_len, write_method) == MATCH_FAILED) return NULL; /* Return the current value of the variable */ switch (v->magic) { case EIGRPDESTNETTYPE: /* 1 */ /* The format of the destination IP network number for a single route in the topology table*/ if (eigrp) { return SNMP_INTEGER(1); } else return SNMP_INTEGER(0); break; case EIGRPDESTNET: /* 2 */ /* The destination IP network number for a single route in the * topology table*/ if (eigrp) { return SNMP_INTEGER(1); } else return SNMP_INTEGER(0); break; case EIGRPDESTNETPREFIXLEN: /* 4 */ /* The prefix length associated with the destination IP network address for a single route in the topology table in the AS*/ if (eigrp) { return SNMP_INTEGER(1); } else return SNMP_INTEGER(0); break; case EIGRPACTIVE: /* 5 */ /* A value of true(1) indicates the route to the destination network has failed A value of false(2) indicates the route is stable (passive).*/ if (eigrp) { return SNMP_INTEGER(1); } else return SNMP_INTEGER(0); break; case EIGRPSTUCKINACTIVE: /* 6 */ /* A value of true(1) indicates that that this route which is in active state has not received any replies to queries for alternate paths */ if (eigrp) { return SNMP_INTEGER(1); } else return SNMP_INTEGER(0); break; case EIGRPDESTSUCCESSORS: /* 7 */ /* Next routing hop for a path to the destination IP network */ if (eigrp) { return SNMP_INTEGER(1); } else return SNMP_INTEGER(0); break; case EIGRPFDISTANCE: /* 8 */ /* Minimum distance from this router to the destination IP * network */ if (eigrp) { return SNMP_INTEGER(1); } else return SNMP_INTEGER(0); break; case EIGRPROUTEORIGINTYPE: /* 9 */ /* Text string describing the internal origin of the EIGRP route */ if (eigrp) { return SNMP_INTEGER(1); } else return SNMP_INTEGER(0); break; case EIGRPROUTEORIGINADDRTYPE: /* 10 */ /* The format of the IP address defined as the origin of this topology route entry */ if (eigrp) { return SNMP_INTEGER(1); } else return SNMP_INTEGER(0); break; case EIGRPROUTEORIGINADDR: /* 11 */ /* If the origin of the topology route entry is external to this router, then this object is the IP address of the router from which it originated */ if (eigrp) { return SNMP_INTEGER(1); } else return SNMP_INTEGER(0); break; case EIGRPNEXTHOPADDRESSTYPE: /* 12 */ /* The format of the next hop IP address */ if (eigrp) { return SNMP_INTEGER(1); } else return SNMP_INTEGER(0); break; case EIGRPNEXTHOPADDRESS: /* 13 */ /* Next hop IP address for the route */ if (eigrp) { return SNMP_INTEGER(1); } else return SNMP_INTEGER(0); break; case EIGRPNEXTHOPINTERFACE: /* 14 */ /* The interface through which the next hop IP address is * reached */ if (eigrp) { return SNMP_INTEGER(1); } else return SNMP_INTEGER(0); break; case EIGRPDISTANCE: /* 15 */ /* The computed distance to the destination network entry from * this router */ if (eigrp) { return SNMP_INTEGER(1); } else return SNMP_INTEGER(0); break; case EIGRPREPORTDISTANCE: /* 16 */ /* The computed distance to the destination network in the topology entry reported to this router by the originator of this route */ if (eigrp) { return SNMP_INTEGER(1); } else return SNMP_INTEGER(0); break; default: return NULL; } return NULL; } static uint8_t *eigrpPeerEntry(struct variable *v, oid *name, size_t *length, int exact, size_t *var_len, WriteMethod **write_method) { struct eigrp *eigrp; struct eigrp_interface *ei; struct eigrp_neighbor *nbr; struct in_addr nbr_addr; unsigned int ifindex; eigrp = eigrp_lookup(); /* Check whether the instance identifier is valid */ if (smux_header_generic(v, name, length, exact, var_len, write_method) == MATCH_FAILED) return NULL; memset(&nbr_addr, 0, sizeof(struct in_addr)); ifindex = 0; nbr = eigrpNbrLookup(v, name, length, &nbr_addr, &ifindex, exact); if (!nbr) return NULL; ei = nbr->ei; if (!ei) return NULL; /* Return the current value of the variable */ switch (v->magic) { case EIGRPHANDLE: /* 1 */ /* The unique internal identifier for the peer in the AS */ if (eigrp) { return SNMP_INTEGER(1); } else return SNMP_INTEGER(0); break; case EIGRPPEERADDRTYPE: /* 2 */ /* The format of the remote source IP address used by the peer */ if (eigrp) { return SNMP_INTEGER(1); } else return SNMP_INTEGER(0); break; case EIGRPPEERADDR: /* 3 */ /* The source IP address used by the peer */ if (eigrp) { return SNMP_INTEGER(1); } else return SNMP_INTEGER(0); break; case EIGRPPEERIFINDEX: /* 4 */ /* The ifIndex of the interface on this router */ if (eigrp) { return SNMP_INTEGER(1); } else return SNMP_INTEGER(0); break; case EIGRPHOLDTIME: /* 5 */ /* How much time must pass without receiving a hello packet from this EIGRP peer before this router declares the peer down */ if (eigrp) { return SNMP_INTEGER(1); } else return SNMP_INTEGER(0); break; case EIGRPUPTIME: /* 6 */ /* The elapsed time since the EIGRP adjacency was first * established */ if (eigrp) { return SNMP_INTEGER(1); } else return SNMP_INTEGER(0); break; case EIGRPSRTT: /* 7 */ /* The computed smooth round trip time for packets to and from * the peer */ if (eigrp) { return SNMP_INTEGER(1); } else return SNMP_INTEGER(0); break; case EIGRPRTO: /* 8 */ /* The computed retransmission timeout for the peer */ if (eigrp) { return SNMP_INTEGER(1); } else return SNMP_INTEGER(0); break; case EIGRPPKTSENQUEUED: /* 9 */ /* The number of any EIGRP packets currently enqueued */ if (eigrp) { return SNMP_INTEGER(1); } else return SNMP_INTEGER(0); break; case EIGRPLASTSEQ: /* 10 */ /* sequence number of the last EIGRP packet sent to this peer */ if (eigrp) { return SNMP_INTEGER(1); } else return SNMP_INTEGER(0); break; case EIGRPVERSION: /* 11 */ /* The EIGRP version information reported by the remote peer */ if (eigrp) { return SNMP_INTEGER(1); } else return SNMP_INTEGER(0); break; case EIGRPRETRANS: /* 12 */ /* The cumulative number of retransmissions to this peer */ if (eigrp) { return SNMP_INTEGER(1); } else return SNMP_INTEGER(0); break; case EIGRPRETRIES: /* 13 */ /* The number of times the current unacknowledged packet has * been retried */ if (eigrp) { return SNMP_INTEGER(1); } else return SNMP_INTEGER(0); break; default: return NULL; } return NULL; } static uint8_t *eigrpInterfaceEntry(struct variable *v, oid *name, size_t *length, int exact, size_t *var_len, WriteMethod **write_method) { struct eigrp *eigrp; struct listnode *node, *nnode; struct keychain *keychain; struct list *keylist; eigrp = eigrp_lookup(); /* Check whether the instance identifier is valid */ if (smux_header_generic(v, name, length, exact, var_len, write_method) == MATCH_FAILED) return NULL; /* Return the current value of the variable */ switch (v->magic) { case EIGRPPEERCOUNT: /* 3 */ /* The number of EIGRP adjacencies currently formed with peers reached through this interface */ if (eigrp) { return SNMP_INTEGER(eigrp_neighbor_count(eigrp)); } else return SNMP_INTEGER(0); break; case EIGRPXMITRELIABLEQ: /* 4 */ /* The number of EIGRP packets currently waiting in the reliable transport transmission queue */ if (eigrp) { return SNMP_INTEGER(1); } else return SNMP_INTEGER(0); break; case EIGRPXMITUNRELIABLEQ: /* 5 */ /* The number of EIGRP packets currently waiting in the unreliable transport transmission queue */ if (eigrp) { return SNMP_INTEGER(1); } else return SNMP_INTEGER(0); break; case EIGRPMEANSRTT: /* 6 */ /* The average of all the computed smooth round trip time values for a packet to and from all peers established on this interface */ if (eigrp) { return SNMP_INTEGER(1); } else return SNMP_INTEGER(0); break; case EIGRPPACINGRELIABLE: /* 7 */ /* The configured time interval between EIGRP packet * transmissions */ if (eigrp) { return SNMP_INTEGER(1); } else return SNMP_INTEGER(0); break; case EIGRPPACINGUNRELIABLE: /* 8 */ /* The configured time interval between EIGRP packet transmissions on the interface when the unreliable transport method is used */ if (eigrp) { return SNMP_INTEGER(1); } else return SNMP_INTEGER(0); break; case EIGRPMFLOWTIMER: /* 9 */ /* The configured multicast flow control timer value */ if (eigrp) { return SNMP_INTEGER(1); } else return SNMP_INTEGER(0); break; case EIGRPPENDINGROUTES: /* 10 */ /* The number of queued EIGRP routing updates awaiting * transmission */ if (eigrp) { return SNMP_INTEGER(1); } else return SNMP_INTEGER(0); break; case EIGRPHELLOINTERVAL: /* 11 */ /* The configured time interval between Hello packet * transmissions */ if (eigrp) { return SNMP_INTEGER(1); } else return SNMP_INTEGER(0); break; case EIGRPXMITNEXTSERIAL: /* 12 */ /* The serial number of the next EIGRP packet that is to be queued for transmission */ if (eigrp) { return SNMP_INTEGER(1); } else return SNMP_INTEGER(0); break; case EIGRPUMCASTS: /* 13 */ /* The total number of unreliable EIGRP multicast packets sent on this interface */ if (eigrp) { return SNMP_INTEGER(1); } else return SNMP_INTEGER(0); break; case EIGRPRMCASTS: /* 14 */ /* The total number of reliable EIGRP multicast packets sent on this interface */ if (eigrp) { return SNMP_INTEGER(1); } else return SNMP_INTEGER(0); break; case EIGRPUUCASTS: /* 15 */ /* The total number of unreliable EIGRP unicast packets sent on this interface */ if (eigrp) { return SNMP_INTEGER(1); } else return SNMP_INTEGER(0); break; case EIGRPRUCASTS: /* 16 */ /* The total number of reliable EIGRP unicast packets sent on this interface */ if (eigrp) { return SNMP_INTEGER(1); } else return SNMP_INTEGER(0); break; case EIGRPMCASTEXCEPTS: /* 17 */ /* The total number of EIGRP multicast exception transmissions */ if (eigrp) { return SNMP_INTEGER(1); } else return SNMP_INTEGER(0); break; case EIGRPCRPKTS: /* 18 */ /* The total number EIGRP Conditional-Receive packets sent on * this interface */ if (eigrp) { return SNMP_INTEGER(1); } else return SNMP_INTEGER(0); break; case EIGRPACKSSUPPRESSED: /* 19 */ /* The total number of individual EIGRP acknowledgement packets that have been suppressed and combined in an already enqueued outbound reliable packet on this interface */ if (eigrp) { return SNMP_INTEGER(1); } else return SNMP_INTEGER(0); break; case EIGRPRETRANSSENT: /* 20 */ /* The total number EIGRP packet retransmissions sent on the * interface */ if (eigrp) { return SNMP_INTEGER(1); } else return SNMP_INTEGER(0); break; case EIGRPOOSRCVD: /* 21 */ /* The total number of out-of-sequence EIGRP packets received */ if (eigrp) { return SNMP_INTEGER(1); } else return SNMP_INTEGER(0); break; case EIGRPAUTHMODE: /* 22 */ /* The EIGRP authentication mode of the interface */ if (eigrp) { return SNMP_INTEGER(1); } else return SNMP_INTEGER(0); break; case EIGRPAUTHKEYCHAIN: /* 23 */ /* The name of the authentication key-chain configured on this interface. */ keylist = keychain_list_get(); for (ALL_LIST_ELEMENTS(keylist, node, nnode, keychain)) { return (uint8_t *)keychain->name; } if (eigrp && keychain) { *var_len = str_len(keychain->name); return (uint8_t *)keychain->name; } else return (uint8_t *)"TEST"; break; default: return NULL; } return NULL; } /* Register EIGRP-MIB. */ void eigrp_snmp_init() { eigrp_snmp_iflist = list_new(); smux_init(eigrp_om->master); REGISTER_MIB("ciscoEigrpMIB", eigrp_variables, variable, eigrp_oid); } #endif frr-7.2.1/eigrpd/eigrp_snmp.h0000644000000000000000000000202113610377563012775 00000000000000/* * EIGRP SNMP Support. * Copyright (C) 2013-2014 * Authors: * Donnie Savage * Jan Janovic * Matej Perina * Peter Orsag * Peter Paluch * * This file is part of GNU Zebra. * * GNU Zebra is free software; you can redistribute it and/or modify it * under the terms of the GNU General Public License as published by the * Free Software Foundation; either version 2, or (at your option) any * later version. * * GNU Zebra 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; see the file COPYING; if not, write to the Free Software * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA */ #ifndef _ZEBRA_EIGRP_SNMP_H #define _ZEBRA_EIGRP_SNMP_H extern void eigrp_snmp_init(void); #endif /* _ZEBRA_EIGRP_SNMP_H */ frr-7.2.1/eigrpd/eigrp_structs.h0000644000000000000000000003070613610377563013542 00000000000000/* * EIGRP Definition of Data Structures. * Copyright (C) 2013-2016 * Authors: * Donnie Savage * Jan Janovic * Matej Perina * Peter Orsag * Peter Paluch * Frantisek Gazo * Tomas Hvorkovy * Martin Kontsek * Lukas Koribsky * * This file is part of GNU Zebra. * * GNU Zebra is free software; you can redistribute it and/or modify it * under the terms of the GNU General Public License as published by the * Free Software Foundation; either version 2, or (at your option) any * later version. * * GNU Zebra 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; see the file COPYING; if not, write to the Free Software * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA */ #ifndef _ZEBRA_EIGRP_STRUCTS_H_ #define _ZEBRA_EIGRP_STRUCTS_H_ #include "filter.h" #include "eigrpd/eigrp_const.h" #include "eigrpd/eigrp_macros.h" /* EIGRP master for system wide configuration and variables. */ struct eigrp_master { /* EIGRP instance. */ struct list *eigrp; /* EIGRP thread master. */ struct thread_master *master; /* Zebra interface list. */ struct list *iflist; /* EIGRP start time. */ time_t start_time; /* Various EIGRP global configuration. */ uint8_t options; #define EIGRP_MASTER_SHUTDOWN (1 << 0) /* deferred-shutdown */ }; struct eigrp_metrics { uint32_t delay; uint32_t bandwidth; unsigned char mtu[3]; uint8_t hop_count; uint8_t reliability; uint8_t load; uint8_t tag; uint8_t flags; }; struct eigrp { vrf_id_t vrf_id; uint16_t AS; /* Autonomous system number */ uint16_t vrid; /* Virtual Router ID */ uint8_t k_values[6]; /*Array for K values configuration*/ uint8_t variance; /*Metric variance multiplier*/ uint8_t max_paths; /*Maximum allowed paths for 1 prefix*/ /*Name of this EIGRP instance*/ char *name; /* EIGRP Router ID. */ struct in_addr router_id; /* Configured automatically. */ struct in_addr router_id_static; /* Configured manually. */ struct list *eiflist; /* eigrp interfaces */ uint8_t passive_interface_default; /* passive-interface default */ int fd; unsigned int maxsndbuflen; uint32_t sequence_number; /*Global EIGRP sequence number*/ struct stream *ibuf; struct list *oi_write_q; /*Threads*/ struct thread *t_write; struct thread *t_read; struct thread *t_distribute; /* timer for distribute list */ struct route_table *networks; /* EIGRP config networks. */ struct route_table *topology_table; uint64_t serno; /* Global serial number counter for topology entry changes*/ uint64_t serno_last_update; /* Highest serial number of information send by last update*/ struct list *topology_changes_internalIPV4; struct list *topology_changes_externalIPV4; /*Neighbor self*/ struct eigrp_neighbor *neighbor_self; /*Configured metric for redistributed routes*/ struct eigrp_metrics dmetric[ZEBRA_ROUTE_MAX + 1]; int redistribute; /* Num of redistributed protocols. */ /* Access-list. */ struct access_list *list[EIGRP_FILTER_MAX]; /* Prefix-list. */ struct prefix_list *prefix[EIGRP_FILTER_MAX]; /* Route-map. */ struct route_map *routemap[EIGRP_FILTER_MAX]; /* For redistribute route map. */ struct { char *name; struct route_map *map; int metric_config; uint32_t metric; } route_map[ZEBRA_ROUTE_MAX]; /* distribute_ctx */ struct distribute_ctx *distribute_ctx; QOBJ_FIELDS }; DECLARE_QOBJ_TYPE(eigrp) struct eigrp_if_params { uint8_t passive_interface; uint32_t v_hello; uint16_t v_wait; uint8_t type; /* type of interface */ uint32_t bandwidth; uint32_t delay; uint8_t reliability; uint8_t load; char *auth_keychain; /* Associated keychain with interface*/ int auth_type; /* EIGRP authentication type */ }; enum { MEMBER_ALLROUTERS = 0, MEMBER_MAX, }; /*EIGRP interface structure*/ struct eigrp_interface { struct eigrp_if_params params; /*multicast group refcnts */ bool member_allrouters; /* This interface's parent eigrp instance. */ struct eigrp *eigrp; /* Interface data from zebra. */ struct interface *ifp; /* Packet send buffer. */ struct eigrp_fifo *obuf; /* Output queue */ /* To which multicast groups do we currently belong? */ uint8_t multicast_memberships; /* EIGRP Network Type. */ uint8_t type; struct prefix address; /* Interface prefix */ /* Neighbor information. */ struct list *nbrs; /* EIGRP Neighbor List */ /* Threads. */ struct thread *t_hello; /* timer */ struct thread *t_distribute; /* timer for distribute list */ int on_write_q; /* Statistics fields. */ uint32_t hello_in; /* Hello message input count. */ uint32_t update_in; /* Update message input count. */ uint32_t query_in; /* Querry message input count. */ uint32_t reply_in; /* Reply message input count. */ uint32_t hello_out; /* Hello message output count. */ uint32_t update_out; /* Update message output count. */ uint32_t query_out; /* Query message output count. */ uint32_t reply_out; /* Reply message output count. */ uint32_t siaQuery_in; uint32_t siaQuery_out; uint32_t siaReply_in; uint32_t siaReply_out; uint32_t ack_out; uint32_t ack_in; uint32_t crypt_seqnum; /* Cryptographic Sequence Number */ /* Access-list. */ struct access_list *list[EIGRP_FILTER_MAX]; /* Prefix-list. */ struct prefix_list *prefix[EIGRP_FILTER_MAX]; /* Route-map. */ struct route_map *routemap[EIGRP_FILTER_MAX]; }; /* Determines if it is first or last packet * when packet consists of multiple packet * chunks because of many route TLV * (all won't fit into one packet) */ enum Packet_part_type { EIGRP_PACKET_PART_NA, EIGRP_PACKET_PART_FIRST, EIGRP_PACKET_PART_LAST }; /* Neighbor Data Structure */ struct eigrp_neighbor { /* This neighbor's parent eigrp interface. */ struct eigrp_interface *ei; /* EIGRP neighbor Information */ uint8_t state; /* neigbor status. */ uint32_t recv_sequence_number; /* Last received sequence Number. */ uint32_t init_sequence_number; /*If packet is unacknowledged, we try to send it again 16 times*/ uint8_t retrans_counter; struct in_addr src; /* Neighbor Src address. */ uint8_t os_rel_major; // system version - just for show uint8_t os_rel_minor; // system version - just for show uint8_t tlv_rel_major; // eigrp version - tells us what TLV format to // use uint8_t tlv_rel_minor; // eigrp version - tells us what TLV format to // use uint8_t K1; uint8_t K2; uint8_t K3; uint8_t K4; uint8_t K5; uint8_t K6; /* Timer values. */ uint16_t v_holddown; /* Threads. */ struct thread *t_holddown; struct thread *t_nbr_send_gr; /* thread for sending multiple GR packet chunks */ struct eigrp_fifo *retrans_queue; struct eigrp_fifo *multicast_queue; uint32_t crypt_seqnum; /* Cryptographic Sequence Number. */ /* prefixes not received from neighbor during Graceful restart */ struct list *nbr_gr_prefixes; /* prefixes not yet send to neighbor during Graceful restart */ struct list *nbr_gr_prefixes_send; /* if packet is first or last during Graceful restart */ enum Packet_part_type nbr_gr_packet_type; }; //--------------------------------------------------------------------------------------------------------------------------------------------- struct eigrp_packet { struct eigrp_packet *next; struct eigrp_packet *previous; /* Pointer to data stream. */ struct stream *s; /* IP destination address. */ struct in_addr dst; /*Packet retransmission thread*/ struct thread *t_retrans_timer; /*Packet retransmission counter*/ uint8_t retrans_counter; uint32_t sequence_number; /* EIGRP packet length. */ uint16_t length; struct eigrp_neighbor *nbr; }; struct eigrp_fifo { struct eigrp_packet *head; struct eigrp_packet *tail; unsigned long count; }; struct eigrp_header { uint8_t version; uint8_t opcode; uint16_t checksum; uint32_t flags; uint32_t sequence; uint32_t ack; uint16_t vrid; uint16_t ASNumber; char *tlv[0]; } __attribute__((packed)); /** * Generic TLV type used for packet decoding. * * +-----+------------------+ * | | | | * | Type| Len | Vector | * | | | | * +-----+------------------+ */ struct eigrp_tlv_hdr_type { uint16_t type; uint16_t length; uint8_t value[0]; } __attribute__((packed)); struct TLV_Parameter_Type { uint16_t type; uint16_t length; uint8_t K1; uint8_t K2; uint8_t K3; uint8_t K4; uint8_t K5; uint8_t K6; uint16_t hold_time; } __attribute__((packed)); struct TLV_MD5_Authentication_Type { uint16_t type; uint16_t length; uint16_t auth_type; uint16_t auth_length; uint32_t key_id; uint32_t key_sequence; uint8_t Nullpad[8]; uint8_t digest[EIGRP_AUTH_TYPE_MD5_LEN]; } __attribute__((packed)); struct TLV_SHA256_Authentication_Type { uint16_t type; uint16_t length; uint16_t auth_type; uint16_t auth_length; uint32_t key_id; uint32_t key_sequence; uint8_t Nullpad[8]; uint8_t digest[EIGRP_AUTH_TYPE_SHA256_LEN]; } __attribute__((packed)); struct TLV_Sequence_Type { uint16_t type; uint16_t length; uint8_t addr_length; struct in_addr *addresses; } __attribute__((packed)); struct TLV_Next_Multicast_Sequence { uint16_t type; uint16_t length; uint32_t multicast_sequence; } __attribute__((packed)); struct TLV_Software_Type { uint16_t type; uint16_t length; uint8_t vender_major; uint8_t vender_minor; uint8_t eigrp_major; uint8_t eigrp_minor; } __attribute__((packed)); struct TLV_IPv4_Internal_type { uint16_t type; uint16_t length; struct in_addr forward; /*Metrics*/ struct eigrp_metrics metric; uint8_t prefix_length; struct in_addr destination; } __attribute__((packed)); struct TLV_IPv4_External_type { uint16_t type; uint16_t length; struct in_addr next_hop; struct in_addr originating_router; uint32_t originating_as; uint32_t administrative_tag; uint32_t external_metric; uint16_t reserved; uint8_t external_protocol; uint8_t external_flags; /*Metrics*/ struct eigrp_metrics metric; uint8_t prefix_length; unsigned char destination_part[4]; struct in_addr destination; } __attribute__((packed)); /* EIGRP Peer Termination TLV - used for hard restart */ struct TLV_Peer_Termination_type { uint16_t type; uint16_t length; uint8_t unknown; uint32_t neighbor_ip; } __attribute__((packed)); /* Who executed Graceful restart */ enum GR_type { EIGRP_GR_MANUAL, EIGRP_GR_FILTER }; //--------------------------------------------------------------------------------------------------------------------------------------------- /* EIGRP Topology table node structure */ struct eigrp_prefix_entry { struct list *entries, *rij; uint32_t fdistance; // FD uint32_t rdistance; // RD uint32_t distance; // D struct eigrp_metrics reported_metric; // RD for sending uint8_t nt; // network type uint8_t state; // route fsm state uint8_t af; // address family uint8_t req_action; // required action struct prefix *destination; // If network type is REMOTE_EXTERNAL, pointer will have reference to // its external TLV struct TLV_IPv4_External_type *extTLV; uint64_t serno; /*Serial number for this entry. Increased with each change of entry*/ }; /* EIGRP Topology table record structure */ struct eigrp_nexthop_entry { struct eigrp_prefix_entry *prefix; uint32_t reported_distance; // distance reported by neighbor uint32_t distance; // sum of reported distance and link cost to // advertised neighbor struct eigrp_metrics reported_metric; struct eigrp_metrics total_metric; struct eigrp_neighbor *adv_router; // ip address of advertising neighbor uint8_t flags; // used for marking successor and FS struct eigrp_interface *ei; // pointer for case of connected entry }; //--------------------------------------------------------------------------------------------------------------------------------------------- typedef enum { EIGRP_CONNECTED, EIGRP_INT, EIGRP_EXT, } msg_data_t; /* EIGRP Finite State Machine */ struct eigrp_fsm_action_message { uint8_t packet_type; // UPDATE, QUERY, SIAQUERY, SIAREPLY struct eigrp *eigrp; // which thread sent mesg struct eigrp_neighbor *adv_router; // advertising neighbor struct eigrp_nexthop_entry *entry; struct eigrp_prefix_entry *prefix; msg_data_t data_type; // internal or external tlv type struct eigrp_metrics metrics; enum metric_change change; }; #endif /* _ZEBRA_EIGRP_STRUCTURES_H_ */ frr-7.2.1/eigrpd/eigrp_topology.c0000644000000000000000000003204113610377563013674 00000000000000/* * EIGRP Topology Table. * Copyright (C) 2013-2016 * Authors: * Donnie Savage * Jan Janovic * Matej Perina * Peter Orsag * Peter Paluch * Frantisek Gazo * Tomas Hvorkovy * Martin Kontsek * Lukas Koribsky * * This file is part of GNU Zebra. * * GNU Zebra is free software; you can redistribute it and/or modify it * under the terms of the GNU General Public License as published by the * Free Software Foundation; either version 2, or (at your option) any * later version. * * GNU Zebra 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; see the file COPYING; if not, write to the Free Software * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA */ #include #include "prefix.h" #include "table.h" #include "memory.h" #include "log.h" #include "linklist.h" #include "vty.h" #include "lib_errors.h" #include "eigrpd/eigrp_structs.h" #include "eigrpd/eigrpd.h" #include "eigrpd/eigrp_interface.h" #include "eigrpd/eigrp_neighbor.h" #include "eigrpd/eigrp_packet.h" #include "eigrpd/eigrp_zebra.h" #include "eigrpd/eigrp_vty.h" #include "eigrpd/eigrp_network.h" #include "eigrpd/eigrp_dump.h" #include "eigrpd/eigrp_topology.h" #include "eigrpd/eigrp_fsm.h" #include "eigrpd/eigrp_memory.h" static int eigrp_nexthop_entry_cmp(struct eigrp_nexthop_entry *, struct eigrp_nexthop_entry *); /* * Returns linkedlist used as topology table * cmp - assigned function for comparing topology nodes * del - assigned function executed before deleting topology node by list * function */ struct route_table *eigrp_topology_new(void) { return route_table_init(); } /* * Returns new created toplogy node * cmp - assigned function for comparing topology entry */ struct eigrp_prefix_entry *eigrp_prefix_entry_new(void) { struct eigrp_prefix_entry *new; new = XCALLOC(MTYPE_EIGRP_PREFIX_ENTRY, sizeof(struct eigrp_prefix_entry)); new->entries = list_new(); new->rij = list_new(); new->entries->cmp = (int (*)(void *, void *))eigrp_nexthop_entry_cmp; new->distance = new->fdistance = new->rdistance = EIGRP_MAX_METRIC; new->destination = NULL; return new; } /* * Topology entry comparison */ static int eigrp_nexthop_entry_cmp(struct eigrp_nexthop_entry *entry1, struct eigrp_nexthop_entry *entry2) { if (entry1->distance < entry2->distance) return -1; if (entry1->distance > entry2->distance) return 1; return 0; } /* * Returns new topology entry */ struct eigrp_nexthop_entry *eigrp_nexthop_entry_new(void) { struct eigrp_nexthop_entry *new; new = XCALLOC(MTYPE_EIGRP_NEXTHOP_ENTRY, sizeof(struct eigrp_nexthop_entry)); new->reported_distance = EIGRP_MAX_METRIC; new->distance = EIGRP_MAX_METRIC; return new; } /* * Freeing topology table list */ void eigrp_topology_free(struct eigrp *eigrp, struct route_table *table) { eigrp_topology_delete_all(eigrp, table); route_table_finish(table); } /* * Adding topology node to topology table */ void eigrp_prefix_entry_add(struct route_table *topology, struct eigrp_prefix_entry *pe) { struct route_node *rn; rn = route_node_get(topology, pe->destination); if (rn->info) { if (IS_DEBUG_EIGRP_EVENT) { char buf[PREFIX_STRLEN]; zlog_debug( "%s: %s Should we have found this entry in the topo table?", __PRETTY_FUNCTION__, prefix2str(pe->destination, buf, sizeof(buf))); } route_unlock_node(rn); } rn->info = pe; } /* * Adding topology entry to topology node */ void eigrp_nexthop_entry_add(struct eigrp *eigrp, struct eigrp_prefix_entry *node, struct eigrp_nexthop_entry *entry) { struct list *l = list_new(); listnode_add(l, entry); if (listnode_lookup(node->entries, entry) == NULL) { listnode_add_sort(node->entries, entry); entry->prefix = node; eigrp_zebra_route_add(eigrp, node->destination, l, node->fdistance); } list_delete(&l); } /* * Deleting topology node from topology table */ void eigrp_prefix_entry_delete(struct eigrp *eigrp, struct route_table *table, struct eigrp_prefix_entry *pe) { struct eigrp_nexthop_entry *ne; struct listnode *node, *nnode; struct route_node *rn; if (!eigrp) return; rn = route_node_lookup(table, pe->destination); if (!rn) return; /* * Emergency removal of the node from this list. * Whatever it is. */ listnode_delete(eigrp->topology_changes_internalIPV4, pe); for (ALL_LIST_ELEMENTS(pe->entries, node, nnode, ne)) eigrp_nexthop_entry_delete(eigrp, pe, ne); list_delete(&pe->entries); list_delete(&pe->rij); eigrp_zebra_route_delete(eigrp, pe->destination); prefix_free(pe->destination); rn->info = NULL; route_unlock_node(rn); // Lookup above route_unlock_node(rn); // Initial creation XFREE(MTYPE_EIGRP_PREFIX_ENTRY, pe); } /* * Deleting topology entry from topology node */ void eigrp_nexthop_entry_delete(struct eigrp *eigrp, struct eigrp_prefix_entry *node, struct eigrp_nexthop_entry *entry) { if (listnode_lookup(node->entries, entry) != NULL) { listnode_delete(node->entries, entry); eigrp_zebra_route_delete(eigrp, node->destination); XFREE(MTYPE_EIGRP_NEXTHOP_ENTRY, entry); } } /* * Deleting all nodes from topology table */ void eigrp_topology_delete_all(struct eigrp *eigrp, struct route_table *topology) { struct route_node *rn; struct eigrp_prefix_entry *pe; for (rn = route_top(topology); rn; rn = route_next(rn)) { pe = rn->info; if (!pe) continue; eigrp_prefix_entry_delete(eigrp, topology, pe); } } struct eigrp_prefix_entry * eigrp_topology_table_lookup_ipv4(struct route_table *table, struct prefix *address) { struct eigrp_prefix_entry *pe; struct route_node *rn; rn = route_node_lookup(table, address); if (!rn) return NULL; pe = rn->info; route_unlock_node(rn); return pe; } /* * For a future optimization, put the successor list into it's * own separate list from the full list? * * That way we can clean up all the list_new and list_delete's * that we are doing. DBS */ struct list *eigrp_topology_get_successor(struct eigrp_prefix_entry *table_node) { struct list *successors = list_new(); struct eigrp_nexthop_entry *data; struct listnode *node1, *node2; for (ALL_LIST_ELEMENTS(table_node->entries, node1, node2, data)) { if (data->flags & EIGRP_NEXTHOP_ENTRY_SUCCESSOR_FLAG) { listnode_add(successors, data); } } /* * If we have no successors return NULL */ if (!successors->count) { list_delete(&successors); successors = NULL; } return successors; } struct list * eigrp_topology_get_successor_max(struct eigrp_prefix_entry *table_node, unsigned int maxpaths) { struct list *successors = eigrp_topology_get_successor(table_node); if (successors && successors->count > maxpaths) { do { struct listnode *node = listtail(successors); list_delete_node(successors, node); } while (successors->count > maxpaths); } return successors; } struct eigrp_nexthop_entry * eigrp_prefix_entry_lookup(struct list *entries, struct eigrp_neighbor *nbr) { struct eigrp_nexthop_entry *data; struct listnode *node, *nnode; for (ALL_LIST_ELEMENTS(entries, node, nnode, data)) { if (data->adv_router == nbr) { return data; } } return NULL; } /* Lookup all prefixes from specified neighbor */ struct list *eigrp_neighbor_prefixes_lookup(struct eigrp *eigrp, struct eigrp_neighbor *nbr) { struct listnode *node2, *node22; struct eigrp_nexthop_entry *entry; struct eigrp_prefix_entry *pe; struct route_node *rn; /* create new empty list for prefixes storage */ struct list *prefixes = list_new(); /* iterate over all prefixes in topology table */ for (rn = route_top(eigrp->topology_table); rn; rn = route_next(rn)) { if (!rn->info) continue; pe = rn->info; /* iterate over all neighbor entry in prefix */ for (ALL_LIST_ELEMENTS(pe->entries, node2, node22, entry)) { /* if entry is from specified neighbor, add to list */ if (entry->adv_router == nbr) { listnode_add(prefixes, pe); } } } /* return list of prefixes from specified neighbor */ return prefixes; } enum metric_change eigrp_topology_update_distance(struct eigrp_fsm_action_message *msg) { struct eigrp *eigrp = msg->eigrp; struct eigrp_prefix_entry *prefix = msg->prefix; struct eigrp_nexthop_entry *entry = msg->entry; enum metric_change change = METRIC_SAME; uint32_t new_reported_distance; assert(entry); switch (msg->data_type) { case EIGRP_CONNECTED: if (prefix->nt == EIGRP_TOPOLOGY_TYPE_CONNECTED) return change; change = METRIC_DECREASE; break; case EIGRP_INT: if (prefix->nt == EIGRP_TOPOLOGY_TYPE_CONNECTED) { change = METRIC_INCREASE; goto distance_done; } if (eigrp_metrics_is_same(msg->metrics, entry->reported_metric)) { return change; // No change } new_reported_distance = eigrp_calculate_metrics(eigrp, msg->metrics); if (entry->reported_distance < new_reported_distance) { change = METRIC_INCREASE; goto distance_done; } else change = METRIC_DECREASE; entry->reported_metric = msg->metrics; entry->reported_distance = new_reported_distance; eigrp_calculate_metrics(eigrp, msg->metrics); entry->distance = eigrp_calculate_total_metrics(eigrp, entry); break; case EIGRP_EXT: if (prefix->nt == EIGRP_TOPOLOGY_TYPE_REMOTE_EXTERNAL) { if (eigrp_metrics_is_same(msg->metrics, entry->reported_metric)) return change; } else { change = METRIC_INCREASE; goto distance_done; } break; default: flog_err(EC_LIB_DEVELOPMENT, "%s: Please implement handler", __PRETTY_FUNCTION__); break; } distance_done: /* * Move to correct position in list according to new distance */ listnode_delete(prefix->entries, entry); listnode_add_sort(prefix->entries, entry); return change; } void eigrp_topology_update_all_node_flags(struct eigrp *eigrp) { struct eigrp_prefix_entry *pe; struct route_node *rn; if (!eigrp) return; for (rn = route_top(eigrp->topology_table); rn; rn = route_next(rn)) { pe = rn->info; if (!pe) continue; eigrp_topology_update_node_flags(eigrp, pe); } } void eigrp_topology_update_node_flags(struct eigrp *eigrp, struct eigrp_prefix_entry *dest) { struct listnode *node; struct eigrp_nexthop_entry *entry; for (ALL_LIST_ELEMENTS_RO(dest->entries, node, entry)) { if (entry->reported_distance < dest->fdistance) { // is feasible successor, can be successor if (((uint64_t)entry->distance <= (uint64_t)dest->distance * (uint64_t)eigrp->variance) && entry->distance != EIGRP_MAX_METRIC) { // is successor entry->flags |= EIGRP_NEXTHOP_ENTRY_SUCCESSOR_FLAG; entry->flags &= ~EIGRP_NEXTHOP_ENTRY_FSUCCESSOR_FLAG; } else { // is feasible successor only entry->flags |= EIGRP_NEXTHOP_ENTRY_FSUCCESSOR_FLAG; entry->flags &= ~EIGRP_NEXTHOP_ENTRY_SUCCESSOR_FLAG; } } else { entry->flags &= ~EIGRP_NEXTHOP_ENTRY_FSUCCESSOR_FLAG; entry->flags &= ~EIGRP_NEXTHOP_ENTRY_SUCCESSOR_FLAG; } } } void eigrp_update_routing_table(struct eigrp *eigrp, struct eigrp_prefix_entry *prefix) { struct list *successors; struct listnode *node; struct eigrp_nexthop_entry *entry; successors = eigrp_topology_get_successor_max(prefix, eigrp->max_paths); if (successors) { eigrp_zebra_route_add(eigrp, prefix->destination, successors, prefix->fdistance); for (ALL_LIST_ELEMENTS_RO(successors, node, entry)) entry->flags |= EIGRP_NEXTHOP_ENTRY_INTABLE_FLAG; list_delete(&successors); } else { eigrp_zebra_route_delete(eigrp, prefix->destination); for (ALL_LIST_ELEMENTS_RO(prefix->entries, node, entry)) entry->flags &= ~EIGRP_NEXTHOP_ENTRY_INTABLE_FLAG; } } void eigrp_topology_neighbor_down(struct eigrp *eigrp, struct eigrp_neighbor *nbr) { struct listnode *node2, *node22; struct eigrp_prefix_entry *pe; struct eigrp_nexthop_entry *entry; struct route_node *rn; for (rn = route_top(eigrp->topology_table); rn; rn = route_next(rn)) { pe = rn->info; if (!pe) continue; for (ALL_LIST_ELEMENTS(pe->entries, node2, node22, entry)) { struct eigrp_fsm_action_message msg; if (entry->adv_router != nbr) continue; msg.metrics.delay = EIGRP_MAX_METRIC; msg.packet_type = EIGRP_OPC_UPDATE; msg.eigrp = eigrp; msg.data_type = EIGRP_INT; msg.adv_router = nbr; msg.entry = entry; msg.prefix = pe; eigrp_fsm_event(&msg); } } eigrp_query_send_all(eigrp); eigrp_update_send_all(eigrp, nbr->ei); } void eigrp_update_topology_table_prefix(struct eigrp *eigrp, struct route_table *table, struct eigrp_prefix_entry *prefix) { struct listnode *node1, *node2; struct eigrp_nexthop_entry *entry; for (ALL_LIST_ELEMENTS(prefix->entries, node1, node2, entry)) { if (entry->distance == EIGRP_MAX_METRIC) { eigrp_nexthop_entry_delete(eigrp, prefix, entry); } } if (prefix->distance == EIGRP_MAX_METRIC && prefix->nt != EIGRP_TOPOLOGY_TYPE_CONNECTED) { eigrp_prefix_entry_delete(eigrp, table, prefix); } } frr-7.2.1/eigrpd/eigrp_topology.h0000644000000000000000000000622113610377563013702 00000000000000/* * EIGRP Topology Table. * Copyright (C) 2013-2016 * Authors: * Donnie Savage * Jan Janovic * Matej Perina * Peter Orsag * Peter Paluch * Frantisek Gazo * Tomas Hvorkovy * Martin Kontsek * Lukas Koribsky * * This file is part of GNU Zebra. * * GNU Zebra is free software; you can redistribute it and/or modify it * under the terms of the GNU General Public License as published by the * Free Software Foundation; either version 2, or (at your option) any * later version. * * GNU Zebra 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; see the file COPYING; if not, write to the Free Software * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA */ #ifndef _ZEBRA_EIGRP_TOPOLOGY_H #define _ZEBRA_EIGRP_TOPOLOGY_H /* EIGRP Topology table related functions. */ extern struct route_table *eigrp_topology_new(void); extern void eigrp_topology_init(struct route_table *table); extern struct eigrp_prefix_entry *eigrp_prefix_entry_new(void); extern struct eigrp_nexthop_entry *eigrp_nexthop_entry_new(void); extern void eigrp_topology_free(struct eigrp *eigrp, struct route_table *table); extern void eigrp_prefix_entry_add(struct route_table *table, struct eigrp_prefix_entry *pe); extern void eigrp_nexthop_entry_add(struct eigrp *eigrp, struct eigrp_prefix_entry *pe, struct eigrp_nexthop_entry *ne); extern void eigrp_prefix_entry_delete(struct eigrp *eigrp, struct route_table *table, struct eigrp_prefix_entry *pe); extern void eigrp_nexthop_entry_delete(struct eigrp *eigrp, struct eigrp_prefix_entry *pe, struct eigrp_nexthop_entry *ne); extern void eigrp_topology_delete_all(struct eigrp *eigrp, struct route_table *table); extern struct eigrp_prefix_entry * eigrp_topology_table_lookup_ipv4(struct route_table *table, struct prefix *p); extern struct list *eigrp_topology_get_successor(struct eigrp_prefix_entry *pe); extern struct list * eigrp_topology_get_successor_max(struct eigrp_prefix_entry *pe, unsigned int maxpaths); extern struct eigrp_nexthop_entry * eigrp_prefix_entry_lookup(struct list *entries, struct eigrp_neighbor *neigh); extern struct list *eigrp_neighbor_prefixes_lookup(struct eigrp *eigrp, struct eigrp_neighbor *n); extern void eigrp_topology_update_all_node_flags(struct eigrp *eigrp); extern void eigrp_topology_update_node_flags(struct eigrp *eigrp, struct eigrp_prefix_entry *pe); extern enum metric_change eigrp_topology_update_distance(struct eigrp_fsm_action_message *msg); extern void eigrp_update_routing_table(struct eigrp *eigrp, struct eigrp_prefix_entry *pe); extern void eigrp_topology_neighbor_down(struct eigrp *eigrp, struct eigrp_neighbor *neigh); extern void eigrp_update_topology_table_prefix(struct eigrp *eigrp, struct route_table *table, struct eigrp_prefix_entry *pe); #endif frr-7.2.1/eigrpd/eigrp_update.c0000644000000000000000000007062313610377563013312 00000000000000/* * EIGRP Sending and Receiving EIGRP Update Packets. * Copyright (C) 2013-2016 * Authors: * Donnie Savage * Jan Janovic * Matej Perina * Peter Orsag * Peter Paluch * Frantisek Gazo * Tomas Hvorkovy * Martin Kontsek * Lukas Koribsky * * This file is part of GNU Zebra. * * GNU Zebra is free software; you can redistribute it and/or modify it * under the terms of the GNU General Public License as published by the * Free Software Foundation; either version 2, or (at your option) any * later version. * * GNU Zebra 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; see the file COPYING; if not, write to the Free Software * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA */ #include #include "thread.h" #include "memory.h" #include "linklist.h" #include "prefix.h" #include "if.h" #include "table.h" #include "sockunion.h" #include "stream.h" #include "log.h" #include "sockopt.h" #include "checksum.h" #include "md5.h" #include "vty.h" #include "plist.h" #include "plist_int.h" #include "routemap.h" #include "vty.h" #include "eigrpd/eigrp_structs.h" #include "eigrpd/eigrpd.h" #include "eigrpd/eigrp_interface.h" #include "eigrpd/eigrp_neighbor.h" #include "eigrpd/eigrp_packet.h" #include "eigrpd/eigrp_zebra.h" #include "eigrpd/eigrp_vty.h" #include "eigrpd/eigrp_dump.h" #include "eigrpd/eigrp_macros.h" #include "eigrpd/eigrp_topology.h" #include "eigrpd/eigrp_fsm.h" #include "eigrpd/eigrp_network.h" #include "eigrpd/eigrp_memory.h" bool eigrp_update_prefix_apply(struct eigrp *eigrp, struct eigrp_interface *ei, int in, struct prefix *prefix) { struct access_list *alist; struct prefix_list *plist; alist = eigrp->list[in]; if (alist && access_list_apply(alist, prefix) == FILTER_DENY) return true; plist = eigrp->prefix[in]; if (plist && prefix_list_apply(plist, prefix) == PREFIX_DENY) return true; alist = ei->list[in]; if (alist && access_list_apply(alist, prefix) == FILTER_DENY) return true; plist = ei->prefix[in]; if (plist && prefix_list_apply(plist, prefix) == PREFIX_DENY) return true; return false; } /** * @fn remove_received_prefix_gr * * @param[in] nbr_prefixes List of neighbor prefixes * @param[in] recv_prefix Prefix which needs to be removed from * list * * @return void * * @par * Function is used for removing received prefix * from list of neighbor prefixes */ static void remove_received_prefix_gr(struct list *nbr_prefixes, struct eigrp_prefix_entry *recv_prefix) { struct listnode *node1, *node11; struct eigrp_prefix_entry *prefix = NULL; /* iterate over all prefixes in list */ for (ALL_LIST_ELEMENTS(nbr_prefixes, node1, node11, prefix)) { /* remove prefix from list if found */ if (prefix == recv_prefix) { listnode_delete(nbr_prefixes, prefix); } } } /** * @fn eigrp_update_receive_GR_ask * * @param[in] eigrp EIGRP process * @param[in] nbr Neighbor update of who we * received * @param[in] nbr_prefixes Prefixes which weren't advertised * * @return void * * @par * Function is used for notifying FSM about prefixes which * weren't advertised by neighbor: * We will send message to FSM with prefix delay set to infinity. */ static void eigrp_update_receive_GR_ask(struct eigrp *eigrp, struct eigrp_neighbor *nbr, struct list *nbr_prefixes) { struct listnode *node1; struct eigrp_prefix_entry *prefix; struct eigrp_fsm_action_message fsm_msg; /* iterate over all prefixes which weren't advertised by neighbor */ for (ALL_LIST_ELEMENTS_RO(nbr_prefixes, node1, prefix)) { char buffer[PREFIX_STRLEN]; zlog_debug( "GR receive: Neighbor not advertised %s", prefix2str(prefix->destination, buffer, PREFIX_STRLEN)); fsm_msg.metrics = prefix->reported_metric; /* set delay to MAX */ fsm_msg.metrics.delay = EIGRP_MAX_METRIC; struct eigrp_nexthop_entry *entry = eigrp_prefix_entry_lookup(prefix->entries, nbr); fsm_msg.packet_type = EIGRP_OPC_UPDATE; fsm_msg.eigrp = eigrp; fsm_msg.data_type = EIGRP_INT; fsm_msg.adv_router = nbr; fsm_msg.entry = entry; fsm_msg.prefix = prefix; /* send message to FSM */ eigrp_fsm_event(&fsm_msg); } } /* * EIGRP UPDATE read function */ void eigrp_update_receive(struct eigrp *eigrp, struct ip *iph, struct eigrp_header *eigrph, struct stream *s, struct eigrp_interface *ei, int size) { struct eigrp_neighbor *nbr; struct TLV_IPv4_Internal_type *tlv; struct eigrp_prefix_entry *pe; struct eigrp_nexthop_entry *ne; uint32_t flags; uint16_t type; uint16_t length; uint8_t same; struct prefix dest_addr; uint8_t graceful_restart; uint8_t graceful_restart_final; struct list *nbr_prefixes = NULL; /* increment statistics. */ ei->update_in++; /* get neighbor struct */ nbr = eigrp_nbr_get(ei, eigrph, iph); /* neighbor must be valid, eigrp_nbr_get creates if none existed */ assert(nbr); flags = ntohl(eigrph->flags); if (flags & EIGRP_CR_FLAG) { return; } same = 0; graceful_restart = 0; graceful_restart_final = 0; if ((nbr->recv_sequence_number) == (ntohl(eigrph->sequence))) same = 1; nbr->recv_sequence_number = ntohl(eigrph->sequence); if (IS_DEBUG_EIGRP_PACKET(0, RECV)) zlog_debug( "Processing Update size[%u] int(%s) nbr(%s) seq [%u] flags [%0x]", size, ifindex2ifname(nbr->ei->ifp->ifindex, eigrp->vrf_id), inet_ntoa(nbr->src), nbr->recv_sequence_number, flags); if ((flags == (EIGRP_INIT_FLAG + EIGRP_RS_FLAG + EIGRP_EOT_FLAG)) && (!same)) { /* Graceful restart Update received with all routes */ zlog_info("Neighbor %s (%s) is resync: peer graceful-restart", inet_ntoa(nbr->src), ifindex2ifname(nbr->ei->ifp->ifindex, eigrp->vrf_id)); /* get all prefixes from neighbor from topology table */ nbr_prefixes = eigrp_neighbor_prefixes_lookup(eigrp, nbr); graceful_restart = 1; graceful_restart_final = 1; } else if ((flags == (EIGRP_INIT_FLAG + EIGRP_RS_FLAG)) && (!same)) { /* Graceful restart Update received, routes also in next packet */ zlog_info("Neighbor %s (%s) is resync: peer graceful-restart", inet_ntoa(nbr->src), ifindex2ifname(nbr->ei->ifp->ifindex, eigrp->vrf_id)); /* get all prefixes from neighbor from topology table */ nbr_prefixes = eigrp_neighbor_prefixes_lookup(eigrp, nbr); /* save prefixes to neighbor for later use */ nbr->nbr_gr_prefixes = nbr_prefixes; graceful_restart = 1; graceful_restart_final = 0; } else if ((flags == (EIGRP_EOT_FLAG)) && (!same)) { /* If there was INIT+RS Update packet before, * consider this as GR EOT */ if (nbr->nbr_gr_prefixes != NULL) { /* this is final packet of GR */ nbr_prefixes = nbr->nbr_gr_prefixes; nbr->nbr_gr_prefixes = NULL; graceful_restart = 1; graceful_restart_final = 1; } } else if ((flags == (0)) && (!same)) { /* If there was INIT+RS Update packet before, * consider this as GR not final packet */ if (nbr->nbr_gr_prefixes != NULL) { /* this is GR not final route packet */ nbr_prefixes = nbr->nbr_gr_prefixes; graceful_restart = 1; graceful_restart_final = 0; } } else if ((flags & EIGRP_INIT_FLAG) && (!same)) { /* When in pending state, send INIT update only if it wasn't already sent before (only if init_sequence is 0) */ if ((nbr->state == EIGRP_NEIGHBOR_PENDING) && (nbr->init_sequence_number == 0)) eigrp_update_send_init(nbr); if (nbr->state == EIGRP_NEIGHBOR_UP) { eigrp_nbr_state_set(nbr, EIGRP_NEIGHBOR_DOWN); eigrp_topology_neighbor_down(nbr->ei->eigrp, nbr); nbr->recv_sequence_number = ntohl(eigrph->sequence); zlog_info("Neighbor %s (%s) is down: peer restarted", inet_ntoa(nbr->src), ifindex2ifname(nbr->ei->ifp->ifindex, eigrp->vrf_id)); eigrp_nbr_state_set(nbr, EIGRP_NEIGHBOR_PENDING); zlog_info("Neighbor %s (%s) is pending: new adjacency", inet_ntoa(nbr->src), ifindex2ifname(nbr->ei->ifp->ifindex, eigrp->vrf_id)); eigrp_update_send_init(nbr); } } /*If there is topology information*/ while (s->endp > s->getp) { type = stream_getw(s); switch (type) { case EIGRP_TLV_IPv4_INT: stream_set_getp(s, s->getp - sizeof(uint16_t)); tlv = eigrp_read_ipv4_tlv(s); /*searching if destination exists */ dest_addr.family = AF_INET; dest_addr.u.prefix4 = tlv->destination; dest_addr.prefixlen = tlv->prefix_length; struct eigrp_prefix_entry *dest = eigrp_topology_table_lookup_ipv4( eigrp->topology_table, &dest_addr); /*if exists it comes to DUAL*/ if (dest != NULL) { /* remove received prefix from neighbor prefix * list if in GR */ if (graceful_restart) remove_received_prefix_gr(nbr_prefixes, dest); struct eigrp_fsm_action_message msg; struct eigrp_nexthop_entry *entry = eigrp_prefix_entry_lookup(dest->entries, nbr); msg.packet_type = EIGRP_OPC_UPDATE; msg.eigrp = eigrp; msg.data_type = EIGRP_INT; msg.adv_router = nbr; msg.metrics = tlv->metric; msg.entry = entry; msg.prefix = dest; eigrp_fsm_event(&msg); } else { /*Here comes topology information save*/ pe = eigrp_prefix_entry_new(); pe->serno = eigrp->serno; pe->destination = (struct prefix *)prefix_ipv4_new(); prefix_copy(pe->destination, &dest_addr); pe->af = AF_INET; pe->state = EIGRP_FSM_STATE_PASSIVE; pe->nt = EIGRP_TOPOLOGY_TYPE_REMOTE; ne = eigrp_nexthop_entry_new(); ne->ei = ei; ne->adv_router = nbr; ne->reported_metric = tlv->metric; ne->reported_distance = eigrp_calculate_metrics( eigrp, tlv->metric); /* * Filtering */ if (eigrp_update_prefix_apply(eigrp, ei, EIGRP_FILTER_IN, &dest_addr)) ne->reported_metric.delay = EIGRP_MAX_METRIC; ne->distance = eigrp_calculate_total_metrics( eigrp, ne); pe->fdistance = pe->distance = pe->rdistance = ne->distance; ne->prefix = pe; ne->flags = EIGRP_NEXTHOP_ENTRY_SUCCESSOR_FLAG; eigrp_prefix_entry_add(eigrp->topology_table, pe); eigrp_nexthop_entry_add(eigrp, pe, ne); pe->distance = pe->fdistance = pe->rdistance = ne->distance; pe->reported_metric = ne->total_metric; eigrp_topology_update_node_flags(eigrp, pe); pe->req_action |= EIGRP_FSM_NEED_UPDATE; listnode_add( eigrp->topology_changes_internalIPV4, pe); } eigrp_IPv4_InternalTLV_free(tlv); break; case EIGRP_TLV_IPv4_EXT: /* DVS: processing of external routes needs packet and fsm work. * for now, lets just not creash the box */ default: length = stream_getw(s); // -2 for type, -2 for len for (length -= 4; length; length--) { (void)stream_getc(s); } } } /* ask about prefixes not present in GR update, * if this is final GR packet */ if (graceful_restart_final) { eigrp_update_receive_GR_ask(eigrp, nbr, nbr_prefixes); } /* * We don't need to send separate Ack for INIT Update. INIT will be * acked in EOT Update. */ if ((nbr->state == EIGRP_NEIGHBOR_UP) && !(flags == EIGRP_INIT_FLAG)) { eigrp_hello_send_ack(nbr); } eigrp_query_send_all(eigrp); eigrp_update_send_all(eigrp, ei); if (nbr_prefixes) list_delete(&nbr_prefixes); } /*send EIGRP Update packet*/ void eigrp_update_send_init(struct eigrp_neighbor *nbr) { struct eigrp_packet *ep; uint16_t length = EIGRP_HEADER_LEN; ep = eigrp_packet_new(EIGRP_PACKET_MTU(nbr->ei->ifp->mtu), nbr); /* Prepare EIGRP INIT UPDATE header */ if (IS_DEBUG_EIGRP_PACKET(0, RECV)) zlog_debug("Enqueuing Update Init Seq [%u] Ack [%u]", nbr->ei->eigrp->sequence_number, nbr->recv_sequence_number); eigrp_packet_header_init( EIGRP_OPC_UPDATE, nbr->ei->eigrp, ep->s, EIGRP_INIT_FLAG, nbr->ei->eigrp->sequence_number, nbr->recv_sequence_number); // encode Authentication TLV, if needed if ((nbr->ei->params.auth_type == EIGRP_AUTH_TYPE_MD5) && (nbr->ei->params.auth_keychain != NULL)) { length += eigrp_add_authTLV_MD5_to_stream(ep->s, nbr->ei); eigrp_make_md5_digest(nbr->ei, ep->s, EIGRP_AUTH_UPDATE_INIT_FLAG); } /* EIGRP Checksum */ eigrp_packet_checksum(nbr->ei, ep->s, length); ep->length = length; ep->dst.s_addr = nbr->src.s_addr; /*This ack number we await from neighbor*/ nbr->init_sequence_number = nbr->ei->eigrp->sequence_number; ep->sequence_number = nbr->ei->eigrp->sequence_number; if (IS_DEBUG_EIGRP_PACKET(0, RECV)) zlog_debug("Enqueuing Update Init Len [%u] Seq [%u] Dest [%s]", ep->length, ep->sequence_number, inet_ntoa(ep->dst)); /*Put packet to retransmission queue*/ eigrp_fifo_push(nbr->retrans_queue, ep); if (nbr->retrans_queue->count == 1) { eigrp_send_packet_reliably(nbr); } } static void eigrp_update_place_on_nbr_queue(struct eigrp_neighbor *nbr, struct eigrp_packet *ep, uint32_t seq_no, int length) { if ((nbr->ei->params.auth_type == EIGRP_AUTH_TYPE_MD5) && (nbr->ei->params.auth_keychain != NULL)) { eigrp_make_md5_digest(nbr->ei, ep->s, EIGRP_AUTH_UPDATE_FLAG); } /* EIGRP Checksum */ eigrp_packet_checksum(nbr->ei, ep->s, length); ep->length = length; ep->dst.s_addr = nbr->src.s_addr; /*This ack number we await from neighbor*/ ep->sequence_number = seq_no; if (IS_DEBUG_EIGRP_PACKET(0, RECV)) zlog_debug("Enqueuing Update Init Len [%u] Seq [%u] Dest [%s]", ep->length, ep->sequence_number, inet_ntoa(ep->dst)); /*Put packet to retransmission queue*/ eigrp_fifo_push(nbr->retrans_queue, ep); if (nbr->retrans_queue->count == 1) eigrp_send_packet_reliably(nbr); } static void eigrp_update_send_to_all_nbrs(struct eigrp_interface *ei, struct eigrp_packet *ep) { struct listnode *node, *nnode; struct eigrp_neighbor *nbr; bool packet_sent = false; for (ALL_LIST_ELEMENTS(ei->nbrs, node, nnode, nbr)) { struct eigrp_packet *ep_dup; if (nbr->state != EIGRP_NEIGHBOR_UP) continue; if (packet_sent) ep_dup = eigrp_packet_duplicate(ep, NULL); else ep_dup = ep; ep_dup->nbr = nbr; packet_sent = true; /*Put packet to retransmission queue*/ eigrp_fifo_push(nbr->retrans_queue, ep_dup); if (nbr->retrans_queue->count == 1) { eigrp_send_packet_reliably(nbr); } } if (!packet_sent) eigrp_packet_free(ep); } void eigrp_update_send_EOT(struct eigrp_neighbor *nbr) { struct eigrp_packet *ep; uint16_t length = EIGRP_HEADER_LEN; struct eigrp_nexthop_entry *te; struct eigrp_prefix_entry *pe; struct listnode *node2, *nnode2; struct eigrp_interface *ei = nbr->ei; struct eigrp *eigrp = ei->eigrp; struct prefix *dest_addr; uint32_t seq_no = eigrp->sequence_number; uint16_t eigrp_mtu = EIGRP_PACKET_MTU(ei->ifp->mtu); struct route_node *rn; ep = eigrp_packet_new(eigrp_mtu, nbr); /* Prepare EIGRP EOT UPDATE header */ eigrp_packet_header_init(EIGRP_OPC_UPDATE, eigrp, ep->s, EIGRP_EOT_FLAG, seq_no, nbr->recv_sequence_number); // encode Authentication TLV, if needed if ((ei->params.auth_type == EIGRP_AUTH_TYPE_MD5) && (ei->params.auth_keychain != NULL)) { length += eigrp_add_authTLV_MD5_to_stream(ep->s, ei); } for (rn = route_top(eigrp->topology_table); rn; rn = route_next(rn)) { if (!rn->info) continue; pe = rn->info; for (ALL_LIST_ELEMENTS(pe->entries, node2, nnode2, te)) { if (eigrp_nbr_split_horizon_check(te, ei)) continue; if ((length + EIGRP_TLV_MAX_IPV4_BYTE) > eigrp_mtu) { eigrp_update_place_on_nbr_queue(nbr, ep, seq_no, length); seq_no++; length = EIGRP_HEADER_LEN; ep = eigrp_packet_new(eigrp_mtu, nbr); eigrp_packet_header_init( EIGRP_OPC_UPDATE, nbr->ei->eigrp, ep->s, EIGRP_EOT_FLAG, seq_no, nbr->recv_sequence_number); if ((ei->params.auth_type == EIGRP_AUTH_TYPE_MD5) && (ei->params.auth_keychain != NULL)) { length += eigrp_add_authTLV_MD5_to_stream( ep->s, ei); } } /* Get destination address from prefix */ dest_addr = pe->destination; /* Check if any list fits */ if (eigrp_update_prefix_apply( eigrp, ei, EIGRP_FILTER_OUT, dest_addr)) continue; else { length += eigrp_add_internalTLV_to_stream(ep->s, pe); } } } eigrp_update_place_on_nbr_queue(nbr, ep, seq_no, length); eigrp->sequence_number = seq_no++; } void eigrp_update_send(struct eigrp_interface *ei) { struct eigrp_packet *ep; struct listnode *node, *nnode; struct eigrp_prefix_entry *pe; uint8_t has_tlv; struct eigrp *eigrp = ei->eigrp; struct prefix *dest_addr; uint32_t seq_no = eigrp->sequence_number; uint16_t eigrp_mtu = EIGRP_PACKET_MTU(ei->ifp->mtu); if (ei->nbrs->count == 0) return; uint16_t length = EIGRP_HEADER_LEN; ep = eigrp_packet_new(eigrp_mtu, NULL); /* Prepare EIGRP INIT UPDATE header */ eigrp_packet_header_init(EIGRP_OPC_UPDATE, eigrp, ep->s, 0, seq_no, 0); // encode Authentication TLV, if needed if ((ei->params.auth_type == EIGRP_AUTH_TYPE_MD5) && (ei->params.auth_keychain != NULL)) { length += eigrp_add_authTLV_MD5_to_stream(ep->s, ei); } has_tlv = 0; for (ALL_LIST_ELEMENTS(ei->eigrp->topology_changes_internalIPV4, node, nnode, pe)) { struct eigrp_nexthop_entry *ne; if (!(pe->req_action & EIGRP_FSM_NEED_UPDATE)) continue; ne = listnode_head(pe->entries); if (eigrp_nbr_split_horizon_check(ne, ei)) continue; if ((length + EIGRP_TLV_MAX_IPV4_BYTE) > eigrp_mtu) { if ((ei->params.auth_type == EIGRP_AUTH_TYPE_MD5) && (ei->params.auth_keychain != NULL)) { eigrp_make_md5_digest(ei, ep->s, EIGRP_AUTH_UPDATE_FLAG); } eigrp_packet_checksum(ei, ep->s, length); ep->length = length; ep->dst.s_addr = htonl(EIGRP_MULTICAST_ADDRESS); ep->sequence_number = seq_no; seq_no++; eigrp_update_send_to_all_nbrs(ei, ep); length = EIGRP_HEADER_LEN; ep = eigrp_packet_new(eigrp_mtu, NULL); eigrp_packet_header_init(EIGRP_OPC_UPDATE, eigrp, ep->s, 0, seq_no, 0); if ((ei->params.auth_type == EIGRP_AUTH_TYPE_MD5) && (ei->params.auth_keychain != NULL)) { length += eigrp_add_authTLV_MD5_to_stream(ep->s, ei); } has_tlv = 0; } /* Get destination address from prefix */ dest_addr = pe->destination; if (eigrp_update_prefix_apply(eigrp, ei, EIGRP_FILTER_OUT, dest_addr)) { // pe->reported_metric.delay = EIGRP_MAX_METRIC; continue; } else { length += eigrp_add_internalTLV_to_stream(ep->s, pe); has_tlv = 1; } } if (!has_tlv) { eigrp_packet_free(ep); return; } if ((ei->params.auth_type == EIGRP_AUTH_TYPE_MD5) && (ei->params.auth_keychain != NULL)) { eigrp_make_md5_digest(ei, ep->s, EIGRP_AUTH_UPDATE_FLAG); } /* EIGRP Checksum */ eigrp_packet_checksum(ei, ep->s, length); ep->length = length; ep->dst.s_addr = htonl(EIGRP_MULTICAST_ADDRESS); /*This ack number we await from neighbor*/ ep->sequence_number = eigrp->sequence_number; if (IS_DEBUG_EIGRP_PACKET(0, RECV)) zlog_debug("Enqueuing Update length[%u] Seq [%u]", length, ep->sequence_number); eigrp_update_send_to_all_nbrs(ei, ep); ei->eigrp->sequence_number = seq_no++; } void eigrp_update_send_all(struct eigrp *eigrp, struct eigrp_interface *exception) { struct eigrp_interface *iface; struct listnode *node, *node2, *nnode2; struct eigrp_prefix_entry *pe; for (ALL_LIST_ELEMENTS_RO(eigrp->eiflist, node, iface)) { if (iface != exception) { eigrp_update_send(iface); } } for (ALL_LIST_ELEMENTS(eigrp->topology_changes_internalIPV4, node2, nnode2, pe)) { if (pe->req_action & EIGRP_FSM_NEED_UPDATE) { pe->req_action &= ~EIGRP_FSM_NEED_UPDATE; listnode_delete(eigrp->topology_changes_internalIPV4, pe); } } } /** * @fn eigrp_update_send_GR_part * * @param[in] nbr contains neighbor who would receive * Graceful * restart * * @return void * * @par * Function used for sending Graceful restart Update packet * and if there are multiple chunks, send only one of them. * It is called from thread. Do not call it directly. * * Uses nbr_gr_packet_type from neighbor. */ static void eigrp_update_send_GR_part(struct eigrp_neighbor *nbr) { struct eigrp_packet *ep; uint16_t length = EIGRP_HEADER_LEN; struct eigrp_prefix_entry *pe; struct prefix *dest_addr; struct eigrp_interface *ei = nbr->ei; struct eigrp *eigrp = ei->eigrp; struct list *prefixes; uint32_t flags; unsigned int send_prefixes; struct route_node *rn; /* get prefixes to send to neighbor */ prefixes = nbr->nbr_gr_prefixes_send; send_prefixes = 0; /* if there already were last packet chunk, we won't continue */ if (nbr->nbr_gr_packet_type == EIGRP_PACKET_PART_LAST) return; /* if this is first packet chunk, we need to decide, * if there will be one or more chunks */ if (nbr->nbr_gr_packet_type == EIGRP_PACKET_PART_FIRST) { if (prefixes->count <= EIGRP_TLV_MAX_IPv4) { /* there will be only one chunk */ flags = EIGRP_INIT_FLAG + EIGRP_RS_FLAG + EIGRP_EOT_FLAG; nbr->nbr_gr_packet_type = EIGRP_PACKET_PART_LAST; } else { /* there will be more chunks */ flags = EIGRP_INIT_FLAG + EIGRP_RS_FLAG; nbr->nbr_gr_packet_type = EIGRP_PACKET_PART_NA; } } else { /* this is not first chunk, and we need to decide, * if there will be more chunks */ if (prefixes->count <= EIGRP_TLV_MAX_IPv4) { /* this is last chunk */ flags = EIGRP_EOT_FLAG; nbr->nbr_gr_packet_type = EIGRP_PACKET_PART_LAST; } else { /* there will be more chunks */ flags = 0; nbr->nbr_gr_packet_type = EIGRP_PACKET_PART_NA; } } ep = eigrp_packet_new(EIGRP_PACKET_MTU(ei->ifp->mtu), nbr); /* Prepare EIGRP Graceful restart UPDATE header */ eigrp_packet_header_init(EIGRP_OPC_UPDATE, eigrp, ep->s, flags, eigrp->sequence_number, nbr->recv_sequence_number); // encode Authentication TLV, if needed if ((ei->params.auth_type == EIGRP_AUTH_TYPE_MD5) && (ei->params.auth_keychain != NULL)) { length += eigrp_add_authTLV_MD5_to_stream(ep->s, ei); } for (rn = route_top(eigrp->topology_table); rn; rn = route_next(rn)) { if (!rn->info) continue; pe = rn->info; /* * Filtering */ dest_addr = pe->destination; if (eigrp_update_prefix_apply(eigrp, ei, EIGRP_FILTER_OUT, dest_addr)) { /* do not send filtered route */ zlog_info("Filtered prefix %s won't be sent out.", inet_ntoa(dest_addr->u.prefix4)); } else { /* sending route which wasn't filtered */ length += eigrp_add_internalTLV_to_stream(ep->s, pe); send_prefixes++; } /* * This makes no sense, Filter out then filter in??? * Look into this more - DBS */ if (eigrp_update_prefix_apply(eigrp, ei, EIGRP_FILTER_IN, dest_addr)) { /* do not send filtered route */ zlog_info("Filtered prefix %s will be removed.", inet_ntoa(dest_addr->u.prefix4)); /* prepare message for FSM */ struct eigrp_fsm_action_message fsm_msg; struct eigrp_nexthop_entry *entry = eigrp_prefix_entry_lookup(pe->entries, nbr); fsm_msg.packet_type = EIGRP_OPC_UPDATE; fsm_msg.eigrp = eigrp; fsm_msg.data_type = EIGRP_INT; fsm_msg.adv_router = nbr; fsm_msg.metrics = pe->reported_metric; /* Set delay to MAX */ fsm_msg.metrics.delay = EIGRP_MAX_METRIC; fsm_msg.entry = entry; fsm_msg.prefix = pe; /* send message to FSM */ eigrp_fsm_event(&fsm_msg); } /* NULL the pointer */ dest_addr = NULL; /* delete processed prefix from list */ listnode_delete(prefixes, pe); /* if there are enough prefixes, send packet */ if (send_prefixes >= EIGRP_TLV_MAX_IPv4) break; } /* compute Auth digest */ if ((ei->params.auth_type == EIGRP_AUTH_TYPE_MD5) && (ei->params.auth_keychain != NULL)) { eigrp_make_md5_digest(ei, ep->s, EIGRP_AUTH_UPDATE_FLAG); } /* EIGRP Checksum */ eigrp_packet_checksum(ei, ep->s, length); ep->length = length; ep->dst.s_addr = nbr->src.s_addr; /*This ack number we await from neighbor*/ ep->sequence_number = eigrp->sequence_number; if (IS_DEBUG_EIGRP_PACKET(0, RECV)) zlog_debug("Enqueuing Update Init Len [%u] Seq [%u] Dest [%s]", ep->length, ep->sequence_number, inet_ntoa(ep->dst)); /*Put packet to retransmission queue*/ eigrp_fifo_push(nbr->retrans_queue, ep); if (nbr->retrans_queue->count == 1) { eigrp_send_packet_reliably(nbr); } } /** * @fn eigrp_update_send_GR_thread * * @param[in] thread contains neighbor who would receive * Graceful restart * * @return int always 0 * * @par * Function used for sending Graceful restart Update packet * in thread, it is prepared for multiple chunks of packet. * * Uses nbr_gr_packet_type and t_nbr_send_gr from neighbor. */ int eigrp_update_send_GR_thread(struct thread *thread) { struct eigrp_neighbor *nbr; /* get argument from thread */ nbr = THREAD_ARG(thread); /* remove this thread pointer */ nbr->t_nbr_send_gr = NULL; /* if there is packet waiting in queue, * schedule this thread again with small delay */ if (nbr->retrans_queue->count > 0) { nbr->t_nbr_send_gr = NULL; thread_add_timer_msec(master, eigrp_update_send_GR_thread, nbr, 10, &nbr->t_nbr_send_gr); return 0; } /* send GR EIGRP packet chunk */ eigrp_update_send_GR_part(nbr); /* if it wasn't last chunk, schedule this thread again */ if (nbr->nbr_gr_packet_type != EIGRP_PACKET_PART_LAST) { thread_execute(master, eigrp_update_send_GR_thread, nbr, 0); nbr->t_nbr_send_gr = NULL; } return 0; } /** * @fn eigrp_update_send_GR * * @param[in] nbr Neighbor who would receive * Graceful * restart * @param[in] gr_type Who executed Graceful restart * @param[in] vty Virtual terminal for log output * * @return void * * @par * Function used for sending Graceful restart Update packet: * Creates Update packet with INIT, RS, EOT flags and include * all route except those filtered */ void eigrp_update_send_GR(struct eigrp_neighbor *nbr, enum GR_type gr_type, struct vty *vty) { struct eigrp_prefix_entry *pe2; struct list *prefixes; struct route_node *rn; struct eigrp_interface *ei = nbr->ei; struct eigrp *eigrp = ei->eigrp; if (gr_type == EIGRP_GR_FILTER) { /* function was called after applying filtration */ zlog_info( "Neighbor %s (%s) is resync: route configuration changed", inet_ntoa(nbr->src), ifindex2ifname(ei->ifp->ifindex, eigrp->vrf_id)); } else if (gr_type == EIGRP_GR_MANUAL) { /* Graceful restart was called manually */ zlog_info("Neighbor %s (%s) is resync: manually cleared", inet_ntoa(nbr->src), ifindex2ifname(ei->ifp->ifindex, eigrp->vrf_id)); if (vty != NULL) { vty_time_print(vty, 0); vty_out(vty, "Neighbor %s (%s) is resync: manually cleared\n", inet_ntoa(nbr->src), ifindex2ifname(ei->ifp->ifindex, eigrp->vrf_id)); } } prefixes = list_new(); /* add all prefixes from topology table to list */ for (rn = route_top(eigrp->topology_table); rn; rn = route_next(rn)) { if (!rn->info) continue; pe2 = rn->info; listnode_add(prefixes, pe2); } /* save prefixes to neighbor */ nbr->nbr_gr_prefixes_send = prefixes; /* indicate, that this is first GR Update packet chunk */ nbr->nbr_gr_packet_type = EIGRP_PACKET_PART_FIRST; /* execute packet sending in thread */ thread_execute(master, eigrp_update_send_GR_thread, nbr, 0); nbr->t_nbr_send_gr = NULL; } /** * @fn eigrp_update_send_interface_GR * * @param[in] ei Interface to neighbors of which * the * GR * is sent * @param[in] gr_type Who executed Graceful restart * @param[in] vty Virtual terminal for log output * * @return void * * @par * Function used for sending Graceful restart Update packet * to all neighbors on specified interface. */ void eigrp_update_send_interface_GR(struct eigrp_interface *ei, enum GR_type gr_type, struct vty *vty) { struct listnode *node; struct eigrp_neighbor *nbr; /* iterate over all neighbors on eigrp interface */ for (ALL_LIST_ELEMENTS_RO(ei->nbrs, node, nbr)) { /* send GR to neighbor */ eigrp_update_send_GR(nbr, gr_type, vty); } } /** * @fn eigrp_update_send_process_GR * * @param[in] eigrp EIGRP process * @param[in] gr_type Who executed Graceful restart * @param[in] vty Virtual terminal for log output * * @return void * * @par * Function used for sending Graceful restart Update packet * to all neighbors in eigrp process. */ void eigrp_update_send_process_GR(struct eigrp *eigrp, enum GR_type gr_type, struct vty *vty) { struct listnode *node; struct eigrp_interface *ei; /* iterate over all eigrp interfaces */ for (ALL_LIST_ELEMENTS_RO(eigrp->eiflist, node, ei)) { /* send GR to all neighbors on interface */ eigrp_update_send_interface_GR(ei, gr_type, vty); } } frr-7.2.1/eigrpd/eigrp_vrf.c0000644000000000000000000000236613610377563012624 00000000000000/* * eigrp - vrf code * Copyright (C) 2019 Cumulus Networks, Inc. * Donald Sharp * * This program is free software; you can redistribute it and/or modify it * under the terms of the 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; see the file COPYING; if not, write to the Free Software * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA */ #include #include "vrf.h" #include "eigrpd/eigrp_vrf.h" static int eigrp_vrf_new(struct vrf *vrf) { return 0; } static int eigrp_vrf_enable(struct vrf *vrf) { return 0; } static int eigrp_vrf_disable(struct vrf *vrf) { return 0; } static int eigrp_vrf_delete(struct vrf *vrf) { return 0; } void eigrp_vrf_init(void) { vrf_init(eigrp_vrf_new, eigrp_vrf_enable, eigrp_vrf_disable, eigrp_vrf_delete, NULL); } frr-7.2.1/eigrpd/eigrp_vrf.h0000644000000000000000000000161113610377563012621 00000000000000/* * eigrp - vrf code * Copyright (C) 2019 Cumulus Networks, Inc. * Donald Sharp * * This program is free software; you can redistribute it and/or modify it * under the terms of the 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; see the file COPYING; if not, write to the Free Software * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA */ #ifndef __EIGRP_VRF_H__ extern void eigrp_vrf_init(void); #endif frr-7.2.1/eigrpd/eigrp_vty.c0000644000000000000000000003321613610377563012647 00000000000000/* * EIGRP VTY Interface. * Copyright (C) 2013-2016 * Authors: * Donnie Savage * Jan Janovic * Matej Perina * Peter Orsag * Peter Paluch * Frantisek Gazo * Tomas Hvorkovy * Martin Kontsek * Lukas Koribsky * * This file is part of GNU Zebra. * * GNU Zebra is free software; you can redistribute it and/or modify it * under the terms of the GNU General Public License as published by the * Free Software Foundation; either version 2, or (at your option) any * later version. * * GNU Zebra 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; see the file COPYING; if not, write to the Free Software * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA */ #include #include "memory.h" #include "thread.h" #include "prefix.h" #include "table.h" #include "vty.h" #include "command.h" #include "plist.h" #include "log.h" #include "zclient.h" #include "keychain.h" #include "linklist.h" #include "distribute.h" #include "eigrpd/eigrp_structs.h" #include "eigrpd/eigrpd.h" #include "eigrpd/eigrp_interface.h" #include "eigrpd/eigrp_neighbor.h" #include "eigrpd/eigrp_packet.h" #include "eigrpd/eigrp_zebra.h" #include "eigrpd/eigrp_vty.h" #include "eigrpd/eigrp_network.h" #include "eigrpd/eigrp_dump.h" #include "eigrpd/eigrp_const.h" #ifndef VTYSH_EXTRACT_PL #include "eigrpd/eigrp_vty_clippy.c" #endif static void eigrp_vty_display_prefix_entry(struct vty *vty, struct eigrp *eigrp, struct eigrp_prefix_entry *pe, bool all) { bool first = true; struct eigrp_nexthop_entry *te; struct listnode *node; for (ALL_LIST_ELEMENTS_RO(pe->entries, node, te)) { if (all || (((te->flags & EIGRP_NEXTHOP_ENTRY_SUCCESSOR_FLAG) == EIGRP_NEXTHOP_ENTRY_SUCCESSOR_FLAG) || ((te->flags & EIGRP_NEXTHOP_ENTRY_FSUCCESSOR_FLAG) == EIGRP_NEXTHOP_ENTRY_FSUCCESSOR_FLAG))) { show_ip_eigrp_nexthop_entry(vty, eigrp, te, &first); first = false; } } } static struct eigrp *eigrp_vty_get_eigrp(struct vty *vty, const char *vrf_name) { struct vrf *vrf; if (vrf_name) vrf = vrf_lookup_by_name(vrf_name); else vrf = vrf_lookup_by_id(VRF_DEFAULT); if (!vrf) { vty_out(vty, "VRF %s specified does not exist", vrf_name ? vrf_name : VRF_DEFAULT_NAME); return NULL; } return eigrp_lookup(vrf->vrf_id); } DEFPY (show_ip_eigrp_topology_all, show_ip_eigrp_topology_all_cmd, "show ip eigrp [vrf NAME] topology [all-links$all]", SHOW_STR IP_STR "IP-EIGRP show commands\n" VRF_CMD_HELP_STR "IP-EIGRP topology\n" "Show all links in topology table\n") { struct eigrp *eigrp; struct eigrp_prefix_entry *tn; struct route_node *rn; eigrp = eigrp_vty_get_eigrp(vty, vrf); if (eigrp == NULL) { vty_out(vty, " EIGRP Routing Process not enabled\n"); return CMD_SUCCESS; } show_ip_eigrp_topology_header(vty, eigrp); for (rn = route_top(eigrp->topology_table); rn; rn = route_next(rn)) { if (!rn->info) continue; tn = rn->info; eigrp_vty_display_prefix_entry(vty, eigrp, tn, all ? true : false); } return CMD_SUCCESS; } DEFPY (show_ip_eigrp_topology, show_ip_eigrp_topology_cmd, "show ip eigrp [vrf NAME] topology ", SHOW_STR IP_STR "IP-EIGRP show commands\n" VRF_CMD_HELP_STR "IP-EIGRP topology\n" "For a specific address\n" "For a specific prefix\n") { struct eigrp *eigrp; struct eigrp_prefix_entry *tn; struct route_node *rn; struct prefix cmp; eigrp = eigrp_vty_get_eigrp(vty, vrf); if (eigrp == NULL) { vty_out(vty, " EIGRP Routing Process not enabled\n"); return CMD_SUCCESS; } show_ip_eigrp_topology_header(vty, eigrp); if (address_str) prefix_str = address_str; if (str2prefix(prefix_str, &cmp) < 0) { vty_out(vty, "%% Malformed address\n"); return CMD_WARNING; } rn = route_node_match(eigrp->topology_table, &cmp); if (!rn) { vty_out(vty, "%% Network not in table\n"); return CMD_WARNING; } if (!rn->info) { vty_out(vty, "%% Network not in table\n"); route_unlock_node(rn); return CMD_WARNING; } tn = rn->info; eigrp_vty_display_prefix_entry(vty, eigrp, tn, argc == 5); route_unlock_node(rn); return CMD_SUCCESS; } DEFPY (show_ip_eigrp_interfaces, show_ip_eigrp_interfaces_cmd, "show ip eigrp [vrf NAME] interfaces [IFNAME] [detail]$detail", SHOW_STR IP_STR "IP-EIGRP show commands\n" VRF_CMD_HELP_STR "IP-EIGRP interfaces\n" "Interface name to look at\n" "Detailed information\n") { struct eigrp_interface *ei; struct eigrp *eigrp; struct listnode *node; eigrp = eigrp_vty_get_eigrp(vty, vrf); if (eigrp == NULL) { vty_out(vty, "EIGRP Routing Process not enabled\n"); return CMD_SUCCESS; } if (!ifname) show_ip_eigrp_interface_header(vty, eigrp); for (ALL_LIST_ELEMENTS_RO(eigrp->eiflist, node, ei)) { if (!ifname || strcmp(ei->ifp->name, ifname) == 0) { show_ip_eigrp_interface_sub(vty, eigrp, ei); if (detail) show_ip_eigrp_interface_detail(vty, eigrp, ei); } } return CMD_SUCCESS; } DEFPY (show_ip_eigrp_neighbors, show_ip_eigrp_neighbors_cmd, "show ip eigrp [vrf NAME] neighbors [IFNAME] [detail]$detail", SHOW_STR IP_STR "IP-EIGRP show commands\n" VRF_CMD_HELP_STR "IP-EIGRP neighbors\n" "Interface to show on\n" "Detailed Information\n") { struct eigrp *eigrp; struct eigrp_interface *ei; struct listnode *node, *node2, *nnode2; struct eigrp_neighbor *nbr; eigrp = eigrp_vty_get_eigrp(vty, vrf); if (eigrp == NULL) { vty_out(vty, " EIGRP Routing Process not enabled\n"); return CMD_SUCCESS; } show_ip_eigrp_neighbor_header(vty, eigrp); for (ALL_LIST_ELEMENTS_RO(eigrp->eiflist, node, ei)) { if (!ifname || strcmp(ei->ifp->name, ifname) == 0) { for (ALL_LIST_ELEMENTS(ei->nbrs, node2, nnode2, nbr)) { if (detail || (nbr->state == EIGRP_NEIGHBOR_UP)) show_ip_eigrp_neighbor_sub(vty, nbr, !!detail); } } } return CMD_SUCCESS; } /* * Execute hard restart for all neighbors */ DEFPY (clear_ip_eigrp_neighbors, clear_ip_eigrp_neighbors_cmd, "clear ip eigrp [vrf NAME] neighbors", CLEAR_STR IP_STR "Clear IP-EIGRP\n" VRF_CMD_HELP_STR "Clear IP-EIGRP neighbors\n") { struct eigrp *eigrp; struct eigrp_interface *ei; struct listnode *node, *node2, *nnode2; struct eigrp_neighbor *nbr; /* Check if eigrp process is enabled */ eigrp = eigrp_vty_get_eigrp(vty, vrf); if (eigrp == NULL) { vty_out(vty, " EIGRP Routing Process not enabled\n"); return CMD_SUCCESS; } /* iterate over all eigrp interfaces */ for (ALL_LIST_ELEMENTS_RO(eigrp->eiflist, node, ei)) { /* send Goodbye Hello */ eigrp_hello_send(ei, EIGRP_HELLO_GRACEFUL_SHUTDOWN, NULL); /* iterate over all neighbors on eigrp interface */ for (ALL_LIST_ELEMENTS(ei->nbrs, node2, nnode2, nbr)) { if (nbr->state != EIGRP_NEIGHBOR_DOWN) { zlog_debug( "Neighbor %s (%s) is down: manually cleared", inet_ntoa(nbr->src), ifindex2ifname(nbr->ei->ifp->ifindex, eigrp->vrf_id)); vty_time_print(vty, 0); vty_out(vty, "Neighbor %s (%s) is down: manually cleared\n", inet_ntoa(nbr->src), ifindex2ifname(nbr->ei->ifp->ifindex, eigrp->vrf_id)); /* set neighbor to DOWN */ nbr->state = EIGRP_NEIGHBOR_DOWN; /* delete neighbor */ eigrp_nbr_delete(nbr); } } } return CMD_SUCCESS; } /* * Execute hard restart for all neighbors on interface */ DEFPY (clear_ip_eigrp_neighbors_int, clear_ip_eigrp_neighbors_int_cmd, "clear ip eigrp [vrf NAME] neighbors IFNAME", CLEAR_STR IP_STR "Clear IP-EIGRP\n" VRF_CMD_HELP_STR "Clear IP-EIGRP neighbors\n" "Interface's name\n") { struct eigrp *eigrp; struct eigrp_interface *ei; struct listnode *node2, *nnode2; struct eigrp_neighbor *nbr; /* Check if eigrp process is enabled */ eigrp = eigrp_vty_get_eigrp(vty, vrf); if (eigrp == NULL) { vty_out(vty, " EIGRP Routing Process not enabled\n"); return CMD_SUCCESS; } /* lookup interface by specified name */ ei = eigrp_if_lookup_by_name(eigrp, ifname); if (ei == NULL) { vty_out(vty, " Interface (%s) doesn't exist\n", ifname); return CMD_WARNING; } /* send Goodbye Hello */ eigrp_hello_send(ei, EIGRP_HELLO_GRACEFUL_SHUTDOWN, NULL); /* iterate over all neighbors on eigrp interface */ for (ALL_LIST_ELEMENTS(ei->nbrs, node2, nnode2, nbr)) { if (nbr->state != EIGRP_NEIGHBOR_DOWN) { zlog_debug("Neighbor %s (%s) is down: manually cleared", inet_ntoa(nbr->src), ifindex2ifname(nbr->ei->ifp->ifindex, eigrp->vrf_id)); vty_time_print(vty, 0); vty_out(vty, "Neighbor %s (%s) is down: manually cleared\n", inet_ntoa(nbr->src), ifindex2ifname(nbr->ei->ifp->ifindex, eigrp->vrf_id)); /* set neighbor to DOWN */ nbr->state = EIGRP_NEIGHBOR_DOWN; /* delete neighbor */ eigrp_nbr_delete(nbr); } } return CMD_SUCCESS; } /* * Execute hard restart for neighbor specified by IP */ DEFPY (clear_ip_eigrp_neighbors_IP, clear_ip_eigrp_neighbors_IP_cmd, "clear ip eigrp [vrf NAME] neighbors A.B.C.D$nbr_addr", CLEAR_STR IP_STR "Clear IP-EIGRP\n" VRF_CMD_HELP_STR "Clear IP-EIGRP neighbors\n" "IP-EIGRP neighbor address\n") { struct eigrp *eigrp; struct eigrp_neighbor *nbr; /* Check if eigrp process is enabled */ eigrp = eigrp_vty_get_eigrp(vty, vrf); if (eigrp == NULL) { vty_out(vty, " EIGRP Routing Process not enabled\n"); return CMD_SUCCESS; } /* lookup neighbor in whole process */ nbr = eigrp_nbr_lookup_by_addr_process(eigrp, nbr_addr); /* if neighbor doesn't exists, notify user and exit */ if (nbr == NULL) { vty_out(vty, "Neighbor with entered address doesn't exists.\n"); return CMD_WARNING; } /* execute hard reset on neighbor */ eigrp_nbr_hard_restart(nbr, vty); return CMD_SUCCESS; } /* * Execute graceful restart for all neighbors */ DEFPY (clear_ip_eigrp_neighbors_soft, clear_ip_eigrp_neighbors_soft_cmd, "clear ip eigrp [vrf NAME] neighbors soft", CLEAR_STR IP_STR "Clear IP-EIGRP\n" VRF_CMD_HELP_STR "Clear IP-EIGRP neighbors\n" "Resync with peers without adjacency reset\n") { struct eigrp *eigrp; /* Check if eigrp process is enabled */ eigrp = eigrp_vty_get_eigrp(vty, vrf); if (eigrp == NULL) { vty_out(vty, " EIGRP Routing Process not enabled\n"); return CMD_SUCCESS; } /* execute graceful restart on all neighbors */ eigrp_update_send_process_GR(eigrp, EIGRP_GR_MANUAL, vty); return CMD_SUCCESS; } /* * Execute graceful restart for all neighbors on interface */ DEFPY (clear_ip_eigrp_neighbors_int_soft, clear_ip_eigrp_neighbors_int_soft_cmd, "clear ip eigrp [vrf NAME] neighbors IFNAME soft", CLEAR_STR IP_STR "Clear IP-EIGRP\n" VRF_CMD_HELP_STR "Clear IP-EIGRP neighbors\n" "Interface's name\n" "Resync with peer without adjacency reset\n") { struct eigrp *eigrp; struct eigrp_interface *ei; /* Check if eigrp process is enabled */ eigrp = eigrp_vty_get_eigrp(vty, vrf); if (eigrp == NULL) { vty_out(vty, " EIGRP Routing Process not enabled\n"); return CMD_SUCCESS; } /* lookup interface by specified name */ ei = eigrp_if_lookup_by_name(eigrp, ifname); if (ei == NULL) { vty_out(vty, " Interface (%s) doesn't exist\n", argv[4]->arg); return CMD_WARNING; } /* execute graceful restart for all neighbors on interface */ eigrp_update_send_interface_GR(ei, EIGRP_GR_MANUAL, vty); return CMD_SUCCESS; } /* * Execute graceful restart for neighbor specified by IP */ DEFPY (clear_ip_eigrp_neighbors_IP_soft, clear_ip_eigrp_neighbors_IP_soft_cmd, "clear ip eigrp [vrf NAME] neighbors A.B.C.D$nbr_addr soft", CLEAR_STR IP_STR "Clear IP-EIGRP\n" VRF_CMD_HELP_STR "Clear IP-EIGRP neighbors\n" "IP-EIGRP neighbor address\n" "Resync with peer without adjacency reset\n") { struct eigrp *eigrp; struct eigrp_neighbor *nbr; /* Check if eigrp process is enabled */ eigrp = eigrp_vty_get_eigrp(vty, vrf); if (eigrp == NULL) { vty_out(vty, " EIGRP Routing Process not enabled\n"); return CMD_SUCCESS; } /* lookup neighbor in whole process */ nbr = eigrp_nbr_lookup_by_addr_process(eigrp, nbr_addr); /* if neighbor doesn't exists, notify user and exit */ if (nbr == NULL) { vty_out(vty, "Neighbor with entered address doesn't exists.\n"); return CMD_WARNING; } /* execute graceful restart on neighbor */ eigrp_update_send_GR(nbr, EIGRP_GR_MANUAL, vty); return CMD_SUCCESS; } void eigrp_vty_show_init(void) { install_element(VIEW_NODE, &show_ip_eigrp_interfaces_cmd); install_element(VIEW_NODE, &show_ip_eigrp_neighbors_cmd); install_element(VIEW_NODE, &show_ip_eigrp_topology_cmd); install_element(VIEW_NODE, &show_ip_eigrp_topology_all_cmd); } /* Install EIGRP related vty commands. */ void eigrp_vty_init(void) { /* commands for manual hard restart */ install_element(ENABLE_NODE, &clear_ip_eigrp_neighbors_cmd); install_element(ENABLE_NODE, &clear_ip_eigrp_neighbors_int_cmd); install_element(ENABLE_NODE, &clear_ip_eigrp_neighbors_IP_cmd); /* commands for manual graceful restart */ install_element(ENABLE_NODE, &clear_ip_eigrp_neighbors_soft_cmd); install_element(ENABLE_NODE, &clear_ip_eigrp_neighbors_int_soft_cmd); install_element(ENABLE_NODE, &clear_ip_eigrp_neighbors_IP_soft_cmd); } frr-7.2.1/eigrpd/eigrp_vty.h0000644000000000000000000000223113610377563012645 00000000000000/* * EIGRP VTY Interface. * Copyright (C) 2013-2016 * Authors: * Donnie Savage * Jan Janovic * Matej Perina * Peter Orsag * Peter Paluch * Frantisek Gazo * Tomas Hvorkovy * Martin Kontsek * Lukas Koribsky * * This file is part of GNU Zebra. * * GNU Zebra is free software; you can redistribute it and/or modify it * under the terms of the GNU General Public License as published by the * Free Software Foundation; either version 2, or (at your option) any * later version. * * GNU Zebra 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; see the file COPYING; if not, write to the Free Software * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA */ #ifndef _QUAGGA_EIGRP_VTY_H #define _QUAGGA_EIGRP_VTY_H /* Prototypes. */ extern void eigrp_vty_init(void); extern void eigrp_vty_show_init(void); #endif /* _Quagga_EIGRP_VTY_H_ */ frr-7.2.1/eigrpd/eigrp_zebra.c0000644000000000000000000002651013610377563013127 00000000000000/* * Zebra connect library for EIGRP. * Copyright (C) 2013-2014 * Authors: * Donnie Savage * Jan Janovic * Matej Perina * Peter Orsag * Peter Paluch * * This file is part of GNU Zebra. * * GNU Zebra is free software; you can redistribute it and/or modify it * under the terms of the GNU General Public License as published by the * Free Software Foundation; either version 2, or (at your option) any * later version. * * GNU Zebra 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; see the file COPYING; if not, write to the Free Software * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA */ #include #include "thread.h" #include "command.h" #include "network.h" #include "prefix.h" #include "routemap.h" #include "table.h" #include "stream.h" #include "memory.h" #include "zclient.h" #include "filter.h" #include "plist.h" #include "log.h" #include "nexthop.h" #include "eigrpd/eigrp_structs.h" #include "eigrpd/eigrpd.h" #include "eigrpd/eigrp_interface.h" #include "eigrpd/eigrp_neighbor.h" #include "eigrpd/eigrp_packet.h" #include "eigrpd/eigrp_zebra.h" #include "eigrpd/eigrp_vty.h" #include "eigrpd/eigrp_dump.h" #include "eigrpd/eigrp_network.h" #include "eigrpd/eigrp_topology.h" #include "eigrpd/eigrp_fsm.h" static int eigrp_interface_add(ZAPI_CALLBACK_ARGS); static int eigrp_interface_delete(ZAPI_CALLBACK_ARGS); static int eigrp_interface_address_add(ZAPI_CALLBACK_ARGS); static int eigrp_interface_address_delete(ZAPI_CALLBACK_ARGS); static int eigrp_interface_state_up(ZAPI_CALLBACK_ARGS); static int eigrp_interface_state_down(ZAPI_CALLBACK_ARGS); static struct interface *zebra_interface_if_lookup(struct stream *, vrf_id_t vrf_id); static int eigrp_zebra_read_route(ZAPI_CALLBACK_ARGS); /* Zebra structure to hold current status. */ struct zclient *zclient = NULL; /* For registering threads. */ extern struct thread_master *master; struct in_addr router_id_zebra; /* Router-id update message from zebra. */ static int eigrp_router_id_update_zebra(ZAPI_CALLBACK_ARGS) { struct eigrp *eigrp; struct prefix router_id; zebra_router_id_update_read(zclient->ibuf, &router_id); router_id_zebra = router_id.u.prefix4; eigrp = eigrp_lookup(vrf_id); if (eigrp != NULL) eigrp_router_id_update(eigrp); return 0; } static int eigrp_zebra_route_notify_owner(ZAPI_CALLBACK_ARGS) { struct prefix p; enum zapi_route_notify_owner note; uint32_t table; if (!zapi_route_notify_decode(zclient->ibuf, &p, &table, ¬e)) return -1; return 0; } static void eigrp_zebra_connected(struct zclient *zclient) { zclient_send_reg_requests(zclient, VRF_DEFAULT); } void eigrp_zebra_init(void) { struct zclient_options opt = {.receive_notify = false}; zclient = zclient_new(master, &opt); zclient_init(zclient, ZEBRA_ROUTE_EIGRP, 0, &eigrpd_privs); zclient->zebra_connected = eigrp_zebra_connected; zclient->router_id_update = eigrp_router_id_update_zebra; zclient->interface_add = eigrp_interface_add; zclient->interface_delete = eigrp_interface_delete; zclient->interface_up = eigrp_interface_state_up; zclient->interface_down = eigrp_interface_state_down; zclient->interface_address_add = eigrp_interface_address_add; zclient->interface_address_delete = eigrp_interface_address_delete; zclient->redistribute_route_add = eigrp_zebra_read_route; zclient->redistribute_route_del = eigrp_zebra_read_route; zclient->route_notify_owner = eigrp_zebra_route_notify_owner; } /* Zebra route add and delete treatment. */ static int eigrp_zebra_read_route(ZAPI_CALLBACK_ARGS) { struct zapi_route api; struct eigrp *eigrp; if (zapi_route_decode(zclient->ibuf, &api) < 0) return -1; if (IPV4_NET127(ntohl(api.prefix.u.prefix4.s_addr))) return 0; eigrp = eigrp_lookup(vrf_id); if (eigrp == NULL) return 0; if (cmd == ZEBRA_REDISTRIBUTE_ROUTE_ADD) { } else /* if (cmd == ZEBRA_REDISTRIBUTE_ROUTE_DEL) */ { } return 0; } /* Inteface addition message from zebra. */ static int eigrp_interface_add(ZAPI_CALLBACK_ARGS) { struct interface *ifp; struct eigrp_interface *ei; ifp = zebra_interface_add_read(zclient->ibuf, vrf_id); if (!ifp->info) return 0; ei = ifp->info; ei->params.type = eigrp_default_iftype(ifp); eigrp_if_update(ifp); return 0; } static int eigrp_interface_delete(ZAPI_CALLBACK_ARGS) { struct interface *ifp; struct stream *s; s = zclient->ibuf; /* zebra_interface_state_read () updates interface structure in iflist */ ifp = zebra_interface_state_read(s, vrf_id); if (ifp == NULL) return 0; if (if_is_up(ifp)) zlog_warn("Zebra: got delete of %s, but interface is still up", ifp->name); if (IS_DEBUG_EIGRP(zebra, ZEBRA_INTERFACE)) zlog_debug( "Zebra: interface delete %s index %d flags %llx metric %d mtu %d", ifp->name, ifp->ifindex, (unsigned long long)ifp->flags, ifp->metric, ifp->mtu); if (ifp->info) eigrp_if_free(ifp->info, INTERFACE_DOWN_BY_ZEBRA); if_set_index(ifp, IFINDEX_INTERNAL); return 0; } static int eigrp_interface_address_add(ZAPI_CALLBACK_ARGS) { struct connected *c; c = zebra_interface_address_read(cmd, zclient->ibuf, vrf_id); if (c == NULL) return 0; if (IS_DEBUG_EIGRP(zebra, ZEBRA_INTERFACE)) { char buf[128]; prefix2str(c->address, buf, sizeof(buf)); zlog_debug("Zebra: interface %s address add %s", c->ifp->name, buf); } eigrp_if_update(c->ifp); return 0; } static int eigrp_interface_address_delete(ZAPI_CALLBACK_ARGS) { struct connected *c; struct interface *ifp; struct eigrp_interface *ei; c = zebra_interface_address_read(cmd, zclient->ibuf, vrf_id); if (c == NULL) return 0; if (IS_DEBUG_EIGRP(zebra, ZEBRA_INTERFACE)) { char buf[128]; prefix2str(c->address, buf, sizeof(buf)); zlog_debug("Zebra: interface %s address delete %s", c->ifp->name, buf); } ifp = c->ifp; ei = ifp->info; if (!ei) return 0; /* Call interface hook functions to clean up */ if (prefix_cmp(&ei->address, c->address) == 0) eigrp_if_free(ei, INTERFACE_DOWN_BY_ZEBRA); connected_free(c); return 0; } static int eigrp_interface_state_up(ZAPI_CALLBACK_ARGS) { struct interface *ifp; ifp = zebra_interface_if_lookup(zclient->ibuf, vrf_id); if (ifp == NULL) return 0; /* Interface is already up. */ if (if_is_operative(ifp)) { /* Temporarily keep ifp values. */ struct interface if_tmp; memcpy(&if_tmp, ifp, sizeof(struct interface)); zebra_interface_if_set_value(zclient->ibuf, ifp); if (IS_DEBUG_EIGRP(zebra, ZEBRA_INTERFACE)) zlog_debug("Zebra: Interface[%s] state update.", ifp->name); if (if_tmp.bandwidth != ifp->bandwidth) { if (IS_DEBUG_EIGRP(zebra, ZEBRA_INTERFACE)) zlog_debug( "Zebra: Interface[%s] bandwidth change %d -> %d.", ifp->name, if_tmp.bandwidth, ifp->bandwidth); // eigrp_if_recalculate_output_cost (ifp); } if (if_tmp.mtu != ifp->mtu) { if (IS_DEBUG_EIGRP(zebra, ZEBRA_INTERFACE)) zlog_debug( "Zebra: Interface[%s] MTU change %u -> %u.", ifp->name, if_tmp.mtu, ifp->mtu); /* Must reset the interface (simulate down/up) when MTU * changes. */ eigrp_if_reset(ifp); } return 0; } zebra_interface_if_set_value(zclient->ibuf, ifp); if (IS_DEBUG_EIGRP(zebra, ZEBRA_INTERFACE)) zlog_debug("Zebra: Interface[%s] state change to up.", ifp->name); if (ifp->info) eigrp_if_up(ifp->info); return 0; } static int eigrp_interface_state_down(ZAPI_CALLBACK_ARGS) { struct interface *ifp; ifp = zebra_interface_state_read(zclient->ibuf, vrf_id); if (ifp == NULL) return 0; if (IS_DEBUG_EIGRP(zebra, ZEBRA_INTERFACE)) zlog_debug("Zebra: Interface[%s] state change to down.", ifp->name); if (ifp->info) eigrp_if_down(ifp->info); return 0; } static struct interface *zebra_interface_if_lookup(struct stream *s, vrf_id_t vrf_id) { char ifname_tmp[INTERFACE_NAMSIZ]; /* Read interface name. */ stream_get(ifname_tmp, s, INTERFACE_NAMSIZ); /* And look it up. */ return if_lookup_by_name(ifname_tmp, vrf_id); } void eigrp_zebra_route_add(struct eigrp *eigrp, struct prefix *p, struct list *successors, uint32_t distance) { struct zapi_route api; struct zapi_nexthop *api_nh; struct eigrp_nexthop_entry *te; struct listnode *node; int count = 0; if (!zclient->redist[AFI_IP][ZEBRA_ROUTE_EIGRP]) return; memset(&api, 0, sizeof(api)); api.vrf_id = eigrp->vrf_id; api.type = ZEBRA_ROUTE_EIGRP; api.safi = SAFI_UNICAST; api.metric = distance; memcpy(&api.prefix, p, sizeof(*p)); SET_FLAG(api.message, ZAPI_MESSAGE_NEXTHOP); SET_FLAG(api.message, ZAPI_MESSAGE_METRIC); /* Nexthop, ifindex, distance and metric information. */ for (ALL_LIST_ELEMENTS_RO(successors, node, te)) { if (count >= MULTIPATH_NUM) break; api_nh = &api.nexthops[count]; api_nh->vrf_id = eigrp->vrf_id; if (te->adv_router->src.s_addr) { api_nh->gate.ipv4 = te->adv_router->src; api_nh->type = NEXTHOP_TYPE_IPV4_IFINDEX; } else api_nh->type = NEXTHOP_TYPE_IFINDEX; api_nh->ifindex = te->ei->ifp->ifindex; count++; } api.nexthop_num = count; if (IS_DEBUG_EIGRP(zebra, ZEBRA_REDISTRIBUTE)) { char buf[2][PREFIX_STRLEN]; zlog_debug("Zebra: Route add %s nexthop %s", prefix2str(p, buf[0], PREFIX_STRLEN), inet_ntop(AF_INET, 0, buf[1], PREFIX_STRLEN)); } zclient_route_send(ZEBRA_ROUTE_ADD, zclient, &api); } void eigrp_zebra_route_delete(struct eigrp *eigrp, struct prefix *p) { struct zapi_route api; if (!zclient->redist[AFI_IP][ZEBRA_ROUTE_EIGRP]) return; memset(&api, 0, sizeof(api)); api.vrf_id = eigrp->vrf_id; api.type = ZEBRA_ROUTE_EIGRP; api.safi = SAFI_UNICAST; memcpy(&api.prefix, p, sizeof(*p)); zclient_route_send(ZEBRA_ROUTE_DELETE, zclient, &api); if (IS_DEBUG_EIGRP(zebra, ZEBRA_REDISTRIBUTE)) { char buf[PREFIX_STRLEN]; zlog_debug("Zebra: Route del %s", prefix2str(p, buf, PREFIX_STRLEN)); } return; } static int eigrp_is_type_redistributed(int type, vrf_id_t vrf_id) { return ((DEFAULT_ROUTE_TYPE(type)) ? vrf_bitmap_check(zclient->default_information[AFI_IP], vrf_id) : vrf_bitmap_check(zclient->redist[AFI_IP][type], vrf_id)); } int eigrp_redistribute_set(struct eigrp *eigrp, int type, struct eigrp_metrics metric) { if (eigrp_is_type_redistributed(type, eigrp->vrf_id)) { if (eigrp_metrics_is_same(metric, eigrp->dmetric[type])) { eigrp->dmetric[type] = metric; } eigrp_external_routes_refresh(eigrp, type); // if (IS_DEBUG_EIGRP(zebra, ZEBRA_REDISTRIBUTE)) // zlog_debug ("Redistribute[%s]: Refresh Type[%d], // Metric[%d]", // eigrp_redist_string(type), // metric_type (eigrp, type), metric_value // (eigrp, type)); return CMD_SUCCESS; } eigrp->dmetric[type] = metric; zclient_redistribute(ZEBRA_REDISTRIBUTE_ADD, zclient, AFI_IP, type, 0, eigrp->vrf_id); ++eigrp->redistribute; return CMD_SUCCESS; } int eigrp_redistribute_unset(struct eigrp *eigrp, int type) { if (eigrp_is_type_redistributed(type, eigrp->vrf_id)) { memset(&eigrp->dmetric[type], 0, sizeof(struct eigrp_metrics)); zclient_redistribute(ZEBRA_REDISTRIBUTE_DELETE, zclient, AFI_IP, type, 0, eigrp->vrf_id); --eigrp->redistribute; } return CMD_SUCCESS; } frr-7.2.1/eigrpd/eigrp_zebra.h0000644000000000000000000000262713610377563013137 00000000000000/* * Zebra connect library for EIGRP. * Copyright (C) 2013-2014 * Authors: * Donnie Savage * Jan Janovic * Matej Perina * Peter Orsag * Peter Paluch * * This file is part of GNU Zebra. * * GNU Zebra is free software; you can redistribute it and/or modify it * under the terms of the GNU General Public License as published by the * Free Software Foundation; either version 2, or (at your option) any * later version. * * GNU Zebra 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; see the file COPYING; if not, write to the Free Software * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA */ #ifndef _ZEBRA_EIGRP_ZEBRA_H_ #define _ZEBRA_EIGRP_ZEBRA_H_ #include "vty.h" #include "vrf.h" extern void eigrp_zebra_init(void); extern void eigrp_zebra_route_add(struct eigrp *eigrp, struct prefix *p, struct list *successors, uint32_t distance); extern void eigrp_zebra_route_delete(struct eigrp *eigrp, struct prefix *); extern int eigrp_redistribute_set(struct eigrp *, int, struct eigrp_metrics); extern int eigrp_redistribute_unset(struct eigrp *, int); #endif /* _ZEBRA_EIGRP_ZEBRA_H_ */ frr-7.2.1/eigrpd/eigrpd.c0000644000000000000000000002004513610377563012105 00000000000000/* * EIGRP Daemon Program. * Copyright (C) 2013-2014 * Authors: * Donnie Savage * Jan Janovic * Matej Perina * Peter Orsag * Peter Paluch * * This file is part of GNU Zebra. * * GNU Zebra is free software; you can redistribute it and/or modify it * under the terms of the GNU General Public License as published by the * Free Software Foundation; either version 2, or (at your option) any * later version. * * GNU Zebra 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; see the file COPYING; if not, write to the Free Software * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA */ #include #include "thread.h" #include "vty.h" #include "command.h" #include "linklist.h" #include "prefix.h" #include "table.h" #include "if.h" #include "memory.h" #include "stream.h" #include "log.h" #include "sockunion.h" /* for inet_aton () */ #include "zclient.h" #include "plist.h" #include "sockopt.h" #include "keychain.h" #include "libfrr.h" #include "lib_errors.h" #include "distribute.h" #include "eigrpd/eigrp_structs.h" #include "eigrpd/eigrpd.h" #include "eigrpd/eigrp_interface.h" #include "eigrpd/eigrp_zebra.h" #include "eigrpd/eigrp_vty.h" #include "eigrpd/eigrp_neighbor.h" #include "eigrpd/eigrp_packet.h" #include "eigrpd/eigrp_network.h" #include "eigrpd/eigrp_topology.h" #include "eigrpd/eigrp_memory.h" #include "eigrpd/eigrp_filter.h" DEFINE_QOBJ_TYPE(eigrp) static struct eigrp_master eigrp_master; struct eigrp_master *eigrp_om; extern struct zclient *zclient; extern struct in_addr router_id_zebra; /* * void eigrp_router_id_update(struct eigrp *eigrp) * * Description: * update routerid associated with this instance of EIGRP. * If the id changes, then call if_update for each interface * to resync the topology database with all neighbors * * Select the router ID based on these priorities: * 1. Statically assigned router ID is always the first choice. * 2. If there is no statically assigned router ID, then try to stick * with the most recent value, since changing router ID's is very * disruptive. * 3. Last choice: just go with whatever the zebra daemon recommends. * * Note: * router id for EIGRP is really just a 32 bit number. Cisco historically * displays it in dotted decimal notation, and will pickup an IP address * from an interface so it can be 'auto-configed" to a uniqe value * * This does not work for IPv6, and to make the code simpler, its * stored and processed internerall as a 32bit number */ void eigrp_router_id_update(struct eigrp *eigrp) { struct vrf *vrf = vrf_lookup_by_id(eigrp->vrf_id); struct interface *ifp; struct in_addr router_id, router_id_old; router_id_old = eigrp->router_id; if (eigrp->router_id_static.s_addr != 0) router_id = eigrp->router_id_static; else if (eigrp->router_id.s_addr != 0) router_id = eigrp->router_id; else router_id = router_id_zebra; eigrp->router_id = router_id; if (router_id_old.s_addr != router_id.s_addr) { // if (IS_DEBUG_EIGRP_EVENT) // zlog_debug("Router-ID[NEW:%s]: Update", // inet_ntoa(eigrp->router_id)); /* update eigrp_interface's */ FOR_ALL_INTERFACES (vrf, ifp) eigrp_if_update(ifp); } } void eigrp_master_init(void) { struct timeval tv; memset(&eigrp_master, 0, sizeof(struct eigrp_master)); eigrp_om = &eigrp_master; eigrp_om->eigrp = list_new(); monotime(&tv); eigrp_om->start_time = tv.tv_sec; } /* Allocate new eigrp structure. */ static struct eigrp *eigrp_new(uint16_t as, vrf_id_t vrf_id) { struct eigrp *eigrp = XCALLOC(MTYPE_EIGRP_TOP, sizeof(struct eigrp)); /* init information relevant to peers */ eigrp->vrf_id = vrf_id; eigrp->vrid = 0; eigrp->AS = as; eigrp->router_id.s_addr = 0; eigrp->router_id_static.s_addr = 0; eigrp->sequence_number = 1; /*Configure default K Values for EIGRP Process*/ eigrp->k_values[0] = EIGRP_K1_DEFAULT; eigrp->k_values[1] = EIGRP_K2_DEFAULT; eigrp->k_values[2] = EIGRP_K3_DEFAULT; eigrp->k_values[3] = EIGRP_K4_DEFAULT; eigrp->k_values[4] = EIGRP_K5_DEFAULT; eigrp->k_values[5] = EIGRP_K6_DEFAULT; /* init internal data structures */ eigrp->eiflist = list_new(); eigrp->passive_interface_default = EIGRP_IF_ACTIVE; eigrp->networks = eigrp_topology_new(); eigrp->fd = eigrp_sock_init(vrf_lookup_by_id(vrf_id)); if (eigrp->fd < 0) { flog_err_sys( EC_LIB_SOCKET, "eigrp_new: fatal error: eigrp_sock_init was unable to open a socket"); exit(1); } eigrp->maxsndbuflen = getsockopt_so_sendbuf(eigrp->fd); eigrp->ibuf = stream_new(EIGRP_PACKET_MAX_LEN + 1); eigrp->t_read = NULL; thread_add_read(master, eigrp_read, eigrp, eigrp->fd, &eigrp->t_read); eigrp->oi_write_q = list_new(); eigrp->topology_table = route_table_init(); eigrp->neighbor_self = eigrp_nbr_new(NULL); eigrp->neighbor_self->src.s_addr = INADDR_ANY; eigrp->variance = EIGRP_VARIANCE_DEFAULT; eigrp->max_paths = EIGRP_MAX_PATHS_DEFAULT; eigrp->serno = 0; eigrp->serno_last_update = 0; eigrp->topology_changes_externalIPV4 = list_new(); eigrp->topology_changes_internalIPV4 = list_new(); eigrp->list[EIGRP_FILTER_IN] = NULL; eigrp->list[EIGRP_FILTER_OUT] = NULL; eigrp->prefix[EIGRP_FILTER_IN] = NULL; eigrp->prefix[EIGRP_FILTER_OUT] = NULL; eigrp->routemap[EIGRP_FILTER_IN] = NULL; eigrp->routemap[EIGRP_FILTER_OUT] = NULL; /* Distribute list install. */ eigrp->distribute_ctx = distribute_list_ctx_create(vrf_lookup_by_id(eigrp->vrf_id)); distribute_list_add_hook(eigrp->distribute_ctx, eigrp_distribute_update); distribute_list_delete_hook(eigrp->distribute_ctx, eigrp_distribute_update); /* eigrp->if_rmap_ctx = if_rmap_ctx_create(eigrp->vrf_id); if_rmap_hook_add (eigrp_if_rmap_update); if_rmap_hook_delete (eigrp_if_rmap_update); */ QOBJ_REG(eigrp, eigrp); return eigrp; } struct eigrp *eigrp_get(uint16_t as, vrf_id_t vrf_id) { struct eigrp *eigrp; eigrp = eigrp_lookup(vrf_id); if (eigrp == NULL) { eigrp = eigrp_new(as, vrf_id); listnode_add(eigrp_om->eigrp, eigrp); } return eigrp; } /* Shut down the entire process */ void eigrp_terminate(void) { struct eigrp *eigrp; struct listnode *node, *nnode; /* shutdown already in progress */ if (CHECK_FLAG(eigrp_om->options, EIGRP_MASTER_SHUTDOWN)) return; SET_FLAG(eigrp_om->options, EIGRP_MASTER_SHUTDOWN); for (ALL_LIST_ELEMENTS(eigrp_om->eigrp, node, nnode, eigrp)) eigrp_finish(eigrp); frr_fini(); } void eigrp_finish(struct eigrp *eigrp) { eigrp_finish_final(eigrp); /* eigrp being shut-down? If so, was this the last eigrp instance? */ if (CHECK_FLAG(eigrp_om->options, EIGRP_MASTER_SHUTDOWN) && (listcount(eigrp_om->eigrp) == 0)) { if (zclient) { zclient_stop(zclient); zclient_free(zclient); } exit(0); } return; } /* Final cleanup of eigrp instance */ void eigrp_finish_final(struct eigrp *eigrp) { struct eigrp_interface *ei; struct eigrp_neighbor *nbr; struct listnode *node, *nnode, *node2, *nnode2; for (ALL_LIST_ELEMENTS(eigrp->eiflist, node, nnode, ei)) { for (ALL_LIST_ELEMENTS(ei->nbrs, node2, nnode2, nbr)) eigrp_nbr_delete(nbr); eigrp_if_free(ei, INTERFACE_DOWN_BY_FINAL); } THREAD_OFF(eigrp->t_write); THREAD_OFF(eigrp->t_read); close(eigrp->fd); list_delete(&eigrp->eiflist); list_delete(&eigrp->oi_write_q); eigrp_topology_free(eigrp, eigrp->topology_table); eigrp_nbr_delete(eigrp->neighbor_self); list_delete(&eigrp->topology_changes_externalIPV4); list_delete(&eigrp->topology_changes_internalIPV4); listnode_delete(eigrp_om->eigrp, eigrp); stream_free(eigrp->ibuf); distribute_list_delete(&eigrp->distribute_ctx); XFREE(MTYPE_EIGRP_TOP, eigrp); } /*Look for existing eigrp process*/ struct eigrp *eigrp_lookup(vrf_id_t vrf_id) { struct eigrp *eigrp; struct listnode *node, *nnode; for (ALL_LIST_ELEMENTS(eigrp_om->eigrp, node, nnode, eigrp)) if (eigrp->vrf_id == vrf_id) return eigrp; return NULL; } frr-7.2.1/eigrpd/eigrpd.conf.sample0000644000000000000000000000027213610377563014070 00000000000000! -*- eigrpd -*- ! ! EIGRPDd sample configuration file ! ! hostname eigrpd password zebra !enable password please-set-at-here ! !router eigrp 4453 ! network 192.168.1.0/24 ! log stdout frr-7.2.1/eigrpd/eigrpd.h0000644000000000000000000000732213610377563012115 00000000000000/* * EIGRP main header. * Copyright (C) 2013-2014 * Authors: * Donnie Savage * Jan Janovic * Matej Perina * Peter Orsag * Peter Paluch * * This file is part of GNU Zebra. * * GNU Zebra is free software; you can redistribute it and/or modify it * under the terms of the GNU General Public License as published by the * Free Software Foundation; either version 2, or (at your option) any * later version. * * GNU Zebra 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; see the file COPYING; if not, write to the Free Software * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA */ #ifndef _ZEBRA_EIGRPD_H #define _ZEBRA_EIGRPD_H #include #include "filter.h" #include "log.h" /* Set EIGRP version is "classic" - wide metrics comes next */ #define EIGRP_MAJOR_VERSION 1 #define EIGRP_MINOR_VERSION 2 /* Extern variables. */ extern struct zclient *zclient; extern struct thread_master *master; extern struct eigrp_master *eigrp_om; extern struct zebra_privs_t eigrpd_privs; /* Prototypes */ extern void eigrp_master_init(void); extern void eigrp_terminate(void); extern void eigrp_finish_final(struct eigrp *); extern void eigrp_finish(struct eigrp *); extern struct eigrp *eigrp_get(uint16_t as, vrf_id_t vrf_id); extern struct eigrp *eigrp_lookup(vrf_id_t vrf_id); extern void eigrp_router_id_update(struct eigrp *); /* eigrp_cli.c */ extern void eigrp_cli_show_header(struct vty *vty, struct lyd_node *dnode, bool show_defaults); extern void eigrp_cli_show_end_header(struct vty *vty, struct lyd_node *dnode); extern void eigrp_cli_show_router_id(struct vty *vty, struct lyd_node *dnode, bool show_defaults); extern void eigrp_cli_show_passive_interface(struct vty *vty, struct lyd_node *dnode, bool show_defaults); extern void eigrp_cli_show_active_time(struct vty *vty, struct lyd_node *dnode, bool show_defaults); extern void eigrp_cli_show_variance(struct vty *vty, struct lyd_node *dnode, bool show_defaults); extern void eigrp_cli_show_maximum_paths(struct vty *vty, struct lyd_node *dnode, bool show_defaults); extern void eigrp_cli_show_metrics(struct vty *vty, struct lyd_node *dnode, bool show_defaults); extern void eigrp_cli_show_network(struct vty *vty, struct lyd_node *dnode, bool show_defaults); extern void eigrp_cli_show_neighbor(struct vty *vty, struct lyd_node *dnode, bool show_defaults); extern void eigrp_cli_show_redistribute(struct vty *vty, struct lyd_node *dnode, bool show_defaults); extern void eigrp_cli_show_delay(struct vty *vty, struct lyd_node *dnode, bool show_defaults); extern void eigrp_cli_show_bandwidth(struct vty *vty, struct lyd_node *dnode, bool show_defaults); extern void eigrp_cli_show_hello_interval(struct vty *vty, struct lyd_node *dnode, bool show_defaults); extern void eigrp_cli_show_hold_time(struct vty *vty, struct lyd_node *dnode, bool show_defaults); extern void eigrp_cli_show_summarize_address(struct vty *vty, struct lyd_node *dnode, bool show_defaults); extern void eigrp_cli_show_authentication(struct vty *vty, struct lyd_node *dnode, bool show_defaults); extern void eigrp_cli_show_keychain(struct vty *vty, struct lyd_node *dnode, bool show_defaults); extern void eigrp_cli_init(void); /* eigrp_northbound.c */ extern const struct frr_yang_module_info frr_eigrpd_info; #endif /* _ZEBRA_EIGRPD_H */ frr-7.2.1/eigrpd/subdir.am0000644000000000000000000000351213610377563012276 00000000000000# # eigrpd # if EIGRPD noinst_LIBRARIES += eigrpd/libeigrp.a sbin_PROGRAMS += eigrpd/eigrpd dist_examples_DATA += eigrpd/eigrpd.conf.sample vtysh_scan += \ $(top_srcdir)/eigrpd/eigrp_cli.c \ $(top_srcdir)/eigrpd/eigrp_dump.c \ $(top_srcdir)/eigrpd/eigrp_vty.c \ # end # $(top_srcdir)/eigrpd/eigrp_routemap.c man8 += $(MANBUILD)/frr-eigrpd.8 endif eigrpd_libeigrp_a_SOURCES = \ eigrpd/eigrp_cli.c \ eigrpd/eigrp_dump.c \ eigrpd/eigrp_errors.c \ eigrpd/eigrp_filter.c \ eigrpd/eigrp_fsm.c \ eigrpd/eigrp_hello.c \ eigrpd/eigrp_interface.c \ eigrpd/eigrp_memory.c \ eigrpd/eigrp_neighbor.c \ eigrpd/eigrp_network.c \ eigrpd/eigrp_northbound.c \ eigrpd/eigrp_packet.c \ eigrpd/eigrp_query.c \ eigrpd/eigrp_reply.c \ eigrpd/eigrp_siaquery.c \ eigrpd/eigrp_siareply.c \ eigrpd/eigrp_snmp.c \ eigrpd/eigrp_topology.c \ eigrpd/eigrp_update.c \ eigrpd/eigrp_vrf.c \ eigrpd/eigrp_vty.c \ eigrpd/eigrp_zebra.c \ eigrpd/eigrpd.c \ # end eigrpdheaderdir = $(pkgincludedir)/eigrpd eigrpdheader_HEADERS = \ eigrpd/eigrp_dump.h \ eigrpd/eigrp_topology.h \ eigrpd/eigrpd.h \ # end eigrpd/eigrp_vty_clippy.c: $(CLIPPY_DEPS) eigrpd/eigrp_vty.$(OBJEXT): eigrpd/eigrp_vty_clippy.c eigrpd/eigrp_cli_clippy.c: $(CLIPPY_DEPS) eigrpd/eigrp_cli.$(OBJEXT): eigrpd/eigrp_cli_clippy.c noinst_HEADERS += \ eigrpd/eigrp_const.h \ eigrpd/eigrp_errors.h \ eigrpd/eigrp_filter.h \ eigrpd/eigrp_fsm.h \ eigrpd/eigrp_interface.h \ eigrpd/eigrp_macros.h \ eigrpd/eigrp_memory.h \ eigrpd/eigrp_neighbor.h \ eigrpd/eigrp_network.h \ eigrpd/eigrp_packet.h \ eigrpd/eigrp_snmp.h \ eigrpd/eigrp_structs.h \ eigrpd/eigrp_vrf.h \ eigrpd/eigrp_vty.h \ eigrpd/eigrp_zebra.h \ # end nodist_eigrpd_eigrpd_SOURCES = \ yang/frr-eigrpd.yang.c \ # end eigrpd_eigrpd_SOURCES = eigrpd/eigrp_main.c eigrpd_eigrpd_LDADD = eigrpd/libeigrp.a lib/libfrr.la $(LIBCAP) frr-7.2.1/fpm/0000755000000000000000000000000013610377563010056 500000000000000frr-7.2.1/fpm/Makefile0000644000000000000000000000023013610377563011431 00000000000000all: ALWAYS @$(MAKE) -s -C .. fpm/libfrrfpm_pb.la %: ALWAYS @$(MAKE) -s -C .. fpm/$@ Makefile: #nothing ALWAYS: .PHONY: ALWAYS makefiles .SUFFIXES: frr-7.2.1/fpm/fpm.h0000644000000000000000000001721313610377563010735 00000000000000/* * Public definitions pertaining to the Forwarding Plane Manager component. * * Permission is granted to use, copy, modify and/or distribute this * software under either one of the licenses below. * * Note that if you use other files from the Quagga tree directly or * indirectly, then the licenses in those files still apply. * * Please retain both licenses below when modifying this code in the * Quagga tree. * * Copyright (C) 2012 by Open Source Routing. * Copyright (C) 2012 by Internet Systems Consortium, Inc. ("ISC") */ /* * License Option 1: GPL * * This program is free software; you can redistribute it and/or modify it * under the terms of the GNU General Public License as published by the Free * Software Foundation; either version 2 of the License, or (at your option) * any later version. * * This program is distributed in the hope that it will be useful,but WITHOUT * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for * more details. * * You should have received a copy of the GNU General Public License along * with this program; if not, write to the Free Software Foundation, Inc., * 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. */ /* * License Option 2: ISC License * * Permission to use, copy, modify, and/or distribute this software * for any purpose with or without fee is hereby granted, provided * that the above copyright notice and this permission notice appear * in all copies. * * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL * WARRANTIES WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED * WARRANTIES OF MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE * AUTHOR BE LIABLE FOR ANY SPECIAL, DIRECT, INDIRECT, OR * CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM LOSS * OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, * NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN * CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. */ #ifndef _FPM_H #define _FPM_H /* * The Forwarding Plane Manager (FPM) is an optional component that * may be used in scenarios where the router has a forwarding path * that is distinct from the kernel, commonly a hardware-based fast * path. It is responsible for programming forwarding information * (such as routes and nexthops) in the fast path. * * In Quagga, the Routing Information Base is maintained in the * 'zebra' infrastructure daemon. Routing protocols communicate their * best routes to zebra, and zebra computes the best route across * protocols for each prefix. This latter information comprises the * bulk of the Forwarding Information Base. * * This header file defines a point-to-point interface using which * zebra can update the FPM about changes in routes. The communication * takes place over a stream socket. The FPM listens on a well-known * TCP port, and zebra initiates the connection. * * All messages sent over the connection start with a short FPM * header, fpm_msg_hdr_t. In the case of route add/delete messages, * the header is followed by a netlink message. Zebra should send a * complete copy of the forwarding table(s) to the FPM, including * routes that it may have picked up from the kernel. * * The FPM interface uses replace semantics. That is, if a 'route add' * message for a prefix is followed by another 'route add' message, the * information in the second message is complete by itself, and replaces * the information sent in the first message. * * If the connection to the FPM goes down for some reason, the client * (zebra) should send the FPM a complete copy of the forwarding * table(s) when it reconnects. */ /* * Local host as a default server for fpm connection */ #define FPM_DEFAULT_IP (htonl (INADDR_LOOPBACK)) /* * default port for fpm connections */ #define FPM_DEFAULT_PORT 2620 /* * Largest message that can be sent to or received from the FPM. */ #define FPM_MAX_MSG_LEN 4096 #ifdef __SUNPRO_C #pragma pack(1) #endif /* * Header that precedes each fpm message to/from the FPM. */ typedef struct fpm_msg_hdr_t_ { /* * Protocol version. */ uint8_t version; /* * Type of message, see below. */ uint8_t msg_type; /* * Length of entire message, including the header, in network byte * order. */ uint16_t msg_len; } __attribute__((packed)) fpm_msg_hdr_t; #ifdef __SUNPRO_C #pragma pack() #endif /* * The current version of the FPM protocol is 1. */ #define FPM_PROTO_VERSION 1 typedef enum fpm_msg_type_e_ { FPM_MSG_TYPE_NONE = 0, /* * Indicates that the payload is a completely formed netlink * message. * * XXX Netlink cares about the alignment of messages. When any * FPM_MSG_TYPE_NETLINK messages are sent over a channel, then all * messages should be sized such that netlink alignment is * maintained. */ FPM_MSG_TYPE_NETLINK = 1, FPM_MSG_TYPE_PROTOBUF = 2, } fpm_msg_type_e; /* * The FPM message header is aligned to the same boundary as netlink * messages (4). This means that a netlink message does not need * padding when encapsulated in an FPM message. */ #define FPM_MSG_ALIGNTO 4 /* * fpm_msg_align * * Round up the given length to the desired alignment. * * **NB**: Alignment is required only when netlink messages are used. */ static inline size_t fpm_msg_align(size_t len) { return (len + FPM_MSG_ALIGNTO - 1) & ~(FPM_MSG_ALIGNTO - 1); } /* * The (rounded up) size of the FPM message header. This ensures that * the message payload always starts at an aligned address. */ #define FPM_MSG_HDR_LEN (sizeof (fpm_msg_hdr_t)) #ifndef COMPILE_ASSERT #define COMPILE_ASSERT(x) extern int __dummy[2 * !!(x) - 1] #endif COMPILE_ASSERT(FPM_MSG_ALIGNTO == FPM_MSG_HDR_LEN); /* * fpm_data_len_to_msg_len * * The length value that should be placed in the msg_len field of the * header for a *payload* of size 'data_len'. */ static inline size_t fpm_data_len_to_msg_len(size_t data_len) { return data_len + FPM_MSG_HDR_LEN; } /* * fpm_msg_data * * Pointer to the payload of the given fpm header. */ static inline void *fpm_msg_data(fpm_msg_hdr_t *hdr) { return ((char *)hdr) + FPM_MSG_HDR_LEN; } /* * fpm_msg_len */ static inline size_t fpm_msg_len(const fpm_msg_hdr_t *hdr) { return ntohs(hdr->msg_len); } /* * fpm_msg_data_len */ static inline size_t fpm_msg_data_len(const fpm_msg_hdr_t *hdr) { return (fpm_msg_len(hdr) - FPM_MSG_HDR_LEN); } /* * fpm_msg_next * * Move to the next message in a buffer. */ static inline fpm_msg_hdr_t *fpm_msg_next(fpm_msg_hdr_t *hdr, size_t *len) { size_t msg_len; msg_len = fpm_msg_len(hdr); if (len) { if (*len < msg_len) { assert(0); return NULL; } *len -= msg_len; } return (fpm_msg_hdr_t *)(((char *)hdr) + msg_len); } /* * fpm_msg_hdr_ok * * Returns true if a message header looks well-formed. */ static inline int fpm_msg_hdr_ok(const fpm_msg_hdr_t *hdr) { size_t msg_len; if (hdr->msg_type == FPM_MSG_TYPE_NONE) return 0; msg_len = fpm_msg_len(hdr); if (msg_len < FPM_MSG_HDR_LEN || msg_len > FPM_MAX_MSG_LEN) return 0; /* * Netlink messages must be aligned properly. */ if (hdr->msg_type == FPM_MSG_TYPE_NETLINK && fpm_msg_align(msg_len) != msg_len) return 0; return 1; } /* * fpm_msg_ok * * Returns true if a message looks well-formed. * * @param len The length in bytes from 'hdr' to the end of the buffer. */ static inline int fpm_msg_ok(const fpm_msg_hdr_t *hdr, size_t len) { if (len < FPM_MSG_HDR_LEN) return 0; if (!fpm_msg_hdr_ok(hdr)) return 0; if (fpm_msg_len(hdr) > len) return 0; return 1; } // tcp maximum range #define TCP_MAX_PORT 65535 // tcp minimum range #define TCP_MIN_PORT 1 #endif /* _FPM_H */ frr-7.2.1/fpm/fpm.proto0000644000000000000000000000421613610377563011650 00000000000000// // fpm.proto // // @copyright Copyright (C) 2016 Sproute Networks, Inc. // // @author Avneesh Sachdev // // Permission to use, copy, modify, and/or distribute this software // for any purpose with or without fee is hereby granted, provided // that the above copyright notice and this permission notice appear // in all copies. // // THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL // WARRANTIES WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED // WARRANTIES OF MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE // AUTHOR BE LIABLE FOR ANY SPECIAL, DIRECT, INDIRECT, OR // CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM LOSS // OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, // NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN // CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. // syntax = "proto2"; // // Protobuf definitions pertaining to the Forwarding Plane Manager component. // package fpm; import "qpb/qpb.proto"; // // A Nexthop for a route. It indicates how packets to a given prefix // should be forwarded (for instance, send them out of a specified // interface to a specified address). // message Nexthop { optional qpb.IfIdentifier if_id = 2; optional qpb.L3Address address = 3; } message RouteKey { optional qpb.L3Prefix prefix = 1; } message DeleteRoute { required uint32 vrf_id = 1; required qpb.AddressFamily address_family = 2; required qpb.SubAddressFamily sub_address_family = 3; required RouteKey key = 4; } enum RouteType { UNKNOWN = 0; NORMAL = 1; UNREACHABLE = 2; BLACKHOLE = 3; } message AddRoute { required uint32 vrf_id = 1; required qpb.AddressFamily address_family = 2; required qpb.SubAddressFamily sub_address_family = 3; required RouteKey key = 4; optional RouteType route_type = 5; required qpb.Protocol protocol = 6; required int32 metric = 8; repeated Nexthop nexthops = 9; } // // Any message from the FPM. // message Message { enum Type { UNKNOWN_MSG = 0; ADD_ROUTE = 1; DELETE_ROUTE = 2; }; optional Type type = 1; optional AddRoute add_route = 2; optional DeleteRoute delete_route = 3; } frr-7.2.1/fpm/fpm_pb.c0000644000000000000000000000163013610377563011405 00000000000000/* * fpm_pb.c * * @copyright Copyright (C) 2016 Sproute Networks, Inc. * * @author Avneesh Sachdev * * This file is part of Quagga. * * Quagga is free software; you can redistribute it and/or modify it * under the terms of the GNU General Public License as published by the * Free Software Foundation; either version 2, or (at your option) any * later version. * * Quagga 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; see the file COPYING; if not, write to the Free Software * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA */ /* * Main file for the fpm_pb library. */ frr-7.2.1/fpm/fpm_pb.h0000644000000000000000000000272513610377563011420 00000000000000/* * fpm_pb.h * * @copyright Copyright (C) 2016 Sproute Networks, Inc. * * @author Avneesh Sachdev * * This file is part of Quagga. * * Quagga is free software; you can redistribute it and/or modify it * under the terms of the GNU General Public License as published by the * Free Software Foundation; either version 2, or (at your option) any * later version. * * Quagga 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; see the file COPYING; if not, write to the Free Software * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA */ /* * Public header file for fpm protobuf definitions. */ #ifndef _FPM_PB_H #define _FPM_PB_H #include "route_types.h" #include "qpb/qpb.h" #include "fpm/fpm.pb-c.h" /* * fpm__route_key__create */ #define fpm_route_key_create fpm__route_key__create static inline Fpm__RouteKey *fpm__route_key__create(qpb_allocator_t *allocator, struct prefix *prefix) { Fpm__RouteKey *key; key = QPB_ALLOC(allocator, typeof(*key)); if (!key) { return NULL; } fpm__route_key__init(key); key->prefix = qpb__l3_prefix__create(allocator, prefix); if (!key->prefix) { return NULL; } return key; } #endif frr-7.2.1/fpm/subdir.am0000644000000000000000000000065713610377563011615 00000000000000if FPM if HAVE_PROTOBUF lib_LTLIBRARIES += fpm/libfrrfpm_pb.la endif endif fpm_libfrrfpm_pb_la_LDFLAGS = -version-info 0:0:0 fpm_libfrrfpm_pb_la_CPPFLAGS = $(AM_CPPFLAGS) $(PROTOBUF_C_CFLAGS) fpm_libfrrfpm_pb_la_SOURCES = \ fpm/fpm.h \ fpm/fpm_pb.h \ fpm/fpm_pb.c \ # end nodist_fpm_libfrrfpm_pb_la_SOURCES = \ fpm/fpm.pb-c.c \ # end CLEANFILES += \ fpm/fpm.pb-c.c \ fpm/fpm.pb-c.h \ # end EXTRA_DIST += fpm/fpm.proto frr-7.2.1/grpc/0000755000000000000000000000000013610377563010227 500000000000000frr-7.2.1/grpc/Makefile0000644000000000000000000000023313610377563011605 00000000000000all: ALWAYS @$(MAKE) -s -C .. grpc/libfrrgrpc_pb.la %: ALWAYS @$(MAKE) -s -C .. grpc/$@ Makefile: #nothing ALWAYS: .PHONY: ALWAYS makefiles .SUFFIXES: frr-7.2.1/grpc/frr-northbound.proto0000644000000000000000000002370013610377563014207 00000000000000// // Copyright (C) 2019 NetDEF, Inc. // Renato Westphal // // This program is free software; you can redistribute it and/or modify it // under the terms of the 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; see the file COPYING; if not, write to the Free Software // Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA // syntax = "proto3"; package frr; // Service specification for the FRR northbound interface. service Northbound { // Retrieve the capabilities supported by the target. rpc GetCapabilities(GetCapabilitiesRequest) returns (GetCapabilitiesResponse) {} // Retrieve configuration data, state data or both from the target. rpc Get(GetRequest) returns (stream GetResponse) {} // Create a new candidate configuration and return a reference to it. The // created candidate is a copy of the running configuration. rpc CreateCandidate(CreateCandidateRequest) returns (CreateCandidateResponse) {} // Delete a candidate configuration. rpc DeleteCandidate(DeleteCandidateRequest) returns (DeleteCandidateResponse) {} // Update a candidate configuration by rebasing the changes on top of the // latest running configuration. Resolve conflicts automatically by giving // preference to the changes done in the candidate configuration. rpc UpdateCandidate(UpdateCandidateRequest) returns (UpdateCandidateResponse) {} // Edit a candidate configuration. All changes are discarded if any error // happens. rpc EditCandidate(EditCandidateRequest) returns (EditCandidateResponse) {} // Load configuration data into a candidate configuration. Both merge and // replace semantics are supported. rpc LoadToCandidate(LoadToCandidateRequest) returns (LoadToCandidateResponse) {} // Create a new configuration transaction using a two-phase commit protocol. rpc Commit(CommitRequest) returns (CommitResponse) {} // List the metadata of all configuration transactions recorded in the // transactions database. rpc ListTransactions(ListTransactionsRequest) returns (stream ListTransactionsResponse) {} // Fetch a configuration (identified by its transaction ID) from the // transactions database. rpc GetTransaction(GetTransactionRequest) returns (GetTransactionResponse) {} // Lock the running configuration, preventing other users from changing it. rpc LockConfig(LockConfigRequest) returns (LockConfigResponse) {} // Unlock the running configuration. rpc UnlockConfig(UnlockConfigRequest) returns (UnlockConfigResponse) {} // Execute a YANG RPC. rpc Execute(ExecuteRequest) returns (ExecuteResponse) {} } // ----------------------- Parameters and return types ------------------------- // // RPC: GetCapabilities() // message GetCapabilitiesRequest { // Empty. } message GetCapabilitiesResponse { // Return values: // - grpc::StatusCode::OK: Success. // FRR version. string frr_version = 1; // Indicates whether FRR was compiled with support for configuration // rollbacks or not (--enable-config-rollbacks). bool rollback_support = 2; // Supported schema modules. repeated ModuleData supported_modules = 3; // Supported encodings. repeated Encoding supported_encodings = 4; } // // RPC: Get() // message GetRequest { // Type of elements within the data tree. enum DataType { // All data elements. ALL = 0; // Config elements. CONFIG = 1; // State elements. STATE = 2; } // The type of data being requested. DataType type = 1; // Encoding to be used. Encoding encoding = 2; // Include implicit default nodes. bool with_defaults = 3; // Paths requested by the client. repeated string path = 4; } message GetResponse { // Return values: // - grpc::StatusCode::OK: Success. // - grpc::StatusCode::INVALID_ARGUMENT: Invalid YANG data path. // Timestamp in nanoseconds since Epoch. int64 timestamp = 1; // The requested data. DataTree data = 2; } // // RPC: CreateCandidate() // message CreateCandidateRequest { // Empty. } message CreateCandidateResponse { // Return values: // - grpc::StatusCode::OK: Success. // - grpc::StatusCode::RESOURCE_EXHAUSTED: can't create candidate // configuration. // Handle to the new created candidate configuration. uint32 candidate_id = 1; } // // RPC: DeleteCandidate() // message DeleteCandidateRequest { // Candidate configuration to delete. uint32 candidate_id = 1; } message DeleteCandidateResponse { // Return values: // - grpc::StatusCode::OK: Success. // - grpc::StatusCode::NOT_FOUND: Candidate wasn't found. } // // RPC: UpdateCandidate() // message UpdateCandidateRequest { // Candidate configuration to update. uint32 candidate_id = 1; } message UpdateCandidateResponse { // Return values: // - grpc::StatusCode::OK: Success. // - grpc::StatusCode::NOT_FOUND: Candidate wasn't found. } // // RPC: EditCandidate() // message EditCandidateRequest { // Candidate configuration that is going to be edited. uint32 candidate_id = 1; // Data elements to be created or updated. repeated PathValue update = 2; // Paths to be deleted from the data tree. repeated PathValue delete = 3; } message EditCandidateResponse { // Return values: // - grpc::StatusCode::OK: Success. // - grpc::StatusCode::NOT_FOUND: Candidate wasn't found. // - grpc::StatusCode::INVALID_ARGUMENT: An error occurred while editing the // candidate configuration. } // // RPC: LoadToCandidate() // message LoadToCandidateRequest { enum LoadType { // Merge the data tree into the candidate configuration. MERGE = 0; // Replace the candidate configuration by the provided data tree. REPLACE = 1; } // Candidate configuration that is going to be edited. uint32 candidate_id = 1; // Load operation to apply. LoadType type = 2; // Configuration data. DataTree config = 3; } message LoadToCandidateResponse { // Return values: // - grpc::StatusCode::OK: Success. // - grpc::StatusCode::INVALID_ARGUMENT: An error occurred while performing // the load operation. } // // RPC: Commit() // message CommitRequest { enum Phase { // Validate if the configuration changes are valid (phase 0). VALIDATE = 0; // Prepare resources to apply the configuration changes (phase 1). PREPARE = 1; // Release previously allocated resources (phase 2). ABORT = 2; // Apply the configuration changes (phase 2). APPLY = 3; // All of the above (VALIDATE + PREPARE + ABORT/APPLY). // // This option can't be used to implement network-wide transactions, // since they require the manager entity to take into account the results // of the preparation phase of multiple managed devices. ALL = 4; } // Candidate configuration that is going to be committed. uint32 candidate_id = 1; // Transaction phase. Phase phase = 2; // Assign a comment to this commit. string comment = 3; } message CommitResponse { // Return values: // - grpc::StatusCode::OK: Success. // - grpc::StatusCode::FAILED_PRECONDITION: misuse of the two-phase commit // protocol. // - grpc::StatusCode::INVALID_ARGUMENT: Validation error. // - grpc::StatusCode::RESOURCE_EXHAUSTED: Failure to allocate resource. // ID of the created configuration transaction (when the phase is APPLY // or ALL). uint32 transaction_id = 1; } // // RPC: ListTransactions() // message ListTransactionsRequest { // Empty. } message ListTransactionsResponse { // Return values: // - grpc::StatusCode::OK: Success. // Transaction ID. uint32 id = 1; // Client that committed the transaction. string client = 2; // Date and time the transaction was committed. string date = 3; // Comment assigned to the transaction. string comment = 4; } // // RPC: GetTransaction() // message GetTransactionRequest { // Transaction to retrieve. uint32 transaction_id = 1; // Encoding to be used. Encoding encoding = 2; // Include implicit default nodes. bool with_defaults = 3; } message GetTransactionResponse { // Return values: // - grpc::StatusCode::OK: Success. // - grpc::StatusCode::NOT_FOUND: Transaction wasn't found in the transactions // database. DataTree config = 1; } // // RPC: LockConfig() // message LockConfigRequest { // Empty. } message LockConfigResponse { // Return values: // - grpc::StatusCode::OK: Success. // - grpc::StatusCode::FAILED_PRECONDITION: Running configuration is // locked already. } // // RPC: UnlockConfig() // message UnlockConfigRequest { // Empty. } message UnlockConfigResponse { // Return values: // - grpc::StatusCode::OK: Success. // - grpc::StatusCode::FAILED_PRECONDITION: Running configuration isn't // locked. } // // RPC: Execute() // message ExecuteRequest { // Path of the YANG RPC or YANG Action. string path = 1; // Input parameters. repeated PathValue input = 2; } message ExecuteResponse { // Return values: // - grpc::StatusCode::OK: Success. // Output parameters. repeated PathValue output = 1; } // -------------------------------- Definitions -------------------------------- // YANG module. message ModuleData { // Name of the YANG module; string name = 1; // Organization publishing the module. string organization = 2; // Latest revision of the module; string revision = 3; } // Supported encodings for YANG instance data. enum Encoding { JSON = 0; XML = 1; } // Path-value pair representing a data element. message PathValue { // YANG data path. string path = 1; // Data value. string value = 2; } // YANG instance data. message DataTree { Encoding encoding = 1; string data = 2; } frr-7.2.1/grpc/subdir.am0000644000000000000000000000154513610377563011763 00000000000000if GRPC lib_LTLIBRARIES += grpc/libfrrgrpc_pb.la endif grpc_libfrrgrpc_pb_la_LDFLAGS = -version-info 0:0:0 grpc_libfrrgrpc_pb_la_CPPFLAGS = $(AM_CPPFLAGS) $(GRPC_CXXFLAGS) nodist_grpc_libfrrgrpc_pb_la_SOURCES = \ grpc/frr-northbound.pb.cc \ grpc/frr-northbound.grpc.pb.cc \ # end CLEANFILES += \ grpc/frr-northbound.pb.cc \ grpc/frr-northbound.pb.h \ grpc/frr-northbound.grpc.pb.cc \ grpc/frr-northbound.grpc.pb.h \ # end EXTRA_DIST += grpc/frr-northbound.proto AM_V_PROTOC = $(am__v_PROTOC_$(V)) am__v_PROTOC_ = $(am__v_PROTOC_$(AM_DEFAULT_VERBOSITY)) am__v_PROTOC_0 = @echo " PROTOC" $@; am__v_PROTOC_1 = .proto.pb.cc: $(AM_V_PROTOC)$(PROTOC) -I$(top_srcdir) --cpp_out=$(top_srcdir) $(top_srcdir)/$^ .proto.grpc.pb.cc: $(AM_V_PROTOC)$(PROTOC) -I$(top_srcdir) --grpc_out=$(top_srcdir) --plugin=protoc-gen-grpc=`which grpc_cpp_plugin` $(top_srcdir)/$^ frr-7.2.1/include/0000755000000000000000000000000013610377563010717 500000000000000frr-7.2.1/include/linux/0000755000000000000000000000000013610377563012056 500000000000000frr-7.2.1/include/linux/fib_rules.h0000644000000000000000000000322313610377563014121 00000000000000#ifndef __LINUX_FIB_RULES_H #define __LINUX_FIB_RULES_H #include #include /* rule is permanent, and cannot be deleted */ #define FIB_RULE_PERMANENT 0x00000001 #define FIB_RULE_INVERT 0x00000002 #define FIB_RULE_UNRESOLVED 0x00000004 #define FIB_RULE_IIF_DETACHED 0x00000008 #define FIB_RULE_DEV_DETACHED FIB_RULE_IIF_DETACHED #define FIB_RULE_OIF_DETACHED 0x00000010 /* try to find source address in routing lookups */ #define FIB_RULE_FIND_SADDR 0x00010000 struct fib_rule_hdr { __u8 family; __u8 dst_len; __u8 src_len; __u8 tos; __u8 table; __u8 res1; /* reserved */ __u8 res2; /* reserved */ __u8 action; __u32 flags; }; enum { FRA_UNSPEC, FRA_DST, /* destination address */ FRA_SRC, /* source address */ FRA_IIFNAME, /* interface name */ #define FRA_IFNAME FRA_IIFNAME FRA_GOTO, /* target to jump to (FR_ACT_GOTO) */ FRA_UNUSED2, FRA_PRIORITY, /* priority/preference */ FRA_UNUSED3, FRA_UNUSED4, FRA_UNUSED5, FRA_FWMARK, /* mark */ FRA_FLOW, /* flow/class id */ FRA_UNUSED6, FRA_SUPPRESS_IFGROUP, FRA_SUPPRESS_PREFIXLEN, FRA_TABLE, /* Extended table id */ FRA_FWMASK, /* mask for netfilter mark */ FRA_OIFNAME, FRA_PAD, FRA_L3MDEV, /* iif or oif is l3mdev goto its table */ __FRA_MAX }; #define FRA_MAX (__FRA_MAX - 1) enum { FR_ACT_UNSPEC, FR_ACT_TO_TBL, /* Pass to fixed table */ FR_ACT_GOTO, /* Jump to another rule */ FR_ACT_NOP, /* No operation */ FR_ACT_RES3, FR_ACT_RES4, FR_ACT_BLACKHOLE, /* Drop without notification */ FR_ACT_UNREACHABLE, /* Drop with ENETUNREACH */ FR_ACT_PROHIBIT, /* Drop with EACCES */ __FR_ACT_MAX, }; #define FR_ACT_MAX (__FR_ACT_MAX - 1) #endif frr-7.2.1/include/linux/if_addr.h0000644000000000000000000000351113610377563013537 00000000000000/* SPDX-License-Identifier: GPL-2.0 WITH Linux-syscall-note */ #ifndef __LINUX_IF_ADDR_H #define __LINUX_IF_ADDR_H #include #include struct ifaddrmsg { __u8 ifa_family; __u8 ifa_prefixlen; /* The prefix length */ __u8 ifa_flags; /* Flags */ __u8 ifa_scope; /* Address scope */ __u32 ifa_index; /* Link index */ }; /* * Important comment: * IFA_ADDRESS is prefix address, rather than local interface address. * It makes no difference for normally configured broadcast interfaces, * but for point-to-point IFA_ADDRESS is DESTINATION address, * local address is supplied in IFA_LOCAL attribute. * * IFA_FLAGS is a u32 attribute that extends the u8 field ifa_flags. * If present, the value from struct ifaddrmsg will be ignored. */ enum { IFA_UNSPEC, IFA_ADDRESS, IFA_LOCAL, IFA_LABEL, IFA_BROADCAST, IFA_ANYCAST, IFA_CACHEINFO, IFA_MULTICAST, IFA_FLAGS, IFA_RT_PRIORITY, /* u32, priority/metric for prefix route */ __IFA_MAX, }; #define IFA_MAX (__IFA_MAX - 1) /* ifa_flags */ #define IFA_F_SECONDARY 0x01 #define IFA_F_TEMPORARY IFA_F_SECONDARY #define IFA_F_NODAD 0x02 #define IFA_F_OPTIMISTIC 0x04 #define IFA_F_DADFAILED 0x08 #define IFA_F_HOMEADDRESS 0x10 #define IFA_F_DEPRECATED 0x20 #define IFA_F_TENTATIVE 0x40 #define IFA_F_PERMANENT 0x80 #define IFA_F_MANAGETEMPADDR 0x100 #define IFA_F_NOPREFIXROUTE 0x200 #define IFA_F_MCAUTOJOIN 0x400 #define IFA_F_STABLE_PRIVACY 0x800 struct ifa_cacheinfo { __u32 ifa_prefered; __u32 ifa_valid; __u32 cstamp; /* created timestamp, hundredths of seconds */ __u32 tstamp; /* updated timestamp, hundredths of seconds */ }; /* backwards compatibility for userspace */ #define IFA_RTA(r) ((struct rtattr*)(((char*)(r)) + NLMSG_ALIGN(sizeof(struct ifaddrmsg)))) #define IFA_PAYLOAD(n) NLMSG_PAYLOAD(n,sizeof(struct ifaddrmsg)) #endif frr-7.2.1/include/linux/if_bridge.h0000644000000000000000000001476513610377563014076 00000000000000/* * Linux ethernet bridge * * Authors: * Lennert Buytenhek * * This program is free software; you can redistribute it and/or * modify it under the terms of the GNU General Public License * as published by the Free Software Foundation; either version * 2 of the License, or (at your option) any later version. */ #ifndef _LINUX_IF_BRIDGE_H #define _LINUX_IF_BRIDGE_H #include #include #include #define SYSFS_BRIDGE_ATTR "bridge" #define SYSFS_BRIDGE_FDB "brforward" #define SYSFS_BRIDGE_PORT_SUBDIR "brif" #define SYSFS_BRIDGE_PORT_ATTR "brport" #define SYSFS_BRIDGE_PORT_LINK "bridge" #define BRCTL_VERSION 1 #define BRCTL_GET_VERSION 0 #define BRCTL_GET_BRIDGES 1 #define BRCTL_ADD_BRIDGE 2 #define BRCTL_DEL_BRIDGE 3 #define BRCTL_ADD_IF 4 #define BRCTL_DEL_IF 5 #define BRCTL_GET_BRIDGE_INFO 6 #define BRCTL_GET_PORT_LIST 7 #define BRCTL_SET_BRIDGE_FORWARD_DELAY 8 #define BRCTL_SET_BRIDGE_HELLO_TIME 9 #define BRCTL_SET_BRIDGE_MAX_AGE 10 #define BRCTL_SET_AGEING_TIME 11 #define BRCTL_SET_GC_INTERVAL 12 #define BRCTL_GET_PORT_INFO 13 #define BRCTL_SET_BRIDGE_STP_STATE 14 #define BRCTL_SET_BRIDGE_PRIORITY 15 #define BRCTL_SET_PORT_PRIORITY 16 #define BRCTL_SET_PATH_COST 17 #define BRCTL_GET_FDB_ENTRIES 18 #define BR_STATE_DISABLED 0 #define BR_STATE_LISTENING 1 #define BR_STATE_LEARNING 2 #define BR_STATE_FORWARDING 3 #define BR_STATE_BLOCKING 4 struct __bridge_info { __u64 designated_root; __u64 bridge_id; __u32 root_path_cost; __u32 max_age; __u32 hello_time; __u32 forward_delay; __u32 bridge_max_age; __u32 bridge_hello_time; __u32 bridge_forward_delay; __u8 topology_change; __u8 topology_change_detected; __u8 root_port; __u8 stp_enabled; __u32 ageing_time; __u32 gc_interval; __u32 hello_timer_value; __u32 tcn_timer_value; __u32 topology_change_timer_value; __u32 gc_timer_value; }; struct __port_info { __u64 designated_root; __u64 designated_bridge; __u16 port_id; __u16 designated_port; __u32 path_cost; __u32 designated_cost; __u8 state; __u8 top_change_ack; __u8 config_pending; __u8 unused0; __u32 message_age_timer_value; __u32 forward_delay_timer_value; __u32 hold_timer_value; }; struct __fdb_entry { __u8 mac_addr[ETH_ALEN]; __u8 port_no; __u8 is_local; __u32 ageing_timer_value; __u8 port_hi; __u8 pad0; __u16 unused; }; /* Bridge Flags */ #define BRIDGE_FLAGS_MASTER 1 /* Bridge command to/from master */ #define BRIDGE_FLAGS_SELF 2 /* Bridge command to/from lowerdev */ #define BRIDGE_MODE_VEB 0 /* Default loopback mode */ #define BRIDGE_MODE_VEPA 1 /* 802.1Qbg defined VEPA mode */ #define BRIDGE_MODE_UNDEF 0xFFFF /* mode undefined */ /* Bridge management nested attributes * [IFLA_AF_SPEC] = { * [IFLA_BRIDGE_FLAGS] * [IFLA_BRIDGE_MODE] * [IFLA_BRIDGE_VLAN_INFO] * } */ enum { IFLA_BRIDGE_FLAGS, IFLA_BRIDGE_MODE, IFLA_BRIDGE_VLAN_INFO, IFLA_BRIDGE_VLAN_TUNNEL_INFO, __IFLA_BRIDGE_MAX, }; #define IFLA_BRIDGE_MAX (__IFLA_BRIDGE_MAX - 1) #define BRIDGE_VLAN_INFO_MASTER (1<<0) /* Operate on Bridge device as well */ #define BRIDGE_VLAN_INFO_PVID (1<<1) /* VLAN is PVID, ingress untagged */ #define BRIDGE_VLAN_INFO_UNTAGGED (1<<2) /* VLAN egresses untagged */ #define BRIDGE_VLAN_INFO_RANGE_BEGIN (1<<3) /* VLAN is start of vlan range */ #define BRIDGE_VLAN_INFO_RANGE_END (1<<4) /* VLAN is end of vlan range */ #define BRIDGE_VLAN_INFO_BRENTRY (1<<5) /* Global bridge VLAN entry */ struct bridge_vlan_info { __u16 flags; __u16 vid; }; enum { IFLA_BRIDGE_VLAN_TUNNEL_UNSPEC, IFLA_BRIDGE_VLAN_TUNNEL_ID, IFLA_BRIDGE_VLAN_TUNNEL_VID, IFLA_BRIDGE_VLAN_TUNNEL_FLAGS, __IFLA_BRIDGE_VLAN_TUNNEL_MAX, }; #define IFLA_BRIDGE_VLAN_TUNNEL_MAX (__IFLA_BRIDGE_VLAN_TUNNEL_MAX - 1) struct bridge_vlan_xstats { __u64 rx_bytes; __u64 rx_packets; __u64 tx_bytes; __u64 tx_packets; __u16 vid; __u16 flags; __u32 pad2; }; /* Bridge multicast database attributes * [MDBA_MDB] = { * [MDBA_MDB_ENTRY] = { * [MDBA_MDB_ENTRY_INFO] { * struct br_mdb_entry * [MDBA_MDB_EATTR attributes] * } * } * } * [MDBA_ROUTER] = { * [MDBA_ROUTER_PORT] = { * u32 ifindex * [MDBA_ROUTER_PATTR attributes] * } * } */ enum { MDBA_UNSPEC, MDBA_MDB, MDBA_ROUTER, __MDBA_MAX, }; #define MDBA_MAX (__MDBA_MAX - 1) enum { MDBA_MDB_UNSPEC, MDBA_MDB_ENTRY, __MDBA_MDB_MAX, }; #define MDBA_MDB_MAX (__MDBA_MDB_MAX - 1) enum { MDBA_MDB_ENTRY_UNSPEC, MDBA_MDB_ENTRY_INFO, __MDBA_MDB_ENTRY_MAX, }; #define MDBA_MDB_ENTRY_MAX (__MDBA_MDB_ENTRY_MAX - 1) /* per mdb entry additional attributes */ enum { MDBA_MDB_EATTR_UNSPEC, MDBA_MDB_EATTR_TIMER, __MDBA_MDB_EATTR_MAX }; #define MDBA_MDB_EATTR_MAX (__MDBA_MDB_EATTR_MAX - 1) /* multicast router types */ enum { MDB_RTR_TYPE_DISABLED, MDB_RTR_TYPE_TEMP_QUERY, MDB_RTR_TYPE_PERM, MDB_RTR_TYPE_TEMP }; enum { MDBA_ROUTER_UNSPEC, MDBA_ROUTER_PORT, __MDBA_ROUTER_MAX, }; #define MDBA_ROUTER_MAX (__MDBA_ROUTER_MAX - 1) /* router port attributes */ enum { MDBA_ROUTER_PATTR_UNSPEC, MDBA_ROUTER_PATTR_TIMER, MDBA_ROUTER_PATTR_TYPE, __MDBA_ROUTER_PATTR_MAX }; #define MDBA_ROUTER_PATTR_MAX (__MDBA_ROUTER_PATTR_MAX - 1) struct br_port_msg { __u8 family; __u32 ifindex; }; struct br_mdb_entry { __u32 ifindex; #define MDB_TEMPORARY 0 #define MDB_PERMANENT 1 __u8 state; #define MDB_FLAGS_OFFLOAD (1 << 0) __u8 flags; __u16 vid; struct { union { __be32 ip4; struct in6_addr ip6; } u; __be16 proto; } addr; }; enum { MDBA_SET_ENTRY_UNSPEC, MDBA_SET_ENTRY, __MDBA_SET_ENTRY_MAX, }; #define MDBA_SET_ENTRY_MAX (__MDBA_SET_ENTRY_MAX - 1) /* Embedded inside LINK_XSTATS_TYPE_BRIDGE */ enum { BRIDGE_XSTATS_UNSPEC, BRIDGE_XSTATS_VLAN, BRIDGE_XSTATS_MCAST, BRIDGE_XSTATS_PAD, __BRIDGE_XSTATS_MAX }; #define BRIDGE_XSTATS_MAX (__BRIDGE_XSTATS_MAX - 1) enum { BR_MCAST_DIR_RX, BR_MCAST_DIR_TX, BR_MCAST_DIR_SIZE }; /* IGMP/MLD statistics */ struct br_mcast_stats { __u64 igmp_v1queries[BR_MCAST_DIR_SIZE]; __u64 igmp_v2queries[BR_MCAST_DIR_SIZE]; __u64 igmp_v3queries[BR_MCAST_DIR_SIZE]; __u64 igmp_leaves[BR_MCAST_DIR_SIZE]; __u64 igmp_v1reports[BR_MCAST_DIR_SIZE]; __u64 igmp_v2reports[BR_MCAST_DIR_SIZE]; __u64 igmp_v3reports[BR_MCAST_DIR_SIZE]; __u64 igmp_parse_errors; __u64 mld_v1queries[BR_MCAST_DIR_SIZE]; __u64 mld_v2queries[BR_MCAST_DIR_SIZE]; __u64 mld_leaves[BR_MCAST_DIR_SIZE]; __u64 mld_v1reports[BR_MCAST_DIR_SIZE]; __u64 mld_v2reports[BR_MCAST_DIR_SIZE]; __u64 mld_parse_errors; __u64 mcast_bytes[BR_MCAST_DIR_SIZE]; __u64 mcast_packets[BR_MCAST_DIR_SIZE]; }; #endif /* _LINUX_IF_BRIDGE_H */ frr-7.2.1/include/linux/if_link.h0000644000000000000000000005216613610377563013574 00000000000000#ifndef _LINUX_IF_LINK_H #define _LINUX_IF_LINK_H #include #include /* This struct should be in sync with struct rtnl_link_stats64 */ struct rtnl_link_stats { __u32 rx_packets; /* total packets received */ __u32 tx_packets; /* total packets transmitted */ __u32 rx_bytes; /* total bytes received */ __u32 tx_bytes; /* total bytes transmitted */ __u32 rx_errors; /* bad packets received */ __u32 tx_errors; /* packet transmit problems */ __u32 rx_dropped; /* no space in linux buffers */ __u32 tx_dropped; /* no space available in linux */ __u32 multicast; /* multicast packets received */ __u32 collisions; /* detailed rx_errors: */ __u32 rx_length_errors; __u32 rx_over_errors; /* receiver ring buff overflow */ __u32 rx_crc_errors; /* recved pkt with crc error */ __u32 rx_frame_errors; /* recv'd frame alignment error */ __u32 rx_fifo_errors; /* recv'r fifo overrun */ __u32 rx_missed_errors; /* receiver missed packet */ /* detailed tx_errors */ __u32 tx_aborted_errors; __u32 tx_carrier_errors; __u32 tx_fifo_errors; __u32 tx_heartbeat_errors; __u32 tx_window_errors; /* for cslip etc */ __u32 rx_compressed; __u32 tx_compressed; __u32 rx_nohandler; /* dropped, no handler found */ }; /* The main device statistics structure */ struct rtnl_link_stats64 { __u64 rx_packets; /* total packets received */ __u64 tx_packets; /* total packets transmitted */ __u64 rx_bytes; /* total bytes received */ __u64 tx_bytes; /* total bytes transmitted */ __u64 rx_errors; /* bad packets received */ __u64 tx_errors; /* packet transmit problems */ __u64 rx_dropped; /* no space in linux buffers */ __u64 tx_dropped; /* no space available in linux */ __u64 multicast; /* multicast packets received */ __u64 collisions; /* detailed rx_errors: */ __u64 rx_length_errors; __u64 rx_over_errors; /* receiver ring buff overflow */ __u64 rx_crc_errors; /* recved pkt with crc error */ __u64 rx_frame_errors; /* recv'd frame alignment error */ __u64 rx_fifo_errors; /* recv'r fifo overrun */ __u64 rx_missed_errors; /* receiver missed packet */ /* detailed tx_errors */ __u64 tx_aborted_errors; __u64 tx_carrier_errors; __u64 tx_fifo_errors; __u64 tx_heartbeat_errors; __u64 tx_window_errors; /* for cslip etc */ __u64 rx_compressed; __u64 tx_compressed; __u64 rx_nohandler; /* dropped, no handler found */ }; /* The struct should be in sync with struct ifmap */ struct rtnl_link_ifmap { __u64 mem_start; __u64 mem_end; __u64 base_addr; __u16 irq; __u8 dma; __u8 port; }; /* * IFLA_AF_SPEC * Contains nested attributes for address family specific attributes. * Each address family may create a attribute with the address family * number as type and create its own attribute structure in it. * * Example: * [IFLA_AF_SPEC] = { * [AF_INET] = { * [IFLA_INET_CONF] = ..., * }, * [AF_INET6] = { * [IFLA_INET6_FLAGS] = ..., * [IFLA_INET6_CONF] = ..., * } * } */ enum { IFLA_UNSPEC, IFLA_ADDRESS, IFLA_BROADCAST, IFLA_IFNAME, IFLA_MTU, IFLA_LINK, IFLA_QDISC, IFLA_STATS, IFLA_COST, #define IFLA_COST IFLA_COST IFLA_PRIORITY, #define IFLA_PRIORITY IFLA_PRIORITY IFLA_MASTER, #define IFLA_MASTER IFLA_MASTER IFLA_WIRELESS, /* Wireless Extension event - see wireless.h */ #define IFLA_WIRELESS IFLA_WIRELESS IFLA_PROTINFO, /* Protocol specific information for a link */ #define IFLA_PROTINFO IFLA_PROTINFO IFLA_TXQLEN, #define IFLA_TXQLEN IFLA_TXQLEN IFLA_MAP, #define IFLA_MAP IFLA_MAP IFLA_WEIGHT, #define IFLA_WEIGHT IFLA_WEIGHT IFLA_OPERSTATE, IFLA_LINKMODE, IFLA_LINKINFO, #define IFLA_LINKINFO IFLA_LINKINFO IFLA_NET_NS_PID, IFLA_IFALIAS, IFLA_NUM_VF, /* Number of VFs if device is SR-IOV PF */ IFLA_VFINFO_LIST, IFLA_STATS64, IFLA_VF_PORTS, IFLA_PORT_SELF, IFLA_AF_SPEC, IFLA_GROUP, /* Group the device belongs to */ IFLA_NET_NS_FD, IFLA_EXT_MASK, /* Extended info mask, VFs, etc */ IFLA_PROMISCUITY, /* Promiscuity count: > 0 means acts PROMISC */ #define IFLA_PROMISCUITY IFLA_PROMISCUITY IFLA_NUM_TX_QUEUES, IFLA_NUM_RX_QUEUES, IFLA_CARRIER, IFLA_PHYS_PORT_ID, IFLA_CARRIER_CHANGES, IFLA_PHYS_SWITCH_ID, IFLA_LINK_NETNSID, IFLA_PHYS_PORT_NAME, IFLA_PROTO_DOWN, IFLA_GSO_MAX_SEGS, IFLA_GSO_MAX_SIZE, IFLA_PAD, IFLA_XDP, IFLA_EVENT, __IFLA_MAX }; #define IFLA_MAX (__IFLA_MAX - 1) /* backwards compatibility for userspace */ #define IFLA_RTA(r) ((struct rtattr*)(((char*)(r)) + NLMSG_ALIGN(sizeof(struct ifinfomsg)))) #define IFLA_PAYLOAD(n) NLMSG_PAYLOAD(n,sizeof(struct ifinfomsg)) enum { IFLA_INET_UNSPEC, IFLA_INET_CONF, __IFLA_INET_MAX, }; #define IFLA_INET_MAX (__IFLA_INET_MAX - 1) /* ifi_flags. IFF_* flags. The only change is: IFF_LOOPBACK, IFF_BROADCAST and IFF_POINTOPOINT are more not changeable by user. They describe link media characteristics and set by device driver. Comments: - Combination IFF_BROADCAST|IFF_POINTOPOINT is invalid - If neither of these three flags are set; the interface is NBMA. - IFF_MULTICAST does not mean anything special: multicasts can be used on all not-NBMA links. IFF_MULTICAST means that this media uses special encapsulation for multicast frames. Apparently, all IFF_POINTOPOINT and IFF_BROADCAST devices are able to use multicasts too. */ /* IFLA_LINK. For usual devices it is equal ifi_index. If it is a "virtual interface" (f.e. tunnel), ifi_link can point to real physical interface (f.e. for bandwidth calculations), or maybe 0, what means, that real media is unknown (usual for IPIP tunnels, when route to endpoint is allowed to change) */ /* Subtype attributes for IFLA_PROTINFO */ enum { IFLA_INET6_UNSPEC, IFLA_INET6_FLAGS, /* link flags */ IFLA_INET6_CONF, /* sysctl parameters */ IFLA_INET6_STATS, /* statistics */ IFLA_INET6_MCAST, /* MC things. What of them? */ IFLA_INET6_CACHEINFO, /* time values and max reasm size */ IFLA_INET6_ICMP6STATS, /* statistics (icmpv6) */ IFLA_INET6_TOKEN, /* device token */ IFLA_INET6_ADDR_GEN_MODE, /* implicit address generator mode */ __IFLA_INET6_MAX }; #define IFLA_INET6_MAX (__IFLA_INET6_MAX - 1) enum in6_addr_gen_mode { IN6_ADDR_GEN_MODE_EUI64, IN6_ADDR_GEN_MODE_NONE, IN6_ADDR_GEN_MODE_STABLE_PRIVACY, IN6_ADDR_GEN_MODE_RANDOM, }; /* Bridge section */ enum { IFLA_BR_UNSPEC, IFLA_BR_FORWARD_DELAY, IFLA_BR_HELLO_TIME, IFLA_BR_MAX_AGE, IFLA_BR_AGEING_TIME, IFLA_BR_STP_STATE, IFLA_BR_PRIORITY, IFLA_BR_VLAN_FILTERING, IFLA_BR_VLAN_PROTOCOL, IFLA_BR_GROUP_FWD_MASK, IFLA_BR_ROOT_ID, IFLA_BR_BRIDGE_ID, IFLA_BR_ROOT_PORT, IFLA_BR_ROOT_PATH_COST, IFLA_BR_TOPOLOGY_CHANGE, IFLA_BR_TOPOLOGY_CHANGE_DETECTED, IFLA_BR_HELLO_TIMER, IFLA_BR_TCN_TIMER, IFLA_BR_TOPOLOGY_CHANGE_TIMER, IFLA_BR_GC_TIMER, IFLA_BR_GROUP_ADDR, IFLA_BR_FDB_FLUSH, IFLA_BR_MCAST_ROUTER, IFLA_BR_MCAST_SNOOPING, IFLA_BR_MCAST_QUERY_USE_IFADDR, IFLA_BR_MCAST_QUERIER, IFLA_BR_MCAST_HASH_ELASTICITY, IFLA_BR_MCAST_HASH_MAX, IFLA_BR_MCAST_LAST_MEMBER_CNT, IFLA_BR_MCAST_STARTUP_QUERY_CNT, IFLA_BR_MCAST_LAST_MEMBER_INTVL, IFLA_BR_MCAST_MEMBERSHIP_INTVL, IFLA_BR_MCAST_QUERIER_INTVL, IFLA_BR_MCAST_QUERY_INTVL, IFLA_BR_MCAST_QUERY_RESPONSE_INTVL, IFLA_BR_MCAST_STARTUP_QUERY_INTVL, IFLA_BR_NF_CALL_IPTABLES, IFLA_BR_NF_CALL_IP6TABLES, IFLA_BR_NF_CALL_ARPTABLES, IFLA_BR_VLAN_DEFAULT_PVID, IFLA_BR_PAD, IFLA_BR_VLAN_STATS_ENABLED, IFLA_BR_MCAST_STATS_ENABLED, IFLA_BR_MCAST_IGMP_VERSION, IFLA_BR_MCAST_MLD_VERSION, __IFLA_BR_MAX, }; #define IFLA_BR_MAX (__IFLA_BR_MAX - 1) struct ifla_bridge_id { __u8 prio[2]; __u8 addr[6]; /* ETH_ALEN */ }; enum { BRIDGE_MODE_UNSPEC, BRIDGE_MODE_HAIRPIN, }; enum { IFLA_BRPORT_UNSPEC, IFLA_BRPORT_STATE, /* Spanning tree state */ IFLA_BRPORT_PRIORITY, /* " priority */ IFLA_BRPORT_COST, /* " cost */ IFLA_BRPORT_MODE, /* mode (hairpin) */ IFLA_BRPORT_GUARD, /* bpdu guard */ IFLA_BRPORT_PROTECT, /* root port protection */ IFLA_BRPORT_FAST_LEAVE, /* multicast fast leave */ IFLA_BRPORT_LEARNING, /* mac learning */ IFLA_BRPORT_UNICAST_FLOOD, /* flood unicast traffic */ IFLA_BRPORT_PROXYARP, /* proxy ARP */ IFLA_BRPORT_LEARNING_SYNC, /* mac learning sync from device */ IFLA_BRPORT_PROXYARP_WIFI, /* proxy ARP for Wi-Fi */ IFLA_BRPORT_ROOT_ID, /* designated root */ IFLA_BRPORT_BRIDGE_ID, /* designated bridge */ IFLA_BRPORT_DESIGNATED_PORT, IFLA_BRPORT_DESIGNATED_COST, IFLA_BRPORT_ID, IFLA_BRPORT_NO, IFLA_BRPORT_TOPOLOGY_CHANGE_ACK, IFLA_BRPORT_CONFIG_PENDING, IFLA_BRPORT_MESSAGE_AGE_TIMER, IFLA_BRPORT_FORWARD_DELAY_TIMER, IFLA_BRPORT_HOLD_TIMER, IFLA_BRPORT_FLUSH, IFLA_BRPORT_MULTICAST_ROUTER, IFLA_BRPORT_PAD, IFLA_BRPORT_MCAST_FLOOD, IFLA_BRPORT_MCAST_TO_UCAST, IFLA_BRPORT_VLAN_TUNNEL, IFLA_BRPORT_BCAST_FLOOD, __IFLA_BRPORT_MAX }; #define IFLA_BRPORT_MAX (__IFLA_BRPORT_MAX - 1) struct ifla_cacheinfo { __u32 max_reasm_len; __u32 tstamp; /* ipv6InterfaceTable updated timestamp */ __u32 reachable_time; __u32 retrans_time; }; enum { IFLA_INFO_UNSPEC, IFLA_INFO_KIND, IFLA_INFO_DATA, IFLA_INFO_XSTATS, IFLA_INFO_SLAVE_KIND, IFLA_INFO_SLAVE_DATA, __IFLA_INFO_MAX, }; #define IFLA_INFO_MAX (__IFLA_INFO_MAX - 1) /* VLAN section */ enum { IFLA_VLAN_UNSPEC, IFLA_VLAN_ID, IFLA_VLAN_FLAGS, IFLA_VLAN_EGRESS_QOS, IFLA_VLAN_INGRESS_QOS, IFLA_VLAN_PROTOCOL, __IFLA_VLAN_MAX, }; #define IFLA_VLAN_MAX (__IFLA_VLAN_MAX - 1) struct ifla_vlan_flags { __u32 flags; __u32 mask; }; enum { IFLA_VLAN_QOS_UNSPEC, IFLA_VLAN_QOS_MAPPING, __IFLA_VLAN_QOS_MAX }; #define IFLA_VLAN_QOS_MAX (__IFLA_VLAN_QOS_MAX - 1) struct ifla_vlan_qos_mapping { __u32 from; __u32 to; }; /* MACVLAN section */ enum { IFLA_MACVLAN_UNSPEC, IFLA_MACVLAN_MODE, IFLA_MACVLAN_FLAGS, IFLA_MACVLAN_MACADDR_MODE, IFLA_MACVLAN_MACADDR, IFLA_MACVLAN_MACADDR_DATA, IFLA_MACVLAN_MACADDR_COUNT, __IFLA_MACVLAN_MAX, }; #define IFLA_MACVLAN_MAX (__IFLA_MACVLAN_MAX - 1) enum macvlan_mode { MACVLAN_MODE_PRIVATE = 1, /* don't talk to other macvlans */ MACVLAN_MODE_VEPA = 2, /* talk to other ports through ext bridge */ MACVLAN_MODE_BRIDGE = 4, /* talk to bridge ports directly */ MACVLAN_MODE_PASSTHRU = 8,/* take over the underlying device */ MACVLAN_MODE_SOURCE = 16,/* use source MAC address list to assign */ }; enum macvlan_macaddr_mode { MACVLAN_MACADDR_ADD, MACVLAN_MACADDR_DEL, MACVLAN_MACADDR_FLUSH, MACVLAN_MACADDR_SET, }; #define MACVLAN_FLAG_NOPROMISC 1 /* VRF section */ enum { IFLA_VRF_UNSPEC, IFLA_VRF_TABLE, __IFLA_VRF_MAX }; #define IFLA_VRF_MAX (__IFLA_VRF_MAX - 1) enum { IFLA_VRF_PORT_UNSPEC, IFLA_VRF_PORT_TABLE, __IFLA_VRF_PORT_MAX }; #define IFLA_VRF_PORT_MAX (__IFLA_VRF_PORT_MAX - 1) /* MACSEC section */ enum { IFLA_MACSEC_UNSPEC, IFLA_MACSEC_SCI, IFLA_MACSEC_PORT, IFLA_MACSEC_ICV_LEN, IFLA_MACSEC_CIPHER_SUITE, IFLA_MACSEC_WINDOW, IFLA_MACSEC_ENCODING_SA, IFLA_MACSEC_ENCRYPT, IFLA_MACSEC_PROTECT, IFLA_MACSEC_INC_SCI, IFLA_MACSEC_ES, IFLA_MACSEC_SCB, IFLA_MACSEC_REPLAY_PROTECT, IFLA_MACSEC_VALIDATION, IFLA_MACSEC_PAD, __IFLA_MACSEC_MAX, }; #define IFLA_MACSEC_MAX (__IFLA_MACSEC_MAX - 1) enum macsec_validation_type { MACSEC_VALIDATE_DISABLED = 0, MACSEC_VALIDATE_CHECK = 1, MACSEC_VALIDATE_STRICT = 2, __MACSEC_VALIDATE_END, MACSEC_VALIDATE_MAX = __MACSEC_VALIDATE_END - 1, }; /* IPVLAN section */ enum { IFLA_IPVLAN_UNSPEC, IFLA_IPVLAN_MODE, __IFLA_IPVLAN_MAX }; #define IFLA_IPVLAN_MAX (__IFLA_IPVLAN_MAX - 1) enum ipvlan_mode { IPVLAN_MODE_L2 = 0, IPVLAN_MODE_L3, IPVLAN_MODE_L3S, IPVLAN_MODE_MAX }; /* VXLAN section */ enum { IFLA_VXLAN_UNSPEC, IFLA_VXLAN_ID, IFLA_VXLAN_GROUP, /* group or remote address */ IFLA_VXLAN_LINK, IFLA_VXLAN_LOCAL, IFLA_VXLAN_TTL, IFLA_VXLAN_TOS, IFLA_VXLAN_LEARNING, IFLA_VXLAN_AGEING, IFLA_VXLAN_LIMIT, IFLA_VXLAN_PORT_RANGE, /* source port */ IFLA_VXLAN_PROXY, IFLA_VXLAN_RSC, IFLA_VXLAN_L2MISS, IFLA_VXLAN_L3MISS, IFLA_VXLAN_PORT, /* destination port */ IFLA_VXLAN_GROUP6, IFLA_VXLAN_LOCAL6, IFLA_VXLAN_UDP_CSUM, IFLA_VXLAN_UDP_ZERO_CSUM6_TX, IFLA_VXLAN_UDP_ZERO_CSUM6_RX, IFLA_VXLAN_REMCSUM_TX, IFLA_VXLAN_REMCSUM_RX, IFLA_VXLAN_GBP, IFLA_VXLAN_REMCSUM_NOPARTIAL, IFLA_VXLAN_COLLECT_METADATA, IFLA_VXLAN_LABEL, IFLA_VXLAN_GPE, __IFLA_VXLAN_MAX }; #define IFLA_VXLAN_MAX (__IFLA_VXLAN_MAX - 1) struct ifla_vxlan_port_range { __be16 low; __be16 high; }; /* GENEVE section */ enum { IFLA_GENEVE_UNSPEC, IFLA_GENEVE_ID, IFLA_GENEVE_REMOTE, IFLA_GENEVE_TTL, IFLA_GENEVE_TOS, IFLA_GENEVE_PORT, /* destination port */ IFLA_GENEVE_COLLECT_METADATA, IFLA_GENEVE_REMOTE6, IFLA_GENEVE_UDP_CSUM, IFLA_GENEVE_UDP_ZERO_CSUM6_TX, IFLA_GENEVE_UDP_ZERO_CSUM6_RX, IFLA_GENEVE_LABEL, __IFLA_GENEVE_MAX }; #define IFLA_GENEVE_MAX (__IFLA_GENEVE_MAX - 1) /* PPP section */ enum { IFLA_PPP_UNSPEC, IFLA_PPP_DEV_FD, __IFLA_PPP_MAX }; #define IFLA_PPP_MAX (__IFLA_PPP_MAX - 1) /* GTP section */ enum ifla_gtp_role { GTP_ROLE_GGSN = 0, GTP_ROLE_SGSN, }; enum { IFLA_GTP_UNSPEC, IFLA_GTP_FD0, IFLA_GTP_FD1, IFLA_GTP_PDP_HASHSIZE, IFLA_GTP_ROLE, __IFLA_GTP_MAX, }; #define IFLA_GTP_MAX (__IFLA_GTP_MAX - 1) /* Bonding section */ enum { IFLA_BOND_UNSPEC, IFLA_BOND_MODE, IFLA_BOND_ACTIVE_SLAVE, IFLA_BOND_MIIMON, IFLA_BOND_UPDELAY, IFLA_BOND_DOWNDELAY, IFLA_BOND_USE_CARRIER, IFLA_BOND_ARP_INTERVAL, IFLA_BOND_ARP_IP_TARGET, IFLA_BOND_ARP_VALIDATE, IFLA_BOND_ARP_ALL_TARGETS, IFLA_BOND_PRIMARY, IFLA_BOND_PRIMARY_RESELECT, IFLA_BOND_FAIL_OVER_MAC, IFLA_BOND_XMIT_HASH_POLICY, IFLA_BOND_RESEND_IGMP, IFLA_BOND_NUM_PEER_NOTIF, IFLA_BOND_ALL_SLAVES_ACTIVE, IFLA_BOND_MIN_LINKS, IFLA_BOND_LP_INTERVAL, IFLA_BOND_PACKETS_PER_SLAVE, IFLA_BOND_AD_LACP_RATE, IFLA_BOND_AD_SELECT, IFLA_BOND_AD_INFO, IFLA_BOND_AD_ACTOR_SYS_PRIO, IFLA_BOND_AD_USER_PORT_KEY, IFLA_BOND_AD_ACTOR_SYSTEM, IFLA_BOND_TLB_DYNAMIC_LB, __IFLA_BOND_MAX, }; #define IFLA_BOND_MAX (__IFLA_BOND_MAX - 1) enum { IFLA_BOND_AD_INFO_UNSPEC, IFLA_BOND_AD_INFO_AGGREGATOR, IFLA_BOND_AD_INFO_NUM_PORTS, IFLA_BOND_AD_INFO_ACTOR_KEY, IFLA_BOND_AD_INFO_PARTNER_KEY, IFLA_BOND_AD_INFO_PARTNER_MAC, __IFLA_BOND_AD_INFO_MAX, }; #define IFLA_BOND_AD_INFO_MAX (__IFLA_BOND_AD_INFO_MAX - 1) enum { IFLA_BOND_SLAVE_UNSPEC, IFLA_BOND_SLAVE_STATE, IFLA_BOND_SLAVE_MII_STATUS, IFLA_BOND_SLAVE_LINK_FAILURE_COUNT, IFLA_BOND_SLAVE_PERM_HWADDR, IFLA_BOND_SLAVE_QUEUE_ID, IFLA_BOND_SLAVE_AD_AGGREGATOR_ID, IFLA_BOND_SLAVE_AD_ACTOR_OPER_PORT_STATE, IFLA_BOND_SLAVE_AD_PARTNER_OPER_PORT_STATE, __IFLA_BOND_SLAVE_MAX, }; #define IFLA_BOND_SLAVE_MAX (__IFLA_BOND_SLAVE_MAX - 1) /* SR-IOV virtual function management section */ enum { IFLA_VF_INFO_UNSPEC, IFLA_VF_INFO, __IFLA_VF_INFO_MAX, }; #define IFLA_VF_INFO_MAX (__IFLA_VF_INFO_MAX - 1) enum { IFLA_VF_UNSPEC, IFLA_VF_MAC, /* Hardware queue specific attributes */ IFLA_VF_VLAN, /* VLAN ID and QoS */ IFLA_VF_TX_RATE, /* Max TX Bandwidth Allocation */ IFLA_VF_SPOOFCHK, /* Spoof Checking on/off switch */ IFLA_VF_LINK_STATE, /* link state enable/disable/auto switch */ IFLA_VF_RATE, /* Min and Max TX Bandwidth Allocation */ IFLA_VF_RSS_QUERY_EN, /* RSS Redirection Table and Hash Key query * on/off switch */ IFLA_VF_STATS, /* network device statistics */ IFLA_VF_TRUST, /* Trust VF */ IFLA_VF_IB_NODE_GUID, /* VF Infiniband node GUID */ IFLA_VF_IB_PORT_GUID, /* VF Infiniband port GUID */ IFLA_VF_VLAN_LIST, /* nested list of vlans, option for QinQ */ __IFLA_VF_MAX, }; #define IFLA_VF_MAX (__IFLA_VF_MAX - 1) struct ifla_vf_mac { __u32 vf; __u8 mac[32]; /* MAX_ADDR_LEN */ }; struct ifla_vf_vlan { __u32 vf; __u32 vlan; /* 0 - 4095, 0 disables VLAN filter */ __u32 qos; }; enum { IFLA_VF_VLAN_INFO_UNSPEC, IFLA_VF_VLAN_INFO, /* VLAN ID, QoS and VLAN protocol */ __IFLA_VF_VLAN_INFO_MAX, }; #define IFLA_VF_VLAN_INFO_MAX (__IFLA_VF_VLAN_INFO_MAX - 1) #define MAX_VLAN_LIST_LEN 1 struct ifla_vf_vlan_info { __u32 vf; __u32 vlan; /* 0 - 4095, 0 disables VLAN filter */ __u32 qos; __be16 vlan_proto; /* VLAN protocol either 802.1Q or 802.1ad */ }; struct ifla_vf_tx_rate { __u32 vf; __u32 rate; /* Max TX bandwidth in Mbps, 0 disables throttling */ }; struct ifla_vf_rate { __u32 vf; __u32 min_tx_rate; /* Min Bandwidth in Mbps */ __u32 max_tx_rate; /* Max Bandwidth in Mbps */ }; struct ifla_vf_spoofchk { __u32 vf; __u32 setting; }; struct ifla_vf_guid { __u32 vf; __u64 guid; }; enum { IFLA_VF_LINK_STATE_AUTO, /* link state of the uplink */ IFLA_VF_LINK_STATE_ENABLE, /* link always up */ IFLA_VF_LINK_STATE_DISABLE, /* link always down */ __IFLA_VF_LINK_STATE_MAX, }; struct ifla_vf_link_state { __u32 vf; __u32 link_state; }; struct ifla_vf_rss_query_en { __u32 vf; __u32 setting; }; enum { IFLA_VF_STATS_RX_PACKETS, IFLA_VF_STATS_TX_PACKETS, IFLA_VF_STATS_RX_BYTES, IFLA_VF_STATS_TX_BYTES, IFLA_VF_STATS_BROADCAST, IFLA_VF_STATS_MULTICAST, IFLA_VF_STATS_PAD, __IFLA_VF_STATS_MAX, }; #define IFLA_VF_STATS_MAX (__IFLA_VF_STATS_MAX - 1) struct ifla_vf_trust { __u32 vf; __u32 setting; }; /* VF ports management section * * Nested layout of set/get msg is: * * [IFLA_NUM_VF] * [IFLA_VF_PORTS] * [IFLA_VF_PORT] * [IFLA_PORT_*], ... * [IFLA_VF_PORT] * [IFLA_PORT_*], ... * ... * [IFLA_PORT_SELF] * [IFLA_PORT_*], ... */ enum { IFLA_VF_PORT_UNSPEC, IFLA_VF_PORT, /* nest */ __IFLA_VF_PORT_MAX, }; #define IFLA_VF_PORT_MAX (__IFLA_VF_PORT_MAX - 1) enum { IFLA_PORT_UNSPEC, IFLA_PORT_VF, /* __u32 */ IFLA_PORT_PROFILE, /* string */ IFLA_PORT_VSI_TYPE, /* 802.1Qbg (pre-)standard VDP */ IFLA_PORT_INSTANCE_UUID, /* binary UUID */ IFLA_PORT_HOST_UUID, /* binary UUID */ IFLA_PORT_REQUEST, /* __u8 */ IFLA_PORT_RESPONSE, /* __u16, output only */ __IFLA_PORT_MAX, }; #define IFLA_PORT_MAX (__IFLA_PORT_MAX - 1) #define PORT_PROFILE_MAX 40 #define PORT_UUID_MAX 16 #define PORT_SELF_VF -1 enum { PORT_REQUEST_PREASSOCIATE = 0, PORT_REQUEST_PREASSOCIATE_RR, PORT_REQUEST_ASSOCIATE, PORT_REQUEST_DISASSOCIATE, }; enum { PORT_VDP_RESPONSE_SUCCESS = 0, PORT_VDP_RESPONSE_INVALID_FORMAT, PORT_VDP_RESPONSE_INSUFFICIENT_RESOURCES, PORT_VDP_RESPONSE_UNUSED_VTID, PORT_VDP_RESPONSE_VTID_VIOLATION, PORT_VDP_RESPONSE_VTID_VERSION_VIOALTION, PORT_VDP_RESPONSE_OUT_OF_SYNC, /* 0x08-0xFF reserved for future VDP use */ PORT_PROFILE_RESPONSE_SUCCESS = 0x100, PORT_PROFILE_RESPONSE_INPROGRESS, PORT_PROFILE_RESPONSE_INVALID, PORT_PROFILE_RESPONSE_BADSTATE, PORT_PROFILE_RESPONSE_INSUFFICIENT_RESOURCES, PORT_PROFILE_RESPONSE_ERROR, }; struct ifla_port_vsi { __u8 vsi_mgr_id; __u8 vsi_type_id[3]; __u8 vsi_type_version; __u8 pad[3]; }; /* IPoIB section */ enum { IFLA_IPOIB_UNSPEC, IFLA_IPOIB_PKEY, IFLA_IPOIB_MODE, IFLA_IPOIB_UMCAST, __IFLA_IPOIB_MAX }; enum { IPOIB_MODE_DATAGRAM = 0, /* using unreliable datagram QPs */ IPOIB_MODE_CONNECTED = 1, /* using connected QPs */ }; #define IFLA_IPOIB_MAX (__IFLA_IPOIB_MAX - 1) /* HSR section */ enum { IFLA_HSR_UNSPEC, IFLA_HSR_SLAVE1, IFLA_HSR_SLAVE2, IFLA_HSR_MULTICAST_SPEC, /* Last byte of supervision addr */ IFLA_HSR_SUPERVISION_ADDR, /* Supervision frame multicast addr */ IFLA_HSR_SEQ_NR, IFLA_HSR_VERSION, /* HSR version */ __IFLA_HSR_MAX, }; #define IFLA_HSR_MAX (__IFLA_HSR_MAX - 1) /* STATS section */ struct if_stats_msg { __u8 family; __u8 pad1; __u16 pad2; __u32 ifindex; __u32 filter_mask; }; /* A stats attribute can be netdev specific or a global stat. * For netdev stats, lets use the prefix IFLA_STATS_LINK_* */ enum { IFLA_STATS_UNSPEC, /* also used as 64bit pad attribute */ IFLA_STATS_LINK_64, IFLA_STATS_LINK_XSTATS, IFLA_STATS_LINK_XSTATS_SLAVE, IFLA_STATS_LINK_OFFLOAD_XSTATS, IFLA_STATS_AF_SPEC, __IFLA_STATS_MAX, }; #define IFLA_STATS_MAX (__IFLA_STATS_MAX - 1) #define IFLA_STATS_FILTER_BIT(ATTR) (1 << (ATTR - 1)) /* These are embedded into IFLA_STATS_LINK_XSTATS: * [IFLA_STATS_LINK_XSTATS] * -> [LINK_XSTATS_TYPE_xxx] * -> [rtnl link type specific attributes] */ enum { LINK_XSTATS_TYPE_UNSPEC, LINK_XSTATS_TYPE_BRIDGE, __LINK_XSTATS_TYPE_MAX }; #define LINK_XSTATS_TYPE_MAX (__LINK_XSTATS_TYPE_MAX - 1) /* These are stats embedded into IFLA_STATS_LINK_OFFLOAD_XSTATS */ enum { IFLA_OFFLOAD_XSTATS_UNSPEC, IFLA_OFFLOAD_XSTATS_CPU_HIT, /* struct rtnl_link_stats64 */ __IFLA_OFFLOAD_XSTATS_MAX }; #define IFLA_OFFLOAD_XSTATS_MAX (__IFLA_OFFLOAD_XSTATS_MAX - 1) /* XDP section */ #define XDP_FLAGS_UPDATE_IF_NOEXIST (1U << 0) #define XDP_FLAGS_SKB_MODE (1U << 1) #define XDP_FLAGS_DRV_MODE (1U << 2) #define XDP_FLAGS_HW_MODE (1U << 3) #define XDP_FLAGS_MODES (XDP_FLAGS_SKB_MODE | \ XDP_FLAGS_DRV_MODE | \ XDP_FLAGS_HW_MODE) #define XDP_FLAGS_MASK (XDP_FLAGS_UPDATE_IF_NOEXIST | \ XDP_FLAGS_MODES) /* These are stored into IFLA_XDP_ATTACHED on dump. */ enum { XDP_ATTACHED_NONE = 0, XDP_ATTACHED_DRV, XDP_ATTACHED_SKB, XDP_ATTACHED_HW, }; enum { IFLA_XDP_UNSPEC, IFLA_XDP_FD, IFLA_XDP_ATTACHED, IFLA_XDP_FLAGS, IFLA_XDP_PROG_ID, __IFLA_XDP_MAX, }; #define IFLA_XDP_MAX (__IFLA_XDP_MAX - 1) enum { IFLA_EVENT_NONE, IFLA_EVENT_REBOOT, /* internal reset / reboot */ IFLA_EVENT_FEATURES, /* change in offload features */ IFLA_EVENT_BONDING_FAILOVER, /* change in active slave */ IFLA_EVENT_NOTIFY_PEERS, /* re-sent grat. arp/ndisc */ IFLA_EVENT_IGMP_RESEND, /* re-sent IGMP JOIN */ IFLA_EVENT_BONDING_OPTIONS, /* change in bonding options */ }; #endif /* _LINUX_IF_LINK_H */ frr-7.2.1/include/linux/lwtunnel.h0000644000000000000000000000227213610377563014022 00000000000000#ifndef _LWTUNNEL_H_ #define _LWTUNNEL_H_ #include enum lwtunnel_encap_types { LWTUNNEL_ENCAP_NONE, LWTUNNEL_ENCAP_MPLS, LWTUNNEL_ENCAP_IP, LWTUNNEL_ENCAP_ILA, LWTUNNEL_ENCAP_IP6, LWTUNNEL_ENCAP_SEG6, LWTUNNEL_ENCAP_BPF, LWTUNNEL_ENCAP_SEG6_LOCAL, __LWTUNNEL_ENCAP_MAX, }; #define LWTUNNEL_ENCAP_MAX (__LWTUNNEL_ENCAP_MAX - 1) enum lwtunnel_ip_t { LWTUNNEL_IP_UNSPEC, LWTUNNEL_IP_ID, LWTUNNEL_IP_DST, LWTUNNEL_IP_SRC, LWTUNNEL_IP_TTL, LWTUNNEL_IP_TOS, LWTUNNEL_IP_FLAGS, LWTUNNEL_IP_PAD, __LWTUNNEL_IP_MAX, }; #define LWTUNNEL_IP_MAX (__LWTUNNEL_IP_MAX - 1) enum lwtunnel_ip6_t { LWTUNNEL_IP6_UNSPEC, LWTUNNEL_IP6_ID, LWTUNNEL_IP6_DST, LWTUNNEL_IP6_SRC, LWTUNNEL_IP6_HOPLIMIT, LWTUNNEL_IP6_TC, LWTUNNEL_IP6_FLAGS, LWTUNNEL_IP6_PAD, __LWTUNNEL_IP6_MAX, }; #define LWTUNNEL_IP6_MAX (__LWTUNNEL_IP6_MAX - 1) enum { LWT_BPF_PROG_UNSPEC, LWT_BPF_PROG_FD, LWT_BPF_PROG_NAME, __LWT_BPF_PROG_MAX, }; #define LWT_BPF_PROG_MAX (__LWT_BPF_PROG_MAX - 1) enum { LWT_BPF_UNSPEC, LWT_BPF_IN, LWT_BPF_OUT, LWT_BPF_XMIT, LWT_BPF_XMIT_HEADROOM, __LWT_BPF_MAX, }; #define LWT_BPF_MAX (__LWT_BPF_MAX - 1) #define LWT_BPF_MAX_HEADROOM 256 #endif /* _LWTUNNEL_H_ */ frr-7.2.1/include/linux/mpls_iptunnel.h0000644000000000000000000000127113610377563015041 00000000000000/* * mpls tunnel api * * Authors: * Roopa Prabhu * * This program is free software; you can redistribute it and/or * modify it under the terms of the GNU General Public License * as published by the Free Software Foundation; either version * 2 of the License, or (at your option) any later version. */ #ifndef _LINUX_MPLS_IPTUNNEL_H #define _LINUX_MPLS_IPTUNNEL_H /* MPLS tunnel attributes * [RTA_ENCAP] = { * [MPLS_IPTUNNEL_DST] * [MPLS_IPTUNNEL_TTL] * } */ enum { MPLS_IPTUNNEL_UNSPEC, MPLS_IPTUNNEL_DST, MPLS_IPTUNNEL_TTL, __MPLS_IPTUNNEL_MAX, }; #define MPLS_IPTUNNEL_MAX (__MPLS_IPTUNNEL_MAX - 1) #endif /* _LINUX_MPLS_IPTUNNEL_H */ frr-7.2.1/include/linux/neighbour.h0000644000000000000000000001026513610377563014135 00000000000000#ifndef __LINUX_NEIGHBOUR_H #define __LINUX_NEIGHBOUR_H #include #include struct ndmsg { __u8 ndm_family; __u8 ndm_pad1; __u16 ndm_pad2; __s32 ndm_ifindex; __u16 ndm_state; __u8 ndm_flags; __u8 ndm_type; }; enum { NDA_UNSPEC, NDA_DST, NDA_LLADDR, NDA_CACHEINFO, NDA_PROBES, NDA_VLAN, NDA_PORT, NDA_VNI, NDA_IFINDEX, NDA_MASTER, NDA_LINK_NETNSID, NDA_SRC_VNI, __NDA_MAX }; #define NDA_MAX (__NDA_MAX - 1) /* * Neighbor Cache Entry Flags */ #define NTF_USE 0x01 #define NTF_SELF 0x02 #define NTF_MASTER 0x04 #define NTF_PROXY 0x08 /* == ATF_PUBL */ #define NTF_EXT_LEARNED 0x10 #define NTF_OFFLOADED 0x20 #define NTF_ROUTER 0x80 /* * Neighbor Cache Entry States. */ #define NUD_INCOMPLETE 0x01 #define NUD_REACHABLE 0x02 #define NUD_STALE 0x04 #define NUD_DELAY 0x08 #define NUD_PROBE 0x10 #define NUD_FAILED 0x20 /* Dummy states */ #define NUD_NOARP 0x40 #define NUD_PERMANENT 0x80 #define NUD_NONE 0x00 /* NUD_NOARP & NUD_PERMANENT are pseudostates, they never change and make no address resolution or NUD. NUD_PERMANENT also cannot be deleted by garbage collectors. */ struct nda_cacheinfo { __u32 ndm_confirmed; __u32 ndm_used; __u32 ndm_updated; __u32 ndm_refcnt; }; /***************************************************************** * Neighbour tables specific messages. * * To retrieve the neighbour tables send RTM_GETNEIGHTBL with the * NLM_F_DUMP flag set. Every neighbour table configuration is * spread over multiple messages to avoid running into message * size limits on systems with many interfaces. The first message * in the sequence transports all not device specific data such as * statistics, configuration, and the default parameter set. * This message is followed by 0..n messages carrying device * specific parameter sets. * Although the ordering should be sufficient, NDTA_NAME can be * used to identify sequences. The initial message can be identified * by checking for NDTA_CONFIG. The device specific messages do * not contain this TLV but have NDTPA_IFINDEX set to the * corresponding interface index. * * To change neighbour table attributes, send RTM_SETNEIGHTBL * with NDTA_NAME set. Changeable attribute include NDTA_THRESH[1-3], * NDTA_GC_INTERVAL, and all TLVs in NDTA_PARMS unless marked * otherwise. Device specific parameter sets can be changed by * setting NDTPA_IFINDEX to the interface index of the corresponding * device. ****/ struct ndt_stats { __u64 ndts_allocs; __u64 ndts_destroys; __u64 ndts_hash_grows; __u64 ndts_res_failed; __u64 ndts_lookups; __u64 ndts_hits; __u64 ndts_rcv_probes_mcast; __u64 ndts_rcv_probes_ucast; __u64 ndts_periodic_gc_runs; __u64 ndts_forced_gc_runs; __u64 ndts_table_fulls; }; enum { NDTPA_UNSPEC, NDTPA_IFINDEX, /* u32, unchangeable */ NDTPA_REFCNT, /* u32, read-only */ NDTPA_REACHABLE_TIME, /* u64, read-only, msecs */ NDTPA_BASE_REACHABLE_TIME, /* u64, msecs */ NDTPA_RETRANS_TIME, /* u64, msecs */ NDTPA_GC_STALETIME, /* u64, msecs */ NDTPA_DELAY_PROBE_TIME, /* u64, msecs */ NDTPA_QUEUE_LEN, /* u32 */ NDTPA_APP_PROBES, /* u32 */ NDTPA_UCAST_PROBES, /* u32 */ NDTPA_MCAST_PROBES, /* u32 */ NDTPA_ANYCAST_DELAY, /* u64, msecs */ NDTPA_PROXY_DELAY, /* u64, msecs */ NDTPA_PROXY_QLEN, /* u32 */ NDTPA_LOCKTIME, /* u64, msecs */ NDTPA_QUEUE_LENBYTES, /* u32 */ NDTPA_MCAST_REPROBES, /* u32 */ NDTPA_PAD, __NDTPA_MAX }; #define NDTPA_MAX (__NDTPA_MAX - 1) struct ndtmsg { __u8 ndtm_family; __u8 ndtm_pad1; __u16 ndtm_pad2; }; struct ndt_config { __u16 ndtc_key_len; __u16 ndtc_entry_size; __u32 ndtc_entries; __u32 ndtc_last_flush; /* delta to now in msecs */ __u32 ndtc_last_rand; /* delta to now in msecs */ __u32 ndtc_hash_rnd; __u32 ndtc_hash_mask; __u32 ndtc_hash_chain_gc; __u32 ndtc_proxy_qlen; }; enum { NDTA_UNSPEC, NDTA_NAME, /* char *, unchangeable */ NDTA_THRESH1, /* u32 */ NDTA_THRESH2, /* u32 */ NDTA_THRESH3, /* u32 */ NDTA_CONFIG, /* struct ndt_config, read-only */ NDTA_PARMS, /* nested TLV NDTPA_* */ NDTA_STATS, /* struct ndt_stats, read-only */ NDTA_GC_INTERVAL, /* u64, msecs */ NDTA_PAD, __NDTA_MAX }; #define NDTA_MAX (__NDTA_MAX - 1) #endif frr-7.2.1/include/linux/net_namespace.h0000644000000000000000000000114113610377563014746 00000000000000/* Copyright (c) 2015 6WIND S.A. * Author: Nicolas Dichtel * * This program is free software; you can redistribute it and/or modify it * under the terms and conditions of the GNU General Public License, * version 2, as published by the Free Software Foundation. */ #ifndef _LINUX_NET_NAMESPACE_H_ #define _LINUX_NET_NAMESPACE_H_ /* Attributes of RTM_NEWNSID/RTM_GETNSID messages */ enum { NETNSA_NONE, #define NETNSA_NSID_NOT_ASSIGNED -1 NETNSA_NSID, NETNSA_PID, NETNSA_FD, __NETNSA_MAX, }; #define NETNSA_MAX (__NETNSA_MAX - 1) #endif /* _LINUX_NET_NAMESPACE_H_ */ frr-7.2.1/include/linux/netlink.h0000644000000000000000000001715613610377563013625 00000000000000/* SPDX-License-Identifier: GPL-2.0 WITH Linux-syscall-note */ #ifndef __LINUX_NETLINK_H #define __LINUX_NETLINK_H #include #include /* for __kernel_sa_family_t */ #include #define NETLINK_ROUTE 0 /* Routing/device hook */ #define NETLINK_UNUSED 1 /* Unused number */ #define NETLINK_USERSOCK 2 /* Reserved for user mode socket protocols */ #define NETLINK_FIREWALL 3 /* Unused number, formerly ip_queue */ #define NETLINK_SOCK_DIAG 4 /* socket monitoring */ #define NETLINK_NFLOG 5 /* netfilter/iptables ULOG */ #define NETLINK_XFRM 6 /* ipsec */ #define NETLINK_SELINUX 7 /* SELinux event notifications */ #define NETLINK_ISCSI 8 /* Open-iSCSI */ #define NETLINK_AUDIT 9 /* auditing */ #define NETLINK_FIB_LOOKUP 10 #define NETLINK_CONNECTOR 11 #define NETLINK_NETFILTER 12 /* netfilter subsystem */ #define NETLINK_IP6_FW 13 #define NETLINK_DNRTMSG 14 /* DECnet routing messages */ #define NETLINK_KOBJECT_UEVENT 15 /* Kernel messages to userspace */ #define NETLINK_GENERIC 16 /* leave room for NETLINK_DM (DM Events) */ #define NETLINK_SCSITRANSPORT 18 /* SCSI Transports */ #define NETLINK_ECRYPTFS 19 #define NETLINK_RDMA 20 #define NETLINK_CRYPTO 21 /* Crypto layer */ #define NETLINK_SMC 22 /* SMC monitoring */ #define NETLINK_INET_DIAG NETLINK_SOCK_DIAG #define MAX_LINKS 32 struct sockaddr_nl { __kernel_sa_family_t nl_family; /* AF_NETLINK */ unsigned short nl_pad; /* zero */ __u32 nl_pid; /* port ID */ __u32 nl_groups; /* multicast groups mask */ }; struct nlmsghdr { __u32 nlmsg_len; /* Length of message including header */ __u16 nlmsg_type; /* Message content */ __u16 nlmsg_flags; /* Additional flags */ __u32 nlmsg_seq; /* Sequence number */ __u32 nlmsg_pid; /* Sending process port ID */ }; /* Flags values */ #define NLM_F_REQUEST 0x01 /* It is request message. */ #define NLM_F_MULTI 0x02 /* Multipart message, terminated by NLMSG_DONE */ #define NLM_F_ACK 0x04 /* Reply with ack, with zero or error code */ #define NLM_F_ECHO 0x08 /* Echo this request */ #define NLM_F_DUMP_INTR 0x10 /* Dump was inconsistent due to sequence change */ #define NLM_F_DUMP_FILTERED 0x20 /* Dump was filtered as requested */ /* Modifiers to GET request */ #define NLM_F_ROOT 0x100 /* specify tree root */ #define NLM_F_MATCH 0x200 /* return all matching */ #define NLM_F_ATOMIC 0x400 /* atomic GET */ #define NLM_F_DUMP (NLM_F_ROOT|NLM_F_MATCH) /* Modifiers to NEW request */ #define NLM_F_REPLACE 0x100 /* Override existing */ #define NLM_F_EXCL 0x200 /* Do not touch, if it exists */ #define NLM_F_CREATE 0x400 /* Create, if it does not exist */ #define NLM_F_APPEND 0x800 /* Add to end of list */ /* Modifiers to DELETE request */ #define NLM_F_NONREC 0x100 /* Do not delete recursively */ /* Flags for ACK message */ #define NLM_F_CAPPED 0x100 /* request was capped */ #define NLM_F_ACK_TLVS 0x200 /* extended ACK TVLs were included */ /* 4.4BSD ADD NLM_F_CREATE|NLM_F_EXCL 4.4BSD CHANGE NLM_F_REPLACE True CHANGE NLM_F_CREATE|NLM_F_REPLACE Append NLM_F_CREATE Check NLM_F_EXCL */ #define NLMSG_ALIGNTO 4U #define NLMSG_ALIGN(len) ( ((len)+NLMSG_ALIGNTO-1) & ~(NLMSG_ALIGNTO-1) ) #define NLMSG_HDRLEN ((int) NLMSG_ALIGN(sizeof(struct nlmsghdr))) #define NLMSG_LENGTH(len) ((len) + NLMSG_HDRLEN) #define NLMSG_SPACE(len) NLMSG_ALIGN(NLMSG_LENGTH(len)) #define NLMSG_DATA(nlh) ((void*)(((char*)nlh) + NLMSG_LENGTH(0))) #define NLMSG_NEXT(nlh,len) ((len) -= NLMSG_ALIGN((nlh)->nlmsg_len), \ (struct nlmsghdr*)(((char*)(nlh)) + NLMSG_ALIGN((nlh)->nlmsg_len))) #define NLMSG_OK(nlh,len) ((len) >= (int)sizeof(struct nlmsghdr) && \ (nlh)->nlmsg_len >= sizeof(struct nlmsghdr) && \ (nlh)->nlmsg_len <= (len)) #define NLMSG_PAYLOAD(nlh,len) ((nlh)->nlmsg_len - NLMSG_SPACE((len))) #define NLMSG_NOOP 0x1 /* Nothing. */ #define NLMSG_ERROR 0x2 /* Error */ #define NLMSG_DONE 0x3 /* End of a dump */ #define NLMSG_OVERRUN 0x4 /* Data lost */ #define NLMSG_MIN_TYPE 0x10 /* < 0x10: reserved control messages */ struct nlmsgerr { int error; struct nlmsghdr msg; /* * followed by the message contents unless NETLINK_CAP_ACK was set * or the ACK indicates success (error == 0) * message length is aligned with NLMSG_ALIGN() */ /* * followed by TLVs defined in enum nlmsgerr_attrs * if NETLINK_EXT_ACK was set */ }; /** * enum nlmsgerr_attrs - nlmsgerr attributes * @NLMSGERR_ATTR_UNUSED: unused * @NLMSGERR_ATTR_MSG: error message string (string) * @NLMSGERR_ATTR_OFFS: offset of the invalid attribute in the original * message, counting from the beginning of the header (u32) * @NLMSGERR_ATTR_COOKIE: arbitrary subsystem specific cookie to * be used - in the success case - to identify a created * object or operation or similar (binary) * @__NLMSGERR_ATTR_MAX: number of attributes * @NLMSGERR_ATTR_MAX: highest attribute number */ enum nlmsgerr_attrs { NLMSGERR_ATTR_UNUSED, NLMSGERR_ATTR_MSG, NLMSGERR_ATTR_OFFS, NLMSGERR_ATTR_COOKIE, __NLMSGERR_ATTR_MAX, NLMSGERR_ATTR_MAX = __NLMSGERR_ATTR_MAX - 1 }; #define NETLINK_ADD_MEMBERSHIP 1 #define NETLINK_DROP_MEMBERSHIP 2 #define NETLINK_PKTINFO 3 #define NETLINK_BROADCAST_ERROR 4 #define NETLINK_NO_ENOBUFS 5 #define NETLINK_RX_RING 6 #define NETLINK_TX_RING 7 #define NETLINK_LISTEN_ALL_NSID 8 #define NETLINK_LIST_MEMBERSHIPS 9 #define NETLINK_CAP_ACK 10 #define NETLINK_EXT_ACK 11 struct nl_pktinfo { __u32 group; }; struct nl_mmap_req { unsigned int nm_block_size; unsigned int nm_block_nr; unsigned int nm_frame_size; unsigned int nm_frame_nr; }; struct nl_mmap_hdr { unsigned int nm_status; unsigned int nm_len; __u32 nm_group; /* credentials */ __u32 nm_pid; __u32 nm_uid; __u32 nm_gid; }; enum nl_mmap_status { NL_MMAP_STATUS_UNUSED, NL_MMAP_STATUS_RESERVED, NL_MMAP_STATUS_VALID, NL_MMAP_STATUS_COPY, NL_MMAP_STATUS_SKIP, }; #define NL_MMAP_MSG_ALIGNMENT NLMSG_ALIGNTO #define NL_MMAP_MSG_ALIGN(sz) __ALIGN_KERNEL(sz, NL_MMAP_MSG_ALIGNMENT) #define NL_MMAP_HDRLEN NL_MMAP_MSG_ALIGN(sizeof(struct nl_mmap_hdr)) #define NET_MAJOR 36 /* Major 36 is reserved for networking */ enum { NETLINK_UNCONNECTED = 0, NETLINK_CONNECTED, }; /* * <------- NLA_HDRLEN ------> <-- NLA_ALIGN(payload)--> * +---------------------+- - -+- - - - - - - - - -+- - -+ * | Header | Pad | Payload | Pad | * | (struct nlattr) | ing | | ing | * +---------------------+- - -+- - - - - - - - - -+- - -+ * <-------------- nlattr->nla_len --------------> */ struct nlattr { __u16 nla_len; __u16 nla_type; }; /* * nla_type (16 bits) * +---+---+-------------------------------+ * | N | O | Attribute Type | * +---+---+-------------------------------+ * N := Carries nested attributes * O := Payload stored in network byte order * * Note: The N and O flag are mutually exclusive. */ #define NLA_F_NESTED (1 << 15) #define NLA_F_NET_BYTEORDER (1 << 14) #define NLA_TYPE_MASK ~(NLA_F_NESTED | NLA_F_NET_BYTEORDER) #define NLA_ALIGNTO 4 #define NLA_ALIGN(len) (((len) + NLA_ALIGNTO - 1) & ~(NLA_ALIGNTO - 1)) #define NLA_HDRLEN ((int) NLA_ALIGN(sizeof(struct nlattr))) /* Generic 32 bitflags attribute content sent to the kernel. * * The value is a bitmap that defines the values being set * The selector is a bitmask that defines which value is legit * * Examples: * value = 0x0, and selector = 0x1 * implies we are selecting bit 1 and we want to set its value to 0. * * value = 0x2, and selector = 0x2 * implies we are selecting bit 2 and we want to set its value to 1. * */ struct nla_bitfield32 { __u32 value; __u32 selector; }; #endif /* __LINUX_NETLINK_H */ frr-7.2.1/include/linux/rtnetlink.h0000644000000000000000000004264513610377563014174 00000000000000#ifndef __LINUX_RTNETLINK_H #define __LINUX_RTNETLINK_H #include #include #include #include #include /* rtnetlink families. Values up to 127 are reserved for real address * families, values above 128 may be used arbitrarily. */ #define RTNL_FAMILY_IPMR 128 #define RTNL_FAMILY_IP6MR 129 #define RTNL_FAMILY_MAX 129 /**** * Routing/neighbour discovery messages. ****/ /* Types of messages */ enum { RTM_BASE = 16, #define RTM_BASE RTM_BASE RTM_NEWLINK = 16, #define RTM_NEWLINK RTM_NEWLINK RTM_DELLINK, #define RTM_DELLINK RTM_DELLINK RTM_GETLINK, #define RTM_GETLINK RTM_GETLINK RTM_SETLINK, #define RTM_SETLINK RTM_SETLINK RTM_NEWADDR = 20, #define RTM_NEWADDR RTM_NEWADDR RTM_DELADDR, #define RTM_DELADDR RTM_DELADDR RTM_GETADDR, #define RTM_GETADDR RTM_GETADDR RTM_NEWROUTE = 24, #define RTM_NEWROUTE RTM_NEWROUTE RTM_DELROUTE, #define RTM_DELROUTE RTM_DELROUTE RTM_GETROUTE, #define RTM_GETROUTE RTM_GETROUTE RTM_NEWNEIGH = 28, #define RTM_NEWNEIGH RTM_NEWNEIGH RTM_DELNEIGH, #define RTM_DELNEIGH RTM_DELNEIGH RTM_GETNEIGH, #define RTM_GETNEIGH RTM_GETNEIGH RTM_NEWRULE = 32, #define RTM_NEWRULE RTM_NEWRULE RTM_DELRULE, #define RTM_DELRULE RTM_DELRULE RTM_GETRULE, #define RTM_GETRULE RTM_GETRULE RTM_NEWQDISC = 36, #define RTM_NEWQDISC RTM_NEWQDISC RTM_DELQDISC, #define RTM_DELQDISC RTM_DELQDISC RTM_GETQDISC, #define RTM_GETQDISC RTM_GETQDISC RTM_NEWTCLASS = 40, #define RTM_NEWTCLASS RTM_NEWTCLASS RTM_DELTCLASS, #define RTM_DELTCLASS RTM_DELTCLASS RTM_GETTCLASS, #define RTM_GETTCLASS RTM_GETTCLASS RTM_NEWTFILTER = 44, #define RTM_NEWTFILTER RTM_NEWTFILTER RTM_DELTFILTER, #define RTM_DELTFILTER RTM_DELTFILTER RTM_GETTFILTER, #define RTM_GETTFILTER RTM_GETTFILTER RTM_NEWACTION = 48, #define RTM_NEWACTION RTM_NEWACTION RTM_DELACTION, #define RTM_DELACTION RTM_DELACTION RTM_GETACTION, #define RTM_GETACTION RTM_GETACTION RTM_NEWPREFIX = 52, #define RTM_NEWPREFIX RTM_NEWPREFIX RTM_GETMULTICAST = 58, #define RTM_GETMULTICAST RTM_GETMULTICAST RTM_GETANYCAST = 62, #define RTM_GETANYCAST RTM_GETANYCAST RTM_NEWNEIGHTBL = 64, #define RTM_NEWNEIGHTBL RTM_NEWNEIGHTBL RTM_GETNEIGHTBL = 66, #define RTM_GETNEIGHTBL RTM_GETNEIGHTBL RTM_SETNEIGHTBL, #define RTM_SETNEIGHTBL RTM_SETNEIGHTBL RTM_NEWNDUSEROPT = 68, #define RTM_NEWNDUSEROPT RTM_NEWNDUSEROPT RTM_NEWADDRLABEL = 72, #define RTM_NEWADDRLABEL RTM_NEWADDRLABEL RTM_DELADDRLABEL, #define RTM_DELADDRLABEL RTM_DELADDRLABEL RTM_GETADDRLABEL, #define RTM_GETADDRLABEL RTM_GETADDRLABEL RTM_GETDCB = 78, #define RTM_GETDCB RTM_GETDCB RTM_SETDCB, #define RTM_SETDCB RTM_SETDCB RTM_NEWNETCONF = 80, #define RTM_NEWNETCONF RTM_NEWNETCONF RTM_DELNETCONF, #define RTM_DELNETCONF RTM_DELNETCONF RTM_GETNETCONF = 82, #define RTM_GETNETCONF RTM_GETNETCONF RTM_NEWMDB = 84, #define RTM_NEWMDB RTM_NEWMDB RTM_DELMDB = 85, #define RTM_DELMDB RTM_DELMDB RTM_GETMDB = 86, #define RTM_GETMDB RTM_GETMDB RTM_NEWNSID = 88, #define RTM_NEWNSID RTM_NEWNSID RTM_DELNSID = 89, #define RTM_DELNSID RTM_DELNSID RTM_GETNSID = 90, #define RTM_GETNSID RTM_GETNSID RTM_NEWSTATS = 92, #define RTM_NEWSTATS RTM_NEWSTATS RTM_GETSTATS = 94, #define RTM_GETSTATS RTM_GETSTATS RTM_NEWCACHEREPORT = 96, #define RTM_NEWCACHEREPORT RTM_NEWCACHEREPORT __RTM_MAX, #define RTM_MAX (((__RTM_MAX + 3) & ~3) - 1) }; #define RTM_NR_MSGTYPES (RTM_MAX + 1 - RTM_BASE) #define RTM_NR_FAMILIES (RTM_NR_MSGTYPES >> 2) #define RTM_FAM(cmd) (((cmd) - RTM_BASE) >> 2) /* Generic structure for encapsulation of optional route information. It is reminiscent of sockaddr, but with sa_family replaced with attribute type. */ struct rtattr { unsigned short rta_len; unsigned short rta_type; }; /* Macros to handle rtattributes */ #define RTA_ALIGNTO 4U #define RTA_ALIGN(len) ( ((len)+RTA_ALIGNTO-1) & ~(RTA_ALIGNTO-1) ) #define RTA_OK(rta,len) ((len) >= (int)sizeof(struct rtattr) && \ (rta)->rta_len >= sizeof(struct rtattr) && \ (rta)->rta_len <= (len)) #define RTA_NEXT(rta,attrlen) ((attrlen) -= RTA_ALIGN((rta)->rta_len), \ (struct rtattr*)(((char*)(rta)) + RTA_ALIGN((rta)->rta_len))) #define RTA_LENGTH(len) (RTA_ALIGN(sizeof(struct rtattr)) + (len)) #define RTA_SPACE(len) RTA_ALIGN(RTA_LENGTH(len)) #define RTA_DATA(rta) ((void*)(((char*)(rta)) + RTA_LENGTH(0))) #define RTA_PAYLOAD(rta) ((int)((rta)->rta_len) - RTA_LENGTH(0)) /****************************************************************************** * Definitions used in routing table administration. ****/ struct rtmsg { unsigned char rtm_family; unsigned char rtm_dst_len; unsigned char rtm_src_len; unsigned char rtm_tos; unsigned char rtm_table; /* Routing table id */ unsigned char rtm_protocol; /* Routing protocol; see below */ unsigned char rtm_scope; /* See below */ unsigned char rtm_type; /* See below */ unsigned rtm_flags; }; /* rtm_type */ enum { RTN_UNSPEC, RTN_UNICAST, /* Gateway or direct route */ RTN_LOCAL, /* Accept locally */ RTN_BROADCAST, /* Accept locally as broadcast, send as broadcast */ RTN_ANYCAST, /* Accept locally as broadcast, but send as unicast */ RTN_MULTICAST, /* Multicast route */ RTN_BLACKHOLE, /* Drop */ RTN_UNREACHABLE, /* Destination is unreachable */ RTN_PROHIBIT, /* Administratively prohibited */ RTN_THROW, /* Not in this table */ RTN_NAT, /* Translate this address */ RTN_XRESOLVE, /* Use external resolver */ __RTN_MAX }; #define RTN_MAX (__RTN_MAX - 1) /* rtm_protocol */ #define RTPROT_UNSPEC 0 #define RTPROT_REDIRECT 1 /* Route installed by ICMP redirects; not used by current IPv4 */ #define RTPROT_KERNEL 2 /* Route installed by kernel */ #define RTPROT_BOOT 3 /* Route installed during boot */ #define RTPROT_STATIC 4 /* Route installed by administrator */ /* Values of protocol >= RTPROT_STATIC are not interpreted by kernel; they are just passed from user and back as is. It will be used by hypothetical multiple routing daemons. Note that protocol values should be standardized in order to avoid conflicts. */ #define RTPROT_GATED 8 /* Apparently, GateD */ #define RTPROT_RA 9 /* RDISC/ND router advertisements */ #define RTPROT_MRT 10 /* Merit MRT */ #define RTPROT_ZEBRA 11 /* Zebra */ #define RTPROT_BIRD 12 /* BIRD */ #define RTPROT_DNROUTED 13 /* DECnet routing daemon */ #define RTPROT_XORP 14 /* XORP */ #define RTPROT_NTK 15 /* Netsukuku */ #define RTPROT_DHCP 16 /* DHCP client */ #define RTPROT_MROUTED 17 /* Multicast daemon */ #define RTPROT_BABEL 42 /* Babel daemon */ /* rtm_scope Really it is not scope, but sort of distance to the destination. NOWHERE are reserved for not existing destinations, HOST is our local addresses, LINK are destinations, located on directly attached link and UNIVERSE is everywhere in the Universe. Intermediate values are also possible f.e. interior routes could be assigned a value between UNIVERSE and LINK. */ enum rt_scope_t { RT_SCOPE_UNIVERSE=0, /* User defined values */ RT_SCOPE_SITE=200, RT_SCOPE_LINK=253, RT_SCOPE_HOST=254, RT_SCOPE_NOWHERE=255 }; /* rtm_flags */ #define RTM_F_NOTIFY 0x100 /* Notify user of route change */ #define RTM_F_CLONED 0x200 /* This route is cloned */ #define RTM_F_EQUALIZE 0x400 /* Multipath equalizer: NI */ #define RTM_F_PREFIX 0x800 /* Prefix addresses */ #define RTM_F_LOOKUP_TABLE 0x1000 /* set rtm_table to FIB lookup result */ #define RTM_F_FIB_MATCH 0x2000 /* return full fib lookup match */ /* Reserved table identifiers */ enum rt_class_t { RT_TABLE_UNSPEC=0, /* User defined values */ RT_TABLE_COMPAT=252, RT_TABLE_DEFAULT=253, RT_TABLE_MAIN=254, RT_TABLE_LOCAL=255, RT_TABLE_MAX=0xFFFFFFFF }; /* Routing message attributes */ enum rtattr_type_t { RTA_UNSPEC, RTA_DST, RTA_SRC, RTA_IIF, RTA_OIF, RTA_GATEWAY, RTA_PRIORITY, RTA_PREFSRC, RTA_METRICS, RTA_MULTIPATH, RTA_PROTOINFO, /* no longer used */ RTA_FLOW, RTA_CACHEINFO, RTA_SESSION, /* no longer used */ RTA_MP_ALGO, /* no longer used */ RTA_TABLE, RTA_MARK, RTA_MFC_STATS, RTA_VIA, RTA_NEWDST, RTA_PREF, RTA_ENCAP_TYPE, RTA_ENCAP, RTA_EXPIRES, RTA_PAD, RTA_UID, RTA_TTL_PROPAGATE, __RTA_MAX }; #define RTA_MAX (__RTA_MAX - 1) #define RTM_RTA(r) ((struct rtattr*)(((char*)(r)) + NLMSG_ALIGN(sizeof(struct rtmsg)))) #define RTM_PAYLOAD(n) NLMSG_PAYLOAD(n,sizeof(struct rtmsg)) /* RTM_MULTIPATH --- array of struct rtnexthop. * * "struct rtnexthop" describes all necessary nexthop information, * i.e. parameters of path to a destination via this nexthop. * * At the moment it is impossible to set different prefsrc, mtu, window * and rtt for different paths from multipath. */ struct rtnexthop { unsigned short rtnh_len; unsigned char rtnh_flags; unsigned char rtnh_hops; int rtnh_ifindex; }; /* rtnh_flags */ #define RTNH_F_DEAD 1 /* Nexthop is dead (used by multipath) */ #define RTNH_F_PERVASIVE 2 /* Do recursive gateway lookup */ #define RTNH_F_ONLINK 4 /* Gateway is forced on link */ #define RTNH_F_OFFLOAD 8 /* offloaded route */ #define RTNH_F_LINKDOWN 16 /* carrier-down on nexthop */ #define RTNH_F_UNRESOLVED 32 /* The entry is unresolved (ipmr) */ #define RTNH_COMPARE_MASK (RTNH_F_DEAD | RTNH_F_LINKDOWN | RTNH_F_OFFLOAD) /* Macros to handle hexthops */ #define RTNH_ALIGNTO 4 #define RTNH_ALIGN(len) ( ((len)+RTNH_ALIGNTO-1) & ~(RTNH_ALIGNTO-1) ) #define RTNH_OK(rtnh,len) ((rtnh)->rtnh_len >= sizeof(struct rtnexthop) && \ ((int)(rtnh)->rtnh_len) <= (len)) #define RTNH_NEXT(rtnh) ((struct rtnexthop*)(((char*)(rtnh)) + RTNH_ALIGN((rtnh)->rtnh_len))) #define RTNH_LENGTH(len) (RTNH_ALIGN(sizeof(struct rtnexthop)) + (len)) #define RTNH_SPACE(len) RTNH_ALIGN(RTNH_LENGTH(len)) #define RTNH_DATA(rtnh) ((struct rtattr*)(((char*)(rtnh)) + RTNH_LENGTH(0))) /* RTA_VIA */ struct rtvia { __kernel_sa_family_t rtvia_family; __u8 rtvia_addr[0]; }; /* RTM_CACHEINFO */ struct rta_cacheinfo { __u32 rta_clntref; __u32 rta_lastuse; __s32 rta_expires; __u32 rta_error; __u32 rta_used; #define RTNETLINK_HAVE_PEERINFO 1 __u32 rta_id; __u32 rta_ts; __u32 rta_tsage; }; /* RTM_METRICS --- array of struct rtattr with types of RTAX_* */ enum { RTAX_UNSPEC, #define RTAX_UNSPEC RTAX_UNSPEC RTAX_LOCK, #define RTAX_LOCK RTAX_LOCK RTAX_MTU, #define RTAX_MTU RTAX_MTU RTAX_WINDOW, #define RTAX_WINDOW RTAX_WINDOW RTAX_RTT, #define RTAX_RTT RTAX_RTT RTAX_RTTVAR, #define RTAX_RTTVAR RTAX_RTTVAR RTAX_SSTHRESH, #define RTAX_SSTHRESH RTAX_SSTHRESH RTAX_CWND, #define RTAX_CWND RTAX_CWND RTAX_ADVMSS, #define RTAX_ADVMSS RTAX_ADVMSS RTAX_REORDERING, #define RTAX_REORDERING RTAX_REORDERING RTAX_HOPLIMIT, #define RTAX_HOPLIMIT RTAX_HOPLIMIT RTAX_INITCWND, #define RTAX_INITCWND RTAX_INITCWND RTAX_FEATURES, #define RTAX_FEATURES RTAX_FEATURES RTAX_RTO_MIN, #define RTAX_RTO_MIN RTAX_RTO_MIN RTAX_INITRWND, #define RTAX_INITRWND RTAX_INITRWND RTAX_QUICKACK, #define RTAX_QUICKACK RTAX_QUICKACK RTAX_CC_ALGO, #define RTAX_CC_ALGO RTAX_CC_ALGO __RTAX_MAX }; #define RTAX_MAX (__RTAX_MAX - 1) #define RTAX_FEATURE_ECN (1 << 0) #define RTAX_FEATURE_SACK (1 << 1) #define RTAX_FEATURE_TIMESTAMP (1 << 2) #define RTAX_FEATURE_ALLFRAG (1 << 3) #define RTAX_FEATURE_MASK (RTAX_FEATURE_ECN | RTAX_FEATURE_SACK | \ RTAX_FEATURE_TIMESTAMP | RTAX_FEATURE_ALLFRAG) struct rta_session { __u8 proto; __u8 pad1; __u16 pad2; union { struct { __u16 sport; __u16 dport; } ports; struct { __u8 type; __u8 code; __u16 ident; } icmpt; __u32 spi; } u; }; struct rta_mfc_stats { __u64 mfcs_packets; __u64 mfcs_bytes; __u64 mfcs_wrong_if; }; /**** * General form of address family dependent message. ****/ struct rtgenmsg { unsigned char rtgen_family; }; /***************************************************************** * Link layer specific messages. ****/ /* struct ifinfomsg * passes link level specific information, not dependent * on network protocol. */ struct ifinfomsg { unsigned char ifi_family; unsigned char __ifi_pad; unsigned short ifi_type; /* ARPHRD_* */ int ifi_index; /* Link index */ unsigned ifi_flags; /* IFF_* flags */ unsigned ifi_change; /* IFF_* change mask */ }; /******************************************************************** * prefix information ****/ struct prefixmsg { unsigned char prefix_family; unsigned char prefix_pad1; unsigned short prefix_pad2; int prefix_ifindex; unsigned char prefix_type; unsigned char prefix_len; unsigned char prefix_flags; unsigned char prefix_pad3; }; enum { PREFIX_UNSPEC, PREFIX_ADDRESS, PREFIX_CACHEINFO, __PREFIX_MAX }; #define PREFIX_MAX (__PREFIX_MAX - 1) struct prefix_cacheinfo { __u32 preferred_time; __u32 valid_time; }; /***************************************************************** * Traffic control messages. ****/ struct tcmsg { unsigned char tcm_family; unsigned char tcm__pad1; unsigned short tcm__pad2; int tcm_ifindex; __u32 tcm_handle; __u32 tcm_parent; __u32 tcm_info; }; enum { TCA_UNSPEC, TCA_KIND, TCA_OPTIONS, TCA_STATS, TCA_XSTATS, TCA_RATE, TCA_FCNT, TCA_STATS2, TCA_STAB, TCA_PAD, TCA_DUMP_INVISIBLE, TCA_CHAIN, __TCA_MAX }; #define TCA_MAX (__TCA_MAX - 1) #define TCA_RTA(r) ((struct rtattr*)(((char*)(r)) + NLMSG_ALIGN(sizeof(struct tcmsg)))) #define TCA_PAYLOAD(n) NLMSG_PAYLOAD(n,sizeof(struct tcmsg)) /******************************************************************** * Neighbor Discovery userland options ****/ struct nduseroptmsg { unsigned char nduseropt_family; unsigned char nduseropt_pad1; unsigned short nduseropt_opts_len; /* Total length of options */ int nduseropt_ifindex; __u8 nduseropt_icmp_type; __u8 nduseropt_icmp_code; unsigned short nduseropt_pad2; unsigned int nduseropt_pad3; /* Followed by one or more ND options */ }; enum { NDUSEROPT_UNSPEC, NDUSEROPT_SRCADDR, __NDUSEROPT_MAX }; #define NDUSEROPT_MAX (__NDUSEROPT_MAX - 1) /* RTnetlink multicast groups - backwards compatibility for userspace */ #define RTMGRP_LINK 1 #define RTMGRP_NOTIFY 2 #define RTMGRP_NEIGH 4 #define RTMGRP_TC 8 #define RTMGRP_IPV4_IFADDR 0x10 #define RTMGRP_IPV4_MROUTE 0x20 #define RTMGRP_IPV4_ROUTE 0x40 #define RTMGRP_IPV4_RULE 0x80 #define RTMGRP_IPV6_IFADDR 0x100 #define RTMGRP_IPV6_MROUTE 0x200 #define RTMGRP_IPV6_ROUTE 0x400 #define RTMGRP_IPV6_IFINFO 0x800 #define RTMGRP_DECnet_IFADDR 0x1000 #define RTMGRP_DECnet_ROUTE 0x4000 #define RTMGRP_IPV6_PREFIX 0x20000 /* RTnetlink multicast groups */ enum rtnetlink_groups { RTNLGRP_NONE, #define RTNLGRP_NONE RTNLGRP_NONE RTNLGRP_LINK, #define RTNLGRP_LINK RTNLGRP_LINK RTNLGRP_NOTIFY, #define RTNLGRP_NOTIFY RTNLGRP_NOTIFY RTNLGRP_NEIGH, #define RTNLGRP_NEIGH RTNLGRP_NEIGH RTNLGRP_TC, #define RTNLGRP_TC RTNLGRP_TC RTNLGRP_IPV4_IFADDR, #define RTNLGRP_IPV4_IFADDR RTNLGRP_IPV4_IFADDR RTNLGRP_IPV4_MROUTE, #define RTNLGRP_IPV4_MROUTE RTNLGRP_IPV4_MROUTE RTNLGRP_IPV4_ROUTE, #define RTNLGRP_IPV4_ROUTE RTNLGRP_IPV4_ROUTE RTNLGRP_IPV4_RULE, #define RTNLGRP_IPV4_RULE RTNLGRP_IPV4_RULE RTNLGRP_IPV6_IFADDR, #define RTNLGRP_IPV6_IFADDR RTNLGRP_IPV6_IFADDR RTNLGRP_IPV6_MROUTE, #define RTNLGRP_IPV6_MROUTE RTNLGRP_IPV6_MROUTE RTNLGRP_IPV6_ROUTE, #define RTNLGRP_IPV6_ROUTE RTNLGRP_IPV6_ROUTE RTNLGRP_IPV6_IFINFO, #define RTNLGRP_IPV6_IFINFO RTNLGRP_IPV6_IFINFO RTNLGRP_DECnet_IFADDR, #define RTNLGRP_DECnet_IFADDR RTNLGRP_DECnet_IFADDR RTNLGRP_NOP2, RTNLGRP_DECnet_ROUTE, #define RTNLGRP_DECnet_ROUTE RTNLGRP_DECnet_ROUTE RTNLGRP_DECnet_RULE, #define RTNLGRP_DECnet_RULE RTNLGRP_DECnet_RULE RTNLGRP_NOP4, RTNLGRP_IPV6_PREFIX, #define RTNLGRP_IPV6_PREFIX RTNLGRP_IPV6_PREFIX RTNLGRP_IPV6_RULE, #define RTNLGRP_IPV6_RULE RTNLGRP_IPV6_RULE RTNLGRP_ND_USEROPT, #define RTNLGRP_ND_USEROPT RTNLGRP_ND_USEROPT RTNLGRP_PHONET_IFADDR, #define RTNLGRP_PHONET_IFADDR RTNLGRP_PHONET_IFADDR RTNLGRP_PHONET_ROUTE, #define RTNLGRP_PHONET_ROUTE RTNLGRP_PHONET_ROUTE RTNLGRP_DCB, #define RTNLGRP_DCB RTNLGRP_DCB RTNLGRP_IPV4_NETCONF, #define RTNLGRP_IPV4_NETCONF RTNLGRP_IPV4_NETCONF RTNLGRP_IPV6_NETCONF, #define RTNLGRP_IPV6_NETCONF RTNLGRP_IPV6_NETCONF RTNLGRP_MDB, #define RTNLGRP_MDB RTNLGRP_MDB RTNLGRP_MPLS_ROUTE, #define RTNLGRP_MPLS_ROUTE RTNLGRP_MPLS_ROUTE RTNLGRP_NSID, #define RTNLGRP_NSID RTNLGRP_NSID RTNLGRP_MPLS_NETCONF, #define RTNLGRP_MPLS_NETCONF RTNLGRP_MPLS_NETCONF RTNLGRP_IPV4_MROUTE_R, #define RTNLGRP_IPV4_MROUTE_R RTNLGRP_IPV4_MROUTE_R RTNLGRP_IPV6_MROUTE_R, #define RTNLGRP_IPV6_MROUTE_R RTNLGRP_IPV6_MROUTE_R __RTNLGRP_MAX }; #define RTNLGRP_MAX (__RTNLGRP_MAX - 1) /* TC action piece */ struct tcamsg { unsigned char tca_family; unsigned char tca__pad1; unsigned short tca__pad2; }; enum { TCA_ROOT_UNSPEC, TCA_ROOT_TAB, #define TCA_ACT_TAB TCA_ROOT_TAB #define TCAA_MAX TCA_ROOT_TAB TCA_ROOT_FLAGS, TCA_ROOT_COUNT, TCA_ROOT_TIME_DELTA, /* in msecs */ __TCA_ROOT_MAX, #define TCA_ROOT_MAX (__TCA_ROOT_MAX - 1) }; #define TA_RTA(r) ((struct rtattr*)(((char*)(r)) + NLMSG_ALIGN(sizeof(struct tcamsg)))) #define TA_PAYLOAD(n) NLMSG_PAYLOAD(n,sizeof(struct tcamsg)) /* tcamsg flags stored in attribute TCA_ROOT_FLAGS * * TCA_FLAG_LARGE_DUMP_ON user->kernel to request for larger than TCA_ACT_MAX_PRIO * actions in a dump. All dump responses will contain the number of actions * being dumped stored in for user app's consumption in TCA_ROOT_COUNT * */ #define TCA_FLAG_LARGE_DUMP_ON (1 << 0) /* New extended info filters for IFLA_EXT_MASK */ #define RTEXT_FILTER_VF (1 << 0) #define RTEXT_FILTER_BRVLAN (1 << 1) #define RTEXT_FILTER_BRVLAN_COMPRESSED (1 << 2) #define RTEXT_FILTER_SKIP_STATS (1 << 3) /* End of information exported to user level */ #endif /* __LINUX_RTNETLINK_H */ frr-7.2.1/include/linux/socket.h0000644000000000000000000000134213610377563013437 00000000000000#ifndef _LINUX_SOCKET_H #define _LINUX_SOCKET_H /* * Desired design of maximum size and alignment (see RFC2553) */ #define _K_SS_MAXSIZE 128 /* Implementation specific max size */ #define _K_SS_ALIGNSIZE (__alignof__ (struct sockaddr *)) /* Implementation specific desired alignment */ typedef unsigned short __kernel_sa_family_t; struct __kernel_sockaddr_storage { __kernel_sa_family_t ss_family; /* address family */ /* Following field(s) are implementation specific */ char __data[_K_SS_MAXSIZE - sizeof(unsigned short)]; /* space to achieve desired size, */ /* _SS_MAXSIZE value minus size of ss_family */ } __attribute__ ((aligned(_K_SS_ALIGNSIZE))); /* force desired alignment */ #endif /* _LINUX_SOCKET_H */ frr-7.2.1/include/subdir.am0000644000000000000000000000053013610377563012444 00000000000000noinst_HEADERS += \ include/linux/if_addr.h \ include/linux/if_bridge.h \ include/linux/if_link.h \ include/linux/lwtunnel.h \ include/linux/mpls_iptunnel.h \ include/linux/neighbour.h \ include/linux/netlink.h \ include/linux/rtnetlink.h \ include/linux/socket.h \ include/linux/net_namespace.h \ include/linux/fib_rules.h \ # end frr-7.2.1/install-sh0000755000000000000000000003601013610377563011220 00000000000000#!/bin/sh # install - install a program, script, or datafile scriptversion=2018-03-11.20; # UTC # This originates from X11R5 (mit/util/scripts/install.sh), which was # later released in X11R6 (xc/config/util/install.sh) with the # following copyright and license. # # Copyright (C) 1994 X Consortium # # Permission is hereby granted, free of charge, to any person obtaining a copy # of this software and associated documentation files (the "Software"), to # deal in the Software without restriction, including without limitation the # rights to use, copy, modify, merge, publish, distribute, sublicense, and/or # sell copies of the Software, and to permit persons to whom the Software is # furnished to do so, subject to the following conditions: # # The above copyright notice and this permission notice shall be included in # all copies or substantial portions of the Software. # # THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR # IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, # FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE # X CONSORTIUM BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN # AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNEC- # TION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. # # Except as contained in this notice, the name of the X Consortium shall not # be used in advertising or otherwise to promote the sale, use or other deal- # ings in this Software without prior written authorization from the X Consor- # tium. # # # FSF changes to this file are in the public domain. # # Calling this script install-sh is preferred over install.sh, to prevent # 'make' implicit rules from creating a file called install from it # when there is no Makefile. # # This script is compatible with the BSD install script, but was written # from scratch. tab=' ' nl=' ' IFS=" $tab$nl" # Set DOITPROG to "echo" to test this script. doit=${DOITPROG-} doit_exec=${doit:-exec} # Put in absolute file names if you don't have them in your path; # or use environment vars. chgrpprog=${CHGRPPROG-chgrp} chmodprog=${CHMODPROG-chmod} chownprog=${CHOWNPROG-chown} cmpprog=${CMPPROG-cmp} cpprog=${CPPROG-cp} mkdirprog=${MKDIRPROG-mkdir} mvprog=${MVPROG-mv} rmprog=${RMPROG-rm} stripprog=${STRIPPROG-strip} posix_mkdir= # Desired mode of installed file. mode=0755 chgrpcmd= chmodcmd=$chmodprog chowncmd= mvcmd=$mvprog rmcmd="$rmprog -f" stripcmd= src= dst= dir_arg= dst_arg= copy_on_change=false is_target_a_directory=possibly usage="\ Usage: $0 [OPTION]... [-T] SRCFILE DSTFILE or: $0 [OPTION]... SRCFILES... DIRECTORY or: $0 [OPTION]... -t DIRECTORY SRCFILES... or: $0 [OPTION]... -d DIRECTORIES... In the 1st form, copy SRCFILE to DSTFILE. In the 2nd and 3rd, copy all SRCFILES to DIRECTORY. In the 4th, create DIRECTORIES. Options: --help display this help and exit. --version display version info and exit. -c (ignored) -C install only if different (preserve the last data modification time) -d create directories instead of installing files. -g GROUP $chgrpprog installed files to GROUP. -m MODE $chmodprog installed files to MODE. -o USER $chownprog installed files to USER. -s $stripprog installed files. -t DIRECTORY install into DIRECTORY. -T report an error if DSTFILE is a directory. Environment variables override the default commands: CHGRPPROG CHMODPROG CHOWNPROG CMPPROG CPPROG MKDIRPROG MVPROG RMPROG STRIPPROG " while test $# -ne 0; do case $1 in -c) ;; -C) copy_on_change=true;; -d) dir_arg=true;; -g) chgrpcmd="$chgrpprog $2" shift;; --help) echo "$usage"; exit $?;; -m) mode=$2 case $mode in *' '* | *"$tab"* | *"$nl"* | *'*'* | *'?'* | *'['*) echo "$0: invalid mode: $mode" >&2 exit 1;; esac shift;; -o) chowncmd="$chownprog $2" shift;; -s) stripcmd=$stripprog;; -t) is_target_a_directory=always dst_arg=$2 # Protect names problematic for 'test' and other utilities. case $dst_arg in -* | [=\(\)!]) dst_arg=./$dst_arg;; esac shift;; -T) is_target_a_directory=never;; --version) echo "$0 $scriptversion"; exit $?;; --) shift break;; -*) echo "$0: invalid option: $1" >&2 exit 1;; *) break;; esac shift done # We allow the use of options -d and -T together, by making -d # take the precedence; this is for compatibility with GNU install. if test -n "$dir_arg"; then if test -n "$dst_arg"; then echo "$0: target directory not allowed when installing a directory." >&2 exit 1 fi fi if test $# -ne 0 && test -z "$dir_arg$dst_arg"; then # When -d is used, all remaining arguments are directories to create. # When -t is used, the destination is already specified. # Otherwise, the last argument is the destination. Remove it from $@. for arg do if test -n "$dst_arg"; then # $@ is not empty: it contains at least $arg. set fnord "$@" "$dst_arg" shift # fnord fi shift # arg dst_arg=$arg # Protect names problematic for 'test' and other utilities. case $dst_arg in -* | [=\(\)!]) dst_arg=./$dst_arg;; esac done fi if test $# -eq 0; then if test -z "$dir_arg"; then echo "$0: no input file specified." >&2 exit 1 fi # It's OK to call 'install-sh -d' without argument. # This can happen when creating conditional directories. exit 0 fi if test -z "$dir_arg"; then if test $# -gt 1 || test "$is_target_a_directory" = always; then if test ! -d "$dst_arg"; then echo "$0: $dst_arg: Is not a directory." >&2 exit 1 fi fi fi if test -z "$dir_arg"; then do_exit='(exit $ret); exit $ret' trap "ret=129; $do_exit" 1 trap "ret=130; $do_exit" 2 trap "ret=141; $do_exit" 13 trap "ret=143; $do_exit" 15 # Set umask so as not to create temps with too-generous modes. # However, 'strip' requires both read and write access to temps. case $mode in # Optimize common cases. *644) cp_umask=133;; *755) cp_umask=22;; *[0-7]) if test -z "$stripcmd"; then u_plus_rw= else u_plus_rw='% 200' fi cp_umask=`expr '(' 777 - $mode % 1000 ')' $u_plus_rw`;; *) if test -z "$stripcmd"; then u_plus_rw= else u_plus_rw=,u+rw fi cp_umask=$mode$u_plus_rw;; esac fi for src do # Protect names problematic for 'test' and other utilities. case $src in -* | [=\(\)!]) src=./$src;; esac if test -n "$dir_arg"; then dst=$src dstdir=$dst test -d "$dstdir" dstdir_status=$? else # Waiting for this to be detected by the "$cpprog $src $dsttmp" command # might cause directories to be created, which would be especially bad # if $src (and thus $dsttmp) contains '*'. if test ! -f "$src" && test ! -d "$src"; then echo "$0: $src does not exist." >&2 exit 1 fi if test -z "$dst_arg"; then echo "$0: no destination specified." >&2 exit 1 fi dst=$dst_arg # If destination is a directory, append the input filename. if test -d "$dst"; then if test "$is_target_a_directory" = never; then echo "$0: $dst_arg: Is a directory" >&2 exit 1 fi dstdir=$dst dstbase=`basename "$src"` case $dst in */) dst=$dst$dstbase;; *) dst=$dst/$dstbase;; esac dstdir_status=0 else dstdir=`dirname "$dst"` test -d "$dstdir" dstdir_status=$? fi fi case $dstdir in */) dstdirslash=$dstdir;; *) dstdirslash=$dstdir/;; esac obsolete_mkdir_used=false if test $dstdir_status != 0; then case $posix_mkdir in '') # Create intermediate dirs using mode 755 as modified by the umask. # This is like FreeBSD 'install' as of 1997-10-28. umask=`umask` case $stripcmd.$umask in # Optimize common cases. *[2367][2367]) mkdir_umask=$umask;; .*0[02][02] | .[02][02] | .[02]) mkdir_umask=22;; *[0-7]) mkdir_umask=`expr $umask + 22 \ - $umask % 100 % 40 + $umask % 20 \ - $umask % 10 % 4 + $umask % 2 `;; *) mkdir_umask=$umask,go-w;; esac # With -d, create the new directory with the user-specified mode. # Otherwise, rely on $mkdir_umask. if test -n "$dir_arg"; then mkdir_mode=-m$mode else mkdir_mode= fi posix_mkdir=false case $umask in *[123567][0-7][0-7]) # POSIX mkdir -p sets u+wx bits regardless of umask, which # is incompatible with FreeBSD 'install' when (umask & 300) != 0. ;; *) # Note that $RANDOM variable is not portable (e.g. dash); Use it # here however when possible just to lower collision chance. tmpdir=${TMPDIR-/tmp}/ins$RANDOM-$$ trap 'ret=$?; rmdir "$tmpdir/a/b" "$tmpdir/a" "$tmpdir" 2>/dev/null; exit $ret' 0 # Because "mkdir -p" follows existing symlinks and we likely work # directly in world-writeable /tmp, make sure that the '$tmpdir' # directory is successfully created first before we actually test # 'mkdir -p' feature. if (umask $mkdir_umask && $mkdirprog $mkdir_mode "$tmpdir" && exec $mkdirprog $mkdir_mode -p -- "$tmpdir/a/b") >/dev/null 2>&1 then if test -z "$dir_arg" || { # Check for POSIX incompatibilities with -m. # HP-UX 11.23 and IRIX 6.5 mkdir -m -p sets group- or # other-writable bit of parent directory when it shouldn't. # FreeBSD 6.1 mkdir -m -p sets mode of existing directory. test_tmpdir="$tmpdir/a" ls_ld_tmpdir=`ls -ld "$test_tmpdir"` case $ls_ld_tmpdir in d????-?r-*) different_mode=700;; d????-?--*) different_mode=755;; *) false;; esac && $mkdirprog -m$different_mode -p -- "$test_tmpdir" && { ls_ld_tmpdir_1=`ls -ld "$test_tmpdir"` test "$ls_ld_tmpdir" = "$ls_ld_tmpdir_1" } } then posix_mkdir=: fi rmdir "$tmpdir/a/b" "$tmpdir/a" "$tmpdir" else # Remove any dirs left behind by ancient mkdir implementations. rmdir ./$mkdir_mode ./-p ./-- "$tmpdir" 2>/dev/null fi trap '' 0;; esac;; esac if $posix_mkdir && ( umask $mkdir_umask && $doit_exec $mkdirprog $mkdir_mode -p -- "$dstdir" ) then : else # The umask is ridiculous, or mkdir does not conform to POSIX, # or it failed possibly due to a race condition. Create the # directory the slow way, step by step, checking for races as we go. case $dstdir in /*) prefix='/';; [-=\(\)!]*) prefix='./';; *) prefix='';; esac oIFS=$IFS IFS=/ set -f set fnord $dstdir shift set +f IFS=$oIFS prefixes= for d do test X"$d" = X && continue prefix=$prefix$d if test -d "$prefix"; then prefixes= else if $posix_mkdir; then (umask=$mkdir_umask && $doit_exec $mkdirprog $mkdir_mode -p -- "$dstdir") && break # Don't fail if two instances are running concurrently. test -d "$prefix" || exit 1 else case $prefix in *\'*) qprefix=`echo "$prefix" | sed "s/'/'\\\\\\\\''/g"`;; *) qprefix=$prefix;; esac prefixes="$prefixes '$qprefix'" fi fi prefix=$prefix/ done if test -n "$prefixes"; then # Don't fail if two instances are running concurrently. (umask $mkdir_umask && eval "\$doit_exec \$mkdirprog $prefixes") || test -d "$dstdir" || exit 1 obsolete_mkdir_used=true fi fi fi if test -n "$dir_arg"; then { test -z "$chowncmd" || $doit $chowncmd "$dst"; } && { test -z "$chgrpcmd" || $doit $chgrpcmd "$dst"; } && { test "$obsolete_mkdir_used$chowncmd$chgrpcmd" = false || test -z "$chmodcmd" || $doit $chmodcmd $mode "$dst"; } || exit 1 else # Make a couple of temp file names in the proper directory. dsttmp=${dstdirslash}_inst.$$_ rmtmp=${dstdirslash}_rm.$$_ # Trap to clean up those temp files at exit. trap 'ret=$?; rm -f "$dsttmp" "$rmtmp" && exit $ret' 0 # Copy the file name to the temp name. (umask $cp_umask && $doit_exec $cpprog "$src" "$dsttmp") && # and set any options; do chmod last to preserve setuid bits. # # If any of these fail, we abort the whole thing. If we want to # ignore errors from any of these, just make sure not to ignore # errors from the above "$doit $cpprog $src $dsttmp" command. # { test -z "$chowncmd" || $doit $chowncmd "$dsttmp"; } && { test -z "$chgrpcmd" || $doit $chgrpcmd "$dsttmp"; } && { test -z "$stripcmd" || $doit $stripcmd "$dsttmp"; } && { test -z "$chmodcmd" || $doit $chmodcmd $mode "$dsttmp"; } && # If -C, don't bother to copy if it wouldn't change the file. if $copy_on_change && old=`LC_ALL=C ls -dlL "$dst" 2>/dev/null` && new=`LC_ALL=C ls -dlL "$dsttmp" 2>/dev/null` && set -f && set X $old && old=:$2:$4:$5:$6 && set X $new && new=:$2:$4:$5:$6 && set +f && test "$old" = "$new" && $cmpprog "$dst" "$dsttmp" >/dev/null 2>&1 then rm -f "$dsttmp" else # Rename the file to the real destination. $doit $mvcmd -f "$dsttmp" "$dst" 2>/dev/null || # The rename failed, perhaps because mv can't rename something else # to itself, or perhaps because mv is so ancient that it does not # support -f. { # Now remove or move aside any old file at destination location. # We try this two ways since rm can't unlink itself on some # systems and the destination file might be busy for other # reasons. In this case, the final cleanup might fail but the new # file should still install successfully. { test ! -f "$dst" || $doit $rmcmd -f "$dst" 2>/dev/null || { $doit $mvcmd -f "$dst" "$rmtmp" 2>/dev/null && { $doit $rmcmd -f "$rmtmp" 2>/dev/null; :; } } || { echo "$0: cannot unlink or rename $dst" >&2 (exit 1); exit 1 } } && # Now rename the file to the real destination. $doit $mvcmd "$dsttmp" "$dst" } fi || exit 1 trap '' 0 fi done # Local variables: # eval: (add-hook 'before-save-hook 'time-stamp) # time-stamp-start: "scriptversion=" # time-stamp-format: "%:y-%02m-%02d.%02H" # time-stamp-time-zone: "UTC0" # time-stamp-end: "; # UTC" # End: frr-7.2.1/isisd/0000755000000000000000000000000013610377563010407 500000000000000frr-7.2.1/isisd/Makefile0000644000000000000000000000022213610377563011763 00000000000000all: ALWAYS @$(MAKE) -s -C .. isisd/isisd %: ALWAYS @$(MAKE) -s -C .. isisd/$@ Makefile: #nothing ALWAYS: .PHONY: ALWAYS makefiles .SUFFIXES: frr-7.2.1/isisd/fabricd.c0000644000000000000000000004655013610377563012077 00000000000000/* * IS-IS Rout(e)ing protocol - OpenFabric extensions * * Copyright (C) 2018 Christian Franke * * This file is part of FreeRangeRouting (FRR) * * FRR is free software; you can redistribute it and/or modify it * under the terms of the GNU General Public License as published by the * Free Software Foundation; either version 2, or (at your option) any * later version. * * FRR 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; see the file COPYING; if not, write to the Free Software * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA */ #include #include "isisd/fabricd.h" #include "isisd/isisd.h" #include "isisd/isis_memory.h" #include "isisd/isis_circuit.h" #include "isisd/isis_misc.h" #include "isisd/isis_adjacency.h" #include "isisd/isis_spf.h" #include "isisd/isis_tlvs.h" #include "isisd/isis_lsp.h" #include "isisd/isis_spf_private.h" #include "isisd/isis_tx_queue.h" #include "isisd/isis_csm.h" DEFINE_MTYPE_STATIC(ISISD, FABRICD_STATE, "ISIS OpenFabric") DEFINE_MTYPE_STATIC(ISISD, FABRICD_NEIGHBOR, "ISIS OpenFabric Neighbor Entry") DEFINE_MTYPE_STATIC(ISISD, FABRICD_FLOODING_INFO, "ISIS OpenFabric Flooding Log") /* Tracks initial synchronization as per section 2.4 * * We declare the sync complete once we have seen at least one * CSNP and there are no more LSPs with SSN or SRM set. */ enum fabricd_sync_state { FABRICD_SYNC_PENDING, FABRICD_SYNC_STARTED, FABRICD_SYNC_COMPLETE }; struct fabricd { struct isis_area *area; enum fabricd_sync_state initial_sync_state; time_t initial_sync_start; struct isis_circuit *initial_sync_circuit; struct thread *initial_sync_timeout; struct isis_spftree *spftree; struct skiplist *neighbors; struct hash *neighbors_neighbors; uint8_t tier; uint8_t tier_config; uint8_t tier_pending; struct thread *tier_calculation_timer; struct thread *tier_set_timer; int csnp_delay; bool always_send_csnp; }; /* Code related to maintaining the neighbor lists */ struct neighbor_entry { uint8_t id[ISIS_SYS_ID_LEN]; struct isis_adjacency *adj; bool present; }; static struct neighbor_entry *neighbor_entry_new(const uint8_t *id, struct isis_adjacency *adj) { struct neighbor_entry *rv = XMALLOC(MTYPE_FABRICD_NEIGHBOR, sizeof(*rv)); memcpy(rv->id, id, sizeof(rv->id)); rv->adj = adj; return rv; } static void neighbor_entry_del(struct neighbor_entry *neighbor) { XFREE(MTYPE_FABRICD_NEIGHBOR, neighbor); } static void neighbor_entry_del_void(void *arg) { neighbor_entry_del((struct neighbor_entry *)arg); } static void neighbor_lists_clear(struct fabricd *f) { while (!skiplist_empty(f->neighbors)) skiplist_delete_first(f->neighbors); hash_clean(f->neighbors_neighbors, neighbor_entry_del_void); } static unsigned neighbor_entry_hash_key(const void *np) { const struct neighbor_entry *n = np; return jhash(n->id, sizeof(n->id), 0x55aa5a5a); } static bool neighbor_entry_hash_cmp(const void *a, const void *b) { const struct neighbor_entry *na = a, *nb = b; return memcmp(na->id, nb->id, sizeof(na->id)) == 0; } static int neighbor_entry_list_cmp(void *a, void *b) { struct neighbor_entry *na = a, *nb = b; return -memcmp(na->id, nb->id, sizeof(na->id)); } static struct neighbor_entry *neighbor_entry_lookup_list(struct skiplist *list, const uint8_t *id) { struct neighbor_entry n = { {0} }; memcpy(n.id, id, sizeof(n.id)); struct neighbor_entry *rv; if (skiplist_search(list, &n, (void**)&rv)) return NULL; if (!rv->present) return NULL; return rv; } static struct neighbor_entry *neighbor_entry_lookup_hash(struct hash *hash, const uint8_t *id) { struct neighbor_entry n = {{0}}; memcpy(n.id, id, sizeof(n.id)); struct neighbor_entry *rv = hash_lookup(hash, &n); if (!rv || !rv->present) return NULL; return rv; } static int fabricd_handle_adj_state_change(struct isis_adjacency *arg) { struct fabricd *f = arg->circuit->area->fabricd; if (!f) return 0; while (!skiplist_empty(f->neighbors)) skiplist_delete_first(f->neighbors); struct listnode *node; struct isis_circuit *circuit; for (ALL_LIST_ELEMENTS_RO(f->area->circuit_list, node, circuit)) { if (circuit->state != C_STATE_UP) continue; struct isis_adjacency *adj = circuit->u.p2p.neighbor; if (!adj || adj->adj_state != ISIS_ADJ_UP) continue; struct neighbor_entry *n = neighbor_entry_new(adj->sysid, adj); skiplist_insert(f->neighbors, n, n); } return 0; } static void neighbors_neighbors_update(struct fabricd *f) { hash_clean(f->neighbors_neighbors, neighbor_entry_del_void); struct listnode *node; struct isis_vertex *v; for (ALL_QUEUE_ELEMENTS_RO(&f->spftree->paths, node, v)) { if (v->d_N < 2 || !VTYPE_IS(v->type)) continue; if (v->d_N > 2) break; struct neighbor_entry *n = neighbor_entry_new(v->N.id, NULL); struct neighbor_entry *inserted; inserted = hash_get(f->neighbors_neighbors, n, hash_alloc_intern); assert(inserted == n); } } struct fabricd *fabricd_new(struct isis_area *area) { struct fabricd *rv = XCALLOC(MTYPE_FABRICD_STATE, sizeof(*rv)); rv->area = area; rv->initial_sync_state = FABRICD_SYNC_PENDING; rv->spftree = isis_spftree_new(area); rv->neighbors = skiplist_new(0, neighbor_entry_list_cmp, neighbor_entry_del_void); rv->neighbors_neighbors = hash_create(neighbor_entry_hash_key, neighbor_entry_hash_cmp, "Fabricd Neighbors"); rv->tier = rv->tier_config = ISIS_TIER_UNDEFINED; rv->csnp_delay = FABRICD_DEFAULT_CSNP_DELAY; return rv; }; void fabricd_finish(struct fabricd *f) { if (f->initial_sync_timeout) thread_cancel(f->initial_sync_timeout); if (f->tier_calculation_timer) thread_cancel(f->tier_calculation_timer); if (f->tier_set_timer) thread_cancel(f->tier_set_timer); isis_spftree_del(f->spftree); neighbor_lists_clear(f); skiplist_free(f->neighbors); hash_free(f->neighbors_neighbors); } static int fabricd_initial_sync_timeout(struct thread *thread) { struct fabricd *f = THREAD_ARG(thread); zlog_info("OpenFabric: Initial synchronization on %s timed out!", f->initial_sync_circuit->interface->name); f->initial_sync_state = FABRICD_SYNC_PENDING; f->initial_sync_circuit = NULL; f->initial_sync_timeout = NULL; return 0; } void fabricd_initial_sync_hello(struct isis_circuit *circuit) { struct fabricd *f = circuit->area->fabricd; if (!f) return; if (f->initial_sync_state > FABRICD_SYNC_PENDING) return; f->initial_sync_state = FABRICD_SYNC_STARTED; long timeout = 2 * circuit->hello_interval[1] * circuit->hello_multiplier[1]; f->initial_sync_circuit = circuit; if (f->initial_sync_timeout) return; thread_add_timer(master, fabricd_initial_sync_timeout, f, timeout, &f->initial_sync_timeout); f->initial_sync_start = monotime(NULL); zlog_info("OpenFabric: Started initial synchronization with %s on %s", sysid_print(circuit->u.p2p.neighbor->sysid), circuit->interface->name); } bool fabricd_initial_sync_is_in_progress(struct isis_area *area) { struct fabricd *f = area->fabricd; if (!f) return false; if (f->initial_sync_state > FABRICD_SYNC_PENDING && f->initial_sync_state < FABRICD_SYNC_COMPLETE) return true; return false; } bool fabricd_initial_sync_is_complete(struct isis_area *area) { struct fabricd *f = area->fabricd; if (!f) return false; return f->initial_sync_state == FABRICD_SYNC_COMPLETE; } struct isis_circuit *fabricd_initial_sync_circuit(struct isis_area *area) { struct fabricd *f = area->fabricd; if (!f) return NULL; return f->initial_sync_circuit; } void fabricd_initial_sync_finish(struct isis_area *area) { struct fabricd *f = area->fabricd; if (!f) return; if (monotime(NULL) - f->initial_sync_start < 5) return; zlog_info("OpenFabric: Initial synchronization on %s complete.", f->initial_sync_circuit->interface->name); f->initial_sync_state = FABRICD_SYNC_COMPLETE; f->initial_sync_circuit = NULL; thread_cancel(f->initial_sync_timeout); f->initial_sync_timeout = NULL; } static void fabricd_bump_tier_calculation_timer(struct fabricd *f); static void fabricd_set_tier(struct fabricd *f, uint8_t tier); static uint8_t fabricd_calculate_fabric_tier(struct isis_area *area) { struct isis_spftree *local_tree = fabricd_spftree(area); struct listnode *node; struct isis_vertex *furthest_t0 = NULL, *second_furthest_t0 = NULL; struct isis_vertex *v; for (ALL_QUEUE_ELEMENTS_RO(&local_tree->paths, node, v)) { struct isis_lsp *lsp = lsp_for_vertex(local_tree, v); if (!lsp || !lsp->tlvs || !lsp->tlvs->spine_leaf || !lsp->tlvs->spine_leaf->has_tier || lsp->tlvs->spine_leaf->tier != 0) continue; second_furthest_t0 = furthest_t0; furthest_t0 = v; } if (!second_furthest_t0) { zlog_info("OpenFabric: Could not find two T0 routers"); return ISIS_TIER_UNDEFINED; } zlog_info("OpenFabric: Found %s as furthest t0 from local system, dist == %" PRIu32, rawlspid_print(furthest_t0->N.id), furthest_t0->d_N); struct isis_spftree *remote_tree = isis_run_hopcount_spf(area, furthest_t0->N.id, NULL); struct isis_vertex *furthest_from_remote = isis_vertex_queue_last(&remote_tree->paths); if (!furthest_from_remote) { zlog_info("OpenFabric: Found no furthest node in remote spf"); isis_spftree_del(remote_tree); return ISIS_TIER_UNDEFINED; } else { zlog_info("OpenFabric: Found %s as furthest from remote dist == %" PRIu32, rawlspid_print(furthest_from_remote->N.id), furthest_from_remote->d_N); } int64_t tier = furthest_from_remote->d_N - furthest_t0->d_N; isis_spftree_del(remote_tree); if (tier < 0 || tier >= ISIS_TIER_UNDEFINED) { zlog_info("OpenFabric: Calculated tier %" PRId64 " seems implausible", tier); return ISIS_TIER_UNDEFINED; } zlog_info("OpenFabric: Calculated %" PRId64 " as tier", tier); return tier; } static int fabricd_tier_set_timer(struct thread *thread) { struct fabricd *f = THREAD_ARG(thread); f->tier_set_timer = NULL; fabricd_set_tier(f, f->tier_pending); return 0; } static int fabricd_tier_calculation_cb(struct thread *thread) { struct fabricd *f = THREAD_ARG(thread); uint8_t tier = ISIS_TIER_UNDEFINED; f->tier_calculation_timer = NULL; tier = fabricd_calculate_fabric_tier(f->area); if (tier == ISIS_TIER_UNDEFINED) return 0; zlog_info("OpenFabric: Got tier %" PRIu8 " from algorithm. Arming timer.", tier); f->tier_pending = tier; thread_add_timer(master, fabricd_tier_set_timer, f, f->area->lsp_gen_interval[ISIS_LEVEL2 - 1], &f->tier_set_timer); return 0; } static void fabricd_bump_tier_calculation_timer(struct fabricd *f) { /* Cancel timer if we already know our tier */ if (f->tier != ISIS_TIER_UNDEFINED || f->tier_set_timer) { if (f->tier_calculation_timer) { thread_cancel(f->tier_calculation_timer); f->tier_calculation_timer = NULL; } return; } /* If we need to calculate the tier, wait some * time for the topology to settle before running * the calculation */ if (f->tier_calculation_timer) { thread_cancel(f->tier_calculation_timer); f->tier_calculation_timer = NULL; } thread_add_timer(master, fabricd_tier_calculation_cb, f, 2 * f->area->lsp_gen_interval[ISIS_LEVEL2 - 1], &f->tier_calculation_timer); } static void fabricd_set_tier(struct fabricd *f, uint8_t tier) { if (f->tier == tier) return; zlog_info("OpenFabric: Set own tier to %" PRIu8, tier); f->tier = tier; fabricd_bump_tier_calculation_timer(f); lsp_regenerate_schedule(f->area, ISIS_LEVEL2, 0); } void fabricd_run_spf(struct isis_area *area) { struct fabricd *f = area->fabricd; if (!f) return; isis_run_hopcount_spf(area, isis->sysid, f->spftree); neighbors_neighbors_update(f); fabricd_bump_tier_calculation_timer(f); } struct isis_spftree *fabricd_spftree(struct isis_area *area) { struct fabricd *f = area->fabricd; if (!f) return NULL; return f->spftree; } void fabricd_configure_tier(struct isis_area *area, uint8_t tier) { struct fabricd *f = area->fabricd; if (!f || f->tier_config == tier) return; f->tier_config = tier; fabricd_set_tier(f, tier); } uint8_t fabricd_tier(struct isis_area *area) { struct fabricd *f = area->fabricd; if (!f) return ISIS_TIER_UNDEFINED; return f->tier; } int fabricd_write_settings(struct isis_area *area, struct vty *vty) { struct fabricd *f = area->fabricd; int written = 0; if (!f) return written; if (f->tier_config != ISIS_TIER_UNDEFINED) { vty_out(vty, " fabric-tier %" PRIu8 "\n", f->tier_config); written++; } if (f->csnp_delay != FABRICD_DEFAULT_CSNP_DELAY || f->always_send_csnp) { vty_out(vty, " triggered-csnp-delay %d%s\n", f->csnp_delay, f->always_send_csnp ? " always" : ""); } return written; } static void move_to_queue(struct isis_lsp *lsp, struct neighbor_entry *n, enum isis_tx_type type, struct isis_circuit *circuit) { n->present = false; if (n->adj && n->adj->circuit == circuit) return; if (isis->debugs & DEBUG_FLOODING) { zlog_debug("OpenFabric: Adding %s to %s", print_sys_hostname(n->id), (type == TX_LSP_NORMAL) ? "RF" : "DNR"); } if (n->adj) isis_tx_queue_add(n->adj->circuit->tx_queue, lsp, type); uint8_t *neighbor_id = XMALLOC(MTYPE_FABRICD_FLOODING_INFO, sizeof(n->id)); memcpy(neighbor_id, n->id, sizeof(n->id)); listnode_add(lsp->flooding_neighbors[type], neighbor_id); } static void mark_neighbor_as_present(struct hash_bucket *bucket, void *arg) { struct neighbor_entry *n = bucket->data; n->present = true; } static void handle_firsthops(struct hash_bucket *bucket, void *arg) { struct isis_lsp *lsp = arg; struct fabricd *f = lsp->area->fabricd; struct isis_vertex *vertex = bucket->data; struct neighbor_entry *n; n = neighbor_entry_lookup_list(f->neighbors, vertex->N.id); if (n) { if (isis->debugs & DEBUG_FLOODING) { zlog_debug("Removing %s from NL as its in the reverse path", print_sys_hostname(n->id)); } n->present = false; } n = neighbor_entry_lookup_hash(f->neighbors_neighbors, vertex->N.id); if (n) { if (isis->debugs & DEBUG_FLOODING) { zlog_debug("Removing %s from NN as its in the reverse path", print_sys_hostname(n->id)); } n->present = false; } } static struct isis_lsp *lsp_for_neighbor(struct fabricd *f, struct neighbor_entry *n) { uint8_t id[ISIS_SYS_ID_LEN + 1] = {0}; memcpy(id, n->id, sizeof(n->id)); struct isis_vertex vertex = {0}; isis_vertex_id_init(&vertex, id, VTYPE_NONPSEUDO_TE_IS); return lsp_for_vertex(f->spftree, &vertex); } static void fabricd_free_lsp_flooding_info(void *val) { XFREE(MTYPE_FABRICD_FLOODING_INFO, val); } static void fabricd_lsp_reset_flooding_info(struct isis_lsp *lsp, struct isis_circuit *circuit) { lsp->flooding_time = time(NULL); XFREE(MTYPE_FABRICD_FLOODING_INFO, lsp->flooding_interface); for (enum isis_tx_type type = TX_LSP_NORMAL; type <= TX_LSP_CIRCUIT_SCOPED; type++) { if (lsp->flooding_neighbors[type]) { list_delete_all_node(lsp->flooding_neighbors[type]); continue; } lsp->flooding_neighbors[type] = list_new(); lsp->flooding_neighbors[type]->del = fabricd_free_lsp_flooding_info; } if (circuit) { lsp->flooding_interface = XSTRDUP(MTYPE_FABRICD_FLOODING_INFO, circuit->interface->name); } lsp->flooding_circuit_scoped = false; } void fabricd_lsp_flood(struct isis_lsp *lsp, struct isis_circuit *circuit) { struct fabricd *f = lsp->area->fabricd; assert(f); fabricd_lsp_reset_flooding_info(lsp, circuit); void *cursor = NULL; struct neighbor_entry *n; /* Mark all elements in NL as present */ while (!skiplist_next(f->neighbors, NULL, (void **)&n, &cursor)) n->present = true; /* Mark all elements in NN as present */ hash_iterate(f->neighbors_neighbors, mark_neighbor_as_present, NULL); struct isis_vertex *originator = isis_find_vertex(&f->spftree->paths, lsp->hdr.lsp_id, VTYPE_NONPSEUDO_TE_IS); /* Remove all IS from NL and NN in the shortest path * to the IS that originated the LSP */ if (originator) hash_iterate(originator->firsthops, handle_firsthops, lsp); /* Iterate over all remaining IS in NL */ cursor = NULL; while (!skiplist_next(f->neighbors, NULL, (void **)&n, &cursor)) { if (!n->present) continue; struct isis_lsp *nlsp = lsp_for_neighbor(f, n); if (!nlsp || !nlsp->tlvs) { if (isis->debugs & DEBUG_FLOODING) { zlog_debug("Moving %s to DNR as it has no LSP", print_sys_hostname(n->id)); } move_to_queue(lsp, n, TX_LSP_CIRCUIT_SCOPED, circuit); continue; } if (isis->debugs & DEBUG_FLOODING) { zlog_debug("Considering %s from NL...", print_sys_hostname(n->id)); } /* For all neighbors of the NL IS check whether they are present * in NN. If yes, remove from NN and set need_reflood. */ bool need_reflood = false; struct isis_extended_reach *er; for (er = (struct isis_extended_reach *)nlsp->tlvs->extended_reach.head; er; er = er->next) { struct neighbor_entry *nn; nn = neighbor_entry_lookup_hash(f->neighbors_neighbors, er->id); if (nn) { if (isis->debugs & DEBUG_FLOODING) { zlog_debug("Found neighbor %s in NN, removing it from NN and setting reflood.", print_sys_hostname(nn->id)); } nn->present = false; need_reflood = true; } } move_to_queue(lsp, n, need_reflood ? TX_LSP_NORMAL : TX_LSP_CIRCUIT_SCOPED, circuit); } if (isis->debugs & DEBUG_FLOODING) { zlog_debug("OpenFabric: Flooding algorithm complete."); } } void fabricd_trigger_csnp(struct isis_area *area, bool circuit_scoped) { struct fabricd *f = area->fabricd; if (!f) return; if (!circuit_scoped && !f->always_send_csnp) return; struct listnode *node; struct isis_circuit *circuit; for (ALL_LIST_ELEMENTS_RO(area->circuit_list, node, circuit)) { if (!circuit->t_send_csnp[1]) continue; thread_cancel(circuit->t_send_csnp[ISIS_LEVEL2 - 1]); thread_add_timer_msec(master, send_l2_csnp, circuit, isis_jitter(f->csnp_delay, CSNP_JITTER), &circuit->t_send_csnp[ISIS_LEVEL2 - 1]); } } struct list *fabricd_ip_addrs(struct isis_circuit *circuit) { if (circuit->ip_addrs && listcount(circuit->ip_addrs)) return circuit->ip_addrs; if (!fabricd || !circuit->area || !circuit->area->circuit_list) return NULL; struct listnode *node; struct isis_circuit *c; for (ALL_LIST_ELEMENTS_RO(circuit->area->circuit_list, node, c)) { if (c->circ_type != CIRCUIT_T_LOOPBACK) continue; if (!c->ip_addrs || !listcount(c->ip_addrs)) return NULL; return c->ip_addrs; } return NULL; } void fabricd_lsp_free(struct isis_lsp *lsp) { XFREE(MTYPE_FABRICD_FLOODING_INFO, lsp->flooding_interface); for (enum isis_tx_type type = TX_LSP_NORMAL; type <= TX_LSP_CIRCUIT_SCOPED; type++) { if (!lsp->flooding_neighbors[type]) continue; list_delete(&lsp->flooding_neighbors[type]); } } void fabricd_update_lsp_no_flood(struct isis_lsp *lsp, struct isis_circuit *circuit) { if (!fabricd) return; fabricd_lsp_reset_flooding_info(lsp, circuit); lsp->flooding_circuit_scoped = true; } void fabricd_configure_triggered_csnp(struct isis_area *area, int delay, bool always_send_csnp) { struct fabricd *f = area->fabricd; if (!f) return; f->csnp_delay = delay; f->always_send_csnp = always_send_csnp; } void fabricd_init(void) { hook_register(isis_adj_state_change_hook, fabricd_handle_adj_state_change); } frr-7.2.1/isisd/fabricd.conf.sample0000644000000000000000000000076513610377563014060 00000000000000! -*- openfabric -*- ! ! fabricd sample configuration file ! hostname fabricd password foo enable password foo log stdout !log file /tmp/fabricd.log ! ! router openfabric DEAD net 47.0023.0000.0003.0300.0100.0102.0304.0506.00 ! lsp-lifetime 65535 ! hostname isisd-router ! domain-password foobar interface eth0 ip router openfabric DEAD ! openfabric hello-interval 5 ! openfabric lsp-interval 1000 ! -- optional ! openfabric retransmit-interval 10 ! openfabric retransmit-throttle-interval ! frr-7.2.1/isisd/fabricd.h0000644000000000000000000000433213610377563012074 00000000000000/* * IS-IS Rout(e)ing protocol - OpenFabric extensions * * Copyright (C) 2018 Christian Franke * * This file is part of FreeRangeRouting (FRR) * * FRR is free software; you can redistribute it and/or modify it * under the terms of the GNU General Public License as published by the * Free Software Foundation; either version 2, or (at your option) any * later version. * * FRR 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; see the file COPYING; if not, write to the Free Software * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA */ #ifndef FABRICD_H #define FABRICD_H #define FABRICD_DEFAULT_CSNP_DELAY 500 struct fabricd; struct isis_circuit; struct isis_area; struct isis_spftree; struct isis_lsp; struct vty; struct fabricd *fabricd_new(struct isis_area *area); void fabricd_finish(struct fabricd *f); void fabricd_initial_sync_hello(struct isis_circuit *circuit); bool fabricd_initial_sync_is_complete(struct isis_area *area); bool fabricd_initial_sync_is_in_progress(struct isis_area *area); struct isis_circuit *fabricd_initial_sync_circuit(struct isis_area *area); void fabricd_initial_sync_finish(struct isis_area *area); void fabricd_run_spf(struct isis_area *area); struct isis_spftree *fabricd_spftree(struct isis_area *area); void fabricd_configure_tier(struct isis_area *area, uint8_t tier); uint8_t fabricd_tier(struct isis_area *area); int fabricd_write_settings(struct isis_area *area, struct vty *vty); void fabricd_lsp_flood(struct isis_lsp *lsp, struct isis_circuit *circuit); void fabricd_trigger_csnp(struct isis_area *area, bool circuit_scoped); struct list *fabricd_ip_addrs(struct isis_circuit *circuit); void fabricd_lsp_free(struct isis_lsp *lsp); void fabricd_update_lsp_no_flood(struct isis_lsp *lsp, struct isis_circuit *circuit); void fabricd_configure_triggered_csnp(struct isis_area *area, int delay, bool always_send_csnp); void fabricd_init(void); void isis_vty_daemon_init(void); #endif frr-7.2.1/isisd/isis_adjacency.c0000644000000000000000000003536613610377563013460 00000000000000/* * IS-IS Rout(e)ing protocol - isis_adjacency.c * handling of IS-IS adjacencies * * Copyright (C) 2001,2002 Sampo Saaristo * Tampere University of Technology * Institute of Communications Engineering * * This program is free software; you can redistribute it and/or modify it * under the terms of the GNU General Public Licenseas 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; see the file COPYING; if not, write to the Free Software * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA */ #include #include "log.h" #include "memory.h" #include "hash.h" #include "vty.h" #include "linklist.h" #include "thread.h" #include "if.h" #include "stream.h" #include "isisd/isis_constants.h" #include "isisd/isis_common.h" #include "isisd/isis_flags.h" #include "isisd/isisd.h" #include "isisd/isis_circuit.h" #include "isisd/isis_adjacency.h" #include "isisd/isis_misc.h" #include "isisd/isis_dr.h" #include "isisd/isis_dynhn.h" #include "isisd/isis_pdu.h" #include "isisd/isis_lsp.h" #include "isisd/isis_spf.h" #include "isisd/isis_events.h" #include "isisd/isis_mt.h" #include "isisd/isis_tlvs.h" #include "isisd/fabricd.h" extern struct isis *isis; static struct isis_adjacency *adj_alloc(const uint8_t *id) { struct isis_adjacency *adj; adj = XCALLOC(MTYPE_ISIS_ADJACENCY, sizeof(struct isis_adjacency)); memcpy(adj->sysid, id, ISIS_SYS_ID_LEN); return adj; } struct isis_adjacency *isis_new_adj(const uint8_t *id, const uint8_t *snpa, int level, struct isis_circuit *circuit) { struct isis_adjacency *adj; int i; adj = adj_alloc(id); /* P2P kludge */ if (snpa) { memcpy(adj->snpa, snpa, ETH_ALEN); } else { memset(adj->snpa, ' ', ETH_ALEN); } adj->circuit = circuit; adj->level = level; adj->flaps = 0; adj->last_flap = time(NULL); adj->threeway_state = ISIS_THREEWAY_DOWN; if (circuit->circ_type == CIRCUIT_T_BROADCAST) { listnode_add(circuit->u.bc.adjdb[level - 1], adj); adj->dischanges[level - 1] = 0; for (i = 0; i < DIS_RECORDS; i++) /* clear N DIS state change records */ { adj->dis_record[(i * ISIS_LEVELS) + level - 1].dis = ISIS_UNKNOWN_DIS; adj->dis_record[(i * ISIS_LEVELS) + level - 1] .last_dis_change = time(NULL); } } return adj; } struct isis_adjacency *isis_adj_lookup(const uint8_t *sysid, struct list *adjdb) { struct isis_adjacency *adj; struct listnode *node; for (ALL_LIST_ELEMENTS_RO(adjdb, node, adj)) if (memcmp(adj->sysid, sysid, ISIS_SYS_ID_LEN) == 0) return adj; return NULL; } struct isis_adjacency *isis_adj_lookup_snpa(const uint8_t *ssnpa, struct list *adjdb) { struct listnode *node; struct isis_adjacency *adj; for (ALL_LIST_ELEMENTS_RO(adjdb, node, adj)) if (memcmp(adj->snpa, ssnpa, ETH_ALEN) == 0) return adj; return NULL; } DEFINE_HOOK(isis_adj_state_change_hook, (struct isis_adjacency *adj), (adj)) void isis_delete_adj(void *arg) { struct isis_adjacency *adj = arg; if (!adj) return; THREAD_TIMER_OFF(adj->t_expire); if (adj->adj_state != ISIS_ADJ_DOWN) { adj->adj_state = ISIS_ADJ_DOWN; hook_call(isis_adj_state_change_hook, adj); } /* remove from SPF trees */ spftree_area_adj_del(adj->circuit->area, adj); XFREE(MTYPE_ISIS_ADJACENCY_INFO, adj->area_addresses); XFREE(MTYPE_ISIS_ADJACENCY_INFO, adj->ipv4_addresses); XFREE(MTYPE_ISIS_ADJACENCY_INFO, adj->ipv6_addresses); adj_mt_finish(adj); XFREE(MTYPE_ISIS_ADJACENCY, adj); return; } static const char *adj_state2string(int state) { switch (state) { case ISIS_ADJ_INITIALIZING: return "Initializing"; case ISIS_ADJ_UP: return "Up"; case ISIS_ADJ_DOWN: return "Down"; default: return "Unknown"; } return NULL; /* not reached */ } void isis_adj_process_threeway(struct isis_adjacency *adj, struct isis_threeway_adj *tw_adj, enum isis_adj_usage adj_usage) { enum isis_threeway_state next_tw_state = ISIS_THREEWAY_DOWN; if (tw_adj && !adj->circuit->disable_threeway_adj) { if (tw_adj->state == ISIS_THREEWAY_DOWN) { next_tw_state = ISIS_THREEWAY_INITIALIZING; } else if (tw_adj->state == ISIS_THREEWAY_INITIALIZING) { next_tw_state = ISIS_THREEWAY_UP; } else if (tw_adj->state == ISIS_THREEWAY_UP) { if (adj->threeway_state == ISIS_THREEWAY_DOWN) next_tw_state = ISIS_THREEWAY_DOWN; else next_tw_state = ISIS_THREEWAY_UP; } } else { next_tw_state = ISIS_THREEWAY_UP; } if (next_tw_state != adj->threeway_state) { if (isis->debugs & DEBUG_ADJ_PACKETS) { zlog_info("ISIS-Adj (%s): Threeway state change %s to %s", adj->circuit->area->area_tag, isis_threeway_state_name(adj->threeway_state), isis_threeway_state_name(next_tw_state)); } } if (next_tw_state != ISIS_THREEWAY_DOWN) fabricd_initial_sync_hello(adj->circuit); if (next_tw_state == ISIS_THREEWAY_DOWN) { isis_adj_state_change(adj, ISIS_ADJ_DOWN, "Neighbor restarted"); return; } if (next_tw_state == ISIS_THREEWAY_UP) { if (adj->adj_state != ISIS_ADJ_UP) { isis_adj_state_change(adj, ISIS_ADJ_UP, NULL); adj->adj_usage = adj_usage; } } if (adj->threeway_state != next_tw_state) { send_hello_sched(adj->circuit, 0, TRIGGERED_IIH_DELAY); } adj->threeway_state = next_tw_state; } void isis_adj_state_change(struct isis_adjacency *adj, enum isis_adj_state new_state, const char *reason) { enum isis_adj_state old_state = adj->adj_state; struct isis_circuit *circuit = adj->circuit; bool del; adj->adj_state = new_state; if (new_state != old_state) { send_hello_sched(circuit, adj->level, TRIGGERED_IIH_DELAY); } if (isis->debugs & DEBUG_ADJ_PACKETS) { zlog_debug("ISIS-Adj (%s): Adjacency state change %d->%d: %s", circuit->area->area_tag, old_state, new_state, reason ? reason : "unspecified"); } if (circuit->area->log_adj_changes) { const char *adj_name; struct isis_dynhn *dyn; dyn = dynhn_find_by_id(adj->sysid); if (dyn) adj_name = dyn->hostname; else adj_name = sysid_print(adj->sysid); zlog_info( "%%ADJCHANGE: Adjacency to %s (%s) changed from %s to %s, %s", adj_name, adj->circuit->interface->name, adj_state2string(old_state), adj_state2string(new_state), reason ? reason : "unspecified"); } #ifndef FABRICD /* send northbound notification */ isis_notif_adj_state_change(adj, new_state, reason); #endif /* ifndef FABRICD */ if (circuit->circ_type == CIRCUIT_T_BROADCAST) { del = false; for (int level = IS_LEVEL_1; level <= IS_LEVEL_2; level++) { if ((adj->level & level) == 0) continue; if (new_state == ISIS_ADJ_UP) { circuit->upadjcount[level - 1]++; hook_call(isis_adj_state_change_hook, adj); /* update counter & timers for debugging * purposes */ adj->last_flap = time(NULL); adj->flaps++; } else if (new_state == ISIS_ADJ_DOWN) { listnode_delete(circuit->u.bc.adjdb[level - 1], adj); circuit->upadjcount[level - 1]--; if (circuit->upadjcount[level - 1] == 0) isis_tx_queue_clean(circuit->tx_queue); hook_call(isis_adj_state_change_hook, adj); del = true; } if (circuit->u.bc.lan_neighs[level - 1]) { list_delete_all_node( circuit->u.bc.lan_neighs[level - 1]); isis_adj_build_neigh_list( circuit->u.bc.adjdb[level - 1], circuit->u.bc.lan_neighs[level - 1]); } /* On adjacency state change send new pseudo LSP if we * are the DR */ if (circuit->u.bc.is_dr[level - 1]) lsp_regenerate_schedule_pseudo(circuit, level); } if (del) isis_delete_adj(adj); } else if (circuit->circ_type == CIRCUIT_T_P2P) { del = false; for (int level = IS_LEVEL_1; level <= IS_LEVEL_2; level++) { if ((adj->level & level) == 0) continue; if (new_state == ISIS_ADJ_UP) { circuit->upadjcount[level - 1]++; hook_call(isis_adj_state_change_hook, adj); /* update counter & timers for debugging * purposes */ adj->last_flap = time(NULL); adj->flaps++; if (level == IS_LEVEL_1) { thread_add_timer(master, send_l1_csnp, circuit, 0, &circuit->t_send_csnp[0]); } else { thread_add_timer(master, send_l2_csnp, circuit, 0, &circuit->t_send_csnp[1]); } } else if (new_state == ISIS_ADJ_DOWN) { if (adj->circuit->u.p2p.neighbor == adj) adj->circuit->u.p2p.neighbor = NULL; circuit->upadjcount[level - 1]--; if (circuit->upadjcount[level - 1] == 0) isis_tx_queue_clean(circuit->tx_queue); hook_call(isis_adj_state_change_hook, adj); del = true; } } if (del) isis_delete_adj(adj); } } void isis_adj_print(struct isis_adjacency *adj) { struct isis_dynhn *dyn; if (!adj) return; dyn = dynhn_find_by_id(adj->sysid); if (dyn) zlog_debug("%s", dyn->hostname); zlog_debug("SystemId %20s SNPA %s, level %d\nHolding Time %d", sysid_print(adj->sysid), snpa_print(adj->snpa), adj->level, adj->hold_time); if (adj->ipv4_address_count) { zlog_debug("IPv4 Address(es):"); for (unsigned int i = 0; i < adj->ipv4_address_count; i++) zlog_debug("%s", inet_ntoa(adj->ipv4_addresses[i])); } if (adj->ipv6_address_count) { zlog_debug("IPv6 Address(es):"); for (unsigned int i = 0; i < adj->ipv6_address_count; i++) { char buf[INET6_ADDRSTRLEN]; inet_ntop(AF_INET6, &adj->ipv6_addresses[i], buf, sizeof(buf)); zlog_debug("%s", buf); } } zlog_debug("Speaks: %s", nlpid2string(&adj->nlpids)); return; } int isis_adj_expire(struct thread *thread) { struct isis_adjacency *adj; /* * Get the adjacency */ adj = THREAD_ARG(thread); assert(adj); adj->t_expire = NULL; /* trigger the adj expire event */ isis_adj_state_change(adj, ISIS_ADJ_DOWN, "holding time expired"); return 0; } /* * show isis neighbor [detail] */ void isis_adj_print_vty(struct isis_adjacency *adj, struct vty *vty, char detail) { time_t now; struct isis_dynhn *dyn; int level; dyn = dynhn_find_by_id(adj->sysid); if (dyn) vty_out(vty, " %-20s", dyn->hostname); else vty_out(vty, " %-20s", sysid_print(adj->sysid)); if (detail == ISIS_UI_LEVEL_BRIEF) { if (adj->circuit) vty_out(vty, "%-12s", adj->circuit->interface->name); else vty_out(vty, "NULL circuit!"); vty_out(vty, "%-3u", adj->level); /* level */ vty_out(vty, "%-13s", adj_state2string(adj->adj_state)); now = time(NULL); if (adj->last_upd) vty_out(vty, "%-9llu", (unsigned long long)adj->last_upd + adj->hold_time - now); else vty_out(vty, "- "); vty_out(vty, "%-10s", snpa_print(adj->snpa)); vty_out(vty, "\n"); } if (detail == ISIS_UI_LEVEL_DETAIL) { level = adj->level; vty_out(vty, "\n"); if (adj->circuit) vty_out(vty, " Interface: %s", adj->circuit->interface->name); else vty_out(vty, " Interface: NULL circuit"); vty_out(vty, ", Level: %u", adj->level); /* level */ vty_out(vty, ", State: %s", adj_state2string(adj->adj_state)); now = time(NULL); if (adj->last_upd) vty_out(vty, ", Expires in %s", time2string(adj->last_upd + adj->hold_time - now)); else vty_out(vty, ", Expires in %s", time2string(adj->hold_time)); vty_out(vty, "\n"); vty_out(vty, " Adjacency flaps: %u", adj->flaps); vty_out(vty, ", Last: %s ago", time2string(now - adj->last_flap)); vty_out(vty, "\n"); vty_out(vty, " Circuit type: %s", circuit_t2string(adj->circuit_t)); vty_out(vty, ", Speaks: %s", nlpid2string(&adj->nlpids)); vty_out(vty, "\n"); if (adj->mt_count != 1 || adj->mt_set[0] != ISIS_MT_IPV4_UNICAST) { vty_out(vty, " Topologies:\n"); for (unsigned int i = 0; i < adj->mt_count; i++) vty_out(vty, " %s\n", isis_mtid2str(adj->mt_set[i])); } vty_out(vty, " SNPA: %s", snpa_print(adj->snpa)); if (adj->circuit && (adj->circuit->circ_type == CIRCUIT_T_BROADCAST)) { dyn = dynhn_find_by_id(adj->lanid); if (dyn) vty_out(vty, ", LAN id: %s.%02x", dyn->hostname, adj->lanid[ISIS_SYS_ID_LEN]); else vty_out(vty, ", LAN id: %s.%02x", sysid_print(adj->lanid), adj->lanid[ISIS_SYS_ID_LEN]); vty_out(vty, "\n"); vty_out(vty, " LAN Priority: %u", adj->prio[adj->level - 1]); vty_out(vty, ", %s, DIS flaps: %u, Last: %s ago", isis_disflag2string( adj->dis_record[ISIS_LEVELS + level - 1] .dis), adj->dischanges[level - 1], time2string(now - (adj->dis_record[ISIS_LEVELS + level - 1] .last_dis_change))); } vty_out(vty, "\n"); if (adj->area_address_count) { vty_out(vty, " Area Address(es):\n"); for (unsigned int i = 0; i < adj->area_address_count; i++) { vty_out(vty, " %s\n", isonet_print(adj->area_addresses[i] .area_addr, adj->area_addresses[i] .addr_len)); } } if (adj->ipv4_address_count) { vty_out(vty, " IPv4 Address(es):\n"); for (unsigned int i = 0; i < adj->ipv4_address_count; i++) vty_out(vty, " %s\n", inet_ntoa(adj->ipv4_addresses[i])); } if (adj->ipv6_address_count) { vty_out(vty, " IPv6 Address(es):\n"); for (unsigned int i = 0; i < adj->ipv6_address_count; i++) { char buf[INET6_ADDRSTRLEN]; inet_ntop(AF_INET6, &adj->ipv6_addresses[i], buf, sizeof(buf)); vty_out(vty, " %s\n", buf); } } vty_out(vty, "\n"); } return; } void isis_adj_build_neigh_list(struct list *adjdb, struct list *list) { struct isis_adjacency *adj; struct listnode *node; if (!list) { zlog_warn("isis_adj_build_neigh_list(): NULL list"); return; } for (ALL_LIST_ELEMENTS_RO(adjdb, node, adj)) { if (!adj) { zlog_warn("isis_adj_build_neigh_list(): NULL adj"); return; } if ((adj->adj_state == ISIS_ADJ_UP || adj->adj_state == ISIS_ADJ_INITIALIZING)) listnode_add(list, adj->snpa); } return; } void isis_adj_build_up_list(struct list *adjdb, struct list *list) { struct isis_adjacency *adj; struct listnode *node; if (adjdb == NULL) { zlog_warn("isis_adj_build_up_list(): adjacency DB is empty"); return; } if (!list) { zlog_warn("isis_adj_build_up_list(): NULL list"); return; } for (ALL_LIST_ELEMENTS_RO(adjdb, node, adj)) { if (!adj) { zlog_warn("isis_adj_build_up_list(): NULL adj"); return; } if (adj->adj_state == ISIS_ADJ_UP) listnode_add(list, adj); } return; } int isis_adj_usage2levels(enum isis_adj_usage usage) { switch (usage) { case ISIS_ADJ_LEVEL1: return IS_LEVEL_1; case ISIS_ADJ_LEVEL2: return IS_LEVEL_2; case ISIS_ADJ_LEVEL1AND2: return IS_LEVEL_1 | IS_LEVEL_2; default: break; } return 0; } frr-7.2.1/isisd/isis_adjacency.h0000644000000000000000000001105213610377563013447 00000000000000/* * IS-IS Rout(e)ing protocol - isis_adjacency.h * IS-IS adjacency handling * * Copyright (C) 2001,2002 Sampo Saaristo * Tampere University of Technology * Institute of Communications Engineering * * * This program is free software; you can redistribute it and/or modify it * under the terms of the GNU General Public Licenseas 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; see the file COPYING; if not, write to the Free Software * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA */ #ifndef _ZEBRA_ISIS_ADJACENCY_H #define _ZEBRA_ISIS_ADJACENCY_H #include "isisd/isis_tlvs.h" enum isis_adj_usage { ISIS_ADJ_NONE, ISIS_ADJ_LEVEL1, ISIS_ADJ_LEVEL2, ISIS_ADJ_LEVEL1AND2 }; enum isis_system_type { ISIS_SYSTYPE_UNKNOWN, ISIS_SYSTYPE_ES, ISIS_SYSTYPE_IS, ISIS_SYSTYPE_L1_IS, ISIS_SYSTYPE_L2_IS }; enum isis_adj_state { ISIS_ADJ_UNKNOWN, ISIS_ADJ_INITIALIZING, ISIS_ADJ_UP, ISIS_ADJ_DOWN }; /* * we use the following codes to give an indication _why_ * a specific adjacency is up or down */ enum isis_adj_updown_reason { ISIS_ADJ_REASON_SEENSELF, ISIS_ADJ_REASON_AREA_MISMATCH, ISIS_ADJ_REASON_HOLDTIMER_EXPIRED, ISIS_ADJ_REASON_AUTH_FAILED, ISIS_ADJ_REASON_CHECKSUM_FAILED }; #define DIS_RECORDS 8 /* keep the last 8 DIS state changes on record */ struct isis_dis_record { int dis; /* is our neighbor the DIS ? */ time_t last_dis_change; /* timestamp for last dis change */ }; struct bfd_session; struct isis_adjacency { uint8_t snpa[ETH_ALEN]; /* NeighbourSNPAAddress */ uint8_t sysid[ISIS_SYS_ID_LEN]; /* neighbourSystemIdentifier */ uint8_t lanid[ISIS_SYS_ID_LEN + 1]; /* LAN id on bcast circuits */ int dischanges[ISIS_LEVELS]; /* how many DIS changes ? */ /* an array of N levels for M records */ struct isis_dis_record dis_record[DIS_RECORDS * ISIS_LEVELS]; enum isis_adj_state adj_state; /* adjacencyState */ enum isis_adj_usage adj_usage; /* adjacencyUsage */ struct area_addr *area_addresses; /* areaAdressesOfNeighbour */ unsigned int area_address_count; struct nlpids nlpids; /* protocols spoken ... */ struct in_addr *ipv4_addresses; unsigned int ipv4_address_count; struct in_addr router_address; struct in6_addr *ipv6_addresses; unsigned int ipv6_address_count; struct in6_addr router_address6; uint8_t prio[ISIS_LEVELS]; /* priorityOfNeighbour for DIS */ int circuit_t; /* from hello PDU hdr */ int level; /* level (1 or 2) */ enum isis_system_type sys_type; /* neighbourSystemType */ uint16_t hold_time; /* entryRemainingTime */ uint32_t last_upd; uint32_t last_flap; /* last time the adj flapped */ enum isis_threeway_state threeway_state; uint32_t ext_circuit_id; int flaps; /* number of adjacency flaps */ struct thread *t_expire; /* expire after hold_time */ struct isis_circuit *circuit; /* back pointer */ uint16_t *mt_set; /* Topologies this adjacency is valid for */ unsigned int mt_count; /* Number of entries in mt_set */ struct bfd_session *bfd_session; }; struct isis_threeway_adj; struct isis_adjacency *isis_adj_lookup(const uint8_t *sysid, struct list *adjdb); struct isis_adjacency *isis_adj_lookup_snpa(const uint8_t *ssnpa, struct list *adjdb); struct isis_adjacency *isis_new_adj(const uint8_t *id, const uint8_t *snpa, int level, struct isis_circuit *circuit); void isis_delete_adj(void *adj); void isis_adj_process_threeway(struct isis_adjacency *adj, struct isis_threeway_adj *tw_adj, enum isis_adj_usage adj_usage); DECLARE_HOOK(isis_adj_state_change_hook, (struct isis_adjacency *adj), (adj)) void isis_adj_state_change(struct isis_adjacency *adj, enum isis_adj_state state, const char *reason); void isis_adj_print(struct isis_adjacency *adj); int isis_adj_expire(struct thread *thread); void isis_adj_print_vty(struct isis_adjacency *adj, struct vty *vty, char detail); void isis_adj_build_neigh_list(struct list *adjdb, struct list *list); void isis_adj_build_up_list(struct list *adjdb, struct list *list); int isis_adj_usage2levels(enum isis_adj_usage usage); #endif /* ISIS_ADJACENCY_H */ frr-7.2.1/isisd/isis_bfd.c0000644000000000000000000002473013610377563012263 00000000000000/* * IS-IS Rout(e)ing protocol - BFD support * Copyright (C) 2018 Christian Franke * * This program is free software; you can redistribute it and/or modify it * under the terms of the 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; see the file COPYING; if not, write to the Free Software * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA */ #include #include "zclient.h" #include "nexthop.h" #include "bfd.h" #include "lib_errors.h" #include "isisd/isis_bfd.h" #include "isisd/isis_zebra.h" #include "isisd/isis_common.h" #include "isisd/isis_constants.h" #include "isisd/isis_adjacency.h" #include "isisd/isis_circuit.h" #include "isisd/isisd.h" #include "isisd/fabricd.h" DEFINE_MTYPE_STATIC(ISISD, BFD_SESSION, "ISIS BFD Session") struct bfd_session { int family; union g_addr dst_ip; union g_addr src_ip; int status; }; static struct bfd_session *bfd_session_new(int family, union g_addr *dst_ip, union g_addr *src_ip) { struct bfd_session *rv; rv = XCALLOC(MTYPE_BFD_SESSION, sizeof(*rv)); rv->family = family; rv->dst_ip = *dst_ip; rv->src_ip = *src_ip; return rv; } static void bfd_session_free(struct bfd_session **session) { if (!*session) return; XFREE(MTYPE_BFD_SESSION, *session); *session = NULL; } static bool bfd_session_same(const struct bfd_session *session, int family, const union g_addr *src, const union g_addr *dst) { if (session->family != family) return false; switch (session->family) { case AF_INET: if (!IPV4_ADDR_SAME(&session->dst_ip.ipv4, &dst->ipv4)) return false; if (!IPV4_ADDR_SAME(&session->src_ip.ipv4, &src->ipv4)) return false; break; case AF_INET6: if (!IPV6_ADDR_SAME(&session->dst_ip.ipv6, &dst->ipv6)) return false; if (!IPV6_ADDR_SAME(&session->src_ip.ipv6, &src->ipv6)) return false; break; default: flog_err(EC_LIB_DEVELOPMENT, "%s: unknown address-family: %u", __func__, session->family); exit(1); } return true; } static void bfd_adj_event(struct isis_adjacency *adj, struct prefix *dst, int new_status) { if (!adj->bfd_session) return; if (adj->bfd_session->family != dst->family) return; switch (adj->bfd_session->family) { case AF_INET: if (!IPV4_ADDR_SAME(&adj->bfd_session->dst_ip.ipv4, &dst->u.prefix4)) return; break; case AF_INET6: if (!IPV6_ADDR_SAME(&adj->bfd_session->dst_ip.ipv6, &dst->u.prefix6)) return; break; default: flog_err(EC_LIB_DEVELOPMENT, "%s: unknown address-family: %u", __func__, adj->bfd_session->family); exit(1); } int old_status = adj->bfd_session->status; adj->bfd_session->status = new_status; if (old_status == new_status) return; if (isis->debugs & DEBUG_BFD) { char dst_str[INET6_ADDRSTRLEN]; inet_ntop(adj->bfd_session->family, &adj->bfd_session->dst_ip, dst_str, sizeof(dst_str)); zlog_debug("ISIS-BFD: Peer %s on %s changed from %s to %s", dst_str, adj->circuit->interface->name, bfd_get_status_str(old_status), bfd_get_status_str(new_status)); } if (old_status != BFD_STATUS_UP || new_status != BFD_STATUS_DOWN) { return; } isis_adj_state_change(adj, ISIS_ADJ_DOWN, "bfd session went down"); } static int isis_bfd_interface_dest_update(ZAPI_CALLBACK_ARGS) { struct interface *ifp; struct prefix dst_ip; int status; ifp = bfd_get_peer_info(zclient->ibuf, &dst_ip, NULL, &status, NULL, vrf_id); if (!ifp || (dst_ip.family != AF_INET && dst_ip.family != AF_INET6)) return 0; if (isis->debugs & DEBUG_BFD) { char dst_buf[INET6_ADDRSTRLEN]; inet_ntop(dst_ip.family, &dst_ip.u.prefix, dst_buf, sizeof(dst_buf)); zlog_debug("ISIS-BFD: Received update for %s on %s: Changed state to %s", dst_buf, ifp->name, bfd_get_status_str(status)); } struct isis_circuit *circuit = circuit_scan_by_ifp(ifp); if (!circuit) return 0; if (circuit->circ_type == CIRCUIT_T_BROADCAST) { for (int level = ISIS_LEVEL1; level <= ISIS_LEVEL2; level++) { struct list *adjdb = circuit->u.bc.adjdb[level - 1]; struct listnode *node, *nnode; struct isis_adjacency *adj; for (ALL_LIST_ELEMENTS(adjdb, node, nnode, adj)) bfd_adj_event(adj, &dst_ip, status); } } else if (circuit->circ_type == CIRCUIT_T_P2P) { if (circuit->u.p2p.neighbor) { bfd_adj_event(circuit->u.p2p.neighbor, &dst_ip, status); } } return 0; } static int isis_bfd_nbr_replay(ZAPI_CALLBACK_ARGS) { bfd_client_sendmsg(zclient, ZEBRA_BFD_CLIENT_REGISTER, vrf_id); struct listnode *anode; struct isis_area *area; if (isis->debugs & DEBUG_BFD) zlog_debug("ISIS-BFD: Got neighbor replay request, resending neighbors."); for (ALL_LIST_ELEMENTS_RO(isis->area_list, anode, area)) { struct listnode *cnode; struct isis_circuit *circuit; for (ALL_LIST_ELEMENTS_RO(area->circuit_list, cnode, circuit)) isis_bfd_circuit_cmd(circuit, ZEBRA_BFD_DEST_UPDATE); } if (isis->debugs & DEBUG_BFD) zlog_debug("ISIS-BFD: Done with replay."); return 0; } static void (*orig_zebra_connected)(struct zclient *); static void isis_bfd_zebra_connected(struct zclient *zclient) { if (orig_zebra_connected) orig_zebra_connected(zclient); bfd_client_sendmsg(zclient, ZEBRA_BFD_CLIENT_REGISTER, VRF_DEFAULT); } static void bfd_debug(int family, union g_addr *dst, union g_addr *src, const char *interface, int command) { if (!(isis->debugs & DEBUG_BFD)) return; char dst_str[INET6_ADDRSTRLEN]; char src_str[INET6_ADDRSTRLEN]; inet_ntop(family, dst, dst_str, sizeof(dst_str)); inet_ntop(family, src, src_str, sizeof(src_str)); const char *command_str; switch (command) { case ZEBRA_BFD_DEST_REGISTER: command_str = "Register"; break; case ZEBRA_BFD_DEST_DEREGISTER: command_str = "Deregister"; break; case ZEBRA_BFD_DEST_UPDATE: command_str = "Update"; break; default: command_str = "Unknown-Cmd"; break; } zlog_debug("ISIS-BFD: %s peer %s on %s (src %s)", command_str, dst_str, interface, src_str); } static void bfd_handle_adj_down(struct isis_adjacency *adj) { if (!adj->bfd_session) return; bfd_debug(adj->bfd_session->family, &adj->bfd_session->dst_ip, &adj->bfd_session->src_ip, adj->circuit->interface->name, ZEBRA_BFD_DEST_DEREGISTER); bfd_peer_sendmsg(zclient, NULL, adj->bfd_session->family, &adj->bfd_session->dst_ip, &adj->bfd_session->src_ip, adj->circuit->interface->name, 0, /* ttl */ 0, /* multihop */ 1, /* control plane independent bit is on */ ZEBRA_BFD_DEST_DEREGISTER, 0, /* set_flag */ VRF_DEFAULT); bfd_session_free(&adj->bfd_session); } static void bfd_handle_adj_up(struct isis_adjacency *adj, int command) { struct isis_circuit *circuit = adj->circuit; int family; union g_addr dst_ip; union g_addr src_ip; struct list *local_ips; struct prefix *local_ip; if (!circuit->bfd_info) goto out; /* * If IS-IS is enabled for both IPv4 and IPv6 on the circuit, prefer * creating a BFD session over IPv6. */ if (circuit->ipv6_router && adj->ipv6_address_count) { family = AF_INET6; dst_ip.ipv6 = adj->ipv6_addresses[0]; local_ips = circuit->ipv6_link; if (!local_ips || list_isempty(local_ips)) goto out; local_ip = listgetdata(listhead(local_ips)); src_ip.ipv6 = local_ip->u.prefix6; } else if (circuit->ip_router && adj->ipv4_address_count) { family = AF_INET; dst_ip.ipv4 = adj->ipv4_addresses[0]; local_ips = fabricd_ip_addrs(adj->circuit); if (!local_ips || list_isempty(local_ips)) goto out; local_ip = listgetdata(listhead(local_ips)); src_ip.ipv4 = local_ip->u.prefix4; } else goto out; if (adj->bfd_session) { if (bfd_session_same(adj->bfd_session, family, &src_ip, &dst_ip)) bfd_handle_adj_down(adj); } if (!adj->bfd_session) adj->bfd_session = bfd_session_new(family, &dst_ip, &src_ip); bfd_debug(adj->bfd_session->family, &adj->bfd_session->dst_ip, &adj->bfd_session->src_ip, circuit->interface->name, command); bfd_peer_sendmsg(zclient, circuit->bfd_info, adj->bfd_session->family, &adj->bfd_session->dst_ip, &adj->bfd_session->src_ip, circuit->interface->name, 0, /* ttl */ 0, /* multihop */ 1, /* control plane independent bit is on */ command, 0, /* set flag */ VRF_DEFAULT); return; out: bfd_handle_adj_down(adj); } static int bfd_handle_adj_state_change(struct isis_adjacency *adj) { if (adj->adj_state == ISIS_ADJ_UP) bfd_handle_adj_up(adj, ZEBRA_BFD_DEST_REGISTER); else bfd_handle_adj_down(adj); return 0; } static void bfd_adj_cmd(struct isis_adjacency *adj, int command) { if (adj->adj_state == ISIS_ADJ_UP && command != ZEBRA_BFD_DEST_DEREGISTER) { bfd_handle_adj_up(adj, command); } else { bfd_handle_adj_down(adj); } } void isis_bfd_circuit_cmd(struct isis_circuit *circuit, int command) { switch (circuit->circ_type) { case CIRCUIT_T_BROADCAST: for (int level = ISIS_LEVEL1; level <= ISIS_LEVEL2; level++) { struct list *adjdb = circuit->u.bc.adjdb[level - 1]; struct listnode *node; struct isis_adjacency *adj; for (ALL_LIST_ELEMENTS_RO(adjdb, node, adj)) bfd_adj_cmd(adj, command); } break; case CIRCUIT_T_P2P: if (circuit->u.p2p.neighbor) bfd_adj_cmd(circuit->u.p2p.neighbor, command); break; default: break; } } void isis_bfd_circuit_param_set(struct isis_circuit *circuit, uint32_t min_rx, uint32_t min_tx, uint32_t detect_mult, int defaults) { int command = 0; bfd_set_param(&circuit->bfd_info, min_rx, min_tx, detect_mult, defaults, &command); if (command) isis_bfd_circuit_cmd(circuit, command); } static int bfd_circuit_write_settings(struct isis_circuit *circuit, struct vty *vty) { struct bfd_info *bfd_info = circuit->bfd_info; if (!bfd_info) return 0; vty_out(vty, " %s bfd\n", PROTO_NAME); return 1; } void isis_bfd_init(void) { bfd_gbl_init(); orig_zebra_connected = zclient->zebra_connected; zclient->zebra_connected = isis_bfd_zebra_connected; zclient->interface_bfd_dest_update = isis_bfd_interface_dest_update; zclient->bfd_dest_replay = isis_bfd_nbr_replay; hook_register(isis_adj_state_change_hook, bfd_handle_adj_state_change); hook_register(isis_circuit_config_write, bfd_circuit_write_settings); } frr-7.2.1/isisd/isis_bfd.h0000644000000000000000000000215513610377563012265 00000000000000/* * IS-IS Rout(e)ing protocol - BFD support * Copyright (C) 2018 Christian Franke * * This program is free software; you can redistribute it and/or modify it * under the terms of the 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; see the file COPYING; if not, write to the Free Software * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA */ #ifndef ISIS_BFD_H #define ISIS_BFD_H struct isis_circuit; void isis_bfd_circuit_cmd(struct isis_circuit *circuit, int command); void isis_bfd_circuit_param_set(struct isis_circuit *circuit, uint32_t min_rx, uint32_t min_tx, uint32_t detect_mult, int defaults); void isis_bfd_init(void); #endif frr-7.2.1/isisd/isis_bpf.c0000644000000000000000000002041213610377563012270 00000000000000/* * IS-IS Rout(e)ing protocol - isis_bpf.c * * Copyright (C) 2001,2002 Sampo Saaristo * Tampere University of Technology * Institute of Communications Engineering * * This program is free software; you can redistribute it and/or modify it * under the terms of the GNU General Public Licenseas 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; see the file COPYING; if not, write to the Free Software * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA */ #include #if ISIS_METHOD == ISIS_METHOD_BPF #include #include #include #include #include #include "log.h" #include "network.h" #include "stream.h" #include "if.h" #include "lib_errors.h" #include "isisd/isis_constants.h" #include "isisd/isis_common.h" #include "isisd/isis_circuit.h" #include "isisd/isis_flags.h" #include "isisd/isisd.h" #include "isisd/isis_constants.h" #include "isisd/isis_circuit.h" #include "isisd/isis_network.h" #include "isisd/isis_pdu.h" #include "privs.h" struct bpf_insn llcfilter[] = { BPF_STMT(BPF_LD + BPF_B + BPF_ABS, ETHER_HDR_LEN), /* check first byte */ BPF_JUMP(BPF_JMP + BPF_JEQ + BPF_K, ISO_SAP, 0, 5), BPF_STMT(BPF_LD + BPF_B + BPF_ABS, ETHER_HDR_LEN + 1), BPF_JUMP(BPF_JMP + BPF_JEQ + BPF_K, ISO_SAP, 0, 3), /* check second byte */ BPF_STMT(BPF_LD + BPF_B + BPF_ABS, ETHER_HDR_LEN + 2), BPF_JUMP(BPF_JMP + BPF_JEQ + BPF_K, 0x03, 0, 1), /* check third byte */ BPF_STMT(BPF_RET + BPF_K, (unsigned int)-1), BPF_STMT(BPF_RET + BPF_K, 0)}; unsigned int readblen = 0; uint8_t *readbuff = NULL; /* * Table 9 - Architectural constants for use with ISO 8802 subnetworks * ISO 10589 - 8.4.8 */ uint8_t ALL_L1_ISS[6] = {0x01, 0x80, 0xC2, 0x00, 0x00, 0x14}; uint8_t ALL_L2_ISS[6] = {0x01, 0x80, 0xC2, 0x00, 0x00, 0x15}; uint8_t ALL_ISS[6] = {0x09, 0x00, 0x2B, 0x00, 0x00, 0x05}; uint8_t ALL_ESS[6] = {0x09, 0x00, 0x2B, 0x00, 0x00, 0x04}; static char sock_buff[8192]; static int open_bpf_dev(struct isis_circuit *circuit) { int i = 0, fd; char bpfdev[128]; struct ifreq ifr; unsigned int blen, immediate; #ifdef BIOCSSEESENT unsigned int seesent; #endif struct timeval timeout; struct bpf_program bpf_prog; do { (void)snprintf(bpfdev, sizeof(bpfdev), "/dev/bpf%d", i++); fd = open(bpfdev, O_RDWR); } while (fd < 0 && errno == EBUSY); if (fd < 0) { zlog_warn("open_bpf_dev(): failed to create bpf socket: %s", safe_strerror(errno)); return ISIS_WARNING; } zlog_debug("Opened BPF device %s", bpfdev); memcpy(ifr.ifr_name, circuit->interface->name, sizeof(ifr.ifr_name)); if (ioctl(fd, BIOCSETIF, (caddr_t)&ifr) < 0) { zlog_warn("open_bpf_dev(): failed to bind to interface: %s", safe_strerror(errno)); return ISIS_WARNING; } if (ioctl(fd, BIOCGBLEN, (caddr_t)&blen) < 0) { zlog_warn("failed to get BPF buffer len"); blen = circuit->interface->mtu; } readblen = blen; if (readbuff == NULL) readbuff = malloc(blen); zlog_debug("BPF buffer len = %u", blen); /* BPF(4): reads return immediately upon packet reception. * Otherwise, a read will block until either the kernel * buffer becomes full or a timeout occurs. */ immediate = 1; if (ioctl(fd, BIOCIMMEDIATE, (caddr_t)&immediate) < 0) { zlog_warn("failed to set BPF dev to immediate mode"); } #ifdef BIOCSSEESENT /* * We want to see only incoming packets */ seesent = 0; if (ioctl(fd, BIOCSSEESENT, (caddr_t)&seesent) < 0) { zlog_warn("failed to set BPF dev to incoming only mode"); } #endif /* * ...but all of them */ if (ioctl(fd, BIOCPROMISC) < 0) { zlog_warn("failed to set BPF dev to promiscuous mode"); } /* * If the buffer length is smaller than our mtu, lets try to increase it */ if (blen < circuit->interface->mtu) { if (ioctl(fd, BIOCSBLEN, &circuit->interface->mtu) < 0) { zlog_warn("failed to set BPF buffer len (%u to %u)", blen, circuit->interface->mtu); } } /* * Set a timeout parameter - hope this helps select() */ timeout.tv_sec = 600; timeout.tv_usec = 0; if (ioctl(fd, BIOCSRTIMEOUT, (caddr_t)&timeout) < 0) { zlog_warn("failed to set BPF device timeout"); } /* * And set the filter */ memset(&bpf_prog, 0, sizeof(struct bpf_program)); bpf_prog.bf_len = 8; bpf_prog.bf_insns = &(llcfilter[0]); if (ioctl(fd, BIOCSETF, (caddr_t)&bpf_prog) < 0) { zlog_warn("open_bpf_dev(): failed to install filter: %s", safe_strerror(errno)); return ISIS_WARNING; } assert(fd > 0); circuit->fd = fd; return ISIS_OK; } /* * Create the socket and set the tx/rx funcs */ int isis_sock_init(struct isis_circuit *circuit) { int retval = ISIS_OK; frr_with_privs(&isisd_privs) { retval = open_bpf_dev(circuit); if (retval != ISIS_OK) { zlog_warn("%s: could not initialize the socket", __func__); break; } if (if_is_broadcast(circuit->interface)) { circuit->tx = isis_send_pdu_bcast; circuit->rx = isis_recv_pdu_bcast; } else { zlog_warn("isis_sock_init(): unknown circuit type"); retval = ISIS_WARNING; break; } } return retval; } int isis_recv_pdu_bcast(struct isis_circuit *circuit, uint8_t *ssnpa) { int bytesread = 0, bytestoread, offset, one = 1; uint8_t *buff_ptr; struct bpf_hdr *bpf_hdr; assert(circuit->fd > 0); if (ioctl(circuit->fd, FIONREAD, (caddr_t)&bytestoread) < 0) { zlog_warn("ioctl() FIONREAD failed: %s", safe_strerror(errno)); } if (bytestoread) { bytesread = read(circuit->fd, readbuff, readblen); } if (bytesread < 0) { zlog_warn("isis_recv_pdu_bcast(): read() failed: %s", safe_strerror(errno)); return ISIS_WARNING; } if (bytesread == 0) return ISIS_WARNING; buff_ptr = readbuff; while (buff_ptr < readbuff + bytesread) { bpf_hdr = (struct bpf_hdr *) buff_ptr; assert(bpf_hdr->bh_caplen == bpf_hdr->bh_datalen); offset = bpf_hdr->bh_hdrlen + LLC_LEN + ETHER_HDR_LEN; /* then we lose the BPF, LLC and ethernet headers */ stream_write(circuit->rcv_stream, buff_ptr + offset, bpf_hdr->bh_caplen - LLC_LEN - ETHER_HDR_LEN); stream_set_getp(circuit->rcv_stream, 0); memcpy(ssnpa, buff_ptr + bpf_hdr->bh_hdrlen + ETHER_ADDR_LEN, ETHER_ADDR_LEN); isis_handle_pdu(circuit, ssnpa); stream_reset(circuit->rcv_stream); buff_ptr += BPF_WORDALIGN(bpf_hdr->bh_hdrlen + bpf_hdr->bh_datalen); } if (ioctl(circuit->fd, BIOCFLUSH, &one) < 0) zlog_warn("Flushing failed: %s", safe_strerror(errno)); return ISIS_OK; } int isis_send_pdu_bcast(struct isis_circuit *circuit, int level) { struct ether_header *eth; ssize_t written; size_t buflen; buflen = stream_get_endp(circuit->snd_stream) + LLC_LEN + ETHER_HDR_LEN; if (buflen > sizeof(sock_buff)) { zlog_warn( "isis_send_pdu_bcast: sock_buff size %zu is less than " "output pdu size %zu on circuit %s", sizeof(sock_buff), buflen, circuit->interface->name); return ISIS_WARNING; } stream_set_getp(circuit->snd_stream, 0); /* * First the eth header */ eth = (struct ether_header *)sock_buff; if (level == 1) memcpy(eth->ether_dhost, ALL_L1_ISS, ETH_ALEN); else memcpy(eth->ether_dhost, ALL_L2_ISS, ETH_ALEN); memcpy(eth->ether_shost, circuit->u.bc.snpa, ETH_ALEN); size_t frame_size = stream_get_endp(circuit->snd_stream) + LLC_LEN; eth->ether_type = htons(isis_ethertype(frame_size)); /* * Then the LLC */ sock_buff[ETHER_HDR_LEN] = ISO_SAP; sock_buff[ETHER_HDR_LEN + 1] = ISO_SAP; sock_buff[ETHER_HDR_LEN + 2] = 0x03; /* then we copy the data */ memcpy(sock_buff + (LLC_LEN + ETHER_HDR_LEN), circuit->snd_stream->data, stream_get_endp(circuit->snd_stream)); /* now we can send this */ written = write(circuit->fd, sock_buff, buflen); if (written < 0) { zlog_warn("IS-IS bpf: could not transmit packet on %s: %s", circuit->interface->name, safe_strerror(errno)); if (ERRNO_IO_RETRY(errno)) return ISIS_WARNING; return ISIS_ERROR; } return ISIS_OK; } #endif /* ISIS_METHOD == ISIS_METHOD_BPF */ frr-7.2.1/isisd/isis_circuit.c0000644000000000000000000011202413610377563013164 00000000000000/* * IS-IS Rout(e)ing protocol - isis_circuit.h * * Copyright (C) 2001,2002 Sampo Saaristo * Tampere University of Technology * Institute of Communications Engineering * * This program is free software; you can redistribute it and/or modify it * under the terms of the GNU General Public Licenseas 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; see the file COPYING; if not, write to the Free Software * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA */ #include #ifdef GNU_LINUX #include #else #include #endif #include "log.h" #include "memory.h" #include "vrf.h" #include "if.h" #include "linklist.h" #include "command.h" #include "thread.h" #include "vty.h" #include "hash.h" #include "prefix.h" #include "stream.h" #include "qobj.h" #include "lib/northbound_cli.h" #include "isisd/isis_constants.h" #include "isisd/isis_common.h" #include "isisd/isis_flags.h" #include "isisd/isis_circuit.h" #include "isisd/isis_lsp.h" #include "isisd/isis_pdu.h" #include "isisd/isis_network.h" #include "isisd/isis_misc.h" #include "isisd/isis_constants.h" #include "isisd/isis_adjacency.h" #include "isisd/isis_dr.h" #include "isisd/isisd.h" #include "isisd/isis_csm.h" #include "isisd/isis_events.h" #include "isisd/isis_te.h" #include "isisd/isis_mt.h" #include "isisd/isis_errors.h" #include "isisd/isis_tx_queue.h" DEFINE_QOBJ_TYPE(isis_circuit) /* * Prototypes. */ int isis_interface_config_write(struct vty *); int isis_if_new_hook(struct interface *); int isis_if_delete_hook(struct interface *); struct isis_circuit *isis_circuit_new(void) { struct isis_circuit *circuit; int i; circuit = XCALLOC(MTYPE_ISIS_CIRCUIT, sizeof(struct isis_circuit)); /* * Default values */ #ifndef FABRICD circuit->is_type = yang_get_default_enum( "/frr-interface:lib/interface/frr-isisd:isis/circuit-type"); circuit->flags = 0; circuit->pad_hellos = yang_get_default_bool( "/frr-interface:lib/interface/frr-isisd:isis/hello/padding"); circuit->hello_interval[0] = yang_get_default_uint32( "/frr-interface:lib/interface/frr-isisd:isis/hello/interval/level-1"); circuit->hello_interval[1] = yang_get_default_uint32( "/frr-interface:lib/interface/frr-isisd:isis/hello/interval/level-2"); circuit->hello_multiplier[0] = yang_get_default_uint32( "/frr-interface:lib/interface/frr-isisd:isis/hello/multiplier/level-1"); circuit->hello_multiplier[1] = yang_get_default_uint32( "/frr-interface:lib/interface/frr-isisd:isis/hello/multiplier/level-2"); circuit->csnp_interval[0] = yang_get_default_uint16( "/frr-interface:lib/interface/frr-isisd:isis/csnp-interval/level-1"); circuit->csnp_interval[1] = yang_get_default_uint16( "/frr-interface:lib/interface/frr-isisd:isis/csnp-interval/level-2"); circuit->psnp_interval[0] = yang_get_default_uint16( "/frr-interface:lib/interface/frr-isisd:isis/psnp-interval/level-1"); circuit->psnp_interval[1] = yang_get_default_uint16( "/frr-interface:lib/interface/frr-isisd:isis/psnp-interval/level-2"); circuit->priority[0] = yang_get_default_uint8( "/frr-interface:lib/interface/frr-isisd:isis/priority/level-1"); circuit->priority[1] = yang_get_default_uint8( "/frr-interface:lib/interface/frr-isisd:isis/priority/level-2"); circuit->metric[0] = yang_get_default_uint32( "/frr-interface:lib/interface/frr-isisd:isis/metric/level-1"); circuit->metric[1] = yang_get_default_uint32( "/frr-interface:lib/interface/frr-isisd:isis/metric/level-2"); circuit->te_metric[0] = yang_get_default_uint32( "/frr-interface:lib/interface/frr-isisd:isis/metric/level-1"); circuit->te_metric[1] = yang_get_default_uint32( "/frr-interface:lib/interface/frr-isisd:isis/metric/level-2"); for (i = 0; i < 2; i++) { circuit->level_arg[i].level = i + 1; circuit->level_arg[i].circuit = circuit; } #else circuit->is_type = IS_LEVEL_1_AND_2; circuit->flags = 0; circuit->pad_hellos = 1; for (i = 0; i < 2; i++) { circuit->hello_interval[i] = DEFAULT_HELLO_INTERVAL; circuit->hello_multiplier[i] = DEFAULT_HELLO_MULTIPLIER; circuit->csnp_interval[i] = DEFAULT_CSNP_INTERVAL; circuit->psnp_interval[i] = DEFAULT_PSNP_INTERVAL; circuit->priority[i] = DEFAULT_PRIORITY; circuit->metric[i] = DEFAULT_CIRCUIT_METRIC; circuit->te_metric[i] = DEFAULT_CIRCUIT_METRIC; circuit->level_arg[i].level = i + 1; circuit->level_arg[i].circuit = circuit; } #endif /* ifndef FABRICD */ circuit->mtc = mpls_te_circuit_new(); circuit_mt_init(circuit); QOBJ_REG(circuit, isis_circuit); return circuit; } void isis_circuit_del(struct isis_circuit *circuit) { if (!circuit) return; QOBJ_UNREG(circuit); isis_circuit_if_unbind(circuit, circuit->interface); circuit_mt_finish(circuit); /* and lastly the circuit itself */ XFREE(MTYPE_ISIS_CIRCUIT, circuit); return; } void isis_circuit_configure(struct isis_circuit *circuit, struct isis_area *area) { assert(area); circuit->area = area; /* * Whenever the is-type of an area is changed, the is-type of each * circuit * in that area is updated to a non-empty subset of the area is-type. * Inversely, when configuring a new circuit, this property should be * ensured as well. */ if (area->is_type != IS_LEVEL_1_AND_2) circuit->is_type = area->is_type; /* * Add the circuit into area */ listnode_add(area->circuit_list, circuit); circuit->idx = flags_get_index(&area->flags); return; } void isis_circuit_deconfigure(struct isis_circuit *circuit, struct isis_area *area) { /* Free the index of SRM and SSN flags */ flags_free_index(&area->flags, circuit->idx); circuit->idx = 0; /* Remove circuit from area */ assert(circuit->area == area); listnode_delete(area->circuit_list, circuit); circuit->area = NULL; return; } struct isis_circuit *circuit_lookup_by_ifp(struct interface *ifp, struct list *list) { struct isis_circuit *circuit = NULL; struct listnode *node; if (!list) return NULL; for (ALL_LIST_ELEMENTS_RO(list, node, circuit)) if (circuit->interface == ifp) { assert(ifp->info == circuit); return circuit; } return NULL; } struct isis_circuit *circuit_scan_by_ifp(struct interface *ifp) { struct isis_area *area; struct listnode *node; struct isis_circuit *circuit; if (ifp->info) return (struct isis_circuit *)ifp->info; if (isis->area_list) { for (ALL_LIST_ELEMENTS_RO(isis->area_list, node, area)) { circuit = circuit_lookup_by_ifp(ifp, area->circuit_list); if (circuit) return circuit; } } return circuit_lookup_by_ifp(ifp, isis->init_circ_list); } void isis_circuit_add_addr(struct isis_circuit *circuit, struct connected *connected) { struct listnode *node; struct prefix_ipv4 *ipv4; #if defined(EXTREME_DEBUG) char buf[PREFIX2STR_BUFFER]; #endif struct prefix_ipv6 *ipv6; if (connected->address->family == AF_INET) { uint32_t addr = connected->address->u.prefix4.s_addr; addr = ntohl(addr); if (IPV4_NET0(addr) || IPV4_NET127(addr) || IN_CLASSD(addr) || IPV4_LINKLOCAL(addr)) return; for (ALL_LIST_ELEMENTS_RO(circuit->ip_addrs, node, ipv4)) if (prefix_same((struct prefix *)ipv4, connected->address)) return; ipv4 = prefix_ipv4_new(); ipv4->prefixlen = connected->address->prefixlen; ipv4->prefix = connected->address->u.prefix4; listnode_add(circuit->ip_addrs, ipv4); /* Update MPLS TE Local IP address parameter */ set_circuitparams_local_ipaddr(circuit->mtc, ipv4->prefix); if (circuit->area) lsp_regenerate_schedule(circuit->area, circuit->is_type, 0); #ifdef EXTREME_DEBUG prefix2str(connected->address, buf, sizeof(buf)); zlog_debug("Added IP address %s to circuit %s", buf, circuit->interface->name); #endif /* EXTREME_DEBUG */ } if (connected->address->family == AF_INET6) { if (IN6_IS_ADDR_LOOPBACK(&connected->address->u.prefix6)) return; for (ALL_LIST_ELEMENTS_RO(circuit->ipv6_link, node, ipv6)) if (prefix_same((struct prefix *)ipv6, connected->address)) return; for (ALL_LIST_ELEMENTS_RO(circuit->ipv6_non_link, node, ipv6)) if (prefix_same((struct prefix *)ipv6, connected->address)) return; ipv6 = prefix_ipv6_new(); ipv6->prefixlen = connected->address->prefixlen; ipv6->prefix = connected->address->u.prefix6; if (IN6_IS_ADDR_LINKLOCAL(&ipv6->prefix)) listnode_add(circuit->ipv6_link, ipv6); else listnode_add(circuit->ipv6_non_link, ipv6); if (circuit->area) lsp_regenerate_schedule(circuit->area, circuit->is_type, 0); #ifdef EXTREME_DEBUG prefix2str(connected->address, buf, sizeof(buf)); zlog_debug("Added IPv6 address %s to circuit %s", buf, circuit->interface->name); #endif /* EXTREME_DEBUG */ } return; } void isis_circuit_del_addr(struct isis_circuit *circuit, struct connected *connected) { struct prefix_ipv4 *ipv4, *ip = NULL; struct listnode *node; char buf[PREFIX2STR_BUFFER]; struct prefix_ipv6 *ipv6, *ip6 = NULL; int found = 0; if (connected->address->family == AF_INET) { ipv4 = prefix_ipv4_new(); ipv4->prefixlen = connected->address->prefixlen; ipv4->prefix = connected->address->u.prefix4; for (ALL_LIST_ELEMENTS_RO(circuit->ip_addrs, node, ip)) if (prefix_same((struct prefix *)ip, (struct prefix *)ipv4)) break; if (ip) { listnode_delete(circuit->ip_addrs, ip); prefix_ipv4_free(ip); if (circuit->area) lsp_regenerate_schedule(circuit->area, circuit->is_type, 0); } else { prefix2str(connected->address, buf, sizeof(buf)); zlog_warn( "Nonexistent ip address %s removal attempt from \ circuit %s", buf, circuit->interface->name); zlog_warn("Current ip addresses on %s:", circuit->interface->name); for (ALL_LIST_ELEMENTS_RO(circuit->ip_addrs, node, ip)) { prefix2str(ip, buf, sizeof(buf)); zlog_warn(" %s", buf); } zlog_warn("End of addresses"); } prefix_ipv4_free(ipv4); } if (connected->address->family == AF_INET6) { ipv6 = prefix_ipv6_new(); ipv6->prefixlen = connected->address->prefixlen; ipv6->prefix = connected->address->u.prefix6; if (IN6_IS_ADDR_LINKLOCAL(&ipv6->prefix)) { for (ALL_LIST_ELEMENTS_RO(circuit->ipv6_link, node, ip6)) { if (prefix_same((struct prefix *)ip6, (struct prefix *)ipv6)) break; } if (ip6) { listnode_delete(circuit->ipv6_link, ip6); prefix_ipv6_free(ip6); found = 1; } } else { for (ALL_LIST_ELEMENTS_RO(circuit->ipv6_non_link, node, ip6)) { if (prefix_same((struct prefix *)ip6, (struct prefix *)ipv6)) break; } if (ip6) { listnode_delete(circuit->ipv6_non_link, ip6); prefix_ipv6_free(ip6); found = 1; } } if (!found) { prefix2str(connected->address, buf, sizeof(buf)); zlog_warn( "Nonexistent ip address %s removal attempt from \ circuit %s", buf, circuit->interface->name); zlog_warn("Current ip addresses on %s:", circuit->interface->name); for (ALL_LIST_ELEMENTS_RO(circuit->ipv6_link, node, ip6)) { prefix2str((struct prefix *)ip6, (char *)buf, sizeof(buf)); zlog_warn(" %s", buf); } zlog_warn(" -----"); for (ALL_LIST_ELEMENTS_RO(circuit->ipv6_non_link, node, ip6)) { prefix2str((struct prefix *)ip6, (char *)buf, sizeof(buf)); zlog_warn(" %s", buf); } zlog_warn("End of addresses"); } else if (circuit->area) lsp_regenerate_schedule(circuit->area, circuit->is_type, 0); prefix_ipv6_free(ipv6); } return; } static uint8_t isis_circuit_id_gen(struct isis *isis, struct interface *ifp) { /* Circuit ids MUST be unique for any broadcast circuits. Otherwise, * Pseudo-Node LSPs cannot be generated correctly. * * Currently, allocate one circuit ID for any circuit, limiting the total * numer of circuits IS-IS can run on to 255. * * We should revisit this when implementing 3-way adjacencies for p2p, since * we then have extended interface IDs available. */ uint8_t id = ifp->ifindex; unsigned int i; for (i = 0; i < 256; i++) { if (id && !_ISIS_CHECK_FLAG(isis->circuit_ids_used, id)) break; id++; } if (i == 256) { zlog_warn("Could not allocate a circuit id for '%s'", ifp->name); return 0; } _ISIS_SET_FLAG(isis->circuit_ids_used, id); return id; } void isis_circuit_if_add(struct isis_circuit *circuit, struct interface *ifp) { struct listnode *node, *nnode; struct connected *conn; isis_circuit_if_bind(circuit, ifp); if (if_is_broadcast(ifp)) { if (fabricd || circuit->circ_type_config == CIRCUIT_T_P2P) circuit->circ_type = CIRCUIT_T_P2P; else circuit->circ_type = CIRCUIT_T_BROADCAST; } else if (if_is_pointopoint(ifp)) { circuit->circ_type = CIRCUIT_T_P2P; } else if (if_is_loopback(ifp)) { circuit->circ_type = CIRCUIT_T_LOOPBACK; circuit->is_passive = 1; } else { /* It's normal in case of loopback etc. */ if (isis->debugs & DEBUG_EVENTS) zlog_debug("isis_circuit_if_add: unsupported media"); circuit->circ_type = CIRCUIT_T_UNKNOWN; } circuit->ip_addrs = list_new(); circuit->ipv6_link = list_new(); circuit->ipv6_non_link = list_new(); for (ALL_LIST_ELEMENTS(ifp->connected, node, nnode, conn)) isis_circuit_add_addr(circuit, conn); } void isis_circuit_if_del(struct isis_circuit *circuit, struct interface *ifp) { struct listnode *node, *nnode; struct connected *conn; assert(circuit->interface == ifp); /* destroy addresses */ for (ALL_LIST_ELEMENTS(ifp->connected, node, nnode, conn)) isis_circuit_del_addr(circuit, conn); if (circuit->ip_addrs) { assert(listcount(circuit->ip_addrs) == 0); list_delete(&circuit->ip_addrs); } if (circuit->ipv6_link) { assert(listcount(circuit->ipv6_link) == 0); list_delete(&circuit->ipv6_link); } if (circuit->ipv6_non_link) { assert(listcount(circuit->ipv6_non_link) == 0); list_delete(&circuit->ipv6_non_link); } circuit->circ_type = CIRCUIT_T_UNKNOWN; } void isis_circuit_if_bind(struct isis_circuit *circuit, struct interface *ifp) { assert(circuit != NULL); assert(ifp != NULL); if (circuit->interface) assert(circuit->interface == ifp); else circuit->interface = ifp; if (ifp->info) assert(ifp->info == circuit); else ifp->info = circuit; isis_link_params_update(circuit, ifp); } void isis_circuit_if_unbind(struct isis_circuit *circuit, struct interface *ifp) { assert(circuit != NULL); assert(ifp != NULL); assert(circuit->interface == ifp); assert(ifp->info == circuit); circuit->interface = NULL; ifp->info = NULL; } static void isis_circuit_update_all_srmflags(struct isis_circuit *circuit, int is_set) { struct isis_area *area; struct isis_lsp *lsp; int level; assert(circuit); area = circuit->area; assert(area); for (level = ISIS_LEVEL1; level <= ISIS_LEVEL2; level++) { if (!(level & circuit->is_type)) continue; if (!lspdb_count(&area->lspdb[level - 1])) continue; frr_each (lspdb, &area->lspdb[level - 1], lsp) { if (is_set) { isis_tx_queue_add(circuit->tx_queue, lsp, TX_LSP_NORMAL); } else { isis_tx_queue_del(circuit->tx_queue, lsp); } } } } size_t isis_circuit_pdu_size(struct isis_circuit *circuit) { return ISO_MTU(circuit); } void isis_circuit_stream(struct isis_circuit *circuit, struct stream **stream) { size_t stream_size = isis_circuit_pdu_size(circuit); if (!*stream) { *stream = stream_new(stream_size); } else { if (STREAM_SIZE(*stream) != stream_size) stream_resize_inplace(stream, stream_size); stream_reset(*stream); } } void isis_circuit_prepare(struct isis_circuit *circuit) { #if ISIS_METHOD != ISIS_METHOD_DLPI thread_add_read(master, isis_receive, circuit, circuit->fd, &circuit->t_read); #else thread_add_timer_msec(master, isis_receive, circuit, listcount(circuit->area->circuit_list) * 100, &circuit->t_read); #endif } int isis_circuit_up(struct isis_circuit *circuit) { int retv; /* Set the flags for all the lsps of the circuit. */ isis_circuit_update_all_srmflags(circuit, 1); if (circuit->state == C_STATE_UP) return ISIS_OK; if (circuit->is_passive) return ISIS_OK; if (circuit->area->lsp_mtu > isis_circuit_pdu_size(circuit)) { flog_err( EC_ISIS_CONFIG, "Interface MTU %zu on %s is too low to support area lsp mtu %u!", isis_circuit_pdu_size(circuit), circuit->interface->name, circuit->area->lsp_mtu); isis_circuit_update_all_srmflags(circuit, 0); return ISIS_ERROR; } if (circuit->circ_type == CIRCUIT_T_BROADCAST) { circuit->circuit_id = isis_circuit_id_gen(isis, circuit->interface); if (!circuit->circuit_id) { flog_err( EC_ISIS_CONFIG, "There are already 255 broadcast circuits active!"); return ISIS_ERROR; } /* * Get the Hardware Address */ if (circuit->interface->hw_addr_len != ETH_ALEN) { zlog_warn("unsupported link layer"); } else { memcpy(circuit->u.bc.snpa, circuit->interface->hw_addr, ETH_ALEN); } #ifdef EXTREME_DEGUG zlog_debug("isis_circuit_if_add: if_id %d, isomtu %d snpa %s", circuit->interface->ifindex, ISO_MTU(circuit), snpa_print(circuit->u.bc.snpa)); #endif /* EXTREME_DEBUG */ circuit->u.bc.adjdb[0] = list_new(); circuit->u.bc.adjdb[1] = list_new(); /* * ISO 10589 - 8.4.1 Enabling of broadcast circuits */ /* initilizing the hello sending threads * for a broadcast IF */ /* 8.4.1 a) commence sending of IIH PDUs */ for (int level = ISIS_LEVEL1; level <= ISIS_LEVEL2; level++) { if (!(circuit->is_type & level)) continue; send_hello_sched(circuit, level, TRIGGERED_IIH_DELAY); circuit->u.bc.lan_neighs[level - 1] = list_new(); thread_add_timer(master, isis_run_dr, &circuit->level_arg[level - 1], 2 * circuit->hello_interval[level - 1], &circuit->u.bc.t_run_dr[level - 1]); } /* 8.4.1 b) FIXME: solicit ES - 8.4.6 */ /* 8.4.1 c) FIXME: listen for ESH PDUs */ } else if (circuit->circ_type == CIRCUIT_T_P2P) { /* initializing the hello send threads * for a ptp IF */ circuit->u.p2p.neighbor = NULL; send_hello_sched(circuit, 0, TRIGGERED_IIH_DELAY); } /* initializing PSNP timers */ if (circuit->is_type & IS_LEVEL_1) thread_add_timer( master, send_l1_psnp, circuit, isis_jitter(circuit->psnp_interval[0], PSNP_JITTER), &circuit->t_send_psnp[0]); if (circuit->is_type & IS_LEVEL_2) thread_add_timer( master, send_l2_psnp, circuit, isis_jitter(circuit->psnp_interval[1], PSNP_JITTER), &circuit->t_send_psnp[1]); /* unified init for circuits; ignore warnings below this level */ retv = isis_sock_init(circuit); if (retv != ISIS_OK) { isis_circuit_down(circuit); return retv; } /* initialize the circuit streams after opening connection */ isis_circuit_stream(circuit, &circuit->rcv_stream); isis_circuit_stream(circuit, &circuit->snd_stream); isis_circuit_prepare(circuit); circuit->tx_queue = isis_tx_queue_new(circuit, send_lsp); #ifndef FABRICD /* send northbound notification */ isis_notif_if_state_change(circuit, false); #endif /* ifndef FABRICD */ return ISIS_OK; } void isis_circuit_down(struct isis_circuit *circuit) { #ifndef FABRICD /* send northbound notification */ isis_notif_if_state_change(circuit, true); #endif /* ifndef FABRICD */ /* Clear the flags for all the lsps of the circuit. */ isis_circuit_update_all_srmflags(circuit, 0); if (circuit->circ_type == CIRCUIT_T_BROADCAST) { /* destroy neighbour lists */ if (circuit->u.bc.lan_neighs[0]) { list_delete(&circuit->u.bc.lan_neighs[0]); circuit->u.bc.lan_neighs[0] = NULL; } if (circuit->u.bc.lan_neighs[1]) { list_delete(&circuit->u.bc.lan_neighs[1]); circuit->u.bc.lan_neighs[1] = NULL; } /* destroy adjacency databases */ if (circuit->u.bc.adjdb[0]) { circuit->u.bc.adjdb[0]->del = isis_delete_adj; list_delete(&circuit->u.bc.adjdb[0]); circuit->u.bc.adjdb[0] = NULL; } if (circuit->u.bc.adjdb[1]) { circuit->u.bc.adjdb[1]->del = isis_delete_adj; list_delete(&circuit->u.bc.adjdb[1]); circuit->u.bc.adjdb[1] = NULL; } if (circuit->u.bc.is_dr[0]) { isis_dr_resign(circuit, 1); circuit->u.bc.is_dr[0] = 0; } memset(circuit->u.bc.l1_desig_is, 0, ISIS_SYS_ID_LEN + 1); if (circuit->u.bc.is_dr[1]) { isis_dr_resign(circuit, 2); circuit->u.bc.is_dr[1] = 0; } memset(circuit->u.bc.l2_desig_is, 0, ISIS_SYS_ID_LEN + 1); memset(circuit->u.bc.snpa, 0, ETH_ALEN); THREAD_TIMER_OFF(circuit->u.bc.t_send_lan_hello[0]); THREAD_TIMER_OFF(circuit->u.bc.t_send_lan_hello[1]); THREAD_TIMER_OFF(circuit->u.bc.t_run_dr[0]); THREAD_TIMER_OFF(circuit->u.bc.t_run_dr[1]); THREAD_TIMER_OFF(circuit->u.bc.t_refresh_pseudo_lsp[0]); THREAD_TIMER_OFF(circuit->u.bc.t_refresh_pseudo_lsp[1]); circuit->lsp_regenerate_pending[0] = 0; circuit->lsp_regenerate_pending[1] = 0; _ISIS_CLEAR_FLAG(isis->circuit_ids_used, circuit->circuit_id); circuit->circuit_id = 0; } else if (circuit->circ_type == CIRCUIT_T_P2P) { isis_delete_adj(circuit->u.p2p.neighbor); circuit->u.p2p.neighbor = NULL; THREAD_TIMER_OFF(circuit->u.p2p.t_send_p2p_hello); } /* Cancel all active threads */ THREAD_TIMER_OFF(circuit->t_send_csnp[0]); THREAD_TIMER_OFF(circuit->t_send_csnp[1]); THREAD_TIMER_OFF(circuit->t_send_psnp[0]); THREAD_TIMER_OFF(circuit->t_send_psnp[1]); THREAD_OFF(circuit->t_read); if (circuit->tx_queue) { isis_tx_queue_free(circuit->tx_queue); circuit->tx_queue = NULL; } /* send one gratuitous hello to spead up convergence */ if (circuit->state == C_STATE_UP) { if (circuit->is_type & IS_LEVEL_1) send_hello(circuit, IS_LEVEL_1); if (circuit->is_type & IS_LEVEL_2) send_hello(circuit, IS_LEVEL_2); } circuit->upadjcount[0] = 0; circuit->upadjcount[1] = 0; /* close the socket */ if (circuit->fd) { close(circuit->fd); circuit->fd = 0; } if (circuit->rcv_stream != NULL) { stream_free(circuit->rcv_stream); circuit->rcv_stream = NULL; } if (circuit->snd_stream != NULL) { stream_free(circuit->snd_stream); circuit->snd_stream = NULL; } thread_cancel_event(master, circuit); return; } void circuit_update_nlpids(struct isis_circuit *circuit) { circuit->nlpids.count = 0; if (circuit->ip_router) { circuit->nlpids.nlpids[0] = NLPID_IP; circuit->nlpids.count++; } if (circuit->ipv6_router) { circuit->nlpids.nlpids[circuit->nlpids.count] = NLPID_IPV6; circuit->nlpids.count++; } return; } void isis_circuit_print_vty(struct isis_circuit *circuit, struct vty *vty, char detail) { if (detail == ISIS_UI_LEVEL_BRIEF) { vty_out(vty, " %-12s", circuit->interface->name); vty_out(vty, "0x%-7x", circuit->circuit_id); vty_out(vty, "%-9s", circuit_state2string(circuit->state)); vty_out(vty, "%-9s", circuit_type2string(circuit->circ_type)); vty_out(vty, "%-9s", circuit_t2string(circuit->is_type)); vty_out(vty, "\n"); } if (detail == ISIS_UI_LEVEL_DETAIL) { struct listnode *node; struct prefix *ip_addr; char buf[BUFSIZ]; vty_out(vty, " Interface: %s", circuit->interface->name); vty_out(vty, ", State: %s", circuit_state2string(circuit->state)); if (circuit->is_passive) vty_out(vty, ", Passive"); else vty_out(vty, ", Active"); vty_out(vty, ", Circuit Id: 0x%x", circuit->circuit_id); vty_out(vty, "\n"); vty_out(vty, " Type: %s", circuit_type2string(circuit->circ_type)); vty_out(vty, ", Level: %s", circuit_t2string(circuit->is_type)); if (circuit->circ_type == CIRCUIT_T_BROADCAST) vty_out(vty, ", SNPA: %-10s", snpa_print(circuit->u.bc.snpa)); vty_out(vty, "\n"); if (circuit->is_type & IS_LEVEL_1) { vty_out(vty, " Level-1 Information:\n"); if (circuit->area->newmetric) vty_out(vty, " Metric: %d", circuit->te_metric[0]); else vty_out(vty, " Metric: %d", circuit->metric[0]); if (!circuit->is_passive) { vty_out(vty, ", Active neighbors: %u\n", circuit->upadjcount[0]); vty_out(vty, " Hello interval: %u, " "Holddown count: %u %s\n", circuit->hello_interval[0], circuit->hello_multiplier[0], (circuit->pad_hellos ? "(pad)" : "(no-pad)")); vty_out(vty, " CNSP interval: %u, " "PSNP interval: %u\n", circuit->csnp_interval[0], circuit->psnp_interval[0]); if (circuit->circ_type == CIRCUIT_T_BROADCAST) vty_out(vty, " LAN Priority: %u, %s\n", circuit->priority[0], (circuit->u.bc.is_dr[0] ? "is DIS" : "is not DIS")); } else { vty_out(vty, "\n"); } } if (circuit->is_type & IS_LEVEL_2) { vty_out(vty, " Level-2 Information:\n"); if (circuit->area->newmetric) vty_out(vty, " Metric: %d", circuit->te_metric[1]); else vty_out(vty, " Metric: %d", circuit->metric[1]); if (!circuit->is_passive) { vty_out(vty, ", Active neighbors: %u\n", circuit->upadjcount[1]); vty_out(vty, " Hello interval: %u, " "Holddown count: %u %s\n", circuit->hello_interval[1], circuit->hello_multiplier[1], (circuit->pad_hellos ? "(pad)" : "(no-pad)")); vty_out(vty, " CNSP interval: %u, " "PSNP interval: %u\n", circuit->csnp_interval[1], circuit->psnp_interval[1]); if (circuit->circ_type == CIRCUIT_T_BROADCAST) vty_out(vty, " LAN Priority: %u, %s\n", circuit->priority[1], (circuit->u.bc.is_dr[1] ? "is DIS" : "is not DIS")); } else { vty_out(vty, "\n"); } } if (circuit->ip_addrs && listcount(circuit->ip_addrs) > 0) { vty_out(vty, " IP Prefix(es):\n"); for (ALL_LIST_ELEMENTS_RO(circuit->ip_addrs, node, ip_addr)) { prefix2str(ip_addr, buf, sizeof(buf)); vty_out(vty, " %s\n", buf); } } if (circuit->ipv6_link && listcount(circuit->ipv6_link) > 0) { vty_out(vty, " IPv6 Link-Locals:\n"); for (ALL_LIST_ELEMENTS_RO(circuit->ipv6_link, node, ip_addr)) { prefix2str(ip_addr, (char *)buf, BUFSIZ); vty_out(vty, " %s\n", buf); } } if (circuit->ipv6_non_link && listcount(circuit->ipv6_non_link) > 0) { vty_out(vty, " IPv6 Prefixes:\n"); for (ALL_LIST_ELEMENTS_RO(circuit->ipv6_non_link, node, ip_addr)) { prefix2str(ip_addr, (char *)buf, BUFSIZ); vty_out(vty, " %s\n", buf); } } vty_out(vty, "\n"); } return; } DEFINE_HOOK(isis_circuit_config_write, (struct isis_circuit *circuit, struct vty *vty), (circuit, vty)) #ifdef FABRICD int isis_interface_config_write(struct vty *vty) { struct vrf *vrf = vrf_lookup_by_id(VRF_DEFAULT); int write = 0; struct listnode *node; struct interface *ifp; struct isis_area *area; struct isis_circuit *circuit; int i; FOR_ALL_INTERFACES (vrf, ifp) { /* IF name */ vty_frame(vty, "interface %s\n", ifp->name); write++; /* IF desc */ if (ifp->desc) { vty_out(vty, " description %s\n", ifp->desc); write++; } /* ISIS Circuit */ for (ALL_LIST_ELEMENTS_RO(isis->area_list, node, area)) { circuit = circuit_lookup_by_ifp(ifp, area->circuit_list); if (circuit == NULL) continue; if (circuit->ip_router) { vty_out(vty, " ip router " PROTO_NAME " %s\n", area->area_tag); write++; } if (circuit->is_passive) { vty_out(vty, " " PROTO_NAME " passive\n"); write++; } if (circuit->circ_type_config == CIRCUIT_T_P2P) { vty_out(vty, " " PROTO_NAME " network point-to-point\n"); write++; } if (circuit->ipv6_router) { vty_out(vty, " ipv6 router " PROTO_NAME " %s\n", area->area_tag); write++; } /* ISIS - circuit type */ if (!fabricd) { if (circuit->is_type == IS_LEVEL_1) { vty_out(vty, " " PROTO_NAME " circuit-type level-1\n"); write++; } else { if (circuit->is_type == IS_LEVEL_2) { vty_out(vty, " " PROTO_NAME " circuit-type level-2-only\n"); write++; } } } /* ISIS - CSNP interval */ if (circuit->csnp_interval[0] == circuit->csnp_interval[1]) { if (circuit->csnp_interval[0] != DEFAULT_CSNP_INTERVAL) { vty_out(vty, " " PROTO_NAME " csnp-interval %d\n", circuit->csnp_interval[0]); write++; } } else { for (i = 0; i < 2; i++) { if (circuit->csnp_interval[i] != DEFAULT_CSNP_INTERVAL) { vty_out(vty, " " PROTO_NAME " csnp-interval %d level-%d\n", circuit->csnp_interval [i], i + 1); write++; } } } /* ISIS - PSNP interval */ if (circuit->psnp_interval[0] == circuit->psnp_interval[1]) { if (circuit->psnp_interval[0] != DEFAULT_PSNP_INTERVAL) { vty_out(vty, " " PROTO_NAME " psnp-interval %d\n", circuit->psnp_interval[0]); write++; } } else { for (i = 0; i < 2; i++) { if (circuit->psnp_interval[i] != DEFAULT_PSNP_INTERVAL) { vty_out(vty, " " PROTO_NAME " psnp-interval %d level-%d\n", circuit->psnp_interval [i], i + 1); write++; } } } /* ISIS - Hello padding - Defaults to true so only * display if false */ if (circuit->pad_hellos == 0) { vty_out(vty, " no " PROTO_NAME " hello padding\n"); write++; } if (circuit->disable_threeway_adj) { vty_out(vty, " no isis three-way-handshake\n"); write++; } /* ISIS - Hello interval */ if (circuit->hello_interval[0] == circuit->hello_interval[1]) { if (circuit->hello_interval[0] != DEFAULT_HELLO_INTERVAL) { vty_out(vty, " " PROTO_NAME " hello-interval %d\n", circuit->hello_interval[0]); write++; } } else { for (i = 0; i < 2; i++) { if (circuit->hello_interval[i] != DEFAULT_HELLO_INTERVAL) { vty_out(vty, " " PROTO_NAME " hello-interval %d level-%d\n", circuit->hello_interval [i], i + 1); write++; } } } /* ISIS - Hello Multiplier */ if (circuit->hello_multiplier[0] == circuit->hello_multiplier[1]) { if (circuit->hello_multiplier[0] != DEFAULT_HELLO_MULTIPLIER) { vty_out(vty, " " PROTO_NAME " hello-multiplier %d\n", circuit->hello_multiplier[0]); write++; } } else { for (i = 0; i < 2; i++) { if (circuit->hello_multiplier[i] != DEFAULT_HELLO_MULTIPLIER) { vty_out(vty, " " PROTO_NAME " hello-multiplier %d level-%d\n", circuit->hello_multiplier [i], i + 1); write++; } } } /* ISIS - Priority */ if (circuit->priority[0] == circuit->priority[1]) { if (circuit->priority[0] != DEFAULT_PRIORITY) { vty_out(vty, " " PROTO_NAME " priority %d\n", circuit->priority[0]); write++; } } else { for (i = 0; i < 2; i++) { if (circuit->priority[i] != DEFAULT_PRIORITY) { vty_out(vty, " " PROTO_NAME " priority %d level-%d\n", circuit->priority[i], i + 1); write++; } } } /* ISIS - Metric */ if (circuit->te_metric[0] == circuit->te_metric[1]) { if (circuit->te_metric[0] != DEFAULT_CIRCUIT_METRIC) { vty_out(vty, " " PROTO_NAME " metric %d\n", circuit->te_metric[0]); write++; } } else { for (i = 0; i < 2; i++) { if (circuit->te_metric[i] != DEFAULT_CIRCUIT_METRIC) { vty_out(vty, " " PROTO_NAME " metric %d level-%d\n", circuit->te_metric[i], i + 1); write++; } } } if (circuit->passwd.type == ISIS_PASSWD_TYPE_HMAC_MD5) { vty_out(vty, " " PROTO_NAME " password md5 %s\n", circuit->passwd.passwd); write++; } else if (circuit->passwd.type == ISIS_PASSWD_TYPE_CLEARTXT) { vty_out(vty, " " PROTO_NAME " password clear %s\n", circuit->passwd.passwd); write++; } write += hook_call(isis_circuit_config_write, circuit, vty); } vty_endframe(vty, "!\n"); } return write; } #else int isis_interface_config_write(struct vty *vty) { struct vrf *vrf = vrf_lookup_by_id(VRF_DEFAULT); int write = 0; struct interface *ifp; struct isis_circuit *circuit; struct lyd_node *dnode; FOR_ALL_INTERFACES (vrf, ifp) { dnode = yang_dnode_get( running_config->dnode, "/frr-interface:lib/interface[name='%s'][vrf='%s']", ifp->name, vrf->name); if (dnode == NULL) continue; write++; nb_cli_show_dnode_cmds(vty, dnode, false); circuit = circuit_scan_by_ifp(ifp); if (circuit) write += hook_call(isis_circuit_config_write, circuit, vty); } return write; } #endif /* ifdef FABRICD */ struct isis_circuit *isis_circuit_create(struct isis_area *area, struct interface *ifp) { struct isis_circuit *circuit = circuit_scan_by_ifp(ifp); if (circuit && circuit->area) return NULL; circuit = isis_csm_state_change(ISIS_ENABLE, circuit, area); if (circuit->state != C_STATE_CONF && circuit->state != C_STATE_UP) return circuit; isis_circuit_if_bind(circuit, ifp); return circuit; } void isis_circuit_af_set(struct isis_circuit *circuit, bool ip_router, bool ipv6_router) { struct isis_area *area = circuit->area; bool change = circuit->ip_router != ip_router || circuit->ipv6_router != ipv6_router; area->ip_circuits += ip_router - circuit->ip_router; area->ipv6_circuits += ipv6_router - circuit->ipv6_router; circuit->ip_router = ip_router; circuit->ipv6_router = ipv6_router; if (!change) return; circuit_update_nlpids(circuit); if (!ip_router && !ipv6_router) isis_csm_state_change(ISIS_DISABLE, circuit, area); else lsp_regenerate_schedule(circuit->area, circuit->is_type, 0); } ferr_r isis_circuit_passive_set(struct isis_circuit *circuit, bool passive) { if (circuit->is_passive == passive) return ferr_ok(); if (if_is_loopback(circuit->interface) && !passive) return ferr_cfg_invalid("loopback is always passive"); if (circuit->state != C_STATE_UP) { circuit->is_passive = passive; } else { struct isis_area *area = circuit->area; isis_csm_state_change(ISIS_DISABLE, circuit, area); circuit->is_passive = passive; isis_csm_state_change(ISIS_ENABLE, circuit, area); } return ferr_ok(); } ferr_r isis_circuit_metric_set(struct isis_circuit *circuit, int level, int metric) { assert(level == IS_LEVEL_1 || level == IS_LEVEL_2); if (metric > MAX_WIDE_LINK_METRIC) return ferr_cfg_invalid("metric %d too large for wide metric", metric); if (circuit->area && circuit->area->oldmetric && metric > MAX_NARROW_LINK_METRIC) return ferr_cfg_invalid("metric %d too large for narrow metric", metric); circuit->te_metric[level - 1] = metric; circuit->metric[level - 1] = metric; if (circuit->area) lsp_regenerate_schedule(circuit->area, level, 0); return ferr_ok(); } ferr_r isis_circuit_passwd_unset(struct isis_circuit *circuit) { memset(&circuit->passwd, 0, sizeof(circuit->passwd)); return ferr_ok(); } ferr_r isis_circuit_passwd_set(struct isis_circuit *circuit, uint8_t passwd_type, const char *passwd) { int len; if (!passwd) return ferr_code_bug("no circuit password given"); len = strlen(passwd); if (len > 254) return ferr_code_bug( "circuit password too long (max 254 chars)"); circuit->passwd.len = len; strlcpy((char *)circuit->passwd.passwd, passwd, sizeof(circuit->passwd.passwd)); circuit->passwd.type = passwd_type; return ferr_ok(); } ferr_r isis_circuit_passwd_cleartext_set(struct isis_circuit *circuit, const char *passwd) { return isis_circuit_passwd_set(circuit, ISIS_PASSWD_TYPE_CLEARTXT, passwd); } ferr_r isis_circuit_passwd_hmac_md5_set(struct isis_circuit *circuit, const char *passwd) { return isis_circuit_passwd_set(circuit, ISIS_PASSWD_TYPE_HMAC_MD5, passwd); } struct cmd_node interface_node = { INTERFACE_NODE, "%s(config-if)# ", 1, }; void isis_circuit_circ_type_set(struct isis_circuit *circuit, int circ_type) { if (circuit->circ_type == circ_type) return; if (circuit->state != C_STATE_UP) { circuit->circ_type = circ_type; circuit->circ_type_config = circ_type; } else { struct isis_area *area = circuit->area; isis_csm_state_change(ISIS_DISABLE, circuit, area); circuit->circ_type = circ_type; circuit->circ_type_config = circ_type; isis_csm_state_change(ISIS_ENABLE, circuit, area); } } int isis_circuit_mt_enabled_set(struct isis_circuit *circuit, uint16_t mtid, bool enabled) { struct isis_circuit_mt_setting *setting; setting = circuit_get_mt_setting(circuit, mtid); if (setting->enabled != enabled) { setting->enabled = enabled; lsp_regenerate_schedule(circuit->area, IS_LEVEL_1 | IS_LEVEL_2, 0); } return CMD_SUCCESS; } int isis_if_new_hook(struct interface *ifp) { return 0; } int isis_if_delete_hook(struct interface *ifp) { struct isis_circuit *circuit; /* Clean up the circuit data */ if (ifp && ifp->info) { circuit = ifp->info; isis_csm_state_change(IF_DOWN_FROM_Z, circuit, circuit->area); isis_csm_state_change(ISIS_DISABLE, circuit, circuit->area); } return 0; } void isis_circuit_init(void) { /* Initialize Zebra interface data structure */ hook_register_prio(if_add, 0, isis_if_new_hook); hook_register_prio(if_del, 0, isis_if_delete_hook); /* Install interface node */ install_node(&interface_node, isis_interface_config_write); if_cmd_init(); } frr-7.2.1/isisd/isis_circuit.h0000644000000000000000000001673513610377563013205 00000000000000/* * IS-IS Rout(e)ing protocol - isis_circuit.h * * Copyright (C) 2001,2002 Sampo Saaristo * Tampere University of Technology * Institute of Communications Engineering * * This program is free software; you can redistribute it and/or modify it * under the terms of the GNU General Public Licenseas 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; see the file COPYING; if not, write to the Free Software * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA */ #ifndef ISIS_CIRCUIT_H #define ISIS_CIRCUIT_H #include "vty.h" #include "if.h" #include "qobj.h" #include "prefix.h" #include "ferr.h" #include "isis_constants.h" #include "isis_common.h" struct isis_lsp; struct password { struct password *next; int len; uint8_t *pass; }; struct metric { uint8_t metric_default; uint8_t metric_error; uint8_t metric_expense; uint8_t metric_delay; }; struct isis_bcast_info { uint8_t snpa[ETH_ALEN]; /* SNPA of this circuit */ char run_dr_elect[2]; /* Should we run dr election ? */ struct thread *t_run_dr[2]; /* DR election thread */ struct thread *t_send_lan_hello[2]; /* send LAN IIHs in this thread */ struct list *adjdb[2]; /* adjacency dbs */ struct list *lan_neighs[2]; /* list of lx neigh snpa */ char is_dr[2]; /* Are we level x DR ? */ uint8_t l1_desig_is[ISIS_SYS_ID_LEN + 1]; /* level-1 DR */ uint8_t l2_desig_is[ISIS_SYS_ID_LEN + 1]; /* level-2 DR */ struct thread *t_refresh_pseudo_lsp[2]; /* refresh pseudo-node LSPs */ }; struct isis_p2p_info { struct isis_adjacency *neighbor; struct thread *t_send_p2p_hello; /* send P2P IIHs in this thread */ }; struct bfd_info; struct isis_circuit_arg { int level; struct isis_circuit *circuit; }; struct isis_circuit { int state; uint8_t circuit_id; /* l1/l2 bcast CircuitID */ struct isis_area *area; /* back pointer to the area */ struct interface *interface; /* interface info from z */ int fd; /* IS-IS l1/2 socket */ int sap_length; /* SAP length for DLPI */ struct nlpids nlpids; /* * Threads */ struct thread *t_read; struct thread *t_send_csnp[2]; struct thread *t_send_psnp[2]; struct isis_tx_queue *tx_queue; struct isis_circuit_arg level_arg[2]; /* used as argument for threads */ /* there is no real point in two streams, just for programming kicker */ int (*rx)(struct isis_circuit *circuit, uint8_t *ssnpa); struct stream *rcv_stream; /* Stream for receiving */ int (*tx)(struct isis_circuit *circuit, int level); struct stream *snd_stream; /* Stream for sending */ int idx; /* idx in S[RM|SN] flags */ #define CIRCUIT_T_UNKNOWN 0 #define CIRCUIT_T_BROADCAST 1 #define CIRCUIT_T_P2P 2 #define CIRCUIT_T_LOOPBACK 3 int circ_type; /* type of the physical interface */ int circ_type_config; /* config type of the physical interface */ union { struct isis_bcast_info bc; struct isis_p2p_info p2p; } u; uint8_t priority[2]; /* l1/2 IS configured priority */ int pad_hellos; /* add padding to Hello PDUs ? */ char ext_domain; /* externalDomain (boolean) */ int lsp_regenerate_pending[ISIS_LEVELS]; /* * Configurables */ struct isis_passwd passwd; /* Circuit rx/tx password */ int is_type; /* circuit is type == level of circuit * differentiated from circuit type (media) */ uint32_t hello_interval[2]; /* hello-interval in seconds */ uint16_t hello_multiplier[2]; /* hello-multiplier */ uint16_t csnp_interval[2]; /* csnp-interval in seconds */ uint16_t psnp_interval[2]; /* psnp-interval in seconds */ uint8_t metric[2]; uint32_t te_metric[2]; struct mpls_te_circuit *mtc; /* MPLS-TE parameters */ int ip_router; /* Route IP ? */ int is_passive; /* Is Passive ? */ struct list *mt_settings; /* IS-IS MT Settings */ struct list *ip_addrs; /* our IP addresses */ int ipv6_router; /* Route IPv6 ? */ struct list *ipv6_link; /* our link local IPv6 addresses */ struct list *ipv6_non_link; /* our non-link local IPv6 addresses */ uint16_t upadjcount[2]; #define ISIS_CIRCUIT_FLAPPED_AFTER_SPF 0x01 uint8_t flags; bool disable_threeway_adj; struct bfd_info *bfd_info; /* * Counters as in 10589--11.2.5.9 */ uint32_t adj_state_changes; /* changesInAdjacencyState */ uint32_t init_failures; /* intialisationFailures */ uint32_t ctrl_pdus_rxed; /* controlPDUsReceived */ uint32_t ctrl_pdus_txed; /* controlPDUsSent */ uint32_t desig_changes[2]; /* lanLxDesignatedIntermediateSystemChanges */ uint32_t rej_adjacencies; /* rejectedAdjacencies */ QOBJ_FIELDS }; DECLARE_QOBJ_TYPE(isis_circuit) void isis_circuit_init(void); struct isis_circuit *isis_circuit_new(void); void isis_circuit_del(struct isis_circuit *circuit); struct isis_circuit *circuit_lookup_by_ifp(struct interface *ifp, struct list *list); struct isis_circuit *circuit_scan_by_ifp(struct interface *ifp); void isis_circuit_configure(struct isis_circuit *circuit, struct isis_area *area); void isis_circuit_deconfigure(struct isis_circuit *circuit, struct isis_area *area); void isis_circuit_if_add(struct isis_circuit *circuit, struct interface *ifp); void isis_circuit_if_del(struct isis_circuit *circuit, struct interface *ifp); void isis_circuit_if_bind(struct isis_circuit *circuit, struct interface *ifp); void isis_circuit_if_unbind(struct isis_circuit *circuit, struct interface *ifp); void isis_circuit_add_addr(struct isis_circuit *circuit, struct connected *conn); void isis_circuit_del_addr(struct isis_circuit *circuit, struct connected *conn); void isis_circuit_prepare(struct isis_circuit *circuit); int isis_circuit_up(struct isis_circuit *circuit); void isis_circuit_down(struct isis_circuit *); void circuit_update_nlpids(struct isis_circuit *circuit); void isis_circuit_print_vty(struct isis_circuit *circuit, struct vty *vty, char detail); size_t isis_circuit_pdu_size(struct isis_circuit *circuit); void isis_circuit_stream(struct isis_circuit *circuit, struct stream **stream); struct isis_circuit *isis_circuit_create(struct isis_area *area, struct interface *ifp); void isis_circuit_af_set(struct isis_circuit *circuit, bool ip_router, bool ipv6_router); ferr_r isis_circuit_passive_set(struct isis_circuit *circuit, bool passive); void isis_circuit_is_type_set(struct isis_circuit *circuit, int is_type); void isis_circuit_circ_type_set(struct isis_circuit *circuit, int circ_type); ferr_r isis_circuit_metric_set(struct isis_circuit *circuit, int level, int metric); ferr_r isis_circuit_passwd_unset(struct isis_circuit *circuit); ferr_r isis_circuit_passwd_set(struct isis_circuit *circuit, uint8_t passwd_type, const char *passwd); ferr_r isis_circuit_passwd_cleartext_set(struct isis_circuit *circuit, const char *passwd); ferr_r isis_circuit_passwd_hmac_md5_set(struct isis_circuit *circuit, const char *passwd); int isis_circuit_mt_enabled_set(struct isis_circuit *circuit, uint16_t mtid, bool enabled); DECLARE_HOOK(isis_circuit_config_write, (struct isis_circuit *circuit, struct vty *vty), (circuit, vty)) #endif /* _ZEBRA_ISIS_CIRCUIT_H */ frr-7.2.1/isisd/isis_cli.c0000644000000000000000000017553613610377563012312 00000000000000/* * Copyright (C) 2001,2002 Sampo Saaristo * Tampere University of Technology * Institute of Communications Engineering * Copyright (C) 2018 Volta Networks * Emanuele Di Pascale * * This program is free software; you can redistribute it and/or modify it * under the terms of the 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; see the file COPYING; if not, write to the Free Software * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA */ #include #include "if.h" #include "vrf.h" #include "log.h" #include "prefix.h" #include "command.h" #include "northbound_cli.h" #include "libfrr.h" #include "yang.h" #include "lib/linklist.h" #include "isisd/isisd.h" #include "isisd/isis_cli.h" #include "isisd/isis_misc.h" #include "isisd/isis_circuit.h" #include "isisd/isis_csm.h" #ifndef VTYSH_EXTRACT_PL #include "isisd/isis_cli_clippy.c" #endif #ifndef FABRICD /* * XPath: /frr-isisd:isis/instance */ DEFPY_NOSH(router_isis, router_isis_cmd, "router isis WORD$tag", ROUTER_STR "ISO IS-IS\n" "ISO Routing area tag\n") { int ret; char base_xpath[XPATH_MAXLEN]; snprintf(base_xpath, XPATH_MAXLEN, "/frr-isisd:isis/instance[area-tag='%s']", tag); nb_cli_enqueue_change(vty, ".", NB_OP_CREATE, NULL); /* default value in yang for is-type is level-1, but in FRR * the first instance is assigned is-type level-1-2. We * need to make sure to set it in the yang model so that it * is consistent with what FRR sees. */ if (listcount(isis->area_list) == 0) nb_cli_enqueue_change(vty, "./is-type", NB_OP_MODIFY, "level-1-2"); ret = nb_cli_apply_changes(vty, base_xpath); if (ret == CMD_SUCCESS) VTY_PUSH_XPATH(ISIS_NODE, base_xpath); return ret; } DEFPY(no_router_isis, no_router_isis_cmd, "no router isis WORD$tag", NO_STR ROUTER_STR "ISO IS-IS\n" "ISO Routing area tag\n") { char temp_xpath[XPATH_MAXLEN]; struct listnode *node, *nnode; struct isis_circuit *circuit = NULL; struct isis_area *area = NULL; if (!yang_dnode_exists(vty->candidate_config->dnode, "/frr-isisd:isis/instance[area-tag='%s']", tag)) { vty_out(vty, "ISIS area %s not found.\n", tag); return CMD_ERR_NOTHING_TODO; } nb_cli_enqueue_change(vty, ".", NB_OP_DESTROY, NULL); area = isis_area_lookup(tag); if (area && area->circuit_list && listcount(area->circuit_list)) { for (ALL_LIST_ELEMENTS(area->circuit_list, node, nnode, circuit)) { /* add callbacks to delete each of the circuits listed */ const char *vrf_name = vrf_lookup_by_id(circuit->interface->vrf_id) ->name; snprintf( temp_xpath, XPATH_MAXLEN, "/frr-interface:lib/interface[name='%s'][vrf='%s']/frr-isisd:isis", circuit->interface->name, vrf_name); nb_cli_enqueue_change(vty, temp_xpath, NB_OP_DESTROY, NULL); } } return nb_cli_apply_changes( vty, "/frr-isisd:isis/instance[area-tag='%s']", tag); } void cli_show_router_isis(struct vty *vty, struct lyd_node *dnode, bool show_defaults) { vty_out(vty, "!\n"); vty_out(vty, "router isis %s\n", yang_dnode_get_string(dnode, "./area-tag")); } /* * XPath: /frr-interface:lib/interface/frr-isisd:isis/ * XPath: /frr-interface:lib/interface/frr-isisd:isis/ipv4-routing * XPath: /frr-interface:lib/interface/frr-isisd:isis/ipv6-routing * XPath: /frr-isisd:isis/instance */ DEFPY(ip_router_isis, ip_router_isis_cmd, "ip router isis WORD$tag", "Interface Internet Protocol config commands\n" "IP router interface commands\n" "IS-IS routing protocol\n" "Routing process tag\n") { char temp_xpath[XPATH_MAXLEN]; const char *circ_type; struct isis_area *area; struct interface *ifp; /* area will be created if it is not present. make sure the yang model * is synced with FRR and call the appropriate NB cb. */ area = isis_area_lookup(tag); if (!area) { snprintf(temp_xpath, XPATH_MAXLEN, "/frr-isisd:isis/instance[area-tag='%s']", tag); nb_cli_enqueue_change(vty, temp_xpath, NB_OP_CREATE, tag); snprintf(temp_xpath, XPATH_MAXLEN, "/frr-isisd:isis/instance[area-tag='%s']/is-type", tag); nb_cli_enqueue_change( vty, temp_xpath, NB_OP_MODIFY, listcount(isis->area_list) == 0 ? "level-1-2" : NULL); nb_cli_enqueue_change(vty, "./frr-isisd:isis", NB_OP_CREATE, NULL); nb_cli_enqueue_change(vty, "./frr-isisd:isis/area-tag", NB_OP_MODIFY, tag); nb_cli_enqueue_change(vty, "./frr-isisd:isis/ipv4-routing", NB_OP_MODIFY, "true"); nb_cli_enqueue_change( vty, "./frr-isisd:isis/circuit-type", NB_OP_MODIFY, listcount(isis->area_list) == 0 ? "level-1-2" : "level-1"); } else { /* area exists, circuit type defaults to its area's is_type */ switch (area->is_type) { case IS_LEVEL_1: circ_type = "level-1"; break; case IS_LEVEL_2: circ_type = "level-2"; break; case IS_LEVEL_1_AND_2: circ_type = "level-1-2"; break; default: /* just to silence compiler warnings */ return CMD_WARNING_CONFIG_FAILED; } nb_cli_enqueue_change(vty, "./frr-isisd:isis", NB_OP_CREATE, NULL); nb_cli_enqueue_change(vty, "./frr-isisd:isis/area-tag", NB_OP_MODIFY, tag); nb_cli_enqueue_change(vty, "./frr-isisd:isis/ipv4-routing", NB_OP_MODIFY, "true"); nb_cli_enqueue_change(vty, "./frr-isisd:isis/circuit-type", NB_OP_MODIFY, circ_type); } /* check if the interface is a loopback and if so set it as passive */ pthread_rwlock_rdlock(&running_config->lock); { ifp = nb_running_get_entry(NULL, VTY_CURR_XPATH, false); if (ifp && if_is_loopback(ifp)) nb_cli_enqueue_change(vty, "./frr-isisd:isis/passive", NB_OP_MODIFY, "true"); } pthread_rwlock_unlock(&running_config->lock); return nb_cli_apply_changes(vty, NULL); } DEFPY(ip6_router_isis, ip6_router_isis_cmd, "ipv6 router isis WORD$tag", "Interface Internet Protocol config commands\n" "IP router interface commands\n" "IS-IS routing protocol\n" "Routing process tag\n") { char temp_xpath[XPATH_MAXLEN]; const char *circ_type; struct isis_area *area; struct interface *ifp; /* area will be created if it is not present. make sure the yang model * is synced with FRR and call the appropriate NB cb. */ area = isis_area_lookup(tag); if (!area) { snprintf(temp_xpath, XPATH_MAXLEN, "/frr-isisd:isis/instance[area-tag='%s']", tag); nb_cli_enqueue_change(vty, temp_xpath, NB_OP_CREATE, tag); snprintf(temp_xpath, XPATH_MAXLEN, "/frr-isisd:isis/instance[area-tag='%s']/is-type", tag); nb_cli_enqueue_change( vty, temp_xpath, NB_OP_MODIFY, listcount(isis->area_list) == 0 ? "level-1-2" : NULL); nb_cli_enqueue_change(vty, "./frr-isisd:isis", NB_OP_CREATE, NULL); nb_cli_enqueue_change(vty, "./frr-isisd:isis/area-tag", NB_OP_MODIFY, tag); nb_cli_enqueue_change(vty, "./frr-isisd:isis/ipv6-routing", NB_OP_MODIFY, "true"); nb_cli_enqueue_change( vty, "./frr-isisd:isis/circuit-type", NB_OP_MODIFY, listcount(isis->area_list) == 0 ? "level-1-2" : "level-1"); } else { /* area exists, circuit type defaults to its area's is_type */ switch (area->is_type) { case IS_LEVEL_1: circ_type = "level-1"; break; case IS_LEVEL_2: circ_type = "level-2"; break; case IS_LEVEL_1_AND_2: circ_type = "level-1-2"; break; default: /* just to silence compiler warnings */ return CMD_WARNING_CONFIG_FAILED; } nb_cli_enqueue_change(vty, "./frr-isisd:isis", NB_OP_CREATE, NULL); nb_cli_enqueue_change(vty, "./frr-isisd:isis/area-tag", NB_OP_MODIFY, tag); nb_cli_enqueue_change(vty, "./frr-isisd:isis/ipv6-routing", NB_OP_MODIFY, "true"); nb_cli_enqueue_change(vty, "./frr-isisd:isis/circuit-type", NB_OP_MODIFY, circ_type); } /* check if the interface is a loopback and if so set it as passive */ pthread_rwlock_rdlock(&running_config->lock); { ifp = nb_running_get_entry(NULL, VTY_CURR_XPATH, false); if (ifp && if_is_loopback(ifp)) nb_cli_enqueue_change(vty, "./frr-isisd:isis/passive", NB_OP_MODIFY, "true"); } pthread_rwlock_unlock(&running_config->lock); return nb_cli_apply_changes(vty, NULL); } DEFPY(no_ip_router_isis, no_ip_router_isis_cmd, "no $ip router isis [WORD]$tag", NO_STR "Interface Internet Protocol config commands\n" "IP router interface commands\n" "IP router interface commands\n" "IS-IS routing protocol\n" "Routing process tag\n") { const struct lyd_node *dnode; dnode = yang_dnode_get(vty->candidate_config->dnode, "%s/frr-isisd:isis", VTY_CURR_XPATH); if (!dnode) return CMD_SUCCESS; /* * If both ipv4 and ipv6 are off delete the interface isis container. */ if (strmatch(ip, "ipv6")) { if (!yang_dnode_get_bool(dnode, "./ipv4-routing")) nb_cli_enqueue_change(vty, "./frr-isisd:isis", NB_OP_DESTROY, NULL); else nb_cli_enqueue_change(vty, "./frr-isisd:isis/ipv6-routing", NB_OP_MODIFY, "false"); } else { if (!yang_dnode_get_bool(dnode, "./ipv6-routing")) nb_cli_enqueue_change(vty, "./frr-isisd:isis", NB_OP_DESTROY, NULL); else nb_cli_enqueue_change(vty, "./frr-isisd:isis/ipv4-routing", NB_OP_MODIFY, "false"); } return nb_cli_apply_changes(vty, NULL); } void cli_show_ip_isis_ipv4(struct vty *vty, struct lyd_node *dnode, bool show_defaults) { if (!yang_dnode_get_bool(dnode, NULL)) vty_out(vty, " no"); vty_out(vty, " ip router isis %s\n", yang_dnode_get_string(dnode, "../area-tag")); } void cli_show_ip_isis_ipv6(struct vty *vty, struct lyd_node *dnode, bool show_defaults) { if (!yang_dnode_get_bool(dnode, NULL)) vty_out(vty, " no"); vty_out(vty, " ipv6 router isis %s\n", yang_dnode_get_string(dnode, "../area-tag")); } /* * XPath: /frr-interface:lib/interface/frr-isisd:isis/bfd-monitoring */ DEFPY(isis_bfd, isis_bfd_cmd, "[no] isis bfd", NO_STR PROTO_HELP "Enable BFD support\n") { const struct lyd_node *dnode; dnode = yang_dnode_get(vty->candidate_config->dnode, "%s/frr-isisd:isis", VTY_CURR_XPATH); if (dnode == NULL) { vty_out(vty, "ISIS is not enabled on this circuit\n"); return CMD_SUCCESS; } nb_cli_enqueue_change(vty, "./frr-isisd:isis/bfd-monitoring", NB_OP_MODIFY, no ? "false" : "true"); return nb_cli_apply_changes(vty, NULL); } void cli_show_ip_isis_bfd_monitoring(struct vty *vty, struct lyd_node *dnode, bool show_defaults) { if (!yang_dnode_get_bool(dnode, NULL)) vty_out(vty, " no"); vty_out(vty, " isis bfd\n"); } /* * XPath: /frr-isisd:isis/instance/area-address */ DEFPY(net, net_cmd, "[no] net WORD", "Remove an existing Network Entity Title for this process\n" "A Network Entity Title for this process (OSI only)\n" "XX.XXXX. ... .XXX.XX Network entity title (NET)\n") { nb_cli_enqueue_change(vty, "./area-address", no ? NB_OP_DESTROY : NB_OP_CREATE, net); return nb_cli_apply_changes(vty, NULL); } void cli_show_isis_area_address(struct vty *vty, struct lyd_node *dnode, bool show_defaults) { vty_out(vty, " net %s\n", yang_dnode_get_string(dnode, NULL)); } /* * XPath: /frr-isisd:isis/instance/is-type */ DEFPY(is_type, is_type_cmd, "is-type $level", "IS Level for this routing process (OSI only)\n" "Act as a station router only\n" "Act as both a station router and an area router\n" "Act as an area router only\n") { nb_cli_enqueue_change(vty, "./is-type", NB_OP_MODIFY, strmatch(level, "level-2-only") ? "level-2" : level); return nb_cli_apply_changes(vty, NULL); } DEFPY(no_is_type, no_is_type_cmd, "no is-type []", NO_STR "IS Level for this routing process (OSI only)\n" "Act as a station router only\n" "Act as both a station router and an area router\n" "Act as an area router only\n") { const char *value; pthread_rwlock_rdlock(&running_config->lock); { struct isis_area *area; area = nb_running_get_entry(NULL, VTY_CURR_XPATH, false); /* * Put the is-type back to defaults: * - level-1-2 on first area * - level-1 for the rest */ if (area && listgetdata(listhead(isis->area_list)) == area) value = "level-1-2"; else value = NULL; } pthread_rwlock_unlock(&running_config->lock); nb_cli_enqueue_change(vty, "./is-type", NB_OP_MODIFY, value); return nb_cli_apply_changes(vty, NULL); } void cli_show_isis_is_type(struct vty *vty, struct lyd_node *dnode, bool show_defaults) { int is_type = yang_dnode_get_enum(dnode, NULL); switch (is_type) { case IS_LEVEL_1: vty_out(vty, " is-type level-1\n"); break; case IS_LEVEL_2: vty_out(vty, " is-type level-2-only\n"); break; case IS_LEVEL_1_AND_2: vty_out(vty, " is-type level-1-2\n"); break; } } /* * XPath: /frr-isisd:isis/instance/dynamic-hostname */ DEFPY(dynamic_hostname, dynamic_hostname_cmd, "[no] hostname dynamic", NO_STR "Dynamic hostname for IS-IS\n" "Dynamic hostname\n") { nb_cli_enqueue_change(vty, "./dynamic-hostname", NB_OP_MODIFY, no ? "false" : "true"); return nb_cli_apply_changes(vty, NULL); } void cli_show_isis_dynamic_hostname(struct vty *vty, struct lyd_node *dnode, bool show_defaults) { if (!yang_dnode_get_bool(dnode, NULL)) vty_out(vty, " no"); vty_out(vty, " hostname dynamic\n"); } /* * XPath: /frr-isisd:isis/instance/overload */ DEFPY(set_overload_bit, set_overload_bit_cmd, "[no] set-overload-bit", "Reset overload bit to accept transit traffic\n" "Set overload bit to avoid any transit traffic\n") { nb_cli_enqueue_change(vty, "./overload", NB_OP_MODIFY, no ? "false" : "true"); return nb_cli_apply_changes(vty, NULL); } void cli_show_isis_overload(struct vty *vty, struct lyd_node *dnode, bool show_defaults) { if (!yang_dnode_get_bool(dnode, NULL)) vty_out(vty, " no"); vty_out(vty, " set-overload-bit\n"); } /* * XPath: /frr-isisd:isis/instance/attached */ DEFPY(set_attached_bit, set_attached_bit_cmd, "[no] set-attached-bit", "Reset attached bit\n" "Set attached bit to identify as L1/L2 router for inter-area traffic\n") { nb_cli_enqueue_change(vty, "./attached", NB_OP_MODIFY, no ? "false" : "true"); return nb_cli_apply_changes(vty, NULL); } void cli_show_isis_attached(struct vty *vty, struct lyd_node *dnode, bool show_defaults) { if (!yang_dnode_get_bool(dnode, NULL)) vty_out(vty, " no"); vty_out(vty, " set-attached-bit\n"); } /* * XPath: /frr-isisd:isis/instance/metric-style */ DEFPY(metric_style, metric_style_cmd, "metric-style $style", "Use old-style (ISO 10589) or new-style packet formats\n" "Use old style of TLVs with narrow metric\n" "Send and accept both styles of TLVs during transition\n" "Use new style of TLVs to carry wider metric\n") { nb_cli_enqueue_change(vty, "./metric-style", NB_OP_MODIFY, style); return nb_cli_apply_changes(vty, NULL); } DEFPY(no_metric_style, no_metric_style_cmd, "no metric-style [narrow|transition|wide]", NO_STR "Use old-style (ISO 10589) or new-style packet formats\n" "Use old style of TLVs with narrow metric\n" "Send and accept both styles of TLVs during transition\n" "Use new style of TLVs to carry wider metric\n") { nb_cli_enqueue_change(vty, "./metric-style", NB_OP_MODIFY, "narrow"); return nb_cli_apply_changes(vty, NULL); } void cli_show_isis_metric_style(struct vty *vty, struct lyd_node *dnode, bool show_defaults) { int metric = yang_dnode_get_enum(dnode, NULL); switch (metric) { case ISIS_NARROW_METRIC: vty_out(vty, " metric-style narrow\n"); break; case ISIS_WIDE_METRIC: vty_out(vty, " metric-style wide\n"); break; case ISIS_TRANSITION_METRIC: vty_out(vty, " metric-style transition\n"); break; } } /* * XPath: /frr-isisd:isis/instance/area-password */ DEFPY(area_passwd, area_passwd_cmd, "area-password $pwd_type WORD$pwd [authenticate snp $snp]", "Configure the authentication password for an area\n" "Clear-text authentication type\n" "MD5 authentication type\n" "Level-wide password\n" "Authentication\n" "SNP PDUs\n" "Send but do not check PDUs on receiving\n" "Send and check PDUs on receiving\n") { nb_cli_enqueue_change(vty, "./area-password", NB_OP_CREATE, NULL); nb_cli_enqueue_change(vty, "./area-password/password", NB_OP_MODIFY, pwd); nb_cli_enqueue_change(vty, "./area-password/password-type", NB_OP_MODIFY, pwd_type); nb_cli_enqueue_change(vty, "./area-password/authenticate-snp", NB_OP_MODIFY, snp ? snp : "none"); return nb_cli_apply_changes(vty, NULL); } void cli_show_isis_area_pwd(struct vty *vty, struct lyd_node *dnode, bool show_defaults) { const char *snp; vty_out(vty, " area-password %s %s", yang_dnode_get_string(dnode, "./password-type"), yang_dnode_get_string(dnode, "./password")); snp = yang_dnode_get_string(dnode, "./authenticate-snp"); if (!strmatch("none", snp)) vty_out(vty, " authenticate snp %s", snp); vty_out(vty, "\n"); } /* * XPath: /frr-isisd:isis/instance/domain-password */ DEFPY(domain_passwd, domain_passwd_cmd, "domain-password $pwd_type WORD$pwd [authenticate snp $snp]", "Set the authentication password for a routing domain\n" "Clear-text authentication type\n" "MD5 authentication type\n" "Level-wide password\n" "Authentication\n" "SNP PDUs\n" "Send but do not check PDUs on receiving\n" "Send and check PDUs on receiving\n") { nb_cli_enqueue_change(vty, "./domain-password", NB_OP_CREATE, NULL); nb_cli_enqueue_change(vty, "./domain-password/password", NB_OP_MODIFY, pwd); nb_cli_enqueue_change(vty, "./domain-password/password-type", NB_OP_MODIFY, pwd_type); nb_cli_enqueue_change(vty, "./domain-password/authenticate-snp", NB_OP_MODIFY, snp ? snp : "none"); return nb_cli_apply_changes(vty, NULL); } DEFPY(no_area_passwd, no_area_passwd_cmd, "no $cmd", NO_STR "Configure the authentication password for an area\n" "Set the authentication password for a routing domain\n") { nb_cli_enqueue_change(vty, ".", NB_OP_DESTROY, NULL); return nb_cli_apply_changes(vty, "./%s", cmd); } void cli_show_isis_domain_pwd(struct vty *vty, struct lyd_node *dnode, bool show_defaults) { const char *snp; vty_out(vty, " domain-password %s %s", yang_dnode_get_string(dnode, "./password-type"), yang_dnode_get_string(dnode, "./password")); snp = yang_dnode_get_string(dnode, "./authenticate-snp"); if (!strmatch("none", snp)) vty_out(vty, " authenticate snp %s", snp); vty_out(vty, "\n"); } /* * XPath: /frr-isisd:isis/instance/lsp/generation-interval */ DEFPY(lsp_gen_interval, lsp_gen_interval_cmd, "lsp-gen-interval [level-1|level-2]$level (1-120)$val", "Minimum interval between regenerating same LSP\n" "Set interval for level 1 only\n" "Set interval for level 2 only\n" "Minimum interval in seconds\n") { if (!level || strmatch(level, "level-1")) nb_cli_enqueue_change(vty, "./lsp/generation-interval/level-1", NB_OP_MODIFY, val_str); if (!level || strmatch(level, "level-2")) nb_cli_enqueue_change(vty, "./lsp/generation-interval/level-2", NB_OP_MODIFY, val_str); return nb_cli_apply_changes(vty, NULL); } DEFPY(no_lsp_gen_interval, no_lsp_gen_interval_cmd, "no lsp-gen-interval [level-1|level-2]$level [(1-120)]", NO_STR "Minimum interval between regenerating same LSP\n" "Set interval for level 1 only\n" "Set interval for level 2 only\n" "Minimum interval in seconds\n") { if (!level || strmatch(level, "level-1")) nb_cli_enqueue_change(vty, "./lsp/generation-interval/level-1", NB_OP_MODIFY, NULL); if (!level || strmatch(level, "level-2")) nb_cli_enqueue_change(vty, "./lsp/generation-interval/level-2", NB_OP_MODIFY, NULL); return nb_cli_apply_changes(vty, NULL); } void cli_show_isis_lsp_gen_interval(struct vty *vty, struct lyd_node *dnode, bool show_defaults) { const char *l1 = yang_dnode_get_string(dnode, "./level-1"); const char *l2 = yang_dnode_get_string(dnode, "./level-2"); if (strmatch(l1, l2)) vty_out(vty, " lsp-gen-interval %s\n", l1); else { vty_out(vty, " lsp-gen-interval level-1 %s\n", l1); vty_out(vty, " lsp-gen-interval level-2 %s\n", l2); } } /* * XPath: /frr-isisd:isis/instance/lsp/refresh-interval */ DEFPY(lsp_refresh_interval, lsp_refresh_interval_cmd, "lsp-refresh-interval [level-1|level-2]$level (1-65235)$val", "LSP refresh interval\n" "LSP refresh interval for Level 1 only\n" "LSP refresh interval for Level 2 only\n" "LSP refresh interval in seconds\n") { if (!level || strmatch(level, "level-1")) nb_cli_enqueue_change(vty, "./lsp/refresh-interval/level-1", NB_OP_MODIFY, val_str); if (!level || strmatch(level, "level-2")) nb_cli_enqueue_change(vty, "./lsp/refresh-interval/level-2", NB_OP_MODIFY, val_str); return nb_cli_apply_changes(vty, NULL); } DEFPY(no_lsp_refresh_interval, no_lsp_refresh_interval_cmd, "no lsp-refresh-interval [level-1|level-2]$level [(1-65235)]", NO_STR "LSP refresh interval\n" "LSP refresh interval for Level 1 only\n" "LSP refresh interval for Level 2 only\n" "LSP refresh interval in seconds\n") { if (!level || strmatch(level, "level-1")) nb_cli_enqueue_change(vty, "./lsp/refresh-interval/level-1", NB_OP_MODIFY, NULL); if (!level || strmatch(level, "level-2")) nb_cli_enqueue_change(vty, "./lsp/refresh-interval/level-2", NB_OP_MODIFY, NULL); return nb_cli_apply_changes(vty, NULL); } void cli_show_isis_lsp_ref_interval(struct vty *vty, struct lyd_node *dnode, bool show_defaults) { const char *l1 = yang_dnode_get_string(dnode, "./level-1"); const char *l2 = yang_dnode_get_string(dnode, "./level-2"); if (strmatch(l1, l2)) vty_out(vty, " lsp-refresh-interval %s\n", l1); else { vty_out(vty, " lsp-refresh-interval level-1 %s\n", l1); vty_out(vty, " lsp-refresh-interval level-2 %s\n", l2); } } /* * XPath: /frr-isisd:isis/instance/lsp/maximum-lifetime */ DEFPY(max_lsp_lifetime, max_lsp_lifetime_cmd, "max-lsp-lifetime [level-1|level-2]$level (350-65535)$val", "Maximum LSP lifetime\n" "Maximum LSP lifetime for Level 1 only\n" "Maximum LSP lifetime for Level 2 only\n" "LSP lifetime in seconds\n") { if (!level || strmatch(level, "level-1")) nb_cli_enqueue_change(vty, "./lsp/maximum-lifetime/level-1", NB_OP_MODIFY, val_str); if (!level || strmatch(level, "level-2")) nb_cli_enqueue_change(vty, "./lsp/maximum-lifetime/level-2", NB_OP_MODIFY, val_str); return nb_cli_apply_changes(vty, NULL); } DEFPY(no_max_lsp_lifetime, no_max_lsp_lifetime_cmd, "no max-lsp-lifetime [level-1|level-2]$level [(350-65535)]", NO_STR "Maximum LSP lifetime\n" "Maximum LSP lifetime for Level 1 only\n" "Maximum LSP lifetime for Level 2 only\n" "LSP lifetime in seconds\n") { if (!level || strmatch(level, "level-1")) nb_cli_enqueue_change(vty, "./lsp/maximum-lifetime/level-1", NB_OP_MODIFY, NULL); if (!level || strmatch(level, "level-2")) nb_cli_enqueue_change(vty, "./lsp/maximum-lifetime/level-2", NB_OP_MODIFY, NULL); return nb_cli_apply_changes(vty, NULL); } void cli_show_isis_lsp_max_lifetime(struct vty *vty, struct lyd_node *dnode, bool show_defaults) { const char *l1 = yang_dnode_get_string(dnode, "./level-1"); const char *l2 = yang_dnode_get_string(dnode, "./level-2"); if (strmatch(l1, l2)) vty_out(vty, " max-lsp-lifetime %s\n", l1); else { vty_out(vty, " max-lsp-lifetime level-1 %s\n", l1); vty_out(vty, " max-lsp-lifetime level-2 %s\n", l2); } } /* * XPath: /frr-isisd:isis/instance/lsp/mtu */ DEFPY(area_lsp_mtu, area_lsp_mtu_cmd, "lsp-mtu (128-4352)$val", "Configure the maximum size of generated LSPs\n" "Maximum size of generated LSPs\n") { nb_cli_enqueue_change(vty, "./lsp/mtu", NB_OP_MODIFY, val_str); return nb_cli_apply_changes(vty, NULL); } DEFPY(no_area_lsp_mtu, no_area_lsp_mtu_cmd, "no lsp-mtu [(128-4352)]", NO_STR "Configure the maximum size of generated LSPs\n" "Maximum size of generated LSPs\n") { nb_cli_enqueue_change(vty, "./lsp/mtu", NB_OP_MODIFY, NULL); return nb_cli_apply_changes(vty, NULL); } void cli_show_isis_lsp_mtu(struct vty *vty, struct lyd_node *dnode, bool show_defaults) { vty_out(vty, " lsp-mtu %s\n", yang_dnode_get_string(dnode, NULL)); } /* * XPath: /frr-isisd:isis/instance/spf/minimum-interval */ DEFPY(spf_interval, spf_interval_cmd, "spf-interval [level-1|level-2]$level (1-120)$val", "Minimum interval between SPF calculations\n" "Set interval for level 1 only\n" "Set interval for level 2 only\n" "Minimum interval between consecutive SPFs in seconds\n") { if (!level || strmatch(level, "level-1")) nb_cli_enqueue_change(vty, "./spf/minimum-interval/level-1", NB_OP_MODIFY, val_str); if (!level || strmatch(level, "level-2")) nb_cli_enqueue_change(vty, "./spf/minimum-interval/level-2", NB_OP_MODIFY, val_str); return nb_cli_apply_changes(vty, NULL); } DEFPY(no_spf_interval, no_spf_interval_cmd, "no spf-interval [level-1|level-2]$level [(1-120)]", NO_STR "Minimum interval between SPF calculations\n" "Set interval for level 1 only\n" "Set interval for level 2 only\n" "Minimum interval between consecutive SPFs in seconds\n") { if (!level || strmatch(level, "level-1")) nb_cli_enqueue_change(vty, "./spf/minimum-interval/level-1", NB_OP_MODIFY, NULL); if (!level || strmatch(level, "level-2")) nb_cli_enqueue_change(vty, "./spf/minimum-interval/level-2", NB_OP_MODIFY, NULL); return nb_cli_apply_changes(vty, NULL); } void cli_show_isis_spf_min_interval(struct vty *vty, struct lyd_node *dnode, bool show_defaults) { const char *l1 = yang_dnode_get_string(dnode, "./level-1"); const char *l2 = yang_dnode_get_string(dnode, "./level-2"); if (strmatch(l1, l2)) vty_out(vty, " spf-interval %s\n", l1); else { vty_out(vty, " spf-interval level-1 %s\n", l1); vty_out(vty, " spf-interval level-2 %s\n", l2); } } /* * XPath: /frr-isisd:isis/instance/spf/ietf-backoff-delay */ DEFPY(spf_delay_ietf, spf_delay_ietf_cmd, "spf-delay-ietf init-delay (0-60000) short-delay (0-60000) long-delay (0-60000) holddown (0-60000) time-to-learn (0-60000)", "IETF SPF delay algorithm\n" "Delay used while in QUIET state\n" "Delay used while in QUIET state in milliseconds\n" "Delay used while in SHORT_WAIT state\n" "Delay used while in SHORT_WAIT state in milliseconds\n" "Delay used while in LONG_WAIT\n" "Delay used while in LONG_WAIT state in milliseconds\n" "Time with no received IGP events before considering IGP stable\n" "Time with no received IGP events before considering IGP stable (in milliseconds)\n" "Maximum duration needed to learn all the events related to a single failure\n" "Maximum duration needed to learn all the events related to a single failure (in milliseconds)\n") { nb_cli_enqueue_change(vty, "./spf/ietf-backoff-delay", NB_OP_CREATE, NULL); nb_cli_enqueue_change(vty, "./spf/ietf-backoff-delay/init-delay", NB_OP_MODIFY, init_delay_str); nb_cli_enqueue_change(vty, "./spf/ietf-backoff-delay/short-delay", NB_OP_MODIFY, short_delay_str); nb_cli_enqueue_change(vty, "./spf/ietf-backoff-delay/long-delay", NB_OP_MODIFY, long_delay_str); nb_cli_enqueue_change(vty, "./spf/ietf-backoff-delay/hold-down", NB_OP_MODIFY, holddown_str); nb_cli_enqueue_change(vty, "./spf/ietf-backoff-delay/time-to-learn", NB_OP_MODIFY, time_to_learn_str); return nb_cli_apply_changes(vty, NULL); } DEFPY(no_spf_delay_ietf, no_spf_delay_ietf_cmd, "no spf-delay-ietf [init-delay (0-60000) short-delay (0-60000) long-delay (0-60000) holddown (0-60000) time-to-learn (0-60000)]", NO_STR "IETF SPF delay algorithm\n" "Delay used while in QUIET state\n" "Delay used while in QUIET state in milliseconds\n" "Delay used while in SHORT_WAIT state\n" "Delay used while in SHORT_WAIT state in milliseconds\n" "Delay used while in LONG_WAIT\n" "Delay used while in LONG_WAIT state in milliseconds\n" "Time with no received IGP events before considering IGP stable\n" "Time with no received IGP events before considering IGP stable (in milliseconds)\n" "Maximum duration needed to learn all the events related to a single failure\n" "Maximum duration needed to learn all the events related to a single failure (in milliseconds)\n") { nb_cli_enqueue_change(vty, "./spf/ietf-backoff-delay", NB_OP_DESTROY, NULL); return nb_cli_apply_changes(vty, NULL); } void cli_show_isis_spf_ietf_backoff(struct vty *vty, struct lyd_node *dnode, bool show_defaults) { vty_out(vty, " spf-delay-ietf init-delay %s short-delay %s long-delay %s holddown %s time-to-learn %s\n", yang_dnode_get_string(dnode, "./init-delay"), yang_dnode_get_string(dnode, "./short-delay"), yang_dnode_get_string(dnode, "./long-delay"), yang_dnode_get_string(dnode, "./hold-down"), yang_dnode_get_string(dnode, "./time-to-learn")); } /* * XPath: /frr-isisd:isis/instance/purge-originator */ DEFPY(area_purge_originator, area_purge_originator_cmd, "[no] purge-originator", NO_STR "Use the RFC 6232 purge-originator\n") { nb_cli_enqueue_change(vty, "./purge-originator", NB_OP_MODIFY, no ? "false" : "true"); return nb_cli_apply_changes(vty, NULL); } void cli_show_isis_purge_origin(struct vty *vty, struct lyd_node *dnode, bool show_defaults) { if (!yang_dnode_get_bool(dnode, NULL)) vty_out(vty, " no"); vty_out(vty, " purge-originator\n"); } /* * XPath: /frr-isisd:isis/instance/mpls-te */ DEFPY(isis_mpls_te_on, isis_mpls_te_on_cmd, "mpls-te on", MPLS_TE_STR "Enable the MPLS-TE functionality\n") { nb_cli_enqueue_change(vty, "./mpls-te", NB_OP_CREATE, NULL); return nb_cli_apply_changes(vty, NULL); } DEFPY(no_isis_mpls_te_on, no_isis_mpls_te_on_cmd, "no mpls-te [on]", NO_STR "Disable the MPLS-TE functionality\n" "Disable the MPLS-TE functionality\n") { nb_cli_enqueue_change(vty, "./mpls-te", NB_OP_DESTROY, NULL); return nb_cli_apply_changes(vty, NULL); } void cli_show_isis_mpls_te(struct vty *vty, struct lyd_node *dnode, bool show_defaults) { vty_out(vty, " mpls-te on\n"); } /* * XPath: /frr-isisd:isis/instance/mpls-te/router-address */ DEFPY(isis_mpls_te_router_addr, isis_mpls_te_router_addr_cmd, "mpls-te router-address A.B.C.D", MPLS_TE_STR "Stable IP address of the advertising router\n" "MPLS-TE router address in IPv4 address format\n") { nb_cli_enqueue_change(vty, "./mpls-te/router-address", NB_OP_MODIFY, router_address_str); return nb_cli_apply_changes(vty, NULL); } DEFPY(no_isis_mpls_te_router_addr, no_isis_mpls_te_router_addr_cmd, "no mpls-te router-address [A.B.C.D]", NO_STR MPLS_TE_STR "Delete IP address of the advertising router\n" "MPLS-TE router address in IPv4 address format\n") { nb_cli_enqueue_change(vty, "./mpls-te/router-address", NB_OP_DESTROY, NULL); return nb_cli_apply_changes(vty, NULL); } void cli_show_isis_mpls_te_router_addr(struct vty *vty, struct lyd_node *dnode, bool show_defaults) { vty_out(vty, " mpls-te router-address %s\n", yang_dnode_get_string(dnode, NULL)); } DEFPY(isis_mpls_te_inter_as, isis_mpls_te_inter_as_cmd, "[no] mpls-te inter-as [level-1|level-1-2|level-2-only]", NO_STR MPLS_TE_STR "Configure MPLS-TE Inter-AS support\n" "AREA native mode self originate INTER-AS LSP with L1 only flooding scope\n" "AREA native mode self originate INTER-AS LSP with L1 and L2 flooding scope\n" "AS native mode self originate INTER-AS LSP with L2 only flooding scope\n") { vty_out(vty, "MPLS-TE Inter-AS is not yet supported\n"); return CMD_SUCCESS; } /* * XPath: /frr-isisd:isis/instance/default-information-originate */ DEFPY(isis_default_originate, isis_default_originate_cmd, "[no] default-information originate $ip" " $level [always]$always" " [{metric (0-16777215)$metric|route-map WORD$rmap}]", NO_STR "Control distribution of default information\n" "Distribute a default route\n" "Distribute default route for IPv4\n" "Distribute default route for IPv6\n" "Distribute default route into level-1\n" "Distribute default route into level-2\n" "Always advertise default route\n" "Metric for default route\n" "ISIS default metric\n" "Route map reference\n" "Pointer to route-map entries\n") { if (no) nb_cli_enqueue_change(vty, ".", NB_OP_DESTROY, NULL); else { nb_cli_enqueue_change(vty, ".", NB_OP_CREATE, NULL); nb_cli_enqueue_change(vty, "./always", NB_OP_MODIFY, always ? "true" : "false"); nb_cli_enqueue_change(vty, "./route-map", rmap ? NB_OP_MODIFY : NB_OP_DESTROY, rmap ? rmap : NULL); nb_cli_enqueue_change(vty, "./metric", NB_OP_MODIFY, metric_str ? metric_str : NULL); if (strmatch(ip, "ipv6") && !always) { vty_out(vty, "Zebra doesn't implement default-originate for IPv6 yet\n"); vty_out(vty, "so use with care or use default-originate always.\n"); } } return nb_cli_apply_changes( vty, "./default-information-originate/%s[level='%s']", ip, level); } static void vty_print_def_origin(struct vty *vty, struct lyd_node *dnode, const char *family, const char *level, bool show_defaults) { vty_out(vty, " default-information originate %s %s", family, level); if (yang_dnode_get_bool(dnode, "./always")) vty_out(vty, " always"); if (yang_dnode_exists(dnode, "./route-map")) vty_out(vty, " route-map %s", yang_dnode_get_string(dnode, "./route-map")); if (show_defaults || !yang_dnode_is_default(dnode, "./metric")) vty_out(vty, " metric %s", yang_dnode_get_string(dnode, "./metric")); vty_out(vty, "\n"); } void cli_show_isis_def_origin_ipv4(struct vty *vty, struct lyd_node *dnode, bool show_defaults) { const char *level = yang_dnode_get_string(dnode, "./level"); vty_print_def_origin(vty, dnode, "ipv4", level, show_defaults); } void cli_show_isis_def_origin_ipv6(struct vty *vty, struct lyd_node *dnode, bool show_defaults) { const char *level = yang_dnode_get_string(dnode, "./level"); vty_print_def_origin(vty, dnode, "ipv6", level, show_defaults); } /* * XPath: /frr-isisd:isis/instance/redistribute */ DEFPY(isis_redistribute, isis_redistribute_cmd, "[no] redistribute $ip " PROTO_REDIST_STR "$proto" " $level" " [{metric (0-16777215)|route-map WORD}]", NO_STR REDIST_STR "Redistribute IPv4 routes\n" "Redistribute IPv6 routes\n" PROTO_REDIST_HELP "Redistribute into level-1\n" "Redistribute into level-2\n" "Metric for redistributed routes\n" "ISIS default metric\n" "Route map reference\n" "Pointer to route-map entries\n") { if (no) nb_cli_enqueue_change(vty, ".", NB_OP_DESTROY, NULL); else { nb_cli_enqueue_change(vty, ".", NB_OP_CREATE, NULL); nb_cli_enqueue_change(vty, "./route-map", route_map ? NB_OP_MODIFY : NB_OP_DESTROY, route_map ? route_map : NULL); nb_cli_enqueue_change(vty, "./metric", NB_OP_MODIFY, metric_str ? metric_str : NULL); } return nb_cli_apply_changes( vty, "./redistribute/%s[protocol='%s'][level='%s']", ip, proto, level); } static void vty_print_redistribute(struct vty *vty, struct lyd_node *dnode, bool show_defaults, const char *family) { const char *level = yang_dnode_get_string(dnode, "./level"); const char *protocol = yang_dnode_get_string(dnode, "./protocol"); vty_out(vty, " redistribute %s %s %s", family, protocol, level); if (show_defaults || !yang_dnode_is_default(dnode, "./metric")) vty_out(vty, " metric %s", yang_dnode_get_string(dnode, "./metric")); if (yang_dnode_exists(dnode, "./route-map")) vty_out(vty, " route-map %s", yang_dnode_get_string(dnode, "./route-map")); vty_out(vty, "\n"); } void cli_show_isis_redistribute_ipv4(struct vty *vty, struct lyd_node *dnode, bool show_defaults) { vty_print_redistribute(vty, dnode, show_defaults, "ipv4"); } void cli_show_isis_redistribute_ipv6(struct vty *vty, struct lyd_node *dnode, bool show_defaults) { vty_print_redistribute(vty, dnode, show_defaults, "ipv6"); } /* * XPath: /frr-isisd:isis/instance/multi-topology */ DEFPY(isis_topology, isis_topology_cmd, "[no] topology " "$topology " "[overload]$overload", NO_STR "Configure IS-IS topologies\n" "IPv4 unicast topology\n" "IPv4 management topology\n" "IPv6 unicast topology\n" "IPv4 multicast topology\n" "IPv6 multicast topology\n" "IPv6 management topology\n" "IPv6 dst-src topology\n" "Set overload bit for topology\n") { char base_xpath[XPATH_MAXLEN]; /* Since IPv4-unicast is not configurable it is not present in the * YANG model, so we need to validate it here */ if (strmatch(topology, "ipv4-unicast")) { vty_out(vty, "Cannot configure IPv4 unicast topology\n"); return CMD_WARNING_CONFIG_FAILED; } if (strmatch(topology, "ipv4-mgmt")) snprintf(base_xpath, XPATH_MAXLEN, "./multi-topology/ipv4-management"); else if (strmatch(topology, "ipv6-mgmt")) snprintf(base_xpath, XPATH_MAXLEN, "./multi-topology/ipv6-management"); else snprintf(base_xpath, XPATH_MAXLEN, "./multi-topology/%s", topology); if (no) nb_cli_enqueue_change(vty, ".", NB_OP_DESTROY, NULL); else { nb_cli_enqueue_change(vty, ".", NB_OP_CREATE, NULL); nb_cli_enqueue_change(vty, "./overload", NB_OP_MODIFY, overload ? "true" : "false"); } return nb_cli_apply_changes(vty, base_xpath); } void cli_show_isis_mt_ipv4_multicast(struct vty *vty, struct lyd_node *dnode, bool show_defaults) { vty_out(vty, " topology ipv4-multicast"); if (yang_dnode_get_bool(dnode, "./overload")) vty_out(vty, " overload"); vty_out(vty, "\n"); } void cli_show_isis_mt_ipv4_mgmt(struct vty *vty, struct lyd_node *dnode, bool show_defaults) { vty_out(vty, " topology ipv4-mgmt"); if (yang_dnode_get_bool(dnode, "./overload")) vty_out(vty, " overload"); vty_out(vty, "\n"); } void cli_show_isis_mt_ipv6_unicast(struct vty *vty, struct lyd_node *dnode, bool show_defaults) { vty_out(vty, " topology ipv6-unicast"); if (yang_dnode_get_bool(dnode, "./overload")) vty_out(vty, " overload"); vty_out(vty, "\n"); } void cli_show_isis_mt_ipv6_multicast(struct vty *vty, struct lyd_node *dnode, bool show_defaults) { vty_out(vty, " topology ipv6-multicast"); if (yang_dnode_get_bool(dnode, "./overload")) vty_out(vty, " overload"); vty_out(vty, "\n"); } void cli_show_isis_mt_ipv6_mgmt(struct vty *vty, struct lyd_node *dnode, bool show_defaults) { vty_out(vty, " topology ipv6-mgmt"); if (yang_dnode_get_bool(dnode, "./overload")) vty_out(vty, " overload"); vty_out(vty, "\n"); } void cli_show_isis_mt_ipv6_dstsrc(struct vty *vty, struct lyd_node *dnode, bool show_defaults) { vty_out(vty, " topology ipv6-dstsrc"); if (yang_dnode_get_bool(dnode, "./overload")) vty_out(vty, " overload"); vty_out(vty, "\n"); } /* * XPath: /frr-interface:lib/interface/frr-isisd:isis/passive */ DEFPY(isis_passive, isis_passive_cmd, "[no] isis passive", NO_STR "IS-IS routing protocol\n" "Configure the passive mode for interface\n") { nb_cli_enqueue_change(vty, "./frr-isisd:isis/passive", NB_OP_MODIFY, no ? "false" : "true"); return nb_cli_apply_changes(vty, NULL); } void cli_show_ip_isis_passive(struct vty *vty, struct lyd_node *dnode, bool show_defaults) { if (!yang_dnode_get_bool(dnode, NULL)) vty_out(vty, " no"); vty_out(vty, " isis passive\n"); } /* * XPath: /frr-interface:lib/interface/frr-isisd:isis/password */ DEFPY(isis_passwd, isis_passwd_cmd, "isis password $type WORD$pwd", "IS-IS routing protocol\n" "Configure the authentication password for a circuit\n" "HMAC-MD5 authentication\n" "Cleartext password\n" "Circuit password\n") { nb_cli_enqueue_change(vty, "./frr-isisd:isis/password", NB_OP_CREATE, NULL); nb_cli_enqueue_change(vty, "./frr-isisd:isis/password/password", NB_OP_MODIFY, pwd); nb_cli_enqueue_change(vty, "./frr-isisd:isis/password/password-type", NB_OP_MODIFY, type); return nb_cli_apply_changes(vty, NULL); } DEFPY(no_isis_passwd, no_isis_passwd_cmd, "no isis password [ WORD]", NO_STR "IS-IS routing protocol\n" "Configure the authentication password for a circuit\n" "HMAC-MD5 authentication\n" "Cleartext password\n" "Circuit password\n") { nb_cli_enqueue_change(vty, "./frr-isisd:isis/password", NB_OP_DESTROY, NULL); return nb_cli_apply_changes(vty, NULL); } void cli_show_ip_isis_password(struct vty *vty, struct lyd_node *dnode, bool show_defaults) { vty_out(vty, " isis password %s %s\n", yang_dnode_get_string(dnode, "./password-type"), yang_dnode_get_string(dnode, "./password")); } /* * XPath: /frr-interface:lib/interface/frr-isisd:isis/metric */ DEFPY(isis_metric, isis_metric_cmd, "isis metric [level-1|level-2]$level (0-16777215)$met", "IS-IS routing protocol\n" "Set default metric for circuit\n" "Specify metric for level-1 routing\n" "Specify metric for level-2 routing\n" "Default metric value\n") { if (!level || strmatch(level, "level-1")) nb_cli_enqueue_change(vty, "./frr-isisd:isis/metric/level-1", NB_OP_MODIFY, met_str); if (!level || strmatch(level, "level-2")) nb_cli_enqueue_change(vty, "./frr-isisd:isis/metric/level-2", NB_OP_MODIFY, met_str); return nb_cli_apply_changes(vty, NULL); } DEFPY(no_isis_metric, no_isis_metric_cmd, "no isis metric [level-1|level-2]$level [(0-16777215)]", NO_STR "IS-IS routing protocol\n" "Set default metric for circuit\n" "Specify metric for level-1 routing\n" "Specify metric for level-2 routing\n" "Default metric value\n") { if (!level || strmatch(level, "level-1")) nb_cli_enqueue_change(vty, "./frr-isisd:isis/metric/level-1", NB_OP_MODIFY, NULL); if (!level || strmatch(level, "level-2")) nb_cli_enqueue_change(vty, "./frr-isisd:isis/metric/level-2", NB_OP_MODIFY, NULL); return nb_cli_apply_changes(vty, NULL); } void cli_show_ip_isis_metric(struct vty *vty, struct lyd_node *dnode, bool show_defaults) { const char *l1 = yang_dnode_get_string(dnode, "./level-1"); const char *l2 = yang_dnode_get_string(dnode, "./level-2"); if (strmatch(l1, l2)) vty_out(vty, " isis metric %s\n", l1); else { vty_out(vty, " isis metric %s level-1\n", l1); vty_out(vty, " isis metric %s level-2\n", l2); } } /* * XPath: /frr-interface:lib/interface/frr-isisd:isis/hello/interval */ DEFPY(isis_hello_interval, isis_hello_interval_cmd, "isis hello-interval [level-1|level-2]$level (1-600)$intv", "IS-IS routing protocol\n" "Set Hello interval\n" "Specify hello-interval for level-1 IIHs\n" "Specify hello-interval for level-2 IIHs\n" "Holdtime 1 seconds, interval depends on multiplier\n") { if (!level || strmatch(level, "level-1")) nb_cli_enqueue_change(vty, "./frr-isisd:isis/hello/interval/level-1", NB_OP_MODIFY, intv_str); if (!level || strmatch(level, "level-2")) nb_cli_enqueue_change(vty, "./frr-isisd:isis/hello/interval/level-2", NB_OP_MODIFY, intv_str); return nb_cli_apply_changes(vty, NULL); } DEFPY(no_isis_hello_interval, no_isis_hello_interval_cmd, "no isis hello-interval [level-1|level-2]$level [(1-600)]", NO_STR "IS-IS routing protocol\n" "Set Hello interval\n" "Specify hello-interval for level-1 IIHs\n" "Specify hello-interval for level-2 IIHs\n" "Holdtime 1 second, interval depends on multiplier\n") { if (!level || strmatch(level, "level-1")) nb_cli_enqueue_change(vty, "./frr-isisd:isis/hello/interval/level-1", NB_OP_MODIFY, NULL); if (!level || strmatch(level, "level-2")) nb_cli_enqueue_change(vty, "./frr-isisd:isis/hello/interval/level-2", NB_OP_MODIFY, NULL); return nb_cli_apply_changes(vty, NULL); } void cli_show_ip_isis_hello_interval(struct vty *vty, struct lyd_node *dnode, bool show_defaults) { const char *l1 = yang_dnode_get_string(dnode, "./level-1"); const char *l2 = yang_dnode_get_string(dnode, "./level-2"); if (strmatch(l1, l2)) vty_out(vty, " isis hello-interval %s\n", l1); else { vty_out(vty, " isis hello-interval %s level-1\n", l1); vty_out(vty, " isis hello-interval %s level-2\n", l2); } } /* * XPath: /frr-interface:lib/interface/frr-isisd:isis/hello/multiplier */ DEFPY(isis_hello_multiplier, isis_hello_multiplier_cmd, "isis hello-multiplier [level-1|level-2]$level (2-100)$mult", "IS-IS routing protocol\n" "Set multiplier for Hello holding time\n" "Specify hello multiplier for level-1 IIHs\n" "Specify hello multiplier for level-2 IIHs\n" "Hello multiplier value\n") { if (!level || strmatch(level, "level-1")) nb_cli_enqueue_change( vty, "./frr-isisd:isis/hello/multiplier/level-1", NB_OP_MODIFY, mult_str); if (!level || strmatch(level, "level-2")) nb_cli_enqueue_change( vty, "./frr-isisd:isis/hello/multiplier/level-2", NB_OP_MODIFY, mult_str); return nb_cli_apply_changes(vty, NULL); } DEFPY(no_isis_hello_multiplier, no_isis_hello_multiplier_cmd, "no isis hello-multiplier [level-1|level-2]$level [(2-100)]", NO_STR "IS-IS routing protocol\n" "Set multiplier for Hello holding time\n" "Specify hello multiplier for level-1 IIHs\n" "Specify hello multiplier for level-2 IIHs\n" "Hello multiplier value\n") { if (!level || strmatch(level, "level-1")) nb_cli_enqueue_change( vty, "./frr-isisd:isis/hello/multiplier/level-1", NB_OP_MODIFY, NULL); if (!level || strmatch(level, "level-2")) nb_cli_enqueue_change( vty, "./frr-isisd:isis/hello/multiplier/level-2", NB_OP_MODIFY, NULL); return nb_cli_apply_changes(vty, NULL); } void cli_show_ip_isis_hello_multi(struct vty *vty, struct lyd_node *dnode, bool show_defaults) { const char *l1 = yang_dnode_get_string(dnode, "./level-1"); const char *l2 = yang_dnode_get_string(dnode, "./level-2"); if (strmatch(l1, l2)) vty_out(vty, " isis hello-multiplier %s\n", l1); else { vty_out(vty, " isis hello-multiplier %s level-1\n", l1); vty_out(vty, " isis hello-multiplier %s level-2\n", l2); } } /* * XPath: * /frr-interface:lib/interface/frr-isisd:isis/disable-three-way-handshake */ DEFPY(isis_threeway_adj, isis_threeway_adj_cmd, "[no] isis three-way-handshake", NO_STR "IS-IS commands\n" "Enable/Disable three-way handshake\n") { nb_cli_enqueue_change(vty, "./frr-isisd:isis/disable-three-way-handshake", NB_OP_MODIFY, no ? "true" : "false"); return nb_cli_apply_changes(vty, NULL); } void cli_show_ip_isis_threeway_shake(struct vty *vty, struct lyd_node *dnode, bool show_defaults) { if (yang_dnode_get_bool(dnode, NULL)) vty_out(vty, " no"); vty_out(vty, " isis three-way-handshake\n"); } /* * XPath: /frr-interface:lib/interface/frr-isisd:isis/hello/padding */ DEFPY(isis_hello_padding, isis_hello_padding_cmd, "[no] isis hello padding", NO_STR "IS-IS routing protocol\n" "Add padding to IS-IS hello packets\n" "Pad hello packets\n") { nb_cli_enqueue_change(vty, "./frr-isisd:isis/hello/padding", NB_OP_MODIFY, no ? "false" : "true"); return nb_cli_apply_changes(vty, NULL); } void cli_show_ip_isis_hello_padding(struct vty *vty, struct lyd_node *dnode, bool show_defaults) { if (!yang_dnode_get_bool(dnode, NULL)) vty_out(vty, " no"); vty_out(vty, " isis hello padding\n"); } /* * XPath: /frr-interface:lib/interface/frr-isisd:isis/csnp-interval */ DEFPY(csnp_interval, csnp_interval_cmd, "isis csnp-interval (1-600)$intv [level-1|level-2]$level", "IS-IS routing protocol\n" "Set CSNP interval in seconds\n" "CSNP interval value\n" "Specify interval for level-1 CSNPs\n" "Specify interval for level-2 CSNPs\n") { if (!level || strmatch(level, "level-1")) nb_cli_enqueue_change(vty, "./frr-isisd:isis/csnp-interval/level-1", NB_OP_MODIFY, intv_str); if (!level || strmatch(level, "level-2")) nb_cli_enqueue_change(vty, "./frr-isisd:isis/csnp-interval/level-2", NB_OP_MODIFY, intv_str); return nb_cli_apply_changes(vty, NULL); } DEFPY(no_csnp_interval, no_csnp_interval_cmd, "no isis csnp-interval [(1-600)] [level-1|level-2]$level", NO_STR "IS-IS routing protocol\n" "Set CSNP interval in seconds\n" "CSNP interval value\n" "Specify interval for level-1 CSNPs\n" "Specify interval for level-2 CSNPs\n") { if (!level || strmatch(level, "level-1")) nb_cli_enqueue_change(vty, "./frr-isisd:isis/csnp-interval/level-1", NB_OP_MODIFY, NULL); if (!level || strmatch(level, "level-2")) nb_cli_enqueue_change(vty, "./frr-isisd:isis/csnp-interval/level-2", NB_OP_MODIFY, NULL); return nb_cli_apply_changes(vty, NULL); } void cli_show_ip_isis_csnp_interval(struct vty *vty, struct lyd_node *dnode, bool show_defaults) { const char *l1 = yang_dnode_get_string(dnode, "./level-1"); const char *l2 = yang_dnode_get_string(dnode, "./level-2"); if (strmatch(l1, l2)) vty_out(vty, " isis csnp-interval %s\n", l1); else { vty_out(vty, " isis csnp-interval %s level-1\n", l1); vty_out(vty, " isis csnp-interval %s level-2\n", l2); } } /* * XPath: /frr-interface:lib/interface/frr-isisd:isis/psnp-interval */ DEFPY(psnp_interval, psnp_interval_cmd, "isis psnp-interval (1-120)$intv [level-1|level-2]$level", "IS-IS routing protocol\n" "Set PSNP interval in seconds\n" "PSNP interval value\n" "Specify interval for level-1 PSNPs\n" "Specify interval for level-2 PSNPs\n") { if (!level || strmatch(level, "level-1")) nb_cli_enqueue_change(vty, "./frr-isisd:isis/psnp-interval/level-1", NB_OP_MODIFY, intv_str); if (!level || strmatch(level, "level-2")) nb_cli_enqueue_change(vty, "./frr-isisd:isis/psnp-interval/level-2", NB_OP_MODIFY, intv_str); return nb_cli_apply_changes(vty, NULL); } DEFPY(no_psnp_interval, no_psnp_interval_cmd, "no isis psnp-interval [(1-120)] [level-1|level-2]$level", NO_STR "IS-IS routing protocol\n" "Set PSNP interval in seconds\n" "PSNP interval value\n" "Specify interval for level-1 PSNPs\n" "Specify interval for level-2 PSNPs\n") { if (!level || strmatch(level, "level-1")) nb_cli_enqueue_change(vty, "./frr-isisd:isis/psnp-interval/level-1", NB_OP_MODIFY, NULL); if (!level || strmatch(level, "level-2")) nb_cli_enqueue_change(vty, "./frr-isisd:isis/psnp-interval/level-2", NB_OP_MODIFY, NULL); return nb_cli_apply_changes(vty, NULL); } void cli_show_ip_isis_psnp_interval(struct vty *vty, struct lyd_node *dnode, bool show_defaults) { const char *l1 = yang_dnode_get_string(dnode, "./level-1"); const char *l2 = yang_dnode_get_string(dnode, "./level-2"); if (strmatch(l1, l2)) vty_out(vty, " isis psnp-interval %s\n", l1); else { vty_out(vty, " isis psnp-interval %s level-1\n", l1); vty_out(vty, " isis psnp-interval %s level-2\n", l2); } } /* * XPath: /frr-interface:lib/interface/frr-isisd:isis/multi-topology */ DEFPY(circuit_topology, circuit_topology_cmd, "[no] isis topology" "$topology", NO_STR "IS-IS routing protocol\n" "Configure interface IS-IS topologies\n" "IPv4 unicast topology\n" "IPv4 management topology\n" "IPv6 unicast topology\n" "IPv4 multicast topology\n" "IPv6 multicast topology\n" "IPv6 management topology\n" "IPv6 dst-src topology\n") { nb_cli_enqueue_change(vty, ".", NB_OP_MODIFY, no ? "false" : "true"); if (strmatch(topology, "ipv4-mgmt")) return nb_cli_apply_changes( vty, "./frr-isisd:isis/multi-topology/ipv4-management"); else if (strmatch(topology, "ipv6-mgmt")) return nb_cli_apply_changes( vty, "./frr-isisd:isis/multi-topology/ipv6-management"); else return nb_cli_apply_changes( vty, "./frr-isisd:isis/multi-topology/%s", topology); } void cli_show_ip_isis_mt_ipv4_unicast(struct vty *vty, struct lyd_node *dnode, bool show_defaults) { if (!yang_dnode_get_bool(dnode, NULL)) vty_out(vty, " no"); vty_out(vty, " isis topology ipv4-unicast\n"); } void cli_show_ip_isis_mt_ipv4_multicast(struct vty *vty, struct lyd_node *dnode, bool show_defaults) { if (!yang_dnode_get_bool(dnode, NULL)) vty_out(vty, " no"); vty_out(vty, " isis topology ipv4-multicast\n"); } void cli_show_ip_isis_mt_ipv4_mgmt(struct vty *vty, struct lyd_node *dnode, bool show_defaults) { if (!yang_dnode_get_bool(dnode, NULL)) vty_out(vty, " no"); vty_out(vty, " isis topology ipv4-mgmt\n"); } void cli_show_ip_isis_mt_ipv6_unicast(struct vty *vty, struct lyd_node *dnode, bool show_defaults) { if (!yang_dnode_get_bool(dnode, NULL)) vty_out(vty, " no"); vty_out(vty, " isis topology ipv6-unicast\n"); } void cli_show_ip_isis_mt_ipv6_multicast(struct vty *vty, struct lyd_node *dnode, bool show_defaults) { if (!yang_dnode_get_bool(dnode, NULL)) vty_out(vty, " no"); vty_out(vty, " isis topology ipv6-multicast\n"); } void cli_show_ip_isis_mt_ipv6_mgmt(struct vty *vty, struct lyd_node *dnode, bool show_defaults) { if (!yang_dnode_get_bool(dnode, NULL)) vty_out(vty, " no"); vty_out(vty, " isis topology ipv6-mgmt\n"); } void cli_show_ip_isis_mt_ipv6_dstsrc(struct vty *vty, struct lyd_node *dnode, bool show_defaults) { if (!yang_dnode_get_bool(dnode, NULL)) vty_out(vty, " no"); vty_out(vty, " isis topology ipv6-dstsrc\n"); } /* * XPath: /frr-interface:lib/interface/frr-isisd:isis/circuit-type */ DEFPY(isis_circuit_type, isis_circuit_type_cmd, "isis circuit-type $type", "IS-IS routing protocol\n" "Configure circuit type for interface\n" "Level-1 only adjacencies are formed\n" "Level-1-2 adjacencies are formed\n" "Level-2 only adjacencies are formed\n") { nb_cli_enqueue_change( vty, "./frr-isisd:isis/circuit-type", NB_OP_MODIFY, strmatch(type, "level-2-only") ? "level-2" : type); return nb_cli_apply_changes(vty, NULL); } DEFPY(no_isis_circuit_type, no_isis_circuit_type_cmd, "no isis circuit-type [level-1|level-1-2|level-2-only]", NO_STR "IS-IS routing protocol\n" "Configure circuit type for interface\n" "Level-1 only adjacencies are formed\n" "Level-1-2 adjacencies are formed\n" "Level-2 only adjacencies are formed\n") { const char *circ_type = NULL; /* * Default value depends on whether the circuit is part of an area, * and the is-type of the area if there is one. So we need to do this * here. */ pthread_rwlock_rdlock(&running_config->lock); { struct interface *ifp; struct isis_circuit *circuit; ifp = nb_running_get_entry(NULL, VTY_CURR_XPATH, false); if (!ifp) goto unlock; circuit = circuit_scan_by_ifp(ifp); if (!circuit || circuit->state != C_STATE_UP) goto unlock; switch (circuit->area->is_type) { case IS_LEVEL_1: circ_type = "level-1"; break; case IS_LEVEL_2: circ_type = "level-2"; break; case IS_LEVEL_1_AND_2: circ_type = "level-1-2"; break; } } unlock: pthread_rwlock_unlock(&running_config->lock); nb_cli_enqueue_change(vty, "./frr-isisd:isis/circuit-type", NB_OP_MODIFY, circ_type); return nb_cli_apply_changes(vty, NULL); } void cli_show_ip_isis_circ_type(struct vty *vty, struct lyd_node *dnode, bool show_defaults) { int level = yang_dnode_get_enum(dnode, NULL); switch (level) { case IS_LEVEL_1: vty_out(vty, " isis circuit-type level-1\n"); break; case IS_LEVEL_2: vty_out(vty, " isis circuit-type level-2-only\n"); break; case IS_LEVEL_1_AND_2: vty_out(vty, " isis circuit-type level-1-2\n"); break; } } /* * XPath: /frr-interface:lib/interface/frr-isisd:isis/network-type */ DEFPY(isis_network, isis_network_cmd, "[no] isis network point-to-point", NO_STR "IS-IS routing protocol\n" "Set network type\n" "point-to-point network type\n") { nb_cli_enqueue_change(vty, "./frr-isisd:isis/network-type", NB_OP_MODIFY, no ? "broadcast" : "point-to-point"); return nb_cli_apply_changes(vty, NULL); } void cli_show_ip_isis_network_type(struct vty *vty, struct lyd_node *dnode, bool show_defaults) { if (yang_dnode_get_enum(dnode, NULL) != CIRCUIT_T_P2P) vty_out(vty, " no"); vty_out(vty, " isis network point-to-point\n"); } /* * XPath: /frr-interface:lib/interface/frr-isisd:isis/priority */ DEFPY(isis_priority, isis_priority_cmd, "isis priority (0-127)$prio [level-1|level-2]$level", "IS-IS routing protocol\n" "Set priority for Designated Router election\n" "Priority value\n" "Specify priority for level-1 routing\n" "Specify priority for level-2 routing\n") { if (!level || strmatch(level, "level-1")) nb_cli_enqueue_change(vty, "./frr-isisd:isis/priority/level-1", NB_OP_MODIFY, prio_str); if (!level || strmatch(level, "level-2")) nb_cli_enqueue_change(vty, "./frr-isisd:isis/priority/level-2", NB_OP_MODIFY, prio_str); return nb_cli_apply_changes(vty, NULL); } DEFPY(no_isis_priority, no_isis_priority_cmd, "no isis priority [(0-127)] [level-1|level-2]$level", NO_STR "IS-IS routing protocol\n" "Set priority for Designated Router election\n" "Priority value\n" "Specify priority for level-1 routing\n" "Specify priority for level-2 routing\n") { if (!level || strmatch(level, "level-1")) nb_cli_enqueue_change(vty, "./frr-isisd:isis/priority/level-1", NB_OP_MODIFY, NULL); if (!level || strmatch(level, "level-2")) nb_cli_enqueue_change(vty, "./frr-isisd:isis/priority/level-2", NB_OP_MODIFY, NULL); return nb_cli_apply_changes(vty, NULL); } void cli_show_ip_isis_priority(struct vty *vty, struct lyd_node *dnode, bool show_defaults) { const char *l1 = yang_dnode_get_string(dnode, "./level-1"); const char *l2 = yang_dnode_get_string(dnode, "./level-2"); if (strmatch(l1, l2)) vty_out(vty, " isis priority %s\n", l1); else { vty_out(vty, " isis priority %s level-1\n", l1); vty_out(vty, " isis priority %s level-2\n", l2); } } /* * XPath: /frr-isisd:isis/instance/log-adjacency-changes */ DEFPY(log_adj_changes, log_adj_changes_cmd, "[no] log-adjacency-changes", NO_STR "Log changes in adjacency state\n") { nb_cli_enqueue_change(vty, "./log-adjacency-changes", NB_OP_MODIFY, no ? "false" : "true"); return nb_cli_apply_changes(vty, NULL); } void cli_show_isis_log_adjacency(struct vty *vty, struct lyd_node *dnode, bool show_defaults) { if (!yang_dnode_get_bool(dnode, NULL)) vty_out(vty, " no"); vty_out(vty, " log-adjacency-changes\n"); } void isis_cli_init(void) { install_element(CONFIG_NODE, &router_isis_cmd); install_element(CONFIG_NODE, &no_router_isis_cmd); install_element(INTERFACE_NODE, &ip_router_isis_cmd); install_element(INTERFACE_NODE, &ip6_router_isis_cmd); install_element(INTERFACE_NODE, &no_ip_router_isis_cmd); install_element(INTERFACE_NODE, &isis_bfd_cmd); install_element(ISIS_NODE, &net_cmd); install_element(ISIS_NODE, &is_type_cmd); install_element(ISIS_NODE, &no_is_type_cmd); install_element(ISIS_NODE, &dynamic_hostname_cmd); install_element(ISIS_NODE, &set_overload_bit_cmd); install_element(ISIS_NODE, &set_attached_bit_cmd); install_element(ISIS_NODE, &metric_style_cmd); install_element(ISIS_NODE, &no_metric_style_cmd); install_element(ISIS_NODE, &area_passwd_cmd); install_element(ISIS_NODE, &domain_passwd_cmd); install_element(ISIS_NODE, &no_area_passwd_cmd); install_element(ISIS_NODE, &lsp_gen_interval_cmd); install_element(ISIS_NODE, &no_lsp_gen_interval_cmd); install_element(ISIS_NODE, &lsp_refresh_interval_cmd); install_element(ISIS_NODE, &no_lsp_refresh_interval_cmd); install_element(ISIS_NODE, &max_lsp_lifetime_cmd); install_element(ISIS_NODE, &no_max_lsp_lifetime_cmd); install_element(ISIS_NODE, &area_lsp_mtu_cmd); install_element(ISIS_NODE, &no_area_lsp_mtu_cmd); install_element(ISIS_NODE, &spf_interval_cmd); install_element(ISIS_NODE, &no_spf_interval_cmd); install_element(ISIS_NODE, &spf_delay_ietf_cmd); install_element(ISIS_NODE, &no_spf_delay_ietf_cmd); install_element(ISIS_NODE, &area_purge_originator_cmd); install_element(ISIS_NODE, &isis_mpls_te_on_cmd); install_element(ISIS_NODE, &no_isis_mpls_te_on_cmd); install_element(ISIS_NODE, &isis_mpls_te_router_addr_cmd); install_element(ISIS_NODE, &no_isis_mpls_te_router_addr_cmd); install_element(ISIS_NODE, &isis_mpls_te_inter_as_cmd); install_element(ISIS_NODE, &isis_default_originate_cmd); install_element(ISIS_NODE, &isis_redistribute_cmd); install_element(ISIS_NODE, &isis_topology_cmd); install_element(INTERFACE_NODE, &isis_passive_cmd); install_element(INTERFACE_NODE, &isis_passwd_cmd); install_element(INTERFACE_NODE, &no_isis_passwd_cmd); install_element(INTERFACE_NODE, &isis_metric_cmd); install_element(INTERFACE_NODE, &no_isis_metric_cmd); install_element(INTERFACE_NODE, &isis_hello_interval_cmd); install_element(INTERFACE_NODE, &no_isis_hello_interval_cmd); install_element(INTERFACE_NODE, &isis_hello_multiplier_cmd); install_element(INTERFACE_NODE, &no_isis_hello_multiplier_cmd); install_element(INTERFACE_NODE, &isis_threeway_adj_cmd); install_element(INTERFACE_NODE, &isis_hello_padding_cmd); install_element(INTERFACE_NODE, &csnp_interval_cmd); install_element(INTERFACE_NODE, &no_csnp_interval_cmd); install_element(INTERFACE_NODE, &psnp_interval_cmd); install_element(INTERFACE_NODE, &no_psnp_interval_cmd); install_element(INTERFACE_NODE, &circuit_topology_cmd); install_element(INTERFACE_NODE, &isis_circuit_type_cmd); install_element(INTERFACE_NODE, &no_isis_circuit_type_cmd); install_element(INTERFACE_NODE, &isis_network_cmd); install_element(INTERFACE_NODE, &isis_priority_cmd); install_element(INTERFACE_NODE, &no_isis_priority_cmd); install_element(ISIS_NODE, &log_adj_changes_cmd); } #endif /* ifndef FABRICD */ frr-7.2.1/isisd/isis_cli.h0000644000000000000000000001410713610377563012301 00000000000000/* * Copyright (C) 2018 Volta Networks * Emanuele Di Pascale * * This program is free software; you can redistribute it and/or modify it * under the terms of the 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; see the file COPYING; if not, write to the Free Software * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA */ #ifndef ISISD_ISIS_CLI_H_ #define ISISD_ISIS_CLI_H_ /* add cli_show declarations here as externs */ void cli_show_router_isis(struct vty *vty, struct lyd_node *dnode, bool show_defaults); void cli_show_ip_isis_ipv4(struct vty *vty, struct lyd_node *dnode, bool show_defaults); void cli_show_ip_isis_ipv6(struct vty *vty, struct lyd_node *dnode, bool show_defaults); void cli_show_ip_isis_bfd_monitoring(struct vty *vty, struct lyd_node *dnode, bool show_defaults); void cli_show_isis_area_address(struct vty *vty, struct lyd_node *dnode, bool show_defaults); void cli_show_isis_is_type(struct vty *vty, struct lyd_node *dnode, bool show_defaults); void cli_show_isis_dynamic_hostname(struct vty *vty, struct lyd_node *dnode, bool show_defaults); void cli_show_isis_attached(struct vty *vty, struct lyd_node *dnode, bool show_defaults); void cli_show_isis_overload(struct vty *vty, struct lyd_node *dnode, bool show_defaults); void cli_show_isis_metric_style(struct vty *vty, struct lyd_node *dnode, bool show_defaults); void cli_show_isis_area_pwd(struct vty *vty, struct lyd_node *dnode, bool show_defaults); void cli_show_isis_domain_pwd(struct vty *vty, struct lyd_node *dnode, bool show_defaults); void cli_show_isis_lsp_gen_interval(struct vty *vty, struct lyd_node *dnode, bool show_defaults); void cli_show_isis_lsp_ref_interval(struct vty *vty, struct lyd_node *dnode, bool show_defaults); void cli_show_isis_lsp_max_lifetime(struct vty *vty, struct lyd_node *dnode, bool show_defaults); void cli_show_isis_lsp_mtu(struct vty *vty, struct lyd_node *dnode, bool show_defaults); void cli_show_isis_spf_min_interval(struct vty *vty, struct lyd_node *dnode, bool show_defaults); void cli_show_isis_spf_ietf_backoff(struct vty *vty, struct lyd_node *dnode, bool show_defaults); void cli_show_isis_purge_origin(struct vty *vty, struct lyd_node *dnode, bool show_defaults); void cli_show_isis_mpls_te(struct vty *vty, struct lyd_node *dnode, bool show_defaults); void cli_show_isis_mpls_te_router_addr(struct vty *vty, struct lyd_node *dnode, bool show_defaults); void cli_show_isis_def_origin_ipv4(struct vty *vty, struct lyd_node *dnode, bool show_defaults); void cli_show_isis_def_origin_ipv6(struct vty *vty, struct lyd_node *dnode, bool show_defaults); void cli_show_isis_redistribute_ipv4(struct vty *vty, struct lyd_node *dnode, bool show_defaults); void cli_show_isis_redistribute_ipv6(struct vty *vty, struct lyd_node *dnode, bool show_defaults); void cli_show_isis_mt_ipv4_multicast(struct vty *vty, struct lyd_node *dnode, bool show_defaults); void cli_show_isis_mt_ipv4_mgmt(struct vty *vty, struct lyd_node *dnode, bool show_defaults); void cli_show_isis_mt_ipv6_unicast(struct vty *vty, struct lyd_node *dnode, bool show_defaults); void cli_show_isis_mt_ipv6_multicast(struct vty *vty, struct lyd_node *dnode, bool show_defaults); void cli_show_isis_mt_ipv6_mgmt(struct vty *vty, struct lyd_node *dnode, bool show_defaults); void cli_show_isis_mt_ipv6_dstsrc(struct vty *vty, struct lyd_node *dnode, bool show_defaults); void cli_show_ip_isis_passive(struct vty *vty, struct lyd_node *dnode, bool show_defaults); void cli_show_ip_isis_password(struct vty *vty, struct lyd_node *dnode, bool show_defaults); void cli_show_ip_isis_metric(struct vty *vty, struct lyd_node *dnode, bool show_defaults); void cli_show_ip_isis_hello_interval(struct vty *vty, struct lyd_node *dnode, bool show_defaults); void cli_show_ip_isis_hello_multi(struct vty *vty, struct lyd_node *dnode, bool show_defaults); void cli_show_ip_isis_threeway_shake(struct vty *vty, struct lyd_node *dnode, bool show_defaults); void cli_show_ip_isis_hello_padding(struct vty *vty, struct lyd_node *dnode, bool show_defaults); void cli_show_ip_isis_csnp_interval(struct vty *vty, struct lyd_node *dnode, bool show_defaults); void cli_show_ip_isis_psnp_interval(struct vty *vty, struct lyd_node *dnode, bool show_defaults); void cli_show_ip_isis_mt_ipv4_unicast(struct vty *vty, struct lyd_node *dnode, bool show_defaults); void cli_show_ip_isis_mt_ipv4_multicast(struct vty *vty, struct lyd_node *dnode, bool show_defaults); void cli_show_ip_isis_mt_ipv4_mgmt(struct vty *vty, struct lyd_node *dnode, bool show_defaults); void cli_show_ip_isis_mt_ipv6_unicast(struct vty *vty, struct lyd_node *dnode, bool show_defaults); void cli_show_ip_isis_mt_ipv6_multicast(struct vty *vty, struct lyd_node *dnode, bool show_defaults); void cli_show_ip_isis_mt_ipv6_mgmt(struct vty *vty, struct lyd_node *dnode, bool show_defaults); void cli_show_ip_isis_mt_ipv6_dstsrc(struct vty *vty, struct lyd_node *dnode, bool show_defaults); void cli_show_ip_isis_circ_type(struct vty *vty, struct lyd_node *dnode, bool show_defaults); void cli_show_ip_isis_network_type(struct vty *vty, struct lyd_node *dnode, bool show_defaults); void cli_show_ip_isis_priority(struct vty *vty, struct lyd_node *dnode, bool show_defaults); void cli_show_isis_log_adjacency(struct vty *vty, struct lyd_node *dnode, bool show_defaults); #endif /* ISISD_ISIS_CLI_H_ */ frr-7.2.1/isisd/isis_common.h0000644000000000000000000000310213610377563013013 00000000000000/* * IS-IS Rout(e)ing protocol - isis_common.h * some common data structures * * Copyright (C) 2001,2002 Sampo Saaristo * Tampere University of Technology * Institute of Communications Engineering * * This program is free software; you can redistribute it and/or modify it * under the terms of the GNU General Public Licenseas 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; see the file COPYING; if not, write to the Free Software * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA */ #ifndef ISIS_COMMON_H #define ISIS_COMMON_H /* * Area Address */ struct area_addr { uint8_t addr_len; uint8_t area_addr[20]; }; struct isis_passwd { uint8_t len; #define ISIS_PASSWD_TYPE_UNUSED 0 #define ISIS_PASSWD_TYPE_CLEARTXT 1 #define ISIS_PASSWD_TYPE_HMAC_MD5 54 #define ISIS_PASSWD_TYPE_PRIVATE 255 uint8_t type; /* Authenticate SNPs? */ #define SNP_AUTH_SEND 0x01 #define SNP_AUTH_RECV 0x02 uint8_t snp_auth; uint8_t passwd[255]; }; /* * Supported Protocol IDs */ struct nlpids { uint8_t count; uint8_t nlpids[4]; /* FIXME: enough ? */ }; #endif frr-7.2.1/isisd/isis_constants.h0000644000000000000000000001206313610377563013545 00000000000000/* * IS-IS Rout(e)ing protocol - isis_constants.h * * Copyright (C) 2001,2002 Sampo Saaristo * Tampere University of Technology * Institute of Communications Engineering * * This program is free software; you can redistribute it and/or modify it * under the terms of the GNU General Public Licenseas 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; see the file COPYING; if not, write to the Free Software * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA */ #ifndef ISIS_CONSTANTS_H #define ISIS_CONSTANTS_H /* * Architectural constant values from p. 35 of ISO/IEC 10589 */ #define MAX_NARROW_LINK_METRIC 63 #define MAX_NARROW_PATH_METRIC 1023 #define MAX_WIDE_LINK_METRIC 0x00FFFFFF /* RFC4444 */ #define MAX_WIDE_PATH_METRIC 0xFE000000 /* RFC3787 */ #define ISO_SAP 0xFE #define INTRADOMAIN_ROUTEING_SELECTOR 0 #define SEQUENCE_MODULUS 4294967296 #define ISO9542_ESIS 0x82 #define ISO10589_ISIS 0x83 /* * implementation specific jitter values */ #define IIH_JITTER 10 /* % */ #define MAX_AGE_JITTER 5 /* % */ #define MAX_LSP_GEN_JITTER 5 /* % */ #define CSNP_JITTER 10 /* % */ #define PSNP_JITTER 10 /* % */ #define RANDOM_SPREAD 100000.0 #define ISIS_LEVELS 2 #define ISIS_LEVEL1 1 #define ISIS_LEVEL2 2 /* * Default values * ISO - 10589 Section 7.3.21 - Parameters * RFC 4444 */ #define MAX_AGE 1200 #define ZERO_AGE_LIFETIME 60 #define MIN_LSP_LIFETIME 350 #define MAX_LSP_LIFETIME 65535 #define DEFAULT_LSP_LIFETIME 1200 #define MIN_MAX_LSP_GEN_INTERVAL 1 #define MAX_MAX_LSP_GEN_INTERVAL 65235 #define DEFAULT_MAX_LSP_GEN_INTERVAL 900 #define MIN_MIN_LSP_GEN_INTERVAL 1 #define MAX_MIN_LSP_GEN_INTERVAL 120 /* RFC 4444 says 65535 */ #define DEFAULT_MIN_LSP_GEN_INTERVAL 30 #define MIN_LSP_RETRANS_INTERVAL 5 /* Seconds */ #define TRIGGERED_IIH_DELAY 50 /* msec */ #define MIN_CSNP_INTERVAL 1 #define MAX_CSNP_INTERVAL 600 #define DEFAULT_CSNP_INTERVAL 10 #define MIN_PSNP_INTERVAL 1 #define MAX_PSNP_INTERVAL 120 #define DEFAULT_PSNP_INTERVAL 2 #define MIN_HELLO_INTERVAL 1 #define MAX_HELLO_INTERVAL 600 #define DEFAULT_HELLO_INTERVAL 3 #define MIN_HELLO_MULTIPLIER 2 #define MAX_HELLO_MULTIPLIER 100 #define DEFAULT_HELLO_MULTIPLIER 10 #define MIN_PRIORITY 0 #define MAX_PRIORITY 127 #define DEFAULT_PRIORITY 64 /* min and max metric varies by new vs old metric types */ #define DEFAULT_CIRCUIT_METRIC 10 #define METRICS_UNSUPPORTED 0x80 #define MINIMUM_SPF_INTERVAL 1 #define ISIS_MAX_PATH_SPLITS 64 /* * NLPID values */ #define NLPID_IP 204 #define NLPID_IPV6 142 #define NLPID_SNAP 128 #define NLPID_CLNP 129 #define NLPID_ESIS 130 /* * Return values for functions */ #define ISIS_OK 0 #define ISIS_WARNING 1 #define ISIS_ERROR 2 #define ISIS_CRITICAL 3 /* * IS-IS Circuit Types */ #define IS_LEVEL_1 1 #define IS_LEVEL_2 2 #define IS_LEVEL_1_AND_2 3 #define SNPA_ADDRSTRLEN 18 #define ISIS_SYS_ID_LEN 6 #define ISIS_NSEL_LEN 1 #define SYSID_STRLEN 24 /* * LSP bit masks */ #define LSPBIT_P 0x80 #define LSPBIT_ATT 0x78 #define LSPBIT_OL 0x04 #define LSPBIT_IST 0x03 /* * LSP bit masking macros * taken from tcpdumps * print-isoclns.c */ #define ISIS_MASK_LSP_OL_BIT(x) ((x)&0x4) #define ISIS_MASK_LSP_IS_L1_BIT(x) ((x)&0x1) #define ISIS_MASK_LSP_IS_L2_BIT(x) ((x)&0x2) #define ISIS_MASK_LSP_PARTITION_BIT(x) ((x)&0x80) #define ISIS_MASK_LSP_ATT_BITS(x) ((x)&0x78) #define ISIS_MASK_LSP_ATT_ERROR_BIT(x) ((x)&0x40) #define ISIS_MASK_LSP_ATT_EXPENSE_BIT(x) ((x)&0x20) #define ISIS_MASK_LSP_ATT_DELAY_BIT(x) ((x)&0x10) #define ISIS_MASK_LSP_ATT_DEFAULT_BIT(x) ((x)&0x8) #define LLC_LEN 3 /* we need to be aware of the fact we are using ISO sized * packets, using isomtu = mtu - LLC_LEN */ #define ISO_MTU(C) \ ((if_is_broadcast((C)->interface)) ? (C->interface->mtu - LLC_LEN) \ : (C->interface->mtu)) #define MAX_LLC_LEN 0x5ff #define ETHERTYPE_EXT_LLC 0x8870 static inline uint16_t isis_ethertype(size_t len) { if (len > MAX_LLC_LEN) return ETHERTYPE_EXT_LLC; return len; } #endif /* ISIS_CONSTANTS_H */ frr-7.2.1/isisd/isis_csm.c0000644000000000000000000001361613610377563012313 00000000000000/* * IS-IS Rout(e)ing protocol - isis_csm.c * IS-IS circuit state machine * Copyright (C) 2001,2002 Sampo Saaristo * Tampere University of Technology * Institute of Communications Engineering * * This program is free software; you can redistribute it and/or modify it * under the terms of the GNU General Public Licenseas 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; see the file COPYING; if not, write to the Free Software * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA */ #include #include "log.h" #include "memory.h" #include "if.h" #include "linklist.h" #include "command.h" #include "thread.h" #include "hash.h" #include "prefix.h" #include "stream.h" #include "isisd/isis_constants.h" #include "isisd/isis_common.h" #include "isisd/isis_flags.h" #include "isisd/isis_circuit.h" #include "isisd/isis_lsp.h" #include "isisd/isis_pdu.h" #include "isisd/isis_network.h" #include "isisd/isis_misc.h" #include "isisd/isis_constants.h" #include "isisd/isis_adjacency.h" #include "isisd/isis_dr.h" #include "isisd/isisd.h" #include "isisd/isis_csm.h" #include "isisd/isis_events.h" #include "isisd/isis_errors.h" extern struct isis *isis; static const char *csm_statestr[] = {"C_STATE_NA", "C_STATE_INIT", "C_STATE_CONF", "C_STATE_UP"}; #define STATE2STR(S) csm_statestr[S] static const char *csm_eventstr[] = { "NO_STATE", "ISIS_ENABLE", "IF_UP_FROM_Z", "ISIS_DISABLE", "IF_DOWN_FROM_Z", }; #define EVENT2STR(E) csm_eventstr[E] struct isis_circuit * isis_csm_state_change(int event, struct isis_circuit *circuit, void *arg) { int old_state; old_state = circuit ? circuit->state : C_STATE_NA; if (isis->debugs & DEBUG_EVENTS) zlog_debug("CSM_EVENT: %s", EVENT2STR(event)); switch (old_state) { case C_STATE_NA: if (circuit) zlog_warn("Non-null circuit while state C_STATE_NA"); assert(circuit == NULL); switch (event) { case ISIS_ENABLE: circuit = isis_circuit_new(); isis_circuit_configure(circuit, (struct isis_area *)arg); circuit->state = C_STATE_CONF; break; case IF_UP_FROM_Z: circuit = isis_circuit_new(); isis_circuit_if_add(circuit, (struct interface *)arg); listnode_add(isis->init_circ_list, circuit); circuit->state = C_STATE_INIT; break; case ISIS_DISABLE: zlog_warn("circuit already disabled"); break; case IF_DOWN_FROM_Z: zlog_warn("circuit already disconnected"); break; } break; case C_STATE_INIT: assert(circuit); switch (event) { case ISIS_ENABLE: isis_circuit_configure(circuit, (struct isis_area *)arg); if (isis_circuit_up(circuit) != ISIS_OK) { isis_circuit_deconfigure( circuit, (struct isis_area *)arg); break; } circuit->state = C_STATE_UP; isis_event_circuit_state_change(circuit, circuit->area, 1); listnode_delete(isis->init_circ_list, circuit); break; case IF_UP_FROM_Z: assert(circuit); zlog_warn("circuit already connected"); break; case ISIS_DISABLE: zlog_warn("circuit already disabled"); break; case IF_DOWN_FROM_Z: isis_circuit_if_del(circuit, (struct interface *)arg); listnode_delete(isis->init_circ_list, circuit); isis_circuit_del(circuit); circuit = NULL; break; } break; case C_STATE_CONF: assert(circuit); switch (event) { case ISIS_ENABLE: zlog_warn("circuit already enabled"); break; case IF_UP_FROM_Z: isis_circuit_if_add(circuit, (struct interface *)arg); if (isis_circuit_up(circuit) != ISIS_OK) { flog_err( EC_ISIS_CONFIG, "Could not bring up %s because of invalid config.", circuit->interface->name); flog_err( EC_ISIS_CONFIG, "Clearing config for %s. Please re-examine it.", circuit->interface->name); if (circuit->ip_router) { circuit->ip_router = 0; circuit->area->ip_circuits--; } if (circuit->ipv6_router) { circuit->ipv6_router = 0; circuit->area->ipv6_circuits--; } circuit_update_nlpids(circuit); isis_circuit_deconfigure(circuit, circuit->area); listnode_add(isis->init_circ_list, circuit); circuit->state = C_STATE_INIT; break; } circuit->state = C_STATE_UP; isis_event_circuit_state_change(circuit, circuit->area, 1); break; case ISIS_DISABLE: isis_circuit_deconfigure(circuit, (struct isis_area *)arg); isis_circuit_del(circuit); circuit = NULL; break; case IF_DOWN_FROM_Z: zlog_warn("circuit already disconnected"); break; } break; case C_STATE_UP: assert(circuit); switch (event) { case ISIS_ENABLE: zlog_warn("circuit already configured"); break; case IF_UP_FROM_Z: zlog_warn("circuit already connected"); break; case ISIS_DISABLE: isis_circuit_down(circuit); isis_circuit_deconfigure(circuit, (struct isis_area *)arg); circuit->state = C_STATE_INIT; isis_event_circuit_state_change( circuit, (struct isis_area *)arg, 0); listnode_add(isis->init_circ_list, circuit); break; case IF_DOWN_FROM_Z: isis_circuit_down(circuit); isis_circuit_if_del(circuit, (struct interface *)arg); circuit->state = C_STATE_CONF; isis_event_circuit_state_change(circuit, circuit->area, 0); break; } break; default: zlog_warn("Invalid circuit state %d", old_state); } if (isis->debugs & DEBUG_EVENTS) zlog_debug("CSM_STATE_CHANGE: %s -> %s ", STATE2STR(old_state), circuit ? STATE2STR(circuit->state) : STATE2STR(C_STATE_NA)); return circuit; } frr-7.2.1/isisd/isis_csm.h0000644000000000000000000000300413610377563012306 00000000000000/* * IS-IS Rout(e)ing protocol - isis_csm.h * IS-IS circuit state machine * * Copyright (C) 2001,2002 Sampo Saaristo * Tampere University of Technology * Institute of Communications Engineering * * * This program is free software; you can redistribute it and/or modify it * under the terms of the GNU General Public Licenseas 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; see the file COPYING; if not, write to the Free Software * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA */ #ifndef _ZEBRA_ISIS_CSM_H #define _ZEBRA_ISIS_CSM_H /* * Circuit states */ #define C_STATE_NA 0 #define C_STATE_INIT 1 /* Connected to interface */ #define C_STATE_CONF 2 /* Configured for ISIS */ #define C_STATE_UP 3 /* CONN | CONF */ /* * Circuit events */ #define ISIS_ENABLE 1 #define IF_UP_FROM_Z 2 #define ISIS_DISABLE 3 #define IF_DOWN_FROM_Z 4 struct isis_circuit * isis_csm_state_change(int event, struct isis_circuit *circuit, void *arg); #endif /* _ZEBRA_ISIS_CSM_H */ frr-7.2.1/isisd/isis_dlpi.c0000644000000000000000000003667513610377563012473 00000000000000/* * IS-IS Rout(e)ing protocol - isis_dlpi.c * * Copyright (C) 2001,2002 Sampo Saaristo * Tampere University of Technology * Institute of Communications Engineering * * This program is free software; you can redistribute it and/or modify it * under the terms of the GNU General Public Licenseas 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; see the file COPYING; if not, write to the Free Software * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA */ #include #if ISIS_METHOD == ISIS_METHOD_DLPI #include #include #include #include #include #include #include #include #include #include "log.h" #include "network.h" #include "stream.h" #include "if.h" #include "lib_errors.h" #include "isisd/isis_constants.h" #include "isisd/isis_common.h" #include "isisd/isis_circuit.h" #include "isisd/isis_flags.h" #include "isisd/isisd.h" #include "isisd/isis_network.h" #include "privs.h" static t_uscalar_t dlpi_ctl[1024]; /* DLPI control messages */ /* * Table 9 - Architectural constants for use with ISO 8802 subnetworks * ISO 10589 - 8.4.8 */ uint8_t ALL_L1_ISS[6] = {0x01, 0x80, 0xC2, 0x00, 0x00, 0x14}; uint8_t ALL_L2_ISS[6] = {0x01, 0x80, 0xC2, 0x00, 0x00, 0x15}; uint8_t ALL_ISS[6] = {0x09, 0x00, 0x2B, 0x00, 0x00, 0x05}; uint8_t ALL_ESS[6] = {0x09, 0x00, 0x2B, 0x00, 0x00, 0x04}; static uint8_t sock_buff[8192]; static unsigned short pf_filter[] = { ENF_PUSHWORD + 0, /* Get the SSAP/DSAP values */ ENF_PUSHLIT | ENF_CAND, /* Check them */ ISO_SAP | (ISO_SAP << 8), ENF_PUSHWORD + 1, /* Get the control value */ ENF_PUSHLIT | ENF_AND, /* Isolate it */ #ifdef _BIG_ENDIAN 0xFF00, #else 0x00FF, #endif ENF_PUSHLIT | ENF_CAND, /* Test for expected value */ #ifdef _BIG_ENDIAN 0x0300 #else 0x0003 #endif }; /* * We would like to use something like libdlpi here, but that's not present on * all versions of Solaris or on any non-Solaris system, so it's nowhere near * as portable as we'd like. Thus, we use the standards-conformant DLPI * interfaces plus the (optional; not needed) Solaris packet filter module. */ static int dlpisend(int fd, const void *cbuf, size_t cbuflen, const void *dbuf, size_t dbuflen, int flags) { const struct strbuf *ctlptr = NULL; const struct strbuf *dataptr = NULL; struct strbuf ctlbuf, databuf; int rv; if (cbuf != NULL) { memset(&ctlbuf, 0, sizeof(ctlbuf)); ctlbuf.len = cbuflen; ctlbuf.buf = (void *)cbuf; ctlptr = &ctlbuf; } if (dbuf != NULL) { memset(&databuf, 0, sizeof(databuf)); databuf.len = dbuflen; databuf.buf = (void *)dbuf; dataptr = &databuf; } /* We assume this doesn't happen often and isn't operationally * significant */ rv = putmsg(fd, ctlptr, dataptr, flags); if (rv == -1 && dbuf == NULL) { /* * For actual PDU transmission - recognizable buf dbuf != NULL, * the error is passed upwards and should not be printed here. */ zlog_debug("%s: putmsg: %s", __func__, safe_strerror(errno)); } return rv; } static ssize_t dlpirctl(int fd) { struct pollfd fds[1]; struct strbuf ctlbuf, databuf; int flags, retv; do { /* Poll is used here in case the device doesn't speak DLPI * correctly */ memset(fds, 0, sizeof(fds)); fds[0].fd = fd; fds[0].events = POLLIN | POLLPRI; if (poll(fds, 1, 1000) <= 0) return -1; memset(&ctlbuf, 0, sizeof(ctlbuf)); memset(&databuf, 0, sizeof(databuf)); ctlbuf.maxlen = sizeof(dlpi_ctl); ctlbuf.buf = (void *)dlpi_ctl; databuf.maxlen = sizeof(sock_buff); databuf.buf = (void *)sock_buff; flags = 0; retv = getmsg(fd, &ctlbuf, &databuf, &flags); if (retv < 0) return -1; } while (ctlbuf.len == 0); if (!(retv & MORECTL)) { while (retv & MOREDATA) { flags = 0; retv = getmsg(fd, NULL, &databuf, &flags); } return ctlbuf.len; } while (retv & MORECTL) { flags = 0; retv = getmsg(fd, &ctlbuf, &databuf, &flags); } return -1; } static int dlpiok(int fd, t_uscalar_t oprim) { int retv; dl_ok_ack_t *doa = (dl_ok_ack_t *)dlpi_ctl; retv = dlpirctl(fd); if (retv < (ssize_t)DL_OK_ACK_SIZE || doa->dl_primitive != DL_OK_ACK || doa->dl_correct_primitive != oprim) { return -1; } else { return 0; } } static int dlpiinfo(int fd) { dl_info_req_t dir; ssize_t retv; memset(&dir, 0, sizeof(dir)); dir.dl_primitive = DL_INFO_REQ; /* Info_req uses M_PCPROTO. */ dlpisend(fd, &dir, sizeof(dir), NULL, 0, RS_HIPRI); retv = dlpirctl(fd); if (retv < (ssize_t)DL_INFO_ACK_SIZE || dlpi_ctl[0] != DL_INFO_ACK) return -1; else return retv; } static int dlpiopen(const char *devpath, ssize_t *acklen) { int fd, flags; fd = open(devpath, O_RDWR | O_NONBLOCK | O_NOCTTY); if (fd == -1) return -1; /* All that we want is for the open itself to be non-blocking, not I/O. */ flags = fcntl(fd, F_GETFL, 0); if (flags != -1) fcntl(fd, F_SETFL, flags & ~O_NONBLOCK); /* After opening, ask for information */ if ((*acklen = dlpiinfo(fd)) == -1) { close(fd); return -1; } return fd; } static int dlpiattach(int fd, int unit) { dl_attach_req_t dar; memset(&dar, 0, sizeof(dar)); dar.dl_primitive = DL_ATTACH_REQ; dar.dl_ppa = unit; dlpisend(fd, &dar, sizeof(dar), NULL, 0, 0); return dlpiok(fd, dar.dl_primitive); } static int dlpibind(int fd) { dl_bind_req_t dbr; int retv; dl_bind_ack_t *dba = (dl_bind_ack_t *)dlpi_ctl; memset(&dbr, 0, sizeof(dbr)); dbr.dl_primitive = DL_BIND_REQ; dbr.dl_service_mode = DL_CLDLS; dlpisend(fd, &dbr, sizeof(dbr), NULL, 0, 0); retv = dlpirctl(fd); if (retv < (ssize_t)DL_BIND_ACK_SIZE || dba->dl_primitive != DL_BIND_ACK) return -1; else return 0; } static int dlpimcast(int fd, const uint8_t *mcaddr) { struct { dl_enabmulti_req_t der; uint8_t addr[ETHERADDRL]; } dler; memset(&dler, 0, sizeof(dler)); dler.der.dl_primitive = DL_ENABMULTI_REQ; dler.der.dl_addr_length = sizeof(dler.addr); dler.der.dl_addr_offset = dler.addr - (uint8_t *)&dler; memcpy(dler.addr, mcaddr, sizeof(dler.addr)); dlpisend(fd, &dler, sizeof(dler), NULL, 0, 0); return dlpiok(fd, dler.der.dl_primitive); } static int dlpiaddr(int fd, uint8_t *addr) { dl_phys_addr_req_t dpar; dl_phys_addr_ack_t *dpaa = (dl_phys_addr_ack_t *)dlpi_ctl; int retv; memset(&dpar, 0, sizeof(dpar)); dpar.dl_primitive = DL_PHYS_ADDR_REQ; dpar.dl_addr_type = DL_CURR_PHYS_ADDR; dlpisend(fd, &dpar, sizeof(dpar), NULL, 0, 0); retv = dlpirctl(fd); if (retv < (ssize_t)DL_PHYS_ADDR_ACK_SIZE || dpaa->dl_primitive != DL_PHYS_ADDR_ACK) return -1; if (dpaa->dl_addr_offset < DL_PHYS_ADDR_ACK_SIZE || dpaa->dl_addr_length != ETHERADDRL || dpaa->dl_addr_offset + dpaa->dl_addr_length > (size_t)retv) return -1; bcopy((char *)dpaa + dpaa->dl_addr_offset, addr, ETHERADDRL); return 0; } static int open_dlpi_dev(struct isis_circuit *circuit) { int fd = -1, unit, retval; char devpath[MAXPATHLEN]; dl_info_ack_t *dia = (dl_info_ack_t *)dlpi_ctl; ssize_t acklen; /* Only broadcast-type are supported at the moment */ if (circuit->circ_type != CIRCUIT_T_BROADCAST) { zlog_warn("%s: non-broadcast interface %s", __func__, circuit->interface->name); return ISIS_WARNING; } /* Try the vanity node first, if permitted */ if (getenv("DLPI_DEVONLY") == NULL) { (void)snprintf(devpath, sizeof(devpath), "/dev/net/%s", circuit->interface->name); fd = dlpiopen(devpath, &acklen); } /* Now try as an ordinary Style 1 node */ if (fd == -1) { (void)snprintf(devpath, sizeof(devpath), "/dev/%s", circuit->interface->name); unit = -1; fd = dlpiopen(devpath, &acklen); } /* If that fails, try again as Style 2 */ if (fd == -1) { char *cp; cp = devpath + strlen(devpath); while (--cp >= devpath && isdigit(*cp)) ; unit = strtol(cp, NULL, 0); *cp = '\0'; fd = dlpiopen(devpath, &acklen); /* If that too fails, then the device really doesn't exist */ if (fd == -1) { zlog_warn("%s: unknown interface %s", __func__, circuit->interface->name); return ISIS_WARNING; } /* Double check the DLPI style */ if (dia->dl_provider_style != DL_STYLE2) { zlog_warn( "open_dlpi_dev(): interface %s: %s is not style 2", circuit->interface->name, devpath); close(fd); return ISIS_WARNING; } /* If it succeeds, then we need to attach to the unit specified */ dlpiattach(fd, unit); /* Reget the information, as it may be different per node */ if ((acklen = dlpiinfo(fd)) == -1) { close(fd); return ISIS_WARNING; } } else { /* Double check the DLPI style */ if (dia->dl_provider_style != DL_STYLE1) { zlog_warn( "open_dlpi_dev(): interface %s: %s is not style 1", circuit->interface->name, devpath); close(fd); return ISIS_WARNING; } } /* Check that the interface we've got is the kind we expect */ if ((dia->dl_sap_length != 2 && dia->dl_sap_length != -2) || dia->dl_service_mode != DL_CLDLS || dia->dl_addr_length != ETHERADDRL + 2 || dia->dl_brdcst_addr_length != ETHERADDRL) { zlog_warn("%s: unsupported interface type for %s", __func__, circuit->interface->name); close(fd); return ISIS_WARNING; } switch (dia->dl_mac_type) { case DL_CSMACD: case DL_ETHER: case DL_100VG: case DL_100VGTPR: case DL_ETH_CSMA: case DL_100BT: break; default: zlog_warn("%s: unexpected mac type on %s: %lld", __func__, circuit->interface->name, (long long)dia->dl_mac_type); close(fd); return ISIS_WARNING; } circuit->sap_length = dia->dl_sap_length; /* * The local hardware address is something that should be provided by * way of * sockaddr_dl for the interface, but isn't on Solaris. We set it here * based * on DLPI's reported address to avoid roto-tilling the world. * (Note that isis_circuit_if_add on Solaris doesn't set the snpa.) * * Unfortunately, GLD is broken and doesn't provide the address after * attach, * so we need to be careful and use DL_PHYS_ADDR_REQ instead. */ if (dlpiaddr(fd, circuit->u.bc.snpa) == -1) { zlog_warn( "open_dlpi_dev(): interface %s: unable to get MAC address", circuit->interface->name); close(fd); return ISIS_WARNING; } /* Now bind to SAP 0. This gives us 802-type traffic. */ if (dlpibind(fd) == -1) { zlog_warn("%s: cannot bind SAP 0 on %s", __func__, circuit->interface->name); close(fd); return ISIS_WARNING; } /* * Join to multicast groups according to * 8.4.2 - Broadcast subnetwork IIH PDUs */ retval = 0; retval |= dlpimcast(fd, ALL_L1_ISS); retval |= dlpimcast(fd, ALL_ISS); retval |= dlpimcast(fd, ALL_L2_ISS); if (retval != 0) { zlog_warn("%s: unable to join multicast on %s", __func__, circuit->interface->name); close(fd); return ISIS_WARNING; } /* Push on the packet filter to avoid stray 802 packets */ if (ioctl(fd, I_PUSH, "pfmod") == 0) { struct packetfilt pfil; struct strioctl sioc; pfil.Pf_Priority = 0; pfil.Pf_FilterLen = array_size(pf_filter); memcpy(pfil.Pf_Filter, pf_filter, sizeof(pf_filter)); /* pfmod does not support transparent ioctls */ sioc.ic_cmd = PFIOCSETF; sioc.ic_timout = 5; sioc.ic_len = sizeof(struct packetfilt); sioc.ic_dp = (char *)&pfil; if (ioctl(fd, I_STR, &sioc) == -1) zlog_warn("%s: could not perform PF_IOCSETF on %s", __func__, circuit->interface->name); } circuit->fd = fd; return ISIS_OK; } /* * Create the socket and set the tx/rx funcs */ int isis_sock_init(struct isis_circuit *circuit) { int retval = ISIS_OK; frr_with_privs(&isisd_privs) { retval = open_dlpi_dev(circuit); if (retval != ISIS_OK) { zlog_warn("%s: could not initialize the socket", __func__); break; } if (circuit->circ_type == CIRCUIT_T_BROADCAST) { circuit->tx = isis_send_pdu_bcast; circuit->rx = isis_recv_pdu_bcast; } else { zlog_warn("isis_sock_init(): unknown circuit type"); retval = ISIS_WARNING; break; } } return retval; } int isis_recv_pdu_bcast(struct isis_circuit *circuit, uint8_t *ssnpa) { struct pollfd fds[1]; struct strbuf ctlbuf, databuf; int flags, retv; dl_unitdata_ind_t *dui = (dl_unitdata_ind_t *)dlpi_ctl; memset(fds, 0, sizeof(fds)); fds[0].fd = circuit->fd; fds[0].events = POLLIN | POLLPRI; if (poll(fds, 1, 0) <= 0) return ISIS_WARNING; memset(&ctlbuf, 0, sizeof(ctlbuf)); memset(&databuf, 0, sizeof(databuf)); ctlbuf.maxlen = sizeof(dlpi_ctl); ctlbuf.buf = (void *)dlpi_ctl; databuf.maxlen = sizeof(sock_buff); databuf.buf = (void *)sock_buff; flags = 0; retv = getmsg(circuit->fd, &ctlbuf, &databuf, &flags); if (retv < 0) { zlog_warn("isis_recv_pdu_bcast: getmsg failed: %s", safe_strerror(errno)); return ISIS_WARNING; } if (retv & (MORECTL | MOREDATA)) { while (retv & (MORECTL | MOREDATA)) { flags = 0; retv = getmsg(circuit->fd, &ctlbuf, &databuf, &flags); } return ISIS_WARNING; } if (ctlbuf.len < (ssize_t)DL_UNITDATA_IND_SIZE || dui->dl_primitive != DL_UNITDATA_IND) return ISIS_WARNING; if (dui->dl_src_addr_length != ETHERADDRL + 2 || dui->dl_src_addr_offset < DL_UNITDATA_IND_SIZE || dui->dl_src_addr_offset + dui->dl_src_addr_length > (size_t)ctlbuf.len) return ISIS_WARNING; memcpy(ssnpa, (char *)dui + dui->dl_src_addr_offset + (circuit->sap_length > 0 ? circuit->sap_length : 0), ETHERADDRL); if (databuf.len < LLC_LEN || sock_buff[0] != ISO_SAP || sock_buff[1] != ISO_SAP || sock_buff[2] != 3) return ISIS_WARNING; stream_write(circuit->rcv_stream, sock_buff + LLC_LEN, databuf.len - LLC_LEN); stream_set_getp(circuit->rcv_stream, 0); return ISIS_OK; } int isis_send_pdu_bcast(struct isis_circuit *circuit, int level) { dl_unitdata_req_t *dur = (dl_unitdata_req_t *)dlpi_ctl; char *dstaddr; unsigned short *dstsap; int buflen; int rv; buflen = stream_get_endp(circuit->snd_stream) + LLC_LEN; if ((size_t)buflen > sizeof(sock_buff)) { zlog_warn( "isis_send_pdu_bcast: sock_buff size %zu is less than " "output pdu size %d on circuit %s", sizeof(sock_buff), buflen, circuit->interface->name); return ISIS_WARNING; } stream_set_getp(circuit->snd_stream, 0); memset(dur, 0, sizeof(*dur)); dur->dl_primitive = DL_UNITDATA_REQ; dur->dl_dest_addr_length = ETHERADDRL + 2; dur->dl_dest_addr_offset = sizeof(*dur); dstaddr = (char *)(dur + 1); if (circuit->sap_length < 0) { dstsap = (unsigned short *)(dstaddr + ETHERADDRL); } else { dstsap = (unsigned short *)dstaddr; dstaddr += circuit->sap_length; } if (level == 1) memcpy(dstaddr, ALL_L1_ISS, ETHERADDRL); else memcpy(dstaddr, ALL_L2_ISS, ETHERADDRL); /* Note: DLPI SAP values are in host byte order */ *dstsap = buflen; sock_buff[0] = ISO_SAP; sock_buff[1] = ISO_SAP; sock_buff[2] = 0x03; memcpy(sock_buff + LLC_LEN, circuit->snd_stream->data, stream_get_endp(circuit->snd_stream)); rv = dlpisend(circuit->fd, dur, sizeof(*dur) + dur->dl_dest_addr_length, sock_buff, buflen, 0); if (rv < 0) { zlog_warn("IS-IS dlpi: could not transmit packet on %s: %s", circuit->interface->name, safe_strerror(errno)); if (ERRNO_IO_RETRY(errno)) return ISIS_WARNING; return ISIS_ERROR; } return ISIS_OK; } #endif /* ISIS_METHOD == ISIS_METHOD_DLPI */ frr-7.2.1/isisd/isis_dr.c0000644000000000000000000002162713610377563012137 00000000000000/* * IS-IS Rout(e)ing protocol - isis_dr.c * IS-IS designated router related routines * * Copyright (C) 2001,2002 Sampo Saaristo * Tampere University of Technology * Institute of Communications Engineering * * This program is free software; you can redistribute it and/or modify it * under the terms of the GNU General Public Licenseas 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; see the file COPYING; if not, write to the Free Software * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA */ #include #include "log.h" #include "hash.h" #include "thread.h" #include "linklist.h" #include "vty.h" #include "stream.h" #include "if.h" #include "isisd/isis_constants.h" #include "isisd/isis_common.h" #include "isisd/isis_misc.h" #include "isisd/isis_flags.h" #include "isisd/isis_circuit.h" #include "isisd/isisd.h" #include "isisd/isis_adjacency.h" #include "isisd/isis_constants.h" #include "isisd/isis_pdu.h" #include "isisd/isis_lsp.h" #include "isisd/isis_dr.h" #include "isisd/isis_events.h" const char *isis_disflag2string(int disflag) { switch (disflag) { case ISIS_IS_NOT_DIS: return "is not DIS"; case ISIS_IS_DIS: return "is DIS"; case ISIS_WAS_DIS: return "was DIS"; default: return "unknown DIS state"; } return NULL; /* not reached */ } int isis_run_dr(struct thread *thread) { struct isis_circuit_arg *arg = THREAD_ARG(thread); assert(arg); struct isis_circuit *circuit = arg->circuit; int level = arg->level; assert(circuit); if (circuit->circ_type != CIRCUIT_T_BROADCAST) { zlog_warn("%s: scheduled for non broadcast circuit from %s:%d", __func__, thread->schedfrom, thread->schedfrom_line); return ISIS_WARNING; } if (circuit->u.bc.run_dr_elect[level - 1]) zlog_warn("isis_run_dr(): run_dr_elect already set for l%d", level); circuit->u.bc.t_run_dr[level - 1] = NULL; circuit->u.bc.run_dr_elect[level - 1] = 1; return ISIS_OK; } static int isis_check_dr_change(struct isis_adjacency *adj, int level) { int i; if (adj->dis_record[level - 1].dis != adj->dis_record[(1 * ISIS_LEVELS) + level - 1].dis) /* was there a DIS state transition ? */ { adj->dischanges[level - 1]++; /* ok rotate the history list through */ for (i = DIS_RECORDS - 1; i > 0; i--) { adj->dis_record[(i * ISIS_LEVELS) + level - 1].dis = adj->dis_record[((i - 1) * ISIS_LEVELS) + level - 1] .dis; adj->dis_record[(i * ISIS_LEVELS) + level - 1] .last_dis_change = adj->dis_record[((i - 1) * ISIS_LEVELS) + level - 1] .last_dis_change; } } return ISIS_OK; } int isis_dr_elect(struct isis_circuit *circuit, int level) { struct list *adjdb; struct listnode *node; struct isis_adjacency *adj, *adj_dr = NULL; struct list *list = list_new(); uint8_t own_prio; int biggest_prio = -1; int cmp_res, retval = ISIS_OK; own_prio = circuit->priority[level - 1]; adjdb = circuit->u.bc.adjdb[level - 1]; if (!adjdb) { zlog_warn("isis_dr_elect() adjdb == NULL"); list_delete(&list); return ISIS_WARNING; } isis_adj_build_up_list(adjdb, list); /* * Loop the adjacencies and find the one with the biggest priority */ for (ALL_LIST_ELEMENTS_RO(list, node, adj)) { /* clear flag for show output */ adj->dis_record[level - 1].dis = ISIS_IS_NOT_DIS; adj->dis_record[level - 1].last_dis_change = time(NULL); if (adj->prio[level - 1] > biggest_prio) { biggest_prio = adj->prio[level - 1]; adj_dr = adj; } else if (adj->prio[level - 1] == biggest_prio) { /* * Comparison of MACs breaks a tie */ if (adj_dr) { cmp_res = memcmp(adj_dr->snpa, adj->snpa, ETH_ALEN); if (cmp_res < 0) { adj_dr = adj; } if (cmp_res == 0) zlog_warn( "isis_dr_elect(): multiple adjacencies with same SNPA"); } else { adj_dr = adj; } } } if (!adj_dr) { /* * Could not find the DR - means we are alone. Resign if we were * DR. */ if (circuit->u.bc.is_dr[level - 1]) retval = isis_dr_resign(circuit, level); list_delete(&list); return retval; } /* * Now we have the DR adjacency, compare it to self */ if (adj_dr->prio[level - 1] < own_prio || (adj_dr->prio[level - 1] == own_prio && memcmp(adj_dr->snpa, circuit->u.bc.snpa, ETH_ALEN) < 0)) { adj_dr->dis_record[level - 1].dis = ISIS_IS_NOT_DIS; adj_dr->dis_record[level - 1].last_dis_change = time(NULL); /* rotate the history log */ for (ALL_LIST_ELEMENTS_RO(list, node, adj)) isis_check_dr_change(adj, level); /* We are the DR, commence DR */ if (circuit->u.bc.is_dr[level - 1] == 0 && listcount(list) > 0) retval = isis_dr_commence(circuit, level); } else { /* ok we have found the DIS - lets mark the adjacency */ /* set flag for show output */ adj_dr->dis_record[level - 1].dis = ISIS_IS_DIS; adj_dr->dis_record[level - 1].last_dis_change = time(NULL); /* now loop through a second time to check if there has been a * DIS change * if yes rotate the history log */ for (ALL_LIST_ELEMENTS_RO(list, node, adj)) isis_check_dr_change(adj, level); /* * We are not DR - if we were -> resign */ if (circuit->u.bc.is_dr[level - 1]) retval = isis_dr_resign(circuit, level); } list_delete(&list); return retval; } int isis_dr_resign(struct isis_circuit *circuit, int level) { uint8_t id[ISIS_SYS_ID_LEN + 2]; zlog_debug("isis_dr_resign l%d", level); circuit->u.bc.is_dr[level - 1] = 0; circuit->u.bc.run_dr_elect[level - 1] = 0; THREAD_TIMER_OFF(circuit->u.bc.t_run_dr[level - 1]); THREAD_TIMER_OFF(circuit->u.bc.t_refresh_pseudo_lsp[level - 1]); circuit->lsp_regenerate_pending[level - 1] = 0; memcpy(id, isis->sysid, ISIS_SYS_ID_LEN); LSP_PSEUDO_ID(id) = circuit->circuit_id; LSP_FRAGMENT(id) = 0; lsp_purge_pseudo(id, circuit, level); if (level == 1) { memset(circuit->u.bc.l1_desig_is, 0, ISIS_SYS_ID_LEN + 1); thread_add_timer(master, send_l1_psnp, circuit, isis_jitter(circuit->psnp_interval[level - 1], PSNP_JITTER), &circuit->t_send_psnp[0]); } else { memset(circuit->u.bc.l2_desig_is, 0, ISIS_SYS_ID_LEN + 1); thread_add_timer(master, send_l2_psnp, circuit, isis_jitter(circuit->psnp_interval[level - 1], PSNP_JITTER), &circuit->t_send_psnp[1]); } THREAD_TIMER_OFF(circuit->t_send_csnp[level - 1]); thread_add_timer(master, isis_run_dr, &circuit->level_arg[level - 1], 2 * circuit->hello_interval[level - 1], &circuit->u.bc.t_run_dr[level - 1]); thread_add_event(master, isis_event_dis_status_change, circuit, 0, NULL); return ISIS_OK; } int isis_dr_commence(struct isis_circuit *circuit, int level) { uint8_t old_dr[ISIS_SYS_ID_LEN + 2]; if (isis->debugs & DEBUG_EVENTS) zlog_debug("isis_dr_commence l%d", level); /* Lets keep a pause in DR election */ circuit->u.bc.run_dr_elect[level - 1] = 0; circuit->u.bc.is_dr[level - 1] = 1; if (level == 1) { memcpy(old_dr, circuit->u.bc.l1_desig_is, ISIS_SYS_ID_LEN + 1); LSP_FRAGMENT(old_dr) = 0; if (LSP_PSEUDO_ID(old_dr)) { /* there was a dr elected, purge its LSPs from the db */ lsp_purge_pseudo(old_dr, circuit, level); } memcpy(circuit->u.bc.l1_desig_is, isis->sysid, ISIS_SYS_ID_LEN); *(circuit->u.bc.l1_desig_is + ISIS_SYS_ID_LEN) = circuit->circuit_id; assert(circuit->circuit_id); /* must be non-zero */ /* if (circuit->t_send_l1_psnp) thread_cancel (circuit->t_send_l1_psnp); */ lsp_generate_pseudo(circuit, 1); thread_add_timer(master, send_l1_csnp, circuit, isis_jitter(circuit->csnp_interval[level - 1], CSNP_JITTER), &circuit->t_send_csnp[0]); } else { memcpy(old_dr, circuit->u.bc.l2_desig_is, ISIS_SYS_ID_LEN + 1); LSP_FRAGMENT(old_dr) = 0; if (LSP_PSEUDO_ID(old_dr)) { /* there was a dr elected, purge its LSPs from the db */ lsp_purge_pseudo(old_dr, circuit, level); } memcpy(circuit->u.bc.l2_desig_is, isis->sysid, ISIS_SYS_ID_LEN); *(circuit->u.bc.l2_desig_is + ISIS_SYS_ID_LEN) = circuit->circuit_id; assert(circuit->circuit_id); /* must be non-zero */ /* if (circuit->t_send_l1_psnp) thread_cancel (circuit->t_send_l1_psnp); */ lsp_generate_pseudo(circuit, 2); thread_add_timer(master, send_l2_csnp, circuit, isis_jitter(circuit->csnp_interval[level - 1], CSNP_JITTER), &circuit->t_send_csnp[1]); } thread_add_timer(master, isis_run_dr, &circuit->level_arg[level - 1], 2 * circuit->hello_interval[level - 1], &circuit->u.bc.t_run_dr[level - 1]); thread_add_event(master, isis_event_dis_status_change, circuit, 0, NULL); return ISIS_OK; } frr-7.2.1/isisd/isis_dr.h0000644000000000000000000000270513610377563012140 00000000000000/* * IS-IS Rout(e)ing protocol - isis_dr.h * IS-IS designated router related routines * * Copyright (C) 2001,2002 Sampo Saaristo * Tampere University of Technology * Institute of Communications Engineering * * This program is free software; you can redistribute it and/or modify it * under the terms of the GNU General Public Licenseas 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; see the file COPYING; if not, write to the Free Software * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA */ #ifndef _ZEBRA_ISIS_DR_H #define _ZEBRA_ISIS_DR_H int isis_run_dr(struct thread *thread); int isis_dr_elect(struct isis_circuit *circuit, int level); int isis_dr_resign(struct isis_circuit *circuit, int level); int isis_dr_commence(struct isis_circuit *circuit, int level); const char *isis_disflag2string(int disflag); enum isis_dis_state { ISIS_IS_NOT_DIS, ISIS_IS_DIS, ISIS_WAS_DIS, ISIS_UNKNOWN_DIS }; #endif /* _ZEBRA_ISIS_DR_H */ frr-7.2.1/isisd/isis_dynhn.c0000644000000000000000000000737113610377563012652 00000000000000/* * IS-IS Rout(e)ing protocol - isis_dynhn.c * Dynamic hostname cache * Copyright (C) 2001,2002 Sampo Saaristo * Tampere University of Technology * Institute of Communications Engineering * * This program is free software; you can redistribute it and/or modify it * under the terms of the GNU General Public Licenseas 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; see the file COPYING; if not, write to the Free Software * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA */ #include #include "vty.h" #include "linklist.h" #include "memory.h" #include "log.h" #include "stream.h" #include "command.h" #include "if.h" #include "thread.h" #include "isisd/isis_constants.h" #include "isisd/isis_common.h" #include "isisd/isis_flags.h" #include "isisd/isis_circuit.h" #include "isisd/isisd.h" #include "isisd/isis_dynhn.h" #include "isisd/isis_misc.h" #include "isisd/isis_constants.h" extern struct host host; struct list *dyn_cache = NULL; static int dyn_cache_cleanup(struct thread *); void dyn_cache_init(void) { if (dyn_cache == NULL) dyn_cache = list_new(); thread_add_timer(master, dyn_cache_cleanup, NULL, 120, &isis->t_dync_clean); return; } static int dyn_cache_cleanup(struct thread *thread) { struct listnode *node, *nnode; struct isis_dynhn *dyn; time_t now = time(NULL); isis->t_dync_clean = NULL; for (ALL_LIST_ELEMENTS(dyn_cache, node, nnode, dyn)) { if ((now - dyn->refresh) < MAX_LSP_LIFETIME) continue; list_delete_node(dyn_cache, node); XFREE(MTYPE_ISIS_DYNHN, dyn); } thread_add_timer(master, dyn_cache_cleanup, NULL, 120, &isis->t_dync_clean); return ISIS_OK; } struct isis_dynhn *dynhn_find_by_id(const uint8_t *id) { struct listnode *node = NULL; struct isis_dynhn *dyn = NULL; for (ALL_LIST_ELEMENTS_RO(dyn_cache, node, dyn)) if (memcmp(dyn->id, id, ISIS_SYS_ID_LEN) == 0) return dyn; return NULL; } struct isis_dynhn *dynhn_find_by_name(const char *hostname) { struct listnode *node = NULL; struct isis_dynhn *dyn = NULL; for (ALL_LIST_ELEMENTS_RO(dyn_cache, node, dyn)) if (strncmp(dyn->hostname, hostname, 255) == 0) return dyn; return NULL; } void isis_dynhn_insert(const uint8_t *id, const char *hostname, int level) { struct isis_dynhn *dyn; dyn = dynhn_find_by_id(id); if (!dyn) { dyn = XCALLOC(MTYPE_ISIS_DYNHN, sizeof(struct isis_dynhn)); memcpy(dyn->id, id, ISIS_SYS_ID_LEN); dyn->level = level; listnode_add(dyn_cache, dyn); } snprintf(dyn->hostname, sizeof(dyn->hostname), "%s", hostname); dyn->refresh = time(NULL); } void isis_dynhn_remove(const uint8_t *id) { struct isis_dynhn *dyn; dyn = dynhn_find_by_id(id); if (!dyn) return; listnode_delete(dyn_cache, dyn); XFREE(MTYPE_ISIS_DYNHN, dyn); } /* * Level System ID Dynamic Hostname (notag) * 2 0000.0000.0001 foo-gw * 2 0000.0000.0002 bar-gw * * 0000.0000.0004 this-gw */ void dynhn_print_all(struct vty *vty) { struct listnode *node; struct isis_dynhn *dyn; vty_out(vty, "Level System ID Dynamic Hostname\n"); for (ALL_LIST_ELEMENTS_RO(dyn_cache, node, dyn)) { vty_out(vty, "%-7d", dyn->level); vty_out(vty, "%-15s%-15s\n", sysid_print(dyn->id), dyn->hostname); } vty_out(vty, " * %s %s\n", sysid_print(isis->sysid), cmd_hostname_get()); return; } frr-7.2.1/isisd/isis_dynhn.h0000644000000000000000000000275313610377563012656 00000000000000/* * IS-IS Rout(e)ing protocol - isis_dynhn.h * Dynamic hostname cache * * Copyright (C) 2001,2002 Sampo Saaristo * Tampere University of Technology * Institute of Communications Engineering * * This program is free software; you can redistribute it and/or modify it * under the terms of the GNU General Public Licenseas 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; see the file COPYING; if not, write to the Free Software * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA */ #ifndef _ZEBRA_ISIS_DYNHN_H #define _ZEBRA_ISIS_DYNHN_H struct isis_dynhn { uint8_t id[ISIS_SYS_ID_LEN]; char hostname[256]; time_t refresh; int level; }; void dyn_cache_init(void); void isis_dynhn_insert(const uint8_t *id, const char *hostname, int level); void isis_dynhn_remove(const uint8_t *id); struct isis_dynhn *dynhn_find_by_id(const uint8_t *id); struct isis_dynhn *dynhn_find_by_name(const char *hostname); void dynhn_print_all(struct vty *vty); #endif /* _ZEBRA_ISIS_DYNHN_H */ frr-7.2.1/isisd/isis_errors.c0000644000000000000000000000275513610377563013047 00000000000000/* * ISIS-specific error messages. * Copyright (C) 2018 Cumulus Networks, Inc. * Donald Sharp * * This program is free software; you can redistribute it and/or modify it * under the terms of the 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; see the file COPYING; if not, write to the Free Software * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA */ #include #include "lib/ferr.h" #include "isis_errors.h" /* clang-format off */ static struct log_ref ferr_isis_err[] = { { .code = EC_ISIS_PACKET, .title = "ISIS Packet Error", .description = "Isis has detected an error with a packet from a peer", .suggestion = "Gather log information and open an issue then restart FRR" }, { .code = EC_ISIS_CONFIG, .title = "ISIS Configuration Error", .description = "Isis has detected an error within configuration for the router", .suggestion = "Ensure configuration is correct" }, { .code = END_FERR, } }; /* clang-format on */ void isis_error_init(void) { log_ref_add(ferr_isis_err); } frr-7.2.1/isisd/isis_errors.h0000644000000000000000000000203113610377563013037 00000000000000/* * ISIS-specific error messages. * Copyright (C) 2018 Cumulus Networks, Inc. * Donald Sharp * * This program is free software; you can redistribute it and/or modify it * under the terms of the 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; see the file COPYING; if not, write to the Free Software * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA */ #ifndef __ISIS_ERRORS_H__ #define __ISIS_ERRORS_H__ #include "lib/ferr.h" enum isis_log_refs { EC_ISIS_PACKET = ISIS_FERR_START, EC_ISIS_CONFIG, }; extern void isis_error_init(void); #endif frr-7.2.1/isisd/isis_events.c0000644000000000000000000001446113610377563013034 00000000000000/* * IS-IS Rout(e)ing protocol - isis_events.h * * Copyright (C) 2001,2002 Sampo Saaristo * Tampere University of Technology * Institute of Communications Engineering * * This program is free software; you can redistribute it and/or modify it * under the terms of the GNU General Public Licenseas 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; see the file COPYING; if not, write to the Free Software * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA */ #include #include "log.h" #include "memory.h" #include "if.h" #include "linklist.h" #include "command.h" #include "thread.h" #include "hash.h" #include "prefix.h" #include "stream.h" #include "table.h" #include "isisd/isis_constants.h" #include "isisd/isis_common.h" #include "isisd/isis_flags.h" #include "isisd/isis_circuit.h" #include "isisd/isis_lsp.h" #include "isisd/isis_pdu.h" #include "isisd/isis_network.h" #include "isisd/isis_misc.h" #include "isisd/isis_constants.h" #include "isisd/isis_adjacency.h" #include "isisd/isis_dr.h" #include "isisd/isisd.h" #include "isisd/isis_csm.h" #include "isisd/isis_events.h" #include "isisd/isis_spf.h" #include "isisd/isis_errors.h" /* debug isis-spf spf-events 4w4d: ISIS-Spf (tlt): L2 SPF needed, new adjacency, from 0x609229F4 4w4d: ISIS-Spf (tlt): L2, 0000.0000.0042.01-00 TLV contents changed, code 0x2 4w4d: ISIS-Spf (tlt): L2, new LSP 0 DEAD.BEEF.0043.00-00 4w5d: ISIS-Spf (tlt): L1 SPF needed, periodic SPF, from 0x6091C844 4w5d: ISIS-Spf (tlt): L2 SPF needed, periodic SPF, from 0x6091C844 */ void isis_event_circuit_state_change(struct isis_circuit *circuit, struct isis_area *area, int up) { area->circuit_state_changes++; if (isis->debugs & DEBUG_EVENTS) zlog_debug("ISIS-Evt (%s) circuit %s", area->area_tag, up ? "up" : "down"); /* * Regenerate LSPs this affects */ lsp_regenerate_schedule(area, IS_LEVEL_1 | IS_LEVEL_2, 0); return; } static void circuit_commence_level(struct isis_circuit *circuit, int level) { if (!circuit->is_passive) { if (level == 1) { thread_add_timer(master, send_l1_psnp, circuit, isis_jitter(circuit->psnp_interval[0], PSNP_JITTER), &circuit->t_send_psnp[0]); } else { thread_add_timer(master, send_l2_psnp, circuit, isis_jitter(circuit->psnp_interval[1], PSNP_JITTER), &circuit->t_send_psnp[1]); } } if (circuit->circ_type == CIRCUIT_T_BROADCAST) { thread_add_timer(master, isis_run_dr, &circuit->level_arg[level - 1], 2 * circuit->hello_interval[level - 1], &circuit->u.bc.t_run_dr[level - 1]); send_hello_sched(circuit, level, TRIGGERED_IIH_DELAY); circuit->u.bc.lan_neighs[level - 1] = list_new(); } } static void circuit_resign_level(struct isis_circuit *circuit, int level) { int idx = level - 1; THREAD_TIMER_OFF(circuit->t_send_csnp[idx]); THREAD_TIMER_OFF(circuit->t_send_psnp[idx]); if (circuit->circ_type == CIRCUIT_T_BROADCAST) { THREAD_TIMER_OFF(circuit->u.bc.t_send_lan_hello[idx]); THREAD_TIMER_OFF(circuit->u.bc.t_run_dr[idx]); THREAD_TIMER_OFF(circuit->u.bc.t_refresh_pseudo_lsp[idx]); circuit->lsp_regenerate_pending[idx] = 0; circuit->u.bc.run_dr_elect[idx] = 0; if (circuit->u.bc.lan_neighs[idx] != NULL) list_delete(&circuit->u.bc.lan_neighs[idx]); } return; } void isis_circuit_is_type_set(struct isis_circuit *circuit, int newtype) { if (circuit->state != C_STATE_UP) { circuit->is_type = newtype; return; } if (isis->debugs & DEBUG_EVENTS) zlog_debug("ISIS-Evt (%s) circuit type change %s -> %s", circuit->area->area_tag, circuit_t2string(circuit->is_type), circuit_t2string(newtype)); if (circuit->is_type == newtype) return; /* No change */ if (!(newtype & circuit->area->is_type)) { flog_err( EC_ISIS_CONFIG, "ISIS-Evt (%s) circuit type change - invalid level %s because area is %s", circuit->area->area_tag, circuit_t2string(newtype), circuit_t2string(circuit->area->is_type)); return; } if (!circuit->is_passive) { switch (circuit->is_type) { case IS_LEVEL_1: if (newtype == IS_LEVEL_2) circuit_resign_level(circuit, 1); circuit_commence_level(circuit, 2); break; case IS_LEVEL_1_AND_2: if (newtype == IS_LEVEL_1) circuit_resign_level(circuit, 2); else circuit_resign_level(circuit, 1); break; case IS_LEVEL_2: if (newtype == IS_LEVEL_1) circuit_resign_level(circuit, 2); circuit_commence_level(circuit, 1); break; default: break; } } circuit->is_type = newtype; lsp_regenerate_schedule(circuit->area, IS_LEVEL_1 | IS_LEVEL_2, 0); return; } /* 04/18/2002 by Gwak. */ /************************************************************************** * * EVENTS for LSP generation * * 1) an Adajacency or Circuit Up/Down event * 2) a chnage in Circuit metric * 3) a change in Reachable Address metric * 4) a change in manualAreaAddresses * 5) a change in systemID * 6) a change in DIS status * 7) a chnage in the waiting status * * *********************************************************************** * * current support event * * 1) Adjacency Up/Down event * 6) a change in DIS status * * ***********************************************************************/ /* events supporting code */ int isis_event_dis_status_change(struct thread *thread) { struct isis_circuit *circuit; circuit = THREAD_ARG(thread); /* invalid arguments */ if (!circuit || !circuit->area) return 0; if (isis->debugs & DEBUG_EVENTS) zlog_debug("ISIS-Evt (%s) DIS status change", circuit->area->area_tag); /* LSP generation again */ lsp_regenerate_schedule(circuit->area, IS_LEVEL_1 | IS_LEVEL_2, 0); return 0; } void isis_event_auth_failure(char *area_tag, const char *error_string, uint8_t *sysid) { if (isis->debugs & DEBUG_EVENTS) zlog_debug("ISIS-Evt (%s) Authentication failure %s from %s", area_tag, error_string, sysid_print(sysid)); return; } frr-7.2.1/isisd/isis_events.h0000644000000000000000000000310013610377563013025 00000000000000/* * IS-IS Rout(e)ing protocol - isis_events.h * * Copyright (C) 2001,2002 Sampo Saaristo * Tampere University of Technology * Institute of Communications Engineering * * This program is free software; you can redistribute it and/or modify it * under the terms of the GNU General Public Licenseas 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; see the file COPYING; if not, write to the Free Software * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA */ #ifndef _ZEBRA_ISIS_EVENTS_H #define _ZEBRA_ISIS_EVENTS_H /* * Events related to circuit */ void isis_event_circuit_state_change(struct isis_circuit *circuit, struct isis_area *area, int state); void isis_event_circuit_type_change(struct isis_circuit *circuit, int newtype); /* * Events related to adjacencies */ int isis_event_dis_status_change(struct thread *thread); /* * Error events */ #define AUTH_ERROR_TYPE_LSP 3 #define AUTH_ERROR_TYPE_SNP 2 #define AUTH_ERROR_TYPE_HELLO 1 void isis_event_auth_failure(char *area_tag, const char *error_string, uint8_t *sysid); #endif /* _ZEBRA_ISIS_EVENTS_H */ frr-7.2.1/isisd/isis_flags.c0000644000000000000000000000405013610377563012615 00000000000000/* * IS-IS Rout(e)ing protocol - isis_flags.c * Routines for manipulation of SSN and SRM flags * * Copyright (C) 2001,2002 Sampo Saaristo * Tampere University of Technology * Institute of Communications Engineering * * This program is free software; you can redistribute it and/or modify it * under the terms of the GNU General Public Licenseas 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; see the file COPYING; if not, write to the Free Software * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA */ #include #include "log.h" #include "linklist.h" #include "isisd/isis_constants.h" #include "isisd/isis_common.h" #include "isisd/isis_flags.h" void flags_initialize(struct flags *flags) { flags->maxindex = 0; flags->free_idcs = NULL; } long int flags_get_index(struct flags *flags) { struct listnode *node; long int index; if (flags->free_idcs == NULL || flags->free_idcs->count == 0) { index = flags->maxindex++; } else { node = listhead(flags->free_idcs); index = (long int)listgetdata(node); listnode_delete(flags->free_idcs, (void *)index); index--; } return index; } void flags_free_index(struct flags *flags, long int index) { if (index + 1 == flags->maxindex) { flags->maxindex--; return; } if (flags->free_idcs == NULL) { flags->free_idcs = list_new(); } listnode_add(flags->free_idcs, (void *)(index + 1)); return; } int flags_any_set(uint32_t *flags) { uint32_t zero[ISIS_MAX_CIRCUITS]; memset(zero, 0x00, ISIS_MAX_CIRCUITS * 4); return bcmp(flags, zero, ISIS_MAX_CIRCUITS * 4); } frr-7.2.1/isisd/isis_flags.h0000644000000000000000000000533113610377563012625 00000000000000/* * IS-IS Rout(e)ing protocol - isis_flags.h * Routines for manipulation of SSN and SRM flags * * Copyright (C) 2001,2002 Sampo Saaristo * Tampere University of Technology * Institute of Communications Engineering * * This program is free software; you can redistribute it and/or modify it * under the terms of the GNU General Public Licenseas 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; see the file COPYING; if not, write to the Free Software * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA */ #ifndef _ZEBRA_ISIS_FLAGS_H #define _ZEBRA_ISIS_FLAGS_H /* The grand plan is to support 1024 circuits so we have 32*32 bit flags * the support will be achived using the newest drafts */ #define ISIS_MAX_CIRCUITS 32 /* = 1024 */ /* * Flags structure for SSN and SRM flags */ struct flags { int maxindex; struct list *free_idcs; }; void flags_initialize(struct flags *flags); long int flags_get_index(struct flags *flags); void flags_free_index(struct flags *flags, long int index); int flags_any_set(uint32_t *flags); #define _ISIS_SET_FLAG(F, C) \ { \ F[(C) >> 5] |= (1 << ((C)&0x1F)); \ } #define ISIS_SET_FLAG(F, C) _ISIS_SET_FLAG(F, C->idx) #define _ISIS_CLEAR_FLAG(F, C) \ { \ F[(C) >> 5] &= ~(1 << ((C)&0x1F)); \ } #define ISIS_CLEAR_FLAG(F, C) _ISIS_CLEAR_FLAG(F, C->idx) #define _ISIS_CHECK_FLAG(F, C) (F[(C)>>5] & (1<<((C) & 0x1F))) #define ISIS_CHECK_FLAG(F, C) _ISIS_CHECK_FLAG(F, C->idx) /* sets all u_32int_t flags to 1 */ #define ISIS_FLAGS_SET_ALL(FLAGS) \ { \ memset(FLAGS, 0xFF, ISIS_MAX_CIRCUITS * 4); \ } #define ISIS_FLAGS_CLEAR_ALL(FLAGS) \ { \ memset(FLAGS, 0x00, ISIS_MAX_CIRCUITS * 4); \ } #endif /* _ZEBRA_ISIS_FLAGS_H */ frr-7.2.1/isisd/isis_lsp.c0000644000000000000000000015172213610377563012330 00000000000000/* * IS-IS Rout(e)ing protocol - isis_lsp.c * LSP processing * * Copyright (C) 2001,2002 Sampo Saaristo * Tampere University of Technology * Institute of Communications Engineering * Copyright (C) 2013-2015 Christian Franke * * This program is free software; you can redistribute it and/or modify it * under the terms of the 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; see the file COPYING; if not, write to the Free Software * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA */ #include #include "linklist.h" #include "thread.h" #include "vty.h" #include "stream.h" #include "memory.h" #include "log.h" #include "prefix.h" #include "command.h" #include "hash.h" #include "if.h" #include "checksum.h" #include "md5.h" #include "table.h" #include "srcdest_table.h" #include "lib_errors.h" #include "isisd/isis_constants.h" #include "isisd/isis_common.h" #include "isisd/isis_flags.h" #include "isisd/isis_circuit.h" #include "isisd/isisd.h" #include "isisd/isis_lsp.h" #include "isisd/isis_pdu.h" #include "isisd/isis_dynhn.h" #include "isisd/isis_misc.h" #include "isisd/isis_csm.h" #include "isisd/isis_adjacency.h" #include "isisd/isis_spf.h" #include "isisd/isis_te.h" #include "isisd/isis_mt.h" #include "isisd/isis_tlvs.h" #include "isisd/fabricd.h" #include "isisd/isis_tx_queue.h" static int lsp_refresh(struct thread *thread); static int lsp_l1_refresh_pseudo(struct thread *thread); static int lsp_l2_refresh_pseudo(struct thread *thread); static void lsp_destroy(struct isis_lsp *lsp); int lsp_id_cmp(uint8_t *id1, uint8_t *id2) { return memcmp(id1, id2, ISIS_SYS_ID_LEN + 2); } int lspdb_compare(const struct isis_lsp *a, const struct isis_lsp *b) { return memcmp(a->hdr.lsp_id, b->hdr.lsp_id, sizeof(a->hdr.lsp_id)); } void lsp_db_init(struct lspdb_head *head) { lspdb_init(head); } void lsp_db_fini(struct lspdb_head *head) { struct isis_lsp *lsp; while ((lsp = lspdb_pop(head))) lsp_destroy(lsp); lspdb_fini(head); } struct isis_lsp *lsp_search(struct lspdb_head *head, const uint8_t *id) { struct isis_lsp searchfor; memcpy(searchfor.hdr.lsp_id, id, sizeof(searchfor.hdr.lsp_id)); return lspdb_find(head, &searchfor); } static void lsp_clear_data(struct isis_lsp *lsp) { if (!lsp) return; isis_free_tlvs(lsp->tlvs); lsp->tlvs = NULL; } static void lsp_remove_frags(struct lspdb_head *head, struct list *frags); static void lsp_destroy(struct isis_lsp *lsp) { struct listnode *cnode; struct isis_circuit *circuit; if (!lsp) return; for (ALL_LIST_ELEMENTS_RO(lsp->area->circuit_list, cnode, circuit)) isis_tx_queue_del(circuit->tx_queue, lsp); ISIS_FLAGS_CLEAR_ALL(lsp->SSNflags); lsp_clear_data(lsp); if (!LSP_FRAGMENT(lsp->hdr.lsp_id)) { if (lsp->lspu.frags) { lsp_remove_frags(&lsp->area->lspdb[lsp->level - 1], lsp->lspu.frags); list_delete(&lsp->lspu.frags); } } else { if (lsp->lspu.zero_lsp && lsp->lspu.zero_lsp->lspu.frags) { listnode_delete(lsp->lspu.zero_lsp->lspu.frags, lsp); } } isis_spf_schedule(lsp->area, lsp->level); if (lsp->pdu) stream_free(lsp->pdu); fabricd_lsp_free(lsp); XFREE(MTYPE_ISIS_LSP, lsp); } /* * Remove all the frags belonging to the given lsp */ static void lsp_remove_frags(struct lspdb_head *head, struct list *frags) { struct listnode *lnode, *lnnode; struct isis_lsp *lsp; for (ALL_LIST_ELEMENTS(frags, lnode, lnnode, lsp)) { lsp = lsp_search(head, lsp->hdr.lsp_id); lspdb_del(head, lsp); lsp_destroy(lsp); } } void lsp_search_and_destroy(struct lspdb_head *head, const uint8_t *id) { struct isis_lsp *lsp; lsp = lsp_search(head, id); if (lsp) { lspdb_del(head, lsp); /* * If this is a zero lsp, remove all the frags now */ if (LSP_FRAGMENT(lsp->hdr.lsp_id) == 0) { if (lsp->lspu.frags) lsp_remove_frags(head, lsp->lspu.frags); } else { /* * else just remove this frag, from the zero lsps' frag * list */ if (lsp->lspu.zero_lsp && lsp->lspu.zero_lsp->lspu.frags) listnode_delete(lsp->lspu.zero_lsp->lspu.frags, lsp); } lsp_destroy(lsp); } } /* * Compares a LSP to given values * Params are given in net order */ int lsp_compare(char *areatag, struct isis_lsp *lsp, uint32_t seqno, uint16_t checksum, uint16_t rem_lifetime) { if (lsp->hdr.seqno == seqno && lsp->hdr.checksum == checksum && ((lsp->hdr.rem_lifetime == 0 && rem_lifetime == 0) || (lsp->hdr.rem_lifetime != 0 && rem_lifetime != 0))) { if (isis->debugs & DEBUG_SNP_PACKETS) { zlog_debug( "ISIS-Snp (%s): Compare LSP %s seq 0x%08" PRIx32 ", cksum 0x%04" PRIx16 ", lifetime %" PRIu16 "s", areatag, rawlspid_print(lsp->hdr.lsp_id), lsp->hdr.seqno, lsp->hdr.checksum, lsp->hdr.rem_lifetime); zlog_debug( "ISIS-Snp (%s): is equal to ours seq 0x%08" PRIx32 ", cksum 0x%04" PRIx16 ", lifetime %" PRIu16 "s", areatag, seqno, checksum, rem_lifetime); } return LSP_EQUAL; } /* * LSPs with identical checksums should only be treated as newer if: * a) The current LSP has a remaining lifetime != 0 and the other LSP * has a * remaining lifetime == 0. In this case, we should participate in * the purge * and should not treat the current LSP with remaining lifetime == 0 * as older. * b) The LSP has an incorrect checksum. In this case, we need to react * as given * in 7.3.16.2. */ if (seqno > lsp->hdr.seqno || (seqno == lsp->hdr.seqno && ((lsp->hdr.rem_lifetime != 0 && rem_lifetime == 0) || (lsp->hdr.checksum != checksum && lsp->hdr.rem_lifetime)))) { if (isis->debugs & DEBUG_SNP_PACKETS) { zlog_debug( "ISIS-Snp (%s): Compare LSP %s seq 0x%08" PRIx32 ", cksum 0x%04" PRIx16 ", lifetime %" PRIu16 "s", areatag, rawlspid_print(lsp->hdr.lsp_id), seqno, checksum, rem_lifetime); zlog_debug( "ISIS-Snp (%s): is newer than ours seq 0x%08" PRIx32 ", cksum 0x%04" PRIx16 ", lifetime %" PRIu16 "s", areatag, lsp->hdr.seqno, lsp->hdr.checksum, lsp->hdr.rem_lifetime); } return LSP_NEWER; } if (isis->debugs & DEBUG_SNP_PACKETS) { zlog_debug("ISIS-Snp (%s): Compare LSP %s seq 0x%08" PRIx32 ", cksum 0x%04" PRIx16 ", lifetime %" PRIu16 "s", areatag, rawlspid_print(lsp->hdr.lsp_id), seqno, checksum, rem_lifetime); zlog_debug( "ISIS-Snp (%s): is older than ours seq 0x%08" PRIx32 ", cksum 0x%04" PRIx16 ", lifetime %" PRIu16 "s", areatag, lsp->hdr.seqno, lsp->hdr.checksum, lsp->hdr.rem_lifetime); } return LSP_OLDER; } static void put_lsp_hdr(struct isis_lsp *lsp, size_t *len_pointer, bool keep) { uint8_t pdu_type = (lsp->level == IS_LEVEL_1) ? L1_LINK_STATE : L2_LINK_STATE; struct isis_lsp_hdr *hdr = &lsp->hdr; struct stream *stream = lsp->pdu; size_t orig_getp = 0, orig_endp = 0; if (keep) { orig_getp = stream_get_getp(lsp->pdu); orig_endp = stream_get_endp(lsp->pdu); } stream_set_getp(lsp->pdu, 0); stream_set_endp(lsp->pdu, 0); fill_fixed_hdr(pdu_type, stream); if (len_pointer) *len_pointer = stream_get_endp(stream); stream_putw(stream, hdr->pdu_len); stream_putw(stream, hdr->rem_lifetime); stream_put(stream, hdr->lsp_id, sizeof(hdr->lsp_id)); stream_putl(stream, hdr->seqno); stream_putw(stream, hdr->checksum); stream_putc(stream, hdr->lsp_bits); if (keep) { stream_set_endp(lsp->pdu, orig_endp); stream_set_getp(lsp->pdu, orig_getp); } } static void lsp_add_auth(struct isis_lsp *lsp) { struct isis_passwd *passwd; passwd = (lsp->level == IS_LEVEL_1) ? &lsp->area->area_passwd : &lsp->area->domain_passwd; isis_tlvs_add_auth(lsp->tlvs, passwd); } static void lsp_pack_pdu(struct isis_lsp *lsp) { if (!lsp->tlvs) lsp->tlvs = isis_alloc_tlvs(); lsp_add_auth(lsp); size_t len_pointer; put_lsp_hdr(lsp, &len_pointer, false); isis_pack_tlvs(lsp->tlvs, lsp->pdu, len_pointer, false, true); lsp->hdr.pdu_len = stream_get_endp(lsp->pdu); lsp->hdr.checksum = ntohs(fletcher_checksum(STREAM_DATA(lsp->pdu) + 12, stream_get_endp(lsp->pdu) - 12, 12)); } void lsp_inc_seqno(struct isis_lsp *lsp, uint32_t seqno) { uint32_t newseq; if (seqno == 0 || lsp->hdr.seqno > seqno) newseq = lsp->hdr.seqno + 1; else newseq = seqno + 1; #ifndef FABRICD /* check for overflow */ if (newseq < lsp->hdr.seqno) { /* send northbound notification */ isis_notif_lsp_exceed_max(lsp->area, rawlspid_print(lsp->hdr.lsp_id)); } #endif /* ifndef FABRICD */ lsp->hdr.seqno = newseq; lsp_pack_pdu(lsp); isis_spf_schedule(lsp->area, lsp->level); } static void lsp_purge_add_poi(struct isis_lsp *lsp, const uint8_t *sender) { if (!lsp->area->purge_originator) return; /* add purge originator identification */ if (!lsp->tlvs) lsp->tlvs = isis_alloc_tlvs(); isis_tlvs_set_purge_originator(lsp->tlvs, isis->sysid, sender); isis_tlvs_set_dynamic_hostname(lsp->tlvs, cmd_hostname_get()); } static void lsp_purge(struct isis_lsp *lsp, int level, const uint8_t *sender) { /* reset stream */ lsp_clear_data(lsp); stream_reset(lsp->pdu); /* update header */ lsp->hdr.checksum = 0; lsp->hdr.rem_lifetime = 0; lsp->level = level; lsp->age_out = lsp->area->max_lsp_lifetime[level - 1]; lsp->area->lsp_purge_count[level - 1]++; lsp_purge_add_poi(lsp, sender); lsp_pack_pdu(lsp); lsp_flood(lsp, NULL); } /* * Generates checksum for LSP and its frags */ static void lsp_seqno_update(struct isis_lsp *lsp0) { struct isis_lsp *lsp; struct listnode *node; lsp_inc_seqno(lsp0, 0); if (!lsp0->lspu.frags) return; for (ALL_LIST_ELEMENTS_RO(lsp0->lspu.frags, node, lsp)) { if (lsp->tlvs) lsp_inc_seqno(lsp, 0); else if (lsp->hdr.rem_lifetime) { /* Purge should only be applied when the fragment has * non-zero remaining lifetime. */ lsp_purge(lsp, lsp0->level, NULL); } } return; } static uint8_t lsp_bits_generate(int level, int overload_bit, int attached_bit) { uint8_t lsp_bits = 0; if (level == IS_LEVEL_1) lsp_bits = IS_LEVEL_1; else lsp_bits = IS_LEVEL_1_AND_2; if (overload_bit) lsp_bits |= overload_bit; if (attached_bit) lsp_bits |= attached_bit; return lsp_bits; } static void lsp_update_data(struct isis_lsp *lsp, struct isis_lsp_hdr *hdr, struct isis_tlvs *tlvs, struct stream *stream, struct isis_area *area, int level) { /* free the old lsp data */ lsp_clear_data(lsp); /* copying only the relevant part of our stream */ if (lsp->pdu != NULL) stream_free(lsp->pdu); lsp->pdu = stream_dup(stream); memcpy(&lsp->hdr, hdr, sizeof(lsp->hdr)); lsp->area = area; lsp->level = level; lsp->age_out = ZERO_AGE_LIFETIME; lsp->installed = time(NULL); lsp->tlvs = tlvs; if (area->dynhostname && lsp->tlvs->hostname && lsp->hdr.rem_lifetime) { isis_dynhn_insert(lsp->hdr.lsp_id, lsp->tlvs->hostname, (lsp->hdr.lsp_bits & LSPBIT_IST) == IS_LEVEL_1_AND_2 ? IS_LEVEL_2 : IS_LEVEL_1); } return; } static void lsp_link_fragment(struct isis_lsp *lsp, struct isis_lsp *lsp0) { if (!LSP_FRAGMENT(lsp->hdr.lsp_id)) { /* zero lsp -> create list to store fragments */ lsp->lspu.frags = list_new(); } else { /* fragment -> set backpointer and add to zero lsps list */ assert(lsp0); lsp->lspu.zero_lsp = lsp0; listnode_add(lsp0->lspu.frags, lsp); } } void lsp_update(struct isis_lsp *lsp, struct isis_lsp_hdr *hdr, struct isis_tlvs *tlvs, struct stream *stream, struct isis_area *area, int level, bool confusion) { if (lsp->own_lsp) { flog_err( EC_LIB_DEVELOPMENT, "ISIS-Upd (%s): BUG updating LSP %s still marked as own LSP", area->area_tag, rawlspid_print(lsp->hdr.lsp_id)); lsp_clear_data(lsp); lsp->own_lsp = 0; } if (confusion) { lsp_purge(lsp, level, NULL); } else { lsp_update_data(lsp, hdr, tlvs, stream, area, level); } if (LSP_FRAGMENT(lsp->hdr.lsp_id) && !lsp->lspu.zero_lsp) { uint8_t lspid[ISIS_SYS_ID_LEN + 2]; struct isis_lsp *lsp0; memcpy(lspid, lsp->hdr.lsp_id, ISIS_SYS_ID_LEN + 1); LSP_FRAGMENT(lspid) = 0; lsp0 = lsp_search(&area->lspdb[level - 1], lspid); if (lsp0) lsp_link_fragment(lsp, lsp0); } if (lsp->hdr.seqno) isis_spf_schedule(lsp->area, lsp->level); } /* creation of LSP directly from what we received */ struct isis_lsp *lsp_new_from_recv(struct isis_lsp_hdr *hdr, struct isis_tlvs *tlvs, struct stream *stream, struct isis_lsp *lsp0, struct isis_area *area, int level) { struct isis_lsp *lsp; lsp = XCALLOC(MTYPE_ISIS_LSP, sizeof(struct isis_lsp)); lsp_update_data(lsp, hdr, tlvs, stream, area, level); lsp_link_fragment(lsp, lsp0); return lsp; } static void lsp_adjust_stream(struct isis_lsp *lsp) { if (lsp->pdu) { if (STREAM_SIZE(lsp->pdu) == LLC_LEN + lsp->area->lsp_mtu) return; stream_free(lsp->pdu); } lsp->pdu = stream_new(LLC_LEN + lsp->area->lsp_mtu); } struct isis_lsp *lsp_new(struct isis_area *area, uint8_t *lsp_id, uint16_t rem_lifetime, uint32_t seqno, uint8_t lsp_bits, uint16_t checksum, struct isis_lsp *lsp0, int level) { struct isis_lsp *lsp; lsp = XCALLOC(MTYPE_ISIS_LSP, sizeof(struct isis_lsp)); lsp->area = area; lsp_adjust_stream(lsp); /* Minimal LSP PDU size */ lsp->hdr.pdu_len = ISIS_FIXED_HDR_LEN + ISIS_LSP_HDR_LEN; memcpy(lsp->hdr.lsp_id, lsp_id, sizeof(lsp->hdr.lsp_id)); lsp->hdr.checksum = checksum; lsp->hdr.seqno = seqno; lsp->hdr.rem_lifetime = rem_lifetime; lsp->hdr.lsp_bits = lsp_bits; lsp->level = level; lsp->age_out = ZERO_AGE_LIFETIME; lsp_link_fragment(lsp, lsp0); put_lsp_hdr(lsp, NULL, false); if (isis->debugs & DEBUG_EVENTS) zlog_debug("New LSP with ID %s-%02x-%02x len %d seqnum %08x", sysid_print(lsp_id), LSP_PSEUDO_ID(lsp->hdr.lsp_id), LSP_FRAGMENT(lsp->hdr.lsp_id), lsp->hdr.pdu_len, lsp->hdr.seqno); return lsp; } void lsp_insert(struct lspdb_head *head, struct isis_lsp *lsp) { lspdb_add(head, lsp); if (lsp->hdr.seqno) isis_spf_schedule(lsp->area, lsp->level); } /* * Build a list of LSPs with non-zero ht bounded by start and stop ids */ void lsp_build_list_nonzero_ht(struct lspdb_head *head, const uint8_t *start_id, const uint8_t *stop_id, struct list *list) { struct isis_lsp searchfor; struct isis_lsp *lsp, *start; memcpy(&searchfor.hdr.lsp_id, start_id, sizeof(searchfor.hdr.lsp_id)); start = lspdb_find_gteq(head, &searchfor); frr_each_from (lspdb, head, lsp, start) { if (memcmp(lsp->hdr.lsp_id, stop_id, ISIS_SYS_ID_LEN + 2) > 0) break; if (lsp->hdr.rem_lifetime) listnode_add(list, lsp); } } static void lsp_set_time(struct isis_lsp *lsp) { assert(lsp); if (lsp->hdr.rem_lifetime == 0) { if (lsp->age_out > 0) lsp->age_out--; return; } lsp->hdr.rem_lifetime--; if (lsp->pdu && stream_get_endp(lsp->pdu) >= 12) stream_putw_at(lsp->pdu, 10, lsp->hdr.rem_lifetime); } void lspid_print(uint8_t *lsp_id, char *dest, char dynhost, char frag) { struct isis_dynhn *dyn = NULL; uint8_t id[SYSID_STRLEN]; if (dynhost) dyn = dynhn_find_by_id(lsp_id); else dyn = NULL; if (dyn) sprintf((char *)id, "%.14s", dyn->hostname); else if (!memcmp(isis->sysid, lsp_id, ISIS_SYS_ID_LEN) && dynhost) sprintf((char *)id, "%.14s", cmd_hostname_get()); else memcpy(id, sysid_print(lsp_id), 15); if (frag) sprintf(dest, "%s.%02x-%02x", id, LSP_PSEUDO_ID(lsp_id), LSP_FRAGMENT(lsp_id)); else sprintf(dest, "%s.%02x", id, LSP_PSEUDO_ID(lsp_id)); } /* Convert the lsp attribute bits to attribute string */ static const char *lsp_bits2string(uint8_t lsp_bits, char *buf, size_t buf_size) { char *pos = buf; if (!lsp_bits) return " none"; if (buf_size < 2 * 3) return " error"; /* we only focus on the default metric */ pos += sprintf(pos, "%d/", ISIS_MASK_LSP_ATT_DEFAULT_BIT(lsp_bits) ? 1 : 0); pos += sprintf(pos, "%d/", ISIS_MASK_LSP_PARTITION_BIT(lsp_bits) ? 1 : 0); sprintf(pos, "%d", ISIS_MASK_LSP_OL_BIT(lsp_bits) ? 1 : 0); return buf; } /* this function prints the lsp on show isis database */ void lsp_print(struct isis_lsp *lsp, struct vty *vty, char dynhost) { char LSPid[255]; char age_out[8]; char b[200]; lspid_print(lsp->hdr.lsp_id, LSPid, dynhost, 1); vty_out(vty, "%-21s%c ", LSPid, lsp->own_lsp ? '*' : ' '); vty_out(vty, "%5" PRIu16 " ", lsp->hdr.pdu_len); vty_out(vty, "0x%08" PRIx32 " ", lsp->hdr.seqno); vty_out(vty, "0x%04" PRIx16 " ", lsp->hdr.checksum); if (lsp->hdr.rem_lifetime == 0) { snprintf(age_out, 8, "(%d)", lsp->age_out); age_out[7] = '\0'; vty_out(vty, "%7s ", age_out); } else vty_out(vty, " %5" PRIu16 " ", lsp->hdr.rem_lifetime); vty_out(vty, "%s\n", lsp_bits2string(lsp->hdr.lsp_bits, b, sizeof(b))); } void lsp_print_detail(struct isis_lsp *lsp, struct vty *vty, char dynhost) { lsp_print(lsp, vty, dynhost); if (lsp->tlvs) vty_multiline(vty, " ", "%s", isis_format_tlvs(lsp->tlvs)); vty_out(vty, "\n"); } /* print all the lsps info in the local lspdb */ int lsp_print_all(struct vty *vty, struct lspdb_head *head, char detail, char dynhost) { struct isis_lsp *lsp; int lsp_count = 0; if (detail == ISIS_UI_LEVEL_BRIEF) { frr_each (lspdb, head, lsp) { lsp_print(lsp, vty, dynhost); lsp_count++; } } else if (detail == ISIS_UI_LEVEL_DETAIL) { frr_each (lspdb, head, lsp) { lsp_print_detail(lsp, vty, dynhost); lsp_count++; } } return lsp_count; } static uint16_t lsp_rem_lifetime(struct isis_area *area, int level) { uint16_t rem_lifetime; /* Add jitter to configured LSP lifetime */ rem_lifetime = isis_jitter(area->max_lsp_lifetime[level - 1], MAX_AGE_JITTER); /* No jitter if the max refresh will be less than configure gen interval */ /* N.B. this calucation is acceptable since rem_lifetime is in * [332,65535] at * this point */ if (area->lsp_gen_interval[level - 1] > (rem_lifetime - 300)) rem_lifetime = area->max_lsp_lifetime[level - 1]; return rem_lifetime; } static uint16_t lsp_refresh_time(struct isis_lsp *lsp, uint16_t rem_lifetime) { struct isis_area *area = lsp->area; int level = lsp->level; uint16_t refresh_time; /* Add jitter to LSP refresh time */ refresh_time = isis_jitter(area->lsp_refresh[level - 1], MAX_LSP_GEN_JITTER); /* RFC 4444 : make sure the refresh time is at least less than 300 * of the remaining lifetime and more than gen interval */ if (refresh_time <= area->lsp_gen_interval[level - 1] || refresh_time > (rem_lifetime - 300)) refresh_time = rem_lifetime - 300; /* In cornercases, refresh_time might be <= lsp_gen_interval, however * we accept this violation to satisfy refresh_time <= rem_lifetime - * 300 */ return refresh_time; } static void lsp_build_ext_reach_ipv4(struct isis_lsp *lsp, struct isis_area *area) { struct route_table *er_table = get_ext_reach(area, AF_INET, lsp->level); if (!er_table) return; for (struct route_node *rn = route_top(er_table); rn; rn = route_next(rn)) { if (!rn->info) continue; struct prefix_ipv4 *ipv4 = (struct prefix_ipv4 *)&rn->p; struct isis_ext_info *info = rn->info; uint32_t metric = info->metric; if (metric > MAX_WIDE_PATH_METRIC) metric = MAX_WIDE_PATH_METRIC; if (area->oldmetric && metric > 0x3f) metric = 0x3f; if (area->oldmetric) isis_tlvs_add_oldstyle_ip_reach(lsp->tlvs, ipv4, metric); if (area->newmetric) isis_tlvs_add_extended_ip_reach(lsp->tlvs, ipv4, metric); } } static void lsp_build_ext_reach_ipv6(struct isis_lsp *lsp, struct isis_area *area) { struct route_table *er_table = get_ext_reach(area, AF_INET6, lsp->level); if (!er_table) return; for (struct route_node *rn = route_top(er_table); rn; rn = srcdest_route_next(rn)) { if (!rn->info) continue; struct isis_ext_info *info = rn->info; struct prefix_ipv6 *p, *src_p; srcdest_rnode_prefixes(rn, (const struct prefix **)&p, (const struct prefix **)&src_p); uint32_t metric = info->metric; if (info->metric > MAX_WIDE_PATH_METRIC) metric = MAX_WIDE_PATH_METRIC; if (!src_p || !src_p->prefixlen) { isis_tlvs_add_ipv6_reach(lsp->tlvs, isis_area_ipv6_topology(area), p, metric); } else if (isis_area_ipv6_dstsrc_enabled(area)) { isis_tlvs_add_ipv6_dstsrc_reach(lsp->tlvs, ISIS_MT_IPV6_DSTSRC, p, src_p, metric); } } } static void lsp_build_ext_reach(struct isis_lsp *lsp, struct isis_area *area) { lsp_build_ext_reach_ipv4(lsp, area); lsp_build_ext_reach_ipv6(lsp, area); } static struct isis_lsp *lsp_next_frag(uint8_t frag_num, struct isis_lsp *lsp0, struct isis_area *area, int level) { struct isis_lsp *lsp; uint8_t frag_id[ISIS_SYS_ID_LEN + 2]; memcpy(frag_id, lsp0->hdr.lsp_id, ISIS_SYS_ID_LEN + 1); LSP_FRAGMENT(frag_id) = frag_num; lsp = lsp_search(&area->lspdb[level - 1], frag_id); if (lsp) { lsp_clear_data(lsp); if (!lsp->lspu.zero_lsp) lsp_link_fragment(lsp, lsp0); return lsp; } lsp = lsp_new(area, frag_id, lsp0->hdr.rem_lifetime, 0, lsp_bits_generate(level, area->overload_bit, area->attached_bit), 0, lsp0, level); lsp->own_lsp = 1; lsp_insert(&area->lspdb[level - 1], lsp); return lsp; } /* * Builds the LSP data part. This func creates a new frag whenever * area->lsp_frag_threshold is exceeded. */ static void lsp_build(struct isis_lsp *lsp, struct isis_area *area) { int level = lsp->level; char buf[PREFIX2STR_BUFFER]; struct listnode *node; struct isis_lsp *frag; lsp_clear_data(lsp); for (ALL_LIST_ELEMENTS_RO(lsp->lspu.frags, node, frag)) lsp_clear_data(frag); lsp->tlvs = isis_alloc_tlvs(); lsp_debug("ISIS (%s): Constructing local system LSP for level %d", area->area_tag, level); lsp->hdr.lsp_bits = lsp_bits_generate(level, area->overload_bit, area->attached_bit); lsp_add_auth(lsp); isis_tlvs_add_area_addresses(lsp->tlvs, area->area_addrs); /* Protocols Supported */ if (area->ip_circuits > 0 || area->ipv6_circuits > 0) { struct nlpids nlpids = {.count = 0}; if (area->ip_circuits > 0) { lsp_debug( "ISIS (%s): Found IPv4 circuit, adding IPv4 to NLPIDs", area->area_tag); nlpids.nlpids[nlpids.count] = NLPID_IP; nlpids.count++; } if (area->ipv6_circuits > 0) { lsp_debug( "ISIS (%s): Found IPv6 circuit, adding IPv6 to NLPIDs", area->area_tag); nlpids.nlpids[nlpids.count] = NLPID_IPV6; nlpids.count++; } isis_tlvs_set_protocols_supported(lsp->tlvs, &nlpids); } if (area_is_mt(area)) { lsp_debug("ISIS (%s): Adding MT router tlv...", area->area_tag); struct isis_area_mt_setting **mt_settings; unsigned int mt_count; mt_settings = area_mt_settings(area, &mt_count); for (unsigned int i = 0; i < mt_count; i++) { isis_tlvs_add_mt_router_info( lsp->tlvs, mt_settings[i]->mtid, mt_settings[i]->overload, false); lsp_debug("ISIS (%s): MT %s", area->area_tag, isis_mtid2str(mt_settings[i]->mtid)); } } else { lsp_debug("ISIS (%s): Not adding MT router tlv (disabled)", area->area_tag); } /* Dynamic Hostname */ if (area->dynhostname) { isis_tlvs_set_dynamic_hostname(lsp->tlvs, cmd_hostname_get()); lsp_debug("ISIS (%s): Adding dynamic hostname '%s'", area->area_tag, cmd_hostname_get()); } else { lsp_debug("ISIS (%s): Not adding dynamic hostname (disabled)", area->area_tag); } /* IPv4 address and TE router ID TLVs. In case of the first one we don't * follow "C" vendor, but "J" vendor behavior - one IPv4 address is put * into * LSP and this address is same as router id. */ if (isis->router_id != 0) { struct in_addr id = {.s_addr = isis->router_id}; inet_ntop(AF_INET, &id, buf, sizeof(buf)); lsp_debug("ISIS (%s): Adding router ID %s as IPv4 tlv.", area->area_tag, buf); isis_tlvs_add_ipv4_address(lsp->tlvs, &id); /* Exactly same data is put into TE router ID TLV, but only if * new style * TLV's are in use. */ if (area->newmetric) { lsp_debug( "ISIS (%s): Adding router ID also as TE router ID tlv.", area->area_tag); isis_tlvs_set_te_router_id(lsp->tlvs, &id); } } else { lsp_debug("ISIS (%s): Router ID is unset. Not adding tlv.", area->area_tag); } lsp_debug("ISIS (%s): Adding circuit specific information.", area->area_tag); if (fabricd) { lsp_debug( "ISIS (%s): Adding tier %" PRIu8 " spine-leaf-extension tlv.", area->area_tag, fabricd_tier(area)); isis_tlvs_add_spine_leaf(lsp->tlvs, fabricd_tier(area), true, false, false, false); } struct isis_circuit *circuit; for (ALL_LIST_ELEMENTS_RO(area->circuit_list, node, circuit)) { if (!circuit->interface) lsp_debug( "ISIS (%s): Processing %s circuit %p with unknown interface", area->area_tag, circuit_type2string(circuit->circ_type), circuit); else lsp_debug("ISIS (%s): Processing %s circuit %s", area->area_tag, circuit_type2string(circuit->circ_type), circuit->interface->name); if (circuit->state != C_STATE_UP) { lsp_debug("ISIS (%s): Circuit is not up, ignoring.", area->area_tag); continue; } uint32_t metric = area->oldmetric ? circuit->metric[level - 1] : circuit->te_metric[level - 1]; if (circuit->ip_router && circuit->ip_addrs && circuit->ip_addrs->count > 0) { lsp_debug( "ISIS (%s): Circuit has IPv4 active, adding respective TLVs.", area->area_tag); struct listnode *ipnode; struct prefix_ipv4 *ipv4; for (ALL_LIST_ELEMENTS_RO(circuit->ip_addrs, ipnode, ipv4)) { if (area->oldmetric) { lsp_debug( "ISIS (%s): Adding old-style IP reachability for %s", area->area_tag, prefix2str(ipv4, buf, sizeof(buf))); isis_tlvs_add_oldstyle_ip_reach( lsp->tlvs, ipv4, metric); } if (area->newmetric) { lsp_debug( "ISIS (%s): Adding te-style IP reachability for %s", area->area_tag, prefix2str(ipv4, buf, sizeof(buf))); isis_tlvs_add_extended_ip_reach( lsp->tlvs, ipv4, metric); } } } if (circuit->ipv6_router && circuit->ipv6_non_link && circuit->ipv6_non_link->count > 0) { struct listnode *ipnode; struct prefix_ipv6 *ipv6; for (ALL_LIST_ELEMENTS_RO(circuit->ipv6_non_link, ipnode, ipv6)) { lsp_debug( "ISIS (%s): Adding IPv6 reachability for %s", area->area_tag, prefix2str(ipv6, buf, sizeof(buf))); isis_tlvs_add_ipv6_reach( lsp->tlvs, isis_area_ipv6_topology(area), ipv6, metric); } } switch (circuit->circ_type) { case CIRCUIT_T_BROADCAST: if (level & circuit->is_type) { uint8_t *ne_id = (level == IS_LEVEL_1) ? circuit->u.bc.l1_desig_is : circuit->u.bc.l2_desig_is; if (LSP_PSEUDO_ID(ne_id)) { if (area->oldmetric) { lsp_debug( "ISIS (%s): Adding DIS %s.%02x as old-style neighbor", area->area_tag, sysid_print(ne_id), LSP_PSEUDO_ID(ne_id)); isis_tlvs_add_oldstyle_reach( lsp->tlvs, ne_id, metric); } if (area->newmetric) { uint8_t subtlvs[256]; uint8_t subtlv_len; if (IS_MPLS_TE(area->mta) && circuit->interface && HAS_LINK_PARAMS( circuit->interface)) subtlv_len = add_te_subtlvs( subtlvs, circuit->mtc); else subtlv_len = 0; tlvs_add_mt_bcast( lsp->tlvs, circuit, level, ne_id, metric, subtlvs, subtlv_len); } } } else { lsp_debug( "ISIS (%s): Circuit is not active for current level. Not adding IS neighbors", area->area_tag); } break; case CIRCUIT_T_P2P: { struct isis_adjacency *nei = circuit->u.p2p.neighbor; if (nei && nei->adj_state == ISIS_ADJ_UP && (level & nei->circuit_t)) { uint8_t ne_id[7]; memcpy(ne_id, nei->sysid, ISIS_SYS_ID_LEN); LSP_PSEUDO_ID(ne_id) = 0; if (area->oldmetric) { lsp_debug( "ISIS (%s): Adding old-style is reach for %s", area->area_tag, sysid_print(ne_id)); isis_tlvs_add_oldstyle_reach( lsp->tlvs, ne_id, metric); } if (area->newmetric) { uint8_t subtlvs[256]; uint8_t subtlv_len; if (IS_MPLS_TE(area->mta) && circuit->interface != NULL && HAS_LINK_PARAMS( circuit->interface)) /* Update Local and Remote IP * address for MPLS TE circuit * parameters */ /* NOTE sure that it is the * pertinent place for that * updates */ /* Local IP address could be * updated in isis_circuit.c - * isis_circuit_add_addr() */ /* But, where update remote IP * address ? in isis_pdu.c - * process_p2p_hello() ? */ /* Add SubTLVs & Adjust real * size of SubTLVs */ subtlv_len = add_te_subtlvs( subtlvs, circuit->mtc); else /* Or keep only TE metric with * no SubTLVs if MPLS_TE is off */ subtlv_len = 0; uint32_t neighbor_metric; if (fabricd_tier(area) == 0) { neighbor_metric = 0xffe; } else { neighbor_metric = metric; } tlvs_add_mt_p2p(lsp->tlvs, circuit, ne_id, neighbor_metric, subtlvs, subtlv_len); } } else { lsp_debug( "ISIS (%s): No adjacency for given level on this circuit. Not adding IS neighbors", area->area_tag); } } break; case CIRCUIT_T_LOOPBACK: break; default: zlog_warn("lsp_area_create: unknown circuit type"); } } lsp_build_ext_reach(lsp, area); struct isis_tlvs *tlvs = lsp->tlvs; lsp->tlvs = NULL; lsp_adjust_stream(lsp); lsp_pack_pdu(lsp); size_t tlv_space = STREAM_WRITEABLE(lsp->pdu) - LLC_LEN; lsp_clear_data(lsp); struct list *fragments = isis_fragment_tlvs(tlvs, tlv_space); if (!fragments) { zlog_warn("BUG: could not fragment own LSP:"); log_multiline(LOG_WARNING, " ", "%s", isis_format_tlvs(tlvs)); isis_free_tlvs(tlvs); return; } isis_free_tlvs(tlvs); bool fragment_overflow = false; frag = lsp; for (ALL_LIST_ELEMENTS_RO(fragments, node, tlvs)) { if (node != listhead(fragments)) { if (LSP_FRAGMENT(frag->hdr.lsp_id) == 255) { if (!fragment_overflow) { fragment_overflow = true; zlog_warn( "ISIS (%s): Too much information for 256 fragments", area->area_tag); } isis_free_tlvs(tlvs); continue; } frag = lsp_next_frag(LSP_FRAGMENT(frag->hdr.lsp_id) + 1, lsp, area, level); lsp_adjust_stream(frag); } frag->tlvs = tlvs; } list_delete(&fragments); lsp_debug("ISIS (%s): LSP construction is complete. Serializing...", area->area_tag); return; } /* * 7.3.7 and 7.3.9 Generation on non-pseudonode LSPs */ int lsp_generate(struct isis_area *area, int level) { struct isis_lsp *oldlsp, *newlsp; uint32_t seq_num = 0; uint8_t lspid[ISIS_SYS_ID_LEN + 2]; uint16_t rem_lifetime, refresh_time; if ((area == NULL) || (area->is_type & level) != level) return ISIS_ERROR; memset(&lspid, 0, ISIS_SYS_ID_LEN + 2); memcpy(&lspid, isis->sysid, ISIS_SYS_ID_LEN); /* only builds the lsp if the area shares the level */ oldlsp = lsp_search(&area->lspdb[level - 1], lspid); if (oldlsp) { /* FIXME: we should actually initiate a purge */ seq_num = oldlsp->hdr.seqno; lsp_search_and_destroy(&area->lspdb[level - 1], oldlsp->hdr.lsp_id); } rem_lifetime = lsp_rem_lifetime(area, level); newlsp = lsp_new(area, lspid, rem_lifetime, seq_num, area->is_type | area->overload_bit | area->attached_bit, 0, NULL, level); newlsp->area = area; newlsp->own_lsp = 1; lsp_insert(&area->lspdb[level - 1], newlsp); /* build_lsp_data (newlsp, area); */ lsp_build(newlsp, area); /* time to calculate our checksum */ lsp_seqno_update(newlsp); newlsp->last_generated = time(NULL); lsp_flood(newlsp, NULL); area->lsp_gen_count[level - 1]++; refresh_time = lsp_refresh_time(newlsp, rem_lifetime); THREAD_TIMER_OFF(area->t_lsp_refresh[level - 1]); area->lsp_regenerate_pending[level - 1] = 0; thread_add_timer(master, lsp_refresh, &area->lsp_refresh_arg[level - 1], refresh_time, &area->t_lsp_refresh[level - 1]); if (isis->debugs & DEBUG_UPDATE_PACKETS) { zlog_debug("ISIS-Upd (%s): Building L%d LSP %s, len %" PRIu16 ", seq 0x%08" PRIx32 ", cksum 0x%04" PRIx16 ", lifetime %" PRIu16 "s refresh %" PRIu16 "s", area->area_tag, level, rawlspid_print(newlsp->hdr.lsp_id), newlsp->hdr.pdu_len, newlsp->hdr.seqno, newlsp->hdr.checksum, newlsp->hdr.rem_lifetime, refresh_time); } sched_debug( "ISIS (%s): Built L%d LSP. Set triggered regenerate to non-pending.", area->area_tag, level); #ifndef FABRICD /* send northbound notification */ isis_notif_lsp_gen(area, rawlspid_print(newlsp->hdr.lsp_id), newlsp->hdr.seqno, newlsp->last_generated); #endif /* ifndef FABRICD */ return ISIS_OK; } /* * Search own LSPs, update holding time and flood */ static int lsp_regenerate(struct isis_area *area, int level) { struct lspdb_head *head; struct isis_lsp *lsp, *frag; struct listnode *node; uint8_t lspid[ISIS_SYS_ID_LEN + 2]; uint16_t rem_lifetime, refresh_time; if ((area == NULL) || (area->is_type & level) != level) return ISIS_ERROR; head = &area->lspdb[level - 1]; memset(lspid, 0, ISIS_SYS_ID_LEN + 2); memcpy(lspid, isis->sysid, ISIS_SYS_ID_LEN); lsp = lsp_search(head, lspid); if (!lsp) { flog_err(EC_LIB_DEVELOPMENT, "ISIS-Upd (%s): lsp_regenerate: no L%d LSP found!", area->area_tag, level); return ISIS_ERROR; } lsp_clear_data(lsp); lsp_build(lsp, area); rem_lifetime = lsp_rem_lifetime(area, level); lsp->hdr.rem_lifetime = rem_lifetime; lsp->last_generated = time(NULL); lsp_flood(lsp, NULL); area->lsp_gen_count[level - 1]++; for (ALL_LIST_ELEMENTS_RO(lsp->lspu.frags, node, frag)) { if (!frag->tlvs) { /* Updating and flooding should only affect fragments * carrying data */ continue; } frag->hdr.lsp_bits = lsp_bits_generate( level, area->overload_bit, area->attached_bit); /* Set the lifetime values of all the fragments to the same * value, * so that no fragment expires before the lsp is refreshed. */ frag->hdr.rem_lifetime = rem_lifetime; frag->age_out = ZERO_AGE_LIFETIME; lsp_flood(frag, NULL); } lsp_seqno_update(lsp); refresh_time = lsp_refresh_time(lsp, rem_lifetime); thread_add_timer(master, lsp_refresh, &area->lsp_refresh_arg[level - 1], refresh_time, &area->t_lsp_refresh[level - 1]); area->lsp_regenerate_pending[level - 1] = 0; if (isis->debugs & DEBUG_UPDATE_PACKETS) { zlog_debug( "ISIS-Upd (%s): Refreshed our L%d LSP %s, len %" PRIu16 ", seq 0x%08" PRIx32 ", cksum 0x%04" PRIx16 ", lifetime %" PRIu16 "s refresh %" PRIu16 "s", area->area_tag, level, rawlspid_print(lsp->hdr.lsp_id), lsp->hdr.pdu_len, lsp->hdr.seqno, lsp->hdr.checksum, lsp->hdr.rem_lifetime, refresh_time); } sched_debug( "ISIS (%s): Rebuilt L%d LSP. Set triggered regenerate to non-pending.", area->area_tag, level); return ISIS_OK; } /* * Something has changed or periodic refresh -> regenerate LSP */ static int lsp_refresh(struct thread *thread) { struct lsp_refresh_arg *arg = THREAD_ARG(thread); assert(arg); struct isis_area *area = arg->area; assert(area); int level = arg->level; area->t_lsp_refresh[level - 1] = NULL; area->lsp_regenerate_pending[level - 1] = 0; if ((area->is_type & level) == 0) return ISIS_ERROR; if (monotime_since(&area->last_lsp_refresh_event[level - 1], NULL) < 100000L) { sched_debug("ISIS (%s): Still unstable, postpone LSP L%d refresh", area->area_tag, level); _lsp_regenerate_schedule(area, level, 0, false, __func__, __FILE__, __LINE__); return 0; } sched_debug( "ISIS (%s): LSP L%d refresh timer expired. Refreshing LSP...", area->area_tag, level); return lsp_regenerate(area, level); } int _lsp_regenerate_schedule(struct isis_area *area, int level, int all_pseudo, bool postpone, const char *func, const char *file, int line) { struct isis_lsp *lsp; uint8_t id[ISIS_SYS_ID_LEN + 2]; time_t now, diff; long timeout; struct listnode *cnode; struct isis_circuit *circuit; int lvl; if (area == NULL) return ISIS_ERROR; sched_debug( "ISIS (%s): Scheduling regeneration of %s LSPs, %sincluding PSNs" " Caller: %s %s:%d", area->area_tag, circuit_t2string(level), all_pseudo ? "" : "not ", func, file, line); memcpy(id, isis->sysid, ISIS_SYS_ID_LEN); LSP_PSEUDO_ID(id) = LSP_FRAGMENT(id) = 0; now = time(NULL); for (lvl = IS_LEVEL_1; lvl <= IS_LEVEL_2; lvl++) { if (!((level & lvl) && (area->is_type & lvl))) continue; if (postpone) { monotime(&area->last_lsp_refresh_event[lvl - 1]); } sched_debug( "ISIS (%s): Checking whether L%d needs to be scheduled", area->area_tag, lvl); if (area->lsp_regenerate_pending[lvl - 1]) { struct timeval remain = thread_timer_remain( area->t_lsp_refresh[lvl - 1]); sched_debug( "ISIS (%s): Regeneration is already pending, nothing todo." " (Due in %lld.%03lld seconds)", area->area_tag, (long long)remain.tv_sec, (long long)remain.tv_usec / 1000); continue; } lsp = lsp_search(&area->lspdb[lvl - 1], id); if (!lsp) { sched_debug( "ISIS (%s): We do not have any LSPs to regenerate, nothing todo.", area->area_tag); continue; } /* * Throttle avoidance */ sched_debug( "ISIS (%s): Will schedule regen timer. Last run was: %lld, Now is: %lld", area->area_tag, (long long)lsp->last_generated, (long long)now); THREAD_TIMER_OFF(area->t_lsp_refresh[lvl - 1]); diff = now - lsp->last_generated; if (diff < area->lsp_gen_interval[lvl - 1]) { timeout = 1000 * (area->lsp_gen_interval[lvl - 1] - diff); sched_debug( "ISIS (%s): Scheduling in %ld ms to match configured lsp_gen_interval", area->area_tag, timeout); } else { /* * lsps are not regenerated if lsp_regenerate function * is called * directly. However if the lsp_regenerate call is * queued for * later execution it works. */ timeout = 100; sched_debug( "ISIS (%s): Last generation was more than lsp_gen_interval ago." " Scheduling for execution in %ld ms.", area->area_tag, timeout); } area->lsp_regenerate_pending[lvl - 1] = 1; thread_add_timer_msec(master, lsp_refresh, &area->lsp_refresh_arg[lvl - 1], timeout, &area->t_lsp_refresh[lvl - 1]); } if (all_pseudo) { for (ALL_LIST_ELEMENTS_RO(area->circuit_list, cnode, circuit)) lsp_regenerate_schedule_pseudo(circuit, level); } return ISIS_OK; } /* * Funcs for pseudonode LSPs */ /* * 7.3.8 and 7.3.10 Generation of level 1 and 2 pseudonode LSPs */ static void lsp_build_pseudo(struct isis_lsp *lsp, struct isis_circuit *circuit, int level) { struct isis_adjacency *adj; struct list *adj_list; struct listnode *node; struct isis_area *area = circuit->area; lsp_clear_data(lsp); lsp->tlvs = isis_alloc_tlvs(); lsp_debug( "ISIS (%s): Constructing pseudo LSP %s for interface %s level %d", area->area_tag, rawlspid_print(lsp->hdr.lsp_id), circuit->interface->name, level); lsp->level = level; /* RFC3787 section 4 SHOULD not set overload bit in pseudo LSPs */ lsp->hdr.lsp_bits = lsp_bits_generate(level, 0, circuit->area->attached_bit); /* * add self to IS neighbours */ uint8_t ne_id[ISIS_SYS_ID_LEN + 1]; memcpy(ne_id, isis->sysid, ISIS_SYS_ID_LEN); LSP_PSEUDO_ID(ne_id) = 0; if (circuit->area->oldmetric) { isis_tlvs_add_oldstyle_reach(lsp->tlvs, ne_id, 0); lsp_debug( "ISIS (%s): Adding %s.%02x as old-style neighbor (self)", area->area_tag, sysid_print(ne_id), LSP_PSEUDO_ID(ne_id)); } if (circuit->area->newmetric) { isis_tlvs_add_extended_reach(lsp->tlvs, ISIS_MT_IPV4_UNICAST, ne_id, 0, NULL, 0); lsp_debug( "ISIS (%s): Adding %s.%02x as te-style neighbor (self)", area->area_tag, sysid_print(ne_id), LSP_PSEUDO_ID(ne_id)); } adj_list = list_new(); isis_adj_build_up_list(circuit->u.bc.adjdb[level - 1], adj_list); for (ALL_LIST_ELEMENTS_RO(adj_list, node, adj)) { if (!(adj->level & level)) { lsp_debug( "ISIS (%s): Ignoring neighbor %s, level does not intersect", area->area_tag, sysid_print(adj->sysid)); continue; } if (!(level == IS_LEVEL_1 && adj->sys_type == ISIS_SYSTYPE_L1_IS) && !(level == IS_LEVEL_1 && adj->sys_type == ISIS_SYSTYPE_L2_IS && adj->adj_usage == ISIS_ADJ_LEVEL1AND2) && !(level == IS_LEVEL_2 && adj->sys_type == ISIS_SYSTYPE_L2_IS)) { lsp_debug( "ISIS (%s): Ignoring neighbor %s, level does not match", area->area_tag, sysid_print(adj->sysid)); continue; } memcpy(ne_id, adj->sysid, ISIS_SYS_ID_LEN); if (circuit->area->oldmetric) { isis_tlvs_add_oldstyle_reach(lsp->tlvs, ne_id, 0); lsp_debug( "ISIS (%s): Adding %s.%02x as old-style neighbor (peer)", area->area_tag, sysid_print(ne_id), LSP_PSEUDO_ID(ne_id)); } if (circuit->area->newmetric) { isis_tlvs_add_extended_reach(lsp->tlvs, ISIS_MT_IPV4_UNICAST, ne_id, 0, NULL, 0); lsp_debug( "ISIS (%s): Adding %s.%02x as te-style neighbor (peer)", area->area_tag, sysid_print(ne_id), LSP_PSEUDO_ID(ne_id)); } } list_delete(&adj_list); return; } int lsp_generate_pseudo(struct isis_circuit *circuit, int level) { struct lspdb_head *head = &circuit->area->lspdb[level - 1]; struct isis_lsp *lsp; uint8_t lsp_id[ISIS_SYS_ID_LEN + 2]; uint16_t rem_lifetime, refresh_time; if ((circuit->is_type & level) != level || (circuit->state != C_STATE_UP) || (circuit->circ_type != CIRCUIT_T_BROADCAST) || (circuit->u.bc.is_dr[level - 1] == 0)) return ISIS_ERROR; memcpy(lsp_id, isis->sysid, ISIS_SYS_ID_LEN); LSP_FRAGMENT(lsp_id) = 0; LSP_PSEUDO_ID(lsp_id) = circuit->circuit_id; /* * If for some reason have a pseudo LSP in the db already -> regenerate */ if (lsp_search(head, lsp_id)) return lsp_regenerate_schedule_pseudo(circuit, level); rem_lifetime = lsp_rem_lifetime(circuit->area, level); /* RFC3787 section 4 SHOULD not set overload bit in pseudo LSPs */ lsp = lsp_new(circuit->area, lsp_id, rem_lifetime, 1, circuit->area->is_type | circuit->area->attached_bit, 0, NULL, level); lsp->area = circuit->area; lsp_build_pseudo(lsp, circuit, level); lsp_pack_pdu(lsp); lsp->own_lsp = 1; lsp_insert(head, lsp); lsp_flood(lsp, NULL); refresh_time = lsp_refresh_time(lsp, rem_lifetime); THREAD_TIMER_OFF(circuit->u.bc.t_refresh_pseudo_lsp[level - 1]); circuit->lsp_regenerate_pending[level - 1] = 0; if (level == IS_LEVEL_1) thread_add_timer( master, lsp_l1_refresh_pseudo, circuit, refresh_time, &circuit->u.bc.t_refresh_pseudo_lsp[level - 1]); else if (level == IS_LEVEL_2) thread_add_timer( master, lsp_l2_refresh_pseudo, circuit, refresh_time, &circuit->u.bc.t_refresh_pseudo_lsp[level - 1]); if (isis->debugs & DEBUG_UPDATE_PACKETS) { zlog_debug( "ISIS-Upd (%s): Built L%d Pseudo LSP %s, len %" PRIu16 ", seq 0x%08" PRIx32 ", cksum 0x%04" PRIx16 ", lifetime %" PRIu16 "s, refresh %" PRIu16 "s", circuit->area->area_tag, level, rawlspid_print(lsp->hdr.lsp_id), lsp->hdr.pdu_len, lsp->hdr.seqno, lsp->hdr.checksum, lsp->hdr.rem_lifetime, refresh_time); } return ISIS_OK; } static int lsp_regenerate_pseudo(struct isis_circuit *circuit, int level) { struct lspdb_head *head = &circuit->area->lspdb[level - 1]; struct isis_lsp *lsp; uint8_t lsp_id[ISIS_SYS_ID_LEN + 2]; uint16_t rem_lifetime, refresh_time; if ((circuit->is_type & level) != level || (circuit->state != C_STATE_UP) || (circuit->circ_type != CIRCUIT_T_BROADCAST) || (circuit->u.bc.is_dr[level - 1] == 0)) return ISIS_ERROR; memcpy(lsp_id, isis->sysid, ISIS_SYS_ID_LEN); LSP_PSEUDO_ID(lsp_id) = circuit->circuit_id; LSP_FRAGMENT(lsp_id) = 0; lsp = lsp_search(head, lsp_id); if (!lsp) { flog_err(EC_LIB_DEVELOPMENT, "lsp_regenerate_pseudo: no l%d LSP %s found!", level, rawlspid_print(lsp_id)); return ISIS_ERROR; } rem_lifetime = lsp_rem_lifetime(circuit->area, level); lsp->hdr.rem_lifetime = rem_lifetime; lsp_build_pseudo(lsp, circuit, level); lsp_inc_seqno(lsp, 0); lsp->last_generated = time(NULL); lsp_flood(lsp, NULL); refresh_time = lsp_refresh_time(lsp, rem_lifetime); if (level == IS_LEVEL_1) thread_add_timer( master, lsp_l1_refresh_pseudo, circuit, refresh_time, &circuit->u.bc.t_refresh_pseudo_lsp[level - 1]); else if (level == IS_LEVEL_2) thread_add_timer( master, lsp_l2_refresh_pseudo, circuit, refresh_time, &circuit->u.bc.t_refresh_pseudo_lsp[level - 1]); if (isis->debugs & DEBUG_UPDATE_PACKETS) { zlog_debug( "ISIS-Upd (%s): Refreshed L%d Pseudo LSP %s, len %" PRIu16 ", seq 0x%08" PRIx32 ", cksum 0x%04" PRIx16 ", lifetime %" PRIu16 "s, refresh %" PRIu16 "s", circuit->area->area_tag, level, rawlspid_print(lsp->hdr.lsp_id), lsp->hdr.pdu_len, lsp->hdr.seqno, lsp->hdr.checksum, lsp->hdr.rem_lifetime, refresh_time); } return ISIS_OK; } /* * Something has changed or periodic refresh -> regenerate pseudo LSP */ static int lsp_l1_refresh_pseudo(struct thread *thread) { struct isis_circuit *circuit; uint8_t id[ISIS_SYS_ID_LEN + 2]; circuit = THREAD_ARG(thread); circuit->u.bc.t_refresh_pseudo_lsp[0] = NULL; circuit->lsp_regenerate_pending[0] = 0; if ((circuit->u.bc.is_dr[0] == 0) || (circuit->is_type & IS_LEVEL_1) == 0) { memcpy(id, isis->sysid, ISIS_SYS_ID_LEN); LSP_PSEUDO_ID(id) = circuit->circuit_id; LSP_FRAGMENT(id) = 0; lsp_purge_pseudo(id, circuit, IS_LEVEL_1); return ISIS_ERROR; } return lsp_regenerate_pseudo(circuit, IS_LEVEL_1); } static int lsp_l2_refresh_pseudo(struct thread *thread) { struct isis_circuit *circuit; uint8_t id[ISIS_SYS_ID_LEN + 2]; circuit = THREAD_ARG(thread); circuit->u.bc.t_refresh_pseudo_lsp[1] = NULL; circuit->lsp_regenerate_pending[1] = 0; if ((circuit->u.bc.is_dr[1] == 0) || (circuit->is_type & IS_LEVEL_2) == 0) { memcpy(id, isis->sysid, ISIS_SYS_ID_LEN); LSP_PSEUDO_ID(id) = circuit->circuit_id; LSP_FRAGMENT(id) = 0; lsp_purge_pseudo(id, circuit, IS_LEVEL_2); return ISIS_ERROR; } return lsp_regenerate_pseudo(circuit, IS_LEVEL_2); } int lsp_regenerate_schedule_pseudo(struct isis_circuit *circuit, int level) { struct isis_lsp *lsp; uint8_t lsp_id[ISIS_SYS_ID_LEN + 2]; time_t now, diff; long timeout; int lvl; struct isis_area *area = circuit->area; if (circuit->circ_type != CIRCUIT_T_BROADCAST || circuit->state != C_STATE_UP) return ISIS_OK; sched_debug( "ISIS (%s): Scheduling regeneration of %s pseudo LSP for interface %s", area->area_tag, circuit_t2string(level), circuit->interface->name); memcpy(lsp_id, isis->sysid, ISIS_SYS_ID_LEN); LSP_PSEUDO_ID(lsp_id) = circuit->circuit_id; LSP_FRAGMENT(lsp_id) = 0; now = time(NULL); for (lvl = IS_LEVEL_1; lvl <= IS_LEVEL_2; lvl++) { sched_debug( "ISIS (%s): Checking whether L%d pseudo LSP needs to be scheduled", area->area_tag, lvl); if (!((level & lvl) && (circuit->is_type & lvl))) { sched_debug("ISIS (%s): Level is not active on circuit", area->area_tag); continue; } if (circuit->u.bc.is_dr[lvl - 1] == 0) { sched_debug( "ISIS (%s): This IS is not DR, nothing to do.", area->area_tag); continue; } if (circuit->lsp_regenerate_pending[lvl - 1]) { struct timeval remain = thread_timer_remain( circuit->u.bc.t_refresh_pseudo_lsp[lvl - 1]); sched_debug( "ISIS (%s): Regenerate is already pending, nothing todo." " (Due in %lld.%03lld seconds)", area->area_tag, (long long)remain.tv_sec, (long long)remain.tv_usec / 1000); continue; } lsp = lsp_search(&circuit->area->lspdb[lvl - 1], lsp_id); if (!lsp) { sched_debug( "ISIS (%s): Pseudonode LSP does not exist yet, nothing to regenerate.", area->area_tag); continue; } /* * Throttle avoidance */ sched_debug( "ISIS (%s): Will schedule PSN regen timer. Last run was: %lld, Now is: %lld", area->area_tag, (long long)lsp->last_generated, (long long)now); THREAD_TIMER_OFF(circuit->u.bc.t_refresh_pseudo_lsp[lvl - 1]); diff = now - lsp->last_generated; if (diff < circuit->area->lsp_gen_interval[lvl - 1]) { timeout = 1000 * (circuit->area->lsp_gen_interval[lvl - 1] - diff); sched_debug( "ISIS (%s): Sechduling in %ld ms to match configured lsp_gen_interval", area->area_tag, timeout); } else { timeout = 100; sched_debug( "ISIS (%s): Last generation was more than lsp_gen_interval ago." " Scheduling for execution in %ld ms.", area->area_tag, timeout); } circuit->lsp_regenerate_pending[lvl - 1] = 1; if (lvl == IS_LEVEL_1) { thread_add_timer_msec( master, lsp_l1_refresh_pseudo, circuit, timeout, &circuit->u.bc.t_refresh_pseudo_lsp[lvl - 1]); } else if (lvl == IS_LEVEL_2) { thread_add_timer_msec( master, lsp_l2_refresh_pseudo, circuit, timeout, &circuit->u.bc.t_refresh_pseudo_lsp[lvl - 1]); } } return ISIS_OK; } /* * Walk through LSPs for an area * - set remaining lifetime */ int lsp_tick(struct thread *thread) { struct isis_area *area; struct isis_lsp *lsp; int level; uint16_t rem_lifetime; bool fabricd_sync_incomplete = false; area = THREAD_ARG(thread); assert(area); area->t_tick = NULL; thread_add_timer(master, lsp_tick, area, 1, &area->t_tick); struct isis_circuit *fabricd_init_c = fabricd_initial_sync_circuit(area); /* * Remove LSPs which have aged out */ for (level = 0; level < ISIS_LEVELS; level++) { struct isis_lsp *next = lspdb_first(&area->lspdb[level]); frr_each_from (lspdb, &area->lspdb[level], lsp, next) { /* * The lsp rem_lifetime is kept at 0 for MaxAge * or * ZeroAgeLifetime depending on explicit purge * or * natural age out. So schedule spf only once * when * the first time rem_lifetime becomes 0. */ rem_lifetime = lsp->hdr.rem_lifetime; lsp_set_time(lsp); /* * Schedule may run spf which should be done * only after * the lsp rem_lifetime becomes 0 for the first * time. * ISO 10589 - 7.3.16.4 first paragraph. */ if (rem_lifetime == 1 && lsp->hdr.seqno != 0) { /* 7.3.16.4 a) set SRM flags on all */ /* 7.3.16.4 b) retain only the header */ if (lsp->area->purge_originator) lsp_purge(lsp, lsp->level, NULL); else lsp_flood(lsp, NULL); /* 7.3.16.4 c) record the time to purge * FIXME */ isis_spf_schedule(lsp->area, lsp->level); } if (lsp->age_out == 0) { zlog_debug( "ISIS-Upd (%s): L%u LSP %s seq " "0x%08" PRIx32 " aged out", area->area_tag, lsp->level, rawlspid_print(lsp->hdr.lsp_id), lsp->hdr.seqno); /* if we're aging out fragment 0, lsp_destroy() * below will delete all other fragments too, * so we need to skip over those */ if (!LSP_FRAGMENT(lsp->hdr.lsp_id)) while (next && !memcmp(next->hdr.lsp_id, lsp->hdr.lsp_id, ISIS_SYS_ID_LEN + 1)) next = lspdb_next( &area->lspdb[level], next); lspdb_del(&area->lspdb[level], lsp); lsp_destroy(lsp); lsp = NULL; } if (fabricd_init_c && lsp) { fabricd_sync_incomplete |= ISIS_CHECK_FLAG(lsp->SSNflags, fabricd_init_c); } } } if (fabricd_init_c && !fabricd_sync_incomplete && !isis_tx_queue_len(fabricd_init_c->tx_queue)) { fabricd_initial_sync_finish(area); } return ISIS_OK; } void lsp_purge_pseudo(uint8_t *id, struct isis_circuit *circuit, int level) { struct isis_lsp *lsp; lsp = lsp_search(&circuit->area->lspdb[level - 1], id); if (!lsp) return; lsp_purge(lsp, level, NULL); } /* * Purge own LSP that is received and we don't have. * -> Do as in 7.3.16.4 */ void lsp_purge_non_exist(int level, struct isis_lsp_hdr *hdr, struct isis_area *area) { struct isis_lsp *lsp; /* * We need to create the LSP to be purged */ lsp = XCALLOC(MTYPE_ISIS_LSP, sizeof(struct isis_lsp)); lsp->area = area; lsp->level = level; lsp_adjust_stream(lsp); lsp->age_out = ZERO_AGE_LIFETIME; lsp->area->lsp_purge_count[level - 1]++; memcpy(&lsp->hdr, hdr, sizeof(lsp->hdr)); lsp->hdr.rem_lifetime = 0; lsp_purge_add_poi(lsp, NULL); lsp_pack_pdu(lsp); lsp_insert(&area->lspdb[lsp->level - 1], lsp); lsp_flood(lsp, NULL); return; } void lsp_set_all_srmflags(struct isis_lsp *lsp, bool set) { struct listnode *node; struct isis_circuit *circuit; assert(lsp); if (!lsp->area) return; struct list *circuit_list = lsp->area->circuit_list; for (ALL_LIST_ELEMENTS_RO(circuit_list, node, circuit)) { if (set) { isis_tx_queue_add(circuit->tx_queue, lsp, TX_LSP_NORMAL); } else { isis_tx_queue_del(circuit->tx_queue, lsp); } } } void _lsp_flood(struct isis_lsp *lsp, struct isis_circuit *circuit, const char *func, const char *file, int line) { if (isis->debugs & DEBUG_FLOODING) { zlog_debug("Flooding LSP %s%s%s (From %s %s:%d)", rawlspid_print(lsp->hdr.lsp_id), circuit ? " except on " : "", circuit ? circuit->interface->name : "", func, file, line); } if (!fabricd) lsp_set_all_srmflags(lsp, true); else fabricd_lsp_flood(lsp, circuit); if (circuit) isis_tx_queue_del(circuit->tx_queue, lsp); } static int lsp_handle_adj_state_change(struct isis_adjacency *adj) { lsp_regenerate_schedule(adj->circuit->area, IS_LEVEL_1 | IS_LEVEL_2, 0); return 0; } void lsp_init(void) { hook_register(isis_adj_state_change_hook, lsp_handle_adj_state_change); } frr-7.2.1/isisd/isis_lsp.h0000644000000000000000000001232013610377563012323 00000000000000/* * IS-IS Rout(e)ing protocol - isis_lsp.h * LSP processing * * Copyright (C) 2001,2002 Sampo Saaristo * Tampere University of Technology * Institute of Communications Engineering * * This program is free software; you can redistribute it and/or modify it * under the terms of the GNU General Public Licenseas 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; see the file COPYING; if not, write to the Free Software * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA */ #ifndef _ZEBRA_ISIS_LSP_H #define _ZEBRA_ISIS_LSP_H #include "lib/typesafe.h" #include "isisd/isis_pdu.h" PREDECL_RBTREE_UNIQ(lspdb) /* Structure for isis_lsp, this structure will only support the fixed * System ID (Currently 6) (atleast for now). In order to support more * We will have to split the header into two parts, and for readability * sake it should better be avoided */ struct isis_lsp { struct lspdb_item dbe; struct isis_lsp_hdr hdr; struct stream *pdu; /* full pdu lsp */ union { struct list *frags; struct isis_lsp *zero_lsp; } lspu; uint32_t SSNflags[ISIS_MAX_CIRCUITS]; int level; /* L1 or L2? */ int scheduled; /* scheduled for sending */ time_t installed; time_t last_generated; int own_lsp; /* used for 60 second counting when rem_lifetime is zero */ int age_out; struct isis_area *area; struct isis_tlvs *tlvs; time_t flooding_time; struct list *flooding_neighbors[TX_LSP_CIRCUIT_SCOPED + 1]; char *flooding_interface; bool flooding_circuit_scoped; }; extern int lspdb_compare(const struct isis_lsp *a, const struct isis_lsp *b); DECLARE_RBTREE_UNIQ(lspdb, struct isis_lsp, dbe, lspdb_compare) void lsp_db_init(struct lspdb_head *head); void lsp_db_fini(struct lspdb_head *head); int lsp_tick(struct thread *thread); int lsp_generate(struct isis_area *area, int level); #define lsp_regenerate_schedule(area, level, all_pseudo) \ _lsp_regenerate_schedule((area), (level), (all_pseudo), true, \ __func__, __FILE__, __LINE__) int _lsp_regenerate_schedule(struct isis_area *area, int level, int all_pseudo, bool postpone, const char *func, const char *file, int line); int lsp_generate_pseudo(struct isis_circuit *circuit, int level); int lsp_regenerate_schedule_pseudo(struct isis_circuit *circuit, int level); struct isis_lsp *lsp_new(struct isis_area *area, uint8_t *lsp_id, uint16_t rem_lifetime, uint32_t seq_num, uint8_t lsp_bits, uint16_t checksum, struct isis_lsp *lsp0, int level); struct isis_lsp *lsp_new_from_recv(struct isis_lsp_hdr *hdr, struct isis_tlvs *tlvs, struct stream *stream, struct isis_lsp *lsp0, struct isis_area *area, int level); void lsp_insert(struct lspdb_head *head, struct isis_lsp *lsp); struct isis_lsp *lsp_search(struct lspdb_head *head, const uint8_t *id); void lsp_build_list(struct lspdb_head *head, const uint8_t *start_id, const uint8_t *stop_id, uint8_t num_lsps, struct list *list); void lsp_build_list_nonzero_ht(struct lspdb_head *head, const uint8_t *start_id, const uint8_t *stop_id, struct list *list); void lsp_search_and_destroy(struct lspdb_head *head, const uint8_t *id); void lsp_purge_pseudo(uint8_t *id, struct isis_circuit *circuit, int level); void lsp_purge_non_exist(int level, struct isis_lsp_hdr *hdr, struct isis_area *area); #define LSP_EQUAL 1 #define LSP_NEWER 2 #define LSP_OLDER 3 #define LSP_PSEUDO_ID(I) ((I)[ISIS_SYS_ID_LEN]) #define LSP_FRAGMENT(I) ((I)[ISIS_SYS_ID_LEN + 1]) #define OWNLSPID(I) \ memcpy((I), isis->sysid, ISIS_SYS_ID_LEN); \ (I)[ISIS_SYS_ID_LEN] = 0; \ (I)[ISIS_SYS_ID_LEN + 1] = 0 int lsp_id_cmp(uint8_t *id1, uint8_t *id2); int lsp_compare(char *areatag, struct isis_lsp *lsp, uint32_t seqno, uint16_t checksum, uint16_t rem_lifetime); void lsp_update(struct isis_lsp *lsp, struct isis_lsp_hdr *hdr, struct isis_tlvs *tlvs, struct stream *stream, struct isis_area *area, int level, bool confusion); void lsp_inc_seqno(struct isis_lsp *lsp, uint32_t seqno); void lspid_print(uint8_t *lsp_id, char *dest, char dynhost, char frag); void lsp_print(struct isis_lsp *lsp, struct vty *vty, char dynhost); void lsp_print_detail(struct isis_lsp *lsp, struct vty *vty, char dynhost); int lsp_print_all(struct vty *vty, struct lspdb_head *head, char detail, char dynhost); /* sets SRMflags for all active circuits of an lsp */ void lsp_set_all_srmflags(struct isis_lsp *lsp, bool set); #define lsp_flood(lsp, circuit) \ _lsp_flood((lsp), (circuit), __func__, __FILE__, __LINE__) void _lsp_flood(struct isis_lsp *lsp, struct isis_circuit *circuit, const char *func, const char *file, int line); void lsp_init(void); #endif /* ISIS_LSP */ frr-7.2.1/isisd/isis_main.c0000644000000000000000000001236613610377563012456 00000000000000/* * IS-IS Rout(e)ing protocol - isis_main.c * * Copyright (C) 2001,2002 Sampo Saaristo * Tampere University of Technology * Institute of Communications Engineering * * This program is free software; you can redistribute it and/or modify it * under the terms of the GNU General Public Licenseas 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; see the file COPYING; if not, write to the Free Software * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA */ #include #include "getopt.h" #include "thread.h" #include "log.h" #include #include "command.h" #include "vty.h" #include "memory.h" #include "memory_vty.h" #include "stream.h" #include "if.h" #include "privs.h" #include "sigevent.h" #include "filter.h" #include "plist.h" #include "zclient.h" #include "vrf.h" #include "qobj.h" #include "libfrr.h" #include "isisd/isis_constants.h" #include "isisd/isis_common.h" #include "isisd/isis_flags.h" #include "isisd/isis_circuit.h" #include "isisd/isisd.h" #include "isisd/isis_dynhn.h" #include "isisd/isis_spf.h" #include "isisd/isis_route.h" #include "isisd/isis_routemap.h" #include "isisd/isis_zebra.h" #include "isisd/isis_te.h" #include "isisd/isis_errors.h" #include "isisd/isis_bfd.h" #include "isisd/isis_lsp.h" #include "isisd/isis_mt.h" #include "isisd/fabricd.h" /* Default configuration file name */ #define ISISD_DEFAULT_CONFIG "isisd.conf" /* Default vty port */ #define ISISD_VTY_PORT 2608 #define FABRICD_VTY_PORT 2618 /* isisd privileges */ zebra_capabilities_t _caps_p[] = {ZCAP_NET_RAW, ZCAP_BIND}; struct zebra_privs_t isisd_privs = { #if defined(FRR_USER) .user = FRR_USER, #endif #if defined FRR_GROUP .group = FRR_GROUP, #endif #ifdef VTY_GROUP .vty_group = VTY_GROUP, #endif .caps_p = _caps_p, .cap_num_p = array_size(_caps_p), .cap_num_i = 0}; /* isisd options */ struct option longopts[] = {{0}}; /* Master of threads. */ struct thread_master *master; /* * Prototypes. */ void sighup(void); void sigint(void); void sigterm(void); void sigusr1(void); static __attribute__((__noreturn__)) void terminate(int i) { isis_zebra_stop(); exit(i); } /* * Signal handlers */ #ifdef FABRICD void sighup(void) { zlog_notice("SIGHUP/reload is not implemented for fabricd"); return; } #else static struct frr_daemon_info isisd_di; void sighup(void) { zlog_info("SIGHUP received"); /* Reload config file. */ vty_read_config(NULL, isisd_di.config_file, config_default); } #endif __attribute__((__noreturn__)) void sigint(void) { zlog_notice("Terminating on signal SIGINT"); terminate(0); } __attribute__((__noreturn__)) void sigterm(void) { zlog_notice("Terminating on signal SIGTERM"); terminate(0); } void sigusr1(void) { zlog_debug("SIGUSR1 received"); zlog_rotate(); } struct quagga_signal_t isisd_signals[] = { { .signal = SIGHUP, .handler = &sighup, }, { .signal = SIGUSR1, .handler = &sigusr1, }, { .signal = SIGINT, .handler = &sigint, }, { .signal = SIGTERM, .handler = &sigterm, }, }; static const struct frr_yang_module_info *isisd_yang_modules[] = { &frr_interface_info, #ifndef FABRICD &frr_isisd_info, #endif /* ifndef FABRICD */ }; #ifdef FABRICD FRR_DAEMON_INFO(fabricd, OPEN_FABRIC, .vty_port = FABRICD_VTY_PORT, .proghelp = "Implementation of the OpenFabric routing protocol.", #else FRR_DAEMON_INFO(isisd, ISIS, .vty_port = ISISD_VTY_PORT, .proghelp = "Implementation of the IS-IS routing protocol.", #endif .copyright = "Copyright (c) 2001-2002 Sampo Saaristo," " Ofer Wald and Hannes Gredler", .signals = isisd_signals, .n_signals = array_size(isisd_signals), .privs = &isisd_privs, .yang_modules = isisd_yang_modules, .n_yang_modules = array_size(isisd_yang_modules), ) /* * Main routine of isisd. Parse arguments and handle IS-IS state machine. */ int main(int argc, char **argv, char **envp) { int opt; #ifdef FABRICD frr_preinit(&fabricd_di, argc, argv); #else frr_preinit(&isisd_di, argc, argv); #endif frr_opt_add("", longopts, ""); /* Command line argument treatment. */ while (1) { opt = frr_getopt(argc, argv, NULL); if (opt == EOF) break; switch (opt) { case 0: break; default: frr_help_exit(1); break; } } /* thread master */ master = frr_init(); /* * initializations */ isis_error_init(); access_list_init(); vrf_init(NULL, NULL, NULL, NULL, NULL); prefix_list_init(); isis_init(); isis_circuit_init(); #ifdef FABRICD isis_vty_daemon_init(); #endif /* FABRICD */ #ifndef FABRICD isis_cli_init(); #endif /* ifdef FABRICD */ isis_spf_cmds_init(); isis_redist_init(); isis_route_map_init(); isis_mpls_te_init(); lsp_init(); mt_init(); /* create the global 'isis' instance */ isis_new(1); isis_zebra_init(master); isis_bfd_init(); fabricd_init(); frr_config_fork(); frr_run(master); /* Not reached. */ exit(0); } frr-7.2.1/isisd/isis_memory.c0000644000000000000000000000352513610377563013037 00000000000000/* isisd memory type definitions * * Copyright (C) 2015 David Lamparter * * This file is part of Quagga. * * Quagga is free software; you can redistribute it and/or modify it * under the terms of the GNU General Public License as published by the * Free Software Foundation; either version 2, or (at your option) any * later version. * * Quagga 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; see the file COPYING; if not, write to the Free Software * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA */ #ifdef HAVE_CONFIG_H #include "config.h" #endif #include "isis_memory.h" DEFINE_MGROUP(ISISD, "isisd") DEFINE_MTYPE(ISISD, ISIS, "ISIS") DEFINE_MTYPE(ISISD, ISIS_TMP, "ISIS TMP") DEFINE_MTYPE(ISISD, ISIS_CIRCUIT, "ISIS circuit") DEFINE_MTYPE(ISISD, ISIS_LSP, "ISIS LSP") DEFINE_MTYPE(ISISD, ISIS_ADJACENCY, "ISIS adjacency") DEFINE_MTYPE(ISISD, ISIS_ADJACENCY_INFO, "ISIS adjacency info") DEFINE_MTYPE(ISISD, ISIS_AREA, "ISIS area") DEFINE_MTYPE(ISISD, ISIS_AREA_ADDR, "ISIS area address") DEFINE_MTYPE(ISISD, ISIS_DYNHN, "ISIS dyn hostname") DEFINE_MTYPE(ISISD, ISIS_SPFTREE, "ISIS SPFtree") DEFINE_MTYPE(ISISD, ISIS_VERTEX, "ISIS vertex") DEFINE_MTYPE(ISISD, ISIS_ROUTE_INFO, "ISIS route info") DEFINE_MTYPE(ISISD, ISIS_NEXTHOP, "ISIS nexthop") DEFINE_MTYPE(ISISD, ISIS_DICT, "ISIS dictionary") DEFINE_MTYPE(ISISD, ISIS_DICT_NODE, "ISIS dictionary node") DEFINE_MTYPE(ISISD, ISIS_EXT_ROUTE, "ISIS redistributed route") DEFINE_MTYPE(ISISD, ISIS_EXT_INFO, "ISIS redistributed route info") DEFINE_MTYPE(ISISD, ISIS_MPLS_TE, "ISIS MPLS_TE parameters") frr-7.2.1/isisd/isis_memory.h0000644000000000000000000000267013610377563013044 00000000000000/* isisd memory type declarations * * Copyright (C) 2015 David Lamparter * * This file is part of Quagga. * * Quagga is free software; you can redistribute it and/or modify it * under the terms of the GNU General Public License as published by the * Free Software Foundation; either version 2, or (at your option) any * later version. * * Quagga 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; see the file COPYING; if not, write to the Free Software * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA */ #ifndef _QUAGGA_ISIS_MEMORY_H #define _QUAGGA_ISIS_MEMORY_H #include "memory.h" DECLARE_MGROUP(ISISD) DECLARE_MTYPE(ISIS) DECLARE_MTYPE(ISIS_TMP) DECLARE_MTYPE(ISIS_CIRCUIT) DECLARE_MTYPE(ISIS_LSP) DECLARE_MTYPE(ISIS_ADJACENCY) DECLARE_MTYPE(ISIS_ADJACENCY_INFO) DECLARE_MTYPE(ISIS_AREA) DECLARE_MTYPE(ISIS_AREA_ADDR) DECLARE_MTYPE(ISIS_DYNHN) DECLARE_MTYPE(ISIS_SPFTREE) DECLARE_MTYPE(ISIS_VERTEX) DECLARE_MTYPE(ISIS_ROUTE_INFO) DECLARE_MTYPE(ISIS_NEXTHOP) DECLARE_MTYPE(ISIS_DICT) DECLARE_MTYPE(ISIS_DICT_NODE) DECLARE_MTYPE(ISIS_EXT_ROUTE) DECLARE_MTYPE(ISIS_EXT_INFO) DECLARE_MTYPE(ISIS_MPLS_TE) #endif /* _QUAGGA_ISIS_MEMORY_H */ frr-7.2.1/isisd/isis_misc.c0000644000000000000000000003004613610377563012460 00000000000000/* * IS-IS Rout(e)ing protocol - isis_misc.h * Miscellanous routines * * Copyright (C) 2001,2002 Sampo Saaristo * Tampere University of Technology * Institute of Communications Engineering * * This program is free software; you can redistribute it and/or modify it * under the terms of the GNU General Public Licenseas 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; see the file COPYING; if not, write to the Free Software * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA */ #include #include "printfrr.h" #include "stream.h" #include "vty.h" #include "hash.h" #include "if.h" #include "command.h" #include "isisd/isis_constants.h" #include "isisd/isis_common.h" #include "isisd/isis_flags.h" #include "isisd/isis_circuit.h" #include "isisd/isis_csm.h" #include "isisd/isisd.h" #include "isisd/isis_misc.h" #include "isisd/isis_lsp.h" #include "isisd/isis_constants.h" #include "isisd/isis_adjacency.h" #include "isisd/isis_dynhn.h" /* staticly assigned vars for printing purposes */ struct in_addr new_prefix; /* len of xx.xxxx.xxxx.xxxx.xxxx.xxxx.xxxx.xxxx.xxxx.xxxx.xx */ /* + place for #0 termination */ char isonet[51]; /* len of xxYxxMxWxdxxhxxmxxs + place for #0 termination */ char datestring[20]; char nlpidstring[30]; /* * This converts the isonet to its printable format */ const char *isonet_print(const uint8_t *from, int len) { int i = 0; char *pos = isonet; if (!from) return "unknown"; while (i < len) { if (i & 1) { sprintf(pos, "%02x", *(from + i)); pos += 2; } else { if (i == (len - 1)) { /* No dot at the end of address */ sprintf(pos, "%02x", *(from + i)); pos += 2; } else { sprintf(pos, "%02x.", *(from + i)); pos += 3; } } i++; } *(pos) = '\0'; return isonet; } /* * Returns 0 on error, length of buff on ok * extract dot from the dotted str, and insert all the number in a buff */ int dotformat2buff(uint8_t *buff, const char *dotted) { int dotlen, len = 0; const char *pos = dotted; uint8_t number[3]; int nextdotpos = 2; number[2] = '\0'; dotlen = strlen(dotted); if (dotlen > 50) { /* this can't be an iso net, its too long */ return 0; } while ((pos - dotted) < dotlen && len < 20) { if (*pos == '.') { /* we expect the . at 2, and than every 5 */ if ((pos - dotted) != nextdotpos) { len = 0; break; } nextdotpos += 5; pos++; continue; } /* we must have at least two chars left here */ if (dotlen - (pos - dotted) < 2) { len = 0; break; } if ((isxdigit((unsigned char)*pos)) && (isxdigit((unsigned char)*(pos + 1)))) { memcpy(number, pos, 2); pos += 2; } else { len = 0; break; } *(buff + len) = (char)strtol((char *)number, NULL, 16); len++; } return len; } /* * conversion of XXXX.XXXX.XXXX to memory */ int sysid2buff(uint8_t *buff, const char *dotted) { int len = 0; const char *pos = dotted; uint8_t number[3]; number[2] = '\0'; // surely not a sysid_string if not 14 length if (strlen(dotted) != 14) { return 0; } while (len < ISIS_SYS_ID_LEN) { if (*pos == '.') { /* the . is not positioned correctly */ if (((pos - dotted) != 4) && ((pos - dotted) != 9)) { len = 0; break; } pos++; continue; } if ((isxdigit((unsigned char)*pos)) && (isxdigit((unsigned char)*(pos + 1)))) { memcpy(number, pos, 2); pos += 2; } else { len = 0; break; } *(buff + len) = (char)strtol((char *)number, NULL, 16); len++; } return len; } const char *nlpid2str(uint8_t nlpid) { static char buf[4]; switch (nlpid) { case NLPID_IP: return "IPv4"; case NLPID_IPV6: return "IPv6"; case NLPID_SNAP: return "SNAP"; case NLPID_CLNP: return "CLNP"; case NLPID_ESIS: return "ES-IS"; default: snprintf(buf, sizeof(buf), "%" PRIu8, nlpid); return buf; } } /* * converts the nlpids struct (filled by TLV #129) * into a string */ char *nlpid2string(struct nlpids *nlpids) { char *pos = nlpidstring; int i; for (i = 0; i < nlpids->count; i++) { pos += sprintf(pos, "%s", nlpid2str(nlpids->nlpids[i])); if (nlpids->count - i > 1) pos += sprintf(pos, ", "); } *(pos) = '\0'; return nlpidstring; } /* * Returns 0 on error, IS-IS Circuit Type on ok */ int string2circuit_t(const char *str) { if (!str) return 0; if (!strcmp(str, "level-1")) return IS_LEVEL_1; if (!strcmp(str, "level-2-only") || !strcmp(str, "level-2")) return IS_LEVEL_2; if (!strcmp(str, "level-1-2")) return IS_LEVEL_1_AND_2; return 0; } const char *circuit_state2string(int state) { switch (state) { case C_STATE_INIT: return "Init"; case C_STATE_CONF: return "Config"; case C_STATE_UP: return "Up"; default: return "Unknown"; } return NULL; } const char *circuit_type2string(int type) { switch (type) { case CIRCUIT_T_P2P: return "p2p"; case CIRCUIT_T_BROADCAST: return "lan"; case CIRCUIT_T_LOOPBACK: return "loopback"; default: return "Unknown"; } return NULL; } const char *circuit_t2string(int circuit_t) { switch (circuit_t) { case IS_LEVEL_1: return "L1"; case IS_LEVEL_2: return "L2"; case IS_LEVEL_1_AND_2: return "L1L2"; default: return "??"; } return NULL; /* not reached */ } const char *syst2string(int type) { switch (type) { case ISIS_SYSTYPE_ES: return "ES"; case ISIS_SYSTYPE_IS: return "IS"; case ISIS_SYSTYPE_L1_IS: return "1"; case ISIS_SYSTYPE_L2_IS: return "2"; default: return "??"; } return NULL; /* not reached */ } /* * Print functions - we print to static vars */ const char *snpa_print(const uint8_t *from) { return isis_format_id(from, ISIS_SYS_ID_LEN); } const char *sysid_print(const uint8_t *from) { return isis_format_id(from, ISIS_SYS_ID_LEN); } const char *rawlspid_print(const uint8_t *from) { return isis_format_id(from, 8); } #define FORMAT_ID_SIZE sizeof("0000.0000.0000.00-00") const char *isis_format_id(const uint8_t *id, size_t len) { #define FORMAT_BUF_COUNT 4 static char buf_ring[FORMAT_BUF_COUNT][FORMAT_ID_SIZE]; static size_t cur_buf = 0; char *rv; cur_buf++; if (cur_buf >= FORMAT_BUF_COUNT) cur_buf = 0; rv = buf_ring[cur_buf]; if (!id) { snprintf(rv, FORMAT_ID_SIZE, "unknown"); return rv; } if (len < 6) { snprintf(rv, FORMAT_ID_SIZE, "Short ID"); return rv; } snprintf(rv, FORMAT_ID_SIZE, "%02x%02x.%02x%02x.%02x%02x", id[0], id[1], id[2], id[3], id[4], id[5]); if (len > 6) snprintf(rv + 14, FORMAT_ID_SIZE - 14, ".%02x", id[6]); if (len > 7) snprintf(rv + 17, FORMAT_ID_SIZE - 17, "-%02x", id[7]); return rv; } const char *time2string(uint32_t time) { char *pos = datestring; uint32_t rest; if (time == 0) return "-"; if (time / SECS_PER_YEAR) pos += sprintf(pos, "%uY", time / SECS_PER_YEAR); rest = time % SECS_PER_YEAR; if (rest / SECS_PER_MONTH) pos += sprintf(pos, "%uM", rest / SECS_PER_MONTH); rest = rest % SECS_PER_MONTH; if (rest / SECS_PER_WEEK) pos += sprintf(pos, "%uw", rest / SECS_PER_WEEK); rest = rest % SECS_PER_WEEK; if (rest / SECS_PER_DAY) pos += sprintf(pos, "%ud", rest / SECS_PER_DAY); rest = rest % SECS_PER_DAY; if (rest / SECS_PER_HOUR) pos += sprintf(pos, "%uh", rest / SECS_PER_HOUR); rest = rest % SECS_PER_HOUR; if (rest / SECS_PER_MINUTE) pos += sprintf(pos, "%um", rest / SECS_PER_MINUTE); rest = rest % SECS_PER_MINUTE; if (rest) pos += sprintf(pos, "%us", rest); *(pos) = 0; return datestring; } /* * routine to decrement a timer by a random * number * * first argument is the timer and the second is * the jitter */ unsigned long isis_jitter(unsigned long timer, unsigned long jitter) { int j, k; if (jitter >= 100) return timer; if (timer == 1) return timer; /* * randomizing just the percent value provides * no good random numbers - hence the spread * to RANDOM_SPREAD (100000), which is ok as * most IS-IS timers are no longer than 16 bit */ j = 1 + (int)((RANDOM_SPREAD * random()) / (RAND_MAX + 1.0)); k = timer - (timer * (100 - jitter)) / 100; timer = timer - (k * j / RANDOM_SPREAD); return timer; } struct in_addr newprefix2inaddr(uint8_t *prefix_start, uint8_t prefix_masklen) { memset(&new_prefix, 0, sizeof(new_prefix)); memcpy(&new_prefix, prefix_start, (prefix_masklen & 0x3F) ? ((((prefix_masklen & 0x3F) - 1) >> 3) + 1) : 0); return new_prefix; } /* * Returns the dynamic hostname associated with the passed system ID. * If no dynamic hostname found then returns formatted system ID. */ const char *print_sys_hostname(const uint8_t *sysid) { struct isis_dynhn *dyn; if (!sysid) return "nullsysid"; /* For our system ID return our host name */ if (memcmp(sysid, isis->sysid, ISIS_SYS_ID_LEN) == 0) return cmd_hostname_get(); dyn = dynhn_find_by_id(sysid); if (dyn) return dyn->hostname; return sysid_print(sysid); } /* * This function is a generic utility that logs data of given length. * Move this to a shared lib so that any protocol can use it. */ void zlog_dump_data(void *data, int len) { int i; unsigned char *p; unsigned char c; char bytestr[4]; char addrstr[10]; char hexstr[16 * 3 + 5]; char charstr[16 * 1 + 5]; p = data; memset(bytestr, 0, sizeof(bytestr)); memset(addrstr, 0, sizeof(addrstr)); memset(hexstr, 0, sizeof(hexstr)); memset(charstr, 0, sizeof(charstr)); for (i = 1; i <= len; i++) { c = *p; if (isalnum(c) == 0) c = '.'; /* store address for this line */ if ((i % 16) == 1) snprintf(addrstr, sizeof(addrstr), "%p", p); /* store hex str (for left side) */ snprintf(bytestr, sizeof(bytestr), "%02X ", *p); strncat(hexstr, bytestr, sizeof(hexstr) - strlen(hexstr) - 1); /* store char str (for right side) */ snprintf(bytestr, sizeof(bytestr), "%c", c); strncat(charstr, bytestr, sizeof(charstr) - strlen(charstr) - 1); if ((i % 16) == 0) { /* line completed */ zlog_debug("[%8.8s] %-50.50s %s", addrstr, hexstr, charstr); hexstr[0] = 0; charstr[0] = 0; } else if ((i % 8) == 0) { /* half line: add whitespaces */ strncat(hexstr, " ", sizeof(hexstr) - strlen(hexstr) - 1); strncat(charstr, " ", sizeof(charstr) - strlen(charstr) - 1); } p++; /* next byte */ } /* print rest of buffer if not empty */ if (strlen(hexstr) > 0) zlog_debug("[%8.8s] %-50.50s %s", addrstr, hexstr, charstr); return; } void log_multiline(int priority, const char *prefix, const char *format, ...) { char shortbuf[256]; va_list ap; char *p; va_start(ap, format); p = vasnprintfrr(MTYPE_TMP, shortbuf, sizeof(shortbuf), format, ap); va_end(ap); if (!p) return; char *saveptr = NULL; for (char *line = strtok_r(p, "\n", &saveptr); line; line = strtok_r(NULL, "\n", &saveptr)) { zlog(priority, "%s%s", prefix, line); } if (p != shortbuf) XFREE(MTYPE_TMP, p); } void vty_multiline(struct vty *vty, const char *prefix, const char *format, ...) { char shortbuf[256]; va_list ap; char *p; va_start(ap, format); p = vasnprintfrr(MTYPE_TMP, shortbuf, sizeof(shortbuf), format, ap); va_end(ap); if (!p) return; char *saveptr = NULL; for (char *line = strtok_r(p, "\n", &saveptr); line; line = strtok_r(NULL, "\n", &saveptr)) { vty_out(vty, "%s%s\n", prefix, line); } if (p != shortbuf) XFREE(MTYPE_TMP, p); } void vty_out_timestr(struct vty *vty, time_t uptime) { struct tm *tm; time_t difftime = time(NULL); difftime -= uptime; tm = gmtime(&difftime); if (difftime < ONE_DAY_SECOND) vty_out(vty, "%02d:%02d:%02d", tm->tm_hour, tm->tm_min, tm->tm_sec); else if (difftime < ONE_WEEK_SECOND) vty_out(vty, "%dd%02dh%02dm", tm->tm_yday, tm->tm_hour, tm->tm_min); else vty_out(vty, "%02dw%dd%02dh", tm->tm_yday / 7, tm->tm_yday - ((tm->tm_yday / 7) * 7), tm->tm_hour); vty_out(vty, " ago"); } frr-7.2.1/isisd/isis_misc.h0000644000000000000000000000557313610377563012474 00000000000000/* * IS-IS Rout(e)ing protocol - isis_misc.h * Miscellanous routines * * Copyright (C) 2001,2002 Sampo Saaristo * Tampere University of Technology * Institute of Communications Engineering * * This program is free software; you can redistribute it and/or modify it * under the terms of the GNU General Public Licenseas 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; see the file COPYING; if not, write to the Free Software * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA */ #ifndef _ZEBRA_ISIS_MISC_H #define _ZEBRA_ISIS_MISC_H int string2circuit_t(const char *); const char *circuit_t2string(int); const char *circuit_state2string(int state); const char *circuit_type2string(int type); const char *syst2string(int); struct in_addr newprefix2inaddr(uint8_t *prefix_start, uint8_t prefix_masklen); /* * Converting input to memory stored format * return value of 0 indicates wrong input */ int dotformat2buff(uint8_t *, const char *); int sysid2buff(uint8_t *, const char *); /* * Printing functions */ const char *isonet_print(const uint8_t *, int len); const char *sysid_print(const uint8_t *); const char *snpa_print(const uint8_t *); const char *rawlspid_print(const uint8_t *); const char *isis_format_id(const uint8_t *id, size_t len); const char *time2string(uint32_t); const char *nlpid2str(uint8_t nlpid); /* typedef struct nlpids nlpids; */ char *nlpid2string(struct nlpids *); const char *print_sys_hostname(const uint8_t *sysid); void zlog_dump_data(void *data, int len); /* * misc functions */ unsigned long isis_jitter(unsigned long timer, unsigned long jitter); /* * macros */ #define GETSYSID(A) \ (A->area_addr + (A->addr_len - (ISIS_SYS_ID_LEN + ISIS_NSEL_LEN))) /* used for calculating nice string representation instead of plain seconds */ #define SECS_PER_MINUTE 60 #define SECS_PER_HOUR 3600 #define SECS_PER_DAY 86400 #define SECS_PER_WEEK 604800 #define SECS_PER_MONTH 2628000 #define SECS_PER_YEAR 31536000 enum { ISIS_UI_LEVEL_BRIEF, ISIS_UI_LEVEL_DETAIL, ISIS_UI_LEVEL_EXTENSIVE, }; #include "lib/log.h" void log_multiline(int priority, const char *prefix, const char *format, ...) PRINTFRR(3, 4); struct vty; void vty_multiline(struct vty *vty, const char *prefix, const char *format, ...) PRINTFRR(3, 4); void vty_out_timestr(struct vty *vty, time_t uptime); #endif frr-7.2.1/isisd/isis_mt.c0000644000000000000000000003371613610377563012154 00000000000000/* * IS-IS Rout(e)ing protocol - Multi Topology Support * * Copyright (C) 2017 Christian Franke * * This file is part of FreeRangeRouting (FRR) * * FRR is free software; you can redistribute it and/or modify it * under the terms of the GNU General Public License as published by the * Free Software Foundation; either version 2, or (at your option) any * later version. * * FRR 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; see the file COPYING; if not, write to the Free Software * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA */ #include #include "isisd/isisd.h" #include "isisd/isis_memory.h" #include "isisd/isis_circuit.h" #include "isisd/isis_adjacency.h" #include "isisd/isis_misc.h" #include "isisd/isis_lsp.h" #include "isisd/isis_mt.h" #include "isisd/isis_tlvs.h" DEFINE_MTYPE_STATIC(ISISD, MT_AREA_SETTING, "ISIS MT Area Setting") DEFINE_MTYPE_STATIC(ISISD, MT_CIRCUIT_SETTING, "ISIS MT Circuit Setting") DEFINE_MTYPE_STATIC(ISISD, MT_ADJ_INFO, "ISIS MT Adjacency Info") bool isis_area_ipv6_dstsrc_enabled(struct isis_area *area) { struct isis_area_mt_setting *area_mt_setting; area_mt_setting = area_lookup_mt_setting(area, ISIS_MT_IPV6_DSTSRC); return (area_mt_setting && area_mt_setting->enabled); } uint16_t isis_area_ipv6_topology(struct isis_area *area) { struct isis_area_mt_setting *area_mt_setting; area_mt_setting = area_lookup_mt_setting(area, ISIS_MT_IPV6_UNICAST); if (area_mt_setting && area_mt_setting->enabled) return ISIS_MT_IPV6_UNICAST; return ISIS_MT_IPV4_UNICAST; } /* MT naming api */ const char *isis_mtid2str(uint16_t mtid) { static char buf[sizeof("65535")]; switch (mtid) { case ISIS_MT_IPV4_UNICAST: return "ipv4-unicast"; case ISIS_MT_IPV4_MGMT: return "ipv4-mgmt"; case ISIS_MT_IPV6_UNICAST: return "ipv6-unicast"; case ISIS_MT_IPV4_MULTICAST: return "ipv4-multicast"; case ISIS_MT_IPV6_MULTICAST: return "ipv6-multicast"; case ISIS_MT_IPV6_MGMT: return "ipv6-mgmt"; case ISIS_MT_IPV6_DSTSRC: return "ipv6-dstsrc"; default: snprintf(buf, sizeof(buf), "%" PRIu16, mtid); return buf; } } uint16_t isis_str2mtid(const char *name) { if (!strcmp(name, "ipv4-unicast")) return ISIS_MT_IPV4_UNICAST; if (!strcmp(name, "ipv4-mgmt")) return ISIS_MT_IPV4_MGMT; if (!strcmp(name, "ipv6-unicast")) return ISIS_MT_IPV6_UNICAST; if (!strcmp(name, "ipv4-multicast")) return ISIS_MT_IPV4_MULTICAST; if (!strcmp(name, "ipv6-multicast")) return ISIS_MT_IPV6_MULTICAST; if (!strcmp(name, "ipv6-mgmt")) return ISIS_MT_IPV6_MGMT; if (!strcmp(name, "ipv6-dstsrc")) return ISIS_MT_IPV6_DSTSRC; return -1; } /* General MT settings api */ struct mt_setting { ISIS_MT_INFO_FIELDS; }; static void *lookup_mt_setting(struct list *mt_list, uint16_t mtid) { struct listnode *node; struct mt_setting *setting; for (ALL_LIST_ELEMENTS_RO(mt_list, node, setting)) { if (setting->mtid == mtid) return setting; } return NULL; } static void add_mt_setting(struct list **mt_list, void *setting) { if (!*mt_list) *mt_list = list_new(); listnode_add(*mt_list, setting); } /* Area specific MT settings api */ struct isis_area_mt_setting *area_lookup_mt_setting(struct isis_area *area, uint16_t mtid) { return lookup_mt_setting(area->mt_settings, mtid); } struct isis_area_mt_setting *area_new_mt_setting(struct isis_area *area, uint16_t mtid) { struct isis_area_mt_setting *setting; setting = XCALLOC(MTYPE_MT_AREA_SETTING, sizeof(*setting)); setting->mtid = mtid; return setting; } static void area_free_mt_setting(void *setting) { XFREE(MTYPE_MT_AREA_SETTING, setting); } void area_add_mt_setting(struct isis_area *area, struct isis_area_mt_setting *setting) { add_mt_setting(&area->mt_settings, setting); } void area_mt_init(struct isis_area *area) { struct isis_area_mt_setting *v4_unicast_setting; /* MTID 0 is always enabled */ v4_unicast_setting = area_new_mt_setting(area, ISIS_MT_IPV4_UNICAST); v4_unicast_setting->enabled = true; add_mt_setting(&area->mt_settings, v4_unicast_setting); area->mt_settings->del = area_free_mt_setting; } void area_mt_finish(struct isis_area *area) { list_delete(&area->mt_settings); } struct isis_area_mt_setting *area_get_mt_setting(struct isis_area *area, uint16_t mtid) { struct isis_area_mt_setting *setting; setting = area_lookup_mt_setting(area, mtid); if (!setting) { setting = area_new_mt_setting(area, mtid); area_add_mt_setting(area, setting); } return setting; } int area_write_mt_settings(struct isis_area *area, struct vty *vty) { int written = 0; struct listnode *node; struct isis_area_mt_setting *setting; for (ALL_LIST_ELEMENTS_RO(area->mt_settings, node, setting)) { const char *name = isis_mtid2str(setting->mtid); if (name && setting->enabled) { if (setting->mtid == ISIS_MT_IPV4_UNICAST) continue; /* always enabled, no need to write out config */ vty_out(vty, " topology %s%s\n", name, setting->overload ? " overload" : ""); written++; } } return written; } bool area_is_mt(struct isis_area *area) { struct listnode *node, *node2; struct isis_area_mt_setting *setting; struct isis_circuit *circuit; struct isis_circuit_mt_setting *csetting; for (ALL_LIST_ELEMENTS_RO(area->mt_settings, node, setting)) { if (setting->enabled && setting->mtid != ISIS_MT_IPV4_UNICAST) return true; } for (ALL_LIST_ELEMENTS_RO(area->circuit_list, node, circuit)) { for (ALL_LIST_ELEMENTS_RO(circuit->mt_settings, node2, csetting)) { if (!csetting->enabled && csetting->mtid == ISIS_MT_IPV4_UNICAST) return true; } } return false; } struct isis_area_mt_setting **area_mt_settings(struct isis_area *area, unsigned int *mt_count) { static unsigned int size = 0; static struct isis_area_mt_setting **rv = NULL; unsigned int count = 0; struct listnode *node; struct isis_area_mt_setting *setting; for (ALL_LIST_ELEMENTS_RO(area->mt_settings, node, setting)) { if (!setting->enabled) continue; count++; if (count > size) { rv = XREALLOC(MTYPE_TMP, rv, count * sizeof(*rv)); size = count; } rv[count - 1] = setting; } *mt_count = count; return rv; } /* Circuit specific MT settings api */ struct isis_circuit_mt_setting * circuit_lookup_mt_setting(struct isis_circuit *circuit, uint16_t mtid) { return lookup_mt_setting(circuit->mt_settings, mtid); } struct isis_circuit_mt_setting * circuit_new_mt_setting(struct isis_circuit *circuit, uint16_t mtid) { struct isis_circuit_mt_setting *setting; setting = XCALLOC(MTYPE_MT_CIRCUIT_SETTING, sizeof(*setting)); setting->mtid = mtid; setting->enabled = true; /* Enabled is default for circuit */ return setting; } static void circuit_free_mt_setting(void *setting) { XFREE(MTYPE_MT_CIRCUIT_SETTING, setting); } void circuit_add_mt_setting(struct isis_circuit *circuit, struct isis_circuit_mt_setting *setting) { add_mt_setting(&circuit->mt_settings, setting); } void circuit_mt_init(struct isis_circuit *circuit) { circuit->mt_settings = list_new(); circuit->mt_settings->del = circuit_free_mt_setting; } void circuit_mt_finish(struct isis_circuit *circuit) { list_delete(&circuit->mt_settings); } struct isis_circuit_mt_setting * circuit_get_mt_setting(struct isis_circuit *circuit, uint16_t mtid) { struct isis_circuit_mt_setting *setting; setting = circuit_lookup_mt_setting(circuit, mtid); if (!setting) { setting = circuit_new_mt_setting(circuit, mtid); circuit_add_mt_setting(circuit, setting); } return setting; } static int circuit_write_mt_settings(struct isis_circuit *circuit, struct vty *vty) { int written = 0; struct listnode *node; struct isis_circuit_mt_setting *setting; for (ALL_LIST_ELEMENTS_RO(circuit->mt_settings, node, setting)) { const char *name = isis_mtid2str(setting->mtid); if (name && !setting->enabled) { vty_out(vty, " no " PROTO_NAME " topology %s\n", name); written++; } } return written; } struct isis_circuit_mt_setting ** circuit_mt_settings(struct isis_circuit *circuit, unsigned int *mt_count) { static unsigned int size = 0; static struct isis_circuit_mt_setting **rv = NULL; struct isis_area_mt_setting **area_settings; unsigned int area_count; unsigned int count = 0; struct listnode *node; struct isis_circuit_mt_setting *setting; area_settings = area_mt_settings(circuit->area, &area_count); for (unsigned int i = 0; i < area_count; i++) { for (ALL_LIST_ELEMENTS_RO(circuit->mt_settings, node, setting)) { if (setting->mtid != area_settings[i]->mtid) continue; break; } if (!setting) setting = circuit_get_mt_setting( circuit, area_settings[i]->mtid); if (!setting->enabled) continue; count++; if (count > size) { rv = XREALLOC(MTYPE_TMP, rv, count * sizeof(*rv)); size = count; } rv[count - 1] = setting; } *mt_count = count; return rv; } /* ADJ specific MT API */ static void adj_mt_set(struct isis_adjacency *adj, unsigned int index, uint16_t mtid) { if (adj->mt_count < index + 1) { adj->mt_set = XREALLOC(MTYPE_MT_ADJ_INFO, adj->mt_set, (index + 1) * sizeof(*adj->mt_set)); adj->mt_count = index + 1; } adj->mt_set[index] = mtid; } bool tlvs_to_adj_mt_set(struct isis_tlvs *tlvs, bool v4_usable, bool v6_usable, struct isis_adjacency *adj) { struct isis_circuit_mt_setting **mt_settings; unsigned int circuit_mt_count; unsigned int intersect_count = 0; uint16_t *old_mt_set = NULL; unsigned int old_mt_count; old_mt_count = adj->mt_count; if (old_mt_count) { old_mt_set = XCALLOC(MTYPE_TMP, old_mt_count * sizeof(*old_mt_set)); memcpy(old_mt_set, adj->mt_set, old_mt_count * sizeof(*old_mt_set)); } mt_settings = circuit_mt_settings(adj->circuit, &circuit_mt_count); for (unsigned int i = 0; i < circuit_mt_count; i++) { if (!tlvs->mt_router_info.count && !tlvs->mt_router_info_empty) { /* Other end does not have MT enabled */ if (mt_settings[i]->mtid == ISIS_MT_IPV4_UNICAST && (v4_usable || v6_usable)) adj_mt_set(adj, intersect_count++, ISIS_MT_IPV4_UNICAST); } else { struct isis_mt_router_info *info_head; info_head = (struct isis_mt_router_info *) tlvs->mt_router_info.head; for (struct isis_mt_router_info *info = info_head; info; info = info->next) { if (mt_settings[i]->mtid == info->mtid) { bool usable; switch (info->mtid) { case ISIS_MT_IPV4_UNICAST: case ISIS_MT_IPV4_MGMT: case ISIS_MT_IPV4_MULTICAST: usable = v4_usable; break; case ISIS_MT_IPV6_UNICAST: case ISIS_MT_IPV6_MGMT: case ISIS_MT_IPV6_MULTICAST: usable = v6_usable; break; default: usable = true; break; } if (usable) adj_mt_set(adj, intersect_count++, info->mtid); } } } } adj->mt_count = intersect_count; bool changed = false; if (adj->mt_count != old_mt_count) changed = true; if (!changed && old_mt_count && memcmp(adj->mt_set, old_mt_set, old_mt_count * sizeof(*old_mt_set))) changed = true; if (old_mt_count) XFREE(MTYPE_TMP, old_mt_set); return changed; } bool adj_has_mt(struct isis_adjacency *adj, uint16_t mtid) { for (unsigned int i = 0; i < adj->mt_count; i++) if (adj->mt_set[i] == mtid) return true; return false; } void adj_mt_finish(struct isis_adjacency *adj) { XFREE(MTYPE_MT_ADJ_INFO, adj->mt_set); adj->mt_count = 0; } static void mt_set_add(uint16_t **mt_set, unsigned int *size, unsigned int *index, uint16_t mtid) { for (unsigned int i = 0; i < *index; i++) { if ((*mt_set)[i] == mtid) return; } if (*index >= *size) { *mt_set = XREALLOC(MTYPE_TMP, *mt_set, sizeof(**mt_set) * ((*index) + 1)); *size = (*index) + 1; } (*mt_set)[*index] = mtid; *index = (*index) + 1; } static uint16_t *circuit_bcast_mt_set(struct isis_circuit *circuit, int level, unsigned int *mt_count) { static uint16_t *rv; static unsigned int size; struct listnode *node; struct isis_adjacency *adj; unsigned int count = 0; if (circuit->circ_type != CIRCUIT_T_BROADCAST) { *mt_count = 0; return NULL; } for (ALL_LIST_ELEMENTS_RO(circuit->u.bc.adjdb[level - 1], node, adj)) { if (adj->adj_state != ISIS_ADJ_UP) continue; for (unsigned int i = 0; i < adj->mt_count; i++) mt_set_add(&rv, &size, &count, adj->mt_set[i]); } *mt_count = count; return rv; } static void tlvs_add_mt_set(struct isis_area *area, struct isis_tlvs *tlvs, unsigned int mt_count, uint16_t *mt_set, uint8_t *id, uint32_t metric, uint8_t *subtlvs, uint8_t subtlv_len) { for (unsigned int i = 0; i < mt_count; i++) { uint16_t mtid = mt_set[i]; if (mt_set[i] == ISIS_MT_IPV4_UNICAST) { lsp_debug( "ISIS (%s): Adding %s.%02x as te-style neighbor", area->area_tag, sysid_print(id), LSP_PSEUDO_ID(id)); } else { lsp_debug( "ISIS (%s): Adding %s.%02x as mt-style neighbor for %s", area->area_tag, sysid_print(id), LSP_PSEUDO_ID(id), isis_mtid2str(mtid)); } isis_tlvs_add_extended_reach(tlvs, mtid, id, metric, subtlvs, subtlv_len); } } void tlvs_add_mt_bcast(struct isis_tlvs *tlvs, struct isis_circuit *circuit, int level, uint8_t *id, uint32_t metric, uint8_t *subtlvs, uint8_t subtlv_len) { unsigned int mt_count; uint16_t *mt_set = circuit_bcast_mt_set(circuit, level, &mt_count); tlvs_add_mt_set(circuit->area, tlvs, mt_count, mt_set, id, metric, subtlvs, subtlv_len); } void tlvs_add_mt_p2p(struct isis_tlvs *tlvs, struct isis_circuit *circuit, uint8_t *id, uint32_t metric, uint8_t *subtlvs, uint8_t subtlv_len) { struct isis_adjacency *adj = circuit->u.p2p.neighbor; tlvs_add_mt_set(circuit->area, tlvs, adj->mt_count, adj->mt_set, id, metric, subtlvs, subtlv_len); } void mt_init(void) { hook_register(isis_circuit_config_write, circuit_write_mt_settings); } frr-7.2.1/isisd/isis_mt.h0000644000000000000000000001146013610377563012151 00000000000000/* * IS-IS Rout(e)ing protocol - Multi Topology Support * * Copyright (C) 2017 Christian Franke * * This file is part of FreeRangeRouting (FRR) * * FRR is free software; you can redistribute it and/or modify it * under the terms of the GNU General Public License as published by the * Free Software Foundation; either version 2, or (at your option) any * later version. * * FRR 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; see the file COPYING; if not, write to the Free Software * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA */ #ifndef ISIS_MT_H #define ISIS_MT_H #define ISIS_MT_MASK 0x0fff #define ISIS_MT_OL_MASK 0x8000 #define ISIS_MT_AT_MASK 0x4000 #define ISIS_MT_IPV4_UNICAST 0 #define ISIS_MT_IPV4_MGMT 1 #define ISIS_MT_IPV6_UNICAST 2 #define ISIS_MT_IPV4_MULTICAST 3 #define ISIS_MT_IPV6_MULTICAST 4 #define ISIS_MT_IPV6_MGMT 5 #define ISIS_MT_IPV6_DSTSRC 3996 /* FIXME: IANA */ #define ISIS_MT_NAMES \ "" #define ISIS_MT_DESCRIPTIONS \ "IPv4 unicast topology\n" \ "IPv4 management topology\n" \ "IPv6 unicast topology\n" \ "IPv4 multicast topology\n" \ "IPv6 multicast topology\n" \ "IPv6 management topology\n" \ "IPv6 dst-src topology\n" \ "" #define ISIS_MT_INFO_FIELDS uint16_t mtid; struct list; struct isis_area_mt_setting { ISIS_MT_INFO_FIELDS bool enabled; bool overload; }; struct isis_circuit_mt_setting { ISIS_MT_INFO_FIELDS bool enabled; }; const char *isis_mtid2str(uint16_t mtid); uint16_t isis_str2mtid(const char *name); struct isis_adjacency; struct isis_area; struct isis_circuit; struct tlvs; struct te_is_neigh; struct isis_tlvs; bool isis_area_ipv6_dstsrc_enabled(struct isis_area *area); uint16_t isis_area_ipv6_topology(struct isis_area *area); struct isis_area_mt_setting *area_lookup_mt_setting(struct isis_area *area, uint16_t mtid); struct isis_area_mt_setting *area_new_mt_setting(struct isis_area *area, uint16_t mtid); void area_add_mt_setting(struct isis_area *area, struct isis_area_mt_setting *setting); void area_mt_init(struct isis_area *area); void area_mt_finish(struct isis_area *area); struct isis_area_mt_setting *area_get_mt_setting(struct isis_area *area, uint16_t mtid); int area_write_mt_settings(struct isis_area *area, struct vty *vty); bool area_is_mt(struct isis_area *area); struct isis_area_mt_setting **area_mt_settings(struct isis_area *area, unsigned int *mt_count); struct isis_circuit_mt_setting * circuit_lookup_mt_setting(struct isis_circuit *circuit, uint16_t mtid); struct isis_circuit_mt_setting * circuit_new_mt_setting(struct isis_circuit *circuit, uint16_t mtid); void circuit_add_mt_setting(struct isis_circuit *circuit, struct isis_circuit_mt_setting *setting); void circuit_mt_init(struct isis_circuit *circuit); void circuit_mt_finish(struct isis_circuit *circuit); struct isis_circuit_mt_setting * circuit_get_mt_setting(struct isis_circuit *circuit, uint16_t mtid); struct isis_circuit_mt_setting ** circuit_mt_settings(struct isis_circuit *circuit, unsigned int *mt_count); bool tlvs_to_adj_mt_set(struct isis_tlvs *tlvs, bool v4_usable, bool v6_usable, struct isis_adjacency *adj); bool adj_has_mt(struct isis_adjacency *adj, uint16_t mtid); void adj_mt_finish(struct isis_adjacency *adj); void tlvs_add_mt_bcast(struct isis_tlvs *tlvs, struct isis_circuit *circuit, int level, uint8_t *id, uint32_t metric, uint8_t *subtlvs, uint8_t subtlv_len); void tlvs_add_mt_p2p(struct isis_tlvs *tlvs, struct isis_circuit *circuit, uint8_t *id, uint32_t metric, uint8_t *subtlvs, uint8_t subtlv_len); void mt_init(void); #endif frr-7.2.1/isisd/isis_network.h0000644000000000000000000000265713610377563013232 00000000000000/* * IS-IS Rout(e)ing protocol - isis_network.h * * Copyright (C) 2001,2002 Sampo Saaristo * Tampere University of Technology * Institute of Communications Engineering * * This program is free software; you can redistribute it and/or modify it * under the terms of the GNU General Public Licenseas 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; see the file COPYING; if not, write to the Free Software * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA */ #ifndef _ZEBRA_ISIS_NETWORK_H #define _ZEBRA_ISIS_NETWORK_H extern uint8_t ALL_L1_ISYSTEMS[]; extern uint8_t ALL_L2_ISYSTEMS[]; int isis_sock_init(struct isis_circuit *circuit); int isis_recv_pdu_bcast(struct isis_circuit *circuit, uint8_t *ssnpa); int isis_recv_pdu_p2p(struct isis_circuit *circuit, uint8_t *ssnpa); int isis_send_pdu_bcast(struct isis_circuit *circuit, int level); int isis_send_pdu_p2p(struct isis_circuit *circuit, int level); #endif /* _ZEBRA_ISIS_NETWORK_H */ frr-7.2.1/isisd/isis_northbound.c0000644000000000000000000027251213610377563013715 00000000000000/* * Copyright (C) 2001,2002 Sampo Saaristo * Tampere University of Technology * Institute of Communications Engineering * Copyright (C) 2018 Volta Networks * Emanuele Di Pascale * * This program is free software; you can redistribute it and/or modify it * under the terms of the 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; see the file COPYING; if not, write to the Free Software * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA */ #include #include "northbound.h" #include "libfrr.h" #include "linklist.h" #include "log.h" #include "lib/bfd.h" #include "isisd/isis_bfd.h" #include "isisd/isis_constants.h" #include "isisd/isis_common.h" #include "isisd/isis_flags.h" #include "isisd/isis_circuit.h" #include "isisd/isisd.h" #include "isisd/isis_lsp.h" #include "isisd/isis_pdu.h" #include "isisd/isis_dynhn.h" #include "isisd/isis_misc.h" #include "isisd/isis_csm.h" #include "isisd/isis_adjacency.h" #include "isisd/isis_spf.h" #include "isisd/isis_te.h" #include "isisd/isis_memory.h" #include "isisd/isis_mt.h" #include "isisd/isis_cli.h" #include "isisd/isis_redist.h" #include "lib/spf_backoff.h" #include "lib/lib_errors.h" #include "lib/vrf.h" /* * XPath: /frr-isisd:isis/instance */ static int isis_instance_create(enum nb_event event, const struct lyd_node *dnode, union nb_resource *resource) { struct isis_area *area; const char *area_tag; if (event != NB_EV_APPLY) return NB_OK; area_tag = yang_dnode_get_string(dnode, "./area-tag"); area = isis_area_lookup(area_tag); if (area) return NB_ERR_INCONSISTENCY; area = isis_area_create(area_tag); /* save area in dnode to avoid looking it up all the time */ nb_running_set_entry(dnode, area); return NB_OK; } static int isis_instance_destroy(enum nb_event event, const struct lyd_node *dnode) { struct isis_area *area; if (event != NB_EV_APPLY) return NB_OK; area = nb_running_unset_entry(dnode); isis_area_destroy(area->area_tag); return NB_OK; } /* * XPath: /frr-isisd:isis/instance/is-type */ static int isis_instance_is_type_modify(enum nb_event event, const struct lyd_node *dnode, union nb_resource *resource) { struct isis_area *area; int type; if (event != NB_EV_APPLY) return NB_OK; area = nb_running_get_entry(dnode, NULL, true); type = yang_dnode_get_enum(dnode, NULL); isis_area_is_type_set(area, type); return NB_OK; } /* * XPath: /frr-isisd:isis/instance/area-address */ static int isis_instance_area_address_create(enum nb_event event, const struct lyd_node *dnode, union nb_resource *resource) { struct isis_area *area; struct area_addr addr, *addrr = NULL, *addrp = NULL; struct listnode *node; uint8_t buff[255]; const char *net_title = yang_dnode_get_string(dnode, NULL); switch (event) { case NB_EV_VALIDATE: addr.addr_len = dotformat2buff(buff, net_title); memcpy(addr.area_addr, buff, addr.addr_len); if (addr.area_addr[addr.addr_len - 1] != 0) { flog_warn( EC_LIB_NB_CB_CONFIG_VALIDATE, "nsel byte (last byte) in area address must be 0"); return NB_ERR_VALIDATION; } if (isis->sysid_set) { /* Check that the SystemID portions match */ if (memcmp(isis->sysid, GETSYSID((&addr)), ISIS_SYS_ID_LEN)) { flog_warn( EC_LIB_NB_CB_CONFIG_VALIDATE, "System ID must not change when defining additional area addresses"); return NB_ERR_VALIDATION; } } break; case NB_EV_PREPARE: addrr = XMALLOC(MTYPE_ISIS_AREA_ADDR, sizeof(struct area_addr)); addrr->addr_len = dotformat2buff(buff, net_title); memcpy(addrr->area_addr, buff, addrr->addr_len); resource->ptr = addrr; break; case NB_EV_ABORT: XFREE(MTYPE_ISIS_AREA_ADDR, resource->ptr); break; case NB_EV_APPLY: area = nb_running_get_entry(dnode, NULL, true); addrr = resource->ptr; if (isis->sysid_set == 0) { /* * First area address - get the SystemID for this router */ memcpy(isis->sysid, GETSYSID(addrr), ISIS_SYS_ID_LEN); isis->sysid_set = 1; } else { /* check that we don't already have this address */ for (ALL_LIST_ELEMENTS_RO(area->area_addrs, node, addrp)) { if ((addrp->addr_len + ISIS_SYS_ID_LEN + ISIS_NSEL_LEN) != (addrr->addr_len)) continue; if (!memcmp(addrp->area_addr, addrr->area_addr, addrr->addr_len)) { XFREE(MTYPE_ISIS_AREA_ADDR, addrr); return NB_OK; /* silent fail */ } } } /*Forget the systemID part of the address */ addrr->addr_len -= (ISIS_SYS_ID_LEN + ISIS_NSEL_LEN); assert(area->area_addrs); /* to silence scan-build sillyness */ listnode_add(area->area_addrs, addrr); /* only now we can safely generate our LSPs for this area */ if (listcount(area->area_addrs) > 0) { if (area->is_type & IS_LEVEL_1) lsp_generate(area, IS_LEVEL_1); if (area->is_type & IS_LEVEL_2) lsp_generate(area, IS_LEVEL_2); } break; } return NB_OK; } static int isis_instance_area_address_destroy(enum nb_event event, const struct lyd_node *dnode) { struct area_addr addr, *addrp = NULL; struct listnode *node; uint8_t buff[255]; struct isis_area *area; const char *net_title; if (event != NB_EV_APPLY) return NB_OK; net_title = yang_dnode_get_string(dnode, NULL); addr.addr_len = dotformat2buff(buff, net_title); memcpy(addr.area_addr, buff, (int)addr.addr_len); area = nb_running_get_entry(dnode, NULL, true); for (ALL_LIST_ELEMENTS_RO(area->area_addrs, node, addrp)) { if ((addrp->addr_len + ISIS_SYS_ID_LEN + 1) == addr.addr_len && !memcmp(addrp->area_addr, addr.area_addr, addr.addr_len)) break; } if (!addrp) return NB_ERR_INCONSISTENCY; listnode_delete(area->area_addrs, addrp); XFREE(MTYPE_ISIS_AREA_ADDR, addrp); /* * Last area address - reset the SystemID for this router */ if (listcount(area->area_addrs) == 0) { memset(isis->sysid, 0, ISIS_SYS_ID_LEN); isis->sysid_set = 0; if (isis->debugs & DEBUG_EVENTS) zlog_debug("Router has no SystemID"); } return NB_OK; } /* * XPath: /frr-isisd:isis/instance/dynamic-hostname */ static int isis_instance_dynamic_hostname_modify(enum nb_event event, const struct lyd_node *dnode, union nb_resource *resource) { struct isis_area *area; if (event != NB_EV_APPLY) return NB_OK; area = nb_running_get_entry(dnode, NULL, true); isis_area_dynhostname_set(area, yang_dnode_get_bool(dnode, NULL)); return NB_OK; } /* * XPath: /frr-isisd:isis/instance/attached */ static int isis_instance_attached_modify(enum nb_event event, const struct lyd_node *dnode, union nb_resource *resource) { struct isis_area *area; bool attached; if (event != NB_EV_APPLY) return NB_OK; area = nb_running_get_entry(dnode, NULL, true); attached = yang_dnode_get_bool(dnode, NULL); isis_area_attached_bit_set(area, attached); return NB_OK; } /* * XPath: /frr-isisd:isis/instance/overload */ static int isis_instance_overload_modify(enum nb_event event, const struct lyd_node *dnode, union nb_resource *resource) { struct isis_area *area; bool overload; if (event != NB_EV_APPLY) return NB_OK; area = nb_running_get_entry(dnode, NULL, true); overload = yang_dnode_get_bool(dnode, NULL); isis_area_overload_bit_set(area, overload); return NB_OK; } /* * XPath: /frr-isisd:isis/instance/metric-style */ static int isis_instance_metric_style_modify(enum nb_event event, const struct lyd_node *dnode, union nb_resource *resource) { struct isis_area *area; bool old_metric, new_metric; enum isis_metric_style metric_style = yang_dnode_get_enum(dnode, NULL); if (event != NB_EV_APPLY) return NB_OK; area = nb_running_get_entry(dnode, NULL, true); old_metric = (metric_style == ISIS_WIDE_METRIC) ? false : true; new_metric = (metric_style == ISIS_NARROW_METRIC) ? false : true; isis_area_metricstyle_set(area, old_metric, new_metric); return NB_OK; } /* * XPath: /frr-isisd:isis/instance/purge-originator */ static int isis_instance_purge_originator_modify(enum nb_event event, const struct lyd_node *dnode, union nb_resource *resource) { struct isis_area *area; if (event != NB_EV_APPLY) return NB_OK; area = nb_running_get_entry(dnode, NULL, true); area->purge_originator = yang_dnode_get_bool(dnode, NULL); return NB_OK; } /* * XPath: /frr-isisd:isis/instance/lsp/mtu */ static int isis_instance_lsp_mtu_modify(enum nb_event event, const struct lyd_node *dnode, union nb_resource *resource) { struct listnode *node; struct isis_circuit *circuit; uint16_t lsp_mtu = yang_dnode_get_uint16(dnode, NULL); struct isis_area *area; switch (event) { case NB_EV_VALIDATE: area = nb_running_get_entry(dnode, NULL, false); if (!area) break; for (ALL_LIST_ELEMENTS_RO(area->circuit_list, node, circuit)) { if (circuit->state != C_STATE_INIT && circuit->state != C_STATE_UP) continue; if (lsp_mtu > isis_circuit_pdu_size(circuit)) { flog_warn( EC_LIB_NB_CB_CONFIG_VALIDATE, "ISIS area contains circuit %s, which has a maximum PDU size of %zu", circuit->interface->name, isis_circuit_pdu_size(circuit)); return NB_ERR_VALIDATION; } } break; case NB_EV_PREPARE: case NB_EV_ABORT: break; case NB_EV_APPLY: area = nb_running_get_entry(dnode, NULL, true); isis_area_lsp_mtu_set(area, lsp_mtu); break; } return NB_OK; } /* * XPath: /frr-isisd:isis/instance/lsp/refresh-interval/level-1 */ static int isis_instance_lsp_refresh_interval_level_1_modify(enum nb_event event, const struct lyd_node *dnode, union nb_resource *resource) { struct isis_area *area; uint16_t refr_int; if (event != NB_EV_APPLY) return NB_OK; refr_int = yang_dnode_get_uint16(dnode, NULL); area = nb_running_get_entry(dnode, NULL, true); isis_area_lsp_refresh_set(area, IS_LEVEL_1, refr_int); return NB_OK; } /* * XPath: /frr-isisd:isis/instance/lsp/refresh-interval/level-2 */ static int isis_instance_lsp_refresh_interval_level_2_modify(enum nb_event event, const struct lyd_node *dnode, union nb_resource *resource) { struct isis_area *area; uint16_t refr_int; if (event != NB_EV_APPLY) return NB_OK; refr_int = yang_dnode_get_uint16(dnode, NULL); area = nb_running_get_entry(dnode, NULL, true); isis_area_lsp_refresh_set(area, IS_LEVEL_2, refr_int); return NB_OK; } /* * XPath: /frr-isisd:isis/instance/lsp/maximum-lifetime/level-1 */ static int isis_instance_lsp_maximum_lifetime_level_1_modify(enum nb_event event, const struct lyd_node *dnode, union nb_resource *resource) { struct isis_area *area; uint16_t max_lt; if (event != NB_EV_APPLY) return NB_OK; max_lt = yang_dnode_get_uint16(dnode, NULL); area = nb_running_get_entry(dnode, NULL, true); isis_area_max_lsp_lifetime_set(area, IS_LEVEL_1, max_lt); return NB_OK; } /* * XPath: /frr-isisd:isis/instance/lsp/maximum-lifetime/level-2 */ static int isis_instance_lsp_maximum_lifetime_level_2_modify(enum nb_event event, const struct lyd_node *dnode, union nb_resource *resource) { struct isis_area *area; uint16_t max_lt; if (event != NB_EV_APPLY) return NB_OK; max_lt = yang_dnode_get_uint16(dnode, NULL); area = nb_running_get_entry(dnode, NULL, true); isis_area_max_lsp_lifetime_set(area, IS_LEVEL_2, max_lt); return NB_OK; } /* * XPath: /frr-isisd:isis/instance/lsp/generation-interval/level-1 */ static int isis_instance_lsp_generation_interval_level_1_modify( enum nb_event event, const struct lyd_node *dnode, union nb_resource *resource) { struct isis_area *area; uint16_t gen_int; if (event != NB_EV_APPLY) return NB_OK; gen_int = yang_dnode_get_uint16(dnode, NULL); area = nb_running_get_entry(dnode, NULL, true); area->lsp_gen_interval[0] = gen_int; return NB_OK; } /* * XPath: /frr-isisd:isis/instance/lsp/generation-interval/level-2 */ static int isis_instance_lsp_generation_interval_level_2_modify( enum nb_event event, const struct lyd_node *dnode, union nb_resource *resource) { struct isis_area *area; uint16_t gen_int; if (event != NB_EV_APPLY) return NB_OK; gen_int = yang_dnode_get_uint16(dnode, NULL); area = nb_running_get_entry(dnode, NULL, true); area->lsp_gen_interval[1] = gen_int; return NB_OK; } /* * XPath: /frr-isisd:isis/instance/spf/ietf-backoff-delay */ static void ietf_backoff_delay_apply_finish(const struct lyd_node *dnode) { long init_delay = yang_dnode_get_uint16(dnode, "./init-delay"); long short_delay = yang_dnode_get_uint16(dnode, "./short-delay"); long long_delay = yang_dnode_get_uint16(dnode, "./long-delay"); long holddown = yang_dnode_get_uint16(dnode, "./hold-down"); long timetolearn = yang_dnode_get_uint16(dnode, "./time-to-learn"); struct isis_area *area = nb_running_get_entry(dnode, NULL, true); size_t bufsiz = strlen(area->area_tag) + sizeof("IS-IS Lx"); char *buf = XCALLOC(MTYPE_TMP, bufsiz); snprintf(buf, bufsiz, "IS-IS %s L1", area->area_tag); spf_backoff_free(area->spf_delay_ietf[0]); area->spf_delay_ietf[0] = spf_backoff_new(master, buf, init_delay, short_delay, long_delay, holddown, timetolearn); snprintf(buf, bufsiz, "IS-IS %s L2", area->area_tag); spf_backoff_free(area->spf_delay_ietf[1]); area->spf_delay_ietf[1] = spf_backoff_new(master, buf, init_delay, short_delay, long_delay, holddown, timetolearn); XFREE(MTYPE_TMP, buf); } static int isis_instance_spf_ietf_backoff_delay_create(enum nb_event event, const struct lyd_node *dnode, union nb_resource *resource) { /* All the work is done in the apply_finish */ return NB_OK; } static int isis_instance_spf_ietf_backoff_delay_destroy(enum nb_event event, const struct lyd_node *dnode) { struct isis_area *area; if (event != NB_EV_APPLY) return NB_OK; area = nb_running_get_entry(dnode, NULL, true); spf_backoff_free(area->spf_delay_ietf[0]); spf_backoff_free(area->spf_delay_ietf[1]); area->spf_delay_ietf[0] = NULL; area->spf_delay_ietf[1] = NULL; return NB_OK; } /* * XPath: /frr-isisd:isis/instance/spf/ietf-backoff-delay/init-delay */ static int isis_instance_spf_ietf_backoff_delay_init_delay_modify( enum nb_event event, const struct lyd_node *dnode, union nb_resource *resource) { /* All the work is done in the apply_finish */ return NB_OK; } /* * XPath: /frr-isisd:isis/instance/spf/ietf-backoff-delay/short-delay */ static int isis_instance_spf_ietf_backoff_delay_short_delay_modify( enum nb_event event, const struct lyd_node *dnode, union nb_resource *resource) { /* All the work is done in the apply_finish */ return NB_OK; } /* * XPath: /frr-isisd:isis/instance/spf/ietf-backoff-delay/long-delay */ static int isis_instance_spf_ietf_backoff_delay_long_delay_modify( enum nb_event event, const struct lyd_node *dnode, union nb_resource *resource) { /* All the work is done in the apply_finish */ return NB_OK; } /* * XPath: /frr-isisd:isis/instance/spf/ietf-backoff-delay/hold-down */ static int isis_instance_spf_ietf_backoff_delay_hold_down_modify( enum nb_event event, const struct lyd_node *dnode, union nb_resource *resource) { /* All the work is done in the apply_finish */ return NB_OK; } /* * XPath: /frr-isisd:isis/instance/spf/ietf-backoff-delay/time-to-learn */ static int isis_instance_spf_ietf_backoff_delay_time_to_learn_modify( enum nb_event event, const struct lyd_node *dnode, union nb_resource *resource) { /* All the work is done in the apply_finish */ return NB_OK; } /* * XPath: /frr-isisd:isis/instance/spf/minimum-interval/level-1 */ static int isis_instance_spf_minimum_interval_level_1_modify(enum nb_event event, const struct lyd_node *dnode, union nb_resource *resource) { struct isis_area *area; if (event != NB_EV_APPLY) return NB_OK; area = nb_running_get_entry(dnode, NULL, true); area->min_spf_interval[0] = yang_dnode_get_uint16(dnode, NULL); return NB_OK; } /* * XPath: /frr-isisd:isis/instance/spf/minimum-interval/level-2 */ static int isis_instance_spf_minimum_interval_level_2_modify(enum nb_event event, const struct lyd_node *dnode, union nb_resource *resource) { struct isis_area *area; if (event != NB_EV_APPLY) return NB_OK; area = nb_running_get_entry(dnode, NULL, true); area->min_spf_interval[1] = yang_dnode_get_uint16(dnode, NULL); return NB_OK; } /* * XPath: /frr-isisd:isis/instance/area-password */ static void area_password_apply_finish(const struct lyd_node *dnode) { const char *password = yang_dnode_get_string(dnode, "./password"); struct isis_area *area = nb_running_get_entry(dnode, NULL, true); int pass_type = yang_dnode_get_enum(dnode, "./password-type"); uint8_t snp_auth = yang_dnode_get_enum(dnode, "./authenticate-snp"); switch (pass_type) { case ISIS_PASSWD_TYPE_CLEARTXT: isis_area_passwd_cleartext_set(area, IS_LEVEL_1, password, snp_auth); break; case ISIS_PASSWD_TYPE_HMAC_MD5: isis_area_passwd_hmac_md5_set(area, IS_LEVEL_1, password, snp_auth); break; } } static int isis_instance_area_password_create(enum nb_event event, const struct lyd_node *dnode, union nb_resource *resource) { /* actual setting is done in apply_finish */ return NB_OK; } static int isis_instance_area_password_destroy(enum nb_event event, const struct lyd_node *dnode) { struct isis_area *area; if (event != NB_EV_APPLY) return NB_OK; area = nb_running_get_entry(dnode, NULL, true); isis_area_passwd_unset(area, IS_LEVEL_1); return NB_OK; } /* * XPath: /frr-isisd:isis/instance/area-password/password */ static int isis_instance_area_password_password_modify(enum nb_event event, const struct lyd_node *dnode, union nb_resource *resource) { /* actual setting is done in apply_finish */ return NB_OK; } /* * XPath: /frr-isisd:isis/instance/area-password/password-type */ static int isis_instance_area_password_password_type_modify(enum nb_event event, const struct lyd_node *dnode, union nb_resource *resource) { /* actual setting is done in apply_finish */ return NB_OK; } /* * XPath: /frr-isisd:isis/instance/area-password/authenticate-snp */ static int isis_instance_area_password_authenticate_snp_modify( enum nb_event event, const struct lyd_node *dnode, union nb_resource *resource) { /* actual setting is done in apply_finish */ return NB_OK; } /* * XPath: /frr-isisd:isis/instance/domain-password */ static void domain_password_apply_finish(const struct lyd_node *dnode) { const char *password = yang_dnode_get_string(dnode, "./password"); struct isis_area *area = nb_running_get_entry(dnode, NULL, true); int pass_type = yang_dnode_get_enum(dnode, "./password-type"); uint8_t snp_auth = yang_dnode_get_enum(dnode, "./authenticate-snp"); switch (pass_type) { case ISIS_PASSWD_TYPE_CLEARTXT: isis_area_passwd_cleartext_set(area, IS_LEVEL_2, password, snp_auth); break; case ISIS_PASSWD_TYPE_HMAC_MD5: isis_area_passwd_hmac_md5_set(area, IS_LEVEL_2, password, snp_auth); break; } } static int isis_instance_domain_password_create(enum nb_event event, const struct lyd_node *dnode, union nb_resource *resource) { /* actual setting is done in apply_finish */ return NB_OK; } static int isis_instance_domain_password_destroy(enum nb_event event, const struct lyd_node *dnode) { struct isis_area *area; if (event != NB_EV_APPLY) return NB_OK; area = nb_running_get_entry(dnode, NULL, true); isis_area_passwd_unset(area, IS_LEVEL_2); return NB_OK; } /* * XPath: /frr-isisd:isis/instance/domain-password/password */ static int isis_instance_domain_password_password_modify(enum nb_event event, const struct lyd_node *dnode, union nb_resource *resource) { /* actual setting is done in apply_finish */ return NB_OK; } /* * XPath: /frr-isisd:isis/instance/domain-password/password-type */ static int isis_instance_domain_password_password_type_modify(enum nb_event event, const struct lyd_node *dnode, union nb_resource *resource) { /* actual setting is done in apply_finish */ return NB_OK; } /* * XPath: /frr-isisd:isis/instance/domain-password/authenticate-snp */ static int isis_instance_domain_password_authenticate_snp_modify( enum nb_event event, const struct lyd_node *dnode, union nb_resource *resource) { /* actual setting is done in apply_finish */ return NB_OK; } /* * XPath: /frr-isisd:isis/instance/default-information-originate/ipv4 */ static void default_info_origin_apply_finish(const struct lyd_node *dnode, int family) { int originate_type = DEFAULT_ORIGINATE; unsigned long metric = 0; const char *routemap = NULL; struct isis_area *area = nb_running_get_entry(dnode, NULL, true); int level = yang_dnode_get_enum(dnode, "./level"); if (yang_dnode_get_bool(dnode, "./always")) { originate_type = DEFAULT_ORIGINATE_ALWAYS; } else if (family == AF_INET6) { zlog_warn( "%s: Zebra doesn't implement default-originate for IPv6 yet, so use with care or use default-originate always.", __func__); } if (yang_dnode_exists(dnode, "./metric")) metric = yang_dnode_get_uint32(dnode, "./metric"); if (yang_dnode_exists(dnode, "./route-map")) routemap = yang_dnode_get_string(dnode, "./route-map"); isis_redist_set(area, level, family, DEFAULT_ROUTE, metric, routemap, originate_type); } static void default_info_origin_ipv4_apply_finish(const struct lyd_node *dnode) { default_info_origin_apply_finish(dnode, AF_INET); } static void default_info_origin_ipv6_apply_finish(const struct lyd_node *dnode) { default_info_origin_apply_finish(dnode, AF_INET6); } static int isis_instance_default_information_originate_ipv4_create( enum nb_event event, const struct lyd_node *dnode, union nb_resource *resource) { /* It's all done by default_info_origin_apply_finish */ return NB_OK; } static int isis_instance_default_information_originate_ipv4_destroy( enum nb_event event, const struct lyd_node *dnode) { struct isis_area *area; int level; if (event != NB_EV_APPLY) return NB_OK; area = nb_running_get_entry(dnode, NULL, true); level = yang_dnode_get_enum(dnode, "./level"); isis_redist_unset(area, level, AF_INET, DEFAULT_ROUTE); return NB_OK; } /* * XPath: /frr-isisd:isis/instance/default-information-originate/ipv4/always */ static int isis_instance_default_information_originate_ipv4_always_modify( enum nb_event event, const struct lyd_node *dnode, union nb_resource *resource) { /* It's all done by default_info_origin_apply_finish */ return NB_OK; } /* * XPath: /frr-isisd:isis/instance/default-information-originate/ipv4/route-map */ static int isis_instance_default_information_originate_ipv4_route_map_modify( enum nb_event event, const struct lyd_node *dnode, union nb_resource *resource) { /* It's all done by default_info_origin_apply_finish */ return NB_OK; } static int isis_instance_default_information_originate_ipv4_route_map_destroy( enum nb_event event, const struct lyd_node *dnode) { /* It's all done by default_info_origin_apply_finish */ return NB_OK; } /* * XPath: /frr-isisd:isis/instance/default-information-originate/ipv4/metric */ static int isis_instance_default_information_originate_ipv4_metric_modify( enum nb_event event, const struct lyd_node *dnode, union nb_resource *resource) { /* It's all done by default_info_origin_apply_finish */ return NB_OK; } /* * XPath: /frr-isisd:isis/instance/default-information-originate/ipv6 */ static int isis_instance_default_information_originate_ipv6_create( enum nb_event event, const struct lyd_node *dnode, union nb_resource *resource) { /* It's all done by default_info_origin_apply_finish */ return NB_OK; } static int isis_instance_default_information_originate_ipv6_destroy( enum nb_event event, const struct lyd_node *dnode) { struct isis_area *area; int level; if (event != NB_EV_APPLY) return NB_OK; area = nb_running_get_entry(dnode, NULL, true); level = yang_dnode_get_enum(dnode, "./level"); isis_redist_unset(area, level, AF_INET6, DEFAULT_ROUTE); return NB_OK; } /* * XPath: /frr-isisd:isis/instance/default-information-originate/ipv6/always */ static int isis_instance_default_information_originate_ipv6_always_modify( enum nb_event event, const struct lyd_node *dnode, union nb_resource *resource) { /* It's all done by default_info_origin_apply_finish */ return NB_OK; } /* * XPath: /frr-isisd:isis/instance/default-information-originate/ipv6/route-map */ static int isis_instance_default_information_originate_ipv6_route_map_modify( enum nb_event event, const struct lyd_node *dnode, union nb_resource *resource) { /* It's all done by default_info_origin_apply_finish */ return NB_OK; } static int isis_instance_default_information_originate_ipv6_route_map_destroy( enum nb_event event, const struct lyd_node *dnode) { /* It's all done by default_info_origin_apply_finish */ return NB_OK; } /* * XPath: /frr-isisd:isis/instance/default-information-originate/ipv6/metric */ static int isis_instance_default_information_originate_ipv6_metric_modify( enum nb_event event, const struct lyd_node *dnode, union nb_resource *resource) { /* It's all done by default_info_origin_apply_finish */ return NB_OK; } /* * XPath: /frr-isisd:isis/instance/redistribute/ipv4 */ static void redistribute_apply_finish(const struct lyd_node *dnode, int family) { assert(family == AF_INET || family == AF_INET6); int type, level; unsigned long metric = 0; const char *routemap = NULL; struct isis_area *area; type = yang_dnode_get_enum(dnode, "./protocol"); level = yang_dnode_get_enum(dnode, "./level"); area = nb_running_get_entry(dnode, NULL, true); if (yang_dnode_exists(dnode, "./metric")) metric = yang_dnode_get_uint32(dnode, "./metric"); if (yang_dnode_exists(dnode, "./route-map")) routemap = yang_dnode_get_string(dnode, "./route-map"); isis_redist_set(area, level, family, type, metric, routemap, 0); } static void redistribute_ipv4_apply_finish(const struct lyd_node *dnode) { redistribute_apply_finish(dnode, AF_INET); } static void redistribute_ipv6_apply_finish(const struct lyd_node *dnode) { redistribute_apply_finish(dnode, AF_INET6); } static int isis_instance_redistribute_ipv4_create(enum nb_event event, const struct lyd_node *dnode, union nb_resource *resource) { /* It's all done by redistribute_apply_finish */ return NB_OK; } static int isis_instance_redistribute_ipv4_destroy(enum nb_event event, const struct lyd_node *dnode) { struct isis_area *area; int level, type; if (event != NB_EV_APPLY) return NB_OK; area = nb_running_get_entry(dnode, NULL, true); level = yang_dnode_get_enum(dnode, "./level"); type = yang_dnode_get_enum(dnode, "./protocol"); isis_redist_unset(area, level, AF_INET, type); return NB_OK; } /* * XPath: /frr-isisd:isis/instance/redistribute/ipv4/route-map */ static int isis_instance_redistribute_ipv4_route_map_modify(enum nb_event event, const struct lyd_node *dnode, union nb_resource *resource) { /* It's all done by redistribute_apply_finish */ return NB_OK; } static int isis_instance_redistribute_ipv4_route_map_destroy(enum nb_event event, const struct lyd_node *dnode) { /* It's all done by redistribute_apply_finish */ return NB_OK; } /* * XPath: /frr-isisd:isis/instance/redistribute/ipv4/metric */ static int isis_instance_redistribute_ipv4_metric_modify(enum nb_event event, const struct lyd_node *dnode, union nb_resource *resource) { /* It's all done by redistribute_apply_finish */ return NB_OK; } /* * XPath: /frr-isisd:isis/instance/redistribute/ipv6 */ static int isis_instance_redistribute_ipv6_create(enum nb_event event, const struct lyd_node *dnode, union nb_resource *resource) { /* It's all done by redistribute_apply_finish */ return NB_OK; } static int isis_instance_redistribute_ipv6_destroy(enum nb_event event, const struct lyd_node *dnode) { struct isis_area *area; int level, type; if (event != NB_EV_APPLY) return NB_OK; area = nb_running_get_entry(dnode, NULL, true); level = yang_dnode_get_enum(dnode, "./level"); type = yang_dnode_get_enum(dnode, "./protocol"); isis_redist_unset(area, level, AF_INET6, type); return NB_OK; } /* * XPath: /frr-isisd:isis/instance/redistribute/ipv6/route-map */ static int isis_instance_redistribute_ipv6_route_map_modify(enum nb_event event, const struct lyd_node *dnode, union nb_resource *resource) { /* It's all done by redistribute_apply_finish */ return NB_OK; } static int isis_instance_redistribute_ipv6_route_map_destroy(enum nb_event event, const struct lyd_node *dnode) { /* It's all done by redistribute_apply_finish */ return NB_OK; } /* * XPath: /frr-isisd:isis/instance/redistribute/ipv6/metric */ static int isis_instance_redistribute_ipv6_metric_modify(enum nb_event event, const struct lyd_node *dnode, union nb_resource *resource) { /* It's all done by redistribute_apply_finish */ return NB_OK; } /* * XPath: /frr-isisd:isis/instance/multi-topology/ipv4-multicast */ static int isis_multi_topology_common(enum nb_event event, const struct lyd_node *dnode, const char *topology, bool create) { struct isis_area *area; struct isis_area_mt_setting *setting; uint16_t mtid = isis_str2mtid(topology); switch (event) { case NB_EV_VALIDATE: if (mtid == (uint16_t)-1) { flog_warn(EC_LIB_NB_CB_CONFIG_VALIDATE, "Unknown topology %s", topology); return NB_ERR_VALIDATION; } break; case NB_EV_PREPARE: case NB_EV_ABORT: break; case NB_EV_APPLY: area = nb_running_get_entry(dnode, NULL, true); setting = area_get_mt_setting(area, mtid); setting->enabled = create; lsp_regenerate_schedule(area, IS_LEVEL_1 | IS_LEVEL_2, 0); break; } return NB_OK; } static int isis_multi_topology_overload_common(enum nb_event event, const struct lyd_node *dnode, const char *topology) { struct isis_area *area; struct isis_area_mt_setting *setting; uint16_t mtid = isis_str2mtid(topology); /* validation is done in isis_multi_topology_common */ if (event != NB_EV_APPLY) return NB_OK; area = nb_running_get_entry(dnode, NULL, true); setting = area_get_mt_setting(area, mtid); setting->overload = yang_dnode_get_bool(dnode, NULL); if (setting->enabled) lsp_regenerate_schedule(area, IS_LEVEL_1 | IS_LEVEL_2, 0); return NB_OK; } static int isis_instance_multi_topology_ipv4_multicast_create(enum nb_event event, const struct lyd_node *dnode, union nb_resource *resource) { return isis_multi_topology_common(event, dnode, "ipv4-multicast", true); } static int isis_instance_multi_topology_ipv4_multicast_destroy(enum nb_event event, const struct lyd_node *dnode) { return isis_multi_topology_common(event, dnode, "ipv4-multicast", false); } /* * XPath: /frr-isisd:isis/instance/multi-topology/ipv4-multicast/overload */ static int isis_instance_multi_topology_ipv4_multicast_overload_modify( enum nb_event event, const struct lyd_node *dnode, union nb_resource *resource) { return isis_multi_topology_overload_common(event, dnode, "ipv4-multicast"); } /* * XPath: /frr-isisd:isis/instance/multi-topology/ipv4-management */ static int isis_instance_multi_topology_ipv4_management_create( enum nb_event event, const struct lyd_node *dnode, union nb_resource *resource) { return isis_multi_topology_common(event, dnode, "ipv4-mgmt", true); } static int isis_instance_multi_topology_ipv4_management_destroy( enum nb_event event, const struct lyd_node *dnode) { return isis_multi_topology_common(event, dnode, "ipv4-mgmt", false); } /* * XPath: /frr-isisd:isis/instance/multi-topology/ipv4-management/overload */ static int isis_instance_multi_topology_ipv4_management_overload_modify( enum nb_event event, const struct lyd_node *dnode, union nb_resource *resource) { return isis_multi_topology_overload_common(event, dnode, "ipv4-mgmt"); } /* * XPath: /frr-isisd:isis/instance/multi-topology/ipv6-unicast */ static int isis_instance_multi_topology_ipv6_unicast_create(enum nb_event event, const struct lyd_node *dnode, union nb_resource *resource) { return isis_multi_topology_common(event, dnode, "ipv6-unicast", true); } static int isis_instance_multi_topology_ipv6_unicast_destroy(enum nb_event event, const struct lyd_node *dnode) { return isis_multi_topology_common(event, dnode, "ipv6-unicast", false); } /* * XPath: /frr-isisd:isis/instance/multi-topology/ipv6-unicast/overload */ static int isis_instance_multi_topology_ipv6_unicast_overload_modify( enum nb_event event, const struct lyd_node *dnode, union nb_resource *resource) { return isis_multi_topology_overload_common(event, dnode, "ipv6-unicast"); } /* * XPath: /frr-isisd:isis/instance/multi-topology/ipv6-multicast */ static int isis_instance_multi_topology_ipv6_multicast_create(enum nb_event event, const struct lyd_node *dnode, union nb_resource *resource) { return isis_multi_topology_common(event, dnode, "ipv6-multicast", true); } static int isis_instance_multi_topology_ipv6_multicast_destroy(enum nb_event event, const struct lyd_node *dnode) { return isis_multi_topology_common(event, dnode, "ipv6-multicast", false); } /* * XPath: /frr-isisd:isis/instance/multi-topology/ipv6-multicast/overload */ static int isis_instance_multi_topology_ipv6_multicast_overload_modify( enum nb_event event, const struct lyd_node *dnode, union nb_resource *resource) { return isis_multi_topology_overload_common(event, dnode, "ipv6-multicast"); } /* * XPath: /frr-isisd:isis/instance/multi-topology/ipv6-management */ static int isis_instance_multi_topology_ipv6_management_create( enum nb_event event, const struct lyd_node *dnode, union nb_resource *resource) { return isis_multi_topology_common(event, dnode, "ipv6-mgmt", true); } static int isis_instance_multi_topology_ipv6_management_destroy( enum nb_event event, const struct lyd_node *dnode) { return isis_multi_topology_common(event, dnode, "ipv6-mgmt", false); } /* * XPath: /frr-isisd:isis/instance/multi-topology/ipv6-management/overload */ static int isis_instance_multi_topology_ipv6_management_overload_modify( enum nb_event event, const struct lyd_node *dnode, union nb_resource *resource) { return isis_multi_topology_overload_common(event, dnode, "ipv6-mgmt"); } /* * XPath: /frr-isisd:isis/instance/multi-topology/ipv6-dstsrc */ static int isis_instance_multi_topology_ipv6_dstsrc_create(enum nb_event event, const struct lyd_node *dnode, union nb_resource *resource) { return isis_multi_topology_common(event, dnode, "ipv6-dstsrc", true); } static int isis_instance_multi_topology_ipv6_dstsrc_destroy(enum nb_event event, const struct lyd_node *dnode) { return isis_multi_topology_common(event, dnode, "ipv6-dstsrc", false); } /* * XPath: /frr-isisd:isis/instance/multi-topology/ipv6-dstsrc/overload */ static int isis_instance_multi_topology_ipv6_dstsrc_overload_modify( enum nb_event event, const struct lyd_node *dnode, union nb_resource *resource) { return isis_multi_topology_overload_common(event, dnode, "ipv6-dstsrc"); } /* * XPath: /frr-isisd:isis/instance/log-adjacency-changes */ static int isis_instance_log_adjacency_changes_modify(enum nb_event event, const struct lyd_node *dnode, union nb_resource *resource) { struct isis_area *area; bool log = yang_dnode_get_bool(dnode, NULL); if (event != NB_EV_APPLY) return NB_OK; area = nb_running_get_entry(dnode, NULL, true); area->log_adj_changes = log ? 1 : 0; return NB_OK; } /* * XPath: /frr-isisd:isis/instance/mpls-te */ static int isis_instance_mpls_te_create(enum nb_event event, const struct lyd_node *dnode, union nb_resource *resource) { struct listnode *node; struct isis_area *area; struct isis_circuit *circuit; if (event != NB_EV_APPLY) return NB_OK; area = nb_running_get_entry(dnode, NULL, true); if (area->mta == NULL) { struct mpls_te_area *new; zlog_debug("ISIS MPLS-TE: Initialize area %s", area->area_tag); new = XCALLOC(MTYPE_ISIS_MPLS_TE, sizeof(struct mpls_te_area)); /* Initialize MPLS_TE structure */ new->status = enable; new->level = 0; new->inter_as = off; new->interas_areaid.s_addr = 0; new->router_id.s_addr = 0; area->mta = new; } else { area->mta->status = enable; } /* * Following code is intended to handle two cases; * * 1) MPLS-TE was disabled at startup time, but now become enabled. * In this case, we must enable MPLS-TE Circuit regarding interface * MPLS_TE flag * 2) MPLS-TE was once enabled then disabled, and now enabled again. */ for (ALL_LIST_ELEMENTS_RO(area->circuit_list, node, circuit)) { if (circuit->mtc == NULL || IS_FLOOD_AS(circuit->mtc->type)) continue; if (!IS_MPLS_TE(circuit->mtc) && HAS_LINK_PARAMS(circuit->interface)) circuit->mtc->status = enable; else continue; /* Reoriginate STD_TE & GMPLS circuits */ if (circuit->area) lsp_regenerate_schedule(circuit->area, circuit->is_type, 0); } return NB_OK; } static int isis_instance_mpls_te_destroy(enum nb_event event, const struct lyd_node *dnode) { struct listnode *node; struct isis_area *area; struct isis_circuit *circuit; if (event != NB_EV_APPLY) return NB_OK; area = nb_running_get_entry(dnode, NULL, true); if (IS_MPLS_TE(area->mta)) area->mta->status = disable; else return NB_OK; /* Flush LSP if circuit engage */ for (ALL_LIST_ELEMENTS_RO(area->circuit_list, node, circuit)) { if (circuit->mtc == NULL || (circuit->mtc->status == disable)) continue; /* disable MPLS_TE Circuit */ circuit->mtc->status = disable; /* Re-originate circuit without STD_TE & GMPLS parameters */ if (circuit->area) lsp_regenerate_schedule(circuit->area, circuit->is_type, 0); } return NB_OK; } /* * XPath: /frr-isisd:isis/instance/mpls-te/router-address */ static int isis_instance_mpls_te_router_address_modify(enum nb_event event, const struct lyd_node *dnode, union nb_resource *resource) { struct in_addr value; struct isis_area *area; if (event != NB_EV_APPLY) return NB_OK; area = nb_running_get_entry(dnode, NULL, true); /* only proceed if MPLS-TE is enabled */ if (!IS_MPLS_TE(area->mta)) return NB_OK; /* Update Area Router ID */ yang_dnode_get_ipv4(&value, dnode, NULL); area->mta->router_id.s_addr = value.s_addr; /* And re-schedule LSP update */ if (listcount(area->area_addrs) > 0) lsp_regenerate_schedule(area, area->is_type, 0); return NB_OK; } static int isis_instance_mpls_te_router_address_destroy(enum nb_event event, const struct lyd_node *dnode) { struct isis_area *area; if (event != NB_EV_APPLY) return NB_OK; area = nb_running_get_entry(dnode, NULL, true); /* only proceed if MPLS-TE is enabled */ if (!IS_MPLS_TE(area->mta)) return NB_OK; /* Reset Area Router ID */ area->mta->router_id.s_addr = INADDR_ANY; /* And re-schedule LSP update */ if (listcount(area->area_addrs) > 0) lsp_regenerate_schedule(area, area->is_type, 0); return NB_OK; } /* * XPath: /frr-interface:lib/interface/frr-isisd:isis */ static int lib_interface_isis_create(enum nb_event event, const struct lyd_node *dnode, union nb_resource *resource) { struct isis_area *area; struct interface *ifp; struct isis_circuit *circuit; const char *area_tag = yang_dnode_get_string(dnode, "./area-tag"); uint32_t min_mtu, actual_mtu; switch (event) { case NB_EV_PREPARE: case NB_EV_ABORT: break; case NB_EV_VALIDATE: /* check if interface mtu is sufficient. If the area has not * been created yet, assume default MTU for the area */ ifp = nb_running_get_entry(dnode, NULL, false); /* zebra might not know yet about the MTU - nothing we can do */ if (!ifp || ifp->mtu == 0) break; actual_mtu = if_is_broadcast(ifp) ? ifp->mtu - LLC_LEN : ifp->mtu; area = isis_area_lookup(area_tag); if (area) min_mtu = area->lsp_mtu; else #ifndef FABRICD min_mtu = yang_get_default_uint16( "/frr-isisd:isis/instance/lsp/mtu"); #else min_mtu = DEFAULT_LSP_MTU; #endif /* ifndef FABRICD */ if (actual_mtu < min_mtu) { flog_warn(EC_LIB_NB_CB_CONFIG_VALIDATE, "Interface %s has MTU %" PRIu32 ", minimum MTU for the area is %" PRIu32 "", ifp->name, actual_mtu, min_mtu); return NB_ERR_VALIDATION; } break; case NB_EV_APPLY: area = isis_area_lookup(area_tag); /* The area should have already be created. We are * setting the priority of the global isis area creation * slightly lower, so it should be executed first, but I * cannot rely on that so here I have to check. */ if (!area) { flog_err( EC_LIB_NB_CB_CONFIG_APPLY, "%s: attempt to create circuit for area %s before the area has been created", __func__, area_tag); abort(); } ifp = nb_running_get_entry(dnode, NULL, true); circuit = isis_circuit_create(area, ifp); assert(circuit && (circuit->state == C_STATE_CONF || circuit->state == C_STATE_UP)); nb_running_set_entry(dnode, circuit); break; } return NB_OK; } static int lib_interface_isis_destroy(enum nb_event event, const struct lyd_node *dnode) { struct isis_circuit *circuit; if (event != NB_EV_APPLY) return NB_OK; circuit = nb_running_unset_entry(dnode); if (!circuit) return NB_ERR_INCONSISTENCY; if (circuit->state == C_STATE_UP || circuit->state == C_STATE_CONF) isis_csm_state_change(ISIS_DISABLE, circuit, circuit->area); return NB_OK; } /* * XPath: /frr-interface:lib/interface/frr-isisd:isis/area-tag */ static int lib_interface_isis_area_tag_modify(enum nb_event event, const struct lyd_node *dnode, union nb_resource *resource) { struct isis_circuit *circuit; struct interface *ifp; struct vrf *vrf; const char *area_tag, *ifname, *vrfname; if (event == NB_EV_VALIDATE) { /* libyang doesn't like relative paths across module boundaries */ ifname = yang_dnode_get_string(dnode->parent->parent, "./name"); vrfname = yang_dnode_get_string(dnode->parent->parent, "./vrf"); vrf = vrf_lookup_by_name(vrfname); assert(vrf); ifp = if_lookup_by_name(ifname, vrf->vrf_id); if (!ifp) return NB_OK; circuit = circuit_lookup_by_ifp(ifp, isis->init_circ_list); area_tag = yang_dnode_get_string(dnode, NULL); if (circuit && circuit->area && circuit->area->area_tag && strcmp(circuit->area->area_tag, area_tag)) { flog_warn(EC_LIB_NB_CB_CONFIG_VALIDATE, "ISIS circuit is already defined on %s", circuit->area->area_tag); return NB_ERR_VALIDATION; } } return NB_OK; } /* * XPath: /frr-interface:lib/interface/frr-isisd:isis/circuit-type */ static int lib_interface_isis_circuit_type_modify(enum nb_event event, const struct lyd_node *dnode, union nb_resource *resource) { int circ_type = yang_dnode_get_enum(dnode, NULL); struct isis_circuit *circuit; struct interface *ifp; struct vrf *vrf; const char *ifname, *vrfname; switch (event) { case NB_EV_VALIDATE: /* libyang doesn't like relative paths across module boundaries */ ifname = yang_dnode_get_string(dnode->parent->parent, "./name"); vrfname = yang_dnode_get_string(dnode->parent->parent, "./vrf"); vrf = vrf_lookup_by_name(vrfname); assert(vrf); ifp = if_lookup_by_name(ifname, vrf->vrf_id); if (!ifp) break; circuit = circuit_lookup_by_ifp(ifp, isis->init_circ_list); if (circuit && circuit->state == C_STATE_UP && circuit->area->is_type != IS_LEVEL_1_AND_2 && circuit->area->is_type != circ_type) { flog_warn(EC_LIB_NB_CB_CONFIG_VALIDATE, "Invalid circuit level for area %s", circuit->area->area_tag); return NB_ERR_VALIDATION; } break; case NB_EV_PREPARE: case NB_EV_ABORT: break; case NB_EV_APPLY: circuit = nb_running_get_entry(dnode, NULL, true); isis_circuit_is_type_set(circuit, circ_type); break; } return NB_OK; } /* * XPath: /frr-interface:lib/interface/frr-isisd:isis/ipv4-routing */ static int lib_interface_isis_ipv4_routing_modify(enum nb_event event, const struct lyd_node *dnode, union nb_resource *resource) { bool ipv4, ipv6; struct isis_circuit *circuit; if (event != NB_EV_APPLY) return NB_OK; circuit = nb_running_get_entry(dnode, NULL, true); ipv4 = yang_dnode_get_bool(dnode, NULL); ipv6 = yang_dnode_get_bool(dnode, "../ipv6-routing"); isis_circuit_af_set(circuit, ipv4, ipv6); return NB_OK; } /* * XPath: /frr-interface:lib/interface/frr-isisd:isis/ipv6-routing */ static int lib_interface_isis_ipv6_routing_modify(enum nb_event event, const struct lyd_node *dnode, union nb_resource *resource) { bool ipv4, ipv6; struct isis_circuit *circuit; if (event != NB_EV_APPLY) return NB_OK; circuit = nb_running_get_entry(dnode, NULL, true); ipv4 = yang_dnode_exists(dnode, "../ipv4-routing"); ipv6 = yang_dnode_get_bool(dnode, NULL); isis_circuit_af_set(circuit, ipv4, ipv6); return NB_OK; } /* * XPath: /frr-interface:lib/interface/frr-isisd:isis/bfd-monitoring */ static int lib_interface_isis_bfd_monitoring_modify(enum nb_event event, const struct lyd_node *dnode, union nb_resource *resource) { struct isis_circuit *circuit; bool bfd_monitoring; if (event != NB_EV_APPLY) return NB_OK; circuit = nb_running_get_entry(dnode, NULL, true); bfd_monitoring = yang_dnode_get_bool(dnode, NULL); if (bfd_monitoring) { isis_bfd_circuit_param_set(circuit, BFD_DEF_MIN_RX, BFD_DEF_MIN_TX, BFD_DEF_DETECT_MULT, true); } else { isis_bfd_circuit_cmd(circuit, ZEBRA_BFD_DEST_DEREGISTER); bfd_info_free(&circuit->bfd_info); } return NB_OK; } /* * XPath: /frr-interface:lib/interface/frr-isisd:isis/csnp-interval/level-1 */ static int lib_interface_isis_csnp_interval_level_1_modify(enum nb_event event, const struct lyd_node *dnode, union nb_resource *resource) { struct isis_circuit *circuit; if (event != NB_EV_APPLY) return NB_OK; circuit = nb_running_get_entry(dnode, NULL, true); circuit->csnp_interval[0] = yang_dnode_get_uint16(dnode, NULL); return NB_OK; } /* * XPath: /frr-interface:lib/interface/frr-isisd:isis/csnp-interval/level-2 */ static int lib_interface_isis_csnp_interval_level_2_modify(enum nb_event event, const struct lyd_node *dnode, union nb_resource *resource) { struct isis_circuit *circuit; if (event != NB_EV_APPLY) return NB_OK; circuit = nb_running_get_entry(dnode, NULL, true); circuit->csnp_interval[1] = yang_dnode_get_uint16(dnode, NULL); return NB_OK; } /* * XPath: /frr-interface:lib/interface/frr-isisd:isis/psnp-interval/level-1 */ static int lib_interface_isis_psnp_interval_level_1_modify(enum nb_event event, const struct lyd_node *dnode, union nb_resource *resource) { struct isis_circuit *circuit; if (event != NB_EV_APPLY) return NB_OK; circuit = nb_running_get_entry(dnode, NULL, true); circuit->psnp_interval[0] = yang_dnode_get_uint16(dnode, NULL); return NB_OK; } /* * XPath: /frr-interface:lib/interface/frr-isisd:isis/psnp-interval/level-2 */ static int lib_interface_isis_psnp_interval_level_2_modify(enum nb_event event, const struct lyd_node *dnode, union nb_resource *resource) { struct isis_circuit *circuit; if (event != NB_EV_APPLY) return NB_OK; circuit = nb_running_get_entry(dnode, NULL, true); circuit->psnp_interval[1] = yang_dnode_get_uint16(dnode, NULL); return NB_OK; } /* * XPath: /frr-interface:lib/interface/frr-isisd:isis/hello/padding */ static int lib_interface_isis_hello_padding_modify(enum nb_event event, const struct lyd_node *dnode, union nb_resource *resource) { struct isis_circuit *circuit; if (event != NB_EV_APPLY) return NB_OK; circuit = nb_running_get_entry(dnode, NULL, true); circuit->pad_hellos = yang_dnode_get_bool(dnode, NULL); return NB_OK; } /* * XPath: /frr-interface:lib/interface/frr-isisd:isis/hello/interval/level-1 */ static int lib_interface_isis_hello_interval_level_1_modify(enum nb_event event, const struct lyd_node *dnode, union nb_resource *resource) { struct isis_circuit *circuit; uint32_t interval; if (event != NB_EV_APPLY) return NB_OK; circuit = nb_running_get_entry(dnode, NULL, true); interval = yang_dnode_get_uint32(dnode, NULL); circuit->hello_interval[0] = interval; return NB_OK; } /* * XPath: /frr-interface:lib/interface/frr-isisd:isis/hello/interval/level-2 */ static int lib_interface_isis_hello_interval_level_2_modify(enum nb_event event, const struct lyd_node *dnode, union nb_resource *resource) { struct isis_circuit *circuit; uint32_t interval; if (event != NB_EV_APPLY) return NB_OK; circuit = nb_running_get_entry(dnode, NULL, true); interval = yang_dnode_get_uint32(dnode, NULL); circuit->hello_interval[1] = interval; return NB_OK; } /* * XPath: /frr-interface:lib/interface/frr-isisd:isis/hello/multiplier/level-1 */ static int lib_interface_isis_hello_multiplier_level_1_modify(enum nb_event event, const struct lyd_node *dnode, union nb_resource *resource) { struct isis_circuit *circuit; uint16_t multi; if (event != NB_EV_APPLY) return NB_OK; circuit = nb_running_get_entry(dnode, NULL, true); multi = yang_dnode_get_uint16(dnode, NULL); circuit->hello_multiplier[0] = multi; return NB_OK; } /* * XPath: /frr-interface:lib/interface/frr-isisd:isis/hello/multiplier/level-2 */ static int lib_interface_isis_hello_multiplier_level_2_modify(enum nb_event event, const struct lyd_node *dnode, union nb_resource *resource) { struct isis_circuit *circuit; uint16_t multi; if (event != NB_EV_APPLY) return NB_OK; circuit = nb_running_get_entry(dnode, NULL, true); multi = yang_dnode_get_uint16(dnode, NULL); circuit->hello_multiplier[1] = multi; return NB_OK; } /* * XPath: /frr-interface:lib/interface/frr-isisd:isis/metric/level-1 */ static int lib_interface_isis_metric_level_1_modify(enum nb_event event, const struct lyd_node *dnode, union nb_resource *resource) { struct isis_circuit *circuit; unsigned int met; if (event != NB_EV_APPLY) return NB_OK; circuit = nb_running_get_entry(dnode, NULL, true); met = yang_dnode_get_uint32(dnode, NULL); isis_circuit_metric_set(circuit, IS_LEVEL_1, met); return NB_OK; } /* * XPath: /frr-interface:lib/interface/frr-isisd:isis/metric/level-2 */ static int lib_interface_isis_metric_level_2_modify(enum nb_event event, const struct lyd_node *dnode, union nb_resource *resource) { struct isis_circuit *circuit; unsigned int met; if (event != NB_EV_APPLY) return NB_OK; circuit = nb_running_get_entry(dnode, NULL, true); met = yang_dnode_get_uint32(dnode, NULL); isis_circuit_metric_set(circuit, IS_LEVEL_2, met); return NB_OK; } /* * XPath: /frr-interface:lib/interface/frr-isisd:isis/priority/level-1 */ static int lib_interface_isis_priority_level_1_modify(enum nb_event event, const struct lyd_node *dnode, union nb_resource *resource) { struct isis_circuit *circuit; if (event != NB_EV_APPLY) return NB_OK; circuit = nb_running_get_entry(dnode, NULL, true); circuit->priority[0] = yang_dnode_get_uint8(dnode, NULL); return NB_OK; } /* * XPath: /frr-interface:lib/interface/frr-isisd:isis/priority/level-2 */ static int lib_interface_isis_priority_level_2_modify(enum nb_event event, const struct lyd_node *dnode, union nb_resource *resource) { struct isis_circuit *circuit; if (event != NB_EV_APPLY) return NB_OK; circuit = nb_running_get_entry(dnode, NULL, true); circuit->priority[1] = yang_dnode_get_uint8(dnode, NULL); return NB_OK; } /* * XPath: /frr-interface:lib/interface/frr-isisd:isis/network-type */ static int lib_interface_isis_network_type_modify(enum nb_event event, const struct lyd_node *dnode, union nb_resource *resource) { struct isis_circuit *circuit; int net_type = yang_dnode_get_enum(dnode, NULL); switch (event) { case NB_EV_VALIDATE: circuit = nb_running_get_entry(dnode, NULL, false); if (!circuit) break; if (circuit->circ_type == CIRCUIT_T_LOOPBACK) { flog_warn( EC_LIB_NB_CB_CONFIG_VALIDATE, "Cannot change network type on loopback interface"); return NB_ERR_VALIDATION; } if (net_type == CIRCUIT_T_BROADCAST && circuit->state == C_STATE_UP && !if_is_broadcast(circuit->interface)) { flog_warn( EC_LIB_NB_CB_CONFIG_VALIDATE, "Cannot configure non-broadcast interface for broadcast operation"); return NB_ERR_VALIDATION; } break; case NB_EV_PREPARE: case NB_EV_ABORT: break; case NB_EV_APPLY: circuit = nb_running_get_entry(dnode, NULL, true); isis_circuit_circ_type_set(circuit, net_type); break; } return NB_OK; } /* * XPath: /frr-interface:lib/interface/frr-isisd:isis/passive */ static int lib_interface_isis_passive_modify(enum nb_event event, const struct lyd_node *dnode, union nb_resource *resource) { struct isis_circuit *circuit; struct isis_area *area; struct interface *ifp; bool passive = yang_dnode_get_bool(dnode, NULL); /* validation only applies if we are setting passive to false */ if (!passive && event == NB_EV_VALIDATE) { circuit = nb_running_get_entry(dnode, NULL, false); if (!circuit) return NB_OK; ifp = circuit->interface; if (!ifp) return NB_OK; if (if_is_loopback(ifp)) { flog_warn(EC_LIB_NB_CB_CONFIG_VALIDATE, "Loopback is always passive"); return NB_ERR_VALIDATION; } } if (event != NB_EV_APPLY) return NB_OK; circuit = nb_running_get_entry(dnode, NULL, true); if (circuit->state != C_STATE_UP) { circuit->is_passive = passive; } else { area = circuit->area; isis_csm_state_change(ISIS_DISABLE, circuit, area); circuit->is_passive = passive; isis_csm_state_change(ISIS_ENABLE, circuit, area); } return NB_OK; } /* * XPath: /frr-interface:lib/interface/frr-isisd:isis/password */ static int lib_interface_isis_password_create(enum nb_event event, const struct lyd_node *dnode, union nb_resource *resource) { return NB_OK; } static int lib_interface_isis_password_destroy(enum nb_event event, const struct lyd_node *dnode) { struct isis_circuit *circuit; if (event != NB_EV_APPLY) return NB_OK; circuit = nb_running_get_entry(dnode, NULL, true); isis_circuit_passwd_unset(circuit); return NB_OK; } /* * XPath: /frr-interface:lib/interface/frr-isisd:isis/password/password */ static int lib_interface_isis_password_password_modify(enum nb_event event, const struct lyd_node *dnode, union nb_resource *resource) { struct isis_circuit *circuit; const char *password; if (event != NB_EV_APPLY) return NB_OK; password = yang_dnode_get_string(dnode, NULL); circuit = nb_running_get_entry(dnode, NULL, true); isis_circuit_passwd_set(circuit, circuit->passwd.type, password); return NB_OK; } /* * XPath: /frr-interface:lib/interface/frr-isisd:isis/password/password-type */ static int lib_interface_isis_password_password_type_modify(enum nb_event event, const struct lyd_node *dnode, union nb_resource *resource) { struct isis_circuit *circuit; uint8_t pass_type; if (event != NB_EV_APPLY) return NB_OK; pass_type = yang_dnode_get_enum(dnode, NULL); circuit = nb_running_get_entry(dnode, NULL, true); circuit->passwd.type = pass_type; return NB_OK; } /* * XPath: * /frr-interface:lib/interface/frr-isisd:isis/disable-three-way-handshake */ static int lib_interface_isis_disable_three_way_handshake_modify( enum nb_event event, const struct lyd_node *dnode, union nb_resource *resource) { struct isis_circuit *circuit; if (event != NB_EV_APPLY) return NB_OK; circuit = nb_running_get_entry(dnode, NULL, true); circuit->disable_threeway_adj = yang_dnode_get_bool(dnode, NULL); return NB_OK; } /* * XPath: * /frr-interface:lib/interface/frr-isisd:isis/multi-topology/ipv4-unicast */ static int lib_interface_isis_multi_topology_common( enum nb_event event, const struct lyd_node *dnode, uint16_t mtid) { struct isis_circuit *circuit; bool value; switch (event) { case NB_EV_VALIDATE: circuit = nb_running_get_entry(dnode, NULL, false); if (circuit && circuit->area && circuit->area->oldmetric) { flog_warn( EC_LIB_NB_CB_CONFIG_VALIDATE, "Multi topology IS-IS can only be used with wide metrics"); return NB_ERR_VALIDATION; } break; case NB_EV_PREPARE: case NB_EV_ABORT: break; case NB_EV_APPLY: circuit = nb_running_get_entry(dnode, NULL, true); value = yang_dnode_get_bool(dnode, NULL); isis_circuit_mt_enabled_set(circuit, mtid, value); break; } return NB_OK; } static int lib_interface_isis_multi_topology_ipv4_unicast_modify( enum nb_event event, const struct lyd_node *dnode, union nb_resource *resource) { return lib_interface_isis_multi_topology_common(event, dnode, ISIS_MT_IPV4_UNICAST); } /* * XPath: * /frr-interface:lib/interface/frr-isisd:isis/multi-topology/ipv4-multicast */ static int lib_interface_isis_multi_topology_ipv4_multicast_modify( enum nb_event event, const struct lyd_node *dnode, union nb_resource *resource) { return lib_interface_isis_multi_topology_common(event, dnode, ISIS_MT_IPV4_MULTICAST); } /* * XPath: * /frr-interface:lib/interface/frr-isisd:isis/multi-topology/ipv4-management */ static int lib_interface_isis_multi_topology_ipv4_management_modify( enum nb_event event, const struct lyd_node *dnode, union nb_resource *resource) { return lib_interface_isis_multi_topology_common(event, dnode, ISIS_MT_IPV4_MGMT); } /* * XPath: * /frr-interface:lib/interface/frr-isisd:isis/multi-topology/ipv6-unicast */ static int lib_interface_isis_multi_topology_ipv6_unicast_modify( enum nb_event event, const struct lyd_node *dnode, union nb_resource *resource) { return lib_interface_isis_multi_topology_common(event, dnode, ISIS_MT_IPV6_UNICAST); } /* * XPath: * /frr-interface:lib/interface/frr-isisd:isis/multi-topology/ipv6-multicast */ static int lib_interface_isis_multi_topology_ipv6_multicast_modify( enum nb_event event, const struct lyd_node *dnode, union nb_resource *resource) { return lib_interface_isis_multi_topology_common(event, dnode, ISIS_MT_IPV6_MULTICAST); } /* * XPath: * /frr-interface:lib/interface/frr-isisd:isis/multi-topology/ipv6-management */ static int lib_interface_isis_multi_topology_ipv6_management_modify( enum nb_event event, const struct lyd_node *dnode, union nb_resource *resource) { return lib_interface_isis_multi_topology_common(event, dnode, ISIS_MT_IPV6_MGMT); } /* * XPath: /frr-interface:lib/interface/frr-isisd:isis/multi-topology/ipv6-dstsrc */ static int lib_interface_isis_multi_topology_ipv6_dstsrc_modify( enum nb_event event, const struct lyd_node *dnode, union nb_resource *resource) { return lib_interface_isis_multi_topology_common(event, dnode, ISIS_MT_IPV6_DSTSRC); } /* * NOTIFICATIONS */ static void notif_prep_instance_hdr(const char *xpath, const struct isis_area *area, const char *routing_instance, struct list *args) { char xpath_arg[XPATH_MAXLEN]; struct yang_data *data; snprintf(xpath_arg, sizeof(xpath_arg), "%s/routing-instance", xpath); data = yang_data_new_string(xpath_arg, routing_instance); listnode_add(args, data); snprintf(xpath_arg, sizeof(xpath_arg), "%s/routing-protocol-name", xpath); data = yang_data_new_string(xpath_arg, area->area_tag); listnode_add(args, data); snprintf(xpath_arg, sizeof(xpath_arg), "%s/isis-level", xpath); data = yang_data_new_enum(xpath_arg, area->is_type); listnode_add(args, data); } static void notif_prepr_iface_hdr(const char *xpath, const struct isis_circuit *circuit, struct list *args) { char xpath_arg[XPATH_MAXLEN]; struct yang_data *data; snprintf(xpath_arg, sizeof(xpath_arg), "%s/interface-name", xpath); data = yang_data_new_string(xpath_arg, circuit->interface->name); listnode_add(args, data); snprintf(xpath_arg, sizeof(xpath_arg), "%s/interface-level", xpath); data = yang_data_new_enum(xpath_arg, circuit->is_type); listnode_add(args, data); snprintf(xpath_arg, sizeof(xpath_arg), "%s/extended-circuit-id", xpath); /* we do not seem to have the extended version of the circuit_id */ data = yang_data_new_uint32(xpath_arg, (uint32_t)circuit->circuit_id); listnode_add(args, data); } /* * XPath: * /frr-isisd:database-overload */ void isis_notif_db_overload(const struct isis_area *area, bool overload) { const char *xpath = "/frr-isisd:database-overload"; struct list *arguments = yang_data_list_new(); char xpath_arg[XPATH_MAXLEN]; struct yang_data *data; notif_prep_instance_hdr(xpath, area, "default", arguments); snprintf(xpath_arg, sizeof(xpath_arg), "%s/overload", xpath); data = yang_data_new_enum(xpath_arg, !!overload); listnode_add(arguments, data); nb_notification_send(xpath, arguments); } /* * XPath: * /frr-isisd:lsp-too-large */ void isis_notif_lsp_too_large(const struct isis_circuit *circuit, uint32_t pdu_size, const char *lsp_id) { const char *xpath = "/frr-isisd:lsp-too-large"; struct list *arguments = yang_data_list_new(); char xpath_arg[XPATH_MAXLEN]; struct yang_data *data; struct isis_area *area = circuit->area; notif_prep_instance_hdr(xpath, area, "default", arguments); notif_prepr_iface_hdr(xpath, circuit, arguments); snprintf(xpath_arg, sizeof(xpath_arg), "%s/pdu-size", xpath); data = yang_data_new_uint32(xpath_arg, pdu_size); listnode_add(arguments, data); snprintf(xpath_arg, sizeof(xpath_arg), "%s/lsp-id", xpath); data = yang_data_new_string(xpath_arg, lsp_id); listnode_add(arguments, data); nb_notification_send(xpath, arguments); } /* * XPath: * /frr-isisd:if-state-change */ void isis_notif_if_state_change(const struct isis_circuit *circuit, bool down) { const char *xpath = "/frr-isisd:if-state-change"; struct list *arguments = yang_data_list_new(); char xpath_arg[XPATH_MAXLEN]; struct yang_data *data; struct isis_area *area = circuit->area; notif_prep_instance_hdr(xpath, area, "default", arguments); notif_prepr_iface_hdr(xpath, circuit, arguments); snprintf(xpath_arg, sizeof(xpath_arg), "%s/state", xpath); data = yang_data_new_enum(xpath_arg, !!down); listnode_add(arguments, data); nb_notification_send(xpath, arguments); } /* * XPath: * /frr-isisd:corrupted-lsp-detected */ void isis_notif_corrupted_lsp(const struct isis_area *area, const char *lsp_id) { const char *xpath = "/frr-isisd:corrupted-lsp-detected"; struct list *arguments = yang_data_list_new(); char xpath_arg[XPATH_MAXLEN]; struct yang_data *data; notif_prep_instance_hdr(xpath, area, "default", arguments); snprintf(xpath_arg, sizeof(xpath_arg), "%s/lsp-id", xpath); data = yang_data_new_string(xpath_arg, lsp_id); listnode_add(arguments, data); nb_notification_send(xpath, arguments); } /* * XPath: * /frr-isisd:attempt-to-exceed-max-sequence */ void isis_notif_lsp_exceed_max(const struct isis_area *area, const char *lsp_id) { const char *xpath = "/frr-isisd:attempt-to-exceed-max-sequence"; struct list *arguments = yang_data_list_new(); char xpath_arg[XPATH_MAXLEN]; struct yang_data *data; notif_prep_instance_hdr(xpath, area, "default", arguments); snprintf(xpath_arg, sizeof(xpath_arg), "%s/lsp-id", xpath); data = yang_data_new_string(xpath_arg, lsp_id); listnode_add(arguments, data); nb_notification_send(xpath, arguments); } /* * XPath: * /frr-isisd:max-area-addresses-mismatch */ void isis_notif_max_area_addr_mismatch(const struct isis_circuit *circuit, uint8_t max_area_addrs, const char *raw_pdu) { const char *xpath = "/frr-isisd:max-area-addresses-mismatch"; struct list *arguments = yang_data_list_new(); char xpath_arg[XPATH_MAXLEN]; struct yang_data *data; struct isis_area *area = circuit->area; notif_prep_instance_hdr(xpath, area, "default", arguments); notif_prepr_iface_hdr(xpath, circuit, arguments); snprintf(xpath_arg, sizeof(xpath_arg), "%s/max-area-addresses", xpath); data = yang_data_new_uint8(xpath_arg, max_area_addrs); listnode_add(arguments, data); snprintf(xpath_arg, sizeof(xpath_arg), "%s/raw-pdu", xpath); data = yang_data_new(xpath_arg, raw_pdu); listnode_add(arguments, data); nb_notification_send(xpath, arguments); } /* * XPath: * /frr-isisd:authentication-type-failure */ void isis_notif_authentication_type_failure(const struct isis_circuit *circuit, const char *raw_pdu) { const char *xpath = "/frr-isisd:authentication-type-failure"; struct list *arguments = yang_data_list_new(); char xpath_arg[XPATH_MAXLEN]; struct yang_data *data; struct isis_area *area = circuit->area; notif_prep_instance_hdr(xpath, area, "default", arguments); notif_prepr_iface_hdr(xpath, circuit, arguments); snprintf(xpath_arg, sizeof(xpath_arg), "%s/raw-pdu", xpath); data = yang_data_new(xpath_arg, raw_pdu); listnode_add(arguments, data); nb_notification_send(xpath, arguments); } /* * XPath: * /frr-isisd:authentication-failure */ void isis_notif_authentication_failure(const struct isis_circuit *circuit, const char *raw_pdu) { const char *xpath = "/frr-isisd:authentication-failure"; struct list *arguments = yang_data_list_new(); char xpath_arg[XPATH_MAXLEN]; struct yang_data *data; struct isis_area *area = circuit->area; notif_prep_instance_hdr(xpath, area, "default", arguments); notif_prepr_iface_hdr(xpath, circuit, arguments); snprintf(xpath_arg, sizeof(xpath_arg), "%s/raw-pdu", xpath); data = yang_data_new(xpath_arg, raw_pdu); listnode_add(arguments, data); nb_notification_send(xpath, arguments); } /* * XPath: * /frr-isisd:adjacency-state-change */ void isis_notif_adj_state_change(const struct isis_adjacency *adj, int new_state, const char *reason) { const char *xpath = "/frr-isisd:adjacency-state-change"; struct list *arguments = yang_data_list_new(); char xpath_arg[XPATH_MAXLEN]; struct yang_data *data; struct isis_circuit *circuit = adj->circuit; struct isis_area *area = circuit->area; struct isis_dynhn *dyn = dynhn_find_by_id(adj->sysid); notif_prep_instance_hdr(xpath, area, "default", arguments); notif_prepr_iface_hdr(xpath, circuit, arguments); if (dyn) { snprintf(xpath_arg, sizeof(xpath_arg), "%s/neighbor", xpath); data = yang_data_new_string(xpath_arg, dyn->hostname); listnode_add(arguments, data); } snprintf(xpath_arg, sizeof(xpath_arg), "%s/neighbor-system-id", xpath); data = yang_data_new_string(xpath_arg, sysid_print(adj->sysid)); listnode_add(arguments, data); snprintf(xpath_arg, sizeof(xpath_arg), "%s/state", xpath); switch (new_state) { case ISIS_ADJ_DOWN: data = yang_data_new_string(xpath_arg, "down"); break; case ISIS_ADJ_UP: data = yang_data_new_string(xpath_arg, "up"); break; case ISIS_ADJ_INITIALIZING: data = yang_data_new_string(xpath_arg, "init"); break; default: data = yang_data_new_string(xpath_arg, "failed"); } listnode_add(arguments, data); if (new_state == ISIS_ADJ_DOWN) { snprintf(xpath_arg, sizeof(xpath_arg), "%s/reason", xpath); data = yang_data_new_string(xpath_arg, reason); listnode_add(arguments, data); } nb_notification_send(xpath, arguments); } /* * XPath: * /frr-isisd:rejected-adjacency */ void isis_notif_reject_adjacency(const struct isis_circuit *circuit, const char *reason, const char *raw_pdu) { const char *xpath = "/frr-isisd:rejected-adjacency"; struct list *arguments = yang_data_list_new(); char xpath_arg[XPATH_MAXLEN]; struct yang_data *data; struct isis_area *area = circuit->area; notif_prep_instance_hdr(xpath, area, "default", arguments); notif_prepr_iface_hdr(xpath, circuit, arguments); snprintf(xpath_arg, sizeof(xpath_arg), "%s/reason", xpath); data = yang_data_new_string(xpath_arg, reason); listnode_add(arguments, data); snprintf(xpath_arg, sizeof(xpath_arg), "%s/raw-pdu", xpath); data = yang_data_new(xpath_arg, raw_pdu); listnode_add(arguments, data); nb_notification_send(xpath, arguments); } /* * XPath: * /frr-isisd:area-mismatch */ void isis_notif_area_mismatch(const struct isis_circuit *circuit, const char *raw_pdu) { const char *xpath = "/frr-isisd:area-mismatch"; struct list *arguments = yang_data_list_new(); char xpath_arg[XPATH_MAXLEN]; struct yang_data *data; struct isis_area *area = circuit->area; notif_prep_instance_hdr(xpath, area, "default", arguments); notif_prepr_iface_hdr(xpath, circuit, arguments); snprintf(xpath_arg, sizeof(xpath_arg), "%s/raw-pdu", xpath); data = yang_data_new(xpath_arg, raw_pdu); listnode_add(arguments, data); nb_notification_send(xpath, arguments); } /* * XPath: * /frr-isisd:lsp-received */ void isis_notif_lsp_received(const struct isis_circuit *circuit, const char *lsp_id, uint32_t seqno, uint32_t timestamp, const char *sys_id) { const char *xpath = "/frr-isisd:lsp-received"; struct list *arguments = yang_data_list_new(); char xpath_arg[XPATH_MAXLEN]; struct yang_data *data; struct isis_area *area = circuit->area; notif_prep_instance_hdr(xpath, area, "default", arguments); notif_prepr_iface_hdr(xpath, circuit, arguments); snprintf(xpath_arg, sizeof(xpath_arg), "%s/lsp-id", xpath); data = yang_data_new_string(xpath_arg, lsp_id); listnode_add(arguments, data); snprintf(xpath_arg, sizeof(xpath_arg), "%s/sequence", xpath); data = yang_data_new_uint32(xpath_arg, seqno); listnode_add(arguments, data); snprintf(xpath_arg, sizeof(xpath_arg), "%s/received-timestamp", xpath); data = yang_data_new_uint32(xpath_arg, timestamp); listnode_add(arguments, data); snprintf(xpath_arg, sizeof(xpath_arg), "%s/neighbor-system-id", xpath); data = yang_data_new_string(xpath_arg, sys_id); listnode_add(arguments, data); nb_notification_send(xpath, arguments); } /* * XPath: * /frr-isisd:lsp-generation */ void isis_notif_lsp_gen(const struct isis_area *area, const char *lsp_id, uint32_t seqno, uint32_t timestamp) { const char *xpath = "/frr-isisd:lsp-generation"; struct list *arguments = yang_data_list_new(); char xpath_arg[XPATH_MAXLEN]; struct yang_data *data; notif_prep_instance_hdr(xpath, area, "default", arguments); snprintf(xpath_arg, sizeof(xpath_arg), "%s/lsp-id", xpath); data = yang_data_new_string(xpath_arg, lsp_id); listnode_add(arguments, data); snprintf(xpath_arg, sizeof(xpath_arg), "%s/sequence", xpath); data = yang_data_new_uint32(xpath_arg, seqno); listnode_add(arguments, data); snprintf(xpath_arg, sizeof(xpath_arg), "%s/send-timestamp", xpath); data = yang_data_new_uint32(xpath_arg, timestamp); listnode_add(arguments, data); nb_notification_send(xpath, arguments); } /* * XPath: * /frr-isisd:id-len-mismatch */ void isis_notif_id_len_mismatch(const struct isis_circuit *circuit, uint8_t rcv_id_len, const char *raw_pdu) { const char *xpath = "/frr-isisd:id-len-mismatch"; struct list *arguments = yang_data_list_new(); char xpath_arg[XPATH_MAXLEN]; struct yang_data *data; struct isis_area *area = circuit->area; notif_prep_instance_hdr(xpath, area, "default", arguments); notif_prepr_iface_hdr(xpath, circuit, arguments); snprintf(xpath_arg, sizeof(xpath_arg), "%s/pdu-field-len", xpath); data = yang_data_new_uint8(xpath_arg, rcv_id_len); listnode_add(arguments, data); snprintf(xpath_arg, sizeof(xpath_arg), "%s/raw-pdu", xpath); data = yang_data_new(xpath_arg, raw_pdu); listnode_add(arguments, data); nb_notification_send(xpath, arguments); } /* * XPath: * /frr-isisd:version-skew */ void isis_notif_version_skew(const struct isis_circuit *circuit, uint8_t version, const char *raw_pdu) { const char *xpath = "/frr-isisd:version-skew"; struct list *arguments = yang_data_list_new(); char xpath_arg[XPATH_MAXLEN]; struct yang_data *data; struct isis_area *area = circuit->area; notif_prep_instance_hdr(xpath, area, "default", arguments); notif_prepr_iface_hdr(xpath, circuit, arguments); snprintf(xpath_arg, sizeof(xpath_arg), "%s/protocol-version", xpath); data = yang_data_new_uint8(xpath_arg, version); listnode_add(arguments, data); snprintf(xpath_arg, sizeof(xpath_arg), "%s/raw-pdu", xpath); data = yang_data_new(xpath_arg, raw_pdu); listnode_add(arguments, data); nb_notification_send(xpath, arguments); } /* * XPath: * /frr-isisd:lsp-error-detected */ void isis_notif_lsp_error(const struct isis_circuit *circuit, const char *lsp_id, const char *raw_pdu, __attribute__((unused)) uint32_t offset, __attribute__((unused)) uint8_t tlv_type) { const char *xpath = "/frr-isisd:lsp-error-detected"; struct list *arguments = yang_data_list_new(); char xpath_arg[XPATH_MAXLEN]; struct yang_data *data; struct isis_area *area = circuit->area; notif_prep_instance_hdr(xpath, area, "default", arguments); notif_prepr_iface_hdr(xpath, circuit, arguments); snprintf(xpath_arg, sizeof(xpath_arg), "%s/lsp-id", xpath); data = yang_data_new_string(xpath_arg, lsp_id); listnode_add(arguments, data); snprintf(xpath_arg, sizeof(xpath_arg), "%s/raw-pdu", xpath); data = yang_data_new(xpath_arg, raw_pdu); listnode_add(arguments, data); /* ignore offset and tlv_type which cannot be set properly */ nb_notification_send(xpath, arguments); } /* * XPath: * /frr-isisd:sequence-number-skipped */ void isis_notif_seqno_skipped(const struct isis_circuit *circuit, const char *lsp_id) { const char *xpath = "/frr-isisd:sequence-number-skipped"; struct list *arguments = yang_data_list_new(); char xpath_arg[XPATH_MAXLEN]; struct yang_data *data; struct isis_area *area = circuit->area; notif_prep_instance_hdr(xpath, area, "default", arguments); notif_prepr_iface_hdr(xpath, circuit, arguments); snprintf(xpath_arg, sizeof(xpath_arg), "%s/lsp-id", xpath); data = yang_data_new_string(xpath_arg, lsp_id); listnode_add(arguments, data); nb_notification_send(xpath, arguments); } /* * XPath: * /frr-isisd:own-lsp-purge */ void isis_notif_own_lsp_purge(const struct isis_circuit *circuit, const char *lsp_id) { const char *xpath = "/frr-isisd:own-lsp-purge"; struct list *arguments = yang_data_list_new(); char xpath_arg[XPATH_MAXLEN]; struct yang_data *data; struct isis_area *area = circuit->area; notif_prep_instance_hdr(xpath, area, "default", arguments); notif_prepr_iface_hdr(xpath, circuit, arguments); snprintf(xpath_arg, sizeof(xpath_arg), "%s/lsp-id", xpath); data = yang_data_new_string(xpath_arg, lsp_id); listnode_add(arguments, data); nb_notification_send(xpath, arguments); } /* clang-format off */ const struct frr_yang_module_info frr_isisd_info = { .name = "frr-isisd", .nodes = { { .xpath = "/frr-isisd:isis/instance", .cbs = { .cli_show = cli_show_router_isis, .create = isis_instance_create, .destroy = isis_instance_destroy, }, .priority = NB_DFLT_PRIORITY - 1, }, { .xpath = "/frr-isisd:isis/instance/is-type", .cbs = { .cli_show = cli_show_isis_is_type, .modify = isis_instance_is_type_modify, }, }, { .xpath = "/frr-isisd:isis/instance/area-address", .cbs = { .cli_show = cli_show_isis_area_address, .create = isis_instance_area_address_create, .destroy = isis_instance_area_address_destroy, }, }, { .xpath = "/frr-isisd:isis/instance/dynamic-hostname", .cbs = { .cli_show = cli_show_isis_dynamic_hostname, .modify = isis_instance_dynamic_hostname_modify, }, }, { .xpath = "/frr-isisd:isis/instance/attached", .cbs = { .cli_show = cli_show_isis_attached, .modify = isis_instance_attached_modify, }, }, { .xpath = "/frr-isisd:isis/instance/overload", .cbs = { .cli_show = cli_show_isis_overload, .modify = isis_instance_overload_modify, }, }, { .xpath = "/frr-isisd:isis/instance/metric-style", .cbs = { .cli_show = cli_show_isis_metric_style, .modify = isis_instance_metric_style_modify, }, }, { .xpath = "/frr-isisd:isis/instance/purge-originator", .cbs = { .cli_show = cli_show_isis_purge_origin, .modify = isis_instance_purge_originator_modify, }, }, { .xpath = "/frr-isisd:isis/instance/lsp/mtu", .cbs = { .cli_show = cli_show_isis_lsp_mtu, .modify = isis_instance_lsp_mtu_modify, }, }, { .xpath = "/frr-isisd:isis/instance/lsp/refresh-interval", .cbs = { .cli_show = cli_show_isis_lsp_ref_interval, }, }, { .xpath = "/frr-isisd:isis/instance/lsp/refresh-interval/level-1", .cbs = { .modify = isis_instance_lsp_refresh_interval_level_1_modify, }, }, { .xpath = "/frr-isisd:isis/instance/lsp/refresh-interval/level-2", .cbs = { .modify = isis_instance_lsp_refresh_interval_level_2_modify, }, }, { .xpath = "/frr-isisd:isis/instance/lsp/maximum-lifetime", .cbs = { .cli_show = cli_show_isis_lsp_max_lifetime, }, }, { .xpath = "/frr-isisd:isis/instance/lsp/maximum-lifetime/level-1", .cbs = { .modify = isis_instance_lsp_maximum_lifetime_level_1_modify, }, }, { .xpath = "/frr-isisd:isis/instance/lsp/maximum-lifetime/level-2", .cbs = { .modify = isis_instance_lsp_maximum_lifetime_level_2_modify, }, }, { .xpath = "/frr-isisd:isis/instance/lsp/generation-interval", .cbs = { .cli_show = cli_show_isis_lsp_gen_interval, }, }, { .xpath = "/frr-isisd:isis/instance/lsp/generation-interval/level-1", .cbs = { .modify = isis_instance_lsp_generation_interval_level_1_modify, }, }, { .xpath = "/frr-isisd:isis/instance/lsp/generation-interval/level-2", .cbs = { .modify = isis_instance_lsp_generation_interval_level_2_modify, }, }, { .xpath = "/frr-isisd:isis/instance/spf/ietf-backoff-delay", .cbs = { .apply_finish = ietf_backoff_delay_apply_finish, .cli_show = cli_show_isis_spf_ietf_backoff, .create = isis_instance_spf_ietf_backoff_delay_create, .destroy = isis_instance_spf_ietf_backoff_delay_destroy, }, }, { .xpath = "/frr-isisd:isis/instance/spf/ietf-backoff-delay/init-delay", .cbs = { .modify = isis_instance_spf_ietf_backoff_delay_init_delay_modify, }, }, { .xpath = "/frr-isisd:isis/instance/spf/ietf-backoff-delay/short-delay", .cbs = { .modify = isis_instance_spf_ietf_backoff_delay_short_delay_modify, }, }, { .xpath = "/frr-isisd:isis/instance/spf/ietf-backoff-delay/long-delay", .cbs = { .modify = isis_instance_spf_ietf_backoff_delay_long_delay_modify, }, }, { .xpath = "/frr-isisd:isis/instance/spf/ietf-backoff-delay/hold-down", .cbs = { .modify = isis_instance_spf_ietf_backoff_delay_hold_down_modify, }, }, { .xpath = "/frr-isisd:isis/instance/spf/ietf-backoff-delay/time-to-learn", .cbs = { .modify = isis_instance_spf_ietf_backoff_delay_time_to_learn_modify, }, }, { .xpath = "/frr-isisd:isis/instance/spf/minimum-interval", .cbs = { .cli_show = cli_show_isis_spf_min_interval, }, }, { .xpath = "/frr-isisd:isis/instance/spf/minimum-interval/level-1", .cbs = { .modify = isis_instance_spf_minimum_interval_level_1_modify, }, }, { .xpath = "/frr-isisd:isis/instance/spf/minimum-interval/level-2", .cbs = { .modify = isis_instance_spf_minimum_interval_level_2_modify, }, }, { .xpath = "/frr-isisd:isis/instance/area-password", .cbs = { .apply_finish = area_password_apply_finish, .cli_show = cli_show_isis_area_pwd, .create = isis_instance_area_password_create, .destroy = isis_instance_area_password_destroy, }, }, { .xpath = "/frr-isisd:isis/instance/area-password/password", .cbs = { .modify = isis_instance_area_password_password_modify, }, }, { .xpath = "/frr-isisd:isis/instance/area-password/password-type", .cbs = { .modify = isis_instance_area_password_password_type_modify, }, }, { .xpath = "/frr-isisd:isis/instance/area-password/authenticate-snp", .cbs = { .modify = isis_instance_area_password_authenticate_snp_modify, }, }, { .xpath = "/frr-isisd:isis/instance/domain-password", .cbs = { .apply_finish = domain_password_apply_finish, .cli_show = cli_show_isis_domain_pwd, .create = isis_instance_domain_password_create, .destroy = isis_instance_domain_password_destroy, }, }, { .xpath = "/frr-isisd:isis/instance/domain-password/password", .cbs = { .modify = isis_instance_domain_password_password_modify, }, }, { .xpath = "/frr-isisd:isis/instance/domain-password/password-type", .cbs = { .modify = isis_instance_domain_password_password_type_modify, }, }, { .xpath = "/frr-isisd:isis/instance/domain-password/authenticate-snp", .cbs = { .modify = isis_instance_domain_password_authenticate_snp_modify, }, }, { .xpath = "/frr-isisd:isis/instance/default-information-originate/ipv4", .cbs = { .apply_finish = default_info_origin_ipv4_apply_finish, .cli_show = cli_show_isis_def_origin_ipv4, .create = isis_instance_default_information_originate_ipv4_create, .destroy = isis_instance_default_information_originate_ipv4_destroy, }, }, { .xpath = "/frr-isisd:isis/instance/default-information-originate/ipv4/always", .cbs = { .modify = isis_instance_default_information_originate_ipv4_always_modify, }, }, { .xpath = "/frr-isisd:isis/instance/default-information-originate/ipv4/route-map", .cbs = { .destroy = isis_instance_default_information_originate_ipv4_route_map_destroy, .modify = isis_instance_default_information_originate_ipv4_route_map_modify, }, }, { .xpath = "/frr-isisd:isis/instance/default-information-originate/ipv4/metric", .cbs = { .modify = isis_instance_default_information_originate_ipv4_metric_modify, }, }, { .xpath = "/frr-isisd:isis/instance/default-information-originate/ipv6", .cbs = { .apply_finish = default_info_origin_ipv6_apply_finish, .cli_show = cli_show_isis_def_origin_ipv6, .create = isis_instance_default_information_originate_ipv6_create, .destroy = isis_instance_default_information_originate_ipv6_destroy, }, }, { .xpath = "/frr-isisd:isis/instance/default-information-originate/ipv6/always", .cbs = { .modify = isis_instance_default_information_originate_ipv6_always_modify, }, }, { .xpath = "/frr-isisd:isis/instance/default-information-originate/ipv6/route-map", .cbs = { .destroy = isis_instance_default_information_originate_ipv6_route_map_destroy, .modify = isis_instance_default_information_originate_ipv6_route_map_modify, }, }, { .xpath = "/frr-isisd:isis/instance/default-information-originate/ipv6/metric", .cbs = { .modify = isis_instance_default_information_originate_ipv6_metric_modify, }, }, { .xpath = "/frr-isisd:isis/instance/redistribute/ipv4", .cbs = { .apply_finish = redistribute_ipv4_apply_finish, .cli_show = cli_show_isis_redistribute_ipv4, .create = isis_instance_redistribute_ipv4_create, .destroy = isis_instance_redistribute_ipv4_destroy, }, }, { .xpath = "/frr-isisd:isis/instance/redistribute/ipv4/route-map", .cbs = { .destroy = isis_instance_redistribute_ipv4_route_map_destroy, .modify = isis_instance_redistribute_ipv4_route_map_modify, }, }, { .xpath = "/frr-isisd:isis/instance/redistribute/ipv4/metric", .cbs = { .modify = isis_instance_redistribute_ipv4_metric_modify, }, }, { .xpath = "/frr-isisd:isis/instance/redistribute/ipv6", .cbs = { .apply_finish = redistribute_ipv6_apply_finish, .cli_show = cli_show_isis_redistribute_ipv6, .create = isis_instance_redistribute_ipv6_create, .destroy = isis_instance_redistribute_ipv6_destroy, }, }, { .xpath = "/frr-isisd:isis/instance/redistribute/ipv6/route-map", .cbs = { .destroy = isis_instance_redistribute_ipv6_route_map_destroy, .modify = isis_instance_redistribute_ipv6_route_map_modify, }, }, { .xpath = "/frr-isisd:isis/instance/redistribute/ipv6/metric", .cbs = { .modify = isis_instance_redistribute_ipv6_metric_modify, }, }, { .xpath = "/frr-isisd:isis/instance/multi-topology/ipv4-multicast", .cbs = { .cli_show = cli_show_isis_mt_ipv4_multicast, .create = isis_instance_multi_topology_ipv4_multicast_create, .destroy = isis_instance_multi_topology_ipv4_multicast_destroy, }, }, { .xpath = "/frr-isisd:isis/instance/multi-topology/ipv4-multicast/overload", .cbs = { .modify = isis_instance_multi_topology_ipv4_multicast_overload_modify, }, }, { .xpath = "/frr-isisd:isis/instance/multi-topology/ipv4-management", .cbs = { .cli_show = cli_show_isis_mt_ipv4_mgmt, .create = isis_instance_multi_topology_ipv4_management_create, .destroy = isis_instance_multi_topology_ipv4_management_destroy, }, }, { .xpath = "/frr-isisd:isis/instance/multi-topology/ipv4-management/overload", .cbs = { .modify = isis_instance_multi_topology_ipv4_management_overload_modify, }, }, { .xpath = "/frr-isisd:isis/instance/multi-topology/ipv6-unicast", .cbs = { .cli_show = cli_show_isis_mt_ipv6_unicast, .create = isis_instance_multi_topology_ipv6_unicast_create, .destroy = isis_instance_multi_topology_ipv6_unicast_destroy, }, }, { .xpath = "/frr-isisd:isis/instance/multi-topology/ipv6-unicast/overload", .cbs = { .modify = isis_instance_multi_topology_ipv6_unicast_overload_modify, }, }, { .xpath = "/frr-isisd:isis/instance/multi-topology/ipv6-multicast", .cbs = { .cli_show = cli_show_isis_mt_ipv6_multicast, .create = isis_instance_multi_topology_ipv6_multicast_create, .destroy = isis_instance_multi_topology_ipv6_multicast_destroy, }, }, { .xpath = "/frr-isisd:isis/instance/multi-topology/ipv6-multicast/overload", .cbs = { .modify = isis_instance_multi_topology_ipv6_multicast_overload_modify, }, }, { .xpath = "/frr-isisd:isis/instance/multi-topology/ipv6-management", .cbs = { .cli_show = cli_show_isis_mt_ipv6_mgmt, .create = isis_instance_multi_topology_ipv6_management_create, .destroy = isis_instance_multi_topology_ipv6_management_destroy, }, }, { .xpath = "/frr-isisd:isis/instance/multi-topology/ipv6-management/overload", .cbs = { .modify = isis_instance_multi_topology_ipv6_management_overload_modify, }, }, { .xpath = "/frr-isisd:isis/instance/multi-topology/ipv6-dstsrc", .cbs = { .cli_show = cli_show_isis_mt_ipv6_dstsrc, .create = isis_instance_multi_topology_ipv6_dstsrc_create, .destroy = isis_instance_multi_topology_ipv6_dstsrc_destroy, }, }, { .xpath = "/frr-isisd:isis/instance/multi-topology/ipv6-dstsrc/overload", .cbs = { .modify = isis_instance_multi_topology_ipv6_dstsrc_overload_modify, }, }, { .xpath = "/frr-isisd:isis/instance/log-adjacency-changes", .cbs = { .cli_show = cli_show_isis_log_adjacency, .modify = isis_instance_log_adjacency_changes_modify, }, }, { .xpath = "/frr-isisd:isis/instance/mpls-te", .cbs = { .cli_show = cli_show_isis_mpls_te, .create = isis_instance_mpls_te_create, .destroy = isis_instance_mpls_te_destroy, }, }, { .xpath = "/frr-isisd:isis/instance/mpls-te/router-address", .cbs = { .cli_show = cli_show_isis_mpls_te_router_addr, .destroy = isis_instance_mpls_te_router_address_destroy, .modify = isis_instance_mpls_te_router_address_modify, }, }, { .xpath = "/frr-interface:lib/interface/frr-isisd:isis", .cbs = { .create = lib_interface_isis_create, .destroy = lib_interface_isis_destroy, }, }, { .xpath = "/frr-interface:lib/interface/frr-isisd:isis/area-tag", .cbs = { .modify = lib_interface_isis_area_tag_modify, }, }, { .xpath = "/frr-interface:lib/interface/frr-isisd:isis/circuit-type", .cbs = { .cli_show = cli_show_ip_isis_circ_type, .modify = lib_interface_isis_circuit_type_modify, }, }, { .xpath = "/frr-interface:lib/interface/frr-isisd:isis/ipv4-routing", .cbs = { .cli_show = cli_show_ip_isis_ipv4, .modify = lib_interface_isis_ipv4_routing_modify, }, }, { .xpath = "/frr-interface:lib/interface/frr-isisd:isis/ipv6-routing", .cbs = { .cli_show = cli_show_ip_isis_ipv6, .modify = lib_interface_isis_ipv6_routing_modify, }, }, { .xpath = "/frr-interface:lib/interface/frr-isisd:isis/bfd-monitoring", .cbs = { .modify = lib_interface_isis_bfd_monitoring_modify, .cli_show = cli_show_ip_isis_bfd_monitoring, } }, { .xpath = "/frr-interface:lib/interface/frr-isisd:isis/csnp-interval", .cbs = { .cli_show = cli_show_ip_isis_csnp_interval, }, }, { .xpath = "/frr-interface:lib/interface/frr-isisd:isis/csnp-interval/level-1", .cbs = { .modify = lib_interface_isis_csnp_interval_level_1_modify, }, }, { .xpath = "/frr-interface:lib/interface/frr-isisd:isis/csnp-interval/level-2", .cbs = { .modify = lib_interface_isis_csnp_interval_level_2_modify, }, }, { .xpath = "/frr-interface:lib/interface/frr-isisd:isis/psnp-interval", .cbs = { .cli_show = cli_show_ip_isis_psnp_interval, }, }, { .xpath = "/frr-interface:lib/interface/frr-isisd:isis/psnp-interval/level-1", .cbs = { .modify = lib_interface_isis_psnp_interval_level_1_modify, }, }, { .xpath = "/frr-interface:lib/interface/frr-isisd:isis/psnp-interval/level-2", .cbs = { .modify = lib_interface_isis_psnp_interval_level_2_modify, }, }, { .xpath = "/frr-interface:lib/interface/frr-isisd:isis/hello/padding", .cbs = { .cli_show = cli_show_ip_isis_hello_padding, .modify = lib_interface_isis_hello_padding_modify, }, }, { .xpath = "/frr-interface:lib/interface/frr-isisd:isis/hello/interval", .cbs = { .cli_show = cli_show_ip_isis_hello_interval, }, }, { .xpath = "/frr-interface:lib/interface/frr-isisd:isis/hello/interval/level-1", .cbs = { .modify = lib_interface_isis_hello_interval_level_1_modify, }, }, { .xpath = "/frr-interface:lib/interface/frr-isisd:isis/hello/interval/level-2", .cbs = { .modify = lib_interface_isis_hello_interval_level_2_modify, }, }, { .xpath = "/frr-interface:lib/interface/frr-isisd:isis/hello/multiplier", .cbs = { .cli_show = cli_show_ip_isis_hello_multi, }, }, { .xpath = "/frr-interface:lib/interface/frr-isisd:isis/hello/multiplier/level-1", .cbs = { .modify = lib_interface_isis_hello_multiplier_level_1_modify, }, }, { .xpath = "/frr-interface:lib/interface/frr-isisd:isis/hello/multiplier/level-2", .cbs = { .modify = lib_interface_isis_hello_multiplier_level_2_modify, }, }, { .xpath = "/frr-interface:lib/interface/frr-isisd:isis/metric", .cbs = { .cli_show = cli_show_ip_isis_metric, }, }, { .xpath = "/frr-interface:lib/interface/frr-isisd:isis/metric/level-1", .cbs = { .modify = lib_interface_isis_metric_level_1_modify, }, }, { .xpath = "/frr-interface:lib/interface/frr-isisd:isis/metric/level-2", .cbs = { .modify = lib_interface_isis_metric_level_2_modify, }, }, { .xpath = "/frr-interface:lib/interface/frr-isisd:isis/priority", .cbs = { .cli_show = cli_show_ip_isis_priority, }, }, { .xpath = "/frr-interface:lib/interface/frr-isisd:isis/priority/level-1", .cbs = { .modify = lib_interface_isis_priority_level_1_modify, }, }, { .xpath = "/frr-interface:lib/interface/frr-isisd:isis/priority/level-2", .cbs = { .modify = lib_interface_isis_priority_level_2_modify, }, }, { .xpath = "/frr-interface:lib/interface/frr-isisd:isis/network-type", .cbs = { .cli_show = cli_show_ip_isis_network_type, .modify = lib_interface_isis_network_type_modify, }, }, { .xpath = "/frr-interface:lib/interface/frr-isisd:isis/passive", .cbs = { .cli_show = cli_show_ip_isis_passive, .modify = lib_interface_isis_passive_modify, }, }, { .xpath = "/frr-interface:lib/interface/frr-isisd:isis/password", .cbs = { .cli_show = cli_show_ip_isis_password, .create = lib_interface_isis_password_create, .destroy = lib_interface_isis_password_destroy, }, }, { .xpath = "/frr-interface:lib/interface/frr-isisd:isis/password/password", .cbs = { .modify = lib_interface_isis_password_password_modify, }, }, { .xpath = "/frr-interface:lib/interface/frr-isisd:isis/password/password-type", .cbs = { .modify = lib_interface_isis_password_password_type_modify, }, }, { .xpath = "/frr-interface:lib/interface/frr-isisd:isis/disable-three-way-handshake", .cbs = { .cli_show = cli_show_ip_isis_threeway_shake, .modify = lib_interface_isis_disable_three_way_handshake_modify, }, }, { .xpath = "/frr-interface:lib/interface/frr-isisd:isis/multi-topology/ipv4-unicast", .cbs = { .cli_show = cli_show_ip_isis_mt_ipv4_unicast, .modify = lib_interface_isis_multi_topology_ipv4_unicast_modify, }, }, { .xpath = "/frr-interface:lib/interface/frr-isisd:isis/multi-topology/ipv4-multicast", .cbs = { .cli_show = cli_show_ip_isis_mt_ipv4_multicast, .modify = lib_interface_isis_multi_topology_ipv4_multicast_modify, }, }, { .xpath = "/frr-interface:lib/interface/frr-isisd:isis/multi-topology/ipv4-management", .cbs = { .cli_show = cli_show_ip_isis_mt_ipv4_mgmt, .modify = lib_interface_isis_multi_topology_ipv4_management_modify, }, }, { .xpath = "/frr-interface:lib/interface/frr-isisd:isis/multi-topology/ipv6-unicast", .cbs = { .cli_show = cli_show_ip_isis_mt_ipv6_unicast, .modify = lib_interface_isis_multi_topology_ipv6_unicast_modify, }, }, { .xpath = "/frr-interface:lib/interface/frr-isisd:isis/multi-topology/ipv6-multicast", .cbs = { .cli_show = cli_show_ip_isis_mt_ipv6_multicast, .modify = lib_interface_isis_multi_topology_ipv6_multicast_modify, }, }, { .xpath = "/frr-interface:lib/interface/frr-isisd:isis/multi-topology/ipv6-management", .cbs = { .cli_show = cli_show_ip_isis_mt_ipv6_mgmt, .modify = lib_interface_isis_multi_topology_ipv6_management_modify, }, }, { .xpath = "/frr-interface:lib/interface/frr-isisd:isis/multi-topology/ipv6-dstsrc", .cbs = { .cli_show = cli_show_ip_isis_mt_ipv6_dstsrc, .modify = lib_interface_isis_multi_topology_ipv6_dstsrc_modify, }, }, { .xpath = NULL, }, } }; frr-7.2.1/isisd/isis_pdu.c0000644000000000000000000021224413610377563012317 00000000000000/* * IS-IS Rout(e)ing protocol - isis_pdu.c * PDU processing * * Copyright (C) 2001,2002 Sampo Saaristo * Tampere University of Technology * Institute of Communications Engineering * * This program is free software; you can redistribute it and/or modify it * under the terms of the GNU General Public Licenseas 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; see the file COPYING; if not, write to the Free Software * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA */ #include #include "memory.h" #include "thread.h" #include "linklist.h" #include "log.h" #include "stream.h" #include "vty.h" #include "hash.h" #include "prefix.h" #include "if.h" #include "checksum.h" #include "md5.h" #include "lib_errors.h" #include "isisd/isis_constants.h" #include "isisd/isis_common.h" #include "isisd/isis_flags.h" #include "isisd/isis_adjacency.h" #include "isisd/isis_circuit.h" #include "isisd/isis_network.h" #include "isisd/isis_misc.h" #include "isisd/isis_dr.h" #include "isisd/isisd.h" #include "isisd/isis_dynhn.h" #include "isisd/isis_lsp.h" #include "isisd/isis_pdu.h" #include "isisd/iso_checksum.h" #include "isisd/isis_csm.h" #include "isisd/isis_events.h" #include "isisd/isis_te.h" #include "isisd/isis_mt.h" #include "isisd/isis_tlvs.h" #include "isisd/isis_errors.h" #include "isisd/fabricd.h" #include "isisd/isis_tx_queue.h" #include "isisd/isis_pdu_counter.h" static int ack_lsp(struct isis_lsp_hdr *hdr, struct isis_circuit *circuit, int level) { unsigned long lenp; int retval; uint16_t length; uint8_t pdu_type = (level == IS_LEVEL_1) ? L1_PARTIAL_SEQ_NUM : L2_PARTIAL_SEQ_NUM; isis_circuit_stream(circuit, &circuit->snd_stream); fill_fixed_hdr(pdu_type, circuit->snd_stream); lenp = stream_get_endp(circuit->snd_stream); stream_putw(circuit->snd_stream, 0); /* PDU length */ stream_put(circuit->snd_stream, isis->sysid, ISIS_SYS_ID_LEN); stream_putc(circuit->snd_stream, circuit->idx); stream_putc(circuit->snd_stream, 9); /* code */ stream_putc(circuit->snd_stream, 16); /* len */ stream_putw(circuit->snd_stream, hdr->rem_lifetime); stream_put(circuit->snd_stream, hdr->lsp_id, ISIS_SYS_ID_LEN + 2); stream_putl(circuit->snd_stream, hdr->seqno); stream_putw(circuit->snd_stream, hdr->checksum); length = (uint16_t)stream_get_endp(circuit->snd_stream); /* Update PDU length */ stream_putw_at(circuit->snd_stream, lenp, length); pdu_counter_count(circuit->area->pdu_tx_counters, pdu_type); retval = circuit->tx(circuit, level); if (retval != ISIS_OK) flog_err(EC_ISIS_PACKET, "ISIS-Upd (%s): Send L%d LSP PSNP on %s failed", circuit->area->area_tag, level, circuit->interface->name); return retval; } /* * RECEIVE SIDE */ struct iih_info { struct isis_circuit *circuit; uint8_t *ssnpa; int level; uint8_t circ_type; uint8_t sys_id[ISIS_SYS_ID_LEN]; uint16_t holdtime; uint16_t pdu_len; uint8_t circuit_id; uint8_t priority; uint8_t dis[ISIS_SYS_ID_LEN + 1]; bool v4_usable; bool v6_usable; struct isis_tlvs *tlvs; }; static int process_p2p_hello(struct iih_info *iih) { struct isis_threeway_adj *tw_adj = iih->tlvs->threeway_adj; if (tw_adj) { if (tw_adj->state > ISIS_THREEWAY_DOWN) { if (isis->debugs & DEBUG_ADJ_PACKETS) { zlog_debug("ISIS-Adj (%s): Rcvd P2P IIH from (%s) with invalid three-way state: %d", iih->circuit->area->area_tag, iih->circuit->interface->name, tw_adj->state); } return ISIS_WARNING; } if (tw_adj->neighbor_set && (memcmp(tw_adj->neighbor_id, isis->sysid, ISIS_SYS_ID_LEN) || tw_adj->neighbor_circuit_id != (uint32_t) iih->circuit->idx)) { if (isis->debugs & DEBUG_ADJ_PACKETS) { zlog_debug("ISIS-Adj (%s): Rcvd P2P IIH from (%s) which lists IS/Circuit different from us as neighbor.", iih->circuit->area->area_tag, iih->circuit->interface->name); } return ISIS_WARNING; } } /* * My interpertation of the ISO, if no adj exists we will create one for * the circuit */ struct isis_adjacency *adj = iih->circuit->u.p2p.neighbor; /* If an adjacency exists, check it is with the source of the hello * packets */ if (adj) { if (memcmp(iih->sys_id, adj->sysid, ISIS_SYS_ID_LEN)) { zlog_debug( "hello source and adjacency do not match, set adj down\n"); isis_adj_state_change(adj, ISIS_ADJ_DOWN, "adj do not exist"); return ISIS_OK; } } if (!adj || adj->level != iih->circ_type) { if (!adj) { adj = isis_new_adj(iih->sys_id, NULL, iih->circ_type, iih->circuit); } else { adj->level = iih->circ_type; } iih->circuit->u.p2p.neighbor = adj; /* Build lsp with the new neighbor entry when a new * adjacency is formed. Set adjacency circuit type to * IIH PDU header circuit type before lsp is regenerated * when an adjacency is up. This will result in the new * adjacency entry getting added to the lsp tlv neighbor list. */ adj->circuit_t = iih->circ_type; isis_adj_state_change(adj, ISIS_ADJ_INITIALIZING, NULL); adj->sys_type = ISIS_SYSTYPE_UNKNOWN; } if (tw_adj) adj->ext_circuit_id = tw_adj->local_circuit_id; /* 8.2.6 Monitoring point-to-point adjacencies */ adj->hold_time = iih->holdtime; adj->last_upd = time(NULL); bool changed; isis_tlvs_to_adj(iih->tlvs, adj, &changed); changed |= tlvs_to_adj_mt_set(iih->tlvs, iih->v4_usable, iih->v6_usable, adj); /* Update MPLS TE Remote IP address parameter if possible */ if (IS_MPLS_TE(iih->circuit->area->mta) && IS_MPLS_TE(iih->circuit->mtc) && adj->ipv4_address_count) set_circuitparams_rmt_ipaddr(iih->circuit->mtc, adj->ipv4_addresses[0]); /* lets take care of the expiry */ THREAD_TIMER_OFF(adj->t_expire); thread_add_timer(master, isis_adj_expire, adj, (long)adj->hold_time, &adj->t_expire); /* While fabricds initial sync is in progress, ignore hellos from other * interfaces than the one we are performing the initial sync on. */ if (fabricd_initial_sync_is_in_progress(iih->circuit->area) && fabricd_initial_sync_circuit(iih->circuit->area) != iih->circuit) return ISIS_OK; /* 8.2.5.2 a) a match was detected */ if (isis_tlvs_area_addresses_match(iih->tlvs, iih->circuit->area->area_addrs)) { /* 8.2.5.2 a) 2) If the system is L1 - table 5 */ if (iih->circuit->area->is_type == IS_LEVEL_1) { switch (iih->circ_type) { case IS_LEVEL_1: case IS_LEVEL_1_AND_2: if (adj->adj_state != ISIS_ADJ_UP || adj->adj_usage == ISIS_ADJ_LEVEL1) { isis_adj_process_threeway(adj, tw_adj, ISIS_ADJ_LEVEL1); } break; case IS_LEVEL_2: if (adj->adj_state != ISIS_ADJ_UP) { /* (7) reject - wrong system type event */ zlog_warn("wrongSystemType"); return ISIS_WARNING; } else if (adj->adj_usage == ISIS_ADJ_LEVEL1) { /* (6) down - wrong system */ isis_adj_state_change(adj, ISIS_ADJ_DOWN, "Wrong System"); } break; } } /* 8.2.5.2 a) 3) If the system is L1L2 - table 6 */ if (iih->circuit->area->is_type == IS_LEVEL_1_AND_2) { switch (iih->circ_type) { case IS_LEVEL_1: if (adj->adj_state != ISIS_ADJ_UP || adj->adj_usage == ISIS_ADJ_LEVEL1) { isis_adj_process_threeway(adj, tw_adj, ISIS_ADJ_LEVEL1); } else if ((adj->adj_usage == ISIS_ADJ_LEVEL1AND2) || (adj->adj_usage == ISIS_ADJ_LEVEL2)) { /* (8) down - wrong system */ isis_adj_state_change(adj, ISIS_ADJ_DOWN, "Wrong System"); } break; case IS_LEVEL_2: if (adj->adj_state != ISIS_ADJ_UP || adj->adj_usage == ISIS_ADJ_LEVEL2) { isis_adj_process_threeway(adj, tw_adj, ISIS_ADJ_LEVEL2); } else if ((adj->adj_usage == ISIS_ADJ_LEVEL1) || (adj->adj_usage == ISIS_ADJ_LEVEL1AND2)) { /* (8) down - wrong system */ isis_adj_state_change(adj, ISIS_ADJ_DOWN, "Wrong System"); } break; case IS_LEVEL_1_AND_2: if (adj->adj_state != ISIS_ADJ_UP || adj->adj_usage == ISIS_ADJ_LEVEL1AND2) { isis_adj_process_threeway(adj, tw_adj, ISIS_ADJ_LEVEL1AND2); } else if ((adj->adj_usage == ISIS_ADJ_LEVEL1) || (adj->adj_usage == ISIS_ADJ_LEVEL2)) { /* (8) down - wrong system */ isis_adj_state_change(adj, ISIS_ADJ_DOWN, "Wrong System"); } break; } } /* 8.2.5.2 a) 4) If the system is L2 - table 7 */ if (iih->circuit->area->is_type == IS_LEVEL_2) { switch (iih->circ_type) { case IS_LEVEL_1: if (adj->adj_state != ISIS_ADJ_UP) { /* (5) reject - wrong system type event */ zlog_warn("wrongSystemType"); return ISIS_WARNING; } else if ((adj->adj_usage == ISIS_ADJ_LEVEL1AND2) || (adj->adj_usage == ISIS_ADJ_LEVEL2)) { /* (6) down - wrong system */ isis_adj_state_change(adj, ISIS_ADJ_DOWN, "Wrong System"); } break; case IS_LEVEL_1_AND_2: case IS_LEVEL_2: if (adj->adj_state != ISIS_ADJ_UP || adj->adj_usage == ISIS_ADJ_LEVEL2) { isis_adj_process_threeway(adj, tw_adj, ISIS_ADJ_LEVEL2); } else if (adj->adj_usage == ISIS_ADJ_LEVEL1AND2) { /* (6) down - wrong system */ isis_adj_state_change(adj, ISIS_ADJ_DOWN, "Wrong System"); } break; } } } /* 8.2.5.2 b) if no match was detected */ else if (listcount(iih->circuit->area->area_addrs) > 0) { if (iih->circuit->area->is_type == IS_LEVEL_1) { /* 8.2.5.2 b) 1) is_type L1 and adj is not up */ if (adj->adj_state != ISIS_ADJ_UP) { isis_adj_state_change(adj, ISIS_ADJ_DOWN, "Area Mismatch"); /* 8.2.5.2 b) 2)is_type L1 and adj is up */ } else { isis_adj_state_change(adj, ISIS_ADJ_DOWN, "Down - Area Mismatch"); } } /* 8.2.5.2 b 3 If the system is L2 or L1L2 - table 8 */ else { switch (iih->circ_type) { case IS_LEVEL_1: if (adj->adj_state != ISIS_ADJ_UP) { /* (6) reject - Area Mismatch event */ zlog_warn("AreaMismatch"); return ISIS_WARNING; } else if (adj->adj_usage == ISIS_ADJ_LEVEL1) { /* (7) down - area mismatch */ isis_adj_state_change(adj, ISIS_ADJ_DOWN, "Area Mismatch"); } else if ((adj->adj_usage == ISIS_ADJ_LEVEL1AND2) || (adj->adj_usage == ISIS_ADJ_LEVEL2)) { /* (7) down - wrong system */ isis_adj_state_change(adj, ISIS_ADJ_DOWN, "Wrong System"); } break; case IS_LEVEL_1_AND_2: case IS_LEVEL_2: if (adj->adj_state != ISIS_ADJ_UP || adj->adj_usage == ISIS_ADJ_LEVEL2) { isis_adj_process_threeway(adj, tw_adj, ISIS_ADJ_LEVEL2); } else if (adj->adj_usage == ISIS_ADJ_LEVEL1) { /* (7) down - wrong system */ isis_adj_state_change(adj, ISIS_ADJ_DOWN, "Wrong System"); } else if (adj->adj_usage == ISIS_ADJ_LEVEL1AND2) { if (iih->circ_type == IS_LEVEL_2) { /* (7) down - wrong system */ isis_adj_state_change( adj, ISIS_ADJ_DOWN, "Wrong System"); } else { /* (7) down - area mismatch */ isis_adj_state_change( adj, ISIS_ADJ_DOWN, "Area Mismatch"); } } break; } } } else { /* down - area mismatch */ isis_adj_state_change(adj, ISIS_ADJ_DOWN, "Area Mismatch"); } if (adj->adj_state == ISIS_ADJ_UP && changed) { lsp_regenerate_schedule(adj->circuit->area, isis_adj_usage2levels(adj->adj_usage), 0); } /* 8.2.5.2 c) if the action was up - comparing circuit IDs */ /* FIXME - Missing parts */ /* some of my own understanding of the ISO, why the heck does * it not say what should I change the system_type to... */ switch (adj->adj_usage) { case ISIS_ADJ_LEVEL1: adj->sys_type = ISIS_SYSTYPE_L1_IS; break; case ISIS_ADJ_LEVEL2: adj->sys_type = ISIS_SYSTYPE_L2_IS; break; case ISIS_ADJ_LEVEL1AND2: adj->sys_type = ISIS_SYSTYPE_L2_IS; break; case ISIS_ADJ_NONE: adj->sys_type = ISIS_SYSTYPE_UNKNOWN; break; } if (isis->debugs & DEBUG_ADJ_PACKETS) { zlog_debug( "ISIS-Adj (%s): Rcvd P2P IIH from (%s), cir type %s," " cir id %hhu, length %" PRIu16, iih->circuit->area->area_tag, iih->circuit->interface->name, circuit_t2string(iih->circuit->is_type), iih->circuit->circuit_id, iih->pdu_len); } return ISIS_OK; } static int process_lan_hello(struct iih_info *iih) { struct isis_adjacency *adj; adj = isis_adj_lookup(iih->sys_id, iih->circuit->u.bc.adjdb[iih->level - 1]); if ((adj == NULL) || (memcmp(adj->snpa, iih->ssnpa, ETH_ALEN)) || (adj->level != iih->level)) { if (!adj) { /* Do as in 8.4.2.5 */ adj = isis_new_adj(iih->sys_id, iih->ssnpa, iih->level, iih->circuit); } else { if (iih->ssnpa) { memcpy(adj->snpa, iih->ssnpa, 6); } else { memset(adj->snpa, ' ', 6); } adj->level = iih->level; } isis_adj_state_change(adj, ISIS_ADJ_INITIALIZING, NULL); if (iih->level == IS_LEVEL_1) adj->sys_type = ISIS_SYSTYPE_L1_IS; else adj->sys_type = ISIS_SYSTYPE_L2_IS; list_delete_all_node( iih->circuit->u.bc.lan_neighs[iih->level - 1]); isis_adj_build_neigh_list( iih->circuit->u.bc.adjdb[iih->level - 1], iih->circuit->u.bc.lan_neighs[iih->level - 1]); } if (adj->dis_record[iih->level - 1].dis == ISIS_IS_DIS) { uint8_t *dis = (iih->level == 1) ? iih->circuit->u.bc.l1_desig_is : iih->circuit->u.bc.l2_desig_is; if (memcmp(dis, iih->dis, ISIS_SYS_ID_LEN + 1)) { thread_add_event(master, isis_event_dis_status_change, iih->circuit, 0, NULL); memcpy(dis, iih->dis, ISIS_SYS_ID_LEN + 1); } } adj->circuit_t = iih->circ_type; adj->hold_time = iih->holdtime; adj->last_upd = time(NULL); adj->prio[iih->level - 1] = iih->priority; memcpy(adj->lanid, iih->dis, ISIS_SYS_ID_LEN + 1); bool changed; isis_tlvs_to_adj(iih->tlvs, adj, &changed); changed |= tlvs_to_adj_mt_set(iih->tlvs, iih->v4_usable, iih->v6_usable, adj); /* lets take care of the expiry */ THREAD_TIMER_OFF(adj->t_expire); thread_add_timer(master, isis_adj_expire, adj, (long)adj->hold_time, &adj->t_expire); /* * If the snpa for this circuit is found from LAN Neighbours TLV * we have two-way communication -> adjacency can be put to state "up" */ bool own_snpa_found = isis_tlvs_own_snpa_found(iih->tlvs, iih->circuit->u.bc.snpa); if (adj->adj_state != ISIS_ADJ_UP) { if (own_snpa_found) { isis_adj_state_change( adj, ISIS_ADJ_UP, "own SNPA found in LAN Neighbours TLV"); } } else { if (!own_snpa_found) { isis_adj_state_change( adj, ISIS_ADJ_INITIALIZING, "own SNPA not found in LAN Neighbours TLV"); } } if (adj->adj_state == ISIS_ADJ_UP && changed) lsp_regenerate_schedule(adj->circuit->area, iih->level, 0); if (isis->debugs & DEBUG_ADJ_PACKETS) { zlog_debug( "ISIS-Adj (%s): Rcvd L%d LAN IIH from %s on %s, cirType %s, cirID %u, length %zd", iih->circuit->area->area_tag, iih->level, snpa_print(iih->ssnpa), iih->circuit->interface->name, circuit_t2string(iih->circuit->is_type), iih->circuit->circuit_id, stream_get_endp(iih->circuit->rcv_stream)); } return ISIS_OK; } static int pdu_len_validate(uint16_t pdu_len, struct isis_circuit *circuit) { if (pdu_len < stream_get_getp(circuit->rcv_stream) || pdu_len > ISO_MTU(circuit) || pdu_len > stream_get_endp(circuit->rcv_stream)) return 1; if (pdu_len < stream_get_endp(circuit->rcv_stream)) stream_set_endp(circuit->rcv_stream, pdu_len); return 0; } static int process_hello(uint8_t pdu_type, struct isis_circuit *circuit, uint8_t *ssnpa) { /* keep a copy of the raw pdu for NB notifications */ size_t pdu_start = stream_get_getp(circuit->rcv_stream); size_t pdu_end = stream_get_endp(circuit->rcv_stream); char raw_pdu[pdu_end - pdu_start]; bool p2p_hello = (pdu_type == P2P_HELLO); int level = p2p_hello ? 0 : (pdu_type == L1_LAN_HELLO) ? ISIS_LEVEL1 : ISIS_LEVEL2; const char *pdu_name = p2p_hello ? "P2P IIH" : (level == ISIS_LEVEL1) ? "L1 LAN IIH" : "L2 LAN IIH"; stream_get_from(raw_pdu, circuit->rcv_stream, pdu_start, pdu_end - pdu_start); if (isis->debugs & DEBUG_ADJ_PACKETS) { zlog_debug("ISIS-Adj (%s): Rcvd %s on %s, cirType %s, cirID %u", circuit->area->area_tag, pdu_name, circuit->interface->name, circuit_t2string(circuit->is_type), circuit->circuit_id); if (isis->debugs & DEBUG_PACKET_DUMP) zlog_dump_data(STREAM_DATA(circuit->rcv_stream), stream_get_endp(circuit->rcv_stream)); } if (p2p_hello) { if (circuit->circ_type != CIRCUIT_T_P2P) { zlog_warn("p2p hello on non p2p circuit"); #ifndef FABRICD isis_notif_reject_adjacency( circuit, "p2p hello on non p2p circuit", raw_pdu); #endif /* ifndef FABRICD */ return ISIS_WARNING; } } else { if (circuit->circ_type != CIRCUIT_T_BROADCAST) { zlog_warn("lan hello on non broadcast circuit"); #ifndef FABRICD isis_notif_reject_adjacency( circuit, "lan hello on non broadcast circuit", raw_pdu); #endif /* ifndef FABRICD */ return ISIS_WARNING; } if (circuit->ext_domain) { zlog_debug( "level %d LAN Hello received over circuit with externalDomain = true", level); #ifndef FABRICD isis_notif_reject_adjacency( circuit, "LAN Hello received over circuit with externalDomain = true", raw_pdu); #endif /* ifndef FABRICD */ return ISIS_WARNING; } if (!(circuit->is_type & level)) { if (isis->debugs & DEBUG_ADJ_PACKETS) { zlog_debug( "ISIS-Adj (%s): Interface level mismatch, %s", circuit->area->area_tag, circuit->interface->name); } #ifndef FABRICD isis_notif_reject_adjacency( circuit, "Interface level mismatch", raw_pdu); #endif /* ifndef FABRICD */ return ISIS_WARNING; } } struct iih_info iih = { .circuit = circuit, .ssnpa = ssnpa, .level = level}; /* Generic IIH Header */ iih.circ_type = stream_getc(circuit->rcv_stream) & 0x03; stream_get(iih.sys_id, circuit->rcv_stream, ISIS_SYS_ID_LEN); iih.holdtime = stream_getw(circuit->rcv_stream); iih.pdu_len = stream_getw(circuit->rcv_stream); if (p2p_hello) { iih.circuit_id = stream_getc(circuit->rcv_stream); } else { iih.priority = stream_getc(circuit->rcv_stream); stream_get(iih.dis, circuit->rcv_stream, ISIS_SYS_ID_LEN + 1); } if (pdu_len_validate(iih.pdu_len, circuit)) { zlog_warn( "ISIS-Adj (%s): Rcvd %s from (%s) with invalid pdu length %" PRIu16, circuit->area->area_tag, pdu_name, circuit->interface->name, iih.pdu_len); #ifndef FABRICD isis_notif_reject_adjacency(circuit, "Invalid PDU length", raw_pdu); #endif /* ifndef FABRICD */ return ISIS_WARNING; } if (!p2p_hello && !(level & iih.circ_type)) { flog_err(EC_ISIS_PACKET, "Level %d LAN Hello with Circuit Type %d", level, iih.circ_type); #ifndef FABRICD isis_notif_reject_adjacency( circuit, "LAN Hello with wrong IS-level", raw_pdu); #endif /* ifndef FABRICD */ return ISIS_ERROR; } const char *error_log; int retval = ISIS_WARNING; if (isis_unpack_tlvs(STREAM_READABLE(circuit->rcv_stream), circuit->rcv_stream, &iih.tlvs, &error_log)) { zlog_warn("isis_unpack_tlvs() failed: %s", error_log); #ifndef FABRICD isis_notif_reject_adjacency(circuit, "Failed to unpack TLVs", raw_pdu); #endif /* ifndef FABRICD */ goto out; } if (!iih.tlvs->area_addresses.count) { zlog_warn("No Area addresses TLV in %s", pdu_name); #ifndef FABRICD /* send northbound notification */ isis_notif_area_mismatch(circuit, raw_pdu); #endif /* ifndef FABRICD */ goto out; } if (!iih.tlvs->protocols_supported.count) { zlog_warn("No supported protocols TLV in %s", pdu_name); #ifndef FABRICD isis_notif_reject_adjacency( circuit, "No supported protocols TLV", raw_pdu); #endif /* ifndef FABRICD */ goto out; } int auth_code = isis_tlvs_auth_is_valid(iih.tlvs, &circuit->passwd, circuit->rcv_stream, false); if (auth_code != ISIS_AUTH_OK) { isis_event_auth_failure(circuit->area->area_tag, "IIH authentication failure", iih.sys_id); #ifndef FABRICD /* send northbound notification */ stream_get_from(raw_pdu, circuit->rcv_stream, pdu_start, pdu_end - pdu_start); if (auth_code == ISIS_AUTH_FAILURE) isis_notif_authentication_failure(circuit, raw_pdu); else /* AUTH_TYPE_FAILURE or NO_VALIDATOR */ isis_notif_authentication_type_failure(circuit, raw_pdu); #endif /* ifndef FABRICD */ goto out; } if (!memcmp(iih.sys_id, isis->sysid, ISIS_SYS_ID_LEN)) { zlog_warn( "ISIS-Adj (%s): Received IIH with own sysid - discard", circuit->area->area_tag); #ifndef FABRICD isis_notif_reject_adjacency( circuit, "Received IIH with our own sysid", raw_pdu); #endif /* ifndef FABRICD */ goto out; } if (!p2p_hello && (listcount(circuit->area->area_addrs) == 0 || (level == ISIS_LEVEL1 && !isis_tlvs_area_addresses_match( iih.tlvs, circuit->area->area_addrs)))) { if (isis->debugs & DEBUG_ADJ_PACKETS) { zlog_debug( "ISIS-Adj (%s): Area mismatch, level %d IIH on %s", circuit->area->area_tag, level, circuit->interface->name); } #ifndef FABRICD /* send northbound notification */ isis_notif_area_mismatch(circuit, raw_pdu); #endif /* ifndef FABRICD */ goto out; } iih.v4_usable = (fabricd_ip_addrs(circuit) && iih.tlvs->ipv4_address.count); iih.v6_usable = (circuit->ipv6_link && listcount(circuit->ipv6_link) && iih.tlvs->ipv6_address.count); if (!iih.v4_usable && !iih.v6_usable) { if (isis->debugs & DEBUG_ADJ_PACKETS) { zlog_warn( "ISIS-Adj (%s): Neither IPv4 nor IPv6 considered usable. Ignoring IIH", circuit->area->area_tag); } #ifndef FABRICD isis_notif_reject_adjacency( circuit, "Neither IPv4 not IPv6 considered usable", raw_pdu); #endif /* ifndef FABRICD */ goto out; } retval = p2p_hello ? process_p2p_hello(&iih) : process_lan_hello(&iih); out: isis_free_tlvs(iih.tlvs); return retval; } static void lsp_flood_or_update(struct isis_lsp *lsp, struct isis_circuit *circuit, bool circuit_scoped) { if (!circuit_scoped) lsp_flood(lsp, circuit); else fabricd_update_lsp_no_flood(lsp, circuit); } /* * Process Level 1/2 Link State * ISO - 10589 * Section 7.3.15.1 - Action on receipt of a link state PDU */ static int process_lsp(uint8_t pdu_type, struct isis_circuit *circuit, const uint8_t *ssnpa, uint8_t max_area_addrs) { int level; bool circuit_scoped; size_t pdu_start = stream_get_getp(circuit->rcv_stream); size_t pdu_end = stream_get_endp(circuit->rcv_stream); char raw_pdu[pdu_end - pdu_start]; stream_get_from(raw_pdu, circuit->rcv_stream, pdu_start, pdu_end - pdu_start); if (pdu_type == FS_LINK_STATE) { if (!fabricd) return ISIS_ERROR; if (max_area_addrs != L2_CIRCUIT_FLOODING_SCOPE) return ISIS_ERROR; level = ISIS_LEVEL2; circuit_scoped = true; /* The stream is used verbatim for sending out new LSPDUs. * So make sure we store it as an L2 LSPDU internally. * (compare for the reverse in `send_lsp`) */ stream_putc_at(circuit->rcv_stream, 4, L2_LINK_STATE); stream_putc_at(circuit->rcv_stream, 7, 0); } else { if (pdu_type == L1_LINK_STATE) level = ISIS_LEVEL1; else level = ISIS_LEVEL2; circuit_scoped = false; } if (isis->debugs & DEBUG_UPDATE_PACKETS) { zlog_debug( "ISIS-Upd (%s): Rcvd %sL%d LSP on %s, cirType %s, cirID %u", circuit->area->area_tag, circuit_scoped ? "Circuit scoped " : "", level, circuit->interface->name, circuit_t2string(circuit->is_type), circuit->circuit_id); if (isis->debugs & DEBUG_PACKET_DUMP) zlog_dump_data(STREAM_DATA(circuit->rcv_stream), stream_get_endp(circuit->rcv_stream)); } struct isis_lsp_hdr hdr = {}; hdr.pdu_len = stream_getw(circuit->rcv_stream); hdr.rem_lifetime = stream_getw(circuit->rcv_stream); stream_get(hdr.lsp_id, circuit->rcv_stream, sizeof(hdr.lsp_id)); hdr.seqno = stream_getl(circuit->rcv_stream); hdr.checksum = stream_getw(circuit->rcv_stream); hdr.lsp_bits = stream_getc(circuit->rcv_stream); #ifndef FABRICD /* send northbound notification */ isis_notif_lsp_received(circuit, rawlspid_print(hdr.lsp_id), hdr.seqno, time(NULL), sysid_print(hdr.lsp_id)); #endif /* ifndef FABRICD */ if (pdu_len_validate(hdr.pdu_len, circuit)) { zlog_debug("ISIS-Upd (%s): LSP %s invalid LSP length %" PRIu16, circuit->area->area_tag, rawlspid_print(hdr.lsp_id), hdr.pdu_len); return ISIS_WARNING; } if (isis->debugs & DEBUG_UPDATE_PACKETS) { zlog_debug("ISIS-Upd (%s): Rcvd L%d LSP %s, seq 0x%08" PRIx32 ", cksum 0x%04" PRIx16 ", lifetime %" PRIu16 "s, len %" PRIu16 ", on %s", circuit->area->area_tag, level, rawlspid_print(hdr.lsp_id), hdr.seqno, hdr.checksum, hdr.rem_lifetime, hdr.pdu_len, circuit->interface->name); } /* lsp is_type check */ if ((hdr.lsp_bits & IS_LEVEL_1) != IS_LEVEL_1) { zlog_debug( "ISIS-Upd (%s): LSP %s invalid LSP is type 0x%" PRIx8, circuit->area->area_tag, rawlspid_print(hdr.lsp_id), hdr.lsp_bits & IS_LEVEL_1_AND_2); /* continue as per RFC1122 Be liberal in what you accept, and * conservative in what you send */ } /* Checksum sanity check - FIXME: move to correct place */ /* 12 = sysid+pdu+remtime */ if (iso_csum_verify(STREAM_DATA(circuit->rcv_stream) + 12, hdr.pdu_len - 12, hdr.checksum, 12)) { zlog_debug( "ISIS-Upd (%s): LSP %s invalid LSP checksum 0x%04" PRIx16, circuit->area->area_tag, rawlspid_print(hdr.lsp_id), hdr.checksum); return ISIS_WARNING; } /* 7.3.15.1 a) 1 - external domain circuit will discard lsps */ if (circuit->ext_domain) { zlog_debug( "ISIS-Upd (%s): LSP %s received at level %d over circuit with " "externalDomain = true", circuit->area->area_tag, rawlspid_print(hdr.lsp_id), level); return ISIS_WARNING; } /* 7.3.15.1 a) 2,3 - manualL2OnlyMode not implemented */ if (!(circuit->is_type & level)) { zlog_debug( "ISIS-Upd (%s): LSP %s received at level %d over circuit of" " type %s", circuit->area->area_tag, rawlspid_print(hdr.lsp_id), level, circuit_t2string(circuit->is_type)); return ISIS_WARNING; } struct isis_tlvs *tlvs = NULL; int retval = ISIS_WARNING; const char *error_log; if (isis_unpack_tlvs(STREAM_READABLE(circuit->rcv_stream), circuit->rcv_stream, &tlvs, &error_log)) { zlog_warn("Something went wrong unpacking the LSP: %s", error_log); #ifndef FABRICD /* send northbound notification. Note that the tlv-type and * offset cannot correctly be set here as they are not returned * by isis_unpack_tlvs, but in there I cannot fire a * notification because I have no circuit information. So until * we change the code above to return those extra fields, we * will send dummy values which are ignored in the callback */ isis_notif_lsp_error(circuit, rawlspid_print(hdr.lsp_id), raw_pdu, 0, 0); #endif /* ifndef FABRICD */ goto out; } /* 7.3.15.1 a) 4 - need to make sure IDLength matches */ /* 7.3.15.1 a) 5 - maximum area match, can be ommited since we only use * 3 */ /* 7.3.15.1 a) 7 - password check */ struct isis_passwd *passwd = (level == ISIS_LEVEL1) ? &circuit->area->area_passwd : &circuit->area->domain_passwd; int auth_code = isis_tlvs_auth_is_valid(tlvs, passwd, circuit->rcv_stream, true); if (auth_code != ISIS_AUTH_OK) { isis_event_auth_failure(circuit->area->area_tag, "LSP authentication failure", hdr.lsp_id); #ifndef FABRICD /* send northbound notification */ if (auth_code == ISIS_AUTH_FAILURE) isis_notif_authentication_failure(circuit, raw_pdu); else /* AUTH_TYPE_FAILURE or NO_VALIDATOR */ isis_notif_authentication_type_failure(circuit, raw_pdu); #endif /* ifndef FABRICD */ goto out; } /* Find the LSP in our database and compare it to this Link State header */ struct isis_lsp *lsp = lsp_search(&circuit->area->lspdb[level - 1], hdr.lsp_id); int comp = 0; if (lsp) comp = lsp_compare(circuit->area->area_tag, lsp, hdr.seqno, hdr.checksum, hdr.rem_lifetime); if (lsp && (lsp->own_lsp)) goto dontcheckadj; /* 7.3.15.1 a) 6 - Must check that we have an adjacency of the same * level */ /* for broadcast circuits, snpa should be compared */ if (circuit->circ_type == CIRCUIT_T_BROADCAST) { if (!isis_adj_lookup_snpa(ssnpa, circuit->u.bc.adjdb[level - 1])) { zlog_debug("(%s): DS ======= LSP %s, seq 0x%08" PRIx32 ", cksum 0x%04" PRIx16 ", lifetime %" PRIu16 "s on %s", circuit->area->area_tag, rawlspid_print(hdr.lsp_id), hdr.seqno, hdr.checksum, hdr.rem_lifetime, circuit->interface->name); goto out; /* Silently discard */ } } /* for non broadcast, we just need to find same level adj */ else { /* If no adj, or no sharing of level */ if (!circuit->u.p2p.neighbor) { retval = ISIS_OK; goto out; } else { if (((level == IS_LEVEL_1) && (circuit->u.p2p.neighbor->adj_usage == ISIS_ADJ_LEVEL2)) || ((level == IS_LEVEL_2) && (circuit->u.p2p.neighbor->adj_usage == ISIS_ADJ_LEVEL1))) goto out; } } bool lsp_confusion; dontcheckadj: /* 7.3.15.1 a) 7 - Passwords for level 1 - not implemented */ /* 7.3.15.1 a) 8 - Passwords for level 2 - not implemented */ /* 7.3.15.1 a) 9 - OriginatingLSPBufferSize - not implemented FIXME: do * it */ /* 7.3.16.2 - If this is an LSP from another IS with identical seq_num * but * wrong checksum, initiate a purge. */ if (lsp && (lsp->hdr.seqno == hdr.seqno) && (lsp->hdr.checksum != hdr.checksum) && hdr.rem_lifetime) { zlog_warn("ISIS-Upd (%s): LSP %s seq 0x%08" PRIx32 " with confused checksum received.", circuit->area->area_tag, rawlspid_print(hdr.lsp_id), hdr.seqno); hdr.rem_lifetime = 0; lsp_confusion = true; } else lsp_confusion = false; /* 7.3.15.1 b) - If the remaining life time is 0, we perform 7.3.16.4 */ if (hdr.rem_lifetime == 0) { if (!lsp) { /* 7.3.16.4 a) 1) No LSP in db -> send an ack, but don't * save */ /* only needed on explicit update, eg - p2p */ if (circuit->circ_type == CIRCUIT_T_P2P) ack_lsp(&hdr, circuit, level); goto out; /* FIXME: do we need a purge? */ } else { if (memcmp(hdr.lsp_id, isis->sysid, ISIS_SYS_ID_LEN)) { /* LSP by some other system -> do 7.3.16.4 b) */ /* 7.3.16.4 b) 1) */ if (comp == LSP_NEWER) { lsp_update(lsp, &hdr, tlvs, circuit->rcv_stream, circuit->area, level, lsp_confusion); tlvs = NULL; /* ii */ lsp_flood_or_update(lsp, NULL, circuit_scoped); /* v */ ISIS_FLAGS_CLEAR_ALL( lsp->SSNflags); /* FIXME: OTHER than c */ /* For the case of lsp confusion, flood * the purge back to its * originator so that it can react. * Otherwise, don't reflood * through incoming circuit as usual */ if (!lsp_confusion) { isis_tx_queue_del( circuit->tx_queue, lsp); /* iv */ if (circuit->circ_type != CIRCUIT_T_BROADCAST) ISIS_SET_FLAG( lsp->SSNflags, circuit); } } /* 7.3.16.4 b) 2) */ else if (comp == LSP_EQUAL) { /* i */ isis_tx_queue_del(circuit->tx_queue, lsp); /* ii */ if (circuit->circ_type != CIRCUIT_T_BROADCAST) ISIS_SET_FLAG(lsp->SSNflags, circuit); } /* 7.3.16.4 b) 3) */ else { isis_tx_queue_add(circuit->tx_queue, lsp, TX_LSP_NORMAL); ISIS_CLEAR_FLAG(lsp->SSNflags, circuit); } } else if (lsp->hdr.rem_lifetime != 0) { /* our own LSP -> 7.3.16.4 c) */ if (comp == LSP_NEWER) { #ifndef FABRICD if (lsp->hdr.seqno < hdr.seqno) { /* send northbound * notification */ isis_notif_seqno_skipped( circuit, rawlspid_print( hdr.lsp_id)); } #endif /* ifndef FABRICD */ lsp_inc_seqno(lsp, hdr.seqno); lsp_flood_or_update(lsp, NULL, circuit_scoped); } else { isis_tx_queue_add(circuit->tx_queue, lsp, TX_LSP_NORMAL); ISIS_CLEAR_FLAG(lsp->SSNflags, circuit); } if (isis->debugs & DEBUG_UPDATE_PACKETS) zlog_debug( "ISIS-Upd (%s): (1) " "re-originating LSP %s new seq " "0x%08" PRIx32, circuit->area->area_tag, rawlspid_print(hdr.lsp_id), lsp->hdr.seqno); } else { /* our own LSP with 0 remaining life time */ #ifndef FABRICD /* send northbound notification */ isis_notif_own_lsp_purge( circuit, rawlspid_print(hdr.lsp_id)); #endif /* ifndef FABRICD */ } } goto out; } /* 7.3.15.1 c) - If this is our own lsp and we don't have it initiate a * purge */ if (memcmp(hdr.lsp_id, isis->sysid, ISIS_SYS_ID_LEN) == 0) { if (!lsp) { /* 7.3.16.4: initiate a purge */ lsp_purge_non_exist(level, &hdr, circuit->area); retval = ISIS_OK; goto out; } /* 7.3.15.1 d) - If this is our own lsp and we have it */ /* In 7.3.16.1, If an Intermediate system R somewhere in the * domain * has information that the current sequence number for source S * is * "greater" than that held by S, ... */ if (comp == LSP_NEWER) { /* 7.3.16.1 */ lsp_inc_seqno(lsp, hdr.seqno); #ifndef FABRICD /* send northbound notification */ isis_notif_seqno_skipped(circuit, rawlspid_print(hdr.lsp_id)); #endif /* ifndef FABRICD */ if (isis->debugs & DEBUG_UPDATE_PACKETS) { zlog_debug( "ISIS-Upd (%s): (2) re-originating LSP %s new seq 0x%08" PRIx32, circuit->area->area_tag, rawlspid_print(hdr.lsp_id), lsp->hdr.seqno); } lsp_flood(lsp, NULL); } else if (comp == LSP_EQUAL) { isis_tx_queue_del(circuit->tx_queue, lsp); if (circuit->circ_type != CIRCUIT_T_BROADCAST) ISIS_SET_FLAG(lsp->SSNflags, circuit); } else { isis_tx_queue_add(circuit->tx_queue, lsp, TX_LSP_NORMAL); ISIS_CLEAR_FLAG(lsp->SSNflags, circuit); } } else { /* 7.3.15.1 e) - This lsp originated on another system */ /* 7.3.15.1 e) 1) LSP newer than the one in db or no LSP in db */ if ((!lsp || comp == LSP_NEWER)) { /* * If this lsp is a frag, need to see if we have zero * lsp present */ struct isis_lsp *lsp0 = NULL; if (LSP_FRAGMENT(hdr.lsp_id) != 0) { uint8_t lspid[ISIS_SYS_ID_LEN + 2]; memcpy(lspid, hdr.lsp_id, ISIS_SYS_ID_LEN + 1); LSP_FRAGMENT(lspid) = 0; lsp0 = lsp_search( &circuit->area->lspdb[level - 1], lspid); if (!lsp0) { zlog_debug( "Got lsp frag, while zero lsp not in database"); goto out; } } /* i */ if (!lsp) { lsp = lsp_new_from_recv( &hdr, tlvs, circuit->rcv_stream, lsp0, circuit->area, level); tlvs = NULL; lsp_insert(&circuit->area->lspdb[level - 1], lsp); } else /* exists, so we overwrite */ { lsp_update(lsp, &hdr, tlvs, circuit->rcv_stream, circuit->area, level, false); tlvs = NULL; } lsp_flood_or_update(lsp, circuit, circuit_scoped); /* iv */ if (circuit->circ_type != CIRCUIT_T_BROADCAST) ISIS_SET_FLAG(lsp->SSNflags, circuit); /* FIXME: v) */ } /* 7.3.15.1 e) 2) LSP equal to the one in db */ else if (comp == LSP_EQUAL) { isis_tx_queue_del(circuit->tx_queue, lsp); lsp_update(lsp, &hdr, tlvs, circuit->rcv_stream, circuit->area, level, false); tlvs = NULL; if (circuit->circ_type != CIRCUIT_T_BROADCAST) ISIS_SET_FLAG(lsp->SSNflags, circuit); } /* 7.3.15.1 e) 3) LSP older than the one in db */ else { isis_tx_queue_add(circuit->tx_queue, lsp, TX_LSP_NORMAL); ISIS_CLEAR_FLAG(lsp->SSNflags, circuit); } } retval = ISIS_OK; out: fabricd_trigger_csnp(circuit->area, circuit_scoped); isis_free_tlvs(tlvs); return retval; } /* * Process Sequence Numbers * ISO - 10589 * Section 7.3.15.2 - Action on receipt of a sequence numbers PDU */ static int process_snp(uint8_t pdu_type, struct isis_circuit *circuit, const uint8_t *ssnpa) { #ifndef FABRICD size_t pdu_start = stream_get_getp(circuit->rcv_stream); size_t pdu_end = stream_get_endp(circuit->rcv_stream); char raw_pdu[pdu_end - pdu_start]; #endif /* ifndef FABRICD */ bool is_csnp = (pdu_type == L1_COMPLETE_SEQ_NUM || pdu_type == L2_COMPLETE_SEQ_NUM); char typechar = is_csnp ? 'C' : 'P'; int level = (pdu_type == L1_COMPLETE_SEQ_NUM || pdu_type == L1_PARTIAL_SEQ_NUM) ? ISIS_LEVEL1 : ISIS_LEVEL2; uint16_t pdu_len = stream_getw(circuit->rcv_stream); uint8_t rem_sys_id[ISIS_SYS_ID_LEN]; stream_get(rem_sys_id, circuit->rcv_stream, ISIS_SYS_ID_LEN); stream_forward_getp(circuit->rcv_stream, 1); /* Circuit ID - unused */ uint8_t start_lsp_id[ISIS_SYS_ID_LEN + 2] = {}; uint8_t stop_lsp_id[ISIS_SYS_ID_LEN + 2] = {}; if (is_csnp) { stream_get(start_lsp_id, circuit->rcv_stream, ISIS_SYS_ID_LEN + 2); stream_get(stop_lsp_id, circuit->rcv_stream, ISIS_SYS_ID_LEN + 2); } if (pdu_len_validate(pdu_len, circuit)) { zlog_warn("Received a CSNP with bogus length %d", pdu_len); return ISIS_WARNING; } if (isis->debugs & DEBUG_SNP_PACKETS) { zlog_debug( "ISIS-Snp (%s): Rcvd L%d %cSNP on %s, cirType %s, cirID %u", circuit->area->area_tag, level, typechar, circuit->interface->name, circuit_t2string(circuit->is_type), circuit->circuit_id); if (isis->debugs & DEBUG_PACKET_DUMP) zlog_dump_data(STREAM_DATA(circuit->rcv_stream), stream_get_endp(circuit->rcv_stream)); } /* 7.3.15.2 a) 1 - external domain circuit will discard snp pdu */ if (circuit->ext_domain) { zlog_debug( "ISIS-Snp (%s): Rcvd L%d %cSNP on %s, " "skipping: circuit externalDomain = true", circuit->area->area_tag, level, typechar, circuit->interface->name); return ISIS_OK; } /* 7.3.15.2 a) 2,3 - manualL2OnlyMode not implemented */ if (!(circuit->is_type & level)) { zlog_debug( "ISIS-Snp (%s): Rcvd L%d %cSNP on %s, " "skipping: circuit type %s does not match level %d", circuit->area->area_tag, level, typechar, circuit->interface->name, circuit_t2string(circuit->is_type), level); return ISIS_OK; } /* 7.3.15.2 a) 4 - not applicable for CSNP only PSNPs on broadcast */ if (!is_csnp && (circuit->circ_type == CIRCUIT_T_BROADCAST) && !circuit->u.bc.is_dr[level - 1]) { zlog_debug( "ISIS-Snp (%s): Rcvd L%d %cSNP from %s on %s, " "skipping: we are not the DIS", circuit->area->area_tag, level, typechar, snpa_print(ssnpa), circuit->interface->name); return ISIS_OK; } /* 7.3.15.2 a) 5 - need to make sure IDLength matches - already checked */ /* 7.3.15.2 a) 6 - maximum area match, can be ommited since we only use * 3 * - already checked */ /* 7.3.15.2 a) 7 - Must check that we have an adjacency of the same * level */ /* for broadcast circuits, snpa should be compared */ /* FIXME : Do we need to check SNPA? */ if (circuit->circ_type == CIRCUIT_T_BROADCAST) { if (!isis_adj_lookup(rem_sys_id, circuit->u.bc.adjdb[level - 1])) return ISIS_OK; /* Silently discard */ } else { if (!fabricd && !circuit->u.p2p.neighbor) { zlog_warn("no p2p neighbor on circuit %s", circuit->interface->name); return ISIS_OK; /* Silently discard */ } } struct isis_tlvs *tlvs; int retval = ISIS_WARNING; const char *error_log; if (isis_unpack_tlvs(STREAM_READABLE(circuit->rcv_stream), circuit->rcv_stream, &tlvs, &error_log)) { zlog_warn("Something went wrong unpacking the SNP: %s", error_log); goto out; } struct isis_passwd *passwd = (level == IS_LEVEL_1) ? &circuit->area->area_passwd : &circuit->area->domain_passwd; if (CHECK_FLAG(passwd->snp_auth, SNP_AUTH_RECV)) { int auth_code = isis_tlvs_auth_is_valid( tlvs, passwd, circuit->rcv_stream, false); if (auth_code != ISIS_AUTH_OK) { isis_event_auth_failure(circuit->area->area_tag, "SNP authentication failure", rem_sys_id); #ifndef FABRICD /* send northbound notification */ stream_get_from(raw_pdu, circuit->rcv_stream, pdu_start, pdu_end - pdu_start); if (auth_code == ISIS_AUTH_FAILURE) isis_notif_authentication_failure(circuit, raw_pdu); else /* AUTH_TYPE_FAILURE or NO_VALIDATOR */ isis_notif_authentication_type_failure(circuit, raw_pdu); #endif /* ifndef FABRICD */ goto out; } } struct isis_lsp_entry *entry_head = (struct isis_lsp_entry *)tlvs->lsp_entries.head; /* debug isis snp-packets */ if (isis->debugs & DEBUG_SNP_PACKETS) { zlog_debug("ISIS-Snp (%s): Rcvd L%d %cSNP from %s on %s", circuit->area->area_tag, level, typechar, snpa_print(ssnpa), circuit->interface->name); for (struct isis_lsp_entry *entry = entry_head; entry; entry = entry->next) { zlog_debug( "ISIS-Snp (%s): %cSNP entry %s, seq 0x%08" PRIx32 ", cksum 0x%04" PRIx16 ", lifetime %" PRIu16 "s", circuit->area->area_tag, typechar, rawlspid_print(entry->id), entry->seqno, entry->checksum, entry->rem_lifetime); } } bool resync_needed = false; /* 7.3.15.2 b) Actions on LSP_ENTRIES reported */ for (struct isis_lsp_entry *entry = entry_head; entry; entry = entry->next) { struct isis_lsp *lsp = lsp_search(&circuit->area->lspdb[level - 1], entry->id); bool own_lsp = !memcmp(entry->id, isis->sysid, ISIS_SYS_ID_LEN); if (lsp) { /* 7.3.15.2 b) 1) is this LSP newer */ int cmp = lsp_compare(circuit->area->area_tag, lsp, entry->seqno, entry->checksum, entry->rem_lifetime); /* 7.3.15.2 b) 2) if it equals, clear SRM on p2p */ if (cmp == LSP_EQUAL) { /* if (circuit->circ_type != * CIRCUIT_T_BROADCAST) */ isis_tx_queue_del(circuit->tx_queue, lsp); } /* 7.3.15.2 b) 3) if it is older, clear SSN and set SRM */ else if (cmp == LSP_OLDER) { ISIS_CLEAR_FLAG(lsp->SSNflags, circuit); isis_tx_queue_add(circuit->tx_queue, lsp, TX_LSP_NORMAL); } /* 7.3.15.2 b) 4) if it is newer, set SSN and clear SRM on p2p */ else { if (own_lsp) { lsp_inc_seqno(lsp, entry->seqno); isis_tx_queue_add(circuit->tx_queue, lsp, TX_LSP_NORMAL); } else { ISIS_SET_FLAG(lsp->SSNflags, circuit); /* if (circuit->circ_type != * CIRCUIT_T_BROADCAST) */ isis_tx_queue_del(circuit->tx_queue, lsp); resync_needed = true; } } } else { /* 7.3.15.2 b) 5) if it was not found, and all of those * are not 0, * insert it and set SSN on it */ if (entry->rem_lifetime && entry->checksum && entry->seqno && memcmp(entry->id, isis->sysid, ISIS_SYS_ID_LEN)) { struct isis_lsp *lsp0 = NULL; if (LSP_FRAGMENT(entry->id)) { uint8_t lspid[ISIS_SYS_ID_LEN + 2]; memcpy(lspid, entry->id, ISIS_SYS_ID_LEN + 1); LSP_FRAGMENT(lspid) = 0; lsp0 = lsp_search( &circuit->area->lspdb[level - 1], lspid); if (!lsp0) { zlog_debug("Got lsp frag in snp, while zero not in database"); continue; } } lsp = lsp_new(circuit->area, entry->id, entry->rem_lifetime, 0, 0, entry->checksum, lsp0, level); lsp_insert(&circuit->area->lspdb[level - 1], lsp); lsp_set_all_srmflags(lsp, false); ISIS_SET_FLAG(lsp->SSNflags, circuit); resync_needed = true; } } } /* 7.3.15.2 c) on CSNP set SRM for all in range which were not reported */ if (is_csnp) { /* * Build a list from our own LSP db bounded with * start_lsp_id and stop_lsp_id */ struct list *lsp_list = list_new(); lsp_build_list_nonzero_ht(&circuit->area->lspdb[level - 1], start_lsp_id, stop_lsp_id, lsp_list); /* Fixme: Find a better solution */ struct listnode *node, *nnode; struct isis_lsp *lsp; for (struct isis_lsp_entry *entry = entry_head; entry; entry = entry->next) { for (ALL_LIST_ELEMENTS(lsp_list, node, nnode, lsp)) { if (lsp_id_cmp(lsp->hdr.lsp_id, entry->id) == 0) { list_delete_node(lsp_list, node); break; } } } /* on remaining LSPs we set SRM (neighbor knew not of) */ for (ALL_LIST_ELEMENTS_RO(lsp_list, node, lsp)) { isis_tx_queue_add(circuit->tx_queue, lsp, TX_LSP_NORMAL); resync_needed = true; } /* lets free it */ list_delete(&lsp_list); } if (fabricd_initial_sync_is_complete(circuit->area) && resync_needed) zlog_warn("OpenFabric: Needed to resync LSPDB using CSNP!"); retval = ISIS_OK; out: isis_free_tlvs(tlvs); return retval; } static int pdu_size(uint8_t pdu_type, uint8_t *size) { switch (pdu_type) { case L1_LAN_HELLO: case L2_LAN_HELLO: *size = ISIS_LANHELLO_HDRLEN; break; case P2P_HELLO: *size = ISIS_P2PHELLO_HDRLEN; break; case L1_LINK_STATE: case L2_LINK_STATE: case FS_LINK_STATE: *size = ISIS_LSP_HDR_LEN; break; case L1_COMPLETE_SEQ_NUM: case L2_COMPLETE_SEQ_NUM: *size = ISIS_CSNP_HDRLEN; break; case L1_PARTIAL_SEQ_NUM: case L2_PARTIAL_SEQ_NUM: *size = ISIS_PSNP_HDRLEN; break; default: return 1; } *size += ISIS_FIXED_HDR_LEN; return 0; } /* * PDU Dispatcher */ int isis_handle_pdu(struct isis_circuit *circuit, uint8_t *ssnpa) { int retval = ISIS_OK; size_t pdu_start = stream_get_getp(circuit->rcv_stream); size_t pdu_end = stream_get_endp(circuit->rcv_stream); char raw_pdu[pdu_end - pdu_start]; stream_get_from(raw_pdu, circuit->rcv_stream, pdu_start, pdu_end - pdu_start); /* Verify that at least the 8 bytes fixed header have been received */ if (stream_get_endp(circuit->rcv_stream) < ISIS_FIXED_HDR_LEN) { flog_err(EC_ISIS_PACKET, "PDU is too short to be IS-IS."); return ISIS_ERROR; } uint8_t idrp = stream_getc(circuit->rcv_stream); uint8_t length = stream_getc(circuit->rcv_stream); uint8_t version1 = stream_getc(circuit->rcv_stream); uint8_t id_len = stream_getc(circuit->rcv_stream); uint8_t pdu_type = stream_getc(circuit->rcv_stream) & 0x1f; /* bits 6-8 are reserved */ uint8_t version2 = stream_getc(circuit->rcv_stream); stream_forward_getp(circuit->rcv_stream, 1); /* reserved */ uint8_t max_area_addrs = stream_getc(circuit->rcv_stream); pdu_counter_count(circuit->area->pdu_rx_counters, pdu_type); if (idrp == ISO9542_ESIS) { flog_err(EC_LIB_DEVELOPMENT, "No support for ES-IS packet IDRP=%" PRIx8, idrp); return ISIS_ERROR; } if (idrp != ISO10589_ISIS) { flog_err(EC_ISIS_PACKET, "Not an IS-IS packet IDRP=%" PRIx8, idrp); return ISIS_ERROR; } if (version1 != 1) { zlog_warn("Unsupported ISIS version %" PRIu8, version1); #ifndef FABRICD /* send northbound notification */ isis_notif_version_skew(circuit, version1, raw_pdu); #endif /* ifndef FABRICD */ return ISIS_WARNING; } if (id_len != 0 && id_len != ISIS_SYS_ID_LEN) { flog_err( EC_ISIS_PACKET, "IDFieldLengthMismatch: ID Length field in a received PDU %" PRIu8 ", while the parameter for this IS is %u", id_len, ISIS_SYS_ID_LEN); #ifndef FABRICD /* send northbound notification */ isis_notif_id_len_mismatch(circuit, id_len, raw_pdu); #endif /* ifndef FABRICD */ return ISIS_ERROR; } uint8_t expected_length; if (pdu_size(pdu_type, &expected_length)) { zlog_warn("Unsupported ISIS PDU %" PRIu8, pdu_type); return ISIS_WARNING; } if (length != expected_length) { flog_err(EC_ISIS_PACKET, "Exepected fixed header length = %" PRIu8 " but got %" PRIu8, expected_length, length); return ISIS_ERROR; } if (stream_get_endp(circuit->rcv_stream) < length) { flog_err( EC_ISIS_PACKET, "PDU is too short to contain fixed header of given PDU type."); return ISIS_ERROR; } if (version2 != 1) { zlog_warn("Unsupported ISIS PDU version %" PRIu8, version2); #ifndef FABRICD /* send northbound notification */ isis_notif_version_skew(circuit, version2, raw_pdu); #endif /* ifndef FABRICD */ return ISIS_WARNING; } if (circuit->is_passive) { zlog_warn("Received ISIS PDU on passive circuit %s", circuit->interface->name); return ISIS_WARNING; } /* either 3 or 0 */ if (pdu_type != FS_LINK_STATE /* FS PDU doesn't contain max area addr field */ && max_area_addrs != 0 && max_area_addrs != isis->max_area_addrs) { flog_err( EC_ISIS_PACKET, "maximumAreaAddressesMismatch: maximumAreaAdresses in a received PDU %" PRIu8 " while the parameter for this IS is %u", max_area_addrs, isis->max_area_addrs); #ifndef FABRICD /* send northbound notification */ isis_notif_max_area_addr_mismatch(circuit, max_area_addrs, raw_pdu); #endif /* ifndef FABRICD */ return ISIS_ERROR; } switch (pdu_type) { case L1_LAN_HELLO: case L2_LAN_HELLO: case P2P_HELLO: if (fabricd && pdu_type != P2P_HELLO) return ISIS_ERROR; retval = process_hello(pdu_type, circuit, ssnpa); break; case L1_LINK_STATE: case L2_LINK_STATE: case FS_LINK_STATE: if (fabricd && pdu_type != L2_LINK_STATE && pdu_type != FS_LINK_STATE) return ISIS_ERROR; retval = process_lsp(pdu_type, circuit, ssnpa, max_area_addrs); break; case L1_COMPLETE_SEQ_NUM: case L2_COMPLETE_SEQ_NUM: case L1_PARTIAL_SEQ_NUM: case L2_PARTIAL_SEQ_NUM: retval = process_snp(pdu_type, circuit, ssnpa); break; default: return ISIS_ERROR; } return retval; } int isis_receive(struct thread *thread) { struct isis_circuit *circuit; uint8_t ssnpa[ETH_ALEN]; int retval; /* * Get the circuit */ circuit = THREAD_ARG(thread); assert(circuit); circuit->t_read = NULL; isis_circuit_stream(circuit, &circuit->rcv_stream); retval = circuit->rx(circuit, ssnpa); #if ISIS_METHOD != ISIS_METHOD_BPF if (retval == ISIS_OK) retval = isis_handle_pdu(circuit, ssnpa); #endif //ISIS_METHOD != ISIS_METHOD_BPF /* * prepare for next packet. */ if (!circuit->is_passive) isis_circuit_prepare(circuit); return retval; } /* * SEND SIDE */ void fill_fixed_hdr(uint8_t pdu_type, struct stream *stream) { uint8_t length; if (pdu_size(pdu_type, &length)) assert(!"Unknown PDU Type"); stream_putc(stream, ISO10589_ISIS); /* IDRP */ stream_putc(stream, length); /* Length of fixed header */ stream_putc(stream, 1); /* Version/Protocol ID Extension 1 */ stream_putc(stream, 0); /* ID Length, 0 => 6 */ stream_putc(stream, pdu_type); stream_putc(stream, 1); /* Subversion */ stream_putc(stream, 0); /* Reserved */ stream_putc(stream, 0); /* Max Area Addresses 0 => 3 */ } static uint8_t hello_pdu_type(struct isis_circuit *circuit, int level) { if (circuit->circ_type == CIRCUIT_T_BROADCAST) return (level == IS_LEVEL_1) ? L1_LAN_HELLO : L2_LAN_HELLO; else return P2P_HELLO; } static void put_hello_hdr(struct isis_circuit *circuit, int level, size_t *len_pointer) { uint8_t pdu_type = hello_pdu_type(circuit, level); isis_circuit_stream(circuit, &circuit->snd_stream); fill_fixed_hdr(pdu_type, circuit->snd_stream); stream_putc(circuit->snd_stream, circuit->is_type); stream_put(circuit->snd_stream, circuit->area->isis->sysid, ISIS_SYS_ID_LEN); uint32_t holdtime = circuit->hello_multiplier[level - 1] * circuit->hello_interval[level - 1]; if (holdtime > 0xffff) holdtime = 0xffff; stream_putw(circuit->snd_stream, holdtime); *len_pointer = stream_get_endp(circuit->snd_stream); stream_putw(circuit->snd_stream, 0); /* length is filled in later */ if (circuit->circ_type == CIRCUIT_T_BROADCAST) { uint8_t *desig_is = (level == IS_LEVEL_1) ? circuit->u.bc.l1_desig_is : circuit->u.bc.l2_desig_is; stream_putc(circuit->snd_stream, circuit->priority[level - 1]); stream_put(circuit->snd_stream, desig_is, ISIS_SYS_ID_LEN + 1); } else { stream_putc(circuit->snd_stream, circuit->circuit_id); } } int send_hello(struct isis_circuit *circuit, int level) { size_t len_pointer; int retval; if (circuit->is_passive) return ISIS_OK; if (circuit->interface->mtu == 0) { zlog_warn("circuit has zero MTU"); return ISIS_WARNING; } put_hello_hdr(circuit, level, &len_pointer); struct isis_tlvs *tlvs = isis_alloc_tlvs(); isis_tlvs_add_auth(tlvs, &circuit->passwd); if (!listcount(circuit->area->area_addrs)) { isis_free_tlvs(tlvs); return ISIS_WARNING; } isis_tlvs_add_area_addresses(tlvs, circuit->area->area_addrs); if (circuit->circ_type == CIRCUIT_T_BROADCAST) { isis_tlvs_add_lan_neighbors( tlvs, circuit->u.bc.lan_neighs[level - 1]); } else if (circuit->circ_type == CIRCUIT_T_P2P && !circuit->disable_threeway_adj) { uint32_t ext_circuit_id = circuit->idx; if (circuit->u.p2p.neighbor) { uint8_t threeway_state; if (fabricd_initial_sync_is_in_progress(circuit->area) && fabricd_initial_sync_circuit(circuit->area) != circuit) threeway_state = ISIS_THREEWAY_DOWN; else threeway_state = circuit->u.p2p.neighbor->threeway_state; isis_tlvs_add_threeway_adj(tlvs, threeway_state, ext_circuit_id, circuit->u.p2p.neighbor->sysid, circuit->u.p2p.neighbor->ext_circuit_id); } else { isis_tlvs_add_threeway_adj(tlvs, ISIS_THREEWAY_DOWN, ext_circuit_id, NULL, 0); } } isis_tlvs_set_protocols_supported(tlvs, &circuit->nlpids); /* * MT Supported TLV * * TLV gets included if no topology is enabled on the interface, * if one topology other than #0 is enabled, or if multiple topologies * are enabled. */ struct isis_circuit_mt_setting **mt_settings; unsigned int mt_count; mt_settings = circuit_mt_settings(circuit, &mt_count); if (mt_count == 0 && area_is_mt(circuit->area)) { tlvs->mt_router_info_empty = true; } else if ((mt_count == 1 && mt_settings[0]->mtid != ISIS_MT_IPV4_UNICAST) || (mt_count > 1)) { for (unsigned int i = 0; i < mt_count; i++) isis_tlvs_add_mt_router_info(tlvs, mt_settings[i]->mtid, false, false); } if (circuit->ip_router) { struct list *circuit_ip_addrs = fabricd_ip_addrs(circuit); if (circuit_ip_addrs) isis_tlvs_add_ipv4_addresses(tlvs, circuit_ip_addrs); } if (circuit->ipv6_router && circuit->ipv6_link) isis_tlvs_add_ipv6_addresses(tlvs, circuit->ipv6_link); if (isis_pack_tlvs(tlvs, circuit->snd_stream, len_pointer, circuit->pad_hellos, false)) { isis_free_tlvs(tlvs); return ISIS_WARNING; /* XXX: Maybe Log TLV structure? */ } if (isis->debugs & DEBUG_ADJ_PACKETS) { if (circuit->circ_type == CIRCUIT_T_BROADCAST) { zlog_debug( "ISIS-Adj (%s): Sending L%d LAN IIH on %s, length %zd", circuit->area->area_tag, level, circuit->interface->name, stream_get_endp(circuit->snd_stream)); } else { zlog_debug( "ISIS-Adj (%s): Sending P2P IIH on %s, length %zd", circuit->area->area_tag, circuit->interface->name, stream_get_endp(circuit->snd_stream)); } if (isis->debugs & DEBUG_PACKET_DUMP) zlog_dump_data(STREAM_DATA(circuit->snd_stream), stream_get_endp(circuit->snd_stream)); } isis_free_tlvs(tlvs); pdu_counter_count(circuit->area->pdu_tx_counters, hello_pdu_type(circuit, level)); retval = circuit->tx(circuit, level); if (retval != ISIS_OK) flog_err(EC_ISIS_PACKET, "ISIS-Adj (%s): Send L%d IIH on %s failed", circuit->area->area_tag, level, circuit->interface->name); return retval; } static int send_hello_cb(struct thread *thread) { struct isis_circuit_arg *arg = THREAD_ARG(thread); assert(arg); struct isis_circuit *circuit = arg->circuit; int level = arg->level; assert(circuit); if (circuit->circ_type == CIRCUIT_T_P2P) { circuit->u.p2p.t_send_p2p_hello = NULL; send_hello(circuit, 1); send_hello_sched(circuit, ISIS_LEVEL1, 1000 * circuit->hello_interval[1]); return ISIS_OK; } if (circuit->circ_type != CIRCUIT_T_BROADCAST) { zlog_warn("ISIS-Hello (%s): Trying to send hello on unknown circuit type %d", circuit->area->area_tag, circuit->circ_type); return ISIS_WARNING; } circuit->u.bc.t_send_lan_hello[level - 1] = NULL; if (!(circuit->is_type & level)) { zlog_warn("ISIS-Hello (%s): Trying to send L%d IIH in L%d-only circuit", circuit->area->area_tag, level, 3 - level); return ISIS_WARNING; } if (circuit->u.bc.run_dr_elect[level - 1]) isis_dr_elect(circuit, level); int rv = send_hello(circuit, level); /* set next timer thread */ send_hello_sched(circuit, level, 1000 * circuit->hello_interval[level - 1]); return rv; } static void _send_hello_sched(struct isis_circuit *circuit, struct thread **threadp, int level, long delay) { if (*threadp) { if (thread_timer_remain_msec(*threadp) < (unsigned long)delay) return; thread_cancel(*threadp); } thread_add_timer_msec(master, send_hello_cb, &circuit->level_arg[level - 1], isis_jitter(delay, IIH_JITTER), threadp); } void send_hello_sched(struct isis_circuit *circuit, int level, long delay) { if (circuit->circ_type == CIRCUIT_T_P2P) { _send_hello_sched(circuit, &circuit->u.p2p.t_send_p2p_hello, ISIS_LEVEL1, delay); return; } if (circuit->circ_type != CIRCUIT_T_BROADCAST) { zlog_warn("%s: encountered unknown circuit type %d on %s", __func__, circuit->circ_type, circuit->interface->name); return; } for (int loop_level = ISIS_LEVEL1; loop_level <= ISIS_LEVEL2; loop_level++) { if (!(loop_level & level)) continue; _send_hello_sched( circuit, &circuit->u.bc.t_send_lan_hello[loop_level - 1], loop_level, delay ); } } /* * Count the maximum number of lsps that can be accomodated by a given size. */ #define LSP_ENTRIES_LEN (10 + ISIS_SYS_ID_LEN) static uint16_t get_max_lsp_count(uint16_t size) { uint16_t tlv_count; uint16_t lsp_count; uint16_t remaining_size; /* First count the full size TLVs */ tlv_count = size / MAX_LSP_ENTRIES_TLV_SIZE; lsp_count = tlv_count * (MAX_LSP_ENTRIES_TLV_SIZE / LSP_ENTRIES_LEN); /* The last TLV, if any */ remaining_size = size % MAX_LSP_ENTRIES_TLV_SIZE; if (remaining_size - 2 >= LSP_ENTRIES_LEN) lsp_count += (remaining_size - 2) / LSP_ENTRIES_LEN; return lsp_count; } int send_csnp(struct isis_circuit *circuit, int level) { if (lspdb_count(&circuit->area->lspdb[level - 1]) == 0) return ISIS_OK; uint8_t pdu_type = (level == ISIS_LEVEL1) ? L1_COMPLETE_SEQ_NUM : L2_COMPLETE_SEQ_NUM; isis_circuit_stream(circuit, &circuit->snd_stream); fill_fixed_hdr(pdu_type, circuit->snd_stream); size_t len_pointer = stream_get_endp(circuit->snd_stream); stream_putw(circuit->snd_stream, 0); stream_put(circuit->snd_stream, isis->sysid, ISIS_SYS_ID_LEN); /* with zero circuit id - ref 9.10, 9.11 */ stream_putc(circuit->snd_stream, 0); size_t start_pointer = stream_get_endp(circuit->snd_stream); stream_put(circuit->snd_stream, 0, ISIS_SYS_ID_LEN + 2); size_t end_pointer = stream_get_endp(circuit->snd_stream); stream_put(circuit->snd_stream, 0, ISIS_SYS_ID_LEN + 2); struct isis_passwd *passwd = (level == ISIS_LEVEL1) ? &circuit->area->area_passwd : &circuit->area->domain_passwd; struct isis_tlvs *tlvs = isis_alloc_tlvs(); if (CHECK_FLAG(passwd->snp_auth, SNP_AUTH_SEND)) isis_tlvs_add_auth(tlvs, passwd); size_t tlv_start = stream_get_endp(circuit->snd_stream); if (isis_pack_tlvs(tlvs, circuit->snd_stream, len_pointer, false, false)) { isis_free_tlvs(tlvs); return ISIS_WARNING; } isis_free_tlvs(tlvs); uint16_t num_lsps = get_max_lsp_count(STREAM_WRITEABLE(circuit->snd_stream)); uint8_t start[ISIS_SYS_ID_LEN + 2]; memset(start, 0x00, ISIS_SYS_ID_LEN + 2); uint8_t stop[ISIS_SYS_ID_LEN + 2]; memset(stop, 0xff, ISIS_SYS_ID_LEN + 2); bool loop = true; while (loop) { tlvs = isis_alloc_tlvs(); if (CHECK_FLAG(passwd->snp_auth, SNP_AUTH_SEND)) isis_tlvs_add_auth(tlvs, passwd); struct isis_lsp *last_lsp; isis_tlvs_add_csnp_entries(tlvs, start, stop, num_lsps, &circuit->area->lspdb[level - 1], &last_lsp); /* * Update the stop lsp_id before encoding this CSNP. */ if (tlvs->lsp_entries.count < num_lsps) { memset(stop, 0xff, ISIS_SYS_ID_LEN + 2); } else { memcpy(stop, last_lsp->hdr.lsp_id, sizeof(stop)); } memcpy(STREAM_DATA(circuit->snd_stream) + start_pointer, start, ISIS_SYS_ID_LEN + 2); memcpy(STREAM_DATA(circuit->snd_stream) + end_pointer, stop, ISIS_SYS_ID_LEN + 2); stream_set_endp(circuit->snd_stream, tlv_start); if (isis_pack_tlvs(tlvs, circuit->snd_stream, len_pointer, false, false)) { isis_free_tlvs(tlvs); return ISIS_WARNING; } if (isis->debugs & DEBUG_SNP_PACKETS) { zlog_debug( "ISIS-Snp (%s): Sending L%d CSNP on %s, length %zd", circuit->area->area_tag, level, circuit->interface->name, stream_get_endp(circuit->snd_stream)); log_multiline(LOG_DEBUG, " ", "%s", isis_format_tlvs(tlvs)); if (isis->debugs & DEBUG_PACKET_DUMP) zlog_dump_data( STREAM_DATA(circuit->snd_stream), stream_get_endp(circuit->snd_stream)); } pdu_counter_count(circuit->area->pdu_tx_counters, pdu_type); int retval = circuit->tx(circuit, level); if (retval != ISIS_OK) { flog_err(EC_ISIS_PACKET, "ISIS-Snp (%s): Send L%d CSNP on %s failed", circuit->area->area_tag, level, circuit->interface->name); isis_free_tlvs(tlvs); return retval; } /* * Start lsp_id of the next CSNP should be one plus the * stop lsp_id in this current CSNP. */ memcpy(start, stop, ISIS_SYS_ID_LEN + 2); loop = false; for (int i = ISIS_SYS_ID_LEN + 1; i >= 0; --i) { if (start[i] < (uint8_t)0xff) { start[i] += 1; loop = true; break; } } memset(stop, 0xff, ISIS_SYS_ID_LEN + 2); isis_free_tlvs(tlvs); } return ISIS_OK; } int send_l1_csnp(struct thread *thread) { struct isis_circuit *circuit; circuit = THREAD_ARG(thread); assert(circuit); circuit->t_send_csnp[0] = NULL; if ((circuit->circ_type == CIRCUIT_T_BROADCAST && circuit->u.bc.is_dr[0]) || circuit->circ_type == CIRCUIT_T_P2P) { send_csnp(circuit, 1); } /* set next timer thread */ thread_add_timer(master, send_l1_csnp, circuit, isis_jitter(circuit->csnp_interval[0], CSNP_JITTER), &circuit->t_send_csnp[0]); return ISIS_OK; } int send_l2_csnp(struct thread *thread) { struct isis_circuit *circuit; circuit = THREAD_ARG(thread); assert(circuit); circuit->t_send_csnp[1] = NULL; if ((circuit->circ_type == CIRCUIT_T_BROADCAST && circuit->u.bc.is_dr[1]) || circuit->circ_type == CIRCUIT_T_P2P) { send_csnp(circuit, 2); } /* set next timer thread */ thread_add_timer(master, send_l2_csnp, circuit, isis_jitter(circuit->csnp_interval[1], CSNP_JITTER), &circuit->t_send_csnp[1]); return ISIS_OK; } /* * 7.3.15.4 action on expiration of partial SNP interval * level 1 */ static int send_psnp(int level, struct isis_circuit *circuit) { if (circuit->circ_type == CIRCUIT_T_BROADCAST && circuit->u.bc.is_dr[level - 1]) return ISIS_OK; if (lspdb_count(&circuit->area->lspdb[level - 1]) == 0) return ISIS_OK; if (!circuit->snd_stream) return ISIS_ERROR; uint8_t pdu_type = (level == ISIS_LEVEL1) ? L1_PARTIAL_SEQ_NUM : L2_PARTIAL_SEQ_NUM; isis_circuit_stream(circuit, &circuit->snd_stream); fill_fixed_hdr(pdu_type, circuit->snd_stream); size_t len_pointer = stream_get_endp(circuit->snd_stream); stream_putw(circuit->snd_stream, 0); /* length is filled in later */ stream_put(circuit->snd_stream, isis->sysid, ISIS_SYS_ID_LEN); stream_putc(circuit->snd_stream, circuit->idx); struct isis_passwd *passwd = (level == ISIS_LEVEL1) ? &circuit->area->area_passwd : &circuit->area->domain_passwd; struct isis_tlvs *tlvs = isis_alloc_tlvs(); if (CHECK_FLAG(passwd->snp_auth, SNP_AUTH_SEND)) isis_tlvs_add_auth(tlvs, passwd); size_t tlv_start = stream_get_endp(circuit->snd_stream); if (isis_pack_tlvs(tlvs, circuit->snd_stream, len_pointer, false, false)) { isis_free_tlvs(tlvs); return ISIS_WARNING; } isis_free_tlvs(tlvs); uint16_t num_lsps = get_max_lsp_count(STREAM_WRITEABLE(circuit->snd_stream)); while (1) { struct isis_lsp *lsp; tlvs = isis_alloc_tlvs(); if (CHECK_FLAG(passwd->snp_auth, SNP_AUTH_SEND)) isis_tlvs_add_auth(tlvs, passwd); frr_each (lspdb, &circuit->area->lspdb[level - 1], lsp) { if (ISIS_CHECK_FLAG(lsp->SSNflags, circuit)) isis_tlvs_add_lsp_entry(tlvs, lsp); if (tlvs->lsp_entries.count == num_lsps) break; } if (!tlvs->lsp_entries.count) { isis_free_tlvs(tlvs); return ISIS_OK; } stream_set_endp(circuit->snd_stream, tlv_start); if (isis_pack_tlvs(tlvs, circuit->snd_stream, len_pointer, false, false)) { isis_free_tlvs(tlvs); return ISIS_WARNING; } if (isis->debugs & DEBUG_SNP_PACKETS) { zlog_debug( "ISIS-Snp (%s): Sending L%d PSNP on %s, length %zd", circuit->area->area_tag, level, circuit->interface->name, stream_get_endp(circuit->snd_stream)); log_multiline(LOG_DEBUG, " ", "%s", isis_format_tlvs(tlvs)); if (isis->debugs & DEBUG_PACKET_DUMP) zlog_dump_data( STREAM_DATA(circuit->snd_stream), stream_get_endp(circuit->snd_stream)); } pdu_counter_count(circuit->area->pdu_tx_counters, pdu_type); int retval = circuit->tx(circuit, level); if (retval != ISIS_OK) { flog_err(EC_ISIS_PACKET, "ISIS-Snp (%s): Send L%d PSNP on %s failed", circuit->area->area_tag, level, circuit->interface->name); isis_free_tlvs(tlvs); return retval; } /* * sending succeeded, we can clear SSN flags of this circuit * for the LSPs in list */ struct isis_lsp_entry *entry_head; entry_head = (struct isis_lsp_entry *)tlvs->lsp_entries.head; for (struct isis_lsp_entry *entry = entry_head; entry; entry = entry->next) ISIS_CLEAR_FLAG(entry->lsp->SSNflags, circuit); isis_free_tlvs(tlvs); } return ISIS_OK; } int send_l1_psnp(struct thread *thread) { struct isis_circuit *circuit; circuit = THREAD_ARG(thread); assert(circuit); circuit->t_send_psnp[0] = NULL; send_psnp(1, circuit); /* set next timer thread */ thread_add_timer(master, send_l1_psnp, circuit, isis_jitter(circuit->psnp_interval[0], PSNP_JITTER), &circuit->t_send_psnp[0]); return ISIS_OK; } /* * 7.3.15.4 action on expiration of partial SNP interval * level 2 */ int send_l2_psnp(struct thread *thread) { struct isis_circuit *circuit; circuit = THREAD_ARG(thread); assert(circuit); circuit->t_send_psnp[1] = NULL; send_psnp(2, circuit); /* set next timer thread */ thread_add_timer(master, send_l2_psnp, circuit, isis_jitter(circuit->psnp_interval[1], PSNP_JITTER), &circuit->t_send_psnp[1]); return ISIS_OK; } /* * ISO 10589 - 7.3.14.3 */ void send_lsp(struct isis_circuit *circuit, struct isis_lsp *lsp, enum isis_tx_type tx_type) { int clear_srm = 1; int retval = ISIS_OK; if (circuit->state != C_STATE_UP || circuit->is_passive == 1) goto out; /* * Do not send if levels do not match */ if (!(lsp->level & circuit->is_type)) goto out; /* * Do not send if we do not have adjacencies in state up on the circuit */ if (circuit->upadjcount[lsp->level - 1] == 0) goto out; /* stream_copy will assert and stop program execution if LSP is larger * than * the circuit's MTU. So handle and log this case here. */ if (stream_get_endp(lsp->pdu) > stream_get_size(circuit->snd_stream)) { flog_err( EC_ISIS_PACKET, "ISIS-Upd (%s): Can't send L%d LSP %s, seq 0x%08" PRIx32 ", cksum 0x%04" PRIx16 ", lifetime %" PRIu16 "s on %s. LSP Size is %zu while interface stream size is %zu.", circuit->area->area_tag, lsp->level, rawlspid_print(lsp->hdr.lsp_id), lsp->hdr.seqno, lsp->hdr.checksum, lsp->hdr.rem_lifetime, circuit->interface->name, stream_get_endp(lsp->pdu), stream_get_size(circuit->snd_stream)); #ifndef FABRICD /* send a northbound notification */ isis_notif_lsp_too_large(circuit, stream_get_endp(lsp->pdu), rawlspid_print(lsp->hdr.lsp_id)); #endif /* ifndef FABRICD */ if (isis->debugs & DEBUG_PACKET_DUMP) zlog_dump_data(STREAM_DATA(lsp->pdu), stream_get_endp(lsp->pdu)); retval = ISIS_ERROR; goto out; } /* copy our lsp to the send buffer */ stream_copy(circuit->snd_stream, lsp->pdu); if (tx_type == TX_LSP_CIRCUIT_SCOPED) { stream_putc_at(circuit->snd_stream, 4, FS_LINK_STATE); stream_putc_at(circuit->snd_stream, 7, L2_CIRCUIT_FLOODING_SCOPE); } if (isis->debugs & DEBUG_UPDATE_PACKETS) { zlog_debug("ISIS-Upd (%s): Sending %sL%d LSP %s, seq 0x%08" PRIx32 ", cksum 0x%04" PRIx16 ", lifetime %" PRIu16 "s on %s", circuit->area->area_tag, (tx_type == TX_LSP_CIRCUIT_SCOPED) ? "Circuit scoped " : "", lsp->level, rawlspid_print(lsp->hdr.lsp_id), lsp->hdr.seqno, lsp->hdr.checksum, lsp->hdr.rem_lifetime, circuit->interface->name); if (isis->debugs & DEBUG_PACKET_DUMP) zlog_dump_data(STREAM_DATA(circuit->snd_stream), stream_get_endp(circuit->snd_stream)); } uint8_t pdu_type = (tx_type == TX_LSP_CIRCUIT_SCOPED) ? FS_LINK_STATE : (lsp->level == ISIS_LEVEL1) ? L1_LINK_STATE : L2_LINK_STATE; clear_srm = 0; pdu_counter_count(circuit->area->pdu_tx_counters, pdu_type); retval = circuit->tx(circuit, lsp->level); if (retval != ISIS_OK) { flog_err(EC_ISIS_PACKET, "ISIS-Upd (%s): Send L%d LSP on %s failed %s", circuit->area->area_tag, lsp->level, circuit->interface->name, (retval == ISIS_WARNING) ? "temporarily" : "permanently"); } out: if (clear_srm || (retval == ISIS_OK && circuit->circ_type == CIRCUIT_T_BROADCAST) || (retval != ISIS_OK && retval != ISIS_WARNING)) { /* SRM flag will trigger retransmission. We will not retransmit * if we * encountered a fatal error. * On success, they should only be cleared if it's a broadcast * circuit. * On a P2P circuit, we will wait for the ack from the neighbor * to clear * the fag. */ isis_tx_queue_del(circuit->tx_queue, lsp); } } frr-7.2.1/isisd/isis_pdu.h0000644000000000000000000002003413610377563012316 00000000000000/* * IS-IS Rout(e)ing protocol - isis_pdu.h * PDU processing * * Copyright (C) 2001,2002 Sampo Saaristo * Tampere University of Technology * Institute of Communications Engineering * * This program is free software; you can redistribute it and/or modify it * under the terms of the GNU General Public Licenseas 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; see the file COPYING; if not, write to the Free Software * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA */ #ifndef _ZEBRA_ISIS_PDU_H #define _ZEBRA_ISIS_PDU_H #include "isisd/isis_tx_queue.h" #ifdef __SUNPRO_C #pragma pack(1) #endif /* * ISO 9542 - 7.5,7.6 * * ES to IS Fixed Header * +-------+-------+-------+-------+-------+-------+-------+-------+ * | Intradomain Routeing Protocol Discriminator | * +-------+-------+-------+-------+-------+-------+-------+-------+ * | Length Indicator | * +-------+-------+-------+-------+-------+-------+-------+-------+ * | Version/Protocol ID extension | * +-------+-------+-------+-------+-------+-------+-------+-------+ * | Reserved = 0 | * +-------+-------+-------+-------+-------+-------+-------+-------+ * | 0 | 0 | 0 | PDU Type | * +-------+-------+-------+-------+-------+-------+-------+-------+ * | Holding Time | 2 * +-------+-------+-------+-------+-------+-------+-------+-------+ * | Checksum | 2 * +-------+-------+-------+-------+-------+-------+-------+-------+ */ struct esis_fixed_hdr { uint8_t idrp; uint8_t length; uint8_t version; uint8_t id_len; uint8_t pdu_type; uint16_t holdtime; uint16_t checksum; } __attribute__((packed)); #define ESIS_FIXED_HDR_LEN 9 #define ESH_PDU 2 #define ISH_PDU 4 #define RD_PDU 5 #define ISIS_FIXED_HDR_LEN 8 /* * IS-IS PDU types. */ #define L1_LAN_HELLO 15 #define L2_LAN_HELLO 16 /* * L1 and L2 LAN IS to IS Hello PDU header * +-------+-------+-------+-------+-------+-------+-------+-------+ * | Reserved | Circuit Type | 1 * +-------+-------+-------+-------+-------+-------+-------+-------+ * + Source ID + id_len * +-------+-------+-------+-------+-------+-------+-------+-------+ * | Holding Time | 2 * +-------+-------+-------+-------+-------+-------+-------+-------+ * | PDU Length | 2 * +-------+-------+-------+-------+-------+-------+-------+-------+ * | R | Priority | 1 * +-------+-------+-------+-------+-------+-------+-------+-------+ * | LAN ID | id_len + 1 * +-------+-------+-------+-------+-------+-------+-------+-------+ */ struct isis_lan_hello_hdr { uint8_t circuit_t; uint8_t source_id[ISIS_SYS_ID_LEN]; uint16_t hold_time; uint16_t pdu_len; uint8_t prio; uint8_t lan_id[ISIS_SYS_ID_LEN + 1]; } __attribute__((packed)); #define ISIS_LANHELLO_HDRLEN 19 #define P2P_HELLO 17 /* * Point-to-point IS to IS hello PDU header * +-------+-------+-------+-------+-------+-------+-------+-------+ * | Reserved | Circuit Type | 1 * +-------+-------+-------+-------+-------+-------+-------+-------+ * + Source ID + id_len * +-------+-------+-------+-------+-------+-------+-------+-------+ * + Holding Time + 2 * +-------+-------+-------+-------+-------+-------+-------+-------+ * + PDU Length + 2 * +-------+-------+-------+-------+-------+-------+-------+-------+ * | Local Circuit ID | 1 * +-------+-------+-------+-------+-------+-------+-------+-------+ */ struct isis_p2p_hello_hdr { uint8_t circuit_t; uint8_t source_id[ISIS_SYS_ID_LEN]; uint16_t hold_time; uint16_t pdu_len; uint8_t local_id; } __attribute__((packed)); #define ISIS_P2PHELLO_HDRLEN 12 #define L1_LINK_STATE 18 #define L2_LINK_STATE 20 #define FS_LINK_STATE 10 #define L2_CIRCUIT_FLOODING_SCOPE 2 struct isis_lsp_hdr { uint16_t pdu_len; uint16_t rem_lifetime; uint8_t lsp_id[ISIS_SYS_ID_LEN + 2]; uint32_t seqno; uint16_t checksum; uint8_t lsp_bits; }; #define ISIS_LSP_HDR_LEN 19 /* * Since the length field of LSP Entries TLV is one byte long, and each LSP * entry is LSP_ENTRIES_LEN (16) bytes long, the maximum number of LSP entries * can be accomodated in a TLV is * 255 / 16 = 15. * * Therefore, the maximum length of the LSP Entries TLV is * 16 * 15 + 2 (header) = 242 bytes. */ #define MAX_LSP_ENTRIES_TLV_SIZE 242 #define L1_COMPLETE_SEQ_NUM 24 #define L2_COMPLETE_SEQ_NUM 25 /* * L1 and L2 IS to IS complete sequence numbers PDU header * +-------+-------+-------+-------+-------+-------+-------+-------+ * + PDU Length + 2 * +-------+-------+-------+-------+-------+-------+-------+-------+ * + Source ID + id_len + 1 * +-------+-------+-------+-------+-------+-------+-------+-------+ * + Start LSP ID + id_len + 2 * +-------+-------+-------+-------+-------+-------+-------+-------+ * + End LSP ID + id_len + 2 * +-------+-------+-------+-------+-------+-------+-------+-------+ */ struct isis_complete_seqnum_hdr { uint16_t pdu_len; uint8_t source_id[ISIS_SYS_ID_LEN + 1]; uint8_t start_lsp_id[ISIS_SYS_ID_LEN + 2]; uint8_t stop_lsp_id[ISIS_SYS_ID_LEN + 2]; }; #define ISIS_CSNP_HDRLEN 25 #define L1_PARTIAL_SEQ_NUM 26 #define L2_PARTIAL_SEQ_NUM 27 /* * L1 and L2 IS to IS partial sequence numbers PDU header * +-------+-------+-------+-------+-------+-------+-------+-------+ * + PDU Length + 2 * +-------+-------+-------+-------+-------+-------+-------+-------+ * + Source ID + id_len + 1 * +---------------------------------------------------------------+ */ struct isis_partial_seqnum_hdr { uint16_t pdu_len; uint8_t source_id[ISIS_SYS_ID_LEN + 1]; }; #define ISIS_PSNP_HDRLEN 9 #ifdef __SUNPRO_C #pragma pack() #endif /* * Function for receiving IS-IS PDUs */ int isis_receive(struct thread *thread); /* * calling arguments for snp_process () */ #define ISIS_SNP_PSNP_FLAG 0 #define ISIS_SNP_CSNP_FLAG 1 #define ISIS_AUTH_MD5_SIZE 16U /* * Sending functions */ void send_hello_sched(struct isis_circuit *circuit, int level, long delay); int send_csnp(struct isis_circuit *circuit, int level); int send_l1_csnp(struct thread *thread); int send_l2_csnp(struct thread *thread); int send_l1_psnp(struct thread *thread); int send_l2_psnp(struct thread *thread); void send_lsp(struct isis_circuit *circuit, struct isis_lsp *lsp, enum isis_tx_type tx_type); void fill_fixed_hdr(uint8_t pdu_type, struct stream *stream); int send_hello(struct isis_circuit *circuit, int level); int isis_handle_pdu(struct isis_circuit *circuit, uint8_t *ssnpa); #endif /* _ZEBRA_ISIS_PDU_H */ frr-7.2.1/isisd/isis_pdu_counter.c0000644000000000000000000000522513610377563014055 00000000000000/* * IS-IS Routing protocol - isis_pdu_counter.c * Copyright (C) 2018 Christian Franke, for NetDEF Inc. * * This program is free software; you can redistribute it and/or modify it * under the terms of the 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; see the file COPYING; if not, write to the Free Software * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA */ #include #include "vty.h" #include "isisd/isis_pdu_counter.h" #include "isisd/isisd.h" #include "isisd/isis_circuit.h" #include "isisd/isis_pdu.h" static int pdu_type_to_counter_index(uint8_t pdu_type) { switch (pdu_type) { case L1_LAN_HELLO: return L1_LAN_HELLO_INDEX; case L2_LAN_HELLO: return L2_LAN_HELLO_INDEX; case P2P_HELLO: return P2P_HELLO_INDEX; case L1_LINK_STATE: return L1_LINK_STATE_INDEX; case L2_LINK_STATE: return L2_LINK_STATE_INDEX; case FS_LINK_STATE: return FS_LINK_STATE_INDEX; case L1_COMPLETE_SEQ_NUM: return L1_COMPLETE_SEQ_NUM_INDEX; case L2_COMPLETE_SEQ_NUM: return L2_COMPLETE_SEQ_NUM_INDEX; case L1_PARTIAL_SEQ_NUM: return L1_PARTIAL_SEQ_NUM_INDEX; case L2_PARTIAL_SEQ_NUM: return L2_PARTIAL_SEQ_NUM_INDEX; default: return -1; } } static const char *pdu_counter_index_to_name(enum pdu_counter_index index) { switch (index) { case L1_LAN_HELLO_INDEX: return " L1 IIH"; case L2_LAN_HELLO_INDEX: return " L2 IIH"; case P2P_HELLO_INDEX: return "P2P IIH"; case L1_LINK_STATE_INDEX: return " L1 LSP"; case L2_LINK_STATE_INDEX: return " L2 LSP"; case FS_LINK_STATE_INDEX: return " FS LSP"; case L1_COMPLETE_SEQ_NUM_INDEX: return "L1 CSNP"; case L2_COMPLETE_SEQ_NUM_INDEX: return "L2 CSNP"; case L1_PARTIAL_SEQ_NUM_INDEX: return "L1 PSNP"; case L2_PARTIAL_SEQ_NUM_INDEX: return "L2 PSNP"; default: return "???????"; } } void pdu_counter_count(pdu_counter_t counter, uint8_t pdu_type) { int index = pdu_type_to_counter_index(pdu_type); if (index < 0) return; counter[index]++; } void pdu_counter_print(struct vty *vty, const char *prefix, pdu_counter_t counter) { for (int i = 0; i < PDU_COUNTER_SIZE; i++) { if (!counter[i]) continue; vty_out(vty, "%s%s: %" PRIu64 "\n", prefix, pdu_counter_index_to_name(i), counter[i]); } } frr-7.2.1/isisd/isis_pdu_counter.h0000644000000000000000000000257513610377563014067 00000000000000/* * IS-IS Routing protocol - isis_pdu_counter.c * Copyright (C) 2018 Christian Franke, for NetDEF Inc. * * This program is free software; you can redistribute it and/or modify it * under the terms of the 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; see the file COPYING; if not, write to the Free Software * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA */ #ifndef ISIS_PDU_COUNTER_H #define ISIS_PDU_COUNTER_H enum pdu_counter_index { L1_LAN_HELLO_INDEX = 0, L2_LAN_HELLO_INDEX, P2P_HELLO_INDEX, L1_LINK_STATE_INDEX, L2_LINK_STATE_INDEX, FS_LINK_STATE_INDEX, L1_COMPLETE_SEQ_NUM_INDEX, L2_COMPLETE_SEQ_NUM_INDEX, L1_PARTIAL_SEQ_NUM_INDEX, L2_PARTIAL_SEQ_NUM_INDEX, PDU_COUNTER_SIZE }; typedef uint64_t pdu_counter_t[PDU_COUNTER_SIZE]; void pdu_counter_print(struct vty *vty, const char *prefix, pdu_counter_t counter); void pdu_counter_count(pdu_counter_t counter, uint8_t pdu_type); #endif frr-7.2.1/isisd/isis_pfpacket.c0000644000000000000000000002742013610377563013324 00000000000000/* * IS-IS Rout(e)ing protocol - isis_pfpacket.c * * Copyright (C) 2001,2002 Sampo Saaristo * Tampere University of Technology * Institute of Communications Engineering * * This program is free software; you can redistribute it and/or modify it * under the terms of the GNU General Public Licenseas 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; see the file COPYING; if not, write to the Free Software * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA */ #include #if ISIS_METHOD == ISIS_METHOD_PFPACKET #include /* the L2 protocols */ #include #include #include "log.h" #include "network.h" #include "stream.h" #include "if.h" #include "lib_errors.h" #include "isisd/isis_constants.h" #include "isisd/isis_common.h" #include "isisd/isis_circuit.h" #include "isisd/isis_flags.h" #include "isisd/isisd.h" #include "isisd/isis_constants.h" #include "isisd/isis_circuit.h" #include "isisd/isis_network.h" #include "privs.h" /* tcpdump -i eth0 'isis' -dd */ static struct sock_filter isisfilter[] = { /* NB: we're in SOCK_DGRAM, so src/dst mac + length are stripped * off! * (OTOH it's a bit more lower-layer agnostic and might work * over GRE?) */ /* { 0x28, 0, 0, 0x0000000c - 14 }, */ /* { 0x25, 5, 0, 0x000005dc }, */ {0x28, 0, 0, 0x0000000e - 14}, {0x15, 0, 3, 0x0000fefe}, {0x30, 0, 0, 0x00000011 - 14}, {0x15, 0, 1, 0x00000083}, {0x6, 0, 0, 0x00040000}, {0x6, 0, 0, 0x00000000}, }; static struct sock_fprog bpf = { .len = array_size(isisfilter), .filter = isisfilter, }; /* * Table 9 - Architectural constants for use with ISO 8802 subnetworks * ISO 10589 - 8.4.8 */ uint8_t ALL_L1_ISS[6] = {0x01, 0x80, 0xC2, 0x00, 0x00, 0x14}; uint8_t ALL_L2_ISS[6] = {0x01, 0x80, 0xC2, 0x00, 0x00, 0x15}; uint8_t ALL_ISS[6] = {0x09, 0x00, 0x2B, 0x00, 0x00, 0x05}; uint8_t ALL_ESS[6] = {0x09, 0x00, 0x2B, 0x00, 0x00, 0x04}; static uint8_t discard_buff[8192]; /* * if level is 0 we are joining p2p multicast * FIXME: and the p2p multicast being ??? */ static int isis_multicast_join(int fd, int registerto, int if_num) { struct packet_mreq mreq; memset(&mreq, 0, sizeof(mreq)); mreq.mr_ifindex = if_num; if (registerto) { mreq.mr_type = PACKET_MR_MULTICAST; mreq.mr_alen = ETH_ALEN; if (registerto == 1) memcpy(&mreq.mr_address, ALL_L1_ISS, ETH_ALEN); else if (registerto == 2) memcpy(&mreq.mr_address, ALL_L2_ISS, ETH_ALEN); else if (registerto == 3) memcpy(&mreq.mr_address, ALL_ISS, ETH_ALEN); else memcpy(&mreq.mr_address, ALL_ESS, ETH_ALEN); } else { mreq.mr_type = PACKET_MR_ALLMULTI; } #ifdef EXTREME_DEBUG zlog_debug( "isis_multicast_join(): fd=%d, reg_to=%d, if_num=%d, " "address = %02x:%02x:%02x:%02x:%02x:%02x", fd, registerto, if_num, mreq.mr_address[0], mreq.mr_address[1], mreq.mr_address[2], mreq.mr_address[3], mreq.mr_address[4], mreq.mr_address[5]); #endif /* EXTREME_DEBUG */ if (setsockopt(fd, SOL_PACKET, PACKET_ADD_MEMBERSHIP, &mreq, sizeof(struct packet_mreq))) { zlog_warn("isis_multicast_join(): setsockopt(): %s", safe_strerror(errno)); return ISIS_WARNING; } return ISIS_OK; } static int open_packet_socket(struct isis_circuit *circuit) { struct sockaddr_ll s_addr; int fd, retval = ISIS_OK; fd = socket(PF_PACKET, SOCK_DGRAM, htons(ETH_P_ALL)); if (fd < 0) { zlog_warn("open_packet_socket(): socket() failed %s", safe_strerror(errno)); return ISIS_WARNING; } if (setsockopt(fd, SOL_SOCKET, SO_ATTACH_FILTER, &bpf, sizeof(bpf))) { zlog_warn("open_packet_socket(): SO_ATTACH_FILTER failed: %s", safe_strerror(errno)); } /* * Bind to the physical interface */ memset(&s_addr, 0, sizeof(struct sockaddr_ll)); s_addr.sll_family = AF_PACKET; s_addr.sll_protocol = htons(ETH_P_ALL); s_addr.sll_ifindex = circuit->interface->ifindex; if (bind(fd, (struct sockaddr *)(&s_addr), sizeof(struct sockaddr_ll)) < 0) { zlog_warn("open_packet_socket(): bind() failed: %s", safe_strerror(errno)); close(fd); return ISIS_WARNING; } circuit->fd = fd; if (if_is_broadcast(circuit->interface)) { /* * Join to multicast groups * according to * 8.4.2 - Broadcast subnetwork IIH PDUs * FIXME: is there a case only one will fail?? */ /* joining ALL_L1_ISS */ retval |= isis_multicast_join(circuit->fd, 1, circuit->interface->ifindex); /* joining ALL_L2_ISS */ retval |= isis_multicast_join(circuit->fd, 2, circuit->interface->ifindex); /* joining ALL_ISS (used in RFC 5309 p2p-over-lan as well) */ retval |= isis_multicast_join(circuit->fd, 3, circuit->interface->ifindex); } else { retval = isis_multicast_join(circuit->fd, 0, circuit->interface->ifindex); } return retval; } /* * Create the socket and set the tx/rx funcs */ int isis_sock_init(struct isis_circuit *circuit) { int retval = ISIS_OK; frr_with_privs(&isisd_privs) { retval = open_packet_socket(circuit); if (retval != ISIS_OK) { zlog_warn("%s: could not initialize the socket", __func__); break; } /* Assign Rx and Tx callbacks are based on real if type */ if (if_is_broadcast(circuit->interface)) { circuit->tx = isis_send_pdu_bcast; circuit->rx = isis_recv_pdu_bcast; } else if (if_is_pointopoint(circuit->interface)) { circuit->tx = isis_send_pdu_p2p; circuit->rx = isis_recv_pdu_p2p; } else { zlog_warn("isis_sock_init(): unknown circuit type"); retval = ISIS_WARNING; break; } } return retval; } static inline int llc_check(uint8_t *llc) { if (*llc != ISO_SAP || *(llc + 1) != ISO_SAP || *(llc + 2) != 3) return 0; return 1; } int isis_recv_pdu_bcast(struct isis_circuit *circuit, uint8_t *ssnpa) { int bytesread, addr_len; struct sockaddr_ll s_addr; uint8_t llc[LLC_LEN]; addr_len = sizeof(s_addr); memset(&s_addr, 0, sizeof(struct sockaddr_ll)); bytesread = recvfrom(circuit->fd, (void *)&llc, LLC_LEN, MSG_PEEK, (struct sockaddr *)&s_addr, (socklen_t *)&addr_len); if ((bytesread < 0) || (s_addr.sll_ifindex != (int)circuit->interface->ifindex)) { if (bytesread < 0) { zlog_warn( "isis_recv_packet_bcast(): ifname %s, fd %d, " "bytesread %d, recvfrom(): %s", circuit->interface->name, circuit->fd, bytesread, safe_strerror(errno)); } if (s_addr.sll_ifindex != (int)circuit->interface->ifindex) { zlog_warn( "packet is received on multiple interfaces: " "socket interface %d, circuit interface %d, " "packet type %u", s_addr.sll_ifindex, circuit->interface->ifindex, s_addr.sll_pkttype); } /* get rid of the packet */ bytesread = recvfrom(circuit->fd, discard_buff, sizeof(discard_buff), MSG_DONTWAIT, (struct sockaddr *)&s_addr, (socklen_t *)&addr_len); if (bytesread < 0) zlog_warn("isis_recv_pdu_bcast(): recvfrom() failed"); return ISIS_WARNING; } /* * Filtering by llc field, discard packets sent by this host (other * circuit) */ if (!llc_check(llc) || s_addr.sll_pkttype == PACKET_OUTGOING) { /* Read the packet into discard buff */ bytesread = recvfrom(circuit->fd, discard_buff, sizeof(discard_buff), MSG_DONTWAIT, (struct sockaddr *)&s_addr, (socklen_t *)&addr_len); if (bytesread < 0) zlog_warn("isis_recv_pdu_bcast(): recvfrom() failed"); return ISIS_WARNING; } /* Ensure that we have enough space for a pdu padded to fill the mtu */ unsigned int max_size = circuit->interface->mtu > circuit->interface->mtu6 ? circuit->interface->mtu : circuit->interface->mtu6; uint8_t temp_buff[max_size]; bytesread = recvfrom(circuit->fd, temp_buff, max_size, MSG_DONTWAIT, (struct sockaddr *)&s_addr, (socklen_t *)&addr_len); if (bytesread < 0) { zlog_warn("%s: recvfrom() failed", __func__); return ISIS_WARNING; } /* then we lose the LLC */ stream_write(circuit->rcv_stream, temp_buff + LLC_LEN, bytesread - LLC_LEN); memcpy(ssnpa, &s_addr.sll_addr, s_addr.sll_halen); return ISIS_OK; } int isis_recv_pdu_p2p(struct isis_circuit *circuit, uint8_t *ssnpa) { int bytesread, addr_len; struct sockaddr_ll s_addr; memset(&s_addr, 0, sizeof(struct sockaddr_ll)); addr_len = sizeof(s_addr); /* we can read directly to the stream */ (void)stream_recvfrom( circuit->rcv_stream, circuit->fd, circuit->interface->mtu, 0, (struct sockaddr *)&s_addr, (socklen_t *)&addr_len); if (s_addr.sll_pkttype == PACKET_OUTGOING) { /* Read the packet into discard buff */ bytesread = recvfrom(circuit->fd, discard_buff, sizeof(discard_buff), MSG_DONTWAIT, (struct sockaddr *)&s_addr, (socklen_t *)&addr_len); if (bytesread < 0) zlog_warn("isis_recv_pdu_p2p(): recvfrom() failed"); return ISIS_WARNING; } /* If we don't have protocol type 0x00FE which is * ISO over GRE we exit with pain :) */ if (ntohs(s_addr.sll_protocol) != 0x00FE) { zlog_warn("isis_recv_pdu_p2p(): protocol mismatch(): %X", ntohs(s_addr.sll_protocol)); return ISIS_WARNING; } memcpy(ssnpa, &s_addr.sll_addr, s_addr.sll_halen); return ISIS_OK; } int isis_send_pdu_bcast(struct isis_circuit *circuit, int level) { struct msghdr msg; struct iovec iov[2]; char temp_buff[LLC_LEN]; /* we need to do the LLC in here because of P2P circuits, which will * not need it */ struct sockaddr_ll sa; stream_set_getp(circuit->snd_stream, 0); memset(&sa, 0, sizeof(struct sockaddr_ll)); sa.sll_family = AF_PACKET; size_t frame_size = stream_get_endp(circuit->snd_stream) + LLC_LEN; sa.sll_protocol = htons(isis_ethertype(frame_size)); sa.sll_ifindex = circuit->interface->ifindex; sa.sll_halen = ETH_ALEN; /* RFC5309 section 4.1 recommends ALL_ISS */ if (circuit->circ_type == CIRCUIT_T_P2P) memcpy(&sa.sll_addr, ALL_ISS, ETH_ALEN); else if (level == 1) memcpy(&sa.sll_addr, ALL_L1_ISS, ETH_ALEN); else memcpy(&sa.sll_addr, ALL_L2_ISS, ETH_ALEN); /* on a broadcast circuit */ /* first we put the LLC in */ temp_buff[0] = 0xFE; temp_buff[1] = 0xFE; temp_buff[2] = 0x03; memset(&msg, 0, sizeof(msg)); msg.msg_name = &sa; msg.msg_namelen = sizeof(struct sockaddr_ll); msg.msg_iov = iov; msg.msg_iovlen = 2; iov[0].iov_base = temp_buff; iov[0].iov_len = LLC_LEN; iov[1].iov_base = circuit->snd_stream->data; iov[1].iov_len = stream_get_endp(circuit->snd_stream); if (sendmsg(circuit->fd, &msg, 0) < 0) { zlog_warn("IS-IS pfpacket: could not transmit packet on %s: %s", circuit->interface->name, safe_strerror(errno)); if (ERRNO_IO_RETRY(errno)) return ISIS_WARNING; return ISIS_ERROR; } return ISIS_OK; } int isis_send_pdu_p2p(struct isis_circuit *circuit, int level) { struct sockaddr_ll sa; ssize_t rv; stream_set_getp(circuit->snd_stream, 0); memset(&sa, 0, sizeof(struct sockaddr_ll)); sa.sll_family = AF_PACKET; sa.sll_ifindex = circuit->interface->ifindex; sa.sll_halen = ETH_ALEN; if (level == 1) memcpy(&sa.sll_addr, ALL_L1_ISS, ETH_ALEN); else memcpy(&sa.sll_addr, ALL_L2_ISS, ETH_ALEN); /* lets try correcting the protocol */ sa.sll_protocol = htons(0x00FE); rv = sendto(circuit->fd, circuit->snd_stream->data, stream_get_endp(circuit->snd_stream), 0, (struct sockaddr *)&sa, sizeof(struct sockaddr_ll)); if (rv < 0) { zlog_warn("IS-IS pfpacket: could not transmit packet on %s: %s", circuit->interface->name, safe_strerror(errno)); if (ERRNO_IO_RETRY(errno)) return ISIS_WARNING; return ISIS_ERROR; } return ISIS_OK; } #endif /* ISIS_METHOD == ISIS_METHOD_PFPACKET */ frr-7.2.1/isisd/isis_redist.c0000644000000000000000000004642613610377563013030 00000000000000/* * IS-IS Rout(e)ing protocol - isis_redist.c * * Copyright (C) 2013-2015 Christian Franke * * This program is free software; you can redistribute it and/or modify it * under the terms of the 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; see the file COPYING; if not, write to the Free Software * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA */ #include #include "command.h" #include "if.h" #include "linklist.h" #include "memory.h" #include "isis_memory.h" #include "prefix.h" #include "routemap.h" #include "stream.h" #include "table.h" #include "vty.h" #include "srcdest_table.h" #include "isisd/isis_constants.h" #include "isisd/isis_common.h" #include "isisd/isis_flags.h" #include "isisd/isis_misc.h" #include "isisd/isis_circuit.h" #include "isisd/isisd.h" #include "isisd/isis_lsp.h" #include "isisd/isis_route.h" #include "isisd/isis_zebra.h" static int redist_protocol(int family) { if (family == AF_INET) return 0; if (family == AF_INET6) return 1; assert(!"Unsupported address family!"); return 0; } static afi_t afi_for_redist_protocol(int protocol) { if (protocol == 0) return AFI_IP; if (protocol == 1) return AFI_IP6; assert(!"Unknown redist protocol!"); return AFI_IP; } static struct route_table *get_ext_info(struct isis *i, int family) { int protocol = redist_protocol(family); return i->ext_info[protocol]; } static struct isis_redist *get_redist_settings(struct isis_area *area, int family, int type, int level) { int protocol = redist_protocol(family); return &area->redist_settings[protocol][type][level - 1]; } struct route_table *get_ext_reach(struct isis_area *area, int family, int level) { int protocol = redist_protocol(family); return area->ext_reach[protocol][level - 1]; } /* Install external reachability information into a * specific area for a specific level. * Schedule an lsp regenerate if necessary */ static void isis_redist_install(struct isis_area *area, int level, const struct prefix *p, const struct prefix_ipv6 *src_p, struct isis_ext_info *info) { int family = p->family; struct route_table *er_table = get_ext_reach(area, family, level); struct route_node *er_node; if (!er_table) { zlog_warn( "%s: External reachability table of area %s" " is not initialized.", __func__, area->area_tag); return; } er_node = srcdest_rnode_get(er_table, p, src_p); if (er_node->info) { route_unlock_node(er_node); /* Don't update/reschedule lsp generation if nothing changed. */ if (!memcmp(er_node->info, info, sizeof(*info))) return; } else { er_node->info = XMALLOC(MTYPE_ISIS_EXT_INFO, sizeof(*info)); } memcpy(er_node->info, info, sizeof(*info)); lsp_regenerate_schedule(area, level, 0); } /* Remove external reachability information from a * specific area for a specific level. * Schedule an lsp regenerate if necessary. */ static void isis_redist_uninstall(struct isis_area *area, int level, const struct prefix *p, const struct prefix_ipv6 *src_p) { int family = p->family; struct route_table *er_table = get_ext_reach(area, family, level); struct route_node *er_node; if (!er_table) { zlog_warn( "%s: External reachability table of area %s" " is not initialized.", __func__, area->area_tag); return; } er_node = srcdest_rnode_lookup(er_table, p, src_p); if (!er_node) return; else route_unlock_node(er_node); if (!er_node->info) return; XFREE(MTYPE_ISIS_EXT_INFO, er_node->info); route_unlock_node(er_node); lsp_regenerate_schedule(area, level, 0); } /* Update external reachability info of area for a given level * and prefix, using the given redistribution settings. */ static void isis_redist_update_ext_reach(struct isis_area *area, int level, struct isis_redist *redist, const struct prefix *p, const struct prefix_ipv6 *src_p, struct isis_ext_info *info) { struct isis_ext_info area_info; route_map_result_t map_ret; memcpy(&area_info, info, sizeof(area_info)); area_info.metric = redist->metric; if (redist->map_name) { map_ret = route_map_apply(redist->map, p, RMAP_ISIS, &area_info); if (map_ret == RMAP_DENYMATCH) area_info.distance = 255; } /* Allow synthesized default routes only on always orignate */ if (area_info.origin == DEFAULT_ROUTE && redist->redist != DEFAULT_ORIGINATE_ALWAYS) area_info.distance = 255; if (area_info.distance < 255) isis_redist_install(area, level, p, src_p, &area_info); else isis_redist_uninstall(area, level, p, src_p); } static void isis_redist_ensure_default(struct isis *isis, int family) { struct prefix p; struct route_table *ei_table = get_ext_info(isis, family); struct route_node *ei_node; struct isis_ext_info *info; if (family == AF_INET) { p.family = AF_INET; p.prefixlen = 0; memset(&p.u.prefix4, 0, sizeof(p.u.prefix4)); } else if (family == AF_INET6) { p.family = AF_INET6; p.prefixlen = 0; memset(&p.u.prefix6, 0, sizeof(p.u.prefix6)); } else assert(!"Unknown family!"); ei_node = srcdest_rnode_get(ei_table, &p, NULL); if (ei_node->info) { route_unlock_node(ei_node); return; } ei_node->info = XCALLOC(MTYPE_ISIS_EXT_INFO, sizeof(struct isis_ext_info)); info = ei_node->info; info->origin = DEFAULT_ROUTE; info->distance = 254; info->metric = MAX_WIDE_PATH_METRIC; } /* Handle notification about route being added */ void isis_redist_add(int type, struct prefix *p, struct prefix_ipv6 *src_p, uint8_t distance, uint32_t metric) { int family = p->family; struct route_table *ei_table = get_ext_info(isis, family); struct route_node *ei_node; struct isis_ext_info *info; struct listnode *node; struct isis_area *area; int level; struct isis_redist *redist; char debug_buf[BUFSIZ]; prefix2str(p, debug_buf, sizeof(debug_buf)); zlog_debug("%s: New route %s from %s: distance %d.", __func__, debug_buf, zebra_route_string(type), distance); if (!ei_table) { zlog_warn("%s: External information table not initialized.", __func__); return; } ei_node = srcdest_rnode_get(ei_table, p, src_p); if (ei_node->info) route_unlock_node(ei_node); else ei_node->info = XCALLOC(MTYPE_ISIS_EXT_INFO, sizeof(struct isis_ext_info)); info = ei_node->info; info->origin = type; info->distance = distance; info->metric = metric; if (is_default_prefix(p) && (!src_p || !src_p->prefixlen)) { type = DEFAULT_ROUTE; } for (ALL_LIST_ELEMENTS_RO(isis->area_list, node, area)) for (level = 1; level <= ISIS_LEVELS; level++) { redist = get_redist_settings(area, family, type, level); if (!redist->redist) continue; isis_redist_update_ext_reach(area, level, redist, p, src_p, info); } } void isis_redist_delete(int type, struct prefix *p, struct prefix_ipv6 *src_p) { int family = p->family; struct route_table *ei_table = get_ext_info(isis, family); struct route_node *ei_node; struct listnode *node; struct isis_area *area; int level; struct isis_redist *redist; char debug_buf[BUFSIZ]; prefix2str(p, debug_buf, sizeof(debug_buf)); zlog_debug("%s: Removing route %s from %s.", __func__, debug_buf, zebra_route_string(type)); if (is_default_prefix(p) && (!src_p || !src_p->prefixlen)) { /* Don't remove default route but add synthetic route for use * by "default-information originate always". Areas without the * "always" setting will ignore routes with origin * DEFAULT_ROUTE. */ isis_redist_add(DEFAULT_ROUTE, p, NULL, 254, MAX_WIDE_PATH_METRIC); return; } if (!ei_table) { zlog_warn("%s: External information table not initialized.", __func__); return; } ei_node = srcdest_rnode_lookup(ei_table, p, src_p); if (!ei_node || !ei_node->info) { char buf[BUFSIZ]; prefix2str(p, buf, sizeof(buf)); zlog_warn( "%s: Got a delete for %s route %s, but that route" " was never added.", __func__, zebra_route_string(type), buf); if (ei_node) route_unlock_node(ei_node); return; } route_unlock_node(ei_node); for (ALL_LIST_ELEMENTS_RO(isis->area_list, node, area)) for (level = ISIS_LEVEL1; level <= ISIS_LEVEL2; level++) { redist = get_redist_settings(area, family, type, level); if (!redist->redist) continue; isis_redist_uninstall(area, level, p, src_p); } XFREE(MTYPE_ISIS_EXT_INFO, ei_node->info); route_unlock_node(ei_node); } static void isis_redist_routemap_set(struct isis_redist *redist, const char *routemap) { if (redist->map_name) { XFREE(MTYPE_ISIS, redist->map_name); route_map_counter_decrement(redist->map); redist->map = NULL; } if (routemap && strlen(routemap)) { redist->map_name = XSTRDUP(MTYPE_ISIS, routemap); redist->map = route_map_lookup_by_name(routemap); route_map_counter_increment(redist->map); } } static void isis_redist_update_zebra_subscriptions(struct isis *isis) { struct listnode *node; struct isis_area *area; int type; int level; int protocol; char do_subscribe[REDIST_PROTOCOL_COUNT][ZEBRA_ROUTE_MAX + 1]; memset(do_subscribe, 0, sizeof(do_subscribe)); for (ALL_LIST_ELEMENTS_RO(isis->area_list, node, area)) for (protocol = 0; protocol < REDIST_PROTOCOL_COUNT; protocol++) for (type = 0; type < ZEBRA_ROUTE_MAX + 1; type++) for (level = 0; level < ISIS_LEVELS; level++) if (area->redist_settings[protocol] [type] [level].redist) do_subscribe[protocol][type] = 1; for (protocol = 0; protocol < REDIST_PROTOCOL_COUNT; protocol++) for (type = 0; type < ZEBRA_ROUTE_MAX + 1; type++) { /* This field is actually controlling transmission of * the IS-IS * routes to Zebra and has nothing to do with * redistribution, * so skip it. */ if (type == PROTO_TYPE) continue; afi_t afi = afi_for_redist_protocol(protocol); if (do_subscribe[protocol][type]) isis_zebra_redistribute_set(afi, type); else isis_zebra_redistribute_unset(afi, type); } } void isis_redist_set(struct isis_area *area, int level, int family, int type, uint32_t metric, const char *routemap, int originate_type) { int protocol = redist_protocol(family); struct isis_redist *redist = get_redist_settings(area, family, type, level); int i; struct route_table *ei_table; struct route_node *rn; struct isis_ext_info *info; redist->redist = (type == DEFAULT_ROUTE) ? originate_type : 1; redist->metric = metric; isis_redist_routemap_set(redist, routemap); if (!area->ext_reach[protocol][level - 1]) { area->ext_reach[protocol][level - 1] = srcdest_table_init(); } for (i = 0; i < REDIST_PROTOCOL_COUNT; i++) { if (!area->isis->ext_info[i]) { area->isis->ext_info[i] = srcdest_table_init(); } } isis_redist_update_zebra_subscriptions(area->isis); if (type == DEFAULT_ROUTE && originate_type == DEFAULT_ORIGINATE_ALWAYS) isis_redist_ensure_default(area->isis, family); ei_table = get_ext_info(area->isis, family); for (rn = route_top(ei_table); rn; rn = srcdest_route_next(rn)) { if (!rn->info) continue; info = rn->info; const struct prefix *p, *src_p; srcdest_rnode_prefixes(rn, &p, &src_p); if (type == DEFAULT_ROUTE) { if (!is_default_prefix(p) || (src_p && src_p->prefixlen)) { continue; } } else { if (info->origin != type) continue; } isis_redist_update_ext_reach(area, level, redist, p, (const struct prefix_ipv6 *)src_p, info); } } void isis_redist_unset(struct isis_area *area, int level, int family, int type) { struct isis_redist *redist = get_redist_settings(area, family, type, level); struct route_table *er_table = get_ext_reach(area, family, level); struct route_node *rn; struct isis_ext_info *info; if (!redist->redist) return; redist->redist = 0; if (!er_table) { zlog_warn("%s: External reachability table uninitialized.", __func__); return; } for (rn = route_top(er_table); rn; rn = srcdest_route_next(rn)) { if (!rn->info) continue; info = rn->info; const struct prefix *p, *src_p; srcdest_rnode_prefixes(rn, &p, &src_p); if (type == DEFAULT_ROUTE) { if (!is_default_prefix(p) || (src_p && src_p->prefixlen)) { continue; } } else { if (info->origin != type) continue; } XFREE(MTYPE_ISIS_EXT_INFO, rn->info); route_unlock_node(rn); } lsp_regenerate_schedule(area, level, 0); isis_redist_update_zebra_subscriptions(area->isis); } void isis_redist_area_finish(struct isis_area *area) { int protocol; int level; int type; for (protocol = 0; protocol < REDIST_PROTOCOL_COUNT; protocol++) for (level = 0; level < ISIS_LEVELS; level++) { for (type = 0; type < ZEBRA_ROUTE_MAX + 1; type++) { struct isis_redist *redist; redist = &area->redist_settings[protocol][type] [level]; redist->redist = 0; XFREE(MTYPE_ISIS, redist->map_name); } route_table_finish(area->ext_reach[protocol][level]); } isis_redist_update_zebra_subscriptions(area->isis); } #ifdef FABRICD DEFUN (isis_redistribute, isis_redistribute_cmd, "redistribute " PROTO_REDIST_STR " [{metric (0-16777215)|route-map WORD}]", REDIST_STR "Redistribute IPv4 routes\n" "Redistribute IPv6 routes\n" PROTO_REDIST_HELP "Metric for redistributed routes\n" "ISIS default metric\n" "Route map reference\n" "Pointer to route-map entries\n") { int idx_afi = 1; int idx_protocol = 2; int idx_metric_rmap = 1; VTY_DECLVAR_CONTEXT(isis_area, area); int family; int afi; int type; int level; unsigned long metric = 0; const char *routemap = NULL; family = str2family(argv[idx_afi]->text); if (family < 0) return CMD_WARNING_CONFIG_FAILED; afi = family2afi(family); if (!afi) return CMD_WARNING_CONFIG_FAILED; type = proto_redistnum(afi, argv[idx_protocol]->text); if (type < 0) return CMD_WARNING_CONFIG_FAILED; level = 2; if ((area->is_type & level) != level) { vty_out(vty, "Node is not a level-%d IS\n", level); return CMD_WARNING_CONFIG_FAILED; } if (argv_find(argv, argc, "metric", &idx_metric_rmap)) { metric = strtoul(argv[idx_metric_rmap + 1]->arg, NULL, 10); } idx_metric_rmap = 1; if (argv_find(argv, argc, "route-map", &idx_metric_rmap)) { routemap = argv[idx_metric_rmap + 1]->arg; } isis_redist_set(area, level, family, type, metric, routemap, 0); return 0; } DEFUN (no_isis_redistribute, no_isis_redistribute_cmd, "no redistribute " PROTO_REDIST_STR, NO_STR REDIST_STR "Redistribute IPv4 routes\n" "Redistribute IPv6 routes\n" PROTO_REDIST_HELP) { int idx_afi = 2; int idx_protocol = 3; VTY_DECLVAR_CONTEXT(isis_area, area); int type; int level; int family; int afi; family = str2family(argv[idx_afi]->arg); if (family < 0) return CMD_WARNING_CONFIG_FAILED; afi = family2afi(family); if (!afi) return CMD_WARNING_CONFIG_FAILED; type = proto_redistnum(afi, argv[idx_protocol]->text); if (type < 0) return CMD_WARNING_CONFIG_FAILED; level = 2; isis_redist_unset(area, level, family, type); return 0; } DEFUN (isis_default_originate, isis_default_originate_cmd, "default-information originate " " [always] [{metric (0-16777215)|route-map WORD}]", "Control distribution of default information\n" "Distribute a default route\n" "Distribute default route for IPv4\n" "Distribute default route for IPv6\n" "Always advertise default route\n" "Metric for default route\n" "ISIS default metric\n" "Route map reference\n" "Pointer to route-map entries\n") { int idx_afi = 2; int idx_always = fabricd ? 3 : 4; int idx_metric_rmap = 1; VTY_DECLVAR_CONTEXT(isis_area, area); int family; int originate_type = DEFAULT_ORIGINATE; int level; unsigned long metric = 0; const char *routemap = NULL; family = str2family(argv[idx_afi]->text); if (family < 0) return CMD_WARNING_CONFIG_FAILED; level = 2; if ((area->is_type & level) != level) { vty_out(vty, "Node is not a level-%d IS\n", level); return CMD_WARNING_CONFIG_FAILED; } if (argc > idx_always && strmatch(argv[idx_always]->text, "always")) { originate_type = DEFAULT_ORIGINATE_ALWAYS; idx_metric_rmap++; } if (argv_find(argv, argc, "metric", &idx_metric_rmap)) { metric = strtoul(argv[idx_metric_rmap + 1]->arg, NULL, 10); } idx_metric_rmap = 1; if (argv_find(argv, argc, "route-map", &idx_metric_rmap)) { routemap = argv[idx_metric_rmap + 1]->arg; } if (family == AF_INET6 && originate_type != DEFAULT_ORIGINATE_ALWAYS) { vty_out(vty, "Zebra doesn't implement default-originate for IPv6 yet\n"); vty_out(vty, "so use with care or use default-originate always.\n"); } isis_redist_set(area, level, family, DEFAULT_ROUTE, metric, routemap, originate_type); return 0; } DEFUN (no_isis_default_originate, no_isis_default_originate_cmd, "no default-information originate ", NO_STR "Control distribution of default information\n" "Distribute a default route\n" "Distribute default route for IPv4\n" "Distribute default route for IPv6\n") { int idx_afi = 3; VTY_DECLVAR_CONTEXT(isis_area, area); int family; int level; family = str2family(argv[idx_afi]->text); if (family < 0) return CMD_WARNING_CONFIG_FAILED; level = 2; isis_redist_unset(area, level, family, DEFAULT_ROUTE); return 0; } #endif /* ifdef FABRICD */ int isis_redist_config_write(struct vty *vty, struct isis_area *area, int family) { int type; int level; int write = 0; struct isis_redist *redist; const char *family_str; if (family == AF_INET) family_str = "ipv4"; else if (family == AF_INET6) family_str = "ipv6"; else return 0; for (type = 0; type < ZEBRA_ROUTE_MAX; type++) { if (type == PROTO_TYPE) continue; for (level = 1; level <= ISIS_LEVELS; level++) { redist = get_redist_settings(area, family, type, level); if (!redist->redist) continue; vty_out(vty, " redistribute %s %s", family_str, zebra_route_string(type)); if (!fabricd) vty_out(vty, " level-%d", level); if (redist->metric) vty_out(vty, " metric %u", redist->metric); if (redist->map_name) vty_out(vty, " route-map %s", redist->map_name); vty_out(vty, "\n"); write++; } } for (level = 1; level <= ISIS_LEVELS; level++) { redist = get_redist_settings(area, family, DEFAULT_ROUTE, level); if (!redist->redist) continue; vty_out(vty, " default-information originate %s", family_str); if (!fabricd) vty_out(vty, " level-%d", level); if (redist->redist == DEFAULT_ORIGINATE_ALWAYS) vty_out(vty, " always"); if (redist->metric) vty_out(vty, " metric %u", redist->metric); if (redist->map_name) vty_out(vty, " route-map %s", redist->map_name); vty_out(vty, "\n"); write++; } return write; } void isis_redist_init(void) { #ifdef FABRICD install_element(ROUTER_NODE, &isis_redistribute_cmd); install_element(ROUTER_NODE, &no_isis_redistribute_cmd); install_element(ROUTER_NODE, &isis_default_originate_cmd); install_element(ROUTER_NODE, &no_isis_default_originate_cmd); #endif /* ifdef FABRICD */ } frr-7.2.1/isisd/isis_redist.h0000644000000000000000000000367613610377563013035 00000000000000/* * IS-IS Rout(e)ing protocol - isis_redist.h * * Copyright (C) 2013-2015 Christian Franke * * This program is free software; you can redistribute it and/or modify it * under the terms of the 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; see the file COPYING; if not, write to the Free Software * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA */ #ifndef ISIS_REDIST_H #define ISIS_REDIST_H #define REDIST_PROTOCOL_COUNT 2 #define DEFAULT_ROUTE ZEBRA_ROUTE_MAX #define DEFAULT_ORIGINATE 1 #define DEFAULT_ORIGINATE_ALWAYS 2 struct isis_ext_info { int origin; uint32_t metric; uint8_t distance; }; struct isis_redist { int redist; uint32_t metric; char *map_name; struct route_map *map; }; struct isis_area; struct prefix; struct prefix_ipv6; struct vty; struct route_table *get_ext_reach(struct isis_area *area, int family, int level); void isis_redist_add(int type, struct prefix *p, struct prefix_ipv6 *src_p, uint8_t distance, uint32_t metric); void isis_redist_delete(int type, struct prefix *p, struct prefix_ipv6 *src_p); int isis_redist_config_write(struct vty *vty, struct isis_area *area, int family); void isis_redist_init(void); void isis_redist_area_finish(struct isis_area *area); void isis_redist_set(struct isis_area *area, int level, int family, int type, uint32_t metric, const char *routemap, int originate_type); void isis_redist_unset(struct isis_area *area, int level, int family, int type); #endif frr-7.2.1/isisd/isis_route.c0000644000000000000000000003267113610377563012671 00000000000000/* * IS-IS Rout(e)ing protocol - isis_route.c * Copyright (C) 2001,2002 Sampo Saaristo * Tampere University of Technology * Institute of Communications Engineering * * based on ../ospf6d/ospf6_route.[ch] * by Yasuhiro Ohara * * This program is free software; you can redistribute it and/or modify it * under the terms of the GNU General Public Licenseas 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; see the file COPYING; if not, write to the Free Software * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA */ #include #include "thread.h" #include "linklist.h" #include "vty.h" #include "log.h" #include "lib_errors.h" #include "memory.h" #include "prefix.h" #include "hash.h" #include "if.h" #include "table.h" #include "srcdest_table.h" #include "isis_constants.h" #include "isis_common.h" #include "isis_flags.h" #include "isisd.h" #include "isis_misc.h" #include "isis_adjacency.h" #include "isis_circuit.h" #include "isis_pdu.h" #include "isis_lsp.h" #include "isis_spf.h" #include "isis_route.h" #include "isis_zebra.h" static struct isis_nexthop *nexthoplookup(struct list *nexthops, int family, union g_addr *ip, ifindex_t ifindex); static struct isis_nexthop *isis_nexthop_create(int family, union g_addr *ip, ifindex_t ifindex) { struct isis_nexthop *nexthop; nexthop = nexthoplookup(isis->nexthops, family, ip, ifindex); if (nexthop) { nexthop->lock++; return nexthop; } nexthop = XCALLOC(MTYPE_ISIS_NEXTHOP, sizeof(struct isis_nexthop)); nexthop->family = family; nexthop->ifindex = ifindex; nexthop->ip = *ip; listnode_add(isis->nexthops, nexthop); nexthop->lock++; return nexthop; } static void isis_nexthop_delete(struct isis_nexthop *nexthop) { nexthop->lock--; if (nexthop->lock == 0) { listnode_delete(isis->nexthops, nexthop); XFREE(MTYPE_ISIS_NEXTHOP, nexthop); } return; } static struct isis_nexthop *nexthoplookup(struct list *nexthops, int family, union g_addr *ip, ifindex_t ifindex) { struct listnode *node; struct isis_nexthop *nh; for (ALL_LIST_ELEMENTS_RO(nexthops, node, nh)) { if (nh->family != family) continue; if (nh->ifindex != ifindex) continue; switch (family) { case AF_INET: if (IPV4_ADDR_CMP(&nh->ip.ipv4, &ip->ipv4)) continue; break; case AF_INET6: if (IPV6_ADDR_CMP(&nh->ip.ipv6, &ip->ipv6)) continue; break; default: flog_err(EC_LIB_DEVELOPMENT, "%s: unknown address family [%d]", __func__, family); exit(1); } return nh; } return NULL; } static void adjinfo2nexthop(int family, struct list *nexthops, struct isis_adjacency *adj) { struct isis_nexthop *nh; union g_addr ip = {}; switch (family) { case AF_INET: for (unsigned int i = 0; i < adj->ipv4_address_count; i++) { ip.ipv4 = adj->ipv4_addresses[i]; if (!nexthoplookup(nexthops, AF_INET, &ip, adj->circuit->interface->ifindex)) { nh = isis_nexthop_create( AF_INET, &ip, adj->circuit->interface->ifindex); listnode_add(nexthops, nh); break; } } break; case AF_INET6: for (unsigned int i = 0; i < adj->ipv6_address_count; i++) { ip.ipv6 = adj->ipv6_addresses[i]; if (!nexthoplookup(nexthops, AF_INET6, &ip, adj->circuit->interface->ifindex)) { nh = isis_nexthop_create( AF_INET6, &ip, adj->circuit->interface->ifindex); listnode_add(nexthops, nh); break; } } break; default: flog_err(EC_LIB_DEVELOPMENT, "%s: unknown address family [%d]", __func__, family); exit(1); } } static struct isis_route_info *isis_route_info_new(struct prefix *prefix, struct prefix_ipv6 *src_p, uint32_t cost, uint32_t depth, struct list *adjacencies) { struct isis_route_info *rinfo; struct isis_adjacency *adj; struct listnode *node; rinfo = XCALLOC(MTYPE_ISIS_ROUTE_INFO, sizeof(struct isis_route_info)); rinfo->nexthops = list_new(); for (ALL_LIST_ELEMENTS_RO(adjacencies, node, adj)) { /* check for force resync this route */ if (CHECK_FLAG(adj->circuit->flags, ISIS_CIRCUIT_FLAPPED_AFTER_SPF)) SET_FLAG(rinfo->flag, ISIS_ROUTE_FLAG_ZEBRA_RESYNC); /* update neighbor router address */ switch (prefix->family) { case AF_INET: if (depth == 2 && prefix->prefixlen == 32) adj->router_address = prefix->u.prefix4; break; case AF_INET6: if (depth == 2 && prefix->prefixlen == 128 && (!src_p || !src_p->prefixlen)) { adj->router_address6 = prefix->u.prefix6; } break; default: flog_err(EC_LIB_DEVELOPMENT, "%s: unknown address family [%d]", __func__, prefix->family); exit(1); } adjinfo2nexthop(prefix->family, rinfo->nexthops, adj); } rinfo->cost = cost; rinfo->depth = depth; return rinfo; } static void isis_route_info_delete(struct isis_route_info *route_info) { if (route_info->nexthops) { route_info->nexthops->del = (void (*)(void *))isis_nexthop_delete; list_delete(&route_info->nexthops); } XFREE(MTYPE_ISIS_ROUTE_INFO, route_info); } static int isis_route_info_same_attrib(struct isis_route_info *new, struct isis_route_info *old) { if (new->cost != old->cost) return 0; if (new->depth != old->depth) return 0; return 1; } static int isis_route_info_same(struct isis_route_info *new, struct isis_route_info *old, uint8_t family) { struct listnode *node; struct isis_nexthop *nexthop; if (!CHECK_FLAG(old->flag, ISIS_ROUTE_FLAG_ZEBRA_SYNCED)) return 0; if (CHECK_FLAG(new->flag, ISIS_ROUTE_FLAG_ZEBRA_RESYNC)) return 0; if (!isis_route_info_same_attrib(new, old)) return 0; for (ALL_LIST_ELEMENTS_RO(new->nexthops, node, nexthop)) if (!nexthoplookup(old->nexthops, nexthop->family, &nexthop->ip, nexthop->ifindex)) return 0; for (ALL_LIST_ELEMENTS_RO(old->nexthops, node, nexthop)) if (!nexthoplookup(new->nexthops, nexthop->family, &nexthop->ip, nexthop->ifindex)) return 0; return 1; } struct isis_route_info *isis_route_create(struct prefix *prefix, struct prefix_ipv6 *src_p, uint32_t cost, uint32_t depth, struct list *adjacencies, struct isis_area *area, struct route_table *table) { struct route_node *route_node; struct isis_route_info *rinfo_new, *rinfo_old, *route_info = NULL; char buff[PREFIX2STR_BUFFER]; uint8_t family; family = prefix->family; /* for debugs */ prefix2str(prefix, buff, sizeof(buff)); if (!table) return NULL; rinfo_new = isis_route_info_new(prefix, src_p, cost, depth, adjacencies); route_node = srcdest_rnode_get(table, prefix, src_p); rinfo_old = route_node->info; if (!rinfo_old) { if (isis->debugs & DEBUG_RTE_EVENTS) zlog_debug("ISIS-Rte (%s) route created: %s", area->area_tag, buff); route_info = rinfo_new; UNSET_FLAG(route_info->flag, ISIS_ROUTE_FLAG_ZEBRA_SYNCED); } else { route_unlock_node(route_node); if (isis->debugs & DEBUG_RTE_EVENTS) zlog_debug("ISIS-Rte (%s) route already exists: %s", area->area_tag, buff); if (isis_route_info_same(rinfo_new, rinfo_old, family)) { if (isis->debugs & DEBUG_RTE_EVENTS) zlog_debug("ISIS-Rte (%s) route unchanged: %s", area->area_tag, buff); isis_route_info_delete(rinfo_new); route_info = rinfo_old; } else { if (isis->debugs & DEBUG_RTE_EVENTS) zlog_debug("ISIS-Rte (%s) route changed: %s", area->area_tag, buff); isis_route_info_delete(rinfo_old); route_info = rinfo_new; UNSET_FLAG(route_info->flag, ISIS_ROUTE_FLAG_ZEBRA_SYNCED); } } SET_FLAG(route_info->flag, ISIS_ROUTE_FLAG_ACTIVE); route_node->info = route_info; return route_info; } static void isis_route_delete(struct route_node *rode, struct route_table *table) { struct isis_route_info *rinfo; char buff[SRCDEST2STR_BUFFER]; struct prefix *prefix; struct prefix_ipv6 *src_p; /* for log */ srcdest_rnode2str(rode, buff, sizeof(buff)); srcdest_rnode_prefixes(rode, (const struct prefix **)&prefix, (const struct prefix **)&src_p); rinfo = rode->info; if (rinfo == NULL) { if (isis->debugs & DEBUG_RTE_EVENTS) zlog_debug( "ISIS-Rte: tried to delete non-existant route %s", buff); return; } if (CHECK_FLAG(rinfo->flag, ISIS_ROUTE_FLAG_ZEBRA_SYNCED)) { UNSET_FLAG(rinfo->flag, ISIS_ROUTE_FLAG_ACTIVE); if (isis->debugs & DEBUG_RTE_EVENTS) zlog_debug("ISIS-Rte: route delete %s", buff); isis_zebra_route_update(prefix, src_p, rinfo); } isis_route_info_delete(rinfo); rode->info = NULL; route_unlock_node(rode); } static void _isis_route_verify_table(struct isis_area *area, struct route_table *table, struct route_table **tables) { struct route_node *rnode, *drnode; struct isis_route_info *rinfo; char buff[SRCDEST2STR_BUFFER]; for (rnode = route_top(table); rnode; rnode = srcdest_route_next(rnode)) { if (rnode->info == NULL) continue; rinfo = rnode->info; struct prefix *dst_p; struct prefix_ipv6 *src_p; srcdest_rnode_prefixes(rnode, (const struct prefix **)&dst_p, (const struct prefix **)&src_p); if (isis->debugs & DEBUG_RTE_EVENTS) { srcdest2str(dst_p, src_p, buff, sizeof(buff)); zlog_debug( "ISIS-Rte (%s): route validate: %s %s %s %s", area->area_tag, (CHECK_FLAG(rinfo->flag, ISIS_ROUTE_FLAG_ZEBRA_SYNCED) ? "synced" : "not-synced"), (CHECK_FLAG(rinfo->flag, ISIS_ROUTE_FLAG_ZEBRA_RESYNC) ? "resync" : "not-resync"), (CHECK_FLAG(rinfo->flag, ISIS_ROUTE_FLAG_ACTIVE) ? "active" : "inactive"), buff); } isis_zebra_route_update(dst_p, src_p, rinfo); if (CHECK_FLAG(rinfo->flag, ISIS_ROUTE_FLAG_ACTIVE)) continue; /* Area is either L1 or L2 => we use level route tables * directly for * validating => no problems with deleting routes. */ if (!tables) { isis_route_delete(rnode, table); continue; } /* If area is L1L2, we work with merge table and * therefore must * delete node from level tables as well before deleting * route info. */ for (int level = ISIS_LEVEL1; level <= ISIS_LEVEL2; level++) { drnode = srcdest_rnode_lookup(tables[level - 1], dst_p, src_p); if (!drnode) continue; route_unlock_node(drnode); if (drnode->info != rnode->info) continue; drnode->info = NULL; route_unlock_node(drnode); } isis_route_delete(rnode, table); } } void isis_route_verify_table(struct isis_area *area, struct route_table *table) { _isis_route_verify_table(area, table, NULL); } /* Function to validate route tables for L1L2 areas. In this case we can't use * level route tables directly, we have to merge them at first. L1 routes are * preferred over the L2 ones. * * Merge algorithm is trivial (at least for now). All L1 paths are copied into * merge table at first, then L2 paths are added if L1 path for same prefix * doesn't already exists there. * * FIXME: Is it right place to do it at all? Maybe we should push both levels * to the RIB with different zebra route types and let RIB handle this? */ void isis_route_verify_merge(struct isis_area *area, struct route_table *level1_table, struct route_table *level2_table) { struct route_table *tables[] = { level1_table, level2_table }; struct route_table *merge; struct route_node *rnode, *mrnode; merge = srcdest_table_init(); for (int level = ISIS_LEVEL1; level <= ISIS_LEVEL2; level++) { for (rnode = route_top(tables[level - 1]); rnode; rnode = srcdest_route_next(rnode)) { struct isis_route_info *rinfo = rnode->info; if (!rinfo) continue; struct prefix *prefix; struct prefix_ipv6 *src_p; srcdest_rnode_prefixes(rnode, (const struct prefix **)&prefix, (const struct prefix **)&src_p); mrnode = srcdest_rnode_get(merge, prefix, src_p); struct isis_route_info *mrinfo = mrnode->info; if (mrinfo) { route_unlock_node(mrnode); if (CHECK_FLAG(mrinfo->flag, ISIS_ROUTE_FLAG_ACTIVE)) { /* Clear the ZEBRA_SYNCED flag on the * L2 route when L1 wins, otherwise L2 * won't get reinstalled when L1 * disappears. */ UNSET_FLAG( rinfo->flag, ISIS_ROUTE_FLAG_ZEBRA_SYNCED ); continue; } else if (CHECK_FLAG(rinfo->flag, ISIS_ROUTE_FLAG_ACTIVE)) { /* Clear the ZEBRA_SYNCED flag on the L1 * route when L2 wins, otherwise L1 * won't get reinstalled when it * reappears. */ UNSET_FLAG( mrinfo->flag, ISIS_ROUTE_FLAG_ZEBRA_SYNCED ); } else if ( CHECK_FLAG( mrinfo->flag, ISIS_ROUTE_FLAG_ZEBRA_SYNCED)) { continue; } } mrnode->info = rnode->info; } } _isis_route_verify_table(area, merge, tables); route_table_finish(merge); } void isis_route_invalidate_table(struct isis_area *area, struct route_table *table) { struct route_node *rode; struct isis_route_info *rinfo; for (rode = route_top(table); rode; rode = srcdest_route_next(rode)) { if (rode->info == NULL) continue; rinfo = rode->info; UNSET_FLAG(rinfo->flag, ISIS_ROUTE_FLAG_ACTIVE); } } frr-7.2.1/isisd/isis_route.h0000644000000000000000000000472613610377563012676 00000000000000/* * IS-IS Rout(e)ing protocol - isis_route.h * * Copyright (C) 2001,2002 Sampo Saaristo * Tampere University of Technology * Institute of Communications Engineering * * based on ../ospf6d/ospf6_route.[ch] * by Yasuhiro Ohara * * This program is free software; you can redistribute it and/or modify it * under the terms of the GNU General Public Licenseas 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; see the file COPYING; if not, write to the Free Software * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA */ #ifndef _ZEBRA_ISIS_ROUTE_H #define _ZEBRA_ISIS_ROUTE_H #include "lib/nexthop.h" struct isis_nexthop { ifindex_t ifindex; int family; union g_addr ip; unsigned int lock; }; struct isis_route_info { #define ISIS_ROUTE_FLAG_ACTIVE 0x01 /* active route for the prefix */ #define ISIS_ROUTE_FLAG_ZEBRA_SYNCED 0x02 /* set when route synced to zebra */ #define ISIS_ROUTE_FLAG_ZEBRA_RESYNC 0x04 /* set when route needs to sync */ uint8_t flag; uint32_t cost; uint32_t depth; struct list *nexthops; }; struct isis_route_info *isis_route_create(struct prefix *prefix, struct prefix_ipv6 *src_p, uint32_t cost, uint32_t depth, struct list *adjacencies, struct isis_area *area, struct route_table *table); /* Walk the given table and install new routes to zebra and remove old ones. * route status is tracked using ISIS_ROUTE_FLAG_ACTIVE */ void isis_route_verify_table(struct isis_area *area, struct route_table *table); /* Same as isis_route_verify_table, but merge L1 and L2 routes before */ void isis_route_verify_merge(struct isis_area *area, struct route_table *level1_table, struct route_table *level2_table); /* Unset ISIS_ROUTE_FLAG_ACTIVE on all routes. Used before running spf. */ void isis_route_invalidate_table(struct isis_area *area, struct route_table *table); #endif /* _ZEBRA_ISIS_ROUTE_H */ frr-7.2.1/isisd/isis_routemap.c0000644000000000000000000001514613610377563013365 00000000000000/* * IS-IS Rout(e)ing protocol - isis_routemap.c * * Copyright (C) 2013-2015 Christian Franke * * This program is free software; you can redistribute it and/or modify it * under the terms of the 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; see the file COPYING; if not, write to the Free Software * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA */ #include #include "command.h" #include "filter.h" #include "hash.h" #include "if.h" #include "linklist.h" #include "log.h" #include "memory.h" #include "prefix.h" #include "plist.h" #include "routemap.h" #include "table.h" #include "thread.h" #include "vty.h" #include "isis_constants.h" #include "isis_common.h" #include "isis_flags.h" #include "isisd.h" #include "isis_misc.h" #include "isis_adjacency.h" #include "isis_circuit.h" #include "isis_pdu.h" #include "isis_lsp.h" #include "isis_spf.h" #include "isis_route.h" #include "isis_zebra.h" #include "isis_routemap.h" static enum route_map_cmd_result_t route_match_ip_address(void *rule, const struct prefix *prefix, route_map_object_t type, void *object) { struct access_list *alist; if (type != RMAP_ISIS) return RMAP_NOMATCH; alist = access_list_lookup(AFI_IP, (char *)rule); if (access_list_apply(alist, prefix) != FILTER_DENY) return RMAP_MATCH; return RMAP_NOMATCH; } static void *route_match_ip_address_compile(const char *arg) { return XSTRDUP(MTYPE_ROUTE_MAP_COMPILED, arg); } static void route_match_ip_address_free(void *rule) { XFREE(MTYPE_ROUTE_MAP_COMPILED, rule); } static struct route_map_rule_cmd route_match_ip_address_cmd = { "ip address", route_match_ip_address, route_match_ip_address_compile, route_match_ip_address_free}; /* ------------------------------------------------------------*/ static enum route_map_cmd_result_t route_match_ip_address_prefix_list(void *rule, const struct prefix *prefix, route_map_object_t type, void *object) { struct prefix_list *plist; if (type != RMAP_ISIS) return RMAP_NOMATCH; plist = prefix_list_lookup(AFI_IP, (char *)rule); if (prefix_list_apply(plist, prefix) != PREFIX_DENY) return RMAP_MATCH; return RMAP_NOMATCH; } static void *route_match_ip_address_prefix_list_compile(const char *arg) { return XSTRDUP(MTYPE_ROUTE_MAP_COMPILED, arg); } static void route_match_ip_address_prefix_list_free(void *rule) { XFREE(MTYPE_ROUTE_MAP_COMPILED, rule); } struct route_map_rule_cmd route_match_ip_address_prefix_list_cmd = { "ip address prefix-list", route_match_ip_address_prefix_list, route_match_ip_address_prefix_list_compile, route_match_ip_address_prefix_list_free}; /* ------------------------------------------------------------*/ static enum route_map_cmd_result_t route_match_ipv6_address(void *rule, const struct prefix *prefix, route_map_object_t type, void *object) { struct access_list *alist; if (type != RMAP_ISIS) return RMAP_NOMATCH; alist = access_list_lookup(AFI_IP6, (char *)rule); if (access_list_apply(alist, prefix) != FILTER_DENY) return RMAP_MATCH; return RMAP_NOMATCH; } static void *route_match_ipv6_address_compile(const char *arg) { return XSTRDUP(MTYPE_ROUTE_MAP_COMPILED, arg); } static void route_match_ipv6_address_free(void *rule) { XFREE(MTYPE_ROUTE_MAP_COMPILED, rule); } static struct route_map_rule_cmd route_match_ipv6_address_cmd = { "ipv6 address", route_match_ipv6_address, route_match_ipv6_address_compile, route_match_ipv6_address_free}; /* ------------------------------------------------------------*/ static enum route_map_cmd_result_t route_match_ipv6_address_prefix_list(void *rule, const struct prefix *prefix, route_map_object_t type, void *object) { struct prefix_list *plist; if (type != RMAP_ISIS) return RMAP_NOMATCH; plist = prefix_list_lookup(AFI_IP6, (char *)rule); if (prefix_list_apply(plist, prefix) != PREFIX_DENY) return RMAP_MATCH; return RMAP_NOMATCH; } static void *route_match_ipv6_address_prefix_list_compile(const char *arg) { return XSTRDUP(MTYPE_ROUTE_MAP_COMPILED, arg); } static void route_match_ipv6_address_prefix_list_free(void *rule) { XFREE(MTYPE_ROUTE_MAP_COMPILED, rule); } struct route_map_rule_cmd route_match_ipv6_address_prefix_list_cmd = { "ipv6 address prefix-list", route_match_ipv6_address_prefix_list, route_match_ipv6_address_prefix_list_compile, route_match_ipv6_address_prefix_list_free}; /* ------------------------------------------------------------*/ static enum route_map_cmd_result_t route_set_metric(void *rule, const struct prefix *prefix, route_map_object_t type, void *object) { uint32_t *metric; struct isis_ext_info *info; if (type == RMAP_ISIS) { metric = rule; info = object; info->metric = *metric; } return RMAP_OKAY; } static void *route_set_metric_compile(const char *arg) { unsigned long metric; char *endp; uint32_t *ret; metric = strtoul(arg, &endp, 10); if (arg[0] == '\0' || *endp != '\0' || metric > MAX_WIDE_PATH_METRIC) return NULL; ret = XCALLOC(MTYPE_ROUTE_MAP_COMPILED, sizeof(*ret)); *ret = metric; return ret; } static void route_set_metric_free(void *rule) { XFREE(MTYPE_ROUTE_MAP_COMPILED, rule); } static struct route_map_rule_cmd route_set_metric_cmd = { "metric", route_set_metric, route_set_metric_compile, route_set_metric_free}; void isis_route_map_init(void) { route_map_init(); route_map_match_ip_address_hook(generic_match_add); route_map_no_match_ip_address_hook(generic_match_delete); route_map_match_ip_address_prefix_list_hook(generic_match_add); route_map_no_match_ip_address_prefix_list_hook(generic_match_delete); route_map_match_ipv6_address_hook(generic_match_add); route_map_no_match_ipv6_address_hook(generic_match_delete); route_map_match_ipv6_address_prefix_list_hook(generic_match_add); route_map_no_match_ipv6_address_prefix_list_hook(generic_match_delete); route_map_set_metric_hook(generic_set_add); route_map_no_set_metric_hook(generic_set_delete); route_map_install_match(&route_match_ip_address_cmd); route_map_install_match(&route_match_ip_address_prefix_list_cmd); route_map_install_match(&route_match_ipv6_address_cmd); route_map_install_match(&route_match_ipv6_address_prefix_list_cmd); route_map_install_set(&route_set_metric_cmd); } frr-7.2.1/isisd/isis_routemap.h0000644000000000000000000000167413610377563013373 00000000000000/* * IS-IS Rout(e)ing protocol - isis_routemap.h * * Copyright (C) 2013-2015 Christian Franke * * This program is free software; you can redistribute it and/or modify it * under the terms of the 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; see the file COPYING; if not, write to the Free Software * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA */ #ifndef ISIS_ROUTEMAP_H #define ISIS_ROUTEMAP_H void isis_route_map_init(void); #endif frr-7.2.1/isisd/isis_spf.c0000644000000000000000000011507113610377563012317 00000000000000/* * IS-IS Rout(e)ing protocol - isis_spf.c * The SPT algorithm * * Copyright (C) 2001,2002 Sampo Saaristo * Tampere University of Technology * Institute of Communications Engineering * Copyright (C) 2017 Christian Franke * * This program is free software; you can redistribute it and/or modify it * under the terms of the GNU General Public Licenseas 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; see the file COPYING; if not, write to the Free Software * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA */ #include #include "thread.h" #include "linklist.h" #include "vty.h" #include "log.h" #include "command.h" #include "memory.h" #include "prefix.h" #include "if.h" #include "table.h" #include "spf_backoff.h" #include "srcdest_table.h" #include "isis_constants.h" #include "isis_common.h" #include "isis_flags.h" #include "isisd.h" #include "isis_misc.h" #include "isis_adjacency.h" #include "isis_circuit.h" #include "isis_pdu.h" #include "isis_lsp.h" #include "isis_dynhn.h" #include "isis_spf.h" #include "isis_route.h" #include "isis_csm.h" #include "isis_mt.h" #include "isis_tlvs.h" #include "fabricd.h" #include "isis_spf_private.h" DEFINE_MTYPE_STATIC(ISISD, ISIS_SPF_RUN, "ISIS SPF Run Info"); /* * supports the given af ? */ static bool speaks(uint8_t *protocols, uint8_t count, int family) { for (uint8_t i = 0; i < count; i++) { if (family == AF_INET && protocols[i] == NLPID_IP) return true; if (family == AF_INET6 && protocols[i] == NLPID_IPV6) return true; } return false; } struct isis_spf_run { struct isis_area *area; int level; }; /* 7.2.7 */ static void remove_excess_adjs(struct list *adjs) { struct listnode *node, *excess = NULL; struct isis_adjacency *adj, *candidate = NULL; int comp; for (ALL_LIST_ELEMENTS_RO(adjs, node, adj)) { if (excess == NULL) excess = node; candidate = listgetdata(excess); if (candidate->sys_type < adj->sys_type) { excess = node; continue; } if (candidate->sys_type > adj->sys_type) continue; comp = memcmp(candidate->sysid, adj->sysid, ISIS_SYS_ID_LEN); if (comp > 0) { excess = node; continue; } if (comp < 0) continue; if (candidate->circuit->idx > adj->circuit->idx) { excess = node; continue; } if (candidate->circuit->idx < adj->circuit->idx) continue; comp = memcmp(candidate->snpa, adj->snpa, ETH_ALEN); if (comp > 0) { excess = node; continue; } } list_delete_node(adjs, excess); return; } static const char *vtype2string(enum vertextype vtype) { switch (vtype) { case VTYPE_PSEUDO_IS: return "pseudo_IS"; break; case VTYPE_PSEUDO_TE_IS: return "pseudo_TE-IS"; break; case VTYPE_NONPSEUDO_IS: return "IS"; break; case VTYPE_NONPSEUDO_TE_IS: return "TE-IS"; break; case VTYPE_ES: return "ES"; break; case VTYPE_IPREACH_INTERNAL: return "IP internal"; break; case VTYPE_IPREACH_EXTERNAL: return "IP external"; break; case VTYPE_IPREACH_TE: return "IP TE"; break; case VTYPE_IP6REACH_INTERNAL: return "IP6 internal"; break; case VTYPE_IP6REACH_EXTERNAL: return "IP6 external"; break; default: return "UNKNOWN"; } return NULL; /* Not reached */ } const char *vid2string(struct isis_vertex *vertex, char *buff, int size) { if (VTYPE_IS(vertex->type) || VTYPE_ES(vertex->type)) { return print_sys_hostname(vertex->N.id); } if (VTYPE_IP(vertex->type)) { srcdest2str(&vertex->N.ip.dest, &vertex->N.ip.src, buff, size); return buff; } return "UNKNOWN"; } static struct isis_vertex *isis_vertex_new(struct isis_spftree *spftree, void *id, enum vertextype vtype) { struct isis_vertex *vertex; vertex = XCALLOC(MTYPE_ISIS_VERTEX, sizeof(struct isis_vertex)); isis_vertex_id_init(vertex, id, vtype); vertex->Adj_N = list_new(); vertex->parents = list_new(); if (spftree->hopcount_metric) { vertex->firsthops = hash_create(isis_vertex_queue_hash_key, isis_vertex_queue_hash_cmp, NULL); } return vertex; } static void isis_vertex_adj_del(struct isis_vertex *vertex, struct isis_adjacency *adj) { struct listnode *node, *nextnode; if (!vertex) return; for (node = listhead(vertex->Adj_N); node; node = nextnode) { nextnode = listnextnode(node); if (listgetdata(node) == adj) list_delete_node(vertex->Adj_N, node); } return; } struct isis_spftree *isis_spftree_new(struct isis_area *area) { struct isis_spftree *tree; tree = XCALLOC(MTYPE_ISIS_SPFTREE, sizeof(struct isis_spftree)); isis_vertex_queue_init(&tree->tents, "IS-IS SPF tents", true); isis_vertex_queue_init(&tree->paths, "IS-IS SPF paths", false); tree->route_table = srcdest_table_init(); tree->area = area; tree->last_run_timestamp = 0; tree->last_run_monotime = 0; tree->last_run_duration = 0; tree->runcount = 0; return tree; } void isis_spftree_del(struct isis_spftree *spftree) { isis_vertex_queue_free(&spftree->tents); isis_vertex_queue_free(&spftree->paths); route_table_finish(spftree->route_table); spftree->route_table = NULL; XFREE(MTYPE_ISIS_SPFTREE, spftree); return; } static void isis_spftree_adj_del(struct isis_spftree *spftree, struct isis_adjacency *adj) { struct listnode *node; struct isis_vertex *v; if (!adj) return; assert(!isis_vertex_queue_count(&spftree->tents)); for (ALL_QUEUE_ELEMENTS_RO(&spftree->paths, node, v)) isis_vertex_adj_del(v, adj); return; } void spftree_area_init(struct isis_area *area) { for (int tree = SPFTREE_IPV4; tree < SPFTREE_COUNT; tree++) { for (int level = ISIS_LEVEL1; level <= ISIS_LEVEL2; level++) { if (!(area->is_type & level)) continue; if (area->spftree[tree][level - 1]) continue; area->spftree[tree][level - 1] = isis_spftree_new(area); } } } void spftree_area_del(struct isis_area *area) { for (int tree = SPFTREE_IPV4; tree < SPFTREE_COUNT; tree++) { for (int level = ISIS_LEVEL1; level <= ISIS_LEVEL2; level++) { if (!(area->is_type & level)) continue; if (!area->spftree[tree][level - 1]) continue; isis_spftree_del(area->spftree[tree][level - 1]); } } } void spftree_area_adj_del(struct isis_area *area, struct isis_adjacency *adj) { for (int tree = SPFTREE_IPV4; tree < SPFTREE_COUNT; tree++) { for (int level = ISIS_LEVEL1; level <= ISIS_LEVEL2; level++) { if (!(area->is_type & level)) continue; if (!area->spftree[tree][level - 1]) continue; isis_spftree_adj_del(area->spftree[tree][level - 1], adj); } } if (fabricd_spftree(area) != NULL) isis_spftree_adj_del(fabricd_spftree(area), adj); } /* * Find the system LSP: returns the LSP in our LSP database * associated with the given system ID. */ static struct isis_lsp *isis_root_system_lsp(struct isis_area *area, int level, uint8_t *sysid) { struct isis_lsp *lsp; uint8_t lspid[ISIS_SYS_ID_LEN + 2]; memcpy(lspid, sysid, ISIS_SYS_ID_LEN); LSP_PSEUDO_ID(lspid) = 0; LSP_FRAGMENT(lspid) = 0; lsp = lsp_search(&area->lspdb[level - 1], lspid); if (lsp && lsp->hdr.rem_lifetime != 0) return lsp; return NULL; } /* * Add this IS to the root of SPT */ static struct isis_vertex *isis_spf_add_root(struct isis_spftree *spftree, uint8_t *sysid) { struct isis_vertex *vertex; struct isis_lsp *lsp; #ifdef EXTREME_DEBUG char buff[VID2STR_BUFFER]; #endif /* EXTREME_DEBUG */ lsp = isis_root_system_lsp(spftree->area, spftree->level, sysid); if (lsp == NULL) zlog_warn("ISIS-Spf: could not find own l%d LSP!", spftree->level); vertex = isis_vertex_new(spftree, sysid, spftree->area->oldmetric ? VTYPE_NONPSEUDO_IS : VTYPE_NONPSEUDO_TE_IS); isis_vertex_queue_append(&spftree->paths, vertex); #ifdef EXTREME_DEBUG zlog_debug("ISIS-Spf: added this IS %s %s depth %d dist %d to PATHS", vtype2string(vertex->type), vid2string(vertex, buff, sizeof(buff)), vertex->depth, vertex->d_N); #endif /* EXTREME_DEBUG */ return vertex; } static void vertex_add_parent_firsthop(struct hash_bucket *bucket, void *arg) { struct isis_vertex *vertex = arg; struct isis_vertex *hop = bucket->data; hash_get(vertex->firsthops, hop, hash_alloc_intern); } static void vertex_update_firsthops(struct isis_vertex *vertex, struct isis_vertex *parent) { if (vertex->d_N <= 2) hash_get(vertex->firsthops, vertex, hash_alloc_intern); if (vertex->d_N < 2 || !parent) return; hash_iterate(parent->firsthops, vertex_add_parent_firsthop, vertex); } /* * Add a vertex to TENT sorted by cost and by vertextype on tie break situation */ static struct isis_vertex *isis_spf_add2tent(struct isis_spftree *spftree, enum vertextype vtype, void *id, uint32_t cost, int depth, struct isis_adjacency *adj, struct isis_vertex *parent) { struct isis_vertex *vertex; struct listnode *node; struct isis_adjacency *parent_adj; #ifdef EXTREME_DEBUG char buff[VID2STR_BUFFER]; #endif assert(isis_find_vertex(&spftree->paths, id, vtype) == NULL); assert(isis_find_vertex(&spftree->tents, id, vtype) == NULL); vertex = isis_vertex_new(spftree, id, vtype); vertex->d_N = cost; vertex->depth = depth; if (parent) { listnode_add(vertex->parents, parent); } if (spftree->hopcount_metric) vertex_update_firsthops(vertex, parent); if (parent && parent->Adj_N && listcount(parent->Adj_N) > 0) { for (ALL_LIST_ELEMENTS_RO(parent->Adj_N, node, parent_adj)) listnode_add(vertex->Adj_N, parent_adj); } else if (adj) { listnode_add(vertex->Adj_N, adj); } #ifdef EXTREME_DEBUG zlog_debug( "ISIS-Spf: add to TENT %s %s %s depth %d dist %d adjcount %d", print_sys_hostname(vertex->N.id), vtype2string(vertex->type), vid2string(vertex, buff, sizeof(buff)), vertex->depth, vertex->d_N, listcount(vertex->Adj_N)); #endif /* EXTREME_DEBUG */ isis_vertex_queue_insert(&spftree->tents, vertex); return vertex; } static void isis_spf_add_local(struct isis_spftree *spftree, enum vertextype vtype, void *id, struct isis_adjacency *adj, uint32_t cost, struct isis_vertex *parent) { struct isis_vertex *vertex; vertex = isis_find_vertex(&spftree->tents, id, vtype); if (vertex) { /* C.2.5 c) */ if (vertex->d_N == cost) { if (adj) listnode_add(vertex->Adj_N, adj); /* d) */ if (listcount(vertex->Adj_N) > ISIS_MAX_PATH_SPLITS) remove_excess_adjs(vertex->Adj_N); if (parent && (listnode_lookup(vertex->parents, parent) == NULL)) listnode_add(vertex->parents, parent); return; } else if (vertex->d_N < cost) { /* e) do nothing */ return; } else { /* vertex->d_N > cost */ /* f) */ isis_vertex_queue_delete(&spftree->tents, vertex); isis_vertex_del(vertex); } } isis_spf_add2tent(spftree, vtype, id, cost, 1, adj, parent); return; } static void process_N(struct isis_spftree *spftree, enum vertextype vtype, void *id, uint32_t dist, uint16_t depth, struct isis_vertex *parent) { struct isis_vertex *vertex; #ifdef EXTREME_DEBUG char buff[VID2STR_BUFFER]; #endif assert(spftree && parent); if (spftree->hopcount_metric && !VTYPE_IS(vtype)) return; struct prefix_pair p; if (vtype >= VTYPE_IPREACH_INTERNAL) { memcpy(&p, id, sizeof(p)); apply_mask(&p.dest); apply_mask((struct prefix *)&p.src); id = &p; } /* RFC3787 section 5.1 */ if (spftree->area->newmetric == 1) { if (dist > MAX_WIDE_PATH_METRIC) return; } /* C.2.6 b) */ else if (spftree->area->oldmetric == 1) { if (dist > MAX_NARROW_PATH_METRIC) return; } /* c) */ vertex = isis_find_vertex(&spftree->paths, id, vtype); if (vertex) { #ifdef EXTREME_DEBUG zlog_debug( "ISIS-Spf: process_N %s %s %s dist %d already found from PATH", print_sys_hostname(vertex->N.id), vtype2string(vtype), vid2string(vertex, buff, sizeof(buff)), dist); #endif /* EXTREME_DEBUG */ assert(dist >= vertex->d_N); return; } vertex = isis_find_vertex(&spftree->tents, id, vtype); /* d) */ if (vertex) { /* 1) */ #ifdef EXTREME_DEBUG zlog_debug( "ISIS-Spf: process_N %s %s %s dist %d parent %s adjcount %d", print_sys_hostname(vertex->N.id), vtype2string(vtype), vid2string(vertex, buff, sizeof(buff)), dist, (parent ? print_sys_hostname(parent->N.id) : "null"), (parent ? listcount(parent->Adj_N) : 0)); #endif /* EXTREME_DEBUG */ if (vertex->d_N == dist) { struct listnode *node; struct isis_adjacency *parent_adj; for (ALL_LIST_ELEMENTS_RO(parent->Adj_N, node, parent_adj)) if (listnode_lookup(vertex->Adj_N, parent_adj) == NULL) listnode_add(vertex->Adj_N, parent_adj); if (spftree->hopcount_metric) vertex_update_firsthops(vertex, parent); /* 2) */ if (listcount(vertex->Adj_N) > ISIS_MAX_PATH_SPLITS) remove_excess_adjs(vertex->Adj_N); if (listnode_lookup(vertex->parents, parent) == NULL) listnode_add(vertex->parents, parent); return; } else if (vertex->d_N < dist) { return; /* 4) */ } else { isis_vertex_queue_delete(&spftree->tents, vertex); isis_vertex_del(vertex); } } #ifdef EXTREME_DEBUG zlog_debug("ISIS-Spf: process_N add2tent %s %s dist %d parent %s", print_sys_hostname(id), vtype2string(vtype), dist, (parent ? print_sys_hostname(parent->N.id) : "null")); #endif /* EXTREME_DEBUG */ isis_spf_add2tent(spftree, vtype, id, dist, depth, NULL, parent); return; } /* * C.2.6 Step 1 */ static int isis_spf_process_lsp(struct isis_spftree *spftree, struct isis_lsp *lsp, uint32_t cost, uint16_t depth, uint8_t *root_sysid, struct isis_vertex *parent) { bool pseudo_lsp = LSP_PSEUDO_ID(lsp->hdr.lsp_id); struct listnode *fragnode = NULL; uint32_t dist; enum vertextype vtype; static const uint8_t null_sysid[ISIS_SYS_ID_LEN]; struct isis_mt_router_info *mt_router_info = NULL; struct prefix_pair ip_info; if (!lsp->tlvs) return ISIS_OK; if (spftree->mtid != ISIS_MT_IPV4_UNICAST) mt_router_info = isis_tlvs_lookup_mt_router_info(lsp->tlvs, spftree->mtid); if (!pseudo_lsp && (spftree->mtid == ISIS_MT_IPV4_UNICAST && !speaks(lsp->tlvs->protocols_supported.protocols, lsp->tlvs->protocols_supported.count, spftree->family)) && !mt_router_info) return ISIS_OK; /* RFC3787 section 4 SHOULD ignore overload bit in pseudo LSPs */ bool no_overload = (pseudo_lsp || (spftree->mtid == ISIS_MT_IPV4_UNICAST && !ISIS_MASK_LSP_OL_BIT(lsp->hdr.lsp_bits)) || (mt_router_info && !mt_router_info->overload)); lspfragloop: if (lsp->hdr.seqno == 0) { zlog_warn( "isis_spf_process_lsp(): lsp with 0 seq_num - ignore"); return ISIS_WARNING; } #ifdef EXTREME_DEBUG zlog_debug("ISIS-Spf: process_lsp %s", print_sys_hostname(lsp->hdr.lsp_id)); #endif /* EXTREME_DEBUG */ if (no_overload) { if (pseudo_lsp || spftree->mtid == ISIS_MT_IPV4_UNICAST) { struct isis_oldstyle_reach *r; for (r = (struct isis_oldstyle_reach *) lsp->tlvs->oldstyle_reach.head; r; r = r->next) { if (fabricd) continue; /* C.2.6 a) */ /* Two way connectivity */ if (!memcmp(r->id, root_sysid, ISIS_SYS_ID_LEN)) continue; if (!pseudo_lsp && !memcmp(r->id, null_sysid, ISIS_SYS_ID_LEN)) continue; dist = cost + r->metric; process_N(spftree, LSP_PSEUDO_ID(r->id) ? VTYPE_PSEUDO_IS : VTYPE_NONPSEUDO_IS, (void *)r->id, dist, depth + 1, parent); } } struct isis_item_list *te_neighs = NULL; if (pseudo_lsp || spftree->mtid == ISIS_MT_IPV4_UNICAST) te_neighs = &lsp->tlvs->extended_reach; else te_neighs = isis_lookup_mt_items(&lsp->tlvs->mt_reach, spftree->mtid); struct isis_extended_reach *er; for (er = te_neighs ? (struct isis_extended_reach *) te_neighs->head : NULL; er; er = er->next) { if (!memcmp(er->id, root_sysid, ISIS_SYS_ID_LEN)) continue; if (!pseudo_lsp && !memcmp(er->id, null_sysid, ISIS_SYS_ID_LEN)) continue; dist = cost + (spftree->hopcount_metric ? 1 : er->metric); process_N(spftree, LSP_PSEUDO_ID(er->id) ? VTYPE_PSEUDO_TE_IS : VTYPE_NONPSEUDO_TE_IS, (void *)er->id, dist, depth + 1, parent); } } if (!fabricd && !pseudo_lsp && spftree->family == AF_INET && spftree->mtid == ISIS_MT_IPV4_UNICAST) { struct isis_item_list *reachs[] = { &lsp->tlvs->oldstyle_ip_reach, &lsp->tlvs->oldstyle_ip_reach_ext}; for (unsigned int i = 0; i < array_size(reachs); i++) { vtype = i ? VTYPE_IPREACH_EXTERNAL : VTYPE_IPREACH_INTERNAL; memset(&ip_info, 0, sizeof(ip_info)); ip_info.dest.family = AF_INET; struct isis_oldstyle_ip_reach *r; for (r = (struct isis_oldstyle_ip_reach *)reachs[i] ->head; r; r = r->next) { dist = cost + r->metric; ip_info.dest.u.prefix4 = r->prefix.prefix; ip_info.dest.prefixlen = r->prefix.prefixlen; process_N(spftree, vtype, &ip_info, dist, depth + 1, parent); } } } if (!pseudo_lsp && spftree->family == AF_INET) { struct isis_item_list *ipv4_reachs; if (spftree->mtid == ISIS_MT_IPV4_UNICAST) ipv4_reachs = &lsp->tlvs->extended_ip_reach; else ipv4_reachs = isis_lookup_mt_items( &lsp->tlvs->mt_ip_reach, spftree->mtid); memset(&ip_info, 0, sizeof(ip_info)); ip_info.dest.family = AF_INET; struct isis_extended_ip_reach *r; for (r = ipv4_reachs ? (struct isis_extended_ip_reach *) ipv4_reachs->head : NULL; r; r = r->next) { dist = cost + r->metric; ip_info.dest.u.prefix4 = r->prefix.prefix; ip_info.dest.prefixlen = r->prefix.prefixlen; process_N(spftree, VTYPE_IPREACH_TE, &ip_info, dist, depth + 1, parent); } } if (!pseudo_lsp && spftree->family == AF_INET6) { struct isis_item_list *ipv6_reachs; if (spftree->mtid == ISIS_MT_IPV4_UNICAST) ipv6_reachs = &lsp->tlvs->ipv6_reach; else ipv6_reachs = isis_lookup_mt_items( &lsp->tlvs->mt_ipv6_reach, spftree->mtid); struct isis_ipv6_reach *r; for (r = ipv6_reachs ? (struct isis_ipv6_reach *)ipv6_reachs->head : NULL; r; r = r->next) { dist = cost + r->metric; vtype = r->external ? VTYPE_IP6REACH_EXTERNAL : VTYPE_IP6REACH_INTERNAL; memset(&ip_info, 0, sizeof(ip_info)); ip_info.dest.family = AF_INET6; ip_info.dest.u.prefix6 = r->prefix.prefix; ip_info.dest.prefixlen = r->prefix.prefixlen; if (r->subtlvs && r->subtlvs->source_prefix && r->subtlvs->source_prefix->prefixlen) { if (spftree->tree_id != SPFTREE_DSTSRC) { char buff[VID2STR_BUFFER]; zlog_warn("Ignoring dest-src route %s in non dest-src topology", srcdest2str( &ip_info.dest, r->subtlvs->source_prefix, buff, sizeof(buff) ) ); continue; } ip_info.src = *r->subtlvs->source_prefix; } process_N(spftree, vtype, &ip_info, dist, depth + 1, parent); } } if (fragnode == NULL) fragnode = listhead(lsp->lspu.frags); else fragnode = listnextnode(fragnode); if (fragnode) { lsp = listgetdata(fragnode); goto lspfragloop; } return ISIS_OK; } static int isis_spf_preload_tent(struct isis_spftree *spftree, uint8_t *root_sysid, struct isis_vertex *parent) { struct isis_circuit *circuit; struct listnode *cnode, *anode, *ipnode; struct isis_adjacency *adj; struct isis_lsp *lsp; struct list *adj_list; struct list *adjdb; struct prefix_ipv4 *ipv4; struct prefix_pair ip_info; int retval = ISIS_OK; uint8_t lsp_id[ISIS_SYS_ID_LEN + 2]; static uint8_t null_lsp_id[ISIS_SYS_ID_LEN + 2]; struct prefix_ipv6 *ipv6; struct isis_circuit_mt_setting *circuit_mt; for (ALL_LIST_ELEMENTS_RO(spftree->area->circuit_list, cnode, circuit)) { circuit_mt = circuit_lookup_mt_setting(circuit, spftree->mtid); if (circuit_mt && !circuit_mt->enabled) continue; if (circuit->state != C_STATE_UP) continue; if (!(circuit->is_type & spftree->level)) continue; if (spftree->family == AF_INET && !circuit->ip_router) continue; if (spftree->family == AF_INET6 && !circuit->ipv6_router) continue; /* * Add IP(v6) addresses of this circuit */ if (spftree->family == AF_INET && !spftree->hopcount_metric) { memset(&ip_info, 0, sizeof(ip_info)); ip_info.dest.family = AF_INET; for (ALL_LIST_ELEMENTS_RO(circuit->ip_addrs, ipnode, ipv4)) { ip_info.dest.u.prefix4 = ipv4->prefix; ip_info.dest.prefixlen = ipv4->prefixlen; apply_mask(&ip_info.dest); isis_spf_add_local(spftree, VTYPE_IPREACH_INTERNAL, &ip_info, NULL, 0, parent); } } if (spftree->family == AF_INET6 && !spftree->hopcount_metric) { memset(&ip_info, 0, sizeof(ip_info)); ip_info.dest.family = AF_INET6; for (ALL_LIST_ELEMENTS_RO(circuit->ipv6_non_link, ipnode, ipv6)) { ip_info.dest.u.prefix6 = ipv6->prefix; ip_info.dest.prefixlen = ipv6->prefixlen; apply_mask(&ip_info.dest); isis_spf_add_local(spftree, VTYPE_IP6REACH_INTERNAL, &ip_info, NULL, 0, parent); } } if (circuit->circ_type == CIRCUIT_T_BROADCAST) { /* * Add the adjacencies */ adj_list = list_new(); adjdb = circuit->u.bc.adjdb[spftree->level - 1]; isis_adj_build_up_list(adjdb, adj_list); if (listcount(adj_list) == 0) { list_delete(&adj_list); if (isis->debugs & DEBUG_SPF_EVENTS) zlog_debug( "ISIS-Spf: no L%d adjacencies on circuit %s", spftree->level, circuit->interface->name); continue; } for (ALL_LIST_ELEMENTS_RO(adj_list, anode, adj)) { if (!adj_has_mt(adj, spftree->mtid)) continue; if (spftree->mtid == ISIS_MT_IPV4_UNICAST && !speaks(adj->nlpids.nlpids, adj->nlpids.count, spftree->family)) continue; switch (adj->sys_type) { case ISIS_SYSTYPE_ES: memcpy(lsp_id, adj->sysid, ISIS_SYS_ID_LEN); LSP_PSEUDO_ID(lsp_id) = 0; isis_spf_add_local( spftree, VTYPE_ES, lsp_id, adj, spftree->hopcount_metric ? 1 : circuit->te_metric [spftree->level - 1], parent); break; case ISIS_SYSTYPE_IS: case ISIS_SYSTYPE_L1_IS: case ISIS_SYSTYPE_L2_IS: memcpy(lsp_id, adj->sysid, ISIS_SYS_ID_LEN); LSP_PSEUDO_ID(lsp_id) = 0; LSP_FRAGMENT(lsp_id) = 0; isis_spf_add_local( spftree, spftree->area->oldmetric ? VTYPE_NONPSEUDO_IS : VTYPE_NONPSEUDO_TE_IS, lsp_id, adj, spftree->hopcount_metric ? 1 : circuit->te_metric [spftree->level - 1], parent); lsp = lsp_search( &spftree->area->lspdb[spftree->level- 1], lsp_id); if (lsp == NULL || lsp->hdr.rem_lifetime == 0) zlog_warn( "ISIS-Spf: No LSP %s found for IS adjacency " "L%d on %s (ID %u)", rawlspid_print(lsp_id), spftree->level, circuit->interface->name, circuit->circuit_id); break; case ISIS_SYSTYPE_UNKNOWN: default: zlog_warn( "isis_spf_preload_tent unknown adj type"); } } list_delete(&adj_list); /* * Add the pseudonode */ if (spftree->level == 1) memcpy(lsp_id, circuit->u.bc.l1_desig_is, ISIS_SYS_ID_LEN + 1); else memcpy(lsp_id, circuit->u.bc.l2_desig_is, ISIS_SYS_ID_LEN + 1); /* can happen during DR reboot */ if (memcmp(lsp_id, null_lsp_id, ISIS_SYS_ID_LEN + 1) == 0) { if (isis->debugs & DEBUG_SPF_EVENTS) zlog_debug( "ISIS-Spf: No L%d DR on %s (ID %d)", spftree->level, circuit->interface->name, circuit->circuit_id); continue; } adj = isis_adj_lookup(lsp_id, adjdb); /* if no adj, we are the dis or error */ if (!adj && !circuit->u.bc.is_dr[spftree->level - 1]) { zlog_warn( "ISIS-Spf: No adjacency found from root " "to L%d DR %s on %s (ID %d)", spftree->level, rawlspid_print(lsp_id), circuit->interface->name, circuit->circuit_id); continue; } lsp = lsp_search( &spftree->area->lspdb[spftree->level - 1], lsp_id); if (lsp == NULL || lsp->hdr.rem_lifetime == 0) { zlog_warn( "ISIS-Spf: No lsp (%p) found from root " "to L%d DR %s on %s (ID %d)", (void *)lsp, spftree->level, rawlspid_print(lsp_id), circuit->interface->name, circuit->circuit_id); continue; } isis_spf_process_lsp(spftree, lsp, spftree->hopcount_metric ? 1 : circuit->te_metric[spftree->level - 1], 0, root_sysid, parent); } else if (circuit->circ_type == CIRCUIT_T_P2P) { adj = circuit->u.p2p.neighbor; if (!adj || adj->adj_state != ISIS_ADJ_UP) continue; if (!adj_has_mt(adj, spftree->mtid)) continue; switch (adj->sys_type) { case ISIS_SYSTYPE_ES: memcpy(lsp_id, adj->sysid, ISIS_SYS_ID_LEN); LSP_PSEUDO_ID(lsp_id) = 0; isis_spf_add_local( spftree, VTYPE_ES, lsp_id, adj, spftree->hopcount_metric ? 1 : circuit->te_metric[spftree->level - 1], parent); break; case ISIS_SYSTYPE_IS: case ISIS_SYSTYPE_L1_IS: case ISIS_SYSTYPE_L2_IS: memcpy(lsp_id, adj->sysid, ISIS_SYS_ID_LEN); LSP_PSEUDO_ID(lsp_id) = 0; LSP_FRAGMENT(lsp_id) = 0; if (spftree->mtid != ISIS_MT_IPV4_UNICAST || speaks(adj->nlpids.nlpids, adj->nlpids.count, spftree->family)) isis_spf_add_local( spftree, spftree->area->oldmetric ? VTYPE_NONPSEUDO_IS : VTYPE_NONPSEUDO_TE_IS, lsp_id, adj, spftree->hopcount_metric ? 1 : circuit->te_metric [spftree->level - 1], parent); break; case ISIS_SYSTYPE_UNKNOWN: default: zlog_warn( "isis_spf_preload_tent unknown adj type"); break; } } else if (circuit->circ_type == CIRCUIT_T_LOOPBACK) { continue; } else { zlog_warn("isis_spf_preload_tent unsupported media"); retval = ISIS_WARNING; } } return retval; } /* * The parent(s) for vertex is set when added to TENT list * now we just put the child pointer(s) in place */ static void add_to_paths(struct isis_spftree *spftree, struct isis_vertex *vertex) { char buff[VID2STR_BUFFER]; if (isis_find_vertex(&spftree->paths, &vertex->N, vertex->type)) return; isis_vertex_queue_append(&spftree->paths, vertex); #ifdef EXTREME_DEBUG zlog_debug("ISIS-Spf: added %s %s %s depth %d dist %d to PATHS", print_sys_hostname(vertex->N.id), vtype2string(vertex->type), vid2string(vertex, buff, sizeof(buff)), vertex->depth, vertex->d_N); #endif /* EXTREME_DEBUG */ if (VTYPE_IP(vertex->type)) { if (listcount(vertex->Adj_N) > 0) isis_route_create(&vertex->N.ip.dest, &vertex->N.ip.src, vertex->d_N, vertex->depth, vertex->Adj_N, spftree->area, spftree->route_table); else if (isis->debugs & DEBUG_SPF_EVENTS) zlog_debug( "ISIS-Spf: no adjacencies do not install route for " "%s depth %d dist %d", vid2string(vertex, buff, sizeof(buff)), vertex->depth, vertex->d_N); } return; } static void init_spt(struct isis_spftree *spftree, int mtid, int level, int family, enum spf_tree_id tree_id, bool hopcount_metric) { isis_vertex_queue_clear(&spftree->tents); isis_vertex_queue_clear(&spftree->paths); spftree->mtid = mtid; spftree->level = level; spftree->family = family; spftree->tree_id = tree_id; spftree->hopcount_metric = hopcount_metric; } static void isis_spf_loop(struct isis_spftree *spftree, uint8_t *root_sysid) { struct isis_vertex *vertex; struct isis_lsp *lsp; while (isis_vertex_queue_count(&spftree->tents)) { vertex = isis_vertex_queue_pop(&spftree->tents); #ifdef EXTREME_DEBUG zlog_debug( "ISIS-Spf: get TENT node %s %s depth %d dist %d to PATHS", print_sys_hostname(vertex->N.id), vtype2string(vertex->type), vertex->depth, vertex->d_N); #endif /* EXTREME_DEBUG */ add_to_paths(spftree, vertex); if (!VTYPE_IS(vertex->type)) continue; lsp = lsp_for_vertex(spftree, vertex); if (!lsp) { zlog_warn("ISIS-Spf: No LSP found for %s", isis_format_id(vertex->N.id, sizeof(vertex->N.id))); continue; } isis_spf_process_lsp(spftree, lsp, vertex->d_N, vertex->depth, root_sysid, vertex); } } struct isis_spftree *isis_run_hopcount_spf(struct isis_area *area, uint8_t *sysid, struct isis_spftree *spftree) { if (!spftree) spftree = isis_spftree_new(area); init_spt(spftree, ISIS_MT_IPV4_UNICAST, ISIS_LEVEL2, AF_INET, SPFTREE_IPV4, true); if (!memcmp(sysid, isis->sysid, ISIS_SYS_ID_LEN)) { /* If we are running locally, initialize with information from adjacencies */ struct isis_vertex *root = isis_spf_add_root(spftree, sysid); isis_spf_preload_tent(spftree, sysid, root); } else { isis_vertex_queue_insert(&spftree->tents, isis_vertex_new( spftree, sysid, VTYPE_NONPSEUDO_TE_IS)); } isis_spf_loop(spftree, sysid); return spftree; } static int isis_run_spf(struct isis_area *area, int level, enum spf_tree_id tree_id, uint8_t *sysid, struct timeval *nowtv) { int retval = ISIS_OK; struct isis_vertex *root_vertex; struct isis_spftree *spftree = area->spftree[tree_id][level - 1]; struct timeval time_now; unsigned long long start_time, end_time; uint16_t mtid = 0; /* Get time that can't roll backwards. */ start_time = nowtv->tv_sec; start_time = (start_time * 1000000) + nowtv->tv_usec; int family = -1; switch (tree_id) { case SPFTREE_IPV4: family = AF_INET; mtid = ISIS_MT_IPV4_UNICAST; break; case SPFTREE_IPV6: family = AF_INET6; mtid = isis_area_ipv6_topology(area); break; case SPFTREE_DSTSRC: family = AF_INET6; mtid = ISIS_MT_IPV6_DSTSRC; break; case SPFTREE_COUNT: assert(!"isis_run_spf should never be called with SPFTREE_COUNT as argument!"); return ISIS_WARNING; } assert(spftree); assert(sysid); /* * C.2.5 Step 0 */ init_spt(spftree, mtid, level, family, tree_id, false); /* a) */ root_vertex = isis_spf_add_root(spftree, sysid); /* b) */ retval = isis_spf_preload_tent(spftree, sysid, root_vertex); if (retval != ISIS_OK) { zlog_warn("ISIS-Spf: failed to load TENT SPF-root:%s", print_sys_hostname(sysid)); goto out; } /* * C.2.7 Step 2 */ if (!isis_vertex_queue_count(&spftree->tents) && (isis->debugs & DEBUG_SPF_EVENTS)) { zlog_warn("ISIS-Spf: TENT is empty SPF-root:%s", print_sys_hostname(sysid)); } isis_spf_loop(spftree, sysid); out: spftree->runcount++; spftree->last_run_timestamp = time(NULL); spftree->last_run_monotime = monotime(&time_now); end_time = time_now.tv_sec; end_time = (end_time * 1000000) + time_now.tv_usec; spftree->last_run_duration = end_time - start_time; return retval; } void isis_spf_verify_routes(struct isis_area *area, struct isis_spftree **trees) { if (area->is_type == IS_LEVEL_1) { isis_route_verify_table(area, trees[0]->route_table); } else if (area->is_type == IS_LEVEL_2) { isis_route_verify_table(area, trees[1]->route_table); } else { isis_route_verify_merge(area, trees[0]->route_table, trees[1]->route_table); } } void isis_spf_invalidate_routes(struct isis_spftree *tree) { isis_route_invalidate_table(tree->area, tree->route_table); } static int isis_run_spf_cb(struct thread *thread) { struct isis_spf_run *run = THREAD_ARG(thread); struct isis_area *area = run->area; int level = run->level; int retval = ISIS_OK; XFREE(MTYPE_ISIS_SPF_RUN, run); area->spf_timer[level - 1] = NULL; if (!(area->is_type & level)) { if (isis->debugs & DEBUG_SPF_EVENTS) zlog_warn("ISIS-SPF (%s) area does not share level", area->area_tag); return ISIS_WARNING; } isis_area_invalidate_routes(area, level); if (isis->debugs & DEBUG_SPF_EVENTS) zlog_debug("ISIS-Spf (%s) L%d SPF needed, periodic SPF", area->area_tag, level); if (area->ip_circuits) retval = isis_run_spf(area, level, SPFTREE_IPV4, isis->sysid, &thread->real); if (area->ipv6_circuits) retval = isis_run_spf(area, level, SPFTREE_IPV6, isis->sysid, &thread->real); if (area->ipv6_circuits && isis_area_ipv6_dstsrc_enabled(area)) retval = isis_run_spf(area, level, SPFTREE_DSTSRC, isis->sysid, &thread->real); isis_area_verify_routes(area); /* walk all circuits and reset any spf specific flags */ struct listnode *node; struct isis_circuit *circuit; for (ALL_LIST_ELEMENTS_RO(area->circuit_list, node, circuit)) UNSET_FLAG(circuit->flags, ISIS_CIRCUIT_FLAPPED_AFTER_SPF); fabricd_run_spf(area); return retval; } static struct isis_spf_run *isis_run_spf_arg(struct isis_area *area, int level) { struct isis_spf_run *run = XMALLOC(MTYPE_ISIS_SPF_RUN, sizeof(*run)); run->area = area; run->level = level; return run; } int _isis_spf_schedule(struct isis_area *area, int level, const char *func, const char *file, int line) { struct isis_spftree *spftree = area->spftree[SPFTREE_IPV4][level - 1]; time_t now = monotime(NULL); int diff = now - spftree->last_run_monotime; assert(diff >= 0); assert(area->is_type & level); if (isis->debugs & DEBUG_SPF_EVENTS) { zlog_debug( "ISIS-Spf (%s) L%d SPF schedule called, lastrun %d sec ago" " Caller: %s %s:%d", area->area_tag, level, diff, func, file, line); } if (area->spf_delay_ietf[level - 1]) { /* Need to call schedule function also if spf delay is running * to * restart holdoff timer - compare * draft-ietf-rtgwg-backoff-algo-04 */ long delay = spf_backoff_schedule(area->spf_delay_ietf[level - 1]); if (area->spf_timer[level - 1]) return ISIS_OK; thread_add_timer_msec(master, isis_run_spf_cb, isis_run_spf_arg(area, level), delay, &area->spf_timer[level - 1]); return ISIS_OK; } if (area->spf_timer[level - 1]) return ISIS_OK; /* wait configured min_spf_interval before doing the SPF */ long timer; if (diff >= area->min_spf_interval[level - 1]) { /* Last run is more than min interval ago, schedule immediate run */ timer = 0; } else { timer = area->min_spf_interval[level - 1] - diff; } thread_add_timer(master, isis_run_spf_cb, isis_run_spf_arg(area, level), timer, &area->spf_timer[level - 1]); if (isis->debugs & DEBUG_SPF_EVENTS) zlog_debug("ISIS-Spf (%s) L%d SPF scheduled %ld sec from now", area->area_tag, level, timer); return ISIS_OK; } static void isis_print_paths(struct vty *vty, struct isis_vertex_queue *queue, uint8_t *root_sysid) { struct listnode *node; struct isis_vertex *vertex; char buff[VID2STR_BUFFER]; vty_out(vty, "Vertex Type Metric Next-Hop Interface Parent\n"); for (ALL_QUEUE_ELEMENTS_RO(queue, node, vertex)) { if (memcmp(vertex->N.id, root_sysid, ISIS_SYS_ID_LEN) == 0) { vty_out(vty, "%-20s %-12s %-6s", print_sys_hostname(root_sysid), "", ""); vty_out(vty, "%-30s\n", ""); continue; } int rows = 0; struct listnode *anode = listhead(vertex->Adj_N); struct listnode *pnode = listhead(vertex->parents); struct isis_adjacency *adj; struct isis_vertex *pvertex; vty_out(vty, "%-20s %-12s %-6u ", vid2string(vertex, buff, sizeof(buff)), vtype2string(vertex->type), vertex->d_N); for (unsigned int i = 0; i < MAX(vertex->Adj_N ? listcount(vertex->Adj_N) : 0, vertex->parents ? listcount(vertex->parents) : 0); i++) { if (anode) { adj = listgetdata(anode); anode = anode->next; } else { adj = NULL; } if (pnode) { pvertex = listgetdata(pnode); pnode = pnode->next; } else { pvertex = NULL; } if (rows) { vty_out(vty, "\n"); vty_out(vty, "%-20s %-12s %-6s ", "", "", ""); } if (adj) { vty_out(vty, "%-20s %-9s ", print_sys_hostname(adj->sysid), adj->circuit->interface->name); } if (pvertex) { if (!adj) vty_out(vty, "%-20s %-9s ", "", ""); vty_out(vty, "%s(%d)", vid2string(pvertex, buff, sizeof(buff)), pvertex->type); } ++rows; } vty_out(vty, "\n"); } } static void isis_print_spftree(struct vty *vty, int level, struct isis_area *area, enum spf_tree_id tree_id) { const char *tree_id_text = NULL; switch (tree_id) { case SPFTREE_IPV4: tree_id_text = "that speak IP"; break; case SPFTREE_IPV6: tree_id_text = "that speak IPv6"; break; case SPFTREE_DSTSRC: tree_id_text = "that support IPv6 dst-src routing"; break; case SPFTREE_COUNT: assert(!"isis_print_spftree shouldn't be called with SPFTREE_COUNT as type"); return; } if (!area->spftree[tree_id][level - 1] || !isis_vertex_queue_count( &area->spftree[tree_id][level - 1]->paths)) return; vty_out(vty, "IS-IS paths to level-%d routers %s\n", level, tree_id_text); isis_print_paths(vty, &area->spftree[tree_id][level - 1]->paths, isis->sysid); vty_out(vty, "\n"); } DEFUN (show_isis_topology, show_isis_topology_cmd, "show " PROTO_NAME " topology" #ifndef FABRICD " []" #endif , SHOW_STR PROTO_HELP "IS-IS paths to Intermediate Systems\n" #ifndef FABRICD "Paths to all level-1 routers in the area\n" "Paths to all level-2 routers in the domain\n" #endif ) { int levels; struct listnode *node; struct isis_area *area; if (argc < 4) levels = ISIS_LEVEL1 | ISIS_LEVEL2; else if (strmatch(argv[3]->text, "level-1")) levels = ISIS_LEVEL1; else levels = ISIS_LEVEL2; if (!isis->area_list || isis->area_list->count == 0) return CMD_SUCCESS; for (ALL_LIST_ELEMENTS_RO(isis->area_list, node, area)) { vty_out(vty, "Area %s:\n", area->area_tag ? area->area_tag : "null"); for (int level = ISIS_LEVEL1; level <= ISIS_LEVELS; level++) { if ((level & levels) == 0) continue; if (area->ip_circuits > 0) { isis_print_spftree(vty, level, area, SPFTREE_IPV4); } if (area->ipv6_circuits > 0) { isis_print_spftree(vty, level, area, SPFTREE_IPV6); } if (isis_area_ipv6_dstsrc_enabled(area)) { isis_print_spftree(vty, level, area, SPFTREE_DSTSRC); } } if (fabricd_spftree(area)) { vty_out(vty, "IS-IS paths to level-2 routers with hop-by-hop metric\n"); isis_print_paths(vty, &fabricd_spftree(area)->paths, isis->sysid); vty_out(vty, "\n"); } vty_out(vty, "\n"); } return CMD_SUCCESS; } void isis_spf_cmds_init(void) { install_element(VIEW_NODE, &show_isis_topology_cmd); } void isis_spf_print(struct isis_spftree *spftree, struct vty *vty) { vty_out(vty, " last run elapsed : "); vty_out_timestr(vty, spftree->last_run_timestamp); vty_out(vty, "\n"); vty_out(vty, " last run duration : %u usec\n", (uint32_t)spftree->last_run_duration); vty_out(vty, " run count : %u\n", spftree->runcount); } frr-7.2.1/isisd/isis_spf.h0000644000000000000000000000376213610377563012327 00000000000000/* * IS-IS Rout(e)ing protocol - isis_spf.h * IS-IS Shortest Path First algorithm * * Copyright (C) 2001,2002 Sampo Saaristo * Tampere University of Technology * Institute of Communications Engineering * * This program is free software; you can redistribute it and/or modify it * under the terms of the GNU General Public Licenseas 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; see the file COPYING; if not, write to the Free Software * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA */ #ifndef _ZEBRA_ISIS_SPF_H #define _ZEBRA_ISIS_SPF_H struct isis_spftree; struct isis_spftree *isis_spftree_new(struct isis_area *area); void isis_spf_invalidate_routes(struct isis_spftree *tree); void isis_spf_verify_routes(struct isis_area *area, struct isis_spftree **trees); void isis_spftree_del(struct isis_spftree *spftree); void spftree_area_init(struct isis_area *area); void spftree_area_del(struct isis_area *area); void spftree_area_adj_del(struct isis_area *area, struct isis_adjacency *adj); #define isis_spf_schedule(area, level) \ _isis_spf_schedule((area), (level), __func__, \ __FILE__, __LINE__) int _isis_spf_schedule(struct isis_area *area, int level, const char *func, const char *file, int line); void isis_spf_cmds_init(void); void isis_spf_print(struct isis_spftree *spftree, struct vty *vty); struct isis_spftree *isis_run_hopcount_spf(struct isis_area *area, uint8_t *sysid, struct isis_spftree *spftree); #endif /* _ZEBRA_ISIS_SPF_H */ frr-7.2.1/isisd/isis_spf_private.h0000644000000000000000000002214313610377563014053 00000000000000/* * IS-IS Rout(e)ing protocol - isis_spf_private.h * * Copyright (C) 2001,2002 Sampo Saaristo * Tampere University of Technology * Institute of Communications Engineering * Copyright (C) 2017 Christian Franke * * This program is free software; you can redistribute it and/or modify it * under the terms of the GNU General Public Licenseas 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; see the file COPYING; if not, write to the Free Software * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA */ #ifndef ISIS_SPF_PRIVATE_H #define ISIS_SPF_PRIVATE_H #include "hash.h" #include "jhash.h" #include "skiplist.h" #include "lib_errors.h" enum vertextype { VTYPE_PSEUDO_IS = 1, VTYPE_PSEUDO_TE_IS, VTYPE_NONPSEUDO_IS, VTYPE_NONPSEUDO_TE_IS, VTYPE_ES, VTYPE_IPREACH_INTERNAL, VTYPE_IPREACH_EXTERNAL, VTYPE_IPREACH_TE, VTYPE_IP6REACH_INTERNAL, VTYPE_IP6REACH_EXTERNAL }; #define VTYPE_IS(t) ((t) >= VTYPE_PSEUDO_IS && (t) <= VTYPE_NONPSEUDO_TE_IS) #define VTYPE_ES(t) ((t) == VTYPE_ES) #define VTYPE_IP(t) ((t) >= VTYPE_IPREACH_INTERNAL && (t) <= VTYPE_IP6REACH_EXTERNAL) struct prefix_pair { struct prefix dest; struct prefix_ipv6 src; }; /* * Triple */ struct isis_vertex { enum vertextype type; union { uint8_t id[ISIS_SYS_ID_LEN + 1]; struct prefix_pair ip; } N; uint32_t d_N; /* d(N) Distance from this IS */ uint16_t depth; /* The depth in the imaginary tree */ struct list *Adj_N; /* {Adj(N)} next hop or neighbor list */ struct list *parents; /* list of parents for ECMP */ struct hash *firsthops; /* first two hops to neighbor */ uint64_t insert_counter; }; /* Vertex Queue and associated functions */ struct isis_vertex_queue { union { struct skiplist *slist; struct list *list; } l; struct hash *hash; uint64_t insert_counter; }; __attribute__((__unused__)) static unsigned isis_vertex_queue_hash_key(const void *vp) { const struct isis_vertex *vertex = vp; if (VTYPE_IP(vertex->type)) { uint32_t key; key = prefix_hash_key(&vertex->N.ip.dest); key = jhash_1word(prefix_hash_key(&vertex->N.ip.src), key); return key; } return jhash(vertex->N.id, ISIS_SYS_ID_LEN + 1, 0x55aa5a5a); } __attribute__((__unused__)) static bool isis_vertex_queue_hash_cmp(const void *a, const void *b) { const struct isis_vertex *va = a, *vb = b; if (va->type != vb->type) return false; if (VTYPE_IP(va->type)) { if (prefix_cmp(&va->N.ip.dest, &vb->N.ip.dest)) return false; return prefix_cmp((const struct prefix *)&va->N.ip.src, (const struct prefix *)&vb->N.ip.src) == 0; } return memcmp(va->N.id, vb->N.id, ISIS_SYS_ID_LEN + 1) == 0; } /* * Compares vertizes for sorting in the TENT list. Returns true * if candidate should be considered before current, false otherwise. */ __attribute__((__unused__)) static int isis_vertex_queue_tent_cmp(void *a, void *b) { struct isis_vertex *va = a; struct isis_vertex *vb = b; if (va->d_N < vb->d_N) return -1; if (va->d_N > vb->d_N) return 1; if (va->type < vb->type) return -1; if (va->type > vb->type) return 1; if (va->insert_counter < vb->insert_counter) return -1; if (va->insert_counter > vb->insert_counter) return 1; return 0; } __attribute__((__unused__)) static struct skiplist *isis_vertex_queue_skiplist(void) { return skiplist_new(0, isis_vertex_queue_tent_cmp, NULL); } __attribute__((__unused__)) static void isis_vertex_queue_init(struct isis_vertex_queue *queue, const char *name, bool ordered) { if (ordered) { queue->insert_counter = 1; queue->l.slist = isis_vertex_queue_skiplist(); } else { queue->insert_counter = 0; queue->l.list = list_new(); } queue->hash = hash_create(isis_vertex_queue_hash_key, isis_vertex_queue_hash_cmp, name); } __attribute__((__unused__)) static void isis_vertex_del(struct isis_vertex *vertex) { list_delete(&vertex->Adj_N); list_delete(&vertex->parents); if (vertex->firsthops) { hash_clean(vertex->firsthops, NULL); hash_free(vertex->firsthops); vertex->firsthops = NULL; } memset(vertex, 0, sizeof(struct isis_vertex)); XFREE(MTYPE_ISIS_VERTEX, vertex); } __attribute__((__unused__)) static void isis_vertex_queue_clear(struct isis_vertex_queue *queue) { hash_clean(queue->hash, NULL); if (queue->insert_counter) { struct isis_vertex *vertex; while (0 == skiplist_first(queue->l.slist, NULL, (void **)&vertex)) { isis_vertex_del(vertex); skiplist_delete_first(queue->l.slist); } queue->insert_counter = 1; } else { queue->l.list->del = (void (*)(void *))isis_vertex_del; list_delete_all_node(queue->l.list); queue->l.list->del = NULL; } } __attribute__((__unused__)) static void isis_vertex_queue_free(struct isis_vertex_queue *queue) { isis_vertex_queue_clear(queue); hash_free(queue->hash); queue->hash = NULL; if (queue->insert_counter) { skiplist_free(queue->l.slist); queue->l.slist = NULL; } else list_delete(&queue->l.list); } __attribute__((__unused__)) static unsigned int isis_vertex_queue_count(struct isis_vertex_queue *queue) { return hashcount(queue->hash); } __attribute__((__unused__)) static void isis_vertex_queue_append(struct isis_vertex_queue *queue, struct isis_vertex *vertex) { assert(!queue->insert_counter); listnode_add(queue->l.list, vertex); struct isis_vertex *inserted; inserted = hash_get(queue->hash, vertex, hash_alloc_intern); assert(inserted == vertex); } __attribute__((__unused__)) static struct isis_vertex *isis_vertex_queue_last(struct isis_vertex_queue *queue) { struct listnode *tail; assert(!queue->insert_counter); tail = listtail(queue->l.list); assert(tail); return listgetdata(tail); } __attribute__((__unused__)) static void isis_vertex_queue_insert(struct isis_vertex_queue *queue, struct isis_vertex *vertex) { assert(queue->insert_counter); vertex->insert_counter = queue->insert_counter++; assert(queue->insert_counter != (uint64_t)-1); skiplist_insert(queue->l.slist, vertex, vertex); struct isis_vertex *inserted; inserted = hash_get(queue->hash, vertex, hash_alloc_intern); assert(inserted == vertex); } __attribute__((__unused__)) static struct isis_vertex * isis_vertex_queue_pop(struct isis_vertex_queue *queue) { assert(queue->insert_counter); struct isis_vertex *rv; if (skiplist_first(queue->l.slist, NULL, (void **)&rv)) return NULL; skiplist_delete_first(queue->l.slist); hash_release(queue->hash, rv); return rv; } __attribute__((__unused__)) static void isis_vertex_queue_delete(struct isis_vertex_queue *queue, struct isis_vertex *vertex) { assert(queue->insert_counter); skiplist_delete(queue->l.slist, vertex, vertex); hash_release(queue->hash, vertex); } #define ALL_QUEUE_ELEMENTS_RO(queue, node, data) \ ALL_LIST_ELEMENTS_RO((queue)->l.list, node, data) /* End of vertex queue definitions */ struct isis_spftree { struct isis_vertex_queue paths; /* the SPT */ struct isis_vertex_queue tents; /* TENT */ struct route_table *route_table; struct isis_area *area; /* back pointer to area */ unsigned int runcount; /* number of runs since uptime */ time_t last_run_timestamp; /* last run timestamp as wall time for display */ time_t last_run_monotime; /* last run as monotime for scheduling */ time_t last_run_duration; /* last run duration in msec */ uint16_t mtid; int family; int level; enum spf_tree_id tree_id; bool hopcount_metric; }; __attribute__((__unused__)) static void isis_vertex_id_init(struct isis_vertex *vertex, const void *id, enum vertextype vtype) { vertex->type = vtype; if (VTYPE_IS(vtype) || VTYPE_ES(vtype)) { memcpy(vertex->N.id, id, ISIS_SYS_ID_LEN + 1); } else if (VTYPE_IP(vtype)) { memcpy(&vertex->N.ip, id, sizeof(vertex->N.ip)); } else { flog_err(EC_LIB_DEVELOPMENT, "Unknown Vertex Type"); } } __attribute__((__unused__)) static struct isis_vertex *isis_find_vertex(struct isis_vertex_queue *queue, const void *id, enum vertextype vtype) { struct isis_vertex querier; isis_vertex_id_init(&querier, id, vtype); return hash_lookup(queue->hash, &querier); } __attribute__((__unused__)) static struct isis_lsp *lsp_for_vertex(struct isis_spftree *spftree, struct isis_vertex *vertex) { uint8_t lsp_id[ISIS_SYS_ID_LEN + 2]; assert(VTYPE_IS(vertex->type)); memcpy(lsp_id, vertex->N.id, ISIS_SYS_ID_LEN + 1); LSP_FRAGMENT(lsp_id) = 0; struct lspdb_head *lspdb = &spftree->area->lspdb[spftree->level - 1]; struct isis_lsp *lsp = lsp_search(lspdb, lsp_id); if (lsp && lsp->hdr.rem_lifetime != 0) return lsp; return NULL; } #define VID2STR_BUFFER SRCDEST2STR_BUFFER const char *vid2string(struct isis_vertex *vertex, char *buff, int size); #endif frr-7.2.1/isisd/isis_te.c0000644000000000000000000010053113610377563012132 00000000000000/* * IS-IS Rout(e)ing protocol - isis_te.c * * This is an implementation of RFC5305 & RFC 7810 * * Copyright (C) 2014 Orange Labs * http://www.orange.com * * This file is part of GNU Zebra. * * GNU Zebra is free software; you can redistribute it and/or modify it * under the terms of the GNU General Public License as published by the * Free Software Foundation; either version 2, or (at your option) any * later version. * * GNU Zebra 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; see the file COPYING; if not, write to the Free Software * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA */ #include #include #include "linklist.h" #include "thread.h" #include "vty.h" #include "stream.h" #include "memory.h" #include "log.h" #include "prefix.h" #include "command.h" #include "hash.h" #include "if.h" #include "vrf.h" #include "checksum.h" #include "md5.h" #include "sockunion.h" #include "network.h" #include "sbuf.h" #include "isisd/isis_constants.h" #include "isisd/isis_common.h" #include "isisd/isis_flags.h" #include "isisd/isis_circuit.h" #include "isisd/isisd.h" #include "isisd/isis_lsp.h" #include "isisd/isis_pdu.h" #include "isisd/isis_dynhn.h" #include "isisd/isis_misc.h" #include "isisd/isis_csm.h" #include "isisd/isis_adjacency.h" #include "isisd/isis_spf.h" #include "isisd/isis_te.h" const char *mode2text[] = {"Disable", "Area", "AS", "Emulate"}; /*------------------------------------------------------------------------* * Followings are control functions for MPLS-TE parameters management. *------------------------------------------------------------------------*/ /* Create new MPLS TE Circuit context */ struct mpls_te_circuit *mpls_te_circuit_new(void) { struct mpls_te_circuit *mtc; zlog_debug("ISIS MPLS-TE: Create new MPLS TE Circuit context"); mtc = XCALLOC(MTYPE_ISIS_MPLS_TE, sizeof(struct mpls_te_circuit)); mtc->status = disable; mtc->type = STD_TE; mtc->length = 0; return mtc; } /* Copy SUB TLVs parameters into a buffer - No space verification are performed */ /* Caller must verify before that there is enough free space in the buffer */ uint8_t add_te_subtlvs(uint8_t *buf, struct mpls_te_circuit *mtc) { uint8_t size, *tlvs = buf; zlog_debug("ISIS MPLS-TE: Add TE Sub TLVs to buffer"); if (mtc == NULL) { zlog_debug( "ISIS MPLS-TE: Abort! No MPLS TE Circuit available has been specified"); return 0; } /* Create buffer if not provided */ if (buf == NULL) { zlog_debug("ISIS MPLS-TE: Abort! No Buffer has been specified"); return 0; } /* TE_SUBTLV_ADMIN_GRP */ if (SUBTLV_TYPE(mtc->admin_grp) != 0) { size = SUBTLV_SIZE(&(mtc->admin_grp.header)); memcpy(tlvs, &(mtc->admin_grp), size); tlvs += size; } /* TE_SUBTLV_LLRI */ if (SUBTLV_TYPE(mtc->llri) != 0) { size = SUBTLV_SIZE(&(mtc->llri.header)); memcpy(tlvs, &(mtc->llri), size); tlvs += size; } /* TE_SUBTLV_LCLIF_IPADDR */ if (SUBTLV_TYPE(mtc->local_ipaddr) != 0) { size = SUBTLV_SIZE(&(mtc->local_ipaddr.header)); memcpy(tlvs, &(mtc->local_ipaddr), size); tlvs += size; } /* TE_SUBTLV_RMTIF_IPADDR */ if (SUBTLV_TYPE(mtc->rmt_ipaddr) != 0) { size = SUBTLV_SIZE(&(mtc->rmt_ipaddr.header)); memcpy(tlvs, &(mtc->rmt_ipaddr), size); tlvs += size; } /* TE_SUBTLV_MAX_BW */ if (SUBTLV_TYPE(mtc->max_bw) != 0) { size = SUBTLV_SIZE(&(mtc->max_bw.header)); memcpy(tlvs, &(mtc->max_bw), size); tlvs += size; } /* TE_SUBTLV_MAX_RSV_BW */ if (SUBTLV_TYPE(mtc->max_rsv_bw) != 0) { size = SUBTLV_SIZE(&(mtc->max_rsv_bw.header)); memcpy(tlvs, &(mtc->max_rsv_bw), size); tlvs += size; } /* TE_SUBTLV_UNRSV_BW */ if (SUBTLV_TYPE(mtc->unrsv_bw) != 0) { size = SUBTLV_SIZE(&(mtc->unrsv_bw.header)); memcpy(tlvs, &(mtc->unrsv_bw), size); tlvs += size; } /* TE_SUBTLV_TE_METRIC */ if (SUBTLV_TYPE(mtc->te_metric) != 0) { size = SUBTLV_SIZE(&(mtc->te_metric.header)); memcpy(tlvs, &(mtc->te_metric), size); tlvs += size; } /* TE_SUBTLV_AV_DELAY */ if (SUBTLV_TYPE(mtc->av_delay) != 0) { size = SUBTLV_SIZE(&(mtc->av_delay.header)); memcpy(tlvs, &(mtc->av_delay), size); tlvs += size; } /* TE_SUBTLV_MM_DELAY */ if (SUBTLV_TYPE(mtc->mm_delay) != 0) { size = SUBTLV_SIZE(&(mtc->mm_delay.header)); memcpy(tlvs, &(mtc->mm_delay), size); tlvs += size; } /* TE_SUBTLV_DELAY_VAR */ if (SUBTLV_TYPE(mtc->delay_var) != 0) { size = SUBTLV_SIZE(&(mtc->delay_var.header)); memcpy(tlvs, &(mtc->delay_var), size); tlvs += size; } /* TE_SUBTLV_PKT_LOSS */ if (SUBTLV_TYPE(mtc->pkt_loss) != 0) { size = SUBTLV_SIZE(&(mtc->pkt_loss.header)); memcpy(tlvs, &(mtc->pkt_loss), size); tlvs += size; } /* TE_SUBTLV_RES_BW */ if (SUBTLV_TYPE(mtc->res_bw) != 0) { size = SUBTLV_SIZE(&(mtc->res_bw.header)); memcpy(tlvs, &(mtc->res_bw), size); tlvs += size; } /* TE_SUBTLV_AVA_BW */ if (SUBTLV_TYPE(mtc->ava_bw) != 0) { size = SUBTLV_SIZE(&(mtc->ava_bw.header)); memcpy(tlvs, &(mtc->ava_bw), size); tlvs += size; } /* TE_SUBTLV_USE_BW */ if (SUBTLV_TYPE(mtc->use_bw) != 0) { size = SUBTLV_SIZE(&(mtc->use_bw.header)); memcpy(tlvs, &(mtc->use_bw), size); tlvs += size; } /* Add before this line any other parsing of TLV */ (void)tlvs; /* Update SubTLVs length */ mtc->length = subtlvs_len(mtc); zlog_debug("ISIS MPLS-TE: Add %d bytes length SubTLVs", mtc->length); return mtc->length; } /* Compute total Sub-TLVs size */ uint8_t subtlvs_len(struct mpls_te_circuit *mtc) { int length = 0; /* Sanity Check */ if (mtc == NULL) return 0; /* TE_SUBTLV_ADMIN_GRP */ if (SUBTLV_TYPE(mtc->admin_grp) != 0) length += SUBTLV_SIZE(&(mtc->admin_grp.header)); /* TE_SUBTLV_LLRI */ if (SUBTLV_TYPE(mtc->llri) != 0) length += SUBTLV_SIZE(&mtc->llri.header); /* TE_SUBTLV_LCLIF_IPADDR */ if (SUBTLV_TYPE(mtc->local_ipaddr) != 0) length += SUBTLV_SIZE(&mtc->local_ipaddr.header); /* TE_SUBTLV_RMTIF_IPADDR */ if (SUBTLV_TYPE(mtc->rmt_ipaddr) != 0) length += SUBTLV_SIZE(&mtc->rmt_ipaddr.header); /* TE_SUBTLV_MAX_BW */ if (SUBTLV_TYPE(mtc->max_bw) != 0) length += SUBTLV_SIZE(&mtc->max_bw.header); /* TE_SUBTLV_MAX_RSV_BW */ if (SUBTLV_TYPE(mtc->max_rsv_bw) != 0) length += SUBTLV_SIZE(&mtc->max_rsv_bw.header); /* TE_SUBTLV_UNRSV_BW */ if (SUBTLV_TYPE(mtc->unrsv_bw) != 0) length += SUBTLV_SIZE(&mtc->unrsv_bw.header); /* TE_SUBTLV_TE_METRIC */ if (SUBTLV_TYPE(mtc->te_metric) != 0) length += SUBTLV_SIZE(&mtc->te_metric.header); /* TE_SUBTLV_AV_DELAY */ if (SUBTLV_TYPE(mtc->av_delay) != 0) length += SUBTLV_SIZE(&mtc->av_delay.header); /* TE_SUBTLV_MM_DELAY */ if (SUBTLV_TYPE(mtc->mm_delay) != 0) length += SUBTLV_SIZE(&mtc->mm_delay.header); /* TE_SUBTLV_DELAY_VAR */ if (SUBTLV_TYPE(mtc->delay_var) != 0) length += SUBTLV_SIZE(&mtc->delay_var.header); /* TE_SUBTLV_PKT_LOSS */ if (SUBTLV_TYPE(mtc->pkt_loss) != 0) length += SUBTLV_SIZE(&mtc->pkt_loss.header); /* TE_SUBTLV_RES_BW */ if (SUBTLV_TYPE(mtc->res_bw) != 0) length += SUBTLV_SIZE(&mtc->res_bw.header); /* TE_SUBTLV_AVA_BW */ if (SUBTLV_TYPE(mtc->ava_bw) != 0) length += SUBTLV_SIZE(&mtc->ava_bw.header); /* TE_SUBTLV_USE_BW */ if (SUBTLV_TYPE(mtc->use_bw) != 0) length += SUBTLV_SIZE(&mtc->use_bw.header); /* Check that length is lower than the MAXIMUM SUBTLV size i.e. 256 */ if (length > MAX_SUBTLV_SIZE) { mtc->length = 0; return 0; } mtc->length = (uint8_t)length; return mtc->length; } /* Following are various functions to set MPLS TE parameters */ static void set_circuitparams_admin_grp(struct mpls_te_circuit *mtc, uint32_t admingrp) { SUBTLV_TYPE(mtc->admin_grp) = TE_SUBTLV_ADMIN_GRP; SUBTLV_LEN(mtc->admin_grp) = SUBTLV_DEF_SIZE; mtc->admin_grp.value = htonl(admingrp); return; } static void __attribute__((unused)) set_circuitparams_llri(struct mpls_te_circuit *mtc, uint32_t local, uint32_t remote) { SUBTLV_TYPE(mtc->llri) = TE_SUBTLV_LLRI; SUBTLV_LEN(mtc->llri) = TE_SUBTLV_LLRI_SIZE; mtc->llri.local = htonl(local); mtc->llri.remote = htonl(remote); } void set_circuitparams_local_ipaddr(struct mpls_te_circuit *mtc, struct in_addr addr) { SUBTLV_TYPE(mtc->local_ipaddr) = TE_SUBTLV_LOCAL_IPADDR; SUBTLV_LEN(mtc->local_ipaddr) = SUBTLV_DEF_SIZE; mtc->local_ipaddr.value.s_addr = addr.s_addr; return; } void set_circuitparams_rmt_ipaddr(struct mpls_te_circuit *mtc, struct in_addr addr) { SUBTLV_TYPE(mtc->rmt_ipaddr) = TE_SUBTLV_RMT_IPADDR; SUBTLV_LEN(mtc->rmt_ipaddr) = SUBTLV_DEF_SIZE; mtc->rmt_ipaddr.value.s_addr = addr.s_addr; return; } static void set_circuitparams_max_bw(struct mpls_te_circuit *mtc, float fp) { SUBTLV_TYPE(mtc->max_bw) = TE_SUBTLV_MAX_BW; SUBTLV_LEN(mtc->max_bw) = SUBTLV_DEF_SIZE; mtc->max_bw.value = htonf(fp); return; } static void set_circuitparams_max_rsv_bw(struct mpls_te_circuit *mtc, float fp) { SUBTLV_TYPE(mtc->max_rsv_bw) = TE_SUBTLV_MAX_RSV_BW; SUBTLV_LEN(mtc->max_rsv_bw) = SUBTLV_DEF_SIZE; mtc->max_rsv_bw.value = htonf(fp); return; } static void set_circuitparams_unrsv_bw(struct mpls_te_circuit *mtc, int priority, float fp) { /* Note that TLV-length field is the size of array. */ SUBTLV_TYPE(mtc->unrsv_bw) = TE_SUBTLV_UNRSV_BW; SUBTLV_LEN(mtc->unrsv_bw) = TE_SUBTLV_UNRSV_SIZE; mtc->unrsv_bw.value[priority] = htonf(fp); return; } static void set_circuitparams_te_metric(struct mpls_te_circuit *mtc, uint32_t te_metric) { SUBTLV_TYPE(mtc->te_metric) = TE_SUBTLV_TE_METRIC; SUBTLV_LEN(mtc->te_metric) = TE_SUBTLV_TE_METRIC_SIZE; mtc->te_metric.value[0] = (te_metric >> 16) & 0xFF; mtc->te_metric.value[1] = (te_metric >> 8) & 0xFF; mtc->te_metric.value[2] = te_metric & 0xFF; return; } static void set_circuitparams_inter_as(struct mpls_te_circuit *mtc, struct in_addr addr, uint32_t as) { /* Set the Remote ASBR IP address and then the associated AS number */ SUBTLV_TYPE(mtc->rip) = TE_SUBTLV_RIP; SUBTLV_LEN(mtc->rip) = SUBTLV_DEF_SIZE; mtc->rip.value.s_addr = addr.s_addr; SUBTLV_TYPE(mtc->ras) = TE_SUBTLV_RAS; SUBTLV_LEN(mtc->ras) = SUBTLV_DEF_SIZE; mtc->ras.value = htonl(as); } static void unset_circuitparams_inter_as(struct mpls_te_circuit *mtc) { /* Reset the Remote ASBR IP address and then the associated AS number */ SUBTLV_TYPE(mtc->rip) = 0; SUBTLV_LEN(mtc->rip) = 0; mtc->rip.value.s_addr = 0; SUBTLV_TYPE(mtc->ras) = 0; SUBTLV_LEN(mtc->ras) = 0; mtc->ras.value = 0; } static void set_circuitparams_av_delay(struct mpls_te_circuit *mtc, uint32_t delay, uint8_t anormal) { uint32_t tmp; /* Note that TLV-length field is the size of array. */ SUBTLV_TYPE(mtc->av_delay) = TE_SUBTLV_AV_DELAY; SUBTLV_LEN(mtc->av_delay) = SUBTLV_DEF_SIZE; tmp = delay & TE_EXT_MASK; if (anormal) tmp |= TE_EXT_ANORMAL; mtc->av_delay.value = htonl(tmp); return; } static void set_circuitparams_mm_delay(struct mpls_te_circuit *mtc, uint32_t low, uint32_t high, uint8_t anormal) { uint32_t tmp; /* Note that TLV-length field is the size of array. */ SUBTLV_TYPE(mtc->mm_delay) = TE_SUBTLV_MM_DELAY; SUBTLV_LEN(mtc->mm_delay) = TE_SUBTLV_MM_DELAY_SIZE; tmp = low & TE_EXT_MASK; if (anormal) tmp |= TE_EXT_ANORMAL; mtc->mm_delay.low = htonl(tmp); mtc->mm_delay.high = htonl(high); return; } static void set_circuitparams_delay_var(struct mpls_te_circuit *mtc, uint32_t jitter) { /* Note that TLV-length field is the size of array. */ SUBTLV_TYPE(mtc->delay_var) = TE_SUBTLV_DELAY_VAR; SUBTLV_LEN(mtc->delay_var) = SUBTLV_DEF_SIZE; mtc->delay_var.value = htonl(jitter & TE_EXT_MASK); return; } static void set_circuitparams_pkt_loss(struct mpls_te_circuit *mtc, uint32_t loss, uint8_t anormal) { uint32_t tmp; /* Note that TLV-length field is the size of array. */ SUBTLV_TYPE(mtc->pkt_loss) = TE_SUBTLV_PKT_LOSS; SUBTLV_LEN(mtc->pkt_loss) = SUBTLV_DEF_SIZE; tmp = loss & TE_EXT_MASK; if (anormal) tmp |= TE_EXT_ANORMAL; mtc->pkt_loss.value = htonl(tmp); return; } static void set_circuitparams_res_bw(struct mpls_te_circuit *mtc, float fp) { /* Note that TLV-length field is the size of array. */ SUBTLV_TYPE(mtc->res_bw) = TE_SUBTLV_RES_BW; SUBTLV_LEN(mtc->res_bw) = SUBTLV_DEF_SIZE; mtc->res_bw.value = htonf(fp); return; } static void set_circuitparams_ava_bw(struct mpls_te_circuit *mtc, float fp) { /* Note that TLV-length field is the size of array. */ SUBTLV_TYPE(mtc->ava_bw) = TE_SUBTLV_AVA_BW; SUBTLV_LEN(mtc->ava_bw) = SUBTLV_DEF_SIZE; mtc->ava_bw.value = htonf(fp); return; } static void set_circuitparams_use_bw(struct mpls_te_circuit *mtc, float fp) { /* Note that TLV-length field is the size of array. */ SUBTLV_TYPE(mtc->use_bw) = TE_SUBTLV_USE_BW; SUBTLV_LEN(mtc->use_bw) = SUBTLV_DEF_SIZE; mtc->use_bw.value = htonf(fp); return; } /* Main initialization / update function of the MPLS TE Circuit context */ /* Call when interface TE Link parameters are modified */ void isis_link_params_update(struct isis_circuit *circuit, struct interface *ifp) { int i; struct prefix_ipv4 *addr; struct mpls_te_circuit *mtc; /* Sanity Check */ if ((circuit == NULL) || (ifp == NULL)) return; zlog_info("MPLS-TE: Initialize circuit parameters for interface %s", ifp->name); /* Check if MPLS TE Circuit context has not been already created */ if (circuit->mtc == NULL) circuit->mtc = mpls_te_circuit_new(); mtc = circuit->mtc; /* Fulfil MTC TLV from ifp TE Link parameters */ if (HAS_LINK_PARAMS(ifp)) { mtc->status = enable; /* STD_TE metrics */ if (IS_PARAM_SET(ifp->link_params, LP_ADM_GRP)) set_circuitparams_admin_grp( mtc, ifp->link_params->admin_grp); else SUBTLV_TYPE(mtc->admin_grp) = 0; /* If not already set, register local IP addr from ip_addr list * if it exists */ if (SUBTLV_TYPE(mtc->local_ipaddr) == 0) { if (circuit->ip_addrs != NULL && listcount(circuit->ip_addrs) != 0) { addr = (struct prefix_ipv4 *)listgetdata( (struct listnode *)listhead( circuit->ip_addrs)); set_circuitparams_local_ipaddr(mtc, addr->prefix); } } /* If not already set, try to determine Remote IP addr if * circuit is P2P */ if ((SUBTLV_TYPE(mtc->rmt_ipaddr) == 0) && (circuit->circ_type == CIRCUIT_T_P2P)) { struct isis_adjacency *adj = circuit->u.p2p.neighbor; if (adj && adj->adj_state == ISIS_ADJ_UP && adj->ipv4_address_count) { set_circuitparams_rmt_ipaddr( mtc, adj->ipv4_addresses[0]); } } if (IS_PARAM_SET(ifp->link_params, LP_MAX_BW)) set_circuitparams_max_bw(mtc, ifp->link_params->max_bw); else SUBTLV_TYPE(mtc->max_bw) = 0; if (IS_PARAM_SET(ifp->link_params, LP_MAX_RSV_BW)) set_circuitparams_max_rsv_bw( mtc, ifp->link_params->max_rsv_bw); else SUBTLV_TYPE(mtc->max_rsv_bw) = 0; if (IS_PARAM_SET(ifp->link_params, LP_UNRSV_BW)) for (i = 0; i < MAX_CLASS_TYPE; i++) set_circuitparams_unrsv_bw( mtc, i, ifp->link_params->unrsv_bw[i]); else SUBTLV_TYPE(mtc->unrsv_bw) = 0; if (IS_PARAM_SET(ifp->link_params, LP_TE_METRIC)) set_circuitparams_te_metric( mtc, ifp->link_params->te_metric); else SUBTLV_TYPE(mtc->te_metric) = 0; /* TE metric Extensions */ if (IS_PARAM_SET(ifp->link_params, LP_DELAY)) set_circuitparams_av_delay( mtc, ifp->link_params->av_delay, 0); else SUBTLV_TYPE(mtc->av_delay) = 0; if (IS_PARAM_SET(ifp->link_params, LP_MM_DELAY)) set_circuitparams_mm_delay( mtc, ifp->link_params->min_delay, ifp->link_params->max_delay, 0); else SUBTLV_TYPE(mtc->mm_delay) = 0; if (IS_PARAM_SET(ifp->link_params, LP_DELAY_VAR)) set_circuitparams_delay_var( mtc, ifp->link_params->delay_var); else SUBTLV_TYPE(mtc->delay_var) = 0; if (IS_PARAM_SET(ifp->link_params, LP_PKT_LOSS)) set_circuitparams_pkt_loss( mtc, ifp->link_params->pkt_loss, 0); else SUBTLV_TYPE(mtc->pkt_loss) = 0; if (IS_PARAM_SET(ifp->link_params, LP_RES_BW)) set_circuitparams_res_bw(mtc, ifp->link_params->res_bw); else SUBTLV_TYPE(mtc->res_bw) = 0; if (IS_PARAM_SET(ifp->link_params, LP_AVA_BW)) set_circuitparams_ava_bw(mtc, ifp->link_params->ava_bw); else SUBTLV_TYPE(mtc->ava_bw) = 0; if (IS_PARAM_SET(ifp->link_params, LP_USE_BW)) set_circuitparams_use_bw(mtc, ifp->link_params->use_bw); else SUBTLV_TYPE(mtc->use_bw) = 0; /* INTER_AS */ if (IS_PARAM_SET(ifp->link_params, LP_RMT_AS)) set_circuitparams_inter_as(mtc, ifp->link_params->rmt_ip, ifp->link_params->rmt_as); else /* reset inter-as TE params */ unset_circuitparams_inter_as(mtc); /* Compute total length of SUB TLVs */ mtc->length = subtlvs_len(mtc); } else mtc->status = disable; /* Finally Update LSP */ #if 0 if (circuit->area && IS_MPLS_TE(circuit->area->mta)) lsp_regenerate_schedule (circuit->area, circuit->is_type, 0); #endif return; } void isis_mpls_te_update(struct interface *ifp) { struct isis_circuit *circuit; /* Sanity Check */ if (ifp == NULL) return; /* Get circuit context from interface */ if ((circuit = circuit_scan_by_ifp(ifp)) == NULL) return; /* Update TE TLVs ... */ isis_link_params_update(circuit, ifp); /* ... and LSP */ if (circuit->area && IS_MPLS_TE(circuit->area->mta)) lsp_regenerate_schedule(circuit->area, circuit->is_type, 0); return; } /*------------------------------------------------------------------------* * Followings are vty session control functions. *------------------------------------------------------------------------*/ static uint8_t print_subtlv_admin_grp(struct sbuf *buf, int indent, struct te_subtlv_admin_grp *tlv) { sbuf_push(buf, indent, "Administrative Group: 0x%" PRIx32 "\n", ntohl(tlv->value)); return (SUBTLV_HDR_SIZE + SUBTLV_DEF_SIZE); } static uint8_t print_subtlv_llri(struct sbuf *buf, int indent, struct te_subtlv_llri *tlv) { sbuf_push(buf, indent, "Link Local ID: %" PRIu32 "\n", ntohl(tlv->local)); sbuf_push(buf, indent, "Link Remote ID: %" PRIu32 "\n", ntohl(tlv->remote)); return (SUBTLV_HDR_SIZE + TE_SUBTLV_LLRI_SIZE); } static uint8_t print_subtlv_local_ipaddr(struct sbuf *buf, int indent, struct te_subtlv_local_ipaddr *tlv) { sbuf_push(buf, indent, "Local Interface IP Address(es): %s\n", inet_ntoa(tlv->value)); return (SUBTLV_HDR_SIZE + SUBTLV_DEF_SIZE); } static uint8_t print_subtlv_rmt_ipaddr(struct sbuf *buf, int indent, struct te_subtlv_rmt_ipaddr *tlv) { sbuf_push(buf, indent, "Remote Interface IP Address(es): %s\n", inet_ntoa(tlv->value)); return (SUBTLV_HDR_SIZE + SUBTLV_DEF_SIZE); } static uint8_t print_subtlv_max_bw(struct sbuf *buf, int indent, struct te_subtlv_max_bw *tlv) { float fval; fval = ntohf(tlv->value); sbuf_push(buf, indent, "Maximum Bandwidth: %g (Bytes/sec)\n", fval); return (SUBTLV_HDR_SIZE + SUBTLV_DEF_SIZE); } static uint8_t print_subtlv_max_rsv_bw(struct sbuf *buf, int indent, struct te_subtlv_max_rsv_bw *tlv) { float fval; fval = ntohf(tlv->value); sbuf_push(buf, indent, "Maximum Reservable Bandwidth: %g (Bytes/sec)\n", fval); return (SUBTLV_HDR_SIZE + SUBTLV_DEF_SIZE); } static uint8_t print_subtlv_unrsv_bw(struct sbuf *buf, int indent, struct te_subtlv_unrsv_bw *tlv) { float fval1, fval2; int i; sbuf_push(buf, indent, "Unreserved Bandwidth:\n"); for (i = 0; i < MAX_CLASS_TYPE; i += 2) { fval1 = ntohf(tlv->value[i]); fval2 = ntohf(tlv->value[i + 1]); sbuf_push(buf, indent + 2, "[%d]: %g (Bytes/sec),\t[%d]: %g (Bytes/sec)\n", i, fval1, i + 1, fval2); } return (SUBTLV_HDR_SIZE + TE_SUBTLV_UNRSV_SIZE); } static uint8_t print_subtlv_te_metric(struct sbuf *buf, int indent, struct te_subtlv_te_metric *tlv) { uint32_t te_metric; te_metric = tlv->value[2] | tlv->value[1] << 8 | tlv->value[0] << 16; sbuf_push(buf, indent, "Traffic Engineering Metric: %u\n", te_metric); return (SUBTLV_HDR_SIZE + SUBTLV_DEF_SIZE); } static uint8_t print_subtlv_ras(struct sbuf *buf, int indent, struct te_subtlv_ras *tlv) { sbuf_push(buf, indent, "Inter-AS TE Remote AS number: %" PRIu32 "\n", ntohl(tlv->value)); return (SUBTLV_HDR_SIZE + SUBTLV_DEF_SIZE); } static uint8_t print_subtlv_rip(struct sbuf *buf, int indent, struct te_subtlv_rip *tlv) { sbuf_push(buf, indent, "Inter-AS TE Remote ASBR IP address: %s\n", inet_ntoa(tlv->value)); return (SUBTLV_HDR_SIZE + SUBTLV_DEF_SIZE); } static uint8_t print_subtlv_av_delay(struct sbuf *buf, int indent, struct te_subtlv_av_delay *tlv) { uint32_t delay; uint32_t A; delay = (uint32_t)ntohl(tlv->value) & TE_EXT_MASK; A = (uint32_t)ntohl(tlv->value) & TE_EXT_ANORMAL; sbuf_push(buf, indent, "%s Average Link Delay: %" PRIu32 " (micro-sec)\n", A ? "Anomalous" : "Normal", delay); return (SUBTLV_HDR_SIZE + SUBTLV_DEF_SIZE); } static uint8_t print_subtlv_mm_delay(struct sbuf *buf, int indent, struct te_subtlv_mm_delay *tlv) { uint32_t low, high; uint32_t A; low = (uint32_t)ntohl(tlv->low) & TE_EXT_MASK; A = (uint32_t)ntohl(tlv->low) & TE_EXT_ANORMAL; high = (uint32_t)ntohl(tlv->high) & TE_EXT_MASK; sbuf_push(buf, indent, "%s Min/Max Link Delay: %" PRIu32 " / %" PRIu32 " (micro-sec)\n", A ? "Anomalous" : "Normal", low, high); return (SUBTLV_HDR_SIZE + SUBTLV_DEF_SIZE); } static uint8_t print_subtlv_delay_var(struct sbuf *buf, int indent, struct te_subtlv_delay_var *tlv) { uint32_t jitter; jitter = (uint32_t)ntohl(tlv->value) & TE_EXT_MASK; sbuf_push(buf, indent, "Delay Variation: %" PRIu32 " (micro-sec)\n", jitter); return (SUBTLV_HDR_SIZE + SUBTLV_DEF_SIZE); } static uint8_t print_subtlv_pkt_loss(struct sbuf *buf, int indent, struct te_subtlv_pkt_loss *tlv) { uint32_t loss; uint32_t A; float fval; loss = (uint32_t)ntohl(tlv->value) & TE_EXT_MASK; fval = (float)(loss * LOSS_PRECISION); A = (uint32_t)ntohl(tlv->value) & TE_EXT_ANORMAL; sbuf_push(buf, indent, "%s Link Packet Loss: %g (%%)\n", A ? "Anomalous" : "Normal", fval); return (SUBTLV_HDR_SIZE + SUBTLV_DEF_SIZE); } static uint8_t print_subtlv_res_bw(struct sbuf *buf, int indent, struct te_subtlv_res_bw *tlv) { float fval; fval = ntohf(tlv->value); sbuf_push(buf, indent, "Unidirectional Residual Bandwidth: %g (Bytes/sec)\n", fval); return (SUBTLV_HDR_SIZE + SUBTLV_DEF_SIZE); } static uint8_t print_subtlv_ava_bw(struct sbuf *buf, int indent, struct te_subtlv_ava_bw *tlv) { float fval; fval = ntohf(tlv->value); sbuf_push(buf, indent, "Unidirectional Available Bandwidth: %g (Bytes/sec)\n", fval); return (SUBTLV_HDR_SIZE + SUBTLV_DEF_SIZE); } static uint8_t print_subtlv_use_bw(struct sbuf *buf, int indent, struct te_subtlv_use_bw *tlv) { float fval; fval = ntohf(tlv->value); sbuf_push(buf, indent, "Unidirectional Utilized Bandwidth: %g (Bytes/sec)\n", fval); return (SUBTLV_HDR_SIZE + SUBTLV_DEF_SIZE); } static uint8_t print_unknown_tlv(struct sbuf *buf, int indent, struct subtlv_header *tlvh) { int i, rtn; uint8_t *v = (uint8_t *)tlvh; if (tlvh->length != 0) { sbuf_push(buf, indent, "Unknown TLV: [type(%#.2x), length(%#.2x)]\n", tlvh->type, tlvh->length); sbuf_push(buf, indent + 2, "Dump: [00]"); rtn = 1; /* initialize end of line counter */ for (i = 0; i < tlvh->length; i++) { sbuf_push(buf, 0, " %#.2x", v[i]); if (rtn == 8) { sbuf_push(buf, 0, "\n"); sbuf_push(buf, indent + 8, "[%.2x]", i + 1); rtn = 1; } else rtn++; } sbuf_push(buf, 0, "\n"); } else { sbuf_push(buf, indent, "Unknown TLV: [type(%#.2x), length(%#.2x)]\n", tlvh->type, tlvh->length); } return SUBTLV_SIZE(tlvh); } /* Main Show function */ void mpls_te_print_detail(struct sbuf *buf, int indent, uint8_t *subtlvs, uint8_t subtlv_len) { struct subtlv_header *tlvh = (struct subtlv_header *)subtlvs; uint16_t sum = 0; for (; sum < subtlv_len; tlvh = (struct subtlv_header *)(subtlvs + sum)) { if (subtlv_len - sum < SUBTLV_SIZE(tlvh)) { sbuf_push(buf, indent, "Available data %" PRIu8 " is less than TLV size %u!\n", subtlv_len - sum, SUBTLV_SIZE(tlvh)); return; } switch (tlvh->type) { case TE_SUBTLV_ADMIN_GRP: if (tlvh->length != SUBTLV_DEF_SIZE) { sbuf_push(buf, indent, "TLV size does not match expected size for Administrative Group!\n"); return; } sum += print_subtlv_admin_grp(buf, indent, (struct te_subtlv_admin_grp *)tlvh); break; case TE_SUBTLV_LLRI: if (tlvh->length != TE_SUBTLV_LLRI_SIZE) { sbuf_push(buf, indent, "TLV size does not match expected size for Link ID!\n"); return; } sum += print_subtlv_llri(buf, indent, (struct te_subtlv_llri *)tlvh); break; case TE_SUBTLV_LOCAL_IPADDR: if (tlvh->length != SUBTLV_DEF_SIZE) { sbuf_push(buf, indent, "TLV size does not match expected size for Local IP address!\n"); return; } sum += print_subtlv_local_ipaddr(buf, indent, (struct te_subtlv_local_ipaddr *)tlvh); break; case TE_SUBTLV_RMT_IPADDR: if (tlvh->length != SUBTLV_DEF_SIZE) { sbuf_push(buf, indent, "TLV size does not match expected size for Remote Interface address!\n"); return; } sum += print_subtlv_rmt_ipaddr(buf, indent, (struct te_subtlv_rmt_ipaddr *)tlvh); break; case TE_SUBTLV_MAX_BW: if (tlvh->length != SUBTLV_DEF_SIZE) { sbuf_push(buf, indent, "TLV size does not match expected size for Maximum Bandwidth!\n"); return; } sum += print_subtlv_max_bw(buf, indent, (struct te_subtlv_max_bw *)tlvh); break; case TE_SUBTLV_MAX_RSV_BW: if (tlvh->length != SUBTLV_DEF_SIZE) { sbuf_push(buf, indent, "TLV size does not match expected size for Maximum Reservable Bandwidth!\n"); return; } sum += print_subtlv_max_rsv_bw(buf, indent, (struct te_subtlv_max_rsv_bw *)tlvh); break; case TE_SUBTLV_UNRSV_BW: if (tlvh->length != TE_SUBTLV_UNRSV_SIZE) { sbuf_push(buf, indent, "TLV size does not match expected size for Unreserved Bandwidth!\n"); return; } sum += print_subtlv_unrsv_bw(buf, indent, (struct te_subtlv_unrsv_bw *)tlvh); break; case TE_SUBTLV_TE_METRIC: if (tlvh->length != SUBTLV_DEF_SIZE) { sbuf_push(buf, indent, "TLV size does not match expected size for Traffic Engineering Metric!\n"); return; } sum += print_subtlv_te_metric(buf, indent, (struct te_subtlv_te_metric *)tlvh); break; case TE_SUBTLV_RAS: if (tlvh->length != SUBTLV_DEF_SIZE) { sbuf_push(buf, indent, "TLV size does not match expected size for Remote AS number!\n"); return; } sum += print_subtlv_ras(buf, indent, (struct te_subtlv_ras *)tlvh); break; case TE_SUBTLV_RIP: if (tlvh->length != SUBTLV_DEF_SIZE) { sbuf_push(buf, indent, "TLV size does not match expected size for Remote ASBR IP Address!\n"); return; } sum += print_subtlv_rip(buf, indent, (struct te_subtlv_rip *)tlvh); break; case TE_SUBTLV_AV_DELAY: if (tlvh->length != SUBTLV_DEF_SIZE) { sbuf_push(buf, indent, "TLV size does not match expected size for Average Link Delay!\n"); return; } sum += print_subtlv_av_delay(buf, indent, (struct te_subtlv_av_delay *)tlvh); break; case TE_SUBTLV_MM_DELAY: if (tlvh->length != SUBTLV_DEF_SIZE) { sbuf_push(buf, indent, "TLV size does not match expected size for Min/Max Link Delay!\n"); return; } sum += print_subtlv_mm_delay(buf, indent, (struct te_subtlv_mm_delay *)tlvh); break; case TE_SUBTLV_DELAY_VAR: if (tlvh->length != SUBTLV_DEF_SIZE) { sbuf_push(buf, indent, "TLV size does not match expected size for Delay Variation!\n"); return; } sum += print_subtlv_delay_var(buf, indent, (struct te_subtlv_delay_var *)tlvh); break; case TE_SUBTLV_PKT_LOSS: if (tlvh->length != SUBTLV_DEF_SIZE) { sbuf_push(buf, indent, "TLV size does not match expected size for Link Packet Loss!\n"); return; } sum += print_subtlv_pkt_loss(buf, indent, (struct te_subtlv_pkt_loss *)tlvh); break; case TE_SUBTLV_RES_BW: if (tlvh->length != SUBTLV_DEF_SIZE) { sbuf_push(buf, indent, "TLV size does not match expected size for Unidirectional Residual Bandwidth!\n"); return; } sum += print_subtlv_res_bw(buf, indent, (struct te_subtlv_res_bw *)tlvh); break; case TE_SUBTLV_AVA_BW: if (tlvh->length != SUBTLV_DEF_SIZE) { sbuf_push(buf, indent, "TLV size does not match expected size for Unidirectional Available Bandwidth!\n"); return; } sum += print_subtlv_ava_bw(buf, indent, (struct te_subtlv_ava_bw *)tlvh); break; case TE_SUBTLV_USE_BW: if (tlvh->length != SUBTLV_DEF_SIZE) { sbuf_push(buf, indent, "TLV size does not match expected size for Unidirectional Utilized Bandwidth!\n"); return; } sum += print_subtlv_use_bw(buf, indent, (struct te_subtlv_use_bw *)tlvh); break; default: sum += print_unknown_tlv(buf, indent, tlvh); break; } } return; } /*------------------------------------------------------------------------* * Followings are vty command functions. *------------------------------------------------------------------------*/ #ifndef FABRICD DEFUN (show_isis_mpls_te_router, show_isis_mpls_te_router_cmd, "show " PROTO_NAME " mpls-te router", SHOW_STR PROTO_HELP MPLS_TE_STR "Router information\n") { struct listnode *anode; struct isis_area *area; if (!isis) { vty_out(vty, "IS-IS Routing Process not enabled\n"); return CMD_SUCCESS; } for (ALL_LIST_ELEMENTS_RO(isis->area_list, anode, area)) { if (!IS_MPLS_TE(area->mta)) continue; vty_out(vty, "Area %s:\n", area->area_tag); if (ntohs(area->mta->router_id.s_addr) != 0) vty_out(vty, " MPLS-TE Router-Address: %s\n", inet_ntoa(area->mta->router_id)); else vty_out(vty, " N/A\n"); } return CMD_SUCCESS; } static void show_mpls_te_sub(struct vty *vty, char *name, struct mpls_te_circuit *mtc) { struct sbuf buf; sbuf_init(&buf, NULL, 0); if (mtc->status != enable) return; vty_out(vty, "-- MPLS-TE link parameters for %s --\n", name); sbuf_reset(&buf); print_subtlv_admin_grp(&buf, 4, &mtc->admin_grp); if (SUBTLV_TYPE(mtc->local_ipaddr) != 0) print_subtlv_local_ipaddr(&buf, 4, &mtc->local_ipaddr); if (SUBTLV_TYPE(mtc->rmt_ipaddr) != 0) print_subtlv_rmt_ipaddr(&buf, 4, &mtc->rmt_ipaddr); print_subtlv_max_bw(&buf, 4, &mtc->max_bw); print_subtlv_max_rsv_bw(&buf, 4, &mtc->max_rsv_bw); print_subtlv_unrsv_bw(&buf, 4, &mtc->unrsv_bw); print_subtlv_te_metric(&buf, 4, &mtc->te_metric); if (IS_INTER_AS(mtc->type)) { if (SUBTLV_TYPE(mtc->ras) != 0) print_subtlv_ras(&buf, 4, &mtc->ras); if (SUBTLV_TYPE(mtc->rip) != 0) print_subtlv_rip(&buf, 4, &mtc->rip); } print_subtlv_av_delay(&buf, 4, &mtc->av_delay); print_subtlv_mm_delay(&buf, 4, &mtc->mm_delay); print_subtlv_delay_var(&buf, 4, &mtc->delay_var); print_subtlv_pkt_loss(&buf, 4, &mtc->pkt_loss); print_subtlv_res_bw(&buf, 4, &mtc->res_bw); print_subtlv_ava_bw(&buf, 4, &mtc->ava_bw); print_subtlv_use_bw(&buf, 4, &mtc->use_bw); vty_multiline(vty, "", "%s", sbuf_buf(&buf)); vty_out(vty, "---------------\n\n"); sbuf_free(&buf); return; } DEFUN (show_isis_mpls_te_interface, show_isis_mpls_te_interface_cmd, "show " PROTO_NAME " mpls-te interface [INTERFACE]", SHOW_STR PROTO_HELP MPLS_TE_STR "Interface information\n" "Interface name\n") { struct listnode *anode, *cnode; struct isis_area *area; struct isis_circuit *circuit; struct interface *ifp; int idx_interface = 4; if (!isis) { vty_out(vty, "IS-IS Routing Process not enabled\n"); return CMD_SUCCESS; } if (argc == idx_interface) { /* Show All Interfaces. */ for (ALL_LIST_ELEMENTS_RO(isis->area_list, anode, area)) { if (!IS_MPLS_TE(area->mta)) continue; vty_out(vty, "Area %s:\n", area->area_tag); for (ALL_LIST_ELEMENTS_RO(area->circuit_list, cnode, circuit)) show_mpls_te_sub(vty, circuit->interface->name, circuit->mtc); } } else { /* Interface name is specified. */ ifp = if_lookup_by_name(argv[idx_interface]->arg, VRF_DEFAULT); if (ifp == NULL) vty_out(vty, "No such interface name\n"); else { circuit = circuit_scan_by_ifp(ifp); if (!circuit) vty_out(vty, "ISIS is not enabled on circuit %s\n", ifp->name); else show_mpls_te_sub(vty, ifp->name, circuit->mtc); } } return CMD_SUCCESS; } #endif /* Initialize MPLS_TE */ void isis_mpls_te_init(void) { #ifndef FABRICD /* Register new VTY commands */ install_element(VIEW_NODE, &show_isis_mpls_te_router_cmd); install_element(VIEW_NODE, &show_isis_mpls_te_interface_cmd); #endif return; } frr-7.2.1/isisd/isis_te.h0000644000000000000000000002533213610377563012144 00000000000000/* * IS-IS Rout(e)ing protocol - isis_te.c * * This is an implementation of RFC5305, RFC 5307 and RFC 7810 * * Copyright (C) 2014 Orange Labs * http://www.orange.com * * This file is part of GNU Zebra. * * GNU Zebra is free software; you can redistribute it and/or modify it * under the terms of the GNU General Public License as published by the * Free Software Foundation; either version 2, or (at your option) any * later version. * * GNU Zebra 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; see the file COPYING; if not, write to the Free Software * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA */ #ifndef _ZEBRA_ISIS_MPLS_TE_H #define _ZEBRA_ISIS_MPLS_TE_H /* * Traffic Engineering information are transport through LSP: * - Extended IS Reachability TLV = 22 * - Traffic Engineering Router ID TLV = 134 * - Extended IP Reachability TLV = 135 * - Inter-AS Reachability Information TLV = 141 * * and support following sub-TLV: * * Name Value Status * _________________________________________________ * Administartive group (color) 3 RFC5305 * Link Local/Remote Identifiers 4 RFC5307 * IPv4 interface address 6 RFC5305 * IPv4 neighbor address 8 RFC5305 * Maximum link bandwidth 9 RFC5305 * Reservable link bandwidth 10 RFC5305 * Unreserved bandwidth 11 RFC5305 * TE Default metric 18 RFC5305 * Link Protection Type 20 RFC5307 * Interface Switching Capability 21 RFC5307 * Remote AS number 24 RFC5316 * IPv4 Remote ASBR identifier 25 RFC5316 * */ /* NOTE: RFC5316 is not yet supported in this version */ /* Following define the type of TE link regarding the various RFC */ #define STD_TE 0x01 #define GMPLS 0x02 #define INTER_AS 0x04 #define FLOOD_L1 0x10 #define FLOOD_L2 0x20 #define FLOOD_AS 0x40 #define EMULATED 0x80 #define IS_STD_TE(x) (x & STD_TE) #define IS_INTER_AS(x) (x & INTER_AS) #define IS_EMULATED(x) (x & EMULATED) #define IS_FLOOD_L1(x) (x & FLOOD_L1) #define IS_FLOOD_L2(x) (x & FLOOD_L2) #define IS_FLOOD_AS(x) (x & FLOOD_AS) #define IS_INTER_AS_EMU(x) (x & INTER_AS & EMULATED) #define IS_INTER_AS_AS(x) (x & INTER_AS & FLOOD_AS) /* * Following section defines subTLV (tag, length, value) structures, * used for Traffic Engineering. */ struct subtlv_header { uint8_t type; /* sub_TLV_XXX type (see above) */ uint8_t length; /* Value portion only, in byte */ }; #define MAX_SUBTLV_SIZE 256 #define SUBTLV_HDR_SIZE 2 /* (sizeof (struct sub_tlv_header)) */ #define SUBTLV_SIZE(stlvh) (SUBTLV_HDR_SIZE + (stlvh)->length) #define SUBTLV_HDR_TOP(lsph) (struct subtlv_header *)((char *)(lsph) + ISIS_LSP_HEADER_SIZE) #define SUBTLV_HDR_NEXT(stlvh) (struct subtlv_header *)((char *)(stlvh) + SUBTLV_SIZE(stlvh)) #define SUBTLV_TYPE(stlvh) stlvh.header.type #define SUBTLV_LEN(stlvh) stlvh.header.length #define SUBTLV_VAL(stlvh) stlvh.value #define SUBTLV_DATA(stlvh) stlvh + SUBTLV_HDR_SIZE #define SUBTLV_DEF_SIZE 4 /* Link Sub-TLV: Resource Class/Color - RFC 5305 */ #define TE_SUBTLV_ADMIN_GRP 3 struct te_subtlv_admin_grp { struct subtlv_header header; /* Value length is 4 octets. */ uint32_t value; /* Admin. group membership. */ } __attribute__((__packed__)); /* Link Local/Remote Identifiers - RFC 5307 */ #define TE_SUBTLV_LLRI 4 #define TE_SUBTLV_LLRI_SIZE 8 struct te_subtlv_llri { struct subtlv_header header; /* Value length is 8 octets. */ uint32_t local; /* Link Local Identifier */ uint32_t remote; /* Link Remote Identifier */ } __attribute__((__packed__)); /* Link Sub-TLV: Local Interface IP Address - RFC 5305 */ #define TE_SUBTLV_LOCAL_IPADDR 6 struct te_subtlv_local_ipaddr { struct subtlv_header header; /* Value length is 4 x N octets. */ struct in_addr value; /* Local IP address(es). */ } __attribute__((__packed__)); /* Link Sub-TLV: Neighbor Interface IP Address - RFC 5305 */ #define TE_SUBTLV_RMT_IPADDR 8 struct te_subtlv_rmt_ipaddr { struct subtlv_header header; /* Value length is 4 x N octets. */ struct in_addr value; /* Neighbor's IP address(es). */ } __attribute__((__packed__)); /* Link Sub-TLV: Maximum Bandwidth - RFC 5305 */ #define TE_SUBTLV_MAX_BW 9 struct te_subtlv_max_bw { struct subtlv_header header; /* Value length is 4 octets. */ float value; /* bytes/sec */ } __attribute__((__packed__)); /* Link Sub-TLV: Maximum Reservable Bandwidth - RFC 5305 */ #define TE_SUBTLV_MAX_RSV_BW 10 struct te_subtlv_max_rsv_bw { struct subtlv_header header; /* Value length is 4 octets. */ float value; /* bytes/sec */ } __attribute__((__packed__)); /* Link Sub-TLV: Unreserved Bandwidth - RFC 5305 */ #define TE_SUBTLV_UNRSV_BW 11 #define TE_SUBTLV_UNRSV_SIZE 32 struct te_subtlv_unrsv_bw { struct subtlv_header header; /* Value length is 32 octets. */ float value[8]; /* One for each priority level. */ } __attribute__((__packed__)); /* Link Sub-TLV: Traffic Engineering Metric - RFC 5305 */ #define TE_SUBTLV_TE_METRIC 18 #define TE_SUBTLV_TE_METRIC_SIZE 3 struct te_subtlv_te_metric { struct subtlv_header header; /* Value length is 4 octets. */ uint8_t value[3]; /* Link metric for TE purpose. */ } __attribute__((__packed__)); /* Remote AS Number sub-TLV - RFC5316 */ #define TE_SUBTLV_RAS 24 struct te_subtlv_ras { struct subtlv_header header; /* Value length is 4 octets. */ uint32_t value; /* Remote AS number */ } __attribute__((__packed__)); /* IPv4 Remote ASBR ID Sub-TLV - RFC5316 */ #define TE_SUBTLV_RIP 25 struct te_subtlv_rip { struct subtlv_header header; /* Value length is 4 octets. */ struct in_addr value; /* Remote ASBR IP address */ } __attribute__((__packed__)); /* TE Metric Extensions - RFC 7810 */ /* Link Sub-TLV: Average Link Delay */ #define TE_SUBTLV_AV_DELAY 33 struct te_subtlv_av_delay { struct subtlv_header header; /* Value length is 4 bytes. */ uint32_t value; /* Average delay in micro-seconds only 24 bits => 0 ... 16777215 with Anomalous Bit (A) as Upper most bit */ } __attribute__((__packed__)); /* Link Sub-TLV: Low/High Link Delay */ #define TE_SUBTLV_MM_DELAY 34 #define TE_SUBTLV_MM_DELAY_SIZE 8 struct te_subtlv_mm_delay { struct subtlv_header header; /* Value length is 8 bytes. */ uint32_t low; /* low delay in micro-seconds only 24 bits => 0 ... 16777215 with Anomalous Bit (A) as Upper most bit */ uint32_t high; /* high delay in micro-seconds only 24 bits => 0 ... 16777215 */ } __attribute__((__packed__)); /* Link Sub-TLV: Link Delay Variation i.e. Jitter */ #define TE_SUBTLV_DELAY_VAR 35 struct te_subtlv_delay_var { struct subtlv_header header; /* Value length is 4 bytes. */ uint32_t value; /* interval in micro-seconds only 24 bits => 0 ... 16777215 */ } __attribute__((__packed__)); /* Link Sub-TLV: Routine Unidirectional Link Packet Loss */ #define TE_SUBTLV_PKT_LOSS 36 struct te_subtlv_pkt_loss { struct subtlv_header header; /* Value length is 4 bytes. */ uint32_t value; /* in percentage of total traffic only 24 bits (2^24 - 2) with Anomalous Bit (A) as Upper most bit */ } __attribute__((__packed__)); /* Link Sub-TLV: Unidirectional Residual Bandwidth */ /* Optional */ #define TE_SUBTLV_RES_BW 37 struct te_subtlv_res_bw { struct subtlv_header header; /* Value length is 4 bytes. */ float value; /* bandwidth in IEEE floating point format with units in bytes per second */ } __attribute__((__packed__)); /* Link Sub-TLV: Unidirectional Available Bandwidth */ /* Optional */ #define TE_SUBTLV_AVA_BW 38 struct te_subtlv_ava_bw { struct subtlv_header header; /* Value length is 4 octets. */ float value; /* bandwidth in IEEE floating point format with units in bytes per second */ } __attribute__((__packed__)); /* Link Sub-TLV: Unidirectional Utilized Bandwidth */ /* Optional */ #define TE_SUBTLV_USE_BW 39 struct te_subtlv_use_bw { struct subtlv_header header; /* Value length is 4 octets. */ float value; /* bandwidth in IEEE floating point format with units in bytes per second */ } __attribute__((__packed__)); #define TE_SUBTLV_MAX 40 /* Last SUBTLV + 1 */ /* Following declaration concerns the MPLS-TE and LINk-TE management */ typedef enum _status_t { disable, enable, learn } status_t; /* Mode for Inter-AS LSP */ /* TODO: Check how if LSP is flooded in RFC5316 */ typedef enum _interas_mode_t { off, region, as, emulate } interas_mode_t; #define IS_MPLS_TE(m) (m && m->status == enable) /* Per area MPLS-TE parameters */ struct mpls_te_area { /* Status of MPLS-TE: enable or disable */ status_t status; /* L1, L1-L2, L2-Only */ uint8_t level; /* RFC5316 */ interas_mode_t inter_as; struct in_addr interas_areaid; /* MPLS_TE router ID */ struct in_addr router_id; }; /* Per Circuit MPLS-TE parameters */ struct mpls_te_circuit { /* Status of MPLS-TE on this interface */ status_t status; /* Type of MPLS-TE circuit: STD_TE(RFC5305), INTER_AS(RFC5316), * INTER_AS_EMU(RFC5316 emulated) */ uint8_t type; /* Total size of sub_tlvs */ uint8_t length; /* Store subTLV in network byte order. */ /* RFC5305 */ struct te_subtlv_admin_grp admin_grp; /* RFC5307 */ struct te_subtlv_llri llri; /* RFC5305 */ struct te_subtlv_local_ipaddr local_ipaddr; struct te_subtlv_rmt_ipaddr rmt_ipaddr; struct te_subtlv_max_bw max_bw; struct te_subtlv_max_rsv_bw max_rsv_bw; struct te_subtlv_unrsv_bw unrsv_bw; struct te_subtlv_te_metric te_metric; /* RFC5316 */ struct te_subtlv_ras ras; struct te_subtlv_rip rip; /* RFC7810 */ struct te_subtlv_av_delay av_delay; struct te_subtlv_mm_delay mm_delay; struct te_subtlv_delay_var delay_var; struct te_subtlv_pkt_loss pkt_loss; struct te_subtlv_res_bw res_bw; struct te_subtlv_ava_bw ava_bw; struct te_subtlv_use_bw use_bw; }; /* Prototypes. */ void isis_mpls_te_init(void); struct mpls_te_circuit *mpls_te_circuit_new(void); struct sbuf; void mpls_te_print_detail(struct sbuf *buf, int indent, uint8_t *subtlvs, uint8_t subtlv_len); void set_circuitparams_local_ipaddr(struct mpls_te_circuit *, struct in_addr); void set_circuitparams_rmt_ipaddr(struct mpls_te_circuit *, struct in_addr); uint8_t subtlvs_len(struct mpls_te_circuit *); uint8_t add_te_subtlvs(uint8_t *, struct mpls_te_circuit *); uint8_t build_te_subtlvs(uint8_t *, struct isis_circuit *); void isis_link_params_update(struct isis_circuit *, struct interface *); void isis_mpls_te_update(struct interface *); #endif /* _ZEBRA_ISIS_MPLS_TE_H */ frr-7.2.1/isisd/isis_tlvs.c0000644000000000000000000031205213610377563012515 00000000000000/* * IS-IS TLV Serializer/Deserializer * * Copyright (C) 2015,2017 Christian Franke * * This file is part of FRR. * * FRR is free software; you can redistribute it and/or modify it * under the terms of the GNU General Public License as published by the * Free Software Foundation; either version 2, or (at your option) any * later version. * * FRR 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 FRR; see the file COPYING. If not, write to the Free * Software Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA * 02111-1307, USA. */ #include #ifdef CRYPTO_INTERNAL #include "md5.h" #endif #include "memory.h" #include "stream.h" #include "sbuf.h" #include "isisd/isisd.h" #include "isisd/isis_memory.h" #include "isisd/isis_tlvs.h" #include "isisd/isis_common.h" #include "isisd/isis_mt.h" #include "isisd/isis_misc.h" #include "isisd/isis_adjacency.h" #include "isisd/isis_circuit.h" #include "isisd/isis_pdu.h" #include "isisd/isis_lsp.h" #include "isisd/isis_te.h" DEFINE_MTYPE_STATIC(ISISD, ISIS_TLV, "ISIS TLVs") DEFINE_MTYPE_STATIC(ISISD, ISIS_SUBTLV, "ISIS Sub-TLVs") DEFINE_MTYPE_STATIC(ISISD, ISIS_MT_ITEM_LIST, "ISIS MT Item Lists") typedef int (*unpack_tlv_func)(enum isis_tlv_context context, uint8_t tlv_type, uint8_t tlv_len, struct stream *s, struct sbuf *log, void *dest, int indent); typedef int (*pack_item_func)(struct isis_item *item, struct stream *s); typedef void (*free_item_func)(struct isis_item *i); typedef int (*unpack_item_func)(uint16_t mtid, uint8_t len, struct stream *s, struct sbuf *log, void *dest, int indent); typedef void (*format_item_func)(uint16_t mtid, struct isis_item *i, struct sbuf *buf, int indent); typedef struct isis_item *(*copy_item_func)(struct isis_item *i); struct tlv_ops { const char *name; unpack_tlv_func unpack; pack_item_func pack_item; free_item_func free_item; unpack_item_func unpack_item; format_item_func format_item; copy_item_func copy_item; }; enum how_to_pack { ISIS_ITEMS, ISIS_MT_ITEMS, }; struct pack_order_entry { enum isis_tlv_context context; enum isis_tlv_type type; enum how_to_pack how_to_pack; size_t what_to_pack; }; #define PACK_ENTRY(t, h, w) \ { \ .context = ISIS_CONTEXT_LSP, .type = ISIS_TLV_##t, \ .how_to_pack = (h), \ .what_to_pack = offsetof(struct isis_tlvs, w), \ } static struct pack_order_entry pack_order[] = { PACK_ENTRY(OLDSTYLE_REACH, ISIS_ITEMS, oldstyle_reach), PACK_ENTRY(LAN_NEIGHBORS, ISIS_ITEMS, lan_neighbor), PACK_ENTRY(LSP_ENTRY, ISIS_ITEMS, lsp_entries), PACK_ENTRY(EXTENDED_REACH, ISIS_ITEMS, extended_reach), PACK_ENTRY(MT_REACH, ISIS_MT_ITEMS, mt_reach), PACK_ENTRY(OLDSTYLE_IP_REACH, ISIS_ITEMS, oldstyle_ip_reach), PACK_ENTRY(OLDSTYLE_IP_REACH_EXT, ISIS_ITEMS, oldstyle_ip_reach_ext), PACK_ENTRY(IPV4_ADDRESS, ISIS_ITEMS, ipv4_address), PACK_ENTRY(IPV6_ADDRESS, ISIS_ITEMS, ipv6_address), PACK_ENTRY(EXTENDED_IP_REACH, ISIS_ITEMS, extended_ip_reach), PACK_ENTRY(MT_IP_REACH, ISIS_MT_ITEMS, mt_ip_reach), PACK_ENTRY(IPV6_REACH, ISIS_ITEMS, ipv6_reach), PACK_ENTRY(MT_IPV6_REACH, ISIS_MT_ITEMS, mt_ipv6_reach)}; /* This is a forward definition. The table is actually initialized * in at the bottom. */ static const struct tlv_ops *tlv_table[ISIS_CONTEXT_MAX][ISIS_TLV_MAX]; /* End of _ops forward definition. */ /* Prototypes */ static void append_item(struct isis_item_list *dest, struct isis_item *item); /* Functions for Sub-TLV 3 SR Prefix-SID */ static struct isis_item *copy_item_prefix_sid(struct isis_item *i) { struct isis_prefix_sid *sid = (struct isis_prefix_sid *)i; struct isis_prefix_sid *rv = XCALLOC(MTYPE_ISIS_SUBTLV, sizeof(*rv)); rv->flags = sid->flags; rv->algorithm = sid->algorithm; rv->value = sid->value; return (struct isis_item *)rv; } static void format_item_prefix_sid(uint16_t mtid, struct isis_item *i, struct sbuf *buf, int indent) { struct isis_prefix_sid *sid = (struct isis_prefix_sid *)i; sbuf_push(buf, indent, "SR Prefix-SID:\n"); sbuf_push(buf, indent, " Flags:%s%s%s%s%s%s\n", sid->flags & ISIS_PREFIX_SID_READVERTISED ? " READVERTISED" : "", sid->flags & ISIS_PREFIX_SID_NODE ? " NODE" : "", sid->flags & ISIS_PREFIX_SID_NO_PHP ? " NO_PHP" : "", sid->flags & ISIS_PREFIX_SID_EXPLICIT_NULL ? " EXPLICIT-NULL" : "", sid->flags & ISIS_PREFIX_SID_VALUE ? " VALUE" : "", sid->flags & ISIS_PREFIX_SID_LOCAL ? " LOCAL" : ""); sbuf_push(buf, indent, " Algorithm: %" PRIu8 "\n", sid->algorithm); if (sid->flags & ISIS_PREFIX_SID_VALUE) { sbuf_push(buf, indent, "Label: %" PRIu32 "\n", sid->value); } else { sbuf_push(buf, indent, "Index: %" PRIu32 "\n", sid->value); } } static void free_item_prefix_sid(struct isis_item *i) { XFREE(MTYPE_ISIS_SUBTLV, i); } static int pack_item_prefix_sid(struct isis_item *i, struct stream *s) { struct isis_prefix_sid *sid = (struct isis_prefix_sid *)i; uint8_t size = (sid->flags & ISIS_PREFIX_SID_VALUE) ? 5 : 6; if (STREAM_WRITEABLE(s) < size) return 1; stream_putc(s, sid->flags); stream_putc(s, sid->algorithm); if (sid->flags & ISIS_PREFIX_SID_VALUE) { stream_put3(s, sid->value); } else { stream_putl(s, sid->value); } return 0; } static int unpack_item_prefix_sid(uint16_t mtid, uint8_t len, struct stream *s, struct sbuf *log, void *dest, int indent) { struct isis_subtlvs *subtlvs = dest; struct isis_prefix_sid sid = { }; sbuf_push(log, indent, "Unpacking SR Prefix-SID...\n"); if (len < 5) { sbuf_push(log, indent, "Not enough data left. (expected 5 or more bytes, got %" PRIu8 ")\n", len); return 1; } sid.flags = stream_getc(s); if ((sid.flags & ISIS_PREFIX_SID_VALUE) != (sid.flags & ISIS_PREFIX_SID_LOCAL)) { sbuf_push(log, indent, "Flags inplausible: Local Flag needs to match Value Flag\n"); return 0; } sid.algorithm = stream_getc(s); uint8_t expected_size = (sid.flags & ISIS_PREFIX_SID_VALUE) ? 5 : 6; if (len != expected_size) { sbuf_push(log, indent, "TLV size differs from expected size. " "(expected %u but got %" PRIu8 ")\n", expected_size, len); return 1; } if (sid.flags & ISIS_PREFIX_SID_VALUE) { sid.value = stream_get3(s); } else { sid.value = stream_getl(s); } format_item_prefix_sid(mtid, (struct isis_item *)&sid, log, indent + 2); append_item(&subtlvs->prefix_sids, copy_item_prefix_sid((struct isis_item *)&sid)); return 0; } /* Functions for Sub-TVL ??? IPv6 Source Prefix */ static struct prefix_ipv6 *copy_subtlv_ipv6_source_prefix(struct prefix_ipv6 *p) { if (!p) return NULL; struct prefix_ipv6 *rv = XCALLOC(MTYPE_ISIS_SUBTLV, sizeof(*rv)); rv->family = p->family; rv->prefixlen = p->prefixlen; memcpy(&rv->prefix, &p->prefix, sizeof(rv->prefix)); return rv; } static void format_subtlv_ipv6_source_prefix(struct prefix_ipv6 *p, struct sbuf *buf, int indent) { if (!p) return; char prefixbuf[PREFIX2STR_BUFFER]; sbuf_push(buf, indent, "IPv6 Source Prefix: %s\n", prefix2str(p, prefixbuf, sizeof(prefixbuf))); } static int pack_subtlv_ipv6_source_prefix(struct prefix_ipv6 *p, struct stream *s) { if (!p) return 0; if (STREAM_WRITEABLE(s) < 3 + (unsigned)PSIZE(p->prefixlen)) return 1; stream_putc(s, ISIS_SUBTLV_IPV6_SOURCE_PREFIX); stream_putc(s, 1 + PSIZE(p->prefixlen)); stream_putc(s, p->prefixlen); stream_put(s, &p->prefix, PSIZE(p->prefixlen)); return 0; } static int unpack_subtlv_ipv6_source_prefix(enum isis_tlv_context context, uint8_t tlv_type, uint8_t tlv_len, struct stream *s, struct sbuf *log, void *dest, int indent) { struct isis_subtlvs *subtlvs = dest; struct prefix_ipv6 p = { .family = AF_INET6, }; sbuf_push(log, indent, "Unpacking IPv6 Source Prefix Sub-TLV...\n"); if (tlv_len < 1) { sbuf_push(log, indent, "Not enough data left. (expected 1 or more bytes, got %" PRIu8 ")\n", tlv_len); return 1; } p.prefixlen = stream_getc(s); if (p.prefixlen > 128) { sbuf_push(log, indent, "Prefixlen %u is inplausible for IPv6\n", p.prefixlen); return 1; } if (tlv_len != 1 + PSIZE(p.prefixlen)) { sbuf_push( log, indent, "TLV size differs from expected size for the prefixlen. " "(expected %u but got %" PRIu8 ")\n", 1 + PSIZE(p.prefixlen), tlv_len); return 1; } stream_get(&p.prefix, s, PSIZE(p.prefixlen)); if (subtlvs->source_prefix) { sbuf_push( log, indent, "WARNING: source prefix Sub-TLV present multiple times.\n"); /* Ignore all but first occurrence of the source prefix Sub-TLV */ return 0; } subtlvs->source_prefix = XCALLOC(MTYPE_ISIS_SUBTLV, sizeof(p)); memcpy(subtlvs->source_prefix, &p, sizeof(p)); return 0; } static void init_item_list(struct isis_item_list *items); static struct isis_item *copy_item(enum isis_tlv_context context, enum isis_tlv_type type, struct isis_item *item); static void copy_items(enum isis_tlv_context context, enum isis_tlv_type type, struct isis_item_list *src, struct isis_item_list *dest); static void format_items_(uint16_t mtid, enum isis_tlv_context context, enum isis_tlv_type type, struct isis_item_list *items, struct sbuf *buf, int indent); #define format_items(...) format_items_(ISIS_MT_IPV4_UNICAST, __VA_ARGS__) static void free_items(enum isis_tlv_context context, enum isis_tlv_type type, struct isis_item_list *items); static int pack_items_(uint16_t mtid, enum isis_tlv_context context, enum isis_tlv_type type, struct isis_item_list *items, struct stream *s, struct isis_tlvs **fragment_tlvs, struct pack_order_entry *pe, struct isis_tlvs *(*new_fragment)(struct list *l), struct list *new_fragment_arg); #define pack_items(...) pack_items_(ISIS_MT_IPV4_UNICAST, __VA_ARGS__) /* Functions related to subtlvs */ static struct isis_subtlvs *isis_alloc_subtlvs(enum isis_tlv_context context) { struct isis_subtlvs *result; result = XCALLOC(MTYPE_ISIS_SUBTLV, sizeof(*result)); result->context = context; init_item_list(&result->prefix_sids); return result; } static struct isis_subtlvs *copy_subtlvs(struct isis_subtlvs *subtlvs) { if (!subtlvs) return NULL; struct isis_subtlvs *rv = XCALLOC(MTYPE_ISIS_SUBTLV, sizeof(*rv)); rv->context = subtlvs->context; copy_items(subtlvs->context, ISIS_SUBTLV_PREFIX_SID, &subtlvs->prefix_sids, &rv->prefix_sids); rv->source_prefix = copy_subtlv_ipv6_source_prefix(subtlvs->source_prefix); return rv; } static void format_subtlvs(struct isis_subtlvs *subtlvs, struct sbuf *buf, int indent) { format_items(subtlvs->context, ISIS_SUBTLV_PREFIX_SID, &subtlvs->prefix_sids, buf, indent); format_subtlv_ipv6_source_prefix(subtlvs->source_prefix, buf, indent); } static void isis_free_subtlvs(struct isis_subtlvs *subtlvs) { if (!subtlvs) return; free_items(subtlvs->context, ISIS_SUBTLV_PREFIX_SID, &subtlvs->prefix_sids); XFREE(MTYPE_ISIS_SUBTLV, subtlvs->source_prefix); XFREE(MTYPE_ISIS_SUBTLV, subtlvs); } static int pack_subtlvs(struct isis_subtlvs *subtlvs, struct stream *s) { int rv; size_t subtlv_len_pos = stream_get_endp(s); if (STREAM_WRITEABLE(s) < 1) return 1; stream_putc(s, 0); /* Put 0 as subtlvs length, filled in later */ rv = pack_items(subtlvs->context, ISIS_SUBTLV_PREFIX_SID, &subtlvs->prefix_sids, s, NULL, NULL, NULL, NULL); if (rv) return rv; rv = pack_subtlv_ipv6_source_prefix(subtlvs->source_prefix, s); if (rv) return rv; size_t subtlv_len = stream_get_endp(s) - subtlv_len_pos - 1; if (subtlv_len > 255) return 1; stream_putc_at(s, subtlv_len_pos, subtlv_len); return 0; } static int unpack_tlvs(enum isis_tlv_context context, size_t avail_len, struct stream *stream, struct sbuf *log, void *dest, int indent, bool *unpacked_known_tlvs); /* Functions related to TLVs 1 Area Addresses */ static struct isis_item *copy_item_area_address(struct isis_item *i) { struct isis_area_address *addr = (struct isis_area_address *)i; struct isis_area_address *rv = XCALLOC(MTYPE_ISIS_TLV, sizeof(*rv)); rv->len = addr->len; memcpy(rv->addr, addr->addr, addr->len); return (struct isis_item *)rv; } static void format_item_area_address(uint16_t mtid, struct isis_item *i, struct sbuf *buf, int indent) { struct isis_area_address *addr = (struct isis_area_address *)i; sbuf_push(buf, indent, "Area Address: %s\n", isonet_print(addr->addr, addr->len)); } static void free_item_area_address(struct isis_item *i) { XFREE(MTYPE_ISIS_TLV, i); } static int pack_item_area_address(struct isis_item *i, struct stream *s) { struct isis_area_address *addr = (struct isis_area_address *)i; if (STREAM_WRITEABLE(s) < (unsigned)1 + addr->len) return 1; stream_putc(s, addr->len); stream_put(s, addr->addr, addr->len); return 0; } static int unpack_item_area_address(uint16_t mtid, uint8_t len, struct stream *s, struct sbuf *log, void *dest, int indent) { struct isis_tlvs *tlvs = dest; struct isis_area_address *rv = NULL; sbuf_push(log, indent, "Unpack area address...\n"); if (len < 1) { sbuf_push( log, indent, "Not enough data left. (Expected 1 byte of address length, got %" PRIu8 ")\n", len); goto out; } rv = XCALLOC(MTYPE_ISIS_TLV, sizeof(*rv)); rv->len = stream_getc(s); if (len < 1 + rv->len) { sbuf_push(log, indent, "Not enough data left. (Expected %" PRIu8 " bytes of address, got %" PRIu8 ")\n", rv->len, len - 1); goto out; } if (rv->len < 1 || rv->len > 20) { sbuf_push(log, indent, "Implausible area address length %" PRIu8 "\n", rv->len); goto out; } stream_get(rv->addr, s, rv->len); format_item_area_address(ISIS_MT_IPV4_UNICAST, (struct isis_item *)rv, log, indent + 2); append_item(&tlvs->area_addresses, (struct isis_item *)rv); return 0; out: XFREE(MTYPE_ISIS_TLV, rv); return 1; } /* Functions related to TLV 2 (Old-Style) IS Reach */ static struct isis_item *copy_item_oldstyle_reach(struct isis_item *i) { struct isis_oldstyle_reach *r = (struct isis_oldstyle_reach *)i; struct isis_oldstyle_reach *rv = XCALLOC(MTYPE_ISIS_TLV, sizeof(*rv)); memcpy(rv->id, r->id, 7); rv->metric = r->metric; return (struct isis_item *)rv; } static void format_item_oldstyle_reach(uint16_t mtid, struct isis_item *i, struct sbuf *buf, int indent) { struct isis_oldstyle_reach *r = (struct isis_oldstyle_reach *)i; sbuf_push(buf, indent, "IS Reachability: %s (Metric: %" PRIu8 ")\n", isis_format_id(r->id, 7), r->metric); } static void free_item_oldstyle_reach(struct isis_item *i) { XFREE(MTYPE_ISIS_TLV, i); } static int pack_item_oldstyle_reach(struct isis_item *i, struct stream *s) { struct isis_oldstyle_reach *r = (struct isis_oldstyle_reach *)i; if (STREAM_WRITEABLE(s) < 11) return 1; stream_putc(s, r->metric); stream_putc(s, 0x80); /* delay metric - unsupported */ stream_putc(s, 0x80); /* expense metric - unsupported */ stream_putc(s, 0x80); /* error metric - unsupported */ stream_put(s, r->id, 7); return 0; } static int unpack_item_oldstyle_reach(uint16_t mtid, uint8_t len, struct stream *s, struct sbuf *log, void *dest, int indent) { struct isis_tlvs *tlvs = dest; sbuf_push(log, indent, "Unpack oldstyle reach...\n"); if (len < 11) { sbuf_push( log, indent, "Not enough data left.(Expected 11 bytes of reach information, got %" PRIu8 ")\n", len); return 1; } struct isis_oldstyle_reach *rv = XCALLOC(MTYPE_ISIS_TLV, sizeof(*rv)); rv->metric = stream_getc(s); if ((rv->metric & 0x3f) != rv->metric) { sbuf_push(log, indent, "Metric has unplausible format\n"); rv->metric &= 0x3f; } stream_forward_getp(s, 3); /* Skip other metrics */ stream_get(rv->id, s, 7); format_item_oldstyle_reach(mtid, (struct isis_item *)rv, log, indent + 2); append_item(&tlvs->oldstyle_reach, (struct isis_item *)rv); return 0; } /* Functions related to TLV 6 LAN Neighbors */ static struct isis_item *copy_item_lan_neighbor(struct isis_item *i) { struct isis_lan_neighbor *n = (struct isis_lan_neighbor *)i; struct isis_lan_neighbor *rv = XCALLOC(MTYPE_ISIS_TLV, sizeof(*rv)); memcpy(rv->mac, n->mac, 6); return (struct isis_item *)rv; } static void format_item_lan_neighbor(uint16_t mtid, struct isis_item *i, struct sbuf *buf, int indent) { struct isis_lan_neighbor *n = (struct isis_lan_neighbor *)i; sbuf_push(buf, indent, "LAN Neighbor: %s\n", isis_format_id(n->mac, 6)); } static void free_item_lan_neighbor(struct isis_item *i) { XFREE(MTYPE_ISIS_TLV, i); } static int pack_item_lan_neighbor(struct isis_item *i, struct stream *s) { struct isis_lan_neighbor *n = (struct isis_lan_neighbor *)i; if (STREAM_WRITEABLE(s) < 6) return 1; stream_put(s, n->mac, 6); return 0; } static int unpack_item_lan_neighbor(uint16_t mtid, uint8_t len, struct stream *s, struct sbuf *log, void *dest, int indent) { struct isis_tlvs *tlvs = dest; sbuf_push(log, indent, "Unpack LAN neighbor...\n"); if (len < 6) { sbuf_push( log, indent, "Not enough data left.(Expected 6 bytes of mac, got %" PRIu8 ")\n", len); return 1; } struct isis_lan_neighbor *rv = XCALLOC(MTYPE_ISIS_TLV, sizeof(*rv)); stream_get(rv->mac, s, 6); format_item_lan_neighbor(mtid, (struct isis_item *)rv, log, indent + 2); append_item(&tlvs->lan_neighbor, (struct isis_item *)rv); return 0; } /* Functions related to TLV 9 LSP Entry */ static struct isis_item *copy_item_lsp_entry(struct isis_item *i) { struct isis_lsp_entry *e = (struct isis_lsp_entry *)i; struct isis_lsp_entry *rv = XCALLOC(MTYPE_ISIS_TLV, sizeof(*rv)); rv->rem_lifetime = e->rem_lifetime; memcpy(rv->id, e->id, sizeof(rv->id)); rv->seqno = e->seqno; rv->checksum = e->checksum; return (struct isis_item *)rv; } static void format_item_lsp_entry(uint16_t mtid, struct isis_item *i, struct sbuf *buf, int indent) { struct isis_lsp_entry *e = (struct isis_lsp_entry *)i; sbuf_push(buf, indent, "LSP Entry: %s, seq 0x%08" PRIx32 ", cksum 0x%04" PRIx16 ", lifetime %" PRIu16 "s\n", isis_format_id(e->id, 8), e->seqno, e->checksum, e->rem_lifetime); } static void free_item_lsp_entry(struct isis_item *i) { XFREE(MTYPE_ISIS_TLV, i); } static int pack_item_lsp_entry(struct isis_item *i, struct stream *s) { struct isis_lsp_entry *e = (struct isis_lsp_entry *)i; if (STREAM_WRITEABLE(s) < 16) return 1; stream_putw(s, e->rem_lifetime); stream_put(s, e->id, 8); stream_putl(s, e->seqno); stream_putw(s, e->checksum); return 0; } static int unpack_item_lsp_entry(uint16_t mtid, uint8_t len, struct stream *s, struct sbuf *log, void *dest, int indent) { struct isis_tlvs *tlvs = dest; sbuf_push(log, indent, "Unpack LSP entry...\n"); if (len < 16) { sbuf_push( log, indent, "Not enough data left. (Expected 16 bytes of LSP info, got %" PRIu8, len); return 1; } struct isis_lsp_entry *rv = XCALLOC(MTYPE_ISIS_TLV, sizeof(*rv)); rv->rem_lifetime = stream_getw(s); stream_get(rv->id, s, 8); rv->seqno = stream_getl(s); rv->checksum = stream_getw(s); format_item_lsp_entry(mtid, (struct isis_item *)rv, log, indent + 2); append_item(&tlvs->lsp_entries, (struct isis_item *)rv); return 0; } /* Functions related to TLVs 22/222 Extended Reach/MT Reach */ static struct isis_item *copy_item_extended_reach(struct isis_item *i) { struct isis_extended_reach *r = (struct isis_extended_reach *)i; struct isis_extended_reach *rv = XCALLOC(MTYPE_ISIS_TLV, sizeof(*rv)); memcpy(rv->id, r->id, 7); rv->metric = r->metric; if (r->subtlvs && r->subtlv_len) { rv->subtlvs = XCALLOC(MTYPE_ISIS_TLV, r->subtlv_len); memcpy(rv->subtlvs, r->subtlvs, r->subtlv_len); rv->subtlv_len = r->subtlv_len; } return (struct isis_item *)rv; } static void format_item_extended_reach(uint16_t mtid, struct isis_item *i, struct sbuf *buf, int indent) { struct isis_extended_reach *r = (struct isis_extended_reach *)i; sbuf_push(buf, indent, "%s Reachability: %s (Metric: %u)", (mtid == ISIS_MT_IPV4_UNICAST) ? "Extended" : "MT", isis_format_id(r->id, 7), r->metric); if (mtid != ISIS_MT_IPV4_UNICAST) sbuf_push(buf, 0, " %s", isis_mtid2str(mtid)); sbuf_push(buf, 0, "\n"); if (r->subtlv_len && r->subtlvs) mpls_te_print_detail(buf, indent + 2, r->subtlvs, r->subtlv_len); } static void free_item_extended_reach(struct isis_item *i) { struct isis_extended_reach *item = (struct isis_extended_reach *)i; XFREE(MTYPE_ISIS_TLV, item->subtlvs); XFREE(MTYPE_ISIS_TLV, item); } static int pack_item_extended_reach(struct isis_item *i, struct stream *s) { struct isis_extended_reach *r = (struct isis_extended_reach *)i; if (STREAM_WRITEABLE(s) < 11 + (unsigned)r->subtlv_len) return 1; stream_put(s, r->id, sizeof(r->id)); stream_put3(s, r->metric); stream_putc(s, r->subtlv_len); stream_put(s, r->subtlvs, r->subtlv_len); return 0; } static int unpack_item_extended_reach(uint16_t mtid, uint8_t len, struct stream *s, struct sbuf *log, void *dest, int indent) { struct isis_tlvs *tlvs = dest; struct isis_extended_reach *rv = NULL; uint8_t subtlv_len; struct isis_item_list *items; if (mtid == ISIS_MT_IPV4_UNICAST) { items = &tlvs->extended_reach; } else { items = isis_get_mt_items(&tlvs->mt_reach, mtid); } sbuf_push(log, indent, "Unpacking %s reachability...\n", (mtid == ISIS_MT_IPV4_UNICAST) ? "extended" : "mt"); if (len < 11) { sbuf_push(log, indent, "Not enough data left. (expected 11 or more bytes, got %" PRIu8 ")\n", len); goto out; } rv = XCALLOC(MTYPE_ISIS_TLV, sizeof(*rv)); stream_get(rv->id, s, 7); rv->metric = stream_get3(s); subtlv_len = stream_getc(s); format_item_extended_reach(mtid, (struct isis_item *)rv, log, indent + 2); if ((size_t)len < ((size_t)11) + subtlv_len) { sbuf_push(log, indent, "Not enough data left for subtlv size %" PRIu8 ", there are only %" PRIu8 " bytes left.\n", subtlv_len, len - 11); goto out; } sbuf_push(log, indent, "Storing %" PRIu8 " bytes of subtlvs\n", subtlv_len); if (subtlv_len) { size_t subtlv_start = stream_get_getp(s); if (unpack_tlvs(ISIS_CONTEXT_SUBTLV_NE_REACH, subtlv_len, s, log, NULL, indent + 4, NULL)) { goto out; } stream_set_getp(s, subtlv_start); rv->subtlvs = XCALLOC(MTYPE_ISIS_TLV, subtlv_len); stream_get(rv->subtlvs, s, subtlv_len); rv->subtlv_len = subtlv_len; } append_item(items, (struct isis_item *)rv); return 0; out: if (rv) free_item_extended_reach((struct isis_item *)rv); return 1; } /* Functions related to TLV 128 (Old-Style) IP Reach */ static struct isis_item *copy_item_oldstyle_ip_reach(struct isis_item *i) { struct isis_oldstyle_ip_reach *r = (struct isis_oldstyle_ip_reach *)i; struct isis_oldstyle_ip_reach *rv = XCALLOC(MTYPE_ISIS_TLV, sizeof(*rv)); rv->metric = r->metric; rv->prefix = r->prefix; return (struct isis_item *)rv; } static void format_item_oldstyle_ip_reach(uint16_t mtid, struct isis_item *i, struct sbuf *buf, int indent) { struct isis_oldstyle_ip_reach *r = (struct isis_oldstyle_ip_reach *)i; char prefixbuf[PREFIX2STR_BUFFER]; sbuf_push(buf, indent, "IP Reachability: %s (Metric: %" PRIu8 ")\n", prefix2str(&r->prefix, prefixbuf, sizeof(prefixbuf)), r->metric); } static void free_item_oldstyle_ip_reach(struct isis_item *i) { XFREE(MTYPE_ISIS_TLV, i); } static int pack_item_oldstyle_ip_reach(struct isis_item *i, struct stream *s) { struct isis_oldstyle_ip_reach *r = (struct isis_oldstyle_ip_reach *)i; if (STREAM_WRITEABLE(s) < 12) return 1; stream_putc(s, r->metric); stream_putc(s, 0x80); /* delay metric - unsupported */ stream_putc(s, 0x80); /* expense metric - unsupported */ stream_putc(s, 0x80); /* error metric - unsupported */ stream_put(s, &r->prefix.prefix, 4); struct in_addr mask; masklen2ip(r->prefix.prefixlen, &mask); stream_put(s, &mask, sizeof(mask)); return 0; } static int unpack_item_oldstyle_ip_reach(uint16_t mtid, uint8_t len, struct stream *s, struct sbuf *log, void *dest, int indent) { sbuf_push(log, indent, "Unpack oldstyle ip reach...\n"); if (len < 12) { sbuf_push( log, indent, "Not enough data left.(Expected 12 bytes of reach information, got %" PRIu8 ")\n", len); return 1; } struct isis_oldstyle_ip_reach *rv = XCALLOC(MTYPE_ISIS_TLV, sizeof(*rv)); rv->metric = stream_getc(s); if ((rv->metric & 0x7f) != rv->metric) { sbuf_push(log, indent, "Metric has unplausible format\n"); rv->metric &= 0x7f; } stream_forward_getp(s, 3); /* Skip other metrics */ rv->prefix.family = AF_INET; stream_get(&rv->prefix.prefix, s, 4); struct in_addr mask; stream_get(&mask, s, 4); rv->prefix.prefixlen = ip_masklen(mask); format_item_oldstyle_ip_reach(mtid, (struct isis_item *)rv, log, indent + 2); append_item(dest, (struct isis_item *)rv); return 0; } /* Functions related to TLV 129 protocols supported */ static void copy_tlv_protocols_supported(struct isis_protocols_supported *src, struct isis_protocols_supported *dest) { if (!src->protocols || !src->count) return; dest->count = src->count; dest->protocols = XCALLOC(MTYPE_ISIS_TLV, src->count); memcpy(dest->protocols, src->protocols, src->count); } static void format_tlv_protocols_supported(struct isis_protocols_supported *p, struct sbuf *buf, int indent) { if (!p || !p->count || !p->protocols) return; sbuf_push(buf, indent, "Protocols Supported: "); for (uint8_t i = 0; i < p->count; i++) { sbuf_push(buf, 0, "%s%s", nlpid2str(p->protocols[i]), (i + 1 < p->count) ? ", " : ""); } sbuf_push(buf, 0, "\n"); } static void free_tlv_protocols_supported(struct isis_protocols_supported *p) { XFREE(MTYPE_ISIS_TLV, p->protocols); } static int pack_tlv_protocols_supported(struct isis_protocols_supported *p, struct stream *s) { if (!p || !p->count || !p->protocols) return 0; if (STREAM_WRITEABLE(s) < (unsigned)(p->count + 2)) return 1; stream_putc(s, ISIS_TLV_PROTOCOLS_SUPPORTED); stream_putc(s, p->count); stream_put(s, p->protocols, p->count); return 0; } static int unpack_tlv_protocols_supported(enum isis_tlv_context context, uint8_t tlv_type, uint8_t tlv_len, struct stream *s, struct sbuf *log, void *dest, int indent) { struct isis_tlvs *tlvs = dest; sbuf_push(log, indent, "Unpacking Protocols Supported TLV...\n"); if (!tlv_len) { sbuf_push(log, indent, "WARNING: No protocols included\n"); return 0; } if (tlvs->protocols_supported.protocols) { sbuf_push( log, indent, "WARNING: protocols supported TLV present multiple times.\n"); stream_forward_getp(s, tlv_len); return 0; } tlvs->protocols_supported.count = tlv_len; tlvs->protocols_supported.protocols = XCALLOC(MTYPE_ISIS_TLV, tlv_len); stream_get(tlvs->protocols_supported.protocols, s, tlv_len); format_tlv_protocols_supported(&tlvs->protocols_supported, log, indent + 2); return 0; } /* Functions related to TLV 132 IPv4 Interface addresses */ static struct isis_item *copy_item_ipv4_address(struct isis_item *i) { struct isis_ipv4_address *a = (struct isis_ipv4_address *)i; struct isis_ipv4_address *rv = XCALLOC(MTYPE_ISIS_TLV, sizeof(*rv)); rv->addr = a->addr; return (struct isis_item *)rv; } static void format_item_ipv4_address(uint16_t mtid, struct isis_item *i, struct sbuf *buf, int indent) { struct isis_ipv4_address *a = (struct isis_ipv4_address *)i; char addrbuf[INET_ADDRSTRLEN]; inet_ntop(AF_INET, &a->addr, addrbuf, sizeof(addrbuf)); sbuf_push(buf, indent, "IPv4 Interface Address: %s\n", addrbuf); } static void free_item_ipv4_address(struct isis_item *i) { XFREE(MTYPE_ISIS_TLV, i); } static int pack_item_ipv4_address(struct isis_item *i, struct stream *s) { struct isis_ipv4_address *a = (struct isis_ipv4_address *)i; if (STREAM_WRITEABLE(s) < 4) return 1; stream_put(s, &a->addr, 4); return 0; } static int unpack_item_ipv4_address(uint16_t mtid, uint8_t len, struct stream *s, struct sbuf *log, void *dest, int indent) { struct isis_tlvs *tlvs = dest; sbuf_push(log, indent, "Unpack IPv4 Interface address...\n"); if (len < 4) { sbuf_push( log, indent, "Not enough data left.(Expected 4 bytes of IPv4 address, got %" PRIu8 ")\n", len); return 1; } struct isis_ipv4_address *rv = XCALLOC(MTYPE_ISIS_TLV, sizeof(*rv)); stream_get(&rv->addr, s, 4); format_item_ipv4_address(mtid, (struct isis_item *)rv, log, indent + 2); append_item(&tlvs->ipv4_address, (struct isis_item *)rv); return 0; } /* Functions related to TLV 232 IPv6 Interface addresses */ static struct isis_item *copy_item_ipv6_address(struct isis_item *i) { struct isis_ipv6_address *a = (struct isis_ipv6_address *)i; struct isis_ipv6_address *rv = XCALLOC(MTYPE_ISIS_TLV, sizeof(*rv)); rv->addr = a->addr; return (struct isis_item *)rv; } static void format_item_ipv6_address(uint16_t mtid, struct isis_item *i, struct sbuf *buf, int indent) { struct isis_ipv6_address *a = (struct isis_ipv6_address *)i; char addrbuf[INET6_ADDRSTRLEN]; inet_ntop(AF_INET6, &a->addr, addrbuf, sizeof(addrbuf)); sbuf_push(buf, indent, "IPv6 Interface Address: %s\n", addrbuf); } static void free_item_ipv6_address(struct isis_item *i) { XFREE(MTYPE_ISIS_TLV, i); } static int pack_item_ipv6_address(struct isis_item *i, struct stream *s) { struct isis_ipv6_address *a = (struct isis_ipv6_address *)i; if (STREAM_WRITEABLE(s) < 16) return 1; stream_put(s, &a->addr, 16); return 0; } static int unpack_item_ipv6_address(uint16_t mtid, uint8_t len, struct stream *s, struct sbuf *log, void *dest, int indent) { struct isis_tlvs *tlvs = dest; sbuf_push(log, indent, "Unpack IPv6 Interface address...\n"); if (len < 16) { sbuf_push( log, indent, "Not enough data left.(Expected 16 bytes of IPv6 address, got %" PRIu8 ")\n", len); return 1; } struct isis_ipv6_address *rv = XCALLOC(MTYPE_ISIS_TLV, sizeof(*rv)); stream_get(&rv->addr, s, 16); format_item_ipv6_address(mtid, (struct isis_item *)rv, log, indent + 2); append_item(&tlvs->ipv6_address, (struct isis_item *)rv); return 0; } /* Functions related to TLV 229 MT Router information */ static struct isis_item *copy_item_mt_router_info(struct isis_item *i) { struct isis_mt_router_info *info = (struct isis_mt_router_info *)i; struct isis_mt_router_info *rv = XCALLOC(MTYPE_ISIS_TLV, sizeof(*rv)); rv->overload = info->overload; rv->attached = info->attached; rv->mtid = info->mtid; return (struct isis_item *)rv; } static void format_item_mt_router_info(uint16_t mtid, struct isis_item *i, struct sbuf *buf, int indent) { struct isis_mt_router_info *info = (struct isis_mt_router_info *)i; sbuf_push(buf, indent, "MT Router Info: %s%s%s\n", isis_mtid2str(info->mtid), info->overload ? " Overload" : "", info->attached ? " Attached" : ""); } static void free_item_mt_router_info(struct isis_item *i) { XFREE(MTYPE_ISIS_TLV, i); } static int pack_item_mt_router_info(struct isis_item *i, struct stream *s) { struct isis_mt_router_info *info = (struct isis_mt_router_info *)i; if (STREAM_WRITEABLE(s) < 2) return 1; uint16_t entry = info->mtid; if (info->overload) entry |= ISIS_MT_OL_MASK; if (info->attached) entry |= ISIS_MT_AT_MASK; stream_putw(s, entry); return 0; } static int unpack_item_mt_router_info(uint16_t mtid, uint8_t len, struct stream *s, struct sbuf *log, void *dest, int indent) { struct isis_tlvs *tlvs = dest; sbuf_push(log, indent, "Unpack MT Router info...\n"); if (len < 2) { sbuf_push( log, indent, "Not enough data left.(Expected 2 bytes of MT info, got %" PRIu8 ")\n", len); return 1; } struct isis_mt_router_info *rv = XCALLOC(MTYPE_ISIS_TLV, sizeof(*rv)); uint16_t entry = stream_getw(s); rv->overload = entry & ISIS_MT_OL_MASK; rv->attached = entry & ISIS_MT_AT_MASK; rv->mtid = entry & ISIS_MT_MASK; format_item_mt_router_info(mtid, (struct isis_item *)rv, log, indent + 2); append_item(&tlvs->mt_router_info, (struct isis_item *)rv); return 0; } /* Functions related to TLV 134 TE Router ID */ static struct in_addr *copy_tlv_te_router_id(const struct in_addr *id) { if (!id) return NULL; struct in_addr *rv = XCALLOC(MTYPE_ISIS_TLV, sizeof(*rv)); memcpy(rv, id, sizeof(*rv)); return rv; } static void format_tlv_te_router_id(const struct in_addr *id, struct sbuf *buf, int indent) { if (!id) return; char addrbuf[INET_ADDRSTRLEN]; inet_ntop(AF_INET, id, addrbuf, sizeof(addrbuf)); sbuf_push(buf, indent, "TE Router ID: %s\n", addrbuf); } static void free_tlv_te_router_id(struct in_addr *id) { XFREE(MTYPE_ISIS_TLV, id); } static int pack_tlv_te_router_id(const struct in_addr *id, struct stream *s) { if (!id) return 0; if (STREAM_WRITEABLE(s) < (unsigned)(2 + sizeof(*id))) return 1; stream_putc(s, ISIS_TLV_TE_ROUTER_ID); stream_putc(s, 4); stream_put(s, id, 4); return 0; } static int unpack_tlv_te_router_id(enum isis_tlv_context context, uint8_t tlv_type, uint8_t tlv_len, struct stream *s, struct sbuf *log, void *dest, int indent) { struct isis_tlvs *tlvs = dest; sbuf_push(log, indent, "Unpacking TE Router ID TLV...\n"); if (tlv_len != 4) { sbuf_push(log, indent, "WARNING: Length invalid\n"); return 1; } if (tlvs->te_router_id) { sbuf_push(log, indent, "WARNING: TE Router ID present multiple times.\n"); stream_forward_getp(s, tlv_len); return 0; } tlvs->te_router_id = XCALLOC(MTYPE_ISIS_TLV, 4); stream_get(tlvs->te_router_id, s, 4); format_tlv_te_router_id(tlvs->te_router_id, log, indent + 2); return 0; } /* Functions related to TLVs 135/235 extended IP reach/MT IP Reach */ static struct isis_item *copy_item_extended_ip_reach(struct isis_item *i) { struct isis_extended_ip_reach *r = (struct isis_extended_ip_reach *)i; struct isis_extended_ip_reach *rv = XCALLOC(MTYPE_ISIS_TLV, sizeof(*rv)); rv->metric = r->metric; rv->down = r->down; rv->prefix = r->prefix; return (struct isis_item *)rv; } static void format_item_extended_ip_reach(uint16_t mtid, struct isis_item *i, struct sbuf *buf, int indent) { struct isis_extended_ip_reach *r = (struct isis_extended_ip_reach *)i; char prefixbuf[PREFIX2STR_BUFFER]; sbuf_push(buf, indent, "%s IP Reachability: %s (Metric: %u)%s", (mtid == ISIS_MT_IPV4_UNICAST) ? "Extended" : "MT", prefix2str(&r->prefix, prefixbuf, sizeof(prefixbuf)), r->metric, r->down ? " Down" : ""); if (mtid != ISIS_MT_IPV4_UNICAST) sbuf_push(buf, 0, " %s", isis_mtid2str(mtid)); sbuf_push(buf, 0, "\n"); if (r->subtlvs) { sbuf_push(buf, indent, " Subtlvs:\n"); format_subtlvs(r->subtlvs, buf, indent + 4); } } static void free_item_extended_ip_reach(struct isis_item *i) { struct isis_extended_ip_reach *item = (struct isis_extended_ip_reach *)i; isis_free_subtlvs(item->subtlvs); XFREE(MTYPE_ISIS_TLV, item); } static int pack_item_extended_ip_reach(struct isis_item *i, struct stream *s) { struct isis_extended_ip_reach *r = (struct isis_extended_ip_reach *)i; uint8_t control; if (STREAM_WRITEABLE(s) < 5) return 1; stream_putl(s, r->metric); control = r->down ? ISIS_EXTENDED_IP_REACH_DOWN : 0; control |= r->prefix.prefixlen; control |= r->subtlvs ? ISIS_EXTENDED_IP_REACH_SUBTLV : 0; stream_putc(s, control); if (STREAM_WRITEABLE(s) < (unsigned)PSIZE(r->prefix.prefixlen)) return 1; stream_put(s, &r->prefix.prefix.s_addr, PSIZE(r->prefix.prefixlen)); if (r->subtlvs) return pack_subtlvs(r->subtlvs, s); return 0; } static int unpack_item_extended_ip_reach(uint16_t mtid, uint8_t len, struct stream *s, struct sbuf *log, void *dest, int indent) { struct isis_tlvs *tlvs = dest; struct isis_extended_ip_reach *rv = NULL; size_t consume; uint8_t control, subtlv_len; struct isis_item_list *items; if (mtid == ISIS_MT_IPV4_UNICAST) { items = &tlvs->extended_ip_reach; } else { items = isis_get_mt_items(&tlvs->mt_ip_reach, mtid); } sbuf_push(log, indent, "Unpacking %s IPv4 reachability...\n", (mtid == ISIS_MT_IPV4_UNICAST) ? "extended" : "mt"); consume = 5; if (len < consume) { sbuf_push(log, indent, "Not enough data left. (expected 5 or more bytes, got %" PRIu8 ")\n", len); goto out; } rv = XCALLOC(MTYPE_ISIS_TLV, sizeof(*rv)); rv->metric = stream_getl(s); control = stream_getc(s); rv->down = (control & ISIS_EXTENDED_IP_REACH_DOWN); rv->prefix.family = AF_INET; rv->prefix.prefixlen = control & 0x3f; if (rv->prefix.prefixlen > 32) { sbuf_push(log, indent, "Prefixlen %u is inplausible for IPv4\n", rv->prefix.prefixlen); goto out; } consume += PSIZE(rv->prefix.prefixlen); if (len < consume) { sbuf_push(log, indent, "Expected %u bytes of prefix, but only %u bytes available.\n", PSIZE(rv->prefix.prefixlen), len - 5); goto out; } stream_get(&rv->prefix.prefix.s_addr, s, PSIZE(rv->prefix.prefixlen)); in_addr_t orig_prefix = rv->prefix.prefix.s_addr; apply_mask_ipv4(&rv->prefix); if (orig_prefix != rv->prefix.prefix.s_addr) sbuf_push(log, indent + 2, "WARNING: Prefix had hostbits set.\n"); format_item_extended_ip_reach(mtid, (struct isis_item *)rv, log, indent + 2); if (control & ISIS_EXTENDED_IP_REACH_SUBTLV) { consume += 1; if (len < consume) { sbuf_push(log, indent, "Expected 1 byte of subtlv len, but no more data present.\n"); goto out; } subtlv_len = stream_getc(s); if (!subtlv_len) { sbuf_push(log, indent + 2, " WARNING: subtlv bit is set, but there are no subtlvs.\n"); } consume += subtlv_len; if (len < consume) { sbuf_push(log, indent, "Expected %" PRIu8 " bytes of subtlvs, but only %u bytes available.\n", subtlv_len, len - 6 - PSIZE(rv->prefix.prefixlen)); goto out; } rv->subtlvs = isis_alloc_subtlvs(ISIS_CONTEXT_SUBTLV_IP_REACH); bool unpacked_known_tlvs = false; if (unpack_tlvs(ISIS_CONTEXT_SUBTLV_IP_REACH, subtlv_len, s, log, rv->subtlvs, indent + 4, &unpacked_known_tlvs)) { goto out; } if (!unpacked_known_tlvs) { isis_free_subtlvs(rv->subtlvs); rv->subtlvs = NULL; } } append_item(items, (struct isis_item *)rv); return 0; out: if (rv) free_item_extended_ip_reach((struct isis_item *)rv); return 1; } /* Functions related to TLV 137 Dynamic Hostname */ static char *copy_tlv_dynamic_hostname(const char *hostname) { if (!hostname) return NULL; return XSTRDUP(MTYPE_ISIS_TLV, hostname); } static void format_tlv_dynamic_hostname(const char *hostname, struct sbuf *buf, int indent) { if (!hostname) return; sbuf_push(buf, indent, "Hostname: %s\n", hostname); } static void free_tlv_dynamic_hostname(char *hostname) { XFREE(MTYPE_ISIS_TLV, hostname); } static int pack_tlv_dynamic_hostname(const char *hostname, struct stream *s) { if (!hostname) return 0; uint8_t name_len = strlen(hostname); if (STREAM_WRITEABLE(s) < (unsigned)(2 + name_len)) return 1; stream_putc(s, ISIS_TLV_DYNAMIC_HOSTNAME); stream_putc(s, name_len); stream_put(s, hostname, name_len); return 0; } static int unpack_tlv_dynamic_hostname(enum isis_tlv_context context, uint8_t tlv_type, uint8_t tlv_len, struct stream *s, struct sbuf *log, void *dest, int indent) { struct isis_tlvs *tlvs = dest; sbuf_push(log, indent, "Unpacking Dynamic Hostname TLV...\n"); if (!tlv_len) { sbuf_push(log, indent, "WARNING: No hostname included\n"); return 0; } if (tlvs->hostname) { sbuf_push(log, indent, "WARNING: Hostname present multiple times.\n"); stream_forward_getp(s, tlv_len); return 0; } tlvs->hostname = XCALLOC(MTYPE_ISIS_TLV, tlv_len + 1); stream_get(tlvs->hostname, s, tlv_len); tlvs->hostname[tlv_len] = '\0'; bool sane = true; for (uint8_t i = 0; i < tlv_len; i++) { if ((unsigned char)tlvs->hostname[i] > 127 || !isprint((unsigned char)tlvs->hostname[i])) { sane = false; tlvs->hostname[i] = '?'; } } if (!sane) { sbuf_push( log, indent, "WARNING: Hostname contained non-printable/non-ascii characters.\n"); } return 0; } /* Functions related to TLV 150 Spine-Leaf-Extension */ static struct isis_spine_leaf *copy_tlv_spine_leaf( const struct isis_spine_leaf *spine_leaf) { if (!spine_leaf) return NULL; struct isis_spine_leaf *rv = XMALLOC(MTYPE_ISIS_TLV, sizeof(*rv)); memcpy(rv, spine_leaf, sizeof(*rv)); return rv; } static void format_tlv_spine_leaf(const struct isis_spine_leaf *spine_leaf, struct sbuf *buf, int indent) { if (!spine_leaf) return; sbuf_push(buf, indent, "Spine-Leaf-Extension:\n"); if (spine_leaf->has_tier) { if (spine_leaf->tier == ISIS_TIER_UNDEFINED) { sbuf_push(buf, indent, " Tier: undefined\n"); } else { sbuf_push(buf, indent, " Tier: %" PRIu8 "\n", spine_leaf->tier); } } sbuf_push(buf, indent, " Flags:%s%s%s\n", spine_leaf->is_leaf ? " LEAF" : "", spine_leaf->is_spine ? " SPINE" : "", spine_leaf->is_backup ? " BACKUP" : ""); } static void free_tlv_spine_leaf(struct isis_spine_leaf *spine_leaf) { XFREE(MTYPE_ISIS_TLV, spine_leaf); } #define ISIS_SPINE_LEAF_FLAG_TIER 0x08 #define ISIS_SPINE_LEAF_FLAG_BACKUP 0x04 #define ISIS_SPINE_LEAF_FLAG_SPINE 0x02 #define ISIS_SPINE_LEAF_FLAG_LEAF 0x01 static int pack_tlv_spine_leaf(const struct isis_spine_leaf *spine_leaf, struct stream *s) { if (!spine_leaf) return 0; uint8_t tlv_len = 2; if (STREAM_WRITEABLE(s) < (unsigned)(2 + tlv_len)) return 1; stream_putc(s, ISIS_TLV_SPINE_LEAF_EXT); stream_putc(s, tlv_len); uint16_t spine_leaf_flags = 0; if (spine_leaf->has_tier) { spine_leaf_flags |= ISIS_SPINE_LEAF_FLAG_TIER; spine_leaf_flags |= spine_leaf->tier << 12; } if (spine_leaf->is_leaf) spine_leaf_flags |= ISIS_SPINE_LEAF_FLAG_LEAF; if (spine_leaf->is_spine) spine_leaf_flags |= ISIS_SPINE_LEAF_FLAG_SPINE; if (spine_leaf->is_backup) spine_leaf_flags |= ISIS_SPINE_LEAF_FLAG_BACKUP; stream_putw(s, spine_leaf_flags); return 0; } static int unpack_tlv_spine_leaf(enum isis_tlv_context context, uint8_t tlv_type, uint8_t tlv_len, struct stream *s, struct sbuf *log, void *dest, int indent) { struct isis_tlvs *tlvs = dest; sbuf_push(log, indent, "Unpacking Spine Leaf Extension TLV...\n"); if (tlv_len < 2) { sbuf_push(log, indent, "WARNING: Unexepected TLV size\n"); stream_forward_getp(s, tlv_len); return 0; } if (tlvs->spine_leaf) { sbuf_push(log, indent, "WARNING: Spine Leaf Extension TLV present multiple times.\n"); stream_forward_getp(s, tlv_len); return 0; } tlvs->spine_leaf = XCALLOC(MTYPE_ISIS_TLV, sizeof(*tlvs->spine_leaf)); uint16_t spine_leaf_flags = stream_getw(s); if (spine_leaf_flags & ISIS_SPINE_LEAF_FLAG_TIER) { tlvs->spine_leaf->has_tier = true; tlvs->spine_leaf->tier = spine_leaf_flags >> 12; } tlvs->spine_leaf->is_leaf = spine_leaf_flags & ISIS_SPINE_LEAF_FLAG_LEAF; tlvs->spine_leaf->is_spine = spine_leaf_flags & ISIS_SPINE_LEAF_FLAG_SPINE; tlvs->spine_leaf->is_backup = spine_leaf_flags & ISIS_SPINE_LEAF_FLAG_BACKUP; stream_forward_getp(s, tlv_len - 2); return 0; } /* Functions related to TLV 240 P2P Three-Way Adjacency */ const char *isis_threeway_state_name(enum isis_threeway_state state) { switch (state) { case ISIS_THREEWAY_DOWN: return "Down"; case ISIS_THREEWAY_INITIALIZING: return "Initializing"; case ISIS_THREEWAY_UP: return "Up"; default: return "Invalid!"; } } static struct isis_threeway_adj *copy_tlv_threeway_adj( const struct isis_threeway_adj *threeway_adj) { if (!threeway_adj) return NULL; struct isis_threeway_adj *rv = XMALLOC(MTYPE_ISIS_TLV, sizeof(*rv)); memcpy(rv, threeway_adj, sizeof(*rv)); return rv; } static void format_tlv_threeway_adj(const struct isis_threeway_adj *threeway_adj, struct sbuf *buf, int indent) { if (!threeway_adj) return; sbuf_push(buf, indent, "P2P Three-Way Adjacency:\n"); sbuf_push(buf, indent, " State: %s (%d)\n", isis_threeway_state_name(threeway_adj->state), threeway_adj->state); sbuf_push(buf, indent, " Extended Local Circuit ID: %" PRIu32 "\n", threeway_adj->local_circuit_id); if (!threeway_adj->neighbor_set) return; sbuf_push(buf, indent, " Neighbor System ID: %s\n", isis_format_id(threeway_adj->neighbor_id, 6)); sbuf_push(buf, indent, " Neighbor Extended Circuit ID: %" PRIu32 "\n", threeway_adj->neighbor_circuit_id); } static void free_tlv_threeway_adj(struct isis_threeway_adj *threeway_adj) { XFREE(MTYPE_ISIS_TLV, threeway_adj); } static int pack_tlv_threeway_adj(const struct isis_threeway_adj *threeway_adj, struct stream *s) { if (!threeway_adj) return 0; uint8_t tlv_len = (threeway_adj->neighbor_set) ? 15 : 5; if (STREAM_WRITEABLE(s) < (unsigned)(2 + tlv_len)) return 1; stream_putc(s, ISIS_TLV_THREE_WAY_ADJ); stream_putc(s, tlv_len); stream_putc(s, threeway_adj->state); stream_putl(s, threeway_adj->local_circuit_id); if (threeway_adj->neighbor_set) { stream_put(s, threeway_adj->neighbor_id, 6); stream_putl(s, threeway_adj->neighbor_circuit_id); } return 0; } static int unpack_tlv_threeway_adj(enum isis_tlv_context context, uint8_t tlv_type, uint8_t tlv_len, struct stream *s, struct sbuf *log, void *dest, int indent) { struct isis_tlvs *tlvs = dest; sbuf_push(log, indent, "Unpacking P2P Three-Way Adjacency TLV...\n"); if (tlv_len != 5 && tlv_len != 15) { sbuf_push(log, indent, "WARNING: Unexepected TLV size\n"); stream_forward_getp(s, tlv_len); return 0; } if (tlvs->threeway_adj) { sbuf_push(log, indent, "WARNING: P2P Three-Way Adjacency TLV present multiple times.\n"); stream_forward_getp(s, tlv_len); return 0; } tlvs->threeway_adj = XCALLOC(MTYPE_ISIS_TLV, sizeof(*tlvs->threeway_adj)); tlvs->threeway_adj->state = stream_getc(s); tlvs->threeway_adj->local_circuit_id = stream_getl(s); if (tlv_len == 15) { tlvs->threeway_adj->neighbor_set = true; stream_get(tlvs->threeway_adj->neighbor_id, s, 6); tlvs->threeway_adj->neighbor_circuit_id = stream_getl(s); } return 0; } /* Functions related to TLVs 236/237 IPv6/MT-IPv6 reach */ static struct isis_item *copy_item_ipv6_reach(struct isis_item *i) { struct isis_ipv6_reach *r = (struct isis_ipv6_reach *)i; struct isis_ipv6_reach *rv = XCALLOC(MTYPE_ISIS_TLV, sizeof(*rv)); rv->metric = r->metric; rv->down = r->down; rv->external = r->external; rv->prefix = r->prefix; rv->subtlvs = copy_subtlvs(r->subtlvs); return (struct isis_item *)rv; } static void format_item_ipv6_reach(uint16_t mtid, struct isis_item *i, struct sbuf *buf, int indent) { struct isis_ipv6_reach *r = (struct isis_ipv6_reach *)i; char prefixbuf[PREFIX2STR_BUFFER]; sbuf_push(buf, indent, "%sIPv6 Reachability: %s (Metric: %u)%s%s", (mtid == ISIS_MT_IPV4_UNICAST) ? "" : "MT ", prefix2str(&r->prefix, prefixbuf, sizeof(prefixbuf)), r->metric, r->down ? " Down" : "", r->external ? " External" : ""); if (mtid != ISIS_MT_IPV4_UNICAST) sbuf_push(buf, 0, " %s", isis_mtid2str(mtid)); sbuf_push(buf, 0, "\n"); if (r->subtlvs) { sbuf_push(buf, indent, " Subtlvs:\n"); format_subtlvs(r->subtlvs, buf, indent + 4); } } static void free_item_ipv6_reach(struct isis_item *i) { struct isis_ipv6_reach *item = (struct isis_ipv6_reach *)i; isis_free_subtlvs(item->subtlvs); XFREE(MTYPE_ISIS_TLV, item); } static int pack_item_ipv6_reach(struct isis_item *i, struct stream *s) { struct isis_ipv6_reach *r = (struct isis_ipv6_reach *)i; uint8_t control; if (STREAM_WRITEABLE(s) < 6) return 1; stream_putl(s, r->metric); control = r->down ? ISIS_IPV6_REACH_DOWN : 0; control |= r->external ? ISIS_IPV6_REACH_EXTERNAL : 0; control |= r->subtlvs ? ISIS_IPV6_REACH_SUBTLV : 0; stream_putc(s, control); stream_putc(s, r->prefix.prefixlen); if (STREAM_WRITEABLE(s) < (unsigned)PSIZE(r->prefix.prefixlen)) return 1; stream_put(s, &r->prefix.prefix.s6_addr, PSIZE(r->prefix.prefixlen)); if (r->subtlvs) return pack_subtlvs(r->subtlvs, s); return 0; } static int unpack_item_ipv6_reach(uint16_t mtid, uint8_t len, struct stream *s, struct sbuf *log, void *dest, int indent) { struct isis_tlvs *tlvs = dest; struct isis_ipv6_reach *rv = NULL; size_t consume; uint8_t control, subtlv_len; struct isis_item_list *items; if (mtid == ISIS_MT_IPV4_UNICAST) { items = &tlvs->ipv6_reach; } else { items = isis_get_mt_items(&tlvs->mt_ipv6_reach, mtid); } sbuf_push(log, indent, "Unpacking %sIPv6 reachability...\n", (mtid == ISIS_MT_IPV4_UNICAST) ? "" : "mt "); consume = 6; if (len < consume) { sbuf_push(log, indent, "Not enough data left. (expected 6 or more bytes, got %" PRIu8 ")\n", len); goto out; } rv = XCALLOC(MTYPE_ISIS_TLV, sizeof(*rv)); rv->metric = stream_getl(s); control = stream_getc(s); rv->down = (control & ISIS_IPV6_REACH_DOWN); rv->external = (control & ISIS_IPV6_REACH_EXTERNAL); rv->prefix.family = AF_INET6; rv->prefix.prefixlen = stream_getc(s); if (rv->prefix.prefixlen > 128) { sbuf_push(log, indent, "Prefixlen %u is inplausible for IPv6\n", rv->prefix.prefixlen); goto out; } consume += PSIZE(rv->prefix.prefixlen); if (len < consume) { sbuf_push(log, indent, "Expected %u bytes of prefix, but only %u bytes available.\n", PSIZE(rv->prefix.prefixlen), len - 6); goto out; } stream_get(&rv->prefix.prefix.s6_addr, s, PSIZE(rv->prefix.prefixlen)); struct in6_addr orig_prefix = rv->prefix.prefix; apply_mask_ipv6(&rv->prefix); if (memcmp(&orig_prefix, &rv->prefix.prefix, sizeof(orig_prefix))) sbuf_push(log, indent + 2, "WARNING: Prefix had hostbits set.\n"); format_item_ipv6_reach(mtid, (struct isis_item *)rv, log, indent + 2); if (control & ISIS_IPV6_REACH_SUBTLV) { consume += 1; if (len < consume) { sbuf_push(log, indent, "Expected 1 byte of subtlv len, but no more data persent.\n"); goto out; } subtlv_len = stream_getc(s); if (!subtlv_len) { sbuf_push(log, indent + 2, " WARNING: subtlv bit set, but there are no subtlvs.\n"); } consume += subtlv_len; if (len < consume) { sbuf_push(log, indent, "Expected %" PRIu8 " bytes of subtlvs, but only %u bytes available.\n", subtlv_len, len - 6 - PSIZE(rv->prefix.prefixlen)); goto out; } rv->subtlvs = isis_alloc_subtlvs(ISIS_CONTEXT_SUBTLV_IPV6_REACH); bool unpacked_known_tlvs = false; if (unpack_tlvs(ISIS_CONTEXT_SUBTLV_IPV6_REACH, subtlv_len, s, log, rv->subtlvs, indent + 4, &unpacked_known_tlvs)) { goto out; } if (!unpacked_known_tlvs) { isis_free_subtlvs(rv->subtlvs); rv->subtlvs = NULL; } } append_item(items, (struct isis_item *)rv); return 0; out: if (rv) free_item_ipv6_reach((struct isis_item *)rv); return 1; } /* Functions related to TLV 10 Authentication */ static struct isis_item *copy_item_auth(struct isis_item *i) { struct isis_auth *auth = (struct isis_auth *)i; struct isis_auth *rv = XCALLOC(MTYPE_ISIS_TLV, sizeof(*rv)); rv->type = auth->type; rv->length = auth->length; memcpy(rv->value, auth->value, sizeof(rv->value)); return (struct isis_item *)rv; } static void format_item_auth(uint16_t mtid, struct isis_item *i, struct sbuf *buf, int indent) { struct isis_auth *auth = (struct isis_auth *)i; char obuf[768]; sbuf_push(buf, indent, "Authentication:\n"); switch (auth->type) { case ISIS_PASSWD_TYPE_CLEARTXT: zlog_sanitize(obuf, sizeof(obuf), auth->value, auth->length); sbuf_push(buf, indent, " Password: %s\n", obuf); break; case ISIS_PASSWD_TYPE_HMAC_MD5: for (unsigned int j = 0; j < 16; j++) { snprintf(obuf + 2 * j, sizeof(obuf) - 2 * j, "%02" PRIx8, auth->value[j]); } sbuf_push(buf, indent, " HMAC-MD5: %s\n", obuf); break; default: sbuf_push(buf, indent, " Unknown (%" PRIu8 ")\n", auth->type); break; } } static void free_item_auth(struct isis_item *i) { XFREE(MTYPE_ISIS_TLV, i); } static int pack_item_auth(struct isis_item *i, struct stream *s) { struct isis_auth *auth = (struct isis_auth *)i; if (STREAM_WRITEABLE(s) < 1) return 1; stream_putc(s, auth->type); switch (auth->type) { case ISIS_PASSWD_TYPE_CLEARTXT: if (STREAM_WRITEABLE(s) < auth->length) return 1; stream_put(s, auth->passwd, auth->length); break; case ISIS_PASSWD_TYPE_HMAC_MD5: if (STREAM_WRITEABLE(s) < 16) return 1; auth->offset = stream_get_endp(s); stream_put(s, NULL, 16); break; default: return 1; } return 0; } static int unpack_item_auth(uint16_t mtid, uint8_t len, struct stream *s, struct sbuf *log, void *dest, int indent) { struct isis_tlvs *tlvs = dest; sbuf_push(log, indent, "Unpack Auth TLV...\n"); if (len < 1) { sbuf_push( log, indent, "Not enough data left.(Expected 1 bytes of auth type, got %" PRIu8 ")\n", len); return 1; } struct isis_auth *rv = XCALLOC(MTYPE_ISIS_TLV, sizeof(*rv)); rv->type = stream_getc(s); rv->length = len - 1; if (rv->type == ISIS_PASSWD_TYPE_HMAC_MD5 && rv->length != 16) { sbuf_push( log, indent, "Unexpected auth length for HMAC-MD5 (expected 16, got %" PRIu8 ")\n", rv->length); XFREE(MTYPE_ISIS_TLV, rv); return 1; } rv->offset = stream_get_getp(s); stream_get(rv->value, s, rv->length); format_item_auth(mtid, (struct isis_item *)rv, log, indent + 2); append_item(&tlvs->isis_auth, (struct isis_item *)rv); return 0; } /* Functions related to TLV 13 Purge Originator */ static struct isis_purge_originator *copy_tlv_purge_originator( struct isis_purge_originator *poi) { if (!poi) return NULL; struct isis_purge_originator *rv; rv = XCALLOC(MTYPE_ISIS_TLV, sizeof(*rv)); rv->sender_set = poi->sender_set; memcpy(rv->generator, poi->generator, sizeof(rv->generator)); if (poi->sender_set) memcpy(rv->sender, poi->sender, sizeof(rv->sender)); return rv; } static void format_tlv_purge_originator(struct isis_purge_originator *poi, struct sbuf *buf, int indent) { if (!poi) return; sbuf_push(buf, indent, "Purge Originator Identification:\n"); sbuf_push(buf, indent, " Generator: %s\n", isis_format_id(poi->generator, sizeof(poi->generator))); if (poi->sender_set) { sbuf_push(buf, indent, " Received-From: %s\n", isis_format_id(poi->sender, sizeof(poi->sender))); } } static void free_tlv_purge_originator(struct isis_purge_originator *poi) { XFREE(MTYPE_ISIS_TLV, poi); } static int pack_tlv_purge_originator(struct isis_purge_originator *poi, struct stream *s) { if (!poi) return 0; uint8_t data_len = 1 + sizeof(poi->generator); if (poi->sender_set) data_len += sizeof(poi->sender); if (STREAM_WRITEABLE(s) < (unsigned)(2 + data_len)) return 1; stream_putc(s, ISIS_TLV_PURGE_ORIGINATOR); stream_putc(s, data_len); stream_putc(s, poi->sender_set ? 2 : 1); stream_put(s, poi->generator, sizeof(poi->generator)); if (poi->sender_set) stream_put(s, poi->sender, sizeof(poi->sender)); return 0; } static int unpack_tlv_purge_originator(enum isis_tlv_context context, uint8_t tlv_type, uint8_t tlv_len, struct stream *s, struct sbuf *log, void *dest, int indent) { struct isis_tlvs *tlvs = dest; struct isis_purge_originator poi = {}; sbuf_push(log, indent, "Unpacking Purge Originator Identification TLV...\n"); if (tlv_len < 7) { sbuf_push(log, indent, "Not enough data left. (Expected at least 7 bytes, got %" PRIu8 ")\n", tlv_len); return 1; } uint8_t number_of_ids = stream_getc(s); if (number_of_ids == 1) { poi.sender_set = false; } else if (number_of_ids == 2) { poi.sender_set = true; } else { sbuf_push(log, indent, "Got invalid value for number of system IDs: %" PRIu8 ")\n", number_of_ids); return 1; } if (tlv_len != 1 + 6 * number_of_ids) { sbuf_push(log, indent, "Incorrect tlv len for number of IDs.\n"); return 1; } stream_get(poi.generator, s, sizeof(poi.generator)); if (poi.sender_set) stream_get(poi.sender, s, sizeof(poi.sender)); if (tlvs->purge_originator) { sbuf_push(log, indent, "WARNING: Purge originator present multiple times, ignoring.\n"); return 0; } tlvs->purge_originator = copy_tlv_purge_originator(&poi); return 0; } /* Functions relating to item TLVs */ static void init_item_list(struct isis_item_list *items) { items->head = NULL; items->tail = &items->head; items->count = 0; } static struct isis_item *copy_item(enum isis_tlv_context context, enum isis_tlv_type type, struct isis_item *item) { const struct tlv_ops *ops = tlv_table[context][type]; if (ops && ops->copy_item) return ops->copy_item(item); assert(!"Unknown item tlv type!"); return NULL; } static void copy_items(enum isis_tlv_context context, enum isis_tlv_type type, struct isis_item_list *src, struct isis_item_list *dest) { struct isis_item *item; init_item_list(dest); for (item = src->head; item; item = item->next) { append_item(dest, copy_item(context, type, item)); } } static void format_item(uint16_t mtid, enum isis_tlv_context context, enum isis_tlv_type type, struct isis_item *i, struct sbuf *buf, int indent) { const struct tlv_ops *ops = tlv_table[context][type]; if (ops && ops->format_item) { ops->format_item(mtid, i, buf, indent); return; } assert(!"Unknown item tlv type!"); } static void format_items_(uint16_t mtid, enum isis_tlv_context context, enum isis_tlv_type type, struct isis_item_list *items, struct sbuf *buf, int indent) { struct isis_item *i; for (i = items->head; i; i = i->next) format_item(mtid, context, type, i, buf, indent); } static void free_item(enum isis_tlv_context tlv_context, enum isis_tlv_type tlv_type, struct isis_item *item) { const struct tlv_ops *ops = tlv_table[tlv_context][tlv_type]; if (ops && ops->free_item) { ops->free_item(item); return; } assert(!"Unknown item tlv type!"); } static void free_items(enum isis_tlv_context context, enum isis_tlv_type type, struct isis_item_list *items) { struct isis_item *item, *next_item; for (item = items->head; item; item = next_item) { next_item = item->next; free_item(context, type, item); } } static int pack_item(enum isis_tlv_context context, enum isis_tlv_type type, struct isis_item *i, struct stream *s, struct isis_tlvs **fragment_tlvs, struct pack_order_entry *pe, uint16_t mtid) { const struct tlv_ops *ops = tlv_table[context][type]; if (ops && ops->pack_item) { return ops->pack_item(i, s); } assert(!"Unknown item tlv type!"); return 1; } static void add_item_to_fragment(struct isis_item *i, struct pack_order_entry *pe, struct isis_tlvs *fragment_tlvs, uint16_t mtid) { struct isis_item_list *l; if (pe->how_to_pack == ISIS_ITEMS) { l = (struct isis_item_list *)(((char *)fragment_tlvs) + pe->what_to_pack); } else { struct isis_mt_item_list *m; m = (struct isis_mt_item_list *)(((char *)fragment_tlvs) + pe->what_to_pack); l = isis_get_mt_items(m, mtid); } append_item(l, copy_item(pe->context, pe->type, i)); } static int pack_items_(uint16_t mtid, enum isis_tlv_context context, enum isis_tlv_type type, struct isis_item_list *items, struct stream *s, struct isis_tlvs **fragment_tlvs, struct pack_order_entry *pe, struct isis_tlvs *(*new_fragment)(struct list *l), struct list *new_fragment_arg) { size_t len_pos, last_len, len; struct isis_item *item = NULL; int rv; if (!items->head) return 0; top: if (STREAM_WRITEABLE(s) < 2) goto too_long; stream_putc(s, type); len_pos = stream_get_endp(s); stream_putc(s, 0); /* Put 0 as length for now */ if (context == ISIS_CONTEXT_LSP && IS_COMPAT_MT_TLV(type) && mtid != ISIS_MT_IPV4_UNICAST) { if (STREAM_WRITEABLE(s) < 2) goto too_long; stream_putw(s, mtid); } if (context == ISIS_CONTEXT_LSP && type == ISIS_TLV_OLDSTYLE_REACH) { if (STREAM_WRITEABLE(s) < 1) goto too_long; stream_putc(s, 0); /* Virtual flag is set to 0 */ } last_len = len = 0; for (item = item ? item : items->head; item; item = item->next) { rv = pack_item(context, type, item, s, fragment_tlvs, pe, mtid); if (rv) goto too_long; len = stream_get_endp(s) - len_pos - 1; /* Multiple auths don't go into one TLV, so always break */ if (context == ISIS_CONTEXT_LSP && type == ISIS_TLV_AUTH) { item = item->next; break; } /* Multiple prefix-sids don't go into one TLV, so always break */ if (type == ISIS_SUBTLV_PREFIX_SID && (context == ISIS_CONTEXT_SUBTLV_IP_REACH || context == ISIS_CONTEXT_SUBTLV_IPV6_REACH)) { item = item->next; break; } if (len > 255) { if (!last_len) /* strange, not a single item fit */ return 1; /* drop last tlv, otherwise, its too long */ stream_set_endp(s, len_pos + 1 + last_len); len = last_len; break; } if (fragment_tlvs) add_item_to_fragment(item, pe, *fragment_tlvs, mtid); last_len = len; } stream_putc_at(s, len_pos, len); if (item) goto top; return 0; too_long: if (!fragment_tlvs) return 1; stream_reset(s); *fragment_tlvs = new_fragment(new_fragment_arg); goto top; } #define pack_items(...) pack_items_(ISIS_MT_IPV4_UNICAST, __VA_ARGS__) static void append_item(struct isis_item_list *dest, struct isis_item *item) { *dest->tail = item; dest->tail = &(*dest->tail)->next; dest->count++; } static struct isis_item *last_item(struct isis_item_list *list) { return container_of(list->tail, struct isis_item, next); } static int unpack_item(uint16_t mtid, enum isis_tlv_context context, uint8_t tlv_type, uint8_t len, struct stream *s, struct sbuf *log, void *dest, int indent) { const struct tlv_ops *ops = tlv_table[context][tlv_type]; if (ops && ops->unpack_item) return ops->unpack_item(mtid, len, s, log, dest, indent); assert(!"Unknown item tlv type!"); sbuf_push(log, indent, "Unknown item tlv type!\n"); return 1; } static int unpack_tlv_with_items(enum isis_tlv_context context, uint8_t tlv_type, uint8_t tlv_len, struct stream *s, struct sbuf *log, void *dest, int indent) { size_t tlv_start; size_t tlv_pos; int rv; uint16_t mtid; tlv_start = stream_get_getp(s); tlv_pos = 0; if (context == ISIS_CONTEXT_LSP && IS_COMPAT_MT_TLV(tlv_type)) { if (tlv_len < 2) { sbuf_push(log, indent, "TLV is too short to contain MTID\n"); return 1; } mtid = stream_getw(s) & ISIS_MT_MASK; tlv_pos += 2; sbuf_push(log, indent, "Unpacking as MT %s item TLV...\n", isis_mtid2str(mtid)); } else { sbuf_push(log, indent, "Unpacking as item TLV...\n"); mtid = ISIS_MT_IPV4_UNICAST; } if (context == ISIS_CONTEXT_LSP && tlv_type == ISIS_TLV_OLDSTYLE_REACH) { if (tlv_len - tlv_pos < 1) { sbuf_push(log, indent, "TLV is too short for old style reach\n"); return 1; } stream_forward_getp(s, 1); tlv_pos += 1; } if (context == ISIS_CONTEXT_LSP && tlv_type == ISIS_TLV_OLDSTYLE_IP_REACH) { struct isis_tlvs *tlvs = dest; dest = &tlvs->oldstyle_ip_reach; } else if (context == ISIS_CONTEXT_LSP && tlv_type == ISIS_TLV_OLDSTYLE_IP_REACH_EXT) { struct isis_tlvs *tlvs = dest; dest = &tlvs->oldstyle_ip_reach_ext; } if (context == ISIS_CONTEXT_LSP && tlv_type == ISIS_TLV_MT_ROUTER_INFO) { struct isis_tlvs *tlvs = dest; tlvs->mt_router_info_empty = (tlv_pos >= (size_t)tlv_len); } while (tlv_pos < (size_t)tlv_len) { rv = unpack_item(mtid, context, tlv_type, tlv_len - tlv_pos, s, log, dest, indent + 2); if (rv) return rv; tlv_pos = stream_get_getp(s) - tlv_start; } return 0; } /* Functions to manipulate mt_item_lists */ static int isis_mt_item_list_cmp(const struct isis_item_list *a, const struct isis_item_list *b) { if (a->mtid < b->mtid) return -1; if (a->mtid > b->mtid) return 1; return 0; } RB_PROTOTYPE(isis_mt_item_list, isis_item_list, mt_tree, isis_mt_item_list_cmp); RB_GENERATE(isis_mt_item_list, isis_item_list, mt_tree, isis_mt_item_list_cmp); struct isis_item_list *isis_get_mt_items(struct isis_mt_item_list *m, uint16_t mtid) { struct isis_item_list *rv; rv = isis_lookup_mt_items(m, mtid); if (!rv) { rv = XCALLOC(MTYPE_ISIS_MT_ITEM_LIST, sizeof(*rv)); init_item_list(rv); rv->mtid = mtid; RB_INSERT(isis_mt_item_list, m, rv); } return rv; } struct isis_item_list *isis_lookup_mt_items(struct isis_mt_item_list *m, uint16_t mtid) { struct isis_item_list key = {.mtid = mtid}; return RB_FIND(isis_mt_item_list, m, &key); } static void free_mt_items(enum isis_tlv_context context, enum isis_tlv_type type, struct isis_mt_item_list *m) { struct isis_item_list *n, *nnext; RB_FOREACH_SAFE (n, isis_mt_item_list, m, nnext) { free_items(context, type, n); RB_REMOVE(isis_mt_item_list, m, n); XFREE(MTYPE_ISIS_MT_ITEM_LIST, n); } } static void format_mt_items(enum isis_tlv_context context, enum isis_tlv_type type, struct isis_mt_item_list *m, struct sbuf *buf, int indent) { struct isis_item_list *n; RB_FOREACH (n, isis_mt_item_list, m) { format_items_(n->mtid, context, type, n, buf, indent); } } static int pack_mt_items(enum isis_tlv_context context, enum isis_tlv_type type, struct isis_mt_item_list *m, struct stream *s, struct isis_tlvs **fragment_tlvs, struct pack_order_entry *pe, struct isis_tlvs *(*new_fragment)(struct list *l), struct list *new_fragment_arg) { struct isis_item_list *n; RB_FOREACH (n, isis_mt_item_list, m) { int rv; rv = pack_items_(n->mtid, context, type, n, s, fragment_tlvs, pe, new_fragment, new_fragment_arg); if (rv) return rv; } return 0; } static void copy_mt_items(enum isis_tlv_context context, enum isis_tlv_type type, struct isis_mt_item_list *src, struct isis_mt_item_list *dest) { struct isis_item_list *n; RB_INIT(isis_mt_item_list, dest); RB_FOREACH (n, isis_mt_item_list, src) { copy_items(context, type, n, isis_get_mt_items(dest, n->mtid)); } } /* Functions related to tlvs in general */ struct isis_tlvs *isis_alloc_tlvs(void) { struct isis_tlvs *result; result = XCALLOC(MTYPE_ISIS_TLV, sizeof(*result)); init_item_list(&result->isis_auth); init_item_list(&result->area_addresses); init_item_list(&result->mt_router_info); init_item_list(&result->oldstyle_reach); init_item_list(&result->lan_neighbor); init_item_list(&result->lsp_entries); init_item_list(&result->extended_reach); RB_INIT(isis_mt_item_list, &result->mt_reach); init_item_list(&result->oldstyle_ip_reach); init_item_list(&result->oldstyle_ip_reach_ext); init_item_list(&result->ipv4_address); init_item_list(&result->ipv6_address); init_item_list(&result->extended_ip_reach); RB_INIT(isis_mt_item_list, &result->mt_ip_reach); init_item_list(&result->ipv6_reach); RB_INIT(isis_mt_item_list, &result->mt_ipv6_reach); return result; } struct isis_tlvs *isis_copy_tlvs(struct isis_tlvs *tlvs) { struct isis_tlvs *rv = XCALLOC(MTYPE_ISIS_TLV, sizeof(*rv)); copy_items(ISIS_CONTEXT_LSP, ISIS_TLV_AUTH, &tlvs->isis_auth, &rv->isis_auth); rv->purge_originator = copy_tlv_purge_originator(tlvs->purge_originator); copy_items(ISIS_CONTEXT_LSP, ISIS_TLV_AREA_ADDRESSES, &tlvs->area_addresses, &rv->area_addresses); copy_items(ISIS_CONTEXT_LSP, ISIS_TLV_MT_ROUTER_INFO, &tlvs->mt_router_info, &rv->mt_router_info); rv->mt_router_info_empty = tlvs->mt_router_info_empty; copy_items(ISIS_CONTEXT_LSP, ISIS_TLV_OLDSTYLE_REACH, &tlvs->oldstyle_reach, &rv->oldstyle_reach); copy_items(ISIS_CONTEXT_LSP, ISIS_TLV_LAN_NEIGHBORS, &tlvs->lan_neighbor, &rv->lan_neighbor); copy_items(ISIS_CONTEXT_LSP, ISIS_TLV_LSP_ENTRY, &tlvs->lsp_entries, &rv->lsp_entries); copy_items(ISIS_CONTEXT_LSP, ISIS_TLV_EXTENDED_REACH, &tlvs->extended_reach, &rv->extended_reach); copy_mt_items(ISIS_CONTEXT_LSP, ISIS_TLV_MT_REACH, &tlvs->mt_reach, &rv->mt_reach); copy_items(ISIS_CONTEXT_LSP, ISIS_TLV_OLDSTYLE_IP_REACH, &tlvs->oldstyle_ip_reach, &rv->oldstyle_ip_reach); copy_tlv_protocols_supported(&tlvs->protocols_supported, &rv->protocols_supported); copy_items(ISIS_CONTEXT_LSP, ISIS_TLV_OLDSTYLE_IP_REACH_EXT, &tlvs->oldstyle_ip_reach_ext, &rv->oldstyle_ip_reach_ext); copy_items(ISIS_CONTEXT_LSP, ISIS_TLV_IPV4_ADDRESS, &tlvs->ipv4_address, &rv->ipv4_address); copy_items(ISIS_CONTEXT_LSP, ISIS_TLV_IPV6_ADDRESS, &tlvs->ipv6_address, &rv->ipv6_address); rv->te_router_id = copy_tlv_te_router_id(tlvs->te_router_id); copy_items(ISIS_CONTEXT_LSP, ISIS_TLV_EXTENDED_IP_REACH, &tlvs->extended_ip_reach, &rv->extended_ip_reach); copy_mt_items(ISIS_CONTEXT_LSP, ISIS_TLV_MT_IP_REACH, &tlvs->mt_ip_reach, &rv->mt_ip_reach); rv->hostname = copy_tlv_dynamic_hostname(tlvs->hostname); copy_items(ISIS_CONTEXT_LSP, ISIS_TLV_IPV6_REACH, &tlvs->ipv6_reach, &rv->ipv6_reach); copy_mt_items(ISIS_CONTEXT_LSP, ISIS_TLV_MT_IPV6_REACH, &tlvs->mt_ipv6_reach, &rv->mt_ipv6_reach); rv->threeway_adj = copy_tlv_threeway_adj(tlvs->threeway_adj); rv->spine_leaf = copy_tlv_spine_leaf(tlvs->spine_leaf); return rv; } static void format_tlvs(struct isis_tlvs *tlvs, struct sbuf *buf, int indent) { format_tlv_protocols_supported(&tlvs->protocols_supported, buf, indent); format_items(ISIS_CONTEXT_LSP, ISIS_TLV_AUTH, &tlvs->isis_auth, buf, indent); format_tlv_purge_originator(tlvs->purge_originator, buf, indent); format_items(ISIS_CONTEXT_LSP, ISIS_TLV_AREA_ADDRESSES, &tlvs->area_addresses, buf, indent); if (tlvs->mt_router_info_empty) { sbuf_push(buf, indent, "MT Router Info: None\n"); } else { format_items(ISIS_CONTEXT_LSP, ISIS_TLV_MT_ROUTER_INFO, &tlvs->mt_router_info, buf, indent); } format_items(ISIS_CONTEXT_LSP, ISIS_TLV_OLDSTYLE_REACH, &tlvs->oldstyle_reach, buf, indent); format_items(ISIS_CONTEXT_LSP, ISIS_TLV_LAN_NEIGHBORS, &tlvs->lan_neighbor, buf, indent); format_items(ISIS_CONTEXT_LSP, ISIS_TLV_LSP_ENTRY, &tlvs->lsp_entries, buf, indent); format_tlv_dynamic_hostname(tlvs->hostname, buf, indent); format_tlv_te_router_id(tlvs->te_router_id, buf, indent); format_items(ISIS_CONTEXT_LSP, ISIS_TLV_EXTENDED_REACH, &tlvs->extended_reach, buf, indent); format_mt_items(ISIS_CONTEXT_LSP, ISIS_TLV_MT_REACH, &tlvs->mt_reach, buf, indent); format_items(ISIS_CONTEXT_LSP, ISIS_TLV_OLDSTYLE_IP_REACH, &tlvs->oldstyle_ip_reach, buf, indent); format_items(ISIS_CONTEXT_LSP, ISIS_TLV_OLDSTYLE_IP_REACH_EXT, &tlvs->oldstyle_ip_reach_ext, buf, indent); format_items(ISIS_CONTEXT_LSP, ISIS_TLV_IPV4_ADDRESS, &tlvs->ipv4_address, buf, indent); format_items(ISIS_CONTEXT_LSP, ISIS_TLV_IPV6_ADDRESS, &tlvs->ipv6_address, buf, indent); format_items(ISIS_CONTEXT_LSP, ISIS_TLV_EXTENDED_IP_REACH, &tlvs->extended_ip_reach, buf, indent); format_mt_items(ISIS_CONTEXT_LSP, ISIS_TLV_MT_IP_REACH, &tlvs->mt_ip_reach, buf, indent); format_items(ISIS_CONTEXT_LSP, ISIS_TLV_IPV6_REACH, &tlvs->ipv6_reach, buf, indent); format_mt_items(ISIS_CONTEXT_LSP, ISIS_TLV_MT_IPV6_REACH, &tlvs->mt_ipv6_reach, buf, indent); format_tlv_threeway_adj(tlvs->threeway_adj, buf, indent); format_tlv_spine_leaf(tlvs->spine_leaf, buf, indent); } const char *isis_format_tlvs(struct isis_tlvs *tlvs) { static struct sbuf buf; if (!sbuf_buf(&buf)) sbuf_init(&buf, NULL, 0); sbuf_reset(&buf); format_tlvs(tlvs, &buf, 0); return sbuf_buf(&buf); } void isis_free_tlvs(struct isis_tlvs *tlvs) { if (!tlvs) return; free_items(ISIS_CONTEXT_LSP, ISIS_TLV_AUTH, &tlvs->isis_auth); free_tlv_purge_originator(tlvs->purge_originator); free_items(ISIS_CONTEXT_LSP, ISIS_TLV_AREA_ADDRESSES, &tlvs->area_addresses); free_items(ISIS_CONTEXT_LSP, ISIS_TLV_MT_ROUTER_INFO, &tlvs->mt_router_info); free_items(ISIS_CONTEXT_LSP, ISIS_TLV_OLDSTYLE_REACH, &tlvs->oldstyle_reach); free_items(ISIS_CONTEXT_LSP, ISIS_TLV_LAN_NEIGHBORS, &tlvs->lan_neighbor); free_items(ISIS_CONTEXT_LSP, ISIS_TLV_LSP_ENTRY, &tlvs->lsp_entries); free_items(ISIS_CONTEXT_LSP, ISIS_TLV_EXTENDED_REACH, &tlvs->extended_reach); free_mt_items(ISIS_CONTEXT_LSP, ISIS_TLV_MT_REACH, &tlvs->mt_reach); free_items(ISIS_CONTEXT_LSP, ISIS_TLV_OLDSTYLE_IP_REACH, &tlvs->oldstyle_ip_reach); free_tlv_protocols_supported(&tlvs->protocols_supported); free_items(ISIS_CONTEXT_LSP, ISIS_TLV_OLDSTYLE_IP_REACH_EXT, &tlvs->oldstyle_ip_reach_ext); free_items(ISIS_CONTEXT_LSP, ISIS_TLV_IPV4_ADDRESS, &tlvs->ipv4_address); free_items(ISIS_CONTEXT_LSP, ISIS_TLV_IPV6_ADDRESS, &tlvs->ipv6_address); free_tlv_te_router_id(tlvs->te_router_id); free_items(ISIS_CONTEXT_LSP, ISIS_TLV_EXTENDED_IP_REACH, &tlvs->extended_ip_reach); free_mt_items(ISIS_CONTEXT_LSP, ISIS_TLV_MT_IP_REACH, &tlvs->mt_ip_reach); free_tlv_dynamic_hostname(tlvs->hostname); free_items(ISIS_CONTEXT_LSP, ISIS_TLV_IPV6_REACH, &tlvs->ipv6_reach); free_mt_items(ISIS_CONTEXT_LSP, ISIS_TLV_MT_IPV6_REACH, &tlvs->mt_ipv6_reach); free_tlv_threeway_adj(tlvs->threeway_adj); free_tlv_spine_leaf(tlvs->spine_leaf); XFREE(MTYPE_ISIS_TLV, tlvs); } static void add_padding(struct stream *s) { while (STREAM_WRITEABLE(s)) { if (STREAM_WRITEABLE(s) == 1) break; uint32_t padding_len = STREAM_WRITEABLE(s) - 2; if (padding_len > 255) { if (padding_len == 256) padding_len = 254; else padding_len = 255; } stream_putc(s, ISIS_TLV_PADDING); stream_putc(s, padding_len); stream_put(s, NULL, padding_len); } } #define LSP_REM_LIFETIME_OFF 10 #define LSP_CHECKSUM_OFF 24 static void safe_auth_md5(struct stream *s, uint16_t *checksum, uint16_t *rem_lifetime) { memcpy(rem_lifetime, STREAM_DATA(s) + LSP_REM_LIFETIME_OFF, sizeof(*rem_lifetime)); memset(STREAM_DATA(s) + LSP_REM_LIFETIME_OFF, 0, sizeof(*rem_lifetime)); memcpy(checksum, STREAM_DATA(s) + LSP_CHECKSUM_OFF, sizeof(*checksum)); memset(STREAM_DATA(s) + LSP_CHECKSUM_OFF, 0, sizeof(*checksum)); } static void restore_auth_md5(struct stream *s, uint16_t checksum, uint16_t rem_lifetime) { memcpy(STREAM_DATA(s) + LSP_REM_LIFETIME_OFF, &rem_lifetime, sizeof(rem_lifetime)); memcpy(STREAM_DATA(s) + LSP_CHECKSUM_OFF, &checksum, sizeof(checksum)); } static void update_auth_hmac_md5(struct isis_auth *auth, struct stream *s, bool is_lsp) { uint8_t digest[16]; uint16_t checksum, rem_lifetime; if (is_lsp) safe_auth_md5(s, &checksum, &rem_lifetime); memset(STREAM_DATA(s) + auth->offset, 0, 16); #ifdef CRYPTO_OPENSSL uint8_t *result = (uint8_t *)HMAC(EVP_md5(), auth->passwd, auth->plength, STREAM_DATA(s), stream_get_endp(s), NULL, NULL); memcpy(digest, result, 16); #elif CRYPTO_INTERNAL hmac_md5(STREAM_DATA(s), stream_get_endp(s), auth->passwd, auth->plength, digest); #endif memcpy(auth->value, digest, 16); memcpy(STREAM_DATA(s) + auth->offset, digest, 16); if (is_lsp) restore_auth_md5(s, checksum, rem_lifetime); } static void update_auth(struct isis_tlvs *tlvs, struct stream *s, bool is_lsp) { struct isis_auth *auth_head = (struct isis_auth *)tlvs->isis_auth.head; for (struct isis_auth *auth = auth_head; auth; auth = auth->next) { if (auth->type == ISIS_PASSWD_TYPE_HMAC_MD5) update_auth_hmac_md5(auth, s, is_lsp); } } static int handle_pack_entry(struct pack_order_entry *pe, struct isis_tlvs *tlvs, struct stream *stream, struct isis_tlvs **fragment_tlvs, struct isis_tlvs *(*new_fragment)(struct list *l), struct list *new_fragment_arg) { int rv; if (pe->how_to_pack == ISIS_ITEMS) { struct isis_item_list *l; l = (struct isis_item_list *)(((char *)tlvs) + pe->what_to_pack); rv = pack_items(pe->context, pe->type, l, stream, fragment_tlvs, pe, new_fragment, new_fragment_arg); } else { struct isis_mt_item_list *l; l = (struct isis_mt_item_list *)(((char *)tlvs) + pe->what_to_pack); rv = pack_mt_items(pe->context, pe->type, l, stream, fragment_tlvs, pe, new_fragment, new_fragment_arg); } return rv; } static int pack_tlvs(struct isis_tlvs *tlvs, struct stream *stream, struct isis_tlvs *fragment_tlvs, struct isis_tlvs *(*new_fragment)(struct list *l), struct list *new_fragment_arg) { int rv; /* When fragmenting, don't add auth as it's already accounted for in the * size we are given. */ if (!fragment_tlvs) { rv = pack_items(ISIS_CONTEXT_LSP, ISIS_TLV_AUTH, &tlvs->isis_auth, stream, NULL, NULL, NULL, NULL); if (rv) return rv; } rv = pack_tlv_purge_originator(tlvs->purge_originator, stream); if (rv) return rv; if (fragment_tlvs) { fragment_tlvs->purge_originator = copy_tlv_purge_originator(tlvs->purge_originator); } rv = pack_tlv_protocols_supported(&tlvs->protocols_supported, stream); if (rv) return rv; if (fragment_tlvs) { copy_tlv_protocols_supported( &tlvs->protocols_supported, &fragment_tlvs->protocols_supported); } rv = pack_items(ISIS_CONTEXT_LSP, ISIS_TLV_AREA_ADDRESSES, &tlvs->area_addresses, stream, NULL, NULL, NULL, NULL); if (rv) return rv; if (fragment_tlvs) { copy_items(ISIS_CONTEXT_LSP, ISIS_TLV_AREA_ADDRESSES, &tlvs->area_addresses, &fragment_tlvs->area_addresses); } if (tlvs->mt_router_info_empty) { if (STREAM_WRITEABLE(stream) < 2) return 1; stream_putc(stream, ISIS_TLV_MT_ROUTER_INFO); stream_putc(stream, 0); if (fragment_tlvs) fragment_tlvs->mt_router_info_empty = true; } else { rv = pack_items(ISIS_CONTEXT_LSP, ISIS_TLV_MT_ROUTER_INFO, &tlvs->mt_router_info, stream, NULL, NULL, NULL, NULL); if (rv) return rv; if (fragment_tlvs) { copy_items(ISIS_CONTEXT_LSP, ISIS_TLV_MT_ROUTER_INFO, &tlvs->mt_router_info, &fragment_tlvs->mt_router_info); } } rv = pack_tlv_dynamic_hostname(tlvs->hostname, stream); if (rv) return rv; if (fragment_tlvs) fragment_tlvs->hostname = copy_tlv_dynamic_hostname(tlvs->hostname); rv = pack_tlv_te_router_id(tlvs->te_router_id, stream); if (rv) return rv; if (fragment_tlvs) { fragment_tlvs->te_router_id = copy_tlv_te_router_id(tlvs->te_router_id); } rv = pack_tlv_threeway_adj(tlvs->threeway_adj, stream); if (rv) return rv; if (fragment_tlvs) { fragment_tlvs->threeway_adj = copy_tlv_threeway_adj(tlvs->threeway_adj); } rv = pack_tlv_spine_leaf(tlvs->spine_leaf, stream); if (rv) return rv; if (fragment_tlvs) { fragment_tlvs->spine_leaf = copy_tlv_spine_leaf(tlvs->spine_leaf); } for (size_t pack_idx = 0; pack_idx < array_size(pack_order); pack_idx++) { rv = handle_pack_entry(&pack_order[pack_idx], tlvs, stream, fragment_tlvs ? &fragment_tlvs : NULL, new_fragment, new_fragment_arg); if (rv) return rv; } return 0; } int isis_pack_tlvs(struct isis_tlvs *tlvs, struct stream *stream, size_t len_pointer, bool pad, bool is_lsp) { int rv; rv = pack_tlvs(tlvs, stream, NULL, NULL, NULL); if (rv) return rv; if (pad) add_padding(stream); if (len_pointer != (size_t)-1) { stream_putw_at(stream, len_pointer, stream_get_endp(stream)); } update_auth(tlvs, stream, is_lsp); return 0; } static struct isis_tlvs *new_fragment(struct list *l) { struct isis_tlvs *rv = isis_alloc_tlvs(); listnode_add(l, rv); return rv; } struct list *isis_fragment_tlvs(struct isis_tlvs *tlvs, size_t size) { struct stream *dummy_stream = stream_new(size); struct list *rv = list_new(); struct isis_tlvs *fragment_tlvs = new_fragment(rv); if (pack_tlvs(tlvs, dummy_stream, fragment_tlvs, new_fragment, rv)) { struct listnode *node; for (ALL_LIST_ELEMENTS_RO(rv, node, fragment_tlvs)) isis_free_tlvs(fragment_tlvs); list_delete(&rv); } stream_free(dummy_stream); return rv; } static int unpack_tlv_unknown(enum isis_tlv_context context, uint8_t tlv_type, uint8_t tlv_len, struct stream *s, struct sbuf *log, int indent) { stream_forward_getp(s, tlv_len); sbuf_push(log, indent, "Skipping unknown TLV %" PRIu8 " (%" PRIu8 " bytes)\n", tlv_type, tlv_len); return 0; } static int unpack_tlv(enum isis_tlv_context context, size_t avail_len, struct stream *stream, struct sbuf *log, void *dest, int indent, bool *unpacked_known_tlvs) { uint8_t tlv_type, tlv_len; const struct tlv_ops *ops; sbuf_push(log, indent, "Unpacking TLV...\n"); if (avail_len < 2) { sbuf_push( log, indent + 2, "Available data %zu too short to contain a TLV header.\n", avail_len); return 1; } tlv_type = stream_getc(stream); tlv_len = stream_getc(stream); sbuf_push(log, indent + 2, "Found TLV of type %" PRIu8 " and len %" PRIu8 ".\n", tlv_type, tlv_len); if (avail_len < ((size_t)tlv_len) + 2) { sbuf_push(log, indent + 2, "Available data %zu too short for claimed TLV len %" PRIu8 ".\n", avail_len - 2, tlv_len); return 1; } ops = tlv_table[context][tlv_type]; if (ops && ops->unpack) { if (unpacked_known_tlvs) *unpacked_known_tlvs = true; return ops->unpack(context, tlv_type, tlv_len, stream, log, dest, indent + 2); } return unpack_tlv_unknown(context, tlv_type, tlv_len, stream, log, indent + 2); } static int unpack_tlvs(enum isis_tlv_context context, size_t avail_len, struct stream *stream, struct sbuf *log, void *dest, int indent, bool *unpacked_known_tlvs) { int rv; size_t tlv_start, tlv_pos; tlv_start = stream_get_getp(stream); tlv_pos = 0; sbuf_push(log, indent, "Unpacking %zu bytes of %s...\n", avail_len, (context == ISIS_CONTEXT_LSP) ? "TLVs" : "sub-TLVs"); while (tlv_pos < avail_len) { rv = unpack_tlv(context, avail_len - tlv_pos, stream, log, dest, indent + 2, unpacked_known_tlvs); if (rv) return rv; tlv_pos = stream_get_getp(stream) - tlv_start; } return 0; } int isis_unpack_tlvs(size_t avail_len, struct stream *stream, struct isis_tlvs **dest, const char **log) { static struct sbuf logbuf; int indent = 0; int rv; struct isis_tlvs *result; if (!sbuf_buf(&logbuf)) sbuf_init(&logbuf, NULL, 0); sbuf_reset(&logbuf); if (avail_len > STREAM_READABLE(stream)) { sbuf_push(&logbuf, indent, "Stream doesn't contain sufficient data. " "Claimed %zu, available %zu\n", avail_len, STREAM_READABLE(stream)); return 1; } result = isis_alloc_tlvs(); rv = unpack_tlvs(ISIS_CONTEXT_LSP, avail_len, stream, &logbuf, result, indent, NULL); *log = sbuf_buf(&logbuf); *dest = result; return rv; } #define TLV_OPS(_name_, _desc_) \ static const struct tlv_ops tlv_##_name_##_ops = { \ .name = _desc_, .unpack = unpack_tlv_##_name_, \ } #define ITEM_TLV_OPS(_name_, _desc_) \ static const struct tlv_ops tlv_##_name_##_ops = { \ .name = _desc_, \ .unpack = unpack_tlv_with_items, \ \ .pack_item = pack_item_##_name_, \ .free_item = free_item_##_name_, \ .unpack_item = unpack_item_##_name_, \ .format_item = format_item_##_name_, \ .copy_item = copy_item_##_name_} #define SUBTLV_OPS(_name_, _desc_) \ static const struct tlv_ops subtlv_##_name_##_ops = { \ .name = _desc_, .unpack = unpack_subtlv_##_name_, \ } #define ITEM_SUBTLV_OPS(_name_, _desc_) \ ITEM_TLV_OPS(_name_, _desc_) ITEM_TLV_OPS(area_address, "TLV 1 Area Addresses"); ITEM_TLV_OPS(oldstyle_reach, "TLV 2 IS Reachability"); ITEM_TLV_OPS(lan_neighbor, "TLV 6 LAN Neighbors"); ITEM_TLV_OPS(lsp_entry, "TLV 9 LSP Entries"); ITEM_TLV_OPS(auth, "TLV 10 IS-IS Auth"); TLV_OPS(purge_originator, "TLV 13 Purge Originator Identification"); ITEM_TLV_OPS(extended_reach, "TLV 22 Extended Reachability"); ITEM_TLV_OPS(oldstyle_ip_reach, "TLV 128/130 IP Reachability"); TLV_OPS(protocols_supported, "TLV 129 Protocols Supported"); ITEM_TLV_OPS(ipv4_address, "TLV 132 IPv4 Interface Address"); TLV_OPS(te_router_id, "TLV 134 TE Router ID"); ITEM_TLV_OPS(extended_ip_reach, "TLV 135 Extended IP Reachability"); TLV_OPS(dynamic_hostname, "TLV 137 Dynamic Hostname"); TLV_OPS(spine_leaf, "TLV 150 Spine Leaf Extensions"); ITEM_TLV_OPS(mt_router_info, "TLV 229 MT Router Information"); TLV_OPS(threeway_adj, "TLV 240 P2P Three-Way Adjacency"); ITEM_TLV_OPS(ipv6_address, "TLV 232 IPv6 Interface Address"); ITEM_TLV_OPS(ipv6_reach, "TLV 236 IPv6 Reachability"); ITEM_SUBTLV_OPS(prefix_sid, "Sub-TLV 3 SR Prefix-SID"); SUBTLV_OPS(ipv6_source_prefix, "Sub-TLV 22 IPv6 Source Prefix"); static const struct tlv_ops *tlv_table[ISIS_CONTEXT_MAX][ISIS_TLV_MAX] = { [ISIS_CONTEXT_LSP] = { [ISIS_TLV_AREA_ADDRESSES] = &tlv_area_address_ops, [ISIS_TLV_OLDSTYLE_REACH] = &tlv_oldstyle_reach_ops, [ISIS_TLV_LAN_NEIGHBORS] = &tlv_lan_neighbor_ops, [ISIS_TLV_LSP_ENTRY] = &tlv_lsp_entry_ops, [ISIS_TLV_AUTH] = &tlv_auth_ops, [ISIS_TLV_PURGE_ORIGINATOR] = &tlv_purge_originator_ops, [ISIS_TLV_EXTENDED_REACH] = &tlv_extended_reach_ops, [ISIS_TLV_MT_REACH] = &tlv_extended_reach_ops, [ISIS_TLV_OLDSTYLE_IP_REACH] = &tlv_oldstyle_ip_reach_ops, [ISIS_TLV_PROTOCOLS_SUPPORTED] = &tlv_protocols_supported_ops, [ISIS_TLV_OLDSTYLE_IP_REACH_EXT] = &tlv_oldstyle_ip_reach_ops, [ISIS_TLV_IPV4_ADDRESS] = &tlv_ipv4_address_ops, [ISIS_TLV_TE_ROUTER_ID] = &tlv_te_router_id_ops, [ISIS_TLV_EXTENDED_IP_REACH] = &tlv_extended_ip_reach_ops, [ISIS_TLV_MT_IP_REACH] = &tlv_extended_ip_reach_ops, [ISIS_TLV_DYNAMIC_HOSTNAME] = &tlv_dynamic_hostname_ops, [ISIS_TLV_SPINE_LEAF_EXT] = &tlv_spine_leaf_ops, [ISIS_TLV_MT_ROUTER_INFO] = &tlv_mt_router_info_ops, [ISIS_TLV_THREE_WAY_ADJ] = &tlv_threeway_adj_ops, [ISIS_TLV_IPV6_ADDRESS] = &tlv_ipv6_address_ops, [ISIS_TLV_IPV6_REACH] = &tlv_ipv6_reach_ops, [ISIS_TLV_MT_IPV6_REACH] = &tlv_ipv6_reach_ops, }, [ISIS_CONTEXT_SUBTLV_NE_REACH] = {}, [ISIS_CONTEXT_SUBTLV_IP_REACH] = { [ISIS_SUBTLV_PREFIX_SID] = &tlv_prefix_sid_ops, }, [ISIS_CONTEXT_SUBTLV_IPV6_REACH] = { [ISIS_SUBTLV_PREFIX_SID] = &tlv_prefix_sid_ops, [ISIS_SUBTLV_IPV6_SOURCE_PREFIX] = &subtlv_ipv6_source_prefix_ops, } }; /* Accessor functions */ void isis_tlvs_add_auth(struct isis_tlvs *tlvs, struct isis_passwd *passwd) { free_items(ISIS_CONTEXT_LSP, ISIS_TLV_AUTH, &tlvs->isis_auth); init_item_list(&tlvs->isis_auth); if (passwd->type == ISIS_PASSWD_TYPE_UNUSED) return; struct isis_auth *auth = XCALLOC(MTYPE_ISIS_TLV, sizeof(*auth)); auth->type = passwd->type; auth->plength = passwd->len; memcpy(auth->passwd, passwd->passwd, MIN(sizeof(auth->passwd), sizeof(passwd->passwd))); if (auth->type == ISIS_PASSWD_TYPE_CLEARTXT) { auth->length = passwd->len; memcpy(auth->value, passwd->passwd, MIN(sizeof(auth->value), sizeof(passwd->passwd))); } append_item(&tlvs->isis_auth, (struct isis_item *)auth); } void isis_tlvs_add_area_addresses(struct isis_tlvs *tlvs, struct list *addresses) { struct listnode *node; struct area_addr *area_addr; for (ALL_LIST_ELEMENTS_RO(addresses, node, area_addr)) { struct isis_area_address *a = XCALLOC(MTYPE_ISIS_TLV, sizeof(*a)); a->len = area_addr->addr_len; memcpy(a->addr, area_addr->area_addr, 20); append_item(&tlvs->area_addresses, (struct isis_item *)a); } } void isis_tlvs_add_lan_neighbors(struct isis_tlvs *tlvs, struct list *neighbors) { struct listnode *node; uint8_t *snpa; for (ALL_LIST_ELEMENTS_RO(neighbors, node, snpa)) { struct isis_lan_neighbor *n = XCALLOC(MTYPE_ISIS_TLV, sizeof(*n)); memcpy(n->mac, snpa, 6); append_item(&tlvs->lan_neighbor, (struct isis_item *)n); } } void isis_tlvs_set_protocols_supported(struct isis_tlvs *tlvs, struct nlpids *nlpids) { tlvs->protocols_supported.count = nlpids->count; XFREE(MTYPE_ISIS_TLV, tlvs->protocols_supported.protocols); if (nlpids->count) { tlvs->protocols_supported.protocols = XCALLOC(MTYPE_ISIS_TLV, nlpids->count); memcpy(tlvs->protocols_supported.protocols, nlpids->nlpids, nlpids->count); } else { tlvs->protocols_supported.protocols = NULL; } } void isis_tlvs_add_mt_router_info(struct isis_tlvs *tlvs, uint16_t mtid, bool overload, bool attached) { struct isis_mt_router_info *i = XCALLOC(MTYPE_ISIS_TLV, sizeof(*i)); i->overload = overload; i->attached = attached; i->mtid = mtid; append_item(&tlvs->mt_router_info, (struct isis_item *)i); } void isis_tlvs_add_ipv4_address(struct isis_tlvs *tlvs, struct in_addr *addr) { struct isis_ipv4_address *a = XCALLOC(MTYPE_ISIS_TLV, sizeof(*a)); a->addr = *addr; append_item(&tlvs->ipv4_address, (struct isis_item *)a); } void isis_tlvs_add_ipv4_addresses(struct isis_tlvs *tlvs, struct list *addresses) { struct listnode *node; struct prefix_ipv4 *ip_addr; unsigned int addr_count = 0; for (ALL_LIST_ELEMENTS_RO(addresses, node, ip_addr)) { isis_tlvs_add_ipv4_address(tlvs, &ip_addr->prefix); addr_count++; if (addr_count >= 63) break; } } void isis_tlvs_add_ipv6_addresses(struct isis_tlvs *tlvs, struct list *addresses) { struct listnode *node; struct prefix_ipv6 *ip_addr; unsigned int addr_count = 0; for (ALL_LIST_ELEMENTS_RO(addresses, node, ip_addr)) { if (addr_count >= 15) break; struct isis_ipv6_address *a = XCALLOC(MTYPE_ISIS_TLV, sizeof(*a)); a->addr = ip_addr->prefix; append_item(&tlvs->ipv6_address, (struct isis_item *)a); addr_count++; } } typedef bool (*auth_validator_func)(struct isis_passwd *passwd, struct stream *stream, struct isis_auth *auth, bool is_lsp); static bool auth_validator_cleartxt(struct isis_passwd *passwd, struct stream *stream, struct isis_auth *auth, bool is_lsp) { return (auth->length == passwd->len && !memcmp(auth->value, passwd->passwd, passwd->len)); } static bool auth_validator_hmac_md5(struct isis_passwd *passwd, struct stream *stream, struct isis_auth *auth, bool is_lsp) { uint8_t digest[16]; uint16_t checksum; uint16_t rem_lifetime; if (is_lsp) safe_auth_md5(stream, &checksum, &rem_lifetime); memset(STREAM_DATA(stream) + auth->offset, 0, 16); #ifdef CRYPTO_OPENSSL uint8_t *result = (uint8_t *)HMAC(EVP_md5(), passwd->passwd, passwd->len, STREAM_DATA(stream), stream_get_endp(stream), NULL, NULL); memcpy(digest, result, 16); #elif CRYPTO_INTERNAL hmac_md5(STREAM_DATA(stream), stream_get_endp(stream), passwd->passwd, passwd->len, digest); #endif memcpy(STREAM_DATA(stream) + auth->offset, auth->value, 16); bool rv = !memcmp(digest, auth->value, 16); if (is_lsp) restore_auth_md5(stream, checksum, rem_lifetime); return rv; } static const auth_validator_func auth_validators[] = { [ISIS_PASSWD_TYPE_CLEARTXT] = auth_validator_cleartxt, [ISIS_PASSWD_TYPE_HMAC_MD5] = auth_validator_hmac_md5, }; int isis_tlvs_auth_is_valid(struct isis_tlvs *tlvs, struct isis_passwd *passwd, struct stream *stream, bool is_lsp) { /* If no auth is set, always pass authentication */ if (!passwd->type) return ISIS_AUTH_OK; /* If we don't known how to validate the auth, return invalid */ if (passwd->type >= array_size(auth_validators) || !auth_validators[passwd->type]) return ISIS_AUTH_NO_VALIDATOR; struct isis_auth *auth_head = (struct isis_auth *)tlvs->isis_auth.head; struct isis_auth *auth; for (auth = auth_head; auth; auth = auth->next) { if (auth->type == passwd->type) break; } /* If matching auth TLV could not be found, return invalid */ if (!auth) return ISIS_AUTH_TYPE_FAILURE; /* Perform validation and return result */ if (auth_validators[passwd->type](passwd, stream, auth, is_lsp)) return ISIS_AUTH_OK; else return ISIS_AUTH_FAILURE; } bool isis_tlvs_area_addresses_match(struct isis_tlvs *tlvs, struct list *addresses) { struct isis_area_address *addr_head; addr_head = (struct isis_area_address *)tlvs->area_addresses.head; for (struct isis_area_address *addr = addr_head; addr; addr = addr->next) { struct listnode *node; struct area_addr *a; for (ALL_LIST_ELEMENTS_RO(addresses, node, a)) { if (a->addr_len == addr->len && !memcmp(a->area_addr, addr->addr, addr->len)) return true; } } return false; } static void tlvs_area_addresses_to_adj(struct isis_tlvs *tlvs, struct isis_adjacency *adj, bool *changed) { if (adj->area_address_count != tlvs->area_addresses.count) { *changed = true; adj->area_address_count = tlvs->area_addresses.count; adj->area_addresses = XREALLOC( MTYPE_ISIS_ADJACENCY_INFO, adj->area_addresses, adj->area_address_count * sizeof(*adj->area_addresses)); } struct isis_area_address *addr = NULL; for (unsigned int i = 0; i < tlvs->area_addresses.count; i++) { if (!addr) addr = (struct isis_area_address *) tlvs->area_addresses.head; else addr = addr->next; if (adj->area_addresses[i].addr_len == addr->len && !memcmp(adj->area_addresses[i].area_addr, addr->addr, addr->len)) { continue; } *changed = true; adj->area_addresses[i].addr_len = addr->len; memcpy(adj->area_addresses[i].area_addr, addr->addr, addr->len); } } static void tlvs_protocols_supported_to_adj(struct isis_tlvs *tlvs, struct isis_adjacency *adj, bool *changed) { bool ipv4_supported = false, ipv6_supported = false; for (uint8_t i = 0; i < tlvs->protocols_supported.count; i++) { if (tlvs->protocols_supported.protocols[i] == NLPID_IP) ipv4_supported = true; if (tlvs->protocols_supported.protocols[i] == NLPID_IPV6) ipv6_supported = true; } struct nlpids reduced = {}; if (ipv4_supported && ipv6_supported) { reduced.count = 2; reduced.nlpids[0] = NLPID_IP; reduced.nlpids[1] = NLPID_IPV6; } else if (ipv4_supported) { reduced.count = 1; reduced.nlpids[0] = NLPID_IP; } else if (ipv6_supported) { reduced.count = 1; reduced.nlpids[0] = NLPID_IPV6; } else { reduced.count = 0; } if (adj->nlpids.count == reduced.count && !memcmp(adj->nlpids.nlpids, reduced.nlpids, reduced.count)) return; *changed = true; adj->nlpids.count = reduced.count; memcpy(adj->nlpids.nlpids, reduced.nlpids, reduced.count); } static void tlvs_ipv4_addresses_to_adj(struct isis_tlvs *tlvs, struct isis_adjacency *adj, bool *changed) { if (adj->ipv4_address_count != tlvs->ipv4_address.count) { *changed = true; adj->ipv4_address_count = tlvs->ipv4_address.count; adj->ipv4_addresses = XREALLOC( MTYPE_ISIS_ADJACENCY_INFO, adj->ipv4_addresses, adj->ipv4_address_count * sizeof(*adj->ipv4_addresses)); } struct isis_ipv4_address *addr = NULL; for (unsigned int i = 0; i < tlvs->ipv4_address.count; i++) { if (!addr) addr = (struct isis_ipv4_address *) tlvs->ipv4_address.head; else addr = addr->next; if (!memcmp(&adj->ipv4_addresses[i], &addr->addr, sizeof(addr->addr))) continue; *changed = true; adj->ipv4_addresses[i] = addr->addr; } } static void tlvs_ipv6_addresses_to_adj(struct isis_tlvs *tlvs, struct isis_adjacency *adj, bool *changed) { if (adj->ipv6_address_count != tlvs->ipv6_address.count) { *changed = true; adj->ipv6_address_count = tlvs->ipv6_address.count; adj->ipv6_addresses = XREALLOC( MTYPE_ISIS_ADJACENCY_INFO, adj->ipv6_addresses, adj->ipv6_address_count * sizeof(*adj->ipv6_addresses)); } struct isis_ipv6_address *addr = NULL; for (unsigned int i = 0; i < tlvs->ipv6_address.count; i++) { if (!addr) addr = (struct isis_ipv6_address *) tlvs->ipv6_address.head; else addr = addr->next; if (!memcmp(&adj->ipv6_addresses[i], &addr->addr, sizeof(addr->addr))) continue; *changed = true; adj->ipv6_addresses[i] = addr->addr; } } void isis_tlvs_to_adj(struct isis_tlvs *tlvs, struct isis_adjacency *adj, bool *changed) { *changed = false; tlvs_area_addresses_to_adj(tlvs, adj, changed); tlvs_protocols_supported_to_adj(tlvs, adj, changed); tlvs_ipv4_addresses_to_adj(tlvs, adj, changed); tlvs_ipv6_addresses_to_adj(tlvs, adj, changed); } bool isis_tlvs_own_snpa_found(struct isis_tlvs *tlvs, uint8_t *snpa) { struct isis_lan_neighbor *ne_head; ne_head = (struct isis_lan_neighbor *)tlvs->lan_neighbor.head; for (struct isis_lan_neighbor *ne = ne_head; ne; ne = ne->next) { if (!memcmp(ne->mac, snpa, ETH_ALEN)) return true; } return false; } void isis_tlvs_add_lsp_entry(struct isis_tlvs *tlvs, struct isis_lsp *lsp) { struct isis_lsp_entry *entry = XCALLOC(MTYPE_ISIS_TLV, sizeof(*entry)); entry->rem_lifetime = lsp->hdr.rem_lifetime; memcpy(entry->id, lsp->hdr.lsp_id, ISIS_SYS_ID_LEN + 2); entry->checksum = lsp->hdr.checksum; entry->seqno = lsp->hdr.seqno; entry->lsp = lsp; append_item(&tlvs->lsp_entries, (struct isis_item *)entry); } void isis_tlvs_add_csnp_entries(struct isis_tlvs *tlvs, uint8_t *start_id, uint8_t *stop_id, uint16_t num_lsps, struct lspdb_head *head, struct isis_lsp **last_lsp) { struct isis_lsp searchfor; struct isis_lsp *first, *lsp; memcpy(&searchfor.hdr.lsp_id, start_id, sizeof(searchfor.hdr.lsp_id)); first = lspdb_find_gteq(head, &searchfor); if (!first) return; frr_each_from (lspdb, head, lsp, first) { if (memcmp(lsp->hdr.lsp_id, stop_id, sizeof(lsp->hdr.lsp_id)) > 0 || tlvs->lsp_entries.count == num_lsps) break; isis_tlvs_add_lsp_entry(tlvs, lsp); *last_lsp = lsp; } } void isis_tlvs_set_dynamic_hostname(struct isis_tlvs *tlvs, const char *hostname) { XFREE(MTYPE_ISIS_TLV, tlvs->hostname); if (hostname) tlvs->hostname = XSTRDUP(MTYPE_ISIS_TLV, hostname); } void isis_tlvs_set_te_router_id(struct isis_tlvs *tlvs, const struct in_addr *id) { XFREE(MTYPE_ISIS_TLV, tlvs->te_router_id); if (!id) return; tlvs->te_router_id = XCALLOC(MTYPE_ISIS_TLV, sizeof(*id)); memcpy(tlvs->te_router_id, id, sizeof(*id)); } void isis_tlvs_add_oldstyle_ip_reach(struct isis_tlvs *tlvs, struct prefix_ipv4 *dest, uint8_t metric) { struct isis_oldstyle_ip_reach *r = XCALLOC(MTYPE_ISIS_TLV, sizeof(*r)); r->metric = metric; memcpy(&r->prefix, dest, sizeof(*dest)); apply_mask_ipv4(&r->prefix); append_item(&tlvs->oldstyle_ip_reach, (struct isis_item *)r); } void isis_tlvs_add_extended_ip_reach(struct isis_tlvs *tlvs, struct prefix_ipv4 *dest, uint32_t metric) { struct isis_extended_ip_reach *r = XCALLOC(MTYPE_ISIS_TLV, sizeof(*r)); r->metric = metric; memcpy(&r->prefix, dest, sizeof(*dest)); apply_mask_ipv4(&r->prefix); append_item(&tlvs->extended_ip_reach, (struct isis_item *)r); } void isis_tlvs_add_ipv6_reach(struct isis_tlvs *tlvs, uint16_t mtid, struct prefix_ipv6 *dest, uint32_t metric) { struct isis_ipv6_reach *r = XCALLOC(MTYPE_ISIS_TLV, sizeof(*r)); r->metric = metric; memcpy(&r->prefix, dest, sizeof(*dest)); apply_mask_ipv6(&r->prefix); struct isis_item_list *l; l = (mtid == ISIS_MT_IPV4_UNICAST) ? &tlvs->ipv6_reach : isis_get_mt_items(&tlvs->mt_ipv6_reach, mtid); append_item(l, (struct isis_item *)r); } void isis_tlvs_add_ipv6_dstsrc_reach(struct isis_tlvs *tlvs, uint16_t mtid, struct prefix_ipv6 *dest, struct prefix_ipv6 *src, uint32_t metric) { isis_tlvs_add_ipv6_reach(tlvs, mtid, dest, metric); struct isis_item_list *l = isis_get_mt_items(&tlvs->mt_ipv6_reach, mtid); struct isis_ipv6_reach *r = (struct isis_ipv6_reach*)last_item(l); r->subtlvs = isis_alloc_subtlvs(ISIS_CONTEXT_SUBTLV_IPV6_REACH); r->subtlvs->source_prefix = XCALLOC(MTYPE_ISIS_SUBTLV, sizeof(*src)); memcpy(r->subtlvs->source_prefix, src, sizeof(*src)); } void isis_tlvs_add_oldstyle_reach(struct isis_tlvs *tlvs, uint8_t *id, uint8_t metric) { struct isis_oldstyle_reach *r = XCALLOC(MTYPE_ISIS_TLV, sizeof(*r)); r->metric = metric; memcpy(r->id, id, sizeof(r->id)); append_item(&tlvs->oldstyle_reach, (struct isis_item *)r); } void isis_tlvs_add_extended_reach(struct isis_tlvs *tlvs, uint16_t mtid, uint8_t *id, uint32_t metric, uint8_t *subtlvs, uint8_t subtlv_len) { struct isis_extended_reach *r = XCALLOC(MTYPE_ISIS_TLV, sizeof(*r)); memcpy(r->id, id, sizeof(r->id)); r->metric = metric; if (subtlvs && subtlv_len) { r->subtlvs = XCALLOC(MTYPE_ISIS_TLV, subtlv_len); memcpy(r->subtlvs, subtlvs, subtlv_len); r->subtlv_len = subtlv_len; } struct isis_item_list *l; if (mtid == ISIS_MT_IPV4_UNICAST) l = &tlvs->extended_reach; else l = isis_get_mt_items(&tlvs->mt_reach, mtid); append_item(l, (struct isis_item *)r); } void isis_tlvs_add_threeway_adj(struct isis_tlvs *tlvs, enum isis_threeway_state state, uint32_t local_circuit_id, const uint8_t *neighbor_id, uint32_t neighbor_circuit_id) { assert(!tlvs->threeway_adj); tlvs->threeway_adj = XCALLOC(MTYPE_ISIS_TLV, sizeof(*tlvs->threeway_adj)); tlvs->threeway_adj->state = state; tlvs->threeway_adj->local_circuit_id = local_circuit_id; if (neighbor_id) { tlvs->threeway_adj->neighbor_set = true; memcpy(tlvs->threeway_adj->neighbor_id, neighbor_id, 6); tlvs->threeway_adj->neighbor_circuit_id = neighbor_circuit_id; } } void isis_tlvs_add_spine_leaf(struct isis_tlvs *tlvs, uint8_t tier, bool has_tier, bool is_leaf, bool is_spine, bool is_backup) { assert(!tlvs->spine_leaf); tlvs->spine_leaf = XCALLOC(MTYPE_ISIS_TLV, sizeof(*tlvs->spine_leaf)); if (has_tier) { tlvs->spine_leaf->tier = tier; } tlvs->spine_leaf->has_tier = has_tier; tlvs->spine_leaf->is_leaf = is_leaf; tlvs->spine_leaf->is_spine = is_spine; tlvs->spine_leaf->is_backup = is_backup; } struct isis_mt_router_info * isis_tlvs_lookup_mt_router_info(struct isis_tlvs *tlvs, uint16_t mtid) { if (tlvs->mt_router_info_empty) return NULL; struct isis_mt_router_info *rv; for (rv = (struct isis_mt_router_info *)tlvs->mt_router_info.head; rv; rv = rv->next) { if (rv->mtid == mtid) return rv; } return NULL; } void isis_tlvs_set_purge_originator(struct isis_tlvs *tlvs, const uint8_t *generator, const uint8_t *sender) { assert(!tlvs->purge_originator); tlvs->purge_originator = XCALLOC(MTYPE_ISIS_TLV, sizeof(*tlvs->purge_originator)); memcpy(tlvs->purge_originator->generator, generator, sizeof(tlvs->purge_originator->generator)); if (sender) { tlvs->purge_originator->sender_set = true; memcpy(tlvs->purge_originator->sender, sender, sizeof(tlvs->purge_originator->sender)); } } frr-7.2.1/isisd/isis_tlvs.h0000644000000000000000000002441413610377563012524 00000000000000/* * IS-IS TLV Serializer/Deserializer * * Copyright (C) 2015,2017 Christian Franke * * This file is part of FRR. * * FRR is free software; you can redistribute it and/or modify it * under the terms of the GNU General Public License as published by the * Free Software Foundation; either version 2, or (at your option) any * later version. * * FRR 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 FRR; see the file COPYING. If not, write to the Free * Software Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA * 02111-1307, USA. */ #ifndef ISIS_TLVS_H #define ISIS_TLVS_H #include "openbsd-tree.h" #include "prefix.h" struct lspdb_head; struct isis_subtlvs; struct isis_area_address; struct isis_area_address { struct isis_area_address *next; uint8_t addr[20]; uint8_t len; }; struct isis_oldstyle_reach; struct isis_oldstyle_reach { struct isis_oldstyle_reach *next; uint8_t id[7]; uint8_t metric; }; struct isis_oldstyle_ip_reach; struct isis_oldstyle_ip_reach { struct isis_oldstyle_ip_reach *next; uint8_t metric; struct prefix_ipv4 prefix; }; struct isis_lsp_entry; struct isis_lsp_entry { struct isis_lsp_entry *next; uint16_t rem_lifetime; uint8_t id[8]; uint16_t checksum; uint32_t seqno; struct isis_lsp *lsp; }; struct isis_extended_reach; struct isis_extended_reach { struct isis_extended_reach *next; uint8_t id[7]; uint32_t metric; uint8_t *subtlvs; uint8_t subtlv_len; }; struct isis_extended_ip_reach; struct isis_extended_ip_reach { struct isis_extended_ip_reach *next; uint32_t metric; bool down; struct prefix_ipv4 prefix; struct isis_subtlvs *subtlvs; }; struct isis_ipv6_reach; struct isis_ipv6_reach { struct isis_ipv6_reach *next; uint32_t metric; bool down; bool external; struct prefix_ipv6 prefix; struct isis_subtlvs *subtlvs; }; struct isis_protocols_supported { uint8_t count; uint8_t *protocols; }; #define ISIS_TIER_UNDEFINED 15 struct isis_spine_leaf { uint8_t tier; bool has_tier; bool is_leaf; bool is_spine; bool is_backup; }; enum isis_threeway_state { ISIS_THREEWAY_DOWN = 2, ISIS_THREEWAY_INITIALIZING = 1, ISIS_THREEWAY_UP = 0 }; struct isis_threeway_adj { enum isis_threeway_state state; uint32_t local_circuit_id; bool neighbor_set; uint8_t neighbor_id[6]; uint32_t neighbor_circuit_id; }; struct isis_item; struct isis_item { struct isis_item *next; }; struct isis_lan_neighbor; struct isis_lan_neighbor { struct isis_lan_neighbor *next; uint8_t mac[6]; }; struct isis_ipv4_address; struct isis_ipv4_address { struct isis_ipv4_address *next; struct in_addr addr; }; struct isis_ipv6_address; struct isis_ipv6_address { struct isis_ipv6_address *next; struct in6_addr addr; }; struct isis_mt_router_info; struct isis_mt_router_info { struct isis_mt_router_info *next; bool overload; bool attached; uint16_t mtid; }; struct isis_auth; struct isis_auth { struct isis_auth *next; uint8_t type; uint8_t length; uint8_t value[256]; uint8_t plength; uint8_t passwd[256]; size_t offset; /* Only valid after packing */ }; struct isis_item_list; struct isis_item_list { struct isis_item *head; struct isis_item **tail; RB_ENTRY(isis_item_list) mt_tree; uint16_t mtid; unsigned int count; }; struct isis_purge_originator { bool sender_set; uint8_t generator[6]; uint8_t sender[6]; }; enum isis_auth_result { ISIS_AUTH_OK = 0, ISIS_AUTH_TYPE_FAILURE, ISIS_AUTH_FAILURE, ISIS_AUTH_NO_VALIDATOR, }; RB_HEAD(isis_mt_item_list, isis_item_list); struct isis_item_list *isis_get_mt_items(struct isis_mt_item_list *m, uint16_t mtid); struct isis_item_list *isis_lookup_mt_items(struct isis_mt_item_list *m, uint16_t mtid); struct isis_tlvs { struct isis_item_list isis_auth; struct isis_purge_originator *purge_originator; struct isis_item_list area_addresses; struct isis_item_list oldstyle_reach; struct isis_item_list lan_neighbor; struct isis_item_list lsp_entries; struct isis_item_list extended_reach; struct isis_mt_item_list mt_reach; struct isis_item_list oldstyle_ip_reach; struct isis_protocols_supported protocols_supported; struct isis_item_list oldstyle_ip_reach_ext; struct isis_item_list ipv4_address; struct isis_item_list ipv6_address; struct isis_item_list mt_router_info; bool mt_router_info_empty; struct in_addr *te_router_id; struct isis_item_list extended_ip_reach; struct isis_mt_item_list mt_ip_reach; char *hostname; struct isis_item_list ipv6_reach; struct isis_mt_item_list mt_ipv6_reach; struct isis_threeway_adj *threeway_adj; struct isis_spine_leaf *spine_leaf; }; #define ISIS_PREFIX_SID_READVERTISED 0x80 #define ISIS_PREFIX_SID_NODE 0x40 #define ISIS_PREFIX_SID_NO_PHP 0x20 #define ISIS_PREFIX_SID_EXPLICIT_NULL 0x10 #define ISIS_PREFIX_SID_VALUE 0x08 #define ISIS_PREFIX_SID_LOCAL 0x04 struct isis_prefix_sid; struct isis_prefix_sid { struct isis_prefix_sid *next; uint8_t flags; uint8_t algorithm; uint32_t value; }; enum isis_tlv_context { ISIS_CONTEXT_LSP, ISIS_CONTEXT_SUBTLV_NE_REACH, ISIS_CONTEXT_SUBTLV_IP_REACH, ISIS_CONTEXT_SUBTLV_IPV6_REACH, ISIS_CONTEXT_MAX }; struct isis_subtlvs { enum isis_tlv_context context; /* draft-baker-ipv6-isis-dst-src-routing-06 */ struct prefix_ipv6 *source_prefix; /* draft-ietf-isis-segment-routing-extensions-16 */ struct isis_item_list prefix_sids; }; enum isis_tlv_type { ISIS_TLV_AREA_ADDRESSES = 1, ISIS_TLV_OLDSTYLE_REACH = 2, ISIS_TLV_LAN_NEIGHBORS = 6, ISIS_TLV_PADDING = 8, ISIS_TLV_LSP_ENTRY = 9, ISIS_TLV_AUTH = 10, ISIS_TLV_PURGE_ORIGINATOR = 13, ISIS_TLV_EXTENDED_REACH = 22, ISIS_TLV_OLDSTYLE_IP_REACH = 128, ISIS_TLV_PROTOCOLS_SUPPORTED = 129, ISIS_TLV_OLDSTYLE_IP_REACH_EXT = 130, ISIS_TLV_IPV4_ADDRESS = 132, ISIS_TLV_TE_ROUTER_ID = 134, ISIS_TLV_EXTENDED_IP_REACH = 135, ISIS_TLV_DYNAMIC_HOSTNAME = 137, ISIS_TLV_SPINE_LEAF_EXT = 150, ISIS_TLV_MT_REACH = 222, ISIS_TLV_MT_ROUTER_INFO = 229, ISIS_TLV_IPV6_ADDRESS = 232, ISIS_TLV_MT_IP_REACH = 235, ISIS_TLV_IPV6_REACH = 236, ISIS_TLV_MT_IPV6_REACH = 237, ISIS_TLV_THREE_WAY_ADJ = 240, ISIS_TLV_MAX = 256, ISIS_SUBTLV_PREFIX_SID = 3, ISIS_SUBTLV_IPV6_SOURCE_PREFIX = 22 }; #define IS_COMPAT_MT_TLV(tlv_type) \ ((tlv_type == ISIS_TLV_MT_REACH) || (tlv_type == ISIS_TLV_MT_IP_REACH) \ || (tlv_type == ISIS_TLV_MT_IPV6_REACH)) struct stream; int isis_pack_tlvs(struct isis_tlvs *tlvs, struct stream *stream, size_t len_pointer, bool pad, bool is_lsp); void isis_free_tlvs(struct isis_tlvs *tlvs); struct isis_tlvs *isis_alloc_tlvs(void); int isis_unpack_tlvs(size_t avail_len, struct stream *stream, struct isis_tlvs **dest, const char **error_log); const char *isis_format_tlvs(struct isis_tlvs *tlvs); struct isis_tlvs *isis_copy_tlvs(struct isis_tlvs *tlvs); struct list *isis_fragment_tlvs(struct isis_tlvs *tlvs, size_t size); #define ISIS_EXTENDED_IP_REACH_DOWN 0x80 #define ISIS_EXTENDED_IP_REACH_SUBTLV 0x40 #define ISIS_IPV6_REACH_DOWN 0x80 #define ISIS_IPV6_REACH_EXTERNAL 0x40 #define ISIS_IPV6_REACH_SUBTLV 0x20 #ifndef ISIS_MT_MASK #define ISIS_MT_MASK 0x0fff #define ISIS_MT_OL_MASK 0x8000 #define ISIS_MT_AT_MASK 0x4000 #endif void isis_tlvs_add_auth(struct isis_tlvs *tlvs, struct isis_passwd *passwd); void isis_tlvs_add_area_addresses(struct isis_tlvs *tlvs, struct list *addresses); void isis_tlvs_add_lan_neighbors(struct isis_tlvs *tlvs, struct list *neighbors); void isis_tlvs_set_protocols_supported(struct isis_tlvs *tlvs, struct nlpids *nlpids); void isis_tlvs_add_mt_router_info(struct isis_tlvs *tlvs, uint16_t mtid, bool overload, bool attached); void isis_tlvs_add_ipv4_address(struct isis_tlvs *tlvs, struct in_addr *addr); void isis_tlvs_add_ipv4_addresses(struct isis_tlvs *tlvs, struct list *addresses); void isis_tlvs_add_ipv6_addresses(struct isis_tlvs *tlvs, struct list *addresses); int isis_tlvs_auth_is_valid(struct isis_tlvs *tlvs, struct isis_passwd *passwd, struct stream *stream, bool is_lsp); bool isis_tlvs_area_addresses_match(struct isis_tlvs *tlvs, struct list *addresses); struct isis_adjacency; void isis_tlvs_to_adj(struct isis_tlvs *tlvs, struct isis_adjacency *adj, bool *changed); bool isis_tlvs_own_snpa_found(struct isis_tlvs *tlvs, uint8_t *snpa); void isis_tlvs_add_lsp_entry(struct isis_tlvs *tlvs, struct isis_lsp *lsp); void isis_tlvs_add_csnp_entries(struct isis_tlvs *tlvs, uint8_t *start_id, uint8_t *stop_id, uint16_t num_lsps, struct lspdb_head *lspdb, struct isis_lsp **last_lsp); void isis_tlvs_set_dynamic_hostname(struct isis_tlvs *tlvs, const char *hostname); void isis_tlvs_set_te_router_id(struct isis_tlvs *tlvs, const struct in_addr *id); void isis_tlvs_add_oldstyle_ip_reach(struct isis_tlvs *tlvs, struct prefix_ipv4 *dest, uint8_t metric); void isis_tlvs_add_extended_ip_reach(struct isis_tlvs *tlvs, struct prefix_ipv4 *dest, uint32_t metric); void isis_tlvs_add_ipv6_reach(struct isis_tlvs *tlvs, uint16_t mtid, struct prefix_ipv6 *dest, uint32_t metric); void isis_tlvs_add_ipv6_dstsrc_reach(struct isis_tlvs *tlvs, uint16_t mtid, struct prefix_ipv6 *dest, struct prefix_ipv6 *src, uint32_t metric); void isis_tlvs_add_oldstyle_reach(struct isis_tlvs *tlvs, uint8_t *id, uint8_t metric); void isis_tlvs_add_extended_reach(struct isis_tlvs *tlvs, uint16_t mtid, uint8_t *id, uint32_t metric, uint8_t *subtlvs, uint8_t subtlv_len); const char *isis_threeway_state_name(enum isis_threeway_state state); void isis_tlvs_add_threeway_adj(struct isis_tlvs *tlvs, enum isis_threeway_state state, uint32_t local_circuit_id, const uint8_t *neighbor_id, uint32_t neighbor_circuit_id); void isis_tlvs_add_spine_leaf(struct isis_tlvs *tlvs, uint8_t tier, bool has_tier, bool is_leaf, bool is_spine, bool is_backup); struct isis_mt_router_info * isis_tlvs_lookup_mt_router_info(struct isis_tlvs *tlvs, uint16_t mtid); void isis_tlvs_set_purge_originator(struct isis_tlvs *tlvs, const uint8_t *generator, const uint8_t *sender); #endif frr-7.2.1/isisd/isis_tx_queue.c0000644000000000000000000001202613610377563013362 00000000000000/* * IS-IS Rout(e)ing protocol - LSP TX Queuing logic * * Copyright (C) 2018 Christian Franke * * This file is part of FreeRangeRouting (FRR) * * FRR is free software; you can redistribute it and/or modify it * under the terms of the GNU General Public License as published by the * Free Software Foundation; either version 2, or (at your option) any * later version. * * FRR 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; see the file COPYING; if not, write to the Free Software * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA */ #include #include "hash.h" #include "jhash.h" #include "isisd/isisd.h" #include "isisd/isis_memory.h" #include "isisd/isis_flags.h" #include "isisd/isis_circuit.h" #include "isisd/isis_lsp.h" #include "isisd/isis_misc.h" #include "isisd/isis_tx_queue.h" DEFINE_MTYPE_STATIC(ISISD, TX_QUEUE, "ISIS TX Queue") DEFINE_MTYPE_STATIC(ISISD, TX_QUEUE_ENTRY, "ISIS TX Queue Entry") struct isis_tx_queue { struct isis_circuit *circuit; void (*send_event)(struct isis_circuit *circuit, struct isis_lsp *, enum isis_tx_type); struct hash *hash; }; struct isis_tx_queue_entry { struct isis_lsp *lsp; enum isis_tx_type type; bool is_retry; struct thread *retry; struct isis_tx_queue *queue; }; static unsigned tx_queue_hash_key(const void *p) { const struct isis_tx_queue_entry *e = p; uint32_t id_key = jhash(e->lsp->hdr.lsp_id, ISIS_SYS_ID_LEN + 2, 0x55aa5a5a); return jhash_1word(e->lsp->level, id_key); } static bool tx_queue_hash_cmp(const void *a, const void *b) { const struct isis_tx_queue_entry *ea = a, *eb = b; if (ea->lsp->level != eb->lsp->level) return false; if (memcmp(ea->lsp->hdr.lsp_id, eb->lsp->hdr.lsp_id, ISIS_SYS_ID_LEN + 2)) return false; return true; } struct isis_tx_queue *isis_tx_queue_new( struct isis_circuit *circuit, void(*send_event)(struct isis_circuit *circuit, struct isis_lsp *, enum isis_tx_type)) { struct isis_tx_queue *rv = XCALLOC(MTYPE_TX_QUEUE, sizeof(*rv)); rv->circuit = circuit; rv->send_event = send_event; rv->hash = hash_create(tx_queue_hash_key, tx_queue_hash_cmp, NULL); return rv; } static void tx_queue_element_free(void *element) { struct isis_tx_queue_entry *e = element; if (e->retry) thread_cancel(e->retry); XFREE(MTYPE_TX_QUEUE_ENTRY, e); } void isis_tx_queue_free(struct isis_tx_queue *queue) { hash_clean(queue->hash, tx_queue_element_free); hash_free(queue->hash); XFREE(MTYPE_TX_QUEUE, queue); } static struct isis_tx_queue_entry *tx_queue_find(struct isis_tx_queue *queue, struct isis_lsp *lsp) { struct isis_tx_queue_entry e = { .lsp = lsp }; return hash_lookup(queue->hash, &e); } static int tx_queue_send_event(struct thread *thread) { struct isis_tx_queue_entry *e = THREAD_ARG(thread); struct isis_tx_queue *queue = e->queue; e->retry = NULL; thread_add_timer(master, tx_queue_send_event, e, 5, &e->retry); if (e->is_retry) queue->circuit->area->lsp_rxmt_count++; else e->is_retry = true; queue->send_event(queue->circuit, e->lsp, e->type); /* Don't access e here anymore, send_event might have destroyed it */ return 0; } void _isis_tx_queue_add(struct isis_tx_queue *queue, struct isis_lsp *lsp, enum isis_tx_type type, const char *func, const char *file, int line) { if (!queue) return; if (isis->debugs & DEBUG_TX_QUEUE) { zlog_debug("Add LSP %s to %s queue as %s LSP. (From %s %s:%d)", rawlspid_print(lsp->hdr.lsp_id), queue->circuit->interface->name, (type == TX_LSP_CIRCUIT_SCOPED) ? "circuit scoped" : "regular", func, file, line); } struct isis_tx_queue_entry *e = tx_queue_find(queue, lsp); if (!e) { e = XCALLOC(MTYPE_TX_QUEUE_ENTRY, sizeof(*e)); e->lsp = lsp; e->queue = queue; struct isis_tx_queue_entry *inserted; inserted = hash_get(queue->hash, e, hash_alloc_intern); assert(inserted == e); } e->type = type; if (e->retry) thread_cancel(e->retry); thread_add_event(master, tx_queue_send_event, e, 0, &e->retry); e->is_retry = false; } void _isis_tx_queue_del(struct isis_tx_queue *queue, struct isis_lsp *lsp, const char *func, const char *file, int line) { if (!queue) return; struct isis_tx_queue_entry *e = tx_queue_find(queue, lsp); if (!e) return; if (isis->debugs & DEBUG_TX_QUEUE) { zlog_debug("Remove LSP %s from %s queue. (From %s %s:%d)", rawlspid_print(lsp->hdr.lsp_id), queue->circuit->interface->name, func, file, line); } if (e->retry) thread_cancel(e->retry); hash_release(queue->hash, e); XFREE(MTYPE_TX_QUEUE_ENTRY, e); } unsigned long isis_tx_queue_len(struct isis_tx_queue *queue) { if (!queue) return 0; return hashcount(queue->hash); } void isis_tx_queue_clean(struct isis_tx_queue *queue) { hash_clean(queue->hash, tx_queue_element_free); } frr-7.2.1/isisd/isis_tx_queue.h0000644000000000000000000000351113610377563013366 00000000000000/* * IS-IS Rout(e)ing protocol - LSP TX Queuing logic * * Copyright (C) 2018 Christian Franke * * This file is part of FreeRangeRouting (FRR) * * FRR is free software; you can redistribute it and/or modify it * under the terms of the GNU General Public License as published by the * Free Software Foundation; either version 2, or (at your option) any * later version. * * FRR 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; see the file COPYING; if not, write to the Free Software * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA */ #ifndef ISIS_TX_QUEUE_H #define ISIS_TX_QUEUE_H enum isis_tx_type { TX_LSP_NORMAL = 0, TX_LSP_CIRCUIT_SCOPED }; struct isis_tx_queue; struct isis_tx_queue *isis_tx_queue_new( struct isis_circuit *circuit, void(*send_event)(struct isis_circuit *circuit, struct isis_lsp *, enum isis_tx_type) ); void isis_tx_queue_free(struct isis_tx_queue *queue); #define isis_tx_queue_add(queue, lsp, type) \ _isis_tx_queue_add((queue), (lsp), (type), \ __func__, __FILE__, __LINE__) void _isis_tx_queue_add(struct isis_tx_queue *queue, struct isis_lsp *lsp, enum isis_tx_type type, const char *func, const char *file, int line); #define isis_tx_queue_del(queue, lsp) \ _isis_tx_queue_del((queue), (lsp), __func__, __FILE__, __LINE__) void _isis_tx_queue_del(struct isis_tx_queue *queue, struct isis_lsp *lsp, const char *func, const char *file, int line); unsigned long isis_tx_queue_len(struct isis_tx_queue *queue); void isis_tx_queue_clean(struct isis_tx_queue *queue); #endif frr-7.2.1/isisd/isis_vty_fabricd.c0000644000000000000000000007455513610377563014036 00000000000000/* * IS-IS Rout(e)ing protocol - isis_vty_fabricd.c * * This file contains the CLI that is specific to OpenFabric * * Copyright (C) 2018 Christian Franke, for NetDEF, Inc. * * This program is free software; you can redistribute it and/or modify it * under the terms of the GNU General Public Licenseas 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; see the file COPYING; if not, write to the Free Software * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA */ #include #include "command.h" #include "lib/bfd.h" #include "isisd/isis_bfd.h" #include "isisd/isisd.h" #include "isisd/fabricd.h" #include "isisd/isis_tlvs.h" #include "isisd/isis_misc.h" #include "isisd/isis_lsp.h" #include "isisd/isis_csm.h" #include "isisd/isis_circuit.h" #include "lib/spf_backoff.h" #include "isisd/isis_mt.h" static struct isis_circuit *isis_circuit_lookup(struct vty *vty) { struct interface *ifp = VTY_GET_CONTEXT(interface); struct isis_circuit *circuit; if (!ifp) { vty_out(vty, "Invalid interface \n"); return NULL; } circuit = circuit_scan_by_ifp(ifp); if (!circuit) { vty_out(vty, "ISIS is not enabled on circuit %s\n", ifp->name); return NULL; } return circuit; } DEFUN (fabric_tier, fabric_tier_cmd, "fabric-tier (0-14)", "Statically configure the tier to advertise\n" "Tier to advertise\n") { VTY_DECLVAR_CONTEXT(isis_area, area); uint8_t tier = atoi(argv[1]->arg); fabricd_configure_tier(area, tier); return CMD_SUCCESS; } DEFUN (no_fabric_tier, no_fabric_tier_cmd, "no fabric-tier [(0-14)]", NO_STR "Statically configure the tier to advertise\n" "Tier to advertise\n") { VTY_DECLVAR_CONTEXT(isis_area, area); fabricd_configure_tier(area, ISIS_TIER_UNDEFINED); return CMD_SUCCESS; } DEFUN (triggered_csnp, triggered_csnp_cmd, "triggered-csnp-delay (100-10000) [always]", "Configure the delay for triggered CSNPs\n" "Delay in milliseconds\n" "Trigger CSNP for all LSPs, not only circuit-scoped\n") { VTY_DECLVAR_CONTEXT(isis_area, area); int csnp_delay = atoi(argv[1]->arg); bool always_send_csnp = (argc == 3); fabricd_configure_triggered_csnp(area, csnp_delay, always_send_csnp); return CMD_SUCCESS; } DEFUN (no_triggered_csnp, no_triggered_csnp_cmd, "no triggered-csnp-delay [(100-10000) [always]]", NO_STR "Configure the delay for triggered CSNPs\n" "Delay in milliseconds\n" "Trigger CSNP for all LSPs, not only circuit-scoped\n") { VTY_DECLVAR_CONTEXT(isis_area, area); fabricd_configure_triggered_csnp(area, FABRICD_DEFAULT_CSNP_DELAY, false); return CMD_SUCCESS; } static void lsp_print_flooding(struct vty *vty, struct isis_lsp *lsp) { char lspid[255]; lspid_print(lsp->hdr.lsp_id, lspid, true, true); vty_out(vty, "Flooding information for %s\n", lspid); if (!lsp->flooding_neighbors[TX_LSP_NORMAL]) { vty_out(vty, " Never received.\n"); return; } vty_out(vty, " Last received on: %s (", lsp->flooding_interface ? lsp->flooding_interface : "(null)"); time_t uptime = time(NULL) - lsp->flooding_time; struct tm *tm = gmtime(&uptime); if (uptime < ONE_DAY_SECOND) vty_out(vty, "%02d:%02d:%02d", tm->tm_hour, tm->tm_min, tm->tm_sec); else if (uptime < ONE_WEEK_SECOND) vty_out(vty, "%dd%02dh%02dm", tm->tm_yday, tm->tm_hour, tm->tm_min); else vty_out(vty, "%02dw%dd%02dh", tm->tm_yday / 7, tm->tm_yday - ((tm->tm_yday / 7) * 7), tm->tm_hour); vty_out(vty, " ago)\n"); if (lsp->flooding_circuit_scoped) { vty_out(vty, " Received as circuit-scoped LSP, so not " "flooded.\n"); return; } for (enum isis_tx_type type = TX_LSP_NORMAL; type <= TX_LSP_CIRCUIT_SCOPED; type++) { struct listnode *node; uint8_t *neighbor_id; vty_out(vty, " %s:\n", (type == TX_LSP_NORMAL) ? "RF" : "DNR"); for (ALL_LIST_ELEMENTS_RO(lsp->flooding_neighbors[type], node, neighbor_id)) { vty_out(vty, " %s\n", print_sys_hostname(neighbor_id)); } } } DEFUN (show_lsp_flooding, show_lsp_flooding_cmd, "show openfabric flooding [WORD]", SHOW_STR PROTO_HELP "Flooding information\n" "LSP ID\n") { const char *lspid = NULL; if (argc == 4) lspid = argv[3]->arg; struct listnode *node; struct isis_area *area; for (ALL_LIST_ELEMENTS_RO(isis->area_list, node, area)) { struct lspdb_head *head = &area->lspdb[ISIS_LEVEL2 - 1]; struct isis_lsp *lsp; vty_out(vty, "Area %s:\n", area->area_tag ? area->area_tag : "null"); if (lspid) { lsp = lsp_for_arg(head, lspid); if (lsp) lsp_print_flooding(vty, lsp); continue; } frr_each (lspdb, head, lsp) { lsp_print_flooding(vty, lsp); vty_out(vty, "\n"); } } return CMD_SUCCESS; } DEFUN (ip_router_isis, ip_router_isis_cmd, "ip router " PROTO_NAME " WORD", "Interface Internet Protocol config commands\n" "IP router interface commands\n" PROTO_HELP "Routing process tag\n") { int idx_afi = 0; int idx_word = 3; VTY_DECLVAR_CONTEXT(interface, ifp); struct isis_circuit *circuit; struct isis_area *area; const char *af = argv[idx_afi]->arg; const char *area_tag = argv[idx_word]->arg; /* Prevent more than one area per circuit */ circuit = circuit_scan_by_ifp(ifp); if (circuit && circuit->area) { if (strcmp(circuit->area->area_tag, area_tag)) { vty_out(vty, "ISIS circuit is already defined on %s\n", circuit->area->area_tag); return CMD_ERR_NOTHING_TODO; } } area = isis_area_lookup(area_tag); if (!area) area = isis_area_create(area_tag); if (!circuit || !circuit->area) { circuit = isis_circuit_create(area, ifp); if (circuit->state != C_STATE_CONF && circuit->state != C_STATE_UP) { vty_out(vty, "Couldn't bring up interface, please check log.\n"); return CMD_WARNING_CONFIG_FAILED; } } bool ip = circuit->ip_router, ipv6 = circuit->ipv6_router; if (af[2] != '\0') ipv6 = true; else ip = true; isis_circuit_af_set(circuit, ip, ipv6); return CMD_SUCCESS; } DEFUN (ip6_router_isis, ip6_router_isis_cmd, "ipv6 router " PROTO_NAME " WORD", "Interface Internet Protocol config commands\n" "IP router interface commands\n" PROTO_HELP "Routing process tag\n") { return ip_router_isis(self, vty, argc, argv); } DEFUN (no_ip_router_isis, no_ip_router_isis_cmd, "no router " PROTO_NAME " WORD", NO_STR "Interface Internet Protocol config commands\n" "IP router interface commands\n" "IP router interface commands\n" PROTO_HELP "Routing process tag\n") { int idx_afi = 1; int idx_word = 4; VTY_DECLVAR_CONTEXT(interface, ifp); struct isis_area *area; struct isis_circuit *circuit; const char *af = argv[idx_afi]->arg; const char *area_tag = argv[idx_word]->arg; area = isis_area_lookup(area_tag); if (!area) { vty_out(vty, "Can't find ISIS instance %s\n", area_tag); return CMD_ERR_NO_MATCH; } circuit = circuit_lookup_by_ifp(ifp, area->circuit_list); if (!circuit) { vty_out(vty, "ISIS is not enabled on circuit %s\n", ifp->name); return CMD_ERR_NO_MATCH; } bool ip = circuit->ip_router, ipv6 = circuit->ipv6_router; if (af[2] != '\0') ipv6 = false; else ip = false; isis_circuit_af_set(circuit, ip, ipv6); return CMD_SUCCESS; } DEFUN (isis_bfd, isis_bfd_cmd, PROTO_NAME " bfd", PROTO_HELP "Enable BFD support\n") { struct isis_circuit *circuit = isis_circuit_lookup(vty); if (!circuit) return CMD_ERR_NO_MATCH; if (circuit->bfd_info && CHECK_FLAG(circuit->bfd_info->flags, BFD_FLAG_PARAM_CFG)) { return CMD_SUCCESS; } isis_bfd_circuit_param_set(circuit, BFD_DEF_MIN_RX, BFD_DEF_MIN_TX, BFD_DEF_DETECT_MULT, true); return CMD_SUCCESS; } DEFUN (no_isis_bfd, no_isis_bfd_cmd, "no " PROTO_NAME " bfd", NO_STR PROTO_HELP "Disables BFD support\n" ) { struct isis_circuit *circuit = isis_circuit_lookup(vty); if (!circuit) return CMD_ERR_NO_MATCH; if (!circuit->bfd_info) return CMD_SUCCESS; isis_bfd_circuit_cmd(circuit, ZEBRA_BFD_DEST_DEREGISTER); bfd_info_free(&circuit->bfd_info); return CMD_SUCCESS; } DEFUN (set_overload_bit, set_overload_bit_cmd, "set-overload-bit", "Set overload bit to avoid any transit traffic\n") { VTY_DECLVAR_CONTEXT(isis_area, area); isis_area_overload_bit_set(area, true); return CMD_SUCCESS; } DEFUN (no_set_overload_bit, no_set_overload_bit_cmd, "no set-overload-bit", "Reset overload bit to accept transit traffic\n" "Reset overload bit\n") { VTY_DECLVAR_CONTEXT(isis_area, area); isis_area_overload_bit_set(area, false); return CMD_SUCCESS; } static int isis_vty_password_set(struct vty *vty, int argc, struct cmd_token *argv[], int level) { VTY_DECLVAR_CONTEXT(isis_area, area); int idx_algo = 1; int idx_password = 2; int idx_snp_auth = 5; uint8_t snp_auth = 0; const char *passwd = argv[idx_password]->arg; if (strlen(passwd) > 254) { vty_out(vty, "Too long area password (>254)\n"); return CMD_WARNING_CONFIG_FAILED; } if (argc > idx_snp_auth) { snp_auth = SNP_AUTH_SEND; if (strmatch(argv[idx_snp_auth]->text, "validate")) snp_auth |= SNP_AUTH_RECV; } if (strmatch(argv[idx_algo]->text, "clear")) { return isis_area_passwd_cleartext_set(area, level, passwd, snp_auth); } else if (strmatch(argv[idx_algo]->text, "md5")) { return isis_area_passwd_hmac_md5_set(area, level, passwd, snp_auth); } return CMD_WARNING_CONFIG_FAILED; } DEFUN (domain_passwd, domain_passwd_cmd, "domain-password WORD [authenticate snp ]", "Set the authentication password for a routing domain\n" "Authentication type\n" "Authentication type\n" "Level-wide password\n" "Authentication\n" "SNP PDUs\n" "Send but do not check PDUs on receiving\n" "Send and check PDUs on receiving\n") { return isis_vty_password_set(vty, argc, argv, IS_LEVEL_2); } DEFUN (no_domain_passwd, no_domain_passwd_cmd, "no domain-password", NO_STR "Set the authentication password for a routing domain\n") { VTY_DECLVAR_CONTEXT(isis_area, area); return isis_area_passwd_unset(area, IS_LEVEL_2); } static int isis_vty_lsp_gen_interval_set(struct vty *vty, int level, uint16_t interval) { VTY_DECLVAR_CONTEXT(isis_area, area); int lvl; for (lvl = IS_LEVEL_1; lvl <= IS_LEVEL_2; ++lvl) { if (!(lvl & level)) continue; if (interval >= area->lsp_refresh[lvl - 1]) { vty_out(vty, "LSP gen interval %us must be less than " "the LSP refresh interval %us\n", interval, area->lsp_refresh[lvl - 1]); return CMD_WARNING_CONFIG_FAILED; } } for (lvl = IS_LEVEL_1; lvl <= IS_LEVEL_2; ++lvl) { if (!(lvl & level)) continue; area->lsp_gen_interval[lvl - 1] = interval; } return CMD_SUCCESS; } DEFUN (lsp_gen_interval, lsp_gen_interval_cmd, "lsp-gen-interval (1-120)", "Minimum interval between regenerating same LSP\n" "Minimum interval in seconds\n") { uint16_t interval = atoi(argv[1]->arg); return isis_vty_lsp_gen_interval_set(vty, IS_LEVEL_1_AND_2, interval); } DEFUN (no_lsp_gen_interval, no_lsp_gen_interval_cmd, "no lsp-gen-interval [(1-120)]", NO_STR "Minimum interval between regenerating same LSP\n" "Minimum interval in seconds\n") { VTY_DECLVAR_CONTEXT(isis_area, area); return isis_vty_lsp_gen_interval_set(vty, IS_LEVEL_1_AND_2, DEFAULT_MIN_LSP_GEN_INTERVAL); } static int isis_vty_lsp_refresh_set(struct vty *vty, int level, uint16_t interval) { VTY_DECLVAR_CONTEXT(isis_area, area); int lvl; for (lvl = IS_LEVEL_1; lvl <= IS_LEVEL_2; ++lvl) { if (!(lvl & level)) continue; if (interval <= area->lsp_gen_interval[lvl - 1]) { vty_out(vty, "LSP refresh interval %us must be greater than " "the configured LSP gen interval %us\n", interval, area->lsp_gen_interval[lvl - 1]); return CMD_WARNING_CONFIG_FAILED; } if (interval > (area->max_lsp_lifetime[lvl - 1] - 300)) { vty_out(vty, "LSP refresh interval %us must be less than " "the configured LSP lifetime %us less 300\n", interval, area->max_lsp_lifetime[lvl - 1]); return CMD_WARNING_CONFIG_FAILED; } } for (lvl = IS_LEVEL_1; lvl <= IS_LEVEL_2; ++lvl) { if (!(lvl & level)) continue; isis_area_lsp_refresh_set(area, lvl, interval); } return CMD_SUCCESS; } DEFUN (lsp_refresh_interval, lsp_refresh_interval_cmd, "lsp-refresh-interval (1-65235)", "LSP refresh interval\n" "LSP refresh interval in seconds\n") { unsigned int interval = atoi(argv[1]->arg); return isis_vty_lsp_refresh_set(vty, IS_LEVEL_1_AND_2, interval); } DEFUN (no_lsp_refresh_interval, no_lsp_refresh_interval_cmd, "no lsp-refresh-interval [(1-65235)]", NO_STR "LSP refresh interval\n" "LSP refresh interval in seconds\n") { return isis_vty_lsp_refresh_set(vty, IS_LEVEL_1_AND_2, DEFAULT_MAX_LSP_GEN_INTERVAL); } static int isis_vty_max_lsp_lifetime_set(struct vty *vty, int level, uint16_t interval) { VTY_DECLVAR_CONTEXT(isis_area, area); int lvl; uint16_t refresh_interval = interval - 300; int set_refresh_interval[ISIS_LEVELS] = {0, 0}; for (lvl = IS_LEVEL_1; lvl <= IS_LEVEL_2; lvl++) { if (!(lvl & level)) continue; if (refresh_interval < area->lsp_refresh[lvl - 1]) { vty_out(vty, "Level %d Max LSP lifetime %us must be 300s greater than " "the configured LSP refresh interval %us\n", lvl, interval, area->lsp_refresh[lvl - 1]); vty_out(vty, "Automatically reducing level %d LSP refresh interval " "to %us\n", lvl, refresh_interval); set_refresh_interval[lvl - 1] = 1; if (refresh_interval <= area->lsp_gen_interval[lvl - 1]) { vty_out(vty, "LSP refresh interval %us must be greater than " "the configured LSP gen interval %us\n", refresh_interval, area->lsp_gen_interval[lvl - 1]); return CMD_WARNING_CONFIG_FAILED; } } } for (lvl = IS_LEVEL_1; lvl <= IS_LEVEL_2; lvl++) { if (!(lvl & level)) continue; isis_area_max_lsp_lifetime_set(area, lvl, interval); if (set_refresh_interval[lvl - 1]) isis_area_lsp_refresh_set(area, lvl, refresh_interval); } return CMD_SUCCESS; } DEFUN (max_lsp_lifetime, max_lsp_lifetime_cmd, "max-lsp-lifetime (350-65535)", "Maximum LSP lifetime\n" "LSP lifetime in seconds\n") { int lifetime = atoi(argv[1]->arg); return isis_vty_max_lsp_lifetime_set(vty, IS_LEVEL_1_AND_2, lifetime); } DEFUN (no_max_lsp_lifetime, no_max_lsp_lifetime_cmd, "no max-lsp-lifetime [(350-65535)]", NO_STR "Maximum LSP lifetime\n" "LSP lifetime in seconds\n") { return isis_vty_max_lsp_lifetime_set(vty, IS_LEVEL_1_AND_2, DEFAULT_LSP_LIFETIME); } DEFUN (spf_interval, spf_interval_cmd, "spf-interval (1-120)", "Minimum interval between SPF calculations\n" "Minimum interval between consecutive SPFs in seconds\n") { VTY_DECLVAR_CONTEXT(isis_area, area); uint16_t interval = atoi(argv[1]->arg); area->min_spf_interval[0] = interval; area->min_spf_interval[1] = interval; return CMD_SUCCESS; } DEFUN (no_spf_interval, no_spf_interval_cmd, "no spf-interval [(1-120)]", NO_STR "Minimum interval between SPF calculations\n" "Minimum interval between consecutive SPFs in seconds\n") { VTY_DECLVAR_CONTEXT(isis_area, area); area->min_spf_interval[0] = MINIMUM_SPF_INTERVAL; area->min_spf_interval[1] = MINIMUM_SPF_INTERVAL; return CMD_SUCCESS; } static int isis_vty_lsp_mtu_set(struct vty *vty, unsigned int lsp_mtu) { VTY_DECLVAR_CONTEXT(isis_area, area); struct listnode *node; struct isis_circuit *circuit; for (ALL_LIST_ELEMENTS_RO(area->circuit_list, node, circuit)) { if (circuit->state != C_STATE_INIT && circuit->state != C_STATE_UP) continue; if (lsp_mtu > isis_circuit_pdu_size(circuit)) { vty_out(vty, "ISIS area contains circuit %s, which has a maximum PDU size of %zu.\n", circuit->interface->name, isis_circuit_pdu_size(circuit)); return CMD_WARNING_CONFIG_FAILED; } } isis_area_lsp_mtu_set(area, lsp_mtu); return CMD_SUCCESS; } DEFUN (area_lsp_mtu, area_lsp_mtu_cmd, "lsp-mtu (128-4352)", "Configure the maximum size of generated LSPs\n" "Maximum size of generated LSPs\n") { int idx_number = 1; unsigned int lsp_mtu; lsp_mtu = strtoul(argv[idx_number]->arg, NULL, 10); return isis_vty_lsp_mtu_set(vty, lsp_mtu); } DEFUN (no_area_lsp_mtu, no_area_lsp_mtu_cmd, "no lsp-mtu [(128-4352)]", NO_STR "Configure the maximum size of generated LSPs\n" "Maximum size of generated LSPs\n") { return isis_vty_lsp_mtu_set(vty, DEFAULT_LSP_MTU); } DEFUN (no_spf_delay_ietf, no_spf_delay_ietf_cmd, "no spf-delay-ietf", NO_STR "IETF SPF delay algorithm\n") { VTY_DECLVAR_CONTEXT(isis_area, area); spf_backoff_free(area->spf_delay_ietf[0]); spf_backoff_free(area->spf_delay_ietf[1]); area->spf_delay_ietf[0] = NULL; area->spf_delay_ietf[1] = NULL; return CMD_SUCCESS; } DEFUN (spf_delay_ietf, spf_delay_ietf_cmd, "spf-delay-ietf init-delay (0-60000) short-delay (0-60000) long-delay (0-60000) holddown (0-60000) time-to-learn (0-60000)", "IETF SPF delay algorithm\n" "Delay used while in QUIET state\n" "Delay used while in QUIET state in milliseconds\n" "Delay used while in SHORT_WAIT state\n" "Delay used while in SHORT_WAIT state in milliseconds\n" "Delay used while in LONG_WAIT\n" "Delay used while in LONG_WAIT state in milliseconds\n" "Time with no received IGP events before considering IGP stable\n" "Time with no received IGP events before considering IGP stable (in milliseconds)\n" "Maximum duration needed to learn all the events related to a single failure\n" "Maximum duration needed to learn all the events related to a single failure (in milliseconds)\n") { VTY_DECLVAR_CONTEXT(isis_area, area); long init_delay = atol(argv[2]->arg); long short_delay = atol(argv[4]->arg); long long_delay = atol(argv[6]->arg); long holddown = atol(argv[8]->arg); long timetolearn = atol(argv[10]->arg); size_t bufsiz = strlen(area->area_tag) + sizeof("IS-IS Lx"); char *buf = XCALLOC(MTYPE_TMP, bufsiz); snprintf(buf, bufsiz, "IS-IS %s L1", area->area_tag); spf_backoff_free(area->spf_delay_ietf[0]); area->spf_delay_ietf[0] = spf_backoff_new(master, buf, init_delay, short_delay, long_delay, holddown, timetolearn); snprintf(buf, bufsiz, "IS-IS %s L2", area->area_tag); spf_backoff_free(area->spf_delay_ietf[1]); area->spf_delay_ietf[1] = spf_backoff_new(master, buf, init_delay, short_delay, long_delay, holddown, timetolearn); XFREE(MTYPE_TMP, buf); return CMD_SUCCESS; } DEFUN (area_purge_originator, area_purge_originator_cmd, "[no] purge-originator", NO_STR "Use the RFC 6232 purge-originator\n") { VTY_DECLVAR_CONTEXT(isis_area, area); area->purge_originator = !!strcmp(argv[0]->text, "no"); return CMD_SUCCESS; } DEFUN (isis_passive, isis_passive_cmd, PROTO_NAME " passive", PROTO_HELP "Configure the passive mode for interface\n") { struct isis_circuit *circuit = isis_circuit_lookup(vty); if (!circuit) return CMD_ERR_NO_MATCH; CMD_FERR_RETURN(isis_circuit_passive_set(circuit, 1), "Cannot set passive: $ERR"); return CMD_SUCCESS; } DEFUN (no_isis_passive, no_isis_passive_cmd, "no " PROTO_NAME " passive", NO_STR PROTO_HELP "Configure the passive mode for interface\n") { struct isis_circuit *circuit = isis_circuit_lookup(vty); if (!circuit) return CMD_ERR_NO_MATCH; CMD_FERR_RETURN(isis_circuit_passive_set(circuit, 0), "Cannot set no passive: $ERR"); return CMD_SUCCESS; } DEFUN (isis_passwd, isis_passwd_cmd, PROTO_NAME " password WORD", PROTO_HELP "Configure the authentication password for a circuit\n" "HMAC-MD5 authentication\n" "Cleartext password\n" "Circuit password\n") { int idx_encryption = 2; int idx_word = 3; struct isis_circuit *circuit = isis_circuit_lookup(vty); ferr_r rv; if (!circuit) return CMD_ERR_NO_MATCH; if (argv[idx_encryption]->arg[0] == 'm') rv = isis_circuit_passwd_hmac_md5_set(circuit, argv[idx_word]->arg); else rv = isis_circuit_passwd_cleartext_set(circuit, argv[idx_word]->arg); CMD_FERR_RETURN(rv, "Failed to set circuit password: $ERR"); return CMD_SUCCESS; } DEFUN (no_isis_passwd, no_isis_passwd_cmd, "no " PROTO_NAME " password [ WORD]", NO_STR PROTO_HELP "Configure the authentication password for a circuit\n" "HMAC-MD5 authentication\n" "Cleartext password\n" "Circuit password\n") { struct isis_circuit *circuit = isis_circuit_lookup(vty); if (!circuit) return CMD_ERR_NO_MATCH; CMD_FERR_RETURN(isis_circuit_passwd_unset(circuit), "Failed to unset circuit password: $ERR"); return CMD_SUCCESS; } DEFUN (isis_metric, isis_metric_cmd, PROTO_NAME " metric (0-16777215)", PROTO_HELP "Set default metric for circuit\n" "Default metric value\n") { int idx_number = 2; int met; struct isis_circuit *circuit = isis_circuit_lookup(vty); if (!circuit) return CMD_ERR_NO_MATCH; met = atoi(argv[idx_number]->arg); /* RFC3787 section 5.1 */ if (circuit->area && circuit->area->oldmetric == 1 && met > MAX_NARROW_LINK_METRIC) { vty_out(vty, "Invalid metric %d - should be <0-63> " "when narrow metric type enabled\n", met); return CMD_WARNING_CONFIG_FAILED; } /* RFC4444 */ if (circuit->area && circuit->area->newmetric == 1 && met > MAX_WIDE_LINK_METRIC) { vty_out(vty, "Invalid metric %d - should be <0-16777215> " "when wide metric type enabled\n", met); return CMD_WARNING_CONFIG_FAILED; } CMD_FERR_RETURN(isis_circuit_metric_set(circuit, IS_LEVEL_1, met), "Failed to set L1 metric: $ERR"); CMD_FERR_RETURN(isis_circuit_metric_set(circuit, IS_LEVEL_2, met), "Failed to set L2 metric: $ERR"); return CMD_SUCCESS; } DEFUN (no_isis_metric, no_isis_metric_cmd, "no " PROTO_NAME " metric [(0-16777215)]", NO_STR PROTO_HELP "Set default metric for circuit\n" "Default metric value\n") { struct isis_circuit *circuit = isis_circuit_lookup(vty); if (!circuit) return CMD_ERR_NO_MATCH; CMD_FERR_RETURN(isis_circuit_metric_set(circuit, IS_LEVEL_1, DEFAULT_CIRCUIT_METRIC), "Failed to set L1 metric: $ERR"); CMD_FERR_RETURN(isis_circuit_metric_set(circuit, IS_LEVEL_2, DEFAULT_CIRCUIT_METRIC), "Failed to set L2 metric: $ERR"); return CMD_SUCCESS; } DEFUN (isis_hello_interval, isis_hello_interval_cmd, PROTO_NAME " hello-interval (1-600)", PROTO_HELP "Set Hello interval\n" "Holdtime 1 seconds, interval depends on multiplier\n") { uint32_t interval = atoi(argv[2]->arg); struct isis_circuit *circuit = isis_circuit_lookup(vty); if (!circuit) return CMD_ERR_NO_MATCH; circuit->hello_interval[0] = interval; circuit->hello_interval[1] = interval; return CMD_SUCCESS; } DEFUN (no_isis_hello_interval, no_isis_hello_interval_cmd, "no " PROTO_NAME " hello-interval [(1-600)]", NO_STR PROTO_HELP "Set Hello interval\n" "Holdtime 1 second, interval depends on multiplier\n") { struct isis_circuit *circuit = isis_circuit_lookup(vty); if (!circuit) return CMD_ERR_NO_MATCH; circuit->hello_interval[0] = DEFAULT_HELLO_INTERVAL; circuit->hello_interval[1] = DEFAULT_HELLO_INTERVAL; return CMD_SUCCESS; } DEFUN (isis_hello_multiplier, isis_hello_multiplier_cmd, PROTO_NAME " hello-multiplier (2-100)", PROTO_HELP "Set multiplier for Hello holding time\n" "Hello multiplier value\n") { uint16_t mult = atoi(argv[2]->arg); struct isis_circuit *circuit = isis_circuit_lookup(vty); if (!circuit) return CMD_ERR_NO_MATCH; circuit->hello_multiplier[0] = mult; circuit->hello_multiplier[1] = mult; return CMD_SUCCESS; } DEFUN (no_isis_hello_multiplier, no_isis_hello_multiplier_cmd, "no " PROTO_NAME " hello-multiplier [(2-100)]", NO_STR PROTO_HELP "Set multiplier for Hello holding time\n" "Hello multiplier value\n") { struct isis_circuit *circuit = isis_circuit_lookup(vty); if (!circuit) return CMD_ERR_NO_MATCH; circuit->hello_multiplier[0] = DEFAULT_HELLO_MULTIPLIER; circuit->hello_multiplier[1] = DEFAULT_HELLO_MULTIPLIER; return CMD_SUCCESS; } DEFUN (csnp_interval, csnp_interval_cmd, PROTO_NAME " csnp-interval (1-600)", PROTO_HELP "Set CSNP interval in seconds\n" "CSNP interval value\n") { uint16_t interval = atoi(argv[2]->arg); struct isis_circuit *circuit = isis_circuit_lookup(vty); if (!circuit) return CMD_ERR_NO_MATCH; circuit->csnp_interval[0] = interval; circuit->csnp_interval[1] = interval; return CMD_SUCCESS; } DEFUN (no_csnp_interval, no_csnp_interval_cmd, "no " PROTO_NAME " csnp-interval [(1-600)]", NO_STR PROTO_HELP "Set CSNP interval in seconds\n" "CSNP interval value\n") { struct isis_circuit *circuit = isis_circuit_lookup(vty); if (!circuit) return CMD_ERR_NO_MATCH; circuit->csnp_interval[0] = DEFAULT_CSNP_INTERVAL; circuit->csnp_interval[1] = DEFAULT_CSNP_INTERVAL; return CMD_SUCCESS; } DEFUN (psnp_interval, psnp_interval_cmd, PROTO_NAME " psnp-interval (1-120)", PROTO_HELP "Set PSNP interval in seconds\n" "PSNP interval value\n") { uint16_t interval = atoi(argv[2]->arg); struct isis_circuit *circuit = isis_circuit_lookup(vty); if (!circuit) return CMD_ERR_NO_MATCH; circuit->psnp_interval[0] = interval; circuit->psnp_interval[1] = interval; return CMD_SUCCESS; } DEFUN (no_psnp_interval, no_psnp_interval_cmd, "no " PROTO_NAME " psnp-interval [(1-120)]", NO_STR PROTO_HELP "Set PSNP interval in seconds\n" "PSNP interval value\n") { struct isis_circuit *circuit = isis_circuit_lookup(vty); if (!circuit) return CMD_ERR_NO_MATCH; circuit->psnp_interval[0] = DEFAULT_PSNP_INTERVAL; circuit->psnp_interval[1] = DEFAULT_PSNP_INTERVAL; return CMD_SUCCESS; } DEFUN (circuit_topology, circuit_topology_cmd, PROTO_NAME " topology " ISIS_MT_NAMES, PROTO_HELP "Configure interface IS-IS topologies\n" ISIS_MT_DESCRIPTIONS) { struct isis_circuit *circuit = isis_circuit_lookup(vty); if (!circuit) return CMD_ERR_NO_MATCH; const char *arg = argv[2]->arg; uint16_t mtid = isis_str2mtid(arg); if (circuit->area && circuit->area->oldmetric) { vty_out(vty, "Multi topology IS-IS can only be used with wide metrics\n"); return CMD_WARNING_CONFIG_FAILED; } if (mtid == (uint16_t)-1) { vty_out(vty, "Don't know topology '%s'\n", arg); return CMD_WARNING_CONFIG_FAILED; } return isis_circuit_mt_enabled_set(circuit, mtid, true); } DEFUN (no_circuit_topology, no_circuit_topology_cmd, "no " PROTO_NAME " topology " ISIS_MT_NAMES, NO_STR PROTO_HELP "Configure interface IS-IS topologies\n" ISIS_MT_DESCRIPTIONS) { struct isis_circuit *circuit = isis_circuit_lookup(vty); if (!circuit) return CMD_ERR_NO_MATCH; const char *arg = argv[3]->arg; uint16_t mtid = isis_str2mtid(arg); if (circuit->area && circuit->area->oldmetric) { vty_out(vty, "Multi topology IS-IS can only be used with wide metrics\n"); return CMD_WARNING_CONFIG_FAILED; } if (mtid == (uint16_t)-1) { vty_out(vty, "Don't know topology '%s'\n", arg); return CMD_WARNING_CONFIG_FAILED; } return isis_circuit_mt_enabled_set(circuit, mtid, false); } void isis_vty_daemon_init(void) { install_element(ROUTER_NODE, &fabric_tier_cmd); install_element(ROUTER_NODE, &no_fabric_tier_cmd); install_element(ROUTER_NODE, &triggered_csnp_cmd); install_element(ROUTER_NODE, &no_triggered_csnp_cmd); install_element(ENABLE_NODE, &show_lsp_flooding_cmd); install_element(INTERFACE_NODE, &ip_router_isis_cmd); install_element(INTERFACE_NODE, &ip6_router_isis_cmd); install_element(INTERFACE_NODE, &no_ip_router_isis_cmd); install_element(INTERFACE_NODE, &isis_bfd_cmd); install_element(INTERFACE_NODE, &no_isis_bfd_cmd); install_element(ROUTER_NODE, &set_overload_bit_cmd); install_element(ROUTER_NODE, &no_set_overload_bit_cmd); install_element(ROUTER_NODE, &domain_passwd_cmd); install_element(ROUTER_NODE, &no_domain_passwd_cmd); install_element(ROUTER_NODE, &lsp_gen_interval_cmd); install_element(ROUTER_NODE, &no_lsp_gen_interval_cmd); install_element(ROUTER_NODE, &lsp_refresh_interval_cmd); install_element(ROUTER_NODE, &no_lsp_refresh_interval_cmd); install_element(ROUTER_NODE, &max_lsp_lifetime_cmd); install_element(ROUTER_NODE, &no_max_lsp_lifetime_cmd); install_element(ROUTER_NODE, &area_lsp_mtu_cmd); install_element(ROUTER_NODE, &no_area_lsp_mtu_cmd); install_element(ROUTER_NODE, &spf_interval_cmd); install_element(ROUTER_NODE, &no_spf_interval_cmd); install_element(ROUTER_NODE, &spf_delay_ietf_cmd); install_element(ROUTER_NODE, &no_spf_delay_ietf_cmd); install_element(ROUTER_NODE, &area_purge_originator_cmd); install_element(INTERFACE_NODE, &isis_passive_cmd); install_element(INTERFACE_NODE, &no_isis_passive_cmd); install_element(INTERFACE_NODE, &isis_passwd_cmd); install_element(INTERFACE_NODE, &no_isis_passwd_cmd); install_element(INTERFACE_NODE, &isis_metric_cmd); install_element(INTERFACE_NODE, &no_isis_metric_cmd); install_element(INTERFACE_NODE, &isis_hello_interval_cmd); install_element(INTERFACE_NODE, &no_isis_hello_interval_cmd); install_element(INTERFACE_NODE, &isis_hello_multiplier_cmd); install_element(INTERFACE_NODE, &no_isis_hello_multiplier_cmd); install_element(INTERFACE_NODE, &csnp_interval_cmd); install_element(INTERFACE_NODE, &no_csnp_interval_cmd); install_element(INTERFACE_NODE, &psnp_interval_cmd); install_element(INTERFACE_NODE, &no_psnp_interval_cmd); install_element(INTERFACE_NODE, &circuit_topology_cmd); install_element(INTERFACE_NODE, &no_circuit_topology_cmd); } frr-7.2.1/isisd/isis_zebra.c0000644000000000000000000002462513610377563012636 00000000000000/* * IS-IS Rout(e)ing protocol - isis_zebra.c * * Copyright (C) 2001,2002 Sampo Saaristo * Tampere University of Technology * Institute of Communications Engineering * Copyright (C) 2013-2015 Christian Franke * * This program is free software; you can redistribute it and/or modify it * under the terms of the GNU General Public Licenseas 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; see the file COPYING; if not, write to the Free Software * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA */ #include #include "thread.h" #include "command.h" #include "memory.h" #include "log.h" #include "lib_errors.h" #include "if.h" #include "network.h" #include "prefix.h" #include "zclient.h" #include "stream.h" #include "linklist.h" #include "nexthop.h" #include "vrf.h" #include "libfrr.h" #include "isisd/isis_constants.h" #include "isisd/isis_common.h" #include "isisd/isis_flags.h" #include "isisd/isis_misc.h" #include "isisd/isis_circuit.h" #include "isisd/isisd.h" #include "isisd/isis_circuit.h" #include "isisd/isis_csm.h" #include "isisd/isis_lsp.h" #include "isisd/isis_route.h" #include "isisd/isis_zebra.h" #include "isisd/isis_te.h" struct zclient *zclient = NULL; /* Router-id update message from zebra. */ static int isis_router_id_update_zebra(ZAPI_CALLBACK_ARGS) { struct isis_area *area; struct listnode *node; struct prefix router_id; zebra_router_id_update_read(zclient->ibuf, &router_id); if (isis->router_id == router_id.u.prefix4.s_addr) return 0; isis->router_id = router_id.u.prefix4.s_addr; for (ALL_LIST_ELEMENTS_RO(isis->area_list, node, area)) if (listcount(area->area_addrs) > 0) lsp_regenerate_schedule(area, area->is_type, 0); return 0; } static int isis_zebra_if_add(ZAPI_CALLBACK_ARGS) { struct interface *ifp; ifp = zebra_interface_add_read(zclient->ibuf, vrf_id); if (if_is_operative(ifp)) isis_csm_state_change(IF_UP_FROM_Z, circuit_scan_by_ifp(ifp), ifp); return 0; } static int isis_zebra_if_del(ZAPI_CALLBACK_ARGS) { struct interface *ifp; struct stream *s; s = zclient->ibuf; ifp = zebra_interface_state_read(s, vrf_id); if (!ifp) return 0; if (if_is_operative(ifp)) zlog_warn("Zebra: got delete of %s, but interface is still up", ifp->name); isis_csm_state_change(IF_DOWN_FROM_Z, circuit_scan_by_ifp(ifp), ifp); /* Cannot call if_delete because we should retain the pseudo interface in case there is configuration info attached to it. */ if_delete_retain(ifp); if_set_index(ifp, IFINDEX_INTERNAL); return 0; } static int isis_zebra_if_state_up(ZAPI_CALLBACK_ARGS) { struct interface *ifp; ifp = zebra_interface_state_read(zclient->ibuf, vrf_id); if (ifp == NULL) return 0; isis_csm_state_change(IF_UP_FROM_Z, circuit_scan_by_ifp(ifp), ifp); return 0; } static int isis_zebra_if_state_down(ZAPI_CALLBACK_ARGS) { struct interface *ifp; struct isis_circuit *circuit; ifp = zebra_interface_state_read(zclient->ibuf, vrf_id); if (ifp == NULL) return 0; circuit = isis_csm_state_change(IF_DOWN_FROM_Z, circuit_scan_by_ifp(ifp), ifp); if (circuit) SET_FLAG(circuit->flags, ISIS_CIRCUIT_FLAPPED_AFTER_SPF); return 0; } static int isis_zebra_if_address_add(ZAPI_CALLBACK_ARGS) { struct connected *c; struct prefix *p; char buf[PREFIX2STR_BUFFER]; c = zebra_interface_address_read(ZEBRA_INTERFACE_ADDRESS_ADD, zclient->ibuf, vrf_id); if (c == NULL) return 0; p = c->address; prefix2str(p, buf, sizeof(buf)); #ifdef EXTREME_DEBUG if (p->family == AF_INET) zlog_debug("connected IP address %s", buf); if (p->family == AF_INET6) zlog_debug("connected IPv6 address %s", buf); #endif /* EXTREME_DEBUG */ if (if_is_operative(c->ifp)) isis_circuit_add_addr(circuit_scan_by_ifp(c->ifp), c); return 0; } static int isis_zebra_if_address_del(ZAPI_CALLBACK_ARGS) { struct connected *c; struct interface *ifp; #ifdef EXTREME_DEBUG struct prefix *p; char buf[PREFIX2STR_BUFFER]; #endif /* EXTREME_DEBUG */ c = zebra_interface_address_read(ZEBRA_INTERFACE_ADDRESS_DELETE, zclient->ibuf, vrf_id); if (c == NULL) return 0; ifp = c->ifp; #ifdef EXTREME_DEBUG p = c->address; prefix2str(p, buf, sizeof(buf)); if (p->family == AF_INET) zlog_debug("disconnected IP address %s", buf); if (p->family == AF_INET6) zlog_debug("disconnected IPv6 address %s", buf); #endif /* EXTREME_DEBUG */ if (if_is_operative(ifp)) isis_circuit_del_addr(circuit_scan_by_ifp(ifp), c); connected_free(c); return 0; } static int isis_zebra_link_params(ZAPI_CALLBACK_ARGS) { struct interface *ifp; ifp = zebra_interface_link_params_read(zclient->ibuf, vrf_id); if (ifp == NULL) return 0; /* Update TE TLV */ isis_mpls_te_update(ifp); return 0; } static void isis_zebra_route_add_route(struct prefix *prefix, struct prefix_ipv6 *src_p, struct isis_route_info *route_info) { struct zapi_route api; struct zapi_nexthop *api_nh; struct isis_nexthop *nexthop; struct listnode *node; int count = 0; if (CHECK_FLAG(route_info->flag, ISIS_ROUTE_FLAG_ZEBRA_SYNCED)) return; memset(&api, 0, sizeof(api)); api.vrf_id = VRF_DEFAULT; api.type = PROTO_TYPE; api.safi = SAFI_UNICAST; api.prefix = *prefix; if (src_p && src_p->prefixlen) { api.src_prefix = *src_p; SET_FLAG(api.message, ZAPI_MESSAGE_SRCPFX); } SET_FLAG(api.message, ZAPI_MESSAGE_NEXTHOP); SET_FLAG(api.message, ZAPI_MESSAGE_METRIC); api.metric = route_info->cost; #if 0 SET_FLAG(api.message, ZAPI_MESSAGE_DISTANCE); api.distance = route_info->depth; #endif /* Nexthops */ for (ALL_LIST_ELEMENTS_RO(route_info->nexthops, node, nexthop)) { if (count >= MULTIPATH_NUM) break; api_nh = &api.nexthops[count]; if (fabricd) api_nh->onlink = true; api_nh->vrf_id = VRF_DEFAULT; switch (nexthop->family) { case AF_INET: /* FIXME: can it be ? */ if (nexthop->ip.ipv4.s_addr != INADDR_ANY) { api_nh->type = NEXTHOP_TYPE_IPV4_IFINDEX; api_nh->gate.ipv4 = nexthop->ip.ipv4; } else { api_nh->type = NEXTHOP_TYPE_IFINDEX; } break; case AF_INET6: if (!IN6_IS_ADDR_LINKLOCAL(&nexthop->ip.ipv6) && !IN6_IS_ADDR_UNSPECIFIED(&nexthop->ip.ipv6)) { continue; } api_nh->gate.ipv6 = nexthop->ip.ipv6; api_nh->type = NEXTHOP_TYPE_IPV6_IFINDEX; break; default: flog_err(EC_LIB_DEVELOPMENT, "%s: unknown address family [%d]", __func__, nexthop->family); exit(1); } api_nh->ifindex = nexthop->ifindex; count++; } if (!count) return; api.nexthop_num = count; zclient_route_send(ZEBRA_ROUTE_ADD, zclient, &api); SET_FLAG(route_info->flag, ISIS_ROUTE_FLAG_ZEBRA_SYNCED); UNSET_FLAG(route_info->flag, ISIS_ROUTE_FLAG_ZEBRA_RESYNC); } static void isis_zebra_route_del_route(struct prefix *prefix, struct prefix_ipv6 *src_p, struct isis_route_info *route_info) { struct zapi_route api; if (!CHECK_FLAG(route_info->flag, ISIS_ROUTE_FLAG_ZEBRA_SYNCED)) return; memset(&api, 0, sizeof(api)); api.vrf_id = VRF_DEFAULT; api.type = PROTO_TYPE; api.safi = SAFI_UNICAST; api.prefix = *prefix; if (src_p && src_p->prefixlen) { api.src_prefix = *src_p; SET_FLAG(api.message, ZAPI_MESSAGE_SRCPFX); } zclient_route_send(ZEBRA_ROUTE_DELETE, zclient, &api); UNSET_FLAG(route_info->flag, ISIS_ROUTE_FLAG_ZEBRA_SYNCED); } void isis_zebra_route_update(struct prefix *prefix, struct prefix_ipv6 *src_p, struct isis_route_info *route_info) { if (zclient->sock < 0) return; if (CHECK_FLAG(route_info->flag, ISIS_ROUTE_FLAG_ACTIVE)) isis_zebra_route_add_route(prefix, src_p, route_info); else isis_zebra_route_del_route(prefix, src_p, route_info); } static int isis_zebra_read(ZAPI_CALLBACK_ARGS) { struct zapi_route api; if (zapi_route_decode(zclient->ibuf, &api) < 0) return -1; if (api.prefix.family == AF_INET6 && IN6_IS_ADDR_LINKLOCAL(&api.prefix.u.prefix6)) return 0; /* * Avoid advertising a false default reachability. (A default * route installed by IS-IS gets redistributed from zebra back * into IS-IS causing us to start advertising default reachabity * without this check) */ if (api.prefix.prefixlen == 0 && api.src_prefix.prefixlen == 0 && api.type == PROTO_TYPE) { cmd = ZEBRA_REDISTRIBUTE_ROUTE_DEL; } if (cmd == ZEBRA_REDISTRIBUTE_ROUTE_ADD) isis_redist_add(api.type, &api.prefix, &api.src_prefix, api.distance, api.metric); else isis_redist_delete(api.type, &api.prefix, &api.src_prefix); return 0; } int isis_distribute_list_update(int routetype) { return 0; } void isis_zebra_redistribute_set(afi_t afi, int type) { if (type == DEFAULT_ROUTE) zclient_redistribute_default(ZEBRA_REDISTRIBUTE_DEFAULT_ADD, zclient, afi, VRF_DEFAULT); else zclient_redistribute(ZEBRA_REDISTRIBUTE_ADD, zclient, afi, type, 0, VRF_DEFAULT); } void isis_zebra_redistribute_unset(afi_t afi, int type) { if (type == DEFAULT_ROUTE) zclient_redistribute_default(ZEBRA_REDISTRIBUTE_DEFAULT_DELETE, zclient, afi, VRF_DEFAULT); else zclient_redistribute(ZEBRA_REDISTRIBUTE_DELETE, zclient, afi, type, 0, VRF_DEFAULT); } static void isis_zebra_connected(struct zclient *zclient) { zclient_send_reg_requests(zclient, VRF_DEFAULT); } void isis_zebra_init(struct thread_master *master) { zclient = zclient_new(master, &zclient_options_default); zclient_init(zclient, PROTO_TYPE, 0, &isisd_privs); zclient->zebra_connected = isis_zebra_connected; zclient->router_id_update = isis_router_id_update_zebra; zclient->interface_add = isis_zebra_if_add; zclient->interface_delete = isis_zebra_if_del; zclient->interface_up = isis_zebra_if_state_up; zclient->interface_down = isis_zebra_if_state_down; zclient->interface_address_add = isis_zebra_if_address_add; zclient->interface_address_delete = isis_zebra_if_address_del; zclient->interface_link_params = isis_zebra_link_params; zclient->redistribute_route_add = isis_zebra_read; zclient->redistribute_route_del = isis_zebra_read; return; } void isis_zebra_stop(void) { zclient_stop(zclient); zclient_free(zclient); frr_fini(); } frr-7.2.1/isisd/isis_zebra.h0000644000000000000000000000271013610377563012632 00000000000000/* * IS-IS Rout(e)ing protocol - isis_zebra.h * * Copyright (C) 2001,2002 Sampo Saaristo * Tampere University of Technology * Institute of Communications Engineering * * This program is free software; you can redistribute it and/or modify it * under the terms of the GNU General Public Licenseas 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; see the file COPYING; if not, write to the Free Software * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA */ #ifndef _ZEBRA_ISIS_ZEBRA_H #define _ZEBRA_ISIS_ZEBRA_H extern struct zclient *zclient; void isis_zebra_init(struct thread_master *); void isis_zebra_stop(void); struct isis_route_info; void isis_zebra_route_update(struct prefix *prefix, struct prefix_ipv6 *src_p, struct isis_route_info *route_info); int isis_distribute_list_update(int routetype); void isis_zebra_redistribute_set(afi_t afi, int type); void isis_zebra_redistribute_unset(afi_t afi, int type); #endif /* _ZEBRA_ISIS_ZEBRA_H */ frr-7.2.1/isisd/isisd.c0000644000000000000000000016177513610377563011627 00000000000000/* * IS-IS Rout(e)ing protocol - isisd.c * * Copyright (C) 2001,2002 Sampo Saaristo * Tampere University of Technology * Institute of Communications Engineering * * This program is free software; you can redistribute it and/or modify it * under the terms of the GNU General Public Licenseas 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; see the file COPYING; if not, write to the Free Software * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA */ #include #include "thread.h" #include "vty.h" #include "command.h" #include "log.h" #include "memory.h" #include "time.h" #include "linklist.h" #include "if.h" #include "hash.h" #include "stream.h" #include "prefix.h" #include "table.h" #include "qobj.h" #include "spf_backoff.h" #include "lib/northbound_cli.h" #include "isisd/isis_constants.h" #include "isisd/isis_common.h" #include "isisd/isis_flags.h" #include "isisd/isis_circuit.h" #include "isisd/isis_csm.h" #include "isisd/isisd.h" #include "isisd/isis_dynhn.h" #include "isisd/isis_adjacency.h" #include "isisd/isis_pdu.h" #include "isisd/isis_misc.h" #include "isisd/isis_constants.h" #include "isisd/isis_lsp.h" #include "isisd/isis_spf.h" #include "isisd/isis_route.h" #include "isisd/isis_zebra.h" #include "isisd/isis_events.h" #include "isisd/isis_te.h" #include "isisd/isis_mt.h" #include "isisd/fabricd.h" struct isis *isis = NULL; DEFINE_QOBJ_TYPE(isis) DEFINE_QOBJ_TYPE(isis_area) /* * Prototypes. */ int isis_area_get(struct vty *, const char *); int area_net_title(struct vty *, const char *); int area_clear_net_title(struct vty *, const char *); int show_isis_interface_common(struct vty *, const char *ifname, char); int show_isis_neighbor_common(struct vty *, const char *id, char); int clear_isis_neighbor_common(struct vty *, const char *id); int isis_config_write(struct vty *); void isis_new(unsigned long process_id) { isis = XCALLOC(MTYPE_ISIS, sizeof(struct isis)); /* * Default values */ isis->max_area_addrs = 3; isis->process_id = process_id; isis->router_id = 0; isis->area_list = list_new(); isis->init_circ_list = list_new(); isis->uptime = time(NULL); isis->nexthops = list_new(); dyn_cache_init(); /* * uncomment the next line for full debugs */ /* isis->debugs = 0xFFFF; */ QOBJ_REG(isis, isis); } struct isis_area *isis_area_create(const char *area_tag) { struct isis_area *area; area = XCALLOC(MTYPE_ISIS_AREA, sizeof(struct isis_area)); /* * Fabricd runs only as level-2. * For IS-IS, the first instance is level-1-2 rest are level-1, * unless otherwise configured */ if (fabricd) { area->is_type = IS_LEVEL_2; } else if (listcount(isis->area_list) == 0) area->is_type = IS_LEVEL_1_AND_2; else area->is_type = yang_get_default_enum( "/frr-isisd:isis/instance/is-type"); /* * intialize the databases */ if (area->is_type & IS_LEVEL_1) lsp_db_init(&area->lspdb[0]); if (area->is_type & IS_LEVEL_2) lsp_db_init(&area->lspdb[1]); spftree_area_init(area); area->circuit_list = list_new(); area->area_addrs = list_new(); thread_add_timer(master, lsp_tick, area, 1, &area->t_tick); flags_initialize(&area->flags); /* * Default values */ #ifndef FABRICD enum isis_metric_style default_style; area->max_lsp_lifetime[0] = yang_get_default_uint16( "/frr-isisd:isis/instance/lsp/maximum-lifetime/level-1"); area->max_lsp_lifetime[1] = yang_get_default_uint16( "/frr-isisd:isis/instance/lsp/maximum-lifetime/level-2"); area->lsp_refresh[0] = yang_get_default_uint16( "/frr-isisd:isis/instance/lsp/refresh-interval/level-1"); area->lsp_refresh[1] = yang_get_default_uint16( "/frr-isisd:isis/instance/lsp/refresh-interval/level-2"); area->lsp_gen_interval[0] = yang_get_default_uint16( "/frr-isisd:isis/instance/lsp/generation-interval/level-1"); area->lsp_gen_interval[1] = yang_get_default_uint16( "/frr-isisd:isis/instance/lsp/generation-interval/level-2"); area->min_spf_interval[0] = yang_get_default_uint16( "/frr-isisd:isis/instance/spf/minimum-interval/level-1"); area->min_spf_interval[1] = yang_get_default_uint16( "/frr-isisd:isis/instance/spf/minimum-interval/level-1"); area->dynhostname = yang_get_default_bool( "/frr-isisd:isis/instance/dynamic-hostname"); default_style = yang_get_default_enum("/frr-isisd:isis/instance/metric-style"); area->oldmetric = default_style == ISIS_WIDE_METRIC ? 0 : 1; area->newmetric = default_style == ISIS_NARROW_METRIC ? 0 : 1; area->lsp_frag_threshold = 90; /* not currently configurable */ area->lsp_mtu = yang_get_default_uint16("/frr-isisd:isis/instance/lsp/mtu"); #else area->max_lsp_lifetime[0] = DEFAULT_LSP_LIFETIME; /* 1200 */ area->max_lsp_lifetime[1] = DEFAULT_LSP_LIFETIME; /* 1200 */ area->lsp_refresh[0] = DEFAULT_MAX_LSP_GEN_INTERVAL; /* 900 */ area->lsp_refresh[1] = DEFAULT_MAX_LSP_GEN_INTERVAL; /* 900 */ area->lsp_gen_interval[0] = DEFAULT_MIN_LSP_GEN_INTERVAL; area->lsp_gen_interval[1] = DEFAULT_MIN_LSP_GEN_INTERVAL; area->min_spf_interval[0] = MINIMUM_SPF_INTERVAL; area->min_spf_interval[1] = MINIMUM_SPF_INTERVAL; area->dynhostname = 1; area->oldmetric = 0; area->newmetric = 1; area->lsp_frag_threshold = 90; area->lsp_mtu = DEFAULT_LSP_MTU; #endif /* ifndef FABRICD */ area_mt_init(area); area->area_tag = strdup(area_tag); listnode_add(isis->area_list, area); area->isis = isis; if (fabricd) area->fabricd = fabricd_new(area); area->lsp_refresh_arg[0].area = area; area->lsp_refresh_arg[0].level = IS_LEVEL_1; area->lsp_refresh_arg[1].area = area; area->lsp_refresh_arg[1].level = IS_LEVEL_2; QOBJ_REG(area, isis_area); return area; } struct isis_area *isis_area_lookup(const char *area_tag) { struct isis_area *area; struct listnode *node; for (ALL_LIST_ELEMENTS_RO(isis->area_list, node, area)) if ((area->area_tag == NULL && area_tag == NULL) || (area->area_tag && area_tag && strcmp(area->area_tag, area_tag) == 0)) return area; return NULL; } int isis_area_get(struct vty *vty, const char *area_tag) { struct isis_area *area; area = isis_area_lookup(area_tag); if (area) { VTY_PUSH_CONTEXT(ROUTER_NODE, area); return CMD_SUCCESS; } area = isis_area_create(area_tag); if (isis->debugs & DEBUG_EVENTS) zlog_debug("New IS-IS area instance %s", area->area_tag); VTY_PUSH_CONTEXT(ROUTER_NODE, area); return CMD_SUCCESS; } int isis_area_destroy(const char *area_tag) { struct isis_area *area; struct listnode *node, *nnode; struct isis_circuit *circuit; struct area_addr *addr; area = isis_area_lookup(area_tag); if (area == NULL) { zlog_warn("%s: could not find area with area-tag %s", __func__, area_tag); return CMD_ERR_NO_MATCH; } QOBJ_UNREG(area); if (fabricd) fabricd_finish(area->fabricd); /* Disable MPLS if necessary before flooding LSP */ if (IS_MPLS_TE(area->mta)) area->mta->status = disable; if (area->circuit_list) { for (ALL_LIST_ELEMENTS(area->circuit_list, node, nnode, circuit)) { circuit->ip_router = 0; circuit->ipv6_router = 0; isis_csm_state_change(ISIS_DISABLE, circuit, area); } list_delete(&area->circuit_list); } lsp_db_fini(&area->lspdb[0]); lsp_db_fini(&area->lspdb[1]); /* invalidate and verify to delete all routes from zebra */ isis_area_invalidate_routes(area, area->is_type); isis_area_verify_routes(area); spftree_area_del(area); THREAD_TIMER_OFF(area->spf_timer[0]); THREAD_TIMER_OFF(area->spf_timer[1]); spf_backoff_free(area->spf_delay_ietf[0]); spf_backoff_free(area->spf_delay_ietf[1]); isis_redist_area_finish(area); for (ALL_LIST_ELEMENTS(area->area_addrs, node, nnode, addr)) { list_delete_node(area->area_addrs, node); XFREE(MTYPE_ISIS_AREA_ADDR, addr); } area->area_addrs = NULL; THREAD_TIMER_OFF(area->t_tick); THREAD_TIMER_OFF(area->t_lsp_refresh[0]); THREAD_TIMER_OFF(area->t_lsp_refresh[1]); thread_cancel_event(master, area); listnode_delete(isis->area_list, area); free(area->area_tag); area_mt_finish(area); XFREE(MTYPE_ISIS_AREA, area); if (listcount(isis->area_list) == 0) { memset(isis->sysid, 0, ISIS_SYS_ID_LEN); isis->sysid_set = 0; } return CMD_SUCCESS; } #ifdef FABRICD static void area_set_mt_enabled(struct isis_area *area, uint16_t mtid, bool enabled) { struct isis_area_mt_setting *setting; setting = area_get_mt_setting(area, mtid); if (setting->enabled != enabled) { setting->enabled = enabled; lsp_regenerate_schedule(area, IS_LEVEL_1 | IS_LEVEL_2, 0); } } static void area_set_mt_overload(struct isis_area *area, uint16_t mtid, bool overload) { struct isis_area_mt_setting *setting; setting = area_get_mt_setting(area, mtid); if (setting->overload != overload) { setting->overload = overload; if (setting->enabled) lsp_regenerate_schedule(area, IS_LEVEL_1 | IS_LEVEL_2, 0); } } #endif /* ifdef FABRICD */ int area_net_title(struct vty *vty, const char *net_title) { VTY_DECLVAR_CONTEXT(isis_area, area); struct area_addr *addr; struct area_addr *addrp; struct listnode *node; uint8_t buff[255]; /* We check that we are not over the maximal number of addresses */ if (listcount(area->area_addrs) >= isis->max_area_addrs) { vty_out(vty, "Maximum of area addresses (%d) already reached \n", isis->max_area_addrs); return CMD_ERR_NOTHING_TODO; } addr = XMALLOC(MTYPE_ISIS_AREA_ADDR, sizeof(struct area_addr)); addr->addr_len = dotformat2buff(buff, net_title); memcpy(addr->area_addr, buff, addr->addr_len); #ifdef EXTREME_DEBUG zlog_debug("added area address %s for area %s (address length %d)", net_title, area->area_tag, addr->addr_len); #endif /* EXTREME_DEBUG */ if (addr->addr_len < 8 || addr->addr_len > 20) { vty_out(vty, "area address must be at least 8..20 octets long (%d)\n", addr->addr_len); XFREE(MTYPE_ISIS_AREA_ADDR, addr); return CMD_WARNING_CONFIG_FAILED; } if (addr->area_addr[addr->addr_len - 1] != 0) { vty_out(vty, "nsel byte (last byte) in area address must be 0\n"); XFREE(MTYPE_ISIS_AREA_ADDR, addr); return CMD_WARNING_CONFIG_FAILED; } if (isis->sysid_set == 0) { /* * First area address - get the SystemID for this router */ memcpy(isis->sysid, GETSYSID(addr), ISIS_SYS_ID_LEN); isis->sysid_set = 1; if (isis->debugs & DEBUG_EVENTS) zlog_debug("Router has SystemID %s", sysid_print(isis->sysid)); } else { /* * Check that the SystemID portions match */ if (memcmp(isis->sysid, GETSYSID(addr), ISIS_SYS_ID_LEN)) { vty_out(vty, "System ID must not change when defining additional area addresses\n"); XFREE(MTYPE_ISIS_AREA_ADDR, addr); return CMD_WARNING_CONFIG_FAILED; } /* now we see that we don't already have this address */ for (ALL_LIST_ELEMENTS_RO(area->area_addrs, node, addrp)) { if ((addrp->addr_len + ISIS_SYS_ID_LEN + ISIS_NSEL_LEN) != (addr->addr_len)) continue; if (!memcmp(addrp->area_addr, addr->area_addr, addr->addr_len)) { XFREE(MTYPE_ISIS_AREA_ADDR, addr); return CMD_SUCCESS; /* silent fail */ } } } /* * Forget the systemID part of the address */ addr->addr_len -= (ISIS_SYS_ID_LEN + ISIS_NSEL_LEN); listnode_add(area->area_addrs, addr); /* only now we can safely generate our LSPs for this area */ if (listcount(area->area_addrs) > 0) { if (area->is_type & IS_LEVEL_1) lsp_generate(area, IS_LEVEL_1); if (area->is_type & IS_LEVEL_2) lsp_generate(area, IS_LEVEL_2); } return CMD_SUCCESS; } int area_clear_net_title(struct vty *vty, const char *net_title) { VTY_DECLVAR_CONTEXT(isis_area, area); struct area_addr addr, *addrp = NULL; struct listnode *node; uint8_t buff[255]; addr.addr_len = dotformat2buff(buff, net_title); if (addr.addr_len < 8 || addr.addr_len > 20) { vty_out(vty, "Unsupported area address length %d, should be 8...20 \n", addr.addr_len); return CMD_WARNING_CONFIG_FAILED; } memcpy(addr.area_addr, buff, (int)addr.addr_len); for (ALL_LIST_ELEMENTS_RO(area->area_addrs, node, addrp)) if ((addrp->addr_len + ISIS_SYS_ID_LEN + 1) == addr.addr_len && !memcmp(addrp->area_addr, addr.area_addr, addr.addr_len)) break; if (!addrp) { vty_out(vty, "No area address %s for area %s \n", net_title, area->area_tag); return CMD_ERR_NO_MATCH; } listnode_delete(area->area_addrs, addrp); XFREE(MTYPE_ISIS_AREA_ADDR, addrp); /* * Last area address - reset the SystemID for this router */ if (listcount(area->area_addrs) == 0) { memset(isis->sysid, 0, ISIS_SYS_ID_LEN); isis->sysid_set = 0; if (isis->debugs & DEBUG_EVENTS) zlog_debug("Router has no SystemID"); } return CMD_SUCCESS; } /* * 'show isis interface' command */ int show_isis_interface_common(struct vty *vty, const char *ifname, char detail) { struct listnode *anode, *cnode; struct isis_area *area; struct isis_circuit *circuit; if (!isis) { vty_out(vty, "IS-IS Routing Process not enabled\n"); return CMD_SUCCESS; } for (ALL_LIST_ELEMENTS_RO(isis->area_list, anode, area)) { vty_out(vty, "Area %s:\n", area->area_tag); if (detail == ISIS_UI_LEVEL_BRIEF) vty_out(vty, " Interface CircId State Type Level\n"); for (ALL_LIST_ELEMENTS_RO(area->circuit_list, cnode, circuit)) if (!ifname) isis_circuit_print_vty(circuit, vty, detail); else if (strcmp(circuit->interface->name, ifname) == 0) isis_circuit_print_vty(circuit, vty, detail); } return CMD_SUCCESS; } DEFUN (show_isis_interface, show_isis_interface_cmd, "show " PROTO_NAME " interface", SHOW_STR PROTO_HELP "ISIS interface\n") { return show_isis_interface_common(vty, NULL, ISIS_UI_LEVEL_BRIEF); } DEFUN (show_isis_interface_detail, show_isis_interface_detail_cmd, "show " PROTO_NAME " interface detail", SHOW_STR PROTO_HELP "ISIS interface\n" "show detailed information\n") { return show_isis_interface_common(vty, NULL, ISIS_UI_LEVEL_DETAIL); } DEFUN (show_isis_interface_arg, show_isis_interface_arg_cmd, "show " PROTO_NAME " interface WORD", SHOW_STR PROTO_HELP "ISIS interface\n" "ISIS interface name\n") { int idx_word = 3; return show_isis_interface_common(vty, argv[idx_word]->arg, ISIS_UI_LEVEL_DETAIL); } /* * 'show isis neighbor' command */ int show_isis_neighbor_common(struct vty *vty, const char *id, char detail) { struct listnode *anode, *cnode, *node; struct isis_area *area; struct isis_circuit *circuit; struct list *adjdb; struct isis_adjacency *adj; struct isis_dynhn *dynhn; uint8_t sysid[ISIS_SYS_ID_LEN]; int i; if (!isis) { vty_out(vty, "IS-IS Routing Process not enabled\n"); return CMD_SUCCESS; } memset(sysid, 0, ISIS_SYS_ID_LEN); if (id) { if (sysid2buff(sysid, id) == 0) { dynhn = dynhn_find_by_name(id); if (dynhn == NULL) { vty_out(vty, "Invalid system id %s\n", id); return CMD_SUCCESS; } memcpy(sysid, dynhn->id, ISIS_SYS_ID_LEN); } } for (ALL_LIST_ELEMENTS_RO(isis->area_list, anode, area)) { vty_out(vty, "Area %s:\n", area->area_tag); if (detail == ISIS_UI_LEVEL_BRIEF) vty_out(vty, " System Id Interface L State Holdtime SNPA\n"); for (ALL_LIST_ELEMENTS_RO(area->circuit_list, cnode, circuit)) { if (circuit->circ_type == CIRCUIT_T_BROADCAST) { for (i = 0; i < 2; i++) { adjdb = circuit->u.bc.adjdb[i]; if (adjdb && adjdb->count) { for (ALL_LIST_ELEMENTS_RO( adjdb, node, adj)) if (!id || !memcmp(adj->sysid, sysid, ISIS_SYS_ID_LEN)) isis_adj_print_vty( adj, vty, detail); } } } else if (circuit->circ_type == CIRCUIT_T_P2P && circuit->u.p2p.neighbor) { adj = circuit->u.p2p.neighbor; if (!id || !memcmp(adj->sysid, sysid, ISIS_SYS_ID_LEN)) isis_adj_print_vty(adj, vty, detail); } } } return CMD_SUCCESS; } /* * 'clear isis neighbor' command */ int clear_isis_neighbor_common(struct vty *vty, const char *id) { struct listnode *anode, *cnode, *cnextnode, *node, *nnode; struct isis_area *area; struct isis_circuit *circuit; struct list *adjdb; struct isis_adjacency *adj; struct isis_dynhn *dynhn; uint8_t sysid[ISIS_SYS_ID_LEN]; int i; if (!isis) { vty_out(vty, "IS-IS Routing Process not enabled\n"); return CMD_SUCCESS; } memset(sysid, 0, ISIS_SYS_ID_LEN); if (id) { if (sysid2buff(sysid, id) == 0) { dynhn = dynhn_find_by_name(id); if (dynhn == NULL) { vty_out(vty, "Invalid system id %s\n", id); return CMD_SUCCESS; } memcpy(sysid, dynhn->id, ISIS_SYS_ID_LEN); } } for (ALL_LIST_ELEMENTS_RO(isis->area_list, anode, area)) { for (ALL_LIST_ELEMENTS(area->circuit_list, cnode, cnextnode, circuit)) { if (circuit->circ_type == CIRCUIT_T_BROADCAST) { for (i = 0; i < 2; i++) { adjdb = circuit->u.bc.adjdb[i]; if (adjdb && adjdb->count) { for (ALL_LIST_ELEMENTS( adjdb, node, nnode, adj)) if (!id || !memcmp(adj->sysid, sysid, ISIS_SYS_ID_LEN)) isis_adj_state_change( adj, ISIS_ADJ_DOWN, "clear user request"); } } } else if (circuit->circ_type == CIRCUIT_T_P2P && circuit->u.p2p.neighbor) { adj = circuit->u.p2p.neighbor; if (!id || !memcmp(adj->sysid, sysid, ISIS_SYS_ID_LEN)) isis_adj_state_change( adj, ISIS_ADJ_DOWN, "clear user request"); } } } return CMD_SUCCESS; } DEFUN (show_isis_neighbor, show_isis_neighbor_cmd, "show " PROTO_NAME " neighbor", SHOW_STR PROTO_HELP "ISIS neighbor adjacencies\n") { return show_isis_neighbor_common(vty, NULL, ISIS_UI_LEVEL_BRIEF); } DEFUN (show_isis_neighbor_detail, show_isis_neighbor_detail_cmd, "show " PROTO_NAME " neighbor detail", SHOW_STR PROTO_HELP "ISIS neighbor adjacencies\n" "show detailed information\n") { return show_isis_neighbor_common(vty, NULL, ISIS_UI_LEVEL_DETAIL); } DEFUN (show_isis_neighbor_arg, show_isis_neighbor_arg_cmd, "show " PROTO_NAME " neighbor WORD", SHOW_STR PROTO_HELP "ISIS neighbor adjacencies\n" "System id\n") { int idx_word = 3; return show_isis_neighbor_common(vty, argv[idx_word]->arg, ISIS_UI_LEVEL_DETAIL); } DEFUN (clear_isis_neighbor, clear_isis_neighbor_cmd, "clear " PROTO_NAME " neighbor", CLEAR_STR PROTO_HELP "ISIS neighbor adjacencies\n") { return clear_isis_neighbor_common(vty, NULL); } DEFUN (clear_isis_neighbor_arg, clear_isis_neighbor_arg_cmd, "clear " PROTO_NAME " neighbor WORD", CLEAR_STR PROTO_HELP "ISIS neighbor adjacencies\n" "System id\n") { int idx_word = 3; return clear_isis_neighbor_common(vty, argv[idx_word]->arg); } /* * 'isis debug', 'show debugging' */ void print_debug(struct vty *vty, int flags, int onoff) { const char *onoffs = onoff ? "on" : "off"; if (flags & DEBUG_ADJ_PACKETS) vty_out(vty, "IS-IS Adjacency related packets debugging is %s\n", onoffs); if (flags & DEBUG_TX_QUEUE) vty_out(vty, "IS-IS TX queue debugging is %s\n", onoffs); if (flags & DEBUG_SNP_PACKETS) vty_out(vty, "IS-IS CSNP/PSNP packets debugging is %s\n", onoffs); if (flags & DEBUG_SPF_EVENTS) vty_out(vty, "IS-IS SPF events debugging is %s\n", onoffs); if (flags & DEBUG_UPDATE_PACKETS) vty_out(vty, "IS-IS Update related packet debugging is %s\n", onoffs); if (flags & DEBUG_RTE_EVENTS) vty_out(vty, "IS-IS Route related debuggin is %s\n", onoffs); if (flags & DEBUG_EVENTS) vty_out(vty, "IS-IS Event debugging is %s\n", onoffs); if (flags & DEBUG_PACKET_DUMP) vty_out(vty, "IS-IS Packet dump debugging is %s\n", onoffs); if (flags & DEBUG_LSP_GEN) vty_out(vty, "IS-IS LSP generation debugging is %s\n", onoffs); if (flags & DEBUG_LSP_SCHED) vty_out(vty, "IS-IS LSP scheduling debugging is %s\n", onoffs); if (flags & DEBUG_FLOODING) vty_out(vty, "IS-IS Flooding debugging is %s\n", onoffs); if (flags & DEBUG_BFD) vty_out(vty, "IS-IS BFD debugging is %s\n", onoffs); } DEFUN_NOSH (show_debugging, show_debugging_isis_cmd, "show debugging [" PROTO_NAME "]", SHOW_STR "State of each debugging option\n" PROTO_HELP) { vty_out(vty, PROTO_NAME " debugging status:\n"); if (isis->debugs) print_debug(vty, isis->debugs, 1); return CMD_SUCCESS; } /* Debug node. */ static struct cmd_node debug_node = {DEBUG_NODE, "", 1}; static int config_write_debug(struct vty *vty) { int write = 0; int flags = isis->debugs; if (flags & DEBUG_ADJ_PACKETS) { vty_out(vty, "debug " PROTO_NAME " adj-packets\n"); write++; } if (flags & DEBUG_TX_QUEUE) { vty_out(vty, "debug " PROTO_NAME " tx-queue\n"); write++; } if (flags & DEBUG_SNP_PACKETS) { vty_out(vty, "debug " PROTO_NAME " snp-packets\n"); write++; } if (flags & DEBUG_SPF_EVENTS) { vty_out(vty, "debug " PROTO_NAME " spf-events\n"); write++; } if (flags & DEBUG_UPDATE_PACKETS) { vty_out(vty, "debug " PROTO_NAME " update-packets\n"); write++; } if (flags & DEBUG_RTE_EVENTS) { vty_out(vty, "debug " PROTO_NAME " route-events\n"); write++; } if (flags & DEBUG_EVENTS) { vty_out(vty, "debug " PROTO_NAME " events\n"); write++; } if (flags & DEBUG_PACKET_DUMP) { vty_out(vty, "debug " PROTO_NAME " packet-dump\n"); write++; } if (flags & DEBUG_LSP_GEN) { vty_out(vty, "debug " PROTO_NAME " lsp-gen\n"); write++; } if (flags & DEBUG_LSP_SCHED) { vty_out(vty, "debug " PROTO_NAME " lsp-sched\n"); write++; } if (flags & DEBUG_FLOODING) { vty_out(vty, "debug " PROTO_NAME " flooding\n"); write++; } if (flags & DEBUG_BFD) { vty_out(vty, "debug " PROTO_NAME " bfd\n"); write++; } write += spf_backoff_write_config(vty); return write; } DEFUN (debug_isis_adj, debug_isis_adj_cmd, "debug " PROTO_NAME " adj-packets", DEBUG_STR PROTO_HELP "IS-IS Adjacency related packets\n") { isis->debugs |= DEBUG_ADJ_PACKETS; print_debug(vty, DEBUG_ADJ_PACKETS, 1); return CMD_SUCCESS; } DEFUN (no_debug_isis_adj, no_debug_isis_adj_cmd, "no debug " PROTO_NAME " adj-packets", NO_STR UNDEBUG_STR PROTO_HELP "IS-IS Adjacency related packets\n") { isis->debugs &= ~DEBUG_ADJ_PACKETS; print_debug(vty, DEBUG_ADJ_PACKETS, 0); return CMD_SUCCESS; } DEFUN (debug_isis_tx_queue, debug_isis_tx_queue_cmd, "debug " PROTO_NAME " tx-queue", DEBUG_STR PROTO_HELP "IS-IS TX queues\n") { isis->debugs |= DEBUG_TX_QUEUE; print_debug(vty, DEBUG_TX_QUEUE, 1); return CMD_SUCCESS; } DEFUN (no_debug_isis_tx_queue, no_debug_isis_tx_queue_cmd, "no debug " PROTO_NAME " tx-queue", NO_STR UNDEBUG_STR PROTO_HELP "IS-IS TX queues\n") { isis->debugs &= ~DEBUG_TX_QUEUE; print_debug(vty, DEBUG_TX_QUEUE, 0); return CMD_SUCCESS; } DEFUN (debug_isis_flooding, debug_isis_flooding_cmd, "debug " PROTO_NAME " flooding", DEBUG_STR PROTO_HELP "Flooding algorithm\n") { isis->debugs |= DEBUG_FLOODING; print_debug(vty, DEBUG_FLOODING, 1); return CMD_SUCCESS; } DEFUN (no_debug_isis_flooding, no_debug_isis_flooding_cmd, "no debug " PROTO_NAME " flooding", NO_STR UNDEBUG_STR PROTO_HELP "Flooding algorithm\n") { isis->debugs &= ~DEBUG_FLOODING; print_debug(vty, DEBUG_FLOODING, 0); return CMD_SUCCESS; } DEFUN (debug_isis_snp, debug_isis_snp_cmd, "debug " PROTO_NAME " snp-packets", DEBUG_STR PROTO_HELP "IS-IS CSNP/PSNP packets\n") { isis->debugs |= DEBUG_SNP_PACKETS; print_debug(vty, DEBUG_SNP_PACKETS, 1); return CMD_SUCCESS; } DEFUN (no_debug_isis_snp, no_debug_isis_snp_cmd, "no debug " PROTO_NAME " snp-packets", NO_STR UNDEBUG_STR PROTO_HELP "IS-IS CSNP/PSNP packets\n") { isis->debugs &= ~DEBUG_SNP_PACKETS; print_debug(vty, DEBUG_SNP_PACKETS, 0); return CMD_SUCCESS; } DEFUN (debug_isis_upd, debug_isis_upd_cmd, "debug " PROTO_NAME " update-packets", DEBUG_STR PROTO_HELP "IS-IS Update related packets\n") { isis->debugs |= DEBUG_UPDATE_PACKETS; print_debug(vty, DEBUG_UPDATE_PACKETS, 1); return CMD_SUCCESS; } DEFUN (no_debug_isis_upd, no_debug_isis_upd_cmd, "no debug " PROTO_NAME " update-packets", NO_STR UNDEBUG_STR PROTO_HELP "IS-IS Update related packets\n") { isis->debugs &= ~DEBUG_UPDATE_PACKETS; print_debug(vty, DEBUG_UPDATE_PACKETS, 0); return CMD_SUCCESS; } DEFUN (debug_isis_spfevents, debug_isis_spfevents_cmd, "debug " PROTO_NAME " spf-events", DEBUG_STR PROTO_HELP "IS-IS Shortest Path First Events\n") { isis->debugs |= DEBUG_SPF_EVENTS; print_debug(vty, DEBUG_SPF_EVENTS, 1); return CMD_SUCCESS; } DEFUN (no_debug_isis_spfevents, no_debug_isis_spfevents_cmd, "no debug " PROTO_NAME " spf-events", NO_STR UNDEBUG_STR PROTO_HELP "IS-IS Shortest Path First Events\n") { isis->debugs &= ~DEBUG_SPF_EVENTS; print_debug(vty, DEBUG_SPF_EVENTS, 0); return CMD_SUCCESS; } DEFUN (debug_isis_rtevents, debug_isis_rtevents_cmd, "debug " PROTO_NAME " route-events", DEBUG_STR PROTO_HELP "IS-IS Route related events\n") { isis->debugs |= DEBUG_RTE_EVENTS; print_debug(vty, DEBUG_RTE_EVENTS, 1); return CMD_SUCCESS; } DEFUN (no_debug_isis_rtevents, no_debug_isis_rtevents_cmd, "no debug " PROTO_NAME " route-events", NO_STR UNDEBUG_STR PROTO_HELP "IS-IS Route related events\n") { isis->debugs &= ~DEBUG_RTE_EVENTS; print_debug(vty, DEBUG_RTE_EVENTS, 0); return CMD_SUCCESS; } DEFUN (debug_isis_events, debug_isis_events_cmd, "debug " PROTO_NAME " events", DEBUG_STR PROTO_HELP "IS-IS Events\n") { isis->debugs |= DEBUG_EVENTS; print_debug(vty, DEBUG_EVENTS, 1); return CMD_SUCCESS; } DEFUN (no_debug_isis_events, no_debug_isis_events_cmd, "no debug " PROTO_NAME " events", NO_STR UNDEBUG_STR PROTO_HELP "IS-IS Events\n") { isis->debugs &= ~DEBUG_EVENTS; print_debug(vty, DEBUG_EVENTS, 0); return CMD_SUCCESS; } DEFUN (debug_isis_packet_dump, debug_isis_packet_dump_cmd, "debug " PROTO_NAME " packet-dump", DEBUG_STR PROTO_HELP "IS-IS packet dump\n") { isis->debugs |= DEBUG_PACKET_DUMP; print_debug(vty, DEBUG_PACKET_DUMP, 1); return CMD_SUCCESS; } DEFUN (no_debug_isis_packet_dump, no_debug_isis_packet_dump_cmd, "no debug " PROTO_NAME " packet-dump", NO_STR UNDEBUG_STR PROTO_HELP "IS-IS packet dump\n") { isis->debugs &= ~DEBUG_PACKET_DUMP; print_debug(vty, DEBUG_PACKET_DUMP, 0); return CMD_SUCCESS; } DEFUN (debug_isis_lsp_gen, debug_isis_lsp_gen_cmd, "debug " PROTO_NAME " lsp-gen", DEBUG_STR PROTO_HELP "IS-IS generation of own LSPs\n") { isis->debugs |= DEBUG_LSP_GEN; print_debug(vty, DEBUG_LSP_GEN, 1); return CMD_SUCCESS; } DEFUN (no_debug_isis_lsp_gen, no_debug_isis_lsp_gen_cmd, "no debug " PROTO_NAME " lsp-gen", NO_STR UNDEBUG_STR PROTO_HELP "IS-IS generation of own LSPs\n") { isis->debugs &= ~DEBUG_LSP_GEN; print_debug(vty, DEBUG_LSP_GEN, 0); return CMD_SUCCESS; } DEFUN (debug_isis_lsp_sched, debug_isis_lsp_sched_cmd, "debug " PROTO_NAME " lsp-sched", DEBUG_STR PROTO_HELP "IS-IS scheduling of LSP generation\n") { isis->debugs |= DEBUG_LSP_SCHED; print_debug(vty, DEBUG_LSP_SCHED, 1); return CMD_SUCCESS; } DEFUN (no_debug_isis_lsp_sched, no_debug_isis_lsp_sched_cmd, "no debug " PROTO_NAME " lsp-sched", NO_STR UNDEBUG_STR PROTO_HELP "IS-IS scheduling of LSP generation\n") { isis->debugs &= ~DEBUG_LSP_SCHED; print_debug(vty, DEBUG_LSP_SCHED, 0); return CMD_SUCCESS; } DEFUN (debug_isis_bfd, debug_isis_bfd_cmd, "debug " PROTO_NAME " bfd", DEBUG_STR PROTO_HELP PROTO_NAME " interaction with BFD\n") { isis->debugs |= DEBUG_BFD; print_debug(vty, DEBUG_BFD, 1); return CMD_SUCCESS; } DEFUN (no_debug_isis_bfd, no_debug_isis_bfd_cmd, "no debug " PROTO_NAME " bfd", NO_STR UNDEBUG_STR PROTO_HELP PROTO_NAME " interaction with BFD\n") { isis->debugs &= ~DEBUG_BFD; print_debug(vty, DEBUG_BFD, 0); return CMD_SUCCESS; } DEFUN (show_hostname, show_hostname_cmd, "show " PROTO_NAME " hostname", SHOW_STR PROTO_HELP "IS-IS Dynamic hostname mapping\n") { dynhn_print_all(vty); return CMD_SUCCESS; } DEFUN (show_isis_spf_ietf, show_isis_spf_ietf_cmd, "show " PROTO_NAME " spf-delay-ietf", SHOW_STR PROTO_HELP "SPF delay IETF information\n") { if (!isis) { vty_out(vty, "ISIS is not running\n"); return CMD_SUCCESS; } struct listnode *node; struct isis_area *area; for (ALL_LIST_ELEMENTS_RO(isis->area_list, node, area)) { vty_out(vty, "Area %s:\n", area->area_tag ? area->area_tag : "null"); for (int level = ISIS_LEVEL1; level <= ISIS_LEVELS; level++) { if ((area->is_type & level) == 0) continue; vty_out(vty, " Level-%d:\n", level); vty_out(vty, " SPF delay status: "); if (area->spf_timer[level - 1]) { struct timeval remain = thread_timer_remain( area->spf_timer[level - 1]); vty_out(vty, "Pending, due in %lld msec\n", (long long)remain.tv_sec * 1000 + remain.tv_usec / 1000); } else { vty_out(vty, "Not scheduled\n"); } if (area->spf_delay_ietf[level - 1]) { vty_out(vty, " Using draft-ietf-rtgwg-backoff-algo-04\n"); spf_backoff_show( area->spf_delay_ietf[level - 1], vty, " "); } else { vty_out(vty, " Using legacy backoff algo\n"); } } } return CMD_SUCCESS; } DEFUN (show_isis_summary, show_isis_summary_cmd, "show " PROTO_NAME " summary", SHOW_STR PROTO_HELP "summary\n") { struct listnode *node, *node2; struct isis_area *area; int level; if (isis == NULL) { vty_out(vty, PROTO_NAME " is not running\n"); return CMD_SUCCESS; } vty_out(vty, "Process Id : %ld\n", isis->process_id); if (isis->sysid_set) vty_out(vty, "System Id : %s\n", sysid_print(isis->sysid)); vty_out(vty, "Up time : "); vty_out_timestr(vty, isis->uptime); vty_out(vty, "\n"); if (isis->area_list) vty_out(vty, "Number of areas : %d\n", isis->area_list->count); for (ALL_LIST_ELEMENTS_RO(isis->area_list, node, area)) { vty_out(vty, "Area %s:\n", area->area_tag ? area->area_tag : "null"); if (fabricd) { uint8_t tier = fabricd_tier(area); if (tier == ISIS_TIER_UNDEFINED) vty_out(vty, " Tier: undefined\n"); else vty_out(vty, " Tier: %" PRIu8 "\n", tier); } if (listcount(area->area_addrs) > 0) { struct area_addr *area_addr; for (ALL_LIST_ELEMENTS_RO(area->area_addrs, node2, area_addr)) { vty_out(vty, " Net: %s\n", isonet_print(area_addr->area_addr, area_addr->addr_len + ISIS_SYS_ID_LEN + 1)); } } vty_out(vty, " TX counters per PDU type:\n"); pdu_counter_print(vty, " ", area->pdu_tx_counters); vty_out(vty, " LSP RXMT: %" PRIu64 "\n", area->lsp_rxmt_count); vty_out(vty, " RX counters per PDU type:\n"); pdu_counter_print(vty, " ", area->pdu_rx_counters); for (level = ISIS_LEVEL1; level <= ISIS_LEVELS; level++) { if ((area->is_type & level) == 0) continue; vty_out(vty, " Level-%d:\n", level); vty_out(vty, " LSP0 regenerated: %" PRIu64 "\n", area->lsp_gen_count[level - 1]); vty_out(vty, " LSPs purged: %" PRIu64 "\n", area->lsp_purge_count[level - 1]); if (area->spf_timer[level - 1]) vty_out(vty, " SPF: (pending)\n"); else vty_out(vty, " SPF:\n"); vty_out(vty, " minimum interval : %d", area->min_spf_interval[level - 1]); if (area->spf_delay_ietf[level - 1]) vty_out(vty, " (not used, IETF SPF delay activated)"); vty_out(vty, "\n"); vty_out(vty, " IPv4 route computation:\n"); isis_spf_print(area->spftree[SPFTREE_IPV4][level - 1], vty); vty_out(vty, " IPv6 route computation:\n"); isis_spf_print(area->spftree[SPFTREE_IPV6][level - 1], vty); vty_out(vty, " IPv6 dst-src route computation:\n"); isis_spf_print(area->spftree[SPFTREE_DSTSRC][level-1], vty); } } vty_out(vty, "\n"); return CMD_SUCCESS; } struct isis_lsp *lsp_for_arg(struct lspdb_head *head, const char *argv) { char sysid[255] = {0}; uint8_t number[3]; const char *pos; uint8_t lspid[ISIS_SYS_ID_LEN + 2] = {0}; struct isis_dynhn *dynhn; struct isis_lsp *lsp = NULL; if (!argv) return NULL; /* * extract fragment and pseudo id from the string argv * in the forms: * (a) .- or * (b) . or * (c) or * Where systemid is in the form: * xxxx.xxxx.xxxx */ if (argv) strlcpy(sysid, argv, sizeof(sysid)); if (argv && strlen(argv) > 3) { pos = argv + strlen(argv) - 3; if (strncmp(pos, "-", 1) == 0) { memcpy(number, ++pos, 2); lspid[ISIS_SYS_ID_LEN + 1] = (uint8_t)strtol((char *)number, NULL, 16); pos -= 4; if (strncmp(pos, ".", 1) != 0) return NULL; } if (strncmp(pos, ".", 1) == 0) { memcpy(number, ++pos, 2); lspid[ISIS_SYS_ID_LEN] = (uint8_t)strtol((char *)number, NULL, 16); sysid[pos - argv - 1] = '\0'; } } /* * Try to find the lsp-id if the argv * string is in * the form * hostname.- */ if (sysid2buff(lspid, sysid)) { lsp = lsp_search(head, lspid); } else if ((dynhn = dynhn_find_by_name(sysid))) { memcpy(lspid, dynhn->id, ISIS_SYS_ID_LEN); lsp = lsp_search(head, lspid); } else if (strncmp(cmd_hostname_get(), sysid, 15) == 0) { memcpy(lspid, isis->sysid, ISIS_SYS_ID_LEN); lsp = lsp_search(head, lspid); } return lsp; } /* * This function supports following display options: * [ show isis database [detail] ] * [ show isis database [detail] ] * [ show isis database [detail] ] * [ show isis database . [detail] ] * [ show isis database . [detail] ] * [ show isis database .- [detail] ] * [ show isis database .- [detail] ] * [ show isis database detail ] * [ show isis database detail ] * [ show isis database detail . ] * [ show isis database detail . ] * [ show isis database detail .- ] * [ show isis database detail .- ] */ static int show_isis_database(struct vty *vty, const char *argv, int ui_level) { struct listnode *node; struct isis_area *area; struct isis_lsp *lsp; int level, lsp_count; if (isis->area_list->count == 0) return CMD_SUCCESS; for (ALL_LIST_ELEMENTS_RO(isis->area_list, node, area)) { vty_out(vty, "Area %s:\n", area->area_tag ? area->area_tag : "null"); for (level = 0; level < ISIS_LEVELS; level++) { if (lspdb_count(&area->lspdb[level]) > 0) { lsp = lsp_for_arg(&area->lspdb[level], argv); if (lsp != NULL || argv == NULL) { vty_out(vty, "IS-IS Level-%d link-state database:\n", level + 1); /* print the title in all cases */ vty_out(vty, "LSP ID PduLen SeqNumber Chksum Holdtime ATT/P/OL\n"); } if (lsp) { if (ui_level == ISIS_UI_LEVEL_DETAIL) lsp_print_detail( lsp, vty, area->dynhostname); else lsp_print(lsp, vty, area->dynhostname); } else if (argv == NULL) { lsp_count = lsp_print_all( vty, &area->lspdb[level], ui_level, area->dynhostname); vty_out(vty, " %u LSPs\n\n", lsp_count); } } } } return CMD_SUCCESS; } DEFUN (show_database, show_database_cmd, "show " PROTO_NAME " database [detail] [WORD]", SHOW_STR PROTO_HELP "Link state database\n" "Detailed information\n" "LSP ID\n") { int idx = 0; int uilevel = argv_find(argv, argc, "detail", &idx) ? ISIS_UI_LEVEL_DETAIL : ISIS_UI_LEVEL_BRIEF; char *id = argv_find(argv, argc, "WORD", &idx) ? argv[idx]->arg : NULL; return show_isis_database(vty, id, uilevel); } #ifdef FABRICD /* * 'router openfabric' command */ DEFUN_NOSH (router_openfabric, router_openfabric_cmd, "router openfabric WORD", ROUTER_STR PROTO_HELP "ISO Routing area tag\n") { int idx_word = 2; return isis_area_get(vty, argv[idx_word]->arg); } /* *'no router openfabric' command */ DEFUN (no_router_openfabric, no_router_openfabric_cmd, "no router openfabric WORD", NO_STR ROUTER_STR PROTO_HELP "ISO Routing area tag\n") { int idx_word = 3; return isis_area_destroy(argv[idx_word]->arg); } #endif /* ifdef FABRICD */ #ifdef FABRICD /* * 'net' command */ DEFUN (net, net_cmd, "net WORD", "A Network Entity Title for this process (OSI only)\n" "XX.XXXX. ... .XXX.XX Network entity title (NET)\n") { int idx_word = 1; return area_net_title(vty, argv[idx_word]->arg); } /* * 'no net' command */ DEFUN (no_net, no_net_cmd, "no net WORD", NO_STR "A Network Entity Title for this process (OSI only)\n" "XX.XXXX. ... .XXX.XX Network entity title (NET)\n") { int idx_word = 2; return area_clear_net_title(vty, argv[idx_word]->arg); } #endif /* ifdef FABRICD */ #ifdef FABRICD DEFUN (isis_topology, isis_topology_cmd, "topology " ISIS_MT_NAMES " [overload]", "Configure IS-IS topologies\n" ISIS_MT_DESCRIPTIONS "Set overload bit for topology\n") { VTY_DECLVAR_CONTEXT(isis_area, area); const char *arg = argv[1]->arg; uint16_t mtid = isis_str2mtid(arg); if (area->oldmetric) { vty_out(vty, "Multi topology IS-IS can only be used with wide metrics\n"); return CMD_WARNING_CONFIG_FAILED; } if (mtid == (uint16_t)-1) { vty_out(vty, "Don't know topology '%s'\n", arg); return CMD_WARNING_CONFIG_FAILED; } if (mtid == ISIS_MT_IPV4_UNICAST) { vty_out(vty, "Cannot configure IPv4 unicast topology\n"); return CMD_WARNING_CONFIG_FAILED; } area_set_mt_enabled(area, mtid, true); area_set_mt_overload(area, mtid, (argc == 3)); return CMD_SUCCESS; } DEFUN (no_isis_topology, no_isis_topology_cmd, "no topology " ISIS_MT_NAMES " [overload]", NO_STR "Configure IS-IS topologies\n" ISIS_MT_DESCRIPTIONS "Set overload bit for topology\n") { VTY_DECLVAR_CONTEXT(isis_area, area); const char *arg = argv[2]->arg; uint16_t mtid = isis_str2mtid(arg); if (area->oldmetric) { vty_out(vty, "Multi topology IS-IS can only be used with wide metrics\n"); return CMD_WARNING_CONFIG_FAILED; } if (mtid == (uint16_t)-1) { vty_out(vty, "Don't know topology '%s'\n", arg); return CMD_WARNING_CONFIG_FAILED; } if (mtid == ISIS_MT_IPV4_UNICAST) { vty_out(vty, "Cannot configure IPv4 unicast topology\n"); return CMD_WARNING_CONFIG_FAILED; } area_set_mt_enabled(area, mtid, false); area_set_mt_overload(area, mtid, false); return CMD_SUCCESS; } #endif /* ifdef FABRICD */ void isis_area_lsp_mtu_set(struct isis_area *area, unsigned int lsp_mtu) { area->lsp_mtu = lsp_mtu; lsp_regenerate_schedule(area, IS_LEVEL_1_AND_2, 1); } static int isis_area_passwd_set(struct isis_area *area, int level, uint8_t passwd_type, const char *passwd, uint8_t snp_auth) { struct isis_passwd *dest; struct isis_passwd modified; int len; assert((level == IS_LEVEL_1) || (level == IS_LEVEL_2)); dest = (level == IS_LEVEL_1) ? &area->area_passwd : &area->domain_passwd; memset(&modified, 0, sizeof(modified)); if (passwd_type != ISIS_PASSWD_TYPE_UNUSED) { if (!passwd) return -1; len = strlen(passwd); if (len > 254) return -1; modified.len = len; strlcpy((char *)modified.passwd, passwd, sizeof(modified.passwd)); modified.type = passwd_type; modified.snp_auth = snp_auth; } if (memcmp(&modified, dest, sizeof(modified))) { memcpy(dest, &modified, sizeof(modified)); lsp_regenerate_schedule(area, IS_LEVEL_1 | IS_LEVEL_2, 1); } return 0; } int isis_area_passwd_unset(struct isis_area *area, int level) { return isis_area_passwd_set(area, level, ISIS_PASSWD_TYPE_UNUSED, NULL, 0); } int isis_area_passwd_cleartext_set(struct isis_area *area, int level, const char *passwd, uint8_t snp_auth) { return isis_area_passwd_set(area, level, ISIS_PASSWD_TYPE_CLEARTXT, passwd, snp_auth); } int isis_area_passwd_hmac_md5_set(struct isis_area *area, int level, const char *passwd, uint8_t snp_auth) { return isis_area_passwd_set(area, level, ISIS_PASSWD_TYPE_HMAC_MD5, passwd, snp_auth); } void isis_area_invalidate_routes(struct isis_area *area, int levels) { for (int level = ISIS_LEVEL1; level <= ISIS_LEVEL2; level++) { if (!(level & levels)) continue; for (int tree = SPFTREE_IPV4; tree < SPFTREE_COUNT; tree++) { isis_spf_invalidate_routes( area->spftree[tree][level - 1]); } } } void isis_area_verify_routes(struct isis_area *area) { for (int tree = SPFTREE_IPV4; tree < SPFTREE_COUNT; tree++) isis_spf_verify_routes(area, area->spftree[tree]); } static void area_resign_level(struct isis_area *area, int level) { isis_area_invalidate_routes(area, level); isis_area_verify_routes(area); lsp_db_fini(&area->lspdb[level - 1]); for (int tree = SPFTREE_IPV4; tree < SPFTREE_COUNT; tree++) { if (area->spftree[tree][level - 1]) { isis_spftree_del(area->spftree[tree][level - 1]); area->spftree[tree][level - 1] = NULL; } } THREAD_TIMER_OFF(area->spf_timer[level - 1]); sched_debug( "ISIS (%s): Resigned from L%d - canceling LSP regeneration timer.", area->area_tag, level); THREAD_TIMER_OFF(area->t_lsp_refresh[level - 1]); area->lsp_regenerate_pending[level - 1] = 0; } void isis_area_is_type_set(struct isis_area *area, int is_type) { struct listnode *node; struct isis_circuit *circuit; if (isis->debugs & DEBUG_EVENTS) zlog_debug("ISIS-Evt (%s) system type change %s -> %s", area->area_tag, circuit_t2string(area->is_type), circuit_t2string(is_type)); if (area->is_type == is_type) return; /* No change */ switch (area->is_type) { case IS_LEVEL_1: if (is_type == IS_LEVEL_2) area_resign_level(area, IS_LEVEL_1); lsp_db_init(&area->lspdb[1]); break; case IS_LEVEL_1_AND_2: if (is_type == IS_LEVEL_1) area_resign_level(area, IS_LEVEL_2); else area_resign_level(area, IS_LEVEL_1); break; case IS_LEVEL_2: if (is_type == IS_LEVEL_1) area_resign_level(area, IS_LEVEL_2); lsp_db_init(&area->lspdb[0]); break; default: break; } area->is_type = is_type; /* override circuit's is_type */ if (area->is_type != IS_LEVEL_1_AND_2) { for (ALL_LIST_ELEMENTS_RO(area->circuit_list, node, circuit)) isis_circuit_is_type_set(circuit, is_type); } spftree_area_init(area); if (listcount(area->area_addrs) > 0) { if (is_type & IS_LEVEL_1) lsp_generate(area, IS_LEVEL_1); if (is_type & IS_LEVEL_2) lsp_generate(area, IS_LEVEL_2); } lsp_regenerate_schedule(area, IS_LEVEL_1 | IS_LEVEL_2, 1); return; } void isis_area_metricstyle_set(struct isis_area *area, bool old_metric, bool new_metric) { area->oldmetric = old_metric; area->newmetric = new_metric; lsp_regenerate_schedule(area, IS_LEVEL_1 | IS_LEVEL_2, 1); } void isis_area_overload_bit_set(struct isis_area *area, bool overload_bit) { char new_overload_bit = overload_bit ? LSPBIT_OL : 0; if (new_overload_bit != area->overload_bit) { area->overload_bit = new_overload_bit; lsp_regenerate_schedule(area, IS_LEVEL_1 | IS_LEVEL_2, 1); } #ifndef FABRICD isis_notif_db_overload(area, overload_bit); #endif /* ifndef FABRICD */ } void isis_area_attached_bit_set(struct isis_area *area, bool attached_bit) { char new_attached_bit = attached_bit ? LSPBIT_ATT : 0; if (new_attached_bit != area->attached_bit) { area->attached_bit = new_attached_bit; lsp_regenerate_schedule(area, IS_LEVEL_1 | IS_LEVEL_2, 1); } } void isis_area_dynhostname_set(struct isis_area *area, bool dynhostname) { if (area->dynhostname != dynhostname) { area->dynhostname = dynhostname; lsp_regenerate_schedule(area, IS_LEVEL_1 | IS_LEVEL_2, 0); } } void isis_area_max_lsp_lifetime_set(struct isis_area *area, int level, uint16_t max_lsp_lifetime) { assert((level == IS_LEVEL_1) || (level == IS_LEVEL_2)); if (area->max_lsp_lifetime[level - 1] == max_lsp_lifetime) return; area->max_lsp_lifetime[level - 1] = max_lsp_lifetime; lsp_regenerate_schedule(area, level, 1); } void isis_area_lsp_refresh_set(struct isis_area *area, int level, uint16_t lsp_refresh) { assert((level == IS_LEVEL_1) || (level == IS_LEVEL_2)); if (area->lsp_refresh[level - 1] == lsp_refresh) return; area->lsp_refresh[level - 1] = lsp_refresh; lsp_regenerate_schedule(area, level, 1); } #ifdef FABRICD DEFUN (log_adj_changes, log_adj_changes_cmd, "log-adjacency-changes", "Log changes in adjacency state\n") { VTY_DECLVAR_CONTEXT(isis_area, area); area->log_adj_changes = 1; return CMD_SUCCESS; } DEFUN (no_log_adj_changes, no_log_adj_changes_cmd, "no log-adjacency-changes", NO_STR "Stop logging changes in adjacency state\n") { VTY_DECLVAR_CONTEXT(isis_area, area); area->log_adj_changes = 0; return CMD_SUCCESS; } #endif /* ifdef FABRICD */ #ifdef FABRICD /* IS-IS configuration write function */ int isis_config_write(struct vty *vty) { int write = 0; if (isis != NULL) { struct isis_area *area; struct listnode *node, *node2; for (ALL_LIST_ELEMENTS_RO(isis->area_list, node, area)) { /* ISIS - Area name */ vty_out(vty, "router " PROTO_NAME " %s\n", area->area_tag); write++; /* ISIS - Net */ if (listcount(area->area_addrs) > 0) { struct area_addr *area_addr; for (ALL_LIST_ELEMENTS_RO(area->area_addrs, node2, area_addr)) { vty_out(vty, " net %s\n", isonet_print( area_addr->area_addr, area_addr->addr_len + ISIS_SYS_ID_LEN + 1)); write++; } } /* ISIS - Dynamic hostname - Defaults to true so only * display if * false. */ if (!area->dynhostname) { vty_out(vty, " no hostname dynamic\n"); write++; } /* ISIS - Metric-Style - when true displays wide */ if (!fabricd) { if (area->newmetric) { if (!area->oldmetric) vty_out(vty, " metric-style wide\n"); else vty_out(vty, " metric-style transition\n"); write++; } else { vty_out(vty, " metric-style narrow\n"); write++; } } /* ISIS - overload-bit */ if (area->overload_bit) { vty_out(vty, " set-overload-bit\n"); write++; } /* ISIS - Area is-type (level-1-2 is default) */ if (!fabricd) { if (area->is_type == IS_LEVEL_1) { vty_out(vty, " is-type level-1\n"); write++; } else if (area->is_type == IS_LEVEL_2) { vty_out(vty, " is-type level-2-only\n"); write++; } } write += isis_redist_config_write(vty, area, AF_INET); write += isis_redist_config_write(vty, area, AF_INET6); /* ISIS - Lsp generation interval */ if (area->lsp_gen_interval[0] == area->lsp_gen_interval[1]) { if (area->lsp_gen_interval[0] != DEFAULT_MIN_LSP_GEN_INTERVAL) { vty_out(vty, " lsp-gen-interval %d\n", area->lsp_gen_interval[0]); write++; } } else { if (area->lsp_gen_interval[0] != DEFAULT_MIN_LSP_GEN_INTERVAL) { vty_out(vty, " lsp-gen-interval level-1 %d\n", area->lsp_gen_interval[0]); write++; } if (area->lsp_gen_interval[1] != DEFAULT_MIN_LSP_GEN_INTERVAL) { vty_out(vty, " lsp-gen-interval level-2 %d\n", area->lsp_gen_interval[1]); write++; } } /* ISIS - LSP lifetime */ if (area->max_lsp_lifetime[0] == area->max_lsp_lifetime[1]) { if (area->max_lsp_lifetime[0] != DEFAULT_LSP_LIFETIME) { vty_out(vty, " max-lsp-lifetime %u\n", area->max_lsp_lifetime[0]); write++; } } else { if (area->max_lsp_lifetime[0] != DEFAULT_LSP_LIFETIME) { vty_out(vty, " max-lsp-lifetime level-1 %u\n", area->max_lsp_lifetime[0]); write++; } if (area->max_lsp_lifetime[1] != DEFAULT_LSP_LIFETIME) { vty_out(vty, " max-lsp-lifetime level-2 %u\n", area->max_lsp_lifetime[1]); write++; } } /* ISIS - LSP refresh interval */ if (area->lsp_refresh[0] == area->lsp_refresh[1]) { if (area->lsp_refresh[0] != DEFAULT_MAX_LSP_GEN_INTERVAL) { vty_out(vty, " lsp-refresh-interval %u\n", area->lsp_refresh[0]); write++; } } else { if (area->lsp_refresh[0] != DEFAULT_MAX_LSP_GEN_INTERVAL) { vty_out(vty, " lsp-refresh-interval level-1 %u\n", area->lsp_refresh[0]); write++; } if (area->lsp_refresh[1] != DEFAULT_MAX_LSP_GEN_INTERVAL) { vty_out(vty, " lsp-refresh-interval level-2 %u\n", area->lsp_refresh[1]); write++; } } if (area->lsp_mtu != DEFAULT_LSP_MTU) { vty_out(vty, " lsp-mtu %u\n", area->lsp_mtu); write++; } if (area->purge_originator) { vty_out(vty, " purge-originator\n"); write++; } /* Minimum SPF interval. */ if (area->min_spf_interval[0] == area->min_spf_interval[1]) { if (area->min_spf_interval[0] != MINIMUM_SPF_INTERVAL) { vty_out(vty, " spf-interval %d\n", area->min_spf_interval[0]); write++; } } else { if (area->min_spf_interval[0] != MINIMUM_SPF_INTERVAL) { vty_out(vty, " spf-interval level-1 %d\n", area->min_spf_interval[0]); write++; } if (area->min_spf_interval[1] != MINIMUM_SPF_INTERVAL) { vty_out(vty, " spf-interval level-2 %d\n", area->min_spf_interval[1]); write++; } } /* IETF SPF interval */ if (area->spf_delay_ietf[0]) { vty_out(vty, " spf-delay-ietf init-delay %ld short-delay %ld long-delay %ld holddown %ld time-to-learn %ld\n", spf_backoff_init_delay( area->spf_delay_ietf[0]), spf_backoff_short_delay( area->spf_delay_ietf[0]), spf_backoff_long_delay( area->spf_delay_ietf[0]), spf_backoff_holddown( area->spf_delay_ietf[0]), spf_backoff_timetolearn( area->spf_delay_ietf[0])); write++; } /* Authentication passwords. */ if (area->area_passwd.type == ISIS_PASSWD_TYPE_HMAC_MD5) { vty_out(vty, " area-password md5 %s", area->area_passwd.passwd); if (CHECK_FLAG(area->area_passwd.snp_auth, SNP_AUTH_SEND)) { vty_out(vty, " authenticate snp "); if (CHECK_FLAG( area->area_passwd.snp_auth, SNP_AUTH_RECV)) vty_out(vty, "validate"); else vty_out(vty, "send-only"); } vty_out(vty, "\n"); write++; } else if (area->area_passwd.type == ISIS_PASSWD_TYPE_CLEARTXT) { vty_out(vty, " area-password clear %s", area->area_passwd.passwd); if (CHECK_FLAG(area->area_passwd.snp_auth, SNP_AUTH_SEND)) { vty_out(vty, " authenticate snp "); if (CHECK_FLAG( area->area_passwd.snp_auth, SNP_AUTH_RECV)) vty_out(vty, "validate"); else vty_out(vty, "send-only"); } vty_out(vty, "\n"); write++; } if (area->domain_passwd.type == ISIS_PASSWD_TYPE_HMAC_MD5) { vty_out(vty, " domain-password md5 %s", area->domain_passwd.passwd); if (CHECK_FLAG(area->domain_passwd.snp_auth, SNP_AUTH_SEND)) { vty_out(vty, " authenticate snp "); if (CHECK_FLAG(area->domain_passwd .snp_auth, SNP_AUTH_RECV)) vty_out(vty, "validate"); else vty_out(vty, "send-only"); } vty_out(vty, "\n"); write++; } else if (area->domain_passwd.type == ISIS_PASSWD_TYPE_CLEARTXT) { vty_out(vty, " domain-password clear %s", area->domain_passwd.passwd); if (CHECK_FLAG(area->domain_passwd.snp_auth, SNP_AUTH_SEND)) { vty_out(vty, " authenticate snp "); if (CHECK_FLAG(area->domain_passwd .snp_auth, SNP_AUTH_RECV)) vty_out(vty, "validate"); else vty_out(vty, "send-only"); } vty_out(vty, "\n"); write++; } if (area->log_adj_changes) { vty_out(vty, " log-adjacency-changes\n"); write++; } write += area_write_mt_settings(area, vty); write += fabricd_write_settings(area, vty); } } return write; } #else /* IS-IS configuration write function */ int isis_config_write(struct vty *vty) { int write = 0; struct lyd_node *dnode; dnode = yang_dnode_get(running_config->dnode, "/frr-isisd:isis"); if (dnode) { nb_cli_show_dnode_cmds(vty, dnode, false); write++; } return write; } #endif /* ifdef FABRICD */ struct cmd_node router_node = {ROUTER_NODE, "%s(config-router)# ", 1}; void isis_init(void) { /* Install IS-IS top node */ install_node(&router_node, isis_config_write); install_element(VIEW_NODE, &show_isis_summary_cmd); install_element(VIEW_NODE, &show_isis_spf_ietf_cmd); install_element(VIEW_NODE, &show_isis_interface_cmd); install_element(VIEW_NODE, &show_isis_interface_detail_cmd); install_element(VIEW_NODE, &show_isis_interface_arg_cmd); install_element(VIEW_NODE, &show_isis_neighbor_cmd); install_element(VIEW_NODE, &show_isis_neighbor_detail_cmd); install_element(VIEW_NODE, &show_isis_neighbor_arg_cmd); install_element(VIEW_NODE, &clear_isis_neighbor_cmd); install_element(VIEW_NODE, &clear_isis_neighbor_arg_cmd); install_element(VIEW_NODE, &show_hostname_cmd); install_element(VIEW_NODE, &show_database_cmd); install_element(ENABLE_NODE, &show_debugging_isis_cmd); install_node(&debug_node, config_write_debug); install_element(ENABLE_NODE, &debug_isis_adj_cmd); install_element(ENABLE_NODE, &no_debug_isis_adj_cmd); install_element(ENABLE_NODE, &debug_isis_tx_queue_cmd); install_element(ENABLE_NODE, &no_debug_isis_tx_queue_cmd); install_element(ENABLE_NODE, &debug_isis_flooding_cmd); install_element(ENABLE_NODE, &no_debug_isis_flooding_cmd); install_element(ENABLE_NODE, &debug_isis_snp_cmd); install_element(ENABLE_NODE, &no_debug_isis_snp_cmd); install_element(ENABLE_NODE, &debug_isis_upd_cmd); install_element(ENABLE_NODE, &no_debug_isis_upd_cmd); install_element(ENABLE_NODE, &debug_isis_spfevents_cmd); install_element(ENABLE_NODE, &no_debug_isis_spfevents_cmd); install_element(ENABLE_NODE, &debug_isis_rtevents_cmd); install_element(ENABLE_NODE, &no_debug_isis_rtevents_cmd); install_element(ENABLE_NODE, &debug_isis_events_cmd); install_element(ENABLE_NODE, &no_debug_isis_events_cmd); install_element(ENABLE_NODE, &debug_isis_packet_dump_cmd); install_element(ENABLE_NODE, &no_debug_isis_packet_dump_cmd); install_element(ENABLE_NODE, &debug_isis_lsp_gen_cmd); install_element(ENABLE_NODE, &no_debug_isis_lsp_gen_cmd); install_element(ENABLE_NODE, &debug_isis_lsp_sched_cmd); install_element(ENABLE_NODE, &no_debug_isis_lsp_sched_cmd); install_element(ENABLE_NODE, &debug_isis_bfd_cmd); install_element(ENABLE_NODE, &no_debug_isis_bfd_cmd); install_element(CONFIG_NODE, &debug_isis_adj_cmd); install_element(CONFIG_NODE, &no_debug_isis_adj_cmd); install_element(CONFIG_NODE, &debug_isis_tx_queue_cmd); install_element(CONFIG_NODE, &no_debug_isis_tx_queue_cmd); install_element(CONFIG_NODE, &debug_isis_flooding_cmd); install_element(CONFIG_NODE, &no_debug_isis_flooding_cmd); install_element(CONFIG_NODE, &debug_isis_snp_cmd); install_element(CONFIG_NODE, &no_debug_isis_snp_cmd); install_element(CONFIG_NODE, &debug_isis_upd_cmd); install_element(CONFIG_NODE, &no_debug_isis_upd_cmd); install_element(CONFIG_NODE, &debug_isis_spfevents_cmd); install_element(CONFIG_NODE, &no_debug_isis_spfevents_cmd); install_element(CONFIG_NODE, &debug_isis_rtevents_cmd); install_element(CONFIG_NODE, &no_debug_isis_rtevents_cmd); install_element(CONFIG_NODE, &debug_isis_events_cmd); install_element(CONFIG_NODE, &no_debug_isis_events_cmd); install_element(CONFIG_NODE, &debug_isis_packet_dump_cmd); install_element(CONFIG_NODE, &no_debug_isis_packet_dump_cmd); install_element(CONFIG_NODE, &debug_isis_lsp_gen_cmd); install_element(CONFIG_NODE, &no_debug_isis_lsp_gen_cmd); install_element(CONFIG_NODE, &debug_isis_lsp_sched_cmd); install_element(CONFIG_NODE, &no_debug_isis_lsp_sched_cmd); install_element(CONFIG_NODE, &debug_isis_bfd_cmd); install_element(CONFIG_NODE, &no_debug_isis_bfd_cmd); install_default(ROUTER_NODE); #ifdef FABRICD install_element(CONFIG_NODE, &router_openfabric_cmd); install_element(CONFIG_NODE, &no_router_openfabric_cmd); install_element(ROUTER_NODE, &net_cmd); install_element(ROUTER_NODE, &no_net_cmd); install_element(ROUTER_NODE, &isis_topology_cmd); install_element(ROUTER_NODE, &no_isis_topology_cmd); install_element(ROUTER_NODE, &log_adj_changes_cmd); install_element(ROUTER_NODE, &no_log_adj_changes_cmd); #endif /* ifdef FABRICD */ spf_backoff_cmd_init(); } frr-7.2.1/isisd/isisd.conf.sample0000644000000000000000000000142513610377563013573 00000000000000! -*- isis -*- ! ! ISISd sample configuration file ! hostname isisd password foo enable password foo log stdout !log file /tmp/isisd.log ! ! router isis DEAD net 47.0023.0000.0003.0300.0100.0102.0304.0506.00 ! is-type level-1 ! -- set the lifetime either for level-1, level-2 or both ! lsp-lifetime level-1 65535 ! lsp-lifetime level-2 65535 ! lsp-lifetime 65535 ! hostname isisd-router ! area-password foobar ! domain-password foobar interface eth0 ip router isis DEAD ! isis hello-interval 5 ! isis lsp-interval 1000 ! -- optional ! isis circuit-type level-1 ! isis password lallaa level-1 ! isis metric 1 level-1 ! isis csnp-interval 5 level-1 ! isis retransmit-interval 10 ! isis retransmit-throttle-interval ! isis hello-multiplier 2 level-1 ! isis priority 64 ! frr-7.2.1/isisd/isisd.h0000644000000000000000000002546013610377563011622 00000000000000/* * IS-IS Rout(e)ing protocol - isisd.h * * Copyright (C) 2001,2002 Sampo Saaristo * Tampere University of Technology * Institute of Communications Engineering * * This program is free software; you can redistribute it and/or modify it * under the terms of the GNU General Public Licenseas 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; see the file COPYING; if not, write to the Free Software * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA */ #ifndef ISISD_H #define ISISD_H #include "vty.h" #include "isisd/isis_constants.h" #include "isisd/isis_common.h" #include "isisd/isis_redist.h" #include "isisd/isis_pdu_counter.h" #include "isisd/isis_circuit.h" #include "isis_flags.h" #include "isis_lsp.h" #include "isis_memory.h" #include "qobj.h" #ifdef FABRICD static const bool fabricd = true; #define PROTO_TYPE ZEBRA_ROUTE_OPENFABRIC #define PROTO_NAME "openfabric" #define PROTO_HELP "OpenFabric routing protocol\n" #define PROTO_REDIST_STR FRR_REDIST_STR_FABRICD #define PROTO_REDIST_HELP FRR_REDIST_HELP_STR_FABRICD #define ROUTER_NODE OPENFABRIC_NODE #else static const bool fabricd = false; #define PROTO_TYPE ZEBRA_ROUTE_ISIS #define PROTO_NAME "isis" #define PROTO_HELP "IS-IS routing protocol\n" #define PROTO_REDIST_STR FRR_REDIST_STR_ISISD #define PROTO_REDIST_HELP FRR_REDIST_HELP_STR_ISISD #define ROUTER_NODE ISIS_NODE extern void isis_cli_init(void); #endif extern struct zebra_privs_t isisd_privs; /* uncomment if you are a developer in bug hunt */ /* #define EXTREME_DEBUG */ /* #define EXTREME_DICT_DEBUG */ struct fabricd; struct isis { unsigned long process_id; int sysid_set; uint8_t sysid[ISIS_SYS_ID_LEN]; /* SystemID for this IS */ uint32_t router_id; /* Router ID from zebra */ struct list *area_list; /* list of IS-IS areas */ struct list *init_circ_list; struct list *nexthops; /* IP next hops from this IS */ uint8_t max_area_addrs; /* maximumAreaAdresses */ struct area_addr *man_area_addrs; /* manualAreaAddresses */ uint32_t debugs; /* bitmap for debug */ time_t uptime; /* when did we start */ struct thread *t_dync_clean; /* dynamic hostname cache cleanup thread */ uint32_t circuit_ids_used[8]; /* 256 bits to track circuit ids 1 through 255 */ struct route_table *ext_info[REDIST_PROTOCOL_COUNT]; QOBJ_FIELDS }; extern struct isis *isis; DECLARE_QOBJ_TYPE(isis_area) enum spf_tree_id { SPFTREE_IPV4 = 0, SPFTREE_IPV6, SPFTREE_DSTSRC, SPFTREE_COUNT }; struct lsp_refresh_arg { struct isis_area *area; int level; }; /* for yang configuration */ enum isis_metric_style { ISIS_NARROW_METRIC = 0, ISIS_WIDE_METRIC, ISIS_TRANSITION_METRIC, }; struct isis_area { struct isis *isis; /* back pointer */ struct lspdb_head lspdb[ISIS_LEVELS]; /* link-state dbs */ struct isis_spftree *spftree[SPFTREE_COUNT][ISIS_LEVELS]; #define DEFAULT_LSP_MTU 1497 unsigned int lsp_mtu; /* Size of LSPs to generate */ struct list *circuit_list; /* IS-IS circuits */ struct flags flags; struct thread *t_tick; /* LSP walker */ struct thread *t_lsp_refresh[ISIS_LEVELS]; struct timeval last_lsp_refresh_event[ISIS_LEVELS]; /* t_lsp_refresh is used in two ways: * a) regular refresh of LSPs * b) (possibly throttled) updates to LSPs * * The lsp_regenerate_pending flag tracks whether the timer is active * for the a) or the b) case. * * It is of utmost importance to clear this flag when the timer is * rescheduled for normal refresh, because otherwise, updates will * be delayed until the next regular refresh. */ int lsp_regenerate_pending[ISIS_LEVELS]; struct fabricd *fabricd; /* * Configurables */ struct isis_passwd area_passwd; struct isis_passwd domain_passwd; /* do we support dynamic hostnames? */ char dynhostname; /* do we support new style metrics? */ char newmetric; char oldmetric; /* identifies the routing instance */ char *area_tag; /* area addresses for this area */ struct list *area_addrs; uint16_t max_lsp_lifetime[ISIS_LEVELS]; char is_type; /* level-1 level-1-2 or level-2-only */ /* are we overloaded? */ char overload_bit; /* L1/L2 router identifier for inter-area traffic */ char attached_bit; uint16_t lsp_refresh[ISIS_LEVELS]; /* minimum time allowed before lsp retransmission */ uint16_t lsp_gen_interval[ISIS_LEVELS]; /* min interval between between consequtive SPFs */ uint16_t min_spf_interval[ISIS_LEVELS]; /* the percentage of LSP mtu size used, before generating a new frag */ int lsp_frag_threshold; uint64_t lsp_gen_count[ISIS_LEVELS]; uint64_t lsp_purge_count[ISIS_LEVELS]; int ip_circuits; /* logging adjacency changes? */ uint8_t log_adj_changes; /* multi topology settings */ struct list *mt_settings; /* MPLS-TE settings */ struct mpls_te_area *mta; int ipv6_circuits; bool purge_originator; /* Counters */ uint32_t circuit_state_changes; struct isis_redist redist_settings[REDIST_PROTOCOL_COUNT] [ZEBRA_ROUTE_MAX + 1][ISIS_LEVELS]; struct route_table *ext_reach[REDIST_PROTOCOL_COUNT][ISIS_LEVELS]; struct spf_backoff *spf_delay_ietf[ISIS_LEVELS]; /*Structure with IETF SPF algo parameters*/ struct thread *spf_timer[ISIS_LEVELS]; struct lsp_refresh_arg lsp_refresh_arg[ISIS_LEVELS]; pdu_counter_t pdu_tx_counters; pdu_counter_t pdu_rx_counters; uint64_t lsp_rxmt_count; QOBJ_FIELDS }; DECLARE_QOBJ_TYPE(isis_area) void isis_init(void); void isis_new(unsigned long); struct isis_area *isis_area_create(const char *); struct isis_area *isis_area_lookup(const char *); int isis_area_get(struct vty *vty, const char *area_tag); int isis_area_destroy(const char *area_tag); void print_debug(struct vty *, int, int); struct isis_lsp *lsp_for_arg(struct lspdb_head *head, const char *argv); void isis_area_invalidate_routes(struct isis_area *area, int levels); void isis_area_verify_routes(struct isis_area *area); void isis_area_overload_bit_set(struct isis_area *area, bool overload_bit); void isis_area_attached_bit_set(struct isis_area *area, bool attached_bit); void isis_area_dynhostname_set(struct isis_area *area, bool dynhostname); void isis_area_metricstyle_set(struct isis_area *area, bool old_metric, bool new_metric); void isis_area_lsp_mtu_set(struct isis_area *area, unsigned int lsp_mtu); void isis_area_is_type_set(struct isis_area *area, int is_type); void isis_area_max_lsp_lifetime_set(struct isis_area *area, int level, uint16_t max_lsp_lifetime); void isis_area_lsp_refresh_set(struct isis_area *area, int level, uint16_t lsp_refresh); /* IS_LEVEL_1 sets area_passwd, IS_LEVEL_2 domain_passwd */ int isis_area_passwd_unset(struct isis_area *area, int level); int isis_area_passwd_cleartext_set(struct isis_area *area, int level, const char *passwd, uint8_t snp_auth); int isis_area_passwd_hmac_md5_set(struct isis_area *area, int level, const char *passwd, uint8_t snp_auth); extern const struct frr_yang_module_info frr_isisd_info; extern void isis_northbound_init(void); /* YANG northbound notifications */ extern void isis_notif_db_overload(const struct isis_area *area, bool overload); extern void isis_notif_lsp_too_large(const struct isis_circuit *circuit, uint32_t pdu_size, const char *lsp_id); extern void isis_notif_if_state_change(const struct isis_circuit *circuit, bool down); extern void isis_notif_corrupted_lsp(const struct isis_area *area, const char *lsp_id); /* currently unused */ extern void isis_notif_lsp_exceed_max(const struct isis_area *area, const char *lsp_id); extern void isis_notif_max_area_addr_mismatch(const struct isis_circuit *circuit, uint8_t max_area_addrs, const char *raw_pdu); extern void isis_notif_authentication_type_failure(const struct isis_circuit *circuit, const char *raw_pdu); extern void isis_notif_authentication_failure(const struct isis_circuit *circuit, const char *raw_pdu); extern void isis_notif_adj_state_change(const struct isis_adjacency *adj, int new_state, const char *reason); extern void isis_notif_reject_adjacency(const struct isis_circuit *circuit, const char *reason, const char *raw_pdu); extern void isis_notif_area_mismatch(const struct isis_circuit *circuit, const char *raw_pdu); extern void isis_notif_lsp_received(const struct isis_circuit *circuit, const char *lsp_id, uint32_t seqno, uint32_t timestamp, const char *sys_id); extern void isis_notif_lsp_gen(const struct isis_area *area, const char *lsp_id, uint32_t seqno, uint32_t timestamp); extern void isis_notif_id_len_mismatch(const struct isis_circuit *circuit, uint8_t rcv_id_len, const char *raw_pdu); extern void isis_notif_version_skew(const struct isis_circuit *circuit, uint8_t version, const char *raw_pdu); extern void isis_notif_lsp_error(const struct isis_circuit *circuit, const char *lsp_id, const char *raw_pdu, uint32_t offset, uint8_t tlv_type); extern void isis_notif_seqno_skipped(const struct isis_circuit *circuit, const char *lsp_id); extern void isis_notif_own_lsp_purge(const struct isis_circuit *circuit, const char *lsp_id); /* Master of threads. */ extern struct thread_master *master; #define DEBUG_ADJ_PACKETS (1<<0) #define DEBUG_SNP_PACKETS (1<<1) #define DEBUG_UPDATE_PACKETS (1<<2) #define DEBUG_SPF_EVENTS (1<<3) #define DEBUG_RTE_EVENTS (1<<4) #define DEBUG_EVENTS (1<<5) #define DEBUG_PACKET_DUMP (1<<6) #define DEBUG_LSP_GEN (1<<7) #define DEBUG_LSP_SCHED (1<<8) #define DEBUG_FLOODING (1<<9) #define DEBUG_BFD (1<<10) #define DEBUG_TX_QUEUE (1<<11) #define lsp_debug(...) \ do { \ if (isis->debugs & DEBUG_LSP_GEN) \ zlog_debug(__VA_ARGS__); \ } while (0) #define sched_debug(...) \ do { \ if (isis->debugs & DEBUG_LSP_SCHED) \ zlog_debug(__VA_ARGS__); \ } while (0) #define DEBUG_TE DEBUG_LSP_GEN #define IS_DEBUG_ISIS(x) (isis->debugs & x) #endif /* ISISD_H */ frr-7.2.1/isisd/iso_checksum.c0000644000000000000000000000353513610377563013155 00000000000000/* * IS-IS Rout(e)ing protocol - iso_checksum.c * ISO checksum related routines * * Copyright (C) 2001,2002 Sampo Saaristo * Tampere University of Technology * Institute of Communications Engineering * * This program is free software; you can redistribute it and/or modify it * under the terms of the GNU General Public Licenseas 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; see the file COPYING; if not, write to the Free Software * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA */ #include #include "iso_checksum.h" #include "checksum.h" /* * Calculations of the OSI checksum. * ISO/IEC 8473 defines the sum as * * L * sum a (mod 255) = 0 * 1 i * * L * sum (L-i+1)a (mod 255) = 0 * 1 i * */ /* * Verifies that the checksum is correct. * Return 0 on correct and 1 on invalid checksum. * Based on Annex C.4 of ISO/IEC 8473 */ int iso_csum_verify(uint8_t *buffer, int len, uint16_t csum, int offset) { uint16_t checksum; uint32_t c0; uint32_t c1; c0 = csum & 0xff00; c1 = csum & 0x00ff; /* * If both are zero return correct */ if (c0 == 0 && c1 == 0) return 0; /* * If either, but not both are zero return incorrect */ if (c0 == 0 || c1 == 0) return 1; checksum = fletcher_checksum(buffer, len, offset); if (checksum == htons(csum)) return 0; return 1; } frr-7.2.1/isisd/iso_checksum.h0000644000000000000000000000224213610377563013154 00000000000000/* * IS-IS Rout(e)ing protocol - iso_checksum.c * ISO checksum related routines * * Copyright (C) 2001,2002 Sampo Saaristo * Tampere University of Technology * Institute of Communications Engineering * * This program is free software; you can redistribute it and/or modify it * under the terms of the GNU General Public Licenseas 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; see the file COPYING; if not, write to the Free Software * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA */ #ifndef _ZEBRA_ISO_CSUM_H #define _ZEBRA_ISO_CSUM_H int iso_csum_verify(uint8_t *buffer, int len, uint16_t csum, int offset); #endif /* _ZEBRA_ISO_CSUM_H */ frr-7.2.1/isisd/subdir.am0000644000000000000000000000525213610377563012142 00000000000000# # isisd # if ISISD noinst_LIBRARIES += isisd/libisis.a sbin_PROGRAMS += isisd/isisd dist_examples_DATA += isisd/isisd.conf.sample vtysh_scan += \ $(top_srcdir)/isisd/isis_cli.c \ $(top_srcdir)/isisd/isis_redist.c \ $(top_srcdir)/isisd/isis_spf.c \ $(top_srcdir)/isisd/isis_te.c \ $(top_srcdir)/isisd/isis_vty_fabricd.c \ $(top_srcdir)/isisd/isisd.c \ # end man8 += $(MANBUILD)/frr-isisd.8 endif if FABRICD noinst_LIBRARIES += isisd/libfabric.a sbin_PROGRAMS += isisd/fabricd dist_examples_DATA += isisd/fabricd.conf.sample endif noinst_HEADERS += \ isisd/isis_adjacency.h \ isisd/isis_bfd.h \ isisd/isis_circuit.h \ isisd/isis_common.h \ isisd/isis_constants.h \ isisd/isis_csm.h \ isisd/isis_dr.h \ isisd/isis_dynhn.h \ isisd/isis_errors.h \ isisd/isis_events.h \ isisd/isis_flags.h \ isisd/isis_lsp.h \ isisd/isis_memory.h \ isisd/isis_misc.h \ isisd/isis_mt.h \ isisd/isis_network.h \ isisd/isis_pdu.h \ isisd/isis_pdu_counter.h \ isisd/isis_redist.h \ isisd/isis_route.h \ isisd/isis_routemap.h \ isisd/isis_spf.h \ isisd/isis_spf_private.h \ isisd/isis_te.h \ isisd/isis_tlvs.h \ isisd/isis_tx_queue.h \ isisd/isis_zebra.h \ isisd/isisd.h \ isisd/iso_checksum.h \ isisd/fabricd.h \ isisd/isis_cli.h \ # end LIBISIS_SOURCES = \ isisd/isis_adjacency.c \ isisd/isis_bfd.c \ isisd/isis_circuit.c \ isisd/isis_csm.c \ isisd/isis_dr.c \ isisd/isis_dynhn.c \ isisd/isis_errors.c \ isisd/isis_events.c \ isisd/isis_flags.c \ isisd/isis_lsp.c \ isisd/isis_memory.c \ isisd/isis_misc.c \ isisd/isis_mt.c \ isisd/isis_pdu.c \ isisd/isis_pdu_counter.c \ isisd/isis_redist.c \ isisd/isis_route.c \ isisd/isis_routemap.c \ isisd/isis_spf.c \ isisd/isis_te.c \ isisd/isis_tlvs.c \ isisd/isis_tx_queue.c \ isisd/isis_zebra.c \ isisd/isisd.c \ isisd/iso_checksum.c \ isisd/fabricd.c \ # end ISIS_SOURCES = \ isisd/isis_bpf.c \ isisd/isis_dlpi.c \ isisd/isis_main.c \ isisd/isis_pfpacket.c \ # end ISIS_LDADD_COMMON = lib/libfrr.la $(LIBCAP) # Building isisd isisd_libisis_a_SOURCES = \ $(LIBISIS_SOURCES) \ isisd/isis_northbound.c \ isisd/isis_cli.c \ #end isisd/isis_cli_clippy.c: $(CLIPPY_DEPS) isisd/isis_cli.$(OBJEXT): isisd/isis_cli_clippy.c isisd_isisd_LDADD = isisd/libisis.a $(ISIS_LDADD_COMMON) isisd_isisd_SOURCES = $(ISIS_SOURCES) nodist_isisd_isisd_SOURCES = \ yang/frr-isisd.yang.c \ # end # Building fabricd FABRICD_CPPFLAGS = -DFABRICD=1 $(AM_CPPFLAGS) isisd_libfabric_a_SOURCES = \ $(LIBISIS_SOURCES) \ isisd/isis_vty_fabricd.c \ #end isisd_libfabric_a_CPPFLAGS = $(FABRICD_CPPFLAGS) isisd_fabricd_LDADD = isisd/libfabric.a $(ISIS_LDADD_COMMON) isisd_fabricd_SOURCES = $(ISIS_SOURCES) isisd_fabricd_CPPFLAGS = $(FABRICD_CPPFLAGS) frr-7.2.1/ldpd/0000755000000000000000000000000013610377563010217 500000000000000frr-7.2.1/ldpd/Makefile0000644000000000000000000000021713610377563011577 00000000000000all: ALWAYS @$(MAKE) -s -C .. ldpd/ldpd %: ALWAYS @$(MAKE) -s -C .. ldpd/$@ Makefile: #nothing ALWAYS: .PHONY: ALWAYS makefiles .SUFFIXES: frr-7.2.1/ldpd/accept.c0000644000000000000000000000556013610377563011550 00000000000000/* $OpenBSD$ */ /* * Copyright (c) 2012 Claudio Jeker * * Permission to use, copy, modify, and distribute this software for any * purpose with or without fee is hereby granted, provided that the above * copyright notice and this permission notice appear in all copies. * * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. */ #include #include "ldpd.h" #include "ldpe.h" #include "log.h" struct accept_ev { LIST_ENTRY(accept_ev) entry; struct thread *ev; int (*accept_cb)(struct thread *); void *arg; int fd; }; struct { LIST_HEAD(, accept_ev) queue; struct thread *evt; } accept_queue; static void accept_arm(void); static void accept_unarm(void); static int accept_cb(struct thread *); static int accept_timeout(struct thread *); void accept_init(void) { LIST_INIT(&accept_queue.queue); } int accept_add(int fd, int (*cb)(struct thread *), void *arg) { struct accept_ev *av; if ((av = calloc(1, sizeof(*av))) == NULL) return (-1); av->fd = fd; av->accept_cb = cb; av->arg = arg; LIST_INSERT_HEAD(&accept_queue.queue, av, entry); av->ev = NULL; thread_add_read(master, accept_cb, av, av->fd, &av->ev); log_debug("%s: accepting on fd %d", __func__, fd); return (0); } void accept_del(int fd) { struct accept_ev *av; LIST_FOREACH(av, &accept_queue.queue, entry) if (av->fd == fd) { log_debug("%s: %d removed from queue", __func__, fd); THREAD_READ_OFF(av->ev); LIST_REMOVE(av, entry); free(av); return; } } void accept_pause(void) { log_debug(__func__); accept_unarm(); accept_queue.evt = NULL; thread_add_timer(master, accept_timeout, NULL, 1, &accept_queue.evt); } void accept_unpause(void) { if (accept_queue.evt != NULL) { log_debug(__func__); THREAD_TIMER_OFF(accept_queue.evt); accept_arm(); } } static void accept_arm(void) { struct accept_ev *av; LIST_FOREACH(av, &accept_queue.queue, entry) { av->ev = NULL; thread_add_read(master, accept_cb, av, av->fd, &av->ev); } } static void accept_unarm(void) { struct accept_ev *av; LIST_FOREACH(av, &accept_queue.queue, entry) THREAD_READ_OFF(av->ev); } static int accept_cb(struct thread *thread) { struct accept_ev *av = THREAD_ARG(thread); av->ev = NULL; thread_add_read(master, accept_cb, av, av->fd, &av->ev); av->accept_cb(thread); return (0); } static int accept_timeout(struct thread *thread) { accept_queue.evt = NULL; log_debug(__func__); accept_arm(); return (0); } frr-7.2.1/ldpd/address.c0000644000000000000000000002361013610377563011732 00000000000000/* $OpenBSD$ */ /* * Copyright (c) 2009 Michele Marchetto * * Permission to use, copy, modify, and distribute this software for any * purpose with or without fee is hereby granted, provided that the above * copyright notice and this permission notice appear in all copies. * * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. */ #include #include "ldpd.h" #include "ldpe.h" #include "lde.h" #include "log.h" #include "ldp_debug.h" static void send_address(struct nbr *, int, struct if_addr_head *, unsigned int, int); static int gen_address_list_tlv(struct ibuf *, int, struct if_addr_head *, unsigned int); static int gen_mac_list_tlv(struct ibuf *, uint8_t *); static void address_list_add(struct if_addr_head *, struct if_addr *); static void address_list_clr(struct if_addr_head *); static void log_msg_address(int, uint16_t, struct nbr *, int, union ldpd_addr *); static void log_msg_mac_withdrawal(int, struct nbr *, uint8_t *); static void send_address(struct nbr *nbr, int af, struct if_addr_head *addr_list, unsigned int addr_count, int withdraw) { struct ibuf *buf; uint16_t msg_type; uint8_t addr_size; struct if_addr *if_addr; uint16_t size; unsigned int tlv_addr_count = 0; int err = 0; /* nothing to send */ if (LIST_EMPTY(addr_list)) return; if (!withdraw) msg_type = MSG_TYPE_ADDR; else msg_type = MSG_TYPE_ADDRWITHDRAW; switch (af) { case AF_INET: addr_size = sizeof(struct in_addr); break; case AF_INET6: addr_size = sizeof(struct in6_addr); break; default: fatalx("send_address: unknown af"); } while ((if_addr = LIST_FIRST(addr_list)) != NULL) { /* * Send as many addresses as possible - respect the session's * negotiated maximum pdu length. */ size = LDP_HDR_SIZE + LDP_MSG_SIZE + ADDR_LIST_SIZE; if (size + addr_count * addr_size <= nbr->max_pdu_len) tlv_addr_count = addr_count; else tlv_addr_count = (nbr->max_pdu_len - size) / addr_size; size += tlv_addr_count * addr_size; addr_count -= tlv_addr_count; if ((buf = ibuf_open(size)) == NULL) fatal(__func__); err |= gen_ldp_hdr(buf, size); size -= LDP_HDR_SIZE; err |= gen_msg_hdr(buf, msg_type, size); size -= LDP_MSG_SIZE; err |= gen_address_list_tlv(buf, af, addr_list, tlv_addr_count); (void)size; if (err) { address_list_clr(addr_list); ibuf_free(buf); return; } while ((if_addr = LIST_FIRST(addr_list)) != NULL) { log_msg_address(1, msg_type, nbr, af, &if_addr->addr); LIST_REMOVE(if_addr, entry); assert(if_addr != LIST_FIRST(addr_list)); free(if_addr); if (--tlv_addr_count == 0) break; } evbuf_enqueue(&nbr->tcp->wbuf, buf); /* no errors - update per neighbor message counters */ switch (msg_type) { case MSG_TYPE_ADDR: nbr->stats.addr_sent++; break; case MSG_TYPE_ADDRWITHDRAW: nbr->stats.addrwdraw_sent++; break; default: break; } } nbr_fsm(nbr, NBR_EVT_PDU_SENT); } void send_address_single(struct nbr *nbr, struct if_addr *if_addr, int withdraw) { struct if_addr_head addr_list; LIST_INIT(&addr_list); address_list_add(&addr_list, if_addr); send_address(nbr, if_addr->af, &addr_list, 1, withdraw); } void send_address_all(struct nbr *nbr, int af) { struct if_addr_head addr_list; struct if_addr *if_addr; unsigned int addr_count = 0; LIST_INIT(&addr_list); LIST_FOREACH(if_addr, &global.addr_list, entry) { if (if_addr->af != af) continue; address_list_add(&addr_list, if_addr); addr_count++; } send_address(nbr, af, &addr_list, addr_count, 0); } void send_mac_withdrawal(struct nbr *nbr, struct map *fec, uint8_t *mac) { struct ibuf *buf; uint16_t size; int err; size = LDP_HDR_SIZE + LDP_MSG_SIZE + ADDR_LIST_SIZE + len_fec_tlv(fec) + TLV_HDR_SIZE; if (mac) size += ETH_ALEN; if ((buf = ibuf_open(size)) == NULL) fatal(__func__); err = gen_ldp_hdr(buf, size); size -= LDP_HDR_SIZE; err |= gen_msg_hdr(buf, MSG_TYPE_ADDRWITHDRAW, size); err |= gen_address_list_tlv(buf, AF_INET, NULL, 0); err |= gen_fec_tlv(buf, fec); err |= gen_mac_list_tlv(buf, mac); if (err) { ibuf_free(buf); return; } log_msg_mac_withdrawal(1, nbr, mac); evbuf_enqueue(&nbr->tcp->wbuf, buf); nbr_fsm(nbr, NBR_EVT_PDU_SENT); } int recv_address(struct nbr *nbr, char *buf, uint16_t len) { struct ldp_msg msg; uint16_t msg_type; enum imsg_type type; struct address_list_tlv alt; uint16_t alt_len; uint16_t alt_family; struct lde_addr lde_addr; memcpy(&msg, buf, sizeof(msg)); msg_type = ntohs(msg.type); switch (msg_type) { case MSG_TYPE_ADDR: type = IMSG_ADDRESS_ADD; break; case MSG_TYPE_ADDRWITHDRAW: type = IMSG_ADDRESS_DEL; break; default: fatalx("recv_address: unexpected msg type"); } buf += LDP_MSG_SIZE; len -= LDP_MSG_SIZE; /* Address List TLV */ if (len < ADDR_LIST_SIZE) { session_shutdown(nbr, S_BAD_MSG_LEN, msg.id, msg.type); return (-1); } memcpy(&alt, buf, sizeof(alt)); alt_len = ntohs(alt.length); alt_family = ntohs(alt.family); if (alt_len > len - TLV_HDR_SIZE) { session_shutdown(nbr, S_BAD_TLV_LEN, msg.id, msg.type); return (-1); } if (ntohs(alt.type) != TLV_TYPE_ADDRLIST) { send_notification(nbr->tcp, S_MISS_MSG, msg.id, msg.type); return (-1); } switch (alt_family) { case AF_IPV4: if (!nbr->v4_enabled) /* just ignore the message */ return (0); break; case AF_IPV6: if (!nbr->v6_enabled) /* just ignore the message */ return (0); break; default: send_notification(nbr->tcp, S_UNSUP_ADDR, msg.id, msg.type); return (-1); } alt_len -= sizeof(alt.family); buf += sizeof(alt); len -= sizeof(alt); /* Process all received addresses */ while (alt_len > 0) { switch (alt_family) { case AF_IPV4: if (alt_len < sizeof(struct in_addr)) { session_shutdown(nbr, S_BAD_TLV_LEN, msg.id, msg.type); return (-1); } memset(&lde_addr, 0, sizeof(lde_addr)); lde_addr.af = AF_INET; memcpy(&lde_addr.addr, buf, sizeof(struct in_addr)); buf += sizeof(struct in_addr); len -= sizeof(struct in_addr); alt_len -= sizeof(struct in_addr); break; case AF_IPV6: if (alt_len < sizeof(struct in6_addr)) { session_shutdown(nbr, S_BAD_TLV_LEN, msg.id, msg.type); return (-1); } memset(&lde_addr, 0, sizeof(lde_addr)); lde_addr.af = AF_INET6; memcpy(&lde_addr.addr, buf, sizeof(struct in6_addr)); buf += sizeof(struct in6_addr); len -= sizeof(struct in6_addr); alt_len -= sizeof(struct in6_addr); break; default: fatalx("recv_address: unknown af"); } log_msg_address(0, msg_type, nbr, lde_addr.af, &lde_addr.addr); ldpe_imsg_compose_lde(type, nbr->peerid, 0, &lde_addr, sizeof(lde_addr)); } /* Optional Parameters */ while (len > 0) { struct tlv tlv; uint16_t tlv_type; uint16_t tlv_len; if (len < sizeof(tlv)) { session_shutdown(nbr, S_BAD_TLV_LEN, msg.id, msg.type); return (-1); } memcpy(&tlv, buf, TLV_HDR_SIZE); tlv_type = ntohs(tlv.type); tlv_len = ntohs(tlv.length); if (tlv_len + TLV_HDR_SIZE > len) { session_shutdown(nbr, S_BAD_TLV_LEN, msg.id, msg.type); return (-1); } buf += TLV_HDR_SIZE; len -= TLV_HDR_SIZE; switch (tlv_type) { default: if (!(ntohs(tlv.type) & UNKNOWN_FLAG)) send_notification_rtlvs(nbr, S_UNKNOWN_TLV, msg.id, msg.type, tlv_type, tlv_len, buf); /* ignore unknown tlv */ break; } buf += tlv_len; len -= tlv_len; } return (0); } static int gen_address_list_tlv(struct ibuf *buf, int af, struct if_addr_head *addr_list, unsigned int tlv_addr_count) { struct address_list_tlv alt; uint16_t addr_size; struct if_addr *if_addr; int err = 0; memset(&alt, 0, sizeof(alt)); alt.type = htons(TLV_TYPE_ADDRLIST); switch (af) { case AF_INET: alt.family = htons(AF_IPV4); addr_size = sizeof(struct in_addr); break; case AF_INET6: alt.family = htons(AF_IPV6); addr_size = sizeof(struct in6_addr); break; default: fatalx("gen_address_list_tlv: unknown af"); } alt.length = htons(sizeof(alt.family) + addr_size * tlv_addr_count); err |= ibuf_add(buf, &alt, sizeof(alt)); if (addr_list == NULL) return (err); LIST_FOREACH(if_addr, addr_list, entry) { err |= ibuf_add(buf, &if_addr->addr, addr_size); if (--tlv_addr_count == 0) break; } return (err); } static int gen_mac_list_tlv(struct ibuf *buf, uint8_t *mac) { struct tlv tlv; int err; memset(&tlv, 0, sizeof(tlv)); tlv.type = htons(TLV_TYPE_MAC_LIST); if (mac) tlv.length = htons(ETH_ALEN); err = ibuf_add(buf, &tlv, sizeof(tlv)); if (mac) err |= ibuf_add(buf, mac, ETH_ALEN); return (err); } static void address_list_add(struct if_addr_head *addr_list, struct if_addr *if_addr) { struct if_addr *new; new = malloc(sizeof(*new)); if (new == NULL) fatal(__func__); *new = *if_addr; LIST_INSERT_HEAD(addr_list, new, entry); } static void address_list_clr(struct if_addr_head *addr_list) { struct if_addr *if_addr; while ((if_addr = LIST_FIRST(addr_list)) != NULL) { LIST_REMOVE(if_addr, entry); assert(if_addr != LIST_FIRST(addr_list)); free(if_addr); } } static void log_msg_address(int out, uint16_t msg_type, struct nbr *nbr, int af, union ldpd_addr *addr) { debug_msg(out, "%s: lsr-id %s, address %s", msg_name(msg_type), inet_ntoa(nbr->id), log_addr(af, addr)); } static void log_msg_mac_withdrawal(int out, struct nbr *nbr, uint8_t *mac) { char buf[ETHER_ADDR_STRLEN]; debug_msg(out, "mac withdrawal: lsr-id %s, mac %s", inet_ntoa(nbr->id), (mac) ? prefix_mac2str((struct ethaddr *)mac, buf, sizeof(buf)) : "wildcard"); } frr-7.2.1/ldpd/adjacency.c0000644000000000000000000002166113610377563012232 00000000000000/* $OpenBSD$ */ /* * Copyright (c) 2013, 2015 Renato Westphal * Copyright (c) 2009 Michele Marchetto * Copyright (c) 2005 Claudio Jeker * Copyright (c) 2004, 2005, 2008 Esben Norby * * Permission to use, copy, modify, and distribute this software for any * purpose with or without fee is hereby granted, provided that the above * copyright notice and this permission notice appear in all copies. * * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. */ #include #include "ldpd.h" #include "ldpe.h" #include "log.h" static __inline int adj_compare(const struct adj *, const struct adj *); static int adj_itimer(struct thread *); static __inline int tnbr_compare(const struct tnbr *, const struct tnbr *); static void tnbr_del(struct ldpd_conf *, struct tnbr *); static void tnbr_start(struct tnbr *); static void tnbr_stop(struct tnbr *); static int tnbr_hello_timer(struct thread *); static void tnbr_start_hello_timer(struct tnbr *); static void tnbr_stop_hello_timer(struct tnbr *); RB_GENERATE(global_adj_head, adj, global_entry, adj_compare) RB_GENERATE(nbr_adj_head, adj, nbr_entry, adj_compare) RB_GENERATE(ia_adj_head, adj, ia_entry, adj_compare) RB_GENERATE(tnbr_head, tnbr, entry, tnbr_compare) static __inline int adj_compare(const struct adj *a, const struct adj *b) { if (adj_get_af(a) < adj_get_af(b)) return (-1); if (adj_get_af(a) > adj_get_af(b)) return (1); if (ntohl(a->lsr_id.s_addr) < ntohl(b->lsr_id.s_addr)) return (-1); if (ntohl(a->lsr_id.s_addr) > ntohl(b->lsr_id.s_addr)) return (1); if (a->source.type < b->source.type) return (-1); if (a->source.type > b->source.type) return (1); switch (a->source.type) { case HELLO_LINK: if (if_cmp_name_func(a->source.link.ia->iface->name, b->source.link.ia->iface->name) < 0) return (-1); if (if_cmp_name_func(a->source.link.ia->iface->name, b->source.link.ia->iface->name) > 0) return (1); return (ldp_addrcmp(a->source.link.ia->af, &a->source.link.src_addr, &b->source.link.src_addr)); case HELLO_TARGETED: return (ldp_addrcmp(a->source.target->af, &a->source.target->addr, &b->source.target->addr)); default: fatalx("adj_compare: unknown hello type"); } return (0); } struct adj * adj_new(struct in_addr lsr_id, struct hello_source *source, union ldpd_addr *addr) { struct adj *adj; log_debug("%s: lsr-id %s, %s", __func__, inet_ntoa(lsr_id), log_hello_src(source)); if ((adj = calloc(1, sizeof(*adj))) == NULL) fatal(__func__); adj->lsr_id = lsr_id; adj->nbr = NULL; adj->source = *source; adj->trans_addr = *addr; RB_INSERT(global_adj_head, &global.adj_tree, adj); switch (source->type) { case HELLO_LINK: RB_INSERT(ia_adj_head, &source->link.ia->adj_tree, adj); break; case HELLO_TARGETED: source->target->adj = adj; break; } return (adj); } void adj_del(struct adj *adj, uint32_t notif_status) { struct nbr *nbr = adj->nbr; log_debug("%s: lsr-id %s, %s (%s)", __func__, inet_ntoa(adj->lsr_id), log_hello_src(&adj->source), af_name(adj_get_af(adj))); adj_stop_itimer(adj); RB_REMOVE(global_adj_head, &global.adj_tree, adj); if (nbr) RB_REMOVE(nbr_adj_head, &nbr->adj_tree, adj); switch (adj->source.type) { case HELLO_LINK: RB_REMOVE(ia_adj_head, &adj->source.link.ia->adj_tree, adj); break; case HELLO_TARGETED: adj->source.target->adj = NULL; break; } free(adj); /* * If the neighbor still exists but none of its remaining * adjacencies (if any) are from the preferred address-family, * then delete it. */ if (nbr && nbr_adj_count(nbr, nbr->af) == 0) { session_shutdown(nbr, notif_status, 0, 0); nbr_del(nbr); } } struct adj * adj_find(struct in_addr lsr_id, struct hello_source *source) { struct adj adj; adj.lsr_id = lsr_id; adj.source = *source; return (RB_FIND(global_adj_head, &global.adj_tree, &adj)); } int adj_get_af(const struct adj *adj) { switch (adj->source.type) { case HELLO_LINK: return (adj->source.link.ia->af); case HELLO_TARGETED: return (adj->source.target->af); default: fatalx("adj_get_af: unknown hello type"); } } /* adjacency timers */ /* ARGSUSED */ static int adj_itimer(struct thread *thread) { struct adj *adj = THREAD_ARG(thread); adj->inactivity_timer = NULL; log_debug("%s: lsr-id %s", __func__, inet_ntoa(adj->lsr_id)); if (adj->source.type == HELLO_TARGETED) { if (!(adj->source.target->flags & F_TNBR_CONFIGURED) && adj->source.target->pw_count == 0) { /* remove dynamic targeted neighbor */ tnbr_del(leconf, adj->source.target); return (0); } } adj_del(adj, S_HOLDTIME_EXP); return (0); } void adj_start_itimer(struct adj *adj) { THREAD_TIMER_OFF(adj->inactivity_timer); adj->inactivity_timer = NULL; thread_add_timer(master, adj_itimer, adj, adj->holdtime, &adj->inactivity_timer); } void adj_stop_itimer(struct adj *adj) { THREAD_TIMER_OFF(adj->inactivity_timer); } /* targeted neighbors */ static __inline int tnbr_compare(const struct tnbr *a, const struct tnbr *b) { if (a->af < b->af) return (-1); if (a->af > b->af) return (1); return (ldp_addrcmp(a->af, &a->addr, &b->addr)); } struct tnbr * tnbr_new(int af, union ldpd_addr *addr) { struct tnbr *tnbr; if ((tnbr = calloc(1, sizeof(*tnbr))) == NULL) fatal(__func__); tnbr->af = af; tnbr->addr = *addr; tnbr->state = TNBR_STA_DOWN; return (tnbr); } static void tnbr_del(struct ldpd_conf *xconf, struct tnbr *tnbr) { tnbr_stop(tnbr); RB_REMOVE(tnbr_head, &xconf->tnbr_tree, tnbr); free(tnbr); } struct tnbr * tnbr_find(struct ldpd_conf *xconf, int af, union ldpd_addr *addr) { struct tnbr tnbr; tnbr.af = af; tnbr.addr = *addr; return (RB_FIND(tnbr_head, &xconf->tnbr_tree, &tnbr)); } struct tnbr * tnbr_check(struct ldpd_conf *xconf, struct tnbr *tnbr) { if (!(tnbr->flags & (F_TNBR_CONFIGURED|F_TNBR_DYNAMIC)) && tnbr->pw_count == 0) { tnbr_del(xconf, tnbr); return (NULL); } return (tnbr); } static void tnbr_start(struct tnbr *tnbr) { send_hello(HELLO_TARGETED, NULL, tnbr); tnbr_start_hello_timer(tnbr); tnbr->state = TNBR_STA_ACTIVE; } static void tnbr_stop(struct tnbr *tnbr) { tnbr_stop_hello_timer(tnbr); if (tnbr->adj) adj_del(tnbr->adj, S_SHUTDOWN); tnbr->state = TNBR_STA_DOWN; } void tnbr_update(struct tnbr *tnbr) { int socket_ok, rtr_id_ok; if ((ldp_af_global_get(&global, tnbr->af))->ldp_edisc_socket != -1) socket_ok = 1; else socket_ok = 0; if (ldp_rtr_id_get(leconf) != INADDR_ANY) rtr_id_ok = 1; else rtr_id_ok = 0; if (tnbr->state == TNBR_STA_DOWN) { if (!socket_ok || !rtr_id_ok) return; tnbr_start(tnbr); } else if (tnbr->state == TNBR_STA_ACTIVE) { if (socket_ok && rtr_id_ok) return; tnbr_stop(tnbr); } } void tnbr_update_all(int af) { struct tnbr *tnbr; /* update targeted neighbors */ RB_FOREACH(tnbr, tnbr_head, &leconf->tnbr_tree) if (tnbr->af == af || af == AF_UNSPEC) tnbr_update(tnbr); } uint16_t tnbr_get_hello_holdtime(struct tnbr *tnbr) { if ((ldp_af_conf_get(leconf, tnbr->af))->thello_holdtime != 0) return ((ldp_af_conf_get(leconf, tnbr->af))->thello_holdtime); return (leconf->thello_holdtime); } uint16_t tnbr_get_hello_interval(struct tnbr *tnbr) { if ((ldp_af_conf_get(leconf, tnbr->af))->thello_interval != 0) return ((ldp_af_conf_get(leconf, tnbr->af))->thello_interval); return (leconf->thello_interval); } /* target neighbors timers */ /* ARGSUSED */ static int tnbr_hello_timer(struct thread *thread) { struct tnbr *tnbr = THREAD_ARG(thread); tnbr->hello_timer = NULL; send_hello(HELLO_TARGETED, NULL, tnbr); tnbr_start_hello_timer(tnbr); return (0); } static void tnbr_start_hello_timer(struct tnbr *tnbr) { THREAD_TIMER_OFF(tnbr->hello_timer); tnbr->hello_timer = NULL; thread_add_timer(master, tnbr_hello_timer, tnbr, tnbr_get_hello_interval(tnbr), &tnbr->hello_timer); } static void tnbr_stop_hello_timer(struct tnbr *tnbr) { THREAD_TIMER_OFF(tnbr->hello_timer); } struct ctl_adj * adj_to_ctl(struct adj *adj) { static struct ctl_adj actl; actl.af = adj_get_af(adj); actl.id = adj->lsr_id; actl.type = adj->source.type; switch (adj->source.type) { case HELLO_LINK: memcpy(actl.ifname, adj->source.link.ia->iface->name, sizeof(actl.ifname)); actl.src_addr = adj->source.link.src_addr; break; case HELLO_TARGETED: actl.src_addr = adj->source.target->addr; break; } actl.holdtime = adj->holdtime; actl.holdtime_remaining = thread_timer_remain_second(adj->inactivity_timer); actl.trans_addr = adj->trans_addr; actl.ds_tlv = adj->ds_tlv; return (&actl); } frr-7.2.1/ldpd/control.c0000644000000000000000000001421713610377563011770 00000000000000/* $OpenBSD$ */ /* * Copyright (c) 2003, 2004 Henning Brauer * * Permission to use, copy, modify, and distribute this software for any * purpose with or without fee is hereby granted, provided that the above * copyright notice and this permission notice appear in all copies. * * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. */ #include #include #include "ldpd.h" #include "ldpe.h" #include "log.h" #include "control.h" #define CONTROL_BACKLOG 5 static int control_accept(struct thread *); static struct ctl_conn *control_connbyfd(int); static struct ctl_conn *control_connbypid(pid_t); static void control_close(int); static int control_dispatch_imsg(struct thread *); struct ctl_conns ctl_conns; static int control_fd; int control_init(char *path) { struct sockaddr_un s_un; int fd; mode_t old_umask; if ((fd = socket(AF_UNIX, SOCK_STREAM, 0)) == -1) { log_warn("%s: socket", __func__); return (-1); } sock_set_nonblock(fd); memset(&s_un, 0, sizeof(s_un)); s_un.sun_family = AF_UNIX; strlcpy(s_un.sun_path, path, sizeof(s_un.sun_path)); if (unlink(path) == -1) if (errno != ENOENT) { log_warn("%s: unlink %s", __func__, path); close(fd); return (-1); } old_umask = umask(S_IXUSR|S_IXGRP|S_IWOTH|S_IROTH|S_IXOTH); if (bind(fd, (struct sockaddr *)&s_un, sizeof(s_un)) == -1) { log_warn("%s: bind: %s", __func__, path); close(fd); umask(old_umask); return (-1); } umask(old_umask); if (chmod(path, S_IRUSR|S_IWUSR|S_IRGRP|S_IWGRP) == -1) { log_warn("%s: chmod", __func__); close(fd); (void)unlink(path); return (-1); } control_fd = fd; return (0); } int control_listen(void) { if (listen(control_fd, CONTROL_BACKLOG) == -1) { log_warn("%s: listen", __func__); return (-1); } return (accept_add(control_fd, control_accept, NULL)); } void control_cleanup(char *path) { accept_del(control_fd); close(control_fd); unlink(path); } /* ARGSUSED */ static int control_accept(struct thread *thread) { int connfd; socklen_t len; struct sockaddr_un s_un; struct ctl_conn *c; len = sizeof(s_un); if ((connfd = accept(THREAD_FD(thread), (struct sockaddr *)&s_un, &len)) == -1) { /* * Pause accept if we are out of file descriptors, or * libevent will haunt us here too. */ if (errno == ENFILE || errno == EMFILE) accept_pause(); else if (errno != EWOULDBLOCK && errno != EINTR && errno != ECONNABORTED) log_warn("%s: accept", __func__); return (0); } sock_set_nonblock(connfd); if ((c = calloc(1, sizeof(struct ctl_conn))) == NULL) { log_warn(__func__); close(connfd); return (0); } imsg_init(&c->iev.ibuf, connfd); c->iev.handler_read = control_dispatch_imsg; c->iev.ev_read = NULL; thread_add_read(master, c->iev.handler_read, &c->iev, c->iev.ibuf.fd, &c->iev.ev_read); c->iev.handler_write = ldp_write_handler; c->iev.ev_write = NULL; TAILQ_INSERT_TAIL(&ctl_conns, c, entry); return (0); } static struct ctl_conn * control_connbyfd(int fd) { struct ctl_conn *c; TAILQ_FOREACH(c, &ctl_conns, entry) { if (c->iev.ibuf.fd == fd) break; } return (c); } static struct ctl_conn * control_connbypid(pid_t pid) { struct ctl_conn *c; TAILQ_FOREACH(c, &ctl_conns, entry) { if (c->iev.ibuf.pid == pid) break; } return (c); } static void control_close(int fd) { struct ctl_conn *c; if ((c = control_connbyfd(fd)) == NULL) { log_warnx("%s: fd %d: not found", __func__, fd); return; } msgbuf_clear(&c->iev.ibuf.w); TAILQ_REMOVE(&ctl_conns, c, entry); THREAD_READ_OFF(c->iev.ev_read); THREAD_WRITE_OFF(c->iev.ev_write); close(c->iev.ibuf.fd); accept_unpause(); free(c); } /* ARGSUSED */ static int control_dispatch_imsg(struct thread *thread) { int fd = THREAD_FD(thread); struct ctl_conn *c; struct imsg imsg; ssize_t n; unsigned int ifidx; if ((c = control_connbyfd(fd)) == NULL) { log_warnx("%s: fd %d: not found", __func__, fd); return (0); } c->iev.ev_read = NULL; if (((n = imsg_read(&c->iev.ibuf)) == -1 && errno != EAGAIN) || n == 0) { control_close(fd); return (0); } for (;;) { if ((n = imsg_get(&c->iev.ibuf, &imsg)) == -1) { control_close(fd); return (0); } if (n == 0) break; switch (imsg.hdr.type) { case IMSG_CTL_FIB_COUPLE: case IMSG_CTL_FIB_DECOUPLE: case IMSG_CTL_RELOAD: case IMSG_CTL_KROUTE: case IMSG_CTL_KROUTE_ADDR: case IMSG_CTL_IFINFO: /* ignore */ break; case IMSG_CTL_SHOW_INTERFACE: if (imsg.hdr.len == IMSG_HEADER_SIZE + sizeof(ifidx)) { memcpy(&ifidx, imsg.data, sizeof(ifidx)); ldpe_iface_ctl(c, ifidx); imsg_compose_event(&c->iev, IMSG_CTL_END, 0, 0, -1, NULL, 0); } break; case IMSG_CTL_SHOW_DISCOVERY: ldpe_adj_ctl(c); break; case IMSG_CTL_SHOW_DISCOVERY_DTL: ldpe_adj_detail_ctl(c); break; case IMSG_CTL_SHOW_LIB: case IMSG_CTL_SHOW_L2VPN_PW: case IMSG_CTL_SHOW_L2VPN_BINDING: c->iev.ibuf.pid = imsg.hdr.pid; ldpe_imsg_compose_lde(imsg.hdr.type, 0, imsg.hdr.pid, imsg.data, imsg.hdr.len - IMSG_HEADER_SIZE); break; case IMSG_CTL_SHOW_NBR: ldpe_nbr_ctl(c); break; case IMSG_CTL_CLEAR_NBR: if (imsg.hdr.len != IMSG_HEADER_SIZE + sizeof(struct ctl_nbr)) break; nbr_clear_ctl(imsg.data); break; case IMSG_CTL_LOG_VERBOSE: /* ignore */ break; default: log_debug("%s: error handling imsg %d", __func__, imsg.hdr.type); break; } imsg_free(&imsg); } imsg_event_add(&c->iev); return (0); } int control_imsg_relay(struct imsg *imsg) { struct ctl_conn *c; if ((c = control_connbypid(imsg->hdr.pid)) == NULL) return (0); return (imsg_compose_event(&c->iev, imsg->hdr.type, 0, imsg->hdr.pid, -1, imsg->data, imsg->hdr.len - IMSG_HEADER_SIZE)); } frr-7.2.1/ldpd/control.h0000644000000000000000000000222113610377563011765 00000000000000/* $OpenBSD$ */ /* * Copyright (c) 2003, 2004 Henning Brauer * * Permission to use, copy, modify, and distribute this software for any * purpose with or without fee is hereby granted, provided that the above * copyright notice and this permission notice appear in all copies. * * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. */ #ifndef _CONTROL_H_ #define _CONTROL_H_ #include "queue.h" struct ctl_conn { TAILQ_ENTRY(ctl_conn) entry; struct imsgev iev; }; TAILQ_HEAD(ctl_conns, ctl_conn); extern struct ctl_conns ctl_conns; int control_init(char *); int control_listen(void); void control_cleanup(char *); int control_imsg_relay(struct imsg *); #endif /* _CONTROL_H_ */ frr-7.2.1/ldpd/hello.c0000644000000000000000000004061013610377563011407 00000000000000/* $OpenBSD$ */ /* * Copyright (c) 2013, 2016 Renato Westphal * Copyright (c) 2009 Michele Marchetto * * Permission to use, copy, modify, and distribute this software for any * purpose with or without fee is hereby granted, provided that the above * copyright notice and this permission notice appear in all copies. * * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. */ #include #include "ldpd.h" #include "ldpe.h" #include "log.h" #include "ldp_debug.h" static int gen_hello_prms_tlv(struct ibuf *buf, uint16_t, uint16_t); static int gen_opt4_hello_prms_tlv(struct ibuf *, uint16_t, uint32_t); static int gen_opt16_hello_prms_tlv(struct ibuf *, uint16_t, uint8_t *); static int gen_ds_hello_prms_tlv(struct ibuf *, uint32_t); static int tlv_decode_hello_prms(char *, uint16_t, uint16_t *, uint16_t *); static int tlv_decode_opt_hello_prms(char *, uint16_t, int *, int, union ldpd_addr *, uint32_t *, uint16_t *); int send_hello(enum hello_type type, struct iface_af *ia, struct tnbr *tnbr) { int af; union ldpd_addr dst; uint16_t size, holdtime = 0, flags = 0; int fd = 0; struct ibuf *buf; int err = 0; switch (type) { case HELLO_LINK: af = ia->af; holdtime = if_get_hello_holdtime(ia); flags = 0; fd = (ldp_af_global_get(&global, af))->ldp_disc_socket; /* multicast destination address */ switch (af) { case AF_INET: if (!(leconf->ipv4.flags & F_LDPD_AF_NO_GTSM)) flags |= F_HELLO_GTSM; dst.v4 = global.mcast_addr_v4; break; case AF_INET6: dst.v6 = global.mcast_addr_v6; break; default: fatalx("send_hello: unknown af"); } break; case HELLO_TARGETED: af = tnbr->af; holdtime = tnbr_get_hello_holdtime(tnbr); flags = F_HELLO_TARGETED; if ((tnbr->flags & F_TNBR_CONFIGURED) || tnbr->pw_count) flags |= F_HELLO_REQ_TARG; fd = (ldp_af_global_get(&global, af))->ldp_edisc_socket; /* unicast destination address */ dst = tnbr->addr; break; default: fatalx("send_hello: unknown hello type"); } /* calculate message size */ size = LDP_HDR_SIZE + LDP_MSG_SIZE + sizeof(struct hello_prms_tlv); switch (af) { case AF_INET: size += sizeof(struct hello_prms_opt4_tlv); break; case AF_INET6: size += sizeof(struct hello_prms_opt16_tlv); break; default: fatalx("send_hello: unknown af"); } size += sizeof(struct hello_prms_opt4_tlv); if (ldp_is_dual_stack(leconf)) size += sizeof(struct hello_prms_opt4_tlv); /* generate message */ if ((buf = ibuf_open(size)) == NULL) fatal(__func__); err |= gen_ldp_hdr(buf, size); size -= LDP_HDR_SIZE; err |= gen_msg_hdr(buf, MSG_TYPE_HELLO, size); err |= gen_hello_prms_tlv(buf, holdtime, flags); /* * RFC 7552 - Section 6.1: * "An LSR MUST include only the transport address whose address * family is the same as that of the IP packet carrying the Hello * message". */ switch (af) { case AF_INET: err |= gen_opt4_hello_prms_tlv(buf, TLV_TYPE_IPV4TRANSADDR, leconf->ipv4.trans_addr.v4.s_addr); break; case AF_INET6: err |= gen_opt16_hello_prms_tlv(buf, TLV_TYPE_IPV6TRANSADDR, leconf->ipv6.trans_addr.v6.s6_addr); break; default: fatalx("send_hello: unknown af"); } err |= gen_opt4_hello_prms_tlv(buf, TLV_TYPE_CONFIG, htonl(global.conf_seqnum)); /* * RFC 7552 - Section 6.1.1: * "A Dual-stack LSR (i.e., an LSR supporting Dual-stack LDP for a peer) * MUST include the Dual-Stack capability TLV in all of its LDP Hellos". */ if (ldp_is_dual_stack(leconf)) err |= gen_ds_hello_prms_tlv(buf, leconf->trans_pref); if (err) { ibuf_free(buf); return (-1); } switch (type) { case HELLO_LINK: debug_hello_send("iface %s (%s) holdtime %u", ia->iface->name, af_name(ia->af), holdtime); break; case HELLO_TARGETED: debug_hello_send("targeted-neighbor %s (%s) holdtime %u", log_addr(tnbr->af, &tnbr->addr), af_name(tnbr->af), holdtime); break; default: fatalx("send_hello: unknown hello type"); } send_packet(fd, af, &dst, ia, buf->buf, buf->wpos); ibuf_free(buf); return (0); } void recv_hello(struct in_addr lsr_id, struct ldp_msg *msg, int af, union ldpd_addr *src, struct iface *iface, int multicast, char *buf, uint16_t len) { struct adj *adj = NULL; struct nbr *nbr, *nbrt; uint16_t holdtime = 0, flags = 0; int tlvs_rcvd; int ds_tlv; union ldpd_addr trans_addr; uint32_t scope_id = 0; uint32_t conf_seqnum; uint16_t trans_pref; int r; struct hello_source source; struct iface_af *ia = NULL; struct tnbr *tnbr = NULL; r = tlv_decode_hello_prms(buf, len, &holdtime, &flags); if (r == -1) { log_debug("%s: lsr-id %s: failed to decode params", __func__, inet_ntoa(lsr_id)); return; } /* safety checks */ if (holdtime != 0 && holdtime < MIN_HOLDTIME) { log_debug("%s: lsr-id %s: invalid hello holdtime (%u)", __func__, inet_ntoa(lsr_id), holdtime); return; } if (multicast && (flags & F_HELLO_TARGETED)) { log_debug("%s: lsr-id %s: multicast targeted hello", __func__, inet_ntoa(lsr_id)); return; } if (!multicast && !((flags & F_HELLO_TARGETED))) { log_debug("%s: lsr-id %s: unicast link hello", __func__, inet_ntoa(lsr_id)); return; } buf += r; len -= r; r = tlv_decode_opt_hello_prms(buf, len, &tlvs_rcvd, af, &trans_addr, &conf_seqnum, &trans_pref); if (r == -1) { log_debug("%s: lsr-id %s: failed to decode optional params", __func__, inet_ntoa(lsr_id)); return; } if (r != len) { log_debug("%s: lsr-id %s: unexpected data in message", __func__, inet_ntoa(lsr_id)); return; } ds_tlv = (tlvs_rcvd & F_HELLO_TLV_RCVD_DS) ? 1 : 0; /* implicit transport address */ if (!(tlvs_rcvd & F_HELLO_TLV_RCVD_ADDR)) trans_addr = *src; if (bad_addr(af, &trans_addr)) { log_debug("%s: lsr-id %s: invalid transport address %s", __func__, inet_ntoa(lsr_id), log_addr(af, &trans_addr)); return; } if (af == AF_INET6 && IN6_IS_SCOPE_EMBED(&trans_addr.v6)) { /* * RFC 7552 - Section 6.1: * "An LSR MUST use a global unicast IPv6 address in an IPv6 * Transport Address optional object of outgoing targeted * Hellos and check for the same in incoming targeted Hellos * (i.e., MUST discard the targeted Hello if it failed the * check)". */ if (flags & F_HELLO_TARGETED) { log_debug("%s: lsr-id %s: invalid targeted hello " "transport address %s", __func__, inet_ntoa(lsr_id), log_addr(af, &trans_addr)); return; } scope_id = iface->ifindex; } memset(&source, 0, sizeof(source)); if (flags & F_HELLO_TARGETED) { /* * RFC 7552 - Section 5.2: * "The link-local IPv6 addresses MUST NOT be used as the * targeted LDP Hello packet's source or destination addresses". */ if (af == AF_INET6 && IN6_IS_SCOPE_EMBED(&src->v6)) { log_debug("%s: lsr-id %s: targeted hello with " "link-local source address", __func__, inet_ntoa(lsr_id)); return; } tnbr = tnbr_find(leconf, af, src); /* remove the dynamic tnbr if the 'R' bit was cleared */ if (tnbr && (tnbr->flags & F_TNBR_DYNAMIC) && !((flags & F_HELLO_REQ_TARG))) { tnbr->flags &= ~F_TNBR_DYNAMIC; tnbr = tnbr_check(leconf, tnbr); } if (!tnbr) { struct ldpd_af_conf *af_conf; if (!(flags & F_HELLO_REQ_TARG)) return; af_conf = ldp_af_conf_get(leconf, af); if (!(af_conf->flags & F_LDPD_AF_THELLO_ACCEPT)) return; if (ldpe_acl_check(af_conf->acl_thello_accept_from, af, src, (af == AF_INET) ? 32 : 128) != FILTER_PERMIT) return; tnbr = tnbr_new(af, src); tnbr->flags |= F_TNBR_DYNAMIC; tnbr_update(tnbr); RB_INSERT(tnbr_head, &leconf->tnbr_tree, tnbr); } source.type = HELLO_TARGETED; source.target = tnbr; } else { ia = iface_af_get(iface, af); source.type = HELLO_LINK; source.link.ia = ia; source.link.src_addr = *src; } debug_hello_recv("%s lsr-id %s transport-address %s holdtime %u%s", log_hello_src(&source), inet_ntoa(lsr_id), log_addr(af, &trans_addr), holdtime, (ds_tlv) ? " (dual stack TLV present)" : ""); adj = adj_find(lsr_id, &source); if (adj && adj->ds_tlv != ds_tlv) { /* * Transient condition, ignore packet and wait until adjacency * times out. */ return; } nbr = nbr_find_ldpid(lsr_id.s_addr); /* check dual-stack tlv */ if (ds_tlv && trans_pref != leconf->trans_pref) { /* * RFC 7552 - Section 6.1.1: * "If the Dual-Stack capability TLV is present and the remote * preference does not match the local preference (or does not * get recognized), then the LSR MUST discard the Hello message * and log an error. * If an LDP session was already in place, then the LSR MUST * send a fatal Notification message with status code of * 'Transport Connection Mismatch' and reset the session". */ log_debug("%s: lsr-id %s: remote transport preference does not " "match the local preference", __func__, inet_ntoa(lsr_id)); if (nbr) session_shutdown(nbr, S_TRANS_MISMTCH, msg->id, msg->type); if (adj) adj_del(adj, S_SHUTDOWN); return; } /* * Check for noncompliant dual-stack neighbor according to * RFC 7552 section 6.1.1. */ if (nbr && !ds_tlv) { switch (af) { case AF_INET: if (nbr_adj_count(nbr, AF_INET6) > 0) { session_shutdown(nbr, S_DS_NONCMPLNCE, msg->id, msg->type); return; } break; case AF_INET6: if (nbr_adj_count(nbr, AF_INET) > 0) { session_shutdown(nbr, S_DS_NONCMPLNCE, msg->id, msg->type); return; } break; default: fatalx("recv_hello: unknown af"); } } /* * Protections against misconfigured networks and buggy implementations. */ if (nbr && nbr->af == af && (ldp_addrcmp(af, &nbr->raddr, &trans_addr) || nbr->raddr_scope != scope_id)) { log_warnx("%s: lsr-id %s: hello packet advertising a different " "transport address", __func__, inet_ntoa(lsr_id)); if (adj) adj_del(adj, S_SHUTDOWN); return; } if (nbr == NULL) { nbrt = nbr_find_addr(af, &trans_addr); if (nbrt) { log_debug("%s: transport address %s is already being " "used by lsr-id %s", __func__, log_addr(af, &trans_addr), inet_ntoa(nbrt->id)); if (adj) adj_del(adj, S_SHUTDOWN); return; } } if (adj == NULL) { adj = adj_new(lsr_id, &source, &trans_addr); if (nbr) { adj->nbr = nbr; RB_INSERT(nbr_adj_head, &nbr->adj_tree, adj); } } adj->ds_tlv = ds_tlv; /* * If the hello adjacency's address-family doesn't match the local * preference, then an adjacency is still created but we don't attempt * to start an LDP session. */ if (nbr == NULL && (!ds_tlv || ((trans_pref == DUAL_STACK_LDPOV4 && af == AF_INET) || (trans_pref == DUAL_STACK_LDPOV6 && af == AF_INET6)))) nbr = nbr_new(lsr_id, af, ds_tlv, &trans_addr, scope_id); /* dynamic LDPv4 GTSM negotiation as per RFC 6720 */ if (nbr) { if (flags & F_HELLO_GTSM) nbr->flags |= F_NBR_GTSM_NEGOTIATED; else nbr->flags &= ~F_NBR_GTSM_NEGOTIATED; } /* update neighbor's configuration sequence number */ if (nbr && (tlvs_rcvd & F_HELLO_TLV_RCVD_CONF)) { if (conf_seqnum > nbr->conf_seqnum && nbr_pending_idtimer(nbr)) nbr_stop_idtimer(nbr); nbr->conf_seqnum = conf_seqnum; } /* always update the holdtime to properly handle runtime changes */ switch (source.type) { case HELLO_LINK: if (holdtime == 0) holdtime = LINK_DFLT_HOLDTIME; adj->holdtime = min(if_get_hello_holdtime(ia), holdtime); break; case HELLO_TARGETED: if (holdtime == 0) holdtime = TARGETED_DFLT_HOLDTIME; adj->holdtime = min(tnbr_get_hello_holdtime(tnbr), holdtime); } if (adj->holdtime != INFINITE_HOLDTIME) adj_start_itimer(adj); else adj_stop_itimer(adj); if (nbr && nbr->state == NBR_STA_PRESENT && !nbr_pending_idtimer(nbr) && nbr_session_active_role(nbr) && !nbr_pending_connect(nbr)) nbr_establish_connection(nbr); } static int gen_hello_prms_tlv(struct ibuf *buf, uint16_t holdtime, uint16_t flags) { struct hello_prms_tlv parms; memset(&parms, 0, sizeof(parms)); parms.type = htons(TLV_TYPE_COMMONHELLO); parms.length = htons(sizeof(parms.holdtime) + sizeof(parms.flags)); parms.holdtime = htons(holdtime); parms.flags = htons(flags); return (ibuf_add(buf, &parms, sizeof(parms))); } static int gen_opt4_hello_prms_tlv(struct ibuf *buf, uint16_t type, uint32_t value) { struct hello_prms_opt4_tlv parms; memset(&parms, 0, sizeof(parms)); parms.type = htons(type); parms.length = htons(sizeof(parms.value)); parms.value = value; return (ibuf_add(buf, &parms, sizeof(parms))); } static int gen_opt16_hello_prms_tlv(struct ibuf *buf, uint16_t type, uint8_t *value) { struct hello_prms_opt16_tlv parms; memset(&parms, 0, sizeof(parms)); parms.type = htons(type); parms.length = htons(sizeof(parms.value)); memcpy(&parms.value, value, sizeof(parms.value)); return (ibuf_add(buf, &parms, sizeof(parms))); } static int gen_ds_hello_prms_tlv(struct ibuf *buf, uint32_t value) { if (leconf->flags & F_LDPD_DS_CISCO_INTEROP) value = htonl(value); else value = htonl(value << 28); return (gen_opt4_hello_prms_tlv(buf, TLV_TYPE_DUALSTACK, value)); } static int tlv_decode_hello_prms(char *buf, uint16_t len, uint16_t *holdtime, uint16_t *flags) { struct hello_prms_tlv tlv; if (len < sizeof(tlv)) return (-1); memcpy(&tlv, buf, sizeof(tlv)); if (tlv.type != htons(TLV_TYPE_COMMONHELLO)) return (-1); if (ntohs(tlv.length) != sizeof(tlv) - TLV_HDR_SIZE) return (-1); *holdtime = ntohs(tlv.holdtime); *flags = ntohs(tlv.flags); return (sizeof(tlv)); } static int tlv_decode_opt_hello_prms(char *buf, uint16_t len, int *tlvs_rcvd, int af, union ldpd_addr *addr, uint32_t *conf_number, uint16_t *trans_pref) { struct tlv tlv; uint16_t tlv_len; int total = 0; *tlvs_rcvd = 0; memset(addr, 0, sizeof(*addr)); *conf_number = 0; *trans_pref = 0; /* * RFC 7552 - Section 6.1: * "An LSR SHOULD accept the Hello message that contains both IPv4 and * IPv6 Transport Address optional objects but MUST use only the * transport address whose address family is the same as that of the * IP packet carrying the Hello message. An LSR SHOULD accept only * the first Transport Address optional object for a given address * family in the received Hello message and ignore the rest if the * LSR receives more than one Transport Address optional object for a * given address family". */ while (len >= sizeof(tlv)) { memcpy(&tlv, buf, TLV_HDR_SIZE); tlv_len = ntohs(tlv.length); if (tlv_len + TLV_HDR_SIZE > len) return (-1); buf += TLV_HDR_SIZE; len -= TLV_HDR_SIZE; total += TLV_HDR_SIZE; switch (ntohs(tlv.type)) { case TLV_TYPE_IPV4TRANSADDR: if (tlv_len != sizeof(addr->v4)) return (-1); if (af != AF_INET) return (-1); if (*tlvs_rcvd & F_HELLO_TLV_RCVD_ADDR) break; memcpy(&addr->v4, buf, sizeof(addr->v4)); *tlvs_rcvd |= F_HELLO_TLV_RCVD_ADDR; break; case TLV_TYPE_IPV6TRANSADDR: if (tlv_len != sizeof(addr->v6)) return (-1); if (af != AF_INET6) return (-1); if (*tlvs_rcvd & F_HELLO_TLV_RCVD_ADDR) break; memcpy(&addr->v6, buf, sizeof(addr->v6)); *tlvs_rcvd |= F_HELLO_TLV_RCVD_ADDR; break; case TLV_TYPE_CONFIG: if (tlv_len != sizeof(uint32_t)) return (-1); memcpy(conf_number, buf, sizeof(uint32_t)); *tlvs_rcvd |= F_HELLO_TLV_RCVD_CONF; break; case TLV_TYPE_DUALSTACK: if (tlv_len != sizeof(uint32_t)) return (-1); /* * RFC 7552 - Section 6.1: * "A Single-stack LSR does not need to use the * Dual-Stack capability in Hello messages and SHOULD * ignore this capability if received". */ if (!ldp_is_dual_stack(leconf)) break; /* Shame on you, Cisco! */ if (leconf->flags & F_LDPD_DS_CISCO_INTEROP) { memcpy(trans_pref, buf + sizeof(uint16_t), sizeof(uint16_t)); *trans_pref = ntohs(*trans_pref); } else { memcpy(trans_pref, buf , sizeof(uint16_t)); *trans_pref = ntohs(*trans_pref) >> 12; } *tlvs_rcvd |= F_HELLO_TLV_RCVD_DS; break; default: /* if unknown flag set, ignore TLV */ if (!(ntohs(tlv.type) & UNKNOWN_FLAG)) return (-1); break; } buf += tlv_len; len -= tlv_len; total += tlv_len; } return (total); } frr-7.2.1/ldpd/init.c0000644000000000000000000002553013610377563011253 00000000000000/* $OpenBSD$ */ /* * Copyright (c) 2009 Michele Marchetto * * Permission to use, copy, modify, and distribute this software for any * purpose with or without fee is hereby granted, provided that the above * copyright notice and this permission notice appear in all copies. * * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. */ #include #include "ldpd.h" #include "ldpe.h" #include "log.h" #include "ldp_debug.h" static int gen_init_prms_tlv(struct ibuf *, struct nbr *); static int gen_cap_dynamic_tlv(struct ibuf *); static int gen_cap_twcard_tlv(struct ibuf *, int); static int gen_cap_unotif_tlv(struct ibuf *, int); void send_init(struct nbr *nbr) { struct ibuf *buf; uint16_t size; int err = 0; debug_msg_send("initialization: lsr-id %s", inet_ntoa(nbr->id)); size = LDP_HDR_SIZE + LDP_MSG_SIZE + SESS_PRMS_SIZE + CAP_TLV_DYNAMIC_SIZE + CAP_TLV_TWCARD_SIZE + CAP_TLV_UNOTIF_SIZE; if ((buf = ibuf_open(size)) == NULL) fatal(__func__); err |= gen_ldp_hdr(buf, size); size -= LDP_HDR_SIZE; err |= gen_msg_hdr(buf, MSG_TYPE_INIT, size); err |= gen_init_prms_tlv(buf, nbr); err |= gen_cap_dynamic_tlv(buf); err |= gen_cap_twcard_tlv(buf, 1); err |= gen_cap_unotif_tlv(buf, 1); if (err) { ibuf_free(buf); return; } evbuf_enqueue(&nbr->tcp->wbuf, buf); } int recv_init(struct nbr *nbr, char *buf, uint16_t len) { struct ldp_msg msg; struct sess_prms_tlv sess; uint16_t max_pdu_len; int caps_rcvd = 0; debug_msg_recv("initialization: lsr-id %s", inet_ntoa(nbr->id)); memcpy(&msg, buf, sizeof(msg)); buf += LDP_MSG_SIZE; len -= LDP_MSG_SIZE; if (len < SESS_PRMS_SIZE) { session_shutdown(nbr, S_BAD_MSG_LEN, msg.id, msg.type); return (-1); } memcpy(&sess, buf, sizeof(sess)); if (ntohs(sess.length) != SESS_PRMS_LEN) { session_shutdown(nbr, S_BAD_TLV_LEN, msg.id, msg.type); return (-1); } if (ntohs(sess.proto_version) != LDP_VERSION) { session_shutdown(nbr, S_BAD_PROTO_VER, msg.id, msg.type); return (-1); } if (ntohs(sess.keepalive_time) < MIN_KEEPALIVE) { session_shutdown(nbr, S_KEEPALIVE_BAD, msg.id, msg.type); return (-1); } if (sess.lsr_id != ldp_rtr_id_get(leconf) || ntohs(sess.lspace_id) != 0) { session_shutdown(nbr, S_NO_HELLO, msg.id, msg.type); return (-1); } buf += SESS_PRMS_SIZE; len -= SESS_PRMS_SIZE; /* Optional Parameters */ while (len > 0) { struct tlv tlv; uint16_t tlv_type; uint16_t tlv_len; if (len < sizeof(tlv)) { session_shutdown(nbr, S_BAD_TLV_LEN, msg.id, msg.type); return (-1); } memcpy(&tlv, buf, TLV_HDR_SIZE); tlv_type = ntohs(tlv.type); tlv_len = ntohs(tlv.length); if (tlv_len + TLV_HDR_SIZE > len) { session_shutdown(nbr, S_BAD_TLV_LEN, msg.id, msg.type); return (-1); } buf += TLV_HDR_SIZE; len -= TLV_HDR_SIZE; /* * RFC 5561 - Section 6: * "The S-bit of a Capability Parameter in an Initialization * message MUST be 1 and SHOULD be ignored on receipt". */ switch (tlv_type) { case TLV_TYPE_ATMSESSIONPAR: session_shutdown(nbr, S_BAD_TLV_VAL, msg.id, msg.type); return (-1); case TLV_TYPE_FRSESSION: session_shutdown(nbr, S_BAD_TLV_VAL, msg.id, msg.type); return (-1); case TLV_TYPE_DYNAMIC_CAP: if (tlv_len != CAP_TLV_DYNAMIC_LEN) { session_shutdown(nbr, S_BAD_TLV_LEN, msg.id, msg.type); return (-1); } if (caps_rcvd & F_CAP_TLV_RCVD_DYNAMIC) { session_shutdown(nbr, S_BAD_TLV_VAL, msg.id, msg.type); return (-1); } caps_rcvd |= F_CAP_TLV_RCVD_DYNAMIC; nbr->flags |= F_NBR_CAP_DYNAMIC; log_debug("%s: lsr-id %s announced the Dynamic " "Capability Announcement capability", __func__, inet_ntoa(nbr->id)); break; case TLV_TYPE_TWCARD_CAP: if (tlv_len != CAP_TLV_TWCARD_LEN) { session_shutdown(nbr, S_BAD_TLV_LEN, msg.id, msg.type); return (-1); } if (caps_rcvd & F_CAP_TLV_RCVD_TWCARD) { session_shutdown(nbr, S_BAD_TLV_VAL, msg.id, msg.type); return (-1); } caps_rcvd |= F_CAP_TLV_RCVD_TWCARD; nbr->flags |= F_NBR_CAP_TWCARD; log_debug("%s: lsr-id %s announced the Typed Wildcard " "FEC capability", __func__, inet_ntoa(nbr->id)); break; case TLV_TYPE_UNOTIF_CAP: if (tlv_len != CAP_TLV_UNOTIF_LEN) { session_shutdown(nbr, S_BAD_TLV_LEN, msg.id, msg.type); return (-1); } if (caps_rcvd & F_CAP_TLV_RCVD_UNOTIF) { session_shutdown(nbr, S_BAD_TLV_VAL, msg.id, msg.type); return (-1); } caps_rcvd |= F_CAP_TLV_RCVD_UNOTIF; nbr->flags |= F_NBR_CAP_UNOTIF; log_debug("%s: lsr-id %s announced the Unrecognized " "Notification capability", __func__, inet_ntoa(nbr->id)); break; default: if (!(ntohs(tlv.type) & UNKNOWN_FLAG)) send_notification_rtlvs(nbr, S_UNSSUPORTDCAP, msg.id, msg.type, tlv_type, tlv_len, buf); /* ignore unknown tlv */ break; } buf += tlv_len; len -= tlv_len; } nbr->keepalive = min(nbr_get_keepalive(nbr->af, nbr->id), ntohs(sess.keepalive_time)); max_pdu_len = ntohs(sess.max_pdu_len); /* * RFC 5036 - Section 3.5.3: * "A value of 255 or less specifies the default maximum length of * 4096 octets". */ if (max_pdu_len <= 255) max_pdu_len = LDP_MAX_LEN; nbr->max_pdu_len = min(max_pdu_len, LDP_MAX_LEN); nbr_fsm(nbr, NBR_EVT_INIT_RCVD); return (0); } void send_capability(struct nbr *nbr, uint16_t capability, int enable) { struct ibuf *buf; uint16_t size; int err = 0; log_debug("%s: lsr-id %s", __func__, inet_ntoa(nbr->id)); size = LDP_HDR_SIZE + LDP_MSG_SIZE + CAP_TLV_DYNAMIC_SIZE; if ((buf = ibuf_open(size)) == NULL) fatal(__func__); err |= gen_ldp_hdr(buf, size); size -= LDP_HDR_SIZE; err |= gen_msg_hdr(buf, MSG_TYPE_CAPABILITY, size); switch (capability) { case TLV_TYPE_TWCARD_CAP: err |= gen_cap_twcard_tlv(buf, enable); break; case TLV_TYPE_UNOTIF_CAP: err |= gen_cap_unotif_tlv(buf, enable); break; case TLV_TYPE_DYNAMIC_CAP: /* * RFC 5561 - Section 9: * "An LDP speaker MUST NOT include the Dynamic Capability * Announcement Parameter in Capability messages sent to * its peers". */ /* FALLTHROUGH */ default: fatalx("send_capability: unsupported capability"); } if (err) { ibuf_free(buf); return; } evbuf_enqueue(&nbr->tcp->wbuf, buf); nbr_fsm(nbr, NBR_EVT_PDU_SENT); nbr->stats.capability_sent++; } int recv_capability(struct nbr *nbr, char *buf, uint16_t len) { struct ldp_msg msg; int enable = 0; int caps_rcvd = 0; log_debug("%s: lsr-id %s", __func__, inet_ntoa(nbr->id)); memcpy(&msg, buf, sizeof(msg)); buf += LDP_MSG_SIZE; len -= LDP_MSG_SIZE; /* Optional Parameters */ while (len > 0) { struct tlv tlv; uint16_t tlv_type; uint16_t tlv_len; uint8_t reserved; if (len < sizeof(tlv)) { session_shutdown(nbr, S_BAD_TLV_LEN, msg.id, msg.type); return (-1); } memcpy(&tlv, buf, TLV_HDR_SIZE); tlv_type = ntohs(tlv.type); tlv_len = ntohs(tlv.length); if (tlv_len + TLV_HDR_SIZE > len) { session_shutdown(nbr, S_BAD_TLV_LEN, msg.id, msg.type); return (-1); } buf += TLV_HDR_SIZE; len -= TLV_HDR_SIZE; switch (tlv_type) { case TLV_TYPE_TWCARD_CAP: if (tlv_len != CAP_TLV_TWCARD_LEN) { session_shutdown(nbr, S_BAD_TLV_LEN, msg.id, msg.type); return (-1); } if (caps_rcvd & F_CAP_TLV_RCVD_TWCARD) { session_shutdown(nbr, S_BAD_TLV_VAL, msg.id, msg.type); return (-1); } caps_rcvd |= F_CAP_TLV_RCVD_TWCARD; memcpy(&reserved, buf, sizeof(reserved)); enable = reserved & STATE_BIT; if (enable) nbr->flags |= F_NBR_CAP_TWCARD; else nbr->flags &= ~F_NBR_CAP_TWCARD; log_debug("%s: lsr-id %s %s the Typed Wildcard FEC " "capability", __func__, inet_ntoa(nbr->id), (enable) ? "announced" : "withdrew"); break; case TLV_TYPE_UNOTIF_CAP: if (tlv_len != CAP_TLV_UNOTIF_LEN) { session_shutdown(nbr, S_BAD_TLV_LEN, msg.id, msg.type); return (-1); } if (caps_rcvd & F_CAP_TLV_RCVD_UNOTIF) { session_shutdown(nbr, S_BAD_TLV_VAL, msg.id, msg.type); return (-1); } caps_rcvd |= F_CAP_TLV_RCVD_UNOTIF; memcpy(&reserved, buf, sizeof(reserved)); enable = reserved & STATE_BIT; if (enable) nbr->flags |= F_NBR_CAP_UNOTIF; else nbr->flags &= ~F_NBR_CAP_UNOTIF; log_debug("%s: lsr-id %s %s the Unrecognized " "Notification capability", __func__, inet_ntoa(nbr->id), (enable) ? "announced" : "withdrew"); break; case TLV_TYPE_DYNAMIC_CAP: /* * RFC 5561 - Section 9: * "An LDP speaker that receives a Capability message * from a peer that includes the Dynamic Capability * Announcement Parameter SHOULD silently ignore the * parameter and process any other Capability Parameters * in the message". */ /* FALLTHROUGH */ default: if (!(ntohs(tlv.type) & UNKNOWN_FLAG)) send_notification_rtlvs(nbr, S_UNSSUPORTDCAP, msg.id, msg.type, tlv_type, tlv_len, buf); /* ignore unknown tlv */ break; } buf += tlv_len; len -= tlv_len; } nbr_fsm(nbr, NBR_EVT_PDU_RCVD); return (0); } static int gen_init_prms_tlv(struct ibuf *buf, struct nbr *nbr) { struct sess_prms_tlv parms; memset(&parms, 0, sizeof(parms)); parms.type = htons(TLV_TYPE_COMMONSESSION); parms.length = htons(SESS_PRMS_LEN); parms.proto_version = htons(LDP_VERSION); parms.keepalive_time = htons(nbr_get_keepalive(nbr->af, nbr->id)); parms.reserved = 0; parms.pvlim = 0; parms.max_pdu_len = 0; parms.lsr_id = nbr->id.s_addr; parms.lspace_id = 0; return (ibuf_add(buf, &parms, SESS_PRMS_SIZE)); } static int gen_cap_dynamic_tlv(struct ibuf *buf) { struct capability_tlv cap; memset(&cap, 0, sizeof(cap)); cap.type = htons(TLV_TYPE_DYNAMIC_CAP); cap.length = htons(CAP_TLV_DYNAMIC_LEN); /* the S-bit is always 1 for the Dynamic Capability Announcement */ cap.reserved = STATE_BIT; return (ibuf_add(buf, &cap, CAP_TLV_DYNAMIC_SIZE)); } static int gen_cap_twcard_tlv(struct ibuf *buf, int enable) { struct capability_tlv cap; memset(&cap, 0, sizeof(cap)); cap.type = htons(TLV_TYPE_TWCARD_CAP); cap.length = htons(CAP_TLV_TWCARD_LEN); if (enable) cap.reserved = STATE_BIT; return (ibuf_add(buf, &cap, CAP_TLV_TWCARD_SIZE)); } static int gen_cap_unotif_tlv(struct ibuf *buf, int enable) { struct capability_tlv cap; memset(&cap, 0, sizeof(cap)); cap.type = htons(TLV_TYPE_UNOTIF_CAP); cap.length = htons(CAP_TLV_UNOTIF_LEN); if (enable) cap.reserved = STATE_BIT; return (ibuf_add(buf, &cap, CAP_TLV_UNOTIF_SIZE)); } frr-7.2.1/ldpd/interface.c0000644000000000000000000003234213610377563012247 00000000000000/* $OpenBSD$ */ /* * Copyright (c) 2013, 2016 Renato Westphal * Copyright (c) 2005 Claudio Jeker * Copyright (c) 2004, 2005, 2008 Esben Norby * * Permission to use, copy, modify, and distribute this software for any * purpose with or without fee is hereby granted, provided that the above * copyright notice and this permission notice appear in all copies. * * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. */ #include #include "ldpd.h" #include "ldpe.h" #include "log.h" #include "sockopt.h" static __inline int iface_compare(const struct iface *, const struct iface *); static struct if_addr *if_addr_new(struct kaddr *); static struct if_addr *if_addr_lookup(struct if_addr_head *, struct kaddr *); static int if_start(struct iface *, int); static int if_reset(struct iface *, int); static void if_update_af(struct iface_af *); static int if_hello_timer(struct thread *); static void if_start_hello_timer(struct iface_af *); static void if_stop_hello_timer(struct iface_af *); static int if_join_ipv4_group(struct iface *, struct in_addr *); static int if_leave_ipv4_group(struct iface *, struct in_addr *); static int if_join_ipv6_group(struct iface *, struct in6_addr *); static int if_leave_ipv6_group(struct iface *, struct in6_addr *); RB_GENERATE(iface_head, iface, entry, iface_compare) static __inline int iface_compare(const struct iface *a, const struct iface *b) { return if_cmp_name_func(a->name, b->name); } struct iface * if_new(const char *name) { struct iface *iface; if ((iface = calloc(1, sizeof(*iface))) == NULL) fatal("if_new: calloc"); strlcpy(iface->name, name, sizeof(iface->name)); /* ipv4 */ iface->ipv4.af = AF_INET; iface->ipv4.iface = iface; iface->ipv4.enabled = 0; /* ipv6 */ iface->ipv6.af = AF_INET6; iface->ipv6.iface = iface; iface->ipv6.enabled = 0; return (iface); } void ldpe_if_init(struct iface *iface) { log_debug("%s: interface %s", __func__, iface->name); LIST_INIT(&iface->addr_list); /* ipv4 */ iface->ipv4.iface = iface; iface->ipv4.state = IF_STA_DOWN; RB_INIT(ia_adj_head, &iface->ipv4.adj_tree); /* ipv6 */ iface->ipv6.iface = iface; iface->ipv6.state = IF_STA_DOWN; RB_INIT(ia_adj_head, &iface->ipv6.adj_tree); } void ldpe_if_exit(struct iface *iface) { struct if_addr *if_addr; log_debug("%s: interface %s", __func__, iface->name); if (iface->ipv4.state == IF_STA_ACTIVE) if_reset(iface, AF_INET); if (iface->ipv6.state == IF_STA_ACTIVE) if_reset(iface, AF_INET6); while ((if_addr = LIST_FIRST(&iface->addr_list)) != NULL) { LIST_REMOVE(if_addr, entry); assert(if_addr != LIST_FIRST(&iface->addr_list)); free(if_addr); } } struct iface * if_lookup(struct ldpd_conf *xconf, unsigned short ifindex) { struct iface *iface; RB_FOREACH(iface, iface_head, &xconf->iface_tree) if (iface->ifindex == ifindex) return (iface); return (NULL); } struct iface * if_lookup_name(struct ldpd_conf *xconf, const char *ifname) { struct iface iface; strlcpy(iface.name, ifname, sizeof(iface.name)); return (RB_FIND(iface_head, &xconf->iface_tree, &iface)); } void if_update_info(struct iface *iface, struct kif *kif) { /* get type */ if (kif->flags & IFF_POINTOPOINT) iface->type = IF_TYPE_POINTOPOINT; if (kif->flags & IFF_BROADCAST && kif->flags & IFF_MULTICAST) iface->type = IF_TYPE_BROADCAST; /* get index and flags */ iface->ifindex = kif->ifindex; iface->operative = kif->operative; } struct iface_af * iface_af_get(struct iface *iface, int af) { switch (af) { case AF_INET: return (&iface->ipv4); case AF_INET6: return (&iface->ipv6); default: fatalx("iface_af_get: unknown af"); } } static struct if_addr * if_addr_new(struct kaddr *ka) { struct if_addr *if_addr; if ((if_addr = calloc(1, sizeof(*if_addr))) == NULL) fatal(__func__); if_addr->af = ka->af; if_addr->addr = ka->addr; if_addr->prefixlen = ka->prefixlen; if_addr->dstbrd = ka->dstbrd; return (if_addr); } static struct if_addr * if_addr_lookup(struct if_addr_head *addr_list, struct kaddr *ka) { struct if_addr *if_addr; int af = ka->af; LIST_FOREACH(if_addr, addr_list, entry) if (!ldp_addrcmp(af, &if_addr->addr, &ka->addr) && if_addr->prefixlen == ka->prefixlen && !ldp_addrcmp(af, &if_addr->dstbrd, &ka->dstbrd)) return (if_addr); return (NULL); } void if_addr_add(struct kaddr *ka) { struct iface *iface; struct if_addr *if_addr; struct nbr *nbr; if (if_addr_lookup(&global.addr_list, ka) == NULL) { if_addr = if_addr_new(ka); LIST_INSERT_HEAD(&global.addr_list, if_addr, entry); RB_FOREACH(nbr, nbr_id_head, &nbrs_by_id) { if (nbr->state != NBR_STA_OPER) continue; if (if_addr->af == AF_INET && !nbr->v4_enabled) continue; if (if_addr->af == AF_INET6 && !nbr->v6_enabled) continue; send_address_single(nbr, if_addr, 0); } } iface = if_lookup_name(leconf, ka->ifname); if (iface) { if (ka->af == AF_INET6 && IN6_IS_ADDR_LINKLOCAL(&ka->addr.v6)) iface->linklocal = ka->addr.v6; if (if_addr_lookup(&iface->addr_list, ka) == NULL) { if_addr = if_addr_new(ka); LIST_INSERT_HEAD(&iface->addr_list, if_addr, entry); ldp_if_update(iface, if_addr->af); } } } void if_addr_del(struct kaddr *ka) { struct iface *iface; struct if_addr *if_addr; struct nbr *nbr; iface = if_lookup_name(leconf, ka->ifname); if (iface) { if (ka->af == AF_INET6 && IN6_ARE_ADDR_EQUAL(&iface->linklocal, &ka->addr.v6)) memset(&iface->linklocal, 0, sizeof(iface->linklocal)); if_addr = if_addr_lookup(&iface->addr_list, ka); if (if_addr) { LIST_REMOVE(if_addr, entry); ldp_if_update(iface, if_addr->af); free(if_addr); } } if_addr = if_addr_lookup(&global.addr_list, ka); if (if_addr) { RB_FOREACH(nbr, nbr_id_head, &nbrs_by_id) { if (nbr->state != NBR_STA_OPER) continue; if (if_addr->af == AF_INET && !nbr->v4_enabled) continue; if (if_addr->af == AF_INET6 && !nbr->v6_enabled) continue; send_address_single(nbr, if_addr, 1); } LIST_REMOVE(if_addr, entry); free(if_addr); } } static int if_start(struct iface *iface, int af) { struct iface_af *ia; struct timeval now; log_debug("%s: %s address-family %s", __func__, iface->name, af_name(af)); ia = iface_af_get(iface, af); gettimeofday(&now, NULL); ia->uptime = now.tv_sec; switch (af) { case AF_INET: if (if_join_ipv4_group(iface, &global.mcast_addr_v4)) return (-1); break; case AF_INET6: if (if_join_ipv6_group(iface, &global.mcast_addr_v6)) return (-1); break; default: fatalx("if_start: unknown af"); } send_hello(HELLO_LINK, ia, NULL); if_start_hello_timer(ia); ia->state = IF_STA_ACTIVE; return (0); } static int if_reset(struct iface *iface, int af) { struct iface_af *ia; struct adj *adj; log_debug("%s: %s address-family %s", __func__, iface->name, af_name(af)); ia = iface_af_get(iface, af); if_stop_hello_timer(ia); while (!RB_EMPTY(ia_adj_head, &ia->adj_tree)) { adj = RB_ROOT(ia_adj_head, &ia->adj_tree); adj_del(adj, S_SHUTDOWN); } /* try to cleanup */ switch (af) { case AF_INET: if (global.ipv4.ldp_disc_socket != -1) if_leave_ipv4_group(iface, &global.mcast_addr_v4); break; case AF_INET6: if (global.ipv6.ldp_disc_socket != -1) if_leave_ipv6_group(iface, &global.mcast_addr_v6); break; default: fatalx("if_reset: unknown af"); } ia->state = IF_STA_DOWN; return (0); } static void if_update_af(struct iface_af *ia) { int addr_ok = 0, socket_ok, rtr_id_ok; struct if_addr *if_addr; switch (ia->af) { case AF_INET: /* * NOTE: for LDPv4, each interface should have at least one * valid IP address otherwise they can not be enabled. */ LIST_FOREACH(if_addr, &ia->iface->addr_list, entry) { if (if_addr->af == AF_INET) { addr_ok = 1; break; } } break; case AF_INET6: /* for IPv6 the link-local address is enough. */ if (IN6_IS_ADDR_LINKLOCAL(&ia->iface->linklocal)) addr_ok = 1; break; default: fatalx("if_update_af: unknown af"); } if ((ldp_af_global_get(&global, ia->af))->ldp_disc_socket != -1) socket_ok = 1; else socket_ok = 0; if (ldp_rtr_id_get(leconf) != INADDR_ANY) rtr_id_ok = 1; else rtr_id_ok = 0; if (ia->state == IF_STA_DOWN) { if (!ia->enabled || !ia->iface->operative || !addr_ok || !socket_ok || !rtr_id_ok) return; if_start(ia->iface, ia->af); } else if (ia->state == IF_STA_ACTIVE) { if (ia->enabled && ia->iface->operative && addr_ok && socket_ok && rtr_id_ok) return; if_reset(ia->iface, ia->af); } } void ldp_if_update(struct iface *iface, int af) { if (af == AF_INET || af == AF_UNSPEC) if_update_af(&iface->ipv4); if (af == AF_INET6 || af == AF_UNSPEC) if_update_af(&iface->ipv6); } void if_update_all(int af) { struct iface *iface; RB_FOREACH(iface, iface_head, &leconf->iface_tree) ldp_if_update(iface, af); } uint16_t if_get_hello_holdtime(struct iface_af *ia) { if (ia->hello_holdtime != 0) return (ia->hello_holdtime); if ((ldp_af_conf_get(leconf, ia->af))->lhello_holdtime != 0) return ((ldp_af_conf_get(leconf, ia->af))->lhello_holdtime); return (leconf->lhello_holdtime); } uint16_t if_get_hello_interval(struct iface_af *ia) { if (ia->hello_interval != 0) return (ia->hello_interval); if ((ldp_af_conf_get(leconf, ia->af))->lhello_interval != 0) return ((ldp_af_conf_get(leconf, ia->af))->lhello_interval); return (leconf->lhello_interval); } /* timers */ /* ARGSUSED */ static int if_hello_timer(struct thread *thread) { struct iface_af *ia = THREAD_ARG(thread); ia->hello_timer = NULL; send_hello(HELLO_LINK, ia, NULL); if_start_hello_timer(ia); return (0); } static void if_start_hello_timer(struct iface_af *ia) { THREAD_TIMER_OFF(ia->hello_timer); ia->hello_timer = NULL; thread_add_timer(master, if_hello_timer, ia, if_get_hello_interval(ia), &ia->hello_timer); } static void if_stop_hello_timer(struct iface_af *ia) { THREAD_TIMER_OFF(ia->hello_timer); } struct ctl_iface * if_to_ctl(struct iface_af *ia) { static struct ctl_iface ictl; struct timeval now; struct adj *adj; ictl.af = ia->af; memcpy(ictl.name, ia->iface->name, sizeof(ictl.name)); ictl.ifindex = ia->iface->ifindex; ictl.state = ia->state; ictl.type = ia->iface->type; ictl.hello_holdtime = if_get_hello_holdtime(ia); ictl.hello_interval = if_get_hello_interval(ia); gettimeofday(&now, NULL); if (ia->state != IF_STA_DOWN && ia->uptime != 0) { ictl.uptime = now.tv_sec - ia->uptime; } else ictl.uptime = 0; ictl.adj_cnt = 0; RB_FOREACH(adj, ia_adj_head, &ia->adj_tree) ictl.adj_cnt++; return (&ictl); } /* multicast membership sockopts */ in_addr_t if_get_ipv4_addr(struct iface *iface) { struct if_addr *if_addr; LIST_FOREACH(if_addr, &iface->addr_list, entry) if (if_addr->af == AF_INET) return (if_addr->addr.v4.s_addr); return (INADDR_ANY); } static int if_join_ipv4_group(struct iface *iface, struct in_addr *addr) { struct in_addr if_addr; log_debug("%s: interface %s addr %s", __func__, iface->name, inet_ntoa(*addr)); if_addr.s_addr = if_get_ipv4_addr(iface); if (setsockopt_ipv4_multicast(global.ipv4.ldp_disc_socket, IP_ADD_MEMBERSHIP, if_addr, addr->s_addr, iface->ifindex) < 0) { log_warn("%s: error IP_ADD_MEMBERSHIP, interface %s address %s", __func__, iface->name, inet_ntoa(*addr)); return (-1); } return (0); } static int if_leave_ipv4_group(struct iface *iface, struct in_addr *addr) { struct in_addr if_addr; log_debug("%s: interface %s addr %s", __func__, iface->name, inet_ntoa(*addr)); if_addr.s_addr = if_get_ipv4_addr(iface); if (setsockopt_ipv4_multicast(global.ipv4.ldp_disc_socket, IP_DROP_MEMBERSHIP, if_addr, addr->s_addr, iface->ifindex) < 0) { log_warn("%s: error IP_DROP_MEMBERSHIP, interface %s " "address %s", __func__, iface->name, inet_ntoa(*addr)); return (-1); } return (0); } static int if_join_ipv6_group(struct iface *iface, struct in6_addr *addr) { struct ipv6_mreq mreq; log_debug("%s: interface %s addr %s", __func__, iface->name, log_in6addr(addr)); mreq.ipv6mr_multiaddr = *addr; mreq.ipv6mr_interface = iface->ifindex; if (setsockopt(global.ipv6.ldp_disc_socket, IPPROTO_IPV6, IPV6_JOIN_GROUP, &mreq, sizeof(mreq)) < 0) { log_warn("%s: error IPV6_JOIN_GROUP, interface %s address %s", __func__, iface->name, log_in6addr(addr)); return (-1); } return (0); } static int if_leave_ipv6_group(struct iface *iface, struct in6_addr *addr) { struct ipv6_mreq mreq; log_debug("%s: interface %s addr %s", __func__, iface->name, log_in6addr(addr)); mreq.ipv6mr_multiaddr = *addr; mreq.ipv6mr_interface = iface->ifindex; if (setsockopt(global.ipv6.ldp_disc_socket, IPPROTO_IPV6, IPV6_LEAVE_GROUP, (void *)&mreq, sizeof(mreq)) < 0) { log_warn("%s: error IPV6_LEAVE_GROUP, interface %s address %s", __func__, iface->name, log_in6addr(addr)); return (-1); } return (0); } frr-7.2.1/ldpd/keepalive.c0000644000000000000000000000323013610377563012246 00000000000000/* $OpenBSD$ */ /* * Copyright (c) 2009 Michele Marchetto * * Permission to use, copy, modify, and distribute this software for any * purpose with or without fee is hereby granted, provided that the above * copyright notice and this permission notice appear in all copies. * * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. */ #include #include "ldpd.h" #include "ldpe.h" #include "log.h" #include "ldp_debug.h" void send_keepalive(struct nbr *nbr) { struct ibuf *buf; uint16_t size; size = LDP_HDR_SIZE + LDP_MSG_SIZE; if ((buf = ibuf_open(size)) == NULL) fatal(__func__); gen_ldp_hdr(buf, size); size -= LDP_HDR_SIZE; gen_msg_hdr(buf, MSG_TYPE_KEEPALIVE, size); debug_kalive_send("keepalive: lsr-id %s", inet_ntoa(nbr->id)); evbuf_enqueue(&nbr->tcp->wbuf, buf); nbr->stats.kalive_sent++; } int recv_keepalive(struct nbr *nbr, char *buf, uint16_t len) { struct ldp_msg msg; memcpy(&msg, buf, sizeof(msg)); if (len != LDP_MSG_SIZE) { session_shutdown(nbr, S_BAD_MSG_LEN, msg.id, msg.type); return (-1); } debug_kalive_recv("keepalive: lsr-id %s", inet_ntoa(nbr->id)); if (nbr->state != NBR_STA_OPER) nbr_fsm(nbr, NBR_EVT_KEEPALIVE_RCVD); return (0); } frr-7.2.1/ldpd/l2vpn.c0000644000000000000000000003657113610377563011360 00000000000000/* $OpenBSD$ */ /* * Copyright (c) 2015 Renato Westphal * Copyright (c) 2009 Michele Marchetto * Copyright (c) 2005 Claudio Jeker * Copyright (c) 2004, 2005, 2008 Esben Norby * * Permission to use, copy, modify, and distribute this software for any * purpose with or without fee is hereby granted, provided that the above * copyright notice and this permission notice appear in all copies. * * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. */ #include #include "ldpd.h" #include "ldpe.h" #include "lde.h" #include "log.h" static void l2vpn_pw_fec(struct l2vpn_pw *, struct fec *); static __inline int l2vpn_compare(const struct l2vpn *, const struct l2vpn *); static __inline int l2vpn_if_compare(const struct l2vpn_if *, const struct l2vpn_if *); static __inline int l2vpn_pw_compare(const struct l2vpn_pw *, const struct l2vpn_pw *); RB_GENERATE(l2vpn_head, l2vpn, entry, l2vpn_compare) RB_GENERATE(l2vpn_if_head, l2vpn_if, entry, l2vpn_if_compare) RB_GENERATE(l2vpn_pw_head, l2vpn_pw, entry, l2vpn_pw_compare) static __inline int l2vpn_compare(const struct l2vpn *a, const struct l2vpn *b) { return (strcmp(a->name, b->name)); } struct l2vpn * l2vpn_new(const char *name) { struct l2vpn *l2vpn; if ((l2vpn = calloc(1, sizeof(*l2vpn))) == NULL) fatal("l2vpn_new: calloc"); strlcpy(l2vpn->name, name, sizeof(l2vpn->name)); /* set default values */ l2vpn->mtu = DEFAULT_L2VPN_MTU; l2vpn->pw_type = DEFAULT_PW_TYPE; RB_INIT(l2vpn_if_head, &l2vpn->if_tree); RB_INIT(l2vpn_pw_head, &l2vpn->pw_tree); RB_INIT(l2vpn_pw_head, &l2vpn->pw_inactive_tree); return (l2vpn); } struct l2vpn * l2vpn_find(struct ldpd_conf *xconf, const char *name) { struct l2vpn l2vpn; strlcpy(l2vpn.name, name, sizeof(l2vpn.name)); return (RB_FIND(l2vpn_head, &xconf->l2vpn_tree, &l2vpn)); } void l2vpn_del(struct l2vpn *l2vpn) { struct l2vpn_if *lif; struct l2vpn_pw *pw; while (!RB_EMPTY(l2vpn_if_head, &l2vpn->if_tree)) { lif = RB_ROOT(l2vpn_if_head, &l2vpn->if_tree); RB_REMOVE(l2vpn_if_head, &l2vpn->if_tree, lif); free(lif); } while (!RB_EMPTY(l2vpn_pw_head, &l2vpn->pw_tree)) { pw = RB_ROOT(l2vpn_pw_head, &l2vpn->pw_tree); RB_REMOVE(l2vpn_pw_head, &l2vpn->pw_tree, pw); free(pw); } while (!RB_EMPTY(l2vpn_pw_head, &l2vpn->pw_inactive_tree)) { pw = RB_ROOT(l2vpn_pw_head, &l2vpn->pw_inactive_tree); RB_REMOVE(l2vpn_pw_head, &l2vpn->pw_inactive_tree, pw); free(pw); } free(l2vpn); } void l2vpn_init(struct l2vpn *l2vpn) { struct l2vpn_pw *pw; RB_FOREACH(pw, l2vpn_pw_head, &l2vpn->pw_tree) l2vpn_pw_init(pw); } void l2vpn_exit(struct l2vpn *l2vpn) { struct l2vpn_pw *pw; RB_FOREACH(pw, l2vpn_pw_head, &l2vpn->pw_tree) l2vpn_pw_exit(pw); } static __inline int l2vpn_if_compare(const struct l2vpn_if *a, const struct l2vpn_if *b) { return if_cmp_name_func(a->ifname, b->ifname); } struct l2vpn_if * l2vpn_if_new(struct l2vpn *l2vpn, const char *ifname) { struct l2vpn_if *lif; if ((lif = calloc(1, sizeof(*lif))) == NULL) fatal("l2vpn_if_new: calloc"); lif->l2vpn = l2vpn; strlcpy(lif->ifname, ifname, sizeof(lif->ifname)); return (lif); } struct l2vpn_if * l2vpn_if_find(struct l2vpn *l2vpn, const char *ifname) { struct l2vpn_if lif; strlcpy(lif.ifname, ifname, sizeof(lif.ifname)); return (RB_FIND(l2vpn_if_head, &l2vpn->if_tree, &lif)); } void l2vpn_if_update_info(struct l2vpn_if *lif, struct kif *kif) { lif->ifindex = kif->ifindex; lif->operative = kif->operative; memcpy(lif->mac, kif->mac, sizeof(lif->mac)); } void l2vpn_if_update(struct l2vpn_if *lif) { struct l2vpn *l2vpn = lif->l2vpn; struct l2vpn_pw *pw; struct map fec; struct nbr *nbr; if (lif->operative) return; RB_FOREACH(pw, l2vpn_pw_head, &l2vpn->pw_tree) { nbr = nbr_find_ldpid(pw->lsr_id.s_addr); if (nbr == NULL) continue; memset(&fec, 0, sizeof(fec)); fec.type = MAP_TYPE_PWID; fec.fec.pwid.type = l2vpn->pw_type; fec.fec.pwid.group_id = 0; fec.flags |= F_MAP_PW_ID; fec.fec.pwid.pwid = pw->pwid; send_mac_withdrawal(nbr, &fec, lif->mac); } } static __inline int l2vpn_pw_compare(const struct l2vpn_pw *a, const struct l2vpn_pw *b) { return if_cmp_name_func(a->ifname, b->ifname); } struct l2vpn_pw * l2vpn_pw_new(struct l2vpn *l2vpn, const char *ifname) { struct l2vpn_pw *pw; if ((pw = calloc(1, sizeof(*pw))) == NULL) fatal("l2vpn_pw_new: calloc"); pw->l2vpn = l2vpn; strlcpy(pw->ifname, ifname, sizeof(pw->ifname)); return (pw); } struct l2vpn_pw * l2vpn_pw_find(struct l2vpn *l2vpn, const char *ifname) { struct l2vpn_pw *pw; struct l2vpn_pw s; strlcpy(s.ifname, ifname, sizeof(s.ifname)); pw = RB_FIND(l2vpn_pw_head, &l2vpn->pw_tree, &s); if (pw) return (pw); return (RB_FIND(l2vpn_pw_head, &l2vpn->pw_inactive_tree, &s)); } struct l2vpn_pw * l2vpn_pw_find_active(struct l2vpn *l2vpn, const char *ifname) { struct l2vpn_pw s; strlcpy(s.ifname, ifname, sizeof(s.ifname)); return (RB_FIND(l2vpn_pw_head, &l2vpn->pw_tree, &s)); } struct l2vpn_pw * l2vpn_pw_find_inactive(struct l2vpn *l2vpn, const char *ifname) { struct l2vpn_pw s; strlcpy(s.ifname, ifname, sizeof(s.ifname)); return (RB_FIND(l2vpn_pw_head, &l2vpn->pw_inactive_tree, &s)); } void l2vpn_pw_update_info(struct l2vpn_pw *pw, struct kif *kif) { pw->ifindex = kif->ifindex; } void l2vpn_pw_init(struct l2vpn_pw *pw) { struct fec fec; struct zapi_pw zpw; l2vpn_pw_reset(pw); pw2zpw(pw, &zpw); lde_imsg_compose_parent(IMSG_KPW_ADD, 0, &zpw, sizeof(zpw)); l2vpn_pw_fec(pw, &fec); lde_kernel_insert(&fec, AF_INET, (union ldpd_addr*)&pw->lsr_id, 0, 0, 0, (void *)pw); lde_kernel_update(&fec); } void l2vpn_pw_exit(struct l2vpn_pw *pw) { struct fec fec; struct zapi_pw zpw; l2vpn_pw_fec(pw, &fec); lde_kernel_remove(&fec, AF_INET, (union ldpd_addr*)&pw->lsr_id, 0, 0); lde_kernel_update(&fec); pw2zpw(pw, &zpw); lde_imsg_compose_parent(IMSG_KPW_DELETE, 0, &zpw, sizeof(zpw)); } static void l2vpn_pw_fec(struct l2vpn_pw *pw, struct fec *fec) { memset(fec, 0, sizeof(*fec)); fec->type = FEC_TYPE_PWID; fec->u.pwid.type = pw->l2vpn->pw_type; fec->u.pwid.pwid = pw->pwid; fec->u.pwid.lsr_id = pw->lsr_id; } void l2vpn_pw_reset(struct l2vpn_pw *pw) { pw->remote_group = 0; pw->remote_mtu = 0; pw->local_status = PW_FORWARDING; pw->remote_status = PW_NOT_FORWARDING; if (pw->flags & F_PW_CWORD_CONF) pw->flags |= F_PW_CWORD; else pw->flags &= ~F_PW_CWORD; if (pw->flags & F_PW_STATUSTLV_CONF) pw->flags |= F_PW_STATUSTLV; else pw->flags &= ~F_PW_STATUSTLV; } int l2vpn_pw_ok(struct l2vpn_pw *pw, struct fec_nh *fnh) { /* check for a remote label */ if (fnh->remote_label == NO_LABEL) { log_warnx("%s: pseudowire %s: no remote label", __func__, pw->ifname); return (0); } /* MTUs must match */ if (pw->l2vpn->mtu != pw->remote_mtu) { log_warnx("%s: pseudowire %s: MTU mismatch detected", __func__, pw->ifname); return (0); } /* check pw status if applicable */ if ((pw->flags & F_PW_STATUSTLV) && pw->remote_status != PW_FORWARDING) { log_warnx("%s: pseudowire %s: remote end is down", __func__, pw->ifname); return (0); } return (1); } int l2vpn_pw_negotiate(struct lde_nbr *ln, struct fec_node *fn, struct map *map) { struct l2vpn_pw *pw; struct status_tlv st; /* NOTE: thanks martini & friends for all this mess */ pw = (struct l2vpn_pw *) fn->data; if (pw == NULL) /* * pseudowire not configured, return and record * the mapping later */ return (0); /* RFC4447 - Section 6.2: control word negotiation */ if (fec_find(&ln->sent_map, &fn->fec)) { if ((map->flags & F_MAP_PW_CWORD) && !(pw->flags & F_PW_CWORD_CONF)) { /* ignore the received label mapping */ return (1); } else if (!(map->flags & F_MAP_PW_CWORD) && (pw->flags & F_PW_CWORD_CONF)) { /* append a "Wrong C-bit" status code */ st.status_code = S_WRONG_CBIT; st.msg_id = map->msg_id; st.msg_type = htons(MSG_TYPE_LABELMAPPING); lde_send_labelwithdraw(ln, fn, NULL, &st); pw->flags &= ~F_PW_CWORD; lde_send_labelmapping(ln, fn, 1); } } else if (map->flags & F_MAP_PW_CWORD) { if (pw->flags & F_PW_CWORD_CONF) pw->flags |= F_PW_CWORD; else /* act as if no label mapping had been received */ return (1); } else pw->flags &= ~F_PW_CWORD; /* RFC4447 - Section 5.4.3: pseudowire status negotiation */ if (fec_find(&ln->recv_map, &fn->fec) == NULL && !(map->flags & F_MAP_PW_STATUS)) pw->flags &= ~F_PW_STATUSTLV; return (0); } void l2vpn_send_pw_status(struct lde_nbr *ln, uint32_t status, struct fec *fec) { struct notify_msg nm; memset(&nm, 0, sizeof(nm)); nm.status_code = S_PW_STATUS; nm.pw_status = status; nm.flags |= F_NOTIF_PW_STATUS; lde_fec2map(fec, &nm.fec); nm.flags |= F_NOTIF_FEC; lde_imsg_compose_ldpe(IMSG_NOTIFICATION_SEND, ln->peerid, 0, &nm, sizeof(nm)); } void l2vpn_send_pw_status_wcard(struct lde_nbr *ln, uint32_t status, uint16_t pw_type, uint32_t group_id) { struct notify_msg nm; memset(&nm, 0, sizeof(nm)); nm.status_code = S_PW_STATUS; nm.pw_status = status; nm.flags |= F_NOTIF_PW_STATUS; nm.fec.type = MAP_TYPE_PWID; nm.fec.fec.pwid.type = pw_type; nm.fec.fec.pwid.group_id = group_id; nm.flags |= F_NOTIF_FEC; lde_imsg_compose_ldpe(IMSG_NOTIFICATION_SEND, ln->peerid, 0, &nm, sizeof(nm)); } void l2vpn_recv_pw_status(struct lde_nbr *ln, struct notify_msg *nm) { struct fec fec; struct fec_node *fn; struct fec_nh *fnh; struct l2vpn_pw *pw; if (nm->fec.type == MAP_TYPE_TYPED_WCARD || !(nm->fec.flags & F_MAP_PW_ID)) { l2vpn_recv_pw_status_wcard(ln, nm); return; } lde_map2fec(&nm->fec, ln->id, &fec); fn = (struct fec_node *)fec_find(&ft, &fec); if (fn == NULL) /* unknown fec */ return; pw = (struct l2vpn_pw *) fn->data; if (pw == NULL) return; fnh = fec_nh_find(fn, AF_INET, (union ldpd_addr *)&ln->id, 0, 0); if (fnh == NULL) return; /* remote status didn't change */ if (pw->remote_status == nm->pw_status) return; pw->remote_status = nm->pw_status; if (l2vpn_pw_ok(pw, fnh)) lde_send_change_klabel(fn, fnh); else lde_send_delete_klabel(fn, fnh); } /* RFC4447 PWid group wildcard */ void l2vpn_recv_pw_status_wcard(struct lde_nbr *ln, struct notify_msg *nm) { struct fec *f; struct fec_node *fn; struct fec_nh *fnh; struct l2vpn_pw *pw; struct map *wcard = &nm->fec; RB_FOREACH(f, fec_tree, &ft) { fn = (struct fec_node *)f; if (fn->fec.type != FEC_TYPE_PWID) continue; pw = (struct l2vpn_pw *) fn->data; if (pw == NULL) continue; switch (wcard->type) { case MAP_TYPE_TYPED_WCARD: if (wcard->fec.twcard.u.pw_type != PW_TYPE_WILDCARD && wcard->fec.twcard.u.pw_type != fn->fec.u.pwid.type) continue; break; case MAP_TYPE_PWID: if (wcard->fec.pwid.type != fn->fec.u.pwid.type) continue; if (wcard->fec.pwid.group_id != pw->remote_group) continue; break; } fnh = fec_nh_find(fn, AF_INET, (union ldpd_addr *)&ln->id, 0, 0); if (fnh == NULL) continue; /* remote status didn't change */ if (pw->remote_status == nm->pw_status) continue; pw->remote_status = nm->pw_status; if (l2vpn_pw_ok(pw, fnh)) lde_send_change_klabel(fn, fnh); else lde_send_delete_klabel(fn, fnh); } } int l2vpn_pw_status_update(struct zapi_pw_status *zpw) { struct l2vpn *l2vpn; struct l2vpn_pw *pw = NULL; struct lde_nbr *ln; struct fec fec; uint32_t local_status; RB_FOREACH(l2vpn, l2vpn_head, &ldeconf->l2vpn_tree) { pw = l2vpn_pw_find(l2vpn, zpw->ifname); if (pw) break; } if (!pw) { log_warnx("%s: pseudowire %s not found", __func__, zpw->ifname); return (1); } if (zpw->status == PW_STATUS_UP) local_status = PW_FORWARDING; else local_status = PW_NOT_FORWARDING; /* local status didn't change */ if (pw->local_status == local_status) return (0); pw->local_status = local_status; /* notify remote peer about the status update */ ln = lde_nbr_find_by_lsrid(pw->lsr_id); if (ln == NULL) return (0); l2vpn_pw_fec(pw, &fec); if (pw->flags & F_PW_STATUSTLV) l2vpn_send_pw_status(ln, local_status, &fec); else { struct fec_node *fn; fn = (struct fec_node *)fec_find(&ft, &fec); if (fn) { if (pw->local_status == PW_FORWARDING) lde_send_labelmapping(ln, fn, 1); else lde_send_labelwithdraw(ln, fn, NULL, NULL); } } return (0); } void l2vpn_pw_ctl(pid_t pid) { struct l2vpn *l2vpn; struct l2vpn_pw *pw; static struct ctl_pw pwctl; RB_FOREACH(l2vpn, l2vpn_head, &ldeconf->l2vpn_tree) RB_FOREACH(pw, l2vpn_pw_head, &l2vpn->pw_tree) { memset(&pwctl, 0, sizeof(pwctl)); strlcpy(pwctl.l2vpn_name, pw->l2vpn->name, sizeof(pwctl.l2vpn_name)); strlcpy(pwctl.ifname, pw->ifname, sizeof(pwctl.ifname)); pwctl.pwid = pw->pwid; pwctl.lsr_id = pw->lsr_id; if (pw->enabled && pw->local_status == PW_FORWARDING && pw->remote_status == PW_FORWARDING) pwctl.status = 1; lde_imsg_compose_ldpe(IMSG_CTL_SHOW_L2VPN_PW, 0, pid, &pwctl, sizeof(pwctl)); } } void l2vpn_binding_ctl(pid_t pid) { struct fec *f; struct fec_node *fn; struct lde_map *me; struct l2vpn_pw *pw; static struct ctl_pw pwctl; RB_FOREACH(f, fec_tree, &ft) { if (f->type != FEC_TYPE_PWID) continue; fn = (struct fec_node *)f; if (fn->local_label == NO_LABEL && RB_EMPTY(lde_map_head, &fn->downstream)) continue; memset(&pwctl, 0, sizeof(pwctl)); pwctl.type = f->u.pwid.type; pwctl.pwid = f->u.pwid.pwid; pwctl.lsr_id = f->u.pwid.lsr_id; pw = (struct l2vpn_pw *) fn->data; if (pw) { pwctl.local_label = fn->local_label; pwctl.local_gid = 0; pwctl.local_ifmtu = pw->l2vpn->mtu; pwctl.local_cword = (pw->flags & F_PW_CWORD_CONF) ? 1 : 0; } else pwctl.local_label = NO_LABEL; RB_FOREACH(me, lde_map_head, &fn->downstream) if (f->u.pwid.lsr_id.s_addr == me->nexthop->id.s_addr) break; if (me) { pwctl.remote_label = me->map.label; pwctl.remote_gid = me->map.fec.pwid.group_id; if (me->map.flags & F_MAP_PW_IFMTU) pwctl.remote_ifmtu = me->map.fec.pwid.ifmtu; if (pw) pwctl.remote_cword = (pw->flags & F_PW_CWORD) ? 1 : 0; lde_imsg_compose_ldpe(IMSG_CTL_SHOW_L2VPN_BINDING, 0, pid, &pwctl, sizeof(pwctl)); } else if (pw) { pwctl.remote_label = NO_LABEL; lde_imsg_compose_ldpe(IMSG_CTL_SHOW_L2VPN_BINDING, 0, pid, &pwctl, sizeof(pwctl)); } } } /* ldpe */ void ldpe_l2vpn_init(struct l2vpn *l2vpn) { struct l2vpn_pw *pw; RB_FOREACH(pw, l2vpn_pw_head, &l2vpn->pw_tree) ldpe_l2vpn_pw_init(pw); } void ldpe_l2vpn_exit(struct l2vpn *l2vpn) { struct l2vpn_pw *pw; RB_FOREACH(pw, l2vpn_pw_head, &l2vpn->pw_tree) ldpe_l2vpn_pw_exit(pw); } void ldpe_l2vpn_pw_init(struct l2vpn_pw *pw) { struct tnbr *tnbr; tnbr = tnbr_find(leconf, pw->af, &pw->addr); if (tnbr == NULL) { tnbr = tnbr_new(pw->af, &pw->addr); tnbr_update(tnbr); RB_INSERT(tnbr_head, &leconf->tnbr_tree, tnbr); } tnbr->pw_count++; } void ldpe_l2vpn_pw_exit(struct l2vpn_pw *pw) { struct tnbr *tnbr; tnbr = tnbr_find(leconf, pw->af, &pw->addr); if (tnbr) { tnbr->pw_count--; tnbr_check(leconf, tnbr); } } frr-7.2.1/ldpd/labelmapping.c0000644000000000000000000005352113610377563012744 00000000000000/* $OpenBSD$ */ /* * Copyright (c) 2014, 2015 Renato Westphal * Copyright (c) 2009 Michele Marchetto * * Permission to use, copy, modify, and distribute this software for any * purpose with or without fee is hereby granted, provided that the above * copyright notice and this permission notice appear in all copies. * * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. */ #include #include "ldpd.h" #include "ldpe.h" #include "log.h" #include "ldp_debug.h" #include "mpls.h" static void enqueue_pdu(struct nbr *, uint16_t, struct ibuf *, uint16_t); static int gen_label_tlv(struct ibuf *, uint32_t); static int gen_reqid_tlv(struct ibuf *, uint32_t); static void log_msg_mapping(int, uint16_t, struct nbr *, struct map *); static void enqueue_pdu(struct nbr *nbr, uint16_t type, struct ibuf *buf, uint16_t size) { struct ldp_hdr *ldp_hdr; ldp_hdr = ibuf_seek(buf, 0, sizeof(struct ldp_hdr)); ldp_hdr->length = htons(size - LDP_HDR_DEAD_LEN); evbuf_enqueue(&nbr->tcp->wbuf, buf); } /* Generic function that handles all Label Message types */ void send_labelmessage(struct nbr *nbr, uint16_t type, struct mapping_head *mh) { struct ibuf *buf = NULL; struct mapping_entry *me; uint16_t msg_size, size = 0; int first = 1; int err = 0; /* nothing to send */ if (TAILQ_EMPTY(mh)) return; while ((me = TAILQ_FIRST(mh)) != NULL) { /* generate pdu */ if (first) { if ((buf = ibuf_open(nbr->max_pdu_len + LDP_HDR_DEAD_LEN)) == NULL) fatal(__func__); /* real size will be set up later */ err |= gen_ldp_hdr(buf, 0); size = LDP_HDR_SIZE; first = 0; } /* calculate size */ msg_size = LDP_MSG_SIZE; msg_size += len_fec_tlv(&me->map); if (me->map.label != NO_LABEL) msg_size += LABEL_TLV_SIZE; if (me->map.flags & F_MAP_REQ_ID) msg_size += REQID_TLV_SIZE; if (me->map.flags & F_MAP_STATUS) msg_size += STATUS_SIZE; /* maximum pdu length exceeded, we need a new ldp pdu */ if (size + msg_size > nbr->max_pdu_len) { enqueue_pdu(nbr, type, buf, size); first = 1; continue; } size += msg_size; /* append message and tlvs */ err |= gen_msg_hdr(buf, type, msg_size); err |= gen_fec_tlv(buf, &me->map); if (me->map.label != NO_LABEL) err |= gen_label_tlv(buf, me->map.label); if (me->map.flags & F_MAP_REQ_ID) err |= gen_reqid_tlv(buf, me->map.requestid); if (me->map.flags & F_MAP_PW_STATUS) err |= gen_pw_status_tlv(buf, me->map.pw_status); if (me->map.flags & F_MAP_STATUS) err |= gen_status_tlv(buf, me->map.st.status_code, me->map.st.msg_id, me->map.st.msg_type); if (err) { ibuf_free(buf); mapping_list_clr(mh); return; } log_msg_mapping(1, type, nbr, &me->map); /* no errors - update per neighbor message counters */ switch (type) { case MSG_TYPE_LABELMAPPING: nbr->stats.labelmap_sent++; break; case MSG_TYPE_LABELREQUEST: nbr->stats.labelreq_sent++; break; case MSG_TYPE_LABELWITHDRAW: nbr->stats.labelwdraw_sent++; break; case MSG_TYPE_LABELRELEASE: nbr->stats.labelrel_sent++; break; case MSG_TYPE_LABELABORTREQ: nbr->stats.labelabreq_sent++; break; default: break; } TAILQ_REMOVE(mh, me, entry); assert(me != TAILQ_FIRST(mh)); free(me); } enqueue_pdu(nbr, type, buf, size); nbr_fsm(nbr, NBR_EVT_PDU_SENT); } /* Generic function that handles all Label Message types */ int recv_labelmessage(struct nbr *nbr, char *buf, uint16_t len, uint16_t type) { struct ldp_msg msg; struct tlv ft; uint32_t label = NO_LABEL, reqid = 0; uint32_t pw_status = 0; uint8_t flags = 0; int feclen, tlen; uint16_t current_tlv = 1; struct mapping_entry *me; struct mapping_head mh; struct map map; memcpy(&msg, buf, sizeof(msg)); buf += LDP_MSG_SIZE; len -= LDP_MSG_SIZE; /* FEC TLV */ if (len < sizeof(ft)) { session_shutdown(nbr, S_BAD_TLV_LEN, msg.id, msg.type); return (-1); } memcpy(&ft, buf, sizeof(ft)); if (ntohs(ft.type) != TLV_TYPE_FEC) { send_notification(nbr->tcp, S_MISS_MSG, msg.id, msg.type); return (-1); } feclen = ntohs(ft.length); if (feclen > len - TLV_HDR_SIZE) { session_shutdown(nbr, S_BAD_TLV_LEN, msg.id, msg.type); return (-1); } buf += TLV_HDR_SIZE; /* just advance to the end of the fec header */ len -= TLV_HDR_SIZE; TAILQ_INIT(&mh); do { memset(&map, 0, sizeof(map)); map.msg_id = msg.id; if ((tlen = tlv_decode_fec_elm(nbr, &msg, buf, feclen, &map)) == -1) goto err; if (map.type == MAP_TYPE_PWID && !(map.flags & F_MAP_PW_ID) && type != MSG_TYPE_LABELWITHDRAW && type != MSG_TYPE_LABELRELEASE) { send_notification(nbr->tcp, S_MISS_MSG, msg.id, msg.type); goto err; } /* * The Wildcard FEC Element can be used only in the * Label Withdraw and Label Release messages. */ if (map.type == MAP_TYPE_WILDCARD) { switch (type) { case MSG_TYPE_LABELMAPPING: case MSG_TYPE_LABELREQUEST: case MSG_TYPE_LABELABORTREQ: session_shutdown(nbr, S_UNKNOWN_FEC, msg.id, msg.type); goto err; default: break; } } /* * RFC 5561 - Section 4: * "An LDP implementation that supports the Typed Wildcard * FEC Element MUST support its use in Label Request, Label * Withdraw, and Label Release messages". */ if (map.type == MAP_TYPE_TYPED_WCARD) { switch (type) { case MSG_TYPE_LABELMAPPING: case MSG_TYPE_LABELABORTREQ: session_shutdown(nbr, S_UNKNOWN_FEC, msg.id, msg.type); goto err; default: break; } } /* * LDP supports the use of multiple FEC Elements per * FEC for the Label Mapping message only. */ if (type != MSG_TYPE_LABELMAPPING && tlen != feclen) { session_shutdown(nbr, S_BAD_TLV_VAL, msg.id, msg.type); goto err; } mapping_list_add(&mh, &map); buf += tlen; len -= tlen; feclen -= tlen; } while (feclen > 0); /* Optional Parameters */ while (len > 0) { struct tlv tlv; uint16_t tlv_type; uint16_t tlv_len; uint32_t reqbuf, labelbuf, statusbuf; if (len < sizeof(tlv)) { session_shutdown(nbr, S_BAD_TLV_LEN, msg.id, msg.type); goto err; } memcpy(&tlv, buf, TLV_HDR_SIZE); tlv_type = ntohs(tlv.type); tlv_len = ntohs(tlv.length); if (tlv_len + TLV_HDR_SIZE > len) { session_shutdown(nbr, S_BAD_TLV_LEN, msg.id, msg.type); goto err; } buf += TLV_HDR_SIZE; len -= TLV_HDR_SIZE; /* * For Label Mapping messages the Label TLV is mandatory and * should appear right after the FEC TLV. */ if (current_tlv == 1 && type == MSG_TYPE_LABELMAPPING && !(tlv_type & TLV_TYPE_GENERICLABEL)) { send_notification(nbr->tcp, S_MISS_MSG, msg.id, msg.type); goto err; } switch (tlv_type) { case TLV_TYPE_LABELREQUEST: switch (type) { case MSG_TYPE_LABELMAPPING: case MSG_TYPE_LABELREQUEST: if (tlv_len != REQID_TLV_LEN) { session_shutdown(nbr, S_BAD_TLV_LEN, msg.id, msg.type); goto err; } flags |= F_MAP_REQ_ID; memcpy(&reqbuf, buf, sizeof(reqbuf)); reqid = ntohl(reqbuf); break; default: /* ignore */ break; } break; case TLV_TYPE_HOPCOUNT: case TLV_TYPE_PATHVECTOR: /* ignore */ break; case TLV_TYPE_GENERICLABEL: switch (type) { case MSG_TYPE_LABELMAPPING: case MSG_TYPE_LABELWITHDRAW: case MSG_TYPE_LABELRELEASE: if (tlv_len != LABEL_TLV_LEN) { session_shutdown(nbr, S_BAD_TLV_LEN, msg.id, msg.type); goto err; } memcpy(&labelbuf, buf, sizeof(labelbuf)); label = ntohl(labelbuf); /* do not accept invalid labels */ if (label > MPLS_LABEL_MAX || (label <= MPLS_LABEL_RESERVED_MAX && label != MPLS_LABEL_IPV4_EXPLICIT_NULL && label != MPLS_LABEL_IPV6_EXPLICIT_NULL && label != MPLS_LABEL_IMPLICIT_NULL)) { session_shutdown(nbr, S_BAD_TLV_VAL, msg.id, msg.type); goto err; } break; default: /* ignore */ break; } break; case TLV_TYPE_ATMLABEL: case TLV_TYPE_FRLABEL: switch (type) { case MSG_TYPE_LABELMAPPING: case MSG_TYPE_LABELWITHDRAW: case MSG_TYPE_LABELRELEASE: /* unsupported */ session_shutdown(nbr, S_BAD_TLV_VAL, msg.id, msg.type); goto err; break; default: /* ignore */ break; } break; case TLV_TYPE_STATUS: if (tlv_len != STATUS_TLV_LEN) { session_shutdown(nbr, S_BAD_TLV_LEN, msg.id, msg.type); goto err; } /* ignore */ break; case TLV_TYPE_PW_STATUS: switch (type) { case MSG_TYPE_LABELMAPPING: if (tlv_len != PW_STATUS_TLV_LEN) { session_shutdown(nbr, S_BAD_TLV_LEN, msg.id, msg.type); goto err; } flags |= F_MAP_PW_STATUS; memcpy(&statusbuf, buf, sizeof(statusbuf)); pw_status = ntohl(statusbuf); break; default: /* ignore */ break; } break; default: if (!(ntohs(tlv.type) & UNKNOWN_FLAG)) send_notification_rtlvs(nbr, S_UNKNOWN_TLV, msg.id, msg.type, tlv_type, tlv_len, buf); /* ignore unknown tlv */ break; } buf += tlv_len; len -= tlv_len; current_tlv++; } /* notify lde about the received message. */ while ((me = TAILQ_FIRST(&mh)) != NULL) { int imsg_type = IMSG_NONE; me->map.flags |= flags; switch (me->map.type) { case MAP_TYPE_PREFIX: switch (me->map.fec.prefix.af) { case AF_INET: if (label == MPLS_LABEL_IPV6_EXPLICIT_NULL) { session_shutdown(nbr, S_BAD_TLV_VAL, msg.id, msg.type); goto err; } if (!nbr->v4_enabled) goto next; break; case AF_INET6: if (label == MPLS_LABEL_IPV4_EXPLICIT_NULL) { session_shutdown(nbr, S_BAD_TLV_VAL, msg.id, msg.type); goto err; } if (!nbr->v6_enabled) goto next; break; default: fatalx("recv_labelmessage: unknown af"); } break; case MAP_TYPE_PWID: if (label <= MPLS_LABEL_RESERVED_MAX) { session_shutdown(nbr, S_BAD_TLV_VAL, msg.id, msg.type); goto err; } if (me->map.flags & F_MAP_PW_STATUS) me->map.pw_status = pw_status; break; default: break; } me->map.label = label; if (me->map.flags & F_MAP_REQ_ID) me->map.requestid = reqid; log_msg_mapping(0, type, nbr, &me->map); switch (type) { case MSG_TYPE_LABELMAPPING: imsg_type = IMSG_LABEL_MAPPING; break; case MSG_TYPE_LABELREQUEST: imsg_type = IMSG_LABEL_REQUEST; break; case MSG_TYPE_LABELWITHDRAW: imsg_type = IMSG_LABEL_WITHDRAW; break; case MSG_TYPE_LABELRELEASE: imsg_type = IMSG_LABEL_RELEASE; break; case MSG_TYPE_LABELABORTREQ: imsg_type = IMSG_LABEL_ABORT; break; default: break; } ldpe_imsg_compose_lde(imsg_type, nbr->peerid, 0, &me->map, sizeof(struct map)); next: TAILQ_REMOVE(&mh, me, entry); assert(me != TAILQ_FIRST(&mh)); free(me); } return (0); err: mapping_list_clr(&mh); return (-1); } /* Other TLV related functions */ static int gen_label_tlv(struct ibuf *buf, uint32_t label) { struct label_tlv lt; lt.type = htons(TLV_TYPE_GENERICLABEL); lt.length = htons(LABEL_TLV_LEN); lt.label = htonl(label); return (ibuf_add(buf, <, sizeof(lt))); } static int gen_reqid_tlv(struct ibuf *buf, uint32_t reqid) { struct reqid_tlv rt; rt.type = htons(TLV_TYPE_LABELREQUEST); rt.length = htons(REQID_TLV_LEN); rt.reqid = htonl(reqid); return (ibuf_add(buf, &rt, sizeof(rt))); } int gen_pw_status_tlv(struct ibuf *buf, uint32_t status) { struct pw_status_tlv st; st.type = htons(TLV_TYPE_PW_STATUS); st.length = htons(PW_STATUS_TLV_LEN); st.value = htonl(status); return (ibuf_add(buf, &st, sizeof(st))); } uint16_t len_fec_tlv(struct map *map) { uint16_t len = TLV_HDR_SIZE; switch (map->type) { case MAP_TYPE_WILDCARD: len += FEC_ELM_WCARD_LEN; break; case MAP_TYPE_PREFIX: len += FEC_ELM_PREFIX_MIN_LEN + PREFIX_SIZE(map->fec.prefix.prefixlen); break; case MAP_TYPE_PWID: len += FEC_PWID_ELM_MIN_LEN; if (map->flags & F_MAP_PW_ID) len += PW_STATUS_TLV_LEN; if (map->flags & F_MAP_PW_IFMTU) len += FEC_SUBTLV_IFMTU_SIZE; if (map->flags & F_MAP_PW_STATUS) len += PW_STATUS_TLV_SIZE; break; case MAP_TYPE_TYPED_WCARD: len += FEC_ELM_TWCARD_MIN_LEN; switch (map->fec.twcard.type) { case MAP_TYPE_PREFIX: case MAP_TYPE_PWID: len += sizeof(uint16_t); break; default: fatalx("len_fec_tlv: unexpected fec type"); } break; default: fatalx("len_fec_tlv: unexpected fec type"); } return (len); } int gen_fec_tlv(struct ibuf *buf, struct map *map) { struct tlv ft; uint16_t family, len, pw_type, ifmtu; uint8_t pw_len = 0, twcard_len; uint32_t group_id, pwid; int err = 0; ft.type = htons(TLV_TYPE_FEC); switch (map->type) { case MAP_TYPE_WILDCARD: ft.length = htons(sizeof(uint8_t)); err |= ibuf_add(buf, &ft, sizeof(ft)); err |= ibuf_add(buf, &map->type, sizeof(map->type)); break; case MAP_TYPE_PREFIX: len = PREFIX_SIZE(map->fec.prefix.prefixlen); ft.length = htons(sizeof(map->type) + sizeof(family) + sizeof(map->fec.prefix.prefixlen) + len); err |= ibuf_add(buf, &ft, sizeof(ft)); err |= ibuf_add(buf, &map->type, sizeof(map->type)); switch (map->fec.prefix.af) { case AF_INET: family = htons(AF_IPV4); break; case AF_INET6: family = htons(AF_IPV6); break; default: fatalx("gen_fec_tlv: unknown af"); break; } err |= ibuf_add(buf, &family, sizeof(family)); err |= ibuf_add(buf, &map->fec.prefix.prefixlen, sizeof(map->fec.prefix.prefixlen)); if (len) err |= ibuf_add(buf, &map->fec.prefix.prefix, len); break; case MAP_TYPE_PWID: if (map->flags & F_MAP_PW_ID) pw_len += FEC_PWID_SIZE; if (map->flags & F_MAP_PW_IFMTU) pw_len += FEC_SUBTLV_IFMTU_SIZE; len = FEC_PWID_ELM_MIN_LEN + pw_len; ft.length = htons(len); err |= ibuf_add(buf, &ft, sizeof(ft)); err |= ibuf_add(buf, &map->type, sizeof(uint8_t)); pw_type = map->fec.pwid.type; if (map->flags & F_MAP_PW_CWORD) pw_type |= CONTROL_WORD_FLAG; pw_type = htons(pw_type); err |= ibuf_add(buf, &pw_type, sizeof(uint16_t)); err |= ibuf_add(buf, &pw_len, sizeof(uint8_t)); group_id = htonl(map->fec.pwid.group_id); err |= ibuf_add(buf, &group_id, sizeof(uint32_t)); if (map->flags & F_MAP_PW_ID) { pwid = htonl(map->fec.pwid.pwid); err |= ibuf_add(buf, &pwid, sizeof(uint32_t)); } if (map->flags & F_MAP_PW_IFMTU) { struct subtlv stlv; stlv.type = SUBTLV_IFMTU; stlv.length = FEC_SUBTLV_IFMTU_SIZE; err |= ibuf_add(buf, &stlv, sizeof(uint16_t)); ifmtu = htons(map->fec.pwid.ifmtu); err |= ibuf_add(buf, &ifmtu, sizeof(uint16_t)); } break; case MAP_TYPE_TYPED_WCARD: len = FEC_ELM_TWCARD_MIN_LEN; switch (map->fec.twcard.type) { case MAP_TYPE_PREFIX: case MAP_TYPE_PWID: len += sizeof(uint16_t); break; default: fatalx("gen_fec_tlv: unexpected fec type"); } ft.length = htons(len); err |= ibuf_add(buf, &ft, sizeof(ft)); err |= ibuf_add(buf, &map->type, sizeof(uint8_t)); err |= ibuf_add(buf, &map->fec.twcard.type, sizeof(uint8_t)); switch (map->fec.twcard.type) { case MAP_TYPE_PREFIX: twcard_len = sizeof(uint16_t); err |= ibuf_add(buf, &twcard_len, sizeof(uint8_t)); switch (map->fec.twcard.u.prefix_af) { case AF_INET: family = htons(AF_IPV4); break; case AF_INET6: family = htons(AF_IPV6); break; default: fatalx("gen_fec_tlv: unknown af"); break; } err |= ibuf_add(buf, &family, sizeof(uint16_t)); break; case MAP_TYPE_PWID: twcard_len = sizeof(uint16_t); err |= ibuf_add(buf, &twcard_len, sizeof(uint8_t)); pw_type = htons(map->fec.twcard.u.pw_type); err |= ibuf_add(buf, &pw_type, sizeof(uint16_t)); break; default: fatalx("gen_fec_tlv: unexpected fec type"); } break; default: break; } return (err); } int tlv_decode_fec_elm(struct nbr *nbr, struct ldp_msg *msg, char *buf, uint16_t len, struct map *map) { uint16_t off = 0; uint8_t pw_len, twcard_len; map->type = *buf; off += sizeof(uint8_t); switch (map->type) { case MAP_TYPE_WILDCARD: if (len == FEC_ELM_WCARD_LEN) return (off); else { session_shutdown(nbr, S_BAD_TLV_VAL, msg->id, msg->type); return (-1); } break; case MAP_TYPE_PREFIX: if (len < FEC_ELM_PREFIX_MIN_LEN) { session_shutdown(nbr, S_BAD_TLV_LEN, msg->id, msg->type); return (-1); } /* Address Family */ memcpy(&map->fec.prefix.af, buf + off, sizeof(map->fec.prefix.af)); off += sizeof(map->fec.prefix.af); map->fec.prefix.af = ntohs(map->fec.prefix.af); switch (map->fec.prefix.af) { case AF_IPV4: map->fec.prefix.af = AF_INET; break; case AF_IPV6: map->fec.prefix.af = AF_INET6; break; default: send_notification(nbr->tcp, S_UNSUP_ADDR, msg->id, msg->type); return (-1); } /* Prefix Length */ map->fec.prefix.prefixlen = buf[off]; off += sizeof(uint8_t); if ((map->fec.prefix.af == AF_IPV4 && map->fec.prefix.prefixlen > IPV4_MAX_PREFIXLEN) || (map->fec.prefix.af == AF_IPV6 && map->fec.prefix.prefixlen > IPV6_MAX_PREFIXLEN)) { session_shutdown(nbr, S_BAD_TLV_VAL, msg->id, msg->type); return (-1); } if (len < off + PREFIX_SIZE(map->fec.prefix.prefixlen)) { session_shutdown(nbr, S_BAD_TLV_LEN, msg->id, msg->type); return (-1); } /* Prefix */ memset(&map->fec.prefix.prefix, 0, sizeof(map->fec.prefix.prefix)); memcpy(&map->fec.prefix.prefix, buf + off, PREFIX_SIZE(map->fec.prefix.prefixlen)); /* Just in case... */ ldp_applymask(map->fec.prefix.af, &map->fec.prefix.prefix, &map->fec.prefix.prefix, map->fec.prefix.prefixlen); return (off + PREFIX_SIZE(map->fec.prefix.prefixlen)); case MAP_TYPE_PWID: if (len < FEC_PWID_ELM_MIN_LEN) { session_shutdown(nbr, S_BAD_TLV_LEN, msg->id, msg->type); return (-1); } /* PW type */ memcpy(&map->fec.pwid.type, buf + off, sizeof(uint16_t)); map->fec.pwid.type = ntohs(map->fec.pwid.type); if (map->fec.pwid.type & CONTROL_WORD_FLAG) { map->flags |= F_MAP_PW_CWORD; map->fec.pwid.type &= ~CONTROL_WORD_FLAG; } off += sizeof(uint16_t); /* PW info Length */ pw_len = buf[off]; off += sizeof(uint8_t); if (len != FEC_PWID_ELM_MIN_LEN + pw_len) { session_shutdown(nbr, S_BAD_TLV_LEN, msg->id, msg->type); return (-1); } /* Group ID */ memcpy(&map->fec.pwid.group_id, buf + off, sizeof(uint32_t)); map->fec.pwid.group_id = ntohl(map->fec.pwid.group_id); off += sizeof(uint32_t); /* PW ID */ if (pw_len == 0) return (off); if (pw_len < sizeof(uint32_t)) { session_shutdown(nbr, S_BAD_TLV_LEN, msg->id, msg->type); return (-1); } memcpy(&map->fec.pwid.pwid, buf + off, sizeof(uint32_t)); map->fec.pwid.pwid = ntohl(map->fec.pwid.pwid); map->flags |= F_MAP_PW_ID; off += sizeof(uint32_t); pw_len -= sizeof(uint32_t); /* Optional Interface Parameter Sub-TLVs */ while (pw_len > 0) { struct subtlv stlv; if (pw_len < sizeof(stlv)) { session_shutdown(nbr, S_BAD_TLV_LEN, msg->id, msg->type); return (-1); } memcpy(&stlv, buf + off, sizeof(stlv)); if (stlv.length > pw_len) { session_shutdown(nbr, S_BAD_TLV_LEN, msg->id, msg->type); return (-1); } switch (stlv.type) { case SUBTLV_IFMTU: if (stlv.length != FEC_SUBTLV_IFMTU_SIZE) { session_shutdown(nbr, S_BAD_TLV_LEN, msg->id, msg->type); return (-1); } memcpy(&map->fec.pwid.ifmtu, buf + off + SUBTLV_HDR_SIZE, sizeof(uint16_t)); map->fec.pwid.ifmtu = ntohs(map->fec.pwid.ifmtu); map->flags |= F_MAP_PW_IFMTU; break; default: /* ignore */ break; } off += stlv.length; pw_len -= stlv.length; } return (off); case MAP_TYPE_TYPED_WCARD: if (len < FEC_ELM_TWCARD_MIN_LEN) { session_shutdown(nbr, S_BAD_TLV_LEN, msg->id, msg->type); return (-1); } memcpy(&map->fec.twcard.type, buf + off, sizeof(uint8_t)); off += sizeof(uint8_t); memcpy(&twcard_len, buf + off, sizeof(uint8_t)); off += sizeof(uint8_t); if (len != FEC_ELM_TWCARD_MIN_LEN + twcard_len) { session_shutdown(nbr, S_BAD_TLV_LEN, msg->id, msg->type); return (-1); } switch (map->fec.twcard.type) { case MAP_TYPE_PREFIX: if (twcard_len != sizeof(uint16_t)) { session_shutdown(nbr, S_BAD_TLV_LEN, msg->id, msg->type); return (-1); } memcpy(&map->fec.twcard.u.prefix_af, buf + off, sizeof(uint16_t)); map->fec.twcard.u.prefix_af = ntohs(map->fec.twcard.u.prefix_af); off += sizeof(uint16_t); switch (map->fec.twcard.u.prefix_af) { case AF_IPV4: map->fec.twcard.u.prefix_af = AF_INET; break; case AF_IPV6: map->fec.twcard.u.prefix_af = AF_INET6; break; default: session_shutdown(nbr, S_BAD_TLV_VAL, msg->id, msg->type); return (-1); } break; case MAP_TYPE_PWID: if (twcard_len != sizeof(uint16_t)) { session_shutdown(nbr, S_BAD_TLV_LEN, msg->id, msg->type); return (-1); } memcpy(&map->fec.twcard.u.pw_type, buf + off, sizeof(uint16_t)); map->fec.twcard.u.pw_type = ntohs(map->fec.twcard.u.pw_type); /* ignore the reserved bit as per RFC 6667 */ map->fec.twcard.u.pw_type &= ~PW_TWCARD_RESERVED_BIT; off += sizeof(uint16_t); break; default: send_notification(nbr->tcp, S_UNKNOWN_FEC, msg->id, msg->type); return (-1); } return (off); default: send_notification(nbr->tcp, S_UNKNOWN_FEC, msg->id, msg->type); break; } return (-1); } static void log_msg_mapping(int out, uint16_t msg_type, struct nbr *nbr, struct map *map) { debug_msg(out, "%s: lsr-id %s, fec %s, label %s", msg_name(msg_type), inet_ntoa(nbr->id), log_map(map), log_label(map->label)); } frr-7.2.1/ldpd/lde.c0000644000000000000000000012441013610377563011051 00000000000000/* $OpenBSD$ */ /* * Copyright (c) 2013, 2016 Renato Westphal * Copyright (c) 2004, 2005 Claudio Jeker * Copyright (c) 2004 Esben Norby * Copyright (c) 2003, 2004 Henning Brauer * * Permission to use, copy, modify, and distribute this software for any * purpose with or without fee is hereby granted, provided that the above * copyright notice and this permission notice appear in all copies. * * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. */ #include #include "ldp.h" #include "ldpd.h" #include "ldpe.h" #include "log.h" #include "lde.h" #include "ldp_debug.h" #include #include "memory.h" #include "privs.h" #include "sigevent.h" #include "mpls.h" #include #include "zclient.h" #include "stream.h" #include "network.h" #include "libfrr.h" static void lde_shutdown(void); static int lde_dispatch_imsg(struct thread *); static int lde_dispatch_parent(struct thread *); static __inline int lde_nbr_compare(const struct lde_nbr *, const struct lde_nbr *); static struct lde_nbr *lde_nbr_new(uint32_t, struct lde_nbr *); static void lde_nbr_del(struct lde_nbr *); static struct lde_nbr *lde_nbr_find(uint32_t); static void lde_nbr_clear(void); static void lde_nbr_addr_update(struct lde_nbr *, struct lde_addr *, int); static __inline int lde_map_compare(const struct lde_map *, const struct lde_map *); static void lde_map_free(void *); static int lde_address_add(struct lde_nbr *, struct lde_addr *); static int lde_address_del(struct lde_nbr *, struct lde_addr *); static void lde_address_list_free(struct lde_nbr *); static void zclient_sync_init(unsigned short instance); static void lde_label_list_init(void); static int lde_get_label_chunk(void); static void on_get_label_chunk_response(uint32_t start, uint32_t end); static uint32_t lde_get_next_label(void); RB_GENERATE(nbr_tree, lde_nbr, entry, lde_nbr_compare) RB_GENERATE(lde_map_head, lde_map, entry, lde_map_compare) struct ldpd_conf *ldeconf; struct nbr_tree lde_nbrs = RB_INITIALIZER(&lde_nbrs); static struct imsgev *iev_ldpe; static struct imsgev *iev_main, *iev_main_sync; /* Master of threads. */ struct thread_master *master; /* lde privileges */ static zebra_capabilities_t _caps_p [] = { ZCAP_NET_ADMIN }; static struct zebra_privs_t lde_privs = { #if defined(VTY_GROUP) .vty_group = VTY_GROUP, #endif .caps_p = _caps_p, .cap_num_p = array_size(_caps_p), .cap_num_i = 0 }; /* List of chunks of labels externally assigned by Zebra */ static struct list *label_chunk_list; static struct listnode *current_label_chunk; /* Synchronous zclient to request labels */ static struct zclient *zclient_sync; /* SIGINT / SIGTERM handler. */ static void sigint(void) { lde_shutdown(); } static struct quagga_signal_t lde_signals[] = { { .signal = SIGHUP, /* ignore */ }, { .signal = SIGINT, .handler = &sigint, }, { .signal = SIGTERM, .handler = &sigint, }, }; /* label decision engine */ void lde(void) { struct thread thread; #ifdef HAVE_SETPROCTITLE setproctitle("label decision engine"); #endif ldpd_process = PROC_LDE_ENGINE; log_procname = log_procnames[PROC_LDE_ENGINE]; master = thread_master_create(NULL); /* setup signal handler */ signal_init(master, array_size(lde_signals), lde_signals); /* setup pipes and event handlers to the parent process */ if ((iev_main = calloc(1, sizeof(struct imsgev))) == NULL) fatal(NULL); imsg_init(&iev_main->ibuf, LDPD_FD_ASYNC); iev_main->handler_read = lde_dispatch_parent; iev_main->ev_read = NULL; thread_add_read(master, iev_main->handler_read, iev_main, iev_main->ibuf.fd, &iev_main->ev_read); iev_main->handler_write = ldp_write_handler; if ((iev_main_sync = calloc(1, sizeof(struct imsgev))) == NULL) fatal(NULL); imsg_init(&iev_main_sync->ibuf, LDPD_FD_SYNC); /* create base configuration */ ldeconf = config_new_empty(); /* Fetch next active thread. */ while (thread_fetch(master, &thread)) thread_call(&thread); } void lde_init(struct ldpd_init *init) { /* drop privileges */ lde_privs.user = init->user; lde_privs.group = init->group; zprivs_preinit(&lde_privs); zprivs_init(&lde_privs); /* start the LIB garbage collector */ lde_gc_start_timer(); /* Init synchronous zclient and label list */ frr_zclient_addr(&zclient_addr, &zclient_addr_len, init->zclient_serv_path); zclient_sync_init(init->instance); lde_label_list_init(); } static void lde_shutdown(void) { /* close pipes */ if (iev_ldpe) { msgbuf_clear(&iev_ldpe->ibuf.w); close(iev_ldpe->ibuf.fd); iev_ldpe->ibuf.fd = -1; } msgbuf_clear(&iev_main->ibuf.w); close(iev_main->ibuf.fd); iev_main->ibuf.fd = -1; msgbuf_clear(&iev_main_sync->ibuf.w); close(iev_main_sync->ibuf.fd); iev_main_sync->ibuf.fd = -1; lde_gc_stop_timer(); lde_nbr_clear(); fec_tree_clear(); config_clear(ldeconf); if (iev_ldpe) free(iev_ldpe); free(iev_main); free(iev_main_sync); log_info("label decision engine exiting"); exit(0); } /* imesg */ int lde_imsg_compose_parent(int type, pid_t pid, void *data, uint16_t datalen) { if (iev_main->ibuf.fd == -1) return (0); return (imsg_compose_event(iev_main, type, 0, pid, -1, data, datalen)); } void lde_imsg_compose_parent_sync(int type, pid_t pid, void *data, uint16_t datalen) { if (iev_main_sync->ibuf.fd == -1) return; imsg_compose_event(iev_main_sync, type, 0, pid, -1, data, datalen); imsg_flush(&iev_main_sync->ibuf); } int lde_imsg_compose_ldpe(int type, uint32_t peerid, pid_t pid, void *data, uint16_t datalen) { if (iev_ldpe->ibuf.fd == -1) return (0); return (imsg_compose_event(iev_ldpe, type, peerid, pid, -1, data, datalen)); } /* ARGSUSED */ static int lde_dispatch_imsg(struct thread *thread) { struct imsgev *iev = THREAD_ARG(thread); struct imsgbuf *ibuf = &iev->ibuf; struct imsg imsg; struct lde_nbr *ln; struct map *map; struct lde_addr *lde_addr; struct notify_msg *nm; ssize_t n; int shut = 0; iev->ev_read = NULL; if ((n = imsg_read(ibuf)) == -1 && errno != EAGAIN) fatal("imsg_read error"); if (n == 0) /* connection closed */ shut = 1; for (;;) { if ((n = imsg_get(ibuf, &imsg)) == -1) fatal("lde_dispatch_imsg: imsg_get error"); if (n == 0) break; switch (imsg.hdr.type) { case IMSG_LABEL_MAPPING_FULL: ln = lde_nbr_find(imsg.hdr.peerid); if (ln == NULL) { log_debug("%s: cannot find lde neighbor", __func__); break; } fec_snap(ln); break; case IMSG_LABEL_MAPPING: case IMSG_LABEL_REQUEST: case IMSG_LABEL_RELEASE: case IMSG_LABEL_WITHDRAW: case IMSG_LABEL_ABORT: if (imsg.hdr.len - IMSG_HEADER_SIZE != sizeof(struct map)) fatalx("lde_dispatch_imsg: wrong imsg len"); map = imsg.data; ln = lde_nbr_find(imsg.hdr.peerid); if (ln == NULL) { log_debug("%s: cannot find lde neighbor", __func__); break; } switch (imsg.hdr.type) { case IMSG_LABEL_MAPPING: lde_check_mapping(map, ln); break; case IMSG_LABEL_REQUEST: lde_check_request(map, ln); break; case IMSG_LABEL_RELEASE: lde_check_release(map, ln); break; case IMSG_LABEL_WITHDRAW: lde_check_withdraw(map, ln); break; case IMSG_LABEL_ABORT: /* not necessary */ break; } break; case IMSG_ADDRESS_ADD: if (imsg.hdr.len - IMSG_HEADER_SIZE != sizeof(struct lde_addr)) fatalx("lde_dispatch_imsg: wrong imsg len"); lde_addr = imsg.data; ln = lde_nbr_find(imsg.hdr.peerid); if (ln == NULL) { log_debug("%s: cannot find lde neighbor", __func__); break; } if (lde_address_add(ln, lde_addr) < 0) { log_debug("%s: cannot add address %s, it " "already exists", __func__, log_addr(lde_addr->af, &lde_addr->addr)); } break; case IMSG_ADDRESS_DEL: if (imsg.hdr.len - IMSG_HEADER_SIZE != sizeof(struct lde_addr)) fatalx("lde_dispatch_imsg: wrong imsg len"); lde_addr = imsg.data; ln = lde_nbr_find(imsg.hdr.peerid); if (ln == NULL) { log_debug("%s: cannot find lde neighbor", __func__); break; } if (lde_address_del(ln, lde_addr) < 0) { log_debug("%s: cannot delete address %s, it " "does not exist", __func__, log_addr(lde_addr->af, &lde_addr->addr)); } break; case IMSG_NOTIFICATION: if (imsg.hdr.len - IMSG_HEADER_SIZE != sizeof(struct notify_msg)) fatalx("lde_dispatch_imsg: wrong imsg len"); nm = imsg.data; ln = lde_nbr_find(imsg.hdr.peerid); if (ln == NULL) { log_debug("%s: cannot find lde neighbor", __func__); break; } switch (nm->status_code) { case S_PW_STATUS: l2vpn_recv_pw_status(ln, nm); break; case S_ENDOFLIB: /* * Do nothing for now. Should be useful in * the future when we implement LDP-IGP * Synchronization (RFC 5443) and Graceful * Restart (RFC 3478). */ default: break; } break; case IMSG_NEIGHBOR_UP: if (imsg.hdr.len - IMSG_HEADER_SIZE != sizeof(struct lde_nbr)) fatalx("lde_dispatch_imsg: wrong imsg len"); if (lde_nbr_find(imsg.hdr.peerid)) fatalx("lde_dispatch_imsg: " "neighbor already exists"); lde_nbr_new(imsg.hdr.peerid, imsg.data); break; case IMSG_NEIGHBOR_DOWN: lde_nbr_del(lde_nbr_find(imsg.hdr.peerid)); break; case IMSG_CTL_SHOW_LIB: rt_dump(imsg.hdr.pid); lde_imsg_compose_ldpe(IMSG_CTL_END, 0, imsg.hdr.pid, NULL, 0); break; case IMSG_CTL_SHOW_L2VPN_PW: l2vpn_pw_ctl(imsg.hdr.pid); lde_imsg_compose_ldpe(IMSG_CTL_END, 0, imsg.hdr.pid, NULL, 0); break; case IMSG_CTL_SHOW_L2VPN_BINDING: l2vpn_binding_ctl(imsg.hdr.pid); lde_imsg_compose_ldpe(IMSG_CTL_END, 0, imsg.hdr.pid, NULL, 0); break; default: log_debug("%s: unexpected imsg %d", __func__, imsg.hdr.type); break; } imsg_free(&imsg); } if (!shut) imsg_event_add(iev); else { /* this pipe is dead, so remove the event handlers and exit */ THREAD_READ_OFF(iev->ev_read); THREAD_WRITE_OFF(iev->ev_write); lde_shutdown(); } return (0); } /* ARGSUSED */ static int lde_dispatch_parent(struct thread *thread) { static struct ldpd_conf *nconf; struct iface *iface, *niface; struct tnbr *ntnbr; struct nbr_params *nnbrp; static struct l2vpn *l2vpn, *nl2vpn; struct l2vpn_if *lif, *nlif; struct l2vpn_pw *pw, *npw; struct imsg imsg; struct kif *kif; struct kroute *kr; int fd; struct imsgev *iev = THREAD_ARG(thread); struct imsgbuf *ibuf = &iev->ibuf; ssize_t n; int shut = 0; struct fec fec; iev->ev_read = NULL; if ((n = imsg_read(ibuf)) == -1 && errno != EAGAIN) fatal("imsg_read error"); if (n == 0) /* connection closed */ shut = 1; for (;;) { if ((n = imsg_get(ibuf, &imsg)) == -1) fatal("lde_dispatch_parent: imsg_get error"); if (n == 0) break; switch (imsg.hdr.type) { case IMSG_IFSTATUS: if (imsg.hdr.len != IMSG_HEADER_SIZE + sizeof(struct kif)) fatalx("IFSTATUS imsg with wrong len"); kif = imsg.data; iface = if_lookup_name(ldeconf, kif->ifname); if (iface) { if_update_info(iface, kif); break; } RB_FOREACH(l2vpn, l2vpn_head, &ldeconf->l2vpn_tree) { lif = l2vpn_if_find(l2vpn, kif->ifname); if (lif) { l2vpn_if_update_info(lif, kif); break; } pw = l2vpn_pw_find(l2vpn, kif->ifname); if (pw) { l2vpn_pw_update_info(pw, kif); break; } } break; case IMSG_PW_UPDATE: if (imsg.hdr.len != IMSG_HEADER_SIZE + sizeof(struct zapi_pw_status)) fatalx("PW_UPDATE imsg with wrong len"); if (l2vpn_pw_status_update(imsg.data) != 0) log_warnx("%s: error updating PW status", __func__); break; case IMSG_NETWORK_ADD: case IMSG_NETWORK_UPDATE: if (imsg.hdr.len != IMSG_HEADER_SIZE + sizeof(struct kroute)) { log_warnx("%s: wrong imsg len", __func__); break; } kr = imsg.data; switch (kr->af) { case AF_INET: fec.type = FEC_TYPE_IPV4; fec.u.ipv4.prefix = kr->prefix.v4; fec.u.ipv4.prefixlen = kr->prefixlen; break; case AF_INET6: fec.type = FEC_TYPE_IPV6; fec.u.ipv6.prefix = kr->prefix.v6; fec.u.ipv6.prefixlen = kr->prefixlen; break; default: fatalx("lde_dispatch_parent: unknown af"); } switch (imsg.hdr.type) { case IMSG_NETWORK_ADD: lde_kernel_insert(&fec, kr->af, &kr->nexthop, kr->ifindex, kr->priority, kr->flags & F_CONNECTED, NULL); break; case IMSG_NETWORK_UPDATE: lde_kernel_update(&fec); break; } break; case IMSG_SOCKET_IPC: if (iev_ldpe) { log_warnx("%s: received unexpected imsg fd " "to ldpe", __func__); break; } if ((fd = imsg.fd) == -1) { log_warnx("%s: expected to receive imsg fd to " "ldpe but didn't receive any", __func__); break; } if ((iev_ldpe = malloc(sizeof(struct imsgev))) == NULL) fatal(NULL); imsg_init(&iev_ldpe->ibuf, fd); iev_ldpe->handler_read = lde_dispatch_imsg; iev_ldpe->ev_read = NULL; thread_add_read(master, iev_ldpe->handler_read, iev_ldpe, iev_ldpe->ibuf.fd, &iev_ldpe->ev_read); iev_ldpe->handler_write = ldp_write_handler; iev_ldpe->ev_write = NULL; break; case IMSG_INIT: if (imsg.hdr.len != IMSG_HEADER_SIZE + sizeof(struct ldpd_init)) fatalx("INIT imsg with wrong len"); memcpy(&init, imsg.data, sizeof(init)); lde_init(&init); break; case IMSG_RECONF_CONF: if ((nconf = malloc(sizeof(struct ldpd_conf))) == NULL) fatal(NULL); memcpy(nconf, imsg.data, sizeof(struct ldpd_conf)); RB_INIT(iface_head, &nconf->iface_tree); RB_INIT(tnbr_head, &nconf->tnbr_tree); RB_INIT(nbrp_head, &nconf->nbrp_tree); RB_INIT(l2vpn_head, &nconf->l2vpn_tree); break; case IMSG_RECONF_IFACE: if ((niface = malloc(sizeof(struct iface))) == NULL) fatal(NULL); memcpy(niface, imsg.data, sizeof(struct iface)); RB_INSERT(iface_head, &nconf->iface_tree, niface); break; case IMSG_RECONF_TNBR: if ((ntnbr = malloc(sizeof(struct tnbr))) == NULL) fatal(NULL); memcpy(ntnbr, imsg.data, sizeof(struct tnbr)); RB_INSERT(tnbr_head, &nconf->tnbr_tree, ntnbr); break; case IMSG_RECONF_NBRP: if ((nnbrp = malloc(sizeof(struct nbr_params))) == NULL) fatal(NULL); memcpy(nnbrp, imsg.data, sizeof(struct nbr_params)); RB_INSERT(nbrp_head, &nconf->nbrp_tree, nnbrp); break; case IMSG_RECONF_L2VPN: if ((nl2vpn = malloc(sizeof(struct l2vpn))) == NULL) fatal(NULL); memcpy(nl2vpn, imsg.data, sizeof(struct l2vpn)); RB_INIT(l2vpn_if_head, &nl2vpn->if_tree); RB_INIT(l2vpn_pw_head, &nl2vpn->pw_tree); RB_INIT(l2vpn_pw_head, &nl2vpn->pw_inactive_tree); RB_INSERT(l2vpn_head, &nconf->l2vpn_tree, nl2vpn); break; case IMSG_RECONF_L2VPN_IF: if ((nlif = malloc(sizeof(struct l2vpn_if))) == NULL) fatal(NULL); memcpy(nlif, imsg.data, sizeof(struct l2vpn_if)); RB_INSERT(l2vpn_if_head, &nl2vpn->if_tree, nlif); break; case IMSG_RECONF_L2VPN_PW: if ((npw = malloc(sizeof(struct l2vpn_pw))) == NULL) fatal(NULL); memcpy(npw, imsg.data, sizeof(struct l2vpn_pw)); RB_INSERT(l2vpn_pw_head, &nl2vpn->pw_tree, npw); break; case IMSG_RECONF_L2VPN_IPW: if ((npw = malloc(sizeof(struct l2vpn_pw))) == NULL) fatal(NULL); memcpy(npw, imsg.data, sizeof(struct l2vpn_pw)); RB_INSERT(l2vpn_pw_head, &nl2vpn->pw_inactive_tree, npw); break; case IMSG_RECONF_END: merge_config(ldeconf, nconf); ldp_clear_config(nconf); nconf = NULL; break; case IMSG_DEBUG_UPDATE: if (imsg.hdr.len != IMSG_HEADER_SIZE + sizeof(ldp_debug)) { log_warnx("%s: wrong imsg len", __func__); break; } memcpy(&ldp_debug, imsg.data, sizeof(ldp_debug)); break; default: log_debug("%s: unexpected imsg %d", __func__, imsg.hdr.type); break; } imsg_free(&imsg); } if (!shut) imsg_event_add(iev); else { /* this pipe is dead, so remove the event handlers and exit */ THREAD_READ_OFF(iev->ev_read); THREAD_WRITE_OFF(iev->ev_write); lde_shutdown(); } return (0); } int lde_acl_check(char *acl_name, int af, union ldpd_addr *addr, uint8_t prefixlen) { return ldp_acl_request(iev_main_sync, acl_name, af, addr, prefixlen); } uint32_t lde_update_label(struct fec_node *fn) { struct fec_nh *fnh; int connected = 0; LIST_FOREACH(fnh, &fn->nexthops, entry) { if (fnh->flags & F_FEC_NH_CONNECTED) { connected = 1; break; } } /* should we allocate a label for this fec? */ switch (fn->fec.type) { case FEC_TYPE_IPV4: if ((ldeconf->ipv4.flags & F_LDPD_AF_ALLOCHOSTONLY) && fn->fec.u.ipv4.prefixlen != 32) return (NO_LABEL); if (lde_acl_check(ldeconf->ipv4.acl_label_allocate_for, AF_INET, (union ldpd_addr *)&fn->fec.u.ipv4.prefix, fn->fec.u.ipv4.prefixlen) != FILTER_PERMIT) return (NO_LABEL); break; case FEC_TYPE_IPV6: if ((ldeconf->ipv6.flags & F_LDPD_AF_ALLOCHOSTONLY) && fn->fec.u.ipv6.prefixlen != 128) return (NO_LABEL); if (lde_acl_check(ldeconf->ipv6.acl_label_allocate_for, AF_INET6, (union ldpd_addr *)&fn->fec.u.ipv6.prefix, fn->fec.u.ipv6.prefixlen) != FILTER_PERMIT) return (NO_LABEL); break; default: break; } if (connected) { /* choose implicit or explicit-null depending on configuration */ switch (fn->fec.type) { case FEC_TYPE_IPV4: if (!(ldeconf->ipv4.flags & F_LDPD_AF_EXPNULL)) return (MPLS_LABEL_IMPLICIT_NULL); if (lde_acl_check(ldeconf->ipv4.acl_label_expnull_for, AF_INET, (union ldpd_addr *)&fn->fec.u.ipv4.prefix, fn->fec.u.ipv4.prefixlen) != FILTER_PERMIT) return (MPLS_LABEL_IMPLICIT_NULL); return MPLS_LABEL_IPV4_EXPLICIT_NULL; case FEC_TYPE_IPV6: if (!(ldeconf->ipv6.flags & F_LDPD_AF_EXPNULL)) return (MPLS_LABEL_IMPLICIT_NULL); if (lde_acl_check(ldeconf->ipv6.acl_label_expnull_for, AF_INET6, (union ldpd_addr *)&fn->fec.u.ipv6.prefix, fn->fec.u.ipv6.prefixlen) != FILTER_PERMIT) return (MPLS_LABEL_IMPLICIT_NULL); return MPLS_LABEL_IPV6_EXPLICIT_NULL; default: fatalx("lde_update_label: unexpected fec type"); break; } } /* preserve current label if there's no need to update it */ if (fn->local_label != NO_LABEL && fn->local_label > MPLS_LABEL_RESERVED_MAX) return (fn->local_label); return (lde_get_next_label()); } void lde_send_change_klabel(struct fec_node *fn, struct fec_nh *fnh) { struct kroute kr; struct zapi_pw zpw; struct l2vpn_pw *pw; switch (fn->fec.type) { case FEC_TYPE_IPV4: memset(&kr, 0, sizeof(kr)); kr.af = AF_INET; kr.prefix.v4 = fn->fec.u.ipv4.prefix; kr.prefixlen = fn->fec.u.ipv4.prefixlen; kr.nexthop.v4 = fnh->nexthop.v4; kr.ifindex = fnh->ifindex; kr.local_label = fn->local_label; kr.remote_label = fnh->remote_label; kr.priority = fnh->priority; lde_imsg_compose_parent(IMSG_KLABEL_CHANGE, 0, &kr, sizeof(kr)); break; case FEC_TYPE_IPV6: memset(&kr, 0, sizeof(kr)); kr.af = AF_INET6; kr.prefix.v6 = fn->fec.u.ipv6.prefix; kr.prefixlen = fn->fec.u.ipv6.prefixlen; kr.nexthop.v6 = fnh->nexthop.v6; kr.ifindex = fnh->ifindex; kr.local_label = fn->local_label; kr.remote_label = fnh->remote_label; kr.priority = fnh->priority; lde_imsg_compose_parent(IMSG_KLABEL_CHANGE, 0, &kr, sizeof(kr)); break; case FEC_TYPE_PWID: pw = (struct l2vpn_pw *) fn->data; if (!pw || fn->local_label == NO_LABEL || fnh->remote_label == NO_LABEL) return; pw->enabled = true; pw2zpw(pw, &zpw); zpw.local_label = fn->local_label; zpw.remote_label = fnh->remote_label; lde_imsg_compose_parent(IMSG_KPW_SET, 0, &zpw, sizeof(zpw)); break; } } void lde_send_delete_klabel(struct fec_node *fn, struct fec_nh *fnh) { struct kroute kr; struct zapi_pw zpw; struct l2vpn_pw *pw; switch (fn->fec.type) { case FEC_TYPE_IPV4: memset(&kr, 0, sizeof(kr)); kr.af = AF_INET; kr.prefix.v4 = fn->fec.u.ipv4.prefix; kr.prefixlen = fn->fec.u.ipv4.prefixlen; kr.nexthop.v4 = fnh->nexthop.v4; kr.ifindex = fnh->ifindex; kr.local_label = fn->local_label; kr.remote_label = fnh->remote_label; kr.priority = fnh->priority; lde_imsg_compose_parent(IMSG_KLABEL_DELETE, 0, &kr, sizeof(kr)); break; case FEC_TYPE_IPV6: memset(&kr, 0, sizeof(kr)); kr.af = AF_INET6; kr.prefix.v6 = fn->fec.u.ipv6.prefix; kr.prefixlen = fn->fec.u.ipv6.prefixlen; kr.nexthop.v6 = fnh->nexthop.v6; kr.ifindex = fnh->ifindex; kr.local_label = fn->local_label; kr.remote_label = fnh->remote_label; kr.priority = fnh->priority; lde_imsg_compose_parent(IMSG_KLABEL_DELETE, 0, &kr, sizeof(kr)); break; case FEC_TYPE_PWID: pw = (struct l2vpn_pw *) fn->data; if (!pw) return; pw->enabled = false; pw2zpw(pw, &zpw); zpw.local_label = fn->local_label; zpw.remote_label = fnh->remote_label; lde_imsg_compose_parent(IMSG_KPW_UNSET, 0, &zpw, sizeof(zpw)); break; } } void lde_fec2map(struct fec *fec, struct map *map) { memset(map, 0, sizeof(*map)); switch (fec->type) { case FEC_TYPE_IPV4: map->type = MAP_TYPE_PREFIX; map->fec.prefix.af = AF_INET; map->fec.prefix.prefix.v4 = fec->u.ipv4.prefix; map->fec.prefix.prefixlen = fec->u.ipv4.prefixlen; break; case FEC_TYPE_IPV6: map->type = MAP_TYPE_PREFIX; map->fec.prefix.af = AF_INET6; map->fec.prefix.prefix.v6 = fec->u.ipv6.prefix; map->fec.prefix.prefixlen = fec->u.ipv6.prefixlen; break; case FEC_TYPE_PWID: map->type = MAP_TYPE_PWID; map->fec.pwid.type = fec->u.pwid.type; map->fec.pwid.group_id = 0; map->flags |= F_MAP_PW_ID; map->fec.pwid.pwid = fec->u.pwid.pwid; break; } } void lde_map2fec(struct map *map, struct in_addr lsr_id, struct fec *fec) { memset(fec, 0, sizeof(*fec)); switch (map->type) { case MAP_TYPE_PREFIX: switch (map->fec.prefix.af) { case AF_INET: fec->type = FEC_TYPE_IPV4; fec->u.ipv4.prefix = map->fec.prefix.prefix.v4; fec->u.ipv4.prefixlen = map->fec.prefix.prefixlen; break; case AF_INET6: fec->type = FEC_TYPE_IPV6; fec->u.ipv6.prefix = map->fec.prefix.prefix.v6; fec->u.ipv6.prefixlen = map->fec.prefix.prefixlen; break; default: fatalx("lde_map2fec: unknown af"); break; } break; case MAP_TYPE_PWID: fec->type = FEC_TYPE_PWID; fec->u.pwid.type = map->fec.pwid.type; fec->u.pwid.pwid = map->fec.pwid.pwid; fec->u.pwid.lsr_id = lsr_id; break; } } void lde_send_labelmapping(struct lde_nbr *ln, struct fec_node *fn, int single) { struct lde_wdraw *lw; struct lde_map *me; struct lde_req *lre; struct map map; struct l2vpn_pw *pw; /* * We shouldn't send a new label mapping if we have a pending * label release to receive. In this case, schedule to send a * label mapping as soon as a label release is received. */ lw = (struct lde_wdraw *)fec_find(&ln->sent_wdraw, &fn->fec); if (lw) { if (!fec_find(&ln->sent_map_pending, &fn->fec)) { debug_evt("%s: FEC %s: scheduling to send label " "mapping later (waiting for pending label release)", __func__, log_fec(&fn->fec)); lde_map_pending_add(ln, fn); } return; } /* * This function skips SL.1 - 3 and SL.9 - 14 because the label * allocation is done way earlier (because of the merging nature of * ldpd). */ lde_fec2map(&fn->fec, &map); switch (fn->fec.type) { case FEC_TYPE_IPV4: if (!ln->v4_enabled) return; if (lde_acl_check(ldeconf->ipv4.acl_label_advertise_to, AF_INET, (union ldpd_addr *)&ln->id, 32) != FILTER_PERMIT) return; if (lde_acl_check(ldeconf->ipv4.acl_label_advertise_for, AF_INET, (union ldpd_addr *)&fn->fec.u.ipv4.prefix, fn->fec.u.ipv4.prefixlen) != FILTER_PERMIT) return; break; case FEC_TYPE_IPV6: if (!ln->v6_enabled) return; if (lde_acl_check(ldeconf->ipv6.acl_label_advertise_to, AF_INET, (union ldpd_addr *)&ln->id, 32) != FILTER_PERMIT) return; if (lde_acl_check(ldeconf->ipv6.acl_label_advertise_for, AF_INET6, (union ldpd_addr *)&fn->fec.u.ipv6.prefix, fn->fec.u.ipv6.prefixlen) != FILTER_PERMIT) return; break; case FEC_TYPE_PWID: pw = (struct l2vpn_pw *) fn->data; if (pw == NULL || pw->lsr_id.s_addr != ln->id.s_addr) /* not the remote end of the pseudowire */ return; map.flags |= F_MAP_PW_IFMTU; map.fec.pwid.ifmtu = pw->l2vpn->mtu; if (pw->flags & F_PW_CWORD) map.flags |= F_MAP_PW_CWORD; if (pw->flags & F_PW_STATUSTLV) { map.flags |= F_MAP_PW_STATUS; map.pw_status = pw->local_status; } break; } map.label = fn->local_label; /* SL.6: is there a pending request for this mapping? */ lre = (struct lde_req *)fec_find(&ln->recv_req, &fn->fec); if (lre) { /* set label request msg id in the mapping response. */ map.requestid = lre->msg_id; map.flags = F_MAP_REQ_ID; /* SL.7: delete record of pending request */ lde_req_del(ln, lre, 0); } /* SL.4: send label mapping */ lde_imsg_compose_ldpe(IMSG_MAPPING_ADD, ln->peerid, 0, &map, sizeof(map)); if (single) lde_imsg_compose_ldpe(IMSG_MAPPING_ADD_END, ln->peerid, 0, NULL, 0); /* SL.5: record sent label mapping */ me = (struct lde_map *)fec_find(&ln->sent_map, &fn->fec); if (me == NULL) me = lde_map_add(ln, fn, 1); me->map = map; } void lde_send_labelwithdraw(struct lde_nbr *ln, struct fec_node *fn, struct map *wcard, struct status_tlv *st) { struct lde_wdraw *lw; struct map map; struct fec *f; struct l2vpn_pw *pw; if (fn) { lde_fec2map(&fn->fec, &map); switch (fn->fec.type) { case FEC_TYPE_IPV4: if (!ln->v4_enabled) return; break; case FEC_TYPE_IPV6: if (!ln->v6_enabled) return; break; case FEC_TYPE_PWID: pw = (struct l2vpn_pw *) fn->data; if (pw == NULL || pw->lsr_id.s_addr != ln->id.s_addr) /* not the remote end of the pseudowire */ return; if (pw->flags & F_PW_CWORD) map.flags |= F_MAP_PW_CWORD; break; } map.label = fn->local_label; } else memcpy(&map, wcard, sizeof(map)); if (st) { map.st.status_code = st->status_code; map.st.msg_id = st->msg_id; map.st.msg_type = st->msg_type; map.flags |= F_MAP_STATUS; } /* SWd.1: send label withdraw. */ lde_imsg_compose_ldpe(IMSG_WITHDRAW_ADD, ln->peerid, 0, &map, sizeof(map)); lde_imsg_compose_ldpe(IMSG_WITHDRAW_ADD_END, ln->peerid, 0, NULL, 0); /* SWd.2: record label withdraw. */ if (fn) { lw = (struct lde_wdraw *)fec_find(&ln->sent_wdraw, &fn->fec); if (lw == NULL) lw = lde_wdraw_add(ln, fn); lw->label = map.label; } else { struct lde_map *me; RB_FOREACH(f, fec_tree, &ft) { fn = (struct fec_node *)f; me = (struct lde_map *)fec_find(&ln->sent_map, &fn->fec); if (lde_wildcard_apply(wcard, &fn->fec, me) == 0) continue; lw = (struct lde_wdraw *)fec_find(&ln->sent_wdraw, &fn->fec); if (lw == NULL) lw = lde_wdraw_add(ln, fn); lw->label = map.label; } } } void lde_send_labelwithdraw_wcard(struct lde_nbr *ln, uint32_t label) { struct map wcard; memset(&wcard, 0, sizeof(wcard)); wcard.type = MAP_TYPE_WILDCARD; wcard.label = label; lde_send_labelwithdraw(ln, NULL, &wcard, NULL); } void lde_send_labelwithdraw_twcard_prefix(struct lde_nbr *ln, uint16_t af, uint32_t label) { struct map wcard; memset(&wcard, 0, sizeof(wcard)); wcard.type = MAP_TYPE_TYPED_WCARD; wcard.fec.twcard.type = MAP_TYPE_PREFIX; wcard.fec.twcard.u.prefix_af = af; wcard.label = label; lde_send_labelwithdraw(ln, NULL, &wcard, NULL); } void lde_send_labelwithdraw_twcard_pwid(struct lde_nbr *ln, uint16_t pw_type, uint32_t label) { struct map wcard; memset(&wcard, 0, sizeof(wcard)); wcard.type = MAP_TYPE_TYPED_WCARD; wcard.fec.twcard.type = MAP_TYPE_PWID; wcard.fec.twcard.u.pw_type = pw_type; wcard.label = label; lde_send_labelwithdraw(ln, NULL, &wcard, NULL); } void lde_send_labelwithdraw_pwid_wcard(struct lde_nbr *ln, uint16_t pw_type, uint32_t group_id) { struct map wcard; memset(&wcard, 0, sizeof(wcard)); wcard.type = MAP_TYPE_PWID; wcard.fec.pwid.type = pw_type; wcard.fec.pwid.group_id = group_id; /* we can not append a Label TLV when using PWid group wildcards. */ wcard.label = NO_LABEL; lde_send_labelwithdraw(ln, NULL, &wcard, NULL); } void lde_send_labelrelease(struct lde_nbr *ln, struct fec_node *fn, struct map *wcard, uint32_t label) { struct map map; struct l2vpn_pw *pw; if (fn) { lde_fec2map(&fn->fec, &map); switch (fn->fec.type) { case FEC_TYPE_IPV4: if (!ln->v4_enabled) return; break; case FEC_TYPE_IPV6: if (!ln->v6_enabled) return; break; case FEC_TYPE_PWID: pw = (struct l2vpn_pw *) fn->data; if (pw == NULL || pw->lsr_id.s_addr != ln->id.s_addr) /* not the remote end of the pseudowire */ return; if (pw->flags & F_PW_CWORD) map.flags |= F_MAP_PW_CWORD; break; } } else memcpy(&map, wcard, sizeof(map)); map.label = label; lde_imsg_compose_ldpe(IMSG_RELEASE_ADD, ln->peerid, 0, &map, sizeof(map)); lde_imsg_compose_ldpe(IMSG_RELEASE_ADD_END, ln->peerid, 0, NULL, 0); } void lde_send_notification(struct lde_nbr *ln, uint32_t status_code, uint32_t msg_id, uint16_t msg_type) { struct notify_msg nm; memset(&nm, 0, sizeof(nm)); nm.status_code = status_code; /* 'msg_id' and 'msg_type' should be in network byte order */ nm.msg_id = msg_id; nm.msg_type = msg_type; lde_imsg_compose_ldpe(IMSG_NOTIFICATION_SEND, ln->peerid, 0, &nm, sizeof(nm)); } void lde_send_notification_eol_prefix(struct lde_nbr *ln, int af) { struct notify_msg nm; memset(&nm, 0, sizeof(nm)); nm.status_code = S_ENDOFLIB; nm.fec.type = MAP_TYPE_TYPED_WCARD; nm.fec.fec.twcard.type = MAP_TYPE_PREFIX; nm.fec.fec.twcard.u.prefix_af = af; nm.flags |= F_NOTIF_FEC; lde_imsg_compose_ldpe(IMSG_NOTIFICATION_SEND, ln->peerid, 0, &nm, sizeof(nm)); } void lde_send_notification_eol_pwid(struct lde_nbr *ln, uint16_t pw_type) { struct notify_msg nm; memset(&nm, 0, sizeof(nm)); nm.status_code = S_ENDOFLIB; nm.fec.type = MAP_TYPE_TYPED_WCARD; nm.fec.fec.twcard.type = MAP_TYPE_PWID; nm.fec.fec.twcard.u.pw_type = pw_type; nm.flags |= F_NOTIF_FEC; lde_imsg_compose_ldpe(IMSG_NOTIFICATION_SEND, ln->peerid, 0, &nm, sizeof(nm)); } static __inline int lde_nbr_compare(const struct lde_nbr *a, const struct lde_nbr *b) { return (a->peerid - b->peerid); } static struct lde_nbr * lde_nbr_new(uint32_t peerid, struct lde_nbr *new) { struct lde_nbr *ln; if ((ln = calloc(1, sizeof(*ln))) == NULL) fatal(__func__); ln->id = new->id; ln->v4_enabled = new->v4_enabled; ln->v6_enabled = new->v6_enabled; ln->flags = new->flags; ln->peerid = peerid; fec_init(&ln->recv_map); fec_init(&ln->sent_map); fec_init(&ln->sent_map_pending); fec_init(&ln->recv_req); fec_init(&ln->sent_req); fec_init(&ln->sent_wdraw); TAILQ_INIT(&ln->addr_list); if (RB_INSERT(nbr_tree, &lde_nbrs, ln) != NULL) fatalx("lde_nbr_new: RB_INSERT failed"); return (ln); } static void lde_nbr_del(struct lde_nbr *ln) { struct fec *f; struct fec_node *fn; struct fec_nh *fnh; struct l2vpn_pw *pw; if (ln == NULL) return; /* uninstall received mappings */ RB_FOREACH(f, fec_tree, &ft) { fn = (struct fec_node *)f; LIST_FOREACH(fnh, &fn->nexthops, entry) { switch (f->type) { case FEC_TYPE_IPV4: case FEC_TYPE_IPV6: if (!lde_address_find(ln, fnh->af, &fnh->nexthop)) continue; break; case FEC_TYPE_PWID: if (f->u.pwid.lsr_id.s_addr != ln->id.s_addr) continue; pw = (struct l2vpn_pw *) fn->data; if (pw) l2vpn_pw_reset(pw); break; default: break; } lde_send_delete_klabel(fn, fnh); fnh->remote_label = NO_LABEL; } } lde_address_list_free(ln); fec_clear(&ln->recv_map, lde_map_free); fec_clear(&ln->sent_map, lde_map_free); fec_clear(&ln->sent_map_pending, free); fec_clear(&ln->recv_req, free); fec_clear(&ln->sent_req, free); fec_clear(&ln->sent_wdraw, free); RB_REMOVE(nbr_tree, &lde_nbrs, ln); free(ln); } static struct lde_nbr * lde_nbr_find(uint32_t peerid) { struct lde_nbr ln; ln.peerid = peerid; return (RB_FIND(nbr_tree, &lde_nbrs, &ln)); } struct lde_nbr * lde_nbr_find_by_lsrid(struct in_addr addr) { struct lde_nbr *ln; RB_FOREACH(ln, nbr_tree, &lde_nbrs) if (ln->id.s_addr == addr.s_addr) return (ln); return (NULL); } struct lde_nbr * lde_nbr_find_by_addr(int af, union ldpd_addr *addr) { struct lde_nbr *ln; RB_FOREACH(ln, nbr_tree, &lde_nbrs) if (lde_address_find(ln, af, addr) != NULL) return (ln); return (NULL); } static void lde_nbr_clear(void) { struct lde_nbr *ln; while (!RB_EMPTY(nbr_tree, &lde_nbrs)) { ln = RB_ROOT(nbr_tree, &lde_nbrs); lde_nbr_del(ln); } } static void lde_nbr_addr_update(struct lde_nbr *ln, struct lde_addr *lde_addr, int removed) { struct fec *fec; struct fec_node *fn; struct fec_nh *fnh; struct lde_map *me; RB_FOREACH(fec, fec_tree, &ln->recv_map) { switch (fec->type) { case FEC_TYPE_IPV4: if (lde_addr->af != AF_INET) continue; break; case FEC_TYPE_IPV6: if (lde_addr->af != AF_INET6) continue; break; default: continue; } fn = (struct fec_node *)fec_find(&ft, fec); if (fn == NULL) /* shouldn't happen */ continue; LIST_FOREACH(fnh, &fn->nexthops, entry) { if (ldp_addrcmp(fnh->af, &fnh->nexthop, &lde_addr->addr)) continue; if (removed) { lde_send_delete_klabel(fn, fnh); fnh->remote_label = NO_LABEL; } else { me = (struct lde_map *)fec; fnh->remote_label = me->map.label; lde_send_change_klabel(fn, fnh); } break; } } } static __inline int lde_map_compare(const struct lde_map *a, const struct lde_map *b) { return (ldp_addrcmp(AF_INET, (union ldpd_addr *)&a->nexthop->id, (union ldpd_addr *)&b->nexthop->id)); } struct lde_map * lde_map_add(struct lde_nbr *ln, struct fec_node *fn, int sent) { struct lde_map *me; me = calloc(1, sizeof(*me)); if (me == NULL) fatal(__func__); me->fec = fn->fec; me->nexthop = ln; if (sent) { RB_INSERT(lde_map_head, &fn->upstream, me); me->head = &fn->upstream; if (fec_insert(&ln->sent_map, &me->fec)) log_warnx("failed to add %s to sent map", log_fec(&me->fec)); /* XXX on failure more cleanup is needed */ } else { RB_INSERT(lde_map_head, &fn->downstream, me); me->head = &fn->downstream; if (fec_insert(&ln->recv_map, &me->fec)) log_warnx("failed to add %s to recv map", log_fec(&me->fec)); } return (me); } void lde_map_del(struct lde_nbr *ln, struct lde_map *me, int sent) { if (sent) fec_remove(&ln->sent_map, &me->fec); else fec_remove(&ln->recv_map, &me->fec); lde_map_free(me); } static void lde_map_free(void *ptr) { struct lde_map *map = ptr; RB_REMOVE(lde_map_head, map->head, map); free(map); } struct fec * lde_map_pending_add(struct lde_nbr *ln, struct fec_node *fn) { struct fec *map; map = calloc(1, sizeof(*map)); if (map == NULL) fatal(__func__); *map = fn->fec; if (fec_insert(&ln->sent_map_pending, map)) log_warnx("failed to add %s to sent map (pending)", log_fec(map)); return (map); } void lde_map_pending_del(struct lde_nbr *ln, struct fec *map) { fec_remove(&ln->sent_map_pending, map); free(map); } struct lde_req * lde_req_add(struct lde_nbr *ln, struct fec *fec, int sent) { struct fec_tree *t; struct lde_req *lre; t = sent ? &ln->sent_req : &ln->recv_req; lre = calloc(1, sizeof(*lre)); if (lre != NULL) { lre->fec = *fec; if (fec_insert(t, &lre->fec)) { log_warnx("failed to add %s to %s req", log_fec(&lre->fec), sent ? "sent" : "recv"); free(lre); return (NULL); } } return (lre); } void lde_req_del(struct lde_nbr *ln, struct lde_req *lre, int sent) { if (sent) fec_remove(&ln->sent_req, &lre->fec); else fec_remove(&ln->recv_req, &lre->fec); free(lre); } struct lde_wdraw * lde_wdraw_add(struct lde_nbr *ln, struct fec_node *fn) { struct lde_wdraw *lw; lw = calloc(1, sizeof(*lw)); if (lw == NULL) fatal(__func__); lw->fec = fn->fec; if (fec_insert(&ln->sent_wdraw, &lw->fec)) log_warnx("failed to add %s to sent wdraw", log_fec(&lw->fec)); return (lw); } void lde_wdraw_del(struct lde_nbr *ln, struct lde_wdraw *lw) { fec_remove(&ln->sent_wdraw, &lw->fec); free(lw); } void lde_change_egress_label(int af) { struct lde_nbr *ln; struct fec *f; struct fec_node *fn; /* explicitly withdraw all null labels */ RB_FOREACH(ln, nbr_tree, &lde_nbrs) { lde_send_labelwithdraw_wcard(ln, MPLS_LABEL_IMPLICIT_NULL); if (ln->v4_enabled) lde_send_labelwithdraw_wcard( ln, MPLS_LABEL_IPV4_EXPLICIT_NULL); if (ln->v6_enabled) lde_send_labelwithdraw_wcard( ln, MPLS_LABEL_IPV6_EXPLICIT_NULL); } /* update label of connected routes */ RB_FOREACH(f, fec_tree, &ft) { fn = (struct fec_node *)f; if (fn->local_label > MPLS_LABEL_RESERVED_MAX) continue; switch (af) { case AF_INET: if (fn->fec.type != FEC_TYPE_IPV4) continue; break; case AF_INET6: if (fn->fec.type != FEC_TYPE_IPV6) continue; break; default: fatalx("lde_change_egress_label: unknown af"); } fn->local_label = lde_update_label(fn); if (fn->local_label != NO_LABEL) RB_FOREACH(ln, nbr_tree, &lde_nbrs) lde_send_labelmapping(ln, fn, 0); } RB_FOREACH(ln, nbr_tree, &lde_nbrs) lde_imsg_compose_ldpe(IMSG_MAPPING_ADD_END, ln->peerid, 0, NULL, 0); } static int lde_address_add(struct lde_nbr *ln, struct lde_addr *lde_addr) { struct lde_addr *new; if (lde_address_find(ln, lde_addr->af, &lde_addr->addr) != NULL) return (-1); if ((new = calloc(1, sizeof(*new))) == NULL) fatal(__func__); new->af = lde_addr->af; new->addr = lde_addr->addr; TAILQ_INSERT_TAIL(&ln->addr_list, new, entry); /* reevaluate the previously received mappings from this neighbor */ lde_nbr_addr_update(ln, lde_addr, 0); return (0); } static int lde_address_del(struct lde_nbr *ln, struct lde_addr *lde_addr) { lde_addr = lde_address_find(ln, lde_addr->af, &lde_addr->addr); if (lde_addr == NULL) return (-1); /* reevaluate the previously received mappings from this neighbor */ lde_nbr_addr_update(ln, lde_addr, 1); TAILQ_REMOVE(&ln->addr_list, lde_addr, entry); free(lde_addr); return (0); } struct lde_addr * lde_address_find(struct lde_nbr *ln, int af, union ldpd_addr *addr) { struct lde_addr *lde_addr; TAILQ_FOREACH(lde_addr, &ln->addr_list, entry) if (lde_addr->af == af && ldp_addrcmp(af, &lde_addr->addr, addr) == 0) return (lde_addr); return (NULL); } static void lde_address_list_free(struct lde_nbr *ln) { struct lde_addr *lde_addr; while ((lde_addr = TAILQ_POP_FIRST(&ln->addr_list, entry)) != NULL) free(lde_addr); } static void zclient_sync_init(unsigned short instance) { /* Initialize special zclient for synchronous message exchanges. */ zclient_sync = zclient_new(master, &zclient_options_default); zclient_sync->sock = -1; zclient_sync->redist_default = ZEBRA_ROUTE_LDP; zclient_sync->instance = instance; zclient_sync->privs = &lde_privs; while (zclient_socket_connect(zclient_sync) < 0) { log_warnx("Error connecting synchronous zclient!"); sleep(1); } /* make socket non-blocking */ sock_set_nonblock(zclient_sync->sock); /* Connect to label manager */ while (lm_label_manager_connect(zclient_sync, 0) != 0) { log_warnx("Error connecting to label manager!"); sleep(1); } } static void lde_del_label_chunk(void *val) { free(val); } static int lde_release_label_chunk(uint32_t start, uint32_t end) { int ret; ret = lm_release_label_chunk(zclient_sync, start, end); if (ret < 0) { log_warnx("Error releasing label chunk!"); return (-1); } return (0); } static int lde_get_label_chunk(void) { int ret; uint32_t start, end; debug_labels("getting label chunk (size %u)", CHUNK_SIZE); ret = lm_get_label_chunk(zclient_sync, 0, MPLS_LABEL_BASE_ANY, CHUNK_SIZE, &start, &end); if (ret < 0) { log_warnx("Error getting label chunk!"); return -1; } on_get_label_chunk_response(start, end); return (0); } static void lde_label_list_init(void) { label_chunk_list = list_new(); label_chunk_list->del = lde_del_label_chunk; /* get first chunk */ while (lde_get_label_chunk () != 0) { log_warnx("Error getting first label chunk!"); sleep(1); } } static void on_get_label_chunk_response(uint32_t start, uint32_t end) { struct label_chunk *new_label_chunk; debug_labels("label chunk assign: %u - %u", start, end); new_label_chunk = calloc(1, sizeof(struct label_chunk)); if (!new_label_chunk) { log_warn("Error trying to allocate label chunk %u - %u", start, end); return; } new_label_chunk->start = start; new_label_chunk->end = end; new_label_chunk->used_mask = 0; listnode_add(label_chunk_list, (void *)new_label_chunk); /* let's update current if needed */ if (!current_label_chunk) current_label_chunk = listtail(label_chunk_list); } void lde_free_label(uint32_t label) { struct listnode *node; struct label_chunk *label_chunk; uint64_t pos; for (ALL_LIST_ELEMENTS_RO(label_chunk_list, node, label_chunk)) { if (label <= label_chunk->end && label >= label_chunk->start) { pos = 1ULL << (label - label_chunk->start); label_chunk->used_mask &= ~pos; /* if nobody is using this chunk and it's not current_label_chunk, then free it */ if (!label_chunk->used_mask && (current_label_chunk != node)) { if (lde_release_label_chunk(label_chunk->start, label_chunk->end) != 0) log_warnx("%s: Error releasing label chunk!", __func__); else { listnode_delete(label_chunk_list, label_chunk); lde_del_label_chunk(label_chunk); } } break; } } return; } static uint32_t lde_get_next_label(void) { struct label_chunk *label_chunk; uint32_t i, size; uint64_t pos; uint32_t label = NO_LABEL; while (current_label_chunk) { label_chunk = listgetdata(current_label_chunk); if (!label_chunk) goto end; /* try to get next free label in currently used label chunk */ size = label_chunk->end - label_chunk->start + 1; for (i = 0, pos = 1; i < size; i++, pos <<= 1) { if (!(pos & label_chunk->used_mask)) { label_chunk->used_mask |= pos; label = label_chunk->start + i; goto end; } } current_label_chunk = listnextnode(current_label_chunk); } end: /* we moved till the last chunk, or were not able to find a label, so let's ask for another one */ if (!current_label_chunk || current_label_chunk == listtail(label_chunk_list) || label == NO_LABEL) { if (lde_get_label_chunk() != 0) log_warn("%s: Error getting label chunk!", __func__); } return (label); } frr-7.2.1/ldpd/lde.h0000644000000000000000000002031313610377563011053 00000000000000/* $OpenBSD$ */ /* * Copyright (c) 2013, 2016 Renato Westphal * Copyright (c) 2009 Michele Marchetto * Copyright (c) 2004, 2005 Esben Norby * * Permission to use, copy, modify, and distribute this software for any * purpose with or without fee is hereby granted, provided that the above * copyright notice and this permission notice appear in all copies. * * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. */ #ifndef _LDE_H_ #define _LDE_H_ #include "queue.h" #include "openbsd-tree.h" #include "if.h" enum fec_type { FEC_TYPE_IPV4, FEC_TYPE_IPV6, FEC_TYPE_PWID }; struct fec { RB_ENTRY(fec) entry; enum fec_type type; union { struct { struct in_addr prefix; uint8_t prefixlen; } ipv4; struct { struct in6_addr prefix; uint8_t prefixlen; } ipv6; struct { uint16_t type; uint32_t pwid; struct in_addr lsr_id; } pwid; } u; }; RB_HEAD(fec_tree, fec); RB_PROTOTYPE(fec_tree, fec, entry, fec_compare) /* request entries */ struct lde_req { struct fec fec; uint32_t msg_id; }; /* mapping entries */ struct lde_map { struct fec fec; struct lde_map_head *head; /* fec_node's upstream/downstream */ RB_ENTRY(lde_map) entry; struct lde_nbr *nexthop; struct map map; }; RB_HEAD(lde_map_head, lde_map); RB_PROTOTYPE(lde_map_head, lde_map, entry, lde_map_cmp); /* withdraw entries */ struct lde_wdraw { struct fec fec; uint32_t label; }; /* Addresses belonging to neighbor */ struct lde_addr { TAILQ_ENTRY(lde_addr) entry; int af; union ldpd_addr addr; }; /* just the info LDE needs */ struct lde_nbr { RB_ENTRY(lde_nbr) entry; uint32_t peerid; struct in_addr id; int v4_enabled; /* announce/process v4 msgs */ int v6_enabled; /* announce/process v6 msgs */ int flags; /* capabilities */ struct fec_tree recv_req; struct fec_tree sent_req; struct fec_tree recv_map; struct fec_tree sent_map; struct fec_tree sent_map_pending; struct fec_tree sent_wdraw; TAILQ_HEAD(, lde_addr) addr_list; }; RB_HEAD(nbr_tree, lde_nbr); RB_PROTOTYPE(nbr_tree, lde_nbr, entry, lde_nbr_compare) struct fec_nh { LIST_ENTRY(fec_nh) entry; int af; union ldpd_addr nexthop; ifindex_t ifindex; uint32_t remote_label; uint8_t priority; uint8_t flags; }; #define F_FEC_NH_NEW 0x01 #define F_FEC_NH_CONNECTED 0x02 struct fec_node { struct fec fec; LIST_HEAD(, fec_nh) nexthops; /* fib nexthops */ struct lde_map_head downstream; /* recv mappings */ struct lde_map_head upstream; /* sent mappings */ uint32_t local_label; void *data; /* fec specific data */ }; #define CHUNK_SIZE 64 struct label_chunk { uint32_t start; uint32_t end; uint64_t used_mask; }; #define LDE_GC_INTERVAL 300 extern struct ldpd_conf *ldeconf; extern struct fec_tree ft; extern struct nbr_tree lde_nbrs; extern struct thread *gc_timer; /* lde.c */ void lde(void); void lde_init(struct ldpd_init *); int lde_imsg_compose_parent(int, pid_t, void *, uint16_t); void lde_imsg_compose_parent_sync(int, pid_t, void *, uint16_t); int lde_imsg_compose_ldpe(int, uint32_t, pid_t, void *, uint16_t); int lde_acl_check(char *, int, union ldpd_addr *, uint8_t); uint32_t lde_update_label(struct fec_node *); void lde_free_label(uint32_t label); void lde_send_change_klabel(struct fec_node *, struct fec_nh *); void lde_send_delete_klabel(struct fec_node *, struct fec_nh *); void lde_fec2map(struct fec *, struct map *); void lde_map2fec(struct map *, struct in_addr, struct fec *); void lde_send_labelmapping(struct lde_nbr *, struct fec_node *, int); void lde_send_labelwithdraw(struct lde_nbr *, struct fec_node *, struct map *, struct status_tlv *); void lde_send_labelwithdraw_wcard(struct lde_nbr *, uint32_t); void lde_send_labelwithdraw_twcard_prefix(struct lde_nbr *, uint16_t, uint32_t); void lde_send_labelwithdraw_twcard_pwid(struct lde_nbr *, uint16_t, uint32_t); void lde_send_labelwithdraw_pwid_wcard(struct lde_nbr *, uint16_t, uint32_t); void lde_send_labelrelease(struct lde_nbr *, struct fec_node *, struct map *, uint32_t); void lde_send_notification(struct lde_nbr *, uint32_t, uint32_t, uint16_t); void lde_send_notification_eol_prefix(struct lde_nbr *, int); void lde_send_notification_eol_pwid(struct lde_nbr *, uint16_t); struct lde_nbr *lde_nbr_find_by_lsrid(struct in_addr); struct lde_nbr *lde_nbr_find_by_addr(int, union ldpd_addr *); struct lde_map *lde_map_add(struct lde_nbr *, struct fec_node *, int); void lde_map_del(struct lde_nbr *, struct lde_map *, int); struct fec *lde_map_pending_add(struct lde_nbr *, struct fec_node *); void lde_map_pending_del(struct lde_nbr *, struct fec *); struct lde_req *lde_req_add(struct lde_nbr *, struct fec *, int); void lde_req_del(struct lde_nbr *, struct lde_req *, int); struct lde_wdraw *lde_wdraw_add(struct lde_nbr *, struct fec_node *); void lde_wdraw_del(struct lde_nbr *, struct lde_wdraw *); void lde_change_egress_label(int); struct lde_addr *lde_address_find(struct lde_nbr *, int, union ldpd_addr *); /* lde_lib.c */ void fec_init(struct fec_tree *); struct fec *fec_find(struct fec_tree *, struct fec *); int fec_insert(struct fec_tree *, struct fec *); int fec_remove(struct fec_tree *, struct fec *); void fec_clear(struct fec_tree *, void (*)(void *)); void rt_dump(pid_t); void fec_snap(struct lde_nbr *); void fec_tree_clear(void); struct fec_nh *fec_nh_find(struct fec_node *, int, union ldpd_addr *, ifindex_t, uint8_t); void lde_kernel_insert(struct fec *, int, union ldpd_addr *, ifindex_t, uint8_t, int, void *); void lde_kernel_remove(struct fec *, int, union ldpd_addr *, ifindex_t, uint8_t); void lde_kernel_update(struct fec *); void lde_check_mapping(struct map *, struct lde_nbr *); void lde_check_request(struct map *, struct lde_nbr *); void lde_check_request_wcard(struct map *, struct lde_nbr *); void lde_check_release(struct map *, struct lde_nbr *); void lde_check_release_wcard(struct map *, struct lde_nbr *); void lde_check_withdraw(struct map *, struct lde_nbr *); void lde_check_withdraw_wcard(struct map *, struct lde_nbr *); int lde_wildcard_apply(struct map *, struct fec *, struct lde_map *); int lde_gc_timer(struct thread *); void lde_gc_start_timer(void); void lde_gc_stop_timer(void); /* l2vpn.c */ struct l2vpn *l2vpn_new(const char *); struct l2vpn *l2vpn_find(struct ldpd_conf *, const char *); void l2vpn_del(struct l2vpn *); void l2vpn_init(struct l2vpn *); void l2vpn_exit(struct l2vpn *); struct l2vpn_if *l2vpn_if_new(struct l2vpn *, const char *); struct l2vpn_if *l2vpn_if_find(struct l2vpn *, const char *); void l2vpn_if_update_info(struct l2vpn_if *, struct kif *); void l2vpn_if_update(struct l2vpn_if *); struct l2vpn_pw *l2vpn_pw_new(struct l2vpn *, const char *); struct l2vpn_pw *l2vpn_pw_find(struct l2vpn *, const char *); struct l2vpn_pw *l2vpn_pw_find_active(struct l2vpn *, const char *); struct l2vpn_pw *l2vpn_pw_find_inactive(struct l2vpn *, const char *); void l2vpn_pw_update_info(struct l2vpn_pw *, struct kif *); void l2vpn_pw_init(struct l2vpn_pw *); void l2vpn_pw_exit(struct l2vpn_pw *); void l2vpn_pw_reset(struct l2vpn_pw *); int l2vpn_pw_ok(struct l2vpn_pw *, struct fec_nh *); int l2vpn_pw_negotiate(struct lde_nbr *, struct fec_node *, struct map *); void l2vpn_send_pw_status(struct lde_nbr *, uint32_t, struct fec *); void l2vpn_send_pw_status_wcard(struct lde_nbr *, uint32_t, uint16_t, uint32_t); void l2vpn_recv_pw_status(struct lde_nbr *, struct notify_msg *); void l2vpn_recv_pw_status_wcard(struct lde_nbr *, struct notify_msg *); int l2vpn_pw_status_update(struct zapi_pw_status *); void l2vpn_pw_ctl(pid_t); void l2vpn_binding_ctl(pid_t); #endif /* _LDE_H_ */ frr-7.2.1/ldpd/lde_lib.c0000644000000000000000000005523413610377563011706 00000000000000/* $OpenBSD$ */ /* * Copyright (c) 2013, 2016 Renato Westphal * Copyright (c) 2009 Michele Marchetto * * Permission to use, copy, modify, and distribute this software for any * purpose with or without fee is hereby granted, provided that the above * copyright notice and this permission notice appear in all copies. * * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. */ #include #include "ldpd.h" #include "lde.h" #include "log.h" #include "mpls.h" static __inline int fec_compare(const struct fec *, const struct fec *); static int lde_nbr_is_nexthop(struct fec_node *, struct lde_nbr *); static void fec_free(void *); static struct fec_node *fec_add(struct fec *fec); static struct fec_nh *fec_nh_add(struct fec_node *, int, union ldpd_addr *, ifindex_t, uint8_t); static void fec_nh_del(struct fec_nh *); RB_GENERATE(fec_tree, fec, entry, fec_compare) struct fec_tree ft = RB_INITIALIZER(&ft); struct thread *gc_timer; /* FEC tree functions */ void fec_init(struct fec_tree *fh) { RB_INIT(fec_tree, fh); } static __inline int fec_compare(const struct fec *a, const struct fec *b) { if (a->type < b->type) return (-1); if (a->type > b->type) return (1); switch (a->type) { case FEC_TYPE_IPV4: if (ntohl(a->u.ipv4.prefix.s_addr) < ntohl(b->u.ipv4.prefix.s_addr)) return (-1); if (ntohl(a->u.ipv4.prefix.s_addr) > ntohl(b->u.ipv4.prefix.s_addr)) return (1); if (a->u.ipv4.prefixlen < b->u.ipv4.prefixlen) return (-1); if (a->u.ipv4.prefixlen > b->u.ipv4.prefixlen) return (1); return (0); case FEC_TYPE_IPV6: if (memcmp(&a->u.ipv6.prefix, &b->u.ipv6.prefix, sizeof(struct in6_addr)) < 0) return (-1); if (memcmp(&a->u.ipv6.prefix, &b->u.ipv6.prefix, sizeof(struct in6_addr)) > 0) return (1); if (a->u.ipv6.prefixlen < b->u.ipv6.prefixlen) return (-1); if (a->u.ipv6.prefixlen > b->u.ipv6.prefixlen) return (1); return (0); case FEC_TYPE_PWID: if (a->u.pwid.type < b->u.pwid.type) return (-1); if (a->u.pwid.type > b->u.pwid.type) return (1); if (a->u.pwid.pwid < b->u.pwid.pwid) return (-1); if (a->u.pwid.pwid > b->u.pwid.pwid) return (1); if (ntohl(a->u.pwid.lsr_id.s_addr) < ntohl(b->u.pwid.lsr_id.s_addr)) return (-1); if (ntohl(a->u.pwid.lsr_id.s_addr) > ntohl(b->u.pwid.lsr_id.s_addr)) return (1); return (0); } return (-1); } struct fec * fec_find(struct fec_tree *fh, struct fec *f) { return (RB_FIND(fec_tree, fh, f)); } int fec_insert(struct fec_tree *fh, struct fec *f) { if (RB_INSERT(fec_tree, fh, f) != NULL) return (-1); return (0); } int fec_remove(struct fec_tree *fh, struct fec *f) { if (RB_REMOVE(fec_tree, fh, f) == NULL) { log_warnx("%s failed for %s", __func__, log_fec(f)); return (-1); } return (0); } void fec_clear(struct fec_tree *fh, void (*free_cb)(void *)) { struct fec *f; while (!RB_EMPTY(fec_tree, fh)) { f = RB_ROOT(fec_tree, fh); fec_remove(fh, f); free_cb(f); } } /* routing table functions */ static int lde_nbr_is_nexthop(struct fec_node *fn, struct lde_nbr *ln) { struct fec_nh *fnh; LIST_FOREACH(fnh, &fn->nexthops, entry) if (lde_address_find(ln, fnh->af, &fnh->nexthop)) return (1); return (0); } void rt_dump(pid_t pid) { struct fec *f; struct fec_node *fn; struct lde_map *me; static struct ctl_rt rtctl; RB_FOREACH(f, fec_tree, &ft) { fn = (struct fec_node *)f; if (fn->local_label == NO_LABEL && RB_EMPTY(lde_map_head, &fn->downstream)) continue; memset(&rtctl, 0, sizeof(rtctl)); switch (fn->fec.type) { case FEC_TYPE_IPV4: rtctl.af = AF_INET; rtctl.prefix.v4 = fn->fec.u.ipv4.prefix; rtctl.prefixlen = fn->fec.u.ipv4.prefixlen; break; case FEC_TYPE_IPV6: rtctl.af = AF_INET6; rtctl.prefix.v6 = fn->fec.u.ipv6.prefix; rtctl.prefixlen = fn->fec.u.ipv6.prefixlen; break; default: continue; } rtctl.local_label = fn->local_label; if (RB_EMPTY(lde_map_head, &fn->downstream)) { rtctl.in_use = 0; rtctl.nexthop.s_addr = INADDR_ANY; rtctl.remote_label = NO_LABEL; rtctl.no_downstream = 1; } lde_imsg_compose_ldpe(IMSG_CTL_SHOW_LIB_BEGIN, 0, pid, &rtctl, sizeof(rtctl)); RB_FOREACH(me, lde_map_head, &fn->upstream) { rtctl.nexthop = me->nexthop->id; lde_imsg_compose_ldpe(IMSG_CTL_SHOW_LIB_SENT, 0, pid, &rtctl, sizeof(rtctl)); } RB_FOREACH(me, lde_map_head, &fn->downstream) { rtctl.in_use = lde_nbr_is_nexthop(fn, me->nexthop); rtctl.nexthop = me->nexthop->id; rtctl.remote_label = me->map.label; lde_imsg_compose_ldpe(IMSG_CTL_SHOW_LIB_RCVD, 0, pid, &rtctl, sizeof(rtctl)); } lde_imsg_compose_ldpe(IMSG_CTL_SHOW_LIB_END, 0, pid, &rtctl, sizeof(rtctl)); } } void fec_snap(struct lde_nbr *ln) { struct fec *f; struct fec_node *fn; RB_FOREACH(f, fec_tree, &ft) { fn = (struct fec_node *)f; if (fn->local_label == NO_LABEL) continue; lde_send_labelmapping(ln, fn, 0); } lde_imsg_compose_ldpe(IMSG_MAPPING_ADD_END, ln->peerid, 0, NULL, 0); } static void fec_free(void *arg) { struct fec_node *fn = arg; struct fec_nh *fnh; while ((fnh = LIST_FIRST(&fn->nexthops))) { fec_nh_del(fnh); assert(fnh != LIST_FIRST(&fn->nexthops)); } if (!RB_EMPTY(lde_map_head, &fn->downstream)) log_warnx("%s: fec %s downstream list not empty", __func__, log_fec(&fn->fec)); if (!RB_EMPTY(lde_map_head, &fn->upstream)) log_warnx("%s: fec %s upstream list not empty", __func__, log_fec(&fn->fec)); free(fn); } void fec_tree_clear(void) { fec_clear(&ft, fec_free); } static struct fec_node * fec_add(struct fec *fec) { struct fec_node *fn; fn = calloc(1, sizeof(*fn)); if (fn == NULL) fatal(__func__); fn->fec = *fec; fn->local_label = NO_LABEL; RB_INIT(lde_map_head, &fn->upstream); RB_INIT(lde_map_head, &fn->downstream); LIST_INIT(&fn->nexthops); if (fec_insert(&ft, &fn->fec)) log_warnx("failed to add %s to ft tree", log_fec(&fn->fec)); return (fn); } struct fec_nh * fec_nh_find(struct fec_node *fn, int af, union ldpd_addr *nexthop, ifindex_t ifindex, uint8_t priority) { struct fec_nh *fnh; LIST_FOREACH(fnh, &fn->nexthops, entry) if (fnh->af == af && ldp_addrcmp(af, &fnh->nexthop, nexthop) == 0 && fnh->ifindex == ifindex && fnh->priority == priority) return (fnh); return (NULL); } static struct fec_nh * fec_nh_add(struct fec_node *fn, int af, union ldpd_addr *nexthop, ifindex_t ifindex, uint8_t priority) { struct fec_nh *fnh; fnh = calloc(1, sizeof(*fnh)); if (fnh == NULL) fatal(__func__); fnh->af = af; fnh->nexthop = *nexthop; fnh->ifindex = ifindex; fnh->remote_label = NO_LABEL; fnh->priority = priority; LIST_INSERT_HEAD(&fn->nexthops, fnh, entry); return (fnh); } static void fec_nh_del(struct fec_nh *fnh) { LIST_REMOVE(fnh, entry); free(fnh); } void lde_kernel_insert(struct fec *fec, int af, union ldpd_addr *nexthop, ifindex_t ifindex, uint8_t priority, int connected, void *data) { struct fec_node *fn; struct fec_nh *fnh; fn = (struct fec_node *)fec_find(&ft, fec); if (fn == NULL) fn = fec_add(fec); if (data) fn->data = data; fnh = fec_nh_find(fn, af, nexthop, ifindex, priority); if (fnh == NULL) fnh = fec_nh_add(fn, af, nexthop, ifindex, priority); fnh->flags |= F_FEC_NH_NEW; if (connected) fnh->flags |= F_FEC_NH_CONNECTED; } void lde_kernel_remove(struct fec *fec, int af, union ldpd_addr *nexthop, ifindex_t ifindex, uint8_t priority) { struct fec_node *fn; struct fec_nh *fnh; fn = (struct fec_node *)fec_find(&ft, fec); if (fn == NULL) /* route lost */ return; fnh = fec_nh_find(fn, af, nexthop, ifindex, priority); if (fnh == NULL) /* route lost */ return; lde_send_delete_klabel(fn, fnh); fec_nh_del(fnh); } /* * Whenever a route is changed, zebra advertises its new version without * withdrawing the old one. So, after processing a ZEBRA_REDISTRIBUTE_IPV[46]_ADD * message, we need to check for nexthops that were removed and, for each of * them (if any), withdraw the associated labels from zebra. */ void lde_kernel_update(struct fec *fec) { struct fec_node *fn; struct fec_nh *fnh, *safe; struct lde_nbr *ln; struct lde_map *me; fn = (struct fec_node *)fec_find(&ft, fec); if (fn == NULL) return; LIST_FOREACH_SAFE(fnh, &fn->nexthops, entry, safe) { if (fnh->flags & F_FEC_NH_NEW) fnh->flags &= ~F_FEC_NH_NEW; else { lde_send_delete_klabel(fn, fnh); fec_nh_del(fnh); } } if (LIST_EMPTY(&fn->nexthops)) { RB_FOREACH(ln, nbr_tree, &lde_nbrs) lde_send_labelwithdraw(ln, fn, NULL, NULL); fn->data = NULL; /* * Do not deallocate the local label now, do that only in the * LIB garbage collector. This will prevent ldpd from changing * the input label of some prefixes too often when running on * an unstable network. Also, restart the garbage collector * timer so that labels are deallocated only when the network * is stabilized. */ lde_gc_start_timer(); } else { fn->local_label = lde_update_label(fn); if (fn->local_label != NO_LABEL) /* FEC.1: perform lsr label distribution procedure */ RB_FOREACH(ln, nbr_tree, &lde_nbrs) lde_send_labelmapping(ln, fn, 1); } LIST_FOREACH(fnh, &fn->nexthops, entry) { lde_send_change_klabel(fn, fnh); switch (fn->fec.type) { case FEC_TYPE_IPV4: case FEC_TYPE_IPV6: ln = lde_nbr_find_by_addr(fnh->af, &fnh->nexthop); break; case FEC_TYPE_PWID: ln = lde_nbr_find_by_lsrid(fn->fec.u.pwid.lsr_id); break; default: ln = NULL; break; } if (ln) { /* FEC.2 */ me = (struct lde_map *)fec_find(&ln->recv_map, &fn->fec); if (me) /* FEC.5 */ lde_check_mapping(&me->map, ln); } } } void lde_check_mapping(struct map *map, struct lde_nbr *ln) { struct fec fec; struct fec_node *fn; struct fec_nh *fnh; struct lde_req *lre; struct lde_map *me; struct l2vpn_pw *pw; lde_map2fec(map, ln->id, &fec); switch (fec.type) { case FEC_TYPE_IPV4: if (lde_acl_check(ldeconf->ipv4.acl_label_accept_from, AF_INET, (union ldpd_addr *)&ln->id, 32) != FILTER_PERMIT) return; if (lde_acl_check(ldeconf->ipv4.acl_label_accept_for, AF_INET, (union ldpd_addr *)&fec.u.ipv4.prefix, fec.u.ipv4.prefixlen) != FILTER_PERMIT) return; break; case FEC_TYPE_IPV6: if (lde_acl_check(ldeconf->ipv6.acl_label_accept_from, AF_INET, (union ldpd_addr *)&ln->id, 32) != FILTER_PERMIT) return; if (lde_acl_check(ldeconf->ipv6.acl_label_accept_for, AF_INET6, (union ldpd_addr *)&fec.u.ipv6.prefix, fec.u.ipv6.prefixlen) != FILTER_PERMIT) return; break; default: break; } fn = (struct fec_node *)fec_find(&ft, &fec); if (fn == NULL) fn = fec_add(&fec); /* LMp.1: first check if we have a pending request running */ lre = (struct lde_req *)fec_find(&ln->sent_req, &fn->fec); if (lre) /* LMp.2: delete record of outstanding label request */ lde_req_del(ln, lre, 1); /* RFC 4447 control word and status tlv negotiation */ if (map->type == MAP_TYPE_PWID && l2vpn_pw_negotiate(ln, fn, map)) return; /* * LMp.3 - LMp.8: loop detection - unnecessary for frame-mode * mpls networks. */ /* LMp.9 */ me = (struct lde_map *)fec_find(&ln->recv_map, &fn->fec); if (me) { /* LMp.10 */ if (me->map.label != map->label && lre == NULL) { /* LMp.10a */ lde_send_labelrelease(ln, fn, NULL, me->map.label); /* * Can not use lde_nbr_find_by_addr() because there's * the possibility of multipath. */ LIST_FOREACH(fnh, &fn->nexthops, entry) { if (lde_address_find(ln, fnh->af, &fnh->nexthop) == NULL) continue; lde_send_delete_klabel(fn, fnh); fnh->remote_label = NO_LABEL; } } } /* * LMp.11 - 12: consider multiple nexthops in order to * support multipath */ LIST_FOREACH(fnh, &fn->nexthops, entry) { /* LMp.15: install FEC in FIB */ switch (fec.type) { case FEC_TYPE_IPV4: case FEC_TYPE_IPV6: if (!lde_address_find(ln, fnh->af, &fnh->nexthop)) continue; fnh->remote_label = map->label; lde_send_change_klabel(fn, fnh); break; case FEC_TYPE_PWID: pw = (struct l2vpn_pw *) fn->data; if (pw == NULL) continue; pw->remote_group = map->fec.pwid.group_id; if (map->flags & F_MAP_PW_IFMTU) pw->remote_mtu = map->fec.pwid.ifmtu; if (map->flags & F_MAP_PW_STATUS) pw->remote_status = map->pw_status; else pw->remote_status = PW_FORWARDING; fnh->remote_label = map->label; if (l2vpn_pw_ok(pw, fnh)) lde_send_change_klabel(fn, fnh); break; default: break; } } /* LMp.13 & LMp.16: Record the mapping from this peer */ if (me == NULL) me = lde_map_add(ln, fn, 0); me->map = *map; /* * LMp.17 - LMp.27 are unnecessary since we don't need to implement * loop detection. LMp.28 - LMp.30 are unnecessary because we are * merging capable. */ } void lde_check_request(struct map *map, struct lde_nbr *ln) { struct fec fec; struct lde_req *lre; struct fec_node *fn; struct fec_nh *fnh; /* wildcard label request */ if (map->type == MAP_TYPE_TYPED_WCARD) { lde_check_request_wcard(map, ln); return; } /* LRq.1: skip loop detection (not necessary) */ /* LRq.2: is there a next hop for fec? */ lde_map2fec(map, ln->id, &fec); fn = (struct fec_node *)fec_find(&ft, &fec); if (fn == NULL || LIST_EMPTY(&fn->nexthops)) { /* LRq.5: send No Route notification */ lde_send_notification(ln, S_NO_ROUTE, map->msg_id, htons(MSG_TYPE_LABELREQUEST)); return; } /* LRq.3: is MsgSource the next hop? */ LIST_FOREACH(fnh, &fn->nexthops, entry) { switch (fec.type) { case FEC_TYPE_IPV4: case FEC_TYPE_IPV6: if (!lde_address_find(ln, fnh->af, &fnh->nexthop)) continue; /* LRq.4: send Loop Detected notification */ lde_send_notification(ln, S_LOOP_DETECTED, map->msg_id, htons(MSG_TYPE_LABELREQUEST)); return; default: break; } } /* LRq.6: first check if we have a pending request running */ lre = (struct lde_req *)fec_find(&ln->recv_req, &fn->fec); if (lre != NULL) /* LRq.7: duplicate request */ return; /* LRq.8: record label request */ lre = lde_req_add(ln, &fn->fec, 0); if (lre != NULL) lre->msg_id = ntohl(map->msg_id); /* LRq.9: perform LSR label distribution */ lde_send_labelmapping(ln, fn, 1); /* * LRq.10: do nothing (Request Never) since we use liberal * label retention. * LRq.11 - 12 are unnecessary since we are merging capable. */ } void lde_check_request_wcard(struct map *map, struct lde_nbr *ln) { struct fec *f; struct fec_node *fn; struct lde_req *lre; RB_FOREACH(f, fec_tree, &ft) { fn = (struct fec_node *)f; /* only a typed wildcard is possible here */ if (lde_wildcard_apply(map, &fn->fec, NULL) == 0) continue; /* LRq.2: is there a next hop for fec? */ if (LIST_EMPTY(&fn->nexthops)) continue; /* LRq.6: first check if we have a pending request running */ lre = (struct lde_req *)fec_find(&ln->recv_req, &fn->fec); if (lre != NULL) /* LRq.7: duplicate request */ continue; /* LRq.8: record label request */ lre = lde_req_add(ln, &fn->fec, 0); if (lre != NULL) lre->msg_id = ntohl(map->msg_id); /* LRq.9: perform LSR label distribution */ lde_send_labelmapping(ln, fn, 1); } } void lde_check_release(struct map *map, struct lde_nbr *ln) { struct fec fec; struct fec_node *fn; struct lde_wdraw *lw; struct lde_map *me; struct fec *pending_map; /* wildcard label release */ if (map->type == MAP_TYPE_WILDCARD || map->type == MAP_TYPE_TYPED_WCARD || (map->type == MAP_TYPE_PWID && !(map->flags & F_MAP_PW_ID))) { lde_check_release_wcard(map, ln); return; } lde_map2fec(map, ln->id, &fec); fn = (struct fec_node *)fec_find(&ft, &fec); /* LRl.1: does FEC match a known FEC? */ if (fn == NULL) return; /* LRl.6: check sent map list and remove it if available */ me = (struct lde_map *)fec_find(&ln->sent_map, &fn->fec); if (me && (map->label == NO_LABEL || map->label == me->map.label)) lde_map_del(ln, me, 1); /* LRl.3: first check if we have a pending withdraw running */ lw = (struct lde_wdraw *)fec_find(&ln->sent_wdraw, &fn->fec); if (lw && (map->label == NO_LABEL || map->label == lw->label)) { /* LRl.4: delete record of outstanding label withdraw */ lde_wdraw_del(ln, lw); /* send pending label mapping if any */ pending_map = fec_find(&ln->sent_map_pending, &fn->fec); if (pending_map) { lde_send_labelmapping(ln, fn, 1); lde_map_pending_del(ln, pending_map); } } /* * LRl.11 - 13 are unnecessary since we remove the label from * forwarding/switching as soon as the FEC is unreachable. */ } void lde_check_release_wcard(struct map *map, struct lde_nbr *ln) { struct fec *f; struct fec_node *fn; struct lde_wdraw *lw; struct lde_map *me; struct fec *pending_map; RB_FOREACH(f, fec_tree, &ft) { fn = (struct fec_node *)f; me = (struct lde_map *)fec_find(&ln->sent_map, &fn->fec); /* LRl.1: does FEC match a known FEC? */ if (lde_wildcard_apply(map, &fn->fec, me) == 0) continue; /* LRl.6: check sent map list and remove it if available */ if (me && (map->label == NO_LABEL || map->label == me->map.label)) lde_map_del(ln, me, 1); /* LRl.3: first check if we have a pending withdraw running */ lw = (struct lde_wdraw *)fec_find(&ln->sent_wdraw, &fn->fec); if (lw && (map->label == NO_LABEL || map->label == lw->label)) { /* LRl.4: delete record of outstanding lbl withdraw */ lde_wdraw_del(ln, lw); /* send pending label mapping if any */ pending_map = fec_find(&ln->sent_map_pending, &fn->fec); if (pending_map) { lde_send_labelmapping(ln, fn, 1); lde_map_pending_del(ln, pending_map); } } /* * LRl.11 - 13 are unnecessary since we remove the label from * forwarding/switching as soon as the FEC is unreachable. */ } } void lde_check_withdraw(struct map *map, struct lde_nbr *ln) { struct fec fec; struct fec_node *fn; struct fec_nh *fnh; struct lde_map *me; struct l2vpn_pw *pw; /* wildcard label withdraw */ if (map->type == MAP_TYPE_WILDCARD || map->type == MAP_TYPE_TYPED_WCARD || (map->type == MAP_TYPE_PWID && !(map->flags & F_MAP_PW_ID))) { lde_check_withdraw_wcard(map, ln); return; } lde_map2fec(map, ln->id, &fec); fn = (struct fec_node *)fec_find(&ft, &fec); if (fn == NULL) fn = fec_add(&fec); /* LWd.1: remove label from forwarding/switching use */ LIST_FOREACH(fnh, &fn->nexthops, entry) { switch (fec.type) { case FEC_TYPE_IPV4: case FEC_TYPE_IPV6: if (!lde_address_find(ln, fnh->af, &fnh->nexthop)) continue; break; case FEC_TYPE_PWID: pw = (struct l2vpn_pw *) fn->data; if (pw == NULL) continue; pw->remote_status = PW_NOT_FORWARDING; break; default: break; } if (map->label != NO_LABEL && map->label != fnh->remote_label) continue; lde_send_delete_klabel(fn, fnh); fnh->remote_label = NO_LABEL; } /* LWd.2: send label release */ lde_send_labelrelease(ln, fn, NULL, map->label); /* LWd.3: check previously received label mapping */ me = (struct lde_map *)fec_find(&ln->recv_map, &fn->fec); if (me && (map->label == NO_LABEL || map->label == me->map.label)) /* LWd.4: remove record of previously received lbl mapping */ lde_map_del(ln, me, 0); } void lde_check_withdraw_wcard(struct map *map, struct lde_nbr *ln) { struct fec *f; struct fec_node *fn; struct fec_nh *fnh; struct lde_map *me; struct l2vpn_pw *pw; /* LWd.2: send label release */ lde_send_labelrelease(ln, NULL, map, map->label); RB_FOREACH(f, fec_tree, &ft) { fn = (struct fec_node *)f; me = (struct lde_map *)fec_find(&ln->recv_map, &fn->fec); if (lde_wildcard_apply(map, &fn->fec, me) == 0) continue; /* LWd.1: remove label from forwarding/switching use */ LIST_FOREACH(fnh, &fn->nexthops, entry) { switch (f->type) { case FEC_TYPE_IPV4: case FEC_TYPE_IPV6: if (!lde_address_find(ln, fnh->af, &fnh->nexthop)) continue; break; case FEC_TYPE_PWID: if (f->u.pwid.lsr_id.s_addr != ln->id.s_addr) continue; pw = (struct l2vpn_pw *) fn->data; if (pw) pw->remote_status = PW_NOT_FORWARDING; break; default: break; } if (map->label != NO_LABEL && map->label != fnh->remote_label) continue; lde_send_delete_klabel(fn, fnh); fnh->remote_label = NO_LABEL; } /* LWd.3: check previously received label mapping */ if (me && (map->label == NO_LABEL || map->label == me->map.label)) /* * LWd.4: remove record of previously received * label mapping */ lde_map_del(ln, me, 0); } } int lde_wildcard_apply(struct map *wcard, struct fec *fec, struct lde_map *me) { switch (wcard->type) { case MAP_TYPE_WILDCARD: /* full wildcard */ return (1); case MAP_TYPE_TYPED_WCARD: switch (wcard->fec.twcard.type) { case MAP_TYPE_PREFIX: if (wcard->fec.twcard.u.prefix_af == AF_INET && fec->type != FEC_TYPE_IPV4) return (0); if (wcard->fec.twcard.u.prefix_af == AF_INET6 && fec->type != FEC_TYPE_IPV6) return (0); return (1); case MAP_TYPE_PWID: if (fec->type != FEC_TYPE_PWID) return (0); if (wcard->fec.twcard.u.pw_type != PW_TYPE_WILDCARD && wcard->fec.twcard.u.pw_type != fec->u.pwid.type) return (0); return (1); default: fatalx("lde_wildcard_apply: unexpected fec type"); } break; case MAP_TYPE_PWID: /* RFC4447 pw-id group wildcard */ if (fec->type != FEC_TYPE_PWID) return (0); if (fec->u.pwid.type != wcard->fec.pwid.type) return (0); if (me == NULL || (me->map.fec.pwid.group_id != wcard->fec.pwid.group_id)) return (0); return (1); default: fatalx("lde_wildcard_apply: unexpected fec type"); } } /* gabage collector timer: timer to remove dead entries from the LIB */ /* ARGSUSED */ int lde_gc_timer(struct thread *thread) { struct fec *fec, *safe; struct fec_node *fn; int count = 0; RB_FOREACH_SAFE(fec, fec_tree, &ft, safe) { fn = (struct fec_node *) fec; if (!LIST_EMPTY(&fn->nexthops) || !RB_EMPTY(lde_map_head, &fn->downstream) || !RB_EMPTY(lde_map_head, &fn->upstream)) continue; if (fn->local_label != NO_LABEL) lde_free_label(fn->local_label); fec_remove(&ft, &fn->fec); free(fn); count++; } if (count > 0) log_debug("%s: %u entries removed", __func__, count); lde_gc_start_timer(); return (0); } void lde_gc_start_timer(void) { THREAD_TIMER_OFF(gc_timer); gc_timer = NULL; thread_add_timer(master, lde_gc_timer, NULL, LDE_GC_INTERVAL, &gc_timer); } void lde_gc_stop_timer(void) { THREAD_TIMER_OFF(gc_timer); } frr-7.2.1/ldpd/ldp.h0000644000000000000000000002017613610377563011075 00000000000000/* $OpenBSD$ */ /* * Copyright (c) 2013, 2016 Renato Westphal * Copyright (c) 2009 Michele Marchetto * Copyright (c) 2004, 2005, 2008 Esben Norby * * Permission to use, copy, modify, and distribute this software for any * purpose with or without fee is hereby granted, provided that the above * copyright notice and this permission notice appear in all copies. * * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. */ /* LDP protocol definitions */ #ifndef _LDP_H_ #define _LDP_H_ /* misc */ #define LDP_VERSION 1 #define LDP_PORT 646 #define LDP_MAX_LEN 4096 /* All Routers on this Subnet group multicast addresses */ #define AllRouters_v4 "224.0.0.2" #define AllRouters_v6 "ff02::2" #define LINK_DFLT_HOLDTIME 15 #define TARGETED_DFLT_HOLDTIME 45 #define MIN_HOLDTIME 3 #define MAX_HOLDTIME 0xffff #define INFINITE_HOLDTIME 0xffff #define DEFAULT_KEEPALIVE 180 #define MIN_KEEPALIVE 3 #define MAX_KEEPALIVE 0xffff #define KEEPALIVE_PER_PERIOD 3 #define INIT_FSM_TIMEOUT 15 #define DEFAULT_HELLO_INTERVAL 5 #define MIN_HELLO_INTERVAL 1 #define MAX_HELLO_INTERVAL 0xffff #define INIT_DELAY_TMR 15 #define MAX_DELAY_TMR 120 #define MIN_PWID_ID 1 #define MAX_PWID_ID 0xffffffff #define DEFAULT_L2VPN_MTU 1500 #define MIN_L2VPN_MTU 512 #define MAX_L2VPN_MTU 0xffff /* LDP message types */ #define MSG_TYPE_NOTIFICATION 0x0001 #define MSG_TYPE_HELLO 0x0100 #define MSG_TYPE_INIT 0x0200 #define MSG_TYPE_KEEPALIVE 0x0201 #define MSG_TYPE_CAPABILITY 0x0202 /* RFC 5561 */ #define MSG_TYPE_ADDR 0x0300 #define MSG_TYPE_ADDRWITHDRAW 0x0301 #define MSG_TYPE_LABELMAPPING 0x0400 #define MSG_TYPE_LABELREQUEST 0x0401 #define MSG_TYPE_LABELWITHDRAW 0x0402 #define MSG_TYPE_LABELRELEASE 0x0403 #define MSG_TYPE_LABELABORTREQ 0x0404 /* LDP TLV types */ #define TLV_TYPE_FEC 0x0100 #define TLV_TYPE_ADDRLIST 0x0101 #define TLV_TYPE_HOPCOUNT 0x0103 #define TLV_TYPE_PATHVECTOR 0x0104 #define TLV_TYPE_GENERICLABEL 0x0200 #define TLV_TYPE_ATMLABEL 0x0201 #define TLV_TYPE_FRLABEL 0x0202 #define TLV_TYPE_STATUS 0x0300 #define TLV_TYPE_EXTSTATUS 0x0301 #define TLV_TYPE_RETURNEDPDU 0x0302 #define TLV_TYPE_RETURNEDMSG 0x0303 #define TLV_TYPE_COMMONHELLO 0x0400 #define TLV_TYPE_IPV4TRANSADDR 0x0401 #define TLV_TYPE_CONFIG 0x0402 #define TLV_TYPE_IPV6TRANSADDR 0x0403 #define TLV_TYPE_COMMONSESSION 0x0500 #define TLV_TYPE_ATMSESSIONPAR 0x0501 #define TLV_TYPE_FRSESSION 0x0502 #define TLV_TYPE_LABELREQUEST 0x0600 /* RFC 4447 */ #define TLV_TYPE_MAC_LIST 0x8404 #define TLV_TYPE_PW_STATUS 0x896A #define TLV_TYPE_PW_IF_PARAM 0x096B #define TLV_TYPE_PW_GROUP_ID 0x096C /* RFC 5561 */ #define TLV_TYPE_RETURNED_TLVS 0x8304 #define TLV_TYPE_DYNAMIC_CAP 0x8506 /* RFC 5918 */ #define TLV_TYPE_TWCARD_CAP 0x850B /* RFC 5919 */ #define TLV_TYPE_UNOTIF_CAP 0x8603 /* RFC 7552 */ #define TLV_TYPE_DUALSTACK 0x8701 /* LDP header */ struct ldp_hdr { uint16_t version; uint16_t length; uint32_t lsr_id; uint16_t lspace_id; } __attribute__ ((packed)); #define LDP_HDR_SIZE 10 /* actual size of the LDP header */ #define LDP_HDR_PDU_LEN 6 /* minimum "PDU Length" */ #define LDP_HDR_DEAD_LEN 4 /* TLV record */ struct tlv { uint16_t type; uint16_t length; }; #define TLV_HDR_SIZE 4 struct ldp_msg { uint16_t type; uint16_t length; uint32_t id; /* Mandatory Parameters */ /* Optional Parameters */ } __attribute__ ((packed)); #define LDP_MSG_SIZE 8 /* minimum size of LDP message */ #define LDP_MSG_LEN 4 /* minimum "Message Length" */ #define LDP_MSG_DEAD_LEN 4 #define UNKNOWN_FLAG 0x8000 #define FORWARD_FLAG 0xc000 struct hello_prms_tlv { uint16_t type; uint16_t length; uint16_t holdtime; uint16_t flags; }; #define F_HELLO_TARGETED 0x8000 #define F_HELLO_REQ_TARG 0x4000 #define F_HELLO_GTSM 0x2000 struct hello_prms_opt4_tlv { uint16_t type; uint16_t length; uint32_t value; }; struct hello_prms_opt16_tlv { uint16_t type; uint16_t length; uint8_t value[16]; }; #define DUAL_STACK_LDPOV4 4 #define DUAL_STACK_LDPOV6 6 #define F_HELLO_TLV_RCVD_ADDR 0x01 #define F_HELLO_TLV_RCVD_CONF 0x02 #define F_HELLO_TLV_RCVD_DS 0x04 #define S_SUCCESS 0x00000000 #define S_BAD_LDP_ID 0x80000001 #define S_BAD_PROTO_VER 0x80000002 #define S_BAD_PDU_LEN 0x80000003 #define S_UNKNOWN_MSG 0x00000004 #define S_BAD_MSG_LEN 0x80000005 #define S_UNKNOWN_TLV 0x00000006 #define S_BAD_TLV_LEN 0x80000007 #define S_BAD_TLV_VAL 0x80000008 #define S_HOLDTIME_EXP 0x80000009 #define S_SHUTDOWN 0x8000000A #define S_LOOP_DETECTED 0x0000000B #define S_UNKNOWN_FEC 0x0000000C #define S_NO_ROUTE 0x0000000D #define S_NO_LABEL_RES 0x0000000E #define S_AVAILABLE 0x0000000F #define S_NO_HELLO 0x80000010 #define S_PARM_ADV_MODE 0x80000011 #define S_MAX_PDU_LEN 0x80000012 #define S_PARM_L_RANGE 0x80000013 #define S_KEEPALIVE_TMR 0x80000014 #define S_LAB_REQ_ABRT 0x00000015 #define S_MISS_MSG 0x00000016 #define S_UNSUP_ADDR 0x00000017 #define S_KEEPALIVE_BAD 0x80000018 #define S_INTERN_ERR 0x80000019 /* RFC 4447 */ #define S_ILLEGAL_CBIT 0x00000024 #define S_WRONG_CBIT 0x00000025 #define S_INCPT_BITRATE 0x00000026 #define S_CEP_MISCONF 0x00000027 #define S_PW_STATUS 0x00000028 #define S_UNASSIGN_TAI 0x00000029 #define S_MISCONF_ERR 0x0000002A #define S_WITHDRAW_MTHD 0x0000002B /* RFC 5561 */ #define S_UNSSUPORTDCAP 0x0000002E /* RFC 5919 */ #define S_ENDOFLIB 0x0000002F /* RFC 7552 */ #define S_TRANS_MISMTCH 0x80000032 #define S_DS_NONCMPLNCE 0x80000033 struct sess_prms_tlv { uint16_t type; uint16_t length; uint16_t proto_version; uint16_t keepalive_time; uint8_t reserved; uint8_t pvlim; uint16_t max_pdu_len; uint32_t lsr_id; uint16_t lspace_id; } __attribute__ ((packed)); #define SESS_PRMS_SIZE 18 #define SESS_PRMS_LEN 14 struct status_tlv { uint16_t type; uint16_t length; uint32_t status_code; uint32_t msg_id; uint16_t msg_type; } __attribute__ ((packed)); #define STATUS_SIZE 14 #define STATUS_TLV_LEN 10 #define STATUS_FATAL 0x80000000 struct capability_tlv { uint16_t type; uint16_t length; uint8_t reserved; }; #define STATE_BIT 0x80 #define F_CAP_TLV_RCVD_DYNAMIC 0x01 #define F_CAP_TLV_RCVD_TWCARD 0x02 #define F_CAP_TLV_RCVD_UNOTIF 0x04 #define CAP_TLV_DYNAMIC_SIZE 5 #define CAP_TLV_DYNAMIC_LEN 1 #define CAP_TLV_TWCARD_SIZE 5 #define CAP_TLV_TWCARD_LEN 1 #define CAP_TLV_UNOTIF_SIZE 5 #define CAP_TLV_UNOTIF_LEN 1 #define AF_IPV4 0x1 #define AF_IPV6 0x2 struct address_list_tlv { uint16_t type; uint16_t length; uint16_t family; /* address entries */ } __attribute__ ((packed)); #define ADDR_LIST_SIZE 6 #define FEC_ELM_WCARD_LEN 1 #define FEC_ELM_PREFIX_MIN_LEN 4 #define FEC_PWID_ELM_MIN_LEN 8 #define FEC_PWID_SIZE 4 #define FEC_ELM_TWCARD_MIN_LEN 3 #define MAP_TYPE_WILDCARD 0x01 #define MAP_TYPE_PREFIX 0x02 #define MAP_TYPE_TYPED_WCARD 0x05 #define MAP_TYPE_PWID 0x80 #define MAP_TYPE_GENPWID 0x81 #define CONTROL_WORD_FLAG 0x8000 #define DEFAULT_PW_TYPE PW_TYPE_ETHERNET #define PW_TWCARD_RESERVED_BIT 0x8000 /* RFC 4447 Sub-TLV record */ struct subtlv { uint8_t type; uint8_t length; }; #define SUBTLV_HDR_SIZE 2 #define SUBTLV_IFMTU 0x01 #define SUBTLV_VLANID 0x06 #define FEC_SUBTLV_IFMTU_SIZE 4 #define FEC_SUBTLV_VLANID_SIZE 4 struct label_tlv { uint16_t type; uint16_t length; uint32_t label; }; #define LABEL_TLV_SIZE 8 #define LABEL_TLV_LEN 4 struct reqid_tlv { uint16_t type; uint16_t length; uint32_t reqid; }; #define REQID_TLV_SIZE 8 #define REQID_TLV_LEN 4 struct pw_status_tlv { uint16_t type; uint16_t length; uint32_t value; }; #define PW_STATUS_TLV_SIZE 8 #define PW_STATUS_TLV_LEN 4 #define PW_FORWARDING 0 #define PW_NOT_FORWARDING (1 << 0) #define PW_LOCAL_RX_FAULT (1 << 1) #define PW_LOCAL_TX_FAULT (1 << 2) #define PW_PSN_RX_FAULT (1 << 3) #define PW_PSN_TX_FAULT (1 << 4) #define NO_LABEL UINT32_MAX #endif /* !_LDP_H_ */ frr-7.2.1/ldpd/ldp_debug.c0000644000000000000000000001230513610377563012231 00000000000000/* * Copyright (C) 2016 by Open Source Routing. * * This program is free software; you can redistribute it and/or modify * it under the terms of the 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; see the file COPYING; if not, write to the * Free Software Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, * MA 02110-1301 USA */ #include #include "command.h" #include "vty.h" #include "ldpd.h" #include "ldp_debug.h" #include "ldp_vty.h" struct ldp_debug conf_ldp_debug; struct ldp_debug ldp_debug; /* Debug node. */ struct cmd_node ldp_debug_node = { DEBUG_NODE, "", 1 }; int ldp_vty_debug(struct vty *vty, const char *negate, const char *type_str, const char *dir_str, const char *all) { if (type_str == NULL) return (CMD_WARNING_CONFIG_FAILED); if (strcmp(type_str, "discovery") == 0) { if (dir_str == NULL) return (CMD_WARNING_CONFIG_FAILED); if (dir_str[0] == 'r') { if (negate) DEBUG_OFF(hello, LDP_DEBUG_HELLO_RECV); else DEBUG_ON(hello, LDP_DEBUG_HELLO_RECV); } else { if (negate) DEBUG_OFF(hello, LDP_DEBUG_HELLO_SEND); else DEBUG_ON(hello, LDP_DEBUG_HELLO_SEND); } } else if (strcmp(type_str, "errors") == 0) { if (negate) DEBUG_OFF(errors, LDP_DEBUG_ERRORS); else DEBUG_ON(errors, LDP_DEBUG_ERRORS); } else if (strcmp(type_str, "event") == 0) { if (negate) DEBUG_OFF(event, LDP_DEBUG_EVENT); else DEBUG_ON(event, LDP_DEBUG_EVENT); } else if (strcmp(type_str, "labels") == 0) { if (negate) DEBUG_OFF(labels, LDP_DEBUG_LABELS); else DEBUG_ON(labels, LDP_DEBUG_LABELS); } else if (strcmp(type_str, "messages") == 0) { if (dir_str == NULL) return (CMD_WARNING_CONFIG_FAILED); if (dir_str[0] == 'r') { if (negate) { DEBUG_OFF(msg, LDP_DEBUG_MSG_RECV); DEBUG_OFF(msg, LDP_DEBUG_MSG_RECV_ALL); } else { DEBUG_ON(msg, LDP_DEBUG_MSG_RECV); if (all) DEBUG_ON(msg, LDP_DEBUG_MSG_RECV_ALL); } } else { if (negate) { DEBUG_OFF(msg, LDP_DEBUG_MSG_SEND); DEBUG_OFF(msg, LDP_DEBUG_MSG_SEND_ALL); } else { DEBUG_ON(msg, LDP_DEBUG_MSG_SEND); if (all) DEBUG_ON(msg, LDP_DEBUG_MSG_SEND_ALL); } } } else if (strcmp(type_str, "zebra") == 0) { if (negate) DEBUG_OFF(zebra, LDP_DEBUG_ZEBRA); else DEBUG_ON(zebra, LDP_DEBUG_ZEBRA); } main_imsg_compose_both(IMSG_DEBUG_UPDATE, &ldp_debug, sizeof(ldp_debug)); return (CMD_SUCCESS); } int ldp_vty_show_debugging(struct vty *vty) { vty_out (vty, "LDP debugging status:\n"); if (LDP_DEBUG(hello, LDP_DEBUG_HELLO_RECV)) vty_out (vty," LDP discovery debugging is on (inbound)\n"); if (LDP_DEBUG(hello, LDP_DEBUG_HELLO_SEND)) vty_out (vty," LDP discovery debugging is on (outbound)\n"); if (LDP_DEBUG(errors, LDP_DEBUG_ERRORS)) vty_out (vty, " LDP errors debugging is on\n"); if (LDP_DEBUG(event, LDP_DEBUG_EVENT)) vty_out (vty, " LDP events debugging is on\n"); if (LDP_DEBUG(labels, LDP_DEBUG_LABELS)) vty_out (vty, " LDP labels debugging is on\n"); if (LDP_DEBUG(msg, LDP_DEBUG_MSG_RECV_ALL)) vty_out (vty, " LDP detailed messages debugging is on (inbound)\n"); else if (LDP_DEBUG(msg, LDP_DEBUG_MSG_RECV)) vty_out (vty," LDP messages debugging is on (inbound)\n"); if (LDP_DEBUG(msg, LDP_DEBUG_MSG_SEND_ALL)) vty_out (vty, " LDP detailed messages debugging is on (outbound)\n"); else if (LDP_DEBUG(msg, LDP_DEBUG_MSG_SEND)) vty_out (vty," LDP messages debugging is on (outbound)\n"); if (LDP_DEBUG(zebra, LDP_DEBUG_ZEBRA)) vty_out (vty, " LDP zebra debugging is on\n"); vty_out (vty, "\n"); return (CMD_SUCCESS); } int ldp_debug_config_write(struct vty *vty) { int write = 0; if (CONF_LDP_DEBUG(hello, LDP_DEBUG_HELLO_RECV)) { vty_out (vty,"debug mpls ldp discovery hello recv\n"); write = 1; } if (CONF_LDP_DEBUG(hello, LDP_DEBUG_HELLO_SEND)) { vty_out (vty,"debug mpls ldp discovery hello sent\n"); write = 1; } if (CONF_LDP_DEBUG(errors, LDP_DEBUG_ERRORS)) { vty_out (vty, "debug mpls ldp errors\n"); write = 1; } if (CONF_LDP_DEBUG(event, LDP_DEBUG_EVENT)) { vty_out (vty, "debug mpls ldp event\n"); write = 1; } if (CONF_LDP_DEBUG(labels, LDP_DEBUG_LABELS)) { vty_out (vty, "debug mpls ldp labels\n"); write = 1; } if (CONF_LDP_DEBUG(msg, LDP_DEBUG_MSG_RECV_ALL)) { vty_out (vty, "debug mpls ldp messages recv all\n"); write = 1; } else if (CONF_LDP_DEBUG(msg, LDP_DEBUG_MSG_RECV)) { vty_out (vty, "debug mpls ldp messages recv\n"); write = 1; } if (CONF_LDP_DEBUG(msg, LDP_DEBUG_MSG_SEND_ALL)) { vty_out (vty, "debug mpls ldp messages sent all\n"); write = 1; } else if (CONF_LDP_DEBUG(msg, LDP_DEBUG_MSG_SEND)) { vty_out (vty, "debug mpls ldp messages sent\n"); write = 1; } if (CONF_LDP_DEBUG(zebra, LDP_DEBUG_ZEBRA)) { vty_out (vty, "debug mpls ldp zebra\n"); write = 1; } return (write); } frr-7.2.1/ldpd/ldp_debug.h0000644000000000000000000000760713610377563012247 00000000000000/* * Copyright (C) 2016 by Open Source Routing. * * This program is free software; you can redistribute it and/or modify * it under the terms of the 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; see the file COPYING; if not, write to the * Free Software Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, * MA 02110-1301 USA */ #ifndef _LDP_DEBUG_H_ #define _LDP_DEBUG_H_ struct ldp_debug { int hello; #define LDP_DEBUG_HELLO_RECV 0x01 #define LDP_DEBUG_HELLO_SEND 0x02 int errors; #define LDP_DEBUG_ERRORS 0x01 int event; #define LDP_DEBUG_EVENT 0x01 int labels; #define LDP_DEBUG_LABELS 0x01 int msg; #define LDP_DEBUG_MSG_RECV 0x01 #define LDP_DEBUG_MSG_RECV_ALL 0x02 #define LDP_DEBUG_MSG_SEND 0x04 #define LDP_DEBUG_MSG_SEND_ALL 0x08 int zebra; #define LDP_DEBUG_ZEBRA 0x01 }; extern struct ldp_debug conf_ldp_debug; extern struct ldp_debug ldp_debug; #define CONF_DEBUG_ON(a, b) (conf_ldp_debug.a |= (b)) #define CONF_DEBUG_OFF(a, b) (conf_ldp_debug.a &= ~(b)) #define TERM_DEBUG_ON(a, b) (ldp_debug.a |= (b)) #define TERM_DEBUG_OFF(a, b) (ldp_debug.a &= ~(b)) #define DEBUG_ON(a, b) \ do { \ if (vty->node == CONFIG_NODE) { \ CONF_DEBUG_ON(a, b); \ TERM_DEBUG_ON(a, b); \ } else \ TERM_DEBUG_ON(a, b); \ } while (0) #define DEBUG_OFF(a, b) \ do { \ CONF_DEBUG_OFF(a, b); \ TERM_DEBUG_OFF(a, b); \ } while (0) #define LDP_DEBUG(a, b) (ldp_debug.a & b) #define CONF_LDP_DEBUG(a, b) (conf_ldp_debug.a & b) #define debug_hello_recv(emsg, ...) \ do { \ if (LDP_DEBUG(hello, LDP_DEBUG_HELLO_RECV)) \ log_debug("discovery[recv]: " emsg, __VA_ARGS__); \ } while (0) #define debug_hello_send(emsg, ...) \ do { \ if (LDP_DEBUG(hello, LDP_DEBUG_HELLO_SEND)) \ log_debug("discovery[send]: " emsg, __VA_ARGS__); \ } while (0) #define debug_err(emsg, ...) \ do { \ if (LDP_DEBUG(errors, LDP_DEBUG_ERRORS)) \ log_debug("error: " emsg, __VA_ARGS__); \ } while (0) #define debug_evt(emsg, ...) \ do { \ if (LDP_DEBUG(event, LDP_DEBUG_EVENT)) \ log_debug("event: " emsg, __VA_ARGS__); \ } while (0) #define debug_labels(emsg, ...) \ do { \ if (LDP_DEBUG(labels, LDP_DEBUG_LABELS)) \ log_debug("labels: " emsg, __VA_ARGS__); \ } while (0) #define debug_msg_recv(emsg, ...) \ do { \ if (LDP_DEBUG(msg, LDP_DEBUG_MSG_RECV)) \ log_debug("msg[in]: " emsg, __VA_ARGS__); \ } while (0) #define debug_msg_send(emsg, ...) \ do { \ if (LDP_DEBUG(msg, LDP_DEBUG_MSG_SEND)) \ log_debug("msg[out]: " emsg, __VA_ARGS__); \ } while (0) #define debug_msg(out, emsg, ...) \ do { \ if (out) \ debug_msg_send(emsg, __VA_ARGS__); \ else \ debug_msg_recv(emsg, __VA_ARGS__); \ } while (0) #define debug_kalive_recv(emsg, ...) \ do { \ if (LDP_DEBUG(msg, LDP_DEBUG_MSG_RECV_ALL)) \ log_debug("kalive[in]: " emsg, __VA_ARGS__); \ } while (0) #define debug_kalive_send(emsg, ...) \ do { \ if (LDP_DEBUG(msg, LDP_DEBUG_MSG_SEND_ALL)) \ log_debug("kalive[out]: " emsg, __VA_ARGS__); \ } while (0) #define debug_zebra_in(emsg, ...) \ do { \ if (LDP_DEBUG(zebra, LDP_DEBUG_ZEBRA)) \ log_debug("zebra[in]: " emsg, __VA_ARGS__); \ } while (0) #define debug_zebra_out(emsg, ...) \ do { \ if (LDP_DEBUG(zebra, LDP_DEBUG_ZEBRA)) \ log_debug("zebra[out]: " emsg, __VA_ARGS__); \ } while (0) #endif /* _LDP_DEBUG_H_ */ frr-7.2.1/ldpd/ldp_vty.h0000644000000000000000000001061413610377563011773 00000000000000/* * Copyright (C) 2016 by Open Source Routing. * * This program is free software; you can redistribute it and/or modify * it under the terms of the 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; see the file COPYING; if not, write to the * Free Software Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, * MA 02110-1301 USA */ #ifndef _LDP_VTY_H_ #define _LDP_VTY_H_ #include "vty.h" extern struct cmd_node ldp_node; extern struct cmd_node ldp_ipv4_node; extern struct cmd_node ldp_ipv6_node; extern struct cmd_node ldp_ipv4_iface_node; extern struct cmd_node ldp_ipv6_iface_node; extern struct cmd_node ldp_l2vpn_node; extern struct cmd_node ldp_pseudowire_node; extern struct cmd_node ldp_debug_node; union ldpd_addr; int ldp_get_address(const char *, int *, union ldpd_addr *); int ldp_config_write(struct vty *); int ldp_l2vpn_config_write(struct vty *); int ldp_debug_config_write(struct vty *); int ldp_vty_mpls_ldp (struct vty *, const char *); int ldp_vty_address_family (struct vty *, const char *, const char *); int ldp_vty_disc_holdtime(struct vty *, const char *, enum hello_type, long); int ldp_vty_disc_interval(struct vty *, const char *, enum hello_type, long); int ldp_vty_targeted_hello_accept(struct vty *, const char *, const char *); int ldp_vty_nbr_session_holdtime(struct vty *, const char *, struct in_addr, long); int ldp_vty_af_session_holdtime(struct vty *, const char *, long); int ldp_vty_interface(struct vty *, const char *, const char *); int ldp_vty_trans_addr(struct vty *, const char *, const char *); int ldp_vty_neighbor_targeted(struct vty *, const char *, const char *); int ldp_vty_label_advertise(struct vty *, const char *, const char *, const char *); int ldp_vty_label_allocate(struct vty *, const char *, const char *, const char *); int ldp_vty_label_expnull(struct vty *, const char *, const char *); int ldp_vty_label_accept(struct vty *, const char *, const char *, const char *); int ldp_vty_ttl_security(struct vty *, const char *); int ldp_vty_router_id(struct vty *, const char *, struct in_addr); int ldp_vty_ds_cisco_interop(struct vty *, const char *); int ldp_vty_trans_pref_ipv4(struct vty *, const char *); int ldp_vty_neighbor_password(struct vty *, const char *, struct in_addr, const char *); int ldp_vty_neighbor_ttl_security(struct vty *, const char *, struct in_addr, const char *); int ldp_vty_l2vpn(struct vty *, const char *, const char *); int ldp_vty_l2vpn_bridge(struct vty *, const char *, const char *); int ldp_vty_l2vpn_mtu(struct vty *, const char *, long); int ldp_vty_l2vpn_pwtype(struct vty *, const char *, const char *); int ldp_vty_l2vpn_interface(struct vty *, const char *, const char *); int ldp_vty_l2vpn_pseudowire(struct vty *, const char *, const char *); int ldp_vty_l2vpn_pw_cword(struct vty *, const char *, const char *); int ldp_vty_l2vpn_pw_nbr_addr(struct vty *, const char *, const char *); int ldp_vty_l2vpn_pw_nbr_id(struct vty *, const char *, struct in_addr); int ldp_vty_l2vpn_pw_pwid(struct vty *, const char *, long); int ldp_vty_l2vpn_pw_pwstatus(struct vty *, const char *); int ldp_vty_clear_nbr(struct vty *, const char *); int ldp_vty_debug(struct vty *, const char *, const char *, const char *, const char *); int ldp_vty_show_binding(struct vty *, const char *, const char *, int, const char *, unsigned long, unsigned long, const char *, const char *); int ldp_vty_show_discovery(struct vty *, const char *, const char *, const char *); int ldp_vty_show_interface(struct vty *, const char *, const char *); int ldp_vty_show_capabilities(struct vty *, const char *); int ldp_vty_show_neighbor(struct vty *, const char *, int, const char *, const char *); int ldp_vty_show_atom_binding(struct vty *, const char *, unsigned long, unsigned long, const char *); int ldp_vty_show_atom_vc(struct vty *, const char *, const char *, const char *, const char *); int ldp_vty_show_debugging(struct vty *); void ldp_vty_init(void); #endif /* _LDP_VTY_H_ */ frr-7.2.1/ldpd/ldp_vty_cmds.c0000644000000000000000000006342313610377563013002 00000000000000/* * Copyright (C) 2016 by Open Source Routing. * * This program is free software; you can redistribute it and/or modify * it under the terms of the 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; see the file COPYING; if not, write to the * Free Software Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, * MA 02110-1301 USA */ #include #include "command.h" #include "vty.h" #include "json.h" #include "ldpd/ldpd.h" #include "ldpd/ldp_vty.h" #ifndef VTYSH_EXTRACT_PL #include "ldpd/ldp_vty_cmds_clippy.c" #endif DEFPY_NOSH(ldp_mpls_ldp, ldp_mpls_ldp_cmd, "mpls ldp", "Global MPLS configuration subcommands\n" "Label Distribution Protocol\n") { return (ldp_vty_mpls_ldp(vty, NULL)); } DEFPY (no_ldp_mpls_ldp, no_ldp_mpls_ldp_cmd, "no mpls ldp", NO_STR "Global MPLS configuration subcommands\n" "Label Distribution Protocol\n") { return (ldp_vty_mpls_ldp(vty, "no")); } DEFPY_NOSH(ldp_l2vpn, ldp_l2vpn_cmd, "l2vpn WORD$l2vpn_name type vpls", "Configure l2vpn commands\n" "L2VPN name\n" "L2VPN type\n" "Virtual Private LAN Service\n") { return (ldp_vty_l2vpn(vty, NULL, l2vpn_name)); } DEFPY (no_ldp_l2vpn, no_ldp_l2vpn_cmd, "no l2vpn WORD$l2vpn_name type vpls", NO_STR "Configure l2vpn commands\n" "L2VPN name\n" "L2VPN type\n" "Virtual Private LAN Service\n") { return (ldp_vty_l2vpn(vty, "no", l2vpn_name)); } DEFPY_NOSH(ldp_address_family, ldp_address_family_cmd, "address-family $af", "Configure Address Family and its parameters\n" "IPv4\n" "IPv6\n") { return (ldp_vty_address_family(vty, NULL, af)); } DEFPY (no_ldp_address_family, no_ldp_address_family_cmd, "no address-family $af", NO_STR "Configure Address Family and its parameters\n" "IPv4\n" "IPv6\n") { return (ldp_vty_address_family(vty, "no", af)); } DEFPY_NOSH(ldp_exit_address_family, ldp_exit_address_family_cmd, "exit-address-family", "Exit from Address Family configuration mode\n") { if (vty->node == LDP_IPV4_NODE || vty->node == LDP_IPV6_NODE) vty->node = LDP_NODE; return CMD_SUCCESS; } DEFPY (ldp_discovery_link_holdtime, ldp_discovery_link_holdtime_cmd, "[no] discovery hello holdtime (1-65535)$holdtime", NO_STR "Configure discovery parameters\n" "LDP Link Hellos\n" "Hello holdtime\n" "Time (seconds) - 65535 implies infinite\n") { return (ldp_vty_disc_holdtime(vty, no, HELLO_LINK, holdtime)); } DEFPY (ldp_discovery_targeted_holdtime, ldp_discovery_targeted_holdtime_cmd, "[no] discovery targeted-hello holdtime (1-65535)$holdtime", NO_STR "Configure discovery parameters\n" "LDP Targeted Hellos\n" "Hello holdtime\n" "Time (seconds) - 65535 implies infinite\n") { return (ldp_vty_disc_holdtime(vty, no, HELLO_TARGETED, holdtime)); } DEFPY (ldp_discovery_link_interval, ldp_discovery_link_interval_cmd, "[no] discovery hello interval (1-65535)$interval", NO_STR "Configure discovery parameters\n" "LDP Link Hellos\n" "Hello interval\n" "Time (seconds)\n") { return (ldp_vty_disc_interval(vty, no, HELLO_LINK, interval)); } DEFPY (ldp_discovery_targeted_interval, ldp_discovery_targeted_interval_cmd, "[no] discovery targeted-hello interval (1-65535)$interval", NO_STR "Configure discovery parameters\n" "LDP Targeted Hellos\n" "Hello interval\n" "Time (seconds)\n") { return (ldp_vty_disc_interval(vty, no, HELLO_TARGETED, interval)); } DEFPY (ldp_dual_stack_transport_connection_prefer_ipv4, ldp_dual_stack_transport_connection_prefer_ipv4_cmd, "[no] dual-stack transport-connection prefer ipv4", NO_STR "Configure dual stack parameters\n" "Configure TCP transport parameters\n" "Configure preferred address family for TCP transport connection with neighbor\n" "IPv4\n") { return (ldp_vty_trans_pref_ipv4(vty, no)); } DEFPY (ldp_dual_stack_cisco_interop, ldp_dual_stack_cisco_interop_cmd, "[no] dual-stack cisco-interop", NO_STR "Configure dual stack parameters\n" "Use Cisco non-compliant format to send and interpret the Dual-Stack capability TLV\n") { return (ldp_vty_ds_cisco_interop(vty, no)); } DEFPY (ldp_neighbor_password, ldp_neighbor_password_cmd, "[no] neighbor A.B.C.D$neighbor password WORD$password", NO_STR "Configure neighbor parameters\n" "LDP Id of neighbor\n" "Configure password for MD5 authentication\n" "The password\n") { return (ldp_vty_neighbor_password(vty, no, neighbor, password)); } DEFPY (ldp_neighbor_session_holdtime, ldp_neighbor_session_holdtime_cmd, "[no] neighbor A.B.C.D$neighbor session holdtime (15-65535)$holdtime", NO_STR "Configure neighbor parameters\n" "LDP Id of neighbor\n" "Configure session parameters\n" "Configure session holdtime\n" "Time (seconds)\n") { return (ldp_vty_nbr_session_holdtime(vty, no, neighbor, holdtime)); } DEFPY (ldp_neighbor_ttl_security, ldp_neighbor_ttl_security_cmd, "[no] neighbor A.B.C.D$neighbor ttl-security ", NO_STR "Configure neighbor parameters\n" "LDP Id of neighbor\n" "LDP ttl security check\n" "Disable ttl security\n" "IP hops\n" "maximum number of hops\n") { return (ldp_vty_neighbor_ttl_security(vty, no, neighbor, hops_str)); } DEFPY (ldp_router_id, ldp_router_id_cmd, "[no] router-id A.B.C.D$address", NO_STR "Configure router Id\n" "LSR Id (in form of an IPv4 address)\n") { return (ldp_vty_router_id(vty, no, address)); } DEFPY (ldp_discovery_targeted_hello_accept, ldp_discovery_targeted_hello_accept_cmd, "[no] discovery targeted-hello accept [from <(1-199)|(1300-2699)|WORD>$from_acl]", NO_STR "Configure discovery parameters\n" "LDP Targeted Hellos\n" "Accept and respond to targeted hellos\n" "Access list to specify acceptable targeted hello source\n" "IP access-list number\n" "IP access-list number (expanded range)\n" "IP access-list name\n") { return (ldp_vty_targeted_hello_accept(vty, no, from_acl)); } DEFPY (ldp_discovery_transport_address_ipv4, ldp_discovery_transport_address_ipv4_cmd, "[no] discovery transport-address A.B.C.D$address", NO_STR "Configure discovery parameters\n" "Specify transport address for TCP connection\n" "IP address to be used as transport address\n") { return (ldp_vty_trans_addr(vty, no, address_str)); } DEFPY (ldp_discovery_transport_address_ipv6, ldp_discovery_transport_address_ipv6_cmd, "[no] discovery transport-address X:X::X:X$address", NO_STR "Configure discovery parameters\n" "Specify transport address for TCP connection\n" "IPv6 address to be used as transport address\n") { return (ldp_vty_trans_addr(vty, no, address_str)); } DEFPY (ldp_label_local_advertise, ldp_label_local_advertise_cmd, "[no] label local advertise [{to <(1-199)|(1300-2699)|WORD>$to_acl|for <(1-199)|(1300-2699)|WORD>$for_acl}]", NO_STR "Configure label control and policies\n" "Configure local label control and policies\n" "Configure outbound label advertisement control\n" "IP Access-list specifying controls on LDP Peers\n" "IP access-list number\n" "IP access-list number (expanded range)\n" "IP access-list name\n" "IP access-list for destination prefixes\n" "IP access-list number\n" "IP access-list number (expanded range)\n" "IP access-list name\n") { return (ldp_vty_label_advertise(vty, no, to_acl, for_acl)); } DEFPY (ldp_label_local_advertise_explicit_null, ldp_label_local_advertise_explicit_null_cmd, "[no] label local advertise explicit-null [for <(1-199)|(1300-2699)|WORD>$for_acl]", NO_STR "Configure label control and policies\n" "Configure local label control and policies\n" "Configure outbound label advertisement control\n" "Configure explicit-null advertisement\n" "IP access-list for destination prefixes\n" "IP access-list number\n" "IP access-list number (expanded range)\n" "IP access-list name\n") { return (ldp_vty_label_expnull(vty, no, for_acl)); } DEFPY (ldp_label_local_allocate, ldp_label_local_allocate_cmd, "[no] label local allocate $for_acl>", NO_STR "Configure label control and policies\n" "Configure local label control and policies\n" "Configure label allocation control\n" "allocate local label for host routes only\n" "IP access-list\n" "IP access-list number\n" "IP access-list number (expanded range)\n" "IP access-list name\n") { return (ldp_vty_label_allocate(vty, no, host_routes, for_acl)); } DEFPY (ldp_label_remote_accept, ldp_label_remote_accept_cmd, "[no] label remote accept {from <(1-199)|(1300-2699)|WORD>$from_acl|for <(1-199)|(1300-2699)|WORD>$for_acl}", NO_STR "Configure label control and policies\n" "Configure remote/peer label control and policies\n" "Configure inbound label acceptance control\n" "Neighbor from whom to accept label advertisement\n" "IP access-list number\n" "IP access-list number (expanded range)\n" "IP access-list name\n" "IP access-list for destination prefixes\n" "IP access-list number\n" "IP access-list number (expanded range)\n" "IP access-list name\n") { return (ldp_vty_label_accept(vty, no, from_acl, for_acl)); } DEFPY (ldp_ttl_security_disable, ldp_ttl_security_disable_cmd, "[no] ttl-security disable", NO_STR "LDP ttl security check\n" "Disable ttl security\n") { return (ldp_vty_ttl_security(vty, no)); } DEFPY (ldp_session_holdtime, ldp_session_holdtime_cmd, "[no] session holdtime (15-65535)$holdtime", NO_STR "Configure session parameters\n" "Configure session holdtime\n" "Time (seconds)\n") { return (ldp_vty_af_session_holdtime(vty, no, holdtime)); } DEFPY_NOSH(ldp_interface, ldp_interface_cmd, "interface IFNAME$ifname", "Enable LDP on an interface and enter interface submode\n" "Interface's name\n") { return (ldp_vty_interface(vty, NULL, ifname)); } DEFPY (no_ldp_interface, no_ldp_interface_cmd, "no interface IFNAME$ifname", NO_STR "Enable LDP on an interface and enter interface submode\n" "Interface's name\n") { return (ldp_vty_interface(vty, "no", ifname)); } DEFPY (ldp_neighbor_ipv4_targeted, ldp_neighbor_ipv4_targeted_cmd, "[no] neighbor A.B.C.D$address targeted", NO_STR "Configure neighbor parameters\n" "IP address of neighbor\n" "Establish targeted session\n") { return (ldp_vty_neighbor_targeted(vty, no, address_str)); } DEFPY (ldp_neighbor_ipv6_targeted, ldp_neighbor_ipv6_targeted_cmd, "[no] neighbor X:X::X:X$address targeted", NO_STR "Configure neighbor parameters\n" "IPv6 address of neighbor\n" "Establish targeted session\n") { return (ldp_vty_neighbor_targeted(vty, no, address_str)); } DEFPY (ldp_bridge, ldp_bridge_cmd, "[no] bridge IFNAME$ifname", NO_STR "Bridge interface\n" "Interface's name\n") { return (ldp_vty_l2vpn_bridge(vty, no, ifname)); } DEFPY (ldp_mtu, ldp_mtu_cmd, "[no] mtu (1500-9180)$mtu", NO_STR "Set Maximum Transmission Unit\n" "Maximum Transmission Unit value\n") { return (ldp_vty_l2vpn_mtu(vty, no, mtu)); } DEFPY (ldp_member_interface, ldp_member_interface_cmd, "[no] member interface IFNAME$ifname", NO_STR "L2VPN member configuration\n" "Local interface\n" "Interface's name\n") { return (ldp_vty_l2vpn_interface(vty, no, ifname)); } DEFPY_NOSH(ldp_member_pseudowire, ldp_member_pseudowire_cmd, "member pseudowire IFNAME$ifname", "L2VPN member configuration\n" "Pseudowire interface\n" "Interface's name\n") { return (ldp_vty_l2vpn_pseudowire(vty, NULL, ifname)); } DEFPY (no_ldp_member_pseudowire, no_ldp_member_pseudowire_cmd, "no member pseudowire IFNAME$ifname", NO_STR "L2VPN member configuration\n" "Pseudowire interface\n" "Interface's name\n") { return (ldp_vty_l2vpn_pseudowire(vty, "no", ifname)); } DEFPY (ldp_vc_type, ldp_vc_type_cmd, "[no] vc type $vc_type", NO_STR "Virtual Circuit options\n" "Virtual Circuit type to use\n" "Ethernet (type 5)\n" "Ethernet-tagged (type 4)\n") { return (ldp_vty_l2vpn_pwtype(vty, no, vc_type)); } DEFPY (ldp_control_word, ldp_control_word_cmd, "[no] control-word $preference", NO_STR "Control-word options\n" "Exclude control-word in pseudowire packets\n" "Include control-word in pseudowire packets\n") { return (ldp_vty_l2vpn_pw_cword(vty, no, preference)); } DEFPY (ldp_neighbor_address, ldp_neighbor_address_cmd, "[no] neighbor address $pw_address", NO_STR "Remote endpoint configuration\n" "Specify the IPv4 or IPv6 address of the remote endpoint\n" "IPv4 address\n" "IPv6 address\n") { return (ldp_vty_l2vpn_pw_nbr_addr(vty, no, pw_address_str)); } DEFPY (ldp_neighbor_lsr_id, ldp_neighbor_lsr_id_cmd, "[no] neighbor lsr-id A.B.C.D$address", NO_STR "Remote endpoint configuration\n" "Specify the LSR-ID of the remote endpoint\n" "IPv4 address\n") { return (ldp_vty_l2vpn_pw_nbr_id(vty, no, address)); } DEFPY (ldp_pw_id, ldp_pw_id_cmd, "[no] pw-id (1-4294967295)$pwid", NO_STR "Set the Virtual Circuit ID\n" "Virtual Circuit ID value\n") { return (ldp_vty_l2vpn_pw_pwid(vty, no, pwid)); } DEFPY (ldp_pw_status_disable, ldp_pw_status_disable_cmd, "[no] pw-status disable", NO_STR "Configure PW status\n" "Disable PW status\n") { return (ldp_vty_l2vpn_pw_pwstatus(vty, no)); } DEFPY (ldp_clear_mpls_ldp_neighbor, ldp_clear_mpls_ldp_neighbor_cmd, "clear mpls ldp neighbor []$address", "Reset functions\n" "Reset MPLS statistical information\n" "Clear LDP state\n" "Clear LDP neighbor sessions\n" "IPv4 address\n" "IPv6 address\n") { return (ldp_vty_clear_nbr(vty, address_str)); } DEFPY (ldp_debug_mpls_ldp_discovery_hello, ldp_debug_mpls_ldp_discovery_hello_cmd, "[no] debug mpls ldp discovery hello $dir", NO_STR "Debugging functions\n" "MPLS information\n" "Label Distribution Protocol\n" "Discovery messages\n" "Discovery hello message\n" "Received messages\n" "Sent messages\n") { return (ldp_vty_debug(vty, no, "discovery", dir, NULL)); } DEFPY (ldp_debug_mpls_ldp_type, ldp_debug_mpls_ldp_type_cmd, "[no] debug mpls ldp $type", NO_STR "Debugging functions\n" "MPLS information\n" "Label Distribution Protocol\n" "Errors\n" "LDP event information\n" "LDP label allocation information\n" "LDP zebra information\n") { return (ldp_vty_debug(vty, no, type, NULL, NULL)); } DEFPY (ldp_debug_mpls_ldp_messages_recv, ldp_debug_mpls_ldp_messages_recv_cmd, "[no] debug mpls ldp messages recv [all]$all", NO_STR "Debugging functions\n" "MPLS information\n" "Label Distribution Protocol\n" "Messages\n" "Received messages, excluding periodic Keep Alives\n" "Received messages, including periodic Keep Alives\n") { return (ldp_vty_debug(vty, no, "messages", "recv", all)); } DEFPY (ldp_debug_mpls_ldp_messages_sent, ldp_debug_mpls_ldp_messages_sent_cmd, "[no] debug mpls ldp messages sent [all]$all", NO_STR "Debugging functions\n" "MPLS information\n" "Label Distribution Protocol\n" "Messages\n" "Sent messages, excluding periodic Keep Alives\n" "Sent messages, including periodic Keep Alives\n") { return (ldp_vty_debug(vty, no, "messages", "sent", all)); } DEFPY (ldp_show_mpls_ldp_binding, ldp_show_mpls_ldp_binding_cmd, "show mpls ldp []$af binding\ [$prefix [longer-prefixes$longer_prefixes]]\ [{\ neighbor A.B.C.D$nbr\ |local-label (0-1048575)$local_label\ |remote-label (0-1048575)$remote_label\ }]\ [detail]$detail [json]$json", "Show running system information\n" "MPLS information\n" "Label Distribution Protocol\n" "IPv4 Address Family\n" "IPv6 Address Family\n" "Label Information Base (LIB) information\n" "Destination prefix (IPv4)\n" "Destination prefix (IPv6)\n" "Include longer matches\n" "Display labels from LDP neighbor\n" "Neighbor LSR-ID\n" "Match locally assigned label values\n" "Locally assigned label value\n" "Match remotely assigned label values\n" "Remotely assigned label value\n" "Show detailed information\n" JSON_STR) { if (!local_label_str) local_label = NO_LABEL; if (!remote_label_str) remote_label = NO_LABEL; return (ldp_vty_show_binding(vty, af, prefix_str, !!longer_prefixes, nbr_str, local_label, remote_label, detail, json)); } DEFPY (ldp_show_mpls_ldp_discovery, ldp_show_mpls_ldp_discovery_cmd, "show mpls ldp []$af discovery [detail]$detail [json]$json", "Show running system information\n" "MPLS information\n" "Label Distribution Protocol\n" "IPv4 Address Family\n" "IPv6 Address Family\n" "Discovery Hello Information\n" "Show detailed information\n" JSON_STR) { return (ldp_vty_show_discovery(vty, af, detail, json)); } DEFPY (ldp_show_mpls_ldp_interface, ldp_show_mpls_ldp_interface_cmd, "show mpls ldp []$af interface [json]$json", "Show running system information\n" "MPLS information\n" "Label Distribution Protocol\n" "IPv4 Address Family\n" "IPv6 Address Family\n" "interface information\n" JSON_STR) { return (ldp_vty_show_interface(vty, af, json)); } DEFPY (ldp_show_mpls_ldp_capabilities, ldp_show_mpls_ldp_capabilities_cmd, "show mpls ldp capabilities [json]$json", "Show running system information\n" "MPLS information\n" "Label Distribution Protocol\n" "Display LDP Capabilities information\n" JSON_STR) { return (ldp_vty_show_capabilities(vty, json)); } DEFPY (ldp_show_mpls_ldp_neighbor, ldp_show_mpls_ldp_neighbor_cmd, "show mpls ldp neighbor [A.B.C.D]$lsr_id [detail]$detail [json]$json", "Show running system information\n" "MPLS information\n" "Label Distribution Protocol\n" "Neighbor information\n" "Neighbor LSR-ID\n" "Show detailed information\n" JSON_STR) { return (ldp_vty_show_neighbor(vty, lsr_id_str, 0, detail, json)); } DEFPY (ldp_show_mpls_ldp_neighbor_capabilities, ldp_show_mpls_ldp_neighbor_capabilities_cmd, "show mpls ldp neighbor [A.B.C.D]$lsr_id capabilities [json]$json", "Show running system information\n" "MPLS information\n" "Label Distribution Protocol\n" "Neighbor information\n" "Neighbor LSR-ID\n" "Display neighbor capability information\n" JSON_STR) { return (ldp_vty_show_neighbor(vty, lsr_id_str, 1, NULL, json)); } DEFPY (ldp_show_l2vpn_atom_binding, ldp_show_l2vpn_atom_binding_cmd, "show l2vpn atom binding\ [{\ A.B.C.D$peer\ |local-label (16-1048575)$local_label\ |remote-label (16-1048575)$remote_label\ }]\ [json]$json", "Show running system information\n" "Show information about Layer2 VPN\n" "Show Any Transport over MPLS information\n" "Show AToM label binding information\n" "Destination address of the VC\n" "Match locally assigned label values\n" "Locally assigned label value\n" "Match remotely assigned label values\n" "Remotely assigned label value\n" JSON_STR) { if (!local_label_str) local_label = NO_LABEL; if (!remote_label_str) remote_label = NO_LABEL; return (ldp_vty_show_atom_binding(vty, peer_str, local_label, remote_label, json)); } DEFPY (ldp_show_l2vpn_atom_vc, ldp_show_l2vpn_atom_vc_cmd, "show l2vpn atom vc\ [{\ A.B.C.D$peer\ |interface IFNAME$ifname\ |vc-id (1-4294967295)$vcid\ }]\ [json]$json", "Show running system information\n" "Show information about Layer2 VPN\n" "Show Any Transport over MPLS information\n" "Show AToM virtual circuit information\n" "Destination address of the VC\n" "Local interface of the pseudowire\n" "Interface's name\n" "VC ID\n" "VC ID\n" JSON_STR) { return (ldp_vty_show_atom_vc(vty, peer_str, ifname, vcid_str, json)); } DEFPY_NOSH (ldp_show_debugging_mpls_ldp, ldp_show_debugging_mpls_ldp_cmd, "show debugging [mpls ldp]", "Show running system information\n" "Debugging functions\n" "MPLS information\n" "Label Distribution Protocol\n") { return (ldp_vty_show_debugging(vty)); } static void l2vpn_autocomplete(vector comps, struct cmd_token *token) { struct l2vpn *l2vpn; RB_FOREACH(l2vpn, l2vpn_head, &vty_conf->l2vpn_tree) vector_set(comps, XSTRDUP(MTYPE_COMPLETION, l2vpn->name)); } static const struct cmd_variable_handler l2vpn_var_handlers[] = { { .varname = "l2vpn_name", .completions = l2vpn_autocomplete }, { .completions = NULL } }; void ldp_vty_init (void) { cmd_variable_handler_register(l2vpn_var_handlers); install_node(&ldp_node, ldp_config_write); install_node(&ldp_ipv4_node, NULL); install_node(&ldp_ipv6_node, NULL); install_node(&ldp_ipv4_iface_node, NULL); install_node(&ldp_ipv6_iface_node, NULL); install_node(&ldp_l2vpn_node, ldp_l2vpn_config_write); install_node(&ldp_pseudowire_node, NULL); install_node(&ldp_debug_node, ldp_debug_config_write); install_default(LDP_NODE); install_default(LDP_IPV4_NODE); install_default(LDP_IPV6_NODE); install_default(LDP_IPV4_IFACE_NODE); install_default(LDP_IPV6_IFACE_NODE); install_default(LDP_L2VPN_NODE); install_default(LDP_PSEUDOWIRE_NODE); install_element(CONFIG_NODE, &ldp_mpls_ldp_cmd); install_element(CONFIG_NODE, &no_ldp_mpls_ldp_cmd); install_element(CONFIG_NODE, &ldp_l2vpn_cmd); install_element(CONFIG_NODE, &no_ldp_l2vpn_cmd); install_element(CONFIG_NODE, &ldp_debug_mpls_ldp_discovery_hello_cmd); install_element(CONFIG_NODE, &ldp_debug_mpls_ldp_type_cmd); install_element(CONFIG_NODE, &ldp_debug_mpls_ldp_messages_recv_cmd); install_element(CONFIG_NODE, &ldp_debug_mpls_ldp_messages_sent_cmd); install_element(LDP_NODE, &ldp_address_family_cmd); install_element(LDP_NODE, &no_ldp_address_family_cmd); install_element(LDP_NODE, &ldp_discovery_link_holdtime_cmd); install_element(LDP_NODE, &ldp_discovery_targeted_holdtime_cmd); install_element(LDP_NODE, &ldp_discovery_link_interval_cmd); install_element(LDP_NODE, &ldp_discovery_targeted_interval_cmd); install_element(LDP_NODE, &ldp_dual_stack_transport_connection_prefer_ipv4_cmd); install_element(LDP_NODE, &ldp_dual_stack_cisco_interop_cmd); install_element(LDP_NODE, &ldp_neighbor_password_cmd); install_element(LDP_NODE, &ldp_neighbor_session_holdtime_cmd); install_element(LDP_NODE, &ldp_neighbor_ttl_security_cmd); install_element(LDP_NODE, &ldp_router_id_cmd); install_element(LDP_IPV4_NODE, &ldp_discovery_link_holdtime_cmd); install_element(LDP_IPV4_NODE, &ldp_discovery_targeted_holdtime_cmd); install_element(LDP_IPV4_NODE, &ldp_discovery_link_interval_cmd); install_element(LDP_IPV4_NODE, &ldp_discovery_targeted_interval_cmd); install_element(LDP_IPV4_NODE, &ldp_discovery_targeted_hello_accept_cmd); install_element(LDP_IPV4_NODE, &ldp_discovery_transport_address_ipv4_cmd); install_element(LDP_IPV4_NODE, &ldp_label_local_advertise_cmd); install_element(LDP_IPV4_NODE, &ldp_label_local_advertise_explicit_null_cmd); install_element(LDP_IPV4_NODE, &ldp_label_local_allocate_cmd); install_element(LDP_IPV4_NODE, &ldp_label_remote_accept_cmd); install_element(LDP_IPV4_NODE, &ldp_ttl_security_disable_cmd); install_element(LDP_IPV4_NODE, &ldp_interface_cmd); install_element(LDP_IPV4_NODE, &no_ldp_interface_cmd); install_element(LDP_IPV4_NODE, &ldp_session_holdtime_cmd); install_element(LDP_IPV4_NODE, &ldp_neighbor_ipv4_targeted_cmd); install_element(LDP_IPV4_NODE, &ldp_exit_address_family_cmd); install_element(LDP_IPV6_NODE, &ldp_discovery_link_holdtime_cmd); install_element(LDP_IPV6_NODE, &ldp_discovery_targeted_holdtime_cmd); install_element(LDP_IPV6_NODE, &ldp_discovery_link_interval_cmd); install_element(LDP_IPV6_NODE, &ldp_discovery_targeted_interval_cmd); install_element(LDP_IPV6_NODE, &ldp_discovery_targeted_hello_accept_cmd); install_element(LDP_IPV6_NODE, &ldp_discovery_transport_address_ipv6_cmd); install_element(LDP_IPV6_NODE, &ldp_label_local_advertise_cmd); install_element(LDP_IPV6_NODE, &ldp_label_local_advertise_explicit_null_cmd); install_element(LDP_IPV6_NODE, &ldp_label_local_allocate_cmd); install_element(LDP_IPV6_NODE, &ldp_label_remote_accept_cmd); install_element(LDP_IPV6_NODE, &ldp_ttl_security_disable_cmd); install_element(LDP_IPV6_NODE, &ldp_interface_cmd); install_element(LDP_IPV6_NODE, &no_ldp_interface_cmd); install_element(LDP_IPV6_NODE, &ldp_session_holdtime_cmd); install_element(LDP_IPV6_NODE, &ldp_neighbor_ipv6_targeted_cmd); install_element(LDP_IPV6_NODE, &ldp_exit_address_family_cmd); install_element(LDP_IPV4_IFACE_NODE, &ldp_discovery_link_holdtime_cmd); install_element(LDP_IPV4_IFACE_NODE, &ldp_discovery_link_interval_cmd); install_element(LDP_IPV6_IFACE_NODE, &ldp_discovery_link_holdtime_cmd); install_element(LDP_IPV6_IFACE_NODE, &ldp_discovery_link_interval_cmd); install_element(LDP_L2VPN_NODE, &ldp_bridge_cmd); install_element(LDP_L2VPN_NODE, &ldp_mtu_cmd); install_element(LDP_L2VPN_NODE, &ldp_member_interface_cmd); install_element(LDP_L2VPN_NODE, &ldp_member_pseudowire_cmd); install_element(LDP_L2VPN_NODE, &no_ldp_member_pseudowire_cmd); install_element(LDP_L2VPN_NODE, &ldp_vc_type_cmd); install_element(LDP_PSEUDOWIRE_NODE, &ldp_control_word_cmd); install_element(LDP_PSEUDOWIRE_NODE, &ldp_neighbor_address_cmd); install_element(LDP_PSEUDOWIRE_NODE, &ldp_neighbor_lsr_id_cmd); install_element(LDP_PSEUDOWIRE_NODE, &ldp_pw_id_cmd); install_element(LDP_PSEUDOWIRE_NODE, &ldp_pw_status_disable_cmd); install_element(ENABLE_NODE, &ldp_clear_mpls_ldp_neighbor_cmd); install_element(ENABLE_NODE, &ldp_debug_mpls_ldp_discovery_hello_cmd); install_element(ENABLE_NODE, &ldp_debug_mpls_ldp_type_cmd); install_element(ENABLE_NODE, &ldp_debug_mpls_ldp_messages_recv_cmd); install_element(ENABLE_NODE, &ldp_debug_mpls_ldp_messages_sent_cmd); install_element(VIEW_NODE, &ldp_show_mpls_ldp_binding_cmd); install_element(VIEW_NODE, &ldp_show_mpls_ldp_discovery_cmd); install_element(VIEW_NODE, &ldp_show_mpls_ldp_interface_cmd); install_element(VIEW_NODE, &ldp_show_mpls_ldp_capabilities_cmd); install_element(VIEW_NODE, &ldp_show_mpls_ldp_neighbor_cmd); install_element(VIEW_NODE, &ldp_show_mpls_ldp_neighbor_capabilities_cmd); install_element(VIEW_NODE, &ldp_show_l2vpn_atom_binding_cmd); install_element(VIEW_NODE, &ldp_show_l2vpn_atom_vc_cmd); install_element(VIEW_NODE, &ldp_show_debugging_mpls_ldp_cmd); } frr-7.2.1/ldpd/ldp_vty_conf.c0000644000000000000000000010513513610377563012776 00000000000000/* * Copyright (C) 2016 by Open Source Routing. * * This program is free software; you can redistribute it and/or modify * it under the terms of the 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; see the file COPYING; if not, write to the * Free Software Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, * MA 02110-1301 USA */ #include #include "ldpd.h" #include "ldpe.h" #include "lde.h" #include "log.h" #include "command.h" #include "vrf.h" #include "if.h" #include "vty.h" #include "ldp_vty.h" static void ldp_af_iface_config_write(struct vty *, int); static void ldp_af_config_write(struct vty *, int, struct ldpd_conf *, struct ldpd_af_conf *); static void ldp_l2vpn_pw_config_write(struct vty *, struct l2vpn_pw *); static int ldp_vty_get_af(struct vty *); static int ldp_iface_is_configured(struct ldpd_conf *, const char *); struct cmd_node ldp_node = { LDP_NODE, "%s(config-ldp)# ", 1, }; struct cmd_node ldp_ipv4_node = { LDP_IPV4_NODE, "%s(config-ldp-af)# ", 1, }; struct cmd_node ldp_ipv6_node = { LDP_IPV6_NODE, "%s(config-ldp-af)# ", 1, }; struct cmd_node ldp_ipv4_iface_node = { LDP_IPV4_IFACE_NODE, "%s(config-ldp-af-if)# ", 1, }; struct cmd_node ldp_ipv6_iface_node = { LDP_IPV6_IFACE_NODE, "%s(config-ldp-af-if)# ", 1, }; struct cmd_node ldp_l2vpn_node = { LDP_L2VPN_NODE, "%s(config-l2vpn)# ", 1, }; struct cmd_node ldp_pseudowire_node = { LDP_PSEUDOWIRE_NODE, "%s(config-l2vpn-pw)# ", 1, }; int ldp_get_address(const char *str, int *af, union ldpd_addr *addr) { if (!str || !af || !addr) return (-1); memset(addr, 0, sizeof(*addr)); if (inet_pton(AF_INET, str, &addr->v4) == 1) { *af = AF_INET; return (0); } if (inet_pton(AF_INET6, str, &addr->v6) == 1) { *af = AF_INET6; return (0); } return (-1); } static void ldp_af_iface_config_write(struct vty *vty, int af) { struct iface *iface; struct iface_af *ia; RB_FOREACH(iface, iface_head, &ldpd_conf->iface_tree) { ia = iface_af_get(iface, af); if (!ia->enabled) continue; vty_out (vty, " !\n"); vty_out (vty, " interface %s\n", iface->name); if (ia->hello_holdtime != LINK_DFLT_HOLDTIME && ia->hello_holdtime != 0) vty_out (vty, " discovery hello holdtime %u\n", ia->hello_holdtime); if (ia->hello_interval != DEFAULT_HELLO_INTERVAL && ia->hello_interval != 0) vty_out (vty, " discovery hello interval %u\n", ia->hello_interval); } } static void ldp_af_config_write(struct vty *vty, int af, struct ldpd_conf *conf, struct ldpd_af_conf *af_conf) { struct tnbr *tnbr; if (!(af_conf->flags & F_LDPD_AF_ENABLED)) return; vty_out (vty, " !\n"); vty_out (vty, " address-family %s\n", af_name(af)); if (af_conf->lhello_holdtime != LINK_DFLT_HOLDTIME && af_conf->lhello_holdtime != 0 ) vty_out (vty, " discovery hello holdtime %u\n", af_conf->lhello_holdtime); if (af_conf->lhello_interval != DEFAULT_HELLO_INTERVAL && af_conf->lhello_interval != 0) vty_out (vty, " discovery hello interval %u\n", af_conf->lhello_interval); if (af_conf->flags & F_LDPD_AF_THELLO_ACCEPT) { vty_out(vty, " discovery targeted-hello accept"); if (af_conf->acl_thello_accept_from[0] != '\0') vty_out(vty, " from %s", af_conf->acl_thello_accept_from); vty_out (vty, "\n"); } if (af_conf->thello_holdtime != TARGETED_DFLT_HOLDTIME && af_conf->thello_holdtime != 0) vty_out (vty, " discovery targeted-hello holdtime %u\n", af_conf->thello_holdtime); if (af_conf->thello_interval != DEFAULT_HELLO_INTERVAL && af_conf->thello_interval != 0) vty_out (vty, " discovery targeted-hello interval %u\n", af_conf->thello_interval); if (ldp_addrisset(af, &af_conf->trans_addr)) vty_out (vty, " discovery transport-address %s\n", log_addr(af, &af_conf->trans_addr)); else vty_out (vty, " ! Incomplete config, specify a discovery transport-address\n"); if ((af_conf->flags & F_LDPD_AF_ALLOCHOSTONLY) || af_conf->acl_label_allocate_for[0] != '\0') { vty_out(vty, " label local allocate"); if (af_conf->flags & F_LDPD_AF_ALLOCHOSTONLY) vty_out(vty, " host-routes"); else vty_out(vty, " for %s", af_conf->acl_label_allocate_for); vty_out (vty, "\n"); } if (af_conf->acl_label_advertise_for[0] != '\0' || af_conf->acl_label_advertise_to[0] != '\0') { vty_out(vty, " label local advertise"); if (af_conf->acl_label_advertise_to[0] != '\0') vty_out(vty, " to %s", af_conf->acl_label_advertise_to); if (af_conf->acl_label_advertise_for[0] != '\0') vty_out(vty, " for %s", af_conf->acl_label_advertise_for); vty_out (vty, "\n"); } if (af_conf->flags & F_LDPD_AF_EXPNULL) { vty_out(vty, " label local advertise explicit-null"); if (af_conf->acl_label_expnull_for[0] != '\0') vty_out(vty, " for %s", af_conf->acl_label_expnull_for); vty_out (vty, "\n"); } if (af_conf->acl_label_accept_for[0] != '\0' || af_conf->acl_label_accept_from[0] != '\0') { vty_out(vty, " label remote accept"); if (af_conf->acl_label_accept_from[0] != '\0') vty_out(vty, " from %s", af_conf->acl_label_accept_from); if (af_conf->acl_label_accept_for[0] != '\0') vty_out(vty, " for %s", af_conf->acl_label_accept_for); vty_out (vty, "\n"); } if (af_conf->flags & F_LDPD_AF_NO_GTSM) vty_out (vty, " ttl-security disable\n"); if (af_conf->keepalive != DEFAULT_KEEPALIVE) vty_out (vty, " session holdtime %u\n",af_conf->keepalive); RB_FOREACH(tnbr, tnbr_head, &ldpd_conf->tnbr_tree) { if (tnbr->af == af) { vty_out (vty, " !\n"); vty_out (vty, " neighbor %s targeted\n", log_addr(tnbr->af, &tnbr->addr)); } } ldp_af_iface_config_write(vty, af); vty_out(vty, " !\n"); vty_out(vty, " exit-address-family\n"); } int ldp_config_write(struct vty *vty) { struct nbr_params *nbrp; if (!(ldpd_conf->flags & F_LDPD_ENABLED)) return (0); vty_out (vty, "mpls ldp\n"); if (ldpd_conf->rtr_id.s_addr != 0) vty_out (vty, " router-id %s\n", inet_ntoa(ldpd_conf->rtr_id)); if (ldpd_conf->lhello_holdtime != LINK_DFLT_HOLDTIME && ldpd_conf->lhello_holdtime != 0) vty_out (vty, " discovery hello holdtime %u\n", ldpd_conf->lhello_holdtime); if (ldpd_conf->lhello_interval != DEFAULT_HELLO_INTERVAL && ldpd_conf->lhello_interval != 0) vty_out (vty, " discovery hello interval %u\n", ldpd_conf->lhello_interval); if (ldpd_conf->thello_holdtime != TARGETED_DFLT_HOLDTIME && ldpd_conf->thello_holdtime != 0) vty_out (vty, " discovery targeted-hello holdtime %u\n", ldpd_conf->thello_holdtime); if (ldpd_conf->thello_interval != DEFAULT_HELLO_INTERVAL && ldpd_conf->thello_interval != 0) vty_out (vty, " discovery targeted-hello interval %u\n", ldpd_conf->thello_interval); if (ldpd_conf->trans_pref == DUAL_STACK_LDPOV4) vty_out (vty, " dual-stack transport-connection prefer ipv4\n"); if (ldpd_conf->flags & F_LDPD_DS_CISCO_INTEROP) vty_out (vty, " dual-stack cisco-interop\n"); RB_FOREACH(nbrp, nbrp_head, &ldpd_conf->nbrp_tree) { if (nbrp->flags & F_NBRP_KEEPALIVE) vty_out (vty, " neighbor %s session holdtime %u\n", inet_ntoa(nbrp->lsr_id),nbrp->keepalive); if (nbrp->flags & F_NBRP_GTSM) { if (nbrp->gtsm_enabled) vty_out (vty, " neighbor %s ttl-security hops " "%u\n", inet_ntoa(nbrp->lsr_id), nbrp->gtsm_hops); else vty_out (vty, " neighbor %s ttl-security " "disable\n",inet_ntoa(nbrp->lsr_id)); } if (nbrp->auth.method == AUTH_MD5SIG) vty_out (vty, " neighbor %s password %s\n", inet_ntoa(nbrp->lsr_id),nbrp->auth.md5key); } ldp_af_config_write(vty, AF_INET, ldpd_conf, &ldpd_conf->ipv4); ldp_af_config_write(vty, AF_INET6, ldpd_conf, &ldpd_conf->ipv6); vty_out (vty, " !\n"); vty_out (vty, "!\n"); return (1); } static void ldp_l2vpn_pw_config_write(struct vty *vty, struct l2vpn_pw *pw) { int missing_lsrid = 0; int missing_pwid = 0; vty_out (vty, " !\n"); vty_out (vty, " member pseudowire %s\n", pw->ifname); if (pw->lsr_id.s_addr != INADDR_ANY) vty_out (vty, " neighbor lsr-id %s\n",inet_ntoa(pw->lsr_id)); else missing_lsrid = 1; if (pw->flags & F_PW_STATIC_NBR_ADDR) vty_out (vty, " neighbor address %s\n", log_addr(pw->af, &pw->addr)); if (pw->pwid != 0) vty_out (vty, " pw-id %u\n", pw->pwid); else missing_pwid = 1; if (!(pw->flags & F_PW_CWORD_CONF)) vty_out (vty, " control-word exclude\n"); if (!(pw->flags & F_PW_STATUSTLV_CONF)) vty_out (vty, " pw-status disable\n"); if (missing_lsrid) vty_out (vty, " ! Incomplete config, specify a neighbor lsr-id\n"); if (missing_pwid) vty_out (vty," ! Incomplete config, specify a pw-id\n"); } int ldp_l2vpn_config_write(struct vty *vty) { struct l2vpn *l2vpn; struct l2vpn_if *lif; struct l2vpn_pw *pw; RB_FOREACH(l2vpn, l2vpn_head, &ldpd_conf->l2vpn_tree) { vty_out (vty, "l2vpn %s type vpls\n", l2vpn->name); if (l2vpn->pw_type != DEFAULT_PW_TYPE) vty_out (vty, " vc type ethernet-tagged\n"); if (l2vpn->mtu != DEFAULT_L2VPN_MTU) vty_out (vty, " mtu %u\n", l2vpn->mtu); if (l2vpn->br_ifname[0] != '\0') vty_out (vty, " bridge %s\n",l2vpn->br_ifname); RB_FOREACH(lif, l2vpn_if_head, &l2vpn->if_tree) vty_out (vty, " member interface %s\n",lif->ifname); RB_FOREACH(pw, l2vpn_pw_head, &l2vpn->pw_tree) ldp_l2vpn_pw_config_write(vty, pw); RB_FOREACH(pw, l2vpn_pw_head, &l2vpn->pw_inactive_tree) ldp_l2vpn_pw_config_write(vty, pw); vty_out (vty, " !\n"); vty_out (vty, "!\n"); } return (0); } static int ldp_vty_get_af(struct vty *vty) { switch (vty->node) { case LDP_IPV4_NODE: case LDP_IPV4_IFACE_NODE: return (AF_INET); case LDP_IPV6_NODE: case LDP_IPV6_IFACE_NODE: return (AF_INET6); default: fatalx("ldp_vty_get_af: unexpected node"); } } static int ldp_iface_is_configured(struct ldpd_conf *xconf, const char *ifname) { struct l2vpn *l2vpn; if (if_lookup_name(xconf, ifname)) return (1); RB_FOREACH(l2vpn, l2vpn_head, &xconf->l2vpn_tree) { if (l2vpn_if_find(l2vpn, ifname)) return (1); if (l2vpn_pw_find(l2vpn, ifname)) return (1); } return (0); } int ldp_vty_mpls_ldp(struct vty *vty, const char *negate) { if (negate) vty_conf->flags &= ~F_LDPD_ENABLED; else { vty->node = LDP_NODE; vty_conf->flags |= F_LDPD_ENABLED; } ldp_config_apply(vty, vty_conf); return (CMD_SUCCESS); } int ldp_vty_address_family(struct vty *vty, const char *negate, const char *af_str) { struct ldpd_af_conf *af_conf; int af; if (af_str == NULL) return (CMD_WARNING_CONFIG_FAILED); if (strcmp(af_str, "ipv4") == 0) { af = AF_INET; af_conf = &vty_conf->ipv4; } else if (strcmp(af_str, "ipv6") == 0) { af = AF_INET6; af_conf = &vty_conf->ipv6; } else return (CMD_WARNING_CONFIG_FAILED); if (negate) { af_conf->flags &= ~F_LDPD_AF_ENABLED; ldp_config_apply(vty, vty_conf); return (CMD_SUCCESS); } switch (af) { case AF_INET: vty->node = LDP_IPV4_NODE; break; case AF_INET6: vty->node = LDP_IPV6_NODE; break; default: fatalx("ldp_vty_address_family: unknown af"); } af_conf->flags |= F_LDPD_AF_ENABLED; ldp_config_apply(vty, vty_conf); return (CMD_SUCCESS); } int ldp_vty_disc_holdtime(struct vty *vty, const char *negate, enum hello_type hello_type, long secs) { struct ldpd_af_conf *af_conf; struct iface *iface; struct iface_af *ia; int af; switch (vty->node) { case LDP_NODE: if (negate) { switch (hello_type) { case HELLO_LINK: vty_conf->lhello_holdtime = LINK_DFLT_HOLDTIME; break; case HELLO_TARGETED: vty_conf->thello_holdtime = TARGETED_DFLT_HOLDTIME; break; } } else { switch (hello_type) { case HELLO_LINK: vty_conf->lhello_holdtime = secs; break; case HELLO_TARGETED: vty_conf->thello_holdtime = secs; break; } } ldp_config_apply(vty, vty_conf); break; case LDP_IPV4_NODE: case LDP_IPV6_NODE: af = ldp_vty_get_af(vty); af_conf = ldp_af_conf_get(vty_conf, af); if (negate) { switch (hello_type) { case HELLO_LINK: af_conf->lhello_holdtime = 0; break; case HELLO_TARGETED: af_conf->thello_holdtime = 0; break; } } else { switch (hello_type) { case HELLO_LINK: af_conf->lhello_holdtime = secs; break; case HELLO_TARGETED: af_conf->thello_holdtime = secs; break; } } ldp_config_apply(vty, vty_conf); break; case LDP_IPV4_IFACE_NODE: case LDP_IPV6_IFACE_NODE: af = ldp_vty_get_af(vty); iface = VTY_GET_CONTEXT(iface); VTY_CHECK_CONTEXT(iface); ia = iface_af_get(iface, af); if (negate) ia->hello_holdtime = 0; else ia->hello_holdtime = secs; ldp_config_apply(vty, vty_conf); break; default: fatalx("ldp_vty_disc_holdtime: unexpected node"); } return (CMD_SUCCESS); } int ldp_vty_disc_interval(struct vty *vty, const char *negate, enum hello_type hello_type, long secs) { struct ldpd_af_conf *af_conf; struct iface *iface; struct iface_af *ia; int af; switch (vty->node) { case LDP_NODE: if (negate) { switch (hello_type) { case HELLO_LINK: vty_conf->lhello_interval = DEFAULT_HELLO_INTERVAL; break; case HELLO_TARGETED: vty_conf->thello_interval = DEFAULT_HELLO_INTERVAL; break; } } else { switch (hello_type) { case HELLO_LINK: vty_conf->lhello_interval = secs; break; case HELLO_TARGETED: vty_conf->thello_interval = secs; break; } } ldp_config_apply(vty, vty_conf); break; case LDP_IPV4_NODE: case LDP_IPV6_NODE: af = ldp_vty_get_af(vty); af_conf = ldp_af_conf_get(vty_conf, af); if (negate) { switch (hello_type) { case HELLO_LINK: af_conf->lhello_interval = 0; break; case HELLO_TARGETED: af_conf->thello_interval = 0; break; } } else { switch (hello_type) { case HELLO_LINK: af_conf->lhello_interval = secs; break; case HELLO_TARGETED: af_conf->thello_interval = secs; break; } } ldp_config_apply(vty, vty_conf); break; case LDP_IPV4_IFACE_NODE: case LDP_IPV6_IFACE_NODE: af = ldp_vty_get_af(vty); iface = VTY_GET_CONTEXT(iface); VTY_CHECK_CONTEXT(iface); ia = iface_af_get(iface, af); if (negate) ia->hello_interval = 0; else ia->hello_interval = secs; ldp_config_apply(vty, vty_conf); break; default: fatalx("ldp_vty_disc_interval: unexpected node"); } return (CMD_SUCCESS); } int ldp_vty_targeted_hello_accept(struct vty *vty, const char *negate, const char *acl_from_str) { struct ldpd_af_conf *af_conf; int af; af = ldp_vty_get_af(vty); af_conf = ldp_af_conf_get(vty_conf, af); if (negate) { af_conf->flags &= ~F_LDPD_AF_THELLO_ACCEPT; af_conf->acl_thello_accept_from[0] = '\0'; } else { af_conf->flags |= F_LDPD_AF_THELLO_ACCEPT; if (acl_from_str) strlcpy(af_conf->acl_thello_accept_from, acl_from_str, sizeof(af_conf->acl_thello_accept_from)); else af_conf->acl_thello_accept_from[0] = '\0'; } ldp_config_apply(vty, vty_conf); return (CMD_SUCCESS); } int ldp_vty_nbr_session_holdtime(struct vty *vty, const char *negate, struct in_addr lsr_id, long secs) { struct nbr_params *nbrp; if (bad_addr_v4(lsr_id)) { vty_out (vty, "%% Malformed address\n"); return (CMD_WARNING_CONFIG_FAILED); } nbrp = nbr_params_find(vty_conf, lsr_id); if (negate) { if (nbrp == NULL) return (CMD_SUCCESS); nbrp->keepalive = 0; nbrp->flags &= ~F_NBRP_KEEPALIVE; } else { if (nbrp == NULL) { nbrp = nbr_params_new(lsr_id); RB_INSERT(nbrp_head, &vty_conf->nbrp_tree, nbrp); QOBJ_REG(nbrp, nbr_params); } else if (nbrp->keepalive == secs) return (CMD_SUCCESS); nbrp->keepalive = secs; nbrp->flags |= F_NBRP_KEEPALIVE; } ldp_config_apply(vty, vty_conf); return (CMD_SUCCESS); } int ldp_vty_af_session_holdtime(struct vty *vty, const char *negate, long secs) { struct ldpd_af_conf *af_conf; int af; af = ldp_vty_get_af(vty); af_conf = ldp_af_conf_get(vty_conf, af); if (negate) af_conf->keepalive = DEFAULT_KEEPALIVE; else af_conf->keepalive = secs; ldp_config_apply(vty, vty_conf); return (CMD_SUCCESS); } int ldp_vty_interface(struct vty *vty, const char *negate, const char *ifname) { int af; struct iface *iface; struct iface_af *ia; if (ifname == NULL) { vty_out (vty, "%% Missing IF name\n"); return (CMD_WARNING_CONFIG_FAILED); } af = ldp_vty_get_af(vty); iface = if_lookup_name(vty_conf, ifname); if (negate) { if (iface == NULL) return (CMD_SUCCESS); ia = iface_af_get(iface, af); if (ia->enabled == 0) return (CMD_SUCCESS); ia->enabled = 0; ia->hello_holdtime = 0; ia->hello_interval = 0; ldp_config_apply(vty, vty_conf); return (CMD_SUCCESS); } if (iface == NULL) { if (ldp_iface_is_configured(vty_conf, ifname)) { vty_out (vty,"%% Interface is already in use\n"); return (CMD_SUCCESS); } iface = if_new(ifname); ia = iface_af_get(iface, af); ia->enabled = 1; RB_INSERT(iface_head, &vty_conf->iface_tree, iface); QOBJ_REG(iface, iface); ldp_config_apply(vty, vty_conf); } else { ia = iface_af_get(iface, af); if (!ia->enabled) { ia->enabled = 1; ldp_config_apply(vty, vty_conf); } } switch (af) { case AF_INET: VTY_PUSH_CONTEXT(LDP_IPV4_IFACE_NODE, iface); break; case AF_INET6: VTY_PUSH_CONTEXT(LDP_IPV6_IFACE_NODE, iface); break; default: break; } return (CMD_SUCCESS); } int ldp_vty_trans_addr(struct vty *vty, const char *negate, const char *addr_str) { struct ldpd_af_conf *af_conf; int af; af = ldp_vty_get_af(vty); af_conf = ldp_af_conf_get(vty_conf, af); if (negate) memset(&af_conf->trans_addr, 0, sizeof(af_conf->trans_addr)); else { if (addr_str == NULL || inet_pton(af, addr_str, &af_conf->trans_addr) != 1 || bad_addr(af, &af_conf->trans_addr)) { vty_out (vty, "%% Malformed address\n"); return (CMD_SUCCESS); } } ldp_config_apply(vty, vty_conf); return (CMD_SUCCESS); } int ldp_vty_neighbor_targeted(struct vty *vty, const char *negate, const char *addr_str) { int af; union ldpd_addr addr; struct tnbr *tnbr; af = ldp_vty_get_af(vty); if (addr_str == NULL || inet_pton(af, addr_str, &addr) != 1 || bad_addr(af, &addr)) { vty_out (vty, "%% Malformed address\n"); return (CMD_WARNING_CONFIG_FAILED); } if (af == AF_INET6 && IN6_IS_SCOPE_EMBED(&addr.v6)) { vty_out (vty, "%% Address can not be link-local\n"); return (CMD_WARNING_CONFIG_FAILED); } tnbr = tnbr_find(vty_conf, af, &addr); if (negate) { if (tnbr == NULL) return (CMD_SUCCESS); QOBJ_UNREG(tnbr); RB_REMOVE(tnbr_head, &vty_conf->tnbr_tree, tnbr); free(tnbr); ldp_config_apply(vty, vty_conf); return (CMD_SUCCESS); } if (tnbr) return (CMD_SUCCESS); tnbr = tnbr_new(af, &addr); tnbr->flags |= F_TNBR_CONFIGURED; RB_INSERT(tnbr_head, &vty_conf->tnbr_tree, tnbr); QOBJ_REG(tnbr, tnbr); ldp_config_apply(vty, vty_conf); return (CMD_SUCCESS); } int ldp_vty_label_advertise(struct vty *vty, const char *negate, const char *acl_to_str, const char *acl_for_str) { struct ldpd_af_conf *af_conf; int af; af = ldp_vty_get_af(vty); af_conf = ldp_af_conf_get(vty_conf, af); if (negate) { af_conf->acl_label_advertise_to[0] = '\0'; af_conf->acl_label_advertise_for[0] = '\0'; } else { if (acl_to_str) strlcpy(af_conf->acl_label_advertise_to, acl_to_str, sizeof(af_conf->acl_label_advertise_to)); else af_conf->acl_label_advertise_to[0] = '\0'; if (acl_for_str) strlcpy(af_conf->acl_label_advertise_for, acl_for_str, sizeof(af_conf->acl_label_advertise_for)); else af_conf->acl_label_advertise_for[0] = '\0'; } ldp_config_apply(vty, vty_conf); return (CMD_SUCCESS); } int ldp_vty_label_allocate(struct vty *vty, const char *negate, const char *host_routes, const char *acl_for_str) { struct ldpd_af_conf *af_conf; int af; af = ldp_vty_get_af(vty); af_conf = ldp_af_conf_get(vty_conf, af); af_conf->flags &= ~F_LDPD_AF_ALLOCHOSTONLY; af_conf->acl_label_allocate_for[0] = '\0'; if (!negate) { if (host_routes) af_conf->flags |= F_LDPD_AF_ALLOCHOSTONLY; else strlcpy(af_conf->acl_label_allocate_for, acl_for_str, sizeof(af_conf->acl_label_allocate_for)); } ldp_config_apply(vty, vty_conf); return (CMD_SUCCESS); } int ldp_vty_label_expnull(struct vty *vty, const char *negate, const char *acl_for_str) { struct ldpd_af_conf *af_conf; int af; af = ldp_vty_get_af(vty); af_conf = ldp_af_conf_get(vty_conf, af); if (negate) { af_conf->flags &= ~F_LDPD_AF_EXPNULL; af_conf->acl_label_expnull_for[0] = '\0'; } else { af_conf->flags |= F_LDPD_AF_EXPNULL; if (acl_for_str) strlcpy(af_conf->acl_label_expnull_for, acl_for_str, sizeof(af_conf->acl_label_expnull_for)); else af_conf->acl_label_expnull_for[0] = '\0'; } ldp_config_apply(vty, vty_conf); return (CMD_SUCCESS); } int ldp_vty_label_accept(struct vty *vty, const char *negate, const char *acl_from_str, const char *acl_for_str) { struct ldpd_af_conf *af_conf; int af; af = ldp_vty_get_af(vty); af_conf = ldp_af_conf_get(vty_conf, af); if (negate) { af_conf->acl_label_accept_from[0] = '\0'; af_conf->acl_label_accept_for[0] = '\0'; } else { if (acl_from_str) strlcpy(af_conf->acl_label_accept_from, acl_from_str, sizeof(af_conf->acl_label_accept_from)); else af_conf->acl_label_accept_from[0] = '\0'; if (acl_for_str) strlcpy(af_conf->acl_label_accept_for, acl_for_str, sizeof(af_conf->acl_label_accept_for)); else af_conf->acl_label_accept_for[0] = '\0'; } ldp_config_apply(vty, vty_conf); return (CMD_SUCCESS); } int ldp_vty_ttl_security(struct vty *vty, const char *negate) { struct ldpd_af_conf *af_conf; int af; af = ldp_vty_get_af(vty); af_conf = ldp_af_conf_get(vty_conf, af); if (negate) af_conf->flags &= ~F_LDPD_AF_NO_GTSM; else af_conf->flags |= F_LDPD_AF_NO_GTSM; ldp_config_apply(vty, vty_conf); return (CMD_SUCCESS); } int ldp_vty_router_id(struct vty *vty, const char *negate, struct in_addr address) { if (negate) vty_conf->rtr_id.s_addr = INADDR_ANY; else { if (bad_addr_v4(address)) { vty_out (vty, "%% Malformed address\n"); return (CMD_SUCCESS); } vty_conf->rtr_id = address; } ldp_config_apply(vty, vty_conf); return (CMD_SUCCESS); } int ldp_vty_ds_cisco_interop(struct vty *vty, const char * negate) { if (negate) vty_conf->flags &= ~F_LDPD_DS_CISCO_INTEROP; else vty_conf->flags |= F_LDPD_DS_CISCO_INTEROP; ldp_config_apply(vty, vty_conf); return (CMD_SUCCESS); } int ldp_vty_trans_pref_ipv4(struct vty *vty, const char *negate) { if (negate) vty_conf->trans_pref = DUAL_STACK_LDPOV6; else vty_conf->trans_pref = DUAL_STACK_LDPOV4; ldp_config_apply(vty, vty_conf); return (CMD_SUCCESS); } int ldp_vty_neighbor_password(struct vty *vty, const char *negate, struct in_addr lsr_id, const char *password_str) { size_t password_len; struct nbr_params *nbrp; if (password_str == NULL) { vty_out (vty, "%% Missing password\n"); return (CMD_WARNING_CONFIG_FAILED); } if (bad_addr_v4(lsr_id)) { vty_out (vty, "%% Malformed address\n"); return (CMD_WARNING_CONFIG_FAILED); } nbrp = nbr_params_find(vty_conf, lsr_id); if (negate) { if (nbrp == NULL) return (CMD_SUCCESS); memset(&nbrp->auth, 0, sizeof(nbrp->auth)); nbrp->auth.method = AUTH_NONE; } else { if (nbrp == NULL) { nbrp = nbr_params_new(lsr_id); RB_INSERT(nbrp_head, &vty_conf->nbrp_tree, nbrp); QOBJ_REG(nbrp, nbr_params); } else if (nbrp->auth.method == AUTH_MD5SIG && strcmp(nbrp->auth.md5key, password_str) == 0) return (CMD_SUCCESS); password_len = strlcpy(nbrp->auth.md5key, password_str, sizeof(nbrp->auth.md5key)); if (password_len >= sizeof(nbrp->auth.md5key)) vty_out(vty, "%% password has been truncated to %zu " "characters.", sizeof(nbrp->auth.md5key) - 1); nbrp->auth.md5key_len = password_len; nbrp->auth.method = AUTH_MD5SIG; } ldp_config_apply(vty, vty_conf); return (CMD_SUCCESS); } int ldp_vty_neighbor_ttl_security(struct vty *vty, const char *negate, struct in_addr lsr_id, const char *hops_str) { struct nbr_params *nbrp; long int hops = 0; char *ep; if (bad_addr_v4(lsr_id)) { vty_out (vty, "%% Malformed address\n"); return (CMD_WARNING_CONFIG_FAILED); } if (hops_str) { hops = strtol(hops_str, &ep, 10); if (*ep != '\0' || hops < 1 || hops > 254) { vty_out (vty, "%% Invalid hop count\n"); return (CMD_SUCCESS); } } nbrp = nbr_params_find(vty_conf, lsr_id); if (negate) { if (nbrp == NULL) return (CMD_SUCCESS); nbrp->flags &= ~(F_NBRP_GTSM|F_NBRP_GTSM_HOPS); nbrp->gtsm_enabled = 0; nbrp->gtsm_hops = 0; } else { if (nbrp == NULL) { nbrp = nbr_params_new(lsr_id); RB_INSERT(nbrp_head, &vty_conf->nbrp_tree, nbrp); QOBJ_REG(nbrp, nbr_params); } nbrp->flags |= F_NBRP_GTSM; nbrp->flags &= ~F_NBRP_GTSM_HOPS; if (hops_str) { nbrp->gtsm_enabled = 1; nbrp->gtsm_hops = hops; nbrp->flags |= F_NBRP_GTSM_HOPS; } else nbrp->gtsm_enabled = 0; } ldp_config_apply(vty, vty_conf); return (CMD_SUCCESS); } int ldp_vty_l2vpn(struct vty *vty, const char *negate, const char *name_str) { struct l2vpn *l2vpn; struct l2vpn_if *lif; struct l2vpn_pw *pw; if (name_str == NULL) { vty_out (vty, "%% Missing name\n"); return (CMD_WARNING_CONFIG_FAILED); } l2vpn = l2vpn_find(vty_conf, name_str); if (negate) { if (l2vpn == NULL) return (CMD_SUCCESS); RB_FOREACH(lif, l2vpn_if_head, &l2vpn->if_tree) QOBJ_UNREG(lif); RB_FOREACH(pw, l2vpn_pw_head, &l2vpn->pw_tree) QOBJ_UNREG(pw); RB_FOREACH(pw, l2vpn_pw_head, &l2vpn->pw_inactive_tree) QOBJ_UNREG(pw); QOBJ_UNREG(l2vpn); RB_REMOVE(l2vpn_head, &vty_conf->l2vpn_tree, l2vpn); l2vpn_del(l2vpn); ldp_config_apply(vty, vty_conf); return (CMD_SUCCESS); } if (l2vpn) { VTY_PUSH_CONTEXT(LDP_L2VPN_NODE, l2vpn); return (CMD_SUCCESS); } l2vpn = l2vpn_new(name_str); l2vpn->type = L2VPN_TYPE_VPLS; RB_INSERT(l2vpn_head, &vty_conf->l2vpn_tree, l2vpn); QOBJ_REG(l2vpn, l2vpn); VTY_PUSH_CONTEXT(LDP_L2VPN_NODE, l2vpn); ldp_config_apply(vty, vty_conf); return (CMD_SUCCESS); } int ldp_vty_l2vpn_bridge(struct vty *vty, const char *negate, const char *ifname) { VTY_DECLVAR_CONTEXT(l2vpn, l2vpn); if (negate) memset(l2vpn->br_ifname, 0, sizeof(l2vpn->br_ifname)); else { if (ifname == NULL) { vty_out (vty, "%% Missing IF name\n"); return (CMD_WARNING_CONFIG_FAILED); } strlcpy(l2vpn->br_ifname, ifname, sizeof(l2vpn->br_ifname)); } ldp_config_apply(vty, vty_conf); return (CMD_SUCCESS); } int ldp_vty_l2vpn_mtu(struct vty *vty, const char *negate, long mtu) { VTY_DECLVAR_CONTEXT(l2vpn, l2vpn); if (negate) l2vpn->mtu = DEFAULT_L2VPN_MTU; else l2vpn->mtu = mtu; ldp_config_apply(vty, vty_conf); return (CMD_SUCCESS); } int ldp_vty_l2vpn_pwtype(struct vty *vty, const char *negate, const char *type_str) { VTY_DECLVAR_CONTEXT(l2vpn, l2vpn); int pw_type; if (type_str == NULL) { vty_out (vty, "%% Missing type\n"); return (CMD_WARNING_CONFIG_FAILED); } if (strcmp(type_str, "ethernet") == 0) pw_type = PW_TYPE_ETHERNET; else pw_type = PW_TYPE_ETHERNET_TAGGED; if (negate) l2vpn->pw_type = DEFAULT_PW_TYPE; else l2vpn->pw_type = pw_type; ldp_config_apply(vty, vty_conf); return (CMD_SUCCESS); } int ldp_vty_l2vpn_interface(struct vty *vty, const char *negate, const char *ifname) { VTY_DECLVAR_CONTEXT(l2vpn, l2vpn); struct l2vpn_if *lif; if (ifname == NULL) { vty_out (vty, "%% Missing IF name\n"); return (CMD_WARNING_CONFIG_FAILED); } lif = l2vpn_if_find(l2vpn, ifname); if (negate) { if (lif == NULL) return (CMD_SUCCESS); QOBJ_UNREG(lif); RB_REMOVE(l2vpn_if_head, &l2vpn->if_tree, lif); free(lif); ldp_config_apply(vty, vty_conf); return (CMD_SUCCESS); } if (lif) return (CMD_SUCCESS); if (ldp_iface_is_configured(vty_conf, ifname)) { vty_out (vty, "%% Interface is already in use\n"); return (CMD_SUCCESS); } lif = l2vpn_if_new(l2vpn, ifname); RB_INSERT(l2vpn_if_head, &l2vpn->if_tree, lif); QOBJ_REG(lif, l2vpn_if); ldp_config_apply(vty, vty_conf); return (CMD_SUCCESS); } int ldp_vty_l2vpn_pseudowire(struct vty *vty, const char *negate, const char *ifname) { VTY_DECLVAR_CONTEXT(l2vpn, l2vpn); struct l2vpn_pw *pw; if (ifname == NULL) { vty_out (vty, "%% Missing IF name\n"); return (CMD_WARNING_CONFIG_FAILED); } pw = l2vpn_pw_find(l2vpn, ifname); if (negate) { if (pw == NULL) return (CMD_SUCCESS); QOBJ_UNREG(pw); if (pw->lsr_id.s_addr == INADDR_ANY || pw->pwid == 0) RB_REMOVE(l2vpn_pw_head, &l2vpn->pw_inactive_tree, pw); else RB_REMOVE(l2vpn_pw_head, &l2vpn->pw_tree, pw); free(pw); ldp_config_apply(vty, vty_conf); return (CMD_SUCCESS); } if (pw) { VTY_PUSH_CONTEXT_SUB(LDP_PSEUDOWIRE_NODE, pw); return (CMD_SUCCESS); } if (ldp_iface_is_configured(vty_conf, ifname)) { vty_out (vty, "%% Interface is already in use\n"); return (CMD_SUCCESS); } pw = l2vpn_pw_new(l2vpn, ifname); pw->flags = F_PW_STATUSTLV_CONF|F_PW_CWORD_CONF; RB_INSERT(l2vpn_pw_head, &l2vpn->pw_inactive_tree, pw); QOBJ_REG(pw, l2vpn_pw); VTY_PUSH_CONTEXT_SUB(LDP_PSEUDOWIRE_NODE, pw); ldp_config_apply(vty, vty_conf); return (CMD_SUCCESS); } int ldp_vty_l2vpn_pw_cword(struct vty *vty, const char *negate, const char *preference_str) { VTY_DECLVAR_CONTEXT_SUB(l2vpn_pw, pw); if (negate) pw->flags |= F_PW_CWORD_CONF; else { if (!preference_str) { vty_out (vty, "%% Missing preference\n"); return (CMD_WARNING_CONFIG_FAILED); } if (preference_str[0] == 'e') pw->flags &= ~F_PW_CWORD_CONF; else pw->flags |= F_PW_CWORD_CONF; } ldp_config_apply(vty, vty_conf); return (CMD_SUCCESS); } int ldp_vty_l2vpn_pw_nbr_addr(struct vty *vty, const char *negate, const char *addr_str) { VTY_DECLVAR_CONTEXT_SUB(l2vpn_pw, pw); int af; union ldpd_addr addr; if (ldp_get_address(addr_str, &af, &addr) == -1 || bad_addr(af, &addr)) { vty_out (vty, "%% Malformed address\n"); return (CMD_WARNING_CONFIG_FAILED); } if (negate) { pw->af = AF_UNSPEC; memset(&pw->addr, 0, sizeof(pw->addr)); pw->flags &= ~F_PW_STATIC_NBR_ADDR; } else { pw->af = af; pw->addr = addr; pw->flags |= F_PW_STATIC_NBR_ADDR; } ldp_config_apply(vty, vty_conf); return (CMD_SUCCESS); } int ldp_vty_l2vpn_pw_nbr_id(struct vty *vty, const char *negate, struct in_addr lsr_id) { VTY_DECLVAR_CONTEXT_SUB(l2vpn_pw, pw); if (bad_addr_v4(lsr_id)) { vty_out (vty, "%% Malformed address\n"); return (CMD_WARNING_CONFIG_FAILED); } if (negate) pw->lsr_id.s_addr = INADDR_ANY; else pw->lsr_id = lsr_id; ldp_config_apply(vty, vty_conf); return (CMD_SUCCESS); } int ldp_vty_l2vpn_pw_pwid(struct vty *vty, const char *negate, long pwid) { VTY_DECLVAR_CONTEXT_SUB(l2vpn_pw, pw); if (negate) pw->pwid = 0; else pw->pwid = pwid; ldp_config_apply(vty, vty_conf); return (CMD_SUCCESS); } int ldp_vty_l2vpn_pw_pwstatus(struct vty *vty, const char *negate) { VTY_DECLVAR_CONTEXT_SUB(l2vpn_pw, pw); if (negate) pw->flags |= F_PW_STATUSTLV_CONF; else pw->flags &= ~F_PW_STATUSTLV_CONF; ldp_config_apply(vty, vty_conf); return (CMD_SUCCESS); } struct iface * iface_new_api(struct ldpd_conf *conf, const char *name) { const char *ifname = name; struct iface *iface; if (ldp_iface_is_configured(conf, ifname)) return (NULL); iface = if_new(name); RB_INSERT(iface_head, &conf->iface_tree, iface); QOBJ_REG(iface, iface); return (iface); } void iface_del_api(struct ldpd_conf *conf, struct iface *iface) { QOBJ_UNREG(iface); RB_REMOVE(iface_head, &conf->iface_tree, iface); free(iface); } struct tnbr * tnbr_new_api(struct ldpd_conf *conf, int af, union ldpd_addr *addr) { struct tnbr *tnbr; if (af == AF_INET6 && IN6_IS_SCOPE_EMBED(&addr->v6)) return (NULL); if (tnbr_find(conf, af, addr)) return (NULL); tnbr = tnbr_new(af, addr); tnbr->flags |= F_TNBR_CONFIGURED; RB_INSERT(tnbr_head, &conf->tnbr_tree, tnbr); QOBJ_REG(tnbr, tnbr); return (tnbr); } void tnbr_del_api(struct ldpd_conf *conf, struct tnbr *tnbr) { QOBJ_UNREG(tnbr); RB_REMOVE(tnbr_head, &conf->tnbr_tree, tnbr); free(tnbr); } struct nbr_params * nbrp_new_api(struct ldpd_conf *conf, struct in_addr lsr_id) { struct nbr_params *nbrp; if (nbr_params_find(conf, lsr_id)) return (NULL); nbrp = nbr_params_new(lsr_id); RB_INSERT(nbrp_head, &conf->nbrp_tree, nbrp); QOBJ_REG(nbrp, nbr_params); return (nbrp); } void nbrp_del_api(struct ldpd_conf *conf, struct nbr_params *nbrp) { QOBJ_UNREG(nbrp); RB_REMOVE(nbrp_head, &conf->nbrp_tree, nbrp); free(nbrp); } struct l2vpn * l2vpn_new_api(struct ldpd_conf *conf, const char *name) { struct l2vpn *l2vpn; if (l2vpn_find(conf, name)) return (NULL); l2vpn = l2vpn_new(name); l2vpn->type = L2VPN_TYPE_VPLS; RB_INSERT(l2vpn_head, &conf->l2vpn_tree, l2vpn); QOBJ_REG(l2vpn, l2vpn); return (l2vpn); } void l2vpn_del_api(struct ldpd_conf *conf, struct l2vpn *l2vpn) { struct l2vpn_if *lif; struct l2vpn_pw *pw; while (!RB_EMPTY(l2vpn_if_head, &l2vpn->if_tree)) { lif = RB_ROOT(l2vpn_if_head, &l2vpn->if_tree); QOBJ_UNREG(lif); RB_REMOVE(l2vpn_if_head, &l2vpn->if_tree, lif); free(lif); } while (!RB_EMPTY(l2vpn_pw_head, &l2vpn->pw_tree)) { pw = RB_ROOT(l2vpn_pw_head, &l2vpn->pw_tree); QOBJ_UNREG(pw); RB_REMOVE(l2vpn_pw_head, &l2vpn->pw_tree, pw); free(pw); } while (!RB_EMPTY(l2vpn_pw_head, &l2vpn->pw_inactive_tree)) { pw = RB_ROOT(l2vpn_pw_head, &l2vpn->pw_inactive_tree); QOBJ_UNREG(pw); RB_REMOVE(l2vpn_pw_head, &l2vpn->pw_inactive_tree, pw); free(pw); } QOBJ_UNREG(l2vpn); RB_REMOVE(l2vpn_head, &conf->l2vpn_tree, l2vpn); free(l2vpn); } struct l2vpn_if * l2vpn_if_new_api(struct ldpd_conf *conf, struct l2vpn *l2vpn, const char *ifname) { struct l2vpn_if *lif; if (ldp_iface_is_configured(conf, ifname)) return (NULL); lif = l2vpn_if_new(l2vpn, ifname); RB_INSERT(l2vpn_if_head, &l2vpn->if_tree, lif); QOBJ_REG(lif, l2vpn_if); return (lif); } void l2vpn_if_del_api(struct l2vpn *l2vpn, struct l2vpn_if *lif) { QOBJ_UNREG(lif); RB_REMOVE(l2vpn_if_head, &l2vpn->if_tree, lif); free(lif); } struct l2vpn_pw * l2vpn_pw_new_api(struct ldpd_conf *conf, struct l2vpn *l2vpn, const char *ifname) { struct l2vpn_pw *pw; if (ldp_iface_is_configured(conf, ifname)) return (NULL); pw = l2vpn_pw_new(l2vpn, ifname); pw->flags = F_PW_STATUSTLV_CONF|F_PW_CWORD_CONF; RB_INSERT(l2vpn_pw_head, &l2vpn->pw_inactive_tree, pw); QOBJ_REG(pw, l2vpn_pw); return (pw); } void l2vpn_pw_del_api(struct l2vpn *l2vpn, struct l2vpn_pw *pw) { QOBJ_UNREG(pw); RB_REMOVE(l2vpn_pw_head, &l2vpn->pw_inactive_tree, pw); free(pw); } frr-7.2.1/ldpd/ldp_vty_exec.c0000644000000000000000000015372413610377563013004 00000000000000/* * Copyright (C) 2016 by Open Source Routing. * * This program is free software; you can redistribute it and/or modify * it under the terms of the 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; see the file COPYING; if not, write to the * Free Software Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, * MA 02110-1301 USA */ #include #include #include "ldpd.h" #include "ldpe.h" #include "lde.h" #include "log.h" #include "ldp_vty.h" #include "lib/json.h" #include "command.h" #include "vty.h" #include "mpls.h" enum show_command { SHOW_DISC, SHOW_IFACE, SHOW_NBR, SHOW_LIB, SHOW_L2VPN_PW, SHOW_L2VPN_BINDING }; struct show_params { int family; union ldpd_addr addr; uint8_t prefixlen; int detail; int json; union { struct { struct in_addr lsr_id; int capabilities; } neighbor; struct { struct prefix prefix; int longer_prefixes; struct in_addr neighbor; uint32_t local_label; uint32_t remote_label; } lib; struct { struct in_addr peer; uint32_t local_label; uint32_t remote_label; char ifname[IFNAMSIZ]; uint32_t vcid; } l2vpn; }; }; #define LDPBUFSIZ 65535 static int show_interface_msg(struct vty *, struct imsg *, struct show_params *); static int show_interface_msg_json(struct imsg *, struct show_params *, json_object *); static int show_discovery_msg(struct vty *, struct imsg *, struct show_params *); static void show_discovery_detail_adj(struct vty *, char *, struct ctl_adj *); static int show_discovery_detail_msg(struct vty *, struct imsg *, struct show_params *); static int show_discovery_msg_json(struct imsg *, struct show_params *, json_object *); static void show_discovery_detail_adj_json(json_object *, struct ctl_adj *); static int show_discovery_detail_msg_json(struct imsg *, struct show_params *, json_object *); static int show_nbr_msg(struct vty *, struct imsg *, struct show_params *); static int show_nbr_msg_json(struct imsg *, struct show_params *, json_object *); static void show_nbr_detail_adj(struct vty *, char *, struct ctl_adj *); static int show_nbr_detail_msg(struct vty *, struct imsg *, struct show_params *); static void show_nbr_detail_adj_json(struct ctl_adj *, json_object *); static int show_nbr_detail_msg_json(struct imsg *, struct show_params *, json_object *); static void show_nbr_capabilities(struct vty *, struct ctl_nbr *); static int show_nbr_capabilities_msg(struct vty *, struct imsg *, struct show_params *); static void show_nbr_capabilities_json(struct ctl_nbr *, json_object *); static int show_nbr_capabilities_msg_json(struct imsg *, struct show_params *, json_object *); static int show_lib_msg(struct vty *, struct imsg *, struct show_params *); static int show_lib_detail_msg(struct vty *, struct imsg *, struct show_params *); static int show_lib_msg_json(struct imsg *, struct show_params *, json_object *); static int show_lib_detail_msg_json(struct imsg *, struct show_params *, json_object *); static int show_l2vpn_binding_msg(struct vty *, struct imsg *, struct show_params *); static int show_l2vpn_binding_msg_json(struct imsg *, struct show_params *, json_object *); static int show_l2vpn_pw_msg(struct vty *, struct imsg *, struct show_params *); static int show_l2vpn_pw_msg_json(struct imsg *, struct show_params *, json_object *); static int ldp_vty_connect(struct imsgbuf *); static int ldp_vty_dispatch_msg(struct vty *, struct imsg *, enum show_command, struct show_params *, json_object *); static int ldp_vty_dispatch(struct vty *, struct imsgbuf *, enum show_command, struct show_params *); static int ldp_vty_get_af(const char *, int *); static int show_interface_msg(struct vty *vty, struct imsg *imsg, struct show_params *params) { struct ctl_iface *iface; char timers[BUFSIZ]; switch (imsg->hdr.type) { case IMSG_CTL_SHOW_INTERFACE: iface = imsg->data; if (params->family != AF_UNSPEC && params->family != iface->af) break; snprintf(timers, sizeof(timers), "%u/%u", iface->hello_interval, iface->hello_holdtime); vty_out (vty, "%-4s %-11s %-6s %-8s %-12s %3u\n", af_name(iface->af), iface->name, if_state_name(iface->state), iface->uptime == 0 ? "00:00:00" : log_time(iface->uptime), timers, iface->adj_cnt); break; case IMSG_CTL_END: vty_out (vty, "\n"); return (1); default: break; } return (0); } static int show_interface_msg_json(struct imsg *imsg, struct show_params *params, json_object *json) { struct ctl_iface *iface; json_object *json_iface; char key_name[64]; switch (imsg->hdr.type) { case IMSG_CTL_SHOW_INTERFACE: iface = imsg->data; if (params->family != AF_UNSPEC && params->family != iface->af) break; json_iface = json_object_new_object(); json_object_string_add(json_iface, "name", iface->name); json_object_string_add(json_iface, "addressFamily", af_name(iface->af)); json_object_string_add(json_iface, "state", if_state_name(iface->state)); json_object_string_add(json_iface, "upTime", log_time(iface->uptime)); json_object_int_add(json_iface, "helloInterval", iface->hello_interval); json_object_int_add(json_iface, "helloHoldtime", iface->hello_holdtime); json_object_int_add(json_iface, "adjacencyCount", iface->adj_cnt); sprintf(key_name, "%s: %s", iface->name, af_name(iface->af)); json_object_object_add(json, key_name, json_iface); break; case IMSG_CTL_END: return (1); default: break; } return (0); } static int show_discovery_msg(struct vty *vty, struct imsg *imsg, struct show_params *params) { struct ctl_adj *adj; const char *addr; switch (imsg->hdr.type) { case IMSG_CTL_SHOW_DISCOVERY: adj = imsg->data; if (params->family != AF_UNSPEC && params->family != adj->af) break; vty_out(vty, "%-4s %-15s ", af_name(adj->af), inet_ntoa(adj->id)); switch(adj->type) { case HELLO_LINK: vty_out(vty, "%-8s %-15s ", "Link", adj->ifname); break; case HELLO_TARGETED: addr = log_addr(adj->af, &adj->src_addr); vty_out(vty, "%-8s %-15s ", "Targeted", addr); if (strlen(addr) > 15) vty_out(vty, "\n%46s", " "); break; } vty_out (vty, "%9u\n", adj->holdtime); break; case IMSG_CTL_END: vty_out (vty, "\n"); return (1); default: break; } return (0); } static void show_discovery_detail_adj(struct vty *vty, char *buffer, struct ctl_adj *adj) { size_t buflen = strlen(buffer); snprintf(buffer + buflen, LDPBUFSIZ - buflen, " LSR Id: %s:0\n", inet_ntoa(adj->id)); buflen = strlen(buffer); snprintf(buffer + buflen, LDPBUFSIZ - buflen, " Source address: %s\n", log_addr(adj->af, &adj->src_addr)); buflen = strlen(buffer); snprintf(buffer + buflen, LDPBUFSIZ - buflen, " Transport address: %s\n", log_addr(adj->af, &adj->trans_addr)); buflen = strlen(buffer); snprintf(buffer + buflen, LDPBUFSIZ - buflen, " Hello hold time: %u secs (due in %u secs)\n", adj->holdtime, adj->holdtime_remaining); buflen = strlen(buffer); snprintf(buffer + buflen, LDPBUFSIZ - buflen, " Dual-stack capability TLV: %s\n", (adj->ds_tlv) ? "yes" : "no"); } static int show_discovery_detail_msg(struct vty *vty, struct imsg *imsg, struct show_params *params) { struct ctl_adj *adj; struct ctl_disc_if *iface; struct ctl_disc_tnbr *tnbr; struct in_addr rtr_id; union ldpd_addr *trans_addr; size_t buflen; static char ifaces_buffer[LDPBUFSIZ]; static char tnbrs_buffer[LDPBUFSIZ]; switch (imsg->hdr.type) { case IMSG_CTL_SHOW_DISCOVERY: ifaces_buffer[0] = '\0'; tnbrs_buffer[0] = '\0'; break; case IMSG_CTL_SHOW_DISC_IFACE: iface = imsg->data; if (params->family != AF_UNSPEC && ((params->family == AF_INET && !iface->active_v4) || (params->family == AF_INET6 && !iface->active_v6))) break; buflen = strlen(ifaces_buffer); snprintf(ifaces_buffer + buflen, LDPBUFSIZ - buflen, " %s: %s\n", iface->name, (iface->no_adj) ? "(no adjacencies)" : ""); break; case IMSG_CTL_SHOW_DISC_TNBR: tnbr = imsg->data; if (params->family != AF_UNSPEC && params->family != tnbr->af) break; trans_addr = &(ldp_af_conf_get(ldpd_conf, tnbr->af))->trans_addr; buflen = strlen(tnbrs_buffer); snprintf(tnbrs_buffer + buflen, LDPBUFSIZ - buflen, " %s -> %s: %s\n", log_addr(tnbr->af, trans_addr), log_addr(tnbr->af, &tnbr->addr), (tnbr->no_adj) ? "(no adjacencies)" : ""); break; case IMSG_CTL_SHOW_DISC_ADJ: adj = imsg->data; if (params->family != AF_UNSPEC && params->family != adj->af) break; switch(adj->type) { case HELLO_LINK: show_discovery_detail_adj(vty, ifaces_buffer, adj); break; case HELLO_TARGETED: show_discovery_detail_adj(vty, tnbrs_buffer, adj); break; } break; case IMSG_CTL_END: rtr_id.s_addr = ldp_rtr_id_get(ldpd_conf); vty_out (vty, "Local:\n"); vty_out (vty, " LSR Id: %s:0\n",inet_ntoa(rtr_id)); if (ldpd_conf->ipv4.flags & F_LDPD_AF_ENABLED) vty_out (vty, " Transport Address (IPv4): %s\n", log_addr(AF_INET, &ldpd_conf->ipv4.trans_addr)); if (ldpd_conf->ipv6.flags & F_LDPD_AF_ENABLED) vty_out (vty, " Transport Address (IPv6): %s\n", log_addr(AF_INET6, &ldpd_conf->ipv6.trans_addr)); vty_out (vty, "Discovery Sources:\n"); vty_out (vty, " Interfaces:\n"); vty_out(vty, "%s", ifaces_buffer); vty_out (vty, " Targeted Hellos:\n"); vty_out(vty, "%s", tnbrs_buffer); vty_out (vty, "\n"); return (1); default: break; } return (0); } static int show_discovery_msg_json(struct imsg *imsg, struct show_params *params, json_object *json) { struct ctl_adj *adj; json_object *json_array; json_object *json_adj; switch (imsg->hdr.type) { case IMSG_CTL_SHOW_DISCOVERY: adj = imsg->data; if (params->family != AF_UNSPEC && params->family != adj->af) break; json_object_object_get_ex(json, "adjacencies", &json_array); if (!json_array) { json_array = json_object_new_array(); json_object_object_add(json, "adjacencies", json_array); } json_adj = json_object_new_object(); json_object_string_add(json_adj, "addressFamily", af_name(adj->af)); json_object_string_add(json_adj, "neighborId", inet_ntoa(adj->id)); switch(adj->type) { case HELLO_LINK: json_object_string_add(json_adj, "type", "link"); json_object_string_add(json_adj, "interface", adj->ifname); break; case HELLO_TARGETED: json_object_string_add(json_adj, "type", "targeted"); json_object_string_add(json_adj, "peer", log_addr(adj->af, &adj->src_addr)); break; } json_object_int_add(json_adj, "helloHoldtime", adj->holdtime); json_object_array_add(json_array, json_adj); break; case IMSG_CTL_END: return (1); default: break; } return (0); } static void show_discovery_detail_adj_json(json_object *json, struct ctl_adj *adj) { json_object *json_adj; json_object *json_array; json_object_object_get_ex(json, "adjacencies", &json_array); if (!json_array) { json_array = json_object_new_array(); json_object_object_add(json, "adjacencies", json_array); } json_adj = json_object_new_object(); json_object_string_add(json_adj, "lsrId", inet_ntoa(adj->id)); json_object_string_add(json_adj, "sourceAddress", log_addr(adj->af, &adj->src_addr)); json_object_string_add(json_adj, "transportAddress", log_addr(adj->af, &adj->trans_addr)); json_object_int_add(json_adj, "helloHoldtime", adj->holdtime); json_object_int_add(json_adj, "helloHoldtimeRemaining", adj->holdtime_remaining); json_object_int_add(json_adj, "dualStackCapabilityTlv", adj->ds_tlv); json_object_array_add(json_array, json_adj); } static int show_discovery_detail_msg_json(struct imsg *imsg, struct show_params *params, json_object *json) { struct ctl_adj *adj; struct ctl_disc_if *iface; struct ctl_disc_tnbr *tnbr; struct in_addr rtr_id; union ldpd_addr *trans_addr; json_object *json_interface; json_object *json_target; static json_object *json_interfaces; static json_object *json_targets; static json_object *json_container; switch (imsg->hdr.type) { case IMSG_CTL_SHOW_DISCOVERY: rtr_id.s_addr = ldp_rtr_id_get(ldpd_conf); json_object_string_add(json, "lsrId", inet_ntoa(rtr_id)); if (ldpd_conf->ipv4.flags & F_LDPD_AF_ENABLED) json_object_string_add(json, "transportAddressIPv4", log_addr(AF_INET, &ldpd_conf->ipv4.trans_addr)); if (ldpd_conf->ipv6.flags & F_LDPD_AF_ENABLED) json_object_string_add(json, "transportAddressIPv6", log_addr(AF_INET6, &ldpd_conf->ipv6.trans_addr)); json_interfaces = json_object_new_object(); json_object_object_add(json, "interfaces", json_interfaces); json_targets = json_object_new_object(); json_object_object_add(json, "targetedHellos", json_targets); json_container = NULL; break; case IMSG_CTL_SHOW_DISC_IFACE: iface = imsg->data; if (params->family != AF_UNSPEC && ((params->family == AF_INET && !iface->active_v4) || (params->family == AF_INET6 && !iface->active_v6))) break; json_interface = json_object_new_object(); json_object_object_add(json_interfaces, iface->name, json_interface); json_container = json_interface; break; case IMSG_CTL_SHOW_DISC_TNBR: tnbr = imsg->data; if (params->family != AF_UNSPEC && params->family != tnbr->af) break; trans_addr = &(ldp_af_conf_get(ldpd_conf, tnbr->af))->trans_addr; json_target = json_object_new_object(); json_object_string_add(json_target, "sourceAddress", log_addr(tnbr->af, trans_addr)); json_object_object_add(json_targets, log_addr(tnbr->af, &tnbr->addr), json_target); json_container = json_target; break; case IMSG_CTL_SHOW_DISC_ADJ: adj = imsg->data; if (params->family != AF_UNSPEC && params->family != adj->af) break; switch(adj->type) { case HELLO_LINK: show_discovery_detail_adj_json(json_container, adj); break; case HELLO_TARGETED: show_discovery_detail_adj_json(json_container, adj); break; } break; case IMSG_CTL_END: return (1); default: break; } return (0); } static int show_nbr_msg(struct vty *vty, struct imsg *imsg, struct show_params *params) { struct ctl_nbr *nbr; const char *addr; switch (imsg->hdr.type) { case IMSG_CTL_SHOW_NBR: nbr = imsg->data; addr = log_addr(nbr->af, &nbr->raddr); vty_out(vty, "%-4s %-15s %-11s %-15s", af_name(nbr->af), inet_ntoa(nbr->id), nbr_state_name(nbr->nbr_state), addr); if (strlen(addr) > 15) vty_out(vty, "\n%48s", " "); vty_out (vty, " %8s\n", log_time(nbr->uptime)); break; case IMSG_CTL_END: return (1); default: break; } return (0); } static void show_nbr_detail_adj(struct vty *vty, char *buffer, struct ctl_adj *adj) { size_t buflen = strlen(buffer); switch (adj->type) { case HELLO_LINK: snprintf(buffer + buflen, LDPBUFSIZ - buflen, " Interface: %s\n", adj->ifname); break; case HELLO_TARGETED: snprintf(buffer + buflen, LDPBUFSIZ - buflen, " Targeted Hello: %s\n", log_addr(adj->af, &adj->src_addr)); break; } } static int show_nbr_detail_msg(struct vty *vty, struct imsg *imsg, struct show_params *params) { struct ctl_nbr *nbr; struct ldp_stats *stats; struct ctl_adj *adj; static char v4adjs_buffer[LDPBUFSIZ]; static char v6adjs_buffer[LDPBUFSIZ]; switch (imsg->hdr.type) { case IMSG_CTL_SHOW_NBR: nbr = imsg->data; v4adjs_buffer[0] = '\0'; v6adjs_buffer[0] = '\0'; vty_out (vty, "Peer LDP Identifier: %s:0\n", inet_ntoa(nbr->id)); vty_out (vty, " TCP connection: %s:%u - %s:%u\n", log_addr(nbr->af, &nbr->laddr), ntohs(nbr->lport), log_addr(nbr->af, &nbr->raddr),ntohs(nbr->rport)); vty_out (vty, " Authentication: %s\n", (nbr->auth_method == AUTH_MD5SIG) ? "TCP MD5 Signature" : "none"); vty_out(vty, " Session Holdtime: %u secs; " "KeepAlive interval: %u secs\n", nbr->holdtime, nbr->holdtime / KEEPALIVE_PER_PERIOD); vty_out(vty, " State: %s; Downstream-Unsolicited\n", nbr_state_name(nbr->nbr_state)); vty_out (vty, " Up time: %s\n",log_time(nbr->uptime)); stats = &nbr->stats; vty_out (vty, " Messages sent/rcvd:\n"); vty_out (vty, " - Keepalive Messages: %u/%u\n", stats->kalive_sent, stats->kalive_rcvd); vty_out (vty, " - Address Messages: %u/%u\n", stats->addr_sent, stats->addr_rcvd); vty_out (vty, " - Address Withdraw Messages: %u/%u\n", stats->addrwdraw_sent, stats->addrwdraw_rcvd); vty_out (vty, " - Notification Messages: %u/%u\n", stats->notif_sent, stats->notif_rcvd); vty_out (vty, " - Capability Messages: %u/%u\n", stats->capability_sent, stats->capability_rcvd); vty_out (vty, " - Label Mapping Messages: %u/%u\n", stats->labelmap_sent, stats->labelmap_rcvd); vty_out (vty, " - Label Request Messages: %u/%u\n", stats->labelreq_sent, stats->labelreq_rcvd); vty_out (vty, " - Label Withdraw Messages: %u/%u\n", stats->labelwdraw_sent, stats->labelwdraw_rcvd); vty_out (vty, " - Label Release Messages: %u/%u\n", stats->labelrel_sent, stats->labelrel_rcvd); vty_out (vty, " - Label Abort Request Messages: %u/%u\n", stats->labelabreq_sent, stats->labelabreq_rcvd); show_nbr_capabilities(vty, nbr); break; case IMSG_CTL_SHOW_NBR_DISC: adj = imsg->data; switch (adj->af) { case AF_INET: show_nbr_detail_adj(vty, v4adjs_buffer, adj); break; case AF_INET6: show_nbr_detail_adj(vty, v6adjs_buffer, adj); break; default: fatalx("show_nbr_detail_msg: unknown af"); } break; case IMSG_CTL_SHOW_NBR_END: vty_out (vty, " LDP Discovery Sources:\n"); if (v4adjs_buffer[0] != '\0') { vty_out (vty, " IPv4:\n"); vty_out(vty, "%s", v4adjs_buffer); } if (v6adjs_buffer[0] != '\0') { vty_out (vty, " IPv6:\n"); vty_out(vty, "%s", v6adjs_buffer); } vty_out (vty, "\n"); break; case IMSG_CTL_END: return (1); default: break; } return (0); } static int show_nbr_msg_json(struct imsg *imsg, struct show_params *params, json_object *json) { struct ctl_nbr *nbr; json_object *json_array; json_object *json_nbr; switch (imsg->hdr.type) { case IMSG_CTL_SHOW_NBR: nbr = imsg->data; json_object_object_get_ex(json, "neighbors", &json_array); if (!json_array) { json_array = json_object_new_array(); json_object_object_add(json, "neighbors", json_array); } json_nbr = json_object_new_object(); json_object_string_add(json_nbr, "addressFamily", af_name(nbr->af)); json_object_string_add(json_nbr, "neighborId", inet_ntoa(nbr->id)); json_object_string_add(json_nbr, "state", nbr_state_name(nbr->nbr_state)); json_object_string_add(json_nbr, "transportAddress", log_addr(nbr->af, &nbr->raddr)); json_object_string_add(json_nbr, "upTime", log_time(nbr->uptime)); json_object_array_add(json_array, json_nbr); break; case IMSG_CTL_END: return (1); default: break; } return (0); } static void show_nbr_detail_adj_json(struct ctl_adj *adj, json_object *adj_list) { char adj_string[128]; switch (adj->type) { case HELLO_LINK: strlcpy(adj_string, "interface: ", sizeof(adj_string)); strlcat(adj_string, adj->ifname, sizeof(adj_string)); break; case HELLO_TARGETED: strlcpy(adj_string, "targetedHello: ", sizeof(adj_string)); strlcat(adj_string, log_addr(adj->af, &adj->src_addr), sizeof(adj_string)); break; } json_object_array_add(adj_list, json_object_new_string(adj_string)); } static int show_nbr_detail_msg_json(struct imsg *imsg, struct show_params *params, json_object *json) { struct ctl_nbr *nbr; struct ldp_stats *stats; struct ctl_adj *adj; json_object *json_nbr; json_object *json_array; json_object *json_counter; static json_object *json_nbr_sources; static json_object *json_v4adjs; static json_object *json_v6adjs; switch (imsg->hdr.type) { case IMSG_CTL_SHOW_NBR: nbr = imsg->data; json_nbr = json_object_new_object(); json_object_object_add(json, inet_ntoa(nbr->id), json_nbr); json_object_string_add(json_nbr, "peerId", inet_ntoa(nbr->id)); json_object_string_add(json_nbr, "tcpLocalAddress", log_addr(nbr->af, &nbr->laddr)); json_object_int_add(json_nbr, "tcpLocalPort", ntohs(nbr->lport)); json_object_string_add(json_nbr, "tcpRemoteAddress", log_addr(nbr->af, &nbr->raddr)); json_object_int_add(json_nbr, "tcpRemotePort", ntohs(nbr->rport)); json_object_string_add(json_nbr, "authentication", (nbr->auth_method == AUTH_MD5SIG) ? "TCP MD5 Signature" : "none"); json_object_int_add(json_nbr, "sessionHoldtime", nbr->holdtime); json_object_int_add(json_nbr, "keepAliveInterval", nbr->holdtime / KEEPALIVE_PER_PERIOD); json_object_string_add(json_nbr, "state", nbr_state_name(nbr->nbr_state)); json_object_string_add(json_nbr, "upTime", log_time(nbr->uptime)); /* message_counters */ stats = &nbr->stats; json_array = json_object_new_array(); json_object_object_add(json_nbr, "sentMessages", json_array); json_counter = json_object_new_object(); json_object_int_add(json_counter, "keepalive", stats->kalive_sent); json_object_array_add(json_array, json_counter); json_counter = json_object_new_object(); json_object_int_add(json_counter, "address", stats->addr_sent); json_object_array_add(json_array, json_counter); json_counter = json_object_new_object(); json_object_int_add(json_counter, "addressWithdraw", stats->addrwdraw_sent); json_object_array_add(json_array, json_counter); json_counter = json_object_new_object(); json_object_int_add(json_counter, "notification", stats->notif_sent); json_object_array_add(json_array, json_counter); json_counter = json_object_new_object(); json_object_int_add(json_counter, "capability", stats->capability_sent); json_object_array_add(json_array, json_counter); json_counter = json_object_new_object(); json_object_int_add(json_counter, "labelMapping", stats->labelmap_sent); json_object_array_add(json_array, json_counter); json_counter = json_object_new_object(); json_object_int_add(json_counter, "labelRequest", stats->labelreq_sent); json_object_array_add(json_array, json_counter); json_counter = json_object_new_object(); json_object_int_add(json_counter, "labelWithdraw", stats->labelwdraw_sent); json_object_array_add(json_array, json_counter); json_counter = json_object_new_object(); json_object_int_add(json_counter, "labelRelease", stats->labelrel_sent); json_object_array_add(json_array, json_counter); json_counter = json_object_new_object(); json_object_int_add(json_counter, "labelAbortRequest", stats->labelabreq_sent); json_object_array_add(json_array, json_counter); json_array = json_object_new_array(); json_object_object_add(json_nbr, "receivedMessages", json_array); json_counter = json_object_new_object(); json_object_int_add(json_counter, "keepalive", stats->kalive_rcvd); json_object_array_add(json_array, json_counter); json_counter = json_object_new_object(); json_object_int_add(json_counter, "address", stats->addr_rcvd); json_object_array_add(json_array, json_counter); json_counter = json_object_new_object(); json_object_int_add(json_counter, "addressWithdraw", stats->addrwdraw_rcvd); json_object_array_add(json_array, json_counter); json_counter = json_object_new_object(); json_object_int_add(json_counter, "notification", stats->notif_rcvd); json_object_array_add(json_array, json_counter); json_counter = json_object_new_object(); json_object_int_add(json_counter, "capability", stats->capability_rcvd); json_object_array_add(json_array, json_counter); json_counter = json_object_new_object(); json_object_int_add(json_counter, "labelMapping", stats->labelmap_rcvd); json_object_array_add(json_array, json_counter); json_counter = json_object_new_object(); json_object_int_add(json_counter, "labelRequest", stats->labelreq_rcvd); json_object_array_add(json_array, json_counter); json_counter = json_object_new_object(); json_object_int_add(json_counter, "labelWithdraw", stats->labelwdraw_rcvd); json_object_array_add(json_array, json_counter); json_counter = json_object_new_object(); json_object_int_add(json_counter, "labelRelease", stats->labelrel_rcvd); json_object_array_add(json_array, json_counter); json_counter = json_object_new_object(); json_object_int_add(json_counter, "labelAbortRequest", stats->labelabreq_rcvd); json_object_array_add(json_array, json_counter); /* capabilities */ show_nbr_capabilities_json(nbr, json_nbr); /* discovery sources */ json_nbr_sources = json_object_new_object(); json_object_object_add(json_nbr, "discoverySources", json_nbr_sources); json_v4adjs = NULL; json_v6adjs = NULL; break; case IMSG_CTL_SHOW_NBR_DISC: adj = imsg->data; switch (adj->af) { case AF_INET: if (!json_v4adjs) { json_v4adjs = json_object_new_array(); json_object_object_add(json_nbr_sources, "ipv4", json_v4adjs); } show_nbr_detail_adj_json(adj, json_v4adjs); break; case AF_INET6: if (!json_v6adjs) { json_v6adjs = json_object_new_array(); json_object_object_add(json_nbr_sources, "ipv6", json_v6adjs); } show_nbr_detail_adj_json(adj, json_v6adjs); break; default: fatalx("show_nbr_detail_msg_json: unknown af"); } break; case IMSG_CTL_SHOW_NBR_END: break; case IMSG_CTL_END: return (1); default: break; } return (0); } void show_nbr_capabilities(struct vty *vty, struct ctl_nbr *nbr) { vty_out (vty, " Capabilities Sent:\n" " - Dynamic Announcement (0x0506)\n" " - Typed Wildcard (0x050B)\n" " - Unrecognized Notification (0x0603)\n"); vty_out (vty, " Capabilities Received:\n"); if (nbr->flags & F_NBR_CAP_DYNAMIC) vty_out (vty," - Dynamic Announcement (0x0506)\n"); if (nbr->flags & F_NBR_CAP_TWCARD) vty_out (vty, " - Typed Wildcard (0x050B)\n"); if (nbr->flags & F_NBR_CAP_UNOTIF) vty_out (vty," - Unrecognized Notification (0x0603)\n"); } static int show_nbr_capabilities_msg(struct vty *vty, struct imsg *imsg, struct show_params *params) { struct ctl_nbr *nbr; switch (imsg->hdr.type) { case IMSG_CTL_SHOW_NBR: nbr = imsg->data; if (nbr->nbr_state != NBR_STA_OPER) break; vty_out (vty, "Peer LDP Identifier: %s:0\n", inet_ntoa(nbr->id)); show_nbr_capabilities(vty, nbr); vty_out (vty, "\n"); break; case IMSG_CTL_END: vty_out (vty, "\n"); return (1); default: break; } return (0); } static void show_nbr_capabilities_json(struct ctl_nbr *nbr, json_object *json_nbr) { json_object *json_array; json_object *json_cap; /* sent capabilities */ json_array = json_object_new_array(); json_object_object_add(json_nbr, "sentCapabilities", json_array); /* Dynamic Announcement (0x0506) */ json_cap = json_object_new_object(); json_object_string_add(json_cap, "description", "Dynamic Announcement"); json_object_string_add(json_cap, "tlvType", "0x0506"); json_object_array_add(json_array, json_cap); /* Typed Wildcard (0x050B) */ json_cap = json_object_new_object(); json_object_string_add(json_cap, "description", "Typed Wildcard"); json_object_string_add(json_cap, "tlvType", "0x050B"); json_object_array_add(json_array, json_cap); /* Unrecognized Notification (0x0603) */ json_cap = json_object_new_object(); json_object_string_add(json_cap, "description", "Unrecognized Notification"); json_object_string_add(json_cap, "tlvType", "0x0603"); json_object_array_add(json_array, json_cap); /* received capabilities */ json_array = json_object_new_array(); json_object_object_add(json_nbr, "receivedCapabilities", json_array); /* Dynamic Announcement (0x0506) */ if (nbr->flags & F_NBR_CAP_DYNAMIC) { json_cap = json_object_new_object(); json_object_string_add(json_cap, "description", "Dynamic Announcement"); json_object_string_add(json_cap, "tlvType", "0x0506"); json_object_array_add(json_array, json_cap); } /* Typed Wildcard (0x050B) */ if (nbr->flags & F_NBR_CAP_TWCARD) { json_cap = json_object_new_object(); json_object_string_add(json_cap, "description", "Typed Wildcard"); json_object_string_add(json_cap, "tlvType", "0x050B"); json_object_array_add(json_array, json_cap); } /* Unrecognized Notification (0x0603) */ if (nbr->flags & F_NBR_CAP_UNOTIF) { json_cap = json_object_new_object(); json_object_string_add(json_cap, "description", "Unrecognized Notification"); json_object_string_add(json_cap, "tlvType", "0x0603"); json_object_array_add(json_array, json_cap); } } static int show_nbr_capabilities_msg_json(struct imsg *imsg, struct show_params *params, json_object *json) { struct ctl_nbr *nbr; json_object *json_nbr; switch (imsg->hdr.type) { case IMSG_CTL_SHOW_NBR: nbr = imsg->data; if (nbr->nbr_state != NBR_STA_OPER) break; json_nbr = json_object_new_object(); json_object_object_add(json, inet_ntoa(nbr->id), json_nbr); show_nbr_capabilities_json(nbr, json_nbr); break; case IMSG_CTL_END: return (1); default: break; } return (0); } static int show_lib_msg(struct vty *vty, struct imsg *imsg, struct show_params *params) { struct ctl_rt *rt; char dstnet[BUFSIZ]; switch (imsg->hdr.type) { case IMSG_CTL_SHOW_LIB_BEGIN: rt = imsg->data; if (params->lib.remote_label != NO_LABEL && params->lib.remote_label != rt->remote_label) return (0); /* FALLTHROUGH */ case IMSG_CTL_SHOW_LIB_RCVD: rt = imsg->data; if (imsg->hdr.type == IMSG_CTL_SHOW_LIB_BEGIN && !rt->no_downstream) break; snprintf(dstnet, sizeof(dstnet), "%s/%d", log_addr(rt->af, &rt->prefix), rt->prefixlen); vty_out(vty, "%-4s %-20s", af_name(rt->af), dstnet); if (strlen(dstnet) > 20) vty_out(vty, "\n%25s", " "); vty_out (vty, " %-15s %-11s %-13s %6s\n", inet_ntoa(rt->nexthop), log_label(rt->local_label), log_label(rt->remote_label), rt->in_use ? "yes" : "no"); break; case IMSG_CTL_END: vty_out (vty, "\n"); return (1); default: break; } return (0); } static int show_lib_detail_msg(struct vty *vty, struct imsg *imsg, struct show_params *params) { struct ctl_rt *rt = NULL; static char dstnet[BUFSIZ]; static int upstream, downstream; size_t buflen; static char sent_buffer[LDPBUFSIZ]; static char rcvd_buffer[LDPBUFSIZ]; switch (imsg->hdr.type) { case IMSG_CTL_SHOW_LIB_BEGIN: rt = imsg->data; upstream = 0; downstream = 0; sent_buffer[0] = '\0'; rcvd_buffer[0] = '\0'; snprintf(dstnet, sizeof(dstnet), "%s/%d", log_addr(rt->af, &rt->prefix), rt->prefixlen); break; case IMSG_CTL_SHOW_LIB_SENT: rt = imsg->data; upstream = 1; buflen = strlen(sent_buffer); snprintf(sent_buffer + buflen, LDPBUFSIZ - buflen, "%12s%s:0\n", "", inet_ntoa(rt->nexthop)); break; case IMSG_CTL_SHOW_LIB_RCVD: rt = imsg->data; downstream = 1; buflen = strlen(rcvd_buffer); snprintf(rcvd_buffer + buflen, LDPBUFSIZ - buflen, "%12s%s:0, label %s%s\n", "", inet_ntoa(rt->nexthop), log_label(rt->remote_label), rt->in_use ? " (in use)" : ""); break; case IMSG_CTL_SHOW_LIB_END: rt = imsg->data; if (params->lib.remote_label != NO_LABEL && !downstream) break; vty_out(vty, "%s\n", dstnet); vty_out(vty, "%-8sLocal binding: label: %s\n", "", log_label(rt->local_label)); if (upstream) { vty_out (vty, "%-8sAdvertised to:\n", ""); vty_out(vty, "%s", sent_buffer); } if (downstream) { vty_out (vty, "%-8sRemote bindings:\n", ""); vty_out(vty, "%s", rcvd_buffer); } else vty_out (vty, "%-8sNo remote bindings\n",""); break; case IMSG_CTL_END: vty_out (vty, "\n"); return (1); default: break; } return (0); } static int show_lib_msg_json(struct imsg *imsg, struct show_params *params, json_object *json) { struct ctl_rt *rt; json_object *json_array; json_object *json_lib_entry; char dstnet[BUFSIZ]; switch (imsg->hdr.type) { case IMSG_CTL_SHOW_LIB_BEGIN: case IMSG_CTL_SHOW_LIB_RCVD: rt = imsg->data; if (imsg->hdr.type == IMSG_CTL_SHOW_LIB_BEGIN && !rt->no_downstream) break; json_object_object_get_ex(json, "bindings", &json_array); if (!json_array) { json_array = json_object_new_array(); json_object_object_add(json, "bindings", json_array); } json_lib_entry = json_object_new_object(); json_object_string_add(json_lib_entry, "addressFamily", af_name(rt->af)); snprintf(dstnet, sizeof(dstnet), "%s/%d", log_addr(rt->af, &rt->prefix), rt->prefixlen); json_object_string_add(json_lib_entry, "prefix", dstnet); json_object_string_add(json_lib_entry, "neighborId", inet_ntoa(rt->nexthop)); json_object_string_add(json_lib_entry, "localLabel", log_label(rt->local_label)); json_object_string_add(json_lib_entry, "remoteLabel", log_label(rt->remote_label)); json_object_int_add(json_lib_entry, "inUse", rt->in_use); json_object_array_add(json_array, json_lib_entry); break; case IMSG_CTL_END: return (1); default: break; } return (0); } static int show_lib_detail_msg_json(struct imsg *imsg, struct show_params *params, json_object *json) { struct ctl_rt *rt = NULL; char dstnet[BUFSIZ]; static json_object *json_lib_entry; static json_object *json_adv_labels; json_object *json_adv_label; static json_object *json_remote_labels; json_object *json_remote_label; switch (imsg->hdr.type) { case IMSG_CTL_SHOW_LIB_BEGIN: rt = imsg->data; snprintf(dstnet, sizeof(dstnet), "%s/%d", log_addr(rt->af, &rt->prefix), rt->prefixlen); json_lib_entry = json_object_new_object(); json_object_string_add(json_lib_entry, "localLabel", log_label(rt->local_label)); json_adv_labels = json_object_new_array(); json_object_object_add(json_lib_entry, "advertisedTo", json_adv_labels); json_remote_labels = json_object_new_array(); json_object_object_add(json_lib_entry, "remoteLabels", json_remote_labels); json_object_object_add(json, dstnet, json_lib_entry); break; case IMSG_CTL_SHOW_LIB_SENT: rt = imsg->data; json_adv_label = json_object_new_object(); json_object_string_add(json_adv_label, "neighborId", inet_ntoa(rt->nexthop)); json_object_array_add(json_adv_labels, json_adv_label); break; case IMSG_CTL_SHOW_LIB_RCVD: rt = imsg->data; json_remote_label = json_object_new_object(); json_object_string_add(json_remote_label, "neighborId", inet_ntoa(rt->nexthop)); json_object_string_add(json_remote_label, "label", log_label(rt->remote_label)); json_object_int_add(json_remote_label, "inUse", rt->in_use); json_object_array_add(json_remote_labels, json_remote_label); break; case IMSG_CTL_END: return (1); default: break; } return (0); } static int show_l2vpn_binding_msg(struct vty *vty, struct imsg *imsg, struct show_params *params) { struct ctl_pw *pw; switch (imsg->hdr.type) { case IMSG_CTL_SHOW_L2VPN_BINDING: pw = imsg->data; vty_out (vty, " Destination Address: %s, VC ID: %u\n", inet_ntoa(pw->lsr_id), pw->pwid); /* local binding */ if (pw->local_label != NO_LABEL) { vty_out (vty, " Local Label: %u\n", pw->local_label); vty_out (vty, "%-8sCbit: %u, VC Type: %s, " "GroupID: %u\n", "", pw->local_cword, pw_type_name(pw->type),pw->local_gid); vty_out (vty, "%-8sMTU: %u\n", "",pw->local_ifmtu); } else vty_out (vty," Local Label: unassigned\n"); /* remote binding */ if (pw->remote_label != NO_LABEL) { vty_out (vty, " Remote Label: %u\n", pw->remote_label); vty_out (vty, "%-8sCbit: %u, VC Type: %s, " "GroupID: %u\n", "", pw->remote_cword, pw_type_name(pw->type),pw->remote_gid); vty_out (vty, "%-8sMTU: %u\n", "",pw->remote_ifmtu); } else vty_out (vty," Remote Label: unassigned\n"); break; case IMSG_CTL_END: vty_out (vty, "\n"); return (1); default: break; } return (0); } static int show_l2vpn_binding_msg_json(struct imsg *imsg, struct show_params *params, json_object *json) { struct ctl_pw *pw; json_object *json_pw; char key_name[64]; switch (imsg->hdr.type) { case IMSG_CTL_SHOW_L2VPN_BINDING: pw = imsg->data; json_pw = json_object_new_object(); json_object_string_add(json_pw, "destination", inet_ntoa(pw->lsr_id)); json_object_int_add(json_pw, "vcId", pw->pwid); /* local binding */ if (pw->local_label != NO_LABEL) { json_object_int_add(json_pw, "localLabel", pw->local_label); json_object_int_add(json_pw, "localControlWord", pw->local_cword); json_object_string_add(json_pw, "localVcType", pw_type_name(pw->type)); json_object_int_add(json_pw, "localGroupID", pw->local_gid); json_object_int_add(json_pw, "localIfMtu", pw->local_ifmtu); } else json_object_string_add(json_pw, "localLabel", "unassigned"); /* remote binding */ if (pw->remote_label != NO_LABEL) { json_object_int_add(json_pw, "remoteLabel", pw->remote_label); json_object_int_add(json_pw, "remoteControlWord", pw->remote_cword); json_object_string_add(json_pw, "remoteVcType", pw_type_name(pw->type)); json_object_int_add(json_pw, "remoteGroupID", pw->remote_gid); json_object_int_add(json_pw, "remoteIfMtu", pw->remote_ifmtu); } else json_object_string_add(json_pw, "remoteLabel", "unassigned"); sprintf(key_name, "%s: %u", inet_ntoa(pw->lsr_id), pw->pwid); json_object_object_add(json, key_name, json_pw); break; case IMSG_CTL_END: return (1); default: break; } return (0); } static int show_l2vpn_pw_msg(struct vty *vty, struct imsg *imsg, struct show_params *params) { struct ctl_pw *pw; switch (imsg->hdr.type) { case IMSG_CTL_SHOW_L2VPN_PW: pw = imsg->data; vty_out (vty, "%-9s %-15s %-10u %-16s %-10s\n", pw->ifname, inet_ntoa(pw->lsr_id), pw->pwid, pw->l2vpn_name, (pw->status ? "UP" : "DOWN")); break; case IMSG_CTL_END: vty_out (vty, "\n"); return (1); default: break; } return (0); } static int show_l2vpn_pw_msg_json(struct imsg *imsg, struct show_params *params, json_object *json) { struct ctl_pw *pw; json_object *json_pw; switch (imsg->hdr.type) { case IMSG_CTL_SHOW_L2VPN_PW: pw = imsg->data; json_pw = json_object_new_object(); json_object_string_add(json_pw, "peerId", inet_ntoa(pw->lsr_id)); json_object_int_add(json_pw, "vcId", pw->pwid); json_object_string_add(json_pw, "VpnName", pw->l2vpn_name); if (pw->status) json_object_string_add(json_pw, "status", "up"); else json_object_string_add(json_pw, "status", "down"); json_object_object_add(json, pw->ifname, json_pw); break; case IMSG_CTL_END: return (1); default: break; } return (0); } static int ldp_vty_connect(struct imsgbuf *ibuf) { struct sockaddr_un s_un; int ctl_sock; /* connect to ldpd control socket */ if ((ctl_sock = socket(AF_UNIX, SOCK_STREAM, 0)) == -1) { log_warn("%s: socket", __func__); return (-1); } memset(&s_un, 0, sizeof(s_un)); s_un.sun_family = AF_UNIX; strlcpy(s_un.sun_path, ctl_sock_path, sizeof(s_un.sun_path)); if (connect(ctl_sock, (struct sockaddr *)&s_un, sizeof(s_un)) == -1) { log_warn("%s: connect: %s", __func__, ctl_sock_path); close(ctl_sock); return (-1); } imsg_init(ibuf, ctl_sock); return (0); } static int ldp_vty_dispatch_iface(struct vty *vty, struct imsg *imsg, struct show_params *params, json_object *json) { int ret; if (params->json) ret = show_interface_msg_json(imsg, params, json); else ret = show_interface_msg(vty, imsg, params); return (ret); } static int ldp_vty_dispatch_disc(struct vty *vty, struct imsg *imsg, struct show_params *params, json_object *json) { int ret; if (params->detail) { if (params->json) ret = show_discovery_detail_msg_json(imsg, params, json); else ret = show_discovery_detail_msg(vty, imsg, params); } else { if (params->json) ret = show_discovery_msg_json(imsg, params, json); else ret = show_discovery_msg(vty, imsg, params); } return (ret); } static int ldp_vty_dispatch_nbr(struct vty *vty, struct imsg *imsg, struct show_params *params, json_object *json) { static bool filtered = false; struct ctl_nbr *nbr; int ret; switch (imsg->hdr.type) { case IMSG_CTL_SHOW_NBR: filtered = false; nbr = imsg->data; if (params->neighbor.lsr_id.s_addr != INADDR_ANY && params->neighbor.lsr_id.s_addr != nbr->id.s_addr) { filtered = true; return (0); } break; case IMSG_CTL_SHOW_NBR_DISC: case IMSG_CTL_SHOW_NBR_END: if (filtered) return (0); break; default: break; } if (params->neighbor.capabilities) { if (params->json) ret = show_nbr_capabilities_msg_json(imsg, params, json); else ret = show_nbr_capabilities_msg(vty, imsg, params); } else if (params->detail) { if (params->json) ret = show_nbr_detail_msg_json(imsg, params, json); else ret = show_nbr_detail_msg(vty, imsg, params); } else { if (params->json) ret = show_nbr_msg_json(imsg, params, json); else ret = show_nbr_msg(vty, imsg, params); } return (ret); } static int ldp_vty_dispatch_lib(struct vty *vty, struct imsg *imsg, struct show_params *params, json_object *json) { static bool filtered = false; struct ctl_rt *rt = NULL; struct prefix prefix; int ret; switch (imsg->hdr.type) { case IMSG_CTL_SHOW_LIB_BEGIN: filtered = false; break; case IMSG_CTL_SHOW_LIB_SENT: case IMSG_CTL_SHOW_LIB_RCVD: case IMSG_CTL_SHOW_LIB_END: if (filtered) return (0); break; default: break; } switch (imsg->hdr.type) { case IMSG_CTL_SHOW_LIB_BEGIN: case IMSG_CTL_SHOW_LIB_SENT: case IMSG_CTL_SHOW_LIB_RCVD: case IMSG_CTL_SHOW_LIB_END: rt = imsg->data; if (params->family != AF_UNSPEC && params->family != rt->af) { filtered = true; return (0); } prefix.family = rt->af; prefix.prefixlen = rt->prefixlen; memcpy(&prefix.u.val, &rt->prefix, sizeof(prefix.u.val)); if (params->lib.prefix.family != AF_UNSPEC) { if (!params->lib.longer_prefixes && !prefix_same(¶ms->lib.prefix, &prefix)) { filtered = true; return (0); } else if (params->lib.longer_prefixes && !prefix_match(¶ms->lib.prefix, &prefix)) { filtered = true; return (0); } } if (params->lib.local_label != NO_LABEL && params->lib.local_label != rt->local_label) { filtered = true; return (0); } break; default: break; } switch (imsg->hdr.type) { case IMSG_CTL_SHOW_LIB_SENT: case IMSG_CTL_SHOW_LIB_RCVD: if (params->lib.neighbor.s_addr != INADDR_ANY && params->lib.neighbor.s_addr != rt->nexthop.s_addr) return (0); break; default: break; } switch (imsg->hdr.type) { case IMSG_CTL_SHOW_LIB_RCVD: if (params->lib.remote_label != NO_LABEL && params->lib.remote_label != rt->remote_label) return (0); break; default: break; } if (params->detail) { if (params->json) ret = show_lib_detail_msg_json(imsg, params, json); else ret = show_lib_detail_msg(vty, imsg, params); } else { if (params->json) ret = show_lib_msg_json(imsg, params, json); else ret = show_lib_msg(vty, imsg, params); } return (ret); } static int ldp_vty_dispatch_l2vpn_pw(struct vty *vty, struct imsg *imsg, struct show_params *params, json_object *json) { struct ctl_pw *pw; int ret; switch (imsg->hdr.type) { case IMSG_CTL_SHOW_L2VPN_PW: pw = imsg->data; if (params->l2vpn.peer.s_addr != INADDR_ANY && params->l2vpn.peer.s_addr != pw->lsr_id.s_addr) return (0); if (params->l2vpn.ifname[0] != '\0' && strcmp(params->l2vpn.ifname, pw->ifname)) return (0); if (params->l2vpn.vcid && params->l2vpn.vcid != pw->pwid) return (0); break; default: break; } if (params->json) ret = show_l2vpn_pw_msg_json(imsg, params, json); else ret = show_l2vpn_pw_msg(vty, imsg, params); return (ret); } static int ldp_vty_dispatch_l2vpn_binding(struct vty *vty, struct imsg *imsg, struct show_params *params, json_object *json) { struct ctl_pw *pw; int ret; switch (imsg->hdr.type) { case IMSG_CTL_SHOW_L2VPN_BINDING: pw = imsg->data; if (params->l2vpn.peer.s_addr != INADDR_ANY && params->l2vpn.peer.s_addr != pw->lsr_id.s_addr) return (0); if (params->l2vpn.local_label != NO_LABEL && params->l2vpn.local_label != pw->local_label) return (0); if (params->l2vpn.remote_label != NO_LABEL && params->l2vpn.remote_label != pw->remote_label) return (0); break; default: break; } if (params->json) ret = show_l2vpn_binding_msg_json(imsg, params, json); else ret = show_l2vpn_binding_msg(vty, imsg, params); return (ret); } static int ldp_vty_dispatch_msg(struct vty *vty, struct imsg *imsg, enum show_command cmd, struct show_params *params, json_object *json) { switch (cmd) { case SHOW_IFACE: return (ldp_vty_dispatch_iface(vty, imsg, params, json)); case SHOW_DISC: return (ldp_vty_dispatch_disc(vty, imsg, params, json)); case SHOW_NBR: return (ldp_vty_dispatch_nbr(vty, imsg, params, json)); case SHOW_LIB: return (ldp_vty_dispatch_lib(vty, imsg, params, json)); case SHOW_L2VPN_PW: return (ldp_vty_dispatch_l2vpn_pw(vty, imsg, params, json)); case SHOW_L2VPN_BINDING: return (ldp_vty_dispatch_l2vpn_binding(vty, imsg, params, json)); default: return (0); } } static int ldp_vty_dispatch(struct vty *vty, struct imsgbuf *ibuf, enum show_command cmd, struct show_params *params) { struct imsg imsg; int n, done = 0, ret = CMD_SUCCESS; json_object *json = NULL; while (ibuf->w.queued) if (msgbuf_write(&ibuf->w) <= 0 && errno != EAGAIN) { log_warn("write error"); close(ibuf->fd); return (CMD_WARNING); } if (params->json) json = json_object_new_object(); while (!done) { if ((n = imsg_read(ibuf)) == -1 && errno != EAGAIN) { log_warnx("imsg_read error"); ret = CMD_WARNING; goto done; } if (n == 0) { log_warnx("pipe closed"); ret = CMD_WARNING; goto done; } while (!done) { if ((n = imsg_get(ibuf, &imsg)) == -1) { log_warnx("imsg_get error"); ret = CMD_WARNING; goto done; } if (n == 0) break; done = ldp_vty_dispatch_msg(vty, &imsg, cmd, params, json); imsg_free(&imsg); } } done: close(ibuf->fd); if (json) { vty_out (vty, "%s\n", json_object_to_json_string_ext(json, JSON_C_TO_STRING_PRETTY)); json_object_free(json); } return (ret); } static int ldp_vty_get_af(const char *str, int *af) { if (str == NULL) { *af = AF_UNSPEC; return (0); } else if (strcmp(str, "ipv4") == 0) { *af = AF_INET; return (0); } else if (strcmp(str, "ipv6") == 0) { *af = AF_INET6; return (0); } return (-1); } int ldp_vty_show_binding(struct vty *vty, const char *af_str, const char *prefix, int longer_prefixes, const char *neighbor, unsigned long local_label, unsigned long remote_label, const char *detail, const char *json) { struct imsgbuf ibuf; struct show_params params; int af; if (ldp_vty_connect(&ibuf) < 0) return (CMD_WARNING); if (ldp_vty_get_af(af_str, &af) < 0) return (CMD_ERR_NO_MATCH); memset(¶ms, 0, sizeof(params)); params.family = af; params.detail = (detail) ? 1 : 0; params.json = (json) ? 1 : 0; if (prefix) { (void)str2prefix(prefix, ¶ms.lib.prefix); params.lib.longer_prefixes = longer_prefixes; } if (neighbor && (inet_pton(AF_INET, neighbor, ¶ms.lib.neighbor) != 1 || bad_addr_v4(params.lib.neighbor))) { vty_out (vty, "%% Malformed address\n"); return (CMD_SUCCESS); } params.lib.local_label = local_label; params.lib.remote_label = remote_label; if (!params.detail && !params.json) vty_out (vty, "%-4s %-20s %-15s %-11s %-13s %6s\n", "AF", "Destination", "Nexthop", "Local Label", "Remote Label", "In Use"); imsg_compose(&ibuf, IMSG_CTL_SHOW_LIB, 0, 0, -1, NULL, 0); return (ldp_vty_dispatch(vty, &ibuf, SHOW_LIB, ¶ms)); } int ldp_vty_show_discovery(struct vty *vty, const char *af_str, const char *detail, const char *json) { struct imsgbuf ibuf; struct show_params params; int af; if (ldp_vty_connect(&ibuf) < 0) return (CMD_WARNING); if (ldp_vty_get_af(af_str, &af) < 0) return (CMD_ERR_NO_MATCH); memset(¶ms, 0, sizeof(params)); params.family = af; params.detail = (detail) ? 1 : 0; params.json = (json) ? 1 : 0; if (!params.detail && !params.json) vty_out (vty, "%-4s %-15s %-8s %-15s %9s\n", "AF", "ID", "Type", "Source", "Holdtime"); if (params.detail) imsg_compose(&ibuf, IMSG_CTL_SHOW_DISCOVERY_DTL, 0, 0, -1, NULL, 0); else imsg_compose(&ibuf, IMSG_CTL_SHOW_DISCOVERY, 0, 0, -1, NULL, 0); return (ldp_vty_dispatch(vty, &ibuf, SHOW_DISC, ¶ms)); } int ldp_vty_show_interface(struct vty *vty, const char *af_str, const char *json) { struct imsgbuf ibuf; struct show_params params; unsigned int ifidx = 0; int af; if (ldp_vty_connect(&ibuf) < 0) return (CMD_WARNING); if (ldp_vty_get_af(af_str, &af) < 0) return (CMD_ERR_NO_MATCH); memset(¶ms, 0, sizeof(params)); params.family = af; params.json = (json) ? 1 : 0; /* header */ if (!params.json) { vty_out (vty, "%-4s %-11s %-6s %-8s %-12s %3s\n", "AF", "Interface", "State", "Uptime", "Hello Timers","ac"); } imsg_compose(&ibuf, IMSG_CTL_SHOW_INTERFACE, 0, 0, -1, &ifidx, sizeof(ifidx)); return (ldp_vty_dispatch(vty, &ibuf, SHOW_IFACE, ¶ms)); } int ldp_vty_show_capabilities(struct vty *vty, const char *json) { if (json) { json_object *json; json_object *json_array; json_object *json_cap; json = json_object_new_object(); json_array = json_object_new_array(); json_object_object_add(json, "capabilities", json_array); /* Dynamic Announcement (0x0506) */ json_cap = json_object_new_object(); json_object_string_add(json_cap, "description", "Dynamic Announcement"); json_object_string_add(json_cap, "tlvType", "0x0506"); json_object_array_add(json_array, json_cap); /* Typed Wildcard (0x050B) */ json_cap = json_object_new_object(); json_object_string_add(json_cap, "description", "Typed Wildcard"); json_object_string_add(json_cap, "tlvType", "0x050B"); json_object_array_add(json_array, json_cap); /* Unrecognized Notification (0x0603) */ json_cap = json_object_new_object(); json_object_string_add(json_cap, "description", "Unrecognized Notification"); json_object_string_add(json_cap, "tlvType", "0x0603"); json_object_array_add(json_array, json_cap); vty_out (vty, "%s\n", json_object_to_json_string_ext(json, JSON_C_TO_STRING_PRETTY)); json_object_free(json); return (0); } vty_out (vty, "Supported LDP Capabilities\n" " * Dynamic Announcement (0x0506)\n" " * Typed Wildcard (0x050B)\n" " * Unrecognized Notification (0x0603)\n\n"); return (0); } int ldp_vty_show_neighbor(struct vty *vty, const char *lsr_id, int capabilities, const char *detail, const char *json) { struct imsgbuf ibuf; struct show_params params; if (ldp_vty_connect(&ibuf) < 0) return (CMD_WARNING); memset(¶ms, 0, sizeof(params)); params.detail = (detail) ? 1 : 0; params.json = (json) ? 1 : 0; params.neighbor.capabilities = capabilities; if (lsr_id && (inet_pton(AF_INET, lsr_id, ¶ms.neighbor.lsr_id) != 1 || bad_addr_v4(params.neighbor.lsr_id))) { vty_out (vty, "%% Malformed address\n"); return (CMD_SUCCESS); } if (params.neighbor.capabilities) params.detail = 1; if (!params.detail && !params.json) vty_out (vty, "%-4s %-15s %-11s %-15s %8s\n", "AF", "ID", "State", "Remote Address","Uptime"); imsg_compose(&ibuf, IMSG_CTL_SHOW_NBR, 0, 0, -1, NULL, 0); return (ldp_vty_dispatch(vty, &ibuf, SHOW_NBR, ¶ms)); } int ldp_vty_show_atom_binding(struct vty *vty, const char *peer, unsigned long local_label, unsigned long remote_label, const char *json) { struct imsgbuf ibuf; struct show_params params; if (ldp_vty_connect(&ibuf) < 0) return (CMD_WARNING); memset(¶ms, 0, sizeof(params)); params.json = (json) ? 1 : 0; if (peer && (inet_pton(AF_INET, peer, ¶ms.l2vpn.peer) != 1 || bad_addr_v4(params.l2vpn.peer))) { vty_out (vty, "%% Malformed address\n"); return (CMD_SUCCESS); } params.l2vpn.local_label = local_label; params.l2vpn.remote_label = remote_label; imsg_compose(&ibuf, IMSG_CTL_SHOW_L2VPN_BINDING, 0, 0, -1, NULL, 0); return (ldp_vty_dispatch(vty, &ibuf, SHOW_L2VPN_BINDING, ¶ms)); } int ldp_vty_show_atom_vc(struct vty *vty, const char *peer, const char *ifname, const char *vcid, const char *json) { struct imsgbuf ibuf; struct show_params params; if (ldp_vty_connect(&ibuf) < 0) return (CMD_WARNING); memset(¶ms, 0, sizeof(params)); params.json = (json) ? 1 : 0; if (peer && (inet_pton(AF_INET, peer, ¶ms.l2vpn.peer) != 1 || bad_addr_v4(params.l2vpn.peer))) { vty_out (vty, "%% Malformed address\n"); return (CMD_SUCCESS); } if (ifname) strlcpy(params.l2vpn.ifname, ifname, sizeof(params.l2vpn.ifname)); if (vcid) params.l2vpn.vcid = atoi(vcid); if (!params.json) { /* header */ vty_out (vty, "%-9s %-15s %-10s %-16s %-10s\n", "Interface", "Peer ID", "VC ID", "Name","Status"); vty_out (vty, "%-9s %-15s %-10s %-16s %-10s\n", "---------", "---------------", "----------", "----------------", "----------"); } imsg_compose(&ibuf, IMSG_CTL_SHOW_L2VPN_PW, 0, 0, -1, NULL, 0); return (ldp_vty_dispatch(vty, &ibuf, SHOW_L2VPN_PW, ¶ms)); } int ldp_vty_clear_nbr(struct vty *vty, const char *addr_str) { struct imsgbuf ibuf; struct ctl_nbr nbr; memset(&nbr, 0, sizeof(nbr)); if (addr_str && (ldp_get_address(addr_str, &nbr.af, &nbr.raddr) == -1 || bad_addr(nbr.af, &nbr.raddr))) { vty_out (vty, "%% Malformed address\n"); return (CMD_WARNING); } if (ldp_vty_connect(&ibuf) < 0) return (CMD_WARNING); imsg_compose(&ibuf, IMSG_CTL_CLEAR_NBR, 0, 0, -1, &nbr, sizeof(nbr)); while (ibuf.w.queued) if (msgbuf_write(&ibuf.w) <= 0 && errno != EAGAIN) { log_warn("write error"); close(ibuf.fd); return (CMD_WARNING); } close(ibuf.fd); return (CMD_SUCCESS); } frr-7.2.1/ldpd/ldp_zebra.c0000644000000000000000000003262713610377563012257 00000000000000/* * Copyright (C) 2016 by Open Source Routing. * * This program is free software; you can redistribute it and/or modify * it under the terms of the 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; see the file COPYING; if not, write to the * Free Software Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, * MA 02110-1301 USA */ #include #include "prefix.h" #include "stream.h" #include "memory.h" #include "zclient.h" #include "command.h" #include "network.h" #include "linklist.h" #include "mpls.h" #include "ldpd.h" #include "ldpe.h" #include "lde.h" #include "log.h" #include "ldp_debug.h" static void ifp2kif(struct interface *, struct kif *); static void ifc2kaddr(struct interface *, struct connected *, struct kaddr *); static int zebra_send_mpls_labels(int, struct kroute *); static int ldp_router_id_update(ZAPI_CALLBACK_ARGS); static int ldp_interface_add(ZAPI_CALLBACK_ARGS); static int ldp_interface_delete(ZAPI_CALLBACK_ARGS); static int ldp_interface_status_change(ZAPI_CALLBACK_ARGS); static int ldp_interface_address_add(ZAPI_CALLBACK_ARGS); static int ldp_interface_address_delete(ZAPI_CALLBACK_ARGS); static int ldp_zebra_read_route(ZAPI_CALLBACK_ARGS); static int ldp_zebra_read_pw_status_update(ZAPI_CALLBACK_ARGS); static void ldp_zebra_connected(struct zclient *); static struct zclient *zclient; static void ifp2kif(struct interface *ifp, struct kif *kif) { memset(kif, 0, sizeof(*kif)); strlcpy(kif->ifname, ifp->name, sizeof(kif->ifname)); kif->ifindex = ifp->ifindex; kif->operative = if_is_operative(ifp); if (ifp->ll_type == ZEBRA_LLT_ETHER) memcpy(kif->mac, ifp->hw_addr, ETH_ALEN); } static void ifc2kaddr(struct interface *ifp, struct connected *ifc, struct kaddr *ka) { memset(ka, 0, sizeof(*ka)); strlcpy(ka->ifname, ifp->name, sizeof(ka->ifname)); ka->ifindex = ifp->ifindex; ka->af = ifc->address->family; ka->prefixlen = ifc->address->prefixlen; switch (ka->af) { case AF_INET: ka->addr.v4 = ifc->address->u.prefix4; if (ifc->destination) ka->dstbrd.v4 = ifc->destination->u.prefix4; break; case AF_INET6: ka->addr.v6 = ifc->address->u.prefix6; if (ifc->destination) ka->dstbrd.v6 = ifc->destination->u.prefix6; break; default: break; } } void pw2zpw(struct l2vpn_pw *pw, struct zapi_pw *zpw) { memset(zpw, 0, sizeof(*zpw)); strlcpy(zpw->ifname, pw->ifname, sizeof(zpw->ifname)); zpw->ifindex = pw->ifindex; zpw->type = pw->l2vpn->pw_type; zpw->af = pw->af; zpw->nexthop.ipv6 = pw->addr.v6; zpw->local_label = NO_LABEL; zpw->remote_label = NO_LABEL; if (pw->flags & F_PW_CWORD) zpw->flags = F_PSEUDOWIRE_CWORD; zpw->data.ldp.lsr_id = pw->lsr_id; zpw->data.ldp.pwid = pw->pwid; strlcpy(zpw->data.ldp.vpn_name, pw->l2vpn->name, sizeof(zpw->data.ldp.vpn_name)); } static int zebra_send_mpls_labels(int cmd, struct kroute *kr) { struct stream *s; if (kr->local_label < MPLS_LABEL_RESERVED_MAX || kr->remote_label == NO_LABEL) return (0); debug_zebra_out("prefix %s/%u nexthop %s ifindex %u labels %s/%s (%s)", log_addr(kr->af, &kr->prefix), kr->prefixlen, log_addr(kr->af, &kr->nexthop), kr->ifindex, log_label(kr->local_label), log_label(kr->remote_label), (cmd == ZEBRA_MPLS_LABELS_ADD) ? "add" : "delete"); /* Reset stream. */ s = zclient->obuf; stream_reset(s); zclient_create_header(s, cmd, VRF_DEFAULT); stream_putc(s, ZEBRA_LSP_LDP); stream_putl(s, kr->af); switch (kr->af) { case AF_INET: stream_put_in_addr(s, &kr->prefix.v4); stream_putc(s, kr->prefixlen); stream_put_in_addr(s, &kr->nexthop.v4); break; case AF_INET6: stream_write(s, (uint8_t *)&kr->prefix.v6, 16); stream_putc(s, kr->prefixlen); stream_write(s, (uint8_t *)&kr->nexthop.v6, 16); break; default: fatalx("kr_change: unknown af"); } stream_putl(s, kr->ifindex); stream_putc(s, kr->priority); stream_putl(s, kr->local_label); stream_putl(s, kr->remote_label); /* Put length at the first point of the stream. */ stream_putw_at(s, 0, stream_get_endp(s)); return (zclient_send_message(zclient)); } int kr_change(struct kroute *kr) { return (zebra_send_mpls_labels(ZEBRA_MPLS_LABELS_ADD, kr)); } int kr_delete(struct kroute *kr) { return (zebra_send_mpls_labels(ZEBRA_MPLS_LABELS_DELETE, kr)); } int kmpw_add(struct zapi_pw *zpw) { debug_zebra_out("pseudowire %s nexthop %s (add)", zpw->ifname, log_addr(zpw->af, (union ldpd_addr *)&zpw->nexthop)); return (zebra_send_pw(zclient, ZEBRA_PW_ADD, zpw)); } int kmpw_del(struct zapi_pw *zpw) { debug_zebra_out("pseudowire %s nexthop %s (del)", zpw->ifname, log_addr(zpw->af, (union ldpd_addr *)&zpw->nexthop)); return (zebra_send_pw(zclient, ZEBRA_PW_DELETE, zpw)); } int kmpw_set(struct zapi_pw *zpw) { debug_zebra_out("pseudowire %s nexthop %s labels %u/%u (set)", zpw->ifname, log_addr(zpw->af, (union ldpd_addr *)&zpw->nexthop), zpw->local_label, zpw->remote_label); return (zebra_send_pw(zclient, ZEBRA_PW_SET, zpw)); } int kmpw_unset(struct zapi_pw *zpw) { debug_zebra_out("pseudowire %s nexthop %s (unset)", zpw->ifname, log_addr(zpw->af, (union ldpd_addr *)&zpw->nexthop)); return (zebra_send_pw(zclient, ZEBRA_PW_UNSET, zpw)); } void kif_redistribute(const char *ifname) { struct vrf *vrf = vrf_lookup_by_id(VRF_DEFAULT); struct listnode *cnode; struct interface *ifp; struct connected *ifc; struct kif kif; struct kaddr ka; FOR_ALL_INTERFACES (vrf, ifp) { if (ifname && strcmp(ifname, ifp->name) != 0) continue; ifp2kif(ifp, &kif); main_imsg_compose_both(IMSG_IFSTATUS, &kif, sizeof(kif)); for (ALL_LIST_ELEMENTS_RO(ifp->connected, cnode, ifc)) { ifc2kaddr(ifp, ifc, &ka); main_imsg_compose_ldpe(IMSG_NEWADDR, 0, &ka, sizeof(ka)); } } } static int ldp_router_id_update(ZAPI_CALLBACK_ARGS) { struct prefix router_id; zebra_router_id_update_read(zclient->ibuf, &router_id); if (bad_addr_v4(router_id.u.prefix4)) return (0); debug_zebra_in("router-id update %s", inet_ntoa(router_id.u.prefix4)); global.rtr_id.s_addr = router_id.u.prefix4.s_addr; main_imsg_compose_ldpe(IMSG_RTRID_UPDATE, 0, &global.rtr_id, sizeof(global.rtr_id)); return (0); } static int ldp_interface_add(ZAPI_CALLBACK_ARGS) { struct interface *ifp; struct kif kif; ifp = zebra_interface_add_read(zclient->ibuf, vrf_id); debug_zebra_in("interface add %s index %d mtu %d", ifp->name, ifp->ifindex, ifp->mtu); ifp2kif(ifp, &kif); main_imsg_compose_both(IMSG_IFSTATUS, &kif, sizeof(kif)); return (0); } static int ldp_interface_delete(ZAPI_CALLBACK_ARGS) { struct interface *ifp; struct kif kif; /* zebra_interface_state_read() updates interface structure in iflist */ ifp = zebra_interface_state_read(zclient->ibuf, vrf_id); if (ifp == NULL) return (0); debug_zebra_in("interface delete %s index %d mtu %d", ifp->name, ifp->ifindex, ifp->mtu); /* To support pseudo interface do not free interface structure. */ /* if_delete(ifp); */ if_set_index(ifp, IFINDEX_INTERNAL); ifp2kif(ifp, &kif); main_imsg_compose_both(IMSG_IFSTATUS, &kif, sizeof(kif)); return (0); } static int ldp_interface_status_change(ZAPI_CALLBACK_ARGS) { struct interface *ifp; struct listnode *node; struct connected *ifc; struct kif kif; struct kaddr ka; /* * zebra_interface_state_read() updates interface structure in * iflist. */ ifp = zebra_interface_state_read(zclient->ibuf, vrf_id); if (ifp == NULL) return (0); debug_zebra_in("interface %s state update", ifp->name); ifp2kif(ifp, &kif); main_imsg_compose_both(IMSG_IFSTATUS, &kif, sizeof(kif)); if (if_is_operative(ifp)) { for (ALL_LIST_ELEMENTS_RO(ifp->connected, node, ifc)) { ifc2kaddr(ifp, ifc, &ka); main_imsg_compose_ldpe(IMSG_NEWADDR, 0, &ka, sizeof(ka)); } } else { for (ALL_LIST_ELEMENTS_RO(ifp->connected, node, ifc)) { ifc2kaddr(ifp, ifc, &ka); main_imsg_compose_ldpe(IMSG_DELADDR, 0, &ka, sizeof(ka)); } } return (0); } static int ldp_interface_address_add(ZAPI_CALLBACK_ARGS) { struct connected *ifc; struct interface *ifp; struct kaddr ka; ifc = zebra_interface_address_read(cmd, zclient->ibuf, vrf_id); if (ifc == NULL) return (0); ifp = ifc->ifp; ifc2kaddr(ifp, ifc, &ka); /* Filter invalid addresses. */ if (bad_addr(ka.af, &ka.addr)) return (0); debug_zebra_in("address add %s/%u interface %s", log_addr(ka.af, &ka.addr), ka.prefixlen, ifp->name); /* notify ldpe about new address */ main_imsg_compose_ldpe(IMSG_NEWADDR, 0, &ka, sizeof(ka)); return (0); } static int ldp_interface_address_delete(ZAPI_CALLBACK_ARGS) { struct connected *ifc; struct interface *ifp; struct kaddr ka; ifc = zebra_interface_address_read(cmd, zclient->ibuf, vrf_id); if (ifc == NULL) return (0); ifp = ifc->ifp; ifc2kaddr(ifp, ifc, &ka); connected_free(ifc); /* Filter invalid addresses. */ if (bad_addr(ka.af, &ka.addr)) return (0); debug_zebra_in("address delete %s/%u interface %s", log_addr(ka.af, &ka.addr), ka.prefixlen, ifp->name); /* notify ldpe about removed address */ main_imsg_compose_ldpe(IMSG_DELADDR, 0, &ka, sizeof(ka)); return (0); } static int ldp_zebra_read_route(ZAPI_CALLBACK_ARGS) { struct zapi_route api; struct zapi_nexthop *api_nh; struct kroute kr; int i, add = 0; if (zapi_route_decode(zclient->ibuf, &api) < 0) return -1; /* we completely ignore srcdest routes for now. */ if (CHECK_FLAG(api.message, ZAPI_MESSAGE_SRCPFX)) return (0); memset(&kr, 0, sizeof(kr)); kr.af = api.prefix.family; switch (kr.af) { case AF_INET: kr.prefix.v4 = api.prefix.u.prefix4; break; case AF_INET6: kr.prefix.v6 = api.prefix.u.prefix6; break; default: break; } kr.prefixlen = api.prefix.prefixlen; kr.priority = api.distance; switch (api.type) { case ZEBRA_ROUTE_CONNECT: kr.flags |= F_CONNECTED; break; case ZEBRA_ROUTE_BGP: /* LDP should follow the IGP and ignore BGP routes */ return (0); default: break; } if (bad_addr(kr.af, &kr.prefix) || (kr.af == AF_INET6 && IN6_IS_SCOPE_EMBED(&kr.prefix.v6))) return (0); if (cmd == ZEBRA_REDISTRIBUTE_ROUTE_ADD) add = 1; if (api.nexthop_num == 0) debug_zebra_in("route %s %s/%d (%s)", (add) ? "add" : "delete", log_addr(kr.af, &kr.prefix), kr.prefixlen, zebra_route_string(api.type)); /* loop through all the nexthops */ for (i = 0; i < api.nexthop_num; i++) { api_nh = &api.nexthops[i]; switch (api_nh->type) { case NEXTHOP_TYPE_IPV4: if (kr.af != AF_INET) continue; kr.nexthop.v4 = api_nh->gate.ipv4; kr.ifindex = 0; break; case NEXTHOP_TYPE_IPV4_IFINDEX: if (kr.af != AF_INET) continue; kr.nexthop.v4 = api_nh->gate.ipv4; kr.ifindex = api_nh->ifindex; break; case NEXTHOP_TYPE_IPV6: if (kr.af != AF_INET6) continue; kr.nexthop.v6 = api_nh->gate.ipv6; kr.ifindex = 0; break; case NEXTHOP_TYPE_IPV6_IFINDEX: if (kr.af != AF_INET6) continue; kr.nexthop.v6 = api_nh->gate.ipv6; kr.ifindex = api_nh->ifindex; break; case NEXTHOP_TYPE_IFINDEX: if (!(kr.flags & F_CONNECTED)) continue; break; default: continue; } debug_zebra_in("route %s %s/%d nexthop %s ifindex %u (%s)", (add) ? "add" : "delete", log_addr(kr.af, &kr.prefix), kr.prefixlen, log_addr(kr.af, &kr.nexthop), kr.ifindex, zebra_route_string(api.type)); if (add) main_imsg_compose_lde(IMSG_NETWORK_ADD, 0, &kr, sizeof(kr)); } main_imsg_compose_lde(IMSG_NETWORK_UPDATE, 0, &kr, sizeof(kr)); return (0); } /* * Receive PW status update from Zebra and send it to LDE process. */ static int ldp_zebra_read_pw_status_update(ZAPI_CALLBACK_ARGS) { struct zapi_pw_status zpw; zebra_read_pw_status_update(cmd, zclient, length, vrf_id, &zpw); debug_zebra_in("pseudowire %s status %s", zpw.ifname, (zpw.status == PW_STATUS_UP) ? "up" : "down"); main_imsg_compose_lde(IMSG_PW_UPDATE, 0, &zpw, sizeof(zpw)); return (0); } static void ldp_zebra_connected(struct zclient *zclient) { zclient_send_reg_requests(zclient, VRF_DEFAULT); zebra_redistribute_send(ZEBRA_REDISTRIBUTE_ADD, zclient, AFI_IP, ZEBRA_ROUTE_ALL, 0, VRF_DEFAULT); zebra_redistribute_send(ZEBRA_REDISTRIBUTE_ADD, zclient, AFI_IP6, ZEBRA_ROUTE_ALL, 0, VRF_DEFAULT); } extern struct zebra_privs_t ldpd_privs; void ldp_zebra_init(struct thread_master *master) { /* Set default values. */ zclient = zclient_new(master, &zclient_options_default); zclient_init(zclient, ZEBRA_ROUTE_LDP, 0, &ldpd_privs); /* set callbacks */ zclient->zebra_connected = ldp_zebra_connected; zclient->router_id_update = ldp_router_id_update; zclient->interface_add = ldp_interface_add; zclient->interface_delete = ldp_interface_delete; zclient->interface_up = ldp_interface_status_change; zclient->interface_down = ldp_interface_status_change; zclient->interface_address_add = ldp_interface_address_add; zclient->interface_address_delete = ldp_interface_address_delete; zclient->redistribute_route_add = ldp_zebra_read_route; zclient->redistribute_route_del = ldp_zebra_read_route; zclient->pw_status_update = ldp_zebra_read_pw_status_update; } void ldp_zebra_destroy(void) { zclient_stop(zclient); zclient_free(zclient); zclient = NULL; } frr-7.2.1/ldpd/ldpd.c0000644000000000000000000013520413610377563011233 00000000000000/* $OpenBSD$ */ /* * Copyright (c) 2013, 2016 Renato Westphal * Copyright (c) 2005 Claudio Jeker * Copyright (c) 2004, 2008 Esben Norby * Copyright (c) 2003, 2004 Henning Brauer * * Permission to use, copy, modify, and distribute this software for any * purpose with or without fee is hereby granted, provided that the above * copyright notice and this permission notice appear in all copies. * * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. */ #include #include #include "ldpd.h" #include "ldpe.h" #include "lde.h" #include "log.h" #include "ldp_vty.h" #include "ldp_debug.h" #include #include #include "getopt.h" #include "vty.h" #include "command.h" #include "memory.h" #include "privs.h" #include "sigevent.h" #include "zclient.h" #include "vrf.h" #include "filter.h" #include "qobj.h" #include "libfrr.h" #include "lib_errors.h" static void ldpd_shutdown(void); static pid_t start_child(enum ldpd_process, char *, int, int); static int main_dispatch_ldpe(struct thread *); static int main_dispatch_lde(struct thread *); static int main_imsg_send_ipc_sockets(struct imsgbuf *, struct imsgbuf *); static void main_imsg_send_net_sockets(int); static void main_imsg_send_net_socket(int, enum socket_type); static int main_imsg_send_config(struct ldpd_conf *); static void ldp_config_normalize(struct ldpd_conf *); static void ldp_config_reset(struct ldpd_conf *); static void ldp_config_reset_main(struct ldpd_conf *); static void ldp_config_reset_af(struct ldpd_conf *, int); static void ldp_config_reset_l2vpns(struct ldpd_conf *); static void merge_global(struct ldpd_conf *, struct ldpd_conf *); static void merge_af(int, struct ldpd_af_conf *, struct ldpd_af_conf *); static void merge_ifaces(struct ldpd_conf *, struct ldpd_conf *); static void merge_iface_af(struct iface_af *, struct iface_af *); static void merge_tnbrs(struct ldpd_conf *, struct ldpd_conf *); static void merge_nbrps(struct ldpd_conf *, struct ldpd_conf *); static void merge_l2vpns(struct ldpd_conf *, struct ldpd_conf *); static void merge_l2vpn(struct ldpd_conf *, struct l2vpn *, struct l2vpn *); DEFINE_QOBJ_TYPE(iface) DEFINE_QOBJ_TYPE(tnbr) DEFINE_QOBJ_TYPE(nbr_params) DEFINE_QOBJ_TYPE(l2vpn_if) DEFINE_QOBJ_TYPE(l2vpn_pw) DEFINE_QOBJ_TYPE(l2vpn) DEFINE_QOBJ_TYPE(ldpd_conf) struct ldpd_global global; struct ldpd_init init; struct ldpd_conf *ldpd_conf, *vty_conf; static struct imsgev *iev_ldpe, *iev_ldpe_sync; static struct imsgev *iev_lde, *iev_lde_sync; static pid_t ldpe_pid; static pid_t lde_pid; #define LDP_DEFAULT_CONFIG "ldpd.conf" #define LDP_VTY_PORT 2612 /* Master of threads. */ struct thread_master *master; static struct frr_daemon_info ldpd_di; /* ldpd privileges */ static zebra_capabilities_t _caps_p [] = { ZCAP_BIND, ZCAP_NET_ADMIN }; struct zebra_privs_t ldpd_privs = { #if defined(FRR_USER) && defined(FRR_GROUP) .user = FRR_USER, .group = FRR_GROUP, #endif #if defined(VTY_GROUP) .vty_group = VTY_GROUP, #endif .caps_p = _caps_p, .cap_num_p = array_size(_caps_p), .cap_num_i = 0 }; /* CTL Socket path */ char ctl_sock_path[MAXPATHLEN]; /* LDPd options. */ #define OPTION_CTLSOCK 1001 static struct option longopts[] = { { "ctl_socket", required_argument, NULL, OPTION_CTLSOCK}, { "instance", required_argument, NULL, 'n'}, { 0 } }; /* SIGHUP handler. */ static void sighup(void) { log_info("SIGHUP received"); /* * Do a full configuration reload. In other words, reset vty_conf * and build a new configuartion from scratch. */ ldp_config_reset(vty_conf); vty_read_config(NULL, ldpd_di.config_file, config_default); ldp_config_apply(NULL, vty_conf); } /* SIGINT / SIGTERM handler. */ static void sigint(void) { log_info("SIGINT received"); ldpd_shutdown(); } /* SIGUSR1 handler. */ static void sigusr1(void) { zlog_rotate(); } static struct quagga_signal_t ldp_signals[] = { { .signal = SIGHUP, .handler = &sighup, }, { .signal = SIGINT, .handler = &sigint, }, { .signal = SIGTERM, .handler = &sigint, }, { .signal = SIGUSR1, .handler = &sigusr1, } }; static const struct frr_yang_module_info *ldpd_yang_modules[] = { }; FRR_DAEMON_INFO(ldpd, LDP, .vty_port = LDP_VTY_PORT, .proghelp = "Implementation of the LDP protocol.", .signals = ldp_signals, .n_signals = array_size(ldp_signals), .privs = &ldpd_privs, .yang_modules = ldpd_yang_modules, .n_yang_modules = array_size(ldpd_yang_modules), ) static int ldp_config_fork_apply(struct thread *t) { /* * So the frr_config_fork() function schedules * the read of the vty config( if there is a * non-integrated config ) to be after the * end of startup and we are starting the * main process loop. We need to schedule * the application of this if necessary * after the read in of the config. */ ldp_config_apply(NULL, vty_conf); return 0; } int main(int argc, char *argv[]) { char *saved_argv0; int lflag = 0, eflag = 0; int pipe_parent2ldpe[2], pipe_parent2ldpe_sync[2]; int pipe_parent2lde[2], pipe_parent2lde_sync[2]; char *ctl_sock_name; struct thread *thread = NULL; bool ctl_sock_used = false; snprintf(ctl_sock_path, sizeof(ctl_sock_path), LDPD_SOCKET, "", ""); ldpd_process = PROC_MAIN; log_procname = log_procnames[ldpd_process]; saved_argv0 = argv[0]; if (saved_argv0 == NULL) saved_argv0 = (char *)"ldpd"; frr_preinit(&ldpd_di, argc, argv); frr_opt_add("LEn:", longopts, " --ctl_socket Override ctl socket path\n" " -n, --instance Instance id\n"); /* set default instance (to differentiate ldpd socket from lde one */ init.instance = 1; while (1) { int opt; opt = frr_getopt(argc, argv, NULL); if (opt == EOF) break; switch (opt) { case 0: break; case OPTION_CTLSOCK: ctl_sock_used = true; ctl_sock_name = strrchr(LDPD_SOCKET, '/'); if (ctl_sock_name) /* skip '/' */ ctl_sock_name++; else /* * LDPD_SOCKET configured as relative path * during config? Should really never happen for * sensible config */ ctl_sock_name = (char *)LDPD_SOCKET; strlcpy(ctl_sock_path, optarg, sizeof(ctl_sock_path)); strlcat(ctl_sock_path, "/", sizeof(ctl_sock_path)); strlcat(ctl_sock_path, ctl_sock_name, sizeof(ctl_sock_path)); break; case 'n': init.instance = atoi(optarg); if (init.instance < 1) exit(0); break; case 'L': lflag = 1; break; case 'E': eflag = 1; break; default: frr_help_exit(1); break; } } if (ldpd_di.pathspace && !ctl_sock_used) snprintf(ctl_sock_path, sizeof(ctl_sock_path), LDPD_SOCKET, "/", ldpd_di.pathspace); strlcpy(init.user, ldpd_privs.user, sizeof(init.user)); strlcpy(init.group, ldpd_privs.group, sizeof(init.group)); strlcpy(init.ctl_sock_path, ctl_sock_path, sizeof(init.ctl_sock_path)); strlcpy(init.zclient_serv_path, frr_zclientpath, sizeof(init.zclient_serv_path)); argc -= optind; if (argc > 0 || (lflag && eflag)) frr_help_exit(1); /* check for root privileges */ if (geteuid() != 0) { errno = EPERM; perror(ldpd_di.progname); exit(1); } if (lflag || eflag) openzlog(ldpd_di.progname, "LDP", 0, LOG_CONS | LOG_NDELAY | LOG_PID, LOG_DAEMON); if (lflag) lde(); else if (eflag) ldpe(); if (socketpair(AF_UNIX, SOCK_STREAM, PF_UNSPEC, pipe_parent2ldpe) == -1) fatal("socketpair"); if (socketpair(AF_UNIX, SOCK_STREAM, PF_UNSPEC, pipe_parent2ldpe_sync) == -1) fatal("socketpair"); if (socketpair(AF_UNIX, SOCK_STREAM, PF_UNSPEC, pipe_parent2lde) == -1) fatal("socketpair"); if (socketpair(AF_UNIX, SOCK_STREAM, PF_UNSPEC, pipe_parent2lde_sync) == -1) fatal("socketpair"); sock_set_nonblock(pipe_parent2ldpe[0]); sock_set_cloexec(pipe_parent2ldpe[0]); sock_set_nonblock(pipe_parent2ldpe[1]); sock_set_cloexec(pipe_parent2ldpe[1]); sock_set_nonblock(pipe_parent2ldpe_sync[0]); sock_set_cloexec(pipe_parent2ldpe_sync[0]); sock_set_cloexec(pipe_parent2ldpe_sync[1]); sock_set_nonblock(pipe_parent2lde[0]); sock_set_cloexec(pipe_parent2lde[0]); sock_set_nonblock(pipe_parent2lde[1]); sock_set_cloexec(pipe_parent2lde[1]); sock_set_nonblock(pipe_parent2lde_sync[0]); sock_set_cloexec(pipe_parent2lde_sync[0]); sock_set_cloexec(pipe_parent2lde_sync[1]); /* start children */ lde_pid = start_child(PROC_LDE_ENGINE, saved_argv0, pipe_parent2lde[1], pipe_parent2lde_sync[1]); ldpe_pid = start_child(PROC_LDP_ENGINE, saved_argv0, pipe_parent2ldpe[1], pipe_parent2ldpe_sync[1]); master = frr_init(); vrf_init(NULL, NULL, NULL, NULL, NULL); access_list_init(); ldp_vty_init(); ldp_zebra_init(master); /* * Create base configuration with sane defaults. All configuration * requests (e.g. CLI) act on vty_conf and then call ldp_config_apply() * to merge the changes into ldpd_conf, which contains the actual * running configuration. */ ldpd_conf = config_new_empty(); vty_conf = config_new_empty(); QOBJ_REG(vty_conf, ldpd_conf); /* read configuration file and daemonize */ frr_config_fork(); /* apply configuration */ thread_add_event(master, ldp_config_fork_apply, NULL, 0, &thread); /* setup pipes to children */ if ((iev_ldpe = calloc(1, sizeof(struct imsgev))) == NULL || (iev_ldpe_sync = calloc(1, sizeof(struct imsgev))) == NULL || (iev_lde = calloc(1, sizeof(struct imsgev))) == NULL || (iev_lde_sync = calloc(1, sizeof(struct imsgev))) == NULL) fatal(NULL); imsg_init(&iev_ldpe->ibuf, pipe_parent2ldpe[0]); iev_ldpe->handler_read = main_dispatch_ldpe; iev_ldpe->ev_read = NULL; thread_add_read(master, iev_ldpe->handler_read, iev_ldpe, iev_ldpe->ibuf.fd, &iev_ldpe->ev_read); iev_ldpe->handler_write = ldp_write_handler; imsg_init(&iev_ldpe_sync->ibuf, pipe_parent2ldpe_sync[0]); iev_ldpe_sync->handler_read = main_dispatch_ldpe; iev_ldpe_sync->ev_read = NULL; thread_add_read(master, iev_ldpe_sync->handler_read, iev_ldpe_sync, iev_ldpe_sync->ibuf.fd, &iev_ldpe_sync->ev_read); iev_ldpe_sync->handler_write = ldp_write_handler; imsg_init(&iev_lde->ibuf, pipe_parent2lde[0]); iev_lde->handler_read = main_dispatch_lde; iev_lde->ev_read = NULL; thread_add_read(master, iev_lde->handler_read, iev_lde, iev_lde->ibuf.fd, &iev_lde->ev_read); iev_lde->handler_write = ldp_write_handler; imsg_init(&iev_lde_sync->ibuf, pipe_parent2lde_sync[0]); iev_lde_sync->handler_read = main_dispatch_lde; iev_lde_sync->ev_read = NULL; thread_add_read(master, iev_lde_sync->handler_read, iev_lde_sync, iev_lde_sync->ibuf.fd, &iev_lde_sync->ev_read); iev_lde_sync->handler_write = ldp_write_handler; if (main_imsg_send_ipc_sockets(&iev_ldpe->ibuf, &iev_lde->ibuf)) fatal("could not establish imsg links"); main_imsg_compose_both(IMSG_DEBUG_UPDATE, &ldp_debug, sizeof(ldp_debug)); main_imsg_compose_both(IMSG_INIT, &init, sizeof(init)); main_imsg_send_config(ldpd_conf); if (ldpd_conf->ipv4.flags & F_LDPD_AF_ENABLED) main_imsg_send_net_sockets(AF_INET); if (ldpd_conf->ipv6.flags & F_LDPD_AF_ENABLED) main_imsg_send_net_sockets(AF_INET6); frr_run(master); /* NOTREACHED */ return (0); } static void ldpd_shutdown(void) { pid_t pid; int status; frr_early_fini(); /* close pipes */ msgbuf_clear(&iev_ldpe->ibuf.w); close(iev_ldpe->ibuf.fd); msgbuf_clear(&iev_lde->ibuf.w); close(iev_lde->ibuf.fd); config_clear(ldpd_conf); ldp_config_reset(vty_conf); QOBJ_UNREG(vty_conf); free(vty_conf); log_debug("waiting for children to terminate"); while (true) { /* Wait for child process. */ pid = wait(&status); if (pid == -1) { /* We got interrupted, try again. */ if (errno == EINTR) continue; /* No more processes were found. */ if (errno == ECHILD) break; /* Unhandled errno condition. */ fatal("wait"); /* UNREACHABLE */ } /* We found something, lets announce it. */ if (WIFSIGNALED(status)) log_warnx("%s terminated; signal %d", (pid == lde_pid ? "label decision engine" : "ldp engine"), WTERMSIG(status)); /* Repeat until there are no more child processes. */ } free(iev_ldpe); free(iev_lde); log_info("terminating"); vrf_terminate(); access_list_reset(); ldp_zebra_destroy(); frr_fini(); exit(0); } static pid_t start_child(enum ldpd_process p, char *argv0, int fd_async, int fd_sync) { char *argv[3]; int argc = 0, nullfd; pid_t pid; switch (pid = fork()) { case -1: fatal("cannot fork"); case 0: break; default: close(fd_async); close(fd_sync); return (pid); } nullfd = open("/dev/null", O_RDONLY | O_NOCTTY); if (nullfd == -1) { flog_err_sys(EC_LIB_SYSTEM_CALL, "%s: failed to open /dev/null: %s", __func__, safe_strerror(errno)); } else { dup2(nullfd, 0); dup2(nullfd, 1); dup2(nullfd, 2); close(nullfd); } if (dup2(fd_async, LDPD_FD_ASYNC) == -1) fatal("cannot setup imsg async fd"); if (dup2(fd_sync, LDPD_FD_SYNC) == -1) fatal("cannot setup imsg sync fd"); argv[argc++] = argv0; switch (p) { case PROC_MAIN: fatalx("Can not start main process"); case PROC_LDE_ENGINE: argv[argc++] = (char *)"-L"; break; case PROC_LDP_ENGINE: argv[argc++] = (char *)"-E"; break; } argv[argc++] = NULL; execvp(argv0, argv); fatal("execvp"); } /* imsg handling */ /* ARGSUSED */ static int main_dispatch_ldpe(struct thread *thread) { struct imsgev *iev = THREAD_ARG(thread); struct imsgbuf *ibuf = &iev->ibuf; struct imsg imsg; int af; ssize_t n; int shut = 0; iev->ev_read = NULL; if ((n = imsg_read(ibuf)) == -1 && errno != EAGAIN) fatal("imsg_read error"); if (n == 0) /* connection closed */ shut = 1; for (;;) { if ((n = imsg_get(ibuf, &imsg)) == -1) fatal("imsg_get"); if (n == 0) break; switch (imsg.hdr.type) { case IMSG_LOG: logit(imsg.hdr.pid, "%s", (const char *)imsg.data); break; case IMSG_REQUEST_SOCKETS: af = imsg.hdr.pid; main_imsg_send_net_sockets(af); break; case IMSG_ACL_CHECK: if (imsg.hdr.len != IMSG_HEADER_SIZE + sizeof(struct acl_check)) fatalx("IMSG_ACL_CHECK imsg with wrong len"); ldp_acl_reply(iev, (struct acl_check *)imsg.data); break; default: log_debug("%s: error handling imsg %d", __func__, imsg.hdr.type); break; } imsg_free(&imsg); } if (!shut) imsg_event_add(iev); else { /* this pipe is dead, so remove the event handlers and exit */ THREAD_READ_OFF(iev->ev_read); THREAD_WRITE_OFF(iev->ev_write); ldpe_pid = 0; if (lde_pid == 0) ldpd_shutdown(); else kill(lde_pid, SIGTERM); } return (0); } /* ARGSUSED */ static int main_dispatch_lde(struct thread *thread) { struct imsgev *iev = THREAD_ARG(thread); struct imsgbuf *ibuf = &iev->ibuf; struct imsg imsg; ssize_t n; int shut = 0; iev->ev_read = NULL; if ((n = imsg_read(ibuf)) == -1 && errno != EAGAIN) fatal("imsg_read error"); if (n == 0) /* connection closed */ shut = 1; for (;;) { if ((n = imsg_get(ibuf, &imsg)) == -1) fatal("imsg_get"); if (n == 0) break; switch (imsg.hdr.type) { case IMSG_LOG: logit(imsg.hdr.pid, "%s", (const char *)imsg.data); break; case IMSG_KLABEL_CHANGE: if (imsg.hdr.len - IMSG_HEADER_SIZE != sizeof(struct kroute)) fatalx("invalid size of IMSG_KLABEL_CHANGE"); if (kr_change(imsg.data)) log_warnx("%s: error changing route", __func__); break; case IMSG_KLABEL_DELETE: if (imsg.hdr.len - IMSG_HEADER_SIZE != sizeof(struct kroute)) fatalx("invalid size of IMSG_KLABEL_DELETE"); if (kr_delete(imsg.data)) log_warnx("%s: error deleting route", __func__); break; case IMSG_KPW_ADD: case IMSG_KPW_DELETE: case IMSG_KPW_SET: case IMSG_KPW_UNSET: if (imsg.hdr.len - IMSG_HEADER_SIZE != sizeof(struct zapi_pw)) fatalx("invalid size of IMSG_KPWLABEL_CHANGE"); switch (imsg.hdr.type) { case IMSG_KPW_ADD: if (kmpw_add(imsg.data)) log_warnx("%s: error adding " "pseudowire", __func__); break; case IMSG_KPW_DELETE: if (kmpw_del(imsg.data)) log_warnx("%s: error deleting " "pseudowire", __func__); break; case IMSG_KPW_SET: if (kmpw_set(imsg.data)) log_warnx("%s: error setting " "pseudowire", __func__); break; case IMSG_KPW_UNSET: if (kmpw_unset(imsg.data)) log_warnx("%s: error unsetting " "pseudowire", __func__); break; } break; case IMSG_ACL_CHECK: if (imsg.hdr.len != IMSG_HEADER_SIZE + sizeof(struct acl_check)) fatalx("IMSG_ACL_CHECK imsg with wrong len"); ldp_acl_reply(iev, (struct acl_check *)imsg.data); break; default: log_debug("%s: error handling imsg %d", __func__, imsg.hdr.type); break; } imsg_free(&imsg); } if (!shut) imsg_event_add(iev); else { /* this pipe is dead, so remove the event handlers and exit */ THREAD_READ_OFF(iev->ev_read); THREAD_WRITE_OFF(iev->ev_write); lde_pid = 0; if (ldpe_pid == 0) ldpd_shutdown(); else kill(ldpe_pid, SIGTERM); } return (0); } /* ARGSUSED */ int ldp_write_handler(struct thread *thread) { struct imsgev *iev = THREAD_ARG(thread); struct imsgbuf *ibuf = &iev->ibuf; ssize_t n; iev->ev_write = NULL; if ((n = msgbuf_write(&ibuf->w)) == -1 && errno != EAGAIN) fatal("msgbuf_write"); if (n == 0) { /* this pipe is dead, so remove the event handlers */ THREAD_READ_OFF(iev->ev_read); THREAD_WRITE_OFF(iev->ev_write); return (0); } imsg_event_add(iev); return (0); } void main_imsg_compose_ldpe(int type, pid_t pid, void *data, uint16_t datalen) { if (iev_ldpe == NULL) return; imsg_compose_event(iev_ldpe, type, 0, pid, -1, data, datalen); } void main_imsg_compose_lde(int type, pid_t pid, void *data, uint16_t datalen) { imsg_compose_event(iev_lde, type, 0, pid, -1, data, datalen); } int main_imsg_compose_both(enum imsg_type type, void *buf, uint16_t len) { if (iev_ldpe == NULL || iev_lde == NULL) return (0); if (imsg_compose_event(iev_ldpe, type, 0, 0, -1, buf, len) == -1) return (-1); if (imsg_compose_event(iev_lde, type, 0, 0, -1, buf, len) == -1) return (-1); return (0); } void imsg_event_add(struct imsgev *iev) { if (iev->handler_read) thread_add_read(master, iev->handler_read, iev, iev->ibuf.fd, &iev->ev_read); if (iev->handler_write && iev->ibuf.w.queued) thread_add_write(master, iev->handler_write, iev, iev->ibuf.fd, &iev->ev_write); } int imsg_compose_event(struct imsgev *iev, uint16_t type, uint32_t peerid, pid_t pid, int fd, void *data, uint16_t datalen) { int ret; if ((ret = imsg_compose(&iev->ibuf, type, peerid, pid, fd, data, datalen)) != -1) imsg_event_add(iev); return (ret); } void evbuf_enqueue(struct evbuf *eb, struct ibuf *buf) { ibuf_close(&eb->wbuf, buf); evbuf_event_add(eb); } void evbuf_event_add(struct evbuf *eb) { if (eb->wbuf.queued) thread_add_write(master, eb->handler, eb->arg, eb->wbuf.fd, &eb->ev); } void evbuf_init(struct evbuf *eb, int fd, int (*handler)(struct thread *), void *arg) { msgbuf_init(&eb->wbuf); eb->wbuf.fd = fd; eb->handler = handler; eb->arg = arg; } void evbuf_clear(struct evbuf *eb) { THREAD_WRITE_OFF(eb->ev); msgbuf_clear(&eb->wbuf); eb->wbuf.fd = -1; } static int main_imsg_send_ipc_sockets(struct imsgbuf *ldpe_buf, struct imsgbuf *lde_buf) { int pipe_ldpe2lde[2]; if (socketpair(AF_UNIX, SOCK_STREAM, PF_UNSPEC, pipe_ldpe2lde) == -1) return (-1); sock_set_nonblock(pipe_ldpe2lde[0]); sock_set_nonblock(pipe_ldpe2lde[1]); if (imsg_compose(ldpe_buf, IMSG_SOCKET_IPC, 0, 0, pipe_ldpe2lde[0], NULL, 0) == -1) return (-1); if (imsg_compose(lde_buf, IMSG_SOCKET_IPC, 0, 0, pipe_ldpe2lde[1], NULL, 0) == -1) return (-1); return (0); } static void main_imsg_send_net_sockets(int af) { if (!ldp_addrisset(af, &(ldp_af_conf_get(ldpd_conf, af))->trans_addr)) return; main_imsg_send_net_socket(af, LDP_SOCKET_DISC); main_imsg_send_net_socket(af, LDP_SOCKET_EDISC); main_imsg_send_net_socket(af, LDP_SOCKET_SESSION); imsg_compose_event(iev_ldpe, IMSG_SETUP_SOCKETS, af, 0, -1, NULL, 0); } static void main_imsg_send_net_socket(int af, enum socket_type type) { int fd; fd = ldp_create_socket(af, type); if (fd == -1) { log_warnx("%s: failed to create %s socket for address-family " "%s", __func__, socket_name(type), af_name(af)); return; } imsg_compose_event(iev_ldpe, IMSG_SOCKET_NET, af, 0, fd, &type, sizeof(type)); } int ldp_acl_request(struct imsgev *iev, char *acl_name, int af, union ldpd_addr *addr, uint8_t prefixlen) { struct imsg imsg; ssize_t n; struct acl_check acl_check; if (acl_name[0] == '\0') return FILTER_PERMIT; /* build request */ strlcpy(acl_check.acl, acl_name, sizeof(acl_check.acl)); acl_check.af = af; acl_check.addr = *addr; acl_check.prefixlen = prefixlen; /* send (blocking) */ imsg_compose_event(iev, IMSG_ACL_CHECK, 0, 0, -1, &acl_check, sizeof(acl_check)); imsg_flush(&iev->ibuf); /* receive (blocking) and parse result */ if ((n = imsg_read(&iev->ibuf)) == -1) fatal("imsg_read error"); if ((n = imsg_get(&iev->ibuf, &imsg)) == -1) fatal("imsg_get"); if (imsg.hdr.type != IMSG_ACL_CHECK || imsg.hdr.len != IMSG_HEADER_SIZE + sizeof(int)) fatalx("ldp_acl_request: invalid response"); return (*((int *)imsg.data)); } void ldp_acl_reply(struct imsgev *iev, struct acl_check *acl_check) { struct access_list *alist; struct prefix prefix; int result; alist = access_list_lookup(family2afi(acl_check->af), acl_check->acl); if (alist == NULL) result = FILTER_DENY; else { prefix.family = acl_check->af; switch (prefix.family) { case AF_INET: prefix.u.prefix4 = acl_check->addr.v4; break; case AF_INET6: prefix.u.prefix6 = acl_check->addr.v6; break; default: fatalx("ldp_acl_reply: unknown af"); } prefix.prefixlen = acl_check->prefixlen; result = access_list_apply(alist, &prefix); } imsg_compose_event(iev, IMSG_ACL_CHECK, 0, 0, -1, &result, sizeof(result)); } struct ldpd_af_conf * ldp_af_conf_get(struct ldpd_conf *xconf, int af) { switch (af) { case AF_INET: return (&xconf->ipv4); case AF_INET6: return (&xconf->ipv6); default: fatalx("ldp_af_conf_get: unknown af"); } } struct ldpd_af_global * ldp_af_global_get(struct ldpd_global *xglobal, int af) { switch (af) { case AF_INET: return (&xglobal->ipv4); case AF_INET6: return (&xglobal->ipv6); default: fatalx("ldp_af_global_get: unknown af"); } } int ldp_is_dual_stack(struct ldpd_conf *xconf) { return ((xconf->ipv4.flags & F_LDPD_AF_ENABLED) && (xconf->ipv6.flags & F_LDPD_AF_ENABLED)); } in_addr_t ldp_rtr_id_get(struct ldpd_conf *xconf) { if (xconf->rtr_id.s_addr != INADDR_ANY) return (xconf->rtr_id.s_addr); else return (global.rtr_id.s_addr); } static int main_imsg_send_config(struct ldpd_conf *xconf) { struct iface *iface; struct tnbr *tnbr; struct nbr_params *nbrp; struct l2vpn *l2vpn; struct l2vpn_if *lif; struct l2vpn_pw *pw; if (main_imsg_compose_both(IMSG_RECONF_CONF, xconf, sizeof(*xconf)) == -1) return (-1); RB_FOREACH(iface, iface_head, &xconf->iface_tree) { if (main_imsg_compose_both(IMSG_RECONF_IFACE, iface, sizeof(*iface)) == -1) return (-1); } RB_FOREACH(tnbr, tnbr_head, &xconf->tnbr_tree) { if (main_imsg_compose_both(IMSG_RECONF_TNBR, tnbr, sizeof(*tnbr)) == -1) return (-1); } RB_FOREACH(nbrp, nbrp_head, &xconf->nbrp_tree) { if (main_imsg_compose_both(IMSG_RECONF_NBRP, nbrp, sizeof(*nbrp)) == -1) return (-1); } RB_FOREACH(l2vpn, l2vpn_head, &xconf->l2vpn_tree) { if (main_imsg_compose_both(IMSG_RECONF_L2VPN, l2vpn, sizeof(*l2vpn)) == -1) return (-1); RB_FOREACH(lif, l2vpn_if_head, &l2vpn->if_tree) { if (main_imsg_compose_both(IMSG_RECONF_L2VPN_IF, lif, sizeof(*lif)) == -1) return (-1); } RB_FOREACH(pw, l2vpn_pw_head, &l2vpn->pw_tree) { if (main_imsg_compose_both(IMSG_RECONF_L2VPN_PW, pw, sizeof(*pw)) == -1) return (-1); } RB_FOREACH(pw, l2vpn_pw_head, &l2vpn->pw_inactive_tree) { if (main_imsg_compose_both(IMSG_RECONF_L2VPN_IPW, pw, sizeof(*pw)) == -1) return (-1); } } if (main_imsg_compose_both(IMSG_RECONF_END, NULL, 0) == -1) return (-1); return (0); } int ldp_config_apply(struct vty *vty, struct ldpd_conf *xconf) { /* * When reading from a configuration file (startup and sighup), we * call merge_config() only once after the whole config has been read. * This is the optimal and least disruptive way to update the running * configuration. */ if (vty && vty->type == VTY_FILE) return (0); ldp_config_normalize(xconf); if (main_imsg_send_config(xconf) == -1) return (-1); merge_config(ldpd_conf, xconf); return (0); } static void ldp_config_normalize(struct ldpd_conf *xconf) { struct iface *iface, *itmp; struct nbr_params *nbrp, *ntmp; struct l2vpn *l2vpn; struct l2vpn_pw *pw, *ptmp; if (!(xconf->flags & F_LDPD_ENABLED)) ldp_config_reset_main(xconf); else { if (!(xconf->ipv4.flags & F_LDPD_AF_ENABLED)) ldp_config_reset_af(xconf, AF_INET); if (!(xconf->ipv6.flags & F_LDPD_AF_ENABLED)) ldp_config_reset_af(xconf, AF_INET6); RB_FOREACH_SAFE(iface, iface_head, &xconf->iface_tree, itmp) { if (iface->ipv4.enabled || iface->ipv6.enabled) continue; QOBJ_UNREG(iface); RB_REMOVE(iface_head, &vty_conf->iface_tree, iface); free(iface); } RB_FOREACH_SAFE(nbrp, nbrp_head, &xconf->nbrp_tree, ntmp) { if (nbrp->flags & (F_NBRP_KEEPALIVE|F_NBRP_GTSM)) continue; if (nbrp->auth.method != AUTH_NONE) continue; QOBJ_UNREG(nbrp); RB_REMOVE(nbrp_head, &vty_conf->nbrp_tree, nbrp); free(nbrp); } } RB_FOREACH(l2vpn, l2vpn_head, &xconf->l2vpn_tree) { RB_FOREACH_SAFE(pw, l2vpn_pw_head, &l2vpn->pw_tree, ptmp) { if (!(pw->flags & F_PW_STATIC_NBR_ADDR)) { pw->af = AF_INET; pw->addr.v4 = pw->lsr_id; } if (pw->lsr_id.s_addr != INADDR_ANY && pw->pwid != 0) continue; RB_REMOVE(l2vpn_pw_head, &l2vpn->pw_tree, pw); RB_INSERT(l2vpn_pw_head, &l2vpn->pw_inactive_tree, pw); } RB_FOREACH_SAFE(pw, l2vpn_pw_head, &l2vpn->pw_inactive_tree, ptmp) { if (!(pw->flags & F_PW_STATIC_NBR_ADDR)) { pw->af = AF_INET; pw->addr.v4 = pw->lsr_id; } if (pw->lsr_id.s_addr == INADDR_ANY || pw->pwid == 0) continue; RB_REMOVE(l2vpn_pw_head, &l2vpn->pw_inactive_tree, pw); RB_INSERT(l2vpn_pw_head, &l2vpn->pw_tree, pw); } } } static void ldp_config_reset(struct ldpd_conf *conf) { ldp_config_reset_main(conf); ldp_config_reset_l2vpns(conf); } static void ldp_config_reset_main(struct ldpd_conf *conf) { struct iface *iface; struct nbr_params *nbrp; while (!RB_EMPTY(iface_head, &conf->iface_tree)) { iface = RB_ROOT(iface_head, &conf->iface_tree); QOBJ_UNREG(iface); RB_REMOVE(iface_head, &conf->iface_tree, iface); free(iface); } while (!RB_EMPTY(nbrp_head, &conf->nbrp_tree)) { nbrp = RB_ROOT(nbrp_head, &conf->nbrp_tree); QOBJ_UNREG(nbrp); RB_REMOVE(nbrp_head, &conf->nbrp_tree, nbrp); free(nbrp); } conf->rtr_id.s_addr = INADDR_ANY; ldp_config_reset_af(conf, AF_INET); ldp_config_reset_af(conf, AF_INET6); conf->lhello_holdtime = LINK_DFLT_HOLDTIME; conf->lhello_interval = DEFAULT_HELLO_INTERVAL; conf->thello_holdtime = TARGETED_DFLT_HOLDTIME; conf->thello_interval = DEFAULT_HELLO_INTERVAL; conf->trans_pref = DUAL_STACK_LDPOV6; conf->flags = 0; } static void ldp_config_reset_af(struct ldpd_conf *conf, int af) { struct ldpd_af_conf *af_conf; struct iface *iface; struct iface_af *ia; struct tnbr *tnbr, *ttmp; RB_FOREACH(iface, iface_head, &conf->iface_tree) { ia = iface_af_get(iface, af); ia->enabled = 0; } RB_FOREACH_SAFE(tnbr, tnbr_head, &conf->tnbr_tree, ttmp) { if (tnbr->af != af) continue; QOBJ_UNREG(tnbr); RB_REMOVE(tnbr_head, &conf->tnbr_tree, tnbr); free(tnbr); } af_conf = ldp_af_conf_get(conf, af); af_conf->keepalive = 180; af_conf->lhello_holdtime = 0; af_conf->lhello_interval = 0; af_conf->thello_holdtime = 0; af_conf->thello_interval = 0; memset(&af_conf->trans_addr, 0, sizeof(af_conf->trans_addr)); af_conf->flags = 0; } static void ldp_config_reset_l2vpns(struct ldpd_conf *conf) { struct l2vpn *l2vpn; struct l2vpn_if *lif; struct l2vpn_pw *pw; while (!RB_EMPTY(l2vpn_head, &conf->l2vpn_tree)) { l2vpn = RB_ROOT(l2vpn_head, &conf->l2vpn_tree); while (!RB_EMPTY(l2vpn_if_head, &l2vpn->if_tree)) { lif = RB_ROOT(l2vpn_if_head, &l2vpn->if_tree); QOBJ_UNREG(lif); RB_REMOVE(l2vpn_if_head, &l2vpn->if_tree, lif); free(lif); } while (!RB_EMPTY(l2vpn_pw_head, &l2vpn->pw_tree)) { pw = RB_ROOT(l2vpn_pw_head, &l2vpn->pw_tree); QOBJ_UNREG(pw); RB_REMOVE(l2vpn_pw_head, &l2vpn->pw_tree, pw); free(pw); } while (!RB_EMPTY(l2vpn_pw_head, &l2vpn->pw_inactive_tree)) { pw = RB_ROOT(l2vpn_pw_head, &l2vpn->pw_inactive_tree); QOBJ_UNREG(pw); RB_REMOVE(l2vpn_pw_head, &l2vpn->pw_inactive_tree, pw); free(pw); } QOBJ_UNREG(l2vpn); RB_REMOVE(l2vpn_head, &conf->l2vpn_tree, l2vpn); free(l2vpn); } } void ldp_clear_config(struct ldpd_conf *xconf) { struct iface *iface; struct tnbr *tnbr; struct nbr_params *nbrp; struct l2vpn *l2vpn; while (!RB_EMPTY(iface_head, &xconf->iface_tree)) { iface = RB_ROOT(iface_head, &xconf->iface_tree); RB_REMOVE(iface_head, &xconf->iface_tree, iface); free(iface); } while (!RB_EMPTY(tnbr_head, &xconf->tnbr_tree)) { tnbr = RB_ROOT(tnbr_head, &xconf->tnbr_tree); RB_REMOVE(tnbr_head, &xconf->tnbr_tree, tnbr); free(tnbr); } while (!RB_EMPTY(nbrp_head, &xconf->nbrp_tree)) { nbrp = RB_ROOT(nbrp_head, &xconf->nbrp_tree); RB_REMOVE(nbrp_head, &xconf->nbrp_tree, nbrp); free(nbrp); } while (!RB_EMPTY(l2vpn_head, &xconf->l2vpn_tree)) { l2vpn = RB_ROOT(l2vpn_head, &xconf->l2vpn_tree); RB_REMOVE(l2vpn_head, &xconf->l2vpn_tree, l2vpn); l2vpn_del(l2vpn); } free(xconf); } #define COPY(a, b) do { \ a = malloc(sizeof(*a)); \ if (a == NULL) \ fatal(__func__); \ *a = *b; \ } while (0) void merge_config(struct ldpd_conf *conf, struct ldpd_conf *xconf) { merge_global(conf, xconf); merge_af(AF_INET, &conf->ipv4, &xconf->ipv4); merge_af(AF_INET6, &conf->ipv6, &xconf->ipv6); merge_ifaces(conf, xconf); merge_tnbrs(conf, xconf); merge_nbrps(conf, xconf); merge_l2vpns(conf, xconf); } static void merge_global(struct ldpd_conf *conf, struct ldpd_conf *xconf) { /* change of router-id requires resetting all neighborships */ if (conf->rtr_id.s_addr != xconf->rtr_id.s_addr) { if (ldpd_process == PROC_LDP_ENGINE) { ldpe_reset_nbrs(AF_UNSPEC); if (conf->rtr_id.s_addr == INADDR_ANY || xconf->rtr_id.s_addr == INADDR_ANY) { if_update_all(AF_UNSPEC); tnbr_update_all(AF_UNSPEC); } } conf->rtr_id = xconf->rtr_id; } conf->lhello_holdtime = xconf->lhello_holdtime; conf->lhello_interval = xconf->lhello_interval; conf->thello_holdtime = xconf->thello_holdtime; conf->thello_interval = xconf->thello_interval; if (conf->trans_pref != xconf->trans_pref) { if (ldpd_process == PROC_LDP_ENGINE) ldpe_reset_ds_nbrs(); conf->trans_pref = xconf->trans_pref; } if ((conf->flags & F_LDPD_DS_CISCO_INTEROP) != (xconf->flags & F_LDPD_DS_CISCO_INTEROP)) { if (ldpd_process == PROC_LDP_ENGINE) ldpe_reset_ds_nbrs(); } conf->flags = xconf->flags; } static void merge_af(int af, struct ldpd_af_conf *af_conf, struct ldpd_af_conf *xa) { int stop_init_backoff = 0; int remove_dynamic_tnbrs = 0; int change_egress_label = 0; int reset_nbrs_ipv4 = 0; int reset_nbrs = 0; int update_sockets = 0; /* update timers */ if (af_conf->keepalive != xa->keepalive) { af_conf->keepalive = xa->keepalive; stop_init_backoff = 1; } af_conf->lhello_holdtime = xa->lhello_holdtime; af_conf->lhello_interval = xa->lhello_interval; af_conf->thello_holdtime = xa->thello_holdtime; af_conf->thello_interval = xa->thello_interval; /* update flags */ if ((af_conf->flags & F_LDPD_AF_THELLO_ACCEPT) && !(xa->flags & F_LDPD_AF_THELLO_ACCEPT)) remove_dynamic_tnbrs = 1; if ((af_conf->flags & F_LDPD_AF_NO_GTSM) != (xa->flags & F_LDPD_AF_NO_GTSM)) { if (af == AF_INET6) /* need to set/unset IPV6_MINHOPCOUNT */ update_sockets = 1; else /* for LDPv4 just resetting the neighbors is enough */ reset_nbrs_ipv4 = 1; } if ((af_conf->flags & F_LDPD_AF_EXPNULL) != (xa->flags & F_LDPD_AF_EXPNULL)) change_egress_label = 1; af_conf->flags = xa->flags; /* update the transport address */ if (ldp_addrcmp(af, &af_conf->trans_addr, &xa->trans_addr)) { af_conf->trans_addr = xa->trans_addr; update_sockets = 1; } /* update ACLs */ if (strcmp(af_conf->acl_label_advertise_to, xa->acl_label_advertise_to) || strcmp(af_conf->acl_label_advertise_for, xa->acl_label_advertise_for) || strcmp(af_conf->acl_label_accept_from, xa->acl_label_accept_from) || strcmp(af_conf->acl_label_accept_for, xa->acl_label_accept_for)) reset_nbrs = 1; if (strcmp(af_conf->acl_thello_accept_from, xa->acl_thello_accept_from)) remove_dynamic_tnbrs = 1; if (strcmp(af_conf->acl_label_expnull_for, xa->acl_label_expnull_for)) change_egress_label = 1; strlcpy(af_conf->acl_thello_accept_from, xa->acl_thello_accept_from, sizeof(af_conf->acl_thello_accept_from)); strlcpy(af_conf->acl_label_allocate_for, xa->acl_label_allocate_for, sizeof(af_conf->acl_label_allocate_for)); strlcpy(af_conf->acl_label_advertise_to, xa->acl_label_advertise_to, sizeof(af_conf->acl_label_advertise_to)); strlcpy(af_conf->acl_label_advertise_for, xa->acl_label_advertise_for, sizeof(af_conf->acl_label_advertise_for)); strlcpy(af_conf->acl_label_accept_from, xa->acl_label_accept_from, sizeof(af_conf->acl_label_accept_from)); strlcpy(af_conf->acl_label_accept_for, xa->acl_label_accept_for, sizeof(af_conf->acl_label_accept_for)); strlcpy(af_conf->acl_label_expnull_for, xa->acl_label_expnull_for, sizeof(af_conf->acl_label_expnull_for)); /* apply the new configuration */ switch (ldpd_process) { case PROC_LDE_ENGINE: if (change_egress_label) lde_change_egress_label(af); break; case PROC_LDP_ENGINE: if (stop_init_backoff) ldpe_stop_init_backoff(af); if (remove_dynamic_tnbrs) ldpe_remove_dynamic_tnbrs(af); if (reset_nbrs) ldpe_reset_nbrs(AF_UNSPEC); else if (reset_nbrs_ipv4) ldpe_reset_nbrs(AF_INET); break; case PROC_MAIN: if (update_sockets && iev_ldpe) imsg_compose_event(iev_ldpe, IMSG_CLOSE_SOCKETS, af, 0, -1, NULL, 0); break; } } static void merge_ifaces(struct ldpd_conf *conf, struct ldpd_conf *xconf) { struct iface *iface, *itmp, *xi; RB_FOREACH_SAFE(iface, iface_head, &conf->iface_tree, itmp) { /* find deleted interfaces */ if ((xi = if_lookup_name(xconf, iface->name)) == NULL) { switch (ldpd_process) { case PROC_LDP_ENGINE: ldpe_if_exit(iface); break; case PROC_LDE_ENGINE: case PROC_MAIN: break; } RB_REMOVE(iface_head, &conf->iface_tree, iface); free(iface); } } RB_FOREACH_SAFE(xi, iface_head, &xconf->iface_tree, itmp) { /* find new interfaces */ if ((iface = if_lookup_name(conf, xi->name)) == NULL) { COPY(iface, xi); RB_INSERT(iface_head, &conf->iface_tree, iface); switch (ldpd_process) { case PROC_LDP_ENGINE: ldpe_if_init(iface); break; case PROC_LDE_ENGINE: break; case PROC_MAIN: /* resend addresses to activate new interfaces */ kif_redistribute(iface->name); break; } continue; } /* update existing interfaces */ merge_iface_af(&iface->ipv4, &xi->ipv4); merge_iface_af(&iface->ipv6, &xi->ipv6); } } static void merge_iface_af(struct iface_af *ia, struct iface_af *xi) { if (ia->enabled != xi->enabled) { ia->enabled = xi->enabled; if (ldpd_process == PROC_LDP_ENGINE) ldp_if_update(ia->iface, ia->af); } ia->hello_holdtime = xi->hello_holdtime; ia->hello_interval = xi->hello_interval; } static void merge_tnbrs(struct ldpd_conf *conf, struct ldpd_conf *xconf) { struct tnbr *tnbr, *ttmp, *xt; RB_FOREACH_SAFE(tnbr, tnbr_head, &conf->tnbr_tree, ttmp) { if (!(tnbr->flags & F_TNBR_CONFIGURED)) continue; /* find deleted tnbrs */ if ((xt = tnbr_find(xconf, tnbr->af, &tnbr->addr)) == NULL) { switch (ldpd_process) { case PROC_LDP_ENGINE: tnbr->flags &= ~F_TNBR_CONFIGURED; tnbr_check(conf, tnbr); break; case PROC_LDE_ENGINE: case PROC_MAIN: RB_REMOVE(tnbr_head, &conf->tnbr_tree, tnbr); free(tnbr); break; } } } RB_FOREACH_SAFE(xt, tnbr_head, &xconf->tnbr_tree, ttmp) { /* find new tnbrs */ if ((tnbr = tnbr_find(conf, xt->af, &xt->addr)) == NULL) { COPY(tnbr, xt); RB_INSERT(tnbr_head, &conf->tnbr_tree, tnbr); switch (ldpd_process) { case PROC_LDP_ENGINE: tnbr_update(tnbr); break; case PROC_LDE_ENGINE: case PROC_MAIN: break; } continue; } /* update existing tnbrs */ if (!(tnbr->flags & F_TNBR_CONFIGURED)) tnbr->flags |= F_TNBR_CONFIGURED; } } static void merge_nbrps(struct ldpd_conf *conf, struct ldpd_conf *xconf) { struct nbr_params *nbrp, *ntmp, *xn; struct nbr *nbr; int nbrp_changed; RB_FOREACH_SAFE(nbrp, nbrp_head, &conf->nbrp_tree, ntmp) { /* find deleted nbrps */ if ((xn = nbr_params_find(xconf, nbrp->lsr_id)) == NULL) { switch (ldpd_process) { case PROC_LDP_ENGINE: nbr = nbr_find_ldpid(nbrp->lsr_id.s_addr); if (nbr) { session_shutdown(nbr, S_SHUTDOWN, 0, 0); #ifdef __OpenBSD__ pfkey_remove(nbr); #else sock_set_md5sig( (ldp_af_global_get(&global, nbr->af))->ldp_session_socket, nbr->af, &nbr->raddr, NULL); #endif nbr->auth.method = AUTH_NONE; if (nbr_session_active_role(nbr)) nbr_establish_connection(nbr); } break; case PROC_LDE_ENGINE: case PROC_MAIN: break; } RB_REMOVE(nbrp_head, &conf->nbrp_tree, nbrp); free(nbrp); } } RB_FOREACH_SAFE(xn, nbrp_head, &xconf->nbrp_tree, ntmp) { /* find new nbrps */ if ((nbrp = nbr_params_find(conf, xn->lsr_id)) == NULL) { COPY(nbrp, xn); RB_INSERT(nbrp_head, &conf->nbrp_tree, nbrp); switch (ldpd_process) { case PROC_LDP_ENGINE: nbr = nbr_find_ldpid(nbrp->lsr_id.s_addr); if (nbr) { session_shutdown(nbr, S_SHUTDOWN, 0, 0); nbr->auth.method = nbrp->auth.method; #ifdef __OpenBSD__ if (pfkey_establish(nbr, nbrp) == -1) fatalx("pfkey setup failed"); #else sock_set_md5sig( (ldp_af_global_get(&global, nbr->af))->ldp_session_socket, nbr->af, &nbr->raddr, nbrp->auth.md5key); #endif if (nbr_session_active_role(nbr)) nbr_establish_connection(nbr); } break; case PROC_LDE_ENGINE: case PROC_MAIN: break; } continue; } /* update existing nbrps */ if (nbrp->flags != xn->flags || nbrp->keepalive != xn->keepalive || nbrp->gtsm_enabled != xn->gtsm_enabled || nbrp->gtsm_hops != xn->gtsm_hops || nbrp->auth.method != xn->auth.method || strcmp(nbrp->auth.md5key, xn->auth.md5key) != 0) nbrp_changed = 1; else nbrp_changed = 0; nbrp->keepalive = xn->keepalive; nbrp->gtsm_enabled = xn->gtsm_enabled; nbrp->gtsm_hops = xn->gtsm_hops; nbrp->auth.method = xn->auth.method; strlcpy(nbrp->auth.md5key, xn->auth.md5key, sizeof(nbrp->auth.md5key)); nbrp->auth.md5key_len = xn->auth.md5key_len; nbrp->flags = xn->flags; if (ldpd_process == PROC_LDP_ENGINE) { nbr = nbr_find_ldpid(nbrp->lsr_id.s_addr); if (nbr && nbrp_changed) { session_shutdown(nbr, S_SHUTDOWN, 0, 0); #ifdef __OpenBSD__ pfkey_remove(nbr); nbr->auth.method = nbrp->auth.method; if (pfkey_establish(nbr, nbrp) == -1) fatalx("pfkey setup failed"); #else nbr->auth.method = nbrp->auth.method; sock_set_md5sig((ldp_af_global_get(&global, nbr->af))->ldp_session_socket, nbr->af, &nbr->raddr, nbrp->auth.md5key); #endif if (nbr_session_active_role(nbr)) nbr_establish_connection(nbr); } } } } static void merge_l2vpns(struct ldpd_conf *conf, struct ldpd_conf *xconf) { struct l2vpn *l2vpn, *ltmp, *xl; RB_FOREACH_SAFE(l2vpn, l2vpn_head, &conf->l2vpn_tree, ltmp) { /* find deleted l2vpns */ if ((xl = l2vpn_find(xconf, l2vpn->name)) == NULL) { switch (ldpd_process) { case PROC_LDE_ENGINE: l2vpn_exit(l2vpn); break; case PROC_LDP_ENGINE: ldpe_l2vpn_exit(l2vpn); break; case PROC_MAIN: break; } RB_REMOVE(l2vpn_head, &conf->l2vpn_tree, l2vpn); l2vpn_del(l2vpn); } } RB_FOREACH_SAFE(xl, l2vpn_head, &xconf->l2vpn_tree, ltmp) { /* find new l2vpns */ if ((l2vpn = l2vpn_find(conf, xl->name)) == NULL) { COPY(l2vpn, xl); RB_INSERT(l2vpn_head, &conf->l2vpn_tree, l2vpn); RB_INIT(l2vpn_if_head, &l2vpn->if_tree); RB_INIT(l2vpn_pw_head, &l2vpn->pw_tree); RB_INIT(l2vpn_pw_head, &l2vpn->pw_inactive_tree); switch (ldpd_process) { case PROC_LDE_ENGINE: l2vpn_init(l2vpn); break; case PROC_LDP_ENGINE: ldpe_l2vpn_init(l2vpn); break; case PROC_MAIN: break; } } /* update existing l2vpns */ merge_l2vpn(conf, l2vpn, xl); } } static void merge_l2vpn(struct ldpd_conf *xconf, struct l2vpn *l2vpn, struct l2vpn *xl) { struct l2vpn_if *lif, *ftmp, *xf; struct l2vpn_pw *pw, *ptmp, *xp; struct nbr *nbr; int reset_nbr, reinstall_pwfec, reinstall_tnbr; int previous_pw_type, previous_mtu; previous_pw_type = l2vpn->pw_type; previous_mtu = l2vpn->mtu; /* merge intefaces */ RB_FOREACH_SAFE(lif, l2vpn_if_head, &l2vpn->if_tree, ftmp) { /* find deleted interfaces */ if ((xf = l2vpn_if_find(xl, lif->ifname)) == NULL) { RB_REMOVE(l2vpn_if_head, &l2vpn->if_tree, lif); free(lif); } } RB_FOREACH_SAFE(xf, l2vpn_if_head, &xl->if_tree, ftmp) { /* find new interfaces */ if ((lif = l2vpn_if_find(l2vpn, xf->ifname)) == NULL) { COPY(lif, xf); RB_INSERT(l2vpn_if_head, &l2vpn->if_tree, lif); lif->l2vpn = l2vpn; switch (ldpd_process) { case PROC_LDP_ENGINE: case PROC_LDE_ENGINE: break; case PROC_MAIN: kif_redistribute(lif->ifname); break; } } } /* merge active pseudowires */ RB_FOREACH_SAFE(pw, l2vpn_pw_head, &l2vpn->pw_tree, ptmp) { /* find deleted active pseudowires */ if ((xp = l2vpn_pw_find_active(xl, pw->ifname)) == NULL) { switch (ldpd_process) { case PROC_LDE_ENGINE: l2vpn_pw_exit(pw); break; case PROC_LDP_ENGINE: ldpe_l2vpn_pw_exit(pw); break; case PROC_MAIN: break; } RB_REMOVE(l2vpn_pw_head, &l2vpn->pw_tree, pw); free(pw); } } RB_FOREACH_SAFE(xp, l2vpn_pw_head, &xl->pw_tree, ptmp) { /* find new active pseudowires */ if ((pw = l2vpn_pw_find_active(l2vpn, xp->ifname)) == NULL) { COPY(pw, xp); RB_INSERT(l2vpn_pw_head, &l2vpn->pw_tree, pw); pw->l2vpn = l2vpn; switch (ldpd_process) { case PROC_LDE_ENGINE: l2vpn_pw_init(pw); break; case PROC_LDP_ENGINE: ldpe_l2vpn_pw_init(pw); break; case PROC_MAIN: kif_redistribute(pw->ifname); break; } continue; } /* update existing active pseudowire */ if (pw->af != xp->af || ldp_addrcmp(pw->af, &pw->addr, &xp->addr)) reinstall_tnbr = 1; else reinstall_tnbr = 0; /* changes that require a session restart */ if ((pw->flags & (F_PW_STATUSTLV_CONF|F_PW_CWORD_CONF)) != (xp->flags & (F_PW_STATUSTLV_CONF|F_PW_CWORD_CONF))) reset_nbr = 1; else reset_nbr = 0; if (l2vpn->pw_type != xl->pw_type || l2vpn->mtu != xl->mtu || pw->pwid != xp->pwid || reinstall_tnbr || reset_nbr || pw->lsr_id.s_addr != xp->lsr_id.s_addr) reinstall_pwfec = 1; else reinstall_pwfec = 0; if (ldpd_process == PROC_LDP_ENGINE) { if (reinstall_tnbr) ldpe_l2vpn_pw_exit(pw); if (reset_nbr) { nbr = nbr_find_ldpid(pw->lsr_id.s_addr); if (nbr && nbr->state == NBR_STA_OPER) session_shutdown(nbr, S_SHUTDOWN, 0, 0); } } if (ldpd_process == PROC_LDE_ENGINE && reinstall_pwfec) l2vpn_pw_exit(pw); pw->lsr_id = xp->lsr_id; pw->af = xp->af; pw->addr = xp->addr; pw->pwid = xp->pwid; strlcpy(pw->ifname, xp->ifname, sizeof(pw->ifname)); pw->ifindex = xp->ifindex; if (xp->flags & F_PW_CWORD_CONF) pw->flags |= F_PW_CWORD_CONF; else pw->flags &= ~F_PW_CWORD_CONF; if (xp->flags & F_PW_STATUSTLV_CONF) pw->flags |= F_PW_STATUSTLV_CONF; else pw->flags &= ~F_PW_STATUSTLV_CONF; if (xp->flags & F_PW_STATIC_NBR_ADDR) pw->flags |= F_PW_STATIC_NBR_ADDR; else pw->flags &= ~F_PW_STATIC_NBR_ADDR; if (ldpd_process == PROC_LDP_ENGINE && reinstall_tnbr) ldpe_l2vpn_pw_init(pw); if (ldpd_process == PROC_LDE_ENGINE && reinstall_pwfec) { l2vpn->pw_type = xl->pw_type; l2vpn->mtu = xl->mtu; l2vpn_pw_init(pw); l2vpn->pw_type = previous_pw_type; l2vpn->mtu = previous_mtu; } } /* merge inactive pseudowires */ RB_FOREACH_SAFE(pw, l2vpn_pw_head, &l2vpn->pw_inactive_tree, ptmp) { /* find deleted inactive pseudowires */ if ((xp = l2vpn_pw_find_inactive(xl, pw->ifname)) == NULL) { RB_REMOVE(l2vpn_pw_head, &l2vpn->pw_inactive_tree, pw); free(pw); } } RB_FOREACH_SAFE(xp, l2vpn_pw_head, &xl->pw_inactive_tree, ptmp) { /* find new inactive pseudowires */ if ((pw = l2vpn_pw_find_inactive(l2vpn, xp->ifname)) == NULL) { COPY(pw, xp); RB_INSERT(l2vpn_pw_head, &l2vpn->pw_inactive_tree, pw); pw->l2vpn = l2vpn; switch (ldpd_process) { case PROC_LDE_ENGINE: case PROC_LDP_ENGINE: break; case PROC_MAIN: kif_redistribute(pw->ifname); break; } continue; } /* update existing inactive pseudowire */ pw->lsr_id.s_addr = xp->lsr_id.s_addr; pw->af = xp->af; pw->addr = xp->addr; pw->pwid = xp->pwid; strlcpy(pw->ifname, xp->ifname, sizeof(pw->ifname)); pw->ifindex = xp->ifindex; pw->flags = xp->flags; } l2vpn->pw_type = xl->pw_type; l2vpn->mtu = xl->mtu; strlcpy(l2vpn->br_ifname, xl->br_ifname, sizeof(l2vpn->br_ifname)); l2vpn->br_ifindex = xl->br_ifindex; } struct ldpd_conf * config_new_empty(void) { struct ldpd_conf *xconf; xconf = calloc(1, sizeof(*xconf)); if (xconf == NULL) fatal(NULL); RB_INIT(iface_head, &xconf->iface_tree); RB_INIT(tnbr_head, &xconf->tnbr_tree); RB_INIT(nbrp_head, &xconf->nbrp_tree); RB_INIT(l2vpn_head, &xconf->l2vpn_tree); /* set default values */ ldp_config_reset(xconf); return (xconf); } void config_clear(struct ldpd_conf *conf) { struct ldpd_conf *xconf; /* * Merge current config with an empty config, this will deactivate * and deallocate all the interfaces, pseudowires and so on. Before * merging, copy the router-id and other variables to avoid some * unnecessary operations, like trying to reset the neighborships. */ xconf = config_new_empty(); xconf->ipv4 = conf->ipv4; xconf->ipv6 = conf->ipv6; xconf->rtr_id = conf->rtr_id; xconf->trans_pref = conf->trans_pref; xconf->flags = conf->flags; merge_config(conf, xconf); free(xconf); free(conf); } frr-7.2.1/ldpd/ldpd.conf.sample0000644000000000000000000000121213610377563013205 00000000000000! -*- ldp -*- ! ! LDPd sample configuration file ! hostname ldpd password zebra log stdout ! interface eth0 ! interface eth1 ! interface lo ! mpls ldp dual-stack cisco-interop neighbor 10.0.1.5 password opensourcerouting neighbor 172.16.0.1 password opensourcerouting ! address-family ipv4 discovery transport-address 10.0.1.1 label local advertise explicit-null ! interface eth0 ! interface eth1 ! ! address-family ipv6 discovery transport-address 2001:db8::1 ! interface eth1 ! ! ! l2vpn ENG type vpls bridge br0 member interface eth2 ! member pseudowire mpw0 neighbor lsr-id 1.1.1.1 pw-id 100 ! ! line vty ! frr-7.2.1/ldpd/ldpd.h0000644000000000000000000005021313610377563011234 00000000000000/* $OpenBSD$ */ /* * Copyright (c) 2013, 2016 Renato Westphal * Copyright (c) 2009 Michele Marchetto * Copyright (c) 2004 Esben Norby * Copyright (c) 2003, 2004 Henning Brauer * * Permission to use, copy, modify, and distribute this software for any * purpose with or without fee is hereby granted, provided that the above * copyright notice and this permission notice appear in all copies. * * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. */ #ifndef _LDPD_H_ #define _LDPD_H_ #include "queue.h" #include "openbsd-tree.h" #include "imsg.h" #include "thread.h" #include "qobj.h" #include "prefix.h" #include "filter.h" #include "vty.h" #include "pw.h" #include "zclient.h" #include "ldp.h" #define CONF_FILE "/etc/ldpd.conf" #define LDPD_USER "_ldpd" #define LDPD_FD_ASYNC 3 #define LDPD_FD_SYNC 4 #define LDPD_OPT_VERBOSE 0x00000001 #define LDPD_OPT_VERBOSE2 0x00000002 #define LDPD_OPT_NOACTION 0x00000004 #define TCP_MD5_KEY_LEN 80 #define RT_BUF_SIZE 16384 #define MAX_RTSOCK_BUF 128 * 1024 #define LDP_BACKLOG 128 #define F_LDPD_INSERTED 0x0001 #define F_CONNECTED 0x0002 #define F_STATIC 0x0004 #define F_DYNAMIC 0x0008 #define F_REJECT 0x0010 #define F_BLACKHOLE 0x0020 #define F_REDISTRIBUTED 0x0040 struct evbuf { struct msgbuf wbuf; struct thread *ev; int (*handler)(struct thread *); void *arg; }; struct imsgev { struct imsgbuf ibuf; int (*handler_write)(struct thread *); struct thread *ev_write; int (*handler_read)(struct thread *); struct thread *ev_read; }; enum imsg_type { IMSG_NONE, IMSG_CTL_RELOAD, IMSG_CTL_SHOW_INTERFACE, IMSG_CTL_SHOW_DISCOVERY, IMSG_CTL_SHOW_DISCOVERY_DTL, IMSG_CTL_SHOW_DISC_IFACE, IMSG_CTL_SHOW_DISC_TNBR, IMSG_CTL_SHOW_DISC_ADJ, IMSG_CTL_SHOW_NBR, IMSG_CTL_SHOW_NBR_DISC, IMSG_CTL_SHOW_NBR_END, IMSG_CTL_SHOW_LIB, IMSG_CTL_SHOW_LIB_BEGIN, IMSG_CTL_SHOW_LIB_SENT, IMSG_CTL_SHOW_LIB_RCVD, IMSG_CTL_SHOW_LIB_END, IMSG_CTL_SHOW_L2VPN_PW, IMSG_CTL_SHOW_L2VPN_BINDING, IMSG_CTL_CLEAR_NBR, IMSG_CTL_FIB_COUPLE, IMSG_CTL_FIB_DECOUPLE, IMSG_CTL_KROUTE, IMSG_CTL_KROUTE_ADDR, IMSG_CTL_IFINFO, IMSG_CTL_END, IMSG_CTL_LOG_VERBOSE, IMSG_KLABEL_CHANGE, IMSG_KLABEL_DELETE, IMSG_KPW_ADD, IMSG_KPW_DELETE, IMSG_KPW_SET, IMSG_KPW_UNSET, IMSG_IFSTATUS, IMSG_NEWADDR, IMSG_DELADDR, IMSG_RTRID_UPDATE, IMSG_LABEL_MAPPING, IMSG_LABEL_MAPPING_FULL, IMSG_LABEL_REQUEST, IMSG_LABEL_RELEASE, IMSG_LABEL_WITHDRAW, IMSG_LABEL_ABORT, IMSG_REQUEST_ADD, IMSG_REQUEST_ADD_END, IMSG_MAPPING_ADD, IMSG_MAPPING_ADD_END, IMSG_RELEASE_ADD, IMSG_RELEASE_ADD_END, IMSG_WITHDRAW_ADD, IMSG_WITHDRAW_ADD_END, IMSG_ADDRESS_ADD, IMSG_ADDRESS_DEL, IMSG_NOTIFICATION, IMSG_NOTIFICATION_SEND, IMSG_NEIGHBOR_UP, IMSG_NEIGHBOR_DOWN, IMSG_NETWORK_ADD, IMSG_NETWORK_UPDATE, IMSG_SOCKET_IPC, IMSG_SOCKET_NET, IMSG_CLOSE_SOCKETS, IMSG_REQUEST_SOCKETS, IMSG_SETUP_SOCKETS, IMSG_RECONF_CONF, IMSG_RECONF_IFACE, IMSG_RECONF_TNBR, IMSG_RECONF_NBRP, IMSG_RECONF_L2VPN, IMSG_RECONF_L2VPN_IF, IMSG_RECONF_L2VPN_PW, IMSG_RECONF_L2VPN_IPW, IMSG_RECONF_END, IMSG_DEBUG_UPDATE, IMSG_LOG, IMSG_ACL_CHECK, IMSG_INIT, IMSG_PW_UPDATE }; struct ldpd_init { char user[256]; char group[256]; char ctl_sock_path[MAXPATHLEN]; char zclient_serv_path[MAXPATHLEN]; unsigned short instance; }; union ldpd_addr { struct in_addr v4; struct in6_addr v6; }; #define IN6_IS_SCOPE_EMBED(a) \ ((IN6_IS_ADDR_LINKLOCAL(a)) || \ (IN6_IS_ADDR_MC_LINKLOCAL(a)) || \ (IN6_IS_ADDR_MC_INTFACELOCAL(a))) /* interface states */ #define IF_STA_DOWN 0x01 #define IF_STA_ACTIVE 0x02 /* targeted neighbor states */ #define TNBR_STA_DOWN 0x01 #define TNBR_STA_ACTIVE 0x02 /* interface types */ enum iface_type { IF_TYPE_POINTOPOINT, IF_TYPE_BROADCAST }; /* neighbor states */ #define NBR_STA_PRESENT 0x0001 #define NBR_STA_INITIAL 0x0002 #define NBR_STA_OPENREC 0x0004 #define NBR_STA_OPENSENT 0x0008 #define NBR_STA_OPER 0x0010 #define NBR_STA_SESSION (NBR_STA_INITIAL | NBR_STA_OPENREC | \ NBR_STA_OPENSENT | NBR_STA_OPER) /* neighbor events */ enum nbr_event { NBR_EVT_NOTHING, NBR_EVT_MATCH_ADJ, NBR_EVT_CONNECT_UP, NBR_EVT_CLOSE_SESSION, NBR_EVT_INIT_RCVD, NBR_EVT_KEEPALIVE_RCVD, NBR_EVT_PDU_RCVD, NBR_EVT_PDU_SENT, NBR_EVT_INIT_SENT }; /* neighbor actions */ enum nbr_action { NBR_ACT_NOTHING, NBR_ACT_RST_KTIMEOUT, NBR_ACT_SESSION_EST, NBR_ACT_RST_KTIMER, NBR_ACT_CONNECT_SETUP, NBR_ACT_PASSIVE_INIT, NBR_ACT_KEEPALIVE_SEND, NBR_ACT_CLOSE_SESSION }; /* forward declarations */ RB_HEAD(global_adj_head, adj); RB_HEAD(nbr_adj_head, adj); RB_HEAD(ia_adj_head, adj); struct map { uint8_t type; uint32_t msg_id; union { struct { uint16_t af; union ldpd_addr prefix; uint8_t prefixlen; } prefix; struct { uint16_t type; uint32_t pwid; uint32_t group_id; uint16_t ifmtu; } pwid; struct { uint8_t type; union { uint16_t prefix_af; uint16_t pw_type; } u; } twcard; } fec; struct { uint32_t status_code; uint32_t msg_id; uint16_t msg_type; } st; uint32_t label; uint32_t requestid; uint32_t pw_status; uint8_t flags; }; #define F_MAP_REQ_ID 0x01 /* optional request message id present */ #define F_MAP_STATUS 0x02 /* status */ #define F_MAP_PW_CWORD 0x04 /* pseudowire control word */ #define F_MAP_PW_ID 0x08 /* pseudowire connection id */ #define F_MAP_PW_IFMTU 0x10 /* pseudowire interface parameter */ #define F_MAP_PW_STATUS 0x20 /* pseudowire status */ struct notify_msg { uint32_t status_code; uint32_t msg_id; /* network byte order */ uint16_t msg_type; /* network byte order */ uint32_t pw_status; struct map fec; struct { uint16_t type; uint16_t length; char *data; } rtlvs; uint8_t flags; }; #define F_NOTIF_PW_STATUS 0x01 /* pseudowire status tlv present */ #define F_NOTIF_FEC 0x02 /* fec tlv present */ #define F_NOTIF_RETURNED_TLVS 0x04 /* returned tlvs present */ struct if_addr { LIST_ENTRY(if_addr) entry; int af; union ldpd_addr addr; uint8_t prefixlen; union ldpd_addr dstbrd; }; LIST_HEAD(if_addr_head, if_addr); struct iface_af { struct iface *iface; int af; int enabled; int state; struct ia_adj_head adj_tree; time_t uptime; struct thread *hello_timer; uint16_t hello_holdtime; uint16_t hello_interval; }; struct iface { RB_ENTRY(iface) entry; char name[IF_NAMESIZE]; unsigned int ifindex; struct if_addr_head addr_list; struct in6_addr linklocal; enum iface_type type; int operative; struct iface_af ipv4; struct iface_af ipv6; QOBJ_FIELDS }; RB_HEAD(iface_head, iface); RB_PROTOTYPE(iface_head, iface, entry, iface_compare); DECLARE_QOBJ_TYPE(iface) /* source of targeted hellos */ struct tnbr { RB_ENTRY(tnbr) entry; struct thread *hello_timer; struct adj *adj; int af; union ldpd_addr addr; int state; uint16_t pw_count; uint8_t flags; QOBJ_FIELDS }; RB_HEAD(tnbr_head, tnbr); RB_PROTOTYPE(tnbr_head, tnbr, entry, tnbr_compare); DECLARE_QOBJ_TYPE(tnbr) #define F_TNBR_CONFIGURED 0x01 #define F_TNBR_DYNAMIC 0x02 enum auth_method { AUTH_NONE, AUTH_MD5SIG }; /* neighbor specific parameters */ struct nbr_params { RB_ENTRY(nbr_params) entry; struct in_addr lsr_id; uint16_t keepalive; int gtsm_enabled; uint8_t gtsm_hops; struct { enum auth_method method; char md5key[TCP_MD5_KEY_LEN]; uint8_t md5key_len; } auth; uint8_t flags; QOBJ_FIELDS }; RB_HEAD(nbrp_head, nbr_params); RB_PROTOTYPE(nbrp_head, nbr_params, entry, nbr_params_compare); DECLARE_QOBJ_TYPE(nbr_params) #define F_NBRP_KEEPALIVE 0x01 #define F_NBRP_GTSM 0x02 #define F_NBRP_GTSM_HOPS 0x04 struct ldp_stats { uint32_t kalive_sent; uint32_t kalive_rcvd; uint32_t addr_sent; uint32_t addr_rcvd; uint32_t addrwdraw_sent; uint32_t addrwdraw_rcvd; uint32_t notif_sent; uint32_t notif_rcvd; uint32_t capability_sent; uint32_t capability_rcvd; uint32_t labelmap_sent; uint32_t labelmap_rcvd; uint32_t labelreq_sent; uint32_t labelreq_rcvd; uint32_t labelwdraw_sent; uint32_t labelwdraw_rcvd; uint32_t labelrel_sent; uint32_t labelrel_rcvd; uint32_t labelabreq_sent; uint32_t labelabreq_rcvd; }; struct l2vpn_if { RB_ENTRY(l2vpn_if) entry; struct l2vpn *l2vpn; char ifname[IF_NAMESIZE]; unsigned int ifindex; int operative; uint8_t mac[ETH_ALEN]; QOBJ_FIELDS }; RB_HEAD(l2vpn_if_head, l2vpn_if); RB_PROTOTYPE(l2vpn_if_head, l2vpn_if, entry, l2vpn_if_compare); DECLARE_QOBJ_TYPE(l2vpn_if) struct l2vpn_pw { RB_ENTRY(l2vpn_pw) entry; struct l2vpn *l2vpn; struct in_addr lsr_id; int af; union ldpd_addr addr; uint32_t pwid; char ifname[IF_NAMESIZE]; unsigned int ifindex; bool enabled; uint32_t remote_group; uint16_t remote_mtu; uint32_t local_status; uint32_t remote_status; uint8_t flags; QOBJ_FIELDS }; RB_HEAD(l2vpn_pw_head, l2vpn_pw); RB_PROTOTYPE(l2vpn_pw_head, l2vpn_pw, entry, l2vpn_pw_compare); DECLARE_QOBJ_TYPE(l2vpn_pw) #define F_PW_STATUSTLV_CONF 0x01 /* status tlv configured */ #define F_PW_STATUSTLV 0x02 /* status tlv negotiated */ #define F_PW_CWORD_CONF 0x04 /* control word configured */ #define F_PW_CWORD 0x08 /* control word negotiated */ #define F_PW_STATIC_NBR_ADDR 0x10 /* static neighbor address configured */ struct l2vpn { RB_ENTRY(l2vpn) entry; char name[L2VPN_NAME_LEN]; int type; int pw_type; int mtu; char br_ifname[IF_NAMESIZE]; unsigned int br_ifindex; struct l2vpn_if_head if_tree; struct l2vpn_pw_head pw_tree; struct l2vpn_pw_head pw_inactive_tree; QOBJ_FIELDS }; RB_HEAD(l2vpn_head, l2vpn); RB_PROTOTYPE(l2vpn_head, l2vpn, entry, l2vpn_compare); DECLARE_QOBJ_TYPE(l2vpn) #define L2VPN_TYPE_VPWS 1 #define L2VPN_TYPE_VPLS 2 /* ldp_conf */ enum ldpd_process { PROC_MAIN, PROC_LDP_ENGINE, PROC_LDE_ENGINE } ldpd_process; static const char * const log_procnames[] = { "parent", "ldpe", "lde" }; enum socket_type { LDP_SOCKET_DISC, LDP_SOCKET_EDISC, LDP_SOCKET_SESSION }; enum hello_type { HELLO_LINK, HELLO_TARGETED }; struct ldpd_af_conf { uint16_t keepalive; uint16_t lhello_holdtime; uint16_t lhello_interval; uint16_t thello_holdtime; uint16_t thello_interval; union ldpd_addr trans_addr; char acl_thello_accept_from[ACL_NAMSIZ]; char acl_label_allocate_for[ACL_NAMSIZ]; char acl_label_advertise_to[ACL_NAMSIZ]; char acl_label_advertise_for[ACL_NAMSIZ]; char acl_label_expnull_for[ACL_NAMSIZ]; char acl_label_accept_from[ACL_NAMSIZ]; char acl_label_accept_for[ACL_NAMSIZ]; int flags; }; #define F_LDPD_AF_ENABLED 0x0001 #define F_LDPD_AF_THELLO_ACCEPT 0x0002 #define F_LDPD_AF_EXPNULL 0x0004 #define F_LDPD_AF_NO_GTSM 0x0008 #define F_LDPD_AF_ALLOCHOSTONLY 0x0010 struct ldpd_conf { struct in_addr rtr_id; struct ldpd_af_conf ipv4; struct ldpd_af_conf ipv6; struct iface_head iface_tree; struct tnbr_head tnbr_tree; struct nbrp_head nbrp_tree; struct l2vpn_head l2vpn_tree; uint16_t lhello_holdtime; uint16_t lhello_interval; uint16_t thello_holdtime; uint16_t thello_interval; uint16_t trans_pref; int flags; QOBJ_FIELDS }; DECLARE_QOBJ_TYPE(ldpd_conf) #define F_LDPD_NO_FIB_UPDATE 0x0001 #define F_LDPD_DS_CISCO_INTEROP 0x0002 #define F_LDPD_ENABLED 0x0004 struct ldpd_af_global { struct thread *disc_ev; struct thread *edisc_ev; int ldp_disc_socket; int ldp_edisc_socket; int ldp_session_socket; }; struct ldpd_global { int cmd_opts; struct in_addr rtr_id; struct ldpd_af_global ipv4; struct ldpd_af_global ipv6; uint32_t conf_seqnum; int pfkeysock; struct if_addr_head addr_list; struct global_adj_head adj_tree; struct in_addr mcast_addr_v4; struct in6_addr mcast_addr_v6; TAILQ_HEAD(, pending_conn) pending_conns; }; /* kroute */ struct kroute { int af; union ldpd_addr prefix; uint8_t prefixlen; union ldpd_addr nexthop; uint32_t local_label; uint32_t remote_label; unsigned short ifindex; uint8_t priority; uint16_t flags; }; struct kaddr { char ifname[IF_NAMESIZE]; unsigned short ifindex; int af; union ldpd_addr addr; uint8_t prefixlen; union ldpd_addr dstbrd; }; struct kif { char ifname[IF_NAMESIZE]; unsigned short ifindex; int flags; int operative; uint8_t mac[ETH_ALEN]; int mtu; }; struct acl_check { char acl[ACL_NAMSIZ]; int af; union ldpd_addr addr; uint8_t prefixlen; }; /* control data structures */ struct ctl_iface { int af; char name[IF_NAMESIZE]; unsigned int ifindex; int state; enum iface_type type; uint16_t hello_holdtime; uint16_t hello_interval; time_t uptime; uint16_t adj_cnt; }; struct ctl_disc_if { char name[IF_NAMESIZE]; int active_v4; int active_v6; int no_adj; }; struct ctl_disc_tnbr { int af; union ldpd_addr addr; int no_adj; }; struct ctl_adj { int af; struct in_addr id; enum hello_type type; char ifname[IF_NAMESIZE]; union ldpd_addr src_addr; uint16_t holdtime; uint16_t holdtime_remaining; union ldpd_addr trans_addr; int ds_tlv; }; struct ctl_nbr { int af; struct in_addr id; union ldpd_addr laddr; in_port_t lport; union ldpd_addr raddr; in_port_t rport; enum auth_method auth_method; uint16_t holdtime; time_t uptime; int nbr_state; struct ldp_stats stats; int flags; }; struct ctl_rt { int af; union ldpd_addr prefix; uint8_t prefixlen; struct in_addr nexthop; /* lsr-id */ uint32_t local_label; uint32_t remote_label; uint8_t flags; uint8_t in_use; int no_downstream; }; struct ctl_pw { uint16_t type; char l2vpn_name[L2VPN_NAME_LEN]; char ifname[IF_NAMESIZE]; uint32_t pwid; struct in_addr lsr_id; uint32_t local_label; uint32_t local_gid; uint16_t local_ifmtu; uint8_t local_cword; uint32_t remote_label; uint32_t remote_gid; uint16_t remote_ifmtu; uint8_t remote_cword; uint32_t status; }; extern struct ldpd_conf *ldpd_conf, *vty_conf; extern struct ldpd_global global; extern struct ldpd_init init; /* parse.y */ struct ldpd_conf *parse_config(char *); int cmdline_symset(char *); /* kroute.c */ void pw2zpw(struct l2vpn_pw *, struct zapi_pw *); void kif_redistribute(const char *); int kr_change(struct kroute *); int kr_delete(struct kroute *); int kmpw_add(struct zapi_pw *); int kmpw_del(struct zapi_pw *); int kmpw_set(struct zapi_pw *); int kmpw_unset(struct zapi_pw *); /* util.c */ uint8_t mask2prefixlen(in_addr_t); uint8_t mask2prefixlen6(struct sockaddr_in6 *); in_addr_t prefixlen2mask(uint8_t); struct in6_addr *prefixlen2mask6(uint8_t); void ldp_applymask(int, union ldpd_addr *, const union ldpd_addr *, int); int ldp_addrcmp(int, const union ldpd_addr *, const union ldpd_addr *); int ldp_addrisset(int, const union ldpd_addr *); int ldp_prefixcmp(int, const union ldpd_addr *, const union ldpd_addr *, uint8_t); int bad_addr_v4(struct in_addr); int bad_addr_v6(struct in6_addr *); int bad_addr(int, union ldpd_addr *); void embedscope(struct sockaddr_in6 *); void recoverscope(struct sockaddr_in6 *); void addscope(struct sockaddr_in6 *, uint32_t); void clearscope(struct in6_addr *); void addr2sa(int af, const union ldpd_addr *, uint16_t, union sockunion *su); void sa2addr(struct sockaddr *, int *, union ldpd_addr *, in_port_t *); socklen_t sockaddr_len(struct sockaddr *); /* ldpd.c */ int ldp_write_handler(struct thread *); void main_imsg_compose_ldpe(int, pid_t, void *, uint16_t); void main_imsg_compose_lde(int, pid_t, void *, uint16_t); int main_imsg_compose_both(enum imsg_type, void *, uint16_t); void imsg_event_add(struct imsgev *); int imsg_compose_event(struct imsgev *, uint16_t, uint32_t, pid_t, int, void *, uint16_t); void evbuf_enqueue(struct evbuf *, struct ibuf *); void evbuf_event_add(struct evbuf *); void evbuf_init(struct evbuf *, int, int (*)(struct thread *), void *); void evbuf_clear(struct evbuf *); int ldp_acl_request(struct imsgev *, char *, int, union ldpd_addr *, uint8_t); void ldp_acl_reply(struct imsgev *, struct acl_check *); struct ldpd_af_conf *ldp_af_conf_get(struct ldpd_conf *, int); struct ldpd_af_global *ldp_af_global_get(struct ldpd_global *, int); int ldp_is_dual_stack(struct ldpd_conf *); in_addr_t ldp_rtr_id_get(struct ldpd_conf *); int ldp_config_apply(struct vty *, struct ldpd_conf *); void ldp_clear_config(struct ldpd_conf *); void merge_config(struct ldpd_conf *, struct ldpd_conf *); struct ldpd_conf *config_new_empty(void); void config_clear(struct ldpd_conf *); /* ldp_vty_conf.c */ /* NOTE: the parameters' names should be preserved because of codegen */ struct iface *iface_new_api(struct ldpd_conf *conf, const char *name); void iface_del_api(struct ldpd_conf *conf, struct iface *iface); struct tnbr *tnbr_new_api(struct ldpd_conf *conf, int af, union ldpd_addr *addr); void tnbr_del_api(struct ldpd_conf *conf, struct tnbr *tnbr); struct nbr_params *nbrp_new_api(struct ldpd_conf *conf, struct in_addr lsr_id); void nbrp_del_api(struct ldpd_conf *conf, struct nbr_params *nbrp); struct l2vpn *l2vpn_new_api(struct ldpd_conf *conf, const char *name); void l2vpn_del_api(struct ldpd_conf *conf, struct l2vpn *l2vpn); struct l2vpn_if *l2vpn_if_new_api(struct ldpd_conf *conf, struct l2vpn *l2vpn, const char *ifname); void l2vpn_if_del_api(struct l2vpn *l2vpn, struct l2vpn_if *lif); struct l2vpn_pw *l2vpn_pw_new_api(struct ldpd_conf *conf, struct l2vpn *l2vpn, const char *ifname); void l2vpn_pw_del_api(struct l2vpn *l2vpn, struct l2vpn_pw *pw); /* socket.c */ int ldp_create_socket(int, enum socket_type); void sock_set_nonblock(int); void sock_set_cloexec(int); void sock_set_recvbuf(int); int sock_set_reuse(int, int); int sock_set_bindany(int, int); int sock_set_md5sig(int, int, union ldpd_addr *, const char *); int sock_set_ipv4_tos(int, int); int sock_set_ipv4_pktinfo(int, int); int sock_set_ipv4_recvdstaddr(int, int); int sock_set_ipv4_recvif(int, int); int sock_set_ipv4_minttl(int, int); int sock_set_ipv4_ucast_ttl(int fd, int); int sock_set_ipv4_mcast_ttl(int, uint8_t); int sock_set_ipv4_mcast(struct iface *); int sock_set_ipv4_mcast_loop(int); int sock_set_ipv6_dscp(int, int); int sock_set_ipv6_pktinfo(int, int); int sock_set_ipv6_minhopcount(int, int); int sock_set_ipv6_ucast_hops(int, int); int sock_set_ipv6_mcast_hops(int, int); int sock_set_ipv6_mcast(struct iface *); int sock_set_ipv6_mcast_loop(int); /* logmsg.h */ struct in6_addr; union ldpd_addr; struct hello_source; struct fec; const char *log_sockaddr(void *); const char *log_in6addr(const struct in6_addr *); const char *log_in6addr_scope(const struct in6_addr *, unsigned int); const char *log_addr(int, const union ldpd_addr *); char *log_label(uint32_t); const char *log_time(time_t); char *log_hello_src(const struct hello_source *); const char *log_map(const struct map *); const char *log_fec(const struct fec *); const char *af_name(int); const char *socket_name(int); const char *nbr_state_name(int); const char *if_state_name(int); const char *if_type_name(enum iface_type); const char *msg_name(uint16_t); const char *status_code_name(uint32_t); const char *pw_type_name(uint16_t); /* quagga */ extern struct thread_master *master; extern char ctl_sock_path[MAXPATHLEN]; /* ldp_zebra.c */ void ldp_zebra_init(struct thread_master *); void ldp_zebra_destroy(void); /* compatibility */ #ifndef __OpenBSD__ #define __IPV6_ADDR_MC_SCOPE(a) ((a)->s6_addr[1] & 0x0f) #define __IPV6_ADDR_SCOPE_INTFACELOCAL 0x01 #define IN6_IS_ADDR_MC_INTFACELOCAL(a) \ (IN6_IS_ADDR_MULTICAST(a) && \ (__IPV6_ADDR_MC_SCOPE(a) == __IPV6_ADDR_SCOPE_INTFACELOCAL)) #endif #endif /* _LDPD_H_ */ frr-7.2.1/ldpd/ldpe.c0000644000000000000000000005670613610377563011245 00000000000000/* $OpenBSD$ */ /* * Copyright (c) 2013, 2016 Renato Westphal * Copyright (c) 2005 Claudio Jeker * Copyright (c) 2004, 2008 Esben Norby * Copyright (c) 2003, 2004 Henning Brauer * * Permission to use, copy, modify, and distribute this software for any * purpose with or without fee is hereby granted, provided that the above * copyright notice and this permission notice appear in all copies. * * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. */ #include #include "ldpd.h" #include "ldpe.h" #include "lde.h" #include "control.h" #include "log.h" #include "ldp_debug.h" #include #include "memory.h" #include "privs.h" #include "sigevent.h" static void ldpe_shutdown(void); static int ldpe_dispatch_main(struct thread *); static int ldpe_dispatch_lde(struct thread *); #ifdef __OpenBSD__ static int ldpe_dispatch_pfkey(struct thread *); #endif static void ldpe_setup_sockets(int, int, int, int); static void ldpe_close_sockets(int); static void ldpe_iface_af_ctl(struct ctl_conn *, int, unsigned int); struct ldpd_conf *leconf; #ifdef __OpenBSD__ struct ldpd_sysdep sysdep; #endif static struct imsgev *iev_main, *iev_main_sync; static struct imsgev *iev_lde; #ifdef __OpenBSD__ static struct thread *pfkey_ev; #endif /* Master of threads. */ struct thread_master *master; /* ldpe privileges */ static zebra_capabilities_t _caps_p [] = { ZCAP_BIND, ZCAP_NET_ADMIN }; struct zebra_privs_t ldpe_privs = { #if defined(VTY_GROUP) .vty_group = VTY_GROUP, #endif .caps_p = _caps_p, .cap_num_p = array_size(_caps_p), .cap_num_i = 0 }; /* SIGINT / SIGTERM handler. */ static void sigint(void) { ldpe_shutdown(); } static struct quagga_signal_t ldpe_signals[] = { { .signal = SIGHUP, /* ignore */ }, { .signal = SIGINT, .handler = &sigint, }, { .signal = SIGTERM, .handler = &sigint, }, }; /* label distribution protocol engine */ void ldpe(void) { struct thread thread; #ifdef HAVE_SETPROCTITLE setproctitle("ldp engine"); #endif ldpd_process = PROC_LDP_ENGINE; log_procname = log_procnames[ldpd_process]; master = thread_master_create(NULL); /* setup signal handler */ signal_init(master, array_size(ldpe_signals), ldpe_signals); /* setup pipes and event handlers to the parent process */ if ((iev_main = calloc(1, sizeof(struct imsgev))) == NULL) fatal(NULL); imsg_init(&iev_main->ibuf, LDPD_FD_ASYNC); iev_main->handler_read = ldpe_dispatch_main; iev_main->ev_read = NULL; thread_add_read(master, iev_main->handler_read, iev_main, iev_main->ibuf.fd, &iev_main->ev_read); iev_main->handler_write = ldp_write_handler; if ((iev_main_sync = calloc(1, sizeof(struct imsgev))) == NULL) fatal(NULL); imsg_init(&iev_main_sync->ibuf, LDPD_FD_SYNC); /* create base configuration */ leconf = config_new_empty(); /* Fetch next active thread. */ while (thread_fetch(master, &thread)) thread_call(&thread); } void ldpe_init(struct ldpd_init *init) { #ifdef __OpenBSD__ /* This socket must be open before dropping privileges. */ global.pfkeysock = pfkey_init(); if (sysdep.no_pfkey == 0) { pfkey_ev = NULL; thread_add_read(master, ldpe_dispatch_pfkey, NULL, global.pfkeysock, &pfkey_ev); } #endif /* drop privileges */ ldpe_privs.user = init->user; ldpe_privs.group = init->group; zprivs_preinit(&ldpe_privs); zprivs_init(&ldpe_privs); /* listen on ldpd control socket */ strlcpy(ctl_sock_path, init->ctl_sock_path, sizeof(ctl_sock_path)); if (control_init(ctl_sock_path) == -1) fatalx("control socket setup failed"); TAILQ_INIT(&ctl_conns); control_listen(); LIST_INIT(&global.addr_list); RB_INIT(global_adj_head, &global.adj_tree); TAILQ_INIT(&global.pending_conns); if (inet_pton(AF_INET, AllRouters_v4, &global.mcast_addr_v4) != 1) fatal("inet_pton"); if (inet_pton(AF_INET6, AllRouters_v6, &global.mcast_addr_v6) != 1) fatal("inet_pton"); /* mark sockets as closed */ global.ipv4.ldp_disc_socket = -1; global.ipv4.ldp_edisc_socket = -1; global.ipv4.ldp_session_socket = -1; global.ipv6.ldp_disc_socket = -1; global.ipv6.ldp_edisc_socket = -1; global.ipv6.ldp_session_socket = -1; if ((pkt_ptr = calloc(1, IBUF_READ_SIZE)) == NULL) fatal(__func__); accept_init(); } static void ldpe_shutdown(void) { struct if_addr *if_addr; struct adj *adj; /* close pipes */ if (iev_lde) { msgbuf_clear(&iev_lde->ibuf.w); close(iev_lde->ibuf.fd); iev_lde->ibuf.fd = -1; } msgbuf_clear(&iev_main->ibuf.w); close(iev_main->ibuf.fd); iev_main->ibuf.fd = -1; msgbuf_clear(&iev_main_sync->ibuf.w); close(iev_main_sync->ibuf.fd); iev_main_sync->ibuf.fd = -1; control_cleanup(ctl_sock_path); config_clear(leconf); #ifdef __OpenBSD__ if (sysdep.no_pfkey == 0) { THREAD_READ_OFF(pfkey_ev); close(global.pfkeysock); } #endif ldpe_close_sockets(AF_INET); ldpe_close_sockets(AF_INET6); /* remove addresses from global list */ while ((if_addr = LIST_FIRST(&global.addr_list)) != NULL) { LIST_REMOVE(if_addr, entry); assert(if_addr != LIST_FIRST(&global.addr_list)); free(if_addr); } while (!RB_EMPTY(global_adj_head, &global.adj_tree)) { adj = RB_ROOT(global_adj_head, &global.adj_tree); adj_del(adj, S_SHUTDOWN); } /* clean up */ if (iev_lde) free(iev_lde); free(iev_main); free(iev_main_sync); free(pkt_ptr); log_info("ldp engine exiting"); exit(0); } /* imesg */ int ldpe_imsg_compose_parent(int type, pid_t pid, void *data, uint16_t datalen) { if (iev_main->ibuf.fd == -1) return (0); return (imsg_compose_event(iev_main, type, 0, pid, -1, data, datalen)); } void ldpe_imsg_compose_parent_sync(int type, pid_t pid, void *data, uint16_t datalen) { if (iev_main_sync->ibuf.fd == -1) return; imsg_compose_event(iev_main_sync, type, 0, pid, -1, data, datalen); imsg_flush(&iev_main_sync->ibuf); } int ldpe_imsg_compose_lde(int type, uint32_t peerid, pid_t pid, void *data, uint16_t datalen) { if (iev_lde->ibuf.fd == -1) return (0); return (imsg_compose_event(iev_lde, type, peerid, pid, -1, data, datalen)); } /* ARGSUSED */ static int ldpe_dispatch_main(struct thread *thread) { static struct ldpd_conf *nconf; struct iface *niface; struct tnbr *ntnbr; struct nbr_params *nnbrp; static struct l2vpn *l2vpn, *nl2vpn; struct l2vpn_if *lif, *nlif; struct l2vpn_pw *pw, *npw; struct imsg imsg; int fd; struct imsgev *iev = THREAD_ARG(thread); struct imsgbuf *ibuf = &iev->ibuf; struct iface *iface = NULL; struct kif *kif; int af; enum socket_type *socket_type; static int disc_socket = -1; static int edisc_socket = -1; static int session_socket = -1; struct nbr *nbr; #ifdef __OpenBSD__ struct nbr_params *nbrp; #endif int n, shut = 0; iev->ev_read = NULL; if ((n = imsg_read(ibuf)) == -1 && errno != EAGAIN) fatal("imsg_read error"); if (n == 0) /* connection closed */ shut = 1; for (;;) { if ((n = imsg_get(ibuf, &imsg)) == -1) fatal("ldpe_dispatch_main: imsg_get error"); if (n == 0) break; switch (imsg.hdr.type) { case IMSG_IFSTATUS: if (imsg.hdr.len != IMSG_HEADER_SIZE + sizeof(struct kif)) fatalx("IFSTATUS imsg with wrong len"); kif = imsg.data; iface = if_lookup_name(leconf, kif->ifname); if (iface) { if_update_info(iface, kif); ldp_if_update(iface, AF_UNSPEC); break; } RB_FOREACH(l2vpn, l2vpn_head, &leconf->l2vpn_tree) { lif = l2vpn_if_find(l2vpn, kif->ifname); if (lif) { l2vpn_if_update_info(lif, kif); l2vpn_if_update(lif); break; } pw = l2vpn_pw_find(l2vpn, kif->ifname); if (pw) { l2vpn_pw_update_info(pw, kif); break; } } break; case IMSG_NEWADDR: if (imsg.hdr.len != IMSG_HEADER_SIZE + sizeof(struct kaddr)) fatalx("NEWADDR imsg with wrong len"); if_addr_add(imsg.data); break; case IMSG_DELADDR: if (imsg.hdr.len != IMSG_HEADER_SIZE + sizeof(struct kaddr)) fatalx("DELADDR imsg with wrong len"); if_addr_del(imsg.data); break; case IMSG_SOCKET_IPC: if (iev_lde) { log_warnx("%s: received unexpected imsg fd " "to lde", __func__); break; } if ((fd = imsg.fd) == -1) { log_warnx("%s: expected to receive imsg fd to " "lde but didn't receive any", __func__); break; } if ((iev_lde = malloc(sizeof(struct imsgev))) == NULL) fatal(NULL); imsg_init(&iev_lde->ibuf, fd); iev_lde->handler_read = ldpe_dispatch_lde; iev_lde->ev_read = NULL; thread_add_read(master, iev_lde->handler_read, iev_lde, iev_lde->ibuf.fd, &iev_lde->ev_read); iev_lde->handler_write = ldp_write_handler; iev_lde->ev_write = NULL; break; case IMSG_INIT: if (imsg.hdr.len != IMSG_HEADER_SIZE + sizeof(struct ldpd_init)) fatalx("INIT imsg with wrong len"); memcpy(&init, imsg.data, sizeof(init)); ldpe_init(&init); break; case IMSG_CLOSE_SOCKETS: af = imsg.hdr.peerid; RB_FOREACH(nbr, nbr_id_head, &nbrs_by_id) { if (nbr->af != af) continue; session_shutdown(nbr, S_SHUTDOWN, 0, 0); #ifdef __OpenBSD__ pfkey_remove(nbr); #endif nbr->auth.method = AUTH_NONE; } ldpe_close_sockets(af); if_update_all(af); tnbr_update_all(af); disc_socket = -1; edisc_socket = -1; session_socket = -1; if ((ldp_af_conf_get(leconf, af))->flags & F_LDPD_AF_ENABLED) ldpe_imsg_compose_parent(IMSG_REQUEST_SOCKETS, af, NULL, 0); break; case IMSG_SOCKET_NET: if (imsg.hdr.len != IMSG_HEADER_SIZE + sizeof(enum socket_type)) fatalx("SOCKET_NET imsg with wrong len"); socket_type = imsg.data; switch (*socket_type) { case LDP_SOCKET_DISC: disc_socket = imsg.fd; break; case LDP_SOCKET_EDISC: edisc_socket = imsg.fd; break; case LDP_SOCKET_SESSION: session_socket = imsg.fd; break; } break; case IMSG_SETUP_SOCKETS: af = imsg.hdr.peerid; if (disc_socket == -1 || edisc_socket == -1 || session_socket == -1) { if (disc_socket != -1) close(disc_socket); if (edisc_socket != -1) close(edisc_socket); if (session_socket != -1) close(session_socket); break; } ldpe_setup_sockets(af, disc_socket, edisc_socket, session_socket); if_update_all(af); tnbr_update_all(af); RB_FOREACH(nbr, nbr_id_head, &nbrs_by_id) { if (nbr->af != af) continue; nbr->laddr = (ldp_af_conf_get(leconf, af))->trans_addr; #ifdef __OpenBSD__ nbrp = nbr_params_find(leconf, nbr->id); if (nbrp) { nbr->auth.method = nbrp->auth.method; if (pfkey_establish(nbr, nbrp) == -1) fatalx("pfkey setup failed"); } #endif if (nbr_session_active_role(nbr)) nbr_establish_connection(nbr); } break; case IMSG_RTRID_UPDATE: memcpy(&global.rtr_id, imsg.data, sizeof(global.rtr_id)); if (leconf->rtr_id.s_addr == INADDR_ANY) { ldpe_reset_nbrs(AF_UNSPEC); } if_update_all(AF_UNSPEC); tnbr_update_all(AF_UNSPEC); break; case IMSG_RECONF_CONF: if ((nconf = malloc(sizeof(struct ldpd_conf))) == NULL) fatal(NULL); memcpy(nconf, imsg.data, sizeof(struct ldpd_conf)); RB_INIT(iface_head, &nconf->iface_tree); RB_INIT(tnbr_head, &nconf->tnbr_tree); RB_INIT(nbrp_head, &nconf->nbrp_tree); RB_INIT(l2vpn_head, &nconf->l2vpn_tree); break; case IMSG_RECONF_IFACE: if ((niface = malloc(sizeof(struct iface))) == NULL) fatal(NULL); memcpy(niface, imsg.data, sizeof(struct iface)); RB_INSERT(iface_head, &nconf->iface_tree, niface); break; case IMSG_RECONF_TNBR: if ((ntnbr = malloc(sizeof(struct tnbr))) == NULL) fatal(NULL); memcpy(ntnbr, imsg.data, sizeof(struct tnbr)); RB_INSERT(tnbr_head, &nconf->tnbr_tree, ntnbr); break; case IMSG_RECONF_NBRP: if ((nnbrp = malloc(sizeof(struct nbr_params))) == NULL) fatal(NULL); memcpy(nnbrp, imsg.data, sizeof(struct nbr_params)); RB_INSERT(nbrp_head, &nconf->nbrp_tree, nnbrp); break; case IMSG_RECONF_L2VPN: if ((nl2vpn = malloc(sizeof(struct l2vpn))) == NULL) fatal(NULL); memcpy(nl2vpn, imsg.data, sizeof(struct l2vpn)); RB_INIT(l2vpn_if_head, &nl2vpn->if_tree); RB_INIT(l2vpn_pw_head, &nl2vpn->pw_tree); RB_INIT(l2vpn_pw_head, &nl2vpn->pw_inactive_tree); RB_INSERT(l2vpn_head, &nconf->l2vpn_tree, nl2vpn); break; case IMSG_RECONF_L2VPN_IF: if ((nlif = malloc(sizeof(struct l2vpn_if))) == NULL) fatal(NULL); memcpy(nlif, imsg.data, sizeof(struct l2vpn_if)); RB_INSERT(l2vpn_if_head, &nl2vpn->if_tree, nlif); break; case IMSG_RECONF_L2VPN_PW: if ((npw = malloc(sizeof(struct l2vpn_pw))) == NULL) fatal(NULL); memcpy(npw, imsg.data, sizeof(struct l2vpn_pw)); RB_INSERT(l2vpn_pw_head, &nl2vpn->pw_tree, npw); break; case IMSG_RECONF_L2VPN_IPW: if ((npw = malloc(sizeof(struct l2vpn_pw))) == NULL) fatal(NULL); memcpy(npw, imsg.data, sizeof(struct l2vpn_pw)); RB_INSERT(l2vpn_pw_head, &nl2vpn->pw_inactive_tree, npw); break; case IMSG_RECONF_END: merge_config(leconf, nconf); ldp_clear_config(nconf); nconf = NULL; global.conf_seqnum++; break; case IMSG_CTL_END: control_imsg_relay(&imsg); break; case IMSG_DEBUG_UPDATE: if (imsg.hdr.len != IMSG_HEADER_SIZE + sizeof(ldp_debug)) { log_warnx("%s: wrong imsg len", __func__); break; } memcpy(&ldp_debug, imsg.data, sizeof(ldp_debug)); break; default: log_debug("ldpe_dispatch_main: error handling imsg %d", imsg.hdr.type); break; } imsg_free(&imsg); } if (!shut) imsg_event_add(iev); else { /* this pipe is dead, so remove the event handlers and exit */ THREAD_READ_OFF(iev->ev_read); THREAD_WRITE_OFF(iev->ev_write); ldpe_shutdown(); } return (0); } /* ARGSUSED */ static int ldpe_dispatch_lde(struct thread *thread) { struct imsgev *iev = THREAD_ARG(thread); struct imsgbuf *ibuf = &iev->ibuf; struct imsg imsg; struct map *map; struct notify_msg *nm; struct nbr *nbr; int n, shut = 0; iev->ev_read = NULL; if ((n = imsg_read(ibuf)) == -1 && errno != EAGAIN) fatal("imsg_read error"); if (n == 0) /* connection closed */ shut = 1; for (;;) { if ((n = imsg_get(ibuf, &imsg)) == -1) fatal("ldpe_dispatch_lde: imsg_get error"); if (n == 0) break; switch (imsg.hdr.type) { case IMSG_MAPPING_ADD: case IMSG_RELEASE_ADD: case IMSG_REQUEST_ADD: case IMSG_WITHDRAW_ADD: if (imsg.hdr.len - IMSG_HEADER_SIZE != sizeof(struct map)) fatalx("invalid size of map request"); map = imsg.data; nbr = nbr_find_peerid(imsg.hdr.peerid); if (nbr == NULL) { log_debug("ldpe_dispatch_lde: cannot find " "neighbor"); break; } if (nbr->state != NBR_STA_OPER) break; switch (imsg.hdr.type) { case IMSG_MAPPING_ADD: mapping_list_add(&nbr->mapping_list, map); break; case IMSG_RELEASE_ADD: mapping_list_add(&nbr->release_list, map); break; case IMSG_REQUEST_ADD: mapping_list_add(&nbr->request_list, map); break; case IMSG_WITHDRAW_ADD: mapping_list_add(&nbr->withdraw_list, map); break; } break; case IMSG_MAPPING_ADD_END: case IMSG_RELEASE_ADD_END: case IMSG_REQUEST_ADD_END: case IMSG_WITHDRAW_ADD_END: nbr = nbr_find_peerid(imsg.hdr.peerid); if (nbr == NULL) { log_debug("ldpe_dispatch_lde: cannot find " "neighbor"); break; } if (nbr->state != NBR_STA_OPER) break; switch (imsg.hdr.type) { case IMSG_MAPPING_ADD_END: send_labelmessage(nbr, MSG_TYPE_LABELMAPPING, &nbr->mapping_list); break; case IMSG_RELEASE_ADD_END: send_labelmessage(nbr, MSG_TYPE_LABELRELEASE, &nbr->release_list); break; case IMSG_REQUEST_ADD_END: send_labelmessage(nbr, MSG_TYPE_LABELREQUEST, &nbr->request_list); break; case IMSG_WITHDRAW_ADD_END: send_labelmessage(nbr, MSG_TYPE_LABELWITHDRAW, &nbr->withdraw_list); break; } break; case IMSG_NOTIFICATION_SEND: if (imsg.hdr.len - IMSG_HEADER_SIZE != sizeof(struct notify_msg)) fatalx("invalid size of OE request"); nm = imsg.data; nbr = nbr_find_peerid(imsg.hdr.peerid); if (nbr == NULL) { log_debug("ldpe_dispatch_lde: cannot find " "neighbor"); break; } if (nbr->state != NBR_STA_OPER) break; send_notification_full(nbr->tcp, nm); break; case IMSG_CTL_END: case IMSG_CTL_SHOW_LIB_BEGIN: case IMSG_CTL_SHOW_LIB_RCVD: case IMSG_CTL_SHOW_LIB_SENT: case IMSG_CTL_SHOW_LIB_END: case IMSG_CTL_SHOW_L2VPN_PW: case IMSG_CTL_SHOW_L2VPN_BINDING: control_imsg_relay(&imsg); break; default: log_debug("ldpe_dispatch_lde: error handling imsg %d", imsg.hdr.type); break; } imsg_free(&imsg); } if (!shut) imsg_event_add(iev); else { /* this pipe is dead, so remove the event handlers and exit */ THREAD_READ_OFF(iev->ev_read); THREAD_WRITE_OFF(iev->ev_write); ldpe_shutdown(); } return (0); } #ifdef __OpenBSD__ /* ARGSUSED */ static int ldpe_dispatch_pfkey(struct thread *thread) { int fd = THREAD_FD(thread); pfkey_ev = NULL; thread_add_read(master, ldpe_dispatch_pfkey, NULL, global.pfkeysock, &pfkey_ev); if (pfkey_read(fd, NULL) == -1) fatal("pfkey_read failed, exiting..."); return (0); } #endif /* __OpenBSD__ */ static void ldpe_setup_sockets(int af, int disc_socket, int edisc_socket, int session_socket) { struct ldpd_af_global *af_global; af_global = ldp_af_global_get(&global, af); /* discovery socket */ af_global->ldp_disc_socket = disc_socket; af_global->disc_ev = NULL; thread_add_read(master, disc_recv_packet, &af_global->disc_ev, af_global->ldp_disc_socket, &af_global->disc_ev); /* extended discovery socket */ af_global->ldp_edisc_socket = edisc_socket; af_global->edisc_ev = NULL; thread_add_read(master, disc_recv_packet, &af_global->edisc_ev, af_global->ldp_edisc_socket, &af_global->edisc_ev); /* session socket */ af_global->ldp_session_socket = session_socket; accept_add(af_global->ldp_session_socket, session_accept, NULL); } static void ldpe_close_sockets(int af) { struct ldpd_af_global *af_global; af_global = ldp_af_global_get(&global, af); /* discovery socket */ THREAD_READ_OFF(af_global->disc_ev); if (af_global->ldp_disc_socket != -1) { close(af_global->ldp_disc_socket); af_global->ldp_disc_socket = -1; } /* extended discovery socket */ THREAD_READ_OFF(af_global->edisc_ev); if (af_global->ldp_edisc_socket != -1) { close(af_global->ldp_edisc_socket); af_global->ldp_edisc_socket = -1; } /* session socket */ if (af_global->ldp_session_socket != -1) { accept_del(af_global->ldp_session_socket); close(af_global->ldp_session_socket); af_global->ldp_session_socket = -1; } } int ldpe_acl_check(char *acl_name, int af, union ldpd_addr *addr, uint8_t prefixlen) { return ldp_acl_request(iev_main_sync, acl_name, af, addr, prefixlen); } void ldpe_reset_nbrs(int af) { struct nbr *nbr; RB_FOREACH(nbr, nbr_id_head, &nbrs_by_id) { if (af == AF_UNSPEC || nbr->af == af) session_shutdown(nbr, S_SHUTDOWN, 0, 0); } } void ldpe_reset_ds_nbrs(void) { struct nbr *nbr; RB_FOREACH(nbr, nbr_id_head, &nbrs_by_id) { if (nbr->ds_tlv) session_shutdown(nbr, S_SHUTDOWN, 0, 0); } } void ldpe_remove_dynamic_tnbrs(int af) { struct tnbr *tnbr, *safe; RB_FOREACH_SAFE(tnbr, tnbr_head, &leconf->tnbr_tree, safe) { if (tnbr->af != af) continue; tnbr->flags &= ~F_TNBR_DYNAMIC; tnbr_check(leconf, tnbr); } } void ldpe_stop_init_backoff(int af) { struct nbr *nbr; RB_FOREACH(nbr, nbr_id_head, &nbrs_by_id) { if (nbr->af == af && nbr_pending_idtimer(nbr)) { nbr_stop_idtimer(nbr); nbr_establish_connection(nbr); } } } static void ldpe_iface_af_ctl(struct ctl_conn *c, int af, unsigned int idx) { struct iface *iface; struct iface_af *ia; struct ctl_iface *ictl; RB_FOREACH(iface, iface_head, &leconf->iface_tree) { if (idx == 0 || idx == iface->ifindex) { ia = iface_af_get(iface, af); if (!ia->enabled) continue; ictl = if_to_ctl(ia); imsg_compose_event(&c->iev, IMSG_CTL_SHOW_INTERFACE, 0, 0, -1, ictl, sizeof(struct ctl_iface)); } } } void ldpe_iface_ctl(struct ctl_conn *c, unsigned int idx) { ldpe_iface_af_ctl(c, AF_INET, idx); ldpe_iface_af_ctl(c, AF_INET6, idx); } void ldpe_adj_ctl(struct ctl_conn *c) { struct adj *adj; struct ctl_adj *actl; RB_FOREACH(adj, global_adj_head, &global.adj_tree) { actl = adj_to_ctl(adj); imsg_compose_event(&c->iev, IMSG_CTL_SHOW_DISCOVERY, 0, 0, -1, actl, sizeof(struct ctl_adj)); } imsg_compose_event(&c->iev, IMSG_CTL_END, 0, 0, -1, NULL, 0); } void ldpe_adj_detail_ctl(struct ctl_conn *c) { struct iface *iface; struct tnbr *tnbr; struct adj *adj; struct ctl_adj *actl; struct ctl_disc_if ictl; struct ctl_disc_tnbr tctl; imsg_compose_event(&c->iev, IMSG_CTL_SHOW_DISCOVERY, 0, 0, -1, NULL, 0); RB_FOREACH(iface, iface_head, &leconf->iface_tree) { memset(&ictl, 0, sizeof(ictl)); ictl.active_v4 = (iface->ipv4.state == IF_STA_ACTIVE); ictl.active_v6 = (iface->ipv6.state == IF_STA_ACTIVE); if (!ictl.active_v4 && !ictl.active_v6) continue; strlcpy(ictl.name, iface->name, sizeof(ictl.name)); if (RB_EMPTY(ia_adj_head, &iface->ipv4.adj_tree) && RB_EMPTY(ia_adj_head, &iface->ipv6.adj_tree)) ictl.no_adj = 1; imsg_compose_event(&c->iev, IMSG_CTL_SHOW_DISC_IFACE, 0, 0, -1, &ictl, sizeof(ictl)); RB_FOREACH(adj, ia_adj_head, &iface->ipv4.adj_tree) { actl = adj_to_ctl(adj); imsg_compose_event(&c->iev, IMSG_CTL_SHOW_DISC_ADJ, 0, 0, -1, actl, sizeof(struct ctl_adj)); } RB_FOREACH(adj, ia_adj_head, &iface->ipv6.adj_tree) { actl = adj_to_ctl(adj); imsg_compose_event(&c->iev, IMSG_CTL_SHOW_DISC_ADJ, 0, 0, -1, actl, sizeof(struct ctl_adj)); } } RB_FOREACH(tnbr, tnbr_head, &leconf->tnbr_tree) { memset(&tctl, 0, sizeof(tctl)); tctl.af = tnbr->af; tctl.addr = tnbr->addr; if (tnbr->adj == NULL) tctl.no_adj = 1; imsg_compose_event(&c->iev, IMSG_CTL_SHOW_DISC_TNBR, 0, 0, -1, &tctl, sizeof(tctl)); if (tnbr->adj == NULL) continue; actl = adj_to_ctl(tnbr->adj); imsg_compose_event(&c->iev, IMSG_CTL_SHOW_DISC_ADJ, 0, 0, -1, actl, sizeof(struct ctl_adj)); } imsg_compose_event(&c->iev, IMSG_CTL_END, 0, 0, -1, NULL, 0); } void ldpe_nbr_ctl(struct ctl_conn *c) { struct adj *adj; struct ctl_adj *actl; struct nbr *nbr; struct ctl_nbr *nctl; RB_FOREACH(nbr, nbr_addr_head, &nbrs_by_addr) { if (nbr->state == NBR_STA_PRESENT) continue; nctl = nbr_to_ctl(nbr); imsg_compose_event(&c->iev, IMSG_CTL_SHOW_NBR, 0, 0, -1, nctl, sizeof(struct ctl_nbr)); RB_FOREACH(adj, nbr_adj_head, &nbr->adj_tree) { actl = adj_to_ctl(adj); imsg_compose_event(&c->iev, IMSG_CTL_SHOW_NBR_DISC, 0, 0, -1, actl, sizeof(struct ctl_adj)); } imsg_compose_event(&c->iev, IMSG_CTL_SHOW_NBR_END, 0, 0, -1, NULL, 0); } imsg_compose_event(&c->iev, IMSG_CTL_END, 0, 0, -1, NULL, 0); } void mapping_list_add(struct mapping_head *mh, struct map *map) { struct mapping_entry *me; me = calloc(1, sizeof(*me)); if (me == NULL) fatal(__func__); me->map = *map; TAILQ_INSERT_TAIL(mh, me, entry); } void mapping_list_clr(struct mapping_head *mh) { struct mapping_entry *me; while ((me = TAILQ_FIRST(mh)) != NULL) { TAILQ_REMOVE(mh, me, entry); assert(me != TAILQ_FIRST(mh)); free(me); } } frr-7.2.1/ldpd/ldpe.h0000644000000000000000000002372313610377563011243 00000000000000/* $OpenBSD$ */ /* * Copyright (c) 2013, 2016 Renato Westphal * Copyright (c) 2009 Michele Marchetto * Copyright (c) 2004, 2005, 2008 Esben Norby * * Permission to use, copy, modify, and distribute this software for any * purpose with or without fee is hereby granted, provided that the above * copyright notice and this permission notice appear in all copies. * * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. */ #ifndef _LDPE_H_ #define _LDPE_H_ #include "queue.h" #include "openbsd-tree.h" #ifdef __OpenBSD__ #include #endif #include "ldpd.h" #define min(x,y) ((x) <= (y) ? (x) : (y)) #define max(x,y) ((x) > (y) ? (x) : (y)) /* forward declarations */ TAILQ_HEAD(mapping_head, mapping_entry); struct hello_source { enum hello_type type; struct { struct iface_af *ia; union ldpd_addr src_addr; } link; struct tnbr *target; }; struct adj { RB_ENTRY(adj) global_entry, nbr_entry, ia_entry; struct in_addr lsr_id; struct nbr *nbr; int ds_tlv; struct hello_source source; struct thread *inactivity_timer; uint16_t holdtime; union ldpd_addr trans_addr; }; RB_PROTOTYPE(global_adj_head, adj, global_entry, adj_compare) RB_PROTOTYPE(nbr_adj_head, adj, nbr_entry, adj_compare) RB_PROTOTYPE(ia_adj_head, adj, ia_entry, adj_compare) struct tcp_conn { struct nbr *nbr; int fd; struct ibuf_read *rbuf; struct evbuf wbuf; struct thread *rev; in_port_t lport; in_port_t rport; }; struct nbr { RB_ENTRY(nbr) id_tree, addr_tree, pid_tree; struct tcp_conn *tcp; struct nbr_adj_head adj_tree; /* adjacencies */ struct thread *ev_connect; struct thread *keepalive_timer; struct thread *keepalive_timeout; struct thread *init_timeout; struct thread *initdelay_timer; struct mapping_head mapping_list; struct mapping_head withdraw_list; struct mapping_head request_list; struct mapping_head release_list; struct mapping_head abortreq_list; uint32_t peerid; /* unique ID in DB */ int af; int ds_tlv; int v4_enabled; /* announce/process v4 msgs */ int v6_enabled; /* announce/process v6 msgs */ struct in_addr id; /* lsr id */ union ldpd_addr laddr; /* local address */ union ldpd_addr raddr; /* remote address */ uint32_t raddr_scope; /* remote address scope (v6) */ time_t uptime; int fd; int state; uint32_t conf_seqnum; int idtimer_cnt; uint16_t keepalive; uint16_t max_pdu_len; struct ldp_stats stats; struct { uint8_t established; uint32_t spi_in; uint32_t spi_out; enum auth_method method; char md5key[TCP_MD5_KEY_LEN]; } auth; int flags; }; #define F_NBR_GTSM_NEGOTIATED 0x01 #define F_NBR_CAP_DYNAMIC 0x02 #define F_NBR_CAP_TWCARD 0x04 #define F_NBR_CAP_UNOTIF 0x08 RB_HEAD(nbr_id_head, nbr); RB_PROTOTYPE(nbr_id_head, nbr, id_tree, nbr_id_compare) RB_HEAD(nbr_addr_head, nbr); RB_PROTOTYPE(nbr_addr_head, nbr, addr_tree, nbr_addr_compare) RB_HEAD(nbr_pid_head, nbr); RB_PROTOTYPE(nbr_pid_head, nbr, pid_tree, nbr_pid_compare) struct pending_conn { TAILQ_ENTRY(pending_conn) entry; int fd; int af; union ldpd_addr addr; struct thread *ev_timeout; }; #define PENDING_CONN_TIMEOUT 5 struct mapping_entry { TAILQ_ENTRY(mapping_entry) entry; struct map map; }; struct ldpd_sysdep { uint8_t no_pfkey; uint8_t no_md5sig; }; extern struct ldpd_conf *leconf; extern struct ldpd_sysdep sysdep; extern struct nbr_id_head nbrs_by_id; extern struct nbr_addr_head nbrs_by_addr; extern struct nbr_pid_head nbrs_by_pid; /* accept.c */ void accept_init(void); int accept_add(int, int (*)(struct thread *), void *); void accept_del(int); void accept_pause(void); void accept_unpause(void); /* hello.c */ int send_hello(enum hello_type, struct iface_af *, struct tnbr *); void recv_hello(struct in_addr, struct ldp_msg *, int, union ldpd_addr *, struct iface *, int, char *, uint16_t); /* init.c */ void send_init(struct nbr *); int recv_init(struct nbr *, char *, uint16_t); void send_capability(struct nbr *, uint16_t, int); int recv_capability(struct nbr *, char *, uint16_t); /* keepalive.c */ void send_keepalive(struct nbr *); int recv_keepalive(struct nbr *, char *, uint16_t); /* notification.c */ void send_notification_full(struct tcp_conn *, struct notify_msg *); void send_notification(struct tcp_conn *, uint32_t, uint32_t, uint16_t); void send_notification_rtlvs(struct nbr *, uint32_t, uint32_t, uint16_t, uint16_t, uint16_t, char *); int recv_notification(struct nbr *, char *, uint16_t); int gen_status_tlv(struct ibuf *, uint32_t, uint32_t, uint16_t); /* address.c */ void send_address_single(struct nbr *, struct if_addr *, int); void send_address_all(struct nbr *, int); void send_mac_withdrawal(struct nbr *, struct map *, uint8_t *); int recv_address(struct nbr *, char *, uint16_t); /* labelmapping.c */ #define PREFIX_SIZE(x) (((x) + 7) / 8) void send_labelmessage(struct nbr *, uint16_t, struct mapping_head *); int recv_labelmessage(struct nbr *, char *, uint16_t, uint16_t); int gen_pw_status_tlv(struct ibuf *, uint32_t); uint16_t len_fec_tlv(struct map *); int gen_fec_tlv(struct ibuf *, struct map *); int tlv_decode_fec_elm(struct nbr *, struct ldp_msg *, char *, uint16_t, struct map *); /* ldpe.c */ void ldpe(void); void ldpe_init(struct ldpd_init *); int ldpe_imsg_compose_parent(int, pid_t, void *, uint16_t); void ldpe_imsg_compose_parent_sync(int, pid_t, void *, uint16_t); int ldpe_imsg_compose_lde(int, uint32_t, pid_t, void *, uint16_t); int ldpe_acl_check(char *, int, union ldpd_addr *, uint8_t); void ldpe_reset_nbrs(int); void ldpe_reset_ds_nbrs(void); void ldpe_remove_dynamic_tnbrs(int); void ldpe_stop_init_backoff(int); struct ctl_conn; void ldpe_iface_ctl(struct ctl_conn *, unsigned int); void ldpe_adj_ctl(struct ctl_conn *); void ldpe_adj_detail_ctl(struct ctl_conn *); void ldpe_nbr_ctl(struct ctl_conn *); void mapping_list_add(struct mapping_head *, struct map *); void mapping_list_clr(struct mapping_head *); /* interface.c */ struct iface *if_new(const char *); void ldpe_if_init(struct iface *); void ldpe_if_exit(struct iface *); struct iface *if_lookup(struct ldpd_conf *, unsigned short); struct iface *if_lookup_name(struct ldpd_conf *, const char *); void if_update_info(struct iface *, struct kif *); struct iface_af *iface_af_get(struct iface *, int); void if_addr_add(struct kaddr *); void if_addr_del(struct kaddr *); void ldp_if_update(struct iface *, int); void if_update_all(int); uint16_t if_get_hello_holdtime(struct iface_af *); uint16_t if_get_hello_interval(struct iface_af *); struct ctl_iface *if_to_ctl(struct iface_af *); in_addr_t if_get_ipv4_addr(struct iface *); /* adjacency.c */ struct adj *adj_new(struct in_addr, struct hello_source *, union ldpd_addr *); void adj_del(struct adj *, uint32_t); struct adj *adj_find(struct in_addr, struct hello_source *); int adj_get_af(const struct adj *adj); void adj_start_itimer(struct adj *); void adj_stop_itimer(struct adj *); struct tnbr *tnbr_new(int, union ldpd_addr *); struct tnbr *tnbr_find(struct ldpd_conf *, int, union ldpd_addr *); struct tnbr *tnbr_check(struct ldpd_conf *, struct tnbr *); void tnbr_update(struct tnbr *); void tnbr_update_all(int); uint16_t tnbr_get_hello_holdtime(struct tnbr *); uint16_t tnbr_get_hello_interval(struct tnbr *); struct ctl_adj *adj_to_ctl(struct adj *); /* neighbor.c */ int nbr_fsm(struct nbr *, enum nbr_event); struct nbr *nbr_new(struct in_addr, int, int, union ldpd_addr *, uint32_t); void nbr_del(struct nbr *); struct nbr *nbr_find_ldpid(uint32_t); struct nbr *nbr_find_addr(int, union ldpd_addr *); struct nbr *nbr_find_peerid(uint32_t); int nbr_adj_count(struct nbr *, int); int nbr_session_active_role(struct nbr *); void nbr_stop_ktimer(struct nbr *); void nbr_stop_ktimeout(struct nbr *); void nbr_stop_itimeout(struct nbr *); void nbr_start_idtimer(struct nbr *); void nbr_stop_idtimer(struct nbr *); int nbr_pending_idtimer(struct nbr *); int nbr_pending_connect(struct nbr *); int nbr_establish_connection(struct nbr *); int nbr_gtsm_enabled(struct nbr *, struct nbr_params *); int nbr_gtsm_setup(int, int, struct nbr_params *); int nbr_gtsm_check(int, struct nbr *, struct nbr_params *); struct nbr_params *nbr_params_new(struct in_addr); struct nbr_params *nbr_params_find(struct ldpd_conf *, struct in_addr); uint16_t nbr_get_keepalive(int, struct in_addr); struct ctl_nbr *nbr_to_ctl(struct nbr *); void nbr_clear_ctl(struct ctl_nbr *); /* packet.c */ int gen_ldp_hdr(struct ibuf *, uint16_t); int gen_msg_hdr(struct ibuf *, uint16_t, uint16_t); int send_packet(int, int, union ldpd_addr *, struct iface_af *, void *, size_t); int disc_recv_packet(struct thread *); int session_accept(struct thread *); void session_accept_nbr(struct nbr *, int); void session_shutdown(struct nbr *, uint32_t, uint32_t, uint32_t); void session_close(struct nbr *); struct tcp_conn *tcp_new(int, struct nbr *); void pending_conn_del(struct pending_conn *); struct pending_conn *pending_conn_find(int, union ldpd_addr *); char *pkt_ptr; /* packet buffer */ /* pfkey.c */ #ifdef __OpenBSD__ int pfkey_read(int, struct sadb_msg *); int pfkey_establish(struct nbr *, struct nbr_params *); int pfkey_remove(struct nbr *); int pfkey_init(void); #endif /* l2vpn.c */ void ldpe_l2vpn_init(struct l2vpn *); void ldpe_l2vpn_exit(struct l2vpn *); void ldpe_l2vpn_pw_init(struct l2vpn_pw *); void ldpe_l2vpn_pw_exit(struct l2vpn_pw *); #endif /* _LDPE_H_ */ frr-7.2.1/ldpd/log.c0000644000000000000000000000544513610377563011074 00000000000000/* $OpenBSD$ */ /* * Copyright (c) 2003, 2004 Henning Brauer * * Permission to use, copy, modify, and distribute this software for any * purpose with or without fee is hereby granted, provided that the above * copyright notice and this permission notice appear in all copies. * * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. */ #include #include "ldpd.h" #include "ldpe.h" #include "lde.h" #include "log.h" #include #include const char *log_procname; void logit(int pri, const char *fmt, ...) { va_list ap; va_start(ap, fmt); vlog(pri, fmt, ap); va_end(ap); } void vlog(int pri, const char *fmt, va_list ap) { char buf[1024]; switch (ldpd_process) { case PROC_LDE_ENGINE: vsnprintf(buf, sizeof(buf), fmt, ap); lde_imsg_compose_parent_sync(IMSG_LOG, pri, buf, strlen(buf) + 1); break; case PROC_LDP_ENGINE: vsnprintf(buf, sizeof(buf), fmt, ap); ldpe_imsg_compose_parent_sync(IMSG_LOG, pri, buf, strlen(buf) + 1); break; case PROC_MAIN: vzlog(pri, fmt, ap); break; } } void log_warn(const char *emsg, ...) { char *nfmt; va_list ap; /* best effort to even work in out of memory situations */ if (emsg == NULL) logit(LOG_ERR, "%s", strerror(errno)); else { va_start(ap, emsg); if (asprintf(&nfmt, "%s: %s", emsg, strerror(errno)) == -1) { /* we tried it... */ vlog(LOG_ERR, emsg, ap); logit(LOG_ERR, "%s", strerror(errno)); } else { vlog(LOG_ERR, nfmt, ap); free(nfmt); } va_end(ap); } } void log_warnx(const char *emsg, ...) { va_list ap; va_start(ap, emsg); vlog(LOG_ERR, emsg, ap); va_end(ap); } void log_info(const char *emsg, ...) { va_list ap; va_start(ap, emsg); vlog(LOG_INFO, emsg, ap); va_end(ap); } void log_notice(const char *emsg, ...) { va_list ap; va_start(ap, emsg); vlog(LOG_NOTICE, emsg, ap); va_end(ap); } void log_debug(const char *emsg, ...) { va_list ap; va_start(ap, emsg); vlog(LOG_DEBUG, emsg, ap); va_end(ap); } void fatal(const char *emsg) { if (emsg == NULL) logit(LOG_CRIT, "fatal in %s: %s", log_procname, strerror(errno)); else if (errno) logit(LOG_CRIT, "fatal in %s: %s: %s", log_procname, emsg, strerror(errno)); else logit(LOG_CRIT, "fatal in %s: %s", log_procname, emsg); exit(1); } void fatalx(const char *emsg) { errno = 0; fatal(emsg); } frr-7.2.1/ldpd/log.h0000644000000000000000000000324113610377563011071 00000000000000/* $OpenBSD$ */ /* * Copyright (c) 2003, 2004 Henning Brauer * * Permission to use, copy, modify, and distribute this software for any * purpose with or without fee is hereby granted, provided that the above * copyright notice and this permission notice appear in all copies. * * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. */ #ifndef LOG_H #define LOG_H #include extern const char *log_procname; void logit(int, const char *, ...) __attribute__((__format__ (printf, 2, 3))); void vlog(int, const char *, va_list) __attribute__((__format__ (printf, 2, 0))); void log_warn(const char *, ...) __attribute__((__format__ (printf, 1, 2))); void log_warnx(const char *, ...) __attribute__((__format__ (printf, 1, 2))); void log_info(const char *, ...) __attribute__((__format__ (printf, 1, 2))); void log_notice(const char *, ...) __attribute__((__format__ (printf, 1, 2))); void log_debug(const char *, ...) __attribute__((__format__ (printf, 1, 2))); void fatal(const char *) __attribute__ ((noreturn)) __attribute__((__format__ (printf, 1, 0))); void fatalx(const char *) __attribute__ ((noreturn)) __attribute__((__format__ (printf, 1, 0))); #endif /* LOG_H */ frr-7.2.1/ldpd/logmsg.c0000644000000000000000000002437713610377563011610 00000000000000/* $OpenBSD$ */ /* * Copyright (c) 2003, 2004 Henning Brauer * * Permission to use, copy, modify, and distribute this software for any * purpose with or without fee is hereby granted, provided that the above * copyright notice and this permission notice appear in all copies. * * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. */ #include #include "mpls.h" #include "ldpd.h" #include "ldpe.h" #include "lde.h" #define NUM_LOGS 4 const char * log_sockaddr(void *vp) { static char buf[NUM_LOGS][NI_MAXHOST]; static int round = 0; struct sockaddr *sa = vp; round = (round + 1) % NUM_LOGS; if (getnameinfo(sa, sockaddr_len(sa), buf[round], NI_MAXHOST, NULL, 0, NI_NUMERICHOST)) return ("(unknown)"); else return (buf[round]); } const char * log_in6addr(const struct in6_addr *addr) { struct sockaddr_in6 sa_in6; memset(&sa_in6, 0, sizeof(sa_in6)); #ifdef HAVE_STRUCT_SOCKADDR_IN_SIN_LEN sa_in6.sin6_len = sizeof(sa_in6); #endif sa_in6.sin6_family = AF_INET6; sa_in6.sin6_addr = *addr; recoverscope(&sa_in6); return (log_sockaddr(&sa_in6)); } const char * log_in6addr_scope(const struct in6_addr *addr, unsigned int ifindex) { struct sockaddr_in6 sa_in6; memset(&sa_in6, 0, sizeof(sa_in6)); #ifdef HAVE_STRUCT_SOCKADDR_IN_SIN_LEN sa_in6.sin6_len = sizeof(sa_in6); #endif sa_in6.sin6_family = AF_INET6; sa_in6.sin6_addr = *addr; addscope(&sa_in6, ifindex); return (log_sockaddr(&sa_in6)); } const char * log_addr(int af, const union ldpd_addr *addr) { static char buf[NUM_LOGS][INET6_ADDRSTRLEN]; static int round = 0; switch (af) { case AF_INET: round = (round + 1) % NUM_LOGS; if (inet_ntop(AF_INET, &addr->v4, buf[round], sizeof(buf[round])) == NULL) return ("???"); return (buf[round]); case AF_INET6: return (log_in6addr(&addr->v6)); default: break; } return ("???"); } #define TF_BUFS 4 #define TF_LEN 32 char * log_label(uint32_t label) { char *buf; static char tfbuf[TF_BUFS][TF_LEN]; /* ring buffer */ static int idx = 0; buf = tfbuf[idx++]; if (idx == TF_BUFS) idx = 0; switch (label) { case NO_LABEL: snprintf(buf, TF_LEN, "-"); break; case MPLS_LABEL_IMPLICIT_NULL: snprintf(buf, TF_LEN, "imp-null"); break; case MPLS_LABEL_IPV4_EXPLICIT_NULL: case MPLS_LABEL_IPV6_EXPLICIT_NULL: snprintf(buf, TF_LEN, "exp-null"); break; default: snprintf(buf, TF_LEN, "%u", label); break; } return (buf); } const char * log_time(time_t t) { char *buf; static char tfbuf[TF_BUFS][TF_LEN]; /* ring buffer */ static int idx = 0; unsigned int sec, min, hrs, day, week; buf = tfbuf[idx++]; if (idx == TF_BUFS) idx = 0; week = t; sec = week % 60; week /= 60; min = week % 60; week /= 60; hrs = week % 24; week /= 24; day = week % 7; week /= 7; if (week > 0) snprintf(buf, TF_LEN, "%02uw%01ud%02uh", week, day, hrs); else if (day > 0) snprintf(buf, TF_LEN, "%01ud%02uh%02um", day, hrs, min); else snprintf(buf, TF_LEN, "%02u:%02u:%02u", hrs, min, sec); return (buf); } char * log_hello_src(const struct hello_source *src) { static char buf[64]; switch (src->type) { case HELLO_LINK: snprintf(buf, sizeof(buf), "iface %s", src->link.ia->iface->name); break; case HELLO_TARGETED: snprintf(buf, sizeof(buf), "source %s", log_addr(src->target->af, &src->target->addr)); break; } return (buf); } const char * log_map(const struct map *map) { static char buf[128]; switch (map->type) { case MAP_TYPE_WILDCARD: if (snprintf(buf, sizeof(buf), "wildcard") < 0) return ("???"); break; case MAP_TYPE_PREFIX: if (snprintf(buf, sizeof(buf), "%s/%u", log_addr(map->fec.prefix.af, &map->fec.prefix.prefix), map->fec.prefix.prefixlen) == -1) return ("???"); break; case MAP_TYPE_PWID: if (snprintf(buf, sizeof(buf), "pw-id %u group-id %u (%s)", map->fec.pwid.pwid, map->fec.pwid.group_id, pw_type_name(map->fec.pwid.type)) == -1) return ("???"); break; case MAP_TYPE_TYPED_WCARD: if (snprintf(buf, sizeof(buf), "typed wildcard") < 0) return ("???"); switch (map->fec.twcard.type) { case MAP_TYPE_PREFIX: if (snprintf(buf + strlen(buf), sizeof(buf) - strlen(buf), " (prefix, address-family %s)", af_name(map->fec.twcard.u.prefix_af)) < 0) return ("???"); break; case MAP_TYPE_PWID: if (snprintf(buf + strlen(buf), sizeof(buf) - strlen(buf), " (pwid, type %s)", pw_type_name(map->fec.twcard.u.pw_type)) < 0) return ("???"); break; default: if (snprintf(buf + strlen(buf), sizeof(buf) - strlen(buf), " (unknown type)") < 0) return ("???"); break; } break; default: return ("???"); } return (buf); } const char * log_fec(const struct fec *fec) { static char buf[64]; union ldpd_addr addr; switch (fec->type) { case FEC_TYPE_IPV4: addr.v4 = fec->u.ipv4.prefix; if (snprintf(buf, sizeof(buf), "ipv4 %s/%u", log_addr(AF_INET, &addr), fec->u.ipv4.prefixlen) == -1) return ("???"); break; case FEC_TYPE_IPV6: addr.v6 = fec->u.ipv6.prefix; if (snprintf(buf, sizeof(buf), "ipv6 %s/%u", log_addr(AF_INET6, &addr), fec->u.ipv6.prefixlen) == -1) return ("???"); break; case FEC_TYPE_PWID: if (snprintf(buf, sizeof(buf), "pwid %u (%s) - %s", fec->u.pwid.pwid, pw_type_name(fec->u.pwid.type), inet_ntoa(fec->u.pwid.lsr_id)) == -1) return ("???"); break; default: return ("???"); } return (buf); } /* names */ const char * af_name(int af) { switch (af) { case AF_INET: return ("ipv4"); case AF_INET6: return ("ipv6"); #ifdef AF_MPLS case AF_MPLS: return ("mpls"); #endif default: return ("UNKNOWN"); } } const char * socket_name(int type) { switch (type) { case LDP_SOCKET_DISC: return ("discovery"); case LDP_SOCKET_EDISC: return ("extended discovery"); case LDP_SOCKET_SESSION: return ("session"); default: return ("UNKNOWN"); } } const char * nbr_state_name(int state) { switch (state) { case NBR_STA_PRESENT: return ("PRESENT"); case NBR_STA_INITIAL: return ("INITIALIZED"); case NBR_STA_OPENREC: return ("OPENREC"); case NBR_STA_OPENSENT: return ("OPENSENT"); case NBR_STA_OPER: return ("OPERATIONAL"); default: return ("UNKNOWN"); } } const char * if_state_name(int state) { switch (state) { case IF_STA_DOWN: return ("DOWN"); case IF_STA_ACTIVE: return ("ACTIVE"); default: return ("UNKNOWN"); } } const char * if_type_name(enum iface_type type) { switch (type) { case IF_TYPE_POINTOPOINT: return ("POINTOPOINT"); case IF_TYPE_BROADCAST: return ("BROADCAST"); } /* NOTREACHED */ return ("UNKNOWN"); } const char * msg_name(uint16_t msg) { static char buf[16]; switch (msg) { case MSG_TYPE_NOTIFICATION: return ("notification"); case MSG_TYPE_HELLO: return ("hello"); case MSG_TYPE_INIT: return ("initialization"); case MSG_TYPE_KEEPALIVE: return ("keepalive"); case MSG_TYPE_CAPABILITY: return ("capability"); case MSG_TYPE_ADDR: return ("address"); case MSG_TYPE_ADDRWITHDRAW: return ("address withdraw"); case MSG_TYPE_LABELMAPPING: return ("label mapping"); case MSG_TYPE_LABELREQUEST: return ("label request"); case MSG_TYPE_LABELWITHDRAW: return ("label withdraw"); case MSG_TYPE_LABELRELEASE: return ("label release"); case MSG_TYPE_LABELABORTREQ: default: snprintf(buf, sizeof(buf), "[%08x]", msg); return (buf); } } const char * status_code_name(uint32_t status) { static char buf[16]; switch (status) { case S_SUCCESS: return ("Success"); case S_BAD_LDP_ID: return ("Bad LDP Identifier"); case S_BAD_PROTO_VER: return ("Bad Protocol Version"); case S_BAD_PDU_LEN: return ("Bad PDU Length"); case S_UNKNOWN_MSG: return ("Unknown Message Type"); case S_BAD_MSG_LEN: return ("Bad Message Length"); case S_UNKNOWN_TLV: return ("Unknown TLV"); case S_BAD_TLV_LEN: return ("Bad TLV Length"); case S_BAD_TLV_VAL: return ("Malformed TLV Value"); case S_HOLDTIME_EXP: return ("Hold Timer Expired"); case S_SHUTDOWN: return ("Shutdown"); case S_LOOP_DETECTED: return ("Loop Detected"); case S_UNKNOWN_FEC: return ("Unknown FEC"); case S_NO_ROUTE: return ("No Route"); case S_NO_LABEL_RES: return ("No Label Resources"); case S_AVAILABLE: return ("Label Resources Available"); case S_NO_HELLO: return ("Session Rejected, No Hello"); case S_PARM_ADV_MODE: return ("Rejected Advertisement Mode Parameter"); case S_MAX_PDU_LEN: return ("Rejected Max PDU Length Parameter"); case S_PARM_L_RANGE: return ("Rejected Label Range Parameter"); case S_KEEPALIVE_TMR: return ("KeepAlive Timer Expired"); case S_LAB_REQ_ABRT: return ("Label Request Aborted"); case S_MISS_MSG: return ("Missing Message Parameters"); case S_UNSUP_ADDR: return ("Unsupported Address Family"); case S_KEEPALIVE_BAD: return ("Bad KeepAlive Time"); case S_INTERN_ERR: return ("Internal Error"); case S_ILLEGAL_CBIT: return ("Illegal C-Bit"); case S_WRONG_CBIT: return ("Wrong C-Bit"); case S_INCPT_BITRATE: return ("Incompatible bit-rate"); case S_CEP_MISCONF: return ("CEP-TDM mis-configuration"); case S_PW_STATUS: return ("PW Status"); case S_UNASSIGN_TAI: return ("Unassigned/Unrecognized TAI"); case S_MISCONF_ERR: return ("Generic Misconfiguration Error"); case S_WITHDRAW_MTHD: return ("Label Withdraw PW Status Method"); case S_UNSSUPORTDCAP: return ("Unsupported Capability"); case S_ENDOFLIB: return ("End-of-LIB"); case S_TRANS_MISMTCH: return ("Transport Connection Mismatch"); case S_DS_NONCMPLNCE: return ("Dual-Stack Noncompliance"); default: snprintf(buf, sizeof(buf), "[%08x]", status); return (buf); } } const char * pw_type_name(uint16_t pw_type) { static char buf[64]; switch (pw_type) { case PW_TYPE_ETHERNET_TAGGED: return ("Eth Tagged"); case PW_TYPE_ETHERNET: return ("Ethernet"); case PW_TYPE_WILDCARD: return ("Wildcard"); default: snprintf(buf, sizeof(buf), "[%0x]", pw_type); return (buf); } } frr-7.2.1/ldpd/neighbor.c0000644000000000000000000004736013610377563012112 00000000000000/* $OpenBSD$ */ /* * Copyright (c) 2013, 2016 Renato Westphal * Copyright (c) 2009 Michele Marchetto * Copyright (c) 2005 Claudio Jeker * Copyright (c) 2004, 2005, 2008 Esben Norby * * Permission to use, copy, modify, and distribute this software for any * purpose with or without fee is hereby granted, provided that the above * copyright notice and this permission notice appear in all copies. * * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. */ #include #include "ldpd.h" #include "ldpe.h" #include "lde.h" #include "log.h" static __inline int nbr_id_compare(const struct nbr *, const struct nbr *); static __inline int nbr_addr_compare(const struct nbr *, const struct nbr *); static __inline int nbr_pid_compare(const struct nbr *, const struct nbr *); static void nbr_update_peerid(struct nbr *); static int nbr_ktimer(struct thread *); static void nbr_start_ktimer(struct nbr *); static int nbr_ktimeout(struct thread *); static void nbr_start_ktimeout(struct nbr *); static int nbr_itimeout(struct thread *); static void nbr_start_itimeout(struct nbr *); static int nbr_idtimer(struct thread *); static int nbr_act_session_operational(struct nbr *); static void nbr_send_labelmappings(struct nbr *); static __inline int nbr_params_compare(const struct nbr_params *, const struct nbr_params *); RB_GENERATE(nbr_id_head, nbr, id_tree, nbr_id_compare) RB_GENERATE(nbr_addr_head, nbr, addr_tree, nbr_addr_compare) RB_GENERATE(nbr_pid_head, nbr, pid_tree, nbr_pid_compare) RB_GENERATE(nbrp_head, nbr_params, entry, nbr_params_compare) struct { int state; enum nbr_event event; enum nbr_action action; int new_state; } nbr_fsm_tbl[] = { /* current state event that happened action to take resulting state */ /* Passive Role */ {NBR_STA_PRESENT, NBR_EVT_MATCH_ADJ, NBR_ACT_NOTHING, NBR_STA_INITIAL}, {NBR_STA_INITIAL, NBR_EVT_INIT_RCVD, NBR_ACT_PASSIVE_INIT, NBR_STA_OPENREC}, {NBR_STA_OPENREC, NBR_EVT_KEEPALIVE_RCVD, NBR_ACT_SESSION_EST, NBR_STA_OPER}, /* Active Role */ {NBR_STA_PRESENT, NBR_EVT_CONNECT_UP, NBR_ACT_CONNECT_SETUP, NBR_STA_INITIAL}, {NBR_STA_INITIAL, NBR_EVT_INIT_SENT, NBR_ACT_NOTHING, NBR_STA_OPENSENT}, {NBR_STA_OPENSENT, NBR_EVT_INIT_RCVD, NBR_ACT_KEEPALIVE_SEND, NBR_STA_OPENREC}, /* Session Maintenance */ {NBR_STA_OPER, NBR_EVT_PDU_RCVD, NBR_ACT_RST_KTIMEOUT, 0}, {NBR_STA_SESSION, NBR_EVT_PDU_RCVD, NBR_ACT_NOTHING, 0}, {NBR_STA_OPER, NBR_EVT_PDU_SENT, NBR_ACT_RST_KTIMER, 0}, {NBR_STA_SESSION, NBR_EVT_PDU_SENT, NBR_ACT_NOTHING, 0}, /* Session Close */ {NBR_STA_PRESENT, NBR_EVT_CLOSE_SESSION, NBR_ACT_NOTHING, 0}, {NBR_STA_SESSION, NBR_EVT_CLOSE_SESSION, NBR_ACT_CLOSE_SESSION, NBR_STA_PRESENT}, {-1, NBR_EVT_NOTHING, NBR_ACT_NOTHING, 0}, }; const char * const nbr_event_names[] = { "NOTHING", "ADJACENCY MATCHED", "CONNECTION UP", "SESSION CLOSE", "INIT RECEIVED", "KEEPALIVE RECEIVED", "PDU RECEIVED", "PDU SENT", "INIT SENT" }; const char * const nbr_action_names[] = { "NOTHING", "RESET KEEPALIVE TIMEOUT", "START NEIGHBOR SESSION", "RESET KEEPALIVE TIMER", "SETUP NEIGHBOR CONNECTION", "SEND INIT AND KEEPALIVE", "SEND KEEPALIVE", "CLOSE SESSION" }; struct nbr_id_head nbrs_by_id = RB_INITIALIZER(&nbrs_by_id); struct nbr_addr_head nbrs_by_addr = RB_INITIALIZER(&nbrs_by_addr); struct nbr_pid_head nbrs_by_pid = RB_INITIALIZER(&nbrs_by_pid); static __inline int nbr_id_compare(const struct nbr *a, const struct nbr *b) { return (ntohl(a->id.s_addr) - ntohl(b->id.s_addr)); } static __inline int nbr_addr_compare(const struct nbr *a, const struct nbr *b) { if (a->af < b->af) return (-1); if (a->af > b->af) return (1); return (ldp_addrcmp(a->af, &a->raddr, &b->raddr)); } static __inline int nbr_pid_compare(const struct nbr *a, const struct nbr *b) { return (a->peerid - b->peerid); } int nbr_fsm(struct nbr *nbr, enum nbr_event event) { struct timeval now; int old_state; int new_state = 0; int i; old_state = nbr->state; for (i = 0; nbr_fsm_tbl[i].state != -1; i++) if ((nbr_fsm_tbl[i].state & old_state) && (nbr_fsm_tbl[i].event == event)) { new_state = nbr_fsm_tbl[i].new_state; break; } if (nbr_fsm_tbl[i].state == -1) { /* event outside of the defined fsm, ignore it. */ log_warnx("%s: lsr-id %s, event %s not expected in " "state %s", __func__, inet_ntoa(nbr->id), nbr_event_names[event], nbr_state_name(old_state)); return (0); } if (new_state != 0) nbr->state = new_state; if (old_state != nbr->state) { log_debug("%s: event %s resulted in action %s and " "changing state for lsr-id %s from %s to %s", __func__, nbr_event_names[event], nbr_action_names[nbr_fsm_tbl[i].action], inet_ntoa(nbr->id), nbr_state_name(old_state), nbr_state_name(nbr->state)); if (nbr->state == NBR_STA_OPER) { gettimeofday(&now, NULL); nbr->uptime = now.tv_sec; } } if (nbr->state == NBR_STA_OPER || nbr->state == NBR_STA_PRESENT) nbr_stop_itimeout(nbr); else nbr_start_itimeout(nbr); switch (nbr_fsm_tbl[i].action) { case NBR_ACT_RST_KTIMEOUT: nbr_start_ktimeout(nbr); break; case NBR_ACT_RST_KTIMER: nbr_start_ktimer(nbr); break; case NBR_ACT_SESSION_EST: nbr_act_session_operational(nbr); nbr_start_ktimer(nbr); nbr_start_ktimeout(nbr); if (nbr->v4_enabled) send_address_all(nbr, AF_INET); if (nbr->v6_enabled) send_address_all(nbr, AF_INET6); nbr_send_labelmappings(nbr); break; case NBR_ACT_CONNECT_SETUP: nbr->tcp = tcp_new(nbr->fd, nbr); /* trigger next state */ send_init(nbr); nbr_fsm(nbr, NBR_EVT_INIT_SENT); break; case NBR_ACT_PASSIVE_INIT: send_init(nbr); send_keepalive(nbr); break; case NBR_ACT_KEEPALIVE_SEND: nbr_start_ktimeout(nbr); send_keepalive(nbr); break; case NBR_ACT_CLOSE_SESSION: ldpe_imsg_compose_lde(IMSG_NEIGHBOR_DOWN, nbr->peerid, 0, NULL, 0); session_close(nbr); break; case NBR_ACT_NOTHING: /* do nothing */ break; } return (0); } struct nbr * nbr_new(struct in_addr id, int af, int ds_tlv, union ldpd_addr *addr, uint32_t scope_id) { struct nbr *nbr; struct nbr_params *nbrp; struct adj *adj; struct pending_conn *pconn; log_debug("%s: lsr-id %s transport-address %s", __func__, inet_ntoa(id), log_addr(af, addr)); if ((nbr = calloc(1, sizeof(*nbr))) == NULL) fatal(__func__); RB_INIT(nbr_adj_head, &nbr->adj_tree); nbr->state = NBR_STA_PRESENT; nbr->peerid = 0; nbr->af = af; nbr->ds_tlv = ds_tlv; if (af == AF_INET || ds_tlv) nbr->v4_enabled = 1; if (af == AF_INET6 || ds_tlv) nbr->v6_enabled = 1; nbr->id = id; nbr->laddr = (ldp_af_conf_get(leconf, af))->trans_addr; nbr->raddr = *addr; nbr->raddr_scope = scope_id; nbr->conf_seqnum = 0; RB_FOREACH(adj, global_adj_head, &global.adj_tree) { if (adj->lsr_id.s_addr == nbr->id.s_addr) { adj->nbr = nbr; RB_INSERT(nbr_adj_head, &nbr->adj_tree, adj); } } if (RB_INSERT(nbr_id_head, &nbrs_by_id, nbr) != NULL) fatalx("nbr_new: RB_INSERT(nbrs_by_id) failed"); if (RB_INSERT(nbr_addr_head, &nbrs_by_addr, nbr) != NULL) fatalx("nbr_new: RB_INSERT(nbrs_by_addr) failed"); TAILQ_INIT(&nbr->mapping_list); TAILQ_INIT(&nbr->withdraw_list); TAILQ_INIT(&nbr->request_list); TAILQ_INIT(&nbr->release_list); TAILQ_INIT(&nbr->abortreq_list); nbrp = nbr_params_find(leconf, nbr->id); if (nbrp) { nbr->auth.method = nbrp->auth.method; #ifdef __OpenBSD__ if (pfkey_establish(nbr, nbrp) == -1) fatalx("pfkey setup failed"); #else sock_set_md5sig( (ldp_af_global_get(&global, nbr->af))->ldp_session_socket, nbr->af, &nbr->raddr, nbrp->auth.md5key); #endif } pconn = pending_conn_find(nbr->af, &nbr->raddr); if (pconn) { session_accept_nbr(nbr, pconn->fd); pending_conn_del(pconn); } return (nbr); } void nbr_del(struct nbr *nbr) { struct adj *adj; log_debug("%s: lsr-id %s", __func__, inet_ntoa(nbr->id)); nbr_fsm(nbr, NBR_EVT_CLOSE_SESSION); #ifdef __OpenBSD__ pfkey_remove(nbr); #else sock_set_md5sig( (ldp_af_global_get(&global, nbr->af))->ldp_session_socket, nbr->af, &nbr->raddr, NULL); #endif nbr->auth.method = AUTH_NONE; if (nbr_pending_connect(nbr)) THREAD_WRITE_OFF(nbr->ev_connect); nbr_stop_ktimer(nbr); nbr_stop_ktimeout(nbr); nbr_stop_itimeout(nbr); nbr_stop_idtimer(nbr); mapping_list_clr(&nbr->mapping_list); mapping_list_clr(&nbr->withdraw_list); mapping_list_clr(&nbr->request_list); mapping_list_clr(&nbr->release_list); mapping_list_clr(&nbr->abortreq_list); while (!RB_EMPTY(nbr_adj_head, &nbr->adj_tree)) { adj = RB_ROOT(nbr_adj_head, &nbr->adj_tree); adj->nbr = NULL; RB_REMOVE(nbr_adj_head, &nbr->adj_tree, adj); } if (nbr->peerid) RB_REMOVE(nbr_pid_head, &nbrs_by_pid, nbr); RB_REMOVE(nbr_id_head, &nbrs_by_id, nbr); RB_REMOVE(nbr_addr_head, &nbrs_by_addr, nbr); free(nbr); } static void nbr_update_peerid(struct nbr *nbr) { static uint32_t peercnt = 1; if (nbr->peerid) RB_REMOVE(nbr_pid_head, &nbrs_by_pid, nbr); /* get next unused peerid */ while (nbr_find_peerid(++peercnt)) ; nbr->peerid = peercnt; if (RB_INSERT(nbr_pid_head, &nbrs_by_pid, nbr) != NULL) fatalx("nbr_update_peerid: RB_INSERT(nbrs_by_pid) failed"); } struct nbr * nbr_find_ldpid(uint32_t lsr_id) { struct nbr n; n.id.s_addr = lsr_id; return (RB_FIND(nbr_id_head, &nbrs_by_id, &n)); } struct nbr * nbr_find_addr(int af, union ldpd_addr *addr) { struct nbr n; n.af = af; n.raddr = *addr; return (RB_FIND(nbr_addr_head, &nbrs_by_addr, &n)); } struct nbr * nbr_find_peerid(uint32_t peerid) { struct nbr n; n.peerid = peerid; return (RB_FIND(nbr_pid_head, &nbrs_by_pid, &n)); } int nbr_adj_count(struct nbr *nbr, int af) { struct adj *adj; int total = 0; RB_FOREACH(adj, nbr_adj_head, &nbr->adj_tree) if (adj_get_af(adj) == af) total++; return (total); } int nbr_session_active_role(struct nbr *nbr) { if (ldp_addrcmp(nbr->af, &nbr->laddr, &nbr->raddr) > 0) return (1); return (0); } /* timers */ /* Keepalive timer: timer to send keepalive message to neighbors */ static int nbr_ktimer(struct thread *thread) { struct nbr *nbr = THREAD_ARG(thread); nbr->keepalive_timer = NULL; send_keepalive(nbr); nbr_start_ktimer(nbr); return (0); } static void nbr_start_ktimer(struct nbr *nbr) { int secs; /* send three keepalives per period */ secs = nbr->keepalive / KEEPALIVE_PER_PERIOD; THREAD_TIMER_OFF(nbr->keepalive_timer); nbr->keepalive_timer = NULL; thread_add_timer(master, nbr_ktimer, nbr, secs, &nbr->keepalive_timer); } void nbr_stop_ktimer(struct nbr *nbr) { THREAD_TIMER_OFF(nbr->keepalive_timer); } /* Keepalive timeout: if the nbr hasn't sent keepalive */ static int nbr_ktimeout(struct thread *thread) { struct nbr *nbr = THREAD_ARG(thread); nbr->keepalive_timeout = NULL; log_debug("%s: lsr-id %s", __func__, inet_ntoa(nbr->id)); session_shutdown(nbr, S_KEEPALIVE_TMR, 0, 0); return (0); } static void nbr_start_ktimeout(struct nbr *nbr) { THREAD_TIMER_OFF(nbr->keepalive_timeout); nbr->keepalive_timeout = NULL; thread_add_timer(master, nbr_ktimeout, nbr, nbr->keepalive, &nbr->keepalive_timeout); } void nbr_stop_ktimeout(struct nbr *nbr) { THREAD_TIMER_OFF(nbr->keepalive_timeout); } /* Session initialization timeout: if nbr got stuck in the initialization FSM */ static int nbr_itimeout(struct thread *thread) { struct nbr *nbr = THREAD_ARG(thread); log_debug("%s: lsr-id %s", __func__, inet_ntoa(nbr->id)); nbr_fsm(nbr, NBR_EVT_CLOSE_SESSION); return (0); } static void nbr_start_itimeout(struct nbr *nbr) { int secs; secs = INIT_FSM_TIMEOUT; THREAD_TIMER_OFF(nbr->init_timeout); nbr->init_timeout = NULL; thread_add_timer(master, nbr_itimeout, nbr, secs, &nbr->init_timeout); } void nbr_stop_itimeout(struct nbr *nbr) { THREAD_TIMER_OFF(nbr->init_timeout); } /* Init delay timer: timer to retry to iniziatize session */ static int nbr_idtimer(struct thread *thread) { struct nbr *nbr = THREAD_ARG(thread); nbr->initdelay_timer = NULL; log_debug("%s: lsr-id %s", __func__, inet_ntoa(nbr->id)); nbr_establish_connection(nbr); return (0); } void nbr_start_idtimer(struct nbr *nbr) { int secs; secs = INIT_DELAY_TMR; switch(nbr->idtimer_cnt) { default: /* do not further increase the counter */ secs = MAX_DELAY_TMR; break; case 2: secs *= 2; /* FALLTHROUGH */ case 1: secs *= 2; /* FALLTHROUGH */ case 0: nbr->idtimer_cnt++; break; } THREAD_TIMER_OFF(nbr->initdelay_timer); nbr->initdelay_timer = NULL; thread_add_timer(master, nbr_idtimer, nbr, secs, &nbr->initdelay_timer); } void nbr_stop_idtimer(struct nbr *nbr) { THREAD_TIMER_OFF(nbr->initdelay_timer); } int nbr_pending_idtimer(struct nbr *nbr) { return (nbr->initdelay_timer != NULL); } int nbr_pending_connect(struct nbr *nbr) { return (nbr->ev_connect != NULL); } static int nbr_connect_cb(struct thread *thread) { struct nbr *nbr = THREAD_ARG(thread); int error; socklen_t len; nbr->ev_connect = NULL; len = sizeof(error); if (getsockopt(nbr->fd, SOL_SOCKET, SO_ERROR, &error, &len) < 0) { log_warn("%s: getsockopt SOL_SOCKET SO_ERROR", __func__); return (0); } if (error) { close(nbr->fd); errno = error; log_debug("%s: error while connecting to %s: %s", __func__, log_addr(nbr->af, &nbr->raddr), strerror(errno)); return (0); } nbr_fsm(nbr, NBR_EVT_CONNECT_UP); return (0); } int nbr_establish_connection(struct nbr *nbr) { union sockunion local_su; union sockunion remote_su; struct adj *adj; struct nbr_params *nbrp; #ifdef __OpenBSD__ int opt = 1; #endif nbr->fd = socket(nbr->af, SOCK_STREAM, 0); if (nbr->fd == -1) { log_warn("%s: error while creating socket", __func__); return (-1); } sock_set_nonblock(nbr->fd); nbrp = nbr_params_find(leconf, nbr->id); if (nbrp && nbrp->auth.method == AUTH_MD5SIG) { #ifdef __OpenBSD__ if (sysdep.no_pfkey || sysdep.no_md5sig) { log_warnx("md5sig configured but not available"); close(nbr->fd); return (-1); } if (setsockopt(nbr->fd, IPPROTO_TCP, TCP_MD5SIG, &opt, sizeof(opt)) == -1) { log_warn("setsockopt md5sig"); close(nbr->fd); return (-1); } #else sock_set_md5sig(nbr->fd, nbr->af, &nbr->raddr, nbrp->auth.md5key); #endif } addr2sa(nbr->af, &nbr->laddr, 0, &local_su); addr2sa(nbr->af, &nbr->raddr, LDP_PORT, &remote_su); if (nbr->af == AF_INET6 && nbr->raddr_scope) addscope(&remote_su.sin6, nbr->raddr_scope); if (bind(nbr->fd, &local_su.sa, sockaddr_len(&local_su.sa)) == -1) { log_warn("%s: error while binding socket to %s", __func__, log_sockaddr(&local_su.sa)); close(nbr->fd); return (-1); } if (nbr_gtsm_check(nbr->fd, nbr, nbrp)) { close(nbr->fd); return (-1); } /* * Send an extra hello to guarantee that the remote peer has formed * an adjacency as well. */ RB_FOREACH(adj, nbr_adj_head, &nbr->adj_tree) send_hello(adj->source.type, adj->source.link.ia, adj->source.target); if (connect(nbr->fd, &remote_su.sa, sockaddr_len(&remote_su.sa)) == -1) { if (errno == EINPROGRESS) { thread_add_write(master, nbr_connect_cb, nbr, nbr->fd, &nbr->ev_connect); return (0); } log_warn("%s: error while connecting to %s", __func__, log_sockaddr(&remote_su.sa)); close(nbr->fd); return (-1); } /* connection completed immediately */ nbr_fsm(nbr, NBR_EVT_CONNECT_UP); return (0); } int nbr_gtsm_enabled(struct nbr *nbr, struct nbr_params *nbrp) { /* * RFC 6720 - Section 3: * "This document allows for the implementation to provide an option to * statically (e.g., via configuration) and/or dynamically override the * default behavior and enable/disable GTSM on a per-peer basis". */ if (nbrp && (nbrp->flags & F_NBRP_GTSM)) return (nbrp->gtsm_enabled); if ((ldp_af_conf_get(leconf, nbr->af))->flags & F_LDPD_AF_NO_GTSM) return (0); /* By default, GTSM support has to be negotiated for LDPv4 */ if (nbr->af == AF_INET && !(nbr->flags & F_NBR_GTSM_NEGOTIATED)) return (0); return (1); } int nbr_gtsm_setup(int fd, int af, struct nbr_params *nbrp) { int ttl = 255; if (nbrp && (nbrp->flags & F_NBRP_GTSM_HOPS)) ttl = 256 - nbrp->gtsm_hops; switch (af) { case AF_INET: if (sock_set_ipv4_minttl(fd, ttl) == -1) return (-1); ttl = 255; if (sock_set_ipv4_ucast_ttl(fd, ttl) == -1) return (-1); break; case AF_INET6: /* ignore any possible error */ sock_set_ipv6_minhopcount(fd, ttl); ttl = 255; if (sock_set_ipv6_ucast_hops(fd, ttl) == -1) return (-1); break; default: fatalx("nbr_gtsm_setup: unknown af"); } return (0); } int nbr_gtsm_check(int fd, struct nbr *nbr, struct nbr_params *nbrp) { if (!nbr_gtsm_enabled(nbr, nbrp)) { switch (nbr->af) { case AF_INET: sock_set_ipv4_ucast_ttl(fd, -1); break; case AF_INET6: /* * Send packets with a Hop Limit of 255 even when GSTM * is disabled to guarantee interoperability. */ sock_set_ipv6_ucast_hops(fd, 255); break; default: fatalx("nbr_gtsm_check: unknown af"); break; } return (0); } if (nbr_gtsm_setup(fd, nbr->af, nbrp) == -1) { log_warnx("%s: error enabling GTSM for lsr-id %s", __func__, inet_ntoa(nbr->id)); return (-1); } return (0); } static int nbr_act_session_operational(struct nbr *nbr) { struct lde_nbr lde_nbr; nbr->idtimer_cnt = 0; /* this is necessary to avoid ipc synchronization issues */ nbr_update_peerid(nbr); memset(&lde_nbr, 0, sizeof(lde_nbr)); lde_nbr.id = nbr->id; lde_nbr.v4_enabled = nbr->v4_enabled; lde_nbr.v6_enabled = nbr->v6_enabled; lde_nbr.flags = nbr->flags; return (ldpe_imsg_compose_lde(IMSG_NEIGHBOR_UP, nbr->peerid, 0, &lde_nbr, sizeof(lde_nbr))); } static void nbr_send_labelmappings(struct nbr *nbr) { ldpe_imsg_compose_lde(IMSG_LABEL_MAPPING_FULL, nbr->peerid, 0, NULL, 0); } static __inline int nbr_params_compare(const struct nbr_params *a, const struct nbr_params *b) { return (ntohl(a->lsr_id.s_addr) - ntohl(b->lsr_id.s_addr)); } struct nbr_params * nbr_params_new(struct in_addr lsr_id) { struct nbr_params *nbrp; if ((nbrp = calloc(1, sizeof(*nbrp))) == NULL) fatal(__func__); nbrp->lsr_id = lsr_id; nbrp->auth.method = AUTH_NONE; return (nbrp); } struct nbr_params * nbr_params_find(struct ldpd_conf *xconf, struct in_addr lsr_id) { struct nbr_params nbrp; nbrp.lsr_id = lsr_id; return (RB_FIND(nbrp_head, &xconf->nbrp_tree, &nbrp)); } uint16_t nbr_get_keepalive(int af, struct in_addr lsr_id) { struct nbr_params *nbrp; nbrp = nbr_params_find(leconf, lsr_id); if (nbrp && (nbrp->flags & F_NBRP_KEEPALIVE)) return (nbrp->keepalive); return ((ldp_af_conf_get(leconf, af))->keepalive); } struct ctl_nbr * nbr_to_ctl(struct nbr *nbr) { static struct ctl_nbr nctl; struct timeval now; nctl.af = nbr->af; nctl.id = nbr->id; nctl.laddr = nbr->laddr; nctl.lport = nbr->tcp->lport; nctl.raddr = nbr->raddr; nctl.rport = nbr->tcp->rport; nctl.auth_method = nbr->auth.method; nctl.holdtime = nbr->keepalive; nctl.nbr_state = nbr->state; nctl.stats = nbr->stats; nctl.flags = nbr->flags; gettimeofday(&now, NULL); if (nbr->state == NBR_STA_OPER) { nctl.uptime = now.tv_sec - nbr->uptime; } else nctl.uptime = 0; return (&nctl); } void nbr_clear_ctl(struct ctl_nbr *nctl) { struct nbr *nbr; RB_FOREACH(nbr, nbr_addr_head, &nbrs_by_addr) { if (ldp_addrisset(nctl->af, &nctl->raddr) && ldp_addrcmp(nctl->af, &nctl->raddr, &nbr->raddr)) continue; log_debug("%s: neighbor %s manually cleared", __func__, log_addr(nbr->af, &nbr->raddr)); session_shutdown(nbr, S_SHUTDOWN, 0, 0); } } frr-7.2.1/ldpd/notification.c0000644000000000000000000002001313610377563012765 00000000000000/* $OpenBSD$ */ /* * Copyright (c) 2009 Michele Marchetto * * Permission to use, copy, modify, and distribute this software for any * purpose with or without fee is hereby granted, provided that the above * copyright notice and this permission notice appear in all copies. * * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. */ #include #include "ldpd.h" #include "ldp.h" #include "log.h" #include "ldpe.h" #include "ldp_debug.h" static int gen_returned_tlvs(struct ibuf *, uint16_t, uint16_t, char *); static void log_msg_notification(int, struct nbr *, struct notify_msg *); void send_notification_full(struct tcp_conn *tcp, struct notify_msg *nm) { struct ibuf *buf; uint16_t size; int err = 0; /* calculate size */ size = LDP_HDR_SIZE + LDP_MSG_SIZE + STATUS_SIZE; if (nm->flags & F_NOTIF_PW_STATUS) size += PW_STATUS_TLV_SIZE; if (nm->flags & F_NOTIF_FEC) size += len_fec_tlv(&nm->fec); if (nm->flags & F_NOTIF_RETURNED_TLVS) size += TLV_HDR_SIZE * 2 + nm->rtlvs.length; if ((buf = ibuf_open(size)) == NULL) fatal(__func__); err |= gen_ldp_hdr(buf, size); size -= LDP_HDR_SIZE; err |= gen_msg_hdr(buf, MSG_TYPE_NOTIFICATION, size); err |= gen_status_tlv(buf, nm->status_code, nm->msg_id, nm->msg_type); /* optional tlvs */ if (nm->flags & F_NOTIF_PW_STATUS) err |= gen_pw_status_tlv(buf, nm->pw_status); if (nm->flags & F_NOTIF_FEC) err |= gen_fec_tlv(buf, &nm->fec); if (nm->flags & F_NOTIF_RETURNED_TLVS) err |= gen_returned_tlvs(buf, nm->rtlvs.type, nm->rtlvs.length, nm->rtlvs.data); if (err) { ibuf_free(buf); return; } if (tcp->nbr) { log_msg_notification(1, tcp->nbr, nm); nbr_fsm(tcp->nbr, NBR_EVT_PDU_SENT); tcp->nbr->stats.notif_sent++; } evbuf_enqueue(&tcp->wbuf, buf); } /* send a notification without optional tlvs */ void send_notification(struct tcp_conn *tcp, uint32_t status_code, uint32_t msg_id, uint16_t msg_type) { struct notify_msg nm; memset(&nm, 0, sizeof(nm)); nm.status_code = status_code; nm.msg_id = msg_id; nm.msg_type = msg_type; send_notification_full(tcp, &nm); } void send_notification_rtlvs(struct nbr *nbr, uint32_t status_code, uint32_t msg_id, uint16_t msg_type, uint16_t tlv_type, uint16_t tlv_len, char *tlv_data) { struct notify_msg nm; memset(&nm, 0, sizeof(nm)); nm.status_code = status_code; nm.msg_id = msg_id; nm.msg_type = msg_type; /* do not append the given TLV if it's too big (shouldn't happen) */ if (tlv_len < 1024) { nm.rtlvs.type = tlv_type; nm.rtlvs.length = tlv_len; nm.rtlvs.data = tlv_data; nm.flags |= F_NOTIF_RETURNED_TLVS; } send_notification_full(nbr->tcp, &nm); } int recv_notification(struct nbr *nbr, char *buf, uint16_t len) { struct ldp_msg msg; struct status_tlv st; struct notify_msg nm; int tlen; memcpy(&msg, buf, sizeof(msg)); buf += LDP_MSG_SIZE; len -= LDP_MSG_SIZE; if (len < STATUS_SIZE) { session_shutdown(nbr, S_BAD_MSG_LEN, msg.id, msg.type); return (-1); } memcpy(&st, buf, sizeof(st)); if (ntohs(st.length) > STATUS_SIZE - TLV_HDR_SIZE || ntohs(st.length) > len - TLV_HDR_SIZE) { session_shutdown(nbr, S_BAD_TLV_LEN, msg.id, msg.type); return (-1); } buf += STATUS_SIZE; len -= STATUS_SIZE; memset(&nm, 0, sizeof(nm)); nm.status_code = ntohl(st.status_code); /* Optional Parameters */ while (len > 0) { struct tlv tlv; uint16_t tlv_type; uint16_t tlv_len; if (len < sizeof(tlv)) { session_shutdown(nbr, S_BAD_TLV_LEN, msg.id, msg.type); return (-1); } memcpy(&tlv, buf, TLV_HDR_SIZE); tlv_type = ntohs(tlv.type); tlv_len = ntohs(tlv.length); if (tlv_len + TLV_HDR_SIZE > len) { session_shutdown(nbr, S_BAD_TLV_LEN, msg.id, msg.type); return (-1); } buf += TLV_HDR_SIZE; len -= TLV_HDR_SIZE; switch (tlv_type) { case TLV_TYPE_EXTSTATUS: case TLV_TYPE_RETURNEDPDU: case TLV_TYPE_RETURNEDMSG: /* TODO is there any use for this? */ break; case TLV_TYPE_PW_STATUS: if (tlv_len != 4) { session_shutdown(nbr, S_BAD_TLV_LEN, msg.id, msg.type); return (-1); } nm.pw_status = ntohl(*(uint32_t *)buf); nm.flags |= F_NOTIF_PW_STATUS; break; case TLV_TYPE_FEC: if ((tlen = tlv_decode_fec_elm(nbr, &msg, buf, tlv_len, &nm.fec)) == -1) return (-1); /* allow only one fec element */ if (tlen != tlv_len) { session_shutdown(nbr, S_BAD_TLV_VAL, msg.id, msg.type); return (-1); } nm.flags |= F_NOTIF_FEC; break; default: if (!(ntohs(tlv.type) & UNKNOWN_FLAG)) send_notification_rtlvs(nbr, S_UNKNOWN_TLV, msg.id, msg.type, tlv_type, tlv_len, buf); /* ignore unknown tlv */ break; } buf += tlv_len; len -= tlv_len; } /* sanity checks */ switch (nm.status_code) { case S_PW_STATUS: if (!(nm.flags & (F_NOTIF_PW_STATUS|F_NOTIF_FEC))) { send_notification(nbr->tcp, S_MISS_MSG, msg.id, msg.type); return (-1); } switch (nm.fec.type) { case MAP_TYPE_PWID: break; default: send_notification(nbr->tcp, S_BAD_TLV_VAL, msg.id, msg.type); return (-1); } break; case S_ENDOFLIB: if (!(nm.flags & F_NOTIF_FEC)) { send_notification(nbr->tcp, S_MISS_MSG, msg.id, msg.type); return (-1); } if (nm.fec.type != MAP_TYPE_TYPED_WCARD) { send_notification(nbr->tcp, S_BAD_TLV_VAL, msg.id, msg.type); return (-1); } break; default: break; } log_msg_notification(0, nbr, &nm); if (st.status_code & htonl(STATUS_FATAL)) { if (nbr->state == NBR_STA_OPENSENT) nbr_start_idtimer(nbr); /* * RFC 5036 - Section 3.5.1.1: * "When an LSR receives a Shutdown message during session * initialization, it SHOULD transmit a Shutdown message and * then close the transport connection". */ if (nbr->state != NBR_STA_OPER && nm.status_code == S_SHUTDOWN) send_notification(nbr->tcp, S_SHUTDOWN, msg.id, msg.type); nbr_fsm(nbr, NBR_EVT_CLOSE_SESSION); return (-1); } /* lde needs to know about a few notification messages */ switch (nm.status_code) { case S_PW_STATUS: case S_ENDOFLIB: ldpe_imsg_compose_lde(IMSG_NOTIFICATION, nbr->peerid, 0, &nm, sizeof(nm)); break; default: break; } return (0); } int gen_status_tlv(struct ibuf *buf, uint32_t status_code, uint32_t msg_id, uint16_t msg_type) { struct status_tlv st; memset(&st, 0, sizeof(st)); st.type = htons(TLV_TYPE_STATUS); st.length = htons(STATUS_TLV_LEN); st.status_code = htonl(status_code); /* * For convenience, msg_id and msg_type are already in network * byte order. */ st.msg_id = msg_id; st.msg_type = msg_type; return (ibuf_add(buf, &st, STATUS_SIZE)); } static int gen_returned_tlvs(struct ibuf *buf, uint16_t type, uint16_t length, char *tlv_data) { struct tlv rtlvs; struct tlv tlv; int err; rtlvs.type = htons(TLV_TYPE_RETURNED_TLVS); rtlvs.length = htons(length + TLV_HDR_SIZE); tlv.type = htons(type); tlv.length = htons(length); err = ibuf_add(buf, &rtlvs, sizeof(rtlvs)); err |= ibuf_add(buf, &tlv, sizeof(tlv)); err |= ibuf_add(buf, tlv_data, length); return (err); } void log_msg_notification(int out, struct nbr *nbr, struct notify_msg *nm) { if (nm->status_code & STATUS_FATAL) { debug_msg(out, "notification: lsr-id %s, status %s " "(fatal error)", inet_ntoa(nbr->id), status_code_name(nm->status_code)); return; } debug_msg(out, "notification: lsr-id %s, status %s", inet_ntoa(nbr->id), status_code_name(nm->status_code)); if (nm->flags & F_NOTIF_FEC) debug_msg(out, "notification: fec %s", log_map(&nm->fec)); if (nm->flags & F_NOTIF_PW_STATUS) debug_msg(out, "notification: pw-status %s", (nm->pw_status) ? "not forwarding" : "forwarding"); } frr-7.2.1/ldpd/packet.c0000644000000000000000000004777613610377563011577 00000000000000/* $OpenBSD$ */ /* * Copyright (c) 2013, 2016 Renato Westphal * Copyright (c) 2009 Michele Marchetto * Copyright (c) 2004, 2005, 2008 Esben Norby * * Permission to use, copy, modify, and distribute this software for any * purpose with or without fee is hereby granted, provided that the above * copyright notice and this permission notice appear in all copies. * * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. */ #include #include "ldpd.h" #include "ldpe.h" #include "log.h" #include "sockopt.h" static struct iface *disc_find_iface(unsigned int, int, union ldpd_addr *); static int session_read(struct thread *); static int session_write(struct thread *); static ssize_t session_get_pdu(struct ibuf_read *, char **); static void tcp_close(struct tcp_conn *); static struct pending_conn *pending_conn_new(int, int, union ldpd_addr *); static int pending_conn_timeout(struct thread *); int gen_ldp_hdr(struct ibuf *buf, uint16_t size) { struct ldp_hdr ldp_hdr; memset(&ldp_hdr, 0, sizeof(ldp_hdr)); ldp_hdr.version = htons(LDP_VERSION); /* exclude the 'Version' and 'PDU Length' fields from the total */ ldp_hdr.length = htons(size - LDP_HDR_DEAD_LEN); ldp_hdr.lsr_id = ldp_rtr_id_get(leconf); ldp_hdr.lspace_id = 0; return (ibuf_add(buf, &ldp_hdr, LDP_HDR_SIZE)); } int gen_msg_hdr(struct ibuf *buf, uint16_t type, uint16_t size) { static int msgcnt = 0; struct ldp_msg msg; memset(&msg, 0, sizeof(msg)); msg.type = htons(type); /* exclude the 'Type' and 'Length' fields from the total */ msg.length = htons(size - LDP_MSG_DEAD_LEN); msg.id = htonl(++msgcnt); return (ibuf_add(buf, &msg, sizeof(msg))); } /* send packets */ int send_packet(int fd, int af, union ldpd_addr *dst, struct iface_af *ia, void *pkt, size_t len) { union sockunion su; switch (af) { case AF_INET: if (ia && IN_MULTICAST(ntohl(dst->v4.s_addr))) { /* set outgoing interface for multicast traffic */ if (sock_set_ipv4_mcast(ia->iface) == -1) { log_debug("%s: error setting multicast " "interface, %s", __func__, ia->iface->name); return (-1); } } break; case AF_INET6: if (ia && IN6_IS_ADDR_MULTICAST(&dst->v6)) { /* set outgoing interface for multicast traffic */ if (sock_set_ipv6_mcast(ia->iface) == -1) { log_debug("%s: error setting multicast " "interface, %s", __func__, ia->iface->name); return (-1); } } break; default: fatalx("send_packet: unknown af"); } addr2sa(af, dst, LDP_PORT, &su); if (sendto(fd, pkt, len, 0, &su.sa, sockaddr_len(&su.sa)) == -1) { log_warn("%s: error sending packet to %s", __func__, log_sockaddr(&su.sa)); return (-1); } return (0); } /* Discovery functions */ int disc_recv_packet(struct thread *thread) { int fd = THREAD_FD(thread); struct thread **threadp = THREAD_ARG(thread); union { struct cmsghdr hdr; #ifdef HAVE_STRUCT_SOCKADDR_DL char buf[CMSG_SPACE(sizeof(struct sockaddr_dl))]; #else char buf[CMSG_SPACE(sizeof(struct in6_pktinfo))]; #endif } cmsgbuf; struct msghdr m; struct sockaddr_storage from; struct iovec iov; char *buf; #ifndef MSG_MCAST struct cmsghdr *cmsg; #endif ssize_t r; int multicast; int af; union ldpd_addr src; unsigned int ifindex = 0; struct iface *iface = NULL; uint16_t len; struct ldp_hdr ldp_hdr; uint16_t pdu_len; struct ldp_msg msg; uint16_t msg_len; struct in_addr lsr_id; /* reschedule read */ *threadp = NULL; thread_add_read(master, disc_recv_packet, threadp, fd, threadp); /* setup buffer */ memset(&m, 0, sizeof(m)); iov.iov_base = buf = pkt_ptr; iov.iov_len = IBUF_READ_SIZE; m.msg_name = &from; m.msg_namelen = sizeof(from); m.msg_iov = &iov; m.msg_iovlen = 1; m.msg_control = &cmsgbuf.buf; m.msg_controllen = sizeof(cmsgbuf.buf); if ((r = recvmsg(fd, &m, 0)) == -1) { if (errno != EAGAIN && errno != EINTR) log_debug("%s: read error: %s", __func__, strerror(errno)); return (0); } sa2addr((struct sockaddr *)&from, &af, &src, NULL); #ifdef MSG_MCAST multicast = (m.msg_flags & MSG_MCAST) ? 1 : 0; #else multicast = 0; for (cmsg = CMSG_FIRSTHDR(&m); cmsg != NULL; cmsg = CMSG_NXTHDR(&m, cmsg)) { #if defined(HAVE_IP_PKTINFO) if (af == AF_INET && cmsg->cmsg_level == IPPROTO_IP && cmsg->cmsg_type == IP_PKTINFO) { struct in_pktinfo *pktinfo; pktinfo = (struct in_pktinfo *)CMSG_DATA(cmsg); if (IN_MULTICAST(ntohl(pktinfo->ipi_addr.s_addr))) multicast = 1; break; } #elif defined(HAVE_IP_RECVDSTADDR) if (af == AF_INET && cmsg->cmsg_level == IPPROTO_IP && cmsg->cmsg_type == IP_RECVDSTADDR) { struct in_addr *addr; addr = (struct in_addr *)CMSG_DATA(cmsg); if (IN_MULTICAST(ntohl(addr->s_addr))) multicast = 1; break; } #else #error "Unsupported socket API" #endif if (af == AF_INET6 && cmsg->cmsg_level == IPPROTO_IPV6 && cmsg->cmsg_type == IPV6_PKTINFO) { struct in6_pktinfo *pktinfo; pktinfo = (struct in6_pktinfo *)CMSG_DATA(cmsg); if (IN6_IS_ADDR_MULTICAST(&pktinfo->ipi6_addr)) multicast = 1; break; } } #endif /* MSG_MCAST */ if (bad_addr(af, &src)) { log_debug("%s: invalid source address: %s", __func__, log_addr(af, &src)); return (0); } ifindex = getsockopt_ifindex(af, &m); /* find a matching interface */ if (multicast) { iface = disc_find_iface(ifindex, af, &src); if (iface == NULL) return (0); } /* check packet size */ len = (uint16_t)r; if (len < (LDP_HDR_SIZE + LDP_MSG_SIZE) || len > LDP_MAX_LEN) { log_debug("%s: bad packet size, source %s", __func__, log_addr(af, &src)); return (0); } /* LDP header sanity checks */ memcpy(&ldp_hdr, buf, sizeof(ldp_hdr)); if (ntohs(ldp_hdr.version) != LDP_VERSION) { log_debug("%s: invalid LDP version %d, source %s", __func__, ntohs(ldp_hdr.version), log_addr(af, &src)); return (0); } if (ntohs(ldp_hdr.lspace_id) != 0) { log_debug("%s: invalid label space %u, source %s", __func__, ntohs(ldp_hdr.lspace_id), log_addr(af, &src)); return (0); } /* check "PDU Length" field */ pdu_len = ntohs(ldp_hdr.length); if ((pdu_len < (LDP_HDR_PDU_LEN + LDP_MSG_SIZE)) || (pdu_len > (len - LDP_HDR_DEAD_LEN))) { log_debug("%s: invalid LDP packet length %u, source %s", __func__, ntohs(ldp_hdr.length), log_addr(af, &src)); return (0); } buf += LDP_HDR_SIZE; len -= LDP_HDR_SIZE; lsr_id.s_addr = ldp_hdr.lsr_id; /* * For UDP, we process only the first message of each packet. This does * not impose any restrictions since LDP uses UDP only for sending Hello * packets. */ memcpy(&msg, buf, sizeof(msg)); /* check "Message Length" field */ msg_len = ntohs(msg.length); if (msg_len < LDP_MSG_LEN || ((msg_len + LDP_MSG_DEAD_LEN) > pdu_len)) { log_debug("%s: invalid LDP message length %u, source %s", __func__, ntohs(msg.length), log_addr(af, &src)); return (0); } buf += LDP_MSG_SIZE; len -= LDP_MSG_SIZE; /* switch LDP packet type */ switch (ntohs(msg.type)) { case MSG_TYPE_HELLO: recv_hello(lsr_id, &msg, af, &src, iface, multicast, buf, len); break; default: log_debug("%s: unknown LDP packet type, source %s", __func__, log_addr(af, &src)); } return (0); } static struct iface * disc_find_iface(unsigned int ifindex, int af, union ldpd_addr *src) { struct iface *iface; struct iface_af *ia; iface = if_lookup(leconf, ifindex); if (iface == NULL) return (NULL); ia = iface_af_get(iface, af); if (!ia->enabled) return (NULL); /* * RFC 7552 - Section 5.1: * "Link-local IPv6 address MUST be used as the source IP address in * IPv6 LDP Link Hellos". */ if (af == AF_INET6 && !IN6_IS_ADDR_LINKLOCAL(&src->v6)) return (NULL); return (iface); } int session_accept(struct thread *thread) { int fd = THREAD_FD(thread); struct sockaddr_storage src; socklen_t len = sizeof(src); int newfd; int af; union ldpd_addr addr; struct nbr *nbr; struct pending_conn *pconn; newfd = accept(fd, (struct sockaddr *)&src, &len); if (newfd == -1) { /* * Pause accept if we are out of file descriptors, or * libevent will haunt us here too. */ if (errno == ENFILE || errno == EMFILE) { accept_pause(); } else if (errno != EWOULDBLOCK && errno != EINTR && errno != ECONNABORTED) log_debug("%s: accept error: %s", __func__, strerror(errno)); return (0); } sock_set_nonblock(newfd); sa2addr((struct sockaddr *)&src, &af, &addr, NULL); /* * Since we don't support label spaces, we can identify this neighbor * just by its source address. This way we don't need to wait for its * Initialization message to know who we are talking to. */ nbr = nbr_find_addr(af, &addr); if (nbr == NULL) { /* * According to RFC 5036, we would need to send a No Hello * Error Notification message and close this TCP connection * right now. But doing so would trigger the backoff exponential * timer in the remote peer, which would considerably slow down * the session establishment process. The trick here is to wait * five seconds before sending the Notification Message. There's * a good chance that the remote peer will send us a Hello * message within this interval, so it's worth waiting before * taking a more drastic measure. */ pconn = pending_conn_find(af, &addr); if (pconn) close(newfd); else pending_conn_new(newfd, af, &addr); return (0); } /* protection against buggy implementations */ if (nbr_session_active_role(nbr)) { close(newfd); return (0); } if (nbr->state != NBR_STA_PRESENT) { log_debug("%s: lsr-id %s: rejecting additional transport " "connection", __func__, inet_ntoa(nbr->id)); close(newfd); return (0); } session_accept_nbr(nbr, newfd); return (0); } void session_accept_nbr(struct nbr *nbr, int fd) { #ifdef __OpenBSD__ struct nbr_params *nbrp; int opt; socklen_t len; nbrp = nbr_params_find(leconf, nbr->id); if (nbr_gtsm_check(fd, nbr, nbrp)) { close(fd); return; } if (nbrp && nbrp->auth.method == AUTH_MD5SIG) { if (sysdep.no_pfkey || sysdep.no_md5sig) { log_warnx("md5sig configured but not available"); close(fd); return; } len = sizeof(opt); if (getsockopt(fd, IPPROTO_TCP, TCP_MD5SIG, &opt, &len) == -1) fatal("getsockopt TCP_MD5SIG"); if (!opt) { /* non-md5'd connection! */ log_warnx("connection attempt without md5 signature"); close(fd); return; } } #endif nbr->tcp = tcp_new(fd, nbr); nbr_fsm(nbr, NBR_EVT_MATCH_ADJ); } static int session_read(struct thread *thread) { int fd = THREAD_FD(thread); struct nbr *nbr = THREAD_ARG(thread); struct tcp_conn *tcp = nbr->tcp; struct ldp_hdr *ldp_hdr; struct ldp_msg *msg; char *buf = NULL, *pdu; ssize_t n, len; uint16_t pdu_len, msg_len, msg_size, max_pdu_len; int ret; tcp->rev = NULL; thread_add_read(master, session_read, nbr, fd, &tcp->rev); if ((n = read(fd, tcp->rbuf->buf + tcp->rbuf->wpos, sizeof(tcp->rbuf->buf) - tcp->rbuf->wpos)) == -1) { if (errno != EINTR && errno != EAGAIN) { log_warn("%s: read error", __func__); nbr_fsm(nbr, NBR_EVT_CLOSE_SESSION); return (0); } /* retry read */ return (0); } if (n == 0) { /* connection closed */ log_debug("%s: connection closed by remote end", __func__); nbr_fsm(nbr, NBR_EVT_CLOSE_SESSION); return (0); } tcp->rbuf->wpos += n; while ((len = session_get_pdu(tcp->rbuf, &buf)) > 0) { pdu = buf; ldp_hdr = (struct ldp_hdr *)pdu; if (ntohs(ldp_hdr->version) != LDP_VERSION) { session_shutdown(nbr, S_BAD_PROTO_VER, 0, 0); free(buf); return (0); } pdu_len = ntohs(ldp_hdr->length); /* * RFC 5036 - Section 3.5.3: * "Prior to completion of the negotiation, the maximum * allowable length is 4096 bytes". */ if (nbr->state == NBR_STA_OPER) max_pdu_len = nbr->max_pdu_len; else max_pdu_len = LDP_MAX_LEN; if (pdu_len < (LDP_HDR_PDU_LEN + LDP_MSG_SIZE) || pdu_len > max_pdu_len) { session_shutdown(nbr, S_BAD_PDU_LEN, 0, 0); free(buf); return (0); } pdu_len -= LDP_HDR_PDU_LEN; if (ldp_hdr->lsr_id != nbr->id.s_addr || ldp_hdr->lspace_id != 0) { session_shutdown(nbr, S_BAD_LDP_ID, 0, 0); free(buf); return (0); } pdu += LDP_HDR_SIZE; len -= LDP_HDR_SIZE; nbr_fsm(nbr, NBR_EVT_PDU_RCVD); while (len >= LDP_MSG_SIZE) { uint16_t type; msg = (struct ldp_msg *)pdu; type = ntohs(msg->type); msg_len = ntohs(msg->length); if (msg_len < LDP_MSG_LEN || (msg_len + LDP_MSG_DEAD_LEN) > pdu_len) { session_shutdown(nbr, S_BAD_MSG_LEN, msg->id, msg->type); free(buf); return (0); } msg_size = msg_len + LDP_MSG_DEAD_LEN; pdu_len -= msg_size; /* check for error conditions earlier */ switch (type) { case MSG_TYPE_INIT: if ((nbr->state != NBR_STA_INITIAL) && (nbr->state != NBR_STA_OPENSENT)) { session_shutdown(nbr, S_SHUTDOWN, msg->id, msg->type); free(buf); return (0); } break; case MSG_TYPE_KEEPALIVE: if ((nbr->state == NBR_STA_INITIAL) || (nbr->state == NBR_STA_OPENSENT)) { session_shutdown(nbr, S_SHUTDOWN, msg->id, msg->type); free(buf); return (0); } break; case MSG_TYPE_NOTIFICATION: break; default: if (nbr->state != NBR_STA_OPER) { session_shutdown(nbr, S_SHUTDOWN, msg->id, msg->type); free(buf); return (0); } break; } /* switch LDP packet type */ switch (type) { case MSG_TYPE_NOTIFICATION: ret = recv_notification(nbr, pdu, msg_size); break; case MSG_TYPE_INIT: ret = recv_init(nbr, pdu, msg_size); break; case MSG_TYPE_KEEPALIVE: ret = recv_keepalive(nbr, pdu, msg_size); break; case MSG_TYPE_CAPABILITY: ret = recv_capability(nbr, pdu, msg_size); break; case MSG_TYPE_ADDR: case MSG_TYPE_ADDRWITHDRAW: ret = recv_address(nbr, pdu, msg_size); break; case MSG_TYPE_LABELMAPPING: case MSG_TYPE_LABELREQUEST: case MSG_TYPE_LABELWITHDRAW: case MSG_TYPE_LABELRELEASE: case MSG_TYPE_LABELABORTREQ: ret = recv_labelmessage(nbr, pdu, msg_size, type); break; default: log_debug("%s: unknown LDP message from nbr %s", __func__, inet_ntoa(nbr->id)); if (!(ntohs(msg->type) & UNKNOWN_FLAG)) send_notification(nbr->tcp, S_UNKNOWN_MSG, msg->id, msg->type); /* ignore the message */ ret = 0; break; } if (ret == -1) { /* parser failed, giving up */ free(buf); return (0); } /* no errors - update per neighbor message counters */ switch (type) { case MSG_TYPE_NOTIFICATION: nbr->stats.notif_rcvd++; break; case MSG_TYPE_KEEPALIVE: nbr->stats.kalive_rcvd++; break; case MSG_TYPE_CAPABILITY: nbr->stats.capability_rcvd++; break; case MSG_TYPE_ADDR: nbr->stats.addr_rcvd++; break; case MSG_TYPE_ADDRWITHDRAW: nbr->stats.addrwdraw_rcvd++; break; case MSG_TYPE_LABELMAPPING: nbr->stats.labelmap_rcvd++; break; case MSG_TYPE_LABELREQUEST: nbr->stats.labelreq_rcvd++; break; case MSG_TYPE_LABELWITHDRAW: nbr->stats.labelwdraw_rcvd++; break; case MSG_TYPE_LABELRELEASE: nbr->stats.labelrel_rcvd++; break; case MSG_TYPE_LABELABORTREQ: nbr->stats.labelabreq_rcvd++; break; default: break; } /* Analyse the next message */ pdu += msg_size; len -= msg_size; } free(buf); buf = NULL; if (len != 0) { session_shutdown(nbr, S_BAD_PDU_LEN, 0, 0); return (0); } } /* shouldn't happen, session_get_pdu should be > 0 if buf was * allocated - but let's get rid of the SA warning. */ free(buf); return (0); } static int session_write(struct thread *thread) { struct tcp_conn *tcp = THREAD_ARG(thread); struct nbr *nbr = tcp->nbr; tcp->wbuf.ev = NULL; if (msgbuf_write(&tcp->wbuf.wbuf) <= 0) if (errno != EAGAIN && nbr) nbr_fsm(nbr, NBR_EVT_CLOSE_SESSION); if (nbr == NULL && !tcp->wbuf.wbuf.queued) { /* * We are done sending the notification message, now we can * close the socket. */ tcp_close(tcp); return (0); } evbuf_event_add(&tcp->wbuf); return (0); } void session_shutdown(struct nbr *nbr, uint32_t status, uint32_t msg_id, uint32_t msg_type) { switch (nbr->state) { case NBR_STA_PRESENT: if (nbr_pending_connect(nbr)) THREAD_WRITE_OFF(nbr->ev_connect); break; case NBR_STA_INITIAL: case NBR_STA_OPENREC: case NBR_STA_OPENSENT: case NBR_STA_OPER: send_notification(nbr->tcp, status, msg_id, msg_type); nbr_fsm(nbr, NBR_EVT_CLOSE_SESSION); break; default: fatalx("session_shutdown: unknown neighbor state"); } } void session_close(struct nbr *nbr) { log_debug("%s: closing session with lsr-id %s", __func__, inet_ntoa(nbr->id)); tcp_close(nbr->tcp); nbr_stop_ktimer(nbr); nbr_stop_ktimeout(nbr); nbr_stop_itimeout(nbr); } static ssize_t session_get_pdu(struct ibuf_read *r, char **b) { struct ldp_hdr l; size_t av, dlen, left; av = r->wpos; if (av < sizeof(l)) return (0); memcpy(&l, r->buf, sizeof(l)); dlen = ntohs(l.length) + LDP_HDR_DEAD_LEN; if (dlen > av) return (0); if ((*b = malloc(dlen)) == NULL) return (-1); memcpy(*b, r->buf, dlen); if (dlen < av) { left = av - dlen; memmove(r->buf, r->buf + dlen, left); r->wpos = left; } else r->wpos = 0; return (dlen); } struct tcp_conn * tcp_new(int fd, struct nbr *nbr) { struct tcp_conn *tcp; struct sockaddr_storage ss; socklen_t len = sizeof(ss); if ((tcp = calloc(1, sizeof(*tcp))) == NULL) fatal(__func__); tcp->fd = fd; evbuf_init(&tcp->wbuf, tcp->fd, session_write, tcp); if (nbr) { if ((tcp->rbuf = calloc(1, sizeof(struct ibuf_read))) == NULL) fatal(__func__); tcp->rev = NULL; thread_add_read(master, session_read, nbr, tcp->fd, &tcp->rev); tcp->nbr = nbr; } if (getsockname(fd, (struct sockaddr *)&ss, &len) != 0) log_warn("%s: getsockname", __func__); else sa2addr((struct sockaddr *)&ss, NULL, NULL, &tcp->lport); if (getpeername(fd, (struct sockaddr *)&ss, &len) != 0) log_warn("%s: getpeername", __func__); else sa2addr((struct sockaddr *)&ss, NULL, NULL, &tcp->rport); return (tcp); } static void tcp_close(struct tcp_conn *tcp) { /* try to flush write buffer */ msgbuf_write(&tcp->wbuf.wbuf); evbuf_clear(&tcp->wbuf); if (tcp->nbr) { THREAD_READ_OFF(tcp->rev); free(tcp->rbuf); tcp->nbr->tcp = NULL; } close(tcp->fd); accept_unpause(); free(tcp); } static struct pending_conn * pending_conn_new(int fd, int af, union ldpd_addr *addr) { struct pending_conn *pconn; if ((pconn = calloc(1, sizeof(*pconn))) == NULL) fatal(__func__); pconn->fd = fd; pconn->af = af; pconn->addr = *addr; TAILQ_INSERT_TAIL(&global.pending_conns, pconn, entry); pconn->ev_timeout = NULL; thread_add_timer(master, pending_conn_timeout, pconn, PENDING_CONN_TIMEOUT, &pconn->ev_timeout); return (pconn); } void pending_conn_del(struct pending_conn *pconn) { THREAD_TIMER_OFF(pconn->ev_timeout); TAILQ_REMOVE(&global.pending_conns, pconn, entry); free(pconn); } struct pending_conn * pending_conn_find(int af, union ldpd_addr *addr) { struct pending_conn *pconn; TAILQ_FOREACH(pconn, &global.pending_conns, entry) if (af == pconn->af && ldp_addrcmp(af, addr, &pconn->addr) == 0) return (pconn); return (NULL); } static int pending_conn_timeout(struct thread *thread) { struct pending_conn *pconn = THREAD_ARG(thread); struct tcp_conn *tcp; pconn->ev_timeout = NULL; log_debug("%s: no adjacency with remote end: %s", __func__, log_addr(pconn->af, &pconn->addr)); /* * Create a write buffer detached from any neighbor to send a * notification message reliably. */ tcp = tcp_new(pconn->fd, NULL); send_notification(tcp, S_NO_HELLO, 0, 0); msgbuf_write(&tcp->wbuf.wbuf); pending_conn_del(pconn); return (0); } frr-7.2.1/ldpd/pfkey.c0000644000000000000000000002627713610377563011437 00000000000000/* $OpenBSD$ */ /* * Copyright (c) 2003, 2004 Henning Brauer * Copyright (c) 2003, 2004 Markus Friedl * * Permission to use, copy, modify, and distribute this software for any * purpose with or without fee is hereby granted, provided that the above * copyright notice and this permission notice appear in all copies. * * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. */ #ifdef HAVE_CONFIG_H #include "config.h" #endif #ifdef __OpenBSD__ #include #include #include #include #include #include #include "ldpd.h" #include "ldpe.h" #include "log.h" static int pfkey_send(int, uint8_t, uint8_t, uint8_t, int, union ldpd_addr *, union ldpd_addr *, uint32_t, uint8_t, int, char *, uint8_t, int, char *, uint16_t, uint16_t); static int pfkey_reply(int, uint32_t *); static int pfkey_sa_add(int, union ldpd_addr *, union ldpd_addr *, uint8_t, char *, uint32_t *); static int pfkey_sa_remove(int, union ldpd_addr *, union ldpd_addr *, uint32_t *); static int pfkey_md5sig_establish(struct nbr *, struct nbr_params *nbrp); static int pfkey_md5sig_remove(struct nbr *); #define PFKEY2_CHUNK sizeof(uint64_t) #define ROUNDUP(x) (((x) + (PFKEY2_CHUNK - 1)) & ~(PFKEY2_CHUNK - 1)) #define IOV_CNT 20 static uint32_t sadb_msg_seq; static uint32_t pid; /* should pid_t but pfkey needs uint32_t */ static int fd; static int pfkey_send(int sd, uint8_t satype, uint8_t mtype, uint8_t dir, int af, union ldpd_addr *src, union ldpd_addr *dst, uint32_t spi, uint8_t aalg, int alen, char *akey, uint8_t ealg, int elen, char *ekey, uint16_t sport, uint16_t dport) { struct sadb_msg smsg; struct sadb_sa sa; struct sadb_address sa_src, sa_dst; struct sadb_key sa_akey, sa_ekey; struct sadb_spirange sa_spirange; struct iovec iov[IOV_CNT]; ssize_t n; int len = 0; int iov_cnt; struct sockaddr_storage smask, dmask; union sockunion su_src, su_dst; if (!pid) pid = getpid(); /* we need clean sockaddr... no ports set */ memset(&smask, 0, sizeof(smask)); addr2sa(af, src, 0, &su_src); switch (af) { case AF_INET: memset(&((struct sockaddr_in *)&smask)->sin_addr, 0xff, 32/8); break; case AF_INET6: memset(&((struct sockaddr_in6 *)&smask)->sin6_addr, 0xff, 128/8); break; default: return (-1); } smask.ss_family = su_src.sa.sa_family; smask.ss_len = sockaddr_len(&su_src.sa); memset(&dmask, 0, sizeof(dmask)); addr2sa(af, dst, 0, &su_dst); switch (af) { case AF_INET: memset(&((struct sockaddr_in *)&dmask)->sin_addr, 0xff, 32/8); break; case AF_INET6: memset(&((struct sockaddr_in6 *)&dmask)->sin6_addr, 0xff, 128/8); break; default: return (-1); } dmask.ss_family = su_dst.sa.sa_family; dmask.ss_len = sockaddr_len(&su_dst.sa); memset(&smsg, 0, sizeof(smsg)); smsg.sadb_msg_version = PF_KEY_V2; smsg.sadb_msg_seq = ++sadb_msg_seq; smsg.sadb_msg_pid = pid; smsg.sadb_msg_len = sizeof(smsg) / 8; smsg.sadb_msg_type = mtype; smsg.sadb_msg_satype = satype; switch (mtype) { case SADB_GETSPI: memset(&sa_spirange, 0, sizeof(sa_spirange)); sa_spirange.sadb_spirange_exttype = SADB_EXT_SPIRANGE; sa_spirange.sadb_spirange_len = sizeof(sa_spirange) / 8; sa_spirange.sadb_spirange_min = 0x100; sa_spirange.sadb_spirange_max = 0xffffffff; sa_spirange.sadb_spirange_reserved = 0; break; case SADB_ADD: case SADB_UPDATE: case SADB_DELETE: memset(&sa, 0, sizeof(sa)); sa.sadb_sa_exttype = SADB_EXT_SA; sa.sadb_sa_len = sizeof(sa) / 8; sa.sadb_sa_replay = 0; sa.sadb_sa_spi = htonl(spi); sa.sadb_sa_state = SADB_SASTATE_MATURE; break; } memset(&sa_src, 0, sizeof(sa_src)); sa_src.sadb_address_exttype = SADB_EXT_ADDRESS_SRC; sa_src.sadb_address_len = (sizeof(sa_src) + ROUNDUP(sockaddr_len(&su_src.sa))) / 8; memset(&sa_dst, 0, sizeof(sa_dst)); sa_dst.sadb_address_exttype = SADB_EXT_ADDRESS_DST; sa_dst.sadb_address_len = (sizeof(sa_dst) + ROUNDUP(sockaddr_len(&su_dst.sa))) / 8; sa.sadb_sa_auth = aalg; sa.sadb_sa_encrypt = SADB_X_EALG_AES; /* XXX */ switch (mtype) { case SADB_ADD: case SADB_UPDATE: memset(&sa_akey, 0, sizeof(sa_akey)); sa_akey.sadb_key_exttype = SADB_EXT_KEY_AUTH; sa_akey.sadb_key_len = (sizeof(sa_akey) + ((alen + 7) / 8) * 8) / 8; sa_akey.sadb_key_bits = 8 * alen; memset(&sa_ekey, 0, sizeof(sa_ekey)); sa_ekey.sadb_key_exttype = SADB_EXT_KEY_ENCRYPT; sa_ekey.sadb_key_len = (sizeof(sa_ekey) + ((elen + 7) / 8) * 8) / 8; sa_ekey.sadb_key_bits = 8 * elen; break; } iov_cnt = 0; /* msghdr */ iov[iov_cnt].iov_base = &smsg; iov[iov_cnt].iov_len = sizeof(smsg); iov_cnt++; switch (mtype) { case SADB_ADD: case SADB_UPDATE: case SADB_DELETE: /* SA hdr */ iov[iov_cnt].iov_base = &sa; iov[iov_cnt].iov_len = sizeof(sa); smsg.sadb_msg_len += sa.sadb_sa_len; iov_cnt++; break; case SADB_GETSPI: /* SPI range */ iov[iov_cnt].iov_base = &sa_spirange; iov[iov_cnt].iov_len = sizeof(sa_spirange); smsg.sadb_msg_len += sa_spirange.sadb_spirange_len; iov_cnt++; break; } /* dest addr */ iov[iov_cnt].iov_base = &sa_dst; iov[iov_cnt].iov_len = sizeof(sa_dst); iov_cnt++; iov[iov_cnt].iov_base = &su_dst; iov[iov_cnt].iov_len = ROUNDUP(sockaddr_len(&su_dst.sa)); smsg.sadb_msg_len += sa_dst.sadb_address_len; iov_cnt++; /* src addr */ iov[iov_cnt].iov_base = &sa_src; iov[iov_cnt].iov_len = sizeof(sa_src); iov_cnt++; iov[iov_cnt].iov_base = &su_src; iov[iov_cnt].iov_len = ROUNDUP(sockaddr_len(&su_src.sa)); smsg.sadb_msg_len += sa_src.sadb_address_len; iov_cnt++; switch (mtype) { case SADB_ADD: case SADB_UPDATE: if (alen) { /* auth key */ iov[iov_cnt].iov_base = &sa_akey; iov[iov_cnt].iov_len = sizeof(sa_akey); iov_cnt++; iov[iov_cnt].iov_base = akey; iov[iov_cnt].iov_len = ((alen + 7) / 8) * 8; smsg.sadb_msg_len += sa_akey.sadb_key_len; iov_cnt++; } if (elen) { /* encryption key */ iov[iov_cnt].iov_base = &sa_ekey; iov[iov_cnt].iov_len = sizeof(sa_ekey); iov_cnt++; iov[iov_cnt].iov_base = ekey; iov[iov_cnt].iov_len = ((elen + 7) / 8) * 8; smsg.sadb_msg_len += sa_ekey.sadb_key_len; iov_cnt++; } break; } len = smsg.sadb_msg_len * 8; do { n = writev(sd, iov, iov_cnt); } while (n == -1 && (errno == EAGAIN || errno == EINTR)); if (n == -1) { log_warn("writev (%d/%d)", iov_cnt, len); return (-1); } return (0); } int pfkey_read(int sd, struct sadb_msg *h) { struct sadb_msg hdr; if (recv(sd, &hdr, sizeof(hdr), MSG_PEEK) != sizeof(hdr)) { if (errno == EAGAIN || errno == EINTR) return (0); log_warn("pfkey peek"); return (-1); } /* XXX: Only one message can be outstanding. */ if (hdr.sadb_msg_seq == sadb_msg_seq && hdr.sadb_msg_pid == pid) { if (h) *h = hdr; return (0); } /* not ours, discard */ if (read(sd, &hdr, sizeof(hdr)) == -1) { if (errno == EAGAIN || errno == EINTR) return (0); log_warn("pfkey read"); return (-1); } return (1); } static int pfkey_reply(int sd, uint32_t *spi) { struct sadb_msg hdr, *msg; struct sadb_ext *ext; struct sadb_sa *sa; uint8_t *data; ssize_t len; int rv; do { rv = pfkey_read(sd, &hdr); if (rv == -1) return (-1); } while (rv); if (hdr.sadb_msg_errno != 0) { errno = hdr.sadb_msg_errno; if (errno == ESRCH) return (0); else { log_warn("pfkey"); return (-1); } } if ((data = reallocarray(NULL, hdr.sadb_msg_len, PFKEY2_CHUNK)) == NULL) { log_warn("pfkey malloc"); return (-1); } len = hdr.sadb_msg_len * PFKEY2_CHUNK; if (read(sd, data, len) != len) { log_warn("pfkey read"); explicit_bzero(data, len); free(data); return (-1); } if (hdr.sadb_msg_type == SADB_GETSPI) { if (spi == NULL) { explicit_bzero(data, len); free(data); return (0); } msg = (struct sadb_msg *)data; for (ext = (struct sadb_ext *)(msg + 1); (size_t)((uint8_t *)ext - (uint8_t *)msg) < msg->sadb_msg_len * PFKEY2_CHUNK; ext = (struct sadb_ext *)((uint8_t *)ext + ext->sadb_ext_len * PFKEY2_CHUNK)) { if (ext->sadb_ext_type == SADB_EXT_SA) { sa = (struct sadb_sa *) ext; *spi = ntohl(sa->sadb_sa_spi); break; } } } explicit_bzero(data, len); free(data); return (0); } static int pfkey_sa_add(int af, union ldpd_addr *src, union ldpd_addr *dst, uint8_t keylen, char *key, uint32_t *spi) { if (pfkey_send(fd, SADB_X_SATYPE_TCPSIGNATURE, SADB_GETSPI, 0, af, src, dst, 0, 0, 0, NULL, 0, 0, NULL, 0, 0) < 0) return (-1); if (pfkey_reply(fd, spi) < 0) return (-1); if (pfkey_send(fd, SADB_X_SATYPE_TCPSIGNATURE, SADB_UPDATE, 0, af, src, dst, *spi, 0, keylen, key, 0, 0, NULL, 0, 0) < 0) return (-1); if (pfkey_reply(fd, NULL) < 0) return (-1); return (0); } static int pfkey_sa_remove(int af, union ldpd_addr *src, union ldpd_addr *dst, uint32_t *spi) { if (pfkey_send(fd, SADB_X_SATYPE_TCPSIGNATURE, SADB_DELETE, 0, af, src, dst, *spi, 0, 0, NULL, 0, 0, NULL, 0, 0) < 0) return (-1); if (pfkey_reply(fd, NULL) < 0) return (-1); *spi = 0; return (0); } static int pfkey_md5sig_establish(struct nbr *nbr, struct nbr_params *nbrp) { sleep(1); if (!nbr->auth.spi_out) if (pfkey_sa_add(nbr->af, &nbr->laddr, &nbr->raddr, nbrp->auth.md5key_len, nbrp->auth.md5key, &nbr->auth.spi_out) == -1) return (-1); if (!nbr->auth.spi_in) if (pfkey_sa_add(nbr->af, &nbr->raddr, &nbr->laddr, nbrp->auth.md5key_len, nbrp->auth.md5key, &nbr->auth.spi_in) == -1) return (-1); nbr->auth.established = 1; return (0); } static int pfkey_md5sig_remove(struct nbr *nbr) { if (nbr->auth.spi_out) if (pfkey_sa_remove(nbr->af, &nbr->laddr, &nbr->raddr, &nbr->auth.spi_out) == -1) return (-1); if (nbr->auth.spi_in) if (pfkey_sa_remove(nbr->af, &nbr->raddr, &nbr->laddr, &nbr->auth.spi_in) == -1) return (-1); nbr->auth.established = 0; nbr->auth.spi_in = 0; nbr->auth.spi_out = 0; nbr->auth.method = AUTH_NONE; memset(nbr->auth.md5key, 0, sizeof(nbr->auth.md5key)); return (0); } int pfkey_establish(struct nbr *nbr, struct nbr_params *nbrp) { if (nbrp->auth.method == AUTH_NONE) return (0); switch (nbr->auth.method) { case AUTH_MD5SIG: strlcpy(nbr->auth.md5key, nbrp->auth.md5key, sizeof(nbr->auth.md5key)); return (pfkey_md5sig_establish(nbr, nbrp)); default: break; } return (0); } int pfkey_remove(struct nbr *nbr) { if (nbr->auth.method == AUTH_NONE || !nbr->auth.established) return (0); switch (nbr->auth.method) { case AUTH_MD5SIG: return (pfkey_md5sig_remove(nbr)); default: break; } return (0); } int pfkey_init(void) { if ((fd = socket(PF_KEY, SOCK_RAW | SOCK_CLOEXEC | SOCK_NONBLOCK, PF_KEY_V2)) == -1) { if (errno == EPROTONOSUPPORT) { log_warnx("PF_KEY not available"); sysdep.no_pfkey = 1; return (-1); } else fatal("pfkey setup failed"); } return (fd); } #endif /* __OpenBSD__ */ frr-7.2.1/ldpd/socket.c0000644000000000000000000002470213610377563011600 00000000000000/* $OpenBSD$ */ /* * Copyright (c) 2016 Renato Westphal * Copyright (c) 2009 Michele Marchetto * Copyright (c) 2005 Claudio Jeker * Copyright (c) 2003, 2004 Henning Brauer * * Permission to use, copy, modify, and distribute this software for any * purpose with or without fee is hereby granted, provided that the above * copyright notice and this permission notice appear in all copies. * * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. */ #include #include "ldpd.h" #include "ldpe.h" #include "log.h" #include "lib/log.h" #include "privs.h" #include "sockopt.h" extern struct zebra_privs_t ldpd_privs; extern struct zebra_privs_t ldpe_privs; int ldp_create_socket(int af, enum socket_type type) { int fd, domain, proto; union ldpd_addr addr; union sockunion local_su; #ifdef __OpenBSD__ int opt; #endif /* create socket */ switch (type) { case LDP_SOCKET_DISC: case LDP_SOCKET_EDISC: domain = SOCK_DGRAM; proto = IPPROTO_UDP; break; case LDP_SOCKET_SESSION: domain = SOCK_STREAM; proto = IPPROTO_TCP; break; default: fatalx("ldp_create_socket: unknown socket type"); } fd = socket(af, domain, proto); if (fd == -1) { log_warn("%s: error creating socket", __func__); return (-1); } sock_set_nonblock(fd); sockopt_v6only(af, fd); /* bind to a local address/port */ switch (type) { case LDP_SOCKET_DISC: /* listen on all addresses */ memset(&addr, 0, sizeof(addr)); addr2sa(af, &addr, LDP_PORT, &local_su); break; case LDP_SOCKET_EDISC: case LDP_SOCKET_SESSION: addr = (ldp_af_conf_get(ldpd_conf, af))->trans_addr; addr2sa(af, &addr, LDP_PORT, &local_su); /* ignore any possible error */ sock_set_bindany(fd, 1); break; } frr_with_privs(&ldpd_privs) { if (sock_set_reuse(fd, 1) == -1) { close(fd); return (-1); } if (bind(fd, &local_su.sa, sockaddr_len(&local_su.sa)) == -1) { log_warnx("%s: error binding socket: %s", __func__, safe_strerror(errno)); close(fd); return (-1); } } /* set options */ switch (af) { case AF_INET: if (sock_set_ipv4_tos(fd, IPTOS_PREC_INTERNETCONTROL) == -1) { close(fd); return (-1); } if (type == LDP_SOCKET_DISC) { if (sock_set_ipv4_mcast_ttl(fd, IP_DEFAULT_MULTICAST_TTL) == -1) { close(fd); return (-1); } if (sock_set_ipv4_mcast_loop(fd) == -1) { close(fd); return (-1); } } if (type == LDP_SOCKET_DISC || type == LDP_SOCKET_EDISC) { if (sock_set_ipv4_recvif(fd, 1) == -1) { close(fd); return (-1); } #ifndef MSG_MCAST #if defined(HAVE_IP_PKTINFO) if (sock_set_ipv4_pktinfo(fd, 1) == -1) { close(fd); return (-1); } #elif defined(HAVE_IP_RECVDSTADDR) if (sock_set_ipv4_recvdstaddr(fd, 1) == -1) { close(fd); return (-1); } #else #error "Unsupported socket API" #endif #endif /* MSG_MCAST */ } if (type == LDP_SOCKET_SESSION) { if (sock_set_ipv4_ucast_ttl(fd, 255) == -1) { close(fd); return (-1); } } break; case AF_INET6: if (sock_set_ipv6_dscp(fd, IPTOS_PREC_INTERNETCONTROL) == -1) { close(fd); return (-1); } if (type == LDP_SOCKET_DISC) { if (sock_set_ipv6_mcast_loop(fd) == -1) { close(fd); return (-1); } if (sock_set_ipv6_mcast_hops(fd, 255) == -1) { close(fd); return (-1); } if (!(ldpd_conf->ipv6.flags & F_LDPD_AF_NO_GTSM)) { /* ignore any possible error */ sock_set_ipv6_minhopcount(fd, 255); } } if (type == LDP_SOCKET_DISC || type == LDP_SOCKET_EDISC) { if (sock_set_ipv6_pktinfo(fd, 1) == -1) { close(fd); return (-1); } } if (type == LDP_SOCKET_SESSION) { if (sock_set_ipv6_ucast_hops(fd, 255) == -1) { close(fd); return (-1); } } break; } switch (type) { case LDP_SOCKET_DISC: case LDP_SOCKET_EDISC: sock_set_recvbuf(fd); break; case LDP_SOCKET_SESSION: if (listen(fd, LDP_BACKLOG) == -1) log_warn("%s: error listening on socket", __func__); #ifdef __OpenBSD__ opt = 1; if (setsockopt(fd, IPPROTO_TCP, TCP_MD5SIG, &opt, sizeof(opt)) == -1) { if (errno == ENOPROTOOPT) { /* system w/o md5sig */ log_warnx("md5sig not available, disabling"); sysdep.no_md5sig = 1; } else { close(fd); return (-1); } } #endif break; } return (fd); } void sock_set_nonblock(int fd) { int flags; if ((flags = fcntl(fd, F_GETFL, 0)) == -1) fatal("fcntl F_GETFL"); flags |= O_NONBLOCK; if ((flags = fcntl(fd, F_SETFL, flags)) == -1) fatal("fcntl F_SETFL"); } void sock_set_cloexec(int fd) { int flags; if ((flags = fcntl(fd, F_GETFD, 0)) == -1) fatal("fcntl F_GETFD"); flags |= FD_CLOEXEC; if ((flags = fcntl(fd, F_SETFD, flags)) == -1) fatal("fcntl F_SETFD"); } void sock_set_recvbuf(int fd) { int bsize; bsize = 65535; while (setsockopt(fd, SOL_SOCKET, SO_RCVBUF, &bsize, sizeof(bsize)) == -1) bsize /= 2; } int sock_set_reuse(int fd, int enable) { if (setsockopt(fd, SOL_SOCKET, SO_REUSEADDR, &enable, sizeof(int)) < 0) { log_warn("%s: error setting SO_REUSEADDR", __func__); return (-1); } return (0); } int sock_set_bindany(int fd, int enable) { #ifdef HAVE_SO_BINDANY frr_with_privs(&ldpd_privs) { if (setsockopt(fd, SOL_SOCKET, SO_BINDANY, &enable, sizeof(int)) < 0) { log_warn("%s: error setting SO_BINDANY", __func__); return (-1); } } return (0); #elif defined(HAVE_IP_FREEBIND) if (setsockopt(fd, IPPROTO_IP, IP_FREEBIND, &enable, sizeof(int)) < 0) { log_warn("%s: error setting IP_FREEBIND", __func__); return (-1); } return (0); #elif defined(IP_BINDANY) frr_with_privs(&ldpd_privs) { if (setsockopt(fd, IPPROTO_IP, IP_BINDANY, &enable, sizeof(int)) < 0) { log_warn("%s: error setting IP_BINDANY", __func__); return (-1); } } return (0); #else log_warnx( "%s: missing SO_BINDANY, IP_FREEBIND and IP_BINDANY, unable to bind to a nonlocal IP address", __func__); return (-1); #endif /* HAVE_SO_BINDANY */ } #ifndef __OpenBSD__ /* * Set MD5 key for the socket, for the given peer address. If the password * is NULL or zero-length, the option will be disabled. */ int sock_set_md5sig(int fd, int af, union ldpd_addr *addr, const char *password) { int ret = -1; int save_errno = ENOSYS; #if HAVE_DECL_TCP_MD5SIG union sockunion su; #endif if (fd == -1) return (0); #if HAVE_DECL_TCP_MD5SIG addr2sa(af, addr, 0, &su); frr_with_privs(&ldpe_privs) { ret = sockopt_tcp_signature(fd, &su, password); save_errno = errno; } #endif /* HAVE_TCP_MD5SIG */ if (ret < 0) log_warnx("%s: can't set TCP_MD5SIG option on fd %d: %s", __func__, fd, safe_strerror(save_errno)); return (ret); } #endif int sock_set_ipv4_tos(int fd, int tos) { if (setsockopt(fd, IPPROTO_IP, IP_TOS, (int *)&tos, sizeof(tos)) < 0) { log_warn("%s: error setting IP_TOS to 0x%x", __func__, tos); return (-1); } return (0); } int sock_set_ipv4_recvif(int fd, int enable) { return (setsockopt_ifindex(AF_INET, fd, enable)); } int sock_set_ipv4_minttl(int fd, int ttl) { return (sockopt_minttl(AF_INET, fd, ttl)); } int sock_set_ipv4_ucast_ttl(int fd, int ttl) { if (setsockopt(fd, IPPROTO_IP, IP_TTL, &ttl, sizeof(ttl)) < 0) { log_warn("%s: error setting IP_TTL", __func__); return (-1); } return (0); } int sock_set_ipv4_mcast_ttl(int fd, uint8_t ttl) { if (setsockopt(fd, IPPROTO_IP, IP_MULTICAST_TTL, (char *)&ttl, sizeof(ttl)) < 0) { log_warn("%s: error setting IP_MULTICAST_TTL to %d", __func__, ttl); return (-1); } return (0); } #ifndef MSG_MCAST #if defined(HAVE_IP_PKTINFO) int sock_set_ipv4_pktinfo(int fd, int enable) { if (setsockopt(fd, IPPROTO_IP, IP_PKTINFO, &enable, sizeof(enable)) < 0) { log_warn("%s: error setting IP_PKTINFO", __func__); return (-1); } return (0); } #elif defined(HAVE_IP_RECVDSTADDR) int sock_set_ipv4_recvdstaddr(int fd, int enable) { if (setsockopt(fd, IPPROTO_IP, IP_RECVDSTADDR, &enable, sizeof(enable)) < 0) { log_warn("%s: error setting IP_RECVDSTADDR", __func__); return (-1); } return (0); } #else #error "Unsupported socket API" #endif #endif /* MSG_MCAST */ int sock_set_ipv4_mcast(struct iface *iface) { struct in_addr if_addr; if_addr.s_addr = if_get_ipv4_addr(iface); if (setsockopt_ipv4_multicast_if(global.ipv4.ldp_disc_socket, if_addr, iface->ifindex) < 0) { log_warn("%s: error setting IP_MULTICAST_IF, interface %s", __func__, iface->name); return (-1); } return (0); } int sock_set_ipv4_mcast_loop(int fd) { return (setsockopt_ipv4_multicast_loop(fd, 0)); } int sock_set_ipv6_dscp(int fd, int dscp) { if (setsockopt(fd, IPPROTO_IPV6, IPV6_TCLASS, &dscp, sizeof(dscp)) < 0) { log_warn("%s: error setting IPV6_TCLASS", __func__); return (-1); } return (0); } int sock_set_ipv6_pktinfo(int fd, int enable) { if (setsockopt(fd, IPPROTO_IPV6, IPV6_RECVPKTINFO, &enable, sizeof(enable)) < 0) { log_warn("%s: error setting IPV6_RECVPKTINFO", __func__); return (-1); } return (0); } int sock_set_ipv6_minhopcount(int fd, int hoplimit) { return (sockopt_minttl(AF_INET6, fd, hoplimit)); } int sock_set_ipv6_ucast_hops(int fd, int hoplimit) { if (setsockopt(fd, IPPROTO_IPV6, IPV6_UNICAST_HOPS, &hoplimit, sizeof(hoplimit)) < 0) { log_warn("%s: error setting IPV6_UNICAST_HOPS", __func__); return (-1); } return (0); } int sock_set_ipv6_mcast_hops(int fd, int hoplimit) { if (setsockopt(fd, IPPROTO_IPV6, IPV6_MULTICAST_HOPS, &hoplimit, sizeof(hoplimit)) < 0) { log_warn("%s: error setting IPV6_MULTICAST_HOPS", __func__); return (-1); } return (0); } int sock_set_ipv6_mcast(struct iface *iface) { if (setsockopt(global.ipv6.ldp_disc_socket, IPPROTO_IPV6, IPV6_MULTICAST_IF, &iface->ifindex, sizeof(iface->ifindex)) < 0) { log_warn("%s: error setting IPV6_MULTICAST_IF, interface %s", __func__, iface->name); return (-1); } return (0); } int sock_set_ipv6_mcast_loop(int fd) { unsigned int loop = 0; if (setsockopt(fd, IPPROTO_IPV6, IPV6_MULTICAST_LOOP, &loop, sizeof(loop)) < 0) { log_warn("%s: error setting IPV6_MULTICAST_LOOP", __func__); return (-1); } return (0); } frr-7.2.1/ldpd/subdir.am0000644000000000000000000000207113610377563011746 00000000000000# # ldpd # if LDPD noinst_LIBRARIES += ldpd/libldp.a sbin_PROGRAMS += ldpd/ldpd dist_examples_DATA += ldpd/ldpd.conf.sample vtysh_scan += $(top_srcdir)/ldpd/ldp_vty_cmds.c man8 += $(MANBUILD)/frr-ldpd.8 endif ldpd_libldp_a_SOURCES = \ ldpd/accept.c \ ldpd/address.c \ ldpd/adjacency.c \ ldpd/control.c \ ldpd/hello.c \ ldpd/init.c \ ldpd/interface.c \ ldpd/keepalive.c \ ldpd/l2vpn.c \ ldpd/labelmapping.c \ ldpd/lde.c \ ldpd/lde_lib.c \ ldpd/ldp_debug.c \ ldpd/ldp_vty_cmds.c \ ldpd/ldp_vty_conf.c \ ldpd/ldp_vty_exec.c \ ldpd/ldp_zebra.c \ ldpd/ldpd.c \ ldpd/ldpe.c \ ldpd/log.c \ ldpd/logmsg.c \ ldpd/neighbor.c \ ldpd/notification.c \ ldpd/packet.c \ ldpd/pfkey.c \ ldpd/socket.c \ ldpd/util.c \ # end ldpd/ldp_vty_cmds_clippy.c: $(CLIPPY_DEPS) ldpd/ldp_vty_cmds.$(OBJEXT): ldpd/ldp_vty_cmds_clippy.c noinst_HEADERS += \ ldpd/control.h \ ldpd/lde.h \ ldpd/ldp.h \ ldpd/ldp_debug.h \ ldpd/ldp_vty.h \ ldpd/ldpd.h \ ldpd/ldpe.h \ ldpd/log.h \ # end ldpd_ldpd_SOURCES = ldpd/ldpd.c ldpd_ldpd_LDADD = ldpd/libldp.a lib/libfrr.la $(LIBCAP) frr-7.2.1/ldpd/util.c0000644000000000000000000002012413610377563011257 00000000000000/* $OpenBSD$ */ /* * Copyright (c) 2015 Renato Westphal * Copyright (c) 2012 Alexander Bluhm * Copyright (c) 2004 Esben Norby * Copyright (c) 2003, 2004 Henning Brauer * * Permission to use, copy, modify, and distribute this software for any * purpose with or without fee is hereby granted, provided that the above * copyright notice and this permission notice appear in all copies. * * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. */ #include #include "ldpd.h" #include "log.h" uint8_t mask2prefixlen(in_addr_t ina) { if (ina == 0) return (0); else return (33 - ffs(ntohl(ina))); } uint8_t mask2prefixlen6(struct sockaddr_in6 *sa_in6) { uint8_t l = 0, *ap, *ep; /* * sin6_len is the size of the sockaddr so substract the offset of * the possibly truncated sin6_addr struct. */ ap = (uint8_t *)&sa_in6->sin6_addr; ep = (uint8_t *)sa_in6 + sockaddr_len((struct sockaddr *)sa_in6); for (; ap < ep; ap++) { /* this "beauty" is adopted from sbin/route/show.c ... */ switch (*ap) { case 0xff: l += 8; break; case 0xfe: l += 7; return (l); case 0xfc: l += 6; return (l); case 0xf8: l += 5; return (l); case 0xf0: l += 4; return (l); case 0xe0: l += 3; return (l); case 0xc0: l += 2; return (l); case 0x80: l += 1; return (l); case 0x00: return (l); default: fatalx("non contiguous inet6 netmask"); } } return (l); } in_addr_t prefixlen2mask(uint8_t prefixlen) { if (prefixlen == 0) return (0); return (htonl(0xffffffff << (32 - prefixlen))); } struct in6_addr * prefixlen2mask6(uint8_t prefixlen) { static struct in6_addr mask; int i; memset(&mask, 0, sizeof(mask)); for (i = 0; i < prefixlen / 8; i++) mask.s6_addr[i] = 0xff; i = prefixlen % 8; if (i) mask.s6_addr[prefixlen / 8] = 0xff00 >> i; return (&mask); } void ldp_applymask(int af, union ldpd_addr *dest, const union ldpd_addr *src, int prefixlen) { struct in6_addr mask; int i; switch (af) { case AF_INET: dest->v4.s_addr = src->v4.s_addr & prefixlen2mask(prefixlen); break; case AF_INET6: memset(&mask, 0, sizeof(mask)); for (i = 0; i < prefixlen / 8; i++) mask.s6_addr[i] = 0xff; i = prefixlen % 8; if (i) mask.s6_addr[prefixlen / 8] = 0xff00 >> i; for (i = 0; i < 16; i++) dest->v6.s6_addr[i] = src->v6.s6_addr[i] & mask.s6_addr[i]; break; default: fatalx("ldp_applymask: unknown af"); } } int ldp_addrcmp(int af, const union ldpd_addr *a, const union ldpd_addr *b) { switch (af) { case AF_INET: if (a->v4.s_addr == b->v4.s_addr) return (0); return ((ntohl(a->v4.s_addr) > ntohl(b->v4.s_addr)) ? 1 : -1); case AF_INET6: return (memcmp(&a->v6, &b->v6, sizeof(struct in6_addr))); default: fatalx("ldp_addrcmp: unknown af"); } } int ldp_addrisset(int af, const union ldpd_addr *addr) { switch (af) { case AF_UNSPEC: return (0); case AF_INET: if (addr->v4.s_addr != INADDR_ANY) return (1); break; case AF_INET6: if (!IN6_IS_ADDR_UNSPECIFIED(&addr->v6)) return (1); break; default: fatalx("ldp_addrisset: unknown af"); } return (0); } int ldp_prefixcmp(int af, const union ldpd_addr *a, const union ldpd_addr *b, uint8_t prefixlen) { in_addr_t mask, aa, ba; int i; uint8_t m; switch (af) { case AF_INET: if (prefixlen == 0) return (0); if (prefixlen > 32) fatalx("ldp_prefixcmp: bad IPv4 prefixlen"); mask = htonl(prefixlen2mask(prefixlen)); aa = htonl(a->v4.s_addr) & mask; ba = htonl(b->v4.s_addr) & mask; return (aa - ba); case AF_INET6: if (prefixlen == 0) return (0); if (prefixlen > 128) fatalx("ldp_prefixcmp: bad IPv6 prefixlen"); for (i = 0; i < prefixlen / 8; i++) if (a->v6.s6_addr[i] != b->v6.s6_addr[i]) return (a->v6.s6_addr[i] - b->v6.s6_addr[i]); i = prefixlen % 8; if (i) { m = 0xff00 >> i; if ((a->v6.s6_addr[prefixlen / 8] & m) != (b->v6.s6_addr[prefixlen / 8] & m)) return ((a->v6.s6_addr[prefixlen / 8] & m) - (b->v6.s6_addr[prefixlen / 8] & m)); } return (0); default: fatalx("ldp_prefixcmp: unknown af"); } return (-1); } int bad_addr_v4(struct in_addr addr) { uint32_t a = ntohl(addr.s_addr); if (((a >> IN_CLASSA_NSHIFT) == 0) || ((a >> IN_CLASSA_NSHIFT) == IN_LOOPBACKNET) || IN_MULTICAST(a) || IN_BADCLASS(a)) return (1); return (0); } int bad_addr_v6(struct in6_addr *addr) { if (IN6_IS_ADDR_UNSPECIFIED(addr) || IN6_IS_ADDR_LOOPBACK(addr) || IN6_IS_ADDR_MULTICAST(addr) || IN6_IS_ADDR_SITELOCAL(addr) || IN6_IS_ADDR_V4MAPPED(addr) || IN6_IS_ADDR_V4COMPAT(addr)) return (1); return (0); } int bad_addr(int af, union ldpd_addr *addr) { switch (af) { case AF_INET: return (bad_addr_v4(addr->v4)); case AF_INET6: return (bad_addr_v6(&addr->v6)); default: fatalx("bad_addr: unknown af"); } } void embedscope(struct sockaddr_in6 *sin6) { uint16_t tmp16; if (IN6_IS_SCOPE_EMBED(&sin6->sin6_addr)) { memcpy(&tmp16, &sin6->sin6_addr.s6_addr[2], sizeof(tmp16)); if (tmp16 != 0) { log_warnx("%s: address %s already has embedded scope %u", __func__, log_sockaddr(sin6), ntohs(tmp16)); } tmp16 = htons(sin6->sin6_scope_id); memcpy(&sin6->sin6_addr.s6_addr[2], &tmp16, sizeof(tmp16)); sin6->sin6_scope_id = 0; } } void recoverscope(struct sockaddr_in6 *sin6) { uint16_t tmp16; if (sin6->sin6_scope_id != 0) log_warnx("%s: address %s already has scope id %u", __func__, log_sockaddr(sin6), sin6->sin6_scope_id); if (IN6_IS_SCOPE_EMBED(&sin6->sin6_addr)) { memcpy(&tmp16, &sin6->sin6_addr.s6_addr[2], sizeof(tmp16)); sin6->sin6_scope_id = ntohs(tmp16); sin6->sin6_addr.s6_addr[2] = 0; sin6->sin6_addr.s6_addr[3] = 0; } } void addscope(struct sockaddr_in6 *sin6, uint32_t id) { if (sin6->sin6_scope_id != 0) log_warnx("%s: address %s already has scope id %u", __func__, log_sockaddr(sin6), sin6->sin6_scope_id); if (IN6_IS_SCOPE_EMBED(&sin6->sin6_addr)) sin6->sin6_scope_id = id; } void clearscope(struct in6_addr *in6) { if (IN6_IS_SCOPE_EMBED(in6)) { in6->s6_addr[2] = 0; in6->s6_addr[3] = 0; } } void addr2sa(int af, const union ldpd_addr *addr, uint16_t port, union sockunion *su) { struct sockaddr_in *sa_in = &su->sin; struct sockaddr_in6 *sa_in6 = &su->sin6; memset(su, 0, sizeof(*su)); switch (af) { case AF_INET: sa_in->sin_family = AF_INET; #ifdef HAVE_STRUCT_SOCKADDR_IN_SIN_LEN sa_in->sin_len = sizeof(struct sockaddr_in); #endif sa_in->sin_addr = addr->v4; sa_in->sin_port = htons(port); break; case AF_INET6: sa_in6->sin6_family = AF_INET6; #ifdef HAVE_STRUCT_SOCKADDR_IN_SIN_LEN sa_in6->sin6_len = sizeof(struct sockaddr_in6); #endif sa_in6->sin6_addr = addr->v6; sa_in6->sin6_port = htons(port); break; default: fatalx("addr2sa: unknown af"); } } void sa2addr(struct sockaddr *sa, int *af, union ldpd_addr *addr, in_port_t *port) { struct sockaddr_in *sa_in = (struct sockaddr_in *)sa; struct sockaddr_in6 *sa_in6 = (struct sockaddr_in6 *)sa; if (addr) memset(addr, 0, sizeof(*addr)); switch (sa->sa_family) { case AF_INET: if (af) *af = AF_INET; if (addr) addr->v4 = sa_in->sin_addr; if (port) *port = sa_in->sin_port; break; case AF_INET6: if (af) *af = AF_INET6; if (addr) addr->v6 = sa_in6->sin6_addr; if (port) *port = sa_in6->sin6_port; break; default: fatalx("sa2addr: unknown af"); } } socklen_t sockaddr_len(struct sockaddr *sa) { #ifdef HAVE_STRUCT_SOCKADDR_IN_SIN_LEN return (sa->sa_len); #else switch (sa->sa_family) { case AF_INET: return (sizeof(struct sockaddr_in)); case AF_INET6: return (sizeof(struct sockaddr_in6)); default: fatalx("sockaddr_len: unknown af"); } #endif } frr-7.2.1/lib/0000755000000000000000000000000013610377563010042 500000000000000frr-7.2.1/lib/Makefile0000644000000000000000000000022213610377563011416 00000000000000all: ALWAYS @$(MAKE) -s -C .. lib/libfrr.la %: ALWAYS @$(MAKE) -s -C .. lib/$@ Makefile: #nothing ALWAYS: .PHONY: ALWAYS makefiles .SUFFIXES: frr-7.2.1/lib/agentx.c0000644000000000000000000002013213610377563011412 00000000000000/* SNMP support * Copyright (C) 2012 Vincent Bernat * * This file is part of GNU Zebra. * * GNU Zebra is free software; you can redistribute it and/or modify it * under the terms of the GNU General Public License as published by the * Free Software Foundation; either version 2, or (at your option) any * later version. * * GNU Zebra 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; see the file COPYING; if not, write to the Free Software * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA */ #include #ifdef SNMP_AGENTX #include #include #include #include #include "command.h" #include "smux.h" #include "memory.h" #include "linklist.h" #include "version.h" #include "lib_errors.h" static int agentx_enabled = 0; static struct thread_master *agentx_tm; static struct thread *timeout_thr = NULL; static struct list *events = NULL; static void agentx_events_update(void); static int agentx_timeout(struct thread *t) { timeout_thr = NULL; snmp_timeout(); run_alarms(); netsnmp_check_outstanding_agent_requests(); agentx_events_update(); return 0; } static int agentx_read(struct thread *t) { fd_set fds; struct listnode *ln = THREAD_ARG(t); list_delete_node(events, ln); FD_ZERO(&fds); FD_SET(THREAD_FD(t), &fds); snmp_read(&fds); netsnmp_check_outstanding_agent_requests(); agentx_events_update(); return 0; } static void agentx_events_update(void) { int maxfd = 0; int block = 1; struct timeval timeout = {.tv_sec = 0, .tv_usec = 0}; fd_set fds; struct listnode *ln; struct thread *thr; int fd, thr_fd; THREAD_OFF(timeout_thr); FD_ZERO(&fds); snmp_select_info(&maxfd, &fds, &timeout, &block); if (!block) { timeout_thr = NULL; thread_add_timer_tv(agentx_tm, agentx_timeout, NULL, &timeout, &timeout_thr); } ln = listhead(events); thr = ln ? listgetdata(ln) : NULL; thr_fd = thr ? THREAD_FD(thr) : -1; /* "two-pointer" / two-list simultaneous iteration * ln/thr/thr_fd point to the next existing event listener to hit while * fd counts to catch up */ for (fd = 0; fd < maxfd; fd++) { /* caught up */ if (thr_fd == fd) { struct listnode *nextln = listnextnode(ln); if (!FD_ISSET(fd, &fds)) { thread_cancel(thr); list_delete_node(events, ln); } ln = nextln; thr = ln ? listgetdata(ln) : NULL; thr_fd = thr ? THREAD_FD(thr) : -1; } /* need listener, but haven't hit one where it would be */ else if (FD_ISSET(fd, &fds)) { struct listnode *newln; thr = NULL; thread_add_read(agentx_tm, agentx_read, NULL, fd, &thr); newln = listnode_add_before(events, ln, thr); thr->arg = newln; } } /* leftover event listeners at this point have fd > maxfd, delete them */ while (ln) { struct listnode *nextln = listnextnode(ln); thread_cancel(listgetdata(ln)); list_delete_node(events, ln); ln = nextln; } } /* AgentX node. */ static struct cmd_node agentx_node = {SMUX_NODE, "", /* AgentX has no interface. */ 1}; /* Logging NetSNMP messages */ static int agentx_log_callback(int major, int minor, void *serverarg, void *clientarg) { struct snmp_log_message *slm = (struct snmp_log_message *)serverarg; char *msg = XSTRDUP(MTYPE_TMP, slm->msg); if (msg) msg[strlen(msg) - 1] = '\0'; switch (slm->priority) { case LOG_EMERG: flog_err(EC_LIB_SNMP, "snmp[emerg]: %s", msg ? msg : slm->msg); break; case LOG_ALERT: flog_err(EC_LIB_SNMP, "snmp[alert]: %s", msg ? msg : slm->msg); break; case LOG_CRIT: flog_err(EC_LIB_SNMP, "snmp[crit]: %s", msg ? msg : slm->msg); break; case LOG_ERR: flog_err(EC_LIB_SNMP, "snmp[err]: %s", msg ? msg : slm->msg); break; case LOG_WARNING: flog_warn(EC_LIB_SNMP, "snmp[warning]: %s", msg ? msg : slm->msg); break; case LOG_NOTICE: zlog_notice("snmp[notice]: %s", msg ? msg : slm->msg); break; case LOG_INFO: zlog_info("snmp[info]: %s", msg ? msg : slm->msg); break; case LOG_DEBUG: zlog_debug("snmp[debug]: %s", msg ? msg : slm->msg); break; } XFREE(MTYPE_TMP, msg); return SNMP_ERR_NOERROR; } static int config_write_agentx(struct vty *vty) { if (agentx_enabled) vty_out(vty, "agentx\n"); return 1; } DEFUN (agentx_enable, agentx_enable_cmd, "agentx", "SNMP AgentX protocol settings\n") { if (!agentx_enabled) { init_snmp(FRR_SMUX_NAME); events = list_new(); agentx_events_update(); agentx_enabled = 1; } return CMD_SUCCESS; } DEFUN (no_agentx, no_agentx_cmd, "no agentx", NO_STR "SNMP AgentX protocol settings\n") { if (!agentx_enabled) return CMD_SUCCESS; vty_out(vty, "SNMP AgentX support cannot be disabled once enabled\n"); return CMD_WARNING_CONFIG_FAILED; } void smux_init(struct thread_master *tm) { agentx_tm = tm; netsnmp_enable_subagent(); snmp_disable_log(); snmp_enable_calllog(); snmp_register_callback(SNMP_CALLBACK_LIBRARY, SNMP_CALLBACK_LOGGING, agentx_log_callback, NULL); init_agent(FRR_SMUX_NAME); install_node(&agentx_node, config_write_agentx); install_element(CONFIG_NODE, &agentx_enable_cmd); install_element(CONFIG_NODE, &no_agentx_cmd); } void smux_register_mib(const char *descr, struct variable *var, size_t width, int num, oid name[], size_t namelen) { register_mib(descr, var, width, num, name, namelen); } int smux_trap(struct variable *vp, size_t vp_len, const oid *ename, size_t enamelen, const oid *name, size_t namelen, const oid *iname, size_t inamelen, const struct trap_object *trapobj, size_t trapobjlen, uint8_t sptrap) { oid objid_snmptrap[] = {1, 3, 6, 1, 6, 3, 1, 1, 4, 1, 0}; size_t objid_snmptrap_len = sizeof objid_snmptrap / sizeof(oid); oid notification_oid[MAX_OID_LEN]; size_t notification_oid_len; unsigned int i; netsnmp_variable_list *notification_vars = NULL; if (!agentx_enabled) return 0; /* snmpTrapOID */ oid_copy(notification_oid, ename, enamelen); notification_oid[enamelen] = sptrap; notification_oid_len = enamelen + 1; snmp_varlist_add_variable(¬ification_vars, objid_snmptrap, objid_snmptrap_len, ASN_OBJECT_ID, (uint8_t *)notification_oid, notification_oid_len * sizeof(oid)); /* Provided bindings */ for (i = 0; i < trapobjlen; i++) { unsigned int j; oid oid[MAX_OID_LEN]; size_t oid_len, onamelen; uint8_t *val; size_t val_len; WriteMethod *wm = NULL; struct variable cvp; /* Make OID. */ if (trapobj[i].namelen > 0) { /* Columnar object */ onamelen = trapobj[i].namelen; oid_copy(oid, name, namelen); oid_copy(oid + namelen, trapobj[i].name, onamelen); oid_copy(oid + namelen + onamelen, iname, inamelen); oid_len = namelen + onamelen + inamelen; } else { /* Scalar object */ onamelen = trapobj[i].namelen * (-1); oid_copy(oid, name, namelen); oid_copy(oid + namelen, trapobj[i].name, onamelen); oid[onamelen + namelen] = 0; oid_len = namelen + onamelen + 1; } /* Locate the appropriate function and type in the MIB registry. */ for (j = 0; j < vp_len; j++) { if (oid_compare(trapobj[i].name, onamelen, vp[j].name, vp[j].namelen) != 0) continue; /* We found the appropriate variable in the MIB * registry. */ oid_copy(cvp.name, name, namelen); oid_copy(cvp.name + namelen, vp[j].name, vp[j].namelen); cvp.namelen = namelen + vp[j].namelen; cvp.type = vp[j].type; cvp.magic = vp[j].magic; cvp.acl = vp[j].acl; cvp.findVar = vp[j].findVar; /* Grab the result. */ val = cvp.findVar(&cvp, oid, &oid_len, 1, &val_len, &wm); if (!val) break; snmp_varlist_add_variable(¬ification_vars, oid, oid_len, vp[j].type, val, val_len); break; } } send_v2trap(notification_vars); snmp_free_varbind(notification_vars); agentx_events_update(); return 1; } #endif /* SNMP_AGENTX */ frr-7.2.1/lib/agg_table.c0000644000000000000000000000316513610377563012040 00000000000000/* * Aggregate Route * Copyright (C) 2018 Cumulus Networks, Inc. * Donald Sharp * * FRR is free software; you can redistribute it and/or modify it * under the terms of the GNU General Public License as published by the * Free Software Foundation; either version 2, or (at your option) any * later version. * * FRR 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; see the file COPYING; if not, write to the Free Software * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA */ #include "zebra.h" #include "agg_table.h" static struct route_node *agg_node_create(route_table_delegate_t *delegate, struct route_table *table) { struct agg_node *node; node = XCALLOC(MTYPE_TMP, sizeof(struct agg_node)); return agg_node_to_rnode(node); } static void agg_node_destroy(route_table_delegate_t *delegate, struct route_table *table, struct route_node *node) { struct agg_node *anode = agg_node_from_rnode(node); XFREE(MTYPE_TMP, anode); } route_table_delegate_t agg_table_delegate = { .create_node = agg_node_create, .destroy_node = agg_node_destroy, }; struct agg_table *agg_table_init(void) { struct agg_table *at; at = XCALLOC(MTYPE_TMP, sizeof(struct agg_table)); at->route_table = route_table_init_with_delegate(&agg_table_delegate); route_table_set_info(at->route_table, at); return at; } frr-7.2.1/lib/agg_table.h0000644000000000000000000000757513610377563012056 00000000000000/* * agg_table - Aggregate Table Header * Copyright (C) 2018 Cumulus Networks, Inc. * Donald Sharp * * FRR is free software; you can redistribute it and/or modify it * under the terms of the GNU General Public License as published by the * Free Software Foundation; either version 2, or (at your option) any * later version. * * FRR 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; see the file COPYING; if not, write to the Free Software * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA */ #ifndef __AGG_TABLE_H__ #define __AGG_TABLE_H__ #include "prefix.h" #include "table.h" #ifdef __cplusplus extern "C" { #endif struct agg_table { struct route_table *route_table; void *info; }; struct agg_node { /* * Caution these must be the very first fields * @see agg_node_to_rnode * @see agg_node_from_rnode */ ROUTE_NODE_FIELDS /* Aggregation. */ void *aggregate; }; static inline struct route_node *agg_node_to_rnode(struct agg_node *node) { return (struct route_node *)node; } static inline struct agg_node *agg_node_from_rnode(struct route_node *node) { return (struct agg_node *)node; } static inline struct agg_node *agg_lock_node(struct agg_node *node) { return (struct agg_node *)route_lock_node(agg_node_to_rnode(node)); } static inline void agg_unlock_node(struct agg_node *node) { route_unlock_node(agg_node_to_rnode(node)); } static inline void agg_set_table_info(struct agg_table *atable, void *data) { atable->info = data; } static inline void *agg_get_table_info(struct agg_table *atable) { return atable->info; } static inline struct agg_node *agg_route_top(struct agg_table *table) { return agg_node_from_rnode(route_top(table->route_table)); } static inline struct agg_node *agg_route_next(struct agg_node *node) { return agg_node_from_rnode(route_next(agg_node_to_rnode(node))); } static inline struct agg_node *agg_node_get(struct agg_table *table, struct prefix *p) { return agg_node_from_rnode(route_node_get(table->route_table, p)); } static inline struct agg_node * agg_node_lookup(const struct agg_table *const table, struct prefix *p) { return agg_node_from_rnode(route_node_lookup(table->route_table, p)); } static inline struct agg_node *agg_route_next_until(struct agg_node *node, struct agg_node *limit) { struct route_node *rnode; rnode = route_next_until(agg_node_to_rnode(node), agg_node_to_rnode(limit)); return agg_node_from_rnode(rnode); } static inline struct agg_node *agg_node_match(struct agg_table *table, struct prefix *p) { return agg_node_from_rnode(route_node_match(table->route_table, p)); } static inline struct agg_node *agg_node_parent(struct agg_node *node) { struct route_node *rn = agg_node_to_rnode(node); return agg_node_from_rnode(rn->parent); } static inline struct agg_node *agg_node_left(struct agg_node *node) { struct route_node *rn = agg_node_to_rnode(node); return agg_node_from_rnode(rn->l_left); } static inline struct agg_node *agg_node_right(struct agg_node *node) { struct route_node *rn = agg_node_to_rnode(node); return agg_node_from_rnode(rn->l_right); } extern struct agg_table *agg_table_init(void); static inline void agg_table_finish(struct agg_table *atable) { route_table_finish(atable->route_table); atable->route_table = NULL; XFREE(MTYPE_TMP, atable); } static inline struct agg_node *agg_route_table_top(struct agg_node *node) { return (struct agg_node *)route_top(node->table); } static inline struct agg_table *agg_get_table(struct agg_node *node) { return (struct agg_table *)route_table_get_info(node->table); } #ifdef __cplusplus } #endif #endif frr-7.2.1/lib/atomlist.c0000644000000000000000000002152013610377563011762 00000000000000/* * Copyright (c) 2016-2018 David Lamparter, for NetDEF, Inc. * * Permission to use, copy, modify, and distribute this software for any * purpose with or without fee is hereby granted, provided that the above * copyright notice and this permission notice appear in all copies. * * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. */ #ifdef HAVE_CONFIG_H #include "config.h" #endif #include "atomlist.h" void atomlist_add_head(struct atomlist_head *h, struct atomlist_item *item) { atomptr_t prevval; atomptr_t i = atomptr_i(item); atomic_fetch_add_explicit(&h->count, 1, memory_order_relaxed); /* updating ->last is possible here, but makes the code considerably * more complicated... let's not. */ prevval = ATOMPTR_NULL; item->next = ATOMPTR_NULL; /* head-insert atomically * release barrier: item + item->next writes must be completed */ while (!atomic_compare_exchange_weak_explicit(&h->first, &prevval, i, memory_order_release, memory_order_relaxed)) atomic_store_explicit(&item->next, prevval, memory_order_relaxed); } void atomlist_add_tail(struct atomlist_head *h, struct atomlist_item *item) { atomptr_t prevval = ATOMPTR_NULL; atomptr_t i = atomptr_i(item); atomptr_t hint; struct atomlist_item *prevptr; _Atomic atomptr_t *prev; item->next = ATOMPTR_NULL; atomic_fetch_add_explicit(&h->count, 1, memory_order_relaxed); /* place new item into ->last * release: item writes completed; acquire: DD barrier on hint */ hint = atomic_exchange_explicit(&h->last, i, memory_order_acq_rel); while (1) { if (atomptr_p(hint) == NULL) prev = &h->first; else prev = &atomlist_itemp(hint)->next; do { prevval = atomic_load_explicit(prev, memory_order_consume); prevptr = atomlist_itemp(prevval); if (prevptr == NULL) break; prev = &prevptr->next; } while (prevptr); /* last item is being deleted - start over */ if (atomptr_l(prevval)) { hint = ATOMPTR_NULL; continue; } /* no barrier - item->next is NULL and was so in xchg above */ if (!atomic_compare_exchange_strong_explicit(prev, &prevval, i, memory_order_consume, memory_order_consume)) { hint = prevval; continue; } break; } } static void atomlist_del_core(struct atomlist_head *h, struct atomlist_item *item, _Atomic atomptr_t *hint, atomptr_t next) { _Atomic atomptr_t *prev = hint ? hint : &h->first, *upd; atomptr_t prevval, updval; struct atomlist_item *prevptr; /* drop us off "last" if needed. no r/w to barrier. */ prevval = atomptr_i(item); atomic_compare_exchange_strong_explicit(&h->last, &prevval, ATOMPTR_NULL, memory_order_relaxed, memory_order_relaxed); atomic_fetch_sub_explicit(&h->count, 1, memory_order_relaxed); /* the following code should be identical (except sort<>list) to * atomsort_del_hint() */ while (1) { upd = NULL; updval = ATOMPTR_LOCK; do { prevval = atomic_load_explicit(prev, memory_order_consume); /* track the beginning of a chain of deleted items * this is neccessary to make this lock-free; we can * complete deletions started by other threads. */ if (!atomptr_l(prevval)) { updval = prevval; upd = prev; } prevptr = atomlist_itemp(prevval); if (prevptr == item) break; prev = &prevptr->next; } while (prevptr); if (prevptr != item) /* another thread completed our deletion */ return; if (!upd || atomptr_l(updval)) { /* failed to find non-deleted predecessor... * have to try again */ prev = &h->first; continue; } if (!atomic_compare_exchange_strong_explicit(upd, &updval, next, memory_order_consume, memory_order_consume)) { /* prev doesn't point to item anymore, something * was inserted. continue at same position forward. */ continue; } break; } } void atomlist_del_hint(struct atomlist_head *h, struct atomlist_item *item, _Atomic atomptr_t *hint) { atomptr_t next; /* mark ourselves in-delete - full barrier */ next = atomic_fetch_or_explicit(&item->next, ATOMPTR_LOCK, memory_order_acquire); assert(!atomptr_l(next)); /* delete race on same item */ atomlist_del_core(h, item, hint, next); } struct atomlist_item *atomlist_pop(struct atomlist_head *h) { struct atomlist_item *item; atomptr_t next; /* grab head of the list - and remember it in replval for the * actual delete below. No matter what, the head of the list is * where we start deleting because either it's our item, or it's * some delete-marked items and then our item. */ next = atomic_load_explicit(&h->first, memory_order_consume); do { item = atomlist_itemp(next); if (!item) return NULL; /* try to mark deletion */ next = atomic_fetch_or_explicit(&item->next, ATOMPTR_LOCK, memory_order_acquire); } while (atomptr_l(next)); /* if loop is taken: delete race on same item (another pop or del) * => proceed to next item * if loop exited here: we have our item selected and marked */ atomlist_del_core(h, item, &h->first, next); return item; } struct atomsort_item *atomsort_add(struct atomsort_head *h, struct atomsort_item *item, int (*cmpfn)( const struct atomsort_item *, const struct atomsort_item *)) { _Atomic atomptr_t *prev; atomptr_t prevval; atomptr_t i = atomptr_i(item); struct atomsort_item *previtem; int cmpval; do { prev = &h->first; do { prevval = atomic_load_explicit(prev, memory_order_acquire); previtem = atomptr_p(prevval); if (!previtem || (cmpval = cmpfn(previtem, item)) > 0) break; if (cmpval == 0) return previtem; prev = &previtem->next; } while (1); if (atomptr_l(prevval)) continue; item->next = prevval; if (atomic_compare_exchange_strong_explicit(prev, &prevval, i, memory_order_release, memory_order_relaxed)) break; } while (1); atomic_fetch_add_explicit(&h->count, 1, memory_order_relaxed); return NULL; } static void atomsort_del_core(struct atomsort_head *h, struct atomsort_item *item, _Atomic atomptr_t *hint, atomptr_t next) { _Atomic atomptr_t *prev = hint ? hint : &h->first, *upd; atomptr_t prevval, updval; struct atomsort_item *prevptr; atomic_fetch_sub_explicit(&h->count, 1, memory_order_relaxed); /* the following code should be identical (except sort<>list) to * atomlist_del_core() */ while (1) { upd = NULL; updval = ATOMPTR_LOCK; do { prevval = atomic_load_explicit(prev, memory_order_consume); /* track the beginning of a chain of deleted items * this is neccessary to make this lock-free; we can * complete deletions started by other threads. */ if (!atomptr_l(prevval)) { updval = prevval; upd = prev; } prevptr = atomsort_itemp(prevval); if (prevptr == item) break; prev = &prevptr->next; } while (prevptr); if (prevptr != item) /* another thread completed our deletion */ return; if (!upd || atomptr_l(updval)) { /* failed to find non-deleted predecessor... * have to try again */ prev = &h->first; continue; } if (!atomic_compare_exchange_strong_explicit(upd, &updval, next, memory_order_relaxed, memory_order_relaxed)) { /* prev doesn't point to item anymore, something * was inserted. continue at same position forward. */ continue; } break; } } void atomsort_del_hint(struct atomsort_head *h, struct atomsort_item *item, _Atomic atomptr_t *hint) { atomptr_t next; /* mark ourselves in-delete - full barrier */ next = atomic_fetch_or_explicit(&item->next, ATOMPTR_LOCK, memory_order_seq_cst); assert(!atomptr_l(next)); /* delete race on same item */ atomsort_del_core(h, item, hint, next); } struct atomsort_item *atomsort_pop(struct atomsort_head *h) { struct atomsort_item *item; atomptr_t next; /* grab head of the list - and remember it in replval for the * actual delete below. No matter what, the head of the list is * where we start deleting because either it's our item, or it's * some delete-marked items and then our item. */ next = atomic_load_explicit(&h->first, memory_order_consume); do { item = atomsort_itemp(next); if (!item) return NULL; /* try to mark deletion */ next = atomic_fetch_or_explicit(&item->next, ATOMPTR_LOCK, memory_order_acquire); } while (atomptr_l(next)); /* if loop is taken: delete race on same item (another pop or del) * => proceed to next item * if loop exited here: we have our item selected and marked */ atomsort_del_core(h, item, &h->first, next); return item; } frr-7.2.1/lib/atomlist.h0000644000000000000000000004434113610377563011775 00000000000000/* * Copyright (c) 2016-2019 David Lamparter, for NetDEF, Inc. * * Permission to use, copy, modify, and distribute this software for any * purpose with or without fee is hereby granted, provided that the above * copyright notice and this permission notice appear in all copies. * * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. */ #ifndef _FRR_ATOMLIST_H #define _FRR_ATOMLIST_H #include "typesafe.h" #include "frratomic.h" /* pointer with lock/deleted/invalid bit in lowest bit * * for atomlist/atomsort, "locked" means "this pointer can't be updated, the * item is being deleted". it is permissible to assume the item will indeed * be deleted (as there are no replace/etc. ops in this). * * in general, lowest 2/3 bits on 32/64bit architectures are available for * uses like this; the only thing that will really break this is putting an * atomlist_item in a struct with "packed" attribute. (it'll break * immediately and consistently.) -- don't do that. * * ATOMPTR_USER is currently unused (and available for atomic hash or skiplist * implementations.) */ typedef uintptr_t atomptr_t; #define ATOMPTR_MASK (UINTPTR_MAX - 3) #define ATOMPTR_LOCK (1) #define ATOMPTR_USER (2) #define ATOMPTR_NULL (0) static inline atomptr_t atomptr_i(void *val) { atomptr_t atomval = (atomptr_t)val; assert(!(atomval & ATOMPTR_LOCK)); return atomval; } static inline void *atomptr_p(atomptr_t val) { return (void *)(val & ATOMPTR_MASK); } static inline bool atomptr_l(atomptr_t val) { return (bool)(val & ATOMPTR_LOCK); } static inline bool atomptr_u(atomptr_t val) { return (bool)(val & ATOMPTR_USER); } /* the problem with, find(), find_gteq() and find_lt() on atomic lists is that * they're neither an "acquire" nor a "release" operation; the element that * was found is still on the list and doesn't change ownership. Therefore, * an atomic transition in ownership state can't be implemented. * * Contrast this with add() or pop(): both function calls atomically transfer * ownership of an item to or from the list, which makes them "acquire" / * "release" operations. * * What can be implemented atomically is a "find_pop()", i.e. try to locate an * item and atomically try to remove it if found. It's not currently * implemented but can be added when needed. * * Either way - for find(), generally speaking, if you need to use find() on * a list then the whole thing probably isn't well-suited to atomic * implementation and you'll need to have extra locks around to make it work * correctly. */ #ifdef WNO_ATOMLIST_UNSAFE_FIND # define atomic_find_warn #else # define atomic_find_warn __attribute__((_DEPRECATED( \ "WARNING: find() on atomic lists cannot be atomic by principle; " \ "check code to make sure usage pattern is OK and if it is, use " \ "#define WNO_ATOMLIST_UNSAFE_FIND"))) #endif /* single-linked list, unsorted/arbitrary. * can be used as queue with add_tail / pop * * all operations are lock-free, but not neccessarily wait-free. this means * that there is no state where the system as a whole stops making process, * but it *is* possible that a *particular* thread is delayed by some time. * * the only way for this to happen is for other threads to continuously make * updates. an inactive / blocked / deadlocked other thread cannot cause such * delays, and to cause such delays a thread must be heavily hitting the list - * it's a rather theoretical concern. */ /* don't use these structs directly */ struct atomlist_item { _Atomic atomptr_t next; }; #define atomlist_itemp(val) ((struct atomlist_item *)atomptr_p(val)) struct atomlist_head { _Atomic atomptr_t first, last; _Atomic size_t count; }; /* use as: * * PREDECL_ATOMLIST(namelist) * struct name { * struct namelist_item nlitem; * } * DECLARE_ATOMLIST(namelist, struct name, nlitem) */ #define PREDECL_ATOMLIST(prefix) \ struct prefix ## _head { struct atomlist_head ah; }; \ struct prefix ## _item { struct atomlist_item ai; }; #define INIT_ATOMLIST(var) { } #define DECLARE_ATOMLIST(prefix, type, field) \ macro_inline void prefix ## _add_head(struct prefix##_head *h, type *item) \ { atomlist_add_head(&h->ah, &item->field.ai); } \ macro_inline void prefix ## _add_tail(struct prefix##_head *h, type *item) \ { atomlist_add_tail(&h->ah, &item->field.ai); } \ macro_inline void prefix ## _del_hint(struct prefix##_head *h, type *item, \ _Atomic atomptr_t *hint) \ { atomlist_del_hint(&h->ah, &item->field.ai, hint); } \ macro_inline type *prefix ## _del(struct prefix##_head *h, type *item) \ { atomlist_del_hint(&h->ah, &item->field.ai, NULL); \ /* TODO: Return NULL if not found */ \ return item; } \ macro_inline type *prefix ## _pop(struct prefix##_head *h) \ { char *p = (char *)atomlist_pop(&h->ah); \ return p ? (type *)(p - offsetof(type, field)) : NULL; } \ macro_inline type *prefix ## _first(struct prefix##_head *h) \ { char *p = atomptr_p(atomic_load_explicit(&h->ah.first, \ memory_order_acquire)); \ return p ? (type *)(p - offsetof(type, field)) : NULL; } \ macro_inline type *prefix ## _next(struct prefix##_head *h, type *item) \ { char *p = atomptr_p(atomic_load_explicit(&item->field.ai.next, \ memory_order_acquire)); \ return p ? (type *)(p - offsetof(type, field)) : NULL; } \ macro_inline type *prefix ## _next_safe(struct prefix##_head *h, type *item) \ { return item ? prefix##_next(h, item) : NULL; } \ macro_inline size_t prefix ## _count(struct prefix##_head *h) \ { return atomic_load_explicit(&h->ah.count, memory_order_relaxed); } \ macro_inline void prefix ## _init(struct prefix##_head *h) \ { \ memset(h, 0, sizeof(*h)); \ } \ macro_inline void prefix ## _fini(struct prefix##_head *h) \ { \ assert(prefix ## _count(h) == 0); \ memset(h, 0, sizeof(*h)); \ } \ /* ... */ /* add_head: * - contention on ->first pointer * - return implies completion */ void atomlist_add_head(struct atomlist_head *h, struct atomlist_item *item); /* add_tail: * - concurrent add_tail can cause wait but has progress guarantee * - return does NOT imply completion. completion is only guaranteed after * all other add_tail operations that started before this add_tail have * completed as well. */ void atomlist_add_tail(struct atomlist_head *h, struct atomlist_item *item); /* del/del_hint: * * OWNER MUST HOLD REFERENCE ON ITEM TO BE DELETED, ENSURING NO OTHER THREAD * WILL TRY TO DELETE THE SAME ITEM. DELETING INCLUDES pop(). * * as with all deletions, threads that started reading earlier may still hold * pointers to the deleted item. completion is however guaranteed for all * reads starting later. */ void atomlist_del_hint(struct atomlist_head *h, struct atomlist_item *item, _Atomic atomptr_t *hint); /* pop: * * as with all deletions, threads that started reading earlier may still hold * pointers to the deleted item. completion is however guaranteed for all * reads starting later. */ struct atomlist_item *atomlist_pop(struct atomlist_head *h); struct atomsort_item { _Atomic atomptr_t next; }; #define atomsort_itemp(val) ((struct atomsort_item *)atomptr_p(val)) struct atomsort_head { _Atomic atomptr_t first; _Atomic size_t count; }; #define _PREDECL_ATOMSORT(prefix) \ struct prefix ## _head { struct atomsort_head ah; }; \ struct prefix ## _item { struct atomsort_item ai; }; #define INIT_ATOMSORT_UNIQ(var) { } #define INIT_ATOMSORT_NONUNIQ(var) { } #define _DECLARE_ATOMSORT(prefix, type, field, cmpfn_nuq, cmpfn_uq) \ macro_inline void prefix ## _init(struct prefix##_head *h) \ { \ memset(h, 0, sizeof(*h)); \ } \ macro_inline void prefix ## _fini(struct prefix##_head *h) \ { \ assert(h->ah.count == 0); \ memset(h, 0, sizeof(*h)); \ } \ macro_inline type *prefix ## _add(struct prefix##_head *h, type *item) \ { \ struct atomsort_item *p; \ p = atomsort_add(&h->ah, &item->field.ai, cmpfn_uq); \ return container_of_null(p, type, field.ai); \ } \ macro_inline type *prefix ## _first(struct prefix##_head *h) \ { \ struct atomsort_item *p; \ p = atomptr_p(atomic_load_explicit(&h->ah.first, \ memory_order_acquire)); \ return container_of_null(p, type, field.ai); \ } \ macro_inline type *prefix ## _next(struct prefix##_head *h, type *item) \ { \ struct atomsort_item *p; \ p = atomptr_p(atomic_load_explicit(&item->field.ai.next, \ memory_order_acquire)); \ return container_of_null(p, type, field.ai); \ } \ macro_inline type *prefix ## _next_safe(struct prefix##_head *h, type *item) \ { \ return item ? prefix##_next(h, item) : NULL; \ } \ atomic_find_warn \ macro_inline type *prefix ## _find_gteq(struct prefix##_head *h, \ const type *item) \ { \ type *p = prefix ## _first(h); \ while (p && cmpfn_nuq(&p->field.ai, &item->field.ai) < 0) \ p = prefix ## _next(h, p); \ return p; \ } \ atomic_find_warn \ macro_inline type *prefix ## _find_lt(struct prefix##_head *h, \ const type *item) \ { \ type *p = prefix ## _first(h), *prev = NULL; \ while (p && cmpfn_nuq(&p->field.ai, &item->field.ai) < 0) \ p = prefix ## _next(h, (prev = p)); \ return prev; \ } \ macro_inline void prefix ## _del_hint(struct prefix##_head *h, type *item, \ _Atomic atomptr_t *hint) \ { \ atomsort_del_hint(&h->ah, &item->field.ai, hint); \ } \ macro_inline type *prefix ## _del(struct prefix##_head *h, type *item) \ { \ atomsort_del_hint(&h->ah, &item->field.ai, NULL); \ /* TODO: Return NULL if not found */ \ return item; \ } \ macro_inline size_t prefix ## _count(struct prefix##_head *h) \ { \ return atomic_load_explicit(&h->ah.count, memory_order_relaxed); \ } \ macro_inline type *prefix ## _pop(struct prefix##_head *h) \ { \ struct atomsort_item *p = atomsort_pop(&h->ah); \ return p ? container_of(p, type, field.ai) : NULL; \ } \ /* ... */ #define PREDECL_ATOMSORT_UNIQ(prefix) \ _PREDECL_ATOMSORT(prefix) #define DECLARE_ATOMSORT_UNIQ(prefix, type, field, cmpfn) \ \ macro_inline int prefix ## __cmp(const struct atomsort_item *a, \ const struct atomsort_item *b) \ { \ return cmpfn(container_of(a, type, field.ai), \ container_of(b, type, field.ai)); \ } \ \ _DECLARE_ATOMSORT(prefix, type, field, \ prefix ## __cmp, prefix ## __cmp) \ \ atomic_find_warn \ macro_inline type *prefix ## _find(struct prefix##_head *h, const type *item) \ { \ type *p = prefix ## _first(h); \ int cmpval = 0; \ while (p && (cmpval = cmpfn(p, item)) < 0) \ p = prefix ## _next(h, p); \ if (!p || cmpval > 0) \ return NULL; \ return p; \ } \ /* ... */ #define PREDECL_ATOMSORT_NONUNIQ(prefix) \ _PREDECL_ATOMSORT(prefix) #define DECLARE_ATOMSORT_NONUNIQ(prefix, type, field, cmpfn) \ \ macro_inline int prefix ## __cmp(const struct atomsort_item *a, \ const struct atomsort_item *b) \ { \ return cmpfn(container_of(a, type, field.ai), \ container_of(b, type, field.ai)); \ } \ macro_inline int prefix ## __cmp_uq(const struct atomsort_item *a, \ const struct atomsort_item *b) \ { \ int cmpval = cmpfn(container_of(a, type, field.ai), \ container_of(b, type, field.ai)); \ if (cmpval) \ return cmpval; \ if (a < b) \ return -1; \ if (a > b) \ return 1; \ return 0; \ } \ \ _DECLARE_ATOMSORT(prefix, type, field, \ prefix ## __cmp, prefix ## __cmp_uq) \ /* ... */ struct atomsort_item *atomsort_add(struct atomsort_head *h, struct atomsort_item *item, int (*cmpfn)( const struct atomsort_item *, const struct atomsort_item *)); void atomsort_del_hint(struct atomsort_head *h, struct atomsort_item *item, _Atomic atomptr_t *hint); struct atomsort_item *atomsort_pop(struct atomsort_head *h); #endif /* _FRR_ATOMLIST_H */ frr-7.2.1/lib/bfd.c0000644000000000000000000002573613610377563010676 00000000000000/** * bfd.c: BFD handling routines * * @copyright Copyright (C) 2015 Cumulus Networks, Inc. * * This file is part of GNU Zebra. * * GNU Zebra is free software; you can redistribute it and/or modify it * under the terms of the GNU General Public License as published by the * Free Software Foundation; either version 2, or (at your option) any * later version. * * GNU Zebra 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; see the file COPYING; if not, write to the Free Software * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA */ #include #include "command.h" #include "memory.h" #include "prefix.h" #include "thread.h" #include "stream.h" #include "zclient.h" #include "table.h" #include "vty.h" #include "bfd.h" DEFINE_MTYPE_STATIC(LIB, BFD_INFO, "BFD info") static int bfd_debug = 0; static struct bfd_gbl bfd_gbl; /* * bfd_gbl_init - Initialize the BFD global structure */ void bfd_gbl_init(void) { memset(&bfd_gbl, 0, sizeof(struct bfd_gbl)); } /* * bfd_gbl_exit - Called when daemon exits */ void bfd_gbl_exit(void) { SET_FLAG(bfd_gbl.flags, BFD_GBL_FLAG_IN_SHUTDOWN); } /* * bfd_info_create - Allocate the BFD information */ struct bfd_info *bfd_info_create(void) { struct bfd_info *bfd_info; bfd_info = XCALLOC(MTYPE_BFD_INFO, sizeof(struct bfd_info)); assert(bfd_info); bfd_info->status = BFD_STATUS_UNKNOWN; bfd_info->type = BFD_TYPE_NOT_CONFIGURED; bfd_info->last_update = 0; return bfd_info; } /* * bfd_info_free - Free the BFD information. */ void bfd_info_free(struct bfd_info **bfd_info) { if (*bfd_info) { XFREE(MTYPE_BFD_INFO, *bfd_info); *bfd_info = NULL; } } /* * bfd_validate_param - Validate the BFD paramter information. */ int bfd_validate_param(struct vty *vty, const char *dm_str, const char *rx_str, const char *tx_str, uint8_t *dm_val, uint32_t *rx_val, uint32_t *tx_val) { *dm_val = strtoul(dm_str, NULL, 10); *rx_val = strtoul(rx_str, NULL, 10); *tx_val = strtoul(tx_str, NULL, 10); return CMD_SUCCESS; } /* * bfd_set_param - Set the configured BFD paramter values */ void bfd_set_param(struct bfd_info **bfd_info, uint32_t min_rx, uint32_t min_tx, uint8_t detect_mult, int defaults, int *command) { if (!*bfd_info) { *bfd_info = bfd_info_create(); *command = ZEBRA_BFD_DEST_REGISTER; } else { if (((*bfd_info)->required_min_rx != min_rx) || ((*bfd_info)->desired_min_tx != min_tx) || ((*bfd_info)->detect_mult != detect_mult)) *command = ZEBRA_BFD_DEST_UPDATE; } if (*command) { (*bfd_info)->required_min_rx = min_rx; (*bfd_info)->desired_min_tx = min_tx; (*bfd_info)->detect_mult = detect_mult; } if (!defaults) SET_FLAG((*bfd_info)->flags, BFD_FLAG_PARAM_CFG); else UNSET_FLAG((*bfd_info)->flags, BFD_FLAG_PARAM_CFG); } /* * bfd_peer_sendmsg - Format and send a peer register/Unregister * command to Zebra to be forwarded to BFD */ void bfd_peer_sendmsg(struct zclient *zclient, struct bfd_info *bfd_info, int family, void *dst_ip, void *src_ip, char *if_name, int ttl, int multihop, int cbit, int command, int set_flag, vrf_id_t vrf_id) { struct stream *s; int ret; int len; /* Individual reg/dereg messages are suppressed during shutdown. */ if (CHECK_FLAG(bfd_gbl.flags, BFD_GBL_FLAG_IN_SHUTDOWN)) { if (bfd_debug) zlog_debug( "%s: Suppressing BFD peer reg/dereg messages", __FUNCTION__); return; } /* Check socket. */ if (!zclient || zclient->sock < 0) { if (bfd_debug) zlog_debug( "%s: Can't send BFD peer register, Zebra client not " "established", __FUNCTION__); return; } s = zclient->obuf; stream_reset(s); zclient_create_header(s, command, vrf_id); stream_putl(s, getpid()); stream_putw(s, family); switch (family) { case AF_INET: stream_put_in_addr(s, (struct in_addr *)dst_ip); break; case AF_INET6: stream_put(s, dst_ip, 16); break; default: break; } if (command != ZEBRA_BFD_DEST_DEREGISTER) { stream_putl(s, bfd_info->required_min_rx); stream_putl(s, bfd_info->desired_min_tx); stream_putc(s, bfd_info->detect_mult); } if (multihop) { stream_putc(s, 1); /* Multi-hop destination send the source IP address to BFD */ if (src_ip) { stream_putw(s, family); switch (family) { case AF_INET: stream_put_in_addr(s, (struct in_addr *)src_ip); break; case AF_INET6: stream_put(s, src_ip, 16); break; default: break; } } stream_putc(s, ttl); } else { stream_putc(s, 0); if ((family == AF_INET6) && (src_ip)) { stream_putw(s, family); stream_put(s, src_ip, 16); } if (if_name) { len = strlen(if_name); stream_putc(s, len); stream_put(s, if_name, len); } else { stream_putc(s, 0); } } /* cbit */ if (cbit) stream_putc(s, 1); else stream_putc(s, 0); stream_putw_at(s, 0, stream_get_endp(s)); ret = zclient_send_message(zclient); if (ret < 0) { if (bfd_debug) zlog_debug( "bfd_peer_sendmsg: zclient_send_message() failed"); return; } if (set_flag) { if (command == ZEBRA_BFD_DEST_REGISTER) SET_FLAG(bfd_info->flags, BFD_FLAG_BFD_REG); else if (command == ZEBRA_BFD_DEST_DEREGISTER) UNSET_FLAG(bfd_info->flags, BFD_FLAG_BFD_REG); } return; } /* * bfd_get_command_dbg_str - Convert command to a debug string. */ const char *bfd_get_command_dbg_str(int command) { switch (command) { case ZEBRA_BFD_DEST_REGISTER: return "Register"; case ZEBRA_BFD_DEST_DEREGISTER: return "Deregister"; case ZEBRA_BFD_DEST_UPDATE: return "Update"; default: return "Unknown"; } } /* * bfd_get_peer_info - Extract the Peer information for which the BFD session * went down from the message sent from Zebra to clients. */ struct interface *bfd_get_peer_info(struct stream *s, struct prefix *dp, struct prefix *sp, int *status, int *remote_cbit, vrf_id_t vrf_id) { unsigned int ifindex; struct interface *ifp = NULL; int plen; int local_remote_cbit; /* Get interface index. */ ifindex = stream_getl(s); /* Lookup index. */ if (ifindex != 0) { ifp = if_lookup_by_index(ifindex, vrf_id); if (ifp == NULL) { if (bfd_debug) zlog_debug( "zebra_interface_bfd_read: " "Can't find interface by ifindex: %d ", ifindex); return NULL; } } /* Fetch destination address. */ dp->family = stream_getc(s); plen = prefix_blen(dp); stream_get(&dp->u.prefix, s, plen); dp->prefixlen = stream_getc(s); /* Get BFD status. */ *status = stream_getl(s); if (sp) { sp->family = stream_getc(s); plen = prefix_blen(sp); stream_get(&sp->u.prefix, s, plen); sp->prefixlen = stream_getc(s); } local_remote_cbit = stream_getc(s); if (remote_cbit) *remote_cbit = local_remote_cbit; return ifp; } /* * bfd_get_status_str - Convert BFD status to a display string. */ const char *bfd_get_status_str(int status) { switch (status) { case BFD_STATUS_DOWN: return "Down"; case BFD_STATUS_UP: return "Up"; case BFD_STATUS_UNKNOWN: default: return "Unknown"; } } /* * bfd_last_update - Calculate the last BFD update time and convert it * into a dd:hh:mm:ss display format. */ static void bfd_last_update(time_t last_update, char *buf, size_t len) { time_t curr; time_t diff; struct tm *tm; struct timeval tv; /* If no BFD satatus update has ever been received, print `never'. */ if (last_update == 0) { snprintf(buf, len, "never"); return; } /* Get current time. */ monotime(&tv); curr = tv.tv_sec; diff = curr - last_update; tm = gmtime(&diff); snprintf(buf, len, "%d:%02d:%02d:%02d", tm->tm_yday, tm->tm_hour, tm->tm_min, tm->tm_sec); } /* * bfd_show_param - Show the BFD parameter information. */ void bfd_show_param(struct vty *vty, struct bfd_info *bfd_info, int bfd_tag, int extra_space, bool use_json, json_object *json_obj) { json_object *json_bfd = NULL; if (!bfd_info) return; if (use_json) { if (bfd_tag) json_bfd = json_object_new_object(); else json_bfd = json_obj; json_object_int_add(json_bfd, "detectMultiplier", bfd_info->detect_mult); json_object_int_add(json_bfd, "rxMinInterval", bfd_info->required_min_rx); json_object_int_add(json_bfd, "txMinInterval", bfd_info->desired_min_tx); if (bfd_tag) json_object_object_add(json_obj, "peerBfdInfo", json_bfd); } else { vty_out(vty, " %s%sDetect Multiplier: %d, Min Rx interval: %d," " Min Tx interval: %d\n", (extra_space) ? " " : "", (bfd_tag) ? "BFD: " : " ", bfd_info->detect_mult, bfd_info->required_min_rx, bfd_info->desired_min_tx); } } /* * bfd_show_status - Show the BFD status information. */ static void bfd_show_status(struct vty *vty, struct bfd_info *bfd_info, int bfd_tag, int extra_space, bool use_json, json_object *json_bfd) { char time_buf[32]; if (!bfd_info) return; bfd_last_update(bfd_info->last_update, time_buf, 32); if (use_json) { json_object_string_add(json_bfd, "status", bfd_get_status_str(bfd_info->status)); json_object_string_add(json_bfd, "lastUpdate", time_buf); } else { vty_out(vty, " %s%sStatus: %s, Last update: %s\n", (extra_space) ? " " : "", (bfd_tag) ? "BFD: " : " ", bfd_get_status_str(bfd_info->status), time_buf); } } /* * bfd_show_info - Show the BFD information. */ void bfd_show_info(struct vty *vty, struct bfd_info *bfd_info, int multihop, int extra_space, bool use_json, json_object *json_obj) { json_object *json_bfd = NULL; if (!bfd_info) return; if (use_json) { json_bfd = json_object_new_object(); if (multihop) json_object_string_add(json_bfd, "type", "multi hop"); else json_object_string_add(json_bfd, "type", "single hop"); } else { vty_out(vty, " %sBFD: Type: %s\n", (extra_space) ? " " : "", (multihop) ? "multi hop" : "single hop"); } bfd_show_param(vty, bfd_info, 0, extra_space, use_json, json_bfd); bfd_show_status(vty, bfd_info, 0, extra_space, use_json, json_bfd); if (use_json) json_object_object_add(json_obj, "peerBfdInfo", json_bfd); else vty_out(vty, "\n"); } /* * bfd_client_sendmsg - Format and send a client register * command to Zebra to be forwarded to BFD */ void bfd_client_sendmsg(struct zclient *zclient, int command, vrf_id_t vrf_id) { struct stream *s; int ret; /* Check socket. */ if (!zclient || zclient->sock < 0) { if (bfd_debug) zlog_debug( "%s: Can't send BFD client register, Zebra client not " "established", __FUNCTION__); return; } s = zclient->obuf; stream_reset(s); zclient_create_header(s, command, vrf_id); stream_putl(s, getpid()); stream_putw_at(s, 0, stream_get_endp(s)); ret = zclient_send_message(zclient); if (ret < 0) { if (bfd_debug) zlog_debug( "bfd_client_sendmsg %ld: zclient_send_message() failed", (long)getpid()); return; } return; } frr-7.2.1/lib/bfd.h0000644000000000000000000000721413610377563010672 00000000000000/** * bfd.h: BFD definitions and structures * * @copyright Copyright (C) 2015 Cumulus Networks, Inc. * * This file is part of GNU Zebra. * * GNU Zebra is free software; you can redistribute it and/or modify it * under the terms of the GNU General Public License as published by the * Free Software Foundation; either version 2, or (at your option) any * later version. * * GNU Zebra 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; see the file COPYING; if not, write to the Free Software * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA */ #ifndef _ZEBRA_BFD_H #define _ZEBRA_BFD_H #include "lib/json.h" #include "lib/zclient.h" #ifdef __cplusplus extern "C" { #endif #define BFD_DEF_MIN_RX 300 #define BFD_MIN_MIN_RX 50 #define BFD_MAX_MIN_RX 60000 #define BFD_DEF_MIN_TX 300 #define BFD_MIN_MIN_TX 50 #define BFD_MAX_MIN_TX 60000 #define BFD_DEF_DETECT_MULT 3 #define BFD_MIN_DETECT_MULT 2 #define BFD_MAX_DETECT_MULT 255 #define BFD_GBL_FLAG_IN_SHUTDOWN (1 << 0) /* The daemon in shutdown */ struct bfd_gbl { uint16_t flags; }; #define BFD_FLAG_PARAM_CFG (1 << 0) /* parameters have been configured */ #define BFD_FLAG_BFD_REG (1 << 1) /* Peer registered with BFD */ #define BFD_FLAG_BFD_TYPE_MULTIHOP (1 << 2) /* Peer registered with BFD as multihop */ #define BFD_FLAG_BFD_CBIT_ON (1 << 3) /* Peer registered with CBIT set to on */ #define BFD_FLAG_BFD_CHECK_CONTROLPLANE (1 << 4) /* BFD and controlplane daemon are linked */ #define BFD_STATUS_UNKNOWN (1 << 0) /* BFD session status never received */ #define BFD_STATUS_DOWN (1 << 1) /* BFD session status is down */ #define BFD_STATUS_UP (1 << 2) /* BFD session status is up */ enum bfd_sess_type { BFD_TYPE_NOT_CONFIGURED, BFD_TYPE_SINGLEHOP, BFD_TYPE_MULTIHOP }; struct bfd_info { uint16_t flags; uint8_t detect_mult; uint32_t desired_min_tx; uint32_t required_min_rx; time_t last_update; uint8_t status; enum bfd_sess_type type; }; extern struct bfd_info *bfd_info_create(void); extern void bfd_info_free(struct bfd_info **bfd_info); extern int bfd_validate_param(struct vty *vty, const char *dm_str, const char *rx_str, const char *tx_str, uint8_t *dm_val, uint32_t *rx_val, uint32_t *tx_val); extern void bfd_set_param(struct bfd_info **bfd_info, uint32_t min_rx, uint32_t min_tx, uint8_t detect_mult, int defaults, int *command); extern void bfd_peer_sendmsg(struct zclient *zclient, struct bfd_info *bfd_info, int family, void *dst_ip, void *src_ip, char *if_name, int ttl, int multihop, int cbit, int command, int set_flag, vrf_id_t vrf_id); extern const char *bfd_get_command_dbg_str(int command); extern struct interface *bfd_get_peer_info(struct stream *s, struct prefix *dp, struct prefix *sp, int *status, int *remote_cbit, vrf_id_t vrf_id); const char *bfd_get_status_str(int status); extern void bfd_show_param(struct vty *vty, struct bfd_info *bfd_info, int bfd_tag, int extra_space, bool use_json, json_object *json_obj); extern void bfd_show_info(struct vty *vty, struct bfd_info *bfd_info, int multihop, int extra_space, bool use_json, json_object *json_obj); extern void bfd_client_sendmsg(struct zclient *zclient, int command, vrf_id_t vrf_id); extern void bfd_gbl_init(void); extern void bfd_gbl_exit(void); #ifdef __cplusplus } #endif #endif /* _ZEBRA_BFD_H */ frr-7.2.1/lib/bitfield.h0000644000000000000000000001413213610377563011716 00000000000000/* Bitfields * Copyright (C) 2016 Cumulus Networks, Inc. * * This file is part of Quagga. * * Quagga is free software; you can redistribute it and/or modify it * under the terms of the GNU General Public License as published by the * Free Software Foundation; either version 2, or (at your option) any * later version. * * Quagga 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; see the file COPYING; if not, write to the Free Software * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA */ /** * A simple bit array implementation to allocate and free IDs. An example * of its usage is in allocating link state IDs for OSPFv3 as OSPFv3 has * removed all address semantics from LS ID. Another usage can be in * allocating IDs for BGP neighbors (and dynamic update groups) for * efficient storage of adj-rib-out. * * An example: * #include "bitfield.h" * * bitfield_t bitfield; * * bf_init(bitfield, 32); * ... * bf_assign_index(bitfield, id1); * bf_assign_index(bitfield, id2); * ... * bf_release_index(bitfield, id1); */ #ifndef _BITFIELD_H #define _BITFIELD_H #include #include #include #ifdef __cplusplus extern "C" { #endif typedef unsigned int word_t; #define WORD_MAX 0xFFFFFFFF #define WORD_SIZE (sizeof(word_t) * 8) /** * The bitfield structure. * @data: the bits to manage. * @n: The current word number that is being used. * @m: total number of words in 'data' */ #define bitfield_t struct { word_t *data; size_t n, m; } /** * Initialize the bits. * @v: an instance of bitfield_t struct. * @N: number of bits to start with, which equates to how many * IDs can be allocated. */ #define bf_init(v, N) \ do { \ (v).n = 0; \ (v).m = ((N) / WORD_SIZE + 1); \ (v).data = calloc(1, ((v).m * sizeof(word_t))); \ } while (0) /** * allocate and assign an id from bitfield v. */ #define bf_assign_index(v, id) \ do { \ bf_find_bit(v, id); \ bf_set_bit(v, id); \ } while (0) /* * allocate and assign 0th bit in the bitfiled. */ #define bf_assign_zero_index(v) \ do { \ int id = 0; \ bf_assign_index(v, id); \ } while (0) /* * return an id to bitfield v */ #define bf_release_index(v, id) \ (v).data[bf_index(id)] &= ~(1 << (bf_offset(id))) /* * return 0th index back to bitfield */ #define bf_release_zero_index(v) bf_release_index(v, 0) #define bf_index(b) ((b) / WORD_SIZE) #define bf_offset(b) ((b) % WORD_SIZE) /** * Set a bit in the array. If it fills up that word and we are * out of words, extend it by one more word. */ #define bf_set_bit(v, b) \ do { \ size_t w = bf_index(b); \ (v).data[w] |= 1 << (bf_offset(b)); \ (v).n += ((v).data[w] == WORD_MAX); \ if ((v).n == (v).m) { \ (v).m = (v).m + 1; \ (v).data = realloc((v).data, (v).m * sizeof(word_t)); \ } \ } while (0) /* Find a clear bit in v and assign it to b. */ #define bf_find_bit(v, b) \ do { \ word_t word = 0; \ unsigned int w, sh; \ for (w = 0; w <= (v).n; w++) { \ if ((word = (v).data[w]) != WORD_MAX) \ break; \ } \ (b) = ((word & 0xFFFF) == 0xFFFF) << 4; \ word >>= (b); \ sh = ((word & 0xFF) == 0xFF) << 3; \ word >>= sh; \ (b) |= sh; \ sh = ((word & 0xF) == 0xF) << 2; \ word >>= sh; \ (b) |= sh; \ sh = ((word & 0x3) == 0x3) << 1; \ word >>= sh; \ (b) |= sh; \ sh = ((word & 0x1) == 0x1) << 0; \ word >>= sh; \ (b) |= sh; \ (b) += (w * WORD_SIZE); \ } while (0) /* * Free the allocated memory for data * @v: an instance of bitfield_t struct. */ #define bf_free(v) \ do { \ if ((v).data) { \ free((v).data); \ } \ } while (0) #ifdef __cplusplus } #endif #endif frr-7.2.1/lib/buffer.c0000644000000000000000000003025713610377563011406 00000000000000/* * Buffering of output and input. * Copyright (C) 1998 Kunihiro Ishiguro * * This file is part of GNU Zebra. * * GNU Zebra is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License as published * by the Free Software Foundation; either version 2, or (at your * option) any later version. * * GNU Zebra 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; see the file COPYING; if not, write to the Free Software * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA */ #include #include "memory.h" #include "buffer.h" #include "log.h" #include "network.h" #include "lib_errors.h" #include DEFINE_MTYPE_STATIC(LIB, BUFFER, "Buffer") DEFINE_MTYPE_STATIC(LIB, BUFFER_DATA, "Buffer data") /* Buffer master. */ struct buffer { /* Data list. */ struct buffer_data *head; struct buffer_data *tail; /* Size of each buffer_data chunk. */ size_t size; }; /* Data container. */ struct buffer_data { struct buffer_data *next; /* Location to add new data. */ size_t cp; /* Pointer to data not yet flushed. */ size_t sp; /* Actual data stream (variable length). */ unsigned char data[]; /* real dimension is buffer->size */ }; /* It should always be true that: 0 <= sp <= cp <= size */ /* Default buffer size (used if none specified). It is rounded up to the next page boundery. */ #define BUFFER_SIZE_DEFAULT 4096 #define BUFFER_DATA_FREE(D) XFREE(MTYPE_BUFFER_DATA, (D)) /* Make new buffer. */ struct buffer *buffer_new(size_t size) { struct buffer *b; b = XCALLOC(MTYPE_BUFFER, sizeof(struct buffer)); if (size) b->size = size; else { static size_t default_size; if (!default_size) { long pgsz = sysconf(_SC_PAGESIZE); default_size = ((((BUFFER_SIZE_DEFAULT - 1) / pgsz) + 1) * pgsz); } b->size = default_size; } return b; } /* Free buffer. */ void buffer_free(struct buffer *b) { buffer_reset(b); XFREE(MTYPE_BUFFER, b); } /* Make string clone. */ char *buffer_getstr(struct buffer *b) { size_t totlen = 0; struct buffer_data *data; char *s; char *p; for (data = b->head; data; data = data->next) totlen += data->cp - data->sp; if (!(s = XMALLOC(MTYPE_TMP, totlen + 1))) return NULL; p = s; for (data = b->head; data; data = data->next) { memcpy(p, data->data + data->sp, data->cp - data->sp); p += data->cp - data->sp; } *p = '\0'; return s; } /* Return 1 if buffer is empty. */ int buffer_empty(struct buffer *b) { return (b->head == NULL); } /* Clear and free all allocated data. */ void buffer_reset(struct buffer *b) { struct buffer_data *data; struct buffer_data *next; for (data = b->head; data; data = next) { next = data->next; BUFFER_DATA_FREE(data); } b->head = b->tail = NULL; } /* Add buffer_data to the end of buffer. */ static struct buffer_data *buffer_add(struct buffer *b) { struct buffer_data *d; d = XMALLOC(MTYPE_BUFFER_DATA, offsetof(struct buffer_data, data) + b->size); d->cp = d->sp = 0; d->next = NULL; if (b->tail) b->tail->next = d; else b->head = d; b->tail = d; return d; } /* Write data to buffer. */ void buffer_put(struct buffer *b, const void *p, size_t size) { struct buffer_data *data = b->tail; const char *ptr = p; /* We use even last one byte of data buffer. */ while (size) { size_t chunk; /* If there is no data buffer add it. */ if (data == NULL || data->cp == b->size) data = buffer_add(b); chunk = ((size <= (b->size - data->cp)) ? size : (b->size - data->cp)); memcpy((data->data + data->cp), ptr, chunk); size -= chunk; ptr += chunk; data->cp += chunk; } } /* Insert character into the buffer. */ void buffer_putc(struct buffer *b, uint8_t c) { buffer_put(b, &c, 1); } /* Put string to the buffer. */ void buffer_putstr(struct buffer *b, const char *c) { buffer_put(b, c, strlen(c)); } /* Expand \n to \r\n */ void buffer_put_crlf(struct buffer *b, const void *origp, size_t origsize) { struct buffer_data *data = b->tail; const char *p = origp, *end = p + origsize, *lf; size_t size; lf = memchr(p, '\n', end - p); /* We use even last one byte of data buffer. */ while (p < end) { size_t avail, chunk; /* If there is no data buffer add it. */ if (data == NULL || data->cp == b->size) data = buffer_add(b); size = (lf ? lf : end) - p; avail = b->size - data->cp; chunk = (size <= avail) ? size : avail; memcpy(data->data + data->cp, p, chunk); p += chunk; data->cp += chunk; if (lf && size <= avail) { /* we just copied up to (including) a '\n' */ if (data->cp == b->size) data = buffer_add(b); data->data[data->cp++] = '\r'; if (data->cp == b->size) data = buffer_add(b); data->data[data->cp++] = '\n'; p++; lf = memchr(p, '\n', end - p); } } } /* Keep flushing data to the fd until the buffer is empty or an error is encountered or the operation would block. */ buffer_status_t buffer_flush_all(struct buffer *b, int fd) { buffer_status_t ret; struct buffer_data *head; size_t head_sp; if (!b->head) return BUFFER_EMPTY; head_sp = (head = b->head)->sp; /* Flush all data. */ while ((ret = buffer_flush_available(b, fd)) == BUFFER_PENDING) { if ((b->head == head) && (head_sp == head->sp) && (errno != EINTR)) /* No data was flushed, so kernel buffer must be full. */ return ret; head_sp = (head = b->head)->sp; } return ret; } /* Flush enough data to fill a terminal window of the given scene (used only by vty telnet interface). */ buffer_status_t buffer_flush_window(struct buffer *b, int fd, int width, int height, int erase_flag, int no_more_flag) { int nbytes; int iov_alloc; int iov_index; struct iovec *iov; struct iovec small_iov[3]; char more[] = " --More-- "; char erase[] = {0x08, 0x08, 0x08, 0x08, 0x08, 0x08, 0x08, 0x08, 0x08, 0x08, ' ', ' ', ' ', ' ', ' ', ' ', ' ', ' ', ' ', ' ', 0x08, 0x08, 0x08, 0x08, 0x08, 0x08, 0x08, 0x08, 0x08, 0x08}; struct buffer_data *data; int column; if (!b->head) return BUFFER_EMPTY; if (height < 1) height = 1; else if (height >= 2) height--; if (width < 1) width = 1; /* For erase and more data add two to b's buffer_data count.*/ if (b->head->next == NULL) { iov_alloc = array_size(small_iov); iov = small_iov; } else { iov_alloc = ((height * (width + 2)) / b->size) + 10; iov = XMALLOC(MTYPE_TMP, iov_alloc * sizeof(*iov)); } iov_index = 0; /* Previously print out is performed. */ if (erase_flag) { iov[iov_index].iov_base = erase; iov[iov_index].iov_len = sizeof erase; iov_index++; } /* Output data. */ column = 1; /* Column position of next character displayed. */ for (data = b->head; data && (height > 0); data = data->next) { size_t cp; cp = data->sp; while ((cp < data->cp) && (height > 0)) { /* Calculate lines remaining and column position after displaying this character. */ if (data->data[cp] == '\r') column = 1; else if ((data->data[cp] == '\n') || (column == width)) { column = 1; height--; } else column++; cp++; } iov[iov_index].iov_base = (char *)(data->data + data->sp); iov[iov_index++].iov_len = cp - data->sp; data->sp = cp; if (iov_index == iov_alloc) /* This should not ordinarily happen. */ { iov_alloc *= 2; if (iov != small_iov) { iov = XREALLOC(MTYPE_TMP, iov, iov_alloc * sizeof(*iov)); } else { /* This should absolutely never occur. */ flog_err_sys( EC_LIB_SYSTEM_CALL, "%s: corruption detected: iov_small overflowed; " "head %p, tail %p, head->next %p", __func__, (void *)b->head, (void *)b->tail, (void *)b->head->next); iov = XMALLOC(MTYPE_TMP, iov_alloc * sizeof(*iov)); memcpy(iov, small_iov, sizeof(small_iov)); } } } /* In case of `more' display need. */ if (b->tail && (b->tail->sp < b->tail->cp) && !no_more_flag) { iov[iov_index].iov_base = more; iov[iov_index].iov_len = sizeof more; iov_index++; } #ifdef IOV_MAX /* IOV_MAX are normally defined in , Posix.1g. example: Solaris2.6 are defined IOV_MAX size at 16. */ { struct iovec *c_iov = iov; nbytes = 0; /* Make sure it's initialized. */ while (iov_index > 0) { int iov_size; iov_size = ((iov_index > IOV_MAX) ? IOV_MAX : iov_index); if ((nbytes = writev(fd, c_iov, iov_size)) < 0) { flog_err(EC_LIB_SOCKET, "%s: writev to fd %d failed: %s", __func__, fd, safe_strerror(errno)); break; } /* move pointer io-vector */ c_iov += iov_size; iov_index -= iov_size; } } #else /* IOV_MAX */ if ((nbytes = writev(fd, iov, iov_index)) < 0) flog_err(EC_LIB_SOCKET, "%s: writev to fd %d failed: %s", __func__, fd, safe_strerror(errno)); #endif /* IOV_MAX */ /* Free printed buffer data. */ while (b->head && (b->head->sp == b->head->cp)) { struct buffer_data *del; if (!(b->head = (del = b->head)->next)) b->tail = NULL; BUFFER_DATA_FREE(del); } if (iov != small_iov) XFREE(MTYPE_TMP, iov); return (nbytes < 0) ? BUFFER_ERROR : (b->head ? BUFFER_PENDING : BUFFER_EMPTY); } /* This function (unlike other buffer_flush* functions above) is designed to work with non-blocking sockets. It does not attempt to write out all of the queued data, just a "big" chunk. It returns 0 if it was able to empty out the buffers completely, 1 if more flushing is required later, or -1 on a fatal write error. */ buffer_status_t buffer_flush_available(struct buffer *b, int fd) { /* These are just reasonable values to make sure a significant amount of data is written. There's no need to go crazy and try to write it all in one shot. */ #ifdef IOV_MAX #define MAX_CHUNKS ((IOV_MAX >= 16) ? 16 : IOV_MAX) #else #define MAX_CHUNKS 16 #endif #define MAX_FLUSH 131072 struct buffer_data *d; size_t written; struct iovec iov[MAX_CHUNKS]; size_t iovcnt = 0; size_t nbyte = 0; if (fd < 0) return BUFFER_ERROR; for (d = b->head; d && (iovcnt < MAX_CHUNKS) && (nbyte < MAX_FLUSH); d = d->next, iovcnt++) { iov[iovcnt].iov_base = d->data + d->sp; nbyte += (iov[iovcnt].iov_len = d->cp - d->sp); } if (!nbyte) /* No data to flush: should we issue a warning message? */ return BUFFER_EMPTY; /* only place where written should be sign compared */ if ((ssize_t)(written = writev(fd, iov, iovcnt)) < 0) { if (ERRNO_IO_RETRY(errno)) /* Calling code should try again later. */ return BUFFER_PENDING; flog_err(EC_LIB_SOCKET, "%s: write error on fd %d: %s", __func__, fd, safe_strerror(errno)); return BUFFER_ERROR; } /* Free printed buffer data. */ while (written > 0) { if (!(d = b->head)) { flog_err( EC_LIB_DEVELOPMENT, "%s: corruption detected: buffer queue empty, but written is %lu", __func__, (unsigned long)written); break; } if (written < d->cp - d->sp) { d->sp += written; return BUFFER_PENDING; } written -= (d->cp - d->sp); if (!(b->head = d->next)) b->tail = NULL; BUFFER_DATA_FREE(d); } return b->head ? BUFFER_PENDING : BUFFER_EMPTY; #undef MAX_CHUNKS #undef MAX_FLUSH } buffer_status_t buffer_write(struct buffer *b, int fd, const void *p, size_t size) { ssize_t nbytes; #if 0 /* * Should we attempt to drain any previously buffered data? * This could help reduce latency in pushing out the data if * we are stuck in a long-running thread that is preventing * the main select loop from calling the flush thread... */ if (b->head && (buffer_flush_available(b, fd) == BUFFER_ERROR)) return BUFFER_ERROR; #endif if (b->head) /* Buffer is not empty, so do not attempt to write the new data. */ nbytes = 0; else if ((nbytes = write(fd, p, size)) < 0) { if (ERRNO_IO_RETRY(errno)) nbytes = 0; else { flog_err(EC_LIB_SOCKET, "%s: write error on fd %d: %s", __func__, fd, safe_strerror(errno)); return BUFFER_ERROR; } } /* Add any remaining data to the buffer. */ { size_t written = nbytes; if (written < size) buffer_put(b, ((const char *)p) + written, size - written); } return b->head ? BUFFER_PENDING : BUFFER_EMPTY; } frr-7.2.1/lib/buffer.h0000644000000000000000000001024513610377563011406 00000000000000/* * Buffering to output and input. * Copyright (C) 1998 Kunihiro Ishiguro * * This file is part of GNU Zebra. * * GNU Zebra is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License as published * by the Free Software Foundation; either version 2, or (at your * option) any later version. * * GNU Zebra 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; see the file COPYING; if not, write to the Free Software * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA */ #ifndef _ZEBRA_BUFFER_H #define _ZEBRA_BUFFER_H #ifdef __cplusplus extern "C" { #endif /* Create a new buffer. Memory will be allocated in chunks of the given size. If the argument is 0, the library will supply a reasonable default size suitable for buffering socket I/O. */ extern struct buffer *buffer_new(size_t); /* Free all data in the buffer. */ extern void buffer_reset(struct buffer *); /* This function first calls buffer_reset to release all buffered data. Then it frees the struct buffer itself. */ extern void buffer_free(struct buffer *); /* Add the given data to the end of the buffer. */ extern void buffer_put(struct buffer *, const void *, size_t); /* Add a single character to the end of the buffer. */ extern void buffer_putc(struct buffer *, uint8_t); /* Add a NUL-terminated string to the end of the buffer. */ extern void buffer_putstr(struct buffer *, const char *); /* Add given data, inline-expanding \n to \r\n */ extern void buffer_put_crlf(struct buffer *b, const void *p, size_t size); /* Combine all accumulated (and unflushed) data inside the buffer into a single NUL-terminated string allocated using XMALLOC(MTYPE_TMP). Note that this function does not alter the state of the buffer, so the data is still inside waiting to be flushed. */ char *buffer_getstr(struct buffer *); /* Returns 1 if there is no pending data in the buffer. Otherwise returns 0. */ int buffer_empty(struct buffer *); typedef enum { /* An I/O error occurred. The buffer should be destroyed and the file descriptor should be closed. */ BUFFER_ERROR = -1, /* The data was written successfully, and the buffer is now empty (there is no pending data waiting to be flushed). */ BUFFER_EMPTY = 0, /* There is pending data in the buffer waiting to be flushed. Please try flushing the buffer when select indicates that the file descriptor is writeable. */ BUFFER_PENDING = 1 } buffer_status_t; /* Try to write this data to the file descriptor. Any data that cannot be written immediately is added to the buffer queue. */ extern buffer_status_t buffer_write(struct buffer *, int fd, const void *, size_t); /* This function attempts to flush some (but perhaps not all) of the queued data to the given file descriptor. */ extern buffer_status_t buffer_flush_available(struct buffer *, int fd); /* The following 2 functions (buffer_flush_all and buffer_flush_window) are for use in lib/vty.c only. They should not be used elsewhere. */ /* Call buffer_flush_available repeatedly until either all data has been flushed, or an I/O error has been encountered, or the operation would block. */ extern buffer_status_t buffer_flush_all(struct buffer *, int fd); /* Attempt to write enough data to the given fd to fill a window of the given width and height (and remove the data written from the buffer). If !no_more, then a message saying " --More-- " is appended. If erase is true, then first overwrite the previous " --More-- " message with spaces. Any write error (including EAGAIN or EINTR) will cause this function to return -1 (because the logic for handling the erase and more features is too complicated to retry the write later). */ extern buffer_status_t buffer_flush_window(struct buffer *, int fd, int width, int height, int erase, int no_more); #ifdef __cplusplus } #endif #endif /* _ZEBRA_BUFFER_H */ frr-7.2.1/lib/checksum.c0000644000000000000000000000662313610377563011737 00000000000000/* * Checksum routine for Internet Protocol family headers (C Version). * * Refer to "Computing the Internet Checksum" by R. Braden, D. Borman and * C. Partridge, Computer Communication Review, Vol. 19, No. 2, April 1989, * pp. 86-101, for additional details on computing this checksum. */ #include #include "checksum.h" int /* return checksum in low-order 16 bits */ in_cksum(void *parg, int nbytes) { unsigned short *ptr = parg; register long sum; /* assumes long == 32 bits */ unsigned short oddbyte; register unsigned short answer; /* assumes unsigned short == 16 bits */ /* * Our algorithm is simple, using a 32-bit accumulator (sum), * we add sequential 16-bit words to it, and at the end, fold back * all the carry bits from the top 16 bits into the lower 16 bits. */ sum = 0; while (nbytes > 1) { sum += *ptr++; nbytes -= 2; } /* mop up an odd byte, if necessary */ if (nbytes == 1) { oddbyte = 0; /* make sure top half is zero */ *((uint8_t *)&oddbyte) = *(uint8_t *)ptr; /* one byte only */ sum += oddbyte; } /* * Add back carry outs from top 16 bits to low 16 bits. */ sum = (sum >> 16) + (sum & 0xffff); /* add high-16 to low-16 */ sum += (sum >> 16); /* add carry */ answer = ~sum; /* ones-complement, then truncate to 16 bits */ return (answer); } int in_cksum_with_ph4(struct ipv4_ph *ph, void *data, int nbytes) { uint8_t dat[sizeof(struct ipv4_ph) + nbytes]; memcpy(dat, ph, sizeof(struct ipv4_ph)); memcpy(dat + sizeof(struct ipv4_ph), data, nbytes); return in_cksum(dat, sizeof(dat)); } int in_cksum_with_ph6(struct ipv6_ph *ph, void *data, int nbytes) { uint8_t dat[sizeof(struct ipv6_ph) + nbytes]; memcpy(dat, ph, sizeof(struct ipv6_ph)); memcpy(dat + sizeof(struct ipv6_ph), data, nbytes); return in_cksum(dat, sizeof(dat)); } /* Fletcher Checksum -- Refer to RFC1008. */ #define MODX 4102U /* 5802 should be fine */ /* To be consistent, offset is 0-based index, rather than the 1-based index required in the specification ISO 8473, Annex C.1 */ /* calling with offset == FLETCHER_CHECKSUM_VALIDATE will validate the checksum without modifying the buffer; a valid checksum returns 0 */ uint16_t fletcher_checksum(uint8_t *buffer, const size_t len, const uint16_t offset) { uint8_t *p; int x, y, c0, c1; uint16_t checksum = 0; uint16_t *csum; size_t partial_len, i, left = len; if (offset != FLETCHER_CHECKSUM_VALIDATE) /* Zero the csum in the packet. */ { assert(offset < (len - 1)); /* account for two bytes of checksum */ csum = (uint16_t *)(buffer + offset); *(csum) = 0; } p = buffer; c0 = 0; c1 = 0; while (left != 0) { partial_len = MIN(left, MODX); for (i = 0; i < partial_len; i++) { c0 = c0 + *(p++); c1 += c0; } c0 = c0 % 255; c1 = c1 % 255; left -= partial_len; } /* The cast is important, to ensure the mod is taken as a signed value. */ x = (int)((len - offset - 1) * c0 - c1) % 255; if (x <= 0) x += 255; y = 510 - c0 - x; if (y > 255) y -= 255; if (offset == FLETCHER_CHECKSUM_VALIDATE) { checksum = (c1 << 8) + c0; } else { /* * Now we write this to the packet. * We could skip this step too, since the checksum returned * would * be stored into the checksum field by the caller. */ buffer[offset] = x; buffer[offset + 1] = y; /* Take care of the endian issue */ checksum = htons((x << 8) | (y & 0xFF)); } return checksum; } frr-7.2.1/lib/checksum.h0000644000000000000000000000137713610377563011745 00000000000000#include #include #ifdef __cplusplus extern "C" { #endif /* IPv4 pseudoheader */ struct ipv4_ph { struct in_addr src; struct in_addr dst; uint8_t rsvd; uint8_t proto; uint16_t len; } __attribute__((packed)); /* IPv6 pseudoheader */ struct ipv6_ph { struct in6_addr src; struct in6_addr dst; uint32_t ulpl; uint8_t zero[3]; uint8_t next_hdr; } __attribute__((packed)); extern int in_cksum(void *data, int nbytes); extern int in_cksum_with_ph4(struct ipv4_ph *ph, void *data, int nbytes); extern int in_cksum_with_ph6(struct ipv6_ph *ph, void *data, int nbytes); #define FLETCHER_CHECKSUM_VALIDATE 0xffff extern uint16_t fletcher_checksum(uint8_t *, const size_t len, const uint16_t offset); #ifdef __cplusplus } #endif frr-7.2.1/lib/clippy.c0000644000000000000000000000717513610377563011440 00000000000000/* * clippy (CLI preparator in python) main executable * Copyright (C) 2016-2017 David Lamparter for NetDEF, Inc. * * This program is free software; you can redistribute it and/or modify it * under the terms of the 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; see the file COPYING; if not, write to the Free Software * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA */ #include "config.h" #include #include #include #include #include "getopt.h" #include "command_graph.h" #include "clippy.h" #if PY_MAJOR_VERSION >= 3 #define pychar wchar_t static wchar_t *wconv(const char *s) { size_t outlen = s ? mbstowcs(NULL, s, 0) : 0; wchar_t *out = malloc((outlen + 1) * sizeof(wchar_t)); if (outlen > 0) mbstowcs(out, s, outlen); out[outlen] = 0; return out; } #else #define pychar char #define wconv(x) x #endif int main(int argc, char **argv) { pychar **wargv; #if PY_VERSION_HEX >= 0x03040000 /* 3.4 */ Py_SetStandardStreamEncoding("UTF-8", NULL); #endif Py_SetProgramName(wconv(argv[0])); PyImport_AppendInittab("_clippy", command_py_init); Py_Initialize(); wargv = malloc(argc * sizeof(pychar *)); for (int i = 1; i < argc; i++) wargv[i - 1] = wconv(argv[i]); PySys_SetArgv(argc - 1, wargv); const char *pyfile = argc > 1 ? argv[1] : NULL; FILE *fp; if (pyfile) { fp = fopen(pyfile, "r"); if (!fp) { fprintf(stderr, "%s: %s\n", pyfile, strerror(errno)); return 1; } } else { fp = stdin; char *ver = strdup(Py_GetVersion()); char *cr = strchr(ver, '\n'); if (cr) *cr = ' '; fprintf(stderr, "clippy interactive shell\n(Python %s)\n", ver); free(ver); PyRun_SimpleString( "import rlcompleter, readline\n" "readline.parse_and_bind('tab: complete')"); } if (PyRun_AnyFile(fp, pyfile)) { if (PyErr_Occurred()) PyErr_Print(); return 1; } Py_Finalize(); #if PY_MAJOR_VERSION >= 3 for (int i = 1; i < argc; i++) free(wargv[i - 1]); #endif free(wargv); return 0; } /* and now for the ugly part... provide simplified logging functions so we * don't need to link libzebra (which would be a circular build dep) */ #ifdef __ASSERT_FUNCTION #undef __ASSERT_FUNCTION #endif #include "log.h" #include "zassert.h" #define ZLOG_FUNC(FUNCNAME) \ void FUNCNAME(const char *format, ...) \ { \ va_list args; \ va_start(args, format); \ vfprintf(stderr, format, args); \ fputs("\n", stderr); \ va_end(args); \ } ZLOG_FUNC(zlog_err) ZLOG_FUNC(zlog_warn) ZLOG_FUNC(zlog_info) ZLOG_FUNC(zlog_notice) ZLOG_FUNC(zlog_debug) void _zlog_assert_failed(const char *assertion, const char *file, unsigned int line, const char *function) { fprintf(stderr, "Assertion `%s' failed in file %s, line %u, function %s", assertion, file, line, (function ? function : "?")); abort(); } void memory_oom(size_t size, const char *name) { abort(); } frr-7.2.1/lib/clippy.h0000644000000000000000000000213213610377563011431 00000000000000/* * clippy (CLI preparator in python) * Copyright (C) 2016-2017 David Lamparter for NetDEF, Inc. * * This program is free software; you can redistribute it and/or modify it * under the terms of the 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; see the file COPYING; if not, write to the Free Software * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA */ #ifndef _FRR_CLIPPY_H #define _FRR_CLIPPY_H #include #ifdef __cplusplus extern "C" { #endif extern PyObject *clippy_parse(PyObject *self, PyObject *args); extern PyMODINIT_FUNC command_py_init(void); #ifdef __cplusplus } #endif #endif /* _FRR_CLIPPY_H */ frr-7.2.1/lib/command.c0000644000000000000000000022663413610377563011561 00000000000000/* * CLI backend interface. * * -- * Copyright (C) 2016 Cumulus Networks, Inc. * Copyright (C) 1997, 98, 99 Kunihiro Ishiguro * Copyright (C) 2013 by Open Source Routing. * Copyright (C) 2013 by Internet Systems Consortium, Inc. ("ISC") * * This file is part of GNU Zebra. * * GNU Zebra is free software; you can redistribute it and/or modify it * under the terms of the GNU General Public License as published by the * Free Software Foundation; either version 2, or (at your option) any * later version. * * GNU Zebra 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; see the file COPYING; if not, write to the Free Software * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA */ #include #include #include "command.h" #include "frrstr.h" #include "memory.h" #include "log.h" #include "log_int.h" #include "thread.h" #include "vector.h" #include "linklist.h" #include "vty.h" #include "workqueue.h" #include "vrf.h" #include "command_match.h" #include "command_graph.h" #include "qobj.h" #include "defaults.h" #include "libfrr.h" #include "jhash.h" #include "hook.h" #include "lib_errors.h" #include "northbound_cli.h" DEFINE_MTYPE_STATIC(LIB, HOST, "Host config") DEFINE_MTYPE(LIB, COMPLETION, "Completion item") #define item(x) \ { \ x, #x \ } /* clang-format off */ const struct message tokennames[] = { item(WORD_TKN), item(VARIABLE_TKN), item(RANGE_TKN), item(IPV4_TKN), item(IPV4_PREFIX_TKN), item(IPV6_TKN), item(IPV6_PREFIX_TKN), item(MAC_TKN), item(MAC_PREFIX_TKN), item(FORK_TKN), item(JOIN_TKN), item(START_TKN), item(END_TKN), {0}, }; const char *node_names[] = { "auth", // AUTH_NODE, "view", // VIEW_NODE, "auth enable", // AUTH_ENABLE_NODE, "enable", // ENABLE_NODE, "config", // CONFIG_NODE, "debug", // DEBUG_NODE, "vrf debug", // VRF_DEBUG_NODE, "northbound debug", // NORTHBOUND_DEBUG_NODE, "vnc debug", // DEBUG_VNC_NODE, "route-map debug", /* RMAP_DEBUG_NODE */ "resolver debug", /* RESOLVER_DEBUG_NODE */ "aaa", // AAA_NODE, "keychain", // KEYCHAIN_NODE, "keychain key", // KEYCHAIN_KEY_NODE, "static ip", // IP_NODE, "vrf", // VRF_NODE, "interface", // INTERFACE_NODE, "nexthop-group", // NH_GROUP_NODE, "zebra", // ZEBRA_NODE, "table", // TABLE_NODE, "rip", // RIP_NODE, "ripng", // RIPNG_NODE, "babel", // BABEL_NODE, "eigrp", // EIGRP_NODE, "bgp", // BGP_NODE, "bgp vpnv4", // BGP_VPNV4_NODE, "bgp vpnv6", // BGP_VPNV6_NODE, "bgp ipv4 unicast", // BGP_IPV4_NODE, "bgp ipv4 multicast", // BGP_IPV4M_NODE, "bgp ipv4 labeled unicast", // BGP_IPV4L_NODE, "bgp ipv6", // BGP_IPV6_NODE, "bgp ipv6 multicast", // BGP_IPV6M_NODE, "bgp ipv6 labeled unicast", // BGP_IPV6L_NODE, "bgp vrf policy", // BGP_VRF_POLICY_NODE, "bgp vnc defaults", // BGP_VNC_DEFAULTS_NODE, "bgp vnc nve", // BGP_VNC_NVE_GROUP_NODE, "bgp vnc l2", // BGP_VNC_L2_GROUP_NODE, "rfp defaults", // RFP_DEFAULTS_NODE, "bgp evpn", // BGP_EVPN_NODE, "ospf", // OSPF_NODE, "ospf6", // OSPF6_NODE, "ldp", // LDP_NODE, "ldp ipv4", // LDP_IPV4_NODE, "ldp ipv6", // LDP_IPV6_NODE, "ldp ipv4 interface", // LDP_IPV4_IFACE_NODE, "ldp ipv6 interface", // LDP_IPV6_IFACE_NODE, "ldp l2vpn", // LDP_L2VPN_NODE, "ldp", // LDP_PSEUDOWIRE_NODE, "isis", // ISIS_NODE, "ipv4 access list", // ACCESS_NODE, "ipv4 prefix list", // PREFIX_NODE, "ipv6 access list", // ACCESS_IPV6_NODE, "MAC access list", // ACCESS_MAC_NODE, "ipv6 prefix list", // PREFIX_IPV6_NODE, "as list", // AS_LIST_NODE, "community list", // COMMUNITY_LIST_NODE, "routemap", // RMAP_NODE, "pbr-map", // PBRMAP_NODE, "smux", // SMUX_NODE, "dump", // DUMP_NODE, "forwarding", // FORWARDING_NODE, "protocol", // PROTOCOL_NODE, "mpls", // MPLS_NODE, "pw", // PW_NODE, "vty", // VTY_NODE, "link-params", // LINK_PARAMS_NODE, "bgp evpn vni", // BGP_EVPN_VNI_NODE, "rpki", // RPKI_NODE "bgp ipv4 flowspec", /* BGP_FLOWSPECV4_NODE */ "bgp ipv6 flowspec", /* BGP_FLOWSPECV6_NODE */ "bfd", /* BFD_NODE */ "bfd peer", /* BFD_PEER_NODE */ "openfabric", // OPENFABRIC_NODE "vrrp", /* VRRP_NODE */ "bmp", /* BMP_NODE */ }; /* clang-format on */ /* Command vector which includes some level of command lists. Normally each daemon maintains each own cmdvec. */ vector cmdvec = NULL; /* Host information structure. */ struct host host; /* * Returns host.name if any, otherwise * it returns the system hostname. */ const char *cmd_hostname_get(void) { return host.name; } /* * Returns unix domainname */ const char *cmd_domainname_get(void) { return host.domainname; } /* Standard command node structures. */ static struct cmd_node auth_node = { AUTH_NODE, "Password: ", }; static struct cmd_node view_node = { VIEW_NODE, "%s> ", }; static struct cmd_node auth_enable_node = { AUTH_ENABLE_NODE, "Password: ", }; static struct cmd_node enable_node = { ENABLE_NODE, "%s# ", }; static struct cmd_node config_node = {CONFIG_NODE, "%s(config)# ", 1}; /* Default motd string. */ static const char *default_motd = FRR_DEFAULT_MOTD; static const struct facility_map { int facility; const char *name; size_t match; } syslog_facilities[] = { {LOG_KERN, "kern", 1}, {LOG_USER, "user", 2}, {LOG_MAIL, "mail", 1}, {LOG_DAEMON, "daemon", 1}, {LOG_AUTH, "auth", 1}, {LOG_SYSLOG, "syslog", 1}, {LOG_LPR, "lpr", 2}, {LOG_NEWS, "news", 1}, {LOG_UUCP, "uucp", 2}, {LOG_CRON, "cron", 1}, #ifdef LOG_FTP {LOG_FTP, "ftp", 1}, #endif {LOG_LOCAL0, "local0", 6}, {LOG_LOCAL1, "local1", 6}, {LOG_LOCAL2, "local2", 6}, {LOG_LOCAL3, "local3", 6}, {LOG_LOCAL4, "local4", 6}, {LOG_LOCAL5, "local5", 6}, {LOG_LOCAL6, "local6", 6}, {LOG_LOCAL7, "local7", 6}, {0, NULL, 0}, }; static const char *facility_name(int facility) { const struct facility_map *fm; for (fm = syslog_facilities; fm->name; fm++) if (fm->facility == facility) return fm->name; return ""; } static int facility_match(const char *str) { const struct facility_map *fm; for (fm = syslog_facilities; fm->name; fm++) if (!strncmp(str, fm->name, fm->match)) return fm->facility; return -1; } static int level_match(const char *s) { int level; for (level = 0; zlog_priority[level] != NULL; level++) if (!strncmp(s, zlog_priority[level], 2)) return level; return ZLOG_DISABLED; } /* This is called from main when a daemon is invoked with -v or --version. */ void print_version(const char *progname) { printf("%s version %s\n", progname, FRR_VERSION); printf("%s\n", FRR_COPYRIGHT); printf("configured with:\n\t%s\n", FRR_CONFIG_ARGS); } char *argv_concat(struct cmd_token **argv, int argc, int shift) { int cnt = MAX(argc - shift, 0); const char *argstr[cnt + 1]; if (!cnt) return NULL; for (int i = 0; i < cnt; i++) argstr[i] = argv[i + shift]->arg; return frrstr_join(argstr, cnt, " "); } vector cmd_make_strvec(const char *string) { if (!string) return NULL; const char *copy = string; /* skip leading whitespace */ while (isspace((unsigned char)*copy) && *copy != '\0') copy++; /* if the entire string was whitespace or a comment, return */ if (*copy == '\0' || *copy == '!' || *copy == '#') return NULL; vector result = frrstr_split_vec(copy, "\n\r\t "); for (unsigned int i = 0; i < vector_active(result); i++) { if (strlen(vector_slot(result, i)) == 0) { XFREE(MTYPE_TMP, vector_slot(result, i)); vector_unset(result, i); } } vector_compact(result); return result; } void cmd_free_strvec(vector v) { frrstr_strvec_free(v); } /** * Convenience function for accessing argv data. * * @param argc * @param argv * @param text definition snippet of the desired token * @param index the starting index, and where to store the * index of the found token if it exists * @return 1 if found, 0 otherwise */ int argv_find(struct cmd_token **argv, int argc, const char *text, int *index) { int found = 0; for (int i = *index; i < argc && found == 0; i++) if ((found = strmatch(text, argv[i]->text))) *index = i; return found; } static unsigned int cmd_hash_key(const void *p) { int size = sizeof(p); return jhash(p, size, 0); } static bool cmd_hash_cmp(const void *a, const void *b) { return a == b; } /* Install top node of command vector. */ void install_node(struct cmd_node *node, int (*func)(struct vty *)) { vector_set_index(cmdvec, node->node, node); node->func = func; node->cmdgraph = graph_new(); node->cmd_vector = vector_init(VECTOR_MIN_SIZE); // add start node struct cmd_token *token = cmd_token_new(START_TKN, CMD_ATTR_NORMAL, NULL, NULL); graph_new_node(node->cmdgraph, token, (void (*)(void *)) & cmd_token_del); node->cmd_hash = hash_create_size(16, cmd_hash_key, cmd_hash_cmp, "Command Hash"); } /* Return prompt character of specified node. */ const char *cmd_prompt(enum node_type node) { struct cmd_node *cnode; cnode = vector_slot(cmdvec, node); return cnode->prompt; } /* Install a command into a node. */ void install_element(enum node_type ntype, struct cmd_element *cmd) { struct cmd_node *cnode; /* cmd_init hasn't been called */ if (!cmdvec) { fprintf(stderr, "%s called before cmd_init, breakage likely\n", __func__); return; } cnode = vector_lookup(cmdvec, ntype); if (cnode == NULL) { fprintf(stderr, "%s[%s]:\n" "\tnode %d (%s) does not exist.\n" "\tplease call install_node() before install_element()\n", cmd->name, cmd->string, ntype, node_names[ntype]); exit(EXIT_FAILURE); } if (hash_lookup(cnode->cmd_hash, cmd) != NULL) { fprintf(stderr, "%s[%s]:\n" "\tnode %d (%s) already has this command installed.\n" "\tduplicate install_element call?\n", cmd->name, cmd->string, ntype, node_names[ntype]); return; } assert(hash_get(cnode->cmd_hash, cmd, hash_alloc_intern)); struct graph *graph = graph_new(); struct cmd_token *token = cmd_token_new(START_TKN, CMD_ATTR_NORMAL, NULL, NULL); graph_new_node(graph, token, (void (*)(void *)) & cmd_token_del); cmd_graph_parse(graph, cmd); cmd_graph_names(graph); cmd_graph_merge(cnode->cmdgraph, graph, +1); graph_delete_graph(graph); vector_set(cnode->cmd_vector, cmd); if (ntype == VIEW_NODE) install_element(ENABLE_NODE, cmd); } void uninstall_element(enum node_type ntype, struct cmd_element *cmd) { struct cmd_node *cnode; /* cmd_init hasn't been called */ if (!cmdvec) { fprintf(stderr, "%s called before cmd_init, breakage likely\n", __func__); return; } cnode = vector_lookup(cmdvec, ntype); if (cnode == NULL) { fprintf(stderr, "%s[%s]:\n" "\tnode %d (%s) does not exist.\n" "\tplease call install_node() before uninstall_element()\n", cmd->name, cmd->string, ntype, node_names[ntype]); exit(EXIT_FAILURE); } if (hash_release(cnode->cmd_hash, cmd) == NULL) { fprintf(stderr, "%s[%s]:\n" "\tnode %d (%s) does not have this command installed.\n" "\tduplicate uninstall_element call?\n", cmd->name, cmd->string, ntype, node_names[ntype]); return; } vector_unset_value(cnode->cmd_vector, cmd); struct graph *graph = graph_new(); struct cmd_token *token = cmd_token_new(START_TKN, CMD_ATTR_NORMAL, NULL, NULL); graph_new_node(graph, token, (void (*)(void *)) & cmd_token_del); cmd_graph_parse(graph, cmd); cmd_graph_names(graph); cmd_graph_merge(cnode->cmdgraph, graph, -1); graph_delete_graph(graph); if (ntype == VIEW_NODE) uninstall_element(ENABLE_NODE, cmd); } static const unsigned char itoa64[] = "./0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz"; static void to64(char *s, long v, int n) { while (--n >= 0) { *s++ = itoa64[v & 0x3f]; v >>= 6; } } static char *zencrypt(const char *passwd) { char salt[6]; struct timeval tv; char *crypt(const char *, const char *); gettimeofday(&tv, 0); to64(&salt[0], random(), 3); to64(&salt[3], tv.tv_usec, 3); salt[5] = '\0'; return crypt(passwd, salt); } /* This function write configuration of this host. */ static int config_write_host(struct vty *vty) { if (cmd_hostname_get()) vty_out(vty, "hostname %s\n", cmd_hostname_get()); if (cmd_domainname_get()) vty_out(vty, "domainname %s\n", cmd_domainname_get()); /* The following are all configuration commands that are not sent to * watchfrr. For instance watchfrr is hardcoded to log to syslog so * we would always display 'log syslog informational' in the config * which would cause other daemons to then switch to syslog when they * parse frr.conf. */ if (strcmp(zlog_default->protoname, "WATCHFRR")) { if (host.encrypt) { if (host.password_encrypt) vty_out(vty, "password 8 %s\n", host.password_encrypt); if (host.enable_encrypt) vty_out(vty, "enable password 8 %s\n", host.enable_encrypt); } else { if (host.password) vty_out(vty, "password %s\n", host.password); if (host.enable) vty_out(vty, "enable password %s\n", host.enable); } if (host.logfile && (zlog_default->maxlvl[ZLOG_DEST_FILE] != ZLOG_DISABLED)) { vty_out(vty, "log file %s", host.logfile); if (zlog_default->maxlvl[ZLOG_DEST_FILE] != zlog_default->default_lvl) vty_out(vty, " %s", zlog_priority [zlog_default->maxlvl [ZLOG_DEST_FILE]]); vty_out(vty, "\n"); } if (zlog_default->maxlvl[ZLOG_DEST_STDOUT] != ZLOG_DISABLED) { vty_out(vty, "log stdout"); if (zlog_default->maxlvl[ZLOG_DEST_STDOUT] != zlog_default->default_lvl) vty_out(vty, " %s", zlog_priority [zlog_default->maxlvl [ZLOG_DEST_STDOUT]]); vty_out(vty, "\n"); } if (zlog_default->maxlvl[ZLOG_DEST_MONITOR] == ZLOG_DISABLED) vty_out(vty, "no log monitor\n"); else if (zlog_default->maxlvl[ZLOG_DEST_MONITOR] != zlog_default->default_lvl) vty_out(vty, "log monitor %s\n", zlog_priority[zlog_default->maxlvl [ZLOG_DEST_MONITOR]]); if (zlog_default->maxlvl[ZLOG_DEST_SYSLOG] != ZLOG_DISABLED) { vty_out(vty, "log syslog"); if (zlog_default->maxlvl[ZLOG_DEST_SYSLOG] != zlog_default->default_lvl) vty_out(vty, " %s", zlog_priority[zlog_default->maxlvl [ZLOG_DEST_SYSLOG]]); vty_out(vty, "\n"); } if (zlog_default->facility != LOG_DAEMON) vty_out(vty, "log facility %s\n", facility_name(zlog_default->facility)); if (zlog_default->record_priority == 1) vty_out(vty, "log record-priority\n"); if (zlog_default->timestamp_precision > 0) vty_out(vty, "log timestamp precision %d\n", zlog_default->timestamp_precision); if (host.advanced) vty_out(vty, "service advanced-vty\n"); if (host.encrypt) vty_out(vty, "service password-encryption\n"); if (host.lines >= 0) vty_out(vty, "service terminal-length %d\n", host.lines); if (host.motdfile) vty_out(vty, "banner motd file %s\n", host.motdfile); else if (!host.motd) vty_out(vty, "no banner motd\n"); } if (debug_memstats_at_exit) vty_out(vty, "!\ndebug memstats-at-exit\n"); return 1; } /* Utility function for getting command graph. */ static struct graph *cmd_node_graph(vector v, enum node_type ntype) { struct cmd_node *cnode = vector_slot(v, ntype); return cnode->cmdgraph; } static int cmd_try_do_shortcut(enum node_type node, char *first_word) { if (first_word != NULL && node != AUTH_NODE && node != VIEW_NODE && node != AUTH_ENABLE_NODE && 0 == strcmp("do", first_word)) return 1; return 0; } /** * Compare function for cmd_token. * Used with qsort to sort command completions. */ static int compare_completions(const void *fst, const void *snd) { const struct cmd_token *first = *(const struct cmd_token * const *)fst, *secnd = *(const struct cmd_token * const *)snd; return strcmp(first->text, secnd->text); } /** * Takes a list of completions returned by command_complete, * dedeuplicates them based on both text and description, * sorts them, and returns them as a vector. * * @param completions linked list of cmd_token * @return deduplicated and sorted vector with */ vector completions_to_vec(struct list *completions) { vector comps = vector_init(VECTOR_MIN_SIZE); struct listnode *ln; struct cmd_token *token, *cr = NULL; unsigned int i, exists; for (ALL_LIST_ELEMENTS_RO(completions, ln, token)) { if (token->type == END_TKN && (cr = token)) continue; // linear search for token in completions vector exists = 0; for (i = 0; i < vector_active(comps) && !exists; i++) { struct cmd_token *curr = vector_slot(comps, i); #ifdef VTYSH_DEBUG exists = !strcmp(curr->text, token->text) && !strcmp(curr->desc, token->desc); #else exists = !strcmp(curr->text, token->text); #endif /* VTYSH_DEBUG */ } if (!exists) vector_set(comps, token); } // sort completions qsort(comps->index, vector_active(comps), sizeof(void *), &compare_completions); // make the first element, if it is present if (cr) { vector_set_index(comps, vector_active(comps), NULL); memmove(comps->index + 1, comps->index, (comps->alloced - 1) * sizeof(void *)); vector_set_index(comps, 0, cr); } return comps; } /** * Generates a vector of cmd_token representing possible completions * on the current input. * * @param vline the vectorized input line * @param vty the vty with the node to match on * @param status pointer to matcher status code * @return vector of struct cmd_token * with possible completions */ static vector cmd_complete_command_real(vector vline, struct vty *vty, int *status) { struct list *completions; struct graph *cmdgraph = cmd_node_graph(cmdvec, vty->node); enum matcher_rv rv = command_complete(cmdgraph, vline, &completions); if (MATCHER_ERROR(rv)) { *status = CMD_ERR_NO_MATCH; return NULL; } vector comps = completions_to_vec(completions); list_delete(&completions); // set status code appropriately switch (vector_active(comps)) { case 0: *status = CMD_ERR_NO_MATCH; break; case 1: *status = CMD_COMPLETE_FULL_MATCH; break; default: *status = CMD_COMPLETE_LIST_MATCH; } return comps; } vector cmd_describe_command(vector vline, struct vty *vty, int *status) { vector ret; if (cmd_try_do_shortcut(vty->node, vector_slot(vline, 0))) { enum node_type onode; int orig_xpath_index; vector shifted_vline; unsigned int index; onode = vty->node; orig_xpath_index = vty->xpath_index; vty->node = ENABLE_NODE; vty->xpath_index = 0; /* We can try it on enable node, cos' the vty is authenticated */ shifted_vline = vector_init(vector_count(vline)); /* use memcpy? */ for (index = 1; index < vector_active(vline); index++) { vector_set_index(shifted_vline, index - 1, vector_lookup(vline, index)); } ret = cmd_complete_command_real(shifted_vline, vty, status); vector_free(shifted_vline); vty->node = onode; vty->xpath_index = orig_xpath_index; return ret; } return cmd_complete_command_real(vline, vty, status); } static struct list *varhandlers = NULL; void cmd_variable_complete(struct cmd_token *token, const char *arg, vector comps) { struct listnode *ln; const struct cmd_variable_handler *cvh; size_t i, argsz; vector tmpcomps; tmpcomps = arg ? vector_init(VECTOR_MIN_SIZE) : comps; for (ALL_LIST_ELEMENTS_RO(varhandlers, ln, cvh)) { if (cvh->tokenname && strcmp(cvh->tokenname, token->text)) continue; if (cvh->varname && (!token->varname || strcmp(cvh->varname, token->varname))) continue; cvh->completions(tmpcomps, token); break; } if (!arg) return; argsz = strlen(arg); for (i = vector_active(tmpcomps); i; i--) { char *item = vector_slot(tmpcomps, i - 1); if (strlen(item) >= argsz && !strncmp(item, arg, argsz)) vector_set(comps, item); else XFREE(MTYPE_COMPLETION, item); } vector_free(tmpcomps); } #define AUTOCOMP_INDENT 5 char *cmd_variable_comp2str(vector comps, unsigned short cols) { size_t bsz = 16; char *buf = XCALLOC(MTYPE_TMP, bsz); int lc = AUTOCOMP_INDENT; size_t cs = AUTOCOMP_INDENT; size_t itemlen; snprintf(buf, bsz, "%*s", AUTOCOMP_INDENT, ""); for (size_t j = 0; j < vector_active(comps); j++) { char *item = vector_slot(comps, j); itemlen = strlen(item); if (cs + itemlen + AUTOCOMP_INDENT + 3 >= bsz) buf = XREALLOC(MTYPE_TMP, buf, (bsz *= 2)); if (lc + itemlen + 1 >= cols) { cs += snprintf(&buf[cs], bsz - cs, "\n%*s", AUTOCOMP_INDENT, ""); lc = AUTOCOMP_INDENT; } size_t written = snprintf(&buf[cs], bsz - cs, "%s ", item); lc += written; cs += written; XFREE(MTYPE_COMPLETION, item); vector_set_index(comps, j, NULL); } return buf; } void cmd_variable_handler_register(const struct cmd_variable_handler *cvh) { if (!varhandlers) return; for (; cvh->completions; cvh++) listnode_add(varhandlers, (void *)cvh); } DEFUN_HIDDEN (autocomplete, autocomplete_cmd, "autocomplete TYPE TEXT VARNAME", "Autocompletion handler (internal, for vtysh)\n" "cmd_token->type\n" "cmd_token->text\n" "cmd_token->varname\n") { struct cmd_token tok; vector comps = vector_init(32); size_t i; memset(&tok, 0, sizeof(tok)); tok.type = atoi(argv[1]->arg); tok.text = argv[2]->arg; tok.varname = argv[3]->arg; if (!strcmp(tok.varname, "-")) tok.varname = NULL; cmd_variable_complete(&tok, NULL, comps); for (i = 0; i < vector_active(comps); i++) { char *text = vector_slot(comps, i); vty_out(vty, "%s\n", text); XFREE(MTYPE_COMPLETION, text); } vector_free(comps); return CMD_SUCCESS; } /** * Generate possible tab-completions for the given input. This function only * returns results that would result in a valid command if used as Readline * completions (as is the case in vtysh). For instance, if the passed vline ends * with '4.3.2', the strings 'A.B.C.D' and 'A.B.C.D/M' will _not_ be returned. * * @param vline vectorized input line * @param vty the vty * @param status location to store matcher status code in * @return set of valid strings for use with Readline as tab-completions. */ char **cmd_complete_command(vector vline, struct vty *vty, int *status) { char **ret = NULL; int original_node = vty->node; vector input_line = vector_init(vector_count(vline)); // if the first token is 'do' we'll want to execute the command in the // enable node int do_shortcut = cmd_try_do_shortcut(vty->node, vector_slot(vline, 0)); vty->node = do_shortcut ? ENABLE_NODE : original_node; // construct the input line we'll be matching on unsigned int offset = (do_shortcut) ? 1 : 0; for (unsigned index = 0; index + offset < vector_active(vline); index++) vector_set_index(input_line, index, vector_lookup(vline, index + offset)); // get token completions -- this is a copying operation vector comps = NULL, initial_comps; initial_comps = cmd_complete_command_real(input_line, vty, status); if (!MATCHER_ERROR(*status)) { assert(initial_comps); // filter out everything that is not suitable for a // tab-completion comps = vector_init(VECTOR_MIN_SIZE); for (unsigned int i = 0; i < vector_active(initial_comps); i++) { struct cmd_token *token = vector_slot(initial_comps, i); if (token->type == WORD_TKN) vector_set(comps, XSTRDUP(MTYPE_COMPLETION, token->text)); else if (IS_VARYING_TOKEN(token->type)) { const char *ref = vector_lookup( vline, vector_active(vline) - 1); cmd_variable_complete(token, ref, comps); } } vector_free(initial_comps); // since we filtered results, we need to re-set status code switch (vector_active(comps)) { case 0: *status = CMD_ERR_NO_MATCH; break; case 1: *status = CMD_COMPLETE_FULL_MATCH; break; default: *status = CMD_COMPLETE_LIST_MATCH; } // copy completions text into an array of char* ret = XMALLOC(MTYPE_TMP, (vector_active(comps) + 1) * sizeof(char *)); unsigned int i; for (i = 0; i < vector_active(comps); i++) { ret[i] = vector_slot(comps, i); } // set the last element to NULL, because this array is used in // a Readline completion_generator function which expects NULL // as a sentinel value ret[i] = NULL; vector_free(comps); comps = NULL; } else if (initial_comps) vector_free(initial_comps); // comps should always be null here assert(!comps); // free the adjusted input line vector_free(input_line); // reset vty->node to its original value vty->node = original_node; return ret; } /* return parent node */ /* MUST eventually converge on CONFIG_NODE */ enum node_type node_parent(enum node_type node) { enum node_type ret; assert(node > CONFIG_NODE); switch (node) { case BGP_VPNV4_NODE: case BGP_VPNV6_NODE: case BGP_FLOWSPECV4_NODE: case BGP_FLOWSPECV6_NODE: case BGP_VRF_POLICY_NODE: case BGP_VNC_DEFAULTS_NODE: case BGP_VNC_NVE_GROUP_NODE: case BGP_VNC_L2_GROUP_NODE: case BGP_IPV4_NODE: case BGP_IPV4M_NODE: case BGP_IPV4L_NODE: case BGP_IPV6_NODE: case BGP_IPV6M_NODE: case BGP_EVPN_NODE: case BGP_IPV6L_NODE: case BMP_NODE: ret = BGP_NODE; break; case BGP_EVPN_VNI_NODE: ret = BGP_EVPN_NODE; break; case KEYCHAIN_KEY_NODE: ret = KEYCHAIN_NODE; break; case LINK_PARAMS_NODE: ret = INTERFACE_NODE; break; case LDP_IPV4_NODE: case LDP_IPV6_NODE: ret = LDP_NODE; break; case LDP_IPV4_IFACE_NODE: ret = LDP_IPV4_NODE; break; case LDP_IPV6_IFACE_NODE: ret = LDP_IPV6_NODE; break; case LDP_PSEUDOWIRE_NODE: ret = LDP_L2VPN_NODE; break; case BFD_PEER_NODE: ret = BFD_NODE; break; default: ret = CONFIG_NODE; break; } return ret; } /* Execute command by argument vline vector. */ static int cmd_execute_command_real(vector vline, enum cmd_filter_type filter, struct vty *vty, const struct cmd_element **cmd) { struct list *argv_list; enum matcher_rv status; const struct cmd_element *matched_element = NULL; struct graph *cmdgraph = cmd_node_graph(cmdvec, vty->node); status = command_match(cmdgraph, vline, &argv_list, &matched_element); if (cmd) *cmd = matched_element; // if matcher error, return corresponding CMD_ERR if (MATCHER_ERROR(status)) { if (argv_list) list_delete(&argv_list); switch (status) { case MATCHER_INCOMPLETE: return CMD_ERR_INCOMPLETE; case MATCHER_AMBIGUOUS: return CMD_ERR_AMBIGUOUS; default: return CMD_ERR_NO_MATCH; } } // build argv array from argv list struct cmd_token **argv = XMALLOC( MTYPE_TMP, argv_list->count * sizeof(struct cmd_token *)); struct listnode *ln; struct cmd_token *token; unsigned int i = 0; for (ALL_LIST_ELEMENTS_RO(argv_list, ln, token)) argv[i++] = token; int argc = argv_list->count; int ret; if (matched_element->daemon) ret = CMD_SUCCESS_DAEMON; else { if (vty->config) { /* Clear array of enqueued configuration changes. */ vty->num_cfg_changes = 0; memset(&vty->cfg_changes, 0, sizeof(vty->cfg_changes)); /* Regenerate candidate configuration. */ if (frr_get_cli_mode() == FRR_CLI_CLASSIC) nb_config_replace(vty->candidate_config, running_config, true); } ret = matched_element->func(matched_element, vty, argc, argv); } // delete list and cmd_token's in it list_delete(&argv_list); XFREE(MTYPE_TMP, argv); return ret; } /** * Execute a given command, handling things like "do ..." and checking * whether the given command might apply at a parent node if doesn't * apply for the current node. * * @param vline Command line input, vector of char* where each element is * one input token. * @param vty The vty context in which the command should be executed. * @param cmd Pointer where the struct cmd_element of the matched command * will be stored, if any. May be set to NULL if this info is * not needed. * @param vtysh If set != 0, don't lookup the command at parent nodes. * @return The status of the command that has been executed or an error code * as to why no command could be executed. */ int cmd_execute_command(vector vline, struct vty *vty, const struct cmd_element **cmd, int vtysh) { int ret, saved_ret = 0; enum node_type onode, try_node; int orig_xpath_index; onode = try_node = vty->node; orig_xpath_index = vty->xpath_index; if (cmd_try_do_shortcut(vty->node, vector_slot(vline, 0))) { vector shifted_vline; unsigned int index; vty->node = ENABLE_NODE; vty->xpath_index = 0; /* We can try it on enable node, cos' the vty is authenticated */ shifted_vline = vector_init(vector_count(vline)); /* use memcpy? */ for (index = 1; index < vector_active(vline); index++) vector_set_index(shifted_vline, index - 1, vector_lookup(vline, index)); ret = cmd_execute_command_real(shifted_vline, FILTER_RELAXED, vty, cmd); vector_free(shifted_vline); vty->node = onode; vty->xpath_index = orig_xpath_index; return ret; } saved_ret = ret = cmd_execute_command_real(vline, FILTER_RELAXED, vty, cmd); if (vtysh) return saved_ret; if (ret != CMD_SUCCESS && ret != CMD_WARNING && ret != CMD_NOT_MY_INSTANCE && ret != CMD_WARNING_CONFIG_FAILED) { /* This assumes all nodes above CONFIG_NODE are childs of * CONFIG_NODE */ while (vty->node > CONFIG_NODE) { try_node = node_parent(try_node); vty->node = try_node; if (vty->xpath_index > 0) vty->xpath_index--; ret = cmd_execute_command_real(vline, FILTER_RELAXED, vty, cmd); if (ret == CMD_SUCCESS || ret == CMD_WARNING || ret == CMD_NOT_MY_INSTANCE || ret == CMD_WARNING_CONFIG_FAILED) return ret; } /* no command succeeded, reset the vty to the original node */ vty->node = onode; vty->xpath_index = orig_xpath_index; } /* return command status for original node */ return saved_ret; } /** * Execute a given command, matching it strictly against the current node. * This mode is used when reading config files. * * @param vline Command line input, vector of char* where each element is * one input token. * @param vty The vty context in which the command should be executed. * @param cmd Pointer where the struct cmd_element* of the matched command * will be stored, if any. May be set to NULL if this info is * not needed. * @return The status of the command that has been executed or an error code * as to why no command could be executed. */ int cmd_execute_command_strict(vector vline, struct vty *vty, const struct cmd_element **cmd) { return cmd_execute_command_real(vline, FILTER_STRICT, vty, cmd); } /* * Hook for preprocessing command string before executing. * * All subscribers are called with the raw command string that is to be * executed. If any changes are to be made, a new string should be allocated * with MTYPE_TMP and *cmd_out updated to point to this new string. The caller * is then responsible for freeing this string. * * All processing functions must be mutually exclusive in their action, i.e. if * one subscriber decides to modify the command, all others must not modify it * when called. Feeding the output of one processing command into a subsequent * one is not supported. * * This hook is intentionally internal to the command processing system. * * cmd_in * The raw command string. * * cmd_out * The result of any processing. */ DECLARE_HOOK(cmd_execute, (struct vty *vty, const char *cmd_in, char **cmd_out), (vty, cmd_in, cmd_out)); DEFINE_HOOK(cmd_execute, (struct vty *vty, const char *cmd_in, char **cmd_out), (vty, cmd_in, cmd_out)); /* Hook executed after a CLI command. */ DECLARE_KOOH(cmd_execute_done, (struct vty *vty, const char *cmd_exec), (vty, cmd_exec)); DEFINE_KOOH(cmd_execute_done, (struct vty *vty, const char *cmd_exec), (vty, cmd_exec)); /* * cmd_execute hook subscriber to handle `|` actions. */ static int handle_pipe_action(struct vty *vty, const char *cmd_in, char **cmd_out) { /* look for `|` */ char *orig, *working, *token, *u; char *pipe = strstr(cmd_in, "| "); if (!pipe) return 0; /* duplicate string for processing purposes, not including pipe */ orig = working = XSTRDUP(MTYPE_TMP, pipe + 2); /* retrieve action */ token = strsep(&working, " "); assert(token); /* match result to known actions */ if (strmatch(token, "include")) { /* the remaining text should be a regexp */ char *regexp = working; if (!regexp) { vty_out(vty, "%% Need a regexp to filter with\n"); goto fail; } bool succ = vty_set_include(vty, regexp); if (!succ) { vty_out(vty, "%% Bad regexp '%s'\n", regexp); goto fail; } *cmd_out = XSTRDUP(MTYPE_TMP, cmd_in); u = *cmd_out; strsep(&u, "|"); } else { vty_out(vty, "%% Unknown action '%s'\n", token); goto fail; } fail: XFREE(MTYPE_TMP, orig); return 0; } static int handle_pipe_action_done(struct vty *vty, const char *cmd_exec) { if (vty->filter) vty_set_include(vty, NULL); return 0; } int cmd_execute(struct vty *vty, const char *cmd, const struct cmd_element **matched, int vtysh) { int ret; char *cmd_out = NULL; const char *cmd_exec; vector vline; hook_call(cmd_execute, vty, cmd, &cmd_out); cmd_exec = cmd_out ? (const char *)cmd_out : cmd; vline = cmd_make_strvec(cmd_exec); if (vline) { ret = cmd_execute_command(vline, vty, matched, vtysh); cmd_free_strvec(vline); } else { ret = CMD_SUCCESS; } hook_call(cmd_execute_done, vty, cmd_exec); XFREE(MTYPE_TMP, cmd_out); return ret; } /** * Parse one line of config, walking up the parse tree attempting to find a * match * * @param vty The vty context in which the command should be executed. * @param cmd Pointer where the struct cmd_element* of the match command * will be stored, if any. May be set to NULL if this info is * not needed. * @param use_daemon Boolean to control whether or not we match on * CMD_SUCCESS_DAEMON * or not. * @return The status of the command that has been executed or an error code * as to why no command could be executed. */ int command_config_read_one_line(struct vty *vty, const struct cmd_element **cmd, uint32_t line_num, int use_daemon) { vector vline; int ret; vline = cmd_make_strvec(vty->buf); /* In case of comment line */ if (vline == NULL) return CMD_SUCCESS; /* Execute configuration command : this is strict match */ ret = cmd_execute_command_strict(vline, vty, cmd); // Climb the tree and try the command again at each node if (!(use_daemon && ret == CMD_SUCCESS_DAEMON) && !(!use_daemon && ret == CMD_ERR_NOTHING_TODO) && ret != CMD_SUCCESS && ret != CMD_WARNING && ret != CMD_NOT_MY_INSTANCE && ret != CMD_WARNING_CONFIG_FAILED && vty->node != CONFIG_NODE) { int saved_node = vty->node; int saved_xpath_index = vty->xpath_index; while (!(use_daemon && ret == CMD_SUCCESS_DAEMON) && !(!use_daemon && ret == CMD_ERR_NOTHING_TODO) && ret != CMD_SUCCESS && ret != CMD_WARNING && vty->node > CONFIG_NODE) { vty->node = node_parent(vty->node); if (vty->xpath_index > 0) vty->xpath_index--; ret = cmd_execute_command_strict(vline, vty, cmd); } // If climbing the tree did not work then ignore the command and // stay at the same node if (!(use_daemon && ret == CMD_SUCCESS_DAEMON) && !(!use_daemon && ret == CMD_ERR_NOTHING_TODO) && ret != CMD_SUCCESS && ret != CMD_WARNING) { vty->node = saved_node; vty->xpath_index = saved_xpath_index; } } if (ret != CMD_SUCCESS && ret != CMD_WARNING && ret != CMD_SUCCESS_DAEMON) { struct vty_error *ve = XCALLOC(MTYPE_TMP, sizeof(*ve)); memcpy(ve->error_buf, vty->buf, VTY_BUFSIZ); ve->line_num = line_num; if (!vty->error) vty->error = list_new(); listnode_add(vty->error, ve); } cmd_free_strvec(vline); return ret; } /* Configuration make from file. */ int config_from_file(struct vty *vty, FILE *fp, unsigned int *line_num) { int ret, error_ret = 0; *line_num = 0; while (fgets(vty->buf, VTY_BUFSIZ, fp)) { ++(*line_num); ret = command_config_read_one_line(vty, NULL, *line_num, 0); if (ret != CMD_SUCCESS && ret != CMD_WARNING && ret != CMD_ERR_NOTHING_TODO) error_ret = ret; } if (error_ret) { return error_ret; } return CMD_SUCCESS; } /* Configuration from terminal */ DEFUN (config_terminal, config_terminal_cmd, "configure [terminal]", "Configuration from vty interface\n" "Configuration terminal\n") { return vty_config_enter(vty, false, false); } /* Enable command */ DEFUN (enable, config_enable_cmd, "enable", "Turn on privileged mode command\n") { /* If enable password is NULL, change to ENABLE_NODE */ if ((host.enable == NULL && host.enable_encrypt == NULL) || vty->type == VTY_SHELL_SERV) vty->node = ENABLE_NODE; else vty->node = AUTH_ENABLE_NODE; return CMD_SUCCESS; } /* Disable command */ DEFUN (disable, config_disable_cmd, "disable", "Turn off privileged mode command\n") { if (vty->node == ENABLE_NODE) vty->node = VIEW_NODE; return CMD_SUCCESS; } /* Down vty node level. */ DEFUN (config_exit, config_exit_cmd, "exit", "Exit current mode and down to previous mode\n") { cmd_exit(vty); return CMD_SUCCESS; } void cmd_exit(struct vty *vty) { switch (vty->node) { case VIEW_NODE: case ENABLE_NODE: if (vty_shell(vty)) exit(0); else vty->status = VTY_CLOSE; break; case CONFIG_NODE: vty->node = ENABLE_NODE; vty_config_exit(vty); break; case INTERFACE_NODE: case PW_NODE: case VRF_NODE: case NH_GROUP_NODE: case ZEBRA_NODE: case BGP_NODE: case RIP_NODE: case EIGRP_NODE: case BABEL_NODE: case RIPNG_NODE: case OSPF_NODE: case OSPF6_NODE: case LDP_NODE: case LDP_L2VPN_NODE: case ISIS_NODE: case OPENFABRIC_NODE: case KEYCHAIN_NODE: case RMAP_NODE: case PBRMAP_NODE: case VTY_NODE: case BFD_NODE: vty->node = CONFIG_NODE; break; case BGP_IPV4_NODE: case BGP_IPV4M_NODE: case BGP_IPV4L_NODE: case BGP_VPNV4_NODE: case BGP_VPNV6_NODE: case BGP_FLOWSPECV4_NODE: case BGP_FLOWSPECV6_NODE: case BGP_VRF_POLICY_NODE: case BGP_VNC_DEFAULTS_NODE: case BGP_VNC_NVE_GROUP_NODE: case BGP_VNC_L2_GROUP_NODE: case BGP_IPV6_NODE: case BGP_IPV6M_NODE: case BGP_EVPN_NODE: case BGP_IPV6L_NODE: case BMP_NODE: vty->node = BGP_NODE; break; case BGP_EVPN_VNI_NODE: vty->node = BGP_EVPN_NODE; break; case LDP_IPV4_NODE: case LDP_IPV6_NODE: vty->node = LDP_NODE; break; case LDP_IPV4_IFACE_NODE: vty->node = LDP_IPV4_NODE; break; case LDP_IPV6_IFACE_NODE: vty->node = LDP_IPV6_NODE; break; case LDP_PSEUDOWIRE_NODE: vty->node = LDP_L2VPN_NODE; break; case KEYCHAIN_KEY_NODE: vty->node = KEYCHAIN_NODE; break; case LINK_PARAMS_NODE: vty->node = INTERFACE_NODE; break; case BFD_PEER_NODE: vty->node = BFD_NODE; break; default: break; } if (vty->xpath_index > 0) vty->xpath_index--; } /* ALIAS_FIXME */ DEFUN (config_quit, config_quit_cmd, "quit", "Exit current mode and down to previous mode\n") { return config_exit(self, vty, argc, argv); } /* End of configuration. */ DEFUN (config_end, config_end_cmd, "end", "End current mode and change to enable mode.\n") { if (vty->config) { vty_config_exit(vty); vty->node = ENABLE_NODE; } return CMD_SUCCESS; } /* Show version. */ DEFUN (show_version, show_version_cmd, "show version", SHOW_STR "Displays zebra version\n") { vty_out(vty, "%s %s (%s).\n", FRR_FULL_NAME, FRR_VERSION, cmd_hostname_get() ? cmd_hostname_get() : ""); vty_out(vty, "%s%s\n", FRR_COPYRIGHT, GIT_INFO); vty_out(vty, "configured with:\n %s\n", FRR_CONFIG_ARGS); return CMD_SUCCESS; } /* "Set" version ... ignore version tags */ DEFUN (frr_version_defaults, frr_version_defaults_cmd, "frr LINE...", "FRRouting global parameters\n" "version configuration was written by\n" "set of configuration defaults used\n" "version string\n") { return CMD_SUCCESS; } /* Help display function for all node. */ DEFUN (config_help, config_help_cmd, "help", "Description of the interactive help system\n") { vty_out(vty, "Quagga VTY provides advanced help feature. When you need help,\n\ anytime at the command line please press '?'.\n\ \n\ If nothing matches, the help list will be empty and you must backup\n\ until entering a '?' shows the available options.\n\ Two styles of help are provided:\n\ 1. Full help is available when you are ready to enter a\n\ command argument (e.g. 'show ?') and describes each possible\n\ argument.\n\ 2. Partial help is provided when an abbreviated argument is entered\n\ and you want to know what arguments match the input\n\ (e.g. 'show me?'.)\n\n"); return CMD_SUCCESS; } static void permute(struct graph_node *start, struct vty *vty) { static struct list *position = NULL; if (!position) position = list_new(); struct cmd_token *stok = start->data; struct graph_node *gnn; struct listnode *ln; // recursive dfs listnode_add(position, start); for (unsigned int i = 0; i < vector_active(start->to); i++) { struct graph_node *gn = vector_slot(start->to, i); struct cmd_token *tok = gn->data; if (tok->attr == CMD_ATTR_HIDDEN || tok->attr == CMD_ATTR_DEPRECATED) continue; else if (tok->type == END_TKN || gn == start) { vty_out(vty, " "); for (ALL_LIST_ELEMENTS_RO(position, ln, gnn)) { struct cmd_token *tt = gnn->data; if (tt->type < SPECIAL_TKN) vty_out(vty, " %s", tt->text); } if (gn == start) vty_out(vty, "..."); vty_out(vty, "\n"); } else { bool skip = false; if (stok->type == FORK_TKN && tok->type != FORK_TKN) for (ALL_LIST_ELEMENTS_RO(position, ln, gnn)) if (gnn == gn) { skip = true; break; } if (!skip) permute(gn, vty); } } list_delete_node(position, listtail(position)); } int cmd_list_cmds(struct vty *vty, int do_permute) { struct cmd_node *node = vector_slot(cmdvec, vty->node); if (do_permute) permute(vector_slot(node->cmdgraph->nodes, 0), vty); else { /* loop over all commands at this node */ struct cmd_element *element = NULL; for (unsigned int i = 0; i < vector_active(node->cmd_vector); i++) if ((element = vector_slot(node->cmd_vector, i)) && element->attr != CMD_ATTR_DEPRECATED && element->attr != CMD_ATTR_HIDDEN) vty_out(vty, " %s\n", element->string); } return CMD_SUCCESS; } /* Help display function for all node. */ DEFUN (config_list, config_list_cmd, "list [permutations]", "Print command list\n" "Print all possible command permutations\n") { return cmd_list_cmds(vty, argc == 2); } DEFUN (show_commandtree, show_commandtree_cmd, "show commandtree [permutations]", SHOW_STR "Show command tree\n" "Permutations that we are interested in\n") { return cmd_list_cmds(vty, argc == 3); } DEFUN_HIDDEN(show_cli_graph, show_cli_graph_cmd, "show cli graph", SHOW_STR "CLI reflection\n" "Dump current command space as DOT graph\n") { struct cmd_node *cn = vector_slot(cmdvec, vty->node); char *dot = cmd_graph_dump_dot(cn->cmdgraph); vty_out(vty, "%s\n", dot); XFREE(MTYPE_TMP, dot); return CMD_SUCCESS; } static int vty_write_config(struct vty *vty) { size_t i; struct cmd_node *node; if (host.noconfig) return CMD_SUCCESS; if (vty->type == VTY_TERM) { vty_out(vty, "\nCurrent configuration:\n"); vty_out(vty, "!\n"); } vty_out(vty, "frr version %s\n", FRR_VER_SHORT); vty_out(vty, "frr defaults %s\n", DFLT_NAME); vty_out(vty, "!\n"); pthread_rwlock_rdlock(&running_config->lock); { for (i = 0; i < vector_active(cmdvec); i++) if ((node = vector_slot(cmdvec, i)) && node->func && (node->vtysh || vty->type != VTY_SHELL)) { if ((*node->func)(vty)) vty_out(vty, "!\n"); } } pthread_rwlock_unlock(&running_config->lock); if (vty->type == VTY_TERM) { vty_out(vty, "end\n"); } return CMD_SUCCESS; } static int file_write_config(struct vty *vty) { int fd, dirfd; char *config_file, *slash; char *config_file_tmp = NULL; char *config_file_sav = NULL; int ret = CMD_WARNING; struct vty *file_vty; struct stat conf_stat; if (host.noconfig) return CMD_SUCCESS; /* Check and see if we are operating under vtysh configuration */ if (host.config == NULL) { vty_out(vty, "Can't save to configuration file, using vtysh.\n"); return CMD_WARNING; } /* Get filename. */ config_file = host.config; #ifndef O_DIRECTORY #define O_DIRECTORY 0 #endif slash = strrchr(config_file, '/'); if (slash) { char *config_dir = XSTRDUP(MTYPE_TMP, config_file); config_dir[slash - config_file] = '\0'; dirfd = open(config_dir, O_DIRECTORY | O_RDONLY); XFREE(MTYPE_TMP, config_dir); } else dirfd = open(".", O_DIRECTORY | O_RDONLY); /* if dirfd is invalid, directory sync fails, but we're still OK */ size_t config_file_sav_sz = strlen(config_file) + strlen(CONF_BACKUP_EXT) + 1; config_file_sav = XMALLOC(MTYPE_TMP, config_file_sav_sz); strlcpy(config_file_sav, config_file, config_file_sav_sz); strlcat(config_file_sav, CONF_BACKUP_EXT, config_file_sav_sz); config_file_tmp = XMALLOC(MTYPE_TMP, strlen(config_file) + 8); sprintf(config_file_tmp, "%s.XXXXXX", config_file); /* Open file to configuration write. */ fd = mkstemp(config_file_tmp); if (fd < 0) { vty_out(vty, "Can't open configuration file %s.\n", config_file_tmp); goto finished; } if (fchmod(fd, CONFIGFILE_MASK) != 0) { vty_out(vty, "Can't chmod configuration file %s: %s (%d).\n", config_file_tmp, safe_strerror(errno), errno); goto finished; } /* Make vty for configuration file. */ file_vty = vty_new(); file_vty->wfd = fd; file_vty->type = VTY_FILE; /* Config file header print. */ vty_out(file_vty, "!\n! Zebra configuration saved from vty\n! "); vty_time_print(file_vty, 1); vty_out(file_vty, "!\n"); vty_write_config(file_vty); vty_close(file_vty); if (stat(config_file, &conf_stat) >= 0) { if (unlink(config_file_sav) != 0) if (errno != ENOENT) { vty_out(vty, "Can't unlink backup configuration file %s.\n", config_file_sav); goto finished; } if (link(config_file, config_file_sav) != 0) { vty_out(vty, "Can't backup old configuration file %s.\n", config_file_sav); goto finished; } if (dirfd >= 0) fsync(dirfd); } if (rename(config_file_tmp, config_file) != 0) { vty_out(vty, "Can't save configuration file %s.\n", config_file); goto finished; } if (dirfd >= 0) fsync(dirfd); vty_out(vty, "Configuration saved to %s\n", config_file); ret = CMD_SUCCESS; finished: if (ret != CMD_SUCCESS) unlink(config_file_tmp); if (dirfd >= 0) close(dirfd); XFREE(MTYPE_TMP, config_file_tmp); XFREE(MTYPE_TMP, config_file_sav); return ret; } /* Write current configuration into file. */ DEFUN (config_write, config_write_cmd, "write []", "Write running configuration to memory, network, or terminal\n" "Write to configuration file\n" "Write configuration currently in memory\n" "Write configuration to terminal\n") { const int idx_type = 1; // if command was 'write terminal' or 'write memory' if (argc == 2 && (!strcmp(argv[idx_type]->text, "terminal"))) { return vty_write_config(vty); } return file_write_config(vty); } /* ALIAS_FIXME for 'write ' */ DEFUN (show_running_config, show_running_config_cmd, "show running-config", SHOW_STR "running configuration (same as write terminal)\n") { return vty_write_config(vty); } /* ALIAS_FIXME for 'write file' */ DEFUN (copy_runningconf_startupconf, copy_runningconf_startupconf_cmd, "copy running-config startup-config", "Copy configuration\n" "Copy running config to... \n" "Copy running config to startup config (same as write file/memory)\n") { return file_write_config(vty); } /** -- **/ /* Write startup configuration into the terminal. */ DEFUN (show_startup_config, show_startup_config_cmd, "show startup-config", SHOW_STR "Contents of startup configuration\n") { char buf[BUFSIZ]; FILE *confp; if (host.noconfig) return CMD_SUCCESS; if (host.config == NULL) return CMD_WARNING; confp = fopen(host.config, "r"); if (confp == NULL) { vty_out(vty, "Can't open configuration file [%s] due to '%s'\n", host.config, safe_strerror(errno)); return CMD_WARNING; } while (fgets(buf, BUFSIZ, confp)) { char *cp = buf; while (*cp != '\r' && *cp != '\n' && *cp != '\0') cp++; *cp = '\0'; vty_out(vty, "%s\n", buf); } fclose(confp); return CMD_SUCCESS; } int cmd_domainname_set(const char *domainname) { XFREE(MTYPE_HOST, host.domainname); host.domainname = domainname ? XSTRDUP(MTYPE_HOST, domainname) : NULL; return CMD_SUCCESS; } /* Hostname configuration */ DEFUN(config_domainname, domainname_cmd, "domainname WORD", "Set system's domain name\n" "This system's domain name\n") { struct cmd_token *word = argv[1]; if (!isalpha((unsigned char)word->arg[0])) { vty_out(vty, "Please specify string starting with alphabet\n"); return CMD_WARNING_CONFIG_FAILED; } return cmd_domainname_set(word->arg); } DEFUN(config_no_domainname, no_domainname_cmd, "no domainname [DOMAINNAME]", NO_STR "Reset system's domain name\n" "domain name of this router\n") { return cmd_domainname_set(NULL); } int cmd_hostname_set(const char *hostname) { XFREE(MTYPE_HOST, host.name); host.name = hostname ? XSTRDUP(MTYPE_HOST, hostname) : NULL; return CMD_SUCCESS; } /* Hostname configuration */ DEFUN (config_hostname, hostname_cmd, "hostname WORD", "Set system's network name\n" "This system's network name\n") { struct cmd_token *word = argv[1]; if (!isalnum((unsigned char)word->arg[0])) { vty_out(vty, "Please specify string starting with alphabet or number\n"); return CMD_WARNING_CONFIG_FAILED; } /* With reference to RFC 1123 Section 2.1 */ if (strlen(word->arg) > HOSTNAME_LEN) { vty_out(vty, "Hostname length should be less than %d chars\n", HOSTNAME_LEN); return CMD_WARNING_CONFIG_FAILED; } return cmd_hostname_set(word->arg); } DEFUN (config_no_hostname, no_hostname_cmd, "no hostname [HOSTNAME]", NO_STR "Reset system's network name\n" "Host name of this router\n") { return cmd_hostname_set(NULL); } /* VTY interface password set. */ DEFUN (config_password, password_cmd, "password [(8-8)] WORD", "Modify the terminal connection password\n" "Specifies a HIDDEN password will follow\n" "The password string\n") { int idx_8 = 1; int idx_word = 2; if (argc == 3) // '8' was specified { if (host.password) XFREE(MTYPE_HOST, host.password); host.password = NULL; if (host.password_encrypt) XFREE(MTYPE_HOST, host.password_encrypt); host.password_encrypt = XSTRDUP(MTYPE_HOST, argv[idx_word]->arg); return CMD_SUCCESS; } if (!isalnum((unsigned char)argv[idx_8]->arg[0])) { vty_out(vty, "Please specify string starting with alphanumeric\n"); return CMD_WARNING_CONFIG_FAILED; } if (host.password) XFREE(MTYPE_HOST, host.password); host.password = NULL; if (host.encrypt) { if (host.password_encrypt) XFREE(MTYPE_HOST, host.password_encrypt); host.password_encrypt = XSTRDUP(MTYPE_HOST, zencrypt(argv[idx_8]->arg)); } else host.password = XSTRDUP(MTYPE_HOST, argv[idx_8]->arg); return CMD_SUCCESS; } /* VTY interface password delete. */ DEFUN (no_config_password, no_password_cmd, "no password", NO_STR "Modify the terminal connection password\n") { bool warned = false; if (host.password) { if (!vty_shell_serv(vty)) { vty_out(vty, NO_PASSWD_CMD_WARNING); warned = true; } XFREE(MTYPE_HOST, host.password); } host.password = NULL; if (host.password_encrypt) { if (!warned && !vty_shell_serv(vty)) vty_out(vty, NO_PASSWD_CMD_WARNING); XFREE(MTYPE_HOST, host.password_encrypt); } host.password_encrypt = NULL; return CMD_SUCCESS; } /* VTY enable password set. */ DEFUN (config_enable_password, enable_password_cmd, "enable password [(8-8)] WORD", "Modify enable password parameters\n" "Assign the privileged level password\n" "Specifies a HIDDEN password will follow\n" "The HIDDEN 'enable' password string\n") { int idx_8 = 2; int idx_word = 3; /* Crypt type is specified. */ if (argc == 4) { if (argv[idx_8]->arg[0] == '8') { if (host.enable) XFREE(MTYPE_HOST, host.enable); host.enable = NULL; if (host.enable_encrypt) XFREE(MTYPE_HOST, host.enable_encrypt); host.enable_encrypt = XSTRDUP(MTYPE_HOST, argv[idx_word]->arg); return CMD_SUCCESS; } else { vty_out(vty, "Unknown encryption type.\n"); return CMD_WARNING_CONFIG_FAILED; } } if (!isalnum((unsigned char)argv[idx_8]->arg[0])) { vty_out(vty, "Please specify string starting with alphanumeric\n"); return CMD_WARNING_CONFIG_FAILED; } if (host.enable) XFREE(MTYPE_HOST, host.enable); host.enable = NULL; /* Plain password input. */ if (host.encrypt) { if (host.enable_encrypt) XFREE(MTYPE_HOST, host.enable_encrypt); host.enable_encrypt = XSTRDUP(MTYPE_HOST, zencrypt(argv[idx_8]->arg)); } else host.enable = XSTRDUP(MTYPE_HOST, argv[idx_8]->arg); return CMD_SUCCESS; } /* VTY enable password delete. */ DEFUN (no_config_enable_password, no_enable_password_cmd, "no enable password", NO_STR "Modify enable password parameters\n" "Assign the privileged level password\n") { bool warned = false; if (host.enable) { if (!vty_shell_serv(vty)) { vty_out(vty, NO_PASSWD_CMD_WARNING); warned = true; } XFREE(MTYPE_HOST, host.enable); } host.enable = NULL; if (host.enable_encrypt) { if (!warned && !vty_shell_serv(vty)) vty_out(vty, NO_PASSWD_CMD_WARNING); XFREE(MTYPE_HOST, host.enable_encrypt); } host.enable_encrypt = NULL; return CMD_SUCCESS; } DEFUN (service_password_encrypt, service_password_encrypt_cmd, "service password-encryption", "Set up miscellaneous service\n" "Enable encrypted passwords\n") { if (host.encrypt) return CMD_SUCCESS; host.encrypt = 1; if (host.password) { if (host.password_encrypt) XFREE(MTYPE_HOST, host.password_encrypt); host.password_encrypt = XSTRDUP(MTYPE_HOST, zencrypt(host.password)); } if (host.enable) { if (host.enable_encrypt) XFREE(MTYPE_HOST, host.enable_encrypt); host.enable_encrypt = XSTRDUP(MTYPE_HOST, zencrypt(host.enable)); } return CMD_SUCCESS; } DEFUN (no_service_password_encrypt, no_service_password_encrypt_cmd, "no service password-encryption", NO_STR "Set up miscellaneous service\n" "Enable encrypted passwords\n") { if (!host.encrypt) return CMD_SUCCESS; host.encrypt = 0; if (host.password_encrypt) XFREE(MTYPE_HOST, host.password_encrypt); host.password_encrypt = NULL; if (host.enable_encrypt) XFREE(MTYPE_HOST, host.enable_encrypt); host.enable_encrypt = NULL; return CMD_SUCCESS; } DEFUN (config_terminal_length, config_terminal_length_cmd, "terminal length (0-512)", "Set terminal line parameters\n" "Set number of lines on a screen\n" "Number of lines on screen (0 for no pausing)\n") { int idx_number = 2; vty->lines = atoi(argv[idx_number]->arg); return CMD_SUCCESS; } DEFUN (config_terminal_no_length, config_terminal_no_length_cmd, "terminal no length", "Set terminal line parameters\n" NO_STR "Set number of lines on a screen\n") { vty->lines = -1; return CMD_SUCCESS; } DEFUN (service_terminal_length, service_terminal_length_cmd, "service terminal-length (0-512)", "Set up miscellaneous service\n" "System wide terminal length configuration\n" "Number of lines of VTY (0 means no line control)\n") { int idx_number = 2; host.lines = atoi(argv[idx_number]->arg); return CMD_SUCCESS; } DEFUN (no_service_terminal_length, no_service_terminal_length_cmd, "no service terminal-length [(0-512)]", NO_STR "Set up miscellaneous service\n" "System wide terminal length configuration\n" "Number of lines of VTY (0 means no line control)\n") { host.lines = -1; return CMD_SUCCESS; } DEFUN_HIDDEN (do_echo, echo_cmd, "echo MESSAGE...", "Echo a message back to the vty\n" "The message to echo\n") { char *message; vty_out(vty, "%s\n", ((message = argv_concat(argv, argc, 1)) ? message : "")); if (message) XFREE(MTYPE_TMP, message); return CMD_SUCCESS; } DEFUN (config_logmsg, config_logmsg_cmd, "logmsg MESSAGE...", "Send a message to enabled logging destinations\n" LOG_LEVEL_DESC "The message to send\n") { int idx_log_level = 1; int idx_message = 2; int level; char *message; if ((level = level_match(argv[idx_log_level]->arg)) == ZLOG_DISABLED) return CMD_ERR_NO_MATCH; zlog(level, "%s", ((message = argv_concat(argv, argc, idx_message)) ? message : "")); if (message) XFREE(MTYPE_TMP, message); return CMD_SUCCESS; } DEFUN (show_logging, show_logging_cmd, "show logging", SHOW_STR "Show current logging configuration\n") { struct zlog *zl = zlog_default; vty_out(vty, "Syslog logging: "); if (zl->maxlvl[ZLOG_DEST_SYSLOG] == ZLOG_DISABLED) vty_out(vty, "disabled"); else vty_out(vty, "level %s, facility %s, ident %s", zlog_priority[zl->maxlvl[ZLOG_DEST_SYSLOG]], facility_name(zl->facility), zl->ident); vty_out(vty, "\n"); vty_out(vty, "Stdout logging: "); if (zl->maxlvl[ZLOG_DEST_STDOUT] == ZLOG_DISABLED) vty_out(vty, "disabled"); else vty_out(vty, "level %s", zlog_priority[zl->maxlvl[ZLOG_DEST_STDOUT]]); vty_out(vty, "\n"); vty_out(vty, "Monitor logging: "); if (zl->maxlvl[ZLOG_DEST_MONITOR] == ZLOG_DISABLED) vty_out(vty, "disabled"); else vty_out(vty, "level %s", zlog_priority[zl->maxlvl[ZLOG_DEST_MONITOR]]); vty_out(vty, "\n"); vty_out(vty, "File logging: "); if ((zl->maxlvl[ZLOG_DEST_FILE] == ZLOG_DISABLED) || !zl->fp) vty_out(vty, "disabled"); else vty_out(vty, "level %s, filename %s", zlog_priority[zl->maxlvl[ZLOG_DEST_FILE]], zl->filename); vty_out(vty, "\n"); vty_out(vty, "Protocol name: %s\n", zl->protoname); vty_out(vty, "Record priority: %s\n", (zl->record_priority ? "enabled" : "disabled")); vty_out(vty, "Timestamp precision: %d\n", zl->timestamp_precision); return CMD_SUCCESS; } DEFUN (config_log_stdout, config_log_stdout_cmd, "log stdout []", "Logging control\n" "Set stdout logging level\n" LOG_LEVEL_DESC) { int idx_log_level = 2; if (argc == idx_log_level) { zlog_set_level(ZLOG_DEST_STDOUT, zlog_default->default_lvl); return CMD_SUCCESS; } int level; if ((level = level_match(argv[idx_log_level]->arg)) == ZLOG_DISABLED) return CMD_ERR_NO_MATCH; zlog_set_level(ZLOG_DEST_STDOUT, level); return CMD_SUCCESS; } DEFUN (no_config_log_stdout, no_config_log_stdout_cmd, "no log stdout []", NO_STR "Logging control\n" "Cancel logging to stdout\n" LOG_LEVEL_DESC) { zlog_set_level(ZLOG_DEST_STDOUT, ZLOG_DISABLED); return CMD_SUCCESS; } DEFUN (config_log_monitor, config_log_monitor_cmd, "log monitor []", "Logging control\n" "Set terminal line (monitor) logging level\n" LOG_LEVEL_DESC) { int idx_log_level = 2; if (argc == idx_log_level) { zlog_set_level(ZLOG_DEST_MONITOR, zlog_default->default_lvl); return CMD_SUCCESS; } int level; if ((level = level_match(argv[idx_log_level]->arg)) == ZLOG_DISABLED) return CMD_ERR_NO_MATCH; zlog_set_level(ZLOG_DEST_MONITOR, level); return CMD_SUCCESS; } DEFUN (no_config_log_monitor, no_config_log_monitor_cmd, "no log monitor []", NO_STR "Logging control\n" "Disable terminal line (monitor) logging\n" LOG_LEVEL_DESC) { zlog_set_level(ZLOG_DEST_MONITOR, ZLOG_DISABLED); return CMD_SUCCESS; } static int set_log_file(struct vty *vty, const char *fname, int loglevel) { int ret; char *p = NULL; const char *fullpath; /* Path detection. */ if (!IS_DIRECTORY_SEP(*fname)) { char cwd[MAXPATHLEN + 1]; cwd[MAXPATHLEN] = '\0'; if (getcwd(cwd, MAXPATHLEN) == NULL) { flog_err_sys(EC_LIB_SYSTEM_CALL, "config_log_file: Unable to alloc mem!"); return CMD_WARNING_CONFIG_FAILED; } p = XMALLOC(MTYPE_TMP, strlen(cwd) + strlen(fname) + 2); sprintf(p, "%s/%s", cwd, fname); fullpath = p; } else fullpath = fname; ret = zlog_set_file(fullpath, loglevel); XFREE(MTYPE_TMP, p); if (!ret) { if (vty) vty_out(vty, "can't open logfile %s\n", fname); return CMD_WARNING_CONFIG_FAILED; } XFREE(MTYPE_HOST, host.logfile); host.logfile = XSTRDUP(MTYPE_HOST, fname); #if defined(HAVE_CUMULUS) if (zlog_default->maxlvl[ZLOG_DEST_SYSLOG] != ZLOG_DISABLED) zlog_set_level(ZLOG_DEST_SYSLOG, ZLOG_DISABLED); #endif return CMD_SUCCESS; } void command_setup_early_logging(const char *dest, const char *level) { char *token; if (level) { int nlevel = level_match(level); if (nlevel != ZLOG_DISABLED) zlog_default->default_lvl = nlevel; } if (!dest) return; if (strcmp(dest, "stdout") == 0) { zlog_set_level(ZLOG_DEST_STDOUT, zlog_default->default_lvl); return; } if (strcmp(dest, "syslog") == 0) { zlog_set_level(ZLOG_DEST_SYSLOG, zlog_default->default_lvl); return; } token = strstr(dest, ":"); if (token == NULL) return; token++; set_log_file(NULL, token, zlog_default->default_lvl); } DEFUN (config_log_file, config_log_file_cmd, "log file FILENAME []", "Logging control\n" "Logging to file\n" "Logging filename\n" LOG_LEVEL_DESC) { int idx_filename = 2; int idx_log_levels = 3; if (argc == 4) { int level; if ((level = level_match(argv[idx_log_levels]->arg)) == ZLOG_DISABLED) return CMD_ERR_NO_MATCH; return set_log_file(vty, argv[idx_filename]->arg, level); } else return set_log_file(vty, argv[idx_filename]->arg, zlog_default->default_lvl); } static void disable_log_file(void) { zlog_reset_file(); XFREE(MTYPE_HOST, host.logfile); host.logfile = NULL; } DEFUN (no_config_log_file, no_config_log_file_cmd, "no log file [FILENAME [LEVEL]]", NO_STR "Logging control\n" "Cancel logging to file\n" "Logging file name\n" "Logging level\n") { disable_log_file(); return CMD_SUCCESS; } DEFUN (config_log_syslog, config_log_syslog_cmd, "log syslog []", "Logging control\n" "Set syslog logging level\n" LOG_LEVEL_DESC) { int idx_log_levels = 2; if (argc == 3) { int level; if ((level = level_match(argv[idx_log_levels]->arg)) == ZLOG_DISABLED) return CMD_ERR_NO_MATCH; zlog_set_level(ZLOG_DEST_SYSLOG, level); return CMD_SUCCESS; } else { zlog_set_level(ZLOG_DEST_SYSLOG, zlog_default->default_lvl); return CMD_SUCCESS; } } DEFUN (no_config_log_syslog, no_config_log_syslog_cmd, "no log syslog [] []", NO_STR "Logging control\n" "Cancel logging to syslog\n" LOG_FACILITY_DESC LOG_LEVEL_DESC) { zlog_set_level(ZLOG_DEST_SYSLOG, ZLOG_DISABLED); return CMD_SUCCESS; } DEFUN (config_log_facility, config_log_facility_cmd, "log facility ", "Logging control\n" "Facility parameter for syslog messages\n" LOG_FACILITY_DESC) { int idx_target = 2; int facility = facility_match(argv[idx_target]->arg); zlog_default->facility = facility; return CMD_SUCCESS; } DEFUN (no_config_log_facility, no_config_log_facility_cmd, "no log facility []", NO_STR "Logging control\n" "Reset syslog facility to default (daemon)\n" LOG_FACILITY_DESC) { zlog_default->facility = LOG_DAEMON; return CMD_SUCCESS; } DEFUN (config_log_record_priority, config_log_record_priority_cmd, "log record-priority", "Logging control\n" "Log the priority of the message within the message\n") { zlog_default->record_priority = 1; return CMD_SUCCESS; } DEFUN (no_config_log_record_priority, no_config_log_record_priority_cmd, "no log record-priority", NO_STR "Logging control\n" "Do not log the priority of the message within the message\n") { zlog_default->record_priority = 0; return CMD_SUCCESS; } DEFUN (config_log_timestamp_precision, config_log_timestamp_precision_cmd, "log timestamp precision (0-6)", "Logging control\n" "Timestamp configuration\n" "Set the timestamp precision\n" "Number of subsecond digits\n") { int idx_number = 3; zlog_default->timestamp_precision = strtoul(argv[idx_number]->arg, NULL, 10); return CMD_SUCCESS; } DEFUN (no_config_log_timestamp_precision, no_config_log_timestamp_precision_cmd, "no log timestamp precision", NO_STR "Logging control\n" "Timestamp configuration\n" "Reset the timestamp precision to the default value of 0\n") { zlog_default->timestamp_precision = 0; return CMD_SUCCESS; } DEFUN (debug_memstats, debug_memstats_cmd, "[no] debug memstats-at-exit", NO_STR DEBUG_STR "Print memory type statistics at exit\n") { debug_memstats_at_exit = !!strcmp(argv[0]->text, "no"); return CMD_SUCCESS; } int cmd_banner_motd_file(const char *file) { int success = CMD_SUCCESS; char p[PATH_MAX]; char *rpath; char *in; rpath = realpath(file, p); if (!rpath) return CMD_ERR_NO_FILE; in = strstr(rpath, SYSCONFDIR); if (in == rpath) { XFREE(MTYPE_HOST, host.motdfile); host.motdfile = XSTRDUP(MTYPE_HOST, file); } else success = CMD_WARNING_CONFIG_FAILED; return success; } DEFUN (banner_motd_file, banner_motd_file_cmd, "banner motd file FILE", "Set banner\n" "Banner for motd\n" "Banner from a file\n" "Filename\n") { int idx_file = 3; const char *filename = argv[idx_file]->arg; int cmd = cmd_banner_motd_file(filename); if (cmd == CMD_ERR_NO_FILE) vty_out(vty, "%s does not exist", filename); else if (cmd == CMD_WARNING_CONFIG_FAILED) vty_out(vty, "%s must be in %s", filename, SYSCONFDIR); return cmd; } DEFUN (banner_motd_default, banner_motd_default_cmd, "banner motd default", "Set banner string\n" "Strings for motd\n" "Default string\n") { host.motd = default_motd; return CMD_SUCCESS; } DEFUN (no_banner_motd, no_banner_motd_cmd, "no banner motd", NO_STR "Set banner string\n" "Strings for motd\n") { host.motd = NULL; if (host.motdfile) XFREE(MTYPE_HOST, host.motdfile); host.motdfile = NULL; return CMD_SUCCESS; } DEFUN(find, find_cmd, "find REGEX", "Find CLI command matching a regular expression\n" "Search pattern (POSIX regex)\n") { char *pattern = argv[1]->arg; const struct cmd_node *node; const struct cmd_element *cli; vector clis; regex_t exp = {}; int cr = regcomp(&exp, pattern, REG_NOSUB | REG_EXTENDED); if (cr != 0) { switch (cr) { case REG_BADBR: vty_out(vty, "%% Invalid {...} expression\n"); break; case REG_BADRPT: vty_out(vty, "%% Bad repetition operator\n"); break; case REG_BADPAT: vty_out(vty, "%% Regex syntax error\n"); break; case REG_ECOLLATE: vty_out(vty, "%% Invalid collating element\n"); break; case REG_ECTYPE: vty_out(vty, "%% Invalid character class name\n"); break; case REG_EESCAPE: vty_out(vty, "%% Regex ended with escape character (\\)\n"); break; case REG_ESUBREG: vty_out(vty, "%% Invalid number in \\digit construction\n"); break; case REG_EBRACK: vty_out(vty, "%% Unbalanced square brackets\n"); break; case REG_EPAREN: vty_out(vty, "%% Unbalanced parentheses\n"); break; case REG_EBRACE: vty_out(vty, "%% Unbalanced braces\n"); break; case REG_ERANGE: vty_out(vty, "%% Invalid endpoint in range expression\n"); break; case REG_ESPACE: vty_out(vty, "%% Failed to compile (out of memory)\n"); break; } goto done; } for (unsigned int i = 0; i < vector_active(cmdvec); i++) { node = vector_slot(cmdvec, i); if (!node) continue; clis = node->cmd_vector; for (unsigned int j = 0; j < vector_active(clis); j++) { cli = vector_slot(clis, j); if (regexec(&exp, cli->string, 0, NULL, 0) == 0) vty_out(vty, " (%s) %s\n", node_names[node->node], cli->string); } } done: regfree(&exp); return CMD_SUCCESS; } /* Set config filename. Called from vty.c */ void host_config_set(const char *filename) { XFREE(MTYPE_HOST, host.config); host.config = XSTRDUP(MTYPE_HOST, filename); } const char *host_config_get(void) { return host.config; } void install_default(enum node_type node) { install_element(node, &config_exit_cmd); install_element(node, &config_quit_cmd); install_element(node, &config_end_cmd); install_element(node, &config_help_cmd); install_element(node, &config_list_cmd); install_element(node, &show_cli_graph_cmd); install_element(node, &find_cmd); install_element(node, &config_write_cmd); install_element(node, &show_running_config_cmd); install_element(node, &autocomplete_cmd); nb_cli_install_default(node); } /* Initialize command interface. Install basic nodes and commands. * * terminal = 0 -- vtysh / no logging, no config control * terminal = 1 -- normal daemon * terminal = -1 -- watchfrr / no logging, but minimal config control */ void cmd_init(int terminal) { struct utsname names; if (array_size(node_names) != NODE_TYPE_MAX) assert(!"Update the CLI node description array!"); uname(&names); qobj_init(); /* register command preprocessors */ hook_register(cmd_execute, handle_pipe_action); hook_register(cmd_execute_done, handle_pipe_action_done); varhandlers = list_new(); /* Allocate initial top vector of commands. */ cmdvec = vector_init(VECTOR_MIN_SIZE); /* Default host value settings. */ host.name = XSTRDUP(MTYPE_HOST, names.nodename); #ifdef HAVE_STRUCT_UTSNAME_DOMAINNAME if ((strcmp(names.domainname, "(none)") == 0)) host.domainname = NULL; else host.domainname = XSTRDUP(MTYPE_HOST, names.domainname); #else host.domainname = NULL; #endif host.password = NULL; host.enable = NULL; host.logfile = NULL; host.config = NULL; host.noconfig = (terminal < 0); host.lines = -1; host.motd = default_motd; host.motdfile = NULL; /* Install top nodes. */ install_node(&view_node, NULL); install_node(&enable_node, NULL); install_node(&auth_node, NULL); install_node(&auth_enable_node, NULL); install_node(&config_node, config_write_host); /* Each node's basic commands. */ install_element(VIEW_NODE, &show_version_cmd); install_element(ENABLE_NODE, &show_startup_config_cmd); if (terminal) { install_element(ENABLE_NODE, &debug_memstats_cmd); install_element(VIEW_NODE, &config_list_cmd); install_element(VIEW_NODE, &config_exit_cmd); install_element(VIEW_NODE, &config_quit_cmd); install_element(VIEW_NODE, &config_help_cmd); install_element(VIEW_NODE, &config_enable_cmd); install_element(VIEW_NODE, &config_terminal_length_cmd); install_element(VIEW_NODE, &config_terminal_no_length_cmd); install_element(VIEW_NODE, &show_logging_cmd); install_element(VIEW_NODE, &show_commandtree_cmd); install_element(VIEW_NODE, &echo_cmd); install_element(VIEW_NODE, &autocomplete_cmd); install_element(VIEW_NODE, &find_cmd); install_element(ENABLE_NODE, &config_end_cmd); install_element(ENABLE_NODE, &config_disable_cmd); install_element(ENABLE_NODE, &config_terminal_cmd); install_element(ENABLE_NODE, ©_runningconf_startupconf_cmd); install_element(ENABLE_NODE, &config_write_cmd); install_element(ENABLE_NODE, &show_running_config_cmd); install_element(ENABLE_NODE, &config_logmsg_cmd); install_default(CONFIG_NODE); thread_cmd_init(); workqueue_cmd_init(); hash_cmd_init(); } install_element(CONFIG_NODE, &hostname_cmd); install_element(CONFIG_NODE, &no_hostname_cmd); install_element(CONFIG_NODE, &domainname_cmd); install_element(CONFIG_NODE, &no_domainname_cmd); install_element(CONFIG_NODE, &frr_version_defaults_cmd); if (terminal > 0) { install_element(CONFIG_NODE, &debug_memstats_cmd); install_element(CONFIG_NODE, &password_cmd); install_element(CONFIG_NODE, &no_password_cmd); install_element(CONFIG_NODE, &enable_password_cmd); install_element(CONFIG_NODE, &no_enable_password_cmd); install_element(CONFIG_NODE, &config_log_stdout_cmd); install_element(CONFIG_NODE, &no_config_log_stdout_cmd); install_element(CONFIG_NODE, &config_log_monitor_cmd); install_element(CONFIG_NODE, &no_config_log_monitor_cmd); install_element(CONFIG_NODE, &config_log_file_cmd); install_element(CONFIG_NODE, &no_config_log_file_cmd); install_element(CONFIG_NODE, &config_log_syslog_cmd); install_element(CONFIG_NODE, &no_config_log_syslog_cmd); install_element(CONFIG_NODE, &config_log_facility_cmd); install_element(CONFIG_NODE, &no_config_log_facility_cmd); install_element(CONFIG_NODE, &config_log_record_priority_cmd); install_element(CONFIG_NODE, &no_config_log_record_priority_cmd); install_element(CONFIG_NODE, &config_log_timestamp_precision_cmd); install_element(CONFIG_NODE, &no_config_log_timestamp_precision_cmd); install_element(CONFIG_NODE, &service_password_encrypt_cmd); install_element(CONFIG_NODE, &no_service_password_encrypt_cmd); install_element(CONFIG_NODE, &banner_motd_default_cmd); install_element(CONFIG_NODE, &banner_motd_file_cmd); install_element(CONFIG_NODE, &no_banner_motd_cmd); install_element(CONFIG_NODE, &service_terminal_length_cmd); install_element(CONFIG_NODE, &no_service_terminal_length_cmd); vrf_install_commands(); } #ifdef DEV_BUILD grammar_sandbox_init(); #endif } void cmd_terminate(void) { struct cmd_node *cmd_node; hook_unregister(cmd_execute, handle_pipe_action); hook_unregister(cmd_execute_done, handle_pipe_action_done); if (cmdvec) { for (unsigned int i = 0; i < vector_active(cmdvec); i++) if ((cmd_node = vector_slot(cmdvec, i)) != NULL) { // deleting the graph delets the cmd_element as // well graph_delete_graph(cmd_node->cmdgraph); vector_free(cmd_node->cmd_vector); hash_clean(cmd_node->cmd_hash, NULL); hash_free(cmd_node->cmd_hash); cmd_node->cmd_hash = NULL; } vector_free(cmdvec); cmdvec = NULL; } XFREE(MTYPE_HOST, host.name); XFREE(MTYPE_HOST, host.domainname); XFREE(MTYPE_HOST, host.password); XFREE(MTYPE_HOST, host.password_encrypt); XFREE(MTYPE_HOST, host.enable); XFREE(MTYPE_HOST, host.enable_encrypt); XFREE(MTYPE_HOST, host.logfile); XFREE(MTYPE_HOST, host.motdfile); XFREE(MTYPE_HOST, host.config); list_delete(&varhandlers); qobj_finish(); } frr-7.2.1/lib/command.h0000644000000000000000000005031513610377563011555 00000000000000/* * Zebra configuration command interface routine * Copyright (C) 1997, 98 Kunihiro Ishiguro * * This file is part of GNU Zebra. * * GNU Zebra is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License as published * by the Free Software Foundation; either version 2, or (at your * option) any later version. * * GNU Zebra 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; see the file COPYING; if not, write to the Free Software * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA */ #ifndef _ZEBRA_COMMAND_H #define _ZEBRA_COMMAND_H #include "vector.h" #include "vty.h" #include "lib/route_types.h" #include "graph.h" #include "memory.h" #include "hash.h" #include "command_graph.h" #ifdef __cplusplus extern "C" { #endif DECLARE_MTYPE(COMPLETION) /* * From RFC 1123 (Requirements for Internet Hosts), Section 2.1 on hostnames: * One aspect of host name syntax is hereby changed: the restriction on * the first character is relaxed to allow either a letter or a digit. * Host software MUST support this more liberal syntax. * * Host software MUST handle host names of up to 63 characters and * SHOULD handle host names of up to 255 characters. */ #define HOSTNAME_LEN 255 /* Host configuration variable */ struct host { /* Host name of this router. */ char *name; /* Domainname of this router */ char *domainname; /* Password for vty interface. */ char *password; char *password_encrypt; /* Enable password */ char *enable; char *enable_encrypt; /* System wide terminal lines. */ int lines; /* Log filename. */ char *logfile; /* config file name of this host */ char *config; int noconfig; /* Flags for services */ int advanced; int encrypt; /* Banner configuration. */ const char *motd; char *motdfile; }; /* List of CLI nodes. Please remember to update the name array in command.c. */ enum node_type { AUTH_NODE, /* Authentication mode of vty interface. */ VIEW_NODE, /* View node. Default mode of vty interface. */ AUTH_ENABLE_NODE, /* Authentication mode for change enable. */ ENABLE_NODE, /* Enable node. */ CONFIG_NODE, /* Config node. Default mode of config file. */ DEBUG_NODE, /* Debug node. */ VRF_DEBUG_NODE, /* Vrf Debug node. */ NORTHBOUND_DEBUG_NODE, /* Northbound Debug node. */ DEBUG_VNC_NODE, /* Debug VNC node. */ RMAP_DEBUG_NODE, /* Route-map debug node */ RESOLVER_DEBUG_NODE, /* Resolver debug node */ AAA_NODE, /* AAA node. */ KEYCHAIN_NODE, /* Key-chain node. */ KEYCHAIN_KEY_NODE, /* Key-chain key node. */ IP_NODE, /* Static ip route node. */ VRF_NODE, /* VRF mode node. */ INTERFACE_NODE, /* Interface mode node. */ NH_GROUP_NODE, /* Nexthop-Group mode node. */ ZEBRA_NODE, /* zebra connection node. */ TABLE_NODE, /* rtm_table selection node. */ RIP_NODE, /* RIP protocol mode node. */ RIPNG_NODE, /* RIPng protocol mode node. */ BABEL_NODE, /* BABEL protocol mode node. */ EIGRP_NODE, /* EIGRP protocol mode node. */ BGP_NODE, /* BGP protocol mode which includes BGP4+ */ BGP_VPNV4_NODE, /* BGP MPLS-VPN PE exchange. */ BGP_VPNV6_NODE, /* BGP MPLS-VPN PE exchange. */ BGP_IPV4_NODE, /* BGP IPv4 unicast address family. */ BGP_IPV4M_NODE, /* BGP IPv4 multicast address family. */ BGP_IPV4L_NODE, /* BGP IPv4 labeled unicast address family. */ BGP_IPV6_NODE, /* BGP IPv6 address family */ BGP_IPV6M_NODE, /* BGP IPv6 multicast address family. */ BGP_IPV6L_NODE, /* BGP IPv6 labeled unicast address family. */ BGP_VRF_POLICY_NODE, /* BGP VRF policy */ BGP_VNC_DEFAULTS_NODE, /* BGP VNC nve defaults */ BGP_VNC_NVE_GROUP_NODE, /* BGP VNC nve group */ BGP_VNC_L2_GROUP_NODE, /* BGP VNC L2 group */ RFP_DEFAULTS_NODE, /* RFP defaults node */ BGP_EVPN_NODE, /* BGP EVPN node. */ OSPF_NODE, /* OSPF protocol mode */ OSPF6_NODE, /* OSPF protocol for IPv6 mode */ LDP_NODE, /* LDP protocol mode */ LDP_IPV4_NODE, /* LDP IPv4 address family */ LDP_IPV6_NODE, /* LDP IPv6 address family */ LDP_IPV4_IFACE_NODE, /* LDP IPv4 Interface */ LDP_IPV6_IFACE_NODE, /* LDP IPv6 Interface */ LDP_L2VPN_NODE, /* LDP L2VPN node */ LDP_PSEUDOWIRE_NODE, /* LDP Pseudowire node */ ISIS_NODE, /* ISIS protocol mode */ ACCESS_NODE, /* Access list node. */ PREFIX_NODE, /* Prefix list node. */ ACCESS_IPV6_NODE, /* Access list node. */ ACCESS_MAC_NODE, /* MAC access list node*/ PREFIX_IPV6_NODE, /* Prefix list node. */ AS_LIST_NODE, /* AS list node. */ COMMUNITY_LIST_NODE, /* Community list node. */ RMAP_NODE, /* Route map node. */ PBRMAP_NODE, /* PBR map node. */ SMUX_NODE, /* SNMP configuration node. */ DUMP_NODE, /* Packet dump node. */ FORWARDING_NODE, /* IP forwarding node. */ PROTOCOL_NODE, /* protocol filtering node */ MPLS_NODE, /* MPLS config node */ PW_NODE, /* Pseudowire config node */ VTY_NODE, /* Vty node. */ LINK_PARAMS_NODE, /* Link-parameters node */ BGP_EVPN_VNI_NODE, /* BGP EVPN VNI */ RPKI_NODE, /* RPKI node for configuration of RPKI cache server connections.*/ BGP_FLOWSPECV4_NODE, /* BGP IPv4 FLOWSPEC Address-Family */ BGP_FLOWSPECV6_NODE, /* BGP IPv6 FLOWSPEC Address-Family */ BFD_NODE, /* BFD protocol mode. */ BFD_PEER_NODE, /* BFD peer configuration mode. */ OPENFABRIC_NODE, /* OpenFabric router configuration node */ VRRP_NODE, /* VRRP node */ BMP_NODE, /* BMP config under router bgp */ NODE_TYPE_MAX, /* maximum */ }; extern vector cmdvec; extern const struct message tokennames[]; extern const char *node_names[]; /* Node which has some commands and prompt string and configuration function pointer . */ struct cmd_node { /* Node index. */ enum node_type node; /* Prompt character at vty interface. */ const char *prompt; /* Is this node's configuration goes to vtysh ? */ int vtysh; /* Node's configuration write function */ int (*func)(struct vty *); /* Node's command graph */ struct graph *cmdgraph; /* Vector of this node's command list. */ vector cmd_vector; /* Hashed index of command node list, for de-dupping primarily */ struct hash *cmd_hash; }; /* Return value of the commands. */ #define CMD_SUCCESS 0 #define CMD_WARNING 1 #define CMD_ERR_NO_MATCH 2 #define CMD_ERR_AMBIGUOUS 3 #define CMD_ERR_INCOMPLETE 4 #define CMD_ERR_EXEED_ARGC_MAX 5 #define CMD_ERR_NOTHING_TODO 6 #define CMD_COMPLETE_FULL_MATCH 7 #define CMD_COMPLETE_MATCH 8 #define CMD_COMPLETE_LIST_MATCH 9 #define CMD_SUCCESS_DAEMON 10 #define CMD_ERR_NO_FILE 11 #define CMD_SUSPEND 12 #define CMD_WARNING_CONFIG_FAILED 13 #define CMD_NOT_MY_INSTANCE 14 /* Argc max counts. */ #define CMD_ARGC_MAX 256 /* Turn off these macros when uisng cpp with extract.pl */ #ifndef VTYSH_EXTRACT_PL /* helper defines for end-user DEFUN* macros */ #define DEFUN_CMD_ELEMENT(funcname, cmdname, cmdstr, helpstr, attrs, dnum) \ static struct cmd_element cmdname = { \ .string = cmdstr, \ .func = funcname, \ .doc = helpstr, \ .attr = attrs, \ .daemon = dnum, \ .name = #cmdname, \ }; #define DEFUN_CMD_FUNC_DECL(funcname) \ static int funcname(const struct cmd_element *, struct vty *, int, \ struct cmd_token *[]); #define DEFUN_CMD_FUNC_TEXT(funcname) \ static int funcname(const struct cmd_element *self \ __attribute__((unused)), \ struct vty *vty __attribute__((unused)), \ int argc __attribute__((unused)), \ struct cmd_token *argv[] __attribute__((unused))) #define DEFPY(funcname, cmdname, cmdstr, helpstr) \ DEFUN_CMD_ELEMENT(funcname, cmdname, cmdstr, helpstr, 0, 0) \ funcdecl_##funcname #define DEFPY_NOSH(funcname, cmdname, cmdstr, helpstr) \ DEFPY(funcname, cmdname, cmdstr, helpstr) #define DEFPY_ATTR(funcname, cmdname, cmdstr, helpstr, attr) \ DEFUN_CMD_ELEMENT(funcname, cmdname, cmdstr, helpstr, attr, 0) \ funcdecl_##funcname #define DEFPY_HIDDEN(funcname, cmdname, cmdstr, helpstr) \ DEFPY_ATTR(funcname, cmdname, cmdstr, helpstr, CMD_ATTR_HIDDEN) #define DEFUN(funcname, cmdname, cmdstr, helpstr) \ DEFUN_CMD_FUNC_DECL(funcname) \ DEFUN_CMD_ELEMENT(funcname, cmdname, cmdstr, helpstr, 0, 0) \ DEFUN_CMD_FUNC_TEXT(funcname) #define DEFUN_ATTR(funcname, cmdname, cmdstr, helpstr, attr) \ DEFUN_CMD_FUNC_DECL(funcname) \ DEFUN_CMD_ELEMENT(funcname, cmdname, cmdstr, helpstr, attr, 0) \ DEFUN_CMD_FUNC_TEXT(funcname) #define DEFUN_HIDDEN(funcname, cmdname, cmdstr, helpstr) \ DEFUN_ATTR(funcname, cmdname, cmdstr, helpstr, CMD_ATTR_HIDDEN) /* DEFUN_NOSH for commands that vtysh should ignore */ #define DEFUN_NOSH(funcname, cmdname, cmdstr, helpstr) \ DEFUN(funcname, cmdname, cmdstr, helpstr) /* DEFSH for vtysh. */ #define DEFSH(daemon, cmdname, cmdstr, helpstr) \ DEFUN_CMD_ELEMENT(NULL, cmdname, cmdstr, helpstr, 0, daemon) #define DEFSH_HIDDEN(daemon, cmdname, cmdstr, helpstr) \ DEFUN_CMD_ELEMENT(NULL, cmdname, cmdstr, helpstr, CMD_ATTR_HIDDEN, \ daemon) /* DEFUN + DEFSH */ #define DEFUNSH(daemon, funcname, cmdname, cmdstr, helpstr) \ DEFUN_CMD_FUNC_DECL(funcname) \ DEFUN_CMD_ELEMENT(funcname, cmdname, cmdstr, helpstr, 0, daemon) \ DEFUN_CMD_FUNC_TEXT(funcname) /* DEFUN + DEFSH with attributes */ #define DEFUNSH_ATTR(daemon, funcname, cmdname, cmdstr, helpstr, attr) \ DEFUN_CMD_FUNC_DECL(funcname) \ DEFUN_CMD_ELEMENT(funcname, cmdname, cmdstr, helpstr, attr, daemon) \ DEFUN_CMD_FUNC_TEXT(funcname) #define DEFUNSH_HIDDEN(daemon, funcname, cmdname, cmdstr, helpstr) \ DEFUNSH_ATTR(daemon, funcname, cmdname, cmdstr, helpstr, \ CMD_ATTR_HIDDEN) #define DEFUNSH_DEPRECATED(daemon, funcname, cmdname, cmdstr, helpstr) \ DEFUNSH_ATTR(daemon, funcname, cmdname, cmdstr, helpstr, \ CMD_ATTR_DEPRECATED) /* ALIAS macro which define existing command's alias. */ #define ALIAS(funcname, cmdname, cmdstr, helpstr) \ DEFUN_CMD_ELEMENT(funcname, cmdname, cmdstr, helpstr, 0, 0) #define ALIAS_ATTR(funcname, cmdname, cmdstr, helpstr, attr) \ DEFUN_CMD_ELEMENT(funcname, cmdname, cmdstr, helpstr, attr, 0) #define ALIAS_HIDDEN(funcname, cmdname, cmdstr, helpstr) \ DEFUN_CMD_ELEMENT(funcname, cmdname, cmdstr, helpstr, CMD_ATTR_HIDDEN, \ 0) #define ALIAS_DEPRECATED(funcname, cmdname, cmdstr, helpstr) \ DEFUN_CMD_ELEMENT(funcname, cmdname, cmdstr, helpstr, \ CMD_ATTR_DEPRECATED, 0) #define ALIAS_SH(daemon, funcname, cmdname, cmdstr, helpstr) \ DEFUN_CMD_ELEMENT(funcname, cmdname, cmdstr, helpstr, 0, daemon) #define ALIAS_SH_HIDDEN(daemon, funcname, cmdname, cmdstr, helpstr) \ DEFUN_CMD_ELEMENT(funcname, cmdname, cmdstr, helpstr, CMD_ATTR_HIDDEN, \ daemon) #define ALIAS_SH_DEPRECATED(daemon, funcname, cmdname, cmdstr, helpstr) \ DEFUN_CMD_ELEMENT(funcname, cmdname, cmdstr, helpstr, \ CMD_ATTR_DEPRECATED, daemon) #else /* VTYSH_EXTRACT_PL */ #define DEFPY(funcname, cmdname, cmdstr, helpstr) \ DEFUN(funcname, cmdname, cmdstr, helpstr) #define DEFPY_NOSH(funcname, cmdname, cmdstr, helpstr) \ DEFUN_NOSH(funcname, cmdname, cmdstr, helpstr) #define DEFPY_ATTR(funcname, cmdname, cmdstr, helpstr, attr) \ DEFUN_ATTR(funcname, cmdname, cmdstr, helpstr, attr) #define DEFPY_HIDDEN(funcname, cmdname, cmdstr, helpstr) \ DEFUN_HIDDEN(funcname, cmdname, cmdstr, helpstr) #endif /* VTYSH_EXTRACT_PL */ /* Some macroes */ /* * Sometimes #defines create maximum values that * need to have strings created from them that * allow the parser to match against them. * These macros allow that. */ #define CMD_CREATE_STR(s) CMD_CREATE_STR_HELPER(s) #define CMD_CREATE_STR_HELPER(s) #s #define CMD_RANGE_STR(a,s) "(" CMD_CREATE_STR(a) "-" CMD_CREATE_STR(s) ")" /* Common descriptions. */ #define SHOW_STR "Show running system information\n" #define IP_STR "IP information\n" #define IPV6_STR "IPv6 information\n" #define NO_STR "Negate a command or set its defaults\n" #define REDIST_STR "Redistribute information from another routing protocol\n" #define CLEAR_STR "Reset functions\n" #define RIP_STR "RIP information\n" #define EIGRP_STR "EIGRP information\n" #define BGP_STR "BGP information\n" #define BGP_SOFT_STR "Soft reconfig inbound and outbound updates\n" #define BGP_SOFT_IN_STR "Send route-refresh unless using 'soft-reconfiguration inbound'\n" #define BGP_SOFT_OUT_STR "Resend all outbound updates\n" #define BGP_SOFT_RSCLIENT_RIB_STR "Soft reconfig for rsclient RIB\n" #define OSPF_STR "OSPF information\n" #define NEIGHBOR_STR "Specify neighbor router\n" #define DEBUG_STR "Debugging functions\n" #define UNDEBUG_STR "Disable debugging functions (see also 'debug')\n" #define ROUTER_STR "Enable a routing process\n" #define AS_STR "AS number\n" #define MAC_STR "MAC address\n" #define MBGP_STR "MBGP information\n" #define MATCH_STR "Match values from routing table\n" #define SET_STR "Set values in destination routing protocol\n" #define OUT_STR "Filter outgoing routing updates\n" #define IN_STR "Filter incoming routing updates\n" #define V4NOTATION_STR "specify by IPv4 address notation(e.g. 0.0.0.0)\n" #define OSPF6_NUMBER_STR "Specify by number\n" #define INTERFACE_STR "Interface information\n" #define IFNAME_STR "Interface name(e.g. ep0)\n" #define IP6_STR "IPv6 Information\n" #define OSPF6_STR "Open Shortest Path First (OSPF) for IPv6\n" #define OSPF6_INSTANCE_STR "(1-65535) Instance ID\n" #define SECONDS_STR "Seconds\n" #define ROUTE_STR "Routing Table\n" #define PREFIX_LIST_STR "Build a prefix list\n" #define OSPF6_DUMP_TYPE_LIST \ "" #define AREA_TAG_STR "[area tag]\n" #define COMMUNITY_AANN_STR "Community number where AA and NN are (0-65535)\n" #define COMMUNITY_VAL_STR "Community number in AA:NN format (where AA and NN are (0-65535)) or local-AS|no-advertise|no-export|internet or additive\n" #define MPLS_TE_STR "MPLS-TE specific commands\n" #define LINK_PARAMS_STR "Configure interface link parameters\n" #define OSPF_RI_STR "OSPF Router Information specific commands\n" #define PCE_STR "PCE Router Information specific commands\n" #define MPLS_STR "MPLS information\n" #define SR_STR "Segment-Routing specific commands\n" #define WATCHFRR_STR "watchfrr information\n" #define ZEBRA_STR "Zebra information\n" #define FILTER_LOG_STR "Filter Logs\n" #define CMD_VNI_RANGE "(1-16777215)" #define CONF_BACKUP_EXT ".sav" /* Command warnings. */ #define NO_PASSWD_CMD_WARNING \ "Please be aware that removing the password is a security risk and you should think twice about this command.\n" /* IPv4 only machine should not accept IPv6 address for peer's IP address. So we replace VTY command string like below. */ #define NEIGHBOR_ADDR_STR "Neighbor address\nIPv6 address\n" #define NEIGHBOR_ADDR_STR2 "Neighbor address\nNeighbor IPv6 address\nInterface name or neighbor tag\n" #define NEIGHBOR_ADDR_STR3 "Neighbor address\nIPv6 address\nInterface name\n" /* Dameons lists */ #define DAEMONS_STR \ "For the zebra daemon\nFor the rip daemon\nFor the ripng daemon\nFor the ospf daemon\nFor the ospfv6 daemon\nFor the bgp daemon\nFor the isis daemon\nFor the pbr daemon\nFor the fabricd daemon\nFor the pim daemon\nFor the static daemon\nFor the sharpd daemon\nFor the vrrpd daemon\n" #define DAEMONS_LIST \ "" /* Prototypes. */ extern void install_node(struct cmd_node *, int (*)(struct vty *)); extern void install_default(enum node_type); extern void install_element(enum node_type, struct cmd_element *); /* known issue with uninstall_element: changes to cmd_token->attr (i.e. * deprecated/hidden) are not reversed. */ extern void uninstall_element(enum node_type, struct cmd_element *); /* Concatenates argv[shift] through argv[argc-1] into a single NUL-terminated string with a space between each element (allocated using XMALLOC(MTYPE_TMP)). Returns NULL if shift >= argc. */ extern char *argv_concat(struct cmd_token **argv, int argc, int shift); /* * It is preferred that you set the index initial value * to a 0. This way in the future if you modify the * cli then there is no need to modify the initial * value of the index */ extern int argv_find(struct cmd_token **argv, int argc, const char *text, int *index); extern vector cmd_make_strvec(const char *); extern void cmd_free_strvec(vector); extern vector cmd_describe_command(vector, struct vty *, int *status); extern char **cmd_complete_command(vector, struct vty *, int *status); extern const char *cmd_prompt(enum node_type); extern int command_config_read_one_line(struct vty *vty, const struct cmd_element **, uint32_t line_num, int use_config_node); extern int config_from_file(struct vty *, FILE *, unsigned int *line_num); extern enum node_type node_parent(enum node_type); /* * Execute command under the given vty context. * * vty * The vty context to execute under. * * cmd * The command string to execute. * * matched * If non-null and a match was found, the address of the matched command is * stored here. No action otherwise. * * vtysh * Whether or not this is being called from vtysh. If this is nonzero, * XXX: then what? * * Returns: * XXX: what does it return */ extern int cmd_execute(struct vty *vty, const char *cmd, const struct cmd_element **matched, int vtysh); extern int cmd_execute_command(vector, struct vty *, const struct cmd_element **, int); extern int cmd_execute_command_strict(vector, struct vty *, const struct cmd_element **); extern void cmd_init(int); extern void cmd_terminate(void); extern void cmd_exit(struct vty *vty); extern int cmd_list_cmds(struct vty *vty, int do_permute); extern int cmd_domainname_set(const char *domainname); extern int cmd_hostname_set(const char *hostname); extern const char *cmd_hostname_get(void); extern const char *cmd_domainname_get(void); /* NOT safe for general use; call this only if DEV_BUILD! */ extern void grammar_sandbox_init(void); extern vector completions_to_vec(struct list *completions); /* Export typical functions. */ extern const char *host_config_get(void); extern void host_config_set(const char *); extern void print_version(const char *); extern int cmd_banner_motd_file(const char *); /* struct host global, ick */ extern struct host host; struct cmd_variable_handler { const char *tokenname, *varname; void (*completions)(vector out, struct cmd_token *token); }; extern void cmd_variable_complete(struct cmd_token *token, const char *arg, vector comps); extern void cmd_variable_handler_register(const struct cmd_variable_handler *cvh); extern char *cmd_variable_comp2str(vector comps, unsigned short cols); extern void command_setup_early_logging(const char *dest, const char *level); #ifdef __cplusplus } #endif #endif /* _ZEBRA_COMMAND_H */ frr-7.2.1/lib/command_graph.c0000644000000000000000000003421213610377563012727 00000000000000/* * CLI graph handling * * -- * Copyright (C) 2016 Cumulus Networks, Inc. * Copyright (C) 1997, 98, 99 Kunihiro Ishiguro * Copyright (C) 2013 by Open Source Routing. * Copyright (C) 2013 by Internet Systems Consortium, Inc. ("ISC") * * This program is free software; you can redistribute it and/or modify it * under the terms of the 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; see the file COPYING; if not, write to the Free Software * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA */ #include #include "command_graph.h" DEFINE_MTYPE_STATIC(LIB, CMD_TOKENS, "Command Tokens") DEFINE_MTYPE_STATIC(LIB, CMD_DESC, "Command Token Text") DEFINE_MTYPE_STATIC(LIB, CMD_TEXT, "Command Token Help") DEFINE_MTYPE(LIB, CMD_ARG, "Command Argument") DEFINE_MTYPE_STATIC(LIB, CMD_VAR, "Command Argument Name") struct cmd_token *cmd_token_new(enum cmd_token_type type, uint8_t attr, const char *text, const char *desc) { struct cmd_token *token = XCALLOC(MTYPE_CMD_TOKENS, sizeof(struct cmd_token)); token->type = type; token->attr = attr; token->text = text ? XSTRDUP(MTYPE_CMD_TEXT, text) : NULL; token->desc = desc ? XSTRDUP(MTYPE_CMD_DESC, desc) : NULL; token->refcnt = 1; token->arg = NULL; token->allowrepeat = false; token->varname = NULL; return token; } void cmd_token_del(struct cmd_token *token) { if (!token) return; XFREE(MTYPE_CMD_TEXT, token->text); XFREE(MTYPE_CMD_DESC, token->desc); XFREE(MTYPE_CMD_ARG, token->arg); XFREE(MTYPE_CMD_VAR, token->varname); XFREE(MTYPE_CMD_TOKENS, token); } struct cmd_token *cmd_token_dup(struct cmd_token *token) { struct cmd_token *copy = cmd_token_new(token->type, token->attr, NULL, NULL); copy->max = token->max; copy->min = token->min; copy->text = token->text ? XSTRDUP(MTYPE_CMD_TEXT, token->text) : NULL; copy->desc = token->desc ? XSTRDUP(MTYPE_CMD_DESC, token->desc) : NULL; copy->arg = token->arg ? XSTRDUP(MTYPE_CMD_ARG, token->arg) : NULL; copy->varname = token->varname ? XSTRDUP(MTYPE_CMD_VAR, token->varname) : NULL; return copy; } void cmd_token_varname_set(struct cmd_token *token, const char *varname) { XFREE(MTYPE_CMD_VAR, token->varname); if (!varname) { token->varname = NULL; return; } size_t len = strlen(varname), i; token->varname = XMALLOC(MTYPE_CMD_VAR, len + 1); for (i = 0; i < len; i++) switch (varname[i]) { case '-': case '+': case '*': case ':': token->varname[i] = '_'; break; default: token->varname[i] = tolower((unsigned char)varname[i]); } token->varname[len] = '\0'; } static bool cmd_nodes_link(struct graph_node *from, struct graph_node *to) { for (size_t i = 0; i < vector_active(from->to); i++) if (vector_slot(from->to, i) == to) return true; return false; } static bool cmd_nodes_equal(struct graph_node *ga, struct graph_node *gb); /* returns a single node to be excluded as "next" from iteration * - for JOIN_TKN, never continue back to the FORK_TKN * - in all other cases, don't try the node itself (in case of "...") */ static inline struct graph_node *cmd_loopstop(struct graph_node *gn) { struct cmd_token *tok = gn->data; if (tok->type == JOIN_TKN) return tok->forkjoin; else return gn; } static bool cmd_subgraph_equal(struct graph_node *ga, struct graph_node *gb, struct graph_node *a_join) { size_t i, j; struct graph_node *a_fork, *b_fork; a_fork = cmd_loopstop(ga); b_fork = cmd_loopstop(gb); if (vector_active(ga->to) != vector_active(gb->to)) return false; for (i = 0; i < vector_active(ga->to); i++) { struct graph_node *cga = vector_slot(ga->to, i); for (j = 0; j < vector_active(gb->to); j++) { struct graph_node *cgb = vector_slot(gb->to, i); if (cga == a_fork && cgb != b_fork) continue; if (cga == a_fork && cgb == b_fork) break; if (cmd_nodes_equal(cga, cgb)) { if (cga == a_join) break; if (cmd_subgraph_equal(cga, cgb, a_join)) break; } } if (j == vector_active(gb->to)) return false; } return true; } /* deep compare -- for FORK_TKN, the entire subgraph is compared. * this is what's needed since we're not currently trying to partially * merge subgraphs */ static bool cmd_nodes_equal(struct graph_node *ga, struct graph_node *gb) { struct cmd_token *a = ga->data, *b = gb->data; if (a->type != b->type || a->allowrepeat != b->allowrepeat) return false; if (a->type < SPECIAL_TKN && strcmp(a->text, b->text)) return false; /* one a ..., the other not. */ if (cmd_nodes_link(ga, ga) != cmd_nodes_link(gb, gb)) return false; if (!a->varname != !b->varname) return false; if (a->varname && strcmp(a->varname, b->varname)) return false; switch (a->type) { case RANGE_TKN: return a->min == b->min && a->max == b->max; case FORK_TKN: /* one is keywords, the other just option or selector ... */ if (cmd_nodes_link(a->forkjoin, ga) != cmd_nodes_link(b->forkjoin, gb)) return false; if (cmd_nodes_link(ga, a->forkjoin) != cmd_nodes_link(gb, b->forkjoin)) return false; return cmd_subgraph_equal(ga, gb, a->forkjoin); default: return true; } } static void cmd_fork_bump_attr(struct graph_node *gn, struct graph_node *join, uint8_t attr) { size_t i; struct cmd_token *tok = gn->data; struct graph_node *stop = cmd_loopstop(gn); tok->attr = attr; for (i = 0; i < vector_active(gn->to); i++) { struct graph_node *next = vector_slot(gn->to, i); if (next == stop || next == join) continue; cmd_fork_bump_attr(next, join, attr); } } /* move an entire subtree from the temporary graph resulting from * parse() into the permanent graph for the command node. * * this touches rather deeply into the graph code unfortunately. */ static void cmd_reparent_tree(struct graph *fromgraph, struct graph *tograph, struct graph_node *node) { struct graph_node *stop = cmd_loopstop(node); size_t i; for (i = 0; i < vector_active(fromgraph->nodes); i++) if (vector_slot(fromgraph->nodes, i) == node) { /* agressive iteration punching through subgraphs - may * hit some * nodes twice. reparent only if found on old graph */ vector_unset(fromgraph->nodes, i); vector_set(tograph->nodes, node); break; } for (i = 0; i < vector_active(node->to); i++) { struct graph_node *next = vector_slot(node->to, i); if (next != stop) cmd_reparent_tree(fromgraph, tograph, next); } } static void cmd_free_recur(struct graph *graph, struct graph_node *node, struct graph_node *stop) { struct graph_node *next, *nstop; for (size_t i = vector_active(node->to); i; i--) { next = vector_slot(node->to, i - 1); if (next == stop) continue; nstop = cmd_loopstop(next); if (nstop != next) cmd_free_recur(graph, next, nstop); cmd_free_recur(graph, nstop, stop); } graph_delete_node(graph, node); } static void cmd_free_node(struct graph *graph, struct graph_node *node) { struct cmd_token *tok = node->data; if (tok->type == JOIN_TKN) cmd_free_recur(graph, tok->forkjoin, node); graph_delete_node(graph, node); } /* recursive graph merge. call with * old ~= new * (which holds true for old == START_TKN, new == START_TKN) */ static void cmd_merge_nodes(struct graph *oldgraph, struct graph *newgraph, struct graph_node *old, struct graph_node *new, int direction) { struct cmd_token *tok; struct graph_node *old_skip, *new_skip; old_skip = cmd_loopstop(old); new_skip = cmd_loopstop(new); assert(direction == 1 || direction == -1); tok = old->data; tok->refcnt += direction; size_t j, i; for (j = 0; j < vector_active(new->to); j++) { struct graph_node *cnew = vector_slot(new->to, j); if (cnew == new_skip) continue; for (i = 0; i < vector_active(old->to); i++) { struct graph_node *cold = vector_slot(old->to, i); if (cold == old_skip) continue; if (cmd_nodes_equal(cold, cnew)) { struct cmd_token *told = cold->data, *tnew = cnew->data; if (told->type == END_TKN) { if (direction < 0) { graph_delete_node( oldgraph, vector_slot(cold->to, 0)); graph_delete_node(oldgraph, cold); } else /* force no-match handling to * install END_TKN */ i = vector_active(old->to); break; } /* the entire fork compared as equal, we * continue after it. */ if (told->type == FORK_TKN) { if (tnew->attr < told->attr && direction > 0) cmd_fork_bump_attr( cold, told->forkjoin, tnew->attr); /* XXX: no reverse bump on uninstall */ told = (cold = told->forkjoin)->data; tnew = (cnew = tnew->forkjoin)->data; } if (tnew->attr < told->attr) told->attr = tnew->attr; cmd_merge_nodes(oldgraph, newgraph, cold, cnew, direction); break; } } /* nothing found => add new to old */ if (i == vector_active(old->to) && direction > 0) { graph_remove_edge(new, cnew); cmd_reparent_tree(newgraph, oldgraph, cnew); graph_add_edge(old, cnew); } } if (!tok->refcnt) cmd_free_node(oldgraph, old); } void cmd_graph_merge(struct graph *old, struct graph *new, int direction) { assert(vector_active(old->nodes) >= 1); assert(vector_active(new->nodes) >= 1); cmd_merge_nodes(old, new, vector_slot(old->nodes, 0), vector_slot(new->nodes, 0), direction); } static void cmd_node_names(struct graph_node *gn, struct graph_node *join, const char *prevname) { size_t i; struct cmd_token *tok = gn->data, *jointok; struct graph_node *stop = cmd_loopstop(gn); switch (tok->type) { case WORD_TKN: prevname = tok->text; break; case VARIABLE_TKN: if (!tok->varname && strcmp(tok->text, "WORD") && strcmp(tok->text, "NAME")) cmd_token_varname_set(tok, tok->text); /* fallthrough */ case RANGE_TKN: case IPV4_TKN: case IPV4_PREFIX_TKN: case IPV6_TKN: case IPV6_PREFIX_TKN: case MAC_TKN: case MAC_PREFIX_TKN: if (!tok->varname && prevname) cmd_token_varname_set(tok, prevname); prevname = NULL; break; case START_TKN: case JOIN_TKN: /* " WORD" -> word is not "bar" or "foo" */ prevname = NULL; break; case FORK_TKN: /* apply "$name" */ jointok = tok->forkjoin->data; if (!jointok->varname) break; for (i = 0; i < vector_active(tok->forkjoin->from); i++) { struct graph_node *tail = vector_slot(tok->forkjoin->from, i); struct cmd_token *tailtok = tail->data; if (tail == gn || tailtok->varname) continue; cmd_token_varname_set(tailtok, jointok->varname); } break; case END_TKN: return; } for (i = 0; i < vector_active(gn->to); i++) { struct graph_node *next = vector_slot(gn->to, i); if (next == stop || next == join) continue; cmd_node_names(next, join, prevname); } if (tok->type == FORK_TKN && tok->forkjoin != join) cmd_node_names(tok->forkjoin, join, NULL); } void cmd_graph_names(struct graph *graph) { struct graph_node *start; assert(vector_active(graph->nodes) >= 1); start = vector_slot(graph->nodes, 0); /* apply varname on initial "[no]" */ do { if (vector_active(start->to) != 1) break; struct graph_node *first = vector_slot(start->to, 0); struct cmd_token *tok = first->data; /* looking for an option with 2 choices, nothing or "no" */ if (tok->type != FORK_TKN || vector_active(first->to) != 2) break; struct graph_node *next0 = vector_slot(first->to, 0); struct graph_node *next1 = vector_slot(first->to, 1); /* one needs to be empty */ if (next0 != tok->forkjoin && next1 != tok->forkjoin) break; struct cmd_token *tok0 = next0->data; struct cmd_token *tok1 = next1->data; /* the other one needs to be "no" (only one will match here) */ if ((tok0->type == WORD_TKN && !strcmp(tok0->text, "no"))) cmd_token_varname_set(tok0, "no"); if ((tok1->type == WORD_TKN && !strcmp(tok1->text, "no"))) cmd_token_varname_set(tok1, "no"); } while (0); cmd_node_names(start, NULL, NULL); } #ifndef BUILDING_CLIPPY #include "command.h" #include "log.h" void cmd_graph_node_print_cb(struct graph_node *gn, struct buffer *buf) { static bool wasend; char nbuf[512]; struct cmd_token *tok = gn->data; const char *color; if (wasend) { wasend = false; return; } if (tok->type == END_TKN) { wasend = true; return; } snprintf(nbuf, sizeof(nbuf), " n%p [ shape=box, label=<", gn); buffer_putstr(buf, nbuf); snprintf(nbuf, sizeof(nbuf), "%s", lookup_msg(tokennames, tok->type, NULL)); buffer_putstr(buf, nbuf); if (tok->attr == CMD_ATTR_DEPRECATED) buffer_putstr(buf, " (d)"); else if (tok->attr == CMD_ATTR_HIDDEN) buffer_putstr(buf, " (h)"); if (tok->text) { if (tok->type == WORD_TKN) snprintf( nbuf, sizeof(nbuf), "
\"%s\"", tok->text); else snprintf(nbuf, sizeof(nbuf), "
%s", tok->text); buffer_putstr(buf, nbuf); } switch (tok->type) { case START_TKN: color = "#ccffcc"; break; case FORK_TKN: color = "#aaddff"; break; case JOIN_TKN: color = "#ddaaff"; break; case WORD_TKN: color = "#ffffff"; break; default: color = "#ffffff"; break; } snprintf(nbuf, sizeof(nbuf), ">, style = filled, fillcolor = \"%s\" ];\n", color); buffer_putstr(buf, nbuf); for (unsigned int i = 0; i < vector_active(gn->to); i++) { struct graph_node *adj = vector_slot(gn->to, i); if (((struct cmd_token *)adj->data)->type == END_TKN) { snprintf(nbuf, sizeof(nbuf), " n%p -> end%p;\n", gn, adj); buffer_putstr(buf, nbuf); snprintf( nbuf, sizeof(nbuf), " end%p [ shape=box, label=, style = filled, fillcolor = \"#ffddaa\" ];\n", adj); } else snprintf(nbuf, sizeof(nbuf), " n%p -> n%p;\n", gn, adj); buffer_putstr(buf, nbuf); } } char *cmd_graph_dump_dot(struct graph *cmdgraph) { struct graph_node *start = vector_slot(cmdgraph->nodes, 0); return graph_dump_dot(cmdgraph, start, cmd_graph_node_print_cb); } #endif /* BUILDING_CLIPPY */ frr-7.2.1/lib/command_graph.h0000644000000000000000000001002013610377563012723 00000000000000/* * CLI graph handling * * -- * Copyright (C) 2016 Cumulus Networks, Inc. * Copyright (C) 1997, 98, 99 Kunihiro Ishiguro * Copyright (C) 2013 by Open Source Routing. * Copyright (C) 2013 by Internet Systems Consortium, Inc. ("ISC") * * This program is free software; you can redistribute it and/or modify it * under the terms of the 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; see the file COPYING; if not, write to the Free Software * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA */ #ifndef _FRR_COMMAND_GRAPH_H #define _FRR_COMMAND_GRAPH_H #include #include #include "memory.h" #include "vector.h" #include "graph.h" #ifdef __cplusplus extern "C" { #endif DECLARE_MTYPE(CMD_ARG) struct vty; /** * Types for tokens. * * The type determines what kind of data the token can match (in the * matching use case) or hold (in the argv use case). */ /* clang-format off */ enum cmd_token_type { WORD_TKN, // words VARIABLE_TKN, // almost anything RANGE_TKN, // integer range IPV4_TKN, // IPV4 addresses IPV4_PREFIX_TKN, // IPV4 network prefixes IPV6_TKN, // IPV6 prefixes IPV6_PREFIX_TKN, // IPV6 network prefixes MAC_TKN, // Ethernet address MAC_PREFIX_TKN, // Ethernet address w/ CIDR mask /* plumbing types */ FORK_TKN, // marks subgraph beginning JOIN_TKN, // marks subgraph end START_TKN, // first token in line END_TKN, // last token in line SPECIAL_TKN = FORK_TKN, }; /* clang-format on */ #define IS_VARYING_TOKEN(x) ((x) >= VARIABLE_TKN && (x) < FORK_TKN) /* Command attributes */ enum { CMD_ATTR_NORMAL, CMD_ATTR_DEPRECATED, CMD_ATTR_HIDDEN, }; /* Comamand token struct. */ struct cmd_token { enum cmd_token_type type; // token type uint8_t attr; // token attributes bool allowrepeat; // matcher allowed to match token repetively? uint32_t refcnt; char *text; // token text char *desc; // token description long long min, max; // for ranges char *arg; // user input that matches this token char *varname; struct graph_node *forkjoin; // paired FORK/JOIN for JOIN/FORK }; /* Structure of command element. */ struct cmd_element { const char *string; /* Command specification by string. */ const char *doc; /* Documentation of this command. */ int daemon; /* Daemon to which this command belong. */ uint8_t attr; /* Command attributes */ /* handler function for command */ int (*func)(const struct cmd_element *, struct vty *, int, struct cmd_token *[]); const char *name; /* symbol name for debugging */ }; /* text for command */ #define CMD_CR_TEXT "" /* memory management for cmd_token */ extern struct cmd_token *cmd_token_new(enum cmd_token_type, uint8_t attr, const char *text, const char *desc); extern struct cmd_token *cmd_token_dup(struct cmd_token *); extern void cmd_token_del(struct cmd_token *); extern void cmd_token_varname_set(struct cmd_token *token, const char *varname); extern void cmd_graph_parse(struct graph *graph, struct cmd_element *cmd); extern void cmd_graph_names(struct graph *graph); extern void cmd_graph_merge(struct graph *old, struct graph *n, int direction); /* * Print callback for DOT dumping. * * See graph.h for more details. */ extern void cmd_graph_node_print_cb(struct graph_node *gn, struct buffer *buf); /* * Dump command graph to DOT. * * cmdgraph * A command graph to dump * * Returns: * String allocated with MTYPE_TMP representing this graph */ char *cmd_graph_dump_dot(struct graph *cmdgraph); #ifdef __cplusplus } #endif #endif /* _FRR_COMMAND_GRAPH_H */ frr-7.2.1/lib/command_lex.c0000644000000000000000000017717513610377563012436 00000000000000#line 2 "lib/command_lex.c" #ifdef HAVE_CONFIG_H #include "config.h" #endif #line 7 "lib/command_lex.c" #define YY_INT_ALIGNED short int /* A lexical scanner generated by flex */ #define FLEX_SCANNER #define YY_FLEX_MAJOR_VERSION 2 #define YY_FLEX_MINOR_VERSION 6 #define YY_FLEX_SUBMINOR_VERSION 4 #if YY_FLEX_SUBMINOR_VERSION > 0 #define FLEX_BETA #endif #ifdef yy_create_buffer #define cmd_yy_create_buffer_ALREADY_DEFINED #else #define yy_create_buffer cmd_yy_create_buffer #endif #ifdef yy_delete_buffer #define cmd_yy_delete_buffer_ALREADY_DEFINED #else #define yy_delete_buffer cmd_yy_delete_buffer #endif #ifdef yy_scan_buffer #define cmd_yy_scan_buffer_ALREADY_DEFINED #else #define yy_scan_buffer cmd_yy_scan_buffer #endif #ifdef yy_scan_string #define cmd_yy_scan_string_ALREADY_DEFINED #else #define yy_scan_string cmd_yy_scan_string #endif #ifdef yy_scan_bytes #define cmd_yy_scan_bytes_ALREADY_DEFINED #else #define yy_scan_bytes cmd_yy_scan_bytes #endif #ifdef yy_init_buffer #define cmd_yy_init_buffer_ALREADY_DEFINED #else #define yy_init_buffer cmd_yy_init_buffer #endif #ifdef yy_flush_buffer #define cmd_yy_flush_buffer_ALREADY_DEFINED #else #define yy_flush_buffer cmd_yy_flush_buffer #endif #ifdef yy_load_buffer_state #define cmd_yy_load_buffer_state_ALREADY_DEFINED #else #define yy_load_buffer_state cmd_yy_load_buffer_state #endif #ifdef yy_switch_to_buffer #define cmd_yy_switch_to_buffer_ALREADY_DEFINED #else #define yy_switch_to_buffer cmd_yy_switch_to_buffer #endif #ifdef yypush_buffer_state #define cmd_yypush_buffer_state_ALREADY_DEFINED #else #define yypush_buffer_state cmd_yypush_buffer_state #endif #ifdef yypop_buffer_state #define cmd_yypop_buffer_state_ALREADY_DEFINED #else #define yypop_buffer_state cmd_yypop_buffer_state #endif #ifdef yyensure_buffer_stack #define cmd_yyensure_buffer_stack_ALREADY_DEFINED #else #define yyensure_buffer_stack cmd_yyensure_buffer_stack #endif #ifdef yylex #define cmd_yylex_ALREADY_DEFINED #else #define yylex cmd_yylex #endif #ifdef yyrestart #define cmd_yyrestart_ALREADY_DEFINED #else #define yyrestart cmd_yyrestart #endif #ifdef yylex_init #define cmd_yylex_init_ALREADY_DEFINED #else #define yylex_init cmd_yylex_init #endif #ifdef yylex_init_extra #define cmd_yylex_init_extra_ALREADY_DEFINED #else #define yylex_init_extra cmd_yylex_init_extra #endif #ifdef yylex_destroy #define cmd_yylex_destroy_ALREADY_DEFINED #else #define yylex_destroy cmd_yylex_destroy #endif #ifdef yyget_debug #define cmd_yyget_debug_ALREADY_DEFINED #else #define yyget_debug cmd_yyget_debug #endif #ifdef yyset_debug #define cmd_yyset_debug_ALREADY_DEFINED #else #define yyset_debug cmd_yyset_debug #endif #ifdef yyget_extra #define cmd_yyget_extra_ALREADY_DEFINED #else #define yyget_extra cmd_yyget_extra #endif #ifdef yyset_extra #define cmd_yyset_extra_ALREADY_DEFINED #else #define yyset_extra cmd_yyset_extra #endif #ifdef yyget_in #define cmd_yyget_in_ALREADY_DEFINED #else #define yyget_in cmd_yyget_in #endif #ifdef yyset_in #define cmd_yyset_in_ALREADY_DEFINED #else #define yyset_in cmd_yyset_in #endif #ifdef yyget_out #define cmd_yyget_out_ALREADY_DEFINED #else #define yyget_out cmd_yyget_out #endif #ifdef yyset_out #define cmd_yyset_out_ALREADY_DEFINED #else #define yyset_out cmd_yyset_out #endif #ifdef yyget_leng #define cmd_yyget_leng_ALREADY_DEFINED #else #define yyget_leng cmd_yyget_leng #endif #ifdef yyget_text #define cmd_yyget_text_ALREADY_DEFINED #else #define yyget_text cmd_yyget_text #endif #ifdef yyget_lineno #define cmd_yyget_lineno_ALREADY_DEFINED #else #define yyget_lineno cmd_yyget_lineno #endif #ifdef yyset_lineno #define cmd_yyset_lineno_ALREADY_DEFINED #else #define yyset_lineno cmd_yyset_lineno #endif #ifdef yyget_column #define cmd_yyget_column_ALREADY_DEFINED #else #define yyget_column cmd_yyget_column #endif #ifdef yyset_column #define cmd_yyset_column_ALREADY_DEFINED #else #define yyset_column cmd_yyset_column #endif #ifdef yywrap #define cmd_yywrap_ALREADY_DEFINED #else #define yywrap cmd_yywrap #endif #ifdef yyget_lval #define cmd_yyget_lval_ALREADY_DEFINED #else #define yyget_lval cmd_yyget_lval #endif #ifdef yyset_lval #define cmd_yyset_lval_ALREADY_DEFINED #else #define yyset_lval cmd_yyset_lval #endif #ifdef yyget_lloc #define cmd_yyget_lloc_ALREADY_DEFINED #else #define yyget_lloc cmd_yyget_lloc #endif #ifdef yyset_lloc #define cmd_yyset_lloc_ALREADY_DEFINED #else #define yyset_lloc cmd_yyset_lloc #endif #ifdef yyalloc #define cmd_yyalloc_ALREADY_DEFINED #else #define yyalloc cmd_yyalloc #endif #ifdef yyrealloc #define cmd_yyrealloc_ALREADY_DEFINED #else #define yyrealloc cmd_yyrealloc #endif #ifdef yyfree #define cmd_yyfree_ALREADY_DEFINED #else #define yyfree cmd_yyfree #endif /* First, we deal with platform-specific or compiler-specific issues. */ /* begin standard C headers. */ #include #include #include #include /* end standard C headers. */ /* flex integer type definitions */ #ifndef FLEXINT_H #define FLEXINT_H /* C99 systems have . Non-C99 systems may or may not. */ #if defined (__STDC_VERSION__) && __STDC_VERSION__ >= 199901L /* C99 says to define __STDC_LIMIT_MACROS before including stdint.h, * if you want the limit (max/min) macros for int types. */ #ifndef __STDC_LIMIT_MACROS #define __STDC_LIMIT_MACROS 1 #endif #include typedef int8_t flex_int8_t; typedef uint8_t flex_uint8_t; typedef int16_t flex_int16_t; typedef uint16_t flex_uint16_t; typedef int32_t flex_int32_t; typedef uint32_t flex_uint32_t; #else typedef signed char flex_int8_t; typedef short int flex_int16_t; typedef int flex_int32_t; typedef unsigned char flex_uint8_t; typedef unsigned short int flex_uint16_t; typedef unsigned int flex_uint32_t; /* Limits of integral types. */ #ifndef INT8_MIN #define INT8_MIN (-128) #endif #ifndef INT16_MIN #define INT16_MIN (-32767-1) #endif #ifndef INT32_MIN #define INT32_MIN (-2147483647-1) #endif #ifndef INT8_MAX #define INT8_MAX (127) #endif #ifndef INT16_MAX #define INT16_MAX (32767) #endif #ifndef INT32_MAX #define INT32_MAX (2147483647) #endif #ifndef UINT8_MAX #define UINT8_MAX (255U) #endif #ifndef UINT16_MAX #define UINT16_MAX (65535U) #endif #ifndef UINT32_MAX #define UINT32_MAX (4294967295U) #endif #ifndef SIZE_MAX #define SIZE_MAX (~(size_t)0) #endif #endif /* ! C99 */ #endif /* ! FLEXINT_H */ /* begin standard C++ headers. */ /* TODO: this is always defined, so inline it */ #define yyconst const #if defined(__GNUC__) && __GNUC__ >= 3 #define yynoreturn __attribute__((__noreturn__)) #else #define yynoreturn #endif /* Returned upon end-of-file. */ #define YY_NULL 0 /* Promotes a possibly negative, possibly signed char to an * integer in range [0..255] for use as an array index. */ #define YY_SC_TO_UI(c) ((YY_CHAR) (c)) /* An opaque pointer. */ #ifndef YY_TYPEDEF_YY_SCANNER_T #define YY_TYPEDEF_YY_SCANNER_T typedef void* yyscan_t; #endif /* For convenience, these vars (plus the bison vars far below) are macros in the reentrant scanner. */ #define yyin yyg->yyin_r #define yyout yyg->yyout_r #define yyextra yyg->yyextra_r #define yyleng yyg->yyleng_r #define yytext yyg->yytext_r #define yylineno (YY_CURRENT_BUFFER_LVALUE->yy_bs_lineno) #define yycolumn (YY_CURRENT_BUFFER_LVALUE->yy_bs_column) #define yy_flex_debug yyg->yy_flex_debug_r /* Enter a start condition. This macro really ought to take a parameter, * but we do it the disgusting crufty way forced on us by the ()-less * definition of BEGIN. */ #define BEGIN yyg->yy_start = 1 + 2 * /* Translate the current start state into a value that can be later handed * to BEGIN to return to the state. The YYSTATE alias is for lex * compatibility. */ #define YY_START ((yyg->yy_start - 1) / 2) #define YYSTATE YY_START /* Action number for EOF rule of a given start state. */ #define YY_STATE_EOF(state) (YY_END_OF_BUFFER + state + 1) /* Special action meaning "start processing a new file". */ #define YY_NEW_FILE yyrestart( yyin , yyscanner ) #define YY_END_OF_BUFFER_CHAR 0 /* Size of default input buffer. */ #ifndef YY_BUF_SIZE #ifdef __ia64__ /* On IA-64, the buffer size is 16k, not 8k. * Moreover, YY_BUF_SIZE is 2*YY_READ_BUF_SIZE in the general case. * Ditto for the __ia64__ case accordingly. */ #define YY_BUF_SIZE 32768 #else #define YY_BUF_SIZE 16384 #endif /* __ia64__ */ #endif /* The state buf must be large enough to hold one state per character in the main buffer. */ #define YY_STATE_BUF_SIZE ((YY_BUF_SIZE + 2) * sizeof(yy_state_type)) #ifndef YY_TYPEDEF_YY_BUFFER_STATE #define YY_TYPEDEF_YY_BUFFER_STATE typedef struct yy_buffer_state *YY_BUFFER_STATE; #endif #ifndef YY_TYPEDEF_YY_SIZE_T #define YY_TYPEDEF_YY_SIZE_T typedef size_t yy_size_t; #endif #define EOB_ACT_CONTINUE_SCAN 0 #define EOB_ACT_END_OF_FILE 1 #define EOB_ACT_LAST_MATCH 2 #define YY_LESS_LINENO(n) #define YY_LINENO_REWIND_TO(ptr) /* Return all but the first "n" matched characters back to the input stream. */ #define yyless(n) \ do \ { \ /* Undo effects of setting up yytext. */ \ int yyless_macro_arg = (n); \ YY_LESS_LINENO(yyless_macro_arg);\ *yy_cp = yyg->yy_hold_char; \ YY_RESTORE_YY_MORE_OFFSET \ yyg->yy_c_buf_p = yy_cp = yy_bp + yyless_macro_arg - YY_MORE_ADJ; \ YY_DO_BEFORE_ACTION; /* set up yytext again */ \ } \ while ( 0 ) #define unput(c) yyunput( c, yyg->yytext_ptr , yyscanner ) #ifndef YY_STRUCT_YY_BUFFER_STATE #define YY_STRUCT_YY_BUFFER_STATE struct yy_buffer_state { FILE *yy_input_file; char *yy_ch_buf; /* input buffer */ char *yy_buf_pos; /* current position in input buffer */ /* Size of input buffer in bytes, not including room for EOB * characters. */ int yy_buf_size; /* Number of characters read into yy_ch_buf, not including EOB * characters. */ int yy_n_chars; /* Whether we "own" the buffer - i.e., we know we created it, * and can realloc() it to grow it, and should free() it to * delete it. */ int yy_is_our_buffer; /* Whether this is an "interactive" input source; if so, and * if we're using stdio for input, then we want to use getc() * instead of fread(), to make sure we stop fetching input after * each newline. */ int yy_is_interactive; /* Whether we're considered to be at the beginning of a line. * If so, '^' rules will be active on the next match, otherwise * not. */ int yy_at_bol; int yy_bs_lineno; /**< The line count. */ int yy_bs_column; /**< The column count. */ /* Whether to try to fill the input buffer when we reach the * end of it. */ int yy_fill_buffer; int yy_buffer_status; #define YY_BUFFER_NEW 0 #define YY_BUFFER_NORMAL 1 /* When an EOF's been seen but there's still some text to process * then we mark the buffer as YY_EOF_PENDING, to indicate that we * shouldn't try reading from the input source any more. We might * still have a bunch of tokens to match, though, because of * possible backing-up. * * When we actually see the EOF, we change the status to "new" * (via yyrestart()), so that the user can continue scanning by * just pointing yyin at a new input file. */ #define YY_BUFFER_EOF_PENDING 2 }; #endif /* !YY_STRUCT_YY_BUFFER_STATE */ /* We provide macros for accessing buffer states in case in the * future we want to put the buffer states in a more general * "scanner state". * * Returns the top of the stack, or NULL. */ #define YY_CURRENT_BUFFER ( yyg->yy_buffer_stack \ ? yyg->yy_buffer_stack[yyg->yy_buffer_stack_top] \ : NULL) /* Same as previous macro, but useful when we know that the buffer stack is not * NULL or when we need an lvalue. For internal use only. */ #define YY_CURRENT_BUFFER_LVALUE yyg->yy_buffer_stack[yyg->yy_buffer_stack_top] void yyrestart ( FILE *input_file , yyscan_t yyscanner ); void yy_switch_to_buffer ( YY_BUFFER_STATE new_buffer , yyscan_t yyscanner ); YY_BUFFER_STATE yy_create_buffer ( FILE *file, int size , yyscan_t yyscanner ); void yy_delete_buffer ( YY_BUFFER_STATE b , yyscan_t yyscanner ); void yy_flush_buffer ( YY_BUFFER_STATE b , yyscan_t yyscanner ); void yypush_buffer_state ( YY_BUFFER_STATE new_buffer , yyscan_t yyscanner ); void yypop_buffer_state ( yyscan_t yyscanner ); static void yyensure_buffer_stack ( yyscan_t yyscanner ); static void yy_load_buffer_state ( yyscan_t yyscanner ); static void yy_init_buffer ( YY_BUFFER_STATE b, FILE *file , yyscan_t yyscanner ); #define YY_FLUSH_BUFFER yy_flush_buffer( YY_CURRENT_BUFFER , yyscanner) YY_BUFFER_STATE yy_scan_buffer ( char *base, yy_size_t size , yyscan_t yyscanner ); YY_BUFFER_STATE yy_scan_string ( const char *yy_str , yyscan_t yyscanner ); YY_BUFFER_STATE yy_scan_bytes ( const char *bytes, int len , yyscan_t yyscanner ); void *yyalloc ( yy_size_t , yyscan_t yyscanner ); void *yyrealloc ( void *, yy_size_t , yyscan_t yyscanner ); void yyfree ( void * , yyscan_t yyscanner ); #define yy_new_buffer yy_create_buffer #define yy_set_interactive(is_interactive) \ { \ if ( ! YY_CURRENT_BUFFER ){ \ yyensure_buffer_stack (yyscanner); \ YY_CURRENT_BUFFER_LVALUE = \ yy_create_buffer( yyin, YY_BUF_SIZE , yyscanner); \ } \ YY_CURRENT_BUFFER_LVALUE->yy_is_interactive = is_interactive; \ } #define yy_set_bol(at_bol) \ { \ if ( ! YY_CURRENT_BUFFER ){\ yyensure_buffer_stack (yyscanner); \ YY_CURRENT_BUFFER_LVALUE = \ yy_create_buffer( yyin, YY_BUF_SIZE , yyscanner); \ } \ YY_CURRENT_BUFFER_LVALUE->yy_at_bol = at_bol; \ } #define YY_AT_BOL() (YY_CURRENT_BUFFER_LVALUE->yy_at_bol) /* Begin user sect3 */ #define cmd_yywrap(yyscanner) (/*CONSTCOND*/1) #define YY_SKIP_YYWRAP typedef flex_uint8_t YY_CHAR; typedef int yy_state_type; #define yytext_ptr yytext_r static yy_state_type yy_get_previous_state ( yyscan_t yyscanner ); static yy_state_type yy_try_NUL_trans ( yy_state_type current_state , yyscan_t yyscanner); static int yy_get_next_buffer ( yyscan_t yyscanner ); static void yynoreturn yy_fatal_error ( const char* msg , yyscan_t yyscanner ); /* Done after the current pattern has been matched and before the * corresponding action - sets up yytext. */ #define YY_DO_BEFORE_ACTION \ yyg->yytext_ptr = yy_bp; \ yyleng = (int) (yy_cp - yy_bp); \ yyg->yy_hold_char = *yy_cp; \ *yy_cp = '\0'; \ yyg->yy_c_buf_p = yy_cp; #define YY_NUM_RULES 12 #define YY_END_OF_BUFFER 13 /* This struct is not used in this scanner, but its presence is necessary. */ struct yy_trans_info { flex_int32_t yy_verify; flex_int32_t yy_nxt; }; static const flex_int16_t yy_accept[91] = { 0, 0, 0, 13, 11, 1, 12, 11, 9, 11, 9, 9, 9, 1, 0, 0, 9, 9, 8, 0, 8, 8, 0, 0, 0, 0, 8, 0, 0, 0, 0, 0, 8, 10, 0, 0, 0, 8, 8, 0, 0, 0, 8, 8, 0, 0, 2, 8, 8, 0, 0, 0, 4, 8, 0, 0, 3, 0, 8, 0, 0, 5, 8, 0, 0, 6, 0, 0, 0, 0, 0, 7, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0 } ; static const YY_CHAR yy_ec[256] = { 0, 1, 1, 1, 1, 1, 1, 1, 1, 2, 3, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 4, 1, 1, 1, 1, 1, 1, 1, 5, 6, 7, 8, 1, 9, 10, 11, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 13, 1, 1, 1, 1, 1, 1, 14, 15, 16, 17, 18, 18, 18, 18, 18, 18, 18, 18, 19, 18, 18, 18, 18, 18, 18, 18, 18, 18, 18, 20, 18, 18, 1, 1, 1, 1, 21, 1, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1 } ; static const YY_CHAR yy_meta[22] = { 0, 1, 1, 1, 1, 1, 1, 2, 3, 4, 5, 1, 6, 7, 6, 6, 6, 6, 6, 6, 6, 4 } ; static const flex_int16_t yy_base[95] = { 0, 0, 0, 185, 186, 20, 186, 17, 0, 0, 23, 30, 171, 30, 171, 35, 0, 0, 0, 167, 0, 161, 171, 44, 37, 169, 165, 42, 165, 49, 53, 160, 15, 186, 52, 59, 165, 152, 150, 54, 63, 145, 148, 138, 64, 65, 139, 117, 113, 67, 74, 96, 93, 73, 75, 76, 186, 63, 46, 78, 85, 186, 25, 86, 87, 31, 89, 96, 8, 97, 98, 186, 100, 107, 108, 109, 111, 118, 119, 120, 122, 129, 130, 131, 133, 140, 141, 142, 142, 17, 186, 153, 158, 163, 167 } ; static const flex_int16_t yy_def[95] = { 0, 90, 1, 90, 90, 90, 90, 90, 91, 92, 93, 93, 11, 90, 90, 90, 91, 91, 11, 90, 94, 94, 90, 90, 90, 90, 94, 90, 90, 90, 90, 90, 94, 90, 90, 90, 90, 94, 94, 90, 90, 90, 94, 94, 90, 90, 90, 94, 94, 90, 90, 90, 94, 94, 90, 90, 90, 90, 94, 90, 90, 90, 94, 90, 90, 94, 90, 90, 90, 90, 90, 90, 90, 90, 90, 90, 90, 90, 90, 90, 90, 90, 90, 90, 90, 90, 90, 90, 90, 90, 0, 90, 90, 90, 90 } ; static const flex_int16_t yy_nxt[208] = { 0, 4, 5, 6, 5, 7, 4, 8, 9, 9, 4, 4, 8, 4, 10, 11, 11, 11, 11, 11, 12, 4, 13, 33, 13, 14, 14, 71, 37, 15, 16, 16, 13, 19, 13, 38, 20, 16, 16, 22, 90, 22, 68, 20, 23, 65, 23, 24, 27, 30, 28, 28, 28, 28, 29, 33, 29, 22, 33, 62, 33, 34, 23, 22, 39, 35, 44, 22, 23, 22, 33, 40, 23, 33, 23, 45, 49, 50, 22, 54, 22, 33, 61, 23, 33, 23, 55, 59, 60, 22, 63, 22, 33, 58, 23, 33, 23, 64, 66, 67, 22, 69, 22, 33, 57, 23, 33, 23, 70, 72, 73, 22, 74, 22, 33, 56, 23, 33, 23, 75, 76, 77, 22, 78, 22, 33, 53, 23, 33, 23, 79, 80, 81, 22, 82, 22, 33, 52, 23, 33, 23, 83, 84, 85, 22, 86, 22, 33, 33, 23, 51, 23, 87, 88, 89, 16, 16, 16, 48, 16, 17, 47, 46, 43, 17, 18, 18, 18, 18, 18, 18, 20, 42, 20, 20, 41, 36, 29, 32, 31, 23, 26, 25, 15, 21, 90, 3, 90, 90, 90, 90, 90, 90, 90, 90, 90, 90, 90, 90, 90, 90, 90, 90, 90, 90, 90, 90, 90 } ; static const flex_int16_t yy_chk[208] = { 0, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 5, 89, 5, 7, 7, 68, 32, 7, 10, 10, 13, 10, 13, 32, 10, 11, 11, 15, 11, 24, 65, 11, 15, 62, 24, 15, 23, 24, 27, 27, 23, 23, 27, 29, 23, 30, 34, 58, 39, 29, 30, 35, 34, 30, 39, 40, 35, 45, 44, 35, 40, 49, 45, 40, 44, 45, 50, 49, 55, 54, 57, 50, 59, 55, 50, 54, 55, 60, 59, 64, 63, 53, 60, 66, 64, 60, 63, 64, 67, 66, 70, 69, 52, 67, 72, 70, 67, 69, 70, 73, 72, 75, 74, 51, 73, 76, 75, 73, 74, 75, 77, 76, 79, 78, 48, 77, 80, 79, 77, 78, 79, 81, 80, 83, 82, 47, 81, 84, 83, 81, 82, 83, 85, 84, 87, 86, 88, 85, 46, 87, 85, 86, 88, 91, 91, 91, 43, 91, 92, 42, 41, 38, 92, 93, 93, 93, 93, 93, 93, 94, 37, 94, 94, 36, 31, 28, 26, 25, 22, 21, 19, 14, 12, 3, 90, 90, 90, 90, 90, 90, 90, 90, 90, 90, 90, 90, 90, 90, 90, 90, 90, 90, 90, 90, 90, 90 } ; /* The intent behind this definition is that it'll catch * any uses of REJECT which flex missed. */ #define REJECT reject_used_but_not_detected #define yymore() yymore_used_but_not_detected #define YY_MORE_ADJ 0 #define YY_RESTORE_YY_MORE_OFFSET #line 1 "lib/command_lex.l" /* * Command format string lexer for CLI backend. * * -- * Copyright (C) 2015 Cumulus Networks, Inc. * * This file is part of GNU Zebra. * * GNU Zebra is free software; you can redistribute it and/or modify it * under the terms of the GNU General Public License as published by the * Free Software Foundation; either version 2, or (at your option) any * later version. * * GNU Zebra is distributed in the hope that it will be useful, but * WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU * General Public License for more details. * * You should have received a copy of the GNU General Public License * along with GNU Zebra; see the file COPYING. If not, write to the Free * Software Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA * 02111-1307, USA. */ #line 31 "lib/command_lex.l" /* ignore flex generated code in static analyzer */ #ifndef __clang_analyzer__ /* ignore harmless bugs in old versions of flex */ #pragma GCC diagnostic ignored "-Wsign-compare" #pragma GCC diagnostic ignored "-Wmissing-prototypes" #include "command_parse.h" #define YY_USER_ACTION yylloc->last_column += yyleng; #define LOC_STEP do { if (yylloc) { \ yylloc->first_column = yylloc->last_column; \ yylloc->first_line = yylloc->last_line; \ } } while(0) #line 772 "lib/command_lex.c" /* yytext shall be a pointer */ #define YY_NO_INPUT 1 #line 775 "lib/command_lex.c" #define INITIAL 0 #ifndef YY_NO_UNISTD_H /* Special case for "unistd.h", since it is non-ANSI. We include it way * down here because we want the user's section 1 to have been scanned first. * The user has a chance to override it with an option. */ #include #endif #ifndef YY_EXTRA_TYPE #define YY_EXTRA_TYPE void * #endif /* Holds the entire state of the reentrant scanner. */ struct yyguts_t { /* User-defined. Not touched by flex. */ YY_EXTRA_TYPE yyextra_r; /* The rest are the same as the globals declared in the non-reentrant scanner. */ FILE *yyin_r, *yyout_r; size_t yy_buffer_stack_top; /**< index of top of stack. */ size_t yy_buffer_stack_max; /**< capacity of stack. */ YY_BUFFER_STATE * yy_buffer_stack; /**< Stack as an array. */ char yy_hold_char; int yy_n_chars; int yyleng_r; char *yy_c_buf_p; int yy_init; int yy_start; int yy_did_buffer_switch_on_eof; int yy_start_stack_ptr; int yy_start_stack_depth; int *yy_start_stack; yy_state_type yy_last_accepting_state; char* yy_last_accepting_cpos; int yylineno_r; int yy_flex_debug_r; char *yytext_r; int yy_more_flag; int yy_more_len; YYSTYPE * yylval_r; YYLTYPE * yylloc_r; }; /* end struct yyguts_t */ static int yy_init_globals ( yyscan_t yyscanner ); /* This must go here because YYSTYPE and YYLTYPE are included * from bison output in section 1.*/ # define yylval yyg->yylval_r # define yylloc yyg->yylloc_r int yylex_init (yyscan_t* scanner); int yylex_init_extra ( YY_EXTRA_TYPE user_defined, yyscan_t* scanner); /* Accessor methods to globals. These are made visible to non-reentrant scanners for convenience. */ int yylex_destroy ( yyscan_t yyscanner ); int yyget_debug ( yyscan_t yyscanner ); void yyset_debug ( int debug_flag , yyscan_t yyscanner ); YY_EXTRA_TYPE yyget_extra ( yyscan_t yyscanner ); void yyset_extra ( YY_EXTRA_TYPE user_defined , yyscan_t yyscanner ); FILE *yyget_in ( yyscan_t yyscanner ); void yyset_in ( FILE * _in_str , yyscan_t yyscanner ); FILE *yyget_out ( yyscan_t yyscanner ); void yyset_out ( FILE * _out_str , yyscan_t yyscanner ); int yyget_leng ( yyscan_t yyscanner ); char *yyget_text ( yyscan_t yyscanner ); int yyget_lineno ( yyscan_t yyscanner ); void yyset_lineno ( int _line_number , yyscan_t yyscanner ); int yyget_column ( yyscan_t yyscanner ); void yyset_column ( int _column_no , yyscan_t yyscanner ); YYSTYPE * yyget_lval ( yyscan_t yyscanner ); void yyset_lval ( YYSTYPE * yylval_param , yyscan_t yyscanner ); YYLTYPE *yyget_lloc ( yyscan_t yyscanner ); void yyset_lloc ( YYLTYPE * yylloc_param , yyscan_t yyscanner ); /* Macros after this point can all be overridden by user definitions in * section 1. */ #ifndef YY_SKIP_YYWRAP #ifdef __cplusplus extern "C" int yywrap ( yyscan_t yyscanner ); #else extern int yywrap ( yyscan_t yyscanner ); #endif #endif #ifndef YY_NO_UNPUT #endif #ifndef yytext_ptr static void yy_flex_strncpy ( char *, const char *, int , yyscan_t yyscanner); #endif #ifdef YY_NEED_STRLEN static int yy_flex_strlen ( const char * , yyscan_t yyscanner); #endif #ifndef YY_NO_INPUT #ifdef __cplusplus static int yyinput ( yyscan_t yyscanner ); #else static int input ( yyscan_t yyscanner ); #endif #endif /* Amount of stuff to slurp up with each read. */ #ifndef YY_READ_BUF_SIZE #ifdef __ia64__ /* On IA-64, the buffer size is 16k, not 8k */ #define YY_READ_BUF_SIZE 16384 #else #define YY_READ_BUF_SIZE 8192 #endif /* __ia64__ */ #endif /* Copy whatever the last rule matched to the standard output. */ #ifndef ECHO /* This used to be an fputs(), but since the string might contain NUL's, * we now use fwrite(). */ #define ECHO do { if (fwrite( yytext, (size_t) yyleng, 1, yyout )) {} } while (0) #endif /* Gets input and stuffs it into "buf". number of characters read, or YY_NULL, * is returned in "result". */ #ifndef YY_INPUT #define YY_INPUT(buf,result,max_size) \ if ( YY_CURRENT_BUFFER_LVALUE->yy_is_interactive ) \ { \ int c = '*'; \ int n; \ for ( n = 0; n < max_size && \ (c = getc( yyin )) != EOF && c != '\n'; ++n ) \ buf[n] = (char) c; \ if ( c == '\n' ) \ buf[n++] = (char) c; \ if ( c == EOF && ferror( yyin ) ) \ YY_FATAL_ERROR( "input in flex scanner failed" ); \ result = n; \ } \ else \ { \ errno=0; \ while ( (result = (int) fread(buf, 1, (yy_size_t) max_size, yyin)) == 0 && ferror(yyin)) \ { \ if( errno != EINTR) \ { \ YY_FATAL_ERROR( "input in flex scanner failed" ); \ break; \ } \ errno=0; \ clearerr(yyin); \ } \ }\ \ #endif /* No semi-colon after return; correct usage is to write "yyterminate();" - * we don't want an extra ';' after the "return" because that will cause * some compilers to complain about unreachable statements. */ #ifndef yyterminate #define yyterminate() return YY_NULL #endif /* Number of entries by which start-condition stack grows. */ #ifndef YY_START_STACK_INCR #define YY_START_STACK_INCR 25 #endif /* Report a fatal error. */ #ifndef YY_FATAL_ERROR #define YY_FATAL_ERROR(msg) yy_fatal_error( msg , yyscanner) #endif /* end tables serialization structures and prototypes */ /* Default declaration of generated scanner - a define so the user can * easily add parameters. */ #ifndef YY_DECL #define YY_DECL_IS_OURS 1 extern int yylex \ (YYSTYPE * yylval_param, YYLTYPE * yylloc_param , yyscan_t yyscanner); #define YY_DECL int yylex \ (YYSTYPE * yylval_param, YYLTYPE * yylloc_param , yyscan_t yyscanner) #endif /* !YY_DECL */ /* Code executed at the beginning of each rule, after yytext and yyleng * have been set up. */ #ifndef YY_USER_ACTION #define YY_USER_ACTION #endif /* Code executed at the end of each rule. */ #ifndef YY_BREAK #define YY_BREAK /*LINTED*/break; #endif #define YY_RULE_SETUP \ YY_USER_ACTION /** The main scanner function which does all the work. */ YY_DECL { yy_state_type yy_current_state; char *yy_cp, *yy_bp; int yy_act; struct yyguts_t * yyg = (struct yyguts_t*)yyscanner; yylval = yylval_param; yylloc = yylloc_param; if ( !yyg->yy_init ) { yyg->yy_init = 1; #ifdef YY_USER_INIT YY_USER_INIT; #endif if ( ! yyg->yy_start ) yyg->yy_start = 1; /* first start state */ if ( ! yyin ) yyin = stdin; if ( ! yyout ) yyout = stdout; if ( ! YY_CURRENT_BUFFER ) { yyensure_buffer_stack (yyscanner); YY_CURRENT_BUFFER_LVALUE = yy_create_buffer( yyin, YY_BUF_SIZE , yyscanner); } yy_load_buffer_state( yyscanner ); } { #line 70 "lib/command_lex.l" #line 73 "lib/command_lex.l" LOC_STEP; #line 1064 "lib/command_lex.c" while ( /*CONSTCOND*/1 ) /* loops until end-of-file is reached */ { yy_cp = yyg->yy_c_buf_p; /* Support of yytext. */ *yy_cp = yyg->yy_hold_char; /* yy_bp points to the position in yy_ch_buf of the start of * the current run. */ yy_bp = yy_cp; yy_current_state = yyg->yy_start; yy_match: do { YY_CHAR yy_c = yy_ec[YY_SC_TO_UI(*yy_cp)] ; if ( yy_accept[yy_current_state] ) { yyg->yy_last_accepting_state = yy_current_state; yyg->yy_last_accepting_cpos = yy_cp; } while ( yy_chk[yy_base[yy_current_state] + yy_c] != yy_current_state ) { yy_current_state = (int) yy_def[yy_current_state]; if ( yy_current_state >= 91 ) yy_c = yy_meta[yy_c]; } yy_current_state = yy_nxt[yy_base[yy_current_state] + yy_c]; ++yy_cp; } while ( yy_base[yy_current_state] != 186 ); yy_find_action: yy_act = yy_accept[yy_current_state]; if ( yy_act == 0 ) { /* have to back up */ yy_cp = yyg->yy_last_accepting_cpos; yy_current_state = yyg->yy_last_accepting_state; yy_act = yy_accept[yy_current_state]; } YY_DO_BEFORE_ACTION; do_action: /* This label is used only to access EOF actions. */ switch ( yy_act ) { /* beginning of action switch */ case 0: /* must back up */ /* undo the effects of YY_DO_BEFORE_ACTION */ *yy_cp = yyg->yy_hold_char; yy_cp = yyg->yy_last_accepting_cpos; yy_current_state = yyg->yy_last_accepting_state; goto yy_find_action; case 1: YY_RULE_SETUP #line 76 "lib/command_lex.l" LOC_STEP /* ignore whitespace */; YY_BREAK case 2: YY_RULE_SETUP #line 77 "lib/command_lex.l" {yylval->string = XSTRDUP(MTYPE_LEX, yytext); return IPV4;} YY_BREAK case 3: YY_RULE_SETUP #line 78 "lib/command_lex.l" {yylval->string = XSTRDUP(MTYPE_LEX, yytext); return IPV4_PREFIX;} YY_BREAK case 4: YY_RULE_SETUP #line 79 "lib/command_lex.l" {yylval->string = XSTRDUP(MTYPE_LEX, yytext); return IPV6;} YY_BREAK case 5: YY_RULE_SETUP #line 80 "lib/command_lex.l" {yylval->string = XSTRDUP(MTYPE_LEX, yytext); return IPV6_PREFIX;} YY_BREAK case 6: YY_RULE_SETUP #line 81 "lib/command_lex.l" {yylval->string = XSTRDUP(MTYPE_LEX, yytext); return MAC;} YY_BREAK case 7: YY_RULE_SETUP #line 82 "lib/command_lex.l" {yylval->string = XSTRDUP(MTYPE_LEX, yytext); return MAC_PREFIX;} YY_BREAK case 8: YY_RULE_SETUP #line 83 "lib/command_lex.l" {yylval->string = XSTRDUP(MTYPE_LEX, yytext); return VARIABLE;} YY_BREAK case 9: YY_RULE_SETUP #line 84 "lib/command_lex.l" {yylval->string = XSTRDUP(MTYPE_LEX, yytext); return WORD;} YY_BREAK case 10: YY_RULE_SETUP #line 85 "lib/command_lex.l" {yylval->string = XSTRDUP(MTYPE_LEX, yytext); return RANGE;} YY_BREAK case 11: YY_RULE_SETUP #line 86 "lib/command_lex.l" {return yytext[0];} YY_BREAK case 12: YY_RULE_SETUP #line 87 "lib/command_lex.l" ECHO; YY_BREAK #line 1181 "lib/command_lex.c" case YY_STATE_EOF(INITIAL): yyterminate(); case YY_END_OF_BUFFER: { /* Amount of text matched not including the EOB char. */ int yy_amount_of_matched_text = (int) (yy_cp - yyg->yytext_ptr) - 1; /* Undo the effects of YY_DO_BEFORE_ACTION. */ *yy_cp = yyg->yy_hold_char; YY_RESTORE_YY_MORE_OFFSET if ( YY_CURRENT_BUFFER_LVALUE->yy_buffer_status == YY_BUFFER_NEW ) { /* We're scanning a new file or input source. It's * possible that this happened because the user * just pointed yyin at a new source and called * yylex(). If so, then we have to assure * consistency between YY_CURRENT_BUFFER and our * globals. Here is the right place to do so, because * this is the first action (other than possibly a * back-up) that will match for the new input source. */ yyg->yy_n_chars = YY_CURRENT_BUFFER_LVALUE->yy_n_chars; YY_CURRENT_BUFFER_LVALUE->yy_input_file = yyin; YY_CURRENT_BUFFER_LVALUE->yy_buffer_status = YY_BUFFER_NORMAL; } /* Note that here we test for yy_c_buf_p "<=" to the position * of the first EOB in the buffer, since yy_c_buf_p will * already have been incremented past the NUL character * (since all states make transitions on EOB to the * end-of-buffer state). Contrast this with the test * in input(). */ if ( yyg->yy_c_buf_p <= &YY_CURRENT_BUFFER_LVALUE->yy_ch_buf[yyg->yy_n_chars] ) { /* This was really a NUL. */ yy_state_type yy_next_state; yyg->yy_c_buf_p = yyg->yytext_ptr + yy_amount_of_matched_text; yy_current_state = yy_get_previous_state( yyscanner ); /* Okay, we're now positioned to make the NUL * transition. We couldn't have * yy_get_previous_state() go ahead and do it * for us because it doesn't know how to deal * with the possibility of jamming (and we don't * want to build jamming into it because then it * will run more slowly). */ yy_next_state = yy_try_NUL_trans( yy_current_state , yyscanner); yy_bp = yyg->yytext_ptr + YY_MORE_ADJ; if ( yy_next_state ) { /* Consume the NUL. */ yy_cp = ++yyg->yy_c_buf_p; yy_current_state = yy_next_state; goto yy_match; } else { yy_cp = yyg->yy_c_buf_p; goto yy_find_action; } } else switch ( yy_get_next_buffer( yyscanner ) ) { case EOB_ACT_END_OF_FILE: { yyg->yy_did_buffer_switch_on_eof = 0; if ( yywrap( yyscanner ) ) { /* Note: because we've taken care in * yy_get_next_buffer() to have set up * yytext, we can now set up * yy_c_buf_p so that if some total * hoser (like flex itself) wants to * call the scanner after we return the * YY_NULL, it'll still work - another * YY_NULL will get returned. */ yyg->yy_c_buf_p = yyg->yytext_ptr + YY_MORE_ADJ; yy_act = YY_STATE_EOF(YY_START); goto do_action; } else { if ( ! yyg->yy_did_buffer_switch_on_eof ) YY_NEW_FILE; } break; } case EOB_ACT_CONTINUE_SCAN: yyg->yy_c_buf_p = yyg->yytext_ptr + yy_amount_of_matched_text; yy_current_state = yy_get_previous_state( yyscanner ); yy_cp = yyg->yy_c_buf_p; yy_bp = yyg->yytext_ptr + YY_MORE_ADJ; goto yy_match; case EOB_ACT_LAST_MATCH: yyg->yy_c_buf_p = &YY_CURRENT_BUFFER_LVALUE->yy_ch_buf[yyg->yy_n_chars]; yy_current_state = yy_get_previous_state( yyscanner ); yy_cp = yyg->yy_c_buf_p; yy_bp = yyg->yytext_ptr + YY_MORE_ADJ; goto yy_find_action; } break; } default: YY_FATAL_ERROR( "fatal flex scanner internal error--no action found" ); } /* end of action switch */ } /* end of scanning one token */ } /* end of user's declarations */ } /* end of yylex */ /* yy_get_next_buffer - try to read in a new buffer * * Returns a code representing an action: * EOB_ACT_LAST_MATCH - * EOB_ACT_CONTINUE_SCAN - continue scanning from current position * EOB_ACT_END_OF_FILE - end of file */ static int yy_get_next_buffer (yyscan_t yyscanner) { struct yyguts_t * yyg = (struct yyguts_t*)yyscanner; char *dest = YY_CURRENT_BUFFER_LVALUE->yy_ch_buf; char *source = yyg->yytext_ptr; int number_to_move, i; int ret_val; if ( yyg->yy_c_buf_p > &YY_CURRENT_BUFFER_LVALUE->yy_ch_buf[yyg->yy_n_chars + 1] ) YY_FATAL_ERROR( "fatal flex scanner internal error--end of buffer missed" ); if ( YY_CURRENT_BUFFER_LVALUE->yy_fill_buffer == 0 ) { /* Don't try to fill the buffer, so this is an EOF. */ if ( yyg->yy_c_buf_p - yyg->yytext_ptr - YY_MORE_ADJ == 1 ) { /* We matched a single character, the EOB, so * treat this as a final EOF. */ return EOB_ACT_END_OF_FILE; } else { /* We matched some text prior to the EOB, first * process it. */ return EOB_ACT_LAST_MATCH; } } /* Try to read more data. */ /* First move last chars to start of buffer. */ number_to_move = (int) (yyg->yy_c_buf_p - yyg->yytext_ptr - 1); for ( i = 0; i < number_to_move; ++i ) *(dest++) = *(source++); if ( YY_CURRENT_BUFFER_LVALUE->yy_buffer_status == YY_BUFFER_EOF_PENDING ) /* don't do the read, it's not guaranteed to return an EOF, * just force an EOF */ YY_CURRENT_BUFFER_LVALUE->yy_n_chars = yyg->yy_n_chars = 0; else { int num_to_read = YY_CURRENT_BUFFER_LVALUE->yy_buf_size - number_to_move - 1; while ( num_to_read <= 0 ) { /* Not enough room in the buffer - grow it. */ /* just a shorter name for the current buffer */ YY_BUFFER_STATE b = YY_CURRENT_BUFFER_LVALUE; int yy_c_buf_p_offset = (int) (yyg->yy_c_buf_p - b->yy_ch_buf); if ( b->yy_is_our_buffer ) { int new_size = b->yy_buf_size * 2; if ( new_size <= 0 ) b->yy_buf_size += b->yy_buf_size / 8; else b->yy_buf_size *= 2; b->yy_ch_buf = (char *) /* Include room in for 2 EOB chars. */ yyrealloc( (void *) b->yy_ch_buf, (yy_size_t) (b->yy_buf_size + 2) , yyscanner ); } else /* Can't grow it, we don't own it. */ b->yy_ch_buf = NULL; if ( ! b->yy_ch_buf ) YY_FATAL_ERROR( "fatal error - scanner input buffer overflow" ); yyg->yy_c_buf_p = &b->yy_ch_buf[yy_c_buf_p_offset]; num_to_read = YY_CURRENT_BUFFER_LVALUE->yy_buf_size - number_to_move - 1; } if ( num_to_read > YY_READ_BUF_SIZE ) num_to_read = YY_READ_BUF_SIZE; /* Read in more data. */ YY_INPUT( (&YY_CURRENT_BUFFER_LVALUE->yy_ch_buf[number_to_move]), yyg->yy_n_chars, num_to_read ); YY_CURRENT_BUFFER_LVALUE->yy_n_chars = yyg->yy_n_chars; } if ( yyg->yy_n_chars == 0 ) { if ( number_to_move == YY_MORE_ADJ ) { ret_val = EOB_ACT_END_OF_FILE; yyrestart( yyin , yyscanner); } else { ret_val = EOB_ACT_LAST_MATCH; YY_CURRENT_BUFFER_LVALUE->yy_buffer_status = YY_BUFFER_EOF_PENDING; } } else ret_val = EOB_ACT_CONTINUE_SCAN; if ((yyg->yy_n_chars + number_to_move) > YY_CURRENT_BUFFER_LVALUE->yy_buf_size) { /* Extend the array by 50%, plus the number we really need. */ int new_size = yyg->yy_n_chars + number_to_move + (yyg->yy_n_chars >> 1); YY_CURRENT_BUFFER_LVALUE->yy_ch_buf = (char *) yyrealloc( (void *) YY_CURRENT_BUFFER_LVALUE->yy_ch_buf, (yy_size_t) new_size , yyscanner ); if ( ! YY_CURRENT_BUFFER_LVALUE->yy_ch_buf ) YY_FATAL_ERROR( "out of dynamic memory in yy_get_next_buffer()" ); /* "- 2" to take care of EOB's */ YY_CURRENT_BUFFER_LVALUE->yy_buf_size = (int) (new_size - 2); } yyg->yy_n_chars += number_to_move; YY_CURRENT_BUFFER_LVALUE->yy_ch_buf[yyg->yy_n_chars] = YY_END_OF_BUFFER_CHAR; YY_CURRENT_BUFFER_LVALUE->yy_ch_buf[yyg->yy_n_chars + 1] = YY_END_OF_BUFFER_CHAR; yyg->yytext_ptr = &YY_CURRENT_BUFFER_LVALUE->yy_ch_buf[0]; return ret_val; } /* yy_get_previous_state - get the state just before the EOB char was reached */ static yy_state_type yy_get_previous_state (yyscan_t yyscanner) { yy_state_type yy_current_state; char *yy_cp; struct yyguts_t * yyg = (struct yyguts_t*)yyscanner; yy_current_state = yyg->yy_start; for ( yy_cp = yyg->yytext_ptr + YY_MORE_ADJ; yy_cp < yyg->yy_c_buf_p; ++yy_cp ) { YY_CHAR yy_c = (*yy_cp ? yy_ec[YY_SC_TO_UI(*yy_cp)] : 1); if ( yy_accept[yy_current_state] ) { yyg->yy_last_accepting_state = yy_current_state; yyg->yy_last_accepting_cpos = yy_cp; } while ( yy_chk[yy_base[yy_current_state] + yy_c] != yy_current_state ) { yy_current_state = (int) yy_def[yy_current_state]; if ( yy_current_state >= 91 ) yy_c = yy_meta[yy_c]; } yy_current_state = yy_nxt[yy_base[yy_current_state] + yy_c]; } return yy_current_state; } /* yy_try_NUL_trans - try to make a transition on the NUL character * * synopsis * next_state = yy_try_NUL_trans( current_state ); */ static yy_state_type yy_try_NUL_trans (yy_state_type yy_current_state , yyscan_t yyscanner) { int yy_is_jam; struct yyguts_t * yyg = (struct yyguts_t*)yyscanner; /* This var may be unused depending upon options. */ char *yy_cp = yyg->yy_c_buf_p; YY_CHAR yy_c = 1; if ( yy_accept[yy_current_state] ) { yyg->yy_last_accepting_state = yy_current_state; yyg->yy_last_accepting_cpos = yy_cp; } while ( yy_chk[yy_base[yy_current_state] + yy_c] != yy_current_state ) { yy_current_state = (int) yy_def[yy_current_state]; if ( yy_current_state >= 91 ) yy_c = yy_meta[yy_c]; } yy_current_state = yy_nxt[yy_base[yy_current_state] + yy_c]; yy_is_jam = (yy_current_state == 90); (void)yyg; return yy_is_jam ? 0 : yy_current_state; } #ifndef YY_NO_UNPUT #endif #ifndef YY_NO_INPUT #ifdef __cplusplus static int yyinput (yyscan_t yyscanner) #else static int input (yyscan_t yyscanner) #endif { int c; struct yyguts_t * yyg = (struct yyguts_t*)yyscanner; *yyg->yy_c_buf_p = yyg->yy_hold_char; if ( *yyg->yy_c_buf_p == YY_END_OF_BUFFER_CHAR ) { /* yy_c_buf_p now points to the character we want to return. * If this occurs *before* the EOB characters, then it's a * valid NUL; if not, then we've hit the end of the buffer. */ if ( yyg->yy_c_buf_p < &YY_CURRENT_BUFFER_LVALUE->yy_ch_buf[yyg->yy_n_chars] ) /* This was really a NUL. */ *yyg->yy_c_buf_p = '\0'; else { /* need more input */ int offset = (int) (yyg->yy_c_buf_p - yyg->yytext_ptr); ++yyg->yy_c_buf_p; switch ( yy_get_next_buffer( yyscanner ) ) { case EOB_ACT_LAST_MATCH: /* This happens because yy_g_n_b() * sees that we've accumulated a * token and flags that we need to * try matching the token before * proceeding. But for input(), * there's no matching to consider. * So convert the EOB_ACT_LAST_MATCH * to EOB_ACT_END_OF_FILE. */ /* Reset buffer status. */ yyrestart( yyin , yyscanner); /*FALLTHROUGH*/ case EOB_ACT_END_OF_FILE: { if ( yywrap( yyscanner ) ) return 0; if ( ! yyg->yy_did_buffer_switch_on_eof ) YY_NEW_FILE; #ifdef __cplusplus return yyinput(yyscanner); #else return input(yyscanner); #endif } case EOB_ACT_CONTINUE_SCAN: yyg->yy_c_buf_p = yyg->yytext_ptr + offset; break; } } } c = *(unsigned char *) yyg->yy_c_buf_p; /* cast for 8-bit char's */ *yyg->yy_c_buf_p = '\0'; /* preserve yytext */ yyg->yy_hold_char = *++yyg->yy_c_buf_p; return c; } #endif /* ifndef YY_NO_INPUT */ /** Immediately switch to a different input stream. * @param input_file A readable stream. * @param yyscanner The scanner object. * @note This function does not reset the start condition to @c INITIAL . */ void yyrestart (FILE * input_file , yyscan_t yyscanner) { struct yyguts_t * yyg = (struct yyguts_t*)yyscanner; if ( ! YY_CURRENT_BUFFER ){ yyensure_buffer_stack (yyscanner); YY_CURRENT_BUFFER_LVALUE = yy_create_buffer( yyin, YY_BUF_SIZE , yyscanner); } yy_init_buffer( YY_CURRENT_BUFFER, input_file , yyscanner); yy_load_buffer_state( yyscanner ); } /** Switch to a different input buffer. * @param new_buffer The new input buffer. * @param yyscanner The scanner object. */ void yy_switch_to_buffer (YY_BUFFER_STATE new_buffer , yyscan_t yyscanner) { struct yyguts_t * yyg = (struct yyguts_t*)yyscanner; /* TODO. We should be able to replace this entire function body * with * yypop_buffer_state(); * yypush_buffer_state(new_buffer); */ yyensure_buffer_stack (yyscanner); if ( YY_CURRENT_BUFFER == new_buffer ) return; if ( YY_CURRENT_BUFFER ) { /* Flush out information for old buffer. */ *yyg->yy_c_buf_p = yyg->yy_hold_char; YY_CURRENT_BUFFER_LVALUE->yy_buf_pos = yyg->yy_c_buf_p; YY_CURRENT_BUFFER_LVALUE->yy_n_chars = yyg->yy_n_chars; } YY_CURRENT_BUFFER_LVALUE = new_buffer; yy_load_buffer_state( yyscanner ); /* We don't actually know whether we did this switch during * EOF (yywrap()) processing, but the only time this flag * is looked at is after yywrap() is called, so it's safe * to go ahead and always set it. */ yyg->yy_did_buffer_switch_on_eof = 1; } static void yy_load_buffer_state (yyscan_t yyscanner) { struct yyguts_t * yyg = (struct yyguts_t*)yyscanner; yyg->yy_n_chars = YY_CURRENT_BUFFER_LVALUE->yy_n_chars; yyg->yytext_ptr = yyg->yy_c_buf_p = YY_CURRENT_BUFFER_LVALUE->yy_buf_pos; yyin = YY_CURRENT_BUFFER_LVALUE->yy_input_file; yyg->yy_hold_char = *yyg->yy_c_buf_p; } /** Allocate and initialize an input buffer state. * @param file A readable stream. * @param size The character buffer size in bytes. When in doubt, use @c YY_BUF_SIZE. * @param yyscanner The scanner object. * @return the allocated buffer state. */ YY_BUFFER_STATE yy_create_buffer (FILE * file, int size , yyscan_t yyscanner) { YY_BUFFER_STATE b; b = (YY_BUFFER_STATE) yyalloc( sizeof( struct yy_buffer_state ) , yyscanner ); if ( ! b ) YY_FATAL_ERROR( "out of dynamic memory in yy_create_buffer()" ); b->yy_buf_size = size; /* yy_ch_buf has to be 2 characters longer than the size given because * we need to put in 2 end-of-buffer characters. */ b->yy_ch_buf = (char *) yyalloc( (yy_size_t) (b->yy_buf_size + 2) , yyscanner ); if ( ! b->yy_ch_buf ) YY_FATAL_ERROR( "out of dynamic memory in yy_create_buffer()" ); b->yy_is_our_buffer = 1; yy_init_buffer( b, file , yyscanner); return b; } /** Destroy the buffer. * @param b a buffer created with yy_create_buffer() * @param yyscanner The scanner object. */ void yy_delete_buffer (YY_BUFFER_STATE b , yyscan_t yyscanner) { struct yyguts_t * yyg = (struct yyguts_t*)yyscanner; if ( ! b ) return; if ( b == YY_CURRENT_BUFFER ) /* Not sure if we should pop here. */ YY_CURRENT_BUFFER_LVALUE = (YY_BUFFER_STATE) 0; if ( b->yy_is_our_buffer ) yyfree( (void *) b->yy_ch_buf , yyscanner ); yyfree( (void *) b , yyscanner ); } /* Initializes or reinitializes a buffer. * This function is sometimes called more than once on the same buffer, * such as during a yyrestart() or at EOF. */ static void yy_init_buffer (YY_BUFFER_STATE b, FILE * file , yyscan_t yyscanner) { int oerrno = errno; struct yyguts_t * yyg = (struct yyguts_t*)yyscanner; yy_flush_buffer( b , yyscanner); b->yy_input_file = file; b->yy_fill_buffer = 1; /* If b is the current buffer, then yy_init_buffer was _probably_ * called from yyrestart() or through yy_get_next_buffer. * In that case, we don't want to reset the lineno or column. */ if (b != YY_CURRENT_BUFFER){ b->yy_bs_lineno = 1; b->yy_bs_column = 0; } b->yy_is_interactive = file ? (isatty( fileno(file) ) > 0) : 0; errno = oerrno; } /** Discard all buffered characters. On the next scan, YY_INPUT will be called. * @param b the buffer state to be flushed, usually @c YY_CURRENT_BUFFER. * @param yyscanner The scanner object. */ void yy_flush_buffer (YY_BUFFER_STATE b , yyscan_t yyscanner) { struct yyguts_t * yyg = (struct yyguts_t*)yyscanner; if ( ! b ) return; b->yy_n_chars = 0; /* We always need two end-of-buffer characters. The first causes * a transition to the end-of-buffer state. The second causes * a jam in that state. */ b->yy_ch_buf[0] = YY_END_OF_BUFFER_CHAR; b->yy_ch_buf[1] = YY_END_OF_BUFFER_CHAR; b->yy_buf_pos = &b->yy_ch_buf[0]; b->yy_at_bol = 1; b->yy_buffer_status = YY_BUFFER_NEW; if ( b == YY_CURRENT_BUFFER ) yy_load_buffer_state( yyscanner ); } /** Pushes the new state onto the stack. The new state becomes * the current state. This function will allocate the stack * if necessary. * @param new_buffer The new state. * @param yyscanner The scanner object. */ void yypush_buffer_state (YY_BUFFER_STATE new_buffer , yyscan_t yyscanner) { struct yyguts_t * yyg = (struct yyguts_t*)yyscanner; if (new_buffer == NULL) return; yyensure_buffer_stack(yyscanner); /* This block is copied from yy_switch_to_buffer. */ if ( YY_CURRENT_BUFFER ) { /* Flush out information for old buffer. */ *yyg->yy_c_buf_p = yyg->yy_hold_char; YY_CURRENT_BUFFER_LVALUE->yy_buf_pos = yyg->yy_c_buf_p; YY_CURRENT_BUFFER_LVALUE->yy_n_chars = yyg->yy_n_chars; } /* Only push if top exists. Otherwise, replace top. */ if (YY_CURRENT_BUFFER) yyg->yy_buffer_stack_top++; YY_CURRENT_BUFFER_LVALUE = new_buffer; /* copied from yy_switch_to_buffer. */ yy_load_buffer_state( yyscanner ); yyg->yy_did_buffer_switch_on_eof = 1; } /** Removes and deletes the top of the stack, if present. * The next element becomes the new top. * @param yyscanner The scanner object. */ void yypop_buffer_state (yyscan_t yyscanner) { struct yyguts_t * yyg = (struct yyguts_t*)yyscanner; if (!YY_CURRENT_BUFFER) return; yy_delete_buffer(YY_CURRENT_BUFFER , yyscanner); YY_CURRENT_BUFFER_LVALUE = NULL; if (yyg->yy_buffer_stack_top > 0) --yyg->yy_buffer_stack_top; if (YY_CURRENT_BUFFER) { yy_load_buffer_state( yyscanner ); yyg->yy_did_buffer_switch_on_eof = 1; } } /* Allocates the stack if it does not exist. * Guarantees space for at least one push. */ static void yyensure_buffer_stack (yyscan_t yyscanner) { yy_size_t num_to_alloc; struct yyguts_t * yyg = (struct yyguts_t*)yyscanner; if (!yyg->yy_buffer_stack) { /* First allocation is just for 2 elements, since we don't know if this * scanner will even need a stack. We use 2 instead of 1 to avoid an * immediate realloc on the next call. */ num_to_alloc = 1; /* After all that talk, this was set to 1 anyways... */ yyg->yy_buffer_stack = (struct yy_buffer_state**)yyalloc (num_to_alloc * sizeof(struct yy_buffer_state*) , yyscanner); if ( ! yyg->yy_buffer_stack ) YY_FATAL_ERROR( "out of dynamic memory in yyensure_buffer_stack()" ); memset(yyg->yy_buffer_stack, 0, num_to_alloc * sizeof(struct yy_buffer_state*)); yyg->yy_buffer_stack_max = num_to_alloc; yyg->yy_buffer_stack_top = 0; return; } if (yyg->yy_buffer_stack_top >= (yyg->yy_buffer_stack_max) - 1){ /* Increase the buffer to prepare for a possible push. */ yy_size_t grow_size = 8 /* arbitrary grow size */; num_to_alloc = yyg->yy_buffer_stack_max + grow_size; yyg->yy_buffer_stack = (struct yy_buffer_state**)yyrealloc (yyg->yy_buffer_stack, num_to_alloc * sizeof(struct yy_buffer_state*) , yyscanner); if ( ! yyg->yy_buffer_stack ) YY_FATAL_ERROR( "out of dynamic memory in yyensure_buffer_stack()" ); /* zero only the new slots.*/ memset(yyg->yy_buffer_stack + yyg->yy_buffer_stack_max, 0, grow_size * sizeof(struct yy_buffer_state*)); yyg->yy_buffer_stack_max = num_to_alloc; } } /** Setup the input buffer state to scan directly from a user-specified character buffer. * @param base the character buffer * @param size the size in bytes of the character buffer * @param yyscanner The scanner object. * @return the newly allocated buffer state object. */ YY_BUFFER_STATE yy_scan_buffer (char * base, yy_size_t size , yyscan_t yyscanner) { YY_BUFFER_STATE b; if ( size < 2 || base[size-2] != YY_END_OF_BUFFER_CHAR || base[size-1] != YY_END_OF_BUFFER_CHAR ) /* They forgot to leave room for the EOB's. */ return NULL; b = (YY_BUFFER_STATE) yyalloc( sizeof( struct yy_buffer_state ) , yyscanner ); if ( ! b ) YY_FATAL_ERROR( "out of dynamic memory in yy_scan_buffer()" ); b->yy_buf_size = (int) (size - 2); /* "- 2" to take care of EOB's */ b->yy_buf_pos = b->yy_ch_buf = base; b->yy_is_our_buffer = 0; b->yy_input_file = NULL; b->yy_n_chars = b->yy_buf_size; b->yy_is_interactive = 0; b->yy_at_bol = 1; b->yy_fill_buffer = 0; b->yy_buffer_status = YY_BUFFER_NEW; yy_switch_to_buffer( b , yyscanner ); return b; } /** Setup the input buffer state to scan a string. The next call to yylex() will * scan from a @e copy of @a str. * @param yystr a NUL-terminated string to scan * @param yyscanner The scanner object. * @return the newly allocated buffer state object. * @note If you want to scan bytes that may contain NUL values, then use * yy_scan_bytes() instead. */ YY_BUFFER_STATE yy_scan_string (const char * yystr , yyscan_t yyscanner) { return yy_scan_bytes( yystr, (int) strlen(yystr) , yyscanner); } /** Setup the input buffer state to scan the given bytes. The next call to yylex() will * scan from a @e copy of @a bytes. * @param yybytes the byte buffer to scan * @param _yybytes_len the number of bytes in the buffer pointed to by @a bytes. * @param yyscanner The scanner object. * @return the newly allocated buffer state object. */ YY_BUFFER_STATE yy_scan_bytes (const char * yybytes, int _yybytes_len , yyscan_t yyscanner) { YY_BUFFER_STATE b; char *buf; yy_size_t n; int i; /* Get memory for full buffer, including space for trailing EOB's. */ n = (yy_size_t) (_yybytes_len + 2); buf = (char *) yyalloc( n , yyscanner ); if ( ! buf ) YY_FATAL_ERROR( "out of dynamic memory in yy_scan_bytes()" ); for ( i = 0; i < _yybytes_len; ++i ) buf[i] = yybytes[i]; buf[_yybytes_len] = buf[_yybytes_len+1] = YY_END_OF_BUFFER_CHAR; b = yy_scan_buffer( buf, n , yyscanner); if ( ! b ) YY_FATAL_ERROR( "bad buffer in yy_scan_bytes()" ); /* It's okay to grow etc. this buffer, and we should throw it * away when we're done. */ b->yy_is_our_buffer = 1; return b; } #ifndef YY_EXIT_FAILURE #define YY_EXIT_FAILURE 2 #endif static void yynoreturn yy_fatal_error (const char* msg , yyscan_t yyscanner) { struct yyguts_t * yyg = (struct yyguts_t*)yyscanner; (void)yyg; fprintf( stderr, "%s\n", msg ); exit( YY_EXIT_FAILURE ); } /* Redefine yyless() so it works in section 3 code. */ #undef yyless #define yyless(n) \ do \ { \ /* Undo effects of setting up yytext. */ \ int yyless_macro_arg = (n); \ YY_LESS_LINENO(yyless_macro_arg);\ yytext[yyleng] = yyg->yy_hold_char; \ yyg->yy_c_buf_p = yytext + yyless_macro_arg; \ yyg->yy_hold_char = *yyg->yy_c_buf_p; \ *yyg->yy_c_buf_p = '\0'; \ yyleng = yyless_macro_arg; \ } \ while ( 0 ) /* Accessor methods (get/set functions) to struct members. */ /** Get the user-defined data for this scanner. * @param yyscanner The scanner object. */ YY_EXTRA_TYPE yyget_extra (yyscan_t yyscanner) { struct yyguts_t * yyg = (struct yyguts_t*)yyscanner; return yyextra; } /** Get the current line number. * @param yyscanner The scanner object. */ int yyget_lineno (yyscan_t yyscanner) { struct yyguts_t * yyg = (struct yyguts_t*)yyscanner; if (! YY_CURRENT_BUFFER) return 0; return yylineno; } /** Get the current column number. * @param yyscanner The scanner object. */ int yyget_column (yyscan_t yyscanner) { struct yyguts_t * yyg = (struct yyguts_t*)yyscanner; if (! YY_CURRENT_BUFFER) return 0; return yycolumn; } /** Get the input stream. * @param yyscanner The scanner object. */ FILE *yyget_in (yyscan_t yyscanner) { struct yyguts_t * yyg = (struct yyguts_t*)yyscanner; return yyin; } /** Get the output stream. * @param yyscanner The scanner object. */ FILE *yyget_out (yyscan_t yyscanner) { struct yyguts_t * yyg = (struct yyguts_t*)yyscanner; return yyout; } /** Get the length of the current token. * @param yyscanner The scanner object. */ int yyget_leng (yyscan_t yyscanner) { struct yyguts_t * yyg = (struct yyguts_t*)yyscanner; return yyleng; } /** Get the current token. * @param yyscanner The scanner object. */ char *yyget_text (yyscan_t yyscanner) { struct yyguts_t * yyg = (struct yyguts_t*)yyscanner; return yytext; } /** Set the user-defined data. This data is never touched by the scanner. * @param user_defined The data to be associated with this scanner. * @param yyscanner The scanner object. */ void yyset_extra (YY_EXTRA_TYPE user_defined , yyscan_t yyscanner) { struct yyguts_t * yyg = (struct yyguts_t*)yyscanner; yyextra = user_defined ; } /** Set the current line number. * @param _line_number line number * @param yyscanner The scanner object. */ void yyset_lineno (int _line_number , yyscan_t yyscanner) { struct yyguts_t * yyg = (struct yyguts_t*)yyscanner; /* lineno is only valid if an input buffer exists. */ if (! YY_CURRENT_BUFFER ) YY_FATAL_ERROR( "yyset_lineno called with no buffer" ); yylineno = _line_number; } /** Set the current column. * @param _column_no column number * @param yyscanner The scanner object. */ void yyset_column (int _column_no , yyscan_t yyscanner) { struct yyguts_t * yyg = (struct yyguts_t*)yyscanner; /* column is only valid if an input buffer exists. */ if (! YY_CURRENT_BUFFER ) YY_FATAL_ERROR( "yyset_column called with no buffer" ); yycolumn = _column_no; } /** Set the input stream. This does not discard the current * input buffer. * @param _in_str A readable stream. * @param yyscanner The scanner object. * @see yy_switch_to_buffer */ void yyset_in (FILE * _in_str , yyscan_t yyscanner) { struct yyguts_t * yyg = (struct yyguts_t*)yyscanner; yyin = _in_str ; } void yyset_out (FILE * _out_str , yyscan_t yyscanner) { struct yyguts_t * yyg = (struct yyguts_t*)yyscanner; yyout = _out_str ; } int yyget_debug (yyscan_t yyscanner) { struct yyguts_t * yyg = (struct yyguts_t*)yyscanner; return yy_flex_debug; } void yyset_debug (int _bdebug , yyscan_t yyscanner) { struct yyguts_t * yyg = (struct yyguts_t*)yyscanner; yy_flex_debug = _bdebug ; } /* Accessor methods for yylval and yylloc */ YYSTYPE * yyget_lval (yyscan_t yyscanner) { struct yyguts_t * yyg = (struct yyguts_t*)yyscanner; return yylval; } void yyset_lval (YYSTYPE * yylval_param , yyscan_t yyscanner) { struct yyguts_t * yyg = (struct yyguts_t*)yyscanner; yylval = yylval_param; } YYLTYPE *yyget_lloc (yyscan_t yyscanner) { struct yyguts_t * yyg = (struct yyguts_t*)yyscanner; return yylloc; } void yyset_lloc (YYLTYPE * yylloc_param , yyscan_t yyscanner) { struct yyguts_t * yyg = (struct yyguts_t*)yyscanner; yylloc = yylloc_param; } /* User-visible API */ /* yylex_init is special because it creates the scanner itself, so it is * the ONLY reentrant function that doesn't take the scanner as the last argument. * That's why we explicitly handle the declaration, instead of using our macros. */ int yylex_init(yyscan_t* ptr_yy_globals) { if (ptr_yy_globals == NULL){ errno = EINVAL; return 1; } *ptr_yy_globals = (yyscan_t) yyalloc ( sizeof( struct yyguts_t ), NULL ); if (*ptr_yy_globals == NULL){ errno = ENOMEM; return 1; } /* By setting to 0xAA, we expose bugs in yy_init_globals. Leave at 0x00 for releases. */ memset(*ptr_yy_globals,0x00,sizeof(struct yyguts_t)); return yy_init_globals ( *ptr_yy_globals ); } /* yylex_init_extra has the same functionality as yylex_init, but follows the * convention of taking the scanner as the last argument. Note however, that * this is a *pointer* to a scanner, as it will be allocated by this call (and * is the reason, too, why this function also must handle its own declaration). * The user defined value in the first argument will be available to yyalloc in * the yyextra field. */ int yylex_init_extra( YY_EXTRA_TYPE yy_user_defined, yyscan_t* ptr_yy_globals ) { struct yyguts_t dummy_yyguts; yyset_extra (yy_user_defined, &dummy_yyguts); if (ptr_yy_globals == NULL){ errno = EINVAL; return 1; } *ptr_yy_globals = (yyscan_t) yyalloc ( sizeof( struct yyguts_t ), &dummy_yyguts ); if (*ptr_yy_globals == NULL){ errno = ENOMEM; return 1; } /* By setting to 0xAA, we expose bugs in yy_init_globals. Leave at 0x00 for releases. */ memset(*ptr_yy_globals,0x00,sizeof(struct yyguts_t)); yyset_extra (yy_user_defined, *ptr_yy_globals); return yy_init_globals ( *ptr_yy_globals ); } static int yy_init_globals (yyscan_t yyscanner) { struct yyguts_t * yyg = (struct yyguts_t*)yyscanner; /* Initialization is the same as for the non-reentrant scanner. * This function is called from yylex_destroy(), so don't allocate here. */ yyg->yy_buffer_stack = NULL; yyg->yy_buffer_stack_top = 0; yyg->yy_buffer_stack_max = 0; yyg->yy_c_buf_p = NULL; yyg->yy_init = 0; yyg->yy_start = 0; yyg->yy_start_stack_ptr = 0; yyg->yy_start_stack_depth = 0; yyg->yy_start_stack = NULL; /* Defined in main.c */ #ifdef YY_STDINIT yyin = stdin; yyout = stdout; #else yyin = NULL; yyout = NULL; #endif /* For future reference: Set errno on error, since we are called by * yylex_init() */ return 0; } /* yylex_destroy is for both reentrant and non-reentrant scanners. */ int yylex_destroy (yyscan_t yyscanner) { struct yyguts_t * yyg = (struct yyguts_t*)yyscanner; /* Pop the buffer stack, destroying each element. */ while(YY_CURRENT_BUFFER){ yy_delete_buffer( YY_CURRENT_BUFFER , yyscanner ); YY_CURRENT_BUFFER_LVALUE = NULL; yypop_buffer_state(yyscanner); } /* Destroy the stack itself. */ yyfree(yyg->yy_buffer_stack , yyscanner); yyg->yy_buffer_stack = NULL; /* Destroy the start condition stack. */ yyfree( yyg->yy_start_stack , yyscanner ); yyg->yy_start_stack = NULL; /* Reset the globals. This is important in a non-reentrant scanner so the next time * yylex() is called, initialization will occur. */ yy_init_globals( yyscanner); /* Destroy the main struct (reentrant only). */ yyfree ( yyscanner , yyscanner ); yyscanner = NULL; return 0; } /* * Internal utility routines. */ #ifndef yytext_ptr static void yy_flex_strncpy (char* s1, const char * s2, int n , yyscan_t yyscanner) { struct yyguts_t * yyg = (struct yyguts_t*)yyscanner; (void)yyg; int i; for ( i = 0; i < n; ++i ) s1[i] = s2[i]; } #endif #ifdef YY_NEED_STRLEN static int yy_flex_strlen (const char * s , yyscan_t yyscanner) { int n; for ( n = 0; s[n]; ++n ) ; return n; } #endif void *yyalloc (yy_size_t size , yyscan_t yyscanner) { struct yyguts_t * yyg = (struct yyguts_t*)yyscanner; (void)yyg; return malloc(size); } void *yyrealloc (void * ptr, yy_size_t size , yyscan_t yyscanner) { struct yyguts_t * yyg = (struct yyguts_t*)yyscanner; (void)yyg; /* The cast to (char *) in the following accommodates both * implementations that use char* generic pointers, and those * that use void* generic pointers. It works with the latter * because both ANSI C and C++ allow castless assignment from * any pointer type to void*, and deal with argument conversions * as though doing an assignment. */ return realloc(ptr, size); } void yyfree (void * ptr , yyscan_t yyscanner) { struct yyguts_t * yyg = (struct yyguts_t*)yyscanner; (void)yyg; free( (char *) ptr ); /* see yyrealloc() for (char *) cast */ } #define YYTABLES_NAME "yytables" #line 87 "lib/command_lex.l" YY_BUFFER_STATE buffer; void set_lexer_string (yyscan_t *scn, const char *string) { *scn = NULL; yylex_init(scn); buffer = yy_scan_string (string, *scn); } void cleanup_lexer (yyscan_t *scn) { // yy_delete_buffer (buffer, *scn); yylex_destroy(*scn); } #endif /* __clang_analyzer__ */ frr-7.2.1/lib/command_lex.h0000644000000000000000000004033313610377563012424 00000000000000#ifndef cmd_yyHEADER_H #define cmd_yyHEADER_H 1 #define cmd_yyIN_HEADER 1 #line 6 "lib/command_lex.h" #ifdef HAVE_CONFIG_H #include "config.h" #endif #line 11 "lib/command_lex.h" #define YY_INT_ALIGNED short int /* A lexical scanner generated by flex */ #define FLEX_SCANNER #define YY_FLEX_MAJOR_VERSION 2 #define YY_FLEX_MINOR_VERSION 6 #define YY_FLEX_SUBMINOR_VERSION 4 #if YY_FLEX_SUBMINOR_VERSION > 0 #define FLEX_BETA #endif #ifdef yy_create_buffer #define cmd_yy_create_buffer_ALREADY_DEFINED #else #define yy_create_buffer cmd_yy_create_buffer #endif #ifdef yy_delete_buffer #define cmd_yy_delete_buffer_ALREADY_DEFINED #else #define yy_delete_buffer cmd_yy_delete_buffer #endif #ifdef yy_scan_buffer #define cmd_yy_scan_buffer_ALREADY_DEFINED #else #define yy_scan_buffer cmd_yy_scan_buffer #endif #ifdef yy_scan_string #define cmd_yy_scan_string_ALREADY_DEFINED #else #define yy_scan_string cmd_yy_scan_string #endif #ifdef yy_scan_bytes #define cmd_yy_scan_bytes_ALREADY_DEFINED #else #define yy_scan_bytes cmd_yy_scan_bytes #endif #ifdef yy_init_buffer #define cmd_yy_init_buffer_ALREADY_DEFINED #else #define yy_init_buffer cmd_yy_init_buffer #endif #ifdef yy_flush_buffer #define cmd_yy_flush_buffer_ALREADY_DEFINED #else #define yy_flush_buffer cmd_yy_flush_buffer #endif #ifdef yy_load_buffer_state #define cmd_yy_load_buffer_state_ALREADY_DEFINED #else #define yy_load_buffer_state cmd_yy_load_buffer_state #endif #ifdef yy_switch_to_buffer #define cmd_yy_switch_to_buffer_ALREADY_DEFINED #else #define yy_switch_to_buffer cmd_yy_switch_to_buffer #endif #ifdef yypush_buffer_state #define cmd_yypush_buffer_state_ALREADY_DEFINED #else #define yypush_buffer_state cmd_yypush_buffer_state #endif #ifdef yypop_buffer_state #define cmd_yypop_buffer_state_ALREADY_DEFINED #else #define yypop_buffer_state cmd_yypop_buffer_state #endif #ifdef yyensure_buffer_stack #define cmd_yyensure_buffer_stack_ALREADY_DEFINED #else #define yyensure_buffer_stack cmd_yyensure_buffer_stack #endif #ifdef yylex #define cmd_yylex_ALREADY_DEFINED #else #define yylex cmd_yylex #endif #ifdef yyrestart #define cmd_yyrestart_ALREADY_DEFINED #else #define yyrestart cmd_yyrestart #endif #ifdef yylex_init #define cmd_yylex_init_ALREADY_DEFINED #else #define yylex_init cmd_yylex_init #endif #ifdef yylex_init_extra #define cmd_yylex_init_extra_ALREADY_DEFINED #else #define yylex_init_extra cmd_yylex_init_extra #endif #ifdef yylex_destroy #define cmd_yylex_destroy_ALREADY_DEFINED #else #define yylex_destroy cmd_yylex_destroy #endif #ifdef yyget_debug #define cmd_yyget_debug_ALREADY_DEFINED #else #define yyget_debug cmd_yyget_debug #endif #ifdef yyset_debug #define cmd_yyset_debug_ALREADY_DEFINED #else #define yyset_debug cmd_yyset_debug #endif #ifdef yyget_extra #define cmd_yyget_extra_ALREADY_DEFINED #else #define yyget_extra cmd_yyget_extra #endif #ifdef yyset_extra #define cmd_yyset_extra_ALREADY_DEFINED #else #define yyset_extra cmd_yyset_extra #endif #ifdef yyget_in #define cmd_yyget_in_ALREADY_DEFINED #else #define yyget_in cmd_yyget_in #endif #ifdef yyset_in #define cmd_yyset_in_ALREADY_DEFINED #else #define yyset_in cmd_yyset_in #endif #ifdef yyget_out #define cmd_yyget_out_ALREADY_DEFINED #else #define yyget_out cmd_yyget_out #endif #ifdef yyset_out #define cmd_yyset_out_ALREADY_DEFINED #else #define yyset_out cmd_yyset_out #endif #ifdef yyget_leng #define cmd_yyget_leng_ALREADY_DEFINED #else #define yyget_leng cmd_yyget_leng #endif #ifdef yyget_text #define cmd_yyget_text_ALREADY_DEFINED #else #define yyget_text cmd_yyget_text #endif #ifdef yyget_lineno #define cmd_yyget_lineno_ALREADY_DEFINED #else #define yyget_lineno cmd_yyget_lineno #endif #ifdef yyset_lineno #define cmd_yyset_lineno_ALREADY_DEFINED #else #define yyset_lineno cmd_yyset_lineno #endif #ifdef yyget_column #define cmd_yyget_column_ALREADY_DEFINED #else #define yyget_column cmd_yyget_column #endif #ifdef yyset_column #define cmd_yyset_column_ALREADY_DEFINED #else #define yyset_column cmd_yyset_column #endif #ifdef yywrap #define cmd_yywrap_ALREADY_DEFINED #else #define yywrap cmd_yywrap #endif #ifdef yyget_lval #define cmd_yyget_lval_ALREADY_DEFINED #else #define yyget_lval cmd_yyget_lval #endif #ifdef yyset_lval #define cmd_yyset_lval_ALREADY_DEFINED #else #define yyset_lval cmd_yyset_lval #endif #ifdef yyget_lloc #define cmd_yyget_lloc_ALREADY_DEFINED #else #define yyget_lloc cmd_yyget_lloc #endif #ifdef yyset_lloc #define cmd_yyset_lloc_ALREADY_DEFINED #else #define yyset_lloc cmd_yyset_lloc #endif #ifdef yyalloc #define cmd_yyalloc_ALREADY_DEFINED #else #define yyalloc cmd_yyalloc #endif #ifdef yyrealloc #define cmd_yyrealloc_ALREADY_DEFINED #else #define yyrealloc cmd_yyrealloc #endif #ifdef yyfree #define cmd_yyfree_ALREADY_DEFINED #else #define yyfree cmd_yyfree #endif /* First, we deal with platform-specific or compiler-specific issues. */ /* begin standard C headers. */ #include #include #include #include /* end standard C headers. */ /* flex integer type definitions */ #ifndef FLEXINT_H #define FLEXINT_H /* C99 systems have . Non-C99 systems may or may not. */ #if defined (__STDC_VERSION__) && __STDC_VERSION__ >= 199901L /* C99 says to define __STDC_LIMIT_MACROS before including stdint.h, * if you want the limit (max/min) macros for int types. */ #ifndef __STDC_LIMIT_MACROS #define __STDC_LIMIT_MACROS 1 #endif #include typedef int8_t flex_int8_t; typedef uint8_t flex_uint8_t; typedef int16_t flex_int16_t; typedef uint16_t flex_uint16_t; typedef int32_t flex_int32_t; typedef uint32_t flex_uint32_t; #else typedef signed char flex_int8_t; typedef short int flex_int16_t; typedef int flex_int32_t; typedef unsigned char flex_uint8_t; typedef unsigned short int flex_uint16_t; typedef unsigned int flex_uint32_t; /* Limits of integral types. */ #ifndef INT8_MIN #define INT8_MIN (-128) #endif #ifndef INT16_MIN #define INT16_MIN (-32767-1) #endif #ifndef INT32_MIN #define INT32_MIN (-2147483647-1) #endif #ifndef INT8_MAX #define INT8_MAX (127) #endif #ifndef INT16_MAX #define INT16_MAX (32767) #endif #ifndef INT32_MAX #define INT32_MAX (2147483647) #endif #ifndef UINT8_MAX #define UINT8_MAX (255U) #endif #ifndef UINT16_MAX #define UINT16_MAX (65535U) #endif #ifndef UINT32_MAX #define UINT32_MAX (4294967295U) #endif #ifndef SIZE_MAX #define SIZE_MAX (~(size_t)0) #endif #endif /* ! C99 */ #endif /* ! FLEXINT_H */ /* begin standard C++ headers. */ /* TODO: this is always defined, so inline it */ #define yyconst const #if defined(__GNUC__) && __GNUC__ >= 3 #define yynoreturn __attribute__((__noreturn__)) #else #define yynoreturn #endif /* An opaque pointer. */ #ifndef YY_TYPEDEF_YY_SCANNER_T #define YY_TYPEDEF_YY_SCANNER_T typedef void* yyscan_t; #endif /* For convenience, these vars (plus the bison vars far below) are macros in the reentrant scanner. */ #define yyin yyg->yyin_r #define yyout yyg->yyout_r #define yyextra yyg->yyextra_r #define yyleng yyg->yyleng_r #define yytext yyg->yytext_r #define yylineno (YY_CURRENT_BUFFER_LVALUE->yy_bs_lineno) #define yycolumn (YY_CURRENT_BUFFER_LVALUE->yy_bs_column) #define yy_flex_debug yyg->yy_flex_debug_r /* Size of default input buffer. */ #ifndef YY_BUF_SIZE #ifdef __ia64__ /* On IA-64, the buffer size is 16k, not 8k. * Moreover, YY_BUF_SIZE is 2*YY_READ_BUF_SIZE in the general case. * Ditto for the __ia64__ case accordingly. */ #define YY_BUF_SIZE 32768 #else #define YY_BUF_SIZE 16384 #endif /* __ia64__ */ #endif #ifndef YY_TYPEDEF_YY_BUFFER_STATE #define YY_TYPEDEF_YY_BUFFER_STATE typedef struct yy_buffer_state *YY_BUFFER_STATE; #endif #ifndef YY_TYPEDEF_YY_SIZE_T #define YY_TYPEDEF_YY_SIZE_T typedef size_t yy_size_t; #endif #ifndef YY_STRUCT_YY_BUFFER_STATE #define YY_STRUCT_YY_BUFFER_STATE struct yy_buffer_state { FILE *yy_input_file; char *yy_ch_buf; /* input buffer */ char *yy_buf_pos; /* current position in input buffer */ /* Size of input buffer in bytes, not including room for EOB * characters. */ int yy_buf_size; /* Number of characters read into yy_ch_buf, not including EOB * characters. */ int yy_n_chars; /* Whether we "own" the buffer - i.e., we know we created it, * and can realloc() it to grow it, and should free() it to * delete it. */ int yy_is_our_buffer; /* Whether this is an "interactive" input source; if so, and * if we're using stdio for input, then we want to use getc() * instead of fread(), to make sure we stop fetching input after * each newline. */ int yy_is_interactive; /* Whether we're considered to be at the beginning of a line. * If so, '^' rules will be active on the next match, otherwise * not. */ int yy_at_bol; int yy_bs_lineno; /**< The line count. */ int yy_bs_column; /**< The column count. */ /* Whether to try to fill the input buffer when we reach the * end of it. */ int yy_fill_buffer; int yy_buffer_status; }; #endif /* !YY_STRUCT_YY_BUFFER_STATE */ void yyrestart ( FILE *input_file , yyscan_t yyscanner ); void yy_switch_to_buffer ( YY_BUFFER_STATE new_buffer , yyscan_t yyscanner ); YY_BUFFER_STATE yy_create_buffer ( FILE *file, int size , yyscan_t yyscanner ); void yy_delete_buffer ( YY_BUFFER_STATE b , yyscan_t yyscanner ); void yy_flush_buffer ( YY_BUFFER_STATE b , yyscan_t yyscanner ); void yypush_buffer_state ( YY_BUFFER_STATE new_buffer , yyscan_t yyscanner ); void yypop_buffer_state ( yyscan_t yyscanner ); YY_BUFFER_STATE yy_scan_buffer ( char *base, yy_size_t size , yyscan_t yyscanner ); YY_BUFFER_STATE yy_scan_string ( const char *yy_str , yyscan_t yyscanner ); YY_BUFFER_STATE yy_scan_bytes ( const char *bytes, int len , yyscan_t yyscanner ); void *yyalloc ( yy_size_t , yyscan_t yyscanner ); void *yyrealloc ( void *, yy_size_t , yyscan_t yyscanner ); void yyfree ( void * , yyscan_t yyscanner ); /* Begin user sect3 */ #define cmd_yywrap(yyscanner) (/*CONSTCOND*/1) #define YY_SKIP_YYWRAP #define yytext_ptr yytext_r #ifdef YY_HEADER_EXPORT_START_CONDITIONS #define INITIAL 0 #endif #ifndef YY_NO_UNISTD_H /* Special case for "unistd.h", since it is non-ANSI. We include it way * down here because we want the user's section 1 to have been scanned first. * The user has a chance to override it with an option. */ #include #endif #ifndef YY_EXTRA_TYPE #define YY_EXTRA_TYPE void * #endif int yylex_init (yyscan_t* scanner); int yylex_init_extra ( YY_EXTRA_TYPE user_defined, yyscan_t* scanner); /* Accessor methods to globals. These are made visible to non-reentrant scanners for convenience. */ int yylex_destroy ( yyscan_t yyscanner ); int yyget_debug ( yyscan_t yyscanner ); void yyset_debug ( int debug_flag , yyscan_t yyscanner ); YY_EXTRA_TYPE yyget_extra ( yyscan_t yyscanner ); void yyset_extra ( YY_EXTRA_TYPE user_defined , yyscan_t yyscanner ); FILE *yyget_in ( yyscan_t yyscanner ); void yyset_in ( FILE * _in_str , yyscan_t yyscanner ); FILE *yyget_out ( yyscan_t yyscanner ); void yyset_out ( FILE * _out_str , yyscan_t yyscanner ); int yyget_leng ( yyscan_t yyscanner ); char *yyget_text ( yyscan_t yyscanner ); int yyget_lineno ( yyscan_t yyscanner ); void yyset_lineno ( int _line_number , yyscan_t yyscanner ); int yyget_column ( yyscan_t yyscanner ); void yyset_column ( int _column_no , yyscan_t yyscanner ); YYSTYPE * yyget_lval ( yyscan_t yyscanner ); void yyset_lval ( YYSTYPE * yylval_param , yyscan_t yyscanner ); YYLTYPE *yyget_lloc ( yyscan_t yyscanner ); void yyset_lloc ( YYLTYPE * yylloc_param , yyscan_t yyscanner ); /* Macros after this point can all be overridden by user definitions in * section 1. */ #ifndef YY_SKIP_YYWRAP #ifdef __cplusplus extern "C" int yywrap ( yyscan_t yyscanner ); #else extern int yywrap ( yyscan_t yyscanner ); #endif #endif #ifndef yytext_ptr static void yy_flex_strncpy ( char *, const char *, int , yyscan_t yyscanner); #endif #ifdef YY_NEED_STRLEN static int yy_flex_strlen ( const char * , yyscan_t yyscanner); #endif #ifndef YY_NO_INPUT #endif /* Amount of stuff to slurp up with each read. */ #ifndef YY_READ_BUF_SIZE #ifdef __ia64__ /* On IA-64, the buffer size is 16k, not 8k */ #define YY_READ_BUF_SIZE 16384 #else #define YY_READ_BUF_SIZE 8192 #endif /* __ia64__ */ #endif /* Number of entries by which start-condition stack grows. */ #ifndef YY_START_STACK_INCR #define YY_START_STACK_INCR 25 #endif /* Default declaration of generated scanner - a define so the user can * easily add parameters. */ #ifndef YY_DECL #define YY_DECL_IS_OURS 1 extern int yylex \ (YYSTYPE * yylval_param, YYLTYPE * yylloc_param , yyscan_t yyscanner); #define YY_DECL int yylex \ (YYSTYPE * yylval_param, YYLTYPE * yylloc_param , yyscan_t yyscanner) #endif /* !YY_DECL */ /* yy_get_previous_state - get the state just before the EOB char was reached */ #undef YY_NEW_FILE #undef YY_FLUSH_BUFFER #undef yy_set_bol #undef yy_new_buffer #undef yy_set_interactive #undef YY_DO_BEFORE_ACTION #ifdef YY_DECL_IS_OURS #undef YY_DECL_IS_OURS #undef YY_DECL #endif #ifndef cmd_yy_create_buffer_ALREADY_DEFINED #undef yy_create_buffer #endif #ifndef cmd_yy_delete_buffer_ALREADY_DEFINED #undef yy_delete_buffer #endif #ifndef cmd_yy_scan_buffer_ALREADY_DEFINED #undef yy_scan_buffer #endif #ifndef cmd_yy_scan_string_ALREADY_DEFINED #undef yy_scan_string #endif #ifndef cmd_yy_scan_bytes_ALREADY_DEFINED #undef yy_scan_bytes #endif #ifndef cmd_yy_init_buffer_ALREADY_DEFINED #undef yy_init_buffer #endif #ifndef cmd_yy_flush_buffer_ALREADY_DEFINED #undef yy_flush_buffer #endif #ifndef cmd_yy_load_buffer_state_ALREADY_DEFINED #undef yy_load_buffer_state #endif #ifndef cmd_yy_switch_to_buffer_ALREADY_DEFINED #undef yy_switch_to_buffer #endif #ifndef cmd_yypush_buffer_state_ALREADY_DEFINED #undef yypush_buffer_state #endif #ifndef cmd_yypop_buffer_state_ALREADY_DEFINED #undef yypop_buffer_state #endif #ifndef cmd_yyensure_buffer_stack_ALREADY_DEFINED #undef yyensure_buffer_stack #endif #ifndef cmd_yylex_ALREADY_DEFINED #undef yylex #endif #ifndef cmd_yyrestart_ALREADY_DEFINED #undef yyrestart #endif #ifndef cmd_yylex_init_ALREADY_DEFINED #undef yylex_init #endif #ifndef cmd_yylex_init_extra_ALREADY_DEFINED #undef yylex_init_extra #endif #ifndef cmd_yylex_destroy_ALREADY_DEFINED #undef yylex_destroy #endif #ifndef cmd_yyget_debug_ALREADY_DEFINED #undef yyget_debug #endif #ifndef cmd_yyset_debug_ALREADY_DEFINED #undef yyset_debug #endif #ifndef cmd_yyget_extra_ALREADY_DEFINED #undef yyget_extra #endif #ifndef cmd_yyset_extra_ALREADY_DEFINED #undef yyset_extra #endif #ifndef cmd_yyget_in_ALREADY_DEFINED #undef yyget_in #endif #ifndef cmd_yyset_in_ALREADY_DEFINED #undef yyset_in #endif #ifndef cmd_yyget_out_ALREADY_DEFINED #undef yyget_out #endif #ifndef cmd_yyset_out_ALREADY_DEFINED #undef yyset_out #endif #ifndef cmd_yyget_leng_ALREADY_DEFINED #undef yyget_leng #endif #ifndef cmd_yyget_text_ALREADY_DEFINED #undef yyget_text #endif #ifndef cmd_yyget_lineno_ALREADY_DEFINED #undef yyget_lineno #endif #ifndef cmd_yyset_lineno_ALREADY_DEFINED #undef yyset_lineno #endif #ifndef cmd_yyget_column_ALREADY_DEFINED #undef yyget_column #endif #ifndef cmd_yyset_column_ALREADY_DEFINED #undef yyset_column #endif #ifndef cmd_yywrap_ALREADY_DEFINED #undef yywrap #endif #ifndef cmd_yyget_lval_ALREADY_DEFINED #undef yyget_lval #endif #ifndef cmd_yyset_lval_ALREADY_DEFINED #undef yyset_lval #endif #ifndef cmd_yyget_lloc_ALREADY_DEFINED #undef yyget_lloc #endif #ifndef cmd_yyset_lloc_ALREADY_DEFINED #undef yyset_lloc #endif #ifndef cmd_yyalloc_ALREADY_DEFINED #undef yyalloc #endif #ifndef cmd_yyrealloc_ALREADY_DEFINED #undef yyrealloc #endif #ifndef cmd_yyfree_ALREADY_DEFINED #undef yyfree #endif #ifndef cmd_yytext_ALREADY_DEFINED #undef yytext #endif #ifndef cmd_yyleng_ALREADY_DEFINED #undef yyleng #endif #ifndef cmd_yyin_ALREADY_DEFINED #undef yyin #endif #ifndef cmd_yyout_ALREADY_DEFINED #undef yyout #endif #ifndef cmd_yy_flex_debug_ALREADY_DEFINED #undef yy_flex_debug #endif #ifndef cmd_yylineno_ALREADY_DEFINED #undef yylineno #endif #ifndef cmd_yytables_fload_ALREADY_DEFINED #undef yytables_fload #endif #ifndef cmd_yytables_destroy_ALREADY_DEFINED #undef yytables_destroy #endif #ifndef cmd_yyTABLES_NAME_ALREADY_DEFINED #undef yyTABLES_NAME #endif #line 87 "lib/command_lex.l" #line 738 "lib/command_lex.h" #undef cmd_yyIN_HEADER #endif /* cmd_yyHEADER_H */ frr-7.2.1/lib/command_lex.l0000644000000000000000000000607113610377563012431 00000000000000/* * Command format string lexer for CLI backend. * * -- * Copyright (C) 2015 Cumulus Networks, Inc. * * This file is part of GNU Zebra. * * GNU Zebra is free software; you can redistribute it and/or modify it * under the terms of the GNU General Public License as published by the * Free Software Foundation; either version 2, or (at your option) any * later version. * * GNU Zebra is distributed in the hope that it will be useful, but * WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU * General Public License for more details. * * You should have received a copy of the GNU General Public License * along with GNU Zebra; see the file COPYING. If not, write to the Free * Software Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA * 02111-1307, USA. */ %top{ #ifdef HAVE_CONFIG_H #include "config.h" #endif } %{ /* ignore flex generated code in static analyzer */ #ifndef __clang_analyzer__ /* ignore harmless bugs in old versions of flex */ #pragma GCC diagnostic ignored "-Wsign-compare" #pragma GCC diagnostic ignored "-Wmissing-prototypes" #include "command_parse.h" #define YY_USER_ACTION yylloc->last_column += yyleng; #define LOC_STEP do { if (yylloc) { \ yylloc->first_column = yylloc->last_column; \ yylloc->first_line = yylloc->last_line; \ } } while(0) %} IPV4 A\.B\.C\.D IPV4_PREFIX A\.B\.C\.D\/M IPV6 X:X::X:X IPV6_PREFIX X:X::X:X\/M MAC X:X:X:X:X:X MAC_PREFIX X:X:X:X:X:X\/M VARIABLE [A-Z][-_A-Z:0-9]+ WORD (\-|\+)?[a-zA-Z0-9\*][-+_a-zA-Z0-9\*]* NUMBER (\-|\+)?[0-9]{1,20} RANGE \({NUMBER}[ ]?\-[ ]?{NUMBER}\) /* yytext shall be a pointer */ %pointer %option noyywrap %option nounput %option noinput %option outfile="lib/command_lex.c" %option header-file="lib/command_lex.h" %option prefix="cmd_yy" %option reentrant %option bison-bridge %option bison-locations %% %{ LOC_STEP; %} [ \t]+ LOC_STEP /* ignore whitespace */; {IPV4} {yylval->string = XSTRDUP(MTYPE_LEX, yytext); return IPV4;} {IPV4_PREFIX} {yylval->string = XSTRDUP(MTYPE_LEX, yytext); return IPV4_PREFIX;} {IPV6} {yylval->string = XSTRDUP(MTYPE_LEX, yytext); return IPV6;} {IPV6_PREFIX} {yylval->string = XSTRDUP(MTYPE_LEX, yytext); return IPV6_PREFIX;} {MAC} {yylval->string = XSTRDUP(MTYPE_LEX, yytext); return MAC;} {MAC_PREFIX} {yylval->string = XSTRDUP(MTYPE_LEX, yytext); return MAC_PREFIX;} {VARIABLE} {yylval->string = XSTRDUP(MTYPE_LEX, yytext); return VARIABLE;} {WORD} {yylval->string = XSTRDUP(MTYPE_LEX, yytext); return WORD;} {RANGE} {yylval->string = XSTRDUP(MTYPE_LEX, yytext); return RANGE;} . {return yytext[0];} %% YY_BUFFER_STATE buffer; void set_lexer_string (yyscan_t *scn, const char *string) { *scn = NULL; yylex_init(scn); buffer = yy_scan_string (string, *scn); } void cleanup_lexer (yyscan_t *scn) { // yy_delete_buffer (buffer, *scn); yylex_destroy(*scn); } #endif /* __clang_analyzer__ */ frr-7.2.1/lib/command_match.c0000644000000000000000000006605013610377563012727 00000000000000/* * Input matching routines for CLI backend. * * -- * Copyright (C) 2016 Cumulus Networks, Inc. * * This file is part of GNU Zebra. * * GNU Zebra is free software; you can redistribute it and/or modify it * under the terms of the GNU General Public License as published by the * Free Software Foundation; either version 2, or (at your option) any * later version. * * GNU Zebra 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; see the file COPYING; if not, write to the Free Software * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA */ #include #include "command_match.h" #include "memory.h" DEFINE_MTYPE_STATIC(LIB, CMD_MATCHSTACK, "Command Match Stack") #ifdef TRACE_MATCHER #define TM 1 #else #define TM 0 #endif #define trace_matcher(...) \ do { \ if (TM) \ fprintf(stderr, __VA_ARGS__); \ } while (0); /* matcher helper prototypes */ static int add_nexthops(struct list *, struct graph_node *, struct graph_node **, size_t); static enum matcher_rv command_match_r(struct graph_node *, vector, unsigned int, struct graph_node **, struct list **); static int score_precedence(enum cmd_token_type); static enum match_type min_match_level(enum cmd_token_type); static void del_arglist(struct list *); static struct cmd_token *disambiguate_tokens(struct cmd_token *, struct cmd_token *, char *); static struct list *disambiguate(struct list *, struct list *, vector, unsigned int); int compare_completions(const void *, const void *); /* token matcher prototypes */ static enum match_type match_token(struct cmd_token *, char *); static enum match_type match_ipv4(const char *); static enum match_type match_ipv4_prefix(const char *); static enum match_type match_ipv6_prefix(const char *, bool); static enum match_type match_range(struct cmd_token *, const char *); static enum match_type match_word(struct cmd_token *, const char *); static enum match_type match_variable(struct cmd_token *, const char *); static enum match_type match_mac(const char *, bool); enum matcher_rv command_match(struct graph *cmdgraph, vector vline, struct list **argv, const struct cmd_element **el) { struct graph_node *stack[CMD_ARGC_MAX]; enum matcher_rv status; *argv = NULL; // prepend a dummy token to match that pesky start node vector vvline = vector_init(vline->alloced + 1); vector_set_index(vvline, 0, (void *)XSTRDUP(MTYPE_TMP, "dummy")); memcpy(vvline->index + 1, vline->index, sizeof(void *) * vline->alloced); vvline->active = vline->active + 1; struct graph_node *start = vector_slot(cmdgraph->nodes, 0); status = command_match_r(start, vvline, 0, stack, argv); if (status == MATCHER_OK) { // successful match struct listnode *head = listhead(*argv); struct listnode *tail = listtail(*argv); assert(head); assert(tail); // delete dummy start node cmd_token_del((struct cmd_token *)head->data); list_delete_node(*argv, head); // get cmd_element out of list tail *el = listgetdata(tail); list_delete_node(*argv, tail); // now argv is an ordered list of cmd_token matching the user // input, with each cmd_token->arg holding the corresponding // input assert(*el); } else if (*argv) { del_arglist(*argv); *argv = NULL; } if (!*el) { trace_matcher("No match\n"); } else { trace_matcher("Matched command\n->string %s\n->desc %s\n", (*el)->string, (*el)->doc); } // free the leader token we alloc'd XFREE(MTYPE_TMP, vector_slot(vvline, 0)); // free vector vector_free(vvline); return status; } /** * Builds an argument list given a DFA and a matching input line. * * First the function determines if the node it is passed matches the first * token of input. If it does not, it returns NULL (MATCHER_NO_MATCH). If it * does match, then it saves the input token as the head of an argument list. * * The next step is to see if there is further input in the input line. If * there is not, the current node's children are searched to see if any of them * are leaves (type END_TKN). If this is the case, then the bottom of the * recursion stack has been reached, the leaf is pushed onto the argument list, * the current node is pushed, and the resulting argument list is * returned (MATCHER_OK). If it is not the case, NULL is returned, indicating * that there is no match for the input along this path (MATCHER_INCOMPLETE). * * If there is further input, then the function recurses on each of the current * node's children, passing them the input line minus the token that was just * matched. For each child, the return value of the recursive call is * inspected. If it is null, then there is no match for the input along the * subgraph headed by that child. If it is not null, then there is at least one * input match in that subgraph (more on this in a moment). * * If a recursive call on a child returns a non-null value, then it has matched * the input given it on the subgraph that starts with that child. However, due * to the flexibility of the grammar, it is sometimes the case that two or more * child graphs match the same input (two or more of the recursive calls have * non-NULL return values). This is not a valid state, since only one true * match is possible. In order to resolve this conflict, the function keeps a * reference to the child node that most specifically matches the input. This * is done by assigning each node type a precedence. If a child is found to * match the remaining input, then the precedence values of the current * best-matching child and this new match are compared. The node with higher * precedence is kept, and the other match is discarded. Due to the recursive * nature of this function, it is only necessary to compare the precedence of * immediate children, since all subsequent children will already have been * disambiguated in this way. * * In the event that two children are found to match with the same precedence, * then the input is ambiguous for the passed cmd_element and NULL is returned. * * @param[in] start the start node. * @param[in] vline the vectorized input line. * @param[in] n the index of the first input token. * @return A linked list of n elements. The first n-1 elements are pointers to * struct cmd_token and represent the sequence of tokens matched by the input. * The ->arg field of each token points to a copy of the input matched on it. * The final nth element is a pointer to struct cmd_element, which is the * command that was matched. * * If no match was found, the return value is NULL. */ static enum matcher_rv command_match_r(struct graph_node *start, vector vline, unsigned int n, struct graph_node **stack, struct list **currbest) { assert(n < vector_active(vline)); enum matcher_rv status = MATCHER_NO_MATCH; // get the minimum match level that can count as a full match struct cmd_token *copy, *token = start->data; enum match_type minmatch = min_match_level(token->type); /* check history/stack of tokens * this disallows matching the same one more than once if there is a * circle in the graph (used for keyword arguments) */ if (n == CMD_ARGC_MAX) return MATCHER_NO_MATCH; if (!token->allowrepeat) for (size_t s = 0; s < n; s++) if (stack[s] == start) return MATCHER_NO_MATCH; // get the current operating input token char *input_token = vector_slot(vline, n); #ifdef TRACE_MATCHER fprintf(stdout, "\"%-20s\" matches \"%-30s\" ? ", input_token, token->text); enum match_type mt = match_token(token, input_token); fprintf(stdout, "type: %d ", token->type); fprintf(stdout, "min: %d - ", minmatch); switch (mt) { case trivial_match: fprintf(stdout, "trivial_match "); break; case no_match: fprintf(stdout, "no_match "); break; case partly_match: fprintf(stdout, "partly_match "); break; case exact_match: fprintf(stdout, "exact_match "); break; } if (mt >= minmatch) fprintf(stdout, " MATCH"); fprintf(stdout, "\n"); #endif // if we don't match this node, die if (match_token(token, input_token) < minmatch) return MATCHER_NO_MATCH; stack[n] = start; // pointers for iterating linklist struct listnode *ln; struct graph_node *gn; // get all possible nexthops struct list *next = list_new(); add_nexthops(next, start, NULL, 0); // determine the best match for (ALL_LIST_ELEMENTS_RO(next, ln, gn)) { // if we've matched all input we're looking for END_TKN if (n + 1 == vector_active(vline)) { struct cmd_token *tok = gn->data; if (tok->type == END_TKN) { // if more than one END_TKN in the follow set if (*currbest) { status = MATCHER_AMBIGUOUS; break; } else { status = MATCHER_OK; } *currbest = list_new(); // node should have one child node with the // element struct graph_node *leaf = vector_slot(gn->to, 0); // last node in the list will hold the // cmd_element; this is important because // list_delete() expects that all nodes have // the same data type, so when deleting this // list the last node must be manually deleted struct cmd_element *el = leaf->data; listnode_add(*currbest, el); (*currbest)->del = (void (*)(void *)) & cmd_token_del; // do not break immediately; continue walking // through the follow set to ensure that there // is exactly one END_TKN } continue; } // else recurse on candidate child node struct list *result = NULL; enum matcher_rv rstat = command_match_r(gn, vline, n + 1, stack, &result); // save the best match if (result && *currbest) { // pick the best of two matches struct list *newbest = disambiguate(*currbest, result, vline, n + 1); // current best and result are ambiguous if (!newbest) status = MATCHER_AMBIGUOUS; // current best is still the best, but ambiguous else if (newbest == *currbest && status == MATCHER_AMBIGUOUS) status = MATCHER_AMBIGUOUS; // result is better, but also ambiguous else if (newbest == result && rstat == MATCHER_AMBIGUOUS) status = MATCHER_AMBIGUOUS; // one or the other is superior and not ambiguous else status = MATCHER_OK; // delete the unnecessary result struct list *todelete = ((newbest && newbest == result) ? *currbest : result); del_arglist(todelete); *currbest = newbest ? newbest : *currbest; } else if (result) { status = rstat; *currbest = result; } else if (!*currbest) { status = MAX(rstat, status); } } if (*currbest) { // copy token, set arg and prepend to currbest token = start->data; copy = cmd_token_dup(token); copy->arg = XSTRDUP(MTYPE_CMD_ARG, input_token); listnode_add_before(*currbest, (*currbest)->head, copy); } else if (n + 1 == vector_active(vline) && status == MATCHER_NO_MATCH) status = MATCHER_INCOMPLETE; // cleanup list_delete(&next); return status; } static void stack_del(void *val) { XFREE(MTYPE_CMD_MATCHSTACK, val); } enum matcher_rv command_complete(struct graph *graph, vector vline, struct list **completions) { // pointer to next input token to match char *input_token; struct list * current = list_new(), // current nodes to match input token against *next = list_new(); // possible next hops after current input // token current->del = next->del = stack_del; // pointers used for iterating lists struct graph_node **gstack, **newstack; struct listnode *node; // add all children of start node to list struct graph_node *start = vector_slot(graph->nodes, 0); add_nexthops(next, start, &start, 0); unsigned int idx; for (idx = 0; idx < vector_active(vline) && next->count > 0; idx++) { list_delete(¤t); current = next; next = list_new(); next->del = stack_del; input_token = vector_slot(vline, idx); int exact_match_exists = 0; for (ALL_LIST_ELEMENTS_RO(current, node, gstack)) if (!exact_match_exists) exact_match_exists = (match_token(gstack[0]->data, input_token) == exact_match); else break; for (ALL_LIST_ELEMENTS_RO(current, node, gstack)) { struct cmd_token *token = gstack[0]->data; if (token->attr == CMD_ATTR_HIDDEN || token->attr == CMD_ATTR_DEPRECATED) continue; enum match_type minmatch = min_match_level(token->type); trace_matcher("\"%s\" matches \"%s\" (%d) ? ", input_token, token->text, token->type); unsigned int last_token = (vector_active(vline) - 1 == idx); enum match_type matchtype = match_token(token, input_token); switch (matchtype) { // occurs when last token is whitespace case trivial_match: trace_matcher("trivial_match\n"); assert(last_token); newstack = XMALLOC(MTYPE_CMD_MATCHSTACK, sizeof(struct graph_node *)); /* we're not recursing here, just the first * element is OK */ newstack[0] = gstack[0]; listnode_add(next, newstack); break; case partly_match: trace_matcher("trivial_match\n"); if (exact_match_exists && !last_token) break; /* fallthru */ case exact_match: trace_matcher("exact_match\n"); if (last_token) { newstack = XMALLOC( MTYPE_CMD_MATCHSTACK, sizeof(struct graph_node *)); /* same as above, not recursing on this */ newstack[0] = gstack[0]; listnode_add(next, newstack); } else if (matchtype >= minmatch) add_nexthops(next, gstack[0], gstack, idx + 1); break; default: trace_matcher("no_match\n"); break; } } } /* Variable summary * ----------------------------------------------------------------- * token = last input token processed * idx = index in `command` of last token processed * current = set of all transitions from the previous input token * next = set of all nodes reachable from all nodes in `matched` */ enum matcher_rv mrv = idx == vector_active(vline) && next->count ? MATCHER_OK : MATCHER_NO_MATCH; *completions = NULL; if (!MATCHER_ERROR(mrv)) { // extract cmd_token into list *completions = list_new(); for (ALL_LIST_ELEMENTS_RO(next, node, gstack)) { listnode_add(*completions, gstack[0]->data); } } list_delete(¤t); list_delete(&next); return mrv; } /** * Adds all children that are reachable by one parser hop to the given list. * special tokens except END_TKN are treated as transparent. * * @param[in] list to add the nexthops to * @param[in] node to start calculating nexthops from * @param[in] stack listing previously visited nodes, if non-NULL. * @param[in] stackpos how many valid entries are in stack * @return the number of children added to the list * * NB: non-null "stack" means that new stacks will be added to "list" as * output, instead of direct node pointers! */ static int add_nexthops(struct list *list, struct graph_node *node, struct graph_node **stack, size_t stackpos) { int added = 0; struct graph_node *child; struct graph_node **nextstack; for (unsigned int i = 0; i < vector_active(node->to); i++) { child = vector_slot(node->to, i); size_t j; struct cmd_token *token = child->data; if (!token->allowrepeat && stack) { for (j = 0; j < stackpos; j++) if (child == stack[j]) break; if (j != stackpos) continue; } if (token->type >= SPECIAL_TKN && token->type != END_TKN) { added += add_nexthops(list, child, stack, stackpos); } else { if (stack) { nextstack = XMALLOC( MTYPE_CMD_MATCHSTACK, (stackpos + 1) * sizeof(struct graph_node *)); nextstack[0] = child; memcpy(nextstack + 1, stack, stackpos * sizeof(struct graph_node *)); listnode_add(list, nextstack); } else listnode_add(list, child); added++; } } return added; } /** * Determines the node types for which a partial match may count as a full * match. Enables command abbrevations. * * @param[in] type node type * @return minimum match level needed to for a token to fully match */ static enum match_type min_match_level(enum cmd_token_type type) { switch (type) { // anything matches a start node, for the sake of recursion case START_TKN: return no_match; // allowing words to partly match enables command abbreviation case WORD_TKN: return partly_match; default: return exact_match; } } /** * Assigns precedence scores to node types. * * @param[in] type node type to score * @return precedence score */ static int score_precedence(enum cmd_token_type type) { switch (type) { // some of these are mutually exclusive, so they share // the same precedence value case IPV4_TKN: case IPV4_PREFIX_TKN: case IPV6_TKN: case IPV6_PREFIX_TKN: case MAC_TKN: case MAC_PREFIX_TKN: case RANGE_TKN: return 2; case WORD_TKN: return 3; case VARIABLE_TKN: return 4; default: return 10; } } /** * Picks the better of two possible matches for a token. * * @param[in] first candidate node matching token * @param[in] second candidate node matching token * @param[in] token the token being matched * @return the best-matching node, or NULL if the two are entirely ambiguous */ static struct cmd_token *disambiguate_tokens(struct cmd_token *first, struct cmd_token *second, char *input_token) { // if the types are different, simply go off of type precedence if (first->type != second->type) { int firstprec = score_precedence(first->type); int secndprec = score_precedence(second->type); if (firstprec != secndprec) return firstprec < secndprec ? first : second; else return NULL; } // if they're the same, return the more exact match enum match_type fmtype = match_token(first, input_token); enum match_type smtype = match_token(second, input_token); if (fmtype != smtype) return fmtype > smtype ? first : second; return NULL; } /** * Picks the better of two possible matches for an input line. * * @param[in] first candidate list of cmd_token matching vline * @param[in] second candidate list of cmd_token matching vline * @param[in] vline the input line being matched * @param[in] n index into vline to start comparing at * @return the best-matching list, or NULL if the two are entirely ambiguous */ static struct list *disambiguate(struct list *first, struct list *second, vector vline, unsigned int n) { assert(first != NULL); assert(second != NULL); // doesn't make sense for these to be inequal length assert(first->count == second->count); assert(first->count == vector_active(vline) - n + 1); struct listnode *fnode = listhead_unchecked(first), *snode = listhead_unchecked(second); struct cmd_token *ftok = listgetdata(fnode), *stok = listgetdata(snode), *best = NULL; // compare each token, if one matches better use that one for (unsigned int i = n; i < vector_active(vline); i++) { char *token = vector_slot(vline, i); if ((best = disambiguate_tokens(ftok, stok, token))) return best == ftok ? first : second; fnode = listnextnode(fnode); snode = listnextnode(snode); ftok = listgetdata(fnode); stok = listgetdata(snode); } return NULL; } /* * Deletion function for arglist. * * Since list->del for arglists expects all listnode->data to hold cmd_token, * but arglists have cmd_element as the data for the tail, this function * manually deletes the tail before deleting the rest of the list as usual. * * The cmd_element at the end is *not* a copy. It is the one and only. * * @param list the arglist to delete */ static void del_arglist(struct list *list) { // manually delete last node struct listnode *tail = listtail(list); tail->data = NULL; list_delete_node(list, tail); // delete the rest of the list as usual list_delete(&list); } /*---------- token level matching functions ----------*/ static enum match_type match_token(struct cmd_token *token, char *input_token) { // nothing trivially matches everything if (!input_token) return trivial_match; switch (token->type) { case WORD_TKN: return match_word(token, input_token); case IPV4_TKN: return match_ipv4(input_token); case IPV4_PREFIX_TKN: return match_ipv4_prefix(input_token); case IPV6_TKN: return match_ipv6_prefix(input_token, false); case IPV6_PREFIX_TKN: return match_ipv6_prefix(input_token, true); case RANGE_TKN: return match_range(token, input_token); case VARIABLE_TKN: return match_variable(token, input_token); case MAC_TKN: return match_mac(input_token, false); case MAC_PREFIX_TKN: return match_mac(input_token, true); case END_TKN: default: return no_match; } } #define IPV4_ADDR_STR "0123456789." #define IPV4_PREFIX_STR "0123456789./" static enum match_type match_ipv4(const char *str) { const char *sp; int dots = 0, nums = 0; char buf[4]; for (;;) { memset(buf, 0, sizeof(buf)); sp = str; while (*str != '\0') { if (*str == '.') { if (dots >= 3) return no_match; if (*(str + 1) == '.') return no_match; if (*(str + 1) == '\0') return partly_match; dots++; break; } if (!isdigit((unsigned char)*str)) return no_match; str++; } if (str - sp > 3) return no_match; memcpy(buf, sp, str - sp); if (atoi(buf) > 255) return no_match; nums++; if (*str == '\0') break; str++; } if (nums < 4) return partly_match; return exact_match; } static enum match_type match_ipv4_prefix(const char *str) { const char *sp; int dots = 0; char buf[4]; for (;;) { memset(buf, 0, sizeof(buf)); sp = str; while (*str != '\0' && *str != '/') { if (*str == '.') { if (dots == 3) return no_match; if (*(str + 1) == '.' || *(str + 1) == '/') return no_match; if (*(str + 1) == '\0') return partly_match; dots++; break; } if (!isdigit((unsigned char)*str)) return no_match; str++; } if (str - sp > 3) return no_match; memcpy(buf, sp, str - sp); if (atoi(buf) > 255) return no_match; if (dots == 3) { if (*str == '/') { if (*(str + 1) == '\0') return partly_match; str++; break; } else if (*str == '\0') return partly_match; } if (*str == '\0') return partly_match; str++; } sp = str; while (*str != '\0') { if (!isdigit((unsigned char)*str)) return no_match; str++; } if (atoi(sp) > 32) return no_match; return exact_match; } #define IPV6_ADDR_STR "0123456789abcdefABCDEF:." #define IPV6_PREFIX_STR "0123456789abcdefABCDEF:./" #define STATE_START 1 #define STATE_COLON 2 #define STATE_DOUBLE 3 #define STATE_ADDR 4 #define STATE_DOT 5 #define STATE_SLASH 6 #define STATE_MASK 7 static enum match_type match_ipv6_prefix(const char *str, bool prefix) { int state = STATE_START; int colons = 0, nums = 0, double_colon = 0; int mask; const char *sp = NULL, *start = str; char *endptr = NULL; if (str == NULL) return partly_match; if (strspn(str, prefix ? IPV6_PREFIX_STR : IPV6_ADDR_STR) != strlen(str)) return no_match; while (*str != '\0' && state != STATE_MASK) { switch (state) { case STATE_START: if (*str == ':') { if (*(str + 1) != ':' && *(str + 1) != '\0') return no_match; colons--; state = STATE_COLON; } else { sp = str; state = STATE_ADDR; } continue; case STATE_COLON: colons++; if (*(str + 1) == '/') return no_match; else if (*(str + 1) == ':') state = STATE_DOUBLE; else { sp = str + 1; state = STATE_ADDR; } break; case STATE_DOUBLE: if (double_colon) return no_match; if (*(str + 1) == ':') return no_match; else { if (*(str + 1) != '\0' && *(str + 1) != '/') colons++; sp = str + 1; if (*(str + 1) == '/') state = STATE_SLASH; else state = STATE_ADDR; } double_colon++; nums += 1; break; case STATE_ADDR: if (*(str + 1) == ':' || *(str + 1) == '.' || *(str + 1) == '\0' || *(str + 1) == '/') { if (str - sp > 3) return no_match; for (; sp <= str; sp++) if (*sp == '/') return no_match; nums++; if (*(str + 1) == ':') state = STATE_COLON; else if (*(str + 1) == '.') { if (colons || double_colon) state = STATE_DOT; else return no_match; } else if (*(str + 1) == '/') state = STATE_SLASH; } break; case STATE_DOT: state = STATE_ADDR; break; case STATE_SLASH: if (*(str + 1) == '\0') return partly_match; state = STATE_MASK; break; default: break; } if (nums > 11) return no_match; if (colons > 7) return no_match; str++; } if (!prefix) { struct sockaddr_in6 sin6_dummy; int ret = inet_pton(AF_INET6, start, &sin6_dummy.sin6_addr); return ret == 1 ? exact_match : partly_match; } if (state < STATE_MASK) return partly_match; mask = strtol(str, &endptr, 10); if (*endptr != '\0') return no_match; if (mask < 0 || mask > 128) return no_match; return exact_match; } static enum match_type match_range(struct cmd_token *token, const char *str) { assert(token->type == RANGE_TKN); char *endptr = NULL; long long val; val = strtoll(str, &endptr, 10); if (*endptr != '\0') return no_match; if (val < token->min || val > token->max) return no_match; else return exact_match; } static enum match_type match_word(struct cmd_token *token, const char *word) { assert(token->type == WORD_TKN); // if the passed token is 0 length, partly match if (!strlen(word)) return partly_match; // if the passed token is strictly a prefix of the full word, partly // match if (strlen(word) < strlen(token->text)) return !strncmp(token->text, word, strlen(word)) ? partly_match : no_match; // if they are the same length and exactly equal, exact match else if (strlen(word) == strlen(token->text)) return !strncmp(token->text, word, strlen(word)) ? exact_match : no_match; return no_match; } static enum match_type match_variable(struct cmd_token *token, const char *word) { assert(token->type == VARIABLE_TKN); return exact_match; } #define MAC_CHARS "ABCDEFabcdef0123456789:" static enum match_type match_mac(const char *word, bool prefix) { /* 6 2-digit hex numbers separated by 5 colons */ size_t mac_explen = 6 * 2 + 5; /* '/' + 2-digit integer */ size_t mask_len = 1 + 2; unsigned int i; char *eptr; unsigned int maskval; /* length check */ if (strlen(word) > mac_explen + (prefix ? mask_len : 0)) return no_match; /* address check */ for (i = 0; i < mac_explen; i++) { if (word[i] == '\0' || !strchr(MAC_CHARS, word[i])) break; if (((i + 1) % 3 == 0) != (word[i] == ':')) return no_match; } /* incomplete address */ if (i < mac_explen && word[i] == '\0') return partly_match; else if (i < mac_explen) return no_match; /* mask check */ if (prefix && word[i] == '/') { if (word[++i] == '\0') return partly_match; maskval = strtoul(&word[i], &eptr, 10); if (*eptr != '\0' || maskval > 48) return no_match; } else if (prefix && word[i] == '\0') { return partly_match; } else if (prefix) { return no_match; } return exact_match; } frr-7.2.1/lib/command_match.h0000644000000000000000000001006313610377563012725 00000000000000/* * Input matching routines for CLI backend. * * -- * Copyright (C) 2016 Cumulus Networks, Inc. * * This file is part of GNU Zebra. * * GNU Zebra is free software; you can redistribute it and/or modify it * under the terms of the GNU General Public License as published by the * Free Software Foundation; either version 2, or (at your option) any * later version. * * GNU Zebra 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; see the file COPYING; if not, write to the Free Software * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA */ #ifndef _ZEBRA_COMMAND_MATCH_H #define _ZEBRA_COMMAND_MATCH_H #include "graph.h" #include "linklist.h" #include "command.h" #ifdef __cplusplus extern "C" { #endif /* These definitions exist in command.c in the current engine but should be * relocated here in the new engine */ enum cmd_filter_type { FILTER_RELAXED, FILTER_STRICT }; /* matcher result value */ enum matcher_rv { MATCHER_NO_MATCH, MATCHER_INCOMPLETE, MATCHER_AMBIGUOUS, MATCHER_OK, }; /* completion match types */ enum match_type { trivial_match, // the input is null no_match, // the input does not match partly_match, // the input matches but is incomplete exact_match // the input matches and is complete }; /* Defines which matcher_rv values constitute an error. Should be used with * matcher_rv return values to do basic error checking. */ #define MATCHER_ERROR(matcher_rv) \ ((matcher_rv) == MATCHER_INCOMPLETE \ || (matcher_rv) == MATCHER_NO_MATCH \ || (matcher_rv) == MATCHER_AMBIGUOUS) /** * Attempt to find an exact command match for a line of user input. * * @param[in] cmdgraph command graph to match against * @param[in] vline vectorized input string * @param[out] argv pointer to argument list if successful match, NULL * otherwise. The elements of this list are pointers to struct cmd_token * and represent the sequence of tokens matched by the inpu. The ->arg * field of each token points to a copy of the input matched on it. These * may be safely deleted or modified. * @param[out] element pointer to matched cmd_element if successful match, * or NULL when MATCHER_ERROR(rv) is true. The cmd_element may *not* be * safely deleted or modified; it is the instance initialized on startup. * @return matcher status */ enum matcher_rv command_match(struct graph *cmdgraph, vector vline, struct list **argv, const struct cmd_element **element); /** * Compiles possible completions for a given line of user input. * * @param[in] start the start node of the DFA to match against * @param[in] vline vectorized input string * @param[out] completions pointer to list of cmd_token representing * acceptable next inputs, or NULL when MATCHER_ERROR(rv) is true. * The elements of this list are pointers to struct cmd_token and take on a * variety of forms depending on the passed vline. If the last element in vline * is NULL, all previous elements are considered to be complete words (the case * when a space is the last token of the line) and completions are generated * based on what could follow that input. If the last element in vline is not * NULL and each sequential element matches the corresponding tokens of one or * more commands exactly (e.g. 'encapv4' and not 'en') the same result is * generated. If the last element is not NULL and the best possible match is a * partial match, then the result generated will be all possible continuations * of that element (e.g. 'encapv4', 'encapv6', etc for input 'en'). * @return matcher status */ enum matcher_rv command_complete(struct graph *cmdgraph, vector vline, struct list **completions); #ifdef __cplusplus } #endif #endif /* _ZEBRA_COMMAND_MATCH_H */ frr-7.2.1/lib/command_parse.c0000644000000000000000000017615513610377563012755 00000000000000/* A Bison parser, made by GNU Bison 3.4.2. */ /* Bison implementation for Yacc-like parsers in C Copyright (C) 1984, 1989-1990, 2000-2015, 2018-2019 Free Software Foundation, Inc. This program is free software: you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation, either version 3 of the License, or (at your option) any later version. This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with this program. If not, see . */ /* As a special exception, you may create a larger work that contains part or all of the Bison parser skeleton and distribute that work under terms of your choice, so long as that work isn't itself a parser generator using the skeleton or a modified version thereof as a parser skeleton. Alternatively, if you modify or redistribute the parser skeleton itself, you may (at your option) remove this special exception, which will cause the skeleton and the resulting Bison output files to be licensed under the GNU General Public License without this special exception. This special exception was added by the Free Software Foundation in version 2.2 of Bison. */ /* C LALR(1) parser skeleton written by Richard Stallman, by simplifying the original so-called "semantic" parser. */ /* All symbols defined below should begin with yy or YY, to avoid infringing on user name space. This should be done even for local variables, as they might otherwise be expanded by user macros. There are some unavoidable exceptions within include files to define necessary library symbols; they are noted "INFRINGES ON USER NAME SPACE" below. */ /* Undocumented macros, especially those whose name start with YY_, are private implementation details. Do not rely on them. */ /* Identify Bison output. */ #define YYBISON 1 /* Bison version. */ #define YYBISON_VERSION "3.4.2" /* Skeleton name. */ #define YYSKELETON_NAME "yacc.c" /* Pure parsers. */ #define YYPURE 2 /* Push parsers. */ #define YYPUSH 0 /* Pull parsers. */ #define YYPULL 1 /* Substitute the type names. */ #define YYSTYPE CMD_YYSTYPE #define YYLTYPE CMD_YYLTYPE /* Substitute the variable and function names. */ #define yyparse cmd_yyparse #define yylex cmd_yylex #define yyerror cmd_yyerror #define yydebug cmd_yydebug #define yynerrs cmd_yynerrs /* First part of user prologue. */ #line 25 "lib/command_parse.y" // compile with debugging facilities #define YYDEBUG 1 #line 83 "lib/command_parse.c" # ifndef YY_NULLPTR # if defined __cplusplus # if 201103L <= __cplusplus # define YY_NULLPTR nullptr # else # define YY_NULLPTR 0 # endif # else # define YY_NULLPTR ((void*)0) # endif # endif /* Enabling verbose error messages. */ #ifdef YYERROR_VERBOSE # undef YYERROR_VERBOSE # define YYERROR_VERBOSE 1 #else # define YYERROR_VERBOSE 1 #endif /* Use api.header.include to #include this header instead of duplicating it here. */ #ifndef YY_CMD_YY_LIB_COMMAND_PARSE_H_INCLUDED # define YY_CMD_YY_LIB_COMMAND_PARSE_H_INCLUDED /* Debug traces. */ #ifndef CMD_YYDEBUG # if defined YYDEBUG #if YYDEBUG # define CMD_YYDEBUG 1 # else # define CMD_YYDEBUG 0 # endif # else /* ! defined YYDEBUG */ # define CMD_YYDEBUG 0 # endif /* ! defined YYDEBUG */ #endif /* ! defined CMD_YYDEBUG */ #if CMD_YYDEBUG extern int cmd_yydebug; #endif /* "%code requires" blocks. */ #line 46 "lib/command_parse.y" #include "config.h" #include #include #include #include #include "command_graph.h" #include "log.h" DECLARE_MTYPE(LEX) #define YYSTYPE CMD_YYSTYPE #define YYLTYPE CMD_YYLTYPE struct parser_ctx; /* subgraph semantic value */ struct subgraph { struct graph_node *start, *end; }; #line 148 "lib/command_parse.c" /* Token type. */ #ifndef CMD_YYTOKENTYPE # define CMD_YYTOKENTYPE enum cmd_yytokentype { WORD = 258, IPV4 = 259, IPV4_PREFIX = 260, IPV6 = 261, IPV6_PREFIX = 262, VARIABLE = 263, RANGE = 264, MAC = 265, MAC_PREFIX = 266 }; #endif /* Tokens. */ #define WORD 258 #define IPV4 259 #define IPV4_PREFIX 260 #define IPV6 261 #define IPV6_PREFIX 262 #define VARIABLE 263 #define RANGE 264 #define MAC 265 #define MAC_PREFIX 266 /* Value type. */ #if ! defined CMD_YYSTYPE && ! defined CMD_YYSTYPE_IS_DECLARED union CMD_YYSTYPE { #line 69 "lib/command_parse.y" long long number; char *string; struct graph_node *node; struct subgraph subgraph; #line 188 "lib/command_parse.c" }; typedef union CMD_YYSTYPE CMD_YYSTYPE; # define CMD_YYSTYPE_IS_TRIVIAL 1 # define CMD_YYSTYPE_IS_DECLARED 1 #endif /* Location type. */ #if ! defined CMD_YYLTYPE && ! defined CMD_YYLTYPE_IS_DECLARED typedef struct CMD_YYLTYPE CMD_YYLTYPE; struct CMD_YYLTYPE { int first_line; int first_column; int last_line; int last_column; }; # define CMD_YYLTYPE_IS_DECLARED 1 # define CMD_YYLTYPE_IS_TRIVIAL 1 #endif int cmd_yyparse (struct parser_ctx *ctx); /* "%code provides" blocks. */ #line 76 "lib/command_parse.y" #ifndef FLEX_SCANNER #include "command_lex.h" #endif extern void set_lexer_string (yyscan_t *scn, const char *string); extern void cleanup_lexer (yyscan_t *scn); struct parser_ctx { yyscan_t scanner; struct cmd_element *el; struct graph *graph; struct graph_node *currnode; /* pointers to copy of command docstring */ char *docstr_start, *docstr; }; #line 235 "lib/command_parse.c" #endif /* !YY_CMD_YY_LIB_COMMAND_PARSE_H_INCLUDED */ /* Unqualified %code blocks. */ #line 121 "lib/command_parse.y" /* bison declarations */ void cmd_yyerror (CMD_YYLTYPE *locp, struct parser_ctx *ctx, char const *msg); /* helper functions for parser */ static const char * doc_next (struct parser_ctx *ctx); static struct graph_node * new_token_node (struct parser_ctx *, enum cmd_token_type type, const char *text, const char *doc); static void terminate_graph (CMD_YYLTYPE *locp, struct parser_ctx *ctx, struct graph_node *); static void cleanup (struct parser_ctx *ctx); static void loopcheck(struct parser_ctx *ctx, struct subgraph *sg); #define scanner ctx->scanner #line 269 "lib/command_parse.c" #ifdef short # undef short #endif #ifdef YYTYPE_UINT8 typedef YYTYPE_UINT8 yytype_uint8; #else typedef unsigned char yytype_uint8; #endif #ifdef YYTYPE_INT8 typedef YYTYPE_INT8 yytype_int8; #else typedef signed char yytype_int8; #endif #ifdef YYTYPE_UINT16 typedef YYTYPE_UINT16 yytype_uint16; #else typedef unsigned short yytype_uint16; #endif #ifdef YYTYPE_INT16 typedef YYTYPE_INT16 yytype_int16; #else typedef short yytype_int16; #endif #ifndef YYSIZE_T # ifdef __SIZE_TYPE__ # define YYSIZE_T __SIZE_TYPE__ # elif defined size_t # define YYSIZE_T size_t # elif ! defined YYSIZE_T # include /* INFRINGES ON USER NAME SPACE */ # define YYSIZE_T size_t # else # define YYSIZE_T unsigned # endif #endif #define YYSIZE_MAXIMUM ((YYSIZE_T) -1) #ifndef YY_ # if defined YYENABLE_NLS && YYENABLE_NLS # if ENABLE_NLS # include /* INFRINGES ON USER NAME SPACE */ # define YY_(Msgid) dgettext ("bison-runtime", Msgid) # endif # endif # ifndef YY_ # define YY_(Msgid) Msgid # endif #endif #ifndef YY_ATTRIBUTE # if (defined __GNUC__ \ && (2 < __GNUC__ || (__GNUC__ == 2 && 96 <= __GNUC_MINOR__))) \ || defined __SUNPRO_C && 0x5110 <= __SUNPRO_C # define YY_ATTRIBUTE(Spec) __attribute__(Spec) # else # define YY_ATTRIBUTE(Spec) /* empty */ # endif #endif #ifndef YY_ATTRIBUTE_PURE # define YY_ATTRIBUTE_PURE YY_ATTRIBUTE ((__pure__)) #endif #ifndef YY_ATTRIBUTE_UNUSED # define YY_ATTRIBUTE_UNUSED YY_ATTRIBUTE ((__unused__)) #endif /* Suppress unused-variable warnings by "using" E. */ #if ! defined lint || defined __GNUC__ # define YYUSE(E) ((void) (E)) #else # define YYUSE(E) /* empty */ #endif #if defined __GNUC__ && ! defined __ICC && 407 <= __GNUC__ * 100 + __GNUC_MINOR__ /* Suppress an incorrect diagnostic about yylval being uninitialized. */ # define YY_IGNORE_MAYBE_UNINITIALIZED_BEGIN \ _Pragma ("GCC diagnostic push") \ _Pragma ("GCC diagnostic ignored \"-Wuninitialized\"")\ _Pragma ("GCC diagnostic ignored \"-Wmaybe-uninitialized\"") # define YY_IGNORE_MAYBE_UNINITIALIZED_END \ _Pragma ("GCC diagnostic pop") #else # define YY_INITIAL_VALUE(Value) Value #endif #ifndef YY_IGNORE_MAYBE_UNINITIALIZED_BEGIN # define YY_IGNORE_MAYBE_UNINITIALIZED_BEGIN # define YY_IGNORE_MAYBE_UNINITIALIZED_END #endif #ifndef YY_INITIAL_VALUE # define YY_INITIAL_VALUE(Value) /* Nothing. */ #endif #define YY_ASSERT(E) ((void) (0 && (E))) #if ! defined yyoverflow || YYERROR_VERBOSE /* The parser invokes alloca or malloc; define the necessary symbols. */ # ifdef YYSTACK_USE_ALLOCA # if YYSTACK_USE_ALLOCA # ifdef __GNUC__ # define YYSTACK_ALLOC __builtin_alloca # elif defined __BUILTIN_VA_ARG_INCR # include /* INFRINGES ON USER NAME SPACE */ # elif defined _AIX # define YYSTACK_ALLOC __alloca # elif defined _MSC_VER # include /* INFRINGES ON USER NAME SPACE */ # define alloca _alloca # else # define YYSTACK_ALLOC alloca # if ! defined _ALLOCA_H && ! defined EXIT_SUCCESS # include /* INFRINGES ON USER NAME SPACE */ /* Use EXIT_SUCCESS as a witness for stdlib.h. */ # ifndef EXIT_SUCCESS # define EXIT_SUCCESS 0 # endif # endif # endif # endif # endif # ifdef YYSTACK_ALLOC /* Pacify GCC's 'empty if-body' warning. */ # define YYSTACK_FREE(Ptr) do { /* empty */; } while (0) # ifndef YYSTACK_ALLOC_MAXIMUM /* The OS might guarantee only one guard page at the bottom of the stack, and a page size can be as small as 4096 bytes. So we cannot safely invoke alloca (N) if N exceeds 4096. Use a slightly smaller number to allow for a few compiler-allocated temporary stack slots. */ # define YYSTACK_ALLOC_MAXIMUM 4032 /* reasonable circa 2006 */ # endif # else # define YYSTACK_ALLOC YYMALLOC # define YYSTACK_FREE YYFREE # ifndef YYSTACK_ALLOC_MAXIMUM # define YYSTACK_ALLOC_MAXIMUM YYSIZE_MAXIMUM # endif # if (defined __cplusplus && ! defined EXIT_SUCCESS \ && ! ((defined YYMALLOC || defined malloc) \ && (defined YYFREE || defined free))) # include /* INFRINGES ON USER NAME SPACE */ # ifndef EXIT_SUCCESS # define EXIT_SUCCESS 0 # endif # endif # ifndef YYMALLOC # define YYMALLOC malloc # if ! defined malloc && ! defined EXIT_SUCCESS void *malloc (YYSIZE_T); /* INFRINGES ON USER NAME SPACE */ # endif # endif # ifndef YYFREE # define YYFREE free # if ! defined free && ! defined EXIT_SUCCESS void free (void *); /* INFRINGES ON USER NAME SPACE */ # endif # endif # endif #endif /* ! defined yyoverflow || YYERROR_VERBOSE */ #if (! defined yyoverflow \ && (! defined __cplusplus \ || (defined CMD_YYLTYPE_IS_TRIVIAL && CMD_YYLTYPE_IS_TRIVIAL \ && defined CMD_YYSTYPE_IS_TRIVIAL && CMD_YYSTYPE_IS_TRIVIAL))) /* A type that is properly aligned for any stack member. */ union yyalloc { yytype_int16 yyss_alloc; YYSTYPE yyvs_alloc; YYLTYPE yyls_alloc; }; /* The size of the maximum gap between one aligned stack and the next. */ # define YYSTACK_GAP_MAXIMUM (sizeof (union yyalloc) - 1) /* The size of an array large to enough to hold all stacks, each with N elements. */ # define YYSTACK_BYTES(N) \ ((N) * (sizeof (yytype_int16) + sizeof (YYSTYPE) + sizeof (YYLTYPE)) \ + 2 * YYSTACK_GAP_MAXIMUM) # define YYCOPY_NEEDED 1 /* Relocate STACK from its old location to the new one. The local variables YYSIZE and YYSTACKSIZE give the old and new number of elements in the stack, and YYPTR gives the new location of the stack. Advance YYPTR to a properly aligned location for the next stack. */ # define YYSTACK_RELOCATE(Stack_alloc, Stack) \ do \ { \ YYSIZE_T yynewbytes; \ YYCOPY (&yyptr->Stack_alloc, Stack, yysize); \ Stack = &yyptr->Stack_alloc; \ yynewbytes = yystacksize * sizeof (*Stack) + YYSTACK_GAP_MAXIMUM; \ yyptr += yynewbytes / sizeof (*yyptr); \ } \ while (0) #endif #if defined YYCOPY_NEEDED && YYCOPY_NEEDED /* Copy COUNT objects from SRC to DST. The source and destination do not overlap. */ # ifndef YYCOPY # if defined __GNUC__ && 1 < __GNUC__ # define YYCOPY(Dst, Src, Count) \ __builtin_memcpy (Dst, Src, (Count) * sizeof (*(Src))) # else # define YYCOPY(Dst, Src, Count) \ do \ { \ YYSIZE_T yyi; \ for (yyi = 0; yyi < (Count); yyi++) \ (Dst)[yyi] = (Src)[yyi]; \ } \ while (0) # endif # endif #endif /* !YYCOPY_NEEDED */ /* YYFINAL -- State number of the termination state. */ #define YYFINAL 3 /* YYLAST -- Last index in YYTABLE. */ #define YYLAST 35 /* YYNTOKENS -- Number of terminals. */ #define YYNTOKENS 21 /* YYNNTS -- Number of nonterminals. */ #define YYNNTS 13 /* YYNRULES -- Number of rules. */ #define YYNRULES 30 /* YYNSTATES -- Number of states. */ #define YYNSTATES 46 #define YYUNDEFTOK 2 #define YYMAXUTOK 266 /* YYTRANSLATE(TOKEN-NUM) -- Symbol number corresponding to TOKEN-NUM as returned by yylex, with out-of-bounds checking. */ #define YYTRANSLATE(YYX) \ ((unsigned) (YYX) <= YYMAXUTOK ? yytranslate[YYX] : YYUNDEFTOK) /* YYTRANSLATE[TOKEN-NUM] -- Symbol number corresponding to TOKEN-NUM as returned by yylex. */ static const yytype_uint8 yytranslate[] = { 0, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 13, 2, 2, 2, 2, 2, 2, 2, 2, 2, 12, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 14, 2, 15, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 19, 2, 20, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 17, 16, 18, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11 }; #if CMD_YYDEBUG /* YYRLINE[YYN] -- Source line where rule number YYN was defined. */ static const yytype_uint16 yyrline[] = { 0, 176, 176, 181, 197, 202, 207, 209, 213, 218, 226, 227, 230, 240, 245, 250, 255, 260, 265, 281, 286, 293, 303, 311, 317, 330, 348, 352, 356, 362, 366 }; #endif #if CMD_YYDEBUG || YYERROR_VERBOSE || 1 /* YYTNAME[SYMBOL-NUM] -- String name of the symbol SYMBOL-NUM. First, the terminals, then, starting at YYNTOKENS, nonterminals. */ static const char *const yytname[] = { "$end", "error", "$undefined", "WORD", "IPV4", "IPV4_PREFIX", "IPV6", "IPV6_PREFIX", "VARIABLE", "RANGE", "MAC", "MAC_PREFIX", "'.'", "'$'", "'<'", "'>'", "'|'", "'{'", "'}'", "'['", "']'", "$accept", "start", "varname_token", "cmd_token_seq", "cmd_token", "simple_token", "literal_token", "placeholder_token_real", "placeholder_token", "selector", "selector_seq_seq", "selector_token", "selector_token_seq", YY_NULLPTR }; #endif # ifdef YYPRINT /* YYTOKNUM[NUM] -- (External) token number corresponding to the (internal) symbol number NUM (which must be that of a token). */ static const yytype_uint16 yytoknum[] = { 0, 256, 257, 258, 259, 260, 261, 262, 263, 264, 265, 266, 46, 36, 60, 62, 124, 123, 125, 91, 93 }; # endif #define YYPACT_NINF -20 #define yypact_value_is_default(Yystate) \ (!!((Yystate) == (-20))) #define YYTABLE_NINF -1 #define yytable_value_is_error(Yytable_value) \ 0 /* YYPACT[STATE-NUM] -- Index in YYTABLE of the portion describing STATE-NUM. */ static const yytype_int8 yypact[] = { -20, 18, -2, -20, 0, -20, -20, -20, -20, -20, -20, -20, -20, -2, -2, -2, -20, -20, -20, 0, 17, -20, 19, -20, -20, -20, -20, 10, -20, -2, 5, -6, -20, 20, -20, 0, -2, -20, 0, 0, 21, -20, -2, -20, -20, -20 }; /* YYDEFACT[STATE-NUM] -- Default reduction number in state STATE-NUM. Performed when YYTABLE does not specify something else to do. Zero means the default is an error. */ static const yytype_uint8 yydefact[] = { 6, 0, 2, 1, 5, 13, 14, 15, 16, 17, 18, 19, 20, 0, 0, 0, 7, 8, 10, 5, 11, 9, 0, 12, 26, 11, 27, 0, 29, 24, 0, 0, 21, 0, 4, 5, 0, 28, 5, 5, 0, 22, 23, 25, 30, 3 }; /* YYPGOTO[NTERM-NUM]. */ static const yytype_int8 yypgoto[] = { -20, -20, -19, -20, -20, 28, -20, -20, 29, 32, 13, -18, -1 }; /* YYDEFGOTO[NTERM-NUM]. */ static const yytype_int8 yydefgoto[] = { -1, 1, 23, 2, 16, 24, 18, 19, 25, 26, 27, 28, 29 }; /* YYTABLE[YYPACT[STATE-NUM]] -- What to do in state STATE-NUM. If positive, shift that token. If negative, reduce the rule whose number is the opposite. If YYTABLE_NINF, syntax error. */ static const yytype_uint8 yytable[] = { 32, 4, 5, 6, 7, 8, 9, 10, 11, 12, 36, 37, 13, 22, 39, 14, 41, 15, 3, 43, 44, 36, 34, 38, 37, 35, 36, 30, 31, 33, 17, 20, 40, 45, 21, 42 }; static const yytype_uint8 yycheck[] = { 19, 3, 4, 5, 6, 7, 8, 9, 10, 11, 16, 29, 14, 13, 20, 17, 35, 19, 0, 38, 39, 16, 3, 18, 42, 15, 16, 14, 15, 12, 2, 2, 12, 12, 2, 36 }; /* YYSTOS[STATE-NUM] -- The (internal number of the) accessing symbol of state STATE-NUM. */ static const yytype_uint8 yystos[] = { 0, 22, 24, 0, 3, 4, 5, 6, 7, 8, 9, 10, 11, 14, 17, 19, 25, 26, 27, 28, 29, 30, 13, 23, 26, 29, 30, 31, 32, 33, 31, 31, 23, 12, 3, 15, 16, 32, 18, 20, 12, 23, 33, 23, 23, 12 }; /* YYR1[YYN] -- Symbol number of symbol that rule YYN derives. */ static const yytype_uint8 yyr1[] = { 0, 21, 22, 22, 23, 23, 24, 24, 25, 25, 26, 26, 27, 28, 28, 28, 28, 28, 28, 28, 28, 29, 30, 31, 31, 30, 32, 32, 33, 33, 30 }; /* YYR2[YYN] -- Number of symbols on the right hand side of rule YYN. */ static const yytype_uint8 yyr2[] = { 0, 2, 1, 5, 2, 0, 0, 2, 1, 1, 1, 1, 2, 1, 1, 1, 1, 1, 1, 1, 1, 2, 4, 3, 1, 4, 1, 1, 2, 1, 4 }; #define yyerrok (yyerrstatus = 0) #define yyclearin (yychar = YYEMPTY) #define YYEMPTY (-2) #define YYEOF 0 #define YYACCEPT goto yyacceptlab #define YYABORT goto yyabortlab #define YYERROR goto yyerrorlab #define YYRECOVERING() (!!yyerrstatus) #define YYBACKUP(Token, Value) \ do \ if (yychar == YYEMPTY) \ { \ yychar = (Token); \ yylval = (Value); \ YYPOPSTACK (yylen); \ yystate = *yyssp; \ goto yybackup; \ } \ else \ { \ yyerror (&yylloc, ctx, YY_("syntax error: cannot back up")); \ YYERROR; \ } \ while (0) /* Error token number */ #define YYTERROR 1 #define YYERRCODE 256 /* YYLLOC_DEFAULT -- Set CURRENT to span from RHS[1] to RHS[N]. If N is 0, then set CURRENT to the empty location which ends the previous symbol: RHS[0] (always defined). */ #ifndef YYLLOC_DEFAULT # define YYLLOC_DEFAULT(Current, Rhs, N) \ do \ if (N) \ { \ (Current).first_line = YYRHSLOC (Rhs, 1).first_line; \ (Current).first_column = YYRHSLOC (Rhs, 1).first_column; \ (Current).last_line = YYRHSLOC (Rhs, N).last_line; \ (Current).last_column = YYRHSLOC (Rhs, N).last_column; \ } \ else \ { \ (Current).first_line = (Current).last_line = \ YYRHSLOC (Rhs, 0).last_line; \ (Current).first_column = (Current).last_column = \ YYRHSLOC (Rhs, 0).last_column; \ } \ while (0) #endif #define YYRHSLOC(Rhs, K) ((Rhs)[K]) /* Enable debugging if requested. */ #if CMD_YYDEBUG # ifndef YYFPRINTF # include /* INFRINGES ON USER NAME SPACE */ # define YYFPRINTF fprintf # endif # define YYDPRINTF(Args) \ do { \ if (yydebug) \ YYFPRINTF Args; \ } while (0) /* YY_LOCATION_PRINT -- Print the location on the stream. This macro was not mandated originally: define only if we know we won't break user code: when these are the locations we know. */ #ifndef YY_LOCATION_PRINT # if defined CMD_YYLTYPE_IS_TRIVIAL && CMD_YYLTYPE_IS_TRIVIAL /* Print *YYLOCP on YYO. Private, do not rely on its existence. */ YY_ATTRIBUTE_UNUSED static int yy_location_print_ (FILE *yyo, YYLTYPE const * const yylocp) { int res = 0; int end_col = 0 != yylocp->last_column ? yylocp->last_column - 1 : 0; if (0 <= yylocp->first_line) { res += YYFPRINTF (yyo, "%d", yylocp->first_line); if (0 <= yylocp->first_column) res += YYFPRINTF (yyo, ".%d", yylocp->first_column); } if (0 <= yylocp->last_line) { if (yylocp->first_line < yylocp->last_line) { res += YYFPRINTF (yyo, "-%d", yylocp->last_line); if (0 <= end_col) res += YYFPRINTF (yyo, ".%d", end_col); } else if (0 <= end_col && yylocp->first_column < end_col) res += YYFPRINTF (yyo, "-%d", end_col); } return res; } # define YY_LOCATION_PRINT(File, Loc) \ yy_location_print_ (File, &(Loc)) # else # define YY_LOCATION_PRINT(File, Loc) ((void) 0) # endif #endif # define YY_SYMBOL_PRINT(Title, Type, Value, Location) \ do { \ if (yydebug) \ { \ YYFPRINTF (stderr, "%s ", Title); \ yy_symbol_print (stderr, \ Type, Value, Location, ctx); \ YYFPRINTF (stderr, "\n"); \ } \ } while (0) /*-----------------------------------. | Print this symbol's value on YYO. | `-----------------------------------*/ static void yy_symbol_value_print (FILE *yyo, int yytype, YYSTYPE const * const yyvaluep, YYLTYPE const * const yylocationp, struct parser_ctx *ctx) { FILE *yyoutput = yyo; YYUSE (yyoutput); YYUSE (yylocationp); YYUSE (ctx); if (!yyvaluep) return; # ifdef YYPRINT if (yytype < YYNTOKENS) YYPRINT (yyo, yytoknum[yytype], *yyvaluep); # endif YY_IGNORE_MAYBE_UNINITIALIZED_BEGIN YYUSE (yytype); YY_IGNORE_MAYBE_UNINITIALIZED_END } /*---------------------------. | Print this symbol on YYO. | `---------------------------*/ static void yy_symbol_print (FILE *yyo, int yytype, YYSTYPE const * const yyvaluep, YYLTYPE const * const yylocationp, struct parser_ctx *ctx) { YYFPRINTF (yyo, "%s %s (", yytype < YYNTOKENS ? "token" : "nterm", yytname[yytype]); YY_LOCATION_PRINT (yyo, *yylocationp); YYFPRINTF (yyo, ": "); yy_symbol_value_print (yyo, yytype, yyvaluep, yylocationp, ctx); YYFPRINTF (yyo, ")"); } /*------------------------------------------------------------------. | yy_stack_print -- Print the state stack from its BOTTOM up to its | | TOP (included). | `------------------------------------------------------------------*/ static void yy_stack_print (yytype_int16 *yybottom, yytype_int16 *yytop) { YYFPRINTF (stderr, "Stack now"); for (; yybottom <= yytop; yybottom++) { int yybot = *yybottom; YYFPRINTF (stderr, " %d", yybot); } YYFPRINTF (stderr, "\n"); } # define YY_STACK_PRINT(Bottom, Top) \ do { \ if (yydebug) \ yy_stack_print ((Bottom), (Top)); \ } while (0) /*------------------------------------------------. | Report that the YYRULE is going to be reduced. | `------------------------------------------------*/ static void yy_reduce_print (yytype_int16 *yyssp, YYSTYPE *yyvsp, YYLTYPE *yylsp, int yyrule, struct parser_ctx *ctx) { unsigned long yylno = yyrline[yyrule]; int yynrhs = yyr2[yyrule]; int yyi; YYFPRINTF (stderr, "Reducing stack by rule %d (line %lu):\n", yyrule - 1, yylno); /* The symbols being reduced. */ for (yyi = 0; yyi < yynrhs; yyi++) { YYFPRINTF (stderr, " $%d = ", yyi + 1); yy_symbol_print (stderr, yystos[yyssp[yyi + 1 - yynrhs]], &yyvsp[(yyi + 1) - (yynrhs)] , &(yylsp[(yyi + 1) - (yynrhs)]) , ctx); YYFPRINTF (stderr, "\n"); } } # define YY_REDUCE_PRINT(Rule) \ do { \ if (yydebug) \ yy_reduce_print (yyssp, yyvsp, yylsp, Rule, ctx); \ } while (0) /* Nonzero means print parse trace. It is left uninitialized so that multiple parsers can coexist. */ int yydebug; #else /* !CMD_YYDEBUG */ # define YYDPRINTF(Args) # define YY_SYMBOL_PRINT(Title, Type, Value, Location) # define YY_STACK_PRINT(Bottom, Top) # define YY_REDUCE_PRINT(Rule) #endif /* !CMD_YYDEBUG */ /* YYINITDEPTH -- initial size of the parser's stacks. */ #ifndef YYINITDEPTH # define YYINITDEPTH 200 #endif /* YYMAXDEPTH -- maximum size the stacks can grow to (effective only if the built-in stack extension method is used). Do not make this value too large; the results are undefined if YYSTACK_ALLOC_MAXIMUM < YYSTACK_BYTES (YYMAXDEPTH) evaluated with infinite-precision integer arithmetic. */ #ifndef YYMAXDEPTH # define YYMAXDEPTH 10000 #endif #if YYERROR_VERBOSE # ifndef yystrlen # if defined __GLIBC__ && defined _STRING_H # define yystrlen strlen # else /* Return the length of YYSTR. */ static YYSIZE_T yystrlen (const char *yystr) { YYSIZE_T yylen; for (yylen = 0; yystr[yylen]; yylen++) continue; return yylen; } # endif # endif # ifndef yystpcpy # if defined __GLIBC__ && defined _STRING_H && defined _GNU_SOURCE # define yystpcpy stpcpy # else /* Copy YYSRC to YYDEST, returning the address of the terminating '\0' in YYDEST. */ static char * yystpcpy (char *yydest, const char *yysrc) { char *yyd = yydest; const char *yys = yysrc; while ((*yyd++ = *yys++) != '\0') continue; return yyd - 1; } # endif # endif # ifndef yytnamerr /* Copy to YYRES the contents of YYSTR after stripping away unnecessary quotes and backslashes, so that it's suitable for yyerror. The heuristic is that double-quoting is unnecessary unless the string contains an apostrophe, a comma, or backslash (other than backslash-backslash). YYSTR is taken from yytname. If YYRES is null, do not copy; instead, return the length of what the result would have been. */ static YYSIZE_T yytnamerr (char *yyres, const char *yystr) { if (*yystr == '"') { YYSIZE_T yyn = 0; char const *yyp = yystr; for (;;) switch (*++yyp) { case '\'': case ',': goto do_not_strip_quotes; case '\\': if (*++yyp != '\\') goto do_not_strip_quotes; else goto append; append: default: if (yyres) yyres[yyn] = *yyp; yyn++; break; case '"': if (yyres) yyres[yyn] = '\0'; return yyn; } do_not_strip_quotes: ; } if (! yyres) return yystrlen (yystr); return (YYSIZE_T) (yystpcpy (yyres, yystr) - yyres); } # endif /* Copy into *YYMSG, which is of size *YYMSG_ALLOC, an error message about the unexpected token YYTOKEN for the state stack whose top is YYSSP. Return 0 if *YYMSG was successfully written. Return 1 if *YYMSG is not large enough to hold the message. In that case, also set *YYMSG_ALLOC to the required number of bytes. Return 2 if the required number of bytes is too large to store. */ static int yysyntax_error (YYSIZE_T *yymsg_alloc, char **yymsg, yytype_int16 *yyssp, int yytoken) { YYSIZE_T yysize0 = yytnamerr (YY_NULLPTR, yytname[yytoken]); YYSIZE_T yysize = yysize0; enum { YYERROR_VERBOSE_ARGS_MAXIMUM = 5 }; /* Internationalized format string. */ const char *yyformat = YY_NULLPTR; /* Arguments of yyformat. */ char const *yyarg[YYERROR_VERBOSE_ARGS_MAXIMUM]; /* Number of reported tokens (one for the "unexpected", one per "expected"). */ int yycount = 0; /* There are many possibilities here to consider: - If this state is a consistent state with a default action, then the only way this function was invoked is if the default action is an error action. In that case, don't check for expected tokens because there are none. - The only way there can be no lookahead present (in yychar) is if this state is a consistent state with a default action. Thus, detecting the absence of a lookahead is sufficient to determine that there is no unexpected or expected token to report. In that case, just report a simple "syntax error". - Don't assume there isn't a lookahead just because this state is a consistent state with a default action. There might have been a previous inconsistent state, consistent state with a non-default action, or user semantic action that manipulated yychar. - Of course, the expected token list depends on states to have correct lookahead information, and it depends on the parser not to perform extra reductions after fetching a lookahead from the scanner and before detecting a syntax error. Thus, state merging (from LALR or IELR) and default reductions corrupt the expected token list. However, the list is correct for canonical LR with one exception: it will still contain any token that will not be accepted due to an error action in a later state. */ if (yytoken != YYEMPTY) { int yyn = yypact[*yyssp]; yyarg[yycount++] = yytname[yytoken]; if (!yypact_value_is_default (yyn)) { /* Start YYX at -YYN if negative to avoid negative indexes in YYCHECK. In other words, skip the first -YYN actions for this state because they are default actions. */ int yyxbegin = yyn < 0 ? -yyn : 0; /* Stay within bounds of both yycheck and yytname. */ int yychecklim = YYLAST - yyn + 1; int yyxend = yychecklim < YYNTOKENS ? yychecklim : YYNTOKENS; int yyx; for (yyx = yyxbegin; yyx < yyxend; ++yyx) if (yycheck[yyx + yyn] == yyx && yyx != YYTERROR && !yytable_value_is_error (yytable[yyx + yyn])) { if (yycount == YYERROR_VERBOSE_ARGS_MAXIMUM) { yycount = 1; yysize = yysize0; break; } yyarg[yycount++] = yytname[yyx]; { YYSIZE_T yysize1 = yysize + yytnamerr (YY_NULLPTR, yytname[yyx]); if (yysize <= yysize1 && yysize1 <= YYSTACK_ALLOC_MAXIMUM) yysize = yysize1; else return 2; } } } } switch (yycount) { # define YYCASE_(N, S) \ case N: \ yyformat = S; \ break default: /* Avoid compiler warnings. */ YYCASE_(0, YY_("syntax error")); YYCASE_(1, YY_("syntax error, unexpected %s")); YYCASE_(2, YY_("syntax error, unexpected %s, expecting %s")); YYCASE_(3, YY_("syntax error, unexpected %s, expecting %s or %s")); YYCASE_(4, YY_("syntax error, unexpected %s, expecting %s or %s or %s")); YYCASE_(5, YY_("syntax error, unexpected %s, expecting %s or %s or %s or %s")); # undef YYCASE_ } { YYSIZE_T yysize1 = yysize + yystrlen (yyformat); if (yysize <= yysize1 && yysize1 <= YYSTACK_ALLOC_MAXIMUM) yysize = yysize1; else return 2; } if (*yymsg_alloc < yysize) { *yymsg_alloc = 2 * yysize; if (! (yysize <= *yymsg_alloc && *yymsg_alloc <= YYSTACK_ALLOC_MAXIMUM)) *yymsg_alloc = YYSTACK_ALLOC_MAXIMUM; return 1; } /* Avoid sprintf, as that infringes on the user's name space. Don't have undefined behavior even if the translation produced a string with the wrong number of "%s"s. */ { char *yyp = *yymsg; int yyi = 0; while ((*yyp = *yyformat) != '\0') if (*yyp == '%' && yyformat[1] == 's' && yyi < yycount) { yyp += yytnamerr (yyp, yyarg[yyi++]); yyformat += 2; } else { yyp++; yyformat++; } } return 0; } #endif /* YYERROR_VERBOSE */ /*-----------------------------------------------. | Release the memory associated to this symbol. | `-----------------------------------------------*/ static void yydestruct (const char *yymsg, int yytype, YYSTYPE *yyvaluep, YYLTYPE *yylocationp, struct parser_ctx *ctx) { YYUSE (yyvaluep); YYUSE (yylocationp); YYUSE (ctx); if (!yymsg) yymsg = "Deleting"; YY_SYMBOL_PRINT (yymsg, yytype, yyvaluep, yylocationp); YY_IGNORE_MAYBE_UNINITIALIZED_BEGIN YYUSE (yytype); YY_IGNORE_MAYBE_UNINITIALIZED_END } /*----------. | yyparse. | `----------*/ int yyparse (struct parser_ctx *ctx) { /* The lookahead symbol. */ int yychar; /* The semantic value of the lookahead symbol. */ /* Default value used for initialization, for pacifying older GCCs or non-GCC compilers. */ YY_INITIAL_VALUE (static YYSTYPE yyval_default;) YYSTYPE yylval YY_INITIAL_VALUE (= yyval_default); /* Location data for the lookahead symbol. */ static YYLTYPE yyloc_default # if defined CMD_YYLTYPE_IS_TRIVIAL && CMD_YYLTYPE_IS_TRIVIAL = { 1, 1, 1, 1 } # endif ; YYLTYPE yylloc = yyloc_default; /* Number of syntax errors so far. */ int yynerrs; int yystate; /* Number of tokens to shift before error messages enabled. */ int yyerrstatus; /* The stacks and their tools: 'yyss': related to states. 'yyvs': related to semantic values. 'yyls': related to locations. Refer to the stacks through separate pointers, to allow yyoverflow to reallocate them elsewhere. */ /* The state stack. */ yytype_int16 yyssa[YYINITDEPTH]; yytype_int16 *yyss; yytype_int16 *yyssp; /* The semantic value stack. */ YYSTYPE yyvsa[YYINITDEPTH]; YYSTYPE *yyvs; YYSTYPE *yyvsp; /* The location stack. */ YYLTYPE yylsa[YYINITDEPTH]; YYLTYPE *yyls; YYLTYPE *yylsp; /* The locations where the error started and ended. */ YYLTYPE yyerror_range[3]; YYSIZE_T yystacksize; int yyn; int yyresult; /* Lookahead token as an internal (translated) token number. */ int yytoken = 0; /* The variables used to return semantic value and location from the action routines. */ YYSTYPE yyval; YYLTYPE yyloc; #if YYERROR_VERBOSE /* Buffer for error messages, and its allocated size. */ char yymsgbuf[128]; char *yymsg = yymsgbuf; YYSIZE_T yymsg_alloc = sizeof yymsgbuf; #endif #define YYPOPSTACK(N) (yyvsp -= (N), yyssp -= (N), yylsp -= (N)) /* The number of symbols on the RHS of the reduced rule. Keep to zero when no symbol should be popped. */ int yylen = 0; yyssp = yyss = yyssa; yyvsp = yyvs = yyvsa; yylsp = yyls = yylsa; yystacksize = YYINITDEPTH; YYDPRINTF ((stderr, "Starting parse\n")); yystate = 0; yyerrstatus = 0; yynerrs = 0; yychar = YYEMPTY; /* Cause a token to be read. */ /* User initialization code. */ #line 154 "lib/command_parse.y" { /* clear state pointers */ ctx->currnode = vector_slot (ctx->graph->nodes, 0); /* copy docstring and keep a pointer to the copy */ if (ctx->el->doc) { // allocate a new buffer, making room for a flag size_t length = (size_t) strlen (ctx->el->doc) + 2; ctx->docstr = malloc (length); memcpy (ctx->docstr, ctx->el->doc, strlen (ctx->el->doc)); // set the flag so doc_next knows when to print a warning ctx->docstr[length - 2] = 0x03; // null terminate ctx->docstr[length - 1] = 0x00; } ctx->docstr_start = ctx->docstr; } #line 1307 "lib/command_parse.c" yylsp[0] = yylloc; goto yysetstate; /*------------------------------------------------------------. | yynewstate -- push a new state, which is found in yystate. | `------------------------------------------------------------*/ yynewstate: /* In all cases, when you get here, the value and location stacks have just been pushed. So pushing a state here evens the stacks. */ yyssp++; /*--------------------------------------------------------------------. | yynewstate -- set current state (the top of the stack) to yystate. | `--------------------------------------------------------------------*/ yysetstate: YYDPRINTF ((stderr, "Entering state %d\n", yystate)); YY_ASSERT (0 <= yystate && yystate < YYNSTATES); *yyssp = (yytype_int16) yystate; if (yyss + yystacksize - 1 <= yyssp) #if !defined yyoverflow && !defined YYSTACK_RELOCATE goto yyexhaustedlab; #else { /* Get the current used size of the three stacks, in elements. */ YYSIZE_T yysize = (YYSIZE_T) (yyssp - yyss + 1); # if defined yyoverflow { /* Give user a chance to reallocate the stack. Use copies of these so that the &'s don't force the real ones into memory. */ YYSTYPE *yyvs1 = yyvs; yytype_int16 *yyss1 = yyss; YYLTYPE *yyls1 = yyls; /* Each stack pointer address is followed by the size of the data in use in that stack, in bytes. This used to be a conditional around just the two extra args, but that might be undefined if yyoverflow is a macro. */ yyoverflow (YY_("memory exhausted"), &yyss1, yysize * sizeof (*yyssp), &yyvs1, yysize * sizeof (*yyvsp), &yyls1, yysize * sizeof (*yylsp), &yystacksize); yyss = yyss1; yyvs = yyvs1; yyls = yyls1; } # else /* defined YYSTACK_RELOCATE */ /* Extend the stack our own way. */ if (YYMAXDEPTH <= yystacksize) goto yyexhaustedlab; yystacksize *= 2; if (YYMAXDEPTH < yystacksize) yystacksize = YYMAXDEPTH; { yytype_int16 *yyss1 = yyss; union yyalloc *yyptr = (union yyalloc *) YYSTACK_ALLOC (YYSTACK_BYTES (yystacksize)); if (! yyptr) goto yyexhaustedlab; YYSTACK_RELOCATE (yyss_alloc, yyss); YYSTACK_RELOCATE (yyvs_alloc, yyvs); YYSTACK_RELOCATE (yyls_alloc, yyls); # undef YYSTACK_RELOCATE if (yyss1 != yyssa) YYSTACK_FREE (yyss1); } # endif yyssp = yyss + yysize - 1; yyvsp = yyvs + yysize - 1; yylsp = yyls + yysize - 1; YYDPRINTF ((stderr, "Stack size increased to %lu\n", (unsigned long) yystacksize)); if (yyss + yystacksize - 1 <= yyssp) YYABORT; } #endif /* !defined yyoverflow && !defined YYSTACK_RELOCATE */ if (yystate == YYFINAL) YYACCEPT; goto yybackup; /*-----------. | yybackup. | `-----------*/ yybackup: /* Do appropriate processing given the current state. Read a lookahead token if we need one and don't already have one. */ /* First try to decide what to do without reference to lookahead token. */ yyn = yypact[yystate]; if (yypact_value_is_default (yyn)) goto yydefault; /* Not known => get a lookahead token if don't already have one. */ /* YYCHAR is either YYEMPTY or YYEOF or a valid lookahead symbol. */ if (yychar == YYEMPTY) { YYDPRINTF ((stderr, "Reading a token: ")); yychar = yylex (&yylval, &yylloc, scanner); } if (yychar <= YYEOF) { yychar = yytoken = YYEOF; YYDPRINTF ((stderr, "Now at end of input.\n")); } else { yytoken = YYTRANSLATE (yychar); YY_SYMBOL_PRINT ("Next token is", yytoken, &yylval, &yylloc); } /* If the proper action on seeing token YYTOKEN is to reduce or to detect an error, take that action. */ yyn += yytoken; if (yyn < 0 || YYLAST < yyn || yycheck[yyn] != yytoken) goto yydefault; yyn = yytable[yyn]; if (yyn <= 0) { if (yytable_value_is_error (yyn)) goto yyerrlab; yyn = -yyn; goto yyreduce; } /* Count tokens shifted since error; after three, turn off error status. */ if (yyerrstatus) yyerrstatus--; /* Shift the lookahead token. */ YY_SYMBOL_PRINT ("Shifting", yytoken, &yylval, &yylloc); /* Discard the shifted token. */ yychar = YYEMPTY; yystate = yyn; YY_IGNORE_MAYBE_UNINITIALIZED_BEGIN *++yyvsp = yylval; YY_IGNORE_MAYBE_UNINITIALIZED_END *++yylsp = yylloc; goto yynewstate; /*-----------------------------------------------------------. | yydefault -- do the default action for the current state. | `-----------------------------------------------------------*/ yydefault: yyn = yydefact[yystate]; if (yyn == 0) goto yyerrlab; goto yyreduce; /*-----------------------------. | yyreduce -- do a reduction. | `-----------------------------*/ yyreduce: /* yyn is the number of a rule to reduce with. */ yylen = yyr2[yyn]; /* If YYLEN is nonzero, implement the default value of the action: '$$ = $1'. Otherwise, the following line sets YYVAL to garbage. This behavior is undocumented and Bison users should not rely upon it. Assigning to YYVAL unconditionally makes the parser a bit smaller, and it avoids a GCC warning that YYVAL may be used uninitialized. */ yyval = yyvsp[1-yylen]; /* Default location. */ YYLLOC_DEFAULT (yyloc, (yylsp - yylen), yylen); yyerror_range[1] = yyloc; YY_REDUCE_PRINT (yyn); switch (yyn) { case 2: #line 177 "lib/command_parse.y" { // tack on the command element terminate_graph (&(yylsp[0]), ctx, ctx->currnode); } #line 1505 "lib/command_parse.c" break; case 3: #line 182 "lib/command_parse.y" { if ((ctx->currnode = graph_add_edge (ctx->currnode, (yyvsp[-3].node))) != (yyvsp[-3].node)) graph_delete_node (ctx->graph, (yyvsp[-3].node)); ((struct cmd_token *)ctx->currnode->data)->allowrepeat = 1; // adding a node as a child of itself accepts any number // of the same token, which is what we want for variadics graph_add_edge (ctx->currnode, ctx->currnode); // tack on the command element terminate_graph (&(yylsp[-4]), ctx, ctx->currnode); } #line 1523 "lib/command_parse.c" break; case 4: #line 198 "lib/command_parse.y" { (yyval.string) = (yyvsp[0].string); } #line 1531 "lib/command_parse.c" break; case 5: #line 202 "lib/command_parse.y" { (yyval.string) = NULL; } #line 1539 "lib/command_parse.c" break; case 8: #line 214 "lib/command_parse.y" { if ((ctx->currnode = graph_add_edge (ctx->currnode, (yyvsp[0].node))) != (yyvsp[0].node)) graph_delete_node (ctx->graph, (yyvsp[0].node)); } #line 1548 "lib/command_parse.c" break; case 9: #line 219 "lib/command_parse.y" { graph_add_edge (ctx->currnode, (yyvsp[0].subgraph).start); ctx->currnode = (yyvsp[0].subgraph).end; } #line 1557 "lib/command_parse.c" break; case 12: #line 231 "lib/command_parse.y" { (yyval.node) = new_token_node (ctx, WORD_TKN, (yyvsp[-1].string), doc_next(ctx)); cmd_token_varname_set ((yyval.node)->data, (yyvsp[0].string)); XFREE (MTYPE_LEX, (yyvsp[0].string)); XFREE (MTYPE_LEX, (yyvsp[-1].string)); } #line 1568 "lib/command_parse.c" break; case 13: #line 241 "lib/command_parse.y" { (yyval.node) = new_token_node (ctx, IPV4_TKN, (yyvsp[0].string), doc_next(ctx)); XFREE (MTYPE_LEX, (yyvsp[0].string)); } #line 1577 "lib/command_parse.c" break; case 14: #line 246 "lib/command_parse.y" { (yyval.node) = new_token_node (ctx, IPV4_PREFIX_TKN, (yyvsp[0].string), doc_next(ctx)); XFREE (MTYPE_LEX, (yyvsp[0].string)); } #line 1586 "lib/command_parse.c" break; case 15: #line 251 "lib/command_parse.y" { (yyval.node) = new_token_node (ctx, IPV6_TKN, (yyvsp[0].string), doc_next(ctx)); XFREE (MTYPE_LEX, (yyvsp[0].string)); } #line 1595 "lib/command_parse.c" break; case 16: #line 256 "lib/command_parse.y" { (yyval.node) = new_token_node (ctx, IPV6_PREFIX_TKN, (yyvsp[0].string), doc_next(ctx)); XFREE (MTYPE_LEX, (yyvsp[0].string)); } #line 1604 "lib/command_parse.c" break; case 17: #line 261 "lib/command_parse.y" { (yyval.node) = new_token_node (ctx, VARIABLE_TKN, (yyvsp[0].string), doc_next(ctx)); XFREE (MTYPE_LEX, (yyvsp[0].string)); } #line 1613 "lib/command_parse.c" break; case 18: #line 266 "lib/command_parse.y" { (yyval.node) = new_token_node (ctx, RANGE_TKN, (yyvsp[0].string), doc_next(ctx)); struct cmd_token *token = (yyval.node)->data; // get the numbers out yylval.string++; token->min = strtoll (yylval.string, &yylval.string, 10); strsep (&yylval.string, "-"); token->max = strtoll (yylval.string, &yylval.string, 10); // validate range if (token->min > token->max) cmd_yyerror (&(yylsp[0]), ctx, "Invalid range."); XFREE (MTYPE_LEX, (yyvsp[0].string)); } #line 1633 "lib/command_parse.c" break; case 19: #line 282 "lib/command_parse.y" { (yyval.node) = new_token_node (ctx, MAC_TKN, (yyvsp[0].string), doc_next(ctx)); XFREE (MTYPE_LEX, (yyvsp[0].string)); } #line 1642 "lib/command_parse.c" break; case 20: #line 287 "lib/command_parse.y" { (yyval.node) = new_token_node (ctx, MAC_PREFIX_TKN, (yyvsp[0].string), doc_next(ctx)); XFREE (MTYPE_LEX, (yyvsp[0].string)); } #line 1651 "lib/command_parse.c" break; case 21: #line 294 "lib/command_parse.y" { struct cmd_token *token = (yyval.node)->data; (yyval.node) = (yyvsp[-1].node); cmd_token_varname_set (token, (yyvsp[0].string)); XFREE (MTYPE_LEX, (yyvsp[0].string)); } #line 1662 "lib/command_parse.c" break; case 22: #line 304 "lib/command_parse.y" { (yyval.subgraph) = (yyvsp[-2].subgraph); cmd_token_varname_set ((yyvsp[-2].subgraph).end->data, (yyvsp[0].string)); XFREE (MTYPE_LEX, (yyvsp[0].string)); } #line 1672 "lib/command_parse.c" break; case 23: #line 312 "lib/command_parse.y" { (yyval.subgraph) = (yyvsp[-2].subgraph); graph_add_edge ((yyval.subgraph).start, (yyvsp[0].subgraph).start); graph_add_edge ((yyvsp[0].subgraph).end, (yyval.subgraph).end); } #line 1682 "lib/command_parse.c" break; case 24: #line 318 "lib/command_parse.y" { (yyval.subgraph).start = new_token_node (ctx, FORK_TKN, NULL, NULL); (yyval.subgraph).end = new_token_node (ctx, JOIN_TKN, NULL, NULL); ((struct cmd_token *)(yyval.subgraph).start->data)->forkjoin = (yyval.subgraph).end; ((struct cmd_token *)(yyval.subgraph).end->data)->forkjoin = (yyval.subgraph).start; graph_add_edge ((yyval.subgraph).start, (yyvsp[0].subgraph).start); graph_add_edge ((yyvsp[0].subgraph).end, (yyval.subgraph).end); } #line 1696 "lib/command_parse.c" break; case 25: #line 331 "lib/command_parse.y" { (yyval.subgraph) = (yyvsp[-2].subgraph); graph_add_edge ((yyval.subgraph).end, (yyval.subgraph).start); /* there is intentionally no start->end link, for two reasons: * 1) this allows "at least 1 of" semantics, which are otherwise impossible * 2) this would add a start->end->start loop in the graph that the current * loop-avoidal fails to handle * just use [{a|b}] if neccessary, that will work perfectly fine, and reason * #1 is good enough to keep it this way. */ loopcheck(ctx, &(yyval.subgraph)); cmd_token_varname_set ((yyvsp[-2].subgraph).end->data, (yyvsp[0].string)); XFREE (MTYPE_LEX, (yyvsp[0].string)); } #line 1715 "lib/command_parse.c" break; case 26: #line 349 "lib/command_parse.y" { (yyval.subgraph).start = (yyval.subgraph).end = (yyvsp[0].node); } #line 1723 "lib/command_parse.c" break; case 28: #line 357 "lib/command_parse.y" { graph_add_edge ((yyvsp[-1].subgraph).end, (yyvsp[0].subgraph).start); (yyval.subgraph).start = (yyvsp[-1].subgraph).start; (yyval.subgraph).end = (yyvsp[0].subgraph).end; } #line 1733 "lib/command_parse.c" break; case 30: #line 367 "lib/command_parse.y" { (yyval.subgraph) = (yyvsp[-2].subgraph); graph_add_edge ((yyval.subgraph).start, (yyval.subgraph).end); cmd_token_varname_set ((yyvsp[-2].subgraph).end->data, (yyvsp[0].string)); XFREE (MTYPE_LEX, (yyvsp[0].string)); } #line 1744 "lib/command_parse.c" break; #line 1748 "lib/command_parse.c" default: break; } /* User semantic actions sometimes alter yychar, and that requires that yytoken be updated with the new translation. We take the approach of translating immediately before every use of yytoken. One alternative is translating here after every semantic action, but that translation would be missed if the semantic action invokes YYABORT, YYACCEPT, or YYERROR immediately after altering yychar or if it invokes YYBACKUP. In the case of YYABORT or YYACCEPT, an incorrect destructor might then be invoked immediately. In the case of YYERROR or YYBACKUP, subsequent parser actions might lead to an incorrect destructor call or verbose syntax error message before the lookahead is translated. */ YY_SYMBOL_PRINT ("-> $$ =", yyr1[yyn], &yyval, &yyloc); YYPOPSTACK (yylen); yylen = 0; YY_STACK_PRINT (yyss, yyssp); *++yyvsp = yyval; *++yylsp = yyloc; /* Now 'shift' the result of the reduction. Determine what state that goes to, based on the state we popped back to and the rule number reduced by. */ { const int yylhs = yyr1[yyn] - YYNTOKENS; const int yyi = yypgoto[yylhs] + *yyssp; yystate = (0 <= yyi && yyi <= YYLAST && yycheck[yyi] == *yyssp ? yytable[yyi] : yydefgoto[yylhs]); } goto yynewstate; /*--------------------------------------. | yyerrlab -- here on detecting error. | `--------------------------------------*/ yyerrlab: /* Make sure we have latest lookahead translation. See comments at user semantic actions for why this is necessary. */ yytoken = yychar == YYEMPTY ? YYEMPTY : YYTRANSLATE (yychar); /* If not already recovering from an error, report this error. */ if (!yyerrstatus) { ++yynerrs; #if ! YYERROR_VERBOSE yyerror (&yylloc, ctx, YY_("syntax error")); #else # define YYSYNTAX_ERROR yysyntax_error (&yymsg_alloc, &yymsg, \ yyssp, yytoken) { char const *yymsgp = YY_("syntax error"); int yysyntax_error_status; yysyntax_error_status = YYSYNTAX_ERROR; if (yysyntax_error_status == 0) yymsgp = yymsg; else if (yysyntax_error_status == 1) { if (yymsg != yymsgbuf) YYSTACK_FREE (yymsg); yymsg = (char *) YYSTACK_ALLOC (yymsg_alloc); if (!yymsg) { yymsg = yymsgbuf; yymsg_alloc = sizeof yymsgbuf; yysyntax_error_status = 2; } else { yysyntax_error_status = YYSYNTAX_ERROR; yymsgp = yymsg; } } yyerror (&yylloc, ctx, yymsgp); if (yysyntax_error_status == 2) goto yyexhaustedlab; } # undef YYSYNTAX_ERROR #endif } yyerror_range[1] = yylloc; if (yyerrstatus == 3) { /* If just tried and failed to reuse lookahead token after an error, discard it. */ if (yychar <= YYEOF) { /* Return failure if at end of input. */ if (yychar == YYEOF) YYABORT; } else { yydestruct ("Error: discarding", yytoken, &yylval, &yylloc, ctx); yychar = YYEMPTY; } } /* Else will try to reuse lookahead token after shifting the error token. */ goto yyerrlab1; /*---------------------------------------------------. | yyerrorlab -- error raised explicitly by YYERROR. | `---------------------------------------------------*/ yyerrorlab: /* Pacify compilers when the user code never invokes YYERROR and the label yyerrorlab therefore never appears in user code. */ if (0) YYERROR; /* Do not reclaim the symbols of the rule whose action triggered this YYERROR. */ YYPOPSTACK (yylen); yylen = 0; YY_STACK_PRINT (yyss, yyssp); yystate = *yyssp; goto yyerrlab1; /*-------------------------------------------------------------. | yyerrlab1 -- common code for both syntax error and YYERROR. | `-------------------------------------------------------------*/ yyerrlab1: yyerrstatus = 3; /* Each real token shifted decrements this. */ for (;;) { yyn = yypact[yystate]; if (!yypact_value_is_default (yyn)) { yyn += YYTERROR; if (0 <= yyn && yyn <= YYLAST && yycheck[yyn] == YYTERROR) { yyn = yytable[yyn]; if (0 < yyn) break; } } /* Pop the current state because it cannot handle the error token. */ if (yyssp == yyss) YYABORT; yyerror_range[1] = *yylsp; yydestruct ("Error: popping", yystos[yystate], yyvsp, yylsp, ctx); YYPOPSTACK (1); yystate = *yyssp; YY_STACK_PRINT (yyss, yyssp); } YY_IGNORE_MAYBE_UNINITIALIZED_BEGIN *++yyvsp = yylval; YY_IGNORE_MAYBE_UNINITIALIZED_END yyerror_range[2] = yylloc; /* Using YYLLOC is tempting, but would change the location of the lookahead. YYLOC is available though. */ YYLLOC_DEFAULT (yyloc, yyerror_range, 2); *++yylsp = yyloc; /* Shift the error token. */ YY_SYMBOL_PRINT ("Shifting", yystos[yyn], yyvsp, yylsp); yystate = yyn; goto yynewstate; /*-------------------------------------. | yyacceptlab -- YYACCEPT comes here. | `-------------------------------------*/ yyacceptlab: yyresult = 0; goto yyreturn; /*-----------------------------------. | yyabortlab -- YYABORT comes here. | `-----------------------------------*/ yyabortlab: yyresult = 1; goto yyreturn; #if !defined yyoverflow || YYERROR_VERBOSE /*-------------------------------------------------. | yyexhaustedlab -- memory exhaustion comes here. | `-------------------------------------------------*/ yyexhaustedlab: yyerror (&yylloc, ctx, YY_("memory exhausted")); yyresult = 2; /* Fall through. */ #endif /*-----------------------------------------------------. | yyreturn -- parsing is finished, return the result. | `-----------------------------------------------------*/ yyreturn: if (yychar != YYEMPTY) { /* Make sure we have latest lookahead translation. See comments at user semantic actions for why this is necessary. */ yytoken = YYTRANSLATE (yychar); yydestruct ("Cleanup: discarding lookahead", yytoken, &yylval, &yylloc, ctx); } /* Do not reclaim the symbols of the rule whose action triggered this YYABORT or YYACCEPT. */ YYPOPSTACK (yylen); YY_STACK_PRINT (yyss, yyssp); while (yyssp != yyss) { yydestruct ("Cleanup: popping", yystos[*yyssp], yyvsp, yylsp, ctx); YYPOPSTACK (1); } #ifndef yyoverflow if (yyss != yyssa) YYSTACK_FREE (yyss); #endif #if YYERROR_VERBOSE if (yymsg != yymsgbuf) YYSTACK_FREE (yymsg); #endif return yyresult; } #line 375 "lib/command_parse.y" #undef scanner DEFINE_MTYPE(LIB, LEX, "Lexer token (temporary)") void cmd_graph_parse (struct graph *graph, struct cmd_element *cmd) { struct parser_ctx ctx = { .graph = graph, .el = cmd }; // set to 1 to enable parser traces yydebug = 0; set_lexer_string (&ctx.scanner, cmd->string); // parse command into DFA cmd_yyparse (&ctx); /* cleanup lexer */ cleanup_lexer (&ctx.scanner); // cleanup cleanup (&ctx); } /* parser helper functions */ static bool loopcheck_inner(struct graph_node *start, struct graph_node *node, struct graph_node *end, size_t depth) { size_t i; bool ret; /* safety check */ if (depth++ == 64) return true; for (i = 0; i < vector_active(node->to); i++) { struct graph_node *next = vector_slot(node->to, i); struct cmd_token *tok = next->data; if (next == end || next == start) return true; if (tok->type < SPECIAL_TKN) continue; ret = loopcheck_inner(start, next, end, depth); if (ret) return true; } return false; } static void loopcheck(struct parser_ctx *ctx, struct subgraph *sg) { if (loopcheck_inner(sg->start, sg->start, sg->end, 0)) zlog_err("FATAL: '%s': {} contains an empty path! Use [{...}]", ctx->el->string); } void yyerror (CMD_YYLTYPE *loc, struct parser_ctx *ctx, char const *msg) { char *tmpstr = strdup(ctx->el->string); char *line, *eol; char spacing[256]; int lineno = 0; zlog_notice ("%s: FATAL parse error: %s", __func__, msg); zlog_notice ("%s: %d:%d-%d of this command definition:", __func__, loc->first_line, loc->first_column, loc->last_column); line = tmpstr; do { lineno++; eol = strchr(line, '\n'); if (eol) *eol++ = '\0'; zlog_notice ("%s: | %s", __func__, line); if (lineno == loc->first_line && lineno == loc->last_line && loc->first_column < (int)sizeof(spacing) - 1 && loc->last_column < (int)sizeof(spacing) - 1) { int len = loc->last_column - loc->first_column; if (len == 0) len = 1; memset(spacing, ' ', loc->first_column - 1); memset(spacing + loc->first_column - 1, '^', len); spacing[loc->first_column - 1 + len] = '\0'; zlog_notice ("%s: | %s", __func__, spacing); } } while ((line = eol)); free(tmpstr); } static void cleanup (struct parser_ctx *ctx) { /* free resources */ free (ctx->docstr_start); /* clear state pointers */ ctx->currnode = NULL; ctx->docstr_start = ctx->docstr = NULL; } static void terminate_graph (CMD_YYLTYPE *locp, struct parser_ctx *ctx, struct graph_node *finalnode) { // end of graph should look like this // * -> finalnode -> END_TKN -> cmd_element struct cmd_element *element = ctx->el; struct graph_node *end_token_node = new_token_node (ctx, END_TKN, CMD_CR_TEXT, ""); struct graph_node *end_element_node = graph_new_node (ctx->graph, element, NULL); if (ctx->docstr && strlen (ctx->docstr) > 1) { zlog_debug ("Excessive docstring while parsing '%s'", ctx->el->string); zlog_debug ("----------"); while (ctx->docstr && ctx->docstr[1] != '\0') zlog_debug ("%s", strsep(&ctx->docstr, "\n")); zlog_debug ("----------\n"); } graph_add_edge (finalnode, end_token_node); graph_add_edge (end_token_node, end_element_node); } static const char * doc_next (struct parser_ctx *ctx) { const char *piece = ctx->docstr ? strsep (&ctx->docstr, "\n") : ""; if (*piece == 0x03) { zlog_debug ("Ran out of docstring while parsing '%s'", ctx->el->string); piece = ""; } return piece; } static struct graph_node * new_token_node (struct parser_ctx *ctx, enum cmd_token_type type, const char *text, const char *doc) { struct cmd_token *token = cmd_token_new (type, ctx->el->attr, text, doc); return graph_new_node (ctx->graph, token, (void (*)(void *)) &cmd_token_del); } frr-7.2.1/lib/command_parse.h0000644000000000000000000001024113610377563012741 00000000000000/* A Bison parser, made by GNU Bison 3.4.2. */ /* Bison interface for Yacc-like parsers in C Copyright (C) 1984, 1989-1990, 2000-2015, 2018-2019 Free Software Foundation, Inc. This program is free software: you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation, either version 3 of the License, or (at your option) any later version. This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with this program. If not, see . */ /* As a special exception, you may create a larger work that contains part or all of the Bison parser skeleton and distribute that work under terms of your choice, so long as that work isn't itself a parser generator using the skeleton or a modified version thereof as a parser skeleton. Alternatively, if you modify or redistribute the parser skeleton itself, you may (at your option) remove this special exception, which will cause the skeleton and the resulting Bison output files to be licensed under the GNU General Public License without this special exception. This special exception was added by the Free Software Foundation in version 2.2 of Bison. */ /* Undocumented macros, especially those whose name start with YY_, are private implementation details. Do not rely on them. */ #ifndef YY_CMD_YY_LIB_COMMAND_PARSE_H_INCLUDED # define YY_CMD_YY_LIB_COMMAND_PARSE_H_INCLUDED /* Debug traces. */ #ifndef CMD_YYDEBUG # if defined YYDEBUG #if YYDEBUG # define CMD_YYDEBUG 1 # else # define CMD_YYDEBUG 0 # endif # else /* ! defined YYDEBUG */ # define CMD_YYDEBUG 0 # endif /* ! defined YYDEBUG */ #endif /* ! defined CMD_YYDEBUG */ #if CMD_YYDEBUG extern int cmd_yydebug; #endif /* "%code requires" blocks. */ #line 46 "lib/command_parse.y" #include "config.h" #include #include #include #include #include "command_graph.h" #include "log.h" DECLARE_MTYPE(LEX) #define YYSTYPE CMD_YYSTYPE #define YYLTYPE CMD_YYLTYPE struct parser_ctx; /* subgraph semantic value */ struct subgraph { struct graph_node *start, *end; }; #line 79 "lib/command_parse.h" /* Token type. */ #ifndef CMD_YYTOKENTYPE # define CMD_YYTOKENTYPE enum cmd_yytokentype { WORD = 258, IPV4 = 259, IPV4_PREFIX = 260, IPV6 = 261, IPV6_PREFIX = 262, VARIABLE = 263, RANGE = 264, MAC = 265, MAC_PREFIX = 266 }; #endif /* Tokens. */ #define WORD 258 #define IPV4 259 #define IPV4_PREFIX 260 #define IPV6 261 #define IPV6_PREFIX 262 #define VARIABLE 263 #define RANGE 264 #define MAC 265 #define MAC_PREFIX 266 /* Value type. */ #if ! defined CMD_YYSTYPE && ! defined CMD_YYSTYPE_IS_DECLARED union CMD_YYSTYPE { #line 69 "lib/command_parse.y" long long number; char *string; struct graph_node *node; struct subgraph subgraph; #line 119 "lib/command_parse.h" }; typedef union CMD_YYSTYPE CMD_YYSTYPE; # define CMD_YYSTYPE_IS_TRIVIAL 1 # define CMD_YYSTYPE_IS_DECLARED 1 #endif /* Location type. */ #if ! defined CMD_YYLTYPE && ! defined CMD_YYLTYPE_IS_DECLARED typedef struct CMD_YYLTYPE CMD_YYLTYPE; struct CMD_YYLTYPE { int first_line; int first_column; int last_line; int last_column; }; # define CMD_YYLTYPE_IS_DECLARED 1 # define CMD_YYLTYPE_IS_TRIVIAL 1 #endif int cmd_yyparse (struct parser_ctx *ctx); /* "%code provides" blocks. */ #line 76 "lib/command_parse.y" #ifndef FLEX_SCANNER #include "command_lex.h" #endif extern void set_lexer_string (yyscan_t *scn, const char *string); extern void cleanup_lexer (yyscan_t *scn); struct parser_ctx { yyscan_t scanner; struct cmd_element *el; struct graph *graph; struct graph_node *currnode; /* pointers to copy of command docstring */ char *docstr_start, *docstr; }; #line 166 "lib/command_parse.h" #endif /* !YY_CMD_YY_LIB_COMMAND_PARSE_H_INCLUDED */ frr-7.2.1/lib/command_parse.y0000644000000000000000000003012013610377563012760 00000000000000/* * Command format string parser for CLI backend. * * -- * Copyright (C) 2016 Cumulus Networks, Inc. * * This file is part of GNU Zebra. * * GNU Zebra is free software; you can redistribute it and/or modify it * under the terms of the GNU General Public License as published by the * Free Software Foundation; either version 2, or (at your option) any * later version. * * GNU Zebra is distributed in the hope that it will be useful, but * WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU * General Public License for more details. * * You should have received a copy of the GNU General Public License * along with GNU Zebra; see the file COPYING. If not, write to the Free * Software Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA * 02111-1307, USA. */ %{ // compile with debugging facilities #define YYDEBUG 1 %} %locations /* define parse.error verbose */ %define api.pure full /* define api.prefix {cmd_yy} */ /* names for generated header and parser files */ %defines "lib/command_parse.h" %output "lib/command_parse.c" /* note: code blocks are output in order, to both .c and .h: * 1. %code requires * 2. %union + bison forward decls * 3. %code provides * command_lex.h needs to be included at 3.; it needs the union and YYSTYPE. * struct parser_ctx is needed for the bison forward decls. */ %code requires { #include "config.h" #include #include #include #include #include "command_graph.h" #include "log.h" DECLARE_MTYPE(LEX) #define YYSTYPE CMD_YYSTYPE #define YYLTYPE CMD_YYLTYPE struct parser_ctx; /* subgraph semantic value */ struct subgraph { struct graph_node *start, *end; }; } %union { long long number; char *string; struct graph_node *node; struct subgraph subgraph; } %code provides { #ifndef FLEX_SCANNER #include "command_lex.h" #endif extern void set_lexer_string (yyscan_t *scn, const char *string); extern void cleanup_lexer (yyscan_t *scn); struct parser_ctx { yyscan_t scanner; struct cmd_element *el; struct graph *graph; struct graph_node *currnode; /* pointers to copy of command docstring */ char *docstr_start, *docstr; }; } /* union types for lexed tokens */ %token WORD %token IPV4 %token IPV4_PREFIX %token IPV6 %token IPV6_PREFIX %token VARIABLE %token RANGE %token MAC %token MAC_PREFIX /* union types for parsed rules */ %type start %type literal_token %type placeholder_token %type placeholder_token_real %type simple_token %type selector %type selector_token %type selector_token_seq %type selector_seq_seq %type varname_token %code { /* bison declarations */ void cmd_yyerror (CMD_YYLTYPE *locp, struct parser_ctx *ctx, char const *msg); /* helper functions for parser */ static const char * doc_next (struct parser_ctx *ctx); static struct graph_node * new_token_node (struct parser_ctx *, enum cmd_token_type type, const char *text, const char *doc); static void terminate_graph (CMD_YYLTYPE *locp, struct parser_ctx *ctx, struct graph_node *); static void cleanup (struct parser_ctx *ctx); static void loopcheck(struct parser_ctx *ctx, struct subgraph *sg); #define scanner ctx->scanner } /* yyparse parameters */ %lex-param {yyscan_t scanner} %parse-param {struct parser_ctx *ctx} /* called automatically before yyparse */ %initial-action { /* clear state pointers */ ctx->currnode = vector_slot (ctx->graph->nodes, 0); /* copy docstring and keep a pointer to the copy */ if (ctx->el->doc) { // allocate a new buffer, making room for a flag size_t length = (size_t) strlen (ctx->el->doc) + 2; ctx->docstr = malloc (length); memcpy (ctx->docstr, ctx->el->doc, strlen (ctx->el->doc)); // set the flag so doc_next knows when to print a warning ctx->docstr[length - 2] = 0x03; // null terminate ctx->docstr[length - 1] = 0x00; } ctx->docstr_start = ctx->docstr; } %% start: cmd_token_seq { // tack on the command element terminate_graph (&@1, ctx, ctx->currnode); } | cmd_token_seq placeholder_token '.' '.' '.' { if ((ctx->currnode = graph_add_edge (ctx->currnode, $2)) != $2) graph_delete_node (ctx->graph, $2); ((struct cmd_token *)ctx->currnode->data)->allowrepeat = 1; // adding a node as a child of itself accepts any number // of the same token, which is what we want for variadics graph_add_edge (ctx->currnode, ctx->currnode); // tack on the command element terminate_graph (&@1, ctx, ctx->currnode); } ; varname_token: '$' WORD { $$ = $2; } | /* empty */ { $$ = NULL; } ; cmd_token_seq: /* empty */ | cmd_token_seq cmd_token ; cmd_token: simple_token { if ((ctx->currnode = graph_add_edge (ctx->currnode, $1)) != $1) graph_delete_node (ctx->graph, $1); } | selector { graph_add_edge (ctx->currnode, $1.start); ctx->currnode = $1.end; } ; simple_token: literal_token | placeholder_token ; literal_token: WORD varname_token { $$ = new_token_node (ctx, WORD_TKN, $1, doc_next(ctx)); cmd_token_varname_set ($$->data, $2); XFREE (MTYPE_LEX, $2); XFREE (MTYPE_LEX, $1); } ; placeholder_token_real: IPV4 { $$ = new_token_node (ctx, IPV4_TKN, $1, doc_next(ctx)); XFREE (MTYPE_LEX, $1); } | IPV4_PREFIX { $$ = new_token_node (ctx, IPV4_PREFIX_TKN, $1, doc_next(ctx)); XFREE (MTYPE_LEX, $1); } | IPV6 { $$ = new_token_node (ctx, IPV6_TKN, $1, doc_next(ctx)); XFREE (MTYPE_LEX, $1); } | IPV6_PREFIX { $$ = new_token_node (ctx, IPV6_PREFIX_TKN, $1, doc_next(ctx)); XFREE (MTYPE_LEX, $1); } | VARIABLE { $$ = new_token_node (ctx, VARIABLE_TKN, $1, doc_next(ctx)); XFREE (MTYPE_LEX, $1); } | RANGE { $$ = new_token_node (ctx, RANGE_TKN, $1, doc_next(ctx)); struct cmd_token *token = $$->data; // get the numbers out yylval.string++; token->min = strtoll (yylval.string, &yylval.string, 10); strsep (&yylval.string, "-"); token->max = strtoll (yylval.string, &yylval.string, 10); // validate range if (token->min > token->max) cmd_yyerror (&@1, ctx, "Invalid range."); XFREE (MTYPE_LEX, $1); } | MAC { $$ = new_token_node (ctx, MAC_TKN, $1, doc_next(ctx)); XFREE (MTYPE_LEX, $1); } | MAC_PREFIX { $$ = new_token_node (ctx, MAC_PREFIX_TKN, $1, doc_next(ctx)); XFREE (MTYPE_LEX, $1); } placeholder_token: placeholder_token_real varname_token { struct cmd_token *token = $$->data; $$ = $1; cmd_token_varname_set (token, $2); XFREE (MTYPE_LEX, $2); }; /* productions */ selector: '<' selector_seq_seq '>' varname_token { $$ = $2; cmd_token_varname_set ($2.end->data, $4); XFREE (MTYPE_LEX, $4); }; selector_seq_seq: selector_seq_seq '|' selector_token_seq { $$ = $1; graph_add_edge ($$.start, $3.start); graph_add_edge ($3.end, $$.end); } | selector_token_seq { $$.start = new_token_node (ctx, FORK_TKN, NULL, NULL); $$.end = new_token_node (ctx, JOIN_TKN, NULL, NULL); ((struct cmd_token *)$$.start->data)->forkjoin = $$.end; ((struct cmd_token *)$$.end->data)->forkjoin = $$.start; graph_add_edge ($$.start, $1.start); graph_add_edge ($1.end, $$.end); } ; /* {keyword} productions */ selector: '{' selector_seq_seq '}' varname_token { $$ = $2; graph_add_edge ($$.end, $$.start); /* there is intentionally no start->end link, for two reasons: * 1) this allows "at least 1 of" semantics, which are otherwise impossible * 2) this would add a start->end->start loop in the graph that the current * loop-avoidal fails to handle * just use [{a|b}] if neccessary, that will work perfectly fine, and reason * #1 is good enough to keep it this way. */ loopcheck(ctx, &$$); cmd_token_varname_set ($2.end->data, $4); XFREE (MTYPE_LEX, $4); }; selector_token: simple_token { $$.start = $$.end = $1; } | selector ; selector_token_seq: selector_token_seq selector_token { graph_add_edge ($1.end, $2.start); $$.start = $1.start; $$.end = $2.end; } | selector_token ; /* [option] productions */ selector: '[' selector_seq_seq ']' varname_token { $$ = $2; graph_add_edge ($$.start, $$.end); cmd_token_varname_set ($2.end->data, $4); XFREE (MTYPE_LEX, $4); } ; %% #undef scanner DEFINE_MTYPE(LIB, LEX, "Lexer token (temporary)") void cmd_graph_parse (struct graph *graph, struct cmd_element *cmd) { struct parser_ctx ctx = { .graph = graph, .el = cmd }; // set to 1 to enable parser traces yydebug = 0; set_lexer_string (&ctx.scanner, cmd->string); // parse command into DFA cmd_yyparse (&ctx); /* cleanup lexer */ cleanup_lexer (&ctx.scanner); // cleanup cleanup (&ctx); } /* parser helper functions */ static bool loopcheck_inner(struct graph_node *start, struct graph_node *node, struct graph_node *end, size_t depth) { size_t i; bool ret; /* safety check */ if (depth++ == 64) return true; for (i = 0; i < vector_active(node->to); i++) { struct graph_node *next = vector_slot(node->to, i); struct cmd_token *tok = next->data; if (next == end || next == start) return true; if (tok->type < SPECIAL_TKN) continue; ret = loopcheck_inner(start, next, end, depth); if (ret) return true; } return false; } static void loopcheck(struct parser_ctx *ctx, struct subgraph *sg) { if (loopcheck_inner(sg->start, sg->start, sg->end, 0)) zlog_err("FATAL: '%s': {} contains an empty path! Use [{...}]", ctx->el->string); } void yyerror (CMD_YYLTYPE *loc, struct parser_ctx *ctx, char const *msg) { char *tmpstr = strdup(ctx->el->string); char *line, *eol; char spacing[256]; int lineno = 0; zlog_notice ("%s: FATAL parse error: %s", __func__, msg); zlog_notice ("%s: %d:%d-%d of this command definition:", __func__, loc->first_line, loc->first_column, loc->last_column); line = tmpstr; do { lineno++; eol = strchr(line, '\n'); if (eol) *eol++ = '\0'; zlog_notice ("%s: | %s", __func__, line); if (lineno == loc->first_line && lineno == loc->last_line && loc->first_column < (int)sizeof(spacing) - 1 && loc->last_column < (int)sizeof(spacing) - 1) { int len = loc->last_column - loc->first_column; if (len == 0) len = 1; memset(spacing, ' ', loc->first_column - 1); memset(spacing + loc->first_column - 1, '^', len); spacing[loc->first_column - 1 + len] = '\0'; zlog_notice ("%s: | %s", __func__, spacing); } } while ((line = eol)); free(tmpstr); } static void cleanup (struct parser_ctx *ctx) { /* free resources */ free (ctx->docstr_start); /* clear state pointers */ ctx->currnode = NULL; ctx->docstr_start = ctx->docstr = NULL; } static void terminate_graph (CMD_YYLTYPE *locp, struct parser_ctx *ctx, struct graph_node *finalnode) { // end of graph should look like this // * -> finalnode -> END_TKN -> cmd_element struct cmd_element *element = ctx->el; struct graph_node *end_token_node = new_token_node (ctx, END_TKN, CMD_CR_TEXT, ""); struct graph_node *end_element_node = graph_new_node (ctx->graph, element, NULL); if (ctx->docstr && strlen (ctx->docstr) > 1) { zlog_debug ("Excessive docstring while parsing '%s'", ctx->el->string); zlog_debug ("----------"); while (ctx->docstr && ctx->docstr[1] != '\0') zlog_debug ("%s", strsep(&ctx->docstr, "\n")); zlog_debug ("----------\n"); } graph_add_edge (finalnode, end_token_node); graph_add_edge (end_token_node, end_element_node); } static const char * doc_next (struct parser_ctx *ctx) { const char *piece = ctx->docstr ? strsep (&ctx->docstr, "\n") : ""; if (*piece == 0x03) { zlog_debug ("Ran out of docstring while parsing '%s'", ctx->el->string); piece = ""; } return piece; } static struct graph_node * new_token_node (struct parser_ctx *ctx, enum cmd_token_type type, const char *text, const char *doc) { struct cmd_token *token = cmd_token_new (type, ctx->el->attr, text, doc); return graph_new_node (ctx->graph, token, (void (*)(void *)) &cmd_token_del); } frr-7.2.1/lib/command_py.c0000644000000000000000000002411613610377563012260 00000000000000/* * clippy (CLI preparator in python) wrapper for FRR command_graph * Copyright (C) 2016-2017 David Lamparter for NetDEF, Inc. * * This program is free software; you can redistribute it and/or modify it * under the terms of the 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; see the file COPYING; if not, write to the Free Software * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA */ /* note: this wrapper is intended to be used as build-time helper. while * it should be generally correct and proper, there may be the occasional * memory leak or SEGV for things that haven't been well-tested. */ /* This file is "exempt" from having #include "config.h" * as the first include statement because Python.h also does environment * setup & these trample over each other. */ #include #include "structmember.h" #include #include #include "command_graph.h" #include "clippy.h" struct wrap_graph; static PyObject *graph_to_pyobj(struct wrap_graph *graph, struct graph_node *gn); /* * nodes are wrapped as follows: * - instances can only be acquired from a graph * - the same node will return the same wrapper object (they're buffered * through "idx") * - a reference is held onto the graph * - fields are copied for easy access with PyMemberDef */ struct wrap_graph_node { PyObject_HEAD bool allowrepeat; const char *type; bool deprecated; bool hidden; const char *text; const char *desc; const char *varname; long long min, max; struct graph_node *node; struct wrap_graph *wgraph; size_t idx; }; /* * graphs are wrapped as follows: * - they can only be created by parsing a definition string * - there's a table here for the wrapped nodes (nodewrappers), indexed * by "idx" (corresponds to node's position in graph's table of nodes) * - graphs do NOT hold references to nodes (would be circular) */ struct wrap_graph { PyObject_HEAD char *definition; struct graph *graph; struct wrap_graph_node **nodewrappers; }; static PyObject *refuse_new(PyTypeObject *type, PyObject *args, PyObject *kwds) { PyErr_SetString(PyExc_ValueError, "cannot create instances of this type"); return NULL; } #define member(name, type) \ { \ (char *)#name, type, offsetof(struct wrap_graph_node, name), \ READONLY, (char *)#name " (" #type ")" \ } static PyMemberDef members_graph_node[] = { member(allowrepeat, T_BOOL), member(type, T_STRING), member(deprecated, T_BOOL), member(hidden, T_BOOL), member(text, T_STRING), member(desc, T_STRING), member(min, T_LONGLONG), member(max, T_LONGLONG), member(varname, T_STRING), {}, }; #undef member /* * node.next() -- returns list of all "next" nodes. * this will include circles if the graph has them. */ static PyObject *graph_node_next(PyObject *self, PyObject *args) { struct wrap_graph_node *wrap = (struct wrap_graph_node *)self; PyObject *pylist; if (wrap->node->data && ((struct cmd_token *)wrap->node->data)->type == END_TKN) return PyList_New(0); pylist = PyList_New(vector_active(wrap->node->to)); for (size_t i = 0; i < vector_active(wrap->node->to); i++) { struct graph_node *gn = vector_slot(wrap->node->to, i); PyList_SetItem(pylist, i, graph_to_pyobj(wrap->wgraph, gn)); } return pylist; }; /* * node.join() -- return FORK's JOIN node or None */ static PyObject *graph_node_join(PyObject *self, PyObject *args) { struct wrap_graph_node *wrap = (struct wrap_graph_node *)self; if (!wrap->node->data || ((struct cmd_token *)wrap->node->data)->type == END_TKN) Py_RETURN_NONE; struct cmd_token *tok = wrap->node->data; if (tok->type != FORK_TKN) Py_RETURN_NONE; return graph_to_pyobj(wrap->wgraph, tok->forkjoin); }; static PyMethodDef methods_graph_node[] = { {"next", graph_node_next, METH_NOARGS, "outbound graph edge list"}, {"join", graph_node_join, METH_NOARGS, "outbound join node"}, {}}; static void graph_node_wrap_free(void *arg) { struct wrap_graph_node *wrap = arg; wrap->wgraph->nodewrappers[wrap->idx] = NULL; Py_DECREF(wrap->wgraph); } static PyTypeObject typeobj_graph_node = { PyVarObject_HEAD_INIT(NULL, 0).tp_name = "_clippy.GraphNode", .tp_basicsize = sizeof(struct wrap_graph_node), .tp_flags = Py_TPFLAGS_DEFAULT, .tp_doc = "struct graph_node *", .tp_new = refuse_new, .tp_free = graph_node_wrap_free, .tp_members = members_graph_node, .tp_methods = methods_graph_node, }; static PyObject *graph_to_pyobj(struct wrap_graph *wgraph, struct graph_node *gn) { struct wrap_graph_node *wrap; size_t i; for (i = 0; i < vector_active(wgraph->graph->nodes); i++) if (vector_slot(wgraph->graph->nodes, i) == gn) break; if (i == vector_active(wgraph->graph->nodes)) { PyErr_SetString(PyExc_ValueError, "cannot find node in graph"); return NULL; } if (wgraph->nodewrappers[i]) { PyObject *obj = (PyObject *)wgraph->nodewrappers[i]; Py_INCREF(obj); return obj; } wrap = (struct wrap_graph_node *)typeobj_graph_node.tp_alloc( &typeobj_graph_node, 0); if (!wrap) return NULL; wgraph->nodewrappers[i] = wrap; Py_INCREF(wgraph); wrap->idx = i; wrap->wgraph = wgraph; wrap->node = gn; wrap->type = "NULL"; wrap->allowrepeat = false; if (gn->data) { struct cmd_token *tok = gn->data; switch (tok->type) { #define item(x) case x: wrap->type = #x; break; item(WORD_TKN) // words item(VARIABLE_TKN) // almost anything item(RANGE_TKN) // integer range item(IPV4_TKN) // IPV4 addresses item(IPV4_PREFIX_TKN) // IPV4 network prefixes item(IPV6_TKN) // IPV6 prefixes item(IPV6_PREFIX_TKN) // IPV6 network prefixes item(MAC_TKN) // MAC address item(MAC_PREFIX_TKN) // MAC address with mask /* plumbing types */ item(FORK_TKN) item(JOIN_TKN) item(START_TKN) item(END_TKN) default : wrap->type = "???"; } wrap->deprecated = (tok->attr == CMD_ATTR_DEPRECATED); wrap->hidden = (tok->attr == CMD_ATTR_HIDDEN); wrap->text = tok->text; wrap->desc = tok->desc; wrap->varname = tok->varname; wrap->min = tok->min; wrap->max = tok->max; wrap->allowrepeat = tok->allowrepeat; } return (PyObject *)wrap; } #define member(name, type) \ { \ (char *)#name, type, offsetof(struct wrap_graph, name), \ READONLY, (char *)#name " (" #type ")" \ } static PyMemberDef members_graph[] = { member(definition, T_STRING), {}, }; #undef member /* graph.first() - root node */ static PyObject *graph_first(PyObject *self, PyObject *args) { struct wrap_graph *gwrap = (struct wrap_graph *)self; struct graph_node *gn = vector_slot(gwrap->graph->nodes, 0); return graph_to_pyobj(gwrap, gn); }; static PyMethodDef methods_graph[] = { {"first", graph_first, METH_NOARGS, "first graph node"}, {}}; static PyObject *graph_parse(PyTypeObject *type, PyObject *args, PyObject *kwds); static void graph_wrap_free(void *arg) { struct wrap_graph *wgraph = arg; graph_delete_graph(wgraph->graph); free(wgraph->nodewrappers); free(wgraph->definition); } static PyTypeObject typeobj_graph = { PyVarObject_HEAD_INIT(NULL, 0).tp_name = "_clippy.Graph", .tp_basicsize = sizeof(struct wrap_graph), .tp_flags = Py_TPFLAGS_DEFAULT, .tp_doc = "struct graph *", .tp_new = graph_parse, .tp_free = graph_wrap_free, .tp_members = members_graph, .tp_methods = methods_graph, }; /* top call / entrypoint for python code */ static PyObject *graph_parse(PyTypeObject *type, PyObject *args, PyObject *kwds) { const char *def, *doc = NULL; struct wrap_graph *gwrap; static const char *kwnames[] = {"cmddef", "doc", NULL}; gwrap = (struct wrap_graph *)typeobj_graph.tp_alloc(&typeobj_graph, 0); if (!gwrap) return NULL; if (!PyArg_ParseTupleAndKeywords(args, kwds, "s|s", (char **)kwnames, &def, &doc)) return NULL; struct graph *graph = graph_new(); struct cmd_token *token = cmd_token_new(START_TKN, 0, NULL, NULL); graph_new_node(graph, token, (void (*)(void *)) & cmd_token_del); struct cmd_element cmd = {.string = def, .doc = doc}; cmd_graph_parse(graph, &cmd); cmd_graph_names(graph); gwrap->graph = graph; gwrap->definition = strdup(def); gwrap->nodewrappers = calloc(vector_active(graph->nodes), sizeof(gwrap->nodewrappers[0])); return (PyObject *)gwrap; } static PyMethodDef clippy_methods[] = { {"parse", clippy_parse, METH_VARARGS, "Parse a C file"}, {NULL, NULL, 0, NULL}}; #if PY_MAJOR_VERSION >= 3 static struct PyModuleDef pymoddef_clippy = { PyModuleDef_HEAD_INIT, "_clippy", NULL, /* docstring */ -1, clippy_methods, }; #define modcreate() PyModule_Create(&pymoddef_clippy) #define initret(val) return val; #else #define modcreate() Py_InitModule("_clippy", clippy_methods) #define initret(val) \ do { \ if (!val) \ Py_FatalError("initialization failure"); \ return; \ } while (0) #endif #pragma GCC diagnostic ignored "-Wstrict-aliasing" PyMODINIT_FUNC command_py_init(void) { PyObject *pymod; if (PyType_Ready(&typeobj_graph_node) < 0) initret(NULL); if (PyType_Ready(&typeobj_graph) < 0) initret(NULL); pymod = modcreate(); if (!pymod) initret(NULL); Py_INCREF(&typeobj_graph_node); PyModule_AddObject(pymod, "GraphNode", (PyObject *)&typeobj_graph_node); Py_INCREF(&typeobj_graph); PyModule_AddObject(pymod, "Graph", (PyObject *)&typeobj_graph); initret(pymod); } frr-7.2.1/lib/compiler.h0000644000000000000000000002767013610377563011761 00000000000000/* * Copyright (c) 2015-2017 David Lamparter, for NetDEF, Inc. * * Permission to use, copy, modify, and distribute this software for any * purpose with or without fee is hereby granted, provided that the above * copyright notice and this permission notice appear in all copies. * * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. */ #ifndef _FRR_COMPILER_H #define _FRR_COMPILER_H #ifdef __cplusplus extern "C" { #endif /* function attributes, use like * void prototype(void) __attribute__((_CONSTRUCTOR(100))); */ #if defined(__clang__) #if __clang_major__ > 3 || (__clang_major__ == 3 && __clang_minor__ >= 5) # define _RET_NONNULL , returns_nonnull #endif #if __has_attribute(fallthrough) # define _FALLTHROUGH __attribute__((fallthrough)); #endif # define _CONSTRUCTOR(x) constructor(x) # define _DEPRECATED(x) deprecated(x) # if __has_builtin(assume) # define assume(x) __builtin_assume(x) # endif #elif defined(__GNUC__) #if __GNUC__ > 4 || (__GNUC__ == 4 && __GNUC_MINOR__ >= 9) # define _RET_NONNULL , returns_nonnull #endif #if __GNUC__ > 4 || (__GNUC__ == 4 && __GNUC_MINOR__ >= 3) # define _CONSTRUCTOR(x) constructor(x) # define _DESTRUCTOR(x) destructor(x) # define _ALLOC_SIZE(x) alloc_size(x) #endif #if __GNUC__ > 4 || (__GNUC__ == 4 && __GNUC_MINOR__ >= 5) # define _DEPRECATED(x) deprecated(x) # define assume(x) do { if (!(x)) __builtin_unreachable(); } while (0) #endif #if __GNUC__ < 5 # define __has_attribute(x) 0 #endif #if __GNUC__ >= 7 # define _FALLTHROUGH __attribute__((fallthrough)); #endif #endif #if __has_attribute(hot) # define _OPTIMIZE_HOT __attribute__((hot)) #else # define _OPTIMIZE_HOT #endif #if __has_attribute(optimize) # define _OPTIMIZE_O3 __attribute__((optimize("3"))) #else # define _OPTIMIZE_O3 #endif #define OPTIMIZE _OPTIMIZE_O3 _OPTIMIZE_HOT #if !defined(__GNUC__) #error module code needs GCC visibility extensions #elif __GNUC__ < 4 #error module code needs GCC visibility extensions #else # define DSO_PUBLIC __attribute__ ((visibility ("default"))) # define DSO_SELF __attribute__ ((visibility ("protected"))) # define DSO_LOCAL __attribute__ ((visibility ("hidden"))) #endif #ifdef __sun /* Solaris doesn't do constructor priorities due to linker restrictions */ #undef _CONSTRUCTOR #undef _DESTRUCTOR #endif /* fallback versions */ #ifndef _RET_NONNULL # define _RET_NONNULL #endif #ifndef _CONSTRUCTOR # define _CONSTRUCTOR(x) constructor #endif #ifndef _DESTRUCTOR # define _DESTRUCTOR(x) destructor #endif #ifndef _ALLOC_SIZE # define _ALLOC_SIZE(x) #endif #ifndef _FALLTHROUGH #define _FALLTHROUGH #endif #ifndef _DEPRECATED #define _DEPRECATED(x) deprecated #endif #ifndef assume #define assume(x) #endif /* pure = function does not modify memory & return value is the same if * memory hasn't changed (=> allows compiler to optimize) * * Mostly autodetected by the compiler if function body is available (i.e. * static inline functions in headers). Since that implies it should only be * used in headers for non-inline functions, the "extern" is included here. */ #define ext_pure extern __attribute__((pure)) /* for helper functions defined inside macros */ #define macro_inline static inline __attribute__((unused)) #define macro_pure static inline __attribute__((unused, pure)) /* variadic macros, use like: * #define V_0() ... * #define V_1(x) ... * #define V(...) MACRO_VARIANT(V, ##__VA_ARGS__)(__VA_ARGS__) */ #define _MACRO_VARIANT(A0,A1,A2,A3,A4,A5,A6,A7,A8,A9,A10, N, ...) N #define _CONCAT2(a, b) a ## b #define _CONCAT(a, b) _CONCAT2(a,b) #define MACRO_VARIANT(NAME, ...) \ _CONCAT(NAME, _MACRO_VARIANT(0, ##__VA_ARGS__, \ _10, _9, _8, _7, _6, _5, _4, _3, _2, _1, _0)) #define NAMECTR(name) _CONCAT(name, __COUNTER__) /* per-arg repeat macros, use like: * #define PERARG(n) ...n... * #define FOO(...) MACRO_REPEAT(PERARG, ##__VA_ARGS__) */ #define _MACRO_REPEAT_0(NAME) #define _MACRO_REPEAT_1(NAME, A1) \ NAME(A1) #define _MACRO_REPEAT_2(NAME, A1, A2) \ NAME(A1) NAME(A2) #define _MACRO_REPEAT_3(NAME, A1, A2, A3) \ NAME(A1) NAME(A2) NAME(A3) #define _MACRO_REPEAT_4(NAME, A1, A2, A3, A4) \ NAME(A1) NAME(A2) NAME(A3) NAME(A4) #define _MACRO_REPEAT_5(NAME, A1, A2, A3, A4, A5) \ NAME(A1) NAME(A2) NAME(A3) NAME(A4) NAME(A5) #define _MACRO_REPEAT_6(NAME, A1, A2, A3, A4, A5, A6) \ NAME(A1) NAME(A2) NAME(A3) NAME(A4) NAME(A5) NAME(A6) #define _MACRO_REPEAT_7(NAME, A1, A2, A3, A4, A5, A6, A7) \ NAME(A1) NAME(A2) NAME(A3) NAME(A4) NAME(A5) NAME(A6) NAME(A7) #define _MACRO_REPEAT_8(NAME, A1, A2, A3, A4, A5, A6, A7, A8) \ NAME(A1) NAME(A2) NAME(A3) NAME(A4) NAME(A5) NAME(A6) NAME(A7) NAME(A8) #define MACRO_REPEAT(NAME, ...) \ MACRO_VARIANT(_MACRO_REPEAT, ##__VA_ARGS__)(NAME, ##__VA_ARGS__) /* * for warnings on macros, put in the macro content like this: * #define MACRO BLA CPP_WARN("MACRO has been deprecated") */ #define CPP_STR(X) #X #if defined(__ICC) #define CPP_NOTICE(text) _Pragma(CPP_STR(message __FILE__ ": " text)) #define CPP_WARN(text) CPP_NOTICE(text) #elif (defined(__GNUC__) \ && (__GNUC__ >= 5 || (__GNUC__ == 4 && __GNUC_MINOR__ >= 8))) \ || (defined(__clang__) \ && (__clang_major__ >= 4 \ || (__clang_major__ == 3 && __clang_minor__ >= 5))) #define CPP_WARN(text) _Pragma(CPP_STR(GCC warning text)) #define CPP_NOTICE(text) _Pragma(CPP_STR(message text)) #else #define CPP_WARN(text) #define CPP_NOTICE(text) #endif /* MAX / MIN are not commonly defined, but useful */ /* note: glibc sys/param.h has #define MIN(a,b) (((a)<(b))?(a):(b)) */ #ifdef MAX #undef MAX #endif #define MAX(a, b) \ ({ \ typeof(a) _max_a = (a); \ typeof(b) _max_b = (b); \ _max_a > _max_b ? _max_a : _max_b; \ }) #ifdef MIN #undef MIN #endif #define MIN(a, b) \ ({ \ typeof(a) _min_a = (a); \ typeof(b) _min_b = (b); \ _min_a < _min_b ? _min_a : _min_b; \ }) #define numcmp(a, b) \ ({ \ typeof(a) _cmp_a = (a); \ typeof(b) _cmp_b = (b); \ (_cmp_a < _cmp_b) ? -1 : ((_cmp_a > _cmp_b) ? 1 : 0); \ }) #ifndef offsetof #ifdef __compiler_offsetof #define offsetof(TYPE, MEMBER) __compiler_offsetof(TYPE,MEMBER) #else #define offsetof(TYPE, MEMBER) ((size_t) &((TYPE *)0)->MEMBER) #endif #endif #ifdef container_of #undef container_of #endif #if !(defined(__cplusplus) || defined(test__cplusplus)) /* this variant of container_of() retains 'const' on pointers without needing * to be told to do so. The following will all work without warning: * * struct member *p; * const struct member *cp; * * const struct cont *x = container_of(cp, struct cont, member); * const struct cont *x = container_of(cp, const struct cont, member); * const struct cont *x = container_of(p, struct cont, member); * const struct cont *x = container_of(p, const struct cont, member); * struct cont *x = container_of(p, struct cont, member); * * but the following will generate warnings about stripping const: * * struct cont *x = container_of(cp, struct cont, member); * struct cont *x = container_of(cp, const struct cont, member); * struct cont *x = container_of(p, const struct cont, member); */ #define container_of(ptr, type, member) \ (__builtin_choose_expr( \ __builtin_types_compatible_p(typeof(&((type *)0)->member), \ typeof(ptr)) \ || __builtin_types_compatible_p(void *, typeof(ptr)), \ ({ \ typeof(((type *)0)->member) *__mptr = (void *)(ptr); \ (type *)((char *)__mptr - offsetof(type, member)); \ }), \ ({ \ typeof(((const type *)0)->member) *__mptr = (ptr); \ (const type *)((const char *)__mptr - \ offsetof(type, member)); \ }) \ )) #else /* current C++ compilers don't have the builtins used above; so this version * of the macro doesn't do the const check. */ #define container_of(ptr, type, member) \ ({ \ const typeof(((type *)0)->member) *__mptr = (ptr); \ (type *)((char *)__mptr - offsetof(type, member)); \ }) #endif #define container_of_null(ptr, type, member) \ ({ \ typeof(ptr) _tmp = (ptr); \ _tmp ? container_of(_tmp, type, member) : NULL; \ }) #define array_size(ar) (sizeof(ar) / sizeof(ar[0])) /* sigh. this is so ugly, it overflows and wraps to being nice again. * * printfrr() supports "%Ld" for , whatever that is typedef'd to. * However, gcc & clang think that "%Ld" is , which doesn't quite * match up since int64_t is on a lot of 64-bit systems. * * If we have _FRR_ATTRIBUTE_PRINTFRR, we loaded a compiler plugin that * replaces the whole format checking bits with a custom version that * understands "%Ld" (along with "%pI4" and co.), so we don't need to do * anything. * * If we don't have that attribute... we still want -Wformat to work. So, * this is the "f*ck it" approach and we just redefine int64_t to always be * . This should work until such a time that is * something else (e.g. 128-bit integer)... let's just guard against that * with the _Static_assert below and work with the world we have right now, * where is always 64-bit. */ /* these need to be included before any of the following, so we can * "overwrite" things. */ #include #include #ifdef _FRR_ATTRIBUTE_PRINTFRR #define PRINTFRR(a, b) __attribute__((printfrr(a, b))) #else /* !_FRR_ATTRIBUTE_PRINTFRR */ #define PRINTFRR(a, b) __attribute__((format(printf, a, b))) /* these should be typedefs, but might also be #define */ #ifdef uint64_t #undef uint64_t #endif #ifdef int64_t #undef int64_t #endif /* can't overwrite the typedef, but we can replace int64_t with _int64_t */ typedef unsigned long long _uint64_t; #define uint64_t _uint64_t typedef signed long long _int64_t; #define int64_t _int64_t /* if this breaks, 128-bit machines may have entered reality (or * is something weird) */ #if __STDC_VERSION__ >= 201112L _Static_assert(sizeof(_uint64_t) == 8 && sizeof(_int64_t) == 8, "nobody expects the spanish intquisition"); #endif /* since we redefined int64_t, we also need to redefine PRI*64 */ #undef PRIu64 #undef PRId64 #undef PRIx64 #define PRIu64 "llu" #define PRId64 "lld" #define PRIx64 "llx" #endif /* !_FRR_ATTRIBUTE_PRINTFRR */ #ifdef __cplusplus } #endif #endif /* _FRR_COMPILER_H */ frr-7.2.1/lib/csv.c0000644000000000000000000003533413610377563010731 00000000000000/* CSV * Copyright (C) 2013 Cumulus Networks, Inc. * * This file is part of Quagga. * * Quagga is free software; you can redistribute it and/or modify it * under the terms of the GNU General Public License as published by the * Free Software Foundation; either version 2, or (at your option) any * later version. * * Quagga 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; see the file COPYING; if not, write to the Free Software * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA */ #ifdef HAVE_CONFIG_H #include "config.h" #endif #include #include #include #include #include #include #include #include #include "csv.h" #define DEBUG_E 1 #define DEBUG_V 1 #define log_error(fmt, ...) \ do { \ if (DEBUG_E) \ fprintf(stderr, "%s:%d:%s(): " fmt, __FILE__, \ __LINE__, __func__, ##__VA_ARGS__); \ } while (0) #define log_verbose(fmt, ...) \ do { \ if (DEBUG_V) \ fprintf(stderr, "%s:%d:%s(): " fmt, __FILE__, \ __LINE__, __func__, __VA_ARGS__); \ } while (0) struct _csv_field_t_ { TAILQ_ENTRY(_csv_field_t_) next_field; char *field; int field_len; }; struct _csv_record_t_ { TAILQ_HEAD(, _csv_field_t_) fields; TAILQ_ENTRY(_csv_record_t_) next_record; char *record; int rec_len; }; struct _csv_t_ { TAILQ_HEAD(, _csv_record_t_) records; char *buf; int buflen; int csv_len; int pointer; int num_recs; }; int csvlen(csv_t *csv) { return (csv->csv_len); } csv_t *csv_init(csv_t *csv, char *buf, int buflen) { if (csv == NULL) { csv = malloc(sizeof(csv_t)); if (csv == NULL) { log_error("CSV Malloc failed\n"); return (NULL); } } memset(csv, 0, sizeof(csv_t)); csv->buf = buf; csv->buflen = buflen; TAILQ_INIT(&(csv->records)); return (csv); } void csv_clean(csv_t *csv) { csv_record_t *rec; csv_record_t *rec_n; rec = TAILQ_FIRST(&(csv->records)); while (rec != NULL) { rec_n = TAILQ_NEXT(rec, next_record); csv_remove_record(csv, rec); rec = rec_n; } } void csv_free(csv_t *csv) { if (csv != NULL) { free(csv); } } static void csv_init_record(csv_record_t *record) { TAILQ_INIT(&(record->fields)); record->rec_len = 0; } csv_record_t *csv_record_iter(csv_t *csv) { return (TAILQ_FIRST(&(csv->records))); } csv_record_t *csv_record_iter_next(csv_record_t *rec) { if (!rec) return NULL; return (TAILQ_NEXT(rec, next_record)); } char *csv_field_iter(csv_record_t *rec, csv_field_t **fld) { if (!rec) return NULL; *fld = TAILQ_FIRST(&(rec->fields)); return ((*fld)->field); } char *csv_field_iter_next(csv_field_t **fld) { *fld = TAILQ_NEXT(*fld, next_field); if ((*fld) == NULL) { return (NULL); } return ((*fld)->field); } int csv_field_len(csv_field_t *fld) { if (fld) { return fld->field_len; } return 0; } static void csv_decode_record(csv_record_t *rec) { char *curr = rec->record; char *field; csv_field_t *fld; field = strpbrk(curr, ","); while (field != NULL) { fld = malloc(sizeof(csv_field_t)); if (fld) { TAILQ_INSERT_TAIL(&(rec->fields), fld, next_field); fld->field = curr; fld->field_len = field - curr; } curr = field + 1; field = strpbrk(curr, ","); } field = strstr(curr, "\n"); if (!field) return; fld = malloc(sizeof(csv_field_t)); if (fld) { fld->field = curr; fld->field_len = field - curr; TAILQ_INSERT_TAIL(&(rec->fields), fld, next_field); } } static csv_field_t *csv_add_field_to_record(csv_t *csv, csv_record_t *rec, char *col) { csv_field_t *fld; char *str = rec->record; int rlen = rec->rec_len; int blen = csv->buflen; fld = malloc(sizeof(csv_field_t)); if (!fld) { log_error("field malloc failed\n"); /* more cleanup needed */ return (NULL); } TAILQ_INSERT_TAIL(&(rec->fields), fld, next_field); fld->field = str + rlen; fld->field_len = snprintf((str + rlen), (blen - rlen), "%s", col); rlen += fld->field_len; rec->rec_len = rlen; return fld; } csv_record_t *csv_encode(csv_t *csv, int count, ...) { int tempc; va_list list; char *buf = csv->buf; int len = csv->buflen; int pointer = csv->pointer; char *str = NULL; char *col; csv_record_t *rec; csv_field_t *fld; if (buf) { str = buf + pointer; } else { /* allocate sufficient buffer */ str = (char *)malloc(csv->buflen); if (!str) { log_error("field str malloc failed\n"); return (NULL); } } va_start(list, count); rec = malloc(sizeof(csv_record_t)); if (!rec) { log_error("record malloc failed\n"); if (!buf) free(str); va_end(list); return (NULL); } csv_init_record(rec); rec->record = str; TAILQ_INSERT_TAIL(&(csv->records), rec, next_record); csv->num_recs++; /** * Iterate through the fields passed as a variable list and add them */ for (tempc = 0; tempc < count; tempc++) { col = va_arg(list, char *); fld = csv_add_field_to_record(csv, rec, col); if (!fld) { log_error("fld malloc failed\n"); csv_remove_record(csv, rec); va_end(list); return (NULL); } if (tempc < (count - 1)) { rec->rec_len += snprintf((str + rec->rec_len), (len - rec->rec_len), ","); } } rec->rec_len += snprintf((str + rec->rec_len), (len - rec->rec_len), "\n"); va_end(list); csv->csv_len += rec->rec_len; csv->pointer += rec->rec_len; return (rec); } int csv_num_records(csv_t *csv) { if (csv) { return csv->num_recs; } return 0; } csv_record_t *csv_encode_record(csv_t *csv, csv_record_t *rec, int count, ...) { int tempc; va_list list; char *str; char *col; csv_field_t *fld = NULL; int i; va_start(list, count); str = csv_field_iter(rec, &fld); if (!fld) { va_end(list); return NULL; } for (tempc = 0; tempc < count; tempc++) { col = va_arg(list, char *); for (i = 0; i < fld->field_len; i++) { str[i] = col[i]; } str = csv_field_iter_next(&fld); } va_end(list); return (rec); } csv_record_t *csv_append_record(csv_t *csv, csv_record_t *rec, int count, ...) { int tempc; va_list list; int len = csv->buflen, tlen; char *str; csv_field_t *fld; char *col; if (csv->buf) { /* not only works with discrete bufs */ return NULL; } if (!rec) { /* create a new rec */ rec = calloc(1, sizeof(csv_record_t)); if (!rec) { log_error("record malloc failed\n"); return NULL; } csv_init_record(rec); rec->record = calloc(1, csv->buflen); if (!rec->record) { log_error("field str malloc failed\n"); free(rec); return NULL; } csv_insert_record(csv, rec); } str = rec->record; va_start(list, count); if (rec->rec_len && (str[rec->rec_len - 1] == '\n')) str[rec->rec_len - 1] = ','; /** * Iterate through the fields passed as a variable list and add them */ tlen = rec->rec_len; for (tempc = 0; tempc < count; tempc++) { col = va_arg(list, char *); fld = csv_add_field_to_record(csv, rec, col); if (!fld) { log_error("fld malloc failed\n"); break; } if (tempc < (count - 1)) { rec->rec_len += snprintf((str + rec->rec_len), (len - rec->rec_len), ","); } } rec->rec_len += snprintf((str + rec->rec_len), (len - rec->rec_len), "\n"); va_end(list); csv->csv_len += (rec->rec_len - tlen); csv->pointer += (rec->rec_len - tlen); return (rec); } int csv_serialize(csv_t *csv, char *msgbuf, int msglen) { csv_record_t *rec; int offset = 0; if (!csv || !msgbuf) return -1; rec = csv_record_iter(csv); while (rec != NULL) { if ((offset + rec->rec_len) >= msglen) return -1; offset += sprintf(&msgbuf[offset], "%s", rec->record); rec = csv_record_iter_next(rec); } return 0; } void csv_clone_record(csv_t *csv, csv_record_t *in_rec, csv_record_t **out_rec) { char *curr; csv_record_t *rec; /* first check if rec belongs to this csv */ if (!csv_is_record_valid(csv, in_rec)) { log_error("rec not in this csv\n"); return; } /* only works with csv with discrete bufs */ if (csv->buf) { log_error( "un-supported for this csv type - single buf detected\n"); return; } /* create a new rec */ rec = calloc(1, sizeof(csv_record_t)); if (!rec) { log_error("record malloc failed\n"); return; } csv_init_record(rec); curr = calloc(1, csv->buflen); if (!curr) { log_error("field str malloc failed\n"); free(rec); return; } rec->record = curr; rec->rec_len = in_rec->rec_len; strcpy(rec->record, in_rec->record); /* decode record into fields */ csv_decode_record(rec); *out_rec = rec; } void csv_remove_record(csv_t *csv, csv_record_t *rec) { csv_field_t *fld = NULL, *p_fld; /* first check if rec belongs to this csv */ if (!csv_is_record_valid(csv, rec)) { log_error("rec not in this csv\n"); return; } /* remove fields */ csv_field_iter(rec, &fld); while (fld) { p_fld = fld; csv_field_iter_next(&fld); TAILQ_REMOVE(&(rec->fields), p_fld, next_field); free(p_fld); } TAILQ_REMOVE(&(csv->records), rec, next_record); csv->num_recs--; csv->csv_len -= rec->rec_len; csv->pointer -= rec->rec_len; if (!csv->buf) free(rec->record); free(rec); } void csv_insert_record(csv_t *csv, csv_record_t *rec) { /* first check if rec already in csv */ if (csv_is_record_valid(csv, rec)) { log_error("rec already in this csv\n"); return; } /* we can only insert records if no buf was supplied during csv init */ if (csv->buf) { log_error( "un-supported for this csv type - single buf detected\n"); return; } /* do we go beyond the max buf set for this csv ?*/ if ((csv->csv_len + rec->rec_len) > csv->buflen) { log_error("cannot insert - exceeded buf size\n"); return; } TAILQ_INSERT_TAIL(&(csv->records), rec, next_record); csv->num_recs++; csv->csv_len += rec->rec_len; csv->pointer += rec->rec_len; } csv_record_t *csv_concat_record(csv_t *csv, csv_record_t *rec1, csv_record_t *rec2) { char *curr; char *ret; csv_record_t *rec; /* first check if rec1 and rec2 belong to this csv */ if (!csv_is_record_valid(csv, rec1) || !csv_is_record_valid(csv, rec2)) { log_error("rec1 and/or rec2 invalid\n"); return (NULL); } /* we can only concat records if no buf was supplied during csv init */ if (csv->buf) { log_error( "un-supported for this csv type - single buf detected\n"); return (NULL); } /* create a new rec */ rec = calloc(1, sizeof(csv_record_t)); if (!rec) { log_error("record malloc failed\n"); return (NULL); } csv_init_record(rec); curr = (char *)calloc(1, csv->buflen); if (!curr) { log_error("field str malloc failed\n"); goto out_rec; } rec->record = curr; /* concat the record string */ ret = strstr(rec1->record, "\n"); if (!ret) { log_error("rec1 str not properly formatted\n"); goto out_curr; } snprintf(curr, (int)(ret - rec1->record + 1), "%s", rec1->record); strcat(curr, ","); ret = strstr(rec2->record, "\n"); if (!ret) { log_error("rec2 str not properly formatted\n"); goto out_curr; } snprintf((curr + strlen(curr)), (int)(ret - rec2->record + 1), "%s", rec2->record); strcat(curr, "\n"); rec->rec_len = strlen(curr); /* paranoia */ assert(csv->buflen > (csv->csv_len - rec1->rec_len - rec2->rec_len + rec->rec_len)); /* decode record into fields */ csv_decode_record(rec); /* now remove rec1 and rec2 and insert rec into this csv */ csv_remove_record(csv, rec1); csv_remove_record(csv, rec2); csv_insert_record(csv, rec); return rec; out_curr: free(curr); out_rec: free(rec); return NULL; } void csv_decode(csv_t *csv, char *inbuf) { char *buf; char *pos; csv_record_t *rec; buf = (inbuf) ? inbuf : csv->buf; assert(buf); pos = strpbrk(buf, "\n"); while (pos != NULL) { rec = calloc(1, sizeof(csv_record_t)); if (!rec) return; csv_init_record(rec); TAILQ_INSERT_TAIL(&(csv->records), rec, next_record); csv->num_recs++; if (csv->buf) rec->record = buf; else { rec->record = calloc(1, csv->buflen); if (!rec->record) { log_error("field str malloc failed\n"); return; } strncpy(rec->record, buf, pos - buf + 1); } rec->rec_len = pos - buf + 1; /* decode record into fields */ csv_decode_record(rec); buf = pos + 1; pos = strpbrk(buf, "\n"); } } int csv_is_record_valid(csv_t *csv, csv_record_t *in_rec) { csv_record_t *rec; int valid = 0; rec = csv_record_iter(csv); while (rec) { if (rec == in_rec) { valid = 1; break; } rec = csv_record_iter_next(rec); } return valid; } void csv_dump(csv_t *csv) { csv_record_t *rec; csv_field_t *fld; char *str; rec = csv_record_iter(csv); while (rec != NULL) { str = csv_field_iter(rec, &fld); while (str != NULL) { fprintf(stderr, "%s\n", str); str = csv_field_iter_next(&fld); } rec = csv_record_iter_next(rec); } } #ifdef TEST_CSV static int get_memory_usage(pid_t pid) { int fd, data, stack; char buf[4096], status_child[BUFSIZ]; char *vm; sprintf(status_child, "/proc/%d/status", pid); if ((fd = open(status_child, O_RDONLY)) < 0) return -1; read(fd, buf, 4095); buf[4095] = '\0'; close(fd); data = stack = 0; vm = strstr(buf, "VmData:"); if (vm) { sscanf(vm, "%*s %d", &data); } vm = strstr(buf, "VmStk:"); if (vm) { sscanf(vm, "%*s %d", &stack); } return data + stack; } int main() { char buf[10000]; csv_t csv; int i; csv_record_t *rec; char hdr1[32], hdr2[32]; log_verbose("Mem: %d\n", get_memory_usage(getpid())); csv_init(&csv, buf, 256); sprintf(hdr1, "%4d", 0); sprintf(hdr2, "%4d", 1); log_verbose("(%zu/%zu/%d/%d)\n", strlen(hdr1), strlen(hdr2), atoi(hdr1), atoi(hdr2)); rec = csv_encode(&csv, 2, hdr1, hdr2); csv_encode(&csv, 4, "name", "age", "sex", "hei"); csv_encode(&csv, 3, NULL, "0", NULL); csv_encode(&csv, 2, "p", "35"); for (i = 0; i < 50; i++) { csv_encode(&csv, 2, "p", "10"); } csv_encode(&csv, 2, "pdfadfadfadsadsaddfdfdsfdsd", "35444554545454545"); log_verbose("%s\n", buf); sprintf(hdr1, "%4d", csv.csv_len); sprintf(hdr2, "%4d", 1); log_verbose("(%zu/%zu/%d/%d)\n", strlen(hdr1), strlen(hdr2), atoi(hdr1), atoi(hdr2)); rec = csv_encode_record(&csv, rec, 2, hdr1, hdr2); log_verbose("(%d/%d)\n%s\n", rec->rec_len, csv.csv_len, buf); log_verbose("Mem: %d\n", get_memory_usage(getpid())); csv_clean(&csv); log_verbose("Mem: %d\n", get_memory_usage(getpid())); csv_init(&csv, buf, 256); csv_decode(&csv, NULL); log_verbose("%s", "AFTER DECODE\n"); csv_dump(&csv); csv_clean(&csv); log_verbose("Mem: %d\n", get_memory_usage(getpid())); } #endif frr-7.2.1/lib/csv.h0000644000000000000000000001253713610377563010736 00000000000000/* CSV * Copyright (C) 2013 Cumulus Networks, Inc. * * This file is part of Quagga. * * Quagga is free software; you can redistribute it and/or modify it * under the terms of the GNU General Public License as published by the * Free Software Foundation; either version 2, or (at your option) any * later version. * * Quagga 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; see the file COPYING; if not, write to the Free Software * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA */ #ifndef __CSV_H__ #define __CSV_H__ /* * CSV encoding and decoding routines. * * Example: * Encoding side: * * csv_t *csv; * csv_record_t *fstrec; * csv_record_t *rec; * char buf[BUFSIZ]; * * csv = csv_init(csv, buf, BUFSIZ); * ... * fstrec = csv_encode(csv, 2, "hello", "world"); * rec = csv_encode(csv, 2, "foo", "bar"); * ... * fstrec = csv_encode_record(csv, fstrec, 2, "HELLO", "WORLD"); * ... * csv_clean(csv); * * Decoding side: * * csv_t *csv; * csv_record_t *rec; * csv_field_t *fld; * char *rcvdbuf; * * csv = csv_init(csv, rcvdbuf, BUFSIZ); * ... * csv_decode(csv); * csv_dump(csv); * * for (rec = csv_record_iter(csv); rec; * rec = csv_record_iter_next(rec)) { * ... * for (str = csv_field_iter(rec, &fld); str; * str = csv_field_iter_next(&fld)) { * ... * } * } * ... * csv_clean(csv); */ #include #include #include #include #include #ifdef __cplusplus extern "C" { #endif typedef struct _csv_field_t_ csv_field_t; typedef struct _csv_record_t_ csv_record_t; typedef struct _csv_t_ csv_t; /** * Initialize the CSV structure (if necessary, allocate first). Point to * the passed string buffer. */ csv_t *csv_init(csv_t *csv, char *buf, int buflen); /** * Encode the variable list of arguments as CSV fields. The csv structure * should have been initialized (with the string buffer). The fields get * concatenated into the string. */ csv_record_t *csv_encode(csv_t *csv, int count, ...); /** * Encode the variable list arguments into an existing record, essentially * overwriting the record. No checking is done for consistency. The number * of fields should be the same as what was encoded and the length of each * field should also be the same as what was encoded before. The "rec" * parameter should be the same as what was returned from a previous call * to csv_encode(). * * Useful for message encoding/decoding that get passed around between * processes/nodes - e.g. the message header record can be rewritten AFTER * encoding all other records, with new information such as total length. */ csv_record_t *csv_encode_record(csv_t *csv, csv_record_t *rec, int count, ...); /** * Decode a CSV formatted string. The csv structure should have been * initialized (with the string). The function creates a LIST of records * (csv_record_t structure) where each record is in turn a LIST of fields * (csv_field_t structure). The record points to the string containing the * list of fields. Similarly, the field points to the field string. * NB: csv initialized for discrete buf , caller will pass inbuf */ void csv_decode(csv_t *csv, char *inbuf); /** * Dump all fields of a decoded CSV to stderr */ void csv_dump(csv_t *csv); /** * Total length of all characters encoded in the CSV. */ int csvlen(csv_t *csv); void csv_clean(csv_t *csv); void csv_free(csv_t *csv); /** * Iterate through the records and fields of an encoded/decoded CSV. */ csv_record_t *csv_record_iter(csv_t *csv); csv_record_t *csv_record_iter_next(csv_record_t *rec); char *csv_field_iter(csv_record_t *rec, csv_field_t **fld); char *csv_field_iter_next(csv_field_t **fld); /** * Return the length of field */ int csv_field_len(csv_field_t *fld); /** * Checks to see if a record belongs to a csv */ int csv_is_record_valid(csv_t *csv, csv_record_t *in_rec); /** * concat two records in a csv * Returns the newly formed record which includes fields from rec1 and rec2 * rec1 and rec2 are removed */ csv_record_t *csv_concat_record(csv_t *csv, csv_record_t *rec1, csv_record_t *rec2); /** * Remove a record from csv * Only works when csv has discrete record bufs */ void csv_remove_record(csv_t *csv, csv_record_t *rec); /** * Insert a record into csv * Only works when csv has discrete record bufs */ void csv_insert_record(csv_t *csv, csv_record_t *rec); /** * append fields to a record * Only works when csv has discrete record bufs */ csv_record_t *csv_append_record(csv_t *csv, csv_record_t *rec, int count, ...); /** * Serialize contents of csv into string * Only works when csv has discrete record bufs */ int csv_serialize(csv_t *csv, char *msgbuf, int msglen); /** * Clone a record. * Only works when csv has discrete record bufs */ void csv_clone_record(csv_t *csv, csv_record_t *in_rec, csv_record_t **out_rec); /** * Return number of records */ int csv_num_records(csv_t *csv); #ifdef __cplusplus } #endif #endif frr-7.2.1/lib/db.c0000644000000000000000000001621713610377563010522 00000000000000/* * Copyright (c) 2018 Rafael Zalamena * * This program is free software; you can redistribute it and/or modify it * under the terms of the 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; see the file COPYING; if not, write to the Free Software * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA */ /* * Copyright (c) 2016 Rafael Zalamena * * Permission to use, copy, modify, and/or distribute this software for any * purpose with or without fee is hereby granted, provided that the above * copyright notice and this permission notice appear in all copies. * * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. */ #include #include "db.h" #include "log.h" static struct sqlite3 *dbp; /* * Initialize the database in path. * * It's possible to use in memory database with ':memory:' path. */ int db_init(const char *path_fmt, ...) { char path[BUFSIZ]; va_list ap; if (dbp) return -1; va_start(ap, path_fmt); vsnprintf(path, sizeof(path), path_fmt, ap); va_end(ap); if (sqlite3_open_v2(path, &dbp, (SQLITE_OPEN_READWRITE | SQLITE_OPEN_CREATE), NULL) != SQLITE_OK) { if (dbp == NULL) { zlog_warn("%s: failed to open dabatase '%s'", __func__, path); return -1; } zlog_warn("%s: failed to open database '%s': %s", __func__, path, sqlite3_errmsg(dbp)); if (sqlite3_close_v2(dbp) != SQLITE_OK) zlog_warn("%s: failed to terminate database", __func__); dbp = NULL; return -1; } return 0; } /* Closes the database if open. */ int db_close(void) { if (dbp == NULL) return 0; if (sqlite3_close_v2(dbp) != SQLITE_OK) { zlog_warn("%s: failed to terminate database", __func__); return -1; } return 0; } /* Helper function to handle formating. */ static int db_vbindf(struct sqlite3_stmt *ss, const char *fmt, va_list vl) { const char *sptr = fmt; int column = 1; const char *str; void *blob; uint64_t uinteger64; uint32_t uinteger; int vlen; while (*sptr) { if (*sptr != '%') { sptr++; continue; } if (sptr++ && *sptr == 0) break; switch (*sptr) { case 'i': uinteger = va_arg(vl, uint32_t); if (sqlite3_bind_int(ss, column++, uinteger) != SQLITE_OK) return -1; break; case 'd': uinteger64 = va_arg(vl, uint64_t); if (sqlite3_bind_int64(ss, column++, uinteger64) != SQLITE_OK) return -1; break; case 's': str = va_arg(vl, const char *); vlen = va_arg(vl, int); if (sqlite3_bind_text(ss, column++, str, vlen, SQLITE_STATIC) != SQLITE_OK) return -1; break; case 'b': blob = va_arg(vl, void *); vlen = va_arg(vl, int); if (sqlite3_bind_blob(ss, column++, blob, vlen, SQLITE_STATIC) != SQLITE_OK) return -1; break; case 'n': if (sqlite3_bind_null(ss, column++) != SQLITE_OK) return -1; break; default: zlog_warn("%s: invalid format '%c'", __func__, *sptr); return -1; } } return 0; } /* * Binds values using format to the database query. * * Might be used to bind variables to a query, insert or update. */ int db_bindf(struct sqlite3_stmt *ss, const char *fmt, ...) { va_list vl; int result; va_start(vl, fmt); result = db_vbindf(ss, fmt, vl); va_end(vl); return result; } /* Prepares an statement to the database with the statement length. */ struct sqlite3_stmt *db_prepare_len(const char *stmt, int stmtlen) { struct sqlite3_stmt *ss; int c; if (dbp == NULL) return NULL; c = sqlite3_prepare_v2(dbp, stmt, stmtlen, &ss, NULL); if (ss == NULL) { zlog_warn("%s: failed to prepare (%d:%s)", __func__, c, sqlite3_errmsg(dbp)); return NULL; } return ss; } /* Prepares an statement to the database. */ struct sqlite3_stmt *db_prepare(const char *stmt) { return db_prepare_len(stmt, strlen(stmt)); } /* Run a prepared statement. */ int db_run(struct sqlite3_stmt *ss) { int result; result = sqlite3_step(ss); switch (result) { case SQLITE_BUSY: /* TODO handle busy database. */ break; case SQLITE_OK: /* * SQLITE_DONE just causes confusion since it means the query went OK, * but it has a different value. */ case SQLITE_DONE: result = SQLITE_OK; break; case SQLITE_ROW: /* NOTHING */ /* It is expected to receive SQLITE_ROW on search queries. */ break; default: zlog_warn("%s: step failed (%d:%s)", __func__, result, sqlite3_errstr(result)); } return result; } /* Helper function to load format to variables. */ static int db_vloadf(struct sqlite3_stmt *ss, const char *fmt, va_list vl) { const char *sptr = fmt; int column = 0; const char **str; void *blob; const void *blobsrc; uint64_t *uinteger64; uint32_t *uinteger; int vlen; int dlen; int columncount; columncount = sqlite3_column_count(ss); if (columncount == 0) return -1; while (*sptr) { if (*sptr != '%') { sptr++; continue; } if (sptr++ && *sptr == 0) break; switch (*sptr) { case 'i': uinteger = va_arg(vl, uint32_t *); *uinteger = sqlite3_column_int(ss, column); break; case 'd': uinteger64 = va_arg(vl, uint64_t *); *uinteger64 = sqlite3_column_int64(ss, column); break; case 's': str = va_arg(vl, const char **); *str = (const char *)sqlite3_column_text(ss, column); break; case 'b': blob = va_arg(vl, void *); vlen = va_arg(vl, int); dlen = sqlite3_column_bytes(ss, column); blobsrc = sqlite3_column_blob(ss, column); memcpy(blob, blobsrc, MIN(vlen, dlen)); break; default: zlog_warn("%s: invalid format '%c'", __func__, *sptr); return -1; } column++; } return 0; } /* Function to load format from database row. */ int db_loadf(struct sqlite3_stmt *ss, const char *fmt, ...) { va_list vl; int result; va_start(vl, fmt); result = db_vloadf(ss, fmt, vl); va_end(vl); return result; } /* Finalize query and return memory. */ void db_finalize(struct sqlite3_stmt **ss) { sqlite3_finalize(*ss); *ss = NULL; } /* Execute one or more statements. */ int db_execute(const char *stmt_fmt, ...) { char stmt[BUFSIZ]; va_list ap; if (dbp == NULL) return -1; va_start(ap, stmt_fmt); vsnprintf(stmt, sizeof(stmt), stmt_fmt, ap); va_end(ap); if (sqlite3_exec(dbp, stmt, NULL, 0, NULL) != SQLITE_OK) { zlog_warn("%s: failed to execute statement(s): %s", __func__, sqlite3_errmsg(dbp)); return -1; } return 0; } frr-7.2.1/lib/db.h0000644000000000000000000000436613610377563010531 00000000000000/* * Copyright (c) 2018 Rafael Zalamena * * This program is free software; you can redistribute it and/or modify it * under the terms of the 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; see the file COPYING; if not, write to the Free Software * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA */ /* * Copyright (c) 2016 Rafael Zalamena * * Permission to use, copy, modify, and/or distribute this software for any * purpose with or without fee is hereby granted, provided that the above * copyright notice and this permission notice appear in all copies. * * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. */ #ifndef _FRR_DB_H_ #define _FRR_DB_H_ #ifdef HAVE_SQLITE3 #include #ifdef __cplusplus extern "C" { #endif extern int db_init(const char *path_fmt, ...); extern int db_close(void); extern int db_bindf(struct sqlite3_stmt *ss, const char *fmt, ...); extern struct sqlite3_stmt *db_prepare_len(const char *stmt, int stmtlen); extern struct sqlite3_stmt *db_prepare(const char *stmt); extern int db_run(struct sqlite3_stmt *ss); extern int db_loadf(struct sqlite3_stmt *ss, const char *fmt, ...); extern void db_finalize(struct sqlite3_stmt **ss); extern int db_execute(const char *stmt_fmt, ...); #ifdef __cplusplus } #endif #endif /* HAVE_SQLITE3 */ #endif /* _FRR_DB_H_ */ frr-7.2.1/lib/debug.c0000644000000000000000000000340713610377563011220 00000000000000/* * Debugging utilities. * Copyright (C) 2018 Cumulus Networks, Inc. * Quentin Young * * This program is free software; you can redistribute it and/or modify it * under the terms of the 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; see the file COPYING; if not, write to the Free Software * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA */ #include #include "typesafe.h" #include "debug.h" #include "command.h" static struct debug_cb_list_head cb_head; DECLARE_LIST(debug_cb_list, struct debug_callbacks, item) /* All code in this section should be reentrant and MT-safe */ DEFUN_NOSH(debug_all, debug_all_cmd, "[no] debug all", NO_STR DEBUG_STR "Toggle all debugging output\n") { struct debug_callbacks *cb; bool set = !strmatch(argv[0]->text, "no"); uint32_t mode = DEBUG_NODE2MODE(vty->node); frr_each (debug_cb_list, &cb_head, cb) cb->debug_set_all(mode, set); return CMD_SUCCESS; } /* ------------------------------------------------------------------------- */ void debug_init(struct debug_callbacks *cb) { static bool inited = false; if (!inited) { inited = true; debug_cb_list_init(&cb_head); } debug_cb_list_add_head(&cb_head, cb); } void debug_init_cli(void) { install_element(ENABLE_NODE, &debug_all_cmd); install_element(CONFIG_NODE, &debug_all_cmd); } frr-7.2.1/lib/debug.h0000644000000000000000000001773313610377563011234 00000000000000/* * Debugging utilities. * Copyright (C) 2018 Cumulus Networks, Inc. * Quentin Young * * This program is free software; you can redistribute it and/or modify it * under the terms of the 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; see the file COPYING; if not, write to the Free Software * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA */ #ifndef _FRRDEBUG_H #define _FRRDEBUG_H #include #include "command.h" #include "frratomic.h" #ifdef __cplusplus extern "C" { #endif /* * Debugging modes. * * FRR's convention is that a debug statement issued under the vty CONFIG_NODE * persists to the config file, whereas the same debug statement issued from * the ENABLE_NODE only persists for the current session. These are mapped to * DEBUG_MODE_CONF and DEBUG_MODE_TERM respectively. * * They are not mutually exclusive and are placed in the MSB of the flags * field in a debugging record. */ #define DEBUG_MODE_TERM 0x01000000 #define DEBUG_MODE_CONF 0x02000000 #define DEBUG_MODE_ALL (DEBUG_MODE_TERM | DEBUG_MODE_CONF) #define DEBUG_MODE_NONE 0x00000000 #define DEBUG_OPT_ALL 0x00FFFFFF #define DEBUG_OPT_NONE 0x00000000 /* * Debugging record. * * All operations on this record exposed in this header are MT-safe. * * flags * A bitfield with the following format (bytes high to low) * - [0] Debugging mode field (MSB) | Mode * - [1] Arbitrary flag field | Option * - [2] Arbitrary flag field | Option * - [3] Arbitrary flag field (LSB) | Option * * ALL THESE BYTES ARE YOURS - EXCEPT MODE. * ATTEMPT NO BIT OPS THERE. * * The MSB of this field determines the debug mode, Use the DEBUG_MODE* * macros to manipulate this byte. * * The low 3 bytes of this field may be used to store arbitrary information. * Usually they are used to store flags that tune how detailed the logging * for a particular debug record is. Use the DEBUG_OPT* macros to manipulate * those bytes. * * All operations performed on this field should be done using the macros * later in this header file. They are guaranteed to be atomic operations * with respect to this field. Using anything except the macros to * manipulate the flags field in a multithreaded environment results in * undefined behavior. * * desc * Human-readable description of this debugging record. */ struct debug { atomic_uint_fast32_t flags; const char *desc; }; PREDECL_LIST(debug_cb_list) /* * Callback set for debugging code. * * debug_set_all * Function pointer to call when the user requests that all debugs have a * mode set. */ struct debug_callbacks { /* * Linked list of Callbacks to call */ struct debug_cb_list_item item; /* * flags * flags to set on debug flag fields * * set * true: set flags * false: unset flags */ void (*debug_set_all)(uint32_t flags, bool set); }; /* * Check if a mode is set for a debug. * * MT-Safe */ #define DEBUG_MODE_CHECK(name, mode) \ CHECK_FLAG_ATOMIC(&(name)->flags, (mode)&DEBUG_MODE_ALL) /* * Check if an option bit is set for a debug. * * MT-Safe */ #define DEBUG_OPT_CHECK(name, opt) \ CHECK_FLAG_ATOMIC(&(name)->flags, (opt)&DEBUG_OPT_ALL) /* * Check if bits are set for a debug. * * MT-Safe */ #define DEBUG_FLAGS_CHECK(name, fl) CHECK_FLAG_ATOMIC(&(name)->flags, (fl)) /* * Set modes on a debug. * * MT-Safe */ #define DEBUG_MODE_SET(name, mode, onoff) \ do { \ if (onoff) \ SET_FLAG_ATOMIC(&(name)->flags, \ (mode)&DEBUG_MODE_ALL); \ else \ UNSET_FLAG_ATOMIC(&(name)->flags, \ (mode)&DEBUG_MODE_ALL); \ } while (0) /* Convenience macros for specific set operations. */ #define DEBUG_MODE_ON(name, mode) DEBUG_MODE_SET(name, mode, true) #define DEBUG_MODE_OFF(name, mode) DEBUG_MODE_SET(name, mode, false) /* * Set options on a debug. * * MT-Safe */ #define DEBUG_OPT_SET(name, opt, onoff) \ do { \ if (onoff) \ SET_FLAG_ATOMIC(&(name)->flags, (opt)&DEBUG_OPT_ALL); \ else \ UNSET_FLAG_ATOMIC(&(name)->flags, \ (opt)&DEBUG_OPT_ALL); \ } while (0) /* Convenience macros for specific set operations. */ #define DEBUG_OPT_ON(name, opt) DEBUG_OPT_SET(name, opt, true) #define DEBUG_OPT_OFF(name, opt) DEBUG_OPT_SET(name, opt, true) /* * Set bits on a debug. * * MT-Safe */ #define DEBUG_FLAGS_SET(name, fl, onoff) \ do { \ if (onoff) \ SET_FLAG_ATOMIC(&(name)->flags, (fl)); \ else \ UNSET_FLAG_ATOMIC(&(name)->flags, (fl)); \ } while (0) /* Convenience macros for specific set operations. */ #define DEBUG_FLAGS_ON(name, fl) DEBUG_FLAGS_SET(&(name)->flags, (type), true) #define DEBUG_FLAGS_OFF(name, fl) DEBUG_FLAGS_SET(&(name)->flags, (type), false) /* * Unset all modes and options on a debug. * * MT-Safe */ #define DEBUG_CLEAR(name) RESET_FLAG_ATOMIC(&(name)->flags) /* * Set all modes and options on a debug. * * MT-Safe */ #define DEBUG_ON(name) \ SET_FLAG_ATOMIC(&(name)->flags, DEBUG_MODE_ALL | DEBUG_OPT_ALL) /* * Map a vty node to the correct debugging mode flags. FRR behaves such that a * debug statement issued under the config node persists to the config file, * whereas the same debug statement issued from the enable node only persists * for the current session. * * MT-Safe */ #define DEBUG_NODE2MODE(vtynode) \ (((vtynode) == CONFIG_NODE) ? DEBUG_MODE_ALL : DEBUG_MODE_TERM) /* * Debug at the given level to the default logging destination. * * MT-Safe */ #define DEBUG(level, name, fmt, ...) \ do { \ if (DEBUG_MODE_CHECK(name, DEBUG_MODE_ALL)) \ zlog_##level(fmt, ##__VA_ARGS__); \ } while (0) /* Convenience macros for the various levels. */ #define DEBUGE(name, fmt, ...) DEBUG(err, name, fmt, ##__VA_ARGS__) #define DEBUGW(name, fmt, ...) DEBUG(warn, name, fmt, ##__VA_ARGS__) #define DEBUGI(name, fmt, ...) DEBUG(info, name, fmt, ##__VA_ARGS__) #define DEBUGN(name, fmt, ...) DEBUG(notice, name, fmt, ##__VA_ARGS__) #define DEBUGD(name, fmt, ...) DEBUG(debug, name, fmt, ##__VA_ARGS__) /* * Optional initializer for debugging. Highly recommended. * * This function installs common debugging commands and allows the caller to * specify callbacks to take when these commands are issued, allowing the * caller to respond to events such as a request to turn off all debugs. * * MT-Safe */ void debug_init(struct debug_callbacks *cb); /* * Turn on the cli to turn on/off debugs. * Should only be called by libfrr */ void debug_init_cli(void); #ifdef __cplusplus } #endif #endif /* _FRRDEBUG_H */ frr-7.2.1/lib/defun_lex.c0000644000000000000000000017767013610377563012121 00000000000000#line 2 "lib/defun_lex.c" #ifdef HAVE_CONFIG_H #include "config.h" #endif #line 7 "lib/defun_lex.c" #define YY_INT_ALIGNED short int /* A lexical scanner generated by flex */ #define yy_create_buffer def_yy_create_buffer #define yy_delete_buffer def_yy_delete_buffer #define yy_scan_buffer def_yy_scan_buffer #define yy_scan_string def_yy_scan_string #define yy_scan_bytes def_yy_scan_bytes #define yy_init_buffer def_yy_init_buffer #define yy_flush_buffer def_yy_flush_buffer #define yy_load_buffer_state def_yy_load_buffer_state #define yy_switch_to_buffer def_yy_switch_to_buffer #define yypush_buffer_state def_yypush_buffer_state #define yypop_buffer_state def_yypop_buffer_state #define yyensure_buffer_stack def_yyensure_buffer_stack #define yy_flex_debug def_yy_flex_debug #define yyin def_yyin #define yyleng def_yyleng #define yylex def_yylex #define yylineno def_yylineno #define yyout def_yyout #define yyrestart def_yyrestart #define yytext def_yytext #define yywrap def_yywrap #define yyalloc def_yyalloc #define yyrealloc def_yyrealloc #define yyfree def_yyfree #define FLEX_SCANNER #define YY_FLEX_MAJOR_VERSION 2 #define YY_FLEX_MINOR_VERSION 6 #define YY_FLEX_SUBMINOR_VERSION 4 #if YY_FLEX_SUBMINOR_VERSION > 0 #define FLEX_BETA #endif #ifdef yy_create_buffer #define def_yy_create_buffer_ALREADY_DEFINED #else #define yy_create_buffer def_yy_create_buffer #endif #ifdef yy_delete_buffer #define def_yy_delete_buffer_ALREADY_DEFINED #else #define yy_delete_buffer def_yy_delete_buffer #endif #ifdef yy_scan_buffer #define def_yy_scan_buffer_ALREADY_DEFINED #else #define yy_scan_buffer def_yy_scan_buffer #endif #ifdef yy_scan_string #define def_yy_scan_string_ALREADY_DEFINED #else #define yy_scan_string def_yy_scan_string #endif #ifdef yy_scan_bytes #define def_yy_scan_bytes_ALREADY_DEFINED #else #define yy_scan_bytes def_yy_scan_bytes #endif #ifdef yy_init_buffer #define def_yy_init_buffer_ALREADY_DEFINED #else #define yy_init_buffer def_yy_init_buffer #endif #ifdef yy_flush_buffer #define def_yy_flush_buffer_ALREADY_DEFINED #else #define yy_flush_buffer def_yy_flush_buffer #endif #ifdef yy_load_buffer_state #define def_yy_load_buffer_state_ALREADY_DEFINED #else #define yy_load_buffer_state def_yy_load_buffer_state #endif #ifdef yy_switch_to_buffer #define def_yy_switch_to_buffer_ALREADY_DEFINED #else #define yy_switch_to_buffer def_yy_switch_to_buffer #endif #ifdef yypush_buffer_state #define def_yypush_buffer_state_ALREADY_DEFINED #else #define yypush_buffer_state def_yypush_buffer_state #endif #ifdef yypop_buffer_state #define def_yypop_buffer_state_ALREADY_DEFINED #else #define yypop_buffer_state def_yypop_buffer_state #endif #ifdef yyensure_buffer_stack #define def_yyensure_buffer_stack_ALREADY_DEFINED #else #define yyensure_buffer_stack def_yyensure_buffer_stack #endif #ifdef yylex #define def_yylex_ALREADY_DEFINED #else #define yylex def_yylex #endif #ifdef yyrestart #define def_yyrestart_ALREADY_DEFINED #else #define yyrestart def_yyrestart #endif #ifdef yylex_init #define def_yylex_init_ALREADY_DEFINED #else #define yylex_init def_yylex_init #endif #ifdef yylex_init_extra #define def_yylex_init_extra_ALREADY_DEFINED #else #define yylex_init_extra def_yylex_init_extra #endif #ifdef yylex_destroy #define def_yylex_destroy_ALREADY_DEFINED #else #define yylex_destroy def_yylex_destroy #endif #ifdef yyget_debug #define def_yyget_debug_ALREADY_DEFINED #else #define yyget_debug def_yyget_debug #endif #ifdef yyset_debug #define def_yyset_debug_ALREADY_DEFINED #else #define yyset_debug def_yyset_debug #endif #ifdef yyget_extra #define def_yyget_extra_ALREADY_DEFINED #else #define yyget_extra def_yyget_extra #endif #ifdef yyset_extra #define def_yyset_extra_ALREADY_DEFINED #else #define yyset_extra def_yyset_extra #endif #ifdef yyget_in #define def_yyget_in_ALREADY_DEFINED #else #define yyget_in def_yyget_in #endif #ifdef yyset_in #define def_yyset_in_ALREADY_DEFINED #else #define yyset_in def_yyset_in #endif #ifdef yyget_out #define def_yyget_out_ALREADY_DEFINED #else #define yyget_out def_yyget_out #endif #ifdef yyset_out #define def_yyset_out_ALREADY_DEFINED #else #define yyset_out def_yyset_out #endif #ifdef yyget_leng #define def_yyget_leng_ALREADY_DEFINED #else #define yyget_leng def_yyget_leng #endif #ifdef yyget_text #define def_yyget_text_ALREADY_DEFINED #else #define yyget_text def_yyget_text #endif #ifdef yyget_lineno #define def_yyget_lineno_ALREADY_DEFINED #else #define yyget_lineno def_yyget_lineno #endif #ifdef yyset_lineno #define def_yyset_lineno_ALREADY_DEFINED #else #define yyset_lineno def_yyset_lineno #endif #ifdef yywrap #define def_yywrap_ALREADY_DEFINED #else #define yywrap def_yywrap #endif #ifdef yyalloc #define def_yyalloc_ALREADY_DEFINED #else #define yyalloc def_yyalloc #endif #ifdef yyrealloc #define def_yyrealloc_ALREADY_DEFINED #else #define yyrealloc def_yyrealloc #endif #ifdef yyfree #define def_yyfree_ALREADY_DEFINED #else #define yyfree def_yyfree #endif #ifdef yytext #define def_yytext_ALREADY_DEFINED #else #define yytext def_yytext #endif #ifdef yyleng #define def_yyleng_ALREADY_DEFINED #else #define yyleng def_yyleng #endif #ifdef yyin #define def_yyin_ALREADY_DEFINED #else #define yyin def_yyin #endif #ifdef yyout #define def_yyout_ALREADY_DEFINED #else #define yyout def_yyout #endif #ifdef yy_flex_debug #define def_yy_flex_debug_ALREADY_DEFINED #else #define yy_flex_debug def_yy_flex_debug #endif #ifdef yylineno #define def_yylineno_ALREADY_DEFINED #else #define yylineno def_yylineno #endif /* First, we deal with platform-specific or compiler-specific issues. */ /* begin standard C headers. */ #include #include #include #include /* end standard C headers. */ /* flex integer type definitions */ #ifndef FLEXINT_H #define FLEXINT_H /* C99 systems have . Non-C99 systems may or may not. */ #if defined (__STDC_VERSION__) && __STDC_VERSION__ >= 199901L /* C99 says to define __STDC_LIMIT_MACROS before including stdint.h, * if you want the limit (max/min) macros for int types. */ #ifndef __STDC_LIMIT_MACROS #define __STDC_LIMIT_MACROS 1 #endif #include typedef int8_t flex_int8_t; typedef uint8_t flex_uint8_t; typedef int16_t flex_int16_t; typedef uint16_t flex_uint16_t; typedef int32_t flex_int32_t; typedef uint32_t flex_uint32_t; #else typedef signed char flex_int8_t; typedef short int flex_int16_t; typedef int flex_int32_t; typedef unsigned char flex_uint8_t; typedef unsigned short int flex_uint16_t; typedef unsigned int flex_uint32_t; /* Limits of integral types. */ #ifndef INT8_MIN #define INT8_MIN (-128) #endif #ifndef INT16_MIN #define INT16_MIN (-32767-1) #endif #ifndef INT32_MIN #define INT32_MIN (-2147483647-1) #endif #ifndef INT8_MAX #define INT8_MAX (127) #endif #ifndef INT16_MAX #define INT16_MAX (32767) #endif #ifndef INT32_MAX #define INT32_MAX (2147483647) #endif #ifndef UINT8_MAX #define UINT8_MAX (255U) #endif #ifndef UINT16_MAX #define UINT16_MAX (65535U) #endif #ifndef UINT32_MAX #define UINT32_MAX (4294967295U) #endif #ifndef SIZE_MAX #define SIZE_MAX (~(size_t)0) #endif #endif /* ! C99 */ #endif /* ! FLEXINT_H */ /* begin standard C++ headers. */ /* TODO: this is always defined, so inline it */ #define yyconst const #if defined(__GNUC__) && __GNUC__ >= 3 #define yynoreturn __attribute__((__noreturn__)) #else #define yynoreturn #endif /* Returned upon end-of-file. */ #define YY_NULL 0 /* Promotes a possibly negative, possibly signed char to an * integer in range [0..255] for use as an array index. */ #define YY_SC_TO_UI(c) ((YY_CHAR) (c)) /* Enter a start condition. This macro really ought to take a parameter, * but we do it the disgusting crufty way forced on us by the ()-less * definition of BEGIN. */ #define BEGIN (yy_start) = 1 + 2 * /* Translate the current start state into a value that can be later handed * to BEGIN to return to the state. The YYSTATE alias is for lex * compatibility. */ #define YY_START (((yy_start) - 1) / 2) #define YYSTATE YY_START /* Action number for EOF rule of a given start state. */ #define YY_STATE_EOF(state) (YY_END_OF_BUFFER + state + 1) /* Special action meaning "start processing a new file". */ #define YY_NEW_FILE yyrestart( yyin ) #define YY_END_OF_BUFFER_CHAR 0 /* Size of default input buffer. */ #ifndef YY_BUF_SIZE #ifdef __ia64__ /* On IA-64, the buffer size is 16k, not 8k. * Moreover, YY_BUF_SIZE is 2*YY_READ_BUF_SIZE in the general case. * Ditto for the __ia64__ case accordingly. */ #define YY_BUF_SIZE 32768 #else #define YY_BUF_SIZE 16384 #endif /* __ia64__ */ #endif /* The state buf must be large enough to hold one state per character in the main buffer. */ #define YY_STATE_BUF_SIZE ((YY_BUF_SIZE + 2) * sizeof(yy_state_type)) #ifndef YY_TYPEDEF_YY_BUFFER_STATE #define YY_TYPEDEF_YY_BUFFER_STATE typedef struct yy_buffer_state *YY_BUFFER_STATE; #endif #ifndef YY_TYPEDEF_YY_SIZE_T #define YY_TYPEDEF_YY_SIZE_T typedef size_t yy_size_t; #endif extern int yyleng; extern FILE *yyin, *yyout; #define EOB_ACT_CONTINUE_SCAN 0 #define EOB_ACT_END_OF_FILE 1 #define EOB_ACT_LAST_MATCH 2 /* Note: We specifically omit the test for yy_rule_can_match_eol because it requires * access to the local variable yy_act. Since yyless() is a macro, it would break * existing scanners that call yyless() from OUTSIDE yylex. * One obvious solution it to make yy_act a global. I tried that, and saw * a 5% performance hit in a non-yylineno scanner, because yy_act is * normally declared as a register variable-- so it is not worth it. */ #define YY_LESS_LINENO(n) \ do { \ int yyl;\ for ( yyl = n; yyl < yyleng; ++yyl )\ if ( yytext[yyl] == '\n' )\ --yylineno;\ }while(0) #define YY_LINENO_REWIND_TO(dst) \ do {\ const char *p;\ for ( p = yy_cp-1; p >= (dst); --p)\ if ( *p == '\n' )\ --yylineno;\ }while(0) /* Return all but the first "n" matched characters back to the input stream. */ #define yyless(n) \ do \ { \ /* Undo effects of setting up yytext. */ \ int yyless_macro_arg = (n); \ YY_LESS_LINENO(yyless_macro_arg);\ *yy_cp = (yy_hold_char); \ YY_RESTORE_YY_MORE_OFFSET \ (yy_c_buf_p) = yy_cp = yy_bp + yyless_macro_arg - YY_MORE_ADJ; \ YY_DO_BEFORE_ACTION; /* set up yytext again */ \ } \ while ( 0 ) #define unput(c) yyunput( c, (yytext_ptr) ) #ifndef YY_STRUCT_YY_BUFFER_STATE #define YY_STRUCT_YY_BUFFER_STATE struct yy_buffer_state { FILE *yy_input_file; char *yy_ch_buf; /* input buffer */ char *yy_buf_pos; /* current position in input buffer */ /* Size of input buffer in bytes, not including room for EOB * characters. */ int yy_buf_size; /* Number of characters read into yy_ch_buf, not including EOB * characters. */ int yy_n_chars; /* Whether we "own" the buffer - i.e., we know we created it, * and can realloc() it to grow it, and should free() it to * delete it. */ int yy_is_our_buffer; /* Whether this is an "interactive" input source; if so, and * if we're using stdio for input, then we want to use getc() * instead of fread(), to make sure we stop fetching input after * each newline. */ int yy_is_interactive; /* Whether we're considered to be at the beginning of a line. * If so, '^' rules will be active on the next match, otherwise * not. */ int yy_at_bol; int yy_bs_lineno; /**< The line count. */ int yy_bs_column; /**< The column count. */ /* Whether to try to fill the input buffer when we reach the * end of it. */ int yy_fill_buffer; int yy_buffer_status; #define YY_BUFFER_NEW 0 #define YY_BUFFER_NORMAL 1 /* When an EOF's been seen but there's still some text to process * then we mark the buffer as YY_EOF_PENDING, to indicate that we * shouldn't try reading from the input source any more. We might * still have a bunch of tokens to match, though, because of * possible backing-up. * * When we actually see the EOF, we change the status to "new" * (via yyrestart()), so that the user can continue scanning by * just pointing yyin at a new input file. */ #define YY_BUFFER_EOF_PENDING 2 }; #endif /* !YY_STRUCT_YY_BUFFER_STATE */ /* Stack of input buffers. */ static size_t yy_buffer_stack_top = 0; /**< index of top of stack. */ static size_t yy_buffer_stack_max = 0; /**< capacity of stack. */ static YY_BUFFER_STATE * yy_buffer_stack = NULL; /**< Stack as an array. */ /* We provide macros for accessing buffer states in case in the * future we want to put the buffer states in a more general * "scanner state". * * Returns the top of the stack, or NULL. */ #define YY_CURRENT_BUFFER ( (yy_buffer_stack) \ ? (yy_buffer_stack)[(yy_buffer_stack_top)] \ : NULL) /* Same as previous macro, but useful when we know that the buffer stack is not * NULL or when we need an lvalue. For internal use only. */ #define YY_CURRENT_BUFFER_LVALUE (yy_buffer_stack)[(yy_buffer_stack_top)] /* yy_hold_char holds the character lost when yytext is formed. */ static char yy_hold_char; static int yy_n_chars; /* number of characters read into yy_ch_buf */ int yyleng; /* Points to current character in buffer. */ static char *yy_c_buf_p = NULL; static int yy_init = 0; /* whether we need to initialize */ static int yy_start = 0; /* start state number */ /* Flag which is used to allow yywrap()'s to do buffer switches * instead of setting up a fresh yyin. A bit of a hack ... */ static int yy_did_buffer_switch_on_eof; void yyrestart ( FILE *input_file ); void yy_switch_to_buffer ( YY_BUFFER_STATE new_buffer ); YY_BUFFER_STATE yy_create_buffer ( FILE *file, int size ); void yy_delete_buffer ( YY_BUFFER_STATE b ); void yy_flush_buffer ( YY_BUFFER_STATE b ); void yypush_buffer_state ( YY_BUFFER_STATE new_buffer ); void yypop_buffer_state ( void ); static void yyensure_buffer_stack ( void ); static void yy_load_buffer_state ( void ); static void yy_init_buffer ( YY_BUFFER_STATE b, FILE *file ); #define YY_FLUSH_BUFFER yy_flush_buffer( YY_CURRENT_BUFFER ) YY_BUFFER_STATE yy_scan_buffer ( char *base, yy_size_t size ); YY_BUFFER_STATE yy_scan_string ( const char *yy_str ); YY_BUFFER_STATE yy_scan_bytes ( const char *bytes, int len ); void *yyalloc ( yy_size_t ); void *yyrealloc ( void *, yy_size_t ); void yyfree ( void * ); #define yy_new_buffer yy_create_buffer #define yy_set_interactive(is_interactive) \ { \ if ( ! YY_CURRENT_BUFFER ){ \ yyensure_buffer_stack (); \ YY_CURRENT_BUFFER_LVALUE = \ yy_create_buffer( yyin, YY_BUF_SIZE ); \ } \ YY_CURRENT_BUFFER_LVALUE->yy_is_interactive = is_interactive; \ } #define yy_set_bol(at_bol) \ { \ if ( ! YY_CURRENT_BUFFER ){\ yyensure_buffer_stack (); \ YY_CURRENT_BUFFER_LVALUE = \ yy_create_buffer( yyin, YY_BUF_SIZE ); \ } \ YY_CURRENT_BUFFER_LVALUE->yy_at_bol = at_bol; \ } #define YY_AT_BOL() (YY_CURRENT_BUFFER_LVALUE->yy_at_bol) /* Begin user sect3 */ #define def_yywrap() (/*CONSTCOND*/1) #define YY_SKIP_YYWRAP typedef flex_uint8_t YY_CHAR; FILE *yyin = NULL, *yyout = NULL; typedef int yy_state_type; extern int yylineno; int yylineno = 1; extern char *yytext; #ifdef yytext_ptr #undef yytext_ptr #endif #define yytext_ptr yytext static yy_state_type yy_get_previous_state ( void ); static yy_state_type yy_try_NUL_trans ( yy_state_type current_state ); static int yy_get_next_buffer ( void ); static void yynoreturn yy_fatal_error ( const char* msg ); /* Done after the current pattern has been matched and before the * corresponding action - sets up yytext. */ #define YY_DO_BEFORE_ACTION \ (yytext_ptr) = yy_bp; \ yyleng = (int) (yy_cp - yy_bp); \ (yy_hold_char) = *yy_cp; \ *yy_cp = '\0'; \ (yy_c_buf_p) = yy_cp; #define YY_NUM_RULES 38 #define YY_END_OF_BUFFER 39 /* This struct is not used in this scanner, but its presence is necessary. */ struct yy_trans_info { flex_int32_t yy_verify; flex_int32_t yy_nxt; }; static const flex_int16_t yy_accept[139] = { 0, 0, 0, 0, 0, 3, 3, 8, 8, 0, 0, 0, 0, 39, 37, 32, 1, 35, 15, 15, 36, 35, 34, 34, 34, 34, 33, 34, 10, 3, 5, 4, 8, 9, 12, 11, 12, 38, 19, 16, 38, 32, 2, 7, 34, 34, 34, 34, 34, 3, 4, 4, 6, 8, 12, 2, 7, 14, 13, 14, 19, 18, 17, 34, 34, 34, 34, 34, 34, 34, 34, 34, 27, 23, 20, 34, 34, 34, 34, 34, 34, 34, 34, 34, 34, 34, 34, 34, 34, 34, 34, 34, 34, 34, 34, 34, 34, 34, 34, 34, 34, 34, 34, 34, 34, 34, 34, 34, 34, 34, 25, 34, 24, 34, 21, 34, 34, 34, 34, 34, 34, 34, 34, 34, 28, 26, 22, 34, 34, 34, 34, 30, 34, 34, 34, 34, 29, 31, 0 } ; static const YY_CHAR yy_ec[256] = { 0, 1, 1, 1, 1, 1, 1, 1, 1, 2, 3, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 2, 4, 5, 6, 1, 4, 4, 7, 8, 8, 9, 4, 8, 4, 4, 10, 11, 11, 11, 11, 11, 11, 11, 11, 11, 11, 4, 4, 4, 4, 4, 4, 1, 12, 11, 13, 14, 15, 16, 17, 18, 19, 11, 11, 20, 11, 21, 22, 23, 11, 24, 25, 26, 27, 28, 29, 11, 30, 11, 4, 31, 4, 4, 32, 1, 33, 11, 11, 11, 34, 11, 11, 11, 35, 11, 11, 36, 37, 38, 11, 11, 11, 11, 39, 40, 11, 11, 11, 11, 11, 11, 4, 4, 4, 4, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1 } ; static const YY_CHAR yy_meta[41] = { 0, 1, 1, 2, 1, 3, 1, 3, 1, 4, 1, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 6, 5, 5, 5, 5, 5, 5, 5, 5, 5 } ; static const flex_int16_t yy_base[152] = { 0, 0, 35, 210, 209, 8, 10, 211, 210, 15, 17, 19, 25, 212, 217, 13, 19, 217, 217, 217, 217, 24, 0, 191, 195, 183, 217, 170, 217, 0, 217, 42, 0, 217, 0, 217, 44, 26, 0, 217, 204, 56, 217, 217, 0, 187, 189, 174, 164, 0, 51, 55, 217, 0, 0, 0, 0, 217, 217, 52, 0, 217, 217, 190, 44, 176, 160, 174, 168, 176, 178, 162, 162, 161, 160, 159, 154, 171, 56, 51, 52, 152, 168, 160, 166, 162, 164, 160, 159, 168, 147, 164, 151, 162, 150, 160, 148, 158, 147, 136, 155, 144, 153, 148, 151, 146, 148, 145, 125, 145, 0, 144, 0, 143, 0, 132, 141, 121, 133, 124, 112, 98, 95, 80, 0, 0, 0, 96, 57, 47, 54, 0, 41, 63, 35, 44, 0, 0, 217, 83, 89, 95, 101, 107, 18, 113, 119, 125, 131, 136, 142, 147 } ; static const flex_int16_t yy_def[152] = { 0, 139, 139, 2, 2, 140, 140, 141, 141, 142, 142, 143, 143, 138, 138, 138, 138, 138, 138, 138, 138, 138, 144, 144, 144, 144, 138, 144, 138, 145, 138, 146, 147, 138, 148, 138, 148, 149, 150, 138, 151, 138, 138, 138, 144, 144, 144, 144, 144, 145, 146, 146, 138, 147, 148, 148, 148, 138, 138, 149, 150, 138, 138, 144, 144, 144, 144, 144, 144, 144, 144, 144, 144, 144, 144, 144, 144, 144, 144, 144, 144, 144, 144, 144, 144, 144, 144, 144, 144, 144, 144, 144, 144, 144, 144, 144, 144, 144, 144, 144, 144, 144, 144, 144, 144, 144, 144, 144, 144, 144, 144, 144, 144, 144, 144, 144, 144, 144, 144, 144, 144, 144, 144, 144, 144, 144, 144, 144, 144, 144, 144, 144, 144, 144, 144, 144, 144, 144, 0, 138, 138, 138, 138, 138, 138, 138, 138, 138, 138, 138, 138, 138 } ; static const flex_int16_t yy_nxt[258] = { 0, 14, 15, 16, 17, 18, 14, 19, 20, 17, 21, 30, 23, 30, 24, 41, 41, 31, 35, 31, 35, 41, 41, 44, 39, 36, 39, 36, 25, 58, 39, 26, 39, 42, 43, 27, 14, 15, 16, 17, 18, 14, 19, 20, 17, 21, 37, 23, 37, 24, 40, 51, 52, 55, 56, 138, 40, 59, 41, 41, 138, 138, 137, 25, 51, 52, 26, 68, 83, 86, 27, 69, 87, 88, 84, 136, 135, 85, 89, 134, 133, 132, 131, 59, 22, 22, 22, 22, 22, 22, 29, 29, 29, 29, 29, 29, 32, 32, 32, 32, 32, 32, 34, 34, 34, 34, 34, 34, 38, 38, 38, 38, 38, 38, 49, 130, 49, 129, 49, 49, 50, 128, 50, 50, 50, 50, 53, 127, 53, 53, 53, 53, 54, 126, 54, 54, 54, 57, 57, 57, 57, 57, 57, 60, 60, 125, 60, 60, 61, 61, 61, 61, 61, 61, 124, 123, 122, 121, 120, 119, 118, 117, 116, 115, 114, 113, 112, 111, 110, 109, 108, 107, 106, 105, 104, 103, 102, 101, 100, 99, 98, 97, 96, 95, 94, 93, 92, 91, 90, 82, 81, 80, 79, 78, 77, 76, 75, 74, 73, 72, 71, 70, 67, 66, 65, 64, 63, 62, 48, 47, 46, 45, 138, 33, 33, 28, 28, 13, 138, 138, 138, 138, 138, 138, 138, 138, 138, 138, 138, 138, 138, 138, 138, 138, 138, 138, 138, 138, 138, 138, 138, 138, 138, 138, 138, 138, 138, 138, 138, 138, 138, 138, 138, 138, 138, 138, 138, 138 } ; static const flex_int16_t yy_chk[258] = { 0, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 5, 1, 6, 1, 15, 15, 5, 9, 6, 10, 16, 16, 144, 11, 9, 11, 10, 1, 37, 12, 1, 12, 21, 21, 1, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 9, 2, 10, 2, 11, 31, 31, 36, 36, 59, 12, 37, 41, 41, 50, 50, 135, 2, 51, 51, 2, 64, 78, 79, 2, 64, 79, 80, 78, 134, 133, 78, 80, 132, 130, 129, 128, 59, 139, 139, 139, 139, 139, 139, 140, 140, 140, 140, 140, 140, 141, 141, 141, 141, 141, 141, 142, 142, 142, 142, 142, 142, 143, 143, 143, 143, 143, 143, 145, 127, 145, 123, 145, 145, 146, 122, 146, 146, 146, 146, 147, 121, 147, 147, 147, 147, 148, 120, 148, 148, 148, 149, 149, 149, 149, 149, 149, 150, 150, 119, 150, 150, 151, 151, 151, 151, 151, 151, 118, 117, 116, 115, 113, 111, 109, 108, 107, 106, 105, 104, 103, 102, 101, 100, 99, 98, 97, 96, 95, 94, 93, 92, 91, 90, 89, 88, 87, 86, 85, 84, 83, 82, 81, 77, 76, 75, 74, 73, 72, 71, 70, 69, 68, 67, 66, 65, 63, 48, 47, 46, 45, 40, 27, 25, 24, 23, 13, 8, 7, 4, 3, 138, 138, 138, 138, 138, 138, 138, 138, 138, 138, 138, 138, 138, 138, 138, 138, 138, 138, 138, 138, 138, 138, 138, 138, 138, 138, 138, 138, 138, 138, 138, 138, 138, 138, 138, 138, 138, 138, 138, 138, 138 } ; /* Table of booleans, true if rule could match eol. */ static const flex_int32_t yy_rule_can_match_eol[39] = { 0, 1, 0, 0, 0, 1, 0, 0, 0, 1, 0, 1, 0, 1, 0, 0, 0, 1, 0, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 0, 0, 0, 0, 0, 0, }; static yy_state_type yy_last_accepting_state; static char *yy_last_accepting_cpos; extern int yy_flex_debug; int yy_flex_debug = 0; /* The intent behind this definition is that it'll catch * any uses of REJECT which flex missed. */ #define REJECT reject_used_but_not_detected #define yymore() yymore_used_but_not_detected #define YY_MORE_ADJ 0 #define YY_RESTORE_YY_MORE_OFFSET char *yytext; #line 1 "lib/defun_lex.l" /* * clippy (CLI preparator in python) C pseudo-lexer * Copyright (C) 2016-2017 David Lamparter for NetDEF, Inc. * * This program is free software; you can redistribute it and/or modify it * under the terms of the 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; see the file COPYING; if not, write to the Free Software * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA */ /* This is just enough of a lexer to make rough sense of a C source file. * It handles C preprocessor directives, strings, and looks for FRR-specific * idioms (aka DEFUN). * * There is some preliminary support for documentation comments for DEFUNs. * They would look like this (note the ~): (replace \ by /) * * \*~ documentation for foobar_cmd * * parameter does xyz * *\ * DEFUN(foobar_cmd, ...) * * This is intended for user documentation / command reference. Don't put * code documentation in it. */ #line 42 "lib/defun_lex.l" /* ignore harmless bugs in old versions of flex */ #pragma GCC diagnostic ignored "-Wsign-compare" #pragma GCC diagnostic ignored "-Wunused-value" #include "config.h" #include #include #include #include "command_graph.h" #include "clippy.h" #define ID 258 #define PREPROC 259 #define OPERATOR 260 #define STRING 261 #define COMMENT 262 #define SPECIAL 263 #define DEFUNNY 270 #define INSTALL 271 #define AUXILIARY 272 int comment_link; char string_end; char *value; static void extendbuf(char **what, const char *arg) { if (!*what) *what = strdup(arg); else { size_t vall = strlen(*what), argl = strlen(arg); *what = realloc(*what, vall + argl + 1); memcpy(*what + vall, arg, argl); (*what)[vall + argl] = '\0'; } } #define extend(x) extendbuf(&value, x) #line 911 "lib/defun_lex.c" #define YY_NO_INPUT 1 #line 914 "lib/defun_lex.c" #define INITIAL 0 #define linestart 1 #define comment 2 #define linecomment 3 #define preproc 4 #define rstring 5 #ifndef YY_NO_UNISTD_H /* Special case for "unistd.h", since it is non-ANSI. We include it way * down here because we want the user's section 1 to have been scanned first. * The user has a chance to override it with an option. */ #include #endif #ifndef YY_EXTRA_TYPE #define YY_EXTRA_TYPE void * #endif static int yy_init_globals ( void ); /* Accessor methods to globals. These are made visible to non-reentrant scanners for convenience. */ int yylex_destroy ( void ); int yyget_debug ( void ); void yyset_debug ( int debug_flag ); YY_EXTRA_TYPE yyget_extra ( void ); void yyset_extra ( YY_EXTRA_TYPE user_defined ); FILE *yyget_in ( void ); void yyset_in ( FILE * _in_str ); FILE *yyget_out ( void ); void yyset_out ( FILE * _out_str ); int yyget_leng ( void ); char *yyget_text ( void ); int yyget_lineno ( void ); void yyset_lineno ( int _line_number ); /* Macros after this point can all be overridden by user definitions in * section 1. */ #ifndef YY_SKIP_YYWRAP #ifdef __cplusplus extern "C" int yywrap ( void ); #else extern int yywrap ( void ); #endif #endif #ifndef YY_NO_UNPUT #endif #ifndef yytext_ptr static void yy_flex_strncpy ( char *, const char *, int ); #endif #ifdef YY_NEED_STRLEN static int yy_flex_strlen ( const char * ); #endif #ifndef YY_NO_INPUT #ifdef __cplusplus static int yyinput ( void ); #else static int input ( void ); #endif #endif /* Amount of stuff to slurp up with each read. */ #ifndef YY_READ_BUF_SIZE #ifdef __ia64__ /* On IA-64, the buffer size is 16k, not 8k */ #define YY_READ_BUF_SIZE 16384 #else #define YY_READ_BUF_SIZE 8192 #endif /* __ia64__ */ #endif /* Copy whatever the last rule matched to the standard output. */ #ifndef ECHO /* This used to be an fputs(), but since the string might contain NUL's, * we now use fwrite(). */ #define ECHO do { if (fwrite( yytext, (size_t) yyleng, 1, yyout )) {} } while (0) #endif /* Gets input and stuffs it into "buf". number of characters read, or YY_NULL, * is returned in "result". */ #ifndef YY_INPUT #define YY_INPUT(buf,result,max_size) \ if ( YY_CURRENT_BUFFER_LVALUE->yy_is_interactive ) \ { \ int c = '*'; \ int n; \ for ( n = 0; n < max_size && \ (c = getc( yyin )) != EOF && c != '\n'; ++n ) \ buf[n] = (char) c; \ if ( c == '\n' ) \ buf[n++] = (char) c; \ if ( c == EOF && ferror( yyin ) ) \ YY_FATAL_ERROR( "input in flex scanner failed" ); \ result = n; \ } \ else \ { \ errno=0; \ while ( (result = (int) fread(buf, 1, (yy_size_t) max_size, yyin)) == 0 && ferror(yyin)) \ { \ if( errno != EINTR) \ { \ YY_FATAL_ERROR( "input in flex scanner failed" ); \ break; \ } \ errno=0; \ clearerr(yyin); \ } \ }\ \ #endif /* No semi-colon after return; correct usage is to write "yyterminate();" - * we don't want an extra ';' after the "return" because that will cause * some compilers to complain about unreachable statements. */ #ifndef yyterminate #define yyterminate() return YY_NULL #endif /* Number of entries by which start-condition stack grows. */ #ifndef YY_START_STACK_INCR #define YY_START_STACK_INCR 25 #endif /* Report a fatal error. */ #ifndef YY_FATAL_ERROR #define YY_FATAL_ERROR(msg) yy_fatal_error( msg ) #endif /* end tables serialization structures and prototypes */ /* Default declaration of generated scanner - a define so the user can * easily add parameters. */ #ifndef YY_DECL #define YY_DECL_IS_OURS 1 extern int yylex (void); #define YY_DECL int yylex (void) #endif /* !YY_DECL */ /* Code executed at the beginning of each rule, after yytext and yyleng * have been set up. */ #ifndef YY_USER_ACTION #define YY_USER_ACTION #endif /* Code executed at the end of each rule. */ #ifndef YY_BREAK #define YY_BREAK /*LINTED*/break; #endif #define YY_RULE_SETUP \ YY_USER_ACTION /** The main scanner function which does all the work. */ YY_DECL { yy_state_type yy_current_state; char *yy_cp, *yy_bp; int yy_act; if ( !(yy_init) ) { (yy_init) = 1; #ifdef YY_USER_INIT YY_USER_INIT; #endif if ( ! (yy_start) ) (yy_start) = 1; /* first start state */ if ( ! yyin ) yyin = stdin; if ( ! yyout ) yyout = stdout; if ( ! YY_CURRENT_BUFFER ) { yyensure_buffer_stack (); YY_CURRENT_BUFFER_LVALUE = yy_create_buffer( yyin, YY_BUF_SIZE ); } yy_load_buffer_state( ); } { #line 103 "lib/defun_lex.l" #line 105 "lib/defun_lex.l" BEGIN(linestart); #line 1139 "lib/defun_lex.c" while ( /*CONSTCOND*/1 ) /* loops until end-of-file is reached */ { yy_cp = (yy_c_buf_p); /* Support of yytext. */ *yy_cp = (yy_hold_char); /* yy_bp points to the position in yy_ch_buf of the start of * the current run. */ yy_bp = yy_cp; yy_current_state = (yy_start); yy_match: do { YY_CHAR yy_c = yy_ec[YY_SC_TO_UI(*yy_cp)] ; if ( yy_accept[yy_current_state] ) { (yy_last_accepting_state) = yy_current_state; (yy_last_accepting_cpos) = yy_cp; } while ( yy_chk[yy_base[yy_current_state] + yy_c] != yy_current_state ) { yy_current_state = (int) yy_def[yy_current_state]; if ( yy_current_state >= 139 ) yy_c = yy_meta[yy_c]; } yy_current_state = yy_nxt[yy_base[yy_current_state] + yy_c]; ++yy_cp; } while ( yy_base[yy_current_state] != 217 ); yy_find_action: yy_act = yy_accept[yy_current_state]; if ( yy_act == 0 ) { /* have to back up */ yy_cp = (yy_last_accepting_cpos); yy_current_state = (yy_last_accepting_state); yy_act = yy_accept[yy_current_state]; } YY_DO_BEFORE_ACTION; if ( yy_act != YY_END_OF_BUFFER && yy_rule_can_match_eol[yy_act] ) { int yyl; for ( yyl = 0; yyl < yyleng; ++yyl ) if ( yytext[yyl] == '\n' ) yylineno++; ; } do_action: /* This label is used only to access EOF actions. */ switch ( yy_act ) { /* beginning of action switch */ case 0: /* must back up */ /* undo the effects of YY_DO_BEFORE_ACTION */ *yy_cp = (yy_hold_char); yy_cp = (yy_last_accepting_cpos); yy_current_state = (yy_last_accepting_state); goto yy_find_action; case 1: /* rule 1 can match eol */ YY_RULE_SETUP #line 107 "lib/defun_lex.l" BEGIN(linestart); YY_BREAK case 2: YY_RULE_SETUP #line 109 "lib/defun_lex.l" comment_link = YY_START; extend(yytext); BEGIN(comment); YY_BREAK case 3: YY_RULE_SETUP #line 110 "lib/defun_lex.l" extend(yytext); YY_BREAK case 4: YY_RULE_SETUP #line 111 "lib/defun_lex.l" extend(yytext); YY_BREAK case 5: /* rule 5 can match eol */ YY_RULE_SETUP #line 112 "lib/defun_lex.l" extend(yytext); YY_BREAK case 6: YY_RULE_SETUP #line 113 "lib/defun_lex.l" extend(yytext); BEGIN(comment_link); return COMMENT; YY_BREAK case 7: YY_RULE_SETUP #line 115 "lib/defun_lex.l" comment_link = YY_START; extend(yytext); BEGIN(linecomment); YY_BREAK case 8: YY_RULE_SETUP #line 116 "lib/defun_lex.l" extend(yytext); YY_BREAK case 9: /* rule 9 can match eol */ YY_RULE_SETUP #line 117 "lib/defun_lex.l" BEGIN((comment_link == INITIAL) ? linestart : comment_link); return COMMENT; YY_BREAK case 10: YY_RULE_SETUP #line 119 "lib/defun_lex.l" BEGIN(preproc); YY_BREAK case 11: /* rule 11 can match eol */ YY_RULE_SETUP #line 120 "lib/defun_lex.l" BEGIN(INITIAL); return PREPROC; YY_BREAK case 12: YY_RULE_SETUP #line 121 "lib/defun_lex.l" extend(yytext); YY_BREAK case 13: /* rule 13 can match eol */ YY_RULE_SETUP #line 122 "lib/defun_lex.l" extend(yytext); YY_BREAK case 14: YY_RULE_SETUP #line 123 "lib/defun_lex.l" extend(yytext); YY_BREAK case 15: YY_RULE_SETUP #line 125 "lib/defun_lex.l" string_end = yytext[0]; extend(yytext); BEGIN(rstring); YY_BREAK case 16: YY_RULE_SETUP #line 126 "lib/defun_lex.l" { extend(yytext); if (yytext[0] == string_end) { BEGIN(INITIAL); return STRING; } } YY_BREAK case 17: /* rule 17 can match eol */ YY_RULE_SETUP #line 133 "lib/defun_lex.l" /* ignore */ YY_BREAK case 18: YY_RULE_SETUP #line 134 "lib/defun_lex.l" extend(yytext); YY_BREAK case 19: /* rule 19 can match eol */ YY_RULE_SETUP #line 135 "lib/defun_lex.l" extend(yytext); YY_BREAK case 20: YY_RULE_SETUP #line 137 "lib/defun_lex.l" value = strdup(yytext); return DEFUNNY; YY_BREAK case 21: YY_RULE_SETUP #line 138 "lib/defun_lex.l" value = strdup(yytext); return DEFUNNY; YY_BREAK case 22: YY_RULE_SETUP #line 139 "lib/defun_lex.l" value = strdup(yytext); return DEFUNNY; YY_BREAK case 23: YY_RULE_SETUP #line 140 "lib/defun_lex.l" value = strdup(yytext); return DEFUNNY; YY_BREAK case 24: YY_RULE_SETUP #line 141 "lib/defun_lex.l" value = strdup(yytext); return DEFUNNY; YY_BREAK case 25: YY_RULE_SETUP #line 142 "lib/defun_lex.l" value = strdup(yytext); return DEFUNNY; YY_BREAK case 26: YY_RULE_SETUP #line 143 "lib/defun_lex.l" value = strdup(yytext); return DEFUNNY; YY_BREAK case 27: YY_RULE_SETUP #line 144 "lib/defun_lex.l" value = strdup(yytext); return DEFUNNY; YY_BREAK case 28: YY_RULE_SETUP #line 145 "lib/defun_lex.l" value = strdup(yytext); return DEFUNNY; YY_BREAK case 29: YY_RULE_SETUP #line 146 "lib/defun_lex.l" value = strdup(yytext); return INSTALL; YY_BREAK case 30: YY_RULE_SETUP #line 147 "lib/defun_lex.l" value = strdup(yytext); return AUXILIARY; YY_BREAK case 31: YY_RULE_SETUP #line 148 "lib/defun_lex.l" value = strdup(yytext); return AUXILIARY; YY_BREAK case 32: /* rule 32 can match eol */ YY_RULE_SETUP #line 150 "lib/defun_lex.l" /* ignore */ YY_BREAK case 33: YY_RULE_SETUP #line 151 "lib/defun_lex.l" /* ignore */ YY_BREAK case 34: YY_RULE_SETUP #line 152 "lib/defun_lex.l" BEGIN(INITIAL); value = strdup(yytext); return ID; YY_BREAK case 35: YY_RULE_SETUP #line 153 "lib/defun_lex.l" BEGIN(INITIAL); value = strdup(yytext); return OPERATOR; YY_BREAK case 36: YY_RULE_SETUP #line 154 "lib/defun_lex.l" BEGIN(INITIAL); value = strdup(yytext); return SPECIAL; YY_BREAK case 37: YY_RULE_SETUP #line 155 "lib/defun_lex.l" /* printf("-- '%s' in init\n", yytext); */ BEGIN(INITIAL); return yytext[0]; YY_BREAK case 38: YY_RULE_SETUP #line 157 "lib/defun_lex.l" ECHO; YY_BREAK #line 1410 "lib/defun_lex.c" case YY_STATE_EOF(INITIAL): case YY_STATE_EOF(linestart): case YY_STATE_EOF(comment): case YY_STATE_EOF(linecomment): case YY_STATE_EOF(preproc): case YY_STATE_EOF(rstring): yyterminate(); case YY_END_OF_BUFFER: { /* Amount of text matched not including the EOB char. */ int yy_amount_of_matched_text = (int) (yy_cp - (yytext_ptr)) - 1; /* Undo the effects of YY_DO_BEFORE_ACTION. */ *yy_cp = (yy_hold_char); YY_RESTORE_YY_MORE_OFFSET if ( YY_CURRENT_BUFFER_LVALUE->yy_buffer_status == YY_BUFFER_NEW ) { /* We're scanning a new file or input source. It's * possible that this happened because the user * just pointed yyin at a new source and called * yylex(). If so, then we have to assure * consistency between YY_CURRENT_BUFFER and our * globals. Here is the right place to do so, because * this is the first action (other than possibly a * back-up) that will match for the new input source. */ (yy_n_chars) = YY_CURRENT_BUFFER_LVALUE->yy_n_chars; YY_CURRENT_BUFFER_LVALUE->yy_input_file = yyin; YY_CURRENT_BUFFER_LVALUE->yy_buffer_status = YY_BUFFER_NORMAL; } /* Note that here we test for yy_c_buf_p "<=" to the position * of the first EOB in the buffer, since yy_c_buf_p will * already have been incremented past the NUL character * (since all states make transitions on EOB to the * end-of-buffer state). Contrast this with the test * in input(). */ if ( (yy_c_buf_p) <= &YY_CURRENT_BUFFER_LVALUE->yy_ch_buf[(yy_n_chars)] ) { /* This was really a NUL. */ yy_state_type yy_next_state; (yy_c_buf_p) = (yytext_ptr) + yy_amount_of_matched_text; yy_current_state = yy_get_previous_state( ); /* Okay, we're now positioned to make the NUL * transition. We couldn't have * yy_get_previous_state() go ahead and do it * for us because it doesn't know how to deal * with the possibility of jamming (and we don't * want to build jamming into it because then it * will run more slowly). */ yy_next_state = yy_try_NUL_trans( yy_current_state ); yy_bp = (yytext_ptr) + YY_MORE_ADJ; if ( yy_next_state ) { /* Consume the NUL. */ yy_cp = ++(yy_c_buf_p); yy_current_state = yy_next_state; goto yy_match; } else { yy_cp = (yy_c_buf_p); goto yy_find_action; } } else switch ( yy_get_next_buffer( ) ) { case EOB_ACT_END_OF_FILE: { (yy_did_buffer_switch_on_eof) = 0; if ( yywrap( ) ) { /* Note: because we've taken care in * yy_get_next_buffer() to have set up * yytext, we can now set up * yy_c_buf_p so that if some total * hoser (like flex itself) wants to * call the scanner after we return the * YY_NULL, it'll still work - another * YY_NULL will get returned. */ (yy_c_buf_p) = (yytext_ptr) + YY_MORE_ADJ; yy_act = YY_STATE_EOF(YY_START); goto do_action; } else { if ( ! (yy_did_buffer_switch_on_eof) ) YY_NEW_FILE; } break; } case EOB_ACT_CONTINUE_SCAN: (yy_c_buf_p) = (yytext_ptr) + yy_amount_of_matched_text; yy_current_state = yy_get_previous_state( ); yy_cp = (yy_c_buf_p); yy_bp = (yytext_ptr) + YY_MORE_ADJ; goto yy_match; case EOB_ACT_LAST_MATCH: (yy_c_buf_p) = &YY_CURRENT_BUFFER_LVALUE->yy_ch_buf[(yy_n_chars)]; yy_current_state = yy_get_previous_state( ); yy_cp = (yy_c_buf_p); yy_bp = (yytext_ptr) + YY_MORE_ADJ; goto yy_find_action; } break; } default: YY_FATAL_ERROR( "fatal flex scanner internal error--no action found" ); } /* end of action switch */ } /* end of scanning one token */ } /* end of user's declarations */ } /* end of yylex */ /* yy_get_next_buffer - try to read in a new buffer * * Returns a code representing an action: * EOB_ACT_LAST_MATCH - * EOB_ACT_CONTINUE_SCAN - continue scanning from current position * EOB_ACT_END_OF_FILE - end of file */ static int yy_get_next_buffer (void) { char *dest = YY_CURRENT_BUFFER_LVALUE->yy_ch_buf; char *source = (yytext_ptr); int number_to_move, i; int ret_val; if ( (yy_c_buf_p) > &YY_CURRENT_BUFFER_LVALUE->yy_ch_buf[(yy_n_chars) + 1] ) YY_FATAL_ERROR( "fatal flex scanner internal error--end of buffer missed" ); if ( YY_CURRENT_BUFFER_LVALUE->yy_fill_buffer == 0 ) { /* Don't try to fill the buffer, so this is an EOF. */ if ( (yy_c_buf_p) - (yytext_ptr) - YY_MORE_ADJ == 1 ) { /* We matched a single character, the EOB, so * treat this as a final EOF. */ return EOB_ACT_END_OF_FILE; } else { /* We matched some text prior to the EOB, first * process it. */ return EOB_ACT_LAST_MATCH; } } /* Try to read more data. */ /* First move last chars to start of buffer. */ number_to_move = (int) ((yy_c_buf_p) - (yytext_ptr) - 1); for ( i = 0; i < number_to_move; ++i ) *(dest++) = *(source++); if ( YY_CURRENT_BUFFER_LVALUE->yy_buffer_status == YY_BUFFER_EOF_PENDING ) /* don't do the read, it's not guaranteed to return an EOF, * just force an EOF */ YY_CURRENT_BUFFER_LVALUE->yy_n_chars = (yy_n_chars) = 0; else { int num_to_read = YY_CURRENT_BUFFER_LVALUE->yy_buf_size - number_to_move - 1; while ( num_to_read <= 0 ) { /* Not enough room in the buffer - grow it. */ /* just a shorter name for the current buffer */ YY_BUFFER_STATE b = YY_CURRENT_BUFFER_LVALUE; int yy_c_buf_p_offset = (int) ((yy_c_buf_p) - b->yy_ch_buf); if ( b->yy_is_our_buffer ) { int new_size = b->yy_buf_size * 2; if ( new_size <= 0 ) b->yy_buf_size += b->yy_buf_size / 8; else b->yy_buf_size *= 2; b->yy_ch_buf = (char *) /* Include room in for 2 EOB chars. */ yyrealloc( (void *) b->yy_ch_buf, (yy_size_t) (b->yy_buf_size + 2) ); } else /* Can't grow it, we don't own it. */ b->yy_ch_buf = NULL; if ( ! b->yy_ch_buf ) YY_FATAL_ERROR( "fatal error - scanner input buffer overflow" ); (yy_c_buf_p) = &b->yy_ch_buf[yy_c_buf_p_offset]; num_to_read = YY_CURRENT_BUFFER_LVALUE->yy_buf_size - number_to_move - 1; } if ( num_to_read > YY_READ_BUF_SIZE ) num_to_read = YY_READ_BUF_SIZE; /* Read in more data. */ YY_INPUT( (&YY_CURRENT_BUFFER_LVALUE->yy_ch_buf[number_to_move]), (yy_n_chars), num_to_read ); YY_CURRENT_BUFFER_LVALUE->yy_n_chars = (yy_n_chars); } if ( (yy_n_chars) == 0 ) { if ( number_to_move == YY_MORE_ADJ ) { ret_val = EOB_ACT_END_OF_FILE; yyrestart( yyin ); } else { ret_val = EOB_ACT_LAST_MATCH; YY_CURRENT_BUFFER_LVALUE->yy_buffer_status = YY_BUFFER_EOF_PENDING; } } else ret_val = EOB_ACT_CONTINUE_SCAN; if (((yy_n_chars) + number_to_move) > YY_CURRENT_BUFFER_LVALUE->yy_buf_size) { /* Extend the array by 50%, plus the number we really need. */ int new_size = (yy_n_chars) + number_to_move + ((yy_n_chars) >> 1); YY_CURRENT_BUFFER_LVALUE->yy_ch_buf = (char *) yyrealloc( (void *) YY_CURRENT_BUFFER_LVALUE->yy_ch_buf, (yy_size_t) new_size ); if ( ! YY_CURRENT_BUFFER_LVALUE->yy_ch_buf ) YY_FATAL_ERROR( "out of dynamic memory in yy_get_next_buffer()" ); /* "- 2" to take care of EOB's */ YY_CURRENT_BUFFER_LVALUE->yy_buf_size = (int) (new_size - 2); } (yy_n_chars) += number_to_move; YY_CURRENT_BUFFER_LVALUE->yy_ch_buf[(yy_n_chars)] = YY_END_OF_BUFFER_CHAR; YY_CURRENT_BUFFER_LVALUE->yy_ch_buf[(yy_n_chars) + 1] = YY_END_OF_BUFFER_CHAR; (yytext_ptr) = &YY_CURRENT_BUFFER_LVALUE->yy_ch_buf[0]; return ret_val; } /* yy_get_previous_state - get the state just before the EOB char was reached */ static yy_state_type yy_get_previous_state (void) { yy_state_type yy_current_state; char *yy_cp; yy_current_state = (yy_start); for ( yy_cp = (yytext_ptr) + YY_MORE_ADJ; yy_cp < (yy_c_buf_p); ++yy_cp ) { YY_CHAR yy_c = (*yy_cp ? yy_ec[YY_SC_TO_UI(*yy_cp)] : 1); if ( yy_accept[yy_current_state] ) { (yy_last_accepting_state) = yy_current_state; (yy_last_accepting_cpos) = yy_cp; } while ( yy_chk[yy_base[yy_current_state] + yy_c] != yy_current_state ) { yy_current_state = (int) yy_def[yy_current_state]; if ( yy_current_state >= 139 ) yy_c = yy_meta[yy_c]; } yy_current_state = yy_nxt[yy_base[yy_current_state] + yy_c]; } return yy_current_state; } /* yy_try_NUL_trans - try to make a transition on the NUL character * * synopsis * next_state = yy_try_NUL_trans( current_state ); */ static yy_state_type yy_try_NUL_trans (yy_state_type yy_current_state ) { int yy_is_jam; char *yy_cp = (yy_c_buf_p); YY_CHAR yy_c = 1; if ( yy_accept[yy_current_state] ) { (yy_last_accepting_state) = yy_current_state; (yy_last_accepting_cpos) = yy_cp; } while ( yy_chk[yy_base[yy_current_state] + yy_c] != yy_current_state ) { yy_current_state = (int) yy_def[yy_current_state]; if ( yy_current_state >= 139 ) yy_c = yy_meta[yy_c]; } yy_current_state = yy_nxt[yy_base[yy_current_state] + yy_c]; yy_is_jam = (yy_current_state == 138); return yy_is_jam ? 0 : yy_current_state; } #ifndef YY_NO_UNPUT #endif #ifndef YY_NO_INPUT #ifdef __cplusplus static int yyinput (void) #else static int input (void) #endif { int c; *(yy_c_buf_p) = (yy_hold_char); if ( *(yy_c_buf_p) == YY_END_OF_BUFFER_CHAR ) { /* yy_c_buf_p now points to the character we want to return. * If this occurs *before* the EOB characters, then it's a * valid NUL; if not, then we've hit the end of the buffer. */ if ( (yy_c_buf_p) < &YY_CURRENT_BUFFER_LVALUE->yy_ch_buf[(yy_n_chars)] ) /* This was really a NUL. */ *(yy_c_buf_p) = '\0'; else { /* need more input */ int offset = (int) ((yy_c_buf_p) - (yytext_ptr)); ++(yy_c_buf_p); switch ( yy_get_next_buffer( ) ) { case EOB_ACT_LAST_MATCH: /* This happens because yy_g_n_b() * sees that we've accumulated a * token and flags that we need to * try matching the token before * proceeding. But for input(), * there's no matching to consider. * So convert the EOB_ACT_LAST_MATCH * to EOB_ACT_END_OF_FILE. */ /* Reset buffer status. */ yyrestart( yyin ); /*FALLTHROUGH*/ case EOB_ACT_END_OF_FILE: { if ( yywrap( ) ) return 0; if ( ! (yy_did_buffer_switch_on_eof) ) YY_NEW_FILE; #ifdef __cplusplus return yyinput(); #else return input(); #endif } case EOB_ACT_CONTINUE_SCAN: (yy_c_buf_p) = (yytext_ptr) + offset; break; } } } c = *(unsigned char *) (yy_c_buf_p); /* cast for 8-bit char's */ *(yy_c_buf_p) = '\0'; /* preserve yytext */ (yy_hold_char) = *++(yy_c_buf_p); if ( c == '\n' ) yylineno++; ; return c; } #endif /* ifndef YY_NO_INPUT */ /** Immediately switch to a different input stream. * @param input_file A readable stream. * * @note This function does not reset the start condition to @c INITIAL . */ void yyrestart (FILE * input_file ) { if ( ! YY_CURRENT_BUFFER ){ yyensure_buffer_stack (); YY_CURRENT_BUFFER_LVALUE = yy_create_buffer( yyin, YY_BUF_SIZE ); } yy_init_buffer( YY_CURRENT_BUFFER, input_file ); yy_load_buffer_state( ); } /** Switch to a different input buffer. * @param new_buffer The new input buffer. * */ void yy_switch_to_buffer (YY_BUFFER_STATE new_buffer ) { /* TODO. We should be able to replace this entire function body * with * yypop_buffer_state(); * yypush_buffer_state(new_buffer); */ yyensure_buffer_stack (); if ( YY_CURRENT_BUFFER == new_buffer ) return; if ( YY_CURRENT_BUFFER ) { /* Flush out information for old buffer. */ *(yy_c_buf_p) = (yy_hold_char); YY_CURRENT_BUFFER_LVALUE->yy_buf_pos = (yy_c_buf_p); YY_CURRENT_BUFFER_LVALUE->yy_n_chars = (yy_n_chars); } YY_CURRENT_BUFFER_LVALUE = new_buffer; yy_load_buffer_state( ); /* We don't actually know whether we did this switch during * EOF (yywrap()) processing, but the only time this flag * is looked at is after yywrap() is called, so it's safe * to go ahead and always set it. */ (yy_did_buffer_switch_on_eof) = 1; } static void yy_load_buffer_state (void) { (yy_n_chars) = YY_CURRENT_BUFFER_LVALUE->yy_n_chars; (yytext_ptr) = (yy_c_buf_p) = YY_CURRENT_BUFFER_LVALUE->yy_buf_pos; yyin = YY_CURRENT_BUFFER_LVALUE->yy_input_file; (yy_hold_char) = *(yy_c_buf_p); } /** Allocate and initialize an input buffer state. * @param file A readable stream. * @param size The character buffer size in bytes. When in doubt, use @c YY_BUF_SIZE. * * @return the allocated buffer state. */ YY_BUFFER_STATE yy_create_buffer (FILE * file, int size ) { YY_BUFFER_STATE b; b = (YY_BUFFER_STATE) yyalloc( sizeof( struct yy_buffer_state ) ); if ( ! b ) YY_FATAL_ERROR( "out of dynamic memory in yy_create_buffer()" ); b->yy_buf_size = size; /* yy_ch_buf has to be 2 characters longer than the size given because * we need to put in 2 end-of-buffer characters. */ b->yy_ch_buf = (char *) yyalloc( (yy_size_t) (b->yy_buf_size + 2) ); if ( ! b->yy_ch_buf ) YY_FATAL_ERROR( "out of dynamic memory in yy_create_buffer()" ); b->yy_is_our_buffer = 1; yy_init_buffer( b, file ); return b; } /** Destroy the buffer. * @param b a buffer created with yy_create_buffer() * */ void yy_delete_buffer (YY_BUFFER_STATE b ) { if ( ! b ) return; if ( b == YY_CURRENT_BUFFER ) /* Not sure if we should pop here. */ YY_CURRENT_BUFFER_LVALUE = (YY_BUFFER_STATE) 0; if ( b->yy_is_our_buffer ) yyfree( (void *) b->yy_ch_buf ); yyfree( (void *) b ); } /* Initializes or reinitializes a buffer. * This function is sometimes called more than once on the same buffer, * such as during a yyrestart() or at EOF. */ static void yy_init_buffer (YY_BUFFER_STATE b, FILE * file ) { int oerrno = errno; yy_flush_buffer( b ); b->yy_input_file = file; b->yy_fill_buffer = 1; /* If b is the current buffer, then yy_init_buffer was _probably_ * called from yyrestart() or through yy_get_next_buffer. * In that case, we don't want to reset the lineno or column. */ if (b != YY_CURRENT_BUFFER){ b->yy_bs_lineno = 1; b->yy_bs_column = 0; } b->yy_is_interactive = file ? (isatty( fileno(file) ) > 0) : 0; errno = oerrno; } /** Discard all buffered characters. On the next scan, YY_INPUT will be called. * @param b the buffer state to be flushed, usually @c YY_CURRENT_BUFFER. * */ void yy_flush_buffer (YY_BUFFER_STATE b ) { if ( ! b ) return; b->yy_n_chars = 0; /* We always need two end-of-buffer characters. The first causes * a transition to the end-of-buffer state. The second causes * a jam in that state. */ b->yy_ch_buf[0] = YY_END_OF_BUFFER_CHAR; b->yy_ch_buf[1] = YY_END_OF_BUFFER_CHAR; b->yy_buf_pos = &b->yy_ch_buf[0]; b->yy_at_bol = 1; b->yy_buffer_status = YY_BUFFER_NEW; if ( b == YY_CURRENT_BUFFER ) yy_load_buffer_state( ); } /** Pushes the new state onto the stack. The new state becomes * the current state. This function will allocate the stack * if necessary. * @param new_buffer The new state. * */ void yypush_buffer_state (YY_BUFFER_STATE new_buffer ) { if (new_buffer == NULL) return; yyensure_buffer_stack(); /* This block is copied from yy_switch_to_buffer. */ if ( YY_CURRENT_BUFFER ) { /* Flush out information for old buffer. */ *(yy_c_buf_p) = (yy_hold_char); YY_CURRENT_BUFFER_LVALUE->yy_buf_pos = (yy_c_buf_p); YY_CURRENT_BUFFER_LVALUE->yy_n_chars = (yy_n_chars); } /* Only push if top exists. Otherwise, replace top. */ if (YY_CURRENT_BUFFER) (yy_buffer_stack_top)++; YY_CURRENT_BUFFER_LVALUE = new_buffer; /* copied from yy_switch_to_buffer. */ yy_load_buffer_state( ); (yy_did_buffer_switch_on_eof) = 1; } /** Removes and deletes the top of the stack, if present. * The next element becomes the new top. * */ void yypop_buffer_state (void) { if (!YY_CURRENT_BUFFER) return; yy_delete_buffer(YY_CURRENT_BUFFER ); YY_CURRENT_BUFFER_LVALUE = NULL; if ((yy_buffer_stack_top) > 0) --(yy_buffer_stack_top); if (YY_CURRENT_BUFFER) { yy_load_buffer_state( ); (yy_did_buffer_switch_on_eof) = 1; } } /* Allocates the stack if it does not exist. * Guarantees space for at least one push. */ static void yyensure_buffer_stack (void) { yy_size_t num_to_alloc; if (!(yy_buffer_stack)) { /* First allocation is just for 2 elements, since we don't know if this * scanner will even need a stack. We use 2 instead of 1 to avoid an * immediate realloc on the next call. */ num_to_alloc = 1; /* After all that talk, this was set to 1 anyways... */ (yy_buffer_stack) = (struct yy_buffer_state**)yyalloc (num_to_alloc * sizeof(struct yy_buffer_state*) ); if ( ! (yy_buffer_stack) ) YY_FATAL_ERROR( "out of dynamic memory in yyensure_buffer_stack()" ); memset((yy_buffer_stack), 0, num_to_alloc * sizeof(struct yy_buffer_state*)); (yy_buffer_stack_max) = num_to_alloc; (yy_buffer_stack_top) = 0; return; } if ((yy_buffer_stack_top) >= ((yy_buffer_stack_max)) - 1){ /* Increase the buffer to prepare for a possible push. */ yy_size_t grow_size = 8 /* arbitrary grow size */; num_to_alloc = (yy_buffer_stack_max) + grow_size; (yy_buffer_stack) = (struct yy_buffer_state**)yyrealloc ((yy_buffer_stack), num_to_alloc * sizeof(struct yy_buffer_state*) ); if ( ! (yy_buffer_stack) ) YY_FATAL_ERROR( "out of dynamic memory in yyensure_buffer_stack()" ); /* zero only the new slots.*/ memset((yy_buffer_stack) + (yy_buffer_stack_max), 0, grow_size * sizeof(struct yy_buffer_state*)); (yy_buffer_stack_max) = num_to_alloc; } } /** Setup the input buffer state to scan directly from a user-specified character buffer. * @param base the character buffer * @param size the size in bytes of the character buffer * * @return the newly allocated buffer state object. */ YY_BUFFER_STATE yy_scan_buffer (char * base, yy_size_t size ) { YY_BUFFER_STATE b; if ( size < 2 || base[size-2] != YY_END_OF_BUFFER_CHAR || base[size-1] != YY_END_OF_BUFFER_CHAR ) /* They forgot to leave room for the EOB's. */ return NULL; b = (YY_BUFFER_STATE) yyalloc( sizeof( struct yy_buffer_state ) ); if ( ! b ) YY_FATAL_ERROR( "out of dynamic memory in yy_scan_buffer()" ); b->yy_buf_size = (int) (size - 2); /* "- 2" to take care of EOB's */ b->yy_buf_pos = b->yy_ch_buf = base; b->yy_is_our_buffer = 0; b->yy_input_file = NULL; b->yy_n_chars = b->yy_buf_size; b->yy_is_interactive = 0; b->yy_at_bol = 1; b->yy_fill_buffer = 0; b->yy_buffer_status = YY_BUFFER_NEW; yy_switch_to_buffer( b ); return b; } /** Setup the input buffer state to scan a string. The next call to yylex() will * scan from a @e copy of @a str. * @param yystr a NUL-terminated string to scan * * @return the newly allocated buffer state object. * @note If you want to scan bytes that may contain NUL values, then use * yy_scan_bytes() instead. */ YY_BUFFER_STATE yy_scan_string (const char * yystr ) { return yy_scan_bytes( yystr, (int) strlen(yystr) ); } /** Setup the input buffer state to scan the given bytes. The next call to yylex() will * scan from a @e copy of @a bytes. * @param yybytes the byte buffer to scan * @param _yybytes_len the number of bytes in the buffer pointed to by @a bytes. * * @return the newly allocated buffer state object. */ YY_BUFFER_STATE yy_scan_bytes (const char * yybytes, int _yybytes_len ) { YY_BUFFER_STATE b; char *buf; yy_size_t n; int i; /* Get memory for full buffer, including space for trailing EOB's. */ n = (yy_size_t) (_yybytes_len + 2); buf = (char *) yyalloc( n ); if ( ! buf ) YY_FATAL_ERROR( "out of dynamic memory in yy_scan_bytes()" ); for ( i = 0; i < _yybytes_len; ++i ) buf[i] = yybytes[i]; buf[_yybytes_len] = buf[_yybytes_len+1] = YY_END_OF_BUFFER_CHAR; b = yy_scan_buffer( buf, n ); if ( ! b ) YY_FATAL_ERROR( "bad buffer in yy_scan_bytes()" ); /* It's okay to grow etc. this buffer, and we should throw it * away when we're done. */ b->yy_is_our_buffer = 1; return b; } #ifndef YY_EXIT_FAILURE #define YY_EXIT_FAILURE 2 #endif static void yynoreturn yy_fatal_error (const char* msg ) { fprintf( stderr, "%s\n", msg ); exit( YY_EXIT_FAILURE ); } /* Redefine yyless() so it works in section 3 code. */ #undef yyless #define yyless(n) \ do \ { \ /* Undo effects of setting up yytext. */ \ int yyless_macro_arg = (n); \ YY_LESS_LINENO(yyless_macro_arg);\ yytext[yyleng] = (yy_hold_char); \ (yy_c_buf_p) = yytext + yyless_macro_arg; \ (yy_hold_char) = *(yy_c_buf_p); \ *(yy_c_buf_p) = '\0'; \ yyleng = yyless_macro_arg; \ } \ while ( 0 ) /* Accessor methods (get/set functions) to struct members. */ /** Get the current line number. * */ int yyget_lineno (void) { return yylineno; } /** Get the input stream. * */ FILE *yyget_in (void) { return yyin; } /** Get the output stream. * */ FILE *yyget_out (void) { return yyout; } /** Get the length of the current token. * */ int yyget_leng (void) { return yyleng; } /** Get the current token. * */ char *yyget_text (void) { return yytext; } /** Set the current line number. * @param _line_number line number * */ void yyset_lineno (int _line_number ) { yylineno = _line_number; } /** Set the input stream. This does not discard the current * input buffer. * @param _in_str A readable stream. * * @see yy_switch_to_buffer */ void yyset_in (FILE * _in_str ) { yyin = _in_str ; } void yyset_out (FILE * _out_str ) { yyout = _out_str ; } int yyget_debug (void) { return yy_flex_debug; } void yyset_debug (int _bdebug ) { yy_flex_debug = _bdebug ; } static int yy_init_globals (void) { /* Initialization is the same as for the non-reentrant scanner. * This function is called from yylex_destroy(), so don't allocate here. */ /* We do not touch yylineno unless the option is enabled. */ yylineno = 1; (yy_buffer_stack) = NULL; (yy_buffer_stack_top) = 0; (yy_buffer_stack_max) = 0; (yy_c_buf_p) = NULL; (yy_init) = 0; (yy_start) = 0; /* Defined in main.c */ #ifdef YY_STDINIT yyin = stdin; yyout = stdout; #else yyin = NULL; yyout = NULL; #endif /* For future reference: Set errno on error, since we are called by * yylex_init() */ return 0; } /* yylex_destroy is for both reentrant and non-reentrant scanners. */ int yylex_destroy (void) { /* Pop the buffer stack, destroying each element. */ while(YY_CURRENT_BUFFER){ yy_delete_buffer( YY_CURRENT_BUFFER ); YY_CURRENT_BUFFER_LVALUE = NULL; yypop_buffer_state(); } /* Destroy the stack itself. */ yyfree((yy_buffer_stack) ); (yy_buffer_stack) = NULL; /* Reset the globals. This is important in a non-reentrant scanner so the next time * yylex() is called, initialization will occur. */ yy_init_globals( ); return 0; } /* * Internal utility routines. */ #ifndef yytext_ptr static void yy_flex_strncpy (char* s1, const char * s2, int n ) { int i; for ( i = 0; i < n; ++i ) s1[i] = s2[i]; } #endif #ifdef YY_NEED_STRLEN static int yy_flex_strlen (const char * s ) { int n; for ( n = 0; s[n]; ++n ) ; return n; } #endif void *yyalloc (yy_size_t size ) { return malloc(size); } void *yyrealloc (void * ptr, yy_size_t size ) { /* The cast to (char *) in the following accommodates both * implementations that use char* generic pointers, and those * that use void* generic pointers. It works with the latter * because both ANSI C and C++ allow castless assignment from * any pointer type to void*, and deal with argument conversions * as though doing an assignment. */ return realloc(ptr, size); } void yyfree (void * ptr ) { free( (char *) ptr ); /* see yyrealloc() for (char *) cast */ } #define YYTABLES_NAME "yytables" #line 157 "lib/defun_lex.l" static int yylex_clr(char **retbuf) { int rv = def_yylex(); *retbuf = value; value = NULL; return rv; } static PyObject *get_args(const char *filename, int lineno) { PyObject *pyObj = PyList_New(0); PyObject *pyArg = NULL; char *tval; int depth = 1; int token; while ((token = yylex_clr(&tval)) != YY_NULL) { if (token == SPECIAL && tval[0] == '(') { free(tval); break; } if (token == COMMENT) { free(tval); continue; } fprintf(stderr, "invalid input!\n"); exit(1); } while ((token = yylex_clr(&tval)) != YY_NULL) { if (token == COMMENT) { free(tval); continue; } if (token == PREPROC) { free(tval); Py_DECREF(pyObj); return PyErr_Format(PyExc_ValueError, "%s:%d: cannot process CPP directive within argument list", filename, lineno); } if (token == SPECIAL) { if (depth == 1 && (tval[0] == ',' || tval[0] == ')')) { if (pyArg) PyList_Append(pyObj, pyArg); pyArg = NULL; if (tval[0] == ')') { free(tval); break; } free(tval); continue; } if (tval[0] == '(') depth++; if (tval[0] == ')') depth--; } if (!pyArg) pyArg = PyList_New(0); PyList_Append(pyArg, PyUnicode_FromString(tval)); free(tval); } return pyObj; } /* _clippy.parse() -- read a C file, returning a list of interesting bits. * note this ditches most of the actual C code. */ PyObject *clippy_parse(PyObject *self, PyObject *args) { const char *filename; if (!PyArg_ParseTuple(args, "s", &filename)) return NULL; FILE *fd = fopen(filename, "r"); if (!fd) return PyErr_SetFromErrnoWithFilename(PyExc_IOError, filename); char *tval; int token; yyin = fd; value = NULL; PyObject *pyCont = PyDict_New(); PyObject *pyObj = PyList_New(0); PyDict_SetItemString(pyCont, "filename", PyUnicode_FromString(filename)); PyDict_SetItemString(pyCont, "data", pyObj); while ((token = yylex_clr(&tval)) != YY_NULL) { int lineno = yylineno; PyObject *pyItem = NULL, *pyArgs; switch (token) { case DEFUNNY: case INSTALL: case AUXILIARY: pyArgs = get_args(filename, lineno); if (!pyArgs) { free(tval); Py_DECREF(pyCont); return NULL; } pyItem = PyDict_New(); PyDict_SetItemString(pyItem, "type", PyUnicode_FromString(tval)); PyDict_SetItemString(pyItem, "args", pyArgs); break; case COMMENT: if (strncmp(tval, "//~", 3) && strncmp(tval, "/*~", 3)) break; pyItem = PyDict_New(); PyDict_SetItemString(pyItem, "type", PyUnicode_FromString("COMMENT")); PyDict_SetItemString(pyItem, "line", PyUnicode_FromString(tval)); break; case PREPROC: pyItem = PyDict_New(); PyDict_SetItemString(pyItem, "type", PyUnicode_FromString("PREPROC")); PyDict_SetItemString(pyItem, "line", PyUnicode_FromString(tval)); lineno--; break; } if (pyItem) { PyDict_SetItemString(pyItem, "lineno", PyLong_FromLong(lineno)); PyList_Append(pyObj, pyItem); } free(tval); } def_yylex_destroy(); fclose(fd); return pyCont; } frr-7.2.1/lib/defun_lex.l0000644000000000000000000001671313610377563012120 00000000000000/* * clippy (CLI preparator in python) C pseudo-lexer * Copyright (C) 2016-2017 David Lamparter for NetDEF, Inc. * * This program is free software; you can redistribute it and/or modify it * under the terms of the 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; see the file COPYING; if not, write to the Free Software * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA */ /* This is just enough of a lexer to make rough sense of a C source file. * It handles C preprocessor directives, strings, and looks for FRR-specific * idioms (aka DEFUN). * * There is some preliminary support for documentation comments for DEFUNs. * They would look like this (note the ~): (replace \ by /) * * \*~ documentation for foobar_cmd * * parameter does xyz * *\ * DEFUN(foobar_cmd, ...) * * This is intended for user documentation / command reference. Don't put * code documentation in it. */ %top{ #ifdef HAVE_CONFIG_H #include "config.h" #endif } %{ /* ignore harmless bugs in old versions of flex */ #pragma GCC diagnostic ignored "-Wsign-compare" #pragma GCC diagnostic ignored "-Wunused-value" #include "config.h" #include #include #include #include "command_graph.h" #include "clippy.h" #define ID 258 #define PREPROC 259 #define OPERATOR 260 #define STRING 261 #define COMMENT 262 #define SPECIAL 263 #define DEFUNNY 270 #define INSTALL 271 #define AUXILIARY 272 int comment_link; char string_end; char *value; static void extendbuf(char **what, const char *arg) { if (!*what) *what = strdup(arg); else { size_t vall = strlen(*what), argl = strlen(arg); *what = realloc(*what, vall + argl + 1); memcpy(*what + vall, arg, argl); (*what)[vall + argl] = '\0'; } } #define extend(x) extendbuf(&value, x) %} ID [A-Za-z0-9_]+ OPERATOR [!%&/\[\]{}=?:^|\*.;><~'\\+-] SPECIAL [(),] %pointer %option yylineno %option noyywrap %option noinput %option nounput %option outfile="lib/defun_lex.c" %option prefix="def_yy" %option 8bit %s linestart %x comment %x linecomment %x preproc %x rstring %% BEGIN(linestart); \n BEGIN(linestart); "/*" comment_link = YY_START; extend(yytext); BEGIN(comment); [^*\n]* extend(yytext); "*"+[^*/\n]* extend(yytext); \n extend(yytext); "*"+"/" extend(yytext); BEGIN(comment_link); return COMMENT; "//" comment_link = YY_START; extend(yytext); BEGIN(linecomment); [^\n]* extend(yytext); \n BEGIN((comment_link == INITIAL) ? linestart : comment_link); return COMMENT; # BEGIN(preproc); \n BEGIN(INITIAL); return PREPROC; [^\n\\]+ extend(yytext); \\\n extend(yytext); \\+[^\n] extend(yytext); [\"\'] string_end = yytext[0]; extend(yytext); BEGIN(rstring); [\"\'] { extend(yytext); if (yytext[0] == string_end) { BEGIN(INITIAL); return STRING; } } \\\n /* ignore */ \\. extend(yytext); [^\\\"\']+ extend(yytext); "DEFUN" value = strdup(yytext); return DEFUNNY; "DEFUN_NOSH" value = strdup(yytext); return DEFUNNY; "DEFUN_HIDDEN" value = strdup(yytext); return DEFUNNY; "DEFPY" value = strdup(yytext); return DEFUNNY; "DEFPY_NOSH" value = strdup(yytext); return DEFUNNY; "DEFPY_ATTR" value = strdup(yytext); return DEFUNNY; "DEFPY_HIDDEN" value = strdup(yytext); return DEFUNNY; "ALIAS" value = strdup(yytext); return DEFUNNY; "ALIAS_HIDDEN" value = strdup(yytext); return DEFUNNY; "install_element" value = strdup(yytext); return INSTALL; "VTYSH_TARGETS" value = strdup(yytext); return AUXILIARY; "VTYSH_NODESWITCH" value = strdup(yytext); return AUXILIARY; [ \t\n]+ /* ignore */ \\ /* ignore */ {ID} BEGIN(INITIAL); value = strdup(yytext); return ID; {OPERATOR} BEGIN(INITIAL); value = strdup(yytext); return OPERATOR; {SPECIAL} BEGIN(INITIAL); value = strdup(yytext); return SPECIAL; . /* printf("-- '%s' in init\n", yytext); */ BEGIN(INITIAL); return yytext[0]; %% static int yylex_clr(char **retbuf) { int rv = def_yylex(); *retbuf = value; value = NULL; return rv; } static PyObject *get_args(const char *filename, int lineno) { PyObject *pyObj = PyList_New(0); PyObject *pyArg = NULL; char *tval; int depth = 1; int token; while ((token = yylex_clr(&tval)) != YY_NULL) { if (token == SPECIAL && tval[0] == '(') { free(tval); break; } if (token == COMMENT) { free(tval); continue; } fprintf(stderr, "invalid input!\n"); exit(1); } while ((token = yylex_clr(&tval)) != YY_NULL) { if (token == COMMENT) { free(tval); continue; } if (token == PREPROC) { free(tval); Py_DECREF(pyObj); return PyErr_Format(PyExc_ValueError, "%s:%d: cannot process CPP directive within argument list", filename, lineno); } if (token == SPECIAL) { if (depth == 1 && (tval[0] == ',' || tval[0] == ')')) { if (pyArg) PyList_Append(pyObj, pyArg); pyArg = NULL; if (tval[0] == ')') { free(tval); break; } free(tval); continue; } if (tval[0] == '(') depth++; if (tval[0] == ')') depth--; } if (!pyArg) pyArg = PyList_New(0); PyList_Append(pyArg, PyUnicode_FromString(tval)); free(tval); } return pyObj; } /* _clippy.parse() -- read a C file, returning a list of interesting bits. * note this ditches most of the actual C code. */ PyObject *clippy_parse(PyObject *self, PyObject *args) { const char *filename; if (!PyArg_ParseTuple(args, "s", &filename)) return NULL; FILE *fd = fopen(filename, "r"); if (!fd) return PyErr_SetFromErrnoWithFilename(PyExc_IOError, filename); char *tval; int token; yyin = fd; value = NULL; PyObject *pyCont = PyDict_New(); PyObject *pyObj = PyList_New(0); PyDict_SetItemString(pyCont, "filename", PyUnicode_FromString(filename)); PyDict_SetItemString(pyCont, "data", pyObj); while ((token = yylex_clr(&tval)) != YY_NULL) { int lineno = yylineno; PyObject *pyItem = NULL, *pyArgs; switch (token) { case DEFUNNY: case INSTALL: case AUXILIARY: pyArgs = get_args(filename, lineno); if (!pyArgs) { free(tval); Py_DECREF(pyCont); return NULL; } pyItem = PyDict_New(); PyDict_SetItemString(pyItem, "type", PyUnicode_FromString(tval)); PyDict_SetItemString(pyItem, "args", pyArgs); break; case COMMENT: if (strncmp(tval, "//~", 3) && strncmp(tval, "/*~", 3)) break; pyItem = PyDict_New(); PyDict_SetItemString(pyItem, "type", PyUnicode_FromString("COMMENT")); PyDict_SetItemString(pyItem, "line", PyUnicode_FromString(tval)); break; case PREPROC: pyItem = PyDict_New(); PyDict_SetItemString(pyItem, "type", PyUnicode_FromString("PREPROC")); PyDict_SetItemString(pyItem, "line", PyUnicode_FromString(tval)); lineno--; break; } if (pyItem) { PyDict_SetItemString(pyItem, "lineno", PyLong_FromLong(lineno)); PyList_Append(pyObj, pyItem); } free(tval); } def_yylex_destroy(); fclose(fd); return pyCont; } frr-7.2.1/lib/distribute.c0000644000000000000000000004217013610377563012310 00000000000000/* Distribute list functions * Copyright (C) 1998, 1999 Kunihiro Ishiguro * * This file is part of GNU Zebra. * * GNU Zebra is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License as published * by the Free Software Foundation; either version 2, or (at your * option) any later version. * * GNU Zebra 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; see the file COPYING; if not, write to the Free Software * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA */ #include #include "hash.h" #include "if.h" #include "filter.h" #include "command.h" #include "distribute.h" #include "memory.h" DEFINE_MTYPE_STATIC(LIB, DISTRIBUTE_CTX, "Distribute ctx") DEFINE_MTYPE_STATIC(LIB, DISTRIBUTE, "Distribute list") DEFINE_MTYPE_STATIC(LIB, DISTRIBUTE_IFNAME, "Dist-list ifname") DEFINE_MTYPE_STATIC(LIB, DISTRIBUTE_NAME, "Dist-list name") static struct list *dist_ctx_list; static struct distribute *distribute_new(void) { return XCALLOC(MTYPE_DISTRIBUTE, sizeof(struct distribute)); } /* Free distribute object. */ static void distribute_free(struct distribute *dist) { int i = 0; XFREE(MTYPE_DISTRIBUTE_IFNAME, dist->ifname); for (i = 0; i < DISTRIBUTE_MAX; i++) { XFREE(MTYPE_DISTRIBUTE_NAME, dist->list[i]); } for (i = 0; i < DISTRIBUTE_MAX; i++) { XFREE(MTYPE_DISTRIBUTE_NAME, dist->prefix[i]); } XFREE(MTYPE_DISTRIBUTE, dist); } static void distribute_free_if_empty(struct distribute_ctx *ctx, struct distribute *dist) { int i; for (i = 0; i < DISTRIBUTE_MAX; i++) if (dist->list[i] != NULL || dist->prefix[i] != NULL) return; hash_release(ctx->disthash, dist); distribute_free(dist); } /* Lookup interface's distribute list. */ struct distribute *distribute_lookup(struct distribute_ctx *ctx, const char *ifname) { struct distribute key; struct distribute *dist; /* temporary reference */ key.ifname = (ifname) ? XSTRDUP(MTYPE_DISTRIBUTE_IFNAME, ifname) : NULL; dist = hash_lookup(ctx->disthash, &key); XFREE(MTYPE_DISTRIBUTE_IFNAME, key.ifname); return dist; } void distribute_list_add_hook(struct distribute_ctx *ctx, void (*func)(struct distribute_ctx *ctx, struct distribute *)) { ctx->distribute_add_hook = func; } void distribute_list_delete_hook(struct distribute_ctx *ctx, void (*func)(struct distribute_ctx *ctx, struct distribute *)) { ctx->distribute_delete_hook = func; } static void *distribute_hash_alloc(struct distribute *arg) { struct distribute *dist; dist = distribute_new(); if (arg->ifname) dist->ifname = XSTRDUP(MTYPE_DISTRIBUTE_IFNAME, arg->ifname); else dist->ifname = NULL; return dist; } /* Make new distribute list and push into hash. */ static struct distribute *distribute_get(struct distribute_ctx *ctx, const char *ifname) { struct distribute key; struct distribute *ret; /* temporary reference */ key.ifname = (ifname) ? XSTRDUP(MTYPE_DISTRIBUTE_IFNAME, ifname) : NULL; ret = hash_get(ctx->disthash, &key, (void *(*)(void *))distribute_hash_alloc); XFREE(MTYPE_DISTRIBUTE_IFNAME, key.ifname); return ret; } static unsigned int distribute_hash_make(const void *arg) { const struct distribute *dist = arg; return dist->ifname ? string_hash_make(dist->ifname) : 0; } /* If two distribute-list have same value then return 1 else return 0. This function is used by hash package. */ static bool distribute_cmp(const struct distribute *dist1, const struct distribute *dist2) { if (dist1->ifname && dist2->ifname) if (strcmp(dist1->ifname, dist2->ifname) == 0) return true; if (!dist1->ifname && !dist2->ifname) return true; return false; } /* Set access-list name to the distribute list. */ static void distribute_list_set(struct distribute_ctx *ctx, const char *ifname, enum distribute_type type, const char *alist_name) { struct distribute *dist; dist = distribute_get(ctx, ifname); XFREE(MTYPE_DISTRIBUTE_NAME, dist->list[type]); dist->list[type] = XSTRDUP(MTYPE_DISTRIBUTE_NAME, alist_name); /* Apply this distribute-list to the interface. */ (ctx->distribute_add_hook)(ctx, dist); } /* Unset distribute-list. If matched distribute-list exist then return 1. */ static int distribute_list_unset(struct distribute_ctx *ctx, const char *ifname, enum distribute_type type, const char *alist_name) { struct distribute *dist; dist = distribute_lookup(ctx, ifname); if (!dist) return 0; if (!dist->list[type]) return 0; if (strcmp(dist->list[type], alist_name) != 0) return 0; XFREE(MTYPE_DISTRIBUTE_NAME, dist->list[type]); dist->list[type] = NULL; /* Apply this distribute-list to the interface. */ (ctx->distribute_delete_hook)(ctx, dist); /* If all dist are NULL, then free distribute list. */ distribute_free_if_empty(ctx, dist); return 1; } /* Set access-list name to the distribute list. */ static void distribute_list_prefix_set(struct distribute_ctx *ctx, const char *ifname, enum distribute_type type, const char *plist_name) { struct distribute *dist; dist = distribute_get(ctx, ifname); XFREE(MTYPE_DISTRIBUTE_NAME, dist->prefix[type]); dist->prefix[type] = XSTRDUP(MTYPE_DISTRIBUTE_NAME, plist_name); /* Apply this distribute-list to the interface. */ (ctx->distribute_add_hook)(ctx, dist); } /* Unset distribute-list. If matched distribute-list exist then return 1. */ static int distribute_list_prefix_unset(struct distribute_ctx *ctx, const char *ifname, enum distribute_type type, const char *plist_name) { struct distribute *dist; dist = distribute_lookup(ctx, ifname); if (!dist) return 0; if (!dist->prefix[type]) return 0; if (strcmp(dist->prefix[type], plist_name) != 0) return 0; XFREE(MTYPE_DISTRIBUTE_NAME, dist->prefix[type]); dist->prefix[type] = NULL; /* Apply this distribute-list to the interface. */ (ctx->distribute_delete_hook)(ctx, dist); /* If all dist are NULL, then free distribute list. */ distribute_free_if_empty(ctx, dist); return 1; } DEFUN (distribute_list, distribute_list_cmd, "distribute-list [prefix] WORD [WORD]", "Filter networks in routing updates\n" "Specify a prefix\n" "Access-list name\n" "Filter incoming routing updates\n" "Filter outgoing routing updates\n" "Interface name\n") { int prefix = (argv[1]->type == WORD_TKN) ? 1 : 0; /* Check of distribute list type. */ enum distribute_type type = argv[2 + prefix]->arg[0] == 'i' ? DISTRIBUTE_V4_IN : DISTRIBUTE_V4_OUT; /* Set appropriate function call */ void (*distfn)(struct distribute_ctx *, const char *, enum distribute_type, const char *) = prefix ? &distribute_list_prefix_set : &distribute_list_set; struct distribute_ctx *ctx = (struct distribute_ctx *)listnode_head(dist_ctx_list); /* if interface is present, get name */ const char *ifname = NULL; if (argv[argc - 1]->type == VARIABLE_TKN) ifname = argv[argc - 1]->arg; /* Get interface name corresponding distribute list. */ distfn(ctx, ifname, type, argv[1 + prefix]->arg); return CMD_SUCCESS; } DEFUN (ipv6_distribute_list, ipv6_distribute_list_cmd, "ipv6 distribute-list [prefix] WORD [WORD]", "IPv6\n" "Filter networks in routing updates\n" "Specify a prefix\n" "Access-list name\n" "Filter incoming routing updates\n" "Filter outgoing routing updates\n" "Interface name\n") { int prefix = (argv[2]->type == WORD_TKN) ? 1 : 0; /* Check of distribute list type. */ enum distribute_type type = argv[3 + prefix]->arg[0] == 'i' ? DISTRIBUTE_V6_IN : DISTRIBUTE_V6_OUT; /* Set appropriate function call */ void (*distfn)(struct distribute_ctx *, const char *, enum distribute_type, const char *) = prefix ? &distribute_list_prefix_set : &distribute_list_set; struct distribute_ctx *ctx = listnode_head(dist_ctx_list); /* if interface is present, get name */ const char *ifname = NULL; if (argv[argc - 1]->type == VARIABLE_TKN) ifname = argv[argc - 1]->arg; /* Get interface name corresponding distribute list. */ distfn(ctx, ifname, type, argv[2 + prefix]->arg); return CMD_SUCCESS; } DEFUN (no_distribute_list, no_distribute_list_cmd, "no distribute-list [prefix] WORD [WORD]", NO_STR "Filter networks in routing updates\n" "Specify a prefix\n" "Access-list name\n" "Filter incoming routing updates\n" "Filter outgoing routing updates\n" "Interface name\n") { int prefix = (argv[2]->type == WORD_TKN) ? 1 : 0; int idx_alname = 2 + prefix; int idx_disttype = idx_alname + 1; enum distribute_type type = argv[idx_disttype]->arg[0] == 'i' ? DISTRIBUTE_V4_IN : DISTRIBUTE_V4_OUT; /* Set appropriate function call */ int (*distfn)(struct distribute_ctx *, const char *, enum distribute_type, const char *) = prefix ? &distribute_list_prefix_unset : &distribute_list_unset; struct distribute_ctx *ctx = listnode_head(dist_ctx_list); /* if interface is present, get name */ const char *ifname = NULL; if (argv[argc - 1]->type == VARIABLE_TKN) ifname = argv[argc - 1]->arg; /* Get interface name corresponding distribute list. */ int ret = distfn(ctx, ifname, type, argv[2 + prefix]->arg); if (!ret) { vty_out(vty, "distribute list doesn't exist\n"); return CMD_WARNING_CONFIG_FAILED; } return CMD_SUCCESS; } DEFUN (no_ipv6_distribute_list, no_ipv6_distribute_list_cmd, "no ipv6 distribute-list [prefix] WORD [WORD]", NO_STR "IPv6\n" "Filter networks in routing updates\n" "Specify a prefix\n" "Access-list name\n" "Filter incoming routing updates\n" "Filter outgoing routing updates\n" "Interface name\n") { int prefix = (argv[3]->type == WORD_TKN) ? 1 : 0; int idx_alname = 3 + prefix; int idx_disttype = idx_alname + 1; enum distribute_type type = argv[idx_disttype]->arg[0] == 'i' ? DISTRIBUTE_V6_IN : DISTRIBUTE_V6_OUT; struct distribute_ctx *ctx = listnode_head(dist_ctx_list); /* Set appropriate function call */ int (*distfn)(struct distribute_ctx *, const char *, enum distribute_type, const char *) = prefix ? &distribute_list_prefix_unset : &distribute_list_unset; /* if interface is present, get name */ const char *ifname = NULL; if (argv[argc - 1]->type == VARIABLE_TKN) ifname = argv[argc - 1]->arg; /* Get interface name corresponding distribute list. */ int ret = distfn(ctx, ifname, type, argv[3 + prefix]->arg); if (!ret) { vty_out(vty, "distribute list doesn't exist\n"); return CMD_WARNING_CONFIG_FAILED; } return CMD_SUCCESS; } static int distribute_print(struct vty *vty, char *tab[], int is_prefix, enum distribute_type type, int has_print) { if (tab[type]) { vty_out(vty, "%s %s%s", has_print ? "," : "", is_prefix ? "(prefix-list) " : "", tab[type]); return 1; } return has_print; } int config_show_distribute(struct vty *vty, struct distribute_ctx *dist_ctxt) { unsigned int i; int has_print = 0; struct hash_bucket *mp; struct distribute *dist; /* Output filter configuration. */ dist = distribute_lookup(dist_ctxt, NULL); vty_out(vty, " Outgoing update filter list for all interface is"); has_print = 0; if (dist) { has_print = distribute_print(vty, dist->list, 0, DISTRIBUTE_V4_OUT, has_print); has_print = distribute_print(vty, dist->prefix, 1, DISTRIBUTE_V4_OUT, has_print); has_print = distribute_print(vty, dist->list, 0, DISTRIBUTE_V6_OUT, has_print); has_print = distribute_print(vty, dist->prefix, 1, DISTRIBUTE_V6_OUT, has_print); } if (has_print) vty_out(vty, "\n"); else vty_out(vty, " not set\n"); for (i = 0; i < dist_ctxt->disthash->size; i++) for (mp = dist_ctxt->disthash->index[i]; mp; mp = mp->next) { dist = mp->data; if (dist->ifname) { vty_out(vty, " %s filtered by", dist->ifname); has_print = 0; has_print = distribute_print(vty, dist->list, 0, DISTRIBUTE_V4_OUT, has_print); has_print = distribute_print( vty, dist->prefix, 1, DISTRIBUTE_V4_OUT, has_print); has_print = distribute_print(vty, dist->list, 0, DISTRIBUTE_V6_OUT, has_print); has_print = distribute_print( vty, dist->prefix, 1, DISTRIBUTE_V6_OUT, has_print); if (has_print) vty_out(vty, "\n"); else vty_out(vty, " nothing\n"); } } /* Input filter configuration. */ dist = distribute_lookup(dist_ctxt, NULL); vty_out(vty, " Incoming update filter list for all interface is"); has_print = 0; if (dist) { has_print = distribute_print(vty, dist->list, 0, DISTRIBUTE_V4_IN, has_print); has_print = distribute_print(vty, dist->prefix, 1, DISTRIBUTE_V4_IN, has_print); has_print = distribute_print(vty, dist->list, 0, DISTRIBUTE_V6_IN, has_print); has_print = distribute_print(vty, dist->prefix, 1, DISTRIBUTE_V6_IN, has_print); } if (has_print) vty_out(vty, "\n"); else vty_out(vty, " not set\n"); for (i = 0; i < dist_ctxt->disthash->size; i++) for (mp = dist_ctxt->disthash->index[i]; mp; mp = mp->next) { dist = mp->data; if (dist->ifname) { vty_out(vty, " %s filtered by", dist->ifname); has_print = 0; has_print = distribute_print(vty, dist->list, 0, DISTRIBUTE_V4_IN, has_print); has_print = distribute_print( vty, dist->prefix, 1, DISTRIBUTE_V4_IN, has_print); has_print = distribute_print(vty, dist->list, 0, DISTRIBUTE_V6_IN, has_print); has_print = distribute_print( vty, dist->prefix, 1, DISTRIBUTE_V6_IN, has_print); if (has_print) vty_out(vty, "\n"); else vty_out(vty, " nothing\n"); } } return 0; } /* Configuration write function. */ int config_write_distribute(struct vty *vty, struct distribute_ctx *dist_ctxt) { unsigned int i; int j; int output, v6; struct hash_bucket *mp; int write = 0; for (i = 0; i < dist_ctxt->disthash->size; i++) for (mp = dist_ctxt->disthash->index[i]; mp; mp = mp->next) { struct distribute *dist; dist = mp->data; for (j = 0; j < DISTRIBUTE_MAX; j++) if (dist->list[j]) { output = j == DISTRIBUTE_V4_OUT || j == DISTRIBUTE_V6_OUT; v6 = j == DISTRIBUTE_V6_IN || j == DISTRIBUTE_V6_OUT; vty_out(vty, " %sdistribute-list %s %s %s\n", v6 ? "ipv6 " : "", dist->list[j], output ? "out" : "in", dist->ifname ? dist->ifname : ""); write++; } for (j = 0; j < DISTRIBUTE_MAX; j++) if (dist->prefix[j]) { output = j == DISTRIBUTE_V4_OUT || j == DISTRIBUTE_V6_OUT; v6 = j == DISTRIBUTE_V6_IN || j == DISTRIBUTE_V6_OUT; vty_out(vty, " %sdistribute-list prefix %s %s %s\n", v6 ? "ipv6 " : "", dist->prefix[j], output ? "out" : "in", dist->ifname ? dist->ifname : ""); write++; } } return write; } void distribute_list_delete(struct distribute_ctx **ctx) { if ((*ctx)->disthash) { hash_clean((*ctx)->disthash, (void (*)(void *))distribute_free); } if (!dist_ctx_list) dist_ctx_list = list_new(); listnode_delete(dist_ctx_list, *ctx); if (list_isempty(dist_ctx_list)) list_delete(&dist_ctx_list); XFREE(MTYPE_DISTRIBUTE_CTX, (*ctx)); } /* Initialize distribute list container */ struct distribute_ctx *distribute_list_ctx_create(struct vrf *vrf) { struct distribute_ctx *ctx; ctx = XCALLOC(MTYPE_DISTRIBUTE_CTX, sizeof(struct distribute_ctx)); ctx->vrf = vrf; ctx->disthash = hash_create( distribute_hash_make, (bool (*)(const void *, const void *))distribute_cmp, NULL); if (!dist_ctx_list) dist_ctx_list = list_new(); listnode_add(dist_ctx_list, ctx); return ctx; } /* Initialize distribute list vty commands */ void distribute_list_init(int node) { /* vtysh command-extraction doesn't grok install_element(node, ) */ if (node == RIP_NODE) { install_element(RIP_NODE, &distribute_list_cmd); install_element(RIP_NODE, &no_distribute_list_cmd); } else if (node == RIPNG_NODE) { install_element(RIPNG_NODE, &distribute_list_cmd); install_element(RIPNG_NODE, &no_distribute_list_cmd); /* install v6 */ install_element(RIPNG_NODE, &ipv6_distribute_list_cmd); install_element(RIPNG_NODE, &no_ipv6_distribute_list_cmd); } /* TODO: install v4 syntax command for v6 only protocols. */ /* if (node == RIPNG_NODE) { * install_element (node, &ipv6_as_v4_distribute_list_all_cmd); * install_element (node, &no_ipv6_as_v4_distribute_list_all_cmd); * install_element (node, &ipv6_as_v4_distribute_list_cmd); * install_element (node, &no_ipv6_as_v4_distribute_list_cmd); * install_element (node, &ipv6_as_v4_distribute_list_prefix_all_cmd); * install_element (node, &no_ipv6_as_v4_distribute_list_prefix_all_cmd); * install_element (node, &ipv6_as_v4_distribute_list_prefix_cmd); * install_element (node, &no_ipv6_as_v4_distribute_list_prefix_cmd); }*/ } frr-7.2.1/lib/distribute.h0000644000000000000000000000523613610377563012317 00000000000000/* Distribute list functions header * Copyright (C) 1999 Kunihiro Ishiguro * * This file is part of GNU Zebra. * * GNU Zebra is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License as published * by the Free Software Foundation; either version 2, or (at your * option) any later version. * * GNU Zebra 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; see the file COPYING; if not, write to the Free Software * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA */ #ifndef _ZEBRA_DISTRIBUTE_H #define _ZEBRA_DISTRIBUTE_H #include #include "if.h" #include "filter.h" #ifdef __cplusplus extern "C" { #endif /* Disctirubte list types. */ enum distribute_type { DISTRIBUTE_V4_IN, DISTRIBUTE_V6_IN, DISTRIBUTE_V4_OUT, DISTRIBUTE_V6_OUT, DISTRIBUTE_MAX }; struct distribute { /* Name of the interface. */ char *ifname; /* Filter name of `in' and `out' */ char *list[DISTRIBUTE_MAX]; /* prefix-list name of `in' and `out' */ char *prefix[DISTRIBUTE_MAX]; }; struct distribute_ctx { /* Hash of distribute list. */ struct hash *disthash; /* Hook functions. */ void (*distribute_add_hook)(struct distribute_ctx *ctx, struct distribute *dist); void (*distribute_delete_hook)(struct distribute_ctx *ctx, struct distribute *dist); /* vrf information */ struct vrf *vrf; }; /* Prototypes for distribute-list. */ extern void distribute_list_init(int node); extern struct distribute_ctx *distribute_list_ctx_create(struct vrf *vrf); extern void distribute_list_delete(struct distribute_ctx **ctx); extern void distribute_list_add_hook(struct distribute_ctx *ctx, void (*)(struct distribute_ctx *ctx, struct distribute *)); extern void distribute_list_delete_hook(struct distribute_ctx *ctx, void (*)(struct distribute_ctx *ctx, struct distribute *)); extern struct distribute *distribute_lookup(struct distribute_ctx *ctx, const char *ifname); extern int config_write_distribute(struct vty *vty, struct distribute_ctx *ctx); extern int config_show_distribute(struct vty *vty, struct distribute_ctx *ctx); extern enum filter_type distribute_apply_in(struct interface *, struct prefix *); extern enum filter_type distribute_apply_out(struct interface *, struct prefix *); #ifdef __cplusplus } #endif #endif /* _ZEBRA_DISTRIBUTE_H */ frr-7.2.1/lib/ferr.c0000644000000000000000000001525113610377563011070 00000000000000/* * Copyright (c) 2015-16 David Lamparter, for NetDEF, Inc. * * Permission to use, copy, modify, and distribute this software for any * purpose with or without fee is hereby granted, provided that the above * copyright notice and this permission notice appear in all copies. * * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. */ #ifdef HAVE_CONFIG_H #include "config.h" #endif #include #include #include #include #include #include #include "ferr.h" #include "vty.h" #include "jhash.h" #include "memory.h" #include "hash.h" #include "command.h" #include "json.h" #include "linklist.h" #include "frr_pthread.h" DEFINE_MTYPE_STATIC(LIB, ERRINFO, "error information") /* * Thread-specific key for temporary storage of allocated ferr. */ static pthread_key_t errkey; static void ferr_free(void *arg) { XFREE(MTYPE_ERRINFO, arg); } static void err_key_init(void) __attribute__((_CONSTRUCTOR(500))); static void err_key_init(void) { pthread_key_create(&errkey, ferr_free); } static void err_key_fini(void) __attribute__((_DESTRUCTOR(500))); static void err_key_fini(void) { pthread_key_delete(errkey); } /* * Global shared hash table holding reference text for all defined errors. */ static pthread_mutex_t refs_mtx = PTHREAD_MUTEX_INITIALIZER; struct hash *refs; static bool ferr_hash_cmp(const void *a, const void *b) { const struct log_ref *f_a = a; const struct log_ref *f_b = b; return f_a->code == f_b->code; } static inline unsigned int ferr_hash_key(const void *a) { const struct log_ref *f = a; return f->code; } void log_ref_add(struct log_ref *ref) { uint32_t i = 0; frr_with_mutex(&refs_mtx) { while (ref[i].code != END_FERR) { hash_get(refs, &ref[i], hash_alloc_intern); i++; } } } struct log_ref *log_ref_get(uint32_t code) { struct log_ref holder; struct log_ref *ref; holder.code = code; frr_with_mutex(&refs_mtx) { ref = hash_lookup(refs, &holder); } return ref; } void log_ref_display(struct vty *vty, uint32_t code, bool json) { struct log_ref *ref; struct json_object *top = NULL, *obj = NULL; struct list *errlist; struct listnode *ln; if (json) top = json_object_new_object(); frr_with_mutex(&refs_mtx) { errlist = code ? list_new() : hash_to_list(refs); } if (code) { ref = log_ref_get(code); if (!ref) return; listnode_add(errlist, ref); } for (ALL_LIST_ELEMENTS_RO(errlist, ln, ref)) { if (json) { char key[11]; snprintf(key, sizeof(key), "%"PRIu32, ref->code); obj = json_object_new_object(); json_object_string_add(obj, "title", ref->title); json_object_string_add(obj, "description", ref->description); json_object_string_add(obj, "suggestion", ref->suggestion); json_object_object_add(top, key, obj); } else { char pbuf[256]; char ubuf[256]; snprintf(pbuf, sizeof(pbuf), "\nError %"PRIu32" - %s", ref->code, ref->title); memset(ubuf, '=', strlen(pbuf)); ubuf[strlen(pbuf)] = '\0'; vty_out(vty, "%s\n%s\n", pbuf, ubuf); vty_out(vty, "Description:\n%s\n\n", ref->description); vty_out(vty, "Recommendation:\n%s\n", ref->suggestion); } } if (json) { const char *str = json_object_to_json_string_ext( top, JSON_C_TO_STRING_PRETTY); vty_out(vty, "%s\n", str); json_object_free(top); } list_delete(&errlist); } DEFUN_NOSH(show_error_code, show_error_code_cmd, "show error <(1-4294967295)|all> [json]", SHOW_STR "Information on errors\n" "Error code to get info about\n" "Information on all errors\n" JSON_STR) { bool json = strmatch(argv[argc-1]->text, "json"); uint32_t arg = 0; if (!strmatch(argv[2]->text, "all")) arg = strtoul(argv[2]->arg, NULL, 10); log_ref_display(vty, arg, json); return CMD_SUCCESS; } void log_ref_init(void) { frr_with_mutex(&refs_mtx) { refs = hash_create(ferr_hash_key, ferr_hash_cmp, "Error Reference Texts"); } } void log_ref_fini(void) { frr_with_mutex(&refs_mtx) { hash_clean(refs, NULL); hash_free(refs); refs = NULL; } } void log_ref_vty_init(void) { install_element(VIEW_NODE, &show_error_code_cmd); } const struct ferr *ferr_get_last(ferr_r errval) { struct ferr *last_error = pthread_getspecific(errkey); if (!last_error || last_error->kind == 0) return NULL; return last_error; } ferr_r ferr_clear(void) { struct ferr *last_error = pthread_getspecific(errkey); if (last_error) last_error->kind = 0; return ferr_ok(); } static ferr_r ferr_set_va(const char *file, int line, const char *func, enum ferr_kind kind, const char *pathname, int errno_val, const char *text, va_list va) { struct ferr *error = pthread_getspecific(errkey); if (!error) { error = XCALLOC(MTYPE_ERRINFO, sizeof(*error)); pthread_setspecific(errkey, error); } error->file = file; error->line = line; error->func = func; error->kind = kind; error->unique_id = jhash(text, strlen(text), jhash(file, strlen(file), 0xd4ed0298)); error->errno_val = errno_val; if (pathname) snprintf(error->pathname, sizeof(error->pathname), "%s", pathname); else error->pathname[0] = '\0'; vsnprintf(error->message, sizeof(error->message), text, va); return -1; } ferr_r ferr_set_internal(const char *file, int line, const char *func, enum ferr_kind kind, const char *text, ...) { ferr_r rv; va_list va; va_start(va, text); rv = ferr_set_va(file, line, func, kind, NULL, 0, text, va); va_end(va); return rv; } ferr_r ferr_set_internal_ext(const char *file, int line, const char *func, enum ferr_kind kind, const char *pathname, int errno_val, const char *text, ...) { ferr_r rv; va_list va; va_start(va, text); rv = ferr_set_va(file, line, func, kind, pathname, errno_val, text, va); va_end(va); return rv; } #define REPLACE "$ERR" void vty_print_error(struct vty *vty, ferr_r err, const char *msg, ...) { char tmpmsg[512], *replacepos; const struct ferr *last_error = ferr_get_last(err); va_list va; va_start(va, msg); vsnprintf(tmpmsg, sizeof(tmpmsg), msg, va); va_end(va); replacepos = strstr(tmpmsg, REPLACE); if (!replacepos) vty_out(vty, "%s\n", tmpmsg); else { replacepos[0] = '\0'; replacepos += sizeof(REPLACE) - 1; vty_out(vty, "%s%s%s\n", tmpmsg, last_error ? last_error->message : "(no error?)", replacepos); } } frr-7.2.1/lib/ferr.h0000644000000000000000000002132213610377563011071 00000000000000/* * Copyright (c) 2015-16 David Lamparter, for NetDEF, Inc. * * Permission to use, copy, modify, and distribute this software for any * purpose with or without fee is hereby granted, provided that the above * copyright notice and this permission notice appear in all copies. * * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. */ #ifndef _FRR_FERR_H #define _FRR_FERR_H /*********************************************************** * scroll down to the end of this file for a full example! * ***********************************************************/ #include #include #include #include "vty.h" #ifdef __cplusplus extern "C" { #endif /* return type when this error indication stuff is used. * * guaranteed to have boolean evaluation to "false" when OK, "true" when error * (i.e. can be changed to pointer in the future if neccessary) * * For checking, always use "if (value)", nothing else. * Do _NOT_ use any integer constant (!= 0), or sign check (< 0). */ typedef int ferr_r; /* rough category of error indication */ enum ferr_kind { /* no error */ FERR_OK = 0, /* something isn't the way it's supposed to be. * (things that might otherwise be asserts, really) */ FERR_CODE_BUG, /* user-supplied parameters don't make sense or is inconsistent * if you can express a rule for it (e.g. "holdtime > 2 * keepalive"), * it's this category. */ FERR_CONFIG_INVALID, /* user-supplied parameters don't line up with reality * (IP address or interface not available, etc.) * NB: these are really TODOs where the code needs to be fixed to * respond to future changes! */ FERR_CONFIG_REALITY, /* out of some system resource (probably memory) * aka "you didn't spend enough money error" */ FERR_RESOURCE, /* system error (permission denied, etc.) */ FERR_SYSTEM, /* error return from some external library * (FERR_SYSTEM and FERR_LIBRARY are not strongly distinct) */ FERR_LIBRARY, }; struct ferr { /* code location */ const char *file; const char *func; int line; enum ferr_kind kind; /* unique_id is calculated as a checksum of source filename and error * message format (*before* calling vsnprintf). Line number and * function name are not used; this keeps the number reasonably static * across changes. */ uint32_t unique_id; char message[384]; /* valid if != 0. note "errno" might be preprocessor foobar. */ int errno_val; /* valid if pathname[0] != '\0' */ char pathname[PATH_MAX]; }; /* Numeric ranges assigned to daemons for use as error codes. */ #define BABEL_FERR_START 0x01000001 #define BABEL_FRRR_END 0x01FFFFFF #define BGP_FERR_START 0x02000001 #define BGP_FERR_END 0x02FFFFFF #define EIGRP_FERR_START 0x03000001 #define EIGRP_FERR_END 0x03FFFFFF #define ISIS_FERR_START 0x04000001 #define ISIS_FERR_END 0x04FFFFFF #define LDP_FERR_START 0x05000001 #define LDP_FERR_END 0x05FFFFFF #define LIB_FERR_START 0x06000001 #define LIB_FERR_END 0x06FFFFFF #define NHRP_FERR_START 0x07000001 #define NHRP_FERR_END 0x07FFFFFF #define OSPF_FERR_START 0x08000001 #define OSPF_FERR_END 0x08FFFFFF #define OSPFV3_FERR_START 0x09000001 #define OSPFV3_FERR_END 0x09FFFFFF #define PBR_FERR_START 0x0A000001 #define PBR_FERR_END 0x0AFFFFFF #define PIM_FERR_START 0x0B000001 #define PIM_FERR_STOP 0x0BFFFFFF #define RIP_FERR_START 0x0C000001 #define RIP_FERR_STOP 0x0CFFFFFF #define RIPNG_FERR_START 0x0D000001 #define RIPNG_FERR_STOP 0x0DFFFFFF #define SHARP_FERR_START 0x0E000001 #define SHARP_FERR_END 0x0EFFFFFF #define VTYSH_FERR_START 0x0F000001 #define VTYSH_FRR_END 0x0FFFFFFF #define WATCHFRR_FERR_START 0x10000001 #define WATCHFRR_FERR_END 0x10FFFFFF #define ZEBRA_FERR_START 0xF1000001 #define ZEBRA_FERR_END 0xF1FFFFFF #define END_FERR 0xFFFFFFFF struct log_ref { /* Unique error code displayed to end user as a reference. -1 means * this is an uncoded error that does not have reference material. */ uint32_t code; /* Ultra brief title */ const char *title; /* Brief description of error */ const char *description; /* Remedial suggestion */ const char *suggestion; }; void log_ref_add(struct log_ref *ref); struct log_ref *log_ref_get(uint32_t code); void log_ref_display(struct vty *vty, uint32_t code, bool json); /* * This function should be called by the * code in libfrr.c */ void log_ref_init(void); void log_ref_fini(void); void log_ref_vty_init(void); /* get error details. * * NB: errval/ferr_r does NOT carry the full error information. It's only * passed around for future API flexibility. ferr_get_last always returns * the last error set in the current thread. */ const struct ferr *ferr_get_last(ferr_r errval); /* * Can optionally be called at strategic locations. * Always returns 0. */ ferr_r ferr_clear(void); /* do NOT call these functions directly. only for macro use! */ ferr_r ferr_set_internal(const char *file, int line, const char *func, enum ferr_kind kind, const char *text, ...); ferr_r ferr_set_internal_ext(const char *file, int line, const char *func, enum ferr_kind kind, const char *pathname, int errno_val, const char *text, ...); #define ferr_ok() 0 /* Report an error. * * If you need to do cleanup (free memory, etc.), save the return value in a * variable of type ferr_r. * * Don't put a \n at the end of the error message. */ #define ferr_code_bug(...) \ ferr_set_internal(__FILE__, __LINE__, __func__, FERR_CODE_BUG, \ __VA_ARGS__) #define ferr_cfg_invalid(...) \ ferr_set_internal(__FILE__, __LINE__, __func__, FERR_CONFIG_INVALID, \ __VA_ARGS__) #define ferr_cfg_reality(...) \ ferr_set_internal(__FILE__, __LINE__, __func__, FERR_CONFIG_REALITY, \ __VA_ARGS__) #define ferr_cfg_resource(...) \ ferr_set_internal(__FILE__, __LINE__, __func__, FERR_RESOURCE, \ __VA_ARGS__) #define ferr_system(...) \ ferr_set_internal(__FILE__, __LINE__, __func__, FERR_SYSTEM, \ __VA_ARGS__) #define ferr_library(...) \ ferr_set_internal(__FILE__, __LINE__, __func__, FERR_LIBRARY, \ __VA_ARGS__) /* extended information variants */ #define ferr_system_errno(...) \ ferr_set_internal_ext(__FILE__, __LINE__, __func__, FERR_SYSTEM, NULL, \ errno, __VA_ARGS__) #define ferr_system_path_errno(path, ...) \ ferr_set_internal_ext(__FILE__, __LINE__, __func__, FERR_SYSTEM, path, \ errno, __VA_ARGS__) #include "vty.h" /* print error message to vty; $ERR is replaced by the error's message */ void vty_print_error(struct vty *vty, ferr_r err, const char *msg, ...); #define CMD_FERR_DO(func, action, ...) \ do { \ ferr_r cmd_retval = func; \ if (cmd_retval) { \ vty_print_error(vty, cmd_retval, __VA_ARGS__); \ action; \ } \ } while (0) #define CMD_FERR_RETURN(func, ...) \ CMD_FERR_DO(func, return CMD_WARNING_CONFIG_FAILED, __VA_ARGS__) #define CMD_FERR_GOTO(func, label, ...) \ CMD_FERR_DO(func, goto label, __VA_ARGS__) /* example: uses bogus #define to keep indent.py happy */ #ifdef THIS_IS_AN_EXAMPLE ferr_r foo_bar_set(struct object *obj, int bar) { if (bar < 1 || bar >= 100) return ferr_config_invalid("bar setting (%d) must be 0bar = bar; if (ioctl(obj->fd, bar)) return ferr_system_errno("couldn't set bar to %d", bar); return ferr_ok(); } DEFUN("bla") { CMD_FERR_RETURN(foo_bar_set(obj, atoi(argv[1])), "command failed: $ERR\n"); return CMD_SUCCESS; } #endif /* THIS_IS_AN_EXAMPLE */ #ifdef __cplusplus } #endif #endif /* _FERR_H */ frr-7.2.1/lib/filter.c0000644000000000000000000023135313610377563011422 00000000000000/* Route filtering function. * Copyright (C) 1998, 1999 Kunihiro Ishiguro * * This file is part of GNU Zebra. * * GNU Zebra is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License as published * by the Free Software Foundation; either version 2, or (at your * option) any later version. * * GNU Zebra 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; see the file COPYING; if not, write to the Free Software * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA */ #include #include "prefix.h" #include "filter.h" #include "memory.h" #include "command.h" #include "sockunion.h" #include "buffer.h" #include "log.h" #include "routemap.h" #include "libfrr.h" DEFINE_MTYPE_STATIC(LIB, ACCESS_LIST, "Access List") DEFINE_MTYPE_STATIC(LIB, ACCESS_LIST_STR, "Access List Str") DEFINE_MTYPE_STATIC(LIB, ACCESS_FILTER, "Access Filter") struct filter_cisco { /* Cisco access-list */ int extended; struct in_addr addr; struct in_addr addr_mask; struct in_addr mask; struct in_addr mask_mask; }; struct filter_zebra { /* If this filter is "exact" match then this flag is set. */ int exact; /* Prefix information. */ struct prefix prefix; }; /* Filter element of access list */ struct filter { /* For doubly linked list. */ struct filter *next; struct filter *prev; /* Filter type information. */ enum filter_type type; /* Sequence number */ int64_t seq; /* Cisco access-list */ int cisco; union { struct filter_cisco cfilter; struct filter_zebra zfilter; } u; }; /* List of access_list. */ struct access_list_list { struct access_list *head; struct access_list *tail; }; /* Master structure of access_list. */ struct access_master { /* List of access_list which name is number. */ struct access_list_list num; /* List of access_list which name is string. */ struct access_list_list str; /* Hook function which is executed when new access_list is added. */ void (*add_hook)(struct access_list *); /* Hook function which is executed when access_list is deleted. */ void (*delete_hook)(struct access_list *); }; /* Static structure for mac access_list's master. */ static struct access_master access_master_mac = { {NULL, NULL}, {NULL, NULL}, NULL, NULL, }; /* Static structure for IPv4 access_list's master. */ static struct access_master access_master_ipv4 = { {NULL, NULL}, {NULL, NULL}, NULL, NULL, }; /* Static structure for IPv6 access_list's master. */ static struct access_master access_master_ipv6 = { {NULL, NULL}, {NULL, NULL}, NULL, NULL, }; static struct access_master *access_master_get(afi_t afi) { if (afi == AFI_IP) return &access_master_ipv4; else if (afi == AFI_IP6) return &access_master_ipv6; else if (afi == AFI_L2VPN) return &access_master_mac; return NULL; } /* Allocate new filter structure. */ static struct filter *filter_new(void) { return XCALLOC(MTYPE_ACCESS_FILTER, sizeof(struct filter)); } static void filter_free(struct filter *filter) { XFREE(MTYPE_ACCESS_FILTER, filter); } /* Return string of filter_type. */ static const char *filter_type_str(struct filter *filter) { switch (filter->type) { case FILTER_PERMIT: return "permit"; break; case FILTER_DENY: return "deny"; break; case FILTER_DYNAMIC: return "dynamic"; break; default: return ""; break; } } /* If filter match to the prefix then return 1. */ static int filter_match_cisco(struct filter *mfilter, const struct prefix *p) { struct filter_cisco *filter; struct in_addr mask; uint32_t check_addr; uint32_t check_mask; filter = &mfilter->u.cfilter; check_addr = p->u.prefix4.s_addr & ~filter->addr_mask.s_addr; if (filter->extended) { masklen2ip(p->prefixlen, &mask); check_mask = mask.s_addr & ~filter->mask_mask.s_addr; if (memcmp(&check_addr, &filter->addr.s_addr, 4) == 0 && memcmp(&check_mask, &filter->mask.s_addr, 4) == 0) return 1; } else if (memcmp(&check_addr, &filter->addr.s_addr, 4) == 0) return 1; return 0; } /* If filter match to the prefix then return 1. */ static int filter_match_zebra(struct filter *mfilter, const struct prefix *p) { struct filter_zebra *filter = NULL; filter = &mfilter->u.zfilter; if (filter->prefix.family == p->family) { if (filter->exact) { if (filter->prefix.prefixlen == p->prefixlen) return prefix_match(&filter->prefix, p); else return 0; } else return prefix_match(&filter->prefix, p); } else return 0; } /* Allocate new access list structure. */ static struct access_list *access_list_new(void) { return XCALLOC(MTYPE_ACCESS_LIST, sizeof(struct access_list)); } /* Free allocated access_list. */ static void access_list_free(struct access_list *access) { XFREE(MTYPE_ACCESS_LIST, access); } /* Delete access_list from access_master and free it. */ static void access_list_delete(struct access_list *access) { struct filter *filter; struct filter *next; struct access_list_list *list; struct access_master *master; for (filter = access->head; filter; filter = next) { next = filter->next; filter_free(filter); } master = access->master; if (access->type == ACCESS_TYPE_NUMBER) list = &master->num; else list = &master->str; if (access->next) access->next->prev = access->prev; else list->tail = access->prev; if (access->prev) access->prev->next = access->next; else list->head = access->next; XFREE(MTYPE_ACCESS_LIST_STR, access->name); XFREE(MTYPE_TMP, access->remark); access_list_free(access); } /* Insert new access list to list of access_list. Each acceess_list is sorted by the name. */ static struct access_list *access_list_insert(afi_t afi, const char *name) { unsigned int i; long number; struct access_list *access; struct access_list *point; struct access_list_list *alist; struct access_master *master; master = access_master_get(afi); if (master == NULL) return NULL; /* Allocate new access_list and copy given name. */ access = access_list_new(); access->name = XSTRDUP(MTYPE_ACCESS_LIST_STR, name); access->master = master; /* If name is made by all digit character. We treat it as number. */ for (number = 0, i = 0; i < strlen(name); i++) { if (isdigit((unsigned char)name[i])) number = (number * 10) + (name[i] - '0'); else break; } /* In case of name is all digit character */ if (i == strlen(name)) { access->type = ACCESS_TYPE_NUMBER; /* Set access_list to number list. */ alist = &master->num; for (point = alist->head; point; point = point->next) if (atol(point->name) >= number) break; } else { access->type = ACCESS_TYPE_STRING; /* Set access_list to string list. */ alist = &master->str; /* Set point to insertion point. */ for (point = alist->head; point; point = point->next) if (strcmp(point->name, name) >= 0) break; } /* In case of this is the first element of master. */ if (alist->head == NULL) { alist->head = alist->tail = access; return access; } /* In case of insertion is made at the tail of access_list. */ if (point == NULL) { access->prev = alist->tail; alist->tail->next = access; alist->tail = access; return access; } /* In case of insertion is made at the head of access_list. */ if (point == alist->head) { access->next = alist->head; alist->head->prev = access; alist->head = access; return access; } /* Insertion is made at middle of the access_list. */ access->next = point; access->prev = point->prev; if (point->prev) point->prev->next = access; point->prev = access; return access; } /* Lookup access_list from list of access_list by name. */ struct access_list *access_list_lookup(afi_t afi, const char *name) { struct access_list *access; struct access_master *master; if (name == NULL) return NULL; master = access_master_get(afi); if (master == NULL) return NULL; for (access = master->num.head; access; access = access->next) if (strcmp(access->name, name) == 0) return access; for (access = master->str.head; access; access = access->next) if (strcmp(access->name, name) == 0) return access; return NULL; } /* Get access list from list of access_list. If there isn't matched access_list create new one and return it. */ static struct access_list *access_list_get(afi_t afi, const char *name) { struct access_list *access; access = access_list_lookup(afi, name); if (access == NULL) access = access_list_insert(afi, name); return access; } /* Apply access list to object (which should be struct prefix *). */ enum filter_type access_list_apply(struct access_list *access, const void *object) { struct filter *filter; const struct prefix *p = (const struct prefix *)object; if (access == NULL) return FILTER_DENY; for (filter = access->head; filter; filter = filter->next) { if (filter->cisco) { if (filter_match_cisco(filter, p)) return filter->type; } else { if (filter_match_zebra(filter, p)) return filter->type; } } return FILTER_DENY; } /* Add hook function. */ void access_list_add_hook(void (*func)(struct access_list *access)) { access_master_ipv4.add_hook = func; access_master_ipv6.add_hook = func; access_master_mac.add_hook = func; } /* Delete hook function. */ void access_list_delete_hook(void (*func)(struct access_list *access)) { access_master_ipv4.delete_hook = func; access_master_ipv6.delete_hook = func; access_master_mac.delete_hook = func; } /* Calculate new sequential number. */ static int64_t filter_new_seq_get(struct access_list *access) { int64_t maxseq; int64_t newseq; struct filter *filter; maxseq = newseq = 0; for (filter = access->head; filter; filter = filter->next) { if (maxseq < filter->seq) maxseq = filter->seq; } newseq = ((maxseq / 5) * 5) + 5; return (newseq > UINT_MAX) ? UINT_MAX : newseq; } /* Return access list entry which has same seq number. */ static struct filter *filter_seq_check(struct access_list *access, int64_t seq) { struct filter *filter; for (filter = access->head; filter; filter = filter->next) if (filter->seq == seq) return filter; return NULL; } /* If access_list has no filter then return 1. */ static int access_list_empty(struct access_list *access) { if (access->head == NULL && access->tail == NULL) return 1; else return 0; } /* Delete filter from specified access_list. If there is hook function execute it. */ static void access_list_filter_delete(struct access_list *access, struct filter *filter) { struct access_master *master; master = access->master; if (filter->next) filter->next->prev = filter->prev; else access->tail = filter->prev; if (filter->prev) filter->prev->next = filter->next; else access->head = filter->next; filter_free(filter); route_map_notify_dependencies(access->name, RMAP_EVENT_FILTER_DELETED); /* Run hook function. */ if (master->delete_hook) (*master->delete_hook)(access); /* If access_list becomes empty delete it from access_master. */ if (access_list_empty(access)) access_list_delete(access); } /* Add new filter to the end of specified access_list. */ static void access_list_filter_add(struct access_list *access, struct filter *filter) { struct filter *replace; struct filter *point; /* Automatic asignment of seq no. */ if (filter->seq == -1) filter->seq = filter_new_seq_get(access); if (access->tail && filter->seq > access->tail->seq) point = NULL; else { /* Is there any same seq access list filter? */ replace = filter_seq_check(access, filter->seq); if (replace) access_list_filter_delete(access, replace); /* Check insert point. */ for (point = access->head; point; point = point->next) if (point->seq >= filter->seq) break; } /* In case of this is the first element of the list. */ filter->next = point; if (point) { if (point->prev) point->prev->next = filter; else access->head = filter; filter->prev = point->prev; point->prev = filter; } else { if (access->tail) access->tail->next = filter; else access->head = filter; filter->prev = access->tail; access->tail = filter; } /* Run hook function. */ if (access->master->add_hook) (*access->master->add_hook)(access); route_map_notify_dependencies(access->name, RMAP_EVENT_FILTER_ADDED); } /* deny Specify packets to reject permit Specify packets to forward dynamic ? */ /* Hostname or A.B.C.D Address to match any Any source host host A single host address */ static struct filter *filter_lookup_cisco(struct access_list *access, struct filter *mnew) { struct filter *mfilter; struct filter_cisco *filter; struct filter_cisco *new; new = &mnew->u.cfilter; for (mfilter = access->head; mfilter; mfilter = mfilter->next) { filter = &mfilter->u.cfilter; if (filter->extended) { if (mfilter->type == mnew->type && filter->addr.s_addr == new->addr.s_addr && filter->addr_mask.s_addr == new->addr_mask.s_addr && filter->mask.s_addr == new->mask.s_addr && filter->mask_mask.s_addr == new->mask_mask.s_addr) return mfilter; } else { if (mfilter->type == mnew->type && filter->addr.s_addr == new->addr.s_addr && filter->addr_mask.s_addr == new->addr_mask.s_addr) return mfilter; } } return NULL; } static struct filter *filter_lookup_zebra(struct access_list *access, struct filter *mnew) { struct filter *mfilter; struct filter_zebra *filter; struct filter_zebra *new; new = &mnew->u.zfilter; for (mfilter = access->head; mfilter; mfilter = mfilter->next) { filter = &mfilter->u.zfilter; if (filter->exact == new->exact && mfilter->type == mnew->type) { if (prefix_same(&filter->prefix, &new->prefix)) return mfilter; } } return NULL; } static int vty_access_list_remark_unset(struct vty *vty, afi_t afi, const char *name) { struct access_list *access; access = access_list_lookup(afi, name); if (!access) { vty_out(vty, "%% access-list %s doesn't exist\n", name); return CMD_WARNING_CONFIG_FAILED; } if (access->remark) { XFREE(MTYPE_TMP, access->remark); access->remark = NULL; } if (access->head == NULL && access->tail == NULL) access_list_delete(access); return CMD_SUCCESS; } static int filter_set_cisco(struct vty *vty, const char *name_str, const char *seq, const char *type_str, const char *addr_str, const char *addr_mask_str, const char *mask_str, const char *mask_mask_str, int extended, int set) { int ret; enum filter_type type = FILTER_DENY; struct filter *mfilter; struct filter_cisco *filter; struct access_list *access; struct in_addr addr; struct in_addr addr_mask; struct in_addr mask; struct in_addr mask_mask; int64_t seqnum = -1; if (seq) seqnum = (int64_t)atol(seq); /* Check of filter type. */ if (type_str) { if (strncmp(type_str, "p", 1) == 0) type = FILTER_PERMIT; else if (strncmp(type_str, "d", 1) == 0) type = FILTER_DENY; else { vty_out(vty, "%% filter type must be permit or deny\n"); return CMD_WARNING_CONFIG_FAILED; } } ret = inet_aton(addr_str, &addr); if (ret <= 0) { vty_out(vty, "%%Inconsistent address and mask\n"); return CMD_WARNING_CONFIG_FAILED; } ret = inet_aton(addr_mask_str, &addr_mask); if (ret <= 0) { vty_out(vty, "%%Inconsistent address and mask\n"); return CMD_WARNING_CONFIG_FAILED; } if (extended) { ret = inet_aton(mask_str, &mask); if (ret <= 0) { vty_out(vty, "%%Inconsistent address and mask\n"); return CMD_WARNING_CONFIG_FAILED; } ret = inet_aton(mask_mask_str, &mask_mask); if (ret <= 0) { vty_out(vty, "%%Inconsistent address and mask\n"); return CMD_WARNING_CONFIG_FAILED; } } mfilter = filter_new(); mfilter->type = type; mfilter->cisco = 1; mfilter->seq = seqnum; filter = &mfilter->u.cfilter; filter->extended = extended; filter->addr.s_addr = addr.s_addr & ~addr_mask.s_addr; filter->addr_mask.s_addr = addr_mask.s_addr; if (extended) { filter->mask.s_addr = mask.s_addr & ~mask_mask.s_addr; filter->mask_mask.s_addr = mask_mask.s_addr; } /* Install new filter to the access_list. */ access = access_list_get(AFI_IP, name_str); if (set) { if (filter_lookup_cisco(access, mfilter)) filter_free(mfilter); else access_list_filter_add(access, mfilter); } else { struct filter *delete_filter; delete_filter = filter_lookup_cisco(access, mfilter); if (delete_filter) access_list_filter_delete(access, delete_filter); filter_free(mfilter); } return CMD_SUCCESS; } /* Standard access-list */ DEFUN (access_list_standard, access_list_standard_cmd, "access-list <(1-99)|(1300-1999)> [seq (1-4294967295)] A.B.C.D A.B.C.D", "Add an access list entry\n" "IP standard access list\n" "IP standard access list (expanded range)\n" "Sequence number of an entry\n" "Sequence number\n" "Specify packets to reject\n" "Specify packets to forward\n" "Address to match\n" "Wildcard bits\n") { int idx_acl = 1; int idx = 0; char *seq = NULL; char *permit_deny = NULL; char *address = NULL; char *wildcard = NULL; argv_find(argv, argc, "(1-4294967295)", &idx); if (idx) seq = argv[idx]->arg; idx = 0; argv_find(argv, argc, "permit", &idx); argv_find(argv, argc, "deny", &idx); if (idx) permit_deny = argv[idx]->arg; idx = 0; argv_find(argv, argc, "A.B.C.D", &idx); if (idx) { address = argv[idx]->arg; wildcard = argv[idx + 1]->arg; } return filter_set_cisco(vty, argv[idx_acl]->arg, seq, permit_deny, address, wildcard, NULL, NULL, 0, 1); } DEFUN (access_list_standard_nomask, access_list_standard_nomask_cmd, "access-list <(1-99)|(1300-1999)> [seq (1-4294967295)] A.B.C.D", "Add an access list entry\n" "IP standard access list\n" "IP standard access list (expanded range)\n" "Sequence number of an entry\n" "Sequence number\n" "Specify packets to reject\n" "Specify packets to forward\n" "Address to match\n") { int idx_acl = 1; int idx = 0; char *seq = NULL; char *permit_deny = NULL; char *address = NULL; argv_find(argv, argc, "(1-4294967295)", &idx); if (idx) seq = argv[idx]->arg; idx = 0; argv_find(argv, argc, "permit", &idx); argv_find(argv, argc, "deny", &idx); if (idx) permit_deny = argv[idx]->arg; idx = 0; argv_find(argv, argc, "A.B.C.D", &idx); if (idx) address = argv[idx]->arg; return filter_set_cisco(vty, argv[idx_acl]->arg, seq, permit_deny, address, "0.0.0.0", NULL, NULL, 0, 1); } DEFUN (access_list_standard_host, access_list_standard_host_cmd, "access-list <(1-99)|(1300-1999)> [seq (1-4294967295)] host A.B.C.D", "Add an access list entry\n" "IP standard access list\n" "IP standard access list (expanded range)\n" "Sequence number of an entry\n" "Sequence number\n" "Specify packets to reject\n" "Specify packets to forward\n" "A single host address\n" "Address to match\n") { int idx_acl = 1; int idx = 0; char *seq = NULL; char *permit_deny = NULL; char *address = NULL; argv_find(argv, argc, "(1-4294967295)", &idx); if (idx) seq = argv[idx]->arg; idx = 0; argv_find(argv, argc, "permit", &idx); argv_find(argv, argc, "deny", &idx); if (idx) permit_deny = argv[idx]->arg; idx = 0; argv_find(argv, argc, "A.B.C.D", &idx); if (idx) address = argv[idx]->arg; return filter_set_cisco(vty, argv[idx_acl]->arg, seq, permit_deny, address, "0.0.0.0", NULL, NULL, 0, 1); } DEFUN (access_list_standard_any, access_list_standard_any_cmd, "access-list <(1-99)|(1300-1999)> [seq (1-4294967295)] any", "Add an access list entry\n" "IP standard access list\n" "IP standard access list (expanded range)\n" "Sequence number of an entry\n" "Sequence number\n" "Specify packets to reject\n" "Specify packets to forward\n" "Any source host\n") { int idx_acl = 1; int idx = 0; char *seq = NULL; char *permit_deny = NULL; argv_find(argv, argc, "(1-4294967295)", &idx); if (idx) seq = argv[idx]->arg; idx = 0; argv_find(argv, argc, "permit", &idx); argv_find(argv, argc, "deny", &idx); if (idx) permit_deny = argv[idx]->arg; return filter_set_cisco(vty, argv[idx_acl]->arg, seq, permit_deny, "0.0.0.0", "255.255.255.255", NULL, NULL, 0, 1); } DEFUN (no_access_list_standard, no_access_list_standard_cmd, "no access-list <(1-99)|(1300-1999)> [seq (1-4294967295)] A.B.C.D A.B.C.D", NO_STR "Add an access list entry\n" "IP standard access list\n" "IP standard access list (expanded range)\n" "Sequence number of an entry\n" "Sequence number\n" "Specify packets to reject\n" "Specify packets to forward\n" "Address to match\n" "Wildcard bits\n") { int idx_acl = 1; int idx = 0; char *seq = NULL; char *permit_deny = NULL; char *address = NULL; char *wildcard = NULL; argv_find(argv, argc, "(1-4294967295)", &idx); if (idx) seq = argv[idx]->arg; idx = 0; argv_find(argv, argc, "permit", &idx); argv_find(argv, argc, "deny", &idx); if (idx) permit_deny = argv[idx]->arg; idx = 0; argv_find(argv, argc, "A.B.C.D", &idx); if (idx) { address = argv[idx]->arg; wildcard = argv[idx + 1]->arg; } return filter_set_cisco(vty, argv[idx_acl]->arg, seq, permit_deny, address, wildcard, NULL, NULL, 0, 0); } DEFUN (no_access_list_standard_nomask, no_access_list_standard_nomask_cmd, "no access-list <(1-99)|(1300-1999)> [seq (1-4294967295)] A.B.C.D", NO_STR "Add an access list entry\n" "IP standard access list\n" "IP standard access list (expanded range)\n" "Sequence number of an entry\n" "Sequence number\n" "Specify packets to reject\n" "Specify packets to forward\n" "Address to match\n") { int idx_acl = 2; int idx = 0; char *seq = NULL; char *permit_deny = NULL; char *address = NULL; argv_find(argv, argc, "(1-4294967295)", &idx); if (idx) seq = argv[idx]->arg; idx = 0; argv_find(argv, argc, "permit", &idx); argv_find(argv, argc, "deny", &idx); if (idx) permit_deny = argv[idx]->arg; idx = 0; argv_find(argv, argc, "A.B.C.D", &idx); if (idx) address = argv[idx]->arg; return filter_set_cisco(vty, argv[idx_acl]->arg, seq, permit_deny, address, "0.0.0.0", NULL, NULL, 0, 0); } DEFUN (no_access_list_standard_host, no_access_list_standard_host_cmd, "no access-list <(1-99)|(1300-1999)> [seq (1-4294967295)] host A.B.C.D", NO_STR "Add an access list entry\n" "IP standard access list\n" "IP standard access list (expanded range)\n" "Sequence number of an entry\n" "Sequence number\n" "Specify packets to reject\n" "Specify packets to forward\n" "A single host address\n" "Address to match\n") { int idx_acl = 2; int idx = 0; char *seq = NULL; char *permit_deny = NULL; char *address = NULL; argv_find(argv, argc, "(1-4294967295)", &idx); if (idx) seq = argv[idx]->arg; idx = 0; argv_find(argv, argc, "permit", &idx); argv_find(argv, argc, "deny", &idx); if (idx) permit_deny = argv[idx]->arg; idx = 0; argv_find(argv, argc, "A.B.C.D", &idx); if (idx) address = argv[idx]->arg; return filter_set_cisco(vty, argv[idx_acl]->arg, seq, permit_deny, address, "0.0.0.0", NULL, NULL, 0, 0); } DEFUN (no_access_list_standard_any, no_access_list_standard_any_cmd, "no access-list <(1-99)|(1300-1999)> [seq (1-4294967295)] any", NO_STR "Add an access list entry\n" "IP standard access list\n" "IP standard access list (expanded range)\n" "Sequence number of an entry\n" "Sequence number\n" "Specify packets to reject\n" "Specify packets to forward\n" "Any source host\n") { int idx_acl = 2; int idx = 0; char *seq = NULL; char *permit_deny = NULL; argv_find(argv, argc, "(1-4294967295)", &idx); if (idx) seq = argv[idx]->arg; idx = 0; argv_find(argv, argc, "permit", &idx); argv_find(argv, argc, "deny", &idx); if (idx) permit_deny = argv[idx]->arg; return filter_set_cisco(vty, argv[idx_acl]->arg, seq, permit_deny, "0.0.0.0", "255.255.255.255", NULL, NULL, 0, 0); } /* Extended access-list */ DEFUN (access_list_extended, access_list_extended_cmd, "access-list <(100-199)|(2000-2699)> [seq (1-4294967295)] ip A.B.C.D A.B.C.D A.B.C.D A.B.C.D", "Add an access list entry\n" "IP extended access list\n" "IP extended access list (expanded range)\n" "Sequence number of an entry\n" "Sequence number\n" "Specify packets to reject\n" "Specify packets to forward\n" "Any Internet Protocol\n" "Source address\n" "Source wildcard bits\n" "Destination address\n" "Destination Wildcard bits\n") { int idx_acl = 1; int idx = 0; char *seq = NULL; char *permit_deny = NULL; char *src = NULL; char *dst = NULL; char *src_wildcard = NULL; char *dst_wildcard = NULL; argv_find(argv, argc, "(1-4294967295)", &idx); if (idx) seq = argv[idx]->arg; idx = 0; argv_find(argv, argc, "permit", &idx); argv_find(argv, argc, "deny", &idx); if (idx) permit_deny = argv[idx]->arg; idx = 0; argv_find(argv, argc, "A.B.C.D", &idx); if (idx) { src = argv[idx]->arg; src_wildcard = argv[idx + 1]->arg; dst = argv[idx + 2]->arg; dst_wildcard = argv[idx + 3]->arg; } return filter_set_cisco(vty, argv[idx_acl]->arg, seq, permit_deny, src, src_wildcard, dst, dst_wildcard, 1, 1); } DEFUN (access_list_extended_mask_any, access_list_extended_mask_any_cmd, "access-list <(100-199)|(2000-2699)> [seq (1-4294967295)] ip A.B.C.D A.B.C.D any", "Add an access list entry\n" "IP extended access list\n" "IP extended access list (expanded range)\n" "Sequence number of an entry\n" "Sequence number\n" "Specify packets to reject\n" "Specify packets to forward\n" "Any Internet Protocol\n" "Source address\n" "Source wildcard bits\n" "Any destination host\n") { int idx_acl = 1; int idx = 0; char *seq = NULL; char *permit_deny = NULL; char *src = NULL; char *src_wildcard = NULL; argv_find(argv, argc, "(1-4294967295)", &idx); if (idx) seq = argv[idx]->arg; idx = 0; argv_find(argv, argc, "permit", &idx); argv_find(argv, argc, "deny", &idx); if (idx) permit_deny = argv[idx]->arg; idx = 0; argv_find(argv, argc, "A.B.C.D", &idx); if (idx) { src = argv[idx]->arg; src_wildcard = argv[idx + 1]->arg; } return filter_set_cisco(vty, argv[idx_acl]->arg, seq, permit_deny, src, src_wildcard, "0.0.0.0", "255.255.255.255", 1, 1); } DEFUN (access_list_extended_any_mask, access_list_extended_any_mask_cmd, "access-list <(100-199)|(2000-2699)> [seq (1-4294967295)] ip any A.B.C.D A.B.C.D", "Add an access list entry\n" "IP extended access list\n" "IP extended access list (expanded range)\n" "Sequence number of an entry\n" "Sequence number\n" "Specify packets to reject\n" "Specify packets to forward\n" "Any Internet Protocol\n" "Any source host\n" "Destination address\n" "Destination Wildcard bits\n") { int idx_acl = 1; int idx = 0; char *seq = NULL; char *permit_deny = NULL; char *dst = NULL; char *dst_wildcard = NULL; argv_find(argv, argc, "(1-4294967295)", &idx); if (idx) seq = argv[idx]->arg; idx = 0; argv_find(argv, argc, "permit", &idx); argv_find(argv, argc, "deny", &idx); if (idx) permit_deny = argv[idx]->arg; idx = 0; argv_find(argv, argc, "A.B.C.D", &idx); if (idx) { dst = argv[idx]->arg; dst_wildcard = argv[idx + 1]->arg; } return filter_set_cisco(vty, argv[idx_acl]->arg, seq, permit_deny, "0.0.0.0", "255.255.255.255", dst, dst_wildcard, 1, 1); } DEFUN (access_list_extended_any_any, access_list_extended_any_any_cmd, "access-list <(100-199)|(2000-2699)> [seq (1-4294967295)] ip any any", "Add an access list entry\n" "IP extended access list\n" "IP extended access list (expanded range)\n" "Sequence number of an entry\n" "Sequence number\n" "Specify packets to reject\n" "Specify packets to forward\n" "Any Internet Protocol\n" "Any source host\n" "Any destination host\n") { int idx_acl = 1; int idx = 0; char *seq = NULL; char *permit_deny = NULL; argv_find(argv, argc, "(1-4294967295)", &idx); if (idx) seq = argv[idx]->arg; idx = 0; argv_find(argv, argc, "permit", &idx); argv_find(argv, argc, "deny", &idx); if (idx) permit_deny = argv[idx]->arg; return filter_set_cisco(vty, argv[idx_acl]->arg, seq, permit_deny, "0.0.0.0", "255.255.255.255", "0.0.0.0", "255.255.255.255", 1, 1); } DEFUN (access_list_extended_mask_host, access_list_extended_mask_host_cmd, "access-list <(100-199)|(2000-2699)> [seq (1-4294967295)] ip A.B.C.D A.B.C.D host A.B.C.D", "Add an access list entry\n" "IP extended access list\n" "IP extended access list (expanded range)\n" "Sequence number of an entry\n" "Sequence number\n" "Specify packets to reject\n" "Specify packets to forward\n" "Any Internet Protocol\n" "Source address\n" "Source wildcard bits\n" "A single destination host\n" "Destination address\n") { int idx_acl = 1; int idx = 0; char *seq = NULL; char *permit_deny = NULL; char *src = NULL; char *dst = NULL; char *src_wildcard = NULL; argv_find(argv, argc, "(1-4294967295)", &idx); if (idx) seq = argv[idx]->arg; idx = 0; argv_find(argv, argc, "permit", &idx); argv_find(argv, argc, "deny", &idx); if (idx) permit_deny = argv[idx]->arg; idx = 0; argv_find(argv, argc, "A.B.C.D", &idx); if (idx) { src = argv[idx]->arg; src_wildcard = argv[idx + 1]->arg; dst = argv[idx + 3]->arg; } return filter_set_cisco(vty, argv[idx_acl]->arg, seq, permit_deny, src, src_wildcard, dst, "0.0.0.0", 1, 1); } DEFUN (access_list_extended_host_mask, access_list_extended_host_mask_cmd, "access-list <(100-199)|(2000-2699)> [seq (1-4294967295)] ip host A.B.C.D A.B.C.D A.B.C.D", "Add an access list entry\n" "IP extended access list\n" "IP extended access list (expanded range)\n" "Sequence number of an entry\n" "Sequence number\n" "Specify packets to reject\n" "Specify packets to forward\n" "Any Internet Protocol\n" "A single source host\n" "Source address\n" "Destination address\n" "Destination Wildcard bits\n") { int idx_acl = 1; int idx = 0; char *seq = NULL; char *permit_deny = NULL; char *src = NULL; char *dst = NULL; char *dst_wildcard = NULL; argv_find(argv, argc, "(1-4294967295)", &idx); if (idx) seq = argv[idx]->arg; idx = 0; argv_find(argv, argc, "permit", &idx); argv_find(argv, argc, "deny", &idx); if (idx) permit_deny = argv[idx]->arg; idx = 0; argv_find(argv, argc, "A.B.C.D", &idx); if (idx) { src = argv[idx]->arg; dst = argv[idx + 1]->arg; dst_wildcard = argv[idx + 2]->arg; } return filter_set_cisco(vty, argv[idx_acl]->arg, seq, permit_deny, src, "0.0.0.0", dst, dst_wildcard, 1, 1); } DEFUN (access_list_extended_host_host, access_list_extended_host_host_cmd, "access-list <(100-199)|(2000-2699)> [seq (1-4294967295)] ip host A.B.C.D host A.B.C.D", "Add an access list entry\n" "IP extended access list\n" "IP extended access list (expanded range)\n" "Sequence number of an entry\n" "Sequence number\n" "Specify packets to reject\n" "Specify packets to forward\n" "Any Internet Protocol\n" "A single source host\n" "Source address\n" "A single destination host\n" "Destination address\n") { int idx_acl = 1; int idx = 0; char *seq = NULL; char *permit_deny = NULL; char *src = NULL; char *dst = NULL; argv_find(argv, argc, "(1-4294967295)", &idx); if (idx) seq = argv[idx]->arg; idx = 0; argv_find(argv, argc, "permit", &idx); argv_find(argv, argc, "deny", &idx); if (idx) permit_deny = argv[idx]->arg; idx = 0; argv_find(argv, argc, "A.B.C.D", &idx); if (idx) { src = argv[idx]->arg; dst = argv[idx + 2]->arg; } return filter_set_cisco(vty, argv[idx_acl]->arg, seq, permit_deny, src, "0.0.0.0", dst, "0.0.0.0", 1, 1); } DEFUN (access_list_extended_any_host, access_list_extended_any_host_cmd, "access-list <(100-199)|(2000-2699)> [seq (1-4294967295)] ip any host A.B.C.D", "Add an access list entry\n" "IP extended access list\n" "IP extended access list (expanded range)\n" "Sequence number of an entry\n" "Sequence number\n" "Specify packets to reject\n" "Specify packets to forward\n" "Any Internet Protocol\n" "Any source host\n" "A single destination host\n" "Destination address\n") { int idx_acl = 1; int idx = 0; char *seq = NULL; char *permit_deny = NULL; char *dst = NULL; argv_find(argv, argc, "(1-4294967295)", &idx); if (idx) seq = argv[idx]->arg; idx = 0; argv_find(argv, argc, "permit", &idx); argv_find(argv, argc, "deny", &idx); if (idx) permit_deny = argv[idx]->arg; idx = 0; argv_find(argv, argc, "A.B.C.D", &idx); if (idx) dst = argv[idx]->arg; return filter_set_cisco(vty, argv[idx_acl]->arg, seq, permit_deny, "0.0.0.0", "255.255.255.255", dst, "0.0.0.0", 1, 1); } DEFUN (access_list_extended_host_any, access_list_extended_host_any_cmd, "access-list <(100-199)|(2000-2699)> [seq (1-4294967295)] ip host A.B.C.D any", "Add an access list entry\n" "IP extended access list\n" "IP extended access list (expanded range)\n" "Sequence number of an entry\n" "Sequence number\n" "Specify packets to reject\n" "Specify packets to forward\n" "Any Internet Protocol\n" "A single source host\n" "Source address\n" "Any destination host\n") { int idx_acl = 1; int idx = 0; char *seq = NULL; char *permit_deny = NULL; char *src = NULL; argv_find(argv, argc, "(1-4294967295)", &idx); if (idx) seq = argv[idx]->arg; idx = 0; argv_find(argv, argc, "permit", &idx); argv_find(argv, argc, "deny", &idx); if (idx) permit_deny = argv[idx]->arg; idx = 0; argv_find(argv, argc, "A.B.C.D", &idx); if (idx) src = argv[idx]->arg; return filter_set_cisco(vty, argv[idx_acl]->arg, seq, permit_deny, src, "0.0.0.0", "0.0.0.0", "255.255.255.255", 1, 1); } DEFUN (no_access_list_extended, no_access_list_extended_cmd, "no access-list <(100-199)|(2000-2699)> [seq (1-4294967295)] ip A.B.C.D A.B.C.D A.B.C.D A.B.C.D", NO_STR "Add an access list entry\n" "IP extended access list\n" "IP extended access list (expanded range)\n" "Sequence number of an entry\n" "Sequence number\n" "Specify packets to reject\n" "Specify packets to forward\n" "Any Internet Protocol\n" "Source address\n" "Source wildcard bits\n" "Destination address\n" "Destination Wildcard bits\n") { int idx_acl = 2; int idx = 0; char *seq = NULL; char *permit_deny = NULL; char *src = NULL; char *dst = NULL; char *src_wildcard = NULL; char *dst_wildcard = NULL; argv_find(argv, argc, "(1-4294967295)", &idx); if (idx) seq = argv[idx]->arg; idx = 0; argv_find(argv, argc, "permit", &idx); argv_find(argv, argc, "deny", &idx); if (idx) permit_deny = argv[idx]->arg; idx = 0; argv_find(argv, argc, "A.B.C.D", &idx); if (idx) { src = argv[idx]->arg; src_wildcard = argv[idx + 1]->arg; dst = argv[idx + 2]->arg; dst_wildcard = argv[idx + 3]->arg; } return filter_set_cisco(vty, argv[idx_acl]->arg, seq, permit_deny, src, src_wildcard, dst, dst_wildcard, 1, 0); } DEFUN (no_access_list_extended_mask_any, no_access_list_extended_mask_any_cmd, "no access-list <(100-199)|(2000-2699)> [seq (1-4294967295)] ip A.B.C.D A.B.C.D any", NO_STR "Add an access list entry\n" "IP extended access list\n" "IP extended access list (expanded range)\n" "Sequence number of an entry\n" "Sequence number\n" "Specify packets to reject\n" "Specify packets to forward\n" "Any Internet Protocol\n" "Source address\n" "Source wildcard bits\n" "Any destination host\n") { int idx_acl = 2; int idx = 0; char *seq = NULL; char *permit_deny = NULL; char *src = NULL; char *src_wildcard = NULL; argv_find(argv, argc, "(1-4294967295)", &idx); if (idx) seq = argv[idx]->arg; idx = 0; argv_find(argv, argc, "permit", &idx); argv_find(argv, argc, "deny", &idx); if (idx) permit_deny = argv[idx]->arg; idx = 0; argv_find(argv, argc, "A.B.C.D", &idx); if (idx) { src = argv[idx]->arg; src_wildcard = argv[idx + 1]->arg; } return filter_set_cisco(vty, argv[idx_acl]->arg, seq, permit_deny, src, src_wildcard, "0.0.0.0", "255.255.255.255", 1, 0); } DEFUN (no_access_list_extended_any_mask, no_access_list_extended_any_mask_cmd, "no access-list <(100-199)|(2000-2699)> [seq (1-4294967295)] ip any A.B.C.D A.B.C.D", NO_STR "Add an access list entry\n" "IP extended access list\n" "IP extended access list (expanded range)\n" "Sequence number of an entry\n" "Sequence number\n" "Specify packets to reject\n" "Specify packets to forward\n" "Any Internet Protocol\n" "Any source host\n" "Destination address\n" "Destination Wildcard bits\n") { int idx_acl = 2; int idx = 0; char *seq = NULL; char *permit_deny = NULL; char *dst = NULL; char *dst_wildcard = NULL; argv_find(argv, argc, "(1-4294967295)", &idx); if (idx) seq = argv[idx]->arg; idx = 0; argv_find(argv, argc, "permit", &idx); argv_find(argv, argc, "deny", &idx); if (idx) permit_deny = argv[idx]->arg; idx = 0; argv_find(argv, argc, "A.B.C.D", &idx); if (idx) { dst = argv[idx]->arg; dst_wildcard = argv[idx + 1]->arg; } return filter_set_cisco(vty, argv[idx_acl]->arg, seq, permit_deny, "0.0.0.0", "255.255.255.255", dst, dst_wildcard, 1, 0); } DEFUN (no_access_list_extended_any_any, no_access_list_extended_any_any_cmd, "no access-list <(100-199)|(2000-2699)> [seq (1-4294967295)] ip any any", NO_STR "Add an access list entry\n" "IP extended access list\n" "IP extended access list (expanded range)\n" "Sequence number of an entry\n" "Sequence number\n" "Specify packets to reject\n" "Specify packets to forward\n" "Any Internet Protocol\n" "Any source host\n" "Any destination host\n") { int idx_acl = 2; int idx = 0; char *seq = NULL; char *permit_deny = NULL; argv_find(argv, argc, "(1-4294967295)", &idx); if (idx) seq = argv[idx]->arg; idx = 0; argv_find(argv, argc, "permit", &idx); argv_find(argv, argc, "deny", &idx); if (idx) permit_deny = argv[idx]->arg; return filter_set_cisco(vty, argv[idx_acl]->arg, seq, permit_deny, "0.0.0.0", "255.255.255.255", "0.0.0.0", "255.255.255.255", 1, 0); } DEFUN (no_access_list_extended_mask_host, no_access_list_extended_mask_host_cmd, "no access-list <(100-199)|(2000-2699)> [seq (1-4294967295)] ip A.B.C.D A.B.C.D host A.B.C.D", NO_STR "Add an access list entry\n" "IP extended access list\n" "IP extended access list (expanded range)\n" "Sequence number of an entry\n" "Sequence number\n" "Specify packets to reject\n" "Specify packets to forward\n" "Any Internet Protocol\n" "Source address\n" "Source wildcard bits\n" "A single destination host\n" "Destination address\n") { int idx_acl = 2; int idx = 0; char *seq = NULL; char *permit_deny = NULL; char *src = NULL; char *dst = NULL; char *src_wildcard = NULL; argv_find(argv, argc, "(1-4294967295)", &idx); if (idx) seq = argv[idx]->arg; idx = 0; argv_find(argv, argc, "permit", &idx); argv_find(argv, argc, "deny", &idx); if (idx) permit_deny = argv[idx]->arg; idx = 0; argv_find(argv, argc, "A.B.C.D", &idx); if (idx) { src = argv[idx]->arg; src_wildcard = argv[idx + 1]->arg; dst = argv[idx + 3]->arg; } return filter_set_cisco(vty, argv[idx_acl]->arg, seq, permit_deny, src, src_wildcard, dst, "0.0.0.0", 1, 0); } DEFUN (no_access_list_extended_host_mask, no_access_list_extended_host_mask_cmd, "no access-list <(100-199)|(2000-2699)> [seq (1-4294967295)] ip host A.B.C.D A.B.C.D A.B.C.D", NO_STR "Add an access list entry\n" "IP extended access list\n" "IP extended access list (expanded range)\n" "Sequence number of an entry\n" "Sequence number\n" "Specify packets to reject\n" "Specify packets to forward\n" "Any Internet Protocol\n" "A single source host\n" "Source address\n" "Destination address\n" "Destination Wildcard bits\n") { int idx_acl = 2; int idx = 0; char *seq = NULL; char *permit_deny = NULL; char *src = NULL; char *dst = NULL; char *dst_wildcard = NULL; argv_find(argv, argc, "(1-4294967295)", &idx); if (idx) seq = argv[idx]->arg; idx = 0; argv_find(argv, argc, "permit", &idx); argv_find(argv, argc, "deny", &idx); if (idx) permit_deny = argv[idx]->arg; idx = 0; argv_find(argv, argc, "A.B.C.D", &idx); if (idx) { src = argv[idx]->arg; dst = argv[idx + 1]->arg; dst_wildcard = argv[idx + 2]->arg; } return filter_set_cisco(vty, argv[idx_acl]->arg, seq, permit_deny, src, "0.0.0.0", dst, dst_wildcard, 1, 0); } DEFUN (no_access_list_extended_host_host, no_access_list_extended_host_host_cmd, "no access-list <(100-199)|(2000-2699)> [seq (1-4294967295)] ip host A.B.C.D host A.B.C.D", NO_STR "Add an access list entry\n" "IP extended access list\n" "IP extended access list (expanded range)\n" "Sequence number of an entry\n" "Sequence number\n" "Specify packets to reject\n" "Specify packets to forward\n" "Any Internet Protocol\n" "A single source host\n" "Source address\n" "A single destination host\n" "Destination address\n") { int idx_acl = 2; int idx = 0; char *seq = NULL; char *permit_deny = NULL; char *src = NULL; char *dst = NULL; argv_find(argv, argc, "(1-4294967295)", &idx); if (idx) seq = argv[idx]->arg; idx = 0; argv_find(argv, argc, "permit", &idx); argv_find(argv, argc, "deny", &idx); if (idx) permit_deny = argv[idx]->arg; idx = 0; argv_find(argv, argc, "A.B.C.D", &idx); if (idx) { src = argv[idx]->arg; dst = argv[idx + 2]->arg; } return filter_set_cisco(vty, argv[idx_acl]->arg, seq, permit_deny, src, "0.0.0.0", dst, "0.0.0.0", 1, 0); } DEFUN (no_access_list_extended_any_host, no_access_list_extended_any_host_cmd, "no access-list <(100-199)|(2000-2699)> [seq (1-4294967295)] ip any host A.B.C.D", NO_STR "Add an access list entry\n" "IP extended access list\n" "IP extended access list (expanded range)\n" "Sequence number of an entry\n" "Sequence number\n" "Specify packets to reject\n" "Specify packets to forward\n" "Any Internet Protocol\n" "Any source host\n" "A single destination host\n" "Destination address\n") { int idx_acl = 2; int idx = 0; char *seq = NULL; char *permit_deny = NULL; char *dst = NULL; argv_find(argv, argc, "(1-4294967295)", &idx); if (idx) seq = argv[idx]->arg; idx = 0; argv_find(argv, argc, "permit", &idx); argv_find(argv, argc, "deny", &idx); if (idx) permit_deny = argv[idx]->arg; idx = 0; argv_find(argv, argc, "A.B.C.D", &idx); if (idx) dst = argv[idx]->arg; return filter_set_cisco(vty, argv[idx_acl]->arg, seq, permit_deny, "0.0.0.0", "255.255.255.255", dst, "0.0.0.0", 1, 0); } DEFUN (no_access_list_extended_host_any, no_access_list_extended_host_any_cmd, "no access-list <(100-199)|(2000-2699)> [seq (1-4294967295)] ip host A.B.C.D any", NO_STR "Add an access list entry\n" "IP extended access list\n" "IP extended access list (expanded range)\n" "Sequence number of an entry\n" "Sequence number\n" "Specify packets to reject\n" "Specify packets to forward\n" "Any Internet Protocol\n" "A single source host\n" "Source address\n" "Any destination host\n") { int idx_acl = 2; int idx = 0; char *seq = NULL; char *permit_deny = NULL; char *src = NULL; argv_find(argv, argc, "(1-4294967295)", &idx); if (idx) seq = argv[idx]->arg; idx = 0; argv_find(argv, argc, "permit", &idx); argv_find(argv, argc, "deny", &idx); if (idx) permit_deny = argv[idx]->arg; idx = 0; argv_find(argv, argc, "A.B.C.D", &idx); if (idx) src = argv[idx]->arg; return filter_set_cisco(vty, argv[idx_acl]->arg, seq, permit_deny, src, "0.0.0.0", "0.0.0.0", "255.255.255.255", 1, 0); } static int filter_set_zebra(struct vty *vty, const char *name_str, const char *seq, const char *type_str, afi_t afi, const char *prefix_str, int exact, int set) { int ret; enum filter_type type = FILTER_DENY; struct filter *mfilter; struct filter_zebra *filter; struct access_list *access; struct prefix p; int64_t seqnum = -1; if (strlen(name_str) > ACL_NAMSIZ) { vty_out(vty, "%% ACL name %s is invalid: length exceeds " "%d characters\n", name_str, ACL_NAMSIZ); return CMD_WARNING_CONFIG_FAILED; } if (seq) seqnum = (int64_t)atol(seq); /* Check of filter type. */ if (type_str) { if (strncmp(type_str, "p", 1) == 0) type = FILTER_PERMIT; else if (strncmp(type_str, "d", 1) == 0) type = FILTER_DENY; else { vty_out(vty, "filter type must be [permit|deny]\n"); return CMD_WARNING_CONFIG_FAILED; } } /* Check string format of prefix and prefixlen. */ if (afi == AFI_IP) { ret = str2prefix_ipv4(prefix_str, (struct prefix_ipv4 *)&p); if (ret <= 0) { vty_out(vty, "IP address prefix/prefixlen is malformed\n"); return CMD_WARNING_CONFIG_FAILED; } } else if (afi == AFI_IP6) { ret = str2prefix_ipv6(prefix_str, (struct prefix_ipv6 *)&p); if (ret <= 0) { vty_out(vty, "IPv6 address prefix/prefixlen is malformed\n"); return CMD_WARNING_CONFIG_FAILED; } } else if (afi == AFI_L2VPN) { ret = str2prefix_eth(prefix_str, (struct prefix_eth *)&p); if (ret <= 0) { vty_out(vty, "MAC address is malformed\n"); return CMD_WARNING; } } else return CMD_WARNING_CONFIG_FAILED; mfilter = filter_new(); mfilter->type = type; mfilter->seq = seqnum; filter = &mfilter->u.zfilter; prefix_copy(&filter->prefix, &p); /* "exact-match" */ if (exact) filter->exact = 1; /* Install new filter to the access_list. */ access = access_list_get(afi, name_str); if (set) { if (filter_lookup_zebra(access, mfilter)) filter_free(mfilter); else access_list_filter_add(access, mfilter); } else { struct filter *delete_filter; delete_filter = filter_lookup_zebra(access, mfilter); if (delete_filter) access_list_filter_delete(access, delete_filter); filter_free(mfilter); } return CMD_SUCCESS; } DEFUN (mac_access_list, mac_access_list_cmd, "mac access-list WORD [seq (1-4294967295)] X:X:X:X:X:X", "Add a mac access-list\n" "Add an access list entry\n" "MAC zebra access-list name\n" "Sequence number of an entry\n" "Sequence number\n" "Specify packets to reject\n" "Specify packets to forward\n" "MAC address to match. e.g. 00:01:00:01:00:01\n") { int idx = 0; char *seq = NULL; char *permit_deny = NULL; char *mac = NULL; argv_find(argv, argc, "(1-4294967295)", &idx); if (idx) seq = argv[idx]->arg; idx = 0; argv_find(argv, argc, "permit", &idx); argv_find(argv, argc, "deny", &idx); if (idx) permit_deny = argv[idx]->arg; idx = 0; argv_find(argv, argc, "X:X:X:X:X:X", &idx); if (idx) mac = argv[idx]->arg; return filter_set_zebra(vty, argv[2]->arg, seq, permit_deny, AFI_L2VPN, mac, 0, 1); } DEFUN (no_mac_access_list, no_mac_access_list_cmd, "no mac access-list WORD [seq (1-4294967295)] X:X:X:X:X:X", NO_STR "Remove a mac access-list\n" "Remove an access list entry\n" "MAC zebra access-list name\n" "Sequence number of an entry\n" "Sequence number\n" "Specify packets to reject\n" "Specify packets to forward\n" "MAC address to match. e.g. 00:01:00:01:00:01\n") { int idx = 0; char *seq = NULL; char *permit_deny = NULL; char *mac = NULL; argv_find(argv, argc, "(1-4294967295)", &idx); if (idx) seq = argv[idx]->arg; idx = 0; argv_find(argv, argc, "permit", &idx); argv_find(argv, argc, "deny", &idx); if (idx) permit_deny = argv[idx]->arg; idx = 0; argv_find(argv, argc, "X:X:X:X:X:X", &idx); if (idx) mac = argv[idx]->arg; return filter_set_zebra(vty, argv[2]->arg, seq, permit_deny, AFI_L2VPN, mac, 0, 0); } DEFUN (mac_access_list_any, mac_access_list_any_cmd, "mac access-list WORD [seq (1-4294967295)] any", "Add a mac access-list\n" "Add an access list entry\n" "MAC zebra access-list name\n" "Sequence number of an entry\n" "Sequence number\n" "Specify packets to reject\n" "Specify packets to forward\n" "MAC address to match. e.g. 00:01:00:01:00:01\n") { int idx = 0; char *seq = NULL; char *permit_deny = NULL; argv_find(argv, argc, "(1-4294967295)", &idx); if (idx) seq = argv[idx]->arg; idx = 0; argv_find(argv, argc, "permit", &idx); argv_find(argv, argc, "deny", &idx); if (idx) permit_deny = argv[idx]->arg; return filter_set_zebra(vty, argv[2]->arg, seq, permit_deny, AFI_L2VPN, "00:00:00:00:00:00", 0, 1); } DEFUN (no_mac_access_list_any, no_mac_access_list_any_cmd, "no mac access-list WORD [seq (1-4294967295)] any", NO_STR "Remove a mac access-list\n" "Remove an access list entry\n" "MAC zebra access-list name\n" "Sequence number of an entry\n" "Sequence number\n" "Specify packets to reject\n" "Specify packets to forward\n" "MAC address to match. e.g. 00:01:00:01:00:01\n") { int idx = 0; char *seq = NULL; char *permit_deny = NULL; argv_find(argv, argc, "(1-4294967295)", &idx); if (idx) seq = argv[idx]->arg; idx = 0; argv_find(argv, argc, "permit", &idx); argv_find(argv, argc, "deny", &idx); if (idx) permit_deny = argv[idx]->arg; return filter_set_zebra(vty, argv[2]->arg, seq, permit_deny, AFI_L2VPN, "00:00:00:00:00:00", 0, 0); } DEFUN (access_list_exact, access_list_exact_cmd, "access-list WORD [seq (1-4294967295)] A.B.C.D/M [exact-match]", "Add an access list entry\n" "IP zebra access-list name\n" "Sequence number of an entry\n" "Sequence number\n" "Specify packets to reject\n" "Specify packets to forward\n" "Prefix to match. e.g. 10.0.0.0/8\n" "Exact match of the prefixes\n") { int idx = 0; int exact = 0; char *seq = NULL; char *permit_deny = NULL; char *prefix = NULL; argv_find(argv, argc, "(1-4294967295)", &idx); if (idx) seq = argv[idx]->arg; idx = 0; argv_find(argv, argc, "permit", &idx); argv_find(argv, argc, "deny", &idx); if (idx) permit_deny = argv[idx]->arg; idx = 0; argv_find(argv, argc, "A.B.C.D/M", &idx); if (idx) prefix = argv[idx]->arg; idx = 0; if (argv_find(argv, argc, "exact-match", &idx)) exact = 1; return filter_set_zebra(vty, argv[1]->arg, seq, permit_deny, AFI_IP, prefix, exact, 1); } DEFUN (access_list_any, access_list_any_cmd, "access-list WORD [seq (1-4294967295)] any", "Add an access list entry\n" "IP zebra access-list name\n" "Sequence number of an entry\n" "Sequence number\n" "Specify packets to reject\n" "Specify packets to forward\n" "Prefix to match. e.g. 10.0.0.0/8\n") { int idx_word = 1; int idx = 0; char *seq = NULL; char *permit_deny = NULL; argv_find(argv, argc, "(1-4294967295)", &idx); if (idx) seq = argv[idx]->arg; idx = 0; argv_find(argv, argc, "permit", &idx); argv_find(argv, argc, "deny", &idx); if (idx) permit_deny = argv[idx]->arg; return filter_set_zebra(vty, argv[idx_word]->arg, seq, permit_deny, AFI_IP, "0.0.0.0/0", 0, 1); } DEFUN (no_access_list_exact, no_access_list_exact_cmd, "no access-list WORD [seq (1-4294967295)] A.B.C.D/M [exact-match]", NO_STR "Add an access list entry\n" "IP zebra access-list name\n" "Sequence number of an entry\n" "Sequence number\n" "Specify packets to reject\n" "Specify packets to forward\n" "Prefix to match. e.g. 10.0.0.0/8\n" "Exact match of the prefixes\n") { int idx = 0; int exact = 0; char *seq = NULL; char *permit_deny = NULL; char *prefix = NULL; argv_find(argv, argc, "(1-4294967295)", &idx); if (idx) seq = argv[idx]->arg; idx = 0; argv_find(argv, argc, "permit", &idx); argv_find(argv, argc, "deny", &idx); if (idx) permit_deny = argv[idx]->arg; idx = 0; argv_find(argv, argc, "A.B.C.D/M", &idx); if (idx) prefix = argv[idx]->arg; idx = 0; if (argv_find(argv, argc, "exact-match", &idx)) exact = 1; return filter_set_zebra(vty, argv[2]->arg, seq, permit_deny, AFI_IP, prefix, exact, 0); } DEFUN (no_access_list_any, no_access_list_any_cmd, "no access-list WORD [seq (1-4294967295)] any", NO_STR "Add an access list entry\n" "IP zebra access-list name\n" "Sequence number of an entry\n" "Sequence number\n" "Specify packets to reject\n" "Specify packets to forward\n" "Prefix to match. e.g. 10.0.0.0/8\n") { int idx_word = 1; int idx = 0; char *seq = NULL; char *permit_deny = NULL; argv_find(argv, argc, "(1-4294967295)", &idx); if (idx) seq = argv[idx]->arg; idx = 0; argv_find(argv, argc, "permit", &idx); argv_find(argv, argc, "deny", &idx); if (idx) permit_deny = argv[idx]->arg; return filter_set_zebra(vty, argv[idx_word]->arg, seq, permit_deny, AFI_IP, "0.0.0.0/0", 0, 0); } DEFUN (no_access_list_all, no_access_list_all_cmd, "no access-list <(1-99)|(100-199)|(1300-1999)|(2000-2699)|WORD>", NO_STR "Add an access list entry\n" "IP standard access list\n" "IP extended access list\n" "IP standard access list (expanded range)\n" "IP extended access list (expanded range)\n" "IP zebra access-list name\n") { int idx_acl = 2; struct access_list *access; struct access_master *master; /* Looking up access_list. */ access = access_list_lookup(AFI_IP, argv[idx_acl]->arg); if (access == NULL) { vty_out(vty, "%% access-list %s doesn't exist\n", argv[idx_acl]->arg); return CMD_WARNING_CONFIG_FAILED; } master = access->master; route_map_notify_dependencies(access->name, RMAP_EVENT_FILTER_DELETED); /* Run hook function. */ if (master->delete_hook) (*master->delete_hook)(access); /* Delete all filter from access-list. */ access_list_delete(access); return CMD_SUCCESS; } DEFUN (access_list_remark, access_list_remark_cmd, "access-list <(1-99)|(100-199)|(1300-1999)|(2000-2699)|WORD> remark LINE...", "Add an access list entry\n" "IP standard access list\n" "IP extended access list\n" "IP standard access list (expanded range)\n" "IP extended access list (expanded range)\n" "IP zebra access-list\n" "Access list entry comment\n" "Comment up to 100 characters\n") { int idx_acl = 1; int idx_remark = 3; struct access_list *access; access = access_list_get(AFI_IP, argv[idx_acl]->arg); if (access->remark) { XFREE(MTYPE_TMP, access->remark); access->remark = NULL; } access->remark = argv_concat(argv, argc, idx_remark); return CMD_SUCCESS; } DEFUN (no_access_list_remark, no_access_list_remark_cmd, "no access-list <(1-99)|(100-199)|(1300-1999)|(2000-2699)|WORD> remark", NO_STR "Add an access list entry\n" "IP standard access list\n" "IP extended access list\n" "IP standard access list (expanded range)\n" "IP extended access list (expanded range)\n" "IP zebra access-list\n" "Access list entry comment\n") { int idx_acl = 2; return vty_access_list_remark_unset(vty, AFI_IP, argv[idx_acl]->arg); } /* ALIAS_FIXME */ DEFUN (no_access_list_remark_comment, no_access_list_remark_comment_cmd, "no access-list <(1-99)|(100-199)|(1300-1999)|(2000-2699)|WORD> remark LINE...", NO_STR "Add an access list entry\n" "IP standard access list\n" "IP extended access list\n" "IP standard access list (expanded range)\n" "IP extended access list (expanded range)\n" "IP zebra access-list\n" "Access list entry comment\n" "Comment up to 100 characters\n") { return no_access_list_remark(self, vty, argc, argv); } DEFUN (ipv6_access_list_exact, ipv6_access_list_exact_cmd, "ipv6 access-list WORD [seq (1-4294967295)] X:X::X:X/M [exact-match]", IPV6_STR "Add an access list entry\n" "IPv6 zebra access-list\n" "Sequence number of an entry\n" "Sequence number\n" "Specify packets to reject\n" "Specify packets to forward\n" "IPv6 prefix\n" "Exact match of the prefixes\n") { int idx = 0; int exact = 0; int idx_word = 2; char *seq = NULL; char *permit_deny = NULL; char *prefix = NULL; argv_find(argv, argc, "(1-4294967295)", &idx); if (idx) seq = argv[idx]->arg; idx = 0; argv_find(argv, argc, "permit", &idx); argv_find(argv, argc, "deny", &idx); if (idx) permit_deny = argv[idx]->arg; idx = 0; argv_find(argv, argc, "X:X::X:X/M", &idx); if (idx) prefix = argv[idx]->arg; idx = 0; if (argv_find(argv, argc, "exact-match", &idx)) exact = 1; return filter_set_zebra(vty, argv[idx_word]->arg, seq, permit_deny, AFI_IP6, prefix, exact, 1); } DEFUN (ipv6_access_list_any, ipv6_access_list_any_cmd, "ipv6 access-list WORD [seq (1-4294967295)] any", IPV6_STR "Add an access list entry\n" "IPv6 zebra access-list\n" "Sequence number of an entry\n" "Sequence number\n" "Specify packets to reject\n" "Specify packets to forward\n" "Any prefixi to match\n") { int idx_word = 2; int idx = 0; char *seq = NULL; char *permit_deny = NULL; argv_find(argv, argc, "(1-4294967295)", &idx); if (idx) seq = argv[idx]->arg; idx = 0; argv_find(argv, argc, "permit", &idx); argv_find(argv, argc, "deny", &idx); if (idx) permit_deny = argv[idx]->arg; return filter_set_zebra(vty, argv[idx_word]->arg, seq, permit_deny, AFI_IP6, "::/0", 0, 1); } DEFUN (no_ipv6_access_list_exact, no_ipv6_access_list_exact_cmd, "no ipv6 access-list WORD [seq (1-4294967295)] X:X::X:X/M [exact-match]", NO_STR IPV6_STR "Add an access list entry\n" "IPv6 zebra access-list\n" "Sequence number of an entry\n" "Sequence number\n" "Specify packets to reject\n" "Specify packets to forward\n" "Prefix to match. e.g. 3ffe:506::/32\n" "Exact match of the prefixes\n") { int idx = 0; int exact = 0; int idx_word = 2; char *seq = NULL; char *permit_deny = NULL; char *prefix = NULL; argv_find(argv, argc, "(1-4294967295)", &idx); if (idx) seq = argv[idx]->arg; idx = 0; argv_find(argv, argc, "permit", &idx); argv_find(argv, argc, "deny", &idx); if (idx) permit_deny = argv[idx]->arg; idx = 0; argv_find(argv, argc, "X:X::X:X/M", &idx); if (idx) prefix = argv[idx]->arg; idx = 0; if (argv_find(argv, argc, "exact-match", &idx)) exact = 1; return filter_set_zebra(vty, argv[idx_word]->arg, seq, permit_deny, AFI_IP6, prefix, exact, 0); } DEFUN (no_ipv6_access_list_any, no_ipv6_access_list_any_cmd, "no ipv6 access-list WORD [seq (1-4294967295)] any", NO_STR IPV6_STR "Add an access list entry\n" "IPv6 zebra access-list\n" "Sequence number of an entry\n" "Sequence number\n" "Specify packets to reject\n" "Specify packets to forward\n" "Any prefixi to match\n") { int idx_word = 2; int idx = 0; char *seq = NULL; char *permit_deny = NULL; argv_find(argv, argc, "(1-4294967295)", &idx); if (idx) seq = argv[idx]->arg; idx = 0; argv_find(argv, argc, "permit", &idx); argv_find(argv, argc, "deny", &idx); if (idx) permit_deny = argv[idx]->arg; return filter_set_zebra(vty, argv[idx_word]->arg, seq, permit_deny, AFI_IP6, "::/0", 0, 0); } DEFUN (no_ipv6_access_list_all, no_ipv6_access_list_all_cmd, "no ipv6 access-list WORD", NO_STR IPV6_STR "Add an access list entry\n" "IPv6 zebra access-list\n") { int idx_word = 3; struct access_list *access; struct access_master *master; /* Looking up access_list. */ access = access_list_lookup(AFI_IP6, argv[idx_word]->arg); if (access == NULL) { vty_out(vty, "%% access-list %s doesn't exist\n", argv[idx_word]->arg); return CMD_WARNING_CONFIG_FAILED; } master = access->master; route_map_notify_dependencies(access->name, RMAP_EVENT_FILTER_DELETED); /* Run hook function. */ if (master->delete_hook) (*master->delete_hook)(access); /* Delete all filter from access-list. */ access_list_delete(access); return CMD_SUCCESS; } DEFUN (ipv6_access_list_remark, ipv6_access_list_remark_cmd, "ipv6 access-list WORD remark LINE...", IPV6_STR "Add an access list entry\n" "IPv6 zebra access-list\n" "Access list entry comment\n" "Comment up to 100 characters\n") { int idx_word = 2; int idx_line = 4; struct access_list *access; access = access_list_get(AFI_IP6, argv[idx_word]->arg); if (access->remark) { XFREE(MTYPE_TMP, access->remark); access->remark = NULL; } access->remark = argv_concat(argv, argc, idx_line); return CMD_SUCCESS; } DEFUN (no_ipv6_access_list_remark, no_ipv6_access_list_remark_cmd, "no ipv6 access-list WORD remark", NO_STR IPV6_STR "Add an access list entry\n" "IPv6 zebra access-list\n" "Access list entry comment\n") { int idx_word = 3; return vty_access_list_remark_unset(vty, AFI_IP6, argv[idx_word]->arg); } /* ALIAS_FIXME */ DEFUN (no_ipv6_access_list_remark_comment, no_ipv6_access_list_remark_comment_cmd, "no ipv6 access-list WORD remark LINE...", NO_STR IPV6_STR "Add an access list entry\n" "IPv6 zebra access-list\n" "Access list entry comment\n" "Comment up to 100 characters\n") { return no_ipv6_access_list_remark(self, vty, argc, argv); } void config_write_access_zebra(struct vty *, struct filter *); void config_write_access_cisco(struct vty *, struct filter *); /* show access-list command. */ static int filter_show(struct vty *vty, const char *name, afi_t afi) { struct access_list *access; struct access_master *master; struct filter *mfilter; struct filter_cisco *filter; int write = 0; master = access_master_get(afi); if (master == NULL) return 0; /* Print the name of the protocol */ vty_out(vty, "%s:\n", frr_protoname); for (access = master->num.head; access; access = access->next) { if (name && strcmp(access->name, name) != 0) continue; write = 1; for (mfilter = access->head; mfilter; mfilter = mfilter->next) { filter = &mfilter->u.cfilter; if (write) { vty_out(vty, "%s %s access list %s\n", mfilter->cisco ? (filter->extended ? "Extended" : "Standard") : "Zebra", (afi == AFI_IP) ? ("IP") : ((afi == AFI_IP6) ? ("IPv6 ") : ("MAC ")), access->name); write = 0; } vty_out(vty, " seq %" PRId64, mfilter->seq); vty_out(vty, " %s%s", filter_type_str(mfilter), mfilter->type == FILTER_DENY ? " " : ""); if (!mfilter->cisco) config_write_access_zebra(vty, mfilter); else if (filter->extended) config_write_access_cisco(vty, mfilter); else { if (filter->addr_mask.s_addr == 0xffffffff) vty_out(vty, " any\n"); else { vty_out(vty, " %s", inet_ntoa(filter->addr)); if (filter->addr_mask.s_addr != 0) vty_out(vty, ", wildcard bits %s", inet_ntoa( filter->addr_mask)); vty_out(vty, "\n"); } } } } for (access = master->str.head; access; access = access->next) { if (name && strcmp(access->name, name) != 0) continue; write = 1; for (mfilter = access->head; mfilter; mfilter = mfilter->next) { filter = &mfilter->u.cfilter; if (write) { vty_out(vty, "%s %s access list %s\n", mfilter->cisco ? (filter->extended ? "Extended" : "Standard") : "Zebra", (afi == AFI_IP) ? ("IP") : ((afi == AFI_IP6) ? ("IPv6 ") : ("MAC ")), access->name); write = 0; } vty_out(vty, " seq %" PRId64, mfilter->seq); vty_out(vty, " %s%s", filter_type_str(mfilter), mfilter->type == FILTER_DENY ? " " : ""); if (!mfilter->cisco) config_write_access_zebra(vty, mfilter); else if (filter->extended) config_write_access_cisco(vty, mfilter); else { if (filter->addr_mask.s_addr == 0xffffffff) vty_out(vty, " any\n"); else { vty_out(vty, " %s", inet_ntoa(filter->addr)); if (filter->addr_mask.s_addr != 0) vty_out(vty, ", wildcard bits %s", inet_ntoa( filter->addr_mask)); vty_out(vty, "\n"); } } } } return CMD_SUCCESS; } /* show MAC access list - this only has MAC filters for now*/ DEFUN (show_mac_access_list, show_mac_access_list_cmd, "show mac access-list", SHOW_STR "mac access lists\n" "List mac access lists\n") { return filter_show(vty, NULL, AFI_L2VPN); } DEFUN (show_mac_access_list_name, show_mac_access_list_name_cmd, "show mac access-list WORD", SHOW_STR "mac access lists\n" "List mac access lists\n" "mac address\n") { return filter_show(vty, argv[3]->arg, AFI_L2VPN); } DEFUN (show_ip_access_list, show_ip_access_list_cmd, "show ip access-list", SHOW_STR IP_STR "List IP access lists\n") { return filter_show(vty, NULL, AFI_IP); } DEFUN (show_ip_access_list_name, show_ip_access_list_name_cmd, "show ip access-list <(1-99)|(100-199)|(1300-1999)|(2000-2699)|WORD>", SHOW_STR IP_STR "List IP access lists\n" "IP standard access list\n" "IP extended access list\n" "IP standard access list (expanded range)\n" "IP extended access list (expanded range)\n" "IP zebra access-list\n") { int idx_acl = 3; return filter_show(vty, argv[idx_acl]->arg, AFI_IP); } DEFUN (show_ipv6_access_list, show_ipv6_access_list_cmd, "show ipv6 access-list", SHOW_STR IPV6_STR "List IPv6 access lists\n") { return filter_show(vty, NULL, AFI_IP6); } DEFUN (show_ipv6_access_list_name, show_ipv6_access_list_name_cmd, "show ipv6 access-list WORD", SHOW_STR IPV6_STR "List IPv6 access lists\n" "IPv6 zebra access-list\n") { int idx_word = 3; return filter_show(vty, argv[idx_word]->arg, AFI_IP6); } void config_write_access_cisco(struct vty *vty, struct filter *mfilter) { struct filter_cisco *filter; filter = &mfilter->u.cfilter; if (filter->extended) { vty_out(vty, " ip"); if (filter->addr_mask.s_addr == 0xffffffff) vty_out(vty, " any"); else if (filter->addr_mask.s_addr == 0) vty_out(vty, " host %s", inet_ntoa(filter->addr)); else { vty_out(vty, " %s", inet_ntoa(filter->addr)); vty_out(vty, " %s", inet_ntoa(filter->addr_mask)); } if (filter->mask_mask.s_addr == 0xffffffff) vty_out(vty, " any"); else if (filter->mask_mask.s_addr == 0) vty_out(vty, " host %s", inet_ntoa(filter->mask)); else { vty_out(vty, " %s", inet_ntoa(filter->mask)); vty_out(vty, " %s", inet_ntoa(filter->mask_mask)); } vty_out(vty, "\n"); } else { if (filter->addr_mask.s_addr == 0xffffffff) vty_out(vty, " any\n"); else { vty_out(vty, " %s", inet_ntoa(filter->addr)); if (filter->addr_mask.s_addr != 0) vty_out(vty, " %s", inet_ntoa(filter->addr_mask)); vty_out(vty, "\n"); } } } void config_write_access_zebra(struct vty *vty, struct filter *mfilter) { struct filter_zebra *filter; struct prefix *p; char buf[BUFSIZ]; filter = &mfilter->u.zfilter; p = &filter->prefix; if (p->prefixlen == 0 && !filter->exact) vty_out(vty, " any"); else if (p->family == AF_INET6 || p->family == AF_INET) vty_out(vty, " %s/%d%s", inet_ntop(p->family, &p->u.prefix, buf, BUFSIZ), p->prefixlen, filter->exact ? " exact-match" : ""); else if (p->family == AF_ETHERNET) { if (p->prefixlen == 0) vty_out(vty, " any"); else vty_out(vty, " %s", prefix_mac2str(&(p->u.prefix_eth), buf, sizeof(buf))); } vty_out(vty, "\n"); } static int config_write_access(struct vty *vty, afi_t afi) { struct access_list *access; struct access_master *master; struct filter *mfilter; int write = 0; master = access_master_get(afi); if (master == NULL) return 0; for (access = master->num.head; access; access = access->next) { if (access->remark) { vty_out(vty, "%saccess-list %s remark %s\n", (afi == AFI_IP) ? ("") : ((afi == AFI_IP6) ? ("ipv6 ") : ("mac ")), access->name, access->remark); write++; } for (mfilter = access->head; mfilter; mfilter = mfilter->next) { vty_out(vty, "%saccess-list %s seq %" PRId64 " %s", (afi == AFI_IP) ? ("") : ((afi == AFI_IP6) ? ("ipv6 ") : ("mac ")), access->name, mfilter->seq, filter_type_str(mfilter)); if (mfilter->cisco) config_write_access_cisco(vty, mfilter); else config_write_access_zebra(vty, mfilter); write++; } } for (access = master->str.head; access; access = access->next) { if (access->remark) { vty_out(vty, "%saccess-list %s remark %s\n", (afi == AFI_IP) ? ("") : ((afi == AFI_IP6) ? ("ipv6 ") : ("mac ")), access->name, access->remark); write++; } for (mfilter = access->head; mfilter; mfilter = mfilter->next) { vty_out(vty, "%saccess-list %s seq %" PRId64 " %s", (afi == AFI_IP) ? ("") : ((afi == AFI_IP6) ? ("ipv6 ") : ("mac ")), access->name, mfilter->seq, filter_type_str(mfilter)); if (mfilter->cisco) config_write_access_cisco(vty, mfilter); else config_write_access_zebra(vty, mfilter); write++; } } return write; } static struct cmd_node access_mac_node = { ACCESS_MAC_NODE, "", /* Access list has no interface. */ 1}; static int config_write_access_mac(struct vty *vty) { return config_write_access(vty, AFI_L2VPN); } static void access_list_reset_mac(void) { struct access_list *access; struct access_list *next; struct access_master *master; master = access_master_get(AFI_L2VPN); if (master == NULL) return; for (access = master->num.head; access; access = next) { next = access->next; access_list_delete(access); } for (access = master->str.head; access; access = next) { next = access->next; access_list_delete(access); } assert(master->num.head == NULL); assert(master->num.tail == NULL); assert(master->str.head == NULL); assert(master->str.tail == NULL); } /* Install vty related command. */ static void access_list_init_mac(void) { install_node(&access_mac_node, config_write_access_mac); install_element(ENABLE_NODE, &show_mac_access_list_cmd); install_element(ENABLE_NODE, &show_mac_access_list_name_cmd); /* Zebra access-list */ install_element(CONFIG_NODE, &mac_access_list_cmd); install_element(CONFIG_NODE, &no_mac_access_list_cmd); install_element(CONFIG_NODE, &mac_access_list_any_cmd); install_element(CONFIG_NODE, &no_mac_access_list_any_cmd); } /* Access-list node. */ static struct cmd_node access_node = {ACCESS_NODE, "", /* Access list has no interface. */ 1}; static int config_write_access_ipv4(struct vty *vty) { return config_write_access(vty, AFI_IP); } static void access_list_reset_ipv4(void) { struct access_list *access; struct access_list *next; struct access_master *master; master = access_master_get(AFI_IP); if (master == NULL) return; for (access = master->num.head; access; access = next) { next = access->next; access_list_delete(access); } for (access = master->str.head; access; access = next) { next = access->next; access_list_delete(access); } assert(master->num.head == NULL); assert(master->num.tail == NULL); assert(master->str.head == NULL); assert(master->str.tail == NULL); } /* Install vty related command. */ static void access_list_init_ipv4(void) { install_node(&access_node, config_write_access_ipv4); install_element(ENABLE_NODE, &show_ip_access_list_cmd); install_element(ENABLE_NODE, &show_ip_access_list_name_cmd); /* Zebra access-list */ install_element(CONFIG_NODE, &access_list_exact_cmd); install_element(CONFIG_NODE, &access_list_any_cmd); install_element(CONFIG_NODE, &no_access_list_exact_cmd); install_element(CONFIG_NODE, &no_access_list_any_cmd); /* Standard access-list */ install_element(CONFIG_NODE, &access_list_standard_cmd); install_element(CONFIG_NODE, &access_list_standard_nomask_cmd); install_element(CONFIG_NODE, &access_list_standard_host_cmd); install_element(CONFIG_NODE, &access_list_standard_any_cmd); install_element(CONFIG_NODE, &no_access_list_standard_cmd); install_element(CONFIG_NODE, &no_access_list_standard_nomask_cmd); install_element(CONFIG_NODE, &no_access_list_standard_host_cmd); install_element(CONFIG_NODE, &no_access_list_standard_any_cmd); /* Extended access-list */ install_element(CONFIG_NODE, &access_list_extended_cmd); install_element(CONFIG_NODE, &access_list_extended_any_mask_cmd); install_element(CONFIG_NODE, &access_list_extended_mask_any_cmd); install_element(CONFIG_NODE, &access_list_extended_any_any_cmd); install_element(CONFIG_NODE, &access_list_extended_host_mask_cmd); install_element(CONFIG_NODE, &access_list_extended_mask_host_cmd); install_element(CONFIG_NODE, &access_list_extended_host_host_cmd); install_element(CONFIG_NODE, &access_list_extended_any_host_cmd); install_element(CONFIG_NODE, &access_list_extended_host_any_cmd); install_element(CONFIG_NODE, &no_access_list_extended_cmd); install_element(CONFIG_NODE, &no_access_list_extended_any_mask_cmd); install_element(CONFIG_NODE, &no_access_list_extended_mask_any_cmd); install_element(CONFIG_NODE, &no_access_list_extended_any_any_cmd); install_element(CONFIG_NODE, &no_access_list_extended_host_mask_cmd); install_element(CONFIG_NODE, &no_access_list_extended_mask_host_cmd); install_element(CONFIG_NODE, &no_access_list_extended_host_host_cmd); install_element(CONFIG_NODE, &no_access_list_extended_any_host_cmd); install_element(CONFIG_NODE, &no_access_list_extended_host_any_cmd); install_element(CONFIG_NODE, &access_list_remark_cmd); install_element(CONFIG_NODE, &no_access_list_all_cmd); install_element(CONFIG_NODE, &no_access_list_remark_cmd); install_element(CONFIG_NODE, &no_access_list_remark_comment_cmd); } static struct cmd_node access_ipv6_node = {ACCESS_IPV6_NODE, "", 1}; static int config_write_access_ipv6(struct vty *vty) { return config_write_access(vty, AFI_IP6); } static void access_list_reset_ipv6(void) { struct access_list *access; struct access_list *next; struct access_master *master; master = access_master_get(AFI_IP6); if (master == NULL) return; for (access = master->num.head; access; access = next) { next = access->next; access_list_delete(access); } for (access = master->str.head; access; access = next) { next = access->next; access_list_delete(access); } assert(master->num.head == NULL); assert(master->num.tail == NULL); assert(master->str.head == NULL); assert(master->str.tail == NULL); } static void access_list_init_ipv6(void) { install_node(&access_ipv6_node, config_write_access_ipv6); install_element(ENABLE_NODE, &show_ipv6_access_list_cmd); install_element(ENABLE_NODE, &show_ipv6_access_list_name_cmd); install_element(CONFIG_NODE, &ipv6_access_list_exact_cmd); install_element(CONFIG_NODE, &ipv6_access_list_any_cmd); install_element(CONFIG_NODE, &no_ipv6_access_list_exact_cmd); install_element(CONFIG_NODE, &no_ipv6_access_list_any_cmd); install_element(CONFIG_NODE, &no_ipv6_access_list_all_cmd); install_element(CONFIG_NODE, &ipv6_access_list_remark_cmd); install_element(CONFIG_NODE, &no_ipv6_access_list_remark_cmd); install_element(CONFIG_NODE, &no_ipv6_access_list_remark_comment_cmd); } void access_list_init(void) { access_list_init_ipv4(); access_list_init_ipv6(); access_list_init_mac(); } void access_list_reset(void) { access_list_reset_ipv4(); access_list_reset_ipv6(); access_list_reset_mac(); } frr-7.2.1/lib/filter.h0000644000000000000000000000376013610377563011426 00000000000000/* * Route filtering function. * Copyright (C) 1998 Kunihiro Ishiguro * * This file is part of GNU Zebra. * * GNU Zebra is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License as published * by the Free Software Foundation; either version 2, or (at your * option) any later version. * * GNU Zebra 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; see the file COPYING; if not, write to the Free Software * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA */ #ifndef _ZEBRA_FILTER_H #define _ZEBRA_FILTER_H #include "if.h" #ifdef __cplusplus extern "C" { #endif /* Maximum ACL name length */ #define ACL_NAMSIZ 128 /* Filter direction. */ #define FILTER_IN 0 #define FILTER_OUT 1 #define FILTER_MAX 2 /* Filter type is made by `permit', `deny' and `dynamic'. */ enum filter_type { FILTER_DENY, FILTER_PERMIT, FILTER_DYNAMIC }; enum access_type { ACCESS_TYPE_STRING, ACCESS_TYPE_NUMBER }; /* Access list */ struct access_list { char *name; char *remark; struct access_master *master; enum access_type type; struct access_list *next; struct access_list *prev; struct filter *head; struct filter *tail; }; /* Prototypes for access-list. */ extern void access_list_init(void); extern void access_list_reset(void); extern void access_list_add_hook(void (*func)(struct access_list *)); extern void access_list_delete_hook(void (*func)(struct access_list *)); extern struct access_list *access_list_lookup(afi_t, const char *); extern enum filter_type access_list_apply(struct access_list *access, const void *object); #ifdef __cplusplus } #endif #endif /* _ZEBRA_FILTER_H */ frr-7.2.1/lib/freebsd-queue.h0000644000000000000000000010031713610377563012671 00000000000000/*- * Copyright (c) 1991, 1993 * The Regents of the University of California. All rights reserved. * * Redistribution and use in source and binary forms, with or without * modification, are permitted provided that the following conditions * are met: * 1. Redistributions of source code must retain the above copyright * notice, this list of conditions and the following disclaimer. * 2. Redistributions in binary form must reproduce the above copyright * notice, this list of conditions and the following disclaimer in the * documentation and/or other materials provided with the distribution. * 4. Neither the name of the University nor the names of its contributors * may be used to endorse or promote products derived from this software * without specific prior written permission. * * THIS SOFTWARE IS PROVIDED BY THE REGENTS AND CONTRIBUTORS ``AS IS'' AND * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE * ARE DISCLAIMED. IN NO EVENT SHALL THE REGENTS OR CONTRIBUTORS BE LIABLE * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF * SUCH DAMAGE. * * @(#)queue.h 8.5 (Berkeley) 8/20/94 * $FreeBSD$ */ #ifndef _SYS_QUEUE_H_ #define _SYS_QUEUE_H_ #ifdef __cplusplus extern "C" { #endif /* * This file defines four types of data structures: singly-linked lists, * singly-linked tail queues, lists and tail queues. * * A singly-linked list is headed by a single forward pointer. The elements * are singly linked for minimum space and pointer manipulation overhead at * the expense of O(n) removal for arbitrary elements. New elements can be * added to the list after an existing element or at the head of the list. * Elements being removed from the head of the list should use the explicit * macro for this purpose for optimum efficiency. A singly-linked list may * only be traversed in the forward direction. Singly-linked lists are ideal * for applications with large datasets and few or no removals or for * implementing a LIFO queue. * * A singly-linked tail queue is headed by a pair of pointers, one to the * head of the list and the other to the tail of the list. The elements are * singly linked for minimum space and pointer manipulation overhead at the * expense of O(n) removal for arbitrary elements. New elements can be added * to the list after an existing element, at the head of the list, or at the * end of the list. Elements being removed from the head of the tail queue * should use the explicit macro for this purpose for optimum efficiency. * A singly-linked tail queue may only be traversed in the forward direction. * Singly-linked tail queues are ideal for applications with large datasets * and few or no removals or for implementing a FIFO queue. * * A list is headed by a single forward pointer (or an array of forward * pointers for a hash table header). The elements are doubly linked * so that an arbitrary element can be removed without a need to * traverse the list. New elements can be added to the list before * or after an existing element or at the head of the list. A list * may only be traversed in the forward direction. * * A tail queue is headed by a pair of pointers, one to the head of the * list and the other to the tail of the list. The elements are doubly * linked so that an arbitrary element can be removed without a need to * traverse the list. New elements can be added to the list before or * after an existing element, at the head of the list, or at the end of * the list. A tail queue may be traversed in either direction. * * For details on the use of these macros, see the queue(3) manual page. * * * SLIST LIST STAILQ TAILQ * _HEAD + + + + * _HEAD_INITIALIZER + + + + * _ENTRY + + + + * _INIT + + + + * _EMPTY + + + + * _FIRST + + + + * _NEXT + + + + * _PREV - - - + * _LAST - - + + * _FOREACH + + + + * _FOREACH_SAFE + + + + * _FOREACH_REVERSE - - - + * _FOREACH_REVERSE_SAFE - - - + * _INSERT_HEAD + + + + * _INSERT_BEFORE - + - + * _INSERT_AFTER + + + + * _INSERT_TAIL - - + + * _CONCAT - - + + * _REMOVE_AFTER + - + - * _REMOVE_HEAD + - + - * _REMOVE + + + + * _SWAP + + + + * */ #ifdef QUEUE_MACRO_DEBUG /* Store the last 2 places the queue element or head was altered */ struct qm_trace { char *lastfile; int lastline; char *prevfile; int prevline; }; #define TRACEBUF struct qm_trace trace; #define TRASHIT(x) do {(x) = (void *)-1;} while (0) #define QMD_SAVELINK(name, link) void **name = (void *)&(link) #define QMD_TRACE_HEAD(head) \ do { \ (head)->trace.prevline = (head)->trace.lastline; \ (head)->trace.prevfile = (head)->trace.lastfile; \ (head)->trace.lastline = __LINE__; \ (head)->trace.lastfile = __FILE__; \ } while (0) #define QMD_TRACE_ELEM(elem) \ do { \ (elem)->trace.prevline = (elem)->trace.lastline; \ (elem)->trace.prevfile = (elem)->trace.lastfile; \ (elem)->trace.lastline = __LINE__; \ (elem)->trace.lastfile = __FILE__; \ } while (0) #else #define QMD_TRACE_ELEM(elem) #define QMD_TRACE_HEAD(head) #define QMD_SAVELINK(name, link) #define TRACEBUF #define TRASHIT(x) #endif /* QUEUE_MACRO_DEBUG */ /* * Singly-linked List declarations. */ #define SLIST_HEAD(name, type) \ struct name { \ struct type *slh_first; /* first element */ \ } #define SLIST_HEAD_INITIALIZER(head) \ { \ NULL \ } #define SLIST_ENTRY(type) \ struct { \ struct type *sle_next; /* next element */ \ } /* * Singly-linked List functions. */ #define SLIST_EMPTY(head) ((head)->slh_first == NULL) #define SLIST_FIRST(head) ((head)->slh_first) #define SLIST_FOREACH(var, head, field) \ for ((var) = SLIST_FIRST((head)); (var); \ (var) = SLIST_NEXT((var), field)) #define SLIST_FOREACH_SAFE(var, head, field, tvar) \ for ((var) = SLIST_FIRST((head)); \ (var) && ((tvar) = SLIST_NEXT((var), field), 1); (var) = (tvar)) #define SLIST_FOREACH_PREVPTR(var, varp, head, field) \ for ((varp) = &SLIST_FIRST((head)); ((var) = *(varp)) != NULL; \ (varp) = &SLIST_NEXT((var), field)) #define SLIST_INIT(head) \ do { \ SLIST_FIRST((head)) = NULL; \ } while (0) #define SLIST_INSERT_AFTER(slistelm, elm, field) \ do { \ SLIST_NEXT((elm), field) = SLIST_NEXT((slistelm), field); \ SLIST_NEXT((slistelm), field) = (elm); \ } while (0) #define SLIST_INSERT_HEAD(head, elm, field) \ do { \ SLIST_NEXT((elm), field) = SLIST_FIRST((head)); \ SLIST_FIRST((head)) = (elm); \ } while (0) #define SLIST_NEXT(elm, field) ((elm)->field.sle_next) #define SLIST_REMOVE(head, elm, type, field) \ do { \ QMD_SAVELINK(oldnext, (elm)->field.sle_next); \ if (SLIST_FIRST((head)) == (elm)) { \ SLIST_REMOVE_HEAD((head), field); \ } else { \ struct type *curelm = SLIST_FIRST((head)); \ while (SLIST_NEXT(curelm, field) != (elm)) \ curelm = SLIST_NEXT(curelm, field); \ SLIST_REMOVE_AFTER(curelm, field); \ } \ TRASHIT(*oldnext); \ } while (0) #define SLIST_REMOVE_AFTER(elm, field) \ do { \ SLIST_NEXT(elm, field) = \ SLIST_NEXT(SLIST_NEXT(elm, field), field); \ } while (0) #define SLIST_REMOVE_HEAD(head, field) \ do { \ SLIST_FIRST((head)) = SLIST_NEXT(SLIST_FIRST((head)), field); \ } while (0) #define SLIST_SWAP(head1, head2, type) \ do { \ struct type *swap_first = SLIST_FIRST(head1); \ SLIST_FIRST(head1) = SLIST_FIRST(head2); \ SLIST_FIRST(head2) = swap_first; \ } while (0) /* * Singly-linked Tail queue declarations. */ #define STAILQ_HEAD(name, type) \ struct name { \ struct type *stqh_first; /* first element */ \ struct type **stqh_last; /* addr of last next element */ \ } #define STAILQ_HEAD_INITIALIZER(head) \ { \ NULL, &(head).stqh_first \ } #define STAILQ_ENTRY(type) \ struct { \ struct type *stqe_next; /* next element */ \ } /* * Singly-linked Tail queue functions. */ #define STAILQ_CONCAT(head1, head2) \ do { \ if (!STAILQ_EMPTY((head2))) { \ *(head1)->stqh_last = (head2)->stqh_first; \ (head1)->stqh_last = (head2)->stqh_last; \ STAILQ_INIT((head2)); \ } \ } while (0) #define STAILQ_EMPTY(head) ((head)->stqh_first == NULL) #define STAILQ_FIRST(head) ((head)->stqh_first) #define STAILQ_FOREACH(var, head, field) \ for ((var) = STAILQ_FIRST((head)); (var); \ (var) = STAILQ_NEXT((var), field)) #define STAILQ_FOREACH_SAFE(var, head, field, tvar) \ for ((var) = STAILQ_FIRST((head)); \ (var) && ((tvar) = STAILQ_NEXT((var), field), 1); (var) = (tvar)) #define STAILQ_INIT(head) \ do { \ STAILQ_FIRST((head)) = NULL; \ (head)->stqh_last = &STAILQ_FIRST((head)); \ } while (0) #define STAILQ_INSERT_AFTER(head, tqelm, elm, field) \ do { \ if ((STAILQ_NEXT((elm), field) = STAILQ_NEXT((tqelm), field)) \ == NULL) \ (head)->stqh_last = &STAILQ_NEXT((elm), field); \ STAILQ_NEXT((tqelm), field) = (elm); \ } while (0) #define STAILQ_INSERT_HEAD(head, elm, field) \ do { \ if ((STAILQ_NEXT((elm), field) = STAILQ_FIRST((head))) \ == NULL) \ (head)->stqh_last = &STAILQ_NEXT((elm), field); \ STAILQ_FIRST((head)) = (elm); \ } while (0) #define STAILQ_INSERT_TAIL(head, elm, field) \ do { \ STAILQ_NEXT((elm), field) = NULL; \ *(head)->stqh_last = (elm); \ (head)->stqh_last = &STAILQ_NEXT((elm), field); \ } while (0) #define STAILQ_LAST(head, type, field) \ (STAILQ_EMPTY((head)) \ ? NULL \ : ((struct type *)(void *)((char *)((head)->stqh_last) \ - offsetof(struct type, field)))) #define STAILQ_NEXT(elm, field) ((elm)->field.stqe_next) #define STAILQ_REMOVE(head, elm, type, field) \ do { \ QMD_SAVELINK(oldnext, (elm)->field.stqe_next); \ if (STAILQ_FIRST((head)) == (elm)) { \ STAILQ_REMOVE_HEAD((head), field); \ } else { \ struct type *curelm = STAILQ_FIRST((head)); \ while (STAILQ_NEXT(curelm, field) != (elm)) \ curelm = STAILQ_NEXT(curelm, field); \ STAILQ_REMOVE_AFTER(head, curelm, field); \ } \ TRASHIT(*oldnext); \ } while (0) #define STAILQ_REMOVE_AFTER(head, elm, field) \ do { \ if ((STAILQ_NEXT(elm, field) = \ STAILQ_NEXT(STAILQ_NEXT(elm, field), field)) \ == NULL) \ (head)->stqh_last = &STAILQ_NEXT((elm), field); \ } while (0) #define STAILQ_REMOVE_HEAD(head, field) \ do { \ if ((STAILQ_FIRST((head)) = \ STAILQ_NEXT(STAILQ_FIRST((head)), field)) \ == NULL) \ (head)->stqh_last = &STAILQ_FIRST((head)); \ } while (0) #define STAILQ_SWAP(head1, head2, type) \ do { \ struct type *swap_first = STAILQ_FIRST(head1); \ struct type **swap_last = (head1)->stqh_last; \ STAILQ_FIRST(head1) = STAILQ_FIRST(head2); \ (head1)->stqh_last = (head2)->stqh_last; \ STAILQ_FIRST(head2) = swap_first; \ (head2)->stqh_last = swap_last; \ if (STAILQ_EMPTY(head1)) \ (head1)->stqh_last = &STAILQ_FIRST(head1); \ if (STAILQ_EMPTY(head2)) \ (head2)->stqh_last = &STAILQ_FIRST(head2); \ } while (0) /* * List declarations. */ #define LIST_HEAD(name, type) \ struct name { \ struct type *lh_first; /* first element */ \ } #define LIST_HEAD_INITIALIZER(head) \ { \ NULL \ } #define LIST_ENTRY(type) \ struct { \ struct type *le_next; /* next element */ \ struct type **le_prev; /* address of previous next element */ \ } /* * List functions. */ #if (defined(_KERNEL) && defined(INVARIANTS)) #define QMD_LIST_CHECK_HEAD(head, field) \ do { \ if (LIST_FIRST((head)) != NULL \ && LIST_FIRST((head))->field.le_prev \ != &LIST_FIRST((head))) \ panic("Bad list head %p first->prev != head", (head)); \ } while (0) #define QMD_LIST_CHECK_NEXT(elm, field) \ do { \ if (LIST_NEXT((elm), field) != NULL \ && LIST_NEXT((elm), field)->field.le_prev \ != &((elm)->field.le_next)) \ panic("Bad link elm %p next->prev != elm", (elm)); \ } while (0) #define QMD_LIST_CHECK_PREV(elm, field) \ do { \ if (*(elm)->field.le_prev != (elm)) \ panic("Bad link elm %p prev->next != elm", (elm)); \ } while (0) #else #define QMD_LIST_CHECK_HEAD(head, field) #define QMD_LIST_CHECK_NEXT(elm, field) #define QMD_LIST_CHECK_PREV(elm, field) #endif /* (_KERNEL && INVARIANTS) */ #define LIST_EMPTY(head) ((head)->lh_first == NULL) #define LIST_FIRST(head) ((head)->lh_first) #define LIST_FOREACH(var, head, field) \ for ((var) = LIST_FIRST((head)); (var); (var) = LIST_NEXT((var), field)) #define LIST_FOREACH_SAFE(var, head, field, tvar) \ for ((var) = LIST_FIRST((head)); \ (var) && ((tvar) = LIST_NEXT((var), field), 1); (var) = (tvar)) #define LIST_INIT(head) \ do { \ LIST_FIRST((head)) = NULL; \ } while (0) #define LIST_INSERT_AFTER(listelm, elm, field) \ do { \ QMD_LIST_CHECK_NEXT(listelm, field); \ if ((LIST_NEXT((elm), field) = LIST_NEXT((listelm), field)) \ != NULL) \ LIST_NEXT((listelm), field)->field.le_prev = \ &LIST_NEXT((elm), field); \ LIST_NEXT((listelm), field) = (elm); \ (elm)->field.le_prev = &LIST_NEXT((listelm), field); \ } while (0) #define LIST_INSERT_BEFORE(listelm, elm, field) \ do { \ QMD_LIST_CHECK_PREV(listelm, field); \ (elm)->field.le_prev = (listelm)->field.le_prev; \ LIST_NEXT((elm), field) = (listelm); \ *(listelm)->field.le_prev = (elm); \ (listelm)->field.le_prev = &LIST_NEXT((elm), field); \ } while (0) #define LIST_INSERT_HEAD(head, elm, field) \ do { \ QMD_LIST_CHECK_HEAD((head), field); \ if ((LIST_NEXT((elm), field) = LIST_FIRST((head))) != NULL) \ LIST_FIRST((head))->field.le_prev = \ &LIST_NEXT((elm), field); \ LIST_FIRST((head)) = (elm); \ (elm)->field.le_prev = &LIST_FIRST((head)); \ } while (0) #define LIST_NEXT(elm, field) ((elm)->field.le_next) #define LIST_REMOVE(elm, field) \ do { \ QMD_SAVELINK(oldnext, (elm)->field.le_next); \ QMD_SAVELINK(oldprev, (elm)->field.le_prev); \ QMD_LIST_CHECK_NEXT(elm, field); \ QMD_LIST_CHECK_PREV(elm, field); \ if (LIST_NEXT((elm), field) != NULL) \ LIST_NEXT((elm), field)->field.le_prev = \ (elm)->field.le_prev; \ *(elm)->field.le_prev = LIST_NEXT((elm), field); \ TRASHIT(*oldnext); \ TRASHIT(*oldprev); \ } while (0) #define LIST_SWAP(head1, head2, type, field) \ do { \ struct type *swap_tmp = LIST_FIRST((head1)); \ LIST_FIRST((head1)) = LIST_FIRST((head2)); \ LIST_FIRST((head2)) = swap_tmp; \ if ((swap_tmp = LIST_FIRST((head1))) != NULL) \ swap_tmp->field.le_prev = &LIST_FIRST((head1)); \ if ((swap_tmp = LIST_FIRST((head2))) != NULL) \ swap_tmp->field.le_prev = &LIST_FIRST((head2)); \ } while (0) /* * Tail queue declarations. */ #define TAILQ_HEAD(name, type) \ struct name { \ struct type *tqh_first; /* first element */ \ struct type **tqh_last; /* addr of last next element */ \ TRACEBUF \ } #define TAILQ_HEAD_INITIALIZER(head) \ { \ NULL, &(head).tqh_first \ } #define TAILQ_ENTRY(type) \ struct { \ struct type *tqe_next; /* next element */ \ struct type **tqe_prev; /* address of previous next element */ \ TRACEBUF \ } /* * Tail queue functions. */ #if (defined(_KERNEL) && defined(INVARIANTS)) #define QMD_TAILQ_CHECK_HEAD(head, field) \ do { \ if (!TAILQ_EMPTY(head) \ && TAILQ_FIRST((head))->field.tqe_prev \ != &TAILQ_FIRST((head))) \ panic("Bad tailq head %p first->prev != head", \ (head)); \ } while (0) #define QMD_TAILQ_CHECK_TAIL(head, field) \ do { \ if (*(head)->tqh_last != NULL) \ panic("Bad tailq NEXT(%p->tqh_last) != NULL", (head)); \ } while (0) #define QMD_TAILQ_CHECK_NEXT(elm, field) \ do { \ if (TAILQ_NEXT((elm), field) != NULL \ && TAILQ_NEXT((elm), field)->field.tqe_prev \ != &((elm)->field.tqe_next)) \ panic("Bad link elm %p next->prev != elm", (elm)); \ } while (0) #define QMD_TAILQ_CHECK_PREV(elm, field) \ do { \ if (*(elm)->field.tqe_prev != (elm)) \ panic("Bad link elm %p prev->next != elm", (elm)); \ } while (0) #else #define QMD_TAILQ_CHECK_HEAD(head, field) #define QMD_TAILQ_CHECK_TAIL(head, headname) #define QMD_TAILQ_CHECK_NEXT(elm, field) #define QMD_TAILQ_CHECK_PREV(elm, field) #endif /* (_KERNEL && INVARIANTS) */ #define TAILQ_CONCAT(head1, head2, field) \ do { \ if (!TAILQ_EMPTY(head2)) { \ *(head1)->tqh_last = (head2)->tqh_first; \ (head2)->tqh_first->field.tqe_prev = \ (head1)->tqh_last; \ (head1)->tqh_last = (head2)->tqh_last; \ TAILQ_INIT((head2)); \ QMD_TRACE_HEAD(head1); \ QMD_TRACE_HEAD(head2); \ } \ } while (0) #define TAILQ_EMPTY(head) ((head)->tqh_first == NULL) #define TAILQ_FIRST(head) ((head)->tqh_first) #define TAILQ_FOREACH(var, head, field) \ for ((var) = TAILQ_FIRST((head)); (var); \ (var) = TAILQ_NEXT((var), field)) #define TAILQ_FOREACH_SAFE(var, head, field, tvar) \ for ((var) = TAILQ_FIRST((head)); \ (var) && ((tvar) = TAILQ_NEXT((var), field), 1); (var) = (tvar)) #define TAILQ_FOREACH_REVERSE(var, head, headname, field) \ for ((var) = TAILQ_LAST((head), headname); (var); \ (var) = TAILQ_PREV((var), headname, field)) #define TAILQ_FOREACH_REVERSE_SAFE(var, head, headname, field, tvar) \ for ((var) = TAILQ_LAST((head), headname); \ (var) && ((tvar) = TAILQ_PREV((var), headname, field), 1); \ (var) = (tvar)) #define TAILQ_INIT(head) \ do { \ TAILQ_FIRST((head)) = NULL; \ (head)->tqh_last = &TAILQ_FIRST((head)); \ QMD_TRACE_HEAD(head); \ } while (0) #define TAILQ_INSERT_AFTER(head, listelm, elm, field) \ do { \ QMD_TAILQ_CHECK_NEXT(listelm, field); \ if ((TAILQ_NEXT((elm), field) = TAILQ_NEXT((listelm), field)) \ != NULL) \ TAILQ_NEXT((elm), field)->field.tqe_prev = \ &TAILQ_NEXT((elm), field); \ else { \ (head)->tqh_last = &TAILQ_NEXT((elm), field); \ QMD_TRACE_HEAD(head); \ } \ TAILQ_NEXT((listelm), field) = (elm); \ (elm)->field.tqe_prev = &TAILQ_NEXT((listelm), field); \ QMD_TRACE_ELEM(&(elm)->field); \ QMD_TRACE_ELEM(&listelm->field); \ } while (0) #define TAILQ_INSERT_BEFORE(listelm, elm, field) \ do { \ QMD_TAILQ_CHECK_PREV(listelm, field); \ (elm)->field.tqe_prev = (listelm)->field.tqe_prev; \ TAILQ_NEXT((elm), field) = (listelm); \ *(listelm)->field.tqe_prev = (elm); \ (listelm)->field.tqe_prev = &TAILQ_NEXT((elm), field); \ QMD_TRACE_ELEM(&(elm)->field); \ QMD_TRACE_ELEM(&listelm->field); \ } while (0) #define TAILQ_INSERT_HEAD(head, elm, field) \ do { \ QMD_TAILQ_CHECK_HEAD(head, field); \ if ((TAILQ_NEXT((elm), field) = TAILQ_FIRST((head))) != NULL) \ TAILQ_FIRST((head))->field.tqe_prev = \ &TAILQ_NEXT((elm), field); \ else \ (head)->tqh_last = &TAILQ_NEXT((elm), field); \ TAILQ_FIRST((head)) = (elm); \ (elm)->field.tqe_prev = &TAILQ_FIRST((head)); \ QMD_TRACE_HEAD(head); \ QMD_TRACE_ELEM(&(elm)->field); \ } while (0) #define TAILQ_INSERT_TAIL(head, elm, field) \ do { \ QMD_TAILQ_CHECK_TAIL(head, field); \ TAILQ_NEXT((elm), field) = NULL; \ (elm)->field.tqe_prev = (head)->tqh_last; \ *(head)->tqh_last = (elm); \ (head)->tqh_last = &TAILQ_NEXT((elm), field); \ QMD_TRACE_HEAD(head); \ QMD_TRACE_ELEM(&(elm)->field); \ } while (0) #define TAILQ_LAST(head, headname) \ (*(((struct headname *)((head)->tqh_last))->tqh_last)) #define TAILQ_NEXT(elm, field) ((elm)->field.tqe_next) #define TAILQ_PREV(elm, headname, field) \ (*(((struct headname *)((elm)->field.tqe_prev))->tqh_last)) #define TAILQ_REMOVE(head, elm, field) \ do { \ QMD_SAVELINK(oldnext, (elm)->field.tqe_next); \ QMD_SAVELINK(oldprev, (elm)->field.tqe_prev); \ QMD_TAILQ_CHECK_NEXT(elm, field); \ QMD_TAILQ_CHECK_PREV(elm, field); \ if ((TAILQ_NEXT((elm), field)) != NULL) \ TAILQ_NEXT((elm), field)->field.tqe_prev = \ (elm)->field.tqe_prev; \ else { \ (head)->tqh_last = (elm)->field.tqe_prev; \ QMD_TRACE_HEAD(head); \ } \ *(elm)->field.tqe_prev = TAILQ_NEXT((elm), field); \ TRASHIT(*oldnext); \ TRASHIT(*oldprev); \ QMD_TRACE_ELEM(&(elm)->field); \ } while (0) #define TAILQ_SWAP(head1, head2, type, field) \ do { \ struct type *swap_first = (head1)->tqh_first; \ struct type **swap_last = (head1)->tqh_last; \ (head1)->tqh_first = (head2)->tqh_first; \ (head1)->tqh_last = (head2)->tqh_last; \ (head2)->tqh_first = swap_first; \ (head2)->tqh_last = swap_last; \ if ((swap_first = (head1)->tqh_first) != NULL) \ swap_first->field.tqe_prev = &(head1)->tqh_first; \ else \ (head1)->tqh_last = &(head1)->tqh_first; \ if ((swap_first = (head2)->tqh_first) != NULL) \ swap_first->field.tqe_prev = &(head2)->tqh_first; \ else \ (head2)->tqh_last = &(head2)->tqh_first; \ } while (0) #ifdef __cplusplus } #endif #endif /* !_SYS_QUEUE_H_ */ frr-7.2.1/lib/frr_pthread.c0000644000000000000000000001665713610377563012445 00000000000000/* * Utilities and interfaces for managing POSIX threads within FRR. * Copyright (C) 2017 Cumulus Networks, Inc. * * This program is free software; you can redistribute it and/or modify * it under the terms of the 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; see the file COPYING; if not, write to the Free Software * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA */ #include #include #ifdef HAVE_PTHREAD_NP_H #include #endif #include #include "frr_pthread.h" #include "memory.h" #include "linklist.h" DEFINE_MTYPE_STATIC(LIB, FRR_PTHREAD, "FRR POSIX Thread") DEFINE_MTYPE_STATIC(LIB, PTHREAD_PRIM, "POSIX sync primitives") /* default frr_pthread start/stop routine prototypes */ static void *fpt_run(void *arg); static int fpt_halt(struct frr_pthread *fpt, void **res); /* default frr_pthread attributes */ struct frr_pthread_attr frr_pthread_attr_default = { .start = fpt_run, .stop = fpt_halt, }; /* list to keep track of all frr_pthreads */ static pthread_mutex_t frr_pthread_list_mtx = PTHREAD_MUTEX_INITIALIZER; static struct list *frr_pthread_list; /* ------------------------------------------------------------------------ */ void frr_pthread_init(void) { frr_with_mutex(&frr_pthread_list_mtx) { frr_pthread_list = list_new(); frr_pthread_list->del = (void (*)(void *))&frr_pthread_destroy; } } void frr_pthread_finish(void) { frr_with_mutex(&frr_pthread_list_mtx) { list_delete(&frr_pthread_list); } } struct frr_pthread *frr_pthread_new(struct frr_pthread_attr *attr, const char *name, const char *os_name) { struct frr_pthread *fpt = NULL; attr = attr ? attr : &frr_pthread_attr_default; fpt = XCALLOC(MTYPE_FRR_PTHREAD, sizeof(struct frr_pthread)); /* initialize mutex */ pthread_mutex_init(&fpt->mtx, NULL); /* create new thread master */ fpt->master = thread_master_create(name); /* set attributes */ fpt->attr = *attr; name = (name ? name : "Anonymous thread"); fpt->name = XSTRDUP(MTYPE_FRR_PTHREAD, name); if (os_name) strlcpy(fpt->os_name, os_name, OS_THREAD_NAMELEN); else strlcpy(fpt->os_name, name, OS_THREAD_NAMELEN); /* initialize startup synchronization primitives */ fpt->running_cond_mtx = XCALLOC( MTYPE_PTHREAD_PRIM, sizeof(pthread_mutex_t)); fpt->running_cond = XCALLOC(MTYPE_PTHREAD_PRIM, sizeof(pthread_cond_t)); pthread_mutex_init(fpt->running_cond_mtx, NULL); pthread_cond_init(fpt->running_cond, NULL); frr_with_mutex(&frr_pthread_list_mtx) { listnode_add(frr_pthread_list, fpt); } return fpt; } void frr_pthread_destroy(struct frr_pthread *fpt) { thread_master_free(fpt->master); pthread_mutex_destroy(&fpt->mtx); pthread_mutex_destroy(fpt->running_cond_mtx); pthread_cond_destroy(fpt->running_cond); XFREE(MTYPE_FRR_PTHREAD, fpt->name); XFREE(MTYPE_PTHREAD_PRIM, fpt->running_cond_mtx); XFREE(MTYPE_PTHREAD_PRIM, fpt->running_cond); XFREE(MTYPE_FRR_PTHREAD, fpt); } int frr_pthread_set_name(struct frr_pthread *fpt) { int ret = 0; #ifdef HAVE_PTHREAD_SETNAME_NP # ifdef GNU_LINUX ret = pthread_setname_np(fpt->thread, fpt->os_name); # elif defined(__NetBSD__) ret = pthread_setname_np(fpt->thread, fpt->os_name, NULL); # endif #elif defined(HAVE_PTHREAD_SET_NAME_NP) pthread_set_name_np(fpt->thread, fpt->os_name); #endif return ret; } static void *frr_pthread_inner(void *arg) { struct frr_pthread *fpt = arg; rcu_thread_start(fpt->rcu_thread); return fpt->attr.start(fpt); } int frr_pthread_run(struct frr_pthread *fpt, const pthread_attr_t *attr) { int ret; fpt->rcu_thread = rcu_thread_prepare(); ret = pthread_create(&fpt->thread, attr, frr_pthread_inner, fpt); /* * Per pthread_create(3), the contents of fpt->thread are undefined if * pthread_create() did not succeed. Reset this value to zero. */ if (ret < 0) { rcu_thread_unprepare(fpt->rcu_thread); memset(&fpt->thread, 0x00, sizeof(fpt->thread)); } return ret; } void frr_pthread_wait_running(struct frr_pthread *fpt) { frr_with_mutex(fpt->running_cond_mtx) { while (!fpt->running) pthread_cond_wait(fpt->running_cond, fpt->running_cond_mtx); } } void frr_pthread_notify_running(struct frr_pthread *fpt) { frr_with_mutex(fpt->running_cond_mtx) { fpt->running = true; pthread_cond_signal(fpt->running_cond); } } int frr_pthread_stop(struct frr_pthread *fpt, void **result) { int ret = (*fpt->attr.stop)(fpt, result); memset(&fpt->thread, 0x00, sizeof(fpt->thread)); return ret; } void frr_pthread_stop_all(void) { frr_with_mutex(&frr_pthread_list_mtx) { struct listnode *n; struct frr_pthread *fpt; for (ALL_LIST_ELEMENTS_RO(frr_pthread_list, n, fpt)) frr_pthread_stop(fpt, NULL); } } /* * ---------------------------------------------------------------------------- * Default Event Loop * ---------------------------------------------------------------------------- */ /* dummy task for sleeper pipe */ static int fpt_dummy(struct thread *thread) { return 0; } /* poison pill task to end event loop */ static int fpt_finish(struct thread *thread) { struct frr_pthread *fpt = THREAD_ARG(thread); atomic_store_explicit(&fpt->running, false, memory_order_relaxed); return 0; } /* stop function, called from other threads to halt this one */ static int fpt_halt(struct frr_pthread *fpt, void **res) { thread_add_event(fpt->master, &fpt_finish, fpt, 0, NULL); pthread_join(fpt->thread, res); return 0; } /* * Entry pthread function & main event loop. * * Upon thread start the following actions occur: * * - frr_pthread's owner field is set to pthread ID. * - All signals are blocked (except for unblockable signals). * - Pthread's threadmaster is set to never handle pending signals * - Poker pipe for poll() is created and queued as I/O source * - The frr_pthread->running_cond condition variable is signalled to indicate * that the previous actions have completed. It is not safe to assume any of * the above have occurred before receiving this signal. * * After initialization is completed, the event loop begins running. Each tick, * the following actions are performed before running the usual event system * tick function: * * - Verify that the running boolean is set * - Verify that there are no pending cancellation requests * - Verify that there are tasks scheduled * * So long as the conditions are met, the event loop tick is run and the * returned task is executed. * * If any of these conditions are not met, the event loop exits, closes the * pipes and dies without running any cleanup functions. */ static void *fpt_run(void *arg) { struct frr_pthread *fpt = arg; fpt->master->owner = pthread_self(); int sleeper[2]; pipe(sleeper); thread_add_read(fpt->master, &fpt_dummy, NULL, sleeper[0], NULL); fpt->master->handle_signals = false; frr_pthread_set_name(fpt); frr_pthread_notify_running(fpt); struct thread task; while (atomic_load_explicit(&fpt->running, memory_order_relaxed)) { pthread_testcancel(); if (thread_fetch(fpt->master, &task)) { thread_call(&task); } } close(sleeper[1]); close(sleeper[0]); return NULL; } frr-7.2.1/lib/frr_pthread.h0000644000000000000000000001762213610377563012443 00000000000000/* * Utilities and interfaces for managing POSIX threads within FRR. * Copyright (C) 2017 Cumulus Networks, Inc. * * This program is free software; you can redistribute it and/or modify * it under the terms of the 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; see the file COPYING; if not, write to the Free Software * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA */ #ifndef _FRR_PTHREAD_H #define _FRR_PTHREAD_H #include #include "frratomic.h" #include "memory.h" #include "frrcu.h" #include "thread.h" #ifdef __cplusplus extern "C" { #endif #define OS_THREAD_NAMELEN 16 struct frr_pthread; struct frr_pthread_attr; struct frr_pthread_attr { void *(*start)(void *); int (*stop)(struct frr_pthread *, void **); }; struct frr_pthread { /* * Mutex protecting this structure. Must be taken for reading some * fields, denoted by a 'Requires: mtx'. */ pthread_mutex_t mtx; /* pthread id */ pthread_t thread; struct rcu_thread *rcu_thread; /* thread master for this pthread's thread.c event loop */ struct thread_master *master; /* caller-specified data; start & stop funcs, name, id */ struct frr_pthread_attr attr; /* * Notification mechanism for allowing pthreads to notify their parents * when they are ready to do work. This mechanism has two associated * functions: * * - frr_pthread_wait_running() * This function should be called by the spawning thread after * frr_pthread_run(). It safely waits until the spawned thread * indicates that is ready to do work by posting to the condition * variable. * * - frr_pthread_notify_running() * This function should be called by the spawned thread when it is * ready to do work. It will wake up any threads waiting on the * previously described condition. */ pthread_cond_t *running_cond; pthread_mutex_t *running_cond_mtx; atomic_bool running; /* * Fake thread-specific storage. No constraints on usage. Helpful when * creating reentrant pthread implementations. Can be used to pass * argument to pthread entry function. * * Requires: mtx */ void *data; /* * Human-readable thread name. * * Requires: mtx */ char *name; /* Used in pthread_set_name max 16 characters */ char os_name[OS_THREAD_NAMELEN]; }; extern struct frr_pthread_attr frr_pthread_attr_default; /* * Initializes this module. * * Must be called before using any of the other functions. */ void frr_pthread_init(void); /* * Uninitializes this module. * * Destroys all registered frr_pthread's and internal data structures. * * It is safe to call frr_pthread_init() after this function to reinitialize * the module. */ void frr_pthread_finish(void); /* * Creates a new frr_pthread with the given attributes. * * The 'attr' argument should be filled out with the desired attributes, * including ID, start and stop functions and the desired name. Alternatively, * if attr is NULL, the default attributes will be used. The pthread will be * set up to run a basic threadmaster loop and the name will be "Anonymous". * Scheduling tasks onto the threadmaster in the 'master' field of the returned * frr_pthread will cause them to run on that pthread. * * @param attr - the thread attributes * @param name - Human-readable name * @param os_name - 16 characters (including '\0') thread name to set in os, * @return the created frr_pthread upon success, or NULL upon failure */ struct frr_pthread *frr_pthread_new(struct frr_pthread_attr *attr, const char *name, const char *os_name); /* * Changes the name of the frr_pthread as reported by the operating * system. * * @param fpt - the frr_pthread to operate on * @return - on success returns 0 otherwise nonzero error number. */ int frr_pthread_set_name(struct frr_pthread *fpt); /* * Destroys an frr_pthread. * * Assumes that the associated pthread, if any, has already terminated. * * @param fpt - the frr_pthread to destroy */ void frr_pthread_destroy(struct frr_pthread *fpt); /* * Creates a new pthread and binds it to a frr_pthread. * * This function is a wrapper for pthread_create. The first parameter is the * frr_pthread to bind the created pthread to. All subsequent arguments are * passed unmodified to pthread_create(). The frr_pthread * provided will be * used as the argument to the pthread entry function. If it is necessary to * pass additional data, the 'data' field in the frr_pthread may be used. * * This function returns the same code as pthread_create(). If the value is * zero, the provided frr_pthread is bound to a running POSIX thread. If the * value is less than zero, the provided frr_pthread is guaranteed to be a * clean instance that may be susbsequently passed to frr_pthread_run(). * * @param fpt - frr_pthread * to run * @param attr - see pthread_create(3) * * @return see pthread_create(3) */ int frr_pthread_run(struct frr_pthread *fpt, const pthread_attr_t *attr); /* * Waits until the specified pthread has finished setting up and is ready to * begin work. * * If the pthread's code makes use of the startup synchronization mechanism, * this function should be called before attempting to use the functionality * exposed by the pthread. It waits until the 'running' condition is satisfied * (see struct definition of frr_pthread). * * @param fpt - the frr_pthread * to wait on */ void frr_pthread_wait_running(struct frr_pthread *fpt); /* * Notifies other pthreads that the calling thread has finished setting up and * is ready to begin work. * * This will allow any other pthreads waiting in 'frr_pthread_wait_running' to * proceed. * * @param fpt - the frr_pthread * that has finished setting up */ void frr_pthread_notify_running(struct frr_pthread *fpt); /* * Stops a frr_pthread with a result. * * @param fpt - frr_pthread * to stop * @param result - where to store the thread's result, if any. May be NULL if a * result is not needed. */ int frr_pthread_stop(struct frr_pthread *fpt, void **result); /* Stops all frr_pthread's. */ void frr_pthread_stop_all(void); #ifndef HAVE_PTHREAD_CONDATTR_SETCLOCK #define pthread_condattr_setclock(A, B) #endif /* mutex auto-lock/unlock */ /* variant 1: * (for short blocks, multiple mutexes supported) * break & return can be used for aborting the block * * frr_with_mutex(&mtx, &mtx2) { * if (error) * break; * ... * } */ #define _frr_with_mutex(mutex) \ *NAMECTR(_mtx_) __attribute__(( \ unused, cleanup(_frr_mtx_unlock))) = _frr_mtx_lock(mutex), \ /* end */ #define frr_with_mutex(...) \ for (pthread_mutex_t MACRO_REPEAT(_frr_with_mutex, ##__VA_ARGS__) \ *_once = NULL; _once == NULL; _once = (void *)1) \ /* end */ /* variant 2: * (more suitable for long blocks, no extra indentation) * * frr_mutex_lock_autounlock(&mtx); * ... */ #define frr_mutex_lock_autounlock(mutex) \ pthread_mutex_t *NAMECTR(_mtx_) \ __attribute__((unused, cleanup(_frr_mtx_unlock))) = \ _frr_mtx_lock(mutex) \ /* end */ static inline pthread_mutex_t *_frr_mtx_lock(pthread_mutex_t *mutex) { pthread_mutex_lock(mutex); return mutex; } static inline void _frr_mtx_unlock(pthread_mutex_t **mutex) { if (!*mutex) return; pthread_mutex_unlock(*mutex); *mutex = NULL; } #ifdef __cplusplus } #endif #endif /* _FRR_PTHREAD_H */ frr-7.2.1/lib/frr_zmq.c0000644000000000000000000002007413610377563011611 00000000000000/* * libzebra ZeroMQ bindings * Copyright (C) 2015 David Lamparter * * This program is free software; you can redistribute it and/or modify it * under the terms of the 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; see the file COPYING; if not, write to the Free Software * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA */ #include #include #include "thread.h" #include "memory.h" #include "frr_zmq.h" #include "log.h" #include "lib_errors.h" DEFINE_MTYPE_STATIC(LIB, ZEROMQ_CB, "ZeroMQ callback") /* libzmq's context */ void *frrzmq_context = NULL; static unsigned frrzmq_initcount = 0; void frrzmq_init(void) { if (frrzmq_initcount++ == 0) { frrzmq_context = zmq_ctx_new(); zmq_ctx_set(frrzmq_context, ZMQ_IPV6, 1); } } void frrzmq_finish(void) { if (--frrzmq_initcount == 0) { zmq_ctx_term(frrzmq_context); frrzmq_context = NULL; } } static int frrzmq_read_msg(struct thread *t) { struct frrzmq_cb **cbp = THREAD_ARG(t); struct frrzmq_cb *cb; zmq_msg_t msg; unsigned partno; unsigned char read = 0; int ret, more; size_t moresz; if (!cbp) return 1; cb = (*cbp); if (!cb || !cb->zmqsock) return 1; while (1) { zmq_pollitem_t polli = {.socket = cb->zmqsock, .events = ZMQ_POLLIN}; ret = zmq_poll(&polli, 1, 0); if (ret < 0) goto out_err; if (!(polli.revents & ZMQ_POLLIN)) break; if (cb->read.cb_msg) { cb->read.cb_msg(cb->read.arg, cb->zmqsock); read = 1; if (cb->read.cancelled) { frrzmq_check_events(cbp, &cb->write, ZMQ_POLLOUT); cb->read.thread = NULL; if (cb->write.cancelled && !cb->write.thread) XFREE(MTYPE_ZEROMQ_CB, cb); return 0; } continue; } partno = 0; if (zmq_msg_init(&msg)) goto out_err; do { ret = zmq_msg_recv(&msg, cb->zmqsock, ZMQ_NOBLOCK); if (ret < 0) { if (errno == EAGAIN) break; zmq_msg_close(&msg); goto out_err; } read = 1; cb->read.cb_part(cb->read.arg, cb->zmqsock, &msg, partno); if (cb->read.cancelled) { zmq_msg_close(&msg); frrzmq_check_events(cbp, &cb->write, ZMQ_POLLOUT); cb->read.thread = NULL; if (cb->write.cancelled && !cb->write.thread) XFREE(MTYPE_ZEROMQ_CB, cb); return 0; } /* cb_part may have read additional parts of the * message; don't use zmq_msg_more here */ moresz = sizeof(more); more = 0; ret = zmq_getsockopt(cb->zmqsock, ZMQ_RCVMORE, &more, &moresz); if (ret < 0) { zmq_msg_close(&msg); goto out_err; } partno++; } while (more); zmq_msg_close(&msg); } if (read) frrzmq_check_events(cbp, &cb->write, ZMQ_POLLOUT); funcname_thread_add_read_write( THREAD_READ, t->master, frrzmq_read_msg, cbp, cb->fd, &cb->read.thread, t->funcname, t->schedfrom, t->schedfrom_line); return 0; out_err: flog_err(EC_LIB_ZMQ, "ZeroMQ read error: %s(%d)", strerror(errno), errno); if (cb->read.cb_error) cb->read.cb_error(cb->read.arg, cb->zmqsock); return 1; } int funcname_frrzmq_thread_add_read(struct thread_master *master, void (*msgfunc)(void *arg, void *zmqsock), void (*partfunc)(void *arg, void *zmqsock, zmq_msg_t *msg, unsigned partnum), void (*errfunc)(void *arg, void *zmqsock), void *arg, void *zmqsock, struct frrzmq_cb **cbp, debugargdef) { int fd, events; size_t len; struct frrzmq_cb *cb; if (!cbp) return -1; if (!(msgfunc || partfunc) || (msgfunc && partfunc)) return -1; len = sizeof(fd); if (zmq_getsockopt(zmqsock, ZMQ_FD, &fd, &len)) return -1; len = sizeof(events); if (zmq_getsockopt(zmqsock, ZMQ_EVENTS, &events, &len)) return -1; if (*cbp) cb = *cbp; else { cb = XCALLOC(MTYPE_ZEROMQ_CB, sizeof(struct frrzmq_cb)); cb->write.cancelled = 1; *cbp = cb; } cb->zmqsock = zmqsock; cb->fd = fd; cb->read.arg = arg; cb->read.cb_msg = msgfunc; cb->read.cb_part = partfunc; cb->read.cb_error = errfunc; cb->read.cancelled = 0; if (events & ZMQ_POLLIN) { if (cb->read.thread) { thread_cancel(cb->read.thread); cb->read.thread = NULL; } funcname_thread_add_event(master, frrzmq_read_msg, cbp, fd, &cb->read.thread, funcname, schedfrom, fromln); } else funcname_thread_add_read_write( THREAD_READ, master, frrzmq_read_msg, cbp, fd, &cb->read.thread, funcname, schedfrom, fromln); return 0; } static int frrzmq_write_msg(struct thread *t) { struct frrzmq_cb **cbp = THREAD_ARG(t); struct frrzmq_cb *cb; unsigned char written = 0; int ret; if (!cbp) return 1; cb = (*cbp); if (!cb || !cb->zmqsock) return 1; while (1) { zmq_pollitem_t polli = {.socket = cb->zmqsock, .events = ZMQ_POLLOUT}; ret = zmq_poll(&polli, 1, 0); if (ret < 0) goto out_err; if (!(polli.revents & ZMQ_POLLOUT)) break; if (cb->write.cb_msg) { cb->write.cb_msg(cb->write.arg, cb->zmqsock); written = 1; if (cb->write.cancelled) { frrzmq_check_events(cbp, &cb->read, ZMQ_POLLIN); cb->write.thread = NULL; if (cb->read.cancelled && !cb->read.thread) XFREE(MTYPE_ZEROMQ_CB, cb); return 0; } continue; } } if (written) frrzmq_check_events(cbp, &cb->read, ZMQ_POLLIN); funcname_thread_add_read_write(THREAD_WRITE, t->master, frrzmq_write_msg, cbp, cb->fd, &cb->write.thread, t->funcname, t->schedfrom, t->schedfrom_line); return 0; out_err: flog_err(EC_LIB_ZMQ, "ZeroMQ write error: %s(%d)", strerror(errno), errno); if (cb->write.cb_error) cb->write.cb_error(cb->write.arg, cb->zmqsock); return 1; } int funcname_frrzmq_thread_add_write(struct thread_master *master, void (*msgfunc)(void *arg, void *zmqsock), void (*errfunc)(void *arg, void *zmqsock), void *arg, void *zmqsock, struct frrzmq_cb **cbp, debugargdef) { int fd, events; size_t len; struct frrzmq_cb *cb; if (!cbp) return -1; if (!msgfunc) return -1; len = sizeof(fd); if (zmq_getsockopt(zmqsock, ZMQ_FD, &fd, &len)) return -1; len = sizeof(events); if (zmq_getsockopt(zmqsock, ZMQ_EVENTS, &events, &len)) return -1; if (*cbp) cb = *cbp; else { cb = XCALLOC(MTYPE_ZEROMQ_CB, sizeof(struct frrzmq_cb)); cb->read.cancelled = 1; *cbp = cb; } cb->zmqsock = zmqsock; cb->fd = fd; cb->write.arg = arg; cb->write.cb_msg = msgfunc; cb->write.cb_part = NULL; cb->write.cb_error = errfunc; cb->write.cancelled = 0; if (events & ZMQ_POLLOUT) { if (cb->write.thread) { thread_cancel(cb->write.thread); cb->write.thread = NULL; } funcname_thread_add_event(master, frrzmq_write_msg, cbp, fd, &cb->write.thread, funcname, schedfrom, fromln); } else funcname_thread_add_read_write( THREAD_WRITE, master, frrzmq_write_msg, cbp, fd, &cb->write.thread, funcname, schedfrom, fromln); return 0; } void frrzmq_thread_cancel(struct frrzmq_cb **cb, struct cb_core *core) { if (!cb || !*cb) return; core->cancelled = 1; if (core->thread) { thread_cancel(core->thread); core->thread = NULL; } if ((*cb)->read.cancelled && !(*cb)->read.thread && (*cb)->write.cancelled && (*cb)->write.thread) XFREE(MTYPE_ZEROMQ_CB, *cb); } void frrzmq_check_events(struct frrzmq_cb **cbp, struct cb_core *core, int event) { struct frrzmq_cb *cb; int events; size_t len; if (!cbp) return; cb = (*cbp); if (!cb || !cb->zmqsock) return; len = sizeof(events); if (zmq_getsockopt(cb->zmqsock, ZMQ_EVENTS, &events, &len)) return; if (events & event && core->thread && !core->cancelled) { struct thread_master *tm = core->thread->master; thread_cancel(core->thread); core->thread = NULL; thread_add_event(tm, (event == ZMQ_POLLIN ? frrzmq_read_msg : frrzmq_write_msg), cbp, cb->fd, &core->thread); } } frr-7.2.1/lib/frr_zmq.h0000644000000000000000000001161213610377563011614 00000000000000/* * libzebra ZeroMQ bindings * Copyright (C) 2015 David Lamparter * * This program is free software; you can redistribute it and/or modify it * under the terms of the 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; see the file COPYING; if not, write to the Free Software * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA */ #ifndef _FRRZMQ_H #define _FRRZMQ_H #include "thread.h" #include #ifdef __cplusplus extern "C" { #endif /* linking/packaging note: this is a separate library that needs to be * linked into any daemon/library/module that wishes to use its * functionality. The purpose of this is to encapsulate the libzmq * dependency and not make libfrr/FRR itself depend on libzmq. * * libfrrzmq should be put in LDFLAGS/LIBADD *before* either libfrr or * libzmq, and both of these should always be listed, e.g. * foo_LDFLAGS = libfrrzmq.la libfrr.la $(ZEROMQ_LIBS) */ /* callback integration */ struct cb_core { struct thread *thread; void *arg; bool cancelled; void (*cb_msg)(void *arg, void *zmqsock); void (*cb_part)(void *arg, void *zmqsock, zmq_msg_t *msg, unsigned partnum); void (*cb_error)(void *arg, void *zmqsock); }; struct frrzmq_cb { void *zmqsock; int fd; struct cb_core read; struct cb_core write; }; /* libzmq's context * * this is mostly here as a convenience, it has IPv6 enabled but nothing * else is tied to it; you can use a separate context without problems */ extern void *frrzmq_context; extern void frrzmq_init(void); extern void frrzmq_finish(void); #define debugargdef const char *funcname, const char *schedfrom, int fromln /* core event registration, one of these 2 macros should be used */ #define frrzmq_thread_add_read_msg(m, f, e, a, z, d) \ funcname_frrzmq_thread_add_read(m, f, NULL, e, a, z, d, #f, __FILE__, \ __LINE__) #define frrzmq_thread_add_read_part(m, f, e, a, z, d) \ funcname_frrzmq_thread_add_read(m, NULL, f, e, a, z, d, #f, __FILE__, \ __LINE__) #define frrzmq_thread_add_write_msg(m, f, e, a, z, d) \ funcname_frrzmq_thread_add_write(m, f, e, a, z, d, #f, __FILE__, \ __LINE__) struct cb_core; struct frrzmq_cb; /* Set up a POLLIN or POLLOUT notification to be called from the libfrr main * loop. This has the following properties: * * - since ZeroMQ works with edge triggered notifications, it will loop and * dispatch as many events as ZeroMQ has pending at the time libfrr calls * into this code * - due to this looping (which means it non-single-issue), the callback is * also persistent. Do _NOT_ re-register the event inside of your * callback function. * - either msgfunc or partfunc will be called (only one can be specified) * - msgfunc is called once for each incoming message * - if partfunc is specified, the message is read and partfunc is called * for each ZeroMQ multi-part subpart. Note that you can't send replies * before all parts have been read because that violates the ZeroMQ FSM. * - write version doesn't allow for partial callback, you must handle the * whole message (all parts) in msgfunc callback * - you can safely cancel the callback from within itself * - installing a callback will check for pending events (ZMQ_EVENTS) and * may schedule the event to run as soon as libfrr is back in its main * loop. */ extern int funcname_frrzmq_thread_add_read( struct thread_master *master, void (*msgfunc)(void *arg, void *zmqsock), void (*partfunc)(void *arg, void *zmqsock, zmq_msg_t *msg, unsigned partnum), void (*errfunc)(void *arg, void *zmqsock), void *arg, void *zmqsock, struct frrzmq_cb **cb, debugargdef); extern int funcname_frrzmq_thread_add_write( struct thread_master *master, void (*msgfunc)(void *arg, void *zmqsock), void (*errfunc)(void *arg, void *zmqsock), void *arg, void *zmqsock, struct frrzmq_cb **cb, debugargdef); extern void frrzmq_thread_cancel(struct frrzmq_cb **cb, struct cb_core *core); /* * http://api.zeromq.org/4-2:zmq-getsockopt#toc10 * * As the descriptor is edge triggered, applications must update the state of * ZMQ_EVENTS after each invocation of zmq_send or zmq_recv.To be more explicit: * after calling zmq_send the socket may become readable (and vice versa) * without triggering a read event on the file descriptor. */ extern void frrzmq_check_events(struct frrzmq_cb **cbp, struct cb_core *core, int event); #ifdef __cplusplus } #endif #endif /* _FRRZMQ_H */ frr-7.2.1/lib/frratomic.h0000644000000000000000000002431413610377563012125 00000000000000/* * Copyright (c) 2015-16 David Lamparter, for NetDEF, Inc. * * Permission to use, copy, modify, and distribute this software for any * purpose with or without fee is hereby granted, provided that the above * copyright notice and this permission notice appear in all copies. * * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. */ #ifndef _FRRATOMIC_H #define _FRRATOMIC_H #ifdef HAVE_CONFIG_H #include "config.h" #endif #ifndef FRR_AUTOCONF_ATOMIC #error autoconf checks for atomic functions were not properly run #endif /* ISO C11 */ #ifdef __cplusplus #include #include using std::atomic_int; using std::memory_order; using std::memory_order_relaxed; using std::memory_order_acquire; using std::memory_order_release; using std::memory_order_acq_rel; using std::memory_order_consume; using std::memory_order_seq_cst; typedef std::atomic atomic_bool; typedef std::atomic atomic_size_t; typedef std::atomic atomic_uint_fast32_t; #elif defined(HAVE_STDATOMIC_H) #include /* These are available in gcc, but not in stdatomic */ #define atomic_add_fetch_explicit __atomic_add_fetch #define atomic_sub_fetch_explicit __atomic_sub_fetch #define atomic_and_fetch_explicit __atomic_and_fetch #define atomic_or_fetch_explicit __atomic_or_fetch /* gcc 4.7 and newer */ #elif defined(HAVE___ATOMIC) #define _Atomic volatile #define _ATOMIC_WANT_TYPEDEFS #define memory_order_relaxed __ATOMIC_RELAXED #define memory_order_consume __ATOMIC_CONSUME #define memory_order_acquire __ATOMIC_ACQUIRE #define memory_order_release __ATOMIC_RELEASE #define memory_order_acq_rel __ATOMIC_ACQ_REL #define memory_order_seq_cst __ATOMIC_SEQ_CST #define atomic_load_explicit __atomic_load_n #define atomic_store_explicit __atomic_store_n #define atomic_exchange_explicit __atomic_exchange_n #define atomic_fetch_add_explicit __atomic_fetch_add #define atomic_fetch_sub_explicit __atomic_fetch_sub #define atomic_fetch_and_explicit __atomic_fetch_and #define atomic_fetch_or_explicit __atomic_fetch_or #define atomic_add_fetch_explicit __atomic_add_fetch #define atomic_sub_fetch_explicit __atomic_sub_fetch #define atomic_and_fetch_explicit __atomic_and_fetch #define atomic_or_fetch_explicit __atomic_or_fetch #define atomic_compare_exchange_weak_explicit(atom, expect, desire, mem1, \ mem2) \ __atomic_compare_exchange_n(atom, expect, desire, 1, mem1, mem2) #define atomic_compare_exchange_strong_explicit(atom, expect, desire, mem1, \ mem2) \ __atomic_compare_exchange_n(atom, expect, desire, 0, mem1, mem2) /* gcc 4.1 and newer, * clang 3.3 (possibly older) * * __sync_swap isn't in gcc's documentation, but clang has it * * note __sync_synchronize() */ #elif defined(HAVE___SYNC) #define _Atomic volatile #define _ATOMIC_WANT_TYPEDEFS #define memory_order_relaxed 0 #define memory_order_consume 0 #define memory_order_acquire 0 #define memory_order_release 0 #define memory_order_acq_rel 0 #define memory_order_seq_cst 0 #define atomic_load_explicit(ptr, mem) \ ({ \ __sync_synchronize(); \ typeof(*ptr) rval = __sync_fetch_and_add((ptr), 0); \ __sync_synchronize(); \ rval; \ }) #define atomic_store_explicit(ptr, val, mem) \ ({ \ __sync_synchronize(); \ *(ptr) = (val); \ __sync_synchronize(); \ (void)0; \ }) #ifdef HAVE___SYNC_SWAP #define atomic_exchange_explicit(ptr, val, mem) \ ({ \ __sync_synchronize(); \ typeof(*ptr) rval = __sync_swap((ptr, val), 0); \ __sync_synchronize(); \ rval; \ }) #else /* !HAVE___SYNC_SWAP */ #define atomic_exchange_explicit(ptr, val, mem) \ ({ \ typeof(ptr) _ptr = (ptr); \ typeof(val) _val = (val); \ __sync_synchronize(); \ typeof(*ptr) old1, old2 = __sync_fetch_and_add(_ptr, 0); \ do { \ old1 = old2; \ old2 = __sync_val_compare_and_swap(_ptr, old1, _val); \ } while (old1 != old2); \ __sync_synchronize(); \ old2; \ }) #endif /* !HAVE___SYNC_SWAP */ #define atomic_fetch_add_explicit(ptr, val, mem) \ ({ \ __sync_synchronize(); \ typeof(*ptr) rval = __sync_fetch_and_add((ptr), (val)); \ __sync_synchronize(); \ rval; \ }) #define atomic_fetch_sub_explicit(ptr, val, mem) \ ({ \ __sync_synchronize(); \ typeof(*ptr) rval = __sync_fetch_and_sub((ptr), (val)); \ __sync_synchronize(); \ rval; \ }) #define atomic_compare_exchange_strong_explicit(atom, expect, desire, mem1, \ mem2) \ ({ \ typeof(atom) _atom = (atom); \ typeof(expect) _expect = (expect); \ typeof(desire) _desire = (desire); \ __sync_synchronize(); \ typeof(*atom) rval = \ __sync_val_compare_and_swap(_atom, *_expect, _desire); \ __sync_synchronize(); \ bool ret = (rval == *_expect); \ *_expect = rval; \ ret; \ }) #define atomic_compare_exchange_weak_explicit \ atomic_compare_exchange_strong_explicit #define atomic_fetch_and_explicit(ptr, val, mem) \ ({ \ __sync_synchronize(); \ typeof(*ptr) rval = __sync_fetch_and_and(ptr, val); \ __sync_synchronize(); \ rval; \ }) #define atomic_fetch_or_explicit(ptr, val, mem) \ ({ \ __sync_synchronize(); \ typeof(*ptr) rval = __sync_fetch_and_or(ptr, val); \ __sync_synchronize(); \ rval; \ }) #define atomic_add_fetch_explicit(ptr, val, mem) \ ({ \ __sync_synchronize(); \ typeof(*ptr) rval = __sync_add_and_fetch((ptr), (val)); \ __sync_synchronize(); \ rval; \ }) #define atomic_sub_fetch_explicit(ptr, val, mem) \ ({ \ __sync_synchronize(); \ typeof(*ptr) rval = __sync_sub_and_fetch((ptr), (val)); \ __sync_synchronize(); \ rval; \ }) #define atomic_and_fetch_explicit(ptr, val, mem) \ ({ \ __sync_synchronize(); \ typeof(*ptr) rval = __sync_and_and_fetch(ptr, val); \ __sync_synchronize(); \ rval; \ }) #define atomic_or_fetch_explicit(ptr, val, mem) \ ({ \ __sync_synchronize(); \ typeof(*ptr) rval = __sync_or_and_fetch(ptr, val); \ __sync_synchronize(); \ rval; \ }) #else /* !HAVE___ATOMIC && !HAVE_STDATOMIC_H */ #error no atomic functions... #endif #ifdef _ATOMIC_WANT_TYPEDEFS #undef _ATOMIC_WANT_TYPEDEFS #include #include typedef _Atomic bool atomic_bool; typedef _Atomic size_t atomic_size_t; typedef _Atomic uint_fast32_t atomic_uint_fast32_t; #endif #endif /* _FRRATOMIC_H */ frr-7.2.1/lib/frrcu.c0000644000000000000000000003512413610377563011254 00000000000000/* * Copyright (c) 2017-19 David Lamparter, for NetDEF, Inc. * * Permission to use, copy, modify, and distribute this software for any * purpose with or without fee is hereby granted, provided that the above * copyright notice and this permission notice appear in all copies. * * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. */ /* implementation notes: this is an epoch-based RCU implementation. rcu_seq * (global variable) counts the current epoch. Threads hold a specific epoch * in rcu_read_lock(). This is the oldest epoch a thread might be accessing * data from. * * The rcu_seq global is only pushed forward on rcu_read_lock() and * rcu_read_unlock() calls. This makes things a tad more efficient since * those are the only places it matters: * - on rcu_read_lock, we don't want to hold an old epoch pointlessly * - on rcu_read_unlock, we want to make sure we're not stuck on an old epoch * when heading into a long idle period where no thread holds RCU * * rcu_thread structures themselves are RCU-free'd. * * rcu_head structures are the most iffy; normally for an ATOMLIST we would * need to make sure we use rcu_free or pthread_rwlock to deallocate old items * to prevent ABA or use-after-free problems. However, our ATOMLIST code * guarantees that if the list remains non-empty in all cases, we only need * the "last" pointer to do an "add_tail()", i.e. we can't run into ABA/UAF * issues - but we do need to keep at least 1 item on the list. * * (Search the atomlist code for all uses of "last") */ #ifdef HAVE_CONFIG_H #include "config.h" #endif #include #ifdef HAVE_PTHREAD_NP_H #include #endif #include #include #include #include "frrcu.h" #include "seqlock.h" #include "atomlist.h" DEFINE_MTYPE_STATIC(LIB, RCU_THREAD, "RCU thread") DECLARE_ATOMLIST(rcu_heads, struct rcu_head, head) PREDECL_ATOMLIST(rcu_threads) struct rcu_thread { struct rcu_threads_item head; struct rcu_head rcu_head; struct seqlock rcu; /* only accessed by thread itself, not atomic */ unsigned depth; }; DECLARE_ATOMLIST(rcu_threads, struct rcu_thread, head) static const struct rcu_action rcua_next = { .type = RCUA_NEXT }; static const struct rcu_action rcua_end = { .type = RCUA_END }; static const struct rcu_action rcua_close = { .type = RCUA_CLOSE }; struct rcu_next { struct rcu_head head_free; struct rcu_head head_next; }; #define rcu_free_internal(mtype, ptr, field) \ do { \ typeof(ptr) _ptr = (ptr); \ struct rcu_head *_rcu_head = &_ptr->field; \ static const struct rcu_action _rcu_action = { \ .type = RCUA_FREE, \ .u.free = { \ .mt = mtype, \ .offset = offsetof(typeof(*_ptr), field), \ }, \ }; \ _rcu_head->action = &_rcu_action; \ rcu_heads_add_tail(&rcu_heads, _rcu_head); \ } while (0) /* primary global RCU position */ static struct seqlock rcu_seq; /* this is set to rcu_seq whenever something is added on the RCU queue. * rcu_read_lock() and rcu_read_unlock() will then bump rcu_seq up one step. */ static _Atomic seqlock_val_t rcu_dirty; static struct rcu_threads_head rcu_threads; static struct rcu_heads_head rcu_heads; /* main thread & RCU sweeper have pre-setup rcu_thread structures. The * reasons are different: * * - rcu_thread_main is there because the main thread isn't started like * other threads, it's implicitly created when the program is started. So * rcu_thread_main matches up implicitly. * * - rcu_thread_rcu isn't actually put on the rcu_threads list (makes no * sense really), it only exists so we can call RCU-using functions from * the RCU thread without special handling in rcu_read_lock/unlock. */ static struct rcu_thread rcu_thread_main; static struct rcu_thread rcu_thread_rcu; static pthread_t rcu_pthread; static pthread_key_t rcu_thread_key; static bool rcu_active; static void rcu_start(void); static void rcu_bump(void); /* * preinitialization for main thread */ static void rcu_thread_end(void *rcu_thread); static void rcu_preinit(void) __attribute__((constructor)); static void rcu_preinit(void) { struct rcu_thread *rt; rt = &rcu_thread_main; rt->depth = 1; seqlock_init(&rt->rcu); seqlock_acquire_val(&rt->rcu, SEQLOCK_STARTVAL); pthread_key_create(&rcu_thread_key, rcu_thread_end); pthread_setspecific(rcu_thread_key, rt); rcu_threads_add_tail(&rcu_threads, rt); /* RCU sweeper's rcu_thread is a dummy, NOT added to rcu_threads */ rt = &rcu_thread_rcu; rt->depth = 1; seqlock_init(&rcu_seq); seqlock_acquire_val(&rcu_seq, SEQLOCK_STARTVAL); } static struct rcu_thread *rcu_self(void) { return (struct rcu_thread *)pthread_getspecific(rcu_thread_key); } /* * thread management (for the non-main thread) */ struct rcu_thread *rcu_thread_prepare(void) { struct rcu_thread *rt, *cur; rcu_assert_read_locked(); if (!rcu_active) rcu_start(); cur = rcu_self(); assert(cur->depth); /* new thread always starts with rcu_read_lock held at depth 1, and * holding the same epoch as the parent (this makes it possible to * use RCU for things passed into the thread through its arg) */ rt = XCALLOC(MTYPE_RCU_THREAD, sizeof(*rt)); rt->depth = 1; seqlock_init(&rt->rcu); seqlock_acquire(&rt->rcu, &cur->rcu); rcu_threads_add_tail(&rcu_threads, rt); return rt; } void rcu_thread_start(struct rcu_thread *rt) { pthread_setspecific(rcu_thread_key, rt); } void rcu_thread_unprepare(struct rcu_thread *rt) { if (rt == &rcu_thread_rcu) return; rt->depth = 1; seqlock_acquire(&rt->rcu, &rcu_seq); rcu_bump(); if (rt != &rcu_thread_main) /* this free() happens after seqlock_release() below */ rcu_free_internal(MTYPE_RCU_THREAD, rt, rcu_head); rcu_threads_del(&rcu_threads, rt); seqlock_release(&rt->rcu); } static void rcu_thread_end(void *rtvoid) { struct rcu_thread *rt = rtvoid; rcu_thread_unprepare(rt); } /* * main RCU control aspects */ static void rcu_bump(void) { struct rcu_next *rn; rn = XMALLOC(MTYPE_RCU_THREAD, sizeof(*rn)); /* note: each RCUA_NEXT item corresponds to exactly one seqno bump. * This means we don't need to communicate which seqno is which * RCUA_NEXT, since we really don't care. */ /* * Important race condition: while rcu_heads_add_tail is executing, * there is an intermediate point where the rcu_heads "last" pointer * already points to rn->head_next, but rn->head_next isn't added to * the list yet. That means any other "add_tail" calls append to this * item, which isn't fully on the list yet. Freeze this thread at * that point and look at another thread doing a rcu_bump. It adds * these two items and then does a seqlock_bump. But the rcu_heads * list is still "interrupted" and there's no RCUA_NEXT on the list * yet (from either the frozen thread or the second thread). So * rcu_main() might actually hit the end of the list at the * "interrupt". * * This situation is prevented by requiring that rcu_read_lock is held * for any calls to rcu_bump, since if we're holding the current RCU * epoch, that means rcu_main can't be chewing on rcu_heads and hit * that interruption point. Only by the time the thread has continued * to rcu_read_unlock() - and therefore completed the add_tail - the * RCU sweeper gobbles up the epoch and can be sure to find at least * the RCUA_NEXT and RCUA_FREE items on rcu_heads. */ rn->head_next.action = &rcua_next; rcu_heads_add_tail(&rcu_heads, &rn->head_next); /* free rn that we allocated above. * * This is INTENTIONALLY not built into the RCUA_NEXT action. This * ensures that after the action above is popped off the queue, there * is still at least 1 item on the RCU queue. This means we never * delete the last item, which is extremely important since it keeps * the atomlist ->last pointer alive and well. * * If we were to "run dry" on the RCU queue, add_tail may run into the * "last item is being deleted - start over" case, and then we may end * up accessing old RCU queue items that are already free'd. */ rcu_free_internal(MTYPE_RCU_THREAD, rn, head_free); /* Only allow the RCU sweeper to run after these 2 items are queued. * * If another thread enqueues some RCU action in the intermediate * window here, nothing bad happens - the queued action is associated * with a larger seq# than strictly necessary. Thus, it might get * executed a bit later, but that's not a problem. * * If another thread acquires the read lock in this window, it holds * the previous epoch, but its RCU queue actions will be in the next * epoch. This isn't a problem either, just a tad inefficient. */ seqlock_bump(&rcu_seq); } static void rcu_bump_maybe(void) { seqlock_val_t dirty; dirty = atomic_load_explicit(&rcu_dirty, memory_order_relaxed); /* no problem if we race here and multiple threads bump rcu_seq; * bumping too much causes no issues while not bumping enough will * result in delayed cleanup */ if (dirty == seqlock_cur(&rcu_seq)) rcu_bump(); } void rcu_read_lock(void) { struct rcu_thread *rt = rcu_self(); assert(rt); if (rt->depth++ > 0) return; seqlock_acquire(&rt->rcu, &rcu_seq); /* need to hold RCU for bump ... */ rcu_bump_maybe(); /* ... but no point in holding the old epoch if we just bumped */ seqlock_acquire(&rt->rcu, &rcu_seq); } void rcu_read_unlock(void) { struct rcu_thread *rt = rcu_self(); assert(rt && rt->depth); if (--rt->depth > 0) return; rcu_bump_maybe(); seqlock_release(&rt->rcu); } void rcu_assert_read_locked(void) { struct rcu_thread *rt = rcu_self(); assert(rt && rt->depth && seqlock_held(&rt->rcu)); } void rcu_assert_read_unlocked(void) { struct rcu_thread *rt = rcu_self(); assert(rt && !rt->depth && !seqlock_held(&rt->rcu)); } /* * RCU resource-release thread */ static void *rcu_main(void *arg); static void rcu_start(void) { /* ensure we never handle signals on the RCU thread by blocking * everything here (new thread inherits signal mask) */ sigset_t oldsigs, blocksigs; sigfillset(&blocksigs); pthread_sigmask(SIG_BLOCK, &blocksigs, &oldsigs); rcu_active = true; assert(!pthread_create(&rcu_pthread, NULL, rcu_main, NULL)); pthread_sigmask(SIG_SETMASK, &oldsigs, NULL); #ifdef HAVE_PTHREAD_SETNAME_NP # ifdef GNU_LINUX pthread_setname_np(rcu_pthread, "RCU sweeper"); # elif defined(__NetBSD__) pthread_setname_np(rcu_pthread, "RCU sweeper", NULL); # endif #elif defined(HAVE_PTHREAD_SET_NAME_NP) pthread_set_name_np(rcu_pthread, "RCU sweeper"); #endif } static void rcu_do(struct rcu_head *rh) { struct rcu_head_close *rhc; void *p; switch (rh->action->type) { case RCUA_FREE: p = (char *)rh - rh->action->u.free.offset; if (rh->action->u.free.mt) qfree(rh->action->u.free.mt, p); else free(p); break; case RCUA_CLOSE: rhc = container_of(rh, struct rcu_head_close, rcu_head); close(rhc->fd); break; case RCUA_CALL: p = (char *)rh - rh->action->u.call.offset; rh->action->u.call.fptr(p); break; case RCUA_INVALID: case RCUA_NEXT: case RCUA_END: default: assert(0); } } static void rcu_watchdog(struct rcu_thread *rt) { #if 0 /* future work: print a backtrace for the thread that's holding up * RCU. The only (good) way of doing that is to send a signal to the * other thread, save away the backtrace in the signal handler, and * block here until the signal is done processing. * * Just haven't implemented that yet. */ fprintf(stderr, "RCU watchdog %p\n", rt); #endif } static void *rcu_main(void *arg) { struct rcu_thread *rt; struct rcu_head *rh = NULL; bool end = false; struct timespec maxwait; seqlock_val_t rcuval = SEQLOCK_STARTVAL; pthread_setspecific(rcu_thread_key, &rcu_thread_rcu); while (!end) { seqlock_wait(&rcu_seq, rcuval); /* RCU watchdog timeout, TODO: configurable value */ clock_gettime(CLOCK_MONOTONIC, &maxwait); maxwait.tv_nsec += 100 * 1000 * 1000; if (maxwait.tv_nsec >= 1000000000) { maxwait.tv_sec++; maxwait.tv_nsec -= 1000000000; } frr_each (rcu_threads, &rcu_threads, rt) if (!seqlock_timedwait(&rt->rcu, rcuval, &maxwait)) { rcu_watchdog(rt); seqlock_wait(&rt->rcu, rcuval); } while ((rh = rcu_heads_pop(&rcu_heads))) { if (rh->action->type == RCUA_NEXT) break; else if (rh->action->type == RCUA_END) end = true; else rcu_do(rh); } rcuval += SEQLOCK_INCR; } /* rcu_shutdown can only be called singlethreaded, and it does a * pthread_join, so it should be impossible that anything ended up * on the queue after RCUA_END */ #if 1 assert(!rcu_heads_first(&rcu_heads)); #else while ((rh = rcu_heads_pop(&rcu_heads))) if (rh->action->type >= RCUA_FREE) rcu_do(rh); #endif return NULL; } void rcu_shutdown(void) { static struct rcu_head rcu_head_end; struct rcu_thread *rt = rcu_self(); void *retval; if (!rcu_active) return; rcu_assert_read_locked(); assert(rcu_threads_count(&rcu_threads) == 1); rcu_enqueue(&rcu_head_end, &rcua_end); rt->depth = 0; seqlock_release(&rt->rcu); seqlock_release(&rcu_seq); rcu_active = false; /* clearing rcu_active is before pthread_join in case we hang in * pthread_join & get a SIGTERM or something - in that case, just * ignore the maybe-still-running RCU thread */ if (pthread_join(rcu_pthread, &retval) == 0) { seqlock_acquire_val(&rcu_seq, SEQLOCK_STARTVAL); seqlock_acquire_val(&rt->rcu, SEQLOCK_STARTVAL); rt->depth = 1; } } /* * RCU'd free functions */ void rcu_enqueue(struct rcu_head *rh, const struct rcu_action *action) { /* refer to rcu_bump() for why we need to hold RCU when adding items * to rcu_heads */ rcu_assert_read_locked(); rh->action = action; if (!rcu_active) { rcu_do(rh); return; } rcu_heads_add_tail(&rcu_heads, rh); atomic_store_explicit(&rcu_dirty, seqlock_cur(&rcu_seq), memory_order_relaxed); } void rcu_close(struct rcu_head_close *rhc, int fd) { rhc->fd = fd; rcu_enqueue(&rhc->rcu_head, &rcua_close); } frr-7.2.1/lib/frrcu.h0000644000000000000000000001465013610377563011262 00000000000000/* * Copyright (c) 2017-19 David Lamparter, for NetDEF, Inc. * * Permission to use, copy, modify, and distribute this software for any * purpose with or without fee is hereby granted, provided that the above * copyright notice and this permission notice appear in all copies. * * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. */ #ifndef _FRRCU_H #define _FRRCU_H #include "memory.h" #include "atomlist.h" #include "seqlock.h" /* quick RCU primer: * There's a global sequence counter. Whenever a thread does a * rcu_read_lock(), it is marked as holding the current sequence counter. * When something is cleaned with RCU, the global sequence counter is * increased and the item is queued for cleanup - *after* all threads are * at a more recent sequence counter (or no sequence counter / unheld). * * So, by delaying resource cleanup, RCU ensures that things don't go away * while another thread may hold a (stale) reference. * * Note that even if a thread is in rcu_read_lock(), it is invalid for that * thread to access bits after rcu_free() & co on them. This is a design * choice to allow no-op'ing out the entire RCU mechanism if we're running * singlethreaded. (Also allows some optimization on the counter bumping.) * * differences from Linux Kernel RCU: * - there's no rcu_synchronize(), if you really need to defer something * use rcu_call() (and double check it's really necessary) * - rcu_dereference() and rcu_assign_pointer() don't exist, use atomic_* * instead (ATOM* list structures do the right thing) */ /* opaque */ struct rcu_thread; /* called before new thread creation, sets up rcu thread info for new thread * before it actually exits. This ensures possible RCU references are held * for thread startup. * * return value must be passed into the new thread's call to rcu_thread_start() */ extern struct rcu_thread *rcu_thread_prepare(void); /* cleanup in case pthread_create() fails */ extern void rcu_thread_unprepare(struct rcu_thread *rcu_thread); /* called early in the new thread, with the return value from the above. * NB: new thread is initially in RCU-held state! (at depth 1) * * TBD: maybe inherit RCU state from rcu_thread_prepare()? */ extern void rcu_thread_start(struct rcu_thread *rcu_thread); /* thread exit is handled through pthread_key_create's destructor function */ /* global RCU shutdown - must be called with only 1 active thread left. waits * until remaining RCU actions are done & RCU thread has exited. * * This is mostly here to get a clean exit without memleaks. */ extern void rcu_shutdown(void); /* enter / exit RCU-held state. counter-based, so can be called nested. */ extern void rcu_read_lock(void); extern void rcu_read_unlock(void); /* for debugging / safety checks */ extern void rcu_assert_read_locked(void); extern void rcu_assert_read_unlocked(void); enum rcu_action_type { RCUA_INVALID = 0, /* used internally by the RCU code, shouldn't ever show up outside */ RCUA_NEXT, RCUA_END, /* normal RCU actions, for outside use */ RCUA_FREE, RCUA_CLOSE, RCUA_CALL, }; /* since rcu_head is intended to be embedded into structs which may exist * with lots of copies, rcu_head is shrunk down to its absolute minimum - * the atomlist pointer + a pointer to this action struct. */ struct rcu_action { enum rcu_action_type type; union { struct { struct memtype *mt; ptrdiff_t offset; } free; struct { void (*fptr)(void *arg); ptrdiff_t offset; } call; } u; }; /* RCU cleanup function queue item */ PREDECL_ATOMLIST(rcu_heads) struct rcu_head { struct rcu_heads_item head; const struct rcu_action *action; }; /* special RCU head for delayed fd-close */ struct rcu_head_close { struct rcu_head rcu_head; int fd; }; /* enqueue RCU action - use the macros below to get the rcu_action set up */ extern void rcu_enqueue(struct rcu_head *head, const struct rcu_action *action); /* RCU free() and file close() operations. * * freed memory / closed fds become _immediately_ unavailable to the calling * thread, but will remain available for other threads until they have passed * into RCU-released state. */ /* may be called with NULL mt to do non-MTYPE free() */ #define rcu_free(mtype, ptr, field) \ do { \ typeof(ptr) _ptr = (ptr); \ struct rcu_head *_rcu_head = &_ptr->field; \ static const struct rcu_action _rcu_action = { \ .type = RCUA_FREE, \ .u.free = { \ .mt = mtype, \ .offset = offsetof(typeof(*_ptr), field), \ }, \ }; \ rcu_enqueue(_rcu_head, &_rcu_action); \ } while (0) /* use this sparingly, it runs on (and blocks) the RCU thread */ #define rcu_call(func, ptr, field) \ do { \ typeof(ptr) _ptr = (ptr); \ void (*fptype)(typeof(ptr)); \ struct rcu_head *_rcu_head = &_ptr->field; \ static const struct rcu_action _rcu_action = { \ .type = RCUA_CALL, \ .u.call = { \ .fptr = (void *)func, \ .offset = offsetof(typeof(*_ptr), field), \ }, \ }; \ (void)(_fptype = func); \ rcu_enqueue(_rcu_head, &_rcu_action); \ } while (0) extern void rcu_close(struct rcu_head_close *head, int fd); #endif /* _FRRCU_H */ frr-7.2.1/lib/frrlua.c0000644000000000000000000000541213610377563011423 00000000000000/* * This file defines the lua interface into * FRRouting. * * Copyright (C) 2016 Cumulus Networks, Inc. * Donald Sharp * * This file is part of FreeRangeRouting (FRR). * * FRR is free software; you can redistribute it and/or modify it under the * terms of the GNU General Public License as published by the Free Software * Foundation; either version 2, or (at your option) any later version. * * FRR 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 FRR; see the file COPYING. If not, write to the Free Software * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. */ #include #if defined(HAVE_LUA) #include "prefix.h" #include "frrlua.h" #include "log.h" static int lua_zlog_debug(lua_State *L) { int debug_lua = 1; const char *string = lua_tostring(L, 1); if (debug_lua) zlog_debug("%s", string); return 0; } const char *get_string(lua_State *L, const char *key) { const char *str; lua_pushstring(L, key); lua_gettable(L, -2); str = (const char *)lua_tostring(L, -1); lua_pop(L, 1); return str; } int get_integer(lua_State *L, const char *key) { int result; lua_pushstring(L, key); lua_gettable(L, -2); result = lua_tointeger(L, -1); lua_pop(L, 1); return result; } static void *lua_alloc(void *ud, void *ptr, size_t osize, size_t nsize) { (void)ud; (void)osize; /* not used */ if (nsize == 0) { free(ptr); return NULL; } else return realloc(ptr, nsize); } lua_State *lua_initialize(const char *file) { int status; lua_State *L = lua_newstate(lua_alloc, NULL); zlog_debug("Newstate: %p", L); luaL_openlibs(L); zlog_debug("Opened lib"); status = luaL_loadfile(L, file); if (status) { zlog_debug("Failure to open %s %d", file, status); lua_close(L); return NULL; } lua_pcall(L, 0, LUA_MULTRET, 0); zlog_debug("Setting global function"); lua_pushcfunction(L, lua_zlog_debug); lua_setglobal(L, "zlog_debug"); return L; } void lua_setup_prefix_table(lua_State *L, const struct prefix *prefix) { char buffer[100]; lua_newtable(L); lua_pushstring(L, prefix2str(prefix, buffer, 100)); lua_setfield(L, -2, "route"); lua_pushinteger(L, prefix->family); lua_setfield(L, -2, "family"); lua_setglobal(L, "prefix"); } enum lua_rm_status lua_run_rm_rule(lua_State *L, const char *rule) { int status; lua_getglobal(L, rule); status = lua_pcall(L, 0, 1, 0); if (status) { zlog_debug("Executing Failure with function: %s: %d", rule, status); return LUA_RM_FAILURE; } status = lua_tonumber(L, -1); return status; } #endif frr-7.2.1/lib/frrlua.h0000644000000000000000000000421113610377563011424 00000000000000/* * This file defines the lua interface into * FRRouting. * * Copyright (C) 2016 Cumulus Networks, Inc. * Donald Sharp * * This file is part of FreeRangeRouting (FRR). * * FRR is free software; you can redistribute it and/or modify it under the * terms of the GNU General Public License as published by the Free Software * Foundation; either version 2, or (at your option) any later version. * * FRR 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 FRR; see the file COPYING. If not, write to the Free Software * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. */ #ifndef __LUA_H__ #define __LUA_H__ #if defined(HAVE_LUA) #include "lua.h" #include "lualib.h" #include "lauxlib.h" #ifdef __cplusplus extern "C" { #endif /* * These functions are helper functions that * try to glom some of the lua_XXX functionality * into what we actually need, instead of having * to make multiple calls to set up what * we want */ enum lua_rm_status { /* * Script function run failure. This will translate into a * deny */ LUA_RM_FAILURE = 0, /* * No Match was found for the route map function */ LUA_RM_NOMATCH, /* * Match was found but no changes were made to the * incoming data. */ LUA_RM_MATCH, /* * Match was found and data was modified, so * figure out what changed */ LUA_RM_MATCH_AND_CHANGE, }; /* * Open up the lua.scr file and parse * initial global values, if any. */ lua_State *lua_initialize(const char *file); void lua_setup_prefix_table(lua_State *L, const struct prefix *prefix); enum lua_rm_status lua_run_rm_rule(lua_State *L, const char *rule); /* * Get particular string/integer information * from a table. It is *assumed* that * the table has already been selected */ const char *get_string(lua_State *L, const char *key); int get_integer(lua_State *L, const char *key); #ifdef __cplusplus } #endif #endif #endif frr-7.2.1/lib/frrstr.c0000644000000000000000000001050613610377563011452 00000000000000/* * FRR string processing utilities. * Copyright (C) 2018 Cumulus Networks, Inc. * Quentin Young * * This program is free software; you can redistribute it and/or modify it * under the terms of the 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; see the file COPYING; if not, write to the Free Software * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA */ #ifdef HAVE_CONFIG_H #include "config.h" #endif #include #include #include #include #include "frrstr.h" #include "memory.h" #include "vector.h" void frrstr_split(const char *string, const char *delimiter, char ***result, int *argc) { if (!string) return; unsigned int sz = 4, idx = 0; char *copy, *copystart; *result = XCALLOC(MTYPE_TMP, sizeof(char *) * sz); copystart = copy = XSTRDUP(MTYPE_TMP, string); *argc = 0; const char *tok = NULL; while (copy) { tok = strsep(©, delimiter); (*result)[idx] = XSTRDUP(MTYPE_TMP, tok); if (++idx == sz) *result = XREALLOC(MTYPE_TMP, *result, (sz *= 2) * sizeof(char *)); (*argc)++; } XFREE(MTYPE_TMP, copystart); } vector frrstr_split_vec(const char *string, const char *delimiter) { char **result; int argc; if (!string) return NULL; frrstr_split(string, delimiter, &result, &argc); vector v = array_to_vector((void **)result, argc); XFREE(MTYPE_TMP, result); return v; } char *frrstr_join(const char **parts, int argc, const char *join) { int i; char *str; char *p; size_t len = 0; size_t joinlen = join ? strlen(join) : 0; if (!argc) return NULL; for (i = 0; i < argc; i++) len += strlen(parts[i]); len += argc * joinlen + 1; if (!len) return NULL; p = str = XMALLOC(MTYPE_TMP, len); for (i = 0; i < argc; i++) { size_t arglen = strlen(parts[i]); memcpy(p, parts[i], arglen); p += arglen; if (i + 1 != argc && join) { memcpy(p, join, joinlen); p += joinlen; } } *p = '\0'; return str; } char *frrstr_join_vec(vector v, const char *join) { char **argv; int argc; vector_to_array(v, (void ***)&argv, &argc); char *ret = frrstr_join((const char **)argv, argc, join); XFREE(MTYPE_TMP, argv); return ret; } void frrstr_filter_vec(vector v, regex_t *filter) { regmatch_t ignored[1]; for (unsigned int i = 0; i < vector_active(v); i++) { if (regexec(filter, vector_slot(v, i), 0, ignored, 0)) { XFREE(MTYPE_TMP, vector_slot(v, i)); vector_unset(v, i); } } } void frrstr_strvec_free(vector v) { unsigned int i; char *cp; if (!v) return; for (i = 0; i < vector_active(v); i++) { cp = vector_slot(v, i); XFREE(MTYPE_TMP, cp); } vector_free(v); } char *frrstr_replace(const char *str, const char *find, const char *replace) { char *ch; char *nustr = XSTRDUP(MTYPE_TMP, str); size_t findlen = strlen(find); size_t repllen = strlen(replace); while ((ch = strstr(nustr, find))) { if (repllen > findlen) { size_t nusz = strlen(nustr) + repllen - findlen + 1; nustr = XREALLOC(MTYPE_TMP, nustr, nusz); ch = strstr(nustr, find); } size_t nustrlen = strlen(nustr); size_t taillen = (nustr + nustrlen) - (ch + findlen); memmove(ch + findlen + (repllen - findlen), ch + findlen, taillen + 1); memcpy(ch, replace, repllen); } return nustr; } bool frrstr_startswith(const char *str, const char *prefix) { if (!str || !prefix) return false; size_t lenstr = strlen(str); size_t lenprefix = strlen(prefix); if (lenprefix > lenstr) return false; return strncmp(str, prefix, lenprefix) == 0; } bool frrstr_endswith(const char *str, const char *suffix) { if (!str || !suffix) return false; size_t lenstr = strlen(str); size_t lensuffix = strlen(suffix); if (lensuffix > lenstr) return false; return strncmp(&str[lenstr - lensuffix], suffix, lensuffix) == 0; } int all_digit(const char *str) { for (; *str != '\0'; str++) if (!isdigit((unsigned char)*str)) return 0; return 1; } frr-7.2.1/lib/frrstr.h0000644000000000000000000000734113610377563011462 00000000000000/* * FRR string processing utilities. * Copyright (C) 2018 Cumulus Networks, Inc. * Quentin Young * * This program is free software; you can redistribute it and/or modify it * under the terms of the 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; see the file COPYING; if not, write to the Free Software * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA */ #ifndef _FRRSTR_H_ #define _FRRSTR_H_ #include #include #include #include "vector.h" #ifdef __cplusplus extern "C" { #endif /* * Tokenizes a string, storing tokens in a vector. Whitespace is ignored. * Delimiter characters are not included. * * string * The string to split * * delimiter * Delimiter string, as used in strsep() * * Returns: * The split string. Each token is allocated with MTYPE_TMP. */ void frrstr_split(const char *string, const char *delimiter, char ***result, int *argc); vector frrstr_split_vec(const char *string, const char *delimiter); /* * Concatenate string array into a single string. * * argv * array of string pointers to concatenate * * argc * array length * * join * string to insert between each part, or NULL for nothing * * Returns: * the joined string, allocated with MTYPE_TMP */ char *frrstr_join(const char **parts, int argc, const char *join); char *frrstr_join_vec(vector v, const char *join); /* * Filter string vector. * Removes lines that do not contain a match for the provided regex. * * v * The vector to filter. * * filter * Regex to filter with. */ void frrstr_filter_vec(vector v, regex_t *filter); /* * Free allocated string vector. * Assumes each item is allocated with MTYPE_TMP. * * v * the vector to free */ void frrstr_strvec_free(vector v); /* * Given a string, replaces all occurrences of a substring with a different * string. The result is a new string. The original string is not modified. * * If 'replace' is longer than 'find', this function performs N+1 allocations, * where N is the number of times 'find' occurs in 'str'. If 'replace' is equal * in length or shorter than 'find', only 1 allocation is performed. * * str * String to perform replacement on. * * find * Substring to replace. * * replace * String to replace 'find' with. * * Returns: * A new string, allocated with MTYPE_TMP, that is the result of performing * the replacement on 'str'. This must be freed by the caller. */ char *frrstr_replace(const char *str, const char *find, const char *replace); /* * Prefix match for string. * * str * string to check for prefix match * * prefix * prefix to look for * * Returns: * true if str starts with prefix, false otherwise */ bool frrstr_startswith(const char *str, const char *prefix); /* * Suffix match for string. * * str * string to check for suffix match * * suffix * suffix to look for * * Returns: * true if str ends with suffix, false otherwise */ bool frrstr_endswith(const char *str, const char *suffix); /* * Check the string only contains digit characters. * * str * string to check for digits * * Returns: * 1 str only contains digit characters, 0 otherwise */ int all_digit(const char *str); #ifdef __cplusplus } #endif #endif /* _FRRSTR_H_ */ frr-7.2.1/lib/getopt.c0000644000000000000000000007265113610377563011443 00000000000000/* Getopt for GNU. * NOTE: getopt is now part of the C library, so if you don't know what * "Keep this file name-space clean" means, talk to drepper@gnu.org * before changing it! * * Copyright (C) 1987, 88, 89, 90, 91, 92, 93, 94, 95, 96, 97, 98 * Free Software Foundation, Inc. * * NOTE: The canonical source of this file is maintained with the GNU C Library. * Bugs can be reported to bug-glibc@gnu.org. * * This program is free software; you can redistribute it and/or modify it * under the terms of the GNU General Public License as published by the * Free Software Foundation; either version 2, 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; see the file COPYING; if not, write to the Free Software * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA */ /* This tells Alpha OSF/1 not to define a getopt prototype in . Ditto for AIX 3.2 and . */ #ifndef _NO_PROTO # define _NO_PROTO #endif #include #if !defined __STDC__ || !__STDC__ /* This is a separate conditional since some stdc systems reject `defined (const)'. */ #ifndef const # define const #endif #endif #include /* Comment out all this code if we are using the GNU C Library, and are not actually compiling the library itself. This code is part of the GNU C Library, but also included in many other GNU distributions. Compiling and linking in this code is a waste when using the GNU C library (especially if it is a shared library). Rather than having every GNU program understand `configure --with-gnu-libc' and omit the object files, it is simpler to just do this in the source for each such file. */ #define GETOPT_INTERFACE_VERSION 2 #if !defined _LIBC && defined __GLIBC__ && __GLIBC__ >= 2 #include #if _GNU_GETOPT_INTERFACE_VERSION == GETOPT_INTERFACE_VERSION # define ELIDE_CODE #endif #endif #ifndef ELIDE_CODE /* This needs to come after some library #include to get __GNU_LIBRARY__ defined. */ #ifdef __GNU_LIBRARY__ /* Don't include stdlib.h for non-GNU C libraries because some of them contain conflicting prototypes for getopt. */ #include #include #endif /* GNU C library. */ #ifdef VMS #include #if HAVE_STRING_H - 0 #include #endif #endif #ifndef _ /* This is for other GNU distributions with internationalized messages. When compiling libc, the _ macro is predefined. */ #ifdef HAVE_LIBINTL_H #include # define _(msgid) gettext (msgid) #else # define _(msgid) (msgid) #endif #endif /* This version of `getopt' appears to the caller like standard Unix `getopt' but it behaves differently for the user, since it allows the user to intersperse the options with the other arguments. As `getopt' works, it permutes the elements of ARGV so that, when it is done, all the options precede everything else. Thus all application programs are extended to handle flexible argument order. Setting the environment variable POSIXLY_CORRECT disables permutation. Then the behavior is completely standard. GNU application programs can use a third alternative mode in which they can distinguish the relative order of options and other arguments. */ #include "getopt.h" /* For communication from `getopt' to the caller. When `getopt' finds an option that takes an argument, the argument value is returned here. Also, when `ordering' is RETURN_IN_ORDER, each non-option ARGV-element is returned here. */ char *optarg = NULL; /* Index in ARGV of the next element to be scanned. This is used for communication to and from the caller and for communication between successive calls to `getopt'. On entry to `getopt', zero means this is the first call; initialize. When `getopt' returns -1, this is the index of the first of the non-option elements that the caller should itself scan. Otherwise, `optind' communicates from one call to the next how much of ARGV has been scanned so far. */ /* 1003.2 says this must be 1 before any call. */ int optind = 1; /* Formerly, initialization of getopt depended on optind==0, which causes problems with re-calling getopt as programs generally don't know that. */ int __getopt_initialized = 0; /* The next char to be scanned in the option-element in which the last option character we returned was found. This allows us to pick up the scan where we left off. If this is zero, or a null string, it means resume the scan by advancing to the next ARGV-element. */ static char *nextchar; /* Callers store zero here to inhibit the error message for unrecognized options. */ int opterr = 1; /* Set to an option character which was unrecognized. This must be initialized on some systems to avoid linking in the system's own getopt implementation. */ int optopt = '?'; /* Describe how to deal with options that follow non-option ARGV-elements. If the caller did not specify anything, the default is REQUIRE_ORDER if the environment variable POSIXLY_CORRECT is defined, PERMUTE otherwise. REQUIRE_ORDER means don't recognize them as options; stop option processing when the first non-option is seen. This is what Unix does. This mode of operation is selected by either setting the environment variable POSIXLY_CORRECT, or using `+' as the first character of the list of option characters. PERMUTE is the default. We permute the contents of ARGV as we scan, so that eventually all the non-options are at the end. This allows options to be given in any order, even with programs that were not written to expect this. RETURN_IN_ORDER is an option available to programs that were written to expect options and other ARGV-elements in any order and that care about the ordering of the two. We describe each non-option ARGV-element as if it were the argument of an option with character code 1. Using `-' as the first character of the list of option characters selects this mode of operation. The special argument `--' forces an end of option-scanning regardless of the value of `ordering'. In the case of RETURN_IN_ORDER, only `--' can cause `getopt' to return -1 with `optind' != ARGC. */ static enum { REQUIRE_ORDER, PERMUTE, RETURN_IN_ORDER } ordering; /* Value of POSIXLY_CORRECT environment variable. */ static char *posixly_correct; #ifdef __GNU_LIBRARY__ /* We want to avoid inclusion of string.h with non-GNU libraries because there are many ways it can cause trouble. On some systems, it contains special magic macros that don't work in GCC. */ #include # define my_index strchr #else #if HAVE_STRING_H #include #else #include #endif /* Avoid depending on library functions or files whose names are inconsistent. */ #ifndef getenv extern char *getenv(); #endif static char *my_index(str, chr) const char *str; int chr; { while (*str) { if (*str == chr) return (char *)str; str++; } return 0; } /* If using GCC, we can safely declare strlen this way. If not using GCC, it is ok not to declare it. */ #ifdef __GNUC__ /* Note that Motorola Delta 68k R3V7 comes with GCC but not stddef.h. That was relevant to code that was here before. */ #if (!defined __STDC__ || !__STDC__) && !defined strlen /* gcc with -traditional declares the built-in strlen to return int, and has done so at least since version 2.4.5. -- rms. */ extern int strlen(const char *); #endif /* not __STDC__ */ #endif /* __GNUC__ */ #endif /* not __GNU_LIBRARY__ */ /* Handle permutation of arguments. */ /* Describe the part of ARGV that contains non-options that have been skipped. `first_nonopt' is the index in ARGV of the first of them; `last_nonopt' is the index after the last of them. */ static int first_nonopt; static int last_nonopt; #ifdef _LIBC /* Bash 2.0 gives us an environment variable containing flags indicating ARGV elements that should not be considered arguments. */ /* Defined in getopt_init.c */ extern char *__getopt_nonoption_flags; static int nonoption_flags_max_len; static int nonoption_flags_len; static int original_argc; static char *const *original_argv; /* Make sure the environment variable bash 2.0 puts in the environment is valid for the getopt call we must make sure that the ARGV passed to getopt is that one passed to the process. */ static void __attribute__((unused)) store_args_and_env(int argc, char *const *argv) { /* XXX This is no good solution. We should rather copy the args so that we can compare them later. But we must not use malloc(3). */ original_argc = argc; original_argv = argv; } #ifdef text_set_element text_set_element(__libc_subinit, store_args_and_env); #endif /* text_set_element */ #define SWAP_FLAGS(ch1, ch2) \ if (nonoption_flags_len > 0) { \ char __tmp = __getopt_nonoption_flags[ch1]; \ __getopt_nonoption_flags[ch1] = __getopt_nonoption_flags[ch2]; \ __getopt_nonoption_flags[ch2] = __tmp; \ } #else /* !_LIBC */ # define SWAP_FLAGS(ch1, ch2) #endif /* _LIBC */ /* Exchange two adjacent subsequences of ARGV. One subsequence is elements [first_nonopt,last_nonopt) which contains all the non-options that have been skipped so far. The other is elements [last_nonopt,optind), which contains all the options processed since those non-options were skipped. `first_nonopt' and `last_nonopt' are relocated so that they describe the new indices of the non-options in ARGV after they are moved. */ #if defined __STDC__ && __STDC__ static void exchange(char **); #endif static void exchange(argv) char **argv; { int bottom = first_nonopt; int middle = last_nonopt; int top = optind; char *tem; /* Exchange the shorter segment with the far end of the longer segment. That puts the shorter segment into the right place. It leaves the longer segment in the right place overall, but it consists of two parts that need to be swapped next. */ #ifdef _LIBC /* First make sure the handling of the `__getopt_nonoption_flags' string can work normally. Our top argument must be in the range of the string. */ if (nonoption_flags_len > 0 && top >= nonoption_flags_max_len) { /* We must extend the array. The user plays games with us and presents new arguments. */ char *new_str = malloc(top + 1); if (new_str == NULL) nonoption_flags_len = nonoption_flags_max_len = 0; else { memset(__mempcpy(new_str, __getopt_nonoption_flags, nonoption_flags_max_len), '\0', top + 1 - nonoption_flags_max_len); nonoption_flags_max_len = top + 1; __getopt_nonoption_flags = new_str; } } #endif while (top > middle && middle > bottom) { if (top - middle > middle - bottom) { /* Bottom segment is the short one. */ int len = middle - bottom; register int i; /* Swap it with the top part of the top segment. */ for (i = 0; i < len; i++) { tem = argv[bottom + i]; argv[bottom + i] = argv[top - (middle - bottom) + i]; argv[top - (middle - bottom) + i] = tem; SWAP_FLAGS(bottom + i, top - (middle - bottom) + i); } /* Exclude the moved bottom segment from further * swapping. */ top -= len; } else { /* Top segment is the short one. */ int len = top - middle; register int i; /* Swap it with the bottom part of the bottom segment. */ for (i = 0; i < len; i++) { tem = argv[bottom + i]; argv[bottom + i] = argv[middle + i]; argv[middle + i] = tem; SWAP_FLAGS(bottom + i, middle + i); } /* Exclude the moved top segment from further swapping. */ bottom += len; } } /* Update records for the slots the non-options now occupy. */ first_nonopt += (optind - last_nonopt); last_nonopt = optind; } /* Initialize the internal data when the first call is made. */ #if defined __STDC__ && __STDC__ static const char *_getopt_initialize(int, char *const *, const char *); #endif static const char *_getopt_initialize(argc, argv, optstring) int argc; char *const *argv; const char *optstring; { /* Start processing options with ARGV-element 1 (since ARGV-element 0 is the program name); the sequence of previously skipped non-option ARGV-elements is empty. */ first_nonopt = last_nonopt = optind; nextchar = NULL; posixly_correct = getenv("POSIXLY_CORRECT"); /* Determine how to handle the ordering of options and nonoptions. */ if (optstring[0] == '-') { ordering = RETURN_IN_ORDER; ++optstring; } else if (optstring[0] == '+') { ordering = REQUIRE_ORDER; ++optstring; } else if (posixly_correct != NULL) ordering = REQUIRE_ORDER; else ordering = PERMUTE; #ifdef _LIBC if (posixly_correct == NULL && argc == original_argc && argv == original_argv) { if (nonoption_flags_max_len == 0) { if (__getopt_nonoption_flags == NULL || __getopt_nonoption_flags[0] == '\0') nonoption_flags_max_len = -1; else { const char *orig_str = __getopt_nonoption_flags; int len = nonoption_flags_max_len = strlen(orig_str); if (nonoption_flags_max_len < argc) nonoption_flags_max_len = argc; __getopt_nonoption_flags = (char *)malloc(nonoption_flags_max_len); if (__getopt_nonoption_flags == NULL) nonoption_flags_max_len = -1; else memset(__mempcpy( __getopt_nonoption_flags, orig_str, len), '\0', nonoption_flags_max_len - len); } } nonoption_flags_len = nonoption_flags_max_len; } else nonoption_flags_len = 0; #endif return optstring; } /* Scan elements of ARGV (whose length is ARGC) for option characters given in OPTSTRING. If an element of ARGV starts with '-', and is not exactly "-" or "--", then it is an option element. The characters of this element (aside from the initial '-') are option characters. If `getopt' is called repeatedly, it returns successively each of the option characters from each of the option elements. If `getopt' finds another option character, it returns that character, updating `optind' and `nextchar' so that the next call to `getopt' can resume the scan with the following option character or ARGV-element. If there are no more option characters, `getopt' returns -1. Then `optind' is the index in ARGV of the first ARGV-element that is not an option. (The ARGV-elements have been permuted so that those that are not options now come last.) OPTSTRING is a string containing the legitimate option characters. If an option character is seen that is not listed in OPTSTRING, return '?' after printing an error message. If you set `opterr' to zero, the error message is suppressed but we still return '?'. If a char in OPTSTRING is followed by a colon, that means it wants an arg, so the following text in the same ARGV-element, or the text of the following ARGV-element, is returned in `optarg'. Two colons mean an option that wants an optional arg; if there is text in the current ARGV-element, it is returned in `optarg', otherwise `optarg' is set to zero. If OPTSTRING starts with `-' or `+', it requests different methods of handling the non-option ARGV-elements. See the comments about RETURN_IN_ORDER and REQUIRE_ORDER, above. Long-named options begin with `--' instead of `-'. Their names may be abbreviated as long as the abbreviation is unique or is an exact match for some defined option. If they have an argument, it follows the option name in the same ARGV-element, separated from the option name by a `=', or else the in next ARGV-element. When `getopt' finds a long-named option, it returns 0 if that option's `flag' field is nonzero, the value of the option's `val' field if the `flag' field is zero. The elements of ARGV aren't really const, because we permute them. But we pretend they're const in the prototype to be compatible with other systems. LONGOPTS is a vector of `struct option' terminated by an element containing a name which is zero. LONGIND returns the index in LONGOPT of the long-named option found. It is only valid when a long-named option has been found by the most recent call. If LONG_ONLY is nonzero, '-' as well as '--' can introduce long-named options. */ int _getopt_internal(argc, argv, optstring, longopts, longind, long_only) int argc; char *const *argv; const char *optstring; const struct option *longopts; int *longind; int long_only; { optarg = NULL; if (optind == 0 || !__getopt_initialized) { if (optind == 0) optind = 1; /* Don't scan ARGV[0], the program name. */ optstring = _getopt_initialize(argc, argv, optstring); __getopt_initialized = 1; } /* Test whether ARGV[optind] points to a non-option argument. Either it does not have option syntax, or there is an environment flag from the shell indicating it is not an option. The later information is only used when the used in the GNU libc. */ #ifdef _LIBC #define NONOPTION_P \ (argv[optind][0] != '-' || argv[optind][1] == '\0' \ || (optind < nonoption_flags_len \ && __getopt_nonoption_flags[optind] == '1')) #else # define NONOPTION_P (argv[optind][0] != '-' || argv[optind][1] == '\0') #endif if (nextchar == NULL || *nextchar == '\0') { /* Advance to the next ARGV-element. */ /* Give FIRST_NONOPT & LAST_NONOPT rational values if OPTIND has been moved back by the user (who may also have changed the arguments). */ if (last_nonopt > optind) last_nonopt = optind; if (first_nonopt > optind) first_nonopt = optind; if (ordering == PERMUTE) { /* If we have just processed some options following some non-options, exchange them so that the options come first. */ if (first_nonopt != last_nonopt && last_nonopt != optind) exchange((char **)argv); else if (last_nonopt != optind) first_nonopt = optind; /* Skip any additional non-options and extend the range of non-options previously skipped. */ while (optind < argc && NONOPTION_P) optind++; last_nonopt = optind; } /* The special ARGV-element `--' means premature end of options. Skip it like a null option, then exchange with previous non-options as if it were an option, then skip everything else like a non-option. */ if (optind != argc && !strcmp(argv[optind], "--")) { optind++; if (first_nonopt != last_nonopt && last_nonopt != optind) exchange((char **)argv); else if (first_nonopt == last_nonopt) first_nonopt = optind; last_nonopt = argc; optind = argc; } /* If we have done all the ARGV-elements, stop the scan and back over any non-options that we skipped and permuted. */ if (optind == argc) { /* Set the next-arg-index to point at the non-options that we previously skipped, so the caller will digest them. */ if (first_nonopt != last_nonopt) optind = first_nonopt; return -1; } /* If we have come to a non-option and did not permute it, either stop the scan or describe it to the caller and pass it by. */ if (NONOPTION_P) { if (ordering == REQUIRE_ORDER) return -1; optarg = argv[optind++]; return 1; } /* We have found another option-ARGV-element. Skip the initial punctuation. */ nextchar = (argv[optind] + 1 + (longopts != NULL && argv[optind][1] == '-')); } /* Decode the current option-ARGV-element. */ /* Check whether the ARGV-element is a long option. If long_only and the ARGV-element has the form "-f", where f is a valid short option, don't consider it an abbreviated form of a long option that starts with f. Otherwise there would be no way to give the -f short option. On the other hand, if there's a long option "fubar" and the ARGV-element is "-fu", do consider that an abbreviation of the long option, just like "--fu", and not "-f" with arg "u". This distinction seems to be the most useful approach. */ if (longopts != NULL && (argv[optind][1] == '-' || (long_only && (argv[optind][2] || !my_index(optstring, argv[optind][1]))))) { char *nameend; const struct option *p; const struct option *pfound = NULL; int exact = 0; int ambig = 0; int indfound = -1; int option_index; for (nameend = nextchar; *nameend && *nameend != '='; nameend++) /* Do nothing. */; /* Test all long options for either exact match or abbreviated matches. */ for (p = longopts, option_index = 0; p->name; p++, option_index++) if (!strncmp(p->name, nextchar, nameend - nextchar)) { if ((unsigned int)(nameend - nextchar) == (unsigned int)strlen(p->name)) { /* Exact match found. */ pfound = p; indfound = option_index; exact = 1; break; } else if (pfound == NULL) { /* First nonexact match found. */ pfound = p; indfound = option_index; } else /* Second or later nonexact match found. */ ambig = 1; } if (ambig && !exact) { if (opterr) fprintf(stderr, _("%s: option `%s' is ambiguous\n"), argv[0], argv[optind]); nextchar += strlen(nextchar); optind++; optopt = 0; return '?'; } if (pfound != NULL) { option_index = indfound; optind++; if (*nameend) { /* Don't test has_arg with >, because some C compilers don't allow it to be used on enums. */ if (pfound->has_arg) optarg = nameend + 1; else { if (opterr) { if (argv[optind - 1][1] == '-') /* --option */ fprintf(stderr, _("%s: option `--%s' doesn't allow an argument\n"), argv[0], pfound->name); else /* +option or -option */ fprintf(stderr, _("%s: option `%c%s' doesn't allow an argument\n"), argv[0], argv[optind - 1] [0], pfound->name); } nextchar += strlen(nextchar); optopt = pfound->val; return '?'; } } else if (pfound->has_arg == 1) { if (optind < argc) optarg = argv[optind++]; else { if (opterr) fprintf(stderr, _("%s: option `%s' requires an argument\n"), argv[0], argv[optind - 1]); nextchar += strlen(nextchar); optopt = pfound->val; return optstring[0] == ':' ? ':' : '?'; } } nextchar += strlen(nextchar); if (longind != NULL) *longind = option_index; if (pfound->flag) { *(pfound->flag) = pfound->val; return 0; } return pfound->val; } /* Can't find it as a long option. If this is not getopt_long_only, or the option starts with '--' or is not a valid short option, then it's an error. Otherwise interpret it as a short option. */ if (!long_only || argv[optind][1] == '-' || my_index(optstring, *nextchar) == NULL) { if (opterr) { if (argv[optind][1] == '-') /* --option */ fprintf(stderr, _("%s: unrecognized option `--%s'\n"), argv[0], nextchar); else /* +option or -option */ fprintf(stderr, _("%s: unrecognized option `%c%s'\n"), argv[0], argv[optind][0], nextchar); } nextchar = (char *)""; optind++; optopt = 0; return '?'; } } /* Look at and handle the next short option-character. */ { char c = *nextchar++; char *temp = my_index(optstring, c); /* Increment `optind' when we start to process its last * character. */ if (*nextchar == '\0') ++optind; if (temp == NULL || c == ':') { if (opterr) { if (posixly_correct) /* 1003.2 specifies the format of this * message. */ fprintf(stderr, _("%s: illegal option -- %c\n"), argv[0], c); else fprintf(stderr, _("%s: invalid option -- %c\n"), argv[0], c); } optopt = c; return '?'; } /* Convenience. Treat POSIX -W foo same as long option --foo */ if (temp[0] == 'W' && temp[1] == ';') { char *nameend; const struct option *p; const struct option *pfound = NULL; int exact = 0; int ambig = 0; int indfound = 0; int option_index; /* This is an option that requires an argument. */ if (*nextchar != '\0') { optarg = nextchar; /* If we end this ARGV-element by taking the rest as an arg, we must advance to the next element now. */ optind++; } else if (optind == argc) { if (opterr) { /* 1003.2 specifies the format of this * message. */ fprintf(stderr, _("%s: option requires an argument -- %c\n"), argv[0], c); } optopt = c; if (optstring[0] == ':') c = ':'; else c = '?'; return c; } else /* We already incremented `optind' once; increment it again when taking next ARGV-elt as argument. */ optarg = argv[optind++]; /* optarg is now the argument, see if it's in the table of longopts. */ for (nextchar = nameend = optarg; *nameend && *nameend != '='; nameend++) /* Do nothing. */; /* Test all long options for either exact match or abbreviated matches. */ for (p = longopts, option_index = 0; p->name; p++, option_index++) if (!strncmp(p->name, nextchar, nameend - nextchar)) { if ((unsigned int)(nameend - nextchar) == strlen(p->name)) { /* Exact match found. */ pfound = p; indfound = option_index; exact = 1; break; } else if (pfound == NULL) { /* First nonexact match found. */ pfound = p; indfound = option_index; } else /* Second or later nonexact * match found. */ ambig = 1; } if (ambig && !exact) { if (opterr) fprintf(stderr, _("%s: option `-W %s' is ambiguous\n"), argv[0], argv[optind]); nextchar += strlen(nextchar); optind++; return '?'; } if (pfound != NULL) { option_index = indfound; if (*nameend) { /* Don't test has_arg with >, because some C compilers don't allow it to be used on enums. */ if (pfound->has_arg) optarg = nameend + 1; else { if (opterr) fprintf(stderr, _("\ %s: option `-W %s' doesn't allow an argument\n"), argv[0], pfound->name); nextchar += strlen(nextchar); return '?'; } } else if (pfound->has_arg == 1) { if (optind < argc) optarg = argv[optind++]; else { if (opterr) fprintf(stderr, _("%s: option `%s' requires an argument\n"), argv[0], argv[optind - 1]); nextchar += strlen(nextchar); return optstring[0] == ':' ? ':' : '?'; } } nextchar += strlen(nextchar); if (longind != NULL) *longind = option_index; if (pfound->flag) { *(pfound->flag) = pfound->val; return 0; } return pfound->val; } nextchar = NULL; return 'W'; /* Let the application handle it. */ } if (temp[1] == ':') { if (temp[2] == ':') { /* This is an option that accepts an argument * optionally. */ if (*nextchar != '\0') { optarg = nextchar; optind++; } else optarg = NULL; nextchar = NULL; } else { /* This is an option that requires an argument. */ if (*nextchar != '\0') { optarg = nextchar; /* If we end this ARGV-element by taking the rest as an arg, we must advance to the next element now. */ optind++; } else if (optind == argc) { if (opterr) { /* 1003.2 specifies the format * of this message. */ fprintf(stderr, _("%s: option requires an argument -- %c\n"), argv[0], c); } optopt = c; if (optstring[0] == ':') c = ':'; else c = '?'; } else /* We already incremented `optind' once; increment it again when taking next ARGV-elt as argument. */ optarg = argv[optind++]; nextchar = NULL; } } return c; } } #ifdef REALLY_NEED_PLAIN_GETOPT int getopt(argc, argv, optstring) int argc; char *const *argv; const char *optstring; { return _getopt_internal(argc, argv, optstring, (const struct option *)0, (int *)0, 0); } #endif /* REALLY_NEED_PLAIN_GETOPT */ #endif /* Not ELIDE_CODE. */ #ifdef TEST /* Compile with -DTEST to make an executable for use in testing the above definition of `getopt'. */ int main(argc, argv) int argc; char **argv; { int c; int digit_optind = 0; while (1) { int this_option_optind = optind ? optind : 1; c = getopt(argc, argv, "abc:d:0123456789"); if (c == -1) break; switch (c) { case '0': case '1': case '2': case '3': case '4': case '5': case '6': case '7': case '8': case '9': if (digit_optind != 0 && digit_optind != this_option_optind) printf("digits occur in two different argv-elements.\n"); digit_optind = this_option_optind; printf("option %c\n", c); break; case 'a': printf("option a\n"); break; case 'b': printf("option b\n"); break; case 'c': printf("option c with value `%s'\n", optarg); break; case '?': break; default: printf("?? getopt returned character code 0%o ??\n", c); } } if (optind < argc) { printf("non-option ARGV-elements: "); while (optind < argc) printf("%s ", argv[optind++]); printf("\n"); } exit(0); } #endif /* TEST */ frr-7.2.1/lib/getopt.h0000644000000000000000000001216313610377563011440 00000000000000/* Declarations for getopt. * Copyright (C) 1989,90,91,92,93,94,96,97 Free Software Foundation, Inc. * * NOTE: The canonical source of this file is maintained with the GNU C Library. * Bugs can be reported to bug-glibc@gnu.org. * * This program is free software; you can redistribute it and/or modify it * under the terms of the GNU General Public License as published by the * Free Software Foundation; either version 2, 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; see the file COPYING; if not, write to the Free Software * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA */ #ifndef _GETOPT_H #define _GETOPT_H 1 /* * The operating system may or may not provide getopt_long(), and if * so it may or may not be a version we are willing to use. Our * strategy is to declare getopt here, and then provide code unless * the supplied version is adequate. The difficult case is when a * declaration for getopt is provided, as our declaration must match. * * XXX Arguably this version should be named differently, and the * local names defined to refer to the system version when we choose * to use the system version. */ #ifdef __cplusplus extern "C" { #endif /* For communication from `getopt' to the caller. When `getopt' finds an option that takes an argument, the argument value is returned here. Also, when `ordering' is RETURN_IN_ORDER, each non-option ARGV-element is returned here. */ extern char *optarg; /* Index in ARGV of the next element to be scanned. This is used for communication to and from the caller and for communication between successive calls to `getopt'. On entry to `getopt', zero means this is the first call; initialize. When `getopt' returns -1, this is the index of the first of the non-option elements that the caller should itself scan. Otherwise, `optind' communicates from one call to the next how much of ARGV has been scanned so far. */ extern int optind; /* Callers store zero here to inhibit the error message `getopt' prints for unrecognized options. */ extern int opterr; /* Set to an option character which was unrecognized. */ extern int optopt; /* Describe the long-named options requested by the application. The LONG_OPTIONS argument to getopt_long or getopt_long_only is a vector of `struct option' terminated by an element containing a name which is zero. The field `has_arg' is: no_argument (or 0) if the option does not take an argument, required_argument (or 1) if the option requires an argument, optional_argument (or 2) if the option takes an optional argument. If the field `flag' is not NULL, it points to a variable that is set to the value given in the field `val' when the option is found, but left unchanged if the option is not found. To have a long-named option do something other than set an `int' to a compiled-in constant, such as set a value from `optarg', set the option's `flag' field to zero and its `val' field to a nonzero value (the equivalent single-letter option character, if there is one). For long options that have a zero `flag' field, `getopt' returns the contents of the `val' field. */ struct option { #if defined(__STDC__) && __STDC__ const char *name; #else char *name; #endif /* has_arg can't be an enum because some compilers complain about type mismatches in all the code that assumes it is an int. */ int has_arg; int *flag; int val; }; /* Names for the values of the `has_arg' field of `struct option'. */ #define no_argument 0 #define required_argument 1 #define optional_argument 2 #if defined(__STDC__) && __STDC__ #if REALLY_NEED_PLAIN_GETOPT /* * getopt is defined in POSIX.2. Assume that if the system defines * getopt that it complies with POSIX.2. If not, an autoconf test * should be written to define NONPOSIX_GETOPT_DEFINITION. */ #ifndef NONPOSIX_GETOPT_DEFINITION extern int getopt(int argc, char *const *argv, const char *shortopts); #else /* NONPOSIX_GETOPT_DEFINITION */ extern int getopt(void); #endif /* NONPOSIX_GETOPT_DEFINITION */ #endif extern int getopt_long(int argc, char *const *argv, const char *shortopts, const struct option *longopts, int *longind); extern int getopt_long_only(int argc, char *const *argv, const char *shortopts, const struct option *longopts, int *longind); /* Internal only. Users should not call this directly. */ extern int _getopt_internal(int argc, char *const *argv, const char *shortopts, const struct option *longopts, int *longind, int long_only); #else /* not __STDC__ */ #ifdef REALLY_NEED_PLAIN_GETOPT extern int getopt(); #endif /* REALLY_NEED_PLAIN_GETOPT */ extern int getopt_long(); extern int getopt_long_only(); extern int _getopt_internal(); #endif /* __STDC__ */ #ifdef __cplusplus } #endif #endif /* getopt.h */ frr-7.2.1/lib/getopt1.c0000644000000000000000000001052013610377563011507 00000000000000/* getopt_long and getopt_long_only entry points for GNU getopt. * Copyright (C) 1987,88,89,90,91,92,93,94,96,97,98 * Free Software Foundation, Inc. * * NOTE: The canonical source of this file is maintained with the GNU C Library. * Bugs can be reported to bug-glibc@gnu.org. * * This program is free software; you can redistribute it and/or modify it * under the terms of the GNU General Public License as published by the * Free Software Foundation; either version 2, 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; see the file COPYING; if not, write to the Free Software * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA */ #include #include "getopt.h" #if !defined __STDC__ || !__STDC__ /* This is a separate conditional since some stdc systems reject `defined (const)'. */ #ifndef const #define const #endif #endif #include /* Comment out all this code if we are using the GNU C Library, and are not actually compiling the library itself. This code is part of the GNU C Library, but also included in many other GNU distributions. Compiling and linking in this code is a waste when using the GNU C library (especially if it is a shared library). Rather than having every GNU program understand `configure --with-gnu-libc' and omit the object files, it is simpler to just do this in the source for each such file. */ #define GETOPT_INTERFACE_VERSION 2 #if !defined _LIBC && defined __GLIBC__ && __GLIBC__ >= 2 #include #if _GNU_GETOPT_INTERFACE_VERSION == GETOPT_INTERFACE_VERSION #define ELIDE_CODE #endif #endif #ifndef ELIDE_CODE /* This needs to come after some library #include to get __GNU_LIBRARY__ defined. */ #ifdef __GNU_LIBRARY__ #include #endif #ifndef NULL #define NULL 0 #endif int getopt_long(argc, argv, options, long_options, opt_index) int argc; char *const *argv; const char *options; const struct option *long_options; int *opt_index; { return _getopt_internal(argc, argv, options, long_options, opt_index, 0); } /* Like getopt_long, but '-' as well as '--' can indicate a long option. If an option that starts with '-' (not '--') doesn't match a long option, but does match a short option, it is parsed as a short option instead. */ int getopt_long_only(argc, argv, options, long_options, opt_index) int argc; char *const *argv; const char *options; const struct option *long_options; int *opt_index; { return _getopt_internal(argc, argv, options, long_options, opt_index, 1); } #endif /* Not ELIDE_CODE. */ #ifdef TEST #include int main(argc, argv) int argc; char **argv; { int c; int digit_optind = 0; while (1) { int this_option_optind = optind ? optind : 1; int option_index = 0; static struct option long_options[] = { {"add", 1, 0, 0}, {"append", 0, 0, 0}, {"delete", 1, 0, 0}, {"verbose", 0, 0, 0}, {"create", 0, 0, 0}, {"file", 1, 0, 0}, {0, 0, 0, 0}}; c = getopt_long(argc, argv, "abc:d:0123456789", long_options, &option_index); if (c == -1) break; switch (c) { case 0: printf("option %s", long_options[option_index].name); if (optarg) printf(" with arg %s", optarg); printf("\n"); break; case '0': case '1': case '2': case '3': case '4': case '5': case '6': case '7': case '8': case '9': if (digit_optind != 0 && digit_optind != this_option_optind) printf("digits occur in two different argv-elements.\n"); digit_optind = this_option_optind; printf("option %c\n", c); break; case 'a': printf("option a\n"); break; case 'b': printf("option b\n"); break; case 'c': printf("option c with value `%s'\n", optarg); break; case 'd': printf("option d with value `%s'\n", optarg); break; case '?': break; default: printf("?? getopt returned character code 0%o ??\n", c); } } if (optind < argc) { printf("non-option ARGV-elements: "); while (optind < argc) printf("%s ", argv[optind++]); printf("\n"); } exit(0); } #endif /* TEST */ frr-7.2.1/lib/gitversion.pl0000644000000000000000000000237613610377563012520 00000000000000#!/usr/bin/perl -w use strict; my $dir = shift; chdir $dir || die "$dir: $!\n"; my $gitdesc = `git describe --always --first-parent --tags --dirty --match 'frr-*' || echo -- \"0-gUNKNOWN\"`; chomp $gitdesc; my $gitsuffix = ($gitdesc =~ /([0-9a-fA-F]{7}(-dirty)?)$/) ? "-g$1" : "-gUNKNOWN"; printf STDERR "git suffix: %s\n", $gitsuffix; printf "#define GIT_SUFFIX \"%s\"\n", $gitsuffix; my $gitcommit = `git log -1 --format=\"%H\" || echo DEADBEEF`; chomp $gitcommit; open(BRANCHES, "git branch -a -v --abbrev=40|") || die "git branch: $!\n"; my @names = (); while () { chomp $_; if (/\s+(.*?)\s+$gitcommit/) { my $branch = $1; if ($branch =~ /^remotes\/(.*?)(\/.*)$/) { my $path = $2; my $url = `git config --get "remote.$1.url"`; chomp $url; $url =~ s/^(git:|https?:|git@)\/\/github\.com/github/i; $url =~ s/^(ssh|git):\/\/git\.sv\.gnu\.org\/srv\/git\//savannah:/i; $url =~ s/^(ssh|git):\/\/git\.savannah\.nongnu\.org\//savannah:/i; push @names, $url.$path; } else { push @names, 'local:'.$branch; } } } printf STDERR "git branches: %s\n", join(", ", @names); my $cr = "\\r\\n\\"; printf <string = command; cmd->doc = "0\n1\n2\n3\n4\n5\n6\n7\n8\n9\n10\n11\n12\n13\n14\n15\n16\n17\n18\n19\n"; cmd->func = NULL; // parse the command and install it into the command graph struct graph *graph = graph_new(); struct cmd_token *token = cmd_token_new(START_TKN, CMD_ATTR_NORMAL, NULL, NULL); graph_new_node(graph, token, (void (*)(void *)) & cmd_token_del); cmd_graph_parse(graph, cmd); cmd_graph_merge(nodegraph, graph, +1); return CMD_SUCCESS; } DEFUN (grammar_test_complete, grammar_test_complete_cmd, "grammar complete COMMAND...", GRAMMAR_STR "attempt to complete input on DFA\n" "command to complete\n") { check_nodegraph(); int idx_command = 2; char *cmdstr = argv_concat(argv, argc, idx_command); if (!cmdstr) return CMD_SUCCESS; vector command = cmd_make_strvec(cmdstr); if (!command) { XFREE(MTYPE_TMP, cmdstr); return CMD_SUCCESS; } // generate completions of user input struct list *completions; enum matcher_rv result = command_complete(nodegraph, command, &completions); // print completions or relevant error message if (!MATCHER_ERROR(result)) { vector comps = completions_to_vec(completions); struct cmd_token *tkn; // calculate length of longest tkn->text in completions unsigned int width = 0, i = 0; for (i = 0; i < vector_active(comps); i++) { tkn = vector_slot(comps, i); unsigned int len = strlen(tkn->text); width = len > width ? len : width; } // print completions for (i = 0; i < vector_active(comps); i++) { tkn = vector_slot(comps, i); vty_out(vty, " %-*s %s\n", width, tkn->text, tkn->desc); } for (i = 0; i < vector_active(comps); i++) cmd_token_del( (struct cmd_token *)vector_slot(comps, i)); vector_free(comps); } else vty_out(vty, "%% No match\n"); // free resources list_delete(&completions); cmd_free_strvec(command); XFREE(MTYPE_TMP, cmdstr); return CMD_SUCCESS; } DEFUN (grammar_test_match, grammar_test_match_cmd, "grammar match COMMAND...", GRAMMAR_STR "attempt to match input on DFA\n" "command to match\n") { check_nodegraph(); int idx_command = 2; if (argv[2]->arg[0] == '#') return CMD_SUCCESS; char *cmdstr = argv_concat(argv, argc, idx_command); if (!cmdstr) return CMD_SUCCESS; vector command = cmd_make_strvec(cmdstr); if (!command) { XFREE(MTYPE_TMP, cmdstr); return CMD_SUCCESS; } struct list *argvv = NULL; const struct cmd_element *element = NULL; enum matcher_rv result = command_match(nodegraph, command, &argvv, &element); // print completions or relevant error message if (element) { vty_out(vty, "Matched: %s\n", element->string); struct listnode *ln; struct cmd_token *token; for (ALL_LIST_ELEMENTS_RO(argvv, ln, token)) vty_out(vty, "%s -- %s\n", token->text, token->arg); vty_out(vty, "func: %p\n", element->func); list_delete(&argvv); } else { assert(MATCHER_ERROR(result)); switch (result) { case MATCHER_NO_MATCH: vty_out(vty, "%% Unknown command\n"); break; case MATCHER_INCOMPLETE: vty_out(vty, "%% Incomplete command\n"); break; case MATCHER_AMBIGUOUS: vty_out(vty, "%% Ambiguous command\n"); break; default: vty_out(vty, "%% Unknown error\n"); break; } } // free resources cmd_free_strvec(command); XFREE(MTYPE_TMP, cmdstr); return CMD_SUCCESS; } /** * Testing shim to test docstrings */ DEFUN (grammar_test_doc, grammar_test_doc_cmd, "grammar test docstring", GRAMMAR_STR "Test function for docstring\n" "Command end\n") { check_nodegraph(); // create cmd_element with docstring struct cmd_element *cmd = XCALLOC(MTYPE_CMD_TOKENS, sizeof(struct cmd_element)); cmd->string = XSTRDUP( MTYPE_CMD_TOKENS, "test docstring (1-255) end VARIABLE [OPTION|set lol] . VARARG"); cmd->doc = XSTRDUP(MTYPE_CMD_TOKENS, "Test stuff\n" "docstring thing\n" "first example\n" "second example\n" "follow\n" "random range\n" "end thingy\n" "variable\n" "optional variable\n" "optional set\n" "optional lol\n" "vararg!\n"); cmd->func = NULL; // parse element cmd_graph_parse(nodegraph, cmd); return CMD_SUCCESS; } /** * Debugging command to print command graph */ DEFUN (grammar_test_show, grammar_test_show_cmd, "grammar show [doc]", GRAMMAR_STR "print current accumulated DFA\n" "include docstrings\n") { check_nodegraph(); struct graph_node *stack[CMD_ARGC_MAX]; pretty_print_graph(vty, vector_slot(nodegraph->nodes, 0), 0, argc >= 3, stack, 0); return CMD_SUCCESS; } DEFUN (grammar_test_dot, grammar_test_dot_cmd, "grammar dotfile OUTNAME", GRAMMAR_STR "print current graph for dot\n" ".dot filename\n") { check_nodegraph(); FILE *ofd = fopen(argv[2]->arg, "w"); if (!ofd) { vty_out(vty, "%s: %s\r\n", argv[2]->arg, strerror(errno)); return CMD_SUCCESS; } char *dot = cmd_graph_dump_dot(nodegraph); fprintf(ofd, "%s", dot); fclose(ofd); XFREE(MTYPE_TMP, dot); return CMD_SUCCESS; } struct cmd_permute_item { char *cmd; struct cmd_element *el; }; static void cmd_permute_free(void *arg) { struct cmd_permute_item *i = arg; XFREE(MTYPE_TMP, i->cmd); XFREE(MTYPE_TMP, i); } static int cmd_permute_cmp(void *a, void *b) { struct cmd_permute_item *aa = a, *bb = b; return strcmp(aa->cmd, bb->cmd); } static void cmd_graph_permute(struct list *out, struct graph_node **stack, size_t stackpos, char *cmd) { struct graph_node *gn = stack[stackpos]; struct cmd_token *tok = gn->data; char *appendp = cmd + strlen(cmd); size_t j; if (tok->type < SPECIAL_TKN) { sprintf(appendp, "%s ", tok->text); appendp += strlen(appendp); } else if (tok->type == END_TKN) { struct cmd_permute_item *i = XMALLOC(MTYPE_TMP, sizeof(*i)); i->el = ((struct graph_node *)vector_slot(gn->to, 0))->data; i->cmd = XSTRDUP(MTYPE_TMP, cmd); i->cmd[strlen(cmd) - 1] = '\0'; listnode_add_sort(out, i); return; } if (++stackpos == CMD_ARGC_MAX) return; for (size_t i = 0; i < vector_active(gn->to); i++) { struct graph_node *gnext = vector_slot(gn->to, i); for (j = 0; j < stackpos; j++) if (stack[j] == gnext) break; if (j != stackpos) continue; stack[stackpos] = gnext; *appendp = '\0'; cmd_graph_permute(out, stack, stackpos, cmd); } } static struct list *cmd_graph_permutations(struct graph *graph) { char accumulate[2048] = ""; struct graph_node *stack[CMD_ARGC_MAX]; struct list *rv = list_new(); rv->cmp = cmd_permute_cmp; rv->del = cmd_permute_free; stack[0] = vector_slot(graph->nodes, 0); cmd_graph_permute(rv, stack, 0, accumulate); return rv; } extern vector cmdvec; DEFUN (grammar_findambig, grammar_findambig_cmd, "grammar find-ambiguous [{printall|nodescan}]", GRAMMAR_STR "Find ambiguous commands\n" "Print all permutations\n" "Scan all nodes\n") { struct list *commands; struct cmd_permute_item *prev = NULL, *cur = NULL; struct listnode *ln; int i, printall, scan, scannode = 0; int ambig = 0; i = 0; printall = argv_find(argv, argc, "printall", &i); i = 0; scan = argv_find(argv, argc, "nodescan", &i); if (scan && nodegraph_free) { graph_delete_graph(nodegraph_free); nodegraph_free = NULL; } if (!scan && !nodegraph) { vty_out(vty, "nodegraph uninitialized\r\n"); return CMD_WARNING_CONFIG_FAILED; } do { if (scan) { struct cmd_node *cnode = vector_slot(cmdvec, scannode++); if (!cnode) continue; nodegraph = cnode->cmdgraph; if (!nodegraph) continue; vty_out(vty, "scanning node %d (%s)\n", scannode - 1, node_names[scannode - 1]); } commands = cmd_graph_permutations(nodegraph); prev = NULL; for (ALL_LIST_ELEMENTS_RO(commands, ln, cur)) { int same = prev && !strcmp(prev->cmd, cur->cmd); if (printall && !same) vty_out(vty, "'%s' [%x]\n", cur->cmd, cur->el->daemon); if (same) { vty_out(vty, "'%s' AMBIGUOUS:\n", cur->cmd); vty_out(vty, " %s\n '%s'\n", prev->el->name, prev->el->string); vty_out(vty, " %s\n '%s'\n", cur->el->name, cur->el->string); vty_out(vty, "\n"); ambig++; } prev = cur; } list_delete(&commands); vty_out(vty, "\n"); } while (scan && scannode < LINK_PARAMS_NODE); vty_out(vty, "%d ambiguous commands found.\n", ambig); if (scan) nodegraph = NULL; return ambig == 0 ? CMD_SUCCESS : CMD_WARNING_CONFIG_FAILED; } DEFUN (grammar_init_graph, grammar_init_graph_cmd, "grammar init", GRAMMAR_STR "(re)initialize graph\n") { if (nodegraph_free) graph_delete_graph(nodegraph_free); nodegraph_free = NULL; init_cmdgraph(vty, &nodegraph); return CMD_SUCCESS; } DEFUN (grammar_access, grammar_access_cmd, "grammar access (0-65535)", GRAMMAR_STR "access node graph\n" "node number\n") { if (nodegraph_free) graph_delete_graph(nodegraph_free); nodegraph_free = NULL; struct cmd_node *cnode; cnode = vector_slot(cmdvec, atoi(argv[2]->arg)); if (!cnode) { vty_out(vty, "%% no such node\n"); return CMD_WARNING_CONFIG_FAILED; } vty_out(vty, "node %d\n", (int)cnode->node); nodegraph = cnode->cmdgraph; return CMD_SUCCESS; } /* this is called in vtysh.c to set up the testing shim */ void grammar_sandbox_init(void) { // install all enable elements install_element(ENABLE_NODE, &grammar_test_cmd); install_element(ENABLE_NODE, &grammar_test_show_cmd); install_element(ENABLE_NODE, &grammar_test_dot_cmd); install_element(ENABLE_NODE, &grammar_test_match_cmd); install_element(ENABLE_NODE, &grammar_test_complete_cmd); install_element(ENABLE_NODE, &grammar_test_doc_cmd); install_element(ENABLE_NODE, &grammar_findambig_cmd); install_element(ENABLE_NODE, &grammar_init_graph_cmd); install_element(ENABLE_NODE, &grammar_access_cmd); } /** * Pretty-prints a graph, assuming it is a tree. * * @param start the node to take as the root * @param level indent level for recursive calls, always pass 0 */ void pretty_print_graph(struct vty *vty, struct graph_node *start, int level, int desc, struct graph_node **stack, size_t stackpos) { // print this node char tokennum[32]; struct cmd_token *tok = start->data; snprintf(tokennum, sizeof(tokennum), "%d?", tok->type); vty_out(vty, "%s", lookup_msg(tokennames, tok->type, NULL)); if (tok->text) vty_out(vty, ":\"%s\"", tok->text); if (tok->varname) vty_out(vty, " => %s", tok->varname); if (desc) vty_out(vty, " ?'%s'", tok->desc); vty_out(vty, " "); if (stackpos == CMD_ARGC_MAX) { vty_out(vty, " -aborting! (depth limit)\n"); return; } stack[stackpos++] = start; int numto = desc ? 2 : vector_active(start->to); if (numto) { if (numto > 1) vty_out(vty, "\n"); for (unsigned int i = 0; i < vector_active(start->to); i++) { struct graph_node *adj = vector_slot(start->to, i); // if we're listing multiple children, indent! if (numto > 1) for (int j = 0; j < level + 1; j++) vty_out(vty, " "); // if this node is a vararg, just print * if (adj == start) vty_out(vty, "*"); else if (((struct cmd_token *)adj->data)->type == END_TKN) vty_out(vty, "--END\n"); else { size_t k; for (k = 0; k < stackpos; k++) if (stack[k] == adj) { vty_out(vty, "< 1 ? level + 1 : level, desc, stack, stackpos); } } } else vty_out(vty, "\n"); } /** stuff that should go in command.c + command.h */ void init_cmdgraph(struct vty *vty, struct graph **graph) { // initialize graph, add start noe *graph = graph_new(); nodegraph_free = *graph; struct cmd_token *token = cmd_token_new(START_TKN, 0, NULL, NULL); graph_new_node(*graph, token, (void (*)(void *)) & cmd_token_del); if (vty) vty_out(vty, "initialized graph\n"); } frr-7.2.1/lib/grammar_sandbox_main.c0000644000000000000000000000360013610377563014275 00000000000000/* * Testing shim and API examples for the new CLI backend. * * Minimal main() to run grammar_sandbox standalone. * [split off grammar_sandbox.c 2017-01-23] * -- * Copyright (C) 2016 Cumulus Networks, Inc. * Copyright (C) 2017 David Lamparter for NetDEF, Inc. * * This file is part of FreeRangeRouting (FRR). * * FRR is free software; you can redistribute it and/or modify it under the * terms of the GNU General Public License as published by the Free Software * Foundation; either version 2, or (at your option) any later version. * * FRR 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; see the file COPYING; if not, write to the Free Software * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA */ #ifdef HAVE_CONFIG_H #include "config.h" #endif #include "command.h" #include "memory_vty.h" static void vty_do_exit(int isexit) { printf("\nend.\n"); if (!isexit) exit(0); } struct thread_master *master; int main(int argc, char **argv) { struct thread thread; master = thread_master_create(NULL); openzlog("grammar_sandbox", "NONE", 0, LOG_CONS | LOG_NDELAY | LOG_PID, LOG_DAEMON); zlog_set_level(ZLOG_DEST_SYSLOG, ZLOG_DISABLED); zlog_set_level(ZLOG_DEST_STDOUT, LOG_DEBUG); zlog_set_level(ZLOG_DEST_MONITOR, ZLOG_DISABLED); /* Library inits. */ cmd_init(1); host.name = strdup("test"); host.domainname = strdup("testdomainname"); vty_init(master, true); memory_init(); yang_init(); nb_init(master, NULL, 0); vty_stdio(vty_do_exit); /* Fetch next active thread. */ while (thread_fetch(master, &thread)) thread_call(&thread); /* Not reached. */ exit(0); } frr-7.2.1/lib/graph.c0000644000000000000000000001340213610377563011227 00000000000000/* * Graph data structure. * * -- * Copyright (C) 2016 Cumulus Networks, Inc. * * This file is part of GNU Zebra. * * GNU Zebra is free software; you can redistribute it and/or modify it * under the terms of the GNU General Public License as published by the * Free Software Foundation; either version 2, or (at your option) any * later version. * * GNU Zebra 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; see the file COPYING; if not, write to the Free Software * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA */ #include #include "graph.h" #include "memory.h" #include "buffer.h" DEFINE_MTYPE_STATIC(LIB, GRAPH, "Graph") DEFINE_MTYPE_STATIC(LIB, GRAPH_NODE, "Graph Node") struct graph *graph_new(void) { struct graph *graph = XCALLOC(MTYPE_GRAPH, sizeof(struct graph)); graph->nodes = vector_init(VECTOR_MIN_SIZE); return graph; } void graph_delete_graph(struct graph *graph) { for (unsigned int i = vector_active(graph->nodes); i--; /**/) graph_delete_node(graph, vector_slot(graph->nodes, i)); vector_free(graph->nodes); XFREE(MTYPE_GRAPH, graph); } struct graph_node *graph_new_node(struct graph *graph, void *data, void (*del)(void *)) { struct graph_node *node = XCALLOC(MTYPE_GRAPH_NODE, sizeof(struct graph_node)); node->from = vector_init(VECTOR_MIN_SIZE); node->to = vector_init(VECTOR_MIN_SIZE); node->data = data; node->del = del; vector_set(graph->nodes, node); return node; } static void graph_vector_remove(vector v, unsigned int ix) { if (ix >= v->active) return; /* v->active is guaranteed >= 1 because ix can't be lower than 0 * and v->active is > ix. */ v->active--; /* if ix == v->active--, we set the item to itself, then to NULL... * still correct, no check neccessary. */ v->index[ix] = v->index[v->active]; v->index[v->active] = NULL; } void graph_delete_node(struct graph *graph, struct graph_node *node) { if (!node) return; // an adjacent node struct graph_node *adj; // remove all edges from other nodes to us for (unsigned int i = vector_active(node->from); i--; /**/) { adj = vector_slot(node->from, i); graph_remove_edge(adj, node); } // remove all edges from us to other nodes for (unsigned int i = vector_active(node->to); i--; /**/) { adj = vector_slot(node->to, i); graph_remove_edge(node, adj); } // if there is a deletion callback, call it if (node->del && node->data) (*node->del)(node->data); // free adjacency lists vector_free(node->to); vector_free(node->from); // remove node from graph->nodes for (unsigned int i = vector_active(graph->nodes); i--; /**/) if (vector_slot(graph->nodes, i) == node) { graph_vector_remove(graph->nodes, i); break; } // free the node itself XFREE(MTYPE_GRAPH_NODE, node); } struct graph_node *graph_add_edge(struct graph_node *from, struct graph_node *to) { vector_set(from->to, to); vector_set(to->from, from); return to; } void graph_remove_edge(struct graph_node *from, struct graph_node *to) { // remove from from to->from for (unsigned int i = vector_active(to->from); i--; /**/) if (vector_slot(to->from, i) == from) { graph_vector_remove(to->from, i); break; } // remove to from from->to for (unsigned int i = vector_active(from->to); i--; /**/) if (vector_slot(from->to, i) == to) { graph_vector_remove(from->to, i); break; } } struct graph_node *graph_find_node(struct graph *graph, void *data) { struct graph_node *g; for (unsigned int i = vector_active(graph->nodes); i--; /**/) { g = vector_slot(graph->nodes, i); if (g->data == data) return g; } return NULL; } bool graph_has_edge(struct graph_node *from, struct graph_node *to) { for (unsigned int i = vector_active(from->to); i--; /**/) if (vector_slot(from->to, i) == to) return true; return false; } static void _graph_dfs(struct graph *graph, struct graph_node *start, vector visited, void (*dfs_cb)(struct graph_node *, void *), void *arg) { /* check that we have not visited this node */ for (unsigned int i = 0; i < vector_active(visited); i++) { if (start == vector_slot(visited, i)) return; } /* put this node in visited stack */ vector_ensure(visited, vector_active(visited)); vector_set_index(visited, vector_active(visited), start); /* callback */ dfs_cb(start, arg); /* recurse into children */ for (unsigned int i = vector_active(start->to); i--; /**/) { struct graph_node *c = vector_slot(start->to, i); _graph_dfs(graph, c, visited, dfs_cb, arg); } } void graph_dfs(struct graph *graph, struct graph_node *start, void (*dfs_cb)(struct graph_node *, void *), void *arg) { vector visited = vector_init(VECTOR_MIN_SIZE); _graph_dfs(graph, start, visited, dfs_cb, arg); vector_free(visited); } #ifndef BUILDING_CLIPPY void graph_dump_dot_default_print_cb(struct graph_node *gn, struct buffer *buf) { char nbuf[64]; for (unsigned int i = 0; i < vector_active(gn->to); i++) { struct graph_node *adj = vector_slot(gn->to, i); snprintf(nbuf, sizeof(nbuf), " n%p -> n%p;\n", gn, adj); buffer_putstr(buf, nbuf); } } char *graph_dump_dot(struct graph *graph, struct graph_node *start, void (*pcb)(struct graph_node *, struct buffer *)) { struct buffer *buf = buffer_new(0); char *ret; pcb = (pcb) ? pcb : graph_dump_dot_default_print_cb; buffer_putstr(buf, "digraph {\n"); graph_dfs(graph, start, (void (*)(struct graph_node *, void *))pcb, buf); buffer_putstr(buf, "}\n"); ret = buffer_getstr(buf); buffer_free(buf); return ret; } #endif /* BUILDING_CLIPPY */ frr-7.2.1/lib/graph.h0000644000000000000000000001172713610377563011244 00000000000000/* * Graph data structure. * * -- * Copyright (C) 2016 Cumulus Networks, Inc. * * This file is part of GNU Zebra. * * GNU Zebra is free software; you can redistribute it and/or modify it * under the terms of the GNU General Public License as published by the * Free Software Foundation; either version 2, or (at your option) any * later version. * * GNU Zebra 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; see the file COPYING; if not, write to the Free Software * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA */ #ifndef _ZEBRA_COMMAND_GRAPH_H #define _ZEBRA_COMMAND_GRAPH_H #include #include "vector.h" #include "buffer.h" #ifdef __cplusplus extern "C" { #endif struct graph { vector nodes; }; struct graph_node { vector from; // nodes which have edges to this node vector to; // nodes which this node has edges to void *data; // node data void (*del)(void *data); // deletion callback }; struct graph *graph_new(void); /** * Creates a new node. * * @struct graph the graph this node exists in * @param[in] data this node's data * @param[in] del data deletion callback * @return the new node */ struct graph_node *graph_new_node(struct graph *graph, void *data, void (*del)(void *)); /** * Deletes a node. * * Before deletion, this function removes all edges to and from this node from * any neighbor nodes. * * If *data and *del are non-null, the following call is made: * (*node->del) (node->data); * * @param[in] graph the graph this node belongs to * @param[out] node pointer to node to delete */ void graph_delete_node(struct graph *graph, struct graph_node *node); /** * Makes a directed edge between two nodes. * * @param[in] from * @param[in] to * @return to */ struct graph_node *graph_add_edge(struct graph_node *from, struct graph_node *to); /** * Removes a directed edge between two nodes. * * @param[in] from * @param[in] to */ void graph_remove_edge(struct graph_node *from, struct graph_node *to); /** * Deletes a graph. * Calls graph_delete_node on each node before freeing the graph struct itself. * * @param graph the graph to delete */ void graph_delete_graph(struct graph *graph); /* * Finds a node in the graph. * * @param[in] graph the graph to search in * @param[in] data the data to key off * @return the first graph node whose data pointer matches `data` */ struct graph_node *graph_find_node(struct graph *graph, void *data); /* * Determines whether two nodes have a directed edge between them. * * @param from * @param to * @return whether there is a directed edge from `from` to `to`. */ bool graph_has_edge(struct graph_node *from, struct graph_node *to); /* * Depth-first search. * * Performs a depth-first traversal of the given graph, visiting each node * exactly once and calling the user-provided callback for each visit. * * @param graph the graph to operate on * @param start the node to take as the root * @param dfs_cb callback called for each node visited in the traversal * @param arg argument to provide to dfs_cb */ void graph_dfs(struct graph *graph, struct graph_node *start, void (*dfs_cb)(struct graph_node *, void *), void *arg); #ifndef BUILDING_CLIPPY /* * Clippy relies on a small subset of sources in lib/, but it cannot link * libfrr since clippy itself is required to build libfrr. Instead it directly * includes the sources it needs. One of these is the command graph * implementation, which wraps this graph implementation. Since we need to use * the buffer.[ch] sources here, which indirectly rely on most of libfrr, we * have to ignore them when compiling clippy to avoid build dependency issues. * * TODO: Fix clippy build. */ /* * Default node printer for use with graph_dump_dot. * * @param gn the node to print * @param buf the buffer to print into */ void graph_dump_dot_default_print_cb(struct graph_node *gn, struct buffer *buf); /* * Prints a graph in the DOT language. * * The generated output is produced from a depth-first traversal of the graph. * * @param graph the graph to print * @param start the node to take as the root * @param pcb callback called for each node in the traversal that should * print the node in the DOT language. Passing NULL for this argument * will use the default printer. See graph_dump_dot_default_print_cb for * an example. * @return representation of graph in DOT language, allocated with MTYPE_TMP. * Caller is responsible for freeing this string. */ char *graph_dump_dot(struct graph *graph, struct graph_node *start, void (*pcb)(struct graph_node *, struct buffer *buf)); #endif /* BUILDING_CLIPPY */ #ifdef __cplusplus } #endif #endif /* _ZEBRA_COMMAND_GRAPH_H */ frr-7.2.1/lib/hash.c0000644000000000000000000002600213610377563011051 00000000000000/* Hash routine. * Copyright (C) 1998 Kunihiro Ishiguro * * This file is part of GNU Zebra. * * GNU Zebra is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License as published * by the Free Software Foundation; either version 2, or (at your * option) any later version. * * GNU Zebra 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; see the file COPYING; if not, write to the Free Software * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA */ #include #include #include "hash.h" #include "memory.h" #include "linklist.h" #include "termtable.h" #include "vty.h" #include "command.h" #include "libfrr.h" #include "frr_pthread.h" DEFINE_MTYPE_STATIC(LIB, HASH, "Hash") DEFINE_MTYPE_STATIC(LIB, HASH_BACKET, "Hash Bucket") DEFINE_MTYPE_STATIC(LIB, HASH_INDEX, "Hash Index") static pthread_mutex_t _hashes_mtx = PTHREAD_MUTEX_INITIALIZER; static struct list *_hashes; struct hash *hash_create_size(unsigned int size, unsigned int (*hash_key)(const void *), bool (*hash_cmp)(const void *, const void *), const char *name) { struct hash *hash; assert((size & (size - 1)) == 0); hash = XCALLOC(MTYPE_HASH, sizeof(struct hash)); hash->index = XCALLOC(MTYPE_HASH_INDEX, sizeof(struct hash_bucket *) * size); hash->size = size; hash->hash_key = hash_key; hash->hash_cmp = hash_cmp; hash->count = 0; hash->name = name ? XSTRDUP(MTYPE_HASH, name) : NULL; hash->stats.empty = hash->size; frr_with_mutex(&_hashes_mtx) { if (!_hashes) _hashes = list_new(); listnode_add(_hashes, hash); } return hash; } struct hash *hash_create(unsigned int (*hash_key)(const void *), bool (*hash_cmp)(const void *, const void *), const char *name) { return hash_create_size(HASH_INITIAL_SIZE, hash_key, hash_cmp, name); } void *hash_alloc_intern(void *arg) { return arg; } #define hash_update_ssq(hz, old, new) \ atomic_fetch_add_explicit(&hz->stats.ssq, (new + old) * (new - old), \ memory_order_relaxed); /* Expand hash if the chain length exceeds the threshold. */ static void hash_expand(struct hash *hash) { unsigned int i, new_size; struct hash_bucket *hb, *hbnext, **new_index; new_size = hash->size * 2; if (hash->max_size && new_size > hash->max_size) return; new_index = XCALLOC(MTYPE_HASH_INDEX, sizeof(struct hash_bucket *) * new_size); hash->stats.empty = new_size; for (i = 0; i < hash->size; i++) for (hb = hash->index[i]; hb; hb = hbnext) { unsigned int h = hb->key & (new_size - 1); hbnext = hb->next; hb->next = new_index[h]; int oldlen = hb->next ? hb->next->len : 0; int newlen = oldlen + 1; if (newlen == 1) hash->stats.empty--; else hb->next->len = 0; hb->len = newlen; hash_update_ssq(hash, oldlen, newlen); new_index[h] = hb; } /* Switch to new table */ XFREE(MTYPE_HASH_INDEX, hash->index); hash->size = new_size; hash->index = new_index; } void *hash_get(struct hash *hash, void *data, void *(*alloc_func)(void *)) { unsigned int key; unsigned int index; void *newdata; struct hash_bucket *bucket; if (!alloc_func && !hash->count) return NULL; key = (*hash->hash_key)(data); index = key & (hash->size - 1); for (bucket = hash->index[index]; bucket != NULL; bucket = bucket->next) { if (bucket->key == key && (*hash->hash_cmp)(bucket->data, data)) return bucket->data; } if (alloc_func) { newdata = (*alloc_func)(data); if (newdata == NULL) return NULL; if (HASH_THRESHOLD(hash->count + 1, hash->size)) { hash_expand(hash); index = key & (hash->size - 1); } bucket = XCALLOC(MTYPE_HASH_BACKET, sizeof(struct hash_bucket)); bucket->data = newdata; bucket->key = key; bucket->next = hash->index[index]; hash->index[index] = bucket; hash->count++; int oldlen = bucket->next ? bucket->next->len : 0; int newlen = oldlen + 1; if (newlen == 1) hash->stats.empty--; else bucket->next->len = 0; bucket->len = newlen; hash_update_ssq(hash, oldlen, newlen); return bucket->data; } return NULL; } void *hash_lookup(struct hash *hash, void *data) { return hash_get(hash, data, NULL); } unsigned int string_hash_make(const char *str) { unsigned int hash = 0; while (*str) hash = (hash * 33) ^ (unsigned int)*str++; return hash; } void *hash_release(struct hash *hash, void *data) { void *ret; unsigned int key; unsigned int index; struct hash_bucket *bucket; struct hash_bucket *pp; key = (*hash->hash_key)(data); index = key & (hash->size - 1); for (bucket = pp = hash->index[index]; bucket; bucket = bucket->next) { if (bucket->key == key && (*hash->hash_cmp)(bucket->data, data)) { int oldlen = hash->index[index]->len; int newlen = oldlen - 1; if (bucket == pp) hash->index[index] = bucket->next; else pp->next = bucket->next; if (hash->index[index]) hash->index[index]->len = newlen; else hash->stats.empty++; hash_update_ssq(hash, oldlen, newlen); ret = bucket->data; XFREE(MTYPE_HASH_BACKET, bucket); hash->count--; return ret; } pp = bucket; } return NULL; } void hash_iterate(struct hash *hash, void (*func)(struct hash_bucket *, void *), void *arg) { unsigned int i; struct hash_bucket *hb; struct hash_bucket *hbnext; for (i = 0; i < hash->size; i++) for (hb = hash->index[i]; hb; hb = hbnext) { /* get pointer to next hash bucket here, in case (*func) * decides to delete hb by calling hash_release */ hbnext = hb->next; (*func)(hb, arg); } } void hash_walk(struct hash *hash, int (*func)(struct hash_bucket *, void *), void *arg) { unsigned int i; struct hash_bucket *hb; struct hash_bucket *hbnext; int ret = HASHWALK_CONTINUE; for (i = 0; i < hash->size; i++) { for (hb = hash->index[i]; hb; hb = hbnext) { /* get pointer to next hash bucket here, in case (*func) * decides to delete hb by calling hash_release */ hbnext = hb->next; ret = (*func)(hb, arg); if (ret == HASHWALK_ABORT) return; } } } void hash_clean(struct hash *hash, void (*free_func)(void *)) { unsigned int i; struct hash_bucket *hb; struct hash_bucket *next; for (i = 0; i < hash->size; i++) { for (hb = hash->index[i]; hb; hb = next) { next = hb->next; if (free_func) (*free_func)(hb->data); XFREE(MTYPE_HASH_BACKET, hb); hash->count--; } hash->index[i] = NULL; } hash->stats.ssq = 0; hash->stats.empty = hash->size; } static void hash_to_list_iter(struct hash_bucket *hb, void *arg) { struct list *list = arg; listnode_add(list, hb->data); } struct list *hash_to_list(struct hash *hash) { struct list *list = list_new(); hash_iterate(hash, hash_to_list_iter, list); return list; } void hash_free(struct hash *hash) { frr_with_mutex(&_hashes_mtx) { if (_hashes) { listnode_delete(_hashes, hash); if (_hashes->count == 0) { list_delete(&_hashes); } } } XFREE(MTYPE_HASH, hash->name); XFREE(MTYPE_HASH_INDEX, hash->index); XFREE(MTYPE_HASH, hash); } /* CLI commands ------------------------------------------------------------ */ DEFUN_NOSH(show_hash_stats, show_hash_stats_cmd, "show debugging hashtable [statistics]", SHOW_STR DEBUG_STR "Statistics about hash tables\n" "Statistics about hash tables\n") { struct hash *h; struct listnode *ln; struct ttable *tt = ttable_new(&ttable_styles[TTSTYLE_BLANK]); ttable_add_row(tt, "Hash table|Buckets|Entries|Empty|LF|SD|FLF|SD"); tt->style.cell.lpad = 2; tt->style.cell.rpad = 1; tt->style.corner = '+'; ttable_restyle(tt); ttable_rowseps(tt, 0, BOTTOM, true, '-'); /* Summary statistics calculated are: * * - Load factor: This is the number of elements in the table divided * by the number of buckets. Since this hash table implementation * uses chaining, this value can be greater than 1. * This number provides information on how 'full' the table is, but * does not provide information on how evenly distributed the * elements are. * Notably, a load factor >= 1 does not imply that every bucket has * an element; with a pathological hash function, all elements could * be in a single bucket. * * - Full load factor: this is the number of elements in the table * divided by the number of buckets that have some elements in them. * * - Std. Dev.: This is the standard deviation calculated from the * relevant load factor. If the load factor is the mean of number of * elements per bucket, the standard deviation measures how much any * particular bucket is likely to deviate from the mean. * As a rule of thumb this number should be less than 2, and ideally * <= 1 for optimal performance. A number larger than 3 generally * indicates a poor hash function. */ double lf; // load factor double flf; // full load factor double var; // overall variance double fvar; // full variance double stdv; // overall stddev double fstdv; // full stddev long double x2; // h->count ^ 2 long double ldc; // (long double) h->count long double full; // h->size - h->stats.empty long double ssq; // ssq casted to long double pthread_mutex_lock(&_hashes_mtx); if (!_hashes) { pthread_mutex_unlock(&_hashes_mtx); ttable_del(tt); vty_out(vty, "No hash tables in use.\n"); return CMD_SUCCESS; } for (ALL_LIST_ELEMENTS_RO(_hashes, ln, h)) { if (!h->name) continue; ssq = (long double)h->stats.ssq; x2 = h->count * h->count; ldc = (long double)h->count; full = h->size - h->stats.empty; lf = h->count / (double)h->size; flf = full ? h->count / (double)(full) : 0; var = ldc ? (1.0 / ldc) * (ssq - x2 / ldc) : 0; fvar = full ? (1.0 / full) * (ssq - x2 / full) : 0; var = (var < .0001) ? 0 : var; fvar = (fvar < .0001) ? 0 : fvar; stdv = sqrt(var); fstdv = sqrt(fvar); ttable_add_row(tt, "%s|%d|%ld|%.0f%%|%.2lf|%.2lf|%.2lf|%.2lf", h->name, h->size, h->count, (h->stats.empty / (double)h->size) * 100, lf, stdv, flf, fstdv); } pthread_mutex_unlock(&_hashes_mtx); /* display header */ char header[] = "Showing hash table statistics for "; char underln[sizeof(header) + strlen(frr_protonameinst)]; memset(underln, '-', sizeof(underln)); underln[sizeof(underln) - 1] = '\0'; vty_out(vty, "%s%s\n", header, frr_protonameinst); vty_out(vty, "%s\n", underln); vty_out(vty, "# allocated: %d\n", _hashes->count); vty_out(vty, "# named: %d\n\n", tt->nrows - 1); if (tt->nrows > 1) { ttable_colseps(tt, 0, RIGHT, true, '|'); char *table = ttable_dump(tt, "\n"); vty_out(vty, "%s\n", table); XFREE(MTYPE_TMP, table); } else vty_out(vty, "No named hash tables to display.\n"); ttable_del(tt); return CMD_SUCCESS; } void hash_cmd_init(void) { install_element(ENABLE_NODE, &show_hash_stats_cmd); } frr-7.2.1/lib/hash.h0000644000000000000000000002242513610377563011063 00000000000000/* Hash routine. * Copyright (C) 1998 Kunihiro Ishiguro * * This file is part of GNU Zebra. * * GNU Zebra is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License as published * by the Free Software Foundation; either version 2, or (at your * option) any later version. * * GNU Zebra 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; see the file COPYING; if not, write to the Free Software * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA */ #ifndef _ZEBRA_HASH_H #define _ZEBRA_HASH_H #include "memory.h" #include "frratomic.h" #ifdef __cplusplus extern "C" { #endif /* Default hash table size. */ #define HASH_INITIAL_SIZE 256 /* Expansion threshold */ #define HASH_THRESHOLD(used, size) ((used) > (size)) #define HASHWALK_CONTINUE 0 #define HASHWALK_ABORT -1 #if CONFDATE > 20200225 CPP_NOTICE("hash.h: time to remove hash_backet #define") #endif #define hash_backet hash_bucket struct hash_bucket { /* * if this bucket is the head of the linked listed, len denotes the * number of elements in the list */ int len; /* Linked list. */ struct hash_bucket *next; /* Hash key. */ unsigned int key; /* Data. */ void *data; }; struct hashstats { /* number of empty hash buckets */ atomic_uint_fast32_t empty; /* sum of squares of bucket length */ atomic_uint_fast32_t ssq; }; struct hash { /* Hash bucket. */ struct hash_bucket **index; /* Hash table size. Must be power of 2 */ unsigned int size; /* If max_size is 0 there is no limit */ unsigned int max_size; /* Key make function. */ unsigned int (*hash_key)(const void *); /* Data compare function. */ bool (*hash_cmp)(const void *, const void *); /* Backet alloc. */ unsigned long count; struct hashstats stats; /* hash name */ char *name; }; #define hashcount(X) ((X)->count) /* * Create a hash table. * * The created hash table uses chaining and a user-provided comparator function * to resolve collisions. For best performance use a perfect hash function. * Worst case lookup time is O(N) when using a constant hash function. Best * case lookup time is O(1) when using a perfect hash function. * * The initial size of the created hash table is HASH_INITIAL_SIZE. * * hash_key * hash function to use; should return a unique unsigned integer when called * with a data item. Collisions are acceptable. * * hash_cmp * comparison function used for resolving collisions; when called with two * data items, should return nonzero if the two items are equal and 0 * otherwise * * name * optional name for the hashtable; this is used when displaying global * hashtable statistics. If this parameter is NULL the hash's name will be * set to NULL and the default name will be displayed when showing * statistics. * * Returns: * a new hash table */ extern struct hash *hash_create(unsigned int (*hash_key)(const void *), bool (*hash_cmp)(const void *, const void *), const char *name); /* * Create a hash table. * * The created hash table uses chaining and a user-provided comparator function * to resolve collisions. For best performance use a perfect hash function. * Worst case lookup time is O(N) when using a constant hash function. Best * case lookup time is O(1) when using a perfect hash function. * * size * initial number of hash buckets to allocate; must be a power of 2 or the * program will assert * * hash_key * hash function to use; should return a unique unsigned integer when called * with a data item. Collisions are acceptable. * * hash_cmp * comparison function used for resolving collisions; when called with two * data items, should return nonzero if the two items are equal and 0 * otherwise * * name * optional name for the hashtable; this is used when displaying global * hashtable statistics. If this parameter is NULL the hash's name will be * set to NULL and the default name will be displayed when showing * statistics. * * Returns: * a new hash table */ extern struct hash * hash_create_size(unsigned int size, unsigned int (*hash_key)(const void *), bool (*hash_cmp)(const void *, const void *), const char *name); /* * Retrieve or insert data from / into a hash table. * * This function is somewhat counterintuitive in its usage. In order to look up * an element from its key, you must provide the data item itself, with the * portions used in the hash function set to the same values as the data item * to retrieve. To insert a data element, either provide the key as just * described and provide alloc_func as descrbied below to allocate the full * data element, or provide the full data element and pass 'hash_alloc_intern' * to alloc_func. * * hash * hash table to operate on * * data * data to insert or retrieve - A hash bucket will not be created if * the alloc_func returns a NULL pointer and nothing will be added to * the hash. As such bucket->data will always be non-NULL. * * alloc_func * function to call if the item is not found in the hash table. This * function is called with the value of 'data' and should create the data * item to insert and return a pointer to it. If the data has already been * completely created and provided in the 'data' parameter, passing * 'hash_alloc_intern' to this parameter will cause 'data' to be inserted. * If this parameter is NULL, then this call to hash_get is equivalent to * hash_lookup. * * Returns: * the data item found or inserted, or NULL if alloc_func is NULL and the * data is not found */ extern void *hash_get(struct hash *hash, void *data, void *(*alloc_func)(void *)); /* * Dummy element allocation function. * * See hash_get for details. * * data * data to insert into the hash table * * Returns: * data */ extern void *hash_alloc_intern(void *data); /* * Retrieve an item from a hash table. * * This function is equivalent to calling hash_get with alloc_func set to NULL. * * hash * hash table to operate on * * data * data element with values used for key computation set * * Returns: * the data element if found, or NULL if not found */ extern void *hash_lookup(struct hash *hash, void *data); /* * Remove an element from a hash table. * * hash * hash table to operate on * * data * data element to remove with values used for key computation set * * Returns: * the removed element if found, or NULL if not found */ extern void *hash_release(struct hash *hash, void *data); /* * Iterate over the elements in a hash table. * * It is safe to delete items passed to the iteration function from the hash * table during iteration. Please note that adding entries to the hash * during the walk will cause undefined behavior in that some new entries * will be walked and some will not. So do not do this. * * The bucket passed to func will have a non-NULL data pointer. * * hash * hash table to operate on * * func * function to call with each data item * * arg * arbitrary argument passed as the second parameter in each call to 'func' */ extern void hash_iterate(struct hash *hash, void (*func)(struct hash_bucket *, void *), void *arg); /* * Iterate over the elements in a hash table, stopping on condition. * * It is safe to delete items passed to the iteration function from the hash * table during iteration. Please note that adding entries to the hash * during the walk will cause undefined behavior in that some new entries * will be walked and some will not. So do not do this. * * The bucket passed to func will have a non-NULL data pointer. * * hash * hash table to operate on * * func * function to call with each data item. If this function returns * HASHWALK_ABORT then the iteration stops. * * arg * arbitrary argument passed as the second parameter in each call to 'func' */ extern void hash_walk(struct hash *hash, int (*func)(struct hash_bucket *, void *), void *arg); /* * Remove all elements from a hash table. * * hash * hash table to operate on * * free_func * function to call with each removed item; intended to free the data */ extern void hash_clean(struct hash *hash, void (*free_func)(void *)); /* * Delete a hash table. * * This function assumes the table is empty. Call hash_clean to delete the * hashtable contents if necessary. * * hash * hash table to delete */ extern void hash_free(struct hash *hash); /* * Converts a hash table to an unsorted linked list. * Does not modify the hash table in any way. * * hash * hash table to convert */ extern struct list *hash_to_list(struct hash *hash); /* * Hash a string using the modified Bernstein hash. * * This is not a perfect hash function. * * str * string to hash * * Returns: * modified Bernstein hash of the string */ extern unsigned int string_hash_make(const char *); /* * Install CLI commands for viewing global hash table statistics. */ extern void hash_cmd_init(void); #ifdef __cplusplus } #endif #endif /* _ZEBRA_HASH_H */ frr-7.2.1/lib/hook.c0000644000000000000000000000344213610377563011071 00000000000000/* * Copyright (c) 2016 David Lamparter, for NetDEF, Inc. * * Permission to use, copy, modify, and distribute this software for any * purpose with or without fee is hereby granted, provided that the above * copyright notice and this permission notice appear in all copies. * * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. */ #ifdef HAVE_CONFIG_H #include "config.h" #endif #include "memory.h" #include "hook.h" DEFINE_MTYPE_STATIC(LIB, HOOK_ENTRY, "Hook entry") void _hook_register(struct hook *hook, void *funcptr, void *arg, bool has_arg, struct frrmod_runtime *module, const char *funcname, int priority) { struct hookent *he = XCALLOC(MTYPE_HOOK_ENTRY, sizeof(*he)), **pos; he->hookfn = funcptr; he->hookarg = arg; he->has_arg = has_arg; he->module = module; he->fnname = funcname; he->priority = priority; for (pos = &hook->entries; *pos; pos = &(*pos)->next) if (hook->reverse ? (*pos)->priority < priority : (*pos)->priority >= priority) break; he->next = *pos; *pos = he; } void _hook_unregister(struct hook *hook, void *funcptr, void *arg, bool has_arg) { struct hookent *he, **prev; for (prev = &hook->entries; (he = *prev) != NULL; prev = &he->next) if (he->hookfn == funcptr && he->hookarg == arg && he->has_arg == has_arg) { *prev = he->next; XFREE(MTYPE_HOOK_ENTRY, he); break; } } frr-7.2.1/lib/hook.h0000644000000000000000000002314213610377563011075 00000000000000/* * Copyright (c) 2016 David Lamparter, for NetDEF, Inc. * * Permission to use, copy, modify, and distribute this software for any * purpose with or without fee is hereby granted, provided that the above * copyright notice and this permission notice appear in all copies. * * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. */ #ifndef _FRR_HOOK_H #define _FRR_HOOK_H #include #include "module.h" #include "memory.h" #ifdef __cplusplus extern "C" { #endif /* type-safe subscribable hook points * * where "type-safe" applies to the function pointers used for subscriptions * * overall usage: * - to create a hook: * * mydaemon.h: * #include "hook.h" * DECLARE_HOOK (some_update_event, (struct eventinfo *info), (info)) * * mydaemon.c: * DEFINE_HOOK (some_update_event, (struct eventinfo *info), (info)) * ... * hook_call (some_update_event, info) * * Note: the second and third macro args must be the hook function's * parameter list, with the same names for each parameter. The second * macro arg is with types (used for defining things), the third arg is * just the names (used for passing along parameters). * * Do not use parameter names starting with "hook", these can collide with * names used by the hook code itself. * * The return value is always "int" for now; hook_call will sum up the * return values from each registered user. Default is 0. * * There are no pre-defined semantics for the value, in most cases it is * ignored. For success/failure indication, 0 should be success, and * handlers should make sure to only return 0 or 1 (not -1 or other values). * * * - to use a hook / create a handler: * * #include "mydaemon.h" * int event_handler (struct eventinfo *info) { ... } * hook_register (some_update_event, event_handler); * * or, if you need an argument to be passed along (addonptr will be added * as first argument when calling the handler): * * #include "mydaemon.h" * int event_handler (void *addonptr, struct eventinfo *info) { ... } * hook_register_arg (some_update_event, event_handler, addonptr); * * (addonptr isn't typesafe, but that should be manageable.) * * Hooks also support a "priority" value for ordering registered calls * relative to each other. The priority is a signed integer where lower * values are called earlier. There is also "Koohs", which is hooks with * reverse priority ordering (for cleanup/deinit hooks, so you can use the * same priority value). * * Recommended priority value ranges are: * * -999 ... 0 ... 999 - main executable / daemon, or library * -1999 ... -1000 - modules registering calls that should run before * the daemon's bits * 1000 ... 1999 - modules calls that should run after daemon's * * Note: the default value is 1000, based on the following 2 expectations: * - most hook_register() usage will be in loadable modules * - usage of hook_register() in the daemon itself may need relative ordering * to itself, making an explicit value the expected case * * The priority value is passed as extra argument on hook_register_prio() / * hook_register_arg_prio(). Whether a hook runs in reverse is determined * solely by the code defining / calling the hook. (DECLARE_KOOH is actually * the same thing as DECLARE_HOOK, it's just there to make it obvious.) */ /* TODO: * - hook_unregister_all_module() * - introspection / CLI / debug * - testcases ;) * * For loadable modules, the idea is that hooks could be automatically * unregistered when a module is unloaded. * * It's also possible to add a constructor (MTYPE style) to DEFINE_HOOK, * which would make it possible for the CLI to show all hooks and all * registered handlers. */ struct hookent { struct hookent *next; void *hookfn; /* actually a function pointer */ void *hookarg; bool has_arg; int priority; struct frrmod_runtime *module; const char *fnname; }; struct hook { const char *name; struct hookent *entries; bool reverse; }; #define HOOK_DEFAULT_PRIORITY 1000 /* subscribe/add callback function to a hook * * always use hook_register(), which uses the static inline helper from * DECLARE_HOOK in order to get type safety */ extern void _hook_register(struct hook *hook, void *funcptr, void *arg, bool has_arg, struct frrmod_runtime *module, const char *funcname, int priority); #define hook_register(hookname, func) \ _hook_register(&_hook_##hookname, _hook_typecheck_##hookname(func), \ NULL, false, THIS_MODULE, #func, HOOK_DEFAULT_PRIORITY) #define hook_register_arg(hookname, func, arg) \ _hook_register(&_hook_##hookname, \ _hook_typecheck_arg_##hookname(func), arg, true, \ THIS_MODULE, #func, HOOK_DEFAULT_PRIORITY) #define hook_register_prio(hookname, prio, func) \ _hook_register(&_hook_##hookname, _hook_typecheck_##hookname(func), \ NULL, false, THIS_MODULE, #func, prio) #define hook_register_arg_prio(hookname, prio, func, arg) \ _hook_register(&_hook_##hookname, \ _hook_typecheck_arg_##hookname(func), arg, true, \ THIS_MODULE, #func, prio) extern void _hook_unregister(struct hook *hook, void *funcptr, void *arg, bool has_arg); #define hook_unregister(hookname, func) \ _hook_unregister(&_hook_##hookname, _hook_typecheck_##hookname(func), \ NULL, false) #define hook_unregister_arg(hookname, func, arg) \ _hook_unregister(&_hook_##hookname, \ _hook_typecheck_arg_##hookname(func), arg, true) /* invoke hooks * this is private (static) to the file that has the DEFINE_HOOK statement */ #define hook_call(hookname, ...) hook_call_##hookname(__VA_ARGS__) /* helpers to add the void * arg */ #define HOOK_ADDDEF(...) (void *hookarg , ## __VA_ARGS__) #define HOOK_ADDARG(...) (hookarg , ## __VA_ARGS__) /* use in header file - declares the hook and its arguments * usage: DECLARE_HOOK(my_hook, (int arg1, struct foo *arg2), (arg1, arg2)) * as above, "passlist" must use the same order and same names as "arglist" * * theoretically passlist is not neccessary, but let's keep things simple and * use exact same args on DECLARE and DEFINE. */ #define DECLARE_HOOK(hookname, arglist, passlist) \ extern struct hook _hook_##hookname; \ __attribute__((unused)) static void *_hook_typecheck_##hookname( \ int(*funcptr) arglist) \ { \ return (void *)funcptr; \ } \ __attribute__((unused)) static void *_hook_typecheck_arg_##hookname( \ int(*funcptr) HOOK_ADDDEF arglist) \ { \ return (void *)funcptr; \ } #define DECLARE_KOOH(hookname, arglist, passlist) \ DECLARE_HOOK(hookname, arglist, passlist) /* use in source file - contains hook-related definitions. */ #define DEFINE_HOOK_INT(hookname, arglist, passlist, rev) \ struct hook _hook_##hookname = { \ .name = #hookname, .entries = NULL, .reverse = rev, \ }; \ static int hook_call_##hookname arglist \ { \ int hooksum = 0; \ struct hookent *he = _hook_##hookname.entries; \ void *hookarg; \ union { \ void *voidptr; \ int(*fptr) arglist; \ int(*farg) HOOK_ADDDEF arglist; \ } hookp; \ for (; he; he = he->next) { \ hookarg = he->hookarg; \ hookp.voidptr = he->hookfn; \ if (!he->has_arg) \ hooksum += hookp.fptr passlist; \ else \ hooksum += hookp.farg HOOK_ADDARG passlist; \ } \ return hooksum; \ } #define DEFINE_HOOK(hookname, arglist, passlist) \ DEFINE_HOOK_INT(hookname, arglist, passlist, false) #define DEFINE_KOOH(hookname, arglist, passlist) \ DEFINE_HOOK_INT(hookname, arglist, passlist, true) #ifdef __cplusplus } #endif #endif /* _FRR_HOOK_H */ frr-7.2.1/lib/iana_afi.h0000644000000000000000000000630113610377563011662 00000000000000/* * iana_afi and safi definitions. * Copyright (C) 2018-2019 Cumulus Networks, Inc. * Donald Sharp * * This program is free software; you can redistribute it and/or modify it * under the terms of the 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; see the file COPYING; if not, write to the Free Software * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA */ #ifndef __IANA_AFI_H__ #include /* * The above AFI and SAFI definitions are for internal use. The protocol * definitions (IANA values) as for example used in BGP protocol packets * are defined below and these will get mapped to/from the internal values * in the appropriate places. * The rationale is that the protocol (IANA) values may be sparse and are * not optimal for use in data-structure sizing. * Note: Only useful (i.e., supported) values are defined below. */ typedef enum { IANA_AFI_RESERVED = 0, IANA_AFI_IPV4 = 1, IANA_AFI_IPV6 = 2, IANA_AFI_L2VPN = 25, } iana_afi_t; typedef enum { IANA_SAFI_RESERVED = 0, IANA_SAFI_UNICAST = 1, IANA_SAFI_MULTICAST = 2, IANA_SAFI_LABELED_UNICAST = 4, IANA_SAFI_ENCAP = 7, IANA_SAFI_EVPN = 70, IANA_SAFI_MPLS_VPN = 128, IANA_SAFI_FLOWSPEC = 133 } iana_safi_t; static inline afi_t afi_iana2int(iana_afi_t afi) { switch (afi) { case IANA_AFI_IPV4: return AFI_IP; case IANA_AFI_IPV6: return AFI_IP6; case IANA_AFI_L2VPN: return AFI_L2VPN; default: return AFI_MAX; } } static inline iana_afi_t afi_int2iana(afi_t afi) { switch (afi) { case AFI_IP: return IANA_AFI_IPV4; case AFI_IP6: return IANA_AFI_IPV6; case AFI_L2VPN: return IANA_AFI_L2VPN; default: return IANA_AFI_RESERVED; } } static inline const char *iana_afi2str(iana_afi_t afi) { return afi2str(afi_iana2int(afi)); } static inline safi_t safi_iana2int(iana_safi_t safi) { switch (safi) { case IANA_SAFI_UNICAST: return SAFI_UNICAST; case IANA_SAFI_MULTICAST: return SAFI_MULTICAST; case IANA_SAFI_MPLS_VPN: return SAFI_MPLS_VPN; case IANA_SAFI_ENCAP: return SAFI_ENCAP; case IANA_SAFI_EVPN: return SAFI_EVPN; case IANA_SAFI_LABELED_UNICAST: return SAFI_LABELED_UNICAST; case IANA_SAFI_FLOWSPEC: return SAFI_FLOWSPEC; default: return SAFI_MAX; } } static inline iana_safi_t safi_int2iana(safi_t safi) { switch (safi) { case SAFI_UNICAST: return IANA_SAFI_UNICAST; case SAFI_MULTICAST: return IANA_SAFI_MULTICAST; case SAFI_MPLS_VPN: return IANA_SAFI_MPLS_VPN; case SAFI_ENCAP: return IANA_SAFI_ENCAP; case SAFI_EVPN: return IANA_SAFI_EVPN; case SAFI_LABELED_UNICAST: return IANA_SAFI_LABELED_UNICAST; case SAFI_FLOWSPEC: return IANA_SAFI_FLOWSPEC; default: return IANA_SAFI_RESERVED; } } static inline const char *iana_safi2str(iana_safi_t safi) { return safi2str(safi_iana2int(safi)); } #endif frr-7.2.1/lib/id_alloc.c0000644000000000000000000002567113610377563011707 00000000000000/* * FRR ID Number Allocator * Copyright (C) 2018 Amazon.com, Inc. or its affiliates * * This program is free software; you can redistribute it and/or modify it * under the terms of the 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; see the file COPYING; if not, write to the Free Software * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA */ #ifdef HAVE_CONFIG_H #include "config.h" #endif #include "id_alloc.h" #include "log.h" #include "lib_errors.h" #include "memory.h" #include DEFINE_MTYPE_STATIC(LIB, IDALLOC_ALLOCATOR, "ID Number Allocator") DEFINE_MTYPE_STATIC(LIB, IDALLOC_ALLOCATOR_NAME, "ID Number Allocator Name") DEFINE_MTYPE_STATIC(LIB, IDALLOC_DIRECTORY, "ID Number Allocator Directory") DEFINE_MTYPE_STATIC(LIB, IDALLOC_SUBDIRECTORY, "ID Number Allocator Subdirectory") DEFINE_MTYPE_STATIC(LIB, IDALLOC_PAGE, "ID Number Allocator Page") DEFINE_MTYPE_STATIC(LIB, IDALLOC_POOL, "ID Number temporary holding pool entry") #if UINT_MAX >= UINT32_MAX #define FFS32(x) ffs(x) #else /* ints less than 32 bits? Yikes. */ #define FFS32(x) ffsl(x) #endif #define DIR_MASK ((1<> DIR_SHIFT) & DIR_MASK) #define ID_SUBDIR(id) ((id >> SUBDIR_SHIFT) & SUBDIR_MASK) #define ID_PAGE(id) ((id >> FRR_ID_PAGE_SHIFT) & FRR_ID_PAGE_MASK) #define ID_WORD(id) ((id >> WORD_SHIFT) & WORD_MASK) #define ID_OFFSET(id) ((id >> OFFSET_SHIFT) & OFFSET_MASK) /* * Find the page that an ID number belongs to in an allocator. * Optionally create the page if it doesn't exist. */ static struct id_alloc_page *find_or_create_page(struct id_alloc *alloc, uint32_t id, int create) { struct id_alloc_dir *dir = NULL; struct id_alloc_subdir *subdir = NULL; struct id_alloc_page *page = NULL; dir = alloc->sublevels[ID_DIR(id)]; if (dir == NULL) { if (create) { dir = XCALLOC(MTYPE_IDALLOC_DIRECTORY, sizeof(*dir)); alloc->sublevels[ID_DIR(id)] = dir; } else { return NULL; } } subdir = dir->sublevels[ID_SUBDIR(id)]; if (subdir == NULL) { if (create) { subdir = XCALLOC(MTYPE_IDALLOC_SUBDIRECTORY, sizeof(*subdir)); dir->sublevels[ID_SUBDIR(id)] = subdir; } else { return NULL; } } page = subdir->sublevels[ID_PAGE(id)]; if (page == NULL && create) { page = XCALLOC(MTYPE_IDALLOC_PAGE, sizeof(*page)); page->base_value = id; subdir->sublevels[ID_PAGE(id)] = page; alloc->capacity += 1 << FRR_ID_PAGE_SHIFT; page->next_has_free = alloc->has_free; alloc->has_free = page; } else if (page != NULL && create) { flog_err( EC_LIB_ID_CONSISTENCY, "ID Allocator %s attempt to re-create page at %" PRIu32, alloc->name, id); } return page; } /* * Return an ID number back to the allocator. * While this ID can be re-assigned through idalloc_allocate, the underlying * memory will not be freed. If this is the first free ID in the page, the page * will be added to the allocator's list of pages with free IDs. */ void idalloc_free(struct id_alloc *alloc, uint32_t id) { struct id_alloc_page *page = NULL; int word, offset; uint32_t old_word, old_word_mask; page = find_or_create_page(alloc, id, 0); if (!page) { flog_err(EC_LIB_ID_CONSISTENCY, "ID Allocator %s cannot free #%" PRIu32 ". ID Block does not exist.", alloc->name, id); return; } word = ID_WORD(id); offset = ID_OFFSET(id); if ((page->allocated_mask[word] & (1 << offset)) == 0) { flog_err(EC_LIB_ID_CONSISTENCY, "ID Allocator %s cannot free #%" PRIu32 ". ID was not allocated at the time of free.", alloc->name, id); return; } old_word = page->allocated_mask[word]; page->allocated_mask[word] &= ~(((uint32_t)1) << offset); alloc->allocated -= 1; if (old_word == UINT32_MAX) { /* first bit in this block of 32 to be freed.*/ old_word_mask = page->full_word_mask; page->full_word_mask &= ~(((uint32_t)1) << word); if (old_word_mask == UINT32_MAX) { /* first bit in page freed, add this to the allocator's * list of pages with free space */ page->next_has_free = alloc->has_free; alloc->has_free = page; } } } /* * Add a allocation page to the end of the allocator's current range. * Returns null if the allocator has had all possible pages allocated already. */ static struct id_alloc_page *create_next_page(struct id_alloc *alloc) { if (alloc->capacity == 0 && alloc->sublevels[0]) return NULL; /* All IDs allocated and the capacity looped. */ return find_or_create_page(alloc, alloc->capacity, 1); } /* * Marks an ID within an allocator page as in use. * If the ID was the last free ID in the page, the page is removed from the * allocator's list of free IDs. In the typical allocation case, this page is * the first page in the list, and removing the page is fast. If instead an ID * is being reserved by number, this may end up scanning the whole single linked * list of pages in order to remove it. */ static void reserve_bit(struct id_alloc *alloc, struct id_alloc_page *page, int word, int offset) { struct id_alloc_page *itr; page->allocated_mask[word] |= ((uint32_t)1) << offset; alloc->allocated += 1; if (page->allocated_mask[word] == UINT32_MAX) { page->full_word_mask |= ((uint32_t)1) << word; if (page->full_word_mask == UINT32_MAX) { if (alloc->has_free == page) { /* allocate always pulls from alloc->has_free */ alloc->has_free = page->next_has_free; } else { /* reserve could pull from any page with free * bits */ itr = alloc->has_free; while (itr) { if (itr->next_has_free == page) { itr->next_has_free = page->next_has_free; return; } itr = itr->next_has_free; } } } } } /* * Reserve an ID number from the allocator. Returns IDALLOC_INVALID (0) if the * allocator has no more IDs available. */ uint32_t idalloc_allocate(struct id_alloc *alloc) { struct id_alloc_page *page; int word, offset; uint32_t return_value; if (alloc->has_free == NULL) create_next_page(alloc); if (alloc->has_free == NULL) { flog_err(EC_LIB_ID_EXHAUST, "ID Allocator %s has run out of IDs.", alloc->name); return IDALLOC_INVALID; } page = alloc->has_free; word = FFS32(~(page->full_word_mask)) - 1; if (word < 0 || word >= 32) { flog_err(EC_LIB_ID_CONSISTENCY, "ID Allocator %s internal error. Page starting at %d is inconsistent.", alloc->name, page->base_value); return IDALLOC_INVALID; } offset = FFS32(~(page->allocated_mask[word])) - 1; if (offset < 0 || offset >= 32) { flog_err(EC_LIB_ID_CONSISTENCY, "ID Allocator %s internal error. Page starting at %d is inconsistent on word %d", alloc->name, page->base_value, word); return IDALLOC_INVALID; } return_value = page->base_value + word * 32 + offset; reserve_bit(alloc, page, word, offset); return return_value; } /* * Tries to allocate a specific ID from the allocator. Returns IDALLOC_INVALID * when the ID being "reserved" has allready been assigned/reserved. This should * only be done with low numbered IDs, as the allocator needs to reserve bit-map * pages in order */ uint32_t idalloc_reserve(struct id_alloc *alloc, uint32_t id) { struct id_alloc_page *page; int word, offset; while (alloc->capacity <= id) create_next_page(alloc); word = ID_WORD(id); offset = ID_OFFSET(id); page = find_or_create_page(alloc, id, 0); /* page can't be null because the loop above ensured it was created. */ if (page->allocated_mask[word] & (((uint32_t)1) << offset)) { flog_err(EC_LIB_ID_CONSISTENCY, "ID Allocator %s could not reserve %" PRIu32 " because it is already allocated.", alloc->name, id); return IDALLOC_INVALID; } reserve_bit(alloc, page, word, offset); return id; } /* * Set up an empty ID allocator, with IDALLOC_INVALID pre-reserved. */ struct id_alloc *idalloc_new(const char *name) { struct id_alloc *ret; ret = XCALLOC(MTYPE_IDALLOC_ALLOCATOR, sizeof(*ret)); ret->name = XSTRDUP(MTYPE_IDALLOC_ALLOCATOR_NAME, name); idalloc_reserve(ret, IDALLOC_INVALID); return ret; } /* * Free a subdir, and all pages below it. */ static void idalloc_destroy_subdir(struct id_alloc_subdir *subdir) { int i; for (i = 0; i < IDALLOC_PAGE_COUNT; i++) { if (subdir->sublevels[i]) XFREE(MTYPE_IDALLOC_PAGE, subdir->sublevels[i]); else break; } XFREE(MTYPE_IDALLOC_SUBDIRECTORY, subdir); } /* * Free a dir, and all subdirs/pages below it. */ static void idalloc_destroy_dir(struct id_alloc_dir *dir) { int i; for (i = 0; i < IDALLOC_SUBDIR_COUNT; i++) { if (dir->sublevels[i]) idalloc_destroy_subdir(dir->sublevels[i]); else break; } XFREE(MTYPE_IDALLOC_DIRECTORY, dir); } /* * Free all memory associated with an ID allocator. */ void idalloc_destroy(struct id_alloc *alloc) { int i; for (i = 0; i < IDALLOC_DIR_COUNT; i++) { if (alloc->sublevels[i]) idalloc_destroy_dir(alloc->sublevels[i]); else break; } XFREE(MTYPE_IDALLOC_ALLOCATOR_NAME, alloc->name); XFREE(MTYPE_IDALLOC_ALLOCATOR, alloc); } /* * Give an ID number to temporary holding pool. */ void idalloc_free_to_pool(struct id_alloc_pool **pool_ptr, uint32_t id) { struct id_alloc_pool *new_pool; new_pool = XMALLOC(MTYPE_IDALLOC_POOL, sizeof(*new_pool)); new_pool->id = id; new_pool->next = *pool_ptr; *pool_ptr = new_pool; } /* * Free all ID numbers held in a holding pool back to the main allocator. */ void idalloc_drain_pool(struct id_alloc *alloc, struct id_alloc_pool **pool_ptr) { struct id_alloc_pool *current, *next; while (*pool_ptr) { current = *pool_ptr; next = current->next; idalloc_free(alloc, current->id); XFREE(MTYPE_IDALLOC_POOL, current); *pool_ptr = next; } } /* * Allocate an ID from either a holding pool, or the main allocator. IDs will * only be pulled form the main allocator when the pool is empty. */ uint32_t idalloc_allocate_prefer_pool(struct id_alloc *alloc, struct id_alloc_pool **pool_ptr) { uint32_t ret; struct id_alloc_pool *pool_head = *pool_ptr; if (pool_head) { ret = pool_head->id; *pool_ptr = pool_head->next; XFREE(MTYPE_IDALLOC_POOL, pool_head); return ret; } else { return idalloc_allocate(alloc); } } frr-7.2.1/lib/id_alloc.h0000644000000000000000000000532313610377563011704 00000000000000/* * FRR ID Number Allocator * Copyright (C) 2018 Amazon.com, Inc. or its affiliates * * This program is free software; you can redistribute it and/or modify it * under the terms of the 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; see the file COPYING; if not, write to the Free Software * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA */ #ifndef _ZEBRA_ID_ALLOC_H #define _ZEBRA_ID_ALLOC_H #include #include #include #ifdef __cplusplus extern "C" { #endif #define IDALLOC_INVALID 0 #define IDALLOC_DIR_BITS 8 #define IDALLOC_SUBDIR_BITS 7 #define IDALLOC_PAGE_BITS 7 #define IDALLOC_WORD_BITS 5 #define IDALLOC_OFFSET_BITS 5 #define IDALLOC_DIR_COUNT (1 << IDALLOC_DIR_BITS) #define IDALLOC_SUBDIR_COUNT (1 << IDALLOC_SUBDIR_BITS) #define IDALLOC_PAGE_COUNT (1 << IDALLOC_PAGE_BITS) #define IDALLOC_WORD_COUNT (1 << IDALLOC_WORD_BITS) struct id_alloc_page { /* Bitmask of allocations. 1s indicates the ID is already allocated. */ uint32_t allocated_mask[IDALLOC_WORD_COUNT]; /* Bitmask for free space in allocated_mask. 1s indicate whole 32 bit * section is full. */ uint32_t full_word_mask; /* The ID that bit 0 in allocated_mask corresponds to. */ uint32_t base_value; struct id_alloc_page *next_has_free; /* Next page with at least one bit open */ }; struct id_alloc_subdir { struct id_alloc_page *sublevels[IDALLOC_PAGE_COUNT]; }; struct id_alloc_dir { struct id_alloc_subdir *sublevels[IDALLOC_SUBDIR_COUNT]; }; struct id_alloc { struct id_alloc_dir *sublevels[IDALLOC_DIR_COUNT]; struct id_alloc_page *has_free; char *name; uint32_t allocated, capacity; }; struct id_alloc_pool { struct id_alloc_pool *next; uint32_t id; }; void idalloc_free(struct id_alloc *alloc, uint32_t id); void idalloc_free_to_pool(struct id_alloc_pool **pool_ptr, uint32_t id); void idalloc_drain_pool(struct id_alloc *alloc, struct id_alloc_pool **pool_ptr); uint32_t idalloc_allocate(struct id_alloc *alloc); uint32_t idalloc_allocate_prefer_pool(struct id_alloc *alloc, struct id_alloc_pool **pool_ptr); uint32_t idalloc_reserve(struct id_alloc *alloc, uint32_t id); struct id_alloc *idalloc_new(const char *name); void idalloc_destroy(struct id_alloc *alloc); #ifdef __cplusplus } #endif #endif frr-7.2.1/lib/if.c0000644000000000000000000011326713610377563010536 00000000000000/* * Interface functions. * Copyright (C) 1997, 98 Kunihiro Ishiguro * * This file is part of GNU Zebra. * * GNU Zebra is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License as published * by the Free Software Foundation; either version 2, or (at your * option) any later version. * * GNU Zebra 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; see the file COPYING; if not, write to the Free Software * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA */ #include #include "linklist.h" #include "vector.h" #include "lib_errors.h" #include "vty.h" #include "command.h" #include "vrf.h" #include "if.h" #include "sockunion.h" #include "prefix.h" #include "memory.h" #include "table.h" #include "buffer.h" #include "log.h" #include "northbound_cli.h" #ifndef VTYSH_EXTRACT_PL #include "lib/if_clippy.c" #endif DEFINE_MTYPE_STATIC(LIB, IF, "Interface") DEFINE_MTYPE_STATIC(LIB, CONNECTED, "Connected") DEFINE_MTYPE_STATIC(LIB, NBR_CONNECTED, "Neighbor Connected") DEFINE_MTYPE(LIB, CONNECTED_LABEL, "Connected interface label") DEFINE_MTYPE_STATIC(LIB, IF_LINK_PARAMS, "Informational Link Parameters") static struct interface *if_lookup_by_ifindex(ifindex_t ifindex, vrf_id_t vrf_id); static int if_cmp_func(const struct interface *, const struct interface *); static int if_cmp_index_func(const struct interface *ifp1, const struct interface *ifp2); RB_GENERATE(if_name_head, interface, name_entry, if_cmp_func); RB_GENERATE(if_index_head, interface, index_entry, if_cmp_index_func); DEFINE_QOBJ_TYPE(interface) DEFINE_HOOK(if_add, (struct interface * ifp), (ifp)) DEFINE_KOOH(if_del, (struct interface * ifp), (ifp)) /* Compare interface names, returning an integer greater than, equal to, or * less than 0, (following the strcmp convention), according to the * relationship between ifp1 and ifp2. Interface names consist of an * alphabetic prefix and a numeric suffix. The primary sort key is * lexicographic by name, and then numeric by number. No number sorts * before all numbers. Examples: de0 < de1, de100 < fxp0 < xl0, devpty < * devpty0, de0 < del0 */ int if_cmp_name_func(const char *p1, const char *p2) { unsigned int l1, l2; long int x1, x2; int res; while (*p1 && *p2) { /* look up to any number */ l1 = strcspn(p1, "0123456789"); l2 = strcspn(p2, "0123456789"); /* name lengths are different -> compare names */ if (l1 != l2) return (strcmp(p1, p2)); /* Note that this relies on all numbers being less than all * letters, so * that de0 < del0. */ res = strncmp(p1, p2, l1); /* names are different -> compare them */ if (res) return res; /* with identical name part, go to numeric part */ p1 += l1; p2 += l1; if (!*p1 && !*p2) return 0; if (!*p1) return -1; if (!*p2) return 1; x1 = strtol(p1, (char **)&p1, 10); x2 = strtol(p2, (char **)&p2, 10); /* let's compare numbers now */ if (x1 < x2) return -1; if (x1 > x2) return 1; /* numbers were equal, lets do it again.. (it happens with name like "eth123.456:789") */ } if (*p1) return 1; if (*p2) return -1; return 0; } static int if_cmp_func(const struct interface *ifp1, const struct interface *ifp2) { return if_cmp_name_func(ifp1->name, ifp2->name); } static int if_cmp_index_func(const struct interface *ifp1, const struct interface *ifp2) { if (ifp1->ifindex == ifp2->ifindex) return 0; else if (ifp1->ifindex > ifp2->ifindex) return 1; else return -1; } /* Create new interface structure. */ static struct interface *if_create_backend(const char *name, ifindex_t ifindex, vrf_id_t vrf_id) { struct vrf *vrf = vrf_get(vrf_id, NULL); struct interface *ifp; ifp = XCALLOC(MTYPE_IF, sizeof(struct interface)); ifp->vrf_id = vrf_id; if (name) { strlcpy(ifp->name, name, sizeof(ifp->name)); IFNAME_RB_INSERT(vrf, ifp); } else ifp->name[0] = '\0'; if (ifindex != IFINDEX_INTERNAL) if_set_index(ifp, ifindex); else ifp->ifindex = ifindex; /* doesn't add it to the list */ ifp->connected = list_new(); ifp->connected->del = (void (*)(void *))connected_free; ifp->nbr_connected = list_new(); ifp->nbr_connected->del = (void (*)(void *))nbr_connected_free; /* Enable Link-detection by default */ SET_FLAG(ifp->status, ZEBRA_INTERFACE_LINKDETECTION); QOBJ_REG(ifp, interface); hook_call(if_add, ifp); return ifp; } struct interface *if_create(const char *name, vrf_id_t vrf_id) { return if_create_backend(name, IFINDEX_INTERNAL, vrf_id); } struct interface *if_create_ifindex(ifindex_t ifindex, vrf_id_t vrf_id) { return if_create_backend(NULL, ifindex, vrf_id); } /* Create new interface structure. */ void if_update_to_new_vrf(struct interface *ifp, vrf_id_t vrf_id) { struct vrf *old_vrf, *vrf; /* remove interface from old master vrf list */ old_vrf = vrf_lookup_by_id(ifp->vrf_id); if (old_vrf) { if (ifp->name[0] != '\0') IFNAME_RB_REMOVE(old_vrf, ifp); if (ifp->ifindex != IFINDEX_INTERNAL) IFINDEX_RB_REMOVE(old_vrf, ifp); } ifp->vrf_id = vrf_id; vrf = vrf_get(ifp->vrf_id, NULL); if (ifp->name[0] != '\0') IFNAME_RB_INSERT(vrf, ifp); if (ifp->ifindex != IFINDEX_INTERNAL) IFINDEX_RB_INSERT(vrf, ifp); /* * HACK: Change the interface VRF in the running configuration directly, * bypassing the northbound layer. This is necessary to avoid deleting * the interface and readding it in the new VRF, which would have * several implications. */ if (yang_module_find("frr-interface")) { struct lyd_node *if_dnode; pthread_rwlock_wrlock(&running_config->lock); { if_dnode = yang_dnode_get( running_config->dnode, "/frr-interface:lib/interface[name='%s'][vrf='%s']/vrf", ifp->name, old_vrf->name); if (if_dnode) { nb_running_unset_entry(if_dnode->parent); yang_dnode_change_leaf(if_dnode, vrf->name); nb_running_set_entry(if_dnode->parent, ifp); running_config->version++; } } pthread_rwlock_unlock(&running_config->lock); } } /* Delete interface structure. */ void if_delete_retain(struct interface *ifp) { hook_call(if_del, ifp); QOBJ_UNREG(ifp); /* Free connected address list */ list_delete_all_node(ifp->connected); /* Free connected nbr address list */ list_delete_all_node(ifp->nbr_connected); } /* Delete and free interface structure. */ void if_delete(struct interface *ifp) { struct vrf *vrf; vrf = vrf_lookup_by_id(ifp->vrf_id); assert(vrf); IFNAME_RB_REMOVE(vrf, ifp); if (ifp->ifindex != IFINDEX_INTERNAL) IFINDEX_RB_REMOVE(vrf, ifp); if_delete_retain(ifp); list_delete(&ifp->connected); list_delete(&ifp->nbr_connected); if_link_params_free(ifp); XFREE(MTYPE_TMP, ifp->desc); XFREE(MTYPE_IF, ifp); } /* Used only internally to check within VRF only */ static struct interface *if_lookup_by_ifindex(ifindex_t ifindex, vrf_id_t vrf_id) { struct vrf *vrf; struct interface if_tmp; vrf = vrf_lookup_by_id(vrf_id); if (!vrf) return NULL; if_tmp.ifindex = ifindex; return RB_FIND(if_index_head, &vrf->ifaces_by_index, &if_tmp); } /* Interface existance check by index. */ struct interface *if_lookup_by_index(ifindex_t ifindex, vrf_id_t vrf_id) { switch (vrf_get_backend()) { case VRF_BACKEND_UNKNOWN: case VRF_BACKEND_NETNS: return(if_lookup_by_ifindex(ifindex, vrf_id)); case VRF_BACKEND_VRF_LITE: return(if_lookup_by_index_all_vrf(ifindex)); } return NULL; } const char *ifindex2ifname(ifindex_t ifindex, vrf_id_t vrf_id) { struct interface *ifp; return ((ifp = if_lookup_by_index(ifindex, vrf_id)) != NULL) ? ifp->name : "unknown"; } ifindex_t ifname2ifindex(const char *name, vrf_id_t vrf_id) { struct interface *ifp; return ((ifp = if_lookup_by_name(name, vrf_id)) != NULL) ? ifp->ifindex : IFINDEX_INTERNAL; } /* Interface existance check by interface name. */ struct interface *if_lookup_by_name(const char *name, vrf_id_t vrf_id) { struct vrf *vrf = vrf_lookup_by_id(vrf_id); struct interface if_tmp; if (!vrf || !name || strnlen(name, INTERFACE_NAMSIZ) == INTERFACE_NAMSIZ) return NULL; strlcpy(if_tmp.name, name, sizeof(if_tmp.name)); return RB_FIND(if_name_head, &vrf->ifaces_by_name, &if_tmp); } struct interface *if_lookup_by_name_all_vrf(const char *name) { struct vrf *vrf; struct interface *ifp; if (!name || strnlen(name, INTERFACE_NAMSIZ) == INTERFACE_NAMSIZ) return NULL; RB_FOREACH (vrf, vrf_id_head, &vrfs_by_id) { ifp = if_lookup_by_name(name, vrf->vrf_id); if (ifp) return ifp; } return NULL; } struct interface *if_lookup_by_index_all_vrf(ifindex_t ifindex) { struct vrf *vrf; struct interface *ifp; if (ifindex == IFINDEX_INTERNAL) return NULL; RB_FOREACH (vrf, vrf_id_head, &vrfs_by_id) { ifp = if_lookup_by_ifindex(ifindex, vrf->vrf_id); if (ifp) return ifp; } return NULL; } /* Lookup interface by IP address. */ struct interface *if_lookup_exact_address(void *src, int family, vrf_id_t vrf_id) { struct vrf *vrf = vrf_lookup_by_id(vrf_id); struct listnode *cnode; struct interface *ifp; struct prefix *p; struct connected *c; FOR_ALL_INTERFACES (vrf, ifp) { for (ALL_LIST_ELEMENTS_RO(ifp->connected, cnode, c)) { p = c->address; if (p && (p->family == family)) { if (family == AF_INET) { if (IPV4_ADDR_SAME( &p->u.prefix4, (struct in_addr *)src)) return ifp; } else if (family == AF_INET6) { if (IPV6_ADDR_SAME( &p->u.prefix6, (struct in6_addr *)src)) return ifp; } } } } return NULL; } /* Lookup interface by IP address. */ struct connected *if_lookup_address(void *matchaddr, int family, vrf_id_t vrf_id) { struct vrf *vrf = vrf_lookup_by_id(vrf_id); struct prefix addr; int bestlen = 0; struct listnode *cnode; struct interface *ifp; struct connected *c; struct connected *match; if (family == AF_INET) { addr.family = AF_INET; addr.u.prefix4 = *((struct in_addr *)matchaddr); addr.prefixlen = IPV4_MAX_BITLEN; } else if (family == AF_INET6) { addr.family = AF_INET6; addr.u.prefix6 = *((struct in6_addr *)matchaddr); addr.prefixlen = IPV6_MAX_BITLEN; } match = NULL; FOR_ALL_INTERFACES (vrf, ifp) { for (ALL_LIST_ELEMENTS_RO(ifp->connected, cnode, c)) { if (c->address && (c->address->family == AF_INET) && prefix_match(CONNECTED_PREFIX(c), &addr) && (c->address->prefixlen > bestlen)) { bestlen = c->address->prefixlen; match = c; } } } return match; } /* Lookup interface by prefix */ struct interface *if_lookup_prefix(struct prefix *prefix, vrf_id_t vrf_id) { struct vrf *vrf = vrf_lookup_by_id(vrf_id); struct listnode *cnode; struct interface *ifp; struct connected *c; FOR_ALL_INTERFACES (vrf, ifp) { for (ALL_LIST_ELEMENTS_RO(ifp->connected, cnode, c)) { if (prefix_cmp(c->address, prefix) == 0) { return ifp; } } } return NULL; } size_t if_lookup_by_hwaddr(const uint8_t *hw_addr, size_t addrsz, struct interface ***result, vrf_id_t vrf_id) { struct vrf *vrf = vrf_lookup_by_id(vrf_id); struct list *rs = list_new(); struct interface *ifp; FOR_ALL_INTERFACES (vrf, ifp) { if (ifp->hw_addr_len == (int)addrsz && !memcmp(hw_addr, ifp->hw_addr, addrsz)) listnode_add(rs, ifp); } if (rs->count) { *result = XCALLOC(MTYPE_TMP, sizeof(struct interface *) * rs->count); list_to_array(rs, (void **)*result, rs->count); } int count = rs->count; list_delete(&rs); return count; } /* Get interface by name if given name interface doesn't exist create one. */ struct interface *if_get_by_name(const char *name, vrf_id_t vrf_id) { struct interface *ifp; switch (vrf_get_backend()) { case VRF_BACKEND_UNKNOWN: case VRF_BACKEND_NETNS: ifp = if_lookup_by_name(name, vrf_id); if (ifp) return ifp; return if_create(name, vrf_id); case VRF_BACKEND_VRF_LITE: ifp = if_lookup_by_name_all_vrf(name); if (ifp) { if (ifp->vrf_id == vrf_id) return ifp; /* If it came from the kernel or by way of zclient, * believe it and update the ifp accordingly. */ if_update_to_new_vrf(ifp, vrf_id); return ifp; } return if_create(name, vrf_id); } return NULL; } struct interface *if_get_by_ifindex(ifindex_t ifindex, vrf_id_t vrf_id) { struct interface *ifp; switch (vrf_get_backend()) { case VRF_BACKEND_UNKNOWN: case VRF_BACKEND_NETNS: ifp = if_lookup_by_ifindex(ifindex, vrf_id); if (ifp) return ifp; return if_create_ifindex(ifindex, vrf_id); case VRF_BACKEND_VRF_LITE: ifp = if_lookup_by_index_all_vrf(ifindex); if (ifp) { if (ifp->vrf_id == vrf_id) return ifp; /* If it came from the kernel or by way of zclient, * believe it and update the ifp accordingly. */ if_update_to_new_vrf(ifp, vrf_id); return ifp; } return if_create_ifindex(ifindex, vrf_id); } return NULL; } void if_set_index(struct interface *ifp, ifindex_t ifindex) { struct vrf *vrf; vrf = vrf_lookup_by_id(ifp->vrf_id); assert(vrf); if (ifp->ifindex == ifindex) return; if (ifp->ifindex != IFINDEX_INTERNAL) IFINDEX_RB_REMOVE(vrf, ifp) ifp->ifindex = ifindex; if (ifp->ifindex != IFINDEX_INTERNAL) IFINDEX_RB_INSERT(vrf, ifp) } /* Does interface up ? */ int if_is_up(const struct interface *ifp) { return ifp->flags & IFF_UP; } /* Is interface running? */ int if_is_running(const struct interface *ifp) { return ifp->flags & IFF_RUNNING; } /* Is the interface operative, eg. either UP & RUNNING or UP & !ZEBRA_INTERFACE_LINK_DETECTION and if ptm checking is enabled, then ptm check has passed */ int if_is_operative(const struct interface *ifp) { return ((ifp->flags & IFF_UP) && (((ifp->flags & IFF_RUNNING) && (ifp->ptm_status || !ifp->ptm_enable)) || !CHECK_FLAG(ifp->status, ZEBRA_INTERFACE_LINKDETECTION))); } /* Is the interface operative, eg. either UP & RUNNING or UP & !ZEBRA_INTERFACE_LINK_DETECTION, without PTM check */ int if_is_no_ptm_operative(const struct interface *ifp) { return ((ifp->flags & IFF_UP) && ((ifp->flags & IFF_RUNNING) || !CHECK_FLAG(ifp->status, ZEBRA_INTERFACE_LINKDETECTION))); } /* Is this loopback interface ? */ int if_is_loopback(const struct interface *ifp) { /* XXX: Do this better, eg what if IFF_WHATEVER means X on platform M * but Y on platform N? */ return (ifp->flags & (IFF_LOOPBACK | IFF_NOXMIT | IFF_VIRTUAL)); } /* Check interface is VRF */ int if_is_vrf(const struct interface *ifp) { return CHECK_FLAG(ifp->status, ZEBRA_INTERFACE_VRF_LOOPBACK); } bool if_is_loopback_or_vrf(const struct interface *ifp) { if (if_is_loopback(ifp) || if_is_vrf(ifp)) return true; return false; } /* Does this interface support broadcast ? */ int if_is_broadcast(const struct interface *ifp) { return ifp->flags & IFF_BROADCAST; } /* Does this interface support broadcast ? */ int if_is_pointopoint(const struct interface *ifp) { return ifp->flags & IFF_POINTOPOINT; } /* Does this interface support multicast ? */ int if_is_multicast(const struct interface *ifp) { return ifp->flags & IFF_MULTICAST; } /* Printout flag information into log */ const char *if_flag_dump(unsigned long flag) { int separator = 0; static char logbuf[BUFSIZ]; #define IFF_OUT_LOG(X, STR) \ if (flag & (X)) { \ if (separator) \ strlcat(logbuf, ",", sizeof(logbuf)); \ else \ separator = 1; \ strlcat(logbuf, STR, sizeof(logbuf)); \ } strlcpy(logbuf, "<", BUFSIZ); IFF_OUT_LOG(IFF_UP, "UP"); IFF_OUT_LOG(IFF_BROADCAST, "BROADCAST"); IFF_OUT_LOG(IFF_DEBUG, "DEBUG"); IFF_OUT_LOG(IFF_LOOPBACK, "LOOPBACK"); IFF_OUT_LOG(IFF_POINTOPOINT, "POINTOPOINT"); IFF_OUT_LOG(IFF_NOTRAILERS, "NOTRAILERS"); IFF_OUT_LOG(IFF_RUNNING, "RUNNING"); IFF_OUT_LOG(IFF_NOARP, "NOARP"); IFF_OUT_LOG(IFF_PROMISC, "PROMISC"); IFF_OUT_LOG(IFF_ALLMULTI, "ALLMULTI"); IFF_OUT_LOG(IFF_OACTIVE, "OACTIVE"); IFF_OUT_LOG(IFF_SIMPLEX, "SIMPLEX"); IFF_OUT_LOG(IFF_LINK0, "LINK0"); IFF_OUT_LOG(IFF_LINK1, "LINK1"); IFF_OUT_LOG(IFF_LINK2, "LINK2"); IFF_OUT_LOG(IFF_MULTICAST, "MULTICAST"); IFF_OUT_LOG(IFF_NOXMIT, "NOXMIT"); IFF_OUT_LOG(IFF_NORTEXCH, "NORTEXCH"); IFF_OUT_LOG(IFF_VIRTUAL, "VIRTUAL"); IFF_OUT_LOG(IFF_IPV4, "IPv4"); IFF_OUT_LOG(IFF_IPV6, "IPv6"); strlcat(logbuf, ">", sizeof(logbuf)); return logbuf; #undef IFF_OUT_LOG } /* For debugging */ static void if_dump(const struct interface *ifp) { struct listnode *node; struct connected *c __attribute__((unused)); for (ALL_LIST_ELEMENTS_RO(ifp->connected, node, c)) zlog_info( "Interface %s vrf %u index %d metric %d mtu %d " "mtu6 %d %s", ifp->name, ifp->vrf_id, ifp->ifindex, ifp->metric, ifp->mtu, ifp->mtu6, if_flag_dump(ifp->flags)); } /* Interface printing for all interface. */ void if_dump_all(void) { struct vrf *vrf; void *ifp; RB_FOREACH (vrf, vrf_id_head, &vrfs_by_id) FOR_ALL_INTERFACES (vrf, ifp) if_dump(ifp); } #ifdef SUNOS_5 /* Need to handle upgrade from SUNWzebra to Quagga. SUNWzebra created * a seperate struct interface for each logical interface, so config * file may be full of 'interface fooX:Y'. Solaris however does not * expose logical interfaces via PF_ROUTE, so trying to track logical * interfaces can be fruitless, for that reason Quagga only tracks * the primary IP interface. * * We try accomodate SUNWzebra by: * - looking up the interface name, to see whether it exists, if so * its useable * - for protocol daemons, this could only because zebra told us of * the interface * - for zebra, only because it learnt from kernel * - if not: * - search the name to see if it contains a sub-ipif / logical interface * seperator, the ':' char. If it does: * - text up to that char must be the primary name - get that name. * if not: * - no idea, just get the name in its entirety. */ static struct interface *if_sunwzebra_get(const char *name, vrf_id_t vrf_id) { struct interface *ifp; char *cp; if ((ifp = if_lookup_by_name(name, vrf_id)) != NULL) return ifp; /* hunt the primary interface name... */ cp = strchr(name, ':'); if (cp) *cp = '\0'; return if_get_by_name(name, vrf_id); } #endif /* SUNOS_5 */ #if 0 /* For debug purpose. */ DEFUN (show_address, show_address_cmd, "show address [vrf NAME]", SHOW_STR "address\n" VRF_CMD_HELP_STR) { int idx_vrf = 3; struct listnode *node; struct interface *ifp; struct connected *ifc; struct prefix *p; vrf_id_t vrf_id = VRF_DEFAULT; if (argc > 2) VRF_GET_ID (vrf_id, argv[idx_vrf]->arg); FOR_ALL_INTERFACES (vrf, ifp) { for (ALL_LIST_ELEMENTS_RO (ifp->connected, node, ifc)) { p = ifc->address; if (p->family == AF_INET) vty_out (vty, "%s/%d\n", inet_ntoa (p->u.prefix4), p->prefixlen); } } return CMD_SUCCESS; } DEFUN (show_address_vrf_all, show_address_vrf_all_cmd, "show address vrf all", SHOW_STR "address\n" VRF_ALL_CMD_HELP_STR) { struct vrf *vrf; struct listnode *node; struct interface *ifp; struct connected *ifc; struct prefix *p; RB_FOREACH (vrf, vrf_name_head, &vrfs_by_name) { if (RB_EMPTY (if_name_head, &vrf->ifaces_by_name)) continue; vty_out (vty, "\nVRF %u\n\n", vrf->vrf_id); FOR_ALL_INTERFACES (vrf, ifp) { for (ALL_LIST_ELEMENTS_RO (ifp->connected, node, ifc)) { p = ifc->address; if (p->family == AF_INET) vty_out (vty, "%s/%d\n", inet_ntoa (p->u.prefix4), p->prefixlen); } } } return CMD_SUCCESS; } #endif /* Allocate connected structure. */ struct connected *connected_new(void) { return XCALLOC(MTYPE_CONNECTED, sizeof(struct connected)); } /* Allocate nbr connected structure. */ struct nbr_connected *nbr_connected_new(void) { return XCALLOC(MTYPE_NBR_CONNECTED, sizeof(struct nbr_connected)); } /* Free connected structure. */ void connected_free(struct connected *connected) { if (connected->address) prefix_free(connected->address); if (connected->destination) prefix_free(connected->destination); XFREE(MTYPE_CONNECTED_LABEL, connected->label); XFREE(MTYPE_CONNECTED, connected); } /* Free nbr connected structure. */ void nbr_connected_free(struct nbr_connected *connected) { if (connected->address) prefix_free(connected->address); XFREE(MTYPE_NBR_CONNECTED, connected); } /* If same interface nbr address already exists... */ struct nbr_connected *nbr_connected_check(struct interface *ifp, struct prefix *p) { struct nbr_connected *ifc; struct listnode *node; for (ALL_LIST_ELEMENTS_RO(ifp->nbr_connected, node, ifc)) if (prefix_same(ifc->address, p)) return ifc; return NULL; } /* Print if_addr structure. */ static void __attribute__((unused)) connected_log(struct connected *connected, char *str) { struct prefix *p; struct interface *ifp; char logbuf[BUFSIZ]; char buf[BUFSIZ]; ifp = connected->ifp; p = connected->address; snprintf(logbuf, BUFSIZ, "%s interface %s vrf %u %s %s/%d ", str, ifp->name, ifp->vrf_id, prefix_family_str(p), inet_ntop(p->family, &p->u.prefix, buf, BUFSIZ), p->prefixlen); p = connected->destination; if (p) { strncat(logbuf, inet_ntop(p->family, &p->u.prefix, buf, BUFSIZ), BUFSIZ - strlen(logbuf)); } zlog_info("%s", logbuf); } /* Print if_addr structure. */ static void __attribute__((unused)) nbr_connected_log(struct nbr_connected *connected, char *str) { struct prefix *p; struct interface *ifp; char logbuf[BUFSIZ]; char buf[BUFSIZ]; ifp = connected->ifp; p = connected->address; snprintf(logbuf, BUFSIZ, "%s interface %s %s %s/%d ", str, ifp->name, prefix_family_str(p), inet_ntop(p->family, &p->u.prefix, buf, BUFSIZ), p->prefixlen); zlog_info("%s", logbuf); } /* If two connected address has same prefix return 1. */ static int connected_same_prefix(struct prefix *p1, struct prefix *p2) { if (p1->family == p2->family) { if (p1->family == AF_INET && IPV4_ADDR_SAME(&p1->u.prefix4, &p2->u.prefix4)) return 1; if (p1->family == AF_INET6 && IPV6_ADDR_SAME(&p1->u.prefix6, &p2->u.prefix6)) return 1; } return 0; } struct connected *connected_lookup_prefix_exact(struct interface *ifp, struct prefix *p) { struct listnode *node; struct listnode *next; struct connected *ifc; for (node = listhead(ifp->connected); node; node = next) { ifc = listgetdata(node); next = node->next; if (connected_same_prefix(ifc->address, p)) return ifc; } return NULL; } struct connected *connected_delete_by_prefix(struct interface *ifp, struct prefix *p) { struct listnode *node; struct listnode *next; struct connected *ifc; /* In case of same prefix come, replace it with new one. */ for (node = listhead(ifp->connected); node; node = next) { ifc = listgetdata(node); next = node->next; if (connected_same_prefix(ifc->address, p)) { listnode_delete(ifp->connected, ifc); return ifc; } } return NULL; } /* Find the address on our side that will be used when packets are sent to dst. */ struct connected *connected_lookup_prefix(struct interface *ifp, struct prefix *addr) { struct listnode *cnode; struct connected *c; struct connected *match; match = NULL; for (ALL_LIST_ELEMENTS_RO(ifp->connected, cnode, c)) { if (c->address && (c->address->family == addr->family) && prefix_match(CONNECTED_PREFIX(c), addr) && (!match || (c->address->prefixlen > match->address->prefixlen))) match = c; } return match; } struct connected *connected_add_by_prefix(struct interface *ifp, struct prefix *p, struct prefix *destination) { struct connected *ifc; /* Allocate new connected address. */ ifc = connected_new(); ifc->ifp = ifp; /* Fetch interface address */ ifc->address = prefix_new(); memcpy(ifc->address, p, sizeof(struct prefix)); /* Fetch dest address */ if (destination) { ifc->destination = prefix_new(); memcpy(ifc->destination, destination, sizeof(struct prefix)); } /* Add connected address to the interface. */ listnode_add(ifp->connected, ifc); return ifc; } struct connected *connected_get_linklocal(struct interface *ifp) { struct listnode *n; struct connected *c = NULL; for (ALL_LIST_ELEMENTS_RO(ifp->connected, n, c)) { if (c->address->family == AF_INET6 && IN6_IS_ADDR_LINKLOCAL(&c->address->u.prefix6)) break; } return c; } #if 0 /* this route_table of struct connected's is unused \ * however, it would be good to use a route_table rather than \ * a list.. \ */ /* Interface looking up by interface's address. */ /* Interface's IPv4 address reverse lookup table. */ struct route_table *ifaddr_ipv4_table; /* struct route_table *ifaddr_ipv6_table; */ static void ifaddr_ipv4_add (struct in_addr *ifaddr, struct interface *ifp) { struct route_node *rn; struct prefix_ipv4 p; p.family = AF_INET; p.prefixlen = IPV4_MAX_PREFIXLEN; p.prefix = *ifaddr; rn = route_node_get (ifaddr_ipv4_table, (struct prefix *) &p); if (rn) { route_unlock_node (rn); zlog_info ("ifaddr_ipv4_add(): address %s is already added", inet_ntoa (*ifaddr)); return; } rn->info = ifp; } static void ifaddr_ipv4_delete (struct in_addr *ifaddr, struct interface *ifp) { struct route_node *rn; struct prefix_ipv4 p; p.family = AF_INET; p.prefixlen = IPV4_MAX_PREFIXLEN; p.prefix = *ifaddr; rn = route_node_lookup (ifaddr_ipv4_table, (struct prefix *) &p); if (! rn) { zlog_info ("ifaddr_ipv4_delete(): can't find address %s", inet_ntoa (*ifaddr)); return; } rn->info = NULL; route_unlock_node (rn); route_unlock_node (rn); } /* Lookup interface by interface's IP address or interface index. */ static struct interface * ifaddr_ipv4_lookup (struct in_addr *addr, ifindex_t ifindex) { struct prefix_ipv4 p; struct route_node *rn; struct interface *ifp; if (addr) { p.family = AF_INET; p.prefixlen = IPV4_MAX_PREFIXLEN; p.prefix = *addr; rn = route_node_lookup (ifaddr_ipv4_table, (struct prefix *) &p); if (! rn) return NULL; ifp = rn->info; route_unlock_node (rn); return ifp; } else return if_lookup_by_index(ifindex, VRF_DEFAULT); } #endif /* ifaddr_ipv4_table */ void if_terminate(struct vrf *vrf) { struct interface *ifp; while (!RB_EMPTY(if_name_head, &vrf->ifaces_by_name)) { ifp = RB_ROOT(if_name_head, &vrf->ifaces_by_name); if (ifp->node) { ifp->node->info = NULL; route_unlock_node(ifp->node); } if_delete(ifp); } } const char *if_link_type_str(enum zebra_link_type llt) { switch (llt) { #define llts(T,S) case (T): return (S) llts(ZEBRA_LLT_UNKNOWN, "Unknown"); llts(ZEBRA_LLT_ETHER, "Ethernet"); llts(ZEBRA_LLT_EETHER, "Experimental Ethernet"); llts(ZEBRA_LLT_AX25, "AX.25 Level 2"); llts(ZEBRA_LLT_PRONET, "PROnet token ring"); llts(ZEBRA_LLT_IEEE802, "IEEE 802.2 Ethernet/TR/TB"); llts(ZEBRA_LLT_ARCNET, "ARCnet"); llts(ZEBRA_LLT_APPLETLK, "AppleTalk"); llts(ZEBRA_LLT_DLCI, "Frame Relay DLCI"); llts(ZEBRA_LLT_ATM, "ATM"); llts(ZEBRA_LLT_METRICOM, "Metricom STRIP"); llts(ZEBRA_LLT_IEEE1394, "IEEE 1394 IPv4"); llts(ZEBRA_LLT_EUI64, "EUI-64"); llts(ZEBRA_LLT_INFINIBAND, "InfiniBand"); llts(ZEBRA_LLT_SLIP, "SLIP"); llts(ZEBRA_LLT_CSLIP, "Compressed SLIP"); llts(ZEBRA_LLT_SLIP6, "SLIPv6"); llts(ZEBRA_LLT_CSLIP6, "Compressed SLIPv6"); llts(ZEBRA_LLT_ROSE, "ROSE packet radio"); llts(ZEBRA_LLT_X25, "CCITT X.25"); llts(ZEBRA_LLT_PPP, "PPP"); llts(ZEBRA_LLT_CHDLC, "Cisco HDLC"); llts(ZEBRA_LLT_RAWHDLC, "Raw HDLC"); llts(ZEBRA_LLT_LAPB, "LAPB"); llts(ZEBRA_LLT_IPIP, "IPIP Tunnel"); llts(ZEBRA_LLT_IPIP6, "IPIP6 Tunnel"); llts(ZEBRA_LLT_FRAD, "FRAD"); llts(ZEBRA_LLT_SKIP, "SKIP vif"); llts(ZEBRA_LLT_LOOPBACK, "Loopback"); llts(ZEBRA_LLT_LOCALTLK, "Localtalk"); llts(ZEBRA_LLT_FDDI, "FDDI"); llts(ZEBRA_LLT_SIT, "IPv6-in-IPv4 SIT"); llts(ZEBRA_LLT_IPDDP, "IP-in-DDP tunnel"); llts(ZEBRA_LLT_IPGRE, "GRE over IP"); llts(ZEBRA_LLT_PIMREG, "PIMSM registration"); llts(ZEBRA_LLT_HIPPI, "HiPPI"); llts(ZEBRA_LLT_IRDA, "IrDA"); llts(ZEBRA_LLT_FCPP, "Fibre-Channel PtP"); llts(ZEBRA_LLT_FCAL, "Fibre-Channel Arbitrated Loop"); llts(ZEBRA_LLT_FCPL, "Fibre-Channel Public Loop"); llts(ZEBRA_LLT_FCFABRIC, "Fibre-Channel Fabric"); llts(ZEBRA_LLT_IEEE802_TR, "IEEE 802.2 Token Ring"); llts(ZEBRA_LLT_IEEE80211, "IEEE 802.11"); llts(ZEBRA_LLT_IEEE80211_RADIOTAP, "IEEE 802.11 Radiotap"); llts(ZEBRA_LLT_IEEE802154, "IEEE 802.15.4"); llts(ZEBRA_LLT_IEEE802154_PHY, "IEEE 802.15.4 Phy"); default: flog_err(EC_LIB_DEVELOPMENT, "Unknown value %d", llt); return "Unknown type!"; #undef llts } return NULL; } struct if_link_params *if_link_params_get(struct interface *ifp) { int i; if (ifp->link_params != NULL) return ifp->link_params; struct if_link_params *iflp = XCALLOC(MTYPE_IF_LINK_PARAMS, sizeof(struct if_link_params)); if (iflp == NULL) return NULL; /* Set TE metric equal to standard metric */ iflp->te_metric = ifp->metric; /* Compute default bandwidth based on interface */ iflp->default_bw = ((ifp->bandwidth ? ifp->bandwidth : DEFAULT_BANDWIDTH) * TE_KILO_BIT / TE_BYTE); /* Set Max, Reservable and Unreserved Bandwidth */ iflp->max_bw = iflp->default_bw; iflp->max_rsv_bw = iflp->default_bw; for (i = 0; i < MAX_CLASS_TYPE; i++) iflp->unrsv_bw[i] = iflp->default_bw; /* Update Link parameters status */ iflp->lp_status = LP_TE_METRIC | LP_MAX_BW | LP_MAX_RSV_BW | LP_UNRSV_BW; /* Finally attach newly created Link Parameters */ ifp->link_params = iflp; return iflp; } void if_link_params_free(struct interface *ifp) { if (ifp->link_params == NULL) return; XFREE(MTYPE_IF_LINK_PARAMS, ifp->link_params); ifp->link_params = NULL; } /* ----------- CLI commands ----------- */ /* * XPath: /frr-interface:lib/interface */ DEFPY_NOSH (interface, interface_cmd, "interface IFNAME [vrf NAME$vrf_name]", "Select an interface to configure\n" "Interface's name\n" VRF_CMD_HELP_STR) { char xpath_list[XPATH_MAXLEN]; vrf_id_t vrf_id; struct interface *ifp; int ret; if (!vrf_name) vrf_name = VRF_DEFAULT_NAME; /* * This command requires special handling to maintain backward * compatibility. If a VRF name is not specified, it means we're willing * to accept any interface with the given name on any VRF. If no * interface is found, then a new one should be created on the default * VRF. */ VRF_GET_ID(vrf_id, vrf_name, false); ifp = if_lookup_by_name_all_vrf(ifname); if (ifp && ifp->vrf_id != vrf_id) { struct vrf *vrf; /* * Special case 1: a VRF name was specified, but the found * interface is associated to different VRF. Reject the command. */ if (vrf_id != VRF_DEFAULT) { vty_out(vty, "%% interface %s not in %s vrf\n", ifname, vrf_name); return CMD_WARNING_CONFIG_FAILED; } /* * Special case 2: a VRF name was *not* specified, and the found * interface is associated to a VRF other than the default one. * Update vrf_id and vrf_name to account for that. */ vrf = vrf_lookup_by_id(ifp->vrf_id); assert(vrf); vrf_id = ifp->vrf_id; vrf_name = vrf->name; } snprintf(xpath_list, sizeof(xpath_list), "/frr-interface:lib/interface[name='%s'][vrf='%s']", ifname, vrf_name); nb_cli_enqueue_change(vty, ".", NB_OP_CREATE, NULL); ret = nb_cli_apply_changes(vty, xpath_list); if (ret == CMD_SUCCESS) { VTY_PUSH_XPATH(INTERFACE_NODE, xpath_list); /* * For backward compatibility with old commands we still need * to use the qobj infrastructure. This can be removed once * all interface-level commands are converted to the new * northbound model. */ ifp = if_lookup_by_name(ifname, vrf_id); if (ifp) VTY_PUSH_CONTEXT(INTERFACE_NODE, ifp); } return ret; } DEFPY (no_interface, no_interface_cmd, "no interface IFNAME [vrf NAME$vrf_name]", NO_STR "Delete a pseudo interface's configuration\n" "Interface's name\n" VRF_CMD_HELP_STR) { if (!vrf_name) vrf_name = VRF_DEFAULT_NAME; nb_cli_enqueue_change(vty, ".", NB_OP_DESTROY, NULL); return nb_cli_apply_changes( vty, "/frr-interface:lib/interface[name='%s'][vrf='%s']", ifname, vrf_name); } static void cli_show_interface(struct vty *vty, struct lyd_node *dnode, bool show_defaults) { const char *vrf; vrf = yang_dnode_get_string(dnode, "./vrf"); vty_out(vty, "!\n"); vty_out(vty, "interface %s", yang_dnode_get_string(dnode, "./name")); if (!strmatch(vrf, VRF_DEFAULT_NAME)) vty_out(vty, " vrf %s", vrf); vty_out(vty, "\n"); } /* * XPath: /frr-interface:lib/interface/description */ DEFPY (interface_desc, interface_desc_cmd, "description LINE...", "Interface specific description\n" "Characters describing this interface\n") { char *desc; int ret; desc = argv_concat(argv, argc, 1); nb_cli_enqueue_change(vty, "./description", NB_OP_MODIFY, desc); ret = nb_cli_apply_changes(vty, NULL); XFREE(MTYPE_TMP, desc); return ret; } DEFPY (no_interface_desc, no_interface_desc_cmd, "no description", NO_STR "Interface specific description\n") { nb_cli_enqueue_change(vty, "./description", NB_OP_DESTROY, NULL); return nb_cli_apply_changes(vty, NULL); } static void cli_show_interface_desc(struct vty *vty, struct lyd_node *dnode, bool show_defaults) { vty_out(vty, " description %s\n", yang_dnode_get_string(dnode, NULL)); } /* Interface autocomplete. */ static void if_autocomplete(vector comps, struct cmd_token *token) { struct interface *ifp; struct vrf *vrf; RB_FOREACH (vrf, vrf_name_head, &vrfs_by_name) { FOR_ALL_INTERFACES (vrf, ifp) { vector_set(comps, XSTRDUP(MTYPE_COMPLETION, ifp->name)); } } } static const struct cmd_variable_handler if_var_handlers[] = { {/* "interface NAME" */ .varname = "interface", .completions = if_autocomplete}, {.tokenname = "IFNAME", .completions = if_autocomplete}, {.tokenname = "INTERFACE", .completions = if_autocomplete}, {.completions = NULL}}; void if_cmd_init(void) { cmd_variable_handler_register(if_var_handlers); install_element(CONFIG_NODE, &interface_cmd); install_element(CONFIG_NODE, &no_interface_cmd); install_default(INTERFACE_NODE); install_element(INTERFACE_NODE, &interface_desc_cmd); install_element(INTERFACE_NODE, &no_interface_desc_cmd); } /* ------- Northbound callbacks ------- */ /* * XPath: /frr-interface:lib/interface */ static int lib_interface_create(enum nb_event event, const struct lyd_node *dnode, union nb_resource *resource) { const char *ifname; const char *vrfname; struct vrf *vrf; struct interface *ifp; ifname = yang_dnode_get_string(dnode, "./name"); vrfname = yang_dnode_get_string(dnode, "./vrf"); switch (event) { case NB_EV_VALIDATE: vrf = vrf_lookup_by_name(vrfname); if (!vrf) { zlog_warn("%s: VRF %s doesn't exist", __func__, vrfname); return NB_ERR_VALIDATION; } if (vrf->vrf_id == VRF_UNKNOWN) { zlog_warn("%s: VRF %s is not active", __func__, vrf->name); return NB_ERR_VALIDATION; } /* if VRF is netns or not yet known - init for instance * then assumption is that passed config is exact * then the user intent was not to use an other iface */ if (vrf_get_backend() == VRF_BACKEND_VRF_LITE) { ifp = if_lookup_by_name_all_vrf(ifname); if (ifp && ifp->vrf_id != vrf->vrf_id) { zlog_warn( "%s: interface %s already exists in another VRF", __func__, ifp->name); return NB_ERR_VALIDATION; } } break; case NB_EV_PREPARE: case NB_EV_ABORT: break; case NB_EV_APPLY: vrf = vrf_lookup_by_name(vrfname); assert(vrf); #ifdef SUNOS_5 ifp = if_sunwzebra_get(ifname, vrf->vrf_id); #else ifp = if_get_by_name(ifname, vrf->vrf_id); #endif /* SUNOS_5 */ nb_running_set_entry(dnode, ifp); break; } return NB_OK; } static int lib_interface_destroy(enum nb_event event, const struct lyd_node *dnode) { struct interface *ifp; switch (event) { case NB_EV_VALIDATE: ifp = nb_running_get_entry(dnode, NULL, true); if (CHECK_FLAG(ifp->status, ZEBRA_INTERFACE_ACTIVE)) { zlog_warn("%s: only inactive interfaces can be deleted", __func__); return NB_ERR_VALIDATION; } break; case NB_EV_PREPARE: case NB_EV_ABORT: break; case NB_EV_APPLY: ifp = nb_running_unset_entry(dnode); if_delete(ifp); break; } return NB_OK; } /* * XPath: /frr-interface:lib/interface/description */ static int lib_interface_description_modify(enum nb_event event, const struct lyd_node *dnode, union nb_resource *resource) { struct interface *ifp; const char *description; if (event != NB_EV_APPLY) return NB_OK; ifp = nb_running_get_entry(dnode, NULL, true); XFREE(MTYPE_TMP, ifp->desc); description = yang_dnode_get_string(dnode, NULL); ifp->desc = XSTRDUP(MTYPE_TMP, description); return NB_OK; } static int lib_interface_description_destroy(enum nb_event event, const struct lyd_node *dnode) { struct interface *ifp; if (event != NB_EV_APPLY) return NB_OK; ifp = nb_running_get_entry(dnode, NULL, true); XFREE(MTYPE_TMP, ifp->desc); return NB_OK; } /* clang-format off */ const struct frr_yang_module_info frr_interface_info = { .name = "frr-interface", .nodes = { { .xpath = "/frr-interface:lib/interface", .cbs = { .create = lib_interface_create, .destroy = lib_interface_destroy, .cli_show = cli_show_interface, }, }, { .xpath = "/frr-interface:lib/interface/description", .cbs = { .modify = lib_interface_description_modify, .destroy = lib_interface_description_destroy, .cli_show = cli_show_interface_desc, }, }, { .xpath = NULL, }, } }; frr-7.2.1/lib/if.h0000644000000000000000000004504013610377563010534 00000000000000/* Interface related header. * Copyright (C) 1997, 98, 99 Kunihiro Ishiguro * * This file is part of GNU Zebra. * * GNU Zebra is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License as published * by the Free Software Foundation; either version 2, or (at your * option) any later version. * * GNU Zebra 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; see the file COPYING; if not, write to the Free Software * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA */ #ifndef _ZEBRA_IF_H #define _ZEBRA_IF_H #include "zebra.h" #include "linklist.h" #include "memory.h" #include "qobj.h" #include "hook.h" #ifdef __cplusplus extern "C" { #endif DECLARE_MTYPE(CONNECTED_LABEL) /* Interface link-layer type, if known. Derived from: * * net/if_arp.h on various platforms - Linux especially. * http://www.iana.org/assignments/arp-parameters/arp-parameters.xhtml * * Some of the more obviously defunct technologies left out. */ enum zebra_link_type { ZEBRA_LLT_UNKNOWN = 0, ZEBRA_LLT_ETHER, ZEBRA_LLT_EETHER, ZEBRA_LLT_AX25, ZEBRA_LLT_PRONET, ZEBRA_LLT_IEEE802, ZEBRA_LLT_ARCNET, ZEBRA_LLT_APPLETLK, ZEBRA_LLT_DLCI, ZEBRA_LLT_ATM, ZEBRA_LLT_METRICOM, ZEBRA_LLT_IEEE1394, ZEBRA_LLT_EUI64, ZEBRA_LLT_INFINIBAND, ZEBRA_LLT_SLIP, ZEBRA_LLT_CSLIP, ZEBRA_LLT_SLIP6, ZEBRA_LLT_CSLIP6, ZEBRA_LLT_RSRVD, ZEBRA_LLT_ADAPT, ZEBRA_LLT_ROSE, ZEBRA_LLT_X25, ZEBRA_LLT_PPP, ZEBRA_LLT_CHDLC, ZEBRA_LLT_LAPB, ZEBRA_LLT_RAWHDLC, ZEBRA_LLT_IPIP, ZEBRA_LLT_IPIP6, ZEBRA_LLT_FRAD, ZEBRA_LLT_SKIP, ZEBRA_LLT_LOOPBACK, ZEBRA_LLT_LOCALTLK, ZEBRA_LLT_FDDI, ZEBRA_LLT_SIT, ZEBRA_LLT_IPDDP, ZEBRA_LLT_IPGRE, ZEBRA_LLT_IP6GRE, ZEBRA_LLT_PIMREG, ZEBRA_LLT_HIPPI, ZEBRA_LLT_ECONET, ZEBRA_LLT_IRDA, ZEBRA_LLT_FCPP, ZEBRA_LLT_FCAL, ZEBRA_LLT_FCPL, ZEBRA_LLT_FCFABRIC, ZEBRA_LLT_IEEE802_TR, ZEBRA_LLT_IEEE80211, ZEBRA_LLT_IEEE80211_RADIOTAP, ZEBRA_LLT_IEEE802154, ZEBRA_LLT_IEEE802154_PHY, }; /* Interface name length. Linux define value in /usr/include/linux/if.h. #define IFNAMSIZ 16 FreeBSD define value in /usr/include/net/if.h. #define IFNAMSIZ 16 */ #define INTERFACE_NAMSIZ 20 #define INTERFACE_HWADDR_MAX 20 typedef signed int ifindex_t; #ifdef HAVE_PROC_NET_DEV struct if_stats { unsigned long rx_packets; /* total packets received */ unsigned long tx_packets; /* total packets transmitted */ unsigned long rx_bytes; /* total bytes received */ unsigned long tx_bytes; /* total bytes transmitted */ unsigned long rx_errors; /* bad packets received */ unsigned long tx_errors; /* packet transmit problems */ unsigned long rx_dropped; /* no space in linux buffers */ unsigned long tx_dropped; /* no space available in linux */ unsigned long rx_multicast; /* multicast packets received */ unsigned long rx_compressed; unsigned long tx_compressed; unsigned long collisions; /* detailed rx_errors: */ unsigned long rx_length_errors; unsigned long rx_over_errors; /* receiver ring buff overflow */ unsigned long rx_crc_errors; /* recved pkt with crc error */ unsigned long rx_frame_errors; /* recv'd frame alignment error */ unsigned long rx_fifo_errors; /* recv'r fifo overrun */ unsigned long rx_missed_errors; /* receiver missed packet */ /* detailed tx_errors */ unsigned long tx_aborted_errors; unsigned long tx_carrier_errors; unsigned long tx_fifo_errors; unsigned long tx_heartbeat_errors; unsigned long tx_window_errors; }; #endif /* HAVE_PROC_NET_DEV */ /* Here are "non-official" architectural constants. */ #define TE_EXT_MASK 0x0FFFFFFF #define TE_EXT_ANORMAL 0x80000000 #define LOSS_PRECISION 0.000003 #define TE_KILO_BIT 1000 #define TE_BYTE 8 #define DEFAULT_BANDWIDTH 10000 #define MAX_CLASS_TYPE 8 #define MAX_PKT_LOSS 50.331642 /* * Link Parameters Status: * equal to 0: unset * different from 0: set */ #define LP_UNSET 0x0000 #define LP_TE_METRIC 0x0001 #define LP_MAX_BW 0x0002 #define LP_MAX_RSV_BW 0x0004 #define LP_UNRSV_BW 0x0008 #define LP_ADM_GRP 0x0010 #define LP_RMT_AS 0x0020 #define LP_DELAY 0x0040 #define LP_MM_DELAY 0x0080 #define LP_DELAY_VAR 0x0100 #define LP_PKT_LOSS 0x0200 #define LP_RES_BW 0x0400 #define LP_AVA_BW 0x0800 #define LP_USE_BW 0x1000 #define IS_PARAM_UNSET(lp, st) !(lp->lp_status & st) #define IS_PARAM_SET(lp, st) (lp->lp_status & st) #define IS_LINK_PARAMS_SET(lp) (lp->lp_status != LP_UNSET) #define SET_PARAM(lp, st) (lp->lp_status) |= (st) #define UNSET_PARAM(lp, st) (lp->lp_status) &= ~(st) #define RESET_LINK_PARAM(lp) (lp->lp_status = LP_UNSET) /* Link Parameters for Traffic Engineering */ struct if_link_params { uint32_t lp_status; /* Status of Link Parameters: */ uint32_t te_metric; /* Traffic Engineering metric */ float default_bw; float max_bw; /* Maximum Bandwidth */ float max_rsv_bw; /* Maximum Reservable Bandwidth */ float unrsv_bw[MAX_CLASS_TYPE]; /* Unreserved Bandwidth per Class Type (8) */ uint32_t admin_grp; /* Administrative group */ uint32_t rmt_as; /* Remote AS number */ struct in_addr rmt_ip; /* Remote IP address */ uint32_t av_delay; /* Link Average Delay */ uint32_t min_delay; /* Link Min Delay */ uint32_t max_delay; /* Link Max Delay */ uint32_t delay_var; /* Link Delay Variation */ float pkt_loss; /* Link Packet Loss */ float res_bw; /* Residual Bandwidth */ float ava_bw; /* Available Bandwidth */ float use_bw; /* Utilized Bandwidth */ }; #define INTERFACE_LINK_PARAMS_SIZE sizeof(struct if_link_params) #define HAS_LINK_PARAMS(ifp) ((ifp)->link_params != NULL) /* Interface structure */ struct interface { RB_ENTRY(interface) name_entry, index_entry; /* Interface name. This should probably never be changed after the interface is created, because the configuration info for this interface is associated with this structure. For that reason, the interface should also never be deleted (to avoid losing configuration info). To delete, just set ifindex to IFINDEX_INTERNAL to indicate that the interface does not exist in the kernel. */ char name[INTERFACE_NAMSIZ]; /* Interface index (should be IFINDEX_INTERNAL for non-kernel or deleted interfaces). WARNING: the ifindex needs to be changed using the if_set_index() function. Failure to respect this will cause corruption in the data structure used to store the interfaces and if_lookup_by_index() will not work as expected. */ ifindex_t ifindex; /* * ifindex of parent interface, if any */ ifindex_t link_ifindex; #define IFINDEX_INTERNAL 0 /* Zebra internal interface status */ uint8_t status; #define ZEBRA_INTERFACE_ACTIVE (1 << 0) #define ZEBRA_INTERFACE_SUB (1 << 1) #define ZEBRA_INTERFACE_LINKDETECTION (1 << 2) #define ZEBRA_INTERFACE_VRF_LOOPBACK (1 << 3) /* Interface flags. */ uint64_t flags; /* Interface metric */ uint32_t metric; /* Interface Speed in Mb/s */ uint32_t speed; /* Interface MTU. */ unsigned int mtu; /* IPv4 MTU */ unsigned int mtu6; /* IPv6 MTU - probably, but not neccessarily same as mtu */ /* Link-layer information and hardware address */ enum zebra_link_type ll_type; uint8_t hw_addr[INTERFACE_HWADDR_MAX]; int hw_addr_len; /* interface bandwidth, kbits */ unsigned int bandwidth; /* Link parameters for Traffic Engineering */ struct if_link_params *link_params; /* description of the interface. */ char *desc; /* Distribute list. */ void *distribute_in; void *distribute_out; /* Connected address list. */ struct list *connected; /* Neighbor connected address list. */ struct list *nbr_connected; /* Daemon specific interface data pointer. */ void *info; char ptm_enable; /* Should we look at ptm_status ? */ char ptm_status; /* Statistics fileds. */ #ifdef HAVE_PROC_NET_DEV struct if_stats stats; #endif /* HAVE_PROC_NET_DEV */ #ifdef HAVE_NET_RT_IFLIST struct if_data stats; #endif /* HAVE_NET_RT_IFLIST */ struct route_node *node; vrf_id_t vrf_id; QOBJ_FIELDS }; RB_HEAD(if_name_head, interface); RB_PROTOTYPE(if_name_head, interface, name_entry, if_cmp_func) RB_HEAD(if_index_head, interface); RB_PROTOTYPE(if_index_head, interface, index_entry, if_cmp_index_func) DECLARE_QOBJ_TYPE(interface) #define IFNAME_RB_INSERT(vrf, ifp) \ if (RB_INSERT(if_name_head, &vrf->ifaces_by_name, (ifp))) \ flog_err(EC_LIB_INTERFACE, \ "%s(%s): corruption detected -- interface with this " \ "name exists already in VRF %u!", \ __func__, (ifp)->name, (ifp)->vrf_id); #define IFNAME_RB_REMOVE(vrf, ifp) \ if (RB_REMOVE(if_name_head, &vrf->ifaces_by_name, (ifp)) == NULL) \ flog_err(EC_LIB_INTERFACE, \ "%s(%s): corruption detected -- interface with this " \ "name doesn't exist in VRF %u!", \ __func__, (ifp)->name, (ifp)->vrf_id); #define IFINDEX_RB_INSERT(vrf, ifp) \ if (RB_INSERT(if_index_head, &vrf->ifaces_by_index, (ifp))) \ flog_err(EC_LIB_INTERFACE, \ "%s(%u): corruption detected -- interface with this " \ "ifindex exists already in VRF %u!", \ __func__, (ifp)->ifindex, (ifp)->vrf_id); #define IFINDEX_RB_REMOVE(vrf, ifp) \ if (RB_REMOVE(if_index_head, &vrf->ifaces_by_index, (ifp)) == NULL) \ flog_err(EC_LIB_INTERFACE, \ "%s(%u): corruption detected -- interface with this " \ "ifindex doesn't exist in VRF %u!", \ __func__, (ifp)->ifindex, (ifp)->vrf_id); #define FOR_ALL_INTERFACES(vrf, ifp) \ if (vrf) \ RB_FOREACH (ifp, if_name_head, &vrf->ifaces_by_name) #define FOR_ALL_INTERFACES_ADDRESSES(ifp, connected, node) \ for (ALL_LIST_ELEMENTS_RO(ifp->connected, node, connected)) /* called from the library code whenever interfaces are created/deleted * note: interfaces may not be fully realized at that point; also they * may not exist in the system (ifindex = IFINDEX_INTERNAL) * * priority values are important here, daemons should be at 0 while modules * can use 1000+ so they run after the daemon has initialised daemon-specific * interface data */ DECLARE_HOOK(if_add, (struct interface * ifp), (ifp)) DECLARE_KOOH(if_del, (struct interface * ifp), (ifp)) #define METRIC_MAX (~0) /* Connected address structure. */ struct connected { /* Attached interface. */ struct interface *ifp; /* Flags for configuration. */ uint8_t conf; #define ZEBRA_IFC_REAL (1 << 0) #define ZEBRA_IFC_CONFIGURED (1 << 1) #define ZEBRA_IFC_QUEUED (1 << 2) /* The ZEBRA_IFC_REAL flag should be set if and only if this address exists in the kernel and is actually usable. (A case where it exists but is not yet usable would be IPv6 with DAD) The ZEBRA_IFC_CONFIGURED flag should be set if and only if this address was configured by the user from inside quagga. The ZEBRA_IFC_QUEUED flag should be set if and only if the address exists in the kernel. It may and should be set although the address might not be usable yet. (compare with ZEBRA_IFC_REAL) */ /* Flags for connected address. */ uint8_t flags; #define ZEBRA_IFA_SECONDARY (1 << 0) #define ZEBRA_IFA_PEER (1 << 1) #define ZEBRA_IFA_UNNUMBERED (1 << 2) /* N.B. the ZEBRA_IFA_PEER flag should be set if and only if a peer address has been configured. If this flag is set, the destination field must contain the peer address. */ /* Address of connected network. */ struct prefix *address; /* Peer address, if ZEBRA_IFA_PEER is set, otherwise NULL */ struct prefix *destination; /* Label for Linux 2.2.X and upper. */ char *label; /* * Used for setting the connected route's cost. If the metric * here is set to METRIC_MAX the connected route falls back to * "struct interface" */ uint32_t metric; }; /* Nbr Connected address structure. */ struct nbr_connected { /* Attached interface. */ struct interface *ifp; /* Address of connected network. */ struct prefix *address; }; /* Does the destination field contain a peer address? */ #define CONNECTED_PEER(C) CHECK_FLAG((C)->flags, ZEBRA_IFA_PEER) /* Prefix to insert into the RIB */ #define CONNECTED_PREFIX(C) \ (CONNECTED_PEER(C) ? (C)->destination : (C)->address) /* Identifying address. We guess that if there's a peer address, but the local address is in the same prefix, then the local address may be unique. */ #define CONNECTED_ID(C) \ ((CONNECTED_PEER(C) && !prefix_match((C)->destination, (C)->address)) \ ? (C)->destination \ : (C)->address) /* There are some interface flags which are only supported by some operating system. */ #ifndef IFF_NOTRAILERS #define IFF_NOTRAILERS 0x0 #endif /* IFF_NOTRAILERS */ #ifndef IFF_OACTIVE #define IFF_OACTIVE 0x0 #endif /* IFF_OACTIVE */ #ifndef IFF_SIMPLEX #define IFF_SIMPLEX 0x0 #endif /* IFF_SIMPLEX */ #ifndef IFF_LINK0 #define IFF_LINK0 0x0 #endif /* IFF_LINK0 */ #ifndef IFF_LINK1 #define IFF_LINK1 0x0 #endif /* IFF_LINK1 */ #ifndef IFF_LINK2 #define IFF_LINK2 0x0 #endif /* IFF_LINK2 */ #ifndef IFF_NOXMIT #define IFF_NOXMIT 0x0 #endif /* IFF_NOXMIT */ #ifndef IFF_NORTEXCH #define IFF_NORTEXCH 0x0 #endif /* IFF_NORTEXCH */ #ifndef IFF_IPV4 #define IFF_IPV4 0x0 #endif /* IFF_IPV4 */ #ifndef IFF_IPV6 #define IFF_IPV6 0x0 #endif /* IFF_IPV6 */ #ifndef IFF_VIRTUAL #define IFF_VIRTUAL 0x0 #endif /* IFF_VIRTUAL */ /* Prototypes. */ extern int if_cmp_name_func(const char *p1, const char *p2); /* * Passing in VRF_UNKNOWN is a valid thing to do, unless we * are creating a new interface. * * This is useful for vrf route-leaking. So more than anything * else think before you use VRF_UNKNOWN */ extern void if_update_to_new_vrf(struct interface *, vrf_id_t vrf_id); extern struct interface *if_create(const char *name, vrf_id_t vrf_id); extern struct interface *if_create_ifindex(ifindex_t ifindex, vrf_id_t vrf_id); extern struct interface *if_lookup_by_index(ifindex_t, vrf_id_t vrf_id); extern struct interface *if_lookup_by_index_all_vrf(ifindex_t); extern struct interface *if_lookup_exact_address(void *matchaddr, int family, vrf_id_t vrf_id); extern struct connected *if_lookup_address(void *matchaddr, int family, vrf_id_t vrf_id); extern struct interface *if_lookup_prefix(struct prefix *prefix, vrf_id_t vrf_id); size_t if_lookup_by_hwaddr(const uint8_t *hw_addr, size_t addrsz, struct interface ***result, vrf_id_t vrf_id); /* These 3 functions are to be used when the ifname argument is terminated by a '\0' character: */ extern struct interface *if_lookup_by_name_all_vrf(const char *ifname); extern struct interface *if_lookup_by_name(const char *ifname, vrf_id_t vrf_id); extern struct interface *if_get_by_name(const char *ifname, vrf_id_t vrf_id); extern struct interface *if_get_by_ifindex(ifindex_t ifindex, vrf_id_t vrf_id); extern void if_set_index(struct interface *ifp, ifindex_t ifindex); /* Delete the interface, but do not free the structure, and leave it in the interface list. It is often advisable to leave the pseudo interface structure because there may be configuration information attached. */ extern void if_delete_retain(struct interface *); /* Delete and free the interface structure: calls if_delete_retain and then deletes it from the interface list and frees the structure. */ extern void if_delete(struct interface *); extern int if_is_up(const struct interface *ifp); extern int if_is_running(const struct interface *ifp); extern int if_is_operative(const struct interface *ifp); extern int if_is_no_ptm_operative(const struct interface *ifp); extern int if_is_loopback(const struct interface *ifp); extern int if_is_vrf(const struct interface *ifp); extern bool if_is_loopback_or_vrf(const struct interface *ifp); extern int if_is_broadcast(const struct interface *ifp); extern int if_is_pointopoint(const struct interface *ifp); extern int if_is_multicast(const struct interface *ifp); struct vrf; extern void if_terminate(struct vrf *vrf); extern void if_dump_all(void); extern const char *if_flag_dump(unsigned long); extern const char *if_link_type_str(enum zebra_link_type); /* Please use ifindex2ifname instead of if_indextoname where possible; ifindex2ifname uses internal interface info, whereas if_indextoname must make a system call. */ extern const char *ifindex2ifname(ifindex_t, vrf_id_t vrf_id); /* Please use ifname2ifindex instead of if_nametoindex where possible; ifname2ifindex uses internal interface info, whereas if_nametoindex must make a system call. */ extern ifindex_t ifname2ifindex(const char *ifname, vrf_id_t vrf_id); /* Connected address functions. */ extern struct connected *connected_new(void); extern void connected_free(struct connected *); extern void connected_add(struct interface *, struct connected *); extern struct connected * connected_add_by_prefix(struct interface *, struct prefix *, struct prefix *); extern struct connected *connected_delete_by_prefix(struct interface *, struct prefix *); extern struct connected *connected_lookup_prefix(struct interface *, struct prefix *); extern struct connected *connected_lookup_prefix_exact(struct interface *, struct prefix *); extern struct nbr_connected *nbr_connected_new(void); extern void nbr_connected_free(struct nbr_connected *); struct nbr_connected *nbr_connected_check(struct interface *, struct prefix *); struct connected *connected_get_linklocal(struct interface *ifp); /* link parameters */ struct if_link_params *if_link_params_get(struct interface *); void if_link_params_free(struct interface *); /* Northbound. */ extern void if_cmd_init(void); extern const struct frr_yang_module_info frr_interface_info; #ifdef __cplusplus } #endif #endif /* _ZEBRA_IF_H */ frr-7.2.1/lib/if_rmap.c0000644000000000000000000002025113610377563011543 00000000000000/* route-map for interface. * Copyright (C) 1999 Kunihiro Ishiguro * * This file is part of GNU Zebra. * * GNU Zebra is free software; you can redistribute it and/or modify it * under the terms of the GNU General Public License as published by the * Free Software Foundation; either version 2, or (at your option) any * later version. * * GNU Zebra 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; see the file COPYING; if not, write to the Free Software * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA */ #include #include "hash.h" #include "command.h" #include "memory.h" #include "if.h" #include "if_rmap.h" DEFINE_MTYPE_STATIC(LIB, IF_RMAP_CTX, "Interface route map container") DEFINE_MTYPE_STATIC(LIB, IF_RMAP_CTX_NAME, "Interface route map container name") DEFINE_MTYPE_STATIC(LIB, IF_RMAP, "Interface route map") DEFINE_MTYPE_STATIC(LIB, IF_RMAP_NAME, "I.f. route map name") static struct list *if_rmap_ctx_list; static struct if_rmap *if_rmap_new(void) { struct if_rmap *new; new = XCALLOC(MTYPE_IF_RMAP, sizeof(struct if_rmap)); return new; } static void if_rmap_free(struct if_rmap *if_rmap) { XFREE(MTYPE_IF_RMAP_NAME, if_rmap->ifname); XFREE(MTYPE_IF_RMAP_NAME, if_rmap->routemap[IF_RMAP_IN]); XFREE(MTYPE_IF_RMAP_NAME, if_rmap->routemap[IF_RMAP_OUT]); XFREE(MTYPE_IF_RMAP, if_rmap); } struct if_rmap *if_rmap_lookup(struct if_rmap_ctx *ctx, const char *ifname) { struct if_rmap key; struct if_rmap *if_rmap; /* temporary copy */ key.ifname = (ifname) ? XSTRDUP(MTYPE_IF_RMAP_NAME, ifname) : NULL; if_rmap = hash_lookup(ctx->ifrmaphash, &key); XFREE(MTYPE_IF_RMAP_NAME, key.ifname); return if_rmap; } void if_rmap_hook_add(struct if_rmap_ctx *ctx, void (*func)(struct if_rmap_ctx *ctx, struct if_rmap *)) { ctx->if_rmap_add_hook = func; } void if_rmap_hook_delete(struct if_rmap_ctx *ctx, void (*func)(struct if_rmap_ctx *ctx, struct if_rmap *)) { ctx->if_rmap_delete_hook = func; } static void *if_rmap_hash_alloc(void *arg) { struct if_rmap *ifarg = (struct if_rmap *)arg; struct if_rmap *if_rmap; if_rmap = if_rmap_new(); if_rmap->ifname = XSTRDUP(MTYPE_IF_RMAP_NAME, ifarg->ifname); return if_rmap; } static struct if_rmap *if_rmap_get(struct if_rmap_ctx *ctx, const char *ifname) { struct if_rmap key; struct if_rmap *ret; /* temporary copy */ key.ifname = (ifname) ? XSTRDUP(MTYPE_IF_RMAP_NAME, ifname) : NULL; ret = hash_get(ctx->ifrmaphash, &key, if_rmap_hash_alloc); XFREE(MTYPE_IF_RMAP_NAME, key.ifname); return ret; } static unsigned int if_rmap_hash_make(const void *data) { const struct if_rmap *if_rmap = data; return string_hash_make(if_rmap->ifname); } static bool if_rmap_hash_cmp(const void *arg1, const void *arg2) { const struct if_rmap *if_rmap1 = arg1; const struct if_rmap *if_rmap2 = arg2; return strcmp(if_rmap1->ifname, if_rmap2->ifname) == 0; } static struct if_rmap *if_rmap_set(struct if_rmap_ctx *ctx, const char *ifname, enum if_rmap_type type, const char *routemap_name) { struct if_rmap *if_rmap; if_rmap = if_rmap_get(ctx, ifname); if (type == IF_RMAP_IN) { XFREE(MTYPE_IF_RMAP_NAME, if_rmap->routemap[IF_RMAP_IN]); if_rmap->routemap[IF_RMAP_IN] = XSTRDUP(MTYPE_IF_RMAP_NAME, routemap_name); } if (type == IF_RMAP_OUT) { XFREE(MTYPE_IF_RMAP_NAME, if_rmap->routemap[IF_RMAP_OUT]); if_rmap->routemap[IF_RMAP_OUT] = XSTRDUP(MTYPE_IF_RMAP_NAME, routemap_name); } if (ctx->if_rmap_add_hook) (ctx->if_rmap_add_hook)(ctx, if_rmap); return if_rmap; } static int if_rmap_unset(struct if_rmap_ctx *ctx, const char *ifname, enum if_rmap_type type, const char *routemap_name) { struct if_rmap *if_rmap; if_rmap = if_rmap_lookup(ctx, ifname); if (!if_rmap) return 0; if (type == IF_RMAP_IN) { if (!if_rmap->routemap[IF_RMAP_IN]) return 0; if (strcmp(if_rmap->routemap[IF_RMAP_IN], routemap_name) != 0) return 0; XFREE(MTYPE_IF_RMAP_NAME, if_rmap->routemap[IF_RMAP_IN]); if_rmap->routemap[IF_RMAP_IN] = NULL; } if (type == IF_RMAP_OUT) { if (!if_rmap->routemap[IF_RMAP_OUT]) return 0; if (strcmp(if_rmap->routemap[IF_RMAP_OUT], routemap_name) != 0) return 0; XFREE(MTYPE_IF_RMAP_NAME, if_rmap->routemap[IF_RMAP_OUT]); if_rmap->routemap[IF_RMAP_OUT] = NULL; } if (ctx->if_rmap_delete_hook) ctx->if_rmap_delete_hook(ctx, if_rmap); if (if_rmap->routemap[IF_RMAP_IN] == NULL && if_rmap->routemap[IF_RMAP_OUT] == NULL) { hash_release(ctx->ifrmaphash, if_rmap); if_rmap_free(if_rmap); } return 1; } DEFUN (if_rmap, if_rmap_cmd, "route-map RMAP_NAME IFNAME", "Route map set\n" "Route map name\n" "Route map set for input filtering\n" "Route map set for output filtering\n" "Route map interface name\n") { int idx_rmap_name = 1; int idx_in_out = 2; int idx_ifname = 3; enum if_rmap_type type; struct if_rmap_ctx *ctx = (struct if_rmap_ctx *)listnode_head(if_rmap_ctx_list); if (strncmp(argv[idx_in_out]->text, "in", 1) == 0) type = IF_RMAP_IN; else if (strncmp(argv[idx_in_out]->text, "out", 1) == 0) type = IF_RMAP_OUT; else { vty_out(vty, "route-map direction must be [in|out]\n"); return CMD_WARNING_CONFIG_FAILED; } if_rmap_set(ctx, argv[idx_ifname]->arg, type, argv[idx_rmap_name]->arg); return CMD_SUCCESS; } DEFUN (no_if_rmap, no_if_rmap_cmd, "no route-map ROUTEMAP_NAME IFNAME", NO_STR "Route map unset\n" "Route map name\n" "Route map for input filtering\n" "Route map for output filtering\n" "Route map interface name\n") { int idx_routemap_name = 2; int idx_in_out = 3; int idx_ifname = 4; int ret; enum if_rmap_type type; struct if_rmap_ctx *ctx = (struct if_rmap_ctx *)listnode_head(if_rmap_ctx_list); if (strncmp(argv[idx_in_out]->arg, "i", 1) == 0) type = IF_RMAP_IN; else if (strncmp(argv[idx_in_out]->arg, "o", 1) == 0) type = IF_RMAP_OUT; else { vty_out(vty, "route-map direction must be [in|out]\n"); return CMD_WARNING_CONFIG_FAILED; } ret = if_rmap_unset(ctx, argv[idx_ifname]->arg, type, argv[idx_routemap_name]->arg); if (!ret) { vty_out(vty, "route-map doesn't exist\n"); return CMD_WARNING_CONFIG_FAILED; } return CMD_SUCCESS; } /* Configuration write function. */ int config_write_if_rmap(struct vty *vty, struct if_rmap_ctx *ctx) { unsigned int i; struct hash_bucket *mp; int write = 0; struct hash *ifrmaphash = ctx->ifrmaphash; for (i = 0; i < ifrmaphash->size; i++) for (mp = ifrmaphash->index[i]; mp; mp = mp->next) { struct if_rmap *if_rmap; if_rmap = mp->data; if (if_rmap->routemap[IF_RMAP_IN]) { vty_out(vty, " route-map %s in %s\n", if_rmap->routemap[IF_RMAP_IN], if_rmap->ifname); write++; } if (if_rmap->routemap[IF_RMAP_OUT]) { vty_out(vty, " route-map %s out %s\n", if_rmap->routemap[IF_RMAP_OUT], if_rmap->ifname); write++; } } return write; } void if_rmap_ctx_delete(struct if_rmap_ctx *ctx) { listnode_delete(if_rmap_ctx_list, ctx); hash_clean(ctx->ifrmaphash, (void (*)(void *))if_rmap_free); if (ctx->name) XFREE(MTYPE_IF_RMAP_CTX_NAME, ctx); XFREE(MTYPE_IF_RMAP_CTX, ctx); } /* name is optional: either vrf name, or other */ struct if_rmap_ctx *if_rmap_ctx_create(const char *name) { struct if_rmap_ctx *ctx; ctx = XCALLOC(MTYPE_IF_RMAP_CTX, sizeof(struct if_rmap_ctx)); if (ctx->name) ctx->name = XSTRDUP(MTYPE_IF_RMAP_CTX_NAME, name); ctx->ifrmaphash = hash_create_size(4, if_rmap_hash_make, if_rmap_hash_cmp, "Interface Route-Map Hash"); if (!if_rmap_ctx_list) if_rmap_ctx_list = list_new(); listnode_add(if_rmap_ctx_list, ctx); return ctx; } void if_rmap_init(int node) { if (node == RIPNG_NODE) { } else if (node == RIP_NODE) { install_element(RIP_NODE, &if_rmap_cmd); install_element(RIP_NODE, &no_if_rmap_cmd); } if_rmap_ctx_list = list_new(); } void if_rmap_terminate(void) { if (!if_rmap_ctx_list) return; list_delete(&if_rmap_ctx_list); } frr-7.2.1/lib/if_rmap.h0000644000000000000000000000377313610377563011562 00000000000000/* route-map for interface. * Copyright (C) 1999 Kunihiro Ishiguro * * This file is part of GNU Zebra. * * GNU Zebra is free software; you can redistribute it and/or modify it * under the terms of the GNU General Public License as published by the * Free Software Foundation; either version 2, or (at your option) any * later version. * * GNU Zebra 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; see the file COPYING; if not, write to the Free Software * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA */ #ifndef _ZEBRA_IF_RMAP_H #define _ZEBRA_IF_RMAP_H #ifdef __cplusplus extern "C" { #endif enum if_rmap_type { IF_RMAP_IN, IF_RMAP_OUT, IF_RMAP_MAX }; struct if_rmap { /* Name of the interface. */ char *ifname; char *routemap[IF_RMAP_MAX]; }; struct if_rmap_ctx { /* if_rmap */ struct hash *ifrmaphash; /* Hook functions. */ void (*if_rmap_add_hook)(struct if_rmap_ctx *ctx, struct if_rmap *ifrmap); void (*if_rmap_delete_hook)(struct if_rmap_ctx *ctx, struct if_rmap *ifrmap); /* naming information */ char *name; }; extern struct if_rmap_ctx *if_rmap_ctx_create(const char *name); extern void if_rmap_ctx_delete(struct if_rmap_ctx *ctx); extern void if_rmap_init(int node); extern void if_rmap_terminate(void); void if_rmap_hook_add(struct if_rmap_ctx *ctx, void (*func)(struct if_rmap_ctx *ctx, struct if_rmap *)); void if_rmap_hook_delete(struct if_rmap_ctx *ctx, void (*func)(struct if_rmap_ctx *ctx, struct if_rmap *)); extern struct if_rmap *if_rmap_lookup(struct if_rmap_ctx *ctx, const char *ifname); extern int config_write_if_rmap(struct vty *, struct if_rmap_ctx *ctx); #ifdef __cplusplus } #endif #endif /* _ZEBRA_IF_RMAP_H */ frr-7.2.1/lib/imsg-buffer.c0000644000000000000000000001314313610377563012336 00000000000000/* $OpenBSD$ */ /* * Copyright (c) 2003, 2004 Henning Brauer * * Permission to use, copy, modify, and distribute this software for any * purpose with or without fee is hereby granted, provided that the above * copyright notice and this permission notice appear in all copies. * * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. */ #include #include "queue.h" #include "imsg.h" static int ibuf_realloc(struct ibuf *, size_t); static void ibuf_enqueue(struct msgbuf *, struct ibuf *); static void ibuf_dequeue(struct msgbuf *, struct ibuf *); struct ibuf *ibuf_open(size_t len) { struct ibuf *buf; if ((buf = calloc(1, sizeof(struct ibuf))) == NULL) return (NULL); if ((buf->buf = malloc(len)) == NULL) { free(buf); return (NULL); } buf->size = buf->max = len; buf->fd = -1; return (buf); } struct ibuf *ibuf_dynamic(size_t len, size_t max) { struct ibuf *buf; if (max < len) return (NULL); if ((buf = ibuf_open(len)) == NULL) return (NULL); if (max > 0) buf->max = max; return (buf); } static int ibuf_realloc(struct ibuf *buf, size_t len) { uint8_t *b; /* on static buffers max is eq size and so the following fails */ if (buf->wpos + len > buf->max) { errno = ERANGE; return (-1); } b = realloc(buf->buf, buf->wpos + len); if (b == NULL) return (-1); buf->buf = b; buf->size = buf->wpos + len; return (0); } int ibuf_add(struct ibuf *buf, const void *data, size_t len) { if (buf->wpos + len > buf->size) if (ibuf_realloc(buf, len) == -1) return (-1); memcpy(buf->buf + buf->wpos, data, len); buf->wpos += len; return (0); } void *ibuf_reserve(struct ibuf *buf, size_t len) { void *b; if (buf->wpos + len > buf->size) if (ibuf_realloc(buf, len) == -1) return (NULL); b = buf->buf + buf->wpos; buf->wpos += len; return (b); } void *ibuf_seek(struct ibuf *buf, size_t pos, size_t len) { /* only allowed to seek in already written parts */ if (pos + len > buf->wpos) return (NULL); return (buf->buf + pos); } size_t ibuf_size(struct ibuf *buf) { return (buf->wpos); } size_t ibuf_left(struct ibuf *buf) { return (buf->max - buf->wpos); } void ibuf_close(struct msgbuf *msgbuf, struct ibuf *buf) { ibuf_enqueue(msgbuf, buf); } int ibuf_write(struct msgbuf *msgbuf) { struct iovec iov[IOV_MAX]; struct ibuf *buf; unsigned int i = 0; ssize_t n; memset(&iov, 0, sizeof(iov)); TAILQ_FOREACH (buf, &msgbuf->bufs, entry) { if (i >= IOV_MAX) break; iov[i].iov_base = buf->buf + buf->rpos; iov[i].iov_len = buf->wpos - buf->rpos; i++; } again: if ((n = writev(msgbuf->fd, iov, i)) == -1) { if (errno == EINTR) goto again; if (errno == ENOBUFS) errno = EAGAIN; return (-1); } if (n == 0) { /* connection closed */ errno = 0; return (0); } msgbuf_drain(msgbuf, n); return (1); } void ibuf_free(struct ibuf *buf) { if (buf == NULL) return; free(buf->buf); free(buf); } void msgbuf_init(struct msgbuf *msgbuf) { msgbuf->queued = 0; msgbuf->fd = -1; TAILQ_INIT(&msgbuf->bufs); } void msgbuf_drain(struct msgbuf *msgbuf, size_t n) { struct ibuf *buf, *next; for (buf = TAILQ_FIRST(&msgbuf->bufs); buf != NULL && n > 0; buf = next) { next = TAILQ_NEXT(buf, entry); if (buf->rpos + n >= buf->wpos) { n -= buf->wpos - buf->rpos; TAILQ_REMOVE(&msgbuf->bufs, buf, entry); ibuf_dequeue(msgbuf, buf); } else { buf->rpos += n; n = 0; } } } void msgbuf_clear(struct msgbuf *msgbuf) { struct ibuf *buf; while ((buf = TAILQ_POP_FIRST(&msgbuf->bufs, entry)) != NULL) ibuf_dequeue(msgbuf, buf); } int msgbuf_write(struct msgbuf *msgbuf) { struct iovec iov[IOV_MAX]; struct ibuf *buf; unsigned int i = 0; ssize_t n; struct msghdr msg; struct cmsghdr *cmsg; union { struct cmsghdr hdr; char buf[CMSG_SPACE(sizeof(int))]; } cmsgbuf; memset(&iov, 0, sizeof(iov)); memset(&msg, 0, sizeof(msg)); memset(&cmsgbuf, 0, sizeof(cmsgbuf)); TAILQ_FOREACH (buf, &msgbuf->bufs, entry) { if (i >= IOV_MAX) break; iov[i].iov_base = buf->buf + buf->rpos; iov[i].iov_len = buf->wpos - buf->rpos; i++; if (buf->fd != -1) break; } msg.msg_iov = iov; msg.msg_iovlen = i; if (buf != NULL && buf->fd != -1) { msg.msg_control = (caddr_t)&cmsgbuf.buf; msg.msg_controllen = sizeof(cmsgbuf.buf); cmsg = CMSG_FIRSTHDR(&msg); cmsg->cmsg_len = CMSG_LEN(sizeof(int)); cmsg->cmsg_level = SOL_SOCKET; cmsg->cmsg_type = SCM_RIGHTS; memcpy(CMSG_DATA(cmsg), &buf->fd, sizeof(int)); } again: if ((n = sendmsg(msgbuf->fd, &msg, 0)) == -1) { if (errno == EINTR) goto again; if (errno == ENOBUFS) errno = EAGAIN; return (-1); } if (n == 0) { /* connection closed */ errno = 0; return (0); } /* * assumption: fd got sent if sendmsg sent anything * this works because fds are passed one at a time */ if (buf != NULL && buf->fd != -1) { close(buf->fd); buf->fd = -1; } msgbuf_drain(msgbuf, n); return (1); } static void ibuf_enqueue(struct msgbuf *msgbuf, struct ibuf *buf) { TAILQ_INSERT_TAIL(&msgbuf->bufs, buf, entry); msgbuf->queued++; } static void ibuf_dequeue(struct msgbuf *msgbuf, struct ibuf *buf) { /* TAILQ_REMOVE done by caller */ if (buf->fd != -1) close(buf->fd); msgbuf->queued--; ibuf_free(buf); } frr-7.2.1/lib/imsg.c0000644000000000000000000001540113610377563011066 00000000000000/* $OpenBSD$ */ /* * Copyright (c) 2003, 2004 Henning Brauer * * Permission to use, copy, modify, and distribute this software for any * purpose with or without fee is hereby granted, provided that the above * copyright notice and this permission notice appear in all copies. * * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. */ #include #include "memory.h" #include "queue.h" #include "imsg.h" static int imsg_fd_overhead = 0; static int imsg_get_fd(struct imsgbuf *); #ifndef __OpenBSD__ /* * The original code calls getdtablecount() which is OpenBSD specific. Use * available_fds() from OpenSMTPD instead. */ static int available_fds(unsigned int n) { unsigned int i; int ret, fds[256]; if (n > (unsigned int)array_size(fds)) return (1); ret = 0; for (i = 0; i < n; i++) { fds[i] = -1; if ((fds[i] = socket(AF_INET, SOCK_DGRAM, 0)) < 0) { if (errno == EAFNOSUPPORT || errno == EPROTONOSUPPORT) fds[i] = socket(AF_INET6, SOCK_DGRAM, 0); if (fds[i] < 0) { ret = 1; break; } } } for (i = 0; i < n && fds[i] >= 0; i++) close(fds[i]); return (ret); } #endif void imsg_init(struct imsgbuf *ibuf, int fd) { msgbuf_init(&ibuf->w); memset(&ibuf->r, 0, sizeof(ibuf->r)); ibuf->fd = fd; ibuf->w.fd = fd; ibuf->pid = getpid(); TAILQ_INIT(&ibuf->fds); } ssize_t imsg_read(struct imsgbuf *ibuf) { struct msghdr msg; struct cmsghdr *cmsg; union { struct cmsghdr hdr; char buf[CMSG_SPACE(sizeof(int) * 1)]; } cmsgbuf; struct iovec iov; ssize_t n; int fd; struct imsg_fd *ifd; memset(&msg, 0, sizeof(msg)); memset(&cmsgbuf, 0, sizeof(cmsgbuf)); iov.iov_base = ibuf->r.buf + ibuf->r.wpos; iov.iov_len = sizeof(ibuf->r.buf) - ibuf->r.wpos; msg.msg_iov = &iov; msg.msg_iovlen = 1; msg.msg_control = &cmsgbuf.buf; msg.msg_controllen = sizeof(cmsgbuf.buf); if ((ifd = calloc(1, sizeof(struct imsg_fd))) == NULL) return (-1); again: #ifdef __OpenBSD__ if (getdtablecount() + imsg_fd_overhead + (int)((CMSG_SPACE(sizeof(int)) - CMSG_SPACE(0)) / sizeof(int)) >= getdtablesize()) { #else if (available_fds(imsg_fd_overhead + (CMSG_SPACE(sizeof(int)) - CMSG_SPACE(0)) / sizeof(int))) { #endif errno = EAGAIN; free(ifd); return (-1); } n = recvmsg(ibuf->fd, &msg, 0); if (n == -1) { if (errno == EINTR) goto again; goto fail; } ibuf->r.wpos += n; for (cmsg = CMSG_FIRSTHDR(&msg); cmsg != NULL; cmsg = CMSG_NXTHDR(&msg, cmsg)) { if (cmsg->cmsg_level == SOL_SOCKET && cmsg->cmsg_type == SCM_RIGHTS) { int i; int j; /* * We only accept one file descriptor. Due to C * padding rules, our control buffer might contain * more than one fd, and we must close them. */ j = ((char *)cmsg + cmsg->cmsg_len - (char *)CMSG_DATA(cmsg)) / sizeof(int); for (i = 0; i < j; i++) { fd = ((int *)CMSG_DATA(cmsg))[i]; if (ifd != NULL) { ifd->fd = fd; TAILQ_INSERT_TAIL(&ibuf->fds, ifd, entry); ifd = NULL; } else close(fd); } } /* we do not handle other ctl data level */ } fail: free(ifd); return (n); } ssize_t imsg_get(struct imsgbuf *ibuf, struct imsg *imsg) { size_t av, left, datalen; av = ibuf->r.wpos; if (IMSG_HEADER_SIZE > av) return (0); memcpy(&imsg->hdr, ibuf->r.buf, sizeof(imsg->hdr)); if (imsg->hdr.len < IMSG_HEADER_SIZE || imsg->hdr.len > MAX_IMSGSIZE) { errno = ERANGE; return (-1); } if (imsg->hdr.len > av) return (0); datalen = imsg->hdr.len - IMSG_HEADER_SIZE; ibuf->r.rptr = ibuf->r.buf + IMSG_HEADER_SIZE; if (datalen == 0) imsg->data = NULL; else if ((imsg->data = malloc(datalen)) == NULL) return (-1); if (imsg->hdr.flags & IMSGF_HASFD) imsg->fd = imsg_get_fd(ibuf); else imsg->fd = -1; if (imsg->data) memcpy(imsg->data, ibuf->r.rptr, datalen); if (imsg->hdr.len < av) { left = av - imsg->hdr.len; memmove(&ibuf->r.buf, ibuf->r.buf + imsg->hdr.len, left); ibuf->r.wpos = left; } else ibuf->r.wpos = 0; return (datalen + IMSG_HEADER_SIZE); } int imsg_compose(struct imsgbuf *ibuf, uint32_t type, uint32_t peerid, pid_t pid, int fd, const void *data, uint16_t datalen) { struct ibuf *wbuf; if ((wbuf = imsg_create(ibuf, type, peerid, pid, datalen)) == NULL) return (-1); if (imsg_add(wbuf, data, datalen) == -1) return (-1); wbuf->fd = fd; imsg_close(ibuf, wbuf); return (1); } int imsg_composev(struct imsgbuf *ibuf, uint32_t type, uint32_t peerid, pid_t pid, int fd, const struct iovec *iov, int iovcnt) { struct ibuf *wbuf; int i, datalen = 0; for (i = 0; i < iovcnt; i++) datalen += iov[i].iov_len; if ((wbuf = imsg_create(ibuf, type, peerid, pid, datalen)) == NULL) return (-1); for (i = 0; i < iovcnt; i++) if (imsg_add(wbuf, iov[i].iov_base, iov[i].iov_len) == -1) return (-1); wbuf->fd = fd; imsg_close(ibuf, wbuf); return (1); } /* ARGSUSED */ struct ibuf *imsg_create(struct imsgbuf *ibuf, uint32_t type, uint32_t peerid, pid_t pid, uint16_t datalen) { struct ibuf *wbuf; struct imsg_hdr hdr; memset(&hdr, 0x00, IMSG_HEADER_SIZE); datalen += IMSG_HEADER_SIZE; if (datalen > MAX_IMSGSIZE) { errno = ERANGE; return (NULL); } hdr.type = type; hdr.flags = 0; hdr.peerid = peerid; if ((hdr.pid = pid) == 0) hdr.pid = ibuf->pid; if ((wbuf = ibuf_dynamic(datalen, MAX_IMSGSIZE)) == NULL) { return (NULL); } if (imsg_add(wbuf, &hdr, sizeof(hdr)) == -1) return (NULL); return (wbuf); } int imsg_add(struct ibuf *msg, const void *data, uint16_t datalen) { if (datalen) if (ibuf_add(msg, data, datalen) == -1) { ibuf_free(msg); return (-1); } return (datalen); } void imsg_close(struct imsgbuf *ibuf, struct ibuf *msg) { struct imsg_hdr *hdr; hdr = (struct imsg_hdr *)msg->buf; hdr->flags &= ~IMSGF_HASFD; if (msg->fd != -1) hdr->flags |= IMSGF_HASFD; hdr->len = (uint16_t)msg->wpos; ibuf_close(&ibuf->w, msg); } void imsg_free(struct imsg *imsg) { free(imsg->data); } int imsg_get_fd(struct imsgbuf *ibuf) { int fd; struct imsg_fd *ifd; if ((ifd = TAILQ_POP_FIRST(&ibuf->fds, entry)) == NULL) return (-1); fd = ifd->fd; free(ifd); return (fd); } int imsg_flush(struct imsgbuf *ibuf) { while (ibuf->w.queued) if (msgbuf_write(&ibuf->w) <= 0) return (-1); return (0); } void imsg_clear(struct imsgbuf *ibuf) { int fd; msgbuf_clear(&ibuf->w); while ((fd = imsg_get_fd(ibuf)) != -1) close(fd); } frr-7.2.1/lib/imsg.h0000644000000000000000000000565613610377563011106 00000000000000/* $OpenBSD$ */ /* * Copyright (c) 2006, 2007 Pierre-Yves Ritschard * Copyright (c) 2006, 2007, 2008 Reyk Floeter * Copyright (c) 2003, 2004 Henning Brauer * * Permission to use, copy, modify, and distribute this software for any * purpose with or without fee is hereby granted, provided that the above * copyright notice and this permission notice appear in all copies. * * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. */ #ifndef _IMSG_H_ #define _IMSG_H_ #ifdef __cplusplus extern "C" { #endif #define IBUF_READ_SIZE 65535 #define IMSG_HEADER_SIZE sizeof(struct imsg_hdr) #define MAX_IMSGSIZE 16384 struct ibuf { TAILQ_ENTRY(ibuf) entry; uint8_t *buf; size_t size; size_t max; size_t wpos; size_t rpos; int fd; }; struct msgbuf { TAILQ_HEAD(, ibuf) bufs; uint32_t queued; int fd; }; struct ibuf_read { uint8_t buf[IBUF_READ_SIZE]; uint8_t *rptr; size_t wpos; }; struct imsg_fd { TAILQ_ENTRY(imsg_fd) entry; int fd; }; struct imsgbuf { TAILQ_HEAD(, imsg_fd) fds; struct ibuf_read r; struct msgbuf w; int fd; pid_t pid; }; #define IMSGF_HASFD 1 struct imsg_hdr { uint32_t type; uint16_t len; uint16_t flags; uint32_t peerid; uint32_t pid; }; struct imsg { struct imsg_hdr hdr; int fd; void *data; }; /* buffer.c */ struct ibuf *ibuf_open(size_t); struct ibuf *ibuf_dynamic(size_t, size_t); int ibuf_add(struct ibuf *, const void *, size_t); void *ibuf_reserve(struct ibuf *, size_t); void *ibuf_seek(struct ibuf *, size_t, size_t); size_t ibuf_size(struct ibuf *); size_t ibuf_left(struct ibuf *); void ibuf_close(struct msgbuf *, struct ibuf *); int ibuf_write(struct msgbuf *); void ibuf_free(struct ibuf *); void msgbuf_init(struct msgbuf *); void msgbuf_clear(struct msgbuf *); int msgbuf_write(struct msgbuf *); void msgbuf_drain(struct msgbuf *, size_t); /* imsg.c */ void imsg_init(struct imsgbuf *, int); ssize_t imsg_read(struct imsgbuf *); ssize_t imsg_get(struct imsgbuf *, struct imsg *); int imsg_compose(struct imsgbuf *, uint32_t, uint32_t, pid_t, int, const void *, uint16_t); int imsg_composev(struct imsgbuf *, uint32_t, uint32_t, pid_t, int, const struct iovec *, int); struct ibuf *imsg_create(struct imsgbuf *, uint32_t, uint32_t, pid_t, uint16_t); int imsg_add(struct ibuf *, const void *, uint16_t); void imsg_close(struct imsgbuf *, struct ibuf *); void imsg_free(struct imsg *); int imsg_flush(struct imsgbuf *); void imsg_clear(struct imsgbuf *); #ifdef __cplusplus } #endif #endif frr-7.2.1/lib/ipaddr.h0000644000000000000000000000627113610377563011404 00000000000000/* * IP address structure (for generic IPv4 or IPv6 address) * Copyright (C) 2016, 2017 Cumulus Networks, Inc. * * This file is part of FRR. * * FRR is free software; you can redistribute it and/or modify it * under the terms of the GNU General Public License as published by the * Free Software Foundation; either version 2, or (at your option) any * later version. * * FRR 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 FRR; see the file COPYING. If not, write to the Free * Software Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA * 02111-1307, USA. */ #ifndef __IPADDR_H__ #define __IPADDR_H__ #include #ifdef __cplusplus extern "C" { #endif /* * Generic IP address - union of IPv4 and IPv6 address. */ enum ipaddr_type_t { IPADDR_NONE = 0, IPADDR_V4 = 1, /* IPv4 */ IPADDR_V6 = 2, /* IPv6 */ }; struct ipaddr { enum ipaddr_type_t ipa_type; union { uint8_t addr; struct in_addr _v4_addr; struct in6_addr _v6_addr; } ip; #define ipaddr_v4 ip._v4_addr #define ipaddr_v6 ip._v6_addr }; #define IS_IPADDR_NONE(p) ((p)->ipa_type == IPADDR_NONE) #define IS_IPADDR_V4(p) ((p)->ipa_type == IPADDR_V4) #define IS_IPADDR_V6(p) ((p)->ipa_type == IPADDR_V6) #define SET_IPADDR_V4(p) (p)->ipa_type = IPADDR_V4 #define SET_IPADDR_V6(p) (p)->ipa_type = IPADDR_V6 #define IPADDRSZ(p) \ (IS_IPADDR_V4((p)) ? sizeof(struct in_addr) : sizeof(struct in6_addr)) static inline int str2ipaddr(const char *str, struct ipaddr *ip) { int ret; memset(ip, 0, sizeof(struct ipaddr)); ret = inet_pton(AF_INET, str, &ip->ipaddr_v4); if (ret > 0) /* Valid IPv4 address. */ { ip->ipa_type = IPADDR_V4; return 0; } ret = inet_pton(AF_INET6, str, &ip->ipaddr_v6); if (ret > 0) /* Valid IPv6 address. */ { ip->ipa_type = IPADDR_V6; return 0; } return -1; } static inline char *ipaddr2str(const struct ipaddr *ip, char *buf, int size) { buf[0] = '\0'; if (ip) { if (IS_IPADDR_V4(ip)) inet_ntop(AF_INET, &ip->ip.addr, buf, size); else if (IS_IPADDR_V6(ip)) inet_ntop(AF_INET6, &ip->ip.addr, buf, size); } return buf; } /* * Convert IPv4 address to IPv4-mapped IPv6 address which is of the * form ::FFFF: (RFC 4291). This IPv6 address can then * be used to represent the IPv4 address, wherever only an IPv6 address * is required. */ static inline void ipv4_to_ipv4_mapped_ipv6(struct in6_addr *in6, struct in_addr in) { uint32_t addr_type = htonl(0xFFFF); memset(in6, 0, sizeof(struct in6_addr)); memcpy((char *)in6 + 8, &addr_type, sizeof(addr_type)); memcpy((char *)in6 + 12, &in, sizeof(struct in_addr)); } /* * convert an ipv4 mapped ipv6 address back to ipv4 address */ static inline void ipv4_mapped_ipv6_to_ipv4(struct in6_addr *in6, struct in_addr *in) { memset(in, 0, sizeof(struct in_addr)); memcpy(in, (char *)in6 + 12, sizeof(struct in_addr)); } #ifdef __cplusplus } #endif #endif /* __IPADDR_H__ */ frr-7.2.1/lib/jhash.c0000644000000000000000000001216313610377563011226 00000000000000/* jhash.h: Jenkins hash support. * * Copyright (C) 1996 Bob Jenkins (bob_jenkins@burtleburtle.net) * * http://burtleburtle.net/bob/hash/ * * These are the credits from Bob's sources: * * lookup2.c, by Bob Jenkins, December 1996, Public Domain. * hash(), hash2(), hash3, and mix() are externally useful functions. * Routines to test the hash are included if SELF_TEST is defined. * You can use this free for any purpose. It has no warranty. * * Copyright (C) 2003 David S. Miller (davem@redhat.com) * * I've modified Bob's hash to be useful in the Linux kernel, and * any bugs present are surely my fault. -DaveM */ #include "zebra.h" #include "jhash.h" /* The golden ration: an arbitrary value */ #define JHASH_GOLDEN_RATIO 0x9e3779b9 /* NOTE: Arguments are modified. */ #define __jhash_mix(a, b, c) \ { \ a -= b; \ a -= c; \ a ^= (c >> 13); \ b -= c; \ b -= a; \ b ^= (a << 8); \ c -= a; \ c -= b; \ c ^= (b >> 13); \ a -= b; \ a -= c; \ a ^= (c >> 12); \ b -= c; \ b -= a; \ b ^= (a << 16); \ c -= a; \ c -= b; \ c ^= (b >> 5); \ a -= b; \ a -= c; \ a ^= (c >> 3); \ b -= c; \ b -= a; \ b ^= (a << 10); \ c -= a; \ c -= b; \ c ^= (b >> 15); \ } /* The most generic version, hashes an arbitrary sequence * of bytes. No alignment or length assumptions are made about * the input key. */ uint32_t jhash(const void *key, uint32_t length, uint32_t initval) { uint32_t a, b, c, len; const uint8_t *k = key; len = length; a = b = JHASH_GOLDEN_RATIO; c = initval; while (len >= 12) { a += (k[0] + ((uint32_t)k[1] << 8) + ((uint32_t)k[2] << 16) + ((uint32_t)k[3] << 24)); b += (k[4] + ((uint32_t)k[5] << 8) + ((uint32_t)k[6] << 16) + ((uint32_t)k[7] << 24)); c += (k[8] + ((uint32_t)k[9] << 8) + ((uint32_t)k[10] << 16) + ((uint32_t)k[11] << 24)); __jhash_mix(a, b, c); k += 12; len -= 12; } c += length; switch (len) { case 11: c += ((uint32_t)k[10] << 24); /* fallthru */ case 10: c += ((uint32_t)k[9] << 16); /* fallthru */ case 9: c += ((uint32_t)k[8] << 8); /* fallthru */ case 8: b += ((uint32_t)k[7] << 24); /* fallthru */ case 7: b += ((uint32_t)k[6] << 16); /* fallthru */ case 6: b += ((uint32_t)k[5] << 8); /* fallthru */ case 5: b += k[4]; /* fallthru */ case 4: a += ((uint32_t)k[3] << 24); /* fallthru */ case 3: a += ((uint32_t)k[2] << 16); /* fallthru */ case 2: a += ((uint32_t)k[1] << 8); /* fallthru */ case 1: a += k[0]; } __jhash_mix(a, b, c); return c; } /* A special optimized version that handles 1 or more of uint32_ts. * The length parameter here is the number of uint32_ts in the key. */ uint32_t jhash2(const uint32_t *k, uint32_t length, uint32_t initval) { uint32_t a, b, c, len; a = b = JHASH_GOLDEN_RATIO; c = initval; len = length; while (len >= 3) { a += k[0]; b += k[1]; c += k[2]; __jhash_mix(a, b, c); k += 3; len -= 3; } c += length * 4; switch (len) { case 2: b += k[1]; /* fallthru */ case 1: a += k[0]; } __jhash_mix(a, b, c); return c; } /* A special ultra-optimized versions that knows they are hashing exactly * 3, 2 or 1 word(s). * * NOTE: In partilar the "c += length; __jhash_mix(a,b,c);" normally * done at the end is not done here. */ uint32_t jhash_3words(uint32_t a, uint32_t b, uint32_t c, uint32_t initval) { a += JHASH_GOLDEN_RATIO; b += JHASH_GOLDEN_RATIO; c += initval; __jhash_mix(a, b, c); return c; } uint32_t jhash_2words(uint32_t a, uint32_t b, uint32_t initval) { return jhash_3words(a, b, 0, initval); } uint32_t jhash_1word(uint32_t a, uint32_t initval) { return jhash_3words(a, 0, 0, initval); } frr-7.2.1/lib/jhash.h0000644000000000000000000000317513610377563011236 00000000000000/* jhash.h: Jenkins hash support. * * Copyright (C) 1996 Bob Jenkins (bob_jenkins@burtleburtle.net) * * http://burtleburtle.net/bob/hash/ * * These are the credits from Bob's sources: * * lookup2.c, by Bob Jenkins, December 1996, Public Domain. * hash(), hash2(), hash3, and mix() are externally useful functions. * Routines to test the hash are included if SELF_TEST is defined. * You can use this free for any purpose. It has no warranty. * * Copyright (C) 2003 David S. Miller (davem@redhat.com) * * I've modified Bob's hash to be useful in the Linux kernel, and * any bugs present are surely my fault. -DaveM */ #ifndef _QUAGGA_JHASH_H #define _QUAGGA_JHASH_H #ifdef __cplusplus extern "C" { #endif /* The most generic version, hashes an arbitrary sequence * of bytes. No alignment or length assumptions are made about * the input key. */ extern uint32_t jhash(const void *key, uint32_t length, uint32_t initval); /* A special optimized version that handles 1 or more of uint32_ts. * The length parameter here is the number of uint32_ts in the key. */ extern uint32_t jhash2(const uint32_t *k, uint32_t length, uint32_t initval); /* A special ultra-optimized versions that knows they are hashing exactly * 3, 2 or 1 word(s). * * NOTE: In partilar the "c += length; __jhash_mix(a,b,c);" normally * done at the end is not done here. */ extern uint32_t jhash_3words(uint32_t a, uint32_t b, uint32_t c, uint32_t initval); extern uint32_t jhash_2words(uint32_t a, uint32_t b, uint32_t initval); extern uint32_t jhash_1word(uint32_t a, uint32_t initval); #ifdef __cplusplus } #endif #endif /* _QUAGGA_JHASH_H */ frr-7.2.1/lib/json.c0000644000000000000000000000462013610377563011101 00000000000000/* json-c wrapper * Copyright (C) 2015 Cumulus Networks, Inc. * * This file is part of GNU Zebra. * * GNU Zebra is free software; you can redistribute it and/or modify it * under the terms of the GNU General Public License as published by the * Free Software Foundation; either version 2, or (at your option) any * later version. * * GNU Zebra 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; see the file COPYING; if not, write to the Free Software * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA */ #include #include "command.h" #include "lib/json.h" /* * This function assumes that the json keyword * is the *last* keyword on the line no matter * what. */ bool use_json(const int argc, struct cmd_token *argv[]) { if (argc == 0) return false; if (argv[argc - 1]->arg && strmatch(argv[argc - 1]->text, "json")) return true; return false; } void json_object_string_add(struct json_object *obj, const char *key, const char *s) { json_object_object_add(obj, key, json_object_new_string(s)); } void json_object_int_add(struct json_object *obj, const char *key, int64_t i) { #if defined(HAVE_JSON_C_JSON_H) json_object_object_add(obj, key, json_object_new_int64(i)); #else json_object_object_add(obj, key, json_object_new_int((int)i)); #endif } void json_object_boolean_false_add(struct json_object *obj, const char *key) { json_object_object_add(obj, key, json_object_new_boolean(0)); } void json_object_boolean_true_add(struct json_object *obj, const char *key) { json_object_object_add(obj, key, json_object_new_boolean(1)); } void json_object_boolean_add(struct json_object *obj, const char *key, bool val) { json_object_object_add(obj, key, json_object_new_boolean(val)); } struct json_object *json_object_lock(struct json_object *obj) { return json_object_get(obj); } void json_object_free(struct json_object *obj) { json_object_put(obj); } #if !defined(HAVE_JSON_C_JSON_H) int json_object_object_get_ex(struct json_object *obj, const char *key, struct json_object **value) { *value = json_object_object_get(obj, key); if (*value) return 1; return 0; } #endif frr-7.2.1/lib/json.h0000644000000000000000000000605313610377563011110 00000000000000/* json-c wrapper * Copyright (C) 2015 Cumulus Networks, Inc. * * This file is part of GNU Zebra. * * GNU Zebra is free software; you can redistribute it and/or modify it * under the terms of the GNU General Public License as published by the * Free Software Foundation; either version 2, or (at your option) any * later version. * * GNU Zebra 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; see the file COPYING; if not, write to the Free Software * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA */ #ifndef _QUAGGA_JSON_H #define _QUAGGA_JSON_H #ifdef __cplusplus extern "C" { #endif #if defined(HAVE_JSON_C_JSON_H) #include /* * FRR style JSON iteration. * Usage: JSON_FOREACH(...) { ... } */ #define JSON_FOREACH(jo, joi, join) \ /* struct json_object *jo; */ \ /* struct json_object_iterator joi; */ \ /* struct json_object_iterator join; */ \ for ((joi) = json_object_iter_begin((jo)), \ (join) = json_object_iter_end((jo)); \ json_object_iter_equal(&(joi), &(join)) == 0; \ json_object_iter_next(&(joi))) #else #include /* * json_object_to_json_string_ext is only available for json-c * so let's just turn it back to the original usage. */ #define json_object_to_json_string_ext(A, B) json_object_to_json_string (A) extern int json_object_object_get_ex(struct json_object *obj, const char *key, struct json_object **value); #endif #include "command.h" extern bool use_json(const int argc, struct cmd_token *argv[]); extern void json_object_string_add(struct json_object *obj, const char *key, const char *s); extern void json_object_int_add(struct json_object *obj, const char *key, int64_t i); void json_object_boolean_add(struct json_object *obj, const char *key, bool val); extern void json_object_boolean_false_add(struct json_object *obj, const char *key); extern void json_object_boolean_true_add(struct json_object *obj, const char *key); extern struct json_object *json_object_lock(struct json_object *obj); extern void json_object_free(struct json_object *obj); #define JSON_STR "JavaScript Object Notation\n" /* NOTE: json-c lib has following commit 316da85 which * handles escape of forward slash. * This allows prefix "20.0.14.0\/24":{ * to "20.0.14.0/24":{ some platforms do not have * latest copy of json-c where defining below macro. */ #ifndef JSON_C_TO_STRING_NOSLASHESCAPE /** * Don't escape forward slashes. */ #define JSON_C_TO_STRING_NOSLASHESCAPE (1<<4) #endif #ifdef __cplusplus } #endif #endif /* _QUAGGA_JSON_H */ frr-7.2.1/lib/keychain.c0000644000000000000000000007135113610377563011730 00000000000000/* key-chain for authentication. * Copyright (C) 2000 Kunihiro Ishiguro * * This file is part of GNU Zebra. * * GNU Zebra is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License as published * by the Free Software Foundation; either version 2, or (at your * option) any later version. * * GNU Zebra 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; see the file COPYING; if not, write to the Free Software * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA */ #include #include "command.h" #include "memory.h" #include "linklist.h" #include "keychain.h" DEFINE_MTYPE_STATIC(LIB, KEY, "Key") DEFINE_MTYPE_STATIC(LIB, KEYCHAIN, "Key chain") DEFINE_QOBJ_TYPE(keychain) DEFINE_QOBJ_TYPE(key) /* Master list of key chain. */ static struct list *keychain_list; static struct keychain *keychain_new(void) { struct keychain *keychain; keychain = XCALLOC(MTYPE_KEYCHAIN, sizeof(struct keychain)); QOBJ_REG(keychain, keychain); return keychain; } static void keychain_free(struct keychain *keychain) { QOBJ_UNREG(keychain); XFREE(MTYPE_KEYCHAIN, keychain); } static struct key *key_new(void) { struct key *key = XCALLOC(MTYPE_KEY, sizeof(struct key)); QOBJ_REG(key, key); return key; } static void key_free(struct key *key) { QOBJ_UNREG(key); XFREE(MTYPE_KEY, key); } struct keychain *keychain_lookup(const char *name) { struct listnode *node; struct keychain *keychain; if (name == NULL) return NULL; for (ALL_LIST_ELEMENTS_RO(keychain_list, node, keychain)) { if (strcmp(keychain->name, name) == 0) return keychain; } return NULL; } static int key_cmp_func(void *arg1, void *arg2) { const struct key *k1 = arg1; const struct key *k2 = arg2; if (k1->index > k2->index) return 1; if (k1->index < k2->index) return -1; return 0; } static void key_delete_func(struct key *key) { if (key->string) free(key->string); key_free(key); } static struct keychain *keychain_get(const char *name) { struct keychain *keychain; keychain = keychain_lookup(name); if (keychain) return keychain; keychain = keychain_new(); keychain->name = XSTRDUP(MTYPE_KEYCHAIN, name); keychain->key = list_new(); keychain->key->cmp = (int (*)(void *, void *))key_cmp_func; keychain->key->del = (void (*)(void *))key_delete_func; listnode_add(keychain_list, keychain); return keychain; } static void keychain_delete(struct keychain *keychain) { XFREE(MTYPE_KEYCHAIN, keychain->name); list_delete(&keychain->key); listnode_delete(keychain_list, keychain); keychain_free(keychain); } static struct key *key_lookup(const struct keychain *keychain, uint32_t index) { struct listnode *node; struct key *key; for (ALL_LIST_ELEMENTS_RO(keychain->key, node, key)) { if (key->index == index) return key; } return NULL; } struct key *key_lookup_for_accept(const struct keychain *keychain, uint32_t index) { struct listnode *node; struct key *key; time_t now; now = time(NULL); for (ALL_LIST_ELEMENTS_RO(keychain->key, node, key)) { if (key->index >= index) { if (key->accept.start == 0) return key; if (key->accept.start <= now) if (key->accept.end >= now || key->accept.end == -1) return key; } } return NULL; } struct key *key_match_for_accept(const struct keychain *keychain, const char *auth_str) { struct listnode *node; struct key *key; time_t now; now = time(NULL); for (ALL_LIST_ELEMENTS_RO(keychain->key, node, key)) { if (key->accept.start == 0 || (key->accept.start <= now && (key->accept.end >= now || key->accept.end == -1))) if (key->string && (strncmp(key->string, auth_str, 16) == 0)) return key; } return NULL; } struct key *key_lookup_for_send(const struct keychain *keychain) { struct listnode *node; struct key *key; time_t now; now = time(NULL); for (ALL_LIST_ELEMENTS_RO(keychain->key, node, key)) { if (key->send.start == 0) return key; if (key->send.start <= now) if (key->send.end >= now || key->send.end == -1) return key; } return NULL; } static struct key *key_get(const struct keychain *keychain, uint32_t index) { struct key *key; key = key_lookup(keychain, index); if (key) return key; key = key_new(); key->index = index; listnode_add_sort(keychain->key, key); return key; } static void key_delete(struct keychain *keychain, struct key *key) { listnode_delete(keychain->key, key); XFREE(MTYPE_KEY, key->string); key_free(key); } DEFUN_NOSH (key_chain, key_chain_cmd, "key chain WORD", "Authentication key management\n" "Key-chain management\n" "Key-chain name\n") { int idx_word = 2; struct keychain *keychain; keychain = keychain_get(argv[idx_word]->arg); VTY_PUSH_CONTEXT(KEYCHAIN_NODE, keychain); return CMD_SUCCESS; } DEFUN (no_key_chain, no_key_chain_cmd, "no key chain WORD", NO_STR "Authentication key management\n" "Key-chain management\n" "Key-chain name\n") { int idx_word = 3; struct keychain *keychain; keychain = keychain_lookup(argv[idx_word]->arg); if (!keychain) { vty_out(vty, "Can't find keychain %s\n", argv[idx_word]->arg); return CMD_WARNING_CONFIG_FAILED; } keychain_delete(keychain); return CMD_SUCCESS; } DEFUN_NOSH (key, key_cmd, "key (0-2147483647)", "Configure a key\n" "Key identifier number\n") { int idx_number = 1; VTY_DECLVAR_CONTEXT(keychain, keychain); struct key *key; uint32_t index; index = strtoul(argv[idx_number]->arg, NULL, 10); key = key_get(keychain, index); VTY_PUSH_CONTEXT_SUB(KEYCHAIN_KEY_NODE, key); return CMD_SUCCESS; } DEFUN (no_key, no_key_cmd, "no key (0-2147483647)", NO_STR "Delete a key\n" "Key identifier number\n") { int idx_number = 2; VTY_DECLVAR_CONTEXT(keychain, keychain); struct key *key; uint32_t index; index = strtoul(argv[idx_number]->arg, NULL, 10); key = key_lookup(keychain, index); if (!key) { vty_out(vty, "Can't find key %d\n", index); return CMD_WARNING_CONFIG_FAILED; } key_delete(keychain, key); vty->node = KEYCHAIN_NODE; return CMD_SUCCESS; } DEFUN (key_string, key_string_cmd, "key-string LINE", "Set key string\n" "The key\n") { int idx_line = 1; VTY_DECLVAR_CONTEXT_SUB(key, key); if (key->string) XFREE(MTYPE_KEY, key->string); key->string = XSTRDUP(MTYPE_KEY, argv[idx_line]->arg); return CMD_SUCCESS; } DEFUN (no_key_string, no_key_string_cmd, "no key-string [LINE]", NO_STR "Unset key string\n" "The key\n") { VTY_DECLVAR_CONTEXT_SUB(key, key); if (key->string) { XFREE(MTYPE_KEY, key->string); key->string = NULL; } return CMD_SUCCESS; } /* Convert HH:MM:SS MON DAY YEAR to time_t value. -1 is returned when given string is malformed. */ static time_t key_str2time(const char *time_str, const char *day_str, const char *month_str, const char *year_str) { int i = 0; char *colon; struct tm tm; time_t time; unsigned int sec, min, hour; unsigned int day, month, year; const char *month_name[] = { "January", "February", "March", "April", "May", "June", "July", "August", "September", "October", "November", "December", NULL}; #define _GET_LONG_RANGE(V, STR, MMCOND) \ { \ unsigned long tmpl; \ char *endptr = NULL; \ tmpl = strtoul((STR), &endptr, 10); \ if (*endptr != '\0' || tmpl == ULONG_MAX) \ return -1; \ if (MMCOND) \ return -1; \ (V) = tmpl; \ } #define GET_LONG_RANGE(V, STR, MIN, MAX) \ _GET_LONG_RANGE(V, STR, tmpl<(MIN) || tmpl>(MAX)) #define GET_LONG_RANGE0(V, STR, MAX) _GET_LONG_RANGE(V, STR, tmpl > (MAX)) /* Check hour field of time_str. */ colon = strchr(time_str, ':'); if (colon == NULL) return -1; *colon = '\0'; /* Hour must be between 0 and 23. */ GET_LONG_RANGE0(hour, time_str, 23); /* Check min field of time_str. */ time_str = colon + 1; colon = strchr(time_str, ':'); if (*time_str == '\0' || colon == NULL) return -1; *colon = '\0'; /* Min must be between 0 and 59. */ GET_LONG_RANGE0(min, time_str, 59); /* Check sec field of time_str. */ time_str = colon + 1; if (*time_str == '\0') return -1; /* Sec must be between 0 and 59. */ GET_LONG_RANGE0(sec, time_str, 59); /* Check day_str. Day must be <1-31>. */ GET_LONG_RANGE(day, day_str, 1, 31); /* Check month_str. Month must match month_name. */ month = 0; if (strlen(month_str) >= 3) for (i = 0; month_name[i]; i++) if (strncmp(month_str, month_name[i], strlen(month_str)) == 0) { month = i; break; } if (!month_name[i]) return -1; /* Check year_str. Year must be <1993-2035>. */ GET_LONG_RANGE(year, year_str, 1993, 2035); memset(&tm, 0, sizeof(struct tm)); tm.tm_sec = sec; tm.tm_min = min; tm.tm_hour = hour; tm.tm_mon = month; tm.tm_mday = day; tm.tm_year = year - 1900; time = mktime(&tm); return time; #undef GET_LONG_RANGE } static int key_lifetime_set(struct vty *vty, struct key_range *krange, const char *stime_str, const char *sday_str, const char *smonth_str, const char *syear_str, const char *etime_str, const char *eday_str, const char *emonth_str, const char *eyear_str) { time_t time_start; time_t time_end; time_start = key_str2time(stime_str, sday_str, smonth_str, syear_str); if (time_start < 0) { vty_out(vty, "Malformed time value\n"); return CMD_WARNING_CONFIG_FAILED; } time_end = key_str2time(etime_str, eday_str, emonth_str, eyear_str); if (time_end < 0) { vty_out(vty, "Malformed time value\n"); return CMD_WARNING_CONFIG_FAILED; } if (time_end <= time_start) { vty_out(vty, "Expire time is not later than start time\n"); return CMD_WARNING_CONFIG_FAILED; } krange->start = time_start; krange->end = time_end; return CMD_SUCCESS; } static int key_lifetime_duration_set(struct vty *vty, struct key_range *krange, const char *stime_str, const char *sday_str, const char *smonth_str, const char *syear_str, const char *duration_str) { time_t time_start; uint32_t duration; time_start = key_str2time(stime_str, sday_str, smonth_str, syear_str); if (time_start < 0) { vty_out(vty, "Malformed time value\n"); return CMD_WARNING_CONFIG_FAILED; } krange->start = time_start; duration = strtoul(duration_str, NULL, 10); krange->duration = 1; krange->end = time_start + duration; return CMD_SUCCESS; } static int key_lifetime_infinite_set(struct vty *vty, struct key_range *krange, const char *stime_str, const char *sday_str, const char *smonth_str, const char *syear_str) { time_t time_start; time_start = key_str2time(stime_str, sday_str, smonth_str, syear_str); if (time_start < 0) { vty_out(vty, "Malformed time value\n"); return CMD_WARNING_CONFIG_FAILED; } krange->start = time_start; krange->end = -1; return CMD_SUCCESS; } DEFUN (accept_lifetime_day_month_day_month, accept_lifetime_day_month_day_month_cmd, "accept-lifetime HH:MM:SS (1-31) MONTH (1993-2035) HH:MM:SS (1-31) MONTH (1993-2035)", "Set accept lifetime of the key\n" "Time to start\n" "Day of th month to start\n" "Month of the year to start\n" "Year to start\n" "Time to expire\n" "Day of th month to expire\n" "Month of the year to expire\n" "Year to expire\n") { int idx_hhmmss = 1; int idx_number = 2; int idx_month = 3; int idx_number_2 = 4; int idx_hhmmss_2 = 5; int idx_number_3 = 6; int idx_month_2 = 7; int idx_number_4 = 8; VTY_DECLVAR_CONTEXT_SUB(key, key); return key_lifetime_set( vty, &key->accept, argv[idx_hhmmss]->arg, argv[idx_number]->arg, argv[idx_month]->arg, argv[idx_number_2]->arg, argv[idx_hhmmss_2]->arg, argv[idx_number_3]->arg, argv[idx_month_2]->arg, argv[idx_number_4]->arg); } DEFUN (accept_lifetime_day_month_month_day, accept_lifetime_day_month_month_day_cmd, "accept-lifetime HH:MM:SS (1-31) MONTH (1993-2035) HH:MM:SS MONTH (1-31) (1993-2035)", "Set accept lifetime of the key\n" "Time to start\n" "Day of th month to start\n" "Month of the year to start\n" "Year to start\n" "Time to expire\n" "Month of the year to expire\n" "Day of th month to expire\n" "Year to expire\n") { int idx_hhmmss = 1; int idx_number = 2; int idx_month = 3; int idx_number_2 = 4; int idx_hhmmss_2 = 5; int idx_month_2 = 6; int idx_number_3 = 7; int idx_number_4 = 8; VTY_DECLVAR_CONTEXT_SUB(key, key); return key_lifetime_set( vty, &key->accept, argv[idx_hhmmss]->arg, argv[idx_number]->arg, argv[idx_month]->arg, argv[idx_number_2]->arg, argv[idx_hhmmss_2]->arg, argv[idx_number_3]->arg, argv[idx_month_2]->arg, argv[idx_number_4]->arg); } DEFUN (accept_lifetime_month_day_day_month, accept_lifetime_month_day_day_month_cmd, "accept-lifetime HH:MM:SS MONTH (1-31) (1993-2035) HH:MM:SS (1-31) MONTH (1993-2035)", "Set accept lifetime of the key\n" "Time to start\n" "Month of the year to start\n" "Day of th month to start\n" "Year to start\n" "Time to expire\n" "Day of th month to expire\n" "Month of the year to expire\n" "Year to expire\n") { int idx_hhmmss = 1; int idx_month = 2; int idx_number = 3; int idx_number_2 = 4; int idx_hhmmss_2 = 5; int idx_number_3 = 6; int idx_month_2 = 7; int idx_number_4 = 8; VTY_DECLVAR_CONTEXT_SUB(key, key); return key_lifetime_set( vty, &key->accept, argv[idx_hhmmss]->arg, argv[idx_number]->arg, argv[idx_month]->arg, argv[idx_number_2]->arg, argv[idx_hhmmss_2]->arg, argv[idx_number_3]->arg, argv[idx_month_2]->arg, argv[idx_number_4]->arg); } DEFUN (accept_lifetime_month_day_month_day, accept_lifetime_month_day_month_day_cmd, "accept-lifetime HH:MM:SS MONTH (1-31) (1993-2035) HH:MM:SS MONTH (1-31) (1993-2035)", "Set accept lifetime of the key\n" "Time to start\n" "Month of the year to start\n" "Day of th month to start\n" "Year to start\n" "Time to expire\n" "Month of the year to expire\n" "Day of th month to expire\n" "Year to expire\n") { int idx_hhmmss = 1; int idx_month = 2; int idx_number = 3; int idx_number_2 = 4; int idx_hhmmss_2 = 5; int idx_month_2 = 6; int idx_number_3 = 7; int idx_number_4 = 8; VTY_DECLVAR_CONTEXT_SUB(key, key); return key_lifetime_set( vty, &key->accept, argv[idx_hhmmss]->arg, argv[idx_number]->arg, argv[idx_month]->arg, argv[idx_number_2]->arg, argv[idx_hhmmss_2]->arg, argv[idx_number_3]->arg, argv[idx_month_2]->arg, argv[idx_number_4]->arg); } DEFUN (accept_lifetime_infinite_day_month, accept_lifetime_infinite_day_month_cmd, "accept-lifetime HH:MM:SS (1-31) MONTH (1993-2035) infinite", "Set accept lifetime of the key\n" "Time to start\n" "Day of th month to start\n" "Month of the year to start\n" "Year to start\n" "Never expires\n") { int idx_hhmmss = 1; int idx_number = 2; int idx_month = 3; int idx_number_2 = 4; VTY_DECLVAR_CONTEXT_SUB(key, key); return key_lifetime_infinite_set( vty, &key->accept, argv[idx_hhmmss]->arg, argv[idx_number]->arg, argv[idx_month]->arg, argv[idx_number_2]->arg); } DEFUN (accept_lifetime_infinite_month_day, accept_lifetime_infinite_month_day_cmd, "accept-lifetime HH:MM:SS MONTH (1-31) (1993-2035) infinite", "Set accept lifetime of the key\n" "Time to start\n" "Month of the year to start\n" "Day of th month to start\n" "Year to start\n" "Never expires\n") { int idx_hhmmss = 1; int idx_month = 2; int idx_number = 3; int idx_number_2 = 4; VTY_DECLVAR_CONTEXT_SUB(key, key); return key_lifetime_infinite_set( vty, &key->accept, argv[idx_hhmmss]->arg, argv[idx_number]->arg, argv[idx_month]->arg, argv[idx_number_2]->arg); } DEFUN (accept_lifetime_duration_day_month, accept_lifetime_duration_day_month_cmd, "accept-lifetime HH:MM:SS (1-31) MONTH (1993-2035) duration (1-2147483646)", "Set accept lifetime of the key\n" "Time to start\n" "Day of th month to start\n" "Month of the year to start\n" "Year to start\n" "Duration of the key\n" "Duration seconds\n") { int idx_hhmmss = 1; int idx_number = 2; int idx_month = 3; int idx_number_2 = 4; int idx_number_3 = 6; VTY_DECLVAR_CONTEXT_SUB(key, key); return key_lifetime_duration_set( vty, &key->accept, argv[idx_hhmmss]->arg, argv[idx_number]->arg, argv[idx_month]->arg, argv[idx_number_2]->arg, argv[idx_number_3]->arg); } DEFUN (accept_lifetime_duration_month_day, accept_lifetime_duration_month_day_cmd, "accept-lifetime HH:MM:SS MONTH (1-31) (1993-2035) duration (1-2147483646)", "Set accept lifetime of the key\n" "Time to start\n" "Month of the year to start\n" "Day of th month to start\n" "Year to start\n" "Duration of the key\n" "Duration seconds\n") { int idx_hhmmss = 1; int idx_month = 2; int idx_number = 3; int idx_number_2 = 4; int idx_number_3 = 6; VTY_DECLVAR_CONTEXT_SUB(key, key); return key_lifetime_duration_set( vty, &key->accept, argv[idx_hhmmss]->arg, argv[idx_number]->arg, argv[idx_month]->arg, argv[idx_number_2]->arg, argv[idx_number_3]->arg); } DEFUN (no_accept_lifetime, no_accept_lifetime_cmd, "no accept-lifetime", NO_STR "Unset accept-lifetime\n") { VTY_DECLVAR_CONTEXT_SUB(key, key); if (key->accept.start) key->accept.start = 0; if (key->accept.end) key->accept.end = 0; if (key->accept.duration) key->accept.duration = 0; return CMD_SUCCESS; } DEFUN (send_lifetime_day_month_day_month, send_lifetime_day_month_day_month_cmd, "send-lifetime HH:MM:SS (1-31) MONTH (1993-2035) HH:MM:SS (1-31) MONTH (1993-2035)", "Set send lifetime of the key\n" "Time to start\n" "Day of th month to start\n" "Month of the year to start\n" "Year to start\n" "Time to expire\n" "Day of th month to expire\n" "Month of the year to expire\n" "Year to expire\n") { int idx_hhmmss = 1; int idx_number = 2; int idx_month = 3; int idx_number_2 = 4; int idx_hhmmss_2 = 5; int idx_number_3 = 6; int idx_month_2 = 7; int idx_number_4 = 8; VTY_DECLVAR_CONTEXT_SUB(key, key); return key_lifetime_set( vty, &key->send, argv[idx_hhmmss]->arg, argv[idx_number]->arg, argv[idx_month]->arg, argv[idx_number_2]->arg, argv[idx_hhmmss_2]->arg, argv[idx_number_3]->arg, argv[idx_month_2]->arg, argv[idx_number_4]->arg); } DEFUN (send_lifetime_day_month_month_day, send_lifetime_day_month_month_day_cmd, "send-lifetime HH:MM:SS (1-31) MONTH (1993-2035) HH:MM:SS MONTH (1-31) (1993-2035)", "Set send lifetime of the key\n" "Time to start\n" "Day of th month to start\n" "Month of the year to start\n" "Year to start\n" "Time to expire\n" "Month of the year to expire\n" "Day of th month to expire\n" "Year to expire\n") { int idx_hhmmss = 1; int idx_number = 2; int idx_month = 3; int idx_number_2 = 4; int idx_hhmmss_2 = 5; int idx_month_2 = 6; int idx_number_3 = 7; int idx_number_4 = 8; VTY_DECLVAR_CONTEXT_SUB(key, key); return key_lifetime_set( vty, &key->send, argv[idx_hhmmss]->arg, argv[idx_number]->arg, argv[idx_month]->arg, argv[idx_number_2]->arg, argv[idx_hhmmss_2]->arg, argv[idx_number_3]->arg, argv[idx_month_2]->arg, argv[idx_number_4]->arg); } DEFUN (send_lifetime_month_day_day_month, send_lifetime_month_day_day_month_cmd, "send-lifetime HH:MM:SS MONTH (1-31) (1993-2035) HH:MM:SS (1-31) MONTH (1993-2035)", "Set send lifetime of the key\n" "Time to start\n" "Month of the year to start\n" "Day of th month to start\n" "Year to start\n" "Time to expire\n" "Day of th month to expire\n" "Month of the year to expire\n" "Year to expire\n") { int idx_hhmmss = 1; int idx_month = 2; int idx_number = 3; int idx_number_2 = 4; int idx_hhmmss_2 = 5; int idx_number_3 = 6; int idx_month_2 = 7; int idx_number_4 = 8; VTY_DECLVAR_CONTEXT_SUB(key, key); return key_lifetime_set( vty, &key->send, argv[idx_hhmmss]->arg, argv[idx_number]->arg, argv[idx_month]->arg, argv[idx_number_2]->arg, argv[idx_hhmmss_2]->arg, argv[idx_number_3]->arg, argv[idx_month_2]->arg, argv[idx_number_4]->arg); } DEFUN (send_lifetime_month_day_month_day, send_lifetime_month_day_month_day_cmd, "send-lifetime HH:MM:SS MONTH (1-31) (1993-2035) HH:MM:SS MONTH (1-31) (1993-2035)", "Set send lifetime of the key\n" "Time to start\n" "Month of the year to start\n" "Day of th month to start\n" "Year to start\n" "Time to expire\n" "Month of the year to expire\n" "Day of th month to expire\n" "Year to expire\n") { int idx_hhmmss = 1; int idx_month = 2; int idx_number = 3; int idx_number_2 = 4; int idx_hhmmss_2 = 5; int idx_month_2 = 6; int idx_number_3 = 7; int idx_number_4 = 8; VTY_DECLVAR_CONTEXT_SUB(key, key); return key_lifetime_set( vty, &key->send, argv[idx_hhmmss]->arg, argv[idx_number]->arg, argv[idx_month]->arg, argv[idx_number_2]->arg, argv[idx_hhmmss_2]->arg, argv[idx_number_3]->arg, argv[idx_month_2]->arg, argv[idx_number_4]->arg); } DEFUN (send_lifetime_infinite_day_month, send_lifetime_infinite_day_month_cmd, "send-lifetime HH:MM:SS (1-31) MONTH (1993-2035) infinite", "Set send lifetime of the key\n" "Time to start\n" "Day of th month to start\n" "Month of the year to start\n" "Year to start\n" "Never expires\n") { int idx_hhmmss = 1; int idx_number = 2; int idx_month = 3; int idx_number_2 = 4; VTY_DECLVAR_CONTEXT_SUB(key, key); return key_lifetime_infinite_set( vty, &key->send, argv[idx_hhmmss]->arg, argv[idx_number]->arg, argv[idx_month]->arg, argv[idx_number_2]->arg); } DEFUN (send_lifetime_infinite_month_day, send_lifetime_infinite_month_day_cmd, "send-lifetime HH:MM:SS MONTH (1-31) (1993-2035) infinite", "Set send lifetime of the key\n" "Time to start\n" "Month of the year to start\n" "Day of th month to start\n" "Year to start\n" "Never expires\n") { int idx_hhmmss = 1; int idx_month = 2; int idx_number = 3; int idx_number_2 = 4; VTY_DECLVAR_CONTEXT_SUB(key, key); return key_lifetime_infinite_set( vty, &key->send, argv[idx_hhmmss]->arg, argv[idx_number]->arg, argv[idx_month]->arg, argv[idx_number_2]->arg); } DEFUN (send_lifetime_duration_day_month, send_lifetime_duration_day_month_cmd, "send-lifetime HH:MM:SS (1-31) MONTH (1993-2035) duration (1-2147483646)", "Set send lifetime of the key\n" "Time to start\n" "Day of th month to start\n" "Month of the year to start\n" "Year to start\n" "Duration of the key\n" "Duration seconds\n") { int idx_hhmmss = 1; int idx_number = 2; int idx_month = 3; int idx_number_2 = 4; int idx_number_3 = 6; VTY_DECLVAR_CONTEXT_SUB(key, key); return key_lifetime_duration_set( vty, &key->send, argv[idx_hhmmss]->arg, argv[idx_number]->arg, argv[idx_month]->arg, argv[idx_number_2]->arg, argv[idx_number_3]->arg); } DEFUN (send_lifetime_duration_month_day, send_lifetime_duration_month_day_cmd, "send-lifetime HH:MM:SS MONTH (1-31) (1993-2035) duration (1-2147483646)", "Set send lifetime of the key\n" "Time to start\n" "Month of the year to start\n" "Day of th month to start\n" "Year to start\n" "Duration of the key\n" "Duration seconds\n") { int idx_hhmmss = 1; int idx_month = 2; int idx_number = 3; int idx_number_2 = 4; int idx_number_3 = 6; VTY_DECLVAR_CONTEXT_SUB(key, key); return key_lifetime_duration_set( vty, &key->send, argv[idx_hhmmss]->arg, argv[idx_number]->arg, argv[idx_month]->arg, argv[idx_number_2]->arg, argv[idx_number_3]->arg); } DEFUN (no_send_lifetime, no_send_lifetime_cmd, "no send-lifetime", NO_STR "Unset send-lifetime\n") { VTY_DECLVAR_CONTEXT_SUB(key, key); if (key->send.start) key->send.start = 0; if (key->send.end) key->send.end = 0; if (key->send.duration) key->send.duration = 0; return CMD_SUCCESS; } static struct cmd_node keychain_node = {KEYCHAIN_NODE, "%s(config-keychain)# ", 1}; static struct cmd_node keychain_key_node = {KEYCHAIN_KEY_NODE, "%s(config-keychain-key)# ", 1}; static int keychain_strftime(char *buf, int bufsiz, time_t *time) { struct tm *tm; size_t len; tm = localtime(time); len = strftime(buf, bufsiz, "%T %b %d %Y", tm); return len; } static int keychain_config_write(struct vty *vty) { struct keychain *keychain; struct key *key; struct listnode *node; struct listnode *knode; char buf[BUFSIZ]; for (ALL_LIST_ELEMENTS_RO(keychain_list, node, keychain)) { vty_out(vty, "key chain %s\n", keychain->name); for (ALL_LIST_ELEMENTS_RO(keychain->key, knode, key)) { vty_out(vty, " key %d\n", key->index); if (key->string) vty_out(vty, " key-string %s\n", key->string); if (key->accept.start) { keychain_strftime(buf, BUFSIZ, &key->accept.start); vty_out(vty, " accept-lifetime %s", buf); if (key->accept.end == -1) vty_out(vty, " infinite"); else if (key->accept.duration) vty_out(vty, " duration %ld", (long)(key->accept.end - key->accept.start)); else { keychain_strftime(buf, BUFSIZ, &key->accept.end); vty_out(vty, " %s", buf); } vty_out(vty, "\n"); } if (key->send.start) { keychain_strftime(buf, BUFSIZ, &key->send.start); vty_out(vty, " send-lifetime %s", buf); if (key->send.end == -1) vty_out(vty, " infinite"); else if (key->send.duration) vty_out(vty, " duration %ld", (long)(key->send.end - key->send.start)); else { keychain_strftime(buf, BUFSIZ, &key->send.end); vty_out(vty, " %s", buf); } vty_out(vty, "\n"); } } vty_out(vty, "!\n"); } return 0; } void keychain_init(void) { keychain_list = list_new(); install_node(&keychain_node, keychain_config_write); install_node(&keychain_key_node, NULL); install_default(KEYCHAIN_NODE); install_default(KEYCHAIN_KEY_NODE); install_element(CONFIG_NODE, &key_chain_cmd); install_element(CONFIG_NODE, &no_key_chain_cmd); install_element(KEYCHAIN_NODE, &key_cmd); install_element(KEYCHAIN_NODE, &no_key_cmd); install_element(KEYCHAIN_NODE, &key_chain_cmd); install_element(KEYCHAIN_NODE, &no_key_chain_cmd); install_element(KEYCHAIN_KEY_NODE, &key_string_cmd); install_element(KEYCHAIN_KEY_NODE, &no_key_string_cmd); install_element(KEYCHAIN_KEY_NODE, &key_chain_cmd); install_element(KEYCHAIN_KEY_NODE, &no_key_chain_cmd); install_element(KEYCHAIN_KEY_NODE, &key_cmd); install_element(KEYCHAIN_KEY_NODE, &no_key_cmd); install_element(KEYCHAIN_KEY_NODE, &accept_lifetime_day_month_day_month_cmd); install_element(KEYCHAIN_KEY_NODE, &accept_lifetime_day_month_month_day_cmd); install_element(KEYCHAIN_KEY_NODE, &accept_lifetime_month_day_day_month_cmd); install_element(KEYCHAIN_KEY_NODE, &accept_lifetime_month_day_month_day_cmd); install_element(KEYCHAIN_KEY_NODE, &accept_lifetime_infinite_day_month_cmd); install_element(KEYCHAIN_KEY_NODE, &accept_lifetime_infinite_month_day_cmd); install_element(KEYCHAIN_KEY_NODE, &accept_lifetime_duration_day_month_cmd); install_element(KEYCHAIN_KEY_NODE, &accept_lifetime_duration_month_day_cmd); install_element(KEYCHAIN_KEY_NODE, &no_accept_lifetime_cmd); install_element(KEYCHAIN_KEY_NODE, &send_lifetime_day_month_day_month_cmd); install_element(KEYCHAIN_KEY_NODE, &send_lifetime_day_month_month_day_cmd); install_element(KEYCHAIN_KEY_NODE, &send_lifetime_month_day_day_month_cmd); install_element(KEYCHAIN_KEY_NODE, &send_lifetime_month_day_month_day_cmd); install_element(KEYCHAIN_KEY_NODE, &send_lifetime_infinite_day_month_cmd); install_element(KEYCHAIN_KEY_NODE, &send_lifetime_infinite_month_day_cmd); install_element(KEYCHAIN_KEY_NODE, &send_lifetime_duration_day_month_cmd); install_element(KEYCHAIN_KEY_NODE, &send_lifetime_duration_month_day_cmd); install_element(KEYCHAIN_KEY_NODE, &no_send_lifetime_cmd); } frr-7.2.1/lib/keychain.h0000644000000000000000000000312513610377563011727 00000000000000/* key-chain for authentication. * Copyright (C) 2000 Kunihiro Ishiguro * * This file is part of GNU Zebra. * * GNU Zebra is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License as published * by the Free Software Foundation; either version 2, or (at your * option) any later version. * * GNU Zebra 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; see the file COPYING; if not, write to the Free Software * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA */ #ifndef _ZEBRA_KEYCHAIN_H #define _ZEBRA_KEYCHAIN_H #include "qobj.h" #ifdef __cplusplus extern "C" { #endif struct keychain { char *name; struct list *key; QOBJ_FIELDS }; DECLARE_QOBJ_TYPE(keychain) struct key_range { time_t start; time_t end; uint8_t duration; }; struct key { uint32_t index; char *string; struct key_range send; struct key_range accept; QOBJ_FIELDS }; DECLARE_QOBJ_TYPE(key) extern void keychain_init(void); extern struct keychain *keychain_lookup(const char *); extern struct key *key_lookup_for_accept(const struct keychain *, uint32_t); extern struct key *key_match_for_accept(const struct keychain *, const char *); extern struct key *key_lookup_for_send(const struct keychain *); #ifdef __cplusplus } #endif #endif /* _ZEBRA_KEYCHAIN_H */ frr-7.2.1/lib/lib_errors.c0000644000000000000000000004133513610377563012276 00000000000000/* * Library-specific error messages. * Copyright (C) 2018 Cumulus Networks, Inc. * Donald Sharp * * This program is free software; you can redistribute it and/or modify it * under the terms of the 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; see the file COPYING; if not, write to the Free Software * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA */ #ifdef HAVE_CONFIG_H #include "config.h" #endif #include "lib_errors.h" /* clang-format off */ static struct log_ref ferr_lib_warn[] = { { .code = EC_LIB_SNMP, .title = "SNMP has discovered a warning", .description = "The SNMP AgentX library has returned a warning that we should report to the end user", .suggestion = "Gather Log data and open an Issue.", }, { .code = EC_LIB_STREAM, .title = "The stream subsystem has encountered an error", .description = "During sanity checking stream.c has detected an error in the data associated with a particular stream", .suggestion = "Gather log data and open an Issue, restart FRR", }, { .code = EC_LIB_LINUX_NS, .title = "The Linux namespace subsystem has encountered a parsing error", .description = "During system startup an invalid parameter for the namesapce was give to FRR", .suggestion = "Gather log data and open an Issue. restart FRR", }, { .code = EC_LIB_SLOW_THREAD, .title = "The Event subsystem has detected a slow process", .description = "The Event subsystem has detected a slow process, this typically indicates that FRR is having trouble completing work in a timely manner. This can be either a misconfiguration, bug, or some combination therof.", .suggestion = "Gather log data and open an Issue", }, { .code = EC_LIB_NO_THREAD, .title = "The Event subsystem has detected an internal FD problem", .description = "The Event subsystem has detected a file descriptor read/write event without an associated handling function. This is a bug, please collect log data and open an issue.", .suggestion = "Gather log data and open an Issue", }, { .code = EC_LIB_RMAP_RECURSION_LIMIT, .title = "Reached the Route-Map Recursion Limit", .description = "The Route-Map subsystem has detected a route-map depth of RMAP_RECURSION_LIMIT and has stopped processing", .suggestion = "Re-work the Route-Map in question to not have so many route-map statements, or recompile FRR with a higher limit", }, { .code = EC_LIB_BACKUP_CONFIG, .title = "Unable to open configuration file", .description = "The config subsystem attempted to read in it's configuration file which failed, so we are falling back to the backup config file to see if it is available", .suggestion = "Create configuration file", }, { .code = EC_LIB_VRF_LENGTH, .title = "The VRF subsystem has encountered a parsing error", .description = "The VRF subsystem, during initialization, has found a parsing error with input it has received", .suggestion = "Check the length of the vrf name and adjust accordingly", }, { .code = EC_LIB_YANG_DATA_TRUNCATED, .title = "YANG data truncation", .description = "The northbound subsystem has detected that YANG data has been truncated as the given buffer wasn't big enough", .suggestion = "Gather log data and open an Issue", }, { .code = EC_LIB_YANG_UNKNOWN_DATA_PATH, .title = "Unknown YANG data path", .description = "The northbound subsystem has detected an unknown YANG data path", .suggestion = "Gather log data and open an Issue", }, { .code = EC_LIB_YANG_TRANSLATOR_LOAD, .title = "Unable to load YANG module translator", .description = "The northbound subsystem has detected an error while loading a YANG module translator", .suggestion = "Ensure the YANG module translator file is valid. See documentation for further information.", }, { .code = EC_LIB_YANG_TRANSLATION_ERROR, .title = "YANG translation error", .description = "The northbound subsystem has detected an error while performing a YANG XPath translation", .suggestion = "Gather log data and open an Issue", }, { .code = EC_LIB_NB_DATABASE, .title = "The northbound database wasn't initialized correctly", .description = "An error occurred while initializing the northbound database. As a result, the configuration rollback feature won't work as expected.", .suggestion = "Ensure permissions are correct for FRR files, users and groups are correct." }, { .code = EC_LIB_NB_CB_UNNEEDED, .title = "Unneeded northbound callback", .description = "The northbound subsystem, during initialization, has detected a callback that doesn't need to be implemented", .suggestion = "This is a bug; please report it" }, { .code = EC_LIB_NB_CB_CONFIG_VALIDATE, .title = "A northbound configuration callback has failed in the VALIDATE phase", .description = "A callback used to process a configuration change has returned a validation error", .suggestion = "The provided configuration is invalid. Fix any inconsistency and try again.", }, { .code = EC_LIB_NB_CB_CONFIG_PREPARE, .title = "A northbound configuration callback has failed in the PREPARE phase", .description = "A callback used to process a configuration change has returned a resource allocation error", .suggestion = "The system might be running out of resources. Check the log for more details.", }, { .code = EC_LIB_NB_CB_STATE, .title = "A northbound callback for operational data has failed", .description = "The northbound subsystem has detected that a callback used to fetch operational data has returned an error", .suggestion = "Gather log data and open an Issue", }, { .code = EC_LIB_NB_CB_RPC, .title = "A northbound RPC callback has failed", .description = "The northbound subsystem has detected that a callback used to process YANG RPCs/actions has returned an error", .suggestion = "The log message should contain further details on the specific error that occurred; investigate the reported error.", }, { .code = EC_LIB_NB_CANDIDATE_INVALID, .title = "Invalid candidate configuration", .description = "The northbound subsystem failed to validate a candidate configuration", .suggestion = "Check the log messages to see the validation errors and edit the candidate configuration to fix them", }, { .code = EC_LIB_NB_CANDIDATE_EDIT_ERROR, .title = "Failure to edit a candidate configuration", .description = "The northbound subsystem failed to edit a candidate configuration", .suggestion = "This is a bug; please report it" }, { .code = EC_LIB_NB_OPERATIONAL_DATA, .title = "Failure to obtain operational data", .description = "The northbound subsystem failed to obtain YANG-modeled operational data", .suggestion = "This is a bug; please report it" }, { .code = EC_LIB_NB_TRANSACTION_CREATION_FAILED, .title = "Failure to create a configuration transaction", .description = "The northbound subsystem failed to create a configuration transaction", .suggestion = "The log message should contain further details on the specific error that occurred; investigate the reported error.", }, { .code = EC_LIB_NB_TRANSACTION_RECORD_FAILED, .title = "Failure to record a configuration transaction", .description = "The northbound subsystem failed to record a configuration transaction in the northbound database", .suggestion = "Gather log data and open an Issue", }, { .code = END_FERR, }, }; static struct log_ref ferr_lib_err[] = { { .code = EC_LIB_PRIVILEGES, .title = "Failure to raise or lower privileges", .description = "FRR attempted to raise or lower its privileges and was unable to do so", .suggestion = "Ensure that you are running FRR as the frr user and that the user has sufficient privileges to properly access root privileges" }, { .code = EC_LIB_VRF_START, .title = "VRF Failure on Start", .description = "Upon startup FRR failed to properly initialize and startup the VRF subsystem", .suggestion = "Ensure that there is sufficient memory to start processes and restart FRR", }, { .code = EC_LIB_SOCKET, .title = "Socket Error", .description = "When attempting to access a socket a system error has occurred and we were unable to properly complete the request", .suggestion = "Ensure that there are sufficient system resources available and ensure that the frr user has sufficient permissions to work. If necessary open an Issue", }, { .code = EC_LIB_ZAPI_MISSMATCH, .title = "ZAPI Error", .description = "A version miss-match has been detected between zebra and client protocol", .suggestion = "Two different versions of FRR have been installed and the install is not properly setup. Completely stop FRR, remove it from the system and reinstall. Typically only developers should see this issue." }, { .code = EC_LIB_ZAPI_ENCODE, .title = "ZAPI Error", .description = "The ZAPI subsystem has detected an encoding issue, between zebra and a client protocol", .suggestion = "Gather data and open an Issue, also Restart FRR" }, { .code = EC_LIB_ZAPI_SOCKET, .title = "ZAPI Error", .description = "The ZAPI subsystem has detected a socket error between zebra and a client", .suggestion = "Restart FRR" }, { .code = EC_LIB_SYSTEM_CALL, .title = "System Call Error", .description = "FRR has detected a error from using a vital system call and has probably already exited", .suggestion = "Ensure permissions are correct for FRR files, users and groups are correct. Additionally check that sufficient system resources are available." }, { .code = EC_LIB_VTY, .title = "VTY Subsystem Error", .description = "FRR has detected a problem with the specified configuration file", .suggestion = "Ensure configuration file exists and has correct permissions for operations Additionally ensure that all config lines are correct as well", }, { .code = EC_LIB_INTERFACE, .title = "Interface Subsystem Error", .description = "FRR has detected a problem with interface data from the kernel as it deviates from what we would expect to happen via normal netlink messaging", .suggestion = "Open an Issue with all relevant log files and restart FRR" }, { .code = EC_LIB_NS, .title = "NameSpace Subsystem Error", .description = "FRR has detected a problem with NameSpace data from the kernel as it deviates from what we would expect to happen via normal kernel messaging", .suggestion = "Open an Issue with all relevant log files and restart FRR" }, { .code = EC_LIB_DEVELOPMENT, .title = "Developmental Escape Error", .description = "FRR has detected an issue where new development has not properly updated all code paths.", .suggestion = "Open an Issue with all relevant log files" }, { .code = EC_LIB_ZMQ, .title = "ZMQ Subsystem Error", .description = "FRR has detected an issue with the Zero MQ subsystem and ZeroMQ is not working properly now", .suggestion = "Open an Issue with all relevant log files and restart FRR" }, { .code = EC_LIB_UNAVAILABLE, .title = "Feature or system unavailable", .description = "FRR was not compiled with support for a particular feature, or it is not available on the current platform", .suggestion = "Recompile FRR with the feature enabled, or find out what platforms support the feature" }, { .code = EC_LIB_YANG_MODULE_LOAD, .title = "Unable to load YANG module from the file system", .description = "The northbound subsystem has detected an error while loading a YANG module from the file system", .suggestion = "Ensure all FRR YANG modules were installed correctly in the system.", }, { .code = EC_LIB_YANG_MODULE_LOADED_ALREADY, .title = "Attempt to load a YANG module that is already loaded", .description = "The northbound subsystem has detected an attempt to load a YANG module that is already loaded", .suggestion = "This is a bug; please report it" }, { .code = EC_LIB_YANG_DATA_CONVERT, .title = "YANG data conversion error", .description = "An error has occurred while converting a YANG data value from string to binary representation or vice-versa", .suggestion = "Open an Issue with all relevant log files and restart FRR" }, { .code = EC_LIB_YANG_DNODE_NOT_FOUND, .title = "YANG data node not found", .description = "The northbound subsystem failed to find a YANG data node that was supposed to exist", .suggestion = "This is a bug; please report it" }, { .code = EC_LIB_NB_CB_MISSING, .title = "Missing northbound callback", .description = "The northbound subsystem, during initialization, has detected a missing callback for one node of the loaded YANG modules", .suggestion = "This is a bug; please report it" }, { .code = EC_LIB_NB_CB_INVALID_PRIO, .title = "Norhtbound callback has an invalid priority", .description = "The northbound subsystem, during initialization, has detected a callback whose priority is invalid", .suggestion = "This is a bug; please report it" }, { .code = EC_LIB_NB_CBS_VALIDATION, .title = "Failure to validate the northbound callbacks", .description = "The northbound subsystem, during initialization, has detected one or more errors while loading the northbound callbacks", .suggestion = "This is a bug; please report it" }, { .code = EC_LIB_LIBYANG, .title = "The libyang library returned an error", .description = "The northbound subsystem has detected that the libyang library returned an error", .suggestion = "Open an Issue with all relevant log files and restart FRR" }, { .code = EC_LIB_LIBYANG_PLUGIN_LOAD, .title = "Failure to load a libyang plugin", .description = "The northbound subsystem, during initialization, has detected that a libyang plugin failed to be loaded", .suggestion = "Check if the FRR libyang plugins were installed correctly in the system", }, { .code = EC_LIB_CONFD_INIT, .title = "ConfD initialization error", .description = "Upon startup FRR failed to properly initialize and startup the ConfD northbound plugin", .suggestion = "Check if ConfD is installed correctly in the system. Also, check if the confd daemon is running.", }, { .code = EC_LIB_CONFD_DATA_CONVERT, .title = "ConfD data conversion error", .description = "An error has occurred while converting a ConfD data value (binary) to a string", .suggestion = "Open an Issue with all relevant log files and restart FRR" }, { .code = EC_LIB_LIBCONFD, .title = "libconfd error", .description = "The northbound subsystem has detected that the libconfd library returned an error", .suggestion = "Open an Issue with all relevant log files and restart FRR" }, { .code = EC_LIB_SYSREPO_INIT, .title = "Sysrepo initialization error", .description = "Upon startup FRR failed to properly initialize and startup the Sysrepo northbound plugin", .suggestion = "Check if Sysrepo is installed correctly in the system", }, { .code = EC_LIB_SYSREPO_DATA_CONVERT, .title = "Sysrepo data conversion error", .description = "An error has occurred while converting a YANG data value to the Sysrepo format", .suggestion = "Open an Issue with all relevant log files and restart FRR" }, { .code = EC_LIB_LIBSYSREPO, .title = "libsysrepo error", .description = "The northbound subsystem has detected that the libsysrepo library returned an error", .suggestion = "Open an Issue with all relevant log files and restart FRR" }, { .code = EC_LIB_GRPC_INIT, .title = "gRPC initialization error", .description = "Upon startup FRR failed to properly initialize and startup the gRPC northbound plugin", .suggestion = "Check if the gRPC libraries are installed correctly in the system.", }, { .code = EC_LIB_NB_CB_CONFIG_ABORT, .title = "A northbound configuration callback has failed in the ABORT phase", .description = "A callback used to process a configuration change has returned an error while trying to abort a change", .suggestion = "Gather log data and open an Issue.", }, { .code = EC_LIB_NB_CB_CONFIG_APPLY, .title = "A northbound configuration callback has failed in the APPLY phase", .description = "A callback used to process a configuration change has returned an error while applying the changes", .suggestion = "Gather log data and open an Issue.", }, { .code = EC_LIB_RESOLVER, .title = "DNS Resolution", .description = "An error was detected while attempting to resolve a hostname", .suggestion = "Ensure that DNS is working properly and the hostname is configured in dns. If you are still seeing this error, open an issue" }, { .code = END_FERR, } }; /* clang-format on */ void lib_error_init(void) { log_ref_add(ferr_lib_warn); log_ref_add(ferr_lib_err); } frr-7.2.1/lib/lib_errors.h0000644000000000000000000000462013610377563012277 00000000000000/* * Library-specific error messages. * Copyright (C) 2018 Cumulus Networks, Inc. * Donald Sharp * * This program is free software; you can redistribute it and/or modify it * under the terms of the 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; see the file COPYING; if not, write to the Free Software * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA */ #ifndef __LIB_ERRORS_H__ #define __LIB_ERRORS_H__ #include "lib/ferr.h" #ifdef __cplusplus extern "C" { #endif enum lib_log_refs { EC_LIB_PRIVILEGES = LIB_FERR_START, EC_LIB_VRF_START, EC_LIB_SOCKET, EC_LIB_ZAPI_MISSMATCH, EC_LIB_ZAPI_ENCODE, EC_LIB_ZAPI_SOCKET, EC_LIB_SYSTEM_CALL, EC_LIB_VTY, EC_LIB_INTERFACE, EC_LIB_NS, EC_LIB_DEVELOPMENT, EC_LIB_ZMQ, EC_LIB_UNAVAILABLE, EC_LIB_SNMP, EC_LIB_STREAM, EC_LIB_LINUX_NS, EC_LIB_SLOW_THREAD, EC_LIB_NO_THREAD, EC_LIB_RMAP_RECURSION_LIMIT, EC_LIB_BACKUP_CONFIG, EC_LIB_VRF_LENGTH, EC_LIB_YANG_MODULE_LOAD, EC_LIB_YANG_MODULE_LOADED_ALREADY, EC_LIB_YANG_DATA_CONVERT, EC_LIB_YANG_DATA_TRUNCATED, EC_LIB_YANG_UNKNOWN_DATA_PATH, EC_LIB_YANG_DNODE_NOT_FOUND, EC_LIB_YANG_TRANSLATOR_LOAD, EC_LIB_YANG_TRANSLATION_ERROR, EC_LIB_NB_DATABASE, EC_LIB_NB_CB_UNNEEDED, EC_LIB_NB_CB_MISSING, EC_LIB_NB_CB_INVALID_PRIO, EC_LIB_NB_CBS_VALIDATION, EC_LIB_NB_CB_CONFIG_VALIDATE, EC_LIB_NB_CB_CONFIG_PREPARE, EC_LIB_NB_CB_CONFIG_ABORT, EC_LIB_NB_CB_CONFIG_APPLY, EC_LIB_NB_CB_STATE, EC_LIB_NB_CB_RPC, EC_LIB_NB_CANDIDATE_INVALID, EC_LIB_NB_CANDIDATE_EDIT_ERROR, EC_LIB_NB_OPERATIONAL_DATA, EC_LIB_NB_TRANSACTION_CREATION_FAILED, EC_LIB_NB_TRANSACTION_RECORD_FAILED, EC_LIB_LIBYANG, EC_LIB_LIBYANG_PLUGIN_LOAD, EC_LIB_CONFD_INIT, EC_LIB_CONFD_DATA_CONVERT, EC_LIB_LIBCONFD, EC_LIB_SYSREPO_INIT, EC_LIB_SYSREPO_DATA_CONVERT, EC_LIB_LIBSYSREPO, EC_LIB_GRPC_INIT, EC_LIB_ID_CONSISTENCY, EC_LIB_ID_EXHAUST, EC_LIB_RESOLVER, }; extern void lib_error_init(void); #ifdef __cplusplus } #endif #endif frr-7.2.1/lib/libfrr.c0000644000000000000000000006455313610377563011423 00000000000000/* * libfrr overall management functions * * Copyright (C) 2016 David Lamparter for NetDEF, Inc. * * This program is free software; you can redistribute it and/or modify it * under the terms of the 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; see the file COPYING; if not, write to the Free Software * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA */ #include #include #include #include #include "libfrr.h" #include "getopt.h" #include "privs.h" #include "vty.h" #include "command.h" #include "version.h" #include "memory_vty.h" #include "log_vty.h" #include "zclient.h" #include "log_int.h" #include "module.h" #include "network.h" #include "lib_errors.h" #include "db.h" #include "northbound_cli.h" #include "northbound_db.h" #include "debug.h" #include "frrcu.h" DEFINE_HOOK(frr_late_init, (struct thread_master * tm), (tm)) DEFINE_KOOH(frr_early_fini, (), ()) DEFINE_KOOH(frr_fini, (), ()) const char frr_sysconfdir[] = SYSCONFDIR; char frr_vtydir[256]; #ifdef HAVE_SQLITE3 const char frr_dbdir[] = DAEMON_DB_DIR; #endif const char frr_moduledir[] = MODULE_PATH; char frr_protoname[256] = "NONE"; char frr_protonameinst[256] = "NONE"; char config_default[512]; char frr_zclientpath[256]; static char pidfile_default[1024]; #ifdef HAVE_SQLITE3 static char dbfile_default[512]; #endif static char vtypath_default[512]; bool debug_memstats_at_exit = false; static bool nodetach_term, nodetach_daemon; static char comb_optstr[256]; static struct option comb_lo[64]; static struct option *comb_next_lo = &comb_lo[0]; static char comb_helpstr[4096]; struct optspec { const char *optstr; const char *helpstr; const struct option *longopts; }; static void opt_extend(const struct optspec *os) { const struct option *lo; strlcat(comb_optstr, os->optstr, sizeof(comb_optstr)); strlcat(comb_helpstr, os->helpstr, sizeof(comb_helpstr)); for (lo = os->longopts; lo->name; lo++) memcpy(comb_next_lo++, lo, sizeof(*lo)); } #define OPTION_VTYSOCK 1000 #define OPTION_MODULEDIR 1002 #define OPTION_LOG 1003 #define OPTION_LOGLEVEL 1004 #define OPTION_TCLI 1005 #define OPTION_DB_FILE 1006 #define OPTION_LOGGING 1007 static const struct option lo_always[] = { {"help", no_argument, NULL, 'h'}, {"version", no_argument, NULL, 'v'}, {"daemon", no_argument, NULL, 'd'}, {"module", no_argument, NULL, 'M'}, {"vty_socket", required_argument, NULL, OPTION_VTYSOCK}, {"moduledir", required_argument, NULL, OPTION_MODULEDIR}, {"log", required_argument, NULL, OPTION_LOG}, {"log-level", required_argument, NULL, OPTION_LOGLEVEL}, {"tcli", no_argument, NULL, OPTION_TCLI}, {"command-log-always", no_argument, NULL, OPTION_LOGGING}, {NULL}}; static const struct optspec os_always = { "hvdM:", " -h, --help Display this help and exit\n" " -v, --version Print program version\n" " -d, --daemon Runs in daemon mode\n" " -M, --module Load specified module\n" " --vty_socket Override vty socket path\n" " --moduledir Override modules directory\n" " --log Set Logging to stdout, syslog, or file:\n" " --log-level Set Logging Level to use, debug, info, warn, etc\n" " --tcli Use transaction-based CLI\n", lo_always}; static const struct option lo_cfg_pid_dry[] = { {"pid_file", required_argument, NULL, 'i'}, {"config_file", required_argument, NULL, 'f'}, #ifdef HAVE_SQLITE3 {"db_file", required_argument, NULL, OPTION_DB_FILE}, #endif {"pathspace", required_argument, NULL, 'N'}, {"dryrun", no_argument, NULL, 'C'}, {"terminal", no_argument, NULL, 't'}, {NULL}}; static const struct optspec os_cfg_pid_dry = { "f:i:CtN:", " -f, --config_file Set configuration file name\n" " -i, --pid_file Set process identifier file name\n" #ifdef HAVE_SQLITE3 " --db_file Set database file name\n" #endif " -N, --pathspace Insert prefix into config & socket paths\n" " -C, --dryrun Check configuration for validity and exit\n" " -t, --terminal Open terminal session on stdio\n" " -d -t Daemonize after terminal session ends\n", lo_cfg_pid_dry}; static const struct option lo_zclient[] = { {"socket", required_argument, NULL, 'z'}, {NULL}}; static const struct optspec os_zclient = { "z:", " -z, --socket Set path of zebra socket\n", lo_zclient}; static const struct option lo_vty[] = { {"vty_addr", required_argument, NULL, 'A'}, {"vty_port", required_argument, NULL, 'P'}, {NULL}}; static const struct optspec os_vty = { "A:P:", " -A, --vty_addr Set vty's bind address\n" " -P, --vty_port Set vty's port number\n", lo_vty}; static const struct option lo_user[] = {{"user", required_argument, NULL, 'u'}, {"group", required_argument, NULL, 'g'}, {NULL}}; static const struct optspec os_user = {"u:g:", " -u, --user User to run as\n" " -g, --group Group to run as\n", lo_user}; bool frr_zclient_addr(struct sockaddr_storage *sa, socklen_t *sa_len, const char *path) { memset(sa, 0, sizeof(*sa)); if (!path) path = frr_zclientpath; if (!strncmp(path, ZAPI_TCP_PATHNAME, strlen(ZAPI_TCP_PATHNAME))) { /* note: this functionality is disabled at bottom */ int af; int port = ZEBRA_PORT; char *err = NULL; struct sockaddr_in *sin = NULL; struct sockaddr_in6 *sin6 = NULL; path += strlen(ZAPI_TCP_PATHNAME); switch (path[0]) { case '4': path++; af = AF_INET; break; case '6': path++; /* fallthrough */ default: af = AF_INET6; break; } switch (path[0]) { case '\0': break; case ':': path++; port = strtoul(path, &err, 10); if (*err || !*path) return false; break; default: return false; } sa->ss_family = af; switch (af) { case AF_INET: sin = (struct sockaddr_in *)sa; sin->sin_port = htons(port); sin->sin_addr.s_addr = htonl(INADDR_LOOPBACK); *sa_len = sizeof(struct sockaddr_in); #ifdef HAVE_STRUCT_SOCKADDR_IN_SIN_LEN sin->sin_len = *sa_len; #endif break; case AF_INET6: sin6 = (struct sockaddr_in6 *)sa; sin6->sin6_port = htons(port); inet_pton(AF_INET6, "::1", &sin6->sin6_addr); *sa_len = sizeof(struct sockaddr_in6); #ifdef SIN6_LEN sin6->sin6_len = *sa_len; #endif break; } #if 1 /* force-disable this path, because tcp-zebra is a * SECURITY ISSUE. there are no checks at all against * untrusted users on the local system connecting on TCP * and injecting bogus routing data into the entire routing * domain. * * The functionality is only left here because it may be * useful during development, in order to be able to get * tcpdump or wireshark watching ZAPI as TCP. If you want * to do that, flip the #if 1 above to #if 0. */ memset(sa, 0, sizeof(*sa)); return false; #endif } else { /* "sun" is a #define on solaris */ struct sockaddr_un *suna = (struct sockaddr_un *)sa; suna->sun_family = AF_UNIX; strlcpy(suna->sun_path, path, sizeof(suna->sun_path)); #ifdef HAVE_STRUCT_SOCKADDR_UN_SUN_LEN *sa_len = suna->sun_len = SUN_LEN(suna); #else *sa_len = sizeof(suna->sun_family) + strlen(suna->sun_path); #endif /* HAVE_STRUCT_SOCKADDR_UN_SUN_LEN */ #if 0 /* this is left here for future reference; Linux abstract * socket namespace support can be enabled by replacing * above #if 0 with #ifdef GNU_LINUX. * * THIS IS A SECURITY ISSUE, the abstract socket namespace * does not have user/group permission control on sockets. * we'd need to implement SCM_CREDENTIALS support first to * check that only proper users can connect to abstract * sockets. (same problem as tcp-zebra, except there is a * fix with SCM_CREDENTIALS. tcp-zebra has no such fix.) */ if (suna->sun_path[0] == '@') suna->sun_path[0] = '\0'; #endif } return true; } static struct frr_daemon_info *di = NULL; void frr_init_vtydir(void) { snprintf(frr_vtydir, sizeof(frr_vtydir), DAEMON_VTY_DIR, "", ""); } void frr_preinit(struct frr_daemon_info *daemon, int argc, char **argv) { di = daemon; /* basename(), opencoded. */ char *p = strrchr(argv[0], '/'); di->progname = p ? p + 1 : argv[0]; umask(0027); opt_extend(&os_always); if (!(di->flags & FRR_NO_CFG_PID_DRY)) opt_extend(&os_cfg_pid_dry); if (!(di->flags & FRR_NO_PRIVSEP)) opt_extend(&os_user); if (!(di->flags & FRR_NO_ZCLIENT)) opt_extend(&os_zclient); if (!(di->flags & FRR_NO_TCPVTY)) opt_extend(&os_vty); if (di->flags & FRR_DETACH_LATER) nodetach_daemon = true; frr_init_vtydir(); snprintf(config_default, sizeof(config_default), "%s/%s.conf", frr_sysconfdir, di->name); snprintf(pidfile_default, sizeof(pidfile_default), "%s/%s.pid", frr_vtydir, di->name); snprintf(frr_zclientpath, sizeof(frr_zclientpath), ZEBRA_SERV_PATH, "", ""); #ifdef HAVE_SQLITE3 snprintf(dbfile_default, sizeof(dbfile_default), "%s/%s.db", frr_dbdir, di->name); #endif strlcpy(frr_protoname, di->logname, sizeof(frr_protoname)); strlcpy(frr_protonameinst, di->logname, sizeof(frr_protonameinst)); di->cli_mode = FRR_CLI_CLASSIC; } void frr_opt_add(const char *optstr, const struct option *longopts, const char *helpstr) { const struct optspec main_opts = {optstr, helpstr, longopts}; opt_extend(&main_opts); } void frr_help_exit(int status) { FILE *target = status ? stderr : stdout; if (status != 0) fprintf(stderr, "Invalid options.\n\n"); if (di->printhelp) di->printhelp(target); else fprintf(target, "Usage: %s [OPTION...]\n\n%s%s%s\n\n%s", di->progname, di->proghelp, di->copyright ? "\n\n" : "", di->copyright ? di->copyright : "", comb_helpstr); fprintf(target, "\nReport bugs to %s\n", FRR_BUG_ADDRESS); exit(status); } struct option_chain { struct option_chain *next; const char *arg; }; static struct option_chain *modules = NULL, **modnext = &modules; static int errors = 0; static int frr_opt(int opt) { static int vty_port_set = 0; static int vty_addr_set = 0; struct option_chain *oc; char *err; switch (opt) { case 'h': frr_help_exit(0); break; case 'v': print_version(di->progname); exit(0); break; case 'd': di->daemon_mode = 1; break; case 'M': oc = XMALLOC(MTYPE_TMP, sizeof(*oc)); oc->arg = optarg; oc->next = NULL; *modnext = oc; modnext = &oc->next; break; case 'i': if (di->flags & FRR_NO_CFG_PID_DRY) return 1; di->pid_file = optarg; break; case 'f': if (di->flags & FRR_NO_CFG_PID_DRY) return 1; di->config_file = optarg; break; case 'N': if (di->flags & FRR_NO_CFG_PID_DRY) return 1; if (di->pathspace) { fprintf(stderr, "-N/--pathspace option specified more than once!\n"); errors++; break; } if (di->zpathspace) fprintf(stderr, "-N option overridden by -z for zebra named socket path\n"); if (strchr(optarg, '/') || strchr(optarg, '.')) { fprintf(stderr, "slashes or dots are not permitted in the --pathspace option.\n"); errors++; break; } di->pathspace = optarg; if (!di->zpathspace) snprintf(frr_zclientpath, sizeof(frr_zclientpath), ZEBRA_SERV_PATH, "/", di->pathspace); snprintf(frr_vtydir, sizeof(frr_vtydir), DAEMON_VTY_DIR, "/", di->pathspace); snprintf(pidfile_default, sizeof(pidfile_default), "%s/%s.pid", frr_vtydir, di->name); break; #ifdef HAVE_SQLITE3 case OPTION_DB_FILE: if (di->flags & FRR_NO_CFG_PID_DRY) return 1; di->db_file = optarg; break; #endif case 'C': if (di->flags & FRR_NO_CFG_PID_DRY) return 1; di->dryrun = 1; break; case 't': if (di->flags & FRR_NO_CFG_PID_DRY) return 1; di->terminal = 1; break; case 'z': di->zpathspace = true; if (di->pathspace) fprintf(stderr, "-z option overrides -N option for zebra named socket path\n"); if (di->flags & FRR_NO_ZCLIENT) return 1; strlcpy(frr_zclientpath, optarg, sizeof(frr_zclientpath)); break; case 'A': if (di->flags & FRR_NO_TCPVTY) return 1; if (vty_addr_set) { fprintf(stderr, "-A option specified more than once!\n"); errors++; break; } vty_addr_set = 1; di->vty_addr = optarg; break; case 'P': if (di->flags & FRR_NO_TCPVTY) return 1; if (vty_port_set) { fprintf(stderr, "-P option specified more than once!\n"); errors++; break; } vty_port_set = 1; di->vty_port = strtoul(optarg, &err, 0); if (*err || !*optarg) { fprintf(stderr, "invalid port number \"%s\" for -P option\n", optarg); errors++; break; } break; case OPTION_VTYSOCK: if (di->vty_sock_path) { fprintf(stderr, "--vty_socket option specified more than once!\n"); errors++; break; } di->vty_sock_path = optarg; break; case OPTION_MODULEDIR: if (di->module_path) { fprintf(stderr, "----moduledir option specified more than once!\n"); errors++; break; } di->module_path = optarg; break; case OPTION_TCLI: di->cli_mode = FRR_CLI_TRANSACTIONAL; break; case 'u': if (di->flags & FRR_NO_PRIVSEP) return 1; di->privs->user = optarg; break; case 'g': if (di->flags & FRR_NO_PRIVSEP) return 1; di->privs->group = optarg; break; case OPTION_LOG: di->early_logging = optarg; break; case OPTION_LOGLEVEL: di->early_loglevel = optarg; break; case OPTION_LOGGING: di->log_always = true; break; default: return 1; } return 0; } int frr_getopt(int argc, char *const argv[], int *longindex) { int opt; int lidx; comb_next_lo->name = NULL; do { opt = getopt_long(argc, argv, comb_optstr, comb_lo, &lidx); if (frr_opt(opt)) break; } while (opt != -1); if (opt == -1 && errors) frr_help_exit(1); if (longindex) *longindex = lidx; return opt; } static void frr_mkdir(const char *path, bool strip) { char buf[256]; mode_t prev; int ret; struct zprivs_ids_t ids; if (strip) { char *slash = strrchr(path, '/'); size_t plen; if (!slash) return; plen = slash - path; if (plen > sizeof(buf) - 1) return; memcpy(buf, path, plen); buf[plen] = '\0'; path = buf; } /* o+rx (..5) is needed for the frrvty group to work properly; * without it, users in the frrvty group can't access the vty sockets. */ prev = umask(0022); ret = mkdir(path, 0755); umask(prev); if (ret != 0) { /* if EEXIST, return without touching the permissions, * so user-set custom permissions are left in place */ if (errno == EEXIST) return; flog_err(EC_LIB_SYSTEM_CALL, "failed to mkdir \"%s\": %s", path, strerror(errno)); return; } zprivs_get_ids(&ids); if (chown(path, ids.uid_normal, ids.gid_normal)) flog_err(EC_LIB_SYSTEM_CALL, "failed to chown \"%s\": %s", path, strerror(errno)); } static struct thread_master *master; struct thread_master *frr_init(void) { struct option_chain *oc; struct frrmod_runtime *module; char moderr[256]; char p_instance[16] = "", p_pathspace[256] = ""; const char *dir; dir = di->module_path ? di->module_path : frr_moduledir; srandom(time(NULL)); if (di->instance) { snprintf(frr_protonameinst, sizeof(frr_protonameinst), "%s[%u]", di->logname, di->instance); snprintf(p_instance, sizeof(p_instance), "-%d", di->instance); } if (di->pathspace) snprintf(p_pathspace, sizeof(p_pathspace), "%s/", di->pathspace); snprintf(config_default, sizeof(config_default), "%s%s%s%s.conf", frr_sysconfdir, p_pathspace, di->name, p_instance); snprintf(pidfile_default, sizeof(pidfile_default), "%s/%s%s.pid", frr_vtydir, di->name, p_instance); #ifdef HAVE_SQLITE3 snprintf(dbfile_default, sizeof(dbfile_default), "%s/%s%s%s.db", frr_dbdir, p_pathspace, di->name, p_instance); #endif zprivs_preinit(di->privs); openzlog(di->progname, di->logname, di->instance, LOG_CONS | LOG_NDELAY | LOG_PID, LOG_DAEMON); command_setup_early_logging(di->early_logging, di->early_loglevel); if (!frr_zclient_addr(&zclient_addr, &zclient_addr_len, frr_zclientpath)) { fprintf(stderr, "Invalid zserv socket path: %s\n", frr_zclientpath); exit(1); } /* don't mkdir these as root... */ if (!(di->flags & FRR_NO_PRIVSEP)) { if (!di->pid_file || !di->vty_path) frr_mkdir(frr_vtydir, false); if (di->pid_file) frr_mkdir(di->pid_file, true); if (di->vty_path) frr_mkdir(di->vty_path, true); } frrmod_init(di->module); while (modules) { modules = (oc = modules)->next; module = frrmod_load(oc->arg, dir, moderr, sizeof(moderr)); if (!module) { fprintf(stderr, "%s\n", moderr); exit(1); } XFREE(MTYPE_TMP, oc); } zprivs_init(di->privs); master = thread_master_create(NULL); signal_init(master, di->n_signals, di->signals); #ifdef HAVE_SQLITE3 if (!di->db_file) di->db_file = dbfile_default; db_init(di->db_file); #endif if (di->flags & FRR_LIMITED_CLI) cmd_init(-1); else cmd_init(1); vty_init(master, di->log_always); memory_init(); log_filter_cmd_init(); log_ref_init(); log_ref_vty_init(); lib_error_init(); yang_init(); debug_init_cli(); nb_init(master, di->yang_modules, di->n_yang_modules); if (nb_db_init() != NB_OK) flog_warn(EC_LIB_NB_DATABASE, "%s: failed to initialize northbound database", __func__); return master; } const char *frr_get_progname(void) { return di ? di->progname : NULL; } enum frr_cli_mode frr_get_cli_mode(void) { return di ? di->cli_mode : FRR_CLI_CLASSIC; } static int rcvd_signal = 0; static void rcv_signal(int signum) { rcvd_signal = signum; /* poll() is interrupted by the signal; handled below */ } static void frr_daemon_wait(int fd) { struct pollfd pfd[1]; int ret; pid_t exitpid; int exitstat; sigset_t sigs, prevsigs; sigemptyset(&sigs); sigaddset(&sigs, SIGTSTP); sigaddset(&sigs, SIGQUIT); sigaddset(&sigs, SIGINT); sigprocmask(SIG_BLOCK, &sigs, &prevsigs); struct sigaction sa = { .sa_handler = rcv_signal, .sa_flags = SA_RESETHAND, }; sigemptyset(&sa.sa_mask); sigaction(SIGTSTP, &sa, NULL); sigaction(SIGQUIT, &sa, NULL); sigaction(SIGINT, &sa, NULL); do { char buf[1]; ssize_t nrecv; pfd[0].fd = fd; pfd[0].events = POLLIN; rcvd_signal = 0; #if defined(HAVE_PPOLL) ret = ppoll(pfd, 1, NULL, &prevsigs); #elif defined(HAVE_POLLTS) ret = pollts(pfd, 1, NULL, &prevsigs); #else /* racy -- only used on FreeBSD 9 */ sigset_t tmpsigs; sigprocmask(SIG_SETMASK, &prevsigs, &tmpsigs); ret = poll(pfd, 1, -1); sigprocmask(SIG_SETMASK, &tmpsigs, NULL); #endif if (ret < 0 && errno != EINTR && errno != EAGAIN) { perror("poll()"); exit(1); } switch (rcvd_signal) { case SIGTSTP: send(fd, "S", 1, 0); do { nrecv = recv(fd, buf, sizeof(buf), 0); } while (nrecv == -1 && (errno == EINTR || errno == EAGAIN)); raise(SIGTSTP); sigaction(SIGTSTP, &sa, NULL); send(fd, "R", 1, 0); break; case SIGINT: send(fd, "I", 1, 0); break; case SIGQUIT: send(fd, "Q", 1, 0); break; } } while (ret <= 0); exitpid = waitpid(-1, &exitstat, WNOHANG); if (exitpid == 0) /* child successfully went to main loop & closed socket */ exit(0); /* child failed one way or another ... */ if (WIFEXITED(exitstat) && WEXITSTATUS(exitstat) == 0) /* can happen in --terminal case if exit is fast enough */ (void)0; else if (WIFEXITED(exitstat)) fprintf(stderr, "%s failed to start, exited %d\n", di->name, WEXITSTATUS(exitstat)); else if (WIFSIGNALED(exitstat)) fprintf(stderr, "%s crashed in startup, signal %d\n", di->name, WTERMSIG(exitstat)); else fprintf(stderr, "%s failed to start, unknown problem\n", di->name); exit(1); } static int daemon_ctl_sock = -1; static void frr_daemonize(void) { int fds[2]; pid_t pid; if (socketpair(AF_UNIX, SOCK_STREAM, 0, fds)) { perror("socketpair() for daemon control"); exit(1); } set_cloexec(fds[0]); set_cloexec(fds[1]); pid = fork(); if (pid < 0) { perror("fork()"); exit(1); } if (pid == 0) { /* child */ close(fds[0]); if (setsid() < 0) { perror("setsid()"); exit(1); } daemon_ctl_sock = fds[1]; return; } close(fds[1]); frr_daemon_wait(fds[0]); } /* * Why is this a thread? * * The read in of config for integrated config happens *after* * thread execution starts( because it is passed in via a vtysh -b -n ) * While if you are not using integrated config we want the ability * to read the config in after thread execution starts, so that * we can match this behavior. */ static int frr_config_read_in(struct thread *t) { if (!vty_read_config(NULL, di->config_file, config_default) && di->backup_config_file) { char *orig = XSTRDUP(MTYPE_TMP, host_config_get()); zlog_info("Attempting to read backup config file: %s specified", di->backup_config_file); vty_read_config(NULL, di->backup_config_file, config_default); host_config_set(orig); XFREE(MTYPE_TMP, orig); } /* * Update the shared candidate after reading the startup configuration. */ pthread_rwlock_rdlock(&running_config->lock); { nb_config_replace(vty_shared_candidate_config, running_config, true); } pthread_rwlock_unlock(&running_config->lock); return 0; } void frr_config_fork(void) { hook_call(frr_late_init, master); if (!(di->flags & FRR_NO_CFG_PID_DRY)) { /* Don't start execution if we are in dry-run mode */ if (di->dryrun) { frr_config_read_in(NULL); exit(0); } thread_add_event(master, frr_config_read_in, NULL, 0, &di->read_in); } if (di->daemon_mode || di->terminal) frr_daemonize(); if (!di->pid_file) di->pid_file = pidfile_default; pid_output(di->pid_file); } static void frr_vty_serv(void) { /* allow explicit override of vty_path in the future * (not currently set anywhere) */ if (!di->vty_path) { const char *dir; char defvtydir[256]; snprintf(defvtydir, sizeof(defvtydir), "%s", frr_vtydir); dir = di->vty_sock_path ? di->vty_sock_path : defvtydir; if (di->instance) snprintf(vtypath_default, sizeof(vtypath_default), "%s/%s-%d.vty", dir, di->name, di->instance); else snprintf(vtypath_default, sizeof(vtypath_default), "%s/%s.vty", dir, di->name); di->vty_path = vtypath_default; } vty_serv_sock(di->vty_addr, di->vty_port, di->vty_path); } static void frr_check_detach(void) { if (nodetach_term || nodetach_daemon) return; if (daemon_ctl_sock != -1) close(daemon_ctl_sock); daemon_ctl_sock = -1; } static void frr_terminal_close(int isexit) { int nullfd; nodetach_term = false; frr_check_detach(); if (!di->daemon_mode || isexit) { printf("\n%s exiting\n", di->name); if (!isexit) raise(SIGINT); return; } else { printf("\n%s daemonizing\n", di->name); fflush(stdout); } nullfd = open("/dev/null", O_RDONLY | O_NOCTTY); if (nullfd == -1) { flog_err_sys(EC_LIB_SYSTEM_CALL, "%s: failed to open /dev/null: %s", __func__, safe_strerror(errno)); } else { dup2(nullfd, 0); dup2(nullfd, 1); dup2(nullfd, 2); close(nullfd); } } static struct thread *daemon_ctl_thread = NULL; static int frr_daemon_ctl(struct thread *t) { char buf[1]; ssize_t nr; nr = recv(daemon_ctl_sock, buf, sizeof(buf), 0); if (nr < 0 && (errno == EINTR || errno == EAGAIN)) goto out; if (nr <= 0) return 0; switch (buf[0]) { case 'S': /* SIGTSTP */ vty_stdio_suspend(); if (send(daemon_ctl_sock, "s", 1, 0) < 0) zlog_err("%s send(\"s\") error (SIGTSTP propagation)", (di && di->name ? di->name : "")); break; case 'R': /* SIGTCNT [implicit] */ vty_stdio_resume(); break; case 'I': /* SIGINT */ di->daemon_mode = false; raise(SIGINT); break; case 'Q': /* SIGQUIT */ di->daemon_mode = true; vty_stdio_close(); break; } out: thread_add_read(master, frr_daemon_ctl, NULL, daemon_ctl_sock, &daemon_ctl_thread); return 0; } void frr_detach(void) { nodetach_daemon = false; frr_check_detach(); } void frr_run(struct thread_master *master) { char instanceinfo[64] = ""; frr_vty_serv(); if (di->instance) snprintf(instanceinfo, sizeof(instanceinfo), "instance %u ", di->instance); zlog_notice("%s %s starting: %svty@%d%s", di->name, FRR_VERSION, instanceinfo, di->vty_port, di->startinfo); if (di->terminal) { nodetach_term = true; vty_stdio(frr_terminal_close); if (daemon_ctl_sock != -1) { set_nonblocking(daemon_ctl_sock); thread_add_read(master, frr_daemon_ctl, NULL, daemon_ctl_sock, &daemon_ctl_thread); } } else if (di->daemon_mode) { int nullfd = open("/dev/null", O_RDONLY | O_NOCTTY); if (nullfd == -1) { flog_err_sys(EC_LIB_SYSTEM_CALL, "%s: failed to open /dev/null: %s", __func__, safe_strerror(errno)); } else { dup2(nullfd, 0); dup2(nullfd, 1); dup2(nullfd, 2); close(nullfd); } frr_check_detach(); } /* end fixed stderr startup logging */ zlog_startup_stderr = false; struct thread thread; while (thread_fetch(master, &thread)) thread_call(&thread); } void frr_early_fini(void) { hook_call(frr_early_fini); } void frr_fini(void) { FILE *fp; char filename[128]; int have_leftovers; hook_call(frr_fini); /* memory_init -> nothing needed */ vty_terminate(); cmd_terminate(); nb_terminate(); yang_terminate(); #ifdef HAVE_SQLITE3 db_close(); #endif log_ref_fini(); zprivs_terminate(di->privs); /* signal_init -> nothing needed */ thread_master_free(master); master = NULL; closezlog(); /* frrmod_init -> nothing needed / hooks */ rcu_shutdown(); if (!debug_memstats_at_exit) return; have_leftovers = log_memstats(stderr, di->name); /* in case we decide at runtime that we want exit-memstats for * a daemon, but it has no stderr because it's daemonized * (only do this if we actually have something to print though) */ if (!have_leftovers) return; snprintf(filename, sizeof(filename), "/tmp/frr-memstats-%s-%llu-%llu", di->name, (unsigned long long)getpid(), (unsigned long long)time(NULL)); fp = fopen(filename, "w"); if (fp) { log_memstats(fp, di->name); fclose(fp); } } #ifdef INTERP static const char interp[] __attribute__((section(".interp"), used)) = INTERP; #endif /* * executable entry point for libfrr.so * * note that libc initialization is skipped for this so the set of functions * that can be called is rather limited */ extern void _libfrr_version(void) __attribute__((visibility("hidden"), noreturn)); void _libfrr_version(void) { const char banner[] = FRR_FULL_NAME " " FRR_VERSION ".\n" FRR_COPYRIGHT GIT_INFO "\n" "configured with:\n " FRR_CONFIG_ARGS "\n"; write(1, banner, sizeof(banner) - 1); _exit(0); } frr-7.2.1/lib/libfrr.h0000644000000000000000000001145213610377563011416 00000000000000/* * libfrr overall management functions * * Copyright (C) 2016 David Lamparter for NetDEF, Inc. * * This program is free software; you can redistribute it and/or modify it * under the terms of the 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; see the file COPYING; if not, write to the Free Software * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA */ #ifndef _ZEBRA_FRR_H #define _ZEBRA_FRR_H #include "sigevent.h" #include "privs.h" #include "thread.h" #include "log.h" #include "getopt.h" #include "module.h" #include "hook.h" #include "northbound.h" #ifdef __cplusplus extern "C" { #endif /* The following options disable specific command line options that * are not applicable for a particular daemon. */ #define FRR_NO_PRIVSEP (1 << 0) #define FRR_NO_TCPVTY (1 << 1) #define FRR_LIMITED_CLI (1 << 2) #define FRR_NO_CFG_PID_DRY (1 << 3) #define FRR_NO_ZCLIENT (1 << 4) /* If FRR_DETACH_LATER is used, the daemon will keep its parent running * until frr_detach() is called. Normally "somedaemon -d" returns once the * main event loop is reached in the daemon; use this for extra startup bits. * * Does nothing if -d isn't used. */ #define FRR_DETACH_LATER (1 << 5) enum frr_cli_mode { FRR_CLI_CLASSIC = 0, FRR_CLI_TRANSACTIONAL, }; struct frr_daemon_info { unsigned flags; const char *progname; const char *name; const char *logname; unsigned short instance; struct frrmod_runtime *module; char *vty_addr; int vty_port; char *vty_sock_path; bool dryrun; bool daemon_mode; bool terminal; enum frr_cli_mode cli_mode; struct thread *read_in; const char *config_file; const char *backup_config_file; const char *pid_file; #ifdef HAVE_SQLITE3 const char *db_file; #endif const char *vty_path; const char *module_path; const char *pathspace; bool zpathspace; const char *early_logging; const char *early_loglevel; const char *proghelp; void (*printhelp)(FILE *target); const char *copyright; char startinfo[128]; struct quagga_signal_t *signals; size_t n_signals; struct zebra_privs_t *privs; const struct frr_yang_module_info **yang_modules; size_t n_yang_modules; bool log_always; }; /* execname is the daemon's executable (and pidfile and configfile) name, * i.e. "zebra" or "bgpd" * constname is the daemons source-level name, primarily for the logging ID, * i.e. "ZEBRA" or "BGP" * * note that this macro is also a latch-on point for other changes (e.g. * upcoming module support) that need to place some per-daemon things. Each * daemon should have one of these. */ #define FRR_DAEMON_INFO(execname, constname, ...) \ static struct frr_daemon_info execname##_di = {.name = #execname, \ .logname = #constname, \ .module = THIS_MODULE, \ __VA_ARGS__}; \ FRR_COREMOD_SETUP(.name = #execname, \ .description = #execname " daemon", \ .version = FRR_VERSION, ) \ /* end */ extern void frr_init_vtydir(void); extern void frr_preinit(struct frr_daemon_info *daemon, int argc, char **argv); extern void frr_opt_add(const char *optstr, const struct option *longopts, const char *helpstr); extern int frr_getopt(int argc, char *const argv[], int *longindex); extern void frr_help_exit(int status); extern struct thread_master *frr_init(void); extern const char *frr_get_progname(void); extern enum frr_cli_mode frr_get_cli_mode(void); DECLARE_HOOK(frr_late_init, (struct thread_master * tm), (tm)) extern void frr_config_fork(void); extern void frr_run(struct thread_master *master); extern void frr_detach(void); extern bool frr_zclient_addr(struct sockaddr_storage *sa, socklen_t *sa_len, const char *path); /* these two are before the protocol daemon does its own shutdown * it's named this way being the counterpart to frr_late_init */ DECLARE_KOOH(frr_early_fini, (), ()) extern void frr_early_fini(void); /* and these two are after the daemon did its own cleanup */ DECLARE_KOOH(frr_fini, (), ()) extern void frr_fini(void); extern char config_default[512]; extern char frr_zclientpath[256]; extern const char frr_sysconfdir[]; extern char frr_vtydir[256]; extern const char frr_moduledir[]; extern char frr_protoname[]; extern char frr_protonameinst[]; extern bool debug_memstats_at_exit; #ifdef __cplusplus } #endif #endif /* _ZEBRA_FRR_H */ frr-7.2.1/lib/libospf.h0000644000000000000000000000672413610377563011602 00000000000000/* * Defines and structures common to OSPFv2 and OSPFv3 * Copyright (C) 1998, 99, 2000 Kunihiro Ishiguro, Toshiaki Takada * * This file is part of GNU Zebra. * * GNU Zebra is free software; you can redistribute it and/or modify it * under the terms of the GNU General Public License as published by the * Free Software Foundation; either version 2, or (at your option) any * later version. * * GNU Zebra 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; see the file COPYING; if not, write to the Free Software * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA */ #ifndef _LIBOSPFD_H #define _LIBOSPFD_H #ifdef __cplusplus extern "C" { #endif /* IP precedence. */ #ifndef IPTOS_PREC_INTERNETCONTROL #define IPTOS_PREC_INTERNETCONTROL 0xC0 #endif /* IPTOS_PREC_INTERNETCONTROL */ /* Default protocol, port number. */ #ifndef IPPROTO_OSPFIGP #define IPPROTO_OSPFIGP 89 #endif /* IPPROTO_OSPFIGP */ /* Architectual Constants */ #ifdef DEBUG #define OSPF_LS_REFRESH_TIME 120 #else #define OSPF_LS_REFRESH_TIME 1800 #endif #define OSPF_MIN_LS_INTERVAL 5000 /* msec */ #define OSPF_MIN_LS_ARRIVAL 1000 /* in milliseconds */ #define OSPF_LSA_INITIAL_AGE 0 /* useful for debug */ #define OSPF_LSA_MAXAGE 3600 #define OSPF_CHECK_AGE 300 #define OSPF_LSA_MAXAGE_DIFF 900 #define OSPF_LS_INFINITY 0xffffff #define OSPF_DEFAULT_DESTINATION 0x00000000 /* 0.0.0.0 */ #define OSPF_INITIAL_SEQUENCE_NUMBER 0x80000001U #define OSPF_MAX_SEQUENCE_NUMBER 0x7fffffffU /* OSPF Interface Types */ #define OSPF_IFTYPE_NONE 0 #define OSPF_IFTYPE_POINTOPOINT 1 #define OSPF_IFTYPE_BROADCAST 2 #define OSPF_IFTYPE_NBMA 3 #define OSPF_IFTYPE_POINTOMULTIPOINT 4 #define OSPF_IFTYPE_VIRTUALLINK 5 #define OSPF_IFTYPE_LOOPBACK 6 #define OSPF_IFTYPE_MAX 7 /* OSPF interface default values. */ #define OSPF_OUTPUT_COST_DEFAULT 10 #define OSPF_OUTPUT_COST_INFINITE UINT16_MAX #define OSPF_ROUTER_DEAD_INTERVAL_DEFAULT 40 #define OSPF_ROUTER_DEAD_INTERVAL_MINIMAL 1 #define OSPF_HELLO_INTERVAL_DEFAULT 10 #define OSPF_ROUTER_PRIORITY_DEFAULT 1 #define OSPF_RETRANSMIT_INTERVAL_DEFAULT 5 #define OSPF_TRANSMIT_DELAY_DEFAULT 1 #define OSPF_DEFAULT_BANDWIDTH 10000 /* Mbps */ #define OSPF_DEFAULT_REF_BANDWIDTH 100000 /* Mbps */ #define OSPF_POLL_INTERVAL_DEFAULT 60 #define OSPF_NEIGHBOR_PRIORITY_DEFAULT 0 #define OSPF_MTU_IGNORE_DEFAULT 0 #define OSPF_FAST_HELLO_DEFAULT 0 #define OSPF_AREA_BACKBONE 0x00000000 /* 0.0.0.0 */ #define OSPF_AREA_RANGE_COST_UNSPEC -1U #define OSPF_AREA_DEFAULT 0 #define OSPF_AREA_STUB 1 #define OSPF_AREA_NSSA 2 #define OSPF_AREA_TYPE_MAX 3 /* SPF Throttling timer values. */ #define OSPF_SPF_DELAY_DEFAULT 0 #define OSPF_SPF_HOLDTIME_DEFAULT 50 #define OSPF_SPF_MAX_HOLDTIME_DEFAULT 5000 #define OSPF_LSA_MAXAGE_CHECK_INTERVAL 30 #define OSPF_LSA_MAXAGE_REMOVE_DELAY_DEFAULT 60 #ifdef __cplusplus } #endif #endif /* _LIBOSPFD_H */ frr-7.2.1/lib/linklist.c0000644000000000000000000001652413610377563011767 00000000000000/* Generic linked list routine. * Copyright (C) 1997, 2000 Kunihiro Ishiguro * * This file is part of GNU Zebra. * * GNU Zebra is free software; you can redistribute it and/or modify it * under the terms of the GNU General Public License as published by the * Free Software Foundation; either version 2, or (at your option) any * later version. * * GNU Zebra 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; see the file COPYING; if not, write to the Free Software * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA */ #include #include #include "linklist.h" #include "memory.h" DEFINE_MTYPE_STATIC(LIB, LINK_LIST, "Link List") DEFINE_MTYPE_STATIC(LIB, LINK_NODE, "Link Node") struct list *list_new(void) { return XCALLOC(MTYPE_LINK_LIST, sizeof(struct list)); } /* Free list. */ static void list_free_internal(struct list *l) { XFREE(MTYPE_LINK_LIST, l); } /* Allocate new listnode. Internal use only. */ static struct listnode *listnode_new(void) { return XCALLOC(MTYPE_LINK_NODE, sizeof(struct listnode)); } /* Free listnode. */ static void listnode_free(struct listnode *node) { XFREE(MTYPE_LINK_NODE, node); } struct listnode *listnode_add(struct list *list, void *val) { struct listnode *node; assert(val != NULL); node = listnode_new(); node->prev = list->tail; node->data = val; if (list->head == NULL) list->head = node; else list->tail->next = node; list->tail = node; list->count++; return node; } void listnode_add_head(struct list *list, void *val) { struct listnode *node; assert(val != NULL); node = listnode_new(); node->next = list->head; node->data = val; if (list->head == NULL) list->head = node; else list->head->prev = node; list->head = node; list->count++; } bool listnode_add_sort_nodup(struct list *list, void *val) { struct listnode *n; struct listnode *new; int ret; assert(val != NULL); if (list->cmp) { for (n = list->head; n; n = n->next) { ret = (*list->cmp)(val, n->data); if (ret < 0) { new = listnode_new(); new->data = val; new->next = n; new->prev = n->prev; if (n->prev) n->prev->next = new; else list->head = new; n->prev = new; list->count++; return true; } /* found duplicate return false */ if (ret == 0) return false; } } new = listnode_new(); new->data = val; LISTNODE_ATTACH(list, new); return true; } void listnode_add_sort(struct list *list, void *val) { struct listnode *n; struct listnode *new; assert(val != NULL); new = listnode_new(); new->data = val; if (list->cmp) { for (n = list->head; n; n = n->next) { if ((*list->cmp)(val, n->data) < 0) { new->next = n; new->prev = n->prev; if (n->prev) n->prev->next = new; else list->head = new; n->prev = new; list->count++; return; } } } new->prev = list->tail; if (list->tail) list->tail->next = new; else list->head = new; list->tail = new; list->count++; } struct listnode *listnode_add_after(struct list *list, struct listnode *pp, void *val) { struct listnode *nn; assert(val != NULL); nn = listnode_new(); nn->data = val; if (pp == NULL) { if (list->head) list->head->prev = nn; else list->tail = nn; nn->next = list->head; nn->prev = pp; list->head = nn; } else { if (pp->next) pp->next->prev = nn; else list->tail = nn; nn->next = pp->next; nn->prev = pp; pp->next = nn; } list->count++; return nn; } struct listnode *listnode_add_before(struct list *list, struct listnode *pp, void *val) { struct listnode *nn; assert(val != NULL); nn = listnode_new(); nn->data = val; if (pp == NULL) { if (list->tail) list->tail->next = nn; else list->head = nn; nn->prev = list->tail; nn->next = pp; list->tail = nn; } else { if (pp->prev) pp->prev->next = nn; else list->head = nn; nn->prev = pp->prev; nn->next = pp; pp->prev = nn; } list->count++; return nn; } void listnode_move_to_tail(struct list *l, struct listnode *n) { LISTNODE_DETACH(l, n); LISTNODE_ATTACH(l, n); } void listnode_delete(struct list *list, const void *val) { struct listnode *node = listnode_lookup(list, val); if (node) list_delete_node(list, node); } void *listnode_head(struct list *list) { struct listnode *node; assert(list); node = list->head; if (node) return node->data; return NULL; } void list_delete_all_node(struct list *list) { struct listnode *node; struct listnode *next; assert(list); for (node = list->head; node; node = next) { next = node->next; if (*list->del) (*list->del)(node->data); listnode_free(node); } list->head = list->tail = NULL; list->count = 0; } void list_filter_out_nodes(struct list *list, bool (*cond)(void *data)) { struct listnode *node; struct listnode *next; void *data; assert(list); for (ALL_LIST_ELEMENTS(list, node, next, data)) { if ((cond && cond(data)) || (!cond)) { if (*list->del) (*list->del)(data); list_delete_node(list, node); } } } void list_delete(struct list **list) { assert(*list); list_delete_all_node(*list); list_free_internal(*list); *list = NULL; } struct listnode *listnode_lookup(struct list *list, const void *data) { struct listnode *node; assert(list); for (node = listhead(list); node; node = listnextnode(node)) if (data == listgetdata(node)) return node; return NULL; } struct listnode *listnode_lookup_nocheck(struct list *list, void *data) { if (!list) return NULL; return listnode_lookup(list, data); } void list_delete_node(struct list *list, struct listnode *node) { if (node->prev) node->prev->next = node->next; else list->head = node->next; if (node->next) node->next->prev = node->prev; else list->tail = node->prev; list->count--; listnode_free(node); } void list_add_list(struct list *list, struct list *add) { struct listnode *n; for (n = listhead(add); n; n = listnextnode(n)) listnode_add(list, n->data); } struct list *list_dup(struct list *list) { struct list *new = list_new(); struct listnode *ln; void *data; new->cmp = list->cmp; new->del = list->del; for (ALL_LIST_ELEMENTS_RO(list, ln, data)) listnode_add(new, data); return new; } void list_sort(struct list *list, int (*cmp)(const void **, const void **)) { struct listnode *ln, *nn; int i = -1; void *data; size_t n = list->count; void **items = XCALLOC(MTYPE_TMP, (sizeof(void *)) * n); int (*realcmp)(const void *, const void *) = (int (*)(const void *, const void *))cmp; for (ALL_LIST_ELEMENTS(list, ln, nn, data)) { items[++i] = data; list_delete_node(list, ln); } qsort(items, n, sizeof(void *), realcmp); for (unsigned int j = 0; j < n; ++j) listnode_add(list, items[j]); XFREE(MTYPE_TMP, items); } struct listnode *listnode_add_force(struct list **list, void *val) { if (*list == NULL) *list = list_new(); return listnode_add(*list, val); } void **list_to_array(struct list *list, void **arr, size_t arrlen) { struct listnode *ln; void *vp; size_t idx = 0; for (ALL_LIST_ELEMENTS_RO(list, ln, vp)) { arr[idx++] = vp; if (idx == arrlen) break; } return arr; } frr-7.2.1/lib/linklist.h0000644000000000000000000002561413610377563011774 00000000000000/* Generic linked list * Copyright (C) 1997, 2000 Kunihiro Ishiguro * * This file is part of GNU Zebra. * * GNU Zebra is free software; you can redistribute it and/or modify it * under the terms of the GNU General Public License as published by the * Free Software Foundation; either version 2, or (at your option) any * later version. * * GNU Zebra 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; see the file COPYING; if not, write to the Free Software * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA */ #ifndef _ZEBRA_LINKLIST_H #define _ZEBRA_LINKLIST_H #ifdef __cplusplus extern "C" { #endif /* listnodes must always contain data to be valid. Adding an empty node * to a list is invalid */ struct listnode { struct listnode *next; struct listnode *prev; /* private member, use getdata() to retrieve, do not access directly */ void *data; }; struct list { struct listnode *head; struct listnode *tail; /* invariant: count is the number of listnodes in the list */ unsigned int count; /* * Returns -1 if val1 < val2, 0 if equal?, 1 if val1 > val2. * Used as definition of sorted for listnode_add_sort */ int (*cmp)(void *val1, void *val2); /* callback to free user-owned data when listnode is deleted. supplying * this callback is very much encouraged! */ void (*del)(void *val); }; #define listnextnode(X) ((X) ? ((X)->next) : NULL) #define listnextnode_unchecked(X) ((X)->next) #define listhead(X) ((X) ? ((X)->head) : NULL) #define listhead_unchecked(X) ((X)->head) #define listtail(X) ((X) ? ((X)->tail) : NULL) #define listcount(X) ((X)->count) #define list_isempty(X) ((X)->head == NULL && (X)->tail == NULL) /* return X->data only if X and X->data are not NULL */ #define listgetdata(X) (assert(X), assert((X)->data != NULL), (X)->data) /* * Create a new linked list. * * Returns: * the created linked list */ extern struct list *list_new(void); /* * Add a new element to the tail of a list. * * Runtime is O(1). * * list * list to operate on * * data * element to add */ extern struct listnode *listnode_add(struct list *list, void *data); /* * Add a new element to the beginning of a list. * * Runtime is O(1). * * list * list to operate on * * data * element to add */ extern void listnode_add_head(struct list *list, void *data); /* * Insert a new element into a list with insertion sort. * * If list->cmp is set, this function is used to determine the position to * insert the new element. If it is not set, this function is equivalent to * listnode_add. * * Runtime is O(N). * * list * list to operate on * * val * element to add */ extern void listnode_add_sort(struct list *list, void *val); /* * Insert a new element into a list after another element. * * Runtime is O(1). * * list * list to operate on * * pp * listnode to insert after * * data * data to insert * * Returns: * pointer to newly created listnode that contains the inserted data */ extern struct listnode *listnode_add_after(struct list *list, struct listnode *pp, void *data); /* * Insert a new element into a list before another element. * * Runtime is O(1). * * list * list to operate on * * pp * listnode to insert before * * data * data to insert * * Returns: * pointer to newly created listnode that contains the inserted data */ extern struct listnode *listnode_add_before(struct list *list, struct listnode *pp, void *data); /* * Move a node to the tail of a list. * * Runtime is O(1). * * list * list to operate on * * node * node to move to tail */ extern void listnode_move_to_tail(struct list *list, struct listnode *node); /* * Delete an element from a list. * * Runtime is O(N). * * list * list to operate on * * data * data to insert into list */ extern void listnode_delete(struct list *list, const void *data); /* * Find the listnode corresponding to an element in a list. * * list * list to operate on * * data * data to search for * * Returns: * pointer to listnode storing the given data if found, NULL otherwise */ extern struct listnode *listnode_lookup(struct list *list, const void *data); /* * Retrieve the element at the head of a list. * * list * list to operate on * * Returns: * data at head of list, or NULL if list is empty */ extern void *listnode_head(struct list *list); /* * Duplicate a list. * * list * list to duplicate * * Returns: * copy of the list */ extern struct list *list_dup(struct list *l); /* * Sort a list in place. * * The sorting algorithm used is quicksort. Runtimes are equivalent to those of * quicksort plus N. The sort is not stable. * * For portability reasons, the comparison function takes a pointer to pointer * to void. This pointer should be dereferenced to get the actual data pointer. * It is always safe to do this. * * list * list to sort * * cmp * comparison function for quicksort. Should return less than, equal to or * greater than zero if the first argument is less than, equal to or greater * than the second argument. */ extern void list_sort(struct list *list, int (*cmp)(const void **, const void **)); /* * Convert a list to an array of void pointers. * * Starts from the list head and ends either on the last node of the list or * when the provided array cannot store any more elements. * * list * list to convert * * arr * Pre-allocated array of void * * * arrlen * Number of elements in arr * * Returns: * arr */ void **list_to_array(struct list *list, void **arr, size_t arrlen); /* * Delete a list and NULL its pointer. * * If non-null, list->del is called with each data element. * * plist * pointer to list pointer; this will be set to NULL after the list has been * deleted */ extern void list_delete(struct list **plist); /* * Delete all nodes from a list without deleting the list itself. * * If non-null, list->del is called with each data element. * * list * list to operate on */ extern void list_delete_all_node(struct list *list); /* * Delete a node from a list. * * list->del is not called with the data associated with the node. * * Runtime is O(1). * * list * list to operate on * * node * the node to delete */ extern void list_delete_node(struct list *list, struct listnode *node); /* * Append a list to an existing list. * * Runtime is O(N) where N = listcount(add). * * list * list to append to * * add * list to append */ extern void list_add_list(struct list *list, struct list *add); /* * Delete all nodes which satisfy a condition from a list. * Deletes the node if cond function returns true for the node. * If function ptr passed is NULL, it deletes all nodes * * list * list to operate on * cond * function pointer which takes node data as input and return true or false */ extern void list_filter_out_nodes(struct list *list, bool (*cond)(void *data)); /* * Insert a new element into a list with insertion sort if there is no * duplicate element present in the list. This assumes the input list is * sorted. If unsorted, it will check for duplicate until it finds out * the position to do insertion sort with the unsorted list. * * If list->cmp is set, this function is used to determine the position to * insert the new element. If it is not set, this function is equivalent to * listnode_add. duplicate element is determined by cmp function returning 0. * * Runtime is O(N). * * list * list to operate on * * val * element to add */ extern bool listnode_add_sort_nodup(struct list *list, void *val); /* List iteration macro. * Usage: for (ALL_LIST_ELEMENTS (...) { ... } * It is safe to delete the listnode using this macro. */ #define ALL_LIST_ELEMENTS(list, node, nextnode, data) \ (node) = listhead(list), ((data) = NULL); \ (node) != NULL \ && ((data) = static_cast(data, listgetdata(node)), \ (nextnode) = node->next, 1); \ (node) = (nextnode), ((data) = NULL) /* read-only list iteration macro. * Usage: as per ALL_LIST_ELEMENTS, but not safe to delete the listnode Only * use this macro when it is *immediately obvious* the listnode is not * deleted in the body of the loop. Does not have forward-reference overhead * of previous macro. */ #define ALL_LIST_ELEMENTS_RO(list, node, data) \ (node) = listhead(list), ((data) = NULL); \ (node) != NULL && ((data) = static_cast(data, listgetdata(node)), 1); \ (node) = listnextnode(node), ((data) = NULL) /* these *do not* cleanup list nodes and referenced data, as the functions * do - these macros simply {de,at}tach a listnode from/to a list. */ /* List node attach macro. */ #define LISTNODE_ATTACH(L, N) \ do { \ (N)->prev = (L)->tail; \ (N)->next = NULL; \ if ((L)->head == NULL) \ (L)->head = (N); \ else \ (L)->tail->next = (N); \ (L)->tail = (N); \ (L)->count++; \ } while (0) /* List node detach macro. */ #define LISTNODE_DETACH(L, N) \ do { \ if ((N)->prev) \ (N)->prev->next = (N)->next; \ else \ (L)->head = (N)->next; \ if ((N)->next) \ (N)->next->prev = (N)->prev; \ else \ (L)->tail = (N)->prev; \ (L)->count--; \ } while (0) extern struct listnode *listnode_lookup_nocheck(struct list *list, void *data); /* * Add a node to *list, if non-NULL. Otherwise, allocate a new list, mail * it back in *list, and add a new node. * * Return: the new node. */ extern struct listnode *listnode_add_force(struct list **list, void *val); #ifdef __cplusplus } #endif #endif /* _ZEBRA_LINKLIST_H */ frr-7.2.1/lib/log.c0000644000000000000000000010416313610377563010714 00000000000000/* * Logging of zebra * Copyright (C) 1997, 1998, 1999 Kunihiro Ishiguro * * This file is part of GNU Zebra. * * GNU Zebra is free software; you can redistribute it and/or modify it * under the terms of the GNU General Public License as published by the * Free Software Foundation; either version 2, or (at your option) any * later version. * * GNU Zebra 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; see the file COPYING; if not, write to the Free Software * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA */ #define FRR_DEFINE_DESC_TABLE #include #include "zclient.h" #include "log.h" #include "log_int.h" #include "memory.h" #include "command.h" #include "lib_errors.h" #include "lib/hook.h" #include "printfrr.h" #include "frr_pthread.h" #ifndef SUNOS_5 #include #endif /* for printstack on solaris */ #ifdef HAVE_UCONTEXT_H #include #endif #ifdef HAVE_LIBUNWIND #define UNW_LOCAL_ONLY #include #include #endif DEFINE_MTYPE_STATIC(LIB, ZLOG, "Logging") /* hook for external logging */ DEFINE_HOOK(zebra_ext_log, (int priority, const char *format, va_list args), (priority, format, args)); static int logfile_fd = -1; /* Used in signal handler. */ struct zlog *zlog_default = NULL; bool zlog_startup_stderr = true; /* lock protecting zlog_default for mt-safe zlog */ static pthread_mutex_t loglock = PTHREAD_MUTEX_INITIALIZER; const char *zlog_priority[] = { "emergencies", "alerts", "critical", "errors", "warnings", "notifications", "informational", "debugging", NULL, }; static char zlog_filters[ZLOG_FILTERS_MAX][ZLOG_FILTER_LENGTH_MAX + 1]; static uint8_t zlog_filter_count; /* * look for a match on the filter in the current filters, loglock must be held */ static int zlog_filter_lookup(const char *lookup) { for (int i = 0; i < zlog_filter_count; i++) { if (strncmp(lookup, zlog_filters[i], sizeof(zlog_filters[0])) == 0) return i; } return -1; } void zlog_filter_clear(void) { frr_with_mutex(&loglock) { zlog_filter_count = 0; } } int zlog_filter_add(const char *filter) { frr_with_mutex(&loglock) { if (zlog_filter_count >= ZLOG_FILTERS_MAX) return 1; if (zlog_filter_lookup(filter) != -1) /* Filter already present */ return -1; strlcpy(zlog_filters[zlog_filter_count], filter, sizeof(zlog_filters[0])); if (zlog_filters[zlog_filter_count][0] == '\0') /* Filter was either empty or didn't get copied * correctly */ return -1; zlog_filter_count++; } return 0; } int zlog_filter_del(const char *filter) { frr_with_mutex(&loglock) { int found_idx = zlog_filter_lookup(filter); int last_idx = zlog_filter_count - 1; if (found_idx == -1) /* Didn't find the filter to delete */ return -1; /* Adjust the filter array */ memmove(zlog_filters[found_idx], zlog_filters[found_idx + 1], (last_idx - found_idx) * sizeof(zlog_filters[0])); zlog_filter_count--; } return 0; } /* Dump all filters to buffer, delimited by new line */ int zlog_filter_dump(char *buf, size_t max_size) { int len = 0; frr_with_mutex(&loglock) { for (int i = 0; i < zlog_filter_count; i++) { int ret; ret = snprintf(buf + len, max_size - len, " %s\n", zlog_filters[i]); len += ret; if ((ret < 0) || ((size_t)len >= max_size)) return -1; } } return len; } /* * write_wrapper * * glibc has declared that the return value from write *must* not be * ignored. * gcc see's this problem and issues a warning for the line. * * Why is this a big deal you say? Because both of them are right * and if you have -Werror enabled then all calls to write * generate a build error and the build stops. * * clang has helpfully allowed this construct: * (void)write(...) * to tell the compiler yeah I know it has a return value * I don't care about it at this time. * gcc doesn't have this ability. * * This code was written such that it didn't care about the * return value from write. At this time do I want * to go through and fix and test this code for correctness. * So just wrapper the bad behavior and move on. */ static void write_wrapper(int fd, const void *buf, size_t count) { if (write(fd, buf, count) <= 0) return; return; } /** * Looks up a message in a message list by key. * * If the message is not found, returns the provided error message. * * Terminates when it hits a struct message that's all zeros. * * @param mz the message list * @param kz the message key * @param nf the message to return if not found * @return the message */ const char *lookup_msg(const struct message *mz, int kz, const char *nf) { static struct message nt = {0}; const char *rz = nf ? nf : "(no message found)"; const struct message *pnt; for (pnt = mz; memcmp(pnt, &nt, sizeof(struct message)); pnt++) if (pnt->key == kz) { rz = pnt->str ? pnt->str : rz; break; } return rz; } /* For time string format. */ size_t quagga_timestamp(int timestamp_precision, char *buf, size_t buflen) { static struct { time_t last; size_t len; char buf[28]; } cache; struct timeval clock; gettimeofday(&clock, NULL); /* first, we update the cache if the time has changed */ if (cache.last != clock.tv_sec) { struct tm *tm; cache.last = clock.tv_sec; tm = localtime(&cache.last); cache.len = strftime(cache.buf, sizeof(cache.buf), "%Y/%m/%d %H:%M:%S", tm); } /* note: it's not worth caching the subsecond part, because chances are that back-to-back calls are not sufficiently close together for the clock not to have ticked forward */ if (buflen > cache.len) { memcpy(buf, cache.buf, cache.len); if ((timestamp_precision > 0) && (buflen > cache.len + 1 + timestamp_precision)) { /* should we worry about locale issues? */ static const int divisor[] = {0, 100000, 10000, 1000, 100, 10, 1}; int prec; char *p = buf + cache.len + 1 + (prec = timestamp_precision); *p-- = '\0'; while (prec > 6) /* this is unlikely to happen, but protect anyway */ { *p-- = '0'; prec--; } clock.tv_usec /= divisor[prec]; do { *p-- = '0' + (clock.tv_usec % 10); clock.tv_usec /= 10; } while (--prec > 0); *p = '.'; return cache.len + 1 + timestamp_precision; } buf[cache.len] = '\0'; return cache.len; } if (buflen > 0) buf[0] = '\0'; return 0; } static inline void timestamp_control_render(struct timestamp_control *ctl) { if (!ctl->already_rendered) { ctl->len = quagga_timestamp(ctl->precision, ctl->buf, sizeof(ctl->buf)); ctl->already_rendered = 1; } } /* Utility routine for current time printing. */ static void time_print(FILE *fp, struct timestamp_control *ctl) { timestamp_control_render(ctl); fprintf(fp, "%s ", ctl->buf); } static int time_print_buf(char *buf, int len, int max_size, struct timestamp_control *ctl) { timestamp_control_render(ctl); if (ctl->len + 1 >= (unsigned long)max_size) return -1; return snprintf(buf + len, max_size - len, "%s ", ctl->buf); } static void vzlog_file(struct zlog *zl, struct timestamp_control *tsctl, const char *proto_str, int record_priority, int priority, FILE *fp, const char *msg) { time_print(fp, tsctl); if (record_priority) fprintf(fp, "%s: ", zlog_priority[priority]); fprintf(fp, "%s%s\n", proto_str, msg); fflush(fp); } /* Search a buf for the filter strings, loglock must be held */ static int search_buf(const char *buf) { char *found = NULL; for (int i = 0; i < zlog_filter_count; i++) { found = strstr(buf, zlog_filters[i]); if (found != NULL) return 0; } return -1; } /* Filter out a log */ static int vzlog_filter(struct zlog *zl, struct timestamp_control *tsctl, const char *proto_str, int priority, const char *msg) { int len = 0; int ret = 0; char buf[1024] = ""; ret = time_print_buf(buf, len, sizeof(buf), tsctl); len += ret; if ((ret < 0) || ((size_t)len >= sizeof(buf))) goto search; if (zl && zl->record_priority) snprintf(buf + len, sizeof(buf) - len, "%s: %s: %s", zlog_priority[priority], proto_str, msg); else snprintf(buf + len, sizeof(buf) - len, "%s: %s", proto_str, msg); search: return search_buf(buf); } /* va_list version of zlog. */ void vzlog(int priority, const char *format, va_list args) { frr_mutex_lock_autounlock(&loglock); char proto_str[32] = ""; int original_errno = errno; struct timestamp_control tsctl = {}; tsctl.already_rendered = 0; struct zlog *zl = zlog_default; char buf[256], *msg; if (zl == NULL) { tsctl.precision = 0; } else { tsctl.precision = zl->timestamp_precision; if (zl->instance) sprintf(proto_str, "%s[%d]: ", zl->protoname, zl->instance); else sprintf(proto_str, "%s: ", zl->protoname); } msg = vasnprintfrr(MTYPE_TMP, buf, sizeof(buf), format, args); /* If it doesn't match on a filter, do nothing with the debug log */ if ((priority == LOG_DEBUG) && zlog_filter_count && vzlog_filter(zl, &tsctl, proto_str, priority, msg)) goto out; /* call external hook */ hook_call(zebra_ext_log, priority, format, args); /* When zlog_default is also NULL, use stderr for logging. */ if (zl == NULL) { time_print(stderr, &tsctl); fprintf(stderr, "%s: %s\n", "unknown", msg); fflush(stderr); goto out; } /* Syslog output */ if (priority <= zl->maxlvl[ZLOG_DEST_SYSLOG]) syslog(priority | zlog_default->facility, "%s", msg); /* File output. */ if ((priority <= zl->maxlvl[ZLOG_DEST_FILE]) && zl->fp) vzlog_file(zl, &tsctl, proto_str, zl->record_priority, priority, zl->fp, msg); /* fixed-config logging to stderr while we're stating up & haven't * daemonized / reached mainloop yet * * note the "else" on stdout output -- we don't want to print the same * message to both stderr and stdout. */ if (zlog_startup_stderr && priority <= LOG_WARNING) vzlog_file(zl, &tsctl, proto_str, 1, priority, stderr, msg); else if (priority <= zl->maxlvl[ZLOG_DEST_STDOUT]) vzlog_file(zl, &tsctl, proto_str, zl->record_priority, priority, stdout, msg); /* Terminal monitor. */ if (priority <= zl->maxlvl[ZLOG_DEST_MONITOR]) vty_log((zl->record_priority ? zlog_priority[priority] : NULL), proto_str, msg, &tsctl); out: if (msg != buf) XFREE(MTYPE_TMP, msg); errno = original_errno; } int vzlog_test(int priority) { frr_mutex_lock_autounlock(&loglock); struct zlog *zl = zlog_default; /* When zlog_default is also NULL, use stderr for logging. */ if (zl == NULL) return 1; /* Syslog output */ else if (priority <= zl->maxlvl[ZLOG_DEST_SYSLOG]) return 1; /* File output. */ else if ((priority <= zl->maxlvl[ZLOG_DEST_FILE]) && zl->fp) return 1; /* stdout output. */ else if (priority <= zl->maxlvl[ZLOG_DEST_STDOUT]) return 1; /* Terminal monitor. */ else if (priority <= zl->maxlvl[ZLOG_DEST_MONITOR]) return 1; return 0; } /* * crash handling * * NB: only AS-Safe (async-signal) functions can be used here! */ /* Needs to be enhanced to support Solaris. */ static int syslog_connect(void) { #ifdef SUNOS_5 return -1; #else int fd; struct sockaddr_un addr; if ((fd = socket(AF_UNIX, SOCK_DGRAM, 0)) < 0) return -1; addr.sun_family = AF_UNIX; #ifdef _PATH_LOG #define SYSLOG_SOCKET_PATH _PATH_LOG #else #define SYSLOG_SOCKET_PATH "/dev/log" #endif strlcpy(addr.sun_path, SYSLOG_SOCKET_PATH, sizeof(addr.sun_path)); #undef SYSLOG_SOCKET_PATH if (connect(fd, (struct sockaddr *)&addr, sizeof(addr)) < 0) { close(fd); return -1; } return fd; #endif } static void syslog_sigsafe(int priority, const char *msg, size_t msglen) { static int syslog_fd = -1; char buf[sizeof("<1234567890>ripngd[1234567890]: ") + msglen + 50]; struct fbuf fb = { .buf = buf, .pos = buf, .len = sizeof(buf) }; if ((syslog_fd < 0) && ((syslog_fd = syslog_connect()) < 0)) return; /* forget about the timestamp, too difficult in a signal handler */ bprintfrr(&fb, "<%d>%s", priority, zlog_default->ident); if (zlog_default->syslog_options & LOG_PID) bprintfrr(&fb, "[%ld]", (long)getpid()); bprintfrr(&fb, ": %s", msg); write_wrapper(syslog_fd, fb.buf, fb.pos - fb.buf); } static int open_crashlog(void) { char crashlog_buf[PATH_MAX]; const char *crashlog_default = "/var/tmp/frr.crashlog", *crashlog; if (!zlog_default || !zlog_default->ident) crashlog = crashlog_default; else { snprintfrr(crashlog_buf, sizeof(crashlog_buf), "/var/tmp/frr.%s.crashlog", zlog_default->ident); crashlog = crashlog_buf; } return open(crashlog, O_WRONLY | O_CREAT | O_EXCL, LOGFILE_MASK); } /* N.B. implicit priority is most severe */ #define PRI LOG_CRIT static void crash_write(struct fbuf *fb, char *msgstart) { if (fb->pos == fb->buf) return; if (!msgstart) msgstart = fb->buf; /* If no file logging configured, try to write to fallback log file. */ if ((logfile_fd >= 0) || ((logfile_fd = open_crashlog()) >= 0)) write(logfile_fd, fb->buf, fb->pos - fb->buf); if (!zlog_default) write(STDERR_FILENO, fb->buf, fb->pos - fb->buf); else { if (PRI <= zlog_default->maxlvl[ZLOG_DEST_STDOUT]) write(STDOUT_FILENO, fb->buf, fb->pos - fb->buf); /* Remove trailing '\n' for monitor and syslog */ fb->pos--; if (PRI <= zlog_default->maxlvl[ZLOG_DEST_MONITOR]) vty_log_fixed(fb->buf, fb->pos - fb->buf); if (PRI <= zlog_default->maxlvl[ZLOG_DEST_SYSLOG]) syslog_sigsafe(PRI | zlog_default->facility, msgstart, fb->pos - msgstart); } } /* Note: the goal here is to use only async-signal-safe functions. */ void zlog_signal(int signo, const char *action, void *siginfo_v, void *program_counter) { siginfo_t *siginfo = siginfo_v; time_t now; char buf[sizeof("DEFAULT: Received signal S at T (si_addr 0xP, PC 0xP); aborting...") + 100]; char *msgstart; struct fbuf fb = { .buf = buf, .pos = buf, .len = sizeof(buf) }; time(&now); if (zlog_default) bprintfrr(&fb, "%s: ", zlog_default->protoname); msgstart = fb.pos; bprintfrr(&fb, "Received signal %d at %lld", signo, (long long)now); if (program_counter) bprintfrr(&fb, " (si_addr 0x%tx, PC 0x%tx)", (ptrdiff_t)siginfo->si_addr, (ptrdiff_t)program_counter); else bprintfrr(&fb, " (si_addr 0x%tx)", (ptrdiff_t)siginfo->si_addr); bprintfrr(&fb, "; %s\n", action); crash_write(&fb, msgstart); zlog_backtrace_sigsafe(PRI, program_counter); fb.pos = buf; struct thread *tc; tc = pthread_getspecific(thread_current); if (!tc) bprintfrr(&fb, "no thread information available\n"); else bprintfrr(&fb, "in thread %s scheduled from %s:%d\n", tc->funcname, tc->schedfrom, tc->schedfrom_line); crash_write(&fb, NULL); } /* Log a backtrace using only async-signal-safe functions. Needs to be enhanced to support syslog logging. */ void zlog_backtrace_sigsafe(int priority, void *program_counter) { #ifdef HAVE_LIBUNWIND char buf[256]; struct fbuf fb = { .buf = buf, .len = sizeof(buf) }; unw_cursor_t cursor; unw_context_t uc; unw_word_t ip, off, sp; Dl_info dlinfo; unw_getcontext(&uc); unw_init_local(&cursor, &uc); while (unw_step(&cursor) > 0) { char name[128] = "?"; unw_get_reg(&cursor, UNW_REG_IP, &ip); unw_get_reg(&cursor, UNW_REG_SP, &sp); if (!unw_get_proc_name(&cursor, buf, sizeof(buf), &off)) snprintfrr(name, sizeof(name), "%s+%#lx", buf, (long)off); fb.pos = buf; if (unw_is_signal_frame(&cursor)) bprintfrr(&fb, " ---- signal ----\n"); bprintfrr(&fb, "%-30s %16lx %16lx", name, (long)ip, (long)sp); if (dladdr((void *)ip, &dlinfo)) bprintfrr(&fb, " %s (mapped at %p)", dlinfo.dli_fname, dlinfo.dli_fbase); bprintfrr(&fb, "\n"); crash_write(&fb, NULL); } #elif defined(HAVE_GLIBC_BACKTRACE) || defined(HAVE_PRINTSTACK) static const char pclabel[] = "Program counter: "; void *array[64]; int size; char buf[128]; struct fbuf fb = { .buf = buf, .pos = buf, .len = sizeof(buf) }; char **bt = NULL; #ifdef HAVE_GLIBC_BACKTRACE size = backtrace(array, array_size(array)); if (size <= 0 || (size_t)size > array_size(array)) return; #define DUMP(FD) \ { \ if (program_counter) { \ write_wrapper(FD, pclabel, sizeof(pclabel) - 1); \ backtrace_symbols_fd(&program_counter, 1, FD); \ } \ write_wrapper(FD, fb.buf, fb.pos - fb.buf); \ backtrace_symbols_fd(array, size, FD); \ } #elif defined(HAVE_PRINTSTACK) size = 0; #define DUMP(FD) \ { \ if (program_counter) \ write_wrapper((FD), pclabel, sizeof(pclabel) - 1); \ write_wrapper((FD), fb.buf, fb.pos - fb.buf); \ printstack((FD)); \ } #endif /* HAVE_GLIBC_BACKTRACE, HAVE_PRINTSTACK */ bprintfrr(&fb, "Backtrace for %d stack frames:\n", size); if ((logfile_fd >= 0) || ((logfile_fd = open_crashlog()) >= 0)) DUMP(logfile_fd) if (!zlog_default) DUMP(STDERR_FILENO) else { if (priority <= zlog_default->maxlvl[ZLOG_DEST_STDOUT]) DUMP(STDOUT_FILENO) /* Remove trailing '\n' for monitor and syslog */ fb.pos--; if (priority <= zlog_default->maxlvl[ZLOG_DEST_MONITOR]) vty_log_fixed(fb.buf, fb.pos - fb.buf); if (priority <= zlog_default->maxlvl[ZLOG_DEST_SYSLOG]) syslog_sigsafe(priority | zlog_default->facility, fb.buf, fb.pos - fb.buf); { int i; #ifdef HAVE_GLIBC_BACKTRACE bt = backtrace_symbols(array, size); #endif /* Just print the function addresses. */ for (i = 0; i < size; i++) { fb.pos = buf; if (bt) bprintfrr(&fb, "%s", bt[i]); else bprintfrr(&fb, "[bt %d] 0x%tx", i, (ptrdiff_t)(array[i])); if (priority <= zlog_default->maxlvl[ZLOG_DEST_MONITOR]) vty_log_fixed(fb.buf, fb.pos - fb.buf); if (priority <= zlog_default->maxlvl[ZLOG_DEST_SYSLOG]) syslog_sigsafe(priority | zlog_default->facility, fb.buf, fb.pos - fb.buf); } if (bt) free(bt); } } #undef DUMP #endif /* HAVE_STRACK_TRACE */ } void zlog_backtrace(int priority) { #ifdef HAVE_LIBUNWIND char buf[100]; unw_cursor_t cursor; unw_context_t uc; unw_word_t ip, off, sp; Dl_info dlinfo; unw_getcontext(&uc); unw_init_local(&cursor, &uc); zlog(priority, "Backtrace:"); while (unw_step(&cursor) > 0) { char name[128] = "?"; unw_get_reg(&cursor, UNW_REG_IP, &ip); unw_get_reg(&cursor, UNW_REG_SP, &sp); if (unw_is_signal_frame(&cursor)) zlog(priority, " ---- signal ----"); if (!unw_get_proc_name(&cursor, buf, sizeof(buf), &off)) snprintf(name, sizeof(name), "%s+%#lx", buf, (long)off); if (dladdr((void *)ip, &dlinfo)) zlog(priority, "%-30s %16lx %16lx %s (mapped at %p)", name, (long)ip, (long)sp, dlinfo.dli_fname, dlinfo.dli_fbase); else zlog(priority, "%-30s %16lx %16lx", name, (long)ip, (long)sp); } #elif defined(HAVE_GLIBC_BACKTRACE) void *array[20]; int size, i; char **strings; size = backtrace(array, array_size(array)); if (size <= 0 || (size_t)size > array_size(array)) { flog_err_sys( EC_LIB_SYSTEM_CALL, "Cannot get backtrace, returned invalid # of frames %d " "(valid range is between 1 and %lu)", size, (unsigned long)(array_size(array))); return; } zlog(priority, "Backtrace for %d stack frames:", size); if (!(strings = backtrace_symbols(array, size))) { flog_err_sys(EC_LIB_SYSTEM_CALL, "Cannot get backtrace symbols (out of memory?)"); for (i = 0; i < size; i++) zlog(priority, "[bt %d] %p", i, array[i]); } else { for (i = 0; i < size; i++) zlog(priority, "[bt %d] %s", i, strings[i]); free(strings); } #else /* !HAVE_GLIBC_BACKTRACE && !HAVE_LIBUNWIND */ zlog(priority, "No backtrace available on this platform."); #endif } void zlog(int priority, const char *format, ...) { va_list args; va_start(args, format); vzlog(priority, format, args); va_end(args); } #define ZLOG_FUNC(FUNCNAME, PRIORITY) \ void FUNCNAME(const char *format, ...) \ { \ va_list args; \ va_start(args, format); \ vzlog(PRIORITY, format, args); \ va_end(args); \ } ZLOG_FUNC(zlog_err, LOG_ERR) ZLOG_FUNC(zlog_warn, LOG_WARNING) ZLOG_FUNC(zlog_info, LOG_INFO) ZLOG_FUNC(zlog_notice, LOG_NOTICE) ZLOG_FUNC(zlog_debug, LOG_DEBUG) #undef ZLOG_FUNC void zlog_thread_info(int log_level) { struct thread *tc; tc = pthread_getspecific(thread_current); if (tc) zlog(log_level, "Current thread function %s, scheduled from " "file %s, line %u", tc->funcname, tc->schedfrom, tc->schedfrom_line); else zlog(log_level, "Current thread not known/applicable"); } void _zlog_assert_failed(const char *assertion, const char *file, unsigned int line, const char *function) { /* Force fallback file logging? */ if (zlog_default && !zlog_default->fp && ((logfile_fd = open_crashlog()) >= 0) && ((zlog_default->fp = fdopen(logfile_fd, "w")) != NULL)) zlog_default->maxlvl[ZLOG_DEST_FILE] = LOG_ERR; zlog(LOG_CRIT, "Assertion `%s' failed in file %s, line %u, function %s", assertion, file, line, (function ? function : "?")); zlog_backtrace(LOG_CRIT); zlog_thread_info(LOG_CRIT); log_memstats(stderr, "log"); abort(); } void memory_oom(size_t size, const char *name) { flog_err_sys(EC_LIB_SYSTEM_CALL, "out of memory: failed to allocate %zu bytes for %s" "object", size, name); zlog_backtrace(LOG_ERR); abort(); } /* Open log stream */ void openzlog(const char *progname, const char *protoname, unsigned short instance, int syslog_flags, int syslog_facility) { struct zlog *zl; unsigned int i; zl = XCALLOC(MTYPE_ZLOG, sizeof(struct zlog)); zl->ident = progname; zl->protoname = protoname; zl->instance = instance; zl->facility = syslog_facility; zl->syslog_options = syslog_flags; /* Set default logging levels. */ for (i = 0; i < array_size(zl->maxlvl); i++) zl->maxlvl[i] = ZLOG_DISABLED; zl->maxlvl[ZLOG_DEST_MONITOR] = LOG_DEBUG; zl->default_lvl = LOG_DEBUG; openlog(progname, syslog_flags, zl->facility); frr_with_mutex(&loglock) { zlog_default = zl; } #ifdef HAVE_GLIBC_BACKTRACE /* work around backtrace() using lazily resolved dynamically linked * symbols, which will otherwise cause funny breakage in the SEGV * handler. * (particularly, the dynamic linker can call malloc(), which uses locks * in programs linked with -pthread, thus can deadlock.) */ void *bt[4]; backtrace(bt, array_size(bt)); free(backtrace_symbols(bt, 0)); backtrace_symbols_fd(bt, 0, 0); #endif } void closezlog(void) { frr_mutex_lock_autounlock(&loglock); struct zlog *zl = zlog_default; closelog(); if (zl->fp != NULL) fclose(zl->fp); XFREE(MTYPE_ZLOG, zl->filename); XFREE(MTYPE_ZLOG, zl); zlog_default = NULL; } /* Called from command.c. */ void zlog_set_level(zlog_dest_t dest, int log_level) { frr_with_mutex(&loglock) { zlog_default->maxlvl[dest] = log_level; } } int zlog_set_file(const char *filename, int log_level) { struct zlog *zl; FILE *fp; mode_t oldumask; int ret = 1; /* There is opend file. */ zlog_reset_file(); /* Open file. */ oldumask = umask(0777 & ~LOGFILE_MASK); fp = fopen(filename, "a"); umask(oldumask); if (fp == NULL) { ret = 0; } else { frr_with_mutex(&loglock) { zl = zlog_default; /* Set flags. */ zl->filename = XSTRDUP(MTYPE_ZLOG, filename); zl->maxlvl[ZLOG_DEST_FILE] = log_level; zl->fp = fp; logfile_fd = fileno(fp); } } return ret; } /* Reset opend file. */ int zlog_reset_file(void) { frr_mutex_lock_autounlock(&loglock); struct zlog *zl = zlog_default; if (zl->fp) fclose(zl->fp); zl->fp = NULL; logfile_fd = -1; zl->maxlvl[ZLOG_DEST_FILE] = ZLOG_DISABLED; XFREE(MTYPE_ZLOG, zl->filename); zl->filename = NULL; return 1; } /* Reopen log file. */ int zlog_rotate(void) { pthread_mutex_lock(&loglock); struct zlog *zl = zlog_default; int level; int ret = 1; if (zl->fp) fclose(zl->fp); zl->fp = NULL; logfile_fd = -1; level = zl->maxlvl[ZLOG_DEST_FILE]; zl->maxlvl[ZLOG_DEST_FILE] = ZLOG_DISABLED; if (zl->filename) { mode_t oldumask; int save_errno; oldumask = umask(0777 & ~LOGFILE_MASK); zl->fp = fopen(zl->filename, "a"); save_errno = errno; umask(oldumask); if (zl->fp == NULL) { pthread_mutex_unlock(&loglock); flog_err_sys( EC_LIB_SYSTEM_CALL, "Log rotate failed: cannot open file %s for append: %s", zl->filename, safe_strerror(save_errno)); ret = -1; pthread_mutex_lock(&loglock); } else { logfile_fd = fileno(zl->fp); zl->maxlvl[ZLOG_DEST_FILE] = level; } } pthread_mutex_unlock(&loglock); return ret; } /* Wrapper around strerror to handle case where it returns NULL. */ const char *safe_strerror(int errnum) { const char *s = strerror(errnum); return (s != NULL) ? s : "Unknown error"; } #define DESC_ENTRY(T) [(T)] = { (T), (#T), '\0' } static const struct zebra_desc_table command_types[] = { DESC_ENTRY(ZEBRA_INTERFACE_ADD), DESC_ENTRY(ZEBRA_INTERFACE_DELETE), DESC_ENTRY(ZEBRA_INTERFACE_ADDRESS_ADD), DESC_ENTRY(ZEBRA_INTERFACE_ADDRESS_DELETE), DESC_ENTRY(ZEBRA_INTERFACE_UP), DESC_ENTRY(ZEBRA_INTERFACE_DOWN), DESC_ENTRY(ZEBRA_INTERFACE_SET_MASTER), DESC_ENTRY(ZEBRA_ROUTE_ADD), DESC_ENTRY(ZEBRA_ROUTE_DELETE), DESC_ENTRY(ZEBRA_ROUTE_NOTIFY_OWNER), DESC_ENTRY(ZEBRA_REDISTRIBUTE_ADD), DESC_ENTRY(ZEBRA_REDISTRIBUTE_DELETE), DESC_ENTRY(ZEBRA_REDISTRIBUTE_DEFAULT_ADD), DESC_ENTRY(ZEBRA_REDISTRIBUTE_DEFAULT_DELETE), DESC_ENTRY(ZEBRA_ROUTER_ID_ADD), DESC_ENTRY(ZEBRA_ROUTER_ID_DELETE), DESC_ENTRY(ZEBRA_ROUTER_ID_UPDATE), DESC_ENTRY(ZEBRA_HELLO), DESC_ENTRY(ZEBRA_CAPABILITIES), DESC_ENTRY(ZEBRA_NEXTHOP_REGISTER), DESC_ENTRY(ZEBRA_NEXTHOP_UNREGISTER), DESC_ENTRY(ZEBRA_NEXTHOP_UPDATE), DESC_ENTRY(ZEBRA_INTERFACE_NBR_ADDRESS_ADD), DESC_ENTRY(ZEBRA_INTERFACE_NBR_ADDRESS_DELETE), DESC_ENTRY(ZEBRA_INTERFACE_BFD_DEST_UPDATE), DESC_ENTRY(ZEBRA_IMPORT_ROUTE_REGISTER), DESC_ENTRY(ZEBRA_IMPORT_ROUTE_UNREGISTER), DESC_ENTRY(ZEBRA_IMPORT_CHECK_UPDATE), DESC_ENTRY(ZEBRA_BFD_DEST_REGISTER), DESC_ENTRY(ZEBRA_BFD_DEST_DEREGISTER), DESC_ENTRY(ZEBRA_BFD_DEST_UPDATE), DESC_ENTRY(ZEBRA_BFD_DEST_REPLAY), DESC_ENTRY(ZEBRA_REDISTRIBUTE_ROUTE_ADD), DESC_ENTRY(ZEBRA_REDISTRIBUTE_ROUTE_DEL), DESC_ENTRY(ZEBRA_VRF_UNREGISTER), DESC_ENTRY(ZEBRA_VRF_ADD), DESC_ENTRY(ZEBRA_VRF_DELETE), DESC_ENTRY(ZEBRA_VRF_LABEL), DESC_ENTRY(ZEBRA_INTERFACE_VRF_UPDATE), DESC_ENTRY(ZEBRA_BFD_CLIENT_REGISTER), DESC_ENTRY(ZEBRA_BFD_CLIENT_DEREGISTER), DESC_ENTRY(ZEBRA_INTERFACE_ENABLE_RADV), DESC_ENTRY(ZEBRA_INTERFACE_DISABLE_RADV), DESC_ENTRY(ZEBRA_IPV4_NEXTHOP_LOOKUP_MRIB), DESC_ENTRY(ZEBRA_INTERFACE_LINK_PARAMS), DESC_ENTRY(ZEBRA_MPLS_LABELS_ADD), DESC_ENTRY(ZEBRA_MPLS_LABELS_DELETE), DESC_ENTRY(ZEBRA_IPMR_ROUTE_STATS), DESC_ENTRY(ZEBRA_LABEL_MANAGER_CONNECT), DESC_ENTRY(ZEBRA_LABEL_MANAGER_CONNECT_ASYNC), DESC_ENTRY(ZEBRA_GET_LABEL_CHUNK), DESC_ENTRY(ZEBRA_RELEASE_LABEL_CHUNK), DESC_ENTRY(ZEBRA_FEC_REGISTER), DESC_ENTRY(ZEBRA_FEC_UNREGISTER), DESC_ENTRY(ZEBRA_FEC_UPDATE), DESC_ENTRY(ZEBRA_ADVERTISE_ALL_VNI), DESC_ENTRY(ZEBRA_ADVERTISE_DEFAULT_GW), DESC_ENTRY(ZEBRA_ADVERTISE_SVI_MACIP), DESC_ENTRY(ZEBRA_ADVERTISE_SUBNET), DESC_ENTRY(ZEBRA_LOCAL_ES_ADD), DESC_ENTRY(ZEBRA_LOCAL_ES_DEL), DESC_ENTRY(ZEBRA_VNI_ADD), DESC_ENTRY(ZEBRA_VNI_DEL), DESC_ENTRY(ZEBRA_L3VNI_ADD), DESC_ENTRY(ZEBRA_L3VNI_DEL), DESC_ENTRY(ZEBRA_REMOTE_VTEP_ADD), DESC_ENTRY(ZEBRA_REMOTE_VTEP_DEL), DESC_ENTRY(ZEBRA_MACIP_ADD), DESC_ENTRY(ZEBRA_MACIP_DEL), DESC_ENTRY(ZEBRA_IP_PREFIX_ROUTE_ADD), DESC_ENTRY(ZEBRA_IP_PREFIX_ROUTE_DEL), DESC_ENTRY(ZEBRA_REMOTE_MACIP_ADD), DESC_ENTRY(ZEBRA_REMOTE_MACIP_DEL), DESC_ENTRY(ZEBRA_DUPLICATE_ADDR_DETECTION), DESC_ENTRY(ZEBRA_PW_ADD), DESC_ENTRY(ZEBRA_PW_DELETE), DESC_ENTRY(ZEBRA_PW_SET), DESC_ENTRY(ZEBRA_PW_UNSET), DESC_ENTRY(ZEBRA_PW_STATUS_UPDATE), DESC_ENTRY(ZEBRA_RULE_ADD), DESC_ENTRY(ZEBRA_RULE_DELETE), DESC_ENTRY(ZEBRA_RULE_NOTIFY_OWNER), DESC_ENTRY(ZEBRA_TABLE_MANAGER_CONNECT), DESC_ENTRY(ZEBRA_GET_TABLE_CHUNK), DESC_ENTRY(ZEBRA_RELEASE_TABLE_CHUNK), DESC_ENTRY(ZEBRA_IPSET_CREATE), DESC_ENTRY(ZEBRA_IPSET_DESTROY), DESC_ENTRY(ZEBRA_IPSET_ENTRY_ADD), DESC_ENTRY(ZEBRA_IPSET_ENTRY_DELETE), DESC_ENTRY(ZEBRA_IPSET_NOTIFY_OWNER), DESC_ENTRY(ZEBRA_IPSET_ENTRY_NOTIFY_OWNER), DESC_ENTRY(ZEBRA_IPTABLE_ADD), DESC_ENTRY(ZEBRA_IPTABLE_DELETE), DESC_ENTRY(ZEBRA_IPTABLE_NOTIFY_OWNER), DESC_ENTRY(ZEBRA_VXLAN_FLOOD_CONTROL), DESC_ENTRY(ZEBRA_VXLAN_SG_ADD), DESC_ENTRY(ZEBRA_VXLAN_SG_DEL), DESC_ENTRY(ZEBRA_VXLAN_SG_REPLAY), }; #undef DESC_ENTRY static const struct zebra_desc_table unknown = {0, "unknown", '?'}; static const struct zebra_desc_table *zroute_lookup(unsigned int zroute) { unsigned int i; if (zroute >= array_size(route_types)) { flog_err(EC_LIB_DEVELOPMENT, "unknown zebra route type: %u", zroute); return &unknown; } if (zroute == route_types[zroute].type) return &route_types[zroute]; for (i = 0; i < array_size(route_types); i++) { if (zroute == route_types[i].type) { zlog_warn( "internal error: route type table out of order " "while searching for %u, please notify developers", zroute); return &route_types[i]; } } flog_err(EC_LIB_DEVELOPMENT, "internal error: cannot find route type %u in table!", zroute); return &unknown; } const char *zebra_route_string(unsigned int zroute) { return zroute_lookup(zroute)->string; } char zebra_route_char(unsigned int zroute) { return zroute_lookup(zroute)->chr; } const char *zserv_command_string(unsigned int command) { if (command >= array_size(command_types)) { flog_err(EC_LIB_DEVELOPMENT, "unknown zserv command type: %u", command); return unknown.string; } return command_types[command].string; } int proto_name2num(const char *s) { unsigned i; for (i = 0; i < array_size(route_types); ++i) if (strcasecmp(s, route_types[i].string) == 0) return route_types[i].type; return -1; } int proto_redistnum(int afi, const char *s) { if (!s) return -1; if (afi == AFI_IP) { if (strmatch(s, "kernel")) return ZEBRA_ROUTE_KERNEL; else if (strmatch(s, "connected")) return ZEBRA_ROUTE_CONNECT; else if (strmatch(s, "static")) return ZEBRA_ROUTE_STATIC; else if (strmatch(s, "rip")) return ZEBRA_ROUTE_RIP; else if (strmatch(s, "eigrp")) return ZEBRA_ROUTE_EIGRP; else if (strmatch(s, "ospf")) return ZEBRA_ROUTE_OSPF; else if (strmatch(s, "isis")) return ZEBRA_ROUTE_ISIS; else if (strmatch(s, "bgp")) return ZEBRA_ROUTE_BGP; else if (strmatch(s, "table")) return ZEBRA_ROUTE_TABLE; else if (strmatch(s, "vnc")) return ZEBRA_ROUTE_VNC; else if (strmatch(s, "vnc-direct")) return ZEBRA_ROUTE_VNC_DIRECT; else if (strmatch(s, "nhrp")) return ZEBRA_ROUTE_NHRP; else if (strmatch(s, "babel")) return ZEBRA_ROUTE_BABEL; else if (strmatch(s, "sharp")) return ZEBRA_ROUTE_SHARP; else if (strmatch(s, "openfabric")) return ZEBRA_ROUTE_OPENFABRIC; } if (afi == AFI_IP6) { if (strmatch(s, "kernel")) return ZEBRA_ROUTE_KERNEL; else if (strmatch(s, "connected")) return ZEBRA_ROUTE_CONNECT; else if (strmatch(s, "static")) return ZEBRA_ROUTE_STATIC; else if (strmatch(s, "ripng")) return ZEBRA_ROUTE_RIPNG; else if (strmatch(s, "ospf6")) return ZEBRA_ROUTE_OSPF6; else if (strmatch(s, "isis")) return ZEBRA_ROUTE_ISIS; else if (strmatch(s, "bgp")) return ZEBRA_ROUTE_BGP; else if (strmatch(s, "table")) return ZEBRA_ROUTE_TABLE; else if (strmatch(s, "vnc")) return ZEBRA_ROUTE_VNC; else if (strmatch(s, "vnc-direct")) return ZEBRA_ROUTE_VNC_DIRECT; else if (strmatch(s, "nhrp")) return ZEBRA_ROUTE_NHRP; else if (strmatch(s, "babel")) return ZEBRA_ROUTE_BABEL; else if (strmatch(s, "sharp")) return ZEBRA_ROUTE_SHARP; else if (strmatch(s, "openfabric")) return ZEBRA_ROUTE_OPENFABRIC; } return -1; } void zlog_hexdump(const void *mem, unsigned int len) { unsigned long i = 0; unsigned int j = 0; unsigned int columns = 8; /* * 19 bytes for 0xADDRESS: * 24 bytes for data; 2 chars plus a space per data byte * 1 byte for space * 8 bytes for ASCII representation * 1 byte for a newline * ===================== * 53 bytes per 8 bytes of data * 1 byte for null term */ size_t bs = ((len / 8) + 1) * 53 + 1; char buf[bs]; char *s = buf; const unsigned char *memch = mem; memset(buf, 0, sizeof(buf)); for (i = 0; i < len + ((len % columns) ? (columns - len % columns) : 0); i++) { /* print offset */ if (i % columns == 0) s += snprintf(s, bs - (s - buf), "0x%016lx: ", (unsigned long)memch + i); /* print hex data */ if (i < len) s += snprintf(s, bs - (s - buf), "%02x ", memch[i]); /* end of block, just aligning for ASCII dump */ else s += snprintf(s, bs - (s - buf), " "); /* print ASCII dump */ if (i % columns == (columns - 1)) { for (j = i - (columns - 1); j <= i; j++) { /* end of block not really printing */ if (j >= len) s += snprintf(s, bs - (s - buf), " "); else if (isprint(memch[j])) s += snprintf(s, bs - (s - buf), "%c", memch[j]); else /* other char */ s += snprintf(s, bs - (s - buf), "."); } s += snprintf(s, bs - (s - buf), "\n"); } } zlog_debug("\n%s", buf); } const char *zlog_sanitize(char *buf, size_t bufsz, const void *in, size_t inlen) { const char *inbuf = in; char *pos = buf, *end = buf + bufsz; const char *iend = inbuf + inlen; memset(buf, 0, bufsz); for (; inbuf < iend; inbuf++) { /* don't write partial escape sequence */ if (end - pos < 5) break; if (*inbuf == '\n') snprintf(pos, end - pos, "\\n"); else if (*inbuf == '\r') snprintf(pos, end - pos, "\\r"); else if (*inbuf == '\t') snprintf(pos, end - pos, "\\t"); else if (*inbuf < ' ' || *inbuf == '"' || *inbuf >= 127) snprintf(pos, end - pos, "\\x%02hhx", *inbuf); else *pos = *inbuf; pos += strlen(pos); } return buf; } frr-7.2.1/lib/log.h0000644000000000000000000002212013610377563010711 00000000000000/* * Zebra logging funcions. * Copyright (C) 1997, 1998, 1999 Kunihiro Ishiguro * * This file is part of GNU Zebra. * * GNU Zebra is free software; you can redistribute it and/or modify it * under the terms of the GNU General Public License as published by the * Free Software Foundation; either version 2, or (at your option) any * later version. * * GNU Zebra 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; see the file COPYING; if not, write to the Free Software * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA */ #ifndef _ZEBRA_LOG_H #define _ZEBRA_LOG_H #include #include #include #include #include #include "lib/hook.h" #ifdef __cplusplus extern "C" { #endif /* Hook for external logging function */ DECLARE_HOOK(zebra_ext_log, (int priority, const char *format, va_list args), (priority, format, args)); /* Here is some guidance on logging levels to use: * * LOG_DEBUG - For all messages that are enabled by optional debugging * features, typically preceded by "if (IS...DEBUG...)" * LOG_INFO - Information that may be of interest, but everything seems * to be working properly. * LOG_NOTICE - Only for message pertaining to daemon startup or shutdown. * LOG_WARNING - Warning conditions: unexpected events, but the daemon believes * it can continue to operate correctly. * LOG_ERR - Error situations indicating malfunctions. Probably require * attention. * * Note: LOG_CRIT, LOG_ALERT, and LOG_EMERG are currently not used anywhere, * please use LOG_ERR instead. */ /* If maxlvl is set to ZLOG_DISABLED, then no messages will be sent to that logging destination. */ #define ZLOG_DISABLED (LOG_EMERG-1) typedef enum { ZLOG_DEST_SYSLOG = 0, ZLOG_DEST_STDOUT, ZLOG_DEST_MONITOR, ZLOG_DEST_FILE } zlog_dest_t; #define ZLOG_NUM_DESTS (ZLOG_DEST_FILE+1) extern bool zlog_startup_stderr; /* Message structure. */ struct message { int key; const char *str; }; /* Open zlog function */ extern void openzlog(const char *progname, const char *protoname, uint16_t instance, int syslog_options, int syslog_facility); /* Close zlog function. */ extern void closezlog(void); /* Handy zlog functions. */ extern void zlog_err(const char *format, ...) PRINTFRR(1, 2); extern void zlog_warn(const char *format, ...) PRINTFRR(1, 2); extern void zlog_info(const char *format, ...) PRINTFRR(1, 2); extern void zlog_notice(const char *format, ...) PRINTFRR(1, 2); extern void zlog_debug(const char *format, ...) PRINTFRR(1, 2); extern void zlog(int priority, const char *format, ...) PRINTFRR(2, 3); /* For logs which have error codes associated with them */ #define flog_err(ferr_id, format, ...) \ zlog_err("[EC %" PRIu32 "] " format, ferr_id, ##__VA_ARGS__) #define flog_err_sys(ferr_id, format, ...) \ flog_err(ferr_id, format, ##__VA_ARGS__) #define flog_warn(ferr_id, format, ...) \ zlog_warn("[EC %" PRIu32 "] " format, ferr_id, ##__VA_ARGS__) #define flog(priority, ferr_id, format, ...) \ zlog(priority, "[EC %" PRIu32 "] " format, ferr_id, ##__VA_ARGS__) extern void zlog_thread_info(int log_level); /* Set logging level for the given destination. If the log_level argument is ZLOG_DISABLED, then the destination is disabled. This function should not be used for file logging (use zlog_set_file or zlog_reset_file instead). */ extern void zlog_set_level(zlog_dest_t, int log_level); /* Set logging to the given filename at the specified level. */ extern int zlog_set_file(const char *filename, int log_level); /* Disable file logging. */ extern int zlog_reset_file(void); /* Rotate log. */ extern int zlog_rotate(void); #define ZLOG_FILTERS_MAX 100 /* Max # of filters at once */ #define ZLOG_FILTER_LENGTH_MAX 80 /* 80 character filter limit */ /* Add/Del/Dump log filters */ extern void zlog_filter_clear(void); extern int zlog_filter_add(const char *filter); extern int zlog_filter_del(const char *filter); extern int zlog_filter_dump(char *buf, size_t max_size); const char *lookup_msg(const struct message *mz, int kz, const char *nf); /* Safe version of strerror -- never returns NULL. */ extern const char *safe_strerror(int errnum); /* To be called when a fatal signal is caught. */ extern void zlog_signal(int signo, const char *action, void *siginfo, void *program_counter); /* Log a backtrace. */ extern void zlog_backtrace(int priority); /* Log a backtrace, but in an async-signal-safe way. Should not be called unless the program is about to exit or abort, since it messes up the state of zlog file pointers. If program_counter is non-NULL, that is logged in addition to the current backtrace. */ extern void zlog_backtrace_sigsafe(int priority, void *program_counter); /* Puts a current timestamp in buf and returns the number of characters written (not including the terminating NUL). The purpose of this function is to avoid calls to localtime appearing all over the code. It caches the most recent localtime result and can therefore avoid multiple calls within the same second. If buflen is too small, *buf will be set to '\0', and 0 will be returned. */ #define QUAGGA_TIMESTAMP_LEN 40 extern size_t quagga_timestamp(int timestamp_precision /* # subsecond digits */, char *buf, size_t buflen); extern void zlog_hexdump(const void *mem, unsigned int len); extern const char *zlog_sanitize(char *buf, size_t bufsz, const void *in, size_t inlen); /* Note: whenever a new route-type or zserv-command is added the * corresponding {command,route}_types[] table in lib/log.c MUST be * updated! */ /* Map a route type to a string. For example, ZEBRA_ROUTE_RIPNG -> "ripng". */ extern const char *zebra_route_string(unsigned int route_type); /* Map a route type to a char. For example, ZEBRA_ROUTE_RIPNG -> 'R'. */ extern char zebra_route_char(unsigned int route_type); /* Map a zserv command type to the same string, * e.g. ZEBRA_INTERFACE_ADD -> "ZEBRA_INTERFACE_ADD" */ /* Map a protocol name to its number. e.g. ZEBRA_ROUTE_BGP->9*/ extern int proto_name2num(const char *s); /* Map redistribute X argument to protocol number. * unlike proto_name2num, this accepts shorthands and takes * an AFI value to restrict input */ extern int proto_redistnum(int afi, const char *s); extern const char *zserv_command_string(unsigned int command); extern int vzlog_test(int priority); /* structure useful for avoiding repeated rendering of the same timestamp */ struct timestamp_control { size_t len; /* length of rendered timestamp */ int precision; /* configuration parameter */ int already_rendered; /* should be initialized to 0 */ char buf[QUAGGA_TIMESTAMP_LEN]; /* will contain the rendered timestamp */ }; /* Defines for use in command construction: */ #define LOG_LEVEL_DESC \ "System is unusable\n" \ "Immediate action needed\n" \ "Critical conditions\n" \ "Error conditions\n" \ "Warning conditions\n" \ "Normal but significant conditions\n" \ "Informational messages\n" \ "Debugging messages\n" #define LOG_FACILITY_DESC \ "Kernel\n" \ "User process\n" \ "Mail system\n" \ "System daemons\n" \ "Authorization system\n" \ "Syslog itself\n" \ "Line printer system\n" \ "USENET news\n" \ "Unix-to-Unix copy system\n" \ "Cron/at facility\n" \ "Local use\n" \ "Local use\n" \ "Local use\n" \ "Local use\n" \ "Local use\n" \ "Local use\n" \ "Local use\n" \ "Local use\n" #ifdef __cplusplus } #endif #endif /* _ZEBRA_LOG_H */ frr-7.2.1/lib/log_int.h0000644000000000000000000000343013610377563011566 00000000000000/* * Zebra logging funcions. * Copyright (C) 1997, 1998, 1999 Kunihiro Ishiguro * * This file is part of GNU Zebra. * * GNU Zebra is free software; you can redistribute it and/or modify it * under the terms of the GNU General Public License as published by the * Free Software Foundation; either version 2, or (at your option) any * later version. * * GNU Zebra 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; see the file COPYING; if not, write to the Free Software * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA */ #ifndef _ZEBRA_LOG_PRIVATE_H #define _ZEBRA_LOG_PRIVATE_H #include "log.h" #ifdef __cplusplus extern "C" { #endif struct zlog { const char *ident; /* daemon name (first arg to openlog) */ const char *protoname; unsigned short instance; int maxlvl[ZLOG_NUM_DESTS]; /* maximum priority to send to associated logging destination */ int default_lvl; /* maxlvl to use if none is specified */ FILE *fp; char *filename; int facility; /* as per syslog facility */ int record_priority; /* should messages logged through stdio include the priority of the message? */ int syslog_options; /* 2nd arg to openlog */ int timestamp_precision; /* # of digits of subsecond precision */ }; /* Default logging strucutre. */ extern struct zlog *zlog_default; extern const char *zlog_priority[]; /* Generic function for zlog. */ extern void vzlog(int priority, const char *format, va_list args); #ifdef __cplusplus } #endif #endif /* _ZEBRA_LOG_PRIVATE_H */ frr-7.2.1/lib/log_vty.c0000644000000000000000000000450513610377563011615 00000000000000/* * Logging - VTY code * Copyright (C) 2019 Cumulus Networks, Inc. * Stephen Worley * * This program is free software; you can redistribute it and/or modify it * under the terms of the 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; see the file COPYING; if not, write to the Free Software * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA */ #include #include "lib/log_vty.h" #include "command.h" #include "lib/vty.h" #include "lib/log.h" #ifndef VTYSH_EXTRACT_PL #include "lib/log_vty_clippy.c" #endif DEFPY (log_filter, log_filter_cmd, "[no] log-filter WORD$filter", NO_STR FILTER_LOG_STR "String to filter by\n") { int ret = 0; if (no) ret = zlog_filter_del(filter); else ret = zlog_filter_add(filter); if (ret == 1) { vty_out(vty, "%% filter table full\n"); return CMD_WARNING; } else if (ret != 0) { vty_out(vty, "%% failed to %s log filter\n", (no ? "remove" : "apply")); return CMD_WARNING; } vty_out(vty, " %s\n", filter); return CMD_SUCCESS; } /* Clear all log filters */ DEFPY (log_filter_clear, log_filter_clear_cmd, "clear log-filter", CLEAR_STR FILTER_LOG_STR) { zlog_filter_clear(); return CMD_SUCCESS; } /* Show log filter */ DEFPY (show_log_filter, show_log_filter_cmd, "show log-filter", SHOW_STR FILTER_LOG_STR) { char log_filters[ZLOG_FILTERS_MAX * (ZLOG_FILTER_LENGTH_MAX + 3)] = ""; int len = 0; len = zlog_filter_dump(log_filters, sizeof(log_filters)); if (len == -1) { vty_out(vty, "%% failed to get filters\n"); return CMD_WARNING; } if (len != 0) vty_out(vty, "%s", log_filters); return CMD_SUCCESS; } void log_filter_cmd_init(void) { install_element(VIEW_NODE, &show_log_filter_cmd); install_element(CONFIG_NODE, &log_filter_cmd); install_element(CONFIG_NODE, &log_filter_clear_cmd); } frr-7.2.1/lib/log_vty.h0000644000000000000000000000170213610377563011616 00000000000000/* * Logging - VTY library * Copyright (C) 2019 Cumulus Networks, Inc. * Stephen Worley * * This program is free software; you can redistribute it and/or modify it * under the terms of the 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; see the file COPYING; if not, write to the Free Software * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA */ #ifndef __LOG_VTY_H__ #define __LOG_VTY_H__ extern void log_filter_cmd_init(void); #endif /* __LOG_VTY_H__ */ frr-7.2.1/lib/md5.c0000644000000000000000000003110513610377563010613 00000000000000/* * Copyright (C) 2004 6WIND * * All rights reserved. * * This MD5 code is Big endian and Little Endian compatible. */ /* * Copyright (C) 1995, 1996, 1997, and 1998 WIDE Project. * All rights reserved. * * Redistribution and use in source and binary forms, with or without * modification, are permitted provided that the following conditions * are met: * 1. Redistributions of source code must retain the above copyright * notice, this list of conditions and the following disclaimer. * 2. Redistributions in binary form must reproduce the above copyright * notice, this list of conditions and the following disclaimer in the * documentation and/or other materials provided with the distribution. * 3. Neither the name of the project nor the names of its contributors * may be used to endorse or promote products derived from this software * without specific prior written permission. * * THIS SOFTWARE IS PROVIDED BY THE PROJECT AND CONTRIBUTORS ``AS IS'' AND * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE * ARE DISCLAIMED. IN NO EVENT SHALL THE PROJECT OR CONTRIBUTORS BE LIABLE * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF * SUCH DAMAGE. */ #include #include "md5.h" #define SHIFT(X, s) (((X) << (s)) | ((X) >> (32 - (s)))) #define F(X, Y, Z) (((X) & (Y)) | ((~X) & (Z))) #define G(X, Y, Z) (((X) & (Z)) | ((Y) & (~Z))) #define H(X, Y, Z) ((X) ^ (Y) ^ (Z)) #define I(X, Y, Z) ((Y) ^ ((X) | (~Z))) #define ROUND1(a, b, c, d, k, s, i) \ { \ (a) = (a) + F((b), (c), (d)) + X[(k)] + T[(i)]; \ (a) = SHIFT((a), (s)); \ (a) = (b) + (a); \ } #define ROUND2(a, b, c, d, k, s, i) \ { \ (a) = (a) + G((b), (c), (d)) + X[(k)] + T[(i)]; \ (a) = SHIFT((a), (s)); \ (a) = (b) + (a); \ } #define ROUND3(a, b, c, d, k, s, i) \ { \ (a) = (a) + H((b), (c), (d)) + X[(k)] + T[(i)]; \ (a) = SHIFT((a), (s)); \ (a) = (b) + (a); \ } #define ROUND4(a, b, c, d, k, s, i) \ { \ (a) = (a) + I((b), (c), (d)) + X[(k)] + T[(i)]; \ (a) = SHIFT((a), (s)); \ (a) = (b) + (a); \ } #define Sa 7 #define Sb 12 #define Sc 17 #define Sd 22 #define Se 5 #define Sf 9 #define Sg 14 #define Sh 20 #define Si 4 #define Sj 11 #define Sk 16 #define Sl 23 #define Sm 6 #define Sn 10 #define So 15 #define Sp 21 #define MD5_A0 0x67452301 #define MD5_B0 0xefcdab89 #define MD5_C0 0x98badcfe #define MD5_D0 0x10325476 /* Integer part of 4294967296 times abs(sin(i)), where i is in radians. */ static const uint32_t T[65] = { 0, 0xd76aa478, 0xe8c7b756, 0x242070db, 0xc1bdceee, 0xf57c0faf, 0x4787c62a, 0xa8304613, 0xfd469501, 0x698098d8, 0x8b44f7af, 0xffff5bb1, 0x895cd7be, 0x6b901122, 0xfd987193, 0xa679438e, 0x49b40821, 0xf61e2562, 0xc040b340, 0x265e5a51, 0xe9b6c7aa, 0xd62f105d, 0x2441453, 0xd8a1e681, 0xe7d3fbc8, 0x21e1cde6, 0xc33707d6, 0xf4d50d87, 0x455a14ed, 0xa9e3e905, 0xfcefa3f8, 0x676f02d9, 0x8d2a4c8a, 0xfffa3942, 0x8771f681, 0x6d9d6122, 0xfde5380c, 0xa4beea44, 0x4bdecfa9, 0xf6bb4b60, 0xbebfbc70, 0x289b7ec6, 0xeaa127fa, 0xd4ef3085, 0x4881d05, 0xd9d4d039, 0xe6db99e5, 0x1fa27cf8, 0xc4ac5665, 0xf4292244, 0x432aff97, 0xab9423a7, 0xfc93a039, 0x655b59c3, 0x8f0ccc92, 0xffeff47d, 0x85845dd1, 0x6fa87e4f, 0xfe2ce6e0, 0xa3014314, 0x4e0811a1, 0xf7537e82, 0xbd3af235, 0x2ad7d2bb, 0xeb86d391, }; static const uint8_t md5_paddat[MD5_BUFLEN] = { 0x80, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, }; static void md5_calc(const uint8_t *, md5_ctxt *); void md5_init(md5_ctxt *ctxt) { ctxt->md5_n = 0; ctxt->md5_i = 0; ctxt->md5_sta = MD5_A0; ctxt->md5_stb = MD5_B0; ctxt->md5_stc = MD5_C0; ctxt->md5_std = MD5_D0; memset(ctxt->md5_buf, 0, sizeof(ctxt->md5_buf)); } void md5_loop(md5_ctxt *ctxt, const void *vinput, uint len) { uint gap, i; const uint8_t *input = vinput; ctxt->md5_n += len * 8; /* byte to bit */ gap = MD5_BUFLEN - ctxt->md5_i; if (len >= gap) { memcpy(ctxt->md5_buf + ctxt->md5_i, input, gap); md5_calc(ctxt->md5_buf, ctxt); for (i = gap; i + MD5_BUFLEN <= len; i += MD5_BUFLEN) { md5_calc((input + i), ctxt); } ctxt->md5_i = len - i; memcpy(ctxt->md5_buf, (input + i), ctxt->md5_i); } else { memcpy(ctxt->md5_buf + ctxt->md5_i, input, len); ctxt->md5_i += len; } } void md5_pad(md5_ctxt *ctxt) { uint gap; /* Don't count up padding. Keep md5_n. */ gap = MD5_BUFLEN - ctxt->md5_i; if (gap > 8) { memcpy(ctxt->md5_buf + ctxt->md5_i, md5_paddat, gap - sizeof(ctxt->md5_n)); } else { /* including gap == 8 */ memcpy(ctxt->md5_buf + ctxt->md5_i, md5_paddat, gap); md5_calc(ctxt->md5_buf, ctxt); memcpy(ctxt->md5_buf, md5_paddat + gap, MD5_BUFLEN - sizeof(ctxt->md5_n)); } /* 8 byte word */ if (BYTE_ORDER == LITTLE_ENDIAN) memcpy(&ctxt->md5_buf[56], &ctxt->md5_n8[0], 8); else { ctxt->md5_buf[56] = ctxt->md5_n8[7]; ctxt->md5_buf[57] = ctxt->md5_n8[6]; ctxt->md5_buf[58] = ctxt->md5_n8[5]; ctxt->md5_buf[59] = ctxt->md5_n8[4]; ctxt->md5_buf[60] = ctxt->md5_n8[3]; ctxt->md5_buf[61] = ctxt->md5_n8[2]; ctxt->md5_buf[62] = ctxt->md5_n8[1]; ctxt->md5_buf[63] = ctxt->md5_n8[0]; } md5_calc(ctxt->md5_buf, ctxt); } void md5_result(uint8_t *digest, md5_ctxt *ctxt) { /* 4 byte words */ if (BYTE_ORDER == LITTLE_ENDIAN) memcpy(digest, &ctxt->md5_st8[0], 16); else if (BYTE_ORDER == BIG_ENDIAN) { digest[0] = ctxt->md5_st8[3]; digest[1] = ctxt->md5_st8[2]; digest[2] = ctxt->md5_st8[1]; digest[3] = ctxt->md5_st8[0]; digest[4] = ctxt->md5_st8[7]; digest[5] = ctxt->md5_st8[6]; digest[6] = ctxt->md5_st8[5]; digest[7] = ctxt->md5_st8[4]; digest[8] = ctxt->md5_st8[11]; digest[9] = ctxt->md5_st8[10]; digest[10] = ctxt->md5_st8[9]; digest[11] = ctxt->md5_st8[8]; digest[12] = ctxt->md5_st8[15]; digest[13] = ctxt->md5_st8[14]; digest[14] = ctxt->md5_st8[13]; digest[15] = ctxt->md5_st8[12]; } } static void md5_calc(const uint8_t *b64, md5_ctxt *ctxt) { uint32_t A = ctxt->md5_sta; uint32_t B = ctxt->md5_stb; uint32_t C = ctxt->md5_stc; uint32_t D = ctxt->md5_std; #if (BYTE_ORDER == LITTLE_ENDIAN) const uint32_t *X = (const uint32_t *)b64; #elif (BYTE_ORDER == BIG_ENDIAN) uint32_t X[16]; if (BYTE_ORDER == BIG_ENDIAN) { /* 4 byte words */ /* what a brute force but fast! */ uint8_t *y = (uint8_t *)X; y[0] = b64[3]; y[1] = b64[2]; y[2] = b64[1]; y[3] = b64[0]; y[4] = b64[7]; y[5] = b64[6]; y[6] = b64[5]; y[7] = b64[4]; y[8] = b64[11]; y[9] = b64[10]; y[10] = b64[9]; y[11] = b64[8]; y[12] = b64[15]; y[13] = b64[14]; y[14] = b64[13]; y[15] = b64[12]; y[16] = b64[19]; y[17] = b64[18]; y[18] = b64[17]; y[19] = b64[16]; y[20] = b64[23]; y[21] = b64[22]; y[22] = b64[21]; y[23] = b64[20]; y[24] = b64[27]; y[25] = b64[26]; y[26] = b64[25]; y[27] = b64[24]; y[28] = b64[31]; y[29] = b64[30]; y[30] = b64[29]; y[31] = b64[28]; y[32] = b64[35]; y[33] = b64[34]; y[34] = b64[33]; y[35] = b64[32]; y[36] = b64[39]; y[37] = b64[38]; y[38] = b64[37]; y[39] = b64[36]; y[40] = b64[43]; y[41] = b64[42]; y[42] = b64[41]; y[43] = b64[40]; y[44] = b64[47]; y[45] = b64[46]; y[46] = b64[45]; y[47] = b64[44]; y[48] = b64[51]; y[49] = b64[50]; y[50] = b64[49]; y[51] = b64[48]; y[52] = b64[55]; y[53] = b64[54]; y[54] = b64[53]; y[55] = b64[52]; y[56] = b64[59]; y[57] = b64[58]; y[58] = b64[57]; y[59] = b64[56]; y[60] = b64[63]; y[61] = b64[62]; y[62] = b64[61]; y[63] = b64[60]; } #endif ROUND1(A, B, C, D, 0, Sa, 1); ROUND1(D, A, B, C, 1, Sb, 2); ROUND1(C, D, A, B, 2, Sc, 3); ROUND1(B, C, D, A, 3, Sd, 4); ROUND1(A, B, C, D, 4, Sa, 5); ROUND1(D, A, B, C, 5, Sb, 6); ROUND1(C, D, A, B, 6, Sc, 7); ROUND1(B, C, D, A, 7, Sd, 8); ROUND1(A, B, C, D, 8, Sa, 9); ROUND1(D, A, B, C, 9, Sb, 10); ROUND1(C, D, A, B, 10, Sc, 11); ROUND1(B, C, D, A, 11, Sd, 12); ROUND1(A, B, C, D, 12, Sa, 13); ROUND1(D, A, B, C, 13, Sb, 14); ROUND1(C, D, A, B, 14, Sc, 15); ROUND1(B, C, D, A, 15, Sd, 16); ROUND2(A, B, C, D, 1, Se, 17); ROUND2(D, A, B, C, 6, Sf, 18); ROUND2(C, D, A, B, 11, Sg, 19); ROUND2(B, C, D, A, 0, Sh, 20); ROUND2(A, B, C, D, 5, Se, 21); ROUND2(D, A, B, C, 10, Sf, 22); ROUND2(C, D, A, B, 15, Sg, 23); ROUND2(B, C, D, A, 4, Sh, 24); ROUND2(A, B, C, D, 9, Se, 25); ROUND2(D, A, B, C, 14, Sf, 26); ROUND2(C, D, A, B, 3, Sg, 27); ROUND2(B, C, D, A, 8, Sh, 28); ROUND2(A, B, C, D, 13, Se, 29); ROUND2(D, A, B, C, 2, Sf, 30); ROUND2(C, D, A, B, 7, Sg, 31); ROUND2(B, C, D, A, 12, Sh, 32); ROUND3(A, B, C, D, 5, Si, 33); ROUND3(D, A, B, C, 8, Sj, 34); ROUND3(C, D, A, B, 11, Sk, 35); ROUND3(B, C, D, A, 14, Sl, 36); ROUND3(A, B, C, D, 1, Si, 37); ROUND3(D, A, B, C, 4, Sj, 38); ROUND3(C, D, A, B, 7, Sk, 39); ROUND3(B, C, D, A, 10, Sl, 40); ROUND3(A, B, C, D, 13, Si, 41); ROUND3(D, A, B, C, 0, Sj, 42); ROUND3(C, D, A, B, 3, Sk, 43); ROUND3(B, C, D, A, 6, Sl, 44); ROUND3(A, B, C, D, 9, Si, 45); ROUND3(D, A, B, C, 12, Sj, 46); ROUND3(C, D, A, B, 15, Sk, 47); ROUND3(B, C, D, A, 2, Sl, 48); ROUND4(A, B, C, D, 0, Sm, 49); ROUND4(D, A, B, C, 7, Sn, 50); ROUND4(C, D, A, B, 14, So, 51); ROUND4(B, C, D, A, 5, Sp, 52); ROUND4(A, B, C, D, 12, Sm, 53); ROUND4(D, A, B, C, 3, Sn, 54); ROUND4(C, D, A, B, 10, So, 55); ROUND4(B, C, D, A, 1, Sp, 56); ROUND4(A, B, C, D, 8, Sm, 57); ROUND4(D, A, B, C, 15, Sn, 58); ROUND4(C, D, A, B, 6, So, 59); ROUND4(B, C, D, A, 13, Sp, 60); ROUND4(A, B, C, D, 4, Sm, 61); ROUND4(D, A, B, C, 11, Sn, 62); ROUND4(C, D, A, B, 2, So, 63); ROUND4(B, C, D, A, 9, Sp, 64); ctxt->md5_sta += A; ctxt->md5_stb += B; ctxt->md5_stc += C; ctxt->md5_std += D; } /* From RFC 2104 */ void hmac_md5(unsigned char *text, int text_len, unsigned char *key, int key_len, uint8_t *digest) { MD5_CTX context; unsigned char k_ipad[65]; /* inner padding - * key XORd with ipad */ unsigned char k_opad[65]; /* outer padding - * key XORd with opad */ unsigned char tk[16]; int i; /* if key is longer than 64 bytes reset it to key=MD5(key) */ if (key_len > 64) { MD5_CTX tctx; MD5Init(&tctx); MD5Update(&tctx, key, key_len); MD5Final(tk, &tctx); key = tk; key_len = 16; } /* * the HMAC_MD5 transform looks like: * * MD5(K XOR opad, MD5(K XOR ipad, text)) * * where K is an n byte key * ipad is the byte 0x36 repeated 64 times * opad is the byte 0x5c repeated 64 times * and text is the data being protected */ /* start out by storing key in pads */ bzero(k_ipad, sizeof k_ipad); bzero(k_opad, sizeof k_opad); bcopy(key, k_ipad, key_len); bcopy(key, k_opad, key_len); /* XOR key with ipad and opad values */ for (i = 0; i < 64; i++) { k_ipad[i] ^= 0x36; k_opad[i] ^= 0x5c; } /* * perform inner MD5 */ MD5Init(&context); /* init context for 1st * pass */ MD5Update(&context, k_ipad, 64); /* start with inner pad */ MD5Update(&context, text, text_len); /* then text of datagram */ MD5Final((uint8_t *)digest, &context); /* finish up 1st pass */ /* * perform outer MD5 */ MD5Init(&context); /* init context for 2nd * pass */ MD5Update(&context, k_opad, 64); /* start with outer pad */ MD5Update(&context, digest, 16); /* then results of 1st * hash */ MD5Final((uint8_t *)digest, &context); /* finish up 2nd pass */ } frr-7.2.1/lib/md5.h0000644000000000000000000000611713610377563010625 00000000000000/* * Copyright (C) 2004 6WIND * * All rights reserved. * * This MD5 code is Big endian and Little Endian compatible. */ /* * Copyright (C) 1995, 1996, 1997, and 1998 WIDE Project. * All rights reserved. * * Redistribution and use in source and binary forms, with or without * modification, are permitted provided that the following conditions * are met: * 1. Redistributions of source code must retain the above copyright * notice, this list of conditions and the following disclaimer. * 2. Redistributions in binary form must reproduce the above copyright * notice, this list of conditions and the following disclaimer in the * documentation and/or other materials provided with the distribution. * 3. Neither the name of the project nor the names of its contributors * may be used to endorse or promote products derived from this software * without specific prior written permission. * * THIS SOFTWARE IS PROVIDED BY THE PROJECT AND CONTRIBUTORS ``AS IS'' AND * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE * ARE DISCLAIMED. IN NO EVENT SHALL THE PROJECT OR CONTRIBUTORS BE LIABLE * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF * SUCH DAMAGE. */ #ifndef _LIBZEBRA_MD5_H_ #define _LIBZEBRA_MD5_H_ #ifdef __cplusplus extern "C" { #endif #define MD5_BUFLEN 64 typedef struct { union { uint32_t md5_state32[4]; uint8_t md5_state8[16]; } md5_st; #define md5_sta md5_st.md5_state32[0] #define md5_stb md5_st.md5_state32[1] #define md5_stc md5_st.md5_state32[2] #define md5_std md5_st.md5_state32[3] #define md5_st8 md5_st.md5_state8 union { uint64_t md5_count64; uint8_t md5_count8[8]; } md5_count; #define md5_n md5_count.md5_count64 #define md5_n8 md5_count.md5_count8 uint md5_i; uint8_t md5_buf[MD5_BUFLEN]; } md5_ctxt; extern void md5_init(md5_ctxt *); extern void md5_loop(md5_ctxt *, const void *, unsigned int); extern void md5_pad(md5_ctxt *); extern void md5_result(uint8_t *, md5_ctxt *); /* compatibility */ #define MD5_CTX md5_ctxt #define MD5Init(x) md5_init((x)) #define MD5Update(x, y, z) md5_loop((x), (y), (z)) #define MD5Final(x, y) \ do { \ md5_pad((y)); \ md5_result((x), (y)); \ } while (0) /* From RFC 2104 */ void hmac_md5(unsigned char *text, int text_len, unsigned char *key, int key_len, uint8_t *digest); #ifdef __cplusplus } #endif #endif /* ! _LIBZEBRA_MD5_H_*/ frr-7.2.1/lib/memory.c0000644000000000000000000001125413610377563011441 00000000000000/* * Copyright (c) 2015-16 David Lamparter, for NetDEF, Inc. * * Permission to use, copy, modify, and distribute this software for any * purpose with or without fee is hereby granted, provided that the above * copyright notice and this permission notice appear in all copies. * * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. */ #include #include #ifdef HAVE_MALLOC_H #include #endif #ifdef HAVE_MALLOC_NP_H #include #endif #ifdef HAVE_MALLOC_MALLOC_H #include #endif #include "memory.h" #include "log.h" static struct memgroup *mg_first = NULL; struct memgroup **mg_insert = &mg_first; DEFINE_MGROUP(LIB, "libfrr") DEFINE_MTYPE(LIB, TMP, "Temporary memory") static inline void mt_count_alloc(struct memtype *mt, size_t size, void *ptr) { size_t current; size_t oldsize; current = 1 + atomic_fetch_add_explicit(&mt->n_alloc, 1, memory_order_relaxed); oldsize = atomic_load_explicit(&mt->n_max, memory_order_relaxed); if (current > oldsize) /* note that this may fail, but approximation is sufficient */ atomic_compare_exchange_weak_explicit(&mt->n_max, &oldsize, current, memory_order_relaxed, memory_order_relaxed); oldsize = atomic_load_explicit(&mt->size, memory_order_relaxed); if (oldsize == 0) oldsize = atomic_exchange_explicit(&mt->size, size, memory_order_relaxed); if (oldsize != 0 && oldsize != size && oldsize != SIZE_VAR) atomic_store_explicit(&mt->size, SIZE_VAR, memory_order_relaxed); #ifdef HAVE_MALLOC_USABLE_SIZE size_t mallocsz = malloc_usable_size(ptr); current = mallocsz + atomic_fetch_add_explicit(&mt->total, mallocsz, memory_order_relaxed); oldsize = atomic_load_explicit(&mt->max_size, memory_order_relaxed); if (current > oldsize) /* note that this may fail, but approximation is sufficient */ atomic_compare_exchange_weak_explicit(&mt->max_size, &oldsize, current, memory_order_relaxed, memory_order_relaxed); #endif } static inline void mt_count_free(struct memtype *mt, void *ptr) { assert(mt->n_alloc); atomic_fetch_sub_explicit(&mt->n_alloc, 1, memory_order_relaxed); #ifdef HAVE_MALLOC_USABLE_SIZE size_t mallocsz = malloc_usable_size(ptr); atomic_fetch_sub_explicit(&mt->total, mallocsz, memory_order_relaxed); #endif } static inline void *mt_checkalloc(struct memtype *mt, void *ptr, size_t size) { if (__builtin_expect(ptr == NULL, 0)) { if (size) { /* malloc(0) is allowed to return NULL */ memory_oom(size, mt->name); } return NULL; } mt_count_alloc(mt, size, ptr); return ptr; } void *qmalloc(struct memtype *mt, size_t size) { return mt_checkalloc(mt, malloc(size), size); } void *qcalloc(struct memtype *mt, size_t size) { return mt_checkalloc(mt, calloc(size, 1), size); } void *qrealloc(struct memtype *mt, void *ptr, size_t size) { if (ptr) mt_count_free(mt, ptr); return mt_checkalloc(mt, ptr ? realloc(ptr, size) : malloc(size), size); } void *qstrdup(struct memtype *mt, const char *str) { return str ? mt_checkalloc(mt, strdup(str), strlen(str) + 1) : NULL; } void qfree(struct memtype *mt, void *ptr) { if (ptr) mt_count_free(mt, ptr); free(ptr); } int qmem_walk(qmem_walk_fn *func, void *arg) { struct memgroup *mg; struct memtype *mt; int rv; for (mg = mg_first; mg; mg = mg->next) { if ((rv = func(arg, mg, NULL))) return rv; for (mt = mg->types; mt; mt = mt->next) if ((rv = func(arg, mg, mt))) return rv; } return 0; } struct exit_dump_args { FILE *fp; const char *prefix; int error; }; static int qmem_exit_walker(void *arg, struct memgroup *mg, struct memtype *mt) { struct exit_dump_args *eda = arg; if (!mt) { fprintf(eda->fp, "%s: showing active allocations in " "memory group %s\n", eda->prefix, mg->name); } else if (mt->n_alloc) { char size[32]; eda->error++; snprintf(size, sizeof(size), "%10zu", mt->size); fprintf(eda->fp, "%s: memstats: %-30s: %6zu * %s\n", eda->prefix, mt->name, mt->n_alloc, mt->size == SIZE_VAR ? "(variably sized)" : size); } return 0; } int log_memstats(FILE *fp, const char *prefix) { struct exit_dump_args eda = {.fp = fp, .prefix = prefix, .error = 0}; qmem_walk(qmem_exit_walker, &eda); return eda.error; } frr-7.2.1/lib/memory.h0000644000000000000000000002103013610377563011437 00000000000000/* * Copyright (c) 2015-16 David Lamparter, for NetDEF, Inc. * * Permission to use, copy, modify, and distribute this software for any * purpose with or without fee is hereby granted, provided that the above * copyright notice and this permission notice appear in all copies. * * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. */ #ifndef _QUAGGA_MEMORY_H #define _QUAGGA_MEMORY_H #include #include #include #include "compiler.h" #ifdef __cplusplus extern "C" { #endif #if defined(HAVE_MALLOC_SIZE) && !defined(HAVE_MALLOC_USABLE_SIZE) #define malloc_usable_size(x) malloc_size(x) #define HAVE_MALLOC_USABLE_SIZE #endif #define SIZE_VAR ~0UL struct memtype { struct memtype *next, **ref; const char *name; atomic_size_t n_alloc; atomic_size_t n_max; atomic_size_t size; #ifdef HAVE_MALLOC_USABLE_SIZE atomic_size_t total; atomic_size_t max_size; #endif }; struct memgroup { struct memgroup *next, **ref; struct memtype *types, **insert; const char *name; }; /* macro usage: * * mydaemon.h * DECLARE_MGROUP(MYDAEMON) * DECLARE_MTYPE(MYDAEMON_COMMON) * * mydaemon.c * DEFINE_MGROUP(MYDAEMON, "my daemon memory") * DEFINE_MTYPE(MYDAEMON, MYDAEMON_COMMON, * "this mtype is used in multiple files in mydaemon") * foo = qmalloc(MTYPE_MYDAEMON_COMMON, sizeof(*foo)) * * mydaemon_io.c * bar = qmalloc(MTYPE_MYDAEMON_COMMON, sizeof(*bar)) * * DEFINE_MTYPE_STATIC(MYDAEMON, MYDAEMON_IO, * "this mtype is used only in this file") * baz = qmalloc(MTYPE_MYDAEMON_IO, sizeof(*baz)) * * Note: Naming conventions (MGROUP_ and MTYPE_ prefixes are enforced * by not having these as part of the macro arguments) * Note: MTYPE_* are symbols to the compiler (of type struct memtype *), * but MGROUP_* aren't. */ #define DECLARE_MGROUP(name) extern struct memgroup _mg_##name; #define DEFINE_MGROUP(mname, desc) \ struct memgroup _mg_##mname \ __attribute__((section(".data.mgroups"))) = { \ .name = desc, \ .types = NULL, \ .next = NULL, \ .insert = NULL, \ .ref = NULL, \ }; \ static void _mginit_##mname(void) __attribute__((_CONSTRUCTOR(1000))); \ static void _mginit_##mname(void) \ { \ extern struct memgroup **mg_insert; \ _mg_##mname.ref = mg_insert; \ *mg_insert = &_mg_##mname; \ mg_insert = &_mg_##mname.next; \ } \ static void _mgfini_##mname(void) __attribute__((_DESTRUCTOR(1000))); \ static void _mgfini_##mname(void) \ { \ if (_mg_##mname.next) \ _mg_##mname.next->ref = _mg_##mname.ref; \ *_mg_##mname.ref = _mg_##mname.next; \ } /* the array is a trick to make the "MTYPE_FOO" name work as a pointer without * putting a & in front of it, so we can do "XMALLOC(MTYPE_FOO, ...)" instead * of "XMALLOC(&MTYPE_FOO, ...)". */ #define DECLARE_MTYPE(name) \ extern struct memtype _mt_##name; \ extern struct memtype MTYPE_##name[1]; \ /* end */ #define DEFINE_MTYPE_ATTR(group, mname, attr, desc) \ attr struct memtype _mt_##mname \ __attribute__((section(".data.mtypes"))) = { \ .name = desc, \ .next = NULL, \ .n_alloc = 0, \ .size = 0, \ .ref = NULL, \ }; \ static void _mtinit_##mname(void) __attribute__((_CONSTRUCTOR(1001))); \ static void _mtinit_##mname(void) \ { \ if (_mg_##group.insert == NULL) \ _mg_##group.insert = &_mg_##group.types; \ _mt_##mname.ref = _mg_##group.insert; \ *_mg_##group.insert = &_mt_##mname; \ _mg_##group.insert = &_mt_##mname.next; \ } \ static void _mtfini_##mname(void) __attribute__((_DESTRUCTOR(1001))); \ static void _mtfini_##mname(void) \ { \ if (_mt_##mname.next) \ _mt_##mname.next->ref = _mt_##mname.ref; \ *_mt_##mname.ref = _mt_##mname.next; \ } \ /* end */ /* can't quite get gcc to emit the alias correctly, so asm-alias it is :/ */ #define DEFINE_MTYPE(group, name, desc) \ DEFINE_MTYPE_ATTR(group, name, , desc) \ __asm__(".equiv MTYPE_" #name ", _mt_" #name "\n\t" \ ".global MTYPE_" #name "\n"); \ /* end */ /* and this one's borked on clang, it drops static on aliases :/, so... asm */ #define DEFINE_MTYPE_STATIC(group, name, desc) \ DEFINE_MTYPE_ATTR(group, name, static, desc) \ extern struct memtype MTYPE_##name[1]; \ __asm__(".equiv MTYPE_" #name ", _mt_" #name "\n"); \ /* end */ DECLARE_MGROUP(LIB) DECLARE_MTYPE(TMP) extern void *qmalloc(struct memtype *mt, size_t size) __attribute__((malloc, _ALLOC_SIZE(2), nonnull(1) _RET_NONNULL)); extern void *qcalloc(struct memtype *mt, size_t size) __attribute__((malloc, _ALLOC_SIZE(2), nonnull(1) _RET_NONNULL)); extern void *qrealloc(struct memtype *mt, void *ptr, size_t size) __attribute__((_ALLOC_SIZE(3), nonnull(1) _RET_NONNULL)); extern void *qstrdup(struct memtype *mt, const char *str) __attribute__((malloc, nonnull(1) _RET_NONNULL)); extern void qfree(struct memtype *mt, void *ptr) __attribute__((nonnull(1))); #define XMALLOC(mtype, size) qmalloc(mtype, size) #define XCALLOC(mtype, size) qcalloc(mtype, size) #define XREALLOC(mtype, ptr, size) qrealloc(mtype, ptr, size) #define XSTRDUP(mtype, str) qstrdup(mtype, str) #define XFREE(mtype, ptr) \ do { \ qfree(mtype, ptr); \ ptr = NULL; \ } while (0) static inline size_t mtype_stats_alloc(struct memtype *mt) { return mt->n_alloc; } /* NB: calls are ordered by memgroup; and there is a call with mt == NULL for * each memgroup (so that a header can be printed, and empty memgroups show) * * return value: 0: continue, !0: abort walk. qmem_walk will return the * last value from qmem_walk_fn. */ typedef int qmem_walk_fn(void *arg, struct memgroup *mg, struct memtype *mt); extern int qmem_walk(qmem_walk_fn *func, void *arg); extern int log_memstats(FILE *fp, const char *); #define log_memstats_stderr(prefix) log_memstats(stderr, prefix) extern void memory_oom(size_t size, const char *name); #ifdef __cplusplus } #endif #endif /* _QUAGGA_MEMORY_H */ frr-7.2.1/lib/memory_vty.c0000644000000000000000000001351213610377563012342 00000000000000/* * Memory and dynamic module VTY routine * * Copyright (C) 1998 Kunihiro Ishiguro * Copyright (C) 2016-2017 David Lamparter for NetDEF, Inc. * * This program is free software; you can redistribute it and/or modify it * under the terms of the 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; see the file COPYING; if not, write to the Free Software * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA */ #include /* malloc.h is generally obsolete, however GNU Libc mallinfo wants it. */ #ifdef HAVE_MALLOC_H #include #endif #ifdef HAVE_MALLOC_MALLOC_H #include #endif #include #ifdef HAVE_LINK_H #include #endif #include "log.h" #include "memory.h" #include "module.h" #include "memory_vty.h" /* Looking up memory status from vty interface. */ #include "vector.h" #include "vty.h" #include "command.h" #ifdef HAVE_MALLINFO static int show_memory_mallinfo(struct vty *vty) { struct mallinfo minfo = mallinfo(); char buf[MTYPE_MEMSTR_LEN]; vty_out(vty, "System allocator statistics:\n"); vty_out(vty, " Total heap allocated: %s\n", mtype_memstr(buf, MTYPE_MEMSTR_LEN, minfo.arena)); vty_out(vty, " Holding block headers: %s\n", mtype_memstr(buf, MTYPE_MEMSTR_LEN, minfo.hblkhd)); vty_out(vty, " Used small blocks: %s\n", mtype_memstr(buf, MTYPE_MEMSTR_LEN, minfo.usmblks)); vty_out(vty, " Used ordinary blocks: %s\n", mtype_memstr(buf, MTYPE_MEMSTR_LEN, minfo.uordblks)); vty_out(vty, " Free small blocks: %s\n", mtype_memstr(buf, MTYPE_MEMSTR_LEN, minfo.fsmblks)); vty_out(vty, " Free ordinary blocks: %s\n", mtype_memstr(buf, MTYPE_MEMSTR_LEN, minfo.fordblks)); vty_out(vty, " Ordinary blocks: %ld\n", (unsigned long)minfo.ordblks); vty_out(vty, " Small blocks: %ld\n", (unsigned long)minfo.smblks); vty_out(vty, " Holding blocks: %ld\n", (unsigned long)minfo.hblks); vty_out(vty, "(see system documentation for 'mallinfo' for meaning)\n"); return 1; } #endif /* HAVE_MALLINFO */ static int qmem_walker(void *arg, struct memgroup *mg, struct memtype *mt) { struct vty *vty = arg; if (!mt) { vty_out(vty, "--- qmem %s ---\n", mg->name); vty_out(vty, "%-30s: %8s %-8s%s %8s %9s\n", "Type", "Current#", " Size", #ifdef HAVE_MALLOC_USABLE_SIZE " Total", #else "", #endif "Max#", #ifdef HAVE_MALLOC_USABLE_SIZE "MaxBytes" #else "" #endif ); } else { if (mt->n_alloc != 0) { char size[32]; snprintf(size, sizeof(size), "%6zu", mt->size); #ifdef HAVE_MALLOC_USABLE_SIZE #define TSTR " %9zu" #define TARG , mt->total #define TARG2 , mt->max_size #else #define TSTR "" #define TARG #define TARG2 #endif vty_out(vty, "%-30s: %8zu %-8s"TSTR" %8zu"TSTR"\n", mt->name, mt->n_alloc, mt->size == 0 ? "" : mt->size == SIZE_VAR ? "variable" : size TARG, mt->n_max TARG2); } } return 0; } DEFUN (show_memory, show_memory_cmd, "show memory", "Show running system information\n" "Memory statistics\n") { #ifdef HAVE_MALLINFO show_memory_mallinfo(vty); #endif /* HAVE_MALLINFO */ qmem_walk(qmem_walker, vty); return CMD_SUCCESS; } DEFUN (show_modules, show_modules_cmd, "show modules", "Show running system information\n" "Loaded modules\n") { struct frrmod_runtime *plug = frrmod_list; vty_out(vty, "%-12s %-25s %s\n\n", "Module Name", "Version", "Description"); while (plug) { const struct frrmod_info *i = plug->info; vty_out(vty, "%-12s %-25s %s\n", i->name, i->version, i->description); if (plug->dl_handle) { #ifdef HAVE_DLINFO_ORIGIN char origin[MAXPATHLEN] = ""; dlinfo(plug->dl_handle, RTLD_DI_ORIGIN, &origin); #ifdef HAVE_DLINFO_LINKMAP const char *name; struct link_map *lm = NULL; dlinfo(plug->dl_handle, RTLD_DI_LINKMAP, &lm); if (lm) { name = strrchr(lm->l_name, '/'); name = name ? name + 1 : lm->l_name; vty_out(vty, "\tfrom: %s/%s\n", origin, name); } #else vty_out(vty, "\tfrom: %s \n", origin, plug->load_name); #endif #else vty_out(vty, "\tfrom: %s\n", plug->load_name); #endif } plug = plug->next; } vty_out(vty, "pid: %u\n", (uint32_t)(getpid())); return CMD_SUCCESS; } void memory_init(void) { install_element(VIEW_NODE, &show_memory_cmd); install_element(VIEW_NODE, &show_modules_cmd); } /* Stats querying from users */ /* Return a pointer to a human friendly string describing * the byte count passed in. E.g: * "0 bytes", "2048 bytes", "110kB", "500MiB", "11GiB", etc. * Up to 4 significant figures will be given. * The pointer returned may be NULL (indicating an error) * or point to the given buffer, or point to static storage. */ const char *mtype_memstr(char *buf, size_t len, unsigned long bytes) { unsigned int m, k; /* easy cases */ if (!bytes) return "0 bytes"; if (bytes == 1) return "1 byte"; /* * When we pass the 2gb barrier mallinfo() can no longer report * correct data so it just does something odd... * Reporting like Terrabytes of data. Which makes users... * edgy.. yes edgy that's the term for it. * So let's just give up gracefully */ if (bytes > 0x7fffffff) return "> 2GB"; m = bytes >> 20; k = bytes >> 10; if (m > 10) { if (bytes & (1 << 19)) m++; snprintf(buf, len, "%d MiB", m); } else if (k > 10) { if (bytes & (1 << 9)) k++; snprintf(buf, len, "%d KiB", k); } else snprintf(buf, len, "%ld bytes", bytes); return buf; } frr-7.2.1/lib/memory_vty.h0000644000000000000000000000223613610377563012350 00000000000000/* Memory management routine * Copyright (C) 1998 Kunihiro Ishiguro * * This file is part of GNU Zebra. * * GNU Zebra is free software; you can redistribute it and/or modify it * under the terms of the GNU General Public License as published by the * Free Software Foundation; either version 2, or (at your option) any * later version. * * GNU Zebra 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; see the file COPYING; if not, write to the Free Software * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA */ #ifndef _ZEBRA_MEMORY_VTY_H #define _ZEBRA_MEMORY_VTY_H #include "memory.h" #ifdef __cplusplus extern "C" { #endif extern void memory_init(void); /* Human friendly string for given byte count */ #define MTYPE_MEMSTR_LEN 20 extern const char *mtype_memstr(char *, size_t, unsigned long); #ifdef __cplusplus } #endif #endif /* _ZEBRA_MEMORY_VTY_H */ frr-7.2.1/lib/mlag.c0000644000000000000000000000223713610377563011052 00000000000000/* mlag generic code. * Copyright (C) 2018 Cumulus Networks, Inc. * Donald Sharp * * This file is part of FRR. * * FRR is free software; you can redistribute it and/or modify it * under the terms of the GNU General Public License as published by the * Free Software Foundation; either version 2, or (at your option) any * later version. * * FRR 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 FRR; see the file COPYING. If not, write to the Free * Software Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA * 02111-1307, USA. */ #include #include char *mlag_role2str(enum mlag_role role, char *buf, size_t size) { switch (role) { case MLAG_ROLE_NONE: snprintf(buf, size, "NONE"); break; case MLAG_ROLE_PRIMARY: snprintf(buf, size, "PRIMARY"); break; case MLAG_ROLE_SECONDARY: snprintf(buf, size, "SECONDARY"); break; } return buf; } frr-7.2.1/lib/mlag.h0000644000000000000000000000211513610377563011052 00000000000000/* mlag header. * Copyright (C) 2018 Cumulus Networks, Inc. * Donald Sharp * * This file is part of FRR. * * FRR is free software; you can redistribute it and/or modify it * under the terms of the GNU General Public License as published by the * Free Software Foundation; either version 2, or (at your option) any * later version. * * FRR 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 FRR; see the file COPYING. If not, write to the Free * Software Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA * 02111-1307, USA. */ #ifndef __MLAG_H__ #define __MLAG_H__ #ifdef __cplusplus extern "C" { #endif enum mlag_role { MLAG_ROLE_NONE, MLAG_ROLE_PRIMARY, MLAG_ROLE_SECONDARY }; extern char *mlag_role2str(enum mlag_role role, char *buf, size_t size); #ifdef __cplusplus } #endif #endif frr-7.2.1/lib/module.c0000644000000000000000000000761213610377563011421 00000000000000/* * Copyright (c) 2015-16 David Lamparter, for NetDEF, Inc. * * Permission to use, copy, modify, and distribute this software for any * purpose with or without fee is hereby granted, provided that the above * copyright notice and this permission notice appear in all copies. * * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. */ #include "config.h" #include #include #include #include #include #include #include "module.h" #include "memory.h" #include "version.h" DEFINE_MTYPE_STATIC(LIB, MODULE_LOADNAME, "Module loading name") DEFINE_MTYPE_STATIC(LIB, MODULE_LOADARGS, "Module loading arguments") static struct frrmod_info frrmod_default_info = { .name = "libfrr", .version = FRR_VERSION, .description = "libfrr core module", }; union _frrmod_runtime_u frrmod_default = { .r = { .info = &frrmod_default_info, .finished_loading = 1, }, }; // if defined(HAVE_SYS_WEAK_ALIAS_ATTRIBUTE) // union _frrmod_runtime_u _frrmod_this_module // __attribute__((weak, alias("frrmod_default"))); // elif defined(HAVE_SYS_WEAK_ALIAS_PRAGMA) #pragma weak _frrmod_this_module = frrmod_default // else // error need weak symbol support // endif struct frrmod_runtime *frrmod_list = &frrmod_default.r; static struct frrmod_runtime **frrmod_last = &frrmod_default.r.next; static const char *execname = NULL; void frrmod_init(struct frrmod_runtime *modinfo) { modinfo->finished_loading = 1; *frrmod_last = modinfo; frrmod_last = &modinfo->next; execname = modinfo->info->name; } struct frrmod_runtime *frrmod_load(const char *spec, const char *dir, char *err, size_t err_len) { void *handle = NULL; char name[PATH_MAX], fullpath[PATH_MAX * 2], *args; struct frrmod_runtime *rtinfo, **rtinfop; const struct frrmod_info *info; snprintf(name, sizeof(name), "%s", spec); args = strchr(name, ':'); if (args) *args++ = '\0'; if (!strchr(name, '/')) { if (execname) { snprintf(fullpath, sizeof(fullpath), "%s/%s_%s.so", dir, execname, name); handle = dlopen(fullpath, RTLD_NOW | RTLD_GLOBAL); } if (!handle) { snprintf(fullpath, sizeof(fullpath), "%s/%s.so", dir, name); handle = dlopen(fullpath, RTLD_NOW | RTLD_GLOBAL); } } if (!handle) { snprintf(fullpath, sizeof(fullpath), "%s", name); handle = dlopen(fullpath, RTLD_NOW | RTLD_GLOBAL); } if (!handle) { if (err) snprintf(err, err_len, "loading module \"%s\" failed: %s", name, dlerror()); return NULL; } rtinfop = dlsym(handle, "frr_module"); if (!rtinfop) { dlclose(handle); if (err) snprintf(err, err_len, "\"%s\" is not an FRR module: %s", name, dlerror()); return NULL; } rtinfo = *rtinfop; rtinfo->load_name = XSTRDUP(MTYPE_MODULE_LOADNAME, name); rtinfo->dl_handle = handle; if (args) rtinfo->load_args = XSTRDUP(MTYPE_MODULE_LOADARGS, args); info = rtinfo->info; if (rtinfo->finished_loading) { dlclose(handle); if (err) snprintf(err, err_len, "module \"%s\" already loaded", name); goto out_fail; } if (info->init && info->init()) { dlclose(handle); if (err) snprintf(err, err_len, "module \"%s\" initialisation failed", name); goto out_fail; } rtinfo->finished_loading = 1; *frrmod_last = rtinfo; frrmod_last = &rtinfo->next; return rtinfo; out_fail: XFREE(MTYPE_MODULE_LOADARGS, rtinfo->load_args); XFREE(MTYPE_MODULE_LOADNAME, rtinfo->load_name); return NULL; } #if 0 void frrmod_unload(struct frrmod_runtime *module) { } #endif frr-7.2.1/lib/module.h0000644000000000000000000000566413610377563011433 00000000000000/* * Copyright (c) 2015-16 David Lamparter, for NetDEF, Inc. * * Permission to use, copy, modify, and distribute this software for any * purpose with or without fee is hereby granted, provided that the above * copyright notice and this permission notice appear in all copies. * * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. */ #ifndef _FRR_MODULE_H #define _FRR_MODULE_H #include #include #ifdef __cplusplus extern "C" { #endif struct frrmod_runtime; struct frrmod_info { /* single-line few-word title */ const char *name; /* human-readable version number, should not contain spaces */ const char *version; /* one-paragraph description */ const char *description; int (*init)(void); }; /* primary entry point structure to be present in loadable module under * "_frrmod_this_module" dlsym() name * * note space for future extensions is reserved below, so other modules * (e.g. memory management, hooks) can add fields * * const members/info are in frrmod_info. */ struct frrmod_runtime { struct frrmod_runtime *next; const struct frrmod_info *info; void *dl_handle; bool finished_loading; char *load_name; char *load_args; }; /* space-reserving foo */ struct _frrmod_runtime_size { struct frrmod_runtime r; /* this will barf if frrmod_runtime exceeds 1024 bytes ... */ uint8_t space[1024 - sizeof(struct frrmod_runtime)]; }; union _frrmod_runtime_u { struct frrmod_runtime r; struct _frrmod_runtime_size s; }; extern union _frrmod_runtime_u _frrmod_this_module; #define THIS_MODULE (&_frrmod_this_module.r) #define FRR_COREMOD_SETUP(...) \ static const struct frrmod_info _frrmod_info = {__VA_ARGS__}; \ DSO_LOCAL union _frrmod_runtime_u _frrmod_this_module = {{ \ NULL, \ &_frrmod_info, \ }}; #define FRR_MODULE_SETUP(...) \ FRR_COREMOD_SETUP(__VA_ARGS__) \ DSO_SELF struct frrmod_runtime *frr_module = &_frrmod_this_module.r; extern struct frrmod_runtime *frrmod_list; extern void frrmod_init(struct frrmod_runtime *modinfo); extern struct frrmod_runtime *frrmod_load(const char *spec, const char *dir, char *err, size_t err_len); #if 0 /* not implemented yet */ extern void frrmod_unload(struct frrmod_runtime *module); #endif #ifdef __cplusplus } #endif #endif /* _FRR_MODULE_H */ frr-7.2.1/lib/monotime.h0000644000000000000000000000626113610377563011767 00000000000000/* * Copyright (c) 2017 David Lamparter, for NetDEF, Inc. * * Permission to use, copy, modify, and distribute this software for any * purpose with or without fee is hereby granted, provided that the above * copyright notice and this permission notice appear in all copies. * * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. */ #ifndef _FRR_MONOTIME_H #define _FRR_MONOTIME_H #include #include #include #ifdef __cplusplus extern "C" { #endif #ifndef TIMESPEC_TO_TIMEVAL /* should be in sys/time.h on BSD & Linux libcs */ #define TIMESPEC_TO_TIMEVAL(tv, ts) \ do { \ (tv)->tv_sec = (ts)->tv_sec; \ (tv)->tv_usec = (ts)->tv_nsec / 1000; \ } while (0) #endif #ifndef TIMEVAL_TO_TIMESPEC /* should be in sys/time.h on BSD & Linux libcs */ #define TIMEVAL_TO_TIMESPEC(tv, ts) \ do { \ (ts)->tv_sec = (tv)->tv_sec; \ (ts)->tv_nsec = (tv)->tv_usec * 1000; \ } while (0) #endif static inline time_t monotime(struct timeval *tvo) { struct timespec ts; clock_gettime(CLOCK_MONOTONIC, &ts); if (tvo) { TIMESPEC_TO_TIMEVAL(tvo, &ts); } return ts.tv_sec; } #define ONE_DAY_SECOND 60*60*24 #define ONE_WEEK_SECOND ONE_DAY_SECOND*7 #define ONE_YEAR_SECOND ONE_DAY_SECOND*365 /* the following two return microseconds, not time_t! * * also, they're negative forms of each other, but having both makes the * code more readable */ static inline int64_t monotime_since(const struct timeval *ref, struct timeval *out) { struct timeval tv; monotime(&tv); timersub(&tv, ref, &tv); if (out) *out = tv; return (int64_t)tv.tv_sec * 1000000LL + tv.tv_usec; } static inline int64_t monotime_until(const struct timeval *ref, struct timeval *out) { struct timeval tv; monotime(&tv); timersub(ref, &tv, &tv); if (out) *out = tv; return (int64_t)tv.tv_sec * 1000000LL + tv.tv_usec; } static inline time_t monotime_to_realtime(const struct timeval *mono, struct timeval *realout) { struct timeval delta, real; monotime_since(mono, &delta); gettimeofday(&real, NULL); timersub(&real, &delta, &real); if (realout) *realout = real; return real.tv_sec; } /* Char buffer size for time-to-string api */ #define MONOTIME_STRLEN 32 static inline char *time_to_string(time_t ts, char *buf) { struct timeval tv; time_t tbuf; monotime(&tv); tbuf = time(NULL) - (tv.tv_sec - ts); return ctime_r(&tbuf, buf); } #ifdef __cplusplus } #endif #endif /* _FRR_MONOTIME_H */ frr-7.2.1/lib/mpls.c0000644000000000000000000000552713610377563011112 00000000000000/* * mpls functions * * Copyright (C) 2018 Cumulus Networks, Inc. * Donald Sharp * * This program is free software; you can redistribute it and/or modify it * under the terms of the 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; see the file COPYING; if not, write to the Free Software * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA */ #include #include #include /* * String to label conversion, labels separated by '/'. * * @param label_str labels separated by / * @param num_labels number of labels; zero if conversion was unsuccessful * @param labels preallocated mpls_label_t array of size MPLS_MAX_LABELS; only * modified if the conversion succeeded * @return 0 on success * -1 if the string could not be parsed as integers * -2 if a label was inside the reserved range (0-15) * -3 if the number of labels given exceeds MPLS_MAX_LABELS */ int mpls_str2label(const char *label_str, uint8_t *num_labels, mpls_label_t *labels) { char *ostr; // copy of label string (start) char *lstr; // copy of label string char *nump; // pointer to next segment char *endp; // end pointer int i; // for iterating label_str int rc; // return code mpls_label_t pl[MPLS_MAX_LABELS]; // parsed labels /* labels to zero until we have a successful parse */ ostr = lstr = XSTRDUP(MTYPE_TMP, label_str); *num_labels = 0; rc = 0; for (i = 0; i < MPLS_MAX_LABELS && lstr && !rc; i++) { nump = strsep(&lstr, "/"); pl[i] = strtoul(nump, &endp, 10); /* format check */ if (*endp != '\0') rc = -1; /* validity check */ else if (!IS_MPLS_UNRESERVED_LABEL(pl[i])) rc = -2; } /* excess labels */ if (!rc && i == MPLS_MAX_LABELS && lstr) rc = -3; if (!rc) { *num_labels = i; memcpy(labels, pl, *num_labels * sizeof(mpls_label_t)); } XFREE(MTYPE_TMP, ostr); return rc; } /* * Label to string conversion, labels in string separated by '/'. */ char *mpls_label2str(uint8_t num_labels, mpls_label_t *labels, char *buf, int len, int pretty) { char label_buf[BUFSIZ]; int i; buf[0] = '\0'; for (i = 0; i < num_labels; i++) { if (i != 0) strlcat(buf, "/", len); if (pretty) label2str(labels[i], label_buf, sizeof(label_buf)); else snprintf(label_buf, sizeof(label_buf), "%u", labels[i]); strlcat(buf, label_buf, len); } return buf; } frr-7.2.1/lib/mpls.h0000644000000000000000000001677413610377563011125 00000000000000/* * MPLS definitions * Copyright 2015 Cumulus Networks, Inc. * * This file is part of GNU Zebra. * * GNU Zebra is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License as published * by the Free Software Foundation; either version 2, or (at your * option) any later version. * * GNU Zebra 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; see the file COPYING; if not, write to the Free Software * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA */ #ifndef _QUAGGA_MPLS_H #define _QUAGGA_MPLS_H #include #include #ifdef __cplusplus extern "C" { #endif #ifdef MPLS_LABEL_MAX #undef MPLS_LABEL_MAX #endif #define MPLS_LABEL_HELPSTR \ "Specify label(s) for this route\nOne or more " \ "labels in the range (16-1048575) separated by '/'\n" /* Well-known MPLS label values (RFC 3032 etc). */ #define MPLS_LABEL_IPV4_EXPLICIT_NULL 0 /* [RFC3032] */ #define MPLS_LABEL_ROUTER_ALERT 1 /* [RFC3032] */ #define MPLS_LABEL_IPV6_EXPLICIT_NULL 2 /* [RFC3032] */ #define MPLS_LABEL_IMPLICIT_NULL 3 /* [RFC3032] */ #define MPLS_LABEL_ELI 7 /* [RFC6790] */ #define MPLS_LABEL_GAL 13 /* [RFC5586] */ #define MPLS_LABEL_OAM_ALERT 14 /* [RFC3429] */ #define MPLS_LABEL_EXTENSION 15 /* [RFC7274] */ #define MPLS_LABEL_MAX 1048575 #define MPLS_LABEL_NONE 0xFFFFFFFF /* for internal use only */ /* Minimum and maximum label values */ #define MPLS_LABEL_RESERVED_MIN 0 #define MPLS_LABEL_RESERVED_MAX 15 #define MPLS_LABEL_UNRESERVED_MIN 16 #define MPLS_LABEL_UNRESERVED_MAX 1048575 #define MPLS_LABEL_BASE_ANY 0 /* Default min and max SRGB label range */ /* Even if the SRGB allows to manage different Label space between routers, * if an operator want to use the same SRGB for all its router, we must fix * a common range. However, Cisco start its SRGB at 16000 and Juniper ends * its SRGB at 16384 for OSPF. Thus, by fixing the minimum SRGB label to * 8000 we could deal with both Cisco and Juniper. */ #define MPLS_DEFAULT_MIN_SRGB_LABEL 8000 #define MPLS_DEFAULT_MAX_SRGB_LABEL 50000 #define MPLS_DEFAULT_MIN_SRGB_SIZE 5000 #define MPLS_DEFAULT_MAX_SRGB_SIZE 20000 /* Maximum # labels that can be pushed. */ #define MPLS_MAX_LABELS 16 #define IS_MPLS_RESERVED_LABEL(label) \ (label >= MPLS_LABEL_RESERVED_MIN && label <= MPLS_LABEL_RESERVED_MAX) #define IS_MPLS_UNRESERVED_LABEL(label) \ (label >= MPLS_LABEL_UNRESERVED_MIN \ && label <= MPLS_LABEL_UNRESERVED_MAX) /* Definitions for a MPLS label stack entry (RFC 3032). This encodes the * label, EXP, BOS and TTL fields. */ typedef unsigned int mpls_lse_t; #define MPLS_LS_LABEL_MASK 0xFFFFF000 #define MPLS_LS_LABEL_SHIFT 12 #define MPLS_LS_EXP_MASK 0x00000E00 #define MPLS_LS_EXP_SHIFT 9 #define MPLS_LS_S_MASK 0x00000100 #define MPLS_LS_S_SHIFT 8 #define MPLS_LS_TTL_MASK 0x000000FF #define MPLS_LS_TTL_SHIFT 0 #define MPLS_LABEL_VALUE(lse) \ ((lse & MPLS_LS_LABEL_MASK) >> MPLS_LS_LABEL_SHIFT) #define MPLS_LABEL_EXP(lse) ((lse & MPLS_LS_EXP_MASK) >> MPLS_LS_EXP_SHIFT) #define MPLS_LABEL_BOS(lse) ((lse & MPLS_LS_S_MASK) >> MPLS_LS_S_SHIFT) #define MPLS_LABEL_TTL(lse) ((lse & MPLS_LS_TTL_MASK) >> MPLS_LS_TTL_SHIFT) #define IS_MPLS_LABEL_BOS(ls) (MPLS_LABEL_BOS(ls) == 1) #define MPLS_LABEL_LEN_BITS 20 /* MPLS label value as a 32-bit (mostly we only care about the label value). */ typedef unsigned int mpls_label_t; struct mpls_label_stack { uint8_t num_labels; uint8_t reserved[3]; mpls_label_t label[0]; /* 1 or more labels */ }; /* The MPLS explicit-null label is 0 which means when you memset a mpls_label_t * to zero you have set that variable to explicit-null which was probably not * your intent. The work-around is to use one bit to indicate if the * mpls_label_t has been set by the user. MPLS_INVALID_LABEL has this bit clear * so that we can use MPLS_INVALID_LABEL to initialize mpls_label_t variables. */ #define MPLS_INVALID_LABEL 0xFFFDFFFF /* LSP types. */ enum lsp_types_t { ZEBRA_LSP_NONE = 0, /* No LSP. */ ZEBRA_LSP_STATIC = 1, /* Static LSP. */ ZEBRA_LSP_LDP = 2, /* LDP LSP. */ ZEBRA_LSP_BGP = 3, /* BGP LSP. */ ZEBRA_LSP_SR = 4, /* Segment Routing LSP. */ ZEBRA_LSP_SHARP = 5, /* Identifier for test protocol */ }; /* Functions for basic label operations. */ /* Encode a label stack entry from fields; convert to network byte-order as * the Netlink interface expects MPLS labels to be in this format. */ static inline mpls_lse_t mpls_lse_encode(mpls_label_t label, uint32_t ttl, uint32_t exp, uint32_t bos) { mpls_lse_t lse; lse = htonl((label << MPLS_LS_LABEL_SHIFT) | (exp << MPLS_LS_EXP_SHIFT) | (bos ? (1 << MPLS_LS_S_SHIFT) : 0) | (ttl << MPLS_LS_TTL_SHIFT)); return lse; } /* Extract the fields from a label stack entry after converting to host-byte * order. This is expected to be called only for messages received over the * Netlink interface. */ static inline void mpls_lse_decode(mpls_lse_t lse, mpls_label_t *label, uint32_t *ttl, uint32_t *exp, uint32_t *bos) { mpls_lse_t local_lse; local_lse = ntohl(lse); *label = MPLS_LABEL_VALUE(local_lse); *exp = MPLS_LABEL_EXP(local_lse); *bos = MPLS_LABEL_BOS(local_lse); *ttl = MPLS_LABEL_TTL(local_lse); } /* Invalid label index value (when used with BGP Prefix-SID). Should * match the BGP definition. */ #define MPLS_INVALID_LABEL_INDEX 0xFFFFFFFF /* Printable string for labels (with consideration for reserved values). */ static inline char *label2str(mpls_label_t label, char *buf, size_t len) { switch (label) { case MPLS_LABEL_IPV4_EXPLICIT_NULL: strlcpy(buf, "IPv4 Explicit Null", len); return (buf); case MPLS_LABEL_ROUTER_ALERT: strlcpy(buf, "Router Alert", len); return (buf); case MPLS_LABEL_IPV6_EXPLICIT_NULL: strlcpy(buf, "IPv6 Explicit Null", len); return (buf); case MPLS_LABEL_IMPLICIT_NULL: strlcpy(buf, "implicit-null", len); return (buf); case MPLS_LABEL_ELI: strlcpy(buf, "Entropy Label Indicator", len); return (buf); case MPLS_LABEL_GAL: strlcpy(buf, "Generic Associated Channel", len); return (buf); case MPLS_LABEL_OAM_ALERT: strlcpy(buf, "OAM Alert", len); return (buf); case MPLS_LABEL_EXTENSION: strlcpy(buf, "Extension", len); return (buf); default: if (label < 16) snprintf(buf, len, "Reserved (%u)", label); else snprintf(buf, len, "%u", label); return (buf); } } /* * String to label conversion, labels separated by '/'. */ int mpls_str2label(const char *label_str, uint8_t *num_labels, mpls_label_t *labels); /* * Label to string conversion, labels in string separated by '/'. */ char *mpls_label2str(uint8_t num_labels, mpls_label_t *labels, char *buf, int len, int pretty); #ifdef __cplusplus } #endif #endif frr-7.2.1/lib/netns_linux.c0000644000000000000000000003101513610377563012474 00000000000000/* * NS functions. * Copyright (C) 2014 6WIND S.A. * * This file is part of GNU Zebra. * * GNU Zebra is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License as published * by the Free Software Foundation; either version 2, or (at your * option) any later version. * * GNU Zebra 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; see the file COPYING; if not, write to the Free Software * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA */ #include #ifdef HAVE_NETNS #undef _GNU_SOURCE #define _GNU_SOURCE #include #endif /* for basename */ #include #include "if.h" #include "ns.h" #include "log.h" #include "memory.h" #include "command.h" #include "vty.h" #include "vrf.h" #include "lib_errors.h" DEFINE_MTYPE_STATIC(LIB, NS, "NetNS Context") DEFINE_MTYPE_STATIC(LIB, NS_NAME, "NetNS Name") /* default NS ID value used when VRF backend is not NETNS */ #define NS_DEFAULT_INTERNAL 0 static inline int ns_compare(const struct ns *ns, const struct ns *ns2); static struct ns *ns_lookup_name_internal(const char *name); RB_GENERATE(ns_head, ns, entry, ns_compare) struct ns_head ns_tree = RB_INITIALIZER(&ns_tree); static struct ns *default_ns; static int ns_current_ns_fd; static int ns_default_ns_fd; static int ns_debug; struct ns_map_nsid { RB_ENTRY(ns_map_nsid) id_entry; ns_id_t ns_id_external; ns_id_t ns_id; }; static inline int ns_map_compare(const struct ns_map_nsid *a, const struct ns_map_nsid *b) { return (a->ns_id - b->ns_id); } RB_HEAD(ns_map_nsid_head, ns_map_nsid); RB_PROTOTYPE(ns_map_nsid_head, ns_map_nsid, id_entry, ns_map_compare); RB_GENERATE(ns_map_nsid_head, ns_map_nsid, id_entry, ns_map_compare); struct ns_map_nsid_head ns_map_nsid_list = RB_INITIALIZER(&ns_map_nsid_list); static ns_id_t ns_id_external_numbering; #ifndef CLONE_NEWNET #define CLONE_NEWNET 0x40000000 /* New network namespace (lo, device, names sockets, etc) */ #endif #ifndef HAVE_SETNS static inline int setns(int fd, int nstype) { #ifdef __NR_setns return syscall(__NR_setns, fd, nstype); #else errno = EINVAL; return -1; #endif } #endif /* !HAVE_SETNS */ #ifdef HAVE_NETNS static int have_netns_enabled = -1; #endif /* HAVE_NETNS */ /* default NS ID value used when VRF backend is not NETNS */ #define NS_DEFAULT_INTERNAL 0 static int have_netns(void) { #ifdef HAVE_NETNS if (have_netns_enabled < 0) { int fd = open(NS_DEFAULT_NAME, O_RDONLY); if (fd < 0) have_netns_enabled = 0; else { have_netns_enabled = 1; close(fd); } } return have_netns_enabled; #else return 0; #endif } /* Holding NS hooks */ struct ns_master { int (*ns_new_hook)(struct ns *ns); int (*ns_delete_hook)(struct ns *ns); int (*ns_enable_hook)(struct ns *ns); int (*ns_disable_hook)(struct ns *ns); } ns_master = { 0, }; static int ns_is_enabled(struct ns *ns); static inline int ns_compare(const struct ns *a, const struct ns *b) { return (a->ns_id - b->ns_id); } /* Look up a NS by identifier. */ static struct ns *ns_lookup_internal(ns_id_t ns_id) { struct ns ns; ns.ns_id = ns_id; return RB_FIND(ns_head, &ns_tree, &ns); } /* Look up a NS by name */ static struct ns *ns_lookup_name_internal(const char *name) { struct ns *ns = NULL; RB_FOREACH (ns, ns_head, &ns_tree) { if (ns->name != NULL) { if (strcmp(name, ns->name) == 0) return ns; } } return NULL; } static struct ns *ns_get_created_internal(struct ns *ns, char *name, ns_id_t ns_id) { int created = 0; /* * Initialize interfaces. */ if (!ns && !name && ns_id != NS_UNKNOWN) ns = ns_lookup_internal(ns_id); if (!ns && name) ns = ns_lookup_name_internal(name); if (!ns) { ns = XCALLOC(MTYPE_NS, sizeof(struct ns)); ns->ns_id = ns_id; if (name) ns->name = XSTRDUP(MTYPE_NS_NAME, name); ns->fd = -1; RB_INSERT(ns_head, &ns_tree, ns); created = 1; } if (ns_id != ns->ns_id) { RB_REMOVE(ns_head, &ns_tree, ns); ns->ns_id = ns_id; RB_INSERT(ns_head, &ns_tree, ns); } if (!created) return ns; if (ns_debug) { if (ns->ns_id != NS_UNKNOWN) zlog_info("NS %u is created.", ns->ns_id); else zlog_info("NS %s is created.", ns->name); } if (ns_master.ns_new_hook) (*ns_master.ns_new_hook)(ns); return ns; } /* * Enable a NS - that is, let the NS be ready to use. * The NS_ENABLE_HOOK callback will be called to inform * that they can allocate resources in this NS. * * RETURN: 1 - enabled successfully; otherwise, 0. */ static int ns_enable_internal(struct ns *ns, void (*func)(ns_id_t, void *)) { if (!ns_is_enabled(ns)) { if (have_netns()) { ns->fd = open(ns->name, O_RDONLY); } else { ns->fd = -2; /* Remember ns_enable_hook has been called */ errno = -ENOTSUP; } if (!ns_is_enabled(ns)) { flog_err_sys(EC_LIB_SYSTEM_CALL, "Can not enable NS %u: %s!", ns->ns_id, safe_strerror(errno)); return 0; } /* Non default NS. leave */ if (ns->ns_id == NS_UNKNOWN) { flog_err(EC_LIB_NS, "Can not enable NS %s %u: Invalid NSID", ns->name, ns->ns_id); return 0; } if (func) func(ns->ns_id, (void *)ns->vrf_ctxt); if (ns_debug) { if (have_netns()) zlog_info("NS %u is associated with NETNS %s.", ns->ns_id, ns->name); zlog_info("NS %u is enabled.", ns->ns_id); } /* zebra first receives NS enable event, * then VRF enable event */ if (ns_master.ns_enable_hook) (*ns_master.ns_enable_hook)(ns); } return 1; } /* * Check whether the NS is enabled - that is, whether the NS * is ready to allocate resources. Currently there's only one * type of resource: socket. */ static int ns_is_enabled(struct ns *ns) { if (have_netns()) return ns && ns->fd >= 0; else return ns && ns->fd == -2 && ns->ns_id == NS_DEFAULT; } /* * Disable a NS - that is, let the NS be unusable. * The NS_DELETE_HOOK callback will be called to inform * that they must release the resources in the NS. */ static void ns_disable_internal(struct ns *ns) { if (ns_is_enabled(ns)) { if (ns_debug) zlog_info("NS %u is to be disabled.", ns->ns_id); if (ns_master.ns_disable_hook) (*ns_master.ns_disable_hook)(ns); if (have_netns()) close(ns->fd); ns->fd = -1; } } /* VRF list existance check by name. */ static struct ns_map_nsid *ns_map_nsid_lookup_by_nsid(ns_id_t ns_id) { struct ns_map_nsid ns_map; ns_map.ns_id = ns_id; return RB_FIND(ns_map_nsid_head, &ns_map_nsid_list, &ns_map); } ns_id_t ns_map_nsid_with_external(ns_id_t ns_id, bool map) { struct ns_map_nsid *ns_map; vrf_id_t ns_id_external; ns_map = ns_map_nsid_lookup_by_nsid(ns_id); if (ns_map && !map) { ns_id_external = ns_map->ns_id_external; RB_REMOVE(ns_map_nsid_head, &ns_map_nsid_list, ns_map); return ns_id_external; } if (ns_map) return ns_map->ns_id_external; ns_map = XCALLOC(MTYPE_NS, sizeof(struct ns_map_nsid)); /* increase vrf_id * default vrf is the first one : 0 */ ns_map->ns_id_external = ns_id_external_numbering++; ns_map->ns_id = ns_id; RB_INSERT(ns_map_nsid_head, &ns_map_nsid_list, ns_map); return ns_map->ns_id_external; } struct ns *ns_get_created(struct ns *ns, char *name, ns_id_t ns_id) { return ns_get_created_internal(ns, name, ns_id); } int ns_have_netns(void) { return have_netns(); } /* Delete a NS. This is called in ns_terminate(). */ void ns_delete(struct ns *ns) { if (ns_debug) zlog_info("NS %u is to be deleted.", ns->ns_id); ns_disable(ns); if (ns_master.ns_delete_hook) (*ns_master.ns_delete_hook)(ns); /* * I'm not entirely sure if the vrf->iflist * needs to be moved into here or not. */ // if_terminate (&ns->iflist); RB_REMOVE(ns_head, &ns_tree, ns); XFREE(MTYPE_NS_NAME, ns->name); XFREE(MTYPE_NS, ns); } /* Look up the data pointer of the specified VRF. */ void *ns_info_lookup(ns_id_t ns_id) { struct ns *ns = ns_lookup_internal(ns_id); return ns ? ns->info : NULL; } /* Look up a NS by name */ struct ns *ns_lookup_name(const char *name) { return ns_lookup_name_internal(name); } int ns_enable(struct ns *ns, void (*func)(ns_id_t, void *)) { return ns_enable_internal(ns, func); } void ns_disable(struct ns *ns) { return ns_disable_internal(ns); } struct ns *ns_lookup(ns_id_t ns_id) { return ns_lookup_internal(ns_id); } void ns_walk_func(int (*func)(struct ns *)) { struct ns *ns = NULL; RB_FOREACH (ns, ns_head, &ns_tree) func(ns); } const char *ns_get_name(struct ns *ns) { if (!ns) return NULL; return ns->name; } /* Add a NS hook. Please add hooks before calling ns_init(). */ void ns_add_hook(int type, int (*func)(struct ns *)) { switch (type) { case NS_NEW_HOOK: ns_master.ns_new_hook = func; break; case NS_DELETE_HOOK: ns_master.ns_delete_hook = func; break; case NS_ENABLE_HOOK: ns_master.ns_enable_hook = func; break; case NS_DISABLE_HOOK: ns_master.ns_disable_hook = func; break; default: break; } } /* * NS realization with NETNS */ char *ns_netns_pathname(struct vty *vty, const char *name) { static char pathname[PATH_MAX]; char *result; char *check_base; if (name[0] == '/') /* absolute pathname */ result = realpath(name, pathname); else { /* relevant pathname */ char tmp_name[PATH_MAX]; snprintf(tmp_name, PATH_MAX, "%s/%s", NS_RUN_DIR, name); result = realpath(tmp_name, pathname); } if (!result) { if (vty) vty_out(vty, "Invalid pathname for %s: %s\n", pathname, safe_strerror(errno)); else flog_warn(EC_LIB_LINUX_NS, "Invalid pathname for %s: %s", pathname, safe_strerror(errno)); return NULL; } check_base = basename(pathname); if (check_base != NULL && strlen(check_base) + 1 > NS_NAMSIZ) { if (vty) vty_out(vty, "NS name (%s) invalid: too long (>%d)\n", check_base, NS_NAMSIZ - 1); else flog_warn(EC_LIB_LINUX_NS, "NS name (%s) invalid: too long (>%d)", check_base, NS_NAMSIZ - 1); return NULL; } return pathname; } void ns_init(void) { static int ns_initialised; ns_debug = 0; /* silently return as initialisation done */ if (ns_initialised == 1) return; errno = 0; if (have_netns()) ns_default_ns_fd = open(NS_DEFAULT_NAME, O_RDONLY); else { ns_default_ns_fd = -1; default_ns = NULL; } ns_current_ns_fd = -1; ns_initialised = 1; } /* Initialize NS module. */ void ns_init_management(ns_id_t default_ns_id, ns_id_t internal_ns) { int fd; ns_init(); default_ns = ns_get_created_internal(NULL, NULL, default_ns_id); if (!default_ns) { flog_err(EC_LIB_NS, "%s: failed to create the default NS!", __func__); exit(1); } if (have_netns()) { fd = open(NS_DEFAULT_NAME, O_RDONLY); default_ns->fd = fd; } default_ns->internal_ns_id = internal_ns; /* Set the default NS name. */ default_ns->name = XSTRDUP(MTYPE_NS_NAME, NS_DEFAULT_NAME); if (ns_debug) zlog_info("%s: default NSID is %u", __func__, default_ns->ns_id); /* Enable the default NS. */ if (!ns_enable(default_ns, NULL)) { flog_err(EC_LIB_NS, "%s: failed to enable the default NS!", __func__); exit(1); } } /* Terminate NS module. */ void ns_terminate(void) { struct ns *ns; while (!RB_EMPTY(ns_head, &ns_tree)) { ns = RB_ROOT(ns_head, &ns_tree); ns_delete(ns); } } int ns_switch_to_netns(const char *name) { int ret; int fd; if (name == NULL) return -1; if (ns_default_ns_fd == -1) return -1; fd = open(name, O_RDONLY); if (fd == -1) { errno = EINVAL; return -1; } ret = setns(fd, CLONE_NEWNET); ns_current_ns_fd = fd; close(fd); return ret; } /* returns 1 if switch() was not called before * return status of setns() otherwise */ int ns_switchback_to_initial(void) { if (ns_current_ns_fd != -1 && ns_default_ns_fd != -1) { int ret; ret = setns(ns_default_ns_fd, CLONE_NEWNET); ns_current_ns_fd = -1; return ret; } /* silently ignore if setns() is not called */ return 1; } /* Create a socket for the NS. */ int ns_socket(int domain, int type, int protocol, ns_id_t ns_id) { struct ns *ns = ns_lookup(ns_id); int ret; if (!ns || !ns_is_enabled(ns)) { errno = EINVAL; return -1; } if (have_netns()) { ret = (ns_id != NS_DEFAULT) ? setns(ns->fd, CLONE_NEWNET) : 0; if (ret >= 0) { ret = socket(domain, type, protocol); if (ns_id != NS_DEFAULT) { setns(ns_lookup(NS_DEFAULT)->fd, CLONE_NEWNET); ns_current_ns_fd = ns_id; } } } else ret = socket(domain, type, protocol); return ret; } ns_id_t ns_get_default_id(void) { if (default_ns) return default_ns->ns_id; return NS_DEFAULT_INTERNAL; } frr-7.2.1/lib/netns_other.c0000644000000000000000000000660413610377563012464 00000000000000/* * NetNS backend for non Linux systems * Copyright (C) 2018 6WIND S.A. * * This program is free software; you can redistribute it and/or modify it * under the terms of the 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; see the file COPYING; if not, write to the Free Software * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA */ #if !defined(GNU_LINUX) && (defined(SUNOS_5) || defined(OPEN_BSD)) /* SUNOS_5 or OPEN_BSD */ #include #include "ns.h" #include "log.h" #include "memory.h" DEFINE_MTYPE_STATIC(LIB, NS, "NetNS Context") DEFINE_MTYPE_STATIC(LIB, NS_NAME, "NetNS Name") static inline int ns_compare(const struct ns *ns, const struct ns *ns2); RB_GENERATE(ns_head, ns, entry, ns_compare) struct ns_head ns_tree = RB_INITIALIZER(&ns_tree); static inline int ns_compare(const struct ns *a, const struct ns *b) { return (a->ns_id - b->ns_id); } void ns_terminate(void) { } /* API to initialize NETNS managerment * parameter is the default ns_id */ void ns_init_management(ns_id_t ns_id) { } /* * NS utilities */ /* Create a socket serving for the given NS */ int ns_socket(int domain, int type, int protocol, ns_id_t ns_id) { return -1; } /* return the path of the NETNS */ char *ns_netns_pathname(struct vty *vty, const char *name) { return NULL; } /* Parse and execute a function on all the NETNS */ void ns_walk_func(int (*func)(struct ns *)) { } /* API to get the NETNS name, from the ns pointer */ const char *ns_get_name(struct ns *ns) { return NULL; } /* only called from vrf ( when removing netns from vrf) * or at VRF termination */ void ns_delete(struct ns *ns) { } /* return > 0 if netns is available * called by VRF to check netns backend is available for VRF */ int ns_have_netns(void) { return 0; } /* API to get context information of a NS */ void *ns_info_lookup(ns_id_t ns_id) { return NULL; } /* * NS init routine * should be called from backendx */ void ns_init(void) { } /* API to retrieve default NS */ ns_id_t ns_get_default_id(void) { return NS_UNKNOWN; } /* API that can be used to change from NS */ int ns_switchback_to_initial(void) { return 0; } int ns_switch_to_netns(const char *netns_name) { return 0; } /* * NS handling routines. * called by modules that use NS backend */ /* API to search for already present NETNS */ struct ns *ns_lookup(ns_id_t ns_id) { return NULL; } struct ns *ns_lookup_name(const char *name) { return NULL; } /* API to handle NS : creation, enable, disable * for enable, a callback function is passed as parameter * the callback belongs to the module that uses NS as backend * upon enabling the NETNS, the upper layer is informed */ int ns_enable(struct ns *ns, int (*func)(ns_id_t, void *)) { return 0; } ns_id_t ns_map_nsid_with_external(ns_id_t ns_id, bool maporunmap) { return NS_UNKNOWN; } struct ns *ns_get_created(struct ns *ns, char *name, ns_id_t ns_id) { return NULL; } void ns_disable(struct ns *ns) { } #endif /* !GNU_LINUX */ frr-7.2.1/lib/network.c0000644000000000000000000000475313610377563011630 00000000000000/* * Network library. * Copyright (C) 1997 Kunihiro Ishiguro * * This file is part of GNU Zebra. * * GNU Zebra is free software; you can redistribute it and/or modify it * under the terms of the GNU General Public License as published by the * Free Software Foundation; either version 2, or (at your option) any * later version. * * GNU Zebra 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; see the file COPYING; if not, write to the Free Software * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA */ #include #include "log.h" #include "network.h" #include "lib_errors.h" /* Read nbytes from fd and store into ptr. */ int readn(int fd, uint8_t *ptr, int nbytes) { int nleft; int nread; nleft = nbytes; while (nleft > 0) { nread = read(fd, ptr, nleft); if (nread < 0) return (nread); else if (nread == 0) break; nleft -= nread; ptr += nread; } return nbytes - nleft; } /* Write nbytes from ptr to fd. */ int writen(int fd, const uint8_t *ptr, int nbytes) { int nleft; int nwritten; nleft = nbytes; while (nleft > 0) { nwritten = write(fd, ptr, nleft); if (nwritten < 0) { if (!ERRNO_IO_RETRY(errno)) return nwritten; } if (nwritten == 0) return (nwritten); nleft -= nwritten; ptr += nwritten; } return nbytes - nleft; } int set_nonblocking(int fd) { int flags; /* According to the Single UNIX Spec, the return value for F_GETFL should never be negative. */ if ((flags = fcntl(fd, F_GETFL)) < 0) { flog_err(EC_LIB_SYSTEM_CALL, "fcntl(F_GETFL) failed for fd %d: %s", fd, safe_strerror(errno)); return -1; } if (fcntl(fd, F_SETFL, (flags | O_NONBLOCK)) < 0) { flog_err(EC_LIB_SYSTEM_CALL, "fcntl failed setting fd %d non-blocking: %s", fd, safe_strerror(errno)); return -1; } return 0; } int set_cloexec(int fd) { int flags; flags = fcntl(fd, F_GETFD, 0); if (flags == -1) return -1; flags |= FD_CLOEXEC; if (fcntl(fd, F_SETFD, flags) == -1) return -1; return 0; } float htonf(float host) { uint32_t lu1, lu2; float convert; memcpy(&lu1, &host, sizeof(uint32_t)); lu2 = htonl(lu1); memcpy(&convert, &lu2, sizeof(uint32_t)); return convert; } float ntohf(float net) { return htonf(net); } frr-7.2.1/lib/network.h0000644000000000000000000000315413610377563011627 00000000000000/* * Network library header. * Copyright (C) 1998 Kunihiro Ishiguro * * This file is part of GNU Zebra. * * GNU Zebra is free software; you can redistribute it and/or modify it * under the terms of the GNU General Public License as published by the * Free Software Foundation; either version 2, or (at your option) any * later version. * * GNU Zebra 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; see the file COPYING; if not, write to the Free Software * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA */ #ifndef _ZEBRA_NETWORK_H #define _ZEBRA_NETWORK_H #ifdef __cplusplus extern "C" { #endif /* Both readn and writen are deprecated and will be removed. They are not suitable for use with non-blocking file descriptors. */ extern int readn(int, uint8_t *, int); extern int writen(int, const uint8_t *, int); /* Set the file descriptor to use non-blocking I/O. Returns 0 for success, -1 on error. */ extern int set_nonblocking(int fd); extern int set_cloexec(int fd); /* Does the I/O error indicate that the operation should be retried later? */ #define ERRNO_IO_RETRY(EN) \ (((EN) == EAGAIN) || ((EN) == EWOULDBLOCK) || ((EN) == EINTR)) extern float htonf(float); extern float ntohf(float); #ifdef __cplusplus } #endif #endif /* _ZEBRA_NETWORK_H */ frr-7.2.1/lib/nexthop.c0000644000000000000000000003025713610377563011622 00000000000000/* A generic nexthop structure * Copyright (C) 2013 Cumulus Networks, Inc. * * This file is part of Quagga. * * Quagga is free software; you can redistribute it and/or modify it * under the terms of the GNU General Public License as published by the * Free Software Foundation; either version 2, or (at your option) any * later version. * * Quagga 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; see the file COPYING; if not, write to the Free Software * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA */ #include #include "prefix.h" #include "table.h" #include "memory.h" #include "command.h" #include "if.h" #include "log.h" #include "sockunion.h" #include "linklist.h" #include "thread.h" #include "prefix.h" #include "nexthop.h" #include "mpls.h" #include "jhash.h" #include "printfrr.h" DEFINE_MTYPE_STATIC(LIB, NEXTHOP, "Nexthop") DEFINE_MTYPE_STATIC(LIB, NH_LABEL, "Nexthop label") static int _nexthop_labels_cmp(const struct nexthop *nh1, const struct nexthop *nh2) { const struct mpls_label_stack *nhl1 = NULL; const struct mpls_label_stack *nhl2 = NULL; nhl1 = nh1->nh_label; nhl2 = nh2->nh_label; /* No labels is a match */ if (!nhl1 && !nhl2) return 0; if (nhl1 && !nhl2) return 1; if (nhl2 && !nhl1) return -1; if (nhl1->num_labels > nhl2->num_labels) return 1; if (nhl1->num_labels < nhl2->num_labels) return -1; return memcmp(nhl1->label, nhl2->label, nhl1->num_labels); } int nexthop_g_addr_cmp(enum nexthop_types_t type, const union g_addr *addr1, const union g_addr *addr2) { int ret = 0; switch (type) { case NEXTHOP_TYPE_IPV4: case NEXTHOP_TYPE_IPV4_IFINDEX: ret = IPV4_ADDR_CMP(&addr1->ipv4, &addr2->ipv4); break; case NEXTHOP_TYPE_IPV6: case NEXTHOP_TYPE_IPV6_IFINDEX: ret = IPV6_ADDR_CMP(&addr1->ipv6, &addr2->ipv6); break; case NEXTHOP_TYPE_IFINDEX: case NEXTHOP_TYPE_BLACKHOLE: /* No addr here */ break; } return ret; } static int _nexthop_gateway_cmp(const struct nexthop *nh1, const struct nexthop *nh2) { return nexthop_g_addr_cmp(nh1->type, &nh1->gate, &nh2->gate); } static int _nexthop_source_cmp(const struct nexthop *nh1, const struct nexthop *nh2) { return nexthop_g_addr_cmp(nh1->type, &nh1->src, &nh2->src); } static int _nexthop_cmp_no_labels(const struct nexthop *next1, const struct nexthop *next2) { int ret = 0; if (next1->vrf_id < next2->vrf_id) return -1; if (next1->vrf_id > next2->vrf_id) return 1; if (next1->type < next2->type) return -1; if (next1->type > next2->type) return 1; switch (next1->type) { case NEXTHOP_TYPE_IPV4: case NEXTHOP_TYPE_IPV6: ret = _nexthop_gateway_cmp(next1, next2); if (ret != 0) return ret; break; case NEXTHOP_TYPE_IPV4_IFINDEX: case NEXTHOP_TYPE_IPV6_IFINDEX: ret = _nexthop_gateway_cmp(next1, next2); if (ret != 0) return ret; /* Intentional Fall-Through */ case NEXTHOP_TYPE_IFINDEX: if (next1->ifindex < next2->ifindex) return -1; if (next1->ifindex > next2->ifindex) return 1; break; case NEXTHOP_TYPE_BLACKHOLE: if (next1->bh_type < next2->bh_type) return -1; if (next1->bh_type > next2->bh_type) return 1; break; } ret = _nexthop_source_cmp(next1, next2); return ret; } int nexthop_cmp(const struct nexthop *next1, const struct nexthop *next2) { int ret = 0; ret = _nexthop_cmp_no_labels(next1, next2); if (ret != 0) return ret; ret = _nexthop_labels_cmp(next1, next2); return ret; } int nexthop_same_firsthop(struct nexthop *next1, struct nexthop *next2) { int type1 = NEXTHOP_FIRSTHOPTYPE(next1->type); int type2 = NEXTHOP_FIRSTHOPTYPE(next2->type); if (type1 != type2) return 0; switch (type1) { case NEXTHOP_TYPE_IPV4_IFINDEX: if (!IPV4_ADDR_SAME(&next1->gate.ipv4, &next2->gate.ipv4)) return 0; if (next1->ifindex != next2->ifindex) return 0; break; case NEXTHOP_TYPE_IFINDEX: if (next1->ifindex != next2->ifindex) return 0; break; case NEXTHOP_TYPE_IPV6_IFINDEX: if (!IPV6_ADDR_SAME(&next1->gate.ipv6, &next2->gate.ipv6)) return 0; if (next1->ifindex != next2->ifindex) return 0; break; default: /* do nothing */ break; } return 1; } /* * nexthop_type_to_str */ const char *nexthop_type_to_str(enum nexthop_types_t nh_type) { static const char *desc[] = { "none", "Directly connected", "IPv4 nexthop", "IPv4 nexthop with ifindex", "IPv6 nexthop", "IPv6 nexthop with ifindex", "Null0 nexthop", }; return desc[nh_type]; } /* * Check if the labels match for the 2 nexthops specified. */ bool nexthop_labels_match(const struct nexthop *nh1, const struct nexthop *nh2) { if (_nexthop_labels_cmp(nh1, nh2) != 0) return false; return true; } struct nexthop *nexthop_new(void) { return XCALLOC(MTYPE_NEXTHOP, sizeof(struct nexthop)); } /* Free nexthop. */ void nexthop_free(struct nexthop *nexthop) { nexthop_del_labels(nexthop); if (nexthop->resolved) nexthops_free(nexthop->resolved); XFREE(MTYPE_NEXTHOP, nexthop); } /* Frees a list of nexthops */ void nexthops_free(struct nexthop *nexthop) { struct nexthop *nh, *next; for (nh = nexthop; nh; nh = next) { next = nh->next; nexthop_free(nh); } } bool nexthop_same(const struct nexthop *nh1, const struct nexthop *nh2) { if (nh1 && !nh2) return false; if (!nh1 && nh2) return false; if (nh1 == nh2) return true; if (nexthop_cmp(nh1, nh2) != 0) return false; return true; } bool nexthop_same_no_labels(const struct nexthop *nh1, const struct nexthop *nh2) { if (nh1 && !nh2) return false; if (!nh1 && nh2) return false; if (nh1 == nh2) return true; if (_nexthop_cmp_no_labels(nh1, nh2) != 0) return false; return true; } /* Update nexthop with label information. */ void nexthop_add_labels(struct nexthop *nexthop, enum lsp_types_t type, uint8_t num_labels, mpls_label_t *label) { struct mpls_label_stack *nh_label; int i; nexthop->nh_label_type = type; nh_label = XCALLOC(MTYPE_NH_LABEL, sizeof(struct mpls_label_stack) + num_labels * sizeof(mpls_label_t)); nh_label->num_labels = num_labels; for (i = 0; i < num_labels; i++) nh_label->label[i] = *(label + i); nexthop->nh_label = nh_label; } /* Free label information of nexthop, if present. */ void nexthop_del_labels(struct nexthop *nexthop) { if (nexthop->nh_label) { XFREE(MTYPE_NH_LABEL, nexthop->nh_label); nexthop->nh_label_type = ZEBRA_LSP_NONE; } } const char *nexthop2str(const struct nexthop *nexthop, char *str, int size) { switch (nexthop->type) { case NEXTHOP_TYPE_IFINDEX: snprintf(str, size, "if %u", nexthop->ifindex); break; case NEXTHOP_TYPE_IPV4: case NEXTHOP_TYPE_IPV4_IFINDEX: snprintf(str, size, "%s if %u", inet_ntoa(nexthop->gate.ipv4), nexthop->ifindex); break; case NEXTHOP_TYPE_IPV6: case NEXTHOP_TYPE_IPV6_IFINDEX: snprintf(str, size, "%s if %u", inet6_ntoa(nexthop->gate.ipv6), nexthop->ifindex); break; case NEXTHOP_TYPE_BLACKHOLE: snprintf(str, size, "blackhole"); break; default: snprintf(str, size, "unknown"); break; } return str; } /* * Iteration step for ALL_NEXTHOPS macro: * This is the tricky part. Check if `nexthop' has * NEXTHOP_FLAG_RECURSIVE set. If yes, this implies that `nexthop' has * at least one nexthop attached to `nexthop->resolved', which will be * the next one. * * If NEXTHOP_FLAG_RECURSIVE is not set, `nexthop' will progress in its * current chain. In case its current chain end is reached, it will move * upwards in the recursion levels and progress there. Whenever a step * forward in a chain is done, recursion will be checked again. * In a nustshell, it's equivalent to a pre-traversal order assuming that * left branch is 'resolved' and right branch is 'next': * https://en.wikipedia.org/wiki/Tree_traversal#/media/File:Sorted_binary_tree_preorder.svg */ struct nexthop *nexthop_next(struct nexthop *nexthop) { if (CHECK_FLAG(nexthop->flags, NEXTHOP_FLAG_RECURSIVE)) return nexthop->resolved; if (nexthop->next) return nexthop->next; for (struct nexthop *par = nexthop->rparent; par; par = par->rparent) if (par->next) return par->next; return NULL; } unsigned int nexthop_level(struct nexthop *nexthop) { unsigned int rv = 0; for (struct nexthop *par = nexthop->rparent; par; par = par->rparent) rv++; return rv; } uint32_t nexthop_hash(const struct nexthop *nexthop) { uint32_t key = 0x45afe398; key = jhash_3words(nexthop->type, nexthop->vrf_id, nexthop->nh_label_type, key); /* gate and blackhole are together in a union */ key = jhash(&nexthop->gate, sizeof(nexthop->gate), key); key = jhash(&nexthop->src, sizeof(nexthop->src), key); key = jhash(&nexthop->rmap_src, sizeof(nexthop->rmap_src), key); if (nexthop->nh_label) { int labels = nexthop->nh_label->num_labels; int i = 0; while (labels >= 3) { key = jhash_3words(nexthop->nh_label->label[i], nexthop->nh_label->label[i + 1], nexthop->nh_label->label[i + 2], key); labels -= 3; i += 3; } if (labels >= 2) { key = jhash_2words(nexthop->nh_label->label[i], nexthop->nh_label->label[i + 1], key); labels -= 2; i += 2; } if (labels >= 1) key = jhash_1word(nexthop->nh_label->label[i], key); } switch (nexthop->type) { case NEXTHOP_TYPE_IPV4_IFINDEX: case NEXTHOP_TYPE_IPV6_IFINDEX: case NEXTHOP_TYPE_IFINDEX: key = jhash_1word(nexthop->ifindex, key); break; case NEXTHOP_TYPE_BLACKHOLE: case NEXTHOP_TYPE_IPV4: case NEXTHOP_TYPE_IPV6: break; } return key; } void nexthop_copy(struct nexthop *copy, const struct nexthop *nexthop, struct nexthop *rparent) { copy->vrf_id = nexthop->vrf_id; copy->ifindex = nexthop->ifindex; copy->type = nexthop->type; copy->flags = nexthop->flags; memcpy(©->gate, &nexthop->gate, sizeof(nexthop->gate)); memcpy(©->src, &nexthop->src, sizeof(nexthop->src)); memcpy(©->rmap_src, &nexthop->rmap_src, sizeof(nexthop->rmap_src)); copy->rparent = rparent; if (nexthop->nh_label) nexthop_add_labels(copy, nexthop->nh_label_type, nexthop->nh_label->num_labels, &nexthop->nh_label->label[0]); } struct nexthop *nexthop_dup(const struct nexthop *nexthop, struct nexthop *rparent) { struct nexthop *new = nexthop_new(); nexthop_copy(new, nexthop, rparent); return new; } /* * nexthop printing variants: * %pNHvv * via 1.2.3.4 * via 1.2.3.4, eth0 * is directly connected, eth0 * unreachable (blackhole) * %pNHv * 1.2.3.4 * 1.2.3.4, via eth0 * directly connected, eth0 * unreachable (blackhole) * %pNHs * nexthop2str() */ printfrr_ext_autoreg_p("NH", printfrr_nh) static ssize_t printfrr_nh(char *buf, size_t bsz, const char *fmt, int prec, const void *ptr) { const struct nexthop *nexthop = ptr; struct fbuf fb = { .buf = buf, .pos = buf, .len = bsz - 1 }; bool do_ifi = false; const char *s, *v_is = "", *v_via = "", *v_viaif = "via "; ssize_t ret = 3; switch (fmt[2]) { case 'v': if (fmt[3] == 'v') { v_is = "is "; v_via = "via "; v_viaif = ""; ret++; } switch (nexthop->type) { case NEXTHOP_TYPE_IPV4: case NEXTHOP_TYPE_IPV4_IFINDEX: bprintfrr(&fb, "%s%pI4", v_via, &nexthop->gate.ipv4); do_ifi = true; break; case NEXTHOP_TYPE_IPV6: case NEXTHOP_TYPE_IPV6_IFINDEX: bprintfrr(&fb, "%s%pI6", v_via, &nexthop->gate.ipv6); do_ifi = true; break; case NEXTHOP_TYPE_IFINDEX: bprintfrr(&fb, "%sdirectly connected, %s", v_is, ifindex2ifname(nexthop->ifindex, nexthop->vrf_id)); break; case NEXTHOP_TYPE_BLACKHOLE: switch (nexthop->bh_type) { case BLACKHOLE_REJECT: s = " (ICMP unreachable)"; break; case BLACKHOLE_ADMINPROHIB: s = " (ICMP admin-prohibited)"; break; case BLACKHOLE_NULL: s = " (blackhole)"; break; default: s = ""; break; } bprintfrr(&fb, "unreachable%s", s); break; default: break; } if (do_ifi && nexthop->ifindex) bprintfrr(&fb, ", %s%s", v_viaif, ifindex2ifname( nexthop->ifindex, nexthop->vrf_id)); *fb.pos = '\0'; return ret; case 's': nexthop2str(nexthop, buf, bsz); return 3; } return 0; } frr-7.2.1/lib/nexthop.h0000644000000000000000000001231013610377563011615 00000000000000/* * Nexthop structure definition. * Copyright (C) 1997, 98, 99, 2001 Kunihiro Ishiguro * Copyright (C) 2013 Cumulus Networks, Inc. * * This file is part of Quagga. * * Quagga is free software; you can redistribute it and/or modify it * under the terms of the GNU General Public License as published by the * Free Software Foundation; either version 2, or (at your option) any * later version. * * Quagga 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; see the file COPYING; if not, write to the Free Software * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA */ #ifndef _LIB_NEXTHOP_H #define _LIB_NEXTHOP_H #include "prefix.h" #include "mpls.h" #ifdef __cplusplus extern "C" { #endif /* Maximum next hop string length - gateway + ifindex */ #define NEXTHOP_STRLEN (INET6_ADDRSTRLEN + 30) union g_addr { struct in_addr ipv4; struct in6_addr ipv6; }; enum nexthop_types_t { NEXTHOP_TYPE_IFINDEX = 1, /* Directly connected. */ NEXTHOP_TYPE_IPV4, /* IPv4 nexthop. */ NEXTHOP_TYPE_IPV4_IFINDEX, /* IPv4 nexthop with ifindex. */ NEXTHOP_TYPE_IPV6, /* IPv6 nexthop. */ NEXTHOP_TYPE_IPV6_IFINDEX, /* IPv6 nexthop with ifindex. */ NEXTHOP_TYPE_BLACKHOLE, /* Null0 nexthop. */ }; enum blackhole_type { BLACKHOLE_UNSPEC = 0, BLACKHOLE_NULL, BLACKHOLE_REJECT, BLACKHOLE_ADMINPROHIB, }; /* IPV[46] -> IPV[46]_IFINDEX */ #define NEXTHOP_FIRSTHOPTYPE(type) \ ((type) == NEXTHOP_TYPE_IFINDEX || (type) == NEXTHOP_TYPE_BLACKHOLE) \ ? (type) \ : ((type) | 1) /* Nexthop structure. */ struct nexthop { struct nexthop *next; struct nexthop *prev; /* * What vrf is this nexthop associated with? */ vrf_id_t vrf_id; /* Interface index. */ ifindex_t ifindex; enum nexthop_types_t type; uint8_t flags; #define NEXTHOP_FLAG_ACTIVE (1 << 0) /* This nexthop is alive. */ #define NEXTHOP_FLAG_FIB (1 << 1) /* FIB nexthop. */ #define NEXTHOP_FLAG_RECURSIVE (1 << 2) /* Recursive nexthop. */ #define NEXTHOP_FLAG_ONLINK (1 << 3) /* Nexthop should be installed onlink. */ #define NEXTHOP_FLAG_MATCHED (1 << 4) /* Already matched vs a nexthop */ #define NEXTHOP_FLAG_DUPLICATE (1 << 5) /* nexthop duplicates another active one */ #define NEXTHOP_FLAG_RNH_FILTERED (1 << 6) /* rmap filtered, used by rnh */ #define NEXTHOP_IS_ACTIVE(flags) \ (CHECK_FLAG(flags, NEXTHOP_FLAG_ACTIVE) \ && !CHECK_FLAG(flags, NEXTHOP_FLAG_DUPLICATE)) /* Nexthop address */ union { union g_addr gate; enum blackhole_type bh_type; }; union g_addr src; union g_addr rmap_src; /* Src is set via routemap */ /* Nexthops obtained by recursive resolution. * * If the nexthop struct needs to be resolved recursively, * NEXTHOP_FLAG_RECURSIVE will be set in flags and the nexthops * obtained by recursive resolution will be added to `resolved'. */ struct nexthop *resolved; /* Recursive parent */ struct nexthop *rparent; /* Type of label(s), if any */ enum lsp_types_t nh_label_type; /* Label(s) associated with this nexthop. */ struct mpls_label_stack *nh_label; }; struct nexthop *nexthop_new(void); void nexthop_free(struct nexthop *nexthop); void nexthops_free(struct nexthop *nexthop); void nexthop_add_labels(struct nexthop *, enum lsp_types_t, uint8_t, mpls_label_t *); void nexthop_del_labels(struct nexthop *); /* * Hash a nexthop. Suitable for use with hash tables. * * This function uses the following values when computing the hash: * - vrf_id * - ifindex * - type * - gate * * nexthop * The nexthop to hash * * Returns: * 32-bit hash of nexthop */ uint32_t nexthop_hash(const struct nexthop *nexthop); extern bool nexthop_same(const struct nexthop *nh1, const struct nexthop *nh2); extern bool nexthop_same_no_labels(const struct nexthop *nh1, const struct nexthop *nh2); extern int nexthop_cmp(const struct nexthop *nh1, const struct nexthop *nh2); extern int nexthop_g_addr_cmp(enum nexthop_types_t type, const union g_addr *addr1, const union g_addr *addr2); extern const char *nexthop_type_to_str(enum nexthop_types_t nh_type); extern bool nexthop_labels_match(const struct nexthop *nh1, const struct nexthop *nh2); extern int nexthop_same_firsthop(struct nexthop *next1, struct nexthop *next2); extern const char *nexthop2str(const struct nexthop *nexthop, char *str, int size); extern struct nexthop *nexthop_next(struct nexthop *nexthop); extern unsigned int nexthop_level(struct nexthop *nexthop); /* Copies to an already allocated nexthop struct */ extern void nexthop_copy(struct nexthop *copy, const struct nexthop *nexthop, struct nexthop *rparent); /* Duplicates a nexthop and returns the newly allocated nexthop */ extern struct nexthop *nexthop_dup(const struct nexthop *nexthop, struct nexthop *rparent); #ifdef __cplusplus } #endif #endif /*_LIB_NEXTHOP_H */ frr-7.2.1/lib/nexthop_group.c0000644000000000000000000004253413610377563013037 00000000000000/* * Nexthop Group structure definition. * Copyright (C) 2018 Cumulus Networks, Inc. * Donald Sharp * * This program is free software; you can redistribute it and/or modify it * under the terms of the 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; see the file COPYING; if not, write to the Free Software * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA */ #include #include #include #include #include #include #include #include #include #ifndef VTYSH_EXTRACT_PL #include "lib/nexthop_group_clippy.c" #endif DEFINE_MTYPE_STATIC(LIB, NEXTHOP_GROUP, "Nexthop Group") struct nexthop_group_hooks { void (*new)(const char *name); void (*add_nexthop)(const struct nexthop_group_cmd *nhg, const struct nexthop *nhop); void (*del_nexthop)(const struct nexthop_group_cmd *nhg, const struct nexthop *nhop); void (*delete)(const char *name); }; static struct nexthop_group_hooks nhg_hooks; static inline int nexthop_group_cmd_compare(const struct nexthop_group_cmd *nhgc1, const struct nexthop_group_cmd *nhgc2); RB_GENERATE(nhgc_entry_head, nexthop_group_cmd, nhgc_entry, nexthop_group_cmd_compare) static struct nhgc_entry_head nhgc_entries; static inline int nexthop_group_cmd_compare(const struct nexthop_group_cmd *nhgc1, const struct nexthop_group_cmd *nhgc2) { return strcmp(nhgc1->name, nhgc2->name); } static struct nexthop *nexthop_group_tail(const struct nexthop_group *nhg) { struct nexthop *nexthop = nhg->nexthop; while (nexthop && nexthop->next) nexthop = nexthop->next; return nexthop; } uint8_t nexthop_group_nexthop_num(const struct nexthop_group *nhg) { struct nexthop *nhop; uint8_t num = 0; for (ALL_NEXTHOPS_PTR(nhg, nhop)) num++; return num; } uint8_t nexthop_group_active_nexthop_num(const struct nexthop_group *nhg) { struct nexthop *nhop; uint8_t num = 0; for (ALL_NEXTHOPS_PTR(nhg, nhop)) { if (CHECK_FLAG(nhop->flags, NEXTHOP_FLAG_ACTIVE)) num++; } return num; } struct nexthop *nexthop_exists(struct nexthop_group *nhg, struct nexthop *nh) { struct nexthop *nexthop; for (nexthop = nhg->nexthop; nexthop; nexthop = nexthop->next) { if (nexthop_same(nh, nexthop)) return nexthop; } return NULL; } struct nexthop_group *nexthop_group_new(void) { return XCALLOC(MTYPE_NEXTHOP_GROUP, sizeof(struct nexthop_group)); } void nexthop_group_copy(struct nexthop_group *to, struct nexthop_group *from) { /* Copy everything, including recursive info */ copy_nexthops(&to->nexthop, from->nexthop, NULL); } void nexthop_group_delete(struct nexthop_group **nhg) { XFREE(MTYPE_NEXTHOP_GROUP, *nhg); } /* Add nexthop to the end of a nexthop list. */ void _nexthop_add(struct nexthop **target, struct nexthop *nexthop) { struct nexthop *last; for (last = *target; last && last->next; last = last->next) ; if (last) last->next = nexthop; else *target = nexthop; nexthop->prev = last; } void _nexthop_group_add_sorted(struct nexthop_group *nhg, struct nexthop *nexthop) { struct nexthop *position, *prev, *tail; /* Try to just append to the end first * This trust it is already sorted */ tail = nexthop_group_tail(nhg); if (tail && (nexthop_cmp(tail, nexthop) < 0)) { tail->next = nexthop; nexthop->prev = tail; return; } for (position = nhg->nexthop, prev = NULL; position; prev = position, position = position->next) { if (nexthop_cmp(position, nexthop) > 0) { nexthop->next = position; nexthop->prev = prev; if (nexthop->prev) nexthop->prev->next = nexthop; else nhg->nexthop = nexthop; position->prev = nexthop; return; } } nexthop->prev = prev; if (prev) prev->next = nexthop; else nhg->nexthop = nexthop; } /* Delete nexthop from a nexthop list. */ void _nexthop_del(struct nexthop_group *nhg, struct nexthop *nh) { struct nexthop *nexthop; for (nexthop = nhg->nexthop; nexthop; nexthop = nexthop->next) { if (nexthop_same(nh, nexthop)) break; } assert(nexthop); if (nexthop->prev) nexthop->prev->next = nexthop->next; else nhg->nexthop = nexthop->next; if (nexthop->next) nexthop->next->prev = nexthop->prev; nh->prev = NULL; nh->next = NULL; } void copy_nexthops(struct nexthop **tnh, const struct nexthop *nh, struct nexthop *rparent) { struct nexthop *nexthop; const struct nexthop *nh1; for (nh1 = nh; nh1; nh1 = nh1->next) { nexthop = nexthop_dup(nh1, rparent); _nexthop_add(tnh, nexthop); if (CHECK_FLAG(nh1->flags, NEXTHOP_FLAG_RECURSIVE)) copy_nexthops(&nexthop->resolved, nh1->resolved, nexthop); } } uint32_t nexthop_group_hash(const struct nexthop_group *nhg) { struct nexthop *nh; uint32_t key = 0; /* * We are not interested in hashing over any recursively * resolved nexthops */ for (nh = nhg->nexthop; nh; nh = nh->next) key = jhash_1word(nexthop_hash(nh), key); return key; } static void nhgc_delete_nexthops(struct nexthop_group_cmd *nhgc) { struct nexthop *nexthop; nexthop = nhgc->nhg.nexthop; while (nexthop) { struct nexthop *next = nexthop_next(nexthop); _nexthop_del(&nhgc->nhg, nexthop); if (nhg_hooks.del_nexthop) nhg_hooks.del_nexthop(nhgc, nexthop); nexthop_free(nexthop); nexthop = next; } } struct nexthop_group_cmd *nhgc_find(const char *name) { struct nexthop_group_cmd find; strlcpy(find.name, name, sizeof(find.name)); return RB_FIND(nhgc_entry_head, &nhgc_entries, &find); } static int nhgc_cmp_helper(const char *a, const char *b) { if (!a && !b) return 0; if (a && !b) return -1; if (!a && b) return 1; return strcmp(a, b); } static int nhgc_addr_cmp_helper(const union sockunion *a, const union sockunion *b) { if (!a && !b) return 0; if (a && !b) return -1; if (!a && b) return 1; return sockunion_cmp(a, b); } static int nhgl_cmp(struct nexthop_hold *nh1, struct nexthop_hold *nh2) { int ret; ret = nhgc_addr_cmp_helper(nh1->addr, nh2->addr); if (ret) return ret; ret = nhgc_cmp_helper(nh1->intf, nh2->intf); if (ret) return ret; return nhgc_cmp_helper(nh1->nhvrf_name, nh2->nhvrf_name); } static void nhgl_delete(struct nexthop_hold *nh) { XFREE(MTYPE_TMP, nh->intf); XFREE(MTYPE_TMP, nh->nhvrf_name); if (nh->addr) sockunion_free(nh->addr); XFREE(MTYPE_TMP, nh); } static struct nexthop_group_cmd *nhgc_get(const char *name) { struct nexthop_group_cmd *nhgc; nhgc = nhgc_find(name); if (!nhgc) { nhgc = XCALLOC(MTYPE_TMP, sizeof(*nhgc)); strlcpy(nhgc->name, name, sizeof(nhgc->name)); QOBJ_REG(nhgc, nexthop_group_cmd); RB_INSERT(nhgc_entry_head, &nhgc_entries, nhgc); nhgc->nhg_list = list_new(); nhgc->nhg_list->cmp = (int (*)(void *, void *))nhgl_cmp; nhgc->nhg_list->del = (void (*)(void *))nhgl_delete; if (nhg_hooks.new) nhg_hooks.new(name); } return nhgc; } static void nhgc_delete(struct nexthop_group_cmd *nhgc) { nhgc_delete_nexthops(nhgc); if (nhg_hooks.delete) nhg_hooks.delete(nhgc->name); RB_REMOVE(nhgc_entry_head, &nhgc_entries, nhgc); list_delete(&nhgc->nhg_list); XFREE(MTYPE_TMP, nhgc); } DEFINE_QOBJ_TYPE(nexthop_group_cmd) DEFUN_NOSH(nexthop_group, nexthop_group_cmd, "nexthop-group NHGNAME", "Enter into the nexthop-group submode\n" "Specify the NAME of the nexthop-group\n") { const char *nhg_name = argv[1]->arg; struct nexthop_group_cmd *nhgc = NULL; nhgc = nhgc_get(nhg_name); VTY_PUSH_CONTEXT(NH_GROUP_NODE, nhgc); return CMD_SUCCESS; } DEFUN_NOSH(no_nexthop_group, no_nexthop_group_cmd, "no nexthop-group NHGNAME", NO_STR "Delete the nexthop-group\n" "Specify the NAME of the nexthop-group\n") { const char *nhg_name = argv[2]->arg; struct nexthop_group_cmd *nhgc = NULL; nhgc = nhgc_find(nhg_name); if (nhgc) nhgc_delete(nhgc); return CMD_SUCCESS; } static void nexthop_group_save_nhop(struct nexthop_group_cmd *nhgc, const char *nhvrf_name, const union sockunion *addr, const char *intf) { struct nexthop_hold *nh; nh = XCALLOC(MTYPE_TMP, sizeof(*nh)); if (nhvrf_name) nh->nhvrf_name = XSTRDUP(MTYPE_TMP, nhvrf_name); if (intf) nh->intf = XSTRDUP(MTYPE_TMP, intf); if (addr) nh->addr = sockunion_dup(addr); listnode_add_sort(nhgc->nhg_list, nh); } static void nexthop_group_unsave_nhop(struct nexthop_group_cmd *nhgc, const char *nhvrf_name, const union sockunion *addr, const char *intf) { struct nexthop_hold *nh; struct listnode *node; for (ALL_LIST_ELEMENTS_RO(nhgc->nhg_list, node, nh)) { if (nhgc_cmp_helper(nhvrf_name, nh->nhvrf_name) == 0 && nhgc_addr_cmp_helper(addr, nh->addr) == 0 && nhgc_cmp_helper(intf, nh->intf) == 0) break; } /* * Something has gone seriously wrong, fail gracefully */ if (!nh) return; list_delete_node(nhgc->nhg_list, node); nhgl_delete(nh); } static bool nexthop_group_parse_nexthop(struct nexthop *nhop, const union sockunion *addr, const char *intf, const char *name) { struct vrf *vrf; memset(nhop, 0, sizeof(*nhop)); if (name) vrf = vrf_lookup_by_name(name); else vrf = vrf_lookup_by_id(VRF_DEFAULT); if (!vrf) return false; nhop->vrf_id = vrf->vrf_id; if (intf) { nhop->ifindex = ifname2ifindex(intf, vrf->vrf_id); if (nhop->ifindex == IFINDEX_INTERNAL) return false; } if (addr) { if (addr->sa.sa_family == AF_INET) { nhop->gate.ipv4.s_addr = addr->sin.sin_addr.s_addr; if (intf) nhop->type = NEXTHOP_TYPE_IPV4_IFINDEX; else nhop->type = NEXTHOP_TYPE_IPV4; } else { nhop->gate.ipv6 = addr->sin6.sin6_addr; if (intf) nhop->type = NEXTHOP_TYPE_IPV6_IFINDEX; else nhop->type = NEXTHOP_TYPE_IPV6; } } else nhop->type = NEXTHOP_TYPE_IFINDEX; return true; } DEFPY(ecmp_nexthops, ecmp_nexthops_cmd, "[no] nexthop\ <\ $addr [INTERFACE$intf]\ |INTERFACE$intf\ >\ [nexthop-vrf NAME$vrf_name]", NO_STR "Specify one of the nexthops in this ECMP group\n" "v4 Address\n" "v6 Address\n" "Interface to use\n" "Interface to use\n" "If the nexthop is in a different vrf tell us\n" "The nexthop-vrf Name\n") { VTY_DECLVAR_CONTEXT(nexthop_group_cmd, nhgc); struct nexthop nhop; struct nexthop *nh; bool legal; legal = nexthop_group_parse_nexthop(&nhop, addr, intf, vrf_name); if (nhop.type == NEXTHOP_TYPE_IPV6 && IN6_IS_ADDR_LINKLOCAL(&nhop.gate.ipv6)) { vty_out(vty, "Specified a v6 LL with no interface, rejecting\n"); return CMD_WARNING_CONFIG_FAILED; } nh = nexthop_exists(&nhgc->nhg, &nhop); if (no) { nexthop_group_unsave_nhop(nhgc, vrf_name, addr, intf); if (nh) { _nexthop_del(&nhgc->nhg, nh); if (nhg_hooks.del_nexthop) nhg_hooks.del_nexthop(nhgc, nh); nexthop_free(nh); } } else if (!nh) { /* must be adding new nexthop since !no and !nexthop_exists */ if (legal) { nh = nexthop_new(); memcpy(nh, &nhop, sizeof(nhop)); _nexthop_add(&nhgc->nhg.nexthop, nh); } nexthop_group_save_nhop(nhgc, vrf_name, addr, intf); if (legal && nhg_hooks.add_nexthop) nhg_hooks.add_nexthop(nhgc, nh); } return CMD_SUCCESS; } struct cmd_node nexthop_group_node = { NH_GROUP_NODE, "%s(config-nh-group)# ", 1 }; void nexthop_group_write_nexthop(struct vty *vty, struct nexthop *nh) { char buf[100]; struct vrf *vrf; vty_out(vty, "nexthop "); switch (nh->type) { case NEXTHOP_TYPE_IFINDEX: vty_out(vty, "%s", ifindex2ifname(nh->ifindex, nh->vrf_id)); break; case NEXTHOP_TYPE_IPV4: vty_out(vty, "%s", inet_ntoa(nh->gate.ipv4)); break; case NEXTHOP_TYPE_IPV4_IFINDEX: vty_out(vty, "%s %s", inet_ntoa(nh->gate.ipv4), ifindex2ifname(nh->ifindex, nh->vrf_id)); break; case NEXTHOP_TYPE_IPV6: vty_out(vty, "%s", inet_ntop(AF_INET6, &nh->gate.ipv6, buf, sizeof(buf))); break; case NEXTHOP_TYPE_IPV6_IFINDEX: vty_out(vty, "%s %s", inet_ntop(AF_INET6, &nh->gate.ipv6, buf, sizeof(buf)), ifindex2ifname(nh->ifindex, nh->vrf_id)); break; case NEXTHOP_TYPE_BLACKHOLE: break; } if (nh->vrf_id != VRF_DEFAULT) { vrf = vrf_lookup_by_id(nh->vrf_id); vty_out(vty, " nexthop-vrf %s", vrf->name); } vty_out(vty, "\n"); } static void nexthop_group_write_nexthop_internal(struct vty *vty, struct nexthop_hold *nh) { char buf[100]; vty_out(vty, "nexthop"); if (nh->addr) vty_out(vty, " %s", sockunion2str(nh->addr, buf, sizeof(buf))); if (nh->intf) vty_out(vty, " %s", nh->intf); if (nh->nhvrf_name) vty_out(vty, " nexthop-vrf %s", nh->nhvrf_name); vty_out(vty, "\n"); } static int nexthop_group_write(struct vty *vty) { struct nexthop_group_cmd *nhgc; struct nexthop_hold *nh; RB_FOREACH (nhgc, nhgc_entry_head, &nhgc_entries) { struct listnode *node; vty_out(vty, "nexthop-group %s\n", nhgc->name); for (ALL_LIST_ELEMENTS_RO(nhgc->nhg_list, node, nh)) { vty_out(vty, " "); nexthop_group_write_nexthop_internal(vty, nh); } vty_out(vty, "!\n"); } return 1; } void nexthop_group_enable_vrf(struct vrf *vrf) { struct nexthop_group_cmd *nhgc; struct nexthop_hold *nhh; RB_FOREACH (nhgc, nhgc_entry_head, &nhgc_entries) { struct listnode *node; for (ALL_LIST_ELEMENTS_RO(nhgc->nhg_list, node, nhh)) { struct nexthop nhop; struct nexthop *nh; if (!nexthop_group_parse_nexthop(&nhop, nhh->addr, nhh->intf, nhh->nhvrf_name)) continue; nh = nexthop_exists(&nhgc->nhg, &nhop); if (nh) continue; if (nhop.vrf_id != vrf->vrf_id) continue; nh = nexthop_new(); memcpy(nh, &nhop, sizeof(nhop)); _nexthop_add(&nhgc->nhg.nexthop, nh); if (nhg_hooks.add_nexthop) nhg_hooks.add_nexthop(nhgc, nh); } } } void nexthop_group_disable_vrf(struct vrf *vrf) { struct nexthop_group_cmd *nhgc; struct nexthop_hold *nhh; RB_FOREACH (nhgc, nhgc_entry_head, &nhgc_entries) { struct listnode *node; for (ALL_LIST_ELEMENTS_RO(nhgc->nhg_list, node, nhh)) { struct nexthop nhop; struct nexthop *nh; if (!nexthop_group_parse_nexthop(&nhop, nhh->addr, nhh->intf, nhh->nhvrf_name)) continue; nh = nexthop_exists(&nhgc->nhg, &nhop); if (!nh) continue; if (nh->vrf_id != vrf->vrf_id) continue; _nexthop_del(&nhgc->nhg, nh); if (nhg_hooks.del_nexthop) nhg_hooks.del_nexthop(nhgc, nh); nexthop_free(nh); } } } void nexthop_group_interface_state_change(struct interface *ifp, ifindex_t oldifindex) { struct nexthop_group_cmd *nhgc; struct nexthop_hold *nhh; RB_FOREACH (nhgc, nhgc_entry_head, &nhgc_entries) { struct listnode *node; struct nexthop *nh; if (if_is_up(ifp)) { for (ALL_LIST_ELEMENTS_RO(nhgc->nhg_list, node, nhh)) { struct nexthop nhop; if (!nexthop_group_parse_nexthop( &nhop, nhh->addr, nhh->intf, nhh->nhvrf_name)) continue; switch (nhop.type) { case NEXTHOP_TYPE_IPV4: case NEXTHOP_TYPE_IPV6: case NEXTHOP_TYPE_BLACKHOLE: continue; case NEXTHOP_TYPE_IFINDEX: case NEXTHOP_TYPE_IPV4_IFINDEX: case NEXTHOP_TYPE_IPV6_IFINDEX: break; } nh = nexthop_exists(&nhgc->nhg, &nhop); if (nh) continue; if (ifp->ifindex != nhop.ifindex) continue; nh = nexthop_new(); memcpy(nh, &nhop, sizeof(nhop)); _nexthop_add(&nhgc->nhg.nexthop, nh); if (nhg_hooks.add_nexthop) nhg_hooks.add_nexthop(nhgc, nh); } } else { struct nexthop *next_nh; for (nh = nhgc->nhg.nexthop; nh; nh = next_nh) { next_nh = nh->next; switch (nh->type) { case NEXTHOP_TYPE_IPV4: case NEXTHOP_TYPE_IPV6: case NEXTHOP_TYPE_BLACKHOLE: continue; case NEXTHOP_TYPE_IFINDEX: case NEXTHOP_TYPE_IPV4_IFINDEX: case NEXTHOP_TYPE_IPV6_IFINDEX: break; } if (oldifindex != nh->ifindex) continue; _nexthop_del(&nhgc->nhg, nh); if (nhg_hooks.del_nexthop) nhg_hooks.del_nexthop(nhgc, nh); nexthop_free(nh); } } } } static void nhg_name_autocomplete(vector comps, struct cmd_token *token) { struct nexthop_group_cmd *nhgc; RB_FOREACH (nhgc, nhgc_entry_head, &nhgc_entries) { vector_set(comps, XSTRDUP(MTYPE_COMPLETION, nhgc->name)); } } static const struct cmd_variable_handler nhg_name_handlers[] = { {.tokenname = "NHGNAME", .completions = nhg_name_autocomplete}, {.completions = NULL}}; void nexthop_group_init(void (*new)(const char *name), void (*add_nexthop)(const struct nexthop_group_cmd *nhg, const struct nexthop *nhop), void (*del_nexthop)(const struct nexthop_group_cmd *nhg, const struct nexthop *nhop), void (*delete)(const char *name)) { RB_INIT(nhgc_entry_head, &nhgc_entries); cmd_variable_handler_register(nhg_name_handlers); install_node(&nexthop_group_node, nexthop_group_write); install_element(CONFIG_NODE, &nexthop_group_cmd); install_element(CONFIG_NODE, &no_nexthop_group_cmd); install_default(NH_GROUP_NODE); install_element(NH_GROUP_NODE, &ecmp_nexthops_cmd); memset(&nhg_hooks, 0, sizeof(nhg_hooks)); if (new) nhg_hooks.new = new; if (add_nexthop) nhg_hooks.add_nexthop = add_nexthop; if (del_nexthop) nhg_hooks.del_nexthop = del_nexthop; if (delete) nhg_hooks.delete = delete; } frr-7.2.1/lib/nexthop_group.h0000644000000000000000000000710513610377563013037 00000000000000/* * Nexthop Group structure definition. * Copyright (C) 2018 Cumulus Networks, Inc. * Donald Sharp * * This program is free software; you can redistribute it and/or modify it * under the terms of the 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; see the file COPYING; if not, write to the Free Software * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA */ #ifndef __NEXTHOP_GROUP__ #define __NEXTHOP_GROUP__ #include #ifdef __cplusplus extern "C" { #endif /* * What is a nexthop group? * * A nexthop group is a collection of nexthops that make up * the ECMP path for the route. * * This module provides a proper abstraction to this idea. */ struct nexthop_group { struct nexthop *nexthop; }; struct nexthop_group *nexthop_group_new(void); void nexthop_group_delete(struct nexthop_group **nhg); void nexthop_group_copy(struct nexthop_group *to, struct nexthop_group *from); void copy_nexthops(struct nexthop **tnh, const struct nexthop *nh, struct nexthop *rparent); uint32_t nexthop_group_hash(const struct nexthop_group *nhg); /* The following for loop allows to iterate over the nexthop * structure of routes. * * head: The pointer to the first nexthop in the chain. * * nexthop: The pointer to the current nexthop, either in the * top-level chain or in a resolved chain. */ #define ALL_NEXTHOPS(head, nhop) \ (nhop) = (head.nexthop); \ (nhop); \ (nhop) = nexthop_next(nhop) #define ALL_NEXTHOPS_PTR(head, nhop) \ (nhop) = ((head)->nexthop); \ (nhop); \ (nhop) = nexthop_next(nhop) struct nexthop_hold { char *nhvrf_name; union sockunion *addr; char *intf; }; struct nexthop_group_cmd { RB_ENTRY(nexthop_group_cmd) nhgc_entry; char name[80]; struct nexthop_group nhg; struct list *nhg_list; QOBJ_FIELDS }; RB_HEAD(nhgc_entry_head, nexthp_group_cmd); RB_PROTOTYPE(nhgc_entry_head, nexthop_group_cmd, nhgc_entry, nexthop_group_cmd_compare) DECLARE_QOBJ_TYPE(nexthop_group_cmd) /* * Initialize nexthop_groups. If you are interested in when * a nexthop_group is added/deleted/modified, then set the * appropriate callback functions to handle it in your * code */ void nexthop_group_init( void (*create)(const char *name), void (*add_nexthop)(const struct nexthop_group_cmd *nhgc, const struct nexthop *nhop), void (*del_nexthop)(const struct nexthop_group_cmd *nhgc, const struct nexthop *nhop), void (*destroy)(const char *name)); void nexthop_group_enable_vrf(struct vrf *vrf); void nexthop_group_disable_vrf(struct vrf *vrf); void nexthop_group_interface_state_change(struct interface *ifp, ifindex_t oldifindex); extern struct nexthop *nexthop_exists(struct nexthop_group *nhg, struct nexthop *nh); extern struct nexthop_group_cmd *nhgc_find(const char *name); extern void nexthop_group_write_nexthop(struct vty *vty, struct nexthop *nh); /* Return the number of nexthops in this nhg */ extern uint8_t nexthop_group_nexthop_num(const struct nexthop_group *nhg); extern uint8_t nexthop_group_active_nexthop_num(const struct nexthop_group *nhg); #ifdef __cplusplus } #endif #endif frr-7.2.1/lib/nexthop_group_private.h0000644000000000000000000000272313610377563014572 00000000000000/* * Nexthop Group Private Functions. * Copyright (C) 2019 Cumulus Networks, Inc. * Stephen Worley * * This program is free software; you can redistribute it and/or modify it * under the terms of the 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; see the file COPYING; if not, write to the Free Software * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA */ /** * These functions should only be used internally for nexthop groups * and in certain special cases. Please use `lib/nexthop_group.h` for * any general nexthop_group api needs. */ #ifndef __NEXTHOP_GROUP_PRIVATE__ #define __NEXTHOP_GROUP_PRIVATE__ #include #ifdef __cplusplus extern "C" { #endif void _nexthop_add(struct nexthop **target, struct nexthop *nexthop); void _nexthop_del(struct nexthop_group *nhg, struct nexthop *nexthop); void _nexthop_group_add_sorted(struct nexthop_group *nhg, struct nexthop *nexthop); #ifdef __cplusplus } #endif #endif /* __NEXTHOP_GROUP_PRIVATE__ */ frr-7.2.1/lib/northbound.c0000644000000000000000000013746013610377563012323 00000000000000/* * Copyright (C) 2018 NetDEF, Inc. * Renato Westphal * * This program is free software; you can redistribute it and/or modify it * under the terms of the 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; see the file COPYING; if not, write to the Free Software * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA */ #include #include "libfrr.h" #include "log.h" #include "lib_errors.h" #include "hash.h" #include "command.h" #include "debug.h" #include "db.h" #include "frr_pthread.h" #include "northbound.h" #include "northbound_cli.h" #include "northbound_db.h" DEFINE_MTYPE_STATIC(LIB, NB_NODE, "Northbound Node") DEFINE_MTYPE_STATIC(LIB, NB_CONFIG, "Northbound Configuration") DEFINE_MTYPE_STATIC(LIB, NB_CONFIG_ENTRY, "Northbound Configuration Entry") /* Running configuration - shouldn't be modified directly. */ struct nb_config *running_config; /* Hash table of user pointers associated with configuration entries. */ static struct hash *running_config_entries; /* Management lock for the running configuration. */ static struct { /* Mutex protecting this structure. */ pthread_mutex_t mtx; /* Actual lock. */ bool locked; /* Northbound client who owns this lock. */ enum nb_client owner_client; /* Northbound user who owns this lock. */ const void *owner_user; } running_config_mgmt_lock; /* * Global lock used to prevent multiple configuration transactions from * happening concurrently. */ static bool transaction_in_progress; static int nb_callback_configuration(const enum nb_event event, struct nb_config_change *change); static struct nb_transaction *nb_transaction_new(struct nb_config *config, struct nb_config_cbs *changes, enum nb_client client, const void *user, const char *comment); static void nb_transaction_free(struct nb_transaction *transaction); static int nb_transaction_process(enum nb_event event, struct nb_transaction *transaction); static void nb_transaction_apply_finish(struct nb_transaction *transaction); static int nb_oper_data_iter_node(const struct lys_node *snode, const char *xpath, const void *list_entry, const struct yang_list_keys *list_keys, struct yang_translator *translator, bool first, uint32_t flags, nb_oper_data_cb cb, void *arg); static int nb_node_check_config_only(const struct lys_node *snode, void *arg) { bool *config_only = arg; if (CHECK_FLAG(snode->flags, LYS_CONFIG_R)) { *config_only = false; return YANG_ITER_STOP; } return YANG_ITER_CONTINUE; } static int nb_node_new_cb(const struct lys_node *snode, void *arg) { struct nb_node *nb_node; struct lys_node *sparent, *sparent_list; nb_node = XCALLOC(MTYPE_NB_NODE, sizeof(*nb_node)); yang_snode_get_path(snode, YANG_PATH_DATA, nb_node->xpath, sizeof(nb_node->xpath)); nb_node->priority = NB_DFLT_PRIORITY; sparent = yang_snode_real_parent(snode); if (sparent) nb_node->parent = sparent->priv; sparent_list = yang_snode_parent_list(snode); if (sparent_list) nb_node->parent_list = sparent_list->priv; /* Set flags. */ if (CHECK_FLAG(snode->nodetype, LYS_CONTAINER | LYS_LIST)) { bool config_only = true; yang_snodes_iterate_subtree(snode, nb_node_check_config_only, YANG_ITER_ALLOW_AUGMENTATIONS, &config_only); if (config_only) SET_FLAG(nb_node->flags, F_NB_NODE_CONFIG_ONLY); } if (CHECK_FLAG(snode->nodetype, LYS_LIST)) { struct lys_node_list *slist; slist = (struct lys_node_list *)snode; if (slist->keys_size == 0) SET_FLAG(nb_node->flags, F_NB_NODE_KEYLESS_LIST); } /* * Link the northbound node and the libyang schema node with one * another. */ nb_node->snode = snode; lys_set_private(snode, nb_node); return YANG_ITER_CONTINUE; } static int nb_node_del_cb(const struct lys_node *snode, void *arg) { struct nb_node *nb_node; nb_node = snode->priv; lys_set_private(snode, NULL); XFREE(MTYPE_NB_NODE, nb_node); return YANG_ITER_CONTINUE; } void nb_nodes_create(void) { yang_snodes_iterate_all(nb_node_new_cb, 0, NULL); } void nb_nodes_delete(void) { yang_snodes_iterate_all(nb_node_del_cb, 0, NULL); } struct nb_node *nb_node_find(const char *xpath) { const struct lys_node *snode; /* * Use libyang to find the schema node associated to the xpath and get * the northbound node from there (snode private pointer). */ snode = ly_ctx_get_node(ly_native_ctx, NULL, xpath, 0); if (!snode) return NULL; return snode->priv; } static int nb_node_validate_cb(const struct nb_node *nb_node, enum nb_operation operation, int callback_implemented, bool optional) { bool valid; valid = nb_operation_is_valid(operation, nb_node->snode); if (!valid && callback_implemented) flog_warn(EC_LIB_NB_CB_UNNEEDED, "unneeded '%s' callback for '%s'", nb_operation_name(operation), nb_node->xpath); if (!optional && valid && !callback_implemented) { flog_err(EC_LIB_NB_CB_MISSING, "missing '%s' callback for '%s'", nb_operation_name(operation), nb_node->xpath); return 1; } return 0; } /* * Check if the required callbacks were implemented for the given northbound * node. */ static unsigned int nb_node_validate_cbs(const struct nb_node *nb_node) { unsigned int error = 0; error += nb_node_validate_cb(nb_node, NB_OP_CREATE, !!nb_node->cbs.create, false); error += nb_node_validate_cb(nb_node, NB_OP_MODIFY, !!nb_node->cbs.modify, false); error += nb_node_validate_cb(nb_node, NB_OP_DESTROY, !!nb_node->cbs.destroy, false); error += nb_node_validate_cb(nb_node, NB_OP_MOVE, !!nb_node->cbs.move, false); error += nb_node_validate_cb(nb_node, NB_OP_APPLY_FINISH, !!nb_node->cbs.apply_finish, true); error += nb_node_validate_cb(nb_node, NB_OP_GET_ELEM, !!nb_node->cbs.get_elem, false); error += nb_node_validate_cb(nb_node, NB_OP_GET_NEXT, !!nb_node->cbs.get_next, false); error += nb_node_validate_cb(nb_node, NB_OP_GET_KEYS, !!nb_node->cbs.get_keys, false); error += nb_node_validate_cb(nb_node, NB_OP_LOOKUP_ENTRY, !!nb_node->cbs.lookup_entry, false); error += nb_node_validate_cb(nb_node, NB_OP_RPC, !!nb_node->cbs.rpc, false); return error; } static unsigned int nb_node_validate_priority(const struct nb_node *nb_node) { /* Top-level nodes can have any priority. */ if (!nb_node->parent) return 0; if (nb_node->priority < nb_node->parent->priority) { flog_err(EC_LIB_NB_CB_INVALID_PRIO, "node has higher priority than its parent [xpath %s]", nb_node->xpath); return 1; } return 0; } static int nb_node_validate(const struct lys_node *snode, void *arg) { struct nb_node *nb_node = snode->priv; unsigned int *errors = arg; /* Validate callbacks and priority. */ *errors += nb_node_validate_cbs(nb_node); *errors += nb_node_validate_priority(nb_node); return YANG_ITER_CONTINUE; } struct nb_config *nb_config_new(struct lyd_node *dnode) { struct nb_config *config; config = XCALLOC(MTYPE_NB_CONFIG, sizeof(*config)); if (dnode) config->dnode = dnode; else config->dnode = yang_dnode_new(ly_native_ctx, true); config->version = 0; pthread_rwlock_init(&config->lock, NULL); return config; } void nb_config_free(struct nb_config *config) { if (config->dnode) yang_dnode_free(config->dnode); pthread_rwlock_destroy(&config->lock); XFREE(MTYPE_NB_CONFIG, config); } struct nb_config *nb_config_dup(const struct nb_config *config) { struct nb_config *dup; dup = XCALLOC(MTYPE_NB_CONFIG, sizeof(*dup)); dup->dnode = yang_dnode_dup(config->dnode); dup->version = config->version; pthread_rwlock_init(&dup->lock, NULL); return dup; } int nb_config_merge(struct nb_config *config_dst, struct nb_config *config_src, bool preserve_source) { int ret; ret = lyd_merge(config_dst->dnode, config_src->dnode, LYD_OPT_EXPLICIT); if (ret != 0) flog_warn(EC_LIB_LIBYANG, "%s: lyd_merge() failed", __func__); if (!preserve_source) nb_config_free(config_src); return (ret == 0) ? NB_OK : NB_ERR; } void nb_config_replace(struct nb_config *config_dst, struct nb_config *config_src, bool preserve_source) { /* Update version. */ if (config_src->version != 0) config_dst->version = config_src->version; /* Update dnode. */ if (config_dst->dnode) yang_dnode_free(config_dst->dnode); if (preserve_source) { config_dst->dnode = yang_dnode_dup(config_src->dnode); } else { config_dst->dnode = config_src->dnode; config_src->dnode = NULL; nb_config_free(config_src); } } /* Generate the nb_config_cbs tree. */ static inline int nb_config_cb_compare(const struct nb_config_cb *a, const struct nb_config_cb *b) { /* Sort by priority first. */ if (a->nb_node->priority < b->nb_node->priority) return -1; if (a->nb_node->priority > b->nb_node->priority) return 1; /* * Use XPath as a tie-breaker. This will naturally sort parent nodes * before their children. */ return strcmp(a->xpath, b->xpath); } RB_GENERATE(nb_config_cbs, nb_config_cb, entry, nb_config_cb_compare); static void nb_config_diff_add_change(struct nb_config_cbs *changes, enum nb_operation operation, const struct lyd_node *dnode) { struct nb_config_change *change; change = XCALLOC(MTYPE_TMP, sizeof(*change)); change->cb.operation = operation; change->cb.nb_node = dnode->schema->priv; yang_dnode_get_path(dnode, change->cb.xpath, sizeof(change->cb.xpath)); change->cb.dnode = dnode; RB_INSERT(nb_config_cbs, changes, &change->cb); } static void nb_config_diff_del_changes(struct nb_config_cbs *changes) { while (!RB_EMPTY(nb_config_cbs, changes)) { struct nb_config_change *change; change = (struct nb_config_change *)RB_ROOT(nb_config_cbs, changes); RB_REMOVE(nb_config_cbs, changes, &change->cb); XFREE(MTYPE_TMP, change); } } /* * Helper function used when calculating the delta between two different * configurations. Given a new subtree, calculate all new YANG data nodes, * excluding default leafs and leaf-lists. This is a recursive function. */ static void nb_config_diff_created(const struct lyd_node *dnode, struct nb_config_cbs *changes) { enum nb_operation operation; struct lyd_node *child; switch (dnode->schema->nodetype) { case LYS_LEAF: case LYS_LEAFLIST: if (lyd_wd_default((struct lyd_node_leaf_list *)dnode)) break; if (nb_operation_is_valid(NB_OP_CREATE, dnode->schema)) operation = NB_OP_CREATE; else if (nb_operation_is_valid(NB_OP_MODIFY, dnode->schema)) operation = NB_OP_MODIFY; else return; nb_config_diff_add_change(changes, operation, dnode); break; case LYS_CONTAINER: case LYS_LIST: if (nb_operation_is_valid(NB_OP_CREATE, dnode->schema)) nb_config_diff_add_change(changes, NB_OP_CREATE, dnode); /* Process child nodes recursively. */ LY_TREE_FOR (dnode->child, child) { nb_config_diff_created(child, changes); } break; default: break; } } static void nb_config_diff_deleted(const struct lyd_node *dnode, struct nb_config_cbs *changes) { if (nb_operation_is_valid(NB_OP_DESTROY, dnode->schema)) nb_config_diff_add_change(changes, NB_OP_DESTROY, dnode); else if (CHECK_FLAG(dnode->schema->nodetype, LYS_CONTAINER)) { struct lyd_node *child; /* * Non-presence containers need special handling since they * don't have "destroy" callbacks. In this case, what we need to * do is to call the "destroy" callbacks of their child nodes * when applicable (i.e. optional nodes). */ LY_TREE_FOR (dnode->child, child) { nb_config_diff_deleted(child, changes); } } } /* Calculate the delta between two different configurations. */ static void nb_config_diff(const struct nb_config *config1, const struct nb_config *config2, struct nb_config_cbs *changes) { struct lyd_difflist *diff; diff = lyd_diff(config1->dnode, config2->dnode, LYD_DIFFOPT_WITHDEFAULTS); assert(diff); for (int i = 0; diff->type[i] != LYD_DIFF_END; i++) { LYD_DIFFTYPE type; struct lyd_node *dnode; type = diff->type[i]; switch (type) { case LYD_DIFF_CREATED: dnode = diff->second[i]; nb_config_diff_created(dnode, changes); break; case LYD_DIFF_DELETED: dnode = diff->first[i]; nb_config_diff_deleted(dnode, changes); break; case LYD_DIFF_CHANGED: dnode = diff->second[i]; nb_config_diff_add_change(changes, NB_OP_MODIFY, dnode); break; case LYD_DIFF_MOVEDAFTER1: case LYD_DIFF_MOVEDAFTER2: default: continue; } } lyd_free_diff(diff); } int nb_candidate_edit(struct nb_config *candidate, const struct nb_node *nb_node, enum nb_operation operation, const char *xpath, const struct yang_data *previous, const struct yang_data *data) { struct lyd_node *dnode; char xpath_edit[XPATH_MAXLEN]; /* Use special notation for leaf-lists (RFC 6020, section 9.13.5). */ if (nb_node->snode->nodetype == LYS_LEAFLIST) snprintf(xpath_edit, sizeof(xpath_edit), "%s[.='%s']", xpath, data->value); else strlcpy(xpath_edit, xpath, sizeof(xpath_edit)); switch (operation) { case NB_OP_CREATE: case NB_OP_MODIFY: ly_errno = 0; dnode = lyd_new_path(candidate->dnode, ly_native_ctx, xpath_edit, (void *)data->value, 0, LYD_PATH_OPT_UPDATE); if (!dnode && ly_errno) { flog_warn(EC_LIB_LIBYANG, "%s: lyd_new_path() failed", __func__); return NB_ERR; } /* * If a new node was created, call lyd_validate() only to create * default child nodes. */ if (dnode) { lyd_schema_sort(dnode, 0); lyd_validate(&dnode, LYD_OPT_CONFIG | LYD_OPT_WHENAUTODEL, ly_native_ctx); } break; case NB_OP_DESTROY: dnode = yang_dnode_get(candidate->dnode, xpath_edit); if (!dnode) /* * Return a special error code so the caller can choose * whether to ignore it or not. */ return NB_ERR_NOT_FOUND; lyd_free(dnode); break; case NB_OP_MOVE: /* TODO: update configuration. */ break; default: flog_warn(EC_LIB_DEVELOPMENT, "%s: unknown operation (%u) [xpath %s]", __func__, operation, xpath_edit); return NB_ERR; } return NB_OK; } bool nb_candidate_needs_update(const struct nb_config *candidate) { bool ret = false; pthread_rwlock_rdlock(&running_config->lock); { if (candidate->version < running_config->version) ret = true; } pthread_rwlock_unlock(&running_config->lock); return ret; } int nb_candidate_update(struct nb_config *candidate) { struct nb_config *updated_config; pthread_rwlock_rdlock(&running_config->lock); { updated_config = nb_config_dup(running_config); } pthread_rwlock_unlock(&running_config->lock); if (nb_config_merge(updated_config, candidate, true) != NB_OK) return NB_ERR; nb_config_replace(candidate, updated_config, false); return NB_OK; } /* * Perform YANG syntactic and semantic validation. * * WARNING: lyd_validate() can change the configuration as part of the * validation process. */ static int nb_candidate_validate_yang(struct nb_config *candidate) { if (lyd_validate(&candidate->dnode, LYD_OPT_STRICT | LYD_OPT_CONFIG | LYD_OPT_WHENAUTODEL, ly_native_ctx) != 0) return NB_ERR_VALIDATION; return NB_OK; } /* Perform code-level validation using the northbound callbacks. */ static int nb_candidate_validate_changes(struct nb_config *candidate, struct nb_config_cbs *changes) { struct nb_config_cb *cb; RB_FOREACH (cb, nb_config_cbs, changes) { struct nb_config_change *change = (struct nb_config_change *)cb; int ret; ret = nb_callback_configuration(NB_EV_VALIDATE, change); if (ret != NB_OK) return NB_ERR_VALIDATION; } return NB_OK; } int nb_candidate_validate(struct nb_config *candidate) { struct nb_config_cbs changes; int ret; if (nb_candidate_validate_yang(candidate) != NB_OK) return NB_ERR_VALIDATION; RB_INIT(nb_config_cbs, &changes); pthread_rwlock_rdlock(&running_config->lock); { nb_config_diff(running_config, candidate, &changes); ret = nb_candidate_validate_changes(candidate, &changes); nb_config_diff_del_changes(&changes); } pthread_rwlock_unlock(&running_config->lock); return ret; } int nb_candidate_commit_prepare(struct nb_config *candidate, enum nb_client client, const void *user, const char *comment, struct nb_transaction **transaction) { struct nb_config_cbs changes; if (nb_candidate_validate_yang(candidate) != NB_OK) { flog_warn(EC_LIB_NB_CANDIDATE_INVALID, "%s: failed to validate candidate configuration", __func__); return NB_ERR_VALIDATION; } RB_INIT(nb_config_cbs, &changes); pthread_rwlock_rdlock(&running_config->lock); { nb_config_diff(running_config, candidate, &changes); if (RB_EMPTY(nb_config_cbs, &changes)) { pthread_rwlock_unlock(&running_config->lock); return NB_ERR_NO_CHANGES; } if (nb_candidate_validate_changes(candidate, &changes) != NB_OK) { flog_warn( EC_LIB_NB_CANDIDATE_INVALID, "%s: failed to validate candidate configuration", __func__); nb_config_diff_del_changes(&changes); pthread_rwlock_unlock(&running_config->lock); return NB_ERR_VALIDATION; } *transaction = nb_transaction_new(candidate, &changes, client, user, comment); if (*transaction == NULL) { flog_warn(EC_LIB_NB_TRANSACTION_CREATION_FAILED, "%s: failed to create transaction", __func__); nb_config_diff_del_changes(&changes); pthread_rwlock_unlock(&running_config->lock); return NB_ERR_LOCKED; } } pthread_rwlock_unlock(&running_config->lock); return nb_transaction_process(NB_EV_PREPARE, *transaction); } void nb_candidate_commit_abort(struct nb_transaction *transaction) { (void)nb_transaction_process(NB_EV_ABORT, transaction); nb_transaction_free(transaction); } void nb_candidate_commit_apply(struct nb_transaction *transaction, bool save_transaction, uint32_t *transaction_id) { (void)nb_transaction_process(NB_EV_APPLY, transaction); nb_transaction_apply_finish(transaction); /* Replace running by candidate. */ transaction->config->version++; pthread_rwlock_wrlock(&running_config->lock); { nb_config_replace(running_config, transaction->config, true); } pthread_rwlock_unlock(&running_config->lock); /* Record transaction. */ if (save_transaction && nb_db_transaction_save(transaction, transaction_id) != NB_OK) flog_warn(EC_LIB_NB_TRANSACTION_RECORD_FAILED, "%s: failed to record transaction", __func__); nb_transaction_free(transaction); } int nb_candidate_commit(struct nb_config *candidate, enum nb_client client, const void *user, bool save_transaction, const char *comment, uint32_t *transaction_id) { struct nb_transaction *transaction = NULL; int ret; ret = nb_candidate_commit_prepare(candidate, client, user, comment, &transaction); /* * Apply the changes if the preparation phase succeeded. Otherwise abort * the transaction. */ if (ret == NB_OK) nb_candidate_commit_apply(transaction, save_transaction, transaction_id); else if (transaction != NULL) nb_candidate_commit_abort(transaction); return ret; } int nb_running_lock(enum nb_client client, const void *user) { int ret = -1; frr_with_mutex(&running_config_mgmt_lock.mtx) { if (!running_config_mgmt_lock.locked) { running_config_mgmt_lock.locked = true; running_config_mgmt_lock.owner_client = client; running_config_mgmt_lock.owner_user = user; ret = 0; } } return ret; } int nb_running_unlock(enum nb_client client, const void *user) { int ret = -1; frr_with_mutex(&running_config_mgmt_lock.mtx) { if (running_config_mgmt_lock.locked && running_config_mgmt_lock.owner_client == client && running_config_mgmt_lock.owner_user == user) { running_config_mgmt_lock.locked = false; running_config_mgmt_lock.owner_client = NB_CLIENT_NONE; running_config_mgmt_lock.owner_user = NULL; ret = 0; } } return ret; } int nb_running_lock_check(enum nb_client client, const void *user) { int ret = -1; frr_with_mutex(&running_config_mgmt_lock.mtx) { if (!running_config_mgmt_lock.locked || (running_config_mgmt_lock.owner_client == client && running_config_mgmt_lock.owner_user == user)) ret = 0; } return ret; } static void nb_log_callback(const enum nb_event event, enum nb_operation operation, const char *xpath, const char *value) { zlog_debug( "northbound callback: event [%s] op [%s] xpath [%s] value [%s]", nb_event_name(event), nb_operation_name(operation), xpath, value ? value : "(NULL)"); } /* * Call the northbound configuration callback associated to a given * configuration change. */ static int nb_callback_configuration(const enum nb_event event, struct nb_config_change *change) { enum nb_operation operation = change->cb.operation; const char *xpath = change->cb.xpath; const struct nb_node *nb_node = change->cb.nb_node; const struct lyd_node *dnode = change->cb.dnode; union nb_resource *resource; int ret = NB_ERR; if (DEBUG_MODE_CHECK(&nb_dbg_cbs_config, DEBUG_MODE_ALL)) { const char *value = "(none)"; if (dnode && !yang_snode_is_typeless_data(dnode->schema)) value = yang_dnode_get_string(dnode, NULL); nb_log_callback(event, operation, xpath, value); } if (event == NB_EV_VALIDATE) resource = NULL; else resource = &change->resource; switch (operation) { case NB_OP_CREATE: ret = (*nb_node->cbs.create)(event, dnode, resource); break; case NB_OP_MODIFY: ret = (*nb_node->cbs.modify)(event, dnode, resource); break; case NB_OP_DESTROY: ret = (*nb_node->cbs.destroy)(event, dnode); break; case NB_OP_MOVE: ret = (*nb_node->cbs.move)(event, dnode); break; default: flog_err(EC_LIB_DEVELOPMENT, "%s: unknown operation (%u) [xpath %s]", __func__, operation, xpath); exit(1); } if (ret != NB_OK) { int priority; enum lib_log_refs ref; switch (event) { case NB_EV_VALIDATE: priority = LOG_WARNING; ref = EC_LIB_NB_CB_CONFIG_VALIDATE; break; case NB_EV_PREPARE: priority = LOG_WARNING; ref = EC_LIB_NB_CB_CONFIG_PREPARE; break; case NB_EV_ABORT: priority = LOG_WARNING; ref = EC_LIB_NB_CB_CONFIG_ABORT; break; case NB_EV_APPLY: priority = LOG_ERR; ref = EC_LIB_NB_CB_CONFIG_APPLY; break; default: flog_err(EC_LIB_DEVELOPMENT, "%s: unknown event (%u) [xpath %s]", __func__, event, xpath); exit(1); } flog(priority, ref, "%s: error processing configuration change: error [%s] event [%s] operation [%s] xpath [%s]", __func__, nb_err_name(ret), nb_event_name(event), nb_operation_name(operation), xpath); } return ret; } struct yang_data *nb_callback_get_elem(const struct nb_node *nb_node, const char *xpath, const void *list_entry) { DEBUGD(&nb_dbg_cbs_state, "northbound callback (get_elem): xpath [%s] list_entry [%p]", xpath, list_entry); return nb_node->cbs.get_elem(xpath, list_entry); } const void *nb_callback_get_next(const struct nb_node *nb_node, const void *parent_list_entry, const void *list_entry) { DEBUGD(&nb_dbg_cbs_state, "northbound callback (get_next): node [%s] parent_list_entry [%p] list_entry [%p]", nb_node->xpath, parent_list_entry, list_entry); return nb_node->cbs.get_next(parent_list_entry, list_entry); } int nb_callback_get_keys(const struct nb_node *nb_node, const void *list_entry, struct yang_list_keys *keys) { DEBUGD(&nb_dbg_cbs_state, "northbound callback (get_keys): node [%s] list_entry [%p]", nb_node->xpath, list_entry); return nb_node->cbs.get_keys(list_entry, keys); } const void *nb_callback_lookup_entry(const struct nb_node *nb_node, const void *parent_list_entry, const struct yang_list_keys *keys) { DEBUGD(&nb_dbg_cbs_state, "northbound callback (lookup_entry): node [%s] parent_list_entry [%p]", nb_node->xpath, parent_list_entry); return nb_node->cbs.lookup_entry(parent_list_entry, keys); } int nb_callback_rpc(const struct nb_node *nb_node, const char *xpath, const struct list *input, struct list *output) { DEBUGD(&nb_dbg_cbs_rpc, "northbound RPC: %s", xpath); return nb_node->cbs.rpc(xpath, input, output); } static struct nb_transaction * nb_transaction_new(struct nb_config *config, struct nb_config_cbs *changes, enum nb_client client, const void *user, const char *comment) { struct nb_transaction *transaction; if (nb_running_lock_check(client, user)) { flog_warn( EC_LIB_NB_TRANSACTION_CREATION_FAILED, "%s: running configuration is locked by another client", __func__); return NULL; } if (transaction_in_progress) { flog_warn( EC_LIB_NB_TRANSACTION_CREATION_FAILED, "%s: error - there's already another transaction in progress", __func__); return NULL; } transaction_in_progress = true; transaction = XCALLOC(MTYPE_TMP, sizeof(*transaction)); transaction->client = client; if (comment) strlcpy(transaction->comment, comment, sizeof(transaction->comment)); transaction->config = config; transaction->changes = *changes; return transaction; } static void nb_transaction_free(struct nb_transaction *transaction) { nb_config_diff_del_changes(&transaction->changes); XFREE(MTYPE_TMP, transaction); transaction_in_progress = false; } /* Process all configuration changes associated to a transaction. */ static int nb_transaction_process(enum nb_event event, struct nb_transaction *transaction) { struct nb_config_cb *cb; /* * Need to lock the running configuration since transaction->changes * can contain pointers to data nodes from the running configuration. */ pthread_rwlock_rdlock(&running_config->lock); { RB_FOREACH (cb, nb_config_cbs, &transaction->changes) { struct nb_config_change *change = (struct nb_config_change *)cb; int ret; /* * Only try to release resources that were allocated * successfully. */ if (event == NB_EV_ABORT && change->prepare_ok == false) break; /* Call the appropriate callback. */ ret = nb_callback_configuration(event, change); switch (event) { case NB_EV_PREPARE: if (ret != NB_OK) { pthread_rwlock_unlock( &running_config->lock); return ret; } change->prepare_ok = true; break; case NB_EV_ABORT: case NB_EV_APPLY: /* * At this point it's not possible to reject the * transaction anymore, so any failure here can * lead to inconsistencies and should be treated * as a bug. Operations prone to errors, like * validations and resource allocations, should * be performed during the 'prepare' phase. */ break; default: break; } } } pthread_rwlock_unlock(&running_config->lock); return NB_OK; } static struct nb_config_cb * nb_apply_finish_cb_new(struct nb_config_cbs *cbs, const char *xpath, const struct nb_node *nb_node, const struct lyd_node *dnode) { struct nb_config_cb *cb; cb = XCALLOC(MTYPE_TMP, sizeof(*cb)); strlcpy(cb->xpath, xpath, sizeof(cb->xpath)); cb->nb_node = nb_node; cb->dnode = dnode; RB_INSERT(nb_config_cbs, cbs, cb); return cb; } static struct nb_config_cb * nb_apply_finish_cb_find(struct nb_config_cbs *cbs, const char *xpath, const struct nb_node *nb_node) { struct nb_config_cb s; strlcpy(s.xpath, xpath, sizeof(s.xpath)); s.nb_node = nb_node; return RB_FIND(nb_config_cbs, cbs, &s); } /* Call the 'apply_finish' callbacks. */ static void nb_transaction_apply_finish(struct nb_transaction *transaction) { struct nb_config_cbs cbs; struct nb_config_cb *cb; /* Initialize tree of 'apply_finish' callbacks. */ RB_INIT(nb_config_cbs, &cbs); /* Identify the 'apply_finish' callbacks that need to be called. */ RB_FOREACH (cb, nb_config_cbs, &transaction->changes) { struct nb_config_change *change = (struct nb_config_change *)cb; const struct lyd_node *dnode = change->cb.dnode; /* * Iterate up to the root of the data tree. When a node is being * deleted, skip its 'apply_finish' callback if one is defined * (the 'apply_finish' callbacks from the node ancestors should * be called though). */ if (change->cb.operation == NB_OP_DESTROY) { char xpath[XPATH_MAXLEN]; dnode = dnode->parent; if (!dnode) break; /* * The dnode from 'delete' callbacks point to elements * from the running configuration. Use yang_dnode_get() * to get the corresponding dnode from the candidate * configuration that is being committed. */ yang_dnode_get_path(dnode, xpath, sizeof(xpath)); dnode = yang_dnode_get(transaction->config->dnode, xpath); } while (dnode) { char xpath[XPATH_MAXLEN]; struct nb_node *nb_node; nb_node = dnode->schema->priv; if (!nb_node->cbs.apply_finish) goto next; /* * Don't call the callback more than once for the same * data node. */ yang_dnode_get_path(dnode, xpath, sizeof(xpath)); if (nb_apply_finish_cb_find(&cbs, xpath, nb_node)) goto next; nb_apply_finish_cb_new(&cbs, xpath, nb_node, dnode); next: dnode = dnode->parent; } } /* Call the 'apply_finish' callbacks, sorted by their priorities. */ RB_FOREACH (cb, nb_config_cbs, &cbs) { if (DEBUG_MODE_CHECK(&nb_dbg_cbs_config, DEBUG_MODE_ALL)) nb_log_callback(NB_EV_APPLY, NB_OP_APPLY_FINISH, cb->xpath, NULL); (*cb->nb_node->cbs.apply_finish)(cb->dnode); } /* Release memory. */ while (!RB_EMPTY(nb_config_cbs, &cbs)) { cb = RB_ROOT(nb_config_cbs, &cbs); RB_REMOVE(nb_config_cbs, &cbs, cb); XFREE(MTYPE_TMP, cb); } } static int nb_oper_data_iter_children(const struct lys_node *snode, const char *xpath, const void *list_entry, const struct yang_list_keys *list_keys, struct yang_translator *translator, bool first, uint32_t flags, nb_oper_data_cb cb, void *arg) { struct lys_node *child; LY_TREE_FOR (snode->child, child) { int ret; ret = nb_oper_data_iter_node(child, xpath, list_entry, list_keys, translator, false, flags, cb, arg); if (ret != NB_OK) return ret; } return NB_OK; } static int nb_oper_data_iter_leaf(const struct nb_node *nb_node, const char *xpath, const void *list_entry, const struct yang_list_keys *list_keys, struct yang_translator *translator, uint32_t flags, nb_oper_data_cb cb, void *arg) { struct yang_data *data; if (CHECK_FLAG(nb_node->snode->flags, LYS_CONFIG_W)) return NB_OK; /* Ignore list keys. */ if (lys_is_key((struct lys_node_leaf *)nb_node->snode, NULL)) return NB_OK; data = nb_callback_get_elem(nb_node, xpath, list_entry); if (data == NULL) /* Leaf of type "empty" is not present. */ return NB_OK; return (*cb)(nb_node->snode, translator, data, arg); } static int nb_oper_data_iter_container(const struct nb_node *nb_node, const char *xpath, const void *list_entry, const struct yang_list_keys *list_keys, struct yang_translator *translator, uint32_t flags, nb_oper_data_cb cb, void *arg) { if (CHECK_FLAG(nb_node->flags, F_NB_NODE_CONFIG_ONLY)) return NB_OK; /* Presence containers. */ if (nb_node->cbs.get_elem) { struct yang_data *data; int ret; data = nb_callback_get_elem(nb_node, xpath, list_entry); if (data == NULL) /* Presence container is not present. */ return NB_OK; ret = (*cb)(nb_node->snode, translator, data, arg); if (ret != NB_OK) return ret; } /* Iterate over the child nodes. */ return nb_oper_data_iter_children(nb_node->snode, xpath, list_entry, list_keys, translator, false, flags, cb, arg); } static int nb_oper_data_iter_leaflist(const struct nb_node *nb_node, const char *xpath, const void *parent_list_entry, const struct yang_list_keys *parent_list_keys, struct yang_translator *translator, uint32_t flags, nb_oper_data_cb cb, void *arg) { const void *list_entry = NULL; if (CHECK_FLAG(nb_node->snode->flags, LYS_CONFIG_W)) return NB_OK; do { struct yang_data *data; int ret; list_entry = nb_callback_get_next(nb_node, parent_list_entry, list_entry); if (!list_entry) /* End of the list. */ break; data = nb_callback_get_elem(nb_node, xpath, list_entry); if (data == NULL) continue; ret = (*cb)(nb_node->snode, translator, data, arg); if (ret != NB_OK) return ret; } while (list_entry); return NB_OK; } static int nb_oper_data_iter_list(const struct nb_node *nb_node, const char *xpath_list, const void *parent_list_entry, const struct yang_list_keys *parent_list_keys, struct yang_translator *translator, uint32_t flags, nb_oper_data_cb cb, void *arg) { struct lys_node_list *slist = (struct lys_node_list *)nb_node->snode; const void *list_entry = NULL; uint32_t position = 1; if (CHECK_FLAG(nb_node->flags, F_NB_NODE_CONFIG_ONLY)) return NB_OK; /* Iterate over all list entries. */ do { struct yang_list_keys list_keys; char xpath[XPATH_MAXLEN * 2]; int ret; /* Obtain list entry. */ list_entry = nb_callback_get_next(nb_node, parent_list_entry, list_entry); if (!list_entry) /* End of the list. */ break; if (!CHECK_FLAG(nb_node->flags, F_NB_NODE_KEYLESS_LIST)) { /* Obtain the list entry keys. */ if (nb_callback_get_keys(nb_node, list_entry, &list_keys) != NB_OK) { flog_warn(EC_LIB_NB_CB_STATE, "%s: failed to get list keys", __func__); return NB_ERR; } /* Build XPath of the list entry. */ strlcpy(xpath, xpath_list, sizeof(xpath)); for (unsigned int i = 0; i < list_keys.num; i++) { snprintf(xpath + strlen(xpath), sizeof(xpath) - strlen(xpath), "[%s='%s']", slist->keys[i]->name, list_keys.key[i]); } } else { /* * Keyless list - build XPath using a positional index. */ snprintf(xpath, sizeof(xpath), "%s[%u]", xpath_list, position); position++; } /* Iterate over the child nodes. */ ret = nb_oper_data_iter_children( nb_node->snode, xpath, list_entry, &list_keys, translator, false, flags, cb, arg); if (ret != NB_OK) return ret; } while (list_entry); return NB_OK; } static int nb_oper_data_iter_node(const struct lys_node *snode, const char *xpath_parent, const void *list_entry, const struct yang_list_keys *list_keys, struct yang_translator *translator, bool first, uint32_t flags, nb_oper_data_cb cb, void *arg) { struct nb_node *nb_node; char xpath[XPATH_MAXLEN]; int ret = NB_OK; if (!first && CHECK_FLAG(flags, NB_OPER_DATA_ITER_NORECURSE) && CHECK_FLAG(snode->nodetype, LYS_CONTAINER | LYS_LIST)) return NB_OK; /* Update XPath. */ strlcpy(xpath, xpath_parent, sizeof(xpath)); if (!first && snode->nodetype != LYS_USES) snprintf(xpath + strlen(xpath), sizeof(xpath) - strlen(xpath), "/%s", snode->name); nb_node = snode->priv; switch (snode->nodetype) { case LYS_CONTAINER: ret = nb_oper_data_iter_container(nb_node, xpath, list_entry, list_keys, translator, flags, cb, arg); break; case LYS_LEAF: ret = nb_oper_data_iter_leaf(nb_node, xpath, list_entry, list_keys, translator, flags, cb, arg); break; case LYS_LEAFLIST: ret = nb_oper_data_iter_leaflist(nb_node, xpath, list_entry, list_keys, translator, flags, cb, arg); break; case LYS_LIST: ret = nb_oper_data_iter_list(nb_node, xpath, list_entry, list_keys, translator, flags, cb, arg); break; case LYS_USES: ret = nb_oper_data_iter_children(snode, xpath, list_entry, list_keys, translator, false, flags, cb, arg); break; default: break; } return ret; } int nb_oper_data_iterate(const char *xpath, struct yang_translator *translator, uint32_t flags, nb_oper_data_cb cb, void *arg) { struct nb_node *nb_node; const void *list_entry = NULL; struct yang_list_keys list_keys; struct list *list_dnodes; struct lyd_node *dnode, *dn; struct listnode *ln; int ret; nb_node = nb_node_find(xpath); if (!nb_node) { flog_warn(EC_LIB_YANG_UNKNOWN_DATA_PATH, "%s: unknown data path: %s", __func__, xpath); return NB_ERR; } /* For now this function works only with containers and lists. */ if (!CHECK_FLAG(nb_node->snode->nodetype, LYS_CONTAINER | LYS_LIST)) { flog_warn( EC_LIB_NB_OPERATIONAL_DATA, "%s: can't iterate over YANG leaf or leaf-list [xpath %s]", __func__, xpath); return NB_ERR; } /* * Create a data tree from the XPath so that we can parse the keys of * all YANG lists (if any). */ ly_errno = 0; dnode = lyd_new_path(NULL, ly_native_ctx, xpath, NULL, 0, LYD_PATH_OPT_UPDATE | LYD_PATH_OPT_NOPARENTRET); if (!dnode) { flog_warn(EC_LIB_LIBYANG, "%s: lyd_new_path() failed", __func__); return NB_ERR; } /* * Create a linked list to sort the data nodes starting from the root. */ list_dnodes = list_new(); for (dn = dnode; dn; dn = dn->parent) { if (dn->schema->nodetype != LYS_LIST || !dn->child) continue; listnode_add_head(list_dnodes, dn); } /* * Use the northbound callbacks to find list entry pointer corresponding * to the given XPath. */ for (ALL_LIST_ELEMENTS_RO(list_dnodes, ln, dn)) { struct lyd_node *child; struct nb_node *nn; unsigned int n = 0; /* Obtain the list entry keys. */ memset(&list_keys, 0, sizeof(list_keys)); LY_TREE_FOR (dn->child, child) { if (!lys_is_key((struct lys_node_leaf *)child->schema, NULL)) continue; strlcpy(list_keys.key[n], yang_dnode_get_string(child, NULL), sizeof(list_keys.key[n])); n++; } list_keys.num = n; if (list_keys.num != ((struct lys_node_list *)dn->schema)->keys_size) { list_delete(&list_dnodes); yang_dnode_free(dnode); return NB_ERR_NOT_FOUND; } /* Find the list entry pointer. */ nn = dn->schema->priv; list_entry = nb_callback_lookup_entry(nn, list_entry, &list_keys); if (list_entry == NULL) { list_delete(&list_dnodes); yang_dnode_free(dnode); return NB_ERR_NOT_FOUND; } } /* If a list entry was given, iterate over that list entry only. */ if (dnode->schema->nodetype == LYS_LIST && dnode->child) ret = nb_oper_data_iter_children( nb_node->snode, xpath, list_entry, &list_keys, translator, true, flags, cb, arg); else ret = nb_oper_data_iter_node(nb_node->snode, xpath, list_entry, &list_keys, translator, true, flags, cb, arg); list_delete(&list_dnodes); yang_dnode_free(dnode); return ret; } bool nb_operation_is_valid(enum nb_operation operation, const struct lys_node *snode) { struct nb_node *nb_node = snode->priv; struct lys_node_container *scontainer; struct lys_node_leaf *sleaf; switch (operation) { case NB_OP_CREATE: if (!CHECK_FLAG(snode->flags, LYS_CONFIG_W)) return false; switch (snode->nodetype) { case LYS_LEAF: sleaf = (struct lys_node_leaf *)snode; if (sleaf->type.base != LY_TYPE_EMPTY) return false; break; case LYS_CONTAINER: scontainer = (struct lys_node_container *)snode; if (!scontainer->presence) return false; break; case LYS_LIST: case LYS_LEAFLIST: break; default: return false; } return true; case NB_OP_MODIFY: if (!CHECK_FLAG(snode->flags, LYS_CONFIG_W)) return false; switch (snode->nodetype) { case LYS_LEAF: sleaf = (struct lys_node_leaf *)snode; if (sleaf->type.base == LY_TYPE_EMPTY) return false; /* List keys can't be modified. */ if (lys_is_key(sleaf, NULL)) return false; break; default: return false; } return true; case NB_OP_DESTROY: if (!CHECK_FLAG(snode->flags, LYS_CONFIG_W)) return false; switch (snode->nodetype) { case LYS_LEAF: sleaf = (struct lys_node_leaf *)snode; /* List keys can't be deleted. */ if (lys_is_key(sleaf, NULL)) return false; /* * Only optional leafs can be deleted, or leafs whose * parent is a case statement. */ if (snode->parent->nodetype == LYS_CASE) return true; if (sleaf->when) return true; if (CHECK_FLAG(sleaf->flags, LYS_MAND_TRUE) || sleaf->dflt) return false; break; case LYS_CONTAINER: scontainer = (struct lys_node_container *)snode; if (!scontainer->presence) return false; break; case LYS_LIST: case LYS_LEAFLIST: break; default: return false; } return true; case NB_OP_MOVE: if (!CHECK_FLAG(snode->flags, LYS_CONFIG_W)) return false; switch (snode->nodetype) { case LYS_LIST: case LYS_LEAFLIST: if (!CHECK_FLAG(snode->flags, LYS_USERORDERED)) return false; break; default: return false; } return true; case NB_OP_APPLY_FINISH: if (!CHECK_FLAG(snode->flags, LYS_CONFIG_W)) return false; return true; case NB_OP_GET_ELEM: if (!CHECK_FLAG(snode->flags, LYS_CONFIG_R)) return false; switch (snode->nodetype) { case LYS_LEAF: case LYS_LEAFLIST: break; case LYS_CONTAINER: scontainer = (struct lys_node_container *)snode; if (!scontainer->presence) return false; break; default: return false; } return true; case NB_OP_GET_NEXT: switch (snode->nodetype) { case LYS_LIST: if (CHECK_FLAG(nb_node->flags, F_NB_NODE_CONFIG_ONLY)) return false; break; case LYS_LEAFLIST: if (CHECK_FLAG(snode->flags, LYS_CONFIG_W)) return false; break; default: return false; } return true; case NB_OP_GET_KEYS: case NB_OP_LOOKUP_ENTRY: switch (snode->nodetype) { case LYS_LIST: if (CHECK_FLAG(nb_node->flags, F_NB_NODE_CONFIG_ONLY)) return false; if (CHECK_FLAG(nb_node->flags, F_NB_NODE_KEYLESS_LIST)) return false; break; default: return false; } return true; case NB_OP_RPC: if (CHECK_FLAG(snode->flags, LYS_CONFIG_W | LYS_CONFIG_R)) return false; switch (snode->nodetype) { case LYS_RPC: case LYS_ACTION: break; default: return false; } return true; default: return false; } } DEFINE_HOOK(nb_notification_send, (const char *xpath, struct list *arguments), (xpath, arguments)); int nb_notification_send(const char *xpath, struct list *arguments) { int ret; DEBUGD(&nb_dbg_notif, "northbound notification: %s", xpath); ret = hook_call(nb_notification_send, xpath, arguments); if (arguments) list_delete(&arguments); return ret; } /* Running configuration user pointers management. */ struct nb_config_entry { char xpath[XPATH_MAXLEN]; void *entry; }; static bool running_config_entry_cmp(const void *value1, const void *value2) { const struct nb_config_entry *c1 = value1; const struct nb_config_entry *c2 = value2; return strmatch(c1->xpath, c2->xpath); } static unsigned int running_config_entry_key_make(const void *value) { return string_hash_make(value); } static void *running_config_entry_alloc(void *p) { struct nb_config_entry *new, *key = p; new = XCALLOC(MTYPE_NB_CONFIG_ENTRY, sizeof(*new)); strlcpy(new->xpath, key->xpath, sizeof(new->xpath)); return new; } static void running_config_entry_free(void *arg) { XFREE(MTYPE_NB_CONFIG_ENTRY, arg); } void nb_running_set_entry(const struct lyd_node *dnode, void *entry) { struct nb_config_entry *config, s; yang_dnode_get_path(dnode, s.xpath, sizeof(s.xpath)); config = hash_get(running_config_entries, &s, running_config_entry_alloc); config->entry = entry; } static void *nb_running_unset_entry_helper(const struct lyd_node *dnode) { struct nb_config_entry *config, s; struct lyd_node *child; void *entry = NULL; yang_dnode_get_path(dnode, s.xpath, sizeof(s.xpath)); config = hash_release(running_config_entries, &s); if (config) { entry = config->entry; running_config_entry_free(config); } /* Unset user pointers from the child nodes. */ if (CHECK_FLAG(dnode->schema->nodetype, LYS_LIST | LYS_CONTAINER)) { LY_TREE_FOR (dnode->child, child) { (void)nb_running_unset_entry_helper(child); } } return entry; } void *nb_running_unset_entry(const struct lyd_node *dnode) { void *entry; entry = nb_running_unset_entry_helper(dnode); assert(entry); return entry; } void *nb_running_get_entry(const struct lyd_node *dnode, const char *xpath, bool abort_if_not_found) { const struct lyd_node *orig_dnode = dnode; char xpath_buf[XPATH_MAXLEN]; assert(dnode || xpath); if (!dnode) dnode = yang_dnode_get(running_config->dnode, xpath); while (dnode) { struct nb_config_entry *config, s; yang_dnode_get_path(dnode, s.xpath, sizeof(s.xpath)); config = hash_lookup(running_config_entries, &s); if (config) return config->entry; dnode = dnode->parent; } if (!abort_if_not_found) return NULL; yang_dnode_get_path(orig_dnode, xpath_buf, sizeof(xpath_buf)); flog_err(EC_LIB_YANG_DNODE_NOT_FOUND, "%s: failed to find entry [xpath %s]", __func__, xpath_buf); zlog_backtrace(LOG_ERR); abort(); } /* Logging functions. */ const char *nb_event_name(enum nb_event event) { switch (event) { case NB_EV_VALIDATE: return "validate"; case NB_EV_PREPARE: return "prepare"; case NB_EV_ABORT: return "abort"; case NB_EV_APPLY: return "apply"; default: return "unknown"; } } const char *nb_operation_name(enum nb_operation operation) { switch (operation) { case NB_OP_CREATE: return "create"; case NB_OP_MODIFY: return "modify"; case NB_OP_DESTROY: return "destroy"; case NB_OP_MOVE: return "move"; case NB_OP_APPLY_FINISH: return "apply_finish"; case NB_OP_GET_ELEM: return "get_elem"; case NB_OP_GET_NEXT: return "get_next"; case NB_OP_GET_KEYS: return "get_keys"; case NB_OP_LOOKUP_ENTRY: return "lookup_entry"; case NB_OP_RPC: return "rpc"; default: return "unknown"; } } const char *nb_err_name(enum nb_error error) { switch (error) { case NB_OK: return "ok"; case NB_ERR: return "generic error"; case NB_ERR_NO_CHANGES: return "no changes"; case NB_ERR_NOT_FOUND: return "element not found"; case NB_ERR_LOCKED: return "resource is locked"; case NB_ERR_VALIDATION: return "validation error"; case NB_ERR_RESOURCE: return "failed to allocate resource"; case NB_ERR_INCONSISTENCY: return "internal inconsistency"; default: return "unknown"; } } const char *nb_client_name(enum nb_client client) { switch (client) { case NB_CLIENT_CLI: return "CLI"; case NB_CLIENT_CONFD: return "ConfD"; case NB_CLIENT_SYSREPO: return "Sysrepo"; case NB_CLIENT_GRPC: return "gRPC"; default: return "unknown"; } } static void nb_load_callbacks(const struct frr_yang_module_info *module) { for (size_t i = 0; module->nodes[i].xpath; i++) { struct nb_node *nb_node; uint32_t priority; nb_node = nb_node_find(module->nodes[i].xpath); if (!nb_node) { flog_warn(EC_LIB_YANG_UNKNOWN_DATA_PATH, "%s: unknown data path: %s", __func__, module->nodes[i].xpath); continue; } nb_node->cbs = module->nodes[i].cbs; priority = module->nodes[i].priority; if (priority != 0) nb_node->priority = priority; } } void nb_init(struct thread_master *tm, const struct frr_yang_module_info *modules[], size_t nmodules) { unsigned int errors = 0; /* Load YANG modules. */ for (size_t i = 0; i < nmodules; i++) yang_module_load(modules[i]->name); /* Create a nb_node for all YANG schema nodes. */ nb_nodes_create(); /* Load northbound callbacks. */ for (size_t i = 0; i < nmodules; i++) nb_load_callbacks(modules[i]); /* Validate northbound callbacks. */ yang_snodes_iterate_all(nb_node_validate, 0, &errors); if (errors > 0) { flog_err( EC_LIB_NB_CBS_VALIDATION, "%s: failed to validate northbound callbacks: %u error(s)", __func__, errors); exit(1); } /* Create an empty running configuration. */ running_config = nb_config_new(NULL); running_config_entries = hash_create(running_config_entry_key_make, running_config_entry_cmp, "Running Configuration Entries"); pthread_mutex_init(&running_config_mgmt_lock.mtx, NULL); /* Initialize the northbound CLI. */ nb_cli_init(tm); } void nb_terminate(void) { /* Terminate the northbound CLI. */ nb_cli_terminate(); /* Delete all nb_node's from all YANG modules. */ nb_nodes_delete(); /* Delete the running configuration. */ hash_clean(running_config_entries, running_config_entry_free); hash_free(running_config_entries); nb_config_free(running_config); pthread_mutex_destroy(&running_config_mgmt_lock.mtx); } frr-7.2.1/lib/northbound.h0000644000000000000000000007010613610377563012321 00000000000000/* * Copyright (C) 2018 NetDEF, Inc. * Renato Westphal * * This program is free software; you can redistribute it and/or modify it * under the terms of the 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; see the file COPYING; if not, write to the Free Software * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA */ #ifndef _FRR_NORTHBOUND_H_ #define _FRR_NORTHBOUND_H_ #include "thread.h" #include "hook.h" #include "linklist.h" #include "openbsd-tree.h" #include "yang.h" #include "yang_translator.h" #ifdef __cplusplus extern "C" { #endif /* Forward declaration(s). */ struct vty; struct debug; /* Northbound events. */ enum nb_event { /* * The configuration callback is supposed to verify that the changes are * valid and can be applied. */ NB_EV_VALIDATE, /* * The configuration callback is supposed to prepare all resources * required to apply the changes. */ NB_EV_PREPARE, /* * Transaction has failed, the configuration callback needs to release * all resources previously allocated. */ NB_EV_ABORT, /* * The configuration changes need to be applied. The changes can't be * rejected at this point (errors are logged and ignored). */ NB_EV_APPLY, }; /* * Northbound operations. * * Refer to the documentation comments of nb_callbacks for more details. */ enum nb_operation { NB_OP_CREATE, NB_OP_MODIFY, NB_OP_DESTROY, NB_OP_MOVE, NB_OP_APPLY_FINISH, NB_OP_GET_ELEM, NB_OP_GET_NEXT, NB_OP_GET_KEYS, NB_OP_LOOKUP_ENTRY, NB_OP_RPC, }; union nb_resource { int fd; void *ptr; }; struct nb_callbacks { /* * Configuration callback. * * A presence container, list entry, leaf-list entry or leaf of type * empty has been created. * * For presence-containers and list entries, the callback is supposed to * initialize the default values of its children (if any) from the YANG * models. * * event * The transaction phase. Refer to the documentation comments of * nb_event for more details. * * dnode * libyang data node that is being created. * * resource * Pointer to store resource(s) allocated during the NB_EV_PREPARE * phase. The same pointer can be used during the NB_EV_ABORT and * NB_EV_APPLY phases to either release or make use of the allocated * resource(s). It's set to NULL when the event is NB_EV_VALIDATE. * * Returns: * - NB_OK on success. * - NB_ERR_VALIDATION when a validation error occurred. * - NB_ERR_RESOURCE when the callback failed to allocate a resource. * - NB_ERR_INCONSISTENCY when an inconsistency was detected. * - NB_ERR for other errors. */ int (*create)(enum nb_event event, const struct lyd_node *dnode, union nb_resource *resource); /* * Configuration callback. * * The value of a leaf has been modified. * * List keys don't need to implement this callback. When a list key is * modified, the northbound treats this as if the list was deleted and a * new one created with the updated key value. * * event * The transaction phase. Refer to the documentation comments of * nb_event for more details. * * dnode * libyang data node that is being modified * * resource * Pointer to store resource(s) allocated during the NB_EV_PREPARE * phase. The same pointer can be used during the NB_EV_ABORT and * NB_EV_APPLY phases to either release or make use of the allocated * resource(s). It's set to NULL when the event is NB_EV_VALIDATE. * * Returns: * - NB_OK on success. * - NB_ERR_VALIDATION when a validation error occurred. * - NB_ERR_RESOURCE when the callback failed to allocate a resource. * - NB_ERR_INCONSISTENCY when an inconsistency was detected. * - NB_ERR for other errors. */ int (*modify)(enum nb_event event, const struct lyd_node *dnode, union nb_resource *resource); /* * Configuration callback. * * A presence container, list entry, leaf-list entry or optional leaf * has been deleted. * * The callback is supposed to delete the entire configuration object, * including its children when they exist. * * event * The transaction phase. Refer to the documentation comments of * nb_event for more details. * * dnode * libyang data node that is being deleted. * * Returns: * - NB_OK on success. * - NB_ERR_VALIDATION when a validation error occurred. * - NB_ERR_INCONSISTENCY when an inconsistency was detected. * - NB_ERR for other errors. */ int (*destroy)(enum nb_event event, const struct lyd_node *dnode); /* * Configuration callback. * * A list entry or leaf-list entry has been moved. Only applicable when * the "ordered-by user" statement is present. * * event * The transaction phase. Refer to the documentation comments of * nb_event for more details. * * dnode * libyang data node that is being moved. * * Returns: * - NB_OK on success. * - NB_ERR_VALIDATION when a validation error occurred. * - NB_ERR_INCONSISTENCY when an inconsistency was detected. * - NB_ERR for other errors. */ int (*move)(enum nb_event event, const struct lyd_node *dnode); /* * Optional configuration callback. * * The 'apply_finish' callbacks are called after all other callbacks * during the apply phase (NB_EV_APPLY). These callbacks are called only * under one of the following two cases: * - The data node has been created or modified (but not deleted); * - Any change was made within the descendants of the data node (e.g. a * child leaf was modified, created or deleted). * * In the second case above, the 'apply_finish' callback is called only * once even if multiple changes occurred within the descendants of the * data node. * * dnode * libyang data node associated with the 'apply_finish' callback. */ void (*apply_finish)(const struct lyd_node *dnode); /* * Operational data callback. * * The callback function should return the value of a specific leaf, * leaf-list entry or inform if a typeless value (presence containers or * leafs of type empty) exists or not. * * xpath * YANG data path of the data we want to get. * * list_entry * Pointer to list entry (might be NULL). * * Returns: * Pointer to newly created yang_data structure, or NULL to indicate * the absence of data. */ struct yang_data *(*get_elem)(const char *xpath, const void *list_entry); /* * Operational data callback for YANG lists and leaf-lists. * * The callback function should return the next entry in the list or * leaf-list. The 'list_entry' parameter will be NULL on the first * invocation. * * parent_list_entry * Pointer to parent list entry. * * list_entry * Pointer to (leaf-)list entry. * * Returns: * Pointer to the next entry in the (leaf-)list, or NULL to signal * that the end of the (leaf-)list was reached. */ const void *(*get_next)(const void *parent_list_entry, const void *list_entry); /* * Operational data callback for YANG lists. * * The callback function should fill the 'keys' parameter based on the * given list_entry. Keyless lists don't need to implement this * callback. * * list_entry * Pointer to list entry. * * keys * Structure to be filled based on the attributes of the provided * list entry. * * Returns: * NB_OK on success, NB_ERR otherwise. */ int (*get_keys)(const void *list_entry, struct yang_list_keys *keys); /* * Operational data callback for YANG lists. * * The callback function should return a list entry based on the list * keys given as a parameter. Keyless lists don't need to implement this * callback. * * parent_list_entry * Pointer to parent list entry. * * keys * Structure containing the keys of the list entry. * * Returns: * Pointer to the list entry if found, or NULL if not found. */ const void *(*lookup_entry)(const void *parent_list_entry, const struct yang_list_keys *keys); /* * RPC and action callback. * * Both 'input' and 'output' are lists of 'yang_data' structures. The * callback should fetch all the input parameters from the 'input' list, * and add output parameters to the 'output' list if necessary. * * xpath * XPath of the YANG RPC or action. * * input * Read-only list of input parameters. * * output * List of output parameters to be populated by the callback. * * Returns: * NB_OK on success, NB_ERR otherwise. */ int (*rpc)(const char *xpath, const struct list *input, struct list *output); /* * Optional callback to show the CLI command associated to the given * YANG data node. * * vty * The vty terminal to dump the configuration to. * * dnode * libyang data node that should be shown in the form of a CLI * command. * * show_defaults * Specify whether to display default configuration values or not. * This parameter can be ignored most of the time since the * northbound doesn't call this callback for default leaves or * non-presence containers that contain only default child nodes. * The exception are commands associated to multiple configuration * nodes, in which case it might be desirable to hide one or more * parts of the command when this parameter is set to false. */ void (*cli_show)(struct vty *vty, struct lyd_node *dnode, bool show_defaults); /* * Optional callback to show the CLI node end for lists or containers. * * vty * The vty terminal to dump the configuration to. * * dnode * libyang data node that should be shown in the form of a CLI * command. */ void (*cli_show_end)(struct vty *vty, struct lyd_node *dnode); }; /* * Northbound-specific data that is allocated for each schema node of the native * YANG modules. */ struct nb_node { /* Back pointer to the libyang schema node. */ const struct lys_node *snode; /* Data path of this YANG node. */ char xpath[XPATH_MAXLEN]; /* Priority - lower priorities are processed first. */ uint32_t priority; /* Callbacks implemented for this node. */ struct nb_callbacks cbs; /* * Pointer to the parent node (disconsidering non-presence containers). */ struct nb_node *parent; /* Pointer to the nearest parent list, if any. */ struct nb_node *parent_list; /* Flags. */ uint8_t flags; #ifdef HAVE_CONFD /* ConfD hash value corresponding to this YANG path. */ int confd_hash; #endif }; /* The YANG container or list contains only config data. */ #define F_NB_NODE_CONFIG_ONLY 0x01 /* The YANG list doesn't contain key leafs. */ #define F_NB_NODE_KEYLESS_LIST 0x02 struct frr_yang_module_info { /* YANG module name. */ const char *name; /* Northbound callbacks. */ const struct { /* Data path of this YANG node. */ const char *xpath; /* Callbacks implemented for this node. */ struct nb_callbacks cbs; /* Priority - lower priorities are processed first. */ uint32_t priority; } nodes[]; }; /* Northbound error codes. */ enum nb_error { NB_OK = 0, NB_ERR, NB_ERR_NO_CHANGES, NB_ERR_NOT_FOUND, NB_ERR_LOCKED, NB_ERR_VALIDATION, NB_ERR_RESOURCE, NB_ERR_INCONSISTENCY, }; /* Default priority. */ #define NB_DFLT_PRIORITY (UINT32_MAX / 2) /* Default maximum of configuration rollbacks to store. */ #define NB_DLFT_MAX_CONFIG_ROLLBACKS 20 /* Northbound clients. */ enum nb_client { NB_CLIENT_NONE = 0, NB_CLIENT_CLI, NB_CLIENT_CONFD, NB_CLIENT_SYSREPO, NB_CLIENT_GRPC, }; /* Northbound configuration. */ struct nb_config { /* Configuration data. */ struct lyd_node *dnode; /* Configuration version. */ uint32_t version; /* * Lock protecting this structure. The use of this lock is always * necessary when reading or modifying the global running configuration. * For candidate configurations, use of this lock is optional depending * on the threading scheme of the northbound plugin. */ pthread_rwlock_t lock; }; /* Northbound configuration callback. */ struct nb_config_cb { RB_ENTRY(nb_config_cb) entry; enum nb_operation operation; char xpath[XPATH_MAXLEN]; const struct nb_node *nb_node; const struct lyd_node *dnode; }; RB_HEAD(nb_config_cbs, nb_config_cb); RB_PROTOTYPE(nb_config_cbs, nb_config_cb, entry, nb_config_cb_compare); /* Northbound configuration change. */ struct nb_config_change { struct nb_config_cb cb; union nb_resource resource; bool prepare_ok; }; /* Northbound configuration transaction. */ struct nb_transaction { enum nb_client client; char comment[80]; struct nb_config *config; struct nb_config_cbs changes; }; /* Callback function used by nb_oper_data_iterate(). */ typedef int (*nb_oper_data_cb)(const struct lys_node *snode, struct yang_translator *translator, struct yang_data *data, void *arg); /* Iterate over direct child nodes only. */ #define NB_OPER_DATA_ITER_NORECURSE 0x0001 /* Hooks. */ DECLARE_HOOK(nb_notification_send, (const char *xpath, struct list *arguments), (xpath, arguments)) DECLARE_HOOK(nb_client_debug_config_write, (struct vty *vty), (vty)) DECLARE_HOOK(nb_client_debug_set_all, (uint32_t flags, bool set), (flags, set)) /* Northbound debugging records */ extern struct debug nb_dbg_cbs_config; extern struct debug nb_dbg_cbs_state; extern struct debug nb_dbg_cbs_rpc; extern struct debug nb_dbg_notif; extern struct debug nb_dbg_events; /* Global running configuration. */ extern struct nb_config *running_config; /* Wrappers for the northbound callbacks. */ extern struct yang_data *nb_callback_get_elem(const struct nb_node *nb_node, const char *xpath, const void *list_entry); extern const void *nb_callback_get_next(const struct nb_node *nb_node, const void *parent_list_entry, const void *list_entry); extern int nb_callback_get_keys(const struct nb_node *nb_node, const void *list_entry, struct yang_list_keys *keys); extern const void *nb_callback_lookup_entry(const struct nb_node *nb_node, const void *parent_list_entry, const struct yang_list_keys *keys); extern int nb_callback_rpc(const struct nb_node *nb_node, const char *xpath, const struct list *input, struct list *output); /* * Create a northbound node for all YANG schema nodes. */ void nb_nodes_create(void); /* * Delete all northbound nodes from all YANG schema nodes. */ void nb_nodes_delete(void); /* * Find the northbound node corresponding to a YANG data path. * * xpath * XPath to search for (with or without predicates). * * Returns: * Pointer to northbound node if found, NULL otherwise. */ extern struct nb_node *nb_node_find(const char *xpath); /* * Create a new northbound configuration. * * dnode * Pointer to a libyang data node containing the configuration data. If NULL * is given, an empty configuration will be created. * * Returns: * Pointer to newly created northbound configuration. */ extern struct nb_config *nb_config_new(struct lyd_node *dnode); /* * Delete a northbound configuration. * * config * Pointer to the config that is going to be deleted. */ extern void nb_config_free(struct nb_config *config); /* * Duplicate a northbound configuration. * * config * Northbound configuration to duplicate. * * Returns: * Pointer to duplicated configuration. */ extern struct nb_config *nb_config_dup(const struct nb_config *config); /* * Merge one configuration into another. * * config_dst * Configuration to merge to. * * config_src * Configuration to merge config_dst with. * * preserve_source * Specify whether config_src should be deleted or not after the merge * operation. * * Returns: * NB_OK on success, NB_ERR otherwise. */ extern int nb_config_merge(struct nb_config *config_dst, struct nb_config *config_src, bool preserve_source); /* * Replace one configuration by another. * * config_dst * Configuration to be replaced. * * config_src * Configuration to replace config_dst. * * preserve_source * Specify whether config_src should be deleted or not after the replace * operation. */ extern void nb_config_replace(struct nb_config *config_dst, struct nb_config *config_src, bool preserve_source); /* * Edit a candidate configuration. * * candidate * Candidate configuration to edit. * * nb_node * Northbound node associated to the configuration being edited. * * operation * Operation to apply. * * xpath * XPath of the configuration node being edited. * * previous * Previous value of the configuration node. Should be used only when the * operation is NB_OP_MOVE, otherwise this parameter is ignored. * * data * New value of the configuration node. * * Returns: * - NB_OK on success. * - NB_ERR_NOT_FOUND when the element to be deleted was not found. * - NB_ERR for other errors. */ extern int nb_candidate_edit(struct nb_config *candidate, const struct nb_node *nb_node, enum nb_operation operation, const char *xpath, const struct yang_data *previous, const struct yang_data *data); /* * Check if a candidate configuration is outdated and needs to be updated. * * candidate * Candidate configuration to check. * * Returns: * true if the candidate is outdated, false otherwise. */ extern bool nb_candidate_needs_update(const struct nb_config *candidate); /* * Update a candidate configuration by rebasing the changes on top of the latest * running configuration. Resolve conflicts automatically by giving preference * to the changes done in the candidate configuration. * * candidate * Candidate configuration to update. * * Returns: * NB_OK on success, NB_ERR otherwise. */ extern int nb_candidate_update(struct nb_config *candidate); /* * Validate a candidate configuration. Perform both YANG syntactic/semantic * validation and code-level validation using the northbound callbacks. * * WARNING: the candidate can be modified as part of the validation process * (e.g. add default nodes). * * candidate * Candidate configuration to validate. * * Returns: * NB_OK on success, NB_ERR_VALIDATION otherwise. */ extern int nb_candidate_validate(struct nb_config *candidate); /* * Create a new configuration transaction but do not commit it yet. Only * validate the candidate and prepare all resources required to apply the * configuration changes. * * candidate * Candidate configuration to commit. * * client * Northbound client performing the commit. * * user * Northbound user performing the commit (can be NULL). * * comment * Optional comment describing the commit. * * transaction * Output parameter providing the created transaction when one is created * successfully. In this case, it must be either aborted using * nb_candidate_commit_abort() or committed using * nb_candidate_commit_apply(). * * Returns: * - NB_OK on success. * - NB_ERR_NO_CHANGES when the candidate is identical to the running * configuration. * - NB_ERR_LOCKED when there's already another transaction in progress. * - NB_ERR_VALIDATION when the candidate fails the validation checks. * - NB_ERR_RESOURCE when the system fails to allocate resources to apply * the candidate configuration. * - NB_ERR for other errors. */ extern int nb_candidate_commit_prepare(struct nb_config *candidate, enum nb_client client, const void *user, const char *comment, struct nb_transaction **transaction); /* * Abort a previously created configuration transaction, releasing all resources * allocated during the preparation phase. * * transaction * Candidate configuration to abort. It's consumed by this function. */ extern void nb_candidate_commit_abort(struct nb_transaction *transaction); /* * Commit a previously created configuration transaction. * * transaction * Configuration transaction to commit. It's consumed by this function. * * save_transaction * Specify whether the transaction should be recorded in the transactions log * or not. * * transaction_id * Optional output parameter providing the ID of the committed transaction. */ extern void nb_candidate_commit_apply(struct nb_transaction *transaction, bool save_transaction, uint32_t *transaction_id); /* * Create a new transaction to commit a candidate configuration. This is a * convenience function that performs the two-phase commit protocol * transparently to the user. The cost is reduced flexibility, since * network-wide and multi-daemon transactions require the network manager to * take into account the results of the preparation phase of multiple managed * entities. * * candidate * Candidate configuration to commit. It's preserved regardless if the commit * operation fails or not. * * client * Northbound client performing the commit. * * user * Northbound user performing the commit (can be NULL). * * save_transaction * Specify whether the transaction should be recorded in the transactions log * or not. * * comment * Optional comment describing the commit. * * transaction_id * Optional output parameter providing the ID of the committed transaction. * * Returns: * - NB_OK on success. * - NB_ERR_NO_CHANGES when the candidate is identical to the running * configuration. * - NB_ERR_LOCKED when there's already another transaction in progress. * - NB_ERR_VALIDATION when the candidate fails the validation checks. * - NB_ERR_RESOURCE when the system fails to allocate resources to apply * the candidate configuration. * - NB_ERR for other errors. */ extern int nb_candidate_commit(struct nb_config *candidate, enum nb_client client, const void *user, bool save_transaction, const char *comment, uint32_t *transaction_id); /* * Lock the running configuration. * * client * Northbound client. * * user * Northbound user (can be NULL). * * Returns: * 0 on success, -1 when the running configuration is already locked. */ extern int nb_running_lock(enum nb_client client, const void *user); /* * Unlock the running configuration. * * client * Northbound client. * * user * Northbound user (can be NULL). * * Returns: * 0 on success, -1 when the running configuration is already unlocked or * locked by another client/user. */ extern int nb_running_unlock(enum nb_client client, const void *user); /* * Check if the running configuration is locked or not for the given * client/user. * * client * Northbound client. * * user * Northbound user (can be NULL). * * Returns: * 0 if the running configuration is unlocked or if the client/user owns the * lock, -1 otherwise. */ extern int nb_running_lock_check(enum nb_client client, const void *user); /* * Iterate over operational data. * * xpath * Data path of the YANG data we want to iterate over. * * translator * YANG module translator (might be NULL). * * flags * NB_OPER_DATA_ITER_ flags to control how the iteration is performed. * * cb * Function to call with each data node. * * arg * Arbitrary argument passed as the fourth parameter in each call to 'cb'. * * Returns: * NB_OK on success, NB_ERR otherwise. */ extern int nb_oper_data_iterate(const char *xpath, struct yang_translator *translator, uint32_t flags, nb_oper_data_cb cb, void *arg); /* * Validate if the northbound operation is valid for the given node. * * operation * Operation we want to check. * * snode * libyang schema node we want to check. * * Returns: * true if the operation is valid, false otherwise. */ extern bool nb_operation_is_valid(enum nb_operation operation, const struct lys_node *snode); /* * Send a YANG notification. This is a no-op unless the 'nb_notification_send' * hook was registered by a northbound plugin. * * xpath * XPath of the YANG notification. * * arguments * Linked list containing the arguments that should be sent. This list is * deleted after being used. * * Returns: * NB_OK on success, NB_ERR otherwise. */ extern int nb_notification_send(const char *xpath, struct list *arguments); /* * Associate a user pointer to a configuration node. * * This should be called by northbound 'create' callbacks in the NB_EV_APPLY * phase only. * * dnode * libyang data node - only its XPath is used. * * entry * Arbitrary user-specified pointer. */ extern void nb_running_set_entry(const struct lyd_node *dnode, void *entry); /* * Unset the user pointer associated to a configuration node. * * This should be called by northbound 'destroy' callbacks in the NB_EV_APPLY * phase only. * * dnode * libyang data node - only its XPath is used. * * Returns: * The user pointer that was unset. */ extern void *nb_running_unset_entry(const struct lyd_node *dnode); /* * Find the user pointer (if any) associated to a configuration node. * * The XPath associated to the configuration node can be provided directly or * indirectly through a libyang data node. * * If an user point is not found, this function follows the parent nodes in the * running configuration until an user pointer is found or until the root node * is reached. * * dnode * libyang data node - only its XPath is used (can be NULL if 'xpath' is * provided). * * xpath * XPath of the configuration node (can be NULL if 'dnode' is provided). * * abort_if_not_found * When set to true, abort the program if no user pointer is found. * * As a rule of thumb, this parameter should be set to true in the following * scenarios: * - Calling this function from any northbound configuration callback during * the NB_EV_APPLY phase. * - Calling this function from a 'delete' northbound configuration callback * during any phase. * * In both the above cases, the given configuration node should contain an * user pointer except when there's a bug in the code, in which case it's * better to abort the program right away and eliminate the need for * unnecessary NULL checks. * * In all other cases, this parameter should be set to false and the caller * should check if the function returned NULL or not. * * Returns: * User pointer if found, NULL otherwise. */ extern void *nb_running_get_entry(const struct lyd_node *dnode, const char *xpath, bool abort_if_not_found); /* * Return a human-readable string representing a northbound event. * * event * Northbound event. * * Returns: * String representation of the given northbound event. */ extern const char *nb_event_name(enum nb_event event); /* * Return a human-readable string representing a northbound operation. * * operation * Northbound operation. * * Returns: * String representation of the given northbound operation. */ extern const char *nb_operation_name(enum nb_operation operation); /* * Return a human-readable string representing a northbound error. * * error * Northbound error. * * Returns: * String representation of the given northbound error. */ extern const char *nb_err_name(enum nb_error error); /* * Return a human-readable string representing a northbound client. * * client * Northbound client. * * Returns: * String representation of the given northbound client. */ extern const char *nb_client_name(enum nb_client client); /* * Initialize the northbound layer. Should be called only once during the * daemon initialization process. * * modules * Array of YANG modules to parse and initialize. * * nmodules * Size of the modules array. */ extern void nb_init(struct thread_master *tm, const struct frr_yang_module_info *modules[], size_t nmodules); /* * Finish the northbound layer gracefully. Should be called only when the daemon * is exiting. */ extern void nb_terminate(void); #ifdef __cplusplus } #endif #endif /* _FRR_NORTHBOUND_H_ */ frr-7.2.1/lib/northbound_cli.c0000644000000000000000000013556113610377563013152 00000000000000/* * Copyright (C) 2018 NetDEF, Inc. * Renato Westphal * * This program is free software; you can redistribute it and/or modify it * under the terms of the 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; see the file COPYING; if not, write to the Free Software * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA */ #include #include "libfrr.h" #include "version.h" #include "log.h" #include "lib_errors.h" #include "command.h" #include "termtable.h" #include "db.h" #include "debug.h" #include "yang_translator.h" #include "northbound.h" #include "northbound_cli.h" #include "northbound_db.h" #ifndef VTYSH_EXTRACT_PL #include "lib/northbound_cli_clippy.c" #endif struct debug nb_dbg_cbs_config = {0, "Northbound callbacks: configuration"}; struct debug nb_dbg_cbs_state = {0, "Northbound callbacks: state"}; struct debug nb_dbg_cbs_rpc = {0, "Northbound callbacks: RPCs"}; struct debug nb_dbg_notif = {0, "Northbound notifications"}; struct debug nb_dbg_events = {0, "Northbound events"}; struct nb_config *vty_shared_candidate_config; static struct thread_master *master; static void vty_show_libyang_errors(struct vty *vty, struct ly_ctx *ly_ctx) { struct ly_err_item *ei; const char *path; ei = ly_err_first(ly_ctx); if (!ei) return; for (; ei; ei = ei->next) vty_out(vty, "%s\n", ei->msg); path = ly_errpath(ly_ctx); if (path) vty_out(vty, "YANG path: %s\n", path); ly_err_clean(ly_ctx, NULL); } void nb_cli_enqueue_change(struct vty *vty, const char *xpath, enum nb_operation operation, const char *value) { struct vty_cfg_change *change; if (vty->num_cfg_changes == VTY_MAXCFGCHANGES) { /* Not expected to happen. */ vty_out(vty, "%% Exceeded the maximum number of changes (%u) for a single command\n\n", VTY_MAXCFGCHANGES); return; } change = &vty->cfg_changes[vty->num_cfg_changes++]; strlcpy(change->xpath, xpath, sizeof(change->xpath)); change->operation = operation; change->value = value; } int nb_cli_apply_changes(struct vty *vty, const char *xpath_base_fmt, ...) { struct nb_config *candidate_transitory; char xpath_base[XPATH_MAXLEN] = {}; bool error = false; int ret; VTY_CHECK_XPATH; /* * Create a copy of the candidate configuration. For consistency, we * need to ensure that either all changes made by the command are * accepted or none are. */ candidate_transitory = nb_config_dup(vty->candidate_config); /* Parse the base XPath format string. */ if (xpath_base_fmt) { va_list ap; va_start(ap, xpath_base_fmt); vsnprintf(xpath_base, sizeof(xpath_base), xpath_base_fmt, ap); va_end(ap); } /* Edit candidate configuration. */ for (size_t i = 0; i < vty->num_cfg_changes; i++) { struct vty_cfg_change *change = &vty->cfg_changes[i]; struct nb_node *nb_node; char xpath[XPATH_MAXLEN]; struct yang_data *data; /* Handle relative XPaths. */ memset(xpath, 0, sizeof(xpath)); if (vty->xpath_index > 0 && ((xpath_base_fmt && xpath_base[0] == '.') || change->xpath[0] == '.')) strlcpy(xpath, VTY_CURR_XPATH, sizeof(xpath)); if (xpath_base_fmt) { if (xpath_base[0] == '.') strlcat(xpath, xpath_base + 1, sizeof(xpath)); else strlcat(xpath, xpath_base, sizeof(xpath)); } if (change->xpath[0] == '.') strlcat(xpath, change->xpath + 1, sizeof(xpath)); else strlcpy(xpath, change->xpath, sizeof(xpath)); /* Find the northbound node associated to the data path. */ nb_node = nb_node_find(xpath); if (!nb_node) { flog_warn(EC_LIB_YANG_UNKNOWN_DATA_PATH, "%s: unknown data path: %s", __func__, xpath); error = true; break; } /* If the value is not set, get the default if it exists. */ if (change->value == NULL) change->value = yang_snode_get_default(nb_node->snode); data = yang_data_new(xpath, change->value); /* * Ignore "not found" errors when editing the candidate * configuration. */ ret = nb_candidate_edit(candidate_transitory, nb_node, change->operation, xpath, NULL, data); yang_data_free(data); if (ret != NB_OK && ret != NB_ERR_NOT_FOUND) { flog_warn( EC_LIB_NB_CANDIDATE_EDIT_ERROR, "%s: failed to edit candidate configuration: operation [%s] xpath [%s]", __func__, nb_operation_name(change->operation), xpath); error = true; break; } } if (error) { nb_config_free(candidate_transitory); switch (frr_get_cli_mode()) { case FRR_CLI_CLASSIC: vty_out(vty, "%% Configuration failed.\n\n"); break; case FRR_CLI_TRANSACTIONAL: vty_out(vty, "%% Failed to edit candidate configuration.\n\n"); break; } vty_show_libyang_errors(vty, ly_native_ctx); return CMD_WARNING_CONFIG_FAILED; } nb_config_replace(vty->candidate_config, candidate_transitory, false); /* Do an implicit "commit" when using the classic CLI mode. */ if (frr_get_cli_mode() == FRR_CLI_CLASSIC) { ret = nb_candidate_commit(vty->candidate_config, NB_CLIENT_CLI, vty, false, NULL, NULL); if (ret != NB_OK && ret != NB_ERR_NO_CHANGES) { vty_out(vty, "%% Configuration failed: %s.\n\n", nb_err_name(ret)); vty_out(vty, "Please check the logs for more details.\n"); /* Regenerate candidate for consistency. */ pthread_rwlock_rdlock(&running_config->lock); { nb_config_replace(vty->candidate_config, running_config, true); } pthread_rwlock_unlock(&running_config->lock); return CMD_WARNING_CONFIG_FAILED; } } return CMD_SUCCESS; } int nb_cli_rpc(const char *xpath, struct list *input, struct list *output) { struct nb_node *nb_node; int ret; nb_node = nb_node_find(xpath); if (!nb_node) { flog_warn(EC_LIB_YANG_UNKNOWN_DATA_PATH, "%s: unknown data path: %s", __func__, xpath); return CMD_WARNING; } ret = nb_callback_rpc(nb_node, xpath, input, output); switch (ret) { case NB_OK: return CMD_SUCCESS; default: return CMD_WARNING; } } void nb_cli_confirmed_commit_clean(struct vty *vty) { THREAD_TIMER_OFF(vty->t_confirmed_commit_timeout); nb_config_free(vty->confirmed_commit_rollback); vty->confirmed_commit_rollback = NULL; } int nb_cli_confirmed_commit_rollback(struct vty *vty) { uint32_t transaction_id; int ret; /* Perform the rollback. */ ret = nb_candidate_commit( vty->confirmed_commit_rollback, NB_CLIENT_CLI, vty, true, "Rollback to previous configuration - confirmed commit has timed out", &transaction_id); if (ret == NB_OK) vty_out(vty, "Rollback performed successfully (Transaction ID #%u).\n", transaction_id); else vty_out(vty, "Failed to rollback to previous configuration.\n"); return ret; } static int nb_cli_confirmed_commit_timeout(struct thread *thread) { struct vty *vty = THREAD_ARG(thread); /* XXX: broadcast this message to all logged-in users? */ vty_out(vty, "\nConfirmed commit has timed out, rolling back to previous configuration.\n\n"); nb_cli_confirmed_commit_rollback(vty); nb_cli_confirmed_commit_clean(vty); return 0; } static int nb_cli_commit(struct vty *vty, bool force, unsigned int confirmed_timeout, char *comment) { uint32_t transaction_id = 0; int ret; /* Check if there's a pending confirmed commit. */ if (vty->t_confirmed_commit_timeout) { if (confirmed_timeout) { /* Reset timeout if "commit confirmed" is used again. */ vty_out(vty, "%% Resetting confirmed-commit timeout to %u minute(s)\n\n", confirmed_timeout); THREAD_TIMER_OFF(vty->t_confirmed_commit_timeout); thread_add_timer(master, nb_cli_confirmed_commit_timeout, vty, confirmed_timeout * 60, &vty->t_confirmed_commit_timeout); } else { /* Accept commit confirmation. */ vty_out(vty, "%% Commit complete.\n\n"); nb_cli_confirmed_commit_clean(vty); } return CMD_SUCCESS; } /* "force" parameter. */ if (!force && nb_candidate_needs_update(vty->candidate_config)) { vty_out(vty, "%% Candidate configuration needs to be updated before commit.\n\n"); vty_out(vty, "Use the \"update\" command or \"commit force\".\n"); return CMD_WARNING; } /* "confirm" parameter. */ if (confirmed_timeout) { pthread_rwlock_rdlock(&running_config->lock); { vty->confirmed_commit_rollback = nb_config_dup(running_config); } pthread_rwlock_unlock(&running_config->lock); vty->t_confirmed_commit_timeout = NULL; thread_add_timer(master, nb_cli_confirmed_commit_timeout, vty, confirmed_timeout * 60, &vty->t_confirmed_commit_timeout); } ret = nb_candidate_commit(vty->candidate_config, NB_CLIENT_CLI, vty, true, comment, &transaction_id); /* Map northbound return code to CLI return code. */ switch (ret) { case NB_OK: pthread_rwlock_rdlock(&running_config->lock); { nb_config_replace(vty->candidate_config_base, running_config, true); } pthread_rwlock_unlock(&running_config->lock); vty_out(vty, "%% Configuration committed successfully (Transaction ID #%u).\n\n", transaction_id); return CMD_SUCCESS; case NB_ERR_NO_CHANGES: vty_out(vty, "%% No configuration changes to commit.\n\n"); return CMD_SUCCESS; default: vty_out(vty, "%% Failed to commit candidate configuration: %s.\n\n", nb_err_name(ret)); vty_out(vty, "Please check the logs for more details.\n"); return CMD_WARNING; } } static int nb_cli_candidate_load_file(struct vty *vty, enum nb_cfg_format format, struct yang_translator *translator, const char *path, bool replace) { struct nb_config *loaded_config = NULL; struct lyd_node *dnode; struct ly_ctx *ly_ctx; int ly_format; switch (format) { case NB_CFG_FMT_CMDS: loaded_config = nb_config_new(NULL); if (!vty_read_config(loaded_config, path, config_default)) { vty_out(vty, "%% Failed to load configuration.\n\n"); vty_out(vty, "Please check the logs for more details.\n"); nb_config_free(loaded_config); return CMD_WARNING; } break; case NB_CFG_FMT_JSON: case NB_CFG_FMT_XML: ly_format = (format == NB_CFG_FMT_JSON) ? LYD_JSON : LYD_XML; ly_ctx = translator ? translator->ly_ctx : ly_native_ctx; dnode = lyd_parse_path(ly_ctx, path, ly_format, LYD_OPT_EDIT); if (!dnode) { flog_warn(EC_LIB_LIBYANG, "%s: lyd_parse_path() failed", __func__); vty_out(vty, "%% Failed to load configuration:\n\n"); vty_show_libyang_errors(vty, ly_ctx); return CMD_WARNING; } if (translator && yang_translate_dnode(translator, YANG_TRANSLATE_TO_NATIVE, &dnode) != YANG_TRANSLATE_SUCCESS) { vty_out(vty, "%% Failed to translate configuration\n"); yang_dnode_free(dnode); return CMD_WARNING; } loaded_config = nb_config_new(dnode); break; } if (replace) nb_config_replace(vty->candidate_config, loaded_config, false); else if (nb_config_merge(vty->candidate_config, loaded_config, false) != NB_OK) { vty_out(vty, "%% Failed to merge the loaded configuration:\n\n"); vty_show_libyang_errors(vty, ly_native_ctx); return CMD_WARNING; } return CMD_SUCCESS; } static int nb_cli_candidate_load_transaction(struct vty *vty, uint32_t transaction_id, bool replace) { struct nb_config *loaded_config; loaded_config = nb_db_transaction_load(transaction_id); if (!loaded_config) { vty_out(vty, "%% Transaction %u does not exist.\n\n", transaction_id); return CMD_WARNING; } if (replace) nb_config_replace(vty->candidate_config, loaded_config, false); else if (nb_config_merge(vty->candidate_config, loaded_config, false) != NB_OK) { vty_out(vty, "%% Failed to merge the loaded configuration:\n\n"); vty_show_libyang_errors(vty, ly_native_ctx); return CMD_WARNING; } return CMD_SUCCESS; } /* * ly_iter_next_is_up: detects when iterating up on the yang model. * * This function detects whether next node in the iteration is upwards, * then return the node otherwise return NULL. */ static struct lyd_node *ly_iter_next_up(const struct lyd_node *elem) { /* Are we going downwards? Is this still not a leaf? */ if (!(elem->schema->nodetype & (LYS_LEAF | LYS_LEAFLIST | LYS_ANYDATA))) return NULL; /* Are there still leaves in this branch? */ if (elem->next != NULL) return NULL; return elem->parent; } void nb_cli_show_dnode_cmds(struct vty *vty, struct lyd_node *root, bool with_defaults) { struct lyd_node *next, *child, *parent; LY_TREE_DFS_BEGIN (root, next, child) { struct nb_node *nb_node; nb_node = child->schema->priv; if (!nb_node->cbs.cli_show) goto next; /* Skip default values. */ if (!with_defaults && yang_dnode_is_default_recursive(child)) goto next; (*nb_node->cbs.cli_show)(vty, child, with_defaults); next: /* * When transiting upwards in the yang model we should * give the previous container/list node a chance to * print its close vty output (e.g. "!" or "end-family" * etc...). */ parent = ly_iter_next_up(child); if (parent != NULL) { nb_node = parent->schema->priv; if (nb_node->cbs.cli_show_end) (*nb_node->cbs.cli_show_end)(vty, parent); } LY_TREE_DFS_END(root, next, child); } } static void nb_cli_show_config_cmds(struct vty *vty, struct nb_config *config, bool with_defaults) { struct lyd_node *root; vty_out(vty, "Configuration:\n"); vty_out(vty, "!\n"); vty_out(vty, "frr version %s\n", FRR_VER_SHORT); vty_out(vty, "frr defaults %s\n", DFLT_NAME); LY_TREE_FOR (config->dnode, root) nb_cli_show_dnode_cmds(vty, root, with_defaults); vty_out(vty, "!\n"); vty_out(vty, "end\n"); } static int nb_cli_show_config_libyang(struct vty *vty, LYD_FORMAT format, struct nb_config *config, struct yang_translator *translator, bool with_defaults) { struct lyd_node *dnode; char *strp; int options = 0; dnode = yang_dnode_dup(config->dnode); if (translator && yang_translate_dnode(translator, YANG_TRANSLATE_FROM_NATIVE, &dnode) != YANG_TRANSLATE_SUCCESS) { vty_out(vty, "%% Failed to translate configuration\n"); yang_dnode_free(dnode); return CMD_WARNING; } SET_FLAG(options, LYP_FORMAT | LYP_WITHSIBLINGS); if (with_defaults) SET_FLAG(options, LYP_WD_ALL); else SET_FLAG(options, LYP_WD_TRIM); if (lyd_print_mem(&strp, dnode, format, options) == 0 && strp) { vty_out(vty, "%s", strp); free(strp); } yang_dnode_free(dnode); return CMD_SUCCESS; } static int nb_cli_show_config(struct vty *vty, struct nb_config *config, enum nb_cfg_format format, struct yang_translator *translator, bool with_defaults) { switch (format) { case NB_CFG_FMT_CMDS: nb_cli_show_config_cmds(vty, config, with_defaults); break; case NB_CFG_FMT_JSON: return nb_cli_show_config_libyang(vty, LYD_JSON, config, translator, with_defaults); case NB_CFG_FMT_XML: return nb_cli_show_config_libyang(vty, LYD_XML, config, translator, with_defaults); } return CMD_SUCCESS; } static int nb_write_config(struct nb_config *config, enum nb_cfg_format format, struct yang_translator *translator, char *path, size_t pathlen) { int fd; struct vty *file_vty; int ret = 0; snprintf(path, pathlen, "/tmp/frr.tmp.XXXXXXXX"); fd = mkstemp(path); if (fd < 0) { flog_warn(EC_LIB_SYSTEM_CALL, "%s: mkstemp() failed: %s", __func__, safe_strerror(errno)); return -1; } /* Make vty for configuration file. */ file_vty = vty_new(); file_vty->wfd = fd; file_vty->type = VTY_FILE; if (config) ret = nb_cli_show_config(file_vty, config, format, translator, false); vty_close(file_vty); return ret; } static int nb_cli_show_config_compare(struct vty *vty, struct nb_config *config1, struct nb_config *config2, enum nb_cfg_format format, struct yang_translator *translator) { char config1_path[256]; char config2_path[256]; char command[BUFSIZ]; FILE *fp; char line[1024]; int lineno = 0; if (nb_write_config(config1, format, translator, config1_path, sizeof(config1_path)) != 0) { vty_out(vty, "%% Failed to process configurations.\n\n"); return CMD_WARNING; } if (nb_write_config(config2, format, translator, config2_path, sizeof(config2_path)) != 0) { vty_out(vty, "%% Failed to process configurations.\n\n"); unlink(config1_path); return CMD_WARNING; } snprintf(command, sizeof(command), "diff -u %s %s", config1_path, config2_path); fp = popen(command, "r"); if (!fp) { vty_out(vty, "%% Failed to generate configuration diff.\n\n"); unlink(config1_path); unlink(config2_path); return CMD_WARNING; } /* Print diff line by line. */ while (fgets(line, sizeof(line), fp) != NULL) { if (lineno++ < 2) continue; vty_out(vty, "%s", line); } pclose(fp); unlink(config1_path); unlink(config2_path); return CMD_SUCCESS; } /* Configure exclusively from this terminal. */ DEFUN (config_exclusive, config_exclusive_cmd, "configure exclusive", "Configuration from vty interface\n" "Configure exclusively from this terminal\n") { return vty_config_enter(vty, true, true); } /* Configure using a private candidate configuration. */ DEFUN (config_private, config_private_cmd, "configure private", "Configuration from vty interface\n" "Configure using a private candidate configuration\n") { return vty_config_enter(vty, true, false); } DEFPY (config_commit, config_commit_cmd, "commit [{force$force|confirmed (1-60)}]", "Commit changes into the running configuration\n" "Force commit even if the candidate is outdated\n" "Rollback this commit unless there is a confirming commit\n" "Timeout in minutes for the commit to be confirmed\n") { return nb_cli_commit(vty, !!force, confirmed, NULL); } DEFPY (config_commit_comment, config_commit_comment_cmd, "commit [{force$force|confirmed (1-60)}] comment LINE...", "Commit changes into the running configuration\n" "Force commit even if the candidate is outdated\n" "Rollback this commit unless there is a confirming commit\n" "Timeout in minutes for the commit to be confirmed\n" "Assign a comment to this commit\n" "Comment for this commit (Max 80 characters)\n") { char *comment; int idx = 0; int ret; argv_find(argv, argc, "LINE", &idx); comment = argv_concat(argv, argc, idx); ret = nb_cli_commit(vty, !!force, confirmed, comment); XFREE(MTYPE_TMP, comment); return ret; } DEFPY (config_commit_check, config_commit_check_cmd, "commit check", "Commit changes into the running configuration\n" "Check if the configuration changes are valid\n") { int ret; ret = nb_candidate_validate(vty->candidate_config); if (ret != NB_OK) { vty_out(vty, "%% Failed to validate candidate configuration.\n\n"); vty_show_libyang_errors(vty, ly_native_ctx); return CMD_WARNING; } vty_out(vty, "%% Candidate configuration validated successfully.\n\n"); return CMD_SUCCESS; } DEFPY (config_update, config_update_cmd, "update", "Update candidate configuration\n") { if (!nb_candidate_needs_update(vty->candidate_config)) { vty_out(vty, "%% Update is not necessary.\n\n"); return CMD_SUCCESS; } if (nb_candidate_update(vty->candidate_config) != NB_OK) { vty_out(vty, "%% Failed to update the candidate configuration.\n\n"); vty_out(vty, "Please check the logs for more details.\n"); return CMD_WARNING; } pthread_rwlock_rdlock(&running_config->lock); { nb_config_replace(vty->candidate_config_base, running_config, true); } pthread_rwlock_unlock(&running_config->lock); vty_out(vty, "%% Candidate configuration updated successfully.\n\n"); return CMD_SUCCESS; } DEFPY (config_discard, config_discard_cmd, "discard", "Discard changes in the candidate configuration\n") { nb_config_replace(vty->candidate_config, vty->candidate_config_base, true); return CMD_SUCCESS; } DEFPY (config_load, config_load_cmd, "configuration load\ <\ file [ [translate WORD$translator_family]] FILENAME$filename\ |transaction (1-4294967295)$tid\ >\ [replace$replace]", "Configuration related settings\n" "Load configuration into candidate\n" "Load configuration file into candidate\n" "Load configuration file in JSON format\n" "Load configuration file in XML format\n" "Translate configuration file\n" "YANG module translator\n" "Configuration file name (full path)\n" "Load configuration from transaction into candidate\n" "Transaction ID\n" "Replace instead of merge\n") { if (filename) { enum nb_cfg_format format; struct yang_translator *translator = NULL; if (json) format = NB_CFG_FMT_JSON; else if (xml) format = NB_CFG_FMT_XML; else format = NB_CFG_FMT_CMDS; if (translator_family) { translator = yang_translator_find(translator_family); if (!translator) { vty_out(vty, "%% Module translator \"%s\" not found\n", translator_family); return CMD_WARNING; } } return nb_cli_candidate_load_file(vty, format, translator, filename, !!replace); } return nb_cli_candidate_load_transaction(vty, tid, !!replace); } DEFPY (show_config_running, show_config_running_cmd, "show configuration running\ [ [translate WORD$translator_family]]\ [with-defaults$with_defaults]", SHOW_STR "Configuration information\n" "Running configuration\n" "Change output format to JSON\n" "Change output format to XML\n" "Translate output\n" "YANG module translator\n" "Show default values\n") { enum nb_cfg_format format; struct yang_translator *translator = NULL; if (json) format = NB_CFG_FMT_JSON; else if (xml) format = NB_CFG_FMT_XML; else format = NB_CFG_FMT_CMDS; if (translator_family) { translator = yang_translator_find(translator_family); if (!translator) { vty_out(vty, "%% Module translator \"%s\" not found\n", translator_family); return CMD_WARNING; } } pthread_rwlock_rdlock(&running_config->lock); { nb_cli_show_config(vty, running_config, format, translator, !!with_defaults); } pthread_rwlock_unlock(&running_config->lock); return CMD_SUCCESS; } DEFPY (show_config_candidate, show_config_candidate_cmd, "show configuration candidate\ [ [translate WORD$translator_family]]\ [<\ with-defaults$with_defaults\ |changes$changes\ >]", SHOW_STR "Configuration information\n" "Candidate configuration\n" "Change output format to JSON\n" "Change output format to XML\n" "Translate output\n" "YANG module translator\n" "Show default values\n" "Show changes applied in the candidate configuration\n") { enum nb_cfg_format format; struct yang_translator *translator = NULL; if (json) format = NB_CFG_FMT_JSON; else if (xml) format = NB_CFG_FMT_XML; else format = NB_CFG_FMT_CMDS; if (translator_family) { translator = yang_translator_find(translator_family); if (!translator) { vty_out(vty, "%% Module translator \"%s\" not found\n", translator_family); return CMD_WARNING; } } if (changes) return nb_cli_show_config_compare( vty, vty->candidate_config_base, vty->candidate_config, format, translator); nb_cli_show_config(vty, vty->candidate_config, format, translator, !!with_defaults); return CMD_SUCCESS; } DEFPY (show_config_candidate_section, show_config_candidate_section_cmd, "show", SHOW_STR) { struct lyd_node *dnode; /* Top-level configuration node, display everything. */ if (vty->xpath_index == 0) return nb_cli_show_config(vty, vty->candidate_config, NB_CFG_FMT_CMDS, NULL, false); /* Display only the current section of the candidate configuration. */ dnode = yang_dnode_get(vty->candidate_config->dnode, VTY_CURR_XPATH); if (!dnode) /* Shouldn't happen. */ return CMD_WARNING; nb_cli_show_dnode_cmds(vty, dnode, 0); vty_out(vty, "!\n"); return CMD_SUCCESS; } DEFPY (show_config_compare, show_config_compare_cmd, "show configuration compare\ <\ candidate$c1_candidate\ |running$c1_running\ |transaction (1-4294967295)$c1_tid\ >\ <\ candidate$c2_candidate\ |running$c2_running\ |transaction (1-4294967295)$c2_tid\ >\ [ [translate WORD$translator_family]]", SHOW_STR "Configuration information\n" "Compare two different configurations\n" "Candidate configuration\n" "Running configuration\n" "Configuration transaction\n" "Transaction ID\n" "Candidate configuration\n" "Running configuration\n" "Configuration transaction\n" "Transaction ID\n" "Change output format to JSON\n" "Change output format to XML\n" "Translate output\n" "YANG module translator\n") { enum nb_cfg_format format; struct yang_translator *translator = NULL; struct nb_config *config1, *config_transaction1 = NULL; struct nb_config *config2, *config_transaction2 = NULL; int ret = CMD_WARNING; /* * For simplicity, lock the running configuration regardless if it's * going to be used or not. */ pthread_rwlock_rdlock(&running_config->lock); { if (c1_candidate) config1 = vty->candidate_config; else if (c1_running) config1 = running_config; else { config_transaction1 = nb_db_transaction_load(c1_tid); if (!config_transaction1) { vty_out(vty, "%% Transaction %u does not exist\n\n", (unsigned int)c1_tid); goto exit; } config1 = config_transaction1; } if (c2_candidate) config2 = vty->candidate_config; else if (c2_running) config2 = running_config; else { config_transaction2 = nb_db_transaction_load(c2_tid); if (!config_transaction2) { vty_out(vty, "%% Transaction %u does not exist\n\n", (unsigned int)c2_tid); goto exit; } config2 = config_transaction2; } if (json) format = NB_CFG_FMT_JSON; else if (xml) format = NB_CFG_FMT_XML; else format = NB_CFG_FMT_CMDS; if (translator_family) { translator = yang_translator_find(translator_family); if (!translator) { vty_out(vty, "%% Module translator \"%s\" not found\n", translator_family); goto exit; } } ret = nb_cli_show_config_compare(vty, config1, config2, format, translator); exit: if (config_transaction1) nb_config_free(config_transaction1); if (config_transaction2) nb_config_free(config_transaction2); } pthread_rwlock_unlock(&running_config->lock); return ret; } /* * Stripped down version of the "show configuration compare" command. * The "candidate" option is not present so the command can be installed in * the enable node. */ ALIAS (show_config_compare, show_config_compare_without_candidate_cmd, "show configuration compare\ <\ running$c1_running\ |transaction (1-4294967295)$c1_tid\ >\ <\ running$c2_running\ |transaction (1-4294967295)$c2_tid\ >\ [ [translate WORD$translator_family]]", SHOW_STR "Configuration information\n" "Compare two different configurations\n" "Running configuration\n" "Configuration transaction\n" "Transaction ID\n" "Running configuration\n" "Configuration transaction\n" "Transaction ID\n" "Change output format to JSON\n" "Change output format to XML\n" "Translate output\n" "YANG module translator\n") DEFPY (clear_config_transactions, clear_config_transactions_cmd, "clear configuration transactions oldest (1-100)$n", CLEAR_STR "Configuration activity\n" "Delete transactions from the transactions log\n" "Delete oldest transactions\n" "Number of transactions to delete\n") { #ifdef HAVE_CONFIG_ROLLBACKS if (nb_db_clear_transactions(n) != NB_OK) { vty_out(vty, "%% Failed to delete transactions.\n\n"); return CMD_WARNING; } #else vty_out(vty, "%% FRR was compiled without --enable-config-rollbacks.\n\n"); #endif /* HAVE_CONFIG_ROLLBACKS */ return CMD_SUCCESS; } DEFPY (config_database_max_transactions, config_database_max_transactions_cmd, "configuration database max-transactions (1-100)$max", "Configuration related settings\n" "Configuration database\n" "Set the maximum number of transactions to store\n" "Number of transactions\n") { #ifdef HAVE_CONFIG_ROLLBACKS if (nb_db_set_max_transactions(max) != NB_OK) { vty_out(vty, "%% Failed to update the maximum number of transactions.\n\n"); return CMD_WARNING; } vty_out(vty, "%% Maximum number of transactions updated successfully.\n\n"); #else vty_out(vty, "%% FRR was compiled without --enable-config-rollbacks.\n\n"); #endif /* HAVE_CONFIG_ROLLBACKS */ return CMD_SUCCESS; } DEFPY (yang_module_translator_load, yang_module_translator_load_cmd, "yang module-translator load FILENAME$filename", "YANG related settings\n" "YANG module translator\n" "Load YANG module translator\n" "File name (full path)\n") { struct yang_translator *translator; translator = yang_translator_load(filename); if (!translator) { vty_out(vty, "%% Failed to load \"%s\"\n\n", filename); vty_out(vty, "Please check the logs for more details.\n"); return CMD_WARNING; } vty_out(vty, "%% Module translator \"%s\" loaded successfully.\n\n", translator->family); return CMD_SUCCESS; } DEFPY (yang_module_translator_unload_family, yang_module_translator_unload_cmd, "yang module-translator unload WORD$translator_family", "YANG related settings\n" "YANG module translator\n" "Unload YANG module translator\n" "Name of the module translator\n") { struct yang_translator *translator; translator = yang_translator_find(translator_family); if (!translator) { vty_out(vty, "%% Module translator \"%s\" not found\n", translator_family); return CMD_WARNING; } yang_translator_unload(translator); return CMD_SUCCESS; } #ifdef HAVE_CONFIG_ROLLBACKS static void nb_cli_show_transactions_cb(void *arg, int transaction_id, const char *client_name, const char *date, const char *comment) { struct ttable *tt = arg; ttable_add_row(tt, "%d|%s|%s|%s", transaction_id, client_name, date, comment); } static int nb_cli_show_transactions(struct vty *vty) { struct ttable *tt; /* Prepare table. */ tt = ttable_new(&ttable_styles[TTSTYLE_BLANK]); ttable_add_row(tt, "Transaction ID|Client|Date|Comment"); tt->style.cell.rpad = 2; tt->style.corner = '+'; ttable_restyle(tt); ttable_rowseps(tt, 0, BOTTOM, true, '-'); /* Fetch transactions from the northbound database. */ if (nb_db_transactions_iterate(nb_cli_show_transactions_cb, tt) != NB_OK) { vty_out(vty, "%% Failed to fetch configuration transactions.\n"); return CMD_WARNING; } /* Dump the generated table. */ if (tt->nrows > 1) { char *table; table = ttable_dump(tt, "\n"); vty_out(vty, "%s\n", table); XFREE(MTYPE_TMP, table); } else vty_out(vty, "No configuration transactions to display.\n\n"); ttable_del(tt); return CMD_SUCCESS; } #endif /* HAVE_CONFIG_ROLLBACKS */ DEFPY (show_config_transaction, show_config_transaction_cmd, "show configuration transaction\ [\ (1-4294967295)$transaction_id\ [ [translate WORD$translator_family]]\ [<\ with-defaults$with_defaults\ |changes$changes\ >]\ ]", SHOW_STR "Configuration information\n" "Configuration transaction\n" "Transaction ID\n" "Change output format to JSON\n" "Change output format to XML\n" "Translate output\n" "YANG module translator\n" "Show default values\n" "Show changes compared to the previous transaction\n") { #ifdef HAVE_CONFIG_ROLLBACKS if (transaction_id) { struct nb_config *config; enum nb_cfg_format format; struct yang_translator *translator = NULL; if (json) format = NB_CFG_FMT_JSON; else if (xml) format = NB_CFG_FMT_XML; else format = NB_CFG_FMT_CMDS; if (translator_family) { translator = yang_translator_find(translator_family); if (!translator) { vty_out(vty, "%% Module translator \"%s\" not found\n", translator_family); return CMD_WARNING; } } config = nb_db_transaction_load(transaction_id); if (!config) { vty_out(vty, "%% Transaction %u does not exist.\n\n", (unsigned int)transaction_id); return CMD_WARNING; } if (changes) { struct nb_config *prev_config; int ret; /* NOTE: this can be NULL. */ prev_config = nb_db_transaction_load(transaction_id - 1); ret = nb_cli_show_config_compare( vty, prev_config, config, format, translator); if (prev_config) nb_config_free(prev_config); nb_config_free(config); return ret; } nb_cli_show_config(vty, config, format, translator, !!with_defaults); nb_config_free(config); return CMD_SUCCESS; } return nb_cli_show_transactions(vty); #else vty_out(vty, "%% FRR was compiled without --enable-config-rollbacks.\n\n"); return CMD_WARNING; #endif /* HAVE_CONFIG_ROLLBACKS */ } static int nb_cli_oper_data_cb(const struct lys_node *snode, struct yang_translator *translator, struct yang_data *data, void *arg) { struct lyd_node *dnode = arg; struct ly_ctx *ly_ctx; if (translator) { int ret; ret = yang_translate_xpath(translator, YANG_TRANSLATE_FROM_NATIVE, data->xpath, sizeof(data->xpath)); switch (ret) { case YANG_TRANSLATE_SUCCESS: break; case YANG_TRANSLATE_NOTFOUND: goto exit; case YANG_TRANSLATE_FAILURE: goto error; } ly_ctx = translator->ly_ctx; } else ly_ctx = ly_native_ctx; ly_errno = 0; dnode = lyd_new_path(dnode, ly_ctx, data->xpath, (void *)data->value, 0, LYD_PATH_OPT_UPDATE); if (!dnode && ly_errno) { flog_warn(EC_LIB_LIBYANG, "%s: lyd_new_path() failed", __func__); goto error; } exit: yang_data_free(data); return NB_OK; error: yang_data_free(data); return NB_ERR; } DEFPY (show_yang_operational_data, show_yang_operational_data_cmd, "show yang operational-data XPATH$xpath\ [{\ format \ |translate WORD$translator_family\ }]", SHOW_STR "YANG information\n" "Show YANG operational data\n" "XPath expression specifying the YANG data path\n" "Set the output format\n" "JavaScript Object Notation\n" "Extensible Markup Language\n" "Translate operational data\n" "YANG module translator\n") { LYD_FORMAT format; struct yang_translator *translator = NULL; struct ly_ctx *ly_ctx; struct lyd_node *dnode; char *strp; if (xml) format = LYD_XML; else format = LYD_JSON; if (translator_family) { translator = yang_translator_find(translator_family); if (!translator) { vty_out(vty, "%% Module translator \"%s\" not found\n", translator_family); return CMD_WARNING; } ly_ctx = translator->ly_ctx; } else ly_ctx = ly_native_ctx; /* Obtain data. */ dnode = yang_dnode_new(ly_ctx, false); if (nb_oper_data_iterate(xpath, translator, 0, nb_cli_oper_data_cb, dnode) != NB_OK) { vty_out(vty, "%% Failed to fetch operational data.\n"); yang_dnode_free(dnode); return CMD_WARNING; } lyd_validate(&dnode, LYD_OPT_GET, ly_ctx); /* Display the data. */ if (lyd_print_mem(&strp, dnode, format, LYP_FORMAT | LYP_WITHSIBLINGS | LYP_WD_ALL) != 0 || !strp) { vty_out(vty, "%% Failed to display operational data.\n"); yang_dnode_free(dnode); return CMD_WARNING; } vty_out(vty, "%s", strp); free(strp); yang_dnode_free(dnode); return CMD_SUCCESS; } DEFPY (show_yang_module, show_yang_module_cmd, "show yang module [module-translator WORD$translator_family]", SHOW_STR "YANG information\n" "Show loaded modules\n" "YANG module translator\n" "YANG module translator\n") { struct ly_ctx *ly_ctx; struct yang_translator *translator = NULL; const struct lys_module *module; struct ttable *tt; uint32_t idx = 0; if (translator_family) { translator = yang_translator_find(translator_family); if (!translator) { vty_out(vty, "%% Module translator \"%s\" not found\n", translator_family); return CMD_WARNING; } ly_ctx = translator->ly_ctx; } else ly_ctx = ly_native_ctx; /* Prepare table. */ tt = ttable_new(&ttable_styles[TTSTYLE_BLANK]); ttable_add_row(tt, "Module|Version|Revision|Flags|Namespace"); tt->style.cell.rpad = 2; tt->style.corner = '+'; ttable_restyle(tt); ttable_rowseps(tt, 0, BOTTOM, true, '-'); while ((module = ly_ctx_get_module_iter(ly_ctx, &idx))) { char flags[8]; snprintf(flags, sizeof(flags), "%c%c", module->implemented ? 'I' : ' ', (module->deviated == 1) ? 'D' : ' '); ttable_add_row(tt, "%s|%s|%s|%s|%s", module->name, (module->version == 2) ? "1.1" : "1.0", (module->rev_size > 0) ? module->rev[0].date : "-", flags, module->ns); } /* Dump the generated table. */ if (tt->nrows > 1) { char *table; vty_out(vty, " Flags: I - Implemented, D - Deviated\n\n"); table = ttable_dump(tt, "\n"); vty_out(vty, "%s\n", table); XFREE(MTYPE_TMP, table); } else vty_out(vty, "No YANG modules to display.\n\n"); ttable_del(tt); return CMD_SUCCESS; } DEFPY (show_yang_module_detail, show_yang_module_detail_cmd, "show yang module\ [module-translator WORD$translator_family]\ WORD$module_name ", SHOW_STR "YANG information\n" "Show loaded modules\n" "YANG module translator\n" "YANG module translator\n" "Module name\n" "Display summary information about the module\n" "Display module in the tree (RFC 8340) format\n" "Display module in the YANG format\n" "Display module in the YIN format\n") { struct ly_ctx *ly_ctx; struct yang_translator *translator = NULL; const struct lys_module *module; LYS_OUTFORMAT format; char *strp; if (translator_family) { translator = yang_translator_find(translator_family); if (!translator) { vty_out(vty, "%% Module translator \"%s\" not found\n", translator_family); return CMD_WARNING; } ly_ctx = translator->ly_ctx; } else ly_ctx = ly_native_ctx; module = ly_ctx_get_module(ly_ctx, module_name, NULL, 0); if (!module) { vty_out(vty, "%% Module \"%s\" not found\n", module_name); return CMD_WARNING; } if (yang) format = LYS_OUT_YANG; else if (yin) format = LYS_OUT_YIN; else if (tree) format = LYS_OUT_TREE; else format = LYS_OUT_INFO; if (lys_print_mem(&strp, module, format, NULL, 0, 0) == 0) { vty_out(vty, "%s\n", strp); free(strp); } else { /* Unexpected. */ vty_out(vty, "%% Error generating module information\n"); return CMD_WARNING; } return CMD_SUCCESS; } DEFPY (show_yang_module_translator, show_yang_module_translator_cmd, "show yang module-translator", SHOW_STR "YANG information\n" "Show loaded YANG module translators\n") { struct yang_translator *translator; struct ttable *tt; /* Prepare table. */ tt = ttable_new(&ttable_styles[TTSTYLE_BLANK]); ttable_add_row(tt, "Family|Module|Deviations|Coverage (%%)"); tt->style.cell.rpad = 2; tt->style.corner = '+'; ttable_restyle(tt); ttable_rowseps(tt, 0, BOTTOM, true, '-'); RB_FOREACH (translator, yang_translators, &yang_translators) { struct yang_tmodule *tmodule; struct listnode *ln; for (ALL_LIST_ELEMENTS_RO(translator->modules, ln, tmodule)) { ttable_add_row(tt, "%s|%s|%s|%.2f", translator->family, tmodule->module->name, tmodule->deviations->name, tmodule->coverage); } } /* Dump the generated table. */ if (tt->nrows > 1) { char *table; table = ttable_dump(tt, "\n"); vty_out(vty, "%s\n", table); XFREE(MTYPE_TMP, table); } else vty_out(vty, "No YANG module translators to display.\n\n"); ttable_del(tt); return CMD_SUCCESS; } #ifdef HAVE_CONFIG_ROLLBACKS static int nb_cli_rollback_configuration(struct vty *vty, uint32_t transaction_id) { struct nb_config *candidate; char comment[80]; int ret; candidate = nb_db_transaction_load(transaction_id); if (!candidate) { vty_out(vty, "%% Transaction %u does not exist.\n\n", transaction_id); return CMD_WARNING; } snprintf(comment, sizeof(comment), "Rollback to transaction %u", transaction_id); ret = nb_candidate_commit(candidate, NB_CLIENT_CLI, vty, true, comment, NULL); nb_config_free(candidate); switch (ret) { case NB_OK: vty_out(vty, "%% Configuration was successfully rolled back.\n\n"); return CMD_SUCCESS; case NB_ERR_NO_CHANGES: vty_out(vty, "%% Aborting - no configuration changes detected.\n\n"); return CMD_WARNING; default: vty_out(vty, "%% Rollback failed.\n\n"); vty_out(vty, "Please check the logs for more details.\n"); return CMD_WARNING; } } #endif /* HAVE_CONFIG_ROLLBACKS */ DEFPY (rollback_config, rollback_config_cmd, "rollback configuration (1-4294967295)$transaction_id", "Rollback to a previous state\n" "Running configuration\n" "Transaction ID\n") { #ifdef HAVE_CONFIG_ROLLBACKS return nb_cli_rollback_configuration(vty, transaction_id); #else vty_out(vty, "%% FRR was compiled without --enable-config-rollbacks.\n\n"); return CMD_SUCCESS; #endif /* HAVE_CONFIG_ROLLBACKS */ } /* Debug CLI commands. */ static struct debug *nb_debugs[] = { &nb_dbg_cbs_config, &nb_dbg_cbs_state, &nb_dbg_cbs_rpc, &nb_dbg_notif, &nb_dbg_events, }; static const char *const nb_debugs_conflines[] = { "debug northbound callbacks configuration", "debug northbound callbacks state", "debug northbound callbacks rpc", "debug northbound notifications", "debug northbound events", }; DEFINE_HOOK(nb_client_debug_set_all, (uint32_t flags, bool set), (flags, set)); static void nb_debug_set_all(uint32_t flags, bool set) { for (unsigned int i = 0; i < array_size(nb_debugs); i++) { DEBUG_FLAGS_SET(nb_debugs[i], flags, set); /* If all modes have been turned off, don't preserve options. */ if (!DEBUG_MODE_CHECK(nb_debugs[i], DEBUG_MODE_ALL)) DEBUG_CLEAR(nb_debugs[i]); } hook_call(nb_client_debug_set_all, flags, set); } DEFPY (debug_nb, debug_nb_cmd, "[no] debug northbound\ [<\ callbacks$cbs [{configuration$cbs_cfg|state$cbs_state|rpc$cbs_rpc}]\ |notifications$notifications\ |events$events\ >]", NO_STR DEBUG_STR "Northbound debugging\n" "Callbacks\n" "Configuration\n" "State\n" "RPC\n" "Notifications\n" "Events\n") { uint32_t mode = DEBUG_NODE2MODE(vty->node); if (cbs) { bool none = (!cbs_cfg && !cbs_state && !cbs_rpc); if (none || cbs_cfg) DEBUG_MODE_SET(&nb_dbg_cbs_config, mode, !no); if (none || cbs_state) DEBUG_MODE_SET(&nb_dbg_cbs_state, mode, !no); if (none || cbs_rpc) DEBUG_MODE_SET(&nb_dbg_cbs_rpc, mode, !no); } if (notifications) DEBUG_MODE_SET(&nb_dbg_notif, mode, !no); if (events) DEBUG_MODE_SET(&nb_dbg_events, mode, !no); /* no specific debug --> act on all of them */ if (strmatch(argv[argc - 1]->text, "northbound")) nb_debug_set_all(mode, !no); return CMD_SUCCESS; } DEFINE_HOOK(nb_client_debug_config_write, (struct vty *vty), (vty)); static int nb_debug_config_write(struct vty *vty) { for (unsigned int i = 0; i < array_size(nb_debugs); i++) if (DEBUG_MODE_CHECK(nb_debugs[i], DEBUG_MODE_CONF)) vty_out(vty, "%s\n", nb_debugs_conflines[i]); hook_call(nb_client_debug_config_write, vty); return 1; } static struct debug_callbacks nb_dbg_cbs = {.debug_set_all = nb_debug_set_all}; static struct cmd_node nb_debug_node = {NORTHBOUND_DEBUG_NODE, "", 1}; void nb_cli_install_default(int node) { install_element(node, &show_config_candidate_section_cmd); if (frr_get_cli_mode() != FRR_CLI_TRANSACTIONAL) return; install_element(node, &config_commit_cmd); install_element(node, &config_commit_comment_cmd); install_element(node, &config_commit_check_cmd); install_element(node, &config_update_cmd); install_element(node, &config_discard_cmd); install_element(node, &show_config_running_cmd); install_element(node, &show_config_candidate_cmd); install_element(node, &show_config_compare_cmd); install_element(node, &show_config_transaction_cmd); } /* YANG module autocomplete. */ static void yang_module_autocomplete(vector comps, struct cmd_token *token) { const struct lys_module *module; struct yang_translator *module_tr; uint32_t idx; idx = 0; while ((module = ly_ctx_get_module_iter(ly_native_ctx, &idx))) vector_set(comps, XSTRDUP(MTYPE_COMPLETION, module->name)); RB_FOREACH (module_tr, yang_translators, &yang_translators) { idx = 0; while ((module = ly_ctx_get_module_iter(module_tr->ly_ctx, &idx))) vector_set(comps, XSTRDUP(MTYPE_COMPLETION, module->name)); } } /* YANG module translator autocomplete. */ static void yang_translator_autocomplete(vector comps, struct cmd_token *token) { struct yang_translator *module_tr; RB_FOREACH (module_tr, yang_translators, &yang_translators) vector_set(comps, XSTRDUP(MTYPE_COMPLETION, module_tr->family)); } static const struct cmd_variable_handler yang_var_handlers[] = { {.varname = "module_name", .completions = yang_module_autocomplete}, {.varname = "translator_family", .completions = yang_translator_autocomplete}, {.completions = NULL}}; void nb_cli_init(struct thread_master *tm) { master = tm; /* Initialize the shared candidate configuration. */ vty_shared_candidate_config = nb_config_new(NULL); debug_init(&nb_dbg_cbs); install_node(&nb_debug_node, nb_debug_config_write); install_element(ENABLE_NODE, &debug_nb_cmd); install_element(CONFIG_NODE, &debug_nb_cmd); /* Install commands specific to the transaction-base mode. */ if (frr_get_cli_mode() == FRR_CLI_TRANSACTIONAL) { install_element(ENABLE_NODE, &config_exclusive_cmd); install_element(ENABLE_NODE, &config_private_cmd); install_element(ENABLE_NODE, &show_config_running_cmd); install_element(ENABLE_NODE, &show_config_compare_without_candidate_cmd); install_element(ENABLE_NODE, &show_config_transaction_cmd); install_element(ENABLE_NODE, &rollback_config_cmd); install_element(ENABLE_NODE, &clear_config_transactions_cmd); install_element(CONFIG_NODE, &config_load_cmd); install_element(CONFIG_NODE, &config_database_max_transactions_cmd); } /* Other commands. */ install_element(CONFIG_NODE, &yang_module_translator_load_cmd); install_element(CONFIG_NODE, &yang_module_translator_unload_cmd); install_element(ENABLE_NODE, &show_yang_operational_data_cmd); install_element(ENABLE_NODE, &show_yang_module_cmd); install_element(ENABLE_NODE, &show_yang_module_detail_cmd); install_element(ENABLE_NODE, &show_yang_module_translator_cmd); cmd_variable_handler_register(yang_var_handlers); } void nb_cli_terminate(void) { nb_config_free(vty_shared_candidate_config); } frr-7.2.1/lib/northbound_cli.h0000644000000000000000000000701313610377563013145 00000000000000/* * Copyright (C) 2018 NetDEF, Inc. * Renato Westphal * * This program is free software; you can redistribute it and/or modify it * under the terms of the 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; see the file COPYING; if not, write to the Free Software * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA */ #ifndef _FRR_NORTHBOUND_CLI_H_ #define _FRR_NORTHBOUND_CLI_H_ #include "northbound.h" #ifdef __cplusplus extern "C" { #endif /* Possible formats in which a configuration can be displayed. */ enum nb_cfg_format { NB_CFG_FMT_CMDS = 0, NB_CFG_FMT_JSON, NB_CFG_FMT_XML, }; extern struct nb_config *vty_shared_candidate_config; /* * Enqueue change to be applied in the candidate configuration. * * vty * The vty context. * * xpath * XPath (absolute or relative) of the configuration option being edited. * * operation * Operation to apply (either NB_OP_CREATE, NB_OP_MODIFY or NB_OP_DELETE). * * value * New value of the configuration option. Should be NULL for typeless YANG * data (e.g. presence-containers). For convenience, NULL can also be used * to restore a leaf to its default value. */ extern void nb_cli_enqueue_change(struct vty *vty, const char *xpath, enum nb_operation operation, const char *value); /* * Apply enqueued changes to the candidate configuration. * * vty * The vty context. * * xpath_base_fmt * Prepend the given XPath (absolute or relative) to all enqueued * configuration changes. This is an optional parameter. * * Returns: * CMD_SUCCESS on success, CMD_WARNING_CONFIG_FAILED otherwise. */ extern int nb_cli_apply_changes(struct vty *vty, const char *xpath_base_fmt, ...); /* * Execute a YANG RPC or Action. * * xpath * XPath of the YANG RPC or Action node. * * input * List of 'yang_data' structures containing the RPC input parameters. It * can be set to NULL when there are no input parameters. * * output * List of 'yang_data' structures used to retrieve the RPC output parameters. * It can be set to NULL when it's known that the given YANG RPC or Action * doesn't have any output parameters. * * Returns: * CMD_SUCCESS on success, CMD_WARNING otherwise. */ extern int nb_cli_rpc(const char *xpath, struct list *input, struct list *output); /* * Show CLI commands associated to the given YANG data node. * * vty * The vty terminal to dump the configuration to. * * dnode * libyang data node that should be shown in the form of CLI commands. * * show_defaults * Specify whether to display default configuration values or not. */ extern void nb_cli_show_dnode_cmds(struct vty *vty, struct lyd_node *dnode, bool show_defaults); /* Prototypes of internal functions. */ extern void nb_cli_confirmed_commit_clean(struct vty *vty); extern int nb_cli_confirmed_commit_rollback(struct vty *vty); extern void nb_cli_install_default(int node); extern void nb_cli_init(struct thread_master *tm); extern void nb_cli_terminate(void); #ifdef __cplusplus } #endif #endif /* _FRR_NORTHBOUND_CLI_H_ */ frr-7.2.1/lib/northbound_confd.c0000644000000000000000000011131413610377563013462 00000000000000/* * Copyright (C) 2018 NetDEF, Inc. * Renato Westphal * * This program is free software; you can redistribute it and/or modify it * under the terms of the 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; see the file COPYING; if not, write to the Free Software * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA */ #include #include "log.h" #include "lib_errors.h" #include "command.h" #include "debug.h" #include "libfrr.h" #include "version.h" #include "northbound.h" #include #include #include #include DEFINE_MTYPE_STATIC(LIB, CONFD, "ConfD module") static struct debug nb_dbg_client_confd = {0, "Northbound client: ConfD"}; static struct thread_master *master; static struct sockaddr confd_addr; static int cdb_sub_sock, dp_ctl_sock, dp_worker_sock; static struct thread *t_cdb_sub, *t_dp_ctl, *t_dp_worker; static struct confd_daemon_ctx *dctx; static struct confd_notification_ctx *live_ctx; static bool confd_connected; static struct list *confd_spoints; static struct nb_transaction *transaction; static void frr_confd_finish_cdb(void); static void frr_confd_finish_dp(void); static int frr_confd_finish(void); #define flog_err_confd(funcname) \ flog_err(EC_LIB_LIBCONFD, "%s: %s() failed: %s (%d): %s", __func__, \ (funcname), confd_strerror(confd_errno), confd_errno, \ confd_lasterr()) /* ------------ Utils ------------ */ /* Get XPath string from ConfD hashed keypath. */ static void frr_confd_get_xpath(const confd_hkeypath_t *kp, char *xpath, size_t len) { char *p; confd_xpath_pp_kpath(xpath, len, 0, kp); /* * Replace double quotes by single quotes (the format accepted by the * northbound API). */ p = xpath; while ((p = strchr(p, '"')) != NULL) *p++ = '\''; } /* Convert ConfD binary value to a string. */ static int frr_confd_val2str(const char *xpath, const confd_value_t *value, char *string, size_t string_size) { struct confd_cs_node *csp; csp = confd_cs_node_cd(NULL, xpath); if (!csp) { flog_err_confd("confd_cs_node_cd"); return -1; } if (confd_val2str(csp->info.type, value, string, string_size) == CONFD_ERR) { flog_err_confd("confd_val2str"); return -1; } return 0; } /* Obtain list entry from ConfD hashed keypath. */ static int frr_confd_hkeypath_get_list_entry(const confd_hkeypath_t *kp, struct nb_node *nb_node, const void **list_entry) { struct nb_node *nb_node_list; int parent_lists = 0; int curr_list = 0; *list_entry = NULL; /* * Count the number of YANG lists in the path, disconsidering the * last element. */ nb_node_list = nb_node; while (nb_node_list->parent_list) { nb_node_list = nb_node_list->parent_list; parent_lists++; } if (nb_node->snode->nodetype != LYS_LIST && parent_lists == 0) return 0; /* Start from the beginning and move down the tree. */ for (int i = kp->len; i >= 0; i--) { struct yang_list_keys keys; /* Not a YANG list. */ if (kp->v[i][0].type != C_BUF) continue; /* Obtain list keys. */ memset(&keys, 0, sizeof(keys)); for (int j = 0; kp->v[i][j].type != C_NOEXISTS; j++) { strlcpy(keys.key[keys.num], (char *)kp->v[i][j].val.buf.ptr, sizeof(keys.key[keys.num])); keys.num++; } /* Obtain northbound node associated to the YANG list. */ nb_node_list = nb_node; for (int j = curr_list; j < parent_lists; j++) nb_node_list = nb_node_list->parent_list; /* Obtain list entry. */ if (!CHECK_FLAG(nb_node_list->flags, F_NB_NODE_KEYLESS_LIST)) { *list_entry = nb_callback_lookup_entry( nb_node, *list_entry, &keys); if (*list_entry == NULL) return -1; } else { unsigned long ptr_ulong; /* Retrieve list entry from pseudo-key (string). */ if (sscanf(keys.key[0], "%lu", &ptr_ulong) != 1) return -1; *list_entry = (const void *)ptr_ulong; } curr_list++; } return 0; } /* Fill the current date and time into a confd_datetime structure. */ static void getdatetime(struct confd_datetime *datetime) { struct tm tm; struct timeval tv; gettimeofday(&tv, NULL); gmtime_r(&tv.tv_sec, &tm); memset(datetime, 0, sizeof(*datetime)); datetime->year = 1900 + tm.tm_year; datetime->month = tm.tm_mon + 1; datetime->day = tm.tm_mday; datetime->sec = tm.tm_sec; datetime->micro = tv.tv_usec; datetime->timezone = 0; datetime->timezone_minutes = 0; datetime->hour = tm.tm_hour; datetime->min = tm.tm_min; } /* ------------ CDB code ------------ */ struct cdb_iter_args { struct nb_config *candidate; bool error; }; static enum cdb_iter_ret frr_confd_cdb_diff_iter(confd_hkeypath_t *kp, enum cdb_iter_op cdb_op, confd_value_t *oldv, confd_value_t *newv, void *args) { char xpath[XPATH_MAXLEN]; struct nb_node *nb_node; enum nb_operation nb_op; struct cdb_iter_args *iter_args = args; char value_str[YANG_VALUE_MAXLEN]; struct yang_data *data; char *sb1, *sb2; int ret; frr_confd_get_xpath(kp, xpath, sizeof(xpath)); /* * HACK: obtain value of leaf-list elements from the XPath due to * a bug in the ConfD API. */ value_str[0] = '\0'; sb1 = strrchr(xpath, '['); sb2 = strrchr(xpath, ']'); if (sb1 && sb2 && !strchr(sb1, '=')) { *sb2 = '\0'; strlcpy(value_str, sb1 + 1, sizeof(value_str)); *sb1 = '\0'; } nb_node = nb_node_find(xpath); if (!nb_node) { flog_warn(EC_LIB_YANG_UNKNOWN_DATA_PATH, "%s: unknown data path: %s", __func__, xpath); iter_args->error = true; return ITER_STOP; } /* Map operation values. */ switch (cdb_op) { case MOP_CREATED: nb_op = NB_OP_CREATE; break; case MOP_DELETED: nb_op = NB_OP_DESTROY; break; case MOP_VALUE_SET: if (nb_operation_is_valid(NB_OP_MODIFY, nb_node->snode)) nb_op = NB_OP_MODIFY; else /* Ignore list keys modifications. */ return ITER_RECURSE; break; case MOP_MOVED_AFTER: nb_op = NB_OP_MOVE; break; case MOP_MODIFIED: /* We're not interested on this. */ return ITER_RECURSE; default: flog_err(EC_LIB_DEVELOPMENT, "%s: unexpected operation %u [xpath %s]", __func__, cdb_op, xpath); iter_args->error = true; return ITER_STOP; } /* Convert ConfD value to a string. */ if (nb_node->snode->nodetype != LYS_LEAFLIST && newv && frr_confd_val2str(nb_node->xpath, newv, value_str, sizeof(value_str)) != 0) { flog_err(EC_LIB_CONFD_DATA_CONVERT, "%s: failed to convert ConfD value to a string", __func__); iter_args->error = true; return ITER_STOP; } /* Edit the candidate configuration. */ data = yang_data_new(xpath, value_str); ret = nb_candidate_edit(iter_args->candidate, nb_node, nb_op, xpath, NULL, data); yang_data_free(data); if (ret != NB_OK) { flog_warn( EC_LIB_NB_CANDIDATE_EDIT_ERROR, "%s: failed to edit candidate configuration: operation [%s] xpath [%s]", __func__, nb_operation_name(nb_op), xpath); iter_args->error = true; return ITER_STOP; } return ITER_RECURSE; } static int frr_confd_cdb_read_cb_prepare(int fd, int *subp, int reslen) { struct nb_config *candidate; struct cdb_iter_args iter_args; int ret; pthread_rwlock_rdlock(&running_config->lock); { candidate = nb_config_dup(running_config); } pthread_rwlock_unlock(&running_config->lock); /* Iterate over all configuration changes. */ iter_args.candidate = candidate; iter_args.error = false; for (int i = 0; i < reslen; i++) { if (cdb_diff_iterate(fd, subp[i], frr_confd_cdb_diff_iter, ITER_WANT_PREV, &iter_args) != CONFD_OK) { flog_err_confd("cdb_diff_iterate"); } } free(subp); if (iter_args.error) { nb_config_free(candidate); if (cdb_sub_abort_trans( cdb_sub_sock, CONFD_ERRCODE_APPLICATION_INTERNAL, 0, 0, "Couldn't apply configuration changes") != CONFD_OK) { flog_err_confd("cdb_sub_abort_trans"); return -1; } return 0; } /* * Validate the configuration changes and allocate all resources * required to apply them. */ transaction = NULL; ret = nb_candidate_commit_prepare(candidate, NB_CLIENT_CONFD, NULL, NULL, &transaction); if (ret != NB_OK && ret != NB_ERR_NO_CHANGES) { enum confd_errcode errcode; const char *errmsg; switch (ret) { case NB_ERR_LOCKED: errcode = CONFD_ERRCODE_IN_USE; errmsg = "Configuration is locked by another process"; break; case NB_ERR_RESOURCE: errcode = CONFD_ERRCODE_RESOURCE_DENIED; errmsg = "Failed do allocate resources"; break; default: errcode = CONFD_ERRCODE_INTERNAL; errmsg = "Internal error"; break; } /* Reject the configuration changes. */ if (cdb_sub_abort_trans(cdb_sub_sock, errcode, 0, 0, "%s", errmsg) != CONFD_OK) { flog_err_confd("cdb_sub_abort_trans"); return -1; } } else { /* Acknowledge the notification. */ if (cdb_sync_subscription_socket(fd, CDB_DONE_PRIORITY) != CONFD_OK) { flog_err_confd("cdb_sync_subscription_socket"); return -1; } /* No configuration changes. */ if (!transaction) nb_config_free(candidate); } return 0; } static int frr_confd_cdb_read_cb_commit(int fd, int *subp, int reslen) { /* * No need to process the configuration changes again as we're already * keeping track of them in the "transaction" variable. */ free(subp); /* Apply the transaction. */ if (transaction) { struct nb_config *candidate = transaction->config; nb_candidate_commit_apply(transaction, true, NULL); nb_config_free(candidate); } /* Acknowledge the notification. */ if (cdb_sync_subscription_socket(fd, CDB_DONE_PRIORITY) != CONFD_OK) { flog_err_confd("cdb_sync_subscription_socket"); return -1; } return 0; } static int frr_confd_cdb_read_cb_abort(int fd, int *subp, int reslen) { /* * No need to process the configuration changes again as we're already * keeping track of them in the "transaction" variable. */ free(subp); /* Abort the transaction. */ if (transaction) { struct nb_config *candidate = transaction->config; nb_candidate_commit_abort(transaction); nb_config_free(candidate); } /* Acknowledge the notification. */ if (cdb_sync_subscription_socket(fd, CDB_DONE_PRIORITY) != CONFD_OK) { flog_err_confd("cdb_sync_subscription_socket"); return -1; } return 0; } static int frr_confd_cdb_read_cb(struct thread *thread) { int fd = THREAD_FD(thread); enum cdb_sub_notification cdb_ev; int flags; int *subp = NULL; int reslen = 0; thread = NULL; thread_add_read(master, frr_confd_cdb_read_cb, NULL, fd, &thread); if (cdb_read_subscription_socket2(fd, &cdb_ev, &flags, &subp, &reslen) != CONFD_OK) { flog_err_confd("cdb_read_subscription_socket2"); return -1; } switch (cdb_ev) { case CDB_SUB_PREPARE: return frr_confd_cdb_read_cb_prepare(fd, subp, reslen); case CDB_SUB_COMMIT: return frr_confd_cdb_read_cb_commit(fd, subp, reslen); case CDB_SUB_ABORT: return frr_confd_cdb_read_cb_abort(fd, subp, reslen); default: flog_err_confd("unknown CDB event"); return -1; } } /* Trigger CDB subscriptions to read the startup configuration. */ static void *thread_cdb_trigger_subscriptions(void *data) { int sock; int *sub_points = NULL, len = 0; struct listnode *node; int *spoint; int i = 0; /* Create CDB data socket. */ sock = socket(PF_INET, SOCK_STREAM, 0); if (sock < 0) { flog_err(EC_LIB_SOCKET, "%s: failed to create socket: %s", __func__, safe_strerror(errno)); return NULL; } if (cdb_connect(sock, CDB_DATA_SOCKET, &confd_addr, sizeof(struct sockaddr_in)) != CONFD_OK) { flog_err_confd("cdb_connect"); return NULL; } /* * Fill array containing the subscription point of all loaded YANG * modules. */ len = listcount(confd_spoints); sub_points = XCALLOC(MTYPE_CONFD, len * sizeof(int)); for (ALL_LIST_ELEMENTS_RO(confd_spoints, node, spoint)) sub_points[i++] = *spoint; if (cdb_trigger_subscriptions(sock, sub_points, len) != CONFD_OK) { flog_err_confd("cdb_trigger_subscriptions"); return NULL; } /* Cleanup and exit thread. */ XFREE(MTYPE_CONFD, sub_points); cdb_close(sock); return NULL; } static int frr_confd_init_cdb(void) { struct yang_module *module; pthread_t cdb_trigger_thread; /* Create CDB subscription socket. */ cdb_sub_sock = socket(PF_INET, SOCK_STREAM, 0); if (cdb_sub_sock < 0) { flog_err(EC_LIB_SOCKET, "%s: failed to create socket: %s", __func__, safe_strerror(errno)); return -1; } if (cdb_connect(cdb_sub_sock, CDB_SUBSCRIPTION_SOCKET, &confd_addr, sizeof(struct sockaddr_in)) != CONFD_OK) { flog_err_confd("cdb_connect"); goto error; } /* Subscribe to all loaded YANG data modules. */ confd_spoints = list_new(); RB_FOREACH (module, yang_modules, &yang_modules) { struct lys_node *snode; module->confd_hash = confd_str2hash(module->info->ns); if (module->confd_hash == 0) { flog_err( EC_LIB_LIBCONFD, "%s: failed to find hash value for namespace %s", __func__, module->info->ns); goto error; } /* * The CDB API doesn't provide a mechanism to subscribe to an * entire YANG module. So we have to find the top level * nodes ourselves and subscribe to their paths. */ LY_TREE_FOR (module->info->data, snode) { struct nb_node *nb_node; int *spoint; int ret; switch (snode->nodetype) { case LYS_CONTAINER: case LYS_LEAF: case LYS_LEAFLIST: case LYS_LIST: break; default: continue; } if (CHECK_FLAG(snode->flags, LYS_CONFIG_R)) continue; nb_node = snode->priv; DEBUGD(&nb_dbg_client_confd, "%s: subscribing to '%s'", __func__, nb_node->xpath); spoint = XMALLOC(MTYPE_CONFD, sizeof(*spoint)); ret = cdb_subscribe2( cdb_sub_sock, CDB_SUB_RUNNING_TWOPHASE, CDB_SUB_WANT_ABORT_ON_ABORT, 3, spoint, module->confd_hash, nb_node->xpath); if (ret != CONFD_OK) { flog_err_confd("cdb_subscribe2"); XFREE(MTYPE_CONFD, spoint); } listnode_add(confd_spoints, spoint); } } if (cdb_subscribe_done(cdb_sub_sock) != CONFD_OK) { flog_err_confd("cdb_subscribe_done"); goto error; } /* Create short lived pthread to trigger the CDB subscriptions. */ if (pthread_create(&cdb_trigger_thread, NULL, thread_cdb_trigger_subscriptions, NULL)) { flog_err(EC_LIB_SYSTEM_CALL, "%s: error creating pthread: %s", __func__, safe_strerror(errno)); goto error; } pthread_detach(cdb_trigger_thread); thread_add_read(master, frr_confd_cdb_read_cb, NULL, cdb_sub_sock, &t_cdb_sub); return 0; error: frr_confd_finish_cdb(); return -1; } static void frr_confd_finish_cdb(void) { if (cdb_sub_sock > 0) { THREAD_OFF(t_cdb_sub); cdb_close(cdb_sub_sock); } } /* ------------ DP code ------------ */ static int frr_confd_transaction_init(struct confd_trans_ctx *tctx) { confd_trans_set_fd(tctx, dp_worker_sock); return CONFD_OK; } #define CONFD_MAX_CHILD_NODES 32 static int frr_confd_data_get_elem(struct confd_trans_ctx *tctx, confd_hkeypath_t *kp) { struct nb_node *nb_node; char xpath[BUFSIZ]; struct yang_data *data; confd_value_t v; const void *list_entry = NULL; frr_confd_get_xpath(kp, xpath, sizeof(xpath)); nb_node = nb_node_find(xpath); if (!nb_node) { flog_warn(EC_LIB_YANG_UNKNOWN_DATA_PATH, "%s: unknown data path: %s", __func__, xpath); confd_data_reply_not_found(tctx); return CONFD_OK; } if (frr_confd_hkeypath_get_list_entry(kp, nb_node, &list_entry) != 0) { confd_data_reply_not_found(tctx); return CONFD_OK; } data = nb_callback_get_elem(nb_node, xpath, list_entry); if (data) { if (data->value) { CONFD_SET_STR(&v, data->value); confd_data_reply_value(tctx, &v); } else confd_data_reply_found(tctx); yang_data_free(data); } else confd_data_reply_not_found(tctx); return CONFD_OK; } static int frr_confd_data_get_next(struct confd_trans_ctx *tctx, confd_hkeypath_t *kp, long next) { struct nb_node *nb_node; char xpath[BUFSIZ]; struct yang_data *data; const void *parent_list_entry, *nb_next; confd_value_t v[LIST_MAXKEYS]; frr_confd_get_xpath(kp, xpath, sizeof(xpath)); nb_node = nb_node_find(xpath); if (!nb_node) { flog_warn(EC_LIB_YANG_UNKNOWN_DATA_PATH, "%s: unknown data path: %s", __func__, xpath); confd_data_reply_next_key(tctx, NULL, -1, -1); return CONFD_OK; } if (frr_confd_hkeypath_get_list_entry(kp, nb_node, &parent_list_entry) != 0) { /* List entry doesn't exist anymore. */ confd_data_reply_next_key(tctx, NULL, -1, -1); return CONFD_OK; } nb_next = nb_callback_get_next(nb_node, parent_list_entry, (next == -1) ? NULL : (void *)next); if (!nb_next) { /* End of the list or leaf-list. */ confd_data_reply_next_key(tctx, NULL, -1, -1); return CONFD_OK; } switch (nb_node->snode->nodetype) { case LYS_LIST: if (!CHECK_FLAG(nb_node->flags, F_NB_NODE_KEYLESS_LIST)) { struct yang_list_keys keys; memset(&keys, 0, sizeof(keys)); if (nb_callback_get_keys(nb_node, nb_next, &keys) != NB_OK) { flog_warn(EC_LIB_NB_CB_STATE, "%s: failed to get list keys", __func__); confd_data_reply_next_key(tctx, NULL, -1, -1); return CONFD_OK; } /* Feed keys to ConfD. */ for (size_t i = 0; i < keys.num; i++) CONFD_SET_STR(&v[i], keys.key[i]); confd_data_reply_next_key(tctx, v, keys.num, (long)nb_next); } else { char pointer_str[16]; /* * ConfD 6.6 user guide, chapter 6.11 (Operational data * lists without keys): * "To support this without having completely separate * APIs, we use a "pseudo" key in the ConfD APIs for * this type of list. This key is not part of the data * model, and completely hidden in the northbound agent * interfaces, but is used with e.g. the get_next() and * get_elem() callbacks as if it were a normal key. This * "pseudo" key is always a single signed 64-bit * integer, i.e. the confd_value_t type is C_INT64. The * values can be chosen arbitrarily by the application, * as long as a key value returned by get_next() can be * used to get the data for the corresponding list entry * with get_elem() or get_object() as usual. It could * e.g. be an index into an array that holds the data, * or even a memory address in integer form". * * Since we're using the CONFD_DAEMON_FLAG_STRINGSONLY * option, we must convert our pseudo-key (a void * pointer) to a string before sending it to confd. */ snprintf(pointer_str, sizeof(pointer_str), "%lu", (unsigned long)nb_next); CONFD_SET_STR(&v[0], pointer_str); confd_data_reply_next_key(tctx, v, 1, (long)nb_next); } break; case LYS_LEAFLIST: data = nb_callback_get_elem(nb_node, xpath, nb_next); if (data) { if (data->value) { CONFD_SET_STR(&v[0], data->value); confd_data_reply_next_key(tctx, v, 1, (long)nb_next); } yang_data_free(data); } else confd_data_reply_next_key(tctx, NULL, -1, -1); break; default: break; } return CONFD_OK; } /* * Optional callback - implemented for performance reasons. */ static int frr_confd_data_get_object(struct confd_trans_ctx *tctx, confd_hkeypath_t *kp) { struct nb_node *nb_node; const struct lys_node *child; char xpath[BUFSIZ]; char xpath_child[XPATH_MAXLEN]; struct list *elements; struct yang_data *data; const void *list_entry; confd_value_t values[CONFD_MAX_CHILD_NODES]; size_t nvalues = 0; frr_confd_get_xpath(kp, xpath, sizeof(xpath)); nb_node = nb_node_find(xpath); if (!nb_node) { flog_warn(EC_LIB_YANG_UNKNOWN_DATA_PATH, "%s: unknown data path: %s", __func__, xpath); confd_data_reply_not_found(tctx); return CONFD_ERR; } if (frr_confd_hkeypath_get_list_entry(kp, nb_node, &list_entry) != 0) { confd_data_reply_not_found(tctx); return CONFD_OK; } elements = yang_data_list_new(); /* Loop through list child nodes. */ LY_TREE_FOR (nb_node->snode->child, child) { struct nb_node *nb_node_child = child->priv; confd_value_t *v; if (nvalues > CONFD_MAX_CHILD_NODES) break; v = &values[nvalues++]; /* Non-presence containers, lists and leaf-lists. */ if (!nb_node_child->cbs.get_elem) { CONFD_SET_NOEXISTS(v); continue; } snprintf(xpath_child, sizeof(xpath_child), "%s/%s", xpath, child->name); data = nb_callback_get_elem(nb_node_child, xpath_child, list_entry); if (data) { if (data->value) CONFD_SET_STR(v, data->value); else { /* Presence containers and empty leafs. */ CONFD_SET_XMLTAG( v, nb_node_child->confd_hash, confd_str2hash(nb_node_child->snode ->module->ns)); } listnode_add(elements, data); } else CONFD_SET_NOEXISTS(v); } confd_data_reply_value_array(tctx, values, nvalues); /* Release memory. */ list_delete(&elements); return CONFD_OK; } /* * Optional callback - implemented for performance reasons. */ static int frr_confd_data_get_next_object(struct confd_trans_ctx *tctx, confd_hkeypath_t *kp, long next) { char xpath[BUFSIZ]; struct nb_node *nb_node; struct list *elements; const void *parent_list_entry; const void *nb_next; #define CONFD_OBJECTS_PER_TIME 100 struct confd_next_object objects[CONFD_OBJECTS_PER_TIME + 1]; char pseudo_keys[CONFD_OBJECTS_PER_TIME][16]; int nobjects = 0; frr_confd_get_xpath(kp, xpath, sizeof(xpath)); nb_node = nb_node_find(xpath); if (!nb_node) { flog_warn(EC_LIB_YANG_UNKNOWN_DATA_PATH, "%s: unknown data path: %s", __func__, xpath); confd_data_reply_next_object_array(tctx, NULL, 0, 0); return CONFD_OK; } if (frr_confd_hkeypath_get_list_entry(kp, nb_node, &parent_list_entry) != 0) { confd_data_reply_next_object_array(tctx, NULL, 0, 0); return CONFD_OK; } elements = yang_data_list_new(); nb_next = (next == -1) ? NULL : (void *)next; memset(objects, 0, sizeof(objects)); for (int j = 0; j < CONFD_OBJECTS_PER_TIME; j++) { struct confd_next_object *object; struct lys_node *child; struct yang_data *data; size_t nvalues = 0; object = &objects[j]; nb_next = nb_callback_get_next(nb_node, parent_list_entry, nb_next); if (!nb_next) /* End of the list. */ break; object->next = (long)nb_next; /* Leaf-lists require special handling. */ if (nb_node->snode->nodetype == LYS_LEAFLIST) { object->v = XMALLOC(MTYPE_CONFD, sizeof(confd_value_t)); data = nb_callback_get_elem(nb_node, xpath, nb_next); assert(data && data->value); CONFD_SET_STR(object->v, data->value); nvalues++; listnode_add(elements, data); goto next; } object->v = XMALLOC(MTYPE_CONFD, CONFD_MAX_CHILD_NODES * sizeof(confd_value_t)); /* * ConfD 6.6 user guide, chapter 6.11 (Operational data lists * without keys): * "In the response to the get_next_object() callback, the data * provider is expected to provide the key values along with the * other leafs in an array that is populated according to the * data model. This must be done also for this type of list, * even though the key isn't actually in the data model. The * "pseudo" key must always be the first element in the array". */ if (CHECK_FLAG(nb_node->flags, F_NB_NODE_KEYLESS_LIST)) { confd_value_t *v; snprintf(pseudo_keys[j], sizeof(pseudo_keys[j]), "%lu", (unsigned long)nb_next); v = &object->v[nvalues++]; CONFD_SET_STR(v, pseudo_keys[j]); } /* Loop through list child nodes. */ LY_TREE_FOR (nb_node->snode->child, child) { struct nb_node *nb_node_child = child->priv; char xpath_child[XPATH_MAXLEN]; confd_value_t *v; if (nvalues > CONFD_MAX_CHILD_NODES) break; v = &object->v[nvalues++]; /* Non-presence containers, lists and leaf-lists. */ if (!nb_node_child->cbs.get_elem) { CONFD_SET_NOEXISTS(v); continue; } snprintf(xpath_child, sizeof(xpath_child), "%s/%s", xpath, child->name); data = nb_callback_get_elem(nb_node_child, xpath_child, nb_next); if (data) { if (data->value) CONFD_SET_STR(v, data->value); else { /* * Presence containers and empty leafs. */ CONFD_SET_XMLTAG( v, nb_node_child->confd_hash, confd_str2hash( nb_node_child->snode ->module->ns)); } listnode_add(elements, data); } else CONFD_SET_NOEXISTS(v); } next: object->n = nvalues; nobjects++; } if (nobjects == 0) { confd_data_reply_next_object_array(tctx, NULL, 0, 0); list_delete(&elements); return CONFD_OK; } /* Detect end of the list. */ if (!nb_next) { nobjects++; objects[nobjects].v = NULL; } /* Reply to ConfD. */ confd_data_reply_next_object_arrays(tctx, objects, nobjects, 0); if (!nb_next) nobjects--; /* Release memory. */ list_delete(&elements); for (int j = 0; j < nobjects; j++) { struct confd_next_object *object; object = &objects[j]; XFREE(MTYPE_CONFD, object->v); } return CONFD_OK; } static int frr_confd_notification_send(const char *xpath, struct list *arguments) { struct nb_node *nb_node; struct yang_module *module; struct confd_datetime now; confd_tag_value_t *values; int nvalues; int i = 0; struct yang_data *data; struct listnode *node; int ret; nb_node = nb_node_find(xpath); if (!nb_node) { flog_warn(EC_LIB_YANG_UNKNOWN_DATA_PATH, "%s: unknown data path: %s", __func__, xpath); return -1; } module = yang_module_find(nb_node->snode->module->name); assert(module); nvalues = 2; if (arguments) nvalues += listcount(arguments); values = XMALLOC(MTYPE_CONFD, nvalues * sizeof(*values)); CONFD_SET_TAG_XMLBEGIN(&values[i++], nb_node->confd_hash, module->confd_hash); for (ALL_LIST_ELEMENTS_RO(arguments, node, data)) { struct nb_node *nb_node_arg; nb_node_arg = nb_node_find(data->xpath); if (!nb_node_arg) { flog_warn(EC_LIB_YANG_UNKNOWN_DATA_PATH, "%s: unknown data path: %s", __func__, data->xpath); XFREE(MTYPE_CONFD, values); return NB_ERR; } CONFD_SET_TAG_STR(&values[i++], nb_node_arg->confd_hash, data->value); } CONFD_SET_TAG_XMLEND(&values[i++], nb_node->confd_hash, module->confd_hash); getdatetime(&now); ret = confd_notification_send(live_ctx, &now, values, nvalues); /* Release memory. */ XFREE(MTYPE_CONFD, values); /* Map ConfD return code to northbound return code. */ switch (ret) { case CONFD_OK: return NB_OK; default: return NB_ERR; } } static int frr_confd_action_init(struct confd_user_info *uinfo) { confd_action_set_fd(uinfo, dp_worker_sock); return CONFD_OK; } static int frr_confd_action_execute(struct confd_user_info *uinfo, struct xml_tag *name, confd_hkeypath_t *kp, confd_tag_value_t *params, int nparams) { char xpath[BUFSIZ]; struct nb_node *nb_node; struct list *input; struct list *output; struct yang_data *data; confd_tag_value_t *reply; int ret = CONFD_OK; /* Getting the XPath is tricky. */ if (kp) { /* This is a YANG RPC. */ frr_confd_get_xpath(kp, xpath, sizeof(xpath)); strlcat(xpath, "/", sizeof(xpath)); strlcat(xpath, confd_hash2str(name->tag), sizeof(xpath)); } else { /* This is a YANG action. */ snprintf(xpath, sizeof(xpath), "/%s:%s", confd_ns2prefix(name->ns), confd_hash2str(name->tag)); } nb_node = nb_node_find(xpath); if (!nb_node) { flog_warn(EC_LIB_YANG_UNKNOWN_DATA_PATH, "%s: unknown data path: %s", __func__, xpath); return CONFD_ERR; } input = yang_data_list_new(); output = yang_data_list_new(); /* Process input nodes. */ for (int i = 0; i < nparams; i++) { char xpath_input[BUFSIZ]; char value_str[YANG_VALUE_MAXLEN]; snprintf(xpath_input, sizeof(xpath_input), "%s/%s", xpath, confd_hash2str(params[i].tag.tag)); if (frr_confd_val2str(xpath_input, ¶ms[i].v, value_str, sizeof(value_str)) != 0) { flog_err( EC_LIB_CONFD_DATA_CONVERT, "%s: failed to convert ConfD value to a string", __func__); ret = CONFD_ERR; goto exit; } data = yang_data_new(xpath_input, value_str); listnode_add(input, data); } /* Execute callback registered for this XPath. */ if (nb_callback_rpc(nb_node, xpath, input, output) != NB_OK) { flog_warn(EC_LIB_NB_CB_RPC, "%s: rpc callback failed: %s", __func__, xpath); ret = CONFD_ERR; goto exit; } /* Process output nodes. */ if (listcount(output) > 0) { struct listnode *node; int i = 0; reply = XMALLOC(MTYPE_CONFD, listcount(output) * sizeof(*reply)); for (ALL_LIST_ELEMENTS_RO(output, node, data)) { struct nb_node *nb_node_output; int hash; nb_node_output = nb_node_find(data->xpath); if (!nb_node_output) { flog_warn(EC_LIB_YANG_UNKNOWN_DATA_PATH, "%s: unknown data path: %s", __func__, data->xpath); goto exit; } hash = confd_str2hash(nb_node_output->snode->name); CONFD_SET_TAG_STR(&reply[i++], hash, data->value); } confd_action_reply_values(uinfo, reply, listcount(output)); XFREE(MTYPE_CONFD, reply); } exit: /* Release memory. */ list_delete(&input); list_delete(&output); return ret; } static int frr_confd_dp_read(struct thread *thread) { struct confd_daemon_ctx *dctx = THREAD_ARG(thread); int fd = THREAD_FD(thread); int ret; thread = NULL; thread_add_read(master, frr_confd_dp_read, dctx, fd, &thread); ret = confd_fd_ready(dctx, fd); if (ret == CONFD_EOF) { flog_err_confd("confd_fd_ready"); frr_confd_finish(); return -1; } else if (ret == CONFD_ERR && confd_errno != CONFD_ERR_EXTERNAL) { flog_err_confd("confd_fd_ready"); frr_confd_finish(); return -1; } return 0; } static int frr_confd_subscribe_state(const struct lys_node *snode, void *arg) { struct nb_node *nb_node = snode->priv; struct confd_data_cbs *data_cbs = arg; if (!CHECK_FLAG(snode->flags, LYS_CONFIG_R)) return YANG_ITER_CONTINUE; /* We only need to subscribe to the root of the state subtrees. */ if (snode->parent && CHECK_FLAG(snode->parent->flags, LYS_CONFIG_R)) return YANG_ITER_CONTINUE; DEBUGD(&nb_dbg_client_confd, "%s: providing data to '%s' (callpoint %s)", __func__, nb_node->xpath, snode->name); strlcpy(data_cbs->callpoint, snode->name, sizeof(data_cbs->callpoint)); if (confd_register_data_cb(dctx, data_cbs) != CONFD_OK) flog_err_confd("confd_register_data_cb"); return YANG_ITER_CONTINUE; } static int frr_confd_init_dp(const char *program_name) { struct confd_trans_cbs trans_cbs; struct confd_data_cbs data_cbs; struct confd_notification_stream_cbs ncbs; struct confd_action_cbs acbs; /* Initialize daemon context. */ dctx = confd_init_daemon(program_name); if (!dctx) { flog_err_confd("confd_init_daemon"); goto error; } /* * Inform we want to receive YANG values as raw strings, and that we * want to provide only strings in the reply functions, regardless of * the YANG type. */ confd_set_daemon_flags(dctx, CONFD_DAEMON_FLAG_STRINGSONLY); /* Establish a control socket. */ dp_ctl_sock = socket(PF_INET, SOCK_STREAM, 0); if (dp_ctl_sock < 0) { flog_err(EC_LIB_SOCKET, "%s: failed to create socket: %s", __func__, safe_strerror(errno)); goto error; } if (confd_connect(dctx, dp_ctl_sock, CONTROL_SOCKET, &confd_addr, sizeof(struct sockaddr_in)) != CONFD_OK) { flog_err_confd("confd_connect"); goto error; } /* * Establish a worker socket (only one since this plugin runs on a * single thread). */ dp_worker_sock = socket(PF_INET, SOCK_STREAM, 0); if (dp_worker_sock < 0) { flog_err(EC_LIB_SOCKET, "%s: failed to create socket: %s", __func__, safe_strerror(errno)); goto error; } if (confd_connect(dctx, dp_worker_sock, WORKER_SOCKET, &confd_addr, sizeof(struct sockaddr_in)) != CONFD_OK) { flog_err_confd("confd_connect"); goto error; } /* Register transaction callback functions. */ memset(&trans_cbs, 0, sizeof(trans_cbs)); trans_cbs.init = frr_confd_transaction_init; confd_register_trans_cb(dctx, &trans_cbs); /* Register our read/write callbacks. */ memset(&data_cbs, 0, sizeof(data_cbs)); data_cbs.get_elem = frr_confd_data_get_elem; data_cbs.exists_optional = frr_confd_data_get_elem; data_cbs.get_next = frr_confd_data_get_next; data_cbs.get_object = frr_confd_data_get_object; data_cbs.get_next_object = frr_confd_data_get_next_object; /* * Iterate over all loaded YANG modules and subscribe to the paths * referent to state data. */ yang_snodes_iterate_all(frr_confd_subscribe_state, 0, &data_cbs); /* Register notification stream. */ memset(&ncbs, 0, sizeof(ncbs)); ncbs.fd = dp_worker_sock; /* * RFC 5277 - Section 3.2.3: * A NETCONF server implementation supporting the notification * capability MUST support the "NETCONF" notification event * stream. This stream contains all NETCONF XML event notifications * supported by the NETCONF server. */ strlcpy(ncbs.streamname, "NETCONF", sizeof(ncbs.streamname)); if (confd_register_notification_stream(dctx, &ncbs, &live_ctx) != CONFD_OK) { flog_err_confd("confd_register_notification_stream"); goto error; } /* Register the action handler callback. */ memset(&acbs, 0, sizeof(acbs)); strlcpy(acbs.actionpoint, "actionpoint", sizeof(acbs.actionpoint)); acbs.init = frr_confd_action_init; acbs.action = frr_confd_action_execute; if (confd_register_action_cbs(dctx, &acbs) != CONFD_OK) { flog_err_confd("confd_register_action_cbs"); goto error; } /* Notify we registered all callbacks we wanted. */ if (confd_register_done(dctx) != CONFD_OK) { flog_err_confd("confd_register_done"); goto error; } thread_add_read(master, frr_confd_dp_read, dctx, dp_ctl_sock, &t_dp_ctl); thread_add_read(master, frr_confd_dp_read, dctx, dp_worker_sock, &t_dp_worker); return 0; error: frr_confd_finish_dp(); return -1; } static void frr_confd_finish_dp(void) { if (dp_worker_sock > 0) { THREAD_OFF(t_dp_worker); close(dp_worker_sock); } if (dp_ctl_sock > 0) { THREAD_OFF(t_dp_ctl); close(dp_ctl_sock); } if (dctx != NULL) confd_release_daemon(dctx); } /* ------------ CLI ------------ */ DEFUN (debug_nb_confd, debug_nb_confd_cmd, "[no] debug northbound client confd", NO_STR DEBUG_STR "Northbound debugging\n" "Client\n" "ConfD\n") { uint32_t mode = DEBUG_NODE2MODE(vty->node); bool no = strmatch(argv[0]->text, "no"); DEBUG_MODE_SET(&nb_dbg_client_confd, mode, !no); return CMD_SUCCESS; } static int frr_confd_debug_config_write(struct vty *vty) { if (DEBUG_MODE_CHECK(&nb_dbg_client_confd, DEBUG_MODE_CONF)) vty_out(vty, "debug northbound client confd\n"); return 0; } static int frr_confd_debug_set_all(uint32_t flags, bool set) { DEBUG_FLAGS_SET(&nb_dbg_client_confd, flags, set); /* If all modes have been turned off, don't preserve options. */ if (!DEBUG_MODE_CHECK(&nb_dbg_client_confd, DEBUG_MODE_ALL)) DEBUG_CLEAR(&nb_dbg_client_confd); return 0; } static void frr_confd_cli_init(void) { hook_register(nb_client_debug_config_write, frr_confd_debug_config_write); hook_register(nb_client_debug_set_all, frr_confd_debug_set_all); install_element(ENABLE_NODE, &debug_nb_confd_cmd); install_element(CONFIG_NODE, &debug_nb_confd_cmd); } /* ------------ Main ------------ */ static int frr_confd_calculate_snode_hash(const struct lys_node *snode, void *arg) { struct nb_node *nb_node = snode->priv; nb_node->confd_hash = confd_str2hash(snode->name); return YANG_ITER_CONTINUE; } static int frr_confd_init(const char *program_name) { struct sockaddr_in *confd_addr4 = (struct sockaddr_in *)&confd_addr; int debuglevel = CONFD_SILENT; int ret = -1; /* Initialize ConfD library. */ confd_init(program_name, stderr, debuglevel); confd_addr4->sin_family = AF_INET; confd_addr4->sin_addr.s_addr = inet_addr("127.0.0.1"); confd_addr4->sin_port = htons(CONFD_PORT); if (confd_load_schemas(&confd_addr, sizeof(struct sockaddr_in)) != CONFD_OK) { flog_err_confd("confd_load_schemas"); return -1; } ret = frr_confd_init_cdb(); if (ret != 0) goto error; ret = frr_confd_init_dp(program_name); if (ret != 0) { frr_confd_finish_cdb(); goto error; } yang_snodes_iterate_all(frr_confd_calculate_snode_hash, 0, NULL); hook_register(nb_notification_send, frr_confd_notification_send); confd_connected = true; return 0; error: confd_free_schemas(); return ret; } static int frr_confd_finish(void) { if (!confd_connected) return 0; frr_confd_finish_cdb(); frr_confd_finish_dp(); confd_free_schemas(); confd_connected = false; return 0; } static int frr_confd_module_late_init(struct thread_master *tm) { master = tm; if (frr_confd_init(frr_get_progname()) < 0) { flog_err(EC_LIB_CONFD_INIT, "failed to initialize the ConfD module"); return -1; } hook_register(frr_fini, frr_confd_finish); frr_confd_cli_init(); return 0; } static int frr_confd_module_init(void) { hook_register(frr_late_init, frr_confd_module_late_init); return 0; } FRR_MODULE_SETUP(.name = "frr_confd", .version = FRR_VERSION, .description = "FRR ConfD integration module", .init = frr_confd_module_init, ) frr-7.2.1/lib/northbound_db.c0000644000000000000000000001543013610377563012760 00000000000000/* * Copyright (C) 2018 NetDEF, Inc. * Renato Westphal * * This program is free software; you can redistribute it and/or modify it * under the terms of the 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; see the file COPYING; if not, write to the Free Software * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA */ #include #include "libfrr.h" #include "log.h" #include "lib_errors.h" #include "command.h" #include "db.h" #include "northbound.h" #include "northbound_db.h" int nb_db_init(void) { #ifdef HAVE_CONFIG_ROLLBACKS /* * NOTE: the delete_tail SQL trigger is used to implement a ring buffer * where only the last N transactions are recorded in the configuration * log. */ if (db_execute( "BEGIN TRANSACTION;\n" " CREATE TABLE IF NOT EXISTS transactions(\n" " client CHAR(32) NOT NULL,\n" " date DATETIME DEFAULT CURRENT_TIMESTAMP,\n" " comment CHAR(80) ,\n" " configuration TEXT NOT NULL\n" " );\n" " CREATE TRIGGER IF NOT EXISTS delete_tail\n" " AFTER INSERT ON transactions\n" " FOR EACH ROW\n" " BEGIN\n" " DELETE\n" " FROM\n" " transactions\n" " WHERE\n" " rowid%%%u=NEW.rowid%%%u AND rowid!=NEW.rowid;\n" " END;\n" "COMMIT;", NB_DLFT_MAX_CONFIG_ROLLBACKS, NB_DLFT_MAX_CONFIG_ROLLBACKS) != 0) return NB_ERR; #endif /* HAVE_CONFIG_ROLLBACKS */ return NB_OK; } int nb_db_transaction_save(const struct nb_transaction *transaction, uint32_t *transaction_id) { #ifdef HAVE_CONFIG_ROLLBACKS struct sqlite3_stmt *ss; const char *client_name; char *config_str = NULL; int ret = NB_ERR; /* * Use a transaction to ensure consistency between the INSERT and SELECT * queries. */ if (db_execute("BEGIN TRANSACTION;") != 0) return NB_ERR; ss = db_prepare( "INSERT INTO transactions\n" " (client, comment, configuration)\n" "VALUES\n" " (?, ?, ?);"); if (!ss) goto exit; client_name = nb_client_name(transaction->client); /* Always record configurations in the XML format. */ if (lyd_print_mem(&config_str, transaction->config->dnode, LYD_XML, LYP_FORMAT | LYP_WITHSIBLINGS) != 0) goto exit; if (db_bindf(ss, "%s%s%s", client_name, strlen(client_name), transaction->comment, strlen(transaction->comment), config_str ? config_str : "", config_str ? strlen(config_str) : 0) != 0) goto exit; if (db_run(ss) != SQLITE_OK) goto exit; db_finalize(&ss); /* * transaction_id is an optional output parameter that provides the ID * of the recorded transaction. */ if (transaction_id) { ss = db_prepare("SELECT last_insert_rowid();"); if (!ss) goto exit; if (db_run(ss) != SQLITE_ROW) goto exit; if (db_loadf(ss, "%i", transaction_id) != 0) goto exit; db_finalize(&ss); } if (db_execute("COMMIT;") != 0) goto exit; ret = NB_OK; exit: if (config_str) free(config_str); if (ss) db_finalize(&ss); if (ret != NB_OK) (void)db_execute("ROLLBACK TRANSACTION;"); return ret; #else /* HAVE_CONFIG_ROLLBACKS */ return NB_OK; #endif /* HAVE_CONFIG_ROLLBACKS */ } struct nb_config *nb_db_transaction_load(uint32_t transaction_id) { struct nb_config *config = NULL; #ifdef HAVE_CONFIG_ROLLBACKS struct lyd_node *dnode; const char *config_str; struct sqlite3_stmt *ss; ss = db_prepare( "SELECT\n" " configuration\n" "FROM\n" " transactions\n" "WHERE\n" " rowid=?;"); if (!ss) return NULL; if (db_bindf(ss, "%d", transaction_id) != 0) goto exit; if (db_run(ss) != SQLITE_ROW) goto exit; if (db_loadf(ss, "%s", &config_str) != 0) goto exit; dnode = lyd_parse_mem(ly_native_ctx, config_str, LYD_XML, LYD_OPT_CONFIG); if (!dnode) flog_warn(EC_LIB_LIBYANG, "%s: lyd_parse_mem() failed", __func__); else config = nb_config_new(dnode); exit: db_finalize(&ss); #endif /* HAVE_CONFIG_ROLLBACKS */ return config; } int nb_db_clear_transactions(unsigned int n_oldest) { #ifdef HAVE_CONFIG_ROLLBACKS /* Delete oldest N entries. */ if (db_execute("DELETE\n" "FROM\n" " transactions\n" "WHERE\n" " ROWID IN (\n" " SELECT\n" " ROWID\n" " FROM\n" " transactions\n" " ORDER BY ROWID ASC LIMIT %u\n" " );", n_oldest) != 0) return NB_ERR; #endif /* HAVE_CONFIG_ROLLBACKS */ return NB_OK; } int nb_db_set_max_transactions(unsigned int max) { #ifdef HAVE_CONFIG_ROLLBACKS /* * Delete old entries if necessary and update the SQL trigger that * auto-deletes old entries. */ if (db_execute("BEGIN TRANSACTION;\n" " DELETE\n" " FROM\n" " transactions\n" " WHERE\n" " ROWID IN (\n" " SELECT\n" " ROWID\n" " FROM\n" " transactions\n" " ORDER BY ROWID DESC LIMIT -1 OFFSET %u\n" " );\n" " DROP TRIGGER delete_tail;\n" " CREATE TRIGGER delete_tail\n" " AFTER INSERT ON transactions\n" " FOR EACH ROW\n" " BEGIN\n" " DELETE\n" " FROM\n" " transactions\n" " WHERE\n" " rowid%%%u=NEW.rowid%%%u AND rowid!=NEW.rowid;\n" " END;\n" "COMMIT;", max, max, max) != 0) return NB_ERR; #endif /* HAVE_CONFIG_ROLLBACKS */ return NB_OK; } int nb_db_transactions_iterate(void (*func)(void *arg, int transaction_id, const char *client_name, const char *date, const char *comment), void *arg) { #ifdef HAVE_CONFIG_ROLLBACKS struct sqlite3_stmt *ss; /* Send SQL query and parse the result. */ ss = db_prepare( "SELECT\n" " rowid, client, date, comment\n" "FROM\n" " transactions\n" "ORDER BY\n" " rowid DESC;"); if (!ss) return NB_ERR; while (db_run(ss) == SQLITE_ROW) { int transaction_id; const char *client_name; const char *date; const char *comment; int ret; ret = db_loadf(ss, "%i%s%s%s", &transaction_id, &client_name, &date, &comment); if (ret != 0) continue; (*func)(arg, transaction_id, client_name, date, comment); } db_finalize(&ss); #endif /* HAVE_CONFIG_ROLLBACKS */ return NB_OK; } frr-7.2.1/lib/northbound_db.h0000644000000000000000000000572313610377563012771 00000000000000/* * Copyright (C) 2018 NetDEF, Inc. * Renato Westphal * * This program is free software; you can redistribute it and/or modify it * under the terms of the 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; see the file COPYING; if not, write to the Free Software * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA */ #ifndef _FRR_NORTHBOUND_DB_H_ #define _FRR_NORTHBOUND_DB_H_ #include "northbound.h" #ifdef __cplusplus extern "C" { #endif /* * Initialize the northbound database. * * Currently the database is used only for storing and retrieving configuration * transactions. * * Returns: * NB_OK on success, NB_ERR otherwise. */ int nb_db_init(void); /* * Save a configuration transaction in the northbound database. * * transaction * Configuration transaction to be saved. * * transaction_id * Output parameter providing the ID of the saved transaction. * * Returns: * NB_OK on success, NB_ERR otherwise. */ int nb_db_transaction_save(const struct nb_transaction *transaction, uint32_t *transaction_id); /* * Load a configuration transaction from the transactions log. * * transaction_id * ID of the transaction to be loaded. * * Returns: * Pointer to newly created configuration or NULL in the case of an error. */ extern struct nb_config *nb_db_transaction_load(uint32_t transaction_id); /* * Delete the specified number of transactions from the transactions log. * * n_oldest * Number of transactions to delete. * * Returns: * NB_OK on success, NB_ERR otherwise. */ extern int nb_db_clear_transactions(unsigned int n_oldest); /* * Specify the maximum number of transactions we want to record in the * transactions log. Note that older transactions can be removed during this * operation. * * max * New upper limit of maximum transactions to log. * * Returns: * NB_OK on success, NB_ERR otherwise. */ extern int nb_db_set_max_transactions(unsigned int max); /* * Iterate over all configuration transactions stored in the northbound * database, sorted in descending order. * * func * Function to call with each configuration transaction. * * arg * Arbitrary argument passed as the first parameter in each call to 'func'. * * Returns: * NB_OK on success, NB_ERR otherwise. */ extern int nb_db_transactions_iterate( void (*func)(void *arg, int transaction_id, const char *client_name, const char *date, const char *comment), void *arg); #ifdef __cplusplus } #endif #endif /* _FRR_NORTHBOUND_DB_H_ */ frr-7.2.1/lib/northbound_grpc.cpp0000644000000000000000000005776313610377563013705 00000000000000// // Copyright (C) 2019 NetDEF, Inc. // Renato Westphal // // This program is free software; you can redistribute it and/or modify it // under the terms of the 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; see the file COPYING; if not, write to the Free Software // Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA // #include #include "log.h" #include "libfrr.h" #include "version.h" #include "command.h" #include "lib_errors.h" #include "northbound.h" #include "northbound_db.h" #include #include #include #include #include #include "grpc/frr-northbound.grpc.pb.h" #define GRPC_DEFAULT_PORT 50051 /* * NOTE: we can't use the FRR debugging infrastructure here since it uses * atomics and C++ has a different atomics API. Enable gRPC debugging * unconditionally until we figure out a way to solve this problem. */ static bool nb_dbg_client_grpc = 1; static pthread_t grpc_pthread; class NorthboundImpl final : public frr::Northbound::Service { public: NorthboundImpl(void) { _nextCandidateId = 0; } ~NorthboundImpl(void) { // Delete candidates. for (auto it = _candidates.begin(); it != _candidates.end(); it++) delete_candidate(&it->second); } grpc::Status GetCapabilities(grpc::ServerContext *context, frr::GetCapabilitiesRequest const *request, frr::GetCapabilitiesResponse *response) override { if (nb_dbg_client_grpc) zlog_debug("received RPC GetCapabilities()"); // Response: string frr_version = 1; response->set_frr_version(FRR_VERSION); // Response: bool rollback_support = 2; #ifdef HAVE_CONFIG_ROLLBACKS response->set_rollback_support(true); #else response->set_rollback_support(false); #endif // Response: repeated ModuleData supported_modules = 3; struct yang_module *module; RB_FOREACH (module, yang_modules, &yang_modules) { auto m = response->add_supported_modules(); m->set_name(module->name); if (module->info->rev_size) m->set_revision(module->info->rev[0].date); m->set_organization(module->info->org); } // Response: repeated Encoding supported_encodings = 4; response->add_supported_encodings(frr::JSON); response->add_supported_encodings(frr::XML); return grpc::Status::OK; } grpc::Status Get(grpc::ServerContext *context, frr::GetRequest const *request, grpc::ServerWriter *writer) override { // Request: DataType type = 1; int type = request->type(); // Request: Encoding encoding = 2; frr::Encoding encoding = request->encoding(); // Request: bool with_defaults = 3; bool with_defaults = request->with_defaults(); if (nb_dbg_client_grpc) zlog_debug( "received RPC Get(type: %u, encoding: %u, with_defaults: %u)", type, encoding, with_defaults); // Request: repeated string path = 4; auto paths = request->path(); for (const std::string &path : paths) { frr::GetResponse response; grpc::Status status; // Response: int64 timestamp = 1; response.set_timestamp(time(NULL)); // Response: DataTree data = 2; auto *data = response.mutable_data(); data->set_encoding(request->encoding()); status = get_path(data, path, type, encoding2lyd_format(encoding), with_defaults); // Something went wrong... if (!status.ok()) return status; writer->Write(response); } if (nb_dbg_client_grpc) zlog_debug("received RPC Get() end"); return grpc::Status::OK; } grpc::Status CreateCandidate(grpc::ServerContext *context, frr::CreateCandidateRequest const *request, frr::CreateCandidateResponse *response) override { if (nb_dbg_client_grpc) zlog_debug("received RPC CreateCandidate()"); struct candidate *candidate = create_candidate(); if (!candidate) return grpc::Status( grpc::StatusCode::RESOURCE_EXHAUSTED, "Can't create candidate configuration"); // Response: uint32 candidate_id = 1; response->set_candidate_id(candidate->id); return grpc::Status::OK; } grpc::Status DeleteCandidate(grpc::ServerContext *context, frr::DeleteCandidateRequest const *request, frr::DeleteCandidateResponse *response) override { // Request: uint32 candidate_id = 1; uint32_t candidate_id = request->candidate_id(); if (nb_dbg_client_grpc) zlog_debug( "received RPC DeleteCandidate(candidate_id: %u)", candidate_id); struct candidate *candidate = get_candidate(candidate_id); if (!candidate) return grpc::Status( grpc::StatusCode::NOT_FOUND, "candidate configuration not found"); delete_candidate(candidate); return grpc::Status::OK; } grpc::Status UpdateCandidate(grpc::ServerContext *context, frr::UpdateCandidateRequest const *request, frr::UpdateCandidateResponse *response) override { // Request: uint32 candidate_id = 1; uint32_t candidate_id = request->candidate_id(); if (nb_dbg_client_grpc) zlog_debug( "received RPC UpdateCandidate(candidate_id: %u)", candidate_id); struct candidate *candidate = get_candidate(candidate_id); if (!candidate) return grpc::Status( grpc::StatusCode::NOT_FOUND, "candidate configuration not found"); if (candidate->transaction) return grpc::Status( grpc::StatusCode::FAILED_PRECONDITION, "candidate is in the middle of a transaction"); if (nb_candidate_update(candidate->config) != NB_OK) return grpc::Status( grpc::StatusCode::INTERNAL, "failed to update candidate configuration"); return grpc::Status::OK; } grpc::Status EditCandidate(grpc::ServerContext *context, frr::EditCandidateRequest const *request, frr::EditCandidateResponse *response) override { // Request: uint32 candidate_id = 1; uint32_t candidate_id = request->candidate_id(); if (nb_dbg_client_grpc) zlog_debug( "received RPC EditCandidate(candidate_id: %u)", candidate_id); struct candidate *candidate = get_candidate(candidate_id); if (!candidate) return grpc::Status( grpc::StatusCode::NOT_FOUND, "candidate configuration not found"); // Create a copy of the candidate. For consistency, we need to // ensure that either all changes are accepted or none are (in // the event of an error). struct nb_config *candidate_tmp = nb_config_dup(candidate->config); auto pvs = request->update(); for (const frr::PathValue &pv : pvs) { if (yang_dnode_edit(candidate_tmp->dnode, pv.path(), pv.value()) != 0) { nb_config_free(candidate_tmp); return grpc::Status( grpc::StatusCode::INVALID_ARGUMENT, "Failed to update \"" + pv.path() + "\""); } } pvs = request->delete_(); for (const frr::PathValue &pv : pvs) { if (yang_dnode_delete(candidate_tmp->dnode, pv.path()) != 0) { nb_config_free(candidate_tmp); return grpc::Status( grpc::StatusCode::INVALID_ARGUMENT, "Failed to remove \"" + pv.path() + "\""); } } // No errors, accept all changes. nb_config_replace(candidate->config, candidate_tmp, false); return grpc::Status::OK; } grpc::Status LoadToCandidate(grpc::ServerContext *context, frr::LoadToCandidateRequest const *request, frr::LoadToCandidateResponse *response) override { // Request: uint32 candidate_id = 1; uint32_t candidate_id = request->candidate_id(); // Request: LoadType type = 2; int load_type = request->type(); // Request: DataTree config = 3; auto config = request->config(); if (nb_dbg_client_grpc) zlog_debug( "received RPC LoadToCandidate(candidate_id: %u)", candidate_id); struct candidate *candidate = get_candidate(candidate_id); if (!candidate) return grpc::Status( grpc::StatusCode::NOT_FOUND, "candidate configuration not found"); struct lyd_node *dnode = dnode_from_data_tree(&config, true); if (!dnode) return grpc::Status( grpc::StatusCode::INTERNAL, "Failed to parse the configuration"); struct nb_config *loaded_config = nb_config_new(dnode); if (load_type == frr::LoadToCandidateRequest::REPLACE) nb_config_replace(candidate->config, loaded_config, false); else if (nb_config_merge(candidate->config, loaded_config, false) != NB_OK) return grpc::Status( grpc::StatusCode::INTERNAL, "Failed to merge the loaded configuration"); return grpc::Status::OK; } grpc::Status Commit(grpc::ServerContext *context, frr::CommitRequest const *request, frr::CommitResponse *response) override { // Request: uint32 candidate_id = 1; uint32_t candidate_id = request->candidate_id(); // Request: Phase phase = 2; int phase = request->phase(); // Request: string comment = 3; const std::string comment = request->comment(); if (nb_dbg_client_grpc) zlog_debug("received RPC Commit(candidate_id: %u)", candidate_id); // Find candidate configuration. struct candidate *candidate = get_candidate(candidate_id); if (!candidate) return grpc::Status( grpc::StatusCode::NOT_FOUND, "candidate configuration not found"); int ret = NB_OK; uint32_t transaction_id = 0; // Check for misuse of the two-phase commit protocol. switch (phase) { case frr::CommitRequest::PREPARE: case frr::CommitRequest::ALL: if (candidate->transaction) return grpc::Status( grpc::StatusCode::FAILED_PRECONDITION, "pending transaction in progress"); break; case frr::CommitRequest::ABORT: case frr::CommitRequest::APPLY: if (!candidate->transaction) return grpc::Status( grpc::StatusCode::FAILED_PRECONDITION, "no transaction in progress"); break; default: break; } // Execute the user request. switch (phase) { case frr::CommitRequest::VALIDATE: ret = nb_candidate_validate(candidate->config); break; case frr::CommitRequest::PREPARE: ret = nb_candidate_commit_prepare( candidate->config, NB_CLIENT_GRPC, NULL, comment.c_str(), &candidate->transaction); break; case frr::CommitRequest::ABORT: nb_candidate_commit_abort(candidate->transaction); break; case frr::CommitRequest::APPLY: nb_candidate_commit_apply(candidate->transaction, true, &transaction_id); break; case frr::CommitRequest::ALL: ret = nb_candidate_commit( candidate->config, NB_CLIENT_GRPC, NULL, true, comment.c_str(), &transaction_id); break; } // Map northbound error codes to gRPC error codes. switch (ret) { case NB_ERR_NO_CHANGES: return grpc::Status( grpc::StatusCode::ABORTED, "No configuration changes detected"); case NB_ERR_LOCKED: return grpc::Status( grpc::StatusCode::UNAVAILABLE, "There's already a transaction in progress"); case NB_ERR_VALIDATION: return grpc::Status(grpc::StatusCode::INVALID_ARGUMENT, "Validation error"); case NB_ERR_RESOURCE: return grpc::Status( grpc::StatusCode::RESOURCE_EXHAUSTED, "Failed do allocate resources"); case NB_ERR: return grpc::Status(grpc::StatusCode::INTERNAL, "Internal error"); default: break; } // Response: uint32 transaction_id = 1; if (transaction_id) response->set_transaction_id(transaction_id); return grpc::Status::OK; } grpc::Status ListTransactions(grpc::ServerContext *context, frr::ListTransactionsRequest const *request, grpc::ServerWriter *writer) override { if (nb_dbg_client_grpc) zlog_debug("received RPC ListTransactions()"); nb_db_transactions_iterate(list_transactions_cb, writer); return grpc::Status::OK; } grpc::Status GetTransaction(grpc::ServerContext *context, frr::GetTransactionRequest const *request, frr::GetTransactionResponse *response) override { struct nb_config *nb_config; // Request: uint32 transaction_id = 1; uint32_t transaction_id = request->transaction_id(); // Request: Encoding encoding = 2; frr::Encoding encoding = request->encoding(); // Request: bool with_defaults = 3; bool with_defaults = request->with_defaults(); if (nb_dbg_client_grpc) zlog_debug( "received RPC GetTransaction(transaction_id: %u, encoding: %u)", transaction_id, encoding); // Load configuration from the transactions database. nb_config = nb_db_transaction_load(transaction_id); if (!nb_config) return grpc::Status(grpc::StatusCode::INVALID_ARGUMENT, "Transaction not found"); // Response: DataTree config = 1; auto config = response->mutable_config(); config->set_encoding(encoding); // Dump data using the requested format. if (data_tree_from_dnode(config, nb_config->dnode, encoding2lyd_format(encoding), with_defaults) != 0) { nb_config_free(nb_config); return grpc::Status(grpc::StatusCode::INTERNAL, "Failed to dump data"); } nb_config_free(nb_config); return grpc::Status::OK; } grpc::Status LockConfig(grpc::ServerContext *context, frr::LockConfigRequest const *request, frr::LockConfigResponse *response) override { if (nb_dbg_client_grpc) zlog_debug("received RPC LockConfig()"); if (nb_running_lock(NB_CLIENT_GRPC, NULL)) return grpc::Status( grpc::StatusCode::FAILED_PRECONDITION, "running configuration is locked already"); return grpc::Status::OK; } grpc::Status UnlockConfig(grpc::ServerContext *context, frr::UnlockConfigRequest const *request, frr::UnlockConfigResponse *response) override { if (nb_dbg_client_grpc) zlog_debug("received RPC UnlockConfig()"); if (nb_running_unlock(NB_CLIENT_GRPC, NULL)) return grpc::Status( grpc::StatusCode::FAILED_PRECONDITION, "failed to unlock the running configuration"); return grpc::Status::OK; } grpc::Status Execute(grpc::ServerContext *context, frr::ExecuteRequest const *request, frr::ExecuteResponse *response) override { struct nb_node *nb_node; struct list *input_list; struct list *output_list; struct listnode *node; struct yang_data *data; const char *xpath; // Request: string path = 1; xpath = request->path().c_str(); if (nb_dbg_client_grpc) zlog_debug("received RPC Execute(path: \"%s\")", xpath); if (request->path().empty()) return grpc::Status(grpc::StatusCode::INVALID_ARGUMENT, "Data path is empty"); nb_node = nb_node_find(xpath); if (!nb_node) return grpc::Status(grpc::StatusCode::INVALID_ARGUMENT, "Unknown data path"); input_list = yang_data_list_new(); output_list = yang_data_list_new(); // Read input parameters. auto input = request->input(); for (const frr::PathValue &pv : input) { // Request: repeated PathValue input = 2; data = yang_data_new(pv.path().c_str(), pv.value().c_str()); listnode_add(input_list, data); } // Execute callback registered for this XPath. if (nb_node->cbs.rpc(xpath, input_list, output_list) != NB_OK) { flog_warn(EC_LIB_NB_CB_RPC, "%s: rpc callback failed: %s", __func__, xpath); list_delete(&input_list); list_delete(&output_list); return grpc::Status(grpc::StatusCode::INTERNAL, "RPC failed"); } // Process output parameters. for (ALL_LIST_ELEMENTS_RO(output_list, node, data)) { // Response: repeated PathValue output = 1; frr::PathValue *pv = response->add_output(); pv->set_path(data->xpath); pv->set_value(data->value); } // Release memory. list_delete(&input_list); list_delete(&output_list); return grpc::Status::OK; } private: struct candidate { uint32_t id; struct nb_config *config; struct nb_transaction *transaction; }; std::map _candidates; uint32_t _nextCandidateId; static int yang_dnode_edit(struct lyd_node *dnode, const std::string &path, const std::string &value) { ly_errno = LY_SUCCESS; dnode = lyd_new_path(dnode, ly_native_ctx, path.c_str(), (void *)value.c_str(), (LYD_ANYDATA_VALUETYPE)0, LYD_PATH_OPT_UPDATE); if (!dnode && ly_errno != LY_SUCCESS) { flog_warn(EC_LIB_LIBYANG, "%s: lyd_new_path() failed", __func__); return -1; } return 0; } static int yang_dnode_delete(struct lyd_node *dnode, const std::string &path) { dnode = yang_dnode_get(dnode, path.c_str()); if (!dnode) return -1; lyd_free(dnode); return 0; } static LYD_FORMAT encoding2lyd_format(enum frr::Encoding encoding) { switch (encoding) { case frr::JSON: return LYD_JSON; case frr::XML: return LYD_XML; } } static int get_oper_data_cb(const struct lys_node *snode, struct yang_translator *translator, struct yang_data *data, void *arg) { struct lyd_node *dnode = static_cast(arg); int ret = yang_dnode_edit(dnode, data->xpath, data->value); yang_data_free(data); return (ret == 0) ? NB_OK : NB_ERR; } static void list_transactions_cb(void *arg, int transaction_id, const char *client_name, const char *date, const char *comment) { grpc::ServerWriter *writer = static_cast *>(arg); frr::ListTransactionsResponse response; // Response: uint32 id = 1; response.set_id(transaction_id); // Response: string client = 2; response.set_client(client_name); // Response: string date = 3; response.set_date(date); // Response: string comment = 4; response.set_comment(comment); writer->Write(response); } static int data_tree_from_dnode(frr::DataTree *dt, const struct lyd_node *dnode, LYD_FORMAT lyd_format, bool with_defaults) { char *strp; int options = 0; SET_FLAG(options, LYP_FORMAT | LYP_WITHSIBLINGS); if (with_defaults) SET_FLAG(options, LYP_WD_ALL); else SET_FLAG(options, LYP_WD_TRIM); if (lyd_print_mem(&strp, dnode, lyd_format, options) == 0) { if (strp) { dt->set_data(strp); free(strp); } return 0; } return -1; } static struct lyd_node *dnode_from_data_tree(const frr::DataTree *dt, bool config_only) { struct lyd_node *dnode; int options; if (config_only) options = LYD_OPT_CONFIG; else options = LYD_OPT_DATA | LYD_OPT_DATA_NO_YANGLIB; dnode = lyd_parse_mem(ly_native_ctx, dt->data().c_str(), encoding2lyd_format(dt->encoding()), options); return dnode; } static struct lyd_node *get_dnode_config(const std::string &path) { struct lyd_node *dnode; pthread_rwlock_rdlock(&running_config->lock); { dnode = yang_dnode_get(running_config->dnode, path.empty() ? NULL : path.c_str()); if (dnode) dnode = yang_dnode_dup(dnode); } pthread_rwlock_unlock(&running_config->lock); return dnode; } static struct lyd_node *get_dnode_state(const std::string &path) { struct lyd_node *dnode; dnode = yang_dnode_new(ly_native_ctx, false); if (nb_oper_data_iterate(path.c_str(), NULL, 0, get_oper_data_cb, dnode) != NB_OK) { yang_dnode_free(dnode); return NULL; } return dnode; } static grpc::Status get_path(frr::DataTree *dt, const std::string &path, int type, LYD_FORMAT lyd_format, bool with_defaults) { struct lyd_node *dnode_config = NULL; struct lyd_node *dnode_state = NULL; struct lyd_node *dnode_final; // Configuration data. if (type == frr::GetRequest_DataType_ALL || type == frr::GetRequest_DataType_CONFIG) { dnode_config = get_dnode_config(path); if (!dnode_config) return grpc::Status( grpc::StatusCode::INVALID_ARGUMENT, "Data path not found"); } // Operational data. if (type == frr::GetRequest_DataType_ALL || type == frr::GetRequest_DataType_STATE) { dnode_state = get_dnode_state(path); if (!dnode_state) { if (dnode_config) yang_dnode_free(dnode_config); return grpc::Status( grpc::StatusCode::INVALID_ARGUMENT, "Failed to fetch operational data"); } } switch (type) { case frr::GetRequest_DataType_ALL: // // Combine configuration and state data into a single // dnode. // if (lyd_merge(dnode_state, dnode_config, LYD_OPT_EXPLICIT) != 0) { yang_dnode_free(dnode_state); yang_dnode_free(dnode_config); return grpc::Status( grpc::StatusCode::INTERNAL, "Failed to merge configuration and state data"); } dnode_final = dnode_state; break; case frr::GetRequest_DataType_CONFIG: dnode_final = dnode_config; break; case frr::GetRequest_DataType_STATE: dnode_final = dnode_state; break; } // Validate data to create implicit default nodes if necessary. int validate_opts = 0; if (type == frr::GetRequest_DataType_CONFIG) validate_opts = LYD_OPT_CONFIG; else validate_opts = LYD_OPT_DATA | LYD_OPT_DATA_NO_YANGLIB; lyd_validate(&dnode_final, validate_opts, ly_native_ctx); // Dump data using the requested format. int ret = data_tree_from_dnode(dt, dnode_final, lyd_format, with_defaults); yang_dnode_free(dnode_final); if (ret != 0) return grpc::Status(grpc::StatusCode::INTERNAL, "Failed to dump data"); return grpc::Status::OK; } struct candidate *create_candidate(void) { uint32_t candidate_id = ++_nextCandidateId; // Check for overflow. // TODO: implement an algorithm for unique reusable IDs. if (candidate_id == 0) return NULL; struct candidate *candidate = &_candidates[candidate_id]; candidate->id = candidate_id; pthread_rwlock_rdlock(&running_config->lock); { candidate->config = nb_config_dup(running_config); } pthread_rwlock_unlock(&running_config->lock); candidate->transaction = NULL; return candidate; } void delete_candidate(struct candidate *candidate) { _candidates.erase(candidate->id); nb_config_free(candidate->config); if (candidate->transaction) nb_candidate_commit_abort(candidate->transaction); } struct candidate *get_candidate(uint32_t candidate_id) { struct candidate *candidate; if (_candidates.count(candidate_id) == 0) return NULL; return &_candidates[candidate_id]; } }; static void *grpc_pthread_start(void *arg) { unsigned long *port = static_cast(arg); NorthboundImpl service; std::stringstream server_address; server_address << "0.0.0.0:" << *port; grpc::ServerBuilder builder; builder.AddListeningPort(server_address.str(), grpc::InsecureServerCredentials()); builder.RegisterService(&service); std::unique_ptr server(builder.BuildAndStart()); zlog_notice("gRPC server listening on %s", server_address.str().c_str()); server->Wait(); return NULL; } static int frr_grpc_init(unsigned long *port) { /* Create a pthread for gRPC since it runs its own event loop. */ if (pthread_create(&grpc_pthread, NULL, grpc_pthread_start, port)) { flog_err(EC_LIB_SYSTEM_CALL, "%s: error creating pthread: %s", __func__, safe_strerror(errno)); return -1; } pthread_detach(grpc_pthread); return 0; } static int frr_grpc_finish(void) { // TODO: cancel the gRPC pthreads gracefully. return 0; } static int frr_grpc_module_late_init(struct thread_master *tm) { static unsigned long port = GRPC_DEFAULT_PORT; const char *args = THIS_MODULE->load_args; // Parse port number. if (args) { try { port = std::stoul(args); if (port < 1024) throw std::invalid_argument( "can't use privileged port"); if (port > UINT16_MAX) throw std::invalid_argument( "port number is too big"); } catch (std::exception &e) { flog_err(EC_LIB_GRPC_INIT, "%s: failed to parse port number: %s", __func__, e.what()); goto error; } } if (frr_grpc_init(&port) < 0) goto error; hook_register(frr_fini, frr_grpc_finish); return 0; error: flog_err(EC_LIB_GRPC_INIT, "failed to initialize the gRPC module"); return -1; } static int frr_grpc_module_init(void) { hook_register(frr_late_init, frr_grpc_module_late_init); return 0; } FRR_MODULE_SETUP(.name = "frr_grpc", .version = FRR_VERSION, .description = "FRR gRPC northbound module", .init = frr_grpc_module_init, ) frr-7.2.1/lib/northbound_sysrepo.c0000644000000000000000000005465713610377563014115 00000000000000/* * Copyright (C) 2018 NetDEF, Inc. * Renato Westphal * * This program is free software; you can redistribute it and/or modify it * under the terms of the 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; see the file COPYING; if not, write to the Free Software * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA */ #include #include "log.h" #include "lib_errors.h" #include "command.h" #include "debug.h" #include "memory.h" #include "libfrr.h" #include "version.h" #include "northbound.h" #include #include #include DEFINE_MTYPE_STATIC(LIB, SYSREPO, "Sysrepo module") static struct debug nb_dbg_client_sysrepo = {0, "Northbound client: Sysrepo"}; static struct thread_master *master; static struct list *sysrepo_threads; static sr_session_ctx_t *session; static sr_conn_ctx_t *connection; static struct nb_transaction *transaction; static int frr_sr_read_cb(struct thread *thread); static int frr_sr_write_cb(struct thread *thread); static int frr_sr_finish(void); /* Convert FRR YANG data value to sysrepo YANG data value. */ static int yang_data_frr2sr(struct yang_data *frr_data, sr_val_t *sr_data) { struct nb_node *nb_node; const struct lys_node *snode; struct lys_node_container *scontainer; struct lys_node_leaf *sleaf; struct lys_node_leaflist *sleaflist; LY_DATA_TYPE type; sr_val_set_xpath(sr_data, frr_data->xpath); nb_node = nb_node_find(frr_data->xpath); if (!nb_node) { flog_warn(EC_LIB_YANG_UNKNOWN_DATA_PATH, "%s: unknown data path: %s", __func__, frr_data->xpath); return -1; } snode = nb_node->snode; switch (snode->nodetype) { case LYS_CONTAINER: scontainer = (struct lys_node_container *)snode; if (!scontainer->presence) return -1; sr_data->type = SR_CONTAINER_PRESENCE_T; return 0; case LYS_LIST: sr_data->type = SR_LIST_T; return 0; case LYS_LEAF: sleaf = (struct lys_node_leaf *)snode; type = sleaf->type.base; break; case LYS_LEAFLIST: sleaflist = (struct lys_node_leaflist *)snode; type = sleaflist->type.base; break; default: return -1; } switch (type) { case LY_TYPE_BINARY: sr_val_set_str_data(sr_data, SR_BINARY_T, frr_data->value); break; case LY_TYPE_BITS: sr_val_set_str_data(sr_data, SR_BITS_T, frr_data->value); break; case LY_TYPE_BOOL: sr_data->type = SR_BOOL_T; sr_data->data.bool_val = yang_str2bool(frr_data->value); break; case LY_TYPE_DEC64: sr_data->type = SR_DECIMAL64_T; sr_data->data.decimal64_val = yang_str2dec64(frr_data->xpath, frr_data->value); break; case LY_TYPE_EMPTY: sr_data->type = SR_LEAF_EMPTY_T; break; case LY_TYPE_ENUM: sr_val_set_str_data(sr_data, SR_ENUM_T, frr_data->value); break; case LY_TYPE_IDENT: sr_val_set_str_data(sr_data, SR_IDENTITYREF_T, frr_data->value); break; case LY_TYPE_INST: sr_val_set_str_data(sr_data, SR_INSTANCEID_T, frr_data->value); break; case LY_TYPE_INT8: sr_data->type = SR_INT8_T; sr_data->data.int8_val = yang_str2int8(frr_data->value); break; case LY_TYPE_INT16: sr_data->type = SR_INT16_T; sr_data->data.int16_val = yang_str2int16(frr_data->value); break; case LY_TYPE_INT32: sr_data->type = SR_INT32_T; sr_data->data.int32_val = yang_str2int32(frr_data->value); break; case LY_TYPE_INT64: sr_data->type = SR_INT64_T; sr_data->data.int64_val = yang_str2int64(frr_data->value); break; case LY_TYPE_STRING: sr_val_set_str_data(sr_data, SR_STRING_T, frr_data->value); break; case LY_TYPE_UINT8: sr_data->type = SR_UINT8_T; sr_data->data.uint8_val = yang_str2uint8(frr_data->value); break; case LY_TYPE_UINT16: sr_data->type = SR_UINT16_T; sr_data->data.uint16_val = yang_str2uint16(frr_data->value); break; case LY_TYPE_UINT32: sr_data->type = SR_UINT32_T; sr_data->data.uint32_val = yang_str2uint32(frr_data->value); break; case LY_TYPE_UINT64: sr_data->type = SR_UINT64_T; sr_data->data.uint64_val = yang_str2uint64(frr_data->value); break; default: return -1; } return 0; } static int frr_sr_process_change(struct nb_config *candidate, sr_change_oper_t sr_op, sr_val_t *sr_old_val, sr_val_t *sr_new_val) { struct nb_node *nb_node; enum nb_operation nb_op; sr_val_t *sr_data; const char *xpath; char value_str[YANG_VALUE_MAXLEN]; struct yang_data *data; int ret; sr_data = sr_new_val ? sr_new_val : sr_old_val; assert(sr_data); xpath = sr_data->xpath; /* Non-presence container - nothing to do. */ if (sr_data->type == SR_CONTAINER_T) return NB_OK; nb_node = nb_node_find(xpath); if (!nb_node) { flog_warn(EC_LIB_YANG_UNKNOWN_DATA_PATH, "%s: unknown data path: %s", __func__, xpath); return NB_ERR; } /* Map operation values. */ switch (sr_op) { case SR_OP_CREATED: case SR_OP_MODIFIED: if (nb_operation_is_valid(NB_OP_CREATE, nb_node->snode)) nb_op = NB_OP_CREATE; else if (nb_operation_is_valid(NB_OP_MODIFY, nb_node->snode)) { nb_op = NB_OP_MODIFY; } else /* Ignore list keys modifications. */ return NB_OK; break; case SR_OP_DELETED: /* * When a list is deleted or one of its keys is changed, we are * notified about the removal of all of its leafs, even the ones * that are non-optional. We need to ignore these notifications. */ if (!nb_operation_is_valid(NB_OP_DESTROY, nb_node->snode)) return NB_OK; nb_op = NB_OP_DESTROY; break; case SR_OP_MOVED: nb_op = NB_OP_MOVE; break; default: flog_err(EC_LIB_DEVELOPMENT, "%s: unexpected operation %u [xpath %s]", __func__, sr_op, xpath); return NB_ERR; } sr_val_to_buff(sr_data, value_str, sizeof(value_str)); data = yang_data_new(xpath, value_str); ret = nb_candidate_edit(candidate, nb_node, nb_op, xpath, NULL, data); yang_data_free(data); if (ret != NB_OK) { flog_warn( EC_LIB_NB_CANDIDATE_EDIT_ERROR, "%s: failed to edit candidate configuration: operation [%s] xpath [%s]", __func__, nb_operation_name(nb_op), xpath); return NB_ERR; } return NB_OK; } static int frr_sr_config_change_cb_verify(sr_session_ctx_t *session, const char *module_name, bool startup_config) { sr_change_iter_t *it; int ret; sr_change_oper_t sr_op; sr_val_t *sr_old_val, *sr_new_val; char xpath[XPATH_MAXLEN]; struct nb_config *candidate; snprintf(xpath, sizeof(xpath), "/%s:*", module_name); ret = sr_get_changes_iter(session, xpath, &it); if (ret != SR_ERR_OK) { flog_err(EC_LIB_LIBSYSREPO, "%s: sr_get_changes_iter() failed for xpath %s", __func__, xpath); return ret; } pthread_rwlock_rdlock(&running_config->lock); { candidate = nb_config_dup(running_config); } pthread_rwlock_unlock(&running_config->lock); while ((ret = sr_get_change_next(session, it, &sr_op, &sr_old_val, &sr_new_val)) == SR_ERR_OK) { ret = frr_sr_process_change(candidate, sr_op, sr_old_val, sr_new_val); sr_free_val(sr_old_val); sr_free_val(sr_new_val); if (ret != NB_OK) break; } sr_free_change_iter(it); if (ret != NB_OK && ret != SR_ERR_NOT_FOUND) { nb_config_free(candidate); return SR_ERR_INTERNAL; } transaction = NULL; if (startup_config) { /* * sysrepod sends the entire startup configuration using a * single event (SR_EV_ENABLED). This means we need to perform * the full two-phase commit protocol in one go here. */ ret = nb_candidate_commit(candidate, NB_CLIENT_SYSREPO, NULL, true, NULL, NULL); } else { /* * Validate the configuration changes and allocate all resources * required to apply them. */ ret = nb_candidate_commit_prepare(candidate, NB_CLIENT_SYSREPO, NULL, NULL, &transaction); } /* Map northbound return code to sysrepo return code. */ switch (ret) { case NB_OK: return SR_ERR_OK; case NB_ERR_NO_CHANGES: nb_config_free(candidate); return SR_ERR_OK; case NB_ERR_LOCKED: return SR_ERR_LOCKED; case NB_ERR_RESOURCE: return SR_ERR_NOMEM; default: return SR_ERR_VALIDATION_FAILED; } } static int frr_sr_config_change_cb_apply(sr_session_ctx_t *session, const char *module_name) { /* Apply the transaction. */ if (transaction) { struct nb_config *candidate = transaction->config; nb_candidate_commit_apply(transaction, true, NULL); nb_config_free(candidate); } return SR_ERR_OK; } static int frr_sr_config_change_cb_abort(sr_session_ctx_t *session, const char *module_name) { /* Abort the transaction. */ if (transaction) { struct nb_config *candidate = transaction->config; nb_candidate_commit_abort(transaction); nb_config_free(candidate); } return SR_ERR_OK; } /* Callback for changes in the running configuration. */ static int frr_sr_config_change_cb(sr_session_ctx_t *session, const char *module_name, sr_notif_event_t sr_ev, void *private_ctx) { switch (sr_ev) { case SR_EV_ENABLED: return frr_sr_config_change_cb_verify(session, module_name, true); case SR_EV_VERIFY: return frr_sr_config_change_cb_verify(session, module_name, false); case SR_EV_APPLY: return frr_sr_config_change_cb_apply(session, module_name); case SR_EV_ABORT: return frr_sr_config_change_cb_abort(session, module_name); default: flog_err(EC_LIB_LIBSYSREPO, "%s: unknown sysrepo event: %u", __func__, sr_ev); return SR_ERR_INTERNAL; } } static int frr_sr_state_data_iter_cb(const struct lys_node *snode, struct yang_translator *translator, struct yang_data *data, void *arg) { struct list *elements = arg; listnode_add(elements, data); return NB_OK; } /* Callback for state retrieval. */ static int frr_sr_state_cb(const char *xpath, sr_val_t **values, size_t *values_cnt, uint64_t request_id, const char *original_xpath, void *private_ctx) { struct list *elements; struct yang_data *data; struct listnode *node; sr_val_t *v; int ret, count, i = 0; elements = yang_data_list_new(); if (nb_oper_data_iterate(xpath, NULL, NB_OPER_DATA_ITER_NORECURSE, frr_sr_state_data_iter_cb, elements) != NB_OK) { flog_warn(EC_LIB_NB_OPERATIONAL_DATA, "%s: failed to obtain operational data [xpath %s]", __func__, xpath); goto exit; } if (list_isempty(elements)) goto exit; count = listcount(elements); ret = sr_new_values(count, &v); if (ret != SR_ERR_OK) { flog_err(EC_LIB_LIBSYSREPO, "%s: sr_new_values(): %s", __func__, sr_strerror(ret)); goto exit; } for (ALL_LIST_ELEMENTS_RO(elements, node, data)) { if (yang_data_frr2sr(data, &v[i++]) != 0) { flog_err(EC_LIB_SYSREPO_DATA_CONVERT, "%s: failed to convert data to sysrepo format", __func__); } } *values = v; *values_cnt = count; list_delete(&elements); return SR_ERR_OK; exit: list_delete(&elements); *values = NULL; *values_cnt = 0; return SR_ERR_OK; } static int frr_sr_config_rpc_cb(const char *xpath, const sr_val_t *sr_input, const size_t input_cnt, sr_val_t **sr_output, size_t *sr_output_cnt, void *private_ctx) { struct nb_node *nb_node; struct list *input; struct list *output; struct yang_data *data; size_t cb_output_cnt; int ret = SR_ERR_OK; nb_node = nb_node_find(xpath); if (!nb_node) { flog_warn(EC_LIB_YANG_UNKNOWN_DATA_PATH, "%s: unknown data path: %s", __func__, xpath); return SR_ERR_INTERNAL; } input = yang_data_list_new(); output = yang_data_list_new(); /* Process input. */ for (size_t i = 0; i < input_cnt; i++) { char value_str[YANG_VALUE_MAXLEN]; sr_val_to_buff(&sr_input[i], value_str, sizeof(value_str)); data = yang_data_new(xpath, value_str); listnode_add(input, data); } /* Execute callback registered for this XPath. */ if (nb_callback_rpc(nb_node, xpath, input, output) != NB_OK) { flog_warn(EC_LIB_NB_CB_RPC, "%s: rpc callback failed: %s", __func__, xpath); ret = SR_ERR_OPERATION_FAILED; goto exit; } /* Process output. */ if (listcount(output) > 0) { sr_val_t *values = NULL; struct listnode *node; int i = 0; cb_output_cnt = listcount(output); ret = sr_new_values(cb_output_cnt, &values); if (ret != SR_ERR_OK) { flog_err(EC_LIB_LIBSYSREPO, "%s: sr_new_values(): %s", __func__, sr_strerror(ret)); goto exit; } for (ALL_LIST_ELEMENTS_RO(output, node, data)) { if (yang_data_frr2sr(data, &values[i++]) != 0) { flog_err( EC_LIB_SYSREPO_DATA_CONVERT, "%s: failed to convert data to Sysrepo format", __func__); ret = SR_ERR_INTERNAL; sr_free_values(values, cb_output_cnt); goto exit; } } *sr_output = values; *sr_output_cnt = cb_output_cnt; } exit: /* Release memory. */ list_delete(&input); list_delete(&output); return ret; } static int frr_sr_notification_send(const char *xpath, struct list *arguments) { sr_val_t *values = NULL; size_t values_cnt = 0; int ret; if (arguments && listcount(arguments) > 0) { struct yang_data *data; struct listnode *node; int i = 0; values_cnt = listcount(arguments); ret = sr_new_values(values_cnt, &values); if (ret != SR_ERR_OK) { flog_err(EC_LIB_LIBSYSREPO, "%s: sr_new_values(): %s", __func__, sr_strerror(ret)); return NB_ERR; } for (ALL_LIST_ELEMENTS_RO(arguments, node, data)) { if (yang_data_frr2sr(data, &values[i++]) != 0) { flog_err( EC_LIB_SYSREPO_DATA_CONVERT, "%s: failed to convert data to sysrepo format", __func__); sr_free_values(values, values_cnt); return NB_ERR; } } } ret = sr_event_notif_send(session, xpath, values, values_cnt, SR_EV_NOTIF_DEFAULT); if (ret != SR_ERR_OK) { flog_err(EC_LIB_LIBSYSREPO, "%s: sr_event_notif_send() failed for xpath %s", __func__, xpath); return NB_ERR; } return NB_OK; } /* Code to integrate the sysrepo client into FRR main event loop. */ struct sysrepo_thread { struct thread *thread; sr_fd_event_t event; int fd; }; static struct sysrepo_thread *frr_sr_fd_lookup(sr_fd_event_t event, int fd) { struct sysrepo_thread *sr_thread; struct listnode *node; for (ALL_LIST_ELEMENTS_RO(sysrepo_threads, node, sr_thread)) { if (sr_thread->event == event && sr_thread->fd == fd) return sr_thread; } return NULL; } static void frr_sr_fd_add(int event, int fd) { struct sysrepo_thread *sr_thread; if (frr_sr_fd_lookup(event, fd) != NULL) return; sr_thread = XCALLOC(MTYPE_SYSREPO, sizeof(*sr_thread)); sr_thread->event = event; sr_thread->fd = fd; listnode_add(sysrepo_threads, sr_thread); switch (event) { case SR_FD_INPUT_READY: thread_add_read(master, frr_sr_read_cb, NULL, fd, &sr_thread->thread); break; case SR_FD_OUTPUT_READY: thread_add_write(master, frr_sr_write_cb, NULL, fd, &sr_thread->thread); break; default: return; } } static void frr_sr_fd_free(struct sysrepo_thread *sr_thread) { THREAD_OFF(sr_thread->thread); XFREE(MTYPE_SYSREPO, sr_thread); } static void frr_sr_fd_del(int event, int fd) { struct sysrepo_thread *sr_thread; sr_thread = frr_sr_fd_lookup(event, fd); if (!sr_thread) return; listnode_delete(sysrepo_threads, sr_thread); frr_sr_fd_free(sr_thread); } static void frr_sr_fd_update(sr_fd_change_t *fd_change_set, size_t fd_change_set_cnt) { for (size_t i = 0; i < fd_change_set_cnt; i++) { int fd = fd_change_set[i].fd; int event = fd_change_set[i].events; if (event != SR_FD_INPUT_READY && event != SR_FD_OUTPUT_READY) continue; switch (fd_change_set[i].action) { case SR_FD_START_WATCHING: frr_sr_fd_add(event, fd); break; case SR_FD_STOP_WATCHING: frr_sr_fd_del(event, fd); break; default: break; } } } static int frr_sr_read_cb(struct thread *thread) { int fd = THREAD_FD(thread); sr_fd_change_t *fd_change_set = NULL; size_t fd_change_set_cnt = 0; int ret; ret = sr_fd_event_process(fd, SR_FD_INPUT_READY, &fd_change_set, &fd_change_set_cnt); if (ret != SR_ERR_OK) { flog_err(EC_LIB_LIBSYSREPO, "%s: sr_fd_event_process(): %s", __func__, sr_strerror(ret)); return -1; } thread = NULL; thread_add_read(master, frr_sr_read_cb, NULL, fd, &thread); frr_sr_fd_update(fd_change_set, fd_change_set_cnt); free(fd_change_set); return 0; } static int frr_sr_write_cb(struct thread *thread) { int fd = THREAD_FD(thread); sr_fd_change_t *fd_change_set = NULL; size_t fd_change_set_cnt = 0; int ret; ret = sr_fd_event_process(fd, SR_FD_OUTPUT_READY, &fd_change_set, &fd_change_set_cnt); if (ret != SR_ERR_OK) { flog_err(EC_LIB_LIBSYSREPO, "%s: sr_fd_event_process(): %s", __func__, sr_strerror(ret)); return -1; } frr_sr_fd_update(fd_change_set, fd_change_set_cnt); free(fd_change_set); return 0; } static void frr_sr_subscribe_config(struct yang_module *module) { int ret; ret = sr_module_change_subscribe( session, module->name, frr_sr_config_change_cb, NULL, 0, SR_SUBSCR_DEFAULT | SR_SUBSCR_EV_ENABLED, &module->sr_subscription); if (ret != SR_ERR_OK) flog_err(EC_LIB_LIBSYSREPO, "sr_module_change_subscribe(): %s", sr_strerror(ret)); } static int frr_sr_subscribe_state(const struct lys_node *snode, void *arg) { struct yang_module *module = arg; struct nb_node *nb_node; int ret; if (!CHECK_FLAG(snode->flags, LYS_CONFIG_R)) return YANG_ITER_CONTINUE; /* We only need to subscribe to the root of the state subtrees. */ if (snode->parent && CHECK_FLAG(snode->parent->flags, LYS_CONFIG_R)) return YANG_ITER_CONTINUE; nb_node = snode->priv; DEBUGD(&nb_dbg_client_sysrepo, "%s: providing data to '%s'", __func__, nb_node->xpath); ret = sr_dp_get_items_subscribe( session, nb_node->xpath, frr_sr_state_cb, NULL, SR_SUBSCR_CTX_REUSE, &module->sr_subscription); if (ret != SR_ERR_OK) flog_err(EC_LIB_LIBSYSREPO, "sr_dp_get_items_subscribe(): %s", sr_strerror(ret)); return YANG_ITER_CONTINUE; } static int frr_sr_subscribe_rpc(const struct lys_node *snode, void *arg) { struct yang_module *module = arg; struct nb_node *nb_node; int ret; if (snode->nodetype != LYS_RPC) return YANG_ITER_CONTINUE; nb_node = snode->priv; DEBUGD(&nb_dbg_client_sysrepo, "%s: providing RPC to '%s'", __func__, nb_node->xpath); ret = sr_rpc_subscribe(session, nb_node->xpath, frr_sr_config_rpc_cb, NULL, SR_SUBSCR_CTX_REUSE, &module->sr_subscription); if (ret != SR_ERR_OK) flog_err(EC_LIB_LIBSYSREPO, "sr_rpc_subscribe(): %s", sr_strerror(ret)); return YANG_ITER_CONTINUE; } static int frr_sr_subscribe_action(const struct lys_node *snode, void *arg) { struct yang_module *module = arg; struct nb_node *nb_node; int ret; if (snode->nodetype != LYS_ACTION) return YANG_ITER_CONTINUE; nb_node = snode->priv; DEBUGD(&nb_dbg_client_sysrepo, "%s: providing action to '%s'", __func__, nb_node->xpath); ret = sr_action_subscribe(session, nb_node->xpath, frr_sr_config_rpc_cb, NULL, SR_SUBSCR_CTX_REUSE, &module->sr_subscription); if (ret != SR_ERR_OK) flog_err(EC_LIB_LIBSYSREPO, "sr_action_subscribe(): %s", sr_strerror(ret)); return YANG_ITER_CONTINUE; } /* CLI commands. */ DEFUN (debug_nb_sr, debug_nb_sr_cmd, "[no] debug northbound client sysrepo", NO_STR DEBUG_STR "Northbound debugging\n" "Northbound client\n" "Sysrepo\n") { uint32_t mode = DEBUG_NODE2MODE(vty->node); bool no = strmatch(argv[0]->text, "no"); DEBUG_MODE_SET(&nb_dbg_client_sysrepo, mode, !no); return CMD_SUCCESS; } static int frr_sr_debug_config_write(struct vty *vty) { if (DEBUG_MODE_CHECK(&nb_dbg_client_sysrepo, DEBUG_MODE_CONF)) vty_out(vty, "debug northbound client sysrepo\n"); return 0; } static int frr_sr_debug_set_all(uint32_t flags, bool set) { DEBUG_FLAGS_SET(&nb_dbg_client_sysrepo, flags, set); /* If all modes have been turned off, don't preserve options. */ if (!DEBUG_MODE_CHECK(&nb_dbg_client_sysrepo, DEBUG_MODE_ALL)) DEBUG_CLEAR(&nb_dbg_client_sysrepo); return 0; } static void frr_sr_cli_init(void) { hook_register(nb_client_debug_config_write, frr_sr_debug_config_write); hook_register(nb_client_debug_set_all, frr_sr_debug_set_all); install_element(ENABLE_NODE, &debug_nb_sr_cmd); install_element(CONFIG_NODE, &debug_nb_sr_cmd); } /* FRR's Sysrepo initialization. */ static int frr_sr_init(const char *program_name) { struct yang_module *module; int sysrepo_fd, ret; sysrepo_threads = list_new(); ret = sr_fd_watcher_init(&sysrepo_fd, NULL); if (ret != SR_ERR_OK) { flog_err(EC_LIB_SYSREPO_INIT, "%s: sr_fd_watcher_init(): %s", __func__, sr_strerror(ret)); goto cleanup; } /* Connect to Sysrepo. */ ret = sr_connect(program_name, SR_CONN_DEFAULT, &connection); if (ret != SR_ERR_OK) { flog_err(EC_LIB_SYSREPO_INIT, "%s: sr_connect(): %s", __func__, sr_strerror(ret)); goto cleanup; } /* Start session. */ ret = sr_session_start(connection, SR_DS_RUNNING, SR_SESS_DEFAULT, &session); if (ret != SR_ERR_OK) { flog_err(EC_LIB_SYSREPO_INIT, "%s: sr_session_start(): %s", __func__, sr_strerror(ret)); goto cleanup; } /* Perform subscriptions. */ RB_FOREACH (module, yang_modules, &yang_modules) { frr_sr_subscribe_config(module); yang_snodes_iterate_module(module->info, frr_sr_subscribe_state, 0, module); yang_snodes_iterate_module(module->info, frr_sr_subscribe_rpc, 0, module); yang_snodes_iterate_module(module->info, frr_sr_subscribe_action, 0, module); } hook_register(nb_notification_send, frr_sr_notification_send); frr_sr_fd_add(SR_FD_INPUT_READY, sysrepo_fd); return 0; cleanup: frr_sr_finish(); return -1; } static int frr_sr_finish(void) { struct yang_module *module; RB_FOREACH (module, yang_modules, &yang_modules) { if (!module->sr_subscription) continue; sr_unsubscribe(session, module->sr_subscription); } if (session) sr_session_stop(session); if (connection) sr_disconnect(connection); sysrepo_threads->del = (void (*)(void *))frr_sr_fd_free; list_delete(&sysrepo_threads); sr_fd_watcher_cleanup(); return 0; } static int frr_sr_module_late_init(struct thread_master *tm) { master = tm; if (frr_sr_init(frr_get_progname()) < 0) { flog_err(EC_LIB_SYSREPO_INIT, "failed to initialize the Sysrepo module"); return -1; } hook_register(frr_fini, frr_sr_finish); frr_sr_cli_init(); return 0; } static int frr_sr_module_init(void) { hook_register(frr_late_init, frr_sr_module_late_init); return 0; } FRR_MODULE_SETUP(.name = "frr_sysrepo", .version = FRR_VERSION, .description = "FRR sysrepo integration module", .init = frr_sr_module_init, ) frr-7.2.1/lib/ns.h0000644000000000000000000001106513610377563010556 00000000000000/* * NS related header. * Copyright (C) 2014 6WIND S.A. * * This file is part of GNU Zebra. * * GNU Zebra is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License as published * by the Free Software Foundation; either version 2, or (at your * option) any later version. * * GNU Zebra 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; see the file COPYING; if not, write to the Free Software * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA */ #ifndef _ZEBRA_NS_H #define _ZEBRA_NS_H #include "openbsd-tree.h" #include "linklist.h" #include "vty.h" #ifdef __cplusplus extern "C" { #endif typedef uint32_t ns_id_t; /* the default NS ID */ #define NS_UNKNOWN UINT32_MAX /* Default netns directory (Linux) */ #define NS_RUN_DIR "/var/run/netns" #ifdef HAVE_NETNS #define NS_DEFAULT_NAME "/proc/self/ns/net" #else /* !HAVE_NETNS */ #define NS_DEFAULT_NAME "default-netns" #endif /* HAVE_NETNS */ struct ns { RB_ENTRY(ns) entry; /* Identifier, same as the vector index */ ns_id_t ns_id; /* Identifier, mapped on the NSID value */ ns_id_t internal_ns_id; /* Name */ char *name; /* File descriptor */ int fd; /* Master list of interfaces belonging to this NS */ struct list *iflist; /* Back Pointer to VRF */ void *vrf_ctxt; /* User data */ void *info; }; RB_HEAD(ns_head, ns); RB_PROTOTYPE(ns_head, ns, entry, ns_compare) extern struct ns_head ns_tree; /* * API for managing NETNS. eg from zebra daemon * one want to manage the list of NETNS, etc... */ /* * NS hooks */ #define NS_NEW_HOOK 0 /* a new netns is just created */ #define NS_DELETE_HOOK 1 /* a netns is to be deleted */ #define NS_ENABLE_HOOK 2 /* a netns is ready to use */ #define NS_DISABLE_HOOK 3 /* a netns is to be unusable */ /* * Add a specific hook ns module. * @param1: hook type * @param2: the callback function * - param 1: the NS ID * - param 2: the address of the user data pointer (the user data * can be stored in or freed from there) */ extern void ns_add_hook(int type, int (*)(struct ns *)); /* * NS initializer/destructor */ extern void ns_terminate(void); /* API to initialize NETNS managerment * parameter is the default ns_id */ extern void ns_init_management(ns_id_t ns_id, ns_id_t internal_ns_idx); /* * NS utilities */ /* Create a socket serving for the given NS */ int ns_socket(int domain, int type, int protocol, ns_id_t ns_id); /* return the path of the NETNS */ extern char *ns_netns_pathname(struct vty *vty, const char *name); /* Parse and execute a function on all the NETNS */ extern void ns_walk_func(int (*func)(struct ns *)); /* API to get the NETNS name, from the ns pointer */ extern const char *ns_get_name(struct ns *ns); /* only called from vrf ( when removing netns from vrf) * or at VRF termination */ extern void ns_delete(struct ns *ns); /* return > 0 if netns is available * called by VRF to check netns backend is available for VRF */ extern int ns_have_netns(void); /* API to get context information of a NS */ extern void *ns_info_lookup(ns_id_t ns_id); /* API to map internal ns id value with * user friendly ns id external value */ extern ns_id_t ns_map_nsid_with_external(ns_id_t ns_id, bool map); /* * NS init routine * should be called from backendx */ extern void ns_init(void); /* API to retrieve default NS */ extern ns_id_t ns_get_default_id(void); #define NS_DEFAULT ns_get_default_id() /* API that can be used to change from NS */ extern int ns_switchback_to_initial(void); extern int ns_switch_to_netns(const char *netns_name); /* * NS handling routines. * called by modules that use NS backend */ /* API to search for already present NETNS */ extern struct ns *ns_lookup(ns_id_t ns_id); extern struct ns *ns_lookup_name(const char *name); /* API to handle NS : creation, enable, disable * for enable, a callback function is passed as parameter * the callback belongs to the module that uses NS as backend * upon enabling the NETNS, the upper layer is informed */ extern int ns_enable(struct ns *ns, void (*func)(ns_id_t, void *)); extern struct ns *ns_get_created(struct ns *ns, char *name, ns_id_t ns_id); extern void ns_disable(struct ns *ns); #ifdef __cplusplus } #endif #endif /*_ZEBRA_NS_H*/ frr-7.2.1/lib/ntop.c0000644000000000000000000001010513610377563011103 00000000000000/* * optimized ntop, about 10x faster than libc versions [as of 2019] * * Copyright (c) 2019 David Lamparter, for NetDEF, Inc. * * Permission to use, copy, modify, and distribute this software for any * purpose with or without fee is hereby granted, provided that the above * copyright notice and this permission notice appear in all copies. * * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. */ #ifdef HAVE_CONFIG_H #include "config.h" #endif #include #include #include #include #include #include #include #include "compiler.h" #define pos (*posx) static inline void putbyte(uint8_t bytex, char **posx) __attribute__((always_inline)) OPTIMIZE; static inline void putbyte(uint8_t bytex, char **posx) { bool zero = false; int byte = bytex, tmp, a, b; if ((tmp = byte - 200) >= 0) { *pos++ = '2'; zero = true; byte = tmp; } else if ((tmp = byte - 100) >= 0) { *pos++ = '1'; zero = true; byte = tmp; } /* make sure the compiler knows the value range of "byte" */ assume(byte < 100 && byte >= 0); b = byte % 10; a = byte / 10; if (a || zero) { *pos++ = '0' + a; *pos++ = '0' + b; } else *pos++ = '0' + b; } static inline void puthex(uint16_t word, char **posx) __attribute__((always_inline)) OPTIMIZE; static inline void puthex(uint16_t word, char **posx) { const char *digits = "0123456789abcdef"; if (word >= 0x1000) *pos++ = digits[(word >> 12) & 0xf]; if (word >= 0x100) *pos++ = digits[(word >> 8) & 0xf]; if (word >= 0x10) *pos++ = digits[(word >> 4) & 0xf]; *pos++ = digits[word & 0xf]; } #undef pos const char *frr_inet_ntop(int af, const void * restrict src, char * restrict dst, socklen_t size) __attribute__((flatten)) OPTIMIZE; const char *frr_inet_ntop(int af, const void * restrict src, char * restrict dst, socklen_t size) { const uint8_t *b = src; /* 8 * "abcd:" for IPv6 * note: the IPv4-embedded IPv6 syntax is only used for ::A.B.C.D, * which isn't longer than 40 chars either. even with ::ffff:A.B.C.D * it's shorter. */ char buf[8 * 5], *o = buf; size_t best = 0, bestlen = 0, curlen = 0, i; switch (af) { case AF_INET: inet4: putbyte(b[0], &o); *o++ = '.'; putbyte(b[1], &o); *o++ = '.'; putbyte(b[2], &o); *o++ = '.'; putbyte(b[3], &o); *o++ = '\0'; break; case AF_INET6: for (i = 0; i < 8; i++) { if (b[i * 2] || b[i * 2 + 1]) { if (curlen && curlen > bestlen) { best = i - curlen; bestlen = curlen; } curlen = 0; continue; } curlen++; } if (curlen && curlen > bestlen) { best = i - curlen; bestlen = curlen; } /* do we want ::ffff:A.B.C.D? */ if (best == 0 && bestlen == 6) { *o++ = ':'; *o++ = ':'; b += 12; goto inet4; } if (bestlen == 1) bestlen = 0; for (i = 0; i < 8; i++) { if (bestlen && i == best) { if (i == 0) *o++ = ':'; *o++ = ':'; continue; } if (i > best && i < best + bestlen) { continue; } puthex((b[i * 2] << 8) | b[i * 2 + 1], &o); if (i < 7) *o++ = ':'; } *o++ = '\0'; break; default: return NULL; } i = o - buf; if (i > size) return NULL; /* compiler might inline memcpy if it knows the length is short, * although neither gcc nor clang actually do this currently [2019] */ assume(i <= 8 * 5); memcpy(dst, buf, i); return dst; } #ifndef INET_NTOP_NO_OVERRIDE /* we want to override libc inet_ntop, but make sure it shows up in backtraces * as frr_inet_ntop (to avoid confusion while debugging) */ const char *inet_ntop(int af, const void *src, char *dst, socklen_t size) __attribute__((alias ("frr_inet_ntop"))); #endif frr-7.2.1/lib/openbsd-queue.h0000644000000000000000000006613513610377563012722 00000000000000/* $OpenBSD: queue.h,v 1.43 2015/12/28 19:38:40 millert Exp $ */ /* $NetBSD: queue.h,v 1.11 1996/05/16 05:17:14 mycroft Exp $ */ /* * Copyright (c) 1991, 1993 * The Regents of the University of California. All rights reserved. * * Redistribution and use in source and binary forms, with or without * modification, are permitted provided that the following conditions * are met: * 1. Redistributions of source code must retain the above copyright * notice, this list of conditions and the following disclaimer. * 2. Redistributions in binary form must reproduce the above copyright * notice, this list of conditions and the following disclaimer in the * documentation and/or other materials provided with the distribution. * 3. Neither the name of the University nor the names of its contributors * may be used to endorse or promote products derived from this software * without specific prior written permission. * * THIS SOFTWARE IS PROVIDED BY THE REGENTS AND CONTRIBUTORS ``AS IS'' AND * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE * ARE DISCLAIMED. IN NO EVENT SHALL THE REGENTS OR CONTRIBUTORS BE LIABLE * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF * SUCH DAMAGE. * * @(#)queue.h 8.5 (Berkeley) 8/20/94 */ #ifndef _SYS_QUEUE_H_ #define _SYS_QUEUE_H_ #ifdef __cplusplus extern "C" { #endif /* * This file defines five types of data structures: singly-linked lists, * lists, simple queues, tail queues and XOR simple queues. * * * A singly-linked list is headed by a single forward pointer. The elements * are singly linked for minimum space and pointer manipulation overhead at * the expense of O(n) removal for arbitrary elements. New elements can be * added to the list after an existing element or at the head of the list. * Elements being removed from the head of the list should use the explicit * macro for this purpose for optimum efficiency. A singly-linked list may * only be traversed in the forward direction. Singly-linked lists are ideal * for applications with large datasets and few or no removals or for * implementing a LIFO queue. * * A list is headed by a single forward pointer (or an array of forward * pointers for a hash table header). The elements are doubly linked * so that an arbitrary element can be removed without a need to * traverse the list. New elements can be added to the list before * or after an existing element or at the head of the list. A list * may only be traversed in the forward direction. * * A simple queue is headed by a pair of pointers, one to the head of the * list and the other to the tail of the list. The elements are singly * linked to save space, so elements can only be removed from the * head of the list. New elements can be added to the list before or after * an existing element, at the head of the list, or at the end of the * list. A simple queue may only be traversed in the forward direction. * * A tail queue is headed by a pair of pointers, one to the head of the * list and the other to the tail of the list. The elements are doubly * linked so that an arbitrary element can be removed without a need to * traverse the list. New elements can be added to the list before or * after an existing element, at the head of the list, or at the end of * the list. A tail queue may be traversed in either direction. * * An XOR simple queue is used in the same way as a regular simple queue. * The difference is that the head structure also includes a "cookie" that * is XOR'd with the queue pointer (first, last or next) to generate the * real pointer value. * * For details on the use of these macros, see the queue(3) manual page. */ #if defined(QUEUE_MACRO_DEBUG) || (defined(_KERNEL) && defined(DIAGNOSTIC)) #define _Q_INVALIDATE(a) (a) = ((void *)-1) #else #define _Q_INVALIDATE(a) #endif /* * Singly-linked List definitions. */ #define SLIST_HEAD(name, type) \ struct name { \ struct type *slh_first; /* first element */ \ } #define SLIST_HEAD_INITIALIZER(head) \ { \ NULL \ } #define SLIST_ENTRY(type) \ struct { \ struct type *sle_next; /* next element */ \ } /* * Singly-linked List access methods. */ #define SLIST_FIRST(head) ((head)->slh_first) #define SLIST_END(head) NULL #define SLIST_EMPTY(head) (SLIST_FIRST(head) == SLIST_END(head)) #define SLIST_NEXT(elm, field) ((elm)->field.sle_next) #define SLIST_FOREACH(var, head, field) \ for ((var) = SLIST_FIRST(head); (var) != SLIST_END(head); \ (var) = SLIST_NEXT(var, field)) #define SLIST_FOREACH_SAFE(var, head, field, tvar) \ for ((var) = SLIST_FIRST(head); \ (var) && ((tvar) = SLIST_NEXT(var, field), 1); (var) = (tvar)) /* * Singly-linked List functions. */ #define SLIST_INIT(head) \ { \ SLIST_FIRST(head) = SLIST_END(head); \ } #define SLIST_INSERT_AFTER(slistelm, elm, field) \ do { \ (elm)->field.sle_next = (slistelm)->field.sle_next; \ (slistelm)->field.sle_next = (elm); \ } while (0) #define SLIST_INSERT_HEAD(head, elm, field) \ do { \ (elm)->field.sle_next = (head)->slh_first; \ (head)->slh_first = (elm); \ } while (0) #define SLIST_REMOVE_AFTER(elm, field) \ do { \ (elm)->field.sle_next = (elm)->field.sle_next->field.sle_next; \ } while (0) #define SLIST_REMOVE_HEAD(head, field) \ do { \ (head)->slh_first = (head)->slh_first->field.sle_next; \ } while (0) #define SLIST_REMOVE(head, elm, type, field) \ do { \ if ((head)->slh_first == (elm)) { \ SLIST_REMOVE_HEAD((head), field); \ } else { \ struct type *curelm = (head)->slh_first; \ \ while (curelm->field.sle_next != (elm)) \ curelm = curelm->field.sle_next; \ curelm->field.sle_next = \ curelm->field.sle_next->field.sle_next; \ } \ _Q_INVALIDATE((elm)->field.sle_next); \ } while (0) /* * List definitions. */ #define LIST_HEAD(name, type) \ struct name { \ struct type *lh_first; /* first element */ \ } #define LIST_HEAD_INITIALIZER(head) \ { \ NULL \ } #define LIST_ENTRY(type) \ struct { \ struct type *le_next; /* next element */ \ struct type **le_prev; /* address of previous next element */ \ } /* * List access methods. */ #define LIST_FIRST(head) ((head)->lh_first) #define LIST_END(head) NULL #define LIST_EMPTY(head) (LIST_FIRST(head) == LIST_END(head)) #define LIST_NEXT(elm, field) ((elm)->field.le_next) #define LIST_FOREACH(var, head, field) \ for ((var) = LIST_FIRST(head); (var) != LIST_END(head); \ (var) = LIST_NEXT(var, field)) #define LIST_FOREACH_SAFE(var, head, field, tvar) \ for ((var) = LIST_FIRST(head); \ (var) && ((tvar) = LIST_NEXT(var, field), 1); (var) = (tvar)) /* * List functions. */ #define LIST_INIT(head) \ do { \ LIST_FIRST(head) = LIST_END(head); \ } while (0) #define LIST_INSERT_AFTER(listelm, elm, field) \ do { \ if (((elm)->field.le_next = (listelm)->field.le_next) != NULL) \ (listelm)->field.le_next->field.le_prev = \ &(elm)->field.le_next; \ (listelm)->field.le_next = (elm); \ (elm)->field.le_prev = &(listelm)->field.le_next; \ } while (0) #define LIST_INSERT_BEFORE(listelm, elm, field) \ do { \ (elm)->field.le_prev = (listelm)->field.le_prev; \ (elm)->field.le_next = (listelm); \ *(listelm)->field.le_prev = (elm); \ (listelm)->field.le_prev = &(elm)->field.le_next; \ } while (0) #define LIST_INSERT_HEAD(head, elm, field) \ do { \ if (((elm)->field.le_next = (head)->lh_first) != NULL) \ (head)->lh_first->field.le_prev = \ &(elm)->field.le_next; \ (head)->lh_first = (elm); \ (elm)->field.le_prev = &(head)->lh_first; \ } while (0) #define LIST_REMOVE(elm, field) \ do { \ if ((elm)->field.le_next != NULL) \ (elm)->field.le_next->field.le_prev = \ (elm)->field.le_prev; \ *(elm)->field.le_prev = (elm)->field.le_next; \ _Q_INVALIDATE((elm)->field.le_prev); \ _Q_INVALIDATE((elm)->field.le_next); \ } while (0) #define LIST_REPLACE(elm, elm2, field) \ do { \ if (((elm2)->field.le_next = (elm)->field.le_next) != NULL) \ (elm2)->field.le_next->field.le_prev = \ &(elm2)->field.le_next; \ (elm2)->field.le_prev = (elm)->field.le_prev; \ *(elm2)->field.le_prev = (elm2); \ _Q_INVALIDATE((elm)->field.le_prev); \ _Q_INVALIDATE((elm)->field.le_next); \ } while (0) /* * Simple queue definitions. */ #define SIMPLEQ_HEAD(name, type) \ struct name { \ struct type *sqh_first; /* first element */ \ struct type **sqh_last; /* addr of last next element */ \ } #define SIMPLEQ_HEAD_INITIALIZER(head) \ { \ NULL, &(head).sqh_first \ } #define SIMPLEQ_ENTRY(type) \ struct { \ struct type *sqe_next; /* next element */ \ } /* * Simple queue access methods. */ #define SIMPLEQ_FIRST(head) ((head)->sqh_first) #define SIMPLEQ_END(head) NULL #define SIMPLEQ_EMPTY(head) (SIMPLEQ_FIRST(head) == SIMPLEQ_END(head)) #define SIMPLEQ_NEXT(elm, field) ((elm)->field.sqe_next) #define SIMPLEQ_FOREACH(var, head, field) \ for ((var) = SIMPLEQ_FIRST(head); (var) != SIMPLEQ_END(head); \ (var) = SIMPLEQ_NEXT(var, field)) #define SIMPLEQ_FOREACH_SAFE(var, head, field, tvar) \ for ((var) = SIMPLEQ_FIRST(head); \ (var) && ((tvar) = SIMPLEQ_NEXT(var, field), 1); (var) = (tvar)) /* * Simple queue functions. */ #define SIMPLEQ_INIT(head) \ do { \ (head)->sqh_first = NULL; \ (head)->sqh_last = &(head)->sqh_first; \ } while (0) #define SIMPLEQ_INSERT_HEAD(head, elm, field) \ do { \ if (((elm)->field.sqe_next = (head)->sqh_first) == NULL) \ (head)->sqh_last = &(elm)->field.sqe_next; \ (head)->sqh_first = (elm); \ } while (0) #define SIMPLEQ_INSERT_TAIL(head, elm, field) \ do { \ (elm)->field.sqe_next = NULL; \ *(head)->sqh_last = (elm); \ (head)->sqh_last = &(elm)->field.sqe_next; \ } while (0) #define SIMPLEQ_INSERT_AFTER(head, listelm, elm, field) \ do { \ if (((elm)->field.sqe_next = (listelm)->field.sqe_next) \ == NULL) \ (head)->sqh_last = &(elm)->field.sqe_next; \ (listelm)->field.sqe_next = (elm); \ } while (0) #define SIMPLEQ_REMOVE_HEAD(head, field) \ do { \ if (((head)->sqh_first = (head)->sqh_first->field.sqe_next) \ == NULL) \ (head)->sqh_last = &(head)->sqh_first; \ } while (0) #define SIMPLEQ_REMOVE_AFTER(head, elm, field) \ do { \ if (((elm)->field.sqe_next = \ (elm)->field.sqe_next->field.sqe_next) \ == NULL) \ (head)->sqh_last = &(elm)->field.sqe_next; \ } while (0) #define SIMPLEQ_CONCAT(head1, head2) \ do { \ if (!SIMPLEQ_EMPTY((head2))) { \ *(head1)->sqh_last = (head2)->sqh_first; \ (head1)->sqh_last = (head2)->sqh_last; \ SIMPLEQ_INIT((head2)); \ } \ } while (0) /* * XOR Simple queue definitions. */ #define XSIMPLEQ_HEAD(name, type) \ struct name { \ struct type *sqx_first; /* first element */ \ struct type **sqx_last; /* addr of last next element */ \ unsigned long sqx_cookie; \ } #define XSIMPLEQ_ENTRY(type) \ struct { \ struct type *sqx_next; /* next element */ \ } /* * XOR Simple queue access methods. */ #define XSIMPLEQ_XOR(head, ptr) \ ((__typeof(ptr))((head)->sqx_cookie ^ (unsigned long)(ptr))) #define XSIMPLEQ_FIRST(head) XSIMPLEQ_XOR(head, ((head)->sqx_first)) #define XSIMPLEQ_END(head) NULL #define XSIMPLEQ_EMPTY(head) (XSIMPLEQ_FIRST(head) == XSIMPLEQ_END(head)) #define XSIMPLEQ_NEXT(head, elm, field) XSIMPLEQ_XOR(head, ((elm)->field.sqx_next)) #define XSIMPLEQ_FOREACH(var, head, field) \ for ((var) = XSIMPLEQ_FIRST(head); (var) != XSIMPLEQ_END(head); \ (var) = XSIMPLEQ_NEXT(head, var, field)) #define XSIMPLEQ_FOREACH_SAFE(var, head, field, tvar) \ for ((var) = XSIMPLEQ_FIRST(head); \ (var) && ((tvar) = XSIMPLEQ_NEXT(head, var, field), 1); \ (var) = (tvar)) /* * XOR Simple queue functions. */ #define XSIMPLEQ_INIT(head) \ do { \ arc4random_buf(&(head)->sqx_cookie, \ sizeof((head)->sqx_cookie)); \ (head)->sqx_first = XSIMPLEQ_XOR(head, NULL); \ (head)->sqx_last = XSIMPLEQ_XOR(head, &(head)->sqx_first); \ } while (0) #define XSIMPLEQ_INSERT_HEAD(head, elm, field) \ do { \ if (((elm)->field.sqx_next = (head)->sqx_first) \ == XSIMPLEQ_XOR(head, NULL)) \ (head)->sqx_last = \ XSIMPLEQ_XOR(head, &(elm)->field.sqx_next); \ (head)->sqx_first = XSIMPLEQ_XOR(head, (elm)); \ } while (0) #define XSIMPLEQ_INSERT_TAIL(head, elm, field) \ do { \ (elm)->field.sqx_next = XSIMPLEQ_XOR(head, NULL); \ *(XSIMPLEQ_XOR(head, (head)->sqx_last)) = \ XSIMPLEQ_XOR(head, (elm)); \ (head)->sqx_last = XSIMPLEQ_XOR(head, &(elm)->field.sqx_next); \ } while (0) #define XSIMPLEQ_INSERT_AFTER(head, listelm, elm, field) \ do { \ if (((elm)->field.sqx_next = (listelm)->field.sqx_next) \ == XSIMPLEQ_XOR(head, NULL)) \ (head)->sqx_last = \ XSIMPLEQ_XOR(head, &(elm)->field.sqx_next); \ (listelm)->field.sqx_next = XSIMPLEQ_XOR(head, (elm)); \ } while (0) #define XSIMPLEQ_REMOVE_HEAD(head, field) \ do { \ if (((head)->sqx_first = XSIMPLEQ_XOR(head, (head)->sqx_first) \ ->field.sqx_next) \ == XSIMPLEQ_XOR(head, NULL)) \ (head)->sqx_last = \ XSIMPLEQ_XOR(head, &(head)->sqx_first); \ } while (0) #define XSIMPLEQ_REMOVE_AFTER(head, elm, field) \ do { \ if (((elm)->field.sqx_next = \ XSIMPLEQ_XOR(head, (elm)->field.sqx_next) \ ->field.sqx_next) \ == XSIMPLEQ_XOR(head, NULL)) \ (head)->sqx_last = \ XSIMPLEQ_XOR(head, &(elm)->field.sqx_next); \ } while (0) /* * Tail queue definitions. */ #define TAILQ_HEAD(name, type) \ struct name { \ struct type *tqh_first; /* first element */ \ struct type **tqh_last; /* addr of last next element */ \ } #define TAILQ_HEAD_INITIALIZER(head) \ { \ NULL, &(head).tqh_first \ } #define TAILQ_ENTRY(type) \ struct { \ struct type *tqe_next; /* next element */ \ struct type **tqe_prev; /* address of previous next element */ \ } /* * Tail queue access methods. */ #define TAILQ_FIRST(head) ((head)->tqh_first) #define TAILQ_END(head) NULL #define TAILQ_NEXT(elm, field) ((elm)->field.tqe_next) #define TAILQ_LAST(head, headname) \ (*(((struct headname *)((head)->tqh_last))->tqh_last)) /* XXX */ #define TAILQ_PREV(elm, headname, field) \ (*(((struct headname *)((elm)->field.tqe_prev))->tqh_last)) #define TAILQ_EMPTY(head) (TAILQ_FIRST(head) == TAILQ_END(head)) #define TAILQ_FOREACH(var, head, field) \ for ((var) = TAILQ_FIRST(head); (var) != TAILQ_END(head); \ (var) = TAILQ_NEXT(var, field)) #define TAILQ_FOREACH_SAFE(var, head, field, tvar) \ for ((var) = TAILQ_FIRST(head); \ (var) != TAILQ_END(head) && ((tvar) = TAILQ_NEXT(var, field), 1); \ (var) = (tvar)) #define TAILQ_FOREACH_REVERSE(var, head, headname, field) \ for ((var) = TAILQ_LAST(head, headname); (var) != TAILQ_END(head); \ (var) = TAILQ_PREV(var, headname, field)) #define TAILQ_FOREACH_REVERSE_SAFE(var, head, headname, field, tvar) \ for ((var) = TAILQ_LAST(head, headname); \ (var) != TAILQ_END(head) \ && ((tvar) = TAILQ_PREV(var, headname, field), 1); \ (var) = (tvar)) /* * Tail queue functions. */ #define TAILQ_INIT(head) \ do { \ (head)->tqh_first = NULL; \ (head)->tqh_last = &(head)->tqh_first; \ } while (0) #define TAILQ_INSERT_HEAD(head, elm, field) \ do { \ if (((elm)->field.tqe_next = (head)->tqh_first) != NULL) \ (head)->tqh_first->field.tqe_prev = \ &(elm)->field.tqe_next; \ else \ (head)->tqh_last = &(elm)->field.tqe_next; \ (head)->tqh_first = (elm); \ (elm)->field.tqe_prev = &(head)->tqh_first; \ } while (0) #define TAILQ_INSERT_TAIL(head, elm, field) \ do { \ (elm)->field.tqe_next = NULL; \ (elm)->field.tqe_prev = (head)->tqh_last; \ *(head)->tqh_last = (elm); \ (head)->tqh_last = &(elm)->field.tqe_next; \ } while (0) #define TAILQ_INSERT_AFTER(head, listelm, elm, field) \ do { \ if (((elm)->field.tqe_next = (listelm)->field.tqe_next) \ != NULL) \ (elm)->field.tqe_next->field.tqe_prev = \ &(elm)->field.tqe_next; \ else \ (head)->tqh_last = &(elm)->field.tqe_next; \ (listelm)->field.tqe_next = (elm); \ (elm)->field.tqe_prev = &(listelm)->field.tqe_next; \ } while (0) #define TAILQ_INSERT_BEFORE(listelm, elm, field) \ do { \ (elm)->field.tqe_prev = (listelm)->field.tqe_prev; \ (elm)->field.tqe_next = (listelm); \ *(listelm)->field.tqe_prev = (elm); \ (listelm)->field.tqe_prev = &(elm)->field.tqe_next; \ } while (0) #define TAILQ_REMOVE(head, elm, field) \ do { \ if (((elm)->field.tqe_next) != NULL) \ (elm)->field.tqe_next->field.tqe_prev = \ (elm)->field.tqe_prev; \ else \ (head)->tqh_last = (elm)->field.tqe_prev; \ *(elm)->field.tqe_prev = (elm)->field.tqe_next; \ _Q_INVALIDATE((elm)->field.tqe_prev); \ _Q_INVALIDATE((elm)->field.tqe_next); \ } while (0) #define TAILQ_REPLACE(head, elm, elm2, field) \ do { \ if (((elm2)->field.tqe_next = (elm)->field.tqe_next) != NULL) \ (elm2)->field.tqe_next->field.tqe_prev = \ &(elm2)->field.tqe_next; \ else \ (head)->tqh_last = &(elm2)->field.tqe_next; \ (elm2)->field.tqe_prev = (elm)->field.tqe_prev; \ *(elm2)->field.tqe_prev = (elm2); \ _Q_INVALIDATE((elm)->field.tqe_prev); \ _Q_INVALIDATE((elm)->field.tqe_next); \ } while (0) #define TAILQ_CONCAT(head1, head2, field) \ do { \ if (!TAILQ_EMPTY(head2)) { \ *(head1)->tqh_last = (head2)->tqh_first; \ (head2)->tqh_first->field.tqe_prev = \ (head1)->tqh_last; \ (head1)->tqh_last = (head2)->tqh_last; \ TAILQ_INIT((head2)); \ } \ } while (0) #ifdef __cplusplus } #endif #endif /* !_SYS_QUEUE_H_ */ frr-7.2.1/lib/openbsd-tree.c0000644000000000000000000003512613610377563012524 00000000000000/* $OpenBSD: subr_tree.c,v 1.9 2017/06/08 03:30:52 dlg Exp $ */ /* * Copyright 2002 Niels Provos * All rights reserved. * * Redistribution and use in source and binary forms, with or without * modification, are permitted provided that the following conditions * are met: * 1. Redistributions of source code must retain the above copyright * notice, this list of conditions and the following disclaimer. * 2. Redistributions in binary form must reproduce the above copyright * notice, this list of conditions and the following disclaimer in the * documentation and/or other materials provided with the distribution. * * THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR * IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES * OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. * IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT, * INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT * NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF * THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. */ /* * Copyright (c) 2016 David Gwynne * * Permission to use, copy, modify, and distribute this software for any * purpose with or without fee is hereby granted, provided that the above * copyright notice and this permission notice appear in all copies. * * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. */ #ifdef HAVE_CONFIG_H #include "config.h" #endif #include #include static inline struct rb_entry *rb_n2e(const struct rb_type *t, void *node) { unsigned long addr = (unsigned long)node; return ((struct rb_entry *)(addr + t->t_offset)); } static inline void *rb_e2n(const struct rb_type *t, struct rb_entry *rbe) { unsigned long addr = (unsigned long)rbe; return ((void *)(addr - t->t_offset)); } #define RBE_LEFT(_rbe) (_rbe)->rbt_left #define RBE_RIGHT(_rbe) (_rbe)->rbt_right #define RBE_PARENT(_rbe) (_rbe)->rbt_parent #define RBE_COLOR(_rbe) (_rbe)->rbt_color #define RBH_ROOT(_rbt) (_rbt)->rbt_root static inline void rbe_set(struct rb_entry *rbe, struct rb_entry *parent) { RBE_PARENT(rbe) = parent; RBE_LEFT(rbe) = RBE_RIGHT(rbe) = NULL; RBE_COLOR(rbe) = RB_RED; } static inline void rbe_set_blackred(struct rb_entry *black, struct rb_entry *red) { RBE_COLOR(black) = RB_BLACK; RBE_COLOR(red) = RB_RED; } static inline void rbe_augment(const struct rb_type *t, struct rb_entry *rbe) { (*t->t_augment)(rb_e2n(t, rbe)); } static inline void rbe_if_augment(const struct rb_type *t, struct rb_entry *rbe) { if (t->t_augment != NULL) rbe_augment(t, rbe); } static inline void rbe_rotate_left(const struct rb_type *t, struct rbt_tree *rbt, struct rb_entry *rbe) { struct rb_entry *parent; struct rb_entry *tmp; tmp = RBE_RIGHT(rbe); RBE_RIGHT(rbe) = RBE_LEFT(tmp); if (RBE_RIGHT(rbe) != NULL) RBE_PARENT(RBE_LEFT(tmp)) = rbe; parent = RBE_PARENT(rbe); RBE_PARENT(tmp) = parent; if (parent != NULL) { if (rbe == RBE_LEFT(parent)) RBE_LEFT(parent) = tmp; else RBE_RIGHT(parent) = tmp; } else RBH_ROOT(rbt) = tmp; RBE_LEFT(tmp) = rbe; RBE_PARENT(rbe) = tmp; if (t->t_augment != NULL) { rbe_augment(t, rbe); rbe_augment(t, tmp); parent = RBE_PARENT(tmp); if (parent != NULL) rbe_augment(t, parent); } } static inline void rbe_rotate_right(const struct rb_type *t, struct rbt_tree *rbt, struct rb_entry *rbe) { struct rb_entry *parent; struct rb_entry *tmp; tmp = RBE_LEFT(rbe); RBE_LEFT(rbe) = RBE_RIGHT(tmp); if (RBE_LEFT(rbe) != NULL) RBE_PARENT(RBE_RIGHT(tmp)) = rbe; parent = RBE_PARENT(rbe); RBE_PARENT(tmp) = parent; if (parent != NULL) { if (rbe == RBE_LEFT(parent)) RBE_LEFT(parent) = tmp; else RBE_RIGHT(parent) = tmp; } else RBH_ROOT(rbt) = tmp; RBE_RIGHT(tmp) = rbe; RBE_PARENT(rbe) = tmp; if (t->t_augment != NULL) { rbe_augment(t, rbe); rbe_augment(t, tmp); parent = RBE_PARENT(tmp); if (parent != NULL) rbe_augment(t, parent); } } static inline void rbe_insert_color(const struct rb_type *t, struct rbt_tree *rbt, struct rb_entry *rbe) { struct rb_entry *parent, *gparent, *tmp; while ((parent = RBE_PARENT(rbe)) != NULL && RBE_COLOR(parent) == RB_RED) { gparent = RBE_PARENT(parent); if (parent == RBE_LEFT(gparent)) { tmp = RBE_RIGHT(gparent); if (tmp != NULL && RBE_COLOR(tmp) == RB_RED) { RBE_COLOR(tmp) = RB_BLACK; rbe_set_blackred(parent, gparent); rbe = gparent; continue; } if (RBE_RIGHT(parent) == rbe) { rbe_rotate_left(t, rbt, parent); tmp = parent; parent = rbe; rbe = tmp; } rbe_set_blackred(parent, gparent); rbe_rotate_right(t, rbt, gparent); } else { tmp = RBE_LEFT(gparent); if (tmp != NULL && RBE_COLOR(tmp) == RB_RED) { RBE_COLOR(tmp) = RB_BLACK; rbe_set_blackred(parent, gparent); rbe = gparent; continue; } if (RBE_LEFT(parent) == rbe) { rbe_rotate_right(t, rbt, parent); tmp = parent; parent = rbe; rbe = tmp; } rbe_set_blackred(parent, gparent); rbe_rotate_left(t, rbt, gparent); } } RBE_COLOR(RBH_ROOT(rbt)) = RB_BLACK; } static inline void rbe_remove_color(const struct rb_type *t, struct rbt_tree *rbt, struct rb_entry *parent, struct rb_entry *rbe) { struct rb_entry *tmp; while ((rbe == NULL || RBE_COLOR(rbe) == RB_BLACK) && rbe != RBH_ROOT(rbt) && parent) { if (RBE_LEFT(parent) == rbe) { tmp = RBE_RIGHT(parent); if (RBE_COLOR(tmp) == RB_RED) { rbe_set_blackred(tmp, parent); rbe_rotate_left(t, rbt, parent); tmp = RBE_RIGHT(parent); } if ((RBE_LEFT(tmp) == NULL || RBE_COLOR(RBE_LEFT(tmp)) == RB_BLACK) && (RBE_RIGHT(tmp) == NULL || RBE_COLOR(RBE_RIGHT(tmp)) == RB_BLACK)) { RBE_COLOR(tmp) = RB_RED; rbe = parent; parent = RBE_PARENT(rbe); } else { if (RBE_RIGHT(tmp) == NULL || RBE_COLOR(RBE_RIGHT(tmp)) == RB_BLACK) { struct rb_entry *oleft; oleft = RBE_LEFT(tmp); if (oleft != NULL) RBE_COLOR(oleft) = RB_BLACK; RBE_COLOR(tmp) = RB_RED; rbe_rotate_right(t, rbt, tmp); tmp = RBE_RIGHT(parent); } RBE_COLOR(tmp) = RBE_COLOR(parent); RBE_COLOR(parent) = RB_BLACK; if (RBE_RIGHT(tmp)) RBE_COLOR(RBE_RIGHT(tmp)) = RB_BLACK; rbe_rotate_left(t, rbt, parent); rbe = RBH_ROOT(rbt); break; } } else { tmp = RBE_LEFT(parent); if (RBE_COLOR(tmp) == RB_RED) { rbe_set_blackred(tmp, parent); rbe_rotate_right(t, rbt, parent); tmp = RBE_LEFT(parent); } if ((RBE_LEFT(tmp) == NULL || RBE_COLOR(RBE_LEFT(tmp)) == RB_BLACK) && (RBE_RIGHT(tmp) == NULL || RBE_COLOR(RBE_RIGHT(tmp)) == RB_BLACK)) { RBE_COLOR(tmp) = RB_RED; rbe = parent; parent = RBE_PARENT(rbe); } else { if (RBE_LEFT(tmp) == NULL || RBE_COLOR(RBE_LEFT(tmp)) == RB_BLACK) { struct rb_entry *oright; oright = RBE_RIGHT(tmp); if (oright != NULL) RBE_COLOR(oright) = RB_BLACK; RBE_COLOR(tmp) = RB_RED; rbe_rotate_left(t, rbt, tmp); tmp = RBE_LEFT(parent); } RBE_COLOR(tmp) = RBE_COLOR(parent); RBE_COLOR(parent) = RB_BLACK; if (RBE_LEFT(tmp) != NULL) RBE_COLOR(RBE_LEFT(tmp)) = RB_BLACK; rbe_rotate_right(t, rbt, parent); rbe = RBH_ROOT(rbt); break; } } } if (rbe != NULL) RBE_COLOR(rbe) = RB_BLACK; } static inline struct rb_entry * rbe_remove(const struct rb_type *t, struct rbt_tree *rbt, struct rb_entry *rbe) { struct rb_entry *child, *parent, *old = rbe; unsigned int color; if (RBE_LEFT(rbe) == NULL) child = RBE_RIGHT(rbe); else if (RBE_RIGHT(rbe) == NULL) child = RBE_LEFT(rbe); else { struct rb_entry *tmp; rbe = RBE_RIGHT(rbe); while ((tmp = RBE_LEFT(rbe)) != NULL) rbe = tmp; child = RBE_RIGHT(rbe); parent = RBE_PARENT(rbe); color = RBE_COLOR(rbe); if (child != NULL) RBE_PARENT(child) = parent; if (parent != NULL) { if (RBE_LEFT(parent) == rbe) RBE_LEFT(parent) = child; else RBE_RIGHT(parent) = child; rbe_if_augment(t, parent); } else RBH_ROOT(rbt) = child; if (RBE_PARENT(rbe) == old) parent = rbe; *rbe = *old; tmp = RBE_PARENT(old); if (tmp != NULL) { if (RBE_LEFT(tmp) == old) RBE_LEFT(tmp) = rbe; else RBE_RIGHT(tmp) = rbe; rbe_if_augment(t, tmp); } else RBH_ROOT(rbt) = rbe; RBE_PARENT(RBE_LEFT(old)) = rbe; if (RBE_RIGHT(old)) RBE_PARENT(RBE_RIGHT(old)) = rbe; if (t->t_augment != NULL && parent != NULL) { tmp = parent; do { rbe_augment(t, tmp); tmp = RBE_PARENT(tmp); } while (tmp != NULL); } goto color; } parent = RBE_PARENT(rbe); color = RBE_COLOR(rbe); if (child != NULL) RBE_PARENT(child) = parent; if (parent != NULL) { if (RBE_LEFT(parent) == rbe) RBE_LEFT(parent) = child; else RBE_RIGHT(parent) = child; rbe_if_augment(t, parent); } else RBH_ROOT(rbt) = child; color: if (color == RB_BLACK) rbe_remove_color(t, rbt, parent, child); return (old); } void *_rb_remove(const struct rb_type *t, struct rbt_tree *rbt, void *elm) { struct rb_entry *rbe = rb_n2e(t, elm); struct rb_entry *old; old = rbe_remove(t, rbt, rbe); return (old == NULL ? NULL : rb_e2n(t, old)); } void *_rb_insert(const struct rb_type *t, struct rbt_tree *rbt, void *elm) { struct rb_entry *rbe = rb_n2e(t, elm); struct rb_entry *tmp; struct rb_entry *parent = NULL; void *node; int comp = 0; tmp = RBH_ROOT(rbt); while (tmp != NULL) { parent = tmp; node = rb_e2n(t, tmp); comp = (*t->t_compare)(elm, node); if (comp < 0) tmp = RBE_LEFT(tmp); else if (comp > 0) tmp = RBE_RIGHT(tmp); else return (node); } rbe_set(rbe, parent); if (parent != NULL) { if (comp < 0) RBE_LEFT(parent) = rbe; else RBE_RIGHT(parent) = rbe; rbe_if_augment(t, parent); } else RBH_ROOT(rbt) = rbe; rbe_insert_color(t, rbt, rbe); return (NULL); } /* Finds the node with the same key as elm */ void *_rb_find(const struct rb_type *t, const struct rbt_tree *rbt, const void *key) { struct rb_entry *tmp = RBH_ROOT(rbt); void *node; int comp; while (tmp != NULL) { node = rb_e2n(t, tmp); comp = (*t->t_compare)(key, node); if (comp < 0) tmp = RBE_LEFT(tmp); else if (comp > 0) tmp = RBE_RIGHT(tmp); else return (node); } return (NULL); } /* Finds the first node greater than or equal to the search key */ void *_rb_nfind(const struct rb_type *t, const struct rbt_tree *rbt, const void *key) { struct rb_entry *tmp = RBH_ROOT(rbt); void *node; void *res = NULL; int comp; while (tmp != NULL) { node = rb_e2n(t, tmp); comp = (*t->t_compare)(key, node); if (comp < 0) { res = node; tmp = RBE_LEFT(tmp); } else if (comp > 0) tmp = RBE_RIGHT(tmp); else return (node); } return (res); } void *_rb_next(const struct rb_type *t, void *elm) { struct rb_entry *rbe = rb_n2e(t, elm); if (RBE_RIGHT(rbe) != NULL) { rbe = RBE_RIGHT(rbe); while (RBE_LEFT(rbe) != NULL) rbe = RBE_LEFT(rbe); } else { if (RBE_PARENT(rbe) && (rbe == RBE_LEFT(RBE_PARENT(rbe)))) rbe = RBE_PARENT(rbe); else { while (RBE_PARENT(rbe) && (rbe == RBE_RIGHT(RBE_PARENT(rbe)))) rbe = RBE_PARENT(rbe); rbe = RBE_PARENT(rbe); } } return (rbe == NULL ? NULL : rb_e2n(t, rbe)); } void *_rb_prev(const struct rb_type *t, void *elm) { struct rb_entry *rbe = rb_n2e(t, elm); if (RBE_LEFT(rbe)) { rbe = RBE_LEFT(rbe); while (RBE_RIGHT(rbe)) rbe = RBE_RIGHT(rbe); } else { if (RBE_PARENT(rbe) && (rbe == RBE_RIGHT(RBE_PARENT(rbe)))) rbe = RBE_PARENT(rbe); else { while (RBE_PARENT(rbe) && (rbe == RBE_LEFT(RBE_PARENT(rbe)))) rbe = RBE_PARENT(rbe); rbe = RBE_PARENT(rbe); } } return (rbe == NULL ? NULL : rb_e2n(t, rbe)); } void *_rb_root(const struct rb_type *t, const struct rbt_tree *rbt) { struct rb_entry *rbe = RBH_ROOT(rbt); return (rbe == NULL ? rbe : rb_e2n(t, rbe)); } void *_rb_min(const struct rb_type *t, const struct rbt_tree *rbt) { struct rb_entry *rbe = RBH_ROOT(rbt); struct rb_entry *parent = NULL; while (rbe != NULL) { parent = rbe; rbe = RBE_LEFT(rbe); } return (parent == NULL ? NULL : rb_e2n(t, parent)); } void *_rb_max(const struct rb_type *t, const struct rbt_tree *rbt) { struct rb_entry *rbe = RBH_ROOT(rbt); struct rb_entry *parent = NULL; while (rbe != NULL) { parent = rbe; rbe = RBE_RIGHT(rbe); } return (parent == NULL ? NULL : rb_e2n(t, parent)); } void *_rb_left(const struct rb_type *t, void *node) { struct rb_entry *rbe = rb_n2e(t, node); rbe = RBE_LEFT(rbe); return (rbe == NULL ? NULL : rb_e2n(t, rbe)); } void *_rb_right(const struct rb_type *t, void *node) { struct rb_entry *rbe = rb_n2e(t, node); rbe = RBE_RIGHT(rbe); return (rbe == NULL ? NULL : rb_e2n(t, rbe)); } void *_rb_parent(const struct rb_type *t, void *node) { struct rb_entry *rbe = rb_n2e(t, node); rbe = RBE_PARENT(rbe); return (rbe == NULL ? NULL : rb_e2n(t, rbe)); } void _rb_set_left(const struct rb_type *t, void *node, void *left) { struct rb_entry *rbe = rb_n2e(t, node); struct rb_entry *rbl = (left == NULL) ? NULL : rb_n2e(t, left); RBE_LEFT(rbe) = rbl; } void _rb_set_right(const struct rb_type *t, void *node, void *right) { struct rb_entry *rbe = rb_n2e(t, node); struct rb_entry *rbr = (right == NULL) ? NULL : rb_n2e(t, right); RBE_RIGHT(rbe) = rbr; } void _rb_set_parent(const struct rb_type *t, void *node, void *parent) { struct rb_entry *rbe = rb_n2e(t, node); struct rb_entry *rbp = (parent == NULL) ? NULL : rb_n2e(t, parent); RBE_PARENT(rbe) = rbp; } void _rb_poison(const struct rb_type *t, void *node, unsigned long poison) { struct rb_entry *rbe = rb_n2e(t, node); RBE_PARENT(rbe) = RBE_LEFT(rbe) = RBE_RIGHT(rbe) = (struct rb_entry *)poison; } int _rb_check(const struct rb_type *t, void *node, unsigned long poison) { struct rb_entry *rbe = rb_n2e(t, node); return ((unsigned long)RBE_PARENT(rbe) == poison && (unsigned long)RBE_LEFT(rbe) == poison && (unsigned long)RBE_RIGHT(rbe) == poison); } frr-7.2.1/lib/openbsd-tree.h0000644000000000000000000007606713610377563012542 00000000000000/* $OpenBSD: tree.h,v 1.14 2015/05/25 03:07:49 deraadt Exp $ */ /* * Copyright 2002 Niels Provos * All rights reserved. * * Redistribution and use in source and binary forms, with or without * modification, are permitted provided that the following conditions * are met: * 1. Redistributions of source code must retain the above copyright * notice, this list of conditions and the following disclaimer. * 2. Redistributions in binary form must reproduce the above copyright * notice, this list of conditions and the following disclaimer in the * documentation and/or other materials provided with the distribution. * * THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR * IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES * OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. * IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT, * INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT * NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF * THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. */ #ifndef _SYS_TREE_H_ #define _SYS_TREE_H_ #ifdef __cplusplus extern "C" { #endif /* * This file defines data structures for different types of trees: * splay trees and red-black trees. * * A splay tree is a self-organizing data structure. Every operation * on the tree causes a splay to happen. The splay moves the requested * node to the root of the tree and partly rebalances it. * * This has the benefit that request locality causes faster lookups as * the requested nodes move to the top of the tree. On the other hand, * every lookup causes memory writes. * * The Balance Theorem bounds the total access time for m operations * and n inserts on an initially empty tree as O((m + n)lg n). The * amortized cost for a sequence of m accesses to a splay tree is O(lg n); * * A red-black tree is a binary search tree with the node color as an * extra attribute. It fulfills a set of conditions: * - every search path from the root to a leaf consists of the * same number of black nodes, * - each red node (except for the root) has a black parent, * - each leaf node is black. * * Every operation on a red-black tree is bounded as O(lg n). * The maximum height of a red-black tree is 2lg (n+1). */ #define SPLAY_HEAD(name, type) \ struct name { \ struct type *sph_root; /* root of the tree */ \ } #define SPLAY_INITIALIZER(root) \ { \ NULL \ } #define SPLAY_INIT(root) \ do { \ (root)->sph_root = NULL; \ } while (0) #define SPLAY_ENTRY(type) \ struct { \ struct type *spe_left; /* left element */ \ struct type *spe_right; /* right element */ \ } #define SPLAY_LEFT(elm, field) (elm)->field.spe_left #define SPLAY_RIGHT(elm, field) (elm)->field.spe_right #define SPLAY_ROOT(head) (head)->sph_root #define SPLAY_EMPTY(head) (SPLAY_ROOT(head) == NULL) /* SPLAY_ROTATE_{LEFT,RIGHT} expect that tmp hold SPLAY_{RIGHT,LEFT} */ #define SPLAY_ROTATE_RIGHT(head, tmp, field) \ do { \ SPLAY_LEFT((head)->sph_root, field) = SPLAY_RIGHT(tmp, field); \ SPLAY_RIGHT(tmp, field) = (head)->sph_root; \ (head)->sph_root = tmp; \ } while (0) #define SPLAY_ROTATE_LEFT(head, tmp, field) \ do { \ SPLAY_RIGHT((head)->sph_root, field) = SPLAY_LEFT(tmp, field); \ SPLAY_LEFT(tmp, field) = (head)->sph_root; \ (head)->sph_root = tmp; \ } while (0) #define SPLAY_LINKLEFT(head, tmp, field) \ do { \ SPLAY_LEFT(tmp, field) = (head)->sph_root; \ tmp = (head)->sph_root; \ (head)->sph_root = SPLAY_LEFT((head)->sph_root, field); \ } while (0) #define SPLAY_LINKRIGHT(head, tmp, field) \ do { \ SPLAY_RIGHT(tmp, field) = (head)->sph_root; \ tmp = (head)->sph_root; \ (head)->sph_root = SPLAY_RIGHT((head)->sph_root, field); \ } while (0) #define SPLAY_ASSEMBLE(head, node, left, right, field) \ do { \ SPLAY_RIGHT(left, field) = \ SPLAY_LEFT((head)->sph_root, field); \ SPLAY_LEFT(right, field) = \ SPLAY_RIGHT((head)->sph_root, field); \ SPLAY_LEFT((head)->sph_root, field) = \ SPLAY_RIGHT(node, field); \ SPLAY_RIGHT((head)->sph_root, field) = \ SPLAY_LEFT(node, field); \ } while (0) /* Generates prototypes and inline functions */ #define SPLAY_PROTOTYPE(name, type, field, cmp) \ void name##_SPLAY(struct name *, struct type *); \ void name##_SPLAY_MINMAX(struct name *, int); \ struct type *name##_SPLAY_INSERT(struct name *, struct type *); \ struct type *name##_SPLAY_REMOVE(struct name *, struct type *); \ \ /* Finds the node with the same key as elm */ \ static __inline struct type *name##_SPLAY_FIND(struct name *head, \ struct type *elm) \ { \ if (SPLAY_EMPTY(head)) \ return (NULL); \ name##_SPLAY(head, elm); \ if ((cmp)(elm, (head)->sph_root) == 0) \ return (head->sph_root); \ return (NULL); \ } \ \ static __inline struct type *name##_SPLAY_NEXT(struct name *head, \ struct type *elm) \ { \ name##_SPLAY(head, elm); \ if (SPLAY_RIGHT(elm, field) != NULL) { \ elm = SPLAY_RIGHT(elm, field); \ while (SPLAY_LEFT(elm, field) != NULL) { \ elm = SPLAY_LEFT(elm, field); \ } \ } else \ elm = NULL; \ return (elm); \ } \ \ static __inline struct type *name##_SPLAY_MIN_MAX(struct name *head, \ int val) \ { \ name##_SPLAY_MINMAX(head, val); \ return (SPLAY_ROOT(head)); \ } /* Main splay operation. * Moves node close to the key of elm to top */ #define SPLAY_GENERATE(name, type, field, cmp) \ struct type *name##_SPLAY_INSERT(struct name *head, struct type *elm) \ { \ if (SPLAY_EMPTY(head)) { \ SPLAY_LEFT(elm, field) = SPLAY_RIGHT(elm, field) = \ NULL; \ } else { \ int __comp; \ name##_SPLAY(head, elm); \ __comp = (cmp)(elm, (head)->sph_root); \ if (__comp < 0) { \ SPLAY_LEFT(elm, field) = \ SPLAY_LEFT((head)->sph_root, field); \ SPLAY_RIGHT(elm, field) = (head)->sph_root; \ SPLAY_LEFT((head)->sph_root, field) = NULL; \ } else if (__comp > 0) { \ SPLAY_RIGHT(elm, field) = \ SPLAY_RIGHT((head)->sph_root, field); \ SPLAY_LEFT(elm, field) = (head)->sph_root; \ SPLAY_RIGHT((head)->sph_root, field) = NULL; \ } else \ return ((head)->sph_root); \ } \ (head)->sph_root = (elm); \ return (NULL); \ } \ \ struct type *name##_SPLAY_REMOVE(struct name *head, struct type *elm) \ { \ struct type *__tmp; \ if (SPLAY_EMPTY(head)) \ return (NULL); \ name##_SPLAY(head, elm); \ if ((cmp)(elm, (head)->sph_root) == 0) { \ if (SPLAY_LEFT((head)->sph_root, field) == NULL) { \ (head)->sph_root = \ SPLAY_RIGHT((head)->sph_root, field); \ } else { \ __tmp = SPLAY_RIGHT((head)->sph_root, field); \ (head)->sph_root = \ SPLAY_LEFT((head)->sph_root, field); \ name##_SPLAY(head, elm); \ SPLAY_RIGHT((head)->sph_root, field) = __tmp; \ } \ return (elm); \ } \ return (NULL); \ } \ \ void name##_SPLAY(struct name *head, struct type *elm) \ { \ struct type __node, *__left, *__right, *__tmp; \ int __comp; \ \ SPLAY_LEFT(&__node, field) = SPLAY_RIGHT(&__node, field) = \ NULL; \ __left = __right = &__node; \ \ while ((__comp = (cmp)(elm, (head)->sph_root))) { \ if (__comp < 0) { \ __tmp = SPLAY_LEFT((head)->sph_root, field); \ if (__tmp == NULL) \ break; \ if ((cmp)(elm, __tmp) < 0) { \ SPLAY_ROTATE_RIGHT(head, __tmp, \ field); \ if (SPLAY_LEFT((head)->sph_root, \ field) \ == NULL) \ break; \ } \ SPLAY_LINKLEFT(head, __right, field); \ } else if (__comp > 0) { \ __tmp = SPLAY_RIGHT((head)->sph_root, field); \ if (__tmp == NULL) \ break; \ if ((cmp)(elm, __tmp) > 0) { \ SPLAY_ROTATE_LEFT(head, __tmp, field); \ if (SPLAY_RIGHT((head)->sph_root, \ field) \ == NULL) \ break; \ } \ SPLAY_LINKRIGHT(head, __left, field); \ } \ } \ SPLAY_ASSEMBLE(head, &__node, __left, __right, field); \ } \ \ /* Splay with either the minimum or the maximum element \ * Used to find minimum or maximum element in tree. \ */ \ void name##_SPLAY_MINMAX(struct name *head, int __comp) \ { \ struct type __node, *__left, *__right, *__tmp; \ \ SPLAY_LEFT(&__node, field) = SPLAY_RIGHT(&__node, field) = \ NULL; \ __left = __right = &__node; \ \ while (1) { \ if (__comp < 0) { \ __tmp = SPLAY_LEFT((head)->sph_root, field); \ if (__tmp == NULL) \ break; \ if (__comp < 0) { \ SPLAY_ROTATE_RIGHT(head, __tmp, \ field); \ if (SPLAY_LEFT((head)->sph_root, \ field) \ == NULL) \ break; \ } \ SPLAY_LINKLEFT(head, __right, field); \ } else if (__comp > 0) { \ __tmp = SPLAY_RIGHT((head)->sph_root, field); \ if (__tmp == NULL) \ break; \ if (__comp > 0) { \ SPLAY_ROTATE_LEFT(head, __tmp, field); \ if (SPLAY_RIGHT((head)->sph_root, \ field) \ == NULL) \ break; \ } \ SPLAY_LINKRIGHT(head, __left, field); \ } \ } \ SPLAY_ASSEMBLE(head, &__node, __left, __right, field); \ } #define SPLAY_NEGINF -1 #define SPLAY_INF 1 #define SPLAY_INSERT(name, x, y) name##_SPLAY_INSERT(x, y) #define SPLAY_REMOVE(name, x, y) name##_SPLAY_REMOVE(x, y) #define SPLAY_FIND(name, x, y) name##_SPLAY_FIND(x, y) #define SPLAY_NEXT(name, x, y) name##_SPLAY_NEXT(x, y) #define SPLAY_MIN(name, x) \ (SPLAY_EMPTY(x) ? NULL : name##_SPLAY_MIN_MAX(x, SPLAY_NEGINF)) #define SPLAY_MAX(name, x) \ (SPLAY_EMPTY(x) ? NULL : name##_SPLAY_MIN_MAX(x, SPLAY_INF)) #define SPLAY_FOREACH(x, name, head) \ for ((x) = SPLAY_MIN(name, head); (x) != NULL; \ (x) = SPLAY_NEXT(name, head, x)) /* * Copyright (c) 2016 David Gwynne * * Permission to use, copy, modify, and distribute this software for any * purpose with or without fee is hereby granted, provided that the above * copyright notice and this permission notice appear in all copies. * * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. */ #define RB_BLACK 0 #define RB_RED 1 struct rb_type { int (*t_compare)(const void *, const void *); void (*t_augment)(void *); unsigned int t_offset; /* offset of rb_entry in type */ }; struct rbt_tree { struct rb_entry *rbt_root; }; struct rb_entry { struct rb_entry *rbt_parent; struct rb_entry *rbt_left; struct rb_entry *rbt_right; unsigned int rbt_color; }; #define RB_HEAD(_name, _type) \ struct _name { \ struct rbt_tree rbh_root; \ } #define RB_ENTRY(_type) struct rb_entry static inline void _rb_init(struct rbt_tree *rbt) { rbt->rbt_root = NULL; } static inline int _rb_empty(const struct rbt_tree *rbt) { return (rbt->rbt_root == NULL); } void *_rb_insert(const struct rb_type *, struct rbt_tree *, void *); void *_rb_remove(const struct rb_type *, struct rbt_tree *, void *); void *_rb_find(const struct rb_type *, const struct rbt_tree *, const void *); void *_rb_nfind(const struct rb_type *, const struct rbt_tree *, const void *); void *_rb_root(const struct rb_type *, const struct rbt_tree *); void *_rb_min(const struct rb_type *, const struct rbt_tree *); void *_rb_max(const struct rb_type *, const struct rbt_tree *); void *_rb_next(const struct rb_type *, void *); void *_rb_prev(const struct rb_type *, void *); void *_rb_left(const struct rb_type *, void *); void *_rb_right(const struct rb_type *, void *); void *_rb_parent(const struct rb_type *, void *); void _rb_set_left(const struct rb_type *, void *, void *); void _rb_set_right(const struct rb_type *, void *, void *); void _rb_set_parent(const struct rb_type *, void *, void *); void _rb_poison(const struct rb_type *, void *, unsigned long); int _rb_check(const struct rb_type *, void *, unsigned long); #define RB_INITIALIZER(_head) { { NULL } } #define RB_PROTOTYPE(_name, _type, _field, _cmp) \ extern const struct rb_type *const _name##_RB_TYPE; \ \ __attribute__((__unused__)) static inline void _name##_RB_INIT( \ struct _name *head) \ { \ _rb_init(&head->rbh_root); \ } \ \ __attribute__((__unused__)) static inline struct _type \ *_name##_RB_INSERT(struct _name *head, struct _type *elm) \ { \ return (struct _type *)_rb_insert(_name##_RB_TYPE, \ &head->rbh_root, elm); \ } \ \ __attribute__((__unused__)) static inline struct _type \ *_name##_RB_REMOVE(struct _name *head, struct _type *elm) \ { \ return (struct _type *)_rb_remove(_name##_RB_TYPE, \ &head->rbh_root, elm); \ } \ \ __attribute__((__unused__)) static inline struct _type \ *_name##_RB_FIND(const struct _name *head, \ const struct _type *key) \ { \ return (struct _type *)_rb_find(_name##_RB_TYPE, \ &head->rbh_root, key); \ } \ \ __attribute__((__unused__)) static inline struct _type \ *_name##_RB_NFIND(const struct _name *head, \ const struct _type *key) \ { \ return (struct _type *)_rb_nfind(_name##_RB_TYPE, \ &head->rbh_root, key); \ } \ \ __attribute__((__unused__)) static inline struct _type \ *_name##_RB_ROOT(const struct _name *head) \ { \ return (struct _type *)_rb_root(_name##_RB_TYPE, \ &head->rbh_root); \ } \ \ __attribute__((__unused__)) static inline int _name##_RB_EMPTY( \ const struct _name *head) \ { \ return _rb_empty(&head->rbh_root); \ } \ \ __attribute__((__unused__)) static inline struct _type \ *_name##_RB_MIN(const struct _name *head) \ { \ return (struct _type *)_rb_min(_name##_RB_TYPE, \ &head->rbh_root); \ } \ \ __attribute__((__unused__)) static inline struct _type \ *_name##_RB_MAX(const struct _name *head) \ { \ return (struct _type *)_rb_max(_name##_RB_TYPE, \ &head->rbh_root); \ } \ \ __attribute__((__unused__)) static inline struct _type \ *_name##_RB_NEXT(struct _type *elm) \ { \ return (struct _type *)_rb_next(_name##_RB_TYPE, elm); \ } \ \ __attribute__((__unused__)) static inline struct _type \ *_name##_RB_PREV(struct _type *elm) \ { \ return (struct _type *)_rb_prev(_name##_RB_TYPE, elm); \ } \ \ __attribute__((__unused__)) static inline struct _type \ *_name##_RB_LEFT(struct _type *elm) \ { \ return (struct _type *)_rb_left(_name##_RB_TYPE, elm); \ } \ \ __attribute__((__unused__)) static inline struct _type \ *_name##_RB_RIGHT(struct _type *elm) \ { \ return (struct _type *)_rb_right(_name##_RB_TYPE, elm); \ } \ \ __attribute__((__unused__)) static inline struct _type \ *_name##_RB_PARENT(struct _type *elm) \ { \ return (struct _type *)_rb_parent(_name##_RB_TYPE, elm); \ } \ \ __attribute__((__unused__)) static inline void _name##_RB_SET_LEFT( \ struct _type *elm, struct _type *left) \ { \ _rb_set_left(_name##_RB_TYPE, elm, left); \ } \ \ __attribute__((__unused__)) static inline void _name##_RB_SET_RIGHT( \ struct _type *elm, struct _type *right) \ { \ _rb_set_right(_name##_RB_TYPE, elm, right); \ } \ \ __attribute__((__unused__)) static inline void _name##_RB_SET_PARENT( \ struct _type *elm, struct _type *parent) \ { \ _rb_set_parent(_name##_RB_TYPE, elm, parent); \ } \ \ __attribute__((__unused__)) static inline void _name##_RB_POISON( \ struct _type *elm, unsigned long poison) \ { \ _rb_poison(_name##_RB_TYPE, elm, poison); \ } \ \ __attribute__((__unused__)) static inline int _name##_RB_CHECK( \ struct _type *elm, unsigned long poison) \ { \ return _rb_check(_name##_RB_TYPE, elm, poison); \ } #define RB_GENERATE_INTERNAL(_name, _type, _field, _cmp, _aug) \ static int _name##_RB_COMPARE(const void *lptr, const void *rptr) \ { \ const struct _type *l = lptr, *r = rptr; \ return _cmp(l, r); \ } \ static const struct rb_type _name##_RB_INFO = { \ _name##_RB_COMPARE, _aug, offsetof(struct _type, _field), \ }; \ const struct rb_type *const _name##_RB_TYPE = &_name##_RB_INFO; #define RB_GENERATE_AUGMENT(_name, _type, _field, _cmp, _aug) \ static void _name##_RB_AUGMENT(void *ptr) \ { \ struct _type *p = ptr; \ return _aug(p); \ } \ RB_GENERATE_INTERNAL(_name, _type, _field, _cmp, _name##_RB_AUGMENT) #define RB_GENERATE(_name, _type, _field, _cmp) \ RB_GENERATE_INTERNAL(_name, _type, _field, _cmp, NULL) #define RB_INIT(_name, _head) _name##_RB_INIT(_head) #define RB_INSERT(_name, _head, _elm) _name##_RB_INSERT(_head, _elm) #define RB_REMOVE(_name, _head, _elm) _name##_RB_REMOVE(_head, _elm) #define RB_FIND(_name, _head, _key) _name##_RB_FIND(_head, _key) #define RB_NFIND(_name, _head, _key) _name##_RB_NFIND(_head, _key) #define RB_ROOT(_name, _head) _name##_RB_ROOT(_head) #define RB_EMPTY(_name, _head) _name##_RB_EMPTY(_head) #define RB_MIN(_name, _head) _name##_RB_MIN(_head) #define RB_MAX(_name, _head) _name##_RB_MAX(_head) #define RB_NEXT(_name, _elm) _name##_RB_NEXT(_elm) #define RB_PREV(_name, _elm) _name##_RB_PREV(_elm) #define RB_LEFT(_name, _elm) _name##_RB_LEFT(_elm) #define RB_RIGHT(_name, _elm) _name##_RB_RIGHT(_elm) #define RB_PARENT(_name, _elm) _name##_RB_PARENT(_elm) #define RB_SET_LEFT(_name, _elm, _l) _name##_RB_SET_LEFT(_elm, _l) #define RB_SET_RIGHT(_name, _elm, _r) _name##_RB_SET_RIGHT(_elm, _r) #define RB_SET_PARENT(_name, _elm, _p) _name##_RB_SET_PARENT(_elm, _p) #define RB_POISON(_name, _elm, _p) _name##_RB_POISON(_elm, _p) #define RB_CHECK(_name, _elm, _p) _name##_RB_CHECK(_elm, _p) #define RB_FOREACH(_e, _name, _head) \ for ((_e) = RB_MIN(_name, (_head)); (_e) != NULL; \ (_e) = RB_NEXT(_name, (_e))) #define RB_FOREACH_SAFE(_e, _name, _head, _n) \ for ((_e) = RB_MIN(_name, (_head)); \ (_e) != NULL && ((_n) = RB_NEXT(_name, (_e)), 1); (_e) = (_n)) #define RB_FOREACH_REVERSE(_e, _name, _head) \ for ((_e) = RB_MAX(_name, (_head)); (_e) != NULL; \ (_e) = RB_PREV(_name, (_e))) #define RB_FOREACH_REVERSE_SAFE(_e, _name, _head, _n) \ for ((_e) = RB_MAX(_name, (_head)); \ (_e) != NULL && ((_n) = RB_PREV(_name, (_e)), 1); (_e) = (_n)) #ifdef __cplusplus } #endif #endif /* _SYS_TREE_H_ */ frr-7.2.1/lib/pbr.h0000644000000000000000000000716513610377563010727 00000000000000/* Policy Based Routing (PBR) main header * Copyright (C) 2018 6WIND * * FRR is free software; you can redistribute it and/or modify it * under the terms of the GNU General Public License as published by the * Free Software Foundation; either version 2, or (at your option) any * later version. * * FRR 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 FRR; see the file COPYING. If not, write to the Free * Software Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA * 02111-1307, USA. */ #ifndef _PBR_H #define _PBR_H #include #include "stream.h" #include "prefix.h" #ifdef __cplusplus extern "C" { #endif #define PBR_STR "Policy Based Routing\n" /* * A PBR filter * * The filter or match criteria in a PBR rule. * For simplicity, all supported filters are grouped into a structure rather * than delineating further. A bitmask denotes which filters are actually * specified. */ struct pbr_filter { uint32_t filter_bm; /* not encoded by zapi */ #define PBR_FILTER_SRC_IP (1 << 0) #define PBR_FILTER_DST_IP (1 << 1) #define PBR_FILTER_SRC_PORT (1 << 2) #define PBR_FILTER_DST_PORT (1 << 3) #define PBR_FILTER_FWMARK (1 << 4) #define PBR_FILTER_PROTO (1 << 5) #define PBR_FILTER_SRC_PORT_RANGE (1 << 6) #define PBR_FILTER_DST_PORT_RANGE (1 << 7) /* Source and Destination IP address with masks. */ struct prefix src_ip; struct prefix dst_ip; /* Source and Destination higher-layer (TCP/UDP) port numbers. */ uint16_t src_port; uint16_t dst_port; /* Filter with fwmark */ uint32_t fwmark; }; /* * A PBR action * * The action corresponding to a PBR rule. * While the user specifies the action in a particular way, the forwarding * plane implementation (Linux only) requires that to be encoded into a * route table and the rule then point to that route table; in some cases, * the user criteria may directly point to a table too. */ struct pbr_action { uint32_t table; }; /* * A PBR rule * * This is a combination of the filter criteria and corresponding action. * Rules also have a user-defined sequence number which defines the relative * order amongst rules. */ struct pbr_rule { vrf_id_t vrf_id; uint32_t seq; uint32_t priority; uint32_t unique; struct pbr_filter filter; struct pbr_action action; uint32_t ifindex; }; /* TCP flags value shared * those are values of byte 13 of TCP header * as mentioned in rfc793 */ #define TCP_HEADER_FIN (0x01) #define TCP_HEADER_SYN (0x02) #define TCP_HEADER_RST (0x04) #define TCP_HEADER_PSH (0x08) #define TCP_HEADER_ACK (0x10) #define TCP_HEADER_URG (0x20) #define TCP_HEADER_ALL_FLAGS (TCP_HEADER_FIN | TCP_HEADER_SYN \ | TCP_HEADER_RST | TCP_HEADER_PSH \ | TCP_HEADER_ACK | TCP_HEADER_URG) /* Pbr IPTable defines * those are common flags shared between BGP and Zebra */ #define MATCH_IP_SRC_SET (1 << 0) #define MATCH_IP_DST_SET (1 << 1) #define MATCH_PORT_SRC_SET (1 << 2) #define MATCH_PORT_DST_SET (1 << 3) #define MATCH_PORT_SRC_RANGE_SET (1 << 4) #define MATCH_PORT_DST_RANGE_SET (1 << 5) #define MATCH_DSCP_SET (1 << 6) #define MATCH_DSCP_INVERSE_SET (1 << 7) #define MATCH_PKT_LEN_INVERSE_SET (1 << 8) #define MATCH_FRAGMENT_INVERSE_SET (1 << 9) #define MATCH_ICMP_SET (1 << 10) #define MATCH_PROTOCOL_SET (1 << 11) extern int zapi_pbr_rule_encode(uint8_t cmd, struct stream *s, struct pbr_rule *zrule); #ifdef __cplusplus } #endif #endif /* _PBR_H */ frr-7.2.1/lib/pid_output.c0000644000000000000000000000431413610377563012324 00000000000000/* * Process id output. * Copyright (C) 1998, 1999 Kunihiro Ishiguro * * This file is part of GNU Zebra. * * GNU Zebra is free software; you can redistribute it and/or modify it * under the terms of the GNU General Public License as published by the * Free Software Foundation; either version 2, or (at your option) any * later version. * * GNU Zebra 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; see the file COPYING; if not, write to the Free Software * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA */ #include #include #include #include "version.h" #include "network.h" #include "lib_errors.h" #define PIDFILE_MASK 0644 pid_t pid_output(const char *path) { int tmp; int fd; pid_t pid; char buf[16]; struct flock lock; mode_t oldumask; pid = getpid(); oldumask = umask(0777 & ~PIDFILE_MASK); fd = open(path, O_RDWR | O_CREAT, PIDFILE_MASK); if (fd < 0) { flog_err_sys(EC_LIB_SYSTEM_CALL, "Can't create pid lock file %s (%s), exiting", path, safe_strerror(errno)); umask(oldumask); exit(1); } else { size_t pidsize; umask(oldumask); memset(&lock, 0, sizeof(lock)); set_cloexec(fd); lock.l_type = F_WRLCK; lock.l_whence = SEEK_SET; if (fcntl(fd, F_SETLK, &lock) < 0) { flog_err_sys(EC_LIB_SYSTEM_CALL, "Could not lock pid_file %s (%s), exiting. Please ensure that the daemon is not already running", path, safe_strerror(errno)); exit(1); } sprintf(buf, "%d\n", (int)pid); pidsize = strlen(buf); if ((tmp = write(fd, buf, pidsize)) != (int)pidsize) flog_err_sys( EC_LIB_SYSTEM_CALL, "Could not write pid %d to pid_file %s, rc was %d: %s", (int)pid, path, tmp, safe_strerror(errno)); else if (ftruncate(fd, pidsize) < 0) flog_err_sys( EC_LIB_SYSTEM_CALL, "Could not truncate pid_file %s to %u bytes: %s", path, (unsigned int)pidsize, safe_strerror(errno)); } return pid; } frr-7.2.1/lib/plist.c0000644000000000000000000015313313610377563011267 00000000000000/* Prefix list functions. * Copyright (C) 1999 Kunihiro Ishiguro * * This file is part of GNU Zebra. * * GNU Zebra is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License as published * by the Free Software Foundation; either version 2, or (at your * option) any later version. * * GNU Zebra 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; see the file COPYING; if not, write to the Free Software * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA */ #include #include "prefix.h" #include "command.h" #include "memory.h" #include "plist.h" #include "sockunion.h" #include "buffer.h" #include "log.h" #include "routemap.h" #include "lib/json.h" #include "libfrr.h" #include "plist_int.h" DEFINE_MTYPE_STATIC(LIB, PREFIX_LIST, "Prefix List") DEFINE_MTYPE_STATIC(LIB, MPREFIX_LIST_STR, "Prefix List Str") DEFINE_MTYPE_STATIC(LIB, PREFIX_LIST_ENTRY, "Prefix List Entry") DEFINE_MTYPE_STATIC(LIB, PREFIX_LIST_TRIE, "Prefix List Trie Table") /* not currently changeable, code assumes bytes further down */ #define PLC_BITS 8 #define PLC_LEN (1 << PLC_BITS) #define PLC_MAXLEVELV4 2 /* /24 for IPv4 */ #define PLC_MAXLEVELV6 4 /* /48 for IPv6 */ #define PLC_MAXLEVEL 4 /* max(v4,v6) */ struct pltrie_entry { union { struct pltrie_table *next_table; struct prefix_list_entry *final_chain; }; struct prefix_list_entry *up_chain; }; struct pltrie_table { struct pltrie_entry entries[PLC_LEN]; }; /* List of struct prefix_list. */ struct prefix_list_list { struct prefix_list *head; struct prefix_list *tail; }; /* Master structure of prefix_list. */ struct prefix_master { /* List of prefix_list which name is number. */ struct prefix_list_list num; /* List of prefix_list which name is string. */ struct prefix_list_list str; /* Whether sequential number is used. */ bool seqnum; /* The latest update. */ struct prefix_list *recent; /* Hook function which is executed when new prefix_list is added. */ void (*add_hook)(struct prefix_list *); /* Hook function which is executed when prefix_list is deleted. */ void (*delete_hook)(struct prefix_list *); /* number of bytes that have a trie level */ size_t trie_depth; }; /* Static structure of IPv4 prefix_list's master. */ static struct prefix_master prefix_master_ipv4 = { {NULL, NULL}, {NULL, NULL}, 1, NULL, NULL, NULL, PLC_MAXLEVELV4, }; /* Static structure of IPv6 prefix-list's master. */ static struct prefix_master prefix_master_ipv6 = { {NULL, NULL}, {NULL, NULL}, 1, NULL, NULL, NULL, PLC_MAXLEVELV6, }; /* Static structure of BGP ORF prefix_list's master. */ static struct prefix_master prefix_master_orf_v4 = { {NULL, NULL}, {NULL, NULL}, 1, NULL, NULL, NULL, PLC_MAXLEVELV4, }; /* Static structure of BGP ORF prefix_list's master. */ static struct prefix_master prefix_master_orf_v6 = { {NULL, NULL}, {NULL, NULL}, 1, NULL, NULL, NULL, PLC_MAXLEVELV6, }; static struct prefix_master *prefix_master_get(afi_t afi, int orf) { if (afi == AFI_IP) return orf ? &prefix_master_orf_v4 : &prefix_master_ipv4; if (afi == AFI_IP6) return orf ? &prefix_master_orf_v6 : &prefix_master_ipv6; return NULL; } const char *prefix_list_name(struct prefix_list *plist) { return plist->name; } afi_t prefix_list_afi(struct prefix_list *plist) { if (plist->master == &prefix_master_ipv4 || plist->master == &prefix_master_orf_v4) return AFI_IP; return AFI_IP6; } /* Lookup prefix_list from list of prefix_list by name. */ static struct prefix_list *prefix_list_lookup_do(afi_t afi, int orf, const char *name) { struct prefix_list *plist; struct prefix_master *master; if (name == NULL) return NULL; master = prefix_master_get(afi, orf); if (master == NULL) return NULL; for (plist = master->num.head; plist; plist = plist->next) if (strcmp(plist->name, name) == 0) return plist; for (plist = master->str.head; plist; plist = plist->next) if (strcmp(plist->name, name) == 0) return plist; return NULL; } struct prefix_list *prefix_list_lookup(afi_t afi, const char *name) { return prefix_list_lookup_do(afi, 0, name); } struct prefix_list *prefix_bgp_orf_lookup(afi_t afi, const char *name) { return prefix_list_lookup_do(afi, 1, name); } static struct prefix_list *prefix_list_new(void) { struct prefix_list *new; new = XCALLOC(MTYPE_PREFIX_LIST, sizeof(struct prefix_list)); return new; } static void prefix_list_free(struct prefix_list *plist) { XFREE(MTYPE_PREFIX_LIST, plist); } static struct prefix_list_entry *prefix_list_entry_new(void) { struct prefix_list_entry *new; new = XCALLOC(MTYPE_PREFIX_LIST_ENTRY, sizeof(struct prefix_list_entry)); return new; } static void prefix_list_entry_free(struct prefix_list_entry *pentry) { XFREE(MTYPE_PREFIX_LIST_ENTRY, pentry); } /* Insert new prefix list to list of prefix_list. Each prefix_list is sorted by the name. */ static struct prefix_list *prefix_list_insert(afi_t afi, int orf, const char *name) { unsigned int i; long number; struct prefix_list *plist; struct prefix_list *point; struct prefix_list_list *list; struct prefix_master *master; master = prefix_master_get(afi, orf); if (master == NULL) return NULL; /* Allocate new prefix_list and copy given name. */ plist = prefix_list_new(); plist->name = XSTRDUP(MTYPE_MPREFIX_LIST_STR, name); plist->master = master; plist->trie = XCALLOC(MTYPE_PREFIX_LIST_TRIE, sizeof(struct pltrie_table)); /* If name is made by all digit character. We treat it as number. */ for (number = 0, i = 0; i < strlen(name); i++) { if (isdigit((unsigned char)name[i])) number = (number * 10) + (name[i] - '0'); else break; } /* In case of name is all digit character */ if (i == strlen(name)) { plist->type = PREFIX_TYPE_NUMBER; /* Set prefix_list to number list. */ list = &master->num; for (point = list->head; point; point = point->next) if (atol(point->name) >= number) break; } else { plist->type = PREFIX_TYPE_STRING; /* Set prefix_list to string list. */ list = &master->str; /* Set point to insertion point. */ for (point = list->head; point; point = point->next) if (strcmp(point->name, name) >= 0) break; } /* In case of this is the first element of master. */ if (list->head == NULL) { list->head = list->tail = plist; return plist; } /* In case of insertion is made at the tail of access_list. */ if (point == NULL) { plist->prev = list->tail; list->tail->next = plist; list->tail = plist; return plist; } /* In case of insertion is made at the head of access_list. */ if (point == list->head) { plist->next = list->head; list->head->prev = plist; list->head = plist; return plist; } /* Insertion is made at middle of the access_list. */ plist->next = point; plist->prev = point->prev; if (point->prev) point->prev->next = plist; point->prev = plist; return plist; } static struct prefix_list *prefix_list_get(afi_t afi, int orf, const char *name) { struct prefix_list *plist; plist = prefix_list_lookup_do(afi, orf, name); if (plist == NULL) plist = prefix_list_insert(afi, orf, name); return plist; } static void prefix_list_trie_del(struct prefix_list *plist, struct prefix_list_entry *pentry); /* Delete prefix-list from prefix_list_master and free it. */ static void prefix_list_delete(struct prefix_list *plist) { struct prefix_list_list *list; struct prefix_master *master; struct prefix_list_entry *pentry; struct prefix_list_entry *next; /* If prefix-list contain prefix_list_entry free all of it. */ for (pentry = plist->head; pentry; pentry = next) { next = pentry->next; prefix_list_trie_del(plist, pentry); prefix_list_entry_free(pentry); plist->count--; } master = plist->master; if (plist->type == PREFIX_TYPE_NUMBER) list = &master->num; else list = &master->str; if (plist->next) plist->next->prev = plist->prev; else list->tail = plist->prev; if (plist->prev) plist->prev->next = plist->next; else list->head = plist->next; XFREE(MTYPE_TMP, plist->desc); /* Make sure master's recent changed prefix-list information is cleared. */ master->recent = NULL; route_map_notify_dependencies(plist->name, RMAP_EVENT_PLIST_DELETED); if (master->delete_hook) (*master->delete_hook)(plist); XFREE(MTYPE_MPREFIX_LIST_STR, plist->name); XFREE(MTYPE_PREFIX_LIST_TRIE, plist->trie); prefix_list_free(plist); } static struct prefix_list_entry * prefix_list_entry_make(struct prefix *prefix, enum prefix_list_type type, int64_t seq, int le, int ge, int any) { struct prefix_list_entry *pentry; pentry = prefix_list_entry_new(); if (any) pentry->any = 1; prefix_copy(&pentry->prefix, prefix); pentry->type = type; pentry->seq = seq; pentry->le = le; pentry->ge = ge; return pentry; } /* Add hook function. */ void prefix_list_add_hook(void (*func)(struct prefix_list *plist)) { prefix_master_ipv4.add_hook = func; prefix_master_ipv6.add_hook = func; } /* Delete hook function. */ void prefix_list_delete_hook(void (*func)(struct prefix_list *plist)) { prefix_master_ipv4.delete_hook = func; prefix_master_ipv6.delete_hook = func; } /* Calculate new sequential number. */ static int64_t prefix_new_seq_get(struct prefix_list *plist) { int64_t maxseq; int64_t newseq; struct prefix_list_entry *pentry; maxseq = newseq = 0; for (pentry = plist->head; pentry; pentry = pentry->next) { if (maxseq < pentry->seq) maxseq = pentry->seq; } newseq = ((maxseq / 5) * 5) + 5; return (newseq > UINT_MAX) ? UINT_MAX : newseq; } /* Return prefix list entry which has same seq number. */ static struct prefix_list_entry *prefix_seq_check(struct prefix_list *plist, int64_t seq) { struct prefix_list_entry *pentry; for (pentry = plist->head; pentry; pentry = pentry->next) if (pentry->seq == seq) return pentry; return NULL; } static struct prefix_list_entry * prefix_list_entry_lookup(struct prefix_list *plist, struct prefix *prefix, enum prefix_list_type type, int64_t seq, int le, int ge) { struct prefix_list_entry *pentry; for (pentry = plist->head; pentry; pentry = pentry->next) if (prefix_same(&pentry->prefix, prefix) && pentry->type == type) { if (seq >= 0 && pentry->seq != seq) continue; if (pentry->le != le) continue; if (pentry->ge != ge) continue; return pentry; } return NULL; } static void trie_walk_affected(size_t validbits, struct pltrie_table *table, uint8_t byte, struct prefix_list_entry *object, void (*fn)(struct prefix_list_entry *object, struct prefix_list_entry **updptr)) { uint8_t mask; uint16_t bwalk; if (validbits > PLC_BITS) { fn(object, &table->entries[byte].final_chain); return; } mask = (1 << (8 - validbits)) - 1; for (bwalk = byte & ~mask; bwalk <= byte + mask; bwalk++) { fn(object, &table->entries[bwalk].up_chain); } } static void trie_uninstall_fn(struct prefix_list_entry *object, struct prefix_list_entry **updptr) { for (; *updptr; updptr = &(*updptr)->next_best) if (*updptr == object) { *updptr = object->next_best; break; } } static int trie_table_empty(struct pltrie_table *table) { size_t i; for (i = 0; i < PLC_LEN; i++) if (table->entries[i].next_table || table->entries[i].up_chain) return 0; return 1; } static void prefix_list_trie_del(struct prefix_list *plist, struct prefix_list_entry *pentry) { size_t depth, maxdepth = plist->master->trie_depth; uint8_t *bytes = pentry->prefix.u.val; size_t validbits = pentry->prefix.prefixlen; struct pltrie_table *table, **tables[PLC_MAXLEVEL]; table = plist->trie; for (depth = 0; validbits > PLC_BITS && depth < maxdepth - 1; depth++) { uint8_t byte = bytes[depth]; assert(table->entries[byte].next_table); tables[depth + 1] = &table->entries[byte].next_table; table = table->entries[byte].next_table; validbits -= PLC_BITS; } trie_walk_affected(validbits, table, bytes[depth], pentry, trie_uninstall_fn); for (; depth > 0; depth--) if (trie_table_empty(*tables[depth])) { XFREE(MTYPE_PREFIX_LIST_TRIE, *tables[depth]); *tables[depth] = NULL; } } static void prefix_list_entry_delete(struct prefix_list *plist, struct prefix_list_entry *pentry, int update_list) { if (plist == NULL || pentry == NULL) return; prefix_list_trie_del(plist, pentry); if (pentry->prev) pentry->prev->next = pentry->next; else plist->head = pentry->next; if (pentry->next) pentry->next->prev = pentry->prev; else plist->tail = pentry->prev; prefix_list_entry_free(pentry); plist->count--; if (update_list) { route_map_notify_dependencies(plist->name, RMAP_EVENT_PLIST_DELETED); if (plist->master->delete_hook) (*plist->master->delete_hook)(plist); if (plist->head == NULL && plist->tail == NULL && plist->desc == NULL) prefix_list_delete(plist); else plist->master->recent = plist; } } static void trie_install_fn(struct prefix_list_entry *object, struct prefix_list_entry **updptr) { while (*updptr) { if (*updptr == object) return; if ((*updptr)->prefix.prefixlen < object->prefix.prefixlen) break; if ((*updptr)->prefix.prefixlen == object->prefix.prefixlen && (*updptr)->seq > object->seq) break; updptr = &(*updptr)->next_best; } if (!object->next_best) object->next_best = *updptr; else assert(object->next_best == *updptr || !*updptr); *updptr = object; } static void prefix_list_trie_add(struct prefix_list *plist, struct prefix_list_entry *pentry) { size_t depth = plist->master->trie_depth; uint8_t *bytes = pentry->prefix.u.val; size_t validbits = pentry->prefix.prefixlen; struct pltrie_table *table; table = plist->trie; while (validbits > PLC_BITS && depth > 1) { if (!table->entries[*bytes].next_table) table->entries[*bytes].next_table = XCALLOC(MTYPE_PREFIX_LIST_TRIE, sizeof(struct pltrie_table)); table = table->entries[*bytes].next_table; bytes++; depth--; validbits -= PLC_BITS; } trie_walk_affected(validbits, table, *bytes, pentry, trie_install_fn); } static void prefix_list_entry_add(struct prefix_list *plist, struct prefix_list_entry *pentry) { struct prefix_list_entry *replace; struct prefix_list_entry *point; /* Automatic asignment of seq no. */ if (pentry->seq == -1) pentry->seq = prefix_new_seq_get(plist); if (plist->tail && pentry->seq > plist->tail->seq) point = NULL; else { /* Is there any same seq prefix list entry? */ replace = prefix_seq_check(plist, pentry->seq); if (replace) prefix_list_entry_delete(plist, replace, 0); /* Check insert point. */ for (point = plist->head; point; point = point->next) if (point->seq >= pentry->seq) break; } /* In case of this is the first element of the list. */ pentry->next = point; if (point) { if (point->prev) point->prev->next = pentry; else plist->head = pentry; pentry->prev = point->prev; point->prev = pentry; } else { if (plist->tail) plist->tail->next = pentry; else plist->head = pentry; pentry->prev = plist->tail; plist->tail = pentry; } prefix_list_trie_add(plist, pentry); /* Increment count. */ plist->count++; /* Run hook function. */ if (plist->master->add_hook) (*plist->master->add_hook)(plist); route_map_notify_dependencies(plist->name, RMAP_EVENT_PLIST_ADDED); plist->master->recent = plist; } /* Return string of prefix_list_type. */ static const char *prefix_list_type_str(struct prefix_list_entry *pentry) { switch (pentry->type) { case PREFIX_PERMIT: return "permit"; case PREFIX_DENY: return "deny"; default: return ""; } } static int prefix_list_entry_match(struct prefix_list_entry *pentry, const struct prefix *p) { int ret; if (pentry->prefix.family != p->family) return 0; ret = prefix_match(&pentry->prefix, p); if (!ret) return 0; /* In case of le nor ge is specified, exact match is performed. */ if (!pentry->le && !pentry->ge) { if (pentry->prefix.prefixlen != p->prefixlen) return 0; } else { if (pentry->le) if (p->prefixlen > pentry->le) return 0; if (pentry->ge) if (p->prefixlen < pentry->ge) return 0; } return 1; } enum prefix_list_type prefix_list_apply_which_prefix( struct prefix_list *plist, const struct prefix **which, const void *object) { struct prefix_list_entry *pentry, *pbest = NULL; const struct prefix *p = (const struct prefix *)object; const uint8_t *byte = p->u.val; size_t depth; size_t validbits = p->prefixlen; struct pltrie_table *table; if (plist == NULL) { if (which) *which = NULL; return PREFIX_DENY; } if (plist->count == 0) { if (which) *which = NULL; return PREFIX_PERMIT; } depth = plist->master->trie_depth; table = plist->trie; while (1) { for (pentry = table->entries[*byte].up_chain; pentry; pentry = pentry->next_best) { if (pbest && pbest->seq < pentry->seq) continue; if (prefix_list_entry_match(pentry, p)) pbest = pentry; } if (validbits <= PLC_BITS) break; validbits -= PLC_BITS; if (--depth) { if (!table->entries[*byte].next_table) break; table = table->entries[*byte].next_table; byte++; continue; } for (pentry = table->entries[*byte].final_chain; pentry; pentry = pentry->next_best) { if (pbest && pbest->seq < pentry->seq) continue; if (prefix_list_entry_match(pentry, p)) pbest = pentry; } break; } if (which) { if (pbest) *which = &pbest->prefix; else *which = NULL; } if (pbest == NULL) return PREFIX_DENY; pbest->hitcnt++; return pbest->type; } static void __attribute__((unused)) prefix_list_print(struct prefix_list *plist) { struct prefix_list_entry *pentry; if (plist == NULL) return; printf("ip prefix-list %s: %d entries\n", plist->name, plist->count); for (pentry = plist->head; pentry; pentry = pentry->next) { if (pentry->any) printf("any %s\n", prefix_list_type_str(pentry)); else { struct prefix *p; char buf[BUFSIZ]; p = &pentry->prefix; printf(" seq %" PRId64 " %s %s/%d", pentry->seq, prefix_list_type_str(pentry), inet_ntop(p->family, p->u.val, buf, BUFSIZ), p->prefixlen); if (pentry->ge) printf(" ge %d", pentry->ge); if (pentry->le) printf(" le %d", pentry->le); printf("\n"); } } } /* Retrun 1 when plist already include pentry policy. */ static struct prefix_list_entry * prefix_entry_dup_check(struct prefix_list *plist, struct prefix_list_entry *new) { size_t depth, maxdepth = plist->master->trie_depth; uint8_t byte, *bytes = new->prefix.u.val; size_t validbits = new->prefix.prefixlen; struct pltrie_table *table; struct prefix_list_entry *pentry; int64_t seq = 0; if (new->seq == -1) seq = prefix_new_seq_get(plist); else seq = new->seq; table = plist->trie; for (depth = 0; validbits > PLC_BITS && depth < maxdepth - 1; depth++) { byte = bytes[depth]; if (!table->entries[byte].next_table) return NULL; table = table->entries[byte].next_table; validbits -= PLC_BITS; } byte = bytes[depth]; if (validbits > PLC_BITS) pentry = table->entries[byte].final_chain; else pentry = table->entries[byte].up_chain; for (; pentry; pentry = pentry->next_best) { if (prefix_same(&pentry->prefix, &new->prefix) && pentry->type == new->type && pentry->le == new->le && pentry->ge == new->ge && pentry->seq != seq) return pentry; } return NULL; } static int vty_invalid_prefix_range(struct vty *vty, const char *prefix) { vty_out(vty, "%% Invalid prefix range for %s, make sure: len < ge-value <= le-value\n", prefix); return CMD_WARNING_CONFIG_FAILED; } static int vty_prefix_list_install(struct vty *vty, afi_t afi, const char *name, const char *seq, const char *typestr, const char *prefix, const char *ge, const char *le) { int ret; enum prefix_list_type type; struct prefix_list *plist; struct prefix_list_entry *pentry; struct prefix_list_entry *dup; struct prefix p, p_tmp; int any = 0; int64_t seqnum = -1; int lenum = 0; int genum = 0; if (name == NULL || prefix == NULL || typestr == NULL) { vty_out(vty, "%% Missing prefix or type\n"); return CMD_WARNING_CONFIG_FAILED; } /* Sequential number. */ if (seq) seqnum = (int64_t)atol(seq); /* ge and le number */ if (ge) genum = atoi(ge); if (le) lenum = atoi(le); /* Check filter type. */ if (strncmp("permit", typestr, 1) == 0) type = PREFIX_PERMIT; else if (strncmp("deny", typestr, 1) == 0) type = PREFIX_DENY; else { vty_out(vty, "%% prefix type must be permit or deny\n"); return CMD_WARNING_CONFIG_FAILED; } /* "any" is special token for matching any IPv4 addresses. */ switch (afi) { case AFI_IP: if (strncmp("any", prefix, strlen(prefix)) == 0) { ret = str2prefix_ipv4("0.0.0.0/0", (struct prefix_ipv4 *)&p); genum = 0; lenum = IPV4_MAX_BITLEN; any = 1; } else ret = str2prefix_ipv4(prefix, (struct prefix_ipv4 *)&p); if (ret <= 0) { vty_out(vty, "%% Malformed IPv4 prefix\n"); return CMD_WARNING_CONFIG_FAILED; } /* make a copy to verify prefix matches mask length */ prefix_copy(&p_tmp, &p); apply_mask_ipv4((struct prefix_ipv4 *)&p_tmp); break; case AFI_IP6: if (strncmp("any", prefix, strlen(prefix)) == 0) { ret = str2prefix_ipv6("::/0", (struct prefix_ipv6 *)&p); genum = 0; lenum = IPV6_MAX_BITLEN; any = 1; } else ret = str2prefix_ipv6(prefix, (struct prefix_ipv6 *)&p); if (ret <= 0) { vty_out(vty, "%% Malformed IPv6 prefix\n"); return CMD_WARNING_CONFIG_FAILED; } /* make a copy to verify prefix matches mask length */ prefix_copy(&p_tmp, &p); apply_mask_ipv6((struct prefix_ipv6 *)&p_tmp); break; case AFI_L2VPN: default: vty_out(vty, "%% Unrecognized AFI (%d)\n", afi); return CMD_WARNING_CONFIG_FAILED; break; } /* If prefix has bits not under the mask, adjust it to fit */ if (!prefix_same(&p_tmp, &p)) { char buf[PREFIX2STR_BUFFER]; char buf_tmp[PREFIX2STR_BUFFER]; prefix2str(&p, buf, sizeof(buf)); prefix2str(&p_tmp, buf_tmp, sizeof(buf_tmp)); vty_out(vty, "%% Prefix-list %s prefix changed from %s to %s to match length\n", name, buf, buf_tmp); zlog_info( "Prefix-list %s prefix changed from %s to %s to match length", name, buf, buf_tmp); p = p_tmp; } /* ge and le check. */ if (genum && (genum <= p.prefixlen)) return vty_invalid_prefix_range(vty, prefix); if (lenum && (lenum < p.prefixlen)) return vty_invalid_prefix_range(vty, prefix); if (lenum && (genum > lenum)) return vty_invalid_prefix_range(vty, prefix); if (genum && (lenum == (afi == AFI_IP ? 32 : 128))) lenum = 0; /* Get prefix_list with name. */ plist = prefix_list_get(afi, 0, name); /* Make prefix entry. */ pentry = prefix_list_entry_make(&p, type, seqnum, lenum, genum, any); /* Check same policy. */ dup = prefix_entry_dup_check(plist, pentry); if (dup) { prefix_list_entry_free(pentry); return CMD_SUCCESS; } /* Install new filter to the access_list. */ prefix_list_entry_add(plist, pentry); return CMD_SUCCESS; } static int vty_prefix_list_uninstall(struct vty *vty, afi_t afi, const char *name, const char *seq, const char *typestr, const char *prefix, const char *ge, const char *le) { int ret; enum prefix_list_type type; struct prefix_list *plist; struct prefix_list_entry *pentry; struct prefix p; int64_t seqnum = -1; int lenum = 0; int genum = 0; /* Check prefix list name. */ plist = prefix_list_lookup(afi, name); if (!plist) { vty_out(vty, "%% Can't find specified prefix-list\n"); return CMD_WARNING_CONFIG_FAILED; } /* Only prefix-list name specified, delete the entire prefix-list. */ if (seq == NULL && typestr == NULL && prefix == NULL && ge == NULL && le == NULL) { prefix_list_delete(plist); return CMD_SUCCESS; } /* Check sequence number. */ if (seq) seqnum = (int64_t)atol(seq); /* Sequence number specified, but nothing else. */ if (seq && typestr == NULL && prefix == NULL && ge == NULL && le == NULL) { pentry = prefix_seq_check(plist, seqnum); if (pentry == NULL) { vty_out(vty, "%% Can't find prefix-list %s with sequence number %" PRIu64 "\n", name, seqnum); return CMD_WARNING_CONFIG_FAILED; } prefix_list_entry_delete(plist, pentry, 1); return CMD_SUCCESS; } /* ge and le number */ if (ge) genum = atoi(ge); if (le) lenum = atoi(le); /* We must have, at a minimum, both the type and prefix here */ if ((typestr == NULL) || (prefix == NULL)) return CMD_WARNING_CONFIG_FAILED; /* Check of filter type. */ if (strncmp("permit", typestr, 1) == 0) type = PREFIX_PERMIT; else if (strncmp("deny", typestr, 1) == 0) type = PREFIX_DENY; else { vty_out(vty, "%% prefix type must be permit or deny\n"); return CMD_WARNING_CONFIG_FAILED; } /* "any" is special token for matching any IPv4 addresses. */ if (afi == AFI_IP) { if (strncmp("any", prefix, strlen(prefix)) == 0) { ret = str2prefix_ipv4("0.0.0.0/0", (struct prefix_ipv4 *)&p); genum = 0; lenum = IPV4_MAX_BITLEN; } else ret = str2prefix_ipv4(prefix, (struct prefix_ipv4 *)&p); if (ret <= 0) { vty_out(vty, "%% Malformed IPv4 prefix\n"); return CMD_WARNING_CONFIG_FAILED; } } else if (afi == AFI_IP6) { if (strncmp("any", prefix, strlen(prefix)) == 0) { ret = str2prefix_ipv6("::/0", (struct prefix_ipv6 *)&p); genum = 0; lenum = IPV6_MAX_BITLEN; } else ret = str2prefix_ipv6(prefix, (struct prefix_ipv6 *)&p); if (ret <= 0) { vty_out(vty, "%% Malformed IPv6 prefix\n"); return CMD_WARNING_CONFIG_FAILED; } } /* Lookup prefix entry. */ pentry = prefix_list_entry_lookup(plist, &p, type, seqnum, lenum, genum); if (pentry == NULL) { vty_out(vty, "%% Can't find specified prefix-list\n"); return CMD_WARNING_CONFIG_FAILED; } /* Install new filter to the access_list. */ prefix_list_entry_delete(plist, pentry, 1); return CMD_SUCCESS; } static int vty_prefix_list_desc_unset(struct vty *vty, afi_t afi, const char *name) { struct prefix_list *plist; plist = prefix_list_lookup(afi, name); if (!plist) { vty_out(vty, "%% Can't find specified prefix-list\n"); return CMD_WARNING_CONFIG_FAILED; } if (plist->desc) { XFREE(MTYPE_TMP, plist->desc); plist->desc = NULL; } if (plist->head == NULL && plist->tail == NULL && plist->desc == NULL) prefix_list_delete(plist); return CMD_SUCCESS; } enum display_type { normal_display, summary_display, detail_display, sequential_display, longer_display, first_match_display }; static void vty_show_prefix_entry(struct vty *vty, afi_t afi, struct prefix_list *plist, struct prefix_master *master, enum display_type dtype, int seqnum) { struct prefix_list_entry *pentry; /* Print the name of the protocol */ vty_out(vty, "%s: ", frr_protoname); if (dtype == normal_display) { vty_out(vty, "ip%s prefix-list %s: %d entries\n", afi == AFI_IP ? "" : "v6", plist->name, plist->count); if (plist->desc) vty_out(vty, " Description: %s\n", plist->desc); } else if (dtype == summary_display || dtype == detail_display) { vty_out(vty, "ip%s prefix-list %s:\n", afi == AFI_IP ? "" : "v6", plist->name); if (plist->desc) vty_out(vty, " Description: %s\n", plist->desc); vty_out(vty, " count: %d, range entries: %d, sequences: %" PRId64 " - %" PRId64 "\n", plist->count, plist->rangecount, plist->head ? plist->head->seq : 0, plist->tail ? plist->tail->seq : 0); } if (dtype != summary_display) { for (pentry = plist->head; pentry; pentry = pentry->next) { if (dtype == sequential_display && pentry->seq != seqnum) continue; vty_out(vty, " "); if (master->seqnum) vty_out(vty, "seq %" PRId64 " ", pentry->seq); vty_out(vty, "%s ", prefix_list_type_str(pentry)); if (pentry->any) vty_out(vty, "any"); else { struct prefix *p = &pentry->prefix; char buf[BUFSIZ]; vty_out(vty, "%s/%d", inet_ntop(p->family, p->u.val, buf, BUFSIZ), p->prefixlen); if (pentry->ge) vty_out(vty, " ge %d", pentry->ge); if (pentry->le) vty_out(vty, " le %d", pentry->le); } if (dtype == detail_display || dtype == sequential_display) vty_out(vty, " (hit count: %ld, refcount: %ld)", pentry->hitcnt, pentry->refcnt); vty_out(vty, "\n"); } } } static int vty_show_prefix_list(struct vty *vty, afi_t afi, const char *name, const char *seq, enum display_type dtype) { struct prefix_list *plist; struct prefix_master *master; int64_t seqnum = 0; master = prefix_master_get(afi, 0); if (master == NULL) return CMD_WARNING; if (seq) seqnum = (int64_t)atol(seq); if (name) { plist = prefix_list_lookup(afi, name); if (!plist) { vty_out(vty, "%% Can't find specified prefix-list\n"); return CMD_WARNING; } vty_show_prefix_entry(vty, afi, plist, master, dtype, seqnum); } else { if (dtype == detail_display || dtype == summary_display) { if (master->recent) vty_out(vty, "Prefix-list with the last deletion/insertion: %s\n", master->recent->name); } for (plist = master->num.head; plist; plist = plist->next) vty_show_prefix_entry(vty, afi, plist, master, dtype, seqnum); for (plist = master->str.head; plist; plist = plist->next) vty_show_prefix_entry(vty, afi, plist, master, dtype, seqnum); } return CMD_SUCCESS; } static int vty_show_prefix_list_prefix(struct vty *vty, afi_t afi, const char *name, const char *prefix, enum display_type type) { struct prefix_list *plist; struct prefix_list_entry *pentry; struct prefix p; int ret; int match; plist = prefix_list_lookup(afi, name); if (!plist) { vty_out(vty, "%% Can't find specified prefix-list\n"); return CMD_WARNING; } ret = str2prefix(prefix, &p); if (ret <= 0) { vty_out(vty, "%% prefix is malformed\n"); return CMD_WARNING; } for (pentry = plist->head; pentry; pentry = pentry->next) { match = 0; if (type == normal_display || type == first_match_display) if (prefix_same(&p, &pentry->prefix)) match = 1; if (type == longer_display) { if ((p.family == pentry->prefix.family) && (prefix_match(&p, &pentry->prefix))) match = 1; } if (match) { vty_out(vty, " seq %" PRId64 " %s ", pentry->seq, prefix_list_type_str(pentry)); if (pentry->any) vty_out(vty, "any"); else { struct prefix *pf = &pentry->prefix; char buf[BUFSIZ]; vty_out(vty, "%s/%d", inet_ntop(pf->family, pf->u.val, buf, BUFSIZ), pf->prefixlen); if (pentry->ge) vty_out(vty, " ge %d", pentry->ge); if (pentry->le) vty_out(vty, " le %d", pentry->le); } if (type == normal_display || type == first_match_display) vty_out(vty, " (hit count: %ld, refcount: %ld)", pentry->hitcnt, pentry->refcnt); vty_out(vty, "\n"); if (type == first_match_display) return CMD_SUCCESS; } } return CMD_SUCCESS; } static int vty_clear_prefix_list(struct vty *vty, afi_t afi, const char *name, const char *prefix) { struct prefix_master *master; struct prefix_list *plist; struct prefix_list_entry *pentry; int ret; struct prefix p; master = prefix_master_get(afi, 0); if (master == NULL) return CMD_WARNING; if (name == NULL && prefix == NULL) { for (plist = master->num.head; plist; plist = plist->next) for (pentry = plist->head; pentry; pentry = pentry->next) pentry->hitcnt = 0; for (plist = master->str.head; plist; plist = plist->next) for (pentry = plist->head; pentry; pentry = pentry->next) pentry->hitcnt = 0; } else { plist = prefix_list_lookup(afi, name); if (!plist) { vty_out(vty, "%% Can't find specified prefix-list\n"); return CMD_WARNING; } if (prefix) { ret = str2prefix(prefix, &p); if (ret <= 0) { vty_out(vty, "%% prefix is malformed\n"); return CMD_WARNING; } } for (pentry = plist->head; pentry; pentry = pentry->next) { if (prefix) { if (pentry->prefix.family == p.family && prefix_match(&pentry->prefix, &p)) pentry->hitcnt = 0; } else pentry->hitcnt = 0; } } return CMD_SUCCESS; } #ifndef VTYSH_EXTRACT_PL #include "lib/plist_clippy.c" #endif DEFPY (ip_prefix_list, ip_prefix_list_cmd, "ip prefix-list WORD [seq (1-4294967295)] $action ", IP_STR PREFIX_LIST_STR "Name of a prefix list\n" "sequence number of an entry\n" "Sequence number\n" "Specify packets to reject\n" "Specify packets to forward\n" "Any prefix match. Same as \"0.0.0.0/0 le 32\"\n" "IP prefix /, e.g., 35.0.0.0/8\n" "Minimum prefix length to be matched\n" "Minimum prefix length\n" "Maximum prefix length to be matched\n" "Maximum prefix length\n") { return vty_prefix_list_install(vty, AFI_IP, prefix_list, seq_str, action, dest, ge_str, le_str); } DEFPY (no_ip_prefix_list, no_ip_prefix_list_cmd, "no ip prefix-list WORD [seq (1-4294967295)] $action ", NO_STR IP_STR PREFIX_LIST_STR "Name of a prefix list\n" "sequence number of an entry\n" "Sequence number\n" "Specify packets to reject\n" "Specify packets to forward\n" "Any prefix match. Same as \"0.0.0.0/0 le 32\"\n" "IP prefix /, e.g., 35.0.0.0/8\n" "Minimum prefix length to be matched\n" "Minimum prefix length\n" "Maximum prefix length to be matched\n" "Maximum prefix length\n") { return vty_prefix_list_uninstall(vty, AFI_IP, prefix_list, seq_str, action, dest, ge_str, le_str); } DEFPY(no_ip_prefix_list_seq, no_ip_prefix_list_seq_cmd, "no ip prefix-list WORD seq (1-4294967295)", NO_STR IP_STR PREFIX_LIST_STR "Name of a prefix list\n" "sequence number of an entry\n" "Sequence number\n") { return vty_prefix_list_uninstall(vty, AFI_IP, prefix_list, seq_str, NULL, NULL, NULL, NULL); } DEFPY (no_ip_prefix_list_all, no_ip_prefix_list_all_cmd, "no ip prefix-list WORD", NO_STR IP_STR PREFIX_LIST_STR "Name of a prefix list\n") { return vty_prefix_list_uninstall(vty, AFI_IP, prefix_list, NULL, NULL, NULL, NULL, NULL); } DEFPY (ip_prefix_list_sequence_number, ip_prefix_list_sequence_number_cmd, "[no] ip prefix-list sequence-number", NO_STR IP_STR PREFIX_LIST_STR "Include/exclude sequence numbers in NVGEN\n") { prefix_master_ipv4.seqnum = no ? false : true; return CMD_SUCCESS; } DEFUN (ip_prefix_list_description, ip_prefix_list_description_cmd, "ip prefix-list WORD description LINE...", IP_STR PREFIX_LIST_STR "Name of a prefix list\n" "Prefix-list specific description\n" "Up to 80 characters describing this prefix-list\n") { int idx_word = 2; int idx_line = 4; struct prefix_list *plist; plist = prefix_list_get(AFI_IP, 0, argv[idx_word]->arg); if (plist->desc) { XFREE(MTYPE_TMP, plist->desc); plist->desc = NULL; } plist->desc = argv_concat(argv, argc, idx_line); return CMD_SUCCESS; } DEFUN (no_ip_prefix_list_description, no_ip_prefix_list_description_cmd, "no ip prefix-list WORD description", NO_STR IP_STR PREFIX_LIST_STR "Name of a prefix list\n" "Prefix-list specific description\n") { int idx_word = 3; return vty_prefix_list_desc_unset(vty, AFI_IP, argv[idx_word]->arg); } /* ALIAS_FIXME */ DEFUN (no_ip_prefix_list_description_comment, no_ip_prefix_list_description_comment_cmd, "no ip prefix-list WORD description LINE...", NO_STR IP_STR PREFIX_LIST_STR "Name of a prefix list\n" "Prefix-list specific description\n" "Up to 80 characters describing this prefix-list\n") { return no_ip_prefix_list_description(self, vty, argc, argv); } DEFPY (show_ip_prefix_list, show_ip_prefix_list_cmd, "show ip prefix-list [WORD [seq$dseq (1-4294967295)$arg]]", SHOW_STR IP_STR PREFIX_LIST_STR "Name of a prefix list\n" "sequence number of an entry\n" "Sequence number\n") { enum display_type dtype = normal_display; if (dseq) dtype = sequential_display; return vty_show_prefix_list(vty, AFI_IP, prefix_list, arg_str, dtype); } DEFPY (show_ip_prefix_list_prefix, show_ip_prefix_list_prefix_cmd, "show ip prefix-list WORD A.B.C.D/M$prefix [longer$dl|first-match$dfm]", SHOW_STR IP_STR PREFIX_LIST_STR "Name of a prefix list\n" "IP prefix /, e.g., 35.0.0.0/8\n" "Lookup longer prefix\n" "First matched prefix\n") { enum display_type dtype = normal_display; if (dl) dtype = longer_display; else if (dfm) dtype = first_match_display; return vty_show_prefix_list_prefix(vty, AFI_IP, prefix_list, prefix_str, dtype); } DEFPY (show_ip_prefix_list_summary, show_ip_prefix_list_summary_cmd, "show ip prefix-list summary [WORD$prefix_list]", SHOW_STR IP_STR PREFIX_LIST_STR "Summary of prefix lists\n" "Name of a prefix list\n") { return vty_show_prefix_list(vty, AFI_IP, prefix_list, NULL, summary_display); } DEFPY (show_ip_prefix_list_detail, show_ip_prefix_list_detail_cmd, "show ip prefix-list detail [WORD$prefix_list]", SHOW_STR IP_STR PREFIX_LIST_STR "Detail of prefix lists\n" "Name of a prefix list\n") { return vty_show_prefix_list(vty, AFI_IP, prefix_list, NULL, detail_display); } DEFPY (clear_ip_prefix_list, clear_ip_prefix_list_cmd, "clear ip prefix-list [WORD [A.B.C.D/M$prefix]]", CLEAR_STR IP_STR PREFIX_LIST_STR "Name of a prefix list\n" "IP prefix /, e.g., 35.0.0.0/8\n") { return vty_clear_prefix_list(vty, AFI_IP, prefix_list, prefix_str); } DEFPY (ipv6_prefix_list, ipv6_prefix_list_cmd, "ipv6 prefix-list WORD [seq (1-4294967295)] $action ", IPV6_STR PREFIX_LIST_STR "Name of a prefix list\n" "sequence number of an entry\n" "Sequence number\n" "Specify packets to reject\n" "Specify packets to forward\n" "Any prefix match. Same as \"::0/0 le 128\"\n" "IPv6 prefix /, e.g., 3ffe::/16\n" "Maximum prefix length to be matched\n" "Maximum prefix length\n" "Minimum prefix length to be matched\n" "Minimum prefix length\n") { return vty_prefix_list_install(vty, AFI_IP6, prefix_list, seq_str, action, dest, ge_str, le_str); } DEFPY (no_ipv6_prefix_list, no_ipv6_prefix_list_cmd, "no ipv6 prefix-list WORD [seq (1-4294967295)] $action ", NO_STR IPV6_STR PREFIX_LIST_STR "Name of a prefix list\n" "sequence number of an entry\n" "Sequence number\n" "Specify packets to reject\n" "Specify packets to forward\n" "Any prefix match. Same as \"::0/0 le 128\"\n" "IPv6 prefix /, e.g., 3ffe::/16\n" "Maximum prefix length to be matched\n" "Maximum prefix length\n" "Minimum prefix length to be matched\n" "Minimum prefix length\n") { return vty_prefix_list_uninstall(vty, AFI_IP6, prefix_list, seq_str, action, dest, ge_str, le_str); } DEFPY (no_ipv6_prefix_list_all, no_ipv6_prefix_list_all_cmd, "no ipv6 prefix-list WORD", NO_STR IPV6_STR PREFIX_LIST_STR "Name of a prefix list\n") { return vty_prefix_list_uninstall(vty, AFI_IP6, prefix_list, NULL, NULL, NULL, NULL, NULL); } DEFPY (ipv6_prefix_list_sequence_number, ipv6_prefix_list_sequence_number_cmd, "[no] ipv6 prefix-list sequence-number", NO_STR IPV6_STR PREFIX_LIST_STR "Include/exclude sequence numbers in NVGEN\n") { prefix_master_ipv6.seqnum = no ? false : true; return CMD_SUCCESS; } DEFUN (ipv6_prefix_list_description, ipv6_prefix_list_description_cmd, "ipv6 prefix-list WORD description LINE...", IPV6_STR PREFIX_LIST_STR "Name of a prefix list\n" "Prefix-list specific description\n" "Up to 80 characters describing this prefix-list\n") { int idx_word = 2; int iddx_line = 4; struct prefix_list *plist; plist = prefix_list_get(AFI_IP6, 0, argv[idx_word]->arg); if (plist->desc) { XFREE(MTYPE_TMP, plist->desc); plist->desc = NULL; } plist->desc = argv_concat(argv, argc, iddx_line); return CMD_SUCCESS; } DEFUN (no_ipv6_prefix_list_description, no_ipv6_prefix_list_description_cmd, "no ipv6 prefix-list WORD description", NO_STR IPV6_STR PREFIX_LIST_STR "Name of a prefix list\n" "Prefix-list specific description\n") { int idx_word = 3; return vty_prefix_list_desc_unset(vty, AFI_IP6, argv[idx_word]->arg); } /* ALIAS_FIXME */ DEFUN (no_ipv6_prefix_list_description_comment, no_ipv6_prefix_list_description_comment_cmd, "no ipv6 prefix-list WORD description LINE...", NO_STR IPV6_STR PREFIX_LIST_STR "Name of a prefix list\n" "Prefix-list specific description\n" "Up to 80 characters describing this prefix-list\n") { return no_ipv6_prefix_list_description(self, vty, argc, argv); } DEFPY (show_ipv6_prefix_list, show_ipv6_prefix_list_cmd, "show ipv6 prefix-list [WORD [seq$dseq (1-4294967295)$arg]]", SHOW_STR IPV6_STR PREFIX_LIST_STR "Name of a prefix list\n" "sequence number of an entry\n" "Sequence number\n") { enum display_type dtype = normal_display; if (dseq) dtype = sequential_display; return vty_show_prefix_list(vty, AFI_IP6, prefix_list, arg_str, dtype); } DEFPY (show_ipv6_prefix_list_prefix, show_ipv6_prefix_list_prefix_cmd, "show ipv6 prefix-list WORD X:X::X:X/M$prefix [longer$dl|first-match$dfm]", SHOW_STR IPV6_STR PREFIX_LIST_STR "Name of a prefix list\n" "IPv6 prefix /, e.g., 3ffe::/16\n" "Lookup longer prefix\n" "First matched prefix\n") { enum display_type dtype = normal_display; if (dl) dtype = longer_display; else if (dfm) dtype = first_match_display; return vty_show_prefix_list_prefix(vty, AFI_IP6, prefix_list, prefix_str, dtype); } DEFPY (show_ipv6_prefix_list_summary, show_ipv6_prefix_list_summary_cmd, "show ipv6 prefix-list summary [WORD$prefix-list]", SHOW_STR IPV6_STR PREFIX_LIST_STR "Summary of prefix lists\n" "Name of a prefix list\n") { return vty_show_prefix_list(vty, AFI_IP6, prefix_list, NULL, summary_display); } DEFPY (show_ipv6_prefix_list_detail, show_ipv6_prefix_list_detail_cmd, "show ipv6 prefix-list detail [WORD$prefix-list]", SHOW_STR IPV6_STR PREFIX_LIST_STR "Detail of prefix lists\n" "Name of a prefix list\n") { return vty_show_prefix_list(vty, AFI_IP6, prefix_list, NULL, detail_display); } DEFPY (clear_ipv6_prefix_list, clear_ipv6_prefix_list_cmd, "clear ipv6 prefix-list [WORD [X:X::X:X/M$prefix]]", CLEAR_STR IPV6_STR PREFIX_LIST_STR "Name of a prefix list\n" "IPv6 prefix /, e.g., 3ffe::/16\n") { return vty_clear_prefix_list(vty, AFI_IP6, prefix_list, prefix_str); } /* Configuration write function. */ static int config_write_prefix_afi(afi_t afi, struct vty *vty) { struct prefix_list *plist; struct prefix_list_entry *pentry; struct prefix_master *master; int write = 0; master = prefix_master_get(afi, 0); if (master == NULL) return 0; if (!master->seqnum) { vty_out(vty, "no ip%s prefix-list sequence-number\n", afi == AFI_IP ? "" : "v6"); vty_out(vty, "!\n"); } for (plist = master->num.head; plist; plist = plist->next) { if (plist->desc) { vty_out(vty, "ip%s prefix-list %s description %s\n", afi == AFI_IP ? "" : "v6", plist->name, plist->desc); write++; } for (pentry = plist->head; pentry; pentry = pentry->next) { vty_out(vty, "ip%s prefix-list %s ", afi == AFI_IP ? "" : "v6", plist->name); if (master->seqnum) vty_out(vty, "seq %" PRId64 " ", pentry->seq); vty_out(vty, "%s ", prefix_list_type_str(pentry)); if (pentry->any) vty_out(vty, "any"); else { struct prefix *p = &pentry->prefix; char buf[BUFSIZ]; vty_out(vty, "%s/%d", inet_ntop(p->family, p->u.val, buf, BUFSIZ), p->prefixlen); if (pentry->ge) vty_out(vty, " ge %d", pentry->ge); if (pentry->le) vty_out(vty, " le %d", pentry->le); } vty_out(vty, "\n"); write++; } /* vty_out (vty, "!\n"); */ } for (plist = master->str.head; plist; plist = plist->next) { if (plist->desc) { vty_out(vty, "ip%s prefix-list %s description %s\n", afi == AFI_IP ? "" : "v6", plist->name, plist->desc); write++; } for (pentry = plist->head; pentry; pentry = pentry->next) { vty_out(vty, "ip%s prefix-list %s ", afi == AFI_IP ? "" : "v6", plist->name); if (master->seqnum) vty_out(vty, "seq %" PRId64 " ", pentry->seq); vty_out(vty, "%s", prefix_list_type_str(pentry)); if (pentry->any) vty_out(vty, " any"); else { struct prefix *p = &pentry->prefix; char buf[BUFSIZ]; vty_out(vty, " %s/%d", inet_ntop(p->family, p->u.val, buf, BUFSIZ), p->prefixlen); if (pentry->ge) vty_out(vty, " ge %d", pentry->ge); if (pentry->le) vty_out(vty, " le %d", pentry->le); } vty_out(vty, "\n"); write++; } } return write; } struct stream *prefix_bgp_orf_entry(struct stream *s, struct prefix_list *plist, uint8_t init_flag, uint8_t permit_flag, uint8_t deny_flag) { struct prefix_list_entry *pentry; if (!plist) return s; for (pentry = plist->head; pentry; pentry = pentry->next) { uint8_t flag = init_flag; struct prefix *p = &pentry->prefix; flag |= (pentry->type == PREFIX_PERMIT ? permit_flag : deny_flag); stream_putc(s, flag); stream_putl(s, (uint32_t)pentry->seq); stream_putc(s, (uint8_t)pentry->ge); stream_putc(s, (uint8_t)pentry->le); stream_put_prefix(s, p); } return s; } int prefix_bgp_orf_set(char *name, afi_t afi, struct orf_prefix *orfp, int permit, int set) { struct prefix_list *plist; struct prefix_list_entry *pentry; /* ge and le value check */ if (orfp->ge && orfp->ge <= orfp->p.prefixlen) return CMD_WARNING_CONFIG_FAILED; if (orfp->le && orfp->le <= orfp->p.prefixlen) return CMD_WARNING_CONFIG_FAILED; if (orfp->le && orfp->ge > orfp->le) return CMD_WARNING_CONFIG_FAILED; if (orfp->ge && orfp->le == (afi == AFI_IP ? 32 : 128)) orfp->le = 0; plist = prefix_list_get(afi, 1, name); if (!plist) return CMD_WARNING_CONFIG_FAILED; apply_mask(&orfp->p); if (set) { pentry = prefix_list_entry_make( &orfp->p, (permit ? PREFIX_PERMIT : PREFIX_DENY), orfp->seq, orfp->le, orfp->ge, 0); if (prefix_entry_dup_check(plist, pentry)) { prefix_list_entry_free(pentry); return CMD_WARNING_CONFIG_FAILED; } prefix_list_entry_add(plist, pentry); } else { pentry = prefix_list_entry_lookup( plist, &orfp->p, (permit ? PREFIX_PERMIT : PREFIX_DENY), orfp->seq, orfp->le, orfp->ge); if (!pentry) return CMD_WARNING_CONFIG_FAILED; prefix_list_entry_delete(plist, pentry, 1); } return CMD_SUCCESS; } void prefix_bgp_orf_remove_all(afi_t afi, char *name) { struct prefix_list *plist; plist = prefix_bgp_orf_lookup(afi, name); if (plist) prefix_list_delete(plist); } /* return prefix count */ int prefix_bgp_show_prefix_list(struct vty *vty, afi_t afi, char *name, bool use_json) { struct prefix_list *plist; struct prefix_list_entry *pentry; json_object *json = NULL; json_object *json_prefix = NULL; json_object *json_list = NULL; plist = prefix_bgp_orf_lookup(afi, name); if (!plist) return 0; if (!vty) return plist->count; if (use_json) { json = json_object_new_object(); json_prefix = json_object_new_object(); json_list = json_object_new_object(); json_object_int_add(json_prefix, "prefixListCounter", plist->count); json_object_string_add(json_prefix, "prefixListName", plist->name); for (pentry = plist->head; pentry; pentry = pentry->next) { struct prefix *p = &pentry->prefix; char buf_a[BUFSIZ]; char buf_b[BUFSIZ]; sprintf(buf_a, "%s/%d", inet_ntop(p->family, p->u.val, buf_b, BUFSIZ), p->prefixlen); json_object_int_add(json_list, "seq", pentry->seq); json_object_string_add(json_list, "seqPrefixListType", prefix_list_type_str(pentry)); if (pentry->ge) json_object_int_add(json_list, "ge", pentry->ge); if (pentry->le) json_object_int_add(json_list, "le", pentry->le); json_object_object_add(json_prefix, buf_a, json_list); } if (afi == AFI_IP) json_object_object_add(json, "ipPrefixList", json_prefix); else json_object_object_add(json, "ipv6PrefixList", json_prefix); vty_out(vty, "%s\n", json_object_to_json_string_ext( json, JSON_C_TO_STRING_PRETTY)); json_object_free(json); } else { vty_out(vty, "ip%s prefix-list %s: %d entries\n", afi == AFI_IP ? "" : "v6", plist->name, plist->count); for (pentry = plist->head; pentry; pentry = pentry->next) { struct prefix *p = &pentry->prefix; char buf[BUFSIZ]; vty_out(vty, " seq %" PRId64 " %s %s/%d", pentry->seq, prefix_list_type_str(pentry), inet_ntop(p->family, p->u.val, buf, BUFSIZ), p->prefixlen); if (pentry->ge) vty_out(vty, " ge %d", pentry->ge); if (pentry->le) vty_out(vty, " le %d", pentry->le); vty_out(vty, "\n"); } } return plist->count; } static void prefix_list_reset_afi(afi_t afi, int orf) { struct prefix_list *plist; struct prefix_list *next; struct prefix_master *master; master = prefix_master_get(afi, orf); if (master == NULL) return; for (plist = master->num.head; plist; plist = next) { next = plist->next; prefix_list_delete(plist); } for (plist = master->str.head; plist; plist = next) { next = plist->next; prefix_list_delete(plist); } assert(master->num.head == NULL); assert(master->num.tail == NULL); assert(master->str.head == NULL); assert(master->str.tail == NULL); master->seqnum = 1; master->recent = NULL; } /* Prefix-list node. */ static struct cmd_node prefix_node = {PREFIX_NODE, "", /* Prefix list has no interface. */ 1}; static int config_write_prefix_ipv4(struct vty *vty) { return config_write_prefix_afi(AFI_IP, vty); } static void plist_autocomplete_afi(afi_t afi, vector comps, struct cmd_token *token) { struct prefix_list *plist; struct prefix_master *master; master = prefix_master_get(afi, 0); if (master == NULL) return; for (plist = master->str.head; plist; plist = plist->next) vector_set(comps, XSTRDUP(MTYPE_COMPLETION, plist->name)); for (plist = master->num.head; plist; plist = plist->next) vector_set(comps, XSTRDUP(MTYPE_COMPLETION, plist->name)); } static void plist_autocomplete(vector comps, struct cmd_token *token) { plist_autocomplete_afi(AFI_IP, comps, token); plist_autocomplete_afi(AFI_IP6, comps, token); } static const struct cmd_variable_handler plist_var_handlers[] = { {/* "prefix-list WORD" */ .varname = "prefix_list", .completions = plist_autocomplete}, {.completions = NULL}}; static void prefix_list_init_ipv4(void) { install_node(&prefix_node, config_write_prefix_ipv4); install_element(CONFIG_NODE, &ip_prefix_list_cmd); install_element(CONFIG_NODE, &no_ip_prefix_list_cmd); install_element(CONFIG_NODE, &no_ip_prefix_list_seq_cmd); install_element(CONFIG_NODE, &no_ip_prefix_list_all_cmd); install_element(CONFIG_NODE, &ip_prefix_list_description_cmd); install_element(CONFIG_NODE, &no_ip_prefix_list_description_cmd); install_element(CONFIG_NODE, &no_ip_prefix_list_description_comment_cmd); install_element(CONFIG_NODE, &ip_prefix_list_sequence_number_cmd); install_element(VIEW_NODE, &show_ip_prefix_list_cmd); install_element(VIEW_NODE, &show_ip_prefix_list_prefix_cmd); install_element(VIEW_NODE, &show_ip_prefix_list_summary_cmd); install_element(VIEW_NODE, &show_ip_prefix_list_detail_cmd); install_element(ENABLE_NODE, &clear_ip_prefix_list_cmd); } /* Prefix-list node. */ static struct cmd_node prefix_ipv6_node = { PREFIX_IPV6_NODE, "", /* Prefix list has no interface. */ 1}; static int config_write_prefix_ipv6(struct vty *vty) { return config_write_prefix_afi(AFI_IP6, vty); } static void prefix_list_init_ipv6(void) { install_node(&prefix_ipv6_node, config_write_prefix_ipv6); install_element(CONFIG_NODE, &ipv6_prefix_list_cmd); install_element(CONFIG_NODE, &no_ipv6_prefix_list_cmd); install_element(CONFIG_NODE, &no_ipv6_prefix_list_all_cmd); install_element(CONFIG_NODE, &ipv6_prefix_list_description_cmd); install_element(CONFIG_NODE, &no_ipv6_prefix_list_description_cmd); install_element(CONFIG_NODE, &no_ipv6_prefix_list_description_comment_cmd); install_element(CONFIG_NODE, &ipv6_prefix_list_sequence_number_cmd); install_element(VIEW_NODE, &show_ipv6_prefix_list_cmd); install_element(VIEW_NODE, &show_ipv6_prefix_list_prefix_cmd); install_element(VIEW_NODE, &show_ipv6_prefix_list_summary_cmd); install_element(VIEW_NODE, &show_ipv6_prefix_list_detail_cmd); install_element(ENABLE_NODE, &clear_ipv6_prefix_list_cmd); } void prefix_list_init(void) { cmd_variable_handler_register(plist_var_handlers); prefix_list_init_ipv4(); prefix_list_init_ipv6(); } void prefix_list_reset(void) { prefix_list_reset_afi(AFI_IP, 0); prefix_list_reset_afi(AFI_IP6, 0); prefix_list_reset_afi(AFI_IP, 1); prefix_list_reset_afi(AFI_IP6, 1); } frr-7.2.1/lib/plist.h0000644000000000000000000000471513610377563011275 00000000000000/* * Prefix list functions. * Copyright (C) 1999 Kunihiro Ishiguro * * This file is part of GNU Zebra. * * GNU Zebra is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License as published * by the Free Software Foundation; either version 2, or (at your * option) any later version. * * GNU Zebra 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; see the file COPYING; if not, write to the Free Software * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA */ #ifndef _QUAGGA_PLIST_H #define _QUAGGA_PLIST_H #include #include "stream.h" #include "vty.h" #ifdef __cplusplus extern "C" { #endif enum prefix_list_type { PREFIX_DENY, PREFIX_PERMIT, }; struct prefix_list; struct orf_prefix { uint32_t seq; uint8_t ge; uint8_t le; struct prefix p; }; /* Prototypes. */ extern void prefix_list_init(void); extern void prefix_list_reset(void); extern void prefix_list_add_hook(void (*func)(struct prefix_list *)); extern void prefix_list_delete_hook(void (*func)(struct prefix_list *)); extern const char *prefix_list_name(struct prefix_list *); extern afi_t prefix_list_afi(struct prefix_list *); extern struct prefix_list *prefix_list_lookup(afi_t, const char *); /* * prefix_list_apply_which_prefix * * Allow calling function to learn which prefix * caused the DENY or PERMIT. * * If no pointer is sent in, do not return anything. * If it is a empty plist return a NULL pointer. */ extern enum prefix_list_type prefix_list_apply_which_prefix(struct prefix_list *plist, const struct prefix **which, const void *object); #define prefix_list_apply(A, B) prefix_list_apply_which_prefix((A), NULL, (B)) extern struct prefix_list *prefix_bgp_orf_lookup(afi_t, const char *); extern struct stream *prefix_bgp_orf_entry(struct stream *, struct prefix_list *, uint8_t, uint8_t, uint8_t); extern int prefix_bgp_orf_set(char *, afi_t, struct orf_prefix *, int, int); extern void prefix_bgp_orf_remove_all(afi_t, char *); extern int prefix_bgp_show_prefix_list(struct vty *vty, afi_t afi, char *name, bool use_json); #ifdef __cplusplus } #endif #endif /* _QUAGGA_PLIST_H */ frr-7.2.1/lib/plist_int.h0000644000000000000000000000331013610377563012135 00000000000000/* * Prefix list internal definitions. * Copyright (C) 1999 Kunihiro Ishiguro * * This file is part of GNU Zebra. * * GNU Zebra is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License as published * by the Free Software Foundation; either version 2, or (at your * option) any later version. * * GNU Zebra 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; see the file COPYING; if not, write to the Free Software * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA */ #ifndef _QUAGGA_PLIST_INT_H #define _QUAGGA_PLIST_INT_H #ifdef __cplusplus extern "C" { #endif enum prefix_name_type { PREFIX_TYPE_STRING, PREFIX_TYPE_NUMBER }; struct pltrie_table; struct prefix_list { char *name; char *desc; struct prefix_master *master; enum prefix_name_type type; int count; int rangecount; struct prefix_list_entry *head; struct prefix_list_entry *tail; struct pltrie_table *trie; struct prefix_list *next; struct prefix_list *prev; }; /* Each prefix-list's entry. */ struct prefix_list_entry { int64_t seq; int le; int ge; enum prefix_list_type type; int any; struct prefix prefix; unsigned long refcnt; unsigned long hitcnt; struct prefix_list_entry *next; struct prefix_list_entry *prev; /* up the chain for best match search */ struct prefix_list_entry *next_best; }; #ifdef __cplusplus } #endif #endif /* _QUAGGA_PLIST_INT_H */ frr-7.2.1/lib/prefix.c0000644000000000000000000013166413610377563011436 00000000000000/* * Prefix related functions. * Copyright (C) 1997, 98, 99 Kunihiro Ishiguro * * This file is part of GNU Zebra. * * GNU Zebra is free software; you can redistribute it and/or modify it * under the terms of the GNU General Public License as published by the * Free Software Foundation; either version 2, or (at your option) any * later version. * * GNU Zebra 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; see the file COPYING; if not, write to the Free Software * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA */ #include #include "prefix.h" #include "vty.h" #include "sockunion.h" #include "memory.h" #include "log.h" #include "jhash.h" #include "lib_errors.h" #include "printfrr.h" DEFINE_MTYPE_STATIC(LIB, PREFIX, "Prefix") DEFINE_MTYPE_STATIC(LIB, PREFIX_FLOWSPEC, "Prefix Flowspec") /* Maskbit. */ static const uint8_t maskbit[] = {0x00, 0x80, 0xc0, 0xe0, 0xf0, 0xf8, 0xfc, 0xfe, 0xff}; static const struct in6_addr maskbytes6[] = { /* /0 */ {{{0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00}}}, /* /1 */ {{{0x80, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00}}}, /* /2 */ {{{0xc0, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00}}}, /* /3 */ {{{0xe0, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00}}}, /* /4 */ {{{0xf0, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00}}}, /* /5 */ {{{0xf8, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00}}}, /* /6 */ {{{0xfc, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00}}}, /* /7 */ {{{0xfe, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00}}}, /* /8 */ {{{0xff, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00}}}, /* /9 */ {{{0xff, 0x80, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00}}}, /* /10 */ {{{0xff, 0xc0, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00}}}, /* /11 */ {{{0xff, 0xe0, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00}}}, /* /12 */ {{{0xff, 0xf0, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00}}}, /* /13 */ {{{0xff, 0xf8, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00}}}, /* /14 */ {{{0xff, 0xfc, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00}}}, /* /15 */ {{{0xff, 0xfe, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00}}}, /* /16 */ {{{0xff, 0xff, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00}}}, /* /17 */ {{{0xff, 0xff, 0x80, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00}}}, /* /18 */ {{{0xff, 0xff, 0xc0, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00}}}, /* /19 */ {{{0xff, 0xff, 0xe0, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00}}}, /* /20 */ {{{0xff, 0xff, 0xf0, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00}}}, /* /21 */ {{{0xff, 0xff, 0xf8, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00}}}, /* /22 */ {{{0xff, 0xff, 0xfc, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00}}}, /* /23 */ {{{0xff, 0xff, 0xfe, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00}}}, /* /24 */ {{{0xff, 0xff, 0xff, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00}}}, /* /25 */ {{{0xff, 0xff, 0xff, 0x80, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00}}}, /* /26 */ {{{0xff, 0xff, 0xff, 0xc0, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00}}}, /* /27 */ {{{0xff, 0xff, 0xff, 0xe0, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00}}}, /* /28 */ {{{0xff, 0xff, 0xff, 0xf0, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00}}}, /* /29 */ {{{0xff, 0xff, 0xff, 0xf8, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00}}}, /* /30 */ {{{0xff, 0xff, 0xff, 0xfc, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00}}}, /* /31 */ {{{0xff, 0xff, 0xff, 0xfe, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00}}}, /* /32 */ {{{0xff, 0xff, 0xff, 0xff, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00}}}, /* /33 */ {{{0xff, 0xff, 0xff, 0xff, 0x80, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00}}}, /* /34 */ {{{0xff, 0xff, 0xff, 0xff, 0xc0, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00}}}, /* /35 */ {{{0xff, 0xff, 0xff, 0xff, 0xe0, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00}}}, /* /36 */ {{{0xff, 0xff, 0xff, 0xff, 0xf0, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00}}}, /* /37 */ {{{0xff, 0xff, 0xff, 0xff, 0xf8, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00}}}, /* /38 */ {{{0xff, 0xff, 0xff, 0xff, 0xfc, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00}}}, /* /39 */ {{{0xff, 0xff, 0xff, 0xff, 0xfe, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00}}}, /* /40 */ {{{0xff, 0xff, 0xff, 0xff, 0xff, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00}}}, /* /41 */ {{{0xff, 0xff, 0xff, 0xff, 0xff, 0x80, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00}}}, /* /42 */ {{{0xff, 0xff, 0xff, 0xff, 0xff, 0xc0, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00}}}, /* /43 */ {{{0xff, 0xff, 0xff, 0xff, 0xff, 0xe0, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00}}}, /* /44 */ {{{0xff, 0xff, 0xff, 0xff, 0xff, 0xf0, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00}}}, /* /45 */ {{{0xff, 0xff, 0xff, 0xff, 0xff, 0xf8, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00}}}, /* /46 */ {{{0xff, 0xff, 0xff, 0xff, 0xff, 0xfc, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00}}}, /* /47 */ {{{0xff, 0xff, 0xff, 0xff, 0xff, 0xfe, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00}}}, /* /48 */ {{{0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00}}}, /* /49 */ {{{0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0x80, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00}}}, /* /50 */ {{{0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xc0, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00}}}, /* /51 */ {{{0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xe0, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00}}}, /* /52 */ {{{0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xf0, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00}}}, /* /53 */ {{{0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xf8, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00}}}, /* /54 */ {{{0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xfc, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00}}}, /* /55 */ {{{0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xfe, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00}}}, /* /56 */ {{{0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00}}}, /* /57 */ {{{0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0x80, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00}}}, /* /58 */ {{{0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xc0, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00}}}, /* /59 */ {{{0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xe0, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00}}}, /* /60 */ {{{0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xf0, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00}}}, /* /61 */ {{{0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xf8, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00}}}, /* /62 */ {{{0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xfc, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00}}}, /* /63 */ {{{0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xfe, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00}}}, /* /64 */ {{{0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00}}}, /* /65 */ {{{0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0x80, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00}}}, /* /66 */ {{{0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xc0, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00}}}, /* /67 */ {{{0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xe0, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00}}}, /* /68 */ {{{0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xf0, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00}}}, /* /69 */ {{{0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xf8, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00}}}, /* /70 */ {{{0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xfc, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00}}}, /* /71 */ {{{0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xfe, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00}}}, /* /72 */ {{{0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00}}}, /* /73 */ {{{0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0x80, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00}}}, /* /74 */ {{{0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xc0, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00}}}, /* /75 */ {{{0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xe0, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00}}}, /* /76 */ {{{0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xf0, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00}}}, /* /77 */ {{{0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xf8, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00}}}, /* /78 */ {{{0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xfc, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00}}}, /* /79 */ {{{0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xfe, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00}}}, /* /80 */ {{{0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00}}}, /* /81 */ {{{0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0x80, 0x00, 0x00, 0x00, 0x00, 0x00}}}, /* /82 */ {{{0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xc0, 0x00, 0x00, 0x00, 0x00, 0x00}}}, /* /83 */ {{{0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xe0, 0x00, 0x00, 0x00, 0x00, 0x00}}}, /* /84 */ {{{0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xf0, 0x00, 0x00, 0x00, 0x00, 0x00}}}, /* /85 */ {{{0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xf8, 0x00, 0x00, 0x00, 0x00, 0x00}}}, /* /86 */ {{{0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xfc, 0x00, 0x00, 0x00, 0x00, 0x00}}}, /* /87 */ {{{0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xfe, 0x00, 0x00, 0x00, 0x00, 0x00}}}, /* /88 */ {{{0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0x00, 0x00, 0x00, 0x00, 0x00}}}, /* /89 */ {{{0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0x80, 0x00, 0x00, 0x00, 0x00}}}, /* /90 */ {{{0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xc0, 0x00, 0x00, 0x00, 0x00}}}, /* /91 */ {{{0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xe0, 0x00, 0x00, 0x00, 0x00}}}, /* /92 */ {{{0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xf0, 0x00, 0x00, 0x00, 0x00}}}, /* /93 */ {{{0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xf8, 0x00, 0x00, 0x00, 0x00}}}, /* /94 */ {{{0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xfc, 0x00, 0x00, 0x00, 0x00}}}, /* /95 */ {{{0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xfe, 0x00, 0x00, 0x00, 0x00}}}, /* /96 */ {{{0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0x00, 0x00, 0x00, 0x00}}}, /* /97 */ {{{0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0x80, 0x00, 0x00, 0x00}}}, /* /98 */ {{{0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xc0, 0x00, 0x00, 0x00}}}, /* /99 */ {{{0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xe0, 0x00, 0x00, 0x00}}}, /* /100 */ {{{0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xf0, 0x00, 0x00, 0x00}}}, /* /101 */ {{{0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xf8, 0x00, 0x00, 0x00}}}, /* /102 */ {{{0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xfc, 0x00, 0x00, 0x00}}}, /* /103 */ {{{0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xfe, 0x00, 0x00, 0x00}}}, /* /104 */ {{{0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0x00, 0x00, 0x00}}}, /* /105 */ {{{0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0x80, 0x00, 0x00}}}, /* /106 */ {{{0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xc0, 0x00, 0x00}}}, /* /107 */ {{{0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xe0, 0x00, 0x00}}}, /* /108 */ {{{0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xf0, 0x00, 0x00}}}, /* /109 */ {{{0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xf8, 0x00, 0x00}}}, /* /110 */ {{{0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xfc, 0x00, 0x00}}}, /* /111 */ {{{0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xfe, 0x00, 0x00}}}, /* /112 */ {{{0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0x00, 0x00}}}, /* /113 */ {{{0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0x80, 0x00}}}, /* /114 */ {{{0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xc0, 0x00}}}, /* /115 */ {{{0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xe0, 0x00}}}, /* /116 */ {{{0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xf0, 0x00}}}, /* /117 */ {{{0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xf8, 0x00}}}, /* /118 */ {{{0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xfc, 0x00}}}, /* /119 */ {{{0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xfe, 0x00}}}, /* /120 */ {{{0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0x00}}}, /* /121 */ {{{0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0x80}}}, /* /122 */ {{{0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xc0}}}, /* /123 */ {{{0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xe0}}}, /* /124 */ {{{0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xf0}}}, /* /125 */ {{{0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xf8}}}, /* /126 */ {{{0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xfc}}}, /* /127 */ {{{0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xfe}}}, /* /128 */ {{{0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff}}}}; /* Number of bits in prefix type. */ #ifndef PNBBY #define PNBBY 8 #endif /* PNBBY */ #define MASKBIT(offset) ((0xff << (PNBBY - (offset))) & 0xff) void prefix_hexdump(const struct prefix *p) { char buf[PREFIX_STRLEN]; zlog_debug("prefix: %s", prefix2str(p, buf, sizeof(buf))); zlog_hexdump(p, sizeof(struct prefix)); } int is_zero_mac(const struct ethaddr *mac) { int i = 0; for (i = 0; i < ETH_ALEN; i++) { if (mac->octet[i]) return 0; } return 1; } unsigned int prefix_bit(const uint8_t *prefix, const uint16_t prefixlen) { unsigned int offset = prefixlen / 8; unsigned int shift = 7 - (prefixlen % 8); return (prefix[offset] >> shift) & 1; } unsigned int prefix6_bit(const struct in6_addr *prefix, const uint16_t prefixlen) { return prefix_bit((const uint8_t *)&prefix->s6_addr, prefixlen); } int str2family(const char *string) { if (!strcmp("ipv4", string)) return AF_INET; else if (!strcmp("ipv6", string)) return AF_INET6; else if (!strcmp("ethernet", string)) return AF_ETHERNET; else if (!strcmp("evpn", string)) return AF_EVPN; return -1; } const char *family2str(int family) { switch (family) { case AF_INET: return "IPv4"; case AF_INET6: return "IPv6"; case AF_ETHERNET: return "Ethernet"; case AF_EVPN: return "Evpn"; } return "?"; } /* Address Famiy Identifier to Address Family converter. */ int afi2family(afi_t afi) { if (afi == AFI_IP) return AF_INET; else if (afi == AFI_IP6) return AF_INET6; else if (afi == AFI_L2VPN) return AF_ETHERNET; /* NOTE: EVPN code should NOT use this interface. */ return 0; } afi_t family2afi(int family) { if (family == AF_INET) return AFI_IP; else if (family == AF_INET6) return AFI_IP6; else if (family == AF_ETHERNET || family == AF_EVPN) return AFI_L2VPN; return 0; } const char *afi2str(afi_t afi) { switch (afi) { case AFI_IP: return "IPv4"; case AFI_IP6: return "IPv6"; case AFI_L2VPN: return "l2vpn"; case AFI_MAX: return "bad-value"; default: break; } return NULL; } const char *safi2str(safi_t safi) { switch (safi) { case SAFI_UNICAST: return "unicast"; case SAFI_MULTICAST: return "multicast"; case SAFI_MPLS_VPN: return "vpn"; case SAFI_ENCAP: return "encap"; case SAFI_EVPN: return "evpn"; case SAFI_LABELED_UNICAST: return "labeled-unicast"; case SAFI_FLOWSPEC: return "flowspec"; default: return "unknown"; } } /* If n includes p prefix then return 1 else return 0. */ int prefix_match(const struct prefix *n, const struct prefix *p) { int offset; int shift; const uint8_t *np, *pp; /* If n's prefix is longer than p's one return 0. */ if (n->prefixlen > p->prefixlen) return 0; if (n->family == AF_FLOWSPEC) { /* prefixlen is unused. look at fs prefix len */ if (n->u.prefix_flowspec.prefixlen > p->u.prefix_flowspec.prefixlen) return 0; /* Set both prefix's head pointer. */ np = (const uint8_t *)&n->u.prefix_flowspec.ptr; pp = (const uint8_t *)&p->u.prefix_flowspec.ptr; offset = n->u.prefix_flowspec.prefixlen; while (offset--) if (np[offset] != pp[offset]) return 0; return 1; } /* Set both prefix's head pointer. */ np = n->u.val; pp = p->u.val; offset = n->prefixlen / PNBBY; shift = n->prefixlen % PNBBY; if (shift) if (maskbit[shift] & (np[offset] ^ pp[offset])) return 0; while (offset--) if (np[offset] != pp[offset]) return 0; return 1; } /* If n includes p then return 1 else return 0. Prefix mask is not considered */ int prefix_match_network_statement(const struct prefix *n, const struct prefix *p) { int offset; int shift; const uint8_t *np, *pp; /* Set both prefix's head pointer. */ np = n->u.val; pp = p->u.val; offset = n->prefixlen / PNBBY; shift = n->prefixlen % PNBBY; if (shift) if (maskbit[shift] & (np[offset] ^ pp[offset])) return 0; while (offset--) if (np[offset] != pp[offset]) return 0; return 1; } #ifdef __clang_analyzer__ #undef prefix_copy /* cf. prefix.h */ #endif void prefix_copy(union prefixptr udest, union prefixconstptr usrc) { struct prefix *dest = udest.p; const struct prefix *src = usrc.p; dest->family = src->family; dest->prefixlen = src->prefixlen; if (src->family == AF_INET) dest->u.prefix4 = src->u.prefix4; else if (src->family == AF_INET6) dest->u.prefix6 = src->u.prefix6; else if (src->family == AF_ETHERNET) { memcpy(&dest->u.prefix_eth, &src->u.prefix_eth, sizeof(struct ethaddr)); } else if (src->family == AF_EVPN) { memcpy(&dest->u.prefix_evpn, &src->u.prefix_evpn, sizeof(struct evpn_addr)); } else if (src->family == AF_UNSPEC) { dest->u.lp.id = src->u.lp.id; dest->u.lp.adv_router = src->u.lp.adv_router; } else if (src->family == AF_FLOWSPEC) { void *temp; int len; len = src->u.prefix_flowspec.prefixlen; dest->u.prefix_flowspec.prefixlen = src->u.prefix_flowspec.prefixlen; dest->family = src->family; temp = XCALLOC(MTYPE_PREFIX_FLOWSPEC, len); dest->u.prefix_flowspec.ptr = (uintptr_t)temp; memcpy((void *)dest->u.prefix_flowspec.ptr, (void *)src->u.prefix_flowspec.ptr, len); } else { flog_err(EC_LIB_DEVELOPMENT, "prefix_copy(): Unknown address family %d", src->family); assert(0); } } /* * Return 1 if the address/netmask contained in the prefix structure * is the same, and else return 0. For this routine, 'same' requires * that not only the prefix length and the network part be the same, * but also the host part. Thus, 10.0.0.1/8 and 10.0.0.2/8 are not * the same. Note that this routine has the same return value sense * as '==' (which is different from prefix_cmp). */ int prefix_same(union prefixconstptr up1, union prefixconstptr up2) { const struct prefix *p1 = up1.p; const struct prefix *p2 = up2.p; if ((p1 && !p2) || (!p1 && p2)) return 0; if (!p1 && !p2) return 1; if (p1->family == p2->family && p1->prefixlen == p2->prefixlen) { if (p1->family == AF_INET) if (IPV4_ADDR_SAME(&p1->u.prefix4, &p2->u.prefix4)) return 1; if (p1->family == AF_INET6) if (IPV6_ADDR_SAME(&p1->u.prefix6.s6_addr, &p2->u.prefix6.s6_addr)) return 1; if (p1->family == AF_ETHERNET) if (!memcmp(&p1->u.prefix_eth, &p2->u.prefix_eth, sizeof(struct ethaddr))) return 1; if (p1->family == AF_EVPN) if (!memcmp(&p1->u.prefix_evpn, &p2->u.prefix_evpn, sizeof(struct evpn_addr))) return 1; if (p1->family == AF_FLOWSPEC) { if (p1->u.prefix_flowspec.prefixlen != p2->u.prefix_flowspec.prefixlen) return 0; if (!memcmp(&p1->u.prefix_flowspec.ptr, &p2->u.prefix_flowspec.ptr, p2->u.prefix_flowspec.prefixlen)) return 1; } } return 0; } /* * Return -1/0/1 comparing the prefixes in a way that gives a full/linear * order. * * Network prefixes are considered the same if the prefix lengths are equal * and the network parts are the same. Host bits (which are considered masked * by the prefix length) are not significant. Thus, 10.0.0.1/8 and * 10.0.0.2/8 are considered equivalent by this routine. Note that * this routine has the same return sense as strcmp (which is different * from prefix_same). */ int prefix_cmp(union prefixconstptr up1, union prefixconstptr up2) { const struct prefix *p1 = up1.p; const struct prefix *p2 = up2.p; int offset; int shift; int i; /* Set both prefix's head pointer. */ const uint8_t *pp1; const uint8_t *pp2; if (p1->family != p2->family) return numcmp(p1->family, p2->family); if (p1->family == AF_FLOWSPEC) { pp1 = (const uint8_t *)p1->u.prefix_flowspec.ptr; pp2 = (const uint8_t *)p2->u.prefix_flowspec.ptr; if (p1->u.prefix_flowspec.prefixlen != p2->u.prefix_flowspec.prefixlen) return numcmp(p1->u.prefix_flowspec.prefixlen, p2->u.prefix_flowspec.prefixlen); offset = p1->u.prefix_flowspec.prefixlen; while (offset--) if (pp1[offset] != pp2[offset]) return numcmp(pp1[offset], pp2[offset]); return 0; } pp1 = p1->u.val; pp2 = p2->u.val; if (p1->prefixlen != p2->prefixlen) return numcmp(p1->prefixlen, p2->prefixlen); offset = p1->prefixlen / PNBBY; shift = p1->prefixlen % PNBBY; i = memcmp(pp1, pp2, offset); if (i) return i; /* * At this point offset was the same, if we have shift * that means we still have data to compare, if shift is * 0 then we are at the end of the data structure * and should just return, as that we will be accessing * memory beyond the end of the party zone */ if (shift) return numcmp(pp1[offset] & maskbit[shift], pp2[offset] & maskbit[shift]); return 0; } /* * Count the number of common bits in 2 prefixes. The prefix length is * ignored for this function; the whole prefix is compared. If the prefix * address families don't match, return -1; otherwise the return value is * in range 0 ... maximum prefix length for the address family. */ int prefix_common_bits(const struct prefix *p1, const struct prefix *p2) { int pos, bit; int length = 0; uint8_t xor ; /* Set both prefix's head pointer. */ const uint8_t *pp1 = p1->u.val; const uint8_t *pp2 = p2->u.val; if (p1->family == AF_INET) length = IPV4_MAX_BYTELEN; if (p1->family == AF_INET6) length = IPV6_MAX_BYTELEN; if (p1->family == AF_ETHERNET) length = ETH_ALEN; if (p1->family == AF_EVPN) length = 8 * sizeof(struct evpn_addr); if (p1->family != p2->family || !length) return -1; for (pos = 0; pos < length; pos++) if (pp1[pos] != pp2[pos]) break; if (pos == length) return pos * 8; xor = pp1[pos] ^ pp2[pos]; for (bit = 0; bit < 8; bit++) if (xor&(1 << (7 - bit))) break; return pos * 8 + bit; } /* Return prefix family type string. */ const char *prefix_family_str(const struct prefix *p) { if (p->family == AF_INET) return "inet"; if (p->family == AF_INET6) return "inet6"; if (p->family == AF_ETHERNET) return "ether"; if (p->family == AF_EVPN) return "evpn"; return "unspec"; } /* Allocate new prefix_ipv4 structure. */ struct prefix_ipv4 *prefix_ipv4_new(void) { struct prefix_ipv4 *p; /* Call prefix_new to allocate a full-size struct prefix to avoid problems where the struct prefix_ipv4 is cast to struct prefix and unallocated bytes were being referenced (e.g. in structure assignments). */ p = (struct prefix_ipv4 *)prefix_new(); p->family = AF_INET; return p; } /* Free prefix_ipv4 structure. */ void prefix_ipv4_free(struct prefix_ipv4 *p) { prefix_free((struct prefix *)p); } /* If given string is valid return 1 else return 0 */ int str2prefix_ipv4(const char *str, struct prefix_ipv4 *p) { int ret; int plen; char *pnt; char *cp; /* Find slash inside string. */ pnt = strchr(str, '/'); /* String doesn't contail slash. */ if (pnt == NULL) { /* Convert string to prefix. */ ret = inet_pton(AF_INET, str, &p->prefix); if (ret == 0) return 0; /* If address doesn't contain slash we assume it host address. */ p->family = AF_INET; p->prefixlen = IPV4_MAX_BITLEN; return ret; } else { cp = XMALLOC(MTYPE_TMP, (pnt - str) + 1); memcpy(cp, str, pnt - str); *(cp + (pnt - str)) = '\0'; ret = inet_pton(AF_INET, cp, &p->prefix); XFREE(MTYPE_TMP, cp); if (ret == 0) return 0; /* Get prefix length. */ plen = (uint8_t)atoi(++pnt); if (plen > IPV4_MAX_PREFIXLEN) return 0; p->family = AF_INET; p->prefixlen = plen; } return ret; } /* When string format is invalid return 0. */ int str2prefix_eth(const char *str, struct prefix_eth *p) { int ret = 0; int plen = 48; char *pnt; char *cp = NULL; const char *str_addr = str; unsigned int a[6]; int i; bool slash = false; if (!strcmp(str, "any")) { memset(p, 0, sizeof(*p)); p->family = AF_ETHERNET; return 1; } /* Find slash inside string. */ pnt = strchr(str, '/'); if (pnt) { /* Get prefix length. */ plen = (uint8_t)atoi(++pnt); if (plen > 48) { ret = 0; goto done; } cp = XMALLOC(MTYPE_TMP, (pnt - str) + 1); memcpy(cp, str, pnt - str); *(cp + (pnt - str)) = '\0'; str_addr = cp; slash = true; } /* Convert string to prefix. */ if (sscanf(str_addr, "%2x:%2x:%2x:%2x:%2x:%2x", a + 0, a + 1, a + 2, a + 3, a + 4, a + 5) != 6) { ret = 0; goto done; } for (i = 0; i < 6; ++i) { p->eth_addr.octet[i] = a[i] & 0xff; } p->prefixlen = plen; p->family = AF_ETHERNET; /* * special case to allow old configurations to work * Since all zero's is implicitly meant to allow * a comparison to zero, let's assume */ if (!slash && is_zero_mac(&(p->eth_addr))) p->prefixlen = 0; ret = 1; done: XFREE(MTYPE_TMP, cp); return ret; } /* Convert masklen into IP address's netmask (network byte order). */ void masklen2ip(const int masklen, struct in_addr *netmask) { assert(masklen >= 0 && masklen <= IPV4_MAX_BITLEN); /* left shift is only defined for less than the size of the type. * we unconditionally use long long in case the target platform * has defined behaviour for << 32 (or has a 64-bit left shift) */ if (sizeof(unsigned long long) > 4) netmask->s_addr = htonl(0xffffffffULL << (32 - masklen)); else netmask->s_addr = htonl(masklen ? 0xffffffffU << (32 - masklen) : 0); } /* Convert IP address's netmask into integer. We assume netmask is * sequential one. Argument netmask should be network byte order. */ uint8_t ip_masklen(struct in_addr netmask) { uint32_t tmp = ~ntohl(netmask.s_addr); /* * clz: count leading zeroes. sadly, the behaviour of this builtin is * undefined for a 0 argument, even though most CPUs give 32 */ return tmp ? __builtin_clz(tmp) : 32; } /* Apply mask to IPv4 prefix (network byte order). */ void apply_mask_ipv4(struct prefix_ipv4 *p) { struct in_addr mask; masklen2ip(p->prefixlen, &mask); p->prefix.s_addr &= mask.s_addr; } /* If prefix is 0.0.0.0/0 then return 1 else return 0. */ int prefix_ipv4_any(const struct prefix_ipv4 *p) { return (p->prefix.s_addr == 0 && p->prefixlen == 0); } /* Allocate a new ip version 6 route */ struct prefix_ipv6 *prefix_ipv6_new(void) { struct prefix_ipv6 *p; /* Allocate a full-size struct prefix to avoid problems with structure size mismatches. */ p = (struct prefix_ipv6 *)prefix_new(); p->family = AF_INET6; return p; } /* Free prefix for IPv6. */ void prefix_ipv6_free(struct prefix_ipv6 *p) { prefix_free((struct prefix *)p); } /* If given string is valid return 1 else return 0 */ int str2prefix_ipv6(const char *str, struct prefix_ipv6 *p) { char *pnt; char *cp; int ret; pnt = strchr(str, '/'); /* If string doesn't contain `/' treat it as host route. */ if (pnt == NULL) { ret = inet_pton(AF_INET6, str, &p->prefix); if (ret == 0) return 0; p->prefixlen = IPV6_MAX_BITLEN; } else { int plen; cp = XMALLOC(MTYPE_TMP, (pnt - str) + 1); memcpy(cp, str, pnt - str); *(cp + (pnt - str)) = '\0'; ret = inet_pton(AF_INET6, cp, &p->prefix); XFREE(MTYPE_TMP, cp); if (ret == 0) return 0; plen = (uint8_t)atoi(++pnt); if (plen > IPV6_MAX_BITLEN) return 0; p->prefixlen = plen; } p->family = AF_INET6; return ret; } /* Convert struct in6_addr netmask into integer. * FIXME return uint8_t as ip_maskleni() does. */ int ip6_masklen(struct in6_addr netmask) { int len = 0; unsigned char val; unsigned char *pnt; pnt = (unsigned char *)&netmask; while ((*pnt == 0xff) && len < IPV6_MAX_BITLEN) { len += 8; pnt++; } if (len < IPV6_MAX_BITLEN) { val = *pnt; while (val) { len++; val <<= 1; } } return len; } void masklen2ip6(const int masklen, struct in6_addr *netmask) { assert(masklen >= 0 && masklen <= IPV6_MAX_BITLEN); memcpy(netmask, maskbytes6 + masklen, sizeof(struct in6_addr)); } void apply_mask_ipv6(struct prefix_ipv6 *p) { uint8_t *pnt; int index; int offset; index = p->prefixlen / 8; if (index < 16) { pnt = (uint8_t *)&p->prefix; offset = p->prefixlen % 8; pnt[index] &= maskbit[offset]; index++; while (index < 16) pnt[index++] = 0; } } void apply_mask(struct prefix *p) { switch (p->family) { case AF_INET: apply_mask_ipv4((struct prefix_ipv4 *)p); break; case AF_INET6: apply_mask_ipv6((struct prefix_ipv6 *)p); break; default: break; } return; } /* Utility function of convert between struct prefix <=> union sockunion. * FIXME This function isn't used anywhere. */ struct prefix *sockunion2prefix(const union sockunion *dest, const union sockunion *mask) { if (dest->sa.sa_family == AF_INET) { struct prefix_ipv4 *p; p = prefix_ipv4_new(); p->family = AF_INET; p->prefix = dest->sin.sin_addr; p->prefixlen = ip_masklen(mask->sin.sin_addr); return (struct prefix *)p; } if (dest->sa.sa_family == AF_INET6) { struct prefix_ipv6 *p; p = prefix_ipv6_new(); p->family = AF_INET6; p->prefixlen = ip6_masklen(mask->sin6.sin6_addr); memcpy(&p->prefix, &dest->sin6.sin6_addr, sizeof(struct in6_addr)); return (struct prefix *)p; } return NULL; } /* Utility function of convert between struct prefix <=> union sockunion. */ struct prefix *sockunion2hostprefix(const union sockunion *su, struct prefix *prefix) { if (su->sa.sa_family == AF_INET) { struct prefix_ipv4 *p; p = prefix ? (struct prefix_ipv4 *)prefix : prefix_ipv4_new(); p->family = AF_INET; p->prefix = su->sin.sin_addr; p->prefixlen = IPV4_MAX_BITLEN; return (struct prefix *)p; } if (su->sa.sa_family == AF_INET6) { struct prefix_ipv6 *p; p = prefix ? (struct prefix_ipv6 *)prefix : prefix_ipv6_new(); p->family = AF_INET6; p->prefixlen = IPV6_MAX_BITLEN; memcpy(&p->prefix, &su->sin6.sin6_addr, sizeof(struct in6_addr)); return (struct prefix *)p; } return NULL; } void prefix2sockunion(const struct prefix *p, union sockunion *su) { memset(su, 0, sizeof(*su)); su->sa.sa_family = p->family; if (p->family == AF_INET) su->sin.sin_addr = p->u.prefix4; if (p->family == AF_INET6) memcpy(&su->sin6.sin6_addr, &p->u.prefix6, sizeof(struct in6_addr)); } int prefix_blen(const struct prefix *p) { switch (p->family) { case AF_INET: return IPV4_MAX_BYTELEN; break; case AF_INET6: return IPV6_MAX_BYTELEN; break; case AF_ETHERNET: return ETH_ALEN; break; } return 0; } /* Generic function for conversion string to struct prefix. */ int str2prefix(const char *str, struct prefix *p) { int ret; if (!str || !p) return 0; /* First we try to convert string to struct prefix_ipv4. */ ret = str2prefix_ipv4(str, (struct prefix_ipv4 *)p); if (ret) return ret; /* Next we try to convert string to struct prefix_ipv6. */ ret = str2prefix_ipv6(str, (struct prefix_ipv6 *)p); if (ret) return ret; /* Next we try to convert string to struct prefix_eth. */ ret = str2prefix_eth(str, (struct prefix_eth *)p); if (ret) return ret; return 0; } static const char *prefixevpn_ead2str(const struct prefix_evpn *p, char *str, int size) { snprintf(str, size, "Unsupported EVPN prefix"); return str; } static const char *prefixevpn_macip2str(const struct prefix_evpn *p, char *str, int size) { uint8_t family; char buf[PREFIX2STR_BUFFER]; char buf2[ETHER_ADDR_STRLEN]; if (is_evpn_prefix_ipaddr_none(p)) snprintf(str, size, "[%d]:[%s]/%d", p->prefix.route_type, prefix_mac2str(&p->prefix.macip_addr.mac, buf2, sizeof(buf2)), p->prefixlen); else { family = is_evpn_prefix_ipaddr_v4(p) ? AF_INET : AF_INET6; snprintf(str, size, "[%d]:[%s]:[%s]/%d", p->prefix.route_type, prefix_mac2str(&p->prefix.macip_addr.mac, buf2, sizeof(buf2)), inet_ntop(family, &p->prefix.macip_addr.ip.ip.addr, buf, PREFIX2STR_BUFFER), p->prefixlen); } return str; } static const char *prefixevpn_imet2str(const struct prefix_evpn *p, char *str, int size) { uint8_t family; char buf[PREFIX2STR_BUFFER]; family = is_evpn_prefix_ipaddr_v4(p) ? AF_INET : AF_INET6; snprintf(str, size, "[%d]:[%s]/%d", p->prefix.route_type, inet_ntop(family, &p->prefix.imet_addr.ip.ip.addr, buf, PREFIX2STR_BUFFER), p->prefixlen); return str; } static const char *prefixevpn_es2str(const struct prefix_evpn *p, char *str, int size) { char buf[ESI_STR_LEN]; snprintf(str, size, "[%d]:[%s]:[%s]/%d", p->prefix.route_type, esi_to_str(&p->prefix.es_addr.esi, buf, sizeof(buf)), inet_ntoa(p->prefix.es_addr.ip.ipaddr_v4), p->prefixlen); return str; } static const char *prefixevpn_prefix2str(const struct prefix_evpn *p, char *str, int size) { uint8_t family; char buf[PREFIX2STR_BUFFER]; family = is_evpn_prefix_ipaddr_v4(p) ? AF_INET : AF_INET6; snprintf(str, size, "[%d]:[%u][%s/%d]/%d", p->prefix.route_type, p->prefix.prefix_addr.eth_tag, inet_ntop(family, &p->prefix.prefix_addr.ip.ip.addr, buf, PREFIX2STR_BUFFER), p->prefix.prefix_addr.ip_prefix_length, p->prefixlen); return str; } static const char *prefixevpn2str(const struct prefix_evpn *p, char *str, int size) { switch (p->prefix.route_type) { case 1: return prefixevpn_ead2str(p, str, size); case 2: return prefixevpn_macip2str(p, str, size); case 3: return prefixevpn_imet2str(p, str, size); case 4: return prefixevpn_es2str(p, str, size); case 5: return prefixevpn_prefix2str(p, str, size); default: snprintf(str, size, "Unsupported EVPN prefix"); break; } return str; } const char *prefix2str(union prefixconstptr pu, char *str, int size) { const struct prefix *p = pu.p; char buf[PREFIX2STR_BUFFER]; int byte, tmp, a, b; bool z = false; size_t l; switch (p->family) { case AF_INET: case AF_INET6: inet_ntop(p->family, &p->u.prefix, buf, sizeof(buf)); l = strlen(buf); buf[l++] = '/'; byte = p->prefixlen; if ((tmp = p->prefixlen - 100) >= 0) { buf[l++] = '1'; z = true; byte = tmp; } b = byte % 10; a = byte / 10; if (a || z) buf[l++] = '0' + a; buf[l++] = '0' + b; buf[l] = '\0'; strlcpy(str, buf, size); break; case AF_ETHERNET: snprintf(str, size, "%s/%d", prefix_mac2str(&p->u.prefix_eth, buf, sizeof(buf)), p->prefixlen); break; case AF_EVPN: prefixevpn2str((const struct prefix_evpn *)p, str, size); break; case AF_FLOWSPEC: strlcpy(str, "FS prefix", size); break; default: strlcpy(str, "UNK prefix", size); break; } return str; } void prefix_mcast_inet4_dump(const char *onfail, struct in_addr addr, char *buf, int buf_size) { int save_errno = errno; if (addr.s_addr == INADDR_ANY) strlcpy(buf, "*", buf_size); else { if (!inet_ntop(AF_INET, &addr, buf, buf_size)) { if (onfail) snprintf(buf, buf_size, "%s", onfail); } } errno = save_errno; } const char *prefix_sg2str(const struct prefix_sg *sg, char *sg_str) { char src_str[INET_ADDRSTRLEN]; char grp_str[INET_ADDRSTRLEN]; prefix_mcast_inet4_dump("", sg->src, src_str, sizeof(src_str)); prefix_mcast_inet4_dump("", sg->grp, grp_str, sizeof(grp_str)); snprintf(sg_str, PREFIX_SG_STR_LEN, "(%s,%s)", src_str, grp_str); return sg_str; } struct prefix *prefix_new(void) { struct prefix *p; p = XCALLOC(MTYPE_PREFIX, sizeof *p); return p; } /* Free prefix structure. */ void prefix_free(struct prefix *p) { XFREE(MTYPE_PREFIX, p); } /* Utility function to convert ipv4 prefixes to Classful prefixes */ void apply_classful_mask_ipv4(struct prefix_ipv4 *p) { uint32_t destination; destination = ntohl(p->prefix.s_addr); if (p->prefixlen == IPV4_MAX_PREFIXLEN) ; /* do nothing for host routes */ else if (IN_CLASSC(destination)) { p->prefixlen = 24; apply_mask_ipv4(p); } else if (IN_CLASSB(destination)) { p->prefixlen = 16; apply_mask_ipv4(p); } else { p->prefixlen = 8; apply_mask_ipv4(p); } } in_addr_t ipv4_network_addr(in_addr_t hostaddr, int masklen) { struct in_addr mask; masklen2ip(masklen, &mask); return hostaddr & mask.s_addr; } in_addr_t ipv4_broadcast_addr(in_addr_t hostaddr, int masklen) { struct in_addr mask; masklen2ip(masklen, &mask); return (masklen != IPV4_MAX_PREFIXLEN - 1) ? /* normal case */ (hostaddr | ~mask.s_addr) : /* special case for /31 */ (hostaddr ^ ~mask.s_addr); } /* Utility function to convert ipv4 netmask to prefixes ex.) "1.1.0.0" "255.255.0.0" => "1.1.0.0/16" ex.) "1.0.0.0" NULL => "1.0.0.0/8" */ int netmask_str2prefix_str(const char *net_str, const char *mask_str, char *prefix_str) { struct in_addr network; struct in_addr mask; uint8_t prefixlen; uint32_t destination; int ret; ret = inet_aton(net_str, &network); if (!ret) return 0; if (mask_str) { ret = inet_aton(mask_str, &mask); if (!ret) return 0; prefixlen = ip_masklen(mask); } else { destination = ntohl(network.s_addr); if (network.s_addr == 0) prefixlen = 0; else if (IN_CLASSC(destination)) prefixlen = 24; else if (IN_CLASSB(destination)) prefixlen = 16; else if (IN_CLASSA(destination)) prefixlen = 8; else return 0; } sprintf(prefix_str, "%s/%d", net_str, prefixlen); return 1; } /* Utility function for making IPv6 address string. */ const char *inet6_ntoa(struct in6_addr addr) { static char buf[INET6_ADDRSTRLEN]; inet_ntop(AF_INET6, &addr, buf, INET6_ADDRSTRLEN); return buf; } /* converts to internal representation of mac address * returns 1 on success, 0 otherwise * format accepted: AA:BB:CC:DD:EE:FF * if mac parameter is null, then check only */ int prefix_str2mac(const char *str, struct ethaddr *mac) { unsigned int a[6]; int i; if (!str) return 0; if (sscanf(str, "%2x:%2x:%2x:%2x:%2x:%2x", a + 0, a + 1, a + 2, a + 3, a + 4, a + 5) != 6) { /* error in incoming str length */ return 0; } /* valid mac address */ if (!mac) return 1; for (i = 0; i < 6; ++i) mac->octet[i] = a[i] & 0xff; return 1; } char *prefix_mac2str(const struct ethaddr *mac, char *buf, int size) { char *ptr; if (!mac) return NULL; if (!buf) ptr = XMALLOC(MTYPE_TMP, ETHER_ADDR_STRLEN * sizeof(char)); else { assert(size >= ETHER_ADDR_STRLEN); ptr = buf; } snprintf(ptr, (ETHER_ADDR_STRLEN), "%02x:%02x:%02x:%02x:%02x:%02x", (uint8_t)mac->octet[0], (uint8_t)mac->octet[1], (uint8_t)mac->octet[2], (uint8_t)mac->octet[3], (uint8_t)mac->octet[4], (uint8_t)mac->octet[5]); return ptr; } unsigned prefix_hash_key(const void *pp) { struct prefix copy; if (((struct prefix *)pp)->family == AF_FLOWSPEC) { uint32_t len; void *temp; /* make sure *all* unused bits are zero, * particularly including alignment / * padding and unused prefix bytes. */ memset(©, 0, sizeof(copy)); prefix_copy(©, (struct prefix *)pp); len = jhash((void *)copy.u.prefix_flowspec.ptr, copy.u.prefix_flowspec.prefixlen, 0x55aa5a5a); temp = (void *)copy.u.prefix_flowspec.ptr; XFREE(MTYPE_PREFIX_FLOWSPEC, temp); copy.u.prefix_flowspec.ptr = (uintptr_t)NULL; return len; } /* make sure *all* unused bits are zero, particularly including * alignment / * padding and unused prefix bytes. */ memset(©, 0, sizeof(copy)); prefix_copy(©, (struct prefix *)pp); return jhash(©, offsetof(struct prefix, u.prefix) + PSIZE(copy.prefixlen), 0x55aa5a5a); } /* converts to internal representation of esi * returns 1 on success, 0 otherwise * format accepted: aa:aa:aa:aa:aa:aa:aa:aa:aa:aa * if esi parameter is null, then check only */ int str_to_esi(const char *str, esi_t *esi) { int i; unsigned int a[ESI_BYTES]; if (!str) return 0; if (sscanf(str, "%2x:%2x:%2x:%2x:%2x:%2x:%2x:%2x:%2x:%2x", a + 0, a + 1, a + 2, a + 3, a + 4, a + 5, a + 6, a + 7, a + 8, a + 9) != ESI_BYTES) { /* error in incoming str length */ return 0; } /* valid ESI */ if (!esi) return 1; for (i = 0; i < ESI_BYTES; ++i) esi->val[i] = a[i] & 0xff; return 1; } char *esi_to_str(const esi_t *esi, char *buf, int size) { char *ptr; if (!esi) return NULL; if (!buf) ptr = XMALLOC(MTYPE_TMP, ESI_STR_LEN * sizeof(char)); else { assert(size >= ESI_STR_LEN); ptr = buf; } snprintf(ptr, ESI_STR_LEN, "%02x:%02x:%02x:%02x:%02x:%02x:%02x:%02x:%02x:%02x", esi->val[0], esi->val[1], esi->val[2], esi->val[3], esi->val[4], esi->val[5], esi->val[6], esi->val[7], esi->val[8], esi->val[9]); return ptr; } printfrr_ext_autoreg_p("I4", printfrr_i4) static ssize_t printfrr_i4(char *buf, size_t bsz, const char *fmt, int prec, const void *ptr) { inet_ntop(AF_INET, ptr, buf, bsz); return 2; } printfrr_ext_autoreg_p("I6", printfrr_i6) static ssize_t printfrr_i6(char *buf, size_t bsz, const char *fmt, int prec, const void *ptr) { inet_ntop(AF_INET6, ptr, buf, bsz); return 2; } printfrr_ext_autoreg_p("FX", printfrr_pfx) static ssize_t printfrr_pfx(char *buf, size_t bsz, const char *fmt, int prec, const void *ptr) { prefix2str(ptr, buf, bsz); return 2; } printfrr_ext_autoreg_p("SG4", printfrr_psg) static ssize_t printfrr_psg(char *buf, size_t bsz, const char *fmt, int prec, const void *ptr) { const struct prefix_sg *sg = ptr; struct fbuf fb = { .buf = buf, .pos = buf, .len = bsz - 1 }; if (sg->src.s_addr == INADDR_ANY) bprintfrr(&fb, "(*,"); else bprintfrr(&fb, "(%pI4,", &sg->src); if (sg->grp.s_addr == INADDR_ANY) bprintfrr(&fb, "*)"); else bprintfrr(&fb, "%pI4)", &sg->grp); fb.pos[0] = '\0'; return 3; } frr-7.2.1/lib/prefix.h0000644000000000000000000003732213610377563011437 00000000000000/* * Prefix structure. * Copyright (C) 1998 Kunihiro Ishiguro * * This file is part of GNU Zebra. * * GNU Zebra is free software; you can redistribute it and/or modify it * under the terms of the GNU General Public License as published by the * Free Software Foundation; either version 2, or (at your option) any * later version. * * GNU Zebra 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; see the file COPYING; if not, write to the Free Software * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA */ #ifndef _ZEBRA_PREFIX_H #define _ZEBRA_PREFIX_H #ifdef SUNOS_5 #include #else #ifdef GNU_LINUX #include #else #include #endif #endif #include "sockunion.h" #include "ipaddr.h" #include "compiler.h" #ifdef __cplusplus extern "C" { #endif #ifndef ETH_ALEN #define ETH_ALEN 6 #endif #define ESI_BYTES 10 #define ESI_STR_LEN (3 * ESI_BYTES) #define ETHER_ADDR_STRLEN (3*ETH_ALEN) /* * there isn't a portable ethernet address type. We define our * own to simplify internal handling */ struct ethaddr { uint8_t octet[ETH_ALEN]; } __attribute__((packed)); /* length is the number of valuable bits of prefix structure * 18 bytes is current length in structure, if address is ipv4 * 30 bytes is in case of ipv6 */ #define PREFIX_LEN_ROUTE_TYPE_5_IPV4 (18*8) #define PREFIX_LEN_ROUTE_TYPE_5_IPV6 (30*8) typedef struct esi_t_ { uint8_t val[10]; } esi_t; struct evpn_ead_addr { esi_t esi; uint32_t eth_tag; }; struct evpn_macip_addr { uint32_t eth_tag; uint8_t ip_prefix_length; struct ethaddr mac; struct ipaddr ip; }; struct evpn_imet_addr { uint32_t eth_tag; uint8_t ip_prefix_length; struct ipaddr ip; }; struct evpn_es_addr { esi_t esi; uint8_t ip_prefix_length; struct ipaddr ip; }; struct evpn_prefix_addr { uint32_t eth_tag; uint8_t ip_prefix_length; struct ipaddr ip; }; /* EVPN address (RFC 7432) */ struct evpn_addr { uint8_t route_type; union { struct evpn_ead_addr _ead_addr; struct evpn_macip_addr _macip_addr; struct evpn_imet_addr _imet_addr; struct evpn_es_addr _es_addr; struct evpn_prefix_addr _prefix_addr; } u; #define ead_addr u._ead_addr #define macip_addr u._macip_addr #define imet_addr u._imet_addr #define es_addr u._es_addr #define prefix_addr u._prefix_addr }; /* * A struct prefix contains an address family, a prefix length, and an * address. This can represent either a 'network prefix' as defined * by CIDR, where the 'host bits' of the prefix are 0 * (e.g. AF_INET:10.0.0.0/8), or an address and netmask * (e.g. AF_INET:10.0.0.9/8), such as might be configured on an * interface. */ /* different OSes use different names */ #if defined(AF_PACKET) #define AF_ETHERNET AF_PACKET #else #if defined(AF_LINK) #define AF_ETHERNET AF_LINK #endif #endif /* The 'family' in the prefix structure is internal to FRR and need not * map to standard OS AF_ definitions except where needed for interacting * with the kernel. However, AF_ definitions are currently in use and * prevalent across the code. Define a new FRR-specific AF for EVPN to * distinguish between 'ethernet' (MAC-only) and 'evpn' prefixes and * ensure it does not conflict with any OS AF_ definition. */ #if !defined(AF_EVPN) #define AF_EVPN (AF_MAX + 1) #endif #if !defined(AF_FLOWSPEC) #define AF_FLOWSPEC (AF_MAX + 2) #endif struct flowspec_prefix { uint16_t prefixlen; /* length in bytes */ uintptr_t ptr; }; /* FRR generic prefix structure. */ struct prefix { uint8_t family; uint16_t prefixlen; union { uint8_t prefix; struct in_addr prefix4; struct in6_addr prefix6; struct { struct in_addr id; struct in_addr adv_router; } lp; struct ethaddr prefix_eth; /* AF_ETHERNET */ uint8_t val[16]; uint32_t val32[4]; uintptr_t ptr; struct evpn_addr prefix_evpn; /* AF_EVPN */ struct flowspec_prefix prefix_flowspec; /* AF_FLOWSPEC */ } u __attribute__((aligned(8))); }; /* IPv4 prefix structure. */ struct prefix_ipv4 { uint8_t family; uint16_t prefixlen; struct in_addr prefix __attribute__((aligned(8))); }; /* IPv6 prefix structure. */ struct prefix_ipv6 { uint8_t family; uint16_t prefixlen; struct in6_addr prefix __attribute__((aligned(8))); }; struct prefix_ls { uint8_t family; uint16_t prefixlen; struct in_addr id __attribute__((aligned(8))); struct in_addr adv_router; }; /* Prefix for routing distinguisher. */ struct prefix_rd { uint8_t family; uint16_t prefixlen; uint8_t val[8] __attribute__((aligned(8))); }; /* Prefix for ethernet. */ struct prefix_eth { uint8_t family; uint16_t prefixlen; struct ethaddr eth_addr __attribute__((aligned(8))); /* AF_ETHERNET */ }; /* EVPN prefix structure. */ struct prefix_evpn { uint8_t family; uint16_t prefixlen; struct evpn_addr prefix __attribute__((aligned(8))); }; static inline int is_evpn_prefix_ipaddr_none(const struct prefix_evpn *evp) { if (evp->prefix.route_type == 2) return IS_IPADDR_NONE(&(evp)->prefix.macip_addr.ip); if (evp->prefix.route_type == 3) return IS_IPADDR_NONE(&(evp)->prefix.imet_addr.ip); if (evp->prefix.route_type == 4) return IS_IPADDR_NONE(&(evp)->prefix.es_addr.ip); if (evp->prefix.route_type == 5) return IS_IPADDR_NONE(&(evp)->prefix.prefix_addr.ip); return 0; } static inline int is_evpn_prefix_ipaddr_v4(const struct prefix_evpn *evp) { if (evp->prefix.route_type == 2) return IS_IPADDR_V4(&(evp)->prefix.macip_addr.ip); if (evp->prefix.route_type == 3) return IS_IPADDR_V4(&(evp)->prefix.imet_addr.ip); if (evp->prefix.route_type == 4) return IS_IPADDR_V4(&(evp)->prefix.es_addr.ip); if (evp->prefix.route_type == 5) return IS_IPADDR_V4(&(evp)->prefix.prefix_addr.ip); return 0; } static inline int is_evpn_prefix_ipaddr_v6(const struct prefix_evpn *evp) { if (evp->prefix.route_type == 2) return IS_IPADDR_V6(&(evp)->prefix.macip_addr.ip); if (evp->prefix.route_type == 3) return IS_IPADDR_V6(&(evp)->prefix.imet_addr.ip); if (evp->prefix.route_type == 4) return IS_IPADDR_V6(&(evp)->prefix.es_addr.ip); if (evp->prefix.route_type == 5) return IS_IPADDR_V6(&(evp)->prefix.prefix_addr.ip); return 0; } /* Prefix for a generic pointer */ struct prefix_ptr { uint8_t family; uint16_t prefixlen; uintptr_t prefix __attribute__((aligned(8))); }; /* Prefix for a Flowspec entry */ struct prefix_fs { uint8_t family; uint16_t prefixlen; /* unused */ struct flowspec_prefix prefix __attribute__((aligned(8))); }; struct prefix_sg { uint8_t family; uint16_t prefixlen; struct in_addr src __attribute__((aligned(8))); struct in_addr grp; }; /* helper to get type safety/avoid casts on calls * (w/o this, functions accepting all prefix types need casts on the caller * side, which strips type safety since the cast will accept any pointer * type.) */ #ifndef __cplusplus #define prefixtype(uname, typename, fieldname) \ typename *fieldname; #else #define prefixtype(uname, typename, fieldname) \ typename *fieldname; \ uname(typename *x) { this->fieldname = x; } #endif union prefixptr { prefixtype(prefixptr, struct prefix, p) prefixtype(prefixptr, struct prefix_ipv4, p4) prefixtype(prefixptr, struct prefix_ipv6, p6) prefixtype(prefixptr, struct prefix_evpn, evp) prefixtype(prefixptr, struct prefix_fs, fs) } __attribute__((transparent_union)); union prefixconstptr { prefixtype(prefixconstptr, const struct prefix, p) prefixtype(prefixconstptr, const struct prefix_ipv4, p4) prefixtype(prefixconstptr, const struct prefix_ipv6, p6) prefixtype(prefixconstptr, const struct prefix_evpn, evp) prefixtype(prefixconstptr, const struct prefix_fs, fs) } __attribute__((transparent_union)); #ifndef INET_ADDRSTRLEN #define INET_ADDRSTRLEN 16 #endif /* INET_ADDRSTRLEN */ #ifndef INET6_ADDRSTRLEN /* dead:beef:dead:beef:dead:beef:dead:beef + \0 */ #define INET6_ADDRSTRLEN 46 #endif /* INET6_ADDRSTRLEN */ #ifndef INET6_BUFSIZ #define INET6_BUFSIZ 53 #endif /* INET6_BUFSIZ */ /* Maximum string length of the result of prefix2str */ #define PREFIX_STRLEN 80 /* * Longest possible length of a (S,G) string is 36 bytes * 123.123.123.123 = 15 * 2 * (,) = 3 * NULL Character at end = 1 * (123.123.123.123,123.123.123.123) */ #define PREFIX_SG_STR_LEN 34 /* Max bit/byte length of IPv4 address. */ #define IPV4_MAX_BYTELEN 4 #define IPV4_MAX_BITLEN 32 #define IPV4_MAX_PREFIXLEN 32 #define IPV4_ADDR_CMP(D,S) memcmp ((D), (S), IPV4_MAX_BYTELEN) static inline bool ipv4_addr_same(const struct in_addr *a, const struct in_addr *b) { return (a->s_addr == b->s_addr); } #define IPV4_ADDR_SAME(A,B) ipv4_addr_same((A), (B)) static inline void ipv4_addr_copy(struct in_addr *dst, const struct in_addr *src) { dst->s_addr = src->s_addr; } #define IPV4_ADDR_COPY(D,S) ipv4_addr_copy((D), (S)) #define IPV4_NET0(a) ((((uint32_t)(a)) & 0xff000000) == 0x00000000) #define IPV4_NET127(a) ((((uint32_t)(a)) & 0xff000000) == 0x7f000000) #define IPV4_LINKLOCAL(a) ((((uint32_t)(a)) & 0xffff0000) == 0xa9fe0000) #define IPV4_CLASS_DE(a) ((((uint32_t)(a)) & 0xe0000000) == 0xe0000000) #define IPV4_MC_LINKLOCAL(a) ((((uint32_t)(a)) & 0xffffff00) == 0xe0000000) /* Max bit/byte length of IPv6 address. */ #define IPV6_MAX_BYTELEN 16 #define IPV6_MAX_BITLEN 128 #define IPV6_MAX_PREFIXLEN 128 #define IPV6_ADDR_CMP(D,S) memcmp ((D), (S), IPV6_MAX_BYTELEN) #define IPV6_ADDR_SAME(D,S) (memcmp ((D), (S), IPV6_MAX_BYTELEN) == 0) #define IPV6_ADDR_COPY(D,S) memcpy ((D), (S), IPV6_MAX_BYTELEN) /* Count prefix size from mask length */ #define PSIZE(a) (((a) + 7) / (8)) #define BSIZE(a) ((a) * (8)) /* Prefix's family member. */ #define PREFIX_FAMILY(p) ((p)->family) /* glibc defines s6_addr32 to __in6_u.__u6_addr32 if __USE_{MISC || GNU} */ #ifndef s6_addr32 #if defined(SUNOS_5) /* Some SunOS define s6_addr32 only to kernel */ #define s6_addr32 _S6_un._S6_u32 #else #define s6_addr32 __u6_addr.__u6_addr32 #endif /* SUNOS_5 */ #endif /*s6_addr32*/ /* Prototypes. */ extern int str2family(const char *); extern int afi2family(afi_t); extern afi_t family2afi(int); extern const char *family2str(int family); extern const char *safi2str(safi_t safi); extern const char *afi2str(afi_t afi); /* Check bit of the prefix. */ extern unsigned int prefix_bit(const uint8_t *prefix, const uint16_t prefixlen); extern unsigned int prefix6_bit(const struct in6_addr *prefix, const uint16_t prefixlen); extern struct prefix *prefix_new(void); extern void prefix_free(struct prefix *); extern const char *prefix_family_str(const struct prefix *); extern int prefix_blen(const struct prefix *); extern int str2prefix(const char *, struct prefix *); #define PREFIX2STR_BUFFER PREFIX_STRLEN extern void prefix_mcast_inet4_dump(const char *onfail, struct in_addr addr, char *buf, int buf_size); extern const char *prefix_sg2str(const struct prefix_sg *sg, char *str); extern const char *prefix2str(union prefixconstptr, char *, int); extern int prefix_match(const struct prefix *, const struct prefix *); extern int prefix_match_network_statement(const struct prefix *, const struct prefix *); extern int prefix_same(union prefixconstptr, union prefixconstptr); extern int prefix_cmp(union prefixconstptr, union prefixconstptr); extern int prefix_common_bits(const struct prefix *, const struct prefix *); extern void prefix_copy(union prefixptr, union prefixconstptr); extern void apply_mask(struct prefix *); #ifdef __clang_analyzer__ /* clang-SA doesn't understand transparent unions, making it think that the * target of prefix_copy is uninitialized. So just memset the target. * cf. https://bugs.llvm.org/show_bug.cgi?id=42811 */ #define prefix_copy(a, b) ({ memset(a, 0, sizeof(*a)); prefix_copy(a, b); }) #endif extern struct prefix *sockunion2prefix(const union sockunion *dest, const union sockunion *mask); extern struct prefix *sockunion2hostprefix(const union sockunion *, struct prefix *p); extern void prefix2sockunion(const struct prefix *, union sockunion *); extern int str2prefix_eth(const char *, struct prefix_eth *); extern struct prefix_ipv4 *prefix_ipv4_new(void); extern void prefix_ipv4_free(struct prefix_ipv4 *); extern int str2prefix_ipv4(const char *, struct prefix_ipv4 *); extern void apply_mask_ipv4(struct prefix_ipv4 *); #define PREFIX_COPY(DST, SRC) \ *((struct prefix *)(DST)) = *((const struct prefix *)(SRC)) #define PREFIX_COPY_IPV4(DST, SRC) \ *((struct prefix_ipv4 *)(DST)) = *((const struct prefix_ipv4 *)(SRC)); extern int prefix_ipv4_any(const struct prefix_ipv4 *); extern void apply_classful_mask_ipv4(struct prefix_ipv4 *); extern uint8_t ip_masklen(struct in_addr); extern void masklen2ip(const int, struct in_addr *); /* returns the network portion of the host address */ extern in_addr_t ipv4_network_addr(in_addr_t hostaddr, int masklen); /* given the address of a host on a network and the network mask length, * calculate the broadcast address for that network; * special treatment for /31: returns the address of the other host * on the network by flipping the host bit */ extern in_addr_t ipv4_broadcast_addr(in_addr_t hostaddr, int masklen); extern int netmask_str2prefix_str(const char *, const char *, char *); extern struct prefix_ipv6 *prefix_ipv6_new(void); extern void prefix_ipv6_free(struct prefix_ipv6 *); extern int str2prefix_ipv6(const char *, struct prefix_ipv6 *); extern void apply_mask_ipv6(struct prefix_ipv6 *); #define PREFIX_COPY_IPV6(DST, SRC) \ *((struct prefix_ipv6 *)(DST)) = *((const struct prefix_ipv6 *)(SRC)); extern int ip6_masklen(struct in6_addr); extern void masklen2ip6(const int, struct in6_addr *); extern const char *inet6_ntoa(struct in6_addr); extern int is_zero_mac(const struct ethaddr *mac); extern int prefix_str2mac(const char *str, struct ethaddr *mac); extern char *prefix_mac2str(const struct ethaddr *mac, char *buf, int size); extern unsigned prefix_hash_key(const void *pp); extern int str_to_esi(const char *str, esi_t *esi); extern char *esi_to_str(const esi_t *esi, char *buf, int size); extern void prefix_hexdump(const struct prefix *p); extern void prefix_evpn_hexdump(const struct prefix_evpn *p); static inline int ipv6_martian(struct in6_addr *addr) { struct in6_addr localhost_addr; inet_pton(AF_INET6, "::1", &localhost_addr); if (IPV6_ADDR_SAME(&localhost_addr, addr)) return 1; return 0; } extern int macstr2prefix_evpn(const char *str, struct prefix_evpn *p); /* NOTE: This routine expects the address argument in network byte order. */ static inline int ipv4_martian(struct in_addr *addr) { in_addr_t ip = ntohl(addr->s_addr); if (IPV4_NET0(ip) || IPV4_NET127(ip) || IPV4_CLASS_DE(ip)) { return 1; } return 0; } static inline int is_default_prefix(const struct prefix *p) { if (!p) return 0; if ((p->family == AF_INET) && (p->u.prefix4.s_addr == INADDR_ANY) && (p->prefixlen == 0)) return 1; if ((p->family == AF_INET6) && (p->prefixlen == 0) && (!memcmp(&p->u.prefix6, &in6addr_any, sizeof(struct in6_addr)))) return 1; return 0; } static inline int is_host_route(struct prefix *p) { if (p->family == AF_INET) return (p->prefixlen == IPV4_MAX_BITLEN); else if (p->family == AF_INET6) return (p->prefixlen == IPV6_MAX_BITLEN); return 0; } static inline int is_default_host_route(struct prefix *p) { if (p->family == AF_INET) { return (p->u.prefix4.s_addr == INADDR_ANY && p->prefixlen == IPV4_MAX_BITLEN); } else if (p->family == AF_INET6) { return ((!memcmp(&p->u.prefix6, &in6addr_any, sizeof(struct in6_addr))) && p->prefixlen == IPV6_MAX_BITLEN); } return 0; } #ifdef __cplusplus } #endif #endif /* _ZEBRA_PREFIX_H */ frr-7.2.1/lib/printf/0000755000000000000000000000000013610377563011344 500000000000000frr-7.2.1/lib/printf/glue.c0000644000000000000000000001366313610377563012375 00000000000000/* * Copyright (c) 2019 David Lamparter, for NetDEF, Inc. * * Permission to use, copy, modify, and distribute this software for any * purpose with or without fee is hereby granted, provided that the above * copyright notice and this permission notice appear in all copies. * * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. */ #ifdef HAVE_CONFIG_H #include "config.h" #endif #include #include #include #include "printfrr.h" #include "printflocal.h" ssize_t bprintfrr(struct fbuf *out, const char *fmt, ...) { ssize_t ret; va_list ap; va_start(ap, fmt); ret = vbprintfrr(out, fmt, ap); va_end(ap); return ret; } ssize_t vsnprintfrr(char *out, size_t outsz, const char *fmt, va_list ap) { struct fbuf fbb = { .buf = out, .pos = out, .len = outsz - 1, }; struct fbuf *fb = (out && outsz) ? &fbb : NULL; ssize_t ret; ret = vbprintfrr(fb, fmt, ap); if (fb) fb->pos[0] = '\0'; return ret; } ssize_t snprintfrr(char *out, size_t outsz, const char *fmt, ...) { struct fbuf fbb = { .buf = out, .pos = out, .len = outsz - 1, }; struct fbuf *fb = (out && outsz) ? &fbb : NULL; ssize_t ret; va_list ap; va_start(ap, fmt); ret = vbprintfrr(fb, fmt, ap); va_end(ap); if (fb) fb->pos[0] = '\0'; return ret; } ssize_t vcsnprintfrr(char *out, size_t outsz, const char *fmt, va_list ap) { if (!out || !outsz) return vbprintfrr(NULL, fmt, ap); struct fbuf fbb = { .buf = out, .pos = out, .len = outsz - 1, }; ssize_t ret; size_t pos; pos = strnlen(out, outsz); fbb.pos += pos; ret = vbprintfrr(&fbb, fmt, ap); fbb.pos[0] = '\0'; return ret >= 0 ? ret + (ssize_t)pos : ret; } ssize_t csnprintfrr(char *out, size_t outsz, const char *fmt, ...) { ssize_t ret; va_list ap; va_start(ap, fmt); ret = vcsnprintfrr(out, outsz, fmt, ap); va_end(ap); return ret; } char *vasnprintfrr(struct memtype *mt, char *out, size_t outsz, const char *fmt, va_list ap) { struct fbuf fb = { .buf = out, .pos = out, .len = outsz - 1, }; ssize_t len; va_list ap2; char *ret = out; va_copy(ap2, ap); len = vbprintfrr(&fb, fmt, ap); if (len < 0) /* error = malformed format string => try something useful */ return qstrdup(mt, fmt); if ((size_t)len >= outsz - 1) { ret = qmalloc(mt, len + 1); fb.buf = fb.pos = ret; fb.len = len; vbprintfrr(&fb, fmt, ap2); } ret[len] = '\0'; return ret; } char *asnprintfrr(struct memtype *mt, char *out, size_t outsz, const char *fmt, ...) { va_list ap; char *ret; va_start(ap, fmt); ret = vasnprintfrr(mt, out, outsz, fmt, ap); va_end(ap); return ret; } char *vasprintfrr(struct memtype *mt, const char *fmt, va_list ap) { char buf[256]; char *ret; ret = vasnprintfrr(mt, buf, sizeof(buf), fmt, ap); if (ret == buf) ret = qstrdup(mt, ret); return ret; } char *asprintfrr(struct memtype *mt, const char *fmt, ...) { char buf[256]; va_list ap; char *ret; va_start(ap, fmt); ret = vasnprintfrr(mt, buf, sizeof(buf), fmt, ap); va_end(ap); if (ret == buf) ret = qstrdup(mt, ret); return ret; } /* Q: WTF? * A: since printf should be reasonably fast (think debugging logs), the idea * here is to keep things close by each other in a cacheline. That's why * ext_quick just has the first 2 characters of an extension, and we do a * nice linear continuous sweep. Only if we find something, we go do more * expensive things. * * Q: doesn't this need a mutex/lock? * A: theoretically, yes, but that's quite expensive and I rather elide that * necessity by putting down some usage rules. Just call this at startup * while singlethreaded and all is fine. Ideally, just use constructors * (and make sure dlopen() doesn't mess things up...) */ #define MAXEXT 64 struct ext_quick { char fmt[2]; }; static uint8_t ext_offsets[26] __attribute__((aligned(32))); static struct ext_quick entries[MAXEXT] __attribute__((aligned(64))); static const struct printfrr_ext *exts[MAXEXT] __attribute__((aligned(64))); void printfrr_ext_reg(const struct printfrr_ext *ext) { uint8_t o; ptrdiff_t i; if (!printfrr_ext_char(ext->match[0])) return; o = ext->match[0] - 'A'; for (i = ext_offsets[o]; i < MAXEXT && entries[i].fmt[0] && memcmp(entries[i].fmt, ext->match, 2) < 0; i++) ; if (i == MAXEXT) return; for (o++; o <= 'Z' - 'A'; o++) ext_offsets[o]++; memmove(entries + i + 1, entries + i, (MAXEXT - i - 1) * sizeof(entries[0])); memmove(exts + i + 1, exts + i, (MAXEXT - i - 1) * sizeof(exts[0])); memcpy(entries[i].fmt, ext->match, 2); exts[i] = ext; } ssize_t printfrr_extp(char *buf, size_t sz, const char *fmt, int prec, const void *ptr) { const struct printfrr_ext *ext; size_t i; for (i = ext_offsets[fmt[0] - 'A']; i < MAXEXT; i++) { if (!entries[i].fmt[0] || entries[i].fmt[0] > fmt[0]) return 0; if (entries[i].fmt[1] && entries[i].fmt[1] != fmt[1]) continue; ext = exts[i]; if (!ext->print_ptr) continue; if (strncmp(ext->match, fmt, strlen(ext->match))) continue; return ext->print_ptr(buf, sz, fmt, prec, ptr); } return 0; } ssize_t printfrr_exti(char *buf, size_t sz, const char *fmt, int prec, uintmax_t num) { const struct printfrr_ext *ext; size_t i; for (i = ext_offsets[fmt[0] - 'A']; i < MAXEXT; i++) { if (!entries[i].fmt[0] || entries[i].fmt[0] > fmt[0]) return 0; if (entries[i].fmt[1] && entries[i].fmt[1] != fmt[1]) continue; ext = exts[i]; if (!ext->print_int) continue; if (strncmp(ext->match, fmt, strlen(ext->match))) continue; return ext->print_int(buf, sz, fmt, prec, num); } return 0; } frr-7.2.1/lib/printf/printf-pos.c0000644000000000000000000004336013610377563013537 00000000000000/*- * SPDX-License-Identifier: BSD-3-Clause * * Copyright (c) 1990, 1993 * The Regents of the University of California. All rights reserved. * * This code is derived from software contributed to Berkeley by * Chris Torek. * * Redistribution and use in source and binary forms, with or without * modification, are permitted provided that the following conditions * are met: * 1. Redistributions of source code must retain the above copyright * notice, this list of conditions and the following disclaimer. * 2. Redistributions in binary form must reproduce the above copyright * notice, this list of conditions and the following disclaimer in the * documentation and/or other materials provided with the distribution. * 3. Neither the name of the University nor the names of its contributors * may be used to endorse or promote products derived from this software * without specific prior written permission. * * THIS SOFTWARE IS PROVIDED BY THE REGENTS AND CONTRIBUTORS ``AS IS'' AND * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE * ARE DISCLAIMED. IN NO EVENT SHALL THE REGENTS OR CONTRIBUTORS BE LIABLE * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF * SUCH DAMAGE. */ #ifdef HAVE_CONFIG_H #include "config.h" #endif #ifdef HAVE_SYS_CDEFS_H #include #endif /* * This is the code responsible for handling positional arguments * (%m$ and %m$.n$) for vfprintf() and vfwprintf(). */ #include #include #include #include #include #include #include #include #include #include #include "printflocal.h" #ifdef NL_ARGMAX #define MAX_POSARG NL_ARGMAX #else #define MAX_POSARG 65536 #endif /* * Type ids for argument type table. */ enum typeid { T_UNUSED, TP_SHORT, T_INT, T_U_INT, TP_INT, T_LONG, T_U_LONG, TP_LONG, T_LLONG, T_U_LLONG, TP_LLONG, T_PTRDIFFT, TP_PTRDIFFT, T_SSIZET, T_SIZET, TP_SSIZET, T_INT64T, T_UINT64T, T_INTMAXT, T_UINTMAXT, TP_INTMAXT, TP_VOID, TP_CHAR, TP_SCHAR, T_DOUBLE, T_LONG_DOUBLE, T_WINT, TP_WCHAR }; /* An expandable array of types. */ struct typetable { enum typeid *table; /* table of types */ enum typeid stattable[STATIC_ARG_TBL_SIZE]; u_int tablesize; /* current size of type table */ u_int tablemax; /* largest used index in table */ u_int nextarg; /* 1-based argument index */ }; static int __grow_type_table(struct typetable *); static void build_arg_table (struct typetable *, va_list, union arg **); /* * Initialize a struct typetable. */ static inline void inittypes(struct typetable *types) { u_int n; types->table = types->stattable; types->tablesize = STATIC_ARG_TBL_SIZE; types->tablemax = 0; types->nextarg = 1; for (n = 0; n < STATIC_ARG_TBL_SIZE; n++) types->table[n] = T_UNUSED; } /* * struct typetable destructor. */ static inline void freetypes(struct typetable *types) { if (types->table != types->stattable) free (types->table); } /* * Ensure that there is space to add a new argument type to the type table. * Expand the table if necessary. Returns 0 on success. */ static inline int _ensurespace(struct typetable *types) { if (types->nextarg >= types->tablesize) { if (__grow_type_table(types)) return (-1); } if (types->nextarg > types->tablemax) types->tablemax = types->nextarg; return (0); } /* * Add an argument type to the table, expanding if necessary. * Returns 0 on success. */ static inline int addtype(struct typetable *types, enum typeid type) { if (_ensurespace(types)) return (-1); types->table[types->nextarg++] = type; return (0); } static inline int addsarg(struct typetable *types, int flags) { if (_ensurespace(types)) return (-1); if (flags & LONGDBL) types->table[types->nextarg++] = T_INT64T; else if (flags & INTMAXT) types->table[types->nextarg++] = T_INTMAXT; else if (flags & SIZET) types->table[types->nextarg++] = T_SSIZET; else if (flags & PTRDIFFT) types->table[types->nextarg++] = T_PTRDIFFT; else if (flags & LLONGINT) types->table[types->nextarg++] = T_LLONG; else if (flags & LONGINT) types->table[types->nextarg++] = T_LONG; else types->table[types->nextarg++] = T_INT; return (0); } static inline int adduarg(struct typetable *types, int flags) { if (_ensurespace(types)) return (-1); if (flags & LONGDBL) types->table[types->nextarg++] = T_UINT64T; else if (flags & INTMAXT) types->table[types->nextarg++] = T_UINTMAXT; else if (flags & SIZET) types->table[types->nextarg++] = T_SIZET; else if (flags & PTRDIFFT) types->table[types->nextarg++] = T_SIZET; else if (flags & LLONGINT) types->table[types->nextarg++] = T_U_LLONG; else if (flags & LONGINT) types->table[types->nextarg++] = T_U_LONG; else types->table[types->nextarg++] = T_U_INT; return (0); } /* * Add * arguments to the type array. */ static inline int addaster(struct typetable *types, char **fmtp) { char *cp; u_int n2; n2 = 0; cp = *fmtp; while (is_digit(*cp)) { n2 = 10 * n2 + to_digit(*cp); cp++; } if (*cp == '$') { u_int hold = types->nextarg; types->nextarg = n2; if (addtype(types, T_INT)) return (-1); types->nextarg = hold; *fmtp = ++cp; } else { if (addtype(types, T_INT)) return (-1); } return (0); } #ifdef WCHAR_SUPPORT static inline int addwaster(struct typetable *types, wchar_t **fmtp) { wchar_t *cp; u_int n2; n2 = 0; cp = *fmtp; while (is_digit(*cp)) { n2 = 10 * n2 + to_digit(*cp); cp++; } if (*cp == '$') { u_int hold = types->nextarg; types->nextarg = n2; if (addtype(types, T_INT)) return (-1); types->nextarg = hold; *fmtp = ++cp; } else { if (addtype(types, T_INT)) return (-1); } return (0); } #endif /* WCHAR_SUPPORT */ /* * Find all arguments when a positional parameter is encountered. Returns a * table, indexed by argument number, of pointers to each arguments. The * initial argument table should be an array of STATIC_ARG_TBL_SIZE entries. * It will be replaces with a malloc-ed one if it overflows. * Returns 0 on success. On failure, returns nonzero and sets errno. */ int _frr_find_arguments (const char *fmt0, va_list ap, union arg **argtable) { char *fmt; /* format string */ int ch; /* character from fmt */ u_int n; /* handy integer (short term usage) */ int error; int flags; /* flags as above */ struct typetable types; /* table of types */ fmt = (char *)fmt0; inittypes(&types); error = 0; /* * Scan the format for conversions (`%' character). */ for (;;) { while ((ch = *fmt) != '\0' && ch != '%') fmt++; if (ch == '\0') goto done; fmt++; /* skip over '%' */ flags = 0; rflag: ch = *fmt++; reswitch: switch (ch) { case ' ': case '#': goto rflag; case '*': if ((error = addaster(&types, &fmt))) goto error; goto rflag; case '-': case '+': case '\'': goto rflag; case '.': if ((ch = *fmt++) == '*') { if ((error = addaster(&types, &fmt))) goto error; goto rflag; } while (is_digit(ch)) { ch = *fmt++; } goto reswitch; case '0': goto rflag; case '1': case '2': case '3': case '4': case '5': case '6': case '7': case '8': case '9': n = 0; do { n = 10 * n + to_digit(ch); /* Detect overflow */ if (n > MAX_POSARG) { error = -1; goto error; } ch = *fmt++; } while (is_digit(ch)); if (ch == '$') { types.nextarg = n; goto rflag; } goto reswitch; case 'L': flags |= LONGDBL; goto rflag; case 'h': if (flags & SHORTINT) { flags &= ~SHORTINT; flags |= CHARINT; } else flags |= SHORTINT; goto rflag; case 'j': flags |= INTMAXT; goto rflag; case 'l': if (flags & LONGINT) { flags &= ~LONGINT; flags |= LLONGINT; } else flags |= LONGINT; goto rflag; case 'q': flags |= LLONGINT; /* not necessarily */ goto rflag; case 't': flags |= PTRDIFFT; goto rflag; case 'z': flags |= SIZET; goto rflag; case 'C': flags |= LONGINT; /*FALLTHROUGH*/ case 'c': error = addtype(&types, (flags & LONGINT) ? T_WINT : T_INT); if (error) goto error; break; case 'D': flags |= LONGINT; /*FALLTHROUGH*/ case 'd': case 'i': if ((error = addsarg(&types, flags))) goto error; break; #ifndef NO_FLOATING_POINT case 'a': case 'A': case 'e': case 'E': case 'f': case 'g': case 'G': error = addtype(&types, (flags & LONGDBL) ? T_LONG_DOUBLE : T_DOUBLE); if (error) goto error; break; #endif /* !NO_FLOATING_POINT */ case 'n': if (flags & INTMAXT) error = addtype(&types, TP_INTMAXT); else if (flags & PTRDIFFT) error = addtype(&types, TP_PTRDIFFT); else if (flags & SIZET) error = addtype(&types, TP_SSIZET); else if (flags & LLONGINT) error = addtype(&types, TP_LLONG); else if (flags & LONGINT) error = addtype(&types, TP_LONG); else if (flags & SHORTINT) error = addtype(&types, TP_SHORT); else if (flags & CHARINT) error = addtype(&types, TP_SCHAR); else error = addtype(&types, TP_INT); if (error) goto error; continue; /* no output */ case 'O': flags |= LONGINT; /*FALLTHROUGH*/ case 'o': if ((error = adduarg(&types, flags))) goto error; break; case 'p': if ((error = addtype(&types, TP_VOID))) goto error; break; case 'S': flags |= LONGINT; /*FALLTHROUGH*/ case 's': error = addtype(&types, (flags & LONGINT) ? TP_WCHAR : TP_CHAR); if (error) goto error; break; case 'U': flags |= LONGINT; /*FALLTHROUGH*/ case 'u': case 'X': case 'x': if ((error = adduarg(&types, flags))) goto error; break; default: /* "%?" prints ?, unless ? is NUL */ if (ch == '\0') goto done; break; } } done: build_arg_table(&types, ap, argtable); error: freetypes(&types); return (error || *argtable == NULL); } #ifdef WCHAR_SUPPORT /* wchar version of __find_arguments. */ int _frr_find_warguments (const wchar_t *fmt0, va_list ap, union arg **argtable) { wchar_t *fmt; /* format string */ wchar_t ch; /* character from fmt */ u_int n; /* handy integer (short term usage) */ int error; int flags; /* flags as above */ struct typetable types; /* table of types */ fmt = (wchar_t *)fmt0; inittypes(&types); error = 0; /* * Scan the format for conversions (`%' character). */ for (;;) { while ((ch = *fmt) != '\0' && ch != '%') fmt++; if (ch == '\0') goto done; fmt++; /* skip over '%' */ flags = 0; rflag: ch = *fmt++; reswitch: switch (ch) { case ' ': case '#': goto rflag; case '*': if ((error = addwaster(&types, &fmt))) goto error; goto rflag; case '-': case '+': case '\'': goto rflag; case '.': if ((ch = *fmt++) == '*') { if ((error = addwaster(&types, &fmt))) goto error; goto rflag; } while (is_digit(ch)) { ch = *fmt++; } goto reswitch; case '0': goto rflag; case '1': case '2': case '3': case '4': case '5': case '6': case '7': case '8': case '9': n = 0; do { n = 10 * n + to_digit(ch); /* Detect overflow */ if (n > MAX_POSARG) { error = -1; goto error; } ch = *fmt++; } while (is_digit(ch)); if (ch == '$') { types.nextarg = n; goto rflag; } goto reswitch; case 'L': flags |= LONGDBL; goto rflag; case 'h': if (flags & SHORTINT) { flags &= ~SHORTINT; flags |= CHARINT; } else flags |= SHORTINT; goto rflag; case 'j': flags |= INTMAXT; goto rflag; case 'l': if (flags & LONGINT) { flags &= ~LONGINT; flags |= LLONGINT; } else flags |= LONGINT; goto rflag; case 'q': flags |= LLONGINT; /* not necessarily */ goto rflag; case 't': flags |= PTRDIFFT; goto rflag; case 'z': flags |= SIZET; goto rflag; case 'C': flags |= LONGINT; /*FALLTHROUGH*/ case 'c': error = addtype(&types, (flags & LONGINT) ? T_WINT : T_INT); if (error) goto error; break; case 'D': flags |= LONGINT; /*FALLTHROUGH*/ case 'd': case 'i': if ((error = addsarg(&types, flags))) goto error; break; #ifndef NO_FLOATING_POINT case 'a': case 'A': case 'e': case 'E': case 'f': case 'g': case 'G': error = addtype(&types, (flags & LONGDBL) ? T_LONG_DOUBLE : T_DOUBLE); if (error) goto error; break; #endif /* !NO_FLOATING_POINT */ case 'n': if (flags & INTMAXT) error = addtype(&types, TP_INTMAXT); else if (flags & PTRDIFFT) error = addtype(&types, TP_PTRDIFFT); else if (flags & SIZET) error = addtype(&types, TP_SSIZET); else if (flags & LLONGINT) error = addtype(&types, TP_LLONG); else if (flags & LONGINT) error = addtype(&types, TP_LONG); else if (flags & SHORTINT) error = addtype(&types, TP_SHORT); else if (flags & CHARINT) error = addtype(&types, TP_SCHAR); else error = addtype(&types, TP_INT); if (error) goto error; continue; /* no output */ case 'O': flags |= LONGINT; /*FALLTHROUGH*/ case 'o': if ((error = adduarg(&types, flags))) goto error; break; case 'p': if ((error = addtype(&types, TP_VOID))) goto error; break; case 'S': flags |= LONGINT; /*FALLTHROUGH*/ case 's': error = addtype(&types, (flags & LONGINT) ? TP_WCHAR : TP_CHAR); if (error) goto error; break; case 'U': flags |= LONGINT; /*FALLTHROUGH*/ case 'u': case 'X': case 'x': if ((error = adduarg(&types, flags))) goto error; break; default: /* "%?" prints ?, unless ? is NUL */ if (ch == '\0') goto done; break; } } done: build_arg_table(&types, ap, argtable); error: freetypes(&types); return (error || *argtable == NULL); } #endif /* WCHAR_SUPPORT */ /* * Increase the size of the type table. Returns 0 on success. */ static int __grow_type_table(struct typetable *types) { enum typeid *const oldtable = types->table; const int oldsize = types->tablesize; enum typeid *newtable; u_int n, newsize; /* Detect overflow */ if (types->nextarg > MAX_POSARG) return (-1); newsize = oldsize * 2; if (newsize < types->nextarg + 1) newsize = types->nextarg + 1; if (oldsize == STATIC_ARG_TBL_SIZE) { if ((newtable = malloc(newsize * sizeof(enum typeid))) == NULL) return (-1); bcopy(oldtable, newtable, oldsize * sizeof(enum typeid)); } else { newtable = realloc(oldtable, newsize * sizeof(enum typeid)); if (newtable == NULL) return (-1); } for (n = oldsize; n < newsize; n++) newtable[n] = T_UNUSED; types->table = newtable; types->tablesize = newsize; return (0); } /* * Build the argument table from the completed type table. * On malloc failure, *argtable is set to NULL. */ static void build_arg_table(struct typetable *types, va_list ap, union arg **argtable) { u_int n; if (types->tablemax >= STATIC_ARG_TBL_SIZE) { *argtable = (union arg *) malloc (sizeof (union arg) * (types->tablemax + 1)); if (*argtable == NULL) return; } (*argtable) [0].intarg = 0; for (n = 1; n <= types->tablemax; n++) { switch (types->table[n]) { case T_UNUSED: /* whoops! */ (*argtable) [n].intarg = va_arg (ap, int); break; case TP_SCHAR: (*argtable) [n].pschararg = va_arg (ap, signed char *); break; case TP_SHORT: (*argtable) [n].pshortarg = va_arg (ap, short *); break; case T_INT: (*argtable) [n].intarg = va_arg (ap, int); break; case T_U_INT: (*argtable) [n].uintarg = va_arg (ap, unsigned int); break; case TP_INT: (*argtable) [n].pintarg = va_arg (ap, int *); break; case T_LONG: (*argtable) [n].longarg = va_arg (ap, long); break; case T_U_LONG: (*argtable) [n].ulongarg = va_arg (ap, unsigned long); break; case TP_LONG: (*argtable) [n].plongarg = va_arg (ap, long *); break; case T_LLONG: (*argtable) [n].longlongarg = va_arg (ap, long long); break; case T_U_LLONG: (*argtable) [n].ulonglongarg = va_arg (ap, unsigned long long); break; case TP_LLONG: (*argtable) [n].plonglongarg = va_arg (ap, long long *); break; case T_PTRDIFFT: (*argtable) [n].ptrdiffarg = va_arg (ap, ptrdiff_t); break; case TP_PTRDIFFT: (*argtable) [n].pptrdiffarg = va_arg (ap, ptrdiff_t *); break; case T_SIZET: (*argtable) [n].sizearg = va_arg (ap, size_t); break; case T_SSIZET: (*argtable) [n].sizearg = va_arg (ap, ssize_t); break; case TP_SSIZET: (*argtable) [n].pssizearg = va_arg (ap, ssize_t *); break; case T_INTMAXT: (*argtable) [n].intmaxarg = va_arg (ap, intmax_t); break; case T_UINTMAXT: (*argtable) [n].uintmaxarg = va_arg (ap, uintmax_t); break; case TP_INTMAXT: (*argtable) [n].pintmaxarg = va_arg (ap, intmax_t *); break; case T_INT64T: (*argtable) [n].intmaxarg = va_arg (ap, int64_t); break; case T_UINT64T: (*argtable) [n].uintmaxarg = va_arg (ap, uint64_t); break; case T_DOUBLE: #ifndef NO_FLOATING_POINT (*argtable) [n].doublearg = va_arg (ap, double); #endif break; case T_LONG_DOUBLE: #ifndef NO_FLOATING_POINT (*argtable) [n].longdoublearg = va_arg (ap, long double); #endif break; case TP_CHAR: (*argtable) [n].pchararg = va_arg (ap, char *); break; case TP_VOID: (*argtable) [n].pvoidarg = va_arg (ap, void *); break; case T_WINT: (*argtable) [n].wintarg = va_arg (ap, wint_t); break; case TP_WCHAR: (*argtable) [n].pwchararg = va_arg (ap, wchar_t *); break; } } } frr-7.2.1/lib/printf/printfcommon.h0000644000000000000000000001420113610377563014146 00000000000000/*- * SPDX-License-Identifier: BSD-3-Clause * * Copyright (c) 1990, 1993 * The Regents of the University of California. All rights reserved. * * This code is derived from software contributed to Berkeley by * Chris Torek. * * Copyright (c) 2011 The FreeBSD Foundation * All rights reserved. * Portions of this software were developed by David Chisnall * under sponsorship from the FreeBSD Foundation. * * Redistribution and use in source and binary forms, with or without * modification, are permitted provided that the following conditions * are met: * 1. Redistributions of source code must retain the above copyright * notice, this list of conditions and the following disclaimer. * 2. Redistributions in binary form must reproduce the above copyright * notice, this list of conditions and the following disclaimer in the * documentation and/or other materials provided with the distribution. * 3. Neither the name of the University nor the names of its contributors * may be used to endorse or promote products derived from this software * without specific prior written permission. * * THIS SOFTWARE IS PROVIDED BY THE REGENTS AND CONTRIBUTORS ``AS IS'' AND * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE * ARE DISCLAIMED. IN NO EVENT SHALL THE REGENTS OR CONTRIBUTORS BE LIABLE * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF * SUCH DAMAGE. * * $FreeBSD$ */ /* * This file defines common routines used by both printf and wprintf. * You must define CHAR to either char or wchar_t prior to including this. */ static CHAR *__ujtoa(uintmax_t, CHAR *, int, int, const char *); static CHAR *__ultoa(u_long, CHAR *, int, int, const char *); #define NIOV 8 struct io_state { struct fbuf *cb; size_t avail; }; static inline void io_init(struct io_state *iop, struct fbuf *cb) { iop->cb = cb; iop->avail = cb ? cb->len - (cb->pos - cb->buf) : 0; } /* * WARNING: The buffer passed to io_print() is not copied immediately; it must * remain valid until io_flush() is called. */ static inline int io_print(struct io_state *iop, const CHAR * __restrict ptr, size_t len) { size_t copylen = len; if (!iop->cb) return 0; if (iop->avail < copylen) copylen = iop->avail; memcpy(iop->cb->pos, ptr, copylen); iop->avail -= copylen; iop->cb->pos += copylen; return 0; } /* * Choose PADSIZE to trade efficiency vs. size. If larger printf * fields occur frequently, increase PADSIZE and make the initialisers * below longer. */ #define PADSIZE 16 /* pad chunk size */ static const CHAR blanks[PADSIZE] = {' ',' ',' ',' ',' ',' ',' ',' ',' ',' ',' ',' ',' ',' ',' ',' '}; static const CHAR zeroes[PADSIZE] = {'0','0','0','0','0','0','0','0','0','0','0','0','0','0','0','0'}; /* * Pad with blanks or zeroes. 'with' should point to either the blanks array * or the zeroes array. */ static inline int io_pad(struct io_state *iop, int howmany, const CHAR * __restrict with) { int n; while (howmany > 0) { n = (howmany >= PADSIZE) ? PADSIZE : howmany; if (io_print(iop, with, n)) return (-1); howmany -= n; } return (0); } /* * Print exactly len characters of the string spanning p to ep, truncating * or padding with 'with' as necessary. */ static inline int io_printandpad(struct io_state *iop, const CHAR *p, const CHAR *ep, int len, const CHAR * __restrict with) { int p_len; p_len = ep - p; if (p_len > len) p_len = len; if (p_len > 0) { if (io_print(iop, p, p_len)) return (-1); } else { p_len = 0; } return (io_pad(iop, len - p_len, with)); } /* * Convert an unsigned long to ASCII for printf purposes, returning * a pointer to the first character of the string representation. * Octal numbers can be forced to have a leading zero; hex numbers * use the given digits. */ static CHAR * __ultoa(u_long val, CHAR *endp, int base, int octzero, const char *xdigs) { CHAR *cp = endp; long sval; /* * Handle the three cases separately, in the hope of getting * better/faster code. */ switch (base) { case 10: if (val < 10) { /* many numbers are 1 digit */ *--cp = to_char(val); return (cp); } /* * On many machines, unsigned arithmetic is harder than * signed arithmetic, so we do at most one unsigned mod and * divide; this is sufficient to reduce the range of * the incoming value to where signed arithmetic works. */ if (val > LONG_MAX) { *--cp = to_char(val % 10); sval = val / 10; } else sval = val; do { *--cp = to_char(sval % 10); sval /= 10; } while (sval != 0); break; case 8: do { *--cp = to_char(val & 7); val >>= 3; } while (val); if (octzero && *cp != '0') *--cp = '0'; break; case 16: do { *--cp = xdigs[val & 15]; val >>= 4; } while (val); break; default: /* oops */ abort(); } return (cp); } /* Identical to __ultoa, but for intmax_t. */ static CHAR * __ujtoa(uintmax_t val, CHAR *endp, int base, int octzero, const char *xdigs) { CHAR *cp = endp; intmax_t sval; /* quick test for small values; __ultoa is typically much faster */ /* (perhaps instead we should run until small, then call __ultoa?) */ if (val <= ULONG_MAX) return (__ultoa((u_long)val, endp, base, octzero, xdigs)); switch (base) { case 10: if (val < 10) { *--cp = to_char(val % 10); return (cp); } if (val > INTMAX_MAX) { *--cp = to_char(val % 10); sval = val / 10; } else sval = val; do { *--cp = to_char(sval % 10); sval /= 10; } while (sval != 0); break; case 8: do { *--cp = to_char(val & 7); val >>= 3; } while (val); if (octzero && *cp != '0') *--cp = '0'; break; case 16: do { *--cp = xdigs[val & 15]; val >>= 4; } while (val); break; default: abort(); } return (cp); } frr-7.2.1/lib/printf/printflocal.h0000644000000000000000000000716213610377563013760 00000000000000/*- * SPDX-License-Identifier: BSD-3-Clause * * Copyright (c) 1990, 1993 * The Regents of the University of California. All rights reserved. * * This code is derived from software contributed to Berkeley by * Chris Torek. * * Redistribution and use in source and binary forms, with or without * modification, are permitted provided that the following conditions * are met: * 1. Redistributions of source code must retain the above copyright * notice, this list of conditions and the following disclaimer. * 2. Redistributions in binary form must reproduce the above copyright * notice, this list of conditions and the following disclaimer in the * documentation and/or other materials provided with the distribution. * 3. Neither the name of the University nor the names of its contributors * may be used to endorse or promote products derived from this software * without specific prior written permission. * * THIS SOFTWARE IS PROVIDED BY THE REGENTS AND CONTRIBUTORS ``AS IS'' AND * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE * ARE DISCLAIMED. IN NO EVENT SHALL THE REGENTS OR CONTRIBUTORS BE LIABLE * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF * SUCH DAMAGE. * * $FreeBSD$ */ #include "compiler.h" #include "printfrr.h" /* * Flags used during conversion. */ #define ALT 0x001 /* alternate form */ #define LADJUST 0x004 /* left adjustment */ #define LONGDBL 0x008 /* long double */ #define LONGINT 0x010 /* long integer */ #define LLONGINT 0x020 /* long long integer */ #define SHORTINT 0x040 /* short integer */ #define ZEROPAD 0x080 /* zero (as opposed to blank) pad */ #define FPT 0x100 /* Floating point number */ #define GROUPING 0x200 /* use grouping ("'" flag) */ /* C99 additional size modifiers: */ #define SIZET 0x400 /* size_t */ #define PTRDIFFT 0x800 /* ptrdiff_t */ #define INTMAXT 0x1000 /* intmax_t */ #define CHARINT 0x2000 /* print char using int format */ /* * Macros for converting digits to letters and vice versa */ #define to_digit(c) ((c) - '0') #define is_digit(c) ((unsigned)to_digit(c) <= 9) #define to_char(n) ((n) + '0') /* Size of the static argument table. */ #define STATIC_ARG_TBL_SIZE 8 union arg { int intarg; u_int uintarg; long longarg; u_long ulongarg; long long longlongarg; unsigned long long ulonglongarg; ptrdiff_t ptrdiffarg; size_t sizearg; intmax_t intmaxarg; uintmax_t uintmaxarg; void *pvoidarg; char *pchararg; signed char *pschararg; short *pshortarg; int *pintarg; long *plongarg; long long *plonglongarg; ptrdiff_t *pptrdiffarg; ssize_t *pssizearg; intmax_t *pintmaxarg; #ifndef NO_FLOATING_POINT double doublearg; long double longdoublearg; #endif wint_t wintarg; wchar_t *pwchararg; }; /* Handle positional parameters. */ int _frr_find_arguments(const char *, va_list, union arg **) DSO_LOCAL; #ifdef WCHAR_SUPPORT int _frr_find_warguments(const wchar_t *, va_list, union arg **) DSO_LOCAL; #endif /* returns number of bytes consumed for extended specifier */ ssize_t printfrr_extp(char *, size_t, const char *, int, const void *) DSO_LOCAL; ssize_t printfrr_exti(char *, size_t, const char *, int, uintmax_t) DSO_LOCAL; frr-7.2.1/lib/printf/vfprintf.c0000644000000000000000000004321013610377563013266 00000000000000/*- * SPDX-License-Identifier: BSD-3-Clause * * Copyright (c) 1990, 1993 * The Regents of the University of California. All rights reserved. * * This code is derived from software contributed to Berkeley by * Chris Torek. * * Copyright (c) 2011 The FreeBSD Foundation * All rights reserved. * Portions of this software were developed by David Chisnall * under sponsorship from the FreeBSD Foundation. * * Redistribution and use in source and binary forms, with or without * modification, are permitted provided that the following conditions * are met: * 1. Redistributions of source code must retain the above copyright * notice, this list of conditions and the following disclaimer. * 2. Redistributions in binary form must reproduce the above copyright * notice, this list of conditions and the following disclaimer in the * documentation and/or other materials provided with the distribution. * 3. Neither the name of the University nor the names of its contributors * may be used to endorse or promote products derived from this software * without specific prior written permission. * * THIS SOFTWARE IS PROVIDED BY THE REGENTS AND CONTRIBUTORS ``AS IS'' AND * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE * ARE DISCLAIMED. IN NO EVENT SHALL THE REGENTS OR CONTRIBUTORS BE LIABLE * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF * SUCH DAMAGE. */ #ifdef HAVE_CONFIG_H #include "config.h" #endif #ifdef HAVE_SYS_CDEFS_H #include #endif /* * Actual printf innards. * * This code is large and complicated... */ #include #include #include #include #include #include #include #include #include #include #include #include #include "printflocal.h" #define CHAR char #include "printfcommon.h" #ifdef WCHAR_SUPPORT /* * Convert a wide character string argument for the %ls format to a multibyte * string representation. If not -1, prec specifies the maximum number of * bytes to output, and also means that we can't assume that the wide char. * string ends is null-terminated. */ static char * __wcsconv(wchar_t *wcsarg, int prec) { static const mbstate_t initial; mbstate_t mbs; char buf[MB_LEN_MAX]; wchar_t *p; char *convbuf; size_t clen, nbytes; /* Allocate space for the maximum number of bytes we could output. */ if (prec < 0) { p = wcsarg; mbs = initial; nbytes = wcsrtombs(NULL, (const wchar_t **)&p, 0, &mbs); if (nbytes == (size_t)-1) return (NULL); } else { /* * Optimisation: if the output precision is small enough, * just allocate enough memory for the maximum instead of * scanning the string. */ if (prec < 128) nbytes = prec; else { nbytes = 0; p = wcsarg; mbs = initial; for (;;) { clen = wcrtomb(buf, *p++, &mbs); if (clen == 0 || clen == (size_t)-1 || nbytes + clen > (size_t)prec) break; nbytes += clen; } } } if ((convbuf = malloc(nbytes + 1)) == NULL) return (NULL); /* Fill the output buffer. */ p = wcsarg; mbs = initial; if ((nbytes = wcsrtombs(convbuf, (const wchar_t **)&p, nbytes, &mbs)) == (size_t)-1) { free(convbuf); return (NULL); } convbuf[nbytes] = '\0'; return (convbuf); } #endif /* WCHAR_SUPPORT */ /* * The size of the buffer we use as scratch space for integer * conversions, among other things. We need enough space to * write a uintmax_t in octal (plus one byte). */ #if UINTMAX_MAX <= UINT64_MAX #define BUF 64 #else #error "BUF must be large enough to format a uintmax_t" #endif /* * Non-MT-safe version */ ssize_t vbprintfrr(struct fbuf *cb, const char *fmt0, va_list ap) { const char *fmt; /* format string */ int ch; /* character from fmt */ int n, n2; /* handy integer (short term usage) */ const char *cp; /* handy char pointer (short term usage) */ int flags; /* flags as above */ int ret; /* return value accumulator */ int width; /* width from format (%8d), or 0 */ int prec; /* precision from format; <0 for N/A */ int saved_errno; char sign; /* sign prefix (' ', '+', '-', or \0) */ u_long ulval = 0; /* integer arguments %[diouxX] */ uintmax_t ujval = 0; /* %j, %ll, %q, %t, %z integers */ void *ptrval; /* %p */ int base; /* base for [diouxX] conversion */ int dprec; /* a copy of prec if [diouxX], 0 otherwise */ int realsz; /* field size expanded by dprec, sign, etc */ int size; /* size of converted field or string */ int prsize; /* max size of printed field */ const char *xdigs; /* digits for %[xX] conversion */ struct io_state io; /* I/O buffering state */ char buf[BUF]; /* buffer with space for digits of uintmax_t */ char ox[2]; /* space for 0x; ox[1] is either x, X, or \0 */ union arg *argtable; /* args, built due to positional arg */ union arg statargtable [STATIC_ARG_TBL_SIZE]; int nextarg; /* 1-based argument index */ va_list orgap; /* original argument pointer */ char *convbuf; /* wide to multibyte conversion result */ static const char xdigs_lower[16] = "0123456789abcdef"; static const char xdigs_upper[16] = "0123456789ABCDEF"; /* BEWARE, these `goto error' on error. */ #define PRINT(ptr, len) { \ if (io_print(&io, (ptr), (len))) \ goto error; \ } #define PAD(howmany, with) { \ if (io_pad(&io, (howmany), (with))) \ goto error; \ } #define PRINTANDPAD(p, ep, len, with) { \ if (io_printandpad(&io, (p), (ep), (len), (with))) \ goto error; \ } #define FLUSH() do { } while (0) /* * Get the argument indexed by nextarg. If the argument table is * built, use it to get the argument. If its not, get the next * argument (and arguments must be gotten sequentially). */ #define GETARG(type) \ ((argtable != NULL) ? *((type*)(&argtable[nextarg++])) : \ (nextarg++, va_arg(ap, type))) /* * To extend shorts properly, we need both signed and unsigned * argument extraction methods. */ #define SARG() \ (flags&LONGINT ? GETARG(long) : \ flags&SHORTINT ? (long)(short)GETARG(int) : \ flags&CHARINT ? (long)(signed char)GETARG(int) : \ (long)GETARG(int)) #define UARG() \ (flags&LONGINT ? GETARG(u_long) : \ flags&SHORTINT ? (u_long)(u_short)GETARG(int) : \ flags&CHARINT ? (u_long)(u_char)GETARG(int) : \ (u_long)GETARG(u_int)) #define INTMAX_SIZE (INTMAXT|SIZET|PTRDIFFT|LLONGINT|LONGDBL) #define SJARG() \ (flags&LONGDBL ? GETARG(int64_t) : \ flags&INTMAXT ? GETARG(intmax_t) : \ flags&SIZET ? (intmax_t)GETARG(ssize_t) : \ flags&PTRDIFFT ? (intmax_t)GETARG(ptrdiff_t) : \ (intmax_t)GETARG(long long)) #define UJARG() \ (flags&LONGDBL ? GETARG(uint64_t) : \ flags&INTMAXT ? GETARG(uintmax_t) : \ flags&SIZET ? (uintmax_t)GETARG(size_t) : \ flags&PTRDIFFT ? (uintmax_t)GETARG(ptrdiff_t) : \ (uintmax_t)GETARG(unsigned long long)) /* * Get * arguments, including the form *nn$. Preserve the nextarg * that the argument can be gotten once the type is determined. */ #define GETASTER(val) \ n2 = 0; \ cp = fmt; \ while (is_digit(*cp)) { \ n2 = 10 * n2 + to_digit(*cp); \ cp++; \ } \ if (*cp == '$') { \ int hold = nextarg; \ if (argtable == NULL) { \ argtable = statargtable; \ if (_frr_find_arguments (fmt0, orgap, &argtable)) { \ ret = EOF; \ goto error; \ } \ } \ nextarg = n2; \ val = GETARG (int); \ nextarg = hold; \ fmt = ++cp; \ } else { \ val = GETARG (int); \ } xdigs = xdigs_lower; saved_errno = errno; convbuf = NULL; fmt = (char *)fmt0; argtable = NULL; nextarg = 1; va_copy(orgap, ap); io_init(&io, cb); ret = 0; /* * Scan the format for conversions (`%' character). */ for (;;) { for (cp = fmt; (ch = *fmt) != '\0' && ch != '%'; fmt++) /* void */; if ((n = fmt - cp) != 0) { if ((unsigned)ret + n > INT_MAX) { ret = EOF; errno = EOVERFLOW; goto error; } PRINT(cp, n); ret += n; } if (ch == '\0') goto done; fmt++; /* skip over '%' */ flags = 0; dprec = 0; width = 0; prec = -1; sign = '\0'; ox[1] = '\0'; rflag: ch = *fmt++; reswitch: switch (ch) { case ' ': /*- * ``If the space and + flags both appear, the space * flag will be ignored.'' * -- ANSI X3J11 */ if (!sign) sign = ' '; goto rflag; case '#': flags |= ALT; goto rflag; case '*': /*- * ``A negative field width argument is taken as a * - flag followed by a positive field width.'' * -- ANSI X3J11 * They don't exclude field widths read from args. */ GETASTER (width); if (width >= 0) goto rflag; width = -width; /* FALLTHROUGH */ case '-': flags |= LADJUST; goto rflag; case '+': sign = '+'; goto rflag; case '\'': flags |= GROUPING; goto rflag; case '.': if ((ch = *fmt++) == '*') { GETASTER (prec); goto rflag; } prec = 0; while (is_digit(ch)) { prec = 10 * prec + to_digit(ch); ch = *fmt++; } goto reswitch; case '0': /*- * ``Note that 0 is taken as a flag, not as the * beginning of a field width.'' * -- ANSI X3J11 */ flags |= ZEROPAD; goto rflag; case '1': case '2': case '3': case '4': case '5': case '6': case '7': case '8': case '9': n = 0; do { n = 10 * n + to_digit(ch); ch = *fmt++; } while (is_digit(ch)); if (ch == '$') { nextarg = n; if (argtable == NULL) { argtable = statargtable; if (_frr_find_arguments (fmt0, orgap, &argtable)) { ret = EOF; goto error; } } goto rflag; } width = n; goto reswitch; case 'L': flags |= LONGDBL; goto rflag; case 'h': if (flags & SHORTINT) { flags &= ~SHORTINT; flags |= CHARINT; } else flags |= SHORTINT; goto rflag; case 'j': flags |= INTMAXT; goto rflag; case 'l': if (flags & LONGINT) { flags &= ~LONGINT; flags |= LLONGINT; } else flags |= LONGINT; goto rflag; case 'q': flags |= LLONGINT; /* not necessarily */ goto rflag; case 't': flags |= PTRDIFFT; goto rflag; case 'z': flags |= SIZET; goto rflag; case 'C': flags |= LONGINT; /*FALLTHROUGH*/ case 'c': #ifdef WCHAR_SUPPORT if (flags & LONGINT) { static const mbstate_t initial; mbstate_t mbs; size_t mbseqlen; mbs = initial; mbseqlen = wcrtomb(cp = buf, (wchar_t)GETARG(wint_t), &mbs); if (mbseqlen == (size_t)-1) { goto error; } size = (int)mbseqlen; } else #endif /* WCHAR_SUPPORT */ { buf[0] = GETARG(int); cp = buf; size = 1; } sign = '\0'; break; case 'D': flags |= LONGINT; /*FALLTHROUGH*/ case 'd': case 'i': if (flags & INTMAX_SIZE) ujval = SJARG(); else ulval = SARG(); if (printfrr_ext_char(fmt[0])) { n2 = printfrr_exti(buf, sizeof(buf), fmt, prec, (flags & INTMAX_SIZE) ? ujval : (uintmax_t)ulval); if (n2 > 0) { fmt += n2; cp = buf; size = strlen(cp); sign = '\0'; break; } } if (flags & INTMAX_SIZE) { if ((intmax_t)ujval < 0) { ujval = -ujval; sign = '-'; } } else { if ((long)ulval < 0) { ulval = -ulval; sign = '-'; } } base = 10; goto number; #ifndef NO_FLOATING_POINT case 'a': case 'A': case 'e': case 'E': case 'f': case 'F': case 'g': case 'G': if (flags & LONGDBL) { long double arg = GETARG(long double); char fmt[6] = "%.*L"; fmt[4] = ch; fmt[5] = '\0'; snprintf(buf, sizeof(buf), fmt, prec, arg); } else { double arg = GETARG(double); char fmt[5] = "%.*"; fmt[3] = ch; fmt[4] = '\0'; snprintf(buf, sizeof(buf), fmt, prec, arg); } cp = buf; /* for proper padding */ if (*cp == '-') { cp++; sign = '-'; } /* "inf" */ if (!is_digit(*cp) && *cp != '.') flags &= ~ZEROPAD; size = strlen(buf); break; #endif case 'm': cp = strerror(saved_errno); size = (prec >= 0) ? strnlen(cp, prec) : strlen(cp); sign = '\0'; break; case 'n': /* * Assignment-like behavior is specified if the * value overflows or is otherwise unrepresentable. * C99 says to use `signed char' for %hhn conversions. */ if (flags & LLONGINT) *GETARG(long long *) = ret; else if (flags & SIZET) *GETARG(ssize_t *) = (ssize_t)ret; else if (flags & PTRDIFFT) *GETARG(ptrdiff_t *) = ret; else if (flags & INTMAXT) *GETARG(intmax_t *) = ret; else if (flags & LONGINT) *GETARG(long *) = ret; else if (flags & SHORTINT) *GETARG(short *) = ret; else if (flags & CHARINT) *GETARG(signed char *) = ret; else *GETARG(int *) = ret; continue; /* no output */ case 'O': flags |= LONGINT; /*FALLTHROUGH*/ case 'o': if (flags & INTMAX_SIZE) ujval = UJARG(); else ulval = UARG(); base = 8; goto nosign; case 'p': /*- * ``The argument shall be a pointer to void. The * value of the pointer is converted to a sequence * of printable characters, in an implementation- * defined manner.'' * -- ANSI X3J11 */ ptrval = GETARG(void *); if (printfrr_ext_char(fmt[0]) && (n2 = printfrr_extp(buf, sizeof(buf), fmt, prec, ptrval)) > 0) { fmt += n2; cp = buf; size = strlen(cp); sign = '\0'; break; } ujval = (uintmax_t)(uintptr_t)ptrval; base = 16; xdigs = xdigs_lower; flags = flags | INTMAXT; ox[1] = 'x'; goto nosign; case 'S': flags |= LONGINT; /*FALLTHROUGH*/ case 's': #ifdef WCHAR_SUPPORT if (flags & LONGINT) { wchar_t *wcp; if (convbuf != NULL) free(convbuf); if ((wcp = GETARG(wchar_t *)) == NULL) cp = "(null)"; else { convbuf = __wcsconv(wcp, prec); if (convbuf == NULL) { goto error; } cp = convbuf; } } else #endif if ((cp = GETARG(char *)) == NULL) cp = "(null)"; size = (prec >= 0) ? strnlen(cp, prec) : strlen(cp); sign = '\0'; break; case 'U': flags |= LONGINT; /*FALLTHROUGH*/ case 'u': if (flags & INTMAX_SIZE) ujval = UJARG(); else ulval = UARG(); base = 10; goto nosign; case 'X': xdigs = xdigs_upper; goto hex; case 'x': xdigs = xdigs_lower; hex: if (flags & INTMAX_SIZE) ujval = UJARG(); else ulval = UARG(); base = 16; /* leading 0x/X only if non-zero */ if (flags & ALT && (flags & INTMAX_SIZE ? ujval != 0 : ulval != 0)) ox[1] = ch; flags &= ~GROUPING; /* unsigned conversions */ nosign: sign = '\0'; /*- * ``... diouXx conversions ... if a precision is * specified, the 0 flag will be ignored.'' * -- ANSI X3J11 */ number: if ((dprec = prec) >= 0) flags &= ~ZEROPAD; /*- * ``The result of converting a zero value with an * explicit precision of zero is no characters.'' * -- ANSI X3J11 * * ``The C Standard is clear enough as is. The call * printf("%#.0o", 0) should print 0.'' * -- Defect Report #151 */ cp = buf + BUF; if (flags & INTMAX_SIZE) { if (ujval != 0 || prec != 0 || (flags & ALT && base == 8)) cp = __ujtoa(ujval, buf + BUF, base, flags & ALT, xdigs); } else { if (ulval != 0 || prec != 0 || (flags & ALT && base == 8)) cp = __ultoa(ulval, buf + BUF, base, flags & ALT, xdigs); } size = buf + BUF - cp; if (size > BUF) /* should never happen */ abort(); break; default: /* "%?" prints ?, unless ? is NUL */ if (ch == '\0') goto done; /* pretend it was %c with argument ch */ buf[0] = ch; cp = buf; size = 1; sign = '\0'; break; } /* * All reasonable formats wind up here. At this point, `cp' * points to a string which (if not flags&LADJUST) should be * padded out to `width' places. If flags&ZEROPAD, it should * first be prefixed by any sign or other prefix; otherwise, * it should be blank padded before the prefix is emitted. * After any left-hand padding and prefixing, emit zeroes * required by a decimal [diouxX] precision, then print the * string proper, then emit zeroes required by any leftover * floating precision; finally, if LADJUST, pad with blanks. * * Compute actual size, so we know how much to pad. * size excludes decimal prec; realsz includes it. */ realsz = dprec > size ? dprec : size; if (sign) realsz++; if (ox[1]) realsz += 2; prsize = width > realsz ? width : realsz; if ((unsigned)ret + prsize > INT_MAX) { ret = EOF; errno = EOVERFLOW; goto error; } /* right-adjusting blank padding */ if ((flags & (LADJUST|ZEROPAD)) == 0) PAD(width - realsz, blanks); /* prefix */ if (sign) PRINT(&sign, 1); if (ox[1]) { /* ox[1] is either x, X, or \0 */ ox[0] = '0'; PRINT(ox, 2); } /* right-adjusting zero padding */ if ((flags & (LADJUST|ZEROPAD)) == ZEROPAD) PAD(width - realsz, zeroes); /* the string or number proper */ /* leading zeroes from decimal precision */ PAD(dprec - size, zeroes); PRINT(cp, size); /* left-adjusting padding (always blank) */ if (flags & LADJUST) PAD(width - realsz, blanks); /* finally, adjust ret */ ret += prsize; FLUSH(); /* copy out the I/O vectors */ } done: FLUSH(); error: va_end(orgap); if (convbuf != NULL) free(convbuf); if ((argtable != NULL) && (argtable != statargtable)) free (argtable); return (ret); /* NOTREACHED */ } frr-7.2.1/lib/printfrr.h0000644000000000000000000001452213610377563012005 00000000000000/* * Copyright (c) 2019 David Lamparter, for NetDEF, Inc. * * Permission to use, copy, modify, and distribute this software for any * purpose with or without fee is hereby granted, provided that the above * copyright notice and this permission notice appear in all copies. * * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. */ #ifndef _FRR_PRINTFRR_H #define _FRR_PRINTFRR_H #include #include #include #include "compiler.h" #include "memory.h" struct fbuf { char *buf; char *pos; size_t len; }; #define at(a, b) \ __attribute__((format(printf, a, b))) #define atn(a, b) \ at(a, b) __attribute__((nonnull(1) _RET_NONNULL)) #define atm(a, b) \ atn(a, b) __attribute__((malloc)) /* return value is length needed for the full string (excluding \0) in all * cases. The functions write as much as they can, but continue regardless, * so the return value is independent of buffer length. Both bprintfrr and * snprintf also accept NULL as output buffer. */ /* bprintfrr does NOT null terminate! use sparingly (only provided since it's * the most direct interface) - useful for incrementally building long text * (call bprintfrr repeatedly with the same buffer) */ ssize_t vbprintfrr(struct fbuf *out, const char *fmt, va_list) at(2, 0); ssize_t bprintfrr(struct fbuf *out, const char *fmt, ...) at(2, 3); /* these do null terminate like their snprintf cousins */ ssize_t vsnprintfrr(char *out, size_t sz, const char *fmt, va_list) at(3, 0); ssize_t snprintfrr(char *out, size_t sz, const char *fmt, ...) at(3, 4); /* c = continue / concatenate (append at the end of the string) * return value is would-be string length (regardless of buffer length), * i.e. includes already written chars */ ssize_t vcsnprintfrr(char *out, size_t sz, const char *fmt, va_list) at(3, 0); ssize_t csnprintfrr(char *out, size_t sz, const char *fmt, ...) at(3, 4); /* memory allocations don't fail in FRR, so you always get something here. * (in case of error, returns a strdup of the format string) */ char *vasprintfrr(struct memtype *mt, const char *fmt, va_list) atm(2, 0); char *asprintfrr(struct memtype *mt, const char *fmt, ...) atm(2, 3); /* try to use provided buffer (presumably from stack), allocate if it's too * short. Must call XFREE(mt, return value) if return value != out. */ char *vasnprintfrr(struct memtype *mt, char *out, size_t sz, const char *fmt, va_list) atn(4, 0); char *asnprintfrr(struct memtype *mt, char *out, size_t sz, const char *fmt, ...) atn(4, 5); #undef at #undef atm /* extension specs must start with a capital letter (this is a restriction * for both performance's and human understanding's sake.) * * Note that the entire thing mostly works because a letter directly following * a %p print specifier is extremely unlikely to occur (why would you want to * print "0x12345678HELLO"?) Normally, you'd expect spacing or punctuation * after a placeholder. That also means that neither of those works well for * extension purposes, e.g. "%p{foo}" is reasonable to see actually used. * * TODO: would be nice to support a "%pF%dF" specifier that consumes 2 * arguments, e.g. to pass an integer + a list of known values... can be * done, but a bit tricky. */ #define printfrr_ext_char(ch) ((ch) >= 'A' && (ch) <= 'Z') struct printfrr_ext { /* embedded string to minimize cache line pollution */ char match[8]; /* both can be given, if not the code continues searching * (you can do %pX and %dX in 2 different entries) * * return value: number of bytes consumed from the format string, so * you can consume extra flags (e.g. register for "%pX", consume * "%pXfoo" or "%pXbar" for flags.) Convention is to make those flags * lowercase letters or numbers. * * bsz is a compile-time constant in printf; it's gonna be relatively * small. This isn't designed to print Shakespeare from a pointer. * * prec is the precision specifier (the 999 in "%.999p") -1 means * none given (value in the format string cannot be negative) */ ssize_t (*print_ptr)(char *buf, size_t bsz, const char *fmt, int prec, const void *); ssize_t (*print_int)(char *buf, size_t bsz, const char *fmt, int prec, uintmax_t); }; /* no locking - must be called when single threaded (e.g. at startup.) * this restriction hopefully won't be a huge bother considering normal usage * scenarios... */ void printfrr_ext_reg(const struct printfrr_ext *); #define printfrr_ext_autoreg_p(matchs, print_fn) \ static ssize_t print_fn(char *, size_t, const char *, int, \ const void *); \ static struct printfrr_ext _printext_##print_fn = { \ .match = matchs, \ .print_ptr = print_fn, \ }; \ static void _printreg_##print_fn(void) __attribute__((constructor)); \ static void _printreg_##print_fn(void) { \ printfrr_ext_reg(&_printext_##print_fn); \ } \ /* end */ #define printfrr_ext_autoreg_i(matchs, print_fn) \ static ssize_t print_fn(char *, size_t, const char *, int, uintmax_t); \ static struct printfrr_ext _printext_##print_fn = { \ .match = matchs, \ .print_int = print_fn, \ }; \ static void _printreg_##print_fn(void) __attribute__((constructor)); \ static void _printreg_##print_fn(void) { \ printfrr_ext_reg(&_printext_##print_fn); \ } \ /* end */ #endif frr-7.2.1/lib/privs.c0000644000000000000000000006411713610377563011302 00000000000000/* * Zebra privileges. * * Copyright (C) 2003 Paul Jakma. * Copyright (c) 2005, 2011, Oracle and/or its affiliates. All rights reserved. * * This file is part of GNU Zebra. * * GNU Zebra is free software; you can redistribute it and/or modify it * under the terms of the GNU General Public License as published by the * Free Software Foundation; either version 2, or (at your option) any * later version. * * GNU Zebra 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; see the file COPYING; if not, write to the Free Software * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA */ #include #include "log.h" #include "privs.h" #include "memory.h" #include "frr_pthread.h" #include "lib_errors.h" #include "lib/queue.h" DEFINE_MTYPE_STATIC(LIB, PRIVS, "Privilege information") /* * Different capabilities/privileges apis have different characteristics: some * are process-wide, and some are per-thread. */ #ifdef HAVE_CAPABILITIES #ifdef HAVE_LCAPS static const bool privs_per_process; /* = false */ #elif defined(HAVE_SOLARIS_CAPABILITIES) static const bool privs_per_process = true; #endif #else static const bool privs_per_process = true; #endif /* HAVE_CAPABILITIES */ #ifdef HAVE_CAPABILITIES /* sort out some generic internal types for: * * privilege values (cap_value_t, priv_t) -> pvalue_t * privilege set (..., priv_set_t) -> pset_t * privilege working storage (cap_t, ...) -> pstorage_t * * values we think of as numeric (they're ints really, but we dont know) * sets are mostly opaque, to hold a set of privileges, related in some way. * storage binds together a set of sets we're interested in. * (in reality: cap_value_t and priv_t are ints) */ #ifdef HAVE_LCAPS /* Linux doesn't have a 'set' type: a set of related privileges */ struct _pset { int num; cap_value_t *caps; }; typedef cap_value_t pvalue_t; typedef struct _pset pset_t; typedef cap_t pstorage_t; #elif defined(HAVE_SOLARIS_CAPABILITIES) typedef priv_t pvalue_t; typedef priv_set_t pset_t; typedef priv_set_t *pstorage_t; #else /* neither LCAPS nor SOLARIS_CAPABILITIES */ #error "HAVE_CAPABILITIES defined, but neither LCAPS nor Solaris Capabilties!" #endif /* HAVE_LCAPS */ #endif /* HAVE_CAPABILITIES */ /* the default NULL state we report is RAISED, but could be LOWERED if * zprivs_terminate is called and the NULL handler is installed. */ static zebra_privs_current_t zprivs_null_state = ZPRIVS_RAISED; /* internal privileges state */ static struct _zprivs_t { #ifdef HAVE_CAPABILITIES pstorage_t caps; /* working storage */ pset_t *syscaps_p; /* system-type requested permitted caps */ pset_t *syscaps_i; /* system-type requested inheritable caps */ #endif /* HAVE_CAPABILITIES */ uid_t zuid, /* uid to run as */ zsuid; /* saved uid */ gid_t zgid; /* gid to run as */ gid_t vtygrp; /* gid for vty sockets */ } zprivs_state; /* externally exported but not directly accessed functions */ #ifdef HAVE_CAPABILITIES int zprivs_change_caps(zebra_privs_ops_t); zebra_privs_current_t zprivs_state_caps(void); #endif /* HAVE_CAPABILITIES */ int zprivs_change_uid(zebra_privs_ops_t); zebra_privs_current_t zprivs_state_uid(void); int zprivs_change_null(zebra_privs_ops_t); zebra_privs_current_t zprivs_state_null(void); #ifdef HAVE_CAPABILITIES /* internal capability API */ static pset_t *zcaps2sys(zebra_capabilities_t *, int); static void zprivs_caps_init(struct zebra_privs_t *); static void zprivs_caps_terminate(void); /* Map of Quagga abstract capabilities to system capabilities */ static struct { int num; pvalue_t *system_caps; } cap_map[ZCAP_MAX] = { #ifdef HAVE_LCAPS /* Quagga -> Linux capabilities mappings */ [ZCAP_SETID] = { 2, (pvalue_t[]){CAP_SETGID, CAP_SETUID}, }, [ZCAP_BIND] = { 1, (pvalue_t[]){CAP_NET_BIND_SERVICE}, }, [ZCAP_NET_ADMIN] = { 1, (pvalue_t[]){CAP_NET_ADMIN}, }, [ZCAP_NET_RAW] = { 1, (pvalue_t[]){CAP_NET_RAW}, }, [ZCAP_CHROOT] = { 1, (pvalue_t[]){ CAP_SYS_CHROOT, }, }, [ZCAP_NICE] = { 1, (pvalue_t[]){CAP_SYS_NICE}, }, [ZCAP_PTRACE] = { 1, (pvalue_t[]){CAP_SYS_PTRACE}, }, [ZCAP_DAC_OVERRIDE] = { 1, (pvalue_t[]){CAP_DAC_OVERRIDE}, }, [ZCAP_READ_SEARCH] = { 1, (pvalue_t[]){CAP_DAC_READ_SEARCH}, }, [ZCAP_SYS_ADMIN] = { 1, (pvalue_t[]){CAP_SYS_ADMIN}, }, [ZCAP_FOWNER] = { 1, (pvalue_t[]){CAP_FOWNER}, }, #elif defined(HAVE_SOLARIS_CAPABILITIES) /* HAVE_LCAPS */ /* Quagga -> Solaris privilege mappings */ [ZCAP_SETID] = { 1, (pvalue_t[]){PRIV_PROC_SETID}, }, [ZCAP_BIND] = { 1, (pvalue_t[]){PRIV_NET_PRIVADDR}, }, /* IP_CONFIG is a subset of NET_CONFIG and is allowed in zones */ #ifdef PRIV_SYS_IP_CONFIG [ZCAP_NET_ADMIN] = { 1, (pvalue_t[]){PRIV_SYS_IP_CONFIG}, }, #else [ZCAP_NET_ADMIN] = { 1, (pvalue_t[]){PRIV_SYS_NET_CONFIG}, }, #endif [ZCAP_NET_RAW] = { 2, (pvalue_t[]){PRIV_NET_RAWACCESS, PRIV_NET_ICMPACCESS}, }, [ZCAP_CHROOT] = { 1, (pvalue_t[]){PRIV_PROC_CHROOT}, }, [ZCAP_NICE] = { 1, (pvalue_t[]){PRIV_PROC_PRIOCNTL}, }, [ZCAP_PTRACE] = { 1, (pvalue_t[]){PRIV_PROC_SESSION}, }, [ZCAP_DAC_OVERRIDE] = { 5, (pvalue_t[]){PRIV_FILE_DAC_EXECUTE, PRIV_FILE_DAC_READ, PRIV_FILE_DAC_SEARCH, PRIV_FILE_DAC_WRITE, PRIV_FILE_DAC_SEARCH}, }, [ZCAP_READ_SEARCH] = { 2, (pvalue_t[]){PRIV_FILE_DAC_SEARCH, PRIV_FILE_DAC_READ}, }, [ZCAP_SYS_ADMIN] = { 1, (pvalue_t[]){PRIV_SYS_ADMIN}, }, [ZCAP_FOWNER] = { 1, (pvalue_t[]){PRIV_FILE_OWNER}, }, #endif /* HAVE_SOLARIS_CAPABILITIES */ }; #ifdef HAVE_LCAPS /* Linux forms of capabilities methods */ /* convert zebras privileges to system capabilities */ static pset_t *zcaps2sys(zebra_capabilities_t *zcaps, int num) { pset_t *syscaps; int i, j = 0, count = 0; if (!num) return NULL; /* first count up how many system caps we have */ for (i = 0; i < num; i++) count += cap_map[zcaps[i]].num; if ((syscaps = XCALLOC(MTYPE_PRIVS, (sizeof(pset_t) * num))) == NULL) { fprintf(stderr, "%s: could not allocate syscaps!", __func__); return NULL; } syscaps->caps = XCALLOC(MTYPE_PRIVS, (sizeof(pvalue_t) * count)); if (!syscaps->caps) { fprintf(stderr, "%s: could not XCALLOC caps!", __func__); return NULL; } /* copy the capabilities over */ count = 0; for (i = 0; i < num; i++) for (j = 0; j < cap_map[zcaps[i]].num; j++) syscaps->caps[count++] = cap_map[zcaps[i]].system_caps[j]; /* iterations above should be exact same as previous count, obviously.. */ syscaps->num = count; return syscaps; } /* set or clear the effective capabilities to/from permitted */ int zprivs_change_caps(zebra_privs_ops_t op) { cap_flag_value_t cflag; /* should be no possibility of being called without valid caps */ assert(zprivs_state.syscaps_p && zprivs_state.caps); if (!(zprivs_state.syscaps_p && zprivs_state.caps)) exit(1); if (op == ZPRIVS_RAISE) cflag = CAP_SET; else if (op == ZPRIVS_LOWER) cflag = CAP_CLEAR; else return -1; if (!cap_set_flag(zprivs_state.caps, CAP_EFFECTIVE, zprivs_state.syscaps_p->num, zprivs_state.syscaps_p->caps, cflag)) return cap_set_proc(zprivs_state.caps); return -1; } zebra_privs_current_t zprivs_state_caps(void) { int i; cap_flag_value_t val; /* should be no possibility of being called without valid caps */ assert(zprivs_state.syscaps_p && zprivs_state.caps); if (!(zprivs_state.syscaps_p && zprivs_state.caps)) exit(1); for (i = 0; i < zprivs_state.syscaps_p->num; i++) { if (cap_get_flag(zprivs_state.caps, zprivs_state.syscaps_p->caps[i], CAP_EFFECTIVE, &val)) { flog_err( EC_LIB_SYSTEM_CALL, "zprivs_state_caps: could not cap_get_flag, %s", safe_strerror(errno)); return ZPRIVS_UNKNOWN; } if (val == CAP_SET) return ZPRIVS_RAISED; } return ZPRIVS_LOWERED; } static void zprivs_caps_init(struct zebra_privs_t *zprivs) { zprivs_state.syscaps_p = zcaps2sys(zprivs->caps_p, zprivs->cap_num_p); zprivs_state.syscaps_i = zcaps2sys(zprivs->caps_i, zprivs->cap_num_i); /* Tell kernel we want caps maintained across uid changes */ if (prctl(PR_SET_KEEPCAPS, 1, 0, 0, 0) == -1) { fprintf(stderr, "privs_init: could not set PR_SET_KEEPCAPS, %s\n", safe_strerror(errno)); exit(1); } /* we have caps, we have no need to ever change back the original user */ /* only change uid if we don't have the correct one */ if ((zprivs_state.zuid) && (zprivs_state.zsuid != zprivs_state.zuid)) { if (setreuid(zprivs_state.zuid, zprivs_state.zuid)) { fprintf(stderr, "zprivs_init (cap): could not setreuid, %s\n", safe_strerror(errno)); exit(1); } } if (!zprivs_state.syscaps_p) return; if (!(zprivs_state.caps = cap_init())) { fprintf(stderr, "privs_init: failed to cap_init, %s\n", safe_strerror(errno)); exit(1); } if (cap_clear(zprivs_state.caps)) { fprintf(stderr, "privs_init: failed to cap_clear, %s\n", safe_strerror(errno)); exit(1); } /* set permitted caps */ cap_set_flag(zprivs_state.caps, CAP_PERMITTED, zprivs_state.syscaps_p->num, zprivs_state.syscaps_p->caps, CAP_SET); /* set inheritable caps, if any */ if (zprivs_state.syscaps_i && zprivs_state.syscaps_i->num) { cap_set_flag(zprivs_state.caps, CAP_INHERITABLE, zprivs_state.syscaps_i->num, zprivs_state.syscaps_i->caps, CAP_SET); } /* apply caps. CAP_EFFECTIVE is cleared. we'll raise the caps as * and when, and only when, they are needed. */ if (cap_set_proc(zprivs_state.caps)) { cap_t current_caps; char *current_caps_text = NULL; char *wanted_caps_text = NULL; fprintf(stderr, "privs_init: initial cap_set_proc failed: %s\n", safe_strerror(errno)); current_caps = cap_get_proc(); if (current_caps) { current_caps_text = cap_to_text(current_caps, NULL); cap_free(current_caps); } wanted_caps_text = cap_to_text(zprivs_state.caps, NULL); fprintf(stderr, "Wanted caps: %s\n", wanted_caps_text ? wanted_caps_text : "???"); fprintf(stderr, "Have caps: %s\n", current_caps_text ? current_caps_text : "???"); if (current_caps_text) cap_free(current_caps_text); if (wanted_caps_text) cap_free(wanted_caps_text); exit(1); } /* set methods for the caller to use */ zprivs->change = zprivs_change_caps; zprivs->current_state = zprivs_state_caps; } static void zprivs_caps_terminate(void) { /* clear all capabilities */ if (zprivs_state.caps) cap_clear(zprivs_state.caps); /* and boom, capabilities are gone forever */ if (cap_set_proc(zprivs_state.caps)) { fprintf(stderr, "privs_terminate: cap_set_proc failed, %s", safe_strerror(errno)); exit(1); } /* free up private state */ if (zprivs_state.syscaps_p->num) { XFREE(MTYPE_PRIVS, zprivs_state.syscaps_p->caps); XFREE(MTYPE_PRIVS, zprivs_state.syscaps_p); } if (zprivs_state.syscaps_i && zprivs_state.syscaps_i->num) { XFREE(MTYPE_PRIVS, zprivs_state.syscaps_i->caps); XFREE(MTYPE_PRIVS, zprivs_state.syscaps_i); } cap_free(zprivs_state.caps); } #elif defined(HAVE_SOLARIS_CAPABILITIES) /* !HAVE_LCAPS */ /* Solaris specific capability/privilege methods * * Resources: * - the 'privileges' man page * - http://cvs.opensolaris.org * - * http://blogs.sun.com/roller/page/gbrunett?entry=privilege_enabling_set_id_programs1 */ static pset_t *zprivs_caps_minimal() { pset_t *minimal; if ((minimal = priv_str_to_set("basic", ",", NULL)) == NULL) { fprintf(stderr, "%s: couldn't get basic set!\n", __func__); exit(1); } /* create a minimal privilege set from the basic set */ (void)priv_delset(minimal, PRIV_PROC_EXEC); (void)priv_delset(minimal, PRIV_PROC_INFO); (void)priv_delset(minimal, PRIV_PROC_SESSION); (void)priv_delset(minimal, PRIV_FILE_LINK_ANY); return minimal; } /* convert zebras privileges to system capabilities */ static pset_t *zcaps2sys(zebra_capabilities_t *zcaps, int num) { pset_t *syscaps; int i, j = 0; if ((syscaps = priv_allocset()) == NULL) { fprintf(stderr, "%s: could not allocate syscaps!\n", __func__); exit(1); } priv_emptyset(syscaps); for (i = 0; i < num; i++) for (j = 0; j < cap_map[zcaps[i]].num; j++) priv_addset(syscaps, cap_map[zcaps[i]].system_caps[j]); return syscaps; } /* callback exported to users to RAISE and LOWER effective privileges * from nothing to the given permitted set and back down */ int zprivs_change_caps(zebra_privs_ops_t op) { pset_t *privset; /* should be no possibility of being called without valid caps */ assert(zprivs_state.syscaps_p); if (!zprivs_state.syscaps_p) { fprintf(stderr, "%s: Eek, missing privileged caps!", __func__); exit(1); } assert(zprivs_state.caps); if (!zprivs_state.caps) { fprintf(stderr, "%s: Eek, missing caps!", __func__); exit(1); } /* to raise: copy original permitted as our working effective set * to lower: copy regular effective set stored in zprivs_state.caps */ if (op == ZPRIVS_RAISE) privset = zprivs_state.syscaps_p; else if (op == ZPRIVS_LOWER) privset = zprivs_state.caps; else return -1; if (setppriv(PRIV_SET, PRIV_EFFECTIVE, privset) != 0) return -1; return 0; } /* Retrieve current privilege state, is it RAISED or LOWERED? */ zebra_privs_current_t zprivs_state_caps(void) { zebra_privs_current_t result; pset_t *effective; if ((effective = priv_allocset()) == NULL) { fprintf(stderr, "%s: failed to get priv_allocset! %s\n", __func__, safe_strerror(errno)); return ZPRIVS_UNKNOWN; } if (getppriv(PRIV_EFFECTIVE, effective)) { fprintf(stderr, "%s: failed to get state! %s\n", __func__, safe_strerror(errno)); result = ZPRIVS_UNKNOWN; } else { if (priv_isequalset(effective, zprivs_state.syscaps_p)) result = ZPRIVS_RAISED; else if (priv_isequalset(effective, zprivs_state.caps)) result = ZPRIVS_LOWERED; else result = ZPRIVS_UNKNOWN; } priv_freeset(effective); return result; } static void zprivs_caps_init(struct zebra_privs_t *zprivs) { pset_t *basic; pset_t *minimal; /* the specified sets */ zprivs_state.syscaps_p = zcaps2sys(zprivs->caps_p, zprivs->cap_num_p); zprivs_state.syscaps_i = zcaps2sys(zprivs->caps_i, zprivs->cap_num_i); /* nonsensical to have gotten here but not have capabilities */ if (!zprivs_state.syscaps_p) { fprintf(stderr, "%s: capabilities enabled, " "but no valid capabilities supplied\n", __func__); } /* We retain the basic set in our permitted set, as Linux has no * equivalent. The basic set on Linux hence is implicit, always * there. */ if ((basic = priv_str_to_set("basic", ",", NULL)) == NULL) { fprintf(stderr, "%s: couldn't get basic set!\n", __func__); exit(1); } /* Add the basic set to the permitted set */ priv_union(basic, zprivs_state.syscaps_p); priv_freeset(basic); /* Hey kernel, we know about privileges! * this isn't strictly required, use of setppriv should have same effect */ if (setpflags(PRIV_AWARE, 1)) { fprintf(stderr, "%s: error setting PRIV_AWARE!, %s\n", __func__, safe_strerror(errno)); exit(1); } /* need either valid or empty sets for both p and i.. */ assert(zprivs_state.syscaps_i && zprivs_state.syscaps_p); /* we have caps, we have no need to ever change back the original user * change real, effective and saved to the specified user. */ /* only change uid if we don't have the correct one */ if ((zprivs_state.zuid) && (zprivs_state.zsuid != zprivs_state.zuid)) { if (setreuid(zprivs_state.zuid, zprivs_state.zuid)) { fprintf(stderr, "%s: could not setreuid, %s\n", __func__, safe_strerror(errno)); exit(1); } } /* set the permitted set */ if (setppriv(PRIV_SET, PRIV_PERMITTED, zprivs_state.syscaps_p)) { fprintf(stderr, "%s: error setting permitted set!, %s\n", __func__, safe_strerror(errno)); exit(1); } /* set the inheritable set */ if (setppriv(PRIV_SET, PRIV_INHERITABLE, zprivs_state.syscaps_i)) { fprintf(stderr, "%s: error setting inheritable set!, %s\n", __func__, safe_strerror(errno)); exit(1); } /* we need a minimal basic set for 'effective', potentially for * inheritable too */ minimal = zprivs_caps_minimal(); /* now set the effective set with a subset of basic privileges */ if (setppriv(PRIV_SET, PRIV_EFFECTIVE, minimal)) { fprintf(stderr, "%s: error setting effective set!, %s\n", __func__, safe_strerror(errno)); exit(1); } /* we'll use the minimal set as our working-storage privset */ zprivs_state.caps = minimal; /* set methods for the caller to use */ zprivs->change = zprivs_change_caps; zprivs->current_state = zprivs_state_caps; } static void zprivs_caps_terminate(void) { assert(zprivs_state.caps); /* clear all capabilities by using working-storage privset */ setppriv(PRIV_SET, PRIV_EFFECTIVE, zprivs_state.caps); setppriv(PRIV_SET, PRIV_PERMITTED, zprivs_state.caps); setppriv(PRIV_SET, PRIV_INHERITABLE, zprivs_state.caps); /* free up private state */ if (zprivs_state.syscaps_p) priv_freeset(zprivs_state.syscaps_p); if (zprivs_state.syscaps_i) priv_freeset(zprivs_state.syscaps_i); priv_freeset(zprivs_state.caps); } #else /* !HAVE_LCAPS && ! HAVE_SOLARIS_CAPABILITIES */ #error "Neither Solaris nor Linux capabilities, dazed and confused..." #endif /* HAVE_LCAPS */ #endif /* HAVE_CAPABILITIES */ int zprivs_change_uid(zebra_privs_ops_t op) { if (zprivs_state.zsuid == zprivs_state.zuid) return 0; if (op == ZPRIVS_RAISE) return seteuid(zprivs_state.zsuid); else if (op == ZPRIVS_LOWER) return seteuid(zprivs_state.zuid); else return -1; } zebra_privs_current_t zprivs_state_uid(void) { return ((zprivs_state.zuid == geteuid()) ? ZPRIVS_LOWERED : ZPRIVS_RAISED); } int zprivs_change_null(zebra_privs_ops_t op) { return 0; } zebra_privs_current_t zprivs_state_null(void) { return zprivs_null_state; } #ifndef HAVE_GETGROUPLIST /* Solaris 11 has no getgrouplist() */ static int getgrouplist(const char *user, gid_t group, gid_t *groups, int *ngroups) { struct group *grp; size_t usridx; int pos = 0, ret; if (pos < *ngroups) groups[pos] = group; pos++; setgrent(); while ((grp = getgrent())) { if (grp->gr_gid == group) continue; for (usridx = 0; grp->gr_mem[usridx] != NULL; usridx++) if (!strcmp(grp->gr_mem[usridx], user)) { if (pos < *ngroups) groups[pos] = grp->gr_gid; pos++; break; } } endgrent(); ret = (pos <= *ngroups) ? pos : -1; *ngroups = pos; return ret; } #endif /* HAVE_GETGROUPLIST */ /* * Helper function that locates a refcounting object to use: a process-wide * object or a per-pthread object. */ static struct zebra_privs_refs_t *get_privs_refs(struct zebra_privs_t *privs) { struct zebra_privs_refs_t *temp, *refs = NULL; pthread_t tid; if (privs_per_process) refs = &(privs->process_refs); else { /* Locate - or create - the object for the current pthread. */ tid = pthread_self(); STAILQ_FOREACH(temp, &(privs->thread_refs), entry) { if (pthread_equal(temp->tid, tid)) { refs = temp; break; } } /* Need to create a new refcounting object. */ if (refs == NULL) { refs = XCALLOC(MTYPE_PRIVS, sizeof(struct zebra_privs_refs_t)); refs->tid = tid; STAILQ_INSERT_TAIL(&(privs->thread_refs), refs, entry); } } return refs; } struct zebra_privs_t *_zprivs_raise(struct zebra_privs_t *privs, const char *funcname) { int save_errno = errno; struct zebra_privs_refs_t *refs; if (!privs) return NULL; /* * Serialize 'raise' operations; particularly important for * OSes where privs are process-wide. */ frr_with_mutex(&(privs->mutex)) { /* Locate ref-counting object to use */ refs = get_privs_refs(privs); if (++(refs->refcount) == 1) { errno = 0; if (privs->change(ZPRIVS_RAISE)) { zlog_err("%s: Failed to raise privileges (%s)", funcname, safe_strerror(errno)); } errno = save_errno; refs->raised_in_funcname = funcname; } } return privs; } void _zprivs_lower(struct zebra_privs_t **privs) { int save_errno = errno; struct zebra_privs_refs_t *refs; if (!*privs) return; /* Serialize 'lower privs' operation - particularly important * when OS privs are process-wide. */ frr_with_mutex(&(*privs)->mutex) { refs = get_privs_refs(*privs); if (--(refs->refcount) == 0) { errno = 0; if ((*privs)->change(ZPRIVS_LOWER)) { zlog_err("%s: Failed to lower privileges (%s)", refs->raised_in_funcname, safe_strerror(errno)); } errno = save_errno; refs->raised_in_funcname = NULL; } } *privs = NULL; } void zprivs_preinit(struct zebra_privs_t *zprivs) { struct passwd *pwentry = NULL; struct group *grentry = NULL; if (!zprivs) { fprintf(stderr, "zprivs_init: called with NULL arg!\n"); exit(1); } pthread_mutex_init(&(zprivs->mutex), NULL); zprivs->process_refs.refcount = 0; zprivs->process_refs.raised_in_funcname = NULL; STAILQ_INIT(&zprivs->thread_refs); if (zprivs->vty_group) { /* in a "NULL" setup, this is allowed to fail too, but still * try. */ if ((grentry = getgrnam(zprivs->vty_group))) zprivs_state.vtygrp = grentry->gr_gid; else zprivs_state.vtygrp = (gid_t)-1; } /* NULL privs */ if (!(zprivs->user || zprivs->group || zprivs->cap_num_p || zprivs->cap_num_i)) { zprivs->change = zprivs_change_null; zprivs->current_state = zprivs_state_null; return; } if (zprivs->user) { if ((pwentry = getpwnam(zprivs->user)) == NULL) { /* cant use log.h here as it depends on vty */ fprintf(stderr, "privs_init: could not lookup user %s\n", zprivs->user); exit(1); } zprivs_state.zuid = pwentry->pw_uid; zprivs_state.zgid = pwentry->pw_gid; } grentry = NULL; if (zprivs->group) { if ((grentry = getgrnam(zprivs->group)) == NULL) { fprintf(stderr, "privs_init: could not lookup group %s\n", zprivs->group); exit(1); } zprivs_state.zgid = grentry->gr_gid; } } void zprivs_init(struct zebra_privs_t *zprivs) { gid_t groups[NGROUPS_MAX] = {}; int i, ngroups = 0; int found = 0; /* NULL privs */ if (!(zprivs->user || zprivs->group || zprivs->cap_num_p || zprivs->cap_num_i)) return; if (zprivs->user) { ngroups = array_size(groups); if (getgrouplist(zprivs->user, zprivs_state.zgid, groups, &ngroups) < 0) { /* cant use log.h here as it depends on vty */ fprintf(stderr, "privs_init: could not getgrouplist for user %s\n", zprivs->user); exit(1); } } if (zprivs->vty_group) /* Add the vty_group to the supplementary groups so it can be chowned to */ { if (zprivs_state.vtygrp == (gid_t)-1) { fprintf(stderr, "privs_init: could not lookup vty group %s\n", zprivs->vty_group); exit(1); } for (i = 0; i < ngroups; i++) if (groups[i] == zprivs_state.vtygrp) { found++; break; } if (!found) { fprintf(stderr, "privs_init: user(%s) is not part of vty group specified(%s)\n", zprivs->user, zprivs->vty_group); exit(1); } if (i >= ngroups && ngroups < (int)array_size(groups)) { groups[i] = zprivs_state.vtygrp; } } zprivs_state.zsuid = geteuid(); /* initial uid */ /* add groups only if we changed uid - otherwise skip */ if ((ngroups) && (zprivs_state.zsuid != zprivs_state.zuid)) { if (setgroups(ngroups, groups)) { fprintf(stderr, "privs_init: could not setgroups, %s\n", safe_strerror(errno)); exit(1); } } /* change gid only if we changed uid - otherwise skip */ if ((zprivs_state.zgid) && (zprivs_state.zsuid != zprivs_state.zuid)) { /* change group now, forever. uid we do later */ if (setregid(zprivs_state.zgid, zprivs_state.zgid)) { fprintf(stderr, "zprivs_init: could not setregid, %s\n", safe_strerror(errno)); exit(1); } } #ifdef HAVE_CAPABILITIES zprivs_caps_init(zprivs); /* * If we have initialized the system with no requested * capabilities, change will not have been set * to anything by zprivs_caps_init, As such * we should make sure that when we attempt * to raize privileges that we actually have * a do nothing function to call instead of a * crash :). */ if (!zprivs->change) zprivs->change = zprivs_change_null; #else /* !HAVE_CAPABILITIES */ /* we dont have caps. we'll need to maintain rid and saved uid * and change euid back to saved uid (who we presume has all neccessary * privileges) whenever we are asked to raise our privileges. * * This is not worth that much security wise, but all we can do. */ zprivs_state.zsuid = geteuid(); /* only change uid if we don't have the correct one */ if ((zprivs_state.zuid) && (zprivs_state.zsuid != zprivs_state.zuid)) { if (setreuid(-1, zprivs_state.zuid)) { fprintf(stderr, "privs_init (uid): could not setreuid, %s\n", safe_strerror(errno)); exit(1); } } zprivs->change = zprivs_change_uid; zprivs->current_state = zprivs_state_uid; #endif /* HAVE_CAPABILITIES */ } void zprivs_terminate(struct zebra_privs_t *zprivs) { struct zebra_privs_refs_t *refs; if (!zprivs) { fprintf(stderr, "%s: no privs struct given, terminating", __func__); exit(0); } #ifdef HAVE_CAPABILITIES if (zprivs->user || zprivs->group || zprivs->cap_num_p || zprivs->cap_num_i) zprivs_caps_terminate(); #else /* !HAVE_CAPABILITIES */ /* only change uid if we don't have the correct one */ if ((zprivs_state.zuid) && (zprivs_state.zsuid != zprivs_state.zuid)) { if (setreuid(zprivs_state.zuid, zprivs_state.zuid)) { fprintf(stderr, "privs_terminate: could not setreuid, %s", safe_strerror(errno)); exit(1); } } #endif /* HAVE_LCAPS */ while ((refs = STAILQ_FIRST(&(zprivs->thread_refs))) != NULL) { STAILQ_REMOVE_HEAD(&(zprivs->thread_refs), entry); XFREE(MTYPE_PRIVS, refs); } zprivs->change = zprivs_change_null; zprivs->current_state = zprivs_state_null; zprivs_null_state = ZPRIVS_LOWERED; return; } void zprivs_get_ids(struct zprivs_ids_t *ids) { ids->uid_priv = getuid(); (zprivs_state.zuid) ? (ids->uid_normal = zprivs_state.zuid) : (ids->uid_normal = -1); (zprivs_state.zgid) ? (ids->gid_normal = zprivs_state.zgid) : (ids->gid_normal = -1); (zprivs_state.vtygrp) ? (ids->gid_vty = zprivs_state.vtygrp) : (ids->gid_vty = -1); return; } frr-7.2.1/lib/privs.h0000644000000000000000000001124113610377563011275 00000000000000/* * Zebra privileges header. * * Copyright (C) 2003 Paul Jakma. * * This file is part of GNU Zebra. * * GNU Zebra is free software; you can redistribute it and/or modify it * under the terms of the GNU General Public License as published by the * Free Software Foundation; either version 2, or (at your option) any * later version. * * GNU Zebra 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; see the file COPYING; if not, write to the Free Software * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA */ #ifndef _ZEBRA_PRIVS_H #define _ZEBRA_PRIVS_H #include #include "lib/queue.h" #ifdef __cplusplus extern "C" { #endif /* list of zebra capabilities */ typedef enum { ZCAP_SETID, ZCAP_BIND, ZCAP_NET_ADMIN, ZCAP_SYS_ADMIN, ZCAP_NET_RAW, ZCAP_CHROOT, ZCAP_NICE, ZCAP_PTRACE, ZCAP_DAC_OVERRIDE, ZCAP_READ_SEARCH, ZCAP_FOWNER, ZCAP_MAX } zebra_capabilities_t; typedef enum { ZPRIVS_LOWERED, ZPRIVS_RAISED, ZPRIVS_UNKNOWN, } zebra_privs_current_t; typedef enum { ZPRIVS_RAISE, ZPRIVS_LOWER, } zebra_privs_ops_t; struct zebra_privs_refs_t { STAILQ_ENTRY(zebra_privs_refs_t) entry; pthread_t tid; uint32_t refcount; const char *raised_in_funcname; }; struct zebra_privs_t { zebra_capabilities_t *caps_p; /* caps required for operation */ zebra_capabilities_t *caps_i; /* caps to allow inheritance of */ int cap_num_p; /* number of caps in arrays */ int cap_num_i; /* Mutex and counter used to avoid race conditions in multi-threaded * processes. If privs status is process-wide, we need to * control changes to the privilege status among threads. * If privs changes are per-thread, we need to be able to * manage that too. */ pthread_mutex_t mutex; struct zebra_privs_refs_t process_refs; STAILQ_HEAD(thread_refs_q, zebra_privs_refs_t) thread_refs; const char *user; /* user and group to run as */ const char *group; const char *vty_group; /* group to chown vty socket to */ /* methods */ int (*change)(zebra_privs_ops_t); /* change privileges, 0 on success */ zebra_privs_current_t (*current_state)( void); /* current privilege state */ }; struct zprivs_ids_t { /* -1 is undefined */ uid_t uid_priv; /* privileged uid */ uid_t uid_normal; /* normal uid */ gid_t gid_priv; /* privileged uid */ gid_t gid_normal; /* normal uid */ gid_t gid_vty; /* vty gid */ }; /* initialise zebra privileges */ extern void zprivs_preinit(struct zebra_privs_t *zprivs); extern void zprivs_init(struct zebra_privs_t *zprivs); /* drop all and terminate privileges */ extern void zprivs_terminate(struct zebra_privs_t *); /* query for runtime uid's and gid's, eg vty needs this */ extern void zprivs_get_ids(struct zprivs_ids_t *); /* * Wrapper around zprivs, to be used as: * frr_with_privs(&privs) { * ... code ... * if (error) * break; -- break can be used to get out of the block * ... code ... * } * * The argument to frr_with_privs() can be NULL to leave privileges as-is * (mostly useful for conditional privilege-raising, i.e.:) * frr_with_privs(cond ? &privs : NULL) {} * * NB: The code block is always executed, regardless of whether privileges * could be raised or not, or whether NULL was given or not. This is fully * intentional; the user may have configured some RBAC or similar that we * are not aware of, but that allows our code to proceed without privileges. * * The point of this wrapper is to prevent accidental bugs where privileges * are elevated but then not dropped. This can happen when, for example, a * "return", "goto" or "break" in the middle of the elevated-privilege code * skips past the privilege dropping call. * * The macro below uses variable cleanup to drop privileges as soon as the * code block is left in any way (and thus the _privs variable goes out of * scope.) _once is just a trick to run the loop exactly once. */ extern struct zebra_privs_t *_zprivs_raise(struct zebra_privs_t *privs, const char *funcname); extern void _zprivs_lower(struct zebra_privs_t **privs); #define frr_with_privs(privs) \ for (struct zebra_privs_t *_once = NULL, \ *_privs __attribute__( \ (unused, cleanup(_zprivs_lower))) = \ _zprivs_raise(privs, __func__); \ _once == NULL; _once = (void *)1) #ifdef __cplusplus } #endif #endif /* _ZEBRA_PRIVS_H */ frr-7.2.1/lib/ptm_lib.c0000644000000000000000000002616313610377563011564 00000000000000/* PTM Library * Copyright (C) 2015 Cumulus Networks, Inc. * * This file is part of Quagga. * * Quagga is free software; you can redistribute it and/or modify it * under the terms of the GNU General Public License as published by the * Free Software Foundation; either version 2, or (at your option) any * later version. * * Quagga 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; see the file COPYING; if not, write to the Free Software * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA */ #ifdef HAVE_CONFIG_H #include "config.h" #endif #include #include #include #include #include #include #include #include #include #include "csv.h" #include "ptm_lib.h" #define DEBUG_E 0 #define DEBUG_V 0 #define ERRLOG(fmt, ...) \ do { \ if (DEBUG_E) \ fprintf(stderr, "%s:%d:%s(): " fmt, __FILE__, \ __LINE__, __func__, ##__VA_ARGS__); \ } while (0) #define DLOG(fmt, ...) \ do { \ if (DEBUG_V) \ fprintf(stderr, "%s:%d:%s(): " fmt, __FILE__, \ __LINE__, __func__, ##__VA_ARGS__); \ } while (0) typedef struct ptm_lib_msg_ctxt_s { int cmd_id; csv_t *csv; ptmlib_msg_type type; } ptm_lib_msg_ctxt_t; static csv_record_t *_ptm_lib_encode_header(csv_t *csv, csv_record_t *rec, int msglen, int version, int type, int cmd_id, char *client_name) { char msglen_buf[16], vers_buf[16], type_buf[16], cmdid_buf[16]; char client_buf[32]; csv_record_t *rec1; sprintf(msglen_buf, "%4d", msglen); sprintf(vers_buf, "%4d", version); sprintf(type_buf, "%4d", type); sprintf(cmdid_buf, "%4d", cmd_id); snprintf(client_buf, 17, "%16.16s", client_name); if (rec) { rec1 = csv_encode_record(csv, rec, 5, msglen_buf, vers_buf, type_buf, cmdid_buf, client_buf); } else { rec1 = csv_encode(csv, 5, msglen_buf, vers_buf, type_buf, cmdid_buf, client_buf); } return (rec1); } static int _ptm_lib_decode_header(csv_t *csv, int *msglen, int *version, int *type, int *cmd_id, char *client_name) { char *hdr; csv_record_t *rec; csv_field_t *fld; int i, j; csv_decode(csv, NULL); rec = csv_record_iter(csv); if (rec == NULL) { DLOG("malformed CSV\n"); return (-1); } hdr = csv_field_iter(rec, &fld); if (hdr == NULL) { DLOG("malformed CSV\n"); return (-1); } *msglen = atoi(hdr); hdr = csv_field_iter_next(&fld); if (hdr == NULL) { DLOG("malformed CSV\n"); return (-1); } *version = atoi(hdr); hdr = csv_field_iter_next(&fld); if (hdr == NULL) { DLOG("malformed CSV\n"); return (-1); } *type = atoi(hdr); hdr = csv_field_iter_next(&fld); if (hdr == NULL) { DLOG("malformed CSV\n"); return (-1); } *cmd_id = atoi(hdr); hdr = csv_field_iter_next(&fld); if (hdr == NULL) { DLOG("malformed CSV\n"); return (-1); } /* remove leading spaces */ for (i = j = 0; i < csv_field_len(fld); i++) { if (!isspace((unsigned char)hdr[i])) { client_name[j] = hdr[i]; j++; } } client_name[j] = '\0'; return (0); } int ptm_lib_append_msg(ptm_lib_handle_t *hdl, void *ctxt, const char *key, const char *val) { ptm_lib_msg_ctxt_t *p_ctxt = ctxt; csv_t *csv; csv_record_t *mh_rec, *rec; if (!p_ctxt) { ERRLOG("%s: no context \n", __FUNCTION__); return -1; } csv = p_ctxt->csv; mh_rec = csv_record_iter(csv); rec = csv_record_iter_next(mh_rec); /* append to the hdr record */ rec = csv_append_record(csv, rec, 1, key); if (!rec) { ERRLOG("%s: Could not append key \n", __FUNCTION__); return -1; } rec = csv_record_iter_next(rec); /* append to the data record */ rec = csv_append_record(csv, rec, 1, val); if (!rec) { ERRLOG("%s: Could not append val \n", __FUNCTION__); return -1; } /* update the msg hdr */ _ptm_lib_encode_header(csv, mh_rec, (csvlen(csv) - PTMLIB_MSG_HDR_LEN), PTMLIB_MSG_VERSION, p_ctxt->type, p_ctxt->cmd_id, hdl->client_name); return 0; } int ptm_lib_init_msg(ptm_lib_handle_t *hdl, int cmd_id, int type, void *in_ctxt, void **out_ctxt) { ptm_lib_msg_ctxt_t *p_ctxt; ptm_lib_msg_ctxt_t *p_in_ctxt = in_ctxt; csv_t *csv; csv_record_t *rec, *d_rec; /* Initialize csv for using discrete record buffers */ csv = csv_init(NULL, NULL, PTMLIB_MSG_SZ); if (!csv) { ERRLOG("%s: Could not allocate csv \n", __FUNCTION__); return -1; } rec = _ptm_lib_encode_header(csv, NULL, 0, PTMLIB_MSG_VERSION, type, cmd_id, hdl->client_name); if (!rec) { ERRLOG("%s: Could not allocate record \n", __FUNCTION__); csv_clean(csv); csv_free(csv); return -1; } p_ctxt = calloc(1, sizeof(*p_ctxt)); if (!p_ctxt) { ERRLOG("%s: Could not allocate context \n", __FUNCTION__); csv_clean(csv); csv_free(csv); return -1; } p_ctxt->csv = csv; p_ctxt->cmd_id = cmd_id; p_ctxt->type = type; *(ptm_lib_msg_ctxt_t **)out_ctxt = p_ctxt; /* caller supplied a context to initialize with? */ if (p_in_ctxt) { /* insert the hdr rec */ rec = csv_record_iter(p_in_ctxt->csv); csv_clone_record(p_in_ctxt->csv, rec, &d_rec); csv_insert_record(csv, d_rec); /* insert the data rec */ rec = csv_record_iter_next(rec); csv_clone_record(p_in_ctxt->csv, rec, &d_rec); csv_insert_record(csv, d_rec); } return 0; } int ptm_lib_cleanup_msg(ptm_lib_handle_t *hdl, void *ctxt) { ptm_lib_msg_ctxt_t *p_ctxt = ctxt; csv_t *csv; if (!p_ctxt) { ERRLOG("%s: no context \n", __FUNCTION__); return -1; } csv = p_ctxt->csv; csv_clean(csv); csv_free(csv); free(p_ctxt); return 0; } int ptm_lib_complete_msg(ptm_lib_handle_t *hdl, void *ctxt, char *buf, int *len) { ptm_lib_msg_ctxt_t *p_ctxt = ctxt; csv_t *csv; csv_record_t *rec; if (!p_ctxt) { ERRLOG("%s: no context \n", __FUNCTION__); return -1; } csv = p_ctxt->csv; rec = csv_record_iter(csv); _ptm_lib_encode_header(csv, rec, (csvlen(csv) - PTMLIB_MSG_HDR_LEN), PTMLIB_MSG_VERSION, p_ctxt->type, p_ctxt->cmd_id, hdl->client_name); /* parse csv contents into string */ if (buf && len) { if (csv_serialize(csv, buf, *len)) { ERRLOG("%s: cannot serialize\n", __FUNCTION__); return -1; } *len = csvlen(csv); } csv_clean(csv); csv_free(csv); free(p_ctxt); return 0; } int ptm_lib_find_key_in_msg(void *ctxt, const char *key, char *val) { ptm_lib_msg_ctxt_t *p_ctxt = ctxt; csv_t *csv = p_ctxt->csv; csv_record_t *hrec, *drec; csv_field_t *hfld, *dfld; char *hstr, *dstr; /** * skip over ptm hdr if present * The next hdr is the keys (column name) * The next hdr is the data */ if (csv_num_records(csv) > 2) { hrec = csv_record_iter(csv); hrec = csv_record_iter_next(hrec); } else { hrec = csv_record_iter(csv); } drec = csv_record_iter_next(hrec); val[0] = '\0'; for (hstr = csv_field_iter(hrec, &hfld), dstr = csv_field_iter(drec, &dfld); (hstr && dstr); hstr = csv_field_iter_next(&hfld), dstr = csv_field_iter_next(&dfld)) { if (!strncmp(hstr, key, csv_field_len(hfld))) { snprintf(val, csv_field_len(dfld) + 1, "%s", dstr); return 0; } } return -1; } static int _ptm_lib_read_ptm_socket(int fd, char *buf, int len) { int retries = 0, rc; int bytes_read = 0; while (bytes_read != len) { rc = recv(fd, (void *)(buf + bytes_read), (len - bytes_read), MSG_DONTWAIT); if (rc <= 0) { if (errno && (errno != EAGAIN) && (errno != EWOULDBLOCK)) { ERRLOG("fatal recv error(%s), closing connection, rc %d\n", strerror(errno), rc); return (rc); } else { if (retries++ < 2) { usleep(10000); continue; } DLOG("max retries - recv error(%d - %s) bytes read %d (%d)\n", errno, strerror(errno), bytes_read, len); return (bytes_read); } break; } else { bytes_read += rc; } } return bytes_read; } int ptm_lib_process_msg(ptm_lib_handle_t *hdl, int fd, char *inbuf, int inlen, void *arg) { int rc, len; char client_name[32]; int cmd_id = 0, type = 0, ver = 0, msglen = 0; csv_t *csv; ptm_lib_msg_ctxt_t *p_ctxt = NULL; len = _ptm_lib_read_ptm_socket(fd, inbuf, PTMLIB_MSG_HDR_LEN); if (len <= 0) return (len); csv = csv_init(NULL, inbuf, PTMLIB_MSG_HDR_LEN); if (!csv) { DLOG("Cannot allocate csv for hdr\n"); return (-1); } rc = _ptm_lib_decode_header(csv, &msglen, &ver, &type, &cmd_id, client_name); csv_clean(csv); csv_free(csv); if (rc < 0) { /* could not decode the CSV - maybe its legacy cmd? * get the entire cmd from the socket and see if we can process * it */ if (len == PTMLIB_MSG_HDR_LEN) { len += _ptm_lib_read_ptm_socket( fd, (inbuf + PTMLIB_MSG_HDR_LEN), inlen - PTMLIB_MSG_HDR_LEN); if (len <= 0) return (len); } inbuf[len] = '\0'; /* we only support the get-status cmd */ if (strcmp(inbuf, PTMLIB_CMD_GET_STATUS)) { DLOG("unsupported legacy cmd %s\n", inbuf); return (-1); } /* internally create a csv-style cmd */ ptm_lib_init_msg(hdl, 0, PTMLIB_MSG_TYPE_CMD, NULL, (void *)&p_ctxt); if (!p_ctxt) { DLOG("couldnt allocate context\n"); return (-1); } ptm_lib_append_msg(hdl, p_ctxt, "cmd", PTMLIB_CMD_GET_STATUS); } else { if (msglen > inlen) { DLOG("msglen [%d] > inlen [%d]\n", msglen, inlen); return -1; } /* read the rest of the msg */ len = _ptm_lib_read_ptm_socket(fd, inbuf, msglen); if (len <= 0) { return (len); } inbuf[len] = '\0'; csv = csv_init(NULL, NULL, PTMLIB_MSG_SZ); if (!csv) { ERRLOG("Cannot allocate csv for msg\n"); return -1; } csv_decode(csv, inbuf); p_ctxt = calloc(1, sizeof(*p_ctxt)); if (!p_ctxt) { ERRLOG("%s: Could not allocate context \n", __FUNCTION__); csv_clean(csv); csv_free(csv); return -1; } p_ctxt->csv = csv; p_ctxt->cmd_id = cmd_id; p_ctxt->type = type; } switch (p_ctxt->type) { case PTMLIB_MSG_TYPE_NOTIFICATION: if (hdl->notify_cb) hdl->notify_cb(arg, p_ctxt); break; case PTMLIB_MSG_TYPE_CMD: if (hdl->cmd_cb) hdl->cmd_cb(arg, p_ctxt); break; case PTMLIB_MSG_TYPE_RESPONSE: if (hdl->response_cb) hdl->response_cb(arg, p_ctxt); break; default: return -1; } csv_clean(p_ctxt->csv); csv_free(p_ctxt->csv); free(p_ctxt); return len; } ptm_lib_handle_t *ptm_lib_register(char *client_name, ptm_cmd_cb cmd_cb, ptm_notify_cb notify_cb, ptm_response_cb response_cb) { ptm_lib_handle_t *hdl; hdl = calloc(1, sizeof(*hdl)); if (hdl) { strncpy(hdl->client_name, client_name, PTMLIB_MAXNAMELEN - 1); hdl->cmd_cb = cmd_cb; hdl->notify_cb = notify_cb; hdl->response_cb = response_cb; } return hdl; } void ptm_lib_deregister(ptm_lib_handle_t *hdl) { if (hdl) { memset(hdl, 0x00, sizeof(*hdl)); free(hdl); } } frr-7.2.1/lib/ptm_lib.h0000644000000000000000000000460413610377563011565 00000000000000/* PTM Library * Copyright (C) 2015 Cumulus Networks, Inc. * * This file is part of Quagga. * * Quagga is free software; you can redistribute it and/or modify it * under the terms of the GNU General Public License as published by the * Free Software Foundation; either version 2, or (at your option) any * later version. * * Quagga 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; see the file COPYING; if not, write to the Free Software * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA */ #ifndef __PTM_LIB_H__ #define __PTM_LIB_H__ #ifdef __cplusplus extern "C" { #endif #define PTMLIB_MSG_SZ 1024 #define PTMLIB_MSG_HDR_LEN 37 #define PTMLIB_MSG_VERSION 2 #define PTMLIB_MAXNAMELEN 32 #define PTMLIB_CMD_GET_STATUS "get-status" #define PTMLIB_CMD_GET_BFD_CLIENT "get-bfd-client" #define PTMLIB_CMD_START_BFD_SESS "start-bfd-sess" #define PTMLIB_CMD_STOP_BFD_SESS "stop-bfd-sess" typedef enum { PTMLIB_MSG_TYPE_NOTIFICATION = 1, PTMLIB_MSG_TYPE_CMD, PTMLIB_MSG_TYPE_RESPONSE, } ptmlib_msg_type; typedef enum { MODULE_BFD = 0, MODULE_LLDP, MODULE_MAX, } ptmlib_mod_type; typedef int (*ptm_cmd_cb)(void *data, void *arg); typedef int (*ptm_notify_cb)(void *data, void *arg); typedef int (*ptm_response_cb)(void *data, void *arg); typedef int (*ptm_log_cb)(void *data, void *arg, ...); typedef struct ptm_lib_handle_s { char client_name[PTMLIB_MAXNAMELEN]; ptm_cmd_cb cmd_cb; ptm_notify_cb notify_cb; ptm_response_cb response_cb; } ptm_lib_handle_t; /* Prototypes */ int ptm_lib_process_msg(ptm_lib_handle_t *, int, char *, int, void *); ptm_lib_handle_t *ptm_lib_register(char *, ptm_cmd_cb, ptm_notify_cb, ptm_response_cb); void ptm_lib_deregister(ptm_lib_handle_t *); int ptm_lib_find_key_in_msg(void *, const char *, char *); int ptm_lib_init_msg(ptm_lib_handle_t *, int, int, void *, void **); int ptm_lib_append_msg(ptm_lib_handle_t *, void *, const char *, const char *); int ptm_lib_complete_msg(ptm_lib_handle_t *, void *, char *, int *); int ptm_lib_cleanup_msg(ptm_lib_handle_t *, void *); #ifdef __cplusplus } #endif #endif frr-7.2.1/lib/pullwr.c0000644000000000000000000001545713610377563011467 00000000000000/* * Pull-driven write event handler * Copyright (C) 2019 David Lamparter * * This program is free software; you can redistribute it and/or modify it * under the terms of the 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; see the file COPYING; if not, write to the Free Software * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA */ #include "zebra.h" #include "pullwr.h" #include "memory.h" #include "monotime.h" /* defaults */ #define PULLWR_THRESH 16384 /* size at which we start to call write() */ #define PULLWR_MAXSPIN 2500 /* max µs to spend grabbing more data */ struct pullwr { int fd; struct thread_master *tm; /* writer == NULL <=> we're idle */ struct thread *writer; void *arg; void (*fill)(void *, struct pullwr *); void (*err)(void *, struct pullwr *, bool); /* ring buffer (although it's "un-ringed" on resizing, it WILL wrap * around if data is trickling in while keeping it at a constant size) */ size_t bufsz, valid, pos; uint64_t total_written; char *buffer; size_t thresh; /* PULLWR_THRESH */ int64_t maxspin; /* PULLWR_MAXSPIN */ }; DEFINE_MTYPE_STATIC(LIB, PULLWR_HEAD, "pull-driven write controller") DEFINE_MTYPE_STATIC(LIB, PULLWR_BUF, "pull-driven write buffer") static int pullwr_run(struct thread *t); struct pullwr *_pullwr_new(struct thread_master *tm, int fd, void *arg, void (*fill)(void *, struct pullwr *), void (*err)(void *, struct pullwr *, bool)) { struct pullwr *pullwr; pullwr = XCALLOC(MTYPE_PULLWR_HEAD, sizeof(*pullwr)); pullwr->fd = fd; pullwr->tm = tm; pullwr->arg = arg; pullwr->fill = fill; pullwr->err = err; pullwr->thresh = PULLWR_THRESH; pullwr->maxspin = PULLWR_MAXSPIN; return pullwr; } void pullwr_del(struct pullwr *pullwr) { THREAD_OFF(pullwr->writer); XFREE(MTYPE_PULLWR_BUF, pullwr->buffer); XFREE(MTYPE_PULLWR_HEAD, pullwr); } void pullwr_cfg(struct pullwr *pullwr, int64_t max_spin_usec, size_t write_threshold) { pullwr->maxspin = max_spin_usec ?: PULLWR_MAXSPIN; pullwr->thresh = write_threshold ?: PULLWR_THRESH; } void pullwr_bump(struct pullwr *pullwr) { if (pullwr->writer) return; thread_add_timer(pullwr->tm, pullwr_run, pullwr, 0, &pullwr->writer); } static size_t pullwr_iov(struct pullwr *pullwr, struct iovec *iov) { size_t len1; if (pullwr->valid == 0) return 0; if (pullwr->pos + pullwr->valid <= pullwr->bufsz) { iov[0].iov_base = pullwr->buffer + pullwr->pos; iov[0].iov_len = pullwr->valid; return 1; } len1 = pullwr->bufsz - pullwr->pos; iov[0].iov_base = pullwr->buffer + pullwr->pos; iov[0].iov_len = len1; iov[1].iov_base = pullwr->buffer; iov[1].iov_len = pullwr->valid - len1; return 2; } static void pullwr_resize(struct pullwr *pullwr, size_t need) { struct iovec iov[2]; size_t niov, newsize; char *newbuf; /* the buffer is maintained at pullwr->thresh * 2 since we'll be * trying to fill it as long as it's anywhere below pullwr->thresh. * That means we frequently end up a little short of it and then write * something that goes over the threshold. So, just use double. */ if (need) { /* resize up */ if (pullwr->bufsz - pullwr->valid >= need) return; newsize = MAX((pullwr->valid + need) * 2, pullwr->thresh * 2); newbuf = XMALLOC(MTYPE_PULLWR_BUF, newsize); } else if (!pullwr->valid) { /* resize down, buffer empty */ newsize = 0; newbuf = NULL; } else { /* resize down */ if (pullwr->bufsz - pullwr->valid < pullwr->thresh) return; newsize = MAX(pullwr->valid, pullwr->thresh * 2); newbuf = XMALLOC(MTYPE_PULLWR_BUF, newsize); } niov = pullwr_iov(pullwr, iov); if (niov >= 1) { memcpy(newbuf, iov[0].iov_base, iov[0].iov_len); if (niov >= 2) memcpy(newbuf + iov[0].iov_len, iov[1].iov_base, iov[1].iov_len); } XFREE(MTYPE_PULLWR_BUF, pullwr->buffer); pullwr->buffer = newbuf; pullwr->bufsz = newsize; pullwr->pos = 0; } void pullwr_write(struct pullwr *pullwr, const void *data, size_t len) { pullwr_resize(pullwr, len); if (pullwr->pos + pullwr->valid > pullwr->bufsz) { size_t pos; pos = (pullwr->pos + pullwr->valid) % pullwr->bufsz; memcpy(pullwr->buffer + pos, data, len); } else { size_t max1, len1; max1 = pullwr->bufsz - (pullwr->pos + pullwr->valid); max1 = MIN(max1, len); memcpy(pullwr->buffer + pullwr->pos + pullwr->valid, data, max1); len1 = len - max1; if (len1) memcpy(pullwr->buffer, (char *)data + max1, len1); } pullwr->valid += len; pullwr_bump(pullwr); } static int pullwr_run(struct thread *t) { struct pullwr *pullwr = THREAD_ARG(t); struct iovec iov[2]; size_t niov, lastvalid; ssize_t nwr; struct timeval t0; bool maxspun = false; monotime(&t0); do { lastvalid = pullwr->valid - 1; while (pullwr->valid < pullwr->thresh && pullwr->valid != lastvalid && !maxspun) { lastvalid = pullwr->valid; pullwr->fill(pullwr->arg, pullwr); /* check after doing at least one fill() call so we * don't spin without making progress on slow boxes */ if (!maxspun && monotime_since(&t0, NULL) >= pullwr->maxspin) maxspun = true; } if (pullwr->valid == 0) { /* we made a fill() call above that didn't feed any * data in, and we have nothing more queued, so we go * into idle, i.e. no calling thread_add_write() */ pullwr_resize(pullwr, 0); return 0; } niov = pullwr_iov(pullwr, iov); assert(niov); nwr = writev(pullwr->fd, iov, niov); if (nwr < 0) { if (errno == EAGAIN || errno == EWOULDBLOCK) break; pullwr->err(pullwr->arg, pullwr, false); return 0; } if (nwr == 0) { pullwr->err(pullwr->arg, pullwr, true); return 0; } pullwr->total_written += nwr; pullwr->valid -= nwr; pullwr->pos += nwr; pullwr->pos %= pullwr->bufsz; } while (pullwr->valid == 0 && !maxspun); /* pullwr->valid != 0 implies we did an incomplete write, i.e. socket * is full and we go wait until it's available for writing again. */ thread_add_write(pullwr->tm, pullwr_run, pullwr, pullwr->fd, &pullwr->writer); /* if we hit the time limit, just keep the buffer, we'll probably need * it anyway & another run is already coming up. */ if (!maxspun) pullwr_resize(pullwr, 0); return 0; } void pullwr_stats(struct pullwr *pullwr, uint64_t *total_written, size_t *pending, size_t *kernel_pending) { int tmp; *total_written = pullwr->total_written; *pending = pullwr->valid; if (ioctl(pullwr->fd, TIOCOUTQ, &tmp) != 0) tmp = 0; *kernel_pending = tmp; } frr-7.2.1/lib/pullwr.h0000644000000000000000000001125613610377563011465 00000000000000/* * Pull-driven write event handler * Copyright (C) 2019 David Lamparter * * This program is free software; you can redistribute it and/or modify it * under the terms of the 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; see the file COPYING; if not, write to the Free Software * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA */ #ifndef _WRITEPOLL_H #define _WRITEPOLL_H #include #include #include "thread.h" #include "stream.h" struct pullwr; /* This is a "pull-driven" write event handler. Instead of having some buffer * or being driven by the availability of data, it triggers on the space being * available on the socket for data to be written on and then calls fill() to * get data to be sent. * * pullwr_* maintains an "idle" vs. "active" state, going into idle when a * fill() call completes without feeing more data into it. The overall * semantics are: * - to put data out, call pullwr_write(). This is possible from both inside * fill() callbacks or anywhere else. Doing so puts the pullwr into * active state. * - in active state, the fill() callback will be called and should feed more * data in. It should NOT loop to push out more than one "unit" of data; * the pullwr code handles this by calling fill() until it has enough data. * - if there's nothing more to be sent, fill() returns without doing anything * and pullwr goes into idle state after flushing all buffered data out. * - when new data becomes available, pullwr_bump() should be called to put * the pullwr back into active mode so it will collect data from fill(), * or you can directly call pullwr_write(). * - only calling pullwr_write() from within fill() is the cleanest way of * doing things. * * When the err() callback is called, the pullwr should be considered unusable * and released with pullwr_del(). This can be done from inside the callback, * the pullwr code holds no more references on it when calling err(). */ extern struct pullwr *_pullwr_new(struct thread_master *tm, int fd, void *arg, void (*fill)(void *, struct pullwr *), void (*err)(void *, struct pullwr *, bool eof)); extern void pullwr_del(struct pullwr *pullwr); /* type-checking wrapper. makes sure fill() and err() take a first argument * whose type is identical to the type of arg. * => use "void fill(struct mystruct *arg, ...)" - no "void *arg" */ #define pullwr_new(tm, fd, arg, fill, err) ({ \ void (*fill_typechk)(typeof(arg), struct pullwr *) = fill; \ void (*err_typechk)(typeof(arg), struct pullwr *, bool) = err; \ _pullwr_new(tm, fd, arg, (void *)fill_typechk, (void *)err_typechk); \ }) /* max_spin_usec is the time after which the pullwr event handler will stop * trying to get more data from fill() and yield control back to the * thread_master. It does reschedule itself to continue later; this is * only to make sure we don't freeze the entire process if we're piping a * lot of data to a local endpoint that reads quickly (i.e. no backpressure) * * default: 2500 (2.5 ms) * * write_threshold is the amount of data buffered from fill() calls at which * the pullwr code starts calling write(). But this is not a "limit". * pullwr will keep poking fill() for more data until * (a) max_spin_usec is reached; fill() will be called again later after * returning to the thread_master to give other events a chance to run * (b) fill() returns without pushing any data onto the pullwr with * pullwr_write(), so fill() will NOT be called again until a call to * pullwr_bump() or pullwr_write() comes in. * * default: 16384 (16 kB) * * passing 0 for either value (or not calling it at all) uses the default. */ extern void pullwr_cfg(struct pullwr *pullwr, int64_t max_spin_usec, size_t write_threshold); extern void pullwr_bump(struct pullwr *pullwr); extern void pullwr_write(struct pullwr *pullwr, const void *data, size_t len); static inline void pullwr_write_stream(struct pullwr *pullwr, struct stream *s) { pullwr_write(pullwr, s->data, stream_get_endp(s)); } extern void pullwr_stats(struct pullwr *pullwr, uint64_t *total_written, size_t *pending, size_t *kernel_pending); #endif /* _WRITEPOLL_H */ frr-7.2.1/lib/pw.h0000644000000000000000000000277313610377563010572 00000000000000/* Pseudowire definitions * Copyright (C) 2016 Volta Networks, Inc. * * This program is free software; you can redistribute it and/or modify * it under the terms of the 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; see the file COPYING; if not, write to the * Free Software Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, * MA 02110-1301 USA */ #ifndef _FRR_PW_H #define _FRR_PW_H #ifdef __cplusplus extern "C" { #endif /* L2VPN name length. */ #define L2VPN_NAME_LEN 32 /* Pseudowire type - LDP and BGP use the same values. */ #define PW_TYPE_ETHERNET_TAGGED 0x0004 /* RFC 4446 */ #define PW_TYPE_ETHERNET 0x0005 /* RFC 4446 */ #define PW_TYPE_WILDCARD 0x7FFF /* RFC 4863, RFC 6668 */ /* Pseudowire flags. */ #define F_PSEUDOWIRE_CWORD 0x01 /* Pseudowire status. */ #define PW_STATUS_DOWN 0 #define PW_STATUS_UP 1 /* * Protocol-specific information about the pseudowire. */ union pw_protocol_fields { struct { struct in_addr lsr_id; uint32_t pwid; char vpn_name[L2VPN_NAME_LEN]; } ldp; }; #ifdef __cplusplus } #endif #endif /* _FRR_PW_H */ frr-7.2.1/lib/qobj.c0000644000000000000000000000557713610377563011077 00000000000000/* * Copyright (c) 2015-16 David Lamparter, for NetDEF, Inc. * * This file is part of Quagga * * Quagga is free software; you can redistribute it and/or modify it * under the terms of the GNU General Public License as published by the * Free Software Foundation; either version 2, or (at your option) any * later version. * * Quagga 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; see the file COPYING; if not, write to the Free Software * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA */ #include #include "thread.h" #include "memory.h" #include "hash.h" #include "log.h" #include "qobj.h" #include "jhash.h" static uint32_t qobj_hash(const struct qobj_node *node) { return (uint32_t)node->nid; } static int qobj_cmp(const struct qobj_node *na, const struct qobj_node *nb) { if (na->nid < nb->nid) return -1; if (na->nid > nb->nid) return 1; return 0; } DECLARE_HASH(qobj_nodes, struct qobj_node, nodehash, qobj_cmp, qobj_hash) static pthread_rwlock_t nodes_lock; static struct qobj_nodes_head nodes = { }; void qobj_reg(struct qobj_node *node, struct qobj_nodetype *type) { node->type = type; pthread_rwlock_wrlock(&nodes_lock); do { node->nid = (uint64_t)random(); node->nid ^= (uint64_t)random() << 32; } while (!node->nid || qobj_nodes_find(&nodes, node)); qobj_nodes_add(&nodes, node); pthread_rwlock_unlock(&nodes_lock); } void qobj_unreg(struct qobj_node *node) { pthread_rwlock_wrlock(&nodes_lock); qobj_nodes_del(&nodes, node); pthread_rwlock_unlock(&nodes_lock); } struct qobj_node *qobj_get(uint64_t id) { struct qobj_node dummy = {.nid = id}, *rv; pthread_rwlock_rdlock(&nodes_lock); rv = qobj_nodes_find(&nodes, &dummy); pthread_rwlock_unlock(&nodes_lock); return rv; } void *qobj_get_typed(uint64_t id, struct qobj_nodetype *type) { struct qobj_node dummy = {.nid = id}; struct qobj_node *node; void *rv; pthread_rwlock_rdlock(&nodes_lock); node = qobj_nodes_find(&nodes, &dummy); /* note: we explicitly hold the lock until after we have checked the * type. * if the caller holds a lock that for example prevents the deletion of * route-maps, we can still race against a delete of something that * isn't * a route-map. */ if (!node || node->type != type) rv = NULL; else rv = (char *)node - node->type->node_member_offset; pthread_rwlock_unlock(&nodes_lock); return rv; } void qobj_init(void) { pthread_rwlock_init(&nodes_lock, NULL); qobj_nodes_init(&nodes); } void qobj_finish(void) { struct qobj_node *node; while ((node = qobj_nodes_pop(&nodes))) qobj_nodes_del(&nodes, node); pthread_rwlock_destroy(&nodes_lock); } frr-7.2.1/lib/qobj.h0000644000000000000000000001253213610377563011071 00000000000000/* * Copyright (c) 2015-16 David Lamparter, for NetDEF, Inc. * * Permission to use, copy, modify, and distribute this software for any * purpose with or without fee is hereby granted, provided that the above * copyright notice and this permission notice appear in all copies. * * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. */ #ifndef _QOBJ_H #define _QOBJ_H #include #include #include #include "typesafe.h" #ifdef __cplusplus extern "C" { #endif /* reserve a specific amount of bytes for a struct, which can grow up to * that size (or be dummy'd out if not needed) * * note the padding's array size will be an error if it gets negative or zero; * this is intentional to prevent the struct from growing beyond the allocated * space. */ #ifndef __cplusplus #define RESERVED_SPACE_STRUCT(name, fieldname, size) \ struct { \ struct name fieldname; \ char padding##fieldname[size - sizeof(struct name)]; \ }; #else #define RESERVED_SPACE_STRUCT(name, fieldname, size) \ struct name fieldname; \ char padding##fieldname[size - sizeof(struct name)]; #endif /* don't need struct definitions for these here. code actually using * these needs to define the struct *before* including this header. * HAVE_QOBJ_xxx should be defined to +1 in that case, like this: * * #if defined(HAVE_QOBJ_NODETYPE_CLI) && HAVE_QOBJ_NODETYPE_CLI < 0 * #error include files are in wrong order * #else * #define HAVE_QOBJ_NODETYPE_CLI 1 * struct qobj_nodetype_cli { ... } * #endif */ #ifndef HAVE_QOBJ_NODETYPE_CLI #define HAVE_QOBJ_NODETYPE_CLI -1 struct qobj_nodetype_cli { int dummy; }; #endif #ifndef HAVE_QOBJ_NODETYPE_CAPNP #define HAVE_QOBJ_NODETYPE_CAPNP -1 struct qobj_nodetype_capnp { int dummy; }; #endif #include "typesafe.h" /* each different kind of object will have a global variable of this type, * which can be used by various other pieces to store type-related bits. * type equality can be tested as pointer equality. (cf. QOBJ_GET_TYPESAFE) */ struct qobj_nodetype { ptrdiff_t node_member_offset; RESERVED_SPACE_STRUCT(qobj_nodetype_cli, cli, 256) RESERVED_SPACE_STRUCT(qobj_nodetype_capnp, capnp, 256) }; PREDECL_HASH(qobj_nodes) /* anchor to be embedded somewhere in the object's struct */ struct qobj_node { uint64_t nid; struct qobj_nodes_item nodehash; struct qobj_nodetype *type; }; #define QOBJ_FIELDS struct qobj_node qobj_node; /* call these at the end of any _create function (QOBJ_REG) * and beginning of any _destroy function (QOBJ_UNREG) */ #define QOBJ_REG(n, structname) qobj_reg(&n->qobj_node, &qobj_t_##structname) #define QOBJ_UNREG(n) qobj_unreg(&n->qobj_node) /* internals - should not be directly used without a good reason * * note: qobj_get is essentially never safe to use in MT context because * the object could be deleted by another thread -- and worse, it could be * of the "wrong" type and deleted. * * with qobj_get_typed, the type check is done under lock, which means that * it can be used as long as another lock prevents the deletion of objects * of the expected type. * * in the long this may need another touch, e.g. built-in per-object locking. */ void qobj_reg(struct qobj_node *node, struct qobj_nodetype *type); void qobj_unreg(struct qobj_node *node); struct qobj_node *qobj_get(uint64_t id); void *qobj_get_typed(uint64_t id, struct qobj_nodetype *type); /* type declarations */ #define DECLARE_QOBJ_TYPE(structname) \ extern struct qobj_nodetype qobj_t_##structname; #define DEFINE_QOBJ_TYPE(structname) \ struct qobj_nodetype qobj_t_##structname = { \ .node_member_offset = \ (ptrdiff_t)offsetof(struct structname, qobj_node)}; #define DEFINE_QOBJ_TYPE_INIT(structname, ...) \ struct qobj_nodetype qobj_t_##structname = { \ .node_member_offset = \ (ptrdiff_t)offsetof(struct structname, qobj_node), \ __VA_ARGS__}; /* ID dereference with typecheck. * will return NULL if id not found or wrong type. */ #define QOBJ_GET_TYPESAFE(id, structname) \ ((struct structname *)qobj_get_typed((id), &qobj_t_##structname)) #define QOBJ_ID(ptr) ((ptr)->qobj_node.nid) #define QOBJ_ID_0SAFE(ptr) \ ({ \ typeof(ptr) _ptr = (ptr); \ _ptr ? _ptr->qobj_node.nid : 0ULL; \ }) void qobj_init(void); void qobj_finish(void); #ifdef __cplusplus } #endif #endif /* _QOBJ_H */ frr-7.2.1/lib/queue.h0000644000000000000000000001110013610377563011250 00000000000000/* * lists and queues implementations * * This program is free software; you can redistribute it and/or modify it * under the terms of the 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; see the file COPYING; if not, write to the Free Software * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA */ #ifndef _FRR_QUEUE_H #define _FRR_QUEUE_H #ifdef __cplusplus extern "C" { #endif #if defined(__OpenBSD__) && !defined(STAILQ_HEAD) #include "openbsd-queue.h" /* Try to map FreeBSD implementation to OpenBSD one. */ #define STAILQ_HEAD(name, type) SIMPLEQ_HEAD(name, type) #define STAILQ_HEAD_INITIALIZER(head) SIMPLEQ_HEAD_INITIALIZER(head) #define STAILQ_ENTRY(entry) SIMPLEQ_ENTRY(entry) #define STAILQ_CONCAT(head1, head2) SIMPLEQ_CONCAT(head1, head2) #define STAILQ_EMPTY(head) SIMPLEQ_EMPTY(head) #define STAILQ_FIRST(head) SIMPLEQ_FIRST(head) #define STAILQ_FOREACH(var, head, field) SIMPLEQ_FOREACH(var, head, field) #define STAILQ_FOREACH_SAFE(var, head, field, tvar) SIMPLEQ_FOREACH_SAFE(var, head, field, tvar) #define STAILQ_INIT(head) SIMPLEQ_INIT(head) #define STAILQ_INSERT_AFTER(head, tqelm, elm, field) SIMPLEQ_INSERT_AFTER(head, tqelm, elm, field) #define STAILQ_INSERT_HEAD(head, elm, field) SIMPLEQ_INSERT_HEAD(head, elm, field) #define STAILQ_INSERT_TAIL(head, elm, field) SIMPLEQ_INSERT_TAIL(head, elm, field) #define STAILQ_LAST(head, type, field) \ (SIMPLEQ_EMPTY((head)) \ ? NULL \ : ((struct type *)(void *)((char *)((head)->sqh_last) \ - offsetof(struct type, field)))) #define STAILQ_NEXT(elm, field) SIMPLEQ_NEXT(elm, field) #define STAILQ_REMOVE(head, elm, type, field) \ do { \ if (SIMPLEQ_FIRST((head)) == (elm)) { \ SIMPLEQ_REMOVE_HEAD((head), field); \ } else { \ struct type *curelm = SIMPLEQ_FIRST((head)); \ while (SIMPLEQ_NEXT(curelm, field) != (elm)) \ curelm = SIMPLEQ_NEXT(curelm, field); \ SIMPLEQ_REMOVE_AFTER(head, curelm, field); \ } \ } while (0) #define STAILQ_REMOVE_AFTER(head, elm, field) SIMPLEQ_REMOVE_AFTER(head, elm, field) #define STAILQ_REMOVE_HEAD(head, field) SIMPLEQ_REMOVE_HEAD(head, field) #define STAILQ_SWAP(head1, head2, type) \ do { \ struct type *swap_first = STAILQ_FIRST(head1); \ struct type **swap_last = (head1)->sqh_last; \ STAILQ_FIRST(head1) = STAILQ_FIRST(head2); \ (head1)->sqh_last = (head2)->sqh_last; \ STAILQ_FIRST(head2) = swap_first; \ (head2)->sqh_last = swap_last; \ if (STAILQ_EMPTY(head1)) \ (head1)->sqh_last = &STAILQ_FIRST(head1); \ if (STAILQ_EMPTY(head2)) \ (head2)->sqh_last = &STAILQ_FIRST(head2); \ } while (0) #else #include "freebsd-queue.h" #endif /* defined(__OpenBSD__) && !defined(STAILQ_HEAD) */ #ifndef TAILQ_POP_FIRST #define TAILQ_POP_FIRST(head, field) \ ({ typeof((head)->tqh_first) _elm = TAILQ_FIRST(head); \ if (_elm) { \ if ((TAILQ_NEXT((_elm), field)) != NULL) \ TAILQ_NEXT((_elm), field)->field.tqe_prev = \ &TAILQ_FIRST(head); \ else \ (head)->tqh_last = &TAILQ_FIRST(head); \ TAILQ_FIRST(head) = TAILQ_NEXT((_elm), field); \ }; _elm; }) #endif #ifdef __cplusplus } #endif #endif /* _FRR_QUEUE_H */ frr-7.2.1/lib/resolver.c0000644000000000000000000001343513610377563011775 00000000000000/* C-Ares integration to Quagga mainloop * Copyright (c) 2014-2015 Timo Teräs * * This file is free software: you may copy, redistribute and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation, either version 2 of the License, or * (at your option) any later version. */ #ifdef HAVE_CONFIG_H #include "config.h" #endif #include #include #include "vector.h" #include "thread.h" #include "lib_errors.h" #include "resolver.h" #include "command.h" struct resolver_state { ares_channel channel; struct thread_master *master; struct thread *timeout; vector read_threads, write_threads; }; static struct resolver_state state; static bool resolver_debug; #define THREAD_RUNNING ((struct thread *)-1) static void resolver_update_timeouts(struct resolver_state *r); static int resolver_cb_timeout(struct thread *t) { struct resolver_state *r = THREAD_ARG(t); r->timeout = THREAD_RUNNING; ares_process(r->channel, NULL, NULL); r->timeout = NULL; resolver_update_timeouts(r); return 0; } static int resolver_cb_socket_readable(struct thread *t) { struct resolver_state *r = THREAD_ARG(t); int fd = THREAD_FD(t); vector_set_index(r->read_threads, fd, THREAD_RUNNING); ares_process_fd(r->channel, fd, ARES_SOCKET_BAD); if (vector_lookup(r->read_threads, fd) == THREAD_RUNNING) { t = NULL; thread_add_read(r->master, resolver_cb_socket_readable, r, fd, &t); vector_set_index(r->read_threads, fd, t); } resolver_update_timeouts(r); return 0; } static int resolver_cb_socket_writable(struct thread *t) { struct resolver_state *r = THREAD_ARG(t); int fd = THREAD_FD(t); vector_set_index(r->write_threads, fd, THREAD_RUNNING); ares_process_fd(r->channel, ARES_SOCKET_BAD, fd); if (vector_lookup(r->write_threads, fd) == THREAD_RUNNING) { t = NULL; thread_add_write(r->master, resolver_cb_socket_writable, r, fd, &t); vector_set_index(r->write_threads, fd, t); } resolver_update_timeouts(r); return 0; } static void resolver_update_timeouts(struct resolver_state *r) { struct timeval *tv, tvbuf; if (r->timeout == THREAD_RUNNING) return; THREAD_OFF(r->timeout); tv = ares_timeout(r->channel, NULL, &tvbuf); if (tv) { unsigned int timeoutms = tv->tv_sec * 1000 + tv->tv_usec / 1000; thread_add_timer_msec(r->master, resolver_cb_timeout, r, timeoutms, &r->timeout); } } static void ares_socket_cb(void *data, ares_socket_t fd, int readable, int writable) { struct resolver_state *r = (struct resolver_state *)data; struct thread *t; if (readable) { t = vector_lookup_ensure(r->read_threads, fd); if (!t) { thread_add_read(r->master, resolver_cb_socket_readable, r, fd, &t); vector_set_index(r->read_threads, fd, t); } } else { t = vector_lookup(r->read_threads, fd); if (t) { if (t != THREAD_RUNNING) { THREAD_OFF(t); } vector_unset(r->read_threads, fd); } } if (writable) { t = vector_lookup_ensure(r->write_threads, fd); if (!t) { thread_add_read(r->master, resolver_cb_socket_writable, r, fd, &t); vector_set_index(r->write_threads, fd, t); } } else { t = vector_lookup(r->write_threads, fd); if (t) { if (t != THREAD_RUNNING) { THREAD_OFF(t); } vector_unset(r->write_threads, fd); } } } static void ares_address_cb(void *arg, int status, int timeouts, struct hostent *he) { struct resolver_query *query = (struct resolver_query *)arg; union sockunion addr[16]; void (*callback)(struct resolver_query *, int, union sockunion *); size_t i; callback = query->callback; query->callback = NULL; if (status != ARES_SUCCESS) { if (resolver_debug) zlog_debug("[%p] Resolving failed", query); callback(query, -1, NULL); return; } for (i = 0; i < array_size(addr) && he->h_addr_list[i] != NULL; i++) { memset(&addr[i], 0, sizeof(addr[i])); addr[i].sa.sa_family = he->h_addrtype; switch (he->h_addrtype) { case AF_INET: memcpy(&addr[i].sin.sin_addr, (uint8_t *)he->h_addr_list[i], he->h_length); break; case AF_INET6: memcpy(&addr[i].sin6.sin6_addr, (uint8_t *)he->h_addr_list[i], he->h_length); break; } } if (resolver_debug) zlog_debug("[%p] Resolved with %d results", query, (int)i); callback(query, i, &addr[0]); } void resolver_resolve(struct resolver_query *query, int af, const char *hostname, void (*callback)(struct resolver_query *, int, union sockunion *)) { if (query->callback != NULL) { flog_err( EC_LIB_RESOLVER, "Trying to resolve '%s', but previous query was not finished yet", hostname); return; } if (resolver_debug) zlog_debug("[%p] Resolving '%s'", query, hostname); query->callback = callback; ares_gethostbyname(state.channel, hostname, af, ares_address_cb, query); resolver_update_timeouts(&state); } DEFUN(debug_resolver, debug_resolver_cmd, "[no] debug resolver", NO_STR DEBUG_STR "Debug DNS resolver actions\n") { resolver_debug = (argc == 2); return CMD_SUCCESS; } static struct cmd_node resolver_debug_node = {RESOLVER_DEBUG_NODE, "", 1}; static int resolver_config_write_debug(struct vty *vty) { if (resolver_debug) vty_out(vty, "debug resolver\n"); return 1; } void resolver_init(struct thread_master *tm) { struct ares_options ares_opts; state.master = tm; state.read_threads = vector_init(1); state.write_threads = vector_init(1); ares_opts = (struct ares_options){ .sock_state_cb = &ares_socket_cb, .sock_state_cb_data = &state, .timeout = 2, .tries = 3, }; ares_init_options(&state.channel, &ares_opts, ARES_OPT_SOCK_STATE_CB | ARES_OPT_TIMEOUT | ARES_OPT_TRIES); install_node(&resolver_debug_node, resolver_config_write_debug); install_element(CONFIG_NODE, &debug_resolver_cmd); install_element(ENABLE_NODE, &debug_resolver_cmd); } frr-7.2.1/lib/resolver.h0000644000000000000000000000140413610377563011773 00000000000000/* C-Ares integration to Quagga mainloop * Copyright (c) 2014-2015 Timo Teräs * * This file is free software: you may copy, redistribute and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation, either version 2 of the License, or * (at your option) any later version. */ #ifndef _FRR_RESOLVER_H #define _FRR_RESOLVER_H #include "thread.h" #include "sockunion.h" struct resolver_query { void (*callback)(struct resolver_query *, int n, union sockunion *); }; void resolver_init(struct thread_master *tm); void resolver_resolve(struct resolver_query *query, int af, const char *hostname, void (*cb)(struct resolver_query *, int, union sockunion *)); #endif /* _FRR_RESOLVER_H */ frr-7.2.1/lib/ringbuf.c0000644000000000000000000000707513610377563011573 00000000000000/* * Circular buffer implementation. * Copyright (C) 2017 Cumulus Networks * Quentin Young * * This program is free software; you can redistribute it and/or modify it * under the terms of the 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; see the file COPYING; if not, write to the Free Software * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA */ #include #include "ringbuf.h" #include "memory.h" DEFINE_MTYPE_STATIC(LIB, RINGBUFFER, "Ring buffer") struct ringbuf *ringbuf_new(size_t size) { struct ringbuf *buf = XCALLOC(MTYPE_RINGBUFFER, sizeof(struct ringbuf)); buf->data = XCALLOC(MTYPE_RINGBUFFER, size); buf->size = size; buf->empty = true; return buf; } void ringbuf_del(struct ringbuf *buf) { XFREE(MTYPE_RINGBUFFER, buf->data); XFREE(MTYPE_RINGBUFFER, buf); } size_t ringbuf_remain(struct ringbuf *buf) { ssize_t diff = buf->end - buf->start; diff += ((diff == 0) && !buf->empty) ? buf->size : 0; diff += (diff < 0) ? buf->size : 0; return (size_t)diff; } size_t ringbuf_space(struct ringbuf *buf) { return buf->size - ringbuf_remain(buf); } size_t ringbuf_put(struct ringbuf *buf, const void *data, size_t size) { const uint8_t *dp = data; size_t space = ringbuf_space(buf); size_t copysize = MIN(size, space); size_t tocopy = copysize; if (tocopy >= buf->size - buf->end) { size_t ts = buf->size - buf->end; memcpy(buf->data + buf->end, dp, ts); buf->end = 0; tocopy -= ts; dp += ts; } memcpy(buf->data + buf->end, dp, tocopy); buf->end += tocopy; buf->empty = (buf->start == buf->end) && (buf->empty && !copysize); return copysize; } size_t ringbuf_get(struct ringbuf *buf, void *data, size_t size) { uint8_t *dp = data; size_t remain = ringbuf_remain(buf); size_t copysize = MIN(remain, size); size_t tocopy = copysize; if (tocopy >= buf->size - buf->start) { size_t ts = buf->size - buf->start; memcpy(dp, buf->data + buf->start, ts); buf->start = 0; tocopy -= ts; dp += ts; } memcpy(dp, buf->data + buf->start, tocopy); buf->start = buf->start + tocopy; buf->empty = (buf->start == buf->end) && (buf->empty || copysize); return copysize; } size_t ringbuf_peek(struct ringbuf *buf, size_t offset, void *data, size_t size) { uint8_t *dp = data; size_t remain = ringbuf_remain(buf); if (offset >= remain) return 0; size_t copysize = MAX(MIN(remain - offset, size), (size_t)0); size_t tocopy = copysize; size_t cstart = (buf->start + offset) % buf->size; if (tocopy >= buf->size - cstart) { size_t ts = buf->size - cstart; memcpy(dp, buf->data + cstart, ts); cstart = 0; tocopy -= ts; dp += ts; } memcpy(dp, buf->data + cstart, tocopy); return copysize; } size_t ringbuf_copy(struct ringbuf *to, struct ringbuf *from, size_t size) { size_t tocopy = MIN(ringbuf_space(to), size); uint8_t *cbuf = XCALLOC(MTYPE_TMP, tocopy); tocopy = ringbuf_peek(from, 0, cbuf, tocopy); size_t put = ringbuf_put(to, cbuf, tocopy); XFREE(MTYPE_TMP, cbuf); return put; } void ringbuf_reset(struct ringbuf *buf) { buf->start = buf->end = 0; buf->empty = true; } void ringbuf_wipe(struct ringbuf *buf) { memset(buf->data, 0x00, buf->size); ringbuf_reset(buf); } frr-7.2.1/lib/ringbuf.h0000644000000000000000000000630313610377563011571 00000000000000/* * Circular buffer implementation. * Copyright (C) 2017 Cumulus Networks * Quentin Young * * This program is free software; you can redistribute it and/or modify it * under the terms of the 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; see the file COPYING; if not, write to the Free Software * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA */ #ifndef _FRR_RINGBUF_H_ #define _FRR_RINGBUF_H_ #include #include #include "memory.h" #ifdef __cplusplus extern "C" { #endif struct ringbuf { size_t size; ssize_t start; ssize_t end; bool empty; uint8_t *data; }; /* * Creates a new ring buffer. * * @param size buffer size, in bytes * @return the newly created buffer */ struct ringbuf *ringbuf_new(size_t size); /* * Deletes a ring buffer and frees all associated resources. * * @param buf the ring buffer to destroy */ void ringbuf_del(struct ringbuf *buf); /* * Get amount of data left to read from the buffer. * * @return number of readable bytes */ size_t ringbuf_remain(struct ringbuf *buf); /* * Get amount of space left to write to the buffer * * @return number of writeable bytes */ size_t ringbuf_space(struct ringbuf *buf); /* * Put data into the ring buffer. * * @param data the data to put in the buffer * @param size how much of data to put in * @return number of bytes written; will be less than size if there was not * enough space */ size_t ringbuf_put(struct ringbuf *buf, const void *data, size_t size); /* * Get data from the ring buffer. * * @param data where to put the data * @param size how much of data to get * @return number of bytes read into data; will be less than size if there was * not enough data to read */ size_t ringbuf_get(struct ringbuf *buf, void *data, size_t size); /* * Peek data from the ring buffer. * * @param offset where to get the data from, in bytes offset from the * start of the data * @param data where to put the data * @param size how much data to get * @return number of bytes read into data; will be less than size * if there was not enough data to read; will be -1 if the * offset exceeds the amount of data left in the ring * buffer */ size_t ringbuf_peek(struct ringbuf *buf, size_t offset, void *data, size_t size); /* * Copy data from one ringbuf to another. * * @param to destination ringbuf * @param from source ringbuf * @param size how much data to copy * @return amount of data copied */ size_t ringbuf_copy(struct ringbuf *to, struct ringbuf *from, size_t size); /* * Reset buffer. Does not wipe. * * @param buf */ void ringbuf_reset(struct ringbuf *buf); /* * Reset buffer. Wipes. * * @param buf */ void ringbuf_wipe(struct ringbuf *buf); #ifdef __cplusplus } #endif #endif /* _FRR_RINGBUF_H_ */ frr-7.2.1/lib/route_types.pl0000755000000000000000000001353413610377563012712 00000000000000#!/usr/bin/perl ## ## Scan a file of route-type definitions (see eg route_types.txt) and ## generate a corresponding header file with: ## ## - enum of Zserv route-types ## - redistribute strings for the various Quagga daemons ## ## See route_types.txt for the format. ## ## ## Copyright (C) 2009 David Lamparter. ## This file is part of GNU Zebra. ## ## GNU Zebra is free software; you can redistribute it and/or modify it ## under the terms of the GNU General Public License as published by the ## Free Software Foundation; either version 2, or (at your option) any ## later version. ## ## GNU Zebra is distributed in the hope that it will be useful, but ## WITHOUT ANY WARRANTY; without even the implied warranty of ## MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU ## General Public License for more details. ## ## You should have received a copy of the GNU General Public License ## along with GNU Zebra; see the file COPYING. If not, write to the Free ## Software Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA ## 02111-1307, USA. ## use strict; # input processing # my @protos; my %protodetail; my %daemons; while () { # skip comments and empty lines next if (/^\s*(#|$)/); # strip whitespace chomp; $_ =~ s/^\s*//; $_ =~ s/\s*$//; # match help strings if (/^(ZEBRA_ROUTE_[^\s]+)\s*,\s*"(.*)"$/) { $protodetail{$1}->{'longhelp'} = $2; next; } $_ =~ s/\s*,\s*/,/g; # else: 8-field line my @f = split(/,/, $_); unless (@f == 8 || @f == 9) { die "invalid input on route_types line $.\n"; } my $proto = $f[0]; $f[3] = $1 if ($f[3] =~ /^'(.*)'$/); $f[7] = $1 if ($f[7] =~ /^"(.*)"$/); $protodetail{$proto} = { "number" => scalar @protos, "type" => $f[0], "cname" => $f[1], "daemon" => $f[2], "char" => $f[3], "ipv4" => int($f[4]), "ipv6" => int($f[5]), "redist" => int($f[6]), "shorthelp" => $f[7], "restrict2" => $f[8], }; push @protos, $proto; $daemons{$f[2]} = { "ipv4" => int($f[4]), "ipv6" => int($f[5]) } unless ($f[2] eq "NULL"); } # output printf <{"ipv4"}); push @protosv6, $p if ($protodetail{$p}->{"ipv6"}); } pop @protos; sub codelist { my (@protos) = @_; my (@lines) = (); my $str = " \"Codes: "; for my $p (@protos) { my $s = sprintf("%s - %s, ", $protodetail{$p}->{"char"}, $protodetail{$p}->{"shorthelp"}); if (length($str . $s) > 70) { $str =~ s/ $//; push @lines, $str . "\\n\" \\\n"; $str = " \" "; } $str .= $s; } $str =~ s/ $//; push @lines, $str . "\\n\" \\\n"; push @lines, " \" > - selected route, * - FIB route, q - queued route, r - rejected route\\n\\n\""; return join("", @lines); } print "\n"; printf "#define SHOW_ROUTE_V4_HEADER \\\n%s\n", codelist(@protosv4); printf "#define SHOW_ROUTE_V6_HEADER \\\n%s\n", codelist(@protosv6); print "\n"; sub collect { my ($daemon, $ipv4, $ipv6, $any) = @_; my (@names, @help) = ((), ()); for my $p (@protos) { next if ($protodetail{$p}->{"daemon"} eq $daemon && $daemon ne "zebra"); next if ($protodetail{$p}->{"restrict2"} ne "" && $protodetail{$p}->{"restrict2"} ne $daemon); next if ($protodetail{$p}->{"redist"} eq 0); next unless (($ipv4 && $protodetail{$p}->{"ipv4"}) || ($ipv6 && $protodetail{$p}->{"ipv6"})); push @names, $protodetail{$p}->{"cname"}; push @help, " \"".$protodetail{$p}->{"longhelp"}."\\n\""; } if ($any == 1) { push @names, "any"; push @help, " \"Any of the above protocols\\n\""; } return ("\"<" . join("|", @names) . ">\"", join(" \\\n", @help)); } for my $daemon (sort keys %daemons) { next unless ($daemons{$daemon}->{"ipv4"} || $daemons{$daemon}->{"ipv6"}); printf "/* %s */\n", $daemon; if ($daemons{$daemon}->{"ipv4"} && $daemons{$daemon}->{"ipv6"}) { my ($names, $help) = collect($daemon, 1, 1, 0); printf "#define FRR_REDIST_STR_%s \\\n %s\n", uc $daemon, $names; printf "#define FRR_REDIST_HELP_STR_%s \\\n%s\n", uc $daemon, $help; ($names, $help) = collect($daemon, 1, 0, 0); printf "#define FRR_IP_REDIST_STR_%s \\\n %s\n", uc $daemon, $names; printf "#define FRR_IP_REDIST_HELP_STR_%s \\\n%s\n", uc $daemon, $help; ($names, $help) = collect($daemon, 0, 1, 0); printf "#define FRR_IP6_REDIST_STR_%s \\\n %s\n", uc $daemon, $names; printf "#define FRR_IP6_REDIST_HELP_STR_%s \\\n%s\n", uc $daemon, $help; if ($daemon eq "zebra") { ($names, $help) = collect($daemon, 1, 0, 1); printf "#define FRR_IP_PROTOCOL_MAP_STR_%s \\\n %s\n", uc $daemon, $names; printf "#define FRR_IP_PROTOCOL_MAP_HELP_STR_%s \\\n%s\n", uc $daemon, $help; ($names, $help) = collect($daemon, 0, 1, 1); printf "#define FRR_IP6_PROTOCOL_MAP_STR_%s \\\n %s\n", uc $daemon, $names; printf "#define FRR_IP6_PROTOCOL_MAP_HELP_STR_%s \\\n%s\n", uc $daemon, $help; } } else { my ($names, $help) = collect($daemon, $daemons{$daemon}->{"ipv4"}, $daemons{$daemon}->{"ipv6"}, 0); printf "#define FRR_REDIST_STR_%s \\\n %s\n", uc $daemon, $names; printf "#define FRR_REDIST_HELP_STR_%s \\\n%s\n", uc $daemon, $help; } print "\n"; } print <{"cname"}, $protodetail{$p}->{"char"}; } print < vnc ZEBRA_ROUTE_BGP_DIRECT, bgp-direct, NULL, 'b', 0, 0, 0, "BGP-Direct" # bgp unicast -> vnc ZEBRA_ROUTE_BGP_DIRECT_EXT, bgp-direct-to-nve-groups, NULL, 'e', 0, 0, 0, "BGP2VNC" ZEBRA_ROUTE_BABEL, babel, babeld, 'A', 1, 1, 1, "Babel" ZEBRA_ROUTE_SHARP, sharp, sharpd, 'D', 1, 1, 1, "SHARP" ZEBRA_ROUTE_PBR, pbr, pbrd, 'F', 1, 1, 0, "PBR" ZEBRA_ROUTE_BFD, bfd, bfdd, '-', 0, 0, 0, "BFD" ZEBRA_ROUTE_OPENFABRIC, openfabric, fabricd, 'f', 1, 1, 1, "OpenFabric" ZEBRA_ROUTE_VRRP, vrrp, vrrpd, '-', 0, 0, 0, "VRRP" ZEBRA_ROUTE_ALL, wildcard, none, '-', 0, 0, 0, "-" ## help strings ZEBRA_ROUTE_SYSTEM, "Reserved route type, for internal use only" ZEBRA_ROUTE_KERNEL, "Kernel routes (not installed via the zebra RIB)" ZEBRA_ROUTE_CONNECT,"Connected routes (directly attached subnet or host)" ZEBRA_ROUTE_STATIC, "Statically configured routes" ZEBRA_ROUTE_RIP, "Routing Information Protocol (RIP)" ZEBRA_ROUTE_RIPNG, "Routing Information Protocol next-generation (IPv6) (RIPng)" ZEBRA_ROUTE_OSPF, "Open Shortest Path First (OSPFv2)" ZEBRA_ROUTE_OSPF6, "Open Shortest Path First (IPv6) (OSPFv3)" ZEBRA_ROUTE_ISIS, "Intermediate System to Intermediate System (IS-IS)" ZEBRA_ROUTE_BGP, "Border Gateway Protocol (BGP)" ZEBRA_ROUTE_PIM, "Protocol Independent Multicast (PIM)" ZEBRA_ROUTE_EIGRP, "Enhanced Interior Gateway Routing Protocol (EIGRP)" ZEBRA_ROUTE_NHRP, "Next Hop Resolution Protocol (NHRP)" ZEBRA_ROUTE_HSLS, "Hazy-Sighted Link State Protocol (HSLS)" ZEBRA_ROUTE_VNC, "Virtual Network Control (VNC)" ZEBRA_ROUTE_OLSR, "Optimised Link State Routing (OLSR)" ZEBRA_ROUTE_TABLE, "Non-main Kernel Routing Table" ZEBRA_ROUTE_LDP, "Label Distribution Protocol (LDP)" ZEBRA_ROUTE_VNC_DIRECT, "VNC direct (not via zebra) routes" ZEBRA_ROUTE_BABEL, "Babel routing protocol (Babel)" ZEBRA_ROUTE_SHARP, "Super Happy Advanced Routing Protocol (sharpd)" ZEBRA_ROUTE_PBR, "Policy Based Routing (PBR)" ZEBRA_ROUTE_BFD, "Bidirectional Fowarding Detection (BFD)" ZEBRA_ROUTE_VRRP, "Virtual Router Redundancy Protocol (VRRP)" ZEBRA_ROUTE_OPENFABRIC, "OpenFabric Routing Protocol" frr-7.2.1/lib/routemap.c0000644000000000000000000026605713610377563012002 00000000000000/* Route map function. * Copyright (C) 1998, 1999 Kunihiro Ishiguro * * This file is part of GNU Zebra. * * GNU Zebra is free software; you can redistribute it and/or modify it * under the terms of the GNU General Public License as published by the * Free Software Foundation; either version 2, or (at your option) any * later version. * * GNU Zebra 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; see the file COPYING; if not, write to the Free Software * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA */ #include #include "linklist.h" #include "memory.h" #include "vector.h" #include "prefix.h" #include "vty.h" #include "routemap.h" #include "command.h" #include "log.h" #include "hash.h" #include "libfrr.h" #include "lib_errors.h" DEFINE_MTYPE_STATIC(LIB, ROUTE_MAP, "Route map") DEFINE_MTYPE(LIB, ROUTE_MAP_NAME, "Route map name") DEFINE_MTYPE_STATIC(LIB, ROUTE_MAP_INDEX, "Route map index") DEFINE_MTYPE(LIB, ROUTE_MAP_RULE, "Route map rule") DEFINE_MTYPE_STATIC(LIB, ROUTE_MAP_RULE_STR, "Route map rule str") DEFINE_MTYPE(LIB, ROUTE_MAP_COMPILED, "Route map compiled") DEFINE_MTYPE_STATIC(LIB, ROUTE_MAP_DEP, "Route map dependency") DEFINE_MTYPE_STATIC(LIB, ROUTE_MAP_DEP_DATA, "Route map dependency data") DEFINE_QOBJ_TYPE(route_map_index) DEFINE_QOBJ_TYPE(route_map) /* Vector for route match rules. */ static vector route_match_vec; /* Vector for route set rules. */ static vector route_set_vec; struct route_map_match_set_hooks { /* match interface */ int (*match_interface)(struct vty *vty, struct route_map_index *index, const char *command, const char *arg, route_map_event_t type); /* no match interface */ int (*no_match_interface)(struct vty *vty, struct route_map_index *index, const char *command, const char *arg, route_map_event_t type); /* match ip address */ int (*match_ip_address)(struct vty *vty, struct route_map_index *index, const char *command, const char *arg, route_map_event_t type); /* no match ip address */ int (*no_match_ip_address)(struct vty *vty, struct route_map_index *index, const char *command, const char *arg, route_map_event_t type); /* match ip address prefix list */ int (*match_ip_address_prefix_list)(struct vty *vty, struct route_map_index *index, const char *command, const char *arg, route_map_event_t type); /* no match ip address prefix list */ int (*no_match_ip_address_prefix_list)(struct vty *vty, struct route_map_index *index, const char *command, const char *arg, route_map_event_t type); /* match ip next hop */ int (*match_ip_next_hop)(struct vty *vty, struct route_map_index *index, const char *command, const char *arg, route_map_event_t type); /* no match ip next hop */ int (*no_match_ip_next_hop)(struct vty *vty, struct route_map_index *index, const char *command, const char *arg, route_map_event_t type); /* match ip next hop prefix list */ int (*match_ip_next_hop_prefix_list)(struct vty *vty, struct route_map_index *index, const char *command, const char *arg, route_map_event_t type); /* no match ip next hop prefix list */ int (*no_match_ip_next_hop_prefix_list)(struct vty *vty, struct route_map_index *index, const char *command, const char *arg, route_map_event_t type); /* match ip next-hop type */ int (*match_ip_next_hop_type)(struct vty *vty, struct route_map_index *index, const char *command, const char *arg, route_map_event_t type); /* no match ip next-hop type */ int (*no_match_ip_next_hop_type)(struct vty *vty, struct route_map_index *index, const char *command, const char *arg, route_map_event_t type); /* match ipv6 address */ int (*match_ipv6_address)(struct vty *vty, struct route_map_index *index, const char *command, const char *arg, route_map_event_t type); /* no match ipv6 address */ int (*no_match_ipv6_address)(struct vty *vty, struct route_map_index *index, const char *command, const char *arg, route_map_event_t type); /* match ipv6 address prefix list */ int (*match_ipv6_address_prefix_list)(struct vty *vty, struct route_map_index *index, const char *command, const char *arg, route_map_event_t type); /* no match ipv6 address prefix list */ int (*no_match_ipv6_address_prefix_list)(struct vty *vty, struct route_map_index *index, const char *command, const char *arg, route_map_event_t type); /* match ipv6 next-hop type */ int (*match_ipv6_next_hop_type)(struct vty *vty, struct route_map_index *index, const char *command, const char *arg, route_map_event_t type); /* no match ipv6 next-hop type */ int (*no_match_ipv6_next_hop_type)(struct vty *vty, struct route_map_index *index, const char *command, const char *arg, route_map_event_t type); /* match metric */ int (*match_metric)(struct vty *vty, struct route_map_index *index, const char *command, const char *arg, route_map_event_t type); /* no match metric */ int (*no_match_metric)(struct vty *vty, struct route_map_index *index, const char *command, const char *arg, route_map_event_t type); /* match tag */ int (*match_tag)(struct vty *vty, struct route_map_index *index, const char *command, const char *arg, route_map_event_t type); /* no match tag */ int (*no_match_tag)(struct vty *vty, struct route_map_index *index, const char *command, const char *arg, route_map_event_t type); /* set ip nexthop */ int (*set_ip_nexthop)(struct vty *vty, struct route_map_index *index, const char *command, const char *arg); /* no set ip nexthop */ int (*no_set_ip_nexthop)(struct vty *vty, struct route_map_index *index, const char *command, const char *arg); /* set ipv6 nexthop local */ int (*set_ipv6_nexthop_local)(struct vty *vty, struct route_map_index *index, const char *command, const char *arg); /* no set ipv6 nexthop local */ int (*no_set_ipv6_nexthop_local)(struct vty *vty, struct route_map_index *index, const char *command, const char *arg); /* set metric */ int (*set_metric)(struct vty *vty, struct route_map_index *index, const char *command, const char *arg); /* no set metric */ int (*no_set_metric)(struct vty *vty, struct route_map_index *index, const char *command, const char *arg); /* set tag */ int (*set_tag)(struct vty *vty, struct route_map_index *index, const char *command, const char *arg); /* no set tag */ int (*no_set_tag)(struct vty *vty, struct route_map_index *index, const char *command, const char *arg); }; struct route_map_match_set_hooks rmap_match_set_hook; /* match interface */ void route_map_match_interface_hook(int (*func)( struct vty *vty, struct route_map_index *index, const char *command, const char *arg, route_map_event_t type)) { rmap_match_set_hook.match_interface = func; } /* no match interface */ void route_map_no_match_interface_hook(int (*func)( struct vty *vty, struct route_map_index *index, const char *command, const char *arg, route_map_event_t type)) { rmap_match_set_hook.no_match_interface = func; } /* match ip address */ void route_map_match_ip_address_hook(int (*func)( struct vty *vty, struct route_map_index *index, const char *command, const char *arg, route_map_event_t type)) { rmap_match_set_hook.match_ip_address = func; } /* no match ip address */ void route_map_no_match_ip_address_hook(int (*func)( struct vty *vty, struct route_map_index *index, const char *command, const char *arg, route_map_event_t type)) { rmap_match_set_hook.no_match_ip_address = func; } /* match ip address prefix list */ void route_map_match_ip_address_prefix_list_hook(int (*func)( struct vty *vty, struct route_map_index *index, const char *command, const char *arg, route_map_event_t type)) { rmap_match_set_hook.match_ip_address_prefix_list = func; } /* no match ip address prefix list */ void route_map_no_match_ip_address_prefix_list_hook(int (*func)( struct vty *vty, struct route_map_index *index, const char *command, const char *arg, route_map_event_t type)) { rmap_match_set_hook.no_match_ip_address_prefix_list = func; } /* match ip next hop */ void route_map_match_ip_next_hop_hook(int (*func)( struct vty *vty, struct route_map_index *index, const char *command, const char *arg, route_map_event_t type)) { rmap_match_set_hook.match_ip_next_hop = func; } /* no match ip next hop */ void route_map_no_match_ip_next_hop_hook(int (*func)( struct vty *vty, struct route_map_index *index, const char *command, const char *arg, route_map_event_t type)) { rmap_match_set_hook.no_match_ip_next_hop = func; } /* match ip next hop prefix list */ void route_map_match_ip_next_hop_prefix_list_hook(int (*func)( struct vty *vty, struct route_map_index *index, const char *command, const char *arg, route_map_event_t type)) { rmap_match_set_hook.match_ip_next_hop_prefix_list = func; } /* no match ip next hop prefix list */ void route_map_no_match_ip_next_hop_prefix_list_hook(int (*func)( struct vty *vty, struct route_map_index *index, const char *command, const char *arg, route_map_event_t type)) { rmap_match_set_hook.no_match_ip_next_hop_prefix_list = func; } /* match ip next-hop type */ void route_map_match_ip_next_hop_type_hook(int (*func)( struct vty *vty, struct route_map_index *index, const char *command, const char *arg, route_map_event_t type)) { rmap_match_set_hook.match_ip_next_hop_type = func; } /* no match ip next-hop type */ void route_map_no_match_ip_next_hop_type_hook(int (*func)( struct vty *vty, struct route_map_index *index, const char *command, const char *arg, route_map_event_t type)) { rmap_match_set_hook.no_match_ip_next_hop_type = func; } /* match ipv6 address */ void route_map_match_ipv6_address_hook(int (*func)( struct vty *vty, struct route_map_index *index, const char *command, const char *arg, route_map_event_t type)) { rmap_match_set_hook.match_ipv6_address = func; } /* no match ipv6 address */ void route_map_no_match_ipv6_address_hook(int (*func)( struct vty *vty, struct route_map_index *index, const char *command, const char *arg, route_map_event_t type)) { rmap_match_set_hook.no_match_ipv6_address = func; } /* match ipv6 address prefix list */ void route_map_match_ipv6_address_prefix_list_hook(int (*func)( struct vty *vty, struct route_map_index *index, const char *command, const char *arg, route_map_event_t type)) { rmap_match_set_hook.match_ipv6_address_prefix_list = func; } /* no match ipv6 address prefix list */ void route_map_no_match_ipv6_address_prefix_list_hook(int (*func)( struct vty *vty, struct route_map_index *index, const char *command, const char *arg, route_map_event_t type)) { rmap_match_set_hook.no_match_ipv6_address_prefix_list = func; } /* match ipv6 next-hop type */ void route_map_match_ipv6_next_hop_type_hook(int (*func)( struct vty *vty, struct route_map_index *index, const char *command, const char *arg, route_map_event_t type)) { rmap_match_set_hook.match_ipv6_next_hop_type = func; } /* no match ipv6 next-hop type */ void route_map_no_match_ipv6_next_hop_type_hook(int (*func)( struct vty *vty, struct route_map_index *index, const char *command, const char *arg, route_map_event_t type)) { rmap_match_set_hook.no_match_ipv6_next_hop_type = func; } /* match metric */ void route_map_match_metric_hook(int (*func)( struct vty *vty, struct route_map_index *index, const char *command, const char *arg, route_map_event_t type)) { rmap_match_set_hook.match_metric = func; } /* no match metric */ void route_map_no_match_metric_hook(int (*func)( struct vty *vty, struct route_map_index *index, const char *command, const char *arg, route_map_event_t type)) { rmap_match_set_hook.no_match_metric = func; } /* match tag */ void route_map_match_tag_hook(int (*func)(struct vty *vty, struct route_map_index *index, const char *command, const char *arg, route_map_event_t type)) { rmap_match_set_hook.match_tag = func; } /* no match tag */ void route_map_no_match_tag_hook(int (*func)( struct vty *vty, struct route_map_index *index, const char *command, const char *arg, route_map_event_t type)) { rmap_match_set_hook.no_match_tag = func; } /* set ip nexthop */ void route_map_set_ip_nexthop_hook(int (*func)(struct vty *vty, struct route_map_index *index, const char *command, const char *arg)) { rmap_match_set_hook.set_ip_nexthop = func; } /* no set ip nexthop */ void route_map_no_set_ip_nexthop_hook(int (*func)(struct vty *vty, struct route_map_index *index, const char *command, const char *arg)) { rmap_match_set_hook.no_set_ip_nexthop = func; } /* set ipv6 nexthop local */ void route_map_set_ipv6_nexthop_local_hook( int (*func)(struct vty *vty, struct route_map_index *index, const char *command, const char *arg)) { rmap_match_set_hook.set_ipv6_nexthop_local = func; } /* no set ipv6 nexthop local */ void route_map_no_set_ipv6_nexthop_local_hook( int (*func)(struct vty *vty, struct route_map_index *index, const char *command, const char *arg)) { rmap_match_set_hook.no_set_ipv6_nexthop_local = func; } /* set metric */ void route_map_set_metric_hook(int (*func)(struct vty *vty, struct route_map_index *index, const char *command, const char *arg)) { rmap_match_set_hook.set_metric = func; } /* no set metric */ void route_map_no_set_metric_hook(int (*func)(struct vty *vty, struct route_map_index *index, const char *command, const char *arg)) { rmap_match_set_hook.no_set_metric = func; } /* set tag */ void route_map_set_tag_hook(int (*func)(struct vty *vty, struct route_map_index *index, const char *command, const char *arg)) { rmap_match_set_hook.set_tag = func; } /* no set tag */ void route_map_no_set_tag_hook(int (*func)(struct vty *vty, struct route_map_index *index, const char *command, const char *arg)) { rmap_match_set_hook.no_set_tag = func; } int generic_match_add(struct vty *vty, struct route_map_index *index, const char *command, const char *arg, route_map_event_t type) { enum rmap_compile_rets ret; ret = route_map_add_match(index, command, arg, type); switch (ret) { case RMAP_COMPILE_SUCCESS: if (type != RMAP_EVENT_MATCH_ADDED) { route_map_upd8_dependency(type, arg, index->map->name); } break; case RMAP_RULE_MISSING: vty_out(vty, "%% [%s] Can't find rule.\n", frr_protonameinst); return CMD_WARNING_CONFIG_FAILED; break; case RMAP_COMPILE_ERROR: vty_out(vty, "%% [%s] Argument form is unsupported or malformed.\n", frr_protonameinst); return CMD_WARNING_CONFIG_FAILED; break; case RMAP_DUPLICATE_RULE: /* * Nothing to do here move along */ break; } return CMD_SUCCESS; } int generic_match_delete(struct vty *vty, struct route_map_index *index, const char *command, const char *arg, route_map_event_t type) { enum rmap_compile_rets ret; int retval = CMD_SUCCESS; char *dep_name = NULL; const char *tmpstr; char *rmap_name = NULL; if (type != RMAP_EVENT_MATCH_DELETED) { /* ignore the mundane, the types without any dependency */ if (arg == NULL) { if ((tmpstr = route_map_get_match_arg(index, command)) != NULL) dep_name = XSTRDUP(MTYPE_ROUTE_MAP_RULE, tmpstr); } else { dep_name = XSTRDUP(MTYPE_ROUTE_MAP_RULE, arg); } rmap_name = XSTRDUP(MTYPE_ROUTE_MAP_NAME, index->map->name); } ret = route_map_delete_match(index, command, dep_name); switch (ret) { case RMAP_RULE_MISSING: vty_out(vty, "%% [%s] Can't find rule.\n", frr_protonameinst); retval = CMD_WARNING_CONFIG_FAILED; break; case RMAP_COMPILE_ERROR: vty_out(vty, "%% [%s] Argument form is unsupported or malformed.\n", frr_protonameinst); retval = CMD_WARNING_CONFIG_FAILED; break; case RMAP_COMPILE_SUCCESS: if (type != RMAP_EVENT_MATCH_DELETED && dep_name) route_map_upd8_dependency(type, dep_name, rmap_name); break; case RMAP_DUPLICATE_RULE: /* * Nothing to do here */ break; } XFREE(MTYPE_ROUTE_MAP_RULE, dep_name); XFREE(MTYPE_ROUTE_MAP_NAME, rmap_name); return retval; } int generic_set_add(struct vty *vty, struct route_map_index *index, const char *command, const char *arg) { enum rmap_compile_rets ret; ret = route_map_add_set(index, command, arg); switch (ret) { case RMAP_RULE_MISSING: vty_out(vty, "%% [%s] Can't find rule.\n", frr_protonameinst); return CMD_WARNING_CONFIG_FAILED; break; case RMAP_COMPILE_ERROR: vty_out(vty, "%% [%s] Argument form is unsupported or malformed.\n", frr_protonameinst); return CMD_WARNING_CONFIG_FAILED; break; case RMAP_COMPILE_SUCCESS: case RMAP_DUPLICATE_RULE: break; } return CMD_SUCCESS; } int generic_set_delete(struct vty *vty, struct route_map_index *index, const char *command, const char *arg) { enum rmap_compile_rets ret; ret = route_map_delete_set(index, command, arg); switch (ret) { case RMAP_RULE_MISSING: vty_out(vty, "%% [%s] Can't find rule.\n", frr_protonameinst); return CMD_WARNING_CONFIG_FAILED; break; case RMAP_COMPILE_ERROR: vty_out(vty, "%% [%s] Argument form is unsupported or malformed.\n", frr_protonameinst); return CMD_WARNING_CONFIG_FAILED; break; case RMAP_COMPILE_SUCCESS: case RMAP_DUPLICATE_RULE: break; } return CMD_SUCCESS; } /* Route map rule. This rule has both `match' rule and `set' rule. */ struct route_map_rule { /* Rule type. */ struct route_map_rule_cmd *cmd; /* For pretty printing. */ char *rule_str; /* Pre-compiled match rule. */ void *value; /* Linked list. */ struct route_map_rule *next; struct route_map_rule *prev; }; /* Making route map list. */ struct route_map_list { struct route_map *head; struct route_map *tail; void (*add_hook)(const char *); void (*delete_hook)(const char *); void (*event_hook)(const char *); }; /* Master list of route map. */ static struct route_map_list route_map_master = {NULL, NULL, NULL, NULL, NULL}; struct hash *route_map_master_hash = NULL; static unsigned int route_map_hash_key_make(const void *p) { const struct route_map *map = p; return string_hash_make(map->name); } static bool route_map_hash_cmp(const void *p1, const void *p2) { const struct route_map *map1 = p1; const struct route_map *map2 = p2; if (map1->deleted == map2->deleted) { if (map1->name && map2->name) { if (!strcmp(map1->name, map2->name)) { return true; } } else if (!map1->name && !map2->name) { return true; } } return false; } enum route_map_upd8_type { ROUTE_MAP_ADD = 1, ROUTE_MAP_DEL, }; /* all possible route-map dependency types */ enum route_map_dep_type { ROUTE_MAP_DEP_RMAP = 1, ROUTE_MAP_DEP_CLIST, ROUTE_MAP_DEP_ECLIST, ROUTE_MAP_DEP_LCLIST, ROUTE_MAP_DEP_PLIST, ROUTE_MAP_DEP_ASPATH, ROUTE_MAP_DEP_FILTER, ROUTE_MAP_DEP_MAX, }; struct route_map_dep { char *dep_name; struct hash *dep_rmap_hash; struct hash *this_hash; /* ptr to the hash structure this is part of */ }; struct route_map_dep_data { /* Route-map name. */ char *rname; /* Count of number of sequences of this * route-map that depend on the same entity. */ uint16_t refcnt; }; /* Hashes maintaining dependency between various sublists used by route maps */ struct hash *route_map_dep_hash[ROUTE_MAP_DEP_MAX]; static unsigned int route_map_dep_hash_make_key(const void *p); static void route_map_clear_all_references(char *rmap_name); static void route_map_rule_delete(struct route_map_rule_list *, struct route_map_rule *); static bool rmap_debug; static void route_map_index_delete(struct route_map_index *, int); /* New route map allocation. Please note route map's name must be specified. */ static struct route_map *route_map_new(const char *name) { struct route_map *new; new = XCALLOC(MTYPE_ROUTE_MAP, sizeof(struct route_map)); new->name = XSTRDUP(MTYPE_ROUTE_MAP_NAME, name); QOBJ_REG(new, route_map); return new; } /* Add new name to route_map. */ static struct route_map *route_map_add(const char *name) { struct route_map *map; struct route_map_list *list; map = route_map_new(name); list = &route_map_master; /* Add map to the hash */ hash_get(route_map_master_hash, map, hash_alloc_intern); /* Add new entry to the head of the list to match how it is added in the * hash table. This is to ensure that if the same route-map has been * created more than once and then marked for deletion (which can happen * if prior deletions haven't completed as BGP hasn't yet done the * route-map processing), the order of the entities is the same in both * the list and the hash table. Otherwise, since there is nothing to * distinguish between the two entries, the wrong entry could get freed. * TODO: This needs to be re-examined to handle it better - e.g., revive * a deleted entry if the route-map is created again. */ map->prev = NULL; map->next = list->head; if (list->head) list->head->prev = map; list->head = map; if (!list->tail) list->tail = map; /* Execute hook. */ if (route_map_master.add_hook) { (*route_map_master.add_hook)(name); route_map_notify_dependencies(name, RMAP_EVENT_CALL_ADDED); } if (rmap_debug) zlog_debug("Add route-map %s", name); return map; } /* this is supposed to be called post processing by * the delete hook function. Don't invoke delete_hook * again in this routine. */ static void route_map_free_map(struct route_map *map) { struct route_map_list *list; struct route_map_index *index; if (map == NULL) return; while ((index = map->head) != NULL) route_map_index_delete(index, 0); if (rmap_debug) zlog_debug("Deleting route-map %s", map->name); list = &route_map_master; QOBJ_UNREG(map); if (map->next) map->next->prev = map->prev; else list->tail = map->prev; if (map->prev) map->prev->next = map->next; else list->head = map->next; hash_release(route_map_master_hash, map); XFREE(MTYPE_ROUTE_MAP_NAME, map->name); XFREE(MTYPE_ROUTE_MAP, map); } /* Route map delete from list. */ static void route_map_delete(struct route_map *map) { struct route_map_index *index; char *name; while ((index = map->head) != NULL) route_map_index_delete(index, 0); name = map->name; map->head = NULL; /* Clear all dependencies */ route_map_clear_all_references(name); map->deleted = true; /* Execute deletion hook. */ if (route_map_master.delete_hook) { (*route_map_master.delete_hook)(name); route_map_notify_dependencies(name, RMAP_EVENT_CALL_DELETED); } if (!map->to_be_processed) { route_map_free_map(map); } } /* Lookup route map by route map name string. */ struct route_map *route_map_lookup_by_name(const char *name) { struct route_map *map; struct route_map tmp_map; if (!name) return NULL; // map.deleted is 0 via memset memset(&tmp_map, 0, sizeof(struct route_map)); tmp_map.name = XSTRDUP(MTYPE_ROUTE_MAP_NAME, name); map = hash_lookup(route_map_master_hash, &tmp_map); XFREE(MTYPE_ROUTE_MAP_NAME, tmp_map.name); return map; } /* Simple helper to warn if route-map does not exist. */ struct route_map *route_map_lookup_warn_noexist(struct vty *vty, const char *name) { struct route_map *route_map = route_map_lookup_by_name(name); if (!route_map) if (vty_shell_serv(vty)) vty_out(vty, "The route-map '%s' does not exist.\n", name); return route_map; } int route_map_mark_updated(const char *name) { struct route_map *map; int ret = -1; struct route_map tmp_map; if (!name) return (ret); map = route_map_lookup_by_name(name); /* If we did not find the routemap with deleted=false try again * with deleted=true */ if (!map) { memset(&tmp_map, 0, sizeof(struct route_map)); tmp_map.name = XSTRDUP(MTYPE_ROUTE_MAP_NAME, name); tmp_map.deleted = true; map = hash_lookup(route_map_master_hash, &tmp_map); XFREE(MTYPE_ROUTE_MAP_NAME, tmp_map.name); } if (map) { map->to_be_processed = true; ret = 0; } return (ret); } static int route_map_clear_updated(struct route_map *map) { int ret = -1; if (map) { map->to_be_processed = false; if (map->deleted) route_map_free_map(map); } return (ret); } /* Lookup route map. If there isn't route map create one and return it. */ static struct route_map *route_map_get(const char *name) { struct route_map *map; map = route_map_lookup_by_name(name); if (map == NULL) map = route_map_add(name); return map; } void route_map_walk_update_list(void (*route_map_update_fn)(char *name)) { struct route_map *node; struct route_map *nnode = NULL; for (node = route_map_master.head; node; node = nnode) { if (node->to_be_processed) { /* DD: Should we add any thread yield code here */ route_map_update_fn(node->name); nnode = node->next; route_map_clear_updated(node); } else nnode = node->next; } } /* Return route map's type string. */ static const char *route_map_type_str(enum route_map_type type) { switch (type) { case RMAP_PERMIT: return "permit"; break; case RMAP_DENY: return "deny"; break; case RMAP_ANY: return ""; break; } return ""; } static const char *route_map_cmd_result_str(enum route_map_cmd_result_t res) { switch (res) { case RMAP_MATCH: return "match"; case RMAP_NOMATCH: return "no match"; case RMAP_NOOP: return "noop"; case RMAP_ERROR: return "error"; case RMAP_OKAY: return "okay"; } return "invalid"; } static const char *route_map_result_str(route_map_result_t res) { switch (res) { case RMAP_DENYMATCH: return "deny"; case RMAP_PERMITMATCH: return "permit"; } return "invalid"; } static int route_map_empty(struct route_map *map) { if (map->head == NULL && map->tail == NULL) return 1; else return 0; } /* show route-map */ static void vty_show_route_map_entry(struct vty *vty, struct route_map *map) { struct route_map_index *index; struct route_map_rule *rule; vty_out(vty, "route-map: %s Invoked: %" PRIu64 "\n", map->name, map->applied - map->applied_clear); for (index = map->head; index; index = index->next) { vty_out(vty, " %s, sequence %d Invoked %" PRIu64 "\n", route_map_type_str(index->type), index->pref, index->applied - index->applied_clear); /* Description */ if (index->description) vty_out(vty, " Description:\n %s\n", index->description); /* Match clauses */ vty_out(vty, " Match clauses:\n"); for (rule = index->match_list.head; rule; rule = rule->next) vty_out(vty, " %s %s\n", rule->cmd->str, rule->rule_str); vty_out(vty, " Set clauses:\n"); for (rule = index->set_list.head; rule; rule = rule->next) vty_out(vty, " %s %s\n", rule->cmd->str, rule->rule_str); /* Call clause */ vty_out(vty, " Call clause:\n"); if (index->nextrm) vty_out(vty, " Call %s\n", index->nextrm); /* Exit Policy */ vty_out(vty, " Action:\n"); if (index->exitpolicy == RMAP_GOTO) vty_out(vty, " Goto %d\n", index->nextpref); else if (index->exitpolicy == RMAP_NEXT) vty_out(vty, " Continue to next entry\n"); else if (index->exitpolicy == RMAP_EXIT) vty_out(vty, " Exit routemap\n"); } } static int sort_route_map(const void **map1, const void **map2) { const struct route_map *m1 = *map1; const struct route_map *m2 = *map2; return strcmp(m1->name, m2->name); } static int vty_show_route_map(struct vty *vty, const char *name) { struct route_map *map; vty_out(vty, "%s:\n", frr_protonameinst); if (name) { map = route_map_lookup_by_name(name); if (map) { vty_show_route_map_entry(vty, map); return CMD_SUCCESS; } else { vty_out(vty, "%s: 'route-map %s' not found\n", frr_protonameinst, name); return CMD_SUCCESS; } } else { struct list *maplist = list_new(); struct listnode *ln; for (map = route_map_master.head; map; map = map->next) listnode_add(maplist, map); list_sort(maplist, sort_route_map); for (ALL_LIST_ELEMENTS_RO(maplist, ln, map)) vty_show_route_map_entry(vty, map); list_delete(&maplist); } return CMD_SUCCESS; } /* Unused route map details */ static int vty_show_unused_route_map(struct vty *vty) { struct list *maplist = list_new(); struct listnode *ln; struct route_map *map; for (map = route_map_master.head; map; map = map->next) { /* If use_count is zero, No protocol is using this routemap. * so adding to the list. */ if (!map->use_count) listnode_add(maplist, map); } if (maplist->count > 0) { vty_out(vty, "\n%s:\n", frr_protonameinst); list_sort(maplist, sort_route_map); for (ALL_LIST_ELEMENTS_RO(maplist, ln, map)) vty_show_route_map_entry(vty, map); } else { vty_out(vty, "\n%s: None\n", frr_protonameinst); } list_delete(&maplist); return CMD_SUCCESS; } /* New route map allocation. Please note route map's name must be specified. */ static struct route_map_index *route_map_index_new(void) { struct route_map_index *new; new = XCALLOC(MTYPE_ROUTE_MAP_INDEX, sizeof(struct route_map_index)); new->exitpolicy = RMAP_EXIT; /* Default to Cisco-style */ QOBJ_REG(new, route_map_index); return new; } /* Free route map index. */ static void route_map_index_delete(struct route_map_index *index, int notify) { struct route_map_rule *rule; QOBJ_UNREG(index); if (rmap_debug) zlog_debug("Deleting route-map %s sequence %d", index->map->name, index->pref); /* Free route match. */ while ((rule = index->match_list.head) != NULL) route_map_rule_delete(&index->match_list, rule); /* Free route set. */ while ((rule = index->set_list.head) != NULL) route_map_rule_delete(&index->set_list, rule); /* Remove index from route map list. */ if (index->next) index->next->prev = index->prev; else index->map->tail = index->prev; if (index->prev) index->prev->next = index->next; else index->map->head = index->next; /* Free 'char *nextrm' if not NULL */ XFREE(MTYPE_ROUTE_MAP_NAME, index->nextrm); /* Execute event hook. */ if (route_map_master.event_hook && notify) { (*route_map_master.event_hook)(index->map->name); route_map_notify_dependencies(index->map->name, RMAP_EVENT_CALL_ADDED); } XFREE(MTYPE_ROUTE_MAP_INDEX, index); } /* Lookup index from route map. */ static struct route_map_index *route_map_index_lookup(struct route_map *map, enum route_map_type type, int pref) { struct route_map_index *index; for (index = map->head; index; index = index->next) if ((index->type == type || type == RMAP_ANY) && index->pref == pref) return index; return NULL; } /* Add new index to route map. */ static struct route_map_index * route_map_index_add(struct route_map *map, enum route_map_type type, int pref) { struct route_map_index *index; struct route_map_index *point; /* Allocate new route map inex. */ index = route_map_index_new(); index->map = map; index->type = type; index->pref = pref; /* Compare preference. */ for (point = map->head; point; point = point->next) if (point->pref >= pref) break; if (map->head == NULL) { map->head = map->tail = index; } else if (point == NULL) { index->prev = map->tail; map->tail->next = index; map->tail = index; } else if (point == map->head) { index->next = map->head; map->head->prev = index; map->head = index; } else { index->next = point; index->prev = point->prev; if (point->prev) point->prev->next = index; point->prev = index; } /* Execute event hook. */ if (route_map_master.event_hook) { (*route_map_master.event_hook)(map->name); route_map_notify_dependencies(map->name, RMAP_EVENT_CALL_ADDED); } if (rmap_debug) zlog_debug("Route-map %s add sequence %d, type: %s", map->name, pref, route_map_type_str(type)); return index; } /* Get route map index. */ static struct route_map_index * route_map_index_get(struct route_map *map, enum route_map_type type, int pref) { struct route_map_index *index; index = route_map_index_lookup(map, RMAP_ANY, pref); if (index && index->type != type) { /* Delete index from route map. */ route_map_index_delete(index, 1); index = NULL; } if (index == NULL) index = route_map_index_add(map, type, pref); return index; } /* New route map rule */ static struct route_map_rule *route_map_rule_new(void) { struct route_map_rule *new; new = XCALLOC(MTYPE_ROUTE_MAP_RULE, sizeof(struct route_map_rule)); return new; } /* Install rule command to the match list. */ void route_map_install_match(struct route_map_rule_cmd *cmd) { vector_set(route_match_vec, cmd); } /* Install rule command to the set list. */ void route_map_install_set(struct route_map_rule_cmd *cmd) { vector_set(route_set_vec, cmd); } /* Lookup rule command from match list. */ static struct route_map_rule_cmd *route_map_lookup_match(const char *name) { unsigned int i; struct route_map_rule_cmd *rule; for (i = 0; i < vector_active(route_match_vec); i++) if ((rule = vector_slot(route_match_vec, i)) != NULL) if (strcmp(rule->str, name) == 0) return rule; return NULL; } /* Lookup rule command from set list. */ static struct route_map_rule_cmd *route_map_lookup_set(const char *name) { unsigned int i; struct route_map_rule_cmd *rule; for (i = 0; i < vector_active(route_set_vec); i++) if ((rule = vector_slot(route_set_vec, i)) != NULL) if (strcmp(rule->str, name) == 0) return rule; return NULL; } /* Add match and set rule to rule list. */ static void route_map_rule_add(struct route_map_rule_list *list, struct route_map_rule *rule) { rule->next = NULL; rule->prev = list->tail; if (list->tail) list->tail->next = rule; else list->head = rule; list->tail = rule; } /* Delete rule from rule list. */ static void route_map_rule_delete(struct route_map_rule_list *list, struct route_map_rule *rule) { if (rule->cmd->func_free) (*rule->cmd->func_free)(rule->value); XFREE(MTYPE_ROUTE_MAP_RULE_STR, rule->rule_str); if (rule->next) rule->next->prev = rule->prev; else list->tail = rule->prev; if (rule->prev) rule->prev->next = rule->next; else list->head = rule->next; XFREE(MTYPE_ROUTE_MAP_RULE, rule); } /* strcmp wrapper function which don't crush even argument is NULL. */ static int rulecmp(const char *dst, const char *src) { if (dst == NULL) { if (src == NULL) return 0; else return 1; } else { if (src == NULL) return 1; else return strcmp(dst, src); } return 1; } /* Use this to return the already specified argument for this match. This is * useful to get the specified argument with a route map match rule when the * rule is being deleted and the argument is not provided. */ const char *route_map_get_match_arg(struct route_map_index *index, const char *match_name) { struct route_map_rule *rule; struct route_map_rule_cmd *cmd; /* First lookup rule for add match statement. */ cmd = route_map_lookup_match(match_name); if (cmd == NULL) return NULL; for (rule = index->match_list.head; rule; rule = rule->next) if (rule->cmd == cmd && rule->rule_str != NULL) return (rule->rule_str); return (NULL); } static route_map_event_t get_route_map_delete_event(route_map_event_t type) { switch (type) { case RMAP_EVENT_CALL_ADDED: return RMAP_EVENT_CALL_DELETED; case RMAP_EVENT_PLIST_ADDED: return RMAP_EVENT_PLIST_DELETED; case RMAP_EVENT_CLIST_ADDED: return RMAP_EVENT_CLIST_DELETED; case RMAP_EVENT_ECLIST_ADDED: return RMAP_EVENT_ECLIST_DELETED; case RMAP_EVENT_LLIST_ADDED: return RMAP_EVENT_LLIST_DELETED; case RMAP_EVENT_ASLIST_ADDED: return RMAP_EVENT_ASLIST_DELETED; case RMAP_EVENT_FILTER_ADDED: return RMAP_EVENT_FILTER_DELETED; case RMAP_EVENT_SET_ADDED: case RMAP_EVENT_SET_DELETED: case RMAP_EVENT_SET_REPLACED: case RMAP_EVENT_MATCH_ADDED: case RMAP_EVENT_MATCH_DELETED: case RMAP_EVENT_MATCH_REPLACED: case RMAP_EVENT_INDEX_ADDED: case RMAP_EVENT_INDEX_DELETED: case RMAP_EVENT_CALL_DELETED: case RMAP_EVENT_PLIST_DELETED: case RMAP_EVENT_CLIST_DELETED: case RMAP_EVENT_ECLIST_DELETED: case RMAP_EVENT_LLIST_DELETED: case RMAP_EVENT_ASLIST_DELETED: case RMAP_EVENT_FILTER_DELETED: /* This function returns the appropriate 'deleted' event type * for every 'added' event type passed to this function. * This is done only for named entities used in the * route-map match commands. * This function is not to be invoked for any of the other event * types. */ assert(0); } assert(0); /* * Return to make c happy but if we get here something has gone * terribly terribly wrong, so yes this return makes no sense. */ return RMAP_EVENT_CALL_ADDED; } /* Add match statement to route map. */ enum rmap_compile_rets route_map_add_match(struct route_map_index *index, const char *match_name, const char *match_arg, route_map_event_t type) { struct route_map_rule *rule; struct route_map_rule *next; struct route_map_rule_cmd *cmd; void *compile; int8_t delete_rmap_event_type = 0; /* First lookup rule for add match statement. */ cmd = route_map_lookup_match(match_name); if (cmd == NULL) return RMAP_RULE_MISSING; /* Next call compile function for this match statement. */ if (cmd->func_compile) { compile = (*cmd->func_compile)(match_arg); if (compile == NULL) return RMAP_COMPILE_ERROR; } else compile = NULL; /* If argument is completely same ignore it. */ for (rule = index->match_list.head; rule; rule = next) { next = rule->next; if (rule->cmd == cmd) { /* If the configured route-map match rule is exactly * the same as the existing configuration then, * ignore the duplicate configuration. */ if (strcmp(match_arg, rule->rule_str) == 0) { if (cmd->func_free) (*cmd->func_free)(compile); return RMAP_DUPLICATE_RULE; } /* Remove the dependency of the route-map on the rule * that is being replaced. */ if (type >= RMAP_EVENT_CALL_ADDED) { delete_rmap_event_type = get_route_map_delete_event(type); route_map_upd8_dependency( delete_rmap_event_type, rule->rule_str, index->map->name); } route_map_rule_delete(&index->match_list, rule); } } /* Add new route map match rule. */ rule = route_map_rule_new(); rule->cmd = cmd; rule->value = compile; if (match_arg) rule->rule_str = XSTRDUP(MTYPE_ROUTE_MAP_RULE_STR, match_arg); else rule->rule_str = NULL; /* Add new route match rule to linked list. */ route_map_rule_add(&index->match_list, rule); /* Execute event hook. */ if (route_map_master.event_hook) { (*route_map_master.event_hook)(index->map->name); route_map_notify_dependencies(index->map->name, RMAP_EVENT_CALL_ADDED); } return RMAP_COMPILE_SUCCESS; } /* Delete specified route match rule. */ enum rmap_compile_rets route_map_delete_match(struct route_map_index *index, const char *match_name, const char *match_arg) { struct route_map_rule *rule; struct route_map_rule_cmd *cmd; cmd = route_map_lookup_match(match_name); if (cmd == NULL) return RMAP_RULE_MISSING; for (rule = index->match_list.head; rule; rule = rule->next) if (rule->cmd == cmd && (rulecmp(rule->rule_str, match_arg) == 0 || match_arg == NULL)) { route_map_rule_delete(&index->match_list, rule); /* Execute event hook. */ if (route_map_master.event_hook) { (*route_map_master.event_hook)(index->map->name); route_map_notify_dependencies( index->map->name, RMAP_EVENT_CALL_ADDED); } return RMAP_COMPILE_SUCCESS; } /* Can't find matched rule. */ return RMAP_RULE_MISSING; } /* Add route-map set statement to the route map. */ enum rmap_compile_rets route_map_add_set(struct route_map_index *index, const char *set_name, const char *set_arg) { struct route_map_rule *rule; struct route_map_rule *next; struct route_map_rule_cmd *cmd; void *compile; cmd = route_map_lookup_set(set_name); if (cmd == NULL) return RMAP_RULE_MISSING; /* Next call compile function for this match statement. */ if (cmd->func_compile) { compile = (*cmd->func_compile)(set_arg); if (compile == NULL) return RMAP_COMPILE_ERROR; } else compile = NULL; /* Add by WJL. if old set command of same kind exist, delete it first to ensure only one set command of same kind exist under a route_map_index. */ for (rule = index->set_list.head; rule; rule = next) { next = rule->next; if (rule->cmd == cmd) route_map_rule_delete(&index->set_list, rule); } /* Add new route map match rule. */ rule = route_map_rule_new(); rule->cmd = cmd; rule->value = compile; if (set_arg) rule->rule_str = XSTRDUP(MTYPE_ROUTE_MAP_RULE_STR, set_arg); else rule->rule_str = NULL; /* Add new route match rule to linked list. */ route_map_rule_add(&index->set_list, rule); /* Execute event hook. */ if (route_map_master.event_hook) { (*route_map_master.event_hook)(index->map->name); route_map_notify_dependencies(index->map->name, RMAP_EVENT_CALL_ADDED); } return RMAP_COMPILE_SUCCESS; } /* Delete route map set rule. */ enum rmap_compile_rets route_map_delete_set(struct route_map_index *index, const char *set_name, const char *set_arg) { struct route_map_rule *rule; struct route_map_rule_cmd *cmd; cmd = route_map_lookup_set(set_name); if (cmd == NULL) return RMAP_RULE_MISSING; for (rule = index->set_list.head; rule; rule = rule->next) if ((rule->cmd == cmd) && (rulecmp(rule->rule_str, set_arg) == 0 || set_arg == NULL)) { route_map_rule_delete(&index->set_list, rule); /* Execute event hook. */ if (route_map_master.event_hook) { (*route_map_master.event_hook)(index->map->name); route_map_notify_dependencies( index->map->name, RMAP_EVENT_CALL_ADDED); } return RMAP_COMPILE_SUCCESS; } /* Can't find matched rule. */ return RMAP_RULE_MISSING; } static enum route_map_cmd_result_t route_map_apply_match(struct route_map_rule_list *match_list, const struct prefix *prefix, route_map_object_t type, void *object) { enum route_map_cmd_result_t ret = RMAP_NOMATCH; struct route_map_rule *match; bool is_matched = false; /* Check all match rule and if there is no match rule, go to the set statement. */ if (!match_list->head) ret = RMAP_MATCH; else { for (match = match_list->head; match; match = match->next) { /* * Try each match statement. If any match does not * return RMAP_MATCH or RMAP_NOOP, return. * Otherwise continue on to next match statement. * All match statements must MATCH for * end-result to be a match. * (Exception:If match stmts result in a mix of * MATCH/NOOP, then also end-result is a match) * If all result in NOOP, end-result is NOOP. */ ret = (*match->cmd->func_apply)(match->value, prefix, type, object); /* * If the consolidated result of func_apply is: * ----------------------------------------------- * | MATCH | NOMATCH | NOOP | Final Result | * ------------------------------------------------ * | yes | yes | yes | NOMATCH | * | no | no | yes | NOOP | * | yes | no | yes | MATCH | * | no | yes | yes | NOMATCH | * |----------------------------------------------- * * Traditionally, all rules within route-map * should match for it to MATCH. * If there are noops within the route-map rules, * it follows the above matrix. * * Eg: route-map rm1 permit 10 * match rule1 * match rule2 * match rule3 * .... * route-map rm1 permit 20 * match ruleX * match ruleY * ... */ switch (ret) { case RMAP_MATCH: is_matched = true; break; case RMAP_NOMATCH: return ret; case RMAP_NOOP: if (is_matched) ret = RMAP_MATCH; break; default: break; } } } return ret; } /* Apply route map's each index to the object. The matrix for a route-map looks like this: (note, this includes the description for the "NEXT" and "GOTO" frobs now | Match | No Match | No op |-----------|--------------|------- permit | action | cont | cont. | | default:deny | default:permit -------------------+----------------------- | deny | cont | cont. deny | | default:deny | default:permit |-----------|--------------|-------- action) -Apply Set statements, accept route -If Call statement is present jump to the specified route-map, if it denies the route we finish. -If NEXT is specified, goto NEXT statement -If GOTO is specified, goto the first clause where pref > nextpref -If nothing is specified, do as Cisco and finish deny) -Route is denied by route-map. cont) -Goto Next index If we get no matches after we've processed all updates, then the route is dropped too. Some notes on the new "CALL", "NEXT" and "GOTO" call WORD - If this clause is matched, then the set statements are executed and then we jump to route-map 'WORD'. If this route-map denies the route, we finish, in other case we do whatever the exit policy (EXIT, NEXT or GOTO) tells. on-match next - If this clause is matched, then the set statements are executed and then we drop through to the next clause on-match goto n - If this clause is matched, then the set statments are executed and then we goto the nth clause, or the first clause greater than this. In order to ensure route-maps *always* exit, you cannot jump backwards. Sorry ;) We need to make sure our route-map processing matches the above */ route_map_result_t route_map_apply(struct route_map *map, const struct prefix *prefix, route_map_object_t type, void *object) { static int recursion = 0; enum route_map_cmd_result_t match_ret = RMAP_NOMATCH; route_map_result_t ret = RMAP_PERMITMATCH; struct route_map_index *index; struct route_map_rule *set; char buf[PREFIX_STRLEN]; if (recursion > RMAP_RECURSION_LIMIT) { flog_warn( EC_LIB_RMAP_RECURSION_LIMIT, "route-map recursion limit (%d) reached, discarding route", RMAP_RECURSION_LIMIT); recursion = 0; return RMAP_DENYMATCH; } if (map == NULL || map->head == NULL) { ret = RMAP_DENYMATCH; goto route_map_apply_end; } map->applied++; for (index = map->head; index; index = index->next) { /* Apply this index. */ index->applied++; match_ret = route_map_apply_match(&index->match_list, prefix, type, object); if (rmap_debug) { zlog_debug("Route-map: %s, sequence: %d, prefix: %s, result: %s", map->name, index->pref, prefix2str(prefix, buf, sizeof(buf)), route_map_cmd_result_str(match_ret)); } /* Now we apply the matrix from above */ if (match_ret == RMAP_NOOP) /* * Do not change the return value. Retain the previous * return value. Previous values can be: * 1)permitmatch (if a nomatch was never * seen before in this route-map.) * 2)denymatch (if a nomatch was seen earlier in one * of the previous sequences) */ /* * 'cont' from matrix - continue to next route-map * sequence */ continue; else if (match_ret == RMAP_NOMATCH) { /* * The return value is now changed to denymatch. * So from here on out, even if we see more noops, * we retain this return value and return this * eventually if there are no matches. */ ret = RMAP_DENYMATCH; /* * 'cont' from matrix - continue to next route-map * sequence */ continue; } else if (match_ret == RMAP_MATCH) { if (index->type == RMAP_PERMIT) /* 'action' */ { /* Match succeeded, rmap is of type permit */ ret = RMAP_PERMITMATCH; /* permit+match must execute sets */ for (set = index->set_list.head; set; set = set->next) /* * set cmds return RMAP_OKAY or * RMAP_ERROR. We do not care if * set succeeded or not. So, ignore * return code. */ (void) (*set->cmd->func_apply)( set->value, prefix, type, object); /* Call another route-map if available */ if (index->nextrm) { struct route_map *nextrm = route_map_lookup_by_name( index->nextrm); if (nextrm) /* Target route-map found, jump to it */ { recursion++; ret = route_map_apply( nextrm, prefix, type, object); recursion--; } /* If nextrm returned 'deny', finish. */ if (ret == RMAP_DENYMATCH) goto route_map_apply_end; } switch (index->exitpolicy) { case RMAP_EXIT: goto route_map_apply_end; case RMAP_NEXT: continue; case RMAP_GOTO: { /* Find the next clause to jump to */ struct route_map_index *next = index->next; int nextpref = index->nextpref; while (next && next->pref < nextpref) { index = next; next = next->next; } if (next == NULL) { /* No clauses match! */ goto route_map_apply_end; } } } } else if (index->type == RMAP_DENY) /* 'deny' */ { ret = RMAP_DENYMATCH; goto route_map_apply_end; } } } route_map_apply_end: if (rmap_debug) { zlog_debug("Route-map: %s, prefix: %s, result: %s", (map ? map->name : "null"), prefix2str(prefix, buf, sizeof(buf)), route_map_result_str(ret)); } return (ret); } void route_map_add_hook(void (*func)(const char *)) { route_map_master.add_hook = func; } void route_map_delete_hook(void (*func)(const char *)) { route_map_master.delete_hook = func; } void route_map_event_hook(void (*func)(const char *name)) { route_map_master.event_hook = func; } /* Routines for route map dependency lists and dependency processing */ static bool route_map_rmap_hash_cmp(const void *p1, const void *p2) { return strcmp(((const struct route_map_dep_data *)p1)->rname, ((const struct route_map_dep_data *)p2)->rname) == 0; } static bool route_map_dep_hash_cmp(const void *p1, const void *p2) { return (strcmp(((const struct route_map_dep *)p1)->dep_name, (const char *)p2) == 0); } static void route_map_clear_reference(struct hash_bucket *bucket, void *arg) { struct route_map_dep *dep = bucket->data; struct route_map_dep_data *dep_data = NULL, tmp_dep_data; if (arg) { memset(&tmp_dep_data, 0, sizeof(struct route_map_dep_data)); tmp_dep_data.rname = arg; dep_data = hash_release(dep->dep_rmap_hash, &tmp_dep_data); if (dep_data) { XFREE(MTYPE_ROUTE_MAP_NAME, dep_data->rname); XFREE(MTYPE_ROUTE_MAP_DEP_DATA, dep_data); } if (!dep->dep_rmap_hash->count) { dep = hash_release(dep->this_hash, (void *)dep->dep_name); hash_free(dep->dep_rmap_hash); XFREE(MTYPE_ROUTE_MAP_NAME, dep->dep_name); XFREE(MTYPE_ROUTE_MAP_DEP, dep); } } } static void route_map_clear_all_references(char *rmap_name) { int i; for (i = 1; i < ROUTE_MAP_DEP_MAX; i++) { hash_iterate(route_map_dep_hash[i], route_map_clear_reference, (void *)rmap_name); } } static unsigned int route_map_dep_data_hash_make_key(const void *p) { const struct route_map_dep_data *dep_data = p; return string_hash_make(dep_data->rname); } static void *route_map_dep_hash_alloc(void *p) { char *dep_name = (char *)p; struct route_map_dep *dep_entry; dep_entry = XCALLOC(MTYPE_ROUTE_MAP_DEP, sizeof(struct route_map_dep)); dep_entry->dep_name = XSTRDUP(MTYPE_ROUTE_MAP_NAME, dep_name); dep_entry->dep_rmap_hash = hash_create_size(8, route_map_dep_data_hash_make_key, route_map_rmap_hash_cmp, "Route Map Dep Hash"); dep_entry->this_hash = NULL; return dep_entry; } static void *route_map_name_hash_alloc(void *p) { struct route_map_dep_data *dep_data = NULL, *tmp_dep_data = NULL; dep_data = XCALLOC(MTYPE_ROUTE_MAP_DEP_DATA, sizeof(struct route_map_dep_data)); tmp_dep_data = p; dep_data->rname = XSTRDUP(MTYPE_ROUTE_MAP_NAME, tmp_dep_data->rname); return dep_data; } static unsigned int route_map_dep_hash_make_key(const void *p) { return (string_hash_make((char *)p)); } static void route_map_print_dependency(struct hash_bucket *bucket, void *data) { struct route_map_dep_data *dep_data = bucket->data; char *rmap_name = dep_data->rname; char *dep_name = data; zlog_debug("%s: Dependency for %s: %s", __FUNCTION__, dep_name, rmap_name); } static int route_map_dep_update(struct hash *dephash, const char *dep_name, const char *rmap_name, route_map_event_t type) { struct route_map_dep *dep = NULL; char *dname, *rname; int ret = 0; struct route_map_dep_data *dep_data = NULL, *ret_dep_data = NULL; struct route_map_dep_data tmp_dep_data; dname = XSTRDUP(MTYPE_ROUTE_MAP_NAME, dep_name); rname = XSTRDUP(MTYPE_ROUTE_MAP_NAME, rmap_name); switch (type) { case RMAP_EVENT_PLIST_ADDED: case RMAP_EVENT_CLIST_ADDED: case RMAP_EVENT_ECLIST_ADDED: case RMAP_EVENT_ASLIST_ADDED: case RMAP_EVENT_LLIST_ADDED: case RMAP_EVENT_CALL_ADDED: case RMAP_EVENT_FILTER_ADDED: if (rmap_debug) zlog_debug("Adding dependency for filter %s in route-map %s", dep_name, rmap_name); dep = (struct route_map_dep *)hash_get( dephash, dname, route_map_dep_hash_alloc); if (!dep) { ret = -1; goto out; } if (!dep->this_hash) dep->this_hash = dephash; memset(&tmp_dep_data, 0, sizeof(struct route_map_dep_data)); tmp_dep_data.rname = rname; dep_data = hash_lookup(dep->dep_rmap_hash, &tmp_dep_data); if (!dep_data) dep_data = hash_get(dep->dep_rmap_hash, &tmp_dep_data, route_map_name_hash_alloc); dep_data->refcnt++; break; case RMAP_EVENT_PLIST_DELETED: case RMAP_EVENT_CLIST_DELETED: case RMAP_EVENT_ECLIST_DELETED: case RMAP_EVENT_ASLIST_DELETED: case RMAP_EVENT_LLIST_DELETED: case RMAP_EVENT_CALL_DELETED: case RMAP_EVENT_FILTER_DELETED: if (rmap_debug) zlog_debug("Deleting dependency for filter %s in route-map %s", dep_name, rmap_name); dep = (struct route_map_dep *)hash_get(dephash, dname, NULL); if (!dep) { goto out; } memset(&tmp_dep_data, 0, sizeof(struct route_map_dep_data)); tmp_dep_data.rname = rname; dep_data = hash_lookup(dep->dep_rmap_hash, &tmp_dep_data); dep_data->refcnt--; if (!dep_data->refcnt) { ret_dep_data = hash_release(dep->dep_rmap_hash, &tmp_dep_data); if (ret_dep_data) { XFREE(MTYPE_ROUTE_MAP_NAME, ret_dep_data->rname); XFREE(MTYPE_ROUTE_MAP_DEP_DATA, ret_dep_data); } } if (!dep->dep_rmap_hash->count) { dep = hash_release(dephash, dname); hash_free(dep->dep_rmap_hash); XFREE(MTYPE_ROUTE_MAP_NAME, dep->dep_name); XFREE(MTYPE_ROUTE_MAP_DEP, dep); dep = NULL; } break; case RMAP_EVENT_SET_ADDED: case RMAP_EVENT_SET_DELETED: case RMAP_EVENT_SET_REPLACED: case RMAP_EVENT_MATCH_ADDED: case RMAP_EVENT_MATCH_DELETED: case RMAP_EVENT_MATCH_REPLACED: case RMAP_EVENT_INDEX_ADDED: case RMAP_EVENT_INDEX_DELETED: break; } if (dep) { if (rmap_debug) hash_iterate(dep->dep_rmap_hash, route_map_print_dependency, dname); } out: XFREE(MTYPE_ROUTE_MAP_NAME, rname); XFREE(MTYPE_ROUTE_MAP_NAME, dname); return ret; } static struct hash *route_map_get_dep_hash(route_map_event_t event) { struct hash *upd8_hash = NULL; switch (event) { case RMAP_EVENT_PLIST_ADDED: case RMAP_EVENT_PLIST_DELETED: upd8_hash = route_map_dep_hash[ROUTE_MAP_DEP_PLIST]; break; case RMAP_EVENT_CLIST_ADDED: case RMAP_EVENT_CLIST_DELETED: upd8_hash = route_map_dep_hash[ROUTE_MAP_DEP_CLIST]; break; case RMAP_EVENT_ECLIST_ADDED: case RMAP_EVENT_ECLIST_DELETED: upd8_hash = route_map_dep_hash[ROUTE_MAP_DEP_ECLIST]; break; case RMAP_EVENT_ASLIST_ADDED: case RMAP_EVENT_ASLIST_DELETED: upd8_hash = route_map_dep_hash[ROUTE_MAP_DEP_ASPATH]; break; case RMAP_EVENT_LLIST_ADDED: case RMAP_EVENT_LLIST_DELETED: upd8_hash = route_map_dep_hash[ROUTE_MAP_DEP_LCLIST]; break; case RMAP_EVENT_CALL_ADDED: case RMAP_EVENT_CALL_DELETED: case RMAP_EVENT_MATCH_ADDED: case RMAP_EVENT_MATCH_DELETED: upd8_hash = route_map_dep_hash[ROUTE_MAP_DEP_RMAP]; break; case RMAP_EVENT_FILTER_ADDED: case RMAP_EVENT_FILTER_DELETED: upd8_hash = route_map_dep_hash[ROUTE_MAP_DEP_FILTER]; break; /* * Should we actually be ignoring these? * I am not sure but at this point in time, let * us get them into this switch and we can peel * them into the appropriate place in the future */ case RMAP_EVENT_SET_ADDED: case RMAP_EVENT_SET_DELETED: case RMAP_EVENT_SET_REPLACED: case RMAP_EVENT_MATCH_REPLACED: case RMAP_EVENT_INDEX_ADDED: case RMAP_EVENT_INDEX_DELETED: upd8_hash = NULL; break; } return (upd8_hash); } static void route_map_process_dependency(struct hash_bucket *bucket, void *data) { struct route_map_dep_data *dep_data = NULL; char *rmap_name = NULL; dep_data = bucket->data; rmap_name = dep_data->rname; if (rmap_debug) zlog_debug("Notifying %s of dependency", rmap_name); if (route_map_master.event_hook) (*route_map_master.event_hook)(rmap_name); } void route_map_upd8_dependency(route_map_event_t type, const char *arg, const char *rmap_name) { struct hash *upd8_hash = NULL; if ((upd8_hash = route_map_get_dep_hash(type))) { route_map_dep_update(upd8_hash, arg, rmap_name, type); if (type == RMAP_EVENT_CALL_ADDED) { /* Execute hook. */ if (route_map_master.add_hook) (*route_map_master.add_hook)(rmap_name); } else if (type == RMAP_EVENT_CALL_DELETED) { /* Execute hook. */ if (route_map_master.delete_hook) (*route_map_master.delete_hook)(rmap_name); } } } void route_map_notify_dependencies(const char *affected_name, route_map_event_t event) { struct route_map_dep *dep; struct hash *upd8_hash; char *name; if (!affected_name) return; name = XSTRDUP(MTYPE_ROUTE_MAP_NAME, affected_name); if ((upd8_hash = route_map_get_dep_hash(event)) == NULL) { XFREE(MTYPE_ROUTE_MAP_NAME, name); return; } dep = (struct route_map_dep *)hash_get(upd8_hash, name, NULL); if (dep) { if (!dep->this_hash) dep->this_hash = upd8_hash; if (rmap_debug) zlog_debug("Filter %s updated", dep->dep_name); hash_iterate(dep->dep_rmap_hash, route_map_process_dependency, (void *)event); } XFREE(MTYPE_ROUTE_MAP_NAME, name); } /* VTY related functions. */ DEFUN (match_interface, match_interface_cmd, "match interface WORD", MATCH_STR "match first hop interface of route\n" "Interface name\n") { int idx_word = 2; VTY_DECLVAR_CONTEXT(route_map_index, index); if (rmap_match_set_hook.match_interface) return rmap_match_set_hook.match_interface( vty, index, "interface", argv[idx_word]->arg, RMAP_EVENT_MATCH_ADDED); return CMD_SUCCESS; } DEFUN (no_match_interface, no_match_interface_cmd, "no match interface [WORD]", NO_STR MATCH_STR "Match first hop interface of route\n" "Interface name\n") { char *iface = (argc == 4) ? argv[3]->arg : NULL; VTY_DECLVAR_CONTEXT(route_map_index, index); if (rmap_match_set_hook.no_match_interface) return rmap_match_set_hook.no_match_interface( vty, index, "interface", iface, RMAP_EVENT_MATCH_DELETED); return CMD_SUCCESS; } DEFUN (match_ip_address, match_ip_address_cmd, "match ip address <(1-199)|(1300-2699)|WORD>", MATCH_STR IP_STR "Match address of route\n" "IP access-list number\n" "IP access-list number (expanded range)\n" "IP Access-list name\n") { int idx_acl = 3; VTY_DECLVAR_CONTEXT(route_map_index, index); if (rmap_match_set_hook.match_ip_address) return rmap_match_set_hook.match_ip_address( vty, index, "ip address", argv[idx_acl]->arg, RMAP_EVENT_FILTER_ADDED); return CMD_SUCCESS; } DEFUN (no_match_ip_address, no_match_ip_address_cmd, "no match ip address [<(1-199)|(1300-2699)|WORD>]", NO_STR MATCH_STR IP_STR "Match address of route\n" "IP access-list number\n" "IP access-list number (expanded range)\n" "IP Access-list name\n") { int idx_word = 4; VTY_DECLVAR_CONTEXT(route_map_index, index); if (rmap_match_set_hook.no_match_ip_address) { if (argc <= idx_word) return rmap_match_set_hook.no_match_ip_address( vty, index, "ip address", NULL, RMAP_EVENT_FILTER_DELETED); return rmap_match_set_hook.no_match_ip_address( vty, index, "ip address", argv[idx_word]->arg, RMAP_EVENT_FILTER_DELETED); } return CMD_SUCCESS; } DEFUN (match_ip_address_prefix_list, match_ip_address_prefix_list_cmd, "match ip address prefix-list WORD", MATCH_STR IP_STR "Match address of route\n" "Match entries of prefix-lists\n" "IP prefix-list name\n") { int idx_word = 4; VTY_DECLVAR_CONTEXT(route_map_index, index); if (rmap_match_set_hook.match_ip_address_prefix_list) return rmap_match_set_hook.match_ip_address_prefix_list( vty, index, "ip address prefix-list", argv[idx_word]->arg, RMAP_EVENT_PLIST_ADDED); return CMD_SUCCESS; } DEFUN (no_match_ip_address_prefix_list, no_match_ip_address_prefix_list_cmd, "no match ip address prefix-list [WORD]", NO_STR MATCH_STR IP_STR "Match address of route\n" "Match entries of prefix-lists\n" "IP prefix-list name\n") { int idx_word = 5; VTY_DECLVAR_CONTEXT(route_map_index, index); if (rmap_match_set_hook.no_match_ip_address_prefix_list) { if (argc <= idx_word) return rmap_match_set_hook .no_match_ip_address_prefix_list( vty, index, "ip address prefix-list", NULL, RMAP_EVENT_PLIST_DELETED); return rmap_match_set_hook.no_match_ip_address_prefix_list( vty, index, "ip address prefix-list", argv[idx_word]->arg, RMAP_EVENT_PLIST_DELETED); } return CMD_SUCCESS; } DEFUN (match_ip_next_hop, match_ip_next_hop_cmd, "match ip next-hop <(1-199)|(1300-2699)|WORD>", MATCH_STR IP_STR "Match next-hop address of route\n" "IP access-list number\n" "IP access-list number (expanded range)\n" "IP Access-list name\n") { int idx_acl = 3; VTY_DECLVAR_CONTEXT(route_map_index, index); if (rmap_match_set_hook.match_ip_next_hop) return rmap_match_set_hook.match_ip_next_hop( vty, index, "ip next-hop", argv[idx_acl]->arg, RMAP_EVENT_FILTER_ADDED); return CMD_SUCCESS; } DEFUN (no_match_ip_next_hop, no_match_ip_next_hop_cmd, "no match ip next-hop [<(1-199)|(1300-2699)|WORD>]", NO_STR MATCH_STR IP_STR "Match next-hop address of route\n" "IP access-list number\n" "IP access-list number (expanded range)\n" "IP Access-list name\n") { int idx_word = 4; VTY_DECLVAR_CONTEXT(route_map_index, index); if (rmap_match_set_hook.no_match_ip_next_hop) { if (argc <= idx_word) return rmap_match_set_hook.no_match_ip_next_hop( vty, index, "ip next-hop", NULL, RMAP_EVENT_FILTER_DELETED); return rmap_match_set_hook.no_match_ip_next_hop( vty, index, "ip next-hop", argv[idx_word]->arg, RMAP_EVENT_FILTER_DELETED); } return CMD_SUCCESS; } DEFUN (match_ip_next_hop_prefix_list, match_ip_next_hop_prefix_list_cmd, "match ip next-hop prefix-list WORD", MATCH_STR IP_STR "Match next-hop address of route\n" "Match entries of prefix-lists\n" "IP prefix-list name\n") { int idx_word = 4; VTY_DECLVAR_CONTEXT(route_map_index, index); if (rmap_match_set_hook.match_ip_next_hop_prefix_list) return rmap_match_set_hook.match_ip_next_hop_prefix_list( vty, index, "ip next-hop prefix-list", argv[idx_word]->arg, RMAP_EVENT_PLIST_ADDED); return CMD_SUCCESS; } DEFUN (no_match_ip_next_hop_prefix_list, no_match_ip_next_hop_prefix_list_cmd, "no match ip next-hop prefix-list [WORD]", NO_STR MATCH_STR IP_STR "Match next-hop address of route\n" "Match entries of prefix-lists\n" "IP prefix-list name\n") { int idx_word = 5; VTY_DECLVAR_CONTEXT(route_map_index, index); if (rmap_match_set_hook.no_match_ip_next_hop) { if (argc <= idx_word) return rmap_match_set_hook.no_match_ip_next_hop( vty, index, "ip next-hop prefix-list", NULL, RMAP_EVENT_PLIST_DELETED); return rmap_match_set_hook.no_match_ip_next_hop( vty, index, "ip next-hop prefix-list", argv[idx_word]->arg, RMAP_EVENT_PLIST_DELETED); } return CMD_SUCCESS; } DEFUN(match_ip_next_hop_type, match_ip_next_hop_type_cmd, "match ip next-hop type ", MATCH_STR IP_STR "Match next-hop address of route\n" "Match entries by type\n" "Blackhole\n") { int idx_word = 4; VTY_DECLVAR_CONTEXT(route_map_index, index); if (rmap_match_set_hook.match_ip_next_hop_type) return rmap_match_set_hook.match_ip_next_hop_type( vty, index, "ip next-hop type", argv[idx_word]->arg, RMAP_EVENT_MATCH_ADDED); return CMD_SUCCESS; } DEFUN(no_match_ip_next_hop_type, no_match_ip_next_hop_type_cmd, "no match ip next-hop type []", NO_STR MATCH_STR IP_STR "Match next-hop address of route\n" "Match entries by type\n" "Blackhole\n") { int idx_word = 5; VTY_DECLVAR_CONTEXT(route_map_index, index); if (rmap_match_set_hook.no_match_ip_next_hop) { if (argc <= idx_word) return rmap_match_set_hook.no_match_ip_next_hop( vty, index, "ip next-hop type", NULL, RMAP_EVENT_MATCH_DELETED); return rmap_match_set_hook.no_match_ip_next_hop( vty, index, "ip next-hop type", argv[idx_word]->arg, RMAP_EVENT_MATCH_DELETED); } return CMD_SUCCESS; } DEFUN (match_ipv6_address, match_ipv6_address_cmd, "match ipv6 address WORD", MATCH_STR IPV6_STR "Match IPv6 address of route\n" "IPv6 access-list name\n") { int idx_word = 3; VTY_DECLVAR_CONTEXT(route_map_index, index); if (rmap_match_set_hook.match_ipv6_address) return rmap_match_set_hook.match_ipv6_address( vty, index, "ipv6 address", argv[idx_word]->arg, RMAP_EVENT_FILTER_ADDED); return CMD_SUCCESS; } DEFUN (no_match_ipv6_address, no_match_ipv6_address_cmd, "no match ipv6 address WORD", NO_STR MATCH_STR IPV6_STR "Match IPv6 address of route\n" "IPv6 access-list name\n") { int idx_word = 4; VTY_DECLVAR_CONTEXT(route_map_index, index); if (rmap_match_set_hook.no_match_ipv6_address) return rmap_match_set_hook.no_match_ipv6_address( vty, index, "ipv6 address", argv[idx_word]->arg, RMAP_EVENT_FILTER_DELETED); return CMD_SUCCESS; } DEFUN (match_ipv6_address_prefix_list, match_ipv6_address_prefix_list_cmd, "match ipv6 address prefix-list WORD", MATCH_STR IPV6_STR "Match address of route\n" "Match entries of prefix-lists\n" "IP prefix-list name\n") { int idx_word = 4; VTY_DECLVAR_CONTEXT(route_map_index, index); if (rmap_match_set_hook.match_ipv6_address_prefix_list) return rmap_match_set_hook.match_ipv6_address_prefix_list( vty, index, "ipv6 address prefix-list", argv[idx_word]->arg, RMAP_EVENT_PLIST_ADDED); return CMD_SUCCESS; } DEFUN (no_match_ipv6_address_prefix_list, no_match_ipv6_address_prefix_list_cmd, "no match ipv6 address prefix-list WORD", NO_STR MATCH_STR IPV6_STR "Match address of route\n" "Match entries of prefix-lists\n" "IP prefix-list name\n") { int idx_word = 5; VTY_DECLVAR_CONTEXT(route_map_index, index); if (rmap_match_set_hook.no_match_ipv6_address_prefix_list) return rmap_match_set_hook.no_match_ipv6_address_prefix_list( vty, index, "ipv6 address prefix-list", argv[idx_word]->arg, RMAP_EVENT_PLIST_DELETED); return CMD_SUCCESS; } DEFUN(match_ipv6_next_hop_type, match_ipv6_next_hop_type_cmd, "match ipv6 next-hop type ", MATCH_STR IPV6_STR "Match next-hop address of route\n" "Match entries by type\n" "Blackhole\n") { int idx_word = 4; VTY_DECLVAR_CONTEXT(route_map_index, index); if (rmap_match_set_hook.match_ipv6_next_hop_type) return rmap_match_set_hook.match_ipv6_next_hop_type( vty, index, "ipv6 next-hop type", argv[idx_word]->arg, RMAP_EVENT_MATCH_ADDED); return CMD_SUCCESS; } DEFUN(no_match_ipv6_next_hop_type, no_match_ipv6_next_hop_type_cmd, "no match ipv6 next-hop type []", NO_STR MATCH_STR IPV6_STR "Match address of route\n" "Match entries by type\n" "Blackhole\n") { int idx_word = 5; VTY_DECLVAR_CONTEXT(route_map_index, index); if (rmap_match_set_hook.no_match_ipv6_next_hop_type) return rmap_match_set_hook.no_match_ipv6_next_hop_type( vty, index, "ipv6 next-hop type", (argc <= idx_word) ? NULL : argv[idx_word]->arg, RMAP_EVENT_MATCH_DELETED); return CMD_SUCCESS; } DEFUN (match_metric, match_metric_cmd, "match metric (0-4294967295)", MATCH_STR "Match metric of route\n" "Metric value\n") { int idx_number = 2; VTY_DECLVAR_CONTEXT(route_map_index, index); if (rmap_match_set_hook.match_metric) return rmap_match_set_hook.match_metric(vty, index, "metric", argv[idx_number]->arg, RMAP_EVENT_MATCH_ADDED); return CMD_SUCCESS; } DEFUN (no_match_metric, no_match_metric_cmd, "no match metric [(0-4294967295)]", NO_STR MATCH_STR "Match metric of route\n" "Metric value\n") { int idx_number = 3; VTY_DECLVAR_CONTEXT(route_map_index, index); if (rmap_match_set_hook.no_match_metric) { if (argc <= idx_number) return rmap_match_set_hook.no_match_metric( vty, index, "metric", NULL, RMAP_EVENT_MATCH_DELETED); return rmap_match_set_hook.no_match_metric( vty, index, "metric", argv[idx_number]->arg, RMAP_EVENT_MATCH_DELETED); } return CMD_SUCCESS; } DEFUN (match_tag, match_tag_cmd, "match tag (1-4294967295)", MATCH_STR "Match tag of route\n" "Tag value\n") { int idx_number = 2; VTY_DECLVAR_CONTEXT(route_map_index, index); if (rmap_match_set_hook.match_tag) return rmap_match_set_hook.match_tag(vty, index, "tag", argv[idx_number]->arg, RMAP_EVENT_MATCH_ADDED); return CMD_SUCCESS; } DEFUN (no_match_tag, no_match_tag_cmd, "no match tag [(1-4294967295)]", NO_STR MATCH_STR "Match tag of route\n" "Tag value\n") { VTY_DECLVAR_CONTEXT(route_map_index, index); int idx = 0; char *arg = argv_find(argv, argc, "(1-4294967295)", &idx) ? argv[idx]->arg : NULL; if (rmap_match_set_hook.no_match_tag) return rmap_match_set_hook.no_match_tag( vty, index, "tag", arg, RMAP_EVENT_MATCH_DELETED); return CMD_SUCCESS; } DEFUN (set_ip_nexthop, set_ip_nexthop_cmd, "set ip next-hop A.B.C.D", SET_STR IP_STR "Next hop address\n" "IP address of next hop\n") { int idx_ipv4 = 3; union sockunion su; int ret; VTY_DECLVAR_CONTEXT(route_map_index, index); ret = str2sockunion(argv[idx_ipv4]->arg, &su); if (ret < 0) { vty_out(vty, "%% Malformed nexthop address\n"); return CMD_WARNING_CONFIG_FAILED; } if (su.sin.sin_addr.s_addr == 0 || IPV4_CLASS_DE(ntohl(su.sin.sin_addr.s_addr))) { vty_out(vty, "%% nexthop address cannot be 0.0.0.0, multicast or reserved\n"); return CMD_WARNING_CONFIG_FAILED; } if (rmap_match_set_hook.set_ip_nexthop) return rmap_match_set_hook.set_ip_nexthop( vty, index, "ip next-hop", argv[idx_ipv4]->arg); return CMD_SUCCESS; } DEFUN (no_set_ip_nexthop, no_set_ip_nexthop_cmd, "no set ip next-hop [A.B.C.D]", NO_STR SET_STR IP_STR "Next hop address\n" "IP address of next hop\n") { int idx = 0; VTY_DECLVAR_CONTEXT(route_map_index, index); const char *arg = NULL; if (argv_find(argv, argc, "A.B.C.D", &idx)) arg = argv[idx]->arg; if (rmap_match_set_hook.no_set_ip_nexthop) return rmap_match_set_hook.no_set_ip_nexthop( vty, index, "ip next-hop", arg); return CMD_SUCCESS; } DEFUN (set_ipv6_nexthop_local, set_ipv6_nexthop_local_cmd, "set ipv6 next-hop local X:X::X:X", SET_STR IPV6_STR "IPv6 next-hop address\n" "IPv6 local address\n" "IPv6 address of next hop\n") { int idx_ipv6 = 4; struct in6_addr addr; int ret; VTY_DECLVAR_CONTEXT(route_map_index, index); ret = inet_pton(AF_INET6, argv[idx_ipv6]->arg, &addr); if (!ret) { vty_out(vty, "%% Malformed nexthop address\n"); return CMD_WARNING_CONFIG_FAILED; } if (!IN6_IS_ADDR_LINKLOCAL(&addr)) { vty_out(vty, "%% Invalid link-local nexthop address\n"); return CMD_WARNING_CONFIG_FAILED; } if (rmap_match_set_hook.set_ipv6_nexthop_local) return rmap_match_set_hook.set_ipv6_nexthop_local( vty, index, "ipv6 next-hop local", argv[idx_ipv6]->arg); return CMD_SUCCESS; } DEFUN (no_set_ipv6_nexthop_local, no_set_ipv6_nexthop_local_cmd, "no set ipv6 next-hop local [X:X::X:X]", NO_STR SET_STR IPV6_STR "IPv6 next-hop address\n" "IPv6 local address\n" "IPv6 address of next hop\n") { int idx_ipv6 = 5; VTY_DECLVAR_CONTEXT(route_map_index, index); if (rmap_match_set_hook.no_set_ipv6_nexthop_local) { if (argc <= idx_ipv6) return rmap_match_set_hook.no_set_ipv6_nexthop_local( vty, index, "ipv6 next-hop local", NULL); return rmap_match_set_hook.no_set_ipv6_nexthop_local( vty, index, "ipv6 next-hop local", argv[5]->arg); } return CMD_SUCCESS; } DEFUN (set_metric, set_metric_cmd, "set metric <(0-4294967295)|rtt|+rtt|-rtt|+metric|-metric>", SET_STR "Metric value for destination routing protocol\n" "Metric value\n" "Assign round trip time\n" "Add round trip time\n" "Subtract round trip time\n" "Add metric\n" "Subtract metric\n") { int idx_number = 2; VTY_DECLVAR_CONTEXT(route_map_index, index); const char *pass = (argv[idx_number]->type == RANGE_TKN) ? argv[idx_number]->arg : argv[idx_number]->text; if (rmap_match_set_hook.set_metric) return rmap_match_set_hook.set_metric(vty, index, "metric", pass); return CMD_SUCCESS; } DEFUN (no_set_metric, no_set_metric_cmd, "no set metric [(0-4294967295)]", NO_STR SET_STR "Metric value for destination routing protocol\n" "Metric value\n") { int idx_number = 3; VTY_DECLVAR_CONTEXT(route_map_index, index); if (rmap_match_set_hook.no_set_metric) { if (argc <= idx_number) return rmap_match_set_hook.no_set_metric( vty, index, "metric", NULL); return rmap_match_set_hook.no_set_metric(vty, index, "metric", argv[idx_number]->arg); } return CMD_SUCCESS; } DEFUN (set_tag, set_tag_cmd, "set tag (1-4294967295)", SET_STR "Tag value for routing protocol\n" "Tag value\n") { VTY_DECLVAR_CONTEXT(route_map_index, index); int idx_number = 2; if (rmap_match_set_hook.set_tag) return rmap_match_set_hook.set_tag(vty, index, "tag", argv[idx_number]->arg); return CMD_SUCCESS; } DEFUN (no_set_tag, no_set_tag_cmd, "no set tag [(1-4294967295)]", NO_STR SET_STR "Tag value for routing protocol\n" "Tag value\n") { VTY_DECLVAR_CONTEXT(route_map_index, index); int idx_number = 3; if (rmap_match_set_hook.no_set_tag) { if (argc <= idx_number) return rmap_match_set_hook.no_set_tag(vty, index, "tag", NULL); return rmap_match_set_hook.no_set_tag(vty, index, "tag", argv[idx_number]->arg); } return CMD_SUCCESS; } DEFUN_NOSH (route_map, route_map_cmd, "route-map WORD (1-65535)", "Create route-map or enter route-map command mode\n" "Route map tag\n" "Route map denies set operations\n" "Route map permits set operations\n" "Sequence to insert to/delete from existing route-map entry\n") { int idx_word = 1; int idx_permit_deny = 2; int idx_number = 3; struct route_map *map; struct route_map_index *index; char *endptr = NULL; int permit = argv[idx_permit_deny]->arg[0] == 'p' ? RMAP_PERMIT : RMAP_DENY; unsigned long pref = strtoul(argv[idx_number]->arg, &endptr, 10); const char *mapname = argv[idx_word]->arg; /* Get route map. */ map = route_map_get(mapname); index = route_map_index_get(map, permit, pref); VTY_PUSH_CONTEXT(RMAP_NODE, index); return CMD_SUCCESS; } DEFUN (no_route_map_all, no_route_map_all_cmd, "no route-map WORD", NO_STR "Create route-map or enter route-map command mode\n" "Route map tag\n") { int idx_word = 2; const char *mapname = argv[idx_word]->arg; struct route_map *map; map = route_map_lookup_by_name(mapname); if (map == NULL) { vty_out(vty, "%% Could not find route-map %s\n", mapname); return CMD_WARNING_CONFIG_FAILED; } route_map_delete(map); return CMD_SUCCESS; } DEFUN (no_route_map, no_route_map_cmd, "no route-map WORD (1-65535)", NO_STR "Create route-map or enter route-map command mode\n" "Route map tag\n" "Route map denies set operations\n" "Route map permits set operations\n" "Sequence to insert to/delete from existing route-map entry\n") { int idx_word = 2; int idx_permit_deny = 3; int idx_number = 4; struct route_map *map; struct route_map_index *index; char *endptr = NULL; int permit = strmatch(argv[idx_permit_deny]->text, "permit") ? RMAP_PERMIT : RMAP_DENY; const char *prefstr = argv[idx_number]->arg; const char *mapname = argv[idx_word]->arg; unsigned long pref = strtoul(prefstr, &endptr, 10); /* Existence check. */ map = route_map_lookup_by_name(mapname); if (map == NULL) { vty_out(vty, "%% Could not find route-map %s\n", mapname); return CMD_WARNING_CONFIG_FAILED; } /* Lookup route map index. */ index = route_map_index_lookup(map, permit, pref); if (index == NULL) { vty_out(vty, "%% Could not find route-map entry %s %s\n", mapname, prefstr); return CMD_WARNING_CONFIG_FAILED; } /* Delete index from route map. */ route_map_index_delete(index, 1); /* If this route rule is the last one, delete route map itself. */ if (route_map_empty(map)) route_map_delete(map); return CMD_SUCCESS; } DEFUN (rmap_onmatch_next, rmap_onmatch_next_cmd, "on-match next", "Exit policy on matches\n" "Next clause\n") { struct route_map_index *index = VTY_GET_CONTEXT(route_map_index); if (index) { if (index->type == RMAP_DENY) { /* Under a deny clause, match means it's finished. No * need to set next */ vty_out(vty, "on-match next not supported under route-map deny\n"); return CMD_WARNING_CONFIG_FAILED; } index->exitpolicy = RMAP_NEXT; } return CMD_SUCCESS; } DEFUN (no_rmap_onmatch_next, no_rmap_onmatch_next_cmd, "no on-match next", NO_STR "Exit policy on matches\n" "Next clause\n") { struct route_map_index *index = VTY_GET_CONTEXT(route_map_index); if (index) index->exitpolicy = RMAP_EXIT; return CMD_SUCCESS; } DEFUN (rmap_onmatch_goto, rmap_onmatch_goto_cmd, "on-match goto (1-65535)", "Exit policy on matches\n" "Goto Clause number\n" "Number\n") { int idx = 0; char *num = argv_find(argv, argc, "(1-65535)", &idx) ? argv[idx]->arg : NULL; struct route_map_index *index = VTY_GET_CONTEXT(route_map_index); int d = 0; if (index) { if (index->type == RMAP_DENY) { /* Under a deny clause, match means it's finished. No * need to go anywhere */ vty_out(vty, "on-match goto not supported under route-map deny\n"); return CMD_WARNING_CONFIG_FAILED; } if (num) d = strtoul(num, NULL, 10); else d = index->pref + 1; if (d <= index->pref) { /* Can't allow you to do that, Dave */ vty_out(vty, "can't jump backwards in route-maps\n"); return CMD_WARNING_CONFIG_FAILED; } else { index->exitpolicy = RMAP_GOTO; index->nextpref = d; } } return CMD_SUCCESS; } DEFUN (no_rmap_onmatch_goto, no_rmap_onmatch_goto_cmd, "no on-match goto", NO_STR "Exit policy on matches\n" "Goto Clause number\n") { struct route_map_index *index = VTY_GET_CONTEXT(route_map_index); if (index) index->exitpolicy = RMAP_EXIT; return CMD_SUCCESS; } /* Cisco/GNU Zebra compatibility aliases */ /* ALIAS_FIXME */ DEFUN (rmap_continue, rmap_continue_cmd, "continue (1-65535)", "Continue on a different entry within the route-map\n" "Route-map entry sequence number\n") { return rmap_onmatch_goto(self, vty, argc, argv); } /* ALIAS_FIXME */ DEFUN (no_rmap_continue, no_rmap_continue_cmd, "no continue [(1-65535)]", NO_STR "Continue on a different entry within the route-map\n" "Route-map entry sequence number\n") { return no_rmap_onmatch_goto(self, vty, argc, argv); } static void clear_route_map_helper(struct route_map *map) { struct route_map_index *index; map->applied_clear = map->applied; for (index = map->head; index; index = index->next) index->applied_clear = index->applied; } DEFUN (rmap_clear_counters, rmap_clear_counters_cmd, "clear route-map counters [WORD]", CLEAR_STR "route-map information\n" "counters associated with the specified route-map\n" "route-map name\n") { int idx_word = 2; struct route_map *map; const char *name = (argc == 3 ) ? argv[idx_word]->arg : NULL; if (name) { map = route_map_lookup_by_name(name); if (map) clear_route_map_helper(map); else { vty_out(vty, "%s: 'route-map %s' not found\n", frr_protonameinst, name); return CMD_SUCCESS; } } else { for (map = route_map_master.head; map; map = map->next) clear_route_map_helper(map); } return CMD_SUCCESS; } DEFUN (rmap_show_name, rmap_show_name_cmd, "show route-map [WORD]", SHOW_STR "route-map information\n" "route-map name\n") { int idx_word = 2; const char *name = (argc == 3) ? argv[idx_word]->arg : NULL; return vty_show_route_map(vty, name); } DEFUN (rmap_show_unused, rmap_show_unused_cmd, "show route-map-unused", SHOW_STR "unused route-map information\n") { return vty_show_unused_route_map(vty); } DEFUN (rmap_call, rmap_call_cmd, "call WORD", "Jump to another Route-Map after match+set\n" "Target route-map name\n") { int idx_word = 1; struct route_map_index *index = VTY_GET_CONTEXT(route_map_index); const char *rmap = argv[idx_word]->arg; assert(index); /* If "call" is invoked with the same route-map name as * the one previously configured then, ignore the duplicate * configuration. */ if (index->nextrm && (strcmp(index->nextrm, rmap) == 0)) return CMD_SUCCESS; if (index->nextrm) { route_map_upd8_dependency(RMAP_EVENT_CALL_DELETED, index->nextrm, index->map->name); XFREE(MTYPE_ROUTE_MAP_NAME, index->nextrm); } index->nextrm = XSTRDUP(MTYPE_ROUTE_MAP_NAME, rmap); /* Execute event hook. */ route_map_upd8_dependency(RMAP_EVENT_CALL_ADDED, index->nextrm, index->map->name); return CMD_SUCCESS; } DEFUN (no_rmap_call, no_rmap_call_cmd, "no call", NO_STR "Jump to another Route-Map after match+set\n") { struct route_map_index *index = VTY_GET_CONTEXT(route_map_index); if (index->nextrm) { route_map_upd8_dependency(RMAP_EVENT_CALL_DELETED, index->nextrm, index->map->name); XFREE(MTYPE_ROUTE_MAP_NAME, index->nextrm); index->nextrm = NULL; } return CMD_SUCCESS; } DEFUN (rmap_description, rmap_description_cmd, "description LINE...", "Route-map comment\n" "Comment describing this route-map rule\n") { int idx_line = 1; struct route_map_index *index = VTY_GET_CONTEXT(route_map_index); if (index) { if (index->description) XFREE(MTYPE_TMP, index->description); index->description = argv_concat(argv, argc, idx_line); } return CMD_SUCCESS; } DEFUN (no_rmap_description, no_rmap_description_cmd, "no description", NO_STR "Route-map comment\n") { struct route_map_index *index = VTY_GET_CONTEXT(route_map_index); if (index) { if (index->description) XFREE(MTYPE_TMP, index->description); index->description = NULL; } return CMD_SUCCESS; } DEFUN (debug_rmap, debug_rmap_cmd, "debug route-map", DEBUG_STR "Debug option set for route-maps\n") { rmap_debug = true; return CMD_SUCCESS; } DEFUN (no_debug_rmap, no_debug_rmap_cmd, "no debug route-map", NO_STR DEBUG_STR "Debug option set for route-maps\n") { rmap_debug = false; return CMD_SUCCESS; } /* Debug node. */ static struct cmd_node rmap_debug_node = {RMAP_DEBUG_NODE, "", 1}; /* Configuration write function. */ static int route_map_config_write(struct vty *vty) { struct route_map *map; struct route_map_index *index; struct route_map_rule *rule; int first = 1; int write = 0; struct listnode *ln; struct list *maplist = list_new(); for (map = route_map_master.head; map; map = map->next) listnode_add(maplist, map); list_sort(maplist, sort_route_map); for (ALL_LIST_ELEMENTS_RO(maplist, ln, map)) for (index = map->head; index; index = index->next) { if (!first) vty_out(vty, "!\n"); else first = 0; vty_out(vty, "route-map %s %s %d\n", map->name, route_map_type_str(index->type), index->pref); if (index->description) vty_out(vty, " description %s\n", index->description); for (rule = index->match_list.head; rule; rule = rule->next) vty_out(vty, " match %s %s\n", rule->cmd->str, rule->rule_str ? rule->rule_str : ""); for (rule = index->set_list.head; rule; rule = rule->next) vty_out(vty, " set %s %s\n", rule->cmd->str, rule->rule_str ? rule->rule_str : ""); if (index->nextrm) vty_out(vty, " call %s\n", index->nextrm); if (index->exitpolicy == RMAP_GOTO) vty_out(vty, " on-match goto %d\n", index->nextpref); if (index->exitpolicy == RMAP_NEXT) vty_out(vty, " on-match next\n"); write++; } list_delete(&maplist); return write; } static int rmap_config_write_debug(struct vty *vty) { int write = 0; if (rmap_debug) { vty_out(vty, "debug route-map\n"); write++; } return write; } /* Route map node structure. */ static struct cmd_node rmap_node = {RMAP_NODE, "%s(config-route-map)# ", 1}; /* Common route map rules */ void *route_map_rule_tag_compile(const char *arg) { unsigned long int tmp; char *endptr; route_tag_t *tag; errno = 0; tmp = strtoul(arg, &endptr, 0); if (arg[0] == '\0' || *endptr != '\0' || errno || tmp > ROUTE_TAG_MAX) return NULL; tag = XMALLOC(MTYPE_ROUTE_MAP_COMPILED, sizeof(*tag)); *tag = tmp; return tag; } void route_map_rule_tag_free(void *rule) { XFREE(MTYPE_ROUTE_MAP_COMPILED, rule); } void route_map_finish(void) { int i; vector_free(route_match_vec); route_match_vec = NULL; vector_free(route_set_vec); route_set_vec = NULL; /* * All protocols are setting these to NULL * by default on shutdown( route_map_finish ) * Why are we making them do this work? */ route_map_master.add_hook = NULL; route_map_master.delete_hook = NULL; route_map_master.event_hook = NULL; /* cleanup route_map */ while (route_map_master.head) { struct route_map *map = route_map_master.head; map->to_be_processed = false; route_map_delete(map); } for (i = 1; i < ROUTE_MAP_DEP_MAX; i++) { hash_free(route_map_dep_hash[i]); route_map_dep_hash[i] = NULL; } hash_free(route_map_master_hash); route_map_master_hash = NULL; } static void rmap_autocomplete(vector comps, struct cmd_token *token) { struct route_map *map; for (map = route_map_master.head; map; map = map->next) vector_set(comps, XSTRDUP(MTYPE_COMPLETION, map->name)); } /* Increment the use_count counter while attaching the route map */ void route_map_counter_increment(struct route_map *map) { if (map) map->use_count++; } /* Decrement the use_count counter while detaching the route map. */ void route_map_counter_decrement(struct route_map *map) { if (map) { if (map->use_count <= 0) return; map->use_count--; } } static const struct cmd_variable_handler rmap_var_handlers[] = { {/* "route-map WORD" */ .varname = "route_map", .completions = rmap_autocomplete}, {.tokenname = "ROUTEMAP_NAME", .completions = rmap_autocomplete}, {.tokenname = "RMAP_NAME", .completions = rmap_autocomplete}, {.completions = NULL}}; /* Initialization of route map vector. */ void route_map_init(void) { int i; /* Make vector for match and set. */ route_match_vec = vector_init(1); route_set_vec = vector_init(1); route_map_master_hash = hash_create_size(8, route_map_hash_key_make, route_map_hash_cmp, "Route Map Master Hash"); for (i = 1; i < ROUTE_MAP_DEP_MAX; i++) route_map_dep_hash[i] = hash_create_size( 8, route_map_dep_hash_make_key, route_map_dep_hash_cmp, "Route Map Dep Hash"); cmd_variable_handler_register(rmap_var_handlers); rmap_debug = false; /* Install route map top node. */ install_node(&rmap_node, route_map_config_write); install_node(&rmap_debug_node, rmap_config_write_debug); /* Install route map commands. */ install_default(RMAP_NODE); install_element(CONFIG_NODE, &route_map_cmd); install_element(CONFIG_NODE, &no_route_map_cmd); install_element(CONFIG_NODE, &no_route_map_all_cmd); install_element(CONFIG_NODE, &debug_rmap_cmd); install_element(CONFIG_NODE, &no_debug_rmap_cmd); /* Install the on-match stuff */ install_element(RMAP_NODE, &route_map_cmd); install_element(RMAP_NODE, &rmap_onmatch_next_cmd); install_element(RMAP_NODE, &no_rmap_onmatch_next_cmd); install_element(RMAP_NODE, &rmap_onmatch_goto_cmd); install_element(RMAP_NODE, &no_rmap_onmatch_goto_cmd); install_element(RMAP_NODE, &rmap_continue_cmd); install_element(RMAP_NODE, &no_rmap_continue_cmd); /* Install the continue stuff (ALIAS of on-match). */ /* Install the call stuff. */ install_element(RMAP_NODE, &rmap_call_cmd); install_element(RMAP_NODE, &no_rmap_call_cmd); /* Install description commands. */ install_element(RMAP_NODE, &rmap_description_cmd); install_element(RMAP_NODE, &no_rmap_description_cmd); /* Install show command */ install_element(ENABLE_NODE, &rmap_clear_counters_cmd); install_element(ENABLE_NODE, &rmap_show_name_cmd); install_element(ENABLE_NODE, &rmap_show_unused_cmd); install_element(ENABLE_NODE, &debug_rmap_cmd); install_element(ENABLE_NODE, &no_debug_rmap_cmd); install_element(RMAP_NODE, &match_interface_cmd); install_element(RMAP_NODE, &no_match_interface_cmd); install_element(RMAP_NODE, &match_ip_address_cmd); install_element(RMAP_NODE, &no_match_ip_address_cmd); install_element(RMAP_NODE, &match_ip_address_prefix_list_cmd); install_element(RMAP_NODE, &no_match_ip_address_prefix_list_cmd); install_element(RMAP_NODE, &match_ip_next_hop_cmd); install_element(RMAP_NODE, &no_match_ip_next_hop_cmd); install_element(RMAP_NODE, &match_ip_next_hop_prefix_list_cmd); install_element(RMAP_NODE, &no_match_ip_next_hop_prefix_list_cmd); install_element(RMAP_NODE, &match_ip_next_hop_type_cmd); install_element(RMAP_NODE, &no_match_ip_next_hop_type_cmd); install_element(RMAP_NODE, &match_ipv6_address_cmd); install_element(RMAP_NODE, &no_match_ipv6_address_cmd); install_element(RMAP_NODE, &match_ipv6_address_prefix_list_cmd); install_element(RMAP_NODE, &no_match_ipv6_address_prefix_list_cmd); install_element(RMAP_NODE, &match_ipv6_next_hop_type_cmd); install_element(RMAP_NODE, &no_match_ipv6_next_hop_type_cmd); install_element(RMAP_NODE, &match_metric_cmd); install_element(RMAP_NODE, &no_match_metric_cmd); install_element(RMAP_NODE, &match_tag_cmd); install_element(RMAP_NODE, &no_match_tag_cmd); install_element(RMAP_NODE, &set_ip_nexthop_cmd); install_element(RMAP_NODE, &no_set_ip_nexthop_cmd); install_element(RMAP_NODE, &set_ipv6_nexthop_local_cmd); install_element(RMAP_NODE, &no_set_ipv6_nexthop_local_cmd); install_element(RMAP_NODE, &set_metric_cmd); install_element(RMAP_NODE, &no_set_metric_cmd); install_element(RMAP_NODE, &set_tag_cmd); install_element(RMAP_NODE, &no_set_tag_cmd); } frr-7.2.1/lib/routemap.h0000644000000000000000000003453313610377563011777 00000000000000/* Route map function. * Copyright (C) 1998 Kunihiro Ishiguro * * This file is part of GNU Zebra. * * GNU Zebra is free software; you can redistribute it and/or modify it * under the terms of the GNU General Public License as published by the * Free Software Foundation; either version 2, or (at your option) any * later version. * * GNU Zebra 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; see the file COPYING; if not, write to the Free Software * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA */ #ifndef _ZEBRA_ROUTEMAP_H #define _ZEBRA_ROUTEMAP_H #include "prefix.h" #include "memory.h" #include "qobj.h" #include "vty.h" #ifdef __cplusplus extern "C" { #endif DECLARE_MTYPE(ROUTE_MAP_NAME) DECLARE_MTYPE(ROUTE_MAP_RULE) DECLARE_MTYPE(ROUTE_MAP_COMPILED) /* Route map's type. */ enum route_map_type { RMAP_PERMIT, RMAP_DENY, RMAP_ANY }; typedef enum { RMAP_DENYMATCH, RMAP_PERMITMATCH } route_map_result_t; /* * Route-map match or set result "Eg: match evpn vni xx" * route-map match cmd always returns match/nomatch/noop * match--> found a match * nomatch--> didnt find a match * noop--> not applicable * route-map set retuns okay/error * okay --> set was successful * error --> set was not successful */ enum route_map_cmd_result_t { /* * route-map match cmd results */ RMAP_MATCH, RMAP_NOMATCH, RMAP_NOOP, /* * route-map set cmd results */ RMAP_OKAY, RMAP_ERROR }; typedef enum { RMAP_RIP, RMAP_RIPNG, RMAP_OSPF, RMAP_OSPF6, RMAP_BGP, RMAP_ZEBRA, RMAP_ISIS, } route_map_object_t; typedef enum { RMAP_EXIT, RMAP_GOTO, RMAP_NEXT } route_map_end_t; typedef enum { RMAP_EVENT_SET_ADDED, RMAP_EVENT_SET_DELETED, RMAP_EVENT_SET_REPLACED, RMAP_EVENT_MATCH_ADDED, RMAP_EVENT_MATCH_DELETED, RMAP_EVENT_MATCH_REPLACED, RMAP_EVENT_INDEX_ADDED, RMAP_EVENT_INDEX_DELETED, RMAP_EVENT_CALL_ADDED, /* call to another routemap added */ RMAP_EVENT_CALL_DELETED, RMAP_EVENT_PLIST_ADDED, RMAP_EVENT_PLIST_DELETED, RMAP_EVENT_CLIST_ADDED, RMAP_EVENT_CLIST_DELETED, RMAP_EVENT_ECLIST_ADDED, RMAP_EVENT_ECLIST_DELETED, RMAP_EVENT_LLIST_ADDED, RMAP_EVENT_LLIST_DELETED, RMAP_EVENT_ASLIST_ADDED, RMAP_EVENT_ASLIST_DELETED, RMAP_EVENT_FILTER_ADDED, RMAP_EVENT_FILTER_DELETED, } route_map_event_t; /* Depth limit in RMAP recursion using RMAP_CALL. */ #define RMAP_RECURSION_LIMIT 10 /* Route map rule structure for matching and setting. */ struct route_map_rule_cmd { /* Route map rule name (e.g. as-path, metric) */ const char *str; /* Function for value set or match. */ enum route_map_cmd_result_t (*func_apply)(void *rule, const struct prefix *prefix, route_map_object_t type, void *object); /* Compile argument and return result as void *. */ void *(*func_compile)(const char *); /* Free allocated value by func_compile (). */ void (*func_free)(void *); }; /* Route map apply error. */ enum rmap_compile_rets { RMAP_COMPILE_SUCCESS, /* Route map rule is missing. */ RMAP_RULE_MISSING, /* Route map rule can't compile */ RMAP_COMPILE_ERROR, /* Route map rule is duplicate */ RMAP_DUPLICATE_RULE }; /* Route map rule list. */ struct route_map_rule_list { struct route_map_rule *head; struct route_map_rule *tail; }; /* Route map index structure. */ struct route_map_index { struct route_map *map; char *description; /* Preference of this route map rule. */ int pref; /* Route map type permit or deny. */ enum route_map_type type; /* Do we follow old rules, or hop forward? */ route_map_end_t exitpolicy; /* If we're using "GOTO", to where do we go? */ int nextpref; /* If we're using "CALL", to which route-map do ew go? */ char *nextrm; /* Matching rule list. */ struct route_map_rule_list match_list; struct route_map_rule_list set_list; /* Make linked list. */ struct route_map_index *next; struct route_map_index *prev; /* Keep track how many times we've try to apply */ uint64_t applied; uint64_t applied_clear; QOBJ_FIELDS }; DECLARE_QOBJ_TYPE(route_map_index) /* Route map list structure. */ struct route_map { /* Name of route map. */ char *name; /* Route map's rule. */ struct route_map_index *head; struct route_map_index *tail; /* Make linked list. */ struct route_map *next; struct route_map *prev; /* Maintain update info */ bool to_be_processed; /* True if modification isn't acted on yet */ bool deleted; /* If 1, then this node will be deleted */ /* How many times have we applied this route-map */ uint64_t applied; uint64_t applied_clear; /* Counter to track active usage of this route-map */ uint16_t use_count; QOBJ_FIELDS }; DECLARE_QOBJ_TYPE(route_map) /* Prototypes. */ extern void route_map_init(void); /* * This should only be called on shutdown * Additionally this function sets the hooks to NULL * before any processing is done. */ extern void route_map_finish(void); /* Add match statement to route map. */ extern enum rmap_compile_rets route_map_add_match(struct route_map_index *index, const char *match_name, const char *match_arg, route_map_event_t type); /* Delete specified route match rule. */ extern enum rmap_compile_rets route_map_delete_match(struct route_map_index *index, const char *match_name, const char *match_arg); extern const char *route_map_get_match_arg(struct route_map_index *index, const char *match_name); /* Add route-map set statement to the route map. */ extern enum rmap_compile_rets route_map_add_set(struct route_map_index *index, const char *set_name, const char *set_arg); /* Delete route map set rule. */ extern enum rmap_compile_rets route_map_delete_set(struct route_map_index *index, const char *set_name, const char *set_arg); /* Install rule command to the match list. */ extern void route_map_install_match(struct route_map_rule_cmd *cmd); /* * Install rule command to the set list. * * When installing a particular item, Allow a difference of handling * of bad cli inputted(return NULL) -vs- this particular daemon cannot use * this form of the command(return a pointer and handle it appropriately * in the apply command). See 'set metric' command * as it is handled in ripd/ripngd and ospfd. */ extern void route_map_install_set(struct route_map_rule_cmd *cmd); /* Lookup route map by name. */ extern struct route_map *route_map_lookup_by_name(const char *name); /* Simple helper to warn if route-map does not exist. */ struct route_map *route_map_lookup_warn_noexist(struct vty *vty, const char *name); /* Apply route map to the object. */ extern route_map_result_t route_map_apply(struct route_map *map, const struct prefix *prefix, route_map_object_t object_type, void *object); extern void route_map_add_hook(void (*func)(const char *)); extern void route_map_delete_hook(void (*func)(const char *)); /* * This is the callback for when something has changed about a * route-map. The interested parties can register to receive * this data. * * name - Is the name of the changed route-map */ extern void route_map_event_hook(void (*func)(const char *name)); extern int route_map_mark_updated(const char *name); extern void route_map_walk_update_list(void (*update_fn)(char *name)); extern void route_map_upd8_dependency(route_map_event_t type, const char *arg, const char *rmap_name); extern void route_map_notify_dependencies(const char *affected_name, route_map_event_t event); extern int generic_match_add(struct vty *vty, struct route_map_index *index, const char *command, const char *arg, route_map_event_t type); extern int generic_match_delete(struct vty *vty, struct route_map_index *index, const char *command, const char *arg, route_map_event_t type); extern int generic_set_add(struct vty *vty, struct route_map_index *index, const char *command, const char *arg); extern int generic_set_delete(struct vty *vty, struct route_map_index *index, const char *command, const char *arg); /* match interface */ extern void route_map_match_interface_hook(int (*func)( struct vty *vty, struct route_map_index *index, const char *command, const char *arg, route_map_event_t type)); /* no match interface */ extern void route_map_no_match_interface_hook(int (*func)( struct vty *vty, struct route_map_index *index, const char *command, const char *arg, route_map_event_t type)); /* match ip address */ extern void route_map_match_ip_address_hook(int (*func)( struct vty *vty, struct route_map_index *index, const char *command, const char *arg, route_map_event_t type)); /* no match ip address */ extern void route_map_no_match_ip_address_hook(int (*func)( struct vty *vty, struct route_map_index *index, const char *command, const char *arg, route_map_event_t type)); /* match ip address prefix list */ extern void route_map_match_ip_address_prefix_list_hook(int (*func)( struct vty *vty, struct route_map_index *index, const char *command, const char *arg, route_map_event_t type)); /* no match ip address prefix list */ extern void route_map_no_match_ip_address_prefix_list_hook(int (*func)( struct vty *vty, struct route_map_index *index, const char *command, const char *arg, route_map_event_t type)); /* match ip next hop */ extern void route_map_match_ip_next_hop_hook(int (*func)( struct vty *vty, struct route_map_index *index, const char *command, const char *arg, route_map_event_t type)); /* no match ip next hop */ extern void route_map_no_match_ip_next_hop_hook(int (*func)( struct vty *vty, struct route_map_index *index, const char *command, const char *arg, route_map_event_t type)); /* match ip next hop prefix list */ extern void route_map_match_ip_next_hop_prefix_list_hook(int (*func)( struct vty *vty, struct route_map_index *index, const char *command, const char *arg, route_map_event_t type)); /* no match ip next hop prefix list */ extern void route_map_no_match_ip_next_hop_prefix_list_hook(int (*func)( struct vty *vty, struct route_map_index *index, const char *command, const char *arg, route_map_event_t type)); /* match ip next hop type */ extern void route_map_match_ip_next_hop_type_hook(int (*func)( struct vty *vty, struct route_map_index *index, const char *command, const char *arg, route_map_event_t type)); /* no match ip next hop type */ extern void route_map_no_match_ip_next_hop_type_hook(int (*func)( struct vty *vty, struct route_map_index *index, const char *command, const char *arg, route_map_event_t type)); /* match ipv6 address */ extern void route_map_match_ipv6_address_hook(int (*func)( struct vty *vty, struct route_map_index *index, const char *command, const char *arg, route_map_event_t type)); /* no match ipv6 address */ extern void route_map_no_match_ipv6_address_hook(int (*func)( struct vty *vty, struct route_map_index *index, const char *command, const char *arg, route_map_event_t type)); /* match ipv6 address prefix list */ extern void route_map_match_ipv6_address_prefix_list_hook(int (*func)( struct vty *vty, struct route_map_index *index, const char *command, const char *arg, route_map_event_t type)); /* no match ipv6 address prefix list */ extern void route_map_no_match_ipv6_address_prefix_list_hook(int (*func)( struct vty *vty, struct route_map_index *index, const char *command, const char *arg, route_map_event_t type)); /* match ipv6 next-hop type */ extern void route_map_match_ipv6_next_hop_type_hook(int (*func)( struct vty *vty, struct route_map_index *index, const char *command, const char *arg, route_map_event_t type)); /* no match ipv6 next-hop type */ extern void route_map_no_match_ipv6_next_hop_type_hook(int (*func)( struct vty *vty, struct route_map_index *index, const char *command, const char *arg, route_map_event_t type)); /* match metric */ extern void route_map_match_metric_hook(int (*func)( struct vty *vty, struct route_map_index *index, const char *command, const char *arg, route_map_event_t type)); /* no match metric */ extern void route_map_no_match_metric_hook(int (*func)( struct vty *vty, struct route_map_index *index, const char *command, const char *arg, route_map_event_t type)); /* match tag */ extern void route_map_match_tag_hook(int (*func)( struct vty *vty, struct route_map_index *index, const char *command, const char *arg, route_map_event_t type)); /* no match tag */ extern void route_map_no_match_tag_hook(int (*func)( struct vty *vty, struct route_map_index *index, const char *command, const char *arg, route_map_event_t type)); /* set ip nexthop */ extern void route_map_set_ip_nexthop_hook( int (*func)(struct vty *vty, struct route_map_index *index, const char *command, const char *arg)); /* no set ip nexthop */ extern void route_map_no_set_ip_nexthop_hook( int (*func)(struct vty *vty, struct route_map_index *index, const char *command, const char *arg)); /* set ipv6 nexthop local */ extern void route_map_set_ipv6_nexthop_local_hook( int (*func)(struct vty *vty, struct route_map_index *index, const char *command, const char *arg)); /* no set ipv6 nexthop local */ extern void route_map_no_set_ipv6_nexthop_local_hook( int (*func)(struct vty *vty, struct route_map_index *index, const char *command, const char *arg)); /* set metric */ extern void route_map_set_metric_hook(int (*func)(struct vty *vty, struct route_map_index *index, const char *command, const char *arg)); /* no set metric */ extern void route_map_no_set_metric_hook( int (*func)(struct vty *vty, struct route_map_index *index, const char *command, const char *arg)); /* set tag */ extern void route_map_set_tag_hook(int (*func)(struct vty *vty, struct route_map_index *index, const char *command, const char *arg)); /* no set tag */ extern void route_map_no_set_tag_hook(int (*func)(struct vty *vty, struct route_map_index *index, const char *command, const char *arg)); extern void *route_map_rule_tag_compile(const char *arg); extern void route_map_rule_tag_free(void *rule); /* Increment the route-map used counter */ extern void route_map_counter_increment(struct route_map *map); /* Decrement the route-map used counter */ extern void route_map_counter_decrement(struct route_map *map); #ifdef __cplusplus } #endif #endif /* _ZEBRA_ROUTEMAP_H */ frr-7.2.1/lib/sbuf.c0000644000000000000000000000453413610377563011073 00000000000000/* * Simple string buffer * * Copyright (C) 2017 Christian Franke * * This file is part of FRR. * * FRR is free software; you can redistribute it and/or modify it * under the terms of the GNU General Public License as published by the * Free Software Foundation; either version 2, or (at your option) any * later version. * * FRR 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 FRR; see the file COPYING. If not, write to the Free * Software Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA * 02111-1307, USA. */ #include #include "printfrr.h" #include "sbuf.h" #include "memory.h" void sbuf_init(struct sbuf *dest, char *buf, size_t size) { dest->fixed = (size > 0); if (dest->fixed) { dest->buf = buf; dest->size = size; } else { dest->buf = XMALLOC(MTYPE_TMP, 4096); dest->size = 4096; } dest->pos = 0; dest->buf[0] = '\0'; } void sbuf_reset(struct sbuf *dest) { dest->pos = 0; dest->buf[0] = '\0'; } const char *sbuf_buf(struct sbuf *buf) { return buf->buf; } void sbuf_free(struct sbuf *buf) { if (!buf->fixed) XFREE(MTYPE_TMP, buf->buf); } void sbuf_push(struct sbuf *buf, int indent, const char *format, ...) { va_list args; int written; if (!buf->fixed) { int written1, written2; size_t new_size; written1 = indent; va_start(args, format); written2 = vsnprintfrr(NULL, 0, format, args); va_end(args); new_size = buf->size; if (written1 >= 0 && written2 >= 0) { while (buf->pos + written1 + written2 >= new_size) new_size *= 2; if (new_size > buf->size) { buf->buf = XREALLOC(MTYPE_TMP, buf->buf, new_size); buf->size = new_size; } } } written = snprintf(buf->buf + buf->pos, buf->size - buf->pos, "%*s", indent, ""); if (written >= 0) buf->pos += written; if (buf->pos > buf->size) buf->pos = buf->size; va_start(args, format); written = vsnprintfrr(buf->buf + buf->pos, buf->size - buf->pos, format, args); va_end(args); if (written >= 0) buf->pos += written; if (buf->pos > buf->size) buf->pos = buf->size; if (buf->pos == buf->size) assert(!"Buffer filled up!"); } frr-7.2.1/lib/sbuf.h0000644000000000000000000000523713610377563011101 00000000000000/* * Simple string buffer * * Copyright (C) 2017 Christian Franke * * This file is part of FRR. * * FRR is free software; you can redistribute it and/or modify it * under the terms of the GNU General Public License as published by the * Free Software Foundation; either version 2, or (at your option) any * later version. * * FRR 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 FRR; see the file COPYING. If not, write to the Free * Software Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA * 02111-1307, USA. */ #ifndef SBUF_H #define SBUF_H #ifdef __cplusplus extern "C" { #endif /* * sbuf provides a simple string buffer. One application where this comes * in handy is the parsing of binary data: If there is an error in the parsing * process due to invalid input data, printing an error message explaining what * went wrong is definitely useful. However, just printing the actual error, * without any information about the previous parsing steps, is usually not very * helpful. * Using sbuf, the parser can log the whole parsing process into a buffer using * a printf like API. When an error ocurrs, all the information about previous * parsing steps is there in the log, without any need for backtracking, and can * be used to give a detailed and useful error description. * When parsing completes successfully without any error, the log can just be * discarded unless debugging is turned on, to not spam the log. * * For the described usecase, the code would look something like this: * * int sbuf_example(..., char **parser_log) * { * struct sbuf logbuf; * * sbuf_init(&logbuf, NULL, 0); * sbuf_push(&logbuf, 0, "Starting parser\n"); * * int rv = do_parse(&logbuf, ...); * * *parser_log = sbuf_buf(&logbuf); * * return 1; * } * * In this case, sbuf_example uses a string buffer with undefined size, which * will * be allocated on the heap by sbuf. The caller of sbuf_example is expected to * free * the string returned in parser_log. */ struct sbuf { bool fixed; char *buf; size_t size; size_t pos; int indent; }; void sbuf_init(struct sbuf *dest, char *buf, size_t size); void sbuf_reset(struct sbuf *buf); const char *sbuf_buf(struct sbuf *buf); void sbuf_free(struct sbuf *buf); #include "lib/log.h" void sbuf_push(struct sbuf *buf, int indent, const char *format, ...) PRINTFRR(3, 4); #ifdef __cplusplus } #endif #endif frr-7.2.1/lib/seqlock.c0000644000000000000000000001773113610377563011600 00000000000000/* * "Sequence" lock primitive * * Copyright (C) 2015 David Lamparter * * This library is free software; you can redistribute it and/or * modify it under the terms of the GNU Lesser General Public * License as published by the Free Software Foundation; either * version 2.1 of the License, or (at your option) any later version. * * This library 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 * Lesser General Public License for more details. * * You should have received a copy of the GNU Lesser General Public * License along with this library; if not, write to the * Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, * Boston, MA 02110-1301 USA */ #define _GNU_SOURCE #ifdef HAVE_CONFIG_H #include "config.h" #endif #include #include #include #include #include #include #include #include #include "seqlock.h" /**************************************** * OS specific synchronization wrappers * ****************************************/ /* * Linux: sys_futex() */ #ifdef HAVE_SYNC_LINUX_FUTEX #include #include static long sys_futex(void *addr1, int op, int val1, const struct timespec *timeout, void *addr2, int val3) { return syscall(SYS_futex, addr1, op, val1, timeout, addr2, val3); } #define wait_once(sqlo, val) \ sys_futex((int *)&sqlo->pos, FUTEX_WAIT, (int)val, NULL, NULL, 0) #define wait_time(sqlo, val, time, reltime) \ sys_futex((int *)&sqlo->pos, FUTEX_WAIT_BITSET, (int)val, time, \ NULL, ~0U) #define wait_poke(sqlo) \ sys_futex((int *)&sqlo->pos, FUTEX_WAKE, INT_MAX, NULL, NULL, 0) /* * OpenBSD: sys_futex(), almost the same as on Linux */ #elif defined(HAVE_SYNC_OPENBSD_FUTEX) #include #include #define TIME_RELATIVE 1 #define wait_once(sqlo, val) \ futex((int *)&sqlo->pos, FUTEX_WAIT, (int)val, NULL, NULL, 0) #define wait_time(sqlo, val, time, reltime) \ futex((int *)&sqlo->pos, FUTEX_WAIT, (int)val, reltime, NULL, 0) #define wait_poke(sqlo) \ futex((int *)&sqlo->pos, FUTEX_WAKE, INT_MAX, NULL, NULL, 0) /* * FreeBSD: _umtx_op() */ #elif defined(HAVE_SYNC_UMTX_OP) #include #define wait_once(sqlo, val) \ _umtx_op((void *)&sqlo->pos, UMTX_OP_WAIT_UINT, val, NULL, NULL) static int wait_time(struct seqlock *sqlo, uint32_t val, const struct timespec *abstime, const struct timespec *reltime) { struct _umtx_time t; t._flags = UMTX_ABSTIME; t._clockid = CLOCK_MONOTONIC; memcpy(&t._timeout, abstime, sizeof(t._timeout)); return _umtx_op((void *)&sqlo->pos, UMTX_OP_WAIT_UINT, val, (void *)(uintptr_t) sizeof(t), &t); } #define wait_poke(sqlo) \ _umtx_op((void *)&sqlo->pos, UMTX_OP_WAKE, INT_MAX, NULL, NULL) /* * generic version. used on NetBSD, Solaris and OSX. really shitty. */ #else #define TIME_ABS_REALTIME 1 #define wait_init(sqlo) do { \ pthread_mutex_init(&sqlo->lock, NULL); \ pthread_cond_init(&sqlo->wake, NULL); \ } while (0) #define wait_prep(sqlo) pthread_mutex_lock(&sqlo->lock) #define wait_once(sqlo, val) pthread_cond_wait(&sqlo->wake, &sqlo->lock) #define wait_time(sqlo, val, time, reltime) \ pthread_cond_timedwait(&sqlo->wake, \ &sqlo->lock, time); #define wait_done(sqlo) pthread_mutex_unlock(&sqlo->lock) #define wait_poke(sqlo) do { \ pthread_mutex_lock(&sqlo->lock); \ pthread_cond_broadcast(&sqlo->wake); \ pthread_mutex_unlock(&sqlo->lock); \ } while (0) #endif #ifndef wait_init #define wait_init(sqlo) /**/ #define wait_prep(sqlo) /**/ #define wait_done(sqlo) /**/ #endif /* wait_init */ void seqlock_wait(struct seqlock *sqlo, seqlock_val_t val) { seqlock_val_t cur, cal; seqlock_assert_valid(val); wait_prep(sqlo); cur = atomic_load_explicit(&sqlo->pos, memory_order_relaxed); while (cur & SEQLOCK_HELD) { cal = SEQLOCK_VAL(cur) - val - 1; assert(cal < 0x40000000 || cal > 0xc0000000); if (cal < 0x80000000) break; if ((cur & SEQLOCK_WAITERS) || atomic_compare_exchange_weak_explicit( &sqlo->pos, &cur, cur | SEQLOCK_WAITERS, memory_order_relaxed, memory_order_relaxed)) { wait_once(sqlo, cur | SEQLOCK_WAITERS); cur = atomic_load_explicit(&sqlo->pos, memory_order_relaxed); } /* else: we failed to swap in cur because it just changed */ } wait_done(sqlo); } bool seqlock_timedwait(struct seqlock *sqlo, seqlock_val_t val, const struct timespec *abs_monotime_limit) { /* * ABS_REALTIME - used on NetBSD, Solaris and OSX */ #if TIME_ABS_REALTIME #define time_arg1 &abs_rt #define time_arg2 NULL #define time_prep struct timespec curmono, abs_rt; clock_gettime(CLOCK_MONOTONIC, &curmono); clock_gettime(CLOCK_REALTIME, &abs_rt); abs_rt.tv_nsec += abs_monotime_limit->tv_nsec - curmono.tv_nsec; if (abs_rt.tv_nsec < 0) { abs_rt.tv_sec--; abs_rt.tv_nsec += 1000000000; } else if (abs_rt.tv_nsec >= 1000000000) { abs_rt.tv_sec++; abs_rt.tv_nsec -= 1000000000; } abs_rt.tv_sec += abs_monotime_limit->tv_sec - curmono.tv_sec; /* * RELATIVE - used on OpenBSD (might get a patch to get absolute monotime) */ #elif TIME_RELATIVE struct timespec reltime; #define time_arg1 abs_monotime_limit #define time_arg2 &reltime #define time_prep \ clock_gettime(CLOCK_MONOTONIC, &reltime); \ reltime.tv_sec = abs_monotime_limit.tv_sec - reltime.tv_sec; \ reltime.tv_nsec = abs_monotime_limit.tv_nsec - reltime.tv_nsec; \ if (reltime.tv_nsec < 0) { \ reltime.tv_sec--; \ reltime.tv_nsec += 1000000000; \ } /* * FreeBSD & Linux: absolute time re. CLOCK_MONOTONIC */ #else #define time_arg1 abs_monotime_limit #define time_arg2 NULL #define time_prep #endif bool ret = true; seqlock_val_t cur, cal; seqlock_assert_valid(val); wait_prep(sqlo); cur = atomic_load_explicit(&sqlo->pos, memory_order_relaxed); while (cur & SEQLOCK_HELD) { cal = SEQLOCK_VAL(cur) - val - 1; assert(cal < 0x40000000 || cal > 0xc0000000); if (cal < 0x80000000) break; if ((cur & SEQLOCK_WAITERS) || atomic_compare_exchange_weak_explicit( &sqlo->pos, &cur, cur | SEQLOCK_WAITERS, memory_order_relaxed, memory_order_relaxed)) { int rv; time_prep rv = wait_time(sqlo, cur | SEQLOCK_WAITERS, time_arg1, time_arg2); if (rv) { ret = false; break; } cur = atomic_load_explicit(&sqlo->pos, memory_order_relaxed); } } wait_done(sqlo); return ret; } bool seqlock_check(struct seqlock *sqlo, seqlock_val_t val) { seqlock_val_t cur; seqlock_assert_valid(val); cur = atomic_load_explicit(&sqlo->pos, memory_order_relaxed); if (!(cur & SEQLOCK_HELD)) return 1; cur = SEQLOCK_VAL(cur) - val - 1; assert(cur < 0x40000000 || cur > 0xc0000000); return cur < 0x80000000; } void seqlock_acquire_val(struct seqlock *sqlo, seqlock_val_t val) { seqlock_val_t prev; seqlock_assert_valid(val); prev = atomic_exchange_explicit(&sqlo->pos, val, memory_order_relaxed); if (prev & SEQLOCK_WAITERS) wait_poke(sqlo); } void seqlock_release(struct seqlock *sqlo) { seqlock_val_t prev; prev = atomic_exchange_explicit(&sqlo->pos, 0, memory_order_relaxed); if (prev & SEQLOCK_WAITERS) wait_poke(sqlo); } void seqlock_init(struct seqlock *sqlo) { sqlo->pos = 0; wait_init(sqlo); } seqlock_val_t seqlock_cur(struct seqlock *sqlo) { return SEQLOCK_VAL(atomic_load_explicit(&sqlo->pos, memory_order_relaxed)); } seqlock_val_t seqlock_bump(struct seqlock *sqlo) { seqlock_val_t val, cur; cur = atomic_load_explicit(&sqlo->pos, memory_order_relaxed); seqlock_assert_valid(cur); do { val = SEQLOCK_VAL(cur) + SEQLOCK_INCR; } while (!atomic_compare_exchange_weak_explicit(&sqlo->pos, &cur, val, memory_order_relaxed, memory_order_relaxed)); if (cur & SEQLOCK_WAITERS) wait_poke(sqlo); return val; } frr-7.2.1/lib/seqlock.h0000644000000000000000000001132713610377563011600 00000000000000/* * "Sequence" lock primitive * * Copyright (C) 2015 David Lamparter * * This library is free software; you can redistribute it and/or * modify it under the terms of the GNU Lesser General Public * License as published by the Free Software Foundation; either * version 2.1 of the License, or (at your option) any later version. * * This library 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 * Lesser General Public License for more details. * * You should have received a copy of the GNU Lesser General Public * License along with this library; if not, write to the * Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, * Boston, MA 02110-1301 USA */ #ifndef _SEQLOCK_H #define _SEQLOCK_H #include #include #include #include "frratomic.h" /* * this locking primitive is intended to use in a 1:N setup. * * - one "counter" seqlock issuing increasing numbers * - multiple seqlock users hold references on these numbers * * this is intended for implementing RCU reference-holding. There is one * global counter, with threads locking a seqlock whenever they take a * reference. A seqlock can also be idle/unlocked. * * The "counter" seqlock will always stay locked; the RCU cleanup thread * continuously counts it up, waiting for threads to release or progress to a * sequence number further ahead. If all threads are > N, references dropped * in N can be free'd. * * generally, the lock function is: * * Thread-A Thread-B * * seqlock_acquire(a) * | running seqlock_wait(b) -- a <= b * seqlock_release() | blocked * OR: seqlock_acquire(a') | -- a' > b * (resumes) */ /* use sequentially increasing "ticket numbers". lowest bit will always * be 1 to have a 'cleared' indication (i.e., counts 1,5,9,13,etc. ) * 2nd lowest bit is used to indicate we have waiters. */ typedef _Atomic uint32_t seqlock_ctr_t; typedef uint32_t seqlock_val_t; #define seqlock_assert_valid(val) assert((val) & SEQLOCK_HELD) /* NB: SEQLOCK_WAITERS is only allowed if SEQLOCK_HELD is also set; can't * have waiters on an unheld seqlock */ #define SEQLOCK_HELD (1U << 0) #define SEQLOCK_WAITERS (1U << 1) #define SEQLOCK_VAL(n) ((n) & ~SEQLOCK_WAITERS) #define SEQLOCK_STARTVAL 1U #define SEQLOCK_INCR 4U /* TODO: originally, this was using "atomic_fetch_add", which is the reason * bit 0 is used to indicate held state. With SEQLOCK_WAITERS added, there's * no fetch_add anymore (cmpxchg loop instead), so we don't need to use bit 0 * for this anymore & can just special-case the value 0 for it and skip it in * counting. */ struct seqlock { /* always used */ seqlock_ctr_t pos; /* used when futexes not available: (i.e. non-linux) */ pthread_mutex_t lock; pthread_cond_t wake; }; /* sqlo = 0 - init state: not held */ extern void seqlock_init(struct seqlock *sqlo); /* basically: "while (sqlo <= val) wait();" * returns when sqlo > val || !seqlock_held(sqlo) */ extern void seqlock_wait(struct seqlock *sqlo, seqlock_val_t val); /* same, but time-limited (limit is an absolute CLOCK_MONOTONIC value) */ extern bool seqlock_timedwait(struct seqlock *sqlo, seqlock_val_t val, const struct timespec *abs_monotime_limit); /* one-shot test, returns true if seqlock_wait would return immediately */ extern bool seqlock_check(struct seqlock *sqlo, seqlock_val_t val); static inline bool seqlock_held(struct seqlock *sqlo) { return !!atomic_load_explicit(&sqlo->pos, memory_order_relaxed); } /* sqlo - get seqlock position -- for the "counter" seqlock */ extern seqlock_val_t seqlock_cur(struct seqlock *sqlo); /* ++sqlo (but atomic & wakes waiters) - returns value that we bumped to. * * guarantees: * - each seqlock_bump call bumps the position by exactly one SEQLOCK_INCR. * There are no skipped/missed or multiple increments. * - each return value is only returned from one seqlock_bump() call */ extern seqlock_val_t seqlock_bump(struct seqlock *sqlo); /* sqlo = val - can be used on held seqlock. */ extern void seqlock_acquire_val(struct seqlock *sqlo, seqlock_val_t val); /* sqlo = ref - standard pattern: acquire relative to other seqlock */ static inline void seqlock_acquire(struct seqlock *sqlo, struct seqlock *ref) { seqlock_acquire_val(sqlo, seqlock_cur(ref)); } /* sqlo = 0 - set seqlock position to 0, marking as non-held */ extern void seqlock_release(struct seqlock *sqlo); /* release should normally be followed by a bump on the "counter", if * anything other than reading RCU items was done */ #endif /* _SEQLOCK_H */ frr-7.2.1/lib/sha256.c0000644000000000000000000002710613610377563011144 00000000000000/*- * Copyright 2005,2007,2009 Colin Percival * All rights reserved. * * Redistribution and use in source and binary forms, with or without * modification, are permitted provided that the following conditions * are met: * 1. Redistributions of source code must retain the above copyright * notice, this list of conditions and the following disclaimer. * 2. Redistributions in binary form must reproduce the above copyright * notice, this list of conditions and the following disclaimer in the * documentation and/or other materials provided with the distribution. * * THIS SOFTWARE IS PROVIDED BY THE AUTHOR AND CONTRIBUTORS ``AS IS'' AND * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE * ARE DISCLAIMED. IN NO EVENT SHALL THE AUTHOR OR CONTRIBUTORS BE LIABLE * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF * SUCH DAMAGE. */ #include #include "sha256.h" #if !HAVE_DECL_BE32DEC static inline uint32_t be32dec(const void *pp) { const uint8_t *p = (uint8_t const *)pp; return ((uint32_t)(p[3]) + ((uint32_t)(p[2]) << 8) + ((uint32_t)(p[1]) << 16) + ((uint32_t)(p[0]) << 24)); } #else #include #endif #if !HAVE_DECL_BE32ENC static inline void be32enc(void *pp, uint32_t x) { uint8_t *p = (uint8_t *)pp; p[3] = x & 0xff; p[2] = (x >> 8) & 0xff; p[1] = (x >> 16) & 0xff; p[0] = (x >> 24) & 0xff; } #else #include #endif /* * Encode a length len/4 vector of (uint32_t) into a length len vector of * (unsigned char) in big-endian form. Assumes len is a multiple of 4. */ static void be32enc_vect(unsigned char *dst, const uint32_t *src, size_t len) { size_t i; for (i = 0; i < len / 4; i++) be32enc(dst + i * 4, src[i]); } /* * Decode a big-endian length len vector of (unsigned char) into a length * len/4 vector of (uint32_t). Assumes len is a multiple of 4. */ static void be32dec_vect(uint32_t *dst, const unsigned char *src, size_t len) { size_t i; for (i = 0; i < len / 4; i++) dst[i] = be32dec(src + i * 4); } /* Elementary functions used by SHA256 */ #define Ch(x, y, z) ((x & (y ^ z)) ^ z) #define Maj(x, y, z) ((x & (y | z)) | (y & z)) #define SHR(x, n) (x >> n) #define ROTR(x, n) ((x >> n) | (x << (32 - n))) #define S0(x) (ROTR(x, 2) ^ ROTR(x, 13) ^ ROTR(x, 22)) #define S1(x) (ROTR(x, 6) ^ ROTR(x, 11) ^ ROTR(x, 25)) #define s0(x) (ROTR(x, 7) ^ ROTR(x, 18) ^ SHR(x, 3)) #define s1(x) (ROTR(x, 17) ^ ROTR(x, 19) ^ SHR(x, 10)) /* SHA256 round function */ #define RND(a, b, c, d, e, f, g, h, k) \ t0 = h + S1(e) + Ch(e, f, g) + k; \ t1 = S0(a) + Maj(a, b, c); \ d += t0; \ h = t0 + t1; /* Adjusted round function for rotating state */ #define RNDr(S, W, i, k) \ RND(S[(64 - i) % 8], S[(65 - i) % 8], S[(66 - i) % 8], \ S[(67 - i) % 8], S[(68 - i) % 8], S[(69 - i) % 8], \ S[(70 - i) % 8], S[(71 - i) % 8], W[i] + k) /* * SHA256 block compression function. The 256-bit state is transformed via * the 512-bit input block to produce a new state. */ static void SHA256_Transform(uint32_t *state, const unsigned char block[64]) { uint32_t W[64]; uint32_t S[8]; uint32_t t0, t1; int i; /* 1. Prepare message schedule W. */ be32dec_vect(W, block, 64); for (i = 16; i < 64; i++) W[i] = s1(W[i - 2]) + W[i - 7] + s0(W[i - 15]) + W[i - 16]; /* 2. Initialize working variables. */ memcpy(S, state, 32); /* 3. Mix. */ RNDr(S, W, 0, 0x428a2f98); RNDr(S, W, 1, 0x71374491); RNDr(S, W, 2, 0xb5c0fbcf); RNDr(S, W, 3, 0xe9b5dba5); RNDr(S, W, 4, 0x3956c25b); RNDr(S, W, 5, 0x59f111f1); RNDr(S, W, 6, 0x923f82a4); RNDr(S, W, 7, 0xab1c5ed5); RNDr(S, W, 8, 0xd807aa98); RNDr(S, W, 9, 0x12835b01); RNDr(S, W, 10, 0x243185be); RNDr(S, W, 11, 0x550c7dc3); RNDr(S, W, 12, 0x72be5d74); RNDr(S, W, 13, 0x80deb1fe); RNDr(S, W, 14, 0x9bdc06a7); RNDr(S, W, 15, 0xc19bf174); RNDr(S, W, 16, 0xe49b69c1); RNDr(S, W, 17, 0xefbe4786); RNDr(S, W, 18, 0x0fc19dc6); RNDr(S, W, 19, 0x240ca1cc); RNDr(S, W, 20, 0x2de92c6f); RNDr(S, W, 21, 0x4a7484aa); RNDr(S, W, 22, 0x5cb0a9dc); RNDr(S, W, 23, 0x76f988da); RNDr(S, W, 24, 0x983e5152); RNDr(S, W, 25, 0xa831c66d); RNDr(S, W, 26, 0xb00327c8); RNDr(S, W, 27, 0xbf597fc7); RNDr(S, W, 28, 0xc6e00bf3); RNDr(S, W, 29, 0xd5a79147); RNDr(S, W, 30, 0x06ca6351); RNDr(S, W, 31, 0x14292967); RNDr(S, W, 32, 0x27b70a85); RNDr(S, W, 33, 0x2e1b2138); RNDr(S, W, 34, 0x4d2c6dfc); RNDr(S, W, 35, 0x53380d13); RNDr(S, W, 36, 0x650a7354); RNDr(S, W, 37, 0x766a0abb); RNDr(S, W, 38, 0x81c2c92e); RNDr(S, W, 39, 0x92722c85); RNDr(S, W, 40, 0xa2bfe8a1); RNDr(S, W, 41, 0xa81a664b); RNDr(S, W, 42, 0xc24b8b70); RNDr(S, W, 43, 0xc76c51a3); RNDr(S, W, 44, 0xd192e819); RNDr(S, W, 45, 0xd6990624); RNDr(S, W, 46, 0xf40e3585); RNDr(S, W, 47, 0x106aa070); RNDr(S, W, 48, 0x19a4c116); RNDr(S, W, 49, 0x1e376c08); RNDr(S, W, 50, 0x2748774c); RNDr(S, W, 51, 0x34b0bcb5); RNDr(S, W, 52, 0x391c0cb3); RNDr(S, W, 53, 0x4ed8aa4a); RNDr(S, W, 54, 0x5b9cca4f); RNDr(S, W, 55, 0x682e6ff3); RNDr(S, W, 56, 0x748f82ee); RNDr(S, W, 57, 0x78a5636f); RNDr(S, W, 58, 0x84c87814); RNDr(S, W, 59, 0x8cc70208); RNDr(S, W, 60, 0x90befffa); RNDr(S, W, 61, 0xa4506ceb); RNDr(S, W, 62, 0xbef9a3f7); RNDr(S, W, 63, 0xc67178f2); /* 4. Mix local working variables into global state */ for (i = 0; i < 8; i++) state[i] += S[i]; /* Clean the stack. */ memset(W, 0, 256); memset(S, 0, 32); memset(&t0, 0, sizeof(t0)); memset(&t1, 0, sizeof(t0)); } static unsigned char PAD[64] = { 0x80, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0}; /* Add padding and terminating bit-count. */ static void SHA256_Pad(SHA256_CTX *ctx) { unsigned char len[8]; uint32_t r, plen; /* * Convert length to a vector of bytes -- we do this now rather * than later because the length will change after we pad. */ be32enc_vect(len, ctx->count, 8); /* Add 1--64 bytes so that the resulting length is 56 mod 64 */ r = (ctx->count[1] >> 3) & 0x3f; plen = (r < 56) ? (56 - r) : (120 - r); SHA256_Update(ctx, PAD, (size_t)plen); /* Add the terminating bit-count */ SHA256_Update(ctx, len, 8); } /* SHA-256 initialization. Begins a SHA-256 operation. */ void SHA256_Init(SHA256_CTX *ctx) { /* Zero bits processed so far */ ctx->count[0] = ctx->count[1] = 0; /* Magic initialization constants */ ctx->state[0] = 0x6A09E667; ctx->state[1] = 0xBB67AE85; ctx->state[2] = 0x3C6EF372; ctx->state[3] = 0xA54FF53A; ctx->state[4] = 0x510E527F; ctx->state[5] = 0x9B05688C; ctx->state[6] = 0x1F83D9AB; ctx->state[7] = 0x5BE0CD19; } /* Add bytes into the hash */ void SHA256_Update(SHA256_CTX *ctx, const void *in, size_t len) { uint32_t bitlen[2]; uint32_t r; const unsigned char *src = in; /* Number of bytes left in the buffer from previous updates */ r = (ctx->count[1] >> 3) & 0x3f; /* Convert the length into a number of bits */ bitlen[1] = ((uint32_t)len) << 3; bitlen[0] = (uint32_t)(len >> 29); /* Update number of bits */ if ((ctx->count[1] += bitlen[1]) < bitlen[1]) ctx->count[0]++; ctx->count[0] += bitlen[0]; /* Handle the case where we don't need to perform any transforms */ if (len < 64 - r) { memcpy(&ctx->buf[r], src, len); return; } /* Finish the current block */ memcpy(&ctx->buf[r], src, 64 - r); SHA256_Transform(ctx->state, ctx->buf); src += 64 - r; len -= 64 - r; /* Perform complete blocks */ while (len >= 64) { SHA256_Transform(ctx->state, src); src += 64; len -= 64; } /* Copy left over data into buffer */ memcpy(ctx->buf, src, len); } /* * SHA-256 finalization. Pads the input data, exports the hash value, * and clears the context state. */ void SHA256_Final(unsigned char digest[32], SHA256_CTX *ctx) { /* Add padding */ SHA256_Pad(ctx); /* Write the hash */ be32enc_vect(digest, ctx->state, 32); /* Clear the context state */ memset((void *)ctx, 0, sizeof(*ctx)); } /* Initialize an HMAC-SHA256 operation with the given key. */ void HMAC__SHA256_Init(HMAC_SHA256_CTX *ctx, const void *_K, size_t Klen) { unsigned char pad[64]; unsigned char khash[32]; const unsigned char *K = _K; size_t i; /* If Klen > 64, the key is really SHA256(K). */ if (Klen > 64) { SHA256_Init(&ctx->ictx); SHA256_Update(&ctx->ictx, K, Klen); SHA256_Final(khash, &ctx->ictx); K = khash; Klen = 32; } /* Inner SHA256 operation is SHA256(K xor [block of 0x36] || data). */ SHA256_Init(&ctx->ictx); memset(pad, 0x36, 64); for (i = 0; i < Klen; i++) pad[i] ^= K[i]; SHA256_Update(&ctx->ictx, pad, 64); /* Outer SHA256 operation is SHA256(K xor [block of 0x5c] || hash). */ SHA256_Init(&ctx->octx); memset(pad, 0x5c, 64); for (i = 0; i < Klen; i++) pad[i] ^= K[i]; SHA256_Update(&ctx->octx, pad, 64); /* Clean the stack. */ memset(khash, 0, 32); } /* Add bytes to the HMAC-SHA256 operation. */ void HMAC__SHA256_Update(HMAC_SHA256_CTX *ctx, const void *in, size_t len) { /* Feed data to the inner SHA256 operation. */ SHA256_Update(&ctx->ictx, in, len); } /* Finish an HMAC-SHA256 operation. */ void HMAC__SHA256_Final(unsigned char digest[32], HMAC_SHA256_CTX *ctx) { unsigned char ihash[32]; /* Finish the inner SHA256 operation. */ SHA256_Final(ihash, &ctx->ictx); /* Feed the inner hash to the outer SHA256 operation. */ SHA256_Update(&ctx->octx, ihash, 32); /* Finish the outer SHA256 operation. */ SHA256_Final(digest, &ctx->octx); /* Clean the stack. */ memset(ihash, 0, 32); } /** * PBKDF2_SHA256(passwd, passwdlen, salt, saltlen, c, buf, dkLen): * Compute PBKDF2(passwd, salt, c, dkLen) using HMAC-SHA256 as the PRF, and * write the output to buf. The value dkLen must be at most 32 * (2^32 - 1). */ void PBKDF2_SHA256(const uint8_t *passwd, size_t passwdlen, const uint8_t *salt, size_t saltlen, uint64_t c, uint8_t *buf, size_t dkLen) { HMAC_SHA256_CTX PShctx, hctx; size_t i; uint8_t ivec[4]; uint8_t U[32]; uint8_t T[32]; uint64_t j; int k; size_t clen; /* Compute HMAC state after processing P and S. */ HMAC__SHA256_Init(&PShctx, passwd, passwdlen); HMAC__SHA256_Update(&PShctx, salt, saltlen); /* Iterate through the blocks. */ for (i = 0; i * 32 < dkLen; i++) { /* Generate INT(i + 1). */ be32enc(ivec, (uint32_t)(i + 1)); /* Compute U_1 = PRF(P, S || INT(i)). */ memcpy(&hctx, &PShctx, sizeof(HMAC_SHA256_CTX)); HMAC__SHA256_Update(&hctx, ivec, 4); HMAC__SHA256_Final(U, &hctx); /* T_i = U_1 ... */ memcpy(T, U, 32); for (j = 2; j <= c; j++) { /* Compute U_j. */ HMAC__SHA256_Init(&hctx, passwd, passwdlen); HMAC__SHA256_Update(&hctx, U, 32); HMAC__SHA256_Final(U, &hctx); /* ... xor U_j ... */ for (k = 0; k < 32; k++) T[k] ^= U[k]; } /* Copy as many bytes as necessary into buf. */ clen = dkLen - i * 32; if (clen > 32) clen = 32; memcpy(&buf[i * 32], T, clen); } /* Clean PShctx, since we never called _Final on it. */ memset(&PShctx, 0, sizeof(HMAC_SHA256_CTX)); } frr-7.2.1/lib/sha256.h0000644000000000000000000000461313610377563011147 00000000000000/*- * Copyright 2005,2007,2009 Colin Percival * All rights reserved. * * Redistribution and use in source and binary forms, with or without * modification, are permitted provided that the following conditions * are met: * 1. Redistributions of source code must retain the above copyright * notice, this list of conditions and the following disclaimer. * 2. Redistributions in binary form must reproduce the above copyright * notice, this list of conditions and the following disclaimer in the * documentation and/or other materials provided with the distribution. * * THIS SOFTWARE IS PROVIDED BY THE AUTHOR AND CONTRIBUTORS ``AS IS'' AND * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE * ARE DISCLAIMED. IN NO EVENT SHALL THE AUTHOR OR CONTRIBUTORS BE LIABLE * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF * SUCH DAMAGE. * * $FreeBSD: src/lib/libmd/sha256.h,v 1.2 2006/01/17 15:35:56 phk Exp $ */ #ifndef _SHA256_H_ #define _SHA256_H_ #ifdef __cplusplus extern "C" { #endif typedef struct SHA256Context { uint32_t state[8]; uint32_t count[2]; unsigned char buf[64]; } SHA256_CTX; typedef struct HMAC_SHA256Context { SHA256_CTX ictx; SHA256_CTX octx; } HMAC_SHA256_CTX; void SHA256_Init(SHA256_CTX *); void SHA256_Update(SHA256_CTX *, const void *, size_t); void SHA256_Final(unsigned char[32], SHA256_CTX *); void HMAC__SHA256_Init(HMAC_SHA256_CTX *, const void *, size_t); void HMAC__SHA256_Update(HMAC_SHA256_CTX *, const void *, size_t); void HMAC__SHA256_Final(unsigned char[32], HMAC_SHA256_CTX *); /** * PBKDF2_SHA256(passwd, passwdlen, salt, saltlen, c, buf, dkLen): * Compute PBKDF2(passwd, salt, c, dkLen) using HMAC-SHA256 as the PRF, and * write the output to buf. The value dkLen must be at most 32 * (2^32 - 1). */ void PBKDF2_SHA256(const uint8_t *, size_t, const uint8_t *, size_t, uint64_t, uint8_t *, size_t); #ifdef __cplusplus } #endif #endif /* !_SHA256_H_ */ frr-7.2.1/lib/sigevent.c0000644000000000000000000001770013610377563011757 00000000000000/* Quagga signal handling functions. * Copyright (C) 2004 Paul Jakma, * * This file is part of Quagga. * * Quagga is free software; you can redistribute it and/or modify it * under the terms of the GNU General Public License as published by the * Free Software Foundation; either version 2, or (at your option) any * later version. * * Quagga 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; see the file COPYING; if not, write to the Free Software * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA */ #include #include #include #include #include #ifdef HAVE_UCONTEXT_H #ifdef GNU_LINUX /* get REG_EIP from ucontext.h */ #ifndef __USE_GNU #define __USE_GNU #endif /* __USE_GNU */ #endif /* GNU_LINUX */ #include #endif /* HAVE_UCONTEXT_H */ /* master signals descriptor struct */ static struct quagga_sigevent_master_t { struct thread *t; struct quagga_signal_t *signals; int sigc; volatile sig_atomic_t caught; } sigmaster; /* Generic signal handler * Schedules signal event thread */ static void quagga_signal_handler(int signo) { int i; struct quagga_signal_t *sig; for (i = 0; i < sigmaster.sigc; i++) { sig = &(sigmaster.signals[i]); if (sig->signal == signo) sig->caught = 1; } sigmaster.caught = 1; } /* check if signals have been caught and run appropriate handlers */ int quagga_sigevent_process(void) { struct quagga_signal_t *sig; int i; #ifdef SIGEVENT_BLOCK_SIGNALS /* shouldnt need to block signals, but potentially may be needed */ sigset_t newmask, oldmask; /* * Block most signals, but be careful not to defer SIGTRAP because * doing so breaks gdb, at least on NetBSD 2.0. Avoid asking to * block SIGKILL, just because we shouldn't be able to do so. */ sigfillset(&newmask); sigdelset(&newmask, SIGTRAP); sigdelset(&newmask, SIGKILL); if ((sigprocmask(SIG_BLOCK, &newmask, &oldmask)) < 0) { flog_err_sys(EC_LIB_SYSTEM_CALL, "quagga_signal_timer: couldnt block signals!"); return -1; } #endif /* SIGEVENT_BLOCK_SIGNALS */ if (sigmaster.caught > 0) { sigmaster.caught = 0; /* must not read or set sigmaster.caught after here, * race condition with per-sig caught flags if one does */ for (i = 0; i < sigmaster.sigc; i++) { sig = &(sigmaster.signals[i]); if (sig->caught > 0) { sig->caught = 0; if (sig->handler) sig->handler(); } } } #ifdef SIGEVENT_BLOCK_SIGNALS if (sigprocmask(SIG_UNBLOCK, &oldmask, NULL) < 0) ; return -1; #endif /* SIGEVENT_BLOCK_SIGNALS */ return 0; } #ifdef SIGEVENT_SCHEDULE_THREAD /* timer thread to check signals. Shouldnt be needed */ int quagga_signal_timer(struct thread *t) { struct quagga_sigevent_master_t *sigm; sigm = THREAD_ARG(t); sigm->t = NULL; thread_add_timer(sigm->t->master, quagga_signal_timer, &sigmaster, QUAGGA_SIGNAL_TIMER_INTERVAL, &sigm->t); return quagga_sigevent_process(); } #endif /* SIGEVENT_SCHEDULE_THREAD */ /* Initialization of signal handles. */ /* Signal wrapper. */ static int signal_set(int signo) { int ret; struct sigaction sig; struct sigaction osig; sig.sa_handler = &quagga_signal_handler; sigfillset(&sig.sa_mask); sig.sa_flags = 0; if (signo == SIGALRM) { #ifdef SA_INTERRUPT sig.sa_flags |= SA_INTERRUPT; /* SunOS */ #endif } else { #ifdef SA_RESTART sig.sa_flags |= SA_RESTART; #endif /* SA_RESTART */ } ret = sigaction(signo, &sig, &osig); if (ret < 0) return ret; else return 0; } /* XXX This function should be enhanced to support more platforms (it currently works only on Linux/x86). */ static void *program_counter(void *context) { #ifdef HAVE_UCONTEXT_H #ifdef GNU_LINUX /* these are from GNU libc, rather than Linux, strictly speaking */ #if defined(REG_EIP) # define REG_INDEX REG_EIP #elif defined(REG_RIP) # define REG_INDEX REG_RIP #elif defined(__powerpc__) # define REG_INDEX 32 #endif #elif defined(SUNOS_5) /* !GNU_LINUX */ # define REG_INDEX REG_PC #endif /* GNU_LINUX */ #ifdef REG_INDEX #ifdef HAVE_UCONTEXT_T_UC_MCONTEXT_GREGS # define REGS gregs[REG_INDEX] #elif defined(HAVE_UCONTEXT_T_UC_MCONTEXT_UC_REGS) # define REGS uc_regs->gregs[REG_INDEX] #endif /* HAVE_UCONTEXT_T_UC_MCONTEXT_GREGS */ #endif /* REG_INDEX */ #ifdef REGS if (context) return (void *)(((ucontext_t *)context)->uc_mcontext.REGS); #elif defined(HAVE_UCONTEXT_T_UC_MCONTEXT_REGS__NIP) /* older Linux / struct pt_regs ? */ if (context) return (void *)(((ucontext_t *)context)->uc_mcontext.regs->nip); #endif /* REGS */ #endif /* HAVE_UCONTEXT_H */ return NULL; } static void __attribute__((noreturn)) exit_handler(int signo, siginfo_t *siginfo, void *context) { void *pc = program_counter(context); zlog_signal(signo, "exiting...", siginfo, pc); _exit(128 + signo); } static void __attribute__((noreturn)) core_handler(int signo, siginfo_t *siginfo, void *context) { void *pc = program_counter(context); /* make sure we don't hang in here. default for SIGALRM is terminate. * - if we're in backtrace for more than a second, abort. */ struct sigaction sa_default = {.sa_handler = SIG_DFL}; sigaction(SIGALRM, &sa_default, NULL); sigset_t sigset; sigemptyset(&sigset); sigaddset(&sigset, SIGALRM); sigprocmask(SIG_UNBLOCK, &sigset, NULL); alarm(1); zlog_signal(signo, "aborting...", siginfo, pc); /* dump memory stats on core */ log_memstats(stderr, "core_handler"); abort(); } static void trap_default_signals(void) { static const int core_signals[] = { SIGQUIT, SIGILL, #ifdef SIGEMT SIGEMT, #endif SIGFPE, SIGBUS, SIGSEGV, #ifdef SIGSYS SIGSYS, #endif #ifdef SIGXCPU SIGXCPU, #endif #ifdef SIGXFSZ SIGXFSZ, #endif }; static const int exit_signals[] = { SIGHUP, SIGINT, SIGALRM, SIGTERM, SIGUSR1, SIGUSR2, #ifdef SIGPOLL SIGPOLL, #endif #ifdef SIGVTALRM SIGVTALRM, #endif #ifdef SIGSTKFLT SIGSTKFLT, #endif }; static const int ignore_signals[] = { SIGPIPE, }; static const struct { const int *sigs; unsigned int nsigs; void (*handler)(int signo, siginfo_t *info, void *context); } sigmap[] = { {core_signals, array_size(core_signals), core_handler}, {exit_signals, array_size(exit_signals), exit_handler}, {ignore_signals, array_size(ignore_signals), NULL}, }; unsigned int i; for (i = 0; i < array_size(sigmap); i++) { unsigned int j; for (j = 0; j < sigmap[i].nsigs; j++) { struct sigaction oact; if ((sigaction(sigmap[i].sigs[j], NULL, &oact) == 0) && (oact.sa_handler == SIG_DFL)) { struct sigaction act; sigfillset(&act.sa_mask); if (sigmap[i].handler == NULL) { act.sa_handler = SIG_IGN; act.sa_flags = 0; } else { /* Request extra arguments to signal * handler. */ act.sa_sigaction = sigmap[i].handler; act.sa_flags = SA_SIGINFO; #ifdef SA_RESETHAND /* don't try to print backtraces * recursively */ if (sigmap[i].handler == core_handler) act.sa_flags |= SA_RESETHAND; #endif } if (sigaction(sigmap[i].sigs[j], &act, NULL) < 0) flog_err( EC_LIB_SYSTEM_CALL, "Unable to set signal handler for signal %d: %s", sigmap[i].sigs[j], safe_strerror(errno)); } } } } void signal_init(struct thread_master *m, int sigc, struct quagga_signal_t signals[]) { int i = 0; struct quagga_signal_t *sig; /* First establish some default handlers that can be overridden by the application. */ trap_default_signals(); while (i < sigc) { sig = &signals[i]; if (signal_set(sig->signal) < 0) exit(-1); i++; } sigmaster.sigc = sigc; sigmaster.signals = signals; #ifdef SIGEVENT_SCHEDULE_THREAD sigmaster.t = NULL; thread_add_timer(m, quagga_signal_timer, &sigmaster, QUAGGA_SIGNAL_TIMER_INTERVAL, &sigmaster.t); #endif /* SIGEVENT_SCHEDULE_THREAD */ } frr-7.2.1/lib/sigevent.h0000644000000000000000000000316613610377563011765 00000000000000/* * Quagga Signal handling header. * * Copyright (C) 2004 Paul Jakma. * * This file is part of Quagga. * * Quagga is free software; you can redistribute it and/or modify it * under the terms of the GNU General Public License as published by the * Free Software Foundation; either version 2, or (at your option) any * later version. * * Quagga 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; see the file COPYING; if not, write to the Free Software * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA */ #ifndef _QUAGGA_SIGNAL_H #define _QUAGGA_SIGNAL_H #include #ifdef __cplusplus extern "C" { #endif #define QUAGGA_SIGNAL_TIMER_INTERVAL 2L struct quagga_signal_t { int signal; /* signal number */ void (*handler)(void); /* handler to call */ volatile sig_atomic_t caught; /* private member */ }; /* initialise sigevent system * takes: * - pointer to valid struct thread_master * - number of elements in passed in signals array * - array of quagga_signal_t's describing signals to handle * and handlers to use for each signal */ extern void signal_init(struct thread_master *m, int sigc, struct quagga_signal_t *signals); /* check whether there are signals to handle, process any found */ extern int quagga_sigevent_process(void); #ifdef __cplusplus } #endif #endif /* _QUAGGA_SIGNAL_H */ frr-7.2.1/lib/skiplist.c0000644000000000000000000003423413610377563011776 00000000000000/* * Copyright 1990 William Pugh * * Redistribution and use in source and binary forms, with or without * modification, are permitted. * * THIS SOFTWARE IS PROVIDED BY THE AUTHOR AND CONTRIBUTORS ``AS IS'' * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED * TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A * PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE AUTHOR OR * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF * USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND * ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, * OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT * OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF * SUCH DAMAGE. * * Permission to include in quagga provide on March 31, 2016 */ /* * Skip List impementation based on code from William Pugh. * ftp://ftp.cs.umd.edu/pub/skipLists/ * * Skip Lists are a probabilistic alternative to balanced trees, as * described in the June 1990 issue of CACM and were invented by * William Pugh in 1987. * * This file contains source code to implement a dictionary using * skip lists and a test driver to test the routines. * * A couple of comments about this implementation: * The routine randomLevel has been hard-coded to generate random * levels using p=0.25. It can be easily changed. * * The insertion routine has been implemented so as to use the * dirty hack described in the CACM paper: if a random level is * generated that is more than the current maximum level, the * current maximum level plus one is used instead. * * Levels start at zero and go up to MaxLevel (which is equal to * (MaxNumberOfLevels-1). * * The run-time flag SKIPLIST_FLAG_ALLOW_DUPLICATES determines whether or * not duplicates are allowed for a given list. If set, duplicates are * allowed and act in a FIFO manner. If not set, an insertion of a value * already in the list updates the previously existing binding. * * BitsInRandom is defined to be the number of bits returned by a call to * random(). For most all machines with 32-bit integers, this is 31 bits * as currently set. */ #include #include "memory.h" #include "log.h" #include "vty.h" #include "skiplist.h" #include "lib_errors.h" DEFINE_MTYPE_STATIC(LIB, SKIP_LIST, "Skip List") DEFINE_MTYPE_STATIC(LIB, SKIP_LIST_NODE, "Skip Node") #define BitsInRandom 31 #define MaxNumberOfLevels 16 #define MaxLevel (MaxNumberOfLevels-1) #define newNodeOfLevel(l) XCALLOC(MTYPE_SKIP_LIST_NODE, sizeof(struct skiplistnode)+(l)*sizeof(struct skiplistnode *)) static int randomsLeft; static int randomBits; static struct skiplist *skiplist_last_created; /* debugging hack */ #if 1 #define CHECKLAST(sl) \ do { \ if ((sl)->header->forward[0] && !(sl)->last) \ assert(0); \ if (!(sl)->header->forward[0] && (sl)->last) \ assert(0); \ } while (0) #else #define CHECKLAST(sl) #endif static int randomLevel(void) { register int level = 0; register int b; do { if (randomsLeft <= 0) { randomBits = random(); randomsLeft = BitsInRandom / 2; } b = randomBits & 3; randomBits >>= 2; --randomsLeft; if (!b) { level++; if (level >= MaxLevel) return MaxLevel; } } while (!b); return level; } static int default_cmp(void *key1, void *key2) { if (key1 < key2) return -1; if (key1 > key2) return 1; return 0; } unsigned int skiplist_count(struct skiplist *l) { return l->count; } struct skiplist *skiplist_new(int flags, int (*cmp)(void *key1, void *key2), void (*del)(void *val)) { struct skiplist *new; new = XCALLOC(MTYPE_SKIP_LIST, sizeof(struct skiplist)); assert(new); new->level = 0; new->count = 0; new->header = newNodeOfLevel(MaxNumberOfLevels); new->stats = newNodeOfLevel(MaxNumberOfLevels); new->flags = flags; if (cmp) new->cmp = cmp; else new->cmp = default_cmp; if (del) new->del = del; skiplist_last_created = new; /* debug */ return new; } void skiplist_free(struct skiplist *l) { register struct skiplistnode *p, *q; p = l->header; do { q = p->forward[0]; if (l->del && p != l->header) (*l->del)(p->value); XFREE(MTYPE_SKIP_LIST_NODE, p); p = q; } while (p); XFREE(MTYPE_SKIP_LIST_NODE, l->stats); XFREE(MTYPE_SKIP_LIST, l); } int skiplist_insert(register struct skiplist *l, register void *key, register void *value) { register int k; struct skiplistnode *update[MaxNumberOfLevels]; register struct skiplistnode *p, *q; CHECKLAST(l); /* DEBUG */ if (!key) { flog_err(EC_LIB_DEVELOPMENT, "%s: key is 0, value is %p", __func__, value); } p = l->header; k = l->level; do { while (q = p->forward[k], q && (*l->cmp)(q->key, key) < 0) p = q; update[k] = p; } while (--k >= 0); if (!(l->flags & SKIPLIST_FLAG_ALLOW_DUPLICATES) && q && ((*l->cmp)(q->key, key) == 0)) { return -1; } k = randomLevel(); assert(k >= 0); if (k > l->level) { k = ++l->level; update[k] = l->header; } q = newNodeOfLevel(k); q->key = key; q->value = value; #if SKIPLIST_0TIMER_DEBUG q->flags = SKIPLIST_NODE_FLAG_INSERTED; /* debug */ #endif ++(l->stats->forward[k]); #if SKIPLIST_DEBUG zlog_debug("%s: incremented stats @%p:%d, now %ld", __func__, l, k, l->stats->forward[k] - (struct skiplistnode *)NULL); #endif do { p = update[k]; q->forward[k] = p->forward[k]; p->forward[k] = q; } while (--k >= 0); /* * If this is the last item in the list, update the "last" pointer */ if (!q->forward[0]) { l->last = q; } ++(l->count); CHECKLAST(l); return 0; } int skiplist_delete(register struct skiplist *l, register void *key, register void *value) /* used only if duplicates allowed */ { register int k, m; struct skiplistnode *update[MaxNumberOfLevels]; register struct skiplistnode *p, *q; CHECKLAST(l); /* to make debugging easier */ for (k = 0; k < MaxNumberOfLevels; ++k) update[k] = NULL; p = l->header; k = m = l->level; do { while (q = p->forward[k], q && (*l->cmp)(q->key, key) < 0) p = q; update[k] = p; } while (--k >= 0); if (l->flags & SKIPLIST_FLAG_ALLOW_DUPLICATES) { while (q && ((*l->cmp)(q->key, key) == 0) && (q->value != value)) { int i; for (i = 0; i <= l->level; ++i) { if (update[i]->forward[i] == q) update[i] = q; } q = q->forward[0]; } } if (q && (*l->cmp)(q->key, key) == 0) { if (!(l->flags & SKIPLIST_FLAG_ALLOW_DUPLICATES) || (q->value == value)) { /* * found node to delete */ #if SKIPLIST_0TIMER_DEBUG q->flags &= ~SKIPLIST_NODE_FLAG_INSERTED; #endif /* * If we are deleting the last element of the list, * update the list's "last" pointer. */ if (l->last == q) { if (update[0] == l->header) l->last = NULL; else l->last = update[0]; } for (k = 0; k <= m && (p = update[k])->forward[k] == q; k++) { p->forward[k] = q->forward[k]; } --(l->stats->forward[k - 1]); #if SKIPLIST_DEBUG zlog_debug("%s: decremented stats @%p:%d, now %ld", __func__, l, k - 1, l->stats->forward[k - 1] - (struct skiplistnode *)NULL); #endif if (l->del) (*l->del)(q->value); XFREE(MTYPE_SKIP_LIST_NODE, q); while (l->header->forward[m] == NULL && m > 0) m--; l->level = m; CHECKLAST(l); --(l->count); return 0; } } CHECKLAST(l); return -1; } /* * Obtain first value matching "key". Unless SKIPLIST_FLAG_ALLOW_DUPLICATES * is set, this will also be the only value matching "key". * * Also set a cursor for use with skiplist_next_value. */ int skiplist_first_value(register struct skiplist *l, /* in */ register void *key, /* in */ void **valuePointer, /* out */ void **cursor) /* out */ { register int k; register struct skiplistnode *p, *q; p = l->header; k = l->level; do { while (q = p->forward[k], q && (*l->cmp)(q->key, key) < 0) p = q; } while (--k >= 0); if (!q || (*l->cmp)(q->key, key)) return -1; if (valuePointer) *valuePointer = q->value; if (cursor) *cursor = q; return 0; } int skiplist_search(register struct skiplist *l, register void *key, void **valuePointer) { return skiplist_first_value(l, key, valuePointer, NULL); } /* * Caller supplies key and value of an existing item in the list. * Function returns the value of the next list item that has the * same key (useful when SKIPLIST_FLAG_ALLOW_DUPLICATES is set). * * Returns 0 on success. If the caller-supplied key and value * do not correspond to a list element, or if they specify the * last element with the given key, -1 is returned. */ int skiplist_next_value(register struct skiplist *l, /* in */ register void *key, /* in */ void **valuePointer, /* in/out */ void **cursor) /* in/out */ { register int k, m; register struct skiplistnode *p, *q; CHECKLAST(l); if (!(l->flags & SKIPLIST_FLAG_ALLOW_DUPLICATES)) { return -1; } if (!cursor || !*cursor) { p = l->header; k = m = l->level; /* * Find matching key */ do { while (q = p->forward[k], q && (*l->cmp)(q->key, key) < 0) p = q; } while (--k >= 0); /* * Find matching value */ while (q && ((*l->cmp)(q->key, key) == 0) && (q->value != *valuePointer)) { q = q->forward[0]; } if (!q || ((*l->cmp)(q->key, key) != 0) || (q->value != *valuePointer)) { /* * No matching value */ CHECKLAST(l); return -1; } } else { q = (struct skiplistnode *)*cursor; } /* * Advance cursor */ q = q->forward[0]; /* * If we reached end-of-list or if the key is no longer the same, * then return error */ if (!q || ((*l->cmp)(q->key, key) != 0)) return -1; *valuePointer = q->value; if (cursor) *cursor = q; CHECKLAST(l); return 0; } int skiplist_first(register struct skiplist *l, void **keyPointer, void **valuePointer) { register struct skiplistnode *p; CHECKLAST(l); p = l->header->forward[0]; if (!p) return -1; if (keyPointer) *keyPointer = p->key; if (valuePointer) *valuePointer = p->value; CHECKLAST(l); return 0; } int skiplist_last(register struct skiplist *l, void **keyPointer, void **valuePointer) { CHECKLAST(l); if (l->last) { if (keyPointer) *keyPointer = l->last->key; if (valuePointer) *valuePointer = l->last->value; return 0; } return -1; } /* * true = empty */ int skiplist_empty(register struct skiplist *l) { CHECKLAST(l); if (l->last) return 0; return 1; } /* * Use this to walk the list. Caller sets *cursor to NULL to obtain * first element. Return value of 0 indicates valid cursor/element * returned, otherwise NULL cursor arg or EOL. */ int skiplist_next(register struct skiplist *l, /* in */ void **keyPointer, /* out */ void **valuePointer, /* out */ void **cursor) /* in/out */ { struct skiplistnode *p; if (!cursor) return -1; CHECKLAST(l); if (!*cursor) { p = l->header->forward[0]; } else { p = *cursor; p = p->forward[0]; } *cursor = p; if (!p) return -1; if (keyPointer) *keyPointer = p->key; if (valuePointer) *valuePointer = p->value; CHECKLAST(l); return 0; } int skiplist_delete_first(register struct skiplist *l) { register int k; register struct skiplistnode *p, *q; int nodelevel = 0; CHECKLAST(l); p = l->header; q = l->header->forward[0]; if (!q) return -1; for (k = l->level; k >= 0; --k) { if (p->forward[k] == q) { p->forward[k] = q->forward[k]; if ((k == l->level) && (p->forward[k] == NULL) && (l->level > 0)) --(l->level); if (!nodelevel) nodelevel = k; } } #if SKIPLIST_0TIMER_DEBUG q->flags &= ~SKIPLIST_NODE_FLAG_INSERTED; #endif /* * If we are deleting the last element of the list, * update the list's "last" pointer. */ if (l->last == q) { l->last = NULL; } --(l->stats->forward[nodelevel]); #if SKIPLIST_DEBUG zlog_debug("%s: decremented stats @%p:%d, now %ld", __func__, l, nodelevel, l->stats->forward[nodelevel] - (struct skiplistnode *)NULL); #endif if (l->del) (*l->del)(q->value); XFREE(MTYPE_SKIP_LIST_NODE, q); CHECKLAST(l); --(l->count); return 0; } void skiplist_debug(struct vty *vty, struct skiplist *l) { int i; if (!l) l = skiplist_last_created; vty_out(vty, "Skiplist %p has max level %d\n", l, l->level); for (i = l->level; i >= 0; --i) vty_out(vty, " @%d: %ld\n", i, (long)((l->stats->forward[i]) - (struct skiplistnode *)NULL)); } static void *scramble(int i) { uintptr_t result; result = (unsigned)(i & 0xff) << 24; result |= (unsigned)i >> 8; return (void *)result; } #define sampleSize 65536 void skiplist_test(struct vty *vty) { struct skiplist *l; register int i, k; void *keys[sampleSize]; void *v; zlog_debug("%s: entry", __func__); l = skiplist_new(SKIPLIST_FLAG_ALLOW_DUPLICATES, NULL, NULL); zlog_debug("%s: skiplist_new returned %p", __func__, l); for (i = 0; i < 4; i++) { for (k = 0; k < sampleSize; k++) { if (!(k % 1000)) { zlog_debug("%s: (%d:%d)", __func__, i, k); } // keys[k] = (void *)random(); keys[k] = (void *)scramble(k); if (skiplist_insert(l, keys[k], keys[k])) zlog_debug("error in insert #%d,#%d", i, k); } zlog_debug("%s: inserts done", __func__); for (k = 0; k < sampleSize; k++) { if (!(k % 1000)) zlog_debug("[%d:%d]", i, k); if (skiplist_search(l, keys[k], &v)) zlog_debug("error in search #%d,#%d", i, k); if (v != keys[k]) zlog_debug("search returned wrong value"); } for (k = 0; k < sampleSize; k++) { if (!(k % 1000)) zlog_debug("<%d:%d>", i, k); if (skiplist_delete(l, keys[k], keys[k])) zlog_debug("error in delete"); keys[k] = (void *)scramble(k ^ 0xf0f0f0f0); if (skiplist_insert(l, keys[k], keys[k])) zlog_debug("error in insert #%d,#%d", i, k); } for (k = 0; k < sampleSize; k++) { if (!(k % 1000)) zlog_debug("{%d:%d}", i, k); if (skiplist_delete_first(l)) zlog_debug("error in delete_first"); } } skiplist_free(l); } frr-7.2.1/lib/skiplist.h0000644000000000000000000000751313610377563012003 00000000000000/* * Copyright 1990 William Pugh * * Redistribution and use in source and binary forms, with or without * modification, are permitted. * * THIS SOFTWARE IS PROVIDED BY THE AUTHOR AND CONTRIBUTORS ``AS IS'' * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED * TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A * PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE AUTHOR OR * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF * USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND * ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, * OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT * OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF * SUCH DAMAGE. * * Permission to include in quagga provide on March 31, 2016 */ /* * Skip List impementation based on code from William Pugh. * ftp://ftp.cs.umd.edu/pub/skipLists/ */ /* skiplist.h */ #ifndef _ZEBRA_SKIPLIST_H #define _ZEBRA_SKIPLIST_H #ifdef __cplusplus extern "C" { #endif #define SKIPLIST_0TIMER_DEBUG 1 /* * skiplistnodes must always contain data to be valid. Adding an * empty node to a list is invalid */ struct skiplistnode { void *key; void *value; #if SKIPLIST_0TIMER_DEBUG int flags; #define SKIPLIST_NODE_FLAG_INSERTED 0x00000001 #endif struct skiplistnode *forward[1]; /* variable sized */ }; struct skiplist { int flags; #define SKIPLIST_FLAG_ALLOW_DUPLICATES 0x00000001 int level; /* max lvl (1 + current # of levels in list) */ unsigned int count; struct skiplistnode *header; struct skiplistnode *stats; struct skiplistnode *last; /* last real list item (NULL if empty list) */ /* * Returns -1 if val1 < val2, 0 if equal?, 1 if val1 > val2. * Used as definition of sorted for listnode_add_sort */ int (*cmp)(void *val1, void *val2); /* callback to free user-owned data when listnode is deleted. supplying * this callback is very much encouraged! */ void (*del)(void *val); }; /* Prototypes. */ extern struct skiplist * skiplist_new(/* encouraged: set list.del callback on new lists */ int flags, int (*cmp)(void *key1, void *key2), /* NULL => default cmp */ void (*del)(void *val)); /* NULL => no auto val free */ extern void skiplist_free(struct skiplist *); extern int skiplist_insert(register struct skiplist *l, register void *key, register void *value); extern int skiplist_delete(register struct skiplist *l, register void *key, register void *value); extern int skiplist_search(register struct skiplist *l, register void *key, void **valuePointer); extern int skiplist_first_value(register struct skiplist *l, /* in */ register void *key, /* in */ void **valuePointer, /* in/out */ void **cursor); /* out */ extern int skiplist_next_value(register struct skiplist *l, /* in */ register void *key, /* in */ void **valuePointer, /* in/out */ void **cursor); /* in/out */ extern int skiplist_first(register struct skiplist *l, void **keyPointer, void **valuePointer); extern int skiplist_last(register struct skiplist *l, void **keyPointer, void **valuePointer); extern int skiplist_delete_first(register struct skiplist *l); extern int skiplist_next(register struct skiplist *l, /* in */ void **keyPointer, /* out */ void **valuePointer, /* out */ void **cursor); /* in/out */ extern int skiplist_empty(register struct skiplist *l); /* in */ extern unsigned int skiplist_count(register struct skiplist *l); /* in */ extern void skiplist_debug(struct vty *vty, struct skiplist *l); extern void skiplist_test(struct vty *vty); #ifdef __cplusplus } #endif #endif /* _ZEBRA_SKIPLIST_H */ frr-7.2.1/lib/smux.h0000644000000000000000000000761113610377563011134 00000000000000/* SNMP support * Copyright (C) 1999 Kunihiro Ishiguro * * This file is part of GNU Zebra. * * GNU Zebra is free software; you can redistribute it and/or modify it * under the terms of the GNU General Public License as published by the * Free Software Foundation; either version 2, or (at your option) any * later version. * * GNU Zebra 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; see the file COPYING; if not, write to the Free Software * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA */ #ifndef _ZEBRA_SNMP_H #define _ZEBRA_SNMP_H #include #include #include "thread.h" #ifdef __cplusplus extern "C" { #endif /* Structures here are mostly compatible with UCD SNMP 4.1.1 */ #define MATCH_FAILED (-1) #define MATCH_SUCCEEDED 0 /* SYNTAX TruthValue from SNMPv2-TC. */ #define SNMP_TRUE 1 #define SNMP_FALSE 2 /* SYNTAX RowStatus from SNMPv2-TC. */ #define SNMP_VALID 1 #define SNMP_INVALID 2 #define IN_ADDR_SIZE sizeof(struct in_addr) #undef REGISTER_MIB #define REGISTER_MIB(descr, var, vartype, theoid) \ smux_register_mib(descr, (struct variable *)var, \ sizeof(struct vartype), \ sizeof(var) / sizeof(struct vartype), theoid, \ sizeof(theoid) / sizeof(oid)) struct trap_object { int namelen; /* Negative if the object is not indexed */ oid name[MAX_OID_LEN]; }; /* Declare SMUX return value. */ #define SNMP_LOCAL_VARIABLES \ static long snmp_int_val __attribute__((unused)); \ static struct in_addr snmp_in_addr_val __attribute__((unused)); #define SNMP_INTEGER(V) \ (*var_len = sizeof(snmp_int_val), snmp_int_val = V, \ (uint8_t *)&snmp_int_val) #define SNMP_IPADDRESS(V) \ (*var_len = sizeof(struct in_addr), snmp_in_addr_val = V, \ (uint8_t *)&snmp_in_addr_val) extern void smux_init(struct thread_master *tm); extern void smux_register_mib(const char *, struct variable *, size_t, int, oid[], size_t); extern int smux_header_generic(struct variable *, oid[], size_t *, int, size_t *, WriteMethod **); extern int smux_header_table(struct variable *, oid *, size_t *, int, size_t *, WriteMethod **); /* For traps, three OID are provided: 1. The enterprise OID to use (the last argument will be appended to it to form the SNMP trap OID) 2. The base OID for objects to be sent in traps. 3. The index OID for objects to be sent in traps. This index is used to designate a particular instance of a column. The provided trap object contains the bindings to be sent with the trap. The base OID will be prefixed to the provided OID and, if the length is positive, the requested OID is assumed to be a columnar object and the index OID will be appended. The two first arguments are the MIB registry used to locate the trap objects. The use of the arguments may differ depending on the implementation used. */ extern int smux_trap(struct variable *, size_t, const oid *, size_t, const oid *, size_t, const oid *, size_t, const struct trap_object *, size_t, uint8_t); extern int oid_compare(const oid *, int, const oid *, int); extern void oid2in_addr(oid[], int, struct in_addr *); extern void *oid_copy(void *, const void *, size_t); extern void oid_copy_addr(oid[], struct in_addr *, int); #ifdef __cplusplus } #endif #endif /* _ZEBRA_SNMP_H */ frr-7.2.1/lib/snmp.c0000644000000000000000000000554713610377563011116 00000000000000/* SNMP support * Copyright (C) 1999 Kunihiro Ishiguro * * This file is part of GNU Zebra. * * GNU Zebra is free software; you can redistribute it and/or modify it * under the terms of the GNU General Public License as published by the * Free Software Foundation; either version 2, or (at your option) any * later version. * * GNU Zebra 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; see the file COPYING; if not, write to the Free Software * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA */ #include #include #include #include "smux.h" #define min(A,B) ((A) < (B) ? (A) : (B)) int oid_compare(const oid *o1, int o1_len, const oid *o2, int o2_len) { int i; for (i = 0; i < min(o1_len, o2_len); i++) { if (o1[i] < o2[i]) return -1; else if (o1[i] > o2[i]) return 1; } if (o1_len < o2_len) return -1; if (o1_len > o2_len) return 1; return 0; } void *oid_copy(void *dest, const void *src, size_t size) { return memcpy(dest, src, size * sizeof(oid)); } void oid2in_addr(oid oid[], int len, struct in_addr *addr) { int i; uint8_t *pnt; if (len == 0) return; pnt = (uint8_t *)addr; for (i = 0; i < len; i++) *pnt++ = oid[i]; } void oid_copy_addr(oid oid[], struct in_addr *addr, int len) { int i; uint8_t *pnt; if (len == 0) return; pnt = (uint8_t *)addr; for (i = 0; i < len; i++) oid[i] = *pnt++; } int smux_header_generic(struct variable *v, oid *name, size_t *length, int exact, size_t *var_len, WriteMethod **write_method) { oid fulloid[MAX_OID_LEN]; int ret; oid_copy(fulloid, v->name, v->namelen); fulloid[v->namelen] = 0; /* Check against full instance. */ ret = oid_compare(name, *length, fulloid, v->namelen + 1); /* Check single instance. */ if ((exact && (ret != 0)) || (!exact && (ret >= 0))) return MATCH_FAILED; /* In case of getnext, fill in full instance. */ memcpy(name, fulloid, (v->namelen + 1) * sizeof(oid)); *length = v->namelen + 1; *write_method = 0; *var_len = sizeof(long); /* default to 'long' results */ return MATCH_SUCCEEDED; } int smux_header_table(struct variable *v, oid *name, size_t *length, int exact, size_t *var_len, WriteMethod **write_method) { /* If the requested OID name is less than OID prefix we handle, adjust it to our prefix. */ if ((oid_compare(name, *length, v->name, v->namelen)) < 0) { if (exact) return MATCH_FAILED; oid_copy(name, v->name, v->namelen); *length = v->namelen; } *write_method = 0; *var_len = sizeof(long); return MATCH_SUCCEEDED; } frr-7.2.1/lib/sockopt.c0000644000000000000000000004514313610377563011617 00000000000000/* setsockopt functions * Copyright (C) 1999 Kunihiro Ishiguro * * This file is part of GNU Zebra. * * GNU Zebra is free software; you can redistribute it and/or modify it * under the terms of the GNU General Public License as published by the * Free Software Foundation; either version 2, or (at your option) any * later version. * * GNU Zebra 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; see the file COPYING; if not, write to the Free Software * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA */ #include #ifdef SUNOS_5 #include #endif #include "log.h" #include "sockopt.h" #include "sockunion.h" #include "lib_errors.h" void setsockopt_so_recvbuf(int sock, int size) { int orig_req = size; while (setsockopt(sock, SOL_SOCKET, SO_RCVBUF, &size, sizeof(size)) == -1) size /= 2; if (size != orig_req) flog_err(EC_LIB_SOCKET, "%s: fd %d: SO_RCVBUF set to %d (requested %d)", __func__, sock, size, orig_req); } void setsockopt_so_sendbuf(const int sock, int size) { int orig_req = size; while (setsockopt(sock, SOL_SOCKET, SO_SNDBUF, &size, sizeof(size)) == -1) size /= 2; if (size != orig_req) flog_err(EC_LIB_SOCKET, "%s: fd %d: SO_SNDBUF set to %d (requested %d)", __func__, sock, size, orig_req); } int getsockopt_so_sendbuf(const int sock) { uint32_t optval; socklen_t optlen = sizeof(optval); int ret = getsockopt(sock, SOL_SOCKET, SO_SNDBUF, (char *)&optval, &optlen); if (ret < 0) { flog_err_sys(EC_LIB_SYSTEM_CALL, "fd %d: can't getsockopt SO_SNDBUF: %d (%s)", sock, errno, safe_strerror(errno)); return ret; } return optval; } static void *getsockopt_cmsg_data(struct msghdr *msgh, int level, int type) { struct cmsghdr *cmsg; void *ptr = NULL; for (cmsg = CMSG_FIRSTHDR(msgh); cmsg != NULL; cmsg = CMSG_NXTHDR(msgh, cmsg)) if (cmsg->cmsg_level == level && cmsg->cmsg_type == type) return (ptr = CMSG_DATA(cmsg)); return NULL; } /* Set IPv6 packet info to the socket. */ int setsockopt_ipv6_pktinfo(int sock, int val) { int ret; #ifdef IPV6_RECVPKTINFO /*2292bis-01*/ ret = setsockopt(sock, IPPROTO_IPV6, IPV6_RECVPKTINFO, &val, sizeof(val)); if (ret < 0) flog_err(EC_LIB_SOCKET, "can't setsockopt IPV6_RECVPKTINFO : %s", safe_strerror(errno)); #else /*RFC2292*/ ret = setsockopt(sock, IPPROTO_IPV6, IPV6_PKTINFO, &val, sizeof(val)); if (ret < 0) flog_err(EC_LIB_SOCKET, "can't setsockopt IPV6_PKTINFO : %s", safe_strerror(errno)); #endif /* INIA_IPV6 */ return ret; } /* Set multicast hops val to the socket. */ int setsockopt_ipv6_checksum(int sock, int val) { int ret; #ifdef GNU_LINUX ret = setsockopt(sock, IPPROTO_RAW, IPV6_CHECKSUM, &val, sizeof(val)); #else ret = setsockopt(sock, IPPROTO_IPV6, IPV6_CHECKSUM, &val, sizeof(val)); #endif /* GNU_LINUX */ if (ret < 0) flog_err(EC_LIB_SOCKET, "can't setsockopt IPV6_CHECKSUM"); return ret; } /* Set multicast hops val to the socket. */ int setsockopt_ipv6_multicast_hops(int sock, int val) { int ret; ret = setsockopt(sock, IPPROTO_IPV6, IPV6_MULTICAST_HOPS, &val, sizeof(val)); if (ret < 0) flog_err(EC_LIB_SOCKET, "can't setsockopt IPV6_MULTICAST_HOPS"); return ret; } /* Set multicast hops val to the socket. */ int setsockopt_ipv6_unicast_hops(int sock, int val) { int ret; ret = setsockopt(sock, IPPROTO_IPV6, IPV6_UNICAST_HOPS, &val, sizeof(val)); if (ret < 0) flog_err(EC_LIB_SOCKET, "can't setsockopt IPV6_UNICAST_HOPS"); return ret; } int setsockopt_ipv6_hoplimit(int sock, int val) { int ret; #ifdef IPV6_RECVHOPLIMIT /*2292bis-01*/ ret = setsockopt(sock, IPPROTO_IPV6, IPV6_RECVHOPLIMIT, &val, sizeof(val)); if (ret < 0) flog_err(EC_LIB_SOCKET, "can't setsockopt IPV6_RECVHOPLIMIT"); #else /*RFC2292*/ ret = setsockopt(sock, IPPROTO_IPV6, IPV6_HOPLIMIT, &val, sizeof(val)); if (ret < 0) flog_err(EC_LIB_SOCKET, "can't setsockopt IPV6_HOPLIMIT"); #endif return ret; } /* Set multicast loop zero to the socket. */ int setsockopt_ipv6_multicast_loop(int sock, int val) { int ret; ret = setsockopt(sock, IPPROTO_IPV6, IPV6_MULTICAST_LOOP, &val, sizeof(val)); if (ret < 0) flog_err(EC_LIB_SOCKET, "can't setsockopt IPV6_MULTICAST_LOOP"); return ret; } static int getsockopt_ipv6_ifindex(struct msghdr *msgh) { struct in6_pktinfo *pktinfo; pktinfo = getsockopt_cmsg_data(msgh, IPPROTO_IPV6, IPV6_PKTINFO); return pktinfo->ipi6_ifindex; } int setsockopt_ipv6_tclass(int sock, int tclass) { int ret = 0; #ifdef IPV6_TCLASS /* RFC3542 */ ret = setsockopt(sock, IPPROTO_IPV6, IPV6_TCLASS, &tclass, sizeof(tclass)); if (ret < 0) flog_err(EC_LIB_SOCKET, "Can't set IPV6_TCLASS option for fd %d to %#x: %s", sock, tclass, safe_strerror(errno)); #endif return ret; } /* * Process multicast socket options for IPv4 in an OS-dependent manner. * Supported options are IP_{ADD,DROP}_MEMBERSHIP. * * Many operating systems have a limit on the number of groups that * can be joined per socket (where each group and local address * counts). This impacts OSPF, which joins groups on each interface * using a single socket. The limit is typically 20, derived from the * original BSD multicast implementation. Some systems have * mechanisms for increasing this limit. * * In many 4.4BSD-derived systems, multicast group operations are not * allowed on interfaces that are not UP. Thus, a previous attempt to * leave the group may have failed, leaving it still joined, and we * drop/join quietly to recover. This may not be necessary, but aims to * defend against unknown behavior in that we will still return an error * if the second join fails. It is not clear how other systems * (e.g. Linux, Solaris) behave when leaving groups on down interfaces, * but this behavior should not be harmful if they behave the same way, * allow leaves, or implicitly leave all groups joined to down interfaces. */ int setsockopt_ipv4_multicast(int sock, int optname, struct in_addr if_addr, unsigned int mcast_addr, ifindex_t ifindex) { #ifdef HAVE_RFC3678 struct group_req gr; struct sockaddr_in *si; int ret; memset(&gr, 0, sizeof(gr)); si = (struct sockaddr_in *)&gr.gr_group; gr.gr_interface = ifindex; si->sin_family = AF_INET; #ifdef HAVE_STRUCT_SOCKADDR_IN_SIN_LEN si->sin_len = sizeof(struct sockaddr_in); #endif /* HAVE_STRUCT_SOCKADDR_IN_SIN_LEN */ si->sin_addr.s_addr = mcast_addr; ret = setsockopt(sock, IPPROTO_IP, (optname == IP_ADD_MEMBERSHIP) ? MCAST_JOIN_GROUP : MCAST_LEAVE_GROUP, (void *)&gr, sizeof(gr)); if ((ret < 0) && (optname == IP_ADD_MEMBERSHIP) && (errno == EADDRINUSE)) { setsockopt(sock, IPPROTO_IP, MCAST_LEAVE_GROUP, (void *)&gr, sizeof(gr)); ret = setsockopt(sock, IPPROTO_IP, MCAST_JOIN_GROUP, (void *)&gr, sizeof(gr)); } return ret; #elif defined(HAVE_STRUCT_IP_MREQN_IMR_IFINDEX) && !defined(__FreeBSD__) struct ip_mreqn mreqn; int ret; assert(optname == IP_ADD_MEMBERSHIP || optname == IP_DROP_MEMBERSHIP); memset(&mreqn, 0, sizeof(mreqn)); mreqn.imr_multiaddr.s_addr = mcast_addr; mreqn.imr_ifindex = ifindex; ret = setsockopt(sock, IPPROTO_IP, optname, (void *)&mreqn, sizeof(mreqn)); if ((ret < 0) && (optname == IP_ADD_MEMBERSHIP) && (errno == EADDRINUSE)) { /* see above: handle possible problem when interface comes back * up */ char buf[1][INET_ADDRSTRLEN]; zlog_info( "setsockopt_ipv4_multicast attempting to drop and " "re-add (fd %d, mcast %s, ifindex %u)", sock, inet_ntop(AF_INET, &mreqn.imr_multiaddr, buf[0], sizeof(buf[0])), ifindex); setsockopt(sock, IPPROTO_IP, IP_DROP_MEMBERSHIP, (void *)&mreqn, sizeof(mreqn)); ret = setsockopt(sock, IPPROTO_IP, IP_ADD_MEMBERSHIP, (void *)&mreqn, sizeof(mreqn)); } return ret; /* Example defines for another OS, boilerplate off other code in this function, AND handle optname as per other sections for consistency !! */ /* #elif defined(BOGON_NIX) && EXAMPLE_VERSION_CODE > -100000 */ /* Add your favourite OS here! */ #elif defined(HAVE_BSD_STRUCT_IP_MREQ_HACK) /* #if OS_TYPE */ /* standard BSD API */ struct ip_mreq mreq; int ret; assert(optname == IP_ADD_MEMBERSHIP || optname == IP_DROP_MEMBERSHIP); memset(&mreq, 0, sizeof(mreq)); mreq.imr_multiaddr.s_addr = mcast_addr; #if !defined __OpenBSD__ mreq.imr_interface.s_addr = htonl(ifindex); #else mreq.imr_interface.s_addr = if_addr.s_addr; #endif ret = setsockopt(sock, IPPROTO_IP, optname, (void *)&mreq, sizeof(mreq)); if ((ret < 0) && (optname == IP_ADD_MEMBERSHIP) && (errno == EADDRINUSE)) { /* see above: handle possible problem when interface comes back * up */ char buf[1][INET_ADDRSTRLEN]; zlog_info( "setsockopt_ipv4_multicast attempting to drop and " "re-add (fd %d, mcast %s, ifindex %u)", sock, inet_ntop(AF_INET, &mreq.imr_multiaddr, buf[0], sizeof(buf[0])), ifindex); setsockopt(sock, IPPROTO_IP, IP_DROP_MEMBERSHIP, (void *)&mreq, sizeof(mreq)); ret = setsockopt(sock, IPPROTO_IP, IP_ADD_MEMBERSHIP, (void *)&mreq, sizeof(mreq)); } return ret; #else #error "Unsupported multicast API" #endif /* #if OS_TYPE */ } /* * Set IP_MULTICAST_IF socket option in an OS-dependent manner. */ int setsockopt_ipv4_multicast_if(int sock, struct in_addr if_addr, ifindex_t ifindex) { #ifdef HAVE_STRUCT_IP_MREQN_IMR_IFINDEX struct ip_mreqn mreqn; memset(&mreqn, 0, sizeof(mreqn)); mreqn.imr_ifindex = ifindex; return setsockopt(sock, IPPROTO_IP, IP_MULTICAST_IF, (void *)&mreqn, sizeof(mreqn)); /* Example defines for another OS, boilerplate off other code in this function */ /* #elif defined(BOGON_NIX) && EXAMPLE_VERSION_CODE > -100000 */ /* Add your favourite OS here! */ #elif defined(HAVE_BSD_STRUCT_IP_MREQ_HACK) struct in_addr m; #if !defined __OpenBSD__ m.s_addr = htonl(ifindex); #else m.s_addr = if_addr.s_addr; #endif return setsockopt(sock, IPPROTO_IP, IP_MULTICAST_IF, (void *)&m, sizeof(m)); #elif defined(SUNOS_5) char ifname[IF_NAMESIZE]; struct ifaddrs *ifa, *ifap; struct in_addr ifaddr; if (if_indextoname(ifindex, ifname) == NULL) return -1; if (getifaddrs(&ifa) != 0) return -1; for (ifap = ifa; ifap != NULL; ifap = ifap->ifa_next) { struct sockaddr_in *sa; if (strcmp(ifap->ifa_name, ifname) != 0) continue; if (ifap->ifa_addr->sa_family != AF_INET) continue; sa = (struct sockaddr_in *)ifap->ifa_addr; memcpy(&ifaddr, &sa->sin_addr, sizeof(ifaddr)); break; } freeifaddrs(ifa); if (!ifap) /* This means we did not find an IP */ return -1; return setsockopt(sock, IPPROTO_IP, IP_MULTICAST_IF, (void *)&ifaddr, sizeof(ifaddr)); #else #error "Unsupported multicast API" #endif } int setsockopt_ipv4_multicast_loop(int sock, uint8_t val) { int ret; ret = setsockopt(sock, IPPROTO_IP, IP_MULTICAST_LOOP, (void *)&val, sizeof(val)); if (ret < 0) flog_err(EC_LIB_SOCKET, "can't setsockopt IP_MULTICAST_LOOP"); return ret; } static int setsockopt_ipv4_ifindex(int sock, ifindex_t val) { int ret; #if defined(IP_PKTINFO) if ((ret = setsockopt(sock, IPPROTO_IP, IP_PKTINFO, &val, sizeof(val))) < 0) flog_err(EC_LIB_SOCKET, "Can't set IP_PKTINFO option for fd %d to %d: %s", sock, val, safe_strerror(errno)); #elif defined(IP_RECVIF) if ((ret = setsockopt(sock, IPPROTO_IP, IP_RECVIF, &val, sizeof(val))) < 0) flog_err(EC_LIB_SOCKET, "Can't set IP_RECVIF option for fd %d to %d: %s", sock, val, safe_strerror(errno)); #else #warning "Neither IP_PKTINFO nor IP_RECVIF is available." #warning "Will not be able to receive link info." #warning "Things might be seriously broken.." /* XXX Does this ever happen? Should there be a zlog_warn message here? */ ret = -1; #endif return ret; } int setsockopt_ipv4_tos(int sock, int tos) { int ret; ret = setsockopt(sock, IPPROTO_IP, IP_TOS, &tos, sizeof(tos)); if (ret < 0) flog_err(EC_LIB_SOCKET, "Can't set IP_TOS option for fd %d to %#x: %s", sock, tos, safe_strerror(errno)); return ret; } int setsockopt_ifindex(int af, int sock, ifindex_t val) { int ret = -1; switch (af) { case AF_INET: ret = setsockopt_ipv4_ifindex(sock, val); break; case AF_INET6: ret = setsockopt_ipv6_pktinfo(sock, val); break; default: flog_err(EC_LIB_DEVELOPMENT, "setsockopt_ifindex: unknown address family %d", af); } return ret; } /* * Requires: msgh is not NULL and points to a valid struct msghdr, which * may or may not have control data about the incoming interface. * * Returns the interface index (small integer >= 1) if it can be * determined, or else 0. */ static ifindex_t getsockopt_ipv4_ifindex(struct msghdr *msgh) { ifindex_t ifindex; #if defined(IP_PKTINFO) /* Linux pktinfo based ifindex retrieval */ struct in_pktinfo *pktinfo; pktinfo = (struct in_pktinfo *)getsockopt_cmsg_data(msgh, IPPROTO_IP, IP_PKTINFO); /* getsockopt_ifindex() will forward this, being 0 "not found" */ if (pktinfo == NULL) return 0; ifindex = pktinfo->ipi_ifindex; #elif defined(IP_RECVIF) /* retrieval based on IP_RECVIF */ #ifndef SUNOS_5 /* BSD systems use a sockaddr_dl as the control message payload. */ struct sockaddr_dl *sdl; #else /* SUNOS_5 uses an integer with the index. */ ifindex_t *ifindex_p; #endif /* SUNOS_5 */ #ifndef SUNOS_5 /* BSD */ sdl = (struct sockaddr_dl *)getsockopt_cmsg_data(msgh, IPPROTO_IP, IP_RECVIF); if (sdl != NULL) ifindex = sdl->sdl_index; else ifindex = 0; #else /* * Solaris. On Solaris 8, IP_RECVIF is defined, but the call to * enable it fails with errno=99, and the struct msghdr has * controllen 0. */ ifindex_p = (uint_t *)getsockopt_cmsg_data(msgh, IPPROTO_IP, IP_RECVIF); if (ifindex_p != NULL) ifindex = *ifindex_p; else ifindex = 0; #endif /* SUNOS_5 */ #else /* * Neither IP_PKTINFO nor IP_RECVIF defined - warn at compile time. * XXX Decide if this is a core service, or if daemons have to cope. * Since Solaris 8 and OpenBSD seem not to provide it, it seems that * daemons have to cope. */ #warning "getsockopt_ipv4_ifindex: Neither IP_PKTINFO nor IP_RECVIF defined." #warning "Some daemons may fail to operate correctly!" ifindex = 0; #endif /* IP_PKTINFO */ return ifindex; } /* return ifindex, 0 if none found */ ifindex_t getsockopt_ifindex(int af, struct msghdr *msgh) { switch (af) { case AF_INET: return (getsockopt_ipv4_ifindex(msgh)); break; case AF_INET6: return (getsockopt_ipv6_ifindex(msgh)); break; default: flog_err(EC_LIB_DEVELOPMENT, "getsockopt_ifindex: unknown address family %d", af); return 0; } } /* swab iph between order system uses for IP_HDRINCL and host order */ void sockopt_iphdrincl_swab_htosys(struct ip *iph) { /* BSD and derived take iph in network order, except for * ip_len and ip_off */ #ifndef HAVE_IP_HDRINCL_BSD_ORDER iph->ip_len = htons(iph->ip_len); iph->ip_off = htons(iph->ip_off); #endif /* HAVE_IP_HDRINCL_BSD_ORDER */ iph->ip_id = htons(iph->ip_id); } void sockopt_iphdrincl_swab_systoh(struct ip *iph) { #ifndef HAVE_IP_HDRINCL_BSD_ORDER iph->ip_len = ntohs(iph->ip_len); iph->ip_off = ntohs(iph->ip_off); #endif /* HAVE_IP_HDRINCL_BSD_ORDER */ iph->ip_id = ntohs(iph->ip_id); } int sockopt_tcp_rtt(int sock) { #ifdef TCP_INFO struct tcp_info ti; socklen_t len = sizeof(ti); if (getsockopt(sock, IPPROTO_TCP, TCP_INFO, &ti, &len) != 0) return 0; return ti.tcpi_rtt / 1000; #else return 0; #endif } int sockopt_tcp_signature_ext(int sock, union sockunion *su, uint16_t prefixlen, const char *password) { #ifndef HAVE_DECL_TCP_MD5SIG /* * We have been asked to enable MD5 auth for an address, but our * platform doesn't support that */ return -2; #endif #ifndef TCP_MD5SIG_EXT /* * We have been asked to enable MD5 auth for a prefix, but our platform * doesn't support that */ if (prefixlen > 0) return -2; #endif #if HAVE_DECL_TCP_MD5SIG int ret; int optname = TCP_MD5SIG; #ifndef GNU_LINUX /* * XXX Need to do PF_KEY operation here to add/remove an SA entry, * and add/remove an SP entry for this peer's packet flows also. */ int md5sig = password && *password ? 1 : 0; #else int keylen = password ? strlen(password) : 0; struct tcp_md5sig md5sig; union sockunion *su2, *susock; /* Figure out whether the socket and the sockunion are the same family.. * adding AF_INET to AF_INET6 needs to be v4 mapped, you'd think.. */ if (!(susock = sockunion_getsockname(sock))) return -1; if (susock->sa.sa_family == su->sa.sa_family) su2 = su; else { /* oops.. */ su2 = susock; if (su2->sa.sa_family == AF_INET) { sockunion_free(susock); return 0; } /* If this does not work, then all users of this sockopt will * need to * differentiate between IPv4 and IPv6, and keep seperate * sockets for * each. * * Sadly, it doesn't seem to work at present. It's unknown * whether * this is a bug or not. */ if (su2->sa.sa_family == AF_INET6 && su->sa.sa_family == AF_INET) { su2->sin6.sin6_family = AF_INET6; /* V4Map the address */ memset(&su2->sin6.sin6_addr, 0, sizeof(struct in6_addr)); su2->sin6.sin6_addr.s6_addr32[2] = htonl(0xffff); memcpy(&su2->sin6.sin6_addr.s6_addr32[3], &su->sin.sin_addr, 4); } } memset(&md5sig, 0, sizeof(md5sig)); memcpy(&md5sig.tcpm_addr, su2, sizeof(*su2)); md5sig.tcpm_keylen = keylen; if (keylen) memcpy(md5sig.tcpm_key, password, keylen); sockunion_free(susock); /* * Handle support for MD5 signatures on prefixes, if available and * requested. Technically the #ifdef check below is not needed because * if prefixlen > 0 and we don't have support for this feature we would * have already returned by now, but leaving it there to be explicit. */ #ifdef TCP_MD5SIG_EXT if (prefixlen > 0) { md5sig.tcpm_prefixlen = prefixlen; md5sig.tcpm_flags = TCP_MD5SIG_FLAG_PREFIX; optname = TCP_MD5SIG_EXT; } #endif /* TCP_MD5SIG_EXT */ #endif /* GNU_LINUX */ if ((ret = setsockopt(sock, IPPROTO_TCP, optname, &md5sig, sizeof md5sig)) < 0) { /* ENOENT is harmless. It is returned when we clear a password for which one was not previously set. */ if (ENOENT == errno) ret = 0; else flog_err_sys( EC_LIB_SYSTEM_CALL, "sockopt_tcp_signature: setsockopt(%d): %s", sock, safe_strerror(errno)); } return ret; #endif /* HAVE_TCP_MD5SIG */ /* * Making compiler happy. If we get to this point we probably * have done something really really wrong. */ return -2; } int sockopt_tcp_signature(int sock, union sockunion *su, const char *password) { return sockopt_tcp_signature_ext(sock, su, 0, password); } frr-7.2.1/lib/sockopt.h0000644000000000000000000001117313610377563011620 00000000000000/* Router advertisement * Copyright (C) 1999 Kunihiro Ishiguro * * This file is part of GNU Zebra. * * GNU Zebra is free software; you can redistribute it and/or modify it * under the terms of the GNU General Public License as published by the * Free Software Foundation; either version 2, or (at your option) any * later version. * * GNU Zebra 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; see the file COPYING; if not, write to the Free Software * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA */ #ifndef _ZEBRA_SOCKOPT_H #define _ZEBRA_SOCKOPT_H #include "sockunion.h" #ifdef __cplusplus extern "C" { #endif extern void setsockopt_so_recvbuf(int sock, int size); extern void setsockopt_so_sendbuf(const int sock, int size); extern int getsockopt_so_sendbuf(const int sock); extern int setsockopt_ipv6_pktinfo(int, int); extern int setsockopt_ipv6_checksum(int, int); extern int setsockopt_ipv6_multicast_hops(int, int); extern int setsockopt_ipv6_unicast_hops(int, int); extern int setsockopt_ipv6_hoplimit(int, int); extern int setsockopt_ipv6_multicast_loop(int, int); extern int setsockopt_ipv6_tclass(int, int); #define SOPT_SIZE_CMSG_PKTINFO_IPV6() (sizeof (struct in6_pktinfo)); /* * Size defines for control messages used to get ifindex. We define * values for each method, and define a macro that can be used by code * that is unaware of which method is in use. * These values are without any alignment needed (see CMSG_SPACE in RFC3542). */ #if defined(IP_PKTINFO) /* Linux in_pktinfo. */ #define SOPT_SIZE_CMSG_PKTINFO_IPV4() (CMSG_SPACE(sizeof (struct in_pktinfo))) /* XXX This should perhaps be defined even if IP_PKTINFO is not. */ #define SOPT_SIZE_CMSG_PKTINFO(af) \ ((af == AF_INET) ? SOPT_SIZE_CMSG_PKTINFO_IPV4() \ : SOPT_SIZE_CMSG_PKTINFO_IPV6() #endif /* IP_PKTINFO */ #if defined(IP_RECVIF) /* BSD/Solaris */ #if defined(SUNOS_5) #define SOPT_SIZE_CMSG_RECVIF_IPV4() (sizeof (uint_t)) #else #define SOPT_SIZE_CMSG_RECVIF_IPV4() (sizeof (struct sockaddr_dl)) #endif /* SUNOS_5 */ #endif /* IP_RECVIF */ /* SOPT_SIZE_CMSG_IFINDEX_IPV4 - portable type */ #if defined(SOPT_SIZE_CMSG_PKTINFO) #define SOPT_SIZE_CMSG_IFINDEX_IPV4() SOPT_SIZE_CMSG_PKTINFO_IPV4() #elif defined(SOPT_SIZE_CMSG_RECVIF_IPV4) #define SOPT_SIZE_CMSG_IFINDEX_IPV4() SOPT_SIZE_CMSG_RECVIF_IPV4() #else /* Nothing available */ #define SOPT_SIZE_CMSG_IFINDEX_IPV4() (sizeof (char *)) #endif /* SOPT_SIZE_CMSG_IFINDEX_IPV4 */ #define SOPT_SIZE_CMSG_IFINDEX(af) \ (((af) == AF_INET) : SOPT_SIZE_CMSG_IFINDEX_IPV4() \ ? SOPT_SIZE_CMSG_PKTINFO_IPV6()) extern int setsockopt_ipv4_multicast_if(int sock, struct in_addr if_addr, ifindex_t ifindex); extern int setsockopt_ipv4_multicast(int sock, int optname, struct in_addr if_addr, unsigned int mcast_addr, ifindex_t ifindex); extern int setsockopt_ipv4_multicast_loop(int sock, uint8_t val); extern int setsockopt_ipv4_tos(int sock, int tos); /* Ask for, and get, ifindex, by whatever method is supported. */ extern int setsockopt_ifindex(int, int, ifindex_t); extern ifindex_t getsockopt_ifindex(int, struct msghdr *); /* swab the fields in iph between the host order and system order expected * for IP_HDRINCL. */ extern void sockopt_iphdrincl_swab_htosys(struct ip *iph); extern void sockopt_iphdrincl_swab_systoh(struct ip *iph); extern int sockopt_tcp_rtt(int); /* * TCP MD5 signature option. This option allows TCP MD5 to be enabled on * addresses. * * sock * Socket to enable option on. * * su * Sockunion specifying address to enable option on. * * password * MD5 auth password */ extern int sockopt_tcp_signature(int sock, union sockunion *su, const char *password); /* * Extended TCP MD5 signature option. This option allows TCP MD5 to be enabled * on prefixes. * * sock * Socket to enable option on. * * su * Sockunion specifying address (or prefix) to enable option on. * * prefixlen * 0 - su is an address; fall back to non-extended mode * Else - su is a prefix; prefixlen is the mask length * * password * MD5 auth password */ extern int sockopt_tcp_signature_ext(int sock, union sockunion *su, uint16_t prefixlen, const char *password); #ifdef __cplusplus } #endif #endif /*_ZEBRA_SOCKOPT_H */ frr-7.2.1/lib/sockunion.c0000644000000000000000000003576613610377563012157 00000000000000/* Socket union related function. * Copyright (c) 1997, 98 Kunihiro Ishiguro * * This file is part of GNU Zebra. * * GNU Zebra is free software; you can redistribute it and/or modify it * under the terms of the GNU General Public License as published by the * Free Software Foundation; either version 2, or (at your option) any * later version. * * GNU Zebra 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; see the file COPYING; if not, write to the Free Software * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA */ #include #include "prefix.h" #include "vty.h" #include "sockunion.h" #include "memory.h" #include "log.h" #include "jhash.h" #include "lib_errors.h" DEFINE_MTYPE_STATIC(LIB, SOCKUNION, "Socket union") const char *inet_sutop(const union sockunion *su, char *str) { switch (su->sa.sa_family) { case AF_INET: inet_ntop(AF_INET, &su->sin.sin_addr, str, INET_ADDRSTRLEN); break; case AF_INET6: inet_ntop(AF_INET6, &su->sin6.sin6_addr, str, INET6_ADDRSTRLEN); break; } return str; } int str2sockunion(const char *str, union sockunion *su) { int ret; if (str == NULL) return -1; memset(su, 0, sizeof(union sockunion)); ret = inet_pton(AF_INET, str, &su->sin.sin_addr); if (ret > 0) /* Valid IPv4 address format. */ { su->sin.sin_family = AF_INET; #ifdef HAVE_STRUCT_SOCKADDR_IN_SIN_LEN su->sin.sin_len = sizeof(struct sockaddr_in); #endif /* HAVE_STRUCT_SOCKADDR_IN_SIN_LEN */ return 0; } ret = inet_pton(AF_INET6, str, &su->sin6.sin6_addr); if (ret > 0) /* Valid IPv6 address format. */ { su->sin6.sin6_family = AF_INET6; #ifdef SIN6_LEN su->sin6.sin6_len = sizeof(struct sockaddr_in6); #endif /* SIN6_LEN */ return 0; } return -1; } const char *sockunion2str(const union sockunion *su, char *buf, size_t len) { switch (sockunion_family(su)) { case AF_UNSPEC: snprintf(buf, len, "(unspec)"); return buf; case AF_INET: return inet_ntop(AF_INET, &su->sin.sin_addr, buf, len); case AF_INET6: return inet_ntop(AF_INET6, &su->sin6.sin6_addr, buf, len); } snprintf(buf, len, "(af %d)", sockunion_family(su)); return buf; } union sockunion *sockunion_str2su(const char *str) { union sockunion *su = XCALLOC(MTYPE_SOCKUNION, sizeof(union sockunion)); if (!str2sockunion(str, su)) return su; XFREE(MTYPE_SOCKUNION, su); return NULL; } /* Convert IPv4 compatible IPv6 address to IPv4 address. */ static void sockunion_normalise_mapped(union sockunion *su) { struct sockaddr_in sin; if (su->sa.sa_family == AF_INET6 && IN6_IS_ADDR_V4MAPPED(&su->sin6.sin6_addr)) { memset(&sin, 0, sizeof(struct sockaddr_in)); sin.sin_family = AF_INET; sin.sin_port = su->sin6.sin6_port; memcpy(&sin.sin_addr, ((char *)&su->sin6.sin6_addr) + 12, 4); memcpy(su, &sin, sizeof(struct sockaddr_in)); } } /* return sockunion structure : this function should be revised. */ static const char *sockunion_log(const union sockunion *su, char *buf, size_t len) { switch (su->sa.sa_family) { case AF_INET: return inet_ntop(AF_INET, &su->sin.sin_addr, buf, len); case AF_INET6: return inet_ntop(AF_INET6, &(su->sin6.sin6_addr), buf, len); break; default: snprintf(buf, len, "af_unknown %d ", su->sa.sa_family); return buf; } } /* Return socket of sockunion. */ int sockunion_socket(const union sockunion *su) { int sock; sock = socket(su->sa.sa_family, SOCK_STREAM, 0); if (sock < 0) { char buf[SU_ADDRSTRLEN]; flog_err(EC_LIB_SOCKET, "Can't make socket for %s : %s", sockunion_log(su, buf, SU_ADDRSTRLEN), safe_strerror(errno)); return -1; } return sock; } /* Return accepted new socket file descriptor. */ int sockunion_accept(int sock, union sockunion *su) { socklen_t len; int client_sock; len = sizeof(union sockunion); client_sock = accept(sock, (struct sockaddr *)su, &len); sockunion_normalise_mapped(su); return client_sock; } /* Return sizeof union sockunion. */ int sockunion_sizeof(const union sockunion *su) { int ret; ret = 0; switch (su->sa.sa_family) { case AF_INET: ret = sizeof(struct sockaddr_in); break; case AF_INET6: ret = sizeof(struct sockaddr_in6); break; } return ret; } /* Performs a non-blocking connect(). */ enum connect_result sockunion_connect(int fd, const union sockunion *peersu, unsigned short port, ifindex_t ifindex) { int ret; union sockunion su; memcpy(&su, peersu, sizeof(union sockunion)); switch (su.sa.sa_family) { case AF_INET: su.sin.sin_port = port; break; case AF_INET6: su.sin6.sin6_port = port; #ifdef KAME if (IN6_IS_ADDR_LINKLOCAL(&su.sin6.sin6_addr) && ifindex) { su.sin6.sin6_scope_id = ifindex; SET_IN6_LINKLOCAL_IFINDEX(su.sin6.sin6_addr, ifindex); } #endif /* KAME */ break; } /* Call connect function. */ ret = connect(fd, (struct sockaddr *)&su, sockunion_sizeof(&su)); /* Immediate success */ if (ret == 0) return connect_success; /* If connect is in progress then return 1 else it's real error. */ if (ret < 0) { if (errno != EINPROGRESS) { char str[SU_ADDRSTRLEN]; zlog_info("can't connect to %s fd %d : %s", sockunion_log(&su, str, sizeof str), fd, safe_strerror(errno)); return connect_error; } } return connect_in_progress; } /* Make socket from sockunion union. */ int sockunion_stream_socket(union sockunion *su) { int sock; if (su->sa.sa_family == 0) su->sa.sa_family = AF_INET_UNION; sock = socket(su->sa.sa_family, SOCK_STREAM, 0); if (sock < 0) flog_err(EC_LIB_SOCKET, "can't make socket sockunion_stream_socket"); return sock; } /* Bind socket to specified address. */ int sockunion_bind(int sock, union sockunion *su, unsigned short port, union sockunion *su_addr) { int size = 0; int ret; if (su->sa.sa_family == AF_INET) { size = sizeof(struct sockaddr_in); su->sin.sin_port = htons(port); #ifdef HAVE_STRUCT_SOCKADDR_IN_SIN_LEN su->sin.sin_len = size; #endif /* HAVE_STRUCT_SOCKADDR_IN_SIN_LEN */ if (su_addr == NULL) sockunion2ip(su) = htonl(INADDR_ANY); } else if (su->sa.sa_family == AF_INET6) { size = sizeof(struct sockaddr_in6); su->sin6.sin6_port = htons(port); #ifdef SIN6_LEN su->sin6.sin6_len = size; #endif /* SIN6_LEN */ if (su_addr == NULL) { #ifdef LINUX_IPV6 memset(&su->sin6.sin6_addr, 0, sizeof(struct in6_addr)); #else su->sin6.sin6_addr = in6addr_any; #endif /* LINUX_IPV6 */ } } ret = bind(sock, (struct sockaddr *)su, size); if (ret < 0) { char buf[SU_ADDRSTRLEN]; flog_err(EC_LIB_SOCKET, "can't bind socket for %s : %s", sockunion_log(su, buf, SU_ADDRSTRLEN), safe_strerror(errno)); } return ret; } int sockopt_reuseaddr(int sock) { int ret; int on = 1; ret = setsockopt(sock, SOL_SOCKET, SO_REUSEADDR, (void *)&on, sizeof(on)); if (ret < 0) { flog_err(EC_LIB_SOCKET, "can't set sockopt SO_REUSEADDR to socket %d", sock); return -1; } return 0; } #ifdef SO_REUSEPORT int sockopt_reuseport(int sock) { int ret; int on = 1; ret = setsockopt(sock, SOL_SOCKET, SO_REUSEPORT, (void *)&on, sizeof(on)); if (ret < 0) { flog_err(EC_LIB_SOCKET, "can't set sockopt SO_REUSEPORT to socket %d", sock); return -1; } return 0; } #else int sockopt_reuseport(int sock) { return 0; } #endif /* 0 */ int sockopt_ttl(int family, int sock, int ttl) { int ret; #ifdef IP_TTL if (family == AF_INET) { ret = setsockopt(sock, IPPROTO_IP, IP_TTL, (void *)&ttl, sizeof(int)); if (ret < 0) { flog_err(EC_LIB_SOCKET, "can't set sockopt IP_TTL %d to socket %d", ttl, sock); return -1; } return 0; } #endif /* IP_TTL */ if (family == AF_INET6) { ret = setsockopt(sock, IPPROTO_IPV6, IPV6_UNICAST_HOPS, (void *)&ttl, sizeof(int)); if (ret < 0) { flog_err( EC_LIB_SOCKET, "can't set sockopt IPV6_UNICAST_HOPS %d to socket %d", ttl, sock); return -1; } return 0; } return 0; } /* * This function called setsockopt(.., TCP_CORK,...) * Which on linux is a no-op since it is enabled by * default and on BSD it uses TCP_NOPUSH to do * the same thing( which it was not configured to * use). This cleanup of the api occurred on 8/1/17 * I imagine if after more than 1 year of no-one * complaining, and a major upgrade release we * can deprecate and remove this function call */ int sockopt_cork(int sock, int onoff) { return 0; } int sockopt_minttl(int family, int sock, int minttl) { #ifdef IP_MINTTL if (family == AF_INET) { int ret = setsockopt(sock, IPPROTO_IP, IP_MINTTL, &minttl, sizeof(minttl)); if (ret < 0) flog_err( EC_LIB_SOCKET, "can't set sockopt IP_MINTTL to %d on socket %d: %s", minttl, sock, safe_strerror(errno)); return ret; } #endif /* IP_MINTTL */ #ifdef IPV6_MINHOPCOUNT if (family == AF_INET6) { int ret = setsockopt(sock, IPPROTO_IPV6, IPV6_MINHOPCOUNT, &minttl, sizeof(minttl)); if (ret < 0) flog_err( EC_LIB_SOCKET, "can't set sockopt IPV6_MINHOPCOUNT to %d on socket %d: %s", minttl, sock, safe_strerror(errno)); return ret; } #endif errno = EOPNOTSUPP; return -1; } int sockopt_v6only(int family, int sock) { int ret, on = 1; #ifdef IPV6_V6ONLY if (family == AF_INET6) { ret = setsockopt(sock, IPPROTO_IPV6, IPV6_V6ONLY, (void *)&on, sizeof(int)); if (ret < 0) { flog_err(EC_LIB_SOCKET, "can't set sockopt IPV6_V6ONLY " "to socket %d", sock); return -1; } return 0; } #endif /* IPV6_V6ONLY */ return 0; } /* If same family and same prefix return 1. */ int sockunion_same(const union sockunion *su1, const union sockunion *su2) { int ret = 0; if (su1->sa.sa_family != su2->sa.sa_family) return 0; switch (su1->sa.sa_family) { case AF_INET: ret = memcmp(&su1->sin.sin_addr, &su2->sin.sin_addr, sizeof(struct in_addr)); break; case AF_INET6: ret = memcmp(&su1->sin6.sin6_addr, &su2->sin6.sin6_addr, sizeof(struct in6_addr)); if ((ret == 0) && IN6_IS_ADDR_LINKLOCAL(&su1->sin6.sin6_addr)) { /* compare interface indices */ if (su1->sin6.sin6_scope_id && su2->sin6.sin6_scope_id) ret = (su1->sin6.sin6_scope_id == su2->sin6.sin6_scope_id) ? 0 : 1; } break; } if (ret == 0) return 1; else return 0; } unsigned int sockunion_hash(const union sockunion *su) { switch (sockunion_family(su)) { case AF_INET: return jhash_1word(su->sin.sin_addr.s_addr, 0); case AF_INET6: return jhash2(su->sin6.sin6_addr.s6_addr32, array_size(su->sin6.sin6_addr.s6_addr32), 0); } return 0; } size_t family2addrsize(int family) { switch (family) { case AF_INET: return sizeof(struct in_addr); case AF_INET6: return sizeof(struct in6_addr); } return 0; } size_t sockunion_get_addrlen(const union sockunion *su) { return family2addrsize(sockunion_family(su)); } const uint8_t *sockunion_get_addr(const union sockunion *su) { switch (sockunion_family(su)) { case AF_INET: return (const uint8_t *)&su->sin.sin_addr.s_addr; case AF_INET6: return (const uint8_t *)&su->sin6.sin6_addr; } return NULL; } void sockunion_set(union sockunion *su, int family, const uint8_t *addr, size_t bytes) { if (family2addrsize(family) != bytes) return; sockunion_family(su) = family; switch (family) { case AF_INET: memcpy(&su->sin.sin_addr.s_addr, addr, bytes); break; case AF_INET6: memcpy(&su->sin6.sin6_addr, addr, bytes); break; } } /* After TCP connection is established. Get local address and port. */ union sockunion *sockunion_getsockname(int fd) { int ret; socklen_t len; union { struct sockaddr sa; struct sockaddr_in sin; struct sockaddr_in6 sin6; char tmp_buffer[128]; } name; union sockunion *su; memset(&name, 0, sizeof name); len = sizeof name; ret = getsockname(fd, (struct sockaddr *)&name, &len); if (ret < 0) { flog_err(EC_LIB_SOCKET, "Can't get local address and port by getsockname: %s", safe_strerror(errno)); return NULL; } if (name.sa.sa_family == AF_INET) { su = XCALLOC(MTYPE_SOCKUNION, sizeof(union sockunion)); memcpy(su, &name, sizeof(struct sockaddr_in)); return su; } if (name.sa.sa_family == AF_INET6) { su = XCALLOC(MTYPE_SOCKUNION, sizeof(union sockunion)); memcpy(su, &name, sizeof(struct sockaddr_in6)); sockunion_normalise_mapped(su); return su; } return NULL; } /* After TCP connection is established. Get remote address and port. */ union sockunion *sockunion_getpeername(int fd) { int ret; socklen_t len; union { struct sockaddr sa; struct sockaddr_in sin; struct sockaddr_in6 sin6; char tmp_buffer[128]; } name; union sockunion *su; memset(&name, 0, sizeof name); len = sizeof name; ret = getpeername(fd, (struct sockaddr *)&name, &len); if (ret < 0) { flog_err(EC_LIB_SOCKET, "Can't get remote address and port: %s", safe_strerror(errno)); return NULL; } if (name.sa.sa_family == AF_INET) { su = XCALLOC(MTYPE_SOCKUNION, sizeof(union sockunion)); memcpy(su, &name, sizeof(struct sockaddr_in)); return su; } if (name.sa.sa_family == AF_INET6) { su = XCALLOC(MTYPE_SOCKUNION, sizeof(union sockunion)); memcpy(su, &name, sizeof(struct sockaddr_in6)); sockunion_normalise_mapped(su); return su; } return NULL; } /* Print sockunion structure */ static void __attribute__((unused)) sockunion_print(const union sockunion *su) { if (su == NULL) return; switch (su->sa.sa_family) { case AF_INET: printf("%s\n", inet_ntoa(su->sin.sin_addr)); break; case AF_INET6: { char buf[SU_ADDRSTRLEN]; printf("%s\n", inet_ntop(AF_INET6, &(su->sin6.sin6_addr), buf, sizeof(buf))); } break; #ifdef AF_LINK case AF_LINK: { struct sockaddr_dl *sdl; sdl = (struct sockaddr_dl *)&(su->sa); printf("link#%d\n", sdl->sdl_index); } break; #endif /* AF_LINK */ default: printf("af_unknown %d\n", su->sa.sa_family); break; } } static int in6addr_cmp(const struct in6_addr *addr1, const struct in6_addr *addr2) { unsigned int i; const uint8_t *p1, *p2; p1 = (const uint8_t *)addr1; p2 = (const uint8_t *)addr2; for (i = 0; i < sizeof(struct in6_addr); i++) { if (p1[i] > p2[i]) return 1; else if (p1[i] < p2[i]) return -1; } return 0; } int sockunion_cmp(const union sockunion *su1, const union sockunion *su2) { if (su1->sa.sa_family > su2->sa.sa_family) return 1; if (su1->sa.sa_family < su2->sa.sa_family) return -1; if (su1->sa.sa_family == AF_INET) { if (ntohl(sockunion2ip(su1)) == ntohl(sockunion2ip(su2))) return 0; if (ntohl(sockunion2ip(su1)) > ntohl(sockunion2ip(su2))) return 1; else return -1; } if (su1->sa.sa_family == AF_INET6) return in6addr_cmp(&su1->sin6.sin6_addr, &su2->sin6.sin6_addr); return 0; } /* Duplicate sockunion. */ union sockunion *sockunion_dup(const union sockunion *su) { union sockunion *dup = XCALLOC(MTYPE_SOCKUNION, sizeof(union sockunion)); memcpy(dup, su, sizeof(union sockunion)); return dup; } void sockunion_free(union sockunion *su) { XFREE(MTYPE_SOCKUNION, su); } void sockunion_init(union sockunion *su) { memset(su, 0, sizeof(union sockunion)); } frr-7.2.1/lib/sockunion.h0000644000000000000000000000750613610377563012153 00000000000000/* * Socket union header. * Copyright (c) 1997 Kunihiro Ishiguro * * This file is part of GNU Zebra. * * GNU Zebra is free software; you can redistribute it and/or modify it * under the terms of the GNU General Public License as published by the * Free Software Foundation; either version 2, or (at your option) any * later version. * * GNU Zebra 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; see the file COPYING; if not, write to the Free Software * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA */ #ifndef _ZEBRA_SOCKUNION_H #define _ZEBRA_SOCKUNION_H #include "privs.h" #include "if.h" #ifdef __OpenBSD__ #include #endif #ifdef __cplusplus extern "C" { #endif union sockunion { struct sockaddr sa; struct sockaddr_in sin; struct sockaddr_in6 sin6; #ifdef __OpenBSD__ struct sockaddr_mpls smpls; struct sockaddr_rtlabel rtlabel; #endif }; enum connect_result { connect_error, connect_success, connect_in_progress }; /* Default address family. */ #define AF_INET_UNION AF_INET6 /* Sockunion address string length. Same as INET6_ADDRSTRLEN. */ #define SU_ADDRSTRLEN 46 /* Macro to set link local index to the IPv6 address. For KAME IPv6 stack. */ #ifdef KAME #define IN6_LINKLOCAL_IFINDEX(a) ((a).s6_addr[2] << 8 | (a).s6_addr[3]) #define SET_IN6_LINKLOCAL_IFINDEX(a, i) \ do { \ (a).s6_addr[2] = ((i) >> 8) & 0xff; \ (a).s6_addr[3] = (i)&0xff; \ } while (0) #else #define IN6_LINKLOCAL_IFINDEX(a) #define SET_IN6_LINKLOCAL_IFINDEX(a, i) #endif /* KAME */ #define sockunion_family(X) (X)->sa.sa_family #define sockunion2ip(X) (X)->sin.sin_addr.s_addr /* Prototypes. */ extern int str2sockunion(const char *, union sockunion *); extern const char *sockunion2str(const union sockunion *, char *, size_t); extern int sockunion_cmp(const union sockunion *, const union sockunion *); extern int sockunion_same(const union sockunion *, const union sockunion *); extern unsigned int sockunion_hash(const union sockunion *); extern size_t family2addrsize(int family); extern size_t sockunion_get_addrlen(const union sockunion *); extern const uint8_t *sockunion_get_addr(const union sockunion *); extern void sockunion_set(union sockunion *, int family, const uint8_t *addr, size_t bytes); extern union sockunion *sockunion_str2su(const char *str); extern int sockunion_accept(int sock, union sockunion *); extern int sockunion_sizeof(const union sockunion *su); extern int sockunion_stream_socket(union sockunion *); extern int sockopt_reuseaddr(int); extern int sockopt_reuseport(int); extern int sockopt_v6only(int family, int sock); extern int sockunion_bind(int sock, union sockunion *, unsigned short, union sockunion *); extern int sockopt_ttl(int family, int sock, int ttl); extern int sockopt_minttl(int family, int sock, int minttl); extern int sockopt_cork(int sock, int onoff); extern int sockunion_socket(const union sockunion *su); extern const char *inet_sutop(const union sockunion *su, char *str); extern enum connect_result sockunion_connect(int fd, const union sockunion *su, unsigned short port, ifindex_t); extern union sockunion *sockunion_getsockname(int); extern union sockunion *sockunion_getpeername(int); extern union sockunion *sockunion_dup(const union sockunion *); extern void sockunion_free(union sockunion *); extern void sockunion_init(union sockunion *); #ifdef __cplusplus } #endif #endif /* _ZEBRA_SOCKUNION_H */ frr-7.2.1/lib/spf_backoff.c0000644000000000000000000002022313610377563012370 00000000000000/* * This is an implementation of the IETF SPF delay algorithm * as explained in draft-ietf-rtgwg-backoff-algo-04 * * Created: 25-01-2017 by S. Litkowski * * Copyright (C) 2017 Orange Labs http://www.orange.com/ * Copyright (C) 2017 by Christian Franke, Open Source Routing / NetDEF Inc. * * This file is part of FreeRangeRouting (FRR) * * FRR is free software; you can redistribute it and/or modify it * under the terms of the GNU General Public License as published by the * Free Software Foundation; either version 2, or (at your option) any * later version. * * FRR 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; see the file COPYING; if not, write to the Free Software * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA */ #include #include "spf_backoff.h" #include "command.h" #include "memory.h" #include "thread.h" #include "vty.h" DEFINE_MTYPE_STATIC(LIB, SPF_BACKOFF, "SPF backoff") DEFINE_MTYPE_STATIC(LIB, SPF_BACKOFF_NAME, "SPF backoff name") static bool debug_spf_backoff = false; #define backoff_debug(...) \ do { \ if (debug_spf_backoff) \ zlog_debug(__VA_ARGS__); \ } while (0) enum spf_backoff_state { SPF_BACKOFF_QUIET, SPF_BACKOFF_SHORT_WAIT, SPF_BACKOFF_LONG_WAIT }; struct spf_backoff { struct thread_master *m; /* Timers as per draft */ long init_delay; long short_delay; long long_delay; long holddown; long timetolearn; /* State machine */ enum spf_backoff_state state; struct thread *t_holddown; struct thread *t_timetolearn; /* For debugging */ char *name; struct timeval first_event_time; struct timeval last_event_time; }; static const char *spf_backoff_state2str(enum spf_backoff_state state) { switch (state) { case SPF_BACKOFF_QUIET: return "QUIET"; case SPF_BACKOFF_SHORT_WAIT: return "SHORT_WAIT"; case SPF_BACKOFF_LONG_WAIT: return "LONG_WAIT"; } return "???"; } struct spf_backoff *spf_backoff_new(struct thread_master *m, const char *name, long init_delay, long short_delay, long long_delay, long holddown, long timetolearn) { struct spf_backoff *rv; rv = XCALLOC(MTYPE_SPF_BACKOFF, sizeof(*rv)); rv->m = m; rv->init_delay = init_delay; rv->short_delay = short_delay; rv->long_delay = long_delay; rv->holddown = holddown; rv->timetolearn = timetolearn; rv->state = SPF_BACKOFF_QUIET; rv->name = XSTRDUP(MTYPE_SPF_BACKOFF_NAME, name); return rv; } void spf_backoff_free(struct spf_backoff *backoff) { if (!backoff) return; THREAD_TIMER_OFF(backoff->t_holddown); THREAD_TIMER_OFF(backoff->t_timetolearn); XFREE(MTYPE_SPF_BACKOFF_NAME, backoff->name); XFREE(MTYPE_SPF_BACKOFF, backoff); } static int spf_backoff_timetolearn_elapsed(struct thread *thread) { struct spf_backoff *backoff = THREAD_ARG(thread); backoff->t_timetolearn = NULL; backoff->state = SPF_BACKOFF_LONG_WAIT; backoff_debug("SPF Back-off(%s) TIMETOLEARN elapsed, move to state %s", backoff->name, spf_backoff_state2str(backoff->state)); return 0; } static int spf_backoff_holddown_elapsed(struct thread *thread) { struct spf_backoff *backoff = THREAD_ARG(thread); backoff->t_holddown = NULL; THREAD_TIMER_OFF(backoff->t_timetolearn); timerclear(&backoff->first_event_time); backoff->state = SPF_BACKOFF_QUIET; backoff_debug("SPF Back-off(%s) HOLDDOWN elapsed, move to state %s", backoff->name, spf_backoff_state2str(backoff->state)); return 0; } long spf_backoff_schedule(struct spf_backoff *backoff) { long rv = 0; struct timeval now; gettimeofday(&now, NULL); backoff_debug("SPF Back-off(%s) schedule called in state %s", backoff->name, spf_backoff_state2str(backoff->state)); backoff->last_event_time = now; switch (backoff->state) { case SPF_BACKOFF_QUIET: backoff->state = SPF_BACKOFF_SHORT_WAIT; thread_add_timer_msec( backoff->m, spf_backoff_timetolearn_elapsed, backoff, backoff->timetolearn, &backoff->t_timetolearn); thread_add_timer_msec(backoff->m, spf_backoff_holddown_elapsed, backoff, backoff->holddown, &backoff->t_holddown); backoff->first_event_time = now; rv = backoff->init_delay; break; case SPF_BACKOFF_SHORT_WAIT: case SPF_BACKOFF_LONG_WAIT: THREAD_TIMER_OFF(backoff->t_holddown); thread_add_timer_msec(backoff->m, spf_backoff_holddown_elapsed, backoff, backoff->holddown, &backoff->t_holddown); if (backoff->state == SPF_BACKOFF_SHORT_WAIT) rv = backoff->short_delay; else rv = backoff->long_delay; break; } backoff_debug( "SPF Back-off(%s) changed state to %s and returned %ld delay", backoff->name, spf_backoff_state2str(backoff->state), rv); return rv; } static const char *timeval_format(struct timeval *tv) { struct tm tm_store; struct tm *tm; static char timebuf[256]; if (!tv->tv_sec && !tv->tv_usec) return "(never)"; tm = localtime_r(&tv->tv_sec, &tm_store); if (!tm || strftime(timebuf, sizeof(timebuf), "%Z %a %Y-%m-%d %H:%M:%S", tm) == 0) { return "???"; } size_t offset = strlen(timebuf); snprintf(timebuf + offset, sizeof(timebuf) - offset, ".%ld", (long int)tv->tv_usec); return timebuf; } void spf_backoff_show(struct spf_backoff *backoff, struct vty *vty, const char *prefix) { vty_out(vty, "%sCurrent state: %s\n", prefix, spf_backoff_state2str(backoff->state)); vty_out(vty, "%sInit timer: %ld msec\n", prefix, backoff->init_delay); vty_out(vty, "%sShort timer: %ld msec\n", prefix, backoff->short_delay); vty_out(vty, "%sLong timer: %ld msec\n", prefix, backoff->long_delay); vty_out(vty, "%sHolddown timer: %ld msec\n", prefix, backoff->holddown); if (backoff->t_holddown) { struct timeval remain = thread_timer_remain(backoff->t_holddown); vty_out(vty, "%s Still runs for %lld msec\n", prefix, (long long)remain.tv_sec * 1000 + remain.tv_usec / 1000); } else { vty_out(vty, "%s Inactive\n", prefix); } vty_out(vty, "%sTimeToLearn timer: %ld msec\n", prefix, backoff->timetolearn); if (backoff->t_timetolearn) { struct timeval remain = thread_timer_remain(backoff->t_timetolearn); vty_out(vty, "%s Still runs for %lld msec\n", prefix, (long long)remain.tv_sec * 1000 + remain.tv_usec / 1000); } else { vty_out(vty, "%s Inactive\n", prefix); } vty_out(vty, "%sFirst event: %s\n", prefix, timeval_format(&backoff->first_event_time)); vty_out(vty, "%sLast event: %s\n", prefix, timeval_format(&backoff->last_event_time)); } DEFUN(spf_backoff_debug, spf_backoff_debug_cmd, "debug spf-delay-ietf", DEBUG_STR "SPF Back-off Debugging\n") { debug_spf_backoff = true; return CMD_SUCCESS; } DEFUN(no_spf_backoff_debug, no_spf_backoff_debug_cmd, "no debug spf-delay-ietf", NO_STR DEBUG_STR "SPF Back-off Debugging\n") { debug_spf_backoff = false; return CMD_SUCCESS; } int spf_backoff_write_config(struct vty *vty) { int written = 0; if (debug_spf_backoff) { vty_out(vty, "debug spf-delay-ietf\n"); written++; } return written; } void spf_backoff_cmd_init(void) { install_element(ENABLE_NODE, &spf_backoff_debug_cmd); install_element(CONFIG_NODE, &spf_backoff_debug_cmd); install_element(ENABLE_NODE, &no_spf_backoff_debug_cmd); install_element(CONFIG_NODE, &no_spf_backoff_debug_cmd); } long spf_backoff_init_delay(struct spf_backoff *backoff) { return backoff->init_delay; } long spf_backoff_short_delay(struct spf_backoff *backoff) { return backoff->short_delay; } long spf_backoff_long_delay(struct spf_backoff *backoff) { return backoff->long_delay; } long spf_backoff_holddown(struct spf_backoff *backoff) { return backoff->holddown; } long spf_backoff_timetolearn(struct spf_backoff *backoff) { return backoff->timetolearn; } frr-7.2.1/lib/spf_backoff.h0000644000000000000000000000443513610377563012404 00000000000000/* * This is an implementation of the IETF SPF delay algorithm * as explained in draft-ietf-rtgwg-backoff-algo-04 * * Created: 25-01-2017 by S. Litkowski * * Copyright (C) 2017 Orange Labs http://www.orange.com/ * Copyright (C) 2017 by Christian Franke, Open Source Routing / NetDEF Inc. * * This file is part of FreeRangeRouting (FRR) * * FRR is free software; you can redistribute it and/or modify it * under the terms of the GNU General Public License as published by the * Free Software Foundation; either version 2, or (at your option) any * later version. * * FRR 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; see the file COPYING; if not, write to the Free Software * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA */ #ifndef _ZEBRA_SPF_BACKOFF_H #define _ZEBRA_SPF_BACKOFF_H #ifdef __cplusplus extern "C" { #endif struct spf_backoff; struct thread_master; struct vty; struct spf_backoff *spf_backoff_new(struct thread_master *m, const char *name, long init_delay, long short_delay, long long_delay, long holddown, long timetolearn); void spf_backoff_free(struct spf_backoff *backoff); /* Called whenever an IGP event is received, returns how many * milliseconds routing table computation should be delayed */ long spf_backoff_schedule(struct spf_backoff *backoff); /* Shows status of SPF backoff instance */ void spf_backoff_show(struct spf_backoff *backoff, struct vty *vty, const char *prefix); /* Writes out global SPF backoff debug config */ int spf_backoff_write_config(struct vty *vty); /* Registers global SPF backoff debug commands */ void spf_backoff_cmd_init(void); /* Accessor functions for SPF backoff parameters */ long spf_backoff_init_delay(struct spf_backoff *backoff); long spf_backoff_short_delay(struct spf_backoff *backoff); long spf_backoff_long_delay(struct spf_backoff *backoff); long spf_backoff_holddown(struct spf_backoff *backoff); long spf_backoff_timetolearn(struct spf_backoff *backoff); #ifdef __cplusplus } #endif #endif frr-7.2.1/lib/srcdest_table.c0000644000000000000000000002160013610377563012743 00000000000000/* * SRC-DEST Routing Table * * Copyright (C) 2017 by David Lamparter & Christian Franke, * Open Source Routing / NetDEF Inc. * * This file is part of FreeRangeRouting (FRR) * * FRR is free software; you can redistribute it and/or modify it * under the terms of the GNU General Public License as published by the * Free Software Foundation; either version 2, or (at your option) any * later version. * * FRR 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; see the file COPYING; if not, write to the Free Software * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA */ #include #include "srcdest_table.h" #include "memory.h" #include "prefix.h" #include "table.h" #include "printfrr.h" DEFINE_MTYPE_STATIC(LIB, ROUTE_SRC_NODE, "Route source node") /* ----- functions to manage rnodes _with_ srcdest table ----- */ struct srcdest_rnode { /* must be first in structure for casting to/from route_node */ ROUTE_NODE_FIELDS; struct route_table *src_table; }; static struct srcdest_rnode *srcdest_rnode_from_rnode(struct route_node *rn) { assert(rnode_is_dstnode(rn)); return (struct srcdest_rnode *)rn; } static struct route_node *srcdest_rnode_to_rnode(struct srcdest_rnode *srn) { return (struct route_node *)srn; } static struct route_node *srcdest_rnode_create(route_table_delegate_t *delegate, struct route_table *table) { struct srcdest_rnode *srn; srn = XCALLOC(MTYPE_ROUTE_NODE, sizeof(struct srcdest_rnode)); return srcdest_rnode_to_rnode(srn); } static void srcdest_rnode_destroy(route_table_delegate_t *delegate, struct route_table *table, struct route_node *rn) { struct srcdest_rnode *srn = srcdest_rnode_from_rnode(rn); struct route_table *src_table; /* Clear route node's src_table here already, otherwise the * deletion of the last node in the src_table will trigger * another call to route_table_finish for the src_table. * * (Compare with srcdest_srcnode_destroy) */ src_table = srn->src_table; srn->src_table = NULL; route_table_finish(src_table); XFREE(MTYPE_ROUTE_NODE, rn); } route_table_delegate_t _srcdest_dstnode_delegate = { .create_node = srcdest_rnode_create, .destroy_node = srcdest_rnode_destroy}; /* ----- functions to manage rnodes _in_ srcdest table ----- */ /* node creation / deletion for srcdest source prefix nodes. * the route_node isn't actually different from the normal route_node, * but the cleanup is special to free the table (and possibly the * destination prefix's route_node) */ static struct route_node * srcdest_srcnode_create(route_table_delegate_t *delegate, struct route_table *table) { return XCALLOC(MTYPE_ROUTE_SRC_NODE, sizeof(struct route_node)); } static void srcdest_srcnode_destroy(route_table_delegate_t *delegate, struct route_table *table, struct route_node *rn) { struct srcdest_rnode *srn; XFREE(MTYPE_ROUTE_SRC_NODE, rn); srn = route_table_get_info(table); if (srn->src_table && route_table_count(srn->src_table) == 0) { /* deleting the route_table from inside destroy_node is ONLY * permitted IF table->count is 0! see lib/table.c * route_node_delete() * for details */ route_table_finish(srn->src_table); srn->src_table = NULL; /* drop the ref we're holding in srcdest_node_get(). there * might be * non-srcdest routes, so the route_node may still exist. * hence, it's * important to clear src_table above. */ route_unlock_node(srcdest_rnode_to_rnode(srn)); } } route_table_delegate_t _srcdest_srcnode_delegate = { .create_node = srcdest_srcnode_create, .destroy_node = srcdest_srcnode_destroy}; /* NB: read comments in code for refcounting before using! */ static struct route_node *srcdest_srcnode_get(struct route_node *rn, const struct prefix_ipv6 *src_p) { struct srcdest_rnode *srn; if (!src_p || src_p->prefixlen == 0) return rn; srn = srcdest_rnode_from_rnode(rn); if (!srn->src_table) { /* this won't use srcdest_rnode, we're already on the source * here */ srn->src_table = route_table_init_with_delegate( &_srcdest_srcnode_delegate); route_table_set_info(srn->src_table, srn); /* there is no route_unlock_node on the original rn here. * The reference is kept for the src_table. */ } else { /* only keep 1 reference for the src_table, makes the * refcounting * more similar to the non-srcdest case. Either way after * return from * function, the only reference held is the one on the return * value. * * We can safely drop our reference here because src_table is * holding * another reference, so this won't free rn */ route_unlock_node(rn); } return route_node_get(srn->src_table, (const struct prefix *)src_p); } static struct route_node *srcdest_srcnode_lookup( struct route_node *rn, const struct prefix_ipv6 *src_p) { struct srcdest_rnode *srn; if (!rn || !src_p || src_p->prefixlen == 0) return rn; /* We got this rn from a lookup, so its refcnt was incremented. As we * won't * return return rn from any point beyond here, we should decrement its * refcnt. */ route_unlock_node(rn); srn = srcdest_rnode_from_rnode(rn); if (!srn->src_table) return NULL; return route_node_lookup(srn->src_table, (const struct prefix *)src_p); } /* ----- exported functions ----- */ struct route_table *srcdest_table_init(void) { return route_table_init_with_delegate(&_srcdest_dstnode_delegate); } struct route_node *srcdest_route_next(struct route_node *rn) { struct route_node *next, *parent; /* For a non src-dest node, just return route_next */ if (!(rnode_is_dstnode(rn) || rnode_is_srcnode(rn))) return route_next(rn); if (rnode_is_dstnode(rn)) { /* This means the route_node is part of the top hierarchy * and refers to a destination prefix. */ struct srcdest_rnode *srn = srcdest_rnode_from_rnode(rn); if (srn->src_table) next = route_top(srn->src_table); else next = NULL; if (next) { /* There is a source prefix. Return the node for it */ route_unlock_node(rn); return next; } else { /* There is no source prefix, just continue as usual */ return route_next(rn); } } /* This part handles the case of iterating source nodes. */ parent = route_lock_node(route_table_get_info(rn->table)); next = route_next(rn); if (next) { /* There is another source node, continue in the source table */ route_unlock_node(parent); return next; } else { /* The source table is complete, continue in the parent table */ return route_next(parent); } } struct route_node *srcdest_rnode_get(struct route_table *table, union prefixconstptr dst_pu, const struct prefix_ipv6 *src_p) { const struct prefix_ipv6 *dst_p = dst_pu.p6; struct route_node *rn; rn = route_node_get(table, (const struct prefix *)dst_p); return srcdest_srcnode_get(rn, src_p); } struct route_node *srcdest_rnode_lookup(struct route_table *table, union prefixconstptr dst_pu, const struct prefix_ipv6 *src_p) { const struct prefix_ipv6 *dst_p = dst_pu.p6; struct route_node *rn; struct route_node *srn; rn = route_node_lookup_maynull(table, (const struct prefix *)dst_p); srn = srcdest_srcnode_lookup(rn, src_p); if (rn != NULL && rn == srn && !rn->info) { /* Match the behavior of route_node_lookup and don't return an * empty route-node for a dest-route */ route_unlock_node(rn); return NULL; } return srn; } void srcdest_rnode_prefixes(const struct route_node *rn, const struct prefix **p, const struct prefix **src_p) { if (rnode_is_srcnode(rn)) { struct route_node *dst_rn = route_table_get_info(rn->table); if (p) *p = &dst_rn->p; if (src_p) *src_p = &rn->p; } else { if (p) *p = &rn->p; if (src_p) *src_p = NULL; } } const char *srcdest2str(const struct prefix *dst_p, const struct prefix_ipv6 *src_p, char *str, int size) { char dst_buf[PREFIX_STRLEN], src_buf[PREFIX_STRLEN]; snprintf(str, size, "%s%s%s", prefix2str(dst_p, dst_buf, sizeof(dst_buf)), (src_p && src_p->prefixlen) ? " from " : "", (src_p && src_p->prefixlen) ? prefix2str(src_p, src_buf, sizeof(src_buf)) : ""); return str; } const char *srcdest_rnode2str(const struct route_node *rn, char *str, int size) { const struct prefix *dst_p, *src_p; srcdest_rnode_prefixes(rn, &dst_p, &src_p); return srcdest2str(dst_p, (const struct prefix_ipv6 *)src_p, str, size); } printfrr_ext_autoreg_p("RN", printfrr_rn) static ssize_t printfrr_rn(char *buf, size_t bsz, const char *fmt, int prec, const void *ptr) { const struct route_node *rn = ptr; const struct prefix *dst_p, *src_p; srcdest_rnode_prefixes(rn, &dst_p, &src_p); srcdest2str(dst_p, (const struct prefix_ipv6 *)src_p, buf, bsz); return 2; } frr-7.2.1/lib/srcdest_table.h0000644000000000000000000000674313610377563012763 00000000000000/* * SRC-DEST Routing Table * * Copyright (C) 2017 by David Lamparter & Christian Franke, * Open Source Routing / NetDEF Inc. * * This file is part of FreeRangeRouting (FRR) * * FRR is free software; you can redistribute it and/or modify it * under the terms of the GNU General Public License as published by the * Free Software Foundation; either version 2, or (at your option) any * later version. * * FRR 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; see the file COPYING; if not, write to the Free Software * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA */ #ifndef _ZEBRA_SRC_DEST_TABLE_H #define _ZEBRA_SRC_DEST_TABLE_H /* old/IPv4/non-srcdest: * table -> route_node .info -> [obj] * * new/IPv6/srcdest: * table -...-> srcdest_rnode [prefix = dest] .info -> [obj] * .src_table -> * srcdest table -...-> route_node [prefix = src] .info -> [obj] * * non-srcdest routes (src = ::/0) are treated just like before, their * information being directly there in the info pointer. * * srcdest routes are found by looking up destination first, then looking * up the source in the "src_table". src_table contains normal route_nodes, * whose prefix is the _source_ prefix. * * NB: info can be NULL on the destination rnode, if there are only srcdest * routes for a particular destination prefix. */ #include "prefix.h" #include "table.h" #ifdef __cplusplus extern "C" { #endif #define SRCDEST2STR_BUFFER (2*PREFIX2STR_BUFFER + sizeof(" from ")) /* extended route node for IPv6 srcdest routing */ struct srcdest_rnode; extern route_table_delegate_t _srcdest_dstnode_delegate; extern route_table_delegate_t _srcdest_srcnode_delegate; extern struct route_table *srcdest_table_init(void); extern struct route_node *srcdest_rnode_get(struct route_table *table, union prefixconstptr dst_pu, const struct prefix_ipv6 *src_p); extern struct route_node *srcdest_rnode_lookup(struct route_table *table, union prefixconstptr dst_pu, const struct prefix_ipv6 *src_p); extern void srcdest_rnode_prefixes(const struct route_node *rn, const struct prefix **p, const struct prefix **src_p); extern const char *srcdest2str(const struct prefix *dst_p, const struct prefix_ipv6 *src_p, char *str, int size); extern const char *srcdest_rnode2str(const struct route_node *rn, char *str, int size); extern struct route_node *srcdest_route_next(struct route_node *rn); static inline int rnode_is_dstnode(const struct route_node *rn) { return rn->table->delegate == &_srcdest_dstnode_delegate; } static inline int rnode_is_srcnode(const struct route_node *rn) { return rn->table->delegate == &_srcdest_srcnode_delegate; } static inline struct route_table *srcdest_rnode_table(struct route_node *rn) { if (rnode_is_srcnode(rn)) { struct route_node *dst_rn = (struct route_node *)route_table_get_info(rn->table); return dst_rn->table; } else { return rn->table; } } static inline void *srcdest_rnode_table_info(struct route_node *rn) { return route_table_get_info(srcdest_rnode_table(rn)); } #ifdef __cplusplus } #endif #endif /* _ZEBRA_SRC_DEST_TABLE_H */ frr-7.2.1/lib/stream.c0000644000000000000000000006264113610377563011432 00000000000000/* * Packet interface * Copyright (C) 1999 Kunihiro Ishiguro * * This file is part of GNU Zebra. * * GNU Zebra is free software; you can redistribute it and/or modify it * under the terms of the GNU General Public License as published by the * Free Software Foundation; either version 2, or (at your option) any * later version. * * GNU Zebra 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; see the file COPYING; if not, write to the Free Software * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA */ #include #include #include #include "stream.h" #include "memory.h" #include "network.h" #include "prefix.h" #include "log.h" #include "frr_pthread.h" #include "lib_errors.h" DEFINE_MTYPE_STATIC(LIB, STREAM, "Stream") DEFINE_MTYPE_STATIC(LIB, STREAM_FIFO, "Stream FIFO") /* Tests whether a position is valid */ #define GETP_VALID(S, G) ((G) <= (S)->endp) #define PUT_AT_VALID(S,G) GETP_VALID(S,G) #define ENDP_VALID(S, E) ((E) <= (S)->size) /* asserting sanity checks. Following must be true before * stream functions are called: * * Following must always be true of stream elements * before and after calls to stream functions: * * getp <= endp <= size * * Note that after a stream function is called following may be true: * if (getp == endp) then stream is no longer readable * if (endp == size) then stream is no longer writeable * * It is valid to put to anywhere within the size of the stream, but only * using stream_put..._at() functions. */ #define STREAM_WARN_OFFSETS(S) \ flog_warn(EC_LIB_STREAM, \ "&(struct stream): %p, size: %lu, getp: %lu, endp: %lu\n", \ (void *)(S), (unsigned long)(S)->size, \ (unsigned long)(S)->getp, (unsigned long)(S)->endp) #define STREAM_VERIFY_SANE(S) \ do { \ if (!(GETP_VALID(S, (S)->getp) && ENDP_VALID(S, (S)->endp))) \ STREAM_WARN_OFFSETS(S); \ assert(GETP_VALID(S, (S)->getp)); \ assert(ENDP_VALID(S, (S)->endp)); \ } while (0) #define STREAM_BOUND_WARN(S, WHAT) \ do { \ flog_warn(EC_LIB_STREAM, "%s: Attempt to %s out of bounds", \ __func__, (WHAT)); \ STREAM_WARN_OFFSETS(S); \ assert(0); \ } while (0) #define STREAM_BOUND_WARN2(S, WHAT) \ do { \ flog_warn(EC_LIB_STREAM, "%s: Attempt to %s out of bounds", \ __func__, (WHAT)); \ STREAM_WARN_OFFSETS(S); \ } while (0) /* XXX: Deprecated macro: do not use */ #define CHECK_SIZE(S, Z) \ do { \ if (((S)->endp + (Z)) > (S)->size) { \ flog_warn( \ EC_LIB_STREAM, \ "CHECK_SIZE: truncating requested size %lu\n", \ (unsigned long)(Z)); \ STREAM_WARN_OFFSETS(S); \ (Z) = (S)->size - (S)->endp; \ } \ } while (0); /* Make stream buffer. */ struct stream *stream_new(size_t size) { struct stream *s; assert(size > 0); s = XMALLOC(MTYPE_STREAM, sizeof(struct stream) + size); s->getp = s->endp = 0; s->next = NULL; s->size = size; return s; } /* Free it now. */ void stream_free(struct stream *s) { if (!s) return; XFREE(MTYPE_STREAM, s); } struct stream *stream_copy(struct stream *new, struct stream *src) { STREAM_VERIFY_SANE(src); assert(new != NULL); assert(STREAM_SIZE(new) >= src->endp); new->endp = src->endp; new->getp = src->getp; memcpy(new->data, src->data, src->endp); return new; } struct stream *stream_dup(struct stream *s) { struct stream *new; STREAM_VERIFY_SANE(s); if ((new = stream_new(s->endp)) == NULL) return NULL; return (stream_copy(new, s)); } struct stream *stream_dupcat(struct stream *s1, struct stream *s2, size_t offset) { struct stream *new; STREAM_VERIFY_SANE(s1); STREAM_VERIFY_SANE(s2); if ((new = stream_new(s1->endp + s2->endp)) == NULL) return NULL; memcpy(new->data, s1->data, offset); memcpy(new->data + offset, s2->data, s2->endp); memcpy(new->data + offset + s2->endp, s1->data + offset, (s1->endp - offset)); new->endp = s1->endp + s2->endp; return new; } size_t stream_resize_inplace(struct stream **sptr, size_t newsize) { struct stream *orig = *sptr; STREAM_VERIFY_SANE(orig); orig = XREALLOC(MTYPE_STREAM, orig, sizeof(struct stream) + newsize); orig->size = newsize; if (orig->endp > orig->size) orig->endp = orig->size; if (orig->getp > orig->endp) orig->getp = orig->endp; STREAM_VERIFY_SANE(orig); *sptr = orig; return orig->size; } size_t stream_get_getp(struct stream *s) { STREAM_VERIFY_SANE(s); return s->getp; } size_t stream_get_endp(struct stream *s) { STREAM_VERIFY_SANE(s); return s->endp; } size_t stream_get_size(struct stream *s) { STREAM_VERIFY_SANE(s); return s->size; } /* Stream structre' stream pointer related functions. */ void stream_set_getp(struct stream *s, size_t pos) { STREAM_VERIFY_SANE(s); if (!GETP_VALID(s, pos)) { STREAM_BOUND_WARN(s, "set getp"); pos = s->endp; } s->getp = pos; } void stream_set_endp(struct stream *s, size_t pos) { STREAM_VERIFY_SANE(s); if (!ENDP_VALID(s, pos)) { STREAM_BOUND_WARN(s, "set endp"); return; } /* * Make sure the current read pointer is not beyond the new endp. */ if (s->getp > pos) { STREAM_BOUND_WARN(s, "set endp"); return; } s->endp = pos; STREAM_VERIFY_SANE(s); } /* Forward pointer. */ void stream_forward_getp(struct stream *s, size_t size) { STREAM_VERIFY_SANE(s); if (!GETP_VALID(s, s->getp + size)) { STREAM_BOUND_WARN(s, "seek getp"); return; } s->getp += size; } void stream_forward_endp(struct stream *s, size_t size) { STREAM_VERIFY_SANE(s); if (!ENDP_VALID(s, s->endp + size)) { STREAM_BOUND_WARN(s, "seek endp"); return; } s->endp += size; } /* Copy from stream to destination. */ bool stream_get2(void *dst, struct stream *s, size_t size) { STREAM_VERIFY_SANE(s); if (STREAM_READABLE(s) < size) { STREAM_BOUND_WARN2(s, "get"); return false; } memcpy(dst, s->data + s->getp, size); s->getp += size; return true; } void stream_get(void *dst, struct stream *s, size_t size) { STREAM_VERIFY_SANE(s); if (STREAM_READABLE(s) < size) { STREAM_BOUND_WARN(s, "get"); return; } memcpy(dst, s->data + s->getp, size); s->getp += size; } /* Get next character from the stream. */ bool stream_getc2(struct stream *s, uint8_t *byte) { STREAM_VERIFY_SANE(s); if (STREAM_READABLE(s) < sizeof(uint8_t)) { STREAM_BOUND_WARN2(s, "get char"); return false; } *byte = s->data[s->getp++]; return true; } uint8_t stream_getc(struct stream *s) { uint8_t c; STREAM_VERIFY_SANE(s); if (STREAM_READABLE(s) < sizeof(uint8_t)) { STREAM_BOUND_WARN(s, "get char"); return 0; } c = s->data[s->getp++]; return c; } /* Get next character from the stream. */ uint8_t stream_getc_from(struct stream *s, size_t from) { uint8_t c; STREAM_VERIFY_SANE(s); if (!GETP_VALID(s, from + sizeof(uint8_t))) { STREAM_BOUND_WARN(s, "get char"); return 0; } c = s->data[from]; return c; } bool stream_getw2(struct stream *s, uint16_t *word) { STREAM_VERIFY_SANE(s); if (STREAM_READABLE(s) < sizeof(uint16_t)) { STREAM_BOUND_WARN2(s, "get "); return false; } *word = s->data[s->getp++] << 8; *word |= s->data[s->getp++]; return true; } /* Get next word from the stream. */ uint16_t stream_getw(struct stream *s) { uint16_t w; STREAM_VERIFY_SANE(s); if (STREAM_READABLE(s) < sizeof(uint16_t)) { STREAM_BOUND_WARN(s, "get "); return 0; } w = s->data[s->getp++] << 8; w |= s->data[s->getp++]; return w; } /* Get next word from the stream. */ uint16_t stream_getw_from(struct stream *s, size_t from) { uint16_t w; STREAM_VERIFY_SANE(s); if (!GETP_VALID(s, from + sizeof(uint16_t))) { STREAM_BOUND_WARN(s, "get "); return 0; } w = s->data[from++] << 8; w |= s->data[from]; return w; } /* Get next 3-byte from the stream. */ uint32_t stream_get3_from(struct stream *s, size_t from) { uint32_t l; STREAM_VERIFY_SANE(s); if (!GETP_VALID(s, from + 3)) { STREAM_BOUND_WARN(s, "get 3byte"); return 0; } l = s->data[from++] << 16; l |= s->data[from++] << 8; l |= s->data[from]; return l; } uint32_t stream_get3(struct stream *s) { uint32_t l; STREAM_VERIFY_SANE(s); if (STREAM_READABLE(s) < 3) { STREAM_BOUND_WARN(s, "get 3byte"); return 0; } l = s->data[s->getp++] << 16; l |= s->data[s->getp++] << 8; l |= s->data[s->getp++]; return l; } /* Get next long word from the stream. */ uint32_t stream_getl_from(struct stream *s, size_t from) { uint32_t l; STREAM_VERIFY_SANE(s); if (!GETP_VALID(s, from + sizeof(uint32_t))) { STREAM_BOUND_WARN(s, "get long"); return 0; } l = (unsigned)(s->data[from++]) << 24; l |= s->data[from++] << 16; l |= s->data[from++] << 8; l |= s->data[from]; return l; } /* Copy from stream at specific location to destination. */ void stream_get_from(void *dst, struct stream *s, size_t from, size_t size) { STREAM_VERIFY_SANE(s); if (!GETP_VALID(s, from + size)) { STREAM_BOUND_WARN(s, "get from"); return; } memcpy(dst, s->data + from, size); } bool stream_getl2(struct stream *s, uint32_t *l) { STREAM_VERIFY_SANE(s); if (STREAM_READABLE(s) < sizeof(uint32_t)) { STREAM_BOUND_WARN2(s, "get long"); return false; } *l = (unsigned int)(s->data[s->getp++]) << 24; *l |= s->data[s->getp++] << 16; *l |= s->data[s->getp++] << 8; *l |= s->data[s->getp++]; return true; } uint32_t stream_getl(struct stream *s) { uint32_t l; STREAM_VERIFY_SANE(s); if (STREAM_READABLE(s) < sizeof(uint32_t)) { STREAM_BOUND_WARN(s, "get long"); return 0; } l = (unsigned)(s->data[s->getp++]) << 24; l |= s->data[s->getp++] << 16; l |= s->data[s->getp++] << 8; l |= s->data[s->getp++]; return l; } /* Get next quad word from the stream. */ uint64_t stream_getq_from(struct stream *s, size_t from) { uint64_t q; STREAM_VERIFY_SANE(s); if (!GETP_VALID(s, from + sizeof(uint64_t))) { STREAM_BOUND_WARN(s, "get quad"); return 0; } q = ((uint64_t)s->data[from++]) << 56; q |= ((uint64_t)s->data[from++]) << 48; q |= ((uint64_t)s->data[from++]) << 40; q |= ((uint64_t)s->data[from++]) << 32; q |= ((uint64_t)s->data[from++]) << 24; q |= ((uint64_t)s->data[from++]) << 16; q |= ((uint64_t)s->data[from++]) << 8; q |= ((uint64_t)s->data[from++]); return q; } uint64_t stream_getq(struct stream *s) { uint64_t q; STREAM_VERIFY_SANE(s); if (STREAM_READABLE(s) < sizeof(uint64_t)) { STREAM_BOUND_WARN(s, "get quad"); return 0; } q = ((uint64_t)s->data[s->getp++]) << 56; q |= ((uint64_t)s->data[s->getp++]) << 48; q |= ((uint64_t)s->data[s->getp++]) << 40; q |= ((uint64_t)s->data[s->getp++]) << 32; q |= ((uint64_t)s->data[s->getp++]) << 24; q |= ((uint64_t)s->data[s->getp++]) << 16; q |= ((uint64_t)s->data[s->getp++]) << 8; q |= ((uint64_t)s->data[s->getp++]); return q; } /* Get next long word from the stream. */ uint32_t stream_get_ipv4(struct stream *s) { uint32_t l; STREAM_VERIFY_SANE(s); if (STREAM_READABLE(s) < sizeof(uint32_t)) { STREAM_BOUND_WARN(s, "get ipv4"); return 0; } memcpy(&l, s->data + s->getp, sizeof(uint32_t)); s->getp += sizeof(uint32_t); return l; } float stream_getf(struct stream *s) { union { float r; uint32_t d; } u; u.d = stream_getl(s); return u.r; } double stream_getd(struct stream *s) { union { double r; uint64_t d; } u; u.d = stream_getq(s); return u.r; } /* Copy to source to stream. * * XXX: This uses CHECK_SIZE and hence has funny semantics -> Size will wrap * around. This should be fixed once the stream updates are working. * * stream_write() is saner */ void stream_put(struct stream *s, const void *src, size_t size) { /* XXX: CHECK_SIZE has strange semantics. It should be deprecated */ CHECK_SIZE(s, size); STREAM_VERIFY_SANE(s); if (STREAM_WRITEABLE(s) < size) { STREAM_BOUND_WARN(s, "put"); return; } if (src) memcpy(s->data + s->endp, src, size); else memset(s->data + s->endp, 0, size); s->endp += size; } /* Put character to the stream. */ int stream_putc(struct stream *s, uint8_t c) { STREAM_VERIFY_SANE(s); if (STREAM_WRITEABLE(s) < sizeof(uint8_t)) { STREAM_BOUND_WARN(s, "put"); return 0; } s->data[s->endp++] = c; return sizeof(uint8_t); } /* Put word to the stream. */ int stream_putw(struct stream *s, uint16_t w) { STREAM_VERIFY_SANE(s); if (STREAM_WRITEABLE(s) < sizeof(uint16_t)) { STREAM_BOUND_WARN(s, "put"); return 0; } s->data[s->endp++] = (uint8_t)(w >> 8); s->data[s->endp++] = (uint8_t)w; return 2; } /* Put long word to the stream. */ int stream_put3(struct stream *s, uint32_t l) { STREAM_VERIFY_SANE(s); if (STREAM_WRITEABLE(s) < 3) { STREAM_BOUND_WARN(s, "put"); return 0; } s->data[s->endp++] = (uint8_t)(l >> 16); s->data[s->endp++] = (uint8_t)(l >> 8); s->data[s->endp++] = (uint8_t)l; return 3; } /* Put long word to the stream. */ int stream_putl(struct stream *s, uint32_t l) { STREAM_VERIFY_SANE(s); if (STREAM_WRITEABLE(s) < sizeof(uint32_t)) { STREAM_BOUND_WARN(s, "put"); return 0; } s->data[s->endp++] = (uint8_t)(l >> 24); s->data[s->endp++] = (uint8_t)(l >> 16); s->data[s->endp++] = (uint8_t)(l >> 8); s->data[s->endp++] = (uint8_t)l; return 4; } /* Put quad word to the stream. */ int stream_putq(struct stream *s, uint64_t q) { STREAM_VERIFY_SANE(s); if (STREAM_WRITEABLE(s) < sizeof(uint64_t)) { STREAM_BOUND_WARN(s, "put quad"); return 0; } s->data[s->endp++] = (uint8_t)(q >> 56); s->data[s->endp++] = (uint8_t)(q >> 48); s->data[s->endp++] = (uint8_t)(q >> 40); s->data[s->endp++] = (uint8_t)(q >> 32); s->data[s->endp++] = (uint8_t)(q >> 24); s->data[s->endp++] = (uint8_t)(q >> 16); s->data[s->endp++] = (uint8_t)(q >> 8); s->data[s->endp++] = (uint8_t)q; return 8; } int stream_putf(struct stream *s, float f) { union { float i; uint32_t o; } u; u.i = f; return stream_putl(s, u.o); } int stream_putd(struct stream *s, double d) { union { double i; uint64_t o; } u; u.i = d; return stream_putq(s, u.o); } int stream_putc_at(struct stream *s, size_t putp, uint8_t c) { STREAM_VERIFY_SANE(s); if (!PUT_AT_VALID(s, putp + sizeof(uint8_t))) { STREAM_BOUND_WARN(s, "put"); return 0; } s->data[putp] = c; return 1; } int stream_putw_at(struct stream *s, size_t putp, uint16_t w) { STREAM_VERIFY_SANE(s); if (!PUT_AT_VALID(s, putp + sizeof(uint16_t))) { STREAM_BOUND_WARN(s, "put"); return 0; } s->data[putp] = (uint8_t)(w >> 8); s->data[putp + 1] = (uint8_t)w; return 2; } int stream_put3_at(struct stream *s, size_t putp, uint32_t l) { STREAM_VERIFY_SANE(s); if (!PUT_AT_VALID(s, putp + 3)) { STREAM_BOUND_WARN(s, "put"); return 0; } s->data[putp] = (uint8_t)(l >> 16); s->data[putp + 1] = (uint8_t)(l >> 8); s->data[putp + 2] = (uint8_t)l; return 3; } int stream_putl_at(struct stream *s, size_t putp, uint32_t l) { STREAM_VERIFY_SANE(s); if (!PUT_AT_VALID(s, putp + sizeof(uint32_t))) { STREAM_BOUND_WARN(s, "put"); return 0; } s->data[putp] = (uint8_t)(l >> 24); s->data[putp + 1] = (uint8_t)(l >> 16); s->data[putp + 2] = (uint8_t)(l >> 8); s->data[putp + 3] = (uint8_t)l; return 4; } int stream_putq_at(struct stream *s, size_t putp, uint64_t q) { STREAM_VERIFY_SANE(s); if (!PUT_AT_VALID(s, putp + sizeof(uint64_t))) { STREAM_BOUND_WARN(s, "put"); return 0; } s->data[putp] = (uint8_t)(q >> 56); s->data[putp + 1] = (uint8_t)(q >> 48); s->data[putp + 2] = (uint8_t)(q >> 40); s->data[putp + 3] = (uint8_t)(q >> 32); s->data[putp + 4] = (uint8_t)(q >> 24); s->data[putp + 5] = (uint8_t)(q >> 16); s->data[putp + 6] = (uint8_t)(q >> 8); s->data[putp + 7] = (uint8_t)q; return 8; } /* Put long word to the stream. */ int stream_put_ipv4(struct stream *s, uint32_t l) { STREAM_VERIFY_SANE(s); if (STREAM_WRITEABLE(s) < sizeof(uint32_t)) { STREAM_BOUND_WARN(s, "put"); return 0; } memcpy(s->data + s->endp, &l, sizeof(uint32_t)); s->endp += sizeof(uint32_t); return sizeof(uint32_t); } /* Put long word to the stream. */ int stream_put_in_addr(struct stream *s, struct in_addr *addr) { STREAM_VERIFY_SANE(s); if (STREAM_WRITEABLE(s) < sizeof(uint32_t)) { STREAM_BOUND_WARN(s, "put"); return 0; } memcpy(s->data + s->endp, addr, sizeof(uint32_t)); s->endp += sizeof(uint32_t); return sizeof(uint32_t); } /* Put in_addr at location in the stream. */ int stream_put_in_addr_at(struct stream *s, size_t putp, struct in_addr *addr) { STREAM_VERIFY_SANE(s); if (!PUT_AT_VALID(s, putp + 4)) { STREAM_BOUND_WARN(s, "put"); return 0; } memcpy(&s->data[putp], addr, 4); return 4; } /* Put in6_addr at location in the stream. */ int stream_put_in6_addr_at(struct stream *s, size_t putp, struct in6_addr *addr) { STREAM_VERIFY_SANE(s); if (!PUT_AT_VALID(s, putp + 16)) { STREAM_BOUND_WARN(s, "put"); return 0; } memcpy(&s->data[putp], addr, 16); return 16; } /* Put prefix by nlri type format. */ int stream_put_prefix_addpath(struct stream *s, struct prefix *p, int addpath_encode, uint32_t addpath_tx_id) { size_t psize; size_t psize_with_addpath; STREAM_VERIFY_SANE(s); psize = PSIZE(p->prefixlen); if (addpath_encode) psize_with_addpath = psize + 4; else psize_with_addpath = psize; if (STREAM_WRITEABLE(s) < (psize_with_addpath + sizeof(uint8_t))) { STREAM_BOUND_WARN(s, "put"); return 0; } if (addpath_encode) { s->data[s->endp++] = (uint8_t)(addpath_tx_id >> 24); s->data[s->endp++] = (uint8_t)(addpath_tx_id >> 16); s->data[s->endp++] = (uint8_t)(addpath_tx_id >> 8); s->data[s->endp++] = (uint8_t)addpath_tx_id; } s->data[s->endp++] = p->prefixlen; memcpy(s->data + s->endp, &p->u.prefix, psize); s->endp += psize; return psize; } int stream_put_prefix(struct stream *s, struct prefix *p) { return stream_put_prefix_addpath(s, p, 0, 0); } /* Put NLRI with label */ int stream_put_labeled_prefix(struct stream *s, struct prefix *p, mpls_label_t *label, int addpath_encode, uint32_t addpath_tx_id) { size_t psize; size_t psize_with_addpath; uint8_t *label_pnt = (uint8_t *)label; STREAM_VERIFY_SANE(s); psize = PSIZE(p->prefixlen); psize_with_addpath = psize + (addpath_encode ? 4 : 0); if (STREAM_WRITEABLE(s) < (psize_with_addpath + 3)) { STREAM_BOUND_WARN(s, "put"); return 0; } if (addpath_encode) { s->data[s->endp++] = (uint8_t)(addpath_tx_id >> 24); s->data[s->endp++] = (uint8_t)(addpath_tx_id >> 16); s->data[s->endp++] = (uint8_t)(addpath_tx_id >> 8); s->data[s->endp++] = (uint8_t)addpath_tx_id; } stream_putc(s, (p->prefixlen + 24)); stream_putc(s, label_pnt[0]); stream_putc(s, label_pnt[1]); stream_putc(s, label_pnt[2]); memcpy(s->data + s->endp, &p->u.prefix, psize); s->endp += psize; return (psize + 3); } /* Read size from fd. */ int stream_read(struct stream *s, int fd, size_t size) { int nbytes; STREAM_VERIFY_SANE(s); if (STREAM_WRITEABLE(s) < size) { STREAM_BOUND_WARN(s, "put"); return 0; } nbytes = readn(fd, s->data + s->endp, size); if (nbytes > 0) s->endp += nbytes; return nbytes; } ssize_t stream_read_try(struct stream *s, int fd, size_t size) { ssize_t nbytes; STREAM_VERIFY_SANE(s); if (STREAM_WRITEABLE(s) < size) { STREAM_BOUND_WARN(s, "put"); /* Fatal (not transient) error, since retrying will not help (stream is too small to contain the desired data). */ return -1; } if ((nbytes = read(fd, s->data + s->endp, size)) >= 0) { s->endp += nbytes; return nbytes; } /* Error: was it transient (return -2) or fatal (return -1)? */ if (ERRNO_IO_RETRY(errno)) return -2; flog_err(EC_LIB_SOCKET, "%s: read failed on fd %d: %s", __func__, fd, safe_strerror(errno)); return -1; } /* Read up to size bytes into the stream from the fd, using recvmsgfrom * whose arguments match the remaining arguments to this function */ ssize_t stream_recvfrom(struct stream *s, int fd, size_t size, int flags, struct sockaddr *from, socklen_t *fromlen) { ssize_t nbytes; STREAM_VERIFY_SANE(s); if (STREAM_WRITEABLE(s) < size) { STREAM_BOUND_WARN(s, "put"); /* Fatal (not transient) error, since retrying will not help (stream is too small to contain the desired data). */ return -1; } if ((nbytes = recvfrom(fd, s->data + s->endp, size, flags, from, fromlen)) >= 0) { s->endp += nbytes; return nbytes; } /* Error: was it transient (return -2) or fatal (return -1)? */ if (ERRNO_IO_RETRY(errno)) return -2; flog_err(EC_LIB_SOCKET, "%s: read failed on fd %d: %s", __func__, fd, safe_strerror(errno)); return -1; } /* Read up to smaller of size or SIZE_REMAIN() bytes to the stream, starting * from endp. * First iovec will be used to receive the data. * Stream need not be empty. */ ssize_t stream_recvmsg(struct stream *s, int fd, struct msghdr *msgh, int flags, size_t size) { int nbytes; struct iovec *iov; STREAM_VERIFY_SANE(s); assert(msgh->msg_iovlen > 0); if (STREAM_WRITEABLE(s) < size) { STREAM_BOUND_WARN(s, "put"); /* This is a logic error in the calling code: the stream is too small to hold the desired data! */ return -1; } iov = &(msgh->msg_iov[0]); iov->iov_base = (s->data + s->endp); iov->iov_len = size; nbytes = recvmsg(fd, msgh, flags); if (nbytes > 0) s->endp += nbytes; return nbytes; } /* Write data to buffer. */ size_t stream_write(struct stream *s, const void *ptr, size_t size) { CHECK_SIZE(s, size); STREAM_VERIFY_SANE(s); if (STREAM_WRITEABLE(s) < size) { STREAM_BOUND_WARN(s, "put"); return 0; } memcpy(s->data + s->endp, ptr, size); s->endp += size; return size; } /* Return current read pointer. * DEPRECATED! * Use stream_get_pnt_to if you must, but decoding streams properly * is preferred */ uint8_t *stream_pnt(struct stream *s) { STREAM_VERIFY_SANE(s); return s->data + s->getp; } /* Check does this stream empty? */ int stream_empty(struct stream *s) { STREAM_VERIFY_SANE(s); return (s->endp == 0); } /* Reset stream. */ void stream_reset(struct stream *s) { STREAM_VERIFY_SANE(s); s->getp = s->endp = 0; } /* Write stream contens to the file discriptor. */ int stream_flush(struct stream *s, int fd) { int nbytes; STREAM_VERIFY_SANE(s); nbytes = write(fd, s->data + s->getp, s->endp - s->getp); return nbytes; } /* Stream first in first out queue. */ struct stream_fifo *stream_fifo_new(void) { struct stream_fifo *new; new = XCALLOC(MTYPE_STREAM_FIFO, sizeof(struct stream_fifo)); pthread_mutex_init(&new->mtx, NULL); return new; } /* Add new stream to fifo. */ void stream_fifo_push(struct stream_fifo *fifo, struct stream *s) { #if defined DEV_BUILD size_t max, curmax; #endif if (fifo->tail) fifo->tail->next = s; else fifo->head = s; fifo->tail = s; fifo->tail->next = NULL; #if !defined DEV_BUILD atomic_fetch_add_explicit(&fifo->count, 1, memory_order_release); #else max = atomic_fetch_add_explicit(&fifo->count, 1, memory_order_release); curmax = atomic_load_explicit(&fifo->max_count, memory_order_relaxed); if (max > curmax) atomic_store_explicit(&fifo->max_count, max, memory_order_relaxed); #endif } void stream_fifo_push_safe(struct stream_fifo *fifo, struct stream *s) { frr_with_mutex(&fifo->mtx) { stream_fifo_push(fifo, s); } } /* Delete first stream from fifo. */ struct stream *stream_fifo_pop(struct stream_fifo *fifo) { struct stream *s; s = fifo->head; if (s) { fifo->head = s->next; if (fifo->head == NULL) fifo->tail = NULL; atomic_fetch_sub_explicit(&fifo->count, 1, memory_order_release); /* ensure stream is scrubbed of references to this fifo */ s->next = NULL; } return s; } struct stream *stream_fifo_pop_safe(struct stream_fifo *fifo) { struct stream *ret; frr_with_mutex(&fifo->mtx) { ret = stream_fifo_pop(fifo); } return ret; } struct stream *stream_fifo_head(struct stream_fifo *fifo) { return fifo->head; } struct stream *stream_fifo_head_safe(struct stream_fifo *fifo) { struct stream *ret; frr_with_mutex(&fifo->mtx) { ret = stream_fifo_head(fifo); } return ret; } void stream_fifo_clean(struct stream_fifo *fifo) { struct stream *s; struct stream *next; for (s = fifo->head; s; s = next) { next = s->next; stream_free(s); } fifo->head = fifo->tail = NULL; atomic_store_explicit(&fifo->count, 0, memory_order_release); } void stream_fifo_clean_safe(struct stream_fifo *fifo) { frr_with_mutex(&fifo->mtx) { stream_fifo_clean(fifo); } } size_t stream_fifo_count_safe(struct stream_fifo *fifo) { return atomic_load_explicit(&fifo->count, memory_order_acquire); } void stream_fifo_free(struct stream_fifo *fifo) { stream_fifo_clean(fifo); pthread_mutex_destroy(&fifo->mtx); XFREE(MTYPE_STREAM_FIFO, fifo); } frr-7.2.1/lib/stream.h0000644000000000000000000003461313610377563011435 00000000000000/* * Packet interface * Copyright (C) 1999 Kunihiro Ishiguro * * This file is part of GNU Zebra. * * GNU Zebra is free software; you can redistribute it and/or modify it * under the terms of the GNU General Public License as published by the * Free Software Foundation; either version 2, or (at your option) any * later version. * * GNU Zebra 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; see the file COPYING; if not, write to the Free Software * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA */ #ifndef _ZEBRA_STREAM_H #define _ZEBRA_STREAM_H #include #include "frratomic.h" #include "mpls.h" #include "prefix.h" #ifdef __cplusplus extern "C" { #endif /* * A stream is an arbitrary buffer, whose contents generally are assumed to * be in network order. * * A stream has the following attributes associated with it: * * - size: the allocated, invariant size of the buffer. * * - getp: the get position marker, denoting the offset in the stream where * the next read (or 'get') will be from. This getp marker is * automatically adjusted when data is read from the stream, the * user may also manipulate this offset as they wish, within limits * (see below) * * - endp: the end position marker, denoting the offset in the stream where * valid data ends, and if the user attempted to write (or * 'put') data where that data would be written (or 'put') to. * * These attributes are all size_t values. * * Constraints: * * 1. getp can never exceed endp * * - hence if getp is equal to endp, there is no more valid data that can be * gotten from the stream (though, the user may reposition getp to earlier in * the stream, if they wish). * * 2. endp can never exceed size * * - hence, if endp is equal to size, then the stream is full, and no more * data can be written to the stream. * * In other words the following must always be true, and the stream * abstraction is allowed internally to assert that the following property * holds true for a stream, as and when it wishes: * * getp <= endp <= size * * It is the users responsibility to ensure this property is never violated. * * A stream therefore can be thought of like this: * * --------------------------------------------------- * |XXXXXXXXXXXXXXXXXXXXXXXX | * --------------------------------------------------- * ^ ^ ^ * getp endp size * * This shows a stream containing data (shown as 'X') up to the endp offset. * The stream is empty from endp to size. Without adjusting getp, there are * still endp-getp bytes of valid data to be read from the stream. * * Methods are provided to get and put to/from the stream, as well as * retrieve the values of the 3 markers and manipulate the getp marker. * * Note: * At the moment, newly allocated streams are zero filled. Hence, one can * use stream_forward_endp() to effectively create arbitrary zero-fill * padding. However, note that stream_reset() does *not* zero-out the * stream. This property should **not** be relied upon. * * Best practice is to use stream_put (, NULL, ) to zero out * any part of a stream which isn't otherwise written to. */ /* Stream buffer. */ struct stream { struct stream *next; /* * Remainder is ***private*** to stream * direct access is frowned upon! * Use the appropriate functions/macros */ size_t getp; /* next get position */ size_t endp; /* last valid data position */ size_t size; /* size of data segment */ unsigned char data[0]; /* data pointer */ }; /* First in first out queue structure. */ struct stream_fifo { /* lock for mt-safe operations */ pthread_mutex_t mtx; /* number of streams in this fifo */ atomic_size_t count; #if defined DEV_BUILD atomic_size_t max_count; #endif struct stream *head; struct stream *tail; }; /* Utility macros. */ #define STREAM_SIZE(S) ((S)->size) /* number of bytes which can still be written */ #define STREAM_WRITEABLE(S) ((S)->size - (S)->endp) /* number of bytes still to be read */ #define STREAM_READABLE(S) ((S)->endp - (S)->getp) #define STREAM_CONCAT_REMAIN(S1, S2, size) ((size) - (S1)->endp - (S2)->endp) /* this macro is deprecated, but not slated for removal anytime soon */ #define STREAM_DATA(S) ((S)->data) /* Stream prototypes. * For stream_{put,get}S, the S suffix mean: * * c: character (unsigned byte) * w: word (two bytes) * l: long (two words) * q: quad (four words) */ extern struct stream *stream_new(size_t); extern void stream_free(struct stream *); extern struct stream *stream_copy(struct stream *, struct stream *src); extern struct stream *stream_dup(struct stream *); extern size_t stream_resize_inplace(struct stream **sptr, size_t newsize); extern size_t stream_get_getp(struct stream *); extern size_t stream_get_endp(struct stream *); extern size_t stream_get_size(struct stream *); extern uint8_t *stream_get_data(struct stream *); /** * Create a new stream structure; copy offset bytes from s1 to the new * stream; copy s2 data to the new stream; copy rest of s1 data to the * new stream. */ extern struct stream *stream_dupcat(struct stream *s1, struct stream *s2, size_t offset); extern void stream_set_getp(struct stream *, size_t); extern void stream_set_endp(struct stream *, size_t); extern void stream_forward_getp(struct stream *, size_t); extern void stream_forward_endp(struct stream *, size_t); /* steam_put: NULL source zeroes out size_t bytes of stream */ extern void stream_put(struct stream *, const void *, size_t); extern int stream_putc(struct stream *, uint8_t); extern int stream_putc_at(struct stream *, size_t, uint8_t); extern int stream_putw(struct stream *, uint16_t); extern int stream_putw_at(struct stream *, size_t, uint16_t); extern int stream_put3(struct stream *, uint32_t); extern int stream_put3_at(struct stream *, size_t, uint32_t); extern int stream_putl(struct stream *, uint32_t); extern int stream_putl_at(struct stream *, size_t, uint32_t); extern int stream_putq(struct stream *, uint64_t); extern int stream_putq_at(struct stream *, size_t, uint64_t); extern int stream_put_ipv4(struct stream *, uint32_t); extern int stream_put_in_addr(struct stream *, struct in_addr *); extern int stream_put_in_addr_at(struct stream *, size_t, struct in_addr *); extern int stream_put_in6_addr_at(struct stream *, size_t, struct in6_addr *); extern int stream_put_prefix_addpath(struct stream *, struct prefix *, int addpath_encode, uint32_t addpath_tx_id); extern int stream_put_prefix(struct stream *, struct prefix *); extern int stream_put_labeled_prefix(struct stream *, struct prefix *, mpls_label_t *, int addpath_encode, uint32_t addpath_tx_id); extern void stream_get(void *, struct stream *, size_t); extern bool stream_get2(void *data, struct stream *s, size_t size); extern void stream_get_from(void *, struct stream *, size_t, size_t); extern uint8_t stream_getc(struct stream *); extern bool stream_getc2(struct stream *s, uint8_t *byte); extern uint8_t stream_getc_from(struct stream *, size_t); extern uint16_t stream_getw(struct stream *); extern bool stream_getw2(struct stream *s, uint16_t *word); extern uint16_t stream_getw_from(struct stream *, size_t); extern uint32_t stream_get3(struct stream *); extern uint32_t stream_get3_from(struct stream *, size_t); extern uint32_t stream_getl(struct stream *); extern bool stream_getl2(struct stream *s, uint32_t *l); extern uint32_t stream_getl_from(struct stream *, size_t); extern uint64_t stream_getq(struct stream *); extern uint64_t stream_getq_from(struct stream *, size_t); extern uint32_t stream_get_ipv4(struct stream *); /* IEEE-754 floats */ extern float stream_getf(struct stream *); extern double stream_getd(struct stream *); extern int stream_putf(struct stream *, float); extern int stream_putd(struct stream *, double); #undef stream_read #undef stream_write /* Deprecated: assumes blocking I/O. Will be removed. Use stream_read_try instead. */ extern int stream_read(struct stream *, int, size_t); /* Read up to size bytes into the stream. Return code: >0: number of bytes read 0: end-of-file -1: fatal error -2: transient error, should retry later (i.e. EAGAIN or EINTR) This is suitable for use with non-blocking file descriptors. */ extern ssize_t stream_read_try(struct stream *s, int fd, size_t size); extern ssize_t stream_recvmsg(struct stream *s, int fd, struct msghdr *, int flags, size_t size); extern ssize_t stream_recvfrom(struct stream *s, int fd, size_t len, int flags, struct sockaddr *from, socklen_t *fromlen); extern size_t stream_write(struct stream *, const void *, size_t); /* reset the stream. See Note above */ extern void stream_reset(struct stream *); extern int stream_flush(struct stream *, int); extern int stream_empty(struct stream *); /* is the stream empty? */ /* deprecated */ extern uint8_t *stream_pnt(struct stream *); /* * Operations on struct stream_fifo. * * Each function has a safe variant, which ensures that the operation performed * is atomic with respect to the operations performed by all other safe * variants. In other words, the safe variants lock the stream_fifo's mutex * before performing their action. These are provided for convenience when * using stream_fifo in a multithreaded context, to alleviate the need for the * caller to implement their own synchronization around the stream_fifo. * * The following functions do not have safe variants. The caller must ensure * that these operations are performed safely in a multithreaded context: * - stream_fifo_new * - stream_fifo_free */ /* * Create a new stream_fifo. * * Returns: * newly created stream_fifo */ extern struct stream_fifo *stream_fifo_new(void); /* * Push a stream onto a stream_fifo. * * fifo * the stream_fifo to push onto * * s * the stream to push onto the stream_fifo */ extern void stream_fifo_push(struct stream_fifo *fifo, struct stream *s); extern void stream_fifo_push_safe(struct stream_fifo *fifo, struct stream *s); /* * Pop a stream off a stream_fifo. * * fifo * the stream_fifo to pop from * * Returns: * the next stream in the stream_fifo */ extern struct stream *stream_fifo_pop(struct stream_fifo *fifo); extern struct stream *stream_fifo_pop_safe(struct stream_fifo *fifo); /* * Retrieve the next stream from a stream_fifo without popping it. * * fifo * the stream_fifo to operate on * * Returns: * the next stream that would be returned from stream_fifo_pop */ extern struct stream *stream_fifo_head(struct stream_fifo *fifo); extern struct stream *stream_fifo_head_safe(struct stream_fifo *fifo); /* * Remove all streams from a stream_fifo. * * fifo * the stream_fifo to clean */ extern void stream_fifo_clean(struct stream_fifo *fifo); extern void stream_fifo_clean_safe(struct stream_fifo *fifo); /* * Retrieve number of streams on a stream_fifo. * * fifo * the stream_fifo to retrieve the count for * * Returns: * the number of streams on the stream_fifo */ extern size_t stream_fifo_count_safe(struct stream_fifo *fifo); /* * Free a stream_fifo. * * Calls stream_fifo_clean, then deinitializes the stream_fifo and frees it. * * fifo * the stream_fifo to free */ extern void stream_fifo_free(struct stream_fifo *fifo); /* This is here because "<< 24" is particularly problematic in C. * This is because the left operand of << is integer-promoted, which means * an uint8_t gets converted into a *signed* int. Shifting into the sign * bit of a signed int is theoretically undefined behaviour, so - the left * operand needs to be cast to unsigned. * * This is not a problem for 16- or 8-bit values (they don't reach the sign * bit), for 64-bit values (you need to cast them anyway), and neither for * encoding (because it's downcasted.) */ static inline uint8_t *ptr_get_be32(uint8_t *ptr, uint32_t *out) { uint32_t tmp; memcpy(&tmp, ptr, sizeof(tmp)); *out = ntohl(tmp); return ptr + 4; } /* * so Normal stream_getX functions assert. Which is anathema * to keeping a daemon up and running when something goes south * Provide a stream_getX2 functions that do not assert. * In addition provide these macro's that upon failure * goto stream_failure. This is modeled upon some NL_XX * macros in the linux kernel. * * This change allows for proper memory freeing * after we've detected an error. * * In the future we will be removing the assert in * the stream functions but we need a transition * plan. */ #define STREAM_GETC(S, P) \ do { \ uint8_t _pval; \ if (!stream_getc2((S), &_pval)) \ goto stream_failure; \ (P) = _pval; \ } while (0) #define STREAM_GETW(S, P) \ do { \ uint16_t _pval; \ if (!stream_getw2((S), &_pval)) \ goto stream_failure; \ (P) = _pval; \ } while (0) #define STREAM_GETL(S, P) \ do { \ uint32_t _pval; \ if (!stream_getl2((S), &_pval)) \ goto stream_failure; \ (P) = _pval; \ } while (0) #define STREAM_GET(P, STR, SIZE) \ do { \ if (!stream_get2((P), (STR), (SIZE))) \ goto stream_failure; \ } while (0) #ifdef __cplusplus } #endif #endif /* _ZEBRA_STREAM_H */ frr-7.2.1/lib/strlcat.c0000644000000000000000000000503713610377563011607 00000000000000/* Append a null-terminated string to another string, with length checking. * Copyright (C) 2016 Free Software Foundation, Inc. * This file is part of the GNU C Library. * * The GNU C Library is free software; you can redistribute it and/or * modify it under the terms of the GNU Lesser General Public * License as published by the Free Software Foundation; either * version 2.1 of the License, or (at your option) any later version. * * The GNU C Library 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 * Lesser General Public License for more details. * * You should have received a copy of the GNU Lesser General Public * License along with the GNU C Library; if not, see * . */ /* adapted for Quagga from glibc patch submission originally from * Florian Weimer , 2016-05-18 */ #ifdef HAVE_CONFIG_H #include "config.h" #endif #include #include #ifndef HAVE_STRLCAT #undef strlcat size_t strlcat(char *__restrict dest, const char *__restrict src, size_t destsize); size_t strlcat(char *__restrict dest, const char *__restrict src, size_t destsize) { size_t src_length = strlen(src); /* Our implementation strlcat supports dest == NULL if size == 0 (for consistency with snprintf and strlcpy), but strnlen does not, so we have to cover this case explicitly. */ if (destsize == 0) return src_length; size_t dest_length = strnlen(dest, destsize); if (dest_length != destsize) { /* Copy at most the remaining number of characters in the destination buffer. Leave for the NUL terminator. */ size_t to_copy = destsize - dest_length - 1; /* But not more than what is available in the source string. */ if (to_copy > src_length) to_copy = src_length; char *target = dest + dest_length; memcpy(target, src, to_copy); target[to_copy] = '\0'; } /* If the sum wraps around, we have more than SIZE_MAX + 2 bytes in the two input strings (including both null terminators). If each byte in the address space can be assigned a unique size_t value (which the static_assert checks), then by the pigeonhole principle, the two input strings must overlap, which is undefined. */ #if __STDC_VERSION__ >= 201112L _Static_assert(sizeof(uintptr_t) == sizeof(size_t), "theoretical maximum object size covers address space"); #endif return dest_length + src_length; } #endif /* HAVE_STRLCAT */ frr-7.2.1/lib/strlcpy.c0000644000000000000000000000361313610377563011631 00000000000000/* Copy a null-terminated string to a fixed-size buffer, with length checking. * Copyright (C) 2016 Free Software Foundation, Inc. * This file is part of the GNU C Library. * * The GNU C Library is free software; you can redistribute it and/or * modify it under the terms of the GNU Lesser General Public * License as published by the Free Software Foundation; either * version 2.1 of the License, or (at your option) any later version. * * The GNU C Library 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 * Lesser General Public License for more details. * * You should have received a copy of the GNU Lesser General Public * License along with the GNU C Library; if not, see * . */ /* adapted for Quagga from glibc patch submission originally from * Florian Weimer , 2016-05-18 */ #ifdef HAVE_CONFIG_H #include "config.h" #endif #include #ifndef HAVE_STRLCPY #undef strlcpy size_t strlcpy(char *__restrict dest, const char *__restrict src, size_t destsize); size_t strlcpy(char *__restrict dest, const char *__restrict src, size_t destsize) { size_t src_length = strlen(src); if (__builtin_expect(src_length >= destsize, 0)) { if (destsize > 0) { /* * Copy the leading portion of the string. The last * character is subsequently overwritten with the NUL * terminator, but the destination destsize is usually * a multiple of a small power of two, so writing it * twice should be more efficient than copying an odd * number of bytes. */ memcpy(dest, src, destsize); dest[destsize - 1] = '\0'; } } else /* Copy the string and its terminating NUL character. */ memcpy(dest, src, src_length + 1); return src_length; } #endif /* HAVE_STRLCPY */ frr-7.2.1/lib/subdir.am0000644000000000000000000002454413610377563011602 00000000000000# # libfrr # lib_LTLIBRARIES += lib/libfrr.la lib_libfrr_la_LDFLAGS = -version-info 0:0:0 -Xlinker -e_libfrr_version lib_libfrr_la_LIBADD = $(LIBCAP) $(UNWIND_LIBS) $(LIBYANG_LIBS) $(LUA_LIB) $(LIBM) lib_libfrr_la_SOURCES = \ lib/agg_table.c \ lib/atomlist.c \ lib/bfd.c \ lib/buffer.c \ lib/checksum.c \ lib/command.c \ lib/command_graph.c \ lib/command_lex.l \ lib/command_match.c \ lib/command_parse.y \ lib/csv.c \ lib/debug.c \ lib/distribute.c \ lib/ferr.c \ lib/filter.c \ lib/frrcu.c \ lib/frrlua.c \ lib/frr_pthread.c \ lib/frrstr.c \ lib/getopt.c \ lib/getopt1.c \ lib/grammar_sandbox.c \ lib/graph.c \ lib/hash.c \ lib/hook.c \ lib/id_alloc.c \ lib/if.c \ lib/if_rmap.c \ lib/imsg-buffer.c \ lib/imsg.c \ lib/jhash.c \ lib/json.c \ lib/keychain.c \ lib/lib_errors.c \ lib/libfrr.c \ lib/linklist.c \ lib/log.c \ lib/log_vty.c \ lib/md5.c \ lib/memory.c \ lib/memory_vty.c \ lib/mlag.c \ lib/module.c \ lib/mpls.c \ lib/network.c \ lib/nexthop.c \ lib/netns_linux.c \ lib/netns_other.c \ lib/nexthop_group.c \ lib/northbound.c \ lib/northbound_cli.c \ lib/northbound_db.c \ lib/ntop.c \ lib/openbsd-tree.c \ lib/pid_output.c \ lib/plist.c \ lib/prefix.c \ lib/privs.c \ lib/ptm_lib.c \ lib/pullwr.c \ lib/qobj.c \ lib/ringbuf.c \ lib/routemap.c \ lib/sbuf.c \ lib/seqlock.c \ lib/sha256.c \ lib/sigevent.c \ lib/skiplist.c \ lib/sockopt.c \ lib/sockunion.c \ lib/spf_backoff.c \ lib/srcdest_table.c \ lib/stream.c \ lib/strlcat.c \ lib/strlcpy.c \ lib/systemd.c \ lib/table.c \ lib/termtable.c \ lib/thread.c \ lib/typerb.c \ lib/typesafe.c \ lib/vector.c \ lib/vrf.c \ lib/vty.c \ lib/wheel.c \ lib/workqueue.c \ lib/yang.c \ lib/yang_translator.c \ lib/yang_wrappers.c \ lib/zclient.c \ lib/printf/printf-pos.c \ lib/printf/vfprintf.c \ lib/printf/glue.c \ # end nodist_lib_libfrr_la_SOURCES = \ yang/frr-interface.yang.c \ yang/frr-route-types.yang.c \ yang/frr-module-translator.yang.c \ # end vtysh_scan += \ $(top_srcdir)/lib/distribute.c \ $(top_srcdir)/lib/filter.c \ $(top_srcdir)/lib/if.c \ $(top_srcdir)/lib/if_rmap.c \ $(top_srcdir)/lib/keychain.c \ $(top_srcdir)/lib/nexthop_group.c \ $(top_srcdir)/lib/plist.c \ $(top_srcdir)/lib/routemap.c \ $(top_srcdir)/lib/vrf.c \ $(top_srcdir)/lib/vty.c \ # end # can be loaded as DSO - always include for vtysh vtysh_scan += $(top_srcdir)/lib/agentx.c if SQLITE3 lib_libfrr_la_LIBADD += $(SQLITE3_LIBS) lib_libfrr_la_SOURCES += lib/db.c endif lib/if_clippy.c: $(CLIPPY_DEPS) lib/if.lo: lib/if_clippy.c lib/plist_clippy.c: $(CLIPPY_DEPS) lib/plist.lo: lib/plist_clippy.c lib/nexthop_group_clippy.c: $(CLIPPY_DEPS) lib/nexthop_group.lo: lib/nexthop_group_clippy.c lib/northbound_cli_clippy.c: $(CLIPPY_DEPS) lib/northbound_cli.lo: lib/northbound_cli_clippy.c lib/vty_clippy.c: $(CLIPPY_DEPS) lib/vty.lo: lib/vty_clippy.c lib/log_vty_clippy.c: $(CLIPPY_DEPS) lib/log_vty.lo: lib/log_vty_clippy.c pkginclude_HEADERS += \ lib/agg_table.h \ lib/atomlist.h \ lib/bfd.h \ lib/bitfield.h \ lib/buffer.h \ lib/checksum.h \ lib/mlag.h \ lib/command.h \ lib/command_graph.h \ lib/command_match.h \ lib/compiler.h \ lib/csv.h \ lib/db.h \ lib/debug.h \ lib/distribute.h \ lib/ferr.h \ lib/filter.h \ lib/freebsd-queue.h \ lib/frrlua.h \ lib/frr_pthread.h \ lib/frratomic.h \ lib/frrcu.h \ lib/frrstr.h \ lib/getopt.h \ lib/graph.h \ lib/hash.h \ lib/hook.h \ lib/iana_afi.h \ lib/id_alloc.h \ lib/if.h \ lib/if_rmap.h \ lib/imsg.h \ lib/ipaddr.h \ lib/jhash.h \ lib/json.h \ lib/keychain.h \ lib/lib_errors.h \ lib/libfrr.h \ lib/libospf.h \ lib/linklist.h \ lib/log.h \ lib/log_vty.h \ lib/md5.h \ lib/memory.h \ lib/memory_vty.h \ lib/module.h \ lib/monotime.h \ lib/mpls.h \ lib/network.h \ lib/nexthop.h \ lib/nexthop_group.h \ lib/nexthop_group_private.h \ lib/northbound.h \ lib/northbound_cli.h \ lib/northbound_db.h \ lib/ns.h \ lib/openbsd-queue.h \ lib/openbsd-tree.h \ lib/plist.h \ lib/prefix.h \ lib/printfrr.h \ lib/privs.h \ lib/ptm_lib.h \ lib/pullwr.h \ lib/pw.h \ lib/qobj.h \ lib/queue.h \ lib/ringbuf.h \ lib/routemap.h \ lib/sbuf.h \ lib/seqlock.h \ lib/sha256.h \ lib/sigevent.h \ lib/skiplist.h \ lib/smux.h \ lib/sockopt.h \ lib/sockunion.h \ lib/spf_backoff.h \ lib/srcdest_table.h \ lib/stream.h \ lib/systemd.h \ lib/table.h \ lib/termtable.h \ lib/thread.h \ lib/typerb.h \ lib/typesafe.h \ lib/vector.h \ lib/vlan.h \ lib/vrf.h \ lib/vrf_int.h \ lib/vty.h \ lib/vxlan.h \ lib/wheel.h \ lib/workqueue.h \ lib/yang.h \ lib/yang_translator.h \ lib/yang_wrappers.h \ lib/zassert.h \ lib/zclient.h \ lib/zebra.h \ lib/pbr.h \ # end nodist_pkginclude_HEADERS += \ lib/route_types.h \ lib/version.h \ # end noinst_HEADERS += \ lib/clippy.h \ lib/log_int.h \ lib/plist_int.h \ lib/printf/printfcommon.h \ lib/printf/printflocal.h \ #end # General note about module and module helper library (libfrrsnmp, libfrrzmq) # linking: If we're linking libfrr statically into daemons, we *must* remove # libfrr from modules because modules will always link it in dynamically and # thus 2 copies of libfrr will be loaded... hilarity ensues. # # Not linking libfrr into modules should generally work fine because the # executable refers to libfrr either way and the dynamic linker should make # libfrr available to modules. If some OS platform has a dynamic linker that # doesn't do that, libfrr needs to be readded to modules, but _only_ _if_ # it's not linked into daemons statically. # # SNMP support # if SNMP lib_LTLIBRARIES += lib/libfrrsnmp.la endif lib_libfrrsnmp_la_CFLAGS = $(WERROR) $(SNMP_CFLAGS) -std=gnu99 lib_libfrrsnmp_la_LDFLAGS = -version-info 0:0:0 lib_libfrrsnmp_la_LIBADD = $(SNMP_LIBS) lib_libfrrsnmp_la_SOURCES = \ lib/agentx.c \ lib/snmp.c \ # end # # c-ares support # if CARES lib_LTLIBRARIES += lib/libfrrcares.la pkginclude_HEADERS += lib/resolver.h endif lib_libfrrcares_la_CFLAGS = $(WERROR) $(CARES_CFLAGS) lib_libfrrcares_la_LDFLAGS = -version-info 0:0:0 lib_libfrrcares_la_LIBADD = $(CARES_LIBS) lib_libfrrcares_la_SOURCES = \ lib/resolver.c \ #end # # ZeroMQ support # if ZEROMQ lib_LTLIBRARIES += lib/libfrrzmq.la pkginclude_HEADERS += lib/frr_zmq.h endif lib_libfrrzmq_la_CFLAGS = $(WERROR) $(ZEROMQ_CFLAGS) lib_libfrrzmq_la_LDFLAGS = -version-info 0:0:0 lib_libfrrzmq_la_LIBADD = $(ZEROMQ_LIBS) lib_libfrrzmq_la_SOURCES = \ lib/frr_zmq.c \ #end # # Tail-f's ConfD support # if CONFD module_LTLIBRARIES += lib/confd.la endif lib_confd_la_CFLAGS = $(WERROR) $(CONFD_CFLAGS) lib_confd_la_LDFLAGS = -avoid-version -module -shared -export-dynamic lib_confd_la_LIBADD = lib/libfrr.la $(CONFD_LIBS) lib_confd_la_SOURCES = lib/northbound_confd.c # # Sysrepo support # if SYSREPO module_LTLIBRARIES += lib/sysrepo.la endif lib_sysrepo_la_CFLAGS = $(WERROR) $(SYSREPO_CFLAGS) lib_sysrepo_la_LDFLAGS = -avoid-version -module -shared -export-dynamic lib_sysrepo_la_LIBADD = lib/libfrr.la $(SYSREPO_LIBS) lib_sysrepo_la_SOURCES = lib/northbound_sysrepo.c # # gRPC northbound plugin # if GRPC module_LTLIBRARIES += lib/grpc.la endif lib_grpc_la_CXXFLAGS = $(WERROR) $(GRPC_CFLAGS) lib_grpc_la_LDFLAGS = -avoid-version -module -shared -export-dynamic lib_grpc_la_LIBADD = lib/libfrr.la grpc/libfrrgrpc_pb.la $(GRPC_LIBS) lib_grpc_la_SOURCES = lib/northbound_grpc.cpp # # CLI utilities # noinst_PROGRAMS += \ lib/grammar_sandbox \ # end if BUILD_CLIPPY noinst_PROGRAMS += lib/clippy else if HOSTTOOLS_CLIPPY $(CLIPPY): @$(MAKE) -C $(top_builddir)/hosttools lib/route_types.h lib/clippy endif endif lib_grammar_sandbox_SOURCES = \ lib/grammar_sandbox_main.c lib_grammar_sandbox_LDADD = \ lib/libfrr.la lib_clippy_CPPFLAGS = $(AM_CPPFLAGS) -D_GNU_SOURCE -DBUILDING_CLIPPY lib_clippy_CFLAGS = $(PYTHON_CFLAGS) lib_clippy_LDADD = $(PYTHON_LIBS) lib_clippy_LDFLAGS = -export-dynamic lib_clippy_SOURCES = \ lib/clippy.c \ lib/command_graph.c \ lib/command_lex.l \ lib/command_parse.y \ lib/command_py.c \ lib/defun_lex.l \ lib/graph.c \ lib/memory.c \ lib/vector.c \ # end # (global) clippy rules for all directories AM_V_CLIPPY = $(am__v_CLIPPY_$(V)) am__v_CLIPPY_ = $(am__v_CLIPPY_$(AM_DEFAULT_VERBOSITY)) am__v_CLIPPY_0 = @echo " CLIPPY " $@; am__v_CLIPPY_1 = CLIPPY_DEPS = $(CLIPPY) $(top_srcdir)/python/clidef.py SUFFIXES = _clippy.c .proto .pb-c.c .pb-c.h .pb.h .pb.cc .grpc.pb.cc .c_clippy.c: $(AM_V_CLIPPY) $(CLIPPY) $(top_srcdir)/python/clidef.py -o $@ $< ## automake's "ylwrap" is a great piece of GNU software... not. .l.c: $(AM_V_LEX)$(am__skiplex) $(LEXCOMPILE) $< .y.c: $(AM_V_YACC)$(am__skipyacc) $(YACCCOMPILE) $< # # generated sources & extra foo # EXTRA_DIST += \ lib/command_lex.h \ lib/command_parse.h \ lib/gitversion.pl \ lib/route_types.pl \ lib/route_types.txt \ # end BUILT_SOURCES += \ lib/gitversion.h \ lib/route_types.h \ # end ## force route_types.h $(lib_clippy_OBJECTS): lib/route_types.h $(lib_libfrr_la_OBJECTS): lib/route_types.h AM_YFLAGS = -d -Dapi.prefix=@BISON_OPENBRACE@cmd_yy@BISON_CLOSEBRACE@ @BISON_VERBOSE@ lib/command_lex.h: lib/command_lex.c @if test ! -f $@; then rm -f "lib/command_lex.c"; else :; fi @if test ! -f $@; then $(MAKE) $(AM_MAKEFLAGS) "lib/command_lex.c"; else :; fi lib/command_lex.lo: lib/command_parse.h lib/command_parse.lo: lib/command_lex.h lib/clippy-command_lex.$(OBJEXT): lib/command_parse.h lib/clippy-command_parse.$(OBJEXT): lib/command_lex.h lib/lib_clippy-command_lex.$(OBJEXT): lib/command_parse.h lib/lib_clippy-command_parse.$(OBJEXT): lib/command_lex.h lib/route_types.h: $(top_srcdir)/lib/route_types.txt $(top_srcdir)/lib/route_types.pl $(PERL) $(top_srcdir)/lib/route_types.pl < $(top_srcdir)/lib/route_types.txt > $@ DISTCLEANFILES += lib/route_types.h if GIT_VERSION # bit of a trick here to always have up-to-date git stamps without triggering # unneccessary rebuilds. .PHONY causes the .tmp file to be rebuilt always, # but if we use that on gitversion.h it'll ripple through the .c file deps. # (even if gitversion.h's file timestamp doesn't change, make will think it # did, because of .PHONY...) PHONY_GITVERSION=lib/gitversion.h.tmp .SILENT: lib/gitversion.h lib/gitversion.h.tmp GITH=lib/gitversion.h lib/gitversion.h.tmp: $(top_srcdir)/.git $(PERL) $(top_srcdir)/lib/gitversion.pl $(top_srcdir) > ${GITH}.tmp lib/gitversion.h: lib/gitversion.h.tmp { test -f ${GITH} && diff -s -q ${GITH}.tmp ${GITH}; } || cp ${GITH}.tmp ${GITH} else PHONY_GITVERSION=lib/gitversion.h lib/gitversion.h: true endif .PHONY: $(PHONY_GITVERSION) frr-7.2.1/lib/systemd.c0000644000000000000000000000512613610377563011622 00000000000000/* lib/systemd Code * Copyright (C) 2016 Cumulus Networks, Inc. * Donald Sharp * * This file is part of Quagga. * * Quagga is free software; you can redistribute it and/or modify it * under the terms of the GNU General Public License as published by the * Free Software Foundation; either version 2, or (at your option) any * later version. * * Quagga 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; see the file COPYING; if not, write to the Free Software * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA */ #include #include "thread.h" #include "systemd.h" #if defined HAVE_SYSTEMD #include #endif /* * Wrapper this silliness if we * don't have systemd */ void systemd_send_information(const char *info) { #if defined HAVE_SYSTEMD sd_notify(0, info); #else return; #endif } /* * A return of 0 means that we are not watchdoged */ static int systemd_get_watchdog_time(int the_process) { #if defined HAVE_SYSTEMD uint64_t usec; char *watchdog = NULL; int ret; ret = sd_watchdog_enabled(0, &usec); /* * If return is 0 -> we don't want watchdog * if return is < 0, some sort of failure occurred */ if (ret < 0) return 0; /* * systemd can return that this process * is not the expected sender of the watchdog timer * If we set the_process = 0 then we expect to * be able to send the watchdog to systemd * irrelevant of the pid of this process. */ if (ret == 0 && the_process) return 0; if (ret == 0 && !the_process) { watchdog = getenv("WATCHDOG_USEC"); if (!watchdog) return 0; usec = atol(watchdog); } return (usec / 1000000) / 3; #else return 0; #endif } void systemd_send_stopping(void) { systemd_send_information("STOPPING=1"); } /* * How many seconds should we wait between watchdog sends */ int wsecs = 0; struct thread_master *systemd_master = NULL; static int systemd_send_watchdog(struct thread *t) { systemd_send_information("WATCHDOG=1"); thread_add_timer(systemd_master, systemd_send_watchdog, NULL, wsecs, NULL); return 1; } void systemd_send_started(struct thread_master *m, int the_process) { assert(m != NULL); wsecs = systemd_get_watchdog_time(the_process); systemd_master = m; systemd_send_information("READY=1"); if (wsecs != 0) thread_add_timer(m, systemd_send_watchdog, m, wsecs, NULL); } frr-7.2.1/lib/systemd.h0000644000000000000000000000267313610377563011633 00000000000000/* lib/systemd Code * Copyright (C) 2016 Cumulus Networks, Inc. * Donald Sharp * * This file is part of Quagga. * * Quagga is free software; you can redistribute it and/or modify it * under the terms of the GNU General Public License as published by the * Free Software Foundation; either version 2, or (at your option) any * later version. * * Quagga 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; see the file COPYING; if not, write to the Free Software * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA */ #ifdef __cplusplus extern "C" { #endif /* * Wrapper functions to systemd calls. * * Design point is that if systemd is not being used on this system * then these functions becomes a no-op. * * To turn on systemd compilation, use --enable-systemd on * configure run. */ void systemd_send_information(const char *info); void systemd_send_stopping(void); /* * master - The struct thread_master * to use to schedule ourself * the_process - Should we send watchdog if we are not the requested * process? */ void systemd_send_started(struct thread_master *master, int the_process); #ifdef __cplusplus } #endif frr-7.2.1/lib/table.c0000644000000000000000000004237013610377563011223 00000000000000/* * Routing Table functions. * Copyright (C) 1998 Kunihiro Ishiguro * * This file is part of GNU Zebra. * * GNU Zebra is free software; you can redistribute it and/or modify it * under the terms of the GNU General Public License as published by the * Free Software Foundation; either version 2, or (at your option) any * later version. * * GNU Zebra 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; see the file COPYING; if not, write to the Free Software * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA */ #define FRR_COMPILING_TABLE_C #include #include "prefix.h" #include "table.h" #include "memory.h" #include "sockunion.h" DEFINE_MTYPE_STATIC(LIB, ROUTE_TABLE, "Route table") DEFINE_MTYPE(LIB, ROUTE_NODE, "Route node") static void route_table_free(struct route_table *); static int route_table_hash_cmp(const struct route_node *a, const struct route_node *b) { return prefix_cmp(&a->p, &b->p); } DECLARE_HASH(rn_hash_node, struct route_node, nodehash, route_table_hash_cmp, prefix_hash_key) /* * route_table_init_with_delegate */ struct route_table * route_table_init_with_delegate(route_table_delegate_t *delegate) { struct route_table *rt; rt = XCALLOC(MTYPE_ROUTE_TABLE, sizeof(struct route_table)); rt->delegate = delegate; rn_hash_node_init(&rt->hash); return rt; } void route_table_finish(struct route_table *rt) { route_table_free(rt); } /* Allocate new route node. */ static struct route_node *route_node_new(struct route_table *table) { return table->delegate->create_node(table->delegate, table); } /* Allocate new route node with prefix set. */ static struct route_node *route_node_set(struct route_table *table, const struct prefix *prefix) { struct route_node *node; node = route_node_new(table); prefix_copy(&node->p, prefix); node->table = table; rn_hash_node_add(&node->table->hash, node); return node; } /* Free route node. */ static void route_node_free(struct route_table *table, struct route_node *node) { if (table->cleanup) table->cleanup(table, node); table->delegate->destroy_node(table->delegate, table, node); } /* Free route table. */ static void route_table_free(struct route_table *rt) { struct route_node *tmp_node; struct route_node *node; if (rt == NULL) return; node = rt->top; /* Bulk deletion of nodes remaining in this table. This function is not called until workers have completed their dependency on this table. A final route_unlock_node() will not be called for these nodes. */ while (node) { if (node->l_left) { node = node->l_left; continue; } if (node->l_right) { node = node->l_right; continue; } tmp_node = node; node = node->parent; tmp_node->table->count--; tmp_node->lock = 0; /* to cause assert if unlocked after this */ rn_hash_node_del(&rt->hash, tmp_node); route_node_free(rt, tmp_node); if (node != NULL) { if (node->l_left == tmp_node) node->l_left = NULL; else node->l_right = NULL; } else { break; } } assert(rt->count == 0); rn_hash_node_fini(&rt->hash); XFREE(MTYPE_ROUTE_TABLE, rt); return; } /* Utility mask array. */ static const uint8_t maskbit[] = {0x00, 0x80, 0xc0, 0xe0, 0xf0, 0xf8, 0xfc, 0xfe, 0xff}; /* Common prefix route genaration. */ static void route_common(const struct prefix *n, const struct prefix *p, struct prefix *new) { int i; uint8_t diff; uint8_t mask; const uint8_t *np; const uint8_t *pp; uint8_t *newp; if (n->family == AF_FLOWSPEC) return prefix_copy(new, p); np = (const uint8_t *)&n->u.prefix; pp = (const uint8_t *)&p->u.prefix; newp = (uint8_t *)&new->u.prefix; for (i = 0; i < p->prefixlen / 8; i++) { if (np[i] == pp[i]) newp[i] = np[i]; else break; } new->prefixlen = i * 8; if (new->prefixlen != p->prefixlen) { diff = np[i] ^ pp[i]; mask = 0x80; while (new->prefixlen < p->prefixlen && !(mask & diff)) { mask >>= 1; new->prefixlen++; } newp[i] = np[i] & maskbit[new->prefixlen % 8]; } } static void set_link(struct route_node *node, struct route_node *new) { unsigned int bit = prefix_bit(&new->p.u.prefix, node->p.prefixlen); node->link[bit] = new; new->parent = node; } /* Find matched prefix. */ struct route_node *route_node_match(struct route_table *table, union prefixconstptr pu) { const struct prefix *p = pu.p; struct route_node *node; struct route_node *matched; matched = NULL; node = table->top; /* Walk down tree. If there is matched route then store it to matched. */ while (node && node->p.prefixlen <= p->prefixlen && prefix_match(&node->p, p)) { if (node->info) matched = node; if (node->p.prefixlen == p->prefixlen) break; node = node->link[prefix_bit(&p->u.prefix, node->p.prefixlen)]; } /* If matched route found, return it. */ if (matched) return route_lock_node(matched); return NULL; } struct route_node *route_node_match_ipv4(struct route_table *table, const struct in_addr *addr) { struct prefix_ipv4 p; memset(&p, 0, sizeof(struct prefix_ipv4)); p.family = AF_INET; p.prefixlen = IPV4_MAX_PREFIXLEN; p.prefix = *addr; return route_node_match(table, (struct prefix *)&p); } struct route_node *route_node_match_ipv6(struct route_table *table, const struct in6_addr *addr) { struct prefix_ipv6 p; memset(&p, 0, sizeof(struct prefix_ipv6)); p.family = AF_INET6; p.prefixlen = IPV6_MAX_PREFIXLEN; p.prefix = *addr; return route_node_match(table, &p); } /* Lookup same prefix node. Return NULL when we can't find route. */ struct route_node *route_node_lookup(struct route_table *table, union prefixconstptr pu) { struct route_node rn, *node; prefix_copy(&rn.p, pu.p); apply_mask(&rn.p); node = rn_hash_node_find(&table->hash, &rn); return (node && node->info) ? route_lock_node(node) : NULL; } /* Lookup same prefix node. Return NULL when we can't find route. */ struct route_node *route_node_lookup_maynull(struct route_table *table, union prefixconstptr pu) { struct route_node rn, *node; prefix_copy(&rn.p, pu.p); apply_mask(&rn.p); node = rn_hash_node_find(&table->hash, &rn); return node ? route_lock_node(node) : NULL; } /* Add node to routing table. */ struct route_node *route_node_get(struct route_table *table, union prefixconstptr pu) { struct route_node search; struct prefix *p = &search.p; prefix_copy(p, pu.p); apply_mask(p); struct route_node *new; struct route_node *node; struct route_node *match; uint16_t prefixlen = p->prefixlen; const uint8_t *prefix = &p->u.prefix; node = rn_hash_node_find(&table->hash, &search); if (node && node->info) return route_lock_node(node); match = NULL; node = table->top; while (node && node->p.prefixlen <= prefixlen && prefix_match(&node->p, p)) { if (node->p.prefixlen == prefixlen) return route_lock_node(node); match = node; node = node->link[prefix_bit(prefix, node->p.prefixlen)]; } if (node == NULL) { new = route_node_set(table, p); if (match) set_link(match, new); else table->top = new; } else { new = route_node_new(table); route_common(&node->p, p, &new->p); new->p.family = p->family; new->table = table; set_link(new, node); rn_hash_node_add(&table->hash, new); if (match) set_link(match, new); else table->top = new; if (new->p.prefixlen != p->prefixlen) { match = new; new = route_node_set(table, p); set_link(match, new); table->count++; } } table->count++; route_lock_node(new); return new; } /* Delete node from the routing table. */ void route_node_delete(struct route_node *node) { struct route_node *child; struct route_node *parent; assert(node->lock == 0); assert(node->info == NULL); if (node->l_left && node->l_right) return; if (node->l_left) child = node->l_left; else child = node->l_right; parent = node->parent; if (child) child->parent = parent; if (parent) { if (parent->l_left == node) parent->l_left = child; else parent->l_right = child; } else node->table->top = child; node->table->count--; rn_hash_node_del(&node->table->hash, node); /* WARNING: FRAGILE CODE! * route_node_free may have the side effect of free'ing the entire * table. * this is permitted only if table->count got decremented to zero above, * because in that case parent will also be NULL, so that we won't try * to * delete a now-stale parent below. * * cf. srcdest_srcnode_destroy() in zebra/zebra_rib.c */ route_node_free(node->table, node); /* If parent node is stub then delete it also. */ if (parent && parent->lock == 0) route_node_delete(parent); } /* Get fist node and lock it. This function is useful when one want to lookup all the node exist in the routing table. */ struct route_node *route_top(struct route_table *table) { /* If there is no node in the routing table return NULL. */ if (table->top == NULL) return NULL; /* Lock the top node and return it. */ route_lock_node(table->top); return table->top; } /* Unlock current node and lock next node then return it. */ struct route_node *route_next(struct route_node *node) { struct route_node *next; struct route_node *start; /* Node may be deleted from route_unlock_node so we have to preserve next node's pointer. */ if (node->l_left) { next = node->l_left; route_lock_node(next); route_unlock_node(node); return next; } if (node->l_right) { next = node->l_right; route_lock_node(next); route_unlock_node(node); return next; } start = node; while (node->parent) { if (node->parent->l_left == node && node->parent->l_right) { next = node->parent->l_right; route_lock_node(next); route_unlock_node(start); return next; } node = node->parent; } route_unlock_node(start); return NULL; } /* Unlock current node and lock next node until limit. */ struct route_node *route_next_until(struct route_node *node, const struct route_node *limit) { struct route_node *next; struct route_node *start; /* Node may be deleted from route_unlock_node so we have to preserve next node's pointer. */ if (node->l_left) { next = node->l_left; route_lock_node(next); route_unlock_node(node); return next; } if (node->l_right) { next = node->l_right; route_lock_node(next); route_unlock_node(node); return next; } start = node; while (node->parent && node != limit) { if (node->parent->l_left == node && node->parent->l_right) { next = node->parent->l_right; route_lock_node(next); route_unlock_node(start); return next; } node = node->parent; } route_unlock_node(start); return NULL; } unsigned long route_table_count(struct route_table *table) { return table->count; } /** * route_node_create * * Default function for creating a route node. */ struct route_node *route_node_create(route_table_delegate_t *delegate, struct route_table *table) { struct route_node *node; node = XCALLOC(MTYPE_ROUTE_NODE, sizeof(struct route_node)); return node; } /** * route_node_destroy * * Default function for destroying a route node. */ void route_node_destroy(route_table_delegate_t *delegate, struct route_table *table, struct route_node *node) { XFREE(MTYPE_ROUTE_NODE, node); } /* * Default delegate. */ static route_table_delegate_t default_delegate = { .create_node = route_node_create, .destroy_node = route_node_destroy}; route_table_delegate_t *route_table_get_default_delegate(void) { return &default_delegate; } /* * route_table_init */ struct route_table *route_table_init(void) { return route_table_init_with_delegate(&default_delegate); } /** * route_table_prefix_iter_cmp * * Compare two prefixes according to the order in which they appear in * an iteration over a tree. * * @return -1 if p1 occurs before p2 (p1 < p2) * 0 if the prefixes are identical (p1 == p2) * +1 if p1 occurs after p2 (p1 > p2) */ int route_table_prefix_iter_cmp(const struct prefix *p1, const struct prefix *p2) { struct prefix common_space; struct prefix *common = &common_space; if (p1->prefixlen <= p2->prefixlen) { if (prefix_match(p1, p2)) { /* * p1 contains p2, or is equal to it. */ return (p1->prefixlen == p2->prefixlen) ? 0 : -1; } } else { /* * Check if p2 contains p1. */ if (prefix_match(p2, p1)) return 1; } route_common(p1, p2, common); assert(common->prefixlen < p1->prefixlen); assert(common->prefixlen < p2->prefixlen); /* * Both prefixes are longer than the common prefix. * * We need to check the bit after the common prefixlen to determine * which one comes later. */ if (prefix_bit(&p1->u.prefix, common->prefixlen)) { /* * We branch to the right to get to p1 from the common prefix. */ assert(!prefix_bit(&p2->u.prefix, common->prefixlen)); return 1; } /* * We branch to the right to get to p2 from the common prefix. */ assert(prefix_bit(&p2->u.prefix, common->prefixlen)); return -1; } /* * route_get_subtree_next * * Helper function that returns the first node that follows the nodes * in the sub-tree under 'node' in iteration order. */ static struct route_node *route_get_subtree_next(struct route_node *node) { while (node->parent) { if (node->parent->l_left == node && node->parent->l_right) return node->parent->l_right; node = node->parent; } return NULL; } /** * route_table_get_next_internal * * Helper function to find the node that occurs after the given prefix in * order of iteration. * * @see route_table_get_next */ static struct route_node * route_table_get_next_internal(struct route_table *table, const struct prefix *p) { struct route_node *node, *tmp_node; int cmp; node = table->top; while (node) { int match; if (node->p.prefixlen < p->prefixlen) match = prefix_match(&node->p, p); else match = prefix_match(p, &node->p); if (match) { if (node->p.prefixlen == p->prefixlen) { /* * The prefix p exists in the tree, just return * the next * node. */ route_lock_node(node); node = route_next(node); if (node) route_unlock_node(node); return (node); } if (node->p.prefixlen > p->prefixlen) { /* * Node is in the subtree of p, and hence * greater than p. */ return node; } /* * p is in the sub-tree under node. */ tmp_node = node->link[prefix_bit(&p->u.prefix, node->p.prefixlen)]; if (tmp_node) { node = tmp_node; continue; } /* * There are no nodes in the direction where p should * be. If * node has a right child, then it must be greater than * p. */ if (node->l_right) return node->l_right; /* * No more children to follow, go upwards looking for * the next * node. */ return route_get_subtree_next(node); } /* * Neither node prefix nor 'p' contains the other. */ cmp = route_table_prefix_iter_cmp(&node->p, p); if (cmp > 0) { /* * Node follows p in iteration order. Return it. */ return node; } assert(cmp < 0); /* * Node and the subtree under it come before prefix p in * iteration order. Prefix p and its sub-tree are not present in * the tree. Go upwards and find the first node that follows the * subtree. That node will also succeed p. */ return route_get_subtree_next(node); } return NULL; } /** * route_table_get_next * * Find the node that occurs after the given prefix in order of * iteration. */ struct route_node *route_table_get_next(struct route_table *table, union prefixconstptr pu) { const struct prefix *p = pu.p; struct route_node *node; node = route_table_get_next_internal(table, p); if (node) { assert(route_table_prefix_iter_cmp(&node->p, p) > 0); route_lock_node(node); } return node; } /* * route_table_iter_init */ void route_table_iter_init(route_table_iter_t *iter, struct route_table *table) { memset(iter, 0, sizeof(*iter)); iter->state = RT_ITER_STATE_INIT; iter->table = table; } /* * route_table_iter_pause * * Pause an iteration over the table. This allows the iteration to be * resumed point after arbitrary additions/deletions from the table. * An iteration can be resumed by just calling route_table_iter_next() * on the iterator. */ void route_table_iter_pause(route_table_iter_t *iter) { switch (iter->state) { case RT_ITER_STATE_INIT: case RT_ITER_STATE_PAUSED: case RT_ITER_STATE_DONE: return; case RT_ITER_STATE_ITERATING: /* * Save the prefix that we are currently at. The next call to * route_table_iter_next() will return the node after this * prefix * in the tree. */ prefix_copy(&iter->pause_prefix, &iter->current->p); route_unlock_node(iter->current); iter->current = NULL; iter->state = RT_ITER_STATE_PAUSED; return; default: assert(0); } } /* * route_table_iter_cleanup * * Release any resources held by the iterator. */ void route_table_iter_cleanup(route_table_iter_t *iter) { if (iter->state == RT_ITER_STATE_ITERATING) { route_unlock_node(iter->current); iter->current = NULL; } assert(!iter->current); /* * Set the state to RT_ITER_STATE_DONE to make any * route_table_iter_next() calls on this iterator return NULL. */ iter->state = RT_ITER_STATE_DONE; } frr-7.2.1/lib/table.h0000644000000000000000000002236013610377563011225 00000000000000/* * Routing Table * Copyright (C) 1998 Kunihiro Ishiguro * * This file is part of GNU Zebra. * * GNU Zebra is free software; you can redistribute it and/or modify it * under the terms of the GNU General Public License as published by the * Free Software Foundation; either version 2, or (at your option) any * later version. * * GNU Zebra 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; see the file COPYING; if not, write to the Free Software * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA */ #ifndef _ZEBRA_TABLE_H #define _ZEBRA_TABLE_H #include "memory.h" #include "hash.h" #include "prefix.h" #include "typesafe.h" #ifdef __cplusplus extern "C" { #endif DECLARE_MTYPE(ROUTE_NODE) /* * Forward declarations. */ struct route_node; struct route_table; /* * route_table_delegate_t * * Function vector that can be used by a client to customize the * behavior of one or more route tables. */ typedef struct route_table_delegate_t_ route_table_delegate_t; typedef struct route_node *(*route_table_create_node_func_t)( route_table_delegate_t *, struct route_table *); typedef void (*route_table_destroy_node_func_t)(route_table_delegate_t *, struct route_table *, struct route_node *); struct route_table_delegate_t_ { route_table_create_node_func_t create_node; route_table_destroy_node_func_t destroy_node; }; PREDECL_HASH(rn_hash_node) /* Routing table top structure. */ struct route_table { struct route_node *top; struct rn_hash_node_head hash; /* * Delegate that performs certain functions for this table. */ route_table_delegate_t *delegate; void (*cleanup)(struct route_table *, struct route_node *); unsigned long count; /* * User data. */ void *info; }; /* * node->link is really internal to the table code and should not be * accessed by outside code. We don't have any writers (yay), though some * readers are left to be fixed. * * rationale: we need to add a hash table in parallel, to speed up * exact-match lookups. * * same really applies for node->parent, though that's less of an issue. * table->link should be - and is - NEVER written by outside code */ #ifdef FRR_COMPILING_TABLE_C #define table_rdonly(x) x #define table_internal(x) x #else #define table_rdonly(x) const x #define table_internal(x) \ const x __attribute__( \ (deprecated("this should only be accessed by lib/table.c"))) /* table_internal is for node->link and node->lock, once we have done * something about remaining accesses */ #endif /* so... the problem with this is that "const" doesn't mean "readonly". * It in fact may allow the compiler to optimize based on the assumption * that the value doesn't change. Hence, since the only purpose of this * is to aid in development, don't put the "const" in release builds. * * (I haven't seen this actually break, but GCC and LLVM are getting ever * more aggressive in optimizing...) */ #ifndef DEV_BUILD #undef table_rdonly #define table_rdonly(x) x #endif /* * Macro that defines all fields in a route node. */ #define ROUTE_NODE_FIELDS \ /* Actual prefix of this radix. */ \ struct prefix p; \ \ /* Tree link. */ \ struct route_table *table_rdonly(table); \ struct route_node *table_rdonly(parent); \ struct route_node *table_rdonly(link[2]); \ \ /* Lock of this radix */ \ unsigned int table_rdonly(lock); \ \ struct rn_hash_node_item nodehash; \ /* Each node of route. */ \ void *info; \ /* Each routing entry. */ struct route_node { ROUTE_NODE_FIELDS #define l_left link[0] #define l_right link[1] }; typedef struct route_table_iter_t_ route_table_iter_t; typedef enum { RT_ITER_STATE_INIT, RT_ITER_STATE_ITERATING, RT_ITER_STATE_PAUSED, RT_ITER_STATE_DONE } route_table_iter_state_t; /* * route_table_iter_t * * Structure that holds state for iterating over a route table. */ struct route_table_iter_t_ { route_table_iter_state_t state; /* * Routing table that we are iterating over. The caller must ensure * that that table outlives the iterator. */ struct route_table *table; /* * The node that the iterator is currently on. */ struct route_node *current; /* * The last prefix that the iterator processed before it was paused. */ struct prefix pause_prefix; }; /* Prototypes. */ extern struct route_table *route_table_init(void); extern struct route_table * route_table_init_with_delegate(route_table_delegate_t *delegate); extern route_table_delegate_t *route_table_get_default_delegate(void); static inline void *route_table_get_info(struct route_table *table) { return table->info; } static inline void route_table_set_info(struct route_table *table, void *d) { table->info = d; } /* ext_pure => extern __attribute__((pure)) * does not modify memory (but depends on mem), allows compiler to optimize */ extern void route_table_finish(struct route_table *table); ext_pure struct route_node *route_top(struct route_table *table); ext_pure struct route_node *route_next(struct route_node *node); ext_pure struct route_node *route_next_until(struct route_node *node, const struct route_node *limit); extern struct route_node *route_node_get(struct route_table *table, union prefixconstptr pu); ext_pure struct route_node *route_node_lookup(struct route_table *table, union prefixconstptr pu); ext_pure struct route_node *route_node_lookup_maynull(struct route_table *table, union prefixconstptr pu); ext_pure struct route_node *route_node_match(struct route_table *table, union prefixconstptr pu); ext_pure struct route_node *route_node_match_ipv4(struct route_table *table, const struct in_addr *addr); ext_pure struct route_node *route_node_match_ipv6(struct route_table *table, const struct in6_addr *addr); ext_pure unsigned long route_table_count(struct route_table *table); extern struct route_node *route_node_create(route_table_delegate_t *delegate, struct route_table *table); extern void route_node_delete(struct route_node *node); extern void route_node_destroy(route_table_delegate_t *delegate, struct route_table *table, struct route_node *node); ext_pure struct route_node *route_table_get_next(struct route_table *table, union prefixconstptr pu); ext_pure int route_table_prefix_iter_cmp(const struct prefix *p1, const struct prefix *p2); /* * Iterator functions. */ extern void route_table_iter_init(route_table_iter_t *iter, struct route_table *table); extern void route_table_iter_pause(route_table_iter_t *iter); extern void route_table_iter_cleanup(route_table_iter_t *iter); /* * Inline functions. */ /* Lock node. */ static inline struct route_node *route_lock_node(struct route_node *node) { (*(unsigned *)&node->lock)++; return node; } /* Unlock node. */ static inline void route_unlock_node(struct route_node *node) { assert(node->lock > 0); (*(unsigned *)&node->lock)--; if (node->lock == 0) route_node_delete(node); } /* * route_table_iter_next * * Get the next node in the tree. */ static inline struct route_node *route_table_iter_next(route_table_iter_t *iter) { struct route_node *node; switch (iter->state) { case RT_ITER_STATE_INIT: /* * We're just starting the iteration. */ node = route_top(iter->table); break; case RT_ITER_STATE_ITERATING: node = route_next(iter->current); break; case RT_ITER_STATE_PAUSED: /* * Start with the node following pause_prefix. */ node = route_table_get_next(iter->table, &iter->pause_prefix); break; case RT_ITER_STATE_DONE: return NULL; default: /* Suppress uninitialized variable warning */ node = NULL; assert(0); } iter->current = node; if (node) iter->state = RT_ITER_STATE_ITERATING; else iter->state = RT_ITER_STATE_DONE; return node; } /* * route_table_iter_is_done * * Returns true if the iteration is complete. */ static inline int route_table_iter_is_done(route_table_iter_t *iter) { return iter->state == RT_ITER_STATE_DONE; } /* * route_table_iter_started * * Returns true if this iterator has started iterating over the tree. */ static inline int route_table_iter_started(route_table_iter_t *iter) { return iter->state != RT_ITER_STATE_INIT; } #ifdef __cplusplus } #endif #endif /* _ZEBRA_TABLE_H */ frr-7.2.1/lib/termtable.c0000644000000000000000000003073113610377563012111 00000000000000/* * ASCII table generator. * Copyright (C) 2017 Cumulus Networks * Quentin Young * * This program is free software; you can redistribute it and/or modify it * under the terms of the 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; see the file COPYING; if not, write to the Free Software * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA */ #include #include #include "printfrr.h" #include "memory.h" #include "termtable.h" DEFINE_MTYPE_STATIC(LIB, TTABLE, "ASCII table") /* clang-format off */ struct ttable_style ttable_styles[] = { { // default ascii .corner = '+', .rownums_on = false, .indent = 1, .border = { .top = '-', .bottom = '-', .left = '|', .right = '|', .top_on = true, .bottom_on = true, .left_on = true, .right_on = true, }, .cell = { .lpad = 1, .rpad = 1, .align = LEFT, .border = { .bottom = '-', .bottom_on = true, .top = '-', .top_on = false, .right = '|', .right_on = true, .left = '|', .left_on = false, }, }, }, { // blank, suitable for plaintext alignment .corner = ' ', .rownums_on = false, .indent = 1, .border = { .top = ' ', .bottom = ' ', .left = ' ', .right = ' ', .top_on = false, .bottom_on = false, .left_on = false, .right_on = false, }, .cell = { .lpad = 0, .rpad = 3, .align = LEFT, .border = { .bottom = ' ', .bottom_on = false, .top = ' ', .top_on = false, .right = ' ', .right_on = false, .left = ' ', .left_on = false, }, } } }; /* clang-format on */ void ttable_del(struct ttable *tt) { for (int i = tt->nrows - 1; i >= 0; i--) ttable_del_row(tt, i); XFREE(MTYPE_TTABLE, tt->table); XFREE(MTYPE_TTABLE, tt); } struct ttable *ttable_new(struct ttable_style *style) { struct ttable *tt; tt = XCALLOC(MTYPE_TTABLE, sizeof(struct ttable)); tt->style = *style; tt->nrows = 0; tt->ncols = 0; tt->size = 0; tt->table = NULL; return tt; } /** * Inserts or appends a new row at the specified index. * * If the index is -1, the row is added to the end of the table. Otherwise the * index must be a valid index into tt->table. * * If the table already has at least one row (and therefore a determinate * number of columns), a format string specifying a number of columns not equal * to tt->ncols will result in a no-op and a return value of NULL. * * @param tt table to insert into * @param i insertion index; inserted row will be (i + 1)'th row * @param format printf format string as in ttable_[add|insert]_row() * @param ap pre-initialized variadic list of arguments for format string * * @return pointer to the first cell of allocated row */ static struct ttable_cell *ttable_insert_row_va(struct ttable *tt, int i, const char *format, va_list ap) { assert(i >= -1 && i < tt->nrows); char shortbuf[256]; char *res, *orig, *section; struct ttable_cell *row; int col = 0; int ncols = 0; /* count how many columns we have */ for (int j = 0; format[j]; j++) ncols += !!(format[j] == '|'); ncols++; if (tt->ncols == 0) tt->ncols = ncols; else if (ncols != tt->ncols) return NULL; /* reallocate chunk if necessary */ while (tt->size < (tt->nrows + 1) * sizeof(struct ttable_cell *)) { tt->size = MAX(2 * tt->size, 2 * sizeof(struct ttable_cell *)); tt->table = XREALLOC(MTYPE_TTABLE, tt->table, tt->size); } /* CALLOC a block of cells */ row = XCALLOC(MTYPE_TTABLE, tt->ncols * sizeof(struct ttable_cell)); res = vasnprintfrr(MTYPE_TMP, shortbuf, sizeof(shortbuf), format, ap); orig = res; while (res && col < tt->ncols) { section = strsep(&res, "|"); row[col].text = XSTRDUP(MTYPE_TTABLE, section); row[col].style = tt->style.cell; col++; } if (orig != shortbuf) XFREE(MTYPE_TMP, orig); /* insert row */ if (i == -1 || i == tt->nrows) tt->table[tt->nrows] = row; else { memmove(&tt->table[i + 1], &tt->table[i], (tt->nrows - i) * sizeof(struct ttable_cell *)); tt->table[i] = row; } tt->nrows++; return row; } struct ttable_cell *ttable_insert_row(struct ttable *tt, unsigned int i, const char *format, ...) { struct ttable_cell *ret; va_list ap; va_start(ap, format); ret = ttable_insert_row_va(tt, i, format, ap); va_end(ap); return ret; } struct ttable_cell *ttable_add_row(struct ttable *tt, const char *format, ...) { struct ttable_cell *ret; va_list ap; va_start(ap, format); ret = ttable_insert_row_va(tt, -1, format, ap); va_end(ap); return ret; } void ttable_del_row(struct ttable *tt, unsigned int i) { assert((int)i < tt->nrows); for (int j = 0; j < tt->ncols; j++) XFREE(MTYPE_TTABLE, tt->table[i][j].text); XFREE(MTYPE_TTABLE, tt->table[i]); memmove(&tt->table[i], &tt->table[i + 1], (tt->nrows - i - 1) * sizeof(struct ttable_cell *)); tt->nrows--; if (tt->nrows == 0) tt->ncols = 0; } void ttable_align(struct ttable *tt, unsigned int row, unsigned int col, unsigned int nrow, unsigned int ncol, enum ttable_align align) { assert((int)row < tt->nrows); assert((int)col < tt->ncols); assert((int)row + (int)nrow <= tt->nrows); assert((int)col + (int)ncol <= tt->ncols); for (unsigned int i = row; i < row + nrow; i++) for (unsigned int j = col; j < col + ncol; j++) tt->table[i][j].style.align = align; } static void ttable_cell_pad(struct ttable_cell *cell, enum ttable_align align, short pad) { if (align == LEFT) cell->style.lpad = pad; else cell->style.rpad = pad; } void ttable_pad(struct ttable *tt, unsigned int row, unsigned int col, unsigned int nrow, unsigned int ncol, enum ttable_align align, short pad) { assert((int)row < tt->nrows); assert((int)col < tt->ncols); assert((int)row + (int)nrow <= tt->nrows); assert((int)col + (int)ncol <= tt->ncols); for (unsigned int i = row; i < row + nrow; i++) for (unsigned int j = col; j < col + ncol; j++) ttable_cell_pad(&tt->table[i][j], align, pad); } void ttable_restyle(struct ttable *tt) { for (int i = 0; i < tt->nrows; i++) for (int j = 0; j < tt->ncols; j++) tt->table[i][j].style = tt->style.cell; } void ttable_colseps(struct ttable *tt, unsigned int col, enum ttable_align align, bool on, char sep) { for (int i = 0; i < tt->nrows; i++) { if (align == RIGHT) { tt->table[i][col].style.border.right_on = on; tt->table[i][col].style.border.right = sep; } else { tt->table[i][col].style.border.left_on = on; tt->table[i][col].style.border.left = sep; } } } void ttable_rowseps(struct ttable *tt, unsigned int row, enum ttable_align align, bool on, char sep) { for (int i = 0; i < tt->ncols; i++) { if (align == TOP) { tt->table[row][i].style.border.top_on = on; tt->table[row][i].style.border.top = sep; } else { tt->table[row][i].style.border.bottom_on = on; tt->table[row][i].style.border.bottom = sep; } } } char *ttable_dump(struct ttable *tt, const char *newline) { /* clang-format off */ char *buf; // print buffer size_t pos; // position in buffer size_t nl_len; // strlen(newline) int cw[tt->ncols]; // calculated column widths int nlines; // total number of newlines / table lines size_t width; // length of one line, with newline int abspad; // calculated whitespace for sprintf char *left; // left part of line size_t lsize; // size of above char *right; // right part of line size_t rsize; // size of above struct ttable_cell *cell, *row; // iteration pointers /* clang-format on */ nl_len = strlen(newline); /* calculate width of each column */ memset(cw, 0x00, sizeof(int) * tt->ncols); for (int j = 0; j < tt->ncols; j++) for (int i = 0, cellw = 0; i < tt->nrows; i++) { cell = &tt->table[i][j]; cellw = 0; cellw += (int)strlen(cell->text); cellw += cell->style.lpad; cellw += cell->style.rpad; if (j != 0) cellw += cell->style.border.left_on ? 1 : 0; if (j != tt->ncols - 1) cellw += cell->style.border.right_on ? 1 : 0; cw[j] = MAX(cw[j], cellw); } /* calculate overall line width, including newline */ width = 0; width += tt->style.indent; width += tt->style.border.left_on ? 1 : 0; width += tt->style.border.right_on ? 1 : 0; width += strlen(newline); for (int i = 0; i < tt->ncols; i++) width += cw[i]; /* calculate number of lines en total */ nlines = tt->nrows; nlines += tt->style.border.top_on ? 1 : 0; nlines += 1; // tt->style.border.bottom_on ? 1 : 1; makes life easier for (int i = 0; i < tt->nrows; i++) { /* if leftmost cell has top / bottom border, whole row does */ nlines += tt->table[i][0].style.border.top_on ? 1 : 0; nlines += tt->table[i][0].style.border.bottom_on ? 1 : 0; } /* initialize left & right */ lsize = tt->style.indent + (tt->style.border.left_on ? 1 : 0); left = XCALLOC(MTYPE_TTABLE, lsize); rsize = nl_len + (tt->style.border.right_on ? 1 : 0); right = XCALLOC(MTYPE_TTABLE, rsize); memset(left, ' ', lsize); if (tt->style.border.left_on) left[lsize - 1] = tt->style.border.left; if (tt->style.border.right_on) { right[0] = tt->style.border.right; memcpy(&right[1], newline, nl_len); } else memcpy(&right[0], newline, nl_len); /* allocate print buffer */ buf = XCALLOC(MTYPE_TMP, width * (nlines + 1) + 1); pos = 0; if (tt->style.border.top_on) { memcpy(&buf[pos], left, lsize); pos += lsize; for (size_t i = 0; i < width - lsize - rsize; i++) buf[pos++] = tt->style.border.top; memcpy(&buf[pos], right, rsize); pos += rsize; } for (int i = 0; i < tt->nrows; i++) { row = tt->table[i]; /* if top border and not first row, print top row border */ if (row[0].style.border.top_on && i != 0) { memcpy(&buf[pos], left, lsize); pos += lsize; for (size_t l = 0; l < width - lsize - rsize; l++) buf[pos++] = row[0].style.border.top; pos -= width - lsize - rsize; for (int k = 0; k < tt->ncols; k++) { if (k != 0 && row[k].style.border.left_on) buf[pos] = tt->style.corner; pos += cw[k]; if (row[k].style.border.right_on && k != tt->ncols - 1) buf[pos - 1] = tt->style.corner; } memcpy(&buf[pos], right, rsize); pos += rsize; } memcpy(&buf[pos], left, lsize); pos += lsize; for (int j = 0; j < tt->ncols; j++) { /* if left border && not first col print left border */ if (row[j].style.border.left_on && j != 0) buf[pos++] = row[j].style.border.left; /* print left padding */ for (int k = 0; k < row[j].style.lpad; k++) buf[pos++] = ' '; /* calculate padding for sprintf */ abspad = cw[j]; abspad -= row[j].style.rpad; abspad -= row[j].style.lpad; if (j != 0) abspad -= row[j].style.border.left_on ? 1 : 0; if (j != tt->ncols - 1) abspad -= row[j].style.border.right_on ? 1 : 0; /* print text */ const char *fmt; if (row[j].style.align == LEFT) fmt = "%-*s"; else fmt = "%*s"; pos += sprintf(&buf[pos], fmt, abspad, row[j].text); /* print right padding */ for (int k = 0; k < row[j].style.rpad; k++) buf[pos++] = ' '; /* if right border && not last col print right border */ if (row[j].style.border.right_on && j != tt->ncols - 1) buf[pos++] = row[j].style.border.right; } memcpy(&buf[pos], right, rsize); pos += rsize; /* if bottom border and not last row, print bottom border */ if (row[0].style.border.bottom_on && i != tt->nrows - 1) { memcpy(&buf[pos], left, lsize); pos += lsize; for (size_t l = 0; l < width - lsize - rsize; l++) buf[pos++] = row[0].style.border.bottom; pos -= width - lsize - rsize; for (int k = 0; k < tt->ncols; k++) { if (k != 0 && row[k].style.border.left_on) buf[pos] = tt->style.corner; pos += cw[k]; if (row[k].style.border.right_on && k != tt->ncols - 1) buf[pos - 1] = tt->style.corner; } memcpy(&buf[pos], right, rsize); pos += rsize; } assert(!buf[pos]); /* pos == & of first \0 in buf */ } if (tt->style.border.bottom_on) { memcpy(&buf[pos], left, lsize); pos += lsize; for (size_t l = 0; l < width - lsize - rsize; l++) buf[pos++] = tt->style.border.bottom; memcpy(&buf[pos], right, rsize); pos += rsize; } buf[pos] = '\0'; XFREE(MTYPE_TTABLE, left); XFREE(MTYPE_TTABLE, right); return buf; } frr-7.2.1/lib/termtable.h0000644000000000000000000001736413610377563012125 00000000000000/* * ASCII table generator. * Copyright (C) 2017 Cumulus Networks * Quentin Young * * This program is free software; you can redistribute it and/or modify it * under the terms of the 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; see the file COPYING; if not, write to the Free Software * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA */ #ifndef _TERMTABLE_H_ #define _TERMTABLE_H_ #include #ifdef __cplusplus extern "C" { #endif enum ttable_align { LEFT, RIGHT, TOP, BOTTOM, }; struct ttable_border { char top; char bottom; char left; char right; bool top_on; bool bottom_on; bool left_on; bool right_on; }; /* cell style and cell */ struct ttable_cellstyle { short lpad; short rpad; enum ttable_align align; struct ttable_border border; }; struct ttable_cell { char *text; struct ttable_cellstyle style; }; /* table style and table */ struct ttable_style { char corner; /* intersection */ int indent; /* left table indent */ bool rownums_on; /* show row numbers; unimplemented */ struct ttable_border border; struct ttable_cellstyle cell; }; struct ttable { int nrows; /* number of rows */ int ncols; /* number of cols */ struct ttable_cell **table; /* table, row x col */ size_t size; /* size */ struct ttable_style style; /* style */ }; /* some predefined styles */ #define TTSTYLE_ASCII 0 #define TTSTYLE_BLANK 1 extern struct ttable_style ttable_styles[2]; /** * Creates a new table with the default style, which looks like this: * * +----------+----------+ * | column 1 | column 2 | * +----------+----------+ * | data... | data!! | * +----------+----------+ * | datums | 12345 | * +----------+----------+ * * @return the created table */ struct ttable *ttable_new(struct ttable_style *tts); /** * Deletes a table and releases all associated resources. * * @param tt the table to destroy */ void ttable_del(struct ttable *tt); /** * Deletes an individual cell. * * @param cell the cell to destroy */ void ttable_cell_del(struct ttable_cell *cell); /** * Inserts a new row at the given index. * * The row contents are determined by a format string. The format string has * the same form as a regular printf format string, except that columns are * delimited by '|'. For example, to make the first column of the table above, * the call is: * * ttable_insert_row(, , "%s|%s", "column 1", "column 2"); * * All features of printf format strings are permissible here. * * Caveats: * - At present you cannot insert '|' into a cell's contents. * - If there are N columns, '|' must appear n-1 times or the row will not be * created * * @param tt table to insert row into * @param row the row number (begins at 0) * @param format column-separated format string * @param ... arguments to format string * * @return pointer to the first cell in the created row, or NULL if not enough * columns were specified */ struct ttable_cell *ttable_insert_row(struct ttable *tt, unsigned int row, const char *format, ...) PRINTFRR(3, 4); /** * Inserts a new row at the end of the table. * * The row contents are determined by a format string. The format string has * the same form as a regular printf format string, except that columns are * delimited by '|'. For example, to make the first column of the table above, * the call is: * * ttable_add_row(, "%s|%s", "column 1", "column 2"); * * All features of printf format strings are permissible here. * * Caveats: * - At present you cannot insert '|' into a cell's contents. * - If there are N columns, '|' must appear n-1 times or the row will not be * created * * @param tt table to insert row into * @param format column-separated format string * @param ... arguments to format string * * @return pointer to the first cell in the created row, or NULL if not enough * columns were specified */ struct ttable_cell *ttable_add_row(struct ttable *tt, const char *format, ...) PRINTFRR(2, 3); /** * Removes a row from the table. * * @param tt table to delete row from * @param row the row number (begins at 0) */ void ttable_del_row(struct ttable *tt, unsigned int row); /** * Sets alignment for a range of cells. * * Available alignments are LEFT and RIGHT. Cell contents will be aligned * accordingly, while respecting padding (if any). Suppose a cell has: * * lpad = 1 * rpad = 1 * align = RIGHT * text = 'datums' * * The cell would look like: * * +-------------------+ * | datums | * +-------------------+ * * On the other hand: * * lpad = 1 * rpad = 10 * align = RIGHT * text = 'datums' * * +-------------------+ * | datums | * +-------------------+ * * The default alignment is LEFT. * * @param tt the table to set alignment on * @param srow starting row index * @param scol starting column index * @param nrow # rows to align * @param ncol # cols to align * @param align the alignment to set */ void ttable_align(struct ttable *tt, unsigned int srow, unsigned int scol, unsigned int erow, unsigned int ecol, enum ttable_align align); /** * Sets padding for a range of cells. * * Available padding options are LEFT and RIGHT (the alignment enum is reused). * Both options may be set. Padding is treated as though it is stuck to the * walls of the cell. Suppose a cell has: * * lpad = 4 * rpad = 2 * align = RIGHT * text = 'datums' * * The cell padding, marked by '.', would look like: * * +--------------+ * | .datums. | * +--------------+ * * If the column is wider than the cell, the cell contents are aligned in an * additional padded field according to the cell alignment. * * +--------------------+ * | Data!!!11!~~~~~:-) | * +--------------------+ * | . datums. | * +--------------------+ * * @param tt the table to set padding on * @param srow starting row index * @param scol starting column index * @param nrow # rows to pad * @param ncol # cols to pad * @param align LEFT or RIGHT * @param pad # spaces to pad with */ void ttable_pad(struct ttable *tt, unsigned int srow, unsigned int scol, unsigned int nrow, unsigned int ncol, enum ttable_align align, short pad); /** * Restyle all cells according to table.cell.style. * * @param tt table to restyle */ void ttable_restyle(struct ttable *tt); /** * Turn left/right column separators on or off for specified column. * * @param tt table * @param col column index * @param align left or right separators * @param on true/false for on/off * @param sep character to use */ void ttable_colseps(struct ttable *tt, unsigned int col, enum ttable_align align, bool on, char sep); /** * Turn bottom row separators on or off for specified row. * * @param tt table * @param row row index * @param align left or right separators * @param on true/false for on/off * @param sep character to use */ void ttable_rowseps(struct ttable *tt, unsigned int row, enum ttable_align align, bool on, char sep); /** * Dumps a table to a heap-allocated string. * * Caller must free this string after use with * * XFREE (MTYPE_TMP, str); * * @param tt the table to dump * @param newline the desired newline sequence to use, null terminated. * @return table in text form */ char *ttable_dump(struct ttable *tt, const char *newline); #ifdef __cplusplus } #endif #endif /* _TERMTABLE_H */ frr-7.2.1/lib/thread.c0000644000000000000000000012207313610377563011402 00000000000000/* Thread management routine * Copyright (C) 1998, 2000 Kunihiro Ishiguro * * This file is part of GNU Zebra. * * GNU Zebra is free software; you can redistribute it and/or modify it * under the terms of the GNU General Public License as published by the * Free Software Foundation; either version 2, or (at your option) any * later version. * * GNU Zebra 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; see the file COPYING; if not, write to the Free Software * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA */ /* #define DEBUG */ #include #include #include "thread.h" #include "memory.h" #include "frrcu.h" #include "log.h" #include "hash.h" #include "command.h" #include "sigevent.h" #include "network.h" #include "jhash.h" #include "frratomic.h" #include "frr_pthread.h" #include "lib_errors.h" DEFINE_MTYPE_STATIC(LIB, THREAD, "Thread") DEFINE_MTYPE_STATIC(LIB, THREAD_MASTER, "Thread master") DEFINE_MTYPE_STATIC(LIB, THREAD_POLL, "Thread Poll Info") DEFINE_MTYPE_STATIC(LIB, THREAD_STATS, "Thread stats") DECLARE_LIST(thread_list, struct thread, threaditem) static int thread_timer_cmp(const struct thread *a, const struct thread *b) { if (a->u.sands.tv_sec < b->u.sands.tv_sec) return -1; if (a->u.sands.tv_sec > b->u.sands.tv_sec) return 1; if (a->u.sands.tv_usec < b->u.sands.tv_usec) return -1; if (a->u.sands.tv_usec > b->u.sands.tv_usec) return 1; return 0; } DECLARE_HEAP(thread_timer_list, struct thread, timeritem, thread_timer_cmp) #if defined(__APPLE__) #include #include #endif #define AWAKEN(m) \ do { \ static unsigned char wakebyte = 0x01; \ write(m->io_pipe[1], &wakebyte, 1); \ } while (0); /* control variable for initializer */ static pthread_once_t init_once = PTHREAD_ONCE_INIT; pthread_key_t thread_current; static pthread_mutex_t masters_mtx = PTHREAD_MUTEX_INITIALIZER; static struct list *masters; static void thread_free(struct thread_master *master, struct thread *thread); /* CLI start ---------------------------------------------------------------- */ static unsigned int cpu_record_hash_key(const struct cpu_thread_history *a) { int size = sizeof(a->func); return jhash(&a->func, size, 0); } static bool cpu_record_hash_cmp(const struct cpu_thread_history *a, const struct cpu_thread_history *b) { return a->func == b->func; } static void *cpu_record_hash_alloc(struct cpu_thread_history *a) { struct cpu_thread_history *new; new = XCALLOC(MTYPE_THREAD_STATS, sizeof(struct cpu_thread_history)); new->func = a->func; new->funcname = a->funcname; return new; } static void cpu_record_hash_free(void *a) { struct cpu_thread_history *hist = a; XFREE(MTYPE_THREAD_STATS, hist); } static void vty_out_cpu_thread_history(struct vty *vty, struct cpu_thread_history *a) { vty_out(vty, "%5zu %10zu.%03zu %9zu %8zu %9zu %8zu %9zu", (size_t)a->total_active, a->cpu.total / 1000, a->cpu.total % 1000, (size_t)a->total_calls, a->cpu.total / a->total_calls, a->cpu.max, a->real.total / a->total_calls, a->real.max); vty_out(vty, " %c%c%c%c%c %s\n", a->types & (1 << THREAD_READ) ? 'R' : ' ', a->types & (1 << THREAD_WRITE) ? 'W' : ' ', a->types & (1 << THREAD_TIMER) ? 'T' : ' ', a->types & (1 << THREAD_EVENT) ? 'E' : ' ', a->types & (1 << THREAD_EXECUTE) ? 'X' : ' ', a->funcname); } static void cpu_record_hash_print(struct hash_bucket *bucket, void *args[]) { struct cpu_thread_history *totals = args[0]; struct cpu_thread_history copy; struct vty *vty = args[1]; uint8_t *filter = args[2]; struct cpu_thread_history *a = bucket->data; copy.total_active = atomic_load_explicit(&a->total_active, memory_order_seq_cst); copy.total_calls = atomic_load_explicit(&a->total_calls, memory_order_seq_cst); copy.cpu.total = atomic_load_explicit(&a->cpu.total, memory_order_seq_cst); copy.cpu.max = atomic_load_explicit(&a->cpu.max, memory_order_seq_cst); copy.real.total = atomic_load_explicit(&a->real.total, memory_order_seq_cst); copy.real.max = atomic_load_explicit(&a->real.max, memory_order_seq_cst); copy.types = atomic_load_explicit(&a->types, memory_order_seq_cst); copy.funcname = a->funcname; if (!(copy.types & *filter)) return; vty_out_cpu_thread_history(vty, ©); totals->total_active += copy.total_active; totals->total_calls += copy.total_calls; totals->real.total += copy.real.total; if (totals->real.max < copy.real.max) totals->real.max = copy.real.max; totals->cpu.total += copy.cpu.total; if (totals->cpu.max < copy.cpu.max) totals->cpu.max = copy.cpu.max; } static void cpu_record_print(struct vty *vty, uint8_t filter) { struct cpu_thread_history tmp; void *args[3] = {&tmp, vty, &filter}; struct thread_master *m; struct listnode *ln; memset(&tmp, 0, sizeof tmp); tmp.funcname = "TOTAL"; tmp.types = filter; frr_with_mutex(&masters_mtx) { for (ALL_LIST_ELEMENTS_RO(masters, ln, m)) { const char *name = m->name ? m->name : "main"; char underline[strlen(name) + 1]; memset(underline, '-', sizeof(underline)); underline[sizeof(underline) - 1] = '\0'; vty_out(vty, "\n"); vty_out(vty, "Showing statistics for pthread %s\n", name); vty_out(vty, "-------------------------------%s\n", underline); vty_out(vty, "%21s %18s %18s\n", "", "CPU (user+system):", "Real (wall-clock):"); vty_out(vty, "Active Runtime(ms) Invoked Avg uSec Max uSecs"); vty_out(vty, " Avg uSec Max uSecs"); vty_out(vty, " Type Thread\n"); if (m->cpu_record->count) hash_iterate( m->cpu_record, (void (*)(struct hash_bucket *, void *))cpu_record_hash_print, args); else vty_out(vty, "No data to display yet.\n"); vty_out(vty, "\n"); } } vty_out(vty, "\n"); vty_out(vty, "Total thread statistics\n"); vty_out(vty, "-------------------------\n"); vty_out(vty, "%21s %18s %18s\n", "", "CPU (user+system):", "Real (wall-clock):"); vty_out(vty, "Active Runtime(ms) Invoked Avg uSec Max uSecs"); vty_out(vty, " Avg uSec Max uSecs"); vty_out(vty, " Type Thread\n"); if (tmp.total_calls > 0) vty_out_cpu_thread_history(vty, &tmp); } static void cpu_record_hash_clear(struct hash_bucket *bucket, void *args[]) { uint8_t *filter = args[0]; struct hash *cpu_record = args[1]; struct cpu_thread_history *a = bucket->data; if (!(a->types & *filter)) return; hash_release(cpu_record, bucket->data); } static void cpu_record_clear(uint8_t filter) { uint8_t *tmp = &filter; struct thread_master *m; struct listnode *ln; frr_with_mutex(&masters_mtx) { for (ALL_LIST_ELEMENTS_RO(masters, ln, m)) { frr_with_mutex(&m->mtx) { void *args[2] = {tmp, m->cpu_record}; hash_iterate( m->cpu_record, (void (*)(struct hash_bucket *, void *))cpu_record_hash_clear, args); } } } } static uint8_t parse_filter(const char *filterstr) { int i = 0; int filter = 0; while (filterstr[i] != '\0') { switch (filterstr[i]) { case 'r': case 'R': filter |= (1 << THREAD_READ); break; case 'w': case 'W': filter |= (1 << THREAD_WRITE); break; case 't': case 'T': filter |= (1 << THREAD_TIMER); break; case 'e': case 'E': filter |= (1 << THREAD_EVENT); break; case 'x': case 'X': filter |= (1 << THREAD_EXECUTE); break; default: break; } ++i; } return filter; } DEFUN (show_thread_cpu, show_thread_cpu_cmd, "show thread cpu [FILTER]", SHOW_STR "Thread information\n" "Thread CPU usage\n" "Display filter (rwtex)\n") { uint8_t filter = (uint8_t)-1U; int idx = 0; if (argv_find(argv, argc, "FILTER", &idx)) { filter = parse_filter(argv[idx]->arg); if (!filter) { vty_out(vty, "Invalid filter \"%s\" specified; must contain at least" "one of 'RWTEXB'\n", argv[idx]->arg); return CMD_WARNING; } } cpu_record_print(vty, filter); return CMD_SUCCESS; } static void show_thread_poll_helper(struct vty *vty, struct thread_master *m) { const char *name = m->name ? m->name : "main"; char underline[strlen(name) + 1]; struct thread *thread; uint32_t i; memset(underline, '-', sizeof(underline)); underline[sizeof(underline) - 1] = '\0'; vty_out(vty, "\nShowing poll FD's for %s\n", name); vty_out(vty, "----------------------%s\n", underline); vty_out(vty, "Count: %u/%d\n", (uint32_t)m->handler.pfdcount, m->fd_limit); for (i = 0; i < m->handler.pfdcount; i++) { vty_out(vty, "\t%6d fd:%6d events:%2d revents:%2d\t\t", i, m->handler.pfds[i].fd, m->handler.pfds[i].events, m->handler.pfds[i].revents); if (m->handler.pfds[i].events & POLLIN) { thread = m->read[m->handler.pfds[i].fd]; if (!thread) vty_out(vty, "ERROR "); else vty_out(vty, "%s ", thread->funcname); } else vty_out(vty, " "); if (m->handler.pfds[i].events & POLLOUT) { thread = m->write[m->handler.pfds[i].fd]; if (!thread) vty_out(vty, "ERROR\n"); else vty_out(vty, "%s\n", thread->funcname); } else vty_out(vty, "\n"); } } DEFUN (show_thread_poll, show_thread_poll_cmd, "show thread poll", SHOW_STR "Thread information\n" "Show poll FD's and information\n") { struct listnode *node; struct thread_master *m; frr_with_mutex(&masters_mtx) { for (ALL_LIST_ELEMENTS_RO(masters, node, m)) { show_thread_poll_helper(vty, m); } } return CMD_SUCCESS; } DEFUN (clear_thread_cpu, clear_thread_cpu_cmd, "clear thread cpu [FILTER]", "Clear stored data in all pthreads\n" "Thread information\n" "Thread CPU usage\n" "Display filter (rwtexb)\n") { uint8_t filter = (uint8_t)-1U; int idx = 0; if (argv_find(argv, argc, "FILTER", &idx)) { filter = parse_filter(argv[idx]->arg); if (!filter) { vty_out(vty, "Invalid filter \"%s\" specified; must contain at least" "one of 'RWTEXB'\n", argv[idx]->arg); return CMD_WARNING; } } cpu_record_clear(filter); return CMD_SUCCESS; } void thread_cmd_init(void) { install_element(VIEW_NODE, &show_thread_cpu_cmd); install_element(VIEW_NODE, &show_thread_poll_cmd); install_element(ENABLE_NODE, &clear_thread_cpu_cmd); } /* CLI end ------------------------------------------------------------------ */ static void cancelreq_del(void *cr) { XFREE(MTYPE_TMP, cr); } /* initializer, only ever called once */ static void initializer(void) { pthread_key_create(&thread_current, NULL); } struct thread_master *thread_master_create(const char *name) { struct thread_master *rv; struct rlimit limit; pthread_once(&init_once, &initializer); rv = XCALLOC(MTYPE_THREAD_MASTER, sizeof(struct thread_master)); /* Initialize master mutex */ pthread_mutex_init(&rv->mtx, NULL); pthread_cond_init(&rv->cancel_cond, NULL); /* Set name */ rv->name = name ? XSTRDUP(MTYPE_THREAD_MASTER, name) : NULL; /* Initialize I/O task data structures */ getrlimit(RLIMIT_NOFILE, &limit); rv->fd_limit = (int)limit.rlim_cur; rv->read = XCALLOC(MTYPE_THREAD_POLL, sizeof(struct thread *) * rv->fd_limit); rv->write = XCALLOC(MTYPE_THREAD_POLL, sizeof(struct thread *) * rv->fd_limit); rv->cpu_record = hash_create_size( 8, (unsigned int (*)(const void *))cpu_record_hash_key, (bool (*)(const void *, const void *))cpu_record_hash_cmp, "Thread Hash"); thread_list_init(&rv->event); thread_list_init(&rv->ready); thread_list_init(&rv->unuse); thread_timer_list_init(&rv->timer); /* Initialize thread_fetch() settings */ rv->spin = true; rv->handle_signals = true; /* Set pthread owner, should be updated by actual owner */ rv->owner = pthread_self(); rv->cancel_req = list_new(); rv->cancel_req->del = cancelreq_del; rv->canceled = true; /* Initialize pipe poker */ pipe(rv->io_pipe); set_nonblocking(rv->io_pipe[0]); set_nonblocking(rv->io_pipe[1]); /* Initialize data structures for poll() */ rv->handler.pfdsize = rv->fd_limit; rv->handler.pfdcount = 0; rv->handler.pfds = XCALLOC(MTYPE_THREAD_MASTER, sizeof(struct pollfd) * rv->handler.pfdsize); rv->handler.copy = XCALLOC(MTYPE_THREAD_MASTER, sizeof(struct pollfd) * rv->handler.pfdsize); /* add to list of threadmasters */ frr_with_mutex(&masters_mtx) { if (!masters) masters = list_new(); listnode_add(masters, rv); } return rv; } void thread_master_set_name(struct thread_master *master, const char *name) { frr_with_mutex(&master->mtx) { XFREE(MTYPE_THREAD_MASTER, master->name); master->name = XSTRDUP(MTYPE_THREAD_MASTER, name); } } #define THREAD_UNUSED_DEPTH 10 /* Move thread to unuse list. */ static void thread_add_unuse(struct thread_master *m, struct thread *thread) { pthread_mutex_t mtxc = thread->mtx; assert(m != NULL && thread != NULL); thread->hist->total_active--; memset(thread, 0, sizeof(struct thread)); thread->type = THREAD_UNUSED; /* Restore the thread mutex context. */ thread->mtx = mtxc; if (thread_list_count(&m->unuse) < THREAD_UNUSED_DEPTH) { thread_list_add_tail(&m->unuse, thread); return; } thread_free(m, thread); } /* Free all unused thread. */ static void thread_list_free(struct thread_master *m, struct thread_list_head *list) { struct thread *t; while ((t = thread_list_pop(list))) thread_free(m, t); } static void thread_array_free(struct thread_master *m, struct thread **thread_array) { struct thread *t; int index; for (index = 0; index < m->fd_limit; ++index) { t = thread_array[index]; if (t) { thread_array[index] = NULL; thread_free(m, t); } } XFREE(MTYPE_THREAD_POLL, thread_array); } /* * thread_master_free_unused * * As threads are finished with they are put on the * unuse list for later reuse. * If we are shutting down, Free up unused threads * So we can see if we forget to shut anything off */ void thread_master_free_unused(struct thread_master *m) { frr_with_mutex(&m->mtx) { struct thread *t; while ((t = thread_list_pop(&m->unuse))) thread_free(m, t); } } /* Stop thread scheduler. */ void thread_master_free(struct thread_master *m) { struct thread *t; frr_with_mutex(&masters_mtx) { listnode_delete(masters, m); if (masters->count == 0) { list_delete(&masters); } } thread_array_free(m, m->read); thread_array_free(m, m->write); while ((t = thread_timer_list_pop(&m->timer))) thread_free(m, t); thread_list_free(m, &m->event); thread_list_free(m, &m->ready); thread_list_free(m, &m->unuse); pthread_mutex_destroy(&m->mtx); pthread_cond_destroy(&m->cancel_cond); close(m->io_pipe[0]); close(m->io_pipe[1]); list_delete(&m->cancel_req); m->cancel_req = NULL; hash_clean(m->cpu_record, cpu_record_hash_free); hash_free(m->cpu_record); m->cpu_record = NULL; XFREE(MTYPE_THREAD_MASTER, m->name); XFREE(MTYPE_THREAD_MASTER, m->handler.pfds); XFREE(MTYPE_THREAD_MASTER, m->handler.copy); XFREE(MTYPE_THREAD_MASTER, m); } /* Return remain time in miliseconds. */ unsigned long thread_timer_remain_msec(struct thread *thread) { int64_t remain; frr_with_mutex(&thread->mtx) { remain = monotime_until(&thread->u.sands, NULL) / 1000LL; } return remain < 0 ? 0 : remain; } /* Return remain time in seconds. */ unsigned long thread_timer_remain_second(struct thread *thread) { return thread_timer_remain_msec(thread) / 1000LL; } #define debugargdef const char *funcname, const char *schedfrom, int fromln #define debugargpass funcname, schedfrom, fromln struct timeval thread_timer_remain(struct thread *thread) { struct timeval remain; frr_with_mutex(&thread->mtx) { monotime_until(&thread->u.sands, &remain); } return remain; } /* Get new thread. */ static struct thread *thread_get(struct thread_master *m, uint8_t type, int (*func)(struct thread *), void *arg, debugargdef) { struct thread *thread = thread_list_pop(&m->unuse); struct cpu_thread_history tmp; if (!thread) { thread = XCALLOC(MTYPE_THREAD, sizeof(struct thread)); /* mutex only needs to be initialized at struct creation. */ pthread_mutex_init(&thread->mtx, NULL); m->alloc++; } thread->type = type; thread->add_type = type; thread->master = m; thread->arg = arg; thread->yield = THREAD_YIELD_TIME_SLOT; /* default */ thread->ref = NULL; /* * So if the passed in funcname is not what we have * stored that means the thread->hist needs to be * updated. We keep the last one around in unused * under the assumption that we are probably * going to immediately allocate the same * type of thread. * This hopefully saves us some serious * hash_get lookups. */ if (thread->funcname != funcname || thread->func != func) { tmp.func = func; tmp.funcname = funcname; thread->hist = hash_get(m->cpu_record, &tmp, (void *(*)(void *))cpu_record_hash_alloc); } thread->hist->total_active++; thread->func = func; thread->funcname = funcname; thread->schedfrom = schedfrom; thread->schedfrom_line = fromln; return thread; } static void thread_free(struct thread_master *master, struct thread *thread) { /* Update statistics. */ assert(master->alloc > 0); master->alloc--; /* Free allocated resources. */ pthread_mutex_destroy(&thread->mtx); XFREE(MTYPE_THREAD, thread); } static int fd_poll(struct thread_master *m, struct pollfd *pfds, nfds_t pfdsize, nfds_t count, const struct timeval *timer_wait) { /* If timer_wait is null here, that means poll() should block * indefinitely, * unless the thread_master has overridden it by setting * ->selectpoll_timeout. * If the value is positive, it specifies the maximum number of * milliseconds * to wait. If the timeout is -1, it specifies that we should never wait * and * always return immediately even if no event is detected. If the value * is * zero, the behavior is default. */ int timeout = -1; /* number of file descriptors with events */ int num; if (timer_wait != NULL && m->selectpoll_timeout == 0) // use the default value timeout = (timer_wait->tv_sec * 1000) + (timer_wait->tv_usec / 1000); else if (m->selectpoll_timeout > 0) // use the user's timeout timeout = m->selectpoll_timeout; else if (m->selectpoll_timeout < 0) // effect a poll (return immediately) timeout = 0; rcu_read_unlock(); rcu_assert_read_unlocked(); /* add poll pipe poker */ assert(count + 1 < pfdsize); pfds[count].fd = m->io_pipe[0]; pfds[count].events = POLLIN; pfds[count].revents = 0x00; num = poll(pfds, count + 1, timeout); unsigned char trash[64]; if (num > 0 && pfds[count].revents != 0 && num--) while (read(m->io_pipe[0], &trash, sizeof(trash)) > 0) ; rcu_read_lock(); return num; } /* Add new read thread. */ struct thread *funcname_thread_add_read_write(int dir, struct thread_master *m, int (*func)(struct thread *), void *arg, int fd, struct thread **t_ptr, debugargdef) { struct thread *thread = NULL; struct thread **thread_array; assert(fd >= 0 && fd < m->fd_limit); frr_with_mutex(&m->mtx) { if (t_ptr && *t_ptr) // thread is already scheduled; don't reschedule break; /* default to a new pollfd */ nfds_t queuepos = m->handler.pfdcount; if (dir == THREAD_READ) thread_array = m->read; else thread_array = m->write; /* if we already have a pollfd for our file descriptor, find and * use it */ for (nfds_t i = 0; i < m->handler.pfdcount; i++) if (m->handler.pfds[i].fd == fd) { queuepos = i; #ifdef DEV_BUILD /* * What happens if we have a thread already * created for this event? */ if (thread_array[fd]) assert(!"Thread already scheduled for file descriptor"); #endif break; } /* make sure we have room for this fd + pipe poker fd */ assert(queuepos + 1 < m->handler.pfdsize); thread = thread_get(m, dir, func, arg, debugargpass); m->handler.pfds[queuepos].fd = fd; m->handler.pfds[queuepos].events |= (dir == THREAD_READ ? POLLIN : POLLOUT); if (queuepos == m->handler.pfdcount) m->handler.pfdcount++; if (thread) { frr_with_mutex(&thread->mtx) { thread->u.fd = fd; thread_array[thread->u.fd] = thread; } if (t_ptr) { *t_ptr = thread; thread->ref = t_ptr; } } AWAKEN(m); } return thread; } static struct thread * funcname_thread_add_timer_timeval(struct thread_master *m, int (*func)(struct thread *), int type, void *arg, struct timeval *time_relative, struct thread **t_ptr, debugargdef) { struct thread *thread; assert(m != NULL); assert(type == THREAD_TIMER); assert(time_relative); frr_with_mutex(&m->mtx) { if (t_ptr && *t_ptr) // thread is already scheduled; don't reschedule return NULL; thread = thread_get(m, type, func, arg, debugargpass); frr_with_mutex(&thread->mtx) { monotime(&thread->u.sands); timeradd(&thread->u.sands, time_relative, &thread->u.sands); thread_timer_list_add(&m->timer, thread); if (t_ptr) { *t_ptr = thread; thread->ref = t_ptr; } } AWAKEN(m); } return thread; } /* Add timer event thread. */ struct thread *funcname_thread_add_timer(struct thread_master *m, int (*func)(struct thread *), void *arg, long timer, struct thread **t_ptr, debugargdef) { struct timeval trel; assert(m != NULL); trel.tv_sec = timer; trel.tv_usec = 0; return funcname_thread_add_timer_timeval(m, func, THREAD_TIMER, arg, &trel, t_ptr, debugargpass); } /* Add timer event thread with "millisecond" resolution */ struct thread *funcname_thread_add_timer_msec(struct thread_master *m, int (*func)(struct thread *), void *arg, long timer, struct thread **t_ptr, debugargdef) { struct timeval trel; assert(m != NULL); trel.tv_sec = timer / 1000; trel.tv_usec = 1000 * (timer % 1000); return funcname_thread_add_timer_timeval(m, func, THREAD_TIMER, arg, &trel, t_ptr, debugargpass); } /* Add timer event thread with "millisecond" resolution */ struct thread *funcname_thread_add_timer_tv(struct thread_master *m, int (*func)(struct thread *), void *arg, struct timeval *tv, struct thread **t_ptr, debugargdef) { return funcname_thread_add_timer_timeval(m, func, THREAD_TIMER, arg, tv, t_ptr, debugargpass); } /* Add simple event thread. */ struct thread *funcname_thread_add_event(struct thread_master *m, int (*func)(struct thread *), void *arg, int val, struct thread **t_ptr, debugargdef) { struct thread *thread = NULL; assert(m != NULL); frr_with_mutex(&m->mtx) { if (t_ptr && *t_ptr) // thread is already scheduled; don't reschedule break; thread = thread_get(m, THREAD_EVENT, func, arg, debugargpass); frr_with_mutex(&thread->mtx) { thread->u.val = val; thread_list_add_tail(&m->event, thread); } if (t_ptr) { *t_ptr = thread; thread->ref = t_ptr; } AWAKEN(m); } return thread; } /* Thread cancellation ------------------------------------------------------ */ /** * NOT's out the .events field of pollfd corresponding to the given file * descriptor. The event to be NOT'd is passed in the 'state' parameter. * * This needs to happen for both copies of pollfd's. See 'thread_fetch' * implementation for details. * * @param master * @param fd * @param state the event to cancel. One or more (OR'd together) of the * following: * - POLLIN * - POLLOUT */ static void thread_cancel_rw(struct thread_master *master, int fd, short state) { bool found = false; /* Cancel POLLHUP too just in case some bozo set it */ state |= POLLHUP; /* find the index of corresponding pollfd */ nfds_t i; for (i = 0; i < master->handler.pfdcount; i++) if (master->handler.pfds[i].fd == fd) { found = true; break; } if (!found) { zlog_debug( "[!] Received cancellation request for nonexistent rw job"); zlog_debug("[!] threadmaster: %s | fd: %d", master->name ? master->name : "", fd); return; } /* NOT out event. */ master->handler.pfds[i].events &= ~(state); /* If all events are canceled, delete / resize the pollfd array. */ if (master->handler.pfds[i].events == 0) { memmove(master->handler.pfds + i, master->handler.pfds + i + 1, (master->handler.pfdcount - i - 1) * sizeof(struct pollfd)); master->handler.pfdcount--; } /* If we have the same pollfd in the copy, perform the same operations, * otherwise return. */ if (i >= master->handler.copycount) return; master->handler.copy[i].events &= ~(state); if (master->handler.copy[i].events == 0) { memmove(master->handler.copy + i, master->handler.copy + i + 1, (master->handler.copycount - i - 1) * sizeof(struct pollfd)); master->handler.copycount--; } } /** * Process cancellation requests. * * This may only be run from the pthread which owns the thread_master. * * @param master the thread master to process * @REQUIRE master->mtx */ static void do_thread_cancel(struct thread_master *master) { struct thread_list_head *list = NULL; struct thread **thread_array = NULL; struct thread *thread; struct cancel_req *cr; struct listnode *ln; for (ALL_LIST_ELEMENTS_RO(master->cancel_req, ln, cr)) { /* If this is an event object cancellation, linear search * through event * list deleting any events which have the specified argument. * We also * need to check every thread in the ready queue. */ if (cr->eventobj) { struct thread *t; frr_each_safe(thread_list, &master->event, t) { if (t->arg != cr->eventobj) continue; thread_list_del(&master->event, t); if (t->ref) *t->ref = NULL; thread_add_unuse(master, t); } frr_each_safe(thread_list, &master->ready, t) { if (t->arg != cr->eventobj) continue; thread_list_del(&master->ready, t); if (t->ref) *t->ref = NULL; thread_add_unuse(master, t); } continue; } /* The pointer varies depending on whether the cancellation * request was * made asynchronously or not. If it was, we need to check * whether the * thread even exists anymore before cancelling it. */ thread = (cr->thread) ? cr->thread : *cr->threadref; if (!thread) continue; /* Determine the appropriate queue to cancel the thread from */ switch (thread->type) { case THREAD_READ: thread_cancel_rw(master, thread->u.fd, POLLIN); thread_array = master->read; break; case THREAD_WRITE: thread_cancel_rw(master, thread->u.fd, POLLOUT); thread_array = master->write; break; case THREAD_TIMER: thread_timer_list_del(&master->timer, thread); break; case THREAD_EVENT: list = &master->event; break; case THREAD_READY: list = &master->ready; break; default: continue; break; } if (list) { thread_list_del(list, thread); } else if (thread_array) { thread_array[thread->u.fd] = NULL; } if (thread->ref) *thread->ref = NULL; thread_add_unuse(thread->master, thread); } /* Delete and free all cancellation requests */ list_delete_all_node(master->cancel_req); /* Wake up any threads which may be blocked in thread_cancel_async() */ master->canceled = true; pthread_cond_broadcast(&master->cancel_cond); } /** * Cancel any events which have the specified argument. * * MT-Unsafe * * @param m the thread_master to cancel from * @param arg the argument passed when creating the event */ void thread_cancel_event(struct thread_master *master, void *arg) { assert(master->owner == pthread_self()); frr_with_mutex(&master->mtx) { struct cancel_req *cr = XCALLOC(MTYPE_TMP, sizeof(struct cancel_req)); cr->eventobj = arg; listnode_add(master->cancel_req, cr); do_thread_cancel(master); } } /** * Cancel a specific task. * * MT-Unsafe * * @param thread task to cancel */ void thread_cancel(struct thread *thread) { struct thread_master *master = thread->master; assert(master->owner == pthread_self()); frr_with_mutex(&master->mtx) { struct cancel_req *cr = XCALLOC(MTYPE_TMP, sizeof(struct cancel_req)); cr->thread = thread; listnode_add(master->cancel_req, cr); do_thread_cancel(master); } } /** * Asynchronous cancellation. * * Called with either a struct thread ** or void * to an event argument, * this function posts the correct cancellation request and blocks until it is * serviced. * * If the thread is currently running, execution blocks until it completes. * * The last two parameters are mutually exclusive, i.e. if you pass one the * other must be NULL. * * When the cancellation procedure executes on the target thread_master, the * thread * provided is checked for nullity. If it is null, the thread is * assumed to no longer exist and the cancellation request is a no-op. Thus * users of this API must pass a back-reference when scheduling the original * task. * * MT-Safe * * @param master the thread master with the relevant event / task * @param thread pointer to thread to cancel * @param eventobj the event */ void thread_cancel_async(struct thread_master *master, struct thread **thread, void *eventobj) { assert(!(thread && eventobj) && (thread || eventobj)); assert(master->owner != pthread_self()); frr_with_mutex(&master->mtx) { master->canceled = false; if (thread) { struct cancel_req *cr = XCALLOC(MTYPE_TMP, sizeof(struct cancel_req)); cr->threadref = thread; listnode_add(master->cancel_req, cr); } else if (eventobj) { struct cancel_req *cr = XCALLOC(MTYPE_TMP, sizeof(struct cancel_req)); cr->eventobj = eventobj; listnode_add(master->cancel_req, cr); } AWAKEN(master); while (!master->canceled) pthread_cond_wait(&master->cancel_cond, &master->mtx); } } /* ------------------------------------------------------------------------- */ static struct timeval *thread_timer_wait(struct thread_timer_list_head *timers, struct timeval *timer_val) { if (!thread_timer_list_count(timers)) return NULL; struct thread *next_timer = thread_timer_list_first(timers); monotime_until(&next_timer->u.sands, timer_val); return timer_val; } static struct thread *thread_run(struct thread_master *m, struct thread *thread, struct thread *fetch) { *fetch = *thread; thread_add_unuse(m, thread); return fetch; } static int thread_process_io_helper(struct thread_master *m, struct thread *thread, short state, short actual_state, int pos) { struct thread **thread_array; /* * poll() clears the .events field, but the pollfd array we * pass to poll() is a copy of the one used to schedule threads. * We need to synchronize state between the two here by applying * the same changes poll() made on the copy of the "real" pollfd * array. * * This cleans up a possible infinite loop where we refuse * to respond to a poll event but poll is insistent that * we should. */ m->handler.pfds[pos].events &= ~(state); if (!thread) { if ((actual_state & (POLLHUP|POLLIN)) != POLLHUP) flog_err(EC_LIB_NO_THREAD, "Attempting to process an I/O event but for fd: %d(%d) no thread to handle this!\n", m->handler.pfds[pos].fd, actual_state); return 0; } if (thread->type == THREAD_READ) thread_array = m->read; else thread_array = m->write; thread_array[thread->u.fd] = NULL; thread_list_add_tail(&m->ready, thread); thread->type = THREAD_READY; return 1; } /** * Process I/O events. * * Walks through file descriptor array looking for those pollfds whose .revents * field has something interesting. Deletes any invalid file descriptors. * * @param m the thread master * @param num the number of active file descriptors (return value of poll()) */ static void thread_process_io(struct thread_master *m, unsigned int num) { unsigned int ready = 0; struct pollfd *pfds = m->handler.copy; for (nfds_t i = 0; i < m->handler.copycount && ready < num; ++i) { /* no event for current fd? immediately continue */ if (pfds[i].revents == 0) continue; ready++; /* Unless someone has called thread_cancel from another pthread, * the only * thing that could have changed in m->handler.pfds while we * were * asleep is the .events field in a given pollfd. Barring * thread_cancel() * that value should be a superset of the values we have in our * copy, so * there's no need to update it. Similarily, barring deletion, * the fd * should still be a valid index into the master's pfds. */ if (pfds[i].revents & (POLLIN | POLLHUP)) { thread_process_io_helper(m, m->read[pfds[i].fd], POLLIN, pfds[i].revents, i); } if (pfds[i].revents & POLLOUT) thread_process_io_helper(m, m->write[pfds[i].fd], POLLOUT, pfds[i].revents, i); /* if one of our file descriptors is garbage, remove the same * from * both pfds + update sizes and index */ if (pfds[i].revents & POLLNVAL) { memmove(m->handler.pfds + i, m->handler.pfds + i + 1, (m->handler.pfdcount - i - 1) * sizeof(struct pollfd)); m->handler.pfdcount--; memmove(pfds + i, pfds + i + 1, (m->handler.copycount - i - 1) * sizeof(struct pollfd)); m->handler.copycount--; i--; } } } /* Add all timers that have popped to the ready list. */ static unsigned int thread_process_timers(struct thread_timer_list_head *timers, struct timeval *timenow) { struct thread *thread; unsigned int ready = 0; while ((thread = thread_timer_list_first(timers))) { if (timercmp(timenow, &thread->u.sands, <)) return ready; thread_timer_list_pop(timers); thread->type = THREAD_READY; thread_list_add_tail(&thread->master->ready, thread); ready++; } return ready; } /* process a list en masse, e.g. for event thread lists */ static unsigned int thread_process(struct thread_list_head *list) { struct thread *thread; unsigned int ready = 0; while ((thread = thread_list_pop(list))) { thread->type = THREAD_READY; thread_list_add_tail(&thread->master->ready, thread); ready++; } return ready; } /* Fetch next ready thread. */ struct thread *thread_fetch(struct thread_master *m, struct thread *fetch) { struct thread *thread = NULL; struct timeval now; struct timeval zerotime = {0, 0}; struct timeval tv; struct timeval *tw = NULL; int num = 0; do { /* Handle signals if any */ if (m->handle_signals) quagga_sigevent_process(); pthread_mutex_lock(&m->mtx); /* Process any pending cancellation requests */ do_thread_cancel(m); /* * Attempt to flush ready queue before going into poll(). * This is performance-critical. Think twice before modifying. */ if ((thread = thread_list_pop(&m->ready))) { fetch = thread_run(m, thread, fetch); if (fetch->ref) *fetch->ref = NULL; pthread_mutex_unlock(&m->mtx); break; } /* otherwise, tick through scheduling sequence */ /* * Post events to ready queue. This must come before the * following block since events should occur immediately */ thread_process(&m->event); /* * If there are no tasks on the ready queue, we will poll() * until a timer expires or we receive I/O, whichever comes * first. The strategy for doing this is: * * - If there are events pending, set the poll() timeout to zero * - If there are no events pending, but there are timers * pending, set the * timeout to the smallest remaining time on any timer * - If there are neither timers nor events pending, but there * are file * descriptors pending, block indefinitely in poll() * - If nothing is pending, it's time for the application to die * * In every case except the last, we need to hit poll() at least * once per loop to avoid starvation by events */ if (!thread_list_count(&m->ready)) tw = thread_timer_wait(&m->timer, &tv); if (thread_list_count(&m->ready) || (tw && !timercmp(tw, &zerotime, >))) tw = &zerotime; if (!tw && m->handler.pfdcount == 0) { /* die */ pthread_mutex_unlock(&m->mtx); fetch = NULL; break; } /* * Copy pollfd array + # active pollfds in it. Not necessary to * copy the array size as this is fixed. */ m->handler.copycount = m->handler.pfdcount; memcpy(m->handler.copy, m->handler.pfds, m->handler.copycount * sizeof(struct pollfd)); pthread_mutex_unlock(&m->mtx); { num = fd_poll(m, m->handler.copy, m->handler.pfdsize, m->handler.copycount, tw); } pthread_mutex_lock(&m->mtx); /* Handle any errors received in poll() */ if (num < 0) { if (errno == EINTR) { pthread_mutex_unlock(&m->mtx); /* loop around to signal handler */ continue; } /* else die */ flog_err(EC_LIB_SYSTEM_CALL, "poll() error: %s", safe_strerror(errno)); pthread_mutex_unlock(&m->mtx); fetch = NULL; break; } /* Post timers to ready queue. */ monotime(&now); thread_process_timers(&m->timer, &now); /* Post I/O to ready queue. */ if (num > 0) thread_process_io(m, num); pthread_mutex_unlock(&m->mtx); } while (!thread && m->spin); return fetch; } static unsigned long timeval_elapsed(struct timeval a, struct timeval b) { return (((a.tv_sec - b.tv_sec) * TIMER_SECOND_MICRO) + (a.tv_usec - b.tv_usec)); } unsigned long thread_consumed_time(RUSAGE_T *now, RUSAGE_T *start, unsigned long *cputime) { /* This is 'user + sys' time. */ *cputime = timeval_elapsed(now->cpu.ru_utime, start->cpu.ru_utime) + timeval_elapsed(now->cpu.ru_stime, start->cpu.ru_stime); return timeval_elapsed(now->real, start->real); } /* We should aim to yield after yield milliseconds, which defaults to THREAD_YIELD_TIME_SLOT . Note: we are using real (wall clock) time for this calculation. It could be argued that CPU time may make more sense in certain contexts. The things to consider are whether the thread may have blocked (in which case wall time increases, but CPU time does not), or whether the system is heavily loaded with other processes competing for CPU time. On balance, wall clock time seems to make sense. Plus it has the added benefit that gettimeofday should be faster than calling getrusage. */ int thread_should_yield(struct thread *thread) { int result; frr_with_mutex(&thread->mtx) { result = monotime_since(&thread->real, NULL) > (int64_t)thread->yield; } return result; } void thread_set_yield_time(struct thread *thread, unsigned long yield_time) { frr_with_mutex(&thread->mtx) { thread->yield = yield_time; } } void thread_getrusage(RUSAGE_T *r) { #if defined RUSAGE_THREAD #define FRR_RUSAGE RUSAGE_THREAD #else #define FRR_RUSAGE RUSAGE_SELF #endif monotime(&r->real); getrusage(FRR_RUSAGE, &(r->cpu)); } /* * Call a thread. * * This function will atomically update the thread's usage history. At present * this is the only spot where usage history is written. Nevertheless the code * has been written such that the introduction of writers in the future should * not need to update it provided the writers atomically perform only the * operations done here, i.e. updating the total and maximum times. In * particular, the maximum real and cpu times must be monotonically increasing * or this code is not correct. */ void thread_call(struct thread *thread) { _Atomic unsigned long realtime, cputime; unsigned long exp; unsigned long helper; RUSAGE_T before, after; GETRUSAGE(&before); thread->real = before.real; pthread_setspecific(thread_current, thread); (*thread->func)(thread); pthread_setspecific(thread_current, NULL); GETRUSAGE(&after); realtime = thread_consumed_time(&after, &before, &helper); cputime = helper; /* update realtime */ atomic_fetch_add_explicit(&thread->hist->real.total, realtime, memory_order_seq_cst); exp = atomic_load_explicit(&thread->hist->real.max, memory_order_seq_cst); while (exp < realtime && !atomic_compare_exchange_weak_explicit( &thread->hist->real.max, &exp, realtime, memory_order_seq_cst, memory_order_seq_cst)) ; /* update cputime */ atomic_fetch_add_explicit(&thread->hist->cpu.total, cputime, memory_order_seq_cst); exp = atomic_load_explicit(&thread->hist->cpu.max, memory_order_seq_cst); while (exp < cputime && !atomic_compare_exchange_weak_explicit( &thread->hist->cpu.max, &exp, cputime, memory_order_seq_cst, memory_order_seq_cst)) ; atomic_fetch_add_explicit(&thread->hist->total_calls, 1, memory_order_seq_cst); atomic_fetch_or_explicit(&thread->hist->types, 1 << thread->add_type, memory_order_seq_cst); #ifdef CONSUMED_TIME_CHECK if (realtime > CONSUMED_TIME_CHECK) { /* * We have a CPU Hog on our hands. * Whinge about it now, so we're aware this is yet another task * to fix. */ flog_warn( EC_LIB_SLOW_THREAD, "SLOW THREAD: task %s (%lx) ran for %lums (cpu time %lums)", thread->funcname, (unsigned long)thread->func, realtime / 1000, cputime / 1000); } #endif /* CONSUMED_TIME_CHECK */ } /* Execute thread */ void funcname_thread_execute(struct thread_master *m, int (*func)(struct thread *), void *arg, int val, debugargdef) { struct thread *thread; /* Get or allocate new thread to execute. */ frr_with_mutex(&m->mtx) { thread = thread_get(m, THREAD_EVENT, func, arg, debugargpass); /* Set its event value. */ frr_with_mutex(&thread->mtx) { thread->add_type = THREAD_EXECUTE; thread->u.val = val; thread->ref = &thread; } } /* Execute thread doing all accounting. */ thread_call(thread); /* Give back or free thread. */ thread_add_unuse(m, thread); } frr-7.2.1/lib/thread.h0000644000000000000000000001726013610377563011410 00000000000000/* Thread management routine header. * Copyright (C) 1998 Kunihiro Ishiguro * * This file is part of GNU Zebra. * * GNU Zebra is free software; you can redistribute it and/or modify it * under the terms of the GNU General Public License as published by the * Free Software Foundation; either version 2, or (at your option) any * later version. * * GNU Zebra 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; see the file COPYING; if not, write to the Free Software * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA */ #ifndef _ZEBRA_THREAD_H #define _ZEBRA_THREAD_H #include #include #include #include "monotime.h" #include "frratomic.h" #include "typesafe.h" #ifdef __cplusplus extern "C" { #endif struct rusage_t { struct rusage cpu; struct timeval real; }; #define RUSAGE_T struct rusage_t #define GETRUSAGE(X) thread_getrusage(X) PREDECL_LIST(thread_list) PREDECL_HEAP(thread_timer_list) struct fd_handler { /* number of pfd that fit in the allocated space of pfds. This is a * constant * and is the same for both pfds and copy. */ nfds_t pfdsize; /* file descriptors to monitor for i/o */ struct pollfd *pfds; /* number of pollfds stored in pfds */ nfds_t pfdcount; /* chunk used for temp copy of pollfds */ struct pollfd *copy; /* number of pollfds stored in copy */ nfds_t copycount; }; struct cancel_req { struct thread *thread; void *eventobj; struct thread **threadref; }; /* Master of the theads. */ struct thread_master { char *name; struct thread **read; struct thread **write; struct thread_timer_list_head timer; struct thread_list_head event, ready, unuse; struct list *cancel_req; bool canceled; pthread_cond_t cancel_cond; struct hash *cpu_record; int io_pipe[2]; int fd_limit; struct fd_handler handler; unsigned long alloc; long selectpoll_timeout; bool spin; bool handle_signals; pthread_mutex_t mtx; pthread_t owner; }; /* Thread itself. */ struct thread { uint8_t type; /* thread type */ uint8_t add_type; /* thread type */ struct thread_list_item threaditem; struct thread_timer_list_item timeritem; struct thread **ref; /* external reference (if given) */ struct thread_master *master; /* pointer to the struct thread_master */ int (*func)(struct thread *); /* event function */ void *arg; /* event argument */ union { int val; /* second argument of the event. */ int fd; /* file descriptor in case of r/w */ struct timeval sands; /* rest of time sands value. */ } u; struct timeval real; struct cpu_thread_history *hist; /* cache pointer to cpu_history */ unsigned long yield; /* yield time in microseconds */ const char *funcname; /* name of thread function */ const char *schedfrom; /* source file thread was scheduled from */ int schedfrom_line; /* line number of source file */ pthread_mutex_t mtx; /* mutex for thread.c functions */ }; struct cpu_thread_history { int (*func)(struct thread *); atomic_uint_fast32_t total_calls; atomic_uint_fast32_t total_active; struct time_stats { atomic_size_t total, max; } real; struct time_stats cpu; atomic_uint_fast32_t types; const char *funcname; }; /* Struct timeval's tv_usec one second value. */ #define TIMER_SECOND_MICRO 1000000L /* Thread types. */ #define THREAD_READ 0 #define THREAD_WRITE 1 #define THREAD_TIMER 2 #define THREAD_EVENT 3 #define THREAD_READY 4 #define THREAD_UNUSED 5 #define THREAD_EXECUTE 6 /* Thread yield time. */ #define THREAD_YIELD_TIME_SLOT 10 * 1000L /* 10ms */ /* Macros. */ #define THREAD_ARG(X) ((X)->arg) #define THREAD_FD(X) ((X)->u.fd) #define THREAD_VAL(X) ((X)->u.val) #define THREAD_OFF(thread) \ do { \ if (thread) { \ thread_cancel(thread); \ thread = NULL; \ } \ } while (0) #define THREAD_READ_OFF(thread) THREAD_OFF(thread) #define THREAD_WRITE_OFF(thread) THREAD_OFF(thread) #define THREAD_TIMER_OFF(thread) THREAD_OFF(thread) #define debugargdef const char *funcname, const char *schedfrom, int fromln #define thread_add_read(m,f,a,v,t) funcname_thread_add_read_write(THREAD_READ,m,f,a,v,t,#f,__FILE__,__LINE__) #define thread_add_write(m,f,a,v,t) funcname_thread_add_read_write(THREAD_WRITE,m,f,a,v,t,#f,__FILE__,__LINE__) #define thread_add_timer(m,f,a,v,t) funcname_thread_add_timer(m,f,a,v,t,#f,__FILE__,__LINE__) #define thread_add_timer_msec(m,f,a,v,t) funcname_thread_add_timer_msec(m,f,a,v,t,#f,__FILE__,__LINE__) #define thread_add_timer_tv(m,f,a,v,t) funcname_thread_add_timer_tv(m,f,a,v,t,#f,__FILE__,__LINE__) #define thread_add_event(m,f,a,v,t) funcname_thread_add_event(m,f,a,v,t,#f,__FILE__,__LINE__) #define thread_execute(m,f,a,v) funcname_thread_execute(m,f,a,v,#f,__FILE__,__LINE__) #define thread_execute_name(m, f, a, v, n) \ funcname_thread_execute(m, f, a, v, n, __FILE__, __LINE__) /* Prototypes. */ extern struct thread_master *thread_master_create(const char *); void thread_master_set_name(struct thread_master *master, const char *name); extern void thread_master_free(struct thread_master *); extern void thread_master_free_unused(struct thread_master *); extern struct thread * funcname_thread_add_read_write(int dir, struct thread_master *, int (*)(struct thread *), void *, int, struct thread **, debugargdef); extern struct thread *funcname_thread_add_timer(struct thread_master *, int (*)(struct thread *), void *, long, struct thread **, debugargdef); extern struct thread * funcname_thread_add_timer_msec(struct thread_master *, int (*)(struct thread *), void *, long, struct thread **, debugargdef); extern struct thread *funcname_thread_add_timer_tv(struct thread_master *, int (*)(struct thread *), void *, struct timeval *, struct thread **, debugargdef); extern struct thread *funcname_thread_add_event(struct thread_master *, int (*)(struct thread *), void *, int, struct thread **, debugargdef); extern void funcname_thread_execute(struct thread_master *, int (*)(struct thread *), void *, int, debugargdef); #undef debugargdef extern void thread_cancel(struct thread *); extern void thread_cancel_async(struct thread_master *, struct thread **, void *); extern void thread_cancel_event(struct thread_master *, void *); extern struct thread *thread_fetch(struct thread_master *, struct thread *); extern void thread_call(struct thread *); extern unsigned long thread_timer_remain_second(struct thread *); extern struct timeval thread_timer_remain(struct thread *); extern unsigned long thread_timer_remain_msec(struct thread *); extern int thread_should_yield(struct thread *); /* set yield time for thread */ extern void thread_set_yield_time(struct thread *, unsigned long); /* Internal libfrr exports */ extern void thread_getrusage(RUSAGE_T *); extern void thread_cmd_init(void); /* Returns elapsed real (wall clock) time. */ extern unsigned long thread_consumed_time(RUSAGE_T *after, RUSAGE_T *before, unsigned long *cpu_time_elapsed); /* only for use in logging functions! */ extern pthread_key_t thread_current; #ifdef __cplusplus } #endif #endif /* _ZEBRA_THREAD_H */ frr-7.2.1/lib/typerb.c0000644000000000000000000002603613610377563011442 00000000000000/* RB-tree */ /* * Copyright 2002 Niels Provos * All rights reserved. * * Redistribution and use in source and binary forms, with or without * modification, are permitted provided that the following conditions * are met: * 1. Redistributions of source code must retain the above copyright * notice, this list of conditions and the following disclaimer. * 2. Redistributions in binary form must reproduce the above copyright * notice, this list of conditions and the following disclaimer in the * documentation and/or other materials provided with the distribution. * * THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR * IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES * OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. * IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT, * INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT * NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF * THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. */ /* * Copyright (c) 2016 David Gwynne * * Permission to use, copy, modify, and distribute this software for any * purpose with or without fee is hereby granted, provided that the above * copyright notice and this permission notice appear in all copies. * * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. */ #ifdef HAVE_CONFIG_H #include "config.h" #endif #include "typerb.h" #define RB_BLACK 0 #define RB_RED 1 #define rb_entry typed_rb_entry #define rbt_tree typed_rb_root #define RBE_LEFT(_rbe) (_rbe)->rbt_left #define RBE_RIGHT(_rbe) (_rbe)->rbt_right #define RBE_PARENT(_rbe) (_rbe)->rbt_parent #define RBE_COLOR(_rbe) (_rbe)->rbt_color #define RBH_ROOT(_rbt) (_rbt)->rbt_root static inline void rbe_set(struct rb_entry *rbe, struct rb_entry *parent) { RBE_PARENT(rbe) = parent; RBE_LEFT(rbe) = RBE_RIGHT(rbe) = NULL; RBE_COLOR(rbe) = RB_RED; } static inline void rbe_set_blackred(struct rb_entry *black, struct rb_entry *red) { RBE_COLOR(black) = RB_BLACK; RBE_COLOR(red) = RB_RED; } static inline void rbe_rotate_left(struct rbt_tree *rbt, struct rb_entry *rbe) { struct rb_entry *parent; struct rb_entry *tmp; tmp = RBE_RIGHT(rbe); RBE_RIGHT(rbe) = RBE_LEFT(tmp); if (RBE_RIGHT(rbe) != NULL) RBE_PARENT(RBE_LEFT(tmp)) = rbe; parent = RBE_PARENT(rbe); RBE_PARENT(tmp) = parent; if (parent != NULL) { if (rbe == RBE_LEFT(parent)) RBE_LEFT(parent) = tmp; else RBE_RIGHT(parent) = tmp; } else RBH_ROOT(rbt) = tmp; RBE_LEFT(tmp) = rbe; RBE_PARENT(rbe) = tmp; } static inline void rbe_rotate_right(struct rbt_tree *rbt, struct rb_entry *rbe) { struct rb_entry *parent; struct rb_entry *tmp; tmp = RBE_LEFT(rbe); RBE_LEFT(rbe) = RBE_RIGHT(tmp); if (RBE_LEFT(rbe) != NULL) RBE_PARENT(RBE_RIGHT(tmp)) = rbe; parent = RBE_PARENT(rbe); RBE_PARENT(tmp) = parent; if (parent != NULL) { if (rbe == RBE_LEFT(parent)) RBE_LEFT(parent) = tmp; else RBE_RIGHT(parent) = tmp; } else RBH_ROOT(rbt) = tmp; RBE_RIGHT(tmp) = rbe; RBE_PARENT(rbe) = tmp; } static inline void rbe_insert_color(struct rbt_tree *rbt, struct rb_entry *rbe) { struct rb_entry *parent, *gparent, *tmp; rbt->count++; while ((parent = RBE_PARENT(rbe)) != NULL && RBE_COLOR(parent) == RB_RED) { gparent = RBE_PARENT(parent); if (parent == RBE_LEFT(gparent)) { tmp = RBE_RIGHT(gparent); if (tmp != NULL && RBE_COLOR(tmp) == RB_RED) { RBE_COLOR(tmp) = RB_BLACK; rbe_set_blackred(parent, gparent); rbe = gparent; continue; } if (RBE_RIGHT(parent) == rbe) { rbe_rotate_left(rbt, parent); tmp = parent; parent = rbe; rbe = tmp; } rbe_set_blackred(parent, gparent); rbe_rotate_right(rbt, gparent); } else { tmp = RBE_LEFT(gparent); if (tmp != NULL && RBE_COLOR(tmp) == RB_RED) { RBE_COLOR(tmp) = RB_BLACK; rbe_set_blackred(parent, gparent); rbe = gparent; continue; } if (RBE_LEFT(parent) == rbe) { rbe_rotate_right(rbt, parent); tmp = parent; parent = rbe; rbe = tmp; } rbe_set_blackred(parent, gparent); rbe_rotate_left(rbt, gparent); } } RBE_COLOR(RBH_ROOT(rbt)) = RB_BLACK; } static inline void rbe_remove_color(struct rbt_tree *rbt, struct rb_entry *parent, struct rb_entry *rbe) { struct rb_entry *tmp; while ((rbe == NULL || RBE_COLOR(rbe) == RB_BLACK) && rbe != RBH_ROOT(rbt) && parent) { if (RBE_LEFT(parent) == rbe) { tmp = RBE_RIGHT(parent); if (RBE_COLOR(tmp) == RB_RED) { rbe_set_blackred(tmp, parent); rbe_rotate_left(rbt, parent); tmp = RBE_RIGHT(parent); } if ((RBE_LEFT(tmp) == NULL || RBE_COLOR(RBE_LEFT(tmp)) == RB_BLACK) && (RBE_RIGHT(tmp) == NULL || RBE_COLOR(RBE_RIGHT(tmp)) == RB_BLACK)) { RBE_COLOR(tmp) = RB_RED; rbe = parent; parent = RBE_PARENT(rbe); } else { if (RBE_RIGHT(tmp) == NULL || RBE_COLOR(RBE_RIGHT(tmp)) == RB_BLACK) { struct rb_entry *oleft; oleft = RBE_LEFT(tmp); if (oleft != NULL) RBE_COLOR(oleft) = RB_BLACK; RBE_COLOR(tmp) = RB_RED; rbe_rotate_right(rbt, tmp); tmp = RBE_RIGHT(parent); } RBE_COLOR(tmp) = RBE_COLOR(parent); RBE_COLOR(parent) = RB_BLACK; if (RBE_RIGHT(tmp)) RBE_COLOR(RBE_RIGHT(tmp)) = RB_BLACK; rbe_rotate_left(rbt, parent); rbe = RBH_ROOT(rbt); break; } } else { tmp = RBE_LEFT(parent); if (RBE_COLOR(tmp) == RB_RED) { rbe_set_blackred(tmp, parent); rbe_rotate_right(rbt, parent); tmp = RBE_LEFT(parent); } if ((RBE_LEFT(tmp) == NULL || RBE_COLOR(RBE_LEFT(tmp)) == RB_BLACK) && (RBE_RIGHT(tmp) == NULL || RBE_COLOR(RBE_RIGHT(tmp)) == RB_BLACK)) { RBE_COLOR(tmp) = RB_RED; rbe = parent; parent = RBE_PARENT(rbe); } else { if (RBE_LEFT(tmp) == NULL || RBE_COLOR(RBE_LEFT(tmp)) == RB_BLACK) { struct rb_entry *oright; oright = RBE_RIGHT(tmp); if (oright != NULL) RBE_COLOR(oright) = RB_BLACK; RBE_COLOR(tmp) = RB_RED; rbe_rotate_left(rbt, tmp); tmp = RBE_LEFT(parent); } RBE_COLOR(tmp) = RBE_COLOR(parent); RBE_COLOR(parent) = RB_BLACK; if (RBE_LEFT(tmp) != NULL) RBE_COLOR(RBE_LEFT(tmp)) = RB_BLACK; rbe_rotate_right(rbt, parent); rbe = RBH_ROOT(rbt); break; } } } if (rbe != NULL) RBE_COLOR(rbe) = RB_BLACK; } static inline struct rb_entry * rbe_remove(struct rbt_tree *rbt, struct rb_entry *rbe) { struct rb_entry *child, *parent, *old = rbe; unsigned int color; if (RBE_LEFT(rbe) == NULL) child = RBE_RIGHT(rbe); else if (RBE_RIGHT(rbe) == NULL) child = RBE_LEFT(rbe); else { struct rb_entry *tmp; rbe = RBE_RIGHT(rbe); while ((tmp = RBE_LEFT(rbe)) != NULL) rbe = tmp; child = RBE_RIGHT(rbe); parent = RBE_PARENT(rbe); color = RBE_COLOR(rbe); if (child != NULL) RBE_PARENT(child) = parent; if (parent != NULL) { if (RBE_LEFT(parent) == rbe) RBE_LEFT(parent) = child; else RBE_RIGHT(parent) = child; } else RBH_ROOT(rbt) = child; if (RBE_PARENT(rbe) == old) parent = rbe; *rbe = *old; tmp = RBE_PARENT(old); if (tmp != NULL) { if (RBE_LEFT(tmp) == old) RBE_LEFT(tmp) = rbe; else RBE_RIGHT(tmp) = rbe; } else RBH_ROOT(rbt) = rbe; RBE_PARENT(RBE_LEFT(old)) = rbe; if (RBE_RIGHT(old)) RBE_PARENT(RBE_RIGHT(old)) = rbe; goto color; } parent = RBE_PARENT(rbe); color = RBE_COLOR(rbe); if (child != NULL) RBE_PARENT(child) = parent; if (parent != NULL) { if (RBE_LEFT(parent) == rbe) RBE_LEFT(parent) = child; else RBE_RIGHT(parent) = child; } else RBH_ROOT(rbt) = child; color: if (color == RB_BLACK) rbe_remove_color(rbt, parent, child); rbt->count--; return (old); } struct typed_rb_entry *typed_rb_remove(struct rbt_tree *rbt, struct rb_entry *rbe) { return rbe_remove(rbt, rbe); } struct typed_rb_entry *typed_rb_insert(struct rbt_tree *rbt, struct rb_entry *rbe, int (*cmpfn)( const struct typed_rb_entry *a, const struct typed_rb_entry *b)) { struct rb_entry *tmp; struct rb_entry *parent = NULL; int comp = 0; tmp = RBH_ROOT(rbt); while (tmp != NULL) { parent = tmp; comp = cmpfn(rbe, tmp); if (comp < 0) tmp = RBE_LEFT(tmp); else if (comp > 0) tmp = RBE_RIGHT(tmp); else return tmp; } rbe_set(rbe, parent); if (parent != NULL) { if (comp < 0) RBE_LEFT(parent) = rbe; else RBE_RIGHT(parent) = rbe; } else RBH_ROOT(rbt) = rbe; rbe_insert_color(rbt, rbe); return NULL; } /* Finds the node with the same key as elm */ struct rb_entry *typed_rb_find(struct rbt_tree *rbt, const struct rb_entry *key, int (*cmpfn)( const struct typed_rb_entry *a, const struct typed_rb_entry *b)) { struct rb_entry *tmp = RBH_ROOT(rbt); int comp; while (tmp != NULL) { comp = cmpfn(key, tmp); if (comp < 0) tmp = RBE_LEFT(tmp); else if (comp > 0) tmp = RBE_RIGHT(tmp); else return tmp; } return (NULL); } struct rb_entry *typed_rb_find_gteq(struct rbt_tree *rbt, const struct rb_entry *key, int (*cmpfn)( const struct typed_rb_entry *a, const struct typed_rb_entry *b)) { struct rb_entry *tmp = RBH_ROOT(rbt), *best = NULL; int comp; while (tmp != NULL) { comp = cmpfn(key, tmp); if (comp < 0) { best = tmp; tmp = RBE_LEFT(tmp); } else if (comp > 0) tmp = RBE_RIGHT(tmp); else return tmp; } return best; } struct rb_entry *typed_rb_find_lt(struct rbt_tree *rbt, const struct rb_entry *key, int (*cmpfn)( const struct typed_rb_entry *a, const struct typed_rb_entry *b)) { struct rb_entry *tmp = RBH_ROOT(rbt), *best = NULL; int comp; while (tmp != NULL) { comp = cmpfn(key, tmp); if (comp <= 0) tmp = RBE_LEFT(tmp); else { best = tmp; tmp = RBE_RIGHT(tmp); } } return best; } struct rb_entry *typed_rb_next(struct rb_entry *rbe) { if (RBE_RIGHT(rbe) != NULL) { rbe = RBE_RIGHT(rbe); while (RBE_LEFT(rbe) != NULL) rbe = RBE_LEFT(rbe); } else { if (RBE_PARENT(rbe) && (rbe == RBE_LEFT(RBE_PARENT(rbe)))) rbe = RBE_PARENT(rbe); else { while (RBE_PARENT(rbe) && (rbe == RBE_RIGHT(RBE_PARENT(rbe)))) rbe = RBE_PARENT(rbe); rbe = RBE_PARENT(rbe); } } return rbe; } struct rb_entry *typed_rb_min(struct rbt_tree *rbt) { struct rb_entry *rbe = RBH_ROOT(rbt); struct rb_entry *parent = NULL; while (rbe != NULL) { parent = rbe; rbe = RBE_LEFT(rbe); } return parent; } frr-7.2.1/lib/typerb.h0000644000000000000000000002527013610377563011446 00000000000000/* * The following Red-Black tree implementation is based off code with * original copyright: * * Copyright (c) 2016 David Gwynne * * Permission to use, copy, modify, and distribute this software for any * purpose with or without fee is hereby granted, provided that the above * copyright notice and this permission notice appear in all copies. * * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. */ #ifndef _FRR_TYPERB_H #define _FRR_TYPERB_H #include "typesafe.h" #ifdef __cplusplus extern "C" { #endif struct typed_rb_entry { struct typed_rb_entry *rbt_parent; struct typed_rb_entry *rbt_left; struct typed_rb_entry *rbt_right; unsigned int rbt_color; }; struct typed_rb_root { struct typed_rb_entry *rbt_root; size_t count; }; struct typed_rb_entry *typed_rb_insert(struct typed_rb_root *rbt, struct typed_rb_entry *rbe, int (*cmpfn)( const struct typed_rb_entry *a, const struct typed_rb_entry *b)); struct typed_rb_entry *typed_rb_remove(struct typed_rb_root *rbt, struct typed_rb_entry *rbe); struct typed_rb_entry *typed_rb_find(struct typed_rb_root *rbt, const struct typed_rb_entry *rbe, int (*cmpfn)( const struct typed_rb_entry *a, const struct typed_rb_entry *b)); struct typed_rb_entry *typed_rb_find_gteq(struct typed_rb_root *rbt, const struct typed_rb_entry *rbe, int (*cmpfn)( const struct typed_rb_entry *a, const struct typed_rb_entry *b)); struct typed_rb_entry *typed_rb_find_lt(struct typed_rb_root *rbt, const struct typed_rb_entry *rbe, int (*cmpfn)( const struct typed_rb_entry *a, const struct typed_rb_entry *b)); struct typed_rb_entry *typed_rb_min(struct typed_rb_root *rbt); struct typed_rb_entry *typed_rb_next(struct typed_rb_entry *rbe); #define _PREDECL_RBTREE(prefix) \ struct prefix ## _head { struct typed_rb_root rr; }; \ struct prefix ## _item { struct typed_rb_entry re; }; #define INIT_RBTREE_UNIQ(var) { } #define INIT_RBTREE_NONUNIQ(var) { } #define _DECLARE_RBTREE(prefix, type, field, cmpfn_nuq, cmpfn_uq) \ \ macro_inline void prefix ## _init(struct prefix##_head *h) \ { \ memset(h, 0, sizeof(*h)); \ } \ macro_inline void prefix ## _fini(struct prefix##_head *h) \ { \ memset(h, 0, sizeof(*h)); \ } \ macro_inline type *prefix ## _add(struct prefix##_head *h, type *item) \ { \ struct typed_rb_entry *re; \ re = typed_rb_insert(&h->rr, &item->field.re, cmpfn_uq); \ return container_of_null(re, type, field.re); \ } \ macro_inline type *prefix ## _find_gteq(struct prefix##_head *h, \ const type *item) \ { \ struct typed_rb_entry *re; \ re = typed_rb_find_gteq(&h->rr, &item->field.re, cmpfn_nuq); \ return container_of_null(re, type, field.re); \ } \ macro_inline type *prefix ## _find_lt(struct prefix##_head *h, \ const type *item) \ { \ struct typed_rb_entry *re; \ re = typed_rb_find_lt(&h->rr, &item->field.re, cmpfn_nuq); \ return container_of_null(re, type, field.re); \ } \ macro_inline type *prefix ## _del(struct prefix##_head *h, type *item) \ { \ struct typed_rb_entry *re; \ re = typed_rb_remove(&h->rr, &item->field.re); \ return container_of_null(re, type, field.re); \ } \ macro_inline type *prefix ## _pop(struct prefix##_head *h) \ { \ struct typed_rb_entry *re; \ re = typed_rb_min(&h->rr); \ if (!re) \ return NULL; \ typed_rb_remove(&h->rr, re); \ return container_of(re, type, field.re); \ } \ macro_pure type *prefix ## _first(struct prefix##_head *h) \ { \ struct typed_rb_entry *re; \ re = typed_rb_min(&h->rr); \ return container_of_null(re, type, field.re); \ } \ macro_pure type *prefix ## _next(struct prefix##_head *h, type *item) \ { \ struct typed_rb_entry *re; \ re = typed_rb_next(&item->field.re); \ return container_of_null(re, type, field.re); \ } \ macro_pure type *prefix ## _next_safe(struct prefix##_head *h, type *item) \ { \ struct typed_rb_entry *re; \ re = item ? typed_rb_next(&item->field.re) : NULL; \ return container_of_null(re, type, field.re); \ } \ macro_pure size_t prefix ## _count(const struct prefix##_head *h) \ { \ return h->rr.count; \ } \ /* ... */ #define PREDECL_RBTREE_UNIQ(prefix) \ _PREDECL_RBTREE(prefix) #define DECLARE_RBTREE_UNIQ(prefix, type, field, cmpfn) \ \ macro_inline int prefix ## __cmp(const struct typed_rb_entry *a, \ const struct typed_rb_entry *b) \ { \ return cmpfn(container_of(a, type, field.re), \ container_of(b, type, field.re)); \ } \ macro_inline type *prefix ## _find(struct prefix##_head *h, const type *item) \ { \ struct typed_rb_entry *re; \ re = typed_rb_find(&h->rr, &item->field.re, &prefix ## __cmp); \ return container_of_null(re, type, field.re); \ } \ \ _DECLARE_RBTREE(prefix, type, field, prefix ## __cmp, prefix ## __cmp) \ /* ... */ #define PREDECL_RBTREE_NONUNIQ(prefix) \ _PREDECL_RBTREE(prefix) #define DECLARE_RBTREE_NONUNIQ(prefix, type, field, cmpfn) \ \ macro_inline int prefix ## __cmp(const struct typed_rb_entry *a, \ const struct typed_rb_entry *b) \ { \ return cmpfn(container_of(a, type, field.re), \ container_of(b, type, field.re)); \ } \ macro_inline int prefix ## __cmp_uq(const struct typed_rb_entry *a, \ const struct typed_rb_entry *b) \ { \ int cmpval = cmpfn(container_of(a, type, field.re), \ container_of(b, type, field.re)); \ if (cmpval) \ return cmpval; \ if (a < b) \ return -1; \ if (a > b) \ return 1; \ return 0; \ } \ \ _DECLARE_RBTREE(prefix, type, field, prefix ## __cmp, prefix ## __cmp_uq) \ /* ... */ #ifdef __cplusplus } #endif #endif /* _FRR_TYPERB_H */ frr-7.2.1/lib/typesafe.c0000644000000000000000000003241013610377563011746 00000000000000/* * Copyright (c) 2019 David Lamparter, for NetDEF, Inc. * * Permission to use, copy, modify, and distribute this software for any * purpose with or without fee is hereby granted, provided that the above * copyright notice and this permission notice appear in all copies. * * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. */ #ifdef HAVE_CONFIG_H #include "config.h" #endif #include #include #include "typesafe.h" #include "memory.h" DEFINE_MTYPE_STATIC(LIB, TYPEDHASH_BUCKET, "Typed-hash bucket") DEFINE_MTYPE_STATIC(LIB, SKIPLIST_OFLOW, "Skiplist overflow") DEFINE_MTYPE_STATIC(LIB, HEAP_ARRAY, "Typed-heap array") #if 0 static void hash_consistency_check(struct thash_head *head) { uint32_t i; struct thash_item *item, *prev; for (i = 0; i < HASH_SIZE(*head); i++) { item = head->entries[i]; prev = NULL; while (item) { assert(HASH_KEY(*head, item->hashval) == i); assert(!prev || item->hashval >= prev->hashval); prev = item; item = item->next; } } } #else #define hash_consistency_check(x) #endif void typesafe_hash_grow(struct thash_head *head) { uint32_t newsize = head->count, i, j; uint8_t newshift, delta; hash_consistency_check(head); newsize |= newsize >> 1; newsize |= newsize >> 2; newsize |= newsize >> 4; newsize |= newsize >> 8; newsize |= newsize >> 16; newsize++; newshift = __builtin_ctz(newsize) + 1; if (head->maxshift && newshift > head->maxshift) newshift = head->maxshift; if (newshift == head->tabshift) return; newsize = _HASH_SIZE(newshift); head->entries = XREALLOC(MTYPE_TYPEDHASH_BUCKET, head->entries, sizeof(head->entries[0]) * newsize); memset(head->entries + HASH_SIZE(*head), 0, sizeof(head->entries[0]) * (newsize - HASH_SIZE(*head))); delta = newshift - head->tabshift; i = HASH_SIZE(*head); if (i == 0) goto out; do { struct thash_item **apos, *item; i--; apos = &head->entries[i]; for (j = 0; j < (1U << delta); j++) { item = *apos; *apos = NULL; head->entries[(i << delta) + j] = item; apos = &head->entries[(i << delta) + j]; while ((item = *apos)) { uint32_t midbits; midbits = _HASH_KEY(newshift, item->hashval); midbits &= (1 << delta) - 1; if (midbits > j) break; apos = &item->next; } } } while (i > 0); out: head->tabshift = newshift; hash_consistency_check(head); } void typesafe_hash_shrink(struct thash_head *head) { uint32_t newsize = head->count, i, j; uint8_t newshift, delta; hash_consistency_check(head); if (!head->count) { XFREE(MTYPE_TYPEDHASH_BUCKET, head->entries); head->tabshift = 0; return; } newsize |= newsize >> 1; newsize |= newsize >> 2; newsize |= newsize >> 4; newsize |= newsize >> 8; newsize |= newsize >> 16; newsize++; newshift = __builtin_ctz(newsize) + 1; if (head->minshift && newshift < head->minshift) newshift = head->minshift; if (newshift == head->tabshift) return; newsize = _HASH_SIZE(newshift); delta = head->tabshift - newshift; for (i = 0; i < newsize; i++) { struct thash_item **apos = &head->entries[i]; for (j = 0; j < (1U << delta); j++) { *apos = head->entries[(i << delta) + j]; while (*apos) apos = &(*apos)->next; } } head->entries = XREALLOC(MTYPE_TYPEDHASH_BUCKET, head->entries, sizeof(head->entries[0]) * newsize); head->tabshift = newshift; hash_consistency_check(head); } /* skiplist */ static inline struct sskip_item *sl_level_get(struct sskip_item *item, size_t level) { if (level < SKIPLIST_OVERFLOW) return item->next[level]; if (level == SKIPLIST_OVERFLOW && !((uintptr_t)item->next[level] & 1)) return item->next[level]; uintptr_t ptrval = (uintptr_t)item->next[SKIPLIST_OVERFLOW]; ptrval &= UINTPTR_MAX - 3; struct sskip_overflow *oflow = (struct sskip_overflow *)ptrval; return oflow->next[level - SKIPLIST_OVERFLOW]; } static inline void sl_level_set(struct sskip_item *item, size_t level, struct sskip_item *value) { if (level < SKIPLIST_OVERFLOW) item->next[level] = value; else if (level == SKIPLIST_OVERFLOW && !((uintptr_t)item->next[level] & 1)) item->next[level] = value; else { uintptr_t ptrval = (uintptr_t)item->next[SKIPLIST_OVERFLOW]; ptrval &= UINTPTR_MAX - 3; struct sskip_overflow *oflow = (struct sskip_overflow *)ptrval; oflow->next[level - SKIPLIST_OVERFLOW] = value; } } struct sskip_item *typesafe_skiplist_add(struct sskip_head *head, struct sskip_item *item, int (*cmpfn)(const struct sskip_item *a, const struct sskip_item *b)) { size_t level = SKIPLIST_MAXDEPTH, newlevel, auxlevel; struct sskip_item *prev = &head->hitem, *next, *auxprev, *auxnext; int cmpval; /* level / newlevel are 1-counted here */ newlevel = __builtin_ctz(random()) + 1; if (newlevel > SKIPLIST_MAXDEPTH) newlevel = SKIPLIST_MAXDEPTH; next = NULL; while (level >= newlevel) { next = sl_level_get(prev, level - 1); if (!next) { level--; continue; } cmpval = cmpfn(next, item); if (cmpval < 0) { prev = next; continue; } else if (cmpval == 0) { return next; } level--; } /* check for duplicate item - could be removed if code doesn't rely * on it, but not really work the complication. */ auxlevel = level; auxprev = prev; while (auxlevel) { auxlevel--; auxnext = sl_level_get(auxprev, auxlevel); cmpval = 1; while (auxnext && (cmpval = cmpfn(auxnext, item)) < 0) { auxprev = auxnext; auxnext = sl_level_get(auxprev, auxlevel); } if (cmpval == 0) return auxnext; }; head->count++; memset(item, 0, sizeof(*item)); if (newlevel > SKIPLIST_EMBED) { struct sskip_overflow *oflow; oflow = XMALLOC(MTYPE_SKIPLIST_OFLOW, sizeof(void *) * (newlevel - SKIPLIST_OVERFLOW)); item->next[SKIPLIST_OVERFLOW] = (struct sskip_item *) ((uintptr_t)oflow | 1); } sl_level_set(item, level, next); sl_level_set(prev, level, item); /* level is now 0-counted and < newlevel*/ while (level) { level--; next = sl_level_get(prev, level); while (next && cmpfn(next, item) < 0) { prev = next; next = sl_level_get(prev, level); } sl_level_set(item, level, next); sl_level_set(prev, level, item); }; return NULL; } /* NOTE: level counting below is 1-based since that makes the code simpler! */ struct sskip_item *typesafe_skiplist_find(struct sskip_head *head, const struct sskip_item *item, int (*cmpfn)( const struct sskip_item *a, const struct sskip_item *b)) { size_t level = SKIPLIST_MAXDEPTH; struct sskip_item *prev = &head->hitem, *next; int cmpval; while (level) { next = sl_level_get(prev, level - 1); if (!next) { level--; continue; } cmpval = cmpfn(next, item); if (cmpval < 0) { prev = next; continue; } if (cmpval == 0) return next; level--; } return NULL; } struct sskip_item *typesafe_skiplist_find_gteq(struct sskip_head *head, const struct sskip_item *item, int (*cmpfn)( const struct sskip_item *a, const struct sskip_item *b)) { size_t level = SKIPLIST_MAXDEPTH; struct sskip_item *prev = &head->hitem, *next; int cmpval; while (level) { next = sl_level_get(prev, level - 1); if (!next) { level--; continue; } cmpval = cmpfn(next, item); if (cmpval < 0) { prev = next; continue; } if (cmpval == 0) return next; level--; } return next; } struct sskip_item *typesafe_skiplist_find_lt(struct sskip_head *head, const struct sskip_item *item, int (*cmpfn)( const struct sskip_item *a, const struct sskip_item *b)) { size_t level = SKIPLIST_MAXDEPTH; struct sskip_item *prev = &head->hitem, *next, *best = NULL; int cmpval; while (level) { next = sl_level_get(prev, level - 1); if (!next) { level--; continue; } cmpval = cmpfn(next, item); if (cmpval < 0) { best = prev = next; continue; } level--; } return best; } struct sskip_item *typesafe_skiplist_del( struct sskip_head *head, struct sskip_item *item, int (*cmpfn)(const struct sskip_item *a, const struct sskip_item *b)) { size_t level = SKIPLIST_MAXDEPTH; struct sskip_item *prev = &head->hitem, *next; int cmpval; bool found = false; while (level) { next = sl_level_get(prev, level - 1); if (!next) { level--; continue; } if (next == item) { sl_level_set(prev, level - 1, sl_level_get(item, level - 1)); level--; found = true; continue; } cmpval = cmpfn(next, item); if (cmpval < 0) { prev = next; continue; } level--; } if (!found) return NULL; /* TBD: assert when trying to remove non-existing item? */ head->count--; if ((uintptr_t)item->next[SKIPLIST_OVERFLOW] & 1) { uintptr_t ptrval = (uintptr_t)item->next[SKIPLIST_OVERFLOW]; ptrval &= UINTPTR_MAX - 3; struct sskip_overflow *oflow = (struct sskip_overflow *)ptrval; XFREE(MTYPE_SKIPLIST_OFLOW, oflow); } memset(item, 0, sizeof(*item)); return item; } struct sskip_item *typesafe_skiplist_pop(struct sskip_head *head) { size_t level = SKIPLIST_MAXDEPTH; struct sskip_item *prev = &head->hitem, *next, *item; item = sl_level_get(prev, 0); if (!item) return NULL; do { level--; next = sl_level_get(prev, level); if (next != item) continue; sl_level_set(prev, level, sl_level_get(item, level)); } while (level); head->count--; if ((uintptr_t)item->next[SKIPLIST_OVERFLOW] & 1) { uintptr_t ptrval = (uintptr_t)item->next[SKIPLIST_OVERFLOW]; ptrval &= UINTPTR_MAX - 3; struct sskip_overflow *oflow = (struct sskip_overflow *)ptrval; XFREE(MTYPE_SKIPLIST_OFLOW, oflow); } memset(item, 0, sizeof(*item)); return item; } /* heap */ #if 0 static void heap_consistency_check(struct heap_head *head, int (*cmpfn)(const struct heap_item *a, const struct heap_item *b), uint32_t pos) { uint32_t rghtpos = pos + 1; uint32_t downpos = HEAP_NARY * (pos + 1); if (pos + 1 > ~0U / HEAP_NARY) downpos = ~0U; if ((pos & (HEAP_NARY - 1)) != HEAP_NARY - 1 && rghtpos < head->count) { assert(cmpfn(head->array[rghtpos], head->array[pos]) >= 0); heap_consistency_check(head, cmpfn, rghtpos); } if (downpos < head->count) { assert(cmpfn(head->array[downpos], head->array[pos]) >= 0); heap_consistency_check(head, cmpfn, downpos); } } #else #define heap_consistency_check(head, cmpfn, pos) #endif void typesafe_heap_resize(struct heap_head *head, bool grow) { uint32_t newsize; if (grow) { newsize = head->arraysz; if (newsize <= 36) newsize = 72; else if (newsize < 262144) newsize += newsize / 2; else if (newsize < 0xaaaa0000) newsize += newsize / 3; else assert(!newsize); } else if (head->count > 0) { newsize = head->count; } else { XFREE(MTYPE_HEAP_ARRAY, head->array); head->arraysz = 0; return; } newsize += HEAP_NARY - 1; newsize &= ~(HEAP_NARY - 1); if (newsize == head->arraysz) return; head->array = XREALLOC(MTYPE_HEAP_ARRAY, head->array, newsize * sizeof(struct heap_item *)); head->arraysz = newsize; } void typesafe_heap_pushdown(struct heap_head *head, uint32_t pos, struct heap_item *item, int (*cmpfn)(const struct heap_item *a, const struct heap_item *b)) { uint32_t rghtpos, downpos, moveto; while (1) { /* rghtpos: neighbor to the "right", inside block of NARY. * may be invalid if last in block, check nary_last() * downpos: first neighbor in the "downwards" block further * away from the root */ rghtpos = pos + 1; /* make sure we can use the full 4G items */ downpos = HEAP_NARY * (pos + 1); if (pos + 1 > ~0U / HEAP_NARY) /* multiplication overflowed. ~0U is guaranteed * to be an invalid index; size limit is enforced in * resize() */ downpos = ~0U; /* only used on break */ moveto = pos; #define nary_last(x) (((x) & (HEAP_NARY - 1)) == HEAP_NARY - 1) if (downpos >= head->count || cmpfn(head->array[downpos], item) >= 0) { /* not moving down; either at end or down is >= item */ if (nary_last(pos) || rghtpos >= head->count || cmpfn(head->array[rghtpos], item) >= 0) /* not moving right either - got our spot */ break; moveto = rghtpos; /* else: downpos is valid and < item. choose between down * or right (if the latter is an option) */ } else if (nary_last(pos) || cmpfn(head->array[rghtpos], head->array[downpos]) >= 0) moveto = downpos; else moveto = rghtpos; #undef nary_last head->array[pos] = head->array[moveto]; head->array[pos]->index = pos; pos = moveto; } head->array[moveto] = item; item->index = moveto; heap_consistency_check(head, cmpfn, 0); } void typesafe_heap_pullup(struct heap_head *head, uint32_t pos, struct heap_item *item, int (*cmpfn)(const struct heap_item *a, const struct heap_item *b)) { uint32_t moveto; while (pos != 0) { if ((pos & (HEAP_NARY - 1)) == 0) moveto = pos / HEAP_NARY - 1; else moveto = pos - 1; if (cmpfn(head->array[moveto], item) <= 0) break; head->array[pos] = head->array[moveto]; head->array[pos]->index = pos; pos = moveto; } head->array[pos] = item; item->index = pos; heap_consistency_check(head, cmpfn, 0); } frr-7.2.1/lib/typesafe.h0000644000000000000000000014260213610377563011760 00000000000000/* * Copyright (c) 2016-2019 David Lamparter, for NetDEF, Inc. * * Permission to use, copy, modify, and distribute this software for any * purpose with or without fee is hereby granted, provided that the above * copyright notice and this permission notice appear in all copies. * * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. */ #ifndef _FRR_TYPESAFE_H #define _FRR_TYPESAFE_H #include #include #include #include #include "compiler.h" #ifdef __cplusplus extern "C" { #endif /* generic macros for all list-like types */ #define frr_each(prefix, head, item) \ for (item = prefix##_first(head); item; \ item = prefix##_next(head, item)) #define frr_each_safe(prefix, head, item) \ for (typeof(prefix##_next_safe(head, NULL)) prefix##_safe = \ prefix##_next_safe(head, \ (item = prefix##_first(head))); \ item; \ item = prefix##_safe, \ prefix##_safe = prefix##_next_safe(head, prefix##_safe)) #define frr_each_from(prefix, head, item, from) \ for (item = from, from = prefix##_next_safe(head, item); \ item; \ item = from, from = prefix##_next_safe(head, from)) /* single-linked list, unsorted/arbitrary. * can be used as queue with add_tail / pop */ /* don't use these structs directly */ struct slist_item { struct slist_item *next; }; struct slist_head { struct slist_item *first, **last_next; size_t count; }; static inline void typesafe_list_add(struct slist_head *head, struct slist_item **pos, struct slist_item *item) { item->next = *pos; *pos = item; if (pos == head->last_next) head->last_next = &item->next; head->count++; } /* use as: * * PREDECL_LIST(namelist) * struct name { * struct namelist_item nlitem; * } * DECLARE_LIST(namelist, struct name, nlitem) */ #define PREDECL_LIST(prefix) \ struct prefix ## _head { struct slist_head sh; }; \ struct prefix ## _item { struct slist_item si; }; #define INIT_LIST(var) { .sh = { .last_next = &var.sh.first, }, } #define DECLARE_LIST(prefix, type, field) \ \ macro_inline void prefix ## _init(struct prefix##_head *h) \ { \ memset(h, 0, sizeof(*h)); \ h->sh.last_next = &h->sh.first; \ } \ macro_inline void prefix ## _fini(struct prefix##_head *h) \ { \ memset(h, 0, sizeof(*h)); \ } \ macro_inline void prefix ## _add_head(struct prefix##_head *h, type *item) \ { \ typesafe_list_add(&h->sh, &h->sh.first, &item->field.si); \ } \ macro_inline void prefix ## _add_tail(struct prefix##_head *h, type *item) \ { \ typesafe_list_add(&h->sh, h->sh.last_next, &item->field.si); \ } \ macro_inline void prefix ## _add_after(struct prefix##_head *h, \ type *after, type *item) \ { \ struct slist_item **nextp; \ nextp = after ? &after->field.si.next : &h->sh.first; \ typesafe_list_add(&h->sh, nextp, &item->field.si); \ } \ /* TODO: del_hint */ \ macro_inline type *prefix ## _del(struct prefix##_head *h, type *item) \ { \ struct slist_item **iter = &h->sh.first; \ while (*iter && *iter != &item->field.si) \ iter = &(*iter)->next; \ if (!*iter) \ return NULL; \ h->sh.count--; \ *iter = item->field.si.next; \ if (!item->field.si.next) \ h->sh.last_next = iter; \ return item; \ } \ macro_inline type *prefix ## _pop(struct prefix##_head *h) \ { \ struct slist_item *sitem = h->sh.first; \ if (!sitem) \ return NULL; \ h->sh.count--; \ h->sh.first = sitem->next; \ if (h->sh.first == NULL) \ h->sh.last_next = &h->sh.first; \ return container_of(sitem, type, field.si); \ } \ macro_pure type *prefix ## _first(struct prefix##_head *h) \ { \ return container_of_null(h->sh.first, type, field.si); \ } \ macro_pure type *prefix ## _next(struct prefix##_head * h, type *item) \ { \ struct slist_item *sitem = &item->field.si; \ return container_of_null(sitem->next, type, field.si); \ } \ macro_pure type *prefix ## _next_safe(struct prefix##_head *h, type *item) \ { \ struct slist_item *sitem; \ if (!item) \ return NULL; \ sitem = &item->field.si; \ return container_of_null(sitem->next, type, field.si); \ } \ macro_pure size_t prefix ## _count(const struct prefix##_head *h) \ { \ return h->sh.count; \ } \ /* ... */ /* don't use these structs directly */ struct dlist_item { struct dlist_item *next; struct dlist_item *prev; }; struct dlist_head { struct dlist_item hitem; size_t count; }; static inline void typesafe_dlist_add(struct dlist_head *head, struct dlist_item *prev, struct dlist_item *item) { item->next = prev->next; item->next->prev = item; item->prev = prev; prev->next = item; head->count++; } /* double-linked list, for fast item deletion */ #define PREDECL_DLIST(prefix) \ struct prefix ## _head { struct dlist_head dh; }; \ struct prefix ## _item { struct dlist_item di; }; #define INIT_DLIST(var) { .dh = { \ .hitem = { &var.dh.hitem, &var.dh.hitem }, }, } #define DECLARE_DLIST(prefix, type, field) \ \ macro_inline void prefix ## _init(struct prefix##_head *h) \ { \ memset(h, 0, sizeof(*h)); \ h->dh.hitem.prev = &h->dh.hitem; \ h->dh.hitem.next = &h->dh.hitem; \ } \ macro_inline void prefix ## _fini(struct prefix##_head *h) \ { \ memset(h, 0, sizeof(*h)); \ } \ macro_inline void prefix ## _add_head(struct prefix##_head *h, type *item) \ { \ typesafe_dlist_add(&h->dh, &h->dh.hitem, &item->field.di); \ } \ macro_inline void prefix ## _add_tail(struct prefix##_head *h, type *item) \ { \ typesafe_dlist_add(&h->dh, h->dh.hitem.prev, &item->field.di); \ } \ macro_inline void prefix ## _add_after(struct prefix##_head *h, \ type *after, type *item) \ { \ struct dlist_item *prev; \ prev = after ? &after->field.di : &h->dh.hitem; \ typesafe_dlist_add(&h->dh, prev, &item->field.di); \ } \ macro_inline type *prefix ## _del(struct prefix##_head *h, type *item) \ { \ struct dlist_item *ditem = &item->field.di; \ ditem->prev->next = ditem->next; \ ditem->next->prev = ditem->prev; \ h->dh.count--; \ ditem->prev = ditem->next = NULL; \ return item; \ } \ macro_inline type *prefix ## _pop(struct prefix##_head *h) \ { \ struct dlist_item *ditem = h->dh.hitem.next; \ if (ditem == &h->dh.hitem) \ return NULL; \ ditem->prev->next = ditem->next; \ ditem->next->prev = ditem->prev; \ h->dh.count--; \ return container_of(ditem, type, field.di); \ } \ macro_pure type *prefix ## _first(struct prefix##_head *h) \ { \ struct dlist_item *ditem = h->dh.hitem.next; \ if (ditem == &h->dh.hitem) \ return NULL; \ return container_of(ditem, type, field.di); \ } \ macro_pure type *prefix ## _next(struct prefix##_head * h, type *item) \ { \ struct dlist_item *ditem = &item->field.di; \ if (ditem->next == &h->dh.hitem) \ return NULL; \ return container_of(ditem->next, type, field.di); \ } \ macro_pure type *prefix ## _next_safe(struct prefix##_head *h, type *item) \ { \ if (!item) \ return NULL; \ return prefix ## _next(h, item); \ } \ macro_pure size_t prefix ## _count(const struct prefix##_head *h) \ { \ return h->dh.count; \ } \ /* ... */ /* note: heap currently caps out at 4G items */ #define HEAP_NARY 8U typedef uint32_t heap_index_i; struct heap_item { uint32_t index; }; struct heap_head { struct heap_item **array; uint32_t arraysz, count; }; #define HEAP_RESIZE_TRESH_UP(h) \ (h->hh.count + 1 >= h->hh.arraysz) #define HEAP_RESIZE_TRESH_DN(h) \ (h->hh.count == 0 || \ h->hh.arraysz - h->hh.count > (h->hh.count + 1024) / 2) #define PREDECL_HEAP(prefix) \ struct prefix ## _head { struct heap_head hh; }; \ struct prefix ## _item { struct heap_item hi; }; #define INIT_HEAP(var) { } #define DECLARE_HEAP(prefix, type, field, cmpfn) \ \ macro_inline void prefix ## _init(struct prefix##_head *h) \ { \ memset(h, 0, sizeof(*h)); \ } \ macro_inline void prefix ## _fini(struct prefix##_head *h) \ { \ assert(h->hh.count == 0); \ memset(h, 0, sizeof(*h)); \ } \ macro_inline int prefix ## __cmp(const struct heap_item *a, \ const struct heap_item *b) \ { \ return cmpfn(container_of(a, type, field.hi), \ container_of(b, type, field.hi)); \ } \ macro_inline type *prefix ## _add(struct prefix##_head *h, type *item) \ { \ if (HEAP_RESIZE_TRESH_UP(h)) \ typesafe_heap_resize(&h->hh, true); \ typesafe_heap_pullup(&h->hh, h->hh.count, &item->field.hi, \ prefix ## __cmp); \ h->hh.count++; \ return NULL; \ } \ macro_inline type *prefix ## _del(struct prefix##_head *h, type *item) \ { \ struct heap_item *other; \ uint32_t index = item->field.hi.index; \ assert(h->hh.array[index] == &item->field.hi); \ h->hh.count--; \ other = h->hh.array[h->hh.count]; \ if (cmpfn(container_of(other, type, field.hi), item) < 0) \ typesafe_heap_pullup(&h->hh, index, other, prefix ## __cmp); \ else \ typesafe_heap_pushdown(&h->hh, index, other, prefix ## __cmp); \ if (HEAP_RESIZE_TRESH_DN(h)) \ typesafe_heap_resize(&h->hh, false); \ return item; \ } \ macro_inline type *prefix ## _pop(struct prefix##_head *h) \ { \ struct heap_item *hitem, *other; \ if (h->hh.count == 0) \ return NULL; \ hitem = h->hh.array[0]; \ h->hh.count--; \ other = h->hh.array[h->hh.count]; \ typesafe_heap_pushdown(&h->hh, 0, other, prefix ## __cmp); \ if (HEAP_RESIZE_TRESH_DN(h)) \ typesafe_heap_resize(&h->hh, false); \ return container_of(hitem, type, field.hi); \ } \ macro_pure type *prefix ## _first(struct prefix##_head *h) \ { \ if (h->hh.count == 0) \ return NULL; \ return container_of(h->hh.array[0], type, field.hi); \ } \ macro_pure type *prefix ## _next(struct prefix##_head *h, type *item) \ { \ uint32_t idx = item->field.hi.index + 1; \ if (idx >= h->hh.count) \ return NULL; \ return container_of(h->hh.array[idx], type, field.hi); \ } \ macro_pure type *prefix ## _next_safe(struct prefix##_head *h, type *item) \ { \ if (!item) \ return NULL; \ return prefix ## _next(h, item); \ } \ macro_pure size_t prefix ## _count(const struct prefix##_head *h) \ { \ return h->hh.count; \ } \ /* ... */ extern void typesafe_heap_resize(struct heap_head *head, bool grow); extern void typesafe_heap_pushdown(struct heap_head *head, uint32_t index, struct heap_item *item, int (*cmpfn)(const struct heap_item *a, const struct heap_item *b)); extern void typesafe_heap_pullup(struct heap_head *head, uint32_t index, struct heap_item *item, int (*cmpfn)(const struct heap_item *a, const struct heap_item *b)); /* single-linked list, sorted. * can be used as priority queue with add / pop */ /* don't use these structs directly */ struct ssort_item { struct ssort_item *next; }; struct ssort_head { struct ssort_item *first; size_t count; }; /* use as: * * PREDECL_SORTLIST(namelist) * struct name { * struct namelist_item nlitem; * } * DECLARE_SORTLIST(namelist, struct name, nlitem) */ #define _PREDECL_SORTLIST(prefix) \ struct prefix ## _head { struct ssort_head sh; }; \ struct prefix ## _item { struct ssort_item si; }; #define INIT_SORTLIST_UNIQ(var) { } #define INIT_SORTLIST_NONUNIQ(var) { } #define PREDECL_SORTLIST_UNIQ(prefix) \ _PREDECL_SORTLIST(prefix) #define PREDECL_SORTLIST_NONUNIQ(prefix) \ _PREDECL_SORTLIST(prefix) #define _DECLARE_SORTLIST(prefix, type, field, cmpfn_nuq, cmpfn_uq) \ \ macro_inline void prefix ## _init(struct prefix##_head *h) \ { \ memset(h, 0, sizeof(*h)); \ } \ macro_inline void prefix ## _fini(struct prefix##_head *h) \ { \ memset(h, 0, sizeof(*h)); \ } \ macro_inline type *prefix ## _add(struct prefix##_head *h, type *item) \ { \ struct ssort_item **np = &h->sh.first; \ int c = 1; \ while (*np && (c = cmpfn_uq( \ container_of(*np, type, field.si), item)) < 0) \ np = &(*np)->next; \ if (c == 0) \ return container_of(*np, type, field.si); \ item->field.si.next = *np; \ *np = &item->field.si; \ h->sh.count++; \ return NULL; \ } \ macro_inline type *prefix ## _find_gteq(struct prefix##_head *h, \ const type *item) \ { \ struct ssort_item *sitem = h->sh.first; \ int cmpval = 0; \ while (sitem && (cmpval = cmpfn_nuq( \ container_of(sitem, type, field.si), item)) < 0) \ sitem = sitem->next; \ return container_of_null(sitem, type, field.si); \ } \ macro_inline type *prefix ## _find_lt(struct prefix##_head *h, \ const type *item) \ { \ struct ssort_item *prev = NULL, *sitem = h->sh.first; \ int cmpval = 0; \ while (sitem && (cmpval = cmpfn_nuq( \ container_of(sitem, type, field.si), item)) < 0) \ sitem = (prev = sitem)->next; \ return container_of_null(prev, type, field.si); \ } \ /* TODO: del_hint */ \ macro_inline type *prefix ## _del(struct prefix##_head *h, type *item) \ { \ struct ssort_item **iter = &h->sh.first; \ while (*iter && *iter != &item->field.si) \ iter = &(*iter)->next; \ if (!*iter) \ return NULL; \ h->sh.count--; \ *iter = item->field.si.next; \ return item; \ } \ macro_inline type *prefix ## _pop(struct prefix##_head *h) \ { \ struct ssort_item *sitem = h->sh.first; \ if (!sitem) \ return NULL; \ h->sh.count--; \ h->sh.first = sitem->next; \ return container_of(sitem, type, field.si); \ } \ macro_pure type *prefix ## _first(struct prefix##_head *h) \ { \ return container_of_null(h->sh.first, type, field.si); \ } \ macro_pure type *prefix ## _next(struct prefix##_head *h, type *item) \ { \ struct ssort_item *sitem = &item->field.si; \ return container_of_null(sitem->next, type, field.si); \ } \ macro_pure type *prefix ## _next_safe(struct prefix##_head *h, type *item) \ { \ struct ssort_item *sitem; \ if (!item) \ return NULL; \ sitem = &item->field.si; \ return container_of_null(sitem->next, type, field.si); \ } \ macro_pure size_t prefix ## _count(const struct prefix##_head *h) \ { \ return h->sh.count; \ } \ /* ... */ #define DECLARE_SORTLIST_UNIQ(prefix, type, field, cmpfn) \ _DECLARE_SORTLIST(prefix, type, field, cmpfn, cmpfn) \ \ macro_inline type *prefix ## _find(struct prefix##_head *h, const type *item) \ { \ struct ssort_item *sitem = h->sh.first; \ int cmpval = 0; \ while (sitem && (cmpval = cmpfn( \ container_of(sitem, type, field.si), item)) < 0) \ sitem = sitem->next; \ if (!sitem || cmpval > 0) \ return NULL; \ return container_of(sitem, type, field.si); \ } \ /* ... */ #define DECLARE_SORTLIST_NONUNIQ(prefix, type, field, cmpfn) \ macro_inline int _ ## prefix ## _cmp(const type *a, const type *b) \ { \ int cmpval = cmpfn(a, b); \ if (cmpval) \ return cmpval; \ if (a < b) \ return -1; \ if (a > b) \ return 1; \ return 0; \ } \ _DECLARE_SORTLIST(prefix, type, field, cmpfn, _ ## prefix ## _cmp) \ /* ... */ /* hash, "sorted" by hash value */ /* don't use these structs directly */ struct thash_item { struct thash_item *next; uint32_t hashval; }; struct thash_head { struct thash_item **entries; uint32_t count; uint8_t tabshift; uint8_t minshift, maxshift; }; #define _HASH_SIZE(tabshift) \ ((1U << (tabshift)) >> 1) #define HASH_SIZE(head) \ _HASH_SIZE((head).tabshift) #define _HASH_KEY(tabshift, val) \ ((val) >> (33 - (tabshift))) #define HASH_KEY(head, val) \ _HASH_KEY((head).tabshift, val) #define HASH_GROW_THRESHOLD(head) \ ((head).count >= HASH_SIZE(head)) #define HASH_SHRINK_THRESHOLD(head) \ ((head).count <= (HASH_SIZE(head) - 1) / 2) extern void typesafe_hash_grow(struct thash_head *head); extern void typesafe_hash_shrink(struct thash_head *head); /* use as: * * PREDECL_HASH(namelist) * struct name { * struct namelist_item nlitem; * } * DECLARE_HASH(namelist, struct name, nlitem, cmpfunc, hashfunc) */ #define PREDECL_HASH(prefix) \ struct prefix ## _head { struct thash_head hh; }; \ struct prefix ## _item { struct thash_item hi; }; #define INIT_HASH(var) { } #define DECLARE_HASH(prefix, type, field, cmpfn, hashfn) \ \ macro_inline void prefix ## _init(struct prefix##_head *h) \ { \ memset(h, 0, sizeof(*h)); \ } \ macro_inline void prefix ## _fini(struct prefix##_head *h) \ { \ assert(h->hh.count == 0); \ h->hh.minshift = 0; \ typesafe_hash_shrink(&h->hh); \ memset(h, 0, sizeof(*h)); \ } \ macro_inline type *prefix ## _add(struct prefix##_head *h, type *item) \ { \ h->hh.count++; \ if (!h->hh.tabshift || HASH_GROW_THRESHOLD(h->hh)) \ typesafe_hash_grow(&h->hh); \ \ uint32_t hval = hashfn(item), hbits = HASH_KEY(h->hh, hval); \ item->field.hi.hashval = hval; \ struct thash_item **np = &h->hh.entries[hbits]; \ while (*np && (*np)->hashval < hval) \ np = &(*np)->next; \ if (*np && cmpfn(container_of(*np, type, field.hi), item) == 0) { \ h->hh.count--; \ return container_of(*np, type, field.hi); \ } \ item->field.hi.next = *np; \ *np = &item->field.hi; \ return NULL; \ } \ macro_inline type *prefix ## _find(struct prefix##_head *h, const type *item) \ { \ if (!h->hh.tabshift) \ return NULL; \ uint32_t hval = hashfn(item), hbits = HASH_KEY(h->hh, hval); \ struct thash_item *hitem = h->hh.entries[hbits]; \ while (hitem && hitem->hashval < hval) \ hitem = hitem->next; \ while (hitem && hitem->hashval == hval) { \ if (!cmpfn(container_of(hitem, type, field.hi), item)) \ return container_of(hitem, type, field.hi); \ hitem = hitem->next; \ } \ return NULL; \ } \ macro_inline type *prefix ## _del(struct prefix##_head *h, type *item) \ { \ if (!h->hh.tabshift) \ return NULL; \ uint32_t hval = item->field.hi.hashval, hbits = HASH_KEY(h->hh, hval); \ struct thash_item **np = &h->hh.entries[hbits]; \ while (*np && (*np)->hashval < hval) \ np = &(*np)->next; \ while (*np && *np != &item->field.hi && (*np)->hashval == hval) \ np = &(*np)->next; \ if (*np != &item->field.hi) \ return NULL; \ *np = item->field.hi.next; \ item->field.hi.next = NULL; \ h->hh.count--; \ if (HASH_SHRINK_THRESHOLD(h->hh)) \ typesafe_hash_shrink(&h->hh); \ return item; \ } \ macro_inline type *prefix ## _pop(struct prefix##_head *h) \ { \ uint32_t i; \ for (i = 0; i < HASH_SIZE(h->hh); i++) \ if (h->hh.entries[i]) { \ struct thash_item *hitem = h->hh.entries[i]; \ h->hh.entries[i] = hitem->next; \ h->hh.count--; \ hitem->next = NULL; \ if (HASH_SHRINK_THRESHOLD(h->hh)) \ typesafe_hash_shrink(&h->hh); \ return container_of(hitem, type, field.hi); \ } \ return NULL; \ } \ macro_pure type *prefix ## _first(struct prefix##_head *h) \ { \ uint32_t i; \ for (i = 0; i < HASH_SIZE(h->hh); i++) \ if (h->hh.entries[i]) \ return container_of(h->hh.entries[i], type, field.hi); \ return NULL; \ } \ macro_pure type *prefix ## _next(struct prefix##_head *h, type *item) \ { \ struct thash_item *hitem = &item->field.hi; \ if (hitem->next) \ return container_of(hitem->next, type, field.hi); \ uint32_t i = HASH_KEY(h->hh, hitem->hashval) + 1; \ for (; i < HASH_SIZE(h->hh); i++) \ if (h->hh.entries[i]) \ return container_of(h->hh.entries[i], type, field.hi); \ return NULL; \ } \ macro_pure type *prefix ## _next_safe(struct prefix##_head *h, type *item) \ { \ if (!item) \ return NULL; \ return prefix ## _next(h, item); \ } \ macro_pure size_t prefix ## _count(const struct prefix##_head *h) \ { \ return h->hh.count; \ } \ /* ... */ /* skiplist, sorted. * can be used as priority queue with add / pop */ /* don't use these structs directly */ #define SKIPLIST_MAXDEPTH 16 #define SKIPLIST_EMBED 4 #define SKIPLIST_OVERFLOW (SKIPLIST_EMBED - 1) struct sskip_item { struct sskip_item *next[SKIPLIST_EMBED]; }; struct sskip_overflow { struct sskip_item *next[SKIPLIST_MAXDEPTH - SKIPLIST_OVERFLOW]; }; struct sskip_head { struct sskip_item hitem; struct sskip_item *overflow[SKIPLIST_MAXDEPTH - SKIPLIST_OVERFLOW]; size_t count; }; /* use as: * * PREDECL_SKIPLIST(namelist) * struct name { * struct namelist_item nlitem; * } * DECLARE_SKIPLIST(namelist, struct name, nlitem, cmpfunc) */ #define _PREDECL_SKIPLIST(prefix) \ struct prefix ## _head { struct sskip_head sh; }; \ struct prefix ## _item { struct sskip_item si; }; #define INIT_SKIPLIST_UNIQ(var) { } #define INIT_SKIPLIST_NONUNIQ(var) { } #define _DECLARE_SKIPLIST(prefix, type, field, cmpfn_nuq, cmpfn_uq) \ \ macro_inline void prefix ## _init(struct prefix##_head *h) \ { \ memset(h, 0, sizeof(*h)); \ h->sh.hitem.next[SKIPLIST_OVERFLOW] = (struct sskip_item *) \ ((uintptr_t)h->sh.overflow | 1); \ } \ macro_inline void prefix ## _fini(struct prefix##_head *h) \ { \ memset(h, 0, sizeof(*h)); \ } \ macro_inline type *prefix ## _add(struct prefix##_head *h, type *item) \ { \ struct sskip_item *si; \ si = typesafe_skiplist_add(&h->sh, &item->field.si, cmpfn_uq); \ return container_of_null(si, type, field.si); \ } \ macro_inline type *prefix ## _find_gteq(struct prefix##_head *h, \ const type *item) \ { \ struct sskip_item *sitem = typesafe_skiplist_find_gteq(&h->sh, \ &item->field.si, cmpfn_nuq); \ return container_of_null(sitem, type, field.si); \ } \ macro_inline type *prefix ## _find_lt(struct prefix##_head *h, \ const type *item) \ { \ struct sskip_item *sitem = typesafe_skiplist_find_lt(&h->sh, \ &item->field.si, cmpfn_nuq); \ return container_of_null(sitem, type, field.si); \ } \ macro_inline type *prefix ## _del(struct prefix##_head *h, type *item) \ { \ struct sskip_item *sitem = typesafe_skiplist_del(&h->sh, \ &item->field.si, cmpfn_uq); \ return container_of_null(sitem, type, field.si); \ } \ macro_inline type *prefix ## _pop(struct prefix##_head *h) \ { \ struct sskip_item *sitem = typesafe_skiplist_pop(&h->sh); \ return container_of_null(sitem, type, field.si); \ } \ macro_pure type *prefix ## _first(struct prefix##_head *h) \ { \ struct sskip_item *first = h->sh.hitem.next[0]; \ return container_of_null(first, type, field.si); \ } \ macro_pure type *prefix ## _next(struct prefix##_head *h, type *item) \ { \ struct sskip_item *next = item->field.si.next[0]; \ return container_of_null(next, type, field.si); \ } \ macro_pure type *prefix ## _next_safe(struct prefix##_head *h, type *item) \ { \ struct sskip_item *next; \ next = item ? item->field.si.next[0] : NULL; \ return container_of_null(next, type, field.si); \ } \ macro_pure size_t prefix ## _count(const struct prefix##_head *h) \ { \ return h->sh.count; \ } \ /* ... */ #define PREDECL_SKIPLIST_UNIQ(prefix) \ _PREDECL_SKIPLIST(prefix) #define DECLARE_SKIPLIST_UNIQ(prefix, type, field, cmpfn) \ \ macro_inline int prefix ## __cmp(const struct sskip_item *a, \ const struct sskip_item *b) \ { \ return cmpfn(container_of(a, type, field.si), \ container_of(b, type, field.si)); \ } \ macro_inline type *prefix ## _find(struct prefix##_head *h, const type *item) \ { \ struct sskip_item *sitem = typesafe_skiplist_find(&h->sh, \ &item->field.si, &prefix ## __cmp); \ return container_of_null(sitem, type, field.si); \ } \ \ _DECLARE_SKIPLIST(prefix, type, field, \ prefix ## __cmp, prefix ## __cmp) \ /* ... */ #define PREDECL_SKIPLIST_NONUNIQ(prefix) \ _PREDECL_SKIPLIST(prefix) #define DECLARE_SKIPLIST_NONUNIQ(prefix, type, field, cmpfn) \ \ macro_inline int prefix ## __cmp(const struct sskip_item *a, \ const struct sskip_item *b) \ { \ return cmpfn(container_of(a, type, field.si), \ container_of(b, type, field.si)); \ } \ macro_inline int prefix ## __cmp_uq(const struct sskip_item *a, \ const struct sskip_item *b) \ { \ int cmpval = cmpfn(container_of(a, type, field.si), \ container_of(b, type, field.si)); \ if (cmpval) \ return cmpval; \ if (a < b) \ return -1; \ if (a > b) \ return 1; \ return 0; \ } \ \ _DECLARE_SKIPLIST(prefix, type, field, \ prefix ## __cmp, prefix ## __cmp_uq) \ /* ... */ extern struct sskip_item *typesafe_skiplist_add(struct sskip_head *head, struct sskip_item *item, int (*cmpfn)( const struct sskip_item *a, const struct sskip_item *b)); extern struct sskip_item *typesafe_skiplist_find(struct sskip_head *head, const struct sskip_item *item, int (*cmpfn)( const struct sskip_item *a, const struct sskip_item *b)); extern struct sskip_item *typesafe_skiplist_find_gteq(struct sskip_head *head, const struct sskip_item *item, int (*cmpfn)( const struct sskip_item *a, const struct sskip_item *b)); extern struct sskip_item *typesafe_skiplist_find_lt(struct sskip_head *head, const struct sskip_item *item, int (*cmpfn)( const struct sskip_item *a, const struct sskip_item *b)); extern struct sskip_item *typesafe_skiplist_del( struct sskip_head *head, struct sskip_item *item, int (*cmpfn)( const struct sskip_item *a, const struct sskip_item *b)); extern struct sskip_item *typesafe_skiplist_pop(struct sskip_head *head); #ifdef __cplusplus } #endif /* this needs to stay at the end because both files include each other. * the resolved order is typesafe.h before typerb.h */ #include "typerb.h" #endif /* _FRR_TYPESAFE_H */ frr-7.2.1/lib/vector.c0000644000000000000000000001101113610377563011422 00000000000000/* Generic vector interface routine * Copyright (C) 1997 Kunihiro Ishiguro * * This file is part of GNU Zebra. * * GNU Zebra is free software; you can redistribute it and/or modify it * under the terms of the GNU General Public License as published by the * Free Software Foundation; either version 2, or (at your option) any * later version. * * GNU Zebra 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; see the file COPYING; if not, write to the Free Software * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA */ #include #include "vector.h" #include "memory.h" DEFINE_MTYPE_STATIC(LIB, VECTOR, "Vector") DEFINE_MTYPE_STATIC(LIB, VECTOR_INDEX, "Vector index") /* Initialize vector : allocate memory and return vector. */ vector vector_init(unsigned int size) { vector v = XCALLOC(MTYPE_VECTOR, sizeof(struct _vector)); /* allocate at least one slot */ if (size == 0) size = 1; v->alloced = size; v->active = 0; v->index = XCALLOC(MTYPE_VECTOR_INDEX, sizeof(void *) * size); return v; } void vector_free(vector v) { XFREE(MTYPE_VECTOR_INDEX, v->index); XFREE(MTYPE_VECTOR, v); } vector vector_copy(vector v) { unsigned int size; vector new = XCALLOC(MTYPE_VECTOR, sizeof(struct _vector)); new->active = v->active; new->alloced = v->alloced; size = sizeof(void *) * (v->alloced); new->index = XCALLOC(MTYPE_VECTOR_INDEX, size); memcpy(new->index, v->index, size); return new; } /* Check assigned index, and if it runs short double index pointer */ void vector_ensure(vector v, unsigned int num) { if (v->alloced > num) return; v->index = XREALLOC(MTYPE_VECTOR_INDEX, v->index, sizeof(void *) * (v->alloced * 2)); memset(&v->index[v->alloced], 0, sizeof(void *) * v->alloced); v->alloced *= 2; if (v->alloced <= num) vector_ensure(v, num); } /* This function only returns next empty slot index. It dose not mean the slot's index memory is assigned, please call vector_ensure() after calling this function. */ int vector_empty_slot(vector v) { unsigned int i; if (v->active == 0) return 0; for (i = 0; i < v->active; i++) if (v->index[i] == 0) return i; return i; } /* Set value to the smallest empty slot. */ int vector_set(vector v, void *val) { unsigned int i; i = vector_empty_slot(v); vector_ensure(v, i); v->index[i] = val; if (v->active <= i) v->active = i + 1; return i; } /* Set value to specified index slot. */ int vector_set_index(vector v, unsigned int i, void *val) { vector_ensure(v, i); v->index[i] = val; if (v->active <= i) v->active = i + 1; return i; } /* Look up vector. */ void *vector_lookup(vector v, unsigned int i) { if (i >= v->active) return NULL; return v->index[i]; } /* Lookup vector, ensure it. */ void *vector_lookup_ensure(vector v, unsigned int i) { vector_ensure(v, i); return v->index[i]; } /* Unset value at specified index slot. */ void vector_unset(vector v, unsigned int i) { if (i >= v->alloced) return; v->index[i] = NULL; if (i + 1 == v->active) { v->active--; while (i && v->index[--i] == NULL && v->active--) ; /* Is this ugly ? */ } } void vector_remove(vector v, unsigned int ix) { if (ix >= v->active) return; int n = (--v->active) - ix; memmove(&v->index[ix], &v->index[ix + 1], n * sizeof(void *)); v->index[v->active] = NULL; } void vector_compact(vector v) { for (unsigned int i = 0; i < vector_active(v); ++i) { if (vector_slot(v, i) == NULL) { vector_remove(v, i); --i; } } } void vector_unset_value(vector v, void *val) { size_t i; for (i = 0; i < v->active; i++) if (v->index[i] == val) { v->index[i] = NULL; break; } if (i + 1 == v->active) do v->active--; while (i && v->index[--i] == NULL); } /* Count the number of not emplty slot. */ unsigned int vector_count(vector v) { unsigned int i; unsigned count = 0; for (i = 0; i < v->active; i++) if (v->index[i] != NULL) count++; return count; } void vector_to_array(vector v, void ***dest, int *argc) { *dest = XCALLOC(MTYPE_TMP, sizeof(void *) * v->active); memcpy(*dest, v->index, sizeof(void *) * v->active); *argc = v->active; } vector array_to_vector(void **src, int argc) { vector v = vector_init(VECTOR_MIN_SIZE); for (int i = 0; i < argc; i++) vector_set_index(v, i, src[i]); return v; } frr-7.2.1/lib/vector.h0000644000000000000000000000461713610377563011445 00000000000000/* * Generic vector interface header. * Copyright (C) 1997, 98 Kunihiro Ishiguro * * This file is part of GNU Zebra. * * GNU Zebra is free software; you can redistribute it and/or modify it * under the terms of the GNU General Public License as published by the * Free Software Foundation; either version 2, or (at your option) any * later version. * * GNU Zebra 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; see the file COPYING; if not, write to the Free Software * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA */ #ifndef _ZEBRA_VECTOR_H #define _ZEBRA_VECTOR_H #include "memory.h" #ifdef __cplusplus extern "C" { #endif /* struct for vector */ struct _vector { unsigned int active; /* number of active slots */ unsigned int alloced; /* number of allocated slot */ void **index; /* index to data */ }; typedef struct _vector *vector; #define VECTOR_MIN_SIZE 1 /* (Sometimes) usefull macros. This macro convert index expression to array expression. */ /* Reference slot at given index, caller must ensure slot is active */ #define vector_slot(V,I) ((V)->index[(I)]) /* Number of active slots. * Note that this differs from vector_count() as it the count returned * will include any empty slots */ #define vector_active(V) ((V)->active) /* Prototypes. */ extern vector vector_init(unsigned int size); extern void vector_ensure(vector v, unsigned int num); extern int vector_empty_slot(vector v); extern int vector_set(vector v, void *val); extern int vector_set_index(vector v, unsigned int i, void *val); extern void vector_unset(vector v, unsigned int i); extern void vector_unset_value(vector v, void *val); extern void vector_remove(vector v, unsigned int ix); extern void vector_compact(vector v); extern unsigned int vector_count(vector v); extern void vector_free(vector v); extern vector vector_copy(vector v); extern void *vector_lookup(vector, unsigned int); extern void *vector_lookup_ensure(vector, unsigned int); extern void vector_to_array(vector v, void ***dest, int *argc); extern vector array_to_vector(void **src, int argc); #ifdef __cplusplus } #endif #endif /* _ZEBRA_VECTOR_H */ frr-7.2.1/lib/version.h.in0000644000000000000000000000320613610377563012226 00000000000000/* @configure_input@ * * Quagga version * Copyright (C) 1997, 1999 Kunihiro Ishiguro * * This file is part of GNU Zebra. * * GNU Zebra is free software; you can redistribute it and/or modify it * under the terms of the GNU General Public License as published by the * Free Software Foundation; either version 2, or (at your option) any * later version. * * GNU Zebra is distributed in the hope that it will be useful, but * WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU * General Public License for more details. * * You should have received a copy of the GNU General Public License * along with GNU Zebra; see the file COPYING. If not, write to the Free * Software Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA * 02111-1307, USA. */ #ifndef _ZEBRA_VERSION_H #define _ZEBRA_VERSION_H #ifdef GIT_VERSION #include "gitversion.h" #endif #ifndef GIT_SUFFIX #define GIT_SUFFIX "" #endif #ifndef GIT_INFO #define GIT_INFO "" #endif #define FRR_PAM_NAME "@PACKAGE_NAME@" #define FRR_SMUX_NAME "@PACKAGE_NAME@" #define FRR_PTM_NAME "@PACKAGE_NAME@" #define FRR_FULL_NAME "FRRouting" #define FRR_VERSION "@PACKAGE_VERSION@" GIT_SUFFIX #define FRR_VER_SHORT "@PACKAGE_VERSION@" #define FRR_BUG_ADDRESS "@PACKAGE_BUGREPORT@" #define FRR_COPYRIGHT "Copyright 1996-2005 Kunihiro Ishiguro, et al." #define FRR_CONFIG_ARGS "@CONFIG_ARGS@" #define FRR_DEFAULT_MOTD \ "\n" \ "Hello, this is " FRR_FULL_NAME " (version " FRR_VERSION ").\n" \ FRR_COPYRIGHT "\n" \ GIT_INFO "\n" pid_t pid_output (const char *); #endif /* _ZEBRA_VERSION_H */ frr-7.2.1/lib/vlan.h0000644000000000000000000000200313610377563011066 00000000000000/* VLAN (802.1q) common header. * Copyright (C) 2016, 2017 Cumulus Networks, Inc. * * This file is part of FRR. * * FRR is free software; you can redistribute it and/or modify it * under the terms of the GNU General Public License as published by the * Free Software Foundation; either version 2, or (at your option) any * later version. * * FRR 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 FRR; see the file COPYING. If not, write to the Free * Software Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA * 02111-1307, USA. */ #ifndef __VLAN_H__ #define __VLAN_H__ #ifdef __cplusplus extern "C" { #endif /* VLAN Identifier */ typedef uint16_t vlanid_t; #define VLANID_MAX 4095 #ifdef __cplusplus } #endif #endif /* __VLAN_H__ */ frr-7.2.1/lib/vrf.c0000644000000000000000000005704213610377563010733 00000000000000/* * VRF functions. * Copyright (C) 2014 6WIND S.A. * * This file is part of GNU Zebra. * * GNU Zebra is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License as published * by the Free Software Foundation; either version 2, or (at your * option) any later version. * * GNU Zebra 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; see the file COPYING; if not, write to the Free Software * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA */ #include /* for basename */ #include #include "if.h" #include "vrf.h" #include "vrf_int.h" #include "prefix.h" #include "table.h" #include "log.h" #include "memory.h" #include "command.h" #include "ns.h" #include "privs.h" #include "nexthop_group.h" #include "lib_errors.h" /* default VRF ID value used when VRF backend is not NETNS */ #define VRF_DEFAULT_INTERNAL 0 #define VRF_DEFAULT_NAME_INTERNAL "default" DEFINE_MTYPE_STATIC(LIB, VRF, "VRF") DEFINE_MTYPE_STATIC(LIB, VRF_BITMAP, "VRF bit-map") DEFINE_QOBJ_TYPE(vrf) static __inline int vrf_id_compare(const struct vrf *, const struct vrf *); static __inline int vrf_name_compare(const struct vrf *, const struct vrf *); RB_GENERATE(vrf_id_head, vrf, id_entry, vrf_id_compare); RB_GENERATE(vrf_name_head, vrf, name_entry, vrf_name_compare); struct vrf_id_head vrfs_by_id = RB_INITIALIZER(&vrfs_by_id); struct vrf_name_head vrfs_by_name = RB_INITIALIZER(&vrfs_by_name); static int vrf_backend; static int vrf_backend_configured; static struct zebra_privs_t *vrf_daemon_privs; static char vrf_default_name[VRF_NAMSIZ] = VRF_DEFAULT_NAME_INTERNAL; /* * Turn on/off debug code * for vrf. */ static int debug_vrf = 0; /* Holding VRF hooks */ struct vrf_master { int (*vrf_new_hook)(struct vrf *); int (*vrf_delete_hook)(struct vrf *); int (*vrf_enable_hook)(struct vrf *); int (*vrf_disable_hook)(struct vrf *); int (*vrf_update_name_hook)(struct vrf *vrf); } vrf_master = { 0, }; static int vrf_is_enabled(struct vrf *vrf); /* VRF list existance check by name. */ struct vrf *vrf_lookup_by_name(const char *name) { struct vrf vrf; strlcpy(vrf.name, name, sizeof(vrf.name)); return (RB_FIND(vrf_name_head, &vrfs_by_name, &vrf)); } static __inline int vrf_id_compare(const struct vrf *a, const struct vrf *b) { return (a->vrf_id - b->vrf_id); } static int vrf_name_compare(const struct vrf *a, const struct vrf *b) { return strcmp(a->name, b->name); } /* if ns_id is different and not VRF_UNKNOWN, * then update vrf identifier, and enable VRF */ static void vrf_update_vrf_id(ns_id_t ns_id, void *opaqueptr) { ns_id_t vrf_id = (vrf_id_t)ns_id; vrf_id_t old_vrf_id; struct vrf *vrf = (struct vrf *)opaqueptr; if (!vrf) return; old_vrf_id = vrf->vrf_id; if (vrf_id == vrf->vrf_id) return; if (vrf->vrf_id != VRF_UNKNOWN) RB_REMOVE(vrf_id_head, &vrfs_by_id, vrf); vrf->vrf_id = vrf_id; RB_INSERT(vrf_id_head, &vrfs_by_id, vrf); if (old_vrf_id == VRF_UNKNOWN) vrf_enable((struct vrf *)vrf); } int vrf_switch_to_netns(vrf_id_t vrf_id) { char *name; struct vrf *vrf = vrf_lookup_by_id(vrf_id); /* VRF is default VRF. silently ignore */ if (!vrf || vrf->vrf_id == VRF_DEFAULT) return 1; /* 1 = default */ /* VRF has no NETNS backend. silently ignore */ if (vrf->data.l.netns_name[0] == '\0') return 2; /* 2 = no netns */ name = ns_netns_pathname(NULL, vrf->data.l.netns_name); if (debug_vrf) zlog_debug("VRF_SWITCH: %s(%u)", name, vrf->vrf_id); return ns_switch_to_netns(name); } int vrf_switchback_to_initial(void) { int ret = ns_switchback_to_initial(); if (ret == 0 && debug_vrf) zlog_debug("VRF_SWITCHBACK"); return ret; } /* Get a VRF. If not found, create one. * Arg: * name - The name of the vrf. May be NULL if unknown. * vrf_id - The vrf_id of the vrf. May be VRF_UNKNOWN if unknown * Description: Please note that this routine can be called with just the name * and 0 vrf-id */ struct vrf *vrf_get(vrf_id_t vrf_id, const char *name) { struct vrf *vrf = NULL; int new = 0; if (debug_vrf) zlog_debug("VRF_GET: %s(%u)", name == NULL ? "(NULL)" : name, vrf_id); /* Nothing to see, move along here */ if (!name && vrf_id == VRF_UNKNOWN) return NULL; /* attempt to find already available VRF */ if (name) vrf = vrf_lookup_by_name(name); if (vrf && vrf_id != VRF_UNKNOWN && vrf->vrf_id != VRF_UNKNOWN && vrf->vrf_id != vrf_id) { zlog_debug("VRF_GET: avoid %s creation(%u), same name exists (%u)", name, vrf_id, vrf->vrf_id); return NULL; } /* Try to find VRF both by ID and name */ if (!vrf && vrf_id != VRF_UNKNOWN) vrf = vrf_lookup_by_id(vrf_id); if (vrf == NULL) { vrf = XCALLOC(MTYPE_VRF, sizeof(struct vrf)); vrf->vrf_id = VRF_UNKNOWN; QOBJ_REG(vrf, vrf); new = 1; if (debug_vrf) zlog_debug("VRF(%u) %s is created.", vrf_id, (name) ? name : "(NULL)"); } /* Set identifier */ if (vrf_id != VRF_UNKNOWN && vrf->vrf_id == VRF_UNKNOWN) { vrf->vrf_id = vrf_id; RB_INSERT(vrf_id_head, &vrfs_by_id, vrf); } /* Set name */ if (name && vrf->name[0] != '\0' && strcmp(name, vrf->name)) { RB_REMOVE(vrf_name_head, &vrfs_by_name, vrf); strlcpy(vrf->name, name, sizeof(vrf->name)); RB_INSERT(vrf_name_head, &vrfs_by_name, vrf); } else if (name && vrf->name[0] == '\0') { strlcpy(vrf->name, name, sizeof(vrf->name)); RB_INSERT(vrf_name_head, &vrfs_by_name, vrf); } if (new &&vrf_master.vrf_new_hook) (*vrf_master.vrf_new_hook)(vrf); return vrf; } /* Delete a VRF. This is called when the underlying VRF goes away, a * pre-configured VRF is deleted or when shutting down (vrf_terminate()). */ void vrf_delete(struct vrf *vrf) { if (debug_vrf) zlog_debug("VRF %u is to be deleted.", vrf->vrf_id); if (vrf_is_enabled(vrf)) vrf_disable(vrf); /* If the VRF is user configured, it'll stick around, just remove * the ID mapping. Interfaces assigned to this VRF should've been * removed already as part of the VRF going down. */ if (vrf_is_user_cfged(vrf)) { if (vrf->vrf_id != VRF_UNKNOWN) { /* Delete any VRF interfaces - should be only * the VRF itself, other interfaces should've * been moved out of the VRF. */ if_terminate(vrf); RB_REMOVE(vrf_id_head, &vrfs_by_id, vrf); vrf->vrf_id = VRF_UNKNOWN; } return; } if (vrf_master.vrf_delete_hook) (*vrf_master.vrf_delete_hook)(vrf); QOBJ_UNREG(vrf); if_terminate(vrf); if (vrf->vrf_id != VRF_UNKNOWN) RB_REMOVE(vrf_id_head, &vrfs_by_id, vrf); if (vrf->name[0] != '\0') RB_REMOVE(vrf_name_head, &vrfs_by_name, vrf); XFREE(MTYPE_VRF, vrf); } /* Look up a VRF by identifier. */ struct vrf *vrf_lookup_by_id(vrf_id_t vrf_id) { struct vrf vrf; vrf.vrf_id = vrf_id; return (RB_FIND(vrf_id_head, &vrfs_by_id, &vrf)); } /* * Enable a VRF - that is, let the VRF be ready to use. * The VRF_ENABLE_HOOK callback will be called to inform * that they can allocate resources in this VRF. * * RETURN: 1 - enabled successfully; otherwise, 0. */ int vrf_enable(struct vrf *vrf) { if (vrf_is_enabled(vrf)) return 1; if (debug_vrf) zlog_debug("VRF %u is enabled.", vrf->vrf_id); SET_FLAG(vrf->status, VRF_ACTIVE); if (vrf_master.vrf_enable_hook) (*vrf_master.vrf_enable_hook)(vrf); /* * If we have any nexthop group entries that * are awaiting vrf initialization then * let's let people know about it */ nexthop_group_enable_vrf(vrf); return 1; } /* * Disable a VRF - that is, let the VRF be unusable. * The VRF_DELETE_HOOK callback will be called to inform * that they must release the resources in the VRF. */ void vrf_disable(struct vrf *vrf) { if (!vrf_is_enabled(vrf)) return; UNSET_FLAG(vrf->status, VRF_ACTIVE); if (debug_vrf) zlog_debug("VRF %u is to be disabled.", vrf->vrf_id); /* Till now, nothing to be done for the default VRF. */ // Pending: see why this statement. if (vrf_master.vrf_disable_hook) (*vrf_master.vrf_disable_hook)(vrf); } const char *vrf_id_to_name(vrf_id_t vrf_id) { struct vrf *vrf; vrf = vrf_lookup_by_id(vrf_id); if (vrf) return vrf->name; return "n/a"; } vrf_id_t vrf_name_to_id(const char *name) { struct vrf *vrf; vrf_id_t vrf_id = VRF_DEFAULT; // Pending: need a way to return invalid // id/ routine not used. if (!name) return vrf_id; vrf = vrf_lookup_by_name(name); if (vrf) vrf_id = vrf->vrf_id; return vrf_id; } /* Get the data pointer of the specified VRF. If not found, create one. */ void *vrf_info_get(vrf_id_t vrf_id) { struct vrf *vrf = vrf_get(vrf_id, NULL); return vrf->info; } /* Look up the data pointer of the specified VRF. */ void *vrf_info_lookup(vrf_id_t vrf_id) { struct vrf *vrf = vrf_lookup_by_id(vrf_id); return vrf ? vrf->info : NULL; } /* * VRF hash for storing set or not. */ struct vrf_bit_set { vrf_id_t vrf_id; bool set; }; static unsigned int vrf_hash_bitmap_key(const void *data) { const struct vrf_bit_set *bit = data; return bit->vrf_id; } static bool vrf_hash_bitmap_cmp(const void *a, const void *b) { const struct vrf_bit_set *bit1 = a; const struct vrf_bit_set *bit2 = b; return bit1->vrf_id == bit2->vrf_id; } static void *vrf_hash_bitmap_alloc(void *data) { struct vrf_bit_set *copy = data; struct vrf_bit_set *bit; bit = XMALLOC(MTYPE_VRF_BITMAP, sizeof(*bit)); bit->vrf_id = copy->vrf_id; return bit; } static void vrf_hash_bitmap_free(void *data) { struct vrf_bit_set *bit = data; XFREE(MTYPE_VRF_BITMAP, bit); } vrf_bitmap_t vrf_bitmap_init(void) { return hash_create_size(32, vrf_hash_bitmap_key, vrf_hash_bitmap_cmp, "VRF BIT HASH"); } void vrf_bitmap_free(vrf_bitmap_t bmap) { struct hash *vrf_hash = bmap; if (vrf_hash == NULL) return; hash_clean(vrf_hash, vrf_hash_bitmap_free); hash_free(vrf_hash); } void vrf_bitmap_set(vrf_bitmap_t bmap, vrf_id_t vrf_id) { struct vrf_bit_set lookup = { .vrf_id = vrf_id }; struct hash *vrf_hash = bmap; struct vrf_bit_set *bit; if (vrf_hash == NULL || vrf_id == VRF_UNKNOWN) return; bit = hash_get(vrf_hash, &lookup, vrf_hash_bitmap_alloc); bit->set = true; } void vrf_bitmap_unset(vrf_bitmap_t bmap, vrf_id_t vrf_id) { struct vrf_bit_set lookup = { .vrf_id = vrf_id }; struct hash *vrf_hash = bmap; struct vrf_bit_set *bit; if (vrf_hash == NULL || vrf_id == VRF_UNKNOWN) return; bit = hash_get(vrf_hash, &lookup, vrf_hash_bitmap_alloc); bit->set = false; } int vrf_bitmap_check(vrf_bitmap_t bmap, vrf_id_t vrf_id) { struct vrf_bit_set lookup = { .vrf_id = vrf_id }; struct hash *vrf_hash = bmap; struct vrf_bit_set *bit; if (vrf_hash == NULL || vrf_id == VRF_UNKNOWN) return 0; bit = hash_lookup(vrf_hash, &lookup); if (bit) return bit->set; return 0; } static void vrf_autocomplete(vector comps, struct cmd_token *token) { struct vrf *vrf = NULL; RB_FOREACH (vrf, vrf_name_head, &vrfs_by_name) vector_set(comps, XSTRDUP(MTYPE_COMPLETION, vrf->name)); } static const struct cmd_variable_handler vrf_var_handlers[] = { { .varname = "vrf", .completions = vrf_autocomplete, }, { .varname = "vrf_name", .completions = vrf_autocomplete, }, { .varname = "nexthop_vrf", .completions = vrf_autocomplete, }, {.completions = NULL}, }; /* Initialize VRF module. */ void vrf_init(int (*create)(struct vrf *), int (*enable)(struct vrf *), int (*disable)(struct vrf *), int (*destroy)(struct vrf *), int ((*update)(struct vrf *))) { struct vrf *default_vrf; /* initialise NS, in case VRF backend if NETNS */ ns_init(); if (debug_vrf) zlog_debug("%s: Initializing VRF subsystem", __PRETTY_FUNCTION__); vrf_master.vrf_new_hook = create; vrf_master.vrf_enable_hook = enable; vrf_master.vrf_disable_hook = disable; vrf_master.vrf_delete_hook = destroy; vrf_master.vrf_update_name_hook = update; /* The default VRF always exists. */ default_vrf = vrf_get(VRF_DEFAULT, VRF_DEFAULT_NAME); if (!default_vrf) { flog_err(EC_LIB_VRF_START, "vrf_init: failed to create the default VRF!"); exit(1); } if (vrf_is_backend_netns()) { struct ns *ns; strlcpy(default_vrf->data.l.netns_name, VRF_DEFAULT_NAME, NS_NAMSIZ); ns = ns_lookup(ns_get_default_id()); ns->vrf_ctxt = default_vrf; default_vrf->ns_ctxt = ns; } /* Enable the default VRF. */ if (!vrf_enable(default_vrf)) { flog_err(EC_LIB_VRF_START, "vrf_init: failed to enable the default VRF!"); exit(1); } cmd_variable_handler_register(vrf_var_handlers); } /* Terminate VRF module. */ void vrf_terminate(void) { struct vrf *vrf; if (debug_vrf) zlog_debug("%s: Shutting down vrf subsystem", __PRETTY_FUNCTION__); while (!RB_EMPTY(vrf_id_head, &vrfs_by_id)) { vrf = RB_ROOT(vrf_id_head, &vrfs_by_id); /* Clear configured flag and invoke delete. */ UNSET_FLAG(vrf->status, VRF_CONFIGURED); vrf_delete(vrf); } while (!RB_EMPTY(vrf_name_head, &vrfs_by_name)) { vrf = RB_ROOT(vrf_name_head, &vrfs_by_name); /* Clear configured flag and invoke delete. */ UNSET_FLAG(vrf->status, VRF_CONFIGURED); vrf_delete(vrf); } } /* Create a socket for the VRF. */ int vrf_socket(int domain, int type, int protocol, vrf_id_t vrf_id, const char *interfacename) { int ret, save_errno, ret2; ret = vrf_switch_to_netns(vrf_id); if (ret < 0) flog_err_sys(EC_LIB_SOCKET, "%s: Can't switch to VRF %u (%s)", __func__, vrf_id, safe_strerror(errno)); ret = socket(domain, type, protocol); save_errno = errno; ret2 = vrf_switchback_to_initial(); if (ret2 < 0) flog_err_sys(EC_LIB_SOCKET, "%s: Can't switchback from VRF %u (%s)", __func__, vrf_id, safe_strerror(errno)); errno = save_errno; if (ret <= 0) return ret; ret2 = vrf_bind(vrf_id, ret, interfacename); if (ret2 < 0) { close(ret); ret = ret2; } return ret; } int vrf_is_backend_netns(void) { return (vrf_backend == VRF_BACKEND_NETNS); } int vrf_get_backend(void) { if (!vrf_backend_configured) return VRF_BACKEND_UNKNOWN; return vrf_backend; } void vrf_configure_backend(int vrf_backend_netns) { vrf_backend = vrf_backend_netns; vrf_backend_configured = 1; } int vrf_handler_create(struct vty *vty, const char *vrfname, struct vrf **vrf) { struct vrf *vrfp; if (strlen(vrfname) > VRF_NAMSIZ) { if (vty) vty_out(vty, "%% VRF name %s invalid: length exceeds %d bytes\n", vrfname, VRF_NAMSIZ); else flog_warn( EC_LIB_VRF_LENGTH, "%% VRF name %s invalid: length exceeds %d bytes\n", vrfname, VRF_NAMSIZ); return CMD_WARNING_CONFIG_FAILED; } vrfp = vrf_get(VRF_UNKNOWN, vrfname); if (vty) VTY_PUSH_CONTEXT(VRF_NODE, vrfp); if (vrf) *vrf = vrfp; return CMD_SUCCESS; } int vrf_netns_handler_create(struct vty *vty, struct vrf *vrf, char *pathname, ns_id_t ns_id, ns_id_t internal_ns_id) { struct ns *ns = NULL; if (!vrf) return CMD_WARNING_CONFIG_FAILED; if (vrf->vrf_id != VRF_UNKNOWN && vrf->ns_ctxt == NULL) { if (vty) vty_out(vty, "VRF %u is already configured with VRF %s\n", vrf->vrf_id, vrf->name); else zlog_info("VRF %u is already configured with VRF %s", vrf->vrf_id, vrf->name); return CMD_WARNING_CONFIG_FAILED; } if (vrf->ns_ctxt != NULL) { ns = (struct ns *)vrf->ns_ctxt; if (!strcmp(ns->name, pathname)) { if (vty) vty_out(vty, "VRF %u already configured with NETNS %s\n", vrf->vrf_id, ns->name); else zlog_info( "VRF %u already configured with NETNS %s", vrf->vrf_id, ns->name); return CMD_WARNING_CONFIG_FAILED; } } ns = ns_lookup_name(pathname); if (ns && ns->vrf_ctxt) { struct vrf *vrf2 = (struct vrf *)ns->vrf_ctxt; if (vrf2 == vrf) return CMD_SUCCESS; if (vty) vty_out(vty, "NS %s is already configured" " with VRF %u(%s)\n", ns->name, vrf2->vrf_id, vrf2->name); else zlog_info("NS %s is already configured with VRF %u(%s)", ns->name, vrf2->vrf_id, vrf2->name); return CMD_WARNING_CONFIG_FAILED; } ns = ns_get_created(ns, pathname, ns_id); ns->internal_ns_id = internal_ns_id; ns->vrf_ctxt = (void *)vrf; vrf->ns_ctxt = (void *)ns; /* update VRF netns NAME */ strlcpy(vrf->data.l.netns_name, basename(pathname), NS_NAMSIZ); if (!ns_enable(ns, vrf_update_vrf_id)) { if (vty) vty_out(vty, "Can not associate NS %u with NETNS %s\n", ns->ns_id, ns->name); else zlog_info("Can not associate NS %u with NETNS %s", ns->ns_id, ns->name); return CMD_WARNING_CONFIG_FAILED; } return CMD_SUCCESS; } /* vrf CLI commands */ DEFUN_NOSH(vrf_exit, vrf_exit_cmd, "exit-vrf", "Exit current mode and down to previous mode\n") { /* We have to set vrf context to default vrf */ VTY_PUSH_CONTEXT(VRF_NODE, vrf_get(VRF_DEFAULT, VRF_DEFAULT_NAME)); vty->node = CONFIG_NODE; return CMD_SUCCESS; } DEFUN_NOSH (vrf, vrf_cmd, "vrf NAME", "Select a VRF to configure\n" "VRF's name\n") { int idx_name = 1; const char *vrfname = argv[idx_name]->arg; return vrf_handler_create(vty, vrfname, NULL); } DEFUN (no_vrf, no_vrf_cmd, "no vrf NAME", NO_STR "Delete a pseudo VRF's configuration\n" "VRF's name\n") { const char *vrfname = argv[2]->arg; struct vrf *vrfp; vrfp = vrf_lookup_by_name(vrfname); if (vrfp == NULL) { vty_out(vty, "%% VRF %s does not exist\n", vrfname); return CMD_WARNING_CONFIG_FAILED; } if (CHECK_FLAG(vrfp->status, VRF_ACTIVE)) { vty_out(vty, "%% Only inactive VRFs can be deleted\n"); return CMD_WARNING_CONFIG_FAILED; } /* Clear configured flag and invoke delete. */ UNSET_FLAG(vrfp->status, VRF_CONFIGURED); vrf_delete(vrfp); return CMD_SUCCESS; } struct cmd_node vrf_node = {VRF_NODE, "%s(config-vrf)# ", 1}; DEFUN_NOSH (vrf_netns, vrf_netns_cmd, "netns NAME", "Attach VRF to a Namespace\n" "The file name in " NS_RUN_DIR ", or a full pathname\n") { int idx_name = 1, ret; char *pathname = ns_netns_pathname(vty, argv[idx_name]->arg); VTY_DECLVAR_CONTEXT(vrf, vrf); if (!pathname) return CMD_WARNING_CONFIG_FAILED; frr_with_privs(vrf_daemon_privs) { ret = vrf_netns_handler_create(vty, vrf, pathname, NS_UNKNOWN, NS_UNKNOWN); } return ret; } DEFUN_NOSH (no_vrf_netns, no_vrf_netns_cmd, "no netns [NAME]", NO_STR "Detach VRF from a Namespace\n" "The file name in " NS_RUN_DIR ", or a full pathname\n") { struct ns *ns = NULL; VTY_DECLVAR_CONTEXT(vrf, vrf); if (!vrf_is_backend_netns()) { vty_out(vty, "VRF backend is not Netns. Aborting\n"); return CMD_WARNING_CONFIG_FAILED; } if (!vrf->ns_ctxt) { vty_out(vty, "VRF %s(%u) is not configured with NetNS\n", vrf->name, vrf->vrf_id); return CMD_WARNING_CONFIG_FAILED; } ns = (struct ns *)vrf->ns_ctxt; ns->vrf_ctxt = NULL; vrf_disable(vrf); /* vrf ID from VRF is necessary for Zebra * so that propagate to other clients is done */ ns_delete(ns); vrf->ns_ctxt = NULL; return CMD_SUCCESS; } /* * Debug CLI for vrf's */ DEFUN (vrf_debug, vrf_debug_cmd, "debug vrf", DEBUG_STR "VRF Debugging\n") { debug_vrf = 1; return CMD_SUCCESS; } DEFUN (no_vrf_debug, no_vrf_debug_cmd, "no debug vrf", NO_STR DEBUG_STR "VRF Debugging\n") { debug_vrf = 0; return CMD_SUCCESS; } static int vrf_write_host(struct vty *vty) { if (debug_vrf) vty_out(vty, "debug vrf\n"); return 1; } static struct cmd_node vrf_debug_node = {VRF_DEBUG_NODE, "", 1}; void vrf_install_commands(void) { install_node(&vrf_debug_node, vrf_write_host); install_element(CONFIG_NODE, &vrf_debug_cmd); install_element(ENABLE_NODE, &vrf_debug_cmd); install_element(CONFIG_NODE, &no_vrf_debug_cmd); install_element(ENABLE_NODE, &no_vrf_debug_cmd); } void vrf_cmd_init(int (*writefunc)(struct vty *vty), struct zebra_privs_t *daemon_privs) { install_element(CONFIG_NODE, &vrf_cmd); install_element(CONFIG_NODE, &no_vrf_cmd); install_node(&vrf_node, writefunc); install_default(VRF_NODE); install_element(VRF_NODE, &vrf_exit_cmd); if (vrf_is_backend_netns() && ns_have_netns()) { /* Install NS commands. */ vrf_daemon_privs = daemon_privs; install_element(VRF_NODE, &vrf_netns_cmd); install_element(VRF_NODE, &no_vrf_netns_cmd); } } void vrf_set_default_name(const char *default_name, bool force) { struct vrf *def_vrf; static bool def_vrf_forced; def_vrf = vrf_lookup_by_id(VRF_DEFAULT); assert(default_name); if (def_vrf && !force && def_vrf_forced) { zlog_debug("VRF: %s, avoid changing name to %s, previously forced (%u)", def_vrf->name, default_name, def_vrf->vrf_id); return; } snprintf(vrf_default_name, VRF_NAMSIZ, "%s", default_name); if (def_vrf) { if (force) def_vrf_forced = true; RB_REMOVE(vrf_name_head, &vrfs_by_name, def_vrf); strlcpy(def_vrf->data.l.netns_name, vrf_default_name, NS_NAMSIZ); strlcpy(def_vrf->name, vrf_default_name, sizeof(def_vrf->name)); RB_INSERT(vrf_name_head, &vrfs_by_name, def_vrf); if (vrf_master.vrf_update_name_hook) (*vrf_master.vrf_update_name_hook)(def_vrf); } } const char *vrf_get_default_name(void) { return vrf_default_name; } vrf_id_t vrf_get_default_id(void) { /* backend netns is only known by zebra * for other daemons, we return VRF_DEFAULT_INTERNAL */ if (vrf_is_backend_netns()) return ns_get_default_id(); else return VRF_DEFAULT_INTERNAL; } int vrf_bind(vrf_id_t vrf_id, int fd, const char *name) { int ret = 0; struct interface *ifp; if (fd < 0 || name == NULL) return fd; /* the device should exist * otherwise we should return * case ifname = vrf in netns mode => return */ ifp = if_lookup_by_name(name, vrf_id); if (!ifp) return fd; #ifdef SO_BINDTODEVICE ret = setsockopt(fd, SOL_SOCKET, SO_BINDTODEVICE, name, strlen(name)+1); if (ret < 0) zlog_debug("bind to interface %s failed, errno=%d", name, errno); #endif /* SO_BINDTODEVICE */ return ret; } int vrf_getaddrinfo(const char *node, const char *service, const struct addrinfo *hints, struct addrinfo **res, vrf_id_t vrf_id) { int ret, ret2, save_errno; ret = vrf_switch_to_netns(vrf_id); if (ret < 0) flog_err_sys(EC_LIB_SOCKET, "%s: Can't switch to VRF %u (%s)", __func__, vrf_id, safe_strerror(errno)); ret = getaddrinfo(node, service, hints, res); save_errno = errno; ret2 = vrf_switchback_to_initial(); if (ret2 < 0) flog_err_sys(EC_LIB_SOCKET, "%s: Can't switchback from VRF %u (%s)", __func__, vrf_id, safe_strerror(errno)); errno = save_errno; return ret; } int vrf_ioctl(vrf_id_t vrf_id, int d, unsigned long request, char *params) { int ret, saved_errno, rc; ret = vrf_switch_to_netns(vrf_id); if (ret < 0) { flog_err_sys(EC_LIB_SOCKET, "%s: Can't switch to VRF %u (%s)", __func__, vrf_id, safe_strerror(errno)); return 0; } rc = ioctl(d, request, params); saved_errno = errno; ret = vrf_switchback_to_initial(); if (ret < 0) flog_err_sys(EC_LIB_SOCKET, "%s: Can't switchback from VRF %u (%s)", __func__, vrf_id, safe_strerror(errno)); errno = saved_errno; return rc; } int vrf_sockunion_socket(const union sockunion *su, vrf_id_t vrf_id, const char *interfacename) { int ret, save_errno, ret2; ret = vrf_switch_to_netns(vrf_id); if (ret < 0) flog_err_sys(EC_LIB_SOCKET, "%s: Can't switch to VRF %u (%s)", __func__, vrf_id, safe_strerror(errno)); ret = sockunion_socket(su); save_errno = errno; ret2 = vrf_switchback_to_initial(); if (ret2 < 0) flog_err_sys(EC_LIB_SOCKET, "%s: Can't switchback from VRF %u (%s)", __func__, vrf_id, safe_strerror(errno)); errno = save_errno; if (ret <= 0) return ret; ret2 = vrf_bind(vrf_id, ret, interfacename); if (ret2 < 0) { close(ret); ret = ret2; } return ret; } vrf_id_t vrf_generate_id(void) { static int vrf_id_local; return ++vrf_id_local; } frr-7.2.1/lib/vrf.h0000644000000000000000000002121513610377563010731 00000000000000/* * VRF related header. * Copyright (C) 2014 6WIND S.A. * * This file is part of GNU Zebra. * * GNU Zebra is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License as published * by the Free Software Foundation; either version 2, or (at your * option) any later version. * * GNU Zebra 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; see the file COPYING; if not, write to the Free Software * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA */ #ifndef _ZEBRA_VRF_H #define _ZEBRA_VRF_H #include "openbsd-tree.h" #include "linklist.h" #include "qobj.h" #include "vty.h" #include "ns.h" #ifdef __cplusplus extern "C" { #endif /* The default VRF ID */ #define VRF_UNKNOWN UINT32_MAX /* Pending: May need to refine this. */ #ifndef IFLA_VRF_MAX enum { IFLA_VRF_UNSPEC, IFLA_VRF_TABLE, __IFLA_VRF_MAX }; #define IFLA_VRF_MAX (__IFLA_VRF_MAX - 1) #endif #define VRF_NAMSIZ 36 #define NS_NAMSIZ 16 /* * The command strings */ #define VRF_CMD_HELP_STR "Specify the VRF\nThe VRF name\n" #define VRF_ALL_CMD_HELP_STR "Specify the VRF\nAll VRFs\n" #define VRF_FULL_CMD_HELP_STR "Specify the VRF\nThe VRF name\nAll VRFs\n" /* * Pass some OS specific data up through * to the daemons */ struct vrf_data { union { struct { uint32_t table_id; char netns_name[NS_NAMSIZ]; } l; }; }; struct vrf { RB_ENTRY(vrf) id_entry, name_entry; /* Identifier, same as the vector index */ vrf_id_t vrf_id; /* Name */ char name[VRF_NAMSIZ + 1]; /* Zebra internal VRF status */ uint8_t status; #define VRF_ACTIVE (1 << 0) /* VRF is up in kernel */ #define VRF_CONFIGURED (1 << 1) /* VRF has some FRR configuration */ /* Interfaces belonging to this VRF */ struct if_name_head ifaces_by_name; struct if_index_head ifaces_by_index; /* User data */ void *info; /* The table_id from the kernel */ struct vrf_data data; /* Back pointer to namespace context */ void *ns_ctxt; QOBJ_FIELDS }; RB_HEAD(vrf_id_head, vrf); RB_PROTOTYPE(vrf_id_head, vrf, id_entry, vrf_id_compare) RB_HEAD(vrf_name_head, vrf); RB_PROTOTYPE(vrf_name_head, vrf, name_entry, vrf_name_compare) DECLARE_QOBJ_TYPE(vrf) /* Allow VRF with netns as backend */ #define VRF_BACKEND_VRF_LITE 0 #define VRF_BACKEND_NETNS 1 #define VRF_BACKEND_UNKNOWN 2 extern struct vrf_id_head vrfs_by_id; extern struct vrf_name_head vrfs_by_name; extern struct vrf *vrf_lookup_by_id(vrf_id_t); extern struct vrf *vrf_lookup_by_name(const char *); extern struct vrf *vrf_get(vrf_id_t, const char *); extern const char *vrf_id_to_name(vrf_id_t vrf_id); extern vrf_id_t vrf_name_to_id(const char *); #define VRF_GET_ID(V, NAME, USE_JSON) \ do { \ struct vrf *_vrf; \ if (!(_vrf = vrf_lookup_by_name(NAME))) { \ if (USE_JSON) { \ vty_out(vty, "{}\n"); \ } else { \ vty_out(vty, "%% VRF %s not found\n", NAME); \ } \ return CMD_WARNING; \ } \ if (_vrf->vrf_id == VRF_UNKNOWN) { \ if (USE_JSON) { \ vty_out(vty, "{}\n"); \ } else { \ vty_out(vty, "%% VRF %s not active\n", NAME); \ } \ return CMD_WARNING; \ } \ (V) = _vrf->vrf_id; \ } while (0) /* * Check whether the VRF is enabled. */ static inline int vrf_is_enabled(struct vrf *vrf) { return vrf && CHECK_FLAG(vrf->status, VRF_ACTIVE); } /* check if the vrf is user configured */ static inline int vrf_is_user_cfged(struct vrf *vrf) { return vrf && CHECK_FLAG(vrf->status, VRF_CONFIGURED); } /* Mark that VRF has user configuration */ static inline void vrf_set_user_cfged(struct vrf *vrf) { SET_FLAG(vrf->status, VRF_CONFIGURED); } /* Mark that VRF no longer has any user configuration */ static inline void vrf_reset_user_cfged(struct vrf *vrf) { UNSET_FLAG(vrf->status, VRF_CONFIGURED); } /* * Utilities to obtain the user data */ /* Get the data pointer of the specified VRF. If not found, create one. */ extern void *vrf_info_get(vrf_id_t); /* Look up the data pointer of the specified VRF. */ extern void *vrf_info_lookup(vrf_id_t); /* * VRF bit-map: maintaining flags, one bit per VRF ID */ typedef void *vrf_bitmap_t; #define VRF_BITMAP_NULL NULL extern vrf_bitmap_t vrf_bitmap_init(void); extern void vrf_bitmap_free(vrf_bitmap_t); extern void vrf_bitmap_set(vrf_bitmap_t, vrf_id_t); extern void vrf_bitmap_unset(vrf_bitmap_t, vrf_id_t); extern int vrf_bitmap_check(vrf_bitmap_t, vrf_id_t); /* * VRF initializer/destructor * * create -> Called back when a new VRF is created. This * can be either through these 3 options: * 1) CLI mentions a vrf before OS knows about it * 2) OS calls zebra and we create the vrf from OS * callback * 3) zebra calls individual protocols to notify * about the new vrf * * enable -> Called back when a VRF is actually usable from * an OS perspective ( 2 and 3 above ) * * disable -> Called back when a VRF is being deleted from * the system ( 2 and 3 ) above * * delete -> Called back when a vrf is being deleted from * the system ( 2 and 3 ) above. */ extern void vrf_init(int (*create)(struct vrf *vrf), int (*enable)(struct vrf *vrf), int (*disable)(struct vrf *vrf), int (*destroy)(struct vrf *vrf), int (*update)(struct vrf *vrf)); /* * Call vrf_terminate when the protocol is being shutdown */ extern void vrf_terminate(void); /* * Utilities to create networks objects, * or call network operations */ /* Create a socket serving for the given VRF */ extern int vrf_socket(int domain, int type, int protocol, vrf_id_t vrf_id, const char *name); extern int vrf_sockunion_socket(const union sockunion *su, vrf_id_t vrf_id, const char *name); extern int vrf_bind(vrf_id_t vrf_id, int fd, const char *name); /* VRF ioctl operations */ extern int vrf_getaddrinfo(const char *node, const char *service, const struct addrinfo *hints, struct addrinfo **res, vrf_id_t vrf_id); extern int vrf_ioctl(vrf_id_t vrf_id, int d, unsigned long request, char *args); /* function called by macro VRF_DEFAULT * to get the default VRF_ID */ extern vrf_id_t vrf_get_default_id(void); /* The default VRF ID */ #define VRF_DEFAULT vrf_get_default_id() extern void vrf_set_default_name(const char *default_name, bool force); extern const char *vrf_get_default_name(void); #define VRF_DEFAULT_NAME vrf_get_default_name() /* VRF switch from NETNS */ extern int vrf_switch_to_netns(vrf_id_t vrf_id); extern int vrf_switchback_to_initial(void); /* * VRF backend routines * should be called from zebra only */ /* VRF vty command initialisation */ extern void vrf_cmd_init(int (*writefunc)(struct vty *vty), struct zebra_privs_t *daemon_priv); /* VRF vty debugging */ extern void vrf_install_commands(void); /* * VRF utilities */ /* API for configuring VRF backend * should be called from zebra only */ extern void vrf_configure_backend(int vrf_backend_netns); extern int vrf_get_backend(void); extern int vrf_is_backend_netns(void); /* API to create a VRF. either from vty * or through discovery */ extern int vrf_handler_create(struct vty *vty, const char *name, struct vrf **vrf); /* API to associate a VRF with a NETNS. * called either from vty or through discovery * should be called from zebra only */ extern int vrf_netns_handler_create(struct vty *vty, struct vrf *vrf, char *pathname, ns_id_t ext_ns_id, ns_id_t ns_id); /* used internally to enable or disable VRF. * Notify a change in the VRF ID of the VRF */ extern void vrf_disable(struct vrf *vrf); extern int vrf_enable(struct vrf *vrf); extern void vrf_delete(struct vrf *vrf); extern vrf_id_t vrf_generate_id(void); #ifdef __cplusplus } #endif #endif /*_ZEBRA_VRF_H*/ frr-7.2.1/lib/vrf_int.h0000644000000000000000000000323413610377563011604 00000000000000/* * VRF Internal Header * Copyright (C) 2017 Cumulus Networks, Inc. * Donald Sharp * * This file is part of FRR. * * FRR is free software; you can redistribute it and/or modify it * under the terms of the GNU General Public License as published by the * Free Software Foundation; either version 2, or (at your option) any * later version. * * FRR 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 FRR; see the file COPYING. If not, write to the Free * Software Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA * 02111-1307, USA. */ #ifndef __LIB_VRF_PRIVATE_H__ #define __LIB_VRF_PRIVATE_H__ #include "vrf.h" #ifdef __cplusplus extern "C" { #endif /* * These functions should only be called by: * zebra/if_netlink.c -> The interface from OS into Zebra * lib/zclient.c -> The interface from Zebra to each daemon * * Why you ask? Well because these are the turn on/off * functions and the only place we can really turn a * vrf on properly is in the call up from the os -> zebra * and the pass through of this informatoin from zebra -> protocols */ /* * vrf_enable * * Given a newly running vrf enable it to be used * by interested routing protocols */ extern int vrf_enable(struct vrf *); /* * vrf_delete * * Given a vrf that is being deleted, delete it * from interested parties */ extern void vrf_delete(struct vrf *); #ifdef __cplusplus } #endif #endif frr-7.2.1/lib/vty.c0000644000000000000000000021232013610377563010750 00000000000000/* * Virtual terminal [aka TeletYpe] interface routine. * Copyright (C) 1997, 98 Kunihiro Ishiguro * * This file is part of GNU Zebra. * * GNU Zebra is free software; you can redistribute it and/or modify it * under the terms of the GNU General Public License as published by the * Free Software Foundation; either version 2, or (at your option) any * later version. * * GNU Zebra 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; see the file COPYING; if not, write to the Free Software * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA */ #include #include #include #include #include #include "linklist.h" #include "thread.h" #include "buffer.h" #include "command.h" #include "sockunion.h" #include "memory.h" #include "log.h" #include "prefix.h" #include "filter.h" #include "vty.h" #include "privs.h" #include "network.h" #include "libfrr.h" #include "frrstr.h" #include "lib_errors.h" #include "northbound_cli.h" #include "printfrr.h" #include #include #ifndef VTYSH_EXTRACT_PL #include "lib/vty_clippy.c" #endif DEFINE_MTYPE_STATIC(LIB, VTY, "VTY") DEFINE_MTYPE_STATIC(LIB, VTY_OUT_BUF, "VTY output buffer") DEFINE_MTYPE_STATIC(LIB, VTY_HIST, "VTY history") /* Vty events */ enum event { VTY_SERV, VTY_READ, VTY_WRITE, VTY_TIMEOUT_RESET, #ifdef VTYSH VTYSH_SERV, VTYSH_READ, VTYSH_WRITE #endif /* VTYSH */ }; static void vty_event(enum event, int, struct vty *); /* Extern host structure from command.c */ extern struct host host; /* Vector which store each vty structure. */ static vector vtyvec; /* Vty timeout value. */ static unsigned long vty_timeout_val = VTY_TIMEOUT_DEFAULT; /* Vty access-class command */ static char *vty_accesslist_name = NULL; /* Vty access-calss for IPv6. */ static char *vty_ipv6_accesslist_name = NULL; /* VTY server thread. */ static vector Vvty_serv_thread; /* Current directory. */ char vty_cwd[MAXPATHLEN]; /* Login password check. */ static int no_password_check = 0; /* Integrated configuration file path */ static char integrate_default[] = SYSCONFDIR INTEGRATE_DEFAULT_CONFIG; static bool do_log_commands; static bool do_log_commands_perm; void vty_frame(struct vty *vty, const char *format, ...) { va_list args; va_start(args, format); vsnprintfrr(vty->frame + vty->frame_pos, sizeof(vty->frame) - vty->frame_pos, format, args); vty->frame_pos = strlen(vty->frame); va_end(args); } void vty_endframe(struct vty *vty, const char *endtext) { if (vty->frame_pos == 0 && endtext) vty_out(vty, "%s", endtext); vty->frame_pos = 0; } bool vty_set_include(struct vty *vty, const char *regexp) { int errcode; bool ret = true; char errbuf[256]; if (!regexp) { if (vty->filter) { regfree(&vty->include); vty->filter = false; } return true; } errcode = regcomp(&vty->include, regexp, REG_EXTENDED | REG_NEWLINE | REG_NOSUB); if (errcode) { ret = false; regerror(ret, &vty->include, errbuf, sizeof(errbuf)); vty_out(vty, "%% Regex compilation error: %s", errbuf); } else { vty->filter = true; } return ret; } /* VTY standard output function. */ int vty_out(struct vty *vty, const char *format, ...) { va_list args; ssize_t len; char buf[1024]; char *p = NULL; char *filtered; if (vty->frame_pos) { vty->frame_pos = 0; vty_out(vty, "%s", vty->frame); } va_start(args, format); p = vasnprintfrr(MTYPE_VTY_OUT_BUF, buf, sizeof(buf), format, args); va_end(args); len = strlen(p); /* filter buffer */ if (vty->filter) { vector lines = frrstr_split_vec(p, "\n"); /* Place first value in the cache */ char *firstline = vector_slot(lines, 0); buffer_put(vty->lbuf, (uint8_t *) firstline, strlen(firstline)); /* If our split returned more than one entry, time to filter */ if (vector_active(lines) > 1) { /* * returned string is MTYPE_TMP so it matches the MTYPE * of everything else in the vector */ char *bstr = buffer_getstr(vty->lbuf); buffer_reset(vty->lbuf); XFREE(MTYPE_TMP, lines->index[0]); vector_set_index(lines, 0, bstr); frrstr_filter_vec(lines, &vty->include); vector_compact(lines); /* * Consider the string "foo\n". If the regex is an empty string * and the line ended with a newline, then the vector will look * like: * * [0]: 'foo' * [1]: '' * * If the regex isn't empty, the vector will look like: * * [0]: 'foo' * * In this case we'd like to preserve the newline, so we add * the empty string [1] as in the first example. */ if (p[strlen(p) - 1] == '\n' && vector_active(lines) > 0 && strlen(vector_slot(lines, vector_active(lines) - 1))) vector_set(lines, XSTRDUP(MTYPE_TMP, "")); filtered = frrstr_join_vec(lines, "\n"); } else { filtered = NULL; } frrstr_strvec_free(lines); } else { filtered = p; } if (!filtered) goto done; switch (vty->type) { case VTY_TERM: /* print with crlf replacement */ buffer_put_crlf(vty->obuf, (uint8_t *)filtered, strlen(filtered)); break; case VTY_SHELL: fprintf(vty->of, "%s", filtered); fflush(vty->of); break; case VTY_SHELL_SERV: case VTY_FILE: default: /* print without crlf replacement */ buffer_put(vty->obuf, (uint8_t *)filtered, strlen(filtered)); break; } done: if (vty->filter && filtered) XFREE(MTYPE_TMP, filtered); /* If p is not different with buf, it is allocated buffer. */ if (p != buf) XFREE(MTYPE_VTY_OUT_BUF, p); return len; } static int vty_log_out(struct vty *vty, const char *level, const char *proto_str, const char *msg, struct timestamp_control *ctl) { int ret; int len; char buf[1024]; if (!ctl->already_rendered) { ctl->len = quagga_timestamp(ctl->precision, ctl->buf, sizeof(ctl->buf)); ctl->already_rendered = 1; } if (ctl->len + 1 >= sizeof(buf)) return -1; memcpy(buf, ctl->buf, len = ctl->len); buf[len++] = ' '; buf[len] = '\0'; if (level) ret = snprintf(buf + len, sizeof(buf) - len, "%s: %s: ", level, proto_str); else ret = snprintf(buf + len, sizeof(buf) - len, "%s: ", proto_str); if ((ret < 0) || ((size_t)(len += ret) >= sizeof(buf))) return -1; if (((ret = snprintf(buf + len, sizeof(buf) - len, "%s", msg)) < 0) || ((size_t)((len += ret) + 2) > sizeof(buf))) return -1; buf[len++] = '\r'; buf[len++] = '\n'; if (write(vty->wfd, buf, len) < 0) { if (ERRNO_IO_RETRY(errno)) /* Kernel buffer is full, probably too much debugging output, so just drop the data and ignore. */ return -1; /* Fatal I/O error. */ vty->monitor = 0; /* disable monitoring to avoid infinite recursion */ flog_err(EC_LIB_SOCKET, "%s: write failed to vty client fd %d, closing: %s", __func__, vty->fd, safe_strerror(errno)); buffer_reset(vty->obuf); buffer_reset(vty->lbuf); /* cannot call vty_close, because a parent routine may still try to access the vty struct */ vty->status = VTY_CLOSE; shutdown(vty->fd, SHUT_RDWR); return -1; } return 0; } /* Output current time to the vty. */ void vty_time_print(struct vty *vty, int cr) { char buf[QUAGGA_TIMESTAMP_LEN]; if (quagga_timestamp(0, buf, sizeof(buf)) == 0) { zlog_info("quagga_timestamp error"); return; } if (cr) vty_out(vty, "%s\n", buf); else vty_out(vty, "%s ", buf); return; } /* Say hello to vty interface. */ void vty_hello(struct vty *vty) { if (host.motdfile) { FILE *f; char buf[4096]; f = fopen(host.motdfile, "r"); if (f) { while (fgets(buf, sizeof(buf), f)) { char *s; /* work backwards to ignore trailling isspace() */ for (s = buf + strlen(buf); (s > buf) && isspace((unsigned char)s[-1]); s--) ; *s = '\0'; vty_out(vty, "%s\n", buf); } fclose(f); } else vty_out(vty, "MOTD file not found\n"); } else if (host.motd) vty_out(vty, "%s", host.motd); #if CONFDATE > 20200901 CPP_NOTICE("Please remove solaris code from system as it is deprecated"); #endif #ifdef SUNOS_5 zlog_warn("If you are using FRR on Solaris, the FRR developers would love to hear from you\n"); zlog_warn("Please send email to dev@lists.frrouting.org about this message\n"); zlog_warn("We are considering deprecating Solaris and want to find users of Solaris systems\n"); #endif } /* Put out prompt and wait input from user. */ static void vty_prompt(struct vty *vty) { if (vty->type == VTY_TERM) { vty_out(vty, cmd_prompt(vty->node), cmd_hostname_get()); } } /* Send WILL TELOPT_ECHO to remote server. */ static void vty_will_echo(struct vty *vty) { unsigned char cmd[] = {IAC, WILL, TELOPT_ECHO, '\0'}; vty_out(vty, "%s", cmd); } /* Make suppress Go-Ahead telnet option. */ static void vty_will_suppress_go_ahead(struct vty *vty) { unsigned char cmd[] = {IAC, WILL, TELOPT_SGA, '\0'}; vty_out(vty, "%s", cmd); } /* Make don't use linemode over telnet. */ static void vty_dont_linemode(struct vty *vty) { unsigned char cmd[] = {IAC, DONT, TELOPT_LINEMODE, '\0'}; vty_out(vty, "%s", cmd); } /* Use window size. */ static void vty_do_window_size(struct vty *vty) { unsigned char cmd[] = {IAC, DO, TELOPT_NAWS, '\0'}; vty_out(vty, "%s", cmd); } #if 0 /* Currently not used. */ /* Make don't use lflow vty interface. */ static void vty_dont_lflow_ahead (struct vty *vty) { unsigned char cmd[] = { IAC, DONT, TELOPT_LFLOW, '\0' }; vty_out (vty, "%s", cmd); } #endif /* 0 */ /* Authentication of vty */ static void vty_auth(struct vty *vty, char *buf) { char *passwd = NULL; enum node_type next_node = 0; int fail; char *crypt(const char *, const char *); switch (vty->node) { case AUTH_NODE: if (host.encrypt) passwd = host.password_encrypt; else passwd = host.password; if (host.advanced) next_node = host.enable ? VIEW_NODE : ENABLE_NODE; else next_node = VIEW_NODE; break; case AUTH_ENABLE_NODE: if (host.encrypt) passwd = host.enable_encrypt; else passwd = host.enable; next_node = ENABLE_NODE; break; } if (passwd) { if (host.encrypt) fail = strcmp(crypt(buf, passwd), passwd); else fail = strcmp(buf, passwd); } else fail = 1; if (!fail) { vty->fail = 0; vty->node = next_node; /* Success ! */ } else { vty->fail++; if (vty->fail >= 3) { if (vty->node == AUTH_NODE) { vty_out(vty, "%% Bad passwords, too many failures!\n"); vty->status = VTY_CLOSE; } else { /* AUTH_ENABLE_NODE */ vty->fail = 0; vty_out(vty, "%% Bad enable passwords, too many failures!\n"); vty->status = VTY_CLOSE; } } } } /* Command execution over the vty interface. */ static int vty_command(struct vty *vty, char *buf) { int ret; const char *protocolname; char *cp = NULL; assert(vty); /* * Log non empty command lines */ if (do_log_commands) cp = buf; if (cp != NULL) { /* Skip white spaces. */ while (isspace((unsigned char)*cp) && *cp != '\0') cp++; } if (cp != NULL && *cp != '\0') { unsigned i; char vty_str[VTY_BUFSIZ]; char prompt_str[VTY_BUFSIZ]; /* format the base vty info */ snprintf(vty_str, sizeof(vty_str), "vty[??]@%s", vty->address); for (i = 0; i < vector_active(vtyvec); i++) if (vty == vector_slot(vtyvec, i)) { snprintf(vty_str, sizeof(vty_str), "vty[%d]@%s", i, vty->address); break; } /* format the prompt */ snprintf(prompt_str, sizeof(prompt_str), cmd_prompt(vty->node), vty_str); /* now log the command */ zlog_notice("%s%s", prompt_str, buf); } #ifdef CONSUMED_TIME_CHECK { RUSAGE_T before; RUSAGE_T after; unsigned long realtime, cputime; GETRUSAGE(&before); #endif /* CONSUMED_TIME_CHECK */ ret = cmd_execute(vty, buf, NULL, 0); /* Get the name of the protocol if any */ protocolname = frr_protoname; #ifdef CONSUMED_TIME_CHECK GETRUSAGE(&after); if ((realtime = thread_consumed_time(&after, &before, &cputime)) > CONSUMED_TIME_CHECK) /* Warn about CPU hog that must be fixed. */ flog_warn( EC_LIB_SLOW_THREAD, "SLOW COMMAND: command took %lums (cpu time %lums): %s", realtime / 1000, cputime / 1000, buf); } #endif /* CONSUMED_TIME_CHECK */ if (ret != CMD_SUCCESS) switch (ret) { case CMD_WARNING: if (vty->type == VTY_FILE) vty_out(vty, "Warning...\n"); break; case CMD_ERR_AMBIGUOUS: vty_out(vty, "%% Ambiguous command.\n"); break; case CMD_ERR_NO_MATCH: vty_out(vty, "%% [%s] Unknown command: %s\n", protocolname, buf); break; case CMD_ERR_INCOMPLETE: vty_out(vty, "%% Command incomplete.\n"); break; } return ret; } static const char telnet_backward_char = 0x08; static const char telnet_space_char = ' '; /* Basic function to write buffer to vty. */ static void vty_write(struct vty *vty, const char *buf, size_t nbytes) { if ((vty->node == AUTH_NODE) || (vty->node == AUTH_ENABLE_NODE)) return; /* Should we do buffering here ? And make vty_flush (vty) ? */ buffer_put(vty->obuf, buf, nbytes); } /* Basic function to insert character into vty. */ static void vty_self_insert(struct vty *vty, char c) { int i; int length; if (vty->length + 1 >= VTY_BUFSIZ) return; length = vty->length - vty->cp; memmove(&vty->buf[vty->cp + 1], &vty->buf[vty->cp], length); vty->buf[vty->cp] = c; vty_write(vty, &vty->buf[vty->cp], length + 1); for (i = 0; i < length; i++) vty_write(vty, &telnet_backward_char, 1); vty->cp++; vty->length++; vty->buf[vty->length] = '\0'; } /* Self insert character 'c' in overwrite mode. */ static void vty_self_insert_overwrite(struct vty *vty, char c) { if (vty->cp == vty->length) { vty_self_insert(vty, c); return; } vty->buf[vty->cp++] = c; vty_write(vty, &c, 1); } /** * Insert a string into vty->buf at the current cursor position. * * If the resultant string would be larger than VTY_BUFSIZ it is * truncated to fit. */ static void vty_insert_word_overwrite(struct vty *vty, char *str) { if (vty->cp == VTY_BUFSIZ) return; size_t nwrite = MIN((int)strlen(str), VTY_BUFSIZ - vty->cp - 1); memcpy(&vty->buf[vty->cp], str, nwrite); vty->cp += nwrite; vty->length = MAX(vty->cp, vty->length); vty->buf[vty->length] = '\0'; vty_write(vty, str, nwrite); } /* Forward character. */ static void vty_forward_char(struct vty *vty) { if (vty->cp < vty->length) { vty_write(vty, &vty->buf[vty->cp], 1); vty->cp++; } } /* Backward character. */ static void vty_backward_char(struct vty *vty) { if (vty->cp > 0) { vty->cp--; vty_write(vty, &telnet_backward_char, 1); } } /* Move to the beginning of the line. */ static void vty_beginning_of_line(struct vty *vty) { while (vty->cp) vty_backward_char(vty); } /* Move to the end of the line. */ static void vty_end_of_line(struct vty *vty) { while (vty->cp < vty->length) vty_forward_char(vty); } static void vty_kill_line_from_beginning(struct vty *); static void vty_redraw_line(struct vty *); /* Print command line history. This function is called from vty_next_line and vty_previous_line. */ static void vty_history_print(struct vty *vty) { int length; vty_kill_line_from_beginning(vty); /* Get previous line from history buffer */ length = strlen(vty->hist[vty->hp]); memcpy(vty->buf, vty->hist[vty->hp], length); vty->cp = vty->length = length; vty->buf[vty->length] = '\0'; /* Redraw current line */ vty_redraw_line(vty); } /* Show next command line history. */ static void vty_next_line(struct vty *vty) { int try_index; if (vty->hp == vty->hindex) return; /* Try is there history exist or not. */ try_index = vty->hp; if (try_index == (VTY_MAXHIST - 1)) try_index = 0; else try_index++; /* If there is not history return. */ if (vty->hist[try_index] == NULL) return; else vty->hp = try_index; vty_history_print(vty); } /* Show previous command line history. */ static void vty_previous_line(struct vty *vty) { int try_index; try_index = vty->hp; if (try_index == 0) try_index = VTY_MAXHIST - 1; else try_index--; if (vty->hist[try_index] == NULL) return; else vty->hp = try_index; vty_history_print(vty); } /* This function redraw all of the command line character. */ static void vty_redraw_line(struct vty *vty) { vty_write(vty, vty->buf, vty->length); vty->cp = vty->length; } /* Forward word. */ static void vty_forward_word(struct vty *vty) { while (vty->cp != vty->length && vty->buf[vty->cp] != ' ') vty_forward_char(vty); while (vty->cp != vty->length && vty->buf[vty->cp] == ' ') vty_forward_char(vty); } /* Backward word without skipping training space. */ static void vty_backward_pure_word(struct vty *vty) { while (vty->cp > 0 && vty->buf[vty->cp - 1] != ' ') vty_backward_char(vty); } /* Backward word. */ static void vty_backward_word(struct vty *vty) { while (vty->cp > 0 && vty->buf[vty->cp - 1] == ' ') vty_backward_char(vty); while (vty->cp > 0 && vty->buf[vty->cp - 1] != ' ') vty_backward_char(vty); } /* When '^D' is typed at the beginning of the line we move to the down level. */ static void vty_down_level(struct vty *vty) { vty_out(vty, "\n"); cmd_exit(vty); vty_prompt(vty); vty->cp = 0; } /* When '^Z' is received from vty, move down to the enable mode. */ static void vty_end_config(struct vty *vty) { vty_out(vty, "\n"); if (vty->config) { vty_config_exit(vty); vty->node = ENABLE_NODE; } vty_prompt(vty); vty->cp = 0; } /* Delete a charcter at the current point. */ static void vty_delete_char(struct vty *vty) { int i; int size; if (vty->length == 0) { vty_down_level(vty); return; } if (vty->cp == vty->length) return; /* completion need here? */ size = vty->length - vty->cp; vty->length--; memmove(&vty->buf[vty->cp], &vty->buf[vty->cp + 1], size - 1); vty->buf[vty->length] = '\0'; if (vty->node == AUTH_NODE || vty->node == AUTH_ENABLE_NODE) return; vty_write(vty, &vty->buf[vty->cp], size - 1); vty_write(vty, &telnet_space_char, 1); for (i = 0; i < size; i++) vty_write(vty, &telnet_backward_char, 1); } /* Delete a character before the point. */ static void vty_delete_backward_char(struct vty *vty) { if (vty->cp == 0) return; vty_backward_char(vty); vty_delete_char(vty); } /* Kill rest of line from current point. */ static void vty_kill_line(struct vty *vty) { int i; int size; size = vty->length - vty->cp; if (size == 0) return; for (i = 0; i < size; i++) vty_write(vty, &telnet_space_char, 1); for (i = 0; i < size; i++) vty_write(vty, &telnet_backward_char, 1); memset(&vty->buf[vty->cp], 0, size); vty->length = vty->cp; } /* Kill line from the beginning. */ static void vty_kill_line_from_beginning(struct vty *vty) { vty_beginning_of_line(vty); vty_kill_line(vty); } /* Delete a word before the point. */ static void vty_forward_kill_word(struct vty *vty) { while (vty->cp != vty->length && vty->buf[vty->cp] == ' ') vty_delete_char(vty); while (vty->cp != vty->length && vty->buf[vty->cp] != ' ') vty_delete_char(vty); } /* Delete a word before the point. */ static void vty_backward_kill_word(struct vty *vty) { while (vty->cp > 0 && vty->buf[vty->cp - 1] == ' ') vty_delete_backward_char(vty); while (vty->cp > 0 && vty->buf[vty->cp - 1] != ' ') vty_delete_backward_char(vty); } /* Transpose chars before or at the point. */ static void vty_transpose_chars(struct vty *vty) { char c1, c2; /* If length is short or point is near by the beginning of line then return. */ if (vty->length < 2 || vty->cp < 1) return; /* In case of point is located at the end of the line. */ if (vty->cp == vty->length) { c1 = vty->buf[vty->cp - 1]; c2 = vty->buf[vty->cp - 2]; vty_backward_char(vty); vty_backward_char(vty); vty_self_insert_overwrite(vty, c1); vty_self_insert_overwrite(vty, c2); } else { c1 = vty->buf[vty->cp]; c2 = vty->buf[vty->cp - 1]; vty_backward_char(vty); vty_self_insert_overwrite(vty, c1); vty_self_insert_overwrite(vty, c2); } } /* Do completion at vty interface. */ static void vty_complete_command(struct vty *vty) { int i; int ret; char **matched = NULL; vector vline; if (vty->node == AUTH_NODE || vty->node == AUTH_ENABLE_NODE) return; vline = cmd_make_strvec(vty->buf); if (vline == NULL) return; /* In case of 'help \t'. */ if (isspace((unsigned char)vty->buf[vty->length - 1])) vector_set(vline, NULL); matched = cmd_complete_command(vline, vty, &ret); cmd_free_strvec(vline); vty_out(vty, "\n"); switch (ret) { case CMD_ERR_AMBIGUOUS: vty_out(vty, "%% Ambiguous command.\n"); vty_prompt(vty); vty_redraw_line(vty); break; case CMD_ERR_NO_MATCH: /* vty_out (vty, "%% There is no matched command.\n"); */ vty_prompt(vty); vty_redraw_line(vty); break; case CMD_COMPLETE_FULL_MATCH: if (!matched[0]) { /* 2016-11-28 equinox -- need to debug, SEGV here */ vty_out(vty, "%% CLI BUG: FULL_MATCH with NULL str\n"); vty_prompt(vty); vty_redraw_line(vty); break; } vty_prompt(vty); vty_redraw_line(vty); vty_backward_pure_word(vty); vty_insert_word_overwrite(vty, matched[0]); vty_self_insert(vty, ' '); XFREE(MTYPE_COMPLETION, matched[0]); break; case CMD_COMPLETE_MATCH: vty_prompt(vty); vty_redraw_line(vty); vty_backward_pure_word(vty); vty_insert_word_overwrite(vty, matched[0]); XFREE(MTYPE_COMPLETION, matched[0]); break; case CMD_COMPLETE_LIST_MATCH: for (i = 0; matched[i] != NULL; i++) { if (i != 0 && ((i % 6) == 0)) vty_out(vty, "\n"); vty_out(vty, "%-10s ", matched[i]); XFREE(MTYPE_COMPLETION, matched[i]); } vty_out(vty, "\n"); vty_prompt(vty); vty_redraw_line(vty); break; case CMD_ERR_NOTHING_TODO: vty_prompt(vty); vty_redraw_line(vty); break; default: break; } XFREE(MTYPE_TMP, matched); } static void vty_describe_fold(struct vty *vty, int cmd_width, unsigned int desc_width, struct cmd_token *token) { char *buf; const char *cmd, *p; int pos; cmd = token->text; if (desc_width <= 0) { vty_out(vty, " %-*s %s\n", cmd_width, cmd, token->desc); return; } buf = XCALLOC(MTYPE_TMP, strlen(token->desc) + 1); for (p = token->desc; strlen(p) > desc_width; p += pos + 1) { for (pos = desc_width; pos > 0; pos--) if (*(p + pos) == ' ') break; if (pos == 0) break; memcpy(buf, p, pos); buf[pos] = '\0'; vty_out(vty, " %-*s %s\n", cmd_width, cmd, buf); cmd = ""; } vty_out(vty, " %-*s %s\n", cmd_width, cmd, p); XFREE(MTYPE_TMP, buf); } /* Describe matched command function. */ static void vty_describe_command(struct vty *vty) { int ret; vector vline; vector describe; unsigned int i, width, desc_width; struct cmd_token *token, *token_cr = NULL; vline = cmd_make_strvec(vty->buf); /* In case of '> ?'. */ if (vline == NULL) { vline = vector_init(1); vector_set(vline, NULL); } else if (isspace((unsigned char)vty->buf[vty->length - 1])) vector_set(vline, NULL); describe = cmd_describe_command(vline, vty, &ret); vty_out(vty, "\n"); /* Ambiguous error. */ switch (ret) { case CMD_ERR_AMBIGUOUS: vty_out(vty, "%% Ambiguous command.\n"); goto out; break; case CMD_ERR_NO_MATCH: vty_out(vty, "%% There is no matched command.\n"); goto out; break; } /* Get width of command string. */ width = 0; for (i = 0; i < vector_active(describe); i++) if ((token = vector_slot(describe, i)) != NULL) { unsigned int len; if (token->text[0] == '\0') continue; len = strlen(token->text); if (width < len) width = len; } /* Get width of description string. */ desc_width = vty->width - (width + 6); /* Print out description. */ for (i = 0; i < vector_active(describe); i++) if ((token = vector_slot(describe, i)) != NULL) { if (token->text[0] == '\0') continue; if (strcmp(token->text, CMD_CR_TEXT) == 0) { token_cr = token; continue; } if (!token->desc) vty_out(vty, " %-s\n", token->text); else if (desc_width >= strlen(token->desc)) vty_out(vty, " %-*s %s\n", width, token->text, token->desc); else vty_describe_fold(vty, width, desc_width, token); if (IS_VARYING_TOKEN(token->type)) { const char *ref = vector_slot( vline, vector_active(vline) - 1); vector varcomps = vector_init(VECTOR_MIN_SIZE); cmd_variable_complete(token, ref, varcomps); if (vector_active(varcomps) > 0) { char *ac = cmd_variable_comp2str( varcomps, vty->width); vty_out(vty, "%s\n", ac); XFREE(MTYPE_TMP, ac); } vector_free(varcomps); } #if 0 vty_out (vty, " %-*s %s\n", width desc->cmd[0] == '.' ? desc->cmd + 1 : desc->cmd, desc->str ? desc->str : ""); #endif /* 0 */ } if ((token = token_cr)) { if (!token->desc) vty_out(vty, " %-s\n", token->text); else if (desc_width >= strlen(token->desc)) vty_out(vty, " %-*s %s\n", width, token->text, token->desc); else vty_describe_fold(vty, width, desc_width, token); } out: cmd_free_strvec(vline); if (describe) vector_free(describe); vty_prompt(vty); vty_redraw_line(vty); } static void vty_clear_buf(struct vty *vty) { memset(vty->buf, 0, vty->max); } /* ^C stop current input and do not add command line to the history. */ static void vty_stop_input(struct vty *vty) { vty->cp = vty->length = 0; vty_clear_buf(vty); vty_out(vty, "\n"); if (vty->config) { vty_config_exit(vty); vty->node = ENABLE_NODE; } vty_prompt(vty); /* Set history pointer to the latest one. */ vty->hp = vty->hindex; } /* Add current command line to the history buffer. */ static void vty_hist_add(struct vty *vty) { int index; if (vty->length == 0) return; index = vty->hindex ? vty->hindex - 1 : VTY_MAXHIST - 1; /* Ignore the same string as previous one. */ if (vty->hist[index]) if (strcmp(vty->buf, vty->hist[index]) == 0) { vty->hp = vty->hindex; return; } /* Insert history entry. */ XFREE(MTYPE_VTY_HIST, vty->hist[vty->hindex]); vty->hist[vty->hindex] = XSTRDUP(MTYPE_VTY_HIST, vty->buf); /* History index rotation. */ vty->hindex++; if (vty->hindex == VTY_MAXHIST) vty->hindex = 0; vty->hp = vty->hindex; } /* #define TELNET_OPTION_DEBUG */ /* Get telnet window size. */ static int vty_telnet_option(struct vty *vty, unsigned char *buf, int nbytes) { #ifdef TELNET_OPTION_DEBUG int i; for (i = 0; i < nbytes; i++) { switch (buf[i]) { case IAC: vty_out(vty, "IAC "); break; case WILL: vty_out(vty, "WILL "); break; case WONT: vty_out(vty, "WONT "); break; case DO: vty_out(vty, "DO "); break; case DONT: vty_out(vty, "DONT "); break; case SB: vty_out(vty, "SB "); break; case SE: vty_out(vty, "SE "); break; case TELOPT_ECHO: vty_out(vty, "TELOPT_ECHO \n"); break; case TELOPT_SGA: vty_out(vty, "TELOPT_SGA \n"); break; case TELOPT_NAWS: vty_out(vty, "TELOPT_NAWS \n"); break; default: vty_out(vty, "%x ", buf[i]); break; } } vty_out(vty, "\n"); #endif /* TELNET_OPTION_DEBUG */ switch (buf[0]) { case SB: vty->sb_len = 0; vty->iac_sb_in_progress = 1; return 0; break; case SE: { if (!vty->iac_sb_in_progress) return 0; if ((vty->sb_len == 0) || (vty->sb_buf[0] == '\0')) { vty->iac_sb_in_progress = 0; return 0; } switch (vty->sb_buf[0]) { case TELOPT_NAWS: if (vty->sb_len != TELNET_NAWS_SB_LEN) flog_err( EC_LIB_SYSTEM_CALL, "RFC 1073 violation detected: telnet NAWS option " "should send %d characters, but we received %lu", TELNET_NAWS_SB_LEN, (unsigned long)vty->sb_len); else if (sizeof(vty->sb_buf) < TELNET_NAWS_SB_LEN) flog_err( EC_LIB_DEVELOPMENT, "Bug detected: sizeof(vty->sb_buf) %lu < %d, too small to handle the telnet NAWS option", (unsigned long)sizeof(vty->sb_buf), TELNET_NAWS_SB_LEN); else { vty->width = ((vty->sb_buf[1] << 8) | vty->sb_buf[2]); vty->height = ((vty->sb_buf[3] << 8) | vty->sb_buf[4]); #ifdef TELNET_OPTION_DEBUG vty_out(vty, "TELNET NAWS window size negotiation completed: " "width %d, height %d\n", vty->width, vty->height); #endif } break; } vty->iac_sb_in_progress = 0; return 0; break; } default: break; } return 1; } /* Execute current command line. */ static int vty_execute(struct vty *vty) { int ret; ret = CMD_SUCCESS; switch (vty->node) { case AUTH_NODE: case AUTH_ENABLE_NODE: vty_auth(vty, vty->buf); break; default: ret = vty_command(vty, vty->buf); if (vty->type == VTY_TERM) vty_hist_add(vty); break; } /* Clear command line buffer. */ vty->cp = vty->length = 0; vty_clear_buf(vty); if (vty->status != VTY_CLOSE) vty_prompt(vty); return ret; } #define CONTROL(X) ((X) - '@') #define VTY_NORMAL 0 #define VTY_PRE_ESCAPE 1 #define VTY_ESCAPE 2 /* Escape character command map. */ static void vty_escape_map(unsigned char c, struct vty *vty) { switch (c) { case ('A'): vty_previous_line(vty); break; case ('B'): vty_next_line(vty); break; case ('C'): vty_forward_char(vty); break; case ('D'): vty_backward_char(vty); break; default: break; } /* Go back to normal mode. */ vty->escape = VTY_NORMAL; } /* Quit print out to the buffer. */ static void vty_buffer_reset(struct vty *vty) { buffer_reset(vty->obuf); buffer_reset(vty->lbuf); vty_prompt(vty); vty_redraw_line(vty); } /* Read data via vty socket. */ static int vty_read(struct thread *thread) { int i; int nbytes; unsigned char buf[VTY_READ_BUFSIZ]; int vty_sock = THREAD_FD(thread); struct vty *vty = THREAD_ARG(thread); /* Read raw data from socket */ if ((nbytes = read(vty->fd, buf, VTY_READ_BUFSIZ)) <= 0) { if (nbytes < 0) { if (ERRNO_IO_RETRY(errno)) { vty_event(VTY_READ, vty_sock, vty); return 0; } vty->monitor = 0; /* disable monitoring to avoid infinite recursion */ flog_err( EC_LIB_SOCKET, "%s: read error on vty client fd %d, closing: %s", __func__, vty->fd, safe_strerror(errno)); buffer_reset(vty->obuf); buffer_reset(vty->lbuf); } vty->status = VTY_CLOSE; } for (i = 0; i < nbytes; i++) { if (buf[i] == IAC) { if (!vty->iac) { vty->iac = 1; continue; } else { vty->iac = 0; } } if (vty->iac_sb_in_progress && !vty->iac) { if (vty->sb_len < sizeof(vty->sb_buf)) vty->sb_buf[vty->sb_len] = buf[i]; vty->sb_len++; continue; } if (vty->iac) { /* In case of telnet command */ int ret = 0; ret = vty_telnet_option(vty, buf + i, nbytes - i); vty->iac = 0; i += ret; continue; } if (vty->status == VTY_MORE) { switch (buf[i]) { case CONTROL('C'): case 'q': case 'Q': vty_buffer_reset(vty); break; #if 0 /* More line does not work for "show ip bgp". */ case '\n': case '\r': vty->status = VTY_MORELINE; break; #endif default: break; } continue; } /* Escape character. */ if (vty->escape == VTY_ESCAPE) { vty_escape_map(buf[i], vty); continue; } /* Pre-escape status. */ if (vty->escape == VTY_PRE_ESCAPE) { switch (buf[i]) { case '[': vty->escape = VTY_ESCAPE; break; case 'b': vty_backward_word(vty); vty->escape = VTY_NORMAL; break; case 'f': vty_forward_word(vty); vty->escape = VTY_NORMAL; break; case 'd': vty_forward_kill_word(vty); vty->escape = VTY_NORMAL; break; case CONTROL('H'): case 0x7f: vty_backward_kill_word(vty); vty->escape = VTY_NORMAL; break; default: vty->escape = VTY_NORMAL; break; } continue; } switch (buf[i]) { case CONTROL('A'): vty_beginning_of_line(vty); break; case CONTROL('B'): vty_backward_char(vty); break; case CONTROL('C'): vty_stop_input(vty); break; case CONTROL('D'): vty_delete_char(vty); break; case CONTROL('E'): vty_end_of_line(vty); break; case CONTROL('F'): vty_forward_char(vty); break; case CONTROL('H'): case 0x7f: vty_delete_backward_char(vty); break; case CONTROL('K'): vty_kill_line(vty); break; case CONTROL('N'): vty_next_line(vty); break; case CONTROL('P'): vty_previous_line(vty); break; case CONTROL('T'): vty_transpose_chars(vty); break; case CONTROL('U'): vty_kill_line_from_beginning(vty); break; case CONTROL('W'): vty_backward_kill_word(vty); break; case CONTROL('Z'): vty_end_config(vty); break; case '\n': case '\r': vty_out(vty, "\n"); vty_execute(vty); break; case '\t': vty_complete_command(vty); break; case '?': if (vty->node == AUTH_NODE || vty->node == AUTH_ENABLE_NODE) vty_self_insert(vty, buf[i]); else vty_describe_command(vty); break; case '\033': if (i + 1 < nbytes && buf[i + 1] == '[') { vty->escape = VTY_ESCAPE; i++; } else vty->escape = VTY_PRE_ESCAPE; break; default: if (buf[i] > 31 && buf[i] < 127) vty_self_insert(vty, buf[i]); break; } } /* Check status. */ if (vty->status == VTY_CLOSE) vty_close(vty); else { vty_event(VTY_WRITE, vty->wfd, vty); vty_event(VTY_READ, vty_sock, vty); } return 0; } /* Flush buffer to the vty. */ static int vty_flush(struct thread *thread) { int erase; buffer_status_t flushrc; int vty_sock = THREAD_FD(thread); struct vty *vty = THREAD_ARG(thread); /* Tempolary disable read thread. */ if (vty->lines == 0) THREAD_OFF(vty->t_read); /* Function execution continue. */ erase = ((vty->status == VTY_MORE || vty->status == VTY_MORELINE)); /* N.B. if width is 0, that means we don't know the window size. */ if ((vty->lines == 0) || (vty->width == 0) || (vty->height == 0)) flushrc = buffer_flush_available(vty->obuf, vty_sock); else if (vty->status == VTY_MORELINE) flushrc = buffer_flush_window(vty->obuf, vty_sock, vty->width, 1, erase, 0); else flushrc = buffer_flush_window( vty->obuf, vty_sock, vty->width, vty->lines >= 0 ? vty->lines : vty->height, erase, 0); switch (flushrc) { case BUFFER_ERROR: vty->monitor = 0; /* disable monitoring to avoid infinite recursion */ zlog_info("buffer_flush failed on vty client fd %d, closing", vty->fd); buffer_reset(vty->lbuf); buffer_reset(vty->obuf); vty_close(vty); return 0; case BUFFER_EMPTY: if (vty->status == VTY_CLOSE) vty_close(vty); else { vty->status = VTY_NORMAL; if (vty->lines == 0) vty_event(VTY_READ, vty_sock, vty); } break; case BUFFER_PENDING: /* There is more data waiting to be written. */ vty->status = VTY_MORE; if (vty->lines == 0) vty_event(VTY_WRITE, vty_sock, vty); break; } return 0; } /* Allocate new vty struct. */ struct vty *vty_new(void) { struct vty *new = XCALLOC(MTYPE_VTY, sizeof(struct vty)); new->fd = new->wfd = -1; new->of = stdout; new->lbuf = buffer_new(0); new->obuf = buffer_new(0); /* Use default buffer size. */ new->buf = XCALLOC(MTYPE_VTY, VTY_BUFSIZ); new->max = VTY_BUFSIZ; return new; } /* allocate and initialise vty */ static struct vty *vty_new_init(int vty_sock) { struct vty *vty; vty = vty_new(); vty->fd = vty_sock; vty->wfd = vty_sock; vty->type = VTY_TERM; vty->node = AUTH_NODE; vty->fail = 0; vty->cp = 0; vty_clear_buf(vty); vty->length = 0; memset(vty->hist, 0, sizeof(vty->hist)); vty->hp = 0; vty->hindex = 0; vty->xpath_index = 0; memset(vty->xpath, 0, sizeof(vty->xpath)); vty->private_config = false; vty->candidate_config = vty_shared_candidate_config; vector_set_index(vtyvec, vty_sock, vty); vty->status = VTY_NORMAL; vty->lines = -1; vty->iac = 0; vty->iac_sb_in_progress = 0; vty->sb_len = 0; return vty; } /* Create new vty structure. */ static struct vty *vty_create(int vty_sock, union sockunion *su) { char buf[SU_ADDRSTRLEN]; struct vty *vty; sockunion2str(su, buf, SU_ADDRSTRLEN); /* Allocate new vty structure and set up default values. */ vty = vty_new_init(vty_sock); /* configurable parameters not part of basic init */ vty->v_timeout = vty_timeout_val; strlcpy(vty->address, buf, sizeof(vty->address)); if (no_password_check) { if (host.advanced) vty->node = ENABLE_NODE; else vty->node = VIEW_NODE; } if (host.lines >= 0) vty->lines = host.lines; if (!no_password_check) { /* Vty is not available if password isn't set. */ if (host.password == NULL && host.password_encrypt == NULL) { vty_out(vty, "Vty password is not set.\n"); vty->status = VTY_CLOSE; vty_close(vty); return NULL; } } /* Say hello to the world. */ vty_hello(vty); if (!no_password_check) vty_out(vty, "\nUser Access Verification\n\n"); /* Setting up terminal. */ vty_will_echo(vty); vty_will_suppress_go_ahead(vty); vty_dont_linemode(vty); vty_do_window_size(vty); /* vty_dont_lflow_ahead (vty); */ vty_prompt(vty); /* Add read/write thread. */ vty_event(VTY_WRITE, vty_sock, vty); vty_event(VTY_READ, vty_sock, vty); return vty; } /* create vty for stdio */ static struct termios stdio_orig_termios; static struct vty *stdio_vty = NULL; static bool stdio_termios = false; static void (*stdio_vty_atclose)(int isexit); static void vty_stdio_reset(int isexit) { if (stdio_vty) { if (stdio_termios) tcsetattr(0, TCSANOW, &stdio_orig_termios); stdio_termios = false; stdio_vty = NULL; if (stdio_vty_atclose) stdio_vty_atclose(isexit); stdio_vty_atclose = NULL; } } static void vty_stdio_atexit(void) { vty_stdio_reset(1); } void vty_stdio_suspend(void) { if (!stdio_vty) return; THREAD_OFF(stdio_vty->t_write); THREAD_OFF(stdio_vty->t_read); THREAD_OFF(stdio_vty->t_timeout); if (stdio_termios) tcsetattr(0, TCSANOW, &stdio_orig_termios); stdio_termios = false; } void vty_stdio_resume(void) { if (!stdio_vty) return; if (!tcgetattr(0, &stdio_orig_termios)) { struct termios termios; termios = stdio_orig_termios; termios.c_iflag &= ~(IGNBRK | BRKINT | PARMRK | ISTRIP | INLCR | IGNCR | ICRNL | IXON); termios.c_oflag &= ~OPOST; termios.c_lflag &= ~(ECHO | ECHONL | ICANON | IEXTEN); termios.c_cflag &= ~(CSIZE | PARENB); termios.c_cflag |= CS8; tcsetattr(0, TCSANOW, &termios); stdio_termios = true; } vty_prompt(stdio_vty); /* Add read/write thread. */ vty_event(VTY_WRITE, 1, stdio_vty); vty_event(VTY_READ, 0, stdio_vty); } void vty_stdio_close(void) { if (!stdio_vty) return; vty_close(stdio_vty); } struct vty *vty_stdio(void (*atclose)(int isexit)) { struct vty *vty; /* refuse creating two vtys on stdio */ if (stdio_vty) return NULL; vty = stdio_vty = vty_new_init(0); stdio_vty_atclose = atclose; vty->wfd = 1; /* always have stdio vty in a known _unchangeable_ state, don't want * config * to have any effect here to make sure scripting this works as intended */ vty->node = ENABLE_NODE; vty->v_timeout = 0; strlcpy(vty->address, "console", sizeof(vty->address)); vty_stdio_resume(); return vty; } /* Accept connection from the network. */ static int vty_accept(struct thread *thread) { int vty_sock; union sockunion su; int ret; unsigned int on; int accept_sock; struct prefix p; struct access_list *acl = NULL; char buf[SU_ADDRSTRLEN]; accept_sock = THREAD_FD(thread); /* We continue hearing vty socket. */ vty_event(VTY_SERV, accept_sock, NULL); memset(&su, 0, sizeof(union sockunion)); /* We can handle IPv4 or IPv6 socket. */ vty_sock = sockunion_accept(accept_sock, &su); if (vty_sock < 0) { flog_err(EC_LIB_SOCKET, "can't accept vty socket : %s", safe_strerror(errno)); return -1; } set_nonblocking(vty_sock); set_cloexec(vty_sock); sockunion2hostprefix(&su, &p); /* VTY's accesslist apply. */ if (p.family == AF_INET && vty_accesslist_name) { if ((acl = access_list_lookup(AFI_IP, vty_accesslist_name)) && (access_list_apply(acl, &p) == FILTER_DENY)) { zlog_info("Vty connection refused from %s", sockunion2str(&su, buf, SU_ADDRSTRLEN)); close(vty_sock); /* continue accepting connections */ vty_event(VTY_SERV, accept_sock, NULL); return 0; } } /* VTY's ipv6 accesslist apply. */ if (p.family == AF_INET6 && vty_ipv6_accesslist_name) { if ((acl = access_list_lookup(AFI_IP6, vty_ipv6_accesslist_name)) && (access_list_apply(acl, &p) == FILTER_DENY)) { zlog_info("Vty connection refused from %s", sockunion2str(&su, buf, SU_ADDRSTRLEN)); close(vty_sock); /* continue accepting connections */ vty_event(VTY_SERV, accept_sock, NULL); return 0; } } on = 1; ret = setsockopt(vty_sock, IPPROTO_TCP, TCP_NODELAY, (char *)&on, sizeof(on)); if (ret < 0) zlog_info("can't set sockopt to vty_sock : %s", safe_strerror(errno)); zlog_info("Vty connection from %s", sockunion2str(&su, buf, SU_ADDRSTRLEN)); vty_create(vty_sock, &su); return 0; } static void vty_serv_sock_addrinfo(const char *hostname, unsigned short port) { int ret; struct addrinfo req; struct addrinfo *ainfo; struct addrinfo *ainfo_save; int sock; char port_str[BUFSIZ]; memset(&req, 0, sizeof(struct addrinfo)); req.ai_flags = AI_PASSIVE; req.ai_family = AF_UNSPEC; req.ai_socktype = SOCK_STREAM; sprintf(port_str, "%d", port); port_str[sizeof(port_str) - 1] = '\0'; ret = getaddrinfo(hostname, port_str, &req, &ainfo); if (ret != 0) { flog_err_sys(EC_LIB_SYSTEM_CALL, "getaddrinfo failed: %s", gai_strerror(ret)); exit(1); } ainfo_save = ainfo; do { if (ainfo->ai_family != AF_INET && ainfo->ai_family != AF_INET6) continue; sock = socket(ainfo->ai_family, ainfo->ai_socktype, ainfo->ai_protocol); if (sock < 0) continue; sockopt_v6only(ainfo->ai_family, sock); sockopt_reuseaddr(sock); sockopt_reuseport(sock); set_cloexec(sock); ret = bind(sock, ainfo->ai_addr, ainfo->ai_addrlen); if (ret < 0) { close(sock); /* Avoid sd leak. */ continue; } ret = listen(sock, 3); if (ret < 0) { close(sock); /* Avoid sd leak. */ continue; } vty_event(VTY_SERV, sock, NULL); } while ((ainfo = ainfo->ai_next) != NULL); freeaddrinfo(ainfo_save); } #ifdef VTYSH /* For sockaddr_un. */ #include /* VTY shell UNIX domain socket. */ static void vty_serv_un(const char *path) { int ret; int sock, len; struct sockaddr_un serv; mode_t old_mask; struct zprivs_ids_t ids; /* First of all, unlink existing socket */ unlink(path); /* Set umask */ old_mask = umask(0007); /* Make UNIX domain socket. */ sock = socket(AF_UNIX, SOCK_STREAM, 0); if (sock < 0) { flog_err_sys(EC_LIB_SOCKET, "Cannot create unix stream socket: %s", safe_strerror(errno)); return; } /* Make server socket. */ memset(&serv, 0, sizeof(struct sockaddr_un)); serv.sun_family = AF_UNIX; strlcpy(serv.sun_path, path, sizeof(serv.sun_path)); #ifdef HAVE_STRUCT_SOCKADDR_UN_SUN_LEN len = serv.sun_len = SUN_LEN(&serv); #else len = sizeof(serv.sun_family) + strlen(serv.sun_path); #endif /* HAVE_STRUCT_SOCKADDR_UN_SUN_LEN */ set_cloexec(sock); ret = bind(sock, (struct sockaddr *)&serv, len); if (ret < 0) { flog_err_sys(EC_LIB_SOCKET, "Cannot bind path %s: %s", path, safe_strerror(errno)); close(sock); /* Avoid sd leak. */ return; } ret = listen(sock, 5); if (ret < 0) { flog_err_sys(EC_LIB_SOCKET, "listen(fd %d) failed: %s", sock, safe_strerror(errno)); close(sock); /* Avoid sd leak. */ return; } umask(old_mask); zprivs_get_ids(&ids); /* Hack: ids.gid_vty is actually a uint, but we stored -1 in it earlier for the case when we don't need to chown the file type casting it here to make a compare */ if ((int)ids.gid_vty > 0) { /* set group of socket */ if (chown(path, -1, ids.gid_vty)) { flog_err_sys(EC_LIB_SYSTEM_CALL, "vty_serv_un: could chown socket, %s", safe_strerror(errno)); } } vty_event(VTYSH_SERV, sock, NULL); } /* #define VTYSH_DEBUG 1 */ static int vtysh_accept(struct thread *thread) { int accept_sock; int sock; int client_len; struct sockaddr_un client; struct vty *vty; accept_sock = THREAD_FD(thread); vty_event(VTYSH_SERV, accept_sock, NULL); memset(&client, 0, sizeof(struct sockaddr_un)); client_len = sizeof(struct sockaddr_un); sock = accept(accept_sock, (struct sockaddr *)&client, (socklen_t *)&client_len); if (sock < 0) { flog_err(EC_LIB_SOCKET, "can't accept vty socket : %s", safe_strerror(errno)); return -1; } if (set_nonblocking(sock) < 0) { flog_err( EC_LIB_SOCKET, "vtysh_accept: could not set vty socket %d to non-blocking, %s, closing", sock, safe_strerror(errno)); close(sock); return -1; } set_cloexec(sock); #ifdef VTYSH_DEBUG printf("VTY shell accept\n"); #endif /* VTYSH_DEBUG */ vty = vty_new(); vty->fd = sock; vty->wfd = sock; vty->type = VTY_SHELL_SERV; vty->node = VIEW_NODE; vty_event(VTYSH_READ, sock, vty); return 0; } static int vtysh_flush(struct vty *vty) { switch (buffer_flush_available(vty->obuf, vty->wfd)) { case BUFFER_PENDING: vty_event(VTYSH_WRITE, vty->wfd, vty); break; case BUFFER_ERROR: vty->monitor = 0; /* disable monitoring to avoid infinite recursion */ flog_err(EC_LIB_SOCKET, "%s: write error to fd %d, closing", __func__, vty->fd); buffer_reset(vty->lbuf); buffer_reset(vty->obuf); vty_close(vty); return -1; break; case BUFFER_EMPTY: break; } return 0; } static int vtysh_read(struct thread *thread) { int ret; int sock; int nbytes; struct vty *vty; unsigned char buf[VTY_READ_BUFSIZ]; unsigned char *p; uint8_t header[4] = {0, 0, 0, 0}; sock = THREAD_FD(thread); vty = THREAD_ARG(thread); if ((nbytes = read(sock, buf, VTY_READ_BUFSIZ)) <= 0) { if (nbytes < 0) { if (ERRNO_IO_RETRY(errno)) { vty_event(VTYSH_READ, sock, vty); return 0; } vty->monitor = 0; /* disable monitoring to avoid infinite recursion */ flog_err( EC_LIB_SOCKET, "%s: read failed on vtysh client fd %d, closing: %s", __func__, sock, safe_strerror(errno)); } buffer_reset(vty->lbuf); buffer_reset(vty->obuf); vty_close(vty); #ifdef VTYSH_DEBUG printf("close vtysh\n"); #endif /* VTYSH_DEBUG */ return 0; } #ifdef VTYSH_DEBUG printf("line: %.*s\n", nbytes, buf); #endif /* VTYSH_DEBUG */ if (vty->length + nbytes >= VTY_BUFSIZ) { /* Clear command line buffer. */ vty->cp = vty->length = 0; vty_clear_buf(vty); vty_out(vty, "%% Command is too long.\n"); } else { for (p = buf; p < buf + nbytes; p++) { vty->buf[vty->length++] = *p; if (*p == '\0') { /* Pass this line to parser. */ ret = vty_execute(vty); /* Note that vty_execute clears the command buffer and resets vty->length to 0. */ /* Return result. */ #ifdef VTYSH_DEBUG printf("result: %d\n", ret); printf("vtysh node: %d\n", vty->node); #endif /* VTYSH_DEBUG */ /* hack for asynchronous "write integrated" * - other commands in "buf" will be ditched * - input during pending config-write is * "unsupported" */ if (ret == CMD_SUSPEND) break; /* warning: watchfrr hardcodes this result write */ header[3] = ret; buffer_put(vty->obuf, header, 4); if (!vty->t_write && (vtysh_flush(vty) < 0)) /* Try to flush results; exit if a write * error occurs. */ return 0; } } } if (vty->status == VTY_CLOSE) vty_close(vty); else vty_event(VTYSH_READ, sock, vty); return 0; } static int vtysh_write(struct thread *thread) { struct vty *vty = THREAD_ARG(thread); vtysh_flush(vty); return 0; } #endif /* VTYSH */ /* Determine address family to bind. */ void vty_serv_sock(const char *addr, unsigned short port, const char *path) { /* If port is set to 0, do not listen on TCP/IP at all! */ if (port) vty_serv_sock_addrinfo(addr, port); #ifdef VTYSH vty_serv_un(path); #endif /* VTYSH */ } static void vty_error_delete(void *arg) { struct vty_error *ve = arg; XFREE(MTYPE_TMP, ve); } /* Close vty interface. Warning: call this only from functions that will be careful not to access the vty afterwards (since it has now been freed). This is safest from top-level functions (called directly by the thread dispatcher). */ void vty_close(struct vty *vty) { int i; bool was_stdio = false; /* Cancel threads.*/ THREAD_OFF(vty->t_read); THREAD_OFF(vty->t_write); THREAD_OFF(vty->t_timeout); /* Flush buffer. */ buffer_flush_all(vty->obuf, vty->wfd); /* Free input buffer. */ buffer_free(vty->obuf); buffer_free(vty->lbuf); /* Free command history. */ for (i = 0; i < VTY_MAXHIST; i++) { XFREE(MTYPE_VTY_HIST, vty->hist[i]); } /* Unset vector. */ if (vty->fd != -1) vector_unset(vtyvec, vty->fd); if (vty->wfd > 0 && vty->type == VTY_FILE) fsync(vty->wfd); /* Close socket. * note check is for fd > STDERR_FILENO, not fd != -1. * We never close stdin/stdout/stderr here, because we may be * running in foreground mode with logging to stdout. Also, * additionally, we'd need to replace these fds with /dev/null. */ if (vty->wfd > STDERR_FILENO && vty->wfd != vty->fd) close(vty->wfd); if (vty->fd > STDERR_FILENO) close(vty->fd); if (vty->fd == STDIN_FILENO) was_stdio = true; XFREE(MTYPE_VTY, vty->buf); if (vty->error) { vty->error->del = vty_error_delete; list_delete(&vty->error); } /* Check configure. */ vty_config_exit(vty); /* OK free vty. */ XFREE(MTYPE_VTY, vty); if (was_stdio) vty_stdio_reset(0); } /* When time out occur output message then close connection. */ static int vty_timeout(struct thread *thread) { struct vty *vty; vty = THREAD_ARG(thread); vty->v_timeout = 0; /* Clear buffer*/ buffer_reset(vty->lbuf); buffer_reset(vty->obuf); vty_out(vty, "\nVty connection is timed out.\n"); /* Close connection. */ vty->status = VTY_CLOSE; vty_close(vty); return 0; } /* Read up configuration file from file_name. */ static void vty_read_file(struct nb_config *config, FILE *confp) { int ret; struct vty *vty; struct vty_error *ve; struct listnode *node; unsigned int line_num = 0; vty = vty_new(); /* vty_close won't close stderr; if some config command prints * something it'll end up there. (not ideal; it'd be beter if output * from a file-load went to logging instead. Also note that if this * function is called after daemonizing, stderr will be /dev/null.) * * vty->fd will be -1 from vty_new() */ vty->wfd = STDERR_FILENO; vty->type = VTY_FILE; vty->node = CONFIG_NODE; vty->config = true; if (config) vty->candidate_config = config; else { vty->private_config = true; vty->candidate_config = nb_config_new(NULL); } /* Execute configuration file */ ret = config_from_file(vty, confp, &line_num); /* Flush any previous errors before printing messages below */ buffer_flush_all(vty->obuf, vty->wfd); if (!((ret == CMD_SUCCESS) || (ret == CMD_ERR_NOTHING_TODO))) { const char *message = NULL; char *nl; switch (ret) { case CMD_ERR_AMBIGUOUS: message = "Ambiguous command"; break; case CMD_ERR_NO_MATCH: message = "No such command"; break; case CMD_WARNING: message = "Command returned Warning"; break; case CMD_WARNING_CONFIG_FAILED: message = "Command returned Warning Config Failed"; break; case CMD_ERR_INCOMPLETE: message = "Command returned Incomplete"; break; case CMD_ERR_EXEED_ARGC_MAX: message = "Command exceeded maximum number of Arguments"; break; default: message = "Command returned unhandled error message"; break; } for (ALL_LIST_ELEMENTS_RO(vty->error, node, ve)) { nl = strchr(ve->error_buf, '\n'); if (nl) *nl = '\0'; flog_err(EC_LIB_VTY, "ERROR: %s on config line %u: %s", message, ve->line_num, ve->error_buf); } } /* * Automatically commit the candidate configuration after * reading the configuration file. */ if (config == NULL && vty->candidate_config && frr_get_cli_mode() == FRR_CLI_TRANSACTIONAL) { ret = nb_candidate_commit(vty->candidate_config, NB_CLIENT_CLI, vty, true, "Read configuration file", NULL); if (ret != NB_OK && ret != NB_ERR_NO_CHANGES) zlog_err("%s: failed to read configuration file.", __func__); } vty_close(vty); } static FILE *vty_use_backup_config(const char *fullpath) { char *fullpath_sav, *fullpath_tmp; FILE *ret = NULL; int tmp, sav; int c; char buffer[512]; size_t fullpath_sav_sz = strlen(fullpath) + strlen(CONF_BACKUP_EXT) + 1; fullpath_sav = malloc(fullpath_sav_sz); strlcpy(fullpath_sav, fullpath, fullpath_sav_sz); strlcat(fullpath_sav, CONF_BACKUP_EXT, fullpath_sav_sz); sav = open(fullpath_sav, O_RDONLY); if (sav < 0) { free(fullpath_sav); return NULL; } fullpath_tmp = malloc(strlen(fullpath) + 8); sprintf(fullpath_tmp, "%s.XXXXXX", fullpath); /* Open file to configuration write. */ tmp = mkstemp(fullpath_tmp); if (tmp < 0) goto out_close_sav; if (fchmod(tmp, CONFIGFILE_MASK) != 0) goto out_close; while ((c = read(sav, buffer, 512)) > 0) { if (write(tmp, buffer, c) <= 0) goto out_close; } close(sav); close(tmp); if (rename(fullpath_tmp, fullpath) == 0) ret = fopen(fullpath, "r"); else unlink(fullpath_tmp); if (0) { out_close: close(tmp); unlink(fullpath_tmp); out_close_sav: close(sav); } free(fullpath_sav); free(fullpath_tmp); return ret; } /* Read up configuration file from file_name. */ bool vty_read_config(struct nb_config *config, const char *config_file, char *config_default_dir) { char cwd[MAXPATHLEN]; FILE *confp = NULL; const char *fullpath; char *tmp = NULL; bool read_success = false; /* If -f flag specified. */ if (config_file != NULL) { if (!IS_DIRECTORY_SEP(config_file[0])) { if (getcwd(cwd, MAXPATHLEN) == NULL) { flog_err_sys( EC_LIB_SYSTEM_CALL, "%s: failure to determine Current Working Directory %d!", __func__, errno); goto tmp_free_and_out; } tmp = XMALLOC(MTYPE_TMP, strlen(cwd) + strlen(config_file) + 2); sprintf(tmp, "%s/%s", cwd, config_file); fullpath = tmp; } else fullpath = config_file; confp = fopen(fullpath, "r"); if (confp == NULL) { flog_warn( EC_LIB_BACKUP_CONFIG, "%s: failed to open configuration file %s: %s, checking backup", __func__, fullpath, safe_strerror(errno)); confp = vty_use_backup_config(fullpath); if (confp) flog_warn( EC_LIB_BACKUP_CONFIG, "WARNING: using backup configuration file!"); else { flog_err( EC_LIB_VTY, "%s: can't open configuration file [%s]", __func__, config_file); goto tmp_free_and_out; } } } else { host_config_set(config_default_dir); #ifdef VTYSH int ret; struct stat conf_stat; /* !!!!PLEASE LEAVE!!!! * This is NEEDED for use with vtysh -b, or else you can get * a real configuration food fight with a lot garbage in the * merged configuration file it creates coming from the per * daemon configuration files. This also allows the daemons * to start if there default configuration file is not * present or ignore them, as needed when using vtysh -b to * configure the daemons at boot - MAG */ /* Stat for vtysh Zebra.conf, if found startup and wait for * boot configuration */ if (strstr(config_default_dir, "vtysh") == NULL) { ret = stat(integrate_default, &conf_stat); if (ret >= 0) { read_success = true; goto tmp_free_and_out; } } #endif /* VTYSH */ confp = fopen(config_default_dir, "r"); if (confp == NULL) { flog_err( EC_LIB_SYSTEM_CALL, "%s: failed to open configuration file %s: %s, checking backup", __func__, config_default_dir, safe_strerror(errno)); confp = vty_use_backup_config(config_default_dir); if (confp) { flog_warn( EC_LIB_BACKUP_CONFIG, "WARNING: using backup configuration file!"); fullpath = config_default_dir; } else { flog_err(EC_LIB_VTY, "can't open configuration file [%s]", config_default_dir); goto tmp_free_and_out; } } else fullpath = config_default_dir; } vty_read_file(config, confp); read_success = true; fclose(confp); host_config_set(fullpath); tmp_free_and_out: XFREE(MTYPE_TMP, tmp); return read_success; } /* Small utility function which output log to the VTY. */ void vty_log(const char *level, const char *proto_str, const char *msg, struct timestamp_control *ctl) { unsigned int i; struct vty *vty; if (!vtyvec) return; for (i = 0; i < vector_active(vtyvec); i++) if ((vty = vector_slot(vtyvec, i)) != NULL) if (vty->monitor) vty_log_out(vty, level, proto_str, msg, ctl); } /* Async-signal-safe version of vty_log for fixed strings. */ void vty_log_fixed(char *buf, size_t len) { unsigned int i; struct iovec iov[2]; char crlf[4] = "\r\n"; /* vty may not have been initialised */ if (!vtyvec) return; iov[0].iov_base = buf; iov[0].iov_len = len; iov[1].iov_base = crlf; iov[1].iov_len = 2; for (i = 0; i < vector_active(vtyvec); i++) { struct vty *vty; if (((vty = vector_slot(vtyvec, i)) != NULL) && vty->monitor) /* N.B. We don't care about the return code, since process is most likely just about to die anyway. */ if (writev(vty->wfd, iov, 2) == -1) { fprintf(stderr, "Failure to writev: %d\n", errno); exit(-1); } } } int vty_config_enter(struct vty *vty, bool private_config, bool exclusive) { if (exclusive && nb_running_lock(NB_CLIENT_CLI, vty)) { vty_out(vty, "%% Configuration is locked by other client\n"); return CMD_WARNING; } vty->node = CONFIG_NODE; vty->config = true; vty->private_config = private_config; vty->xpath_index = 0; pthread_rwlock_rdlock(&running_config->lock); { if (private_config) { vty->candidate_config = nb_config_dup(running_config); vty->candidate_config_base = nb_config_dup(running_config); vty_out(vty, "Warning: uncommitted changes will be discarded on exit.\n\n"); } else { vty->candidate_config = vty_shared_candidate_config; if (frr_get_cli_mode() == FRR_CLI_TRANSACTIONAL) vty->candidate_config_base = nb_config_dup(running_config); } } pthread_rwlock_unlock(&running_config->lock); return CMD_SUCCESS; } void vty_config_exit(struct vty *vty) { /* Check if there's a pending confirmed commit. */ if (vty->t_confirmed_commit_timeout) { vty_out(vty, "WARNING: exiting with a pending confirmed commit. Rolling back to previous configuration.\n\n"); nb_cli_confirmed_commit_rollback(vty); nb_cli_confirmed_commit_clean(vty); } (void)nb_running_unlock(NB_CLIENT_CLI, vty); if (vty->candidate_config) { if (vty->private_config) nb_config_free(vty->candidate_config); vty->candidate_config = NULL; } if (vty->candidate_config_base) { nb_config_free(vty->candidate_config_base); vty->candidate_config_base = NULL; } vty->config = false; } /* Master of the threads. */ static struct thread_master *vty_master; static void vty_event(enum event event, int sock, struct vty *vty) { struct thread *vty_serv_thread = NULL; switch (event) { case VTY_SERV: vty_serv_thread = thread_add_read(vty_master, vty_accept, vty, sock, NULL); vector_set_index(Vvty_serv_thread, sock, vty_serv_thread); break; #ifdef VTYSH case VTYSH_SERV: vty_serv_thread = thread_add_read(vty_master, vtysh_accept, vty, sock, NULL); vector_set_index(Vvty_serv_thread, sock, vty_serv_thread); break; case VTYSH_READ: thread_add_read(vty_master, vtysh_read, vty, sock, &vty->t_read); break; case VTYSH_WRITE: thread_add_write(vty_master, vtysh_write, vty, sock, &vty->t_write); break; #endif /* VTYSH */ case VTY_READ: thread_add_read(vty_master, vty_read, vty, sock, &vty->t_read); /* Time out treatment. */ if (vty->v_timeout) { THREAD_OFF(vty->t_timeout); thread_add_timer(vty_master, vty_timeout, vty, vty->v_timeout, &vty->t_timeout); } break; case VTY_WRITE: thread_add_write(vty_master, vty_flush, vty, sock, &vty->t_write); break; case VTY_TIMEOUT_RESET: THREAD_OFF(vty->t_timeout); if (vty->v_timeout) thread_add_timer(vty_master, vty_timeout, vty, vty->v_timeout, &vty->t_timeout); break; } } DEFUN_NOSH (config_who, config_who_cmd, "who", "Display who is on vty\n") { unsigned int i; struct vty *v; for (i = 0; i < vector_active(vtyvec); i++) if ((v = vector_slot(vtyvec, i)) != NULL) vty_out(vty, "%svty[%d] connected from %s.\n", v->config ? "*" : " ", i, v->address); return CMD_SUCCESS; } /* Move to vty configuration mode. */ DEFUN_NOSH (line_vty, line_vty_cmd, "line vty", "Configure a terminal line\n" "Virtual terminal\n") { vty->node = VTY_NODE; return CMD_SUCCESS; } /* Set time out value. */ static int exec_timeout(struct vty *vty, const char *min_str, const char *sec_str) { unsigned long timeout = 0; /* min_str and sec_str are already checked by parser. So it must be all digit string. */ if (min_str) { timeout = strtol(min_str, NULL, 10); timeout *= 60; } if (sec_str) timeout += strtol(sec_str, NULL, 10); vty_timeout_val = timeout; vty->v_timeout = timeout; vty_event(VTY_TIMEOUT_RESET, 0, vty); return CMD_SUCCESS; } DEFUN (exec_timeout_min, exec_timeout_min_cmd, "exec-timeout (0-35791)", "Set timeout value\n" "Timeout value in minutes\n") { int idx_number = 1; return exec_timeout(vty, argv[idx_number]->arg, NULL); } DEFUN (exec_timeout_sec, exec_timeout_sec_cmd, "exec-timeout (0-35791) (0-2147483)", "Set the EXEC timeout\n" "Timeout in minutes\n" "Timeout in seconds\n") { int idx_number = 1; int idx_number_2 = 2; return exec_timeout(vty, argv[idx_number]->arg, argv[idx_number_2]->arg); } DEFUN (no_exec_timeout, no_exec_timeout_cmd, "no exec-timeout", NO_STR "Set the EXEC timeout\n") { return exec_timeout(vty, NULL, NULL); } /* Set vty access class. */ DEFUN (vty_access_class, vty_access_class_cmd, "access-class WORD", "Filter connections based on an IP access list\n" "IP access list\n") { int idx_word = 1; if (vty_accesslist_name) XFREE(MTYPE_VTY, vty_accesslist_name); vty_accesslist_name = XSTRDUP(MTYPE_VTY, argv[idx_word]->arg); return CMD_SUCCESS; } /* Clear vty access class. */ DEFUN (no_vty_access_class, no_vty_access_class_cmd, "no access-class [WORD]", NO_STR "Filter connections based on an IP access list\n" "IP access list\n") { int idx_word = 2; const char *accesslist = (argc == 3) ? argv[idx_word]->arg : NULL; if (!vty_accesslist_name || (argc == 3 && strcmp(vty_accesslist_name, accesslist))) { vty_out(vty, "Access-class is not currently applied to vty\n"); return CMD_WARNING_CONFIG_FAILED; } XFREE(MTYPE_VTY, vty_accesslist_name); vty_accesslist_name = NULL; return CMD_SUCCESS; } /* Set vty access class. */ DEFUN (vty_ipv6_access_class, vty_ipv6_access_class_cmd, "ipv6 access-class WORD", IPV6_STR "Filter connections based on an IP access list\n" "IPv6 access list\n") { int idx_word = 2; if (vty_ipv6_accesslist_name) XFREE(MTYPE_VTY, vty_ipv6_accesslist_name); vty_ipv6_accesslist_name = XSTRDUP(MTYPE_VTY, argv[idx_word]->arg); return CMD_SUCCESS; } /* Clear vty access class. */ DEFUN (no_vty_ipv6_access_class, no_vty_ipv6_access_class_cmd, "no ipv6 access-class [WORD]", NO_STR IPV6_STR "Filter connections based on an IP access list\n" "IPv6 access list\n") { int idx_word = 3; const char *accesslist = (argc == 4) ? argv[idx_word]->arg : NULL; if (!vty_ipv6_accesslist_name || (argc == 4 && strcmp(vty_ipv6_accesslist_name, accesslist))) { vty_out(vty, "IPv6 access-class is not currently applied to vty\n"); return CMD_WARNING_CONFIG_FAILED; } XFREE(MTYPE_VTY, vty_ipv6_accesslist_name); vty_ipv6_accesslist_name = NULL; return CMD_SUCCESS; } /* vty login. */ DEFUN (vty_login, vty_login_cmd, "login", "Enable password checking\n") { no_password_check = 0; return CMD_SUCCESS; } DEFUN (no_vty_login, no_vty_login_cmd, "no login", NO_STR "Enable password checking\n") { no_password_check = 1; return CMD_SUCCESS; } DEFUN (service_advanced_vty, service_advanced_vty_cmd, "service advanced-vty", "Set up miscellaneous service\n" "Enable advanced mode vty interface\n") { host.advanced = 1; return CMD_SUCCESS; } DEFUN (no_service_advanced_vty, no_service_advanced_vty_cmd, "no service advanced-vty", NO_STR "Set up miscellaneous service\n" "Enable advanced mode vty interface\n") { host.advanced = 0; return CMD_SUCCESS; } DEFUN_NOSH (terminal_monitor, terminal_monitor_cmd, "terminal monitor", "Set terminal line parameters\n" "Copy debug output to the current terminal line\n") { vty->monitor = 1; return CMD_SUCCESS; } DEFUN_NOSH (terminal_no_monitor, terminal_no_monitor_cmd, "terminal no monitor", "Set terminal line parameters\n" NO_STR "Copy debug output to the current terminal line\n") { vty->monitor = 0; return CMD_SUCCESS; } DEFUN_NOSH (no_terminal_monitor, no_terminal_monitor_cmd, "no terminal monitor", NO_STR "Set terminal line parameters\n" "Copy debug output to the current terminal line\n") { return terminal_no_monitor(self, vty, argc, argv); } DEFUN_NOSH (show_history, show_history_cmd, "show history", SHOW_STR "Display the session command history\n") { int index; for (index = vty->hindex + 1; index != vty->hindex;) { if (index == VTY_MAXHIST) { index = 0; continue; } if (vty->hist[index] != NULL) vty_out(vty, " %s\n", vty->hist[index]); index++; } return CMD_SUCCESS; } /* vty login. */ DEFPY (log_commands, log_commands_cmd, "[no] log commands", NO_STR "Logging control\n" "Log all commands\n") { if (no) { if (do_log_commands_perm) { vty_out(vty, "Daemon started with permanent logging turned on for commands, ignoring\n"); return CMD_WARNING; } do_log_commands = false; } else do_log_commands = true; return CMD_SUCCESS; } /* Display current configuration. */ static int vty_config_write(struct vty *vty) { vty_out(vty, "line vty\n"); if (vty_accesslist_name) vty_out(vty, " access-class %s\n", vty_accesslist_name); if (vty_ipv6_accesslist_name) vty_out(vty, " ipv6 access-class %s\n", vty_ipv6_accesslist_name); /* exec-timeout */ if (vty_timeout_val != VTY_TIMEOUT_DEFAULT) vty_out(vty, " exec-timeout %ld %ld\n", vty_timeout_val / 60, vty_timeout_val % 60); /* login */ if (no_password_check) vty_out(vty, " no login\n"); if (do_log_commands) vty_out(vty, "log commands\n"); vty_out(vty, "!\n"); return CMD_SUCCESS; } struct cmd_node vty_node = { VTY_NODE, "%s(config-line)# ", 1, }; /* Reset all VTY status. */ void vty_reset(void) { unsigned int i; struct vty *vty; struct thread *vty_serv_thread; for (i = 0; i < vector_active(vtyvec); i++) if ((vty = vector_slot(vtyvec, i)) != NULL) { buffer_reset(vty->lbuf); buffer_reset(vty->obuf); vty->status = VTY_CLOSE; vty_close(vty); } for (i = 0; i < vector_active(Vvty_serv_thread); i++) if ((vty_serv_thread = vector_slot(Vvty_serv_thread, i)) != NULL) { THREAD_OFF(vty_serv_thread); vector_slot(Vvty_serv_thread, i) = NULL; close(i); } vty_timeout_val = VTY_TIMEOUT_DEFAULT; if (vty_accesslist_name) { XFREE(MTYPE_VTY, vty_accesslist_name); vty_accesslist_name = NULL; } if (vty_ipv6_accesslist_name) { XFREE(MTYPE_VTY, vty_ipv6_accesslist_name); vty_ipv6_accesslist_name = NULL; } } static void vty_save_cwd(void) { char *c; c = getcwd(vty_cwd, sizeof(vty_cwd)); if (!c) { /* * At this point if these go wrong, more than likely * the whole world is coming down around us * Hence not worrying about it too much. */ if (!chdir(SYSCONFDIR)) { flog_err_sys(EC_LIB_SYSTEM_CALL, "Failure to chdir to %s, errno: %d", SYSCONFDIR, errno); exit(-1); } if (getcwd(vty_cwd, sizeof(vty_cwd)) == NULL) { flog_err_sys(EC_LIB_SYSTEM_CALL, "Failure to getcwd, errno: %d", errno); exit(-1); } } } char *vty_get_cwd(void) { return vty_cwd; } int vty_shell(struct vty *vty) { return vty->type == VTY_SHELL ? 1 : 0; } int vty_shell_serv(struct vty *vty) { return vty->type == VTY_SHELL_SERV ? 1 : 0; } void vty_init_vtysh(void) { vtyvec = vector_init(VECTOR_MIN_SIZE); } /* Install vty's own commands like `who' command. */ void vty_init(struct thread_master *master_thread, bool do_command_logging) { /* For further configuration read, preserve current directory. */ vty_save_cwd(); vtyvec = vector_init(VECTOR_MIN_SIZE); vty_master = master_thread; atexit(vty_stdio_atexit); /* Initilize server thread vector. */ Vvty_serv_thread = vector_init(VECTOR_MIN_SIZE); /* Install bgp top node. */ install_node(&vty_node, vty_config_write); install_element(VIEW_NODE, &config_who_cmd); install_element(VIEW_NODE, &show_history_cmd); install_element(CONFIG_NODE, &line_vty_cmd); install_element(CONFIG_NODE, &service_advanced_vty_cmd); install_element(CONFIG_NODE, &no_service_advanced_vty_cmd); install_element(CONFIG_NODE, &show_history_cmd); install_element(CONFIG_NODE, &log_commands_cmd); if (do_command_logging) { do_log_commands = true; do_log_commands_perm = true; } install_element(ENABLE_NODE, &terminal_monitor_cmd); install_element(ENABLE_NODE, &terminal_no_monitor_cmd); install_element(ENABLE_NODE, &no_terminal_monitor_cmd); install_default(VTY_NODE); install_element(VTY_NODE, &exec_timeout_min_cmd); install_element(VTY_NODE, &exec_timeout_sec_cmd); install_element(VTY_NODE, &no_exec_timeout_cmd); install_element(VTY_NODE, &vty_access_class_cmd); install_element(VTY_NODE, &no_vty_access_class_cmd); install_element(VTY_NODE, &vty_login_cmd); install_element(VTY_NODE, &no_vty_login_cmd); install_element(VTY_NODE, &vty_ipv6_access_class_cmd); install_element(VTY_NODE, &no_vty_ipv6_access_class_cmd); } void vty_terminate(void) { memset(vty_cwd, 0x00, sizeof(vty_cwd)); if (vtyvec && Vvty_serv_thread) { vty_reset(); vector_free(vtyvec); vector_free(Vvty_serv_thread); vtyvec = NULL; Vvty_serv_thread = NULL; } } frr-7.2.1/lib/vty.h0000644000000000000000000002461013610377563010760 00000000000000/* Virtual terminal [aka TeletYpe] interface routine * Copyright (C) 1997 Kunihiro Ishiguro * * This file is part of GNU Zebra. * * GNU Zebra is free software; you can redistribute it and/or modify it * under the terms of the GNU General Public License as published by the * Free Software Foundation; either version 2, or (at your option) any * later version. * * GNU Zebra 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; see the file COPYING; if not, write to the Free Software * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA */ #ifndef _ZEBRA_VTY_H #define _ZEBRA_VTY_H #include #include #include "thread.h" #include "log.h" #include "sockunion.h" #include "qobj.h" #include "compiler.h" #include "northbound.h" #ifdef __cplusplus extern "C" { #endif #define VTY_BUFSIZ 4096 #define VTY_MAXHIST 20 #define VTY_MAXDEPTH 8 #define VTY_MAXCFGCHANGES 8 struct vty_error { char error_buf[VTY_BUFSIZ]; uint32_t line_num; }; struct vty_cfg_change { char xpath[XPATH_MAXLEN]; enum nb_operation operation; const char *value; }; /* VTY struct. */ struct vty { /* File descripter of this vty. */ int fd; /* output FD, to support stdin/stdout combination */ int wfd; /* File output, used for VTYSH only */ FILE *of; FILE *of_saved; /* whether we are using pager or not */ bool is_paged; /* Is this vty connect to file or not */ enum { VTY_TERM, VTY_FILE, VTY_SHELL, VTY_SHELL_SERV } type; /* Node status of this vty */ int node; /* Failure count */ int fail; /* Output filer regex */ bool filter; regex_t include; /* Line buffer */ struct buffer *lbuf; /* Output buffer. */ struct buffer *obuf; /* Command input buffer */ char *buf; /* Command input error buffer */ struct list *error; /* Command cursor point */ int cp; /* Command length */ int length; /* Command max length. */ int max; /* Histry of command */ char *hist[VTY_MAXHIST]; /* History lookup current point */ int hp; /* History insert end point */ int hindex; /* Changes enqueued to be applied in the candidate configuration. */ size_t num_cfg_changes; struct vty_cfg_change cfg_changes[VTY_MAXCFGCHANGES]; /* XPath of the current node */ int xpath_index; char xpath[VTY_MAXDEPTH][XPATH_MAXLEN]; /* In configure mode. */ bool config; /* Private candidate configuration mode. */ bool private_config; /* Candidate configuration. */ struct nb_config *candidate_config; /* Base candidate configuration. */ struct nb_config *candidate_config_base; /* Confirmed-commit timeout and rollback configuration. */ struct thread *t_confirmed_commit_timeout; struct nb_config *confirmed_commit_rollback; /* qobj object ID (replacement for "index") */ uint64_t qobj_index; /* qobj second-level object ID (replacement for "index_sub") */ uint64_t qobj_index_sub; /* For escape character. */ unsigned char escape; /* Current vty status. */ enum { VTY_NORMAL, VTY_CLOSE, VTY_MORE, VTY_MORELINE } status; /* IAC handling: was the last character received the IAC (interpret-as-command) escape character (and therefore the next character will be the command code)? Refer to Telnet RFC 854. */ unsigned char iac; /* IAC SB (option subnegotiation) handling */ unsigned char iac_sb_in_progress; /* At the moment, we care only about the NAWS (window size) negotiation, and that requires just a 5-character buffer (RFC 1073): <16-bit width> <16-bit height> */ #define TELNET_NAWS_SB_LEN 5 unsigned char sb_buf[TELNET_NAWS_SB_LEN]; /* How many subnegotiation characters have we received? We just drop those that do not fit in the buffer. */ size_t sb_len; /* Window width/height. */ int width; int height; /* Configure lines. */ int lines; /* Terminal monitor. */ int monitor; /* Read and write thread. */ struct thread *t_read; struct thread *t_write; /* Timeout seconds and thread. */ unsigned long v_timeout; struct thread *t_timeout; /* What address is this vty comming from. */ char address[SU_ADDRSTRLEN]; /* "frame" output. This is buffered and will be printed if some * actual output follows, or will be discarded if the frame ends * without any output. */ size_t frame_pos; char frame[1024]; }; static inline void vty_push_context(struct vty *vty, int node, uint64_t id) { vty->node = node; vty->qobj_index = id; } /* note: VTY_PUSH_CONTEXT(..., NULL) doesn't work, since it will try to * dereference "NULL->qobj_node.nid" */ #define VTY_PUSH_CONTEXT(nodeval, ptr) \ vty_push_context(vty, nodeval, QOBJ_ID_0SAFE(ptr)) #define VTY_PUSH_CONTEXT_NULL(nodeval) vty_push_context(vty, nodeval, 0ULL) #define VTY_PUSH_CONTEXT_SUB(nodeval, ptr) \ do { \ vty->node = nodeval; \ /* qobj_index stays untouched */ \ vty->qobj_index_sub = QOBJ_ID_0SAFE(ptr); \ } while (0) /* can return NULL if context is invalid! */ #define VTY_GET_CONTEXT(structname) \ QOBJ_GET_TYPESAFE(vty->qobj_index, structname) #define VTY_GET_CONTEXT_SUB(structname) \ QOBJ_GET_TYPESAFE(vty->qobj_index_sub, structname) /* will return if ptr is NULL. */ #define VTY_CHECK_CONTEXT(ptr) \ if (!ptr) { \ vty_out(vty, \ "Current configuration object was deleted " \ "by another process.\n"); \ return CMD_WARNING; \ } /* struct structname *ptr = ; ptr will never be NULL. */ #define VTY_DECLVAR_CONTEXT(structname, ptr) \ struct structname *ptr = VTY_GET_CONTEXT(structname); \ VTY_CHECK_CONTEXT(ptr); #define VTY_DECLVAR_CONTEXT_SUB(structname, ptr) \ struct structname *ptr = VTY_GET_CONTEXT_SUB(structname); \ VTY_CHECK_CONTEXT(ptr); #define VTY_DECLVAR_INSTANCE_CONTEXT(structname, ptr) \ if (vty->qobj_index == 0) \ return CMD_NOT_MY_INSTANCE; \ struct structname *ptr = VTY_GET_CONTEXT(structname); \ VTY_CHECK_CONTEXT(ptr); /* XPath macros. */ #define VTY_PUSH_XPATH(nodeval, value) \ do { \ if (vty->xpath_index >= VTY_MAXDEPTH) { \ vty_out(vty, "%% Reached maximum CLI depth (%u)\n", \ VTY_MAXDEPTH); \ return CMD_WARNING; \ } \ vty->node = nodeval; \ strlcpy(vty->xpath[vty->xpath_index], value, \ sizeof(vty->xpath[0])); \ vty->xpath_index++; \ } while (0) #define VTY_CURR_XPATH vty->xpath[vty->xpath_index - 1] #define VTY_CHECK_XPATH \ do { \ if (vty->xpath_index > 0 \ && !yang_dnode_exists(vty->candidate_config->dnode, \ VTY_CURR_XPATH)) { \ vty_out(vty, \ "Current configuration object was deleted " \ "by another process.\n\n"); \ return CMD_WARNING; \ } \ } while (0) struct vty_arg { const char *name; const char *value; const char **argv; int argc; }; /* Integrated configuration file. */ #define INTEGRATE_DEFAULT_CONFIG "frr.conf" /* Default time out value */ #define VTY_TIMEOUT_DEFAULT 600 /* Vty read buffer size. */ #define VTY_READ_BUFSIZ 512 /* Directory separator. */ #ifndef DIRECTORY_SEP #define DIRECTORY_SEP '/' #endif /* DIRECTORY_SEP */ #ifndef IS_DIRECTORY_SEP #define IS_DIRECTORY_SEP(c) ((c) == DIRECTORY_SEP) #endif /* Prototypes. */ extern void vty_init(struct thread_master *, bool do_command_logging); extern void vty_init_vtysh(void); extern void vty_terminate(void); extern void vty_reset(void); extern struct vty *vty_new(void); extern struct vty *vty_stdio(void (*atclose)(int isexit)); /* - vty_frame() output goes to a buffer (for context-begin markers) * - vty_out() will first print this buffer, and clear it * - vty_endframe() clears the buffer without printing it, and prints an * extra string if the buffer was empty before (for context-end markers) */ extern int vty_out(struct vty *, const char *, ...) PRINTFRR(2, 3); extern void vty_frame(struct vty *, const char *, ...) PRINTFRR(2, 3); extern void vty_endframe(struct vty *, const char *); bool vty_set_include(struct vty *vty, const char *regexp); extern bool vty_read_config(struct nb_config *config, const char *config_file, char *config_default_dir); extern void vty_time_print(struct vty *, int); extern void vty_serv_sock(const char *, unsigned short, const char *); extern void vty_close(struct vty *); extern char *vty_get_cwd(void); extern void vty_log(const char *level, const char *proto, const char *msg, struct timestamp_control *); extern int vty_config_enter(struct vty *vty, bool private_config, bool exclusive); extern void vty_config_exit(struct vty *); extern int vty_shell(struct vty *); extern int vty_shell_serv(struct vty *); extern void vty_hello(struct vty *); /* ^Z / SIGTSTP handling */ extern void vty_stdio_suspend(void); extern void vty_stdio_resume(void); extern void vty_stdio_close(void); /* Send a fixed-size message to all vty terminal monitors; this should be an async-signal-safe function. */ extern void vty_log_fixed(char *buf, size_t len); #ifdef __cplusplus } #endif #endif /* _ZEBRA_VTY_H */ frr-7.2.1/lib/vxlan.h0000644000000000000000000000255413610377563011271 00000000000000/* VxLAN common header. * Copyright (C) 2016, 2017 Cumulus Networks, Inc. * * This file is part of FRR. * * FRR is free software; you can redistribute it and/or modify it * under the terms of the GNU General Public License as published by the * Free Software Foundation; either version 2, or (at your option) any * later version. * * FRR 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 FRR; see the file COPYING. If not, write to the Free * Software Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA * 02111-1307, USA. */ #ifndef __VXLAN_H__ #define __VXLAN_H__ #ifdef __cplusplus extern "C" { #endif /* VxLAN Network Identifier - 24-bit (RFC 7348) */ typedef uint32_t vni_t; #define VNI_MAX 16777215 /* (2^24 - 1) */ /* Flooding mechanisms for BUM packets. */ /* Currently supported mechanisms are head-end (ingress) replication * (which is the default) and no flooding. Future options could be * using PIM-SM, PIM-Bidir etc. */ enum vxlan_flood_control { VXLAN_FLOOD_HEAD_END_REPL = 0, VXLAN_FLOOD_DISABLED, VXLAN_FLOOD_PIM_SM, }; #ifdef __cplusplus } #endif #endif /* __VXLAN_H__ */ frr-7.2.1/lib/wheel.c0000644000000000000000000001055513610377563011240 00000000000000/* * Timer Wheel * Copyright (C) 2016 Cumulus Networks, Inc. * Donald Sharp * * This program is free software; you can redistribute it and/or modify * it under the terms of the 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; see the file COPYING; if not, write to the Free Software * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA */ #include "zebra.h" #include "linklist.h" #include "thread.h" #include "memory.h" #include "wheel.h" #include "log.h" DEFINE_MTYPE_STATIC(LIB, TIMER_WHEEL, "Timer Wheel") DEFINE_MTYPE_STATIC(LIB, TIMER_WHEEL_LIST, "Timer Wheel Slot List") static int debug_timer_wheel = 0; static int wheel_timer_thread(struct thread *t); static int wheel_timer_thread_helper(struct thread *t) { struct listnode *node, *nextnode; unsigned long long curr_slot; unsigned int slots_to_skip = 1; struct timer_wheel *wheel; void *data; wheel = THREAD_ARG(t); THREAD_OFF(wheel->timer); wheel->curr_slot += wheel->slots_to_skip; curr_slot = wheel->curr_slot % wheel->slots; if (debug_timer_wheel) zlog_debug("%s: Wheel Slot: %lld(%lld) count: %d", __PRETTY_FUNCTION__, wheel->curr_slot, curr_slot, listcount(wheel->wheel_slot_lists[curr_slot])); for (ALL_LIST_ELEMENTS(wheel->wheel_slot_lists[curr_slot], node, nextnode, data)) (*wheel->slot_run)(data); while (list_isempty(wheel->wheel_slot_lists[(curr_slot + slots_to_skip) % wheel->slots]) && (curr_slot + slots_to_skip) % wheel->slots != curr_slot) slots_to_skip++; wheel->slots_to_skip = slots_to_skip; thread_add_timer_msec(wheel->master, wheel_timer_thread, wheel, wheel->nexttime * slots_to_skip, &wheel->timer); return 0; } static int wheel_timer_thread(struct thread *t) { struct timer_wheel *wheel; wheel = THREAD_ARG(t); thread_execute_name(wheel->master, wheel_timer_thread_helper, wheel, 0, wheel->name); return 0; } struct timer_wheel *wheel_init(struct thread_master *master, int period, size_t slots, unsigned int (*slot_key)(const void *), void (*slot_run)(void *), const char *run_name) { struct timer_wheel *wheel; size_t i; wheel = XCALLOC(MTYPE_TIMER_WHEEL, sizeof(struct timer_wheel)); wheel->name = XSTRDUP(MTYPE_TIMER_WHEEL, run_name); wheel->slot_key = slot_key; wheel->slot_run = slot_run; wheel->period = period; wheel->slots = slots; wheel->curr_slot = 0; wheel->master = master; wheel->nexttime = period / slots; wheel->wheel_slot_lists = XCALLOC(MTYPE_TIMER_WHEEL_LIST, slots * sizeof(struct listnode *)); for (i = 0; i < slots; i++) wheel->wheel_slot_lists[i] = list_new(); thread_add_timer_msec(wheel->master, wheel_timer_thread, wheel, wheel->nexttime, &wheel->timer); return wheel; } void wheel_delete(struct timer_wheel *wheel) { int i; for (i = 0; i < wheel->slots; i++) { list_delete(&wheel->wheel_slot_lists[i]); } THREAD_OFF(wheel->timer); XFREE(MTYPE_TIMER_WHEEL_LIST, wheel->wheel_slot_lists); XFREE(MTYPE_TIMER_WHEEL, wheel->name); XFREE(MTYPE_TIMER_WHEEL, wheel); } int wheel_stop(struct timer_wheel *wheel) { THREAD_OFF(wheel->timer); return 0; } int wheel_start(struct timer_wheel *wheel) { if (!wheel->timer) thread_add_timer_msec(wheel->master, wheel_timer_thread, wheel, wheel->nexttime, &wheel->timer); return 0; } int wheel_add_item(struct timer_wheel *wheel, void *item) { long long slot; slot = (*wheel->slot_key)(item); if (debug_timer_wheel) zlog_debug("%s: Inserting %p: %lld %lld", __PRETTY_FUNCTION__, item, slot, slot % wheel->slots); listnode_add(wheel->wheel_slot_lists[slot % wheel->slots], item); return 0; } int wheel_remove_item(struct timer_wheel *wheel, void *item) { long long slot; slot = (*wheel->slot_key)(item); if (debug_timer_wheel) zlog_debug("%s: Removing %p: %lld %lld", __PRETTY_FUNCTION__, item, slot, slot % wheel->slots); listnode_delete(wheel->wheel_slot_lists[slot % wheel->slots], item); return 0; } frr-7.2.1/lib/wheel.h0000644000000000000000000000760113610377563011243 00000000000000/* * Timer Wheel * Copyright (C) 2016 Cumulus Networks, Inc. * Donald Sharp * * This program is free software; you can redistribute it and/or modify * it under the terms of the 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; see the file COPYING; if not, write to the Free Software * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA */ #ifndef __WHEEL_H__ #define __WHEEL_H__ #ifdef __cplusplus extern "C" { #endif struct timer_wheel { char *name; struct thread_master *master; int slots; long long curr_slot; unsigned int period; unsigned int nexttime; unsigned int slots_to_skip; struct list **wheel_slot_lists; struct thread *timer; /* * Key to determine what slot the item belongs in */ unsigned int (*slot_key)(const void *); void (*slot_run)(void *); }; /* * Creates a timer wheel * * master - Thread master structure for the process * period - The Time in seconds that the timer wheel will * take before it starts issuing commands again * for items in each slot * slots - The number of slots to have in this particular * timer wheel * slot_key - A hashing function of some sort that will allow * the timer wheel to put items into individual slots * slot_run - The function to run over each item in a particular slot * * Creates a timer wheel that will wake up 'slots' times over the entire * wheel. Each time the timer wheel wakes up it will iterate through * and run the slot_run function for each item stored in that particular * slot. * * The timer code is 'intelligent' in that it notices if anything is * in a particular slot and can schedule the next timer to skip * the empty slot. * * The general purpose of a timer wheel is to reduce events in a system. * A perfect example of usage for this is say hello packets that need * to be sent out to all your neighbors. Suppose a large routing protocol * has to send keepalive packets every Y seconds to each of it's peers. * At scale we can have a very large number of peers, X. * This means that we will have X timing events every Y seconds. * If you replace these events with a timer wheel that has Z slots * you will have at most Y/Z timer events if each slot has a work item * in it. * * When X is large the number of events in a system can quickly escalate * and cause significant amount of time handling thread events instead * of running your code. */ struct timer_wheel *wheel_init(struct thread_master *master, int period, size_t slots, unsigned int (*slot_key)(const void *), void (*slot_run)(void *), const char *run_name); /* * Delete the specified timer wheel created */ void wheel_delete(struct timer_wheel *); /* * Pause the Wheel from running */ int wheel_stop(struct timer_wheel *wheel); /* * Start the wheel running again */ int wheel_start(struct timer_wheel *wheel); /* * wheel - The Timer wheel being modified * item - The generic data structure that will be handed * to the slot_run function. * * Add item to a slot setup by the slot_key, * possibly change next time pop. */ int wheel_add_item(struct timer_wheel *wheel, void *item); /* * wheel - The Timer wheel being modified. * item - The item to remove from one of the slots in * the timer wheel. * * Remove a item to a slot setup by the slot_key, * possibly change next time pop. */ int wheel_remove_item(struct timer_wheel *wheel, void *item); #ifdef __cplusplus } #endif #endif frr-7.2.1/lib/workqueue.c0000644000000000000000000002353513610377563012165 00000000000000/* * Quagga Work Queue Support. * * Copyright (C) 2005 Sun Microsystems, Inc. * * This file is part of GNU Zebra. * * Quagga is free software; you can redistribute it and/or modify it * under the terms of the GNU General Public License as published by the * Free Software Foundation; either version 2, or (at your option) any * later version. * * Quagga 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; see the file COPYING; if not, write to the Free Software * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA */ #include #include "thread.h" #include "memory.h" #include "workqueue.h" #include "linklist.h" #include "command.h" #include "log.h" DEFINE_MTYPE(LIB, WORK_QUEUE, "Work queue") DEFINE_MTYPE_STATIC(LIB, WORK_QUEUE_ITEM, "Work queue item") DEFINE_MTYPE_STATIC(LIB, WORK_QUEUE_NAME, "Work queue name string") /* master list of work_queues */ static struct list _work_queues; /* pointer primarily to avoid an otherwise harmless warning on * ALL_LIST_ELEMENTS_RO */ static struct list *work_queues = &_work_queues; #define WORK_QUEUE_MIN_GRANULARITY 1 static struct work_queue_item *work_queue_item_new(struct work_queue *wq) { struct work_queue_item *item; assert(wq); item = XCALLOC(MTYPE_WORK_QUEUE_ITEM, sizeof(struct work_queue_item)); return item; } static void work_queue_item_free(struct work_queue_item *item) { XFREE(MTYPE_WORK_QUEUE_ITEM, item); return; } static void work_queue_item_remove(struct work_queue *wq, struct work_queue_item *item) { assert(item && item->data); /* call private data deletion callback if needed */ if (wq->spec.del_item_data) wq->spec.del_item_data(wq, item->data); work_queue_item_dequeue(wq, item); work_queue_item_free(item); return; } /* create new work queue */ struct work_queue *work_queue_new(struct thread_master *m, const char *queue_name) { struct work_queue *new; new = XCALLOC(MTYPE_WORK_QUEUE, sizeof(struct work_queue)); new->name = XSTRDUP(MTYPE_WORK_QUEUE_NAME, queue_name); new->master = m; SET_FLAG(new->flags, WQ_UNPLUGGED); STAILQ_INIT(&new->items); listnode_add(work_queues, new); new->cycles.granularity = WORK_QUEUE_MIN_GRANULARITY; /* Default values, can be overridden by caller */ new->spec.hold = WORK_QUEUE_DEFAULT_HOLD; new->spec.yield = THREAD_YIELD_TIME_SLOT; new->spec.retry = WORK_QUEUE_DEFAULT_RETRY; return new; } void work_queue_free_and_null(struct work_queue **wqp) { struct work_queue *wq = *wqp; if (wq->thread != NULL) thread_cancel(wq->thread); while (!work_queue_empty(wq)) { struct work_queue_item *item = work_queue_last_item(wq); work_queue_item_remove(wq, item); } listnode_delete(work_queues, wq); XFREE(MTYPE_WORK_QUEUE_NAME, wq->name); XFREE(MTYPE_WORK_QUEUE, wq); *wqp = NULL; } bool work_queue_is_scheduled(struct work_queue *wq) { return (wq->thread != NULL); } static int work_queue_schedule(struct work_queue *wq, unsigned int delay) { /* if appropriate, schedule work queue thread */ if (CHECK_FLAG(wq->flags, WQ_UNPLUGGED) && (wq->thread == NULL) && !work_queue_empty(wq)) { wq->thread = NULL; /* Schedule timer if there's a delay, otherwise just schedule * as an 'event' */ if (delay > 0) thread_add_timer_msec(wq->master, work_queue_run, wq, delay, &wq->thread); else thread_add_event(wq->master, work_queue_run, wq, 0, &wq->thread); /* set thread yield time, if needed */ if (wq->thread && wq->spec.yield != THREAD_YIELD_TIME_SLOT) thread_set_yield_time(wq->thread, wq->spec.yield); return 1; } else return 0; } void work_queue_add(struct work_queue *wq, void *data) { struct work_queue_item *item; assert(wq); item = work_queue_item_new(wq); item->data = data; work_queue_item_enqueue(wq, item); work_queue_schedule(wq, wq->spec.hold); return; } static void work_queue_item_requeue(struct work_queue *wq, struct work_queue_item *item) { work_queue_item_dequeue(wq, item); /* attach to end of list */ work_queue_item_enqueue(wq, item); } DEFUN (show_work_queues, show_work_queues_cmd, "show work-queues", SHOW_STR "Work Queue information\n") { struct listnode *node; struct work_queue *wq; vty_out(vty, "%c %8s %5s %8s %8s %21s\n", ' ', "List", "(ms) ", "Q. Runs", "Yields", "Cycle Counts "); vty_out(vty, "%c %8s %5s %8s %8s %7s %6s %8s %6s %s\n", 'P', "Items", "Hold", "Total", "Total", "Best", "Gran.", "Total", "Avg.", "Name"); for (ALL_LIST_ELEMENTS_RO(work_queues, node, wq)) { vty_out(vty, "%c %8d %5d %8ld %8ld %7d %6d %8ld %6u %s\n", (CHECK_FLAG(wq->flags, WQ_UNPLUGGED) ? ' ' : 'P'), work_queue_item_count(wq), wq->spec.hold, wq->runs, wq->yields, wq->cycles.best, wq->cycles.granularity, wq->cycles.total, (wq->runs) ? (unsigned int)(wq->cycles.total / wq->runs) : 0, wq->name); } return CMD_SUCCESS; } void workqueue_cmd_init(void) { install_element(VIEW_NODE, &show_work_queues_cmd); } /* 'plug' a queue: Stop it from being scheduled, * ie: prevent the queue from draining. */ void work_queue_plug(struct work_queue *wq) { if (wq->thread) thread_cancel(wq->thread); wq->thread = NULL; UNSET_FLAG(wq->flags, WQ_UNPLUGGED); } /* unplug queue, schedule it again, if appropriate * Ie: Allow the queue to be drained again */ void work_queue_unplug(struct work_queue *wq) { SET_FLAG(wq->flags, WQ_UNPLUGGED); /* if thread isnt already waiting, add one */ work_queue_schedule(wq, wq->spec.hold); } /* timer thread to process a work queue * will reschedule itself if required, * otherwise work_queue_item_add */ int work_queue_run(struct thread *thread) { struct work_queue *wq; struct work_queue_item *item, *titem; wq_item_status ret = WQ_SUCCESS; unsigned int cycles = 0; char yielded = 0; wq = THREAD_ARG(thread); assert(wq); wq->thread = NULL; /* calculate cycle granularity: * list iteration == 1 run * listnode processing == 1 cycle * granularity == # cycles between checks whether we should yield. * * granularity should be > 0, and can increase slowly after each run to * provide some hysteris, but not past cycles.best or 2*cycles. * * Best: starts low, can only increase * * Granularity: starts at WORK_QUEUE_MIN_GRANULARITY, can be decreased * if we run to end of time slot, can increase otherwise * by a small factor. * * We could use just the average and save some work, however we want to * be * able to adjust quickly to CPU pressure. Average wont shift much if * daemon has been running a long time. */ if (wq->cycles.granularity == 0) wq->cycles.granularity = WORK_QUEUE_MIN_GRANULARITY; STAILQ_FOREACH_SAFE (item, &wq->items, wq, titem) { assert(item->data); /* dont run items which are past their allowed retries */ if (item->ran > wq->spec.max_retries) { /* run error handler, if any */ if (wq->spec.errorfunc) wq->spec.errorfunc(wq, item); work_queue_item_remove(wq, item); continue; } /* run and take care of items that want to be retried * immediately */ do { ret = wq->spec.workfunc(wq, item->data); item->ran++; } while ((ret == WQ_RETRY_NOW) && (item->ran < wq->spec.max_retries)); switch (ret) { case WQ_QUEUE_BLOCKED: { /* decrement item->ran again, cause this isn't an item * specific error, and fall through to WQ_RETRY_LATER */ item->ran--; } case WQ_RETRY_LATER: { goto stats; } case WQ_REQUEUE: { item->ran--; work_queue_item_requeue(wq, item); /* If a single node is being used with a meta-queue * (e.g., zebra), * update the next node as we don't want to exit the * thread and * reschedule it after every node. By definition, * WQ_REQUEUE is * meant to continue the processing; the yield logic * will kick in * to terminate the thread when time has exceeded. */ if (titem == NULL) titem = item; break; } case WQ_RETRY_NOW: /* a RETRY_NOW that gets here has exceeded max_tries, same as * ERROR */ case WQ_ERROR: { if (wq->spec.errorfunc) wq->spec.errorfunc(wq, item); } /* fallthru */ case WQ_SUCCESS: default: { work_queue_item_remove(wq, item); break; } } /* completed cycle */ cycles++; /* test if we should yield */ if (!(cycles % wq->cycles.granularity) && thread_should_yield(thread)) { yielded = 1; goto stats; } } stats: #define WQ_HYSTERESIS_FACTOR 4 /* we yielded, check whether granularity should be reduced */ if (yielded && (cycles < wq->cycles.granularity)) { wq->cycles.granularity = ((cycles > 0) ? cycles : WORK_QUEUE_MIN_GRANULARITY); } /* otherwise, should granularity increase? */ else if (cycles >= (wq->cycles.granularity)) { if (cycles > wq->cycles.best) wq->cycles.best = cycles; /* along with yielded check, provides hysteresis for granularity */ if (cycles > (wq->cycles.granularity * WQ_HYSTERESIS_FACTOR * WQ_HYSTERESIS_FACTOR)) wq->cycles.granularity *= WQ_HYSTERESIS_FACTOR; /* quick ramp-up */ else if (cycles > (wq->cycles.granularity * WQ_HYSTERESIS_FACTOR)) wq->cycles.granularity += WQ_HYSTERESIS_FACTOR; } #undef WQ_HYSTERIS_FACTOR wq->runs++; wq->cycles.total += cycles; if (yielded) wq->yields++; #if 0 printf ("%s: cycles %d, new: best %d, worst %d\n", __func__, cycles, wq->cycles.best, wq->cycles.granularity); #endif /* Is the queue done yet? If it is, call the completion callback. */ if (!work_queue_empty(wq)) { if (ret == WQ_RETRY_LATER || ret == WQ_QUEUE_BLOCKED) work_queue_schedule(wq, wq->spec.retry); else work_queue_schedule(wq, 0); } else if (wq->spec.completion_func) wq->spec.completion_func(wq); return 0; } frr-7.2.1/lib/workqueue.h0000644000000000000000000001250213610377563012162 00000000000000/* * Quagga Work Queues. * * Copyright (C) 2005 Sun Microsystems, Inc. * * This file is part of Quagga. * * Quagga is free software; you can redistribute it and/or modify it * under the terms of the GNU General Public License as published by the * Free Software Foundation; either version 2, or (at your option) any * later version. * * Quagga 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; see the file COPYING; if not, write to the Free Software * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA */ #ifndef _QUAGGA_WORK_QUEUE_H #define _QUAGGA_WORK_QUEUE_H #include "memory.h" #include "queue.h" #ifdef __cplusplus extern "C" { #endif DECLARE_MTYPE(WORK_QUEUE) /* Hold time for the initial schedule of a queue run, in millisec */ #define WORK_QUEUE_DEFAULT_HOLD 50 /* Retry for queue that is 'blocked' or 'retry later' */ #define WORK_QUEUE_DEFAULT_RETRY 0 /* action value, for use by item processor and item error handlers */ typedef enum { WQ_SUCCESS = 0, WQ_ERROR, /* Error, run error handler if provided */ WQ_RETRY_NOW, /* retry immediately */ WQ_RETRY_LATER, /* retry later, cease processing work queue */ WQ_REQUEUE, /* requeue item, continue processing work queue */ WQ_QUEUE_BLOCKED, /* Queue cant be processed at this time. * Similar to WQ_RETRY_LATER, but doesn't penalise * the particular item.. */ } wq_item_status; /* A single work queue item, unsurprisingly */ struct work_queue_item { STAILQ_ENTRY(work_queue_item) wq; void *data; /* opaque data */ unsigned short ran; /* # of times item has been run */ }; #define WQ_UNPLUGGED (1 << 0) /* available for draining */ struct work_queue { /* Everything but the specification struct is private * the following may be read */ struct thread_master *master; /* thread master */ struct thread *thread; /* thread, if one is active */ char *name; /* work queue name */ /* Specification for this work queue. * Public, must be set before use by caller. May be modified at will. */ struct { /* optional opaque user data, global to the queue. */ void *data; /* work function to process items with: * First argument is the workqueue queue. * Second argument is the item data */ wq_item_status (*workfunc)(struct work_queue *, void *); /* error handling function, optional */ void (*errorfunc)(struct work_queue *, struct work_queue_item *); /* callback to delete user specific item data */ void (*del_item_data)(struct work_queue *, void *); /* completion callback, called when queue is emptied, optional */ void (*completion_func)(struct work_queue *); /* max number of retries to make for item that errors */ unsigned int max_retries; unsigned int hold; /* hold time for first run, in ms */ unsigned long yield; /* yield time in us for associated thread */ uint32_t retry; /* Optional retry timeout if queue is blocked */ } spec; /* remaining fields should be opaque to users */ STAILQ_HEAD(work_queue_items, work_queue_item) items; /* queue item list */ int item_count; /* queued items */ unsigned long runs; /* runs count */ unsigned long yields; /* yields count */ struct { unsigned int best; unsigned int granularity; unsigned long total; } cycles; /* cycle counts */ /* private state */ uint16_t flags; /* user set flag */ }; /* User API */ static inline int work_queue_item_count(struct work_queue *wq) { return wq->item_count; } static inline bool work_queue_empty(struct work_queue *wq) { return (wq->item_count == 0) ? true : false; } static inline struct work_queue_item * work_queue_last_item(struct work_queue *wq) { return STAILQ_LAST(&wq->items, work_queue_item, wq); } static inline void work_queue_item_enqueue(struct work_queue *wq, struct work_queue_item *item) { STAILQ_INSERT_TAIL(&wq->items, item, wq); wq->item_count++; } static inline void work_queue_item_dequeue(struct work_queue *wq, struct work_queue_item *item) { assert(wq->item_count > 0); wq->item_count--; STAILQ_REMOVE(&wq->items, item, work_queue_item, wq); } /* create a new work queue, of given name. * user must fill in the spec of the returned work queue before adding * anything to it */ extern struct work_queue *work_queue_new(struct thread_master *, const char *); /* destroy work queue */ /* * The usage of work_queue_free is being transitioned to pass * in the double pointer to remove use after free's. */ extern void work_queue_free_and_null(struct work_queue **wqp); /* Add the supplied data as an item onto the workqueue */ extern void work_queue_add(struct work_queue *wq, void *item); /* plug the queue, ie prevent it from being drained / processed */ extern void work_queue_plug(struct work_queue *wq); /* unplug the queue, allow it to be drained again */ extern void work_queue_unplug(struct work_queue *wq); bool work_queue_is_scheduled(struct work_queue *); /* Helpers, exported for thread.c and command.c */ extern int work_queue_run(struct thread *); extern void workqueue_cmd_init(void); #ifdef __cplusplus } #endif #endif /* _QUAGGA_WORK_QUEUE_H */ frr-7.2.1/lib/yang.c0000644000000000000000000003545213610377563011075 00000000000000/* * Copyright (C) 2018 NetDEF, Inc. * Renato Westphal * * This program is free software; you can redistribute it and/or modify it * under the terms of the 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; see the file COPYING; if not, write to the Free Software * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA */ #include #include "log.h" #include "lib_errors.h" #include "yang.h" #include "yang_translator.h" #include "northbound.h" #include DEFINE_MTYPE_STATIC(LIB, YANG_MODULE, "YANG module") DEFINE_MTYPE_STATIC(LIB, YANG_DATA, "YANG data structure") /* libyang container. */ struct ly_ctx *ly_native_ctx; static struct yang_module_embed *embeds, **embedupd = &embeds; void yang_module_embed(struct yang_module_embed *embed) { embed->next = NULL; *embedupd = embed; embedupd = &embed->next; } static const char *yang_module_imp_clb(const char *mod_name, const char *mod_rev, const char *submod_name, const char *submod_rev, void *user_data, LYS_INFORMAT *format, void (**free_module_data) (void *, void*)) { struct yang_module_embed *e; if (submod_name || submod_rev) return NULL; for (e = embeds; e; e = e->next) { if (strcmp(e->mod_name, mod_name)) continue; if (mod_rev && strcmp(e->mod_rev, mod_rev)) continue; *format = e->format; return e->data; } flog_warn(EC_LIB_YANG_MODULE_LOAD, "YANG model \"%s@%s\" not embedded, trying external file", mod_name, mod_rev ? mod_rev : "*"); return NULL; } static const char *const frr_native_modules[] = { "frr-interface", "frr-ripd", "frr-ripngd", "frr-isisd", }; /* Generate the yang_modules tree. */ static inline int yang_module_compare(const struct yang_module *a, const struct yang_module *b) { return strcmp(a->name, b->name); } RB_GENERATE(yang_modules, yang_module, entry, yang_module_compare) struct yang_modules yang_modules = RB_INITIALIZER(&yang_modules); struct yang_module *yang_module_load(const char *module_name) { struct yang_module *module; const struct lys_module *module_info; module_info = ly_ctx_load_module(ly_native_ctx, module_name, NULL); if (!module_info) { flog_err(EC_LIB_YANG_MODULE_LOAD, "%s: failed to load data model: %s", __func__, module_name); exit(1); } module = XCALLOC(MTYPE_YANG_MODULE, sizeof(*module)); module->name = module_name; module->info = module_info; if (RB_INSERT(yang_modules, &yang_modules, module) != NULL) { flog_err(EC_LIB_YANG_MODULE_LOADED_ALREADY, "%s: YANG module is loaded already: %s", __func__, module_name); exit(1); } return module; } void yang_module_load_all(void) { for (size_t i = 0; i < array_size(frr_native_modules); i++) yang_module_load(frr_native_modules[i]); } struct yang_module *yang_module_find(const char *module_name) { struct yang_module s; s.name = module_name; return RB_FIND(yang_modules, &yang_modules, &s); } int yang_snodes_iterate_subtree(const struct lys_node *snode, yang_iterate_cb cb, uint16_t flags, void *arg) { struct lys_node *child; int ret = YANG_ITER_CONTINUE; if (CHECK_FLAG(flags, YANG_ITER_FILTER_IMPLICIT)) { switch (snode->nodetype) { case LYS_CASE: case LYS_INPUT: case LYS_OUTPUT: if (CHECK_FLAG(snode->flags, LYS_IMPLICIT)) goto next; break; default: break; } } switch (snode->nodetype) { case LYS_CONTAINER: if (CHECK_FLAG(flags, YANG_ITER_FILTER_NPCONTAINERS)) { struct lys_node_container *scontainer; scontainer = (struct lys_node_container *)snode; if (!scontainer->presence) goto next; } break; case LYS_LEAF: if (CHECK_FLAG(flags, YANG_ITER_FILTER_LIST_KEYS)) { struct lys_node_leaf *sleaf; /* Ignore list keys. */ sleaf = (struct lys_node_leaf *)snode; if (lys_is_key(sleaf, NULL)) goto next; } break; case LYS_GROUPING: /* Return since we're not interested in the grouping subtree. */ return YANG_ITER_CONTINUE; case LYS_USES: case LYS_AUGMENT: /* Always ignore nodes of these types. */ goto next; case LYS_INPUT: case LYS_OUTPUT: if (CHECK_FLAG(flags, YANG_ITER_FILTER_INPUT_OUTPUT)) goto next; break; default: break; } ret = (*cb)(snode, arg); if (ret == YANG_ITER_STOP) return ret; next: /* * YANG leafs and leaf-lists can't have child nodes, and trying to * access snode->child is undefined behavior. */ if (CHECK_FLAG(snode->nodetype, LYS_LEAF | LYS_LEAFLIST)) return YANG_ITER_CONTINUE; LY_TREE_FOR (snode->child, child) { if (!CHECK_FLAG(flags, YANG_ITER_ALLOW_AUGMENTATIONS) && child->parent != snode) continue; ret = yang_snodes_iterate_subtree(child, cb, flags, arg); if (ret == YANG_ITER_STOP) return ret; } return ret; } int yang_snodes_iterate_module(const struct lys_module *module, yang_iterate_cb cb, uint16_t flags, void *arg) { struct lys_node *snode; int ret = YANG_ITER_CONTINUE; LY_TREE_FOR (module->data, snode) { ret = yang_snodes_iterate_subtree(snode, cb, flags, arg); if (ret == YANG_ITER_STOP) return ret; } for (uint8_t i = 0; i < module->augment_size; i++) { ret = yang_snodes_iterate_subtree( (const struct lys_node *)&module->augment[i], cb, flags, arg); if (ret == YANG_ITER_STOP) return ret; } return ret; } int yang_snodes_iterate_all(yang_iterate_cb cb, uint16_t flags, void *arg) { struct yang_module *module; int ret = YANG_ITER_CONTINUE; RB_FOREACH (module, yang_modules, &yang_modules) { ret = yang_snodes_iterate_module(module->info, cb, flags, arg); if (ret == YANG_ITER_STOP) return ret; } return ret; } void yang_snode_get_path(const struct lys_node *snode, enum yang_path_type type, char *xpath, size_t xpath_len) { char *xpath_ptr; switch (type) { case YANG_PATH_SCHEMA: xpath_ptr = lys_path(snode, 0); break; case YANG_PATH_DATA: xpath_ptr = lys_data_path(snode); break; default: flog_err(EC_LIB_DEVELOPMENT, "%s: unknown yang path type: %u", __func__, type); exit(1); } strlcpy(xpath, xpath_ptr, xpath_len); free(xpath_ptr); } struct lys_node *yang_snode_real_parent(const struct lys_node *snode) { struct lys_node *parent = snode->parent; while (parent) { struct lys_node_container *scontainer; switch (parent->nodetype) { case LYS_CONTAINER: scontainer = (struct lys_node_container *)parent; if (scontainer->presence) return parent; break; case LYS_LIST: return parent; default: break; } parent = parent->parent; } return NULL; } struct lys_node *yang_snode_parent_list(const struct lys_node *snode) { struct lys_node *parent = snode->parent; while (parent) { switch (parent->nodetype) { case LYS_LIST: return parent; default: break; } parent = parent->parent; } return NULL; } bool yang_snode_is_typeless_data(const struct lys_node *snode) { struct lys_node_leaf *sleaf; switch (snode->nodetype) { case LYS_LEAF: sleaf = (struct lys_node_leaf *)snode; if (sleaf->type.base == LY_TYPE_EMPTY) return true; return false; case LYS_LEAFLIST: return false; default: return true; } } const char *yang_snode_get_default(const struct lys_node *snode) { struct lys_node_leaf *sleaf; switch (snode->nodetype) { case LYS_LEAF: sleaf = (struct lys_node_leaf *)snode; /* NOTE: this might be null. */ return sleaf->dflt; case LYS_LEAFLIST: /* TODO: check leaf-list default values */ return NULL; default: return NULL; } } const struct lys_type *yang_snode_get_type(const struct lys_node *snode) { struct lys_node_leaf *sleaf = (struct lys_node_leaf *)snode; struct lys_type *type; if (!CHECK_FLAG(sleaf->nodetype, LYS_LEAF | LYS_LEAFLIST)) return NULL; type = &sleaf->type; while (type->base == LY_TYPE_LEAFREF) type = &type->info.lref.target->type; return type; } void yang_dnode_get_path(const struct lyd_node *dnode, char *xpath, size_t xpath_len) { char *xpath_ptr; xpath_ptr = lyd_path(dnode); strlcpy(xpath, xpath_ptr, xpath_len); free(xpath_ptr); } const char *yang_dnode_get_schema_name(const struct lyd_node *dnode, const char *xpath_fmt, ...) { if (xpath_fmt) { va_list ap; char xpath[XPATH_MAXLEN]; va_start(ap, xpath_fmt); vsnprintf(xpath, sizeof(xpath), xpath_fmt, ap); va_end(ap); dnode = yang_dnode_get(dnode, xpath); if (!dnode) { flog_err(EC_LIB_YANG_DNODE_NOT_FOUND, "%s: couldn't find %s", __func__, xpath); zlog_backtrace(LOG_ERR); abort(); } } return dnode->schema->name; } struct lyd_node *yang_dnode_get(const struct lyd_node *dnode, const char *xpath_fmt, ...) { va_list ap; char xpath[XPATH_MAXLEN]; struct ly_set *set; struct lyd_node *dnode_ret = NULL; va_start(ap, xpath_fmt); vsnprintf(xpath, sizeof(xpath), xpath_fmt, ap); va_end(ap); set = lyd_find_path(dnode, xpath); assert(set); if (set->number == 0) goto exit; if (set->number > 1) { flog_warn(EC_LIB_YANG_DNODE_NOT_FOUND, "%s: found %u elements (expected 0 or 1) [xpath %s]", __func__, set->number, xpath); goto exit; } dnode_ret = set->set.d[0]; exit: ly_set_free(set); return dnode_ret; } bool yang_dnode_exists(const struct lyd_node *dnode, const char *xpath_fmt, ...) { va_list ap; char xpath[XPATH_MAXLEN]; struct ly_set *set; bool found; va_start(ap, xpath_fmt); vsnprintf(xpath, sizeof(xpath), xpath_fmt, ap); va_end(ap); set = lyd_find_path(dnode, xpath); assert(set); found = (set->number > 0); ly_set_free(set); return found; } bool yang_dnode_is_default(const struct lyd_node *dnode, const char *xpath_fmt, ...) { struct lys_node *snode; struct lys_node_leaf *sleaf; struct lys_node_container *scontainer; if (xpath_fmt) { va_list ap; char xpath[XPATH_MAXLEN]; va_start(ap, xpath_fmt); vsnprintf(xpath, sizeof(xpath), xpath_fmt, ap); va_end(ap); dnode = yang_dnode_get(dnode, xpath); } assert(dnode); snode = dnode->schema; switch (snode->nodetype) { case LYS_LEAF: sleaf = (struct lys_node_leaf *)snode; if (sleaf->type.base == LY_TYPE_EMPTY) return false; return lyd_wd_default((struct lyd_node_leaf_list *)dnode); case LYS_LEAFLIST: /* TODO: check leaf-list default values */ return false; case LYS_CONTAINER: scontainer = (struct lys_node_container *)snode; if (scontainer->presence) return false; return true; default: return false; } } bool yang_dnode_is_default_recursive(const struct lyd_node *dnode) { struct lys_node *snode; struct lyd_node *root, *next, *dnode_iter; snode = dnode->schema; if (CHECK_FLAG(snode->nodetype, LYS_LEAF | LYS_LEAFLIST)) return yang_dnode_is_default(dnode, NULL); if (!yang_dnode_is_default(dnode, NULL)) return false; LY_TREE_FOR (dnode->child, root) { LY_TREE_DFS_BEGIN (root, next, dnode_iter) { if (!yang_dnode_is_default(dnode_iter, NULL)) return false; LY_TREE_DFS_END(root, next, dnode_iter); } } return true; } void yang_dnode_change_leaf(struct lyd_node *dnode, const char *value) { assert(dnode->schema->nodetype == LYS_LEAF); lyd_change_leaf((struct lyd_node_leaf_list *)dnode, value); } struct lyd_node *yang_dnode_new(struct ly_ctx *ly_ctx, bool config_only) { struct lyd_node *dnode; int options; if (config_only) options = LYD_OPT_CONFIG; else options = LYD_OPT_DATA | LYD_OPT_DATA_NO_YANGLIB; dnode = NULL; if (lyd_validate(&dnode, options, ly_ctx) != 0) { /* Should never happen. */ flog_err(EC_LIB_LIBYANG, "%s: lyd_validate() failed", __func__); exit(1); } return dnode; } struct lyd_node *yang_dnode_dup(const struct lyd_node *dnode) { return lyd_dup_withsiblings(dnode, 1); } void yang_dnode_free(struct lyd_node *dnode) { while (dnode->parent) dnode = dnode->parent; lyd_free_withsiblings(dnode); } struct yang_data *yang_data_new(const char *xpath, const char *value) { struct yang_data *data; data = XCALLOC(MTYPE_YANG_DATA, sizeof(*data)); strlcpy(data->xpath, xpath, sizeof(data->xpath)); if (value) data->value = strdup(value); return data; } void yang_data_free(struct yang_data *data) { if (data->value) free(data->value); XFREE(MTYPE_YANG_DATA, data); } struct list *yang_data_list_new(void) { struct list *list; list = list_new(); list->del = (void (*)(void *))yang_data_free; return list; } struct yang_data *yang_data_list_find(const struct list *list, const char *xpath_fmt, ...) { char xpath[XPATH_MAXLEN]; struct yang_data *data; struct listnode *node; va_list ap; va_start(ap, xpath_fmt); vsnprintf(xpath, sizeof(xpath), xpath_fmt, ap); va_end(ap); for (ALL_LIST_ELEMENTS_RO(list, node, data)) if (strmatch(data->xpath, xpath)) return data; return NULL; } /* Make libyang log its errors using FRR logging infrastructure. */ static void ly_log_cb(LY_LOG_LEVEL level, const char *msg, const char *path) { int priority; switch (level) { case LY_LLERR: priority = LOG_ERR; break; case LY_LLWRN: priority = LOG_WARNING; break; case LY_LLVRB: priority = LOG_DEBUG; break; default: return; } if (path) zlog(priority, "libyang: %s (%s)", msg, path); else zlog(priority, "libyang: %s", msg); } struct ly_ctx *yang_ctx_new_setup(void) { struct ly_ctx *ctx; const char *yang_models_path = YANG_MODELS_PATH; if (access(yang_models_path, R_OK | X_OK)) { yang_models_path = NULL; if (errno == ENOENT) zlog_info("yang model directory \"%s\" does not exist", YANG_MODELS_PATH); else flog_err_sys(EC_LIB_LIBYANG, "cannot access yang model directory \"%s\"", YANG_MODELS_PATH); } ctx = ly_ctx_new(yang_models_path, LY_CTX_DISABLE_SEARCHDIR_CWD); if (!ctx) return NULL; ly_ctx_set_module_imp_clb(ctx, yang_module_imp_clb, NULL); return ctx; } void yang_init(void) { /* Initialize libyang global parameters that affect all containers. */ ly_set_log_clb(ly_log_cb, 1); ly_log_options(LY_LOLOG | LY_LOSTORE); /* Initialize libyang container for native models. */ ly_native_ctx = yang_ctx_new_setup(); if (!ly_native_ctx) { flog_err(EC_LIB_LIBYANG, "%s: ly_ctx_new() failed", __func__); exit(1); } yang_translator_init(); } void yang_terminate(void) { struct yang_module *module; yang_translator_terminate(); while (!RB_EMPTY(yang_modules, &yang_modules)) { module = RB_ROOT(yang_modules, &yang_modules); /* * We shouldn't call ly_ctx_remove_module() here because this * function also removes other modules that depend on it. * * ly_ctx_destroy() will release all memory for us. */ RB_REMOVE(yang_modules, &yang_modules, module); XFREE(MTYPE_YANG_MODULE, module); } ly_ctx_destroy(ly_native_ctx, NULL); } frr-7.2.1/lib/yang.h0000644000000000000000000003035413610377563011076 00000000000000/* * Copyright (C) 2018 NetDEF, Inc. * Renato Westphal * * This program is free software; you can redistribute it and/or modify it * under the terms of the 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; see the file COPYING; if not, write to the Free Software * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA */ #ifndef _FRR_YANG_H_ #define _FRR_YANG_H_ #include "memory.h" #include #ifdef HAVE_SYSREPO #include #endif #include "yang_wrappers.h" #ifdef __cplusplus extern "C" { #endif /* Maximum XPath length. */ #define XPATH_MAXLEN 256 /* Maximum list key length. */ #define LIST_MAXKEYS 8 /* Maximum list key length. */ #define LIST_MAXKEYLEN 128 /* Maximum string length of an YANG value. */ #define YANG_VALUE_MAXLEN 1024 struct yang_module_embed { struct yang_module_embed *next; const char *mod_name, *mod_rev; const char *data; LYS_INFORMAT format; }; struct yang_module { RB_ENTRY(yang_module) entry; const char *name; const struct lys_module *info; #ifdef HAVE_CONFD int confd_hash; #endif #ifdef HAVE_SYSREPO sr_subscription_ctx_t *sr_subscription; #endif }; RB_HEAD(yang_modules, yang_module); RB_PROTOTYPE(yang_modules, yang_module, entry, yang_module_compare); struct yang_data { /* XPath identifier of the data element. */ char xpath[XPATH_MAXLEN]; /* Value encoded as a raw string. */ char *value; }; struct yang_list_keys { /* Number os keys (max: LIST_MAXKEYS). */ uint8_t num; /* Value encoded as a raw string. */ char key[LIST_MAXKEYS][LIST_MAXKEYLEN]; }; enum yang_path_type { YANG_PATH_SCHEMA = 0, YANG_PATH_DATA, }; enum yang_iter_flags { /* Filter non-presence containers. */ YANG_ITER_FILTER_NPCONTAINERS = (1<<0), /* Filter list keys (leafs). */ YANG_ITER_FILTER_LIST_KEYS = (1<<1), /* Filter RPC input/output nodes. */ YANG_ITER_FILTER_INPUT_OUTPUT = (1<<2), /* Filter implicitely created nodes. */ YANG_ITER_FILTER_IMPLICIT = (1<<3), /* Allow iteration over augmentations. */ YANG_ITER_ALLOW_AUGMENTATIONS = (1<<4), }; /* Callback used by the yang_snodes_iterate_*() family of functions. */ typedef int (*yang_iterate_cb)(const struct lys_node *snode, void *arg); /* Return values of the 'yang_iterate_cb' callback. */ #define YANG_ITER_CONTINUE 0 #define YANG_ITER_STOP -1 /* Global libyang context for native FRR models. */ extern struct ly_ctx *ly_native_ctx; /* Tree of all loaded YANG modules. */ extern struct yang_modules yang_modules; /* * Create a new YANG module and load it using libyang. If the YANG module is not * found in the YANG_MODELS_PATH directory, the program will exit with an error. * Once loaded, a YANG module can't be unloaded anymore. * * module_name * Name of the YANG module. * * Returns: * Pointer to newly created YANG module. */ extern struct yang_module *yang_module_load(const char *module_name); /* * Load all FRR native YANG models. */ extern void yang_module_load_all(void); /* * Find a YANG module by its name. * * module_name * Name of the YANG module. * * Returns: * Pointer to YANG module if found, NULL otherwise. */ extern struct yang_module *yang_module_find(const char *module_name); /* * Register a YANG module embedded in the binary file. Should be called * from a constructor function. * * embed * YANG module embedding structure to register. (static global provided * by caller.) */ extern void yang_module_embed(struct yang_module_embed *embed); /* * Iterate recursively over all children of a schema node. * * snode * YANG schema node to operate on. * * cb * Function to call with each schema node. * * flags * YANG_ITER_* flags to control how the iteration is performed. * * arg * Arbitrary argument passed as the second parameter in each call to 'cb'. * * Returns: * The return value of the last called callback. */ extern int yang_snodes_iterate_subtree(const struct lys_node *snode, yang_iterate_cb cb, uint16_t flags, void *arg); /* * Iterate over all libyang schema nodes from the given YANG module. * * module * YANG module to operate on. * * cb * Function to call with each schema node. * * flags * YANG_ITER_* flags to control how the iteration is performed. * * arg * Arbitrary argument passed as the second parameter in each call to 'cb'. * * Returns: * The return value of the last called callback. */ extern int yang_snodes_iterate_module(const struct lys_module *module, yang_iterate_cb cb, uint16_t flags, void *arg); /* * Iterate over all libyang schema nodes from all loaded YANG modules. * * cb * Function to call with each schema node. * * flags * YANG_ITER_* flags to control how the iteration is performed. * * arg * Arbitrary argument passed as the second parameter in each call to 'cb'. * * Returns: * The return value of the last called callback. */ extern int yang_snodes_iterate_all(yang_iterate_cb cb, uint16_t flags, void *arg); /* * Build schema path or data path of the schema node. * * snode * libyang schema node to be processed. * * type * Specify whether a schema path or a data path should be built. * * xpath * Pointer to previously allocated buffer. * * xpath_len * Size of the xpath buffer. */ extern void yang_snode_get_path(const struct lys_node *snode, enum yang_path_type type, char *xpath, size_t xpath_len); /* * Find first parent schema node which is a presence-container or a list * (non-presence containers are ignored). * * snode * libyang schema node to operate on. * * Returns: * The parent libyang schema node if found, or NULL if not found. */ extern struct lys_node *yang_snode_real_parent(const struct lys_node *snode); /* * Find first parent schema node which is a list. * * snode * libyang schema node to operate on. * * Returns: * The parent libyang schema node (list) if found, or NULL if not found. */ extern struct lys_node *yang_snode_parent_list(const struct lys_node *snode); /* * Check if the libyang schema node represents typeless data (e.g. containers, * leafs of type empty, etc). * * snode * libyang schema node to operate on. * * Returns: * true if the schema node represents typeless data, false otherwise. */ extern bool yang_snode_is_typeless_data(const struct lys_node *snode); /* * Get the default value associated to a YANG leaf or leaf-list. * * snode * libyang schema node to operate on. * * Returns: * The default value if it exists, NULL otherwise. */ extern const char *yang_snode_get_default(const struct lys_node *snode); /* * Get the type structure of a leaf of leaf-list. If the type is a leafref, the * final (if there is a chain of leafrefs) target's type is found. * * snode * libyang schema node to operate on. * * Returns: * The found type if the schema node represents a leaf or a leaf-list, NULL * otherwise. */ extern const struct lys_type *yang_snode_get_type(const struct lys_node *snode); /* * Build data path of the data node. * * dnode * libyang data node to be processed. * * xpath * Pointer to previously allocated buffer. * * xpath_len * Size of the xpath buffer. */ extern void yang_dnode_get_path(const struct lyd_node *dnode, char *xpath, size_t xpath_len); /* * Return the schema name of the given libyang data node. * * dnode * libyang data node. * * xpath_fmt * Optional XPath expression (absolute or relative) to specify a different * data node to operate on in the same data tree. * * Returns: * Schema name of the libyang data node. */ extern const char *yang_dnode_get_schema_name(const struct lyd_node *dnode, const char *xpath_fmt, ...); /* * Find a libyang data node by its YANG data path. * * dnode * Base libyang data node to operate on. * * xpath_fmt * XPath expression (absolute or relative). * * Returns: * The libyang data node if found, or NULL if not found. */ extern struct lyd_node *yang_dnode_get(const struct lyd_node *dnode, const char *xpath_fmt, ...); /* * Check if a libyang data node exists. * * dnode * Base libyang data node to operate on. * * xpath_fmt * XPath expression (absolute or relative). * * Returns: * true if the libyang data node was found, false otherwise. */ extern bool yang_dnode_exists(const struct lyd_node *dnode, const char *xpath_fmt, ...); /* * Check if the libyang data node contains a default value. Non-presence * containers are assumed to always contain a default value. * * dnode * Base libyang data node to operate on. * * xpath_fmt * Optional XPath expression (absolute or relative) to specify a different * data node to operate on in the same data tree. * * Returns: * true if the data node contains the default value, false otherwise. */ extern bool yang_dnode_is_default(const struct lyd_node *dnode, const char *xpath_fmt, ...); /* * Check if the libyang data node and all of its children contain default * values. Non-presence containers are assumed to always contain a default * value. * * dnode * libyang data node to operate on. * * Returns: * true if the data node and all of its children contain default values, * false otherwise. */ extern bool yang_dnode_is_default_recursive(const struct lyd_node *dnode); /* * Change the value of a libyang leaf node. * * dnode * libyang data node to operate on. * * value * String representing the new value. */ extern void yang_dnode_change_leaf(struct lyd_node *dnode, const char *value); /* * Create a new libyang data node. * * ly_ctx * libyang context to operate on. * * config * Specify whether the data node will contain only configuration data (true) * or both configuration data and state data (false). * * Returns: * Pointer to newly created libyang data node. */ extern struct lyd_node *yang_dnode_new(struct ly_ctx *ly_ctx, bool config_only); /* * Duplicate a libyang data node. * * dnode * libyang data node to duplicate. * * Returns: * Pointer to duplicated libyang data node. */ extern struct lyd_node *yang_dnode_dup(const struct lyd_node *dnode); /* * Delete a libyang data node. * * dnode * Pointer to the libyang data node that is going to be deleted. */ extern void yang_dnode_free(struct lyd_node *dnode); /* * Create a new yang_data structure. * * xpath * Data path of the YANG data. * * value * String representing the value of the YANG data. * * Returns: * Pointer to newly created yang_data structure. */ extern struct yang_data *yang_data_new(const char *xpath, const char *value); /* * Delete a yang_data structure. * * data * yang_data to delete. */ extern void yang_data_free(struct yang_data *data); /* * Create a new linked list of yang_data structures. The list 'del' callback is * initialized appropriately so that the entire list can be deleted safely with * list_delete_and_null(). * * Returns: * Pointer to newly created linked list. */ extern struct list *yang_data_list_new(void); /* * Find the yang_data structure corresponding to an XPath in a list. * * list * list of yang_data structures to operate on. * * xpath_fmt * XPath to search for (format string). * * Returns: * Pointer to yang_data if found, NULL otherwise. */ extern struct yang_data *yang_data_list_find(const struct list *list, const char *xpath_fmt, ...); /* * Create and set up a libyang context (for use by the translator) */ extern struct ly_ctx *yang_ctx_new_setup(void); /* * Initialize the YANG subsystem. Should be called only once during the * daemon initialization process. */ extern void yang_init(void); /* * Finish the YANG subsystem gracefully. Should be called only when the daemon * is exiting. */ extern void yang_terminate(void); #ifdef __cplusplus } #endif #endif /* _FRR_YANG_H_ */ frr-7.2.1/lib/yang_translator.c0000644000000000000000000003563113610377563013345 00000000000000/* * Copyright (C) 2018 NetDEF, Inc. * Renato Westphal * * This program is free software; you can redistribute it and/or modify it * under the terms of the 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; see the file COPYING; if not, write to the Free Software * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA */ #include #include "log.h" #include "lib_errors.h" #include "hash.h" #include "yang.h" #include "yang_translator.h" #include "frrstr.h" DEFINE_MTYPE_STATIC(LIB, YANG_TRANSLATOR, "YANG Translator") DEFINE_MTYPE_STATIC(LIB, YANG_TRANSLATOR_MODULE, "YANG Translator Module") DEFINE_MTYPE_STATIC(LIB, YANG_TRANSLATOR_MAPPING, "YANG Translator Mapping") /* Generate the yang_translators tree. */ static inline int yang_translator_compare(const struct yang_translator *a, const struct yang_translator *b) { return strcmp(a->family, b->family); } RB_GENERATE(yang_translators, yang_translator, entry, yang_translator_compare) struct yang_translators yang_translators = RB_INITIALIZER(&yang_translators); /* Separate libyang context for the translator module. */ static struct ly_ctx *ly_translator_ctx; static unsigned int yang_translator_validate(struct yang_translator *translator); static unsigned int yang_module_nodes_count(const struct lys_module *module); struct yang_mapping_node { char xpath_from_canonical[XPATH_MAXLEN]; char xpath_from_fmt[XPATH_MAXLEN]; char xpath_to_fmt[XPATH_MAXLEN]; }; static bool yang_mapping_hash_cmp(const void *value1, const void *value2) { const struct yang_mapping_node *c1 = value1; const struct yang_mapping_node *c2 = value2; return strmatch(c1->xpath_from_canonical, c2->xpath_from_canonical); } static unsigned int yang_mapping_hash_key(const void *value) { return string_hash_make(value); } static void *yang_mapping_hash_alloc(void *p) { struct yang_mapping_node *new, *key = p; new = XCALLOC(MTYPE_YANG_TRANSLATOR_MAPPING, sizeof(*new)); strlcpy(new->xpath_from_canonical, key->xpath_from_canonical, sizeof(new->xpath_from_canonical)); return new; } static void yang_mapping_hash_free(void *arg) { XFREE(MTYPE_YANG_TRANSLATOR_MAPPING, arg); } static struct yang_mapping_node * yang_mapping_lookup(const struct yang_translator *translator, int dir, const char *xpath) { struct yang_mapping_node s; strlcpy(s.xpath_from_canonical, xpath, sizeof(s.xpath_from_canonical)); return hash_lookup(translator->mappings[dir], &s); } static void yang_mapping_add(struct yang_translator *translator, int dir, const struct lys_node *snode, const char *xpath_from_fmt, const char *xpath_to_fmt) { struct yang_mapping_node *mapping, s; yang_snode_get_path(snode, YANG_PATH_DATA, s.xpath_from_canonical, sizeof(s.xpath_from_canonical)); mapping = hash_get(translator->mappings[dir], &s, yang_mapping_hash_alloc); strlcpy(mapping->xpath_from_fmt, xpath_from_fmt, sizeof(mapping->xpath_from_fmt)); strlcpy(mapping->xpath_to_fmt, xpath_to_fmt, sizeof(mapping->xpath_to_fmt)); const char *keys[] = {"KEY1", "KEY2", "KEY3", "KEY4"}; char *xpfmt; for (unsigned int i = 0; i < array_size(keys); i++) { xpfmt = frrstr_replace(mapping->xpath_from_fmt, keys[i], "%[^']"); strlcpy(mapping->xpath_from_fmt, xpfmt, sizeof(mapping->xpath_from_fmt)); XFREE(MTYPE_TMP, xpfmt); } for (unsigned int i = 0; i < array_size(keys); i++) { xpfmt = frrstr_replace(mapping->xpath_to_fmt, keys[i], "%s"); strlcpy(mapping->xpath_to_fmt, xpfmt, sizeof(mapping->xpath_to_fmt)); XFREE(MTYPE_TMP, xpfmt); } } struct yang_translator *yang_translator_load(const char *path) { struct yang_translator *translator; struct yang_tmodule *tmodule; const char *family; struct lyd_node *dnode; struct ly_set *set; struct listnode *ln; /* Load module translator (JSON file). */ dnode = lyd_parse_path(ly_translator_ctx, path, LYD_JSON, LYD_OPT_CONFIG); if (!dnode) { flog_warn(EC_LIB_YANG_TRANSLATOR_LOAD, "%s: lyd_parse_path() failed", __func__); return NULL; } dnode = yang_dnode_get(dnode, "/frr-module-translator:frr-module-translator"); /* * libyang guarantees the "frr-module-translator" top-level container is * always present since it contains mandatory child nodes. */ assert(dnode); family = yang_dnode_get_string(dnode, "./family"); translator = yang_translator_find(family); if (translator != NULL) { flog_warn(EC_LIB_YANG_TRANSLATOR_LOAD, "%s: module translator \"%s\" is loaded already", __func__, family); return NULL; } translator = XCALLOC(MTYPE_YANG_TRANSLATOR, sizeof(*translator)); strlcpy(translator->family, family, sizeof(translator->family)); translator->modules = list_new(); for (size_t i = 0; i < YANG_TRANSLATE_MAX; i++) translator->mappings[i] = hash_create(yang_mapping_hash_key, yang_mapping_hash_cmp, "YANG translation table"); RB_INSERT(yang_translators, &yang_translators, translator); /* Initialize the translator libyang context. */ translator->ly_ctx = yang_ctx_new_setup(); if (!translator->ly_ctx) { flog_warn(EC_LIB_LIBYANG, "%s: ly_ctx_new() failed", __func__); goto error; } /* Load modules and deviations. */ set = lyd_find_path(dnode, "./module"); assert(set); for (size_t i = 0; i < set->number; i++) { const char *module_name; tmodule = XCALLOC(MTYPE_YANG_TRANSLATOR_MODULE, sizeof(*tmodule)); module_name = yang_dnode_get_string(set->set.d[i], "./name"); tmodule->module = ly_ctx_load_module(translator->ly_ctx, module_name, NULL); if (!tmodule->module) { flog_warn(EC_LIB_YANG_TRANSLATOR_LOAD, "%s: failed to load module: %s", __func__, module_name); ly_set_free(set); goto error; } module_name = yang_dnode_get_string(set->set.d[i], "./deviations"); tmodule->deviations = ly_ctx_load_module(translator->ly_ctx, module_name, NULL); if (!tmodule->deviations) { flog_warn(EC_LIB_YANG_TRANSLATOR_LOAD, "%s: failed to load module: %s", __func__, module_name); ly_set_free(set); goto error; } lys_set_disabled(tmodule->deviations); listnode_add(translator->modules, tmodule); } ly_set_free(set); /* Calculate the coverage. */ for (ALL_LIST_ELEMENTS_RO(translator->modules, ln, tmodule)) { tmodule->nodes_before_deviations = yang_module_nodes_count(tmodule->module); lys_set_enabled(tmodule->deviations); tmodule->nodes_after_deviations = yang_module_nodes_count(tmodule->module); tmodule->coverage = ((double)tmodule->nodes_after_deviations / (double)tmodule->nodes_before_deviations) * 100; } /* Load mappings. */ set = lyd_find_path(dnode, "./module/mappings"); assert(set); for (size_t i = 0; i < set->number; i++) { const char *xpath_custom, *xpath_native; const struct lys_node *snode_custom, *snode_native; xpath_custom = yang_dnode_get_string(set->set.d[i], "./custom"); snode_custom = ly_ctx_get_node(translator->ly_ctx, NULL, xpath_custom, 0); if (!snode_custom) { flog_warn(EC_LIB_YANG_TRANSLATOR_LOAD, "%s: unknown data path: %s", __func__, xpath_custom); ly_set_free(set); goto error; } xpath_native = yang_dnode_get_string(set->set.d[i], "./native"); snode_native = ly_ctx_get_node(ly_native_ctx, NULL, xpath_native, 0); if (!snode_native) { flog_warn(EC_LIB_YANG_TRANSLATOR_LOAD, "%s: unknown data path: %s", __func__, xpath_native); ly_set_free(set); goto error; } yang_mapping_add(translator, YANG_TRANSLATE_TO_NATIVE, snode_custom, xpath_custom, xpath_native); yang_mapping_add(translator, YANG_TRANSLATE_FROM_NATIVE, snode_native, xpath_native, xpath_custom); } ly_set_free(set); /* Validate mappings. */ if (yang_translator_validate(translator) != 0) goto error; yang_dnode_free(dnode); return translator; error: yang_dnode_free(dnode); yang_translator_unload(translator); return NULL; } static void yang_tmodule_delete(struct yang_tmodule *tmodule) { XFREE(MTYPE_YANG_TRANSLATOR_MODULE, tmodule); } void yang_translator_unload(struct yang_translator *translator) { for (size_t i = 0; i < YANG_TRANSLATE_MAX; i++) hash_clean(translator->mappings[i], yang_mapping_hash_free); translator->modules->del = (void (*)(void *))yang_tmodule_delete; list_delete(&translator->modules); ly_ctx_destroy(translator->ly_ctx, NULL); RB_REMOVE(yang_translators, &yang_translators, translator); XFREE(MTYPE_YANG_TRANSLATOR, translator); } struct yang_translator *yang_translator_find(const char *family) { struct yang_translator s; strlcpy(s.family, family, sizeof(s.family)); return RB_FIND(yang_translators, &yang_translators, &s); } enum yang_translate_result yang_translate_xpath(const struct yang_translator *translator, int dir, char *xpath, size_t xpath_len) { struct ly_ctx *ly_ctx; const struct lys_node *snode; struct yang_mapping_node *mapping; char xpath_canonical[XPATH_MAXLEN]; char keys[4][LIST_MAXKEYLEN]; int n; if (dir == YANG_TRANSLATE_TO_NATIVE) ly_ctx = translator->ly_ctx; else ly_ctx = ly_native_ctx; snode = ly_ctx_get_node(ly_ctx, NULL, xpath, 0); if (!snode) { flog_warn(EC_LIB_YANG_TRANSLATION_ERROR, "%s: unknown data path: %s", __func__, xpath); return YANG_TRANSLATE_FAILURE; } yang_snode_get_path(snode, YANG_PATH_DATA, xpath_canonical, sizeof(xpath_canonical)); mapping = yang_mapping_lookup(translator, dir, xpath_canonical); if (!mapping) return YANG_TRANSLATE_NOTFOUND; n = sscanf(xpath, mapping->xpath_from_fmt, keys[0], keys[1], keys[2], keys[3]); if (n < 0) { flog_warn(EC_LIB_YANG_TRANSLATION_ERROR, "%s: sscanf() failed: %s", __func__, safe_strerror(errno)); return YANG_TRANSLATE_FAILURE; } snprintf(xpath, xpath_len, mapping->xpath_to_fmt, keys[0], keys[1], keys[2], keys[3]); return YANG_TRANSLATE_SUCCESS; } int yang_translate_dnode(const struct yang_translator *translator, int dir, struct lyd_node **dnode) { struct ly_ctx *ly_ctx; struct lyd_node *new; struct lyd_node *root, *next, *dnode_iter; /* Create new libyang data node to hold the translated data. */ if (dir == YANG_TRANSLATE_TO_NATIVE) ly_ctx = ly_native_ctx; else ly_ctx = translator->ly_ctx; new = yang_dnode_new(ly_ctx, false); /* Iterate over all nodes from the data tree. */ LY_TREE_FOR (*dnode, root) { LY_TREE_DFS_BEGIN (root, next, dnode_iter) { char xpath[XPATH_MAXLEN]; enum yang_translate_result ret; yang_dnode_get_path(dnode_iter, xpath, sizeof(xpath)); ret = yang_translate_xpath(translator, dir, xpath, sizeof(xpath)); switch (ret) { case YANG_TRANSLATE_SUCCESS: break; case YANG_TRANSLATE_NOTFOUND: goto next; case YANG_TRANSLATE_FAILURE: goto error; } /* Create new node in the tree of translated data. */ ly_errno = 0; if (!lyd_new_path(new, ly_ctx, xpath, (void *)yang_dnode_get_string( dnode_iter, NULL), 0, LYD_PATH_OPT_UPDATE) && ly_errno) { flog_err(EC_LIB_LIBYANG, "%s: lyd_new_path() failed", __func__); goto error; } next: LY_TREE_DFS_END(root, next, dnode_iter); } } /* Replace dnode by the new translated dnode. */ yang_dnode_free(*dnode); *dnode = new; return YANG_TRANSLATE_SUCCESS; error: yang_dnode_free(new); return YANG_TRANSLATE_FAILURE; } struct translator_validate_args { struct yang_translator *translator; unsigned int errors; }; static int yang_translator_validate_cb(const struct lys_node *snode_custom, void *arg) { struct translator_validate_args *args = arg; struct yang_mapping_node *mapping; const struct lys_node *snode_native; const struct lys_type *stype_custom, *stype_native; char xpath[XPATH_MAXLEN]; yang_snode_get_path(snode_custom, YANG_PATH_DATA, xpath, sizeof(xpath)); mapping = yang_mapping_lookup(args->translator, YANG_TRANSLATE_TO_NATIVE, xpath); if (!mapping) { flog_warn(EC_LIB_YANG_TRANSLATOR_LOAD, "%s: missing mapping for \"%s\"", __func__, xpath); args->errors += 1; return YANG_ITER_CONTINUE; } snode_native = ly_ctx_get_node(ly_native_ctx, NULL, mapping->xpath_to_fmt, 0); assert(snode_native); /* Check if the YANG types are compatible. */ stype_custom = yang_snode_get_type(snode_custom); stype_native = yang_snode_get_type(snode_native); if (stype_custom && stype_native) { if (stype_custom->base != stype_native->base) { flog_warn( EC_LIB_YANG_TRANSLATOR_LOAD, "%s: YANG types are incompatible (xpath: \"%s\")", __func__, xpath); args->errors += 1; return YANG_ITER_CONTINUE; } /* TODO: check if the value spaces are identical. */ } return YANG_ITER_CONTINUE; } /* * Check if the modules from the translator have a mapping for all of their * schema nodes (after loading the deviations). */ static unsigned int yang_translator_validate(struct yang_translator *translator) { struct yang_tmodule *tmodule; struct listnode *ln; struct translator_validate_args args; args.translator = translator; args.errors = 0; for (ALL_LIST_ELEMENTS_RO(translator->modules, ln, tmodule)) { yang_snodes_iterate_module( tmodule->module, yang_translator_validate_cb, YANG_ITER_FILTER_NPCONTAINERS | YANG_ITER_FILTER_LIST_KEYS | YANG_ITER_FILTER_INPUT_OUTPUT, &args); } if (args.errors) flog_warn( EC_LIB_YANG_TRANSLATOR_LOAD, "%s: failed to validate \"%s\" module translator: %u error(s)", __func__, translator->family, args.errors); return args.errors; } static int yang_module_nodes_count_cb(const struct lys_node *snode, void *arg) { unsigned int *total = arg; *total += 1; return YANG_ITER_CONTINUE; } /* Calculate the number of nodes for the given module. */ static unsigned int yang_module_nodes_count(const struct lys_module *module) { unsigned int total = 0; yang_snodes_iterate_module(module, yang_module_nodes_count_cb, YANG_ITER_FILTER_NPCONTAINERS | YANG_ITER_FILTER_LIST_KEYS | YANG_ITER_FILTER_INPUT_OUTPUT, &total); return total; } void yang_translator_init(void) { ly_translator_ctx = yang_ctx_new_setup(); if (!ly_translator_ctx) { flog_err(EC_LIB_LIBYANG, "%s: ly_ctx_new() failed", __func__); exit(1); } if (!ly_ctx_load_module(ly_translator_ctx, "frr-module-translator", NULL)) { flog_err( EC_LIB_YANG_MODULE_LOAD, "%s: failed to load the \"frr-module-translator\" module", __func__); exit(1); } } void yang_translator_terminate(void) { while (!RB_EMPTY(yang_translators, &yang_translators)) { struct yang_translator *translator; translator = RB_ROOT(yang_translators, &yang_translators); yang_translator_unload(translator); } ly_ctx_destroy(ly_translator_ctx, NULL); } frr-7.2.1/lib/yang_translator.h0000644000000000000000000001014113610377563013337 00000000000000/* * Copyright (C) 2018 NetDEF, Inc. * Renato Westphal * * This program is free software; you can redistribute it and/or modify it * under the terms of the 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; see the file COPYING; if not, write to the Free Software * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA */ #ifndef _FRR_YANG_TRANSLATOR_H_ #define _FRR_YANG_TRANSLATOR_H_ #ifdef __cplusplus extern "C" { #endif #define YANG_TRANSLATE_TO_NATIVE 0 #define YANG_TRANSLATE_FROM_NATIVE 1 #define YANG_TRANSLATE_MAX 2 struct yang_tmodule { const struct lys_module *module; const struct lys_module *deviations; uint32_t nodes_before_deviations; uint32_t nodes_after_deviations; double coverage; }; struct yang_translator { RB_ENTRY(yang_translator) entry; char family[32]; struct ly_ctx *ly_ctx; struct list *modules; struct hash *mappings[YANG_TRANSLATE_MAX]; }; RB_HEAD(yang_translators, yang_translator); RB_PROTOTYPE(yang_translators, yang_translator, entry, yang_translator_compare); enum yang_translate_result { YANG_TRANSLATE_SUCCESS, YANG_TRANSLATE_NOTFOUND, YANG_TRANSLATE_FAILURE, }; /* Tree of all loaded YANG module translators. */ extern struct yang_translators yang_translators; /* * Load a YANG module translator from a JSON file. * * path * Absolute path to the module translator file. * * Returns: * Pointer to newly created YANG module translator, or NULL in the case of an * error. */ extern struct yang_translator *yang_translator_load(const char *path); /* * Unload a YANG module translator. * * translator * Pointer to the YANG module translator. */ extern void yang_translator_unload(struct yang_translator *translator); /* * Find a YANG module translator by its family name. * * family * Family of the YANG module translator (e.g. ietf, openconfig). * * Returns: * Pointer to the YANG module translator if found, NULL otherwise. */ extern struct yang_translator *yang_translator_find(const char *family); /* * Translate an XPath expression. * * translator * Pointer to YANG module translator. * * dir * Direction of the translation (either YANG_TRANSLATE_TO_NATIVE or * YANG_TRANSLATE_FROM_NATIVE). * * xpath * Pointer to previously allocated buffer containing the xpath expression to * be translated. * * xpath_len * Size of the xpath buffer. * * Returns: * - YANG_TRANSLATE_SUCCESS on success. * - YANG_TRANSLATE_NOTFOUND when there's no available mapping to perform * the translation. * - YANG_TRANSLATE_FAILURE when an error occurred during the translation. */ extern enum yang_translate_result yang_translate_xpath(const struct yang_translator *translator, int dir, char *xpath, size_t xpath_len); /* * Translate an entire libyang data node. * * translator * Pointer to YANG module translator. * * dir * Direction of the translation (either YANG_TRANSLATE_TO_NATIVE or * YANG_TRANSLATE_FROM_NATIVE). * * dnode * libyang schema node we want to translate. * * Returns: * - YANG_TRANSLATE_SUCCESS on success. * - YANG_TRANSLATE_FAILURE when an error occurred during the translation. */ extern int yang_translate_dnode(const struct yang_translator *translator, int dir, struct lyd_node **dnode); /* * Initialize the YANG module translator subsystem. Should be called only once * during the daemon initialization process. */ extern void yang_translator_init(void); /* * Finish the YANG module translator subsystem gracefully. Should be called only * when the daemon is exiting. */ extern void yang_translator_terminate(void); #ifdef __cplusplus } #endif #endif /* _FRR_YANG_TRANSLATOR_H_ */ frr-7.2.1/lib/yang_wrappers.c0000644000000000000000000005507313610377563013021 00000000000000/* * Copyright (C) 2018 NetDEF, Inc. * Renato Westphal * * This program is free software; you can redistribute it and/or modify it * under the terms of the 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; see the file COPYING; if not, write to the Free Software * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA */ #include #include "log.h" #include "lib_errors.h" #include "northbound.h" static const char *yang_get_default_value(const char *xpath) { const struct lys_node *snode; const char *value; snode = ly_ctx_get_node(ly_native_ctx, NULL, xpath, 0); if (snode == NULL) { flog_err(EC_LIB_YANG_UNKNOWN_DATA_PATH, "%s: unknown data path: %s", __func__, xpath); zlog_backtrace(LOG_ERR); abort(); } value = yang_snode_get_default(snode); assert(value); return value; } #define YANG_DNODE_GET_ASSERT(dnode, xpath) \ do { \ if ((dnode) == NULL) { \ flog_err(EC_LIB_YANG_DNODE_NOT_FOUND, \ "%s: couldn't find %s", __func__, (xpath)); \ zlog_backtrace(LOG_ERR); \ abort(); \ } \ } while (0) /* * Primitive type: bool. */ bool yang_str2bool(const char *value) { return strmatch(value, "true"); } struct yang_data *yang_data_new_bool(const char *xpath, bool value) { return yang_data_new(xpath, (value) ? "true" : "false"); } bool yang_dnode_get_bool(const struct lyd_node *dnode, const char *xpath_fmt, ...) { const struct lyd_node_leaf_list *dleaf; assert(dnode); if (xpath_fmt) { va_list ap; char xpath[XPATH_MAXLEN]; va_start(ap, xpath_fmt); vsnprintf(xpath, sizeof(xpath), xpath_fmt, ap); va_end(ap); dnode = yang_dnode_get(dnode, xpath); YANG_DNODE_GET_ASSERT(dnode, xpath); } dleaf = (const struct lyd_node_leaf_list *)dnode; assert(dleaf->value_type == LY_TYPE_BOOL); return dleaf->value.bln; } bool yang_get_default_bool(const char *xpath_fmt, ...) { char xpath[XPATH_MAXLEN]; const char *value; va_list ap; va_start(ap, xpath_fmt); vsnprintf(xpath, sizeof(xpath), xpath_fmt, ap); va_end(ap); value = yang_get_default_value(xpath); return yang_str2bool(value); } /* * Primitive type: dec64. */ double yang_str2dec64(const char *xpath, const char *value) { double dbl = 0; if (sscanf(value, "%lf", &dbl) != 1) { flog_err(EC_LIB_YANG_DATA_CONVERT, "%s: couldn't convert string to decimal64 [xpath %s]", __func__, xpath); zlog_backtrace(LOG_ERR); abort(); } return dbl; } struct yang_data *yang_data_new_dec64(const char *xpath, double value) { char value_str[BUFSIZ]; snprintf(value_str, sizeof(value_str), "%lf", value); return yang_data_new(xpath, value_str); } double yang_dnode_get_dec64(const struct lyd_node *dnode, const char *xpath_fmt, ...) { const struct lyd_node_leaf_list *dleaf; assert(dnode); if (xpath_fmt) { va_list ap; char xpath[XPATH_MAXLEN]; va_start(ap, xpath_fmt); vsnprintf(xpath, sizeof(xpath), xpath_fmt, ap); va_end(ap); dnode = yang_dnode_get(dnode, xpath); YANG_DNODE_GET_ASSERT(dnode, xpath); } dleaf = (const struct lyd_node_leaf_list *)dnode; assert(dleaf->value_type == LY_TYPE_DEC64); return lyd_dec64_to_double(dnode); } double yang_get_default_dec64(const char *xpath_fmt, ...) { char xpath[XPATH_MAXLEN]; const char *value; va_list ap; va_start(ap, xpath_fmt); vsnprintf(xpath, sizeof(xpath), xpath_fmt, ap); va_end(ap); value = yang_get_default_value(xpath); return yang_str2dec64(xpath, value); } /* * Primitive type: enum. */ int yang_str2enum(const char *xpath, const char *value) { const struct lys_node *snode; const struct lys_node_leaf *sleaf; const struct lys_type *type; const struct lys_type_info_enums *enums; snode = ly_ctx_get_node(ly_native_ctx, NULL, xpath, 0); if (snode == NULL) { flog_err(EC_LIB_YANG_UNKNOWN_DATA_PATH, "%s: unknown data path: %s", __func__, xpath); zlog_backtrace(LOG_ERR); abort(); } sleaf = (const struct lys_node_leaf *)snode; type = &sleaf->type; enums = &type->info.enums; while (enums->count == 0 && type->der) { type = &type->der->type; enums = &type->info.enums; } for (unsigned int i = 0; i < enums->count; i++) { const struct lys_type_enum *enm = &enums->enm[i]; if (strmatch(value, enm->name)) return enm->value; } flog_err(EC_LIB_YANG_DATA_CONVERT, "%s: couldn't convert string to enum [xpath %s]", __func__, xpath); zlog_backtrace(LOG_ERR); abort(); } struct yang_data *yang_data_new_enum(const char *xpath, int value) { const struct lys_node *snode; const struct lys_node_leaf *sleaf; const struct lys_type *type; const struct lys_type_info_enums *enums; snode = ly_ctx_get_node(ly_native_ctx, NULL, xpath, 0); if (snode == NULL) { flog_err(EC_LIB_YANG_UNKNOWN_DATA_PATH, "%s: unknown data path: %s", __func__, xpath); zlog_backtrace(LOG_ERR); abort(); } sleaf = (const struct lys_node_leaf *)snode; type = &sleaf->type; enums = &type->info.enums; while (enums->count == 0 && type->der) { type = &type->der->type; enums = &type->info.enums; } for (unsigned int i = 0; i < enums->count; i++) { const struct lys_type_enum *enm = &enums->enm[i]; if (value == enm->value) return yang_data_new(xpath, enm->name); } flog_err(EC_LIB_YANG_DATA_CONVERT, "%s: couldn't convert enum to string [xpath %s]", __func__, xpath); zlog_backtrace(LOG_ERR); abort(); } int yang_dnode_get_enum(const struct lyd_node *dnode, const char *xpath_fmt, ...) { const struct lyd_node_leaf_list *dleaf; assert(dnode); if (xpath_fmt) { va_list ap; char xpath[XPATH_MAXLEN]; va_start(ap, xpath_fmt); vsnprintf(xpath, sizeof(xpath), xpath_fmt, ap); va_end(ap); dnode = yang_dnode_get(dnode, xpath); YANG_DNODE_GET_ASSERT(dnode, xpath); } dleaf = (const struct lyd_node_leaf_list *)dnode; assert(dleaf->value_type == LY_TYPE_ENUM); return dleaf->value.enm->value; } int yang_get_default_enum(const char *xpath_fmt, ...) { char xpath[XPATH_MAXLEN]; const char *value; va_list ap; va_start(ap, xpath_fmt); vsnprintf(xpath, sizeof(xpath), xpath_fmt, ap); va_end(ap); value = yang_get_default_value(xpath); return yang_str2enum(xpath, value); } /* * Primitive type: int8. */ int8_t yang_str2int8(const char *value) { return strtol(value, NULL, 10); } struct yang_data *yang_data_new_int8(const char *xpath, int8_t value) { char value_str[BUFSIZ]; snprintf(value_str, sizeof(value_str), "%d", value); return yang_data_new(xpath, value_str); } int8_t yang_dnode_get_int8(const struct lyd_node *dnode, const char *xpath_fmt, ...) { const struct lyd_node_leaf_list *dleaf; assert(dnode); if (xpath_fmt) { va_list ap; char xpath[XPATH_MAXLEN]; va_start(ap, xpath_fmt); vsnprintf(xpath, sizeof(xpath), xpath_fmt, ap); va_end(ap); dnode = yang_dnode_get(dnode, xpath); YANG_DNODE_GET_ASSERT(dnode, xpath); } dleaf = (const struct lyd_node_leaf_list *)dnode; assert(dleaf->value_type == LY_TYPE_INT8); return dleaf->value.int8; } int8_t yang_get_default_int8(const char *xpath_fmt, ...) { char xpath[XPATH_MAXLEN]; const char *value; va_list ap; va_start(ap, xpath_fmt); vsnprintf(xpath, sizeof(xpath), xpath_fmt, ap); va_end(ap); value = yang_get_default_value(xpath); return yang_str2int8(value); } /* * Primitive type: int16. */ int16_t yang_str2int16(const char *value) { return strtol(value, NULL, 10); } struct yang_data *yang_data_new_int16(const char *xpath, int16_t value) { char value_str[BUFSIZ]; snprintf(value_str, sizeof(value_str), "%d", value); return yang_data_new(xpath, value_str); } int16_t yang_dnode_get_int16(const struct lyd_node *dnode, const char *xpath_fmt, ...) { const struct lyd_node_leaf_list *dleaf; assert(dnode); if (xpath_fmt) { va_list ap; char xpath[XPATH_MAXLEN]; va_start(ap, xpath_fmt); vsnprintf(xpath, sizeof(xpath), xpath_fmt, ap); va_end(ap); dnode = yang_dnode_get(dnode, xpath); YANG_DNODE_GET_ASSERT(dnode, xpath); } dleaf = (const struct lyd_node_leaf_list *)dnode; assert(dleaf->value_type == LY_TYPE_INT16); return dleaf->value.int16; } int16_t yang_get_default_int16(const char *xpath_fmt, ...) { char xpath[XPATH_MAXLEN]; const char *value; va_list ap; va_start(ap, xpath_fmt); vsnprintf(xpath, sizeof(xpath), xpath_fmt, ap); va_end(ap); value = yang_get_default_value(xpath); return yang_str2int16(value); } /* * Primitive type: int32. */ int32_t yang_str2int32(const char *value) { return strtol(value, NULL, 10); } struct yang_data *yang_data_new_int32(const char *xpath, int32_t value) { char value_str[BUFSIZ]; snprintf(value_str, sizeof(value_str), "%d", value); return yang_data_new(xpath, value_str); } int32_t yang_dnode_get_int32(const struct lyd_node *dnode, const char *xpath_fmt, ...) { const struct lyd_node_leaf_list *dleaf; assert(dnode); if (xpath_fmt) { va_list ap; char xpath[XPATH_MAXLEN]; va_start(ap, xpath_fmt); vsnprintf(xpath, sizeof(xpath), xpath_fmt, ap); va_end(ap); dnode = yang_dnode_get(dnode, xpath); YANG_DNODE_GET_ASSERT(dnode, xpath); } dleaf = (const struct lyd_node_leaf_list *)dnode; assert(dleaf->value_type == LY_TYPE_INT32); return dleaf->value.int32; } int32_t yang_get_default_int32(const char *xpath_fmt, ...) { char xpath[XPATH_MAXLEN]; const char *value; va_list ap; va_start(ap, xpath_fmt); vsnprintf(xpath, sizeof(xpath), xpath_fmt, ap); va_end(ap); value = yang_get_default_value(xpath); return yang_str2int32(value); } /* * Primitive type: int64. */ int64_t yang_str2int64(const char *value) { return strtoll(value, NULL, 10); } struct yang_data *yang_data_new_int64(const char *xpath, int64_t value) { char value_str[BUFSIZ]; snprintf(value_str, sizeof(value_str), "%" PRId64, value); return yang_data_new(xpath, value_str); } int64_t yang_dnode_get_int64(const struct lyd_node *dnode, const char *xpath_fmt, ...) { const struct lyd_node_leaf_list *dleaf; assert(dnode); if (xpath_fmt) { va_list ap; char xpath[XPATH_MAXLEN]; va_start(ap, xpath_fmt); vsnprintf(xpath, sizeof(xpath), xpath_fmt, ap); va_end(ap); dnode = yang_dnode_get(dnode, xpath); YANG_DNODE_GET_ASSERT(dnode, xpath); } dleaf = (const struct lyd_node_leaf_list *)dnode; assert(dleaf->value_type == LY_TYPE_INT64); return dleaf->value.int64; } int64_t yang_get_default_int64(const char *xpath_fmt, ...) { char xpath[XPATH_MAXLEN]; const char *value; va_list ap; va_start(ap, xpath_fmt); vsnprintf(xpath, sizeof(xpath), xpath_fmt, ap); va_end(ap); value = yang_get_default_value(xpath); return yang_str2int64(value); } /* * Primitive type: uint8. */ uint8_t yang_str2uint8(const char *value) { return strtoul(value, NULL, 10); } struct yang_data *yang_data_new_uint8(const char *xpath, uint8_t value) { char value_str[BUFSIZ]; snprintf(value_str, sizeof(value_str), "%u", value); return yang_data_new(xpath, value_str); } uint8_t yang_dnode_get_uint8(const struct lyd_node *dnode, const char *xpath_fmt, ...) { const struct lyd_node_leaf_list *dleaf; assert(dnode); if (xpath_fmt) { va_list ap; char xpath[XPATH_MAXLEN]; va_start(ap, xpath_fmt); vsnprintf(xpath, sizeof(xpath), xpath_fmt, ap); va_end(ap); dnode = yang_dnode_get(dnode, xpath); YANG_DNODE_GET_ASSERT(dnode, xpath); } dleaf = (const struct lyd_node_leaf_list *)dnode; assert(dleaf->value_type == LY_TYPE_UINT8); return dleaf->value.uint8; } uint8_t yang_get_default_uint8(const char *xpath_fmt, ...) { char xpath[XPATH_MAXLEN]; const char *value; va_list ap; va_start(ap, xpath_fmt); vsnprintf(xpath, sizeof(xpath), xpath_fmt, ap); va_end(ap); value = yang_get_default_value(xpath); return yang_str2uint8(value); } /* * Primitive type: uint16. */ uint16_t yang_str2uint16(const char *value) { return strtoul(value, NULL, 10); } struct yang_data *yang_data_new_uint16(const char *xpath, uint16_t value) { char value_str[BUFSIZ]; snprintf(value_str, sizeof(value_str), "%u", value); return yang_data_new(xpath, value_str); } uint16_t yang_dnode_get_uint16(const struct lyd_node *dnode, const char *xpath_fmt, ...) { const struct lyd_node_leaf_list *dleaf; assert(dnode); if (xpath_fmt) { va_list ap; char xpath[XPATH_MAXLEN]; va_start(ap, xpath_fmt); vsnprintf(xpath, sizeof(xpath), xpath_fmt, ap); va_end(ap); dnode = yang_dnode_get(dnode, xpath); YANG_DNODE_GET_ASSERT(dnode, xpath); } dleaf = (const struct lyd_node_leaf_list *)dnode; assert(dleaf->value_type == LY_TYPE_UINT16); return dleaf->value.uint16; } uint16_t yang_get_default_uint16(const char *xpath_fmt, ...) { char xpath[XPATH_MAXLEN]; const char *value; va_list ap; va_start(ap, xpath_fmt); vsnprintf(xpath, sizeof(xpath), xpath_fmt, ap); va_end(ap); value = yang_get_default_value(xpath); return yang_str2uint16(value); } /* * Primitive type: uint32. */ uint32_t yang_str2uint32(const char *value) { return strtoul(value, NULL, 10); } struct yang_data *yang_data_new_uint32(const char *xpath, uint32_t value) { char value_str[BUFSIZ]; snprintf(value_str, sizeof(value_str), "%u", value); return yang_data_new(xpath, value_str); } uint32_t yang_dnode_get_uint32(const struct lyd_node *dnode, const char *xpath_fmt, ...) { const struct lyd_node_leaf_list *dleaf; assert(dnode); if (xpath_fmt) { va_list ap; char xpath[XPATH_MAXLEN]; va_start(ap, xpath_fmt); vsnprintf(xpath, sizeof(xpath), xpath_fmt, ap); va_end(ap); dnode = yang_dnode_get(dnode, xpath); YANG_DNODE_GET_ASSERT(dnode, xpath); } dleaf = (const struct lyd_node_leaf_list *)dnode; assert(dleaf->value_type == LY_TYPE_UINT32); return dleaf->value.uint32; } uint32_t yang_get_default_uint32(const char *xpath_fmt, ...) { char xpath[XPATH_MAXLEN]; const char *value; va_list ap; va_start(ap, xpath_fmt); vsnprintf(xpath, sizeof(xpath), xpath_fmt, ap); va_end(ap); value = yang_get_default_value(xpath); return yang_str2uint32(value); } /* * Primitive type: uint64. */ uint64_t yang_str2uint64(const char *value) { return strtoull(value, NULL, 10); } struct yang_data *yang_data_new_uint64(const char *xpath, uint64_t value) { char value_str[BUFSIZ]; snprintf(value_str, sizeof(value_str), "%" PRIu64, value); return yang_data_new(xpath, value_str); } uint64_t yang_dnode_get_uint64(const struct lyd_node *dnode, const char *xpath_fmt, ...) { const struct lyd_node_leaf_list *dleaf; assert(dnode); if (xpath_fmt) { va_list ap; char xpath[XPATH_MAXLEN]; va_start(ap, xpath_fmt); vsnprintf(xpath, sizeof(xpath), xpath_fmt, ap); va_end(ap); dnode = yang_dnode_get(dnode, xpath); YANG_DNODE_GET_ASSERT(dnode, xpath); } dleaf = (const struct lyd_node_leaf_list *)dnode; assert(dleaf->value_type == LY_TYPE_UINT64); return dleaf->value.uint64; } uint64_t yang_get_default_uint64(const char *xpath_fmt, ...) { char xpath[XPATH_MAXLEN]; const char *value; va_list ap; va_start(ap, xpath_fmt); vsnprintf(xpath, sizeof(xpath), xpath_fmt, ap); va_end(ap); value = yang_get_default_value(xpath); return yang_str2uint64(value); } /* * Primitive type: string. * * All string wrappers can be used with non-string types. */ struct yang_data *yang_data_new_string(const char *xpath, const char *value) { return yang_data_new(xpath, value); } const char *yang_dnode_get_string(const struct lyd_node *dnode, const char *xpath_fmt, ...) { const struct lyd_node_leaf_list *dleaf; assert(dnode); if (xpath_fmt) { va_list ap; char xpath[XPATH_MAXLEN]; va_start(ap, xpath_fmt); vsnprintf(xpath, sizeof(xpath), xpath_fmt, ap); va_end(ap); dnode = yang_dnode_get(dnode, xpath); YANG_DNODE_GET_ASSERT(dnode, xpath); } dleaf = (const struct lyd_node_leaf_list *)dnode; return dleaf->value_str; } void yang_dnode_get_string_buf(char *buf, size_t size, const struct lyd_node *dnode, const char *xpath_fmt, ...) { const struct lyd_node_leaf_list *dleaf; assert(dnode); if (xpath_fmt) { va_list ap; char xpath[XPATH_MAXLEN]; va_start(ap, xpath_fmt); vsnprintf(xpath, sizeof(xpath), xpath_fmt, ap); va_end(ap); dnode = yang_dnode_get(dnode, xpath); YANG_DNODE_GET_ASSERT(dnode, xpath); } dleaf = (const struct lyd_node_leaf_list *)dnode; if (strlcpy(buf, dleaf->value_str, size) >= size) { char xpath[XPATH_MAXLEN]; yang_dnode_get_path(dnode, xpath, sizeof(xpath)); flog_warn(EC_LIB_YANG_DATA_TRUNCATED, "%s: value was truncated [xpath %s]", __func__, xpath); } } const char *yang_get_default_string(const char *xpath_fmt, ...) { char xpath[XPATH_MAXLEN]; va_list ap; va_start(ap, xpath_fmt); vsnprintf(xpath, sizeof(xpath), xpath_fmt, ap); va_end(ap); return yang_get_default_value(xpath); } void yang_get_default_string_buf(char *buf, size_t size, const char *xpath_fmt, ...) { char xpath[XPATH_MAXLEN]; const char *value; va_list ap; va_start(ap, xpath_fmt); vsnprintf(xpath, sizeof(xpath), xpath_fmt, ap); va_end(ap); value = yang_get_default_value(xpath); if (strlcpy(buf, value, size) >= size) flog_warn(EC_LIB_YANG_DATA_TRUNCATED, "%s: value was truncated [xpath %s]", __func__, xpath); } /* * Derived type: ipv4. */ void yang_str2ipv4(const char *value, struct in_addr *addr) { (void)inet_pton(AF_INET, value, addr); } struct yang_data *yang_data_new_ipv4(const char *xpath, const struct in_addr *addr) { char value_str[INET_ADDRSTRLEN]; (void)inet_ntop(AF_INET, addr, value_str, sizeof(value_str)); return yang_data_new(xpath, value_str); } void yang_dnode_get_ipv4(struct in_addr *addr, const struct lyd_node *dnode, const char *xpath_fmt, ...) { const struct lyd_node_leaf_list *dleaf; assert(dnode); if (xpath_fmt) { va_list ap; char xpath[XPATH_MAXLEN]; va_start(ap, xpath_fmt); vsnprintf(xpath, sizeof(xpath), xpath_fmt, ap); va_end(ap); dnode = yang_dnode_get(dnode, xpath); YANG_DNODE_GET_ASSERT(dnode, xpath); } dleaf = (const struct lyd_node_leaf_list *)dnode; assert(dleaf->value_type == LY_TYPE_STRING); (void)inet_pton(AF_INET, dleaf->value_str, addr); } void yang_get_default_ipv4(struct in_addr *var, const char *xpath_fmt, ...) { char xpath[XPATH_MAXLEN]; const char *value; va_list ap; va_start(ap, xpath_fmt); vsnprintf(xpath, sizeof(xpath), xpath_fmt, ap); va_end(ap); value = yang_get_default_value(xpath); yang_str2ipv4(value, var); } /* * Derived type: ipv4p. */ void yang_str2ipv4p(const char *value, union prefixptr prefix) { struct prefix_ipv4 *prefix4 = prefix.p4; (void)str2prefix_ipv4(value, prefix4); apply_mask_ipv4(prefix4); } struct yang_data *yang_data_new_ipv4p(const char *xpath, union prefixconstptr prefix) { char value_str[PREFIX2STR_BUFFER]; (void)prefix2str(prefix.p, value_str, sizeof(value_str)); return yang_data_new(xpath, value_str); } void yang_dnode_get_ipv4p(union prefixptr prefix, const struct lyd_node *dnode, const char *xpath_fmt, ...) { const struct lyd_node_leaf_list *dleaf; struct prefix_ipv4 *prefix4 = prefix.p4; assert(dnode); if (xpath_fmt) { va_list ap; char xpath[XPATH_MAXLEN]; va_start(ap, xpath_fmt); vsnprintf(xpath, sizeof(xpath), xpath_fmt, ap); va_end(ap); dnode = yang_dnode_get(dnode, xpath); YANG_DNODE_GET_ASSERT(dnode, xpath); } dleaf = (const struct lyd_node_leaf_list *)dnode; assert(dleaf->value_type == LY_TYPE_STRING); (void)str2prefix_ipv4(dleaf->value_str, prefix4); } void yang_get_default_ipv4p(union prefixptr var, const char *xpath_fmt, ...) { char xpath[XPATH_MAXLEN]; const char *value; va_list ap; va_start(ap, xpath_fmt); vsnprintf(xpath, sizeof(xpath), xpath_fmt, ap); va_end(ap); value = yang_get_default_value(xpath); yang_str2ipv4p(value, var); } /* * Derived type: ipv6. */ void yang_str2ipv6(const char *value, struct in6_addr *addr) { (void)inet_pton(AF_INET6, value, addr); } struct yang_data *yang_data_new_ipv6(const char *xpath, const struct in6_addr *addr) { char value_str[INET6_ADDRSTRLEN]; (void)inet_ntop(AF_INET6, addr, value_str, sizeof(value_str)); return yang_data_new(xpath, value_str); } void yang_dnode_get_ipv6(struct in6_addr *addr, const struct lyd_node *dnode, const char *xpath_fmt, ...) { const struct lyd_node_leaf_list *dleaf; assert(dnode); if (xpath_fmt) { va_list ap; char xpath[XPATH_MAXLEN]; va_start(ap, xpath_fmt); vsnprintf(xpath, sizeof(xpath), xpath_fmt, ap); va_end(ap); dnode = yang_dnode_get(dnode, xpath); YANG_DNODE_GET_ASSERT(dnode, xpath); } dleaf = (const struct lyd_node_leaf_list *)dnode; assert(dleaf->value_type == LY_TYPE_STRING); (void)inet_pton(AF_INET6, dleaf->value_str, addr); } void yang_get_default_ipv6(struct in6_addr *var, const char *xpath_fmt, ...) { char xpath[XPATH_MAXLEN]; const char *value; va_list ap; va_start(ap, xpath_fmt); vsnprintf(xpath, sizeof(xpath), xpath_fmt, ap); va_end(ap); value = yang_get_default_value(xpath); yang_str2ipv6(value, var); } /* * Derived type: ipv6p. */ void yang_str2ipv6p(const char *value, union prefixptr prefix) { struct prefix_ipv6 *prefix6 = prefix.p6; (void)str2prefix_ipv6(value, prefix6); apply_mask_ipv6(prefix6); } struct yang_data *yang_data_new_ipv6p(const char *xpath, union prefixconstptr prefix) { char value_str[PREFIX2STR_BUFFER]; (void)prefix2str(prefix.p, value_str, sizeof(value_str)); return yang_data_new(xpath, value_str); } void yang_dnode_get_ipv6p(union prefixptr prefix, const struct lyd_node *dnode, const char *xpath_fmt, ...) { const struct lyd_node_leaf_list *dleaf; struct prefix_ipv6 *prefix6 = prefix.p6; assert(dnode); if (xpath_fmt) { va_list ap; char xpath[XPATH_MAXLEN]; va_start(ap, xpath_fmt); vsnprintf(xpath, sizeof(xpath), xpath_fmt, ap); va_end(ap); dnode = yang_dnode_get(dnode, xpath); YANG_DNODE_GET_ASSERT(dnode, xpath); } dleaf = (const struct lyd_node_leaf_list *)dnode; assert(dleaf->value_type == LY_TYPE_STRING); (void)str2prefix_ipv6(dleaf->value_str, prefix6); } void yang_get_default_ipv6p(union prefixptr var, const char *xpath_fmt, ...) { char xpath[XPATH_MAXLEN]; const char *value; va_list ap; va_start(ap, xpath_fmt); vsnprintf(xpath, sizeof(xpath), xpath_fmt, ap); va_end(ap); value = yang_get_default_value(xpath); yang_str2ipv6p(value, var); } frr-7.2.1/lib/yang_wrappers.h0000644000000000000000000001465513610377563013027 00000000000000/* * Copyright (C) 2018 NetDEF, Inc. * Renato Westphal * * This program is free software; you can redistribute it and/or modify it * under the terms of the 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; see the file COPYING; if not, write to the Free Software * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA */ #ifndef _FRR_NORTHBOUND_WRAPPERS_H_ #define _FRR_NORTHBOUND_WRAPPERS_H_ #include "prefix.h" /* bool */ extern bool yang_str2bool(const char *value); extern struct yang_data *yang_data_new_bool(const char *xpath, bool value); extern bool yang_dnode_get_bool(const struct lyd_node *dnode, const char *xpath_fmt, ...); extern bool yang_get_default_bool(const char *xpath_fmt, ...); /* dec64 */ extern double yang_str2dec64(const char *xpath, const char *value); extern struct yang_data *yang_data_new_dec64(const char *xpath, double value); extern double yang_dnode_get_dec64(const struct lyd_node *dnode, const char *xpath_fmt, ...); extern double yang_get_default_dec64(const char *xpath_fmt, ...); /* enum */ extern int yang_str2enum(const char *xpath, const char *value); extern struct yang_data *yang_data_new_enum(const char *xpath, int value); extern int yang_dnode_get_enum(const struct lyd_node *dnode, const char *xpath_fmt, ...); extern int yang_get_default_enum(const char *xpath_fmt, ...); /* int8 */ extern int8_t yang_str2int8(const char *value); extern struct yang_data *yang_data_new_int8(const char *xpath, int8_t value); extern int8_t yang_dnode_get_int8(const struct lyd_node *dnode, const char *xpath_fmt, ...); extern int8_t yang_get_default_int8(const char *xpath_fmt, ...); /* int16 */ extern int16_t yang_str2int16(const char *value); extern struct yang_data *yang_data_new_int16(const char *xpath, int16_t value); extern int16_t yang_dnode_get_int16(const struct lyd_node *dnode, const char *xpath_fmt, ...); extern int16_t yang_get_default_int16(const char *xpath_fmt, ...); /* int32 */ extern int32_t yang_str2int32(const char *value); extern struct yang_data *yang_data_new_int32(const char *xpath, int32_t value); extern int32_t yang_dnode_get_int32(const struct lyd_node *dnode, const char *xpath_fmt, ...); extern int32_t yang_get_default_int32(const char *xpath_fmt, ...); /* int64 */ extern int64_t yang_str2int64(const char *value); extern struct yang_data *yang_data_new_int64(const char *xpath, int64_t value); extern int64_t yang_dnode_get_int64(const struct lyd_node *dnode, const char *xpath_fmt, ...); extern int64_t yang_get_default_int64(const char *xpath_fmt, ...); /* uint8 */ extern uint8_t yang_str2uint8(const char *value); extern struct yang_data *yang_data_new_uint8(const char *xpath, uint8_t value); extern uint8_t yang_dnode_get_uint8(const struct lyd_node *dnode, const char *xpath_fmt, ...); extern uint8_t yang_get_default_uint8(const char *xpath_fmt, ...); /* uint16 */ extern uint16_t yang_str2uint16(const char *value); extern struct yang_data *yang_data_new_uint16(const char *xpath, uint16_t value); extern uint16_t yang_dnode_get_uint16(const struct lyd_node *dnode, const char *xpath_fmt, ...); extern uint16_t yang_get_default_uint16(const char *xpath_fmt, ...); /* uint32 */ extern uint32_t yang_str2uint32(const char *value); extern struct yang_data *yang_data_new_uint32(const char *xpath, uint32_t value); extern uint32_t yang_dnode_get_uint32(const struct lyd_node *dnode, const char *xpath_fmt, ...); extern uint32_t yang_get_default_uint32(const char *xpath_fmt, ...); /* uint64 */ extern uint64_t yang_str2uint64(const char *value); extern struct yang_data *yang_data_new_uint64(const char *xpath, uint64_t value); extern uint64_t yang_dnode_get_uint64(const struct lyd_node *dnode, const char *xpath_fmt, ...); extern uint64_t yang_get_default_uint64(const char *xpath_fmt, ...); /* string */ extern struct yang_data *yang_data_new_string(const char *xpath, const char *value); extern const char *yang_dnode_get_string(const struct lyd_node *dnode, const char *xpath_fmt, ...); extern void yang_dnode_get_string_buf(char *buf, size_t size, const struct lyd_node *dnode, const char *xpath_fmt, ...); extern const char *yang_get_default_string(const char *xpath_fmt, ...); extern void yang_get_default_string_buf(char *buf, size_t size, const char *xpath_fmt, ...); /* ipv4 */ extern void yang_str2ipv4(const char *value, struct in_addr *addr); extern struct yang_data *yang_data_new_ipv4(const char *xpath, const struct in_addr *addr); extern void yang_dnode_get_ipv4(struct in_addr *addr, const struct lyd_node *dnode, const char *xpath_fmt, ...); extern void yang_get_default_ipv4(struct in_addr *var, const char *xpath_fmt, ...); /* ipv4p */ extern void yang_str2ipv4p(const char *value, union prefixptr prefix); extern struct yang_data *yang_data_new_ipv4p(const char *xpath, union prefixconstptr prefix); extern void yang_dnode_get_ipv4p(union prefixptr prefix, const struct lyd_node *dnode, const char *xpath_fmt, ...); extern void yang_get_default_ipv4p(union prefixptr var, const char *xpath_fmt, ...); /* ipv6 */ extern void yang_str2ipv6(const char *value, struct in6_addr *addr); extern struct yang_data *yang_data_new_ipv6(const char *xpath, const struct in6_addr *addr); extern void yang_dnode_get_ipv6(struct in6_addr *addr, const struct lyd_node *dnode, const char *xpath_fmt, ...); extern void yang_get_default_ipv6(struct in6_addr *var, const char *xpath_fmt, ...); /* ipv6p */ extern void yang_str2ipv6p(const char *value, union prefixptr prefix); extern struct yang_data *yang_data_new_ipv6p(const char *xpath, union prefixconstptr prefix); extern void yang_dnode_get_ipv6p(union prefixptr prefix, const struct lyd_node *dnode, const char *xpath_fmt, ...); extern void yang_get_default_ipv6p(union prefixptr var, const char *xpath_fmt, ...); #endif /* _FRR_NORTHBOUND_WRAPPERS_H_ */ frr-7.2.1/lib/zassert.h0000644000000000000000000000264413610377563011634 00000000000000/* * This file is part of Quagga. * * Quagga is free software; you can redistribute it and/or modify it * under the terms of the GNU General Public License as published by the * Free Software Foundation; either version 2, or (at your option) any * later version. * * Quagga 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; see the file COPYING; if not, write to the Free Software * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA */ #ifndef _QUAGGA_ASSERT_H #define _QUAGGA_ASSERT_H extern void _zlog_assert_failed(const char *assertion, const char *file, unsigned int line, const char *function) __attribute__((noreturn)); #if defined(__STDC_VERSION__) && __STDC_VERSION__ >= 199901L #define __ASSERT_FUNCTION __func__ #elif defined(__GNUC__) #define __ASSERT_FUNCTION __FUNCTION__ #else #define __ASSERT_FUNCTION NULL #endif #define zassert(EX) \ ((void)((EX) ? 0 : (_zlog_assert_failed(#EX, __FILE__, __LINE__, \ __ASSERT_FUNCTION), \ 0))) #undef assert #define assert(EX) zassert(EX) #endif /* _QUAGGA_ASSERT_H */ frr-7.2.1/lib/zclient.c0000644000000000000000000022525613610377563011612 00000000000000/* Zebra's client library. * Copyright (C) 1999 Kunihiro Ishiguro * Copyright (C) 2005 Andrew J. Schorr * * This file is part of GNU Zebra. * * GNU Zebra is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License as published * by the Free Software Foundation; either version 2, or (at your * option) any later version. * * GNU Zebra 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; see the file COPYING; if not, write to the Free Software * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA */ #include #include "prefix.h" #include "stream.h" #include "buffer.h" #include "network.h" #include "vrf.h" #include "vrf_int.h" #include "if.h" #include "log.h" #include "thread.h" #include "zclient.h" #include "memory.h" #include "table.h" #include "nexthop.h" #include "mpls.h" #include "sockopt.h" #include "pbr.h" #include "nexthop_group.h" #include "lib_errors.h" DEFINE_MTYPE_STATIC(LIB, ZCLIENT, "Zclient") DEFINE_MTYPE_STATIC(LIB, REDIST_INST, "Redistribution instance IDs") /* Zebra client events. */ enum event { ZCLIENT_SCHEDULE, ZCLIENT_READ, ZCLIENT_CONNECT }; /* Prototype for event manager. */ static void zclient_event(enum event, struct zclient *); struct zclient_options zclient_options_default = {.receive_notify = false}; struct sockaddr_storage zclient_addr; socklen_t zclient_addr_len; /* This file local debug flag. */ static int zclient_debug; /* Allocate zclient structure. */ struct zclient *zclient_new(struct thread_master *master, struct zclient_options *opt) { struct zclient *zclient; size_t stream_size = MAX(ZEBRA_MAX_PACKET_SIZ, sizeof(struct zapi_route)); zclient = XCALLOC(MTYPE_ZCLIENT, sizeof(struct zclient)); zclient->ibuf = stream_new(stream_size); zclient->obuf = stream_new(stream_size); zclient->wb = buffer_new(0); zclient->master = master; zclient->receive_notify = opt->receive_notify; return zclient; } /* This function is only called when exiting, because many parts of the code do not check for I/O errors, so they could reference an invalid pointer if the structure was ever freed. Free zclient structure. */ void zclient_free(struct zclient *zclient) { if (zclient->ibuf) stream_free(zclient->ibuf); if (zclient->obuf) stream_free(zclient->obuf); if (zclient->wb) buffer_free(zclient->wb); XFREE(MTYPE_ZCLIENT, zclient); } unsigned short *redist_check_instance(struct redist_proto *red, unsigned short instance) { struct listnode *node; unsigned short *id; if (!red->instances) return NULL; for (ALL_LIST_ELEMENTS_RO(red->instances, node, id)) if (*id == instance) return id; return NULL; } void redist_add_instance(struct redist_proto *red, unsigned short instance) { unsigned short *in; red->enabled = 1; if (!red->instances) red->instances = list_new(); in = XMALLOC(MTYPE_REDIST_INST, sizeof(unsigned short)); *in = instance; listnode_add(red->instances, in); } void redist_del_instance(struct redist_proto *red, unsigned short instance) { unsigned short *id; id = redist_check_instance(red, instance); if (!id) return; listnode_delete(red->instances, id); XFREE(MTYPE_REDIST_INST, id); if (!red->instances->count) { red->enabled = 0; list_delete(&red->instances); } } /* Stop zebra client services. */ void zclient_stop(struct zclient *zclient) { afi_t afi; int i; if (zclient_debug) zlog_debug("zclient stopped"); /* Stop threads. */ THREAD_OFF(zclient->t_read); THREAD_OFF(zclient->t_connect); THREAD_OFF(zclient->t_write); /* Reset streams. */ stream_reset(zclient->ibuf); stream_reset(zclient->obuf); /* Empty the write buffer. */ buffer_reset(zclient->wb); /* Close socket. */ if (zclient->sock >= 0) { close(zclient->sock); zclient->sock = -1; } zclient->fail = 0; for (afi = AFI_IP; afi < AFI_MAX; afi++) { for (i = 0; i < ZEBRA_ROUTE_MAX; i++) { vrf_bitmap_free(zclient->redist[afi][i]); zclient->redist[afi][i] = VRF_BITMAP_NULL; } redist_del_instance( &zclient->mi_redist[afi][zclient->redist_default], zclient->instance); vrf_bitmap_free(zclient->default_information[afi]); zclient->default_information[afi] = VRF_BITMAP_NULL; } } void zclient_reset(struct zclient *zclient) { afi_t afi; zclient_stop(zclient); for (afi = AFI_IP; afi < AFI_MAX; afi++) redist_del_instance( &zclient->mi_redist[afi][zclient->redist_default], zclient->instance); zclient_init(zclient, zclient->redist_default, zclient->instance, zclient->privs); } /** * Connect to zebra daemon. * @param zclient a pointer to zclient structure * @return socket fd just to make sure that connection established * @see zclient_init * @see zclient_new */ int zclient_socket_connect(struct zclient *zclient) { int sock; int ret; /* We should think about IPv6 connection. */ sock = socket(zclient_addr.ss_family, SOCK_STREAM, 0); if (sock < 0) return -1; set_cloexec(sock); setsockopt_so_sendbuf(sock, 1048576); /* Connect to zebra. */ ret = connect(sock, (struct sockaddr *)&zclient_addr, zclient_addr_len); if (ret < 0) { if (zclient_debug) zlog_debug("%s connect failure: %d(%s)", __PRETTY_FUNCTION__, errno, safe_strerror(errno)); close(sock); return -1; } zclient->sock = sock; return sock; } static int zclient_failed(struct zclient *zclient) { zclient->fail++; zclient_stop(zclient); zclient_event(ZCLIENT_CONNECT, zclient); return -1; } static int zclient_flush_data(struct thread *thread) { struct zclient *zclient = THREAD_ARG(thread); zclient->t_write = NULL; if (zclient->sock < 0) return -1; switch (buffer_flush_available(zclient->wb, zclient->sock)) { case BUFFER_ERROR: flog_err( EC_LIB_ZAPI_SOCKET, "%s: buffer_flush_available failed on zclient fd %d, closing", __func__, zclient->sock); return zclient_failed(zclient); break; case BUFFER_PENDING: zclient->t_write = NULL; thread_add_write(zclient->master, zclient_flush_data, zclient, zclient->sock, &zclient->t_write); break; case BUFFER_EMPTY: break; } return 0; } int zclient_send_message(struct zclient *zclient) { if (zclient->sock < 0) return -1; switch (buffer_write(zclient->wb, zclient->sock, STREAM_DATA(zclient->obuf), stream_get_endp(zclient->obuf))) { case BUFFER_ERROR: flog_err(EC_LIB_ZAPI_SOCKET, "%s: buffer_write failed to zclient fd %d, closing", __func__, zclient->sock); return zclient_failed(zclient); break; case BUFFER_EMPTY: THREAD_OFF(zclient->t_write); break; case BUFFER_PENDING: thread_add_write(zclient->master, zclient_flush_data, zclient, zclient->sock, &zclient->t_write); break; } return 0; } void zclient_create_header(struct stream *s, uint16_t command, vrf_id_t vrf_id) { /* length placeholder, caller can update */ stream_putw(s, ZEBRA_HEADER_SIZE); stream_putc(s, ZEBRA_HEADER_MARKER); stream_putc(s, ZSERV_VERSION); stream_putl(s, vrf_id); stream_putw(s, command); } int zclient_read_header(struct stream *s, int sock, uint16_t *size, uint8_t *marker, uint8_t *version, vrf_id_t *vrf_id, uint16_t *cmd) { if (stream_read(s, sock, ZEBRA_HEADER_SIZE) != ZEBRA_HEADER_SIZE) return -1; STREAM_GETW(s, *size); *size -= ZEBRA_HEADER_SIZE; STREAM_GETC(s, *marker); STREAM_GETC(s, *version); STREAM_GETL(s, *vrf_id); STREAM_GETW(s, *cmd); if (*version != ZSERV_VERSION || *marker != ZEBRA_HEADER_MARKER) { flog_err( EC_LIB_ZAPI_MISSMATCH, "%s: socket %d version mismatch, marker %d, version %d", __func__, sock, *marker, *version); return -1; } if (*size && stream_read(s, sock, *size) != *size) return -1; return 0; stream_failure: return -1; } bool zapi_parse_header(struct stream *zmsg, struct zmsghdr *hdr) { STREAM_GETW(zmsg, hdr->length); STREAM_GETC(zmsg, hdr->marker); STREAM_GETC(zmsg, hdr->version); STREAM_GETL(zmsg, hdr->vrf_id); STREAM_GETW(zmsg, hdr->command); return true; stream_failure: return false; } /* Send simple Zebra message. */ static int zebra_message_send(struct zclient *zclient, int command, vrf_id_t vrf_id) { struct stream *s; /* Get zclient output buffer. */ s = zclient->obuf; stream_reset(s); /* Send very simple command only Zebra message. */ zclient_create_header(s, command, vrf_id); return zclient_send_message(zclient); } static int zebra_hello_send(struct zclient *zclient) { struct stream *s; if (zclient->redist_default) { s = zclient->obuf; stream_reset(s); /* The VRF ID in the HELLO message is always 0. */ zclient_create_header(s, ZEBRA_HELLO, VRF_DEFAULT); stream_putc(s, zclient->redist_default); stream_putw(s, zclient->instance); if (zclient->receive_notify) stream_putc(s, 1); else stream_putc(s, 0); stream_putw_at(s, 0, stream_get_endp(s)); return zclient_send_message(zclient); } return 0; } void zclient_send_vrf_label(struct zclient *zclient, vrf_id_t vrf_id, afi_t afi, mpls_label_t label, enum lsp_types_t ltype) { struct stream *s; s = zclient->obuf; stream_reset(s); zclient_create_header(s, ZEBRA_VRF_LABEL, vrf_id); stream_putl(s, label); stream_putc(s, afi); stream_putc(s, ltype); stream_putw_at(s, 0, stream_get_endp(s)); zclient_send_message(zclient); } /* Send register requests to zebra daemon for the information in a VRF. */ void zclient_send_reg_requests(struct zclient *zclient, vrf_id_t vrf_id) { int i; afi_t afi; /* If not connected to the zebra yet. */ if (zclient->sock < 0) return; if (zclient_debug) zlog_debug("%s: send register messages for VRF %u", __func__, vrf_id); /* We need router-id information. */ zebra_message_send(zclient, ZEBRA_ROUTER_ID_ADD, vrf_id); /* We need interface information. */ zebra_message_send(zclient, ZEBRA_INTERFACE_ADD, vrf_id); /* Set unwanted redistribute route. */ for (afi = AFI_IP; afi < AFI_MAX; afi++) vrf_bitmap_set(zclient->redist[afi][zclient->redist_default], vrf_id); /* Flush all redistribute request. */ if (vrf_id == VRF_DEFAULT) { for (afi = AFI_IP; afi < AFI_MAX; afi++) { for (i = 0; i < ZEBRA_ROUTE_MAX; i++) { if (!zclient->mi_redist[afi][i].enabled) continue; struct listnode *node; unsigned short *id; for (ALL_LIST_ELEMENTS_RO( zclient->mi_redist[afi][i] .instances, node, id)) if (!(i == zclient->redist_default && *id == zclient->instance)) zebra_redistribute_send( ZEBRA_REDISTRIBUTE_ADD, zclient, afi, i, *id, VRF_DEFAULT); } } } /* Resend all redistribute request. */ for (afi = AFI_IP; afi < AFI_MAX; afi++) { for (i = 0; i < ZEBRA_ROUTE_MAX; i++) if (i != zclient->redist_default && vrf_bitmap_check(zclient->redist[afi][i], vrf_id)) zebra_redistribute_send(ZEBRA_REDISTRIBUTE_ADD, zclient, afi, i, 0, vrf_id); /* If default information is needed. */ if (vrf_bitmap_check(zclient->default_information[afi], vrf_id)) zebra_redistribute_default_send( ZEBRA_REDISTRIBUTE_DEFAULT_ADD, zclient, afi, vrf_id); } } /* Send unregister requests to zebra daemon for the information in a VRF. */ void zclient_send_dereg_requests(struct zclient *zclient, vrf_id_t vrf_id) { int i; afi_t afi; /* If not connected to the zebra yet. */ if (zclient->sock < 0) return; if (zclient_debug) zlog_debug("%s: send deregister messages for VRF %u", __func__, vrf_id); /* We need router-id information. */ zebra_message_send(zclient, ZEBRA_ROUTER_ID_DELETE, vrf_id); zebra_message_send(zclient, ZEBRA_INTERFACE_DELETE, vrf_id); /* Set unwanted redistribute route. */ for (afi = AFI_IP; afi < AFI_MAX; afi++) vrf_bitmap_unset(zclient->redist[afi][zclient->redist_default], vrf_id); /* Flush all redistribute request. */ if (vrf_id == VRF_DEFAULT) { for (afi = AFI_IP; afi < AFI_MAX; afi++) { for (i = 0; i < ZEBRA_ROUTE_MAX; i++) { if (!zclient->mi_redist[afi][i].enabled) continue; struct listnode *node; unsigned short *id; for (ALL_LIST_ELEMENTS_RO( zclient->mi_redist[afi][i] .instances, node, id)) if (!(i == zclient->redist_default && *id == zclient->instance)) zebra_redistribute_send( ZEBRA_REDISTRIBUTE_DELETE, zclient, afi, i, *id, VRF_DEFAULT); } } } /* Flush all redistribute request. */ for (afi = AFI_IP; afi < AFI_MAX; afi++) { for (i = 0; i < ZEBRA_ROUTE_MAX; i++) if (i != zclient->redist_default && vrf_bitmap_check(zclient->redist[afi][i], vrf_id)) zebra_redistribute_send( ZEBRA_REDISTRIBUTE_DELETE, zclient, afi, i, 0, vrf_id); /* If default information is needed. */ if (vrf_bitmap_check(zclient->default_information[afi], vrf_id)) zebra_redistribute_default_send( ZEBRA_REDISTRIBUTE_DEFAULT_DELETE, zclient, afi, vrf_id); } } /* Send request to zebra daemon to start or stop RA. */ void zclient_send_interface_radv_req(struct zclient *zclient, vrf_id_t vrf_id, struct interface *ifp, int enable, int ra_interval) { struct stream *s; /* If not connected to the zebra yet. */ if (zclient->sock < 0) return; /* Form and send message. */ s = zclient->obuf; stream_reset(s); if (enable) zclient_create_header(s, ZEBRA_INTERFACE_ENABLE_RADV, vrf_id); else zclient_create_header(s, ZEBRA_INTERFACE_DISABLE_RADV, vrf_id); stream_putl(s, ifp->ifindex); stream_putl(s, ra_interval); stream_putw_at(s, 0, stream_get_endp(s)); zclient_send_message(zclient); } int zclient_send_interface_protodown(struct zclient *zclient, vrf_id_t vrf_id, struct interface *ifp, bool down) { struct stream *s; if (zclient->sock < 0) return -1; s = zclient->obuf; stream_reset(s); zclient_create_header(s, ZEBRA_INTERFACE_SET_PROTODOWN, vrf_id); stream_putl(s, ifp->ifindex); stream_putc(s, !!down); stream_putw_at(s, 0, stream_get_endp(s)); zclient_send_message(zclient); return 0; } /* Make connection to zebra daemon. */ int zclient_start(struct zclient *zclient) { if (zclient_debug) zlog_info("zclient_start is called"); /* If already connected to the zebra. */ if (zclient->sock >= 0) return 0; /* Check connect thread. */ if (zclient->t_connect) return 0; if (zclient_socket_connect(zclient) < 0) { if (zclient_debug) zlog_debug("zclient connection fail"); zclient->fail++; zclient_event(ZCLIENT_CONNECT, zclient); return -1; } if (set_nonblocking(zclient->sock) < 0) flog_err(EC_LIB_ZAPI_SOCKET, "%s: set_nonblocking(%d) failed", __func__, zclient->sock); /* Clear fail count. */ zclient->fail = 0; if (zclient_debug) zlog_debug("zclient connect success with socket [%d]", zclient->sock); /* Create read thread. */ zclient_event(ZCLIENT_READ, zclient); zebra_hello_send(zclient); zebra_message_send(zclient, ZEBRA_INTERFACE_ADD, VRF_DEFAULT); /* Inform the successful connection. */ if (zclient->zebra_connected) (*zclient->zebra_connected)(zclient); return 0; } /* Initialize zebra client. Argument redist_default is unwanted redistribute route type. */ void zclient_init(struct zclient *zclient, int redist_default, unsigned short instance, struct zebra_privs_t *privs) { int afi, i; /* Set -1 to the default socket value. */ zclient->sock = -1; zclient->privs = privs; /* Clear redistribution flags. */ for (afi = AFI_IP; afi < AFI_MAX; afi++) for (i = 0; i < ZEBRA_ROUTE_MAX; i++) zclient->redist[afi][i] = vrf_bitmap_init(); /* Set unwanted redistribute route. bgpd does not need BGP route redistribution. */ zclient->redist_default = redist_default; zclient->instance = instance; /* Pending: make afi(s) an arg. */ for (afi = AFI_IP; afi < AFI_MAX; afi++) { redist_add_instance(&zclient->mi_redist[afi][redist_default], instance); /* Set default-information redistribute to zero. */ zclient->default_information[afi] = vrf_bitmap_init(); } if (zclient_debug) zlog_debug("scheduling zclient connection"); zclient_event(ZCLIENT_SCHEDULE, zclient); } /* This function is a wrapper function for calling zclient_start from timer or event thread. */ static int zclient_connect(struct thread *t) { struct zclient *zclient; zclient = THREAD_ARG(t); zclient->t_connect = NULL; if (zclient_debug) zlog_debug("zclient_connect is called"); return zclient_start(zclient); } int zclient_send_rnh(struct zclient *zclient, int command, struct prefix *p, bool exact_match, vrf_id_t vrf_id) { struct stream *s; s = zclient->obuf; stream_reset(s); zclient_create_header(s, command, vrf_id); stream_putc(s, (exact_match) ? 1 : 0); stream_putw(s, PREFIX_FAMILY(p)); stream_putc(s, p->prefixlen); switch (PREFIX_FAMILY(p)) { case AF_INET: stream_put_in_addr(s, &p->u.prefix4); break; case AF_INET6: stream_put(s, &(p->u.prefix6), 16); break; default: break; } stream_putw_at(s, 0, stream_get_endp(s)); return zclient_send_message(zclient); } /* * "xdr_encode"-like interface that allows daemon (client) to send * a message to zebra server for a route that needs to be * added/deleted to the kernel. Info about the route is specified * by the caller in a struct zapi_route. zapi_route_encode() then writes * the info down the zclient socket using the stream_* functions. * * The corresponding read ("xdr_decode") function on the server * side is zapi_route_decode(). * * 0 1 2 3 4 5 6 7 8 9 A B C D E F 0 1 2 3 4 5 6 7 8 9 A B C D E F * +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ * | Length (2) | Command | Route Type | * +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ * | ZEBRA Flags | Message Flags | Prefix length | * +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ * | Destination IPv4 Prefix for route | * +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ * | Nexthop count | * +-+-+-+-+-+-+-+-+ * * * A number of IPv4 nexthop(s) or nexthop interface index(es) are then * described, as per the Nexthop count. Each nexthop described as: * * +-+-+-+-+-+-+-+-+ * | Nexthop Type | Set to one of ZEBRA_NEXTHOP_* * +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ * | IPv4 Nexthop address or Interface Index number | * +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ * * Alternatively, if the route is a blackhole route, then Nexthop count * is set to 1 and a nexthop of type NEXTHOP_TYPE_BLACKHOLE is the sole * nexthop. * * The original struct zapi_route_*() infrastructure was built around * the traditional (32-bit "gate OR ifindex") nexthop data unit. * A special encoding can be used to feed onlink (64-bit "gate AND ifindex") * nexthops into zapi_route_encode() using the same zapi_route structure. * This is done by setting zapi_route fields as follows: * - .message |= ZAPI_MESSAGE_NEXTHOP | ZAPI_MESSAGE_ONLINK * - .nexthop_num == .ifindex_num * - .nexthop and .ifindex are filled with gate and ifindex parts of * each compound nexthop, both in the same order * * If ZAPI_MESSAGE_DISTANCE is set, the distance value is written as a 1 * byte value. * * If ZAPI_MESSAGE_METRIC is set, the metric value is written as an 8 * byte value. * * If ZAPI_MESSAGE_TAG is set, the tag value is written as a 4 byte value * * If ZAPI_MESSAGE_MTU is set, the mtu value is written as a 4 byte value * * XXX: No attention paid to alignment. */ int zclient_route_send(uint8_t cmd, struct zclient *zclient, struct zapi_route *api) { if (zapi_route_encode(cmd, zclient->obuf, api) < 0) return -1; return zclient_send_message(zclient); } static int zapi_nexthop_labels_cmp(const struct zapi_nexthop *next1, const struct zapi_nexthop *next2) { if (next1->label_num > next2->label_num) return 1; if (next1->label_num < next2->label_num) return -1; return memcmp(next1->labels, next2->labels, next1->label_num); } static int zapi_nexthop_cmp_no_labels(const struct zapi_nexthop *next1, const struct zapi_nexthop *next2) { int ret = 0; if (next1->vrf_id < next2->vrf_id) return -1; if (next1->vrf_id > next2->vrf_id) return 1; if (next1->type < next2->type) return -1; if (next1->type > next2->type) return 1; switch (next1->type) { case NEXTHOP_TYPE_IPV4: case NEXTHOP_TYPE_IPV6: ret = nexthop_g_addr_cmp(next1->type, &next1->gate, &next2->gate); if (ret != 0) return ret; break; case NEXTHOP_TYPE_IPV4_IFINDEX: case NEXTHOP_TYPE_IPV6_IFINDEX: ret = nexthop_g_addr_cmp(next1->type, &next1->gate, &next2->gate); if (ret != 0) return ret; /* Intentional Fall-Through */ case NEXTHOP_TYPE_IFINDEX: if (next1->ifindex < next2->ifindex) return -1; if (next1->ifindex > next2->ifindex) return 1; break; case NEXTHOP_TYPE_BLACKHOLE: if (next1->bh_type < next2->bh_type) return -1; if (next1->bh_type > next2->bh_type) return 1; break; } return 0; } static int zapi_nexthop_cmp(const void *item1, const void *item2) { int ret = 0; const struct zapi_nexthop *next1 = item1; const struct zapi_nexthop *next2 = item2; ret = zapi_nexthop_cmp_no_labels(next1, next2); if (ret != 0) return ret; ret = zapi_nexthop_labels_cmp(next1, next2); return ret; } static void zapi_nexthop_group_sort(struct zapi_nexthop *nh_grp, uint16_t nexthop_num) { qsort(nh_grp, nexthop_num, sizeof(struct zapi_nexthop), &zapi_nexthop_cmp); } int zapi_route_encode(uint8_t cmd, struct stream *s, struct zapi_route *api) { struct zapi_nexthop *api_nh; int i; int psize; stream_reset(s); zclient_create_header(s, cmd, api->vrf_id); if (api->type >= ZEBRA_ROUTE_MAX) { flog_err(EC_LIB_ZAPI_ENCODE, "%s: Specified route type (%u) is not a legal value\n", __PRETTY_FUNCTION__, api->type); return -1; } stream_putc(s, api->type); stream_putw(s, api->instance); stream_putl(s, api->flags); stream_putc(s, api->message); if (api->safi < SAFI_UNICAST || api->safi >= SAFI_MAX) { flog_err(EC_LIB_ZAPI_ENCODE, "%s: Specified route SAFI (%u) is not a legal value\n", __PRETTY_FUNCTION__, api->safi); return -1; } stream_putc(s, api->safi); /* Put prefix information. */ stream_putc(s, api->prefix.family); psize = PSIZE(api->prefix.prefixlen); stream_putc(s, api->prefix.prefixlen); stream_write(s, (uint8_t *)&api->prefix.u.prefix, psize); if (CHECK_FLAG(api->message, ZAPI_MESSAGE_SRCPFX)) { psize = PSIZE(api->src_prefix.prefixlen); stream_putc(s, api->src_prefix.prefixlen); stream_write(s, (uint8_t *)&api->src_prefix.prefix, psize); } /* Nexthops. */ if (CHECK_FLAG(api->message, ZAPI_MESSAGE_NEXTHOP)) { /* limit the number of nexthops if necessary */ if (api->nexthop_num > MULTIPATH_NUM) { char buf[PREFIX2STR_BUFFER]; prefix2str(&api->prefix, buf, sizeof(buf)); flog_err( EC_LIB_ZAPI_ENCODE, "%s: prefix %s: can't encode %u nexthops (maximum is %u)", __func__, buf, api->nexthop_num, MULTIPATH_NUM); return -1; } zapi_nexthop_group_sort(api->nexthops, api->nexthop_num); stream_putw(s, api->nexthop_num); for (i = 0; i < api->nexthop_num; i++) { api_nh = &api->nexthops[i]; stream_putl(s, api_nh->vrf_id); stream_putc(s, api_nh->type); stream_putc(s, api_nh->onlink); switch (api_nh->type) { case NEXTHOP_TYPE_BLACKHOLE: stream_putc(s, api_nh->bh_type); break; case NEXTHOP_TYPE_IPV4: stream_put_in_addr(s, &api_nh->gate.ipv4); break; case NEXTHOP_TYPE_IPV4_IFINDEX: stream_put_in_addr(s, &api_nh->gate.ipv4); stream_putl(s, api_nh->ifindex); break; case NEXTHOP_TYPE_IFINDEX: stream_putl(s, api_nh->ifindex); break; case NEXTHOP_TYPE_IPV6: stream_write(s, (uint8_t *)&api_nh->gate.ipv6, 16); break; case NEXTHOP_TYPE_IPV6_IFINDEX: stream_write(s, (uint8_t *)&api_nh->gate.ipv6, 16); stream_putl(s, api_nh->ifindex); break; } /* MPLS labels for BGP-LU or Segment Routing */ if (CHECK_FLAG(api->message, ZAPI_MESSAGE_LABEL)) { if (api_nh->label_num > MPLS_MAX_LABELS) { char buf[PREFIX2STR_BUFFER]; prefix2str(&api->prefix, buf, sizeof(buf)); flog_err(EC_LIB_ZAPI_ENCODE, "%s: prefix %s: can't encode " "%u labels (maximum is %u)", __func__, buf, api_nh->label_num, MPLS_MAX_LABELS); return -1; } stream_putc(s, api_nh->label_num); stream_put(s, &api_nh->labels[0], api_nh->label_num * sizeof(mpls_label_t)); } /* Router MAC for EVPN routes. */ if (CHECK_FLAG(api->flags, ZEBRA_FLAG_EVPN_ROUTE)) stream_put(s, &(api_nh->rmac), sizeof(struct ethaddr)); } } /* Attributes. */ if (CHECK_FLAG(api->message, ZAPI_MESSAGE_DISTANCE)) stream_putc(s, api->distance); if (CHECK_FLAG(api->message, ZAPI_MESSAGE_METRIC)) stream_putl(s, api->metric); if (CHECK_FLAG(api->message, ZAPI_MESSAGE_TAG)) stream_putl(s, api->tag); if (CHECK_FLAG(api->message, ZAPI_MESSAGE_MTU)) stream_putl(s, api->mtu); if (CHECK_FLAG(api->message, ZAPI_MESSAGE_TABLEID)) stream_putl(s, api->tableid); /* Put length at the first point of the stream. */ stream_putw_at(s, 0, stream_get_endp(s)); return 0; } int zapi_route_decode(struct stream *s, struct zapi_route *api) { struct zapi_nexthop *api_nh; int i; memset(api, 0, sizeof(*api)); /* Type, flags, message. */ STREAM_GETC(s, api->type); if (api->type >= ZEBRA_ROUTE_MAX) { flog_err(EC_LIB_ZAPI_ENCODE, "%s: Specified route type: %d is not a legal value\n", __PRETTY_FUNCTION__, api->type); return -1; } STREAM_GETW(s, api->instance); STREAM_GETL(s, api->flags); STREAM_GETC(s, api->message); STREAM_GETC(s, api->safi); if (api->safi < SAFI_UNICAST || api->safi >= SAFI_MAX) { flog_err(EC_LIB_ZAPI_ENCODE, "%s: Specified route SAFI (%u) is not a legal value\n", __PRETTY_FUNCTION__, api->safi); return -1; } /* Prefix. */ STREAM_GETC(s, api->prefix.family); STREAM_GETC(s, api->prefix.prefixlen); switch (api->prefix.family) { case AF_INET: if (api->prefix.prefixlen > IPV4_MAX_PREFIXLEN) { flog_err( EC_LIB_ZAPI_ENCODE, "%s: V4 prefixlen is %d which should not be more than 32", __PRETTY_FUNCTION__, api->prefix.prefixlen); return -1; } break; case AF_INET6: if (api->prefix.prefixlen > IPV6_MAX_PREFIXLEN) { flog_err( EC_LIB_ZAPI_ENCODE, "%s: v6 prefixlen is %d which should not be more than 128", __PRETTY_FUNCTION__, api->prefix.prefixlen); return -1; } break; default: flog_err(EC_LIB_ZAPI_ENCODE, "%s: Specified family %d is not v4 or v6", __PRETTY_FUNCTION__, api->prefix.family); return -1; } STREAM_GET(&api->prefix.u.prefix, s, PSIZE(api->prefix.prefixlen)); if (CHECK_FLAG(api->message, ZAPI_MESSAGE_SRCPFX)) { api->src_prefix.family = AF_INET6; STREAM_GETC(s, api->src_prefix.prefixlen); if (api->src_prefix.prefixlen > IPV6_MAX_PREFIXLEN) { flog_err( EC_LIB_ZAPI_ENCODE, "%s: SRC Prefix prefixlen received: %d is too large", __PRETTY_FUNCTION__, api->src_prefix.prefixlen); return -1; } STREAM_GET(&api->src_prefix.prefix, s, PSIZE(api->src_prefix.prefixlen)); if (api->prefix.family != AF_INET6 || api->src_prefix.prefixlen == 0) { flog_err( EC_LIB_ZAPI_ENCODE, "%s: SRC prefix specified in some manner that makes no sense", __PRETTY_FUNCTION__); return -1; } } /* Nexthops. */ if (CHECK_FLAG(api->message, ZAPI_MESSAGE_NEXTHOP)) { STREAM_GETW(s, api->nexthop_num); if (api->nexthop_num > MULTIPATH_NUM) { flog_err(EC_LIB_ZAPI_ENCODE, "%s: invalid number of nexthops (%u)", __func__, api->nexthop_num); return -1; } for (i = 0; i < api->nexthop_num; i++) { api_nh = &api->nexthops[i]; STREAM_GETL(s, api_nh->vrf_id); STREAM_GETC(s, api_nh->type); STREAM_GETC(s, api_nh->onlink); switch (api_nh->type) { case NEXTHOP_TYPE_BLACKHOLE: STREAM_GETC(s, api_nh->bh_type); break; case NEXTHOP_TYPE_IPV4: STREAM_GET(&api_nh->gate.ipv4.s_addr, s, IPV4_MAX_BYTELEN); break; case NEXTHOP_TYPE_IPV4_IFINDEX: STREAM_GET(&api_nh->gate.ipv4.s_addr, s, IPV4_MAX_BYTELEN); STREAM_GETL(s, api_nh->ifindex); break; case NEXTHOP_TYPE_IFINDEX: STREAM_GETL(s, api_nh->ifindex); break; case NEXTHOP_TYPE_IPV6: STREAM_GET(&api_nh->gate.ipv6, s, 16); break; case NEXTHOP_TYPE_IPV6_IFINDEX: STREAM_GET(&api_nh->gate.ipv6, s, 16); STREAM_GETL(s, api_nh->ifindex); break; } /* MPLS labels for BGP-LU or Segment Routing */ if (CHECK_FLAG(api->message, ZAPI_MESSAGE_LABEL)) { STREAM_GETC(s, api_nh->label_num); if (api_nh->label_num > MPLS_MAX_LABELS) { flog_err( EC_LIB_ZAPI_ENCODE, "%s: invalid number of MPLS labels (%u)", __func__, api_nh->label_num); return -1; } STREAM_GET(&api_nh->labels[0], s, api_nh->label_num * sizeof(mpls_label_t)); } /* Router MAC for EVPN routes. */ if (CHECK_FLAG(api->flags, ZEBRA_FLAG_EVPN_ROUTE)) stream_get(&(api_nh->rmac), s, sizeof(struct ethaddr)); } } /* Attributes. */ if (CHECK_FLAG(api->message, ZAPI_MESSAGE_DISTANCE)) STREAM_GETC(s, api->distance); if (CHECK_FLAG(api->message, ZAPI_MESSAGE_METRIC)) STREAM_GETL(s, api->metric); if (CHECK_FLAG(api->message, ZAPI_MESSAGE_TAG)) STREAM_GETL(s, api->tag); if (CHECK_FLAG(api->message, ZAPI_MESSAGE_MTU)) STREAM_GETL(s, api->mtu); if (CHECK_FLAG(api->message, ZAPI_MESSAGE_TABLEID)) STREAM_GETL(s, api->tableid); return 0; stream_failure: return -1; } static void zapi_encode_prefix(struct stream *s, struct prefix *p, uint8_t family) { struct prefix any; if (!p) { memset(&any, 0, sizeof(any)); any.family = family; p = &any; } stream_putc(s, p->family); stream_putc(s, p->prefixlen); stream_put(s, &p->u.prefix, prefix_blen(p)); } int zapi_pbr_rule_encode(uint8_t cmd, struct stream *s, struct pbr_rule *zrule) { stream_reset(s); zclient_create_header(s, cmd, zrule->vrf_id); /* * We are sending one item at a time at the moment */ stream_putl(s, 1); stream_putl(s, zrule->seq); stream_putl(s, zrule->priority); stream_putl(s, zrule->unique); zapi_encode_prefix(s, &(zrule->filter.src_ip), zrule->filter.src_ip.family); stream_putw(s, zrule->filter.src_port); /* src port */ zapi_encode_prefix(s, &(zrule->filter.dst_ip), zrule->filter.src_ip.family); stream_putw(s, zrule->filter.dst_port); /* dst port */ stream_putw(s, zrule->filter.fwmark); /* fwmark */ stream_putl(s, zrule->action.table); stream_putl(s, zrule->ifindex); /* Put length at the first point of the stream. */ stream_putw_at(s, 0, stream_get_endp(s)); return 0; } bool zapi_route_notify_decode(struct stream *s, struct prefix *p, uint32_t *tableid, enum zapi_route_notify_owner *note) { uint32_t t; STREAM_GET(note, s, sizeof(*note)); STREAM_GETC(s, p->family); STREAM_GETC(s, p->prefixlen); STREAM_GET(&p->u.prefix, s, prefix_blen(p)); STREAM_GETL(s, t); *tableid = t; return true; stream_failure: return false; } bool zapi_rule_notify_decode(struct stream *s, uint32_t *seqno, uint32_t *priority, uint32_t *unique, ifindex_t *ifindex, enum zapi_rule_notify_owner *note) { uint32_t prio, seq, uni; ifindex_t ifi; STREAM_GET(note, s, sizeof(*note)); STREAM_GETL(s, seq); STREAM_GETL(s, prio); STREAM_GETL(s, uni); STREAM_GETL(s, ifi); if (zclient_debug) zlog_debug("%s: %u %u %u %u", __PRETTY_FUNCTION__, seq, prio, uni, ifi); *seqno = seq; *priority = prio; *unique = uni; *ifindex = ifi; return true; stream_failure: return false; } bool zapi_ipset_notify_decode(struct stream *s, uint32_t *unique, enum zapi_ipset_notify_owner *note) { uint32_t uni; STREAM_GET(note, s, sizeof(*note)); STREAM_GETL(s, uni); if (zclient_debug) zlog_debug("%s: %u", __PRETTY_FUNCTION__, uni); *unique = uni; return true; stream_failure: return false; } bool zapi_ipset_entry_notify_decode(struct stream *s, uint32_t *unique, char *ipset_name, enum zapi_ipset_entry_notify_owner *note) { uint32_t uni; STREAM_GET(note, s, sizeof(*note)); STREAM_GETL(s, uni); STREAM_GET(ipset_name, s, ZEBRA_IPSET_NAME_SIZE); if (zclient_debug) zlog_debug("%s: %u", __PRETTY_FUNCTION__, uni); *unique = uni; return true; stream_failure: return false; } bool zapi_iptable_notify_decode(struct stream *s, uint32_t *unique, enum zapi_iptable_notify_owner *note) { uint32_t uni; STREAM_GET(note, s, sizeof(*note)); STREAM_GETL(s, uni); if (zclient_debug) zlog_debug("%s: %u", __PRETTY_FUNCTION__, uni); *unique = uni; return true; stream_failure: return false; } struct nexthop *nexthop_from_zapi_nexthop(struct zapi_nexthop *znh) { struct nexthop *n = nexthop_new(); n->type = znh->type; n->vrf_id = znh->vrf_id; n->ifindex = znh->ifindex; n->gate = znh->gate; /* * This function currently handles labels */ if (znh->label_num) { nexthop_add_labels(n, ZEBRA_LSP_NONE, znh->label_num, znh->labels); } return n; } bool zapi_nexthop_update_decode(struct stream *s, struct zapi_route *nhr) { uint32_t i; memset(nhr, 0, sizeof(*nhr)); STREAM_GETW(s, nhr->prefix.family); STREAM_GETC(s, nhr->prefix.prefixlen); switch (nhr->prefix.family) { case AF_INET: STREAM_GET(&nhr->prefix.u.prefix4.s_addr, s, IPV4_MAX_BYTELEN); break; case AF_INET6: STREAM_GET(&nhr->prefix.u.prefix6, s, IPV6_MAX_BYTELEN); break; default: break; } STREAM_GETC(s, nhr->type); STREAM_GETW(s, nhr->instance); STREAM_GETC(s, nhr->distance); STREAM_GETL(s, nhr->metric); STREAM_GETC(s, nhr->nexthop_num); for (i = 0; i < nhr->nexthop_num; i++) { STREAM_GETL(s, nhr->nexthops[i].vrf_id); STREAM_GETC(s, nhr->nexthops[i].type); switch (nhr->nexthops[i].type) { case NEXTHOP_TYPE_IPV4: case NEXTHOP_TYPE_IPV4_IFINDEX: STREAM_GET(&nhr->nexthops[i].gate.ipv4.s_addr, s, IPV4_MAX_BYTELEN); STREAM_GETL(s, nhr->nexthops[i].ifindex); break; case NEXTHOP_TYPE_IFINDEX: STREAM_GETL(s, nhr->nexthops[i].ifindex); break; case NEXTHOP_TYPE_IPV6: case NEXTHOP_TYPE_IPV6_IFINDEX: STREAM_GET(&nhr->nexthops[i].gate.ipv6, s, IPV6_MAX_BYTELEN); STREAM_GETL(s, nhr->nexthops[i].ifindex); break; case NEXTHOP_TYPE_BLACKHOLE: break; } STREAM_GETC(s, nhr->nexthops[i].label_num); if (nhr->nexthops[i].label_num > MPLS_MAX_LABELS) { flog_err(EC_LIB_ZAPI_ENCODE, "%s: invalid number of MPLS labels (%u)", __func__, nhr->nexthops[i].label_num); return false; } if (nhr->nexthops[i].label_num) STREAM_GET(&nhr->nexthops[i].labels[0], s, nhr->nexthops[i].label_num * sizeof(mpls_label_t)); } return true; stream_failure: return false; } /* * send a ZEBRA_REDISTRIBUTE_ADD or ZEBRA_REDISTRIBUTE_DELETE * for the route type (ZEBRA_ROUTE_KERNEL etc.). The zebra server will * then set/unset redist[type] in the client handle (a struct zserv) for the * sending client */ int zebra_redistribute_send(int command, struct zclient *zclient, afi_t afi, int type, unsigned short instance, vrf_id_t vrf_id) { struct stream *s; s = zclient->obuf; stream_reset(s); zclient_create_header(s, command, vrf_id); stream_putc(s, afi); stream_putc(s, type); stream_putw(s, instance); stream_putw_at(s, 0, stream_get_endp(s)); return zclient_send_message(zclient); } int zebra_redistribute_default_send(int command, struct zclient *zclient, afi_t afi, vrf_id_t vrf_id) { struct stream *s; s = zclient->obuf; stream_reset(s); zclient_create_header(s, command, vrf_id); stream_putc(s, afi); stream_putw_at(s, 0, stream_get_endp(s)); return zclient_send_message(zclient); } /* Get prefix in ZServ format; family should be filled in on prefix */ static void zclient_stream_get_prefix(struct stream *s, struct prefix *p) { size_t plen = prefix_blen(p); uint8_t c; p->prefixlen = 0; if (plen == 0) return; stream_get(&p->u.prefix, s, plen); STREAM_GETC(s, c); p->prefixlen = MIN(plen * 8, c); stream_failure: return; } /* Router-id update from zebra daemon. */ void zebra_router_id_update_read(struct stream *s, struct prefix *rid) { /* Fetch interface address. */ STREAM_GETC(s, rid->family); zclient_stream_get_prefix(s, rid); stream_failure: return; } /* Interface addition from zebra daemon. */ /* * The format of the message sent with type ZEBRA_INTERFACE_ADD or * ZEBRA_INTERFACE_DELETE from zebra to the client is: * 0 1 2 3 * 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 * +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ * | ifname | * | | * | | * | | * | | * +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ * | ifindex | * +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ * | status | * +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ * | if_flags | * | | * +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ * | metric | * +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ * | speed | * +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ * | ifmtu | * +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ * | ifmtu6 | * +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ * | bandwidth | * +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ * | parent ifindex | * +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ * | Link Layer Type | * +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ * | Harware Address Length | * +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ * | Hardware Address if HW lenght different from 0 | * | ... max INTERFACE_HWADDR_MAX | * +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ * | Link_params? | Whether a link-params follows: 1 or 0. * +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ * | Link_params 0 or 1 INTERFACE_LINK_PARAMS_SIZE sized | * | .... (struct if_link_params). | * +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ */ static void zclient_vrf_add(struct zclient *zclient, vrf_id_t vrf_id) { struct vrf *vrf; char vrfname_tmp[VRF_NAMSIZ]; struct vrf_data data; stream_get(&data, zclient->ibuf, sizeof(struct vrf_data)); /* Read interface name. */ stream_get(vrfname_tmp, zclient->ibuf, VRF_NAMSIZ); /* Lookup/create vrf by vrf_id. */ vrf = vrf_get(vrf_id, vrfname_tmp); vrf->data.l.table_id = data.l.table_id; memcpy(vrf->data.l.netns_name, data.l.netns_name, NS_NAMSIZ); /* overwrite default vrf */ if (vrf_id == VRF_DEFAULT) vrf_set_default_name(vrfname_tmp, false); vrf_enable(vrf); } static void zclient_vrf_delete(struct zclient *zclient, vrf_id_t vrf_id) { struct vrf *vrf; /* Lookup vrf by vrf_id. */ vrf = vrf_lookup_by_id(vrf_id); /* * If a routing protocol doesn't know about a * vrf that is about to be deleted. There is * no point in attempting to delete it. */ if (!vrf) return; vrf_delete(vrf); } struct interface *zebra_interface_add_read(struct stream *s, vrf_id_t vrf_id) { struct interface *ifp; char ifname_tmp[INTERFACE_NAMSIZ]; /* Read interface name. */ stream_get(ifname_tmp, s, INTERFACE_NAMSIZ); /* Lookup/create interface by name. */ ifp = if_get_by_name(ifname_tmp, vrf_id); zebra_interface_if_set_value(s, ifp); return ifp; } /* * Read interface up/down msg (ZEBRA_INTERFACE_UP/ZEBRA_INTERFACE_DOWN) * from zebra server. The format of this message is the same as * that sent for ZEBRA_INTERFACE_ADD/ZEBRA_INTERFACE_DELETE (see * comments for zebra_interface_add_read), except that no sockaddr_dl * is sent at the tail of the message. */ struct interface *zebra_interface_state_read(struct stream *s, vrf_id_t vrf_id) { struct interface *ifp; char ifname_tmp[INTERFACE_NAMSIZ]; /* Read interface name. */ stream_get(ifname_tmp, s, INTERFACE_NAMSIZ); /* Lookup this by interface index. */ ifp = if_lookup_by_name(ifname_tmp, vrf_id); if (ifp == NULL) { flog_err(EC_LIB_ZAPI_ENCODE, "INTERFACE_STATE: Cannot find IF %s in VRF %d", ifname_tmp, vrf_id); return NULL; } zebra_interface_if_set_value(s, ifp); return ifp; } static void link_params_set_value(struct stream *s, struct if_link_params *iflp) { if (iflp == NULL) return; iflp->lp_status = stream_getl(s); iflp->te_metric = stream_getl(s); iflp->max_bw = stream_getf(s); iflp->max_rsv_bw = stream_getf(s); uint32_t bwclassnum = stream_getl(s); { unsigned int i; for (i = 0; i < bwclassnum && i < MAX_CLASS_TYPE; i++) iflp->unrsv_bw[i] = stream_getf(s); if (i < bwclassnum) flog_err( EC_LIB_ZAPI_MISSMATCH, "%s: received %d > %d (MAX_CLASS_TYPE) bw entries" " - outdated library?", __func__, bwclassnum, MAX_CLASS_TYPE); } iflp->admin_grp = stream_getl(s); iflp->rmt_as = stream_getl(s); iflp->rmt_ip.s_addr = stream_get_ipv4(s); iflp->av_delay = stream_getl(s); iflp->min_delay = stream_getl(s); iflp->max_delay = stream_getl(s); iflp->delay_var = stream_getl(s); iflp->pkt_loss = stream_getf(s); iflp->res_bw = stream_getf(s); iflp->ava_bw = stream_getf(s); iflp->use_bw = stream_getf(s); } struct interface *zebra_interface_link_params_read(struct stream *s, vrf_id_t vrf_id) { struct if_link_params *iflp; ifindex_t ifindex; assert(s); ifindex = stream_getl(s); struct interface *ifp = if_lookup_by_index(ifindex, vrf_id); if (ifp == NULL) { flog_err(EC_LIB_ZAPI_ENCODE, "%s: unknown ifindex %u, shouldn't happen", __func__, ifindex); return NULL; } if ((iflp = if_link_params_get(ifp)) == NULL) return NULL; link_params_set_value(s, iflp); return ifp; } void zebra_interface_if_set_value(struct stream *s, struct interface *ifp) { uint8_t link_params_status = 0; ifindex_t old_ifindex; old_ifindex = ifp->ifindex; /* Read interface's index. */ if_set_index(ifp, stream_getl(s)); ifp->status = stream_getc(s); /* Read interface's value. */ ifp->flags = stream_getq(s); ifp->ptm_enable = stream_getc(s); ifp->ptm_status = stream_getc(s); ifp->metric = stream_getl(s); ifp->speed = stream_getl(s); ifp->mtu = stream_getl(s); ifp->mtu6 = stream_getl(s); ifp->bandwidth = stream_getl(s); ifp->link_ifindex = stream_getl(s); ifp->ll_type = stream_getl(s); ifp->hw_addr_len = stream_getl(s); if (ifp->hw_addr_len) stream_get(ifp->hw_addr, s, MIN(ifp->hw_addr_len, INTERFACE_HWADDR_MAX)); /* Read Traffic Engineering status */ link_params_status = stream_getc(s); /* Then, Traffic Engineering parameters if any */ if (link_params_status) { struct if_link_params *iflp = if_link_params_get(ifp); link_params_set_value(s, iflp); } nexthop_group_interface_state_change(ifp, old_ifindex); } size_t zebra_interface_link_params_write(struct stream *s, struct interface *ifp) { size_t w; struct if_link_params *iflp; int i; if (s == NULL || ifp == NULL || ifp->link_params == NULL) return 0; iflp = ifp->link_params; w = 0; w += stream_putl(s, iflp->lp_status); w += stream_putl(s, iflp->te_metric); w += stream_putf(s, iflp->max_bw); w += stream_putf(s, iflp->max_rsv_bw); w += stream_putl(s, MAX_CLASS_TYPE); for (i = 0; i < MAX_CLASS_TYPE; i++) w += stream_putf(s, iflp->unrsv_bw[i]); w += stream_putl(s, iflp->admin_grp); w += stream_putl(s, iflp->rmt_as); w += stream_put_in_addr(s, &iflp->rmt_ip); w += stream_putl(s, iflp->av_delay); w += stream_putl(s, iflp->min_delay); w += stream_putl(s, iflp->max_delay); w += stream_putl(s, iflp->delay_var); w += stream_putf(s, iflp->pkt_loss); w += stream_putf(s, iflp->res_bw); w += stream_putf(s, iflp->ava_bw); w += stream_putf(s, iflp->use_bw); return w; } /* * format of message for address additon is: * 0 * 0 1 2 3 4 5 6 7 * +-+-+-+-+-+-+-+-+ * | type | ZEBRA_INTERFACE_ADDRESS_ADD or * +-+-+-+-+-+-+-+-+ ZEBRA_INTERFACE_ADDRES_DELETE * | | * + + * | ifindex | * + + * | | * + + * | | * +-+-+-+-+-+-+-+-+ * | ifc_flags | flags for connected address * +-+-+-+-+-+-+-+-+ * | addr_family | * +-+-+-+-+-+-+-+-+ * | addr... | * : : * | | * +-+-+-+-+-+-+-+-+ * | addr_len | len of addr. E.g., addr_len = 4 for ipv4 addrs. * +-+-+-+-+-+-+-+-+ * | daddr.. | * : : * | | * +-+-+-+-+-+-+-+-+ */ static int memconstant(const void *s, int c, size_t n) { const uint8_t *p = s; while (n-- > 0) if (*p++ != c) return 0; return 1; } struct connected *zebra_interface_address_read(int type, struct stream *s, vrf_id_t vrf_id) { ifindex_t ifindex; struct interface *ifp; struct connected *ifc; struct prefix p, d, *dp; int plen; uint8_t ifc_flags; memset(&p, 0, sizeof(p)); memset(&d, 0, sizeof(d)); /* Get interface index. */ ifindex = stream_getl(s); /* Lookup index. */ ifp = if_lookup_by_index(ifindex, vrf_id); if (ifp == NULL) { flog_err(EC_LIB_ZAPI_ENCODE, "INTERFACE_ADDRESS_%s: Cannot find IF %u in VRF %d", (type == ZEBRA_INTERFACE_ADDRESS_ADD) ? "ADD" : "DEL", ifindex, vrf_id); return NULL; } /* Fetch flag. */ ifc_flags = stream_getc(s); /* Fetch interface address. */ d.family = p.family = stream_getc(s); plen = prefix_blen(&d); zclient_stream_get_prefix(s, &p); /* Fetch destination address. */ stream_get(&d.u.prefix, s, plen); /* N.B. NULL destination pointers are encoded as all zeroes */ dp = memconstant(&d.u.prefix, 0, plen) ? NULL : &d; if (type == ZEBRA_INTERFACE_ADDRESS_ADD) { ifc = connected_lookup_prefix_exact(ifp, &p); if (!ifc) { /* N.B. NULL destination pointers are encoded as all * zeroes */ ifc = connected_add_by_prefix(ifp, &p, dp); } if (ifc) { ifc->flags = ifc_flags; if (ifc->destination) ifc->destination->prefixlen = ifc->address->prefixlen; else if (CHECK_FLAG(ifc->flags, ZEBRA_IFA_PEER)) { /* carp interfaces on OpenBSD with 0.0.0.0/0 as * "peer" */ char buf[PREFIX_STRLEN]; flog_err( EC_LIB_ZAPI_ENCODE, "warning: interface %s address %s with peer flag set, but no peer address!", ifp->name, prefix2str(ifc->address, buf, sizeof buf)); UNSET_FLAG(ifc->flags, ZEBRA_IFA_PEER); } } } else { assert(type == ZEBRA_INTERFACE_ADDRESS_DELETE); ifc = connected_delete_by_prefix(ifp, &p); } return ifc; } /* * format of message for neighbor connected address is: * 0 * 0 1 2 3 4 5 6 7 * +-+-+-+-+-+-+-+-+ * | type | ZEBRA_INTERFACE_NBR_ADDRESS_ADD or * +-+-+-+-+-+-+-+-+ ZEBRA_INTERFACE_NBR_ADDRES_DELETE * | | * + + * | ifindex | * + + * | | * + + * | | * +-+-+-+-+-+-+-+-+ * | addr_family | * +-+-+-+-+-+-+-+-+ * | addr... | * : : * | | * +-+-+-+-+-+-+-+-+ * | addr_len | len of addr. * +-+-+-+-+-+-+-+-+ */ struct nbr_connected * zebra_interface_nbr_address_read(int type, struct stream *s, vrf_id_t vrf_id) { unsigned int ifindex; struct interface *ifp; struct prefix p; struct nbr_connected *ifc; /* Get interface index. */ ifindex = stream_getl(s); /* Lookup index. */ ifp = if_lookup_by_index(ifindex, vrf_id); if (ifp == NULL) { flog_err(EC_LIB_ZAPI_ENCODE, "INTERFACE_NBR_%s: Cannot find IF %u in VRF %d", (type == ZEBRA_INTERFACE_NBR_ADDRESS_ADD) ? "ADD" : "DELETE", ifindex, vrf_id); return NULL; } p.family = stream_getc(s); stream_get(&p.u.prefix, s, prefix_blen(&p)); p.prefixlen = stream_getc(s); if (type == ZEBRA_INTERFACE_NBR_ADDRESS_ADD) { /* Currently only supporting P2P links, so any new RA source address is considered as the replacement of the previously learnt Link-Local address. */ if (!(ifc = listnode_head(ifp->nbr_connected))) { ifc = nbr_connected_new(); ifc->address = prefix_new(); ifc->ifp = ifp; listnode_add(ifp->nbr_connected, ifc); } prefix_copy(ifc->address, &p); } else { assert(type == ZEBRA_INTERFACE_NBR_ADDRESS_DELETE); ifc = nbr_connected_check(ifp, &p); if (ifc) listnode_delete(ifp->nbr_connected, ifc); } return ifc; } struct interface *zebra_interface_vrf_update_read(struct stream *s, vrf_id_t vrf_id, vrf_id_t *new_vrf_id) { char ifname[INTERFACE_NAMSIZ]; struct interface *ifp; vrf_id_t new_id; /* Read interface name. */ stream_get(ifname, s, INTERFACE_NAMSIZ); /* Lookup interface. */ ifp = if_lookup_by_name(ifname, vrf_id); if (ifp == NULL) { flog_err(EC_LIB_ZAPI_ENCODE, "INTERFACE_VRF_UPDATE: Cannot find IF %s in VRF %d", ifname, vrf_id); return NULL; } /* Fetch new VRF Id. */ new_id = stream_getl(s); *new_vrf_id = new_id; return ifp; } /* filter unwanted messages until the expected one arrives */ static int zclient_read_sync_response(struct zclient *zclient, uint16_t expected_cmd) { struct stream *s; uint16_t size = -1; uint8_t marker; uint8_t version; vrf_id_t vrf_id; uint16_t cmd; fd_set readfds; int ret; ret = 0; cmd = expected_cmd + 1; while (ret == 0 && cmd != expected_cmd) { s = zclient->ibuf; stream_reset(s); /* wait until response arrives */ FD_ZERO(&readfds); FD_SET(zclient->sock, &readfds); select(zclient->sock + 1, &readfds, NULL, NULL, NULL); if (!FD_ISSET(zclient->sock, &readfds)) continue; /* read response */ ret = zclient_read_header(s, zclient->sock, &size, &marker, &version, &vrf_id, &cmd); if (zclient_debug) zlog_debug("%s: Response (%d bytes) received", __func__, size); } if (ret != 0) { flog_err(EC_LIB_ZAPI_ENCODE, "%s: Invalid Sync Message Reply", __func__); return -1; } return 0; } /** * Connect to label manager in a syncronous way * * It first writes the request to zcient output buffer and then * immediately reads the answer from the input buffer. * * @param zclient Zclient used to connect to label manager (zebra) * @param async Synchronous (0) or asynchronous (1) operation * @result Result of response */ int lm_label_manager_connect(struct zclient *zclient, int async) { int ret; struct stream *s; uint8_t result; uint16_t cmd = async ? ZEBRA_LABEL_MANAGER_CONNECT_ASYNC : ZEBRA_LABEL_MANAGER_CONNECT; if (zclient_debug) zlog_debug("Connecting to Label Manager (LM)"); if (zclient->sock < 0) { zlog_debug("%s: invalid zclient socket", __func__); return -1; } /* send request */ s = zclient->obuf; stream_reset(s); zclient_create_header(s, cmd, VRF_DEFAULT); /* proto */ stream_putc(s, zclient->redist_default); /* instance */ stream_putw(s, zclient->instance); /* Put length at the first point of the stream. */ stream_putw_at(s, 0, stream_get_endp(s)); ret = writen(zclient->sock, s->data, stream_get_endp(s)); if (ret < 0) { flog_err(EC_LIB_ZAPI_SOCKET, "Can't write to zclient sock"); close(zclient->sock); zclient->sock = -1; return -1; } if (ret == 0) { flog_err(EC_LIB_ZAPI_SOCKET, "Zclient sock closed"); close(zclient->sock); zclient->sock = -1; return -1; } if (zclient_debug) zlog_debug("LM connect request sent (%d bytes)", ret); if (async) return 0; /* read response */ if (zclient_read_sync_response(zclient, cmd) != 0) return -1; s = zclient->ibuf; /* read instance and proto */ uint8_t proto = stream_getc(s); uint16_t instance = stream_getw(s); /* sanity */ if (proto != zclient->redist_default) flog_err( EC_LIB_ZAPI_ENCODE, "Wrong proto (%u) in LM connect response. Should be %u", proto, zclient->redist_default); if (instance != zclient->instance) flog_err( EC_LIB_ZAPI_ENCODE, "Wrong instId (%u) in LM connect response. Should be %u", instance, zclient->instance); /* result code */ result = stream_getc(s); if (zclient_debug) zlog_debug("LM connect-response received, result %u", result); return (int)result; } /* * Asynchronous label chunk request * * @param zclient Zclient used to connect to label manager (zebra) * @param keep Avoid garbage collection * @param chunk_size Amount of labels requested * @param base Base for the label chunk. if MPLS_LABEL_BASE_ANY we do not care * @result 0 on success, -1 otherwise */ int zclient_send_get_label_chunk(struct zclient *zclient, uint8_t keep, uint32_t chunk_size, uint32_t base) { struct stream *s; if (zclient_debug) zlog_debug("Getting Label Chunk"); if (zclient->sock < 0) return -1; s = zclient->obuf; stream_reset(s); zclient_create_header(s, ZEBRA_GET_LABEL_CHUNK, VRF_DEFAULT); /* proto */ stream_putc(s, zclient->redist_default); /* instance */ stream_putw(s, zclient->instance); stream_putc(s, keep); stream_putl(s, chunk_size); stream_putl(s, base); /* Put length at the first point of the stream. */ stream_putw_at(s, 0, stream_get_endp(s)); return zclient_send_message(zclient); } /** * Function to request a label chunk in a syncronous way * * It first writes the request to zlcient output buffer and then * immediately reads the answer from the input buffer. * * @param zclient Zclient used to connect to label manager (zebra) * @param keep Avoid garbage collection * @param chunk_size Amount of labels requested * @param start To write first assigned chunk label to * @param end To write last assigned chunk label to * @result 0 on success, -1 otherwise */ int lm_get_label_chunk(struct zclient *zclient, uint8_t keep, uint32_t base, uint32_t chunk_size, uint32_t *start, uint32_t *end) { int ret; struct stream *s; uint8_t response_keep; if (zclient_debug) zlog_debug("Getting Label Chunk"); if (zclient->sock < 0) return -1; /* send request */ s = zclient->obuf; stream_reset(s); zclient_create_header(s, ZEBRA_GET_LABEL_CHUNK, VRF_DEFAULT); /* proto */ stream_putc(s, zclient->redist_default); /* instance */ stream_putw(s, zclient->instance); /* keep */ stream_putc(s, keep); /* chunk size */ stream_putl(s, chunk_size); /* requested chunk base */ stream_putl(s, base); /* Put length at the first point of the stream. */ stream_putw_at(s, 0, stream_get_endp(s)); ret = writen(zclient->sock, s->data, stream_get_endp(s)); if (ret < 0) { flog_err(EC_LIB_ZAPI_SOCKET, "Can't write to zclient sock"); close(zclient->sock); zclient->sock = -1; return -1; } if (ret == 0) { flog_err(EC_LIB_ZAPI_SOCKET, "Zclient sock closed"); close(zclient->sock); zclient->sock = -1; return -1; } if (zclient_debug) zlog_debug("Label chunk request (%d bytes) sent", ret); /* read response */ if (zclient_read_sync_response(zclient, ZEBRA_GET_LABEL_CHUNK) != 0) return -1; /* parse response */ s = zclient->ibuf; /* read proto and instance */ uint8_t proto = stream_getc(s); uint16_t instance = stream_getw(s); /* sanities */ if (proto != zclient->redist_default) flog_err(EC_LIB_ZAPI_ENCODE, "Wrong proto (%u) in get chunk response. Should be %u", proto, zclient->redist_default); if (instance != zclient->instance) flog_err(EC_LIB_ZAPI_ENCODE, "Wrong instId (%u) in get chunk response Should be %u", instance, zclient->instance); /* if we requested a specific chunk and it could not be allocated, the * response message will end here */ if (!STREAM_READABLE(s)) { zlog_info("Unable to assign Label Chunk to %s instance %u", zebra_route_string(proto), instance); return -1; } /* keep */ response_keep = stream_getc(s); /* start and end labels */ *start = stream_getl(s); *end = stream_getl(s); /* not owning this response */ if (keep != response_keep) { flog_err( EC_LIB_ZAPI_ENCODE, "Invalid Label chunk: %u - %u, keeps mismatch %u != %u", *start, *end, keep, response_keep); } /* sanity */ if (*start > *end || *start < MPLS_LABEL_UNRESERVED_MIN || *end > MPLS_LABEL_UNRESERVED_MAX) { flog_err(EC_LIB_ZAPI_ENCODE, "Invalid Label chunk: %u - %u", *start, *end); return -1; } if (zclient_debug) zlog_debug("Label Chunk assign: %u - %u (%u)", *start, *end, response_keep); return 0; } /** * Function to release a label chunk * * @param zclient Zclient used to connect to label manager (zebra) * @param start First label of chunk * @param end Last label of chunk * @result 0 on success, -1 otherwise */ int lm_release_label_chunk(struct zclient *zclient, uint32_t start, uint32_t end) { int ret; struct stream *s; if (zclient_debug) zlog_debug("Releasing Label Chunk %u - %u", start, end); if (zclient->sock < 0) return -1; /* send request */ s = zclient->obuf; stream_reset(s); zclient_create_header(s, ZEBRA_RELEASE_LABEL_CHUNK, VRF_DEFAULT); /* proto */ stream_putc(s, zclient->redist_default); /* instance */ stream_putw(s, zclient->instance); /* start */ stream_putl(s, start); /* end */ stream_putl(s, end); /* Put length at the first point of the stream. */ stream_putw_at(s, 0, stream_get_endp(s)); ret = writen(zclient->sock, s->data, stream_get_endp(s)); if (ret < 0) { flog_err(EC_LIB_ZAPI_SOCKET, "Can't write to zclient sock"); close(zclient->sock); zclient->sock = -1; return -1; } if (ret == 0) { flog_err(EC_LIB_ZAPI_SOCKET, "Zclient sock connection closed"); close(zclient->sock); zclient->sock = -1; return -1; } return 0; } /** * Connect to table manager in a syncronous way * * It first writes the request to zcient output buffer and then * immediately reads the answer from the input buffer. * * @param zclient Zclient used to connect to table manager (zebra) * @result Result of response */ int tm_table_manager_connect(struct zclient *zclient) { int ret; struct stream *s; uint8_t result; if (zclient_debug) zlog_debug("Connecting to Table Manager"); if (zclient->sock < 0) return -1; /* send request */ s = zclient->obuf; stream_reset(s); zclient_create_header(s, ZEBRA_TABLE_MANAGER_CONNECT, VRF_DEFAULT); /* proto */ stream_putc(s, zclient->redist_default); /* instance */ stream_putw(s, zclient->instance); /* Put length at the first point of the stream. */ stream_putw_at(s, 0, stream_get_endp(s)); ret = zclient_send_message(zclient); if (ret < 0) return -1; if (zclient_debug) zlog_debug("%s: Table manager connect request sent", __func__); /* read response */ if (zclient_read_sync_response(zclient, ZEBRA_TABLE_MANAGER_CONNECT) != 0) return -1; /* result */ s = zclient->ibuf; STREAM_GETC(s, result); if (zclient_debug) zlog_debug( "%s: Table Manager connect response received, result %u", __func__, result); return (int)result; stream_failure: return -1; } /** * Function to request a table chunk in a syncronous way * * It first writes the request to zclient output buffer and then * immediately reads the answer from the input buffer. * * @param zclient Zclient used to connect to table manager (zebra) * @param chunk_size Amount of table requested * @param start to write first assigned chunk table RT ID to * @param end To write last assigned chunk table RT ID to * @result 0 on success, -1 otherwise */ int tm_get_table_chunk(struct zclient *zclient, uint32_t chunk_size, uint32_t *start, uint32_t *end) { int ret; struct stream *s; if (zclient_debug) zlog_debug("Getting Table Chunk"); if (zclient->sock < 0) return -1; /* send request */ s = zclient->obuf; stream_reset(s); zclient_create_header(s, ZEBRA_GET_TABLE_CHUNK, VRF_DEFAULT); /* chunk size */ stream_putl(s, chunk_size); /* Put length at the first point of the stream. */ stream_putw_at(s, 0, stream_get_endp(s)); ret = writen(zclient->sock, s->data, stream_get_endp(s)); if (ret < 0) { flog_err(EC_LIB_ZAPI_SOCKET, "%s: can't write to zclient->sock", __func__); close(zclient->sock); zclient->sock = -1; return -1; } if (ret == 0) { flog_err(EC_LIB_ZAPI_SOCKET, "%s: zclient->sock connection closed", __func__); close(zclient->sock); zclient->sock = -1; return -1; } if (zclient_debug) zlog_debug("%s: Table chunk request (%d bytes) sent", __func__, ret); /* read response */ if (zclient_read_sync_response(zclient, ZEBRA_GET_TABLE_CHUNK) != 0) return -1; s = zclient->ibuf; /* start and end table IDs */ STREAM_GETL(s, *start); STREAM_GETL(s, *end); if (zclient_debug) zlog_debug("Table Chunk assign: %u - %u ", *start, *end); return 0; stream_failure: return -1; } /** * Function to release a table chunk * * @param zclient Zclient used to connect to table manager (zebra) * @param start First label of table * @param end Last label of chunk * @result 0 on success, -1 otherwise */ int tm_release_table_chunk(struct zclient *zclient, uint32_t start, uint32_t end) { struct stream *s; if (zclient_debug) zlog_debug("Releasing Table Chunk"); if (zclient->sock < 0) return -1; /* send request */ s = zclient->obuf; stream_reset(s); zclient_create_header(s, ZEBRA_RELEASE_TABLE_CHUNK, VRF_DEFAULT); /* start */ stream_putl(s, start); /* end */ stream_putl(s, end); /* Put length at the first point of the stream. */ stream_putw_at(s, 0, stream_get_endp(s)); return zclient_send_message(zclient); } int zebra_send_pw(struct zclient *zclient, int command, struct zapi_pw *pw) { struct stream *s; /* Reset stream. */ s = zclient->obuf; stream_reset(s); zclient_create_header(s, command, VRF_DEFAULT); stream_write(s, pw->ifname, IF_NAMESIZE); stream_putl(s, pw->ifindex); /* Put type */ stream_putl(s, pw->type); /* Put nexthop */ stream_putl(s, pw->af); switch (pw->af) { case AF_INET: stream_put_in_addr(s, &pw->nexthop.ipv4); break; case AF_INET6: stream_write(s, (uint8_t *)&pw->nexthop.ipv6, 16); break; default: flog_err(EC_LIB_ZAPI_ENCODE, "%s: unknown af", __func__); return -1; } /* Put labels */ stream_putl(s, pw->local_label); stream_putl(s, pw->remote_label); /* Put flags */ stream_putc(s, pw->flags); /* Protocol specific fields */ stream_write(s, &pw->data, sizeof(union pw_protocol_fields)); /* Put length at the first point of the stream. */ stream_putw_at(s, 0, stream_get_endp(s)); return zclient_send_message(zclient); } /* * Receive PW status update from Zebra and send it to LDE process. */ void zebra_read_pw_status_update(ZAPI_CALLBACK_ARGS, struct zapi_pw_status *pw) { struct stream *s; memset(pw, 0, sizeof(struct zapi_pw_status)); s = zclient->ibuf; /* Get data. */ stream_get(pw->ifname, s, IF_NAMESIZE); pw->ifindex = stream_getl(s); pw->status = stream_getl(s); } static void zclient_capability_decode(ZAPI_CALLBACK_ARGS) { struct zclient_capabilities cap; struct stream *s = zclient->ibuf; int vrf_backend; uint8_t mpls_enabled; STREAM_GETL(s, vrf_backend); vrf_configure_backend(vrf_backend); memset(&cap, 0, sizeof(cap)); STREAM_GETC(s, mpls_enabled); cap.mpls_enabled = !!mpls_enabled; STREAM_GETL(s, cap.ecmp); STREAM_GETC(s, cap.role); if (zclient->zebra_capabilities) (*zclient->zebra_capabilities)(&cap); stream_failure: return; } /* Zebra client message read function. */ static int zclient_read(struct thread *thread) { size_t already; uint16_t length, command; uint8_t marker, version; vrf_id_t vrf_id; struct zclient *zclient; /* Get socket to zebra. */ zclient = THREAD_ARG(thread); zclient->t_read = NULL; /* Read zebra header (if we don't have it already). */ if ((already = stream_get_endp(zclient->ibuf)) < ZEBRA_HEADER_SIZE) { ssize_t nbyte; if (((nbyte = stream_read_try(zclient->ibuf, zclient->sock, ZEBRA_HEADER_SIZE - already)) == 0) || (nbyte == -1)) { if (zclient_debug) zlog_debug( "zclient connection closed socket [%d].", zclient->sock); return zclient_failed(zclient); } if (nbyte != (ssize_t)(ZEBRA_HEADER_SIZE - already)) { /* Try again later. */ zclient_event(ZCLIENT_READ, zclient); return 0; } already = ZEBRA_HEADER_SIZE; } /* Reset to read from the beginning of the incoming packet. */ stream_set_getp(zclient->ibuf, 0); /* Fetch header values. */ length = stream_getw(zclient->ibuf); marker = stream_getc(zclient->ibuf); version = stream_getc(zclient->ibuf); vrf_id = stream_getl(zclient->ibuf); command = stream_getw(zclient->ibuf); if (marker != ZEBRA_HEADER_MARKER || version != ZSERV_VERSION) { flog_err( EC_LIB_ZAPI_MISSMATCH, "%s: socket %d version mismatch, marker %d, version %d", __func__, zclient->sock, marker, version); return zclient_failed(zclient); } if (length < ZEBRA_HEADER_SIZE) { flog_err(EC_LIB_ZAPI_MISSMATCH, "%s: socket %d message length %u is less than %d ", __func__, zclient->sock, length, ZEBRA_HEADER_SIZE); return zclient_failed(zclient); } /* Length check. */ if (length > STREAM_SIZE(zclient->ibuf)) { struct stream *ns; flog_err( EC_LIB_ZAPI_ENCODE, "%s: message size %u exceeds buffer size %lu, expanding...", __func__, length, (unsigned long)STREAM_SIZE(zclient->ibuf)); ns = stream_new(length); stream_copy(ns, zclient->ibuf); stream_free(zclient->ibuf); zclient->ibuf = ns; } /* Read rest of zebra packet. */ if (already < length) { ssize_t nbyte; if (((nbyte = stream_read_try(zclient->ibuf, zclient->sock, length - already)) == 0) || (nbyte == -1)) { if (zclient_debug) zlog_debug( "zclient connection closed socket [%d].", zclient->sock); return zclient_failed(zclient); } if (nbyte != (ssize_t)(length - already)) { /* Try again later. */ zclient_event(ZCLIENT_READ, zclient); return 0; } } length -= ZEBRA_HEADER_SIZE; if (zclient_debug) zlog_debug("zclient 0x%p command %s VRF %u", (void *)zclient, zserv_command_string(command), vrf_id); switch (command) { case ZEBRA_CAPABILITIES: zclient_capability_decode(command, zclient, length, vrf_id); break; case ZEBRA_ROUTER_ID_UPDATE: if (zclient->router_id_update) (*zclient->router_id_update)(command, zclient, length, vrf_id); break; case ZEBRA_VRF_ADD: zclient_vrf_add(zclient, vrf_id); break; case ZEBRA_VRF_DELETE: zclient_vrf_delete(zclient, vrf_id); break; case ZEBRA_INTERFACE_ADD: if (zclient->interface_add) (*zclient->interface_add)(command, zclient, length, vrf_id); break; case ZEBRA_INTERFACE_DELETE: if (zclient->interface_delete) (*zclient->interface_delete)(command, zclient, length, vrf_id); break; case ZEBRA_INTERFACE_ADDRESS_ADD: if (zclient->interface_address_add) (*zclient->interface_address_add)(command, zclient, length, vrf_id); break; case ZEBRA_INTERFACE_ADDRESS_DELETE: if (zclient->interface_address_delete) (*zclient->interface_address_delete)(command, zclient, length, vrf_id); break; case ZEBRA_INTERFACE_BFD_DEST_UPDATE: if (zclient->interface_bfd_dest_update) (*zclient->interface_bfd_dest_update)(command, zclient, length, vrf_id); break; case ZEBRA_INTERFACE_NBR_ADDRESS_ADD: if (zclient->interface_nbr_address_add) (*zclient->interface_nbr_address_add)(command, zclient, length, vrf_id); break; case ZEBRA_INTERFACE_NBR_ADDRESS_DELETE: if (zclient->interface_nbr_address_delete) (*zclient->interface_nbr_address_delete)( command, zclient, length, vrf_id); break; case ZEBRA_INTERFACE_UP: if (zclient->interface_up) (*zclient->interface_up)(command, zclient, length, vrf_id); break; case ZEBRA_INTERFACE_DOWN: if (zclient->interface_down) (*zclient->interface_down)(command, zclient, length, vrf_id); break; case ZEBRA_INTERFACE_VRF_UPDATE: if (zclient->interface_vrf_update) (*zclient->interface_vrf_update)(command, zclient, length, vrf_id); break; case ZEBRA_NEXTHOP_UPDATE: if (zclient_debug) zlog_debug("zclient rcvd nexthop update"); if (zclient->nexthop_update) (*zclient->nexthop_update)(command, zclient, length, vrf_id); break; case ZEBRA_IMPORT_CHECK_UPDATE: if (zclient_debug) zlog_debug("zclient rcvd import check update"); if (zclient->import_check_update) (*zclient->import_check_update)(command, zclient, length, vrf_id); break; case ZEBRA_BFD_DEST_REPLAY: if (zclient->bfd_dest_replay) (*zclient->bfd_dest_replay)(command, zclient, length, vrf_id); break; case ZEBRA_REDISTRIBUTE_ROUTE_ADD: if (zclient->redistribute_route_add) (*zclient->redistribute_route_add)(command, zclient, length, vrf_id); break; case ZEBRA_REDISTRIBUTE_ROUTE_DEL: if (zclient->redistribute_route_del) (*zclient->redistribute_route_del)(command, zclient, length, vrf_id); break; case ZEBRA_INTERFACE_LINK_PARAMS: if (zclient->interface_link_params) (*zclient->interface_link_params)(command, zclient, length, vrf_id); break; case ZEBRA_FEC_UPDATE: if (zclient_debug) zlog_debug("zclient rcvd fec update"); if (zclient->fec_update) (*zclient->fec_update)(command, zclient, length); break; case ZEBRA_LOCAL_ES_ADD: if (zclient->local_es_add) (*zclient->local_es_add)(command, zclient, length, vrf_id); break; case ZEBRA_LOCAL_ES_DEL: if (zclient->local_es_del) (*zclient->local_es_del)(command, zclient, length, vrf_id); break; case ZEBRA_VNI_ADD: if (zclient->local_vni_add) (*zclient->local_vni_add)(command, zclient, length, vrf_id); break; case ZEBRA_VNI_DEL: if (zclient->local_vni_del) (*zclient->local_vni_del)(command, zclient, length, vrf_id); break; case ZEBRA_L3VNI_ADD: if (zclient->local_l3vni_add) (*zclient->local_l3vni_add)(command, zclient, length, vrf_id); break; case ZEBRA_L3VNI_DEL: if (zclient->local_l3vni_del) (*zclient->local_l3vni_del)(command, zclient, length, vrf_id); break; case ZEBRA_MACIP_ADD: if (zclient->local_macip_add) (*zclient->local_macip_add)(command, zclient, length, vrf_id); break; case ZEBRA_MACIP_DEL: if (zclient->local_macip_del) (*zclient->local_macip_del)(command, zclient, length, vrf_id); break; case ZEBRA_IP_PREFIX_ROUTE_ADD: if (zclient->local_ip_prefix_add) (*zclient->local_ip_prefix_add)(command, zclient, length, vrf_id); break; case ZEBRA_IP_PREFIX_ROUTE_DEL: if (zclient->local_ip_prefix_del) (*zclient->local_ip_prefix_del)(command, zclient, length, vrf_id); break; case ZEBRA_PW_STATUS_UPDATE: if (zclient->pw_status_update) (*zclient->pw_status_update)(command, zclient, length, vrf_id); break; case ZEBRA_ROUTE_NOTIFY_OWNER: if (zclient->route_notify_owner) (*zclient->route_notify_owner)(command, zclient, length, vrf_id); break; case ZEBRA_RULE_NOTIFY_OWNER: if (zclient->rule_notify_owner) (*zclient->rule_notify_owner)(command, zclient, length, vrf_id); break; case ZEBRA_GET_LABEL_CHUNK: if (zclient->label_chunk) (*zclient->label_chunk)(command, zclient, length, vrf_id); break; case ZEBRA_IPSET_NOTIFY_OWNER: if (zclient->ipset_notify_owner) (*zclient->ipset_notify_owner)(command, zclient, length, vrf_id); break; case ZEBRA_IPSET_ENTRY_NOTIFY_OWNER: if (zclient->ipset_entry_notify_owner) (*zclient->ipset_entry_notify_owner)(command, zclient, length, vrf_id); break; case ZEBRA_IPTABLE_NOTIFY_OWNER: if (zclient->iptable_notify_owner) (*zclient->iptable_notify_owner)(command, zclient, length, vrf_id); break; case ZEBRA_VXLAN_SG_ADD: if (zclient->vxlan_sg_add) (*zclient->vxlan_sg_add)(command, zclient, length, vrf_id); break; case ZEBRA_VXLAN_SG_DEL: if (zclient->vxlan_sg_del) (*zclient->vxlan_sg_del)(command, zclient, length, vrf_id); break; default: break; } if (zclient->sock < 0) /* Connection was closed during packet processing. */ return -1; /* Register read thread. */ stream_reset(zclient->ibuf); zclient_event(ZCLIENT_READ, zclient); return 0; } void zclient_redistribute(int command, struct zclient *zclient, afi_t afi, int type, unsigned short instance, vrf_id_t vrf_id) { if (instance) { if (command == ZEBRA_REDISTRIBUTE_ADD) { if (redist_check_instance( &zclient->mi_redist[afi][type], instance)) return; redist_add_instance(&zclient->mi_redist[afi][type], instance); } else { if (!redist_check_instance( &zclient->mi_redist[afi][type], instance)) return; redist_del_instance(&zclient->mi_redist[afi][type], instance); } } else { if (command == ZEBRA_REDISTRIBUTE_ADD) { if (vrf_bitmap_check(zclient->redist[afi][type], vrf_id)) return; vrf_bitmap_set(zclient->redist[afi][type], vrf_id); } else { if (!vrf_bitmap_check(zclient->redist[afi][type], vrf_id)) return; vrf_bitmap_unset(zclient->redist[afi][type], vrf_id); } } if (zclient->sock > 0) zebra_redistribute_send(command, zclient, afi, type, instance, vrf_id); } void zclient_redistribute_default(int command, struct zclient *zclient, afi_t afi, vrf_id_t vrf_id) { if (command == ZEBRA_REDISTRIBUTE_DEFAULT_ADD) { if (vrf_bitmap_check(zclient->default_information[afi], vrf_id)) return; vrf_bitmap_set(zclient->default_information[afi], vrf_id); } else { if (!vrf_bitmap_check(zclient->default_information[afi], vrf_id)) return; vrf_bitmap_unset(zclient->default_information[afi], vrf_id); } if (zclient->sock > 0) zebra_redistribute_default_send(command, zclient, afi, vrf_id); } static void zclient_event(enum event event, struct zclient *zclient) { switch (event) { case ZCLIENT_SCHEDULE: thread_add_event(zclient->master, zclient_connect, zclient, 0, &zclient->t_connect); break; case ZCLIENT_CONNECT: if (zclient_debug) zlog_debug( "zclient connect failures: %d schedule interval is now %d", zclient->fail, zclient->fail < 3 ? 10 : 60); thread_add_timer(zclient->master, zclient_connect, zclient, zclient->fail < 3 ? 10 : 60, &zclient->t_connect); break; case ZCLIENT_READ: zclient->t_read = NULL; thread_add_read(zclient->master, zclient_read, zclient, zclient->sock, &zclient->t_read); break; } } void zclient_interface_set_master(struct zclient *client, struct interface *master, struct interface *slave) { struct stream *s; s = client->obuf; stream_reset(s); zclient_create_header(s, ZEBRA_INTERFACE_SET_MASTER, master->vrf_id); stream_putl(s, master->vrf_id); stream_putl(s, master->ifindex); stream_putl(s, slave->vrf_id); stream_putl(s, slave->ifindex); stream_putw_at(s, 0, stream_get_endp(s)); zclient_send_message(client); } frr-7.2.1/lib/zclient.h0000644000000000000000000004770613610377563011621 00000000000000/* Zebra's client header. * Copyright (C) 1999 Kunihiro Ishiguro * * This file is part of GNU Zebra. * * GNU Zebra is free software; you can redistribute it and/or modify it * under the terms of the GNU General Public License as published by * the Free Software Foundation; either version 2, or (at your option) * any later version. * * GNU Zebra 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; see the file COPYING; if not, write to the Free Software * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA */ #ifndef _ZEBRA_ZCLIENT_H #define _ZEBRA_ZCLIENT_H /* For struct zapi_route. */ #include "prefix.h" /* For struct interface and struct connected. */ #include "if.h" /* For vrf_bitmap_t. */ #include "vrf.h" /* For union g_addr */ #include "nexthop.h" /* For union pw_protocol_fields */ #include "pw.h" #include "mlag.h" /* Zebra types. Used in Zserv message header. */ typedef uint16_t zebra_size_t; /* Marker value used in new Zserv, in the byte location corresponding * the command value in the old zserv header. To allow old and new * Zserv headers to be distinguished from each other. */ #define ZEBRA_HEADER_MARKER 254 /* For input/output buffer to zebra. */ #define ZEBRA_MAX_PACKET_SIZ 16384U /* Zebra header size. */ #define ZEBRA_HEADER_SIZE 10 /* special socket path name to use TCP * @ is used as first character because that's abstract socket names on Linux */ #define ZAPI_TCP_PATHNAME "@tcp" /* IPset size name stands for the name of the ipset entry * that can be created by using some zapi interfaces */ #define ZEBRA_IPSET_NAME_SIZE 32 /* IPTable action is defined by two values: either * forward or drop */ #define ZEBRA_IPTABLES_FORWARD 0 #define ZEBRA_IPTABLES_DROP 1 /* Zebra FEC register command flags. */ #define ZEBRA_FEC_REGISTER_LABEL 0x1 #define ZEBRA_FEC_REGISTER_LABEL_INDEX 0x2 extern struct sockaddr_storage zclient_addr; extern socklen_t zclient_addr_len; /* Zebra message types. */ typedef enum { ZEBRA_INTERFACE_ADD, ZEBRA_INTERFACE_DELETE, ZEBRA_INTERFACE_ADDRESS_ADD, ZEBRA_INTERFACE_ADDRESS_DELETE, ZEBRA_INTERFACE_UP, ZEBRA_INTERFACE_DOWN, ZEBRA_INTERFACE_SET_MASTER, ZEBRA_INTERFACE_SET_PROTODOWN, ZEBRA_ROUTE_ADD, ZEBRA_ROUTE_DELETE, ZEBRA_ROUTE_NOTIFY_OWNER, ZEBRA_REDISTRIBUTE_ADD, ZEBRA_REDISTRIBUTE_DELETE, ZEBRA_REDISTRIBUTE_DEFAULT_ADD, ZEBRA_REDISTRIBUTE_DEFAULT_DELETE, ZEBRA_ROUTER_ID_ADD, ZEBRA_ROUTER_ID_DELETE, ZEBRA_ROUTER_ID_UPDATE, ZEBRA_HELLO, ZEBRA_CAPABILITIES, ZEBRA_NEXTHOP_REGISTER, ZEBRA_NEXTHOP_UNREGISTER, ZEBRA_NEXTHOP_UPDATE, ZEBRA_INTERFACE_NBR_ADDRESS_ADD, ZEBRA_INTERFACE_NBR_ADDRESS_DELETE, ZEBRA_INTERFACE_BFD_DEST_UPDATE, ZEBRA_IMPORT_ROUTE_REGISTER, ZEBRA_IMPORT_ROUTE_UNREGISTER, ZEBRA_IMPORT_CHECK_UPDATE, ZEBRA_BFD_DEST_REGISTER, ZEBRA_BFD_DEST_DEREGISTER, ZEBRA_BFD_DEST_UPDATE, ZEBRA_BFD_DEST_REPLAY, ZEBRA_REDISTRIBUTE_ROUTE_ADD, ZEBRA_REDISTRIBUTE_ROUTE_DEL, ZEBRA_VRF_UNREGISTER, ZEBRA_VRF_ADD, ZEBRA_VRF_DELETE, ZEBRA_VRF_LABEL, ZEBRA_INTERFACE_VRF_UPDATE, ZEBRA_BFD_CLIENT_REGISTER, ZEBRA_BFD_CLIENT_DEREGISTER, ZEBRA_INTERFACE_ENABLE_RADV, ZEBRA_INTERFACE_DISABLE_RADV, ZEBRA_IPV4_NEXTHOP_LOOKUP_MRIB, ZEBRA_INTERFACE_LINK_PARAMS, ZEBRA_MPLS_LABELS_ADD, ZEBRA_MPLS_LABELS_DELETE, ZEBRA_IPMR_ROUTE_STATS, ZEBRA_LABEL_MANAGER_CONNECT, ZEBRA_LABEL_MANAGER_CONNECT_ASYNC, ZEBRA_GET_LABEL_CHUNK, ZEBRA_RELEASE_LABEL_CHUNK, ZEBRA_FEC_REGISTER, ZEBRA_FEC_UNREGISTER, ZEBRA_FEC_UPDATE, ZEBRA_ADVERTISE_DEFAULT_GW, ZEBRA_ADVERTISE_SVI_MACIP, ZEBRA_ADVERTISE_SUBNET, ZEBRA_ADVERTISE_ALL_VNI, ZEBRA_LOCAL_ES_ADD, ZEBRA_LOCAL_ES_DEL, ZEBRA_VNI_ADD, ZEBRA_VNI_DEL, ZEBRA_L3VNI_ADD, ZEBRA_L3VNI_DEL, ZEBRA_REMOTE_VTEP_ADD, ZEBRA_REMOTE_VTEP_DEL, ZEBRA_MACIP_ADD, ZEBRA_MACIP_DEL, ZEBRA_IP_PREFIX_ROUTE_ADD, ZEBRA_IP_PREFIX_ROUTE_DEL, ZEBRA_REMOTE_MACIP_ADD, ZEBRA_REMOTE_MACIP_DEL, ZEBRA_DUPLICATE_ADDR_DETECTION, ZEBRA_PW_ADD, ZEBRA_PW_DELETE, ZEBRA_PW_SET, ZEBRA_PW_UNSET, ZEBRA_PW_STATUS_UPDATE, ZEBRA_RULE_ADD, ZEBRA_RULE_DELETE, ZEBRA_RULE_NOTIFY_OWNER, ZEBRA_TABLE_MANAGER_CONNECT, ZEBRA_GET_TABLE_CHUNK, ZEBRA_RELEASE_TABLE_CHUNK, ZEBRA_IPSET_CREATE, ZEBRA_IPSET_DESTROY, ZEBRA_IPSET_ENTRY_ADD, ZEBRA_IPSET_ENTRY_DELETE, ZEBRA_IPSET_NOTIFY_OWNER, ZEBRA_IPSET_ENTRY_NOTIFY_OWNER, ZEBRA_IPTABLE_ADD, ZEBRA_IPTABLE_DELETE, ZEBRA_IPTABLE_NOTIFY_OWNER, ZEBRA_VXLAN_FLOOD_CONTROL, ZEBRA_VXLAN_SG_ADD, ZEBRA_VXLAN_SG_DEL, ZEBRA_VXLAN_SG_REPLAY, } zebra_message_types_t; struct redist_proto { uint8_t enabled; struct list *instances; }; struct zclient_capabilities { uint32_t ecmp; bool mpls_enabled; enum mlag_role role; }; /* Structure for the zebra client. */ struct zclient { /* The thread master we schedule ourselves on */ struct thread_master *master; /* Priviledges to change socket values */ struct zebra_privs_t *privs; /* Do we care about failure events for route install? */ bool receive_notify; /* Socket to zebra daemon. */ int sock; /* Connection failure count. */ int fail; /* Input buffer for zebra message. */ struct stream *ibuf; /* Output buffer for zebra message. */ struct stream *obuf; /* Buffer of data waiting to be written to zebra. */ struct buffer *wb; /* Read and connect thread. */ struct thread *t_read; struct thread *t_connect; /* Thread to write buffered data to zebra. */ struct thread *t_write; /* Redistribute information. */ uint8_t redist_default; /* clients protocol */ unsigned short instance; struct redist_proto mi_redist[AFI_MAX][ZEBRA_ROUTE_MAX]; vrf_bitmap_t redist[AFI_MAX][ZEBRA_ROUTE_MAX]; /* Redistribute defauilt. */ vrf_bitmap_t default_information[AFI_MAX]; #define ZAPI_CALLBACK_ARGS \ int cmd, struct zclient *zclient, uint16_t length, vrf_id_t vrf_id /* Pointer to the callback functions. */ void (*zebra_connected)(struct zclient *); void (*zebra_capabilities)(struct zclient_capabilities *cap); int (*router_id_update)(ZAPI_CALLBACK_ARGS); int (*interface_add)(ZAPI_CALLBACK_ARGS); int (*interface_delete)(ZAPI_CALLBACK_ARGS); int (*interface_up)(ZAPI_CALLBACK_ARGS); int (*interface_down)(ZAPI_CALLBACK_ARGS); int (*interface_address_add)(ZAPI_CALLBACK_ARGS); int (*interface_address_delete)(ZAPI_CALLBACK_ARGS); int (*interface_link_params)(ZAPI_CALLBACK_ARGS); int (*interface_bfd_dest_update)(ZAPI_CALLBACK_ARGS); int (*interface_nbr_address_add)(ZAPI_CALLBACK_ARGS); int (*interface_nbr_address_delete)(ZAPI_CALLBACK_ARGS); int (*interface_vrf_update)(ZAPI_CALLBACK_ARGS); int (*nexthop_update)(ZAPI_CALLBACK_ARGS); int (*import_check_update)(ZAPI_CALLBACK_ARGS); int (*bfd_dest_replay)(ZAPI_CALLBACK_ARGS); int (*redistribute_route_add)(ZAPI_CALLBACK_ARGS); int (*redistribute_route_del)(ZAPI_CALLBACK_ARGS); int (*fec_update)(int, struct zclient *, uint16_t); int (*local_es_add)(ZAPI_CALLBACK_ARGS); int (*local_es_del)(ZAPI_CALLBACK_ARGS); int (*local_vni_add)(ZAPI_CALLBACK_ARGS); int (*local_vni_del)(ZAPI_CALLBACK_ARGS); int (*local_l3vni_add)(ZAPI_CALLBACK_ARGS); int (*local_l3vni_del)(ZAPI_CALLBACK_ARGS); void (*local_ip_prefix_add)(ZAPI_CALLBACK_ARGS); void (*local_ip_prefix_del)(ZAPI_CALLBACK_ARGS); int (*local_macip_add)(ZAPI_CALLBACK_ARGS); int (*local_macip_del)(ZAPI_CALLBACK_ARGS); int (*pw_status_update)(ZAPI_CALLBACK_ARGS); int (*route_notify_owner)(ZAPI_CALLBACK_ARGS); int (*rule_notify_owner)(ZAPI_CALLBACK_ARGS); void (*label_chunk)(ZAPI_CALLBACK_ARGS); int (*ipset_notify_owner)(ZAPI_CALLBACK_ARGS); int (*ipset_entry_notify_owner)(ZAPI_CALLBACK_ARGS); int (*iptable_notify_owner)(ZAPI_CALLBACK_ARGS); int (*vxlan_sg_add)(ZAPI_CALLBACK_ARGS); int (*vxlan_sg_del)(ZAPI_CALLBACK_ARGS); }; /* Zebra API message flag. */ #define ZAPI_MESSAGE_NEXTHOP 0x01 #define ZAPI_MESSAGE_DISTANCE 0x02 #define ZAPI_MESSAGE_METRIC 0x04 #define ZAPI_MESSAGE_TAG 0x08 #define ZAPI_MESSAGE_MTU 0x10 #define ZAPI_MESSAGE_SRCPFX 0x20 #define ZAPI_MESSAGE_LABEL 0x40 /* * This should only be used by a DAEMON that needs to communicate * the table being used is not in the VRF. You must pass the * default vrf, else this will be ignored. */ #define ZAPI_MESSAGE_TABLEID 0x80 #define ZSERV_VERSION 6 /* Zserv protocol message header */ struct zmsghdr { uint16_t length; /* Always set to 255 in new zserv */ uint8_t marker; uint8_t version; vrf_id_t vrf_id; uint16_t command; }; struct zapi_nexthop { enum nexthop_types_t type; vrf_id_t vrf_id; ifindex_t ifindex; bool onlink; union { union g_addr gate; enum blackhole_type bh_type; }; /* MPLS labels for BGP-LU or Segment Routing */ uint8_t label_num; mpls_label_t labels[MPLS_MAX_LABELS]; struct ethaddr rmac; }; /* * Some of these data structures do not map easily to * a actual data structure size giving different compilers * and systems. For those data structures we need * to use the smallest available stream_getX/putX functions * to encode/decode. */ struct zapi_route { uint8_t type; unsigned short instance; uint32_t flags; /* * Cause Zebra to consider this routes nexthops recursively */ #define ZEBRA_FLAG_ALLOW_RECURSION 0x01 /* * This is a route that is read in on startup that was left around * from a previous run of FRR */ #define ZEBRA_FLAG_SELFROUTE 0x02 /* * This flag is used to tell Zebra that the BGP route being passed * down is a IBGP route */ #define ZEBRA_FLAG_IBGP 0x04 /* * This is a route that has been selected for FIB installation. * This flag is set in zebra and can be passed up to routing daemons */ #define ZEBRA_FLAG_SELECTED 0x08 /* * This is a route that we are telling Zebra that this route *must* * win and will be installed even over ZEBRA_FLAG_SELECTED */ #define ZEBRA_FLAG_FIB_OVERRIDE 0x10 /* * This flag tells Zebra that the route is a EVPN route and should * be treated specially */ #define ZEBRA_FLAG_EVPN_ROUTE 0x20 /* * This flag tells Zebra that it should treat the distance passed * down as an additional discriminator for route selection of the * route entry. This mainly is used for backup static routes. */ #define ZEBRA_FLAG_RR_USE_DISTANCE 0x40 uint8_t message; /* * This is an enum but we are going to treat it as a uint8_t * for purpose of encoding/decoding */ safi_t safi; struct prefix prefix; struct prefix_ipv6 src_prefix; uint16_t nexthop_num; struct zapi_nexthop nexthops[MULTIPATH_NUM]; uint8_t distance; uint32_t metric; route_tag_t tag; uint32_t mtu; vrf_id_t vrf_id; uint32_t tableid; }; struct zapi_pw { char ifname[IF_NAMESIZE]; ifindex_t ifindex; int type; int af; union g_addr nexthop; uint32_t local_label; uint32_t remote_label; uint8_t flags; union pw_protocol_fields data; uint8_t protocol; }; struct zapi_pw_status { char ifname[IF_NAMESIZE]; ifindex_t ifindex; uint32_t status; }; enum zapi_route_notify_owner { ZAPI_ROUTE_FAIL_INSTALL, ZAPI_ROUTE_BETTER_ADMIN_WON, ZAPI_ROUTE_INSTALLED, ZAPI_ROUTE_REMOVED, ZAPI_ROUTE_REMOVE_FAIL, }; enum zapi_rule_notify_owner { ZAPI_RULE_FAIL_INSTALL, ZAPI_RULE_INSTALLED, ZAPI_RULE_REMOVED, ZAPI_RULE_FAIL_REMOVE, }; enum ipset_type { IPSET_NET_NET = 1, IPSET_NET_PORT_NET, IPSET_NET_PORT, IPSET_NET }; enum zapi_ipset_notify_owner { ZAPI_IPSET_FAIL_INSTALL, ZAPI_IPSET_INSTALLED, ZAPI_IPSET_REMOVED, ZAPI_IPSET_FAIL_REMOVE, }; enum zapi_ipset_entry_notify_owner { ZAPI_IPSET_ENTRY_FAIL_INSTALL, ZAPI_IPSET_ENTRY_INSTALLED, ZAPI_IPSET_ENTRY_REMOVED, ZAPI_IPSET_ENTRY_FAIL_REMOVE, }; enum zapi_iptable_notify_owner { ZAPI_IPTABLE_FAIL_INSTALL, ZAPI_IPTABLE_INSTALLED, ZAPI_IPTABLE_REMOVED, ZAPI_IPTABLE_FAIL_REMOVE, }; /* Zebra MAC types */ #define ZEBRA_MACIP_TYPE_STICKY 0x01 /* Sticky MAC*/ #define ZEBRA_MACIP_TYPE_GW 0x02 /* gateway (SVI) mac*/ #define ZEBRA_MACIP_TYPE_ROUTER_FLAG 0x04 /* Router Flag - proxy NA */ #define ZEBRA_MACIP_TYPE_OVERRIDE_FLAG 0x08 /* Override Flag */ enum zebra_neigh_state { ZEBRA_NEIGH_INACTIVE = 0, ZEBRA_NEIGH_ACTIVE = 1 }; struct zclient_options { bool receive_notify; }; extern struct zclient_options zclient_options_default; extern struct zclient *zclient_new(struct thread_master *m, struct zclient_options *opt); extern void zclient_init(struct zclient *, int, unsigned short, struct zebra_privs_t *privs); extern int zclient_start(struct zclient *); extern void zclient_stop(struct zclient *); extern void zclient_reset(struct zclient *); extern void zclient_free(struct zclient *); extern int zclient_socket_connect(struct zclient *); extern unsigned short *redist_check_instance(struct redist_proto *, unsigned short); extern void redist_add_instance(struct redist_proto *, unsigned short); extern void redist_del_instance(struct redist_proto *, unsigned short); /* * Send to zebra that the specified vrf is using label to resolve * itself for L3VPN's. Repeated calls of this function with * different labels will cause an effective update of the * label for lookup. If you pass in MPLS_LABEL_NONE * we will cause a delete action and remove this label pop * operation. * * The underlying AF_MPLS doesn't care about afi's * but we can make the zebra_vrf keep track of what * we have installed and play some special games * to get them both installed. */ extern void zclient_send_vrf_label(struct zclient *zclient, vrf_id_t vrf_id, afi_t afi, mpls_label_t label, enum lsp_types_t ltype); extern void zclient_send_reg_requests(struct zclient *, vrf_id_t); extern void zclient_send_dereg_requests(struct zclient *, vrf_id_t); extern void zclient_send_interface_radv_req(struct zclient *zclient, vrf_id_t vrf_id, struct interface *ifp, int enable, int ra_interval); extern int zclient_send_interface_protodown(struct zclient *zclient, vrf_id_t vrf_id, struct interface *ifp, bool down); /* Send redistribute command to zebra daemon. Do not update zclient state. */ extern int zebra_redistribute_send(int command, struct zclient *, afi_t, int type, unsigned short instance, vrf_id_t vrf_id); extern int zebra_redistribute_default_send(int command, struct zclient *zclient, afi_t afi, vrf_id_t vrf_id); /* If state has changed, update state and call zebra_redistribute_send. */ extern void zclient_redistribute(int command, struct zclient *, afi_t, int type, unsigned short instance, vrf_id_t vrf_id); /* If state has changed, update state and send the command to zebra. */ extern void zclient_redistribute_default(int command, struct zclient *, afi_t, vrf_id_t vrf_id); /* Send the message in zclient->obuf to the zebra daemon (or enqueue it). Returns 0 for success or -1 on an I/O error. */ extern int zclient_send_message(struct zclient *); /* create header for command, length to be filled in by user later */ extern void zclient_create_header(struct stream *, uint16_t, vrf_id_t); /* * Read sizeof(struct zmsghdr) bytes from the provided socket and parse the * received data into the specified fields. If this is successful, read the * rest of the packet into the provided stream. * * s * The stream to read into * * sock * The socket to read from * * size * Parsed message size will be placed in the pointed-at integer * * marker * Parsed marker will be placed in the pointed-at byte * * version * Parsed version will be placed in the pointed-at byte * * vrf_id * Parsed VRF ID will be placed in the pointed-at vrf_id_t * * cmd * Parsed command number will be placed in the pointed-at integer * * Returns: * -1 if: * - insufficient data for header was read * - a version mismatch was detected * - a marker mismatch was detected * - header size field specified more data than could be read */ extern int zclient_read_header(struct stream *s, int sock, uint16_t *size, uint8_t *marker, uint8_t *version, vrf_id_t *vrf_id, uint16_t *cmd); /* * Parse header from ZAPI message stream into struct zmsghdr. * This function assumes the stream getp points at the first byte of the header. * If the function is successful then the stream getp will point to the byte * immediately after the last byte of the header. * * zmsg * The stream containing the header * * hdr * The header struct to parse into. * * Returns: * true if parsing succeeded, false otherwise */ extern bool zapi_parse_header(struct stream *zmsg, struct zmsghdr *hdr); extern void zclient_interface_set_master(struct zclient *client, struct interface *master, struct interface *slave); extern struct interface *zebra_interface_add_read(struct stream *, vrf_id_t); extern struct interface *zebra_interface_state_read(struct stream *s, vrf_id_t); extern struct connected *zebra_interface_address_read(int, struct stream *, vrf_id_t); extern struct nbr_connected * zebra_interface_nbr_address_read(int, struct stream *, vrf_id_t); extern struct interface *zebra_interface_vrf_update_read(struct stream *s, vrf_id_t vrf_id, vrf_id_t *new_vrf_id); extern void zebra_interface_if_set_value(struct stream *, struct interface *); extern void zebra_router_id_update_read(struct stream *s, struct prefix *rid); extern struct interface *zebra_interface_link_params_read(struct stream *s, vrf_id_t vrf_id); extern size_t zebra_interface_link_params_write(struct stream *, struct interface *); extern int zclient_send_get_label_chunk(struct zclient *zclient, uint8_t keep, uint32_t chunk_size, uint32_t base); extern int lm_label_manager_connect(struct zclient *zclient, int async); extern int lm_get_label_chunk(struct zclient *zclient, uint8_t keep, uint32_t base, uint32_t chunk_size, uint32_t *start, uint32_t *end); extern int lm_release_label_chunk(struct zclient *zclient, uint32_t start, uint32_t end); extern int tm_table_manager_connect(struct zclient *zclient); extern int tm_get_table_chunk(struct zclient *zclient, uint32_t chunk_size, uint32_t *start, uint32_t *end); extern int tm_release_table_chunk(struct zclient *zclient, uint32_t start, uint32_t end); extern int zebra_send_pw(struct zclient *zclient, int command, struct zapi_pw *pw); extern void zebra_read_pw_status_update(ZAPI_CALLBACK_ARGS, struct zapi_pw_status *pw); extern int zclient_route_send(uint8_t, struct zclient *, struct zapi_route *); extern int zclient_send_rnh(struct zclient *zclient, int command, struct prefix *p, bool exact_match, vrf_id_t vrf_id); extern int zapi_route_encode(uint8_t, struct stream *, struct zapi_route *); extern int zapi_route_decode(struct stream *, struct zapi_route *); bool zapi_route_notify_decode(struct stream *s, struct prefix *p, uint32_t *tableid, enum zapi_route_notify_owner *note); bool zapi_rule_notify_decode(struct stream *s, uint32_t *seqno, uint32_t *priority, uint32_t *unique, ifindex_t *ifindex, enum zapi_rule_notify_owner *note); bool zapi_ipset_notify_decode(struct stream *s, uint32_t *unique, enum zapi_ipset_notify_owner *note); #define ZEBRA_IPSET_NAME_SIZE 32 bool zapi_ipset_entry_notify_decode(struct stream *s, uint32_t *unique, char *ipset_name, enum zapi_ipset_entry_notify_owner *note); bool zapi_iptable_notify_decode(struct stream *s, uint32_t *unique, enum zapi_iptable_notify_owner *note); extern struct nexthop *nexthop_from_zapi_nexthop(struct zapi_nexthop *znh); extern bool zapi_nexthop_update_decode(struct stream *s, struct zapi_route *nhr); static inline void zapi_route_set_blackhole(struct zapi_route *api, enum blackhole_type bh_type) { api->nexthop_num = 1; api->nexthops[0].type = NEXTHOP_TYPE_BLACKHOLE; api->nexthops[0].vrf_id = VRF_DEFAULT; api->nexthops[0].bh_type = bh_type; SET_FLAG(api->message, ZAPI_MESSAGE_NEXTHOP); }; #endif /* _ZEBRA_ZCLIENT_H */ frr-7.2.1/lib/zebra.h0000644000000000000000000002500013610377563011233 00000000000000/* Zebra common header. * Copyright (C) 1997, 1998, 1999, 2000, 2001, 2002 Kunihiro Ishiguro * * This file is part of GNU Zebra. * * GNU Zebra is free software; you can redistribute it and/or modify it * under the terms of the GNU General Public License as published by the * Free Software Foundation; either version 2, or (at your option) any * later version. * * GNU Zebra 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; see the file COPYING; if not, write to the Free Software * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA */ #ifndef _ZEBRA_H #define _ZEBRA_H #ifdef HAVE_CONFIG_H #include "config.h" #endif /* HAVE_CONFIG_H */ #include "compiler.h" #ifdef SUNOS_5 typedef unsigned int uint32_t; typedef unsigned short uint16_t; typedef unsigned char uint8_t; #endif /* SUNOS_5 */ #include #include #include #include #include #include #include #include #include #include #include #ifdef HAVE_STROPTS_H #include #endif /* HAVE_STROPTS_H */ #include #include #include #include #ifdef HAVE_SYS_SYSCTL_H #ifndef GNU_LINUX #include #endif #endif /* HAVE_SYS_SYSCTL_H */ #include #ifdef HAVE_SYS_CONF_H #include #endif /* HAVE_SYS_CONF_H */ #ifdef HAVE_SYS_KSYM_H #include #endif /* HAVE_SYS_KSYM_H */ #include #include #include #include #include #include #include #include #include /* machine dependent includes */ #ifdef SUNOS_5 #include #endif /* SUNOS_5 */ /* machine dependent includes */ #ifdef HAVE_LINUX_VERSION_H #include #endif /* HAVE_LINUX_VERSION_H */ #ifdef HAVE_ASM_TYPES_H #include #endif /* HAVE_ASM_TYPES_H */ /* misc include group */ #include #if !(defined(__STDC_VERSION__) && __STDC_VERSION__ >= 199901L) /* Not C99; do we need to define va_copy? */ #ifndef va_copy #ifdef __va_copy #define va_copy(DST,SRC) __va_copy(DST,SRC) #else /* Now we are desperate; this should work on many typical platforms. But this is slightly dangerous, because the standard does not require va_copy to be a macro. */ #define va_copy(DST,SRC) memcpy(&(DST), &(SRC), sizeof(va_list)) #warning "Not C99 and no va_copy macro available, falling back to memcpy" #endif /* __va_copy */ #endif /* !va_copy */ #endif /* !C99 */ #ifdef HAVE_LCAPS #include #include #endif /* HAVE_LCAPS */ #ifdef HAVE_SOLARIS_CAPABILITIES #include #endif /* HAVE_SOLARIS_CAPABILITIES */ /* network include group */ #include #ifdef HAVE_SYS_SOCKIO_H #include #endif /* HAVE_SYS_SOCKIO_H */ #ifdef __APPLE__ #define __APPLE_USE_RFC_3542 #endif #ifndef HAVE_LIBCRYPT #ifdef HAVE_LIBCRYPTO #include # define crypt DES_crypt #endif #endif #ifdef CRYPTO_OPENSSL #include #include #endif #include "openbsd-tree.h" #include #include #include #include #ifdef HAVE_NET_NETOPT_H #include #endif /* HAVE_NET_NETOPT_H */ #include #ifdef HAVE_NET_IF_DL_H #include #endif /* HAVE_NET_IF_DL_H */ #ifdef HAVE_NET_IF_VAR_H #include #endif /* HAVE_NET_IF_VAR_H */ #include #ifdef HAVE_NETLINK #include #include #include #else #define RT_TABLE_MAIN 0 #endif /* HAVE_NETLINK */ #include #include #ifdef HAVE_INET_ND_H #include #endif /* HAVE_INET_ND_H */ #ifdef HAVE_NETINET_IN_VAR_H #include #endif /* HAVE_NETINET_IN_VAR_H */ #ifdef HAVE_NETINET6_IN6_VAR_H #include #endif /* HAVE_NETINET6_IN6_VAR_H */ #ifdef HAVE_NETINET_IN6_VAR_H #include #endif /* HAVE_NETINET_IN6_VAR_H */ #ifdef HAVE_NETINET6_IN_H #include #endif /* HAVE_NETINET6_IN_H */ #ifdef HAVE_NETINET6_IP6_H #include #endif /* HAVE_NETINET6_IP6_H */ #include #ifdef HAVE_NETINET6_ND6_H #include #endif /* HAVE_NETINET6_ND6_H */ /* Some systems do not define UINT32_MAX, etc.. from inttypes.h * e.g. this makes life easier for FBSD 4.11 users. */ #ifndef INT16_MAX #define INT16_MAX (32767) #endif #ifndef INT32_MAX #define INT32_MAX (2147483647) #endif #ifndef UINT16_MAX #define UINT16_MAX (65535U) #endif #ifndef UINT32_MAX #define UINT32_MAX (4294967295U) #endif #ifdef HAVE_GLIBC_BACKTRACE #include #endif /* HAVE_GLIBC_BACKTRACE */ /* Local includes: */ #if !(defined(__GNUC__) || defined(VTYSH_EXTRACT_PL)) #define __attribute__(x) #endif /* !__GNUC__ || VTYSH_EXTRACT_PL */ #include "zassert.h" /* * Add explicit static cast only when using a C++ compiler. */ #ifdef __cplusplus #define static_cast(l, r) static_cast((r)) #else #define static_cast(l, r) (r) #endif #ifndef HAVE_STRLCAT size_t strlcat(char *__restrict dest, const char *__restrict src, size_t destsize); #endif #ifndef HAVE_STRLCPY size_t strlcpy(char *__restrict dest, const char *__restrict src, size_t destsize); #endif /* * RFC 3542 defines several macros for using struct cmsghdr. * Here, we define those that are not present */ /* * Internal defines, for use only in this file. * These are likely wrong on other than ILP32 machines, so warn. */ #ifndef _CMSG_DATA_ALIGN #define _CMSG_DATA_ALIGN(n) (((n) + 3) & ~3) #endif /* _CMSG_DATA_ALIGN */ #ifndef _CMSG_HDR_ALIGN #define _CMSG_HDR_ALIGN(n) (((n) + 3) & ~3) #endif /* _CMSG_HDR_ALIGN */ /* * CMSG_SPACE and CMSG_LEN are required in RFC3542, but were new in that * version. */ #ifndef CMSG_SPACE #define CMSG_SPACE(l) \ (_CMSG_DATA_ALIGN(sizeof(struct cmsghdr)) + _CMSG_HDR_ALIGN(l)) #warning "assuming 4-byte alignment for CMSG_SPACE" #endif /* CMSG_SPACE */ #ifndef CMSG_LEN #define CMSG_LEN(l) (_CMSG_DATA_ALIGN(sizeof(struct cmsghdr)) + (l)) #warning "assuming 4-byte alignment for CMSG_LEN" #endif /* CMSG_LEN */ /* The definition of struct in_pktinfo is missing in old version of GLIBC 2.1 (Redhat 6.1). */ #if defined(GNU_LINUX) && !defined(HAVE_STRUCT_IN_PKTINFO) struct in_pktinfo { int ipi_ifindex; struct in_addr ipi_spec_dst; struct in_addr ipi_addr; }; #endif /* * IP_HDRINCL / struct ip byte order * * Linux: network byte order * *BSD: network, except for length and offset. (cf Stevens) * SunOS: nominally as per BSD. but bug: network order on LE. * OpenBSD: network byte order, apart from older versions which are as per * *BSD */ #if defined(__NetBSD__) \ || (defined(__FreeBSD__) && (__FreeBSD_version < 1100030)) \ || (defined(__OpenBSD__) && (OpenBSD < 200311)) \ || (defined(__APPLE__)) \ || (defined(SUNOS_5) && defined(WORDS_BIGENDIAN)) #define HAVE_IP_HDRINCL_BSD_ORDER #endif /* Define BYTE_ORDER, if not defined. Useful for compiler conditional * code, rather than preprocessor conditional. * Not all the world has this BSD define. */ #ifndef BYTE_ORDER #define BIG_ENDIAN 4321 /* least-significant byte first (vax, pc) */ #define LITTLE_ENDIAN 1234 /* most-significant byte first (IBM, net) */ #define PDP_ENDIAN 3412 /* LSB first in word, MSW first in long (pdp) */ #if defined(WORDS_BIGENDIAN) #define BYTE_ORDER BIG_ENDIAN #else /* !WORDS_BIGENDIAN */ #define BYTE_ORDER LITTLE_ENDIAN #endif /* WORDS_BIGENDIAN */ #endif /* ndef BYTE_ORDER */ /* For old definition. */ #ifndef IN6_ARE_ADDR_EQUAL #define IN6_ARE_ADDR_EQUAL IN6_IS_ADDR_EQUAL #endif /* IN6_ARE_ADDR_EQUAL */ /* default zebra TCP port for zclient */ #define ZEBRA_PORT 2600 /* * The compiler.h header is used for anyone using the CPP_NOTICE * since this is universally needed, let's add it to zebra.h */ #include "compiler.h" /* Zebra route's types are defined in route_types.h */ #include "route_types.h" #define strmatch(a,b) (!strcmp((a), (b))) #ifndef INADDR_LOOPBACK #define INADDR_LOOPBACK 0x7f000001 /* Internet address 127.0.0.1. */ #endif /* Address family numbers from RFC1700. */ typedef enum { AFI_UNSPEC = 0, AFI_IP = 1, AFI_IP6 = 2, AFI_L2VPN = 3, AFI_MAX = 4 } afi_t; /* Subsequent Address Family Identifier. */ typedef enum { SAFI_UNSPEC = 0, SAFI_UNICAST = 1, SAFI_MULTICAST = 2, SAFI_MPLS_VPN = 3, SAFI_ENCAP = 4, SAFI_EVPN = 5, SAFI_LABELED_UNICAST = 6, SAFI_FLOWSPEC = 7, SAFI_MAX = 8 } safi_t; /* Default Administrative Distance of each protocol. */ #define ZEBRA_KERNEL_DISTANCE_DEFAULT 0 #define ZEBRA_CONNECT_DISTANCE_DEFAULT 0 #define ZEBRA_STATIC_DISTANCE_DEFAULT 1 #define ZEBRA_RIP_DISTANCE_DEFAULT 120 #define ZEBRA_RIPNG_DISTANCE_DEFAULT 120 #define ZEBRA_OSPF_DISTANCE_DEFAULT 110 #define ZEBRA_OSPF6_DISTANCE_DEFAULT 110 #define ZEBRA_ISIS_DISTANCE_DEFAULT 115 #define ZEBRA_IBGP_DISTANCE_DEFAULT 200 #define ZEBRA_EBGP_DISTANCE_DEFAULT 20 #define ZEBRA_TABLE_DISTANCE_DEFAULT 15 /* Flag manipulation macros. */ #define CHECK_FLAG(V,F) ((V) & (F)) #define SET_FLAG(V,F) (V) |= (F) #define UNSET_FLAG(V,F) (V) &= ~(F) #define RESET_FLAG(V) (V) = 0 #define COND_FLAG(V, F, C) ((C) ? (SET_FLAG(V, F)) : (UNSET_FLAG(V, F))) /* Atomic flag manipulation macros. */ #define CHECK_FLAG_ATOMIC(PV, F) \ ((atomic_load_explicit(PV, memory_order_seq_cst)) & (F)) #define SET_FLAG_ATOMIC(PV, F) \ ((atomic_fetch_or_explicit(PV, (F), memory_order_seq_cst))) #define UNSET_FLAG_ATOMIC(PV, F) \ ((atomic_fetch_and_explicit(PV, ~(F), memory_order_seq_cst))) #define RESET_FLAG_ATOMIC(PV) \ ((atomic_store_explicit(PV, 0, memory_order_seq_cst))) /* VRF ID type. */ typedef uint32_t vrf_id_t; typedef uint32_t route_tag_t; #define ROUTE_TAG_MAX UINT32_MAX #define ROUTE_TAG_PRI PRIu32 #endif /* _ZEBRA_H */ frr-7.2.1/ltmain.sh0000644000000000000000000117671413610377563011055 00000000000000#! /bin/sh ## DO NOT EDIT - This file generated from ./build-aux/ltmain.in ## by inline-source v2014-01-03.01 # libtool (GNU libtool) 2.4.6 # Provide generalized library-building support services. # Written by Gordon Matzigkeit , 1996 # Copyright (C) 1996-2015 Free Software Foundation, Inc. # This is free software; see the source for copying conditions. There is NO # warranty; not even for MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. # GNU Libtool is free software; you can redistribute it and/or modify # it under the terms of the GNU General Public License as published by # the Free Software Foundation; either version 2 of the License, or # (at your option) any later version. # # As a special exception to the GNU General Public License, # if you distribute this file as part of a program or library that # is built using GNU Libtool, you may include this file under the # same distribution terms that you use for the rest of that program. # # GNU Libtool is distributed in the hope that it will be useful, but # WITHOUT ANY WARRANTY; without even the implied warranty of # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU # General Public License for more details. # # You should have received a copy of the GNU General Public License # along with this program. If not, see . PROGRAM=libtool PACKAGE=libtool VERSION="2.4.6 Debian-2.4.6-11" package_revision=2.4.6 ## ------ ## ## Usage. ## ## ------ ## # Run './libtool --help' for help with using this script from the # command line. ## ------------------------------- ## ## User overridable command paths. ## ## ------------------------------- ## # After configure completes, it has a better idea of some of the # shell tools we need than the defaults used by the functions shared # with bootstrap, so set those here where they can still be over- # ridden by the user, but otherwise take precedence. : ${AUTOCONF="autoconf"} : ${AUTOMAKE="automake"} ## -------------------------- ## ## Source external libraries. ## ## -------------------------- ## # Much of our low-level functionality needs to be sourced from external # libraries, which are installed to $pkgauxdir. # Set a version string for this script. scriptversion=2015-01-20.17; # UTC # General shell script boiler plate, and helper functions. # Written by Gary V. Vaughan, 2004 # Copyright (C) 2004-2015 Free Software Foundation, Inc. # This is free software; see the source for copying conditions. There is NO # warranty; not even for MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. # This program is free software; you can redistribute it and/or modify # it under the terms of the GNU General Public License as published by # the Free Software Foundation; either version 3 of the License, or # (at your option) any later version. # As a special exception to the GNU General Public License, if you distribute # this file as part of a program or library that is built using GNU Libtool, # you may include this file under the same distribution terms that you use # for the rest of that program. # This program is distributed in the hope that it will be useful, # but WITHOUT ANY WARRANTY; without even the implied warranty of # MERCHANTABILITY or FITNES FOR A PARTICULAR PURPOSE. See the GNU # General Public License for more details. # You should have received a copy of the GNU General Public License # along with this program. If not, see . # Please report bugs or propose patches to gary@gnu.org. ## ------ ## ## Usage. ## ## ------ ## # Evaluate this file near the top of your script to gain access to # the functions and variables defined here: # # . `echo "$0" | ${SED-sed} 's|[^/]*$||'`/build-aux/funclib.sh # # If you need to override any of the default environment variable # settings, do that before evaluating this file. ## -------------------- ## ## Shell normalisation. ## ## -------------------- ## # Some shells need a little help to be as Bourne compatible as possible. # Before doing anything else, make sure all that help has been provided! DUALCASE=1; export DUALCASE # for MKS sh if test -n "${ZSH_VERSION+set}" && (emulate sh) >/dev/null 2>&1; then : emulate sh NULLCMD=: # Pre-4.2 versions of Zsh do word splitting on ${1+"$@"}, which # is contrary to our usage. Disable this feature. alias -g '${1+"$@"}'='"$@"' setopt NO_GLOB_SUBST else case `(set -o) 2>/dev/null` in *posix*) set -o posix ;; esac fi # NLS nuisances: We save the old values in case they are required later. _G_user_locale= _G_safe_locale= for _G_var in LANG LANGUAGE LC_ALL LC_CTYPE LC_COLLATE LC_MESSAGES do eval "if test set = \"\${$_G_var+set}\"; then save_$_G_var=\$$_G_var $_G_var=C export $_G_var _G_user_locale=\"$_G_var=\\\$save_\$_G_var; \$_G_user_locale\" _G_safe_locale=\"$_G_var=C; \$_G_safe_locale\" fi" done # CDPATH. (unset CDPATH) >/dev/null 2>&1 && unset CDPATH # Make sure IFS has a sensible default sp=' ' nl=' ' IFS="$sp $nl" # There are apparently some retarded systems that use ';' as a PATH separator! if test "${PATH_SEPARATOR+set}" != set; then PATH_SEPARATOR=: (PATH='/bin;/bin'; FPATH=$PATH; sh -c :) >/dev/null 2>&1 && { (PATH='/bin:/bin'; FPATH=$PATH; sh -c :) >/dev/null 2>&1 || PATH_SEPARATOR=';' } fi ## ------------------------- ## ## Locate command utilities. ## ## ------------------------- ## # func_executable_p FILE # ---------------------- # Check that FILE is an executable regular file. func_executable_p () { test -f "$1" && test -x "$1" } # func_path_progs PROGS_LIST CHECK_FUNC [PATH] # -------------------------------------------- # Search for either a program that responds to --version with output # containing "GNU", or else returned by CHECK_FUNC otherwise, by # trying all the directories in PATH with each of the elements of # PROGS_LIST. # # CHECK_FUNC should accept the path to a candidate program, and # set $func_check_prog_result if it truncates its output less than # $_G_path_prog_max characters. func_path_progs () { _G_progs_list=$1 _G_check_func=$2 _G_PATH=${3-"$PATH"} _G_path_prog_max=0 _G_path_prog_found=false _G_save_IFS=$IFS; IFS=${PATH_SEPARATOR-:} for _G_dir in $_G_PATH; do IFS=$_G_save_IFS test -z "$_G_dir" && _G_dir=. for _G_prog_name in $_G_progs_list; do for _exeext in '' .EXE; do _G_path_prog=$_G_dir/$_G_prog_name$_exeext func_executable_p "$_G_path_prog" || continue case `"$_G_path_prog" --version 2>&1` in *GNU*) func_path_progs_result=$_G_path_prog _G_path_prog_found=: ;; *) $_G_check_func $_G_path_prog func_path_progs_result=$func_check_prog_result ;; esac $_G_path_prog_found && break 3 done done done IFS=$_G_save_IFS test -z "$func_path_progs_result" && { echo "no acceptable sed could be found in \$PATH" >&2 exit 1 } } # We want to be able to use the functions in this file before configure # has figured out where the best binaries are kept, which means we have # to search for them ourselves - except when the results are already set # where we skip the searches. # Unless the user overrides by setting SED, search the path for either GNU # sed, or the sed that truncates its output the least. test -z "$SED" && { _G_sed_script=s/aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa/bbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbb/ for _G_i in 1 2 3 4 5 6 7; do _G_sed_script=$_G_sed_script$nl$_G_sed_script done echo "$_G_sed_script" 2>/dev/null | sed 99q >conftest.sed _G_sed_script= func_check_prog_sed () { _G_path_prog=$1 _G_count=0 printf 0123456789 >conftest.in while : do cat conftest.in conftest.in >conftest.tmp mv conftest.tmp conftest.in cp conftest.in conftest.nl echo '' >> conftest.nl "$_G_path_prog" -f conftest.sed conftest.out 2>/dev/null || break diff conftest.out conftest.nl >/dev/null 2>&1 || break _G_count=`expr $_G_count + 1` if test "$_G_count" -gt "$_G_path_prog_max"; then # Best one so far, save it but keep looking for a better one func_check_prog_result=$_G_path_prog _G_path_prog_max=$_G_count fi # 10*(2^10) chars as input seems more than enough test 10 -lt "$_G_count" && break done rm -f conftest.in conftest.tmp conftest.nl conftest.out } func_path_progs "sed gsed" func_check_prog_sed $PATH:/usr/xpg4/bin rm -f conftest.sed SED=$func_path_progs_result } # Unless the user overrides by setting GREP, search the path for either GNU # grep, or the grep that truncates its output the least. test -z "$GREP" && { func_check_prog_grep () { _G_path_prog=$1 _G_count=0 _G_path_prog_max=0 printf 0123456789 >conftest.in while : do cat conftest.in conftest.in >conftest.tmp mv conftest.tmp conftest.in cp conftest.in conftest.nl echo 'GREP' >> conftest.nl "$_G_path_prog" -e 'GREP$' -e '-(cannot match)-' conftest.out 2>/dev/null || break diff conftest.out conftest.nl >/dev/null 2>&1 || break _G_count=`expr $_G_count + 1` if test "$_G_count" -gt "$_G_path_prog_max"; then # Best one so far, save it but keep looking for a better one func_check_prog_result=$_G_path_prog _G_path_prog_max=$_G_count fi # 10*(2^10) chars as input seems more than enough test 10 -lt "$_G_count" && break done rm -f conftest.in conftest.tmp conftest.nl conftest.out } func_path_progs "grep ggrep" func_check_prog_grep $PATH:/usr/xpg4/bin GREP=$func_path_progs_result } ## ------------------------------- ## ## User overridable command paths. ## ## ------------------------------- ## # All uppercase variable names are used for environment variables. These # variables can be overridden by the user before calling a script that # uses them if a suitable command of that name is not already available # in the command search PATH. : ${CP="cp -f"} : ${ECHO="printf %s\n"} : ${EGREP="$GREP -E"} : ${FGREP="$GREP -F"} : ${LN_S="ln -s"} : ${MAKE="make"} : ${MKDIR="mkdir"} : ${MV="mv -f"} : ${RM="rm -f"} : ${SHELL="${CONFIG_SHELL-/bin/sh}"} ## -------------------- ## ## Useful sed snippets. ## ## -------------------- ## sed_dirname='s|/[^/]*$||' sed_basename='s|^.*/||' # Sed substitution that helps us do robust quoting. It backslashifies # metacharacters that are still active within double-quoted strings. sed_quote_subst='s|\([`"$\\]\)|\\\1|g' # Same as above, but do not quote variable references. sed_double_quote_subst='s/\(["`\\]\)/\\\1/g' # Sed substitution that turns a string into a regex matching for the # string literally. sed_make_literal_regex='s|[].[^$\\*\/]|\\&|g' # Sed substitution that converts a w32 file name or path # that contains forward slashes, into one that contains # (escaped) backslashes. A very naive implementation. sed_naive_backslashify='s|\\\\*|\\|g;s|/|\\|g;s|\\|\\\\|g' # Re-'\' parameter expansions in output of sed_double_quote_subst that # were '\'-ed in input to the same. If an odd number of '\' preceded a # '$' in input to sed_double_quote_subst, that '$' was protected from # expansion. Since each input '\' is now two '\'s, look for any number # of runs of four '\'s followed by two '\'s and then a '$'. '\' that '$'. _G_bs='\\' _G_bs2='\\\\' _G_bs4='\\\\\\\\' _G_dollar='\$' sed_double_backslash="\ s/$_G_bs4/&\\ /g s/^$_G_bs2$_G_dollar/$_G_bs&/ s/\\([^$_G_bs]\\)$_G_bs2$_G_dollar/\\1$_G_bs2$_G_bs$_G_dollar/g s/\n//g" ## ----------------- ## ## Global variables. ## ## ----------------- ## # Except for the global variables explicitly listed below, the following # functions in the '^func_' namespace, and the '^require_' namespace # variables initialised in the 'Resource management' section, sourcing # this file will not pollute your global namespace with anything # else. There's no portable way to scope variables in Bourne shell # though, so actually running these functions will sometimes place # results into a variable named after the function, and often use # temporary variables in the '^_G_' namespace. If you are careful to # avoid using those namespaces casually in your sourcing script, things # should continue to work as you expect. And, of course, you can freely # overwrite any of the functions or variables defined here before # calling anything to customize them. EXIT_SUCCESS=0 EXIT_FAILURE=1 EXIT_MISMATCH=63 # $? = 63 is used to indicate version mismatch to missing. EXIT_SKIP=77 # $? = 77 is used to indicate a skipped test to automake. # Allow overriding, eg assuming that you follow the convention of # putting '$debug_cmd' at the start of all your functions, you can get # bash to show function call trace with: # # debug_cmd='eval echo "${FUNCNAME[0]} $*" >&2' bash your-script-name debug_cmd=${debug_cmd-":"} exit_cmd=: # By convention, finish your script with: # # exit $exit_status # # so that you can set exit_status to non-zero if you want to indicate # something went wrong during execution without actually bailing out at # the point of failure. exit_status=$EXIT_SUCCESS # Work around backward compatibility issue on IRIX 6.5. On IRIX 6.4+, sh # is ksh but when the shell is invoked as "sh" and the current value of # the _XPG environment variable is not equal to 1 (one), the special # positional parameter $0, within a function call, is the name of the # function. progpath=$0 # The name of this program. progname=`$ECHO "$progpath" |$SED "$sed_basename"` # Make sure we have an absolute progpath for reexecution: case $progpath in [\\/]*|[A-Za-z]:\\*) ;; *[\\/]*) progdir=`$ECHO "$progpath" |$SED "$sed_dirname"` progdir=`cd "$progdir" && pwd` progpath=$progdir/$progname ;; *) _G_IFS=$IFS IFS=${PATH_SEPARATOR-:} for progdir in $PATH; do IFS=$_G_IFS test -x "$progdir/$progname" && break done IFS=$_G_IFS test -n "$progdir" || progdir=`pwd` progpath=$progdir/$progname ;; esac ## ----------------- ## ## Standard options. ## ## ----------------- ## # The following options affect the operation of the functions defined # below, and should be set appropriately depending on run-time para- # meters passed on the command line. opt_dry_run=false opt_quiet=false opt_verbose=false # Categories 'all' and 'none' are always available. Append any others # you will pass as the first argument to func_warning from your own # code. warning_categories= # By default, display warnings according to 'opt_warning_types'. Set # 'warning_func' to ':' to elide all warnings, or func_fatal_error to # treat the next displayed warning as a fatal error. warning_func=func_warn_and_continue # Set to 'all' to display all warnings, 'none' to suppress all # warnings, or a space delimited list of some subset of # 'warning_categories' to display only the listed warnings. opt_warning_types=all ## -------------------- ## ## Resource management. ## ## -------------------- ## # This section contains definitions for functions that each ensure a # particular resource (a file, or a non-empty configuration variable for # example) is available, and if appropriate to extract default values # from pertinent package files. Call them using their associated # 'require_*' variable to ensure that they are executed, at most, once. # # It's entirely deliberate that calling these functions can set # variables that don't obey the namespace limitations obeyed by the rest # of this file, in order that that they be as useful as possible to # callers. # require_term_colors # ------------------- # Allow display of bold text on terminals that support it. require_term_colors=func_require_term_colors func_require_term_colors () { $debug_cmd test -t 1 && { # COLORTERM and USE_ANSI_COLORS environment variables take # precedence, because most terminfo databases neglect to describe # whether color sequences are supported. test -n "${COLORTERM+set}" && : ${USE_ANSI_COLORS="1"} if test 1 = "$USE_ANSI_COLORS"; then # Standard ANSI escape sequences tc_reset='' tc_bold=''; tc_standout='' tc_red=''; tc_green='' tc_blue=''; tc_cyan='' else # Otherwise trust the terminfo database after all. test -n "`tput sgr0 2>/dev/null`" && { tc_reset=`tput sgr0` test -n "`tput bold 2>/dev/null`" && tc_bold=`tput bold` tc_standout=$tc_bold test -n "`tput smso 2>/dev/null`" && tc_standout=`tput smso` test -n "`tput setaf 1 2>/dev/null`" && tc_red=`tput setaf 1` test -n "`tput setaf 2 2>/dev/null`" && tc_green=`tput setaf 2` test -n "`tput setaf 4 2>/dev/null`" && tc_blue=`tput setaf 4` test -n "`tput setaf 5 2>/dev/null`" && tc_cyan=`tput setaf 5` } fi } require_term_colors=: } ## ----------------- ## ## Function library. ## ## ----------------- ## # This section contains a variety of useful functions to call in your # scripts. Take note of the portable wrappers for features provided by # some modern shells, which will fall back to slower equivalents on # less featureful shells. # func_append VAR VALUE # --------------------- # Append VALUE onto the existing contents of VAR. # We should try to minimise forks, especially on Windows where they are # unreasonably slow, so skip the feature probes when bash or zsh are # being used: if test set = "${BASH_VERSION+set}${ZSH_VERSION+set}"; then : ${_G_HAVE_ARITH_OP="yes"} : ${_G_HAVE_XSI_OPS="yes"} # The += operator was introduced in bash 3.1 case $BASH_VERSION in [12].* | 3.0 | 3.0*) ;; *) : ${_G_HAVE_PLUSEQ_OP="yes"} ;; esac fi # _G_HAVE_PLUSEQ_OP # Can be empty, in which case the shell is probed, "yes" if += is # useable or anything else if it does not work. test -z "$_G_HAVE_PLUSEQ_OP" \ && (eval 'x=a; x+=" b"; test "a b" = "$x"') 2>/dev/null \ && _G_HAVE_PLUSEQ_OP=yes if test yes = "$_G_HAVE_PLUSEQ_OP" then # This is an XSI compatible shell, allowing a faster implementation... eval 'func_append () { $debug_cmd eval "$1+=\$2" }' else # ...otherwise fall back to using expr, which is often a shell builtin. func_append () { $debug_cmd eval "$1=\$$1\$2" } fi # func_append_quoted VAR VALUE # ---------------------------- # Quote VALUE and append to the end of shell variable VAR, separated # by a space. if test yes = "$_G_HAVE_PLUSEQ_OP"; then eval 'func_append_quoted () { $debug_cmd func_quote_for_eval "$2" eval "$1+=\\ \$func_quote_for_eval_result" }' else func_append_quoted () { $debug_cmd func_quote_for_eval "$2" eval "$1=\$$1\\ \$func_quote_for_eval_result" } fi # func_append_uniq VAR VALUE # -------------------------- # Append unique VALUE onto the existing contents of VAR, assuming # entries are delimited by the first character of VALUE. For example: # # func_append_uniq options " --another-option option-argument" # # will only append to $options if " --another-option option-argument " # is not already present somewhere in $options already (note spaces at # each end implied by leading space in second argument). func_append_uniq () { $debug_cmd eval _G_current_value='`$ECHO $'$1'`' _G_delim=`expr "$2" : '\(.\)'` case $_G_delim$_G_current_value$_G_delim in *"$2$_G_delim"*) ;; *) func_append "$@" ;; esac } # func_arith TERM... # ------------------ # Set func_arith_result to the result of evaluating TERMs. test -z "$_G_HAVE_ARITH_OP" \ && (eval 'test 2 = $(( 1 + 1 ))') 2>/dev/null \ && _G_HAVE_ARITH_OP=yes if test yes = "$_G_HAVE_ARITH_OP"; then eval 'func_arith () { $debug_cmd func_arith_result=$(( $* )) }' else func_arith () { $debug_cmd func_arith_result=`expr "$@"` } fi # func_basename FILE # ------------------ # Set func_basename_result to FILE with everything up to and including # the last / stripped. if test yes = "$_G_HAVE_XSI_OPS"; then # If this shell supports suffix pattern removal, then use it to avoid # forking. Hide the definitions single quotes in case the shell chokes # on unsupported syntax... _b='func_basename_result=${1##*/}' _d='case $1 in */*) func_dirname_result=${1%/*}$2 ;; * ) func_dirname_result=$3 ;; esac' else # ...otherwise fall back to using sed. _b='func_basename_result=`$ECHO "$1" |$SED "$sed_basename"`' _d='func_dirname_result=`$ECHO "$1" |$SED "$sed_dirname"` if test "X$func_dirname_result" = "X$1"; then func_dirname_result=$3 else func_append func_dirname_result "$2" fi' fi eval 'func_basename () { $debug_cmd '"$_b"' }' # func_dirname FILE APPEND NONDIR_REPLACEMENT # ------------------------------------------- # Compute the dirname of FILE. If nonempty, add APPEND to the result, # otherwise set result to NONDIR_REPLACEMENT. eval 'func_dirname () { $debug_cmd '"$_d"' }' # func_dirname_and_basename FILE APPEND NONDIR_REPLACEMENT # -------------------------------------------------------- # Perform func_basename and func_dirname in a single function # call: # dirname: Compute the dirname of FILE. If nonempty, # add APPEND to the result, otherwise set result # to NONDIR_REPLACEMENT. # value returned in "$func_dirname_result" # basename: Compute filename of FILE. # value retuned in "$func_basename_result" # For efficiency, we do not delegate to the functions above but instead # duplicate the functionality here. eval 'func_dirname_and_basename () { $debug_cmd '"$_b"' '"$_d"' }' # func_echo ARG... # ---------------- # Echo program name prefixed message. func_echo () { $debug_cmd _G_message=$* func_echo_IFS=$IFS IFS=$nl for _G_line in $_G_message; do IFS=$func_echo_IFS $ECHO "$progname: $_G_line" done IFS=$func_echo_IFS } # func_echo_all ARG... # -------------------- # Invoke $ECHO with all args, space-separated. func_echo_all () { $ECHO "$*" } # func_echo_infix_1 INFIX ARG... # ------------------------------ # Echo program name, followed by INFIX on the first line, with any # additional lines not showing INFIX. func_echo_infix_1 () { $debug_cmd $require_term_colors _G_infix=$1; shift _G_indent=$_G_infix _G_prefix="$progname: $_G_infix: " _G_message=$* # Strip color escape sequences before counting printable length for _G_tc in "$tc_reset" "$tc_bold" "$tc_standout" "$tc_red" "$tc_green" "$tc_blue" "$tc_cyan" do test -n "$_G_tc" && { _G_esc_tc=`$ECHO "$_G_tc" | $SED "$sed_make_literal_regex"` _G_indent=`$ECHO "$_G_indent" | $SED "s|$_G_esc_tc||g"` } done _G_indent="$progname: "`echo "$_G_indent" | $SED 's|.| |g'`" " ## exclude from sc_prohibit_nested_quotes func_echo_infix_1_IFS=$IFS IFS=$nl for _G_line in $_G_message; do IFS=$func_echo_infix_1_IFS $ECHO "$_G_prefix$tc_bold$_G_line$tc_reset" >&2 _G_prefix=$_G_indent done IFS=$func_echo_infix_1_IFS } # func_error ARG... # ----------------- # Echo program name prefixed message to standard error. func_error () { $debug_cmd $require_term_colors func_echo_infix_1 " $tc_standout${tc_red}error$tc_reset" "$*" >&2 } # func_fatal_error ARG... # ----------------------- # Echo program name prefixed message to standard error, and exit. func_fatal_error () { $debug_cmd func_error "$*" exit $EXIT_FAILURE } # func_grep EXPRESSION FILENAME # ----------------------------- # Check whether EXPRESSION matches any line of FILENAME, without output. func_grep () { $debug_cmd $GREP "$1" "$2" >/dev/null 2>&1 } # func_len STRING # --------------- # Set func_len_result to the length of STRING. STRING may not # start with a hyphen. test -z "$_G_HAVE_XSI_OPS" \ && (eval 'x=a/b/c; test 5aa/bb/cc = "${#x}${x%%/*}${x%/*}${x#*/}${x##*/}"') 2>/dev/null \ && _G_HAVE_XSI_OPS=yes if test yes = "$_G_HAVE_XSI_OPS"; then eval 'func_len () { $debug_cmd func_len_result=${#1} }' else func_len () { $debug_cmd func_len_result=`expr "$1" : ".*" 2>/dev/null || echo $max_cmd_len` } fi # func_mkdir_p DIRECTORY-PATH # --------------------------- # Make sure the entire path to DIRECTORY-PATH is available. func_mkdir_p () { $debug_cmd _G_directory_path=$1 _G_dir_list= if test -n "$_G_directory_path" && test : != "$opt_dry_run"; then # Protect directory names starting with '-' case $_G_directory_path in -*) _G_directory_path=./$_G_directory_path ;; esac # While some portion of DIR does not yet exist... while test ! -d "$_G_directory_path"; do # ...make a list in topmost first order. Use a colon delimited # list incase some portion of path contains whitespace. _G_dir_list=$_G_directory_path:$_G_dir_list # If the last portion added has no slash in it, the list is done case $_G_directory_path in */*) ;; *) break ;; esac # ...otherwise throw away the child directory and loop _G_directory_path=`$ECHO "$_G_directory_path" | $SED -e "$sed_dirname"` done _G_dir_list=`$ECHO "$_G_dir_list" | $SED 's|:*$||'` func_mkdir_p_IFS=$IFS; IFS=: for _G_dir in $_G_dir_list; do IFS=$func_mkdir_p_IFS # mkdir can fail with a 'File exist' error if two processes # try to create one of the directories concurrently. Don't # stop in that case! $MKDIR "$_G_dir" 2>/dev/null || : done IFS=$func_mkdir_p_IFS # Bail out if we (or some other process) failed to create a directory. test -d "$_G_directory_path" || \ func_fatal_error "Failed to create '$1'" fi } # func_mktempdir [BASENAME] # ------------------------- # Make a temporary directory that won't clash with other running # libtool processes, and avoids race conditions if possible. If # given, BASENAME is the basename for that directory. func_mktempdir () { $debug_cmd _G_template=${TMPDIR-/tmp}/${1-$progname} if test : = "$opt_dry_run"; then # Return a directory name, but don't create it in dry-run mode _G_tmpdir=$_G_template-$$ else # If mktemp works, use that first and foremost _G_tmpdir=`mktemp -d "$_G_template-XXXXXXXX" 2>/dev/null` if test ! -d "$_G_tmpdir"; then # Failing that, at least try and use $RANDOM to avoid a race _G_tmpdir=$_G_template-${RANDOM-0}$$ func_mktempdir_umask=`umask` umask 0077 $MKDIR "$_G_tmpdir" umask $func_mktempdir_umask fi # If we're not in dry-run mode, bomb out on failure test -d "$_G_tmpdir" || \ func_fatal_error "cannot create temporary directory '$_G_tmpdir'" fi $ECHO "$_G_tmpdir" } # func_normal_abspath PATH # ------------------------ # Remove doubled-up and trailing slashes, "." path components, # and cancel out any ".." path components in PATH after making # it an absolute path. func_normal_abspath () { $debug_cmd # These SED scripts presuppose an absolute path with a trailing slash. _G_pathcar='s|^/\([^/]*\).*$|\1|' _G_pathcdr='s|^/[^/]*||' _G_removedotparts=':dotsl s|/\./|/|g t dotsl s|/\.$|/|' _G_collapseslashes='s|/\{1,\}|/|g' _G_finalslash='s|/*$|/|' # Start from root dir and reassemble the path. func_normal_abspath_result= func_normal_abspath_tpath=$1 func_normal_abspath_altnamespace= case $func_normal_abspath_tpath in "") # Empty path, that just means $cwd. func_stripname '' '/' "`pwd`" func_normal_abspath_result=$func_stripname_result return ;; # The next three entries are used to spot a run of precisely # two leading slashes without using negated character classes; # we take advantage of case's first-match behaviour. ///*) # Unusual form of absolute path, do nothing. ;; //*) # Not necessarily an ordinary path; POSIX reserves leading '//' # and for example Cygwin uses it to access remote file shares # over CIFS/SMB, so we conserve a leading double slash if found. func_normal_abspath_altnamespace=/ ;; /*) # Absolute path, do nothing. ;; *) # Relative path, prepend $cwd. func_normal_abspath_tpath=`pwd`/$func_normal_abspath_tpath ;; esac # Cancel out all the simple stuff to save iterations. We also want # the path to end with a slash for ease of parsing, so make sure # there is one (and only one) here. func_normal_abspath_tpath=`$ECHO "$func_normal_abspath_tpath" | $SED \ -e "$_G_removedotparts" -e "$_G_collapseslashes" -e "$_G_finalslash"` while :; do # Processed it all yet? if test / = "$func_normal_abspath_tpath"; then # If we ascended to the root using ".." the result may be empty now. if test -z "$func_normal_abspath_result"; then func_normal_abspath_result=/ fi break fi func_normal_abspath_tcomponent=`$ECHO "$func_normal_abspath_tpath" | $SED \ -e "$_G_pathcar"` func_normal_abspath_tpath=`$ECHO "$func_normal_abspath_tpath" | $SED \ -e "$_G_pathcdr"` # Figure out what to do with it case $func_normal_abspath_tcomponent in "") # Trailing empty path component, ignore it. ;; ..) # Parent dir; strip last assembled component from result. func_dirname "$func_normal_abspath_result" func_normal_abspath_result=$func_dirname_result ;; *) # Actual path component, append it. func_append func_normal_abspath_result "/$func_normal_abspath_tcomponent" ;; esac done # Restore leading double-slash if one was found on entry. func_normal_abspath_result=$func_normal_abspath_altnamespace$func_normal_abspath_result } # func_notquiet ARG... # -------------------- # Echo program name prefixed message only when not in quiet mode. func_notquiet () { $debug_cmd $opt_quiet || func_echo ${1+"$@"} # A bug in bash halts the script if the last line of a function # fails when set -e is in force, so we need another command to # work around that: : } # func_relative_path SRCDIR DSTDIR # -------------------------------- # Set func_relative_path_result to the relative path from SRCDIR to DSTDIR. func_relative_path () { $debug_cmd func_relative_path_result= func_normal_abspath "$1" func_relative_path_tlibdir=$func_normal_abspath_result func_normal_abspath "$2" func_relative_path_tbindir=$func_normal_abspath_result # Ascend the tree starting from libdir while :; do # check if we have found a prefix of bindir case $func_relative_path_tbindir in $func_relative_path_tlibdir) # found an exact match func_relative_path_tcancelled= break ;; $func_relative_path_tlibdir*) # found a matching prefix func_stripname "$func_relative_path_tlibdir" '' "$func_relative_path_tbindir" func_relative_path_tcancelled=$func_stripname_result if test -z "$func_relative_path_result"; then func_relative_path_result=. fi break ;; *) func_dirname $func_relative_path_tlibdir func_relative_path_tlibdir=$func_dirname_result if test -z "$func_relative_path_tlibdir"; then # Have to descend all the way to the root! func_relative_path_result=../$func_relative_path_result func_relative_path_tcancelled=$func_relative_path_tbindir break fi func_relative_path_result=../$func_relative_path_result ;; esac done # Now calculate path; take care to avoid doubling-up slashes. func_stripname '' '/' "$func_relative_path_result" func_relative_path_result=$func_stripname_result func_stripname '/' '/' "$func_relative_path_tcancelled" if test -n "$func_stripname_result"; then func_append func_relative_path_result "/$func_stripname_result" fi # Normalisation. If bindir is libdir, return '.' else relative path. if test -n "$func_relative_path_result"; then func_stripname './' '' "$func_relative_path_result" func_relative_path_result=$func_stripname_result fi test -n "$func_relative_path_result" || func_relative_path_result=. : } # func_quote_for_eval ARG... # -------------------------- # Aesthetically quote ARGs to be evaled later. # This function returns two values: # i) func_quote_for_eval_result # double-quoted, suitable for a subsequent eval # ii) func_quote_for_eval_unquoted_result # has all characters that are still active within double # quotes backslashified. func_quote_for_eval () { $debug_cmd func_quote_for_eval_unquoted_result= func_quote_for_eval_result= while test 0 -lt $#; do case $1 in *[\\\`\"\$]*) _G_unquoted_arg=`printf '%s\n' "$1" |$SED "$sed_quote_subst"` ;; *) _G_unquoted_arg=$1 ;; esac if test -n "$func_quote_for_eval_unquoted_result"; then func_append func_quote_for_eval_unquoted_result " $_G_unquoted_arg" else func_append func_quote_for_eval_unquoted_result "$_G_unquoted_arg" fi case $_G_unquoted_arg in # Double-quote args containing shell metacharacters to delay # word splitting, command substitution and variable expansion # for a subsequent eval. # Many Bourne shells cannot handle close brackets correctly # in scan sets, so we specify it separately. *[\[\~\#\^\&\*\(\)\{\}\|\;\<\>\?\'\ \ ]*|*]*|"") _G_quoted_arg=\"$_G_unquoted_arg\" ;; *) _G_quoted_arg=$_G_unquoted_arg ;; esac if test -n "$func_quote_for_eval_result"; then func_append func_quote_for_eval_result " $_G_quoted_arg" else func_append func_quote_for_eval_result "$_G_quoted_arg" fi shift done } # func_quote_for_expand ARG # ------------------------- # Aesthetically quote ARG to be evaled later; same as above, # but do not quote variable references. func_quote_for_expand () { $debug_cmd case $1 in *[\\\`\"]*) _G_arg=`$ECHO "$1" | $SED \ -e "$sed_double_quote_subst" -e "$sed_double_backslash"` ;; *) _G_arg=$1 ;; esac case $_G_arg in # Double-quote args containing shell metacharacters to delay # word splitting and command substitution for a subsequent eval. # Many Bourne shells cannot handle close brackets correctly # in scan sets, so we specify it separately. *[\[\~\#\^\&\*\(\)\{\}\|\;\<\>\?\'\ \ ]*|*]*|"") _G_arg=\"$_G_arg\" ;; esac func_quote_for_expand_result=$_G_arg } # func_stripname PREFIX SUFFIX NAME # --------------------------------- # strip PREFIX and SUFFIX from NAME, and store in func_stripname_result. # PREFIX and SUFFIX must not contain globbing or regex special # characters, hashes, percent signs, but SUFFIX may contain a leading # dot (in which case that matches only a dot). if test yes = "$_G_HAVE_XSI_OPS"; then eval 'func_stripname () { $debug_cmd # pdksh 5.2.14 does not do ${X%$Y} correctly if both X and Y are # positional parameters, so assign one to ordinary variable first. func_stripname_result=$3 func_stripname_result=${func_stripname_result#"$1"} func_stripname_result=${func_stripname_result%"$2"} }' else func_stripname () { $debug_cmd case $2 in .*) func_stripname_result=`$ECHO "$3" | $SED -e "s%^$1%%" -e "s%\\\\$2\$%%"`;; *) func_stripname_result=`$ECHO "$3" | $SED -e "s%^$1%%" -e "s%$2\$%%"`;; esac } fi # func_show_eval CMD [FAIL_EXP] # ----------------------------- # Unless opt_quiet is true, then output CMD. Then, if opt_dryrun is # not true, evaluate CMD. If the evaluation of CMD fails, and FAIL_EXP # is given, then evaluate it. func_show_eval () { $debug_cmd _G_cmd=$1 _G_fail_exp=${2-':'} func_quote_for_expand "$_G_cmd" eval "func_notquiet $func_quote_for_expand_result" $opt_dry_run || { eval "$_G_cmd" _G_status=$? if test 0 -ne "$_G_status"; then eval "(exit $_G_status); $_G_fail_exp" fi } } # func_show_eval_locale CMD [FAIL_EXP] # ------------------------------------ # Unless opt_quiet is true, then output CMD. Then, if opt_dryrun is # not true, evaluate CMD. If the evaluation of CMD fails, and FAIL_EXP # is given, then evaluate it. Use the saved locale for evaluation. func_show_eval_locale () { $debug_cmd _G_cmd=$1 _G_fail_exp=${2-':'} $opt_quiet || { func_quote_for_expand "$_G_cmd" eval "func_echo $func_quote_for_expand_result" } $opt_dry_run || { eval "$_G_user_locale $_G_cmd" _G_status=$? eval "$_G_safe_locale" if test 0 -ne "$_G_status"; then eval "(exit $_G_status); $_G_fail_exp" fi } } # func_tr_sh # ---------- # Turn $1 into a string suitable for a shell variable name. # Result is stored in $func_tr_sh_result. All characters # not in the set a-zA-Z0-9_ are replaced with '_'. Further, # if $1 begins with a digit, a '_' is prepended as well. func_tr_sh () { $debug_cmd case $1 in [0-9]* | *[!a-zA-Z0-9_]*) func_tr_sh_result=`$ECHO "$1" | $SED -e 's/^\([0-9]\)/_\1/' -e 's/[^a-zA-Z0-9_]/_/g'` ;; * ) func_tr_sh_result=$1 ;; esac } # func_verbose ARG... # ------------------- # Echo program name prefixed message in verbose mode only. func_verbose () { $debug_cmd $opt_verbose && func_echo "$*" : } # func_warn_and_continue ARG... # ----------------------------- # Echo program name prefixed warning message to standard error. func_warn_and_continue () { $debug_cmd $require_term_colors func_echo_infix_1 "${tc_red}warning$tc_reset" "$*" >&2 } # func_warning CATEGORY ARG... # ---------------------------- # Echo program name prefixed warning message to standard error. Warning # messages can be filtered according to CATEGORY, where this function # elides messages where CATEGORY is not listed in the global variable # 'opt_warning_types'. func_warning () { $debug_cmd # CATEGORY must be in the warning_categories list! case " $warning_categories " in *" $1 "*) ;; *) func_internal_error "invalid warning category '$1'" ;; esac _G_category=$1 shift case " $opt_warning_types " in *" $_G_category "*) $warning_func ${1+"$@"} ;; esac } # func_sort_ver VER1 VER2 # ----------------------- # 'sort -V' is not generally available. # Note this deviates from the version comparison in automake # in that it treats 1.5 < 1.5.0, and treats 1.4.4a < 1.4-p3a # but this should suffice as we won't be specifying old # version formats or redundant trailing .0 in bootstrap.conf. # If we did want full compatibility then we should probably # use m4_version_compare from autoconf. func_sort_ver () { $debug_cmd printf '%s\n%s\n' "$1" "$2" \ | sort -t. -k 1,1n -k 2,2n -k 3,3n -k 4,4n -k 5,5n -k 6,6n -k 7,7n -k 8,8n -k 9,9n } # func_lt_ver PREV CURR # --------------------- # Return true if PREV and CURR are in the correct order according to # func_sort_ver, otherwise false. Use it like this: # # func_lt_ver "$prev_ver" "$proposed_ver" || func_fatal_error "..." func_lt_ver () { $debug_cmd test "x$1" = x`func_sort_ver "$1" "$2" | $SED 1q` } # Local variables: # mode: shell-script # sh-indentation: 2 # eval: (add-hook 'before-save-hook 'time-stamp) # time-stamp-pattern: "10/scriptversion=%:y-%02m-%02d.%02H; # UTC" # time-stamp-time-zone: "UTC" # End: #! /bin/sh # Set a version string for this script. scriptversion=2015-10-07.11; # UTC # A portable, pluggable option parser for Bourne shell. # Written by Gary V. Vaughan, 2010 # Copyright (C) 2010-2015 Free Software Foundation, Inc. # This is free software; see the source for copying conditions. There is NO # warranty; not even for MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. # This program is free software: you can redistribute it and/or modify # it under the terms of the GNU General Public License as published by # the Free Software Foundation, either version 3 of the License, or # (at your option) any later version. # This program is distributed in the hope that it will be useful, # but WITHOUT ANY WARRANTY; without even the implied warranty of # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the # GNU General Public License for more details. # You should have received a copy of the GNU General Public License # along with this program. If not, see . # Please report bugs or propose patches to gary@gnu.org. ## ------ ## ## Usage. ## ## ------ ## # This file is a library for parsing options in your shell scripts along # with assorted other useful supporting features that you can make use # of too. # # For the simplest scripts you might need only: # # #!/bin/sh # . relative/path/to/funclib.sh # . relative/path/to/options-parser # scriptversion=1.0 # func_options ${1+"$@"} # eval set dummy "$func_options_result"; shift # ...rest of your script... # # In order for the '--version' option to work, you will need to have a # suitably formatted comment like the one at the top of this file # starting with '# Written by ' and ending with '# warranty; '. # # For '-h' and '--help' to work, you will also need a one line # description of your script's purpose in a comment directly above the # '# Written by ' line, like the one at the top of this file. # # The default options also support '--debug', which will turn on shell # execution tracing (see the comment above debug_cmd below for another # use), and '--verbose' and the func_verbose function to allow your script # to display verbose messages only when your user has specified # '--verbose'. # # After sourcing this file, you can plug processing for additional # options by amending the variables from the 'Configuration' section # below, and following the instructions in the 'Option parsing' # section further down. ## -------------- ## ## Configuration. ## ## -------------- ## # You should override these variables in your script after sourcing this # file so that they reflect the customisations you have added to the # option parser. # The usage line for option parsing errors and the start of '-h' and # '--help' output messages. You can embed shell variables for delayed # expansion at the time the message is displayed, but you will need to # quote other shell meta-characters carefully to prevent them being # expanded when the contents are evaled. usage='$progpath [OPTION]...' # Short help message in response to '-h' and '--help'. Add to this or # override it after sourcing this library to reflect the full set of # options your script accepts. usage_message="\ --debug enable verbose shell tracing -W, --warnings=CATEGORY report the warnings falling in CATEGORY [all] -v, --verbose verbosely report processing --version print version information and exit -h, --help print short or long help message and exit " # Additional text appended to 'usage_message' in response to '--help'. long_help_message=" Warning categories include: 'all' show all warnings 'none' turn off all the warnings 'error' warnings are treated as fatal errors" # Help message printed before fatal option parsing errors. fatal_help="Try '\$progname --help' for more information." ## ------------------------- ## ## Hook function management. ## ## ------------------------- ## # This section contains functions for adding, removing, and running hooks # to the main code. A hook is just a named list of of function, that can # be run in order later on. # func_hookable FUNC_NAME # ----------------------- # Declare that FUNC_NAME will run hooks added with # 'func_add_hook FUNC_NAME ...'. func_hookable () { $debug_cmd func_append hookable_fns " $1" } # func_add_hook FUNC_NAME HOOK_FUNC # --------------------------------- # Request that FUNC_NAME call HOOK_FUNC before it returns. FUNC_NAME must # first have been declared "hookable" by a call to 'func_hookable'. func_add_hook () { $debug_cmd case " $hookable_fns " in *" $1 "*) ;; *) func_fatal_error "'$1' does not accept hook functions." ;; esac eval func_append ${1}_hooks '" $2"' } # func_remove_hook FUNC_NAME HOOK_FUNC # ------------------------------------ # Remove HOOK_FUNC from the list of functions called by FUNC_NAME. func_remove_hook () { $debug_cmd eval ${1}_hooks='`$ECHO "\$'$1'_hooks" |$SED "s| '$2'||"`' } # func_run_hooks FUNC_NAME [ARG]... # --------------------------------- # Run all hook functions registered to FUNC_NAME. # It is assumed that the list of hook functions contains nothing more # than a whitespace-delimited list of legal shell function names, and # no effort is wasted trying to catch shell meta-characters or preserve # whitespace. func_run_hooks () { $debug_cmd _G_rc_run_hooks=false case " $hookable_fns " in *" $1 "*) ;; *) func_fatal_error "'$1' does not support hook funcions.n" ;; esac eval _G_hook_fns=\$$1_hooks; shift for _G_hook in $_G_hook_fns; do if eval $_G_hook '"$@"'; then # store returned options list back into positional # parameters for next 'cmd' execution. eval _G_hook_result=\$${_G_hook}_result eval set dummy "$_G_hook_result"; shift _G_rc_run_hooks=: fi done $_G_rc_run_hooks && func_run_hooks_result=$_G_hook_result } ## --------------- ## ## Option parsing. ## ## --------------- ## # In order to add your own option parsing hooks, you must accept the # full positional parameter list in your hook function, you may remove/edit # any options that you action, and then pass back the remaining unprocessed # options in '_result', escaped suitably for # 'eval'. In this case you also must return $EXIT_SUCCESS to let the # hook's caller know that it should pay attention to # '_result'. Returning $EXIT_FAILURE signalizes that # arguments are left untouched by the hook and therefore caller will ignore the # result variable. # # Like this: # # my_options_prep () # { # $debug_cmd # # # Extend the existing usage message. # usage_message=$usage_message' # -s, --silent don'\''t print informational messages # ' # # No change in '$@' (ignored completely by this hook). There is # # no need to do the equivalent (but slower) action: # # func_quote_for_eval ${1+"$@"} # # my_options_prep_result=$func_quote_for_eval_result # false # } # func_add_hook func_options_prep my_options_prep # # # my_silent_option () # { # $debug_cmd # # args_changed=false # # # Note that for efficiency, we parse as many options as we can # # recognise in a loop before passing the remainder back to the # # caller on the first unrecognised argument we encounter. # while test $# -gt 0; do # opt=$1; shift # case $opt in # --silent|-s) opt_silent=: # args_changed=: # ;; # # Separate non-argument short options: # -s*) func_split_short_opt "$_G_opt" # set dummy "$func_split_short_opt_name" \ # "-$func_split_short_opt_arg" ${1+"$@"} # shift # args_changed=: # ;; # *) # Make sure the first unrecognised option "$_G_opt" # # is added back to "$@", we could need that later # # if $args_changed is true. # set dummy "$_G_opt" ${1+"$@"}; shift; break ;; # esac # done # # if $args_changed; then # func_quote_for_eval ${1+"$@"} # my_silent_option_result=$func_quote_for_eval_result # fi # # $args_changed # } # func_add_hook func_parse_options my_silent_option # # # my_option_validation () # { # $debug_cmd # # $opt_silent && $opt_verbose && func_fatal_help "\ # '--silent' and '--verbose' options are mutually exclusive." # # false # } # func_add_hook func_validate_options my_option_validation # # You'll also need to manually amend $usage_message to reflect the extra # options you parse. It's preferable to append if you can, so that # multiple option parsing hooks can be added safely. # func_options_finish [ARG]... # ---------------------------- # Finishing the option parse loop (call 'func_options' hooks ATM). func_options_finish () { $debug_cmd _G_func_options_finish_exit=false if func_run_hooks func_options ${1+"$@"}; then func_options_finish_result=$func_run_hooks_result _G_func_options_finish_exit=: fi $_G_func_options_finish_exit } # func_options [ARG]... # --------------------- # All the functions called inside func_options are hookable. See the # individual implementations for details. func_hookable func_options func_options () { $debug_cmd _G_rc_options=false for my_func in options_prep parse_options validate_options options_finish do if eval func_$my_func '${1+"$@"}'; then eval _G_res_var='$'"func_${my_func}_result" eval set dummy "$_G_res_var" ; shift _G_rc_options=: fi done # Save modified positional parameters for caller. As a top-level # options-parser function we always need to set the 'func_options_result' # variable (regardless the $_G_rc_options value). if $_G_rc_options; then func_options_result=$_G_res_var else func_quote_for_eval ${1+"$@"} func_options_result=$func_quote_for_eval_result fi $_G_rc_options } # func_options_prep [ARG]... # -------------------------- # All initialisations required before starting the option parse loop. # Note that when calling hook functions, we pass through the list of # positional parameters. If a hook function modifies that list, and # needs to propagate that back to rest of this script, then the complete # modified list must be put in 'func_run_hooks_result' before # returning $EXIT_SUCCESS (otherwise $EXIT_FAILURE is returned). func_hookable func_options_prep func_options_prep () { $debug_cmd # Option defaults: opt_verbose=false opt_warning_types= _G_rc_options_prep=false if func_run_hooks func_options_prep ${1+"$@"}; then _G_rc_options_prep=: # save modified positional parameters for caller func_options_prep_result=$func_run_hooks_result fi $_G_rc_options_prep } # func_parse_options [ARG]... # --------------------------- # The main option parsing loop. func_hookable func_parse_options func_parse_options () { $debug_cmd func_parse_options_result= _G_rc_parse_options=false # this just eases exit handling while test $# -gt 0; do # Defer to hook functions for initial option parsing, so they # get priority in the event of reusing an option name. if func_run_hooks func_parse_options ${1+"$@"}; then eval set dummy "$func_run_hooks_result"; shift _G_rc_parse_options=: fi # Break out of the loop if we already parsed every option. test $# -gt 0 || break _G_match_parse_options=: _G_opt=$1 shift case $_G_opt in --debug|-x) debug_cmd='set -x' func_echo "enabling shell trace mode" $debug_cmd ;; --no-warnings|--no-warning|--no-warn) set dummy --warnings none ${1+"$@"} shift ;; --warnings|--warning|-W) if test $# = 0 && func_missing_arg $_G_opt; then _G_rc_parse_options=: break fi case " $warning_categories $1" in *" $1 "*) # trailing space prevents matching last $1 above func_append_uniq opt_warning_types " $1" ;; *all) opt_warning_types=$warning_categories ;; *none) opt_warning_types=none warning_func=: ;; *error) opt_warning_types=$warning_categories warning_func=func_fatal_error ;; *) func_fatal_error \ "unsupported warning category: '$1'" ;; esac shift ;; --verbose|-v) opt_verbose=: ;; --version) func_version ;; -\?|-h) func_usage ;; --help) func_help ;; # Separate optargs to long options (plugins may need this): --*=*) func_split_equals "$_G_opt" set dummy "$func_split_equals_lhs" \ "$func_split_equals_rhs" ${1+"$@"} shift ;; # Separate optargs to short options: -W*) func_split_short_opt "$_G_opt" set dummy "$func_split_short_opt_name" \ "$func_split_short_opt_arg" ${1+"$@"} shift ;; # Separate non-argument short options: -\?*|-h*|-v*|-x*) func_split_short_opt "$_G_opt" set dummy "$func_split_short_opt_name" \ "-$func_split_short_opt_arg" ${1+"$@"} shift ;; --) _G_rc_parse_options=: ; break ;; -*) func_fatal_help "unrecognised option: '$_G_opt'" ;; *) set dummy "$_G_opt" ${1+"$@"}; shift _G_match_parse_options=false break ;; esac $_G_match_parse_options && _G_rc_parse_options=: done if $_G_rc_parse_options; then # save modified positional parameters for caller func_quote_for_eval ${1+"$@"} func_parse_options_result=$func_quote_for_eval_result fi $_G_rc_parse_options } # func_validate_options [ARG]... # ------------------------------ # Perform any sanity checks on option settings and/or unconsumed # arguments. func_hookable func_validate_options func_validate_options () { $debug_cmd _G_rc_validate_options=false # Display all warnings if -W was not given. test -n "$opt_warning_types" || opt_warning_types=" $warning_categories" if func_run_hooks func_validate_options ${1+"$@"}; then # save modified positional parameters for caller func_validate_options_result=$func_run_hooks_result _G_rc_validate_options=: fi # Bail if the options were screwed! $exit_cmd $EXIT_FAILURE $_G_rc_validate_options } ## ----------------- ## ## Helper functions. ## ## ----------------- ## # This section contains the helper functions used by the rest of the # hookable option parser framework in ascii-betical order. # func_fatal_help ARG... # ---------------------- # Echo program name prefixed message to standard error, followed by # a help hint, and exit. func_fatal_help () { $debug_cmd eval \$ECHO \""Usage: $usage"\" eval \$ECHO \""$fatal_help"\" func_error ${1+"$@"} exit $EXIT_FAILURE } # func_help # --------- # Echo long help message to standard output and exit. func_help () { $debug_cmd func_usage_message $ECHO "$long_help_message" exit 0 } # func_missing_arg ARGNAME # ------------------------ # Echo program name prefixed message to standard error and set global # exit_cmd. func_missing_arg () { $debug_cmd func_error "Missing argument for '$1'." exit_cmd=exit } # func_split_equals STRING # ------------------------ # Set func_split_equals_lhs and func_split_equals_rhs shell variables after # splitting STRING at the '=' sign. test -z "$_G_HAVE_XSI_OPS" \ && (eval 'x=a/b/c; test 5aa/bb/cc = "${#x}${x%%/*}${x%/*}${x#*/}${x##*/}"') 2>/dev/null \ && _G_HAVE_XSI_OPS=yes if test yes = "$_G_HAVE_XSI_OPS" then # This is an XSI compatible shell, allowing a faster implementation... eval 'func_split_equals () { $debug_cmd func_split_equals_lhs=${1%%=*} func_split_equals_rhs=${1#*=} test "x$func_split_equals_lhs" = "x$1" \ && func_split_equals_rhs= }' else # ...otherwise fall back to using expr, which is often a shell builtin. func_split_equals () { $debug_cmd func_split_equals_lhs=`expr "x$1" : 'x\([^=]*\)'` func_split_equals_rhs= test "x$func_split_equals_lhs" = "x$1" \ || func_split_equals_rhs=`expr "x$1" : 'x[^=]*=\(.*\)$'` } fi #func_split_equals # func_split_short_opt SHORTOPT # ----------------------------- # Set func_split_short_opt_name and func_split_short_opt_arg shell # variables after splitting SHORTOPT after the 2nd character. if test yes = "$_G_HAVE_XSI_OPS" then # This is an XSI compatible shell, allowing a faster implementation... eval 'func_split_short_opt () { $debug_cmd func_split_short_opt_arg=${1#??} func_split_short_opt_name=${1%"$func_split_short_opt_arg"} }' else # ...otherwise fall back to using expr, which is often a shell builtin. func_split_short_opt () { $debug_cmd func_split_short_opt_name=`expr "x$1" : 'x-\(.\)'` func_split_short_opt_arg=`expr "x$1" : 'x-.\(.*\)$'` } fi #func_split_short_opt # func_usage # ---------- # Echo short help message to standard output and exit. func_usage () { $debug_cmd func_usage_message $ECHO "Run '$progname --help |${PAGER-more}' for full usage" exit 0 } # func_usage_message # ------------------ # Echo short help message to standard output. func_usage_message () { $debug_cmd eval \$ECHO \""Usage: $usage"\" echo $SED -n 's|^# || /^Written by/{ x;p;x } h /^Written by/q' < "$progpath" echo eval \$ECHO \""$usage_message"\" } # func_version # ------------ # Echo version message to standard output and exit. func_version () { $debug_cmd printf '%s\n' "$progname $scriptversion" $SED -n ' /(C)/!b go :more /\./!{ N s|\n# | | b more } :go /^# Written by /,/# warranty; / { s|^# || s|^# *$|| s|\((C)\)[ 0-9,-]*[ ,-]\([1-9][0-9]* \)|\1 \2| p } /^# Written by / { s|^# || p } /^warranty; /q' < "$progpath" exit $? } # Local variables: # mode: shell-script # sh-indentation: 2 # eval: (add-hook 'before-save-hook 'time-stamp) # time-stamp-pattern: "10/scriptversion=%:y-%02m-%02d.%02H; # UTC" # time-stamp-time-zone: "UTC" # End: # Set a version string. scriptversion='(GNU libtool) 2.4.6' # func_echo ARG... # ---------------- # Libtool also displays the current mode in messages, so override # funclib.sh func_echo with this custom definition. func_echo () { $debug_cmd _G_message=$* func_echo_IFS=$IFS IFS=$nl for _G_line in $_G_message; do IFS=$func_echo_IFS $ECHO "$progname${opt_mode+: $opt_mode}: $_G_line" done IFS=$func_echo_IFS } # func_warning ARG... # ------------------- # Libtool warnings are not categorized, so override funclib.sh # func_warning with this simpler definition. func_warning () { $debug_cmd $warning_func ${1+"$@"} } ## ---------------- ## ## Options parsing. ## ## ---------------- ## # Hook in the functions to make sure our own options are parsed during # the option parsing loop. usage='$progpath [OPTION]... [MODE-ARG]...' # Short help message in response to '-h'. usage_message="Options: --config show all configuration variables --debug enable verbose shell tracing -n, --dry-run display commands without modifying any files --features display basic configuration information and exit --mode=MODE use operation mode MODE --no-warnings equivalent to '-Wnone' --preserve-dup-deps don't remove duplicate dependency libraries --quiet, --silent don't print informational messages --tag=TAG use configuration variables from tag TAG -v, --verbose print more informational messages than default --version print version information -W, --warnings=CATEGORY report the warnings falling in CATEGORY [all] -h, --help, --help-all print short, long, or detailed help message " # Additional text appended to 'usage_message' in response to '--help'. func_help () { $debug_cmd func_usage_message $ECHO "$long_help_message MODE must be one of the following: clean remove files from the build directory compile compile a source file into a libtool object execute automatically set library path, then run a program finish complete the installation of libtool libraries install install libraries or executables link create a library or an executable uninstall remove libraries from an installed directory MODE-ARGS vary depending on the MODE. When passed as first option, '--mode=MODE' may be abbreviated as 'MODE' or a unique abbreviation of that. Try '$progname --help --mode=MODE' for a more detailed description of MODE. When reporting a bug, please describe a test case to reproduce it and include the following information: host-triplet: $host shell: $SHELL compiler: $LTCC compiler flags: $LTCFLAGS linker: $LD (gnu? $with_gnu_ld) version: $progname $scriptversion Debian-2.4.6-11 automake: `($AUTOMAKE --version) 2>/dev/null |$SED 1q` autoconf: `($AUTOCONF --version) 2>/dev/null |$SED 1q` Report bugs to . GNU libtool home page: . General help using GNU software: ." exit 0 } # func_lo2o OBJECT-NAME # --------------------- # Transform OBJECT-NAME from a '.lo' suffix to the platform specific # object suffix. lo2o=s/\\.lo\$/.$objext/ o2lo=s/\\.$objext\$/.lo/ if test yes = "$_G_HAVE_XSI_OPS"; then eval 'func_lo2o () { case $1 in *.lo) func_lo2o_result=${1%.lo}.$objext ;; * ) func_lo2o_result=$1 ;; esac }' # func_xform LIBOBJ-OR-SOURCE # --------------------------- # Transform LIBOBJ-OR-SOURCE from a '.o' or '.c' (or otherwise) # suffix to a '.lo' libtool-object suffix. eval 'func_xform () { func_xform_result=${1%.*}.lo }' else # ...otherwise fall back to using sed. func_lo2o () { func_lo2o_result=`$ECHO "$1" | $SED "$lo2o"` } func_xform () { func_xform_result=`$ECHO "$1" | $SED 's|\.[^.]*$|.lo|'` } fi # func_fatal_configuration ARG... # ------------------------------- # Echo program name prefixed message to standard error, followed by # a configuration failure hint, and exit. func_fatal_configuration () { func__fatal_error ${1+"$@"} \ "See the $PACKAGE documentation for more information." \ "Fatal configuration error." } # func_config # ----------- # Display the configuration for all the tags in this script. func_config () { re_begincf='^# ### BEGIN LIBTOOL' re_endcf='^# ### END LIBTOOL' # Default configuration. $SED "1,/$re_begincf CONFIG/d;/$re_endcf CONFIG/,\$d" < "$progpath" # Now print the configurations for the tags. for tagname in $taglist; do $SED -n "/$re_begincf TAG CONFIG: $tagname\$/,/$re_endcf TAG CONFIG: $tagname\$/p" < "$progpath" done exit $? } # func_features # ------------- # Display the features supported by this script. func_features () { echo "host: $host" if test yes = "$build_libtool_libs"; then echo "enable shared libraries" else echo "disable shared libraries" fi if test yes = "$build_old_libs"; then echo "enable static libraries" else echo "disable static libraries" fi exit $? } # func_enable_tag TAGNAME # ----------------------- # Verify that TAGNAME is valid, and either flag an error and exit, or # enable the TAGNAME tag. We also add TAGNAME to the global $taglist # variable here. func_enable_tag () { # Global variable: tagname=$1 re_begincf="^# ### BEGIN LIBTOOL TAG CONFIG: $tagname\$" re_endcf="^# ### END LIBTOOL TAG CONFIG: $tagname\$" sed_extractcf=/$re_begincf/,/$re_endcf/p # Validate tagname. case $tagname in *[!-_A-Za-z0-9,/]*) func_fatal_error "invalid tag name: $tagname" ;; esac # Don't test for the "default" C tag, as we know it's # there but not specially marked. case $tagname in CC) ;; *) if $GREP "$re_begincf" "$progpath" >/dev/null 2>&1; then taglist="$taglist $tagname" # Evaluate the configuration. Be careful to quote the path # and the sed script, to avoid splitting on whitespace, but # also don't use non-portable quotes within backquotes within # quotes we have to do it in 2 steps: extractedcf=`$SED -n -e "$sed_extractcf" < "$progpath"` eval "$extractedcf" else func_error "ignoring unknown tag $tagname" fi ;; esac } # func_check_version_match # ------------------------ # Ensure that we are using m4 macros, and libtool script from the same # release of libtool. func_check_version_match () { if test "$package_revision" != "$macro_revision"; then if test "$VERSION" != "$macro_version"; then if test -z "$macro_version"; then cat >&2 <<_LT_EOF $progname: Version mismatch error. This is $PACKAGE $VERSION, but the $progname: definition of this LT_INIT comes from an older release. $progname: You should recreate aclocal.m4 with macros from $PACKAGE $VERSION $progname: and run autoconf again. _LT_EOF else cat >&2 <<_LT_EOF $progname: Version mismatch error. This is $PACKAGE $VERSION, but the $progname: definition of this LT_INIT comes from $PACKAGE $macro_version. $progname: You should recreate aclocal.m4 with macros from $PACKAGE $VERSION $progname: and run autoconf again. _LT_EOF fi else cat >&2 <<_LT_EOF $progname: Version mismatch error. This is $PACKAGE $VERSION, revision $package_revision, $progname: but the definition of this LT_INIT comes from revision $macro_revision. $progname: You should recreate aclocal.m4 with macros from revision $package_revision $progname: of $PACKAGE $VERSION and run autoconf again. _LT_EOF fi exit $EXIT_MISMATCH fi } # libtool_options_prep [ARG]... # ----------------------------- # Preparation for options parsed by libtool. libtool_options_prep () { $debug_mode # Option defaults: opt_config=false opt_dlopen= opt_dry_run=false opt_help=false opt_mode= opt_preserve_dup_deps=false opt_quiet=false nonopt= preserve_args= _G_rc_lt_options_prep=: # Shorthand for --mode=foo, only valid as the first argument case $1 in clean|clea|cle|cl) shift; set dummy --mode clean ${1+"$@"}; shift ;; compile|compil|compi|comp|com|co|c) shift; set dummy --mode compile ${1+"$@"}; shift ;; execute|execut|execu|exec|exe|ex|e) shift; set dummy --mode execute ${1+"$@"}; shift ;; finish|finis|fini|fin|fi|f) shift; set dummy --mode finish ${1+"$@"}; shift ;; install|instal|insta|inst|ins|in|i) shift; set dummy --mode install ${1+"$@"}; shift ;; link|lin|li|l) shift; set dummy --mode link ${1+"$@"}; shift ;; uninstall|uninstal|uninsta|uninst|unins|unin|uni|un|u) shift; set dummy --mode uninstall ${1+"$@"}; shift ;; *) _G_rc_lt_options_prep=false ;; esac if $_G_rc_lt_options_prep; then # Pass back the list of options. func_quote_for_eval ${1+"$@"} libtool_options_prep_result=$func_quote_for_eval_result fi $_G_rc_lt_options_prep } func_add_hook func_options_prep libtool_options_prep # libtool_parse_options [ARG]... # --------------------------------- # Provide handling for libtool specific options. libtool_parse_options () { $debug_cmd _G_rc_lt_parse_options=false # Perform our own loop to consume as many options as possible in # each iteration. while test $# -gt 0; do _G_match_lt_parse_options=: _G_opt=$1 shift case $_G_opt in --dry-run|--dryrun|-n) opt_dry_run=: ;; --config) func_config ;; --dlopen|-dlopen) opt_dlopen="${opt_dlopen+$opt_dlopen }$1" shift ;; --preserve-dup-deps) opt_preserve_dup_deps=: ;; --features) func_features ;; --finish) set dummy --mode finish ${1+"$@"}; shift ;; --help) opt_help=: ;; --help-all) opt_help=': help-all' ;; --mode) test $# = 0 && func_missing_arg $_G_opt && break opt_mode=$1 case $1 in # Valid mode arguments: clean|compile|execute|finish|install|link|relink|uninstall) ;; # Catch anything else as an error *) func_error "invalid argument for $_G_opt" exit_cmd=exit break ;; esac shift ;; --no-silent|--no-quiet) opt_quiet=false func_append preserve_args " $_G_opt" ;; --no-warnings|--no-warning|--no-warn) opt_warning=false func_append preserve_args " $_G_opt" ;; --no-verbose) opt_verbose=false func_append preserve_args " $_G_opt" ;; --silent|--quiet) opt_quiet=: opt_verbose=false func_append preserve_args " $_G_opt" ;; --tag) test $# = 0 && func_missing_arg $_G_opt && break opt_tag=$1 func_append preserve_args " $_G_opt $1" func_enable_tag "$1" shift ;; --verbose|-v) opt_quiet=false opt_verbose=: func_append preserve_args " $_G_opt" ;; # An option not handled by this hook function: *) set dummy "$_G_opt" ${1+"$@"} ; shift _G_match_lt_parse_options=false break ;; esac $_G_match_lt_parse_options && _G_rc_lt_parse_options=: done if $_G_rc_lt_parse_options; then # save modified positional parameters for caller func_quote_for_eval ${1+"$@"} libtool_parse_options_result=$func_quote_for_eval_result fi $_G_rc_lt_parse_options } func_add_hook func_parse_options libtool_parse_options # libtool_validate_options [ARG]... # --------------------------------- # Perform any sanity checks on option settings and/or unconsumed # arguments. libtool_validate_options () { # save first non-option argument if test 0 -lt $#; then nonopt=$1 shift fi # preserve --debug test : = "$debug_cmd" || func_append preserve_args " --debug" case $host in # Solaris2 added to fix http://debbugs.gnu.org/cgi/bugreport.cgi?bug=16452 # see also: http://gcc.gnu.org/bugzilla/show_bug.cgi?id=59788 *cygwin* | *mingw* | *pw32* | *cegcc* | *solaris2* | *os2*) # don't eliminate duplications in $postdeps and $predeps opt_duplicate_compiler_generated_deps=: ;; *) opt_duplicate_compiler_generated_deps=$opt_preserve_dup_deps ;; esac $opt_help || { # Sanity checks first: func_check_version_match test yes != "$build_libtool_libs" \ && test yes != "$build_old_libs" \ && func_fatal_configuration "not configured to build any kind of library" # Darwin sucks eval std_shrext=\"$shrext_cmds\" # Only execute mode is allowed to have -dlopen flags. if test -n "$opt_dlopen" && test execute != "$opt_mode"; then func_error "unrecognized option '-dlopen'" $ECHO "$help" 1>&2 exit $EXIT_FAILURE fi # Change the help message to a mode-specific one. generic_help=$help help="Try '$progname --help --mode=$opt_mode' for more information." } # Pass back the unparsed argument list func_quote_for_eval ${1+"$@"} libtool_validate_options_result=$func_quote_for_eval_result } func_add_hook func_validate_options libtool_validate_options # Process options as early as possible so that --help and --version # can return quickly. func_options ${1+"$@"} eval set dummy "$func_options_result"; shift ## ----------- ## ## Main. ## ## ----------- ## magic='%%%MAGIC variable%%%' magic_exe='%%%MAGIC EXE variable%%%' # Global variables. extracted_archives= extracted_serial=0 # If this variable is set in any of the actions, the command in it # will be execed at the end. This prevents here-documents from being # left over by shells. exec_cmd= # A function that is used when there is no print builtin or printf. func_fallback_echo () { eval 'cat <<_LTECHO_EOF $1 _LTECHO_EOF' } # func_generated_by_libtool # True iff stdin has been generated by Libtool. This function is only # a basic sanity check; it will hardly flush out determined imposters. func_generated_by_libtool_p () { $GREP "^# Generated by .*$PACKAGE" > /dev/null 2>&1 } # func_lalib_p file # True iff FILE is a libtool '.la' library or '.lo' object file. # This function is only a basic sanity check; it will hardly flush out # determined imposters. func_lalib_p () { test -f "$1" && $SED -e 4q "$1" 2>/dev/null | func_generated_by_libtool_p } # func_lalib_unsafe_p file # True iff FILE is a libtool '.la' library or '.lo' object file. # This function implements the same check as func_lalib_p without # resorting to external programs. To this end, it redirects stdin and # closes it afterwards, without saving the original file descriptor. # As a safety measure, use it only where a negative result would be # fatal anyway. Works if 'file' does not exist. func_lalib_unsafe_p () { lalib_p=no if test -f "$1" && test -r "$1" && exec 5<&0 <"$1"; then for lalib_p_l in 1 2 3 4 do read lalib_p_line case $lalib_p_line in \#\ Generated\ by\ *$PACKAGE* ) lalib_p=yes; break;; esac done exec 0<&5 5<&- fi test yes = "$lalib_p" } # func_ltwrapper_script_p file # True iff FILE is a libtool wrapper script # This function is only a basic sanity check; it will hardly flush out # determined imposters. func_ltwrapper_script_p () { test -f "$1" && $lt_truncate_bin < "$1" 2>/dev/null | func_generated_by_libtool_p } # func_ltwrapper_executable_p file # True iff FILE is a libtool wrapper executable # This function is only a basic sanity check; it will hardly flush out # determined imposters. func_ltwrapper_executable_p () { func_ltwrapper_exec_suffix= case $1 in *.exe) ;; *) func_ltwrapper_exec_suffix=.exe ;; esac $GREP "$magic_exe" "$1$func_ltwrapper_exec_suffix" >/dev/null 2>&1 } # func_ltwrapper_scriptname file # Assumes file is an ltwrapper_executable # uses $file to determine the appropriate filename for a # temporary ltwrapper_script. func_ltwrapper_scriptname () { func_dirname_and_basename "$1" "" "." func_stripname '' '.exe' "$func_basename_result" func_ltwrapper_scriptname_result=$func_dirname_result/$objdir/${func_stripname_result}_ltshwrapper } # func_ltwrapper_p file # True iff FILE is a libtool wrapper script or wrapper executable # This function is only a basic sanity check; it will hardly flush out # determined imposters. func_ltwrapper_p () { func_ltwrapper_script_p "$1" || func_ltwrapper_executable_p "$1" } # func_execute_cmds commands fail_cmd # Execute tilde-delimited COMMANDS. # If FAIL_CMD is given, eval that upon failure. # FAIL_CMD may read-access the current command in variable CMD! func_execute_cmds () { $debug_cmd save_ifs=$IFS; IFS='~' for cmd in $1; do IFS=$sp$nl eval cmd=\"$cmd\" IFS=$save_ifs func_show_eval "$cmd" "${2-:}" done IFS=$save_ifs } # func_source file # Source FILE, adding directory component if necessary. # Note that it is not necessary on cygwin/mingw to append a dot to # FILE even if both FILE and FILE.exe exist: automatic-append-.exe # behavior happens only for exec(3), not for open(2)! Also, sourcing # 'FILE.' does not work on cygwin managed mounts. func_source () { $debug_cmd case $1 in */* | *\\*) . "$1" ;; *) . "./$1" ;; esac } # func_resolve_sysroot PATH # Replace a leading = in PATH with a sysroot. Store the result into # func_resolve_sysroot_result func_resolve_sysroot () { func_resolve_sysroot_result=$1 case $func_resolve_sysroot_result in =*) func_stripname '=' '' "$func_resolve_sysroot_result" func_resolve_sysroot_result=$lt_sysroot$func_stripname_result ;; esac } # func_replace_sysroot PATH # If PATH begins with the sysroot, replace it with = and # store the result into func_replace_sysroot_result. func_replace_sysroot () { case $lt_sysroot:$1 in ?*:"$lt_sysroot"*) func_stripname "$lt_sysroot" '' "$1" func_replace_sysroot_result='='$func_stripname_result ;; *) # Including no sysroot. func_replace_sysroot_result=$1 ;; esac } # func_infer_tag arg # Infer tagged configuration to use if any are available and # if one wasn't chosen via the "--tag" command line option. # Only attempt this if the compiler in the base compile # command doesn't match the default compiler. # arg is usually of the form 'gcc ...' func_infer_tag () { $debug_cmd if test -n "$available_tags" && test -z "$tagname"; then CC_quoted= for arg in $CC; do func_append_quoted CC_quoted "$arg" done CC_expanded=`func_echo_all $CC` CC_quoted_expanded=`func_echo_all $CC_quoted` case $@ in # Blanks in the command may have been stripped by the calling shell, # but not from the CC environment variable when configure was run. " $CC "* | "$CC "* | " $CC_expanded "* | "$CC_expanded "* | \ " $CC_quoted"* | "$CC_quoted "* | " $CC_quoted_expanded "* | "$CC_quoted_expanded "*) ;; # Blanks at the start of $base_compile will cause this to fail # if we don't check for them as well. *) for z in $available_tags; do if $GREP "^# ### BEGIN LIBTOOL TAG CONFIG: $z$" < "$progpath" > /dev/null; then # Evaluate the configuration. eval "`$SED -n -e '/^# ### BEGIN LIBTOOL TAG CONFIG: '$z'$/,/^# ### END LIBTOOL TAG CONFIG: '$z'$/p' < $progpath`" CC_quoted= for arg in $CC; do # Double-quote args containing other shell metacharacters. func_append_quoted CC_quoted "$arg" done CC_expanded=`func_echo_all $CC` CC_quoted_expanded=`func_echo_all $CC_quoted` case "$@ " in " $CC "* | "$CC "* | " $CC_expanded "* | "$CC_expanded "* | \ " $CC_quoted"* | "$CC_quoted "* | " $CC_quoted_expanded "* | "$CC_quoted_expanded "*) # The compiler in the base compile command matches # the one in the tagged configuration. # Assume this is the tagged configuration we want. tagname=$z break ;; esac fi done # If $tagname still isn't set, then no tagged configuration # was found and let the user know that the "--tag" command # line option must be used. if test -z "$tagname"; then func_echo "unable to infer tagged configuration" func_fatal_error "specify a tag with '--tag'" # else # func_verbose "using $tagname tagged configuration" fi ;; esac fi } # func_write_libtool_object output_name pic_name nonpic_name # Create a libtool object file (analogous to a ".la" file), # but don't create it if we're doing a dry run. func_write_libtool_object () { write_libobj=$1 if test yes = "$build_libtool_libs"; then write_lobj=\'$2\' else write_lobj=none fi if test yes = "$build_old_libs"; then write_oldobj=\'$3\' else write_oldobj=none fi $opt_dry_run || { cat >${write_libobj}T </dev/null` if test "$?" -eq 0 && test -n "$func_convert_core_file_wine_to_w32_tmp"; then func_convert_core_file_wine_to_w32_result=`$ECHO "$func_convert_core_file_wine_to_w32_tmp" | $SED -e "$sed_naive_backslashify"` else func_convert_core_file_wine_to_w32_result= fi fi } # end: func_convert_core_file_wine_to_w32 # func_convert_core_path_wine_to_w32 ARG # Helper function used by path conversion functions when $build is *nix, and # $host is mingw, cygwin, or some other w32 environment. Relies on a correctly # configured wine environment available, with the winepath program in $build's # $PATH. Assumes ARG has no leading or trailing path separator characters. # # ARG is path to be converted from $build format to win32. # Result is available in $func_convert_core_path_wine_to_w32_result. # Unconvertible file (directory) names in ARG are skipped; if no directory names # are convertible, then the result may be empty. func_convert_core_path_wine_to_w32 () { $debug_cmd # unfortunately, winepath doesn't convert paths, only file names func_convert_core_path_wine_to_w32_result= if test -n "$1"; then oldIFS=$IFS IFS=: for func_convert_core_path_wine_to_w32_f in $1; do IFS=$oldIFS func_convert_core_file_wine_to_w32 "$func_convert_core_path_wine_to_w32_f" if test -n "$func_convert_core_file_wine_to_w32_result"; then if test -z "$func_convert_core_path_wine_to_w32_result"; then func_convert_core_path_wine_to_w32_result=$func_convert_core_file_wine_to_w32_result else func_append func_convert_core_path_wine_to_w32_result ";$func_convert_core_file_wine_to_w32_result" fi fi done IFS=$oldIFS fi } # end: func_convert_core_path_wine_to_w32 # func_cygpath ARGS... # Wrapper around calling the cygpath program via LT_CYGPATH. This is used when # when (1) $build is *nix and Cygwin is hosted via a wine environment; or (2) # $build is MSYS and $host is Cygwin, or (3) $build is Cygwin. In case (1) or # (2), returns the Cygwin file name or path in func_cygpath_result (input # file name or path is assumed to be in w32 format, as previously converted # from $build's *nix or MSYS format). In case (3), returns the w32 file name # or path in func_cygpath_result (input file name or path is assumed to be in # Cygwin format). Returns an empty string on error. # # ARGS are passed to cygpath, with the last one being the file name or path to # be converted. # # Specify the absolute *nix (or w32) name to cygpath in the LT_CYGPATH # environment variable; do not put it in $PATH. func_cygpath () { $debug_cmd if test -n "$LT_CYGPATH" && test -f "$LT_CYGPATH"; then func_cygpath_result=`$LT_CYGPATH "$@" 2>/dev/null` if test "$?" -ne 0; then # on failure, ensure result is empty func_cygpath_result= fi else func_cygpath_result= func_error "LT_CYGPATH is empty or specifies non-existent file: '$LT_CYGPATH'" fi } #end: func_cygpath # func_convert_core_msys_to_w32 ARG # Convert file name or path ARG from MSYS format to w32 format. Return # result in func_convert_core_msys_to_w32_result. func_convert_core_msys_to_w32 () { $debug_cmd # awkward: cmd appends spaces to result func_convert_core_msys_to_w32_result=`( cmd //c echo "$1" ) 2>/dev/null | $SED -e 's/[ ]*$//' -e "$sed_naive_backslashify"` } #end: func_convert_core_msys_to_w32 # func_convert_file_check ARG1 ARG2 # Verify that ARG1 (a file name in $build format) was converted to $host # format in ARG2. Otherwise, emit an error message, but continue (resetting # func_to_host_file_result to ARG1). func_convert_file_check () { $debug_cmd if test -z "$2" && test -n "$1"; then func_error "Could not determine host file name corresponding to" func_error " '$1'" func_error "Continuing, but uninstalled executables may not work." # Fallback: func_to_host_file_result=$1 fi } # end func_convert_file_check # func_convert_path_check FROM_PATHSEP TO_PATHSEP FROM_PATH TO_PATH # Verify that FROM_PATH (a path in $build format) was converted to $host # format in TO_PATH. Otherwise, emit an error message, but continue, resetting # func_to_host_file_result to a simplistic fallback value (see below). func_convert_path_check () { $debug_cmd if test -z "$4" && test -n "$3"; then func_error "Could not determine the host path corresponding to" func_error " '$3'" func_error "Continuing, but uninstalled executables may not work." # Fallback. This is a deliberately simplistic "conversion" and # should not be "improved". See libtool.info. if test "x$1" != "x$2"; then lt_replace_pathsep_chars="s|$1|$2|g" func_to_host_path_result=`echo "$3" | $SED -e "$lt_replace_pathsep_chars"` else func_to_host_path_result=$3 fi fi } # end func_convert_path_check # func_convert_path_front_back_pathsep FRONTPAT BACKPAT REPL ORIG # Modifies func_to_host_path_result by prepending REPL if ORIG matches FRONTPAT # and appending REPL if ORIG matches BACKPAT. func_convert_path_front_back_pathsep () { $debug_cmd case $4 in $1 ) func_to_host_path_result=$3$func_to_host_path_result ;; esac case $4 in $2 ) func_append func_to_host_path_result "$3" ;; esac } # end func_convert_path_front_back_pathsep ################################################## # $build to $host FILE NAME CONVERSION FUNCTIONS # ################################################## # invoked via '$to_host_file_cmd ARG' # # In each case, ARG is the path to be converted from $build to $host format. # Result will be available in $func_to_host_file_result. # func_to_host_file ARG # Converts the file name ARG from $build format to $host format. Return result # in func_to_host_file_result. func_to_host_file () { $debug_cmd $to_host_file_cmd "$1" } # end func_to_host_file # func_to_tool_file ARG LAZY # converts the file name ARG from $build format to toolchain format. Return # result in func_to_tool_file_result. If the conversion in use is listed # in (the comma separated) LAZY, no conversion takes place. func_to_tool_file () { $debug_cmd case ,$2, in *,"$to_tool_file_cmd",*) func_to_tool_file_result=$1 ;; *) $to_tool_file_cmd "$1" func_to_tool_file_result=$func_to_host_file_result ;; esac } # end func_to_tool_file # func_convert_file_noop ARG # Copy ARG to func_to_host_file_result. func_convert_file_noop () { func_to_host_file_result=$1 } # end func_convert_file_noop # func_convert_file_msys_to_w32 ARG # Convert file name ARG from (mingw) MSYS to (mingw) w32 format; automatic # conversion to w32 is not available inside the cwrapper. Returns result in # func_to_host_file_result. func_convert_file_msys_to_w32 () { $debug_cmd func_to_host_file_result=$1 if test -n "$1"; then func_convert_core_msys_to_w32 "$1" func_to_host_file_result=$func_convert_core_msys_to_w32_result fi func_convert_file_check "$1" "$func_to_host_file_result" } # end func_convert_file_msys_to_w32 # func_convert_file_cygwin_to_w32 ARG # Convert file name ARG from Cygwin to w32 format. Returns result in # func_to_host_file_result. func_convert_file_cygwin_to_w32 () { $debug_cmd func_to_host_file_result=$1 if test -n "$1"; then # because $build is cygwin, we call "the" cygpath in $PATH; no need to use # LT_CYGPATH in this case. func_to_host_file_result=`cygpath -m "$1"` fi func_convert_file_check "$1" "$func_to_host_file_result" } # end func_convert_file_cygwin_to_w32 # func_convert_file_nix_to_w32 ARG # Convert file name ARG from *nix to w32 format. Requires a wine environment # and a working winepath. Returns result in func_to_host_file_result. func_convert_file_nix_to_w32 () { $debug_cmd func_to_host_file_result=$1 if test -n "$1"; then func_convert_core_file_wine_to_w32 "$1" func_to_host_file_result=$func_convert_core_file_wine_to_w32_result fi func_convert_file_check "$1" "$func_to_host_file_result" } # end func_convert_file_nix_to_w32 # func_convert_file_msys_to_cygwin ARG # Convert file name ARG from MSYS to Cygwin format. Requires LT_CYGPATH set. # Returns result in func_to_host_file_result. func_convert_file_msys_to_cygwin () { $debug_cmd func_to_host_file_result=$1 if test -n "$1"; then func_convert_core_msys_to_w32 "$1" func_cygpath -u "$func_convert_core_msys_to_w32_result" func_to_host_file_result=$func_cygpath_result fi func_convert_file_check "$1" "$func_to_host_file_result" } # end func_convert_file_msys_to_cygwin # func_convert_file_nix_to_cygwin ARG # Convert file name ARG from *nix to Cygwin format. Requires Cygwin installed # in a wine environment, working winepath, and LT_CYGPATH set. Returns result # in func_to_host_file_result. func_convert_file_nix_to_cygwin () { $debug_cmd func_to_host_file_result=$1 if test -n "$1"; then # convert from *nix to w32, then use cygpath to convert from w32 to cygwin. func_convert_core_file_wine_to_w32 "$1" func_cygpath -u "$func_convert_core_file_wine_to_w32_result" func_to_host_file_result=$func_cygpath_result fi func_convert_file_check "$1" "$func_to_host_file_result" } # end func_convert_file_nix_to_cygwin ############################################# # $build to $host PATH CONVERSION FUNCTIONS # ############################################# # invoked via '$to_host_path_cmd ARG' # # In each case, ARG is the path to be converted from $build to $host format. # The result will be available in $func_to_host_path_result. # # Path separators are also converted from $build format to $host format. If # ARG begins or ends with a path separator character, it is preserved (but # converted to $host format) on output. # # All path conversion functions are named using the following convention: # file name conversion function : func_convert_file_X_to_Y () # path conversion function : func_convert_path_X_to_Y () # where, for any given $build/$host combination the 'X_to_Y' value is the # same. If conversion functions are added for new $build/$host combinations, # the two new functions must follow this pattern, or func_init_to_host_path_cmd # will break. # func_init_to_host_path_cmd # Ensures that function "pointer" variable $to_host_path_cmd is set to the # appropriate value, based on the value of $to_host_file_cmd. to_host_path_cmd= func_init_to_host_path_cmd () { $debug_cmd if test -z "$to_host_path_cmd"; then func_stripname 'func_convert_file_' '' "$to_host_file_cmd" to_host_path_cmd=func_convert_path_$func_stripname_result fi } # func_to_host_path ARG # Converts the path ARG from $build format to $host format. Return result # in func_to_host_path_result. func_to_host_path () { $debug_cmd func_init_to_host_path_cmd $to_host_path_cmd "$1" } # end func_to_host_path # func_convert_path_noop ARG # Copy ARG to func_to_host_path_result. func_convert_path_noop () { func_to_host_path_result=$1 } # end func_convert_path_noop # func_convert_path_msys_to_w32 ARG # Convert path ARG from (mingw) MSYS to (mingw) w32 format; automatic # conversion to w32 is not available inside the cwrapper. Returns result in # func_to_host_path_result. func_convert_path_msys_to_w32 () { $debug_cmd func_to_host_path_result=$1 if test -n "$1"; then # Remove leading and trailing path separator characters from ARG. MSYS # behavior is inconsistent here; cygpath turns them into '.;' and ';.'; # and winepath ignores them completely. func_stripname : : "$1" func_to_host_path_tmp1=$func_stripname_result func_convert_core_msys_to_w32 "$func_to_host_path_tmp1" func_to_host_path_result=$func_convert_core_msys_to_w32_result func_convert_path_check : ";" \ "$func_to_host_path_tmp1" "$func_to_host_path_result" func_convert_path_front_back_pathsep ":*" "*:" ";" "$1" fi } # end func_convert_path_msys_to_w32 # func_convert_path_cygwin_to_w32 ARG # Convert path ARG from Cygwin to w32 format. Returns result in # func_to_host_file_result. func_convert_path_cygwin_to_w32 () { $debug_cmd func_to_host_path_result=$1 if test -n "$1"; then # See func_convert_path_msys_to_w32: func_stripname : : "$1" func_to_host_path_tmp1=$func_stripname_result func_to_host_path_result=`cygpath -m -p "$func_to_host_path_tmp1"` func_convert_path_check : ";" \ "$func_to_host_path_tmp1" "$func_to_host_path_result" func_convert_path_front_back_pathsep ":*" "*:" ";" "$1" fi } # end func_convert_path_cygwin_to_w32 # func_convert_path_nix_to_w32 ARG # Convert path ARG from *nix to w32 format. Requires a wine environment and # a working winepath. Returns result in func_to_host_file_result. func_convert_path_nix_to_w32 () { $debug_cmd func_to_host_path_result=$1 if test -n "$1"; then # See func_convert_path_msys_to_w32: func_stripname : : "$1" func_to_host_path_tmp1=$func_stripname_result func_convert_core_path_wine_to_w32 "$func_to_host_path_tmp1" func_to_host_path_result=$func_convert_core_path_wine_to_w32_result func_convert_path_check : ";" \ "$func_to_host_path_tmp1" "$func_to_host_path_result" func_convert_path_front_back_pathsep ":*" "*:" ";" "$1" fi } # end func_convert_path_nix_to_w32 # func_convert_path_msys_to_cygwin ARG # Convert path ARG from MSYS to Cygwin format. Requires LT_CYGPATH set. # Returns result in func_to_host_file_result. func_convert_path_msys_to_cygwin () { $debug_cmd func_to_host_path_result=$1 if test -n "$1"; then # See func_convert_path_msys_to_w32: func_stripname : : "$1" func_to_host_path_tmp1=$func_stripname_result func_convert_core_msys_to_w32 "$func_to_host_path_tmp1" func_cygpath -u -p "$func_convert_core_msys_to_w32_result" func_to_host_path_result=$func_cygpath_result func_convert_path_check : : \ "$func_to_host_path_tmp1" "$func_to_host_path_result" func_convert_path_front_back_pathsep ":*" "*:" : "$1" fi } # end func_convert_path_msys_to_cygwin # func_convert_path_nix_to_cygwin ARG # Convert path ARG from *nix to Cygwin format. Requires Cygwin installed in a # a wine environment, working winepath, and LT_CYGPATH set. Returns result in # func_to_host_file_result. func_convert_path_nix_to_cygwin () { $debug_cmd func_to_host_path_result=$1 if test -n "$1"; then # Remove leading and trailing path separator characters from # ARG. msys behavior is inconsistent here, cygpath turns them # into '.;' and ';.', and winepath ignores them completely. func_stripname : : "$1" func_to_host_path_tmp1=$func_stripname_result func_convert_core_path_wine_to_w32 "$func_to_host_path_tmp1" func_cygpath -u -p "$func_convert_core_path_wine_to_w32_result" func_to_host_path_result=$func_cygpath_result func_convert_path_check : : \ "$func_to_host_path_tmp1" "$func_to_host_path_result" func_convert_path_front_back_pathsep ":*" "*:" : "$1" fi } # end func_convert_path_nix_to_cygwin # func_dll_def_p FILE # True iff FILE is a Windows DLL '.def' file. # Keep in sync with _LT_DLL_DEF_P in libtool.m4 func_dll_def_p () { $debug_cmd func_dll_def_p_tmp=`$SED -n \ -e 's/^[ ]*//' \ -e '/^\(;.*\)*$/d' \ -e 's/^\(EXPORTS\|LIBRARY\)\([ ].*\)*$/DEF/p' \ -e q \ "$1"` test DEF = "$func_dll_def_p_tmp" } # func_mode_compile arg... func_mode_compile () { $debug_cmd # Get the compilation command and the source file. base_compile= srcfile=$nonopt # always keep a non-empty value in "srcfile" suppress_opt=yes suppress_output= arg_mode=normal libobj= later= pie_flag= for arg do case $arg_mode in arg ) # do not "continue". Instead, add this to base_compile lastarg=$arg arg_mode=normal ;; target ) libobj=$arg arg_mode=normal continue ;; normal ) # Accept any command-line options. case $arg in -o) test -n "$libobj" && \ func_fatal_error "you cannot specify '-o' more than once" arg_mode=target continue ;; -pie | -fpie | -fPIE) func_append pie_flag " $arg" continue ;; -shared | -static | -prefer-pic | -prefer-non-pic) func_append later " $arg" continue ;; -no-suppress) suppress_opt=no continue ;; -Xcompiler) arg_mode=arg # the next one goes into the "base_compile" arg list continue # The current "srcfile" will either be retained or ;; # replaced later. I would guess that would be a bug. -Wc,*) func_stripname '-Wc,' '' "$arg" args=$func_stripname_result lastarg= save_ifs=$IFS; IFS=, for arg in $args; do IFS=$save_ifs func_append_quoted lastarg "$arg" done IFS=$save_ifs func_stripname ' ' '' "$lastarg" lastarg=$func_stripname_result # Add the arguments to base_compile. func_append base_compile " $lastarg" continue ;; *) # Accept the current argument as the source file. # The previous "srcfile" becomes the current argument. # lastarg=$srcfile srcfile=$arg ;; esac # case $arg ;; esac # case $arg_mode # Aesthetically quote the previous argument. func_append_quoted base_compile "$lastarg" done # for arg case $arg_mode in arg) func_fatal_error "you must specify an argument for -Xcompile" ;; target) func_fatal_error "you must specify a target with '-o'" ;; *) # Get the name of the library object. test -z "$libobj" && { func_basename "$srcfile" libobj=$func_basename_result } ;; esac # Recognize several different file suffixes. # If the user specifies -o file.o, it is replaced with file.lo case $libobj in *.[cCFSifmso] | \ *.ada | *.adb | *.ads | *.asm | \ *.c++ | *.cc | *.ii | *.class | *.cpp | *.cxx | \ *.[fF][09]? | *.for | *.java | *.go | *.obj | *.sx | *.cu | *.cup) func_xform "$libobj" libobj=$func_xform_result ;; esac case $libobj in *.lo) func_lo2o "$libobj"; obj=$func_lo2o_result ;; *) func_fatal_error "cannot determine name of library object from '$libobj'" ;; esac func_infer_tag $base_compile for arg in $later; do case $arg in -shared) test yes = "$build_libtool_libs" \ || func_fatal_configuration "cannot build a shared library" build_old_libs=no continue ;; -static) build_libtool_libs=no build_old_libs=yes continue ;; -prefer-pic) pic_mode=yes continue ;; -prefer-non-pic) pic_mode=no continue ;; esac done func_quote_for_eval "$libobj" test "X$libobj" != "X$func_quote_for_eval_result" \ && $ECHO "X$libobj" | $GREP '[]~#^*{};<>?"'"'"' &()|`$[]' \ && func_warning "libobj name '$libobj' may not contain shell special characters." func_dirname_and_basename "$obj" "/" "" objname=$func_basename_result xdir=$func_dirname_result lobj=$xdir$objdir/$objname test -z "$base_compile" && \ func_fatal_help "you must specify a compilation command" # Delete any leftover library objects. if test yes = "$build_old_libs"; then removelist="$obj $lobj $libobj ${libobj}T" else removelist="$lobj $libobj ${libobj}T" fi # On Cygwin there's no "real" PIC flag so we must build both object types case $host_os in cygwin* | mingw* | pw32* | os2* | cegcc*) pic_mode=default ;; esac if test no = "$pic_mode" && test pass_all != "$deplibs_check_method"; then # non-PIC code in shared libraries is not supported pic_mode=default fi # Calculate the filename of the output object if compiler does # not support -o with -c if test no = "$compiler_c_o"; then output_obj=`$ECHO "$srcfile" | $SED 's%^.*/%%; s%\.[^.]*$%%'`.$objext lockfile=$output_obj.lock else output_obj= need_locks=no lockfile= fi # Lock this critical section if it is needed # We use this script file to make the link, it avoids creating a new file if test yes = "$need_locks"; then until $opt_dry_run || ln "$progpath" "$lockfile" 2>/dev/null; do func_echo "Waiting for $lockfile to be removed" sleep 2 done elif test warn = "$need_locks"; then if test -f "$lockfile"; then $ECHO "\ *** ERROR, $lockfile exists and contains: `cat $lockfile 2>/dev/null` This indicates that another process is trying to use the same temporary object file, and libtool could not work around it because your compiler does not support '-c' and '-o' together. If you repeat this compilation, it may succeed, by chance, but you had better avoid parallel builds (make -j) in this platform, or get a better compiler." $opt_dry_run || $RM $removelist exit $EXIT_FAILURE fi func_append removelist " $output_obj" $ECHO "$srcfile" > "$lockfile" fi $opt_dry_run || $RM $removelist func_append removelist " $lockfile" trap '$opt_dry_run || $RM $removelist; exit $EXIT_FAILURE' 1 2 15 func_to_tool_file "$srcfile" func_convert_file_msys_to_w32 srcfile=$func_to_tool_file_result func_quote_for_eval "$srcfile" qsrcfile=$func_quote_for_eval_result # Only build a PIC object if we are building libtool libraries. if test yes = "$build_libtool_libs"; then # Without this assignment, base_compile gets emptied. fbsd_hideous_sh_bug=$base_compile if test no != "$pic_mode"; then command="$base_compile $qsrcfile $pic_flag" else # Don't build PIC code command="$base_compile $qsrcfile" fi func_mkdir_p "$xdir$objdir" if test -z "$output_obj"; then # Place PIC objects in $objdir func_append command " -o $lobj" fi func_show_eval_locale "$command" \ 'test -n "$output_obj" && $RM $removelist; exit $EXIT_FAILURE' if test warn = "$need_locks" && test "X`cat $lockfile 2>/dev/null`" != "X$srcfile"; then $ECHO "\ *** ERROR, $lockfile contains: `cat $lockfile 2>/dev/null` but it should contain: $srcfile This indicates that another process is trying to use the same temporary object file, and libtool could not work around it because your compiler does not support '-c' and '-o' together. If you repeat this compilation, it may succeed, by chance, but you had better avoid parallel builds (make -j) in this platform, or get a better compiler." $opt_dry_run || $RM $removelist exit $EXIT_FAILURE fi # Just move the object if needed, then go on to compile the next one if test -n "$output_obj" && test "X$output_obj" != "X$lobj"; then func_show_eval '$MV "$output_obj" "$lobj"' \ 'error=$?; $opt_dry_run || $RM $removelist; exit $error' fi # Allow error messages only from the first compilation. if test yes = "$suppress_opt"; then suppress_output=' >/dev/null 2>&1' fi fi # Only build a position-dependent object if we build old libraries. if test yes = "$build_old_libs"; then if test yes != "$pic_mode"; then # Don't build PIC code command="$base_compile $qsrcfile$pie_flag" else command="$base_compile $qsrcfile $pic_flag" fi if test yes = "$compiler_c_o"; then func_append command " -o $obj" fi # Suppress compiler output if we already did a PIC compilation. func_append command "$suppress_output" func_show_eval_locale "$command" \ '$opt_dry_run || $RM $removelist; exit $EXIT_FAILURE' if test warn = "$need_locks" && test "X`cat $lockfile 2>/dev/null`" != "X$srcfile"; then $ECHO "\ *** ERROR, $lockfile contains: `cat $lockfile 2>/dev/null` but it should contain: $srcfile This indicates that another process is trying to use the same temporary object file, and libtool could not work around it because your compiler does not support '-c' and '-o' together. If you repeat this compilation, it may succeed, by chance, but you had better avoid parallel builds (make -j) in this platform, or get a better compiler." $opt_dry_run || $RM $removelist exit $EXIT_FAILURE fi # Just move the object if needed if test -n "$output_obj" && test "X$output_obj" != "X$obj"; then func_show_eval '$MV "$output_obj" "$obj"' \ 'error=$?; $opt_dry_run || $RM $removelist; exit $error' fi fi $opt_dry_run || { func_write_libtool_object "$libobj" "$objdir/$objname" "$objname" # Unlock the critical section if it was locked if test no != "$need_locks"; then removelist=$lockfile $RM "$lockfile" fi } exit $EXIT_SUCCESS } $opt_help || { test compile = "$opt_mode" && func_mode_compile ${1+"$@"} } func_mode_help () { # We need to display help for each of the modes. case $opt_mode in "") # Generic help is extracted from the usage comments # at the start of this file. func_help ;; clean) $ECHO \ "Usage: $progname [OPTION]... --mode=clean RM [RM-OPTION]... FILE... Remove files from the build directory. RM is the name of the program to use to delete files associated with each FILE (typically '/bin/rm'). RM-OPTIONS are options (such as '-f') to be passed to RM. If FILE is a libtool library, object or program, all the files associated with it are deleted. Otherwise, only FILE itself is deleted using RM." ;; compile) $ECHO \ "Usage: $progname [OPTION]... --mode=compile COMPILE-COMMAND... SOURCEFILE Compile a source file into a libtool library object. This mode accepts the following additional options: -o OUTPUT-FILE set the output file name to OUTPUT-FILE -no-suppress do not suppress compiler output for multiple passes -prefer-pic try to build PIC objects only -prefer-non-pic try to build non-PIC objects only -shared do not build a '.o' file suitable for static linking -static only build a '.o' file suitable for static linking -Wc,FLAG pass FLAG directly to the compiler COMPILE-COMMAND is a command to be used in creating a 'standard' object file from the given SOURCEFILE. The output file name is determined by removing the directory component from SOURCEFILE, then substituting the C source code suffix '.c' with the library object suffix, '.lo'." ;; execute) $ECHO \ "Usage: $progname [OPTION]... --mode=execute COMMAND [ARGS]... Automatically set library path, then run a program. This mode accepts the following additional options: -dlopen FILE add the directory containing FILE to the library path This mode sets the library path environment variable according to '-dlopen' flags. If any of the ARGS are libtool executable wrappers, then they are translated into their corresponding uninstalled binary, and any of their required library directories are added to the library path. Then, COMMAND is executed, with ARGS as arguments." ;; finish) $ECHO \ "Usage: $progname [OPTION]... --mode=finish [LIBDIR]... Complete the installation of libtool libraries. Each LIBDIR is a directory that contains libtool libraries. The commands that this mode executes may require superuser privileges. Use the '--dry-run' option if you just want to see what would be executed." ;; install) $ECHO \ "Usage: $progname [OPTION]... --mode=install INSTALL-COMMAND... Install executables or libraries. INSTALL-COMMAND is the installation command. The first component should be either the 'install' or 'cp' program. The following components of INSTALL-COMMAND are treated specially: -inst-prefix-dir PREFIX-DIR Use PREFIX-DIR as a staging area for installation The rest of the components are interpreted as arguments to that command (only BSD-compatible install options are recognized)." ;; link) $ECHO \ "Usage: $progname [OPTION]... --mode=link LINK-COMMAND... Link object files or libraries together to form another library, or to create an executable program. LINK-COMMAND is a command using the C compiler that you would use to create a program from several object files. The following components of LINK-COMMAND are treated specially: -all-static do not do any dynamic linking at all -avoid-version do not add a version suffix if possible -bindir BINDIR specify path to binaries directory (for systems where libraries must be found in the PATH setting at runtime) -dlopen FILE '-dlpreopen' FILE if it cannot be dlopened at runtime -dlpreopen FILE link in FILE and add its symbols to lt_preloaded_symbols -export-dynamic allow symbols from OUTPUT-FILE to be resolved with dlsym(3) -export-symbols SYMFILE try to export only the symbols listed in SYMFILE -export-symbols-regex REGEX try to export only the symbols matching REGEX -LLIBDIR search LIBDIR for required installed libraries -lNAME OUTPUT-FILE requires the installed library libNAME -module build a library that can dlopened -no-fast-install disable the fast-install mode -no-install link a not-installable executable -no-undefined declare that a library does not refer to external symbols -o OUTPUT-FILE create OUTPUT-FILE from the specified objects -objectlist FILE use a list of object files found in FILE to specify objects -os2dllname NAME force a short DLL name on OS/2 (no effect on other OSes) -precious-files-regex REGEX don't remove output files matching REGEX -release RELEASE specify package release information -rpath LIBDIR the created library will eventually be installed in LIBDIR -R[ ]LIBDIR add LIBDIR to the runtime path of programs and libraries -shared only do dynamic linking of libtool libraries -shrext SUFFIX override the standard shared library file extension -static do not do any dynamic linking of uninstalled libtool libraries -static-libtool-libs do not do any dynamic linking of libtool libraries -version-info CURRENT[:REVISION[:AGE]] specify library version info [each variable defaults to 0] -weak LIBNAME declare that the target provides the LIBNAME interface -Wc,FLAG -Xcompiler FLAG pass linker-specific FLAG directly to the compiler -Wl,FLAG -Xlinker FLAG pass linker-specific FLAG directly to the linker -XCClinker FLAG pass link-specific FLAG to the compiler driver (CC) All other options (arguments beginning with '-') are ignored. Every other argument is treated as a filename. Files ending in '.la' are treated as uninstalled libtool libraries, other files are standard or library object files. If the OUTPUT-FILE ends in '.la', then a libtool library is created, only library objects ('.lo' files) may be specified, and '-rpath' is required, except when creating a convenience library. If OUTPUT-FILE ends in '.a' or '.lib', then a standard library is created using 'ar' and 'ranlib', or on Windows using 'lib'. If OUTPUT-FILE ends in '.lo' or '.$objext', then a reloadable object file is created, otherwise an executable program is created." ;; uninstall) $ECHO \ "Usage: $progname [OPTION]... --mode=uninstall RM [RM-OPTION]... FILE... Remove libraries from an installation directory. RM is the name of the program to use to delete files associated with each FILE (typically '/bin/rm'). RM-OPTIONS are options (such as '-f') to be passed to RM. If FILE is a libtool library, all the files associated with it are deleted. Otherwise, only FILE itself is deleted using RM." ;; *) func_fatal_help "invalid operation mode '$opt_mode'" ;; esac echo $ECHO "Try '$progname --help' for more information about other modes." } # Now that we've collected a possible --mode arg, show help if necessary if $opt_help; then if test : = "$opt_help"; then func_mode_help else { func_help noexit for opt_mode in compile link execute install finish uninstall clean; do func_mode_help done } | $SED -n '1p; 2,$s/^Usage:/ or: /p' { func_help noexit for opt_mode in compile link execute install finish uninstall clean; do echo func_mode_help done } | $SED '1d /^When reporting/,/^Report/{ H d } $x /information about other modes/d /more detailed .*MODE/d s/^Usage:.*--mode=\([^ ]*\) .*/Description of \1 mode:/' fi exit $? fi # func_mode_execute arg... func_mode_execute () { $debug_cmd # The first argument is the command name. cmd=$nonopt test -z "$cmd" && \ func_fatal_help "you must specify a COMMAND" # Handle -dlopen flags immediately. for file in $opt_dlopen; do test -f "$file" \ || func_fatal_help "'$file' is not a file" dir= case $file in *.la) func_resolve_sysroot "$file" file=$func_resolve_sysroot_result # Check to see that this really is a libtool archive. func_lalib_unsafe_p "$file" \ || func_fatal_help "'$lib' is not a valid libtool archive" # Read the libtool library. dlname= library_names= func_source "$file" # Skip this library if it cannot be dlopened. if test -z "$dlname"; then # Warn if it was a shared library. test -n "$library_names" && \ func_warning "'$file' was not linked with '-export-dynamic'" continue fi func_dirname "$file" "" "." dir=$func_dirname_result if test -f "$dir/$objdir/$dlname"; then func_append dir "/$objdir" else if test ! -f "$dir/$dlname"; then func_fatal_error "cannot find '$dlname' in '$dir' or '$dir/$objdir'" fi fi ;; *.lo) # Just add the directory containing the .lo file. func_dirname "$file" "" "." dir=$func_dirname_result ;; *) func_warning "'-dlopen' is ignored for non-libtool libraries and objects" continue ;; esac # Get the absolute pathname. absdir=`cd "$dir" && pwd` test -n "$absdir" && dir=$absdir # Now add the directory to shlibpath_var. if eval "test -z \"\$$shlibpath_var\""; then eval "$shlibpath_var=\"\$dir\"" else eval "$shlibpath_var=\"\$dir:\$$shlibpath_var\"" fi done # This variable tells wrapper scripts just to set shlibpath_var # rather than running their programs. libtool_execute_magic=$magic # Check if any of the arguments is a wrapper script. args= for file do case $file in -* | *.la | *.lo ) ;; *) # Do a test to see if this is really a libtool program. if func_ltwrapper_script_p "$file"; then func_source "$file" # Transform arg to wrapped name. file=$progdir/$program elif func_ltwrapper_executable_p "$file"; then func_ltwrapper_scriptname "$file" func_source "$func_ltwrapper_scriptname_result" # Transform arg to wrapped name. file=$progdir/$program fi ;; esac # Quote arguments (to preserve shell metacharacters). func_append_quoted args "$file" done if $opt_dry_run; then # Display what would be done. if test -n "$shlibpath_var"; then eval "\$ECHO \"\$shlibpath_var=\$$shlibpath_var\"" echo "export $shlibpath_var" fi $ECHO "$cmd$args" exit $EXIT_SUCCESS else if test -n "$shlibpath_var"; then # Export the shlibpath_var. eval "export $shlibpath_var" fi # Restore saved environment variables for lt_var in LANG LANGUAGE LC_ALL LC_CTYPE LC_COLLATE LC_MESSAGES do eval "if test \"\${save_$lt_var+set}\" = set; then $lt_var=\$save_$lt_var; export $lt_var else $lt_unset $lt_var fi" done # Now prepare to actually exec the command. exec_cmd=\$cmd$args fi } test execute = "$opt_mode" && func_mode_execute ${1+"$@"} # func_mode_finish arg... func_mode_finish () { $debug_cmd libs= libdirs= admincmds= for opt in "$nonopt" ${1+"$@"} do if test -d "$opt"; then func_append libdirs " $opt" elif test -f "$opt"; then if func_lalib_unsafe_p "$opt"; then func_append libs " $opt" else func_warning "'$opt' is not a valid libtool archive" fi else func_fatal_error "invalid argument '$opt'" fi done if test -n "$libs"; then if test -n "$lt_sysroot"; then sysroot_regex=`$ECHO "$lt_sysroot" | $SED "$sed_make_literal_regex"` sysroot_cmd="s/\([ ']\)$sysroot_regex/\1/g;" else sysroot_cmd= fi # Remove sysroot references if $opt_dry_run; then for lib in $libs; do echo "removing references to $lt_sysroot and '=' prefixes from $lib" done else tmpdir=`func_mktempdir` for lib in $libs; do $SED -e "$sysroot_cmd s/\([ ']-[LR]\)=/\1/g; s/\([ ']\)=/\1/g" $lib \ > $tmpdir/tmp-la mv -f $tmpdir/tmp-la $lib done ${RM}r "$tmpdir" fi fi if test -n "$finish_cmds$finish_eval" && test -n "$libdirs"; then for libdir in $libdirs; do if test -n "$finish_cmds"; then # Do each command in the finish commands. func_execute_cmds "$finish_cmds" 'admincmds="$admincmds '"$cmd"'"' fi if test -n "$finish_eval"; then # Do the single finish_eval. eval cmds=\"$finish_eval\" $opt_dry_run || eval "$cmds" || func_append admincmds " $cmds" fi done fi # Exit here if they wanted silent mode. $opt_quiet && exit $EXIT_SUCCESS if test -n "$finish_cmds$finish_eval" && test -n "$libdirs"; then echo "----------------------------------------------------------------------" echo "Libraries have been installed in:" for libdir in $libdirs; do $ECHO " $libdir" done echo echo "If you ever happen to want to link against installed libraries" echo "in a given directory, LIBDIR, you must either use libtool, and" echo "specify the full pathname of the library, or use the '-LLIBDIR'" echo "flag during linking and do at least one of the following:" if test -n "$shlibpath_var"; then echo " - add LIBDIR to the '$shlibpath_var' environment variable" echo " during execution" fi if test -n "$runpath_var"; then echo " - add LIBDIR to the '$runpath_var' environment variable" echo " during linking" fi if test -n "$hardcode_libdir_flag_spec"; then libdir=LIBDIR eval flag=\"$hardcode_libdir_flag_spec\" $ECHO " - use the '$flag' linker flag" fi if test -n "$admincmds"; then $ECHO " - have your system administrator run these commands:$admincmds" fi if test -f /etc/ld.so.conf; then echo " - have your system administrator add LIBDIR to '/etc/ld.so.conf'" fi echo echo "See any operating system documentation about shared libraries for" case $host in solaris2.[6789]|solaris2.1[0-9]) echo "more information, such as the ld(1), crle(1) and ld.so(8) manual" echo "pages." ;; *) echo "more information, such as the ld(1) and ld.so(8) manual pages." ;; esac echo "----------------------------------------------------------------------" fi exit $EXIT_SUCCESS } test finish = "$opt_mode" && func_mode_finish ${1+"$@"} # func_mode_install arg... func_mode_install () { $debug_cmd # There may be an optional sh(1) argument at the beginning of # install_prog (especially on Windows NT). if test "$SHELL" = "$nonopt" || test /bin/sh = "$nonopt" || # Allow the use of GNU shtool's install command. case $nonopt in *shtool*) :;; *) false;; esac then # Aesthetically quote it. func_quote_for_eval "$nonopt" install_prog="$func_quote_for_eval_result " arg=$1 shift else install_prog= arg=$nonopt fi # The real first argument should be the name of the installation program. # Aesthetically quote it. func_quote_for_eval "$arg" func_append install_prog "$func_quote_for_eval_result" install_shared_prog=$install_prog case " $install_prog " in *[\\\ /]cp\ *) install_cp=: ;; *) install_cp=false ;; esac # We need to accept at least all the BSD install flags. dest= files= opts= prev= install_type= isdir=false stripme= no_mode=: for arg do arg2= if test -n "$dest"; then func_append files " $dest" dest=$arg continue fi case $arg in -d) isdir=: ;; -f) if $install_cp; then :; else prev=$arg fi ;; -g | -m | -o) prev=$arg ;; -s) stripme=" -s" continue ;; -*) ;; *) # If the previous option needed an argument, then skip it. if test -n "$prev"; then if test X-m = "X$prev" && test -n "$install_override_mode"; then arg2=$install_override_mode no_mode=false fi prev= else dest=$arg continue fi ;; esac # Aesthetically quote the argument. func_quote_for_eval "$arg" func_append install_prog " $func_quote_for_eval_result" if test -n "$arg2"; then func_quote_for_eval "$arg2" fi func_append install_shared_prog " $func_quote_for_eval_result" done test -z "$install_prog" && \ func_fatal_help "you must specify an install program" test -n "$prev" && \ func_fatal_help "the '$prev' option requires an argument" if test -n "$install_override_mode" && $no_mode; then if $install_cp; then :; else func_quote_for_eval "$install_override_mode" func_append install_shared_prog " -m $func_quote_for_eval_result" fi fi if test -z "$files"; then if test -z "$dest"; then func_fatal_help "no file or destination specified" else func_fatal_help "you must specify a destination" fi fi # Strip any trailing slash from the destination. func_stripname '' '/' "$dest" dest=$func_stripname_result # Check to see that the destination is a directory. test -d "$dest" && isdir=: if $isdir; then destdir=$dest destname= else func_dirname_and_basename "$dest" "" "." destdir=$func_dirname_result destname=$func_basename_result # Not a directory, so check to see that there is only one file specified. set dummy $files; shift test "$#" -gt 1 && \ func_fatal_help "'$dest' is not a directory" fi case $destdir in [\\/]* | [A-Za-z]:[\\/]*) ;; *) for file in $files; do case $file in *.lo) ;; *) func_fatal_help "'$destdir' must be an absolute directory name" ;; esac done ;; esac # This variable tells wrapper scripts just to set variables rather # than running their programs. libtool_install_magic=$magic staticlibs= future_libdirs= current_libdirs= for file in $files; do # Do each installation. case $file in *.$libext) # Do the static libraries later. func_append staticlibs " $file" ;; *.la) func_resolve_sysroot "$file" file=$func_resolve_sysroot_result # Check to see that this really is a libtool archive. func_lalib_unsafe_p "$file" \ || func_fatal_help "'$file' is not a valid libtool archive" library_names= old_library= relink_command= func_source "$file" # Add the libdir to current_libdirs if it is the destination. if test "X$destdir" = "X$libdir"; then case "$current_libdirs " in *" $libdir "*) ;; *) func_append current_libdirs " $libdir" ;; esac else # Note the libdir as a future libdir. case "$future_libdirs " in *" $libdir "*) ;; *) func_append future_libdirs " $libdir" ;; esac fi func_dirname "$file" "/" "" dir=$func_dirname_result func_append dir "$objdir" if test -n "$relink_command"; then # Determine the prefix the user has applied to our future dir. inst_prefix_dir=`$ECHO "$destdir" | $SED -e "s%$libdir\$%%"` # Don't allow the user to place us outside of our expected # location b/c this prevents finding dependent libraries that # are installed to the same prefix. # At present, this check doesn't affect windows .dll's that # are installed into $libdir/../bin (currently, that works fine) # but it's something to keep an eye on. test "$inst_prefix_dir" = "$destdir" && \ func_fatal_error "error: cannot install '$file' to a directory not ending in $libdir" if test -n "$inst_prefix_dir"; then # Stick the inst_prefix_dir data into the link command. relink_command=`$ECHO "$relink_command" | $SED "s%@inst_prefix_dir@%-inst-prefix-dir $inst_prefix_dir%"` else relink_command=`$ECHO "$relink_command" | $SED "s%@inst_prefix_dir@%%"` fi func_warning "relinking '$file'" func_show_eval "$relink_command" \ 'func_fatal_error "error: relink '\''$file'\'' with the above command before installing it"' fi # See the names of the shared library. set dummy $library_names; shift if test -n "$1"; then realname=$1 shift srcname=$realname test -n "$relink_command" && srcname=${realname}T # Install the shared library and build the symlinks. func_show_eval "$install_shared_prog $dir/$srcname $destdir/$realname" \ 'exit $?' tstripme=$stripme case $host_os in cygwin* | mingw* | pw32* | cegcc*) case $realname in *.dll.a) tstripme= ;; esac ;; os2*) case $realname in *_dll.a) tstripme= ;; esac ;; esac if test -n "$tstripme" && test -n "$striplib"; then func_show_eval "$striplib $destdir/$realname" 'exit $?' fi if test "$#" -gt 0; then # Delete the old symlinks, and create new ones. # Try 'ln -sf' first, because the 'ln' binary might depend on # the symlink we replace! Solaris /bin/ln does not understand -f, # so we also need to try rm && ln -s. for linkname do test "$linkname" != "$realname" \ && func_show_eval "(cd $destdir && { $LN_S -f $realname $linkname || { $RM $linkname && $LN_S $realname $linkname; }; })" done fi # Do each command in the postinstall commands. lib=$destdir/$realname func_execute_cmds "$postinstall_cmds" 'exit $?' fi # Install the pseudo-library for information purposes. func_basename "$file" name=$func_basename_result instname=$dir/${name}i func_show_eval "$install_prog $instname $destdir/$name" 'exit $?' # Maybe install the static library, too. test -n "$old_library" && func_append staticlibs " $dir/$old_library" ;; *.lo) # Install (i.e. copy) a libtool object. # Figure out destination file name, if it wasn't already specified. if test -n "$destname"; then destfile=$destdir/$destname else func_basename "$file" destfile=$func_basename_result destfile=$destdir/$destfile fi # Deduce the name of the destination old-style object file. case $destfile in *.lo) func_lo2o "$destfile" staticdest=$func_lo2o_result ;; *.$objext) staticdest=$destfile destfile= ;; *) func_fatal_help "cannot copy a libtool object to '$destfile'" ;; esac # Install the libtool object if requested. test -n "$destfile" && \ func_show_eval "$install_prog $file $destfile" 'exit $?' # Install the old object if enabled. if test yes = "$build_old_libs"; then # Deduce the name of the old-style object file. func_lo2o "$file" staticobj=$func_lo2o_result func_show_eval "$install_prog \$staticobj \$staticdest" 'exit $?' fi exit $EXIT_SUCCESS ;; *) # Figure out destination file name, if it wasn't already specified. if test -n "$destname"; then destfile=$destdir/$destname else func_basename "$file" destfile=$func_basename_result destfile=$destdir/$destfile fi # If the file is missing, and there is a .exe on the end, strip it # because it is most likely a libtool script we actually want to # install stripped_ext= case $file in *.exe) if test ! -f "$file"; then func_stripname '' '.exe' "$file" file=$func_stripname_result stripped_ext=.exe fi ;; esac # Do a test to see if this is really a libtool program. case $host in *cygwin* | *mingw*) if func_ltwrapper_executable_p "$file"; then func_ltwrapper_scriptname "$file" wrapper=$func_ltwrapper_scriptname_result else func_stripname '' '.exe' "$file" wrapper=$func_stripname_result fi ;; *) wrapper=$file ;; esac if func_ltwrapper_script_p "$wrapper"; then notinst_deplibs= relink_command= func_source "$wrapper" # Check the variables that should have been set. test -z "$generated_by_libtool_version" && \ func_fatal_error "invalid libtool wrapper script '$wrapper'" finalize=: for lib in $notinst_deplibs; do # Check to see that each library is installed. libdir= if test -f "$lib"; then func_source "$lib" fi libfile=$libdir/`$ECHO "$lib" | $SED 's%^.*/%%g'` if test -n "$libdir" && test ! -f "$libfile"; then func_warning "'$lib' has not been installed in '$libdir'" finalize=false fi done relink_command= func_source "$wrapper" outputname= if test no = "$fast_install" && test -n "$relink_command"; then $opt_dry_run || { if $finalize; then tmpdir=`func_mktempdir` func_basename "$file$stripped_ext" file=$func_basename_result outputname=$tmpdir/$file # Replace the output file specification. relink_command=`$ECHO "$relink_command" | $SED 's%@OUTPUT@%'"$outputname"'%g'` $opt_quiet || { func_quote_for_expand "$relink_command" eval "func_echo $func_quote_for_expand_result" } if eval "$relink_command"; then : else func_error "error: relink '$file' with the above command before installing it" $opt_dry_run || ${RM}r "$tmpdir" continue fi file=$outputname else func_warning "cannot relink '$file'" fi } else # Install the binary that we compiled earlier. file=`$ECHO "$file$stripped_ext" | $SED "s%\([^/]*\)$%$objdir/\1%"` fi fi # remove .exe since cygwin /usr/bin/install will append another # one anyway case $install_prog,$host in */usr/bin/install*,*cygwin*) case $file:$destfile in *.exe:*.exe) # this is ok ;; *.exe:*) destfile=$destfile.exe ;; *:*.exe) func_stripname '' '.exe' "$destfile" destfile=$func_stripname_result ;; esac ;; esac func_show_eval "$install_prog\$stripme \$file \$destfile" 'exit $?' $opt_dry_run || if test -n "$outputname"; then ${RM}r "$tmpdir" fi ;; esac done for file in $staticlibs; do func_basename "$file" name=$func_basename_result # Set up the ranlib parameters. oldlib=$destdir/$name func_to_tool_file "$oldlib" func_convert_file_msys_to_w32 tool_oldlib=$func_to_tool_file_result func_show_eval "$install_prog \$file \$oldlib" 'exit $?' if test -n "$stripme" && test -n "$old_striplib"; then func_show_eval "$old_striplib $tool_oldlib" 'exit $?' fi # Do each command in the postinstall commands. func_execute_cmds "$old_postinstall_cmds" 'exit $?' done test -n "$future_libdirs" && \ func_warning "remember to run '$progname --finish$future_libdirs'" if test -n "$current_libdirs"; then # Maybe just do a dry run. $opt_dry_run && current_libdirs=" -n$current_libdirs" exec_cmd='$SHELL "$progpath" $preserve_args --finish$current_libdirs' else exit $EXIT_SUCCESS fi } test install = "$opt_mode" && func_mode_install ${1+"$@"} # func_generate_dlsyms outputname originator pic_p # Extract symbols from dlprefiles and create ${outputname}S.o with # a dlpreopen symbol table. func_generate_dlsyms () { $debug_cmd my_outputname=$1 my_originator=$2 my_pic_p=${3-false} my_prefix=`$ECHO "$my_originator" | $SED 's%[^a-zA-Z0-9]%_%g'` my_dlsyms= if test -n "$dlfiles$dlprefiles" || test no != "$dlself"; then if test -n "$NM" && test -n "$global_symbol_pipe"; then my_dlsyms=${my_outputname}S.c else func_error "not configured to extract global symbols from dlpreopened files" fi fi if test -n "$my_dlsyms"; then case $my_dlsyms in "") ;; *.c) # Discover the nlist of each of the dlfiles. nlist=$output_objdir/$my_outputname.nm func_show_eval "$RM $nlist ${nlist}S ${nlist}T" # Parse the name list into a source file. func_verbose "creating $output_objdir/$my_dlsyms" $opt_dry_run || $ECHO > "$output_objdir/$my_dlsyms" "\ /* $my_dlsyms - symbol resolution table for '$my_outputname' dlsym emulation. */ /* Generated by $PROGRAM (GNU $PACKAGE) $VERSION */ #ifdef __cplusplus extern \"C\" { #endif #if defined __GNUC__ && (((__GNUC__ == 4) && (__GNUC_MINOR__ >= 4)) || (__GNUC__ > 4)) #pragma GCC diagnostic ignored \"-Wstrict-prototypes\" #endif /* Keep this code in sync between libtool.m4, ltmain, lt_system.h, and tests. */ #if defined _WIN32 || defined __CYGWIN__ || defined _WIN32_WCE /* DATA imports from DLLs on WIN32 can't be const, because runtime relocations are performed -- see ld's documentation on pseudo-relocs. */ # define LT_DLSYM_CONST #elif defined __osf__ /* This system does not cope well with relocations in const data. */ # define LT_DLSYM_CONST #else # define LT_DLSYM_CONST const #endif #define STREQ(s1, s2) (strcmp ((s1), (s2)) == 0) /* External symbol declarations for the compiler. */\ " if test yes = "$dlself"; then func_verbose "generating symbol list for '$output'" $opt_dry_run || echo ': @PROGRAM@ ' > "$nlist" # Add our own program objects to the symbol list. progfiles=`$ECHO "$objs$old_deplibs" | $SP2NL | $SED "$lo2o" | $NL2SP` for progfile in $progfiles; do func_to_tool_file "$progfile" func_convert_file_msys_to_w32 func_verbose "extracting global C symbols from '$func_to_tool_file_result'" $opt_dry_run || eval "$NM $func_to_tool_file_result | $global_symbol_pipe >> '$nlist'" done if test -n "$exclude_expsyms"; then $opt_dry_run || { eval '$EGREP -v " ($exclude_expsyms)$" "$nlist" > "$nlist"T' eval '$MV "$nlist"T "$nlist"' } fi if test -n "$export_symbols_regex"; then $opt_dry_run || { eval '$EGREP -e "$export_symbols_regex" "$nlist" > "$nlist"T' eval '$MV "$nlist"T "$nlist"' } fi # Prepare the list of exported symbols if test -z "$export_symbols"; then export_symbols=$output_objdir/$outputname.exp $opt_dry_run || { $RM $export_symbols eval "$SED -n -e '/^: @PROGRAM@ $/d' -e 's/^.* \(.*\)$/\1/p' "'< "$nlist" > "$export_symbols"' case $host in *cygwin* | *mingw* | *cegcc* ) eval "echo EXPORTS "'> "$output_objdir/$outputname.def"' eval 'cat "$export_symbols" >> "$output_objdir/$outputname.def"' ;; esac } else $opt_dry_run || { eval "$SED -e 's/\([].[*^$]\)/\\\\\1/g' -e 's/^/ /' -e 's/$/$/'"' < "$export_symbols" > "$output_objdir/$outputname.exp"' eval '$GREP -f "$output_objdir/$outputname.exp" < "$nlist" > "$nlist"T' eval '$MV "$nlist"T "$nlist"' case $host in *cygwin* | *mingw* | *cegcc* ) eval "echo EXPORTS "'> "$output_objdir/$outputname.def"' eval 'cat "$nlist" >> "$output_objdir/$outputname.def"' ;; esac } fi fi for dlprefile in $dlprefiles; do func_verbose "extracting global C symbols from '$dlprefile'" func_basename "$dlprefile" name=$func_basename_result case $host in *cygwin* | *mingw* | *cegcc* ) # if an import library, we need to obtain dlname if func_win32_import_lib_p "$dlprefile"; then func_tr_sh "$dlprefile" eval "curr_lafile=\$libfile_$func_tr_sh_result" dlprefile_dlbasename= if test -n "$curr_lafile" && func_lalib_p "$curr_lafile"; then # Use subshell, to avoid clobbering current variable values dlprefile_dlname=`source "$curr_lafile" && echo "$dlname"` if test -n "$dlprefile_dlname"; then func_basename "$dlprefile_dlname" dlprefile_dlbasename=$func_basename_result else # no lafile. user explicitly requested -dlpreopen . $sharedlib_from_linklib_cmd "$dlprefile" dlprefile_dlbasename=$sharedlib_from_linklib_result fi fi $opt_dry_run || { if test -n "$dlprefile_dlbasename"; then eval '$ECHO ": $dlprefile_dlbasename" >> "$nlist"' else func_warning "Could not compute DLL name from $name" eval '$ECHO ": $name " >> "$nlist"' fi func_to_tool_file "$dlprefile" func_convert_file_msys_to_w32 eval "$NM \"$func_to_tool_file_result\" 2>/dev/null | $global_symbol_pipe | $SED -e '/I __imp/d' -e 's/I __nm_/D /;s/_nm__//' >> '$nlist'" } else # not an import lib $opt_dry_run || { eval '$ECHO ": $name " >> "$nlist"' func_to_tool_file "$dlprefile" func_convert_file_msys_to_w32 eval "$NM \"$func_to_tool_file_result\" 2>/dev/null | $global_symbol_pipe >> '$nlist'" } fi ;; *) $opt_dry_run || { eval '$ECHO ": $name " >> "$nlist"' func_to_tool_file "$dlprefile" func_convert_file_msys_to_w32 eval "$NM \"$func_to_tool_file_result\" 2>/dev/null | $global_symbol_pipe >> '$nlist'" } ;; esac done $opt_dry_run || { # Make sure we have at least an empty file. test -f "$nlist" || : > "$nlist" if test -n "$exclude_expsyms"; then $EGREP -v " ($exclude_expsyms)$" "$nlist" > "$nlist"T $MV "$nlist"T "$nlist" fi # Try sorting and uniquifying the output. if $GREP -v "^: " < "$nlist" | if sort -k 3 /dev/null 2>&1; then sort -k 3 else sort +2 fi | uniq > "$nlist"S; then : else $GREP -v "^: " < "$nlist" > "$nlist"S fi if test -f "$nlist"S; then eval "$global_symbol_to_cdecl"' < "$nlist"S >> "$output_objdir/$my_dlsyms"' else echo '/* NONE */' >> "$output_objdir/$my_dlsyms" fi func_show_eval '$RM "${nlist}I"' if test -n "$global_symbol_to_import"; then eval "$global_symbol_to_import"' < "$nlist"S > "$nlist"I' fi echo >> "$output_objdir/$my_dlsyms" "\ /* The mapping between symbol names and symbols. */ typedef struct { const char *name; void *address; } lt_dlsymlist; extern LT_DLSYM_CONST lt_dlsymlist lt_${my_prefix}_LTX_preloaded_symbols[];\ " if test -s "$nlist"I; then echo >> "$output_objdir/$my_dlsyms" "\ static void lt_syminit(void) { LT_DLSYM_CONST lt_dlsymlist *symbol = lt_${my_prefix}_LTX_preloaded_symbols; for (; symbol->name; ++symbol) {" $SED 's/.*/ if (STREQ (symbol->name, \"&\")) symbol->address = (void *) \&&;/' < "$nlist"I >> "$output_objdir/$my_dlsyms" echo >> "$output_objdir/$my_dlsyms" "\ } }" fi echo >> "$output_objdir/$my_dlsyms" "\ LT_DLSYM_CONST lt_dlsymlist lt_${my_prefix}_LTX_preloaded_symbols[] = { {\"$my_originator\", (void *) 0}," if test -s "$nlist"I; then echo >> "$output_objdir/$my_dlsyms" "\ {\"@INIT@\", (void *) <_syminit}," fi case $need_lib_prefix in no) eval "$global_symbol_to_c_name_address" < "$nlist" >> "$output_objdir/$my_dlsyms" ;; *) eval "$global_symbol_to_c_name_address_lib_prefix" < "$nlist" >> "$output_objdir/$my_dlsyms" ;; esac echo >> "$output_objdir/$my_dlsyms" "\ {0, (void *) 0} }; /* This works around a problem in FreeBSD linker */ #ifdef FREEBSD_WORKAROUND static const void *lt_preloaded_setup() { return lt_${my_prefix}_LTX_preloaded_symbols; } #endif #ifdef __cplusplus } #endif\ " } # !$opt_dry_run pic_flag_for_symtable= case "$compile_command " in *" -static "*) ;; *) case $host in # compiling the symbol table file with pic_flag works around # a FreeBSD bug that causes programs to crash when -lm is # linked before any other PIC object. But we must not use # pic_flag when linking with -static. The problem exists in # FreeBSD 2.2.6 and is fixed in FreeBSD 3.1. *-*-freebsd2.*|*-*-freebsd3.0*|*-*-freebsdelf3.0*) pic_flag_for_symtable=" $pic_flag -DFREEBSD_WORKAROUND" ;; *-*-hpux*) pic_flag_for_symtable=" $pic_flag" ;; *) $my_pic_p && pic_flag_for_symtable=" $pic_flag" ;; esac ;; esac symtab_cflags= for arg in $LTCFLAGS; do case $arg in -pie | -fpie | -fPIE) ;; *) func_append symtab_cflags " $arg" ;; esac done # Now compile the dynamic symbol file. func_show_eval '(cd $output_objdir && $LTCC$symtab_cflags -c$no_builtin_flag$pic_flag_for_symtable "$my_dlsyms")' 'exit $?' # Clean up the generated files. func_show_eval '$RM "$output_objdir/$my_dlsyms" "$nlist" "${nlist}S" "${nlist}T" "${nlist}I"' # Transform the symbol file into the correct name. symfileobj=$output_objdir/${my_outputname}S.$objext case $host in *cygwin* | *mingw* | *cegcc* ) if test -f "$output_objdir/$my_outputname.def"; then compile_command=`$ECHO "$compile_command" | $SED "s%@SYMFILE@%$output_objdir/$my_outputname.def $symfileobj%"` finalize_command=`$ECHO "$finalize_command" | $SED "s%@SYMFILE@%$output_objdir/$my_outputname.def $symfileobj%"` else compile_command=`$ECHO "$compile_command" | $SED "s%@SYMFILE@%$symfileobj%"` finalize_command=`$ECHO "$finalize_command" | $SED "s%@SYMFILE@%$symfileobj%"` fi ;; *) compile_command=`$ECHO "$compile_command" | $SED "s%@SYMFILE@%$symfileobj%"` finalize_command=`$ECHO "$finalize_command" | $SED "s%@SYMFILE@%$symfileobj%"` ;; esac ;; *) func_fatal_error "unknown suffix for '$my_dlsyms'" ;; esac else # We keep going just in case the user didn't refer to # lt_preloaded_symbols. The linker will fail if global_symbol_pipe # really was required. # Nullify the symbol file. compile_command=`$ECHO "$compile_command" | $SED "s% @SYMFILE@%%"` finalize_command=`$ECHO "$finalize_command" | $SED "s% @SYMFILE@%%"` fi } # func_cygming_gnu_implib_p ARG # This predicate returns with zero status (TRUE) if # ARG is a GNU/binutils-style import library. Returns # with nonzero status (FALSE) otherwise. func_cygming_gnu_implib_p () { $debug_cmd func_to_tool_file "$1" func_convert_file_msys_to_w32 func_cygming_gnu_implib_tmp=`$NM "$func_to_tool_file_result" | eval "$global_symbol_pipe" | $EGREP ' (_head_[A-Za-z0-9_]+_[ad]l*|[A-Za-z0-9_]+_[ad]l*_iname)$'` test -n "$func_cygming_gnu_implib_tmp" } # func_cygming_ms_implib_p ARG # This predicate returns with zero status (TRUE) if # ARG is an MS-style import library. Returns # with nonzero status (FALSE) otherwise. func_cygming_ms_implib_p () { $debug_cmd func_to_tool_file "$1" func_convert_file_msys_to_w32 func_cygming_ms_implib_tmp=`$NM "$func_to_tool_file_result" | eval "$global_symbol_pipe" | $GREP '_NULL_IMPORT_DESCRIPTOR'` test -n "$func_cygming_ms_implib_tmp" } # func_win32_libid arg # return the library type of file 'arg' # # Need a lot of goo to handle *both* DLLs and import libs # Has to be a shell function in order to 'eat' the argument # that is supplied when $file_magic_command is called. # Despite the name, also deal with 64 bit binaries. func_win32_libid () { $debug_cmd win32_libid_type=unknown win32_fileres=`file -L $1 2>/dev/null` case $win32_fileres in *ar\ archive\ import\ library*) # definitely import win32_libid_type="x86 archive import" ;; *ar\ archive*) # could be an import, or static # Keep the egrep pattern in sync with the one in _LT_CHECK_MAGIC_METHOD. if eval $OBJDUMP -f $1 | $SED -e '10q' 2>/dev/null | $EGREP 'file format (pei*-i386(.*architecture: i386)?|pe-arm-wince|pe-x86-64)' >/dev/null; then case $nm_interface in "MS dumpbin") if func_cygming_ms_implib_p "$1" || func_cygming_gnu_implib_p "$1" then win32_nmres=import else win32_nmres= fi ;; *) func_to_tool_file "$1" func_convert_file_msys_to_w32 win32_nmres=`eval $NM -f posix -A \"$func_to_tool_file_result\" | $SED -n -e ' 1,100{ / I /{ s|.*|import| p q } }'` ;; esac case $win32_nmres in import*) win32_libid_type="x86 archive import";; *) win32_libid_type="x86 archive static";; esac fi ;; *DLL*) win32_libid_type="x86 DLL" ;; *executable*) # but shell scripts are "executable" too... case $win32_fileres in *MS\ Windows\ PE\ Intel*) win32_libid_type="x86 DLL" ;; esac ;; esac $ECHO "$win32_libid_type" } # func_cygming_dll_for_implib ARG # # Platform-specific function to extract the # name of the DLL associated with the specified # import library ARG. # Invoked by eval'ing the libtool variable # $sharedlib_from_linklib_cmd # Result is available in the variable # $sharedlib_from_linklib_result func_cygming_dll_for_implib () { $debug_cmd sharedlib_from_linklib_result=`$DLLTOOL --identify-strict --identify "$1"` } # func_cygming_dll_for_implib_fallback_core SECTION_NAME LIBNAMEs # # The is the core of a fallback implementation of a # platform-specific function to extract the name of the # DLL associated with the specified import library LIBNAME. # # SECTION_NAME is either .idata$6 or .idata$7, depending # on the platform and compiler that created the implib. # # Echos the name of the DLL associated with the # specified import library. func_cygming_dll_for_implib_fallback_core () { $debug_cmd match_literal=`$ECHO "$1" | $SED "$sed_make_literal_regex"` $OBJDUMP -s --section "$1" "$2" 2>/dev/null | $SED '/^Contents of section '"$match_literal"':/{ # Place marker at beginning of archive member dllname section s/.*/====MARK====/ p d } # These lines can sometimes be longer than 43 characters, but # are always uninteresting /:[ ]*file format pe[i]\{,1\}-/d /^In archive [^:]*:/d # Ensure marker is printed /^====MARK====/p # Remove all lines with less than 43 characters /^.\{43\}/!d # From remaining lines, remove first 43 characters s/^.\{43\}//' | $SED -n ' # Join marker and all lines until next marker into a single line /^====MARK====/ b para H $ b para b :para x s/\n//g # Remove the marker s/^====MARK====// # Remove trailing dots and whitespace s/[\. \t]*$// # Print /./p' | # we now have a list, one entry per line, of the stringified # contents of the appropriate section of all members of the # archive that possess that section. Heuristic: eliminate # all those that have a first or second character that is # a '.' (that is, objdump's representation of an unprintable # character.) This should work for all archives with less than # 0x302f exports -- but will fail for DLLs whose name actually # begins with a literal '.' or a single character followed by # a '.'. # # Of those that remain, print the first one. $SED -e '/^\./d;/^.\./d;q' } # func_cygming_dll_for_implib_fallback ARG # Platform-specific function to extract the # name of the DLL associated with the specified # import library ARG. # # This fallback implementation is for use when $DLLTOOL # does not support the --identify-strict option. # Invoked by eval'ing the libtool variable # $sharedlib_from_linklib_cmd # Result is available in the variable # $sharedlib_from_linklib_result func_cygming_dll_for_implib_fallback () { $debug_cmd if func_cygming_gnu_implib_p "$1"; then # binutils import library sharedlib_from_linklib_result=`func_cygming_dll_for_implib_fallback_core '.idata$7' "$1"` elif func_cygming_ms_implib_p "$1"; then # ms-generated import library sharedlib_from_linklib_result=`func_cygming_dll_for_implib_fallback_core '.idata$6' "$1"` else # unknown sharedlib_from_linklib_result= fi } # func_extract_an_archive dir oldlib func_extract_an_archive () { $debug_cmd f_ex_an_ar_dir=$1; shift f_ex_an_ar_oldlib=$1 if test yes = "$lock_old_archive_extraction"; then lockfile=$f_ex_an_ar_oldlib.lock until $opt_dry_run || ln "$progpath" "$lockfile" 2>/dev/null; do func_echo "Waiting for $lockfile to be removed" sleep 2 done fi func_show_eval "(cd \$f_ex_an_ar_dir && $AR x \"\$f_ex_an_ar_oldlib\")" \ 'stat=$?; rm -f "$lockfile"; exit $stat' if test yes = "$lock_old_archive_extraction"; then $opt_dry_run || rm -f "$lockfile" fi if ($AR t "$f_ex_an_ar_oldlib" | sort | sort -uc >/dev/null 2>&1); then : else func_fatal_error "object name conflicts in archive: $f_ex_an_ar_dir/$f_ex_an_ar_oldlib" fi } # func_extract_archives gentop oldlib ... func_extract_archives () { $debug_cmd my_gentop=$1; shift my_oldlibs=${1+"$@"} my_oldobjs= my_xlib= my_xabs= my_xdir= for my_xlib in $my_oldlibs; do # Extract the objects. case $my_xlib in [\\/]* | [A-Za-z]:[\\/]*) my_xabs=$my_xlib ;; *) my_xabs=`pwd`"/$my_xlib" ;; esac func_basename "$my_xlib" my_xlib=$func_basename_result my_xlib_u=$my_xlib while :; do case " $extracted_archives " in *" $my_xlib_u "*) func_arith $extracted_serial + 1 extracted_serial=$func_arith_result my_xlib_u=lt$extracted_serial-$my_xlib ;; *) break ;; esac done extracted_archives="$extracted_archives $my_xlib_u" my_xdir=$my_gentop/$my_xlib_u func_mkdir_p "$my_xdir" case $host in *-darwin*) func_verbose "Extracting $my_xabs" # Do not bother doing anything if just a dry run $opt_dry_run || { darwin_orig_dir=`pwd` cd $my_xdir || exit $? darwin_archive=$my_xabs darwin_curdir=`pwd` func_basename "$darwin_archive" darwin_base_archive=$func_basename_result darwin_arches=`$LIPO -info "$darwin_archive" 2>/dev/null | $GREP Architectures 2>/dev/null || true` if test -n "$darwin_arches"; then darwin_arches=`$ECHO "$darwin_arches" | $SED -e 's/.*are://'` darwin_arch= func_verbose "$darwin_base_archive has multiple architectures $darwin_arches" for darwin_arch in $darwin_arches; do func_mkdir_p "unfat-$$/$darwin_base_archive-$darwin_arch" $LIPO -thin $darwin_arch -output "unfat-$$/$darwin_base_archive-$darwin_arch/$darwin_base_archive" "$darwin_archive" cd "unfat-$$/$darwin_base_archive-$darwin_arch" func_extract_an_archive "`pwd`" "$darwin_base_archive" cd "$darwin_curdir" $RM "unfat-$$/$darwin_base_archive-$darwin_arch/$darwin_base_archive" done # $darwin_arches ## Okay now we've a bunch of thin objects, gotta fatten them up :) darwin_filelist=`find unfat-$$ -type f -name \*.o -print -o -name \*.lo -print | $SED -e "$sed_basename" | sort -u` darwin_file= darwin_files= for darwin_file in $darwin_filelist; do darwin_files=`find unfat-$$ -name $darwin_file -print | sort | $NL2SP` $LIPO -create -output "$darwin_file" $darwin_files done # $darwin_filelist $RM -rf unfat-$$ cd "$darwin_orig_dir" else cd $darwin_orig_dir func_extract_an_archive "$my_xdir" "$my_xabs" fi # $darwin_arches } # !$opt_dry_run ;; *) func_extract_an_archive "$my_xdir" "$my_xabs" ;; esac my_oldobjs="$my_oldobjs "`find $my_xdir -name \*.$objext -print -o -name \*.lo -print | sort | $NL2SP` done func_extract_archives_result=$my_oldobjs } # func_emit_wrapper [arg=no] # # Emit a libtool wrapper script on stdout. # Don't directly open a file because we may want to # incorporate the script contents within a cygwin/mingw # wrapper executable. Must ONLY be called from within # func_mode_link because it depends on a number of variables # set therein. # # ARG is the value that the WRAPPER_SCRIPT_BELONGS_IN_OBJDIR # variable will take. If 'yes', then the emitted script # will assume that the directory where it is stored is # the $objdir directory. This is a cygwin/mingw-specific # behavior. func_emit_wrapper () { func_emit_wrapper_arg1=${1-no} $ECHO "\ #! $SHELL # $output - temporary wrapper script for $objdir/$outputname # Generated by $PROGRAM (GNU $PACKAGE) $VERSION # # The $output program cannot be directly executed until all the libtool # libraries that it depends on are installed. # # This wrapper script should never be moved out of the build directory. # If it is, it will not operate correctly. # Sed substitution that helps us do robust quoting. It backslashifies # metacharacters that are still active within double-quoted strings. sed_quote_subst='$sed_quote_subst' # Be Bourne compatible if test -n \"\${ZSH_VERSION+set}\" && (emulate sh) >/dev/null 2>&1; then emulate sh NULLCMD=: # Zsh 3.x and 4.x performs word splitting on \${1+\"\$@\"}, which # is contrary to our usage. Disable this feature. alias -g '\${1+\"\$@\"}'='\"\$@\"' setopt NO_GLOB_SUBST else case \`(set -o) 2>/dev/null\` in *posix*) set -o posix;; esac fi BIN_SH=xpg4; export BIN_SH # for Tru64 DUALCASE=1; export DUALCASE # for MKS sh # The HP-UX ksh and POSIX shell print the target directory to stdout # if CDPATH is set. (unset CDPATH) >/dev/null 2>&1 && unset CDPATH relink_command=\"$relink_command\" # This environment variable determines our operation mode. if test \"\$libtool_install_magic\" = \"$magic\"; then # install mode needs the following variables: generated_by_libtool_version='$macro_version' notinst_deplibs='$notinst_deplibs' else # When we are sourced in execute mode, \$file and \$ECHO are already set. if test \"\$libtool_execute_magic\" != \"$magic\"; then file=\"\$0\"" qECHO=`$ECHO "$ECHO" | $SED "$sed_quote_subst"` $ECHO "\ # A function that is used when there is no print builtin or printf. func_fallback_echo () { eval 'cat <<_LTECHO_EOF \$1 _LTECHO_EOF' } ECHO=\"$qECHO\" fi # Very basic option parsing. These options are (a) specific to # the libtool wrapper, (b) are identical between the wrapper # /script/ and the wrapper /executable/ that is used only on # windows platforms, and (c) all begin with the string "--lt-" # (application programs are unlikely to have options that match # this pattern). # # There are only two supported options: --lt-debug and # --lt-dump-script. There is, deliberately, no --lt-help. # # The first argument to this parsing function should be the # script's $0 value, followed by "$@". lt_option_debug= func_parse_lt_options () { lt_script_arg0=\$0 shift for lt_opt do case \"\$lt_opt\" in --lt-debug) lt_option_debug=1 ;; --lt-dump-script) lt_dump_D=\`\$ECHO \"X\$lt_script_arg0\" | $SED -e 's/^X//' -e 's%/[^/]*$%%'\` test \"X\$lt_dump_D\" = \"X\$lt_script_arg0\" && lt_dump_D=. lt_dump_F=\`\$ECHO \"X\$lt_script_arg0\" | $SED -e 's/^X//' -e 's%^.*/%%'\` cat \"\$lt_dump_D/\$lt_dump_F\" exit 0 ;; --lt-*) \$ECHO \"Unrecognized --lt- option: '\$lt_opt'\" 1>&2 exit 1 ;; esac done # Print the debug banner immediately: if test -n \"\$lt_option_debug\"; then echo \"$outputname:$output:\$LINENO: libtool wrapper (GNU $PACKAGE) $VERSION\" 1>&2 fi } # Used when --lt-debug. Prints its arguments to stdout # (redirection is the responsibility of the caller) func_lt_dump_args () { lt_dump_args_N=1; for lt_arg do \$ECHO \"$outputname:$output:\$LINENO: newargv[\$lt_dump_args_N]: \$lt_arg\" lt_dump_args_N=\`expr \$lt_dump_args_N + 1\` done } # Core function for launching the target application func_exec_program_core () { " case $host in # Backslashes separate directories on plain windows *-*-mingw | *-*-os2* | *-cegcc*) $ECHO "\ if test -n \"\$lt_option_debug\"; then \$ECHO \"$outputname:$output:\$LINENO: newargv[0]: \$progdir\\\\\$program\" 1>&2 func_lt_dump_args \${1+\"\$@\"} 1>&2 fi exec \"\$progdir\\\\\$program\" \${1+\"\$@\"} " ;; *) $ECHO "\ if test -n \"\$lt_option_debug\"; then \$ECHO \"$outputname:$output:\$LINENO: newargv[0]: \$progdir/\$program\" 1>&2 func_lt_dump_args \${1+\"\$@\"} 1>&2 fi exec \"\$progdir/\$program\" \${1+\"\$@\"} " ;; esac $ECHO "\ \$ECHO \"\$0: cannot exec \$program \$*\" 1>&2 exit 1 } # A function to encapsulate launching the target application # Strips options in the --lt-* namespace from \$@ and # launches target application with the remaining arguments. func_exec_program () { case \" \$* \" in *\\ --lt-*) for lt_wr_arg do case \$lt_wr_arg in --lt-*) ;; *) set x \"\$@\" \"\$lt_wr_arg\"; shift;; esac shift done ;; esac func_exec_program_core \${1+\"\$@\"} } # Parse options func_parse_lt_options \"\$0\" \${1+\"\$@\"} # Find the directory that this script lives in. thisdir=\`\$ECHO \"\$file\" | $SED 's%/[^/]*$%%'\` test \"x\$thisdir\" = \"x\$file\" && thisdir=. # Follow symbolic links until we get to the real thisdir. file=\`ls -ld \"\$file\" | $SED -n 's/.*-> //p'\` while test -n \"\$file\"; do destdir=\`\$ECHO \"\$file\" | $SED 's%/[^/]*\$%%'\` # If there was a directory component, then change thisdir. if test \"x\$destdir\" != \"x\$file\"; then case \"\$destdir\" in [\\\\/]* | [A-Za-z]:[\\\\/]*) thisdir=\"\$destdir\" ;; *) thisdir=\"\$thisdir/\$destdir\" ;; esac fi file=\`\$ECHO \"\$file\" | $SED 's%^.*/%%'\` file=\`ls -ld \"\$thisdir/\$file\" | $SED -n 's/.*-> //p'\` done # Usually 'no', except on cygwin/mingw when embedded into # the cwrapper. WRAPPER_SCRIPT_BELONGS_IN_OBJDIR=$func_emit_wrapper_arg1 if test \"\$WRAPPER_SCRIPT_BELONGS_IN_OBJDIR\" = \"yes\"; then # special case for '.' if test \"\$thisdir\" = \".\"; then thisdir=\`pwd\` fi # remove .libs from thisdir case \"\$thisdir\" in *[\\\\/]$objdir ) thisdir=\`\$ECHO \"\$thisdir\" | $SED 's%[\\\\/][^\\\\/]*$%%'\` ;; $objdir ) thisdir=. ;; esac fi # Try to get the absolute directory name. absdir=\`cd \"\$thisdir\" && pwd\` test -n \"\$absdir\" && thisdir=\"\$absdir\" " if test yes = "$fast_install"; then $ECHO "\ program=lt-'$outputname'$exeext progdir=\"\$thisdir/$objdir\" if test ! -f \"\$progdir/\$program\" || { file=\`ls -1dt \"\$progdir/\$program\" \"\$progdir/../\$program\" 2>/dev/null | $SED 1q\`; \\ test \"X\$file\" != \"X\$progdir/\$program\"; }; then file=\"\$\$-\$program\" if test ! -d \"\$progdir\"; then $MKDIR \"\$progdir\" else $RM \"\$progdir/\$file\" fi" $ECHO "\ # relink executable if necessary if test -n \"\$relink_command\"; then if relink_command_output=\`eval \$relink_command 2>&1\`; then : else \$ECHO \"\$relink_command_output\" >&2 $RM \"\$progdir/\$file\" exit 1 fi fi $MV \"\$progdir/\$file\" \"\$progdir/\$program\" 2>/dev/null || { $RM \"\$progdir/\$program\"; $MV \"\$progdir/\$file\" \"\$progdir/\$program\"; } $RM \"\$progdir/\$file\" fi" else $ECHO "\ program='$outputname' progdir=\"\$thisdir/$objdir\" " fi $ECHO "\ if test -f \"\$progdir/\$program\"; then" # fixup the dll searchpath if we need to. # # Fix the DLL searchpath if we need to. Do this before prepending # to shlibpath, because on Windows, both are PATH and uninstalled # libraries must come first. if test -n "$dllsearchpath"; then $ECHO "\ # Add the dll search path components to the executable PATH PATH=$dllsearchpath:\$PATH " fi # Export our shlibpath_var if we have one. if test yes = "$shlibpath_overrides_runpath" && test -n "$shlibpath_var" && test -n "$temp_rpath"; then $ECHO "\ # Add our own library path to $shlibpath_var $shlibpath_var=\"$temp_rpath\$$shlibpath_var\" # Some systems cannot cope with colon-terminated $shlibpath_var # The second colon is a workaround for a bug in BeOS R4 sed $shlibpath_var=\`\$ECHO \"\$$shlibpath_var\" | $SED 's/::*\$//'\` export $shlibpath_var " fi $ECHO "\ if test \"\$libtool_execute_magic\" != \"$magic\"; then # Run the actual program with our arguments. func_exec_program \${1+\"\$@\"} fi else # The program doesn't exist. \$ECHO \"\$0: error: '\$progdir/\$program' does not exist\" 1>&2 \$ECHO \"This script is just a wrapper for \$program.\" 1>&2 \$ECHO \"See the $PACKAGE documentation for more information.\" 1>&2 exit 1 fi fi\ " } # func_emit_cwrapperexe_src # emit the source code for a wrapper executable on stdout # Must ONLY be called from within func_mode_link because # it depends on a number of variable set therein. func_emit_cwrapperexe_src () { cat < #include #ifdef _MSC_VER # include # include # include #else # include # include # ifdef __CYGWIN__ # include # endif #endif #include #include #include #include #include #include #include #include #define STREQ(s1, s2) (strcmp ((s1), (s2)) == 0) /* declarations of non-ANSI functions */ #if defined __MINGW32__ # ifdef __STRICT_ANSI__ int _putenv (const char *); # endif #elif defined __CYGWIN__ # ifdef __STRICT_ANSI__ char *realpath (const char *, char *); int putenv (char *); int setenv (const char *, const char *, int); # endif /* #elif defined other_platform || defined ... */ #endif /* portability defines, excluding path handling macros */ #if defined _MSC_VER # define setmode _setmode # define stat _stat # define chmod _chmod # define getcwd _getcwd # define putenv _putenv # define S_IXUSR _S_IEXEC #elif defined __MINGW32__ # define setmode _setmode # define stat _stat # define chmod _chmod # define getcwd _getcwd # define putenv _putenv #elif defined __CYGWIN__ # define HAVE_SETENV # define FOPEN_WB "wb" /* #elif defined other platforms ... */ #endif #if defined PATH_MAX # define LT_PATHMAX PATH_MAX #elif defined MAXPATHLEN # define LT_PATHMAX MAXPATHLEN #else # define LT_PATHMAX 1024 #endif #ifndef S_IXOTH # define S_IXOTH 0 #endif #ifndef S_IXGRP # define S_IXGRP 0 #endif /* path handling portability macros */ #ifndef DIR_SEPARATOR # define DIR_SEPARATOR '/' # define PATH_SEPARATOR ':' #endif #if defined _WIN32 || defined __MSDOS__ || defined __DJGPP__ || \ defined __OS2__ # define HAVE_DOS_BASED_FILE_SYSTEM # define FOPEN_WB "wb" # ifndef DIR_SEPARATOR_2 # define DIR_SEPARATOR_2 '\\' # endif # ifndef PATH_SEPARATOR_2 # define PATH_SEPARATOR_2 ';' # endif #endif #ifndef DIR_SEPARATOR_2 # define IS_DIR_SEPARATOR(ch) ((ch) == DIR_SEPARATOR) #else /* DIR_SEPARATOR_2 */ # define IS_DIR_SEPARATOR(ch) \ (((ch) == DIR_SEPARATOR) || ((ch) == DIR_SEPARATOR_2)) #endif /* DIR_SEPARATOR_2 */ #ifndef PATH_SEPARATOR_2 # define IS_PATH_SEPARATOR(ch) ((ch) == PATH_SEPARATOR) #else /* PATH_SEPARATOR_2 */ # define IS_PATH_SEPARATOR(ch) ((ch) == PATH_SEPARATOR_2) #endif /* PATH_SEPARATOR_2 */ #ifndef FOPEN_WB # define FOPEN_WB "w" #endif #ifndef _O_BINARY # define _O_BINARY 0 #endif #define XMALLOC(type, num) ((type *) xmalloc ((num) * sizeof(type))) #define XFREE(stale) do { \ if (stale) { free (stale); stale = 0; } \ } while (0) #if defined LT_DEBUGWRAPPER static int lt_debug = 1; #else static int lt_debug = 0; #endif const char *program_name = "libtool-wrapper"; /* in case xstrdup fails */ void *xmalloc (size_t num); char *xstrdup (const char *string); const char *base_name (const char *name); char *find_executable (const char *wrapper); char *chase_symlinks (const char *pathspec); int make_executable (const char *path); int check_executable (const char *path); char *strendzap (char *str, const char *pat); void lt_debugprintf (const char *file, int line, const char *fmt, ...); void lt_fatal (const char *file, int line, const char *message, ...); static const char *nonnull (const char *s); static const char *nonempty (const char *s); void lt_setenv (const char *name, const char *value); char *lt_extend_str (const char *orig_value, const char *add, int to_end); void lt_update_exe_path (const char *name, const char *value); void lt_update_lib_path (const char *name, const char *value); char **prepare_spawn (char **argv); void lt_dump_script (FILE *f); EOF cat <= 0) && (st.st_mode & (S_IXUSR | S_IXGRP | S_IXOTH))) return 1; else return 0; } int make_executable (const char *path) { int rval = 0; struct stat st; lt_debugprintf (__FILE__, __LINE__, "(make_executable): %s\n", nonempty (path)); if ((!path) || (!*path)) return 0; if (stat (path, &st) >= 0) { rval = chmod (path, st.st_mode | S_IXOTH | S_IXGRP | S_IXUSR); } return rval; } /* Searches for the full path of the wrapper. Returns newly allocated full path name if found, NULL otherwise Does not chase symlinks, even on platforms that support them. */ char * find_executable (const char *wrapper) { int has_slash = 0; const char *p; const char *p_next; /* static buffer for getcwd */ char tmp[LT_PATHMAX + 1]; size_t tmp_len; char *concat_name; lt_debugprintf (__FILE__, __LINE__, "(find_executable): %s\n", nonempty (wrapper)); if ((wrapper == NULL) || (*wrapper == '\0')) return NULL; /* Absolute path? */ #if defined HAVE_DOS_BASED_FILE_SYSTEM if (isalpha ((unsigned char) wrapper[0]) && wrapper[1] == ':') { concat_name = xstrdup (wrapper); if (check_executable (concat_name)) return concat_name; XFREE (concat_name); } else { #endif if (IS_DIR_SEPARATOR (wrapper[0])) { concat_name = xstrdup (wrapper); if (check_executable (concat_name)) return concat_name; XFREE (concat_name); } #if defined HAVE_DOS_BASED_FILE_SYSTEM } #endif for (p = wrapper; *p; p++) if (*p == '/') { has_slash = 1; break; } if (!has_slash) { /* no slashes; search PATH */ const char *path = getenv ("PATH"); if (path != NULL) { for (p = path; *p; p = p_next) { const char *q; size_t p_len; for (q = p; *q; q++) if (IS_PATH_SEPARATOR (*q)) break; p_len = (size_t) (q - p); p_next = (*q == '\0' ? q : q + 1); if (p_len == 0) { /* empty path: current directory */ if (getcwd (tmp, LT_PATHMAX) == NULL) lt_fatal (__FILE__, __LINE__, "getcwd failed: %s", nonnull (strerror (errno))); tmp_len = strlen (tmp); concat_name = XMALLOC (char, tmp_len + 1 + strlen (wrapper) + 1); memcpy (concat_name, tmp, tmp_len); concat_name[tmp_len] = '/'; strcpy (concat_name + tmp_len + 1, wrapper); } else { concat_name = XMALLOC (char, p_len + 1 + strlen (wrapper) + 1); memcpy (concat_name, p, p_len); concat_name[p_len] = '/'; strcpy (concat_name + p_len + 1, wrapper); } if (check_executable (concat_name)) return concat_name; XFREE (concat_name); } } /* not found in PATH; assume curdir */ } /* Relative path | not found in path: prepend cwd */ if (getcwd (tmp, LT_PATHMAX) == NULL) lt_fatal (__FILE__, __LINE__, "getcwd failed: %s", nonnull (strerror (errno))); tmp_len = strlen (tmp); concat_name = XMALLOC (char, tmp_len + 1 + strlen (wrapper) + 1); memcpy (concat_name, tmp, tmp_len); concat_name[tmp_len] = '/'; strcpy (concat_name + tmp_len + 1, wrapper); if (check_executable (concat_name)) return concat_name; XFREE (concat_name); return NULL; } char * chase_symlinks (const char *pathspec) { #ifndef S_ISLNK return xstrdup (pathspec); #else char buf[LT_PATHMAX]; struct stat s; char *tmp_pathspec = xstrdup (pathspec); char *p; int has_symlinks = 0; while (strlen (tmp_pathspec) && !has_symlinks) { lt_debugprintf (__FILE__, __LINE__, "checking path component for symlinks: %s\n", tmp_pathspec); if (lstat (tmp_pathspec, &s) == 0) { if (S_ISLNK (s.st_mode) != 0) { has_symlinks = 1; break; } /* search backwards for last DIR_SEPARATOR */ p = tmp_pathspec + strlen (tmp_pathspec) - 1; while ((p > tmp_pathspec) && (!IS_DIR_SEPARATOR (*p))) p--; if ((p == tmp_pathspec) && (!IS_DIR_SEPARATOR (*p))) { /* no more DIR_SEPARATORS left */ break; } *p = '\0'; } else { lt_fatal (__FILE__, __LINE__, "error accessing file \"%s\": %s", tmp_pathspec, nonnull (strerror (errno))); } } XFREE (tmp_pathspec); if (!has_symlinks) { return xstrdup (pathspec); } tmp_pathspec = realpath (pathspec, buf); if (tmp_pathspec == 0) { lt_fatal (__FILE__, __LINE__, "could not follow symlinks for %s", pathspec); } return xstrdup (tmp_pathspec); #endif } char * strendzap (char *str, const char *pat) { size_t len, patlen; assert (str != NULL); assert (pat != NULL); len = strlen (str); patlen = strlen (pat); if (patlen <= len) { str += len - patlen; if (STREQ (str, pat)) *str = '\0'; } return str; } void lt_debugprintf (const char *file, int line, const char *fmt, ...) { va_list args; if (lt_debug) { (void) fprintf (stderr, "%s:%s:%d: ", program_name, file, line); va_start (args, fmt); (void) vfprintf (stderr, fmt, args); va_end (args); } } static void lt_error_core (int exit_status, const char *file, int line, const char *mode, const char *message, va_list ap) { fprintf (stderr, "%s:%s:%d: %s: ", program_name, file, line, mode); vfprintf (stderr, message, ap); fprintf (stderr, ".\n"); if (exit_status >= 0) exit (exit_status); } void lt_fatal (const char *file, int line, const char *message, ...) { va_list ap; va_start (ap, message); lt_error_core (EXIT_FAILURE, file, line, "FATAL", message, ap); va_end (ap); } static const char * nonnull (const char *s) { return s ? s : "(null)"; } static const char * nonempty (const char *s) { return (s && !*s) ? "(empty)" : nonnull (s); } void lt_setenv (const char *name, const char *value) { lt_debugprintf (__FILE__, __LINE__, "(lt_setenv) setting '%s' to '%s'\n", nonnull (name), nonnull (value)); { #ifdef HAVE_SETENV /* always make a copy, for consistency with !HAVE_SETENV */ char *str = xstrdup (value); setenv (name, str, 1); #else size_t len = strlen (name) + 1 + strlen (value) + 1; char *str = XMALLOC (char, len); sprintf (str, "%s=%s", name, value); if (putenv (str) != EXIT_SUCCESS) { XFREE (str); } #endif } } char * lt_extend_str (const char *orig_value, const char *add, int to_end) { char *new_value; if (orig_value && *orig_value) { size_t orig_value_len = strlen (orig_value); size_t add_len = strlen (add); new_value = XMALLOC (char, add_len + orig_value_len + 1); if (to_end) { strcpy (new_value, orig_value); strcpy (new_value + orig_value_len, add); } else { strcpy (new_value, add); strcpy (new_value + add_len, orig_value); } } else { new_value = xstrdup (add); } return new_value; } void lt_update_exe_path (const char *name, const char *value) { lt_debugprintf (__FILE__, __LINE__, "(lt_update_exe_path) modifying '%s' by prepending '%s'\n", nonnull (name), nonnull (value)); if (name && *name && value && *value) { char *new_value = lt_extend_str (getenv (name), value, 0); /* some systems can't cope with a ':'-terminated path #' */ size_t len = strlen (new_value); while ((len > 0) && IS_PATH_SEPARATOR (new_value[len-1])) { new_value[--len] = '\0'; } lt_setenv (name, new_value); XFREE (new_value); } } void lt_update_lib_path (const char *name, const char *value) { lt_debugprintf (__FILE__, __LINE__, "(lt_update_lib_path) modifying '%s' by prepending '%s'\n", nonnull (name), nonnull (value)); if (name && *name && value && *value) { char *new_value = lt_extend_str (getenv (name), value, 0); lt_setenv (name, new_value); XFREE (new_value); } } EOF case $host_os in mingw*) cat <<"EOF" /* Prepares an argument vector before calling spawn(). Note that spawn() does not by itself call the command interpreter (getenv ("COMSPEC") != NULL ? getenv ("COMSPEC") : ({ OSVERSIONINFO v; v.dwOSVersionInfoSize = sizeof(OSVERSIONINFO); GetVersionEx(&v); v.dwPlatformId == VER_PLATFORM_WIN32_NT; }) ? "cmd.exe" : "command.com"). Instead it simply concatenates the arguments, separated by ' ', and calls CreateProcess(). We must quote the arguments since Win32 CreateProcess() interprets characters like ' ', '\t', '\\', '"' (but not '<' and '>') in a special way: - Space and tab are interpreted as delimiters. They are not treated as delimiters if they are surrounded by double quotes: "...". - Unescaped double quotes are removed from the input. Their only effect is that within double quotes, space and tab are treated like normal characters. - Backslashes not followed by double quotes are not special. - But 2*n+1 backslashes followed by a double quote become n backslashes followed by a double quote (n >= 0): \" -> " \\\" -> \" \\\\\" -> \\" */ #define SHELL_SPECIAL_CHARS "\"\\ \001\002\003\004\005\006\007\010\011\012\013\014\015\016\017\020\021\022\023\024\025\026\027\030\031\032\033\034\035\036\037" #define SHELL_SPACE_CHARS " \001\002\003\004\005\006\007\010\011\012\013\014\015\016\017\020\021\022\023\024\025\026\027\030\031\032\033\034\035\036\037" char ** prepare_spawn (char **argv) { size_t argc; char **new_argv; size_t i; /* Count number of arguments. */ for (argc = 0; argv[argc] != NULL; argc++) ; /* Allocate new argument vector. */ new_argv = XMALLOC (char *, argc + 1); /* Put quoted arguments into the new argument vector. */ for (i = 0; i < argc; i++) { const char *string = argv[i]; if (string[0] == '\0') new_argv[i] = xstrdup ("\"\""); else if (strpbrk (string, SHELL_SPECIAL_CHARS) != NULL) { int quote_around = (strpbrk (string, SHELL_SPACE_CHARS) != NULL); size_t length; unsigned int backslashes; const char *s; char *quoted_string; char *p; length = 0; backslashes = 0; if (quote_around) length++; for (s = string; *s != '\0'; s++) { char c = *s; if (c == '"') length += backslashes + 1; length++; if (c == '\\') backslashes++; else backslashes = 0; } if (quote_around) length += backslashes + 1; quoted_string = XMALLOC (char, length + 1); p = quoted_string; backslashes = 0; if (quote_around) *p++ = '"'; for (s = string; *s != '\0'; s++) { char c = *s; if (c == '"') { unsigned int j; for (j = backslashes + 1; j > 0; j--) *p++ = '\\'; } *p++ = c; if (c == '\\') backslashes++; else backslashes = 0; } if (quote_around) { unsigned int j; for (j = backslashes; j > 0; j--) *p++ = '\\'; *p++ = '"'; } *p = '\0'; new_argv[i] = quoted_string; } else new_argv[i] = (char *) string; } new_argv[argc] = NULL; return new_argv; } EOF ;; esac cat <<"EOF" void lt_dump_script (FILE* f) { EOF func_emit_wrapper yes | $SED -n -e ' s/^\(.\{79\}\)\(..*\)/\1\ \2/ h s/\([\\"]\)/\\\1/g s/$/\\n/ s/\([^\n]*\).*/ fputs ("\1", f);/p g D' cat <<"EOF" } EOF } # end: func_emit_cwrapperexe_src # func_win32_import_lib_p ARG # True if ARG is an import lib, as indicated by $file_magic_cmd func_win32_import_lib_p () { $debug_cmd case `eval $file_magic_cmd \"\$1\" 2>/dev/null | $SED -e 10q` in *import*) : ;; *) false ;; esac } # func_suncc_cstd_abi # !!ONLY CALL THIS FOR SUN CC AFTER $compile_command IS FULLY EXPANDED!! # Several compiler flags select an ABI that is incompatible with the # Cstd library. Avoid specifying it if any are in CXXFLAGS. func_suncc_cstd_abi () { $debug_cmd case " $compile_command " in *" -compat=g "*|*\ -std=c++[0-9][0-9]\ *|*" -library=stdcxx4 "*|*" -library=stlport4 "*) suncc_use_cstd_abi=no ;; *) suncc_use_cstd_abi=yes ;; esac } # func_mode_link arg... func_mode_link () { $debug_cmd case $host in *-*-cygwin* | *-*-mingw* | *-*-pw32* | *-*-os2* | *-cegcc*) # It is impossible to link a dll without this setting, and # we shouldn't force the makefile maintainer to figure out # what system we are compiling for in order to pass an extra # flag for every libtool invocation. # allow_undefined=no # FIXME: Unfortunately, there are problems with the above when trying # to make a dll that has undefined symbols, in which case not # even a static library is built. For now, we need to specify # -no-undefined on the libtool link line when we can be certain # that all symbols are satisfied, otherwise we get a static library. allow_undefined=yes ;; *) allow_undefined=yes ;; esac libtool_args=$nonopt base_compile="$nonopt $@" compile_command=$nonopt finalize_command=$nonopt compile_rpath= finalize_rpath= compile_shlibpath= finalize_shlibpath= convenience= old_convenience= deplibs= old_deplibs= compiler_flags= linker_flags= dllsearchpath= lib_search_path=`pwd` inst_prefix_dir= new_inherited_linker_flags= avoid_version=no bindir= dlfiles= dlprefiles= dlself=no export_dynamic=no export_symbols= export_symbols_regex= generated= libobjs= ltlibs= module=no no_install=no objs= os2dllname= non_pic_objects= precious_files_regex= prefer_static_libs=no preload=false prev= prevarg= release= rpath= xrpath= perm_rpath= temp_rpath= thread_safe=no vinfo= vinfo_number=no weak_libs= single_module=$wl-single_module func_infer_tag $base_compile # We need to know -static, to get the right output filenames. for arg do case $arg in -shared) test yes != "$build_libtool_libs" \ && func_fatal_configuration "cannot build a shared library" build_old_libs=no break ;; -all-static | -static | -static-libtool-libs) case $arg in -all-static) if test yes = "$build_libtool_libs" && test -z "$link_static_flag"; then func_warning "complete static linking is impossible in this configuration" fi if test -n "$link_static_flag"; then dlopen_self=$dlopen_self_static fi prefer_static_libs=yes ;; -static) if test -z "$pic_flag" && test -n "$link_static_flag"; then dlopen_self=$dlopen_self_static fi prefer_static_libs=built ;; -static-libtool-libs) if test -z "$pic_flag" && test -n "$link_static_flag"; then dlopen_self=$dlopen_self_static fi prefer_static_libs=yes ;; esac build_libtool_libs=no build_old_libs=yes break ;; esac done # See if our shared archives depend on static archives. test -n "$old_archive_from_new_cmds" && build_old_libs=yes # Go through the arguments, transforming them on the way. while test "$#" -gt 0; do arg=$1 shift func_quote_for_eval "$arg" qarg=$func_quote_for_eval_unquoted_result func_append libtool_args " $func_quote_for_eval_result" # If the previous option needs an argument, assign it. if test -n "$prev"; then case $prev in output) func_append compile_command " @OUTPUT@" func_append finalize_command " @OUTPUT@" ;; esac case $prev in bindir) bindir=$arg prev= continue ;; dlfiles|dlprefiles) $preload || { # Add the symbol object into the linking commands. func_append compile_command " @SYMFILE@" func_append finalize_command " @SYMFILE@" preload=: } case $arg in *.la | *.lo) ;; # We handle these cases below. force) if test no = "$dlself"; then dlself=needless export_dynamic=yes fi prev= continue ;; self) if test dlprefiles = "$prev"; then dlself=yes elif test dlfiles = "$prev" && test yes != "$dlopen_self"; then dlself=yes else dlself=needless export_dynamic=yes fi prev= continue ;; *) if test dlfiles = "$prev"; then func_append dlfiles " $arg" else func_append dlprefiles " $arg" fi prev= continue ;; esac ;; expsyms) export_symbols=$arg test -f "$arg" \ || func_fatal_error "symbol file '$arg' does not exist" prev= continue ;; expsyms_regex) export_symbols_regex=$arg prev= continue ;; framework) case $host in *-*-darwin*) case "$deplibs " in *" $qarg.ltframework "*) ;; *) func_append deplibs " $qarg.ltframework" # this is fixed later ;; esac ;; esac prev= continue ;; inst_prefix) inst_prefix_dir=$arg prev= continue ;; mllvm) # Clang does not use LLVM to link, so we can simply discard any # '-mllvm $arg' options when doing the link step. prev= continue ;; objectlist) if test -f "$arg"; then save_arg=$arg moreargs= for fil in `cat "$save_arg"` do # func_append moreargs " $fil" arg=$fil # A libtool-controlled object. # Check to see that this really is a libtool object. if func_lalib_unsafe_p "$arg"; then pic_object= non_pic_object= # Read the .lo file func_source "$arg" if test -z "$pic_object" || test -z "$non_pic_object" || test none = "$pic_object" && test none = "$non_pic_object"; then func_fatal_error "cannot find name of object for '$arg'" fi # Extract subdirectory from the argument. func_dirname "$arg" "/" "" xdir=$func_dirname_result if test none != "$pic_object"; then # Prepend the subdirectory the object is found in. pic_object=$xdir$pic_object if test dlfiles = "$prev"; then if test yes = "$build_libtool_libs" && test yes = "$dlopen_support"; then func_append dlfiles " $pic_object" prev= continue else # If libtool objects are unsupported, then we need to preload. prev=dlprefiles fi fi # CHECK ME: I think I busted this. -Ossama if test dlprefiles = "$prev"; then # Preload the old-style object. func_append dlprefiles " $pic_object" prev= fi # A PIC object. func_append libobjs " $pic_object" arg=$pic_object fi # Non-PIC object. if test none != "$non_pic_object"; then # Prepend the subdirectory the object is found in. non_pic_object=$xdir$non_pic_object # A standard non-PIC object func_append non_pic_objects " $non_pic_object" if test -z "$pic_object" || test none = "$pic_object"; then arg=$non_pic_object fi else # If the PIC object exists, use it instead. # $xdir was prepended to $pic_object above. non_pic_object=$pic_object func_append non_pic_objects " $non_pic_object" fi else # Only an error if not doing a dry-run. if $opt_dry_run; then # Extract subdirectory from the argument. func_dirname "$arg" "/" "" xdir=$func_dirname_result func_lo2o "$arg" pic_object=$xdir$objdir/$func_lo2o_result non_pic_object=$xdir$func_lo2o_result func_append libobjs " $pic_object" func_append non_pic_objects " $non_pic_object" else func_fatal_error "'$arg' is not a valid libtool object" fi fi done else func_fatal_error "link input file '$arg' does not exist" fi arg=$save_arg prev= continue ;; os2dllname) os2dllname=$arg prev= continue ;; precious_regex) precious_files_regex=$arg prev= continue ;; release) release=-$arg prev= continue ;; rpath | xrpath) # We need an absolute path. case $arg in [\\/]* | [A-Za-z]:[\\/]*) ;; *) func_fatal_error "only absolute run-paths are allowed" ;; esac if test rpath = "$prev"; then case "$rpath " in *" $arg "*) ;; *) func_append rpath " $arg" ;; esac else case "$xrpath " in *" $arg "*) ;; *) func_append xrpath " $arg" ;; esac fi prev= continue ;; shrext) shrext_cmds=$arg prev= continue ;; weak) func_append weak_libs " $arg" prev= continue ;; xcclinker) func_append linker_flags " $qarg" func_append compiler_flags " $qarg" prev= func_append compile_command " $qarg" func_append finalize_command " $qarg" continue ;; xcompiler) func_append compiler_flags " $qarg" prev= func_append compile_command " $qarg" func_append finalize_command " $qarg" continue ;; xlinker) func_append linker_flags " $qarg" func_append compiler_flags " $wl$qarg" prev= func_append compile_command " $wl$qarg" func_append finalize_command " $wl$qarg" continue ;; *) eval "$prev=\"\$arg\"" prev= continue ;; esac fi # test -n "$prev" prevarg=$arg case $arg in -all-static) if test -n "$link_static_flag"; then # See comment for -static flag below, for more details. func_append compile_command " $link_static_flag" func_append finalize_command " $link_static_flag" fi continue ;; -allow-undefined) # FIXME: remove this flag sometime in the future. func_fatal_error "'-allow-undefined' must not be used because it is the default" ;; -avoid-version) avoid_version=yes continue ;; -bindir) prev=bindir continue ;; -dlopen) prev=dlfiles continue ;; -dlpreopen) prev=dlprefiles continue ;; -export-dynamic) export_dynamic=yes continue ;; -export-symbols | -export-symbols-regex) if test -n "$export_symbols" || test -n "$export_symbols_regex"; then func_fatal_error "more than one -exported-symbols argument is not allowed" fi if test X-export-symbols = "X$arg"; then prev=expsyms else prev=expsyms_regex fi continue ;; -framework) prev=framework continue ;; -inst-prefix-dir) prev=inst_prefix continue ;; # The native IRIX linker understands -LANG:*, -LIST:* and -LNO:* # so, if we see these flags be careful not to treat them like -L -L[A-Z][A-Z]*:*) case $with_gcc/$host in no/*-*-irix* | /*-*-irix*) func_append compile_command " $arg" func_append finalize_command " $arg" ;; esac continue ;; -L*) func_stripname "-L" '' "$arg" if test -z "$func_stripname_result"; then if test "$#" -gt 0; then func_fatal_error "require no space between '-L' and '$1'" else func_fatal_error "need path for '-L' option" fi fi func_resolve_sysroot "$func_stripname_result" dir=$func_resolve_sysroot_result # We need an absolute path. case $dir in [\\/]* | [A-Za-z]:[\\/]*) ;; *) absdir=`cd "$dir" && pwd` test -z "$absdir" && \ func_fatal_error "cannot determine absolute directory name of '$dir'" dir=$absdir ;; esac case "$deplibs " in *" -L$dir "* | *" $arg "*) # Will only happen for absolute or sysroot arguments ;; *) # Preserve sysroot, but never include relative directories case $dir in [\\/]* | [A-Za-z]:[\\/]* | =*) func_append deplibs " $arg" ;; *) func_append deplibs " -L$dir" ;; esac func_append lib_search_path " $dir" ;; esac case $host in *-*-cygwin* | *-*-mingw* | *-*-pw32* | *-*-os2* | *-cegcc*) testbindir=`$ECHO "$dir" | $SED 's*/lib$*/bin*'` case :$dllsearchpath: in *":$dir:"*) ;; ::) dllsearchpath=$dir;; *) func_append dllsearchpath ":$dir";; esac case :$dllsearchpath: in *":$testbindir:"*) ;; ::) dllsearchpath=$testbindir;; *) func_append dllsearchpath ":$testbindir";; esac ;; esac continue ;; -l*) if test X-lc = "X$arg" || test X-lm = "X$arg"; then case $host in *-*-cygwin* | *-*-mingw* | *-*-pw32* | *-*-beos* | *-cegcc* | *-*-haiku*) # These systems don't actually have a C or math library (as such) continue ;; *-*-os2*) # These systems don't actually have a C library (as such) test X-lc = "X$arg" && continue ;; *-*-openbsd* | *-*-freebsd* | *-*-dragonfly* | *-*-bitrig*) # Do not include libc due to us having libc/libc_r. test X-lc = "X$arg" && continue ;; *-*-rhapsody* | *-*-darwin1.[012]) # Rhapsody C and math libraries are in the System framework func_append deplibs " System.ltframework" continue ;; *-*-sco3.2v5* | *-*-sco5v6*) # Causes problems with __ctype test X-lc = "X$arg" && continue ;; *-*-sysv4.2uw2* | *-*-sysv5* | *-*-unixware* | *-*-OpenUNIX*) # Compiler inserts libc in the correct place for threads to work test X-lc = "X$arg" && continue ;; esac elif test X-lc_r = "X$arg"; then case $host in *-*-openbsd* | *-*-freebsd* | *-*-dragonfly* | *-*-bitrig*) # Do not include libc_r directly, use -pthread flag. continue ;; esac fi func_append deplibs " $arg" continue ;; -mllvm) prev=mllvm continue ;; -module) module=yes continue ;; # Tru64 UNIX uses -model [arg] to determine the layout of C++ # classes, name mangling, and exception handling. # Darwin uses the -arch flag to determine output architecture. -model|-arch|-isysroot|--sysroot) func_append compiler_flags " $arg" func_append compile_command " $arg" func_append finalize_command " $arg" prev=xcompiler continue ;; -mt|-mthreads|-kthread|-Kthread|-pthread|-pthreads|--thread-safe \ |-threads|-fopenmp|-openmp|-mp|-xopenmp|-omp|-qsmp=*) func_append compiler_flags " $arg" func_append compile_command " $arg" func_append finalize_command " $arg" case "$new_inherited_linker_flags " in *" $arg "*) ;; * ) func_append new_inherited_linker_flags " $arg" ;; esac continue ;; -multi_module) single_module=$wl-multi_module continue ;; -no-fast-install) fast_install=no continue ;; -no-install) case $host in *-*-cygwin* | *-*-mingw* | *-*-pw32* | *-*-os2* | *-*-darwin* | *-cegcc*) # The PATH hackery in wrapper scripts is required on Windows # and Darwin in order for the loader to find any dlls it needs. func_warning "'-no-install' is ignored for $host" func_warning "assuming '-no-fast-install' instead" fast_install=no ;; *) no_install=yes ;; esac continue ;; -no-undefined) allow_undefined=no continue ;; -objectlist) prev=objectlist continue ;; -os2dllname) prev=os2dllname continue ;; -o) prev=output ;; -precious-files-regex) prev=precious_regex continue ;; -release) prev=release continue ;; -rpath) prev=rpath continue ;; -R) prev=xrpath continue ;; -R*) func_stripname '-R' '' "$arg" dir=$func_stripname_result # We need an absolute path. case $dir in [\\/]* | [A-Za-z]:[\\/]*) ;; =*) func_stripname '=' '' "$dir" dir=$lt_sysroot$func_stripname_result ;; *) func_fatal_error "only absolute run-paths are allowed" ;; esac case "$xrpath " in *" $dir "*) ;; *) func_append xrpath " $dir" ;; esac continue ;; -shared) # The effects of -shared are defined in a previous loop. continue ;; -shrext) prev=shrext continue ;; -static | -static-libtool-libs) # The effects of -static are defined in a previous loop. # We used to do the same as -all-static on platforms that # didn't have a PIC flag, but the assumption that the effects # would be equivalent was wrong. It would break on at least # Digital Unix and AIX. continue ;; -thread-safe) thread_safe=yes continue ;; -version-info) prev=vinfo continue ;; -version-number) prev=vinfo vinfo_number=yes continue ;; -weak) prev=weak continue ;; -Wc,*) func_stripname '-Wc,' '' "$arg" args=$func_stripname_result arg= save_ifs=$IFS; IFS=, for flag in $args; do IFS=$save_ifs func_quote_for_eval "$flag" func_append arg " $func_quote_for_eval_result" func_append compiler_flags " $func_quote_for_eval_result" done IFS=$save_ifs func_stripname ' ' '' "$arg" arg=$func_stripname_result ;; -Wl,*) func_stripname '-Wl,' '' "$arg" args=$func_stripname_result arg= save_ifs=$IFS; IFS=, for flag in $args; do IFS=$save_ifs func_quote_for_eval "$flag" func_append arg " $wl$func_quote_for_eval_result" func_append compiler_flags " $wl$func_quote_for_eval_result" func_append linker_flags " $func_quote_for_eval_result" done IFS=$save_ifs func_stripname ' ' '' "$arg" arg=$func_stripname_result ;; -Xcompiler) prev=xcompiler continue ;; -Xlinker) prev=xlinker continue ;; -XCClinker) prev=xcclinker continue ;; # -msg_* for osf cc -msg_*) func_quote_for_eval "$arg" arg=$func_quote_for_eval_result ;; # Flags to be passed through unchanged, with rationale: # -64, -mips[0-9] enable 64-bit mode for the SGI compiler # -r[0-9][0-9]* specify processor for the SGI compiler # -xarch=*, -xtarget=* enable 64-bit mode for the Sun compiler # +DA*, +DD* enable 64-bit mode for the HP compiler # -q* compiler args for the IBM compiler # -m*, -t[45]*, -txscale* architecture-specific flags for GCC # -F/path path to uninstalled frameworks, gcc on darwin # -p, -pg, --coverage, -fprofile-* profiling flags for GCC # -fstack-protector* stack protector flags for GCC # @file GCC response files # -tp=* Portland pgcc target processor selection # --sysroot=* for sysroot support # -O*, -g*, -flto*, -fwhopr*, -fuse-linker-plugin GCC link-time optimization # -specs=* GCC specs files # -stdlib=* select c++ std lib with clang # -fsanitize=* Clang/GCC memory and address sanitizer # -fuse-ld=* Linker select flags for GCC -64|-mips[0-9]|-r[0-9][0-9]*|-xarch=*|-xtarget=*|+DA*|+DD*|-q*|-m*| \ -t[45]*|-txscale*|-p|-pg|--coverage|-fprofile-*|-F*|@*|-tp=*|--sysroot=*| \ -O*|-g*|-flto*|-fwhopr*|-fuse-linker-plugin|-fstack-protector*|-stdlib=*| \ -specs=*|-fsanitize=*|-fuse-ld=*) func_quote_for_eval "$arg" arg=$func_quote_for_eval_result func_append compile_command " $arg" func_append finalize_command " $arg" func_append compiler_flags " $arg" continue ;; -Z*) if test os2 = "`expr $host : '.*\(os2\)'`"; then # OS/2 uses -Zxxx to specify OS/2-specific options compiler_flags="$compiler_flags $arg" func_append compile_command " $arg" func_append finalize_command " $arg" case $arg in -Zlinker | -Zstack) prev=xcompiler ;; esac continue else # Otherwise treat like 'Some other compiler flag' below func_quote_for_eval "$arg" arg=$func_quote_for_eval_result fi ;; # Some other compiler flag. -* | +*) func_quote_for_eval "$arg" arg=$func_quote_for_eval_result ;; *.$objext) # A standard object. func_append objs " $arg" ;; *.lo) # A libtool-controlled object. # Check to see that this really is a libtool object. if func_lalib_unsafe_p "$arg"; then pic_object= non_pic_object= # Read the .lo file func_source "$arg" if test -z "$pic_object" || test -z "$non_pic_object" || test none = "$pic_object" && test none = "$non_pic_object"; then func_fatal_error "cannot find name of object for '$arg'" fi # Extract subdirectory from the argument. func_dirname "$arg" "/" "" xdir=$func_dirname_result test none = "$pic_object" || { # Prepend the subdirectory the object is found in. pic_object=$xdir$pic_object if test dlfiles = "$prev"; then if test yes = "$build_libtool_libs" && test yes = "$dlopen_support"; then func_append dlfiles " $pic_object" prev= continue else # If libtool objects are unsupported, then we need to preload. prev=dlprefiles fi fi # CHECK ME: I think I busted this. -Ossama if test dlprefiles = "$prev"; then # Preload the old-style object. func_append dlprefiles " $pic_object" prev= fi # A PIC object. func_append libobjs " $pic_object" arg=$pic_object } # Non-PIC object. if test none != "$non_pic_object"; then # Prepend the subdirectory the object is found in. non_pic_object=$xdir$non_pic_object # A standard non-PIC object func_append non_pic_objects " $non_pic_object" if test -z "$pic_object" || test none = "$pic_object"; then arg=$non_pic_object fi else # If the PIC object exists, use it instead. # $xdir was prepended to $pic_object above. non_pic_object=$pic_object func_append non_pic_objects " $non_pic_object" fi else # Only an error if not doing a dry-run. if $opt_dry_run; then # Extract subdirectory from the argument. func_dirname "$arg" "/" "" xdir=$func_dirname_result func_lo2o "$arg" pic_object=$xdir$objdir/$func_lo2o_result non_pic_object=$xdir$func_lo2o_result func_append libobjs " $pic_object" func_append non_pic_objects " $non_pic_object" else func_fatal_error "'$arg' is not a valid libtool object" fi fi ;; *.$libext) # An archive. func_append deplibs " $arg" func_append old_deplibs " $arg" continue ;; *.la) # A libtool-controlled library. func_resolve_sysroot "$arg" if test dlfiles = "$prev"; then # This library was specified with -dlopen. func_append dlfiles " $func_resolve_sysroot_result" prev= elif test dlprefiles = "$prev"; then # The library was specified with -dlpreopen. func_append dlprefiles " $func_resolve_sysroot_result" prev= else func_append deplibs " $func_resolve_sysroot_result" fi continue ;; # Some other compiler argument. *) # Unknown arguments in both finalize_command and compile_command need # to be aesthetically quoted because they are evaled later. func_quote_for_eval "$arg" arg=$func_quote_for_eval_result ;; esac # arg # Now actually substitute the argument into the commands. if test -n "$arg"; then func_append compile_command " $arg" func_append finalize_command " $arg" fi done # argument parsing loop test -n "$prev" && \ func_fatal_help "the '$prevarg' option requires an argument" if test yes = "$export_dynamic" && test -n "$export_dynamic_flag_spec"; then eval arg=\"$export_dynamic_flag_spec\" func_append compile_command " $arg" func_append finalize_command " $arg" fi oldlibs= # calculate the name of the file, without its directory func_basename "$output" outputname=$func_basename_result libobjs_save=$libobjs if test -n "$shlibpath_var"; then # get the directories listed in $shlibpath_var eval shlib_search_path=\`\$ECHO \"\$$shlibpath_var\" \| \$SED \'s/:/ /g\'\` else shlib_search_path= fi eval sys_lib_search_path=\"$sys_lib_search_path_spec\" eval sys_lib_dlsearch_path=\"$sys_lib_dlsearch_path_spec\" # Definition is injected by LT_CONFIG during libtool generation. func_munge_path_list sys_lib_dlsearch_path "$LT_SYS_LIBRARY_PATH" func_dirname "$output" "/" "" output_objdir=$func_dirname_result$objdir func_to_tool_file "$output_objdir/" tool_output_objdir=$func_to_tool_file_result # Create the object directory. func_mkdir_p "$output_objdir" # Determine the type of output case $output in "") func_fatal_help "you must specify an output file" ;; *.$libext) linkmode=oldlib ;; *.lo | *.$objext) linkmode=obj ;; *.la) linkmode=lib ;; *) linkmode=prog ;; # Anything else should be a program. esac specialdeplibs= libs= # Find all interdependent deplibs by searching for libraries # that are linked more than once (e.g. -la -lb -la) for deplib in $deplibs; do if $opt_preserve_dup_deps; then case "$libs " in *" $deplib "*) func_append specialdeplibs " $deplib" ;; esac fi func_append libs " $deplib" done if test lib = "$linkmode"; then libs="$predeps $libs $compiler_lib_search_path $postdeps" # Compute libraries that are listed more than once in $predeps # $postdeps and mark them as special (i.e., whose duplicates are # not to be eliminated). pre_post_deps= if $opt_duplicate_compiler_generated_deps; then for pre_post_dep in $predeps $postdeps; do case "$pre_post_deps " in *" $pre_post_dep "*) func_append specialdeplibs " $pre_post_deps" ;; esac func_append pre_post_deps " $pre_post_dep" done fi pre_post_deps= fi deplibs= newdependency_libs= newlib_search_path= need_relink=no # whether we're linking any uninstalled libtool libraries notinst_deplibs= # not-installed libtool libraries notinst_path= # paths that contain not-installed libtool libraries case $linkmode in lib) passes="conv dlpreopen link" for file in $dlfiles $dlprefiles; do case $file in *.la) ;; *) func_fatal_help "libraries can '-dlopen' only libtool libraries: $file" ;; esac done ;; prog) compile_deplibs= finalize_deplibs= alldeplibs=false newdlfiles= newdlprefiles= passes="conv scan dlopen dlpreopen link" ;; *) passes="conv" ;; esac for pass in $passes; do # The preopen pass in lib mode reverses $deplibs; put it back here # so that -L comes before libs that need it for instance... if test lib,link = "$linkmode,$pass"; then ## FIXME: Find the place where the list is rebuilt in the wrong ## order, and fix it there properly tmp_deplibs= for deplib in $deplibs; do tmp_deplibs="$deplib $tmp_deplibs" done deplibs=$tmp_deplibs fi if test lib,link = "$linkmode,$pass" || test prog,scan = "$linkmode,$pass"; then libs=$deplibs deplibs= fi if test prog = "$linkmode"; then case $pass in dlopen) libs=$dlfiles ;; dlpreopen) libs=$dlprefiles ;; link) libs="$deplibs %DEPLIBS%" test "X$link_all_deplibs" != Xno && libs="$libs $dependency_libs" ;; esac fi if test lib,dlpreopen = "$linkmode,$pass"; then # Collect and forward deplibs of preopened libtool libs for lib in $dlprefiles; do # Ignore non-libtool-libs dependency_libs= func_resolve_sysroot "$lib" case $lib in *.la) func_source "$func_resolve_sysroot_result" ;; esac # Collect preopened libtool deplibs, except any this library # has declared as weak libs for deplib in $dependency_libs; do func_basename "$deplib" deplib_base=$func_basename_result case " $weak_libs " in *" $deplib_base "*) ;; *) func_append deplibs " $deplib" ;; esac done done libs=$dlprefiles fi if test dlopen = "$pass"; then # Collect dlpreopened libraries save_deplibs=$deplibs deplibs= fi for deplib in $libs; do lib= found=false case $deplib in -mt|-mthreads|-kthread|-Kthread|-pthread|-pthreads|--thread-safe \ |-threads|-fopenmp|-openmp|-mp|-xopenmp|-omp|-qsmp=*) if test prog,link = "$linkmode,$pass"; then compile_deplibs="$deplib $compile_deplibs" finalize_deplibs="$deplib $finalize_deplibs" else func_append compiler_flags " $deplib" if test lib = "$linkmode"; then case "$new_inherited_linker_flags " in *" $deplib "*) ;; * ) func_append new_inherited_linker_flags " $deplib" ;; esac fi fi continue ;; -l*) if test lib != "$linkmode" && test prog != "$linkmode"; then func_warning "'-l' is ignored for archives/objects" continue fi func_stripname '-l' '' "$deplib" name=$func_stripname_result if test lib = "$linkmode"; then searchdirs="$newlib_search_path $lib_search_path $compiler_lib_search_dirs $sys_lib_search_path $shlib_search_path" else searchdirs="$newlib_search_path $lib_search_path $sys_lib_search_path $shlib_search_path" fi for searchdir in $searchdirs; do for search_ext in .la $std_shrext .so .a; do # Search the libtool library lib=$searchdir/lib$name$search_ext if test -f "$lib"; then if test .la = "$search_ext"; then found=: else found=false fi break 2 fi done done if $found; then # deplib is a libtool library # If $allow_libtool_libs_with_static_runtimes && $deplib is a stdlib, # We need to do some special things here, and not later. if test yes = "$allow_libtool_libs_with_static_runtimes"; then case " $predeps $postdeps " in *" $deplib "*) if func_lalib_p "$lib"; then library_names= old_library= func_source "$lib" for l in $old_library $library_names; do ll=$l done if test "X$ll" = "X$old_library"; then # only static version available found=false func_dirname "$lib" "" "." ladir=$func_dirname_result lib=$ladir/$old_library if test prog,link = "$linkmode,$pass"; then compile_deplibs="$deplib $compile_deplibs" finalize_deplibs="$deplib $finalize_deplibs" else deplibs="$deplib $deplibs" test lib = "$linkmode" && newdependency_libs="$deplib $newdependency_libs" fi continue fi fi ;; *) ;; esac fi else # deplib doesn't seem to be a libtool library if test prog,link = "$linkmode,$pass"; then compile_deplibs="$deplib $compile_deplibs" finalize_deplibs="$deplib $finalize_deplibs" else deplibs="$deplib $deplibs" test lib = "$linkmode" && newdependency_libs="$deplib $newdependency_libs" fi continue fi ;; # -l *.ltframework) if test prog,link = "$linkmode,$pass"; then compile_deplibs="$deplib $compile_deplibs" finalize_deplibs="$deplib $finalize_deplibs" else deplibs="$deplib $deplibs" if test lib = "$linkmode"; then case "$new_inherited_linker_flags " in *" $deplib "*) ;; * ) func_append new_inherited_linker_flags " $deplib" ;; esac fi fi continue ;; -L*) case $linkmode in lib) deplibs="$deplib $deplibs" test conv = "$pass" && continue newdependency_libs="$deplib $newdependency_libs" func_stripname '-L' '' "$deplib" func_resolve_sysroot "$func_stripname_result" func_append newlib_search_path " $func_resolve_sysroot_result" ;; prog) if test conv = "$pass"; then deplibs="$deplib $deplibs" continue fi if test scan = "$pass"; then deplibs="$deplib $deplibs" else compile_deplibs="$deplib $compile_deplibs" finalize_deplibs="$deplib $finalize_deplibs" fi func_stripname '-L' '' "$deplib" func_resolve_sysroot "$func_stripname_result" func_append newlib_search_path " $func_resolve_sysroot_result" ;; *) func_warning "'-L' is ignored for archives/objects" ;; esac # linkmode continue ;; # -L -R*) if test link = "$pass"; then func_stripname '-R' '' "$deplib" func_resolve_sysroot "$func_stripname_result" dir=$func_resolve_sysroot_result # Make sure the xrpath contains only unique directories. case "$xrpath " in *" $dir "*) ;; *) func_append xrpath " $dir" ;; esac fi deplibs="$deplib $deplibs" continue ;; *.la) func_resolve_sysroot "$deplib" lib=$func_resolve_sysroot_result ;; *.$libext) if test conv = "$pass"; then deplibs="$deplib $deplibs" continue fi case $linkmode in lib) # Linking convenience modules into shared libraries is allowed, # but linking other static libraries is non-portable. case " $dlpreconveniencelibs " in *" $deplib "*) ;; *) valid_a_lib=false case $deplibs_check_method in match_pattern*) set dummy $deplibs_check_method; shift match_pattern_regex=`expr "$deplibs_check_method" : "$1 \(.*\)"` if eval "\$ECHO \"$deplib\"" 2>/dev/null | $SED 10q \ | $EGREP "$match_pattern_regex" > /dev/null; then valid_a_lib=: fi ;; pass_all) valid_a_lib=: ;; esac if $valid_a_lib; then echo $ECHO "*** Warning: Linking the shared library $output against the" $ECHO "*** static library $deplib is not portable!" deplibs="$deplib $deplibs" else echo $ECHO "*** Warning: Trying to link with static lib archive $deplib." echo "*** I have the capability to make that library automatically link in when" echo "*** you link to this library. But I can only do this if you have a" echo "*** shared version of the library, which you do not appear to have" echo "*** because the file extensions .$libext of this argument makes me believe" echo "*** that it is just a static archive that I should not use here." fi ;; esac continue ;; prog) if test link != "$pass"; then deplibs="$deplib $deplibs" else compile_deplibs="$deplib $compile_deplibs" finalize_deplibs="$deplib $finalize_deplibs" fi continue ;; esac # linkmode ;; # *.$libext *.lo | *.$objext) if test conv = "$pass"; then deplibs="$deplib $deplibs" elif test prog = "$linkmode"; then if test dlpreopen = "$pass" || test yes != "$dlopen_support" || test no = "$build_libtool_libs"; then # If there is no dlopen support or we're linking statically, # we need to preload. func_append newdlprefiles " $deplib" compile_deplibs="$deplib $compile_deplibs" finalize_deplibs="$deplib $finalize_deplibs" else func_append newdlfiles " $deplib" fi fi continue ;; %DEPLIBS%) alldeplibs=: continue ;; esac # case $deplib $found || test -f "$lib" \ || func_fatal_error "cannot find the library '$lib' or unhandled argument '$deplib'" # Check to see that this really is a libtool archive. func_lalib_unsafe_p "$lib" \ || func_fatal_error "'$lib' is not a valid libtool archive" func_dirname "$lib" "" "." ladir=$func_dirname_result dlname= dlopen= dlpreopen= libdir= library_names= old_library= inherited_linker_flags= # If the library was installed with an old release of libtool, # it will not redefine variables installed, or shouldnotlink installed=yes shouldnotlink=no avoidtemprpath= # Read the .la file func_source "$lib" # Convert "-framework foo" to "foo.ltframework" if test -n "$inherited_linker_flags"; then tmp_inherited_linker_flags=`$ECHO "$inherited_linker_flags" | $SED 's/-framework \([^ $]*\)/\1.ltframework/g'` for tmp_inherited_linker_flag in $tmp_inherited_linker_flags; do case " $new_inherited_linker_flags " in *" $tmp_inherited_linker_flag "*) ;; *) func_append new_inherited_linker_flags " $tmp_inherited_linker_flag";; esac done fi dependency_libs=`$ECHO " $dependency_libs" | $SED 's% \([^ $]*\).ltframework% -framework \1%g'` if test lib,link = "$linkmode,$pass" || test prog,scan = "$linkmode,$pass" || { test prog != "$linkmode" && test lib != "$linkmode"; }; then test -n "$dlopen" && func_append dlfiles " $dlopen" test -n "$dlpreopen" && func_append dlprefiles " $dlpreopen" fi if test conv = "$pass"; then # Only check for convenience libraries deplibs="$lib $deplibs" if test -z "$libdir"; then if test -z "$old_library"; then func_fatal_error "cannot find name of link library for '$lib'" fi # It is a libtool convenience library, so add in its objects. func_append convenience " $ladir/$objdir/$old_library" func_append old_convenience " $ladir/$objdir/$old_library" tmp_libs= for deplib in $dependency_libs; do deplibs="$deplib $deplibs" if $opt_preserve_dup_deps; then case "$tmp_libs " in *" $deplib "*) func_append specialdeplibs " $deplib" ;; esac fi func_append tmp_libs " $deplib" done elif test prog != "$linkmode" && test lib != "$linkmode"; then func_fatal_error "'$lib' is not a convenience library" fi continue fi # $pass = conv # Get the name of the library we link against. linklib= if test -n "$old_library" && { test yes = "$prefer_static_libs" || test built,no = "$prefer_static_libs,$installed"; }; then linklib=$old_library else for l in $old_library $library_names; do linklib=$l done fi if test -z "$linklib"; then func_fatal_error "cannot find name of link library for '$lib'" fi # This library was specified with -dlopen. if test dlopen = "$pass"; then test -z "$libdir" \ && func_fatal_error "cannot -dlopen a convenience library: '$lib'" if test -z "$dlname" || test yes != "$dlopen_support" || test no = "$build_libtool_libs" then # If there is no dlname, no dlopen support or we're linking # statically, we need to preload. We also need to preload any # dependent libraries so libltdl's deplib preloader doesn't # bomb out in the load deplibs phase. func_append dlprefiles " $lib $dependency_libs" else func_append newdlfiles " $lib" fi continue fi # $pass = dlopen # We need an absolute path. case $ladir in [\\/]* | [A-Za-z]:[\\/]*) abs_ladir=$ladir ;; *) abs_ladir=`cd "$ladir" && pwd` if test -z "$abs_ladir"; then func_warning "cannot determine absolute directory name of '$ladir'" func_warning "passing it literally to the linker, although it might fail" abs_ladir=$ladir fi ;; esac func_basename "$lib" laname=$func_basename_result # Find the relevant object directory and library name. if test yes = "$installed"; then if test ! -f "$lt_sysroot$libdir/$linklib" && test -f "$abs_ladir/$linklib"; then func_warning "library '$lib' was moved." dir=$ladir absdir=$abs_ladir libdir=$abs_ladir else dir=$lt_sysroot$libdir absdir=$lt_sysroot$libdir fi test yes = "$hardcode_automatic" && avoidtemprpath=yes else if test ! -f "$ladir/$objdir/$linklib" && test -f "$abs_ladir/$linklib"; then dir=$ladir absdir=$abs_ladir # Remove this search path later func_append notinst_path " $abs_ladir" else dir=$ladir/$objdir absdir=$abs_ladir/$objdir # Remove this search path later func_append notinst_path " $abs_ladir" fi fi # $installed = yes func_stripname 'lib' '.la' "$laname" name=$func_stripname_result # This library was specified with -dlpreopen. if test dlpreopen = "$pass"; then if test -z "$libdir" && test prog = "$linkmode"; then func_fatal_error "only libraries may -dlpreopen a convenience library: '$lib'" fi case $host in # special handling for platforms with PE-DLLs. *cygwin* | *mingw* | *cegcc* ) # Linker will automatically link against shared library if both # static and shared are present. Therefore, ensure we extract # symbols from the import library if a shared library is present # (otherwise, the dlopen module name will be incorrect). We do # this by putting the import library name into $newdlprefiles. # We recover the dlopen module name by 'saving' the la file # name in a special purpose variable, and (later) extracting the # dlname from the la file. if test -n "$dlname"; then func_tr_sh "$dir/$linklib" eval "libfile_$func_tr_sh_result=\$abs_ladir/\$laname" func_append newdlprefiles " $dir/$linklib" else func_append newdlprefiles " $dir/$old_library" # Keep a list of preopened convenience libraries to check # that they are being used correctly in the link pass. test -z "$libdir" && \ func_append dlpreconveniencelibs " $dir/$old_library" fi ;; * ) # Prefer using a static library (so that no silly _DYNAMIC symbols # are required to link). if test -n "$old_library"; then func_append newdlprefiles " $dir/$old_library" # Keep a list of preopened convenience libraries to check # that they are being used correctly in the link pass. test -z "$libdir" && \ func_append dlpreconveniencelibs " $dir/$old_library" # Otherwise, use the dlname, so that lt_dlopen finds it. elif test -n "$dlname"; then func_append newdlprefiles " $dir/$dlname" else func_append newdlprefiles " $dir/$linklib" fi ;; esac fi # $pass = dlpreopen if test -z "$libdir"; then # Link the convenience library if test lib = "$linkmode"; then deplibs="$dir/$old_library $deplibs" elif test prog,link = "$linkmode,$pass"; then compile_deplibs="$dir/$old_library $compile_deplibs" finalize_deplibs="$dir/$old_library $finalize_deplibs" else deplibs="$lib $deplibs" # used for prog,scan pass fi continue fi if test prog = "$linkmode" && test link != "$pass"; then func_append newlib_search_path " $ladir" deplibs="$lib $deplibs" linkalldeplibs=false if test no != "$link_all_deplibs" || test -z "$library_names" || test no = "$build_libtool_libs"; then linkalldeplibs=: fi tmp_libs= for deplib in $dependency_libs; do case $deplib in -L*) func_stripname '-L' '' "$deplib" func_resolve_sysroot "$func_stripname_result" func_append newlib_search_path " $func_resolve_sysroot_result" ;; esac # Need to link against all dependency_libs? if $linkalldeplibs; then deplibs="$deplib $deplibs" else # Need to hardcode shared library paths # or/and link against static libraries newdependency_libs="$deplib $newdependency_libs" fi if $opt_preserve_dup_deps; then case "$tmp_libs " in *" $deplib "*) func_append specialdeplibs " $deplib" ;; esac fi func_append tmp_libs " $deplib" done # for deplib continue fi # $linkmode = prog... if test prog,link = "$linkmode,$pass"; then if test -n "$library_names" && { { test no = "$prefer_static_libs" || test built,yes = "$prefer_static_libs,$installed"; } || test -z "$old_library"; }; then # We need to hardcode the library path if test -n "$shlibpath_var" && test -z "$avoidtemprpath"; then # Make sure the rpath contains only unique directories. case $temp_rpath: in *"$absdir:"*) ;; *) func_append temp_rpath "$absdir:" ;; esac fi # Hardcode the library path. # Skip directories that are in the system default run-time # search path. case " $sys_lib_dlsearch_path " in *" $absdir "*) ;; *) case "$compile_rpath " in *" $absdir "*) ;; *) func_append compile_rpath " $absdir" ;; esac ;; esac case " $sys_lib_dlsearch_path " in *" $libdir "*) ;; *) case "$finalize_rpath " in *" $libdir "*) ;; *) func_append finalize_rpath " $libdir" ;; esac ;; esac fi # $linkmode,$pass = prog,link... if $alldeplibs && { test pass_all = "$deplibs_check_method" || { test yes = "$build_libtool_libs" && test -n "$library_names"; }; }; then # We only need to search for static libraries continue fi fi link_static=no # Whether the deplib will be linked statically use_static_libs=$prefer_static_libs if test built = "$use_static_libs" && test yes = "$installed"; then use_static_libs=no fi if test -n "$library_names" && { test no = "$use_static_libs" || test -z "$old_library"; }; then case $host in *cygwin* | *mingw* | *cegcc* | *os2*) # No point in relinking DLLs because paths are not encoded func_append notinst_deplibs " $lib" need_relink=no ;; *) if test no = "$installed"; then func_append notinst_deplibs " $lib" need_relink=yes fi ;; esac # This is a shared library # Warn about portability, can't link against -module's on some # systems (darwin). Don't bleat about dlopened modules though! dlopenmodule= for dlpremoduletest in $dlprefiles; do if test "X$dlpremoduletest" = "X$lib"; then dlopenmodule=$dlpremoduletest break fi done if test -z "$dlopenmodule" && test yes = "$shouldnotlink" && test link = "$pass"; then echo if test prog = "$linkmode"; then $ECHO "*** Warning: Linking the executable $output against the loadable module" else $ECHO "*** Warning: Linking the shared library $output against the loadable module" fi $ECHO "*** $linklib is not portable!" fi if test lib = "$linkmode" && test yes = "$hardcode_into_libs"; then # Hardcode the library path. # Skip directories that are in the system default run-time # search path. case " $sys_lib_dlsearch_path " in *" $absdir "*) ;; *) case "$compile_rpath " in *" $absdir "*) ;; *) func_append compile_rpath " $absdir" ;; esac ;; esac case " $sys_lib_dlsearch_path " in *" $libdir "*) ;; *) case "$finalize_rpath " in *" $libdir "*) ;; *) func_append finalize_rpath " $libdir" ;; esac ;; esac fi if test -n "$old_archive_from_expsyms_cmds"; then # figure out the soname set dummy $library_names shift realname=$1 shift libname=`eval "\\$ECHO \"$libname_spec\""` # use dlname if we got it. it's perfectly good, no? if test -n "$dlname"; then soname=$dlname elif test -n "$soname_spec"; then # bleh windows case $host in *cygwin* | mingw* | *cegcc* | *os2*) func_arith $current - $age major=$func_arith_result versuffix=-$major ;; esac eval soname=\"$soname_spec\" else soname=$realname fi # Make a new name for the extract_expsyms_cmds to use soroot=$soname func_basename "$soroot" soname=$func_basename_result func_stripname 'lib' '.dll' "$soname" newlib=libimp-$func_stripname_result.a # If the library has no export list, then create one now if test -f "$output_objdir/$soname-def"; then : else func_verbose "extracting exported symbol list from '$soname'" func_execute_cmds "$extract_expsyms_cmds" 'exit $?' fi # Create $newlib if test -f "$output_objdir/$newlib"; then :; else func_verbose "generating import library for '$soname'" func_execute_cmds "$old_archive_from_expsyms_cmds" 'exit $?' fi # make sure the library variables are pointing to the new library dir=$output_objdir linklib=$newlib fi # test -n "$old_archive_from_expsyms_cmds" if test prog = "$linkmode" || test relink != "$opt_mode"; then add_shlibpath= add_dir= add= lib_linked=yes case $hardcode_action in immediate | unsupported) if test no = "$hardcode_direct"; then add=$dir/$linklib case $host in *-*-sco3.2v5.0.[024]*) add_dir=-L$dir ;; *-*-sysv4*uw2*) add_dir=-L$dir ;; *-*-sysv5OpenUNIX* | *-*-sysv5UnixWare7.[01].[10]* | \ *-*-unixware7*) add_dir=-L$dir ;; *-*-darwin* ) # if the lib is a (non-dlopened) module then we cannot # link against it, someone is ignoring the earlier warnings if /usr/bin/file -L $add 2> /dev/null | $GREP ": [^:]* bundle" >/dev/null; then if test "X$dlopenmodule" != "X$lib"; then $ECHO "*** Warning: lib $linklib is a module, not a shared library" if test -z "$old_library"; then echo echo "*** And there doesn't seem to be a static archive available" echo "*** The link will probably fail, sorry" else add=$dir/$old_library fi elif test -n "$old_library"; then add=$dir/$old_library fi fi esac elif test no = "$hardcode_minus_L"; then case $host in *-*-sunos*) add_shlibpath=$dir ;; esac add_dir=-L$dir add=-l$name elif test no = "$hardcode_shlibpath_var"; then add_shlibpath=$dir add=-l$name else lib_linked=no fi ;; relink) if test yes = "$hardcode_direct" && test no = "$hardcode_direct_absolute"; then add=$dir/$linklib elif test yes = "$hardcode_minus_L"; then add_dir=-L$absdir # Try looking first in the location we're being installed to. if test -n "$inst_prefix_dir"; then case $libdir in [\\/]*) func_append add_dir " -L$inst_prefix_dir$libdir" ;; esac fi add=-l$name elif test yes = "$hardcode_shlibpath_var"; then add_shlibpath=$dir add=-l$name else lib_linked=no fi ;; *) lib_linked=no ;; esac if test yes != "$lib_linked"; then func_fatal_configuration "unsupported hardcode properties" fi if test -n "$add_shlibpath"; then case :$compile_shlibpath: in *":$add_shlibpath:"*) ;; *) func_append compile_shlibpath "$add_shlibpath:" ;; esac fi if test prog = "$linkmode"; then test -n "$add_dir" && compile_deplibs="$add_dir $compile_deplibs" test -n "$add" && compile_deplibs="$add $compile_deplibs" else test -n "$add_dir" && deplibs="$add_dir $deplibs" test -n "$add" && deplibs="$add $deplibs" if test yes != "$hardcode_direct" && test yes != "$hardcode_minus_L" && test yes = "$hardcode_shlibpath_var"; then case :$finalize_shlibpath: in *":$libdir:"*) ;; *) func_append finalize_shlibpath "$libdir:" ;; esac fi fi fi if test prog = "$linkmode" || test relink = "$opt_mode"; then add_shlibpath= add_dir= add= # Finalize command for both is simple: just hardcode it. if test yes = "$hardcode_direct" && test no = "$hardcode_direct_absolute"; then add=$libdir/$linklib elif test yes = "$hardcode_minus_L"; then add_dir=-L$libdir add=-l$name elif test yes = "$hardcode_shlibpath_var"; then case :$finalize_shlibpath: in *":$libdir:"*) ;; *) func_append finalize_shlibpath "$libdir:" ;; esac add=-l$name elif test yes = "$hardcode_automatic"; then if test -n "$inst_prefix_dir" && test -f "$inst_prefix_dir$libdir/$linklib"; then add=$inst_prefix_dir$libdir/$linklib else add=$libdir/$linklib fi else # We cannot seem to hardcode it, guess we'll fake it. add_dir=-L$libdir # Try looking first in the location we're being installed to. if test -n "$inst_prefix_dir"; then case $libdir in [\\/]*) func_append add_dir " -L$inst_prefix_dir$libdir" ;; esac fi add=-l$name fi if test prog = "$linkmode"; then test -n "$add_dir" && finalize_deplibs="$add_dir $finalize_deplibs" test -n "$add" && finalize_deplibs="$add $finalize_deplibs" else test -n "$add_dir" && deplibs="$add_dir $deplibs" test -n "$add" && deplibs="$add $deplibs" fi fi elif test prog = "$linkmode"; then # Here we assume that one of hardcode_direct or hardcode_minus_L # is not unsupported. This is valid on all known static and # shared platforms. if test unsupported != "$hardcode_direct"; then test -n "$old_library" && linklib=$old_library compile_deplibs="$dir/$linklib $compile_deplibs" finalize_deplibs="$dir/$linklib $finalize_deplibs" else compile_deplibs="-l$name -L$dir $compile_deplibs" finalize_deplibs="-l$name -L$dir $finalize_deplibs" fi elif test yes = "$build_libtool_libs"; then # Not a shared library if test pass_all != "$deplibs_check_method"; then # We're trying link a shared library against a static one # but the system doesn't support it. # Just print a warning and add the library to dependency_libs so # that the program can be linked against the static library. echo $ECHO "*** Warning: This system cannot link to static lib archive $lib." echo "*** I have the capability to make that library automatically link in when" echo "*** you link to this library. But I can only do this if you have a" echo "*** shared version of the library, which you do not appear to have." if test yes = "$module"; then echo "*** But as you try to build a module library, libtool will still create " echo "*** a static module, that should work as long as the dlopening application" echo "*** is linked with the -dlopen flag to resolve symbols at runtime." if test -z "$global_symbol_pipe"; then echo echo "*** However, this would only work if libtool was able to extract symbol" echo "*** lists from a program, using 'nm' or equivalent, but libtool could" echo "*** not find such a program. So, this module is probably useless." echo "*** 'nm' from GNU binutils and a full rebuild may help." fi if test no = "$build_old_libs"; then build_libtool_libs=module build_old_libs=yes else build_libtool_libs=no fi fi else deplibs="$dir/$old_library $deplibs" link_static=yes fi fi # link shared/static library? if test lib = "$linkmode"; then if test -n "$dependency_libs" && { test yes != "$hardcode_into_libs" || test yes = "$build_old_libs" || test yes = "$link_static"; }; then # Extract -R from dependency_libs temp_deplibs= for libdir in $dependency_libs; do case $libdir in -R*) func_stripname '-R' '' "$libdir" temp_xrpath=$func_stripname_result case " $xrpath " in *" $temp_xrpath "*) ;; *) func_append xrpath " $temp_xrpath";; esac;; *) func_append temp_deplibs " $libdir";; esac done dependency_libs=$temp_deplibs fi func_append newlib_search_path " $absdir" # Link against this library test no = "$link_static" && newdependency_libs="$abs_ladir/$laname $newdependency_libs" # ... and its dependency_libs tmp_libs= for deplib in $dependency_libs; do newdependency_libs="$deplib $newdependency_libs" case $deplib in -L*) func_stripname '-L' '' "$deplib" func_resolve_sysroot "$func_stripname_result";; *) func_resolve_sysroot "$deplib" ;; esac if $opt_preserve_dup_deps; then case "$tmp_libs " in *" $func_resolve_sysroot_result "*) func_append specialdeplibs " $func_resolve_sysroot_result" ;; esac fi func_append tmp_libs " $func_resolve_sysroot_result" done if test no != "$link_all_deplibs"; then # Add the search paths of all dependency libraries for deplib in $dependency_libs; do path= case $deplib in -L*) path=$deplib ;; *.la) func_resolve_sysroot "$deplib" deplib=$func_resolve_sysroot_result func_dirname "$deplib" "" "." dir=$func_dirname_result # We need an absolute path. case $dir in [\\/]* | [A-Za-z]:[\\/]*) absdir=$dir ;; *) absdir=`cd "$dir" && pwd` if test -z "$absdir"; then func_warning "cannot determine absolute directory name of '$dir'" absdir=$dir fi ;; esac if $GREP "^installed=no" $deplib > /dev/null; then case $host in *-*-darwin*) depdepl= eval deplibrary_names=`$SED -n -e 's/^library_names=\(.*\)$/\1/p' $deplib` if test -n "$deplibrary_names"; then for tmp in $deplibrary_names; do depdepl=$tmp done if test -f "$absdir/$objdir/$depdepl"; then depdepl=$absdir/$objdir/$depdepl darwin_install_name=`$OTOOL -L $depdepl | awk '{if (NR == 2) {print $1;exit}}'` if test -z "$darwin_install_name"; then darwin_install_name=`$OTOOL64 -L $depdepl | awk '{if (NR == 2) {print $1;exit}}'` fi func_append compiler_flags " $wl-dylib_file $wl$darwin_install_name:$depdepl" func_append linker_flags " -dylib_file $darwin_install_name:$depdepl" path= fi fi ;; *) path=-L$absdir/$objdir ;; esac else eval libdir=`$SED -n -e 's/^libdir=\(.*\)$/\1/p' $deplib` test -z "$libdir" && \ func_fatal_error "'$deplib' is not a valid libtool archive" test "$absdir" != "$libdir" && \ func_warning "'$deplib' seems to be moved" path=-L$absdir fi ;; esac case " $deplibs " in *" $path "*) ;; *) deplibs="$path $deplibs" ;; esac done fi # link_all_deplibs != no fi # linkmode = lib done # for deplib in $libs if test link = "$pass"; then if test prog = "$linkmode"; then compile_deplibs="$new_inherited_linker_flags $compile_deplibs" finalize_deplibs="$new_inherited_linker_flags $finalize_deplibs" else compiler_flags="$compiler_flags "`$ECHO " $new_inherited_linker_flags" | $SED 's% \([^ $]*\).ltframework% -framework \1%g'` fi fi dependency_libs=$newdependency_libs if test dlpreopen = "$pass"; then # Link the dlpreopened libraries before other libraries for deplib in $save_deplibs; do deplibs="$deplib $deplibs" done fi if test dlopen != "$pass"; then test conv = "$pass" || { # Make sure lib_search_path contains only unique directories. lib_search_path= for dir in $newlib_search_path; do case "$lib_search_path " in *" $dir "*) ;; *) func_append lib_search_path " $dir" ;; esac done newlib_search_path= } if test prog,link = "$linkmode,$pass"; then vars="compile_deplibs finalize_deplibs" else vars=deplibs fi for var in $vars dependency_libs; do # Add libraries to $var in reverse order eval tmp_libs=\"\$$var\" new_libs= for deplib in $tmp_libs; do # FIXME: Pedantically, this is the right thing to do, so # that some nasty dependency loop isn't accidentally # broken: #new_libs="$deplib $new_libs" # Pragmatically, this seems to cause very few problems in # practice: case $deplib in -L*) new_libs="$deplib $new_libs" ;; -R*) ;; *) # And here is the reason: when a library appears more # than once as an explicit dependence of a library, or # is implicitly linked in more than once by the # compiler, it is considered special, and multiple # occurrences thereof are not removed. Compare this # with having the same library being listed as a # dependency of multiple other libraries: in this case, # we know (pedantically, we assume) the library does not # need to be listed more than once, so we keep only the # last copy. This is not always right, but it is rare # enough that we require users that really mean to play # such unportable linking tricks to link the library # using -Wl,-lname, so that libtool does not consider it # for duplicate removal. case " $specialdeplibs " in *" $deplib "*) new_libs="$deplib $new_libs" ;; *) case " $new_libs " in *" $deplib "*) ;; *) new_libs="$deplib $new_libs" ;; esac ;; esac ;; esac done tmp_libs= for deplib in $new_libs; do case $deplib in -L*) case " $tmp_libs " in *" $deplib "*) ;; *) func_append tmp_libs " $deplib" ;; esac ;; *) func_append tmp_libs " $deplib" ;; esac done eval $var=\"$tmp_libs\" done # for var fi # Add Sun CC postdeps if required: test CXX = "$tagname" && { case $host_os in linux*) case `$CC -V 2>&1 | sed 5q` in *Sun\ C*) # Sun C++ 5.9 func_suncc_cstd_abi if test no != "$suncc_use_cstd_abi"; then func_append postdeps ' -library=Cstd -library=Crun' fi ;; esac ;; solaris*) func_cc_basename "$CC" case $func_cc_basename_result in CC* | sunCC*) func_suncc_cstd_abi if test no != "$suncc_use_cstd_abi"; then func_append postdeps ' -library=Cstd -library=Crun' fi ;; esac ;; esac } # Last step: remove runtime libs from dependency_libs # (they stay in deplibs) tmp_libs= for i in $dependency_libs; do case " $predeps $postdeps $compiler_lib_search_path " in *" $i "*) i= ;; esac if test -n "$i"; then func_append tmp_libs " $i" fi done dependency_libs=$tmp_libs done # for pass if test prog = "$linkmode"; then dlfiles=$newdlfiles fi if test prog = "$linkmode" || test lib = "$linkmode"; then dlprefiles=$newdlprefiles fi case $linkmode in oldlib) if test -n "$dlfiles$dlprefiles" || test no != "$dlself"; then func_warning "'-dlopen' is ignored for archives" fi case " $deplibs" in *\ -l* | *\ -L*) func_warning "'-l' and '-L' are ignored for archives" ;; esac test -n "$rpath" && \ func_warning "'-rpath' is ignored for archives" test -n "$xrpath" && \ func_warning "'-R' is ignored for archives" test -n "$vinfo" && \ func_warning "'-version-info/-version-number' is ignored for archives" test -n "$release" && \ func_warning "'-release' is ignored for archives" test -n "$export_symbols$export_symbols_regex" && \ func_warning "'-export-symbols' is ignored for archives" # Now set the variables for building old libraries. build_libtool_libs=no oldlibs=$output func_append objs "$old_deplibs" ;; lib) # Make sure we only generate libraries of the form 'libNAME.la'. case $outputname in lib*) func_stripname 'lib' '.la' "$outputname" name=$func_stripname_result eval shared_ext=\"$shrext_cmds\" eval libname=\"$libname_spec\" ;; *) test no = "$module" \ && func_fatal_help "libtool library '$output' must begin with 'lib'" if test no != "$need_lib_prefix"; then # Add the "lib" prefix for modules if required func_stripname '' '.la' "$outputname" name=$func_stripname_result eval shared_ext=\"$shrext_cmds\" eval libname=\"$libname_spec\" else func_stripname '' '.la' "$outputname" libname=$func_stripname_result fi ;; esac if test -n "$objs"; then if test pass_all != "$deplibs_check_method"; then func_fatal_error "cannot build libtool library '$output' from non-libtool objects on this host:$objs" else echo $ECHO "*** Warning: Linking the shared library $output against the non-libtool" $ECHO "*** objects $objs is not portable!" func_append libobjs " $objs" fi fi test no = "$dlself" \ || func_warning "'-dlopen self' is ignored for libtool libraries" set dummy $rpath shift test 1 -lt "$#" \ && func_warning "ignoring multiple '-rpath's for a libtool library" install_libdir=$1 oldlibs= if test -z "$rpath"; then if test yes = "$build_libtool_libs"; then # Building a libtool convenience library. # Some compilers have problems with a '.al' extension so # convenience libraries should have the same extension an # archive normally would. oldlibs="$output_objdir/$libname.$libext $oldlibs" build_libtool_libs=convenience build_old_libs=yes fi test -n "$vinfo" && \ func_warning "'-version-info/-version-number' is ignored for convenience libraries" test -n "$release" && \ func_warning "'-release' is ignored for convenience libraries" else # Parse the version information argument. save_ifs=$IFS; IFS=: set dummy $vinfo 0 0 0 shift IFS=$save_ifs test -n "$7" && \ func_fatal_help "too many parameters to '-version-info'" # convert absolute version numbers to libtool ages # this retains compatibility with .la files and attempts # to make the code below a bit more comprehensible case $vinfo_number in yes) number_major=$1 number_minor=$2 number_revision=$3 # # There are really only two kinds -- those that # use the current revision as the major version # and those that subtract age and use age as # a minor version. But, then there is irix # that has an extra 1 added just for fun # case $version_type in # correct linux to gnu/linux during the next big refactor darwin|freebsd-elf|linux|osf|windows|none) func_arith $number_major + $number_minor current=$func_arith_result age=$number_minor revision=$number_revision ;; freebsd-aout|qnx|sunos) current=$number_major revision=$number_minor age=0 ;; irix|nonstopux) func_arith $number_major + $number_minor current=$func_arith_result age=$number_minor revision=$number_minor lt_irix_increment=no ;; *) func_fatal_configuration "$modename: unknown library version type '$version_type'" ;; esac ;; no) current=$1 revision=$2 age=$3 ;; esac # Check that each of the things are valid numbers. case $current in 0|[1-9]|[1-9][0-9]|[1-9][0-9][0-9]|[1-9][0-9][0-9][0-9]|[1-9][0-9][0-9][0-9][0-9]) ;; *) func_error "CURRENT '$current' must be a nonnegative integer" func_fatal_error "'$vinfo' is not valid version information" ;; esac case $revision in 0|[1-9]|[1-9][0-9]|[1-9][0-9][0-9]|[1-9][0-9][0-9][0-9]|[1-9][0-9][0-9][0-9][0-9]) ;; *) func_error "REVISION '$revision' must be a nonnegative integer" func_fatal_error "'$vinfo' is not valid version information" ;; esac case $age in 0|[1-9]|[1-9][0-9]|[1-9][0-9][0-9]|[1-9][0-9][0-9][0-9]|[1-9][0-9][0-9][0-9][0-9]) ;; *) func_error "AGE '$age' must be a nonnegative integer" func_fatal_error "'$vinfo' is not valid version information" ;; esac if test "$age" -gt "$current"; then func_error "AGE '$age' is greater than the current interface number '$current'" func_fatal_error "'$vinfo' is not valid version information" fi # Calculate the version variables. major= versuffix= verstring= case $version_type in none) ;; darwin) # Like Linux, but with the current version available in # verstring for coding it into the library header func_arith $current - $age major=.$func_arith_result versuffix=$major.$age.$revision # Darwin ld doesn't like 0 for these options... func_arith $current + 1 minor_current=$func_arith_result xlcverstring="$wl-compatibility_version $wl$minor_current $wl-current_version $wl$minor_current.$revision" verstring="-compatibility_version $minor_current -current_version $minor_current.$revision" # On Darwin other compilers case $CC in nagfor*) verstring="$wl-compatibility_version $wl$minor_current $wl-current_version $wl$minor_current.$revision" ;; *) verstring="-compatibility_version $minor_current -current_version $minor_current.$revision" ;; esac ;; freebsd-aout) major=.$current versuffix=.$current.$revision ;; freebsd-elf) func_arith $current - $age major=.$func_arith_result versuffix=$major.$age.$revision ;; irix | nonstopux) if test no = "$lt_irix_increment"; then func_arith $current - $age else func_arith $current - $age + 1 fi major=$func_arith_result case $version_type in nonstopux) verstring_prefix=nonstopux ;; *) verstring_prefix=sgi ;; esac verstring=$verstring_prefix$major.$revision # Add in all the interfaces that we are compatible with. loop=$revision while test 0 -ne "$loop"; do func_arith $revision - $loop iface=$func_arith_result func_arith $loop - 1 loop=$func_arith_result verstring=$verstring_prefix$major.$iface:$verstring done # Before this point, $major must not contain '.'. major=.$major versuffix=$major.$revision ;; linux) # correct to gnu/linux during the next big refactor func_arith $current - $age major=.$func_arith_result versuffix=$major.$age.$revision ;; osf) func_arith $current - $age major=.$func_arith_result versuffix=.$current.$age.$revision verstring=$current.$age.$revision # Add in all the interfaces that we are compatible with. loop=$age while test 0 -ne "$loop"; do func_arith $current - $loop iface=$func_arith_result func_arith $loop - 1 loop=$func_arith_result verstring=$verstring:$iface.0 done # Make executables depend on our current version. func_append verstring ":$current.0" ;; qnx) major=.$current versuffix=.$current ;; sco) major=.$current versuffix=.$current ;; sunos) major=.$current versuffix=.$current.$revision ;; windows) # Use '-' rather than '.', since we only want one # extension on DOS 8.3 file systems. func_arith $current - $age major=$func_arith_result versuffix=-$major ;; *) func_fatal_configuration "unknown library version type '$version_type'" ;; esac # Clear the version info if we defaulted, and they specified a release. if test -z "$vinfo" && test -n "$release"; then major= case $version_type in darwin) # we can't check for "0.0" in archive_cmds due to quoting # problems, so we reset it completely verstring= ;; *) verstring=0.0 ;; esac if test no = "$need_version"; then versuffix= else versuffix=.0.0 fi fi # Remove version info from name if versioning should be avoided if test yes,no = "$avoid_version,$need_version"; then major= versuffix= verstring= fi # Check to see if the archive will have undefined symbols. if test yes = "$allow_undefined"; then if test unsupported = "$allow_undefined_flag"; then if test yes = "$build_old_libs"; then func_warning "undefined symbols not allowed in $host shared libraries; building static only" build_libtool_libs=no else func_fatal_error "can't build $host shared library unless -no-undefined is specified" fi fi else # Don't allow undefined symbols. allow_undefined_flag=$no_undefined_flag fi fi func_generate_dlsyms "$libname" "$libname" : func_append libobjs " $symfileobj" test " " = "$libobjs" && libobjs= if test relink != "$opt_mode"; then # Remove our outputs, but don't remove object files since they # may have been created when compiling PIC objects. removelist= tempremovelist=`$ECHO "$output_objdir/*"` for p in $tempremovelist; do case $p in *.$objext | *.gcno) ;; $output_objdir/$outputname | $output_objdir/$libname.* | $output_objdir/$libname$release.*) if test -n "$precious_files_regex"; then if $ECHO "$p" | $EGREP -e "$precious_files_regex" >/dev/null 2>&1 then continue fi fi func_append removelist " $p" ;; *) ;; esac done test -n "$removelist" && \ func_show_eval "${RM}r \$removelist" fi # Now set the variables for building old libraries. if test yes = "$build_old_libs" && test convenience != "$build_libtool_libs"; then func_append oldlibs " $output_objdir/$libname.$libext" # Transform .lo files to .o files. oldobjs="$objs "`$ECHO "$libobjs" | $SP2NL | $SED "/\.$libext$/d; $lo2o" | $NL2SP` fi # Eliminate all temporary directories. #for path in $notinst_path; do # lib_search_path=`$ECHO "$lib_search_path " | $SED "s% $path % %g"` # deplibs=`$ECHO "$deplibs " | $SED "s% -L$path % %g"` # dependency_libs=`$ECHO "$dependency_libs " | $SED "s% -L$path % %g"` #done if test -n "$xrpath"; then # If the user specified any rpath flags, then add them. temp_xrpath= for libdir in $xrpath; do func_replace_sysroot "$libdir" func_append temp_xrpath " -R$func_replace_sysroot_result" case "$finalize_rpath " in *" $libdir "*) ;; *) func_append finalize_rpath " $libdir" ;; esac done if test yes != "$hardcode_into_libs" || test yes = "$build_old_libs"; then dependency_libs="$temp_xrpath $dependency_libs" fi fi # Make sure dlfiles contains only unique files that won't be dlpreopened old_dlfiles=$dlfiles dlfiles= for lib in $old_dlfiles; do case " $dlprefiles $dlfiles " in *" $lib "*) ;; *) func_append dlfiles " $lib" ;; esac done # Make sure dlprefiles contains only unique files old_dlprefiles=$dlprefiles dlprefiles= for lib in $old_dlprefiles; do case "$dlprefiles " in *" $lib "*) ;; *) func_append dlprefiles " $lib" ;; esac done if test yes = "$build_libtool_libs"; then if test -n "$rpath"; then case $host in *-*-cygwin* | *-*-mingw* | *-*-pw32* | *-*-os2* | *-*-beos* | *-cegcc* | *-*-haiku*) # these systems don't actually have a c library (as such)! ;; *-*-rhapsody* | *-*-darwin1.[012]) # Rhapsody C library is in the System framework func_append deplibs " System.ltframework" ;; *-*-netbsd*) # Don't link with libc until the a.out ld.so is fixed. ;; *-*-openbsd* | *-*-freebsd* | *-*-dragonfly*) # Do not include libc due to us having libc/libc_r. ;; *-*-sco3.2v5* | *-*-sco5v6*) # Causes problems with __ctype ;; *-*-sysv4.2uw2* | *-*-sysv5* | *-*-unixware* | *-*-OpenUNIX*) # Compiler inserts libc in the correct place for threads to work ;; *) # Add libc to deplibs on all other systems if necessary. if test yes = "$build_libtool_need_lc"; then func_append deplibs " -lc" fi ;; esac fi # Transform deplibs into only deplibs that can be linked in shared. name_save=$name libname_save=$libname release_save=$release versuffix_save=$versuffix major_save=$major # I'm not sure if I'm treating the release correctly. I think # release should show up in the -l (ie -lgmp5) so we don't want to # add it in twice. Is that correct? release= versuffix= major= newdeplibs= droppeddeps=no case $deplibs_check_method in pass_all) # Don't check for shared/static. Everything works. # This might be a little naive. We might want to check # whether the library exists or not. But this is on # osf3 & osf4 and I'm not really sure... Just # implementing what was already the behavior. newdeplibs=$deplibs ;; test_compile) # This code stresses the "libraries are programs" paradigm to its # limits. Maybe even breaks it. We compile a program, linking it # against the deplibs as a proxy for the library. Then we can check # whether they linked in statically or dynamically with ldd. $opt_dry_run || $RM conftest.c cat > conftest.c </dev/null` $nocaseglob else potential_libs=`ls $i/$libnameglob[.-]* 2>/dev/null` fi for potent_lib in $potential_libs; do # Follow soft links. if ls -lLd "$potent_lib" 2>/dev/null | $GREP " -> " >/dev/null; then continue fi # The statement above tries to avoid entering an # endless loop below, in case of cyclic links. # We might still enter an endless loop, since a link # loop can be closed while we follow links, # but so what? potlib=$potent_lib while test -h "$potlib" 2>/dev/null; do potliblink=`ls -ld $potlib | $SED 's/.* -> //'` case $potliblink in [\\/]* | [A-Za-z]:[\\/]*) potlib=$potliblink;; *) potlib=`$ECHO "$potlib" | $SED 's|[^/]*$||'`"$potliblink";; esac done if eval $file_magic_cmd \"\$potlib\" 2>/dev/null | $SED -e 10q | $EGREP "$file_magic_regex" > /dev/null; then func_append newdeplibs " $a_deplib" a_deplib= break 2 fi done done fi if test -n "$a_deplib"; then droppeddeps=yes echo $ECHO "*** Warning: linker path does not have real file for library $a_deplib." echo "*** I have the capability to make that library automatically link in when" echo "*** you link to this library. But I can only do this if you have a" echo "*** shared version of the library, which you do not appear to have" echo "*** because I did check the linker path looking for a file starting" if test -z "$potlib"; then $ECHO "*** with $libname but no candidates were found. (...for file magic test)" else $ECHO "*** with $libname and none of the candidates passed a file format test" $ECHO "*** using a file magic. Last file checked: $potlib" fi fi ;; *) # Add a -L argument. func_append newdeplibs " $a_deplib" ;; esac done # Gone through all deplibs. ;; match_pattern*) set dummy $deplibs_check_method; shift match_pattern_regex=`expr "$deplibs_check_method" : "$1 \(.*\)"` for a_deplib in $deplibs; do case $a_deplib in -l*) func_stripname -l '' "$a_deplib" name=$func_stripname_result if test yes = "$allow_libtool_libs_with_static_runtimes"; then case " $predeps $postdeps " in *" $a_deplib "*) func_append newdeplibs " $a_deplib" a_deplib= ;; esac fi if test -n "$a_deplib"; then libname=`eval "\\$ECHO \"$libname_spec\""` for i in $lib_search_path $sys_lib_search_path $shlib_search_path; do potential_libs=`ls $i/$libname[.-]* 2>/dev/null` for potent_lib in $potential_libs; do potlib=$potent_lib # see symlink-check above in file_magic test if eval "\$ECHO \"$potent_lib\"" 2>/dev/null | $SED 10q | \ $EGREP "$match_pattern_regex" > /dev/null; then func_append newdeplibs " $a_deplib" a_deplib= break 2 fi done done fi if test -n "$a_deplib"; then droppeddeps=yes echo $ECHO "*** Warning: linker path does not have real file for library $a_deplib." echo "*** I have the capability to make that library automatically link in when" echo "*** you link to this library. But I can only do this if you have a" echo "*** shared version of the library, which you do not appear to have" echo "*** because I did check the linker path looking for a file starting" if test -z "$potlib"; then $ECHO "*** with $libname but no candidates were found. (...for regex pattern test)" else $ECHO "*** with $libname and none of the candidates passed a file format test" $ECHO "*** using a regex pattern. Last file checked: $potlib" fi fi ;; *) # Add a -L argument. func_append newdeplibs " $a_deplib" ;; esac done # Gone through all deplibs. ;; none | unknown | *) newdeplibs= tmp_deplibs=`$ECHO " $deplibs" | $SED 's/ -lc$//; s/ -[LR][^ ]*//g'` if test yes = "$allow_libtool_libs_with_static_runtimes"; then for i in $predeps $postdeps; do # can't use Xsed below, because $i might contain '/' tmp_deplibs=`$ECHO " $tmp_deplibs" | $SED "s|$i||"` done fi case $tmp_deplibs in *[!\ \ ]*) echo if test none = "$deplibs_check_method"; then echo "*** Warning: inter-library dependencies are not supported in this platform." else echo "*** Warning: inter-library dependencies are not known to be supported." fi echo "*** All declared inter-library dependencies are being dropped." droppeddeps=yes ;; esac ;; esac versuffix=$versuffix_save major=$major_save release=$release_save libname=$libname_save name=$name_save case $host in *-*-rhapsody* | *-*-darwin1.[012]) # On Rhapsody replace the C library with the System framework newdeplibs=`$ECHO " $newdeplibs" | $SED 's/ -lc / System.ltframework /'` ;; esac if test yes = "$droppeddeps"; then if test yes = "$module"; then echo echo "*** Warning: libtool could not satisfy all declared inter-library" $ECHO "*** dependencies of module $libname. Therefore, libtool will create" echo "*** a static module, that should work as long as the dlopening" echo "*** application is linked with the -dlopen flag." if test -z "$global_symbol_pipe"; then echo echo "*** However, this would only work if libtool was able to extract symbol" echo "*** lists from a program, using 'nm' or equivalent, but libtool could" echo "*** not find such a program. So, this module is probably useless." echo "*** 'nm' from GNU binutils and a full rebuild may help." fi if test no = "$build_old_libs"; then oldlibs=$output_objdir/$libname.$libext build_libtool_libs=module build_old_libs=yes else build_libtool_libs=no fi else echo "*** The inter-library dependencies that have been dropped here will be" echo "*** automatically added whenever a program is linked with this library" echo "*** or is declared to -dlopen it." if test no = "$allow_undefined"; then echo echo "*** Since this library must not contain undefined symbols," echo "*** because either the platform does not support them or" echo "*** it was explicitly requested with -no-undefined," echo "*** libtool will only create a static version of it." if test no = "$build_old_libs"; then oldlibs=$output_objdir/$libname.$libext build_libtool_libs=module build_old_libs=yes else build_libtool_libs=no fi fi fi fi # Done checking deplibs! deplibs=$newdeplibs fi # Time to change all our "foo.ltframework" stuff back to "-framework foo" case $host in *-*-darwin*) newdeplibs=`$ECHO " $newdeplibs" | $SED 's% \([^ $]*\).ltframework% -framework \1%g'` new_inherited_linker_flags=`$ECHO " $new_inherited_linker_flags" | $SED 's% \([^ $]*\).ltframework% -framework \1%g'` deplibs=`$ECHO " $deplibs" | $SED 's% \([^ $]*\).ltframework% -framework \1%g'` ;; esac # move library search paths that coincide with paths to not yet # installed libraries to the beginning of the library search list new_libs= for path in $notinst_path; do case " $new_libs " in *" -L$path/$objdir "*) ;; *) case " $deplibs " in *" -L$path/$objdir "*) func_append new_libs " -L$path/$objdir" ;; esac ;; esac done for deplib in $deplibs; do case $deplib in -L*) case " $new_libs " in *" $deplib "*) ;; *) func_append new_libs " $deplib" ;; esac ;; *) func_append new_libs " $deplib" ;; esac done deplibs=$new_libs # All the library-specific variables (install_libdir is set above). library_names= old_library= dlname= # Test again, we may have decided not to build it any more if test yes = "$build_libtool_libs"; then # Remove $wl instances when linking with ld. # FIXME: should test the right _cmds variable. case $archive_cmds in *\$LD\ *) wl= ;; esac if test yes = "$hardcode_into_libs"; then # Hardcode the library paths hardcode_libdirs= dep_rpath= rpath=$finalize_rpath test relink = "$opt_mode" || rpath=$compile_rpath$rpath for libdir in $rpath; do if test -n "$hardcode_libdir_flag_spec"; then if test -n "$hardcode_libdir_separator"; then func_replace_sysroot "$libdir" libdir=$func_replace_sysroot_result if test -z "$hardcode_libdirs"; then hardcode_libdirs=$libdir else # Just accumulate the unique libdirs. case $hardcode_libdir_separator$hardcode_libdirs$hardcode_libdir_separator in *"$hardcode_libdir_separator$libdir$hardcode_libdir_separator"*) ;; *) func_append hardcode_libdirs "$hardcode_libdir_separator$libdir" ;; esac fi else eval flag=\"$hardcode_libdir_flag_spec\" func_append dep_rpath " $flag" fi elif test -n "$runpath_var"; then case "$perm_rpath " in *" $libdir "*) ;; *) func_append perm_rpath " $libdir" ;; esac fi done # Substitute the hardcoded libdirs into the rpath. if test -n "$hardcode_libdir_separator" && test -n "$hardcode_libdirs"; then libdir=$hardcode_libdirs eval "dep_rpath=\"$hardcode_libdir_flag_spec\"" fi if test -n "$runpath_var" && test -n "$perm_rpath"; then # We should set the runpath_var. rpath= for dir in $perm_rpath; do func_append rpath "$dir:" done eval "$runpath_var='$rpath\$$runpath_var'; export $runpath_var" fi test -n "$dep_rpath" && deplibs="$dep_rpath $deplibs" fi shlibpath=$finalize_shlibpath test relink = "$opt_mode" || shlibpath=$compile_shlibpath$shlibpath if test -n "$shlibpath"; then eval "$shlibpath_var='$shlibpath\$$shlibpath_var'; export $shlibpath_var" fi # Get the real and link names of the library. eval shared_ext=\"$shrext_cmds\" eval library_names=\"$library_names_spec\" set dummy $library_names shift realname=$1 shift if test -n "$soname_spec"; then eval soname=\"$soname_spec\" else soname=$realname fi if test -z "$dlname"; then dlname=$soname fi lib=$output_objdir/$realname linknames= for link do func_append linknames " $link" done # Use standard objects if they are pic test -z "$pic_flag" && libobjs=`$ECHO "$libobjs" | $SP2NL | $SED "$lo2o" | $NL2SP` test "X$libobjs" = "X " && libobjs= delfiles= if test -n "$export_symbols" && test -n "$include_expsyms"; then $opt_dry_run || cp "$export_symbols" "$output_objdir/$libname.uexp" export_symbols=$output_objdir/$libname.uexp func_append delfiles " $export_symbols" fi orig_export_symbols= case $host_os in cygwin* | mingw* | cegcc*) if test -n "$export_symbols" && test -z "$export_symbols_regex"; then # exporting using user supplied symfile func_dll_def_p "$export_symbols" || { # and it's NOT already a .def file. Must figure out # which of the given symbols are data symbols and tag # them as such. So, trigger use of export_symbols_cmds. # export_symbols gets reassigned inside the "prepare # the list of exported symbols" if statement, so the # include_expsyms logic still works. orig_export_symbols=$export_symbols export_symbols= always_export_symbols=yes } fi ;; esac # Prepare the list of exported symbols if test -z "$export_symbols"; then if test yes = "$always_export_symbols" || test -n "$export_symbols_regex"; then func_verbose "generating symbol list for '$libname.la'" export_symbols=$output_objdir/$libname.exp $opt_dry_run || $RM $export_symbols cmds=$export_symbols_cmds save_ifs=$IFS; IFS='~' for cmd1 in $cmds; do IFS=$save_ifs # Take the normal branch if the nm_file_list_spec branch # doesn't work or if tool conversion is not needed. case $nm_file_list_spec~$to_tool_file_cmd in *~func_convert_file_noop | *~func_convert_file_msys_to_w32 | ~*) try_normal_branch=yes eval cmd=\"$cmd1\" func_len " $cmd" len=$func_len_result ;; *) try_normal_branch=no ;; esac if test yes = "$try_normal_branch" \ && { test "$len" -lt "$max_cmd_len" \ || test "$max_cmd_len" -le -1; } then func_show_eval "$cmd" 'exit $?' skipped_export=false elif test -n "$nm_file_list_spec"; then func_basename "$output" output_la=$func_basename_result save_libobjs=$libobjs save_output=$output output=$output_objdir/$output_la.nm func_to_tool_file "$output" libobjs=$nm_file_list_spec$func_to_tool_file_result func_append delfiles " $output" func_verbose "creating $NM input file list: $output" for obj in $save_libobjs; do func_to_tool_file "$obj" $ECHO "$func_to_tool_file_result" done > "$output" eval cmd=\"$cmd1\" func_show_eval "$cmd" 'exit $?' output=$save_output libobjs=$save_libobjs skipped_export=false else # The command line is too long to execute in one step. func_verbose "using reloadable object file for export list..." skipped_export=: # Break out early, otherwise skipped_export may be # set to false by a later but shorter cmd. break fi done IFS=$save_ifs if test -n "$export_symbols_regex" && test : != "$skipped_export"; then func_show_eval '$EGREP -e "$export_symbols_regex" "$export_symbols" > "${export_symbols}T"' func_show_eval '$MV "${export_symbols}T" "$export_symbols"' fi fi fi if test -n "$export_symbols" && test -n "$include_expsyms"; then tmp_export_symbols=$export_symbols test -n "$orig_export_symbols" && tmp_export_symbols=$orig_export_symbols $opt_dry_run || eval '$ECHO "$include_expsyms" | $SP2NL >> "$tmp_export_symbols"' fi if test : != "$skipped_export" && test -n "$orig_export_symbols"; then # The given exports_symbols file has to be filtered, so filter it. func_verbose "filter symbol list for '$libname.la' to tag DATA exports" # FIXME: $output_objdir/$libname.filter potentially contains lots of # 's' commands, which not all seds can handle. GNU sed should be fine # though. Also, the filter scales superlinearly with the number of # global variables. join(1) would be nice here, but unfortunately # isn't a blessed tool. $opt_dry_run || $SED -e '/[ ,]DATA/!d;s,\(.*\)\([ \,].*\),s|^\1$|\1\2|,' < $export_symbols > $output_objdir/$libname.filter func_append delfiles " $export_symbols $output_objdir/$libname.filter" export_symbols=$output_objdir/$libname.def $opt_dry_run || $SED -f $output_objdir/$libname.filter < $orig_export_symbols > $export_symbols fi tmp_deplibs= for test_deplib in $deplibs; do case " $convenience " in *" $test_deplib "*) ;; *) func_append tmp_deplibs " $test_deplib" ;; esac done deplibs=$tmp_deplibs if test -n "$convenience"; then if test -n "$whole_archive_flag_spec" && test yes = "$compiler_needs_object" && test -z "$libobjs"; then # extract the archives, so we have objects to list. # TODO: could optimize this to just extract one archive. whole_archive_flag_spec= fi if test -n "$whole_archive_flag_spec"; then save_libobjs=$libobjs eval libobjs=\"\$libobjs $whole_archive_flag_spec\" test "X$libobjs" = "X " && libobjs= else gentop=$output_objdir/${outputname}x func_append generated " $gentop" func_extract_archives $gentop $convenience func_append libobjs " $func_extract_archives_result" test "X$libobjs" = "X " && libobjs= fi fi if test yes = "$thread_safe" && test -n "$thread_safe_flag_spec"; then eval flag=\"$thread_safe_flag_spec\" func_append linker_flags " $flag" fi # Make a backup of the uninstalled library when relinking if test relink = "$opt_mode"; then $opt_dry_run || eval '(cd $output_objdir && $RM ${realname}U && $MV $realname ${realname}U)' || exit $? fi # Do each of the archive commands. if test yes = "$module" && test -n "$module_cmds"; then if test -n "$export_symbols" && test -n "$module_expsym_cmds"; then eval test_cmds=\"$module_expsym_cmds\" cmds=$module_expsym_cmds else eval test_cmds=\"$module_cmds\" cmds=$module_cmds fi else if test -n "$export_symbols" && test -n "$archive_expsym_cmds"; then eval test_cmds=\"$archive_expsym_cmds\" cmds=$archive_expsym_cmds else eval test_cmds=\"$archive_cmds\" cmds=$archive_cmds fi fi if test : != "$skipped_export" && func_len " $test_cmds" && len=$func_len_result && test "$len" -lt "$max_cmd_len" || test "$max_cmd_len" -le -1; then : else # The command line is too long to link in one step, link piecewise # or, if using GNU ld and skipped_export is not :, use a linker # script. # Save the value of $output and $libobjs because we want to # use them later. If we have whole_archive_flag_spec, we # want to use save_libobjs as it was before # whole_archive_flag_spec was expanded, because we can't # assume the linker understands whole_archive_flag_spec. # This may have to be revisited, in case too many # convenience libraries get linked in and end up exceeding # the spec. if test -z "$convenience" || test -z "$whole_archive_flag_spec"; then save_libobjs=$libobjs fi save_output=$output func_basename "$output" output_la=$func_basename_result # Clear the reloadable object creation command queue and # initialize k to one. test_cmds= concat_cmds= objlist= last_robj= k=1 if test -n "$save_libobjs" && test : != "$skipped_export" && test yes = "$with_gnu_ld"; then output=$output_objdir/$output_la.lnkscript func_verbose "creating GNU ld script: $output" echo 'INPUT (' > $output for obj in $save_libobjs do func_to_tool_file "$obj" $ECHO "$func_to_tool_file_result" >> $output done echo ')' >> $output func_append delfiles " $output" func_to_tool_file "$output" output=$func_to_tool_file_result elif test -n "$save_libobjs" && test : != "$skipped_export" && test -n "$file_list_spec"; then output=$output_objdir/$output_la.lnk func_verbose "creating linker input file list: $output" : > $output set x $save_libobjs shift firstobj= if test yes = "$compiler_needs_object"; then firstobj="$1 " shift fi for obj do func_to_tool_file "$obj" $ECHO "$func_to_tool_file_result" >> $output done func_append delfiles " $output" func_to_tool_file "$output" output=$firstobj\"$file_list_spec$func_to_tool_file_result\" else if test -n "$save_libobjs"; then func_verbose "creating reloadable object files..." output=$output_objdir/$output_la-$k.$objext eval test_cmds=\"$reload_cmds\" func_len " $test_cmds" len0=$func_len_result len=$len0 # Loop over the list of objects to be linked. for obj in $save_libobjs do func_len " $obj" func_arith $len + $func_len_result len=$func_arith_result if test -z "$objlist" || test "$len" -lt "$max_cmd_len"; then func_append objlist " $obj" else # The command $test_cmds is almost too long, add a # command to the queue. if test 1 -eq "$k"; then # The first file doesn't have a previous command to add. reload_objs=$objlist eval concat_cmds=\"$reload_cmds\" else # All subsequent reloadable object files will link in # the last one created. reload_objs="$objlist $last_robj" eval concat_cmds=\"\$concat_cmds~$reload_cmds~\$RM $last_robj\" fi last_robj=$output_objdir/$output_la-$k.$objext func_arith $k + 1 k=$func_arith_result output=$output_objdir/$output_la-$k.$objext objlist=" $obj" func_len " $last_robj" func_arith $len0 + $func_len_result len=$func_arith_result fi done # Handle the remaining objects by creating one last # reloadable object file. All subsequent reloadable object # files will link in the last one created. test -z "$concat_cmds" || concat_cmds=$concat_cmds~ reload_objs="$objlist $last_robj" eval concat_cmds=\"\$concat_cmds$reload_cmds\" if test -n "$last_robj"; then eval concat_cmds=\"\$concat_cmds~\$RM $last_robj\" fi func_append delfiles " $output" else output= fi ${skipped_export-false} && { func_verbose "generating symbol list for '$libname.la'" export_symbols=$output_objdir/$libname.exp $opt_dry_run || $RM $export_symbols libobjs=$output # Append the command to create the export file. test -z "$concat_cmds" || concat_cmds=$concat_cmds~ eval concat_cmds=\"\$concat_cmds$export_symbols_cmds\" if test -n "$last_robj"; then eval concat_cmds=\"\$concat_cmds~\$RM $last_robj\" fi } test -n "$save_libobjs" && func_verbose "creating a temporary reloadable object file: $output" # Loop through the commands generated above and execute them. save_ifs=$IFS; IFS='~' for cmd in $concat_cmds; do IFS=$save_ifs $opt_quiet || { func_quote_for_expand "$cmd" eval "func_echo $func_quote_for_expand_result" } $opt_dry_run || eval "$cmd" || { lt_exit=$? # Restore the uninstalled library and exit if test relink = "$opt_mode"; then ( cd "$output_objdir" && \ $RM "${realname}T" && \ $MV "${realname}U" "$realname" ) fi exit $lt_exit } done IFS=$save_ifs if test -n "$export_symbols_regex" && ${skipped_export-false}; then func_show_eval '$EGREP -e "$export_symbols_regex" "$export_symbols" > "${export_symbols}T"' func_show_eval '$MV "${export_symbols}T" "$export_symbols"' fi fi ${skipped_export-false} && { if test -n "$export_symbols" && test -n "$include_expsyms"; then tmp_export_symbols=$export_symbols test -n "$orig_export_symbols" && tmp_export_symbols=$orig_export_symbols $opt_dry_run || eval '$ECHO "$include_expsyms" | $SP2NL >> "$tmp_export_symbols"' fi if test -n "$orig_export_symbols"; then # The given exports_symbols file has to be filtered, so filter it. func_verbose "filter symbol list for '$libname.la' to tag DATA exports" # FIXME: $output_objdir/$libname.filter potentially contains lots of # 's' commands, which not all seds can handle. GNU sed should be fine # though. Also, the filter scales superlinearly with the number of # global variables. join(1) would be nice here, but unfortunately # isn't a blessed tool. $opt_dry_run || $SED -e '/[ ,]DATA/!d;s,\(.*\)\([ \,].*\),s|^\1$|\1\2|,' < $export_symbols > $output_objdir/$libname.filter func_append delfiles " $export_symbols $output_objdir/$libname.filter" export_symbols=$output_objdir/$libname.def $opt_dry_run || $SED -f $output_objdir/$libname.filter < $orig_export_symbols > $export_symbols fi } libobjs=$output # Restore the value of output. output=$save_output if test -n "$convenience" && test -n "$whole_archive_flag_spec"; then eval libobjs=\"\$libobjs $whole_archive_flag_spec\" test "X$libobjs" = "X " && libobjs= fi # Expand the library linking commands again to reset the # value of $libobjs for piecewise linking. # Do each of the archive commands. if test yes = "$module" && test -n "$module_cmds"; then if test -n "$export_symbols" && test -n "$module_expsym_cmds"; then cmds=$module_expsym_cmds else cmds=$module_cmds fi else if test -n "$export_symbols" && test -n "$archive_expsym_cmds"; then cmds=$archive_expsym_cmds else cmds=$archive_cmds fi fi fi if test -n "$delfiles"; then # Append the command to remove temporary files to $cmds. eval cmds=\"\$cmds~\$RM $delfiles\" fi # Add any objects from preloaded convenience libraries if test -n "$dlprefiles"; then gentop=$output_objdir/${outputname}x func_append generated " $gentop" func_extract_archives $gentop $dlprefiles func_append libobjs " $func_extract_archives_result" test "X$libobjs" = "X " && libobjs= fi save_ifs=$IFS; IFS='~' for cmd in $cmds; do IFS=$sp$nl eval cmd=\"$cmd\" IFS=$save_ifs $opt_quiet || { func_quote_for_expand "$cmd" eval "func_echo $func_quote_for_expand_result" } $opt_dry_run || eval "$cmd" || { lt_exit=$? # Restore the uninstalled library and exit if test relink = "$opt_mode"; then ( cd "$output_objdir" && \ $RM "${realname}T" && \ $MV "${realname}U" "$realname" ) fi exit $lt_exit } done IFS=$save_ifs # Restore the uninstalled library and exit if test relink = "$opt_mode"; then $opt_dry_run || eval '(cd $output_objdir && $RM ${realname}T && $MV $realname ${realname}T && $MV ${realname}U $realname)' || exit $? if test -n "$convenience"; then if test -z "$whole_archive_flag_spec"; then func_show_eval '${RM}r "$gentop"' fi fi exit $EXIT_SUCCESS fi # Create links to the real library. for linkname in $linknames; do if test "$realname" != "$linkname"; then func_show_eval '(cd "$output_objdir" && $RM "$linkname" && $LN_S "$realname" "$linkname")' 'exit $?' fi done # If -module or -export-dynamic was specified, set the dlname. if test yes = "$module" || test yes = "$export_dynamic"; then # On all known operating systems, these are identical. dlname=$soname fi fi ;; obj) if test -n "$dlfiles$dlprefiles" || test no != "$dlself"; then func_warning "'-dlopen' is ignored for objects" fi case " $deplibs" in *\ -l* | *\ -L*) func_warning "'-l' and '-L' are ignored for objects" ;; esac test -n "$rpath" && \ func_warning "'-rpath' is ignored for objects" test -n "$xrpath" && \ func_warning "'-R' is ignored for objects" test -n "$vinfo" && \ func_warning "'-version-info' is ignored for objects" test -n "$release" && \ func_warning "'-release' is ignored for objects" case $output in *.lo) test -n "$objs$old_deplibs" && \ func_fatal_error "cannot build library object '$output' from non-libtool objects" libobj=$output func_lo2o "$libobj" obj=$func_lo2o_result ;; *) libobj= obj=$output ;; esac # Delete the old objects. $opt_dry_run || $RM $obj $libobj # Objects from convenience libraries. This assumes # single-version convenience libraries. Whenever we create # different ones for PIC/non-PIC, this we'll have to duplicate # the extraction. reload_conv_objs= gentop= # if reload_cmds runs $LD directly, get rid of -Wl from # whole_archive_flag_spec and hope we can get by with turning comma # into space. case $reload_cmds in *\$LD[\ \$]*) wl= ;; esac if test -n "$convenience"; then if test -n "$whole_archive_flag_spec"; then eval tmp_whole_archive_flags=\"$whole_archive_flag_spec\" test -n "$wl" || tmp_whole_archive_flags=`$ECHO "$tmp_whole_archive_flags" | $SED 's|,| |g'` reload_conv_objs=$reload_objs\ $tmp_whole_archive_flags else gentop=$output_objdir/${obj}x func_append generated " $gentop" func_extract_archives $gentop $convenience reload_conv_objs="$reload_objs $func_extract_archives_result" fi fi # If we're not building shared, we need to use non_pic_objs test yes = "$build_libtool_libs" || libobjs=$non_pic_objects # Create the old-style object. reload_objs=$objs$old_deplibs' '`$ECHO "$libobjs" | $SP2NL | $SED "/\.$libext$/d; /\.lib$/d; $lo2o" | $NL2SP`' '$reload_conv_objs output=$obj func_execute_cmds "$reload_cmds" 'exit $?' # Exit if we aren't doing a library object file. if test -z "$libobj"; then if test -n "$gentop"; then func_show_eval '${RM}r "$gentop"' fi exit $EXIT_SUCCESS fi test yes = "$build_libtool_libs" || { if test -n "$gentop"; then func_show_eval '${RM}r "$gentop"' fi # Create an invalid libtool object if no PIC, so that we don't # accidentally link it into a program. # $show "echo timestamp > $libobj" # $opt_dry_run || eval "echo timestamp > $libobj" || exit $? exit $EXIT_SUCCESS } if test -n "$pic_flag" || test default != "$pic_mode"; then # Only do commands if we really have different PIC objects. reload_objs="$libobjs $reload_conv_objs" output=$libobj func_execute_cmds "$reload_cmds" 'exit $?' fi if test -n "$gentop"; then func_show_eval '${RM}r "$gentop"' fi exit $EXIT_SUCCESS ;; prog) case $host in *cygwin*) func_stripname '' '.exe' "$output" output=$func_stripname_result.exe;; esac test -n "$vinfo" && \ func_warning "'-version-info' is ignored for programs" test -n "$release" && \ func_warning "'-release' is ignored for programs" $preload \ && test unknown,unknown,unknown = "$dlopen_support,$dlopen_self,$dlopen_self_static" \ && func_warning "'LT_INIT([dlopen])' not used. Assuming no dlopen support." case $host in *-*-rhapsody* | *-*-darwin1.[012]) # On Rhapsody replace the C library is the System framework compile_deplibs=`$ECHO " $compile_deplibs" | $SED 's/ -lc / System.ltframework /'` finalize_deplibs=`$ECHO " $finalize_deplibs" | $SED 's/ -lc / System.ltframework /'` ;; esac case $host in *-*-darwin*) # Don't allow lazy linking, it breaks C++ global constructors # But is supposedly fixed on 10.4 or later (yay!). if test CXX = "$tagname"; then case ${MACOSX_DEPLOYMENT_TARGET-10.0} in 10.[0123]) func_append compile_command " $wl-bind_at_load" func_append finalize_command " $wl-bind_at_load" ;; esac fi # Time to change all our "foo.ltframework" stuff back to "-framework foo" compile_deplibs=`$ECHO " $compile_deplibs" | $SED 's% \([^ $]*\).ltframework% -framework \1%g'` finalize_deplibs=`$ECHO " $finalize_deplibs" | $SED 's% \([^ $]*\).ltframework% -framework \1%g'` ;; esac # move library search paths that coincide with paths to not yet # installed libraries to the beginning of the library search list new_libs= for path in $notinst_path; do case " $new_libs " in *" -L$path/$objdir "*) ;; *) case " $compile_deplibs " in *" -L$path/$objdir "*) func_append new_libs " -L$path/$objdir" ;; esac ;; esac done for deplib in $compile_deplibs; do case $deplib in -L*) case " $new_libs " in *" $deplib "*) ;; *) func_append new_libs " $deplib" ;; esac ;; *) func_append new_libs " $deplib" ;; esac done compile_deplibs=$new_libs func_append compile_command " $compile_deplibs" func_append finalize_command " $finalize_deplibs" if test -n "$rpath$xrpath"; then # If the user specified any rpath flags, then add them. for libdir in $rpath $xrpath; do # This is the magic to use -rpath. case "$finalize_rpath " in *" $libdir "*) ;; *) func_append finalize_rpath " $libdir" ;; esac done fi # Now hardcode the library paths rpath= hardcode_libdirs= for libdir in $compile_rpath $finalize_rpath; do if test -n "$hardcode_libdir_flag_spec"; then if test -n "$hardcode_libdir_separator"; then if test -z "$hardcode_libdirs"; then hardcode_libdirs=$libdir else # Just accumulate the unique libdirs. case $hardcode_libdir_separator$hardcode_libdirs$hardcode_libdir_separator in *"$hardcode_libdir_separator$libdir$hardcode_libdir_separator"*) ;; *) func_append hardcode_libdirs "$hardcode_libdir_separator$libdir" ;; esac fi else eval flag=\"$hardcode_libdir_flag_spec\" func_append rpath " $flag" fi elif test -n "$runpath_var"; then case "$perm_rpath " in *" $libdir "*) ;; *) func_append perm_rpath " $libdir" ;; esac fi case $host in *-*-cygwin* | *-*-mingw* | *-*-pw32* | *-*-os2* | *-cegcc*) testbindir=`$ECHO "$libdir" | $SED -e 's*/lib$*/bin*'` case :$dllsearchpath: in *":$libdir:"*) ;; ::) dllsearchpath=$libdir;; *) func_append dllsearchpath ":$libdir";; esac case :$dllsearchpath: in *":$testbindir:"*) ;; ::) dllsearchpath=$testbindir;; *) func_append dllsearchpath ":$testbindir";; esac ;; esac done # Substitute the hardcoded libdirs into the rpath. if test -n "$hardcode_libdir_separator" && test -n "$hardcode_libdirs"; then libdir=$hardcode_libdirs eval rpath=\" $hardcode_libdir_flag_spec\" fi compile_rpath=$rpath rpath= hardcode_libdirs= for libdir in $finalize_rpath; do if test -n "$hardcode_libdir_flag_spec"; then if test -n "$hardcode_libdir_separator"; then if test -z "$hardcode_libdirs"; then hardcode_libdirs=$libdir else # Just accumulate the unique libdirs. case $hardcode_libdir_separator$hardcode_libdirs$hardcode_libdir_separator in *"$hardcode_libdir_separator$libdir$hardcode_libdir_separator"*) ;; *) func_append hardcode_libdirs "$hardcode_libdir_separator$libdir" ;; esac fi else eval flag=\"$hardcode_libdir_flag_spec\" func_append rpath " $flag" fi elif test -n "$runpath_var"; then case "$finalize_perm_rpath " in *" $libdir "*) ;; *) func_append finalize_perm_rpath " $libdir" ;; esac fi done # Substitute the hardcoded libdirs into the rpath. if test -n "$hardcode_libdir_separator" && test -n "$hardcode_libdirs"; then libdir=$hardcode_libdirs eval rpath=\" $hardcode_libdir_flag_spec\" fi finalize_rpath=$rpath if test -n "$libobjs" && test yes = "$build_old_libs"; then # Transform all the library objects into standard objects. compile_command=`$ECHO "$compile_command" | $SP2NL | $SED "$lo2o" | $NL2SP` finalize_command=`$ECHO "$finalize_command" | $SP2NL | $SED "$lo2o" | $NL2SP` fi func_generate_dlsyms "$outputname" "@PROGRAM@" false # template prelinking step if test -n "$prelink_cmds"; then func_execute_cmds "$prelink_cmds" 'exit $?' fi wrappers_required=: case $host in *cegcc* | *mingw32ce*) # Disable wrappers for cegcc and mingw32ce hosts, we are cross compiling anyway. wrappers_required=false ;; *cygwin* | *mingw* ) test yes = "$build_libtool_libs" || wrappers_required=false ;; *) if test no = "$need_relink" || test yes != "$build_libtool_libs"; then wrappers_required=false fi ;; esac $wrappers_required || { # Replace the output file specification. compile_command=`$ECHO "$compile_command" | $SED 's%@OUTPUT@%'"$output"'%g'` link_command=$compile_command$compile_rpath # We have no uninstalled library dependencies, so finalize right now. exit_status=0 func_show_eval "$link_command" 'exit_status=$?' if test -n "$postlink_cmds"; then func_to_tool_file "$output" postlink_cmds=`func_echo_all "$postlink_cmds" | $SED -e 's%@OUTPUT@%'"$output"'%g' -e 's%@TOOL_OUTPUT@%'"$func_to_tool_file_result"'%g'` func_execute_cmds "$postlink_cmds" 'exit $?' fi # Delete the generated files. if test -f "$output_objdir/${outputname}S.$objext"; then func_show_eval '$RM "$output_objdir/${outputname}S.$objext"' fi exit $exit_status } if test -n "$compile_shlibpath$finalize_shlibpath"; then compile_command="$shlibpath_var=\"$compile_shlibpath$finalize_shlibpath\$$shlibpath_var\" $compile_command" fi if test -n "$finalize_shlibpath"; then finalize_command="$shlibpath_var=\"$finalize_shlibpath\$$shlibpath_var\" $finalize_command" fi compile_var= finalize_var= if test -n "$runpath_var"; then if test -n "$perm_rpath"; then # We should set the runpath_var. rpath= for dir in $perm_rpath; do func_append rpath "$dir:" done compile_var="$runpath_var=\"$rpath\$$runpath_var\" " fi if test -n "$finalize_perm_rpath"; then # We should set the runpath_var. rpath= for dir in $finalize_perm_rpath; do func_append rpath "$dir:" done finalize_var="$runpath_var=\"$rpath\$$runpath_var\" " fi fi if test yes = "$no_install"; then # We don't need to create a wrapper script. link_command=$compile_var$compile_command$compile_rpath # Replace the output file specification. link_command=`$ECHO "$link_command" | $SED 's%@OUTPUT@%'"$output"'%g'` # Delete the old output file. $opt_dry_run || $RM $output # Link the executable and exit func_show_eval "$link_command" 'exit $?' if test -n "$postlink_cmds"; then func_to_tool_file "$output" postlink_cmds=`func_echo_all "$postlink_cmds" | $SED -e 's%@OUTPUT@%'"$output"'%g' -e 's%@TOOL_OUTPUT@%'"$func_to_tool_file_result"'%g'` func_execute_cmds "$postlink_cmds" 'exit $?' fi exit $EXIT_SUCCESS fi case $hardcode_action,$fast_install in relink,*) # Fast installation is not supported link_command=$compile_var$compile_command$compile_rpath relink_command=$finalize_var$finalize_command$finalize_rpath func_warning "this platform does not like uninstalled shared libraries" func_warning "'$output' will be relinked during installation" ;; *,yes) link_command=$finalize_var$compile_command$finalize_rpath relink_command=`$ECHO "$compile_var$compile_command$compile_rpath" | $SED 's%@OUTPUT@%\$progdir/\$file%g'` ;; *,no) link_command=$compile_var$compile_command$compile_rpath relink_command=$finalize_var$finalize_command$finalize_rpath ;; *,needless) link_command=$finalize_var$compile_command$finalize_rpath relink_command= ;; esac # Replace the output file specification. link_command=`$ECHO "$link_command" | $SED 's%@OUTPUT@%'"$output_objdir/$outputname"'%g'` # Delete the old output files. $opt_dry_run || $RM $output $output_objdir/$outputname $output_objdir/lt-$outputname func_show_eval "$link_command" 'exit $?' if test -n "$postlink_cmds"; then func_to_tool_file "$output_objdir/$outputname" postlink_cmds=`func_echo_all "$postlink_cmds" | $SED -e 's%@OUTPUT@%'"$output_objdir/$outputname"'%g' -e 's%@TOOL_OUTPUT@%'"$func_to_tool_file_result"'%g'` func_execute_cmds "$postlink_cmds" 'exit $?' fi # Now create the wrapper script. func_verbose "creating $output" # Quote the relink command for shipping. if test -n "$relink_command"; then # Preserve any variables that may affect compiler behavior for var in $variables_saved_for_relink; do if eval test -z \"\${$var+set}\"; then relink_command="{ test -z \"\${$var+set}\" || $lt_unset $var || { $var=; export $var; }; }; $relink_command" elif eval var_value=\$$var; test -z "$var_value"; then relink_command="$var=; export $var; $relink_command" else func_quote_for_eval "$var_value" relink_command="$var=$func_quote_for_eval_result; export $var; $relink_command" fi done relink_command="(cd `pwd`; $relink_command)" relink_command=`$ECHO "$relink_command" | $SED "$sed_quote_subst"` fi # Only actually do things if not in dry run mode. $opt_dry_run || { # win32 will think the script is a binary if it has # a .exe suffix, so we strip it off here. case $output in *.exe) func_stripname '' '.exe' "$output" output=$func_stripname_result ;; esac # test for cygwin because mv fails w/o .exe extensions case $host in *cygwin*) exeext=.exe func_stripname '' '.exe' "$outputname" outputname=$func_stripname_result ;; *) exeext= ;; esac case $host in *cygwin* | *mingw* ) func_dirname_and_basename "$output" "" "." output_name=$func_basename_result output_path=$func_dirname_result cwrappersource=$output_path/$objdir/lt-$output_name.c cwrapper=$output_path/$output_name.exe $RM $cwrappersource $cwrapper trap "$RM $cwrappersource $cwrapper; exit $EXIT_FAILURE" 1 2 15 func_emit_cwrapperexe_src > $cwrappersource # The wrapper executable is built using the $host compiler, # because it contains $host paths and files. If cross- # compiling, it, like the target executable, must be # executed on the $host or under an emulation environment. $opt_dry_run || { $LTCC $LTCFLAGS -o $cwrapper $cwrappersource $STRIP $cwrapper } # Now, create the wrapper script for func_source use: func_ltwrapper_scriptname $cwrapper $RM $func_ltwrapper_scriptname_result trap "$RM $func_ltwrapper_scriptname_result; exit $EXIT_FAILURE" 1 2 15 $opt_dry_run || { # note: this script will not be executed, so do not chmod. if test "x$build" = "x$host"; then $cwrapper --lt-dump-script > $func_ltwrapper_scriptname_result else func_emit_wrapper no > $func_ltwrapper_scriptname_result fi } ;; * ) $RM $output trap "$RM $output; exit $EXIT_FAILURE" 1 2 15 func_emit_wrapper no > $output chmod +x $output ;; esac } exit $EXIT_SUCCESS ;; esac # See if we need to build an old-fashioned archive. for oldlib in $oldlibs; do case $build_libtool_libs in convenience) oldobjs="$libobjs_save $symfileobj" addlibs=$convenience build_libtool_libs=no ;; module) oldobjs=$libobjs_save addlibs=$old_convenience build_libtool_libs=no ;; *) oldobjs="$old_deplibs $non_pic_objects" $preload && test -f "$symfileobj" \ && func_append oldobjs " $symfileobj" addlibs=$old_convenience ;; esac if test -n "$addlibs"; then gentop=$output_objdir/${outputname}x func_append generated " $gentop" func_extract_archives $gentop $addlibs func_append oldobjs " $func_extract_archives_result" fi # Do each command in the archive commands. if test -n "$old_archive_from_new_cmds" && test yes = "$build_libtool_libs"; then cmds=$old_archive_from_new_cmds else # Add any objects from preloaded convenience libraries if test -n "$dlprefiles"; then gentop=$output_objdir/${outputname}x func_append generated " $gentop" func_extract_archives $gentop $dlprefiles func_append oldobjs " $func_extract_archives_result" fi # POSIX demands no paths to be encoded in archives. We have # to avoid creating archives with duplicate basenames if we # might have to extract them afterwards, e.g., when creating a # static archive out of a convenience library, or when linking # the entirety of a libtool archive into another (currently # not supported by libtool). if (for obj in $oldobjs do func_basename "$obj" $ECHO "$func_basename_result" done | sort | sort -uc >/dev/null 2>&1); then : else echo "copying selected object files to avoid basename conflicts..." gentop=$output_objdir/${outputname}x func_append generated " $gentop" func_mkdir_p "$gentop" save_oldobjs=$oldobjs oldobjs= counter=1 for obj in $save_oldobjs do func_basename "$obj" objbase=$func_basename_result case " $oldobjs " in " ") oldobjs=$obj ;; *[\ /]"$objbase "*) while :; do # Make sure we don't pick an alternate name that also # overlaps. newobj=lt$counter-$objbase func_arith $counter + 1 counter=$func_arith_result case " $oldobjs " in *[\ /]"$newobj "*) ;; *) if test ! -f "$gentop/$newobj"; then break; fi ;; esac done func_show_eval "ln $obj $gentop/$newobj || cp $obj $gentop/$newobj" func_append oldobjs " $gentop/$newobj" ;; *) func_append oldobjs " $obj" ;; esac done fi func_to_tool_file "$oldlib" func_convert_file_msys_to_w32 tool_oldlib=$func_to_tool_file_result eval cmds=\"$old_archive_cmds\" func_len " $cmds" len=$func_len_result if test "$len" -lt "$max_cmd_len" || test "$max_cmd_len" -le -1; then cmds=$old_archive_cmds elif test -n "$archiver_list_spec"; then func_verbose "using command file archive linking..." for obj in $oldobjs do func_to_tool_file "$obj" $ECHO "$func_to_tool_file_result" done > $output_objdir/$libname.libcmd func_to_tool_file "$output_objdir/$libname.libcmd" oldobjs=" $archiver_list_spec$func_to_tool_file_result" cmds=$old_archive_cmds else # the command line is too long to link in one step, link in parts func_verbose "using piecewise archive linking..." save_RANLIB=$RANLIB RANLIB=: objlist= concat_cmds= save_oldobjs=$oldobjs oldobjs= # Is there a better way of finding the last object in the list? for obj in $save_oldobjs do last_oldobj=$obj done eval test_cmds=\"$old_archive_cmds\" func_len " $test_cmds" len0=$func_len_result len=$len0 for obj in $save_oldobjs do func_len " $obj" func_arith $len + $func_len_result len=$func_arith_result func_append objlist " $obj" if test "$len" -lt "$max_cmd_len"; then : else # the above command should be used before it gets too long oldobjs=$objlist if test "$obj" = "$last_oldobj"; then RANLIB=$save_RANLIB fi test -z "$concat_cmds" || concat_cmds=$concat_cmds~ eval concat_cmds=\"\$concat_cmds$old_archive_cmds\" objlist= len=$len0 fi done RANLIB=$save_RANLIB oldobjs=$objlist if test -z "$oldobjs"; then eval cmds=\"\$concat_cmds\" else eval cmds=\"\$concat_cmds~\$old_archive_cmds\" fi fi fi func_execute_cmds "$cmds" 'exit $?' done test -n "$generated" && \ func_show_eval "${RM}r$generated" # Now create the libtool archive. case $output in *.la) old_library= test yes = "$build_old_libs" && old_library=$libname.$libext func_verbose "creating $output" # Preserve any variables that may affect compiler behavior for var in $variables_saved_for_relink; do if eval test -z \"\${$var+set}\"; then relink_command="{ test -z \"\${$var+set}\" || $lt_unset $var || { $var=; export $var; }; }; $relink_command" elif eval var_value=\$$var; test -z "$var_value"; then relink_command="$var=; export $var; $relink_command" else func_quote_for_eval "$var_value" relink_command="$var=$func_quote_for_eval_result; export $var; $relink_command" fi done # Quote the link command for shipping. relink_command="(cd `pwd`; $SHELL \"$progpath\" $preserve_args --mode=relink $libtool_args @inst_prefix_dir@)" relink_command=`$ECHO "$relink_command" | $SED "$sed_quote_subst"` if test yes = "$hardcode_automatic"; then relink_command= fi # Only create the output if not a dry run. $opt_dry_run || { for installed in no yes; do if test yes = "$installed"; then if test -z "$install_libdir"; then break fi output=$output_objdir/${outputname}i # Replace all uninstalled libtool libraries with the installed ones newdependency_libs= for deplib in $dependency_libs; do case $deplib in *.la) func_basename "$deplib" name=$func_basename_result func_resolve_sysroot "$deplib" eval libdir=`$SED -n -e 's/^libdir=\(.*\)$/\1/p' $func_resolve_sysroot_result` test -z "$libdir" && \ func_fatal_error "'$deplib' is not a valid libtool archive" func_append newdependency_libs " ${lt_sysroot:+=}$libdir/$name" ;; -L*) func_stripname -L '' "$deplib" func_replace_sysroot "$func_stripname_result" func_append newdependency_libs " -L$func_replace_sysroot_result" ;; -R*) func_stripname -R '' "$deplib" func_replace_sysroot "$func_stripname_result" func_append newdependency_libs " -R$func_replace_sysroot_result" ;; *) func_append newdependency_libs " $deplib" ;; esac done dependency_libs=$newdependency_libs newdlfiles= for lib in $dlfiles; do case $lib in *.la) func_basename "$lib" name=$func_basename_result eval libdir=`$SED -n -e 's/^libdir=\(.*\)$/\1/p' $lib` test -z "$libdir" && \ func_fatal_error "'$lib' is not a valid libtool archive" func_append newdlfiles " ${lt_sysroot:+=}$libdir/$name" ;; *) func_append newdlfiles " $lib" ;; esac done dlfiles=$newdlfiles newdlprefiles= for lib in $dlprefiles; do case $lib in *.la) # Only pass preopened files to the pseudo-archive (for # eventual linking with the app. that links it) if we # didn't already link the preopened objects directly into # the library: func_basename "$lib" name=$func_basename_result eval libdir=`$SED -n -e 's/^libdir=\(.*\)$/\1/p' $lib` test -z "$libdir" && \ func_fatal_error "'$lib' is not a valid libtool archive" func_append newdlprefiles " ${lt_sysroot:+=}$libdir/$name" ;; esac done dlprefiles=$newdlprefiles else newdlfiles= for lib in $dlfiles; do case $lib in [\\/]* | [A-Za-z]:[\\/]*) abs=$lib ;; *) abs=`pwd`"/$lib" ;; esac func_append newdlfiles " $abs" done dlfiles=$newdlfiles newdlprefiles= for lib in $dlprefiles; do case $lib in [\\/]* | [A-Za-z]:[\\/]*) abs=$lib ;; *) abs=`pwd`"/$lib" ;; esac func_append newdlprefiles " $abs" done dlprefiles=$newdlprefiles fi $RM $output # place dlname in correct position for cygwin # In fact, it would be nice if we could use this code for all target # systems that can't hard-code library paths into their executables # and that have no shared library path variable independent of PATH, # but it turns out we can't easily determine that from inspecting # libtool variables, so we have to hard-code the OSs to which it # applies here; at the moment, that means platforms that use the PE # object format with DLL files. See the long comment at the top of # tests/bindir.at for full details. tdlname=$dlname case $host,$output,$installed,$module,$dlname in *cygwin*,*lai,yes,no,*.dll | *mingw*,*lai,yes,no,*.dll | *cegcc*,*lai,yes,no,*.dll) # If a -bindir argument was supplied, place the dll there. if test -n "$bindir"; then func_relative_path "$install_libdir" "$bindir" tdlname=$func_relative_path_result/$dlname else # Otherwise fall back on heuristic. tdlname=../bin/$dlname fi ;; esac $ECHO > $output "\ # $outputname - a libtool library file # Generated by $PROGRAM (GNU $PACKAGE) $VERSION # # Please DO NOT delete this file! # It is necessary for linking the library. # The name that we can dlopen(3). dlname='$tdlname' # Names of this library. library_names='$library_names' # The name of the static archive. old_library='$old_library' # Linker flags that cannot go in dependency_libs. inherited_linker_flags='$new_inherited_linker_flags' # Libraries that this one depends upon. dependency_libs='$dependency_libs' # Names of additional weak libraries provided by this library weak_library_names='$weak_libs' # Version information for $libname. current=$current age=$age revision=$revision # Is this an already installed library? installed=$installed # Should we warn about portability when linking against -modules? shouldnotlink=$module # Files to dlopen/dlpreopen dlopen='$dlfiles' dlpreopen='$dlprefiles' # Directory that this library needs to be installed in: libdir='$install_libdir'" if test no,yes = "$installed,$need_relink"; then $ECHO >> $output "\ relink_command=\"$relink_command\"" fi done } # Do a symbolic link so that the libtool archive can be found in # LD_LIBRARY_PATH before the program is installed. func_show_eval '( cd "$output_objdir" && $RM "$outputname" && $LN_S "../$outputname" "$outputname" )' 'exit $?' ;; esac exit $EXIT_SUCCESS } if test link = "$opt_mode" || test relink = "$opt_mode"; then func_mode_link ${1+"$@"} fi # func_mode_uninstall arg... func_mode_uninstall () { $debug_cmd RM=$nonopt files= rmforce=false exit_status=0 # This variable tells wrapper scripts just to set variables rather # than running their programs. libtool_install_magic=$magic for arg do case $arg in -f) func_append RM " $arg"; rmforce=: ;; -*) func_append RM " $arg" ;; *) func_append files " $arg" ;; esac done test -z "$RM" && \ func_fatal_help "you must specify an RM program" rmdirs= for file in $files; do func_dirname "$file" "" "." dir=$func_dirname_result if test . = "$dir"; then odir=$objdir else odir=$dir/$objdir fi func_basename "$file" name=$func_basename_result test uninstall = "$opt_mode" && odir=$dir # Remember odir for removal later, being careful to avoid duplicates if test clean = "$opt_mode"; then case " $rmdirs " in *" $odir "*) ;; *) func_append rmdirs " $odir" ;; esac fi # Don't error if the file doesn't exist and rm -f was used. if { test -L "$file"; } >/dev/null 2>&1 || { test -h "$file"; } >/dev/null 2>&1 || test -f "$file"; then : elif test -d "$file"; then exit_status=1 continue elif $rmforce; then continue fi rmfiles=$file case $name in *.la) # Possibly a libtool archive, so verify it. if func_lalib_p "$file"; then func_source $dir/$name # Delete the libtool libraries and symlinks. for n in $library_names; do func_append rmfiles " $odir/$n" done test -n "$old_library" && func_append rmfiles " $odir/$old_library" case $opt_mode in clean) case " $library_names " in *" $dlname "*) ;; *) test -n "$dlname" && func_append rmfiles " $odir/$dlname" ;; esac test -n "$libdir" && func_append rmfiles " $odir/$name $odir/${name}i" ;; uninstall) if test -n "$library_names"; then # Do each command in the postuninstall commands. func_execute_cmds "$postuninstall_cmds" '$rmforce || exit_status=1' fi if test -n "$old_library"; then # Do each command in the old_postuninstall commands. func_execute_cmds "$old_postuninstall_cmds" '$rmforce || exit_status=1' fi # FIXME: should reinstall the best remaining shared library. ;; esac fi ;; *.lo) # Possibly a libtool object, so verify it. if func_lalib_p "$file"; then # Read the .lo file func_source $dir/$name # Add PIC object to the list of files to remove. if test -n "$pic_object" && test none != "$pic_object"; then func_append rmfiles " $dir/$pic_object" fi # Add non-PIC object to the list of files to remove. if test -n "$non_pic_object" && test none != "$non_pic_object"; then func_append rmfiles " $dir/$non_pic_object" fi fi ;; *) if test clean = "$opt_mode"; then noexename=$name case $file in *.exe) func_stripname '' '.exe' "$file" file=$func_stripname_result func_stripname '' '.exe' "$name" noexename=$func_stripname_result # $file with .exe has already been added to rmfiles, # add $file without .exe func_append rmfiles " $file" ;; esac # Do a test to see if this is a libtool program. if func_ltwrapper_p "$file"; then if func_ltwrapper_executable_p "$file"; then func_ltwrapper_scriptname "$file" relink_command= func_source $func_ltwrapper_scriptname_result func_append rmfiles " $func_ltwrapper_scriptname_result" else relink_command= func_source $dir/$noexename fi # note $name still contains .exe if it was in $file originally # as does the version of $file that was added into $rmfiles func_append rmfiles " $odir/$name $odir/${name}S.$objext" if test yes = "$fast_install" && test -n "$relink_command"; then func_append rmfiles " $odir/lt-$name" fi if test "X$noexename" != "X$name"; then func_append rmfiles " $odir/lt-$noexename.c" fi fi fi ;; esac func_show_eval "$RM $rmfiles" 'exit_status=1' done # Try to remove the $objdir's in the directories where we deleted files for dir in $rmdirs; do if test -d "$dir"; then func_show_eval "rmdir $dir >/dev/null 2>&1" fi done exit $exit_status } if test uninstall = "$opt_mode" || test clean = "$opt_mode"; then func_mode_uninstall ${1+"$@"} fi test -z "$opt_mode" && { help=$generic_help func_fatal_help "you must specify a MODE" } test -z "$exec_cmd" && \ func_fatal_help "invalid operation mode '$opt_mode'" if test -n "$exec_cmd"; then eval exec "$exec_cmd" exit $EXIT_FAILURE fi exit $exit_status # The TAGs below are defined such that we never get into a situation # where we disable both kinds of libraries. Given conflicting # choices, we go for a static library, that is the most portable, # since we can't tell whether shared libraries were disabled because # the user asked for that or because the platform doesn't support # them. This is particularly important on AIX, because we don't # support having both static and shared libraries enabled at the same # time on that platform, so we default to a shared-only configuration. # If a disable-shared tag is given, we'll fallback to a static-only # configuration. But we'll never go from static-only to shared-only. # ### BEGIN LIBTOOL TAG CONFIG: disable-shared build_libtool_libs=no build_old_libs=yes # ### END LIBTOOL TAG CONFIG: disable-shared # ### BEGIN LIBTOOL TAG CONFIG: disable-static build_old_libs=`case $build_libtool_libs in yes) echo no;; *) echo yes;; esac` # ### END LIBTOOL TAG CONFIG: disable-static # Local Variables: # mode:shell-script # sh-indentation:2 # End: frr-7.2.1/m4/0000755000000000000000000000000013610377563007614 500000000000000frr-7.2.1/m4/README.txt0000644000000000000000000000077213610377563011240 00000000000000This directory contains local additions to/overrides for the Quagga autoconf build system. At this time additions are: - m4 files taken from libtool CVS circa august 2004. These have been imported into Quagga as they are more robust with respect to configuring libtool support for languages which Quagga does not use. As and when libtool releases become commonly available with that capability, these can be removed. The files are: argz.m4 libtool.m4 ltdl.m4 ltoptions.m4 ltsugar.m4 ltversion.m4 frr-7.2.1/m4/ax_compare_version.m40000644000000000000000000001465213610377563013671 00000000000000# =========================================================================== # http://www.gnu.org/software/autoconf-archive/ax_compare_version.html # =========================================================================== # # SYNOPSIS # # AX_COMPARE_VERSION(VERSION_A, OP, VERSION_B, [ACTION-IF-TRUE], [ACTION-IF-FALSE]) # # DESCRIPTION # # This macro compares two version strings. Due to the various number of # minor-version numbers that can exist, and the fact that string # comparisons are not compatible with numeric comparisons, this is not # necessarily trivial to do in a autoconf script. This macro makes doing # these comparisons easy. # # The six basic comparisons are available, as well as checking equality # limited to a certain number of minor-version levels. # # The operator OP determines what type of comparison to do, and can be one # of: # # eq - equal (test A == B) # ne - not equal (test A != B) # le - less than or equal (test A <= B) # ge - greater than or equal (test A >= B) # lt - less than (test A < B) # gt - greater than (test A > B) # # Additionally, the eq and ne operator can have a number after it to limit # the test to that number of minor versions. # # eq0 - equal up to the length of the shorter version # ne0 - not equal up to the length of the shorter version # eqN - equal up to N sub-version levels # neN - not equal up to N sub-version levels # # When the condition is true, shell commands ACTION-IF-TRUE are run, # otherwise shell commands ACTION-IF-FALSE are run. The environment # variable 'ax_compare_version' is always set to either 'true' or 'false' # as well. # # Examples: # # AX_COMPARE_VERSION([3.15.7],[lt],[3.15.8]) # AX_COMPARE_VERSION([3.15],[lt],[3.15.8]) # # would both be true. # # AX_COMPARE_VERSION([3.15.7],[eq],[3.15.8]) # AX_COMPARE_VERSION([3.15],[gt],[3.15.8]) # # would both be false. # # AX_COMPARE_VERSION([3.15.7],[eq2],[3.15.8]) # # would be true because it is only comparing two minor versions. # # AX_COMPARE_VERSION([3.15.7],[eq0],[3.15]) # # would be true because it is only comparing the lesser number of minor # versions of the two values. # # Note: The characters that separate the version numbers do not matter. An # empty string is the same as version 0. OP is evaluated by autoconf, not # configure, so must be a string, not a variable. # # The author would like to acknowledge Guido Draheim whose advice about # the m4_case and m4_ifvaln functions make this macro only include the # portions necessary to perform the specific comparison specified by the # OP argument in the final configure script. # # LICENSE # # Copyright (c) 2008 Tim Toolan # # Copying and distribution of this file, with or without modification, are # permitted in any medium without royalty provided the copyright notice # and this notice are preserved. This file is offered as-is, without any # warranty. #serial 11 dnl ######################################################################### AC_DEFUN([AX_COMPARE_VERSION], [ AC_REQUIRE([AC_PROG_AWK]) # Used to indicate true or false condition ax_compare_version=false # Convert the two version strings to be compared into a format that # allows a simple string comparison. The end result is that a version # string of the form 1.12.5-r617 will be converted to the form # 0001001200050617. In other words, each number is zero padded to four # digits, and non digits are removed. AS_VAR_PUSHDEF([A],[ax_compare_version_A]) A=`echo "$1" | sed -e 's/\([[0-9]]*\)/Z\1Z/g' \ -e 's/Z\([[0-9]]\)Z/Z0\1Z/g' \ -e 's/Z\([[0-9]][[0-9]]\)Z/Z0\1Z/g' \ -e 's/Z\([[0-9]][[0-9]][[0-9]]\)Z/Z0\1Z/g' \ -e 's/[[^0-9]]//g'` AS_VAR_PUSHDEF([B],[ax_compare_version_B]) B=`echo "$3" | sed -e 's/\([[0-9]]*\)/Z\1Z/g' \ -e 's/Z\([[0-9]]\)Z/Z0\1Z/g' \ -e 's/Z\([[0-9]][[0-9]]\)Z/Z0\1Z/g' \ -e 's/Z\([[0-9]][[0-9]][[0-9]]\)Z/Z0\1Z/g' \ -e 's/[[^0-9]]//g'` dnl # In the case of le, ge, lt, and gt, the strings are sorted as necessary dnl # then the first line is used to determine if the condition is true. dnl # The sed right after the echo is to remove any indented white space. m4_case(m4_tolower($2), [lt],[ ax_compare_version=`echo "x$A x$B" | sed 's/^ *//' | sort -r | sed "s/x${A}/false/;s/x${B}/true/;1q"` ], [gt],[ ax_compare_version=`echo "x$A x$B" | sed 's/^ *//' | sort | sed "s/x${A}/false/;s/x${B}/true/;1q"` ], [le],[ ax_compare_version=`echo "x$A x$B" | sed 's/^ *//' | sort | sed "s/x${A}/true/;s/x${B}/false/;1q"` ], [ge],[ ax_compare_version=`echo "x$A x$B" | sed 's/^ *//' | sort -r | sed "s/x${A}/true/;s/x${B}/false/;1q"` ],[ dnl Split the operator from the subversion count if present. m4_bmatch(m4_substr($2,2), [0],[ # A count of zero means use the length of the shorter version. # Determine the number of characters in A and B. ax_compare_version_len_A=`echo "$A" | $AWK '{print(length)}'` ax_compare_version_len_B=`echo "$B" | $AWK '{print(length)}'` # Set A to no more than B's length and B to no more than A's length. A=`echo "$A" | sed "s/\(.\{$ax_compare_version_len_B\}\).*/\1/"` B=`echo "$B" | sed "s/\(.\{$ax_compare_version_len_A\}\).*/\1/"` ], [[0-9]+],[ # A count greater than zero means use only that many subversions A=`echo "$A" | sed "s/\(\([[0-9]]\{4\}\)\{m4_substr($2,2)\}\).*/\1/"` B=`echo "$B" | sed "s/\(\([[0-9]]\{4\}\)\{m4_substr($2,2)\}\).*/\1/"` ], [.+],[ AC_WARNING( [illegal OP numeric parameter: $2]) ],[]) # Pad zeros at end of numbers to make same length. ax_compare_version_tmp_A="$A`echo $B | sed 's/./0/g'`" B="$B`echo $A | sed 's/./0/g'`" A="$ax_compare_version_tmp_A" # Check for equality or inequality as necessary. m4_case(m4_tolower(m4_substr($2,0,2)), [eq],[ test "x$A" = "x$B" && ax_compare_version=true ], [ne],[ test "x$A" != "x$B" && ax_compare_version=true ],[ AC_WARNING([illegal OP parameter: $2]) ]) ]) AS_VAR_POPDEF([A])dnl AS_VAR_POPDEF([B])dnl dnl # Execute ACTION-IF-TRUE / ACTION-IF-FALSE. if test "$ax_compare_version" = "true" ; then m4_ifvaln([$4],[$4],[:])dnl m4_ifvaln([$5],[else $5])dnl fi ]) dnl AX_COMPARE_VERSION frr-7.2.1/m4/ax_lua.m40000644000000000000000000006360113610377563011255 00000000000000# =========================================================================== # http://www.gnu.org/software/autoconf-archive/ax_lua.html # =========================================================================== # # SYNOPSIS # # AX_PROG_LUA[([MINIMUM-VERSION], [TOO-BIG-VERSION], [ACTION-IF-FOUND], [ACTION-IF-NOT-FOUND])] # AX_LUA_HEADERS[([ACTION-IF-FOUND], [ACTION-IF-NOT-FOUND])] # AX_LUA_LIBS[([ACTION-IF-FOUND], [ACTION-IF-NOT-FOUND])] # AX_LUA_READLINE[([ACTION-IF-FOUND], [ACTION-IF-NOT-FOUND])] # # DESCRIPTION # # Detect a Lua interpreter, optionally specifying a minimum and maximum # version number. Set up important Lua paths, such as the directories in # which to install scripts and modules (shared libraries). # # Also detect Lua headers and libraries. The Lua version contained in the # header is checked to match the Lua interpreter version exactly. When # searching for Lua libraries, the version number is used as a suffix. # This is done with the goal of supporting multiple Lua installs (5.1, # 5.2, and 5.3 side-by-side). # # A note on compatibility with previous versions: This file has been # mostly rewritten for serial 18. Most developers should be able to use # these macros without needing to modify configure.ac. Care has been taken # to preserve each macro's behavior, but there are some differences: # # 1) AX_WITH_LUA is deprecated; it now expands to the exact same thing as # AX_PROG_LUA with no arguments. # # 2) AX_LUA_HEADERS now checks that the version number defined in lua.h # matches the interpreter version. AX_LUA_HEADERS_VERSION is therefore # unnecessary, so it is deprecated and does not expand to anything. # # 3) The configure flag --with-lua-suffix no longer exists; the user # should instead specify the LUA precious variable on the command line. # See the AX_PROG_LUA description for details. # # Please read the macro descriptions below for more information. # # This file was inspired by Andrew Dalke's and James Henstridge's # python.m4 and Tom Payne's, Matthieu Moy's, and Reuben Thomas's ax_lua.m4 # (serial 17). Basically, this file is a mash-up of those two files. I # like to think it combines the best of the two! # # AX_PROG_LUA: Search for the Lua interpreter, and set up important Lua # paths. Adds precious variable LUA, which may contain the path of the Lua # interpreter. If LUA is blank, the user's path is searched for an # suitable interpreter. # # If MINIMUM-VERSION is supplied, then only Lua interpreters with a # version number greater or equal to MINIMUM-VERSION will be accepted. If # TOO-BIG-VERSION is also supplied, then only Lua interpreters with a # version number greater or equal to MINIMUM-VERSION and less than # TOO-BIG-VERSION will be accepted. # # The Lua version number, LUA_VERSION, is found from the interpreter, and # substituted. LUA_PLATFORM is also found, but not currently supported (no # standard representation). # # Finally, the macro finds four paths: # # luadir Directory to install Lua scripts. # pkgluadir $luadir/$PACKAGE # luaexecdir Directory to install Lua modules. # pkgluaexecdir $luaexecdir/$PACKAGE # # These paths are found based on $prefix, $exec_prefix, Lua's # package.path, and package.cpath. The first path of package.path # beginning with $prefix is selected as luadir. The first path of # package.cpath beginning with $exec_prefix is used as luaexecdir. This # should work on all reasonable Lua installations. If a path cannot be # determined, a default path is used. Of course, the user can override # these later when invoking make. # # luadir Default: $prefix/share/lua/$LUA_VERSION # luaexecdir Default: $exec_prefix/lib/lua/$LUA_VERSION # # These directories can be used by Automake as install destinations. The # variable name minus 'dir' needs to be used as a prefix to the # appropriate Automake primary, e.g. lua_SCRIPS or luaexec_LIBRARIES. # # If an acceptable Lua interpreter is found, then ACTION-IF-FOUND is # performed, otherwise ACTION-IF-NOT-FOUND is preformed. If ACTION-IF-NOT- # FOUND is blank, then it will default to printing an error. To prevent # the default behavior, give ':' as an action. # # AX_LUA_HEADERS: Search for Lua headers. Requires that AX_PROG_LUA be # expanded before this macro. Adds precious variable LUA_INCLUDE, which # may contain Lua specific include flags, e.g. -I/usr/include/lua5.1. If # LUA_INCLUDE is blank, then this macro will attempt to find suitable # flags. # # LUA_INCLUDE can be used by Automake to compile Lua modules or # executables with embedded interpreters. The *_CPPFLAGS variables should # be used for this purpose, e.g. myprog_CPPFLAGS = $(LUA_INCLUDE). # # This macro searches for the header lua.h (and others). The search is # performed with a combination of CPPFLAGS, CPATH, etc, and LUA_INCLUDE. # If the search is unsuccessful, then some common directories are tried. # If the headers are then found, then LUA_INCLUDE is set accordingly. # # The paths automatically searched are: # # * /usr/include/luaX.Y # * /usr/include/lua/X.Y # * /usr/include/luaXY # * /usr/local/include/luaX.Y # * /usr/local/include/lua-X.Y # * /usr/local/include/lua/X.Y # * /usr/local/include/luaXY # # (Where X.Y is the Lua version number, e.g. 5.1.) # # The Lua version number found in the headers is always checked to match # the Lua interpreter's version number. Lua headers with mismatched # version numbers are not accepted. # # If headers are found, then ACTION-IF-FOUND is performed, otherwise # ACTION-IF-NOT-FOUND is performed. If ACTION-IF-NOT-FOUND is blank, then # it will default to printing an error. To prevent the default behavior, # set the action to ':'. # # AX_LUA_LIBS: Search for Lua libraries. Requires that AX_PROG_LUA be # expanded before this macro. Adds precious variable LUA_LIB, which may # contain Lua specific linker flags, e.g. -llua5.1. If LUA_LIB is blank, # then this macro will attempt to find suitable flags. # # LUA_LIB can be used by Automake to link Lua modules or executables with # embedded interpreters. The *_LIBADD and *_LDADD variables should be used # for this purpose, e.g. mymod_LIBADD = $(LUA_LIB). # # This macro searches for the Lua library. More technically, it searches # for a library containing the function lua_load. The search is performed # with a combination of LIBS, LIBRARY_PATH, and LUA_LIB. # # If the search determines that some linker flags are missing, then those # flags will be added to LUA_LIB. # # If libraries are found, then ACTION-IF-FOUND is performed, otherwise # ACTION-IF-NOT-FOUND is performed. If ACTION-IF-NOT-FOUND is blank, then # it will default to printing an error. To prevent the default behavior, # set the action to ':'. # # AX_LUA_READLINE: Search for readline headers and libraries. Requires the # AX_LIB_READLINE macro, which is provided by ax_lib_readline.m4 from the # Autoconf Archive. # # If a readline compatible library is found, then ACTION-IF-FOUND is # performed, otherwise ACTION-IF-NOT-FOUND is performed. # # LICENSE # # Copyright (c) 2015 Reuben Thomas # Copyright (c) 2014 Tim Perkins # # This program is free software: you can redistribute it and/or modify it # under the terms of the GNU General Public License as published by the # Free Software Foundation, either version 3 of the License, or (at your # option) any later version. # # This program is distributed in the hope that it will be useful, but # WITHOUT ANY WARRANTY; without even the implied warranty of # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General # Public License for more details. # # You should have received a copy of the GNU General Public License along # with this program. If not, see . # # As a special exception, the respective Autoconf Macro's copyright owner # gives unlimited permission to copy, distribute and modify the configure # scripts that are the output of Autoconf when processing the Macro. You # need not follow the terms of the GNU General Public License when using # or distributing such scripts, even though portions of the text of the # Macro appear in them. The GNU General Public License (GPL) does govern # all other use of the material that constitutes the Autoconf Macro. # # This special exception to the GPL applies to versions of the Autoconf # Macro released by the Autoconf Archive. When you make and distribute a # modified version of the Autoconf Macro, you may extend this special # exception to the GPL to apply to your modified version as well. #serial 39 dnl ========================================================================= dnl AX_PROG_LUA([MINIMUM-VERSION], [TOO-BIG-VERSION], dnl [ACTION-IF-FOUND], [ACTION-IF-NOT-FOUND]) dnl ========================================================================= AC_DEFUN([AX_PROG_LUA], [ dnl Check for required tools. AC_REQUIRE([AC_PROG_GREP]) AC_REQUIRE([AC_PROG_SED]) dnl Make LUA a precious variable. AC_ARG_VAR([LUA], [The Lua interpreter, e.g. /usr/bin/lua5.1]) dnl Find a Lua interpreter. m4_define_default([_AX_LUA_INTERPRETER_LIST], [lua lua5.3 lua53 lua5.2 lua52 lua5.1 lua51 lua50]) m4_if([$1], [], [ dnl No version check is needed. Find any Lua interpreter. AS_IF([test "x$LUA" = 'x'], [AC_PATH_PROGS([LUA], [_AX_LUA_INTERPRETER_LIST], [:])]) ax_display_LUA='lua' AS_IF([test "x$LUA" != 'x:'], [ dnl At least check if this is a Lua interpreter. AC_MSG_CHECKING([if $LUA is a Lua interpreter]) _AX_LUA_CHK_IS_INTRP([$LUA], [AC_MSG_RESULT([yes])], [ AC_MSG_RESULT([no]) AC_MSG_ERROR([not a Lua interpreter]) ]) ]) ], [ dnl A version check is needed. AS_IF([test "x$LUA" != 'x'], [ dnl Check if this is a Lua interpreter. AC_MSG_CHECKING([if $LUA is a Lua interpreter]) _AX_LUA_CHK_IS_INTRP([$LUA], [AC_MSG_RESULT([yes])], [ AC_MSG_RESULT([no]) AC_MSG_ERROR([not a Lua interpreter]) ]) dnl Check the version. m4_if([$2], [], [_ax_check_text="whether $LUA version >= $1"], [_ax_check_text="whether $LUA version >= $1, < $2"]) AC_MSG_CHECKING([$_ax_check_text]) _AX_LUA_CHK_VER([$LUA], [$1], [$2], [AC_MSG_RESULT([yes])], [ AC_MSG_RESULT([no]) AC_MSG_ERROR([version is out of range for specified LUA])]) ax_display_LUA=$LUA ], [ dnl Try each interpreter until we find one that satisfies VERSION. m4_if([$2], [], [_ax_check_text="for a Lua interpreter with version >= $1"], [_ax_check_text="for a Lua interpreter with version >= $1, < $2"]) AC_CACHE_CHECK([$_ax_check_text], [ax_cv_pathless_LUA], [ for ax_cv_pathless_LUA in _AX_LUA_INTERPRETER_LIST none; do test "x$ax_cv_pathless_LUA" = 'xnone' && break _AX_LUA_CHK_IS_INTRP([$ax_cv_pathless_LUA], [], [continue]) _AX_LUA_CHK_VER([$ax_cv_pathless_LUA], [$1], [$2], [break]) done ]) dnl Set $LUA to the absolute path of $ax_cv_pathless_LUA. AS_IF([test "x$ax_cv_pathless_LUA" = 'xnone'], [LUA=':'], [AC_PATH_PROG([LUA], [$ax_cv_pathless_LUA])]) ax_display_LUA=$ax_cv_pathless_LUA ]) ]) AS_IF([test "x$LUA" = 'x:'], [ dnl Run any user-specified action, or abort. m4_default([$4], [AC_MSG_ERROR([cannot find suitable Lua interpreter])]) ], [ dnl Query Lua for its version number. AC_CACHE_CHECK([for $ax_display_LUA version], [ax_cv_lua_version], [ dnl Get the interpreter version in X.Y format. This should work for dnl interpreters version 5.0 and beyond. ax_cv_lua_version=[`$LUA -e ' -- return a version number in X.Y format local _, _, ver = string.find(_VERSION, "^Lua (%d+%.%d+)") print(ver)'`] ]) AS_IF([test "x$ax_cv_lua_version" = 'x'], [AC_MSG_ERROR([invalid Lua version number])]) AC_SUBST([LUA_VERSION], [$ax_cv_lua_version]) AC_SUBST([LUA_SHORT_VERSION], [`echo "$LUA_VERSION" | $SED 's|\.||'`]) dnl The following check is not supported: dnl At times (like when building shared libraries) you may want to know dnl which OS platform Lua thinks this is. AC_CACHE_CHECK([for $ax_display_LUA platform], [ax_cv_lua_platform], [ax_cv_lua_platform=[`$LUA -e 'print("unknown")'`]]) AC_SUBST([LUA_PLATFORM], [$ax_cv_lua_platform]) dnl Use the values of $prefix and $exec_prefix for the corresponding dnl values of LUA_PREFIX and LUA_EXEC_PREFIX. These are made distinct dnl variables so they can be overridden if need be. However, the general dnl consensus is that you shouldn't need this ability. AC_SUBST([LUA_PREFIX], ['${prefix}']) AC_SUBST([LUA_EXEC_PREFIX], ['${exec_prefix}']) dnl Lua provides no way to query the script directory, and instead dnl provides LUA_PATH. However, we should be able to make a safe educated dnl guess. If the built-in search path contains a directory which is dnl prefixed by $prefix, then we can store scripts there. The first dnl matching path will be used. AC_CACHE_CHECK([for $ax_display_LUA script directory], [ax_cv_lua_luadir], [ AS_IF([test "x$prefix" = 'xNONE'], [ax_lua_prefix=$ac_default_prefix], [ax_lua_prefix=$prefix]) dnl Initialize to the default path. ax_cv_lua_luadir="$LUA_PREFIX/share/lua/$LUA_VERSION" dnl Try to find a path with the prefix. _AX_LUA_FND_PRFX_PTH([$LUA], [$ax_lua_prefix], [script]) AS_IF([test "x$ax_lua_prefixed_path" != 'x'], [ dnl Fix the prefix. _ax_strip_prefix=`echo "$ax_lua_prefix" | $SED 's|.|.|g'` ax_cv_lua_luadir=`echo "$ax_lua_prefixed_path" | \ $SED "s|^$_ax_strip_prefix|$LUA_PREFIX|"` ]) ]) AC_SUBST([luadir], [$ax_cv_lua_luadir]) AC_SUBST([pkgluadir], [\${luadir}/$PACKAGE]) dnl Lua provides no way to query the module directory, and instead dnl provides LUA_PATH. However, we should be able to make a safe educated dnl guess. If the built-in search path contains a directory which is dnl prefixed by $exec_prefix, then we can store modules there. The first dnl matching path will be used. AC_CACHE_CHECK([for $ax_display_LUA module directory], [ax_cv_lua_luaexecdir], [ AS_IF([test "x$exec_prefix" = 'xNONE'], [ax_lua_exec_prefix=$ax_lua_prefix], [ax_lua_exec_prefix=$exec_prefix]) dnl Initialize to the default path. ax_cv_lua_luaexecdir="$LUA_EXEC_PREFIX/lib/lua/$LUA_VERSION" dnl Try to find a path with the prefix. _AX_LUA_FND_PRFX_PTH([$LUA], [$ax_lua_exec_prefix], [module]) AS_IF([test "x$ax_lua_prefixed_path" != 'x'], [ dnl Fix the prefix. _ax_strip_prefix=`echo "$ax_lua_exec_prefix" | $SED 's|.|.|g'` ax_cv_lua_luaexecdir=`echo "$ax_lua_prefixed_path" | \ $SED "s|^$_ax_strip_prefix|$LUA_EXEC_PREFIX|"` ]) ]) AC_SUBST([luaexecdir], [$ax_cv_lua_luaexecdir]) AC_SUBST([pkgluaexecdir], [\${luaexecdir}/$PACKAGE]) dnl Run any user specified action. $3 ]) ]) dnl AX_WITH_LUA is now the same thing as AX_PROG_LUA. AC_DEFUN([AX_WITH_LUA], [ AC_MSG_WARN([[$0 is deprecated, please use AX_PROG_LUA instead]]) AX_PROG_LUA ]) dnl ========================================================================= dnl _AX_LUA_CHK_IS_INTRP(PROG, [ACTION-IF-TRUE], [ACTION-IF-FALSE]) dnl ========================================================================= AC_DEFUN([_AX_LUA_CHK_IS_INTRP], [ dnl A minimal Lua factorial to prove this is an interpreter. This should work dnl for Lua interpreters version 5.0 and beyond. _ax_lua_factorial=[`$1 2>/dev/null -e ' -- a simple factorial function fact (n) if n == 0 then return 1 else return n * fact(n-1) end end print("fact(5) is " .. fact(5))'`] AS_IF([test "$_ax_lua_factorial" = 'fact(5) is 120'], [$2], [$3]) ]) dnl ========================================================================= dnl _AX_LUA_CHK_VER(PROG, MINIMUM-VERSION, [TOO-BIG-VERSION], dnl [ACTION-IF-TRUE], [ACTION-IF-FALSE]) dnl ========================================================================= AC_DEFUN([_AX_LUA_CHK_VER], [ dnl Check that the Lua version is within the bounds. Only the major and minor dnl version numbers are considered. This should work for Lua interpreters dnl version 5.0 and beyond. _ax_lua_good_version=[`$1 -e ' -- a script to compare versions function verstr2num(verstr) local _, _, majorver, minorver = string.find(verstr, "^(%d+)%.(%d+)") if majorver and minorver then return tonumber(majorver) * 100 + tonumber(minorver) end end local minver = verstr2num("$2") local _, _, trimver = string.find(_VERSION, "^Lua (.*)") local ver = verstr2num(trimver) local maxver = verstr2num("$3") or 1e9 if minver <= ver and ver < maxver then print("yes") else print("no") end'`] AS_IF([test "x$_ax_lua_good_version" = "xyes"], [$4], [$5]) ]) dnl ========================================================================= dnl _AX_LUA_FND_PRFX_PTH(PROG, PREFIX, SCRIPT-OR-MODULE-DIR) dnl ========================================================================= AC_DEFUN([_AX_LUA_FND_PRFX_PTH], [ dnl Get the script or module directory by querying the Lua interpreter, dnl filtering on the given prefix, and selecting the shallowest path. If no dnl path is found matching the prefix, the result will be an empty string. dnl The third argument determines the type of search, it can be 'script' or dnl 'module'. Supplying 'script' will perform the search with package.path dnl and LUA_PATH, and supplying 'module' will search with package.cpath and dnl LUA_CPATH. This is done for compatibility with Lua 5.0. ax_lua_prefixed_path=[`$1 -e ' -- get the path based on search type local searchtype = "$3" local paths = "" if searchtype == "script" then paths = (package and package.path) or LUA_PATH elseif searchtype == "module" then paths = (package and package.cpath) or LUA_CPATH end -- search for the prefix local prefix = "'$2'" local minpath = "" local mindepth = 1e9 string.gsub(paths, "(@<:@^;@:>@+)", function (path) path = string.gsub(path, "%?.*$", "") path = string.gsub(path, "/@<:@^/@:>@*$", "") if string.find(path, prefix) then local depth = string.len(string.gsub(path, "@<:@^/@:>@", "")) if depth < mindepth then minpath = path mindepth = depth end end end) print(minpath)'`] ]) dnl ========================================================================= dnl AX_LUA_HEADERS([ACTION-IF-FOUND], [ACTION-IF-NOT-FOUND]) dnl ========================================================================= AC_DEFUN([AX_LUA_HEADERS], [ dnl Check for LUA_VERSION. AC_MSG_CHECKING([if LUA_VERSION is defined]) AS_IF([test "x$LUA_VERSION" != 'x'], [AC_MSG_RESULT([yes])], [ AC_MSG_RESULT([no]) AC_MSG_ERROR([cannot check Lua headers without knowing LUA_VERSION]) ]) dnl Make LUA_INCLUDE a precious variable. AC_ARG_VAR([LUA_INCLUDE], [The Lua includes, e.g. -I/usr/include/lua5.1]) dnl Some default directories to search. LUA_SHORT_VERSION=`echo "$LUA_VERSION" | $SED 's|\.||'` m4_define_default([_AX_LUA_INCLUDE_LIST], [ /usr/include/lua$LUA_VERSION \ /usr/include/lua-$LUA_VERSION \ /usr/include/lua/$LUA_VERSION \ /usr/include/lua$LUA_SHORT_VERSION \ /usr/local/include/lua$LUA_VERSION \ /usr/local/include/lua-$LUA_VERSION \ /usr/local/include/lua/$LUA_VERSION \ /usr/local/include/lua$LUA_SHORT_VERSION \ ]) dnl Try to find the headers. _ax_lua_saved_cppflags=$CPPFLAGS CPPFLAGS="$CPPFLAGS $LUA_INCLUDE" AC_CHECK_HEADERS([lua.h lualib.h lauxlib.h luaconf.h]) CPPFLAGS=$_ax_lua_saved_cppflags dnl Try some other directories if LUA_INCLUDE was not set. AS_IF([test "x$LUA_INCLUDE" = 'x' && test "x$ac_cv_header_lua_h" != 'xyes'], [ dnl Try some common include paths. for _ax_include_path in _AX_LUA_INCLUDE_LIST; do test ! -d "$_ax_include_path" && continue AC_MSG_CHECKING([for Lua headers in]) AC_MSG_RESULT([$_ax_include_path]) AS_UNSET([ac_cv_header_lua_h]) AS_UNSET([ac_cv_header_lualib_h]) AS_UNSET([ac_cv_header_lauxlib_h]) AS_UNSET([ac_cv_header_luaconf_h]) _ax_lua_saved_cppflags=$CPPFLAGS CPPFLAGS="$CPPFLAGS -I$_ax_include_path" AC_CHECK_HEADERS([lua.h lualib.h lauxlib.h luaconf.h]) CPPFLAGS=$_ax_lua_saved_cppflags AS_IF([test "x$ac_cv_header_lua_h" = 'xyes'], [ LUA_INCLUDE="-I$_ax_include_path" break ]) done ]) AS_IF([test "x$ac_cv_header_lua_h" = 'xyes'], [ dnl Make a program to print LUA_VERSION defined in the header. dnl TODO It would be really nice if we could do this without compiling a dnl program, then it would work when cross compiling. But I'm not sure how dnl to do this reliably. For now, assume versions match when cross compiling. AS_IF([test "x$cross_compiling" != 'xyes'], [ AC_CACHE_CHECK([for Lua header version], [ax_cv_lua_header_version], [ _ax_lua_saved_cppflags=$CPPFLAGS CPPFLAGS="$CPPFLAGS $LUA_INCLUDE" AC_RUN_IFELSE( [ AC_LANG_SOURCE([[ #include #include #include int main(int argc, char ** argv) { if(argc > 1) printf("%s", LUA_VERSION); exit(EXIT_SUCCESS); } ]]) ], [ ax_cv_lua_header_version=`./conftest$EXEEXT p | \ $SED -n "s|^Lua \(@<:@0-9@:>@\{1,\}\.@<:@0-9@:>@\{1,\}\).\{0,\}|\1|p"` ], [ax_cv_lua_header_version='unknown']) CPPFLAGS=$_ax_lua_saved_cppflags ]) dnl Compare this to the previously found LUA_VERSION. AC_MSG_CHECKING([if Lua header version matches $LUA_VERSION]) AS_IF([test "x$ax_cv_lua_header_version" = "x$LUA_VERSION"], [ AC_MSG_RESULT([yes]) ax_header_version_match='yes' ], [ AC_MSG_RESULT([no]) ax_header_version_match='no' ]) ], [ AC_MSG_WARN([cross compiling so assuming header version number matches]) ax_header_version_match='yes' ]) ]) dnl Was LUA_INCLUDE specified? AS_IF([test "x$ax_header_version_match" != 'xyes' && test "x$LUA_INCLUDE" != 'x'], [AC_MSG_ERROR([cannot find headers for specified LUA_INCLUDE])]) dnl Test the final result and run user code. AS_IF([test "x$ax_header_version_match" = 'xyes'], [$1], [m4_default([$2], [AC_MSG_ERROR([cannot find Lua includes])])]) ]) dnl AX_LUA_HEADERS_VERSION no longer exists, use AX_LUA_HEADERS. AC_DEFUN([AX_LUA_HEADERS_VERSION], [ AC_MSG_WARN([[$0 is deprecated, please use AX_LUA_HEADERS instead]]) ]) dnl ========================================================================= dnl AX_LUA_LIBS([ACTION-IF-FOUND], [ACTION-IF-NOT-FOUND]) dnl ========================================================================= AC_DEFUN([AX_LUA_LIBS], [ dnl TODO Should this macro also check various -L flags? dnl Check for LUA_VERSION. AC_MSG_CHECKING([if LUA_VERSION is defined]) AS_IF([test "x$LUA_VERSION" != 'x'], [AC_MSG_RESULT([yes])], [ AC_MSG_RESULT([no]) AC_MSG_ERROR([cannot check Lua libs without knowing LUA_VERSION]) ]) dnl Make LUA_LIB a precious variable. AC_ARG_VAR([LUA_LIB], [The Lua library, e.g. -llua5.1]) AS_IF([test "x$LUA_LIB" != 'x'], [ dnl Check that LUA_LIBS works. _ax_lua_saved_libs=$LIBS LIBS="$LIBS $LUA_LIB" AC_SEARCH_LIBS([lua_load], [], [_ax_found_lua_libs='yes'], [_ax_found_lua_libs='no']) LIBS=$_ax_lua_saved_libs dnl Check the result. AS_IF([test "x$_ax_found_lua_libs" != 'xyes'], [AC_MSG_ERROR([cannot find libs for specified LUA_LIB])]) ], [ dnl First search for extra libs. _ax_lua_extra_libs='' _ax_lua_saved_libs=$LIBS LIBS="$LIBS $LUA_LIB" AC_SEARCH_LIBS([exp], [m]) AC_SEARCH_LIBS([dlopen], [dl]) LIBS=$_ax_lua_saved_libs AS_IF([test "x$ac_cv_search_exp" != 'xno' && test "x$ac_cv_search_exp" != 'xnone required'], [_ax_lua_extra_libs="$_ax_lua_extra_libs $ac_cv_search_exp"]) AS_IF([test "x$ac_cv_search_dlopen" != 'xno' && test "x$ac_cv_search_dlopen" != 'xnone required'], [_ax_lua_extra_libs="$_ax_lua_extra_libs $ac_cv_search_dlopen"]) dnl Try to find the Lua libs. _ax_lua_saved_libs=$LIBS LIBS="$LIBS $LUA_LIB" AC_SEARCH_LIBS([lua_load], [ lua$LUA_VERSION \ lua$LUA_SHORT_VERSION \ lua-$LUA_VERSION \ lua-$LUA_SHORT_VERSION \ lua \ ], [_ax_found_lua_libs='yes'], [_ax_found_lua_libs='no'], [$_ax_lua_extra_libs]) LIBS=$_ax_lua_saved_libs AS_IF([test "x$ac_cv_search_lua_load" != 'xno' && test "x$ac_cv_search_lua_load" != 'xnone required'], [LUA_LIB="$ac_cv_search_lua_load $_ax_lua_extra_libs"]) ]) dnl Test the result and run user code. AS_IF([test "x$_ax_found_lua_libs" = 'xyes'], [$1], [m4_default([$2], [AC_MSG_ERROR([cannot find Lua libs])])]) ]) dnl ========================================================================= dnl AX_LUA_READLINE([ACTION-IF-FOUND], [ACTION-IF-NOT-FOUND]) dnl ========================================================================= AC_DEFUN([AX_LUA_READLINE], [ AX_LIB_READLINE AS_IF([test "x$ac_cv_header_readline_readline_h" != 'x' && test "x$ac_cv_header_readline_history_h" != 'x'], [ LUA_LIBS_CFLAGS="-DLUA_USE_READLINE $LUA_LIBS_CFLAGS" $1 ], [$2]) ]) frr-7.2.1/m4/ax_pthread.m40000644000000000000000000003267613610377563012133 00000000000000# =========================================================================== # http://www.gnu.org/software/autoconf-archive/ax_pthread.html # =========================================================================== # # SYNOPSIS # # AX_PTHREAD([ACTION-IF-FOUND[, ACTION-IF-NOT-FOUND]]) # # DESCRIPTION # # This macro figures out how to build C programs using POSIX threads. It # sets the PTHREAD_LIBS output variable to the threads library and linker # flags, and the PTHREAD_CFLAGS output variable to any special C compiler # flags that are needed. (The user can also force certain compiler # flags/libs to be tested by setting these environment variables.) # # Also sets PTHREAD_CC to any special C compiler that is needed for # multi-threaded programs (defaults to the value of CC otherwise). (This # is necessary on AIX to use the special cc_r compiler alias.) # # NOTE: You are assumed to not only compile your program with these flags, # but also link it with them as well. e.g. you should link with # $PTHREAD_CC $CFLAGS $PTHREAD_CFLAGS $LDFLAGS ... $PTHREAD_LIBS $LIBS # # If you are only building threads programs, you may wish to use these # variables in your default LIBS, CFLAGS, and CC: # # LIBS="$PTHREAD_LIBS $LIBS" # CFLAGS="$CFLAGS $PTHREAD_CFLAGS" # CC="$PTHREAD_CC" # # In addition, if the PTHREAD_CREATE_JOINABLE thread-attribute constant # has a nonstandard name, defines PTHREAD_CREATE_JOINABLE to that name # (e.g. PTHREAD_CREATE_UNDETACHED on AIX). # # Also HAVE_PTHREAD_PRIO_INHERIT is defined if pthread is found and the # PTHREAD_PRIO_INHERIT symbol is defined when compiling with # PTHREAD_CFLAGS. # # ACTION-IF-FOUND is a list of shell commands to run if a threads library # is found, and ACTION-IF-NOT-FOUND is a list of commands to run it if it # is not found. If ACTION-IF-FOUND is not specified, the default action # will define HAVE_PTHREAD. # # Please let the authors know if this macro fails on any platform, or if # you have any other suggestions or comments. This macro was based on work # by SGJ on autoconf scripts for FFTW (http://www.fftw.org/) (with help # from M. Frigo), as well as ac_pthread and hb_pthread macros posted by # Alejandro Forero Cuervo to the autoconf macro repository. We are also # grateful for the helpful feedback of numerous users. # # Updated for Autoconf 2.68 by Daniel Richard G. # # LICENSE # # Copyright (c) 2008 Steven G. Johnson # Copyright (c) 2011 Daniel Richard G. # # This program is free software: you can redistribute it and/or modify it # under the terms of the GNU General Public License as published by the # Free Software Foundation, either version 3 of the License, or (at your # option) any later version. # # This program is distributed in the hope that it will be useful, but # WITHOUT ANY WARRANTY; without even the implied warranty of # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General # Public License for more details. # # You should have received a copy of the GNU General Public License along # with this program. If not, see . # # As a special exception, the respective Autoconf Macro's copyright owner # gives unlimited permission to copy, distribute and modify the configure # scripts that are the output of Autoconf when processing the Macro. You # need not follow the terms of the GNU General Public License when using # or distributing such scripts, even though portions of the text of the # Macro appear in them. The GNU General Public License (GPL) does govern # all other use of the material that constitutes the Autoconf Macro. # # This special exception to the GPL applies to versions of the Autoconf # Macro released by the Autoconf Archive. When you make and distribute a # modified version of the Autoconf Macro, you may extend this special # exception to the GPL to apply to your modified version as well. #serial 21 AU_ALIAS([ACX_PTHREAD], [AX_PTHREAD]) AC_DEFUN([AX_PTHREAD], [ AC_REQUIRE([AC_CANONICAL_HOST]) AC_LANG_PUSH([C]) ax_pthread_ok=no # We used to check for pthread.h first, but this fails if pthread.h # requires special compiler flags (e.g. on True64 or Sequent). # It gets checked for in the link test anyway. # First of all, check if the user has set any of the PTHREAD_LIBS, # etcetera environment variables, and if threads linking works using # them: if test x"$PTHREAD_LIBS$PTHREAD_CFLAGS" != x; then save_CFLAGS="$CFLAGS" CFLAGS="$CFLAGS $PTHREAD_CFLAGS" save_LIBS="$LIBS" LIBS="$PTHREAD_LIBS $LIBS" AC_MSG_CHECKING([for pthread_join in LIBS=$PTHREAD_LIBS with CFLAGS=$PTHREAD_CFLAGS]) AC_TRY_LINK_FUNC([pthread_join], [ax_pthread_ok=yes]) AC_MSG_RESULT([$ax_pthread_ok]) if test x"$ax_pthread_ok" = xno; then PTHREAD_LIBS="" PTHREAD_CFLAGS="" fi LIBS="$save_LIBS" CFLAGS="$save_CFLAGS" fi # We must check for the threads library under a number of different # names; the ordering is very important because some systems # (e.g. DEC) have both -lpthread and -lpthreads, where one of the # libraries is broken (non-POSIX). # Create a list of thread flags to try. Items starting with a "-" are # C compiler flags, and other items are library names, except for "none" # which indicates that we try without any flags at all, and "pthread-config" # which is a program returning the flags for the Pth emulation library. ax_pthread_flags="pthreads none -Kthread -kthread lthread -pthread -pthreads -mthreads pthread --thread-safe -mt pthread-config" # The ordering *is* (sometimes) important. Some notes on the # individual items follow: # pthreads: AIX (must check this before -lpthread) # none: in case threads are in libc; should be tried before -Kthread and # other compiler flags to prevent continual compiler warnings # -Kthread: Sequent (threads in libc, but -Kthread needed for pthread.h) # -kthread: FreeBSD kernel threads (preferred to -pthread since SMP-able) # lthread: LinuxThreads port on FreeBSD (also preferred to -pthread) # -pthread: Linux/gcc (kernel threads), BSD/gcc (userland threads) # -pthreads: Solaris/gcc # -mthreads: Mingw32/gcc, Lynx/gcc # -mt: Sun Workshop C (may only link SunOS threads [-lthread], but it # doesn't hurt to check since this sometimes defines pthreads too; # also defines -D_REENTRANT) # ... -mt is also the pthreads flag for HP/aCC # pthread: Linux, etcetera # --thread-safe: KAI C++ # pthread-config: use pthread-config program (for GNU Pth library) case ${host_os} in solaris*) # On Solaris (at least, for some versions), libc contains stubbed # (non-functional) versions of the pthreads routines, so link-based # tests will erroneously succeed. (We need to link with -pthreads/-mt/ # -lpthread.) (The stubs are missing pthread_cleanup_push, or rather # a function called by this macro, so we could check for that, but # who knows whether they'll stub that too in a future libc.) So, # we'll just look for -pthreads and -lpthread first: ax_pthread_flags="-pthreads pthread -mt -pthread $ax_pthread_flags" ;; darwin*) ax_pthread_flags="-pthread $ax_pthread_flags" ;; esac # Clang doesn't consider unrecognized options an error unless we specify # -Werror. We throw in some extra Clang-specific options to ensure that # this doesn't happen for GCC, which also accepts -Werror. AC_MSG_CHECKING([if compiler needs -Werror to reject unknown flags]) save_CFLAGS="$CFLAGS" ax_pthread_extra_flags="-Werror" CFLAGS="$CFLAGS $ax_pthread_extra_flags -Wunknown-warning-option -Wsizeof-array-argument" AC_COMPILE_IFELSE([AC_LANG_PROGRAM([int foo(void);],[foo()])], [AC_MSG_RESULT([yes])], [ax_pthread_extra_flags= AC_MSG_RESULT([no])]) CFLAGS="$save_CFLAGS" if test x"$ax_pthread_ok" = xno; then for flag in $ax_pthread_flags; do case $flag in none) AC_MSG_CHECKING([whether pthreads work without any flags]) ;; -*) AC_MSG_CHECKING([whether pthreads work with $flag]) PTHREAD_CFLAGS="$flag" ;; pthread-config) AC_CHECK_PROG([ax_pthread_config], [pthread-config], [yes], [no]) if test x"$ax_pthread_config" = xno; then continue; fi PTHREAD_CFLAGS="`pthread-config --cflags`" PTHREAD_LIBS="`pthread-config --ldflags` `pthread-config --libs`" ;; *) AC_MSG_CHECKING([for the pthreads library -l$flag]) PTHREAD_LIBS="-l$flag" ;; esac save_LIBS="$LIBS" save_CFLAGS="$CFLAGS" LIBS="$PTHREAD_LIBS $LIBS" CFLAGS="$CFLAGS $PTHREAD_CFLAGS $ax_pthread_extra_flags" # Check for various functions. We must include pthread.h, # since some functions may be macros. (On the Sequent, we # need a special flag -Kthread to make this header compile.) # We check for pthread_join because it is in -lpthread on IRIX # while pthread_create is in libc. We check for pthread_attr_init # due to DEC craziness with -lpthreads. We check for # pthread_cleanup_push because it is one of the few pthread # functions on Solaris that doesn't have a non-functional libc stub. # We try pthread_create on general principles. AC_LINK_IFELSE([AC_LANG_PROGRAM([#include static void routine(void *a) { a = 0; } static void *start_routine(void *a) { return a; }], [pthread_t th; pthread_attr_t attr; pthread_create(&th, 0, start_routine, 0); pthread_join(th, 0); pthread_attr_init(&attr); pthread_cleanup_push(routine, 0); pthread_cleanup_pop(0) /* ; */])], [ax_pthread_ok=yes], []) LIBS="$save_LIBS" CFLAGS="$save_CFLAGS" AC_MSG_RESULT([$ax_pthread_ok]) if test "x$ax_pthread_ok" = xyes; then break; fi PTHREAD_LIBS="" PTHREAD_CFLAGS="" done fi # Various other checks: if test "x$ax_pthread_ok" = xyes; then save_LIBS="$LIBS" LIBS="$PTHREAD_LIBS $LIBS" save_CFLAGS="$CFLAGS" CFLAGS="$CFLAGS $PTHREAD_CFLAGS" # Detect AIX lossage: JOINABLE attribute is called UNDETACHED. AC_MSG_CHECKING([for joinable pthread attribute]) attr_name=unknown for attr in PTHREAD_CREATE_JOINABLE PTHREAD_CREATE_UNDETACHED; do AC_LINK_IFELSE([AC_LANG_PROGRAM([#include ], [int attr = $attr; return attr /* ; */])], [attr_name=$attr; break], []) done AC_MSG_RESULT([$attr_name]) if test "$attr_name" != PTHREAD_CREATE_JOINABLE; then AC_DEFINE_UNQUOTED([PTHREAD_CREATE_JOINABLE], [$attr_name], [Define to necessary symbol if this constant uses a non-standard name on your system.]) fi AC_MSG_CHECKING([if more special flags are required for pthreads]) flag=no case ${host_os} in aix* | freebsd* | darwin*) flag="-D_THREAD_SAFE";; osf* | hpux*) flag="-D_REENTRANT";; solaris*) if test "$GCC" = "yes"; then flag="-D_REENTRANT" else # TODO: What about Clang on Solaris? flag="-mt -D_REENTRANT" fi ;; esac AC_MSG_RESULT([$flag]) if test "x$flag" != xno; then PTHREAD_CFLAGS="$flag $PTHREAD_CFLAGS" fi AC_CACHE_CHECK([for PTHREAD_PRIO_INHERIT], [ax_cv_PTHREAD_PRIO_INHERIT], [ AC_LINK_IFELSE([AC_LANG_PROGRAM([[#include ]], [[int i = PTHREAD_PRIO_INHERIT;]])], [ax_cv_PTHREAD_PRIO_INHERIT=yes], [ax_cv_PTHREAD_PRIO_INHERIT=no]) ]) AS_IF([test "x$ax_cv_PTHREAD_PRIO_INHERIT" = "xyes"], [AC_DEFINE([HAVE_PTHREAD_PRIO_INHERIT], [1], [Have PTHREAD_PRIO_INHERIT.])]) LIBS="$save_LIBS" CFLAGS="$save_CFLAGS" # More AIX lossage: compile with *_r variant if test "x$GCC" != xyes; then case $host_os in aix*) AS_CASE(["x/$CC"], [x*/c89|x*/c89_128|x*/c99|x*/c99_128|x*/cc|x*/cc128|x*/xlc|x*/xlc_v6|x*/xlc128|x*/xlc128_v6], [#handle absolute path differently from PATH based program lookup AS_CASE(["x$CC"], [x/*], [AS_IF([AS_EXECUTABLE_P([${CC}_r])],[PTHREAD_CC="${CC}_r"])], [AC_CHECK_PROGS([PTHREAD_CC],[${CC}_r],[$CC])])]) ;; esac fi fi test -n "$PTHREAD_CC" || PTHREAD_CC="$CC" AC_SUBST([PTHREAD_LIBS]) AC_SUBST([PTHREAD_CFLAGS]) AC_SUBST([PTHREAD_CC]) # Finally, execute ACTION-IF-FOUND/ACTION-IF-NOT-FOUND: if test x"$ax_pthread_ok" = xyes; then ifelse([$1],,[AC_DEFINE([HAVE_PTHREAD],[1],[Define if you have POSIX threads libraries and header files.])],[$1]) : else ax_pthread_ok=no $2 fi AC_LANG_POP ])dnl AX_PTHREAD frr-7.2.1/m4/ax_python.m40000644000000000000000000001752713610377563012023 00000000000000dnl FRR Python autoconf magic dnl 2019 David Lamparter for NetDEF, Inc. dnl SPDX-License-Identifier: GPL-2.0-or-later dnl the _ at the beginning will be cut off (to support the empty version string) m4_define_default([_FRR_PY_VERS], [_3 _ _2 _3.7 _3.6 _3.5 _3.4 _3.3 _3.2 _2.7]) dnl check basic interpreter properties (py2/py3) dnl doubles as simple check whether the interpreter actually works dnl also swaps in the full path to the interpreter dnl arg1: if-true, arg2: if-false AC_DEFUN([_FRR_PYTHON_INTERP], [dnl AC_ARG_VAR([PYTHON], [Python interpreter to use])dnl AC_MSG_CHECKING([python interpreter $PYTHON]) AC_RUN_LOG(["$PYTHON" -c 'import sys; open("conftest.pyver", "w").write(sys.executable or ""); sys.exit(not (sys.version_info.major == 2 and sys.version_info.minor >= 7))']) py2=$ac_status _py2_full="`cat conftest.pyver 2>/dev/null`" rm -f "conftest.pyver" >/dev/null 2>/dev/null AC_RUN_LOG(["$PYTHON" -c 'import sys; open("conftest.pyver", "w").write(sys.executable or ""); sys.exit(not ((sys.version_info.major == 3 and sys.version_info.minor >= 2) or sys.version_info.major > 3))']) py3=$ac_status _py3_full="`cat conftest.pyver 2>/dev/null`" rm -f "conftest.pyver" >/dev/null 2>/dev/null case "p${py2}p${py3}" in p0p1) frr_cv_python=python2 _python_full="$_py2_full" ;; p1p0) frr_cv_python=python3 _python_full="$_py3_full" ;; *) frr_cv_python=none ;; esac if test "$frr_cv_python" = none; then AC_MSG_RESULT([not working]) $2 else test -n "$_python_full" -a -x "$_python_full" && PYTHON="$_python_full" AC_MSG_RESULT([$PYTHON ($frr_cv_python)]) $1 fi dnl return value test "$frr_cv_python" != none ]) dnl check whether $PYTHON has modules available dnl arg1: list of modules (space separated) dnl arg2: if all true, arg3: if any missing dnl also sets frr_py_mod_ to "true" or "false" AC_DEFUN([FRR_PYTHON_MODULES], [ result=true for pymod in $1; do AC_MSG_CHECKING([whether $PYTHON module $pymod is available]) AC_RUN_LOG(["$PYTHON" -c "import $pymod"]) sane="`echo \"$pymod\" | tr -c '[a-zA-Z0-9\n]' '_'`" if test "$ac_status" -eq 0; then AC_MSG_RESULT([yes]) eval frr_py_mod_$sane=true else AC_MSG_RESULT([no]) eval frr_py_mod_$sane=false result=false fi done if $result; then m4_default([$2], [:]) else m4_default([$3], [:]) fi $result ]) dnl check whether $PYTHON has modules available dnl arg1: list of modules (space separated) dnl arg2: command line parameters for executing dnl arg3: if all true, arg4: if any missing dnl also sets frr_py_modexec_ to "true" or "false" AC_DEFUN([FRR_PYTHON_MOD_EXEC], [ result=true for pymod in $1; do AC_MSG_CHECKING([whether $PYTHON module $pymod is executable]) AC_RUN_LOG(["$PYTHON" -m "$pymod" $2 > /dev/null]) sane="`echo \"$pymod\" | tr -c '[a-zA-Z0-9\n]' '_'`" if test "$ac_status" -eq 0; then AC_MSG_RESULT([yes]) eval frr_py_modexec_$sane=true else AC_MSG_RESULT([no]) eval frr_py_modexec_$sane=false result=false fi done if $result; then m4_default([$3], [:]) else m4_default([$4], [:]) fi $result ]) dnl check whether we can build & link python bits dnl input: PYTHON_CFLAGS and PYTHON_LIBS AC_DEFUN([_FRR_PYTHON_DEVENV], [ result=true AC_LINK_IFELSE_FLAGS([$PYTHON_CFLAGS], [$PYTHON_LIBS], [AC_LANG_PROGRAM([ #include #if PY_VERSION_HEX < 0x02070000 #error python too old #endif int main(void); ], [ { Py_Initialize(); return 0; } ])], [ # some python installs are missing the zlib dependency... PYTHON_LIBS="${PYTHON_LIBS} -lz" AC_LINK_IFELSE_FLAGS([$PYTHON_CFLAGS], [$PYTHON_LIBS], [AC_LANG_PROGRAM([ #include #if PY_VERSION_HEX < 0x02070000 #error python too old #endif int main(void); ], [ { Py_Initialize(); return 0; } ])], [ result=false AC_MSG_RESULT([no]) ], [:]) ], [:]) if $result; then AC_LINK_IFELSE_FLAGS([$PYTHON_CFLAGS], [$PYTHON_LIBS], [AC_LANG_PROGRAM([ #include #if PY_VERSION_HEX != $1 #error python version mismatch #endif int main(void); ], [ { Py_Initialize(); return 0; } ])], [ result=false AC_MSG_RESULT([version mismatch]) ], [ AC_MSG_RESULT([yes]) ]) fi if $result; then m4_default([$2], [:]) else m4_default([$3], [ unset PYTHON_LIBS unset PYTHON_CFLAGS ]) fi ]) AC_DEFUN([_FRR_PYTHON_GETDEV], [dnl AC_REQUIRE([PKG_PROG_PKG_CONFIG])dnl py_abi="` \"$1\" -c \"import sys; print(getattr(sys, 'abiflags', ''))\"`" py_hex="` \"$1\" -c \"import sys; print(hex(sys.hexversion))\"`" py_ldver="` \"$1\" -c \"import sysconfig; print(sysconfig.get_config_var('LDVERSION') or '')\"`" py_ver="` \"$1\" -c \"import sysconfig; print(sysconfig.get_config_var('VERSION') or '')\"`" py_bindir="`\"$1\" -c \"import sysconfig; print(sysconfig.get_config_var('BINDIR') or '')\"`" test -z "$py_bindir" || py_bindir="$py_bindir/" echo "py_abi=${py_abi} py_ldver=${py_ldver} py_ver=${py_ver} py_bindir=${py_bindir}" >&AS_MESSAGE_LOG_FD py_found=false for tryver in "${py_ldver}" "${py_ver}"; do pycfg="${py_bindir}python${tryver}-config" AC_MSG_CHECKING([whether ${pycfg} is available]) if "$pycfg" --configdir >/dev/null 2>/dev/null; then AC_MSG_RESULT([yes]) PYTHON_CFLAGS="`\"$pycfg\" --includes`" if test x"${py_ver}" == x"3.8" || test x"{py_ver}" == x"3.9"; then PYTHON_LIBS="`\"$pycfg\" --ldflags --embed`" else PYTHON_LIBS="`\"$pycfg\" --ldflags`" fi AC_MSG_CHECKING([whether ${pycfg} provides a working build environment]) _FRR_PYTHON_DEVENV([$py_hex], [ py_found=true break ]) else AC_MSG_RESULT([no]) fi pkg_failed=no AC_MSG_CHECKING([whether pkg-config python-${tryver} is available]) unset PYTHON_CFLAGS unset PYTHON_LIBS pkg="python-${tryver}" pkg="${pkg%-}" _PKG_CONFIG([PYTHON_CFLAGS], [cflags], [${pkg}]) _PKG_CONFIG([PYTHON_LIBS], [libs], [${pkg}]) if test $pkg_failed = no; then AC_MSG_RESULT([yes]) PYTHON_CFLAGS=$pkg_cv_PYTHON_CFLAGS PYTHON_LIBS=$pkg_cv_PYTHON_LIBS AC_MSG_CHECKING([whether pkg-config python-${tryver} provides a working build environment]) _FRR_PYTHON_DEVENV([$py_hex], [ py_found=true break ]) else AC_MSG_RESULT([no]) fi done if $py_found; then m4_default([$2], [:]) else unset PYTHON_CFLAGS unset PYTHON_LIBS m4_default([$3], [:]) fi ]) dnl just find python without checking headers/libs AC_DEFUN([FRR_PYTHON], [ dnl user override if test "x$PYTHON" != "x"; then _FRR_PYTHON_INTERP([], [ AC_MSG_ERROR([PYTHON ($PYTHON) explicitly specified but not working]) ]) else for frr_pyver in _FRR_PY_VERS; do PYTHON="python${frr_pyver#_}" _FRR_PYTHON_INTERP([break]) PYTHON=":" done if test "$PYTHON" = ":"; then AC_MSG_ERROR([no working python version found]) fi fi AC_SUBST([PYTHON]) ]) dnl find python with checking headers/libs AC_DEFUN([FRR_PYTHON_DEV], [dnl AC_ARG_VAR([PYTHON_CFLAGS], [C compiler flags for Python])dnl AC_ARG_VAR([PYTHON_LIBS], [linker flags for Python])dnl dnl user override if test "x$PYTHON" != "x"; then _FRR_PYTHON_INTERP([], [ AC_MSG_ERROR([PYTHON ($PYTHON) explicitly specified but not working]) ]) _FRR_PYTHON_GETDEV([$PYTHON], [], [ AC_MSG_ERROR([PYTHON ($PYTHON) explicitly specified but development environment not working]) ]) else for frr_pyver in _FRR_PY_VERS; do PYTHON="python${frr_pyver#_}" _FRR_PYTHON_INTERP([ _FRR_PYTHON_GETDEV([$PYTHON], [ break ]) ]) PYTHON=":" done if test "$PYTHON" = ":"; then AC_MSG_ERROR([no working python version found]) fi fi AC_SUBST([PYTHON_CFLAGS]) AC_SUBST([PYTHON_LIBS]) AC_SUBST([PYTHON]) ]) frr-7.2.1/m4/libtool-whole-archive.patch0000644000000000000000000000162313610377563014756 00000000000000--- /usr/share/libtool/build-aux/ltmain.sh 2017-08-01 07:13:09.611041402 +0200 +++ ltmain.sh 2018-08-31 17:32:15.381903718 +0200 @@ -8439,8 +8439,13 @@ # shared platforms. if test unsupported != "$hardcode_direct"; then test -n "$old_library" && linklib=$old_library - compile_deplibs="$dir/$linklib $compile_deplibs" - finalize_deplibs="$dir/$linklib $finalize_deplibs" + if test yes,yes = "$export_dynamic,$with_gnu_ld"; then + compile_deplibs="-Wl,--no-whole-archive $dir/$linklib -Wl,--whole-archive $compile_deplibs" + finalize_deplibs="-Wl,--no-whole-archive $dir/$linklib -Wl,--whole-archive $finalize_deplibs" + else + compile_deplibs="$dir/$linklib $compile_deplibs" + finalize_deplibs="$dir/$linklib $finalize_deplibs" + fi else compile_deplibs="-l$name -L$dir $compile_deplibs" finalize_deplibs="-l$name -L$dir $finalize_deplibs" frr-7.2.1/m4/libtool.m40000644000000000000000000112676613610377563011465 00000000000000# libtool.m4 - Configure libtool for the host system. -*-Autoconf-*- # # Copyright (C) 1996-2001, 2003-2015 Free Software Foundation, Inc. # Written by Gordon Matzigkeit, 1996 # # This file is free software; the Free Software Foundation gives # unlimited permission to copy and/or distribute it, with or without # modifications, as long as this notice is preserved. m4_define([_LT_COPYING], [dnl # Copyright (C) 2014 Free Software Foundation, Inc. # This is free software; see the source for copying conditions. There is NO # warranty; not even for MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. # GNU Libtool is free software; you can redistribute it and/or modify # it under the terms of the GNU General Public License as published by # the Free Software Foundation; either version 2 of of the License, or # (at your option) any later version. # # As a special exception to the GNU General Public License, if you # distribute this file as part of a program or library that is built # using GNU Libtool, you may include this file under the same # distribution terms that you use for the rest of that program. # # GNU Libtool is distributed in the hope that it will be useful, but # WITHOUT ANY WARRANTY; without even the implied warranty of # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the # GNU General Public License for more details. # # You should have received a copy of the GNU General Public License # along with this program. If not, see . ]) # serial 58 LT_INIT # LT_PREREQ(VERSION) # ------------------ # Complain and exit if this libtool version is less that VERSION. m4_defun([LT_PREREQ], [m4_if(m4_version_compare(m4_defn([LT_PACKAGE_VERSION]), [$1]), -1, [m4_default([$3], [m4_fatal([Libtool version $1 or higher is required], 63)])], [$2])]) # _LT_CHECK_BUILDDIR # ------------------ # Complain if the absolute build directory name contains unusual characters m4_defun([_LT_CHECK_BUILDDIR], [case `pwd` in *\ * | *\ *) AC_MSG_WARN([Libtool does not cope well with whitespace in `pwd`]) ;; esac ]) # LT_INIT([OPTIONS]) # ------------------ AC_DEFUN([LT_INIT], [AC_PREREQ([2.62])dnl We use AC_PATH_PROGS_FEATURE_CHECK AC_REQUIRE([AC_CONFIG_AUX_DIR_DEFAULT])dnl AC_BEFORE([$0], [LT_LANG])dnl AC_BEFORE([$0], [LT_OUTPUT])dnl AC_BEFORE([$0], [LTDL_INIT])dnl m4_require([_LT_CHECK_BUILDDIR])dnl dnl Autoconf doesn't catch unexpanded LT_ macros by default: m4_pattern_forbid([^_?LT_[A-Z_]+$])dnl m4_pattern_allow([^(_LT_EOF|LT_DLGLOBAL|LT_DLLAZY_OR_NOW|LT_MULTI_MODULE)$])dnl dnl aclocal doesn't pull ltoptions.m4, ltsugar.m4, or ltversion.m4 dnl unless we require an AC_DEFUNed macro: AC_REQUIRE([LTOPTIONS_VERSION])dnl AC_REQUIRE([LTSUGAR_VERSION])dnl AC_REQUIRE([LTVERSION_VERSION])dnl AC_REQUIRE([LTOBSOLETE_VERSION])dnl m4_require([_LT_PROG_LTMAIN])dnl _LT_SHELL_INIT([SHELL=${CONFIG_SHELL-/bin/sh}]) dnl Parse OPTIONS _LT_SET_OPTIONS([$0], [$1]) # This can be used to rebuild libtool when needed LIBTOOL_DEPS=$ltmain # Always use our own libtool. LIBTOOL='$(SHELL) $(top_builddir)/libtool' AC_SUBST(LIBTOOL)dnl _LT_SETUP # Only expand once: m4_define([LT_INIT]) ])# LT_INIT # Old names: AU_ALIAS([AC_PROG_LIBTOOL], [LT_INIT]) AU_ALIAS([AM_PROG_LIBTOOL], [LT_INIT]) dnl aclocal-1.4 backwards compatibility: dnl AC_DEFUN([AC_PROG_LIBTOOL], []) dnl AC_DEFUN([AM_PROG_LIBTOOL], []) # _LT_PREPARE_CC_BASENAME # ----------------------- m4_defun([_LT_PREPARE_CC_BASENAME], [ # Calculate cc_basename. Skip known compiler wrappers and cross-prefix. func_cc_basename () { for cc_temp in @S|@*""; do case $cc_temp in compile | *[[\\/]]compile | ccache | *[[\\/]]ccache ) ;; distcc | *[[\\/]]distcc | purify | *[[\\/]]purify ) ;; \-*) ;; *) break;; esac done func_cc_basename_result=`$ECHO "$cc_temp" | $SED "s%.*/%%; s%^$host_alias-%%"` } ])# _LT_PREPARE_CC_BASENAME # _LT_CC_BASENAME(CC) # ------------------- # It would be clearer to call AC_REQUIREs from _LT_PREPARE_CC_BASENAME, # but that macro is also expanded into generated libtool script, which # arranges for $SED and $ECHO to be set by different means. m4_defun([_LT_CC_BASENAME], [m4_require([_LT_PREPARE_CC_BASENAME])dnl AC_REQUIRE([_LT_DECL_SED])dnl AC_REQUIRE([_LT_PROG_ECHO_BACKSLASH])dnl func_cc_basename $1 cc_basename=$func_cc_basename_result ]) # _LT_FILEUTILS_DEFAULTS # ---------------------- # It is okay to use these file commands and assume they have been set # sensibly after 'm4_require([_LT_FILEUTILS_DEFAULTS])'. m4_defun([_LT_FILEUTILS_DEFAULTS], [: ${CP="cp -f"} : ${MV="mv -f"} : ${RM="rm -f"} ])# _LT_FILEUTILS_DEFAULTS # _LT_SETUP # --------- m4_defun([_LT_SETUP], [AC_REQUIRE([AC_CANONICAL_HOST])dnl AC_REQUIRE([AC_CANONICAL_BUILD])dnl AC_REQUIRE([_LT_PREPARE_SED_QUOTE_VARS])dnl AC_REQUIRE([_LT_PROG_ECHO_BACKSLASH])dnl _LT_DECL([], [PATH_SEPARATOR], [1], [The PATH separator for the build system])dnl dnl _LT_DECL([], [host_alias], [0], [The host system])dnl _LT_DECL([], [host], [0])dnl _LT_DECL([], [host_os], [0])dnl dnl _LT_DECL([], [build_alias], [0], [The build system])dnl _LT_DECL([], [build], [0])dnl _LT_DECL([], [build_os], [0])dnl dnl AC_REQUIRE([AC_PROG_CC])dnl AC_REQUIRE([LT_PATH_LD])dnl AC_REQUIRE([LT_PATH_NM])dnl dnl AC_REQUIRE([AC_PROG_LN_S])dnl test -z "$LN_S" && LN_S="ln -s" _LT_DECL([], [LN_S], [1], [Whether we need soft or hard links])dnl dnl AC_REQUIRE([LT_CMD_MAX_LEN])dnl _LT_DECL([objext], [ac_objext], [0], [Object file suffix (normally "o")])dnl _LT_DECL([], [exeext], [0], [Executable file suffix (normally "")])dnl dnl m4_require([_LT_FILEUTILS_DEFAULTS])dnl m4_require([_LT_CHECK_SHELL_FEATURES])dnl m4_require([_LT_PATH_CONVERSION_FUNCTIONS])dnl m4_require([_LT_CMD_RELOAD])dnl m4_require([_LT_CHECK_MAGIC_METHOD])dnl m4_require([_LT_CHECK_SHAREDLIB_FROM_LINKLIB])dnl m4_require([_LT_CMD_OLD_ARCHIVE])dnl m4_require([_LT_CMD_GLOBAL_SYMBOLS])dnl m4_require([_LT_WITH_SYSROOT])dnl m4_require([_LT_CMD_TRUNCATE])dnl _LT_CONFIG_LIBTOOL_INIT([ # See if we are running on zsh, and set the options that allow our # commands through without removal of \ escapes INIT. if test -n "\${ZSH_VERSION+set}"; then setopt NO_GLOB_SUBST fi ]) if test -n "${ZSH_VERSION+set}"; then setopt NO_GLOB_SUBST fi _LT_CHECK_OBJDIR m4_require([_LT_TAG_COMPILER])dnl case $host_os in aix3*) # AIX sometimes has problems with the GCC collect2 program. For some # reason, if we set the COLLECT_NAMES environment variable, the problems # vanish in a puff of smoke. if test set != "${COLLECT_NAMES+set}"; then COLLECT_NAMES= export COLLECT_NAMES fi ;; esac # Global variables: ofile=libtool can_build_shared=yes # All known linkers require a '.a' archive for static linking (except MSVC, # which needs '.lib'). libext=a with_gnu_ld=$lt_cv_prog_gnu_ld old_CC=$CC old_CFLAGS=$CFLAGS # Set sane defaults for various variables test -z "$CC" && CC=cc test -z "$LTCC" && LTCC=$CC test -z "$LTCFLAGS" && LTCFLAGS=$CFLAGS test -z "$LD" && LD=ld test -z "$ac_objext" && ac_objext=o _LT_CC_BASENAME([$compiler]) # Only perform the check for file, if the check method requires it test -z "$MAGIC_CMD" && MAGIC_CMD=file case $deplibs_check_method in file_magic*) if test "$file_magic_cmd" = '$MAGIC_CMD'; then _LT_PATH_MAGIC fi ;; esac # Use C for the default configuration in the libtool script LT_SUPPORTED_TAG([CC]) _LT_LANG_C_CONFIG _LT_LANG_DEFAULT_CONFIG _LT_CONFIG_COMMANDS ])# _LT_SETUP # _LT_PREPARE_SED_QUOTE_VARS # -------------------------- # Define a few sed substitution that help us do robust quoting. m4_defun([_LT_PREPARE_SED_QUOTE_VARS], [# Backslashify metacharacters that are still active within # double-quoted strings. sed_quote_subst='s/\([["`$\\]]\)/\\\1/g' # Same as above, but do not quote variable references. double_quote_subst='s/\([["`\\]]\)/\\\1/g' # Sed substitution to delay expansion of an escaped shell variable in a # double_quote_subst'ed string. delay_variable_subst='s/\\\\\\\\\\\$/\\\\\\$/g' # Sed substitution to delay expansion of an escaped single quote. delay_single_quote_subst='s/'\''/'\'\\\\\\\'\''/g' # Sed substitution to avoid accidental globbing in evaled expressions no_glob_subst='s/\*/\\\*/g' ]) # _LT_PROG_LTMAIN # --------------- # Note that this code is called both from 'configure', and 'config.status' # now that we use AC_CONFIG_COMMANDS to generate libtool. Notably, # 'config.status' has no value for ac_aux_dir unless we are using Automake, # so we pass a copy along to make sure it has a sensible value anyway. m4_defun([_LT_PROG_LTMAIN], [m4_ifdef([AC_REQUIRE_AUX_FILE], [AC_REQUIRE_AUX_FILE([ltmain.sh])])dnl _LT_CONFIG_LIBTOOL_INIT([ac_aux_dir='$ac_aux_dir']) ltmain=$ac_aux_dir/ltmain.sh ])# _LT_PROG_LTMAIN ## ------------------------------------- ## ## Accumulate code for creating libtool. ## ## ------------------------------------- ## # So that we can recreate a full libtool script including additional # tags, we accumulate the chunks of code to send to AC_CONFIG_COMMANDS # in macros and then make a single call at the end using the 'libtool' # label. # _LT_CONFIG_LIBTOOL_INIT([INIT-COMMANDS]) # ---------------------------------------- # Register INIT-COMMANDS to be passed to AC_CONFIG_COMMANDS later. m4_define([_LT_CONFIG_LIBTOOL_INIT], [m4_ifval([$1], [m4_append([_LT_OUTPUT_LIBTOOL_INIT], [$1 ])])]) # Initialize. m4_define([_LT_OUTPUT_LIBTOOL_INIT]) # _LT_CONFIG_LIBTOOL([COMMANDS]) # ------------------------------ # Register COMMANDS to be passed to AC_CONFIG_COMMANDS later. m4_define([_LT_CONFIG_LIBTOOL], [m4_ifval([$1], [m4_append([_LT_OUTPUT_LIBTOOL_COMMANDS], [$1 ])])]) # Initialize. m4_define([_LT_OUTPUT_LIBTOOL_COMMANDS]) # _LT_CONFIG_SAVE_COMMANDS([COMMANDS], [INIT_COMMANDS]) # ----------------------------------------------------- m4_defun([_LT_CONFIG_SAVE_COMMANDS], [_LT_CONFIG_LIBTOOL([$1]) _LT_CONFIG_LIBTOOL_INIT([$2]) ]) # _LT_FORMAT_COMMENT([COMMENT]) # ----------------------------- # Add leading comment marks to the start of each line, and a trailing # full-stop to the whole comment if one is not present already. m4_define([_LT_FORMAT_COMMENT], [m4_ifval([$1], [ m4_bpatsubst([m4_bpatsubst([$1], [^ *], [# ])], [['`$\]], [\\\&])]m4_bmatch([$1], [[!?.]$], [], [.]) )]) ## ------------------------ ## ## FIXME: Eliminate VARNAME ## ## ------------------------ ## # _LT_DECL([CONFIGNAME], VARNAME, VALUE, [DESCRIPTION], [IS-TAGGED?]) # ------------------------------------------------------------------- # CONFIGNAME is the name given to the value in the libtool script. # VARNAME is the (base) name used in the configure script. # VALUE may be 0, 1 or 2 for a computed quote escaped value based on # VARNAME. Any other value will be used directly. m4_define([_LT_DECL], [lt_if_append_uniq([lt_decl_varnames], [$2], [, ], [lt_dict_add_subkey([lt_decl_dict], [$2], [libtool_name], [m4_ifval([$1], [$1], [$2])]) lt_dict_add_subkey([lt_decl_dict], [$2], [value], [$3]) m4_ifval([$4], [lt_dict_add_subkey([lt_decl_dict], [$2], [description], [$4])]) lt_dict_add_subkey([lt_decl_dict], [$2], [tagged?], [m4_ifval([$5], [yes], [no])])]) ]) # _LT_TAGDECL([CONFIGNAME], VARNAME, VALUE, [DESCRIPTION]) # -------------------------------------------------------- m4_define([_LT_TAGDECL], [_LT_DECL([$1], [$2], [$3], [$4], [yes])]) # lt_decl_tag_varnames([SEPARATOR], [VARNAME1...]) # ------------------------------------------------ m4_define([lt_decl_tag_varnames], [_lt_decl_filter([tagged?], [yes], $@)]) # _lt_decl_filter(SUBKEY, VALUE, [SEPARATOR], [VARNAME1..]) # --------------------------------------------------------- m4_define([_lt_decl_filter], [m4_case([$#], [0], [m4_fatal([$0: too few arguments: $#])], [1], [m4_fatal([$0: too few arguments: $#: $1])], [2], [lt_dict_filter([lt_decl_dict], [$1], [$2], [], lt_decl_varnames)], [3], [lt_dict_filter([lt_decl_dict], [$1], [$2], [$3], lt_decl_varnames)], [lt_dict_filter([lt_decl_dict], $@)])[]dnl ]) # lt_decl_quote_varnames([SEPARATOR], [VARNAME1...]) # -------------------------------------------------- m4_define([lt_decl_quote_varnames], [_lt_decl_filter([value], [1], $@)]) # lt_decl_dquote_varnames([SEPARATOR], [VARNAME1...]) # --------------------------------------------------- m4_define([lt_decl_dquote_varnames], [_lt_decl_filter([value], [2], $@)]) # lt_decl_varnames_tagged([SEPARATOR], [VARNAME1...]) # --------------------------------------------------- m4_define([lt_decl_varnames_tagged], [m4_assert([$# <= 2])dnl _$0(m4_quote(m4_default([$1], [[, ]])), m4_ifval([$2], [[$2]], [m4_dquote(lt_decl_tag_varnames)]), m4_split(m4_normalize(m4_quote(_LT_TAGS)), [ ]))]) m4_define([_lt_decl_varnames_tagged], [m4_ifval([$3], [lt_combine([$1], [$2], [_], $3)])]) # lt_decl_all_varnames([SEPARATOR], [VARNAME1...]) # ------------------------------------------------ m4_define([lt_decl_all_varnames], [_$0(m4_quote(m4_default([$1], [[, ]])), m4_if([$2], [], m4_quote(lt_decl_varnames), m4_quote(m4_shift($@))))[]dnl ]) m4_define([_lt_decl_all_varnames], [lt_join($@, lt_decl_varnames_tagged([$1], lt_decl_tag_varnames([[, ]], m4_shift($@))))dnl ]) # _LT_CONFIG_STATUS_DECLARE([VARNAME]) # ------------------------------------ # Quote a variable value, and forward it to 'config.status' so that its # declaration there will have the same value as in 'configure'. VARNAME # must have a single quote delimited value for this to work. m4_define([_LT_CONFIG_STATUS_DECLARE], [$1='`$ECHO "$][$1" | $SED "$delay_single_quote_subst"`']) # _LT_CONFIG_STATUS_DECLARATIONS # ------------------------------ # We delimit libtool config variables with single quotes, so when # we write them to config.status, we have to be sure to quote all # embedded single quotes properly. In configure, this macro expands # each variable declared with _LT_DECL (and _LT_TAGDECL) into: # # ='`$ECHO "$" | $SED "$delay_single_quote_subst"`' m4_defun([_LT_CONFIG_STATUS_DECLARATIONS], [m4_foreach([_lt_var], m4_quote(lt_decl_all_varnames), [m4_n([_LT_CONFIG_STATUS_DECLARE(_lt_var)])])]) # _LT_LIBTOOL_TAGS # ---------------- # Output comment and list of tags supported by the script m4_defun([_LT_LIBTOOL_TAGS], [_LT_FORMAT_COMMENT([The names of the tagged configurations supported by this script])dnl available_tags='_LT_TAGS'dnl ]) # _LT_LIBTOOL_DECLARE(VARNAME, [TAG]) # ----------------------------------- # Extract the dictionary values for VARNAME (optionally with TAG) and # expand to a commented shell variable setting: # # # Some comment about what VAR is for. # visible_name=$lt_internal_name m4_define([_LT_LIBTOOL_DECLARE], [_LT_FORMAT_COMMENT(m4_quote(lt_dict_fetch([lt_decl_dict], [$1], [description])))[]dnl m4_pushdef([_libtool_name], m4_quote(lt_dict_fetch([lt_decl_dict], [$1], [libtool_name])))[]dnl m4_case(m4_quote(lt_dict_fetch([lt_decl_dict], [$1], [value])), [0], [_libtool_name=[$]$1], [1], [_libtool_name=$lt_[]$1], [2], [_libtool_name=$lt_[]$1], [_libtool_name=lt_dict_fetch([lt_decl_dict], [$1], [value])])[]dnl m4_ifval([$2], [_$2])[]m4_popdef([_libtool_name])[]dnl ]) # _LT_LIBTOOL_CONFIG_VARS # ----------------------- # Produce commented declarations of non-tagged libtool config variables # suitable for insertion in the LIBTOOL CONFIG section of the 'libtool' # script. Tagged libtool config variables (even for the LIBTOOL CONFIG # section) are produced by _LT_LIBTOOL_TAG_VARS. m4_defun([_LT_LIBTOOL_CONFIG_VARS], [m4_foreach([_lt_var], m4_quote(_lt_decl_filter([tagged?], [no], [], lt_decl_varnames)), [m4_n([_LT_LIBTOOL_DECLARE(_lt_var)])])]) # _LT_LIBTOOL_TAG_VARS(TAG) # ------------------------- m4_define([_LT_LIBTOOL_TAG_VARS], [m4_foreach([_lt_var], m4_quote(lt_decl_tag_varnames), [m4_n([_LT_LIBTOOL_DECLARE(_lt_var, [$1])])])]) # _LT_TAGVAR(VARNAME, [TAGNAME]) # ------------------------------ m4_define([_LT_TAGVAR], [m4_ifval([$2], [$1_$2], [$1])]) # _LT_CONFIG_COMMANDS # ------------------- # Send accumulated output to $CONFIG_STATUS. Thanks to the lists of # variables for single and double quote escaping we saved from calls # to _LT_DECL, we can put quote escaped variables declarations # into 'config.status', and then the shell code to quote escape them in # for loops in 'config.status'. Finally, any additional code accumulated # from calls to _LT_CONFIG_LIBTOOL_INIT is expanded. m4_defun([_LT_CONFIG_COMMANDS], [AC_PROVIDE_IFELSE([LT_OUTPUT], dnl If the libtool generation code has been placed in $CONFIG_LT, dnl instead of duplicating it all over again into config.status, dnl then we will have config.status run $CONFIG_LT later, so it dnl needs to know what name is stored there: [AC_CONFIG_COMMANDS([libtool], [$SHELL $CONFIG_LT || AS_EXIT(1)], [CONFIG_LT='$CONFIG_LT'])], dnl If the libtool generation code is destined for config.status, dnl expand the accumulated commands and init code now: [AC_CONFIG_COMMANDS([libtool], [_LT_OUTPUT_LIBTOOL_COMMANDS], [_LT_OUTPUT_LIBTOOL_COMMANDS_INIT])]) ])#_LT_CONFIG_COMMANDS # Initialize. m4_define([_LT_OUTPUT_LIBTOOL_COMMANDS_INIT], [ # The HP-UX ksh and POSIX shell print the target directory to stdout # if CDPATH is set. (unset CDPATH) >/dev/null 2>&1 && unset CDPATH sed_quote_subst='$sed_quote_subst' double_quote_subst='$double_quote_subst' delay_variable_subst='$delay_variable_subst' _LT_CONFIG_STATUS_DECLARATIONS LTCC='$LTCC' LTCFLAGS='$LTCFLAGS' compiler='$compiler_DEFAULT' # A function that is used when there is no print builtin or printf. func_fallback_echo () { eval 'cat <<_LTECHO_EOF \$[]1 _LTECHO_EOF' } # Quote evaled strings. for var in lt_decl_all_varnames([[ \ ]], lt_decl_quote_varnames); do case \`eval \\\\\$ECHO \\\\""\\\\\$\$var"\\\\"\` in *[[\\\\\\\`\\"\\\$]]*) eval "lt_\$var=\\\\\\"\\\`\\\$ECHO \\"\\\$\$var\\" | \\\$SED \\"\\\$sed_quote_subst\\"\\\`\\\\\\"" ## exclude from sc_prohibit_nested_quotes ;; *) eval "lt_\$var=\\\\\\"\\\$\$var\\\\\\"" ;; esac done # Double-quote double-evaled strings. for var in lt_decl_all_varnames([[ \ ]], lt_decl_dquote_varnames); do case \`eval \\\\\$ECHO \\\\""\\\\\$\$var"\\\\"\` in *[[\\\\\\\`\\"\\\$]]*) eval "lt_\$var=\\\\\\"\\\`\\\$ECHO \\"\\\$\$var\\" | \\\$SED -e \\"\\\$double_quote_subst\\" -e \\"\\\$sed_quote_subst\\" -e \\"\\\$delay_variable_subst\\"\\\`\\\\\\"" ## exclude from sc_prohibit_nested_quotes ;; *) eval "lt_\$var=\\\\\\"\\\$\$var\\\\\\"" ;; esac done _LT_OUTPUT_LIBTOOL_INIT ]) # _LT_GENERATED_FILE_INIT(FILE, [COMMENT]) # ------------------------------------ # Generate a child script FILE with all initialization necessary to # reuse the environment learned by the parent script, and make the # file executable. If COMMENT is supplied, it is inserted after the # '#!' sequence but before initialization text begins. After this # macro, additional text can be appended to FILE to form the body of # the child script. The macro ends with non-zero status if the # file could not be fully written (such as if the disk is full). m4_ifdef([AS_INIT_GENERATED], [m4_defun([_LT_GENERATED_FILE_INIT],[AS_INIT_GENERATED($@)])], [m4_defun([_LT_GENERATED_FILE_INIT], [m4_require([AS_PREPARE])]dnl [m4_pushdef([AS_MESSAGE_LOG_FD])]dnl [lt_write_fail=0 cat >$1 <<_ASEOF || lt_write_fail=1 #! $SHELL # Generated by $as_me. $2 SHELL=\${CONFIG_SHELL-$SHELL} export SHELL _ASEOF cat >>$1 <<\_ASEOF || lt_write_fail=1 AS_SHELL_SANITIZE _AS_PREPARE exec AS_MESSAGE_FD>&1 _ASEOF test 0 = "$lt_write_fail" && chmod +x $1[]dnl m4_popdef([AS_MESSAGE_LOG_FD])])])# _LT_GENERATED_FILE_INIT # LT_OUTPUT # --------- # This macro allows early generation of the libtool script (before # AC_OUTPUT is called), incase it is used in configure for compilation # tests. AC_DEFUN([LT_OUTPUT], [: ${CONFIG_LT=./config.lt} AC_MSG_NOTICE([creating $CONFIG_LT]) _LT_GENERATED_FILE_INIT(["$CONFIG_LT"], [# Run this file to recreate a libtool stub with the current configuration.]) cat >>"$CONFIG_LT" <<\_LTEOF lt_cl_silent=false exec AS_MESSAGE_LOG_FD>>config.log { echo AS_BOX([Running $as_me.]) } >&AS_MESSAGE_LOG_FD lt_cl_help="\ '$as_me' creates a local libtool stub from the current configuration, for use in further configure time tests before the real libtool is generated. Usage: $[0] [[OPTIONS]] -h, --help print this help, then exit -V, --version print version number, then exit -q, --quiet do not print progress messages -d, --debug don't remove temporary files Report bugs to ." lt_cl_version="\ m4_ifset([AC_PACKAGE_NAME], [AC_PACKAGE_NAME ])config.lt[]dnl m4_ifset([AC_PACKAGE_VERSION], [ AC_PACKAGE_VERSION]) configured by $[0], generated by m4_PACKAGE_STRING. Copyright (C) 2011 Free Software Foundation, Inc. This config.lt script is free software; the Free Software Foundation gives unlimited permision to copy, distribute and modify it." while test 0 != $[#] do case $[1] in --version | --v* | -V ) echo "$lt_cl_version"; exit 0 ;; --help | --h* | -h ) echo "$lt_cl_help"; exit 0 ;; --debug | --d* | -d ) debug=: ;; --quiet | --q* | --silent | --s* | -q ) lt_cl_silent=: ;; -*) AC_MSG_ERROR([unrecognized option: $[1] Try '$[0] --help' for more information.]) ;; *) AC_MSG_ERROR([unrecognized argument: $[1] Try '$[0] --help' for more information.]) ;; esac shift done if $lt_cl_silent; then exec AS_MESSAGE_FD>/dev/null fi _LTEOF cat >>"$CONFIG_LT" <<_LTEOF _LT_OUTPUT_LIBTOOL_COMMANDS_INIT _LTEOF cat >>"$CONFIG_LT" <<\_LTEOF AC_MSG_NOTICE([creating $ofile]) _LT_OUTPUT_LIBTOOL_COMMANDS AS_EXIT(0) _LTEOF chmod +x "$CONFIG_LT" # configure is writing to config.log, but config.lt does its own redirection, # appending to config.log, which fails on DOS, as config.log is still kept # open by configure. Here we exec the FD to /dev/null, effectively closing # config.log, so it can be properly (re)opened and appended to by config.lt. lt_cl_success=: test yes = "$silent" && lt_config_lt_args="$lt_config_lt_args --quiet" exec AS_MESSAGE_LOG_FD>/dev/null $SHELL "$CONFIG_LT" $lt_config_lt_args || lt_cl_success=false exec AS_MESSAGE_LOG_FD>>config.log $lt_cl_success || AS_EXIT(1) ])# LT_OUTPUT # _LT_CONFIG(TAG) # --------------- # If TAG is the built-in tag, create an initial libtool script with a # default configuration from the untagged config vars. Otherwise add code # to config.status for appending the configuration named by TAG from the # matching tagged config vars. m4_defun([_LT_CONFIG], [m4_require([_LT_FILEUTILS_DEFAULTS])dnl _LT_CONFIG_SAVE_COMMANDS([ m4_define([_LT_TAG], m4_if([$1], [], [C], [$1]))dnl m4_if(_LT_TAG, [C], [ # See if we are running on zsh, and set the options that allow our # commands through without removal of \ escapes. if test -n "${ZSH_VERSION+set}"; then setopt NO_GLOB_SUBST fi cfgfile=${ofile}T trap "$RM \"$cfgfile\"; exit 1" 1 2 15 $RM "$cfgfile" cat <<_LT_EOF >> "$cfgfile" #! $SHELL # Generated automatically by $as_me ($PACKAGE) $VERSION # NOTE: Changes made to this file will be lost: look at ltmain.sh. # Provide generalized library-building support services. # Written by Gordon Matzigkeit, 1996 _LT_COPYING _LT_LIBTOOL_TAGS # Configured defaults for sys_lib_dlsearch_path munging. : \${LT_SYS_LIBRARY_PATH="$configure_time_lt_sys_library_path"} # ### BEGIN LIBTOOL CONFIG _LT_LIBTOOL_CONFIG_VARS _LT_LIBTOOL_TAG_VARS # ### END LIBTOOL CONFIG _LT_EOF cat <<'_LT_EOF' >> "$cfgfile" # ### BEGIN FUNCTIONS SHARED WITH CONFIGURE _LT_PREPARE_MUNGE_PATH_LIST _LT_PREPARE_CC_BASENAME # ### END FUNCTIONS SHARED WITH CONFIGURE _LT_EOF case $host_os in aix3*) cat <<\_LT_EOF >> "$cfgfile" # AIX sometimes has problems with the GCC collect2 program. For some # reason, if we set the COLLECT_NAMES environment variable, the problems # vanish in a puff of smoke. if test set != "${COLLECT_NAMES+set}"; then COLLECT_NAMES= export COLLECT_NAMES fi _LT_EOF ;; esac _LT_PROG_LTMAIN # We use sed instead of cat because bash on DJGPP gets confused if # if finds mixed CR/LF and LF-only lines. Since sed operates in # text mode, it properly converts lines to CR/LF. This bash problem # is reportedly fixed, but why not run on old versions too? sed '$q' "$ltmain" >> "$cfgfile" \ || (rm -f "$cfgfile"; exit 1) mv -f "$cfgfile" "$ofile" || (rm -f "$ofile" && cp "$cfgfile" "$ofile" && rm -f "$cfgfile") chmod +x "$ofile" ], [cat <<_LT_EOF >> "$ofile" dnl Unfortunately we have to use $1 here, since _LT_TAG is not expanded dnl in a comment (ie after a #). # ### BEGIN LIBTOOL TAG CONFIG: $1 _LT_LIBTOOL_TAG_VARS(_LT_TAG) # ### END LIBTOOL TAG CONFIG: $1 _LT_EOF ])dnl /m4_if ], [m4_if([$1], [], [ PACKAGE='$PACKAGE' VERSION='$VERSION' RM='$RM' ofile='$ofile'], []) ])dnl /_LT_CONFIG_SAVE_COMMANDS ])# _LT_CONFIG # LT_SUPPORTED_TAG(TAG) # --------------------- # Trace this macro to discover what tags are supported by the libtool # --tag option, using: # autoconf --trace 'LT_SUPPORTED_TAG:$1' AC_DEFUN([LT_SUPPORTED_TAG], []) # C support is built-in for now m4_define([_LT_LANG_C_enabled], []) m4_define([_LT_TAGS], []) # LT_LANG(LANG) # ------------- # Enable libtool support for the given language if not already enabled. AC_DEFUN([LT_LANG], [AC_BEFORE([$0], [LT_OUTPUT])dnl m4_case([$1], [C], [_LT_LANG(C)], [C++], [_LT_LANG(CXX)], [Go], [_LT_LANG(GO)], [Java], [_LT_LANG(GCJ)], [Fortran 77], [_LT_LANG(F77)], [Fortran], [_LT_LANG(FC)], [Windows Resource], [_LT_LANG(RC)], [m4_ifdef([_LT_LANG_]$1[_CONFIG], [_LT_LANG($1)], [m4_fatal([$0: unsupported language: "$1"])])])dnl ])# LT_LANG # _LT_LANG(LANGNAME) # ------------------ m4_defun([_LT_LANG], [m4_ifdef([_LT_LANG_]$1[_enabled], [], [LT_SUPPORTED_TAG([$1])dnl m4_append([_LT_TAGS], [$1 ])dnl m4_define([_LT_LANG_]$1[_enabled], [])dnl _LT_LANG_$1_CONFIG($1)])dnl ])# _LT_LANG m4_ifndef([AC_PROG_GO], [ ############################################################ # NOTE: This macro has been submitted for inclusion into # # GNU Autoconf as AC_PROG_GO. When it is available in # # a released version of Autoconf we should remove this # # macro and use it instead. # ############################################################ m4_defun([AC_PROG_GO], [AC_LANG_PUSH(Go)dnl AC_ARG_VAR([GOC], [Go compiler command])dnl AC_ARG_VAR([GOFLAGS], [Go compiler flags])dnl _AC_ARG_VAR_LDFLAGS()dnl AC_CHECK_TOOL(GOC, gccgo) if test -z "$GOC"; then if test -n "$ac_tool_prefix"; then AC_CHECK_PROG(GOC, [${ac_tool_prefix}gccgo], [${ac_tool_prefix}gccgo]) fi fi if test -z "$GOC"; then AC_CHECK_PROG(GOC, gccgo, gccgo, false) fi ])#m4_defun ])#m4_ifndef # _LT_LANG_DEFAULT_CONFIG # ----------------------- m4_defun([_LT_LANG_DEFAULT_CONFIG], [AC_PROVIDE_IFELSE([AC_PROG_CXX], [LT_LANG(CXX)], [m4_define([AC_PROG_CXX], defn([AC_PROG_CXX])[LT_LANG(CXX)])]) AC_PROVIDE_IFELSE([AC_PROG_F77], [LT_LANG(F77)], [m4_define([AC_PROG_F77], defn([AC_PROG_F77])[LT_LANG(F77)])]) AC_PROVIDE_IFELSE([AC_PROG_FC], [LT_LANG(FC)], [m4_define([AC_PROG_FC], defn([AC_PROG_FC])[LT_LANG(FC)])]) dnl The call to [A][M_PROG_GCJ] is quoted like that to stop aclocal dnl pulling things in needlessly. AC_PROVIDE_IFELSE([AC_PROG_GCJ], [LT_LANG(GCJ)], [AC_PROVIDE_IFELSE([A][M_PROG_GCJ], [LT_LANG(GCJ)], [AC_PROVIDE_IFELSE([LT_PROG_GCJ], [LT_LANG(GCJ)], [m4_ifdef([AC_PROG_GCJ], [m4_define([AC_PROG_GCJ], defn([AC_PROG_GCJ])[LT_LANG(GCJ)])]) m4_ifdef([A][M_PROG_GCJ], [m4_define([A][M_PROG_GCJ], defn([A][M_PROG_GCJ])[LT_LANG(GCJ)])]) m4_ifdef([LT_PROG_GCJ], [m4_define([LT_PROG_GCJ], defn([LT_PROG_GCJ])[LT_LANG(GCJ)])])])])]) AC_PROVIDE_IFELSE([AC_PROG_GO], [LT_LANG(GO)], [m4_define([AC_PROG_GO], defn([AC_PROG_GO])[LT_LANG(GO)])]) AC_PROVIDE_IFELSE([LT_PROG_RC], [LT_LANG(RC)], [m4_define([LT_PROG_RC], defn([LT_PROG_RC])[LT_LANG(RC)])]) ])# _LT_LANG_DEFAULT_CONFIG # Obsolete macros: AU_DEFUN([AC_LIBTOOL_CXX], [LT_LANG(C++)]) AU_DEFUN([AC_LIBTOOL_F77], [LT_LANG(Fortran 77)]) AU_DEFUN([AC_LIBTOOL_FC], [LT_LANG(Fortran)]) AU_DEFUN([AC_LIBTOOL_GCJ], [LT_LANG(Java)]) AU_DEFUN([AC_LIBTOOL_RC], [LT_LANG(Windows Resource)]) dnl aclocal-1.4 backwards compatibility: dnl AC_DEFUN([AC_LIBTOOL_CXX], []) dnl AC_DEFUN([AC_LIBTOOL_F77], []) dnl AC_DEFUN([AC_LIBTOOL_FC], []) dnl AC_DEFUN([AC_LIBTOOL_GCJ], []) dnl AC_DEFUN([AC_LIBTOOL_RC], []) # _LT_TAG_COMPILER # ---------------- m4_defun([_LT_TAG_COMPILER], [AC_REQUIRE([AC_PROG_CC])dnl _LT_DECL([LTCC], [CC], [1], [A C compiler])dnl _LT_DECL([LTCFLAGS], [CFLAGS], [1], [LTCC compiler flags])dnl _LT_TAGDECL([CC], [compiler], [1], [A language specific compiler])dnl _LT_TAGDECL([with_gcc], [GCC], [0], [Is the compiler the GNU compiler?])dnl # If no C compiler was specified, use CC. LTCC=${LTCC-"$CC"} # If no C compiler flags were specified, use CFLAGS. LTCFLAGS=${LTCFLAGS-"$CFLAGS"} # Allow CC to be a program name with arguments. compiler=$CC ])# _LT_TAG_COMPILER # _LT_COMPILER_BOILERPLATE # ------------------------ # Check for compiler boilerplate output or warnings with # the simple compiler test code. m4_defun([_LT_COMPILER_BOILERPLATE], [m4_require([_LT_DECL_SED])dnl ac_outfile=conftest.$ac_objext echo "$lt_simple_compile_test_code" >conftest.$ac_ext eval "$ac_compile" 2>&1 >/dev/null | $SED '/^$/d; /^ *+/d' >conftest.err _lt_compiler_boilerplate=`cat conftest.err` $RM conftest* ])# _LT_COMPILER_BOILERPLATE # _LT_LINKER_BOILERPLATE # ---------------------- # Check for linker boilerplate output or warnings with # the simple link test code. m4_defun([_LT_LINKER_BOILERPLATE], [m4_require([_LT_DECL_SED])dnl ac_outfile=conftest.$ac_objext echo "$lt_simple_link_test_code" >conftest.$ac_ext eval "$ac_link" 2>&1 >/dev/null | $SED '/^$/d; /^ *+/d' >conftest.err _lt_linker_boilerplate=`cat conftest.err` $RM -r conftest* ])# _LT_LINKER_BOILERPLATE # _LT_REQUIRED_DARWIN_CHECKS # ------------------------- m4_defun_once([_LT_REQUIRED_DARWIN_CHECKS],[ case $host_os in rhapsody* | darwin*) AC_CHECK_TOOL([DSYMUTIL], [dsymutil], [:]) AC_CHECK_TOOL([NMEDIT], [nmedit], [:]) AC_CHECK_TOOL([LIPO], [lipo], [:]) AC_CHECK_TOOL([OTOOL], [otool], [:]) AC_CHECK_TOOL([OTOOL64], [otool64], [:]) _LT_DECL([], [DSYMUTIL], [1], [Tool to manipulate archived DWARF debug symbol files on Mac OS X]) _LT_DECL([], [NMEDIT], [1], [Tool to change global to local symbols on Mac OS X]) _LT_DECL([], [LIPO], [1], [Tool to manipulate fat objects and archives on Mac OS X]) _LT_DECL([], [OTOOL], [1], [ldd/readelf like tool for Mach-O binaries on Mac OS X]) _LT_DECL([], [OTOOL64], [1], [ldd/readelf like tool for 64 bit Mach-O binaries on Mac OS X 10.4]) AC_CACHE_CHECK([for -single_module linker flag],[lt_cv_apple_cc_single_mod], [lt_cv_apple_cc_single_mod=no if test -z "$LT_MULTI_MODULE"; then # By default we will add the -single_module flag. You can override # by either setting the environment variable LT_MULTI_MODULE # non-empty at configure time, or by adding -multi_module to the # link flags. rm -rf libconftest.dylib* echo "int foo(void){return 1;}" > conftest.c echo "$LTCC $LTCFLAGS $LDFLAGS -o libconftest.dylib \ -dynamiclib -Wl,-single_module conftest.c" >&AS_MESSAGE_LOG_FD $LTCC $LTCFLAGS $LDFLAGS -o libconftest.dylib \ -dynamiclib -Wl,-single_module conftest.c 2>conftest.err _lt_result=$? # If there is a non-empty error log, and "single_module" # appears in it, assume the flag caused a linker warning if test -s conftest.err && $GREP single_module conftest.err; then cat conftest.err >&AS_MESSAGE_LOG_FD # Otherwise, if the output was created with a 0 exit code from # the compiler, it worked. elif test -f libconftest.dylib && test 0 = "$_lt_result"; then lt_cv_apple_cc_single_mod=yes else cat conftest.err >&AS_MESSAGE_LOG_FD fi rm -rf libconftest.dylib* rm -f conftest.* fi]) AC_CACHE_CHECK([for -exported_symbols_list linker flag], [lt_cv_ld_exported_symbols_list], [lt_cv_ld_exported_symbols_list=no save_LDFLAGS=$LDFLAGS echo "_main" > conftest.sym LDFLAGS="$LDFLAGS -Wl,-exported_symbols_list,conftest.sym" AC_LINK_IFELSE([AC_LANG_PROGRAM([],[])], [lt_cv_ld_exported_symbols_list=yes], [lt_cv_ld_exported_symbols_list=no]) LDFLAGS=$save_LDFLAGS ]) AC_CACHE_CHECK([for -force_load linker flag],[lt_cv_ld_force_load], [lt_cv_ld_force_load=no cat > conftest.c << _LT_EOF int forced_loaded() { return 2;} _LT_EOF echo "$LTCC $LTCFLAGS -c -o conftest.o conftest.c" >&AS_MESSAGE_LOG_FD $LTCC $LTCFLAGS -c -o conftest.o conftest.c 2>&AS_MESSAGE_LOG_FD echo "$AR cru libconftest.a conftest.o" >&AS_MESSAGE_LOG_FD $AR cru libconftest.a conftest.o 2>&AS_MESSAGE_LOG_FD echo "$RANLIB libconftest.a" >&AS_MESSAGE_LOG_FD $RANLIB libconftest.a 2>&AS_MESSAGE_LOG_FD cat > conftest.c << _LT_EOF int main() { return 0;} _LT_EOF echo "$LTCC $LTCFLAGS $LDFLAGS -o conftest conftest.c -Wl,-force_load,./libconftest.a" >&AS_MESSAGE_LOG_FD $LTCC $LTCFLAGS $LDFLAGS -o conftest conftest.c -Wl,-force_load,./libconftest.a 2>conftest.err _lt_result=$? if test -s conftest.err && $GREP force_load conftest.err; then cat conftest.err >&AS_MESSAGE_LOG_FD elif test -f conftest && test 0 = "$_lt_result" && $GREP forced_load conftest >/dev/null 2>&1; then lt_cv_ld_force_load=yes else cat conftest.err >&AS_MESSAGE_LOG_FD fi rm -f conftest.err libconftest.a conftest conftest.c rm -rf conftest.dSYM ]) case $host_os in rhapsody* | darwin1.[[012]]) _lt_dar_allow_undefined='$wl-undefined ${wl}suppress' ;; darwin1.*) _lt_dar_allow_undefined='$wl-flat_namespace $wl-undefined ${wl}suppress' ;; darwin*) # darwin 5.x on # if running on 10.5 or later, the deployment target defaults # to the OS version, if on x86, and 10.4, the deployment # target defaults to 10.4. Don't you love it? case ${MACOSX_DEPLOYMENT_TARGET-10.0},$host in 10.0,*86*-darwin8*|10.0,*-darwin[[91]]*) _lt_dar_allow_undefined='$wl-undefined ${wl}dynamic_lookup' ;; 10.[[012]][[,.]]*) _lt_dar_allow_undefined='$wl-flat_namespace $wl-undefined ${wl}suppress' ;; 10.*) _lt_dar_allow_undefined='$wl-undefined ${wl}dynamic_lookup' ;; esac ;; esac if test yes = "$lt_cv_apple_cc_single_mod"; then _lt_dar_single_mod='$single_module' fi if test yes = "$lt_cv_ld_exported_symbols_list"; then _lt_dar_export_syms=' $wl-exported_symbols_list,$output_objdir/$libname-symbols.expsym' else _lt_dar_export_syms='~$NMEDIT -s $output_objdir/$libname-symbols.expsym $lib' fi if test : != "$DSYMUTIL" && test no = "$lt_cv_ld_force_load"; then _lt_dsymutil='~$DSYMUTIL $lib || :' else _lt_dsymutil= fi ;; esac ]) # _LT_DARWIN_LINKER_FEATURES([TAG]) # --------------------------------- # Checks for linker and compiler features on darwin m4_defun([_LT_DARWIN_LINKER_FEATURES], [ m4_require([_LT_REQUIRED_DARWIN_CHECKS]) _LT_TAGVAR(archive_cmds_need_lc, $1)=no _LT_TAGVAR(hardcode_direct, $1)=no _LT_TAGVAR(hardcode_automatic, $1)=yes _LT_TAGVAR(hardcode_shlibpath_var, $1)=unsupported if test yes = "$lt_cv_ld_force_load"; then _LT_TAGVAR(whole_archive_flag_spec, $1)='`for conv in $convenience\"\"; do test -n \"$conv\" && new_convenience=\"$new_convenience $wl-force_load,$conv\"; done; func_echo_all \"$new_convenience\"`' m4_case([$1], [F77], [_LT_TAGVAR(compiler_needs_object, $1)=yes], [FC], [_LT_TAGVAR(compiler_needs_object, $1)=yes]) else _LT_TAGVAR(whole_archive_flag_spec, $1)='' fi _LT_TAGVAR(link_all_deplibs, $1)=yes _LT_TAGVAR(allow_undefined_flag, $1)=$_lt_dar_allow_undefined case $cc_basename in ifort*|nagfor*) _lt_dar_can_shared=yes ;; *) _lt_dar_can_shared=$GCC ;; esac if test yes = "$_lt_dar_can_shared"; then output_verbose_link_cmd=func_echo_all _LT_TAGVAR(archive_cmds, $1)="\$CC -dynamiclib \$allow_undefined_flag -o \$lib \$libobjs \$deplibs \$compiler_flags -install_name \$rpath/\$soname \$verstring $_lt_dar_single_mod$_lt_dsymutil" _LT_TAGVAR(module_cmds, $1)="\$CC \$allow_undefined_flag -o \$lib -bundle \$libobjs \$deplibs \$compiler_flags$_lt_dsymutil" _LT_TAGVAR(archive_expsym_cmds, $1)="sed 's|^|_|' < \$export_symbols > \$output_objdir/\$libname-symbols.expsym~\$CC -dynamiclib \$allow_undefined_flag -o \$lib \$libobjs \$deplibs \$compiler_flags -install_name \$rpath/\$soname \$verstring $_lt_dar_single_mod$_lt_dar_export_syms$_lt_dsymutil" _LT_TAGVAR(module_expsym_cmds, $1)="sed -e 's|^|_|' < \$export_symbols > \$output_objdir/\$libname-symbols.expsym~\$CC \$allow_undefined_flag -o \$lib -bundle \$libobjs \$deplibs \$compiler_flags$_lt_dar_export_syms$_lt_dsymutil" m4_if([$1], [CXX], [ if test yes != "$lt_cv_apple_cc_single_mod"; then _LT_TAGVAR(archive_cmds, $1)="\$CC -r -keep_private_externs -nostdlib -o \$lib-master.o \$libobjs~\$CC -dynamiclib \$allow_undefined_flag -o \$lib \$lib-master.o \$deplibs \$compiler_flags -install_name \$rpath/\$soname \$verstring$_lt_dsymutil" _LT_TAGVAR(archive_expsym_cmds, $1)="sed 's|^|_|' < \$export_symbols > \$output_objdir/\$libname-symbols.expsym~\$CC -r -keep_private_externs -nostdlib -o \$lib-master.o \$libobjs~\$CC -dynamiclib \$allow_undefined_flag -o \$lib \$lib-master.o \$deplibs \$compiler_flags -install_name \$rpath/\$soname \$verstring$_lt_dar_export_syms$_lt_dsymutil" fi ],[]) else _LT_TAGVAR(ld_shlibs, $1)=no fi ]) # _LT_SYS_MODULE_PATH_AIX([TAGNAME]) # ---------------------------------- # Links a minimal program and checks the executable # for the system default hardcoded library path. In most cases, # this is /usr/lib:/lib, but when the MPI compilers are used # the location of the communication and MPI libs are included too. # If we don't find anything, use the default library path according # to the aix ld manual. # Store the results from the different compilers for each TAGNAME. # Allow to override them for all tags through lt_cv_aix_libpath. m4_defun([_LT_SYS_MODULE_PATH_AIX], [m4_require([_LT_DECL_SED])dnl if test set = "${lt_cv_aix_libpath+set}"; then aix_libpath=$lt_cv_aix_libpath else AC_CACHE_VAL([_LT_TAGVAR([lt_cv_aix_libpath_], [$1])], [AC_LINK_IFELSE([AC_LANG_PROGRAM],[ lt_aix_libpath_sed='[ /Import File Strings/,/^$/ { /^0/ { s/^0 *\([^ ]*\) *$/\1/ p } }]' _LT_TAGVAR([lt_cv_aix_libpath_], [$1])=`dump -H conftest$ac_exeext 2>/dev/null | $SED -n -e "$lt_aix_libpath_sed"` # Check for a 64-bit object if we didn't find anything. if test -z "$_LT_TAGVAR([lt_cv_aix_libpath_], [$1])"; then _LT_TAGVAR([lt_cv_aix_libpath_], [$1])=`dump -HX64 conftest$ac_exeext 2>/dev/null | $SED -n -e "$lt_aix_libpath_sed"` fi],[]) if test -z "$_LT_TAGVAR([lt_cv_aix_libpath_], [$1])"; then _LT_TAGVAR([lt_cv_aix_libpath_], [$1])=/usr/lib:/lib fi ]) aix_libpath=$_LT_TAGVAR([lt_cv_aix_libpath_], [$1]) fi ])# _LT_SYS_MODULE_PATH_AIX # _LT_SHELL_INIT(ARG) # ------------------- m4_define([_LT_SHELL_INIT], [m4_divert_text([M4SH-INIT], [$1 ])])# _LT_SHELL_INIT # _LT_PROG_ECHO_BACKSLASH # ----------------------- # Find how we can fake an echo command that does not interpret backslash. # In particular, with Autoconf 2.60 or later we add some code to the start # of the generated configure script that will find a shell with a builtin # printf (that we can use as an echo command). m4_defun([_LT_PROG_ECHO_BACKSLASH], [ECHO='\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\' ECHO=$ECHO$ECHO$ECHO$ECHO$ECHO ECHO=$ECHO$ECHO$ECHO$ECHO$ECHO$ECHO AC_MSG_CHECKING([how to print strings]) # Test print first, because it will be a builtin if present. if test "X`( print -r -- -n ) 2>/dev/null`" = X-n && \ test "X`print -r -- $ECHO 2>/dev/null`" = "X$ECHO"; then ECHO='print -r --' elif test "X`printf %s $ECHO 2>/dev/null`" = "X$ECHO"; then ECHO='printf %s\n' else # Use this function as a fallback that always works. func_fallback_echo () { eval 'cat <<_LTECHO_EOF $[]1 _LTECHO_EOF' } ECHO='func_fallback_echo' fi # func_echo_all arg... # Invoke $ECHO with all args, space-separated. func_echo_all () { $ECHO "$*" } case $ECHO in printf*) AC_MSG_RESULT([printf]) ;; print*) AC_MSG_RESULT([print -r]) ;; *) AC_MSG_RESULT([cat]) ;; esac m4_ifdef([_AS_DETECT_SUGGESTED], [_AS_DETECT_SUGGESTED([ test -n "${ZSH_VERSION+set}${BASH_VERSION+set}" || ( ECHO='\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\' ECHO=$ECHO$ECHO$ECHO$ECHO$ECHO ECHO=$ECHO$ECHO$ECHO$ECHO$ECHO$ECHO PATH=/empty FPATH=/empty; export PATH FPATH test "X`printf %s $ECHO`" = "X$ECHO" \ || test "X`print -r -- $ECHO`" = "X$ECHO" )])]) _LT_DECL([], [SHELL], [1], [Shell to use when invoking shell scripts]) _LT_DECL([], [ECHO], [1], [An echo program that protects backslashes]) ])# _LT_PROG_ECHO_BACKSLASH # _LT_WITH_SYSROOT # ---------------- AC_DEFUN([_LT_WITH_SYSROOT], [AC_MSG_CHECKING([for sysroot]) AC_ARG_WITH([sysroot], [AS_HELP_STRING([--with-sysroot@<:@=DIR@:>@], [Search for dependent libraries within DIR (or the compiler's sysroot if not specified).])], [], [with_sysroot=no]) dnl lt_sysroot will always be passed unquoted. We quote it here dnl in case the user passed a directory name. lt_sysroot= case $with_sysroot in #( yes) if test yes = "$GCC"; then lt_sysroot=`$CC --print-sysroot 2>/dev/null` fi ;; #( /*) lt_sysroot=`echo "$with_sysroot" | sed -e "$sed_quote_subst"` ;; #( no|'') ;; #( *) AC_MSG_RESULT([$with_sysroot]) AC_MSG_ERROR([The sysroot must be an absolute path.]) ;; esac AC_MSG_RESULT([${lt_sysroot:-no}]) _LT_DECL([], [lt_sysroot], [0], [The root where to search for ]dnl [dependent libraries, and where our libraries should be installed.])]) # _LT_ENABLE_LOCK # --------------- m4_defun([_LT_ENABLE_LOCK], [AC_ARG_ENABLE([libtool-lock], [AS_HELP_STRING([--disable-libtool-lock], [avoid locking (might break parallel builds)])]) test no = "$enable_libtool_lock" || enable_libtool_lock=yes # Some flags need to be propagated to the compiler or linker for good # libtool support. case $host in ia64-*-hpux*) # Find out what ABI is being produced by ac_compile, and set mode # options accordingly. echo 'int i;' > conftest.$ac_ext if AC_TRY_EVAL(ac_compile); then case `/usr/bin/file conftest.$ac_objext` in *ELF-32*) HPUX_IA64_MODE=32 ;; *ELF-64*) HPUX_IA64_MODE=64 ;; esac fi rm -rf conftest* ;; *-*-irix6*) # Find out what ABI is being produced by ac_compile, and set linker # options accordingly. echo '[#]line '$LINENO' "configure"' > conftest.$ac_ext if AC_TRY_EVAL(ac_compile); then if test yes = "$lt_cv_prog_gnu_ld"; then case `/usr/bin/file conftest.$ac_objext` in *32-bit*) LD="${LD-ld} -melf32bsmip" ;; *N32*) LD="${LD-ld} -melf32bmipn32" ;; *64-bit*) LD="${LD-ld} -melf64bmip" ;; esac else case `/usr/bin/file conftest.$ac_objext` in *32-bit*) LD="${LD-ld} -32" ;; *N32*) LD="${LD-ld} -n32" ;; *64-bit*) LD="${LD-ld} -64" ;; esac fi fi rm -rf conftest* ;; mips64*-*linux*) # Find out what ABI is being produced by ac_compile, and set linker # options accordingly. echo '[#]line '$LINENO' "configure"' > conftest.$ac_ext if AC_TRY_EVAL(ac_compile); then emul=elf case `/usr/bin/file conftest.$ac_objext` in *32-bit*) emul="${emul}32" ;; *64-bit*) emul="${emul}64" ;; esac case `/usr/bin/file conftest.$ac_objext` in *MSB*) emul="${emul}btsmip" ;; *LSB*) emul="${emul}ltsmip" ;; esac case `/usr/bin/file conftest.$ac_objext` in *N32*) emul="${emul}n32" ;; esac LD="${LD-ld} -m $emul" fi rm -rf conftest* ;; x86_64-*kfreebsd*-gnu|x86_64-*linux*|powerpc*-*linux*| \ s390*-*linux*|s390*-*tpf*|sparc*-*linux*) # Find out what ABI is being produced by ac_compile, and set linker # options accordingly. Note that the listed cases only cover the # situations where additional linker options are needed (such as when # doing 32-bit compilation for a host where ld defaults to 64-bit, or # vice versa); the common cases where no linker options are needed do # not appear in the list. echo 'int i;' > conftest.$ac_ext if AC_TRY_EVAL(ac_compile); then case `/usr/bin/file conftest.o` in *32-bit*) case $host in x86_64-*kfreebsd*-gnu) LD="${LD-ld} -m elf_i386_fbsd" ;; x86_64-*linux*) case `/usr/bin/file conftest.o` in *x86-64*) LD="${LD-ld} -m elf32_x86_64" ;; *) LD="${LD-ld} -m elf_i386" ;; esac ;; powerpc64le-*linux*) LD="${LD-ld} -m elf32lppclinux" ;; powerpc64-*linux*) LD="${LD-ld} -m elf32ppclinux" ;; s390x-*linux*) LD="${LD-ld} -m elf_s390" ;; sparc64-*linux*) LD="${LD-ld} -m elf32_sparc" ;; esac ;; *64-bit*) case $host in x86_64-*kfreebsd*-gnu) LD="${LD-ld} -m elf_x86_64_fbsd" ;; x86_64-*linux*) LD="${LD-ld} -m elf_x86_64" ;; powerpcle-*linux*) LD="${LD-ld} -m elf64lppc" ;; powerpc-*linux*) LD="${LD-ld} -m elf64ppc" ;; s390*-*linux*|s390*-*tpf*) LD="${LD-ld} -m elf64_s390" ;; sparc*-*linux*) LD="${LD-ld} -m elf64_sparc" ;; esac ;; esac fi rm -rf conftest* ;; *-*-sco3.2v5*) # On SCO OpenServer 5, we need -belf to get full-featured binaries. SAVE_CFLAGS=$CFLAGS CFLAGS="$CFLAGS -belf" AC_CACHE_CHECK([whether the C compiler needs -belf], lt_cv_cc_needs_belf, [AC_LANG_PUSH(C) AC_LINK_IFELSE([AC_LANG_PROGRAM([[]],[[]])],[lt_cv_cc_needs_belf=yes],[lt_cv_cc_needs_belf=no]) AC_LANG_POP]) if test yes != "$lt_cv_cc_needs_belf"; then # this is probably gcc 2.8.0, egcs 1.0 or newer; no need for -belf CFLAGS=$SAVE_CFLAGS fi ;; *-*solaris*) # Find out what ABI is being produced by ac_compile, and set linker # options accordingly. echo 'int i;' > conftest.$ac_ext if AC_TRY_EVAL(ac_compile); then case `/usr/bin/file conftest.o` in *64-bit*) case $lt_cv_prog_gnu_ld in yes*) case $host in i?86-*-solaris*|x86_64-*-solaris*) LD="${LD-ld} -m elf_x86_64" ;; sparc*-*-solaris*) LD="${LD-ld} -m elf64_sparc" ;; esac # GNU ld 2.21 introduced _sol2 emulations. Use them if available. if ${LD-ld} -V | grep _sol2 >/dev/null 2>&1; then LD=${LD-ld}_sol2 fi ;; *) if ${LD-ld} -64 -r -o conftest2.o conftest.o >/dev/null 2>&1; then LD="${LD-ld} -64" fi ;; esac ;; esac fi rm -rf conftest* ;; esac need_locks=$enable_libtool_lock ])# _LT_ENABLE_LOCK # _LT_PROG_AR # ----------- m4_defun([_LT_PROG_AR], [AC_CHECK_TOOLS(AR, [ar], false) : ${AR=ar} : ${AR_FLAGS=cru} _LT_DECL([], [AR], [1], [The archiver]) _LT_DECL([], [AR_FLAGS], [1], [Flags to create an archive]) AC_CACHE_CHECK([for archiver @FILE support], [lt_cv_ar_at_file], [lt_cv_ar_at_file=no AC_COMPILE_IFELSE([AC_LANG_PROGRAM], [echo conftest.$ac_objext > conftest.lst lt_ar_try='$AR $AR_FLAGS libconftest.a @conftest.lst >&AS_MESSAGE_LOG_FD' AC_TRY_EVAL([lt_ar_try]) if test 0 -eq "$ac_status"; then # Ensure the archiver fails upon bogus file names. rm -f conftest.$ac_objext libconftest.a AC_TRY_EVAL([lt_ar_try]) if test 0 -ne "$ac_status"; then lt_cv_ar_at_file=@ fi fi rm -f conftest.* libconftest.a ]) ]) if test no = "$lt_cv_ar_at_file"; then archiver_list_spec= else archiver_list_spec=$lt_cv_ar_at_file fi _LT_DECL([], [archiver_list_spec], [1], [How to feed a file listing to the archiver]) ])# _LT_PROG_AR # _LT_CMD_OLD_ARCHIVE # ------------------- m4_defun([_LT_CMD_OLD_ARCHIVE], [_LT_PROG_AR AC_CHECK_TOOL(STRIP, strip, :) test -z "$STRIP" && STRIP=: _LT_DECL([], [STRIP], [1], [A symbol stripping program]) AC_CHECK_TOOL(RANLIB, ranlib, :) test -z "$RANLIB" && RANLIB=: _LT_DECL([], [RANLIB], [1], [Commands used to install an old-style archive]) # Determine commands to create old-style static archives. old_archive_cmds='$AR $AR_FLAGS $oldlib$oldobjs' old_postinstall_cmds='chmod 644 $oldlib' old_postuninstall_cmds= if test -n "$RANLIB"; then case $host_os in bitrig* | openbsd*) old_postinstall_cmds="$old_postinstall_cmds~\$RANLIB -t \$tool_oldlib" ;; *) old_postinstall_cmds="$old_postinstall_cmds~\$RANLIB \$tool_oldlib" ;; esac old_archive_cmds="$old_archive_cmds~\$RANLIB \$tool_oldlib" fi case $host_os in darwin*) lock_old_archive_extraction=yes ;; *) lock_old_archive_extraction=no ;; esac _LT_DECL([], [old_postinstall_cmds], [2]) _LT_DECL([], [old_postuninstall_cmds], [2]) _LT_TAGDECL([], [old_archive_cmds], [2], [Commands used to build an old-style archive]) _LT_DECL([], [lock_old_archive_extraction], [0], [Whether to use a lock for old archive extraction]) ])# _LT_CMD_OLD_ARCHIVE # _LT_COMPILER_OPTION(MESSAGE, VARIABLE-NAME, FLAGS, # [OUTPUT-FILE], [ACTION-SUCCESS], [ACTION-FAILURE]) # ---------------------------------------------------------------- # Check whether the given compiler option works AC_DEFUN([_LT_COMPILER_OPTION], [m4_require([_LT_FILEUTILS_DEFAULTS])dnl m4_require([_LT_DECL_SED])dnl AC_CACHE_CHECK([$1], [$2], [$2=no m4_if([$4], , [ac_outfile=conftest.$ac_objext], [ac_outfile=$4]) echo "$lt_simple_compile_test_code" > conftest.$ac_ext lt_compiler_flag="$3" ## exclude from sc_useless_quotes_in_assignment # Insert the option either (1) after the last *FLAGS variable, or # (2) before a word containing "conftest.", or (3) at the end. # Note that $ac_compile itself does not contain backslashes and begins # with a dollar sign (not a hyphen), so the echo should work correctly. # The option is referenced via a variable to avoid confusing sed. lt_compile=`echo "$ac_compile" | $SED \ -e 's:.*FLAGS}\{0,1\} :&$lt_compiler_flag :; t' \ -e 's: [[^ ]]*conftest\.: $lt_compiler_flag&:; t' \ -e 's:$: $lt_compiler_flag:'` (eval echo "\"\$as_me:$LINENO: $lt_compile\"" >&AS_MESSAGE_LOG_FD) (eval "$lt_compile" 2>conftest.err) ac_status=$? cat conftest.err >&AS_MESSAGE_LOG_FD echo "$as_me:$LINENO: \$? = $ac_status" >&AS_MESSAGE_LOG_FD if (exit $ac_status) && test -s "$ac_outfile"; then # The compiler can only warn and ignore the option if not recognized # So say no if there are warnings other than the usual output. $ECHO "$_lt_compiler_boilerplate" | $SED '/^$/d' >conftest.exp $SED '/^$/d; /^ *+/d' conftest.err >conftest.er2 if test ! -s conftest.er2 || diff conftest.exp conftest.er2 >/dev/null; then $2=yes fi fi $RM conftest* ]) if test yes = "[$]$2"; then m4_if([$5], , :, [$5]) else m4_if([$6], , :, [$6]) fi ])# _LT_COMPILER_OPTION # Old name: AU_ALIAS([AC_LIBTOOL_COMPILER_OPTION], [_LT_COMPILER_OPTION]) dnl aclocal-1.4 backwards compatibility: dnl AC_DEFUN([AC_LIBTOOL_COMPILER_OPTION], []) # _LT_LINKER_OPTION(MESSAGE, VARIABLE-NAME, FLAGS, # [ACTION-SUCCESS], [ACTION-FAILURE]) # ---------------------------------------------------- # Check whether the given linker option works AC_DEFUN([_LT_LINKER_OPTION], [m4_require([_LT_FILEUTILS_DEFAULTS])dnl m4_require([_LT_DECL_SED])dnl AC_CACHE_CHECK([$1], [$2], [$2=no save_LDFLAGS=$LDFLAGS LDFLAGS="$LDFLAGS $3" echo "$lt_simple_link_test_code" > conftest.$ac_ext if (eval $ac_link 2>conftest.err) && test -s conftest$ac_exeext; then # The linker can only warn and ignore the option if not recognized # So say no if there are warnings if test -s conftest.err; then # Append any errors to the config.log. cat conftest.err 1>&AS_MESSAGE_LOG_FD $ECHO "$_lt_linker_boilerplate" | $SED '/^$/d' > conftest.exp $SED '/^$/d; /^ *+/d' conftest.err >conftest.er2 if diff conftest.exp conftest.er2 >/dev/null; then $2=yes fi else $2=yes fi fi $RM -r conftest* LDFLAGS=$save_LDFLAGS ]) if test yes = "[$]$2"; then m4_if([$4], , :, [$4]) else m4_if([$5], , :, [$5]) fi ])# _LT_LINKER_OPTION # Old name: AU_ALIAS([AC_LIBTOOL_LINKER_OPTION], [_LT_LINKER_OPTION]) dnl aclocal-1.4 backwards compatibility: dnl AC_DEFUN([AC_LIBTOOL_LINKER_OPTION], []) # LT_CMD_MAX_LEN #--------------- AC_DEFUN([LT_CMD_MAX_LEN], [AC_REQUIRE([AC_CANONICAL_HOST])dnl # find the maximum length of command line arguments AC_MSG_CHECKING([the maximum length of command line arguments]) AC_CACHE_VAL([lt_cv_sys_max_cmd_len], [dnl i=0 teststring=ABCD case $build_os in msdosdjgpp*) # On DJGPP, this test can blow up pretty badly due to problems in libc # (any single argument exceeding 2000 bytes causes a buffer overrun # during glob expansion). Even if it were fixed, the result of this # check would be larger than it should be. lt_cv_sys_max_cmd_len=12288; # 12K is about right ;; gnu*) # Under GNU Hurd, this test is not required because there is # no limit to the length of command line arguments. # Libtool will interpret -1 as no limit whatsoever lt_cv_sys_max_cmd_len=-1; ;; cygwin* | mingw* | cegcc*) # On Win9x/ME, this test blows up -- it succeeds, but takes # about 5 minutes as the teststring grows exponentially. # Worse, since 9x/ME are not pre-emptively multitasking, # you end up with a "frozen" computer, even though with patience # the test eventually succeeds (with a max line length of 256k). # Instead, let's just punt: use the minimum linelength reported by # all of the supported platforms: 8192 (on NT/2K/XP). lt_cv_sys_max_cmd_len=8192; ;; mint*) # On MiNT this can take a long time and run out of memory. lt_cv_sys_max_cmd_len=8192; ;; amigaos*) # On AmigaOS with pdksh, this test takes hours, literally. # So we just punt and use a minimum line length of 8192. lt_cv_sys_max_cmd_len=8192; ;; bitrig* | darwin* | dragonfly* | freebsd* | netbsd* | openbsd*) # This has been around since 386BSD, at least. Likely further. if test -x /sbin/sysctl; then lt_cv_sys_max_cmd_len=`/sbin/sysctl -n kern.argmax` elif test -x /usr/sbin/sysctl; then lt_cv_sys_max_cmd_len=`/usr/sbin/sysctl -n kern.argmax` else lt_cv_sys_max_cmd_len=65536 # usable default for all BSDs fi # And add a safety zone lt_cv_sys_max_cmd_len=`expr $lt_cv_sys_max_cmd_len \/ 4` lt_cv_sys_max_cmd_len=`expr $lt_cv_sys_max_cmd_len \* 3` ;; interix*) # We know the value 262144 and hardcode it with a safety zone (like BSD) lt_cv_sys_max_cmd_len=196608 ;; os2*) # The test takes a long time on OS/2. lt_cv_sys_max_cmd_len=8192 ;; osf*) # Dr. Hans Ekkehard Plesser reports seeing a kernel panic running configure # due to this test when exec_disable_arg_limit is 1 on Tru64. It is not # nice to cause kernel panics so lets avoid the loop below. # First set a reasonable default. lt_cv_sys_max_cmd_len=16384 # if test -x /sbin/sysconfig; then case `/sbin/sysconfig -q proc exec_disable_arg_limit` in *1*) lt_cv_sys_max_cmd_len=-1 ;; esac fi ;; sco3.2v5*) lt_cv_sys_max_cmd_len=102400 ;; sysv5* | sco5v6* | sysv4.2uw2*) kargmax=`grep ARG_MAX /etc/conf/cf.d/stune 2>/dev/null` if test -n "$kargmax"; then lt_cv_sys_max_cmd_len=`echo $kargmax | sed 's/.*[[ ]]//'` else lt_cv_sys_max_cmd_len=32768 fi ;; *) lt_cv_sys_max_cmd_len=`(getconf ARG_MAX) 2> /dev/null` if test -n "$lt_cv_sys_max_cmd_len" && \ test undefined != "$lt_cv_sys_max_cmd_len"; then lt_cv_sys_max_cmd_len=`expr $lt_cv_sys_max_cmd_len \/ 4` lt_cv_sys_max_cmd_len=`expr $lt_cv_sys_max_cmd_len \* 3` else # Make teststring a little bigger before we do anything with it. # a 1K string should be a reasonable start. for i in 1 2 3 4 5 6 7 8; do teststring=$teststring$teststring done SHELL=${SHELL-${CONFIG_SHELL-/bin/sh}} # If test is not a shell built-in, we'll probably end up computing a # maximum length that is only half of the actual maximum length, but # we can't tell. while { test X`env echo "$teststring$teststring" 2>/dev/null` \ = "X$teststring$teststring"; } >/dev/null 2>&1 && test 17 != "$i" # 1/2 MB should be enough do i=`expr $i + 1` teststring=$teststring$teststring done # Only check the string length outside the loop. lt_cv_sys_max_cmd_len=`expr "X$teststring" : ".*" 2>&1` teststring= # Add a significant safety factor because C++ compilers can tack on # massive amounts of additional arguments before passing them to the # linker. It appears as though 1/2 is a usable value. lt_cv_sys_max_cmd_len=`expr $lt_cv_sys_max_cmd_len \/ 2` fi ;; esac ]) if test -n "$lt_cv_sys_max_cmd_len"; then AC_MSG_RESULT($lt_cv_sys_max_cmd_len) else AC_MSG_RESULT(none) fi max_cmd_len=$lt_cv_sys_max_cmd_len _LT_DECL([], [max_cmd_len], [0], [What is the maximum length of a command?]) ])# LT_CMD_MAX_LEN # Old name: AU_ALIAS([AC_LIBTOOL_SYS_MAX_CMD_LEN], [LT_CMD_MAX_LEN]) dnl aclocal-1.4 backwards compatibility: dnl AC_DEFUN([AC_LIBTOOL_SYS_MAX_CMD_LEN], []) # _LT_HEADER_DLFCN # ---------------- m4_defun([_LT_HEADER_DLFCN], [AC_CHECK_HEADERS([dlfcn.h], [], [], [AC_INCLUDES_DEFAULT])dnl ])# _LT_HEADER_DLFCN # _LT_TRY_DLOPEN_SELF (ACTION-IF-TRUE, ACTION-IF-TRUE-W-USCORE, # ACTION-IF-FALSE, ACTION-IF-CROSS-COMPILING) # ---------------------------------------------------------------- m4_defun([_LT_TRY_DLOPEN_SELF], [m4_require([_LT_HEADER_DLFCN])dnl if test yes = "$cross_compiling"; then : [$4] else lt_dlunknown=0; lt_dlno_uscore=1; lt_dlneed_uscore=2 lt_status=$lt_dlunknown cat > conftest.$ac_ext <<_LT_EOF [#line $LINENO "configure" #include "confdefs.h" #if HAVE_DLFCN_H #include #endif #include #ifdef RTLD_GLOBAL # define LT_DLGLOBAL RTLD_GLOBAL #else # ifdef DL_GLOBAL # define LT_DLGLOBAL DL_GLOBAL # else # define LT_DLGLOBAL 0 # endif #endif /* We may have to define LT_DLLAZY_OR_NOW in the command line if we find out it does not work in some platform. */ #ifndef LT_DLLAZY_OR_NOW # ifdef RTLD_LAZY # define LT_DLLAZY_OR_NOW RTLD_LAZY # else # ifdef DL_LAZY # define LT_DLLAZY_OR_NOW DL_LAZY # else # ifdef RTLD_NOW # define LT_DLLAZY_OR_NOW RTLD_NOW # else # ifdef DL_NOW # define LT_DLLAZY_OR_NOW DL_NOW # else # define LT_DLLAZY_OR_NOW 0 # endif # endif # endif # endif #endif /* When -fvisibility=hidden is used, assume the code has been annotated correspondingly for the symbols needed. */ #if defined __GNUC__ && (((__GNUC__ == 3) && (__GNUC_MINOR__ >= 3)) || (__GNUC__ > 3)) int fnord () __attribute__((visibility("default"))); #endif int fnord () { return 42; } int main () { void *self = dlopen (0, LT_DLGLOBAL|LT_DLLAZY_OR_NOW); int status = $lt_dlunknown; if (self) { if (dlsym (self,"fnord")) status = $lt_dlno_uscore; else { if (dlsym( self,"_fnord")) status = $lt_dlneed_uscore; else puts (dlerror ()); } /* dlclose (self); */ } else puts (dlerror ()); return status; }] _LT_EOF if AC_TRY_EVAL(ac_link) && test -s "conftest$ac_exeext" 2>/dev/null; then (./conftest; exit; ) >&AS_MESSAGE_LOG_FD 2>/dev/null lt_status=$? case x$lt_status in x$lt_dlno_uscore) $1 ;; x$lt_dlneed_uscore) $2 ;; x$lt_dlunknown|x*) $3 ;; esac else : # compilation failed $3 fi fi rm -fr conftest* ])# _LT_TRY_DLOPEN_SELF # LT_SYS_DLOPEN_SELF # ------------------ AC_DEFUN([LT_SYS_DLOPEN_SELF], [m4_require([_LT_HEADER_DLFCN])dnl if test yes != "$enable_dlopen"; then enable_dlopen=unknown enable_dlopen_self=unknown enable_dlopen_self_static=unknown else lt_cv_dlopen=no lt_cv_dlopen_libs= case $host_os in beos*) lt_cv_dlopen=load_add_on lt_cv_dlopen_libs= lt_cv_dlopen_self=yes ;; mingw* | pw32* | cegcc*) lt_cv_dlopen=LoadLibrary lt_cv_dlopen_libs= ;; cygwin*) lt_cv_dlopen=dlopen lt_cv_dlopen_libs= ;; darwin*) # if libdl is installed we need to link against it AC_CHECK_LIB([dl], [dlopen], [lt_cv_dlopen=dlopen lt_cv_dlopen_libs=-ldl],[ lt_cv_dlopen=dyld lt_cv_dlopen_libs= lt_cv_dlopen_self=yes ]) ;; tpf*) # Don't try to run any link tests for TPF. We know it's impossible # because TPF is a cross-compiler, and we know how we open DSOs. lt_cv_dlopen=dlopen lt_cv_dlopen_libs= lt_cv_dlopen_self=no ;; *) AC_CHECK_FUNC([shl_load], [lt_cv_dlopen=shl_load], [AC_CHECK_LIB([dld], [shl_load], [lt_cv_dlopen=shl_load lt_cv_dlopen_libs=-ldld], [AC_CHECK_FUNC([dlopen], [lt_cv_dlopen=dlopen], [AC_CHECK_LIB([dl], [dlopen], [lt_cv_dlopen=dlopen lt_cv_dlopen_libs=-ldl], [AC_CHECK_LIB([svld], [dlopen], [lt_cv_dlopen=dlopen lt_cv_dlopen_libs=-lsvld], [AC_CHECK_LIB([dld], [dld_link], [lt_cv_dlopen=dld_link lt_cv_dlopen_libs=-ldld]) ]) ]) ]) ]) ]) ;; esac if test no = "$lt_cv_dlopen"; then enable_dlopen=no else enable_dlopen=yes fi case $lt_cv_dlopen in dlopen) save_CPPFLAGS=$CPPFLAGS test yes = "$ac_cv_header_dlfcn_h" && CPPFLAGS="$CPPFLAGS -DHAVE_DLFCN_H" save_LDFLAGS=$LDFLAGS wl=$lt_prog_compiler_wl eval LDFLAGS=\"\$LDFLAGS $export_dynamic_flag_spec\" save_LIBS=$LIBS LIBS="$lt_cv_dlopen_libs $LIBS" AC_CACHE_CHECK([whether a program can dlopen itself], lt_cv_dlopen_self, [dnl _LT_TRY_DLOPEN_SELF( lt_cv_dlopen_self=yes, lt_cv_dlopen_self=yes, lt_cv_dlopen_self=no, lt_cv_dlopen_self=cross) ]) if test yes = "$lt_cv_dlopen_self"; then wl=$lt_prog_compiler_wl eval LDFLAGS=\"\$LDFLAGS $lt_prog_compiler_static\" AC_CACHE_CHECK([whether a statically linked program can dlopen itself], lt_cv_dlopen_self_static, [dnl _LT_TRY_DLOPEN_SELF( lt_cv_dlopen_self_static=yes, lt_cv_dlopen_self_static=yes, lt_cv_dlopen_self_static=no, lt_cv_dlopen_self_static=cross) ]) fi CPPFLAGS=$save_CPPFLAGS LDFLAGS=$save_LDFLAGS LIBS=$save_LIBS ;; esac case $lt_cv_dlopen_self in yes|no) enable_dlopen_self=$lt_cv_dlopen_self ;; *) enable_dlopen_self=unknown ;; esac case $lt_cv_dlopen_self_static in yes|no) enable_dlopen_self_static=$lt_cv_dlopen_self_static ;; *) enable_dlopen_self_static=unknown ;; esac fi _LT_DECL([dlopen_support], [enable_dlopen], [0], [Whether dlopen is supported]) _LT_DECL([dlopen_self], [enable_dlopen_self], [0], [Whether dlopen of programs is supported]) _LT_DECL([dlopen_self_static], [enable_dlopen_self_static], [0], [Whether dlopen of statically linked programs is supported]) ])# LT_SYS_DLOPEN_SELF # Old name: AU_ALIAS([AC_LIBTOOL_DLOPEN_SELF], [LT_SYS_DLOPEN_SELF]) dnl aclocal-1.4 backwards compatibility: dnl AC_DEFUN([AC_LIBTOOL_DLOPEN_SELF], []) # _LT_COMPILER_C_O([TAGNAME]) # --------------------------- # Check to see if options -c and -o are simultaneously supported by compiler. # This macro does not hard code the compiler like AC_PROG_CC_C_O. m4_defun([_LT_COMPILER_C_O], [m4_require([_LT_DECL_SED])dnl m4_require([_LT_FILEUTILS_DEFAULTS])dnl m4_require([_LT_TAG_COMPILER])dnl AC_CACHE_CHECK([if $compiler supports -c -o file.$ac_objext], [_LT_TAGVAR(lt_cv_prog_compiler_c_o, $1)], [_LT_TAGVAR(lt_cv_prog_compiler_c_o, $1)=no $RM -r conftest 2>/dev/null mkdir conftest cd conftest mkdir out echo "$lt_simple_compile_test_code" > conftest.$ac_ext lt_compiler_flag="-o out/conftest2.$ac_objext" # Insert the option either (1) after the last *FLAGS variable, or # (2) before a word containing "conftest.", or (3) at the end. # Note that $ac_compile itself does not contain backslashes and begins # with a dollar sign (not a hyphen), so the echo should work correctly. lt_compile=`echo "$ac_compile" | $SED \ -e 's:.*FLAGS}\{0,1\} :&$lt_compiler_flag :; t' \ -e 's: [[^ ]]*conftest\.: $lt_compiler_flag&:; t' \ -e 's:$: $lt_compiler_flag:'` (eval echo "\"\$as_me:$LINENO: $lt_compile\"" >&AS_MESSAGE_LOG_FD) (eval "$lt_compile" 2>out/conftest.err) ac_status=$? cat out/conftest.err >&AS_MESSAGE_LOG_FD echo "$as_me:$LINENO: \$? = $ac_status" >&AS_MESSAGE_LOG_FD if (exit $ac_status) && test -s out/conftest2.$ac_objext then # The compiler can only warn and ignore the option if not recognized # So say no if there are warnings $ECHO "$_lt_compiler_boilerplate" | $SED '/^$/d' > out/conftest.exp $SED '/^$/d; /^ *+/d' out/conftest.err >out/conftest.er2 if test ! -s out/conftest.er2 || diff out/conftest.exp out/conftest.er2 >/dev/null; then _LT_TAGVAR(lt_cv_prog_compiler_c_o, $1)=yes fi fi chmod u+w . 2>&AS_MESSAGE_LOG_FD $RM conftest* # SGI C++ compiler will create directory out/ii_files/ for # template instantiation test -d out/ii_files && $RM out/ii_files/* && rmdir out/ii_files $RM out/* && rmdir out cd .. $RM -r conftest $RM conftest* ]) _LT_TAGDECL([compiler_c_o], [lt_cv_prog_compiler_c_o], [1], [Does compiler simultaneously support -c and -o options?]) ])# _LT_COMPILER_C_O # _LT_COMPILER_FILE_LOCKS([TAGNAME]) # ---------------------------------- # Check to see if we can do hard links to lock some files if needed m4_defun([_LT_COMPILER_FILE_LOCKS], [m4_require([_LT_ENABLE_LOCK])dnl m4_require([_LT_FILEUTILS_DEFAULTS])dnl _LT_COMPILER_C_O([$1]) hard_links=nottested if test no = "$_LT_TAGVAR(lt_cv_prog_compiler_c_o, $1)" && test no != "$need_locks"; then # do not overwrite the value of need_locks provided by the user AC_MSG_CHECKING([if we can lock with hard links]) hard_links=yes $RM conftest* ln conftest.a conftest.b 2>/dev/null && hard_links=no touch conftest.a ln conftest.a conftest.b 2>&5 || hard_links=no ln conftest.a conftest.b 2>/dev/null && hard_links=no AC_MSG_RESULT([$hard_links]) if test no = "$hard_links"; then AC_MSG_WARN(['$CC' does not support '-c -o', so 'make -j' may be unsafe]) need_locks=warn fi else need_locks=no fi _LT_DECL([], [need_locks], [1], [Must we lock files when doing compilation?]) ])# _LT_COMPILER_FILE_LOCKS # _LT_CHECK_OBJDIR # ---------------- m4_defun([_LT_CHECK_OBJDIR], [AC_CACHE_CHECK([for objdir], [lt_cv_objdir], [rm -f .libs 2>/dev/null mkdir .libs 2>/dev/null if test -d .libs; then lt_cv_objdir=.libs else # MS-DOS does not allow filenames that begin with a dot. lt_cv_objdir=_libs fi rmdir .libs 2>/dev/null]) objdir=$lt_cv_objdir _LT_DECL([], [objdir], [0], [The name of the directory that contains temporary libtool files])dnl m4_pattern_allow([LT_OBJDIR])dnl AC_DEFINE_UNQUOTED([LT_OBJDIR], "$lt_cv_objdir/", [Define to the sub-directory where libtool stores uninstalled libraries.]) ])# _LT_CHECK_OBJDIR # _LT_LINKER_HARDCODE_LIBPATH([TAGNAME]) # -------------------------------------- # Check hardcoding attributes. m4_defun([_LT_LINKER_HARDCODE_LIBPATH], [AC_MSG_CHECKING([how to hardcode library paths into programs]) _LT_TAGVAR(hardcode_action, $1)= if test -n "$_LT_TAGVAR(hardcode_libdir_flag_spec, $1)" || test -n "$_LT_TAGVAR(runpath_var, $1)" || test yes = "$_LT_TAGVAR(hardcode_automatic, $1)"; then # We can hardcode non-existent directories. if test no != "$_LT_TAGVAR(hardcode_direct, $1)" && # If the only mechanism to avoid hardcoding is shlibpath_var, we # have to relink, otherwise we might link with an installed library # when we should be linking with a yet-to-be-installed one ## test no != "$_LT_TAGVAR(hardcode_shlibpath_var, $1)" && test no != "$_LT_TAGVAR(hardcode_minus_L, $1)"; then # Linking always hardcodes the temporary library directory. _LT_TAGVAR(hardcode_action, $1)=relink else # We can link without hardcoding, and we can hardcode nonexisting dirs. _LT_TAGVAR(hardcode_action, $1)=immediate fi else # We cannot hardcode anything, or else we can only hardcode existing # directories. _LT_TAGVAR(hardcode_action, $1)=unsupported fi AC_MSG_RESULT([$_LT_TAGVAR(hardcode_action, $1)]) if test relink = "$_LT_TAGVAR(hardcode_action, $1)" || test yes = "$_LT_TAGVAR(inherit_rpath, $1)"; then # Fast installation is not supported enable_fast_install=no elif test yes = "$shlibpath_overrides_runpath" || test no = "$enable_shared"; then # Fast installation is not necessary enable_fast_install=needless fi _LT_TAGDECL([], [hardcode_action], [0], [How to hardcode a shared library path into an executable]) ])# _LT_LINKER_HARDCODE_LIBPATH # _LT_CMD_STRIPLIB # ---------------- m4_defun([_LT_CMD_STRIPLIB], [m4_require([_LT_DECL_EGREP]) striplib= old_striplib= AC_MSG_CHECKING([whether stripping libraries is possible]) if test -n "$STRIP" && $STRIP -V 2>&1 | $GREP "GNU strip" >/dev/null; then test -z "$old_striplib" && old_striplib="$STRIP --strip-debug" test -z "$striplib" && striplib="$STRIP --strip-unneeded" AC_MSG_RESULT([yes]) else # FIXME - insert some real tests, host_os isn't really good enough case $host_os in darwin*) if test -n "$STRIP"; then striplib="$STRIP -x" old_striplib="$STRIP -S" AC_MSG_RESULT([yes]) else AC_MSG_RESULT([no]) fi ;; *) AC_MSG_RESULT([no]) ;; esac fi _LT_DECL([], [old_striplib], [1], [Commands to strip libraries]) _LT_DECL([], [striplib], [1]) ])# _LT_CMD_STRIPLIB # _LT_PREPARE_MUNGE_PATH_LIST # --------------------------- # Make sure func_munge_path_list() is defined correctly. m4_defun([_LT_PREPARE_MUNGE_PATH_LIST], [[# func_munge_path_list VARIABLE PATH # ----------------------------------- # VARIABLE is name of variable containing _space_ separated list of # directories to be munged by the contents of PATH, which is string # having a format: # "DIR[:DIR]:" # string "DIR[ DIR]" will be prepended to VARIABLE # ":DIR[:DIR]" # string "DIR[ DIR]" will be appended to VARIABLE # "DIRP[:DIRP]::[DIRA:]DIRA" # string "DIRP[ DIRP]" will be prepended to VARIABLE and string # "DIRA[ DIRA]" will be appended to VARIABLE # "DIR[:DIR]" # VARIABLE will be replaced by "DIR[ DIR]" func_munge_path_list () { case x@S|@2 in x) ;; *:) eval @S|@1=\"`$ECHO @S|@2 | $SED 's/:/ /g'` \@S|@@S|@1\" ;; x:*) eval @S|@1=\"\@S|@@S|@1 `$ECHO @S|@2 | $SED 's/:/ /g'`\" ;; *::*) eval @S|@1=\"\@S|@@S|@1\ `$ECHO @S|@2 | $SED -e 's/.*:://' -e 's/:/ /g'`\" eval @S|@1=\"`$ECHO @S|@2 | $SED -e 's/::.*//' -e 's/:/ /g'`\ \@S|@@S|@1\" ;; *) eval @S|@1=\"`$ECHO @S|@2 | $SED 's/:/ /g'`\" ;; esac } ]])# _LT_PREPARE_PATH_LIST # _LT_SYS_DYNAMIC_LINKER([TAG]) # ----------------------------- # PORTME Fill in your ld.so characteristics m4_defun([_LT_SYS_DYNAMIC_LINKER], [AC_REQUIRE([AC_CANONICAL_HOST])dnl m4_require([_LT_DECL_EGREP])dnl m4_require([_LT_FILEUTILS_DEFAULTS])dnl m4_require([_LT_DECL_OBJDUMP])dnl m4_require([_LT_DECL_SED])dnl m4_require([_LT_CHECK_SHELL_FEATURES])dnl m4_require([_LT_PREPARE_MUNGE_PATH_LIST])dnl AC_MSG_CHECKING([dynamic linker characteristics]) m4_if([$1], [], [ if test yes = "$GCC"; then case $host_os in darwin*) lt_awk_arg='/^libraries:/,/LR/' ;; *) lt_awk_arg='/^libraries:/' ;; esac case $host_os in mingw* | cegcc*) lt_sed_strip_eq='s|=\([[A-Za-z]]:\)|\1|g' ;; *) lt_sed_strip_eq='s|=/|/|g' ;; esac lt_search_path_spec=`$CC -print-search-dirs | awk $lt_awk_arg | $SED -e "s/^libraries://" -e $lt_sed_strip_eq` case $lt_search_path_spec in *\;*) # if the path contains ";" then we assume it to be the separator # otherwise default to the standard path separator (i.e. ":") - it is # assumed that no part of a normal pathname contains ";" but that should # okay in the real world where ";" in dirpaths is itself problematic. lt_search_path_spec=`$ECHO "$lt_search_path_spec" | $SED 's/;/ /g'` ;; *) lt_search_path_spec=`$ECHO "$lt_search_path_spec" | $SED "s/$PATH_SEPARATOR/ /g"` ;; esac # Ok, now we have the path, separated by spaces, we can step through it # and add multilib dir if necessary... lt_tmp_lt_search_path_spec= lt_multi_os_dir=/`$CC $CPPFLAGS $CFLAGS $LDFLAGS -print-multi-os-directory 2>/dev/null` # ...but if some path component already ends with the multilib dir we assume # that all is fine and trust -print-search-dirs as is (GCC 4.2? or newer). case "$lt_multi_os_dir; $lt_search_path_spec " in "/; "* | "/.; "* | "/./; "* | *"$lt_multi_os_dir "* | *"$lt_multi_os_dir/ "*) lt_multi_os_dir= ;; esac for lt_sys_path in $lt_search_path_spec; do if test -d "$lt_sys_path$lt_multi_os_dir"; then lt_tmp_lt_search_path_spec="$lt_tmp_lt_search_path_spec $lt_sys_path$lt_multi_os_dir" elif test -n "$lt_multi_os_dir"; then test -d "$lt_sys_path" && \ lt_tmp_lt_search_path_spec="$lt_tmp_lt_search_path_spec $lt_sys_path" fi done lt_search_path_spec=`$ECHO "$lt_tmp_lt_search_path_spec" | awk ' BEGIN {RS = " "; FS = "/|\n";} { lt_foo = ""; lt_count = 0; for (lt_i = NF; lt_i > 0; lt_i--) { if ($lt_i != "" && $lt_i != ".") { if ($lt_i == "..") { lt_count++; } else { if (lt_count == 0) { lt_foo = "/" $lt_i lt_foo; } else { lt_count--; } } } } if (lt_foo != "") { lt_freq[[lt_foo]]++; } if (lt_freq[[lt_foo]] == 1) { print lt_foo; } }'` # AWK program above erroneously prepends '/' to C:/dos/paths # for these hosts. case $host_os in mingw* | cegcc*) lt_search_path_spec=`$ECHO "$lt_search_path_spec" |\ $SED 's|/\([[A-Za-z]]:\)|\1|g'` ;; esac sys_lib_search_path_spec=`$ECHO "$lt_search_path_spec" | $lt_NL2SP` else sys_lib_search_path_spec="/lib /usr/lib /usr/local/lib" fi]) library_names_spec= libname_spec='lib$name' soname_spec= shrext_cmds=.so postinstall_cmds= postuninstall_cmds= finish_cmds= finish_eval= shlibpath_var= shlibpath_overrides_runpath=unknown version_type=none dynamic_linker="$host_os ld.so" sys_lib_dlsearch_path_spec="/lib /usr/lib" need_lib_prefix=unknown hardcode_into_libs=no # when you set need_version to no, make sure it does not cause -set_version # flags to be left without arguments need_version=unknown AC_ARG_VAR([LT_SYS_LIBRARY_PATH], [User-defined run-time library search path.]) case $host_os in aix3*) version_type=linux # correct to gnu/linux during the next big refactor library_names_spec='$libname$release$shared_ext$versuffix $libname.a' shlibpath_var=LIBPATH # AIX 3 has no versioning support, so we append a major version to the name. soname_spec='$libname$release$shared_ext$major' ;; aix[[4-9]]*) version_type=linux # correct to gnu/linux during the next big refactor need_lib_prefix=no need_version=no hardcode_into_libs=yes if test ia64 = "$host_cpu"; then # AIX 5 supports IA64 library_names_spec='$libname$release$shared_ext$major $libname$release$shared_ext$versuffix $libname$shared_ext' shlibpath_var=LD_LIBRARY_PATH else # With GCC up to 2.95.x, collect2 would create an import file # for dependence libraries. The import file would start with # the line '#! .'. This would cause the generated library to # depend on '.', always an invalid library. This was fixed in # development snapshots of GCC prior to 3.0. case $host_os in aix4 | aix4.[[01]] | aix4.[[01]].*) if { echo '#if __GNUC__ > 2 || (__GNUC__ == 2 && __GNUC_MINOR__ >= 97)' echo ' yes ' echo '#endif'; } | $CC -E - | $GREP yes > /dev/null; then : else can_build_shared=no fi ;; esac # Using Import Files as archive members, it is possible to support # filename-based versioning of shared library archives on AIX. While # this would work for both with and without runtime linking, it will # prevent static linking of such archives. So we do filename-based # shared library versioning with .so extension only, which is used # when both runtime linking and shared linking is enabled. # Unfortunately, runtime linking may impact performance, so we do # not want this to be the default eventually. Also, we use the # versioned .so libs for executables only if there is the -brtl # linker flag in LDFLAGS as well, or --with-aix-soname=svr4 only. # To allow for filename-based versioning support, we need to create # libNAME.so.V as an archive file, containing: # *) an Import File, referring to the versioned filename of the # archive as well as the shared archive member, telling the # bitwidth (32 or 64) of that shared object, and providing the # list of exported symbols of that shared object, eventually # decorated with the 'weak' keyword # *) the shared object with the F_LOADONLY flag set, to really avoid # it being seen by the linker. # At run time we better use the real file rather than another symlink, # but for link time we create the symlink libNAME.so -> libNAME.so.V case $with_aix_soname,$aix_use_runtimelinking in # AIX (on Power*) has no versioning support, so currently we cannot hardcode correct # soname into executable. Probably we can add versioning support to # collect2, so additional links can be useful in future. aix,yes) # traditional libtool dynamic_linker='AIX unversionable lib.so' # If using run time linking (on AIX 4.2 or later) use lib.so # instead of lib.a to let people know that these are not # typical AIX shared libraries. library_names_spec='$libname$release$shared_ext$versuffix $libname$release$shared_ext$major $libname$shared_ext' ;; aix,no) # traditional AIX only dynamic_linker='AIX lib.a[(]lib.so.V[)]' # We preserve .a as extension for shared libraries through AIX4.2 # and later when we are not doing run time linking. library_names_spec='$libname$release.a $libname.a' soname_spec='$libname$release$shared_ext$major' ;; svr4,*) # full svr4 only dynamic_linker="AIX lib.so.V[(]$shared_archive_member_spec.o[)]" library_names_spec='$libname$release$shared_ext$major $libname$shared_ext' # We do not specify a path in Import Files, so LIBPATH fires. shlibpath_overrides_runpath=yes ;; *,yes) # both, prefer svr4 dynamic_linker="AIX lib.so.V[(]$shared_archive_member_spec.o[)], lib.a[(]lib.so.V[)]" library_names_spec='$libname$release$shared_ext$major $libname$shared_ext' # unpreferred sharedlib libNAME.a needs extra handling postinstall_cmds='test -n "$linkname" || linkname="$realname"~func_stripname "" ".so" "$linkname"~$install_shared_prog "$dir/$func_stripname_result.$libext" "$destdir/$func_stripname_result.$libext"~test -z "$tstripme" || test -z "$striplib" || $striplib "$destdir/$func_stripname_result.$libext"' postuninstall_cmds='for n in $library_names $old_library; do :; done~func_stripname "" ".so" "$n"~test "$func_stripname_result" = "$n" || func_append rmfiles " $odir/$func_stripname_result.$libext"' # We do not specify a path in Import Files, so LIBPATH fires. shlibpath_overrides_runpath=yes ;; *,no) # both, prefer aix dynamic_linker="AIX lib.a[(]lib.so.V[)], lib.so.V[(]$shared_archive_member_spec.o[)]" library_names_spec='$libname$release.a $libname.a' soname_spec='$libname$release$shared_ext$major' # unpreferred sharedlib libNAME.so.V and symlink libNAME.so need extra handling postinstall_cmds='test -z "$dlname" || $install_shared_prog $dir/$dlname $destdir/$dlname~test -z "$tstripme" || test -z "$striplib" || $striplib $destdir/$dlname~test -n "$linkname" || linkname=$realname~func_stripname "" ".a" "$linkname"~(cd "$destdir" && $LN_S -f $dlname $func_stripname_result.so)' postuninstall_cmds='test -z "$dlname" || func_append rmfiles " $odir/$dlname"~for n in $old_library $library_names; do :; done~func_stripname "" ".a" "$n"~func_append rmfiles " $odir/$func_stripname_result.so"' ;; esac shlibpath_var=LIBPATH fi ;; amigaos*) case $host_cpu in powerpc) # Since July 2007 AmigaOS4 officially supports .so libraries. # When compiling the executable, add -use-dynld -Lsobjs: to the compileline. library_names_spec='$libname$release$shared_ext$versuffix $libname$release$shared_ext$major $libname$shared_ext' ;; m68k) library_names_spec='$libname.ixlibrary $libname.a' # Create ${libname}_ixlibrary.a entries in /sys/libs. finish_eval='for lib in `ls $libdir/*.ixlibrary 2>/dev/null`; do libname=`func_echo_all "$lib" | $SED '\''s%^.*/\([[^/]]*\)\.ixlibrary$%\1%'\''`; $RM /sys/libs/${libname}_ixlibrary.a; $show "cd /sys/libs && $LN_S $lib ${libname}_ixlibrary.a"; cd /sys/libs && $LN_S $lib ${libname}_ixlibrary.a || exit 1; done' ;; esac ;; beos*) library_names_spec='$libname$shared_ext' dynamic_linker="$host_os ld.so" shlibpath_var=LIBRARY_PATH ;; bsdi[[45]]*) version_type=linux # correct to gnu/linux during the next big refactor need_version=no library_names_spec='$libname$release$shared_ext$versuffix $libname$release$shared_ext$major $libname$shared_ext' soname_spec='$libname$release$shared_ext$major' finish_cmds='PATH="\$PATH:/sbin" ldconfig $libdir' shlibpath_var=LD_LIBRARY_PATH sys_lib_search_path_spec="/shlib /usr/lib /usr/X11/lib /usr/contrib/lib /lib /usr/local/lib" sys_lib_dlsearch_path_spec="/shlib /usr/lib /usr/local/lib" # the default ld.so.conf also contains /usr/contrib/lib and # /usr/X11R6/lib (/usr/X11 is a link to /usr/X11R6), but let us allow # libtool to hard-code these into programs ;; cygwin* | mingw* | pw32* | cegcc*) version_type=windows shrext_cmds=.dll need_version=no need_lib_prefix=no case $GCC,$cc_basename in yes,*) # gcc library_names_spec='$libname.dll.a' # DLL is installed to $(libdir)/../bin by postinstall_cmds postinstall_cmds='base_file=`basename \$file`~ dlpath=`$SHELL 2>&1 -c '\''. $dir/'\''\$base_file'\''i; echo \$dlname'\''`~ dldir=$destdir/`dirname \$dlpath`~ test -d \$dldir || mkdir -p \$dldir~ $install_prog $dir/$dlname \$dldir/$dlname~ chmod a+x \$dldir/$dlname~ if test -n '\''$stripme'\'' && test -n '\''$striplib'\''; then eval '\''$striplib \$dldir/$dlname'\'' || exit \$?; fi' postuninstall_cmds='dldll=`$SHELL 2>&1 -c '\''. $file; echo \$dlname'\''`~ dlpath=$dir/\$dldll~ $RM \$dlpath' shlibpath_overrides_runpath=yes case $host_os in cygwin*) # Cygwin DLLs use 'cyg' prefix rather than 'lib' soname_spec='`echo $libname | sed -e 's/^lib/cyg/'``echo $release | $SED -e 's/[[.]]/-/g'`$versuffix$shared_ext' m4_if([$1], [],[ sys_lib_search_path_spec="$sys_lib_search_path_spec /usr/lib/w32api"]) ;; mingw* | cegcc*) # MinGW DLLs use traditional 'lib' prefix soname_spec='$libname`echo $release | $SED -e 's/[[.]]/-/g'`$versuffix$shared_ext' ;; pw32*) # pw32 DLLs use 'pw' prefix rather than 'lib' library_names_spec='`echo $libname | sed -e 's/^lib/pw/'``echo $release | $SED -e 's/[[.]]/-/g'`$versuffix$shared_ext' ;; esac dynamic_linker='Win32 ld.exe' ;; *,cl*) # Native MSVC libname_spec='$name' soname_spec='$libname`echo $release | $SED -e 's/[[.]]/-/g'`$versuffix$shared_ext' library_names_spec='$libname.dll.lib' case $build_os in mingw*) sys_lib_search_path_spec= lt_save_ifs=$IFS IFS=';' for lt_path in $LIB do IFS=$lt_save_ifs # Let DOS variable expansion print the short 8.3 style file name. lt_path=`cd "$lt_path" 2>/dev/null && cmd //C "for %i in (".") do @echo %~si"` sys_lib_search_path_spec="$sys_lib_search_path_spec $lt_path" done IFS=$lt_save_ifs # Convert to MSYS style. sys_lib_search_path_spec=`$ECHO "$sys_lib_search_path_spec" | sed -e 's|\\\\|/|g' -e 's| \\([[a-zA-Z]]\\):| /\\1|g' -e 's|^ ||'` ;; cygwin*) # Convert to unix form, then to dos form, then back to unix form # but this time dos style (no spaces!) so that the unix form looks # like /cygdrive/c/PROGRA~1:/cygdr... sys_lib_search_path_spec=`cygpath --path --unix "$LIB"` sys_lib_search_path_spec=`cygpath --path --dos "$sys_lib_search_path_spec" 2>/dev/null` sys_lib_search_path_spec=`cygpath --path --unix "$sys_lib_search_path_spec" | $SED -e "s/$PATH_SEPARATOR/ /g"` ;; *) sys_lib_search_path_spec=$LIB if $ECHO "$sys_lib_search_path_spec" | [$GREP ';[c-zC-Z]:/' >/dev/null]; then # It is most probably a Windows format PATH. sys_lib_search_path_spec=`$ECHO "$sys_lib_search_path_spec" | $SED -e 's/;/ /g'` else sys_lib_search_path_spec=`$ECHO "$sys_lib_search_path_spec" | $SED -e "s/$PATH_SEPARATOR/ /g"` fi # FIXME: find the short name or the path components, as spaces are # common. (e.g. "Program Files" -> "PROGRA~1") ;; esac # DLL is installed to $(libdir)/../bin by postinstall_cmds postinstall_cmds='base_file=`basename \$file`~ dlpath=`$SHELL 2>&1 -c '\''. $dir/'\''\$base_file'\''i; echo \$dlname'\''`~ dldir=$destdir/`dirname \$dlpath`~ test -d \$dldir || mkdir -p \$dldir~ $install_prog $dir/$dlname \$dldir/$dlname' postuninstall_cmds='dldll=`$SHELL 2>&1 -c '\''. $file; echo \$dlname'\''`~ dlpath=$dir/\$dldll~ $RM \$dlpath' shlibpath_overrides_runpath=yes dynamic_linker='Win32 link.exe' ;; *) # Assume MSVC wrapper library_names_spec='$libname`echo $release | $SED -e 's/[[.]]/-/g'`$versuffix$shared_ext $libname.lib' dynamic_linker='Win32 ld.exe' ;; esac # FIXME: first we should search . and the directory the executable is in shlibpath_var=PATH ;; darwin* | rhapsody*) dynamic_linker="$host_os dyld" version_type=darwin need_lib_prefix=no need_version=no library_names_spec='$libname$release$major$shared_ext $libname$shared_ext' soname_spec='$libname$release$major$shared_ext' shlibpath_overrides_runpath=yes shlibpath_var=DYLD_LIBRARY_PATH shrext_cmds='`test .$module = .yes && echo .so || echo .dylib`' m4_if([$1], [],[ sys_lib_search_path_spec="$sys_lib_search_path_spec /usr/local/lib"]) sys_lib_dlsearch_path_spec='/usr/local/lib /lib /usr/lib' ;; dgux*) version_type=linux # correct to gnu/linux during the next big refactor need_lib_prefix=no need_version=no library_names_spec='$libname$release$shared_ext$versuffix $libname$release$shared_ext$major $libname$shared_ext' soname_spec='$libname$release$shared_ext$major' shlibpath_var=LD_LIBRARY_PATH ;; freebsd* | dragonfly*) # DragonFly does not have aout. When/if they implement a new # versioning mechanism, adjust this. if test -x /usr/bin/objformat; then objformat=`/usr/bin/objformat` else case $host_os in freebsd[[23]].*) objformat=aout ;; *) objformat=elf ;; esac fi version_type=freebsd-$objformat case $version_type in freebsd-elf*) library_names_spec='$libname$release$shared_ext$versuffix $libname$release$shared_ext$major $libname$shared_ext' soname_spec='$libname$release$shared_ext$major' need_version=no need_lib_prefix=no ;; freebsd-*) library_names_spec='$libname$release$shared_ext$versuffix $libname$shared_ext$versuffix' need_version=yes ;; esac shlibpath_var=LD_LIBRARY_PATH case $host_os in freebsd2.*) shlibpath_overrides_runpath=yes ;; freebsd3.[[01]]* | freebsdelf3.[[01]]*) shlibpath_overrides_runpath=yes hardcode_into_libs=yes ;; freebsd3.[[2-9]]* | freebsdelf3.[[2-9]]* | \ freebsd4.[[0-5]] | freebsdelf4.[[0-5]] | freebsd4.1.1 | freebsdelf4.1.1) shlibpath_overrides_runpath=no hardcode_into_libs=yes ;; *) # from 4.6 on, and DragonFly shlibpath_overrides_runpath=yes hardcode_into_libs=yes ;; esac ;; haiku*) version_type=linux # correct to gnu/linux during the next big refactor need_lib_prefix=no need_version=no dynamic_linker="$host_os runtime_loader" library_names_spec='$libname$release$shared_ext$versuffix $libname$release$shared_ext$major $libname$shared_ext' soname_spec='$libname$release$shared_ext$major' shlibpath_var=LIBRARY_PATH shlibpath_overrides_runpath=no sys_lib_dlsearch_path_spec='/boot/home/config/lib /boot/common/lib /boot/system/lib' hardcode_into_libs=yes ;; hpux9* | hpux10* | hpux11*) # Give a soname corresponding to the major version so that dld.sl refuses to # link against other versions. version_type=sunos need_lib_prefix=no need_version=no case $host_cpu in ia64*) shrext_cmds='.so' hardcode_into_libs=yes dynamic_linker="$host_os dld.so" shlibpath_var=LD_LIBRARY_PATH shlibpath_overrides_runpath=yes # Unless +noenvvar is specified. library_names_spec='$libname$release$shared_ext$versuffix $libname$release$shared_ext$major $libname$shared_ext' soname_spec='$libname$release$shared_ext$major' if test 32 = "$HPUX_IA64_MODE"; then sys_lib_search_path_spec="/usr/lib/hpux32 /usr/local/lib/hpux32 /usr/local/lib" sys_lib_dlsearch_path_spec=/usr/lib/hpux32 else sys_lib_search_path_spec="/usr/lib/hpux64 /usr/local/lib/hpux64" sys_lib_dlsearch_path_spec=/usr/lib/hpux64 fi ;; hppa*64*) shrext_cmds='.sl' hardcode_into_libs=yes dynamic_linker="$host_os dld.sl" shlibpath_var=LD_LIBRARY_PATH # How should we handle SHLIB_PATH shlibpath_overrides_runpath=yes # Unless +noenvvar is specified. library_names_spec='$libname$release$shared_ext$versuffix $libname$release$shared_ext$major $libname$shared_ext' soname_spec='$libname$release$shared_ext$major' sys_lib_search_path_spec="/usr/lib/pa20_64 /usr/ccs/lib/pa20_64" sys_lib_dlsearch_path_spec=$sys_lib_search_path_spec ;; *) shrext_cmds='.sl' dynamic_linker="$host_os dld.sl" shlibpath_var=SHLIB_PATH shlibpath_overrides_runpath=no # +s is required to enable SHLIB_PATH library_names_spec='$libname$release$shared_ext$versuffix $libname$release$shared_ext$major $libname$shared_ext' soname_spec='$libname$release$shared_ext$major' ;; esac # HP-UX runs *really* slowly unless shared libraries are mode 555, ... postinstall_cmds='chmod 555 $lib' # or fails outright, so override atomically: install_override_mode=555 ;; interix[[3-9]]*) version_type=linux # correct to gnu/linux during the next big refactor need_lib_prefix=no need_version=no library_names_spec='$libname$release$shared_ext$versuffix $libname$release$shared_ext$major $libname$shared_ext' soname_spec='$libname$release$shared_ext$major' dynamic_linker='Interix 3.x ld.so.1 (PE, like ELF)' shlibpath_var=LD_LIBRARY_PATH shlibpath_overrides_runpath=no hardcode_into_libs=yes ;; irix5* | irix6* | nonstopux*) case $host_os in nonstopux*) version_type=nonstopux ;; *) if test yes = "$lt_cv_prog_gnu_ld"; then version_type=linux # correct to gnu/linux during the next big refactor else version_type=irix fi ;; esac need_lib_prefix=no need_version=no soname_spec='$libname$release$shared_ext$major' library_names_spec='$libname$release$shared_ext$versuffix $libname$release$shared_ext$major $libname$release$shared_ext $libname$shared_ext' case $host_os in irix5* | nonstopux*) libsuff= shlibsuff= ;; *) case $LD in # libtool.m4 will add one of these switches to LD *-32|*"-32 "|*-melf32bsmip|*"-melf32bsmip ") libsuff= shlibsuff= libmagic=32-bit;; *-n32|*"-n32 "|*-melf32bmipn32|*"-melf32bmipn32 ") libsuff=32 shlibsuff=N32 libmagic=N32;; *-64|*"-64 "|*-melf64bmip|*"-melf64bmip ") libsuff=64 shlibsuff=64 libmagic=64-bit;; *) libsuff= shlibsuff= libmagic=never-match;; esac ;; esac shlibpath_var=LD_LIBRARY${shlibsuff}_PATH shlibpath_overrides_runpath=no sys_lib_search_path_spec="/usr/lib$libsuff /lib$libsuff /usr/local/lib$libsuff" sys_lib_dlsearch_path_spec="/usr/lib$libsuff /lib$libsuff" hardcode_into_libs=yes ;; # No shared lib support for Linux oldld, aout, or coff. linux*oldld* | linux*aout* | linux*coff*) dynamic_linker=no ;; linux*android*) version_type=none # Android doesn't support versioned libraries. need_lib_prefix=no need_version=no library_names_spec='$libname$release$shared_ext' soname_spec='$libname$release$shared_ext' finish_cmds= shlibpath_var=LD_LIBRARY_PATH shlibpath_overrides_runpath=yes # This implies no fast_install, which is unacceptable. # Some rework will be needed to allow for fast_install # before this can be enabled. hardcode_into_libs=yes dynamic_linker='Android linker' # Don't embed -rpath directories since the linker doesn't support them. _LT_TAGVAR(hardcode_libdir_flag_spec, $1)='-L$libdir' ;; # This must be glibc/ELF. linux* | k*bsd*-gnu | kopensolaris*-gnu | gnu*) version_type=linux # correct to gnu/linux during the next big refactor need_lib_prefix=no need_version=no library_names_spec='$libname$release$shared_ext$versuffix $libname$release$shared_ext$major $libname$shared_ext' soname_spec='$libname$release$shared_ext$major' finish_cmds='PATH="\$PATH:/sbin" ldconfig -n $libdir' shlibpath_var=LD_LIBRARY_PATH shlibpath_overrides_runpath=no # Some binutils ld are patched to set DT_RUNPATH AC_CACHE_VAL([lt_cv_shlibpath_overrides_runpath], [lt_cv_shlibpath_overrides_runpath=no save_LDFLAGS=$LDFLAGS save_libdir=$libdir eval "libdir=/foo; wl=\"$_LT_TAGVAR(lt_prog_compiler_wl, $1)\"; \ LDFLAGS=\"\$LDFLAGS $_LT_TAGVAR(hardcode_libdir_flag_spec, $1)\"" AC_LINK_IFELSE([AC_LANG_PROGRAM([],[])], [AS_IF([ ($OBJDUMP -p conftest$ac_exeext) 2>/dev/null | grep "RUNPATH.*$libdir" >/dev/null], [lt_cv_shlibpath_overrides_runpath=yes])]) LDFLAGS=$save_LDFLAGS libdir=$save_libdir ]) shlibpath_overrides_runpath=$lt_cv_shlibpath_overrides_runpath # This implies no fast_install, which is unacceptable. # Some rework will be needed to allow for fast_install # before this can be enabled. hardcode_into_libs=yes # Ideally, we could use ldconfig to report *all* directores which are # searched for libraries, however this is still not possible. Aside from not # being certain /sbin/ldconfig is available, command # 'ldconfig -N -X -v | grep ^/' on 64bit Fedora does not report /usr/lib64, # even though it is searched at run-time. Try to do the best guess by # appending ld.so.conf contents (and includes) to the search path. if test -f /etc/ld.so.conf; then lt_ld_extra=`awk '/^include / { system(sprintf("cd /etc; cat %s 2>/dev/null", \[$]2)); skip = 1; } { if (!skip) print \[$]0; skip = 0; }' < /etc/ld.so.conf | $SED -e 's/#.*//;/^[ ]*hwcap[ ]/d;s/[:, ]/ /g;s/=[^=]*$//;s/=[^= ]* / /g;s/"//g;/^$/d' | tr '\n' ' '` sys_lib_dlsearch_path_spec="/lib /usr/lib $lt_ld_extra" fi # We used to test for /lib/ld.so.1 and disable shared libraries on # powerpc, because MkLinux only supported shared libraries with the # GNU dynamic linker. Since this was broken with cross compilers, # most powerpc-linux boxes support dynamic linking these days and # people can always --disable-shared, the test was removed, and we # assume the GNU/Linux dynamic linker is in use. dynamic_linker='GNU/Linux ld.so' ;; netbsdelf*-gnu) version_type=linux need_lib_prefix=no need_version=no library_names_spec='${libname}${release}${shared_ext}$versuffix ${libname}${release}${shared_ext}$major ${libname}${shared_ext}' soname_spec='${libname}${release}${shared_ext}$major' shlibpath_var=LD_LIBRARY_PATH shlibpath_overrides_runpath=no hardcode_into_libs=yes dynamic_linker='NetBSD ld.elf_so' ;; netbsd*) version_type=sunos need_lib_prefix=no need_version=no if echo __ELF__ | $CC -E - | $GREP __ELF__ >/dev/null; then library_names_spec='$libname$release$shared_ext$versuffix $libname$shared_ext$versuffix' finish_cmds='PATH="\$PATH:/sbin" ldconfig -m $libdir' dynamic_linker='NetBSD (a.out) ld.so' else library_names_spec='$libname$release$shared_ext$versuffix $libname$release$shared_ext$major $libname$shared_ext' soname_spec='$libname$release$shared_ext$major' dynamic_linker='NetBSD ld.elf_so' fi shlibpath_var=LD_LIBRARY_PATH shlibpath_overrides_runpath=yes hardcode_into_libs=yes ;; newsos6) version_type=linux # correct to gnu/linux during the next big refactor library_names_spec='$libname$release$shared_ext$versuffix $libname$release$shared_ext$major $libname$shared_ext' shlibpath_var=LD_LIBRARY_PATH shlibpath_overrides_runpath=yes ;; *nto* | *qnx*) version_type=qnx need_lib_prefix=no need_version=no library_names_spec='$libname$release$shared_ext$versuffix $libname$release$shared_ext$major $libname$shared_ext' soname_spec='$libname$release$shared_ext$major' shlibpath_var=LD_LIBRARY_PATH shlibpath_overrides_runpath=no hardcode_into_libs=yes dynamic_linker='ldqnx.so' ;; openbsd* | bitrig*) version_type=sunos sys_lib_dlsearch_path_spec=/usr/lib need_lib_prefix=no if test -z "`echo __ELF__ | $CC -E - | $GREP __ELF__`"; then need_version=no else need_version=yes fi library_names_spec='$libname$release$shared_ext$versuffix $libname$shared_ext$versuffix' finish_cmds='PATH="\$PATH:/sbin" ldconfig -m $libdir' shlibpath_var=LD_LIBRARY_PATH shlibpath_overrides_runpath=yes ;; os2*) libname_spec='$name' version_type=windows shrext_cmds=.dll need_version=no need_lib_prefix=no # OS/2 can only load a DLL with a base name of 8 characters or less. soname_spec='`test -n "$os2dllname" && libname="$os2dllname"; v=$($ECHO $release$versuffix | tr -d .-); n=$($ECHO $libname | cut -b -$((8 - ${#v})) | tr . _); $ECHO $n$v`$shared_ext' library_names_spec='${libname}_dll.$libext' dynamic_linker='OS/2 ld.exe' shlibpath_var=BEGINLIBPATH sys_lib_search_path_spec="/lib /usr/lib /usr/local/lib" sys_lib_dlsearch_path_spec=$sys_lib_search_path_spec postinstall_cmds='base_file=`basename \$file`~ dlpath=`$SHELL 2>&1 -c '\''. $dir/'\''\$base_file'\''i; $ECHO \$dlname'\''`~ dldir=$destdir/`dirname \$dlpath`~ test -d \$dldir || mkdir -p \$dldir~ $install_prog $dir/$dlname \$dldir/$dlname~ chmod a+x \$dldir/$dlname~ if test -n '\''$stripme'\'' && test -n '\''$striplib'\''; then eval '\''$striplib \$dldir/$dlname'\'' || exit \$?; fi' postuninstall_cmds='dldll=`$SHELL 2>&1 -c '\''. $file; $ECHO \$dlname'\''`~ dlpath=$dir/\$dldll~ $RM \$dlpath' ;; osf3* | osf4* | osf5*) version_type=osf need_lib_prefix=no need_version=no soname_spec='$libname$release$shared_ext$major' library_names_spec='$libname$release$shared_ext$versuffix $libname$release$shared_ext$major $libname$shared_ext' shlibpath_var=LD_LIBRARY_PATH sys_lib_search_path_spec="/usr/shlib /usr/ccs/lib /usr/lib/cmplrs/cc /usr/lib /usr/local/lib /var/shlib" sys_lib_dlsearch_path_spec=$sys_lib_search_path_spec ;; rdos*) dynamic_linker=no ;; solaris*) version_type=linux # correct to gnu/linux during the next big refactor need_lib_prefix=no need_version=no library_names_spec='$libname$release$shared_ext$versuffix $libname$release$shared_ext$major $libname$shared_ext' soname_spec='$libname$release$shared_ext$major' shlibpath_var=LD_LIBRARY_PATH shlibpath_overrides_runpath=yes hardcode_into_libs=yes # ldd complains unless libraries are executable postinstall_cmds='chmod +x $lib' ;; sunos4*) version_type=sunos library_names_spec='$libname$release$shared_ext$versuffix $libname$shared_ext$versuffix' finish_cmds='PATH="\$PATH:/usr/etc" ldconfig $libdir' shlibpath_var=LD_LIBRARY_PATH shlibpath_overrides_runpath=yes if test yes = "$with_gnu_ld"; then need_lib_prefix=no fi need_version=yes ;; sysv4 | sysv4.3*) version_type=linux # correct to gnu/linux during the next big refactor library_names_spec='$libname$release$shared_ext$versuffix $libname$release$shared_ext$major $libname$shared_ext' soname_spec='$libname$release$shared_ext$major' shlibpath_var=LD_LIBRARY_PATH case $host_vendor in sni) shlibpath_overrides_runpath=no need_lib_prefix=no runpath_var=LD_RUN_PATH ;; siemens) need_lib_prefix=no ;; motorola) need_lib_prefix=no need_version=no shlibpath_overrides_runpath=no sys_lib_search_path_spec='/lib /usr/lib /usr/ccs/lib' ;; esac ;; sysv4*MP*) if test -d /usr/nec; then version_type=linux # correct to gnu/linux during the next big refactor library_names_spec='$libname$shared_ext.$versuffix $libname$shared_ext.$major $libname$shared_ext' soname_spec='$libname$shared_ext.$major' shlibpath_var=LD_LIBRARY_PATH fi ;; sysv5* | sco3.2v5* | sco5v6* | unixware* | OpenUNIX* | sysv4*uw2*) version_type=sco need_lib_prefix=no need_version=no library_names_spec='$libname$release$shared_ext$versuffix $libname$release$shared_ext $libname$shared_ext' soname_spec='$libname$release$shared_ext$major' shlibpath_var=LD_LIBRARY_PATH shlibpath_overrides_runpath=yes hardcode_into_libs=yes if test yes = "$with_gnu_ld"; then sys_lib_search_path_spec='/usr/local/lib /usr/gnu/lib /usr/ccs/lib /usr/lib /lib' else sys_lib_search_path_spec='/usr/ccs/lib /usr/lib' case $host_os in sco3.2v5*) sys_lib_search_path_spec="$sys_lib_search_path_spec /lib" ;; esac fi sys_lib_dlsearch_path_spec='/usr/lib' ;; tpf*) # TPF is a cross-target only. Preferred cross-host = GNU/Linux. version_type=linux # correct to gnu/linux during the next big refactor need_lib_prefix=no need_version=no library_names_spec='$libname$release$shared_ext$versuffix $libname$release$shared_ext$major $libname$shared_ext' shlibpath_var=LD_LIBRARY_PATH shlibpath_overrides_runpath=no hardcode_into_libs=yes ;; uts4*) version_type=linux # correct to gnu/linux during the next big refactor library_names_spec='$libname$release$shared_ext$versuffix $libname$release$shared_ext$major $libname$shared_ext' soname_spec='$libname$release$shared_ext$major' shlibpath_var=LD_LIBRARY_PATH ;; *) dynamic_linker=no ;; esac AC_MSG_RESULT([$dynamic_linker]) test no = "$dynamic_linker" && can_build_shared=no variables_saved_for_relink="PATH $shlibpath_var $runpath_var" if test yes = "$GCC"; then variables_saved_for_relink="$variables_saved_for_relink GCC_EXEC_PREFIX COMPILER_PATH LIBRARY_PATH" fi if test set = "${lt_cv_sys_lib_search_path_spec+set}"; then sys_lib_search_path_spec=$lt_cv_sys_lib_search_path_spec fi if test set = "${lt_cv_sys_lib_dlsearch_path_spec+set}"; then sys_lib_dlsearch_path_spec=$lt_cv_sys_lib_dlsearch_path_spec fi # remember unaugmented sys_lib_dlsearch_path content for libtool script decls... configure_time_dlsearch_path=$sys_lib_dlsearch_path_spec # ... but it needs LT_SYS_LIBRARY_PATH munging for other configure-time code func_munge_path_list sys_lib_dlsearch_path_spec "$LT_SYS_LIBRARY_PATH" # to be used as default LT_SYS_LIBRARY_PATH value in generated libtool configure_time_lt_sys_library_path=$LT_SYS_LIBRARY_PATH _LT_DECL([], [variables_saved_for_relink], [1], [Variables whose values should be saved in libtool wrapper scripts and restored at link time]) _LT_DECL([], [need_lib_prefix], [0], [Do we need the "lib" prefix for modules?]) _LT_DECL([], [need_version], [0], [Do we need a version for libraries?]) _LT_DECL([], [version_type], [0], [Library versioning type]) _LT_DECL([], [runpath_var], [0], [Shared library runtime path variable]) _LT_DECL([], [shlibpath_var], [0],[Shared library path variable]) _LT_DECL([], [shlibpath_overrides_runpath], [0], [Is shlibpath searched before the hard-coded library search path?]) _LT_DECL([], [libname_spec], [1], [Format of library name prefix]) _LT_DECL([], [library_names_spec], [1], [[List of archive names. First name is the real one, the rest are links. The last name is the one that the linker finds with -lNAME]]) _LT_DECL([], [soname_spec], [1], [[The coded name of the library, if different from the real name]]) _LT_DECL([], [install_override_mode], [1], [Permission mode override for installation of shared libraries]) _LT_DECL([], [postinstall_cmds], [2], [Command to use after installation of a shared archive]) _LT_DECL([], [postuninstall_cmds], [2], [Command to use after uninstallation of a shared archive]) _LT_DECL([], [finish_cmds], [2], [Commands used to finish a libtool library installation in a directory]) _LT_DECL([], [finish_eval], [1], [[As "finish_cmds", except a single script fragment to be evaled but not shown]]) _LT_DECL([], [hardcode_into_libs], [0], [Whether we should hardcode library paths into libraries]) _LT_DECL([], [sys_lib_search_path_spec], [2], [Compile-time system search path for libraries]) _LT_DECL([sys_lib_dlsearch_path_spec], [configure_time_dlsearch_path], [2], [Detected run-time system search path for libraries]) _LT_DECL([], [configure_time_lt_sys_library_path], [2], [Explicit LT_SYS_LIBRARY_PATH set during ./configure time]) ])# _LT_SYS_DYNAMIC_LINKER # _LT_PATH_TOOL_PREFIX(TOOL) # -------------------------- # find a file program that can recognize shared library AC_DEFUN([_LT_PATH_TOOL_PREFIX], [m4_require([_LT_DECL_EGREP])dnl AC_MSG_CHECKING([for $1]) AC_CACHE_VAL(lt_cv_path_MAGIC_CMD, [case $MAGIC_CMD in [[\\/*] | ?:[\\/]*]) lt_cv_path_MAGIC_CMD=$MAGIC_CMD # Let the user override the test with a path. ;; *) lt_save_MAGIC_CMD=$MAGIC_CMD lt_save_ifs=$IFS; IFS=$PATH_SEPARATOR dnl $ac_dummy forces splitting on constant user-supplied paths. dnl POSIX.2 word splitting is done only on the output of word expansions, dnl not every word. This closes a longstanding sh security hole. ac_dummy="m4_if([$2], , $PATH, [$2])" for ac_dir in $ac_dummy; do IFS=$lt_save_ifs test -z "$ac_dir" && ac_dir=. if test -f "$ac_dir/$1"; then lt_cv_path_MAGIC_CMD=$ac_dir/"$1" if test -n "$file_magic_test_file"; then case $deplibs_check_method in "file_magic "*) file_magic_regex=`expr "$deplibs_check_method" : "file_magic \(.*\)"` MAGIC_CMD=$lt_cv_path_MAGIC_CMD if eval $file_magic_cmd \$file_magic_test_file 2> /dev/null | $EGREP "$file_magic_regex" > /dev/null; then : else cat <<_LT_EOF 1>&2 *** Warning: the command libtool uses to detect shared libraries, *** $file_magic_cmd, produces output that libtool cannot recognize. *** The result is that libtool may fail to recognize shared libraries *** as such. This will affect the creation of libtool libraries that *** depend on shared libraries, but programs linked with such libtool *** libraries will work regardless of this problem. Nevertheless, you *** may want to report the problem to your system manager and/or to *** bug-libtool@gnu.org _LT_EOF fi ;; esac fi break fi done IFS=$lt_save_ifs MAGIC_CMD=$lt_save_MAGIC_CMD ;; esac]) MAGIC_CMD=$lt_cv_path_MAGIC_CMD if test -n "$MAGIC_CMD"; then AC_MSG_RESULT($MAGIC_CMD) else AC_MSG_RESULT(no) fi _LT_DECL([], [MAGIC_CMD], [0], [Used to examine libraries when file_magic_cmd begins with "file"])dnl ])# _LT_PATH_TOOL_PREFIX # Old name: AU_ALIAS([AC_PATH_TOOL_PREFIX], [_LT_PATH_TOOL_PREFIX]) dnl aclocal-1.4 backwards compatibility: dnl AC_DEFUN([AC_PATH_TOOL_PREFIX], []) # _LT_PATH_MAGIC # -------------- # find a file program that can recognize a shared library m4_defun([_LT_PATH_MAGIC], [_LT_PATH_TOOL_PREFIX(${ac_tool_prefix}file, /usr/bin$PATH_SEPARATOR$PATH) if test -z "$lt_cv_path_MAGIC_CMD"; then if test -n "$ac_tool_prefix"; then _LT_PATH_TOOL_PREFIX(file, /usr/bin$PATH_SEPARATOR$PATH) else MAGIC_CMD=: fi fi ])# _LT_PATH_MAGIC # LT_PATH_LD # ---------- # find the pathname to the GNU or non-GNU linker AC_DEFUN([LT_PATH_LD], [AC_REQUIRE([AC_PROG_CC])dnl AC_REQUIRE([AC_CANONICAL_HOST])dnl AC_REQUIRE([AC_CANONICAL_BUILD])dnl m4_require([_LT_DECL_SED])dnl m4_require([_LT_DECL_EGREP])dnl m4_require([_LT_PROG_ECHO_BACKSLASH])dnl AC_ARG_WITH([gnu-ld], [AS_HELP_STRING([--with-gnu-ld], [assume the C compiler uses GNU ld @<:@default=no@:>@])], [test no = "$withval" || with_gnu_ld=yes], [with_gnu_ld=no])dnl ac_prog=ld if test yes = "$GCC"; then # Check if gcc -print-prog-name=ld gives a path. AC_MSG_CHECKING([for ld used by $CC]) case $host in *-*-mingw*) # gcc leaves a trailing carriage return, which upsets mingw ac_prog=`($CC -print-prog-name=ld) 2>&5 | tr -d '\015'` ;; *) ac_prog=`($CC -print-prog-name=ld) 2>&5` ;; esac case $ac_prog in # Accept absolute paths. [[\\/]]* | ?:[[\\/]]*) re_direlt='/[[^/]][[^/]]*/\.\./' # Canonicalize the pathname of ld ac_prog=`$ECHO "$ac_prog"| $SED 's%\\\\%/%g'` while $ECHO "$ac_prog" | $GREP "$re_direlt" > /dev/null 2>&1; do ac_prog=`$ECHO $ac_prog| $SED "s%$re_direlt%/%"` done test -z "$LD" && LD=$ac_prog ;; "") # If it fails, then pretend we aren't using GCC. ac_prog=ld ;; *) # If it is relative, then search for the first ld in PATH. with_gnu_ld=unknown ;; esac elif test yes = "$with_gnu_ld"; then AC_MSG_CHECKING([for GNU ld]) else AC_MSG_CHECKING([for non-GNU ld]) fi AC_CACHE_VAL(lt_cv_path_LD, [if test -z "$LD"; then lt_save_ifs=$IFS; IFS=$PATH_SEPARATOR for ac_dir in $PATH; do IFS=$lt_save_ifs test -z "$ac_dir" && ac_dir=. if test -f "$ac_dir/$ac_prog" || test -f "$ac_dir/$ac_prog$ac_exeext"; then lt_cv_path_LD=$ac_dir/$ac_prog # Check to see if the program is GNU ld. I'd rather use --version, # but apparently some variants of GNU ld only accept -v. # Break only if it was the GNU/non-GNU ld that we prefer. case `"$lt_cv_path_LD" -v 2>&1 &1 conftest.i cat conftest.i conftest.i >conftest2.i : ${lt_DD:=$DD} AC_PATH_PROGS_FEATURE_CHECK([lt_DD], [dd], [if "$ac_path_lt_DD" bs=32 count=1 conftest.out 2>/dev/null; then cmp -s conftest.i conftest.out \ && ac_cv_path_lt_DD="$ac_path_lt_DD" ac_path_lt_DD_found=: fi]) rm -f conftest.i conftest2.i conftest.out]) ])# _LT_PATH_DD # _LT_CMD_TRUNCATE # ---------------- # find command to truncate a binary pipe m4_defun([_LT_CMD_TRUNCATE], [m4_require([_LT_PATH_DD]) AC_CACHE_CHECK([how to truncate binary pipes], [lt_cv_truncate_bin], [printf 0123456789abcdef0123456789abcdef >conftest.i cat conftest.i conftest.i >conftest2.i lt_cv_truncate_bin= if "$ac_cv_path_lt_DD" bs=32 count=1 conftest.out 2>/dev/null; then cmp -s conftest.i conftest.out \ && lt_cv_truncate_bin="$ac_cv_path_lt_DD bs=4096 count=1" fi rm -f conftest.i conftest2.i conftest.out test -z "$lt_cv_truncate_bin" && lt_cv_truncate_bin="$SED -e 4q"]) _LT_DECL([lt_truncate_bin], [lt_cv_truncate_bin], [1], [Command to truncate a binary pipe]) ])# _LT_CMD_TRUNCATE # _LT_CHECK_MAGIC_METHOD # ---------------------- # how to check for library dependencies # -- PORTME fill in with the dynamic library characteristics m4_defun([_LT_CHECK_MAGIC_METHOD], [m4_require([_LT_DECL_EGREP]) m4_require([_LT_DECL_OBJDUMP]) AC_CACHE_CHECK([how to recognize dependent libraries], lt_cv_deplibs_check_method, [lt_cv_file_magic_cmd='$MAGIC_CMD' lt_cv_file_magic_test_file= lt_cv_deplibs_check_method='unknown' # Need to set the preceding variable on all platforms that support # interlibrary dependencies. # 'none' -- dependencies not supported. # 'unknown' -- same as none, but documents that we really don't know. # 'pass_all' -- all dependencies passed with no checks. # 'test_compile' -- check by making test program. # 'file_magic [[regex]]' -- check by looking for files in library path # that responds to the $file_magic_cmd with a given extended regex. # If you have 'file' or equivalent on your system and you're not sure # whether 'pass_all' will *always* work, you probably want this one. case $host_os in aix[[4-9]]*) lt_cv_deplibs_check_method=pass_all ;; beos*) lt_cv_deplibs_check_method=pass_all ;; bsdi[[45]]*) lt_cv_deplibs_check_method='file_magic ELF [[0-9]][[0-9]]*-bit [[ML]]SB (shared object|dynamic lib)' lt_cv_file_magic_cmd='/usr/bin/file -L' lt_cv_file_magic_test_file=/shlib/libc.so ;; cygwin*) # func_win32_libid is a shell function defined in ltmain.sh lt_cv_deplibs_check_method='file_magic ^x86 archive import|^x86 DLL' lt_cv_file_magic_cmd='func_win32_libid' ;; mingw* | pw32*) # Base MSYS/MinGW do not provide the 'file' command needed by # func_win32_libid shell function, so use a weaker test based on 'objdump', # unless we find 'file', for example because we are cross-compiling. if ( file / ) >/dev/null 2>&1; then lt_cv_deplibs_check_method='file_magic ^x86 archive import|^x86 DLL' lt_cv_file_magic_cmd='func_win32_libid' else # Keep this pattern in sync with the one in func_win32_libid. lt_cv_deplibs_check_method='file_magic file format (pei*-i386(.*architecture: i386)?|pe-arm-wince|pe-x86-64)' lt_cv_file_magic_cmd='$OBJDUMP -f' fi ;; cegcc*) # use the weaker test based on 'objdump'. See mingw*. lt_cv_deplibs_check_method='file_magic file format pe-arm-.*little(.*architecture: arm)?' lt_cv_file_magic_cmd='$OBJDUMP -f' ;; darwin* | rhapsody*) lt_cv_deplibs_check_method=pass_all ;; freebsd* | dragonfly*) if echo __ELF__ | $CC -E - | $GREP __ELF__ > /dev/null; then case $host_cpu in i*86 ) # Not sure whether the presence of OpenBSD here was a mistake. # Let's accept both of them until this is cleared up. lt_cv_deplibs_check_method='file_magic (FreeBSD|OpenBSD|DragonFly)/i[[3-9]]86 (compact )?demand paged shared library' lt_cv_file_magic_cmd=/usr/bin/file lt_cv_file_magic_test_file=`echo /usr/lib/libc.so.*` ;; esac else lt_cv_deplibs_check_method=pass_all fi ;; haiku*) lt_cv_deplibs_check_method=pass_all ;; hpux10.20* | hpux11*) lt_cv_file_magic_cmd=/usr/bin/file case $host_cpu in ia64*) lt_cv_deplibs_check_method='file_magic (s[[0-9]][[0-9]][[0-9]]|ELF-[[0-9]][[0-9]]) shared object file - IA64' lt_cv_file_magic_test_file=/usr/lib/hpux32/libc.so ;; hppa*64*) [lt_cv_deplibs_check_method='file_magic (s[0-9][0-9][0-9]|ELF[ -][0-9][0-9])(-bit)?( [LM]SB)? shared object( file)?[, -]* PA-RISC [0-9]\.[0-9]'] lt_cv_file_magic_test_file=/usr/lib/pa20_64/libc.sl ;; *) lt_cv_deplibs_check_method='file_magic (s[[0-9]][[0-9]][[0-9]]|PA-RISC[[0-9]]\.[[0-9]]) shared library' lt_cv_file_magic_test_file=/usr/lib/libc.sl ;; esac ;; interix[[3-9]]*) # PIC code is broken on Interix 3.x, that's why |\.a not |_pic\.a here lt_cv_deplibs_check_method='match_pattern /lib[[^/]]+(\.so|\.a)$' ;; irix5* | irix6* | nonstopux*) case $LD in *-32|*"-32 ") libmagic=32-bit;; *-n32|*"-n32 ") libmagic=N32;; *-64|*"-64 ") libmagic=64-bit;; *) libmagic=never-match;; esac lt_cv_deplibs_check_method=pass_all ;; # This must be glibc/ELF. linux* | k*bsd*-gnu | kopensolaris*-gnu | gnu*) lt_cv_deplibs_check_method=pass_all ;; netbsd* | netbsdelf*-gnu) if echo __ELF__ | $CC -E - | $GREP __ELF__ > /dev/null; then lt_cv_deplibs_check_method='match_pattern /lib[[^/]]+(\.so\.[[0-9]]+\.[[0-9]]+|_pic\.a)$' else lt_cv_deplibs_check_method='match_pattern /lib[[^/]]+(\.so|_pic\.a)$' fi ;; newos6*) lt_cv_deplibs_check_method='file_magic ELF [[0-9]][[0-9]]*-bit [[ML]]SB (executable|dynamic lib)' lt_cv_file_magic_cmd=/usr/bin/file lt_cv_file_magic_test_file=/usr/lib/libnls.so ;; *nto* | *qnx*) lt_cv_deplibs_check_method=pass_all ;; openbsd* | bitrig*) if test -z "`echo __ELF__ | $CC -E - | $GREP __ELF__`"; then lt_cv_deplibs_check_method='match_pattern /lib[[^/]]+(\.so\.[[0-9]]+\.[[0-9]]+|\.so|_pic\.a)$' else lt_cv_deplibs_check_method='match_pattern /lib[[^/]]+(\.so\.[[0-9]]+\.[[0-9]]+|_pic\.a)$' fi ;; osf3* | osf4* | osf5*) lt_cv_deplibs_check_method=pass_all ;; rdos*) lt_cv_deplibs_check_method=pass_all ;; solaris*) lt_cv_deplibs_check_method=pass_all ;; sysv5* | sco3.2v5* | sco5v6* | unixware* | OpenUNIX* | sysv4*uw2*) lt_cv_deplibs_check_method=pass_all ;; sysv4 | sysv4.3*) case $host_vendor in motorola) lt_cv_deplibs_check_method='file_magic ELF [[0-9]][[0-9]]*-bit [[ML]]SB (shared object|dynamic lib) M[[0-9]][[0-9]]* Version [[0-9]]' lt_cv_file_magic_test_file=`echo /usr/lib/libc.so*` ;; ncr) lt_cv_deplibs_check_method=pass_all ;; sequent) lt_cv_file_magic_cmd='/bin/file' lt_cv_deplibs_check_method='file_magic ELF [[0-9]][[0-9]]*-bit [[LM]]SB (shared object|dynamic lib )' ;; sni) lt_cv_file_magic_cmd='/bin/file' lt_cv_deplibs_check_method="file_magic ELF [[0-9]][[0-9]]*-bit [[LM]]SB dynamic lib" lt_cv_file_magic_test_file=/lib/libc.so ;; siemens) lt_cv_deplibs_check_method=pass_all ;; pc) lt_cv_deplibs_check_method=pass_all ;; esac ;; tpf*) lt_cv_deplibs_check_method=pass_all ;; os2*) lt_cv_deplibs_check_method=pass_all ;; esac ]) file_magic_glob= want_nocaseglob=no if test "$build" = "$host"; then case $host_os in mingw* | pw32*) if ( shopt | grep nocaseglob ) >/dev/null 2>&1; then want_nocaseglob=yes else file_magic_glob=`echo aAbBcCdDeEfFgGhHiIjJkKlLmMnNoOpPqQrRsStTuUvVwWxXyYzZ | $SED -e "s/\(..\)/s\/[[\1]]\/[[\1]]\/g;/g"` fi ;; esac fi file_magic_cmd=$lt_cv_file_magic_cmd deplibs_check_method=$lt_cv_deplibs_check_method test -z "$deplibs_check_method" && deplibs_check_method=unknown _LT_DECL([], [deplibs_check_method], [1], [Method to check whether dependent libraries are shared objects]) _LT_DECL([], [file_magic_cmd], [1], [Command to use when deplibs_check_method = "file_magic"]) _LT_DECL([], [file_magic_glob], [1], [How to find potential files when deplibs_check_method = "file_magic"]) _LT_DECL([], [want_nocaseglob], [1], [Find potential files using nocaseglob when deplibs_check_method = "file_magic"]) ])# _LT_CHECK_MAGIC_METHOD # LT_PATH_NM # ---------- # find the pathname to a BSD- or MS-compatible name lister AC_DEFUN([LT_PATH_NM], [AC_REQUIRE([AC_PROG_CC])dnl AC_CACHE_CHECK([for BSD- or MS-compatible name lister (nm)], lt_cv_path_NM, [if test -n "$NM"; then # Let the user override the test. lt_cv_path_NM=$NM else lt_nm_to_check=${ac_tool_prefix}nm if test -n "$ac_tool_prefix" && test "$build" = "$host"; then lt_nm_to_check="$lt_nm_to_check nm" fi for lt_tmp_nm in $lt_nm_to_check; do lt_save_ifs=$IFS; IFS=$PATH_SEPARATOR for ac_dir in $PATH /usr/ccs/bin/elf /usr/ccs/bin /usr/ucb /bin; do IFS=$lt_save_ifs test -z "$ac_dir" && ac_dir=. tmp_nm=$ac_dir/$lt_tmp_nm if test -f "$tmp_nm" || test -f "$tmp_nm$ac_exeext"; then # Check to see if the nm accepts a BSD-compat flag. # Adding the 'sed 1q' prevents false positives on HP-UX, which says: # nm: unknown option "B" ignored # Tru64's nm complains that /dev/null is an invalid object file # MSYS converts /dev/null to NUL, MinGW nm treats NUL as empty case $build_os in mingw*) lt_bad_file=conftest.nm/nofile ;; *) lt_bad_file=/dev/null ;; esac case `"$tmp_nm" -B $lt_bad_file 2>&1 | sed '1q'` in *$lt_bad_file* | *'Invalid file or object type'*) lt_cv_path_NM="$tmp_nm -B" break 2 ;; *) case `"$tmp_nm" -p /dev/null 2>&1 | sed '1q'` in */dev/null*) lt_cv_path_NM="$tmp_nm -p" break 2 ;; *) lt_cv_path_NM=${lt_cv_path_NM="$tmp_nm"} # keep the first match, but continue # so that we can try to find one that supports BSD flags ;; esac ;; esac fi done IFS=$lt_save_ifs done : ${lt_cv_path_NM=no} fi]) if test no != "$lt_cv_path_NM"; then NM=$lt_cv_path_NM else # Didn't find any BSD compatible name lister, look for dumpbin. if test -n "$DUMPBIN"; then : # Let the user override the test. else AC_CHECK_TOOLS(DUMPBIN, [dumpbin "link -dump"], :) case `$DUMPBIN -symbols -headers /dev/null 2>&1 | sed '1q'` in *COFF*) DUMPBIN="$DUMPBIN -symbols -headers" ;; *) DUMPBIN=: ;; esac fi AC_SUBST([DUMPBIN]) if test : != "$DUMPBIN"; then NM=$DUMPBIN fi fi test -z "$NM" && NM=nm AC_SUBST([NM]) _LT_DECL([], [NM], [1], [A BSD- or MS-compatible name lister])dnl AC_CACHE_CHECK([the name lister ($NM) interface], [lt_cv_nm_interface], [lt_cv_nm_interface="BSD nm" echo "int some_variable = 0;" > conftest.$ac_ext (eval echo "\"\$as_me:$LINENO: $ac_compile\"" >&AS_MESSAGE_LOG_FD) (eval "$ac_compile" 2>conftest.err) cat conftest.err >&AS_MESSAGE_LOG_FD (eval echo "\"\$as_me:$LINENO: $NM \\\"conftest.$ac_objext\\\"\"" >&AS_MESSAGE_LOG_FD) (eval "$NM \"conftest.$ac_objext\"" 2>conftest.err > conftest.out) cat conftest.err >&AS_MESSAGE_LOG_FD (eval echo "\"\$as_me:$LINENO: output\"" >&AS_MESSAGE_LOG_FD) cat conftest.out >&AS_MESSAGE_LOG_FD if $GREP 'External.*some_variable' conftest.out > /dev/null; then lt_cv_nm_interface="MS dumpbin" fi rm -f conftest*]) ])# LT_PATH_NM # Old names: AU_ALIAS([AM_PROG_NM], [LT_PATH_NM]) AU_ALIAS([AC_PROG_NM], [LT_PATH_NM]) dnl aclocal-1.4 backwards compatibility: dnl AC_DEFUN([AM_PROG_NM], []) dnl AC_DEFUN([AC_PROG_NM], []) # _LT_CHECK_SHAREDLIB_FROM_LINKLIB # -------------------------------- # how to determine the name of the shared library # associated with a specific link library. # -- PORTME fill in with the dynamic library characteristics m4_defun([_LT_CHECK_SHAREDLIB_FROM_LINKLIB], [m4_require([_LT_DECL_EGREP]) m4_require([_LT_DECL_OBJDUMP]) m4_require([_LT_DECL_DLLTOOL]) AC_CACHE_CHECK([how to associate runtime and link libraries], lt_cv_sharedlib_from_linklib_cmd, [lt_cv_sharedlib_from_linklib_cmd='unknown' case $host_os in cygwin* | mingw* | pw32* | cegcc*) # two different shell functions defined in ltmain.sh; # decide which one to use based on capabilities of $DLLTOOL case `$DLLTOOL --help 2>&1` in *--identify-strict*) lt_cv_sharedlib_from_linklib_cmd=func_cygming_dll_for_implib ;; *) lt_cv_sharedlib_from_linklib_cmd=func_cygming_dll_for_implib_fallback ;; esac ;; *) # fallback: assume linklib IS sharedlib lt_cv_sharedlib_from_linklib_cmd=$ECHO ;; esac ]) sharedlib_from_linklib_cmd=$lt_cv_sharedlib_from_linklib_cmd test -z "$sharedlib_from_linklib_cmd" && sharedlib_from_linklib_cmd=$ECHO _LT_DECL([], [sharedlib_from_linklib_cmd], [1], [Command to associate shared and link libraries]) ])# _LT_CHECK_SHAREDLIB_FROM_LINKLIB # _LT_PATH_MANIFEST_TOOL # ---------------------- # locate the manifest tool m4_defun([_LT_PATH_MANIFEST_TOOL], [AC_CHECK_TOOL(MANIFEST_TOOL, mt, :) test -z "$MANIFEST_TOOL" && MANIFEST_TOOL=mt AC_CACHE_CHECK([if $MANIFEST_TOOL is a manifest tool], [lt_cv_path_mainfest_tool], [lt_cv_path_mainfest_tool=no echo "$as_me:$LINENO: $MANIFEST_TOOL '-?'" >&AS_MESSAGE_LOG_FD $MANIFEST_TOOL '-?' 2>conftest.err > conftest.out cat conftest.err >&AS_MESSAGE_LOG_FD if $GREP 'Manifest Tool' conftest.out > /dev/null; then lt_cv_path_mainfest_tool=yes fi rm -f conftest*]) if test yes != "$lt_cv_path_mainfest_tool"; then MANIFEST_TOOL=: fi _LT_DECL([], [MANIFEST_TOOL], [1], [Manifest tool])dnl ])# _LT_PATH_MANIFEST_TOOL # _LT_DLL_DEF_P([FILE]) # --------------------- # True iff FILE is a Windows DLL '.def' file. # Keep in sync with func_dll_def_p in the libtool script AC_DEFUN([_LT_DLL_DEF_P], [dnl test DEF = "`$SED -n dnl -e '\''s/^[[ ]]*//'\'' dnl Strip leading whitespace -e '\''/^\(;.*\)*$/d'\'' dnl Delete empty lines and comments -e '\''s/^\(EXPORTS\|LIBRARY\)\([[ ]].*\)*$/DEF/p'\'' dnl -e q dnl Only consider the first "real" line $1`" dnl ])# _LT_DLL_DEF_P # LT_LIB_M # -------- # check for math library AC_DEFUN([LT_LIB_M], [AC_REQUIRE([AC_CANONICAL_HOST])dnl LIBM= case $host in *-*-beos* | *-*-cegcc* | *-*-cygwin* | *-*-haiku* | *-*-pw32* | *-*-darwin*) # These system don't have libm, or don't need it ;; *-ncr-sysv4.3*) AC_CHECK_LIB(mw, _mwvalidcheckl, LIBM=-lmw) AC_CHECK_LIB(m, cos, LIBM="$LIBM -lm") ;; *) AC_CHECK_LIB(m, cos, LIBM=-lm) ;; esac AC_SUBST([LIBM]) ])# LT_LIB_M # Old name: AU_ALIAS([AC_CHECK_LIBM], [LT_LIB_M]) dnl aclocal-1.4 backwards compatibility: dnl AC_DEFUN([AC_CHECK_LIBM], []) # _LT_COMPILER_NO_RTTI([TAGNAME]) # ------------------------------- m4_defun([_LT_COMPILER_NO_RTTI], [m4_require([_LT_TAG_COMPILER])dnl _LT_TAGVAR(lt_prog_compiler_no_builtin_flag, $1)= if test yes = "$GCC"; then case $cc_basename in nvcc*) _LT_TAGVAR(lt_prog_compiler_no_builtin_flag, $1)=' -Xcompiler -fno-builtin' ;; *) _LT_TAGVAR(lt_prog_compiler_no_builtin_flag, $1)=' -fno-builtin' ;; esac _LT_COMPILER_OPTION([if $compiler supports -fno-rtti -fno-exceptions], lt_cv_prog_compiler_rtti_exceptions, [-fno-rtti -fno-exceptions], [], [_LT_TAGVAR(lt_prog_compiler_no_builtin_flag, $1)="$_LT_TAGVAR(lt_prog_compiler_no_builtin_flag, $1) -fno-rtti -fno-exceptions"]) fi _LT_TAGDECL([no_builtin_flag], [lt_prog_compiler_no_builtin_flag], [1], [Compiler flag to turn off builtin functions]) ])# _LT_COMPILER_NO_RTTI # _LT_CMD_GLOBAL_SYMBOLS # ---------------------- m4_defun([_LT_CMD_GLOBAL_SYMBOLS], [AC_REQUIRE([AC_CANONICAL_HOST])dnl AC_REQUIRE([AC_PROG_CC])dnl AC_REQUIRE([AC_PROG_AWK])dnl AC_REQUIRE([LT_PATH_NM])dnl AC_REQUIRE([LT_PATH_LD])dnl m4_require([_LT_DECL_SED])dnl m4_require([_LT_DECL_EGREP])dnl m4_require([_LT_TAG_COMPILER])dnl # Check for command to grab the raw symbol name followed by C symbol from nm. AC_MSG_CHECKING([command to parse $NM output from $compiler object]) AC_CACHE_VAL([lt_cv_sys_global_symbol_pipe], [ # These are sane defaults that work on at least a few old systems. # [They come from Ultrix. What could be older than Ultrix?!! ;)] # Character class describing NM global symbol codes. symcode='[[BCDEGRST]]' # Regexp to match symbols that can be accessed directly from C. sympat='\([[_A-Za-z]][[_A-Za-z0-9]]*\)' # Define system-specific variables. case $host_os in aix*) symcode='[[BCDT]]' ;; cygwin* | mingw* | pw32* | cegcc*) symcode='[[ABCDGISTW]]' ;; hpux*) if test ia64 = "$host_cpu"; then symcode='[[ABCDEGRST]]' fi ;; irix* | nonstopux*) symcode='[[BCDEGRST]]' ;; osf*) symcode='[[BCDEGQRST]]' ;; solaris*) symcode='[[BDRT]]' ;; sco3.2v5*) symcode='[[DT]]' ;; sysv4.2uw2*) symcode='[[DT]]' ;; sysv5* | sco5v6* | unixware* | OpenUNIX*) symcode='[[ABDT]]' ;; sysv4) symcode='[[DFNSTU]]' ;; esac # If we're using GNU nm, then use its standard symbol codes. case `$NM -V 2>&1` in *GNU* | *'with BFD'*) symcode='[[ABCDGIRSTW]]' ;; esac if test "$lt_cv_nm_interface" = "MS dumpbin"; then # Gets list of data symbols to import. lt_cv_sys_global_symbol_to_import="sed -n -e 's/^I .* \(.*\)$/\1/p'" # Adjust the below global symbol transforms to fixup imported variables. lt_cdecl_hook=" -e 's/^I .* \(.*\)$/extern __declspec(dllimport) char \1;/p'" lt_c_name_hook=" -e 's/^I .* \(.*\)$/ {\"\1\", (void *) 0},/p'" lt_c_name_lib_hook="\ -e 's/^I .* \(lib.*\)$/ {\"\1\", (void *) 0},/p'\ -e 's/^I .* \(.*\)$/ {\"lib\1\", (void *) 0},/p'" else # Disable hooks by default. lt_cv_sys_global_symbol_to_import= lt_cdecl_hook= lt_c_name_hook= lt_c_name_lib_hook= fi # Transform an extracted symbol line into a proper C declaration. # Some systems (esp. on ia64) link data and code symbols differently, # so use this general approach. lt_cv_sys_global_symbol_to_cdecl="sed -n"\ $lt_cdecl_hook\ " -e 's/^T .* \(.*\)$/extern int \1();/p'"\ " -e 's/^$symcode$symcode* .* \(.*\)$/extern char \1;/p'" # Transform an extracted symbol line into symbol name and symbol address lt_cv_sys_global_symbol_to_c_name_address="sed -n"\ $lt_c_name_hook\ " -e 's/^: \(.*\) .*$/ {\"\1\", (void *) 0},/p'"\ " -e 's/^$symcode$symcode* .* \(.*\)$/ {\"\1\", (void *) \&\1},/p'" # Transform an extracted symbol line into symbol name with lib prefix and # symbol address. lt_cv_sys_global_symbol_to_c_name_address_lib_prefix="sed -n"\ $lt_c_name_lib_hook\ " -e 's/^: \(.*\) .*$/ {\"\1\", (void *) 0},/p'"\ " -e 's/^$symcode$symcode* .* \(lib.*\)$/ {\"\1\", (void *) \&\1},/p'"\ " -e 's/^$symcode$symcode* .* \(.*\)$/ {\"lib\1\", (void *) \&\1},/p'" # Handle CRLF in mingw tool chain opt_cr= case $build_os in mingw*) opt_cr=`$ECHO 'x\{0,1\}' | tr x '\015'` # option cr in regexp ;; esac # Try without a prefix underscore, then with it. for ac_symprfx in "" "_"; do # Transform symcode, sympat, and symprfx into a raw symbol and a C symbol. symxfrm="\\1 $ac_symprfx\\2 \\2" # Write the raw and C identifiers. if test "$lt_cv_nm_interface" = "MS dumpbin"; then # Fake it for dumpbin and say T for any non-static function, # D for any global variable and I for any imported variable. # Also find C++ and __fastcall symbols from MSVC++, # which start with @ or ?. lt_cv_sys_global_symbol_pipe="$AWK ['"\ " {last_section=section; section=\$ 3};"\ " /^COFF SYMBOL TABLE/{for(i in hide) delete hide[i]};"\ " /Section length .*#relocs.*(pick any)/{hide[last_section]=1};"\ " /^ *Symbol name *: /{split(\$ 0,sn,\":\"); si=substr(sn[2],2)};"\ " /^ *Type *: code/{print \"T\",si,substr(si,length(prfx))};"\ " /^ *Type *: data/{print \"I\",si,substr(si,length(prfx))};"\ " \$ 0!~/External *\|/{next};"\ " / 0+ UNDEF /{next}; / UNDEF \([^|]\)*()/{next};"\ " {if(hide[section]) next};"\ " {f=\"D\"}; \$ 0~/\(\).*\|/{f=\"T\"};"\ " {split(\$ 0,a,/\||\r/); split(a[2],s)};"\ " s[1]~/^[@?]/{print f,s[1],s[1]; next};"\ " s[1]~prfx {split(s[1],t,\"@\"); print f,t[1],substr(t[1],length(prfx))}"\ " ' prfx=^$ac_symprfx]" else lt_cv_sys_global_symbol_pipe="sed -n -e 's/^.*[[ ]]\($symcode$symcode*\)[[ ]][[ ]]*$ac_symprfx$sympat$opt_cr$/$symxfrm/p'" fi lt_cv_sys_global_symbol_pipe="$lt_cv_sys_global_symbol_pipe | sed '/ __gnu_lto/d'" # Check to see that the pipe works correctly. pipe_works=no rm -f conftest* cat > conftest.$ac_ext <<_LT_EOF #ifdef __cplusplus extern "C" { #endif char nm_test_var; void nm_test_func(void); void nm_test_func(void){} #ifdef __cplusplus } #endif int main(){nm_test_var='a';nm_test_func();return(0);} _LT_EOF if AC_TRY_EVAL(ac_compile); then # Now try to grab the symbols. nlist=conftest.nm $ECHO "$as_me:$LINENO: $NM conftest.$ac_objext | $lt_cv_sys_global_symbol_pipe > $nlist" >&AS_MESSAGE_LOG_FD if eval "$NM" conftest.$ac_objext \| "$lt_cv_sys_global_symbol_pipe" \> $nlist 2>&AS_MESSAGE_LOG_FD && test -s "$nlist"; then # Try sorting and uniquifying the output. if sort "$nlist" | uniq > "$nlist"T; then mv -f "$nlist"T "$nlist" else rm -f "$nlist"T fi # Make sure that we snagged all the symbols we need. if $GREP ' nm_test_var$' "$nlist" >/dev/null; then if $GREP ' nm_test_func$' "$nlist" >/dev/null; then cat <<_LT_EOF > conftest.$ac_ext /* Keep this code in sync between libtool.m4, ltmain, lt_system.h, and tests. */ #if defined _WIN32 || defined __CYGWIN__ || defined _WIN32_WCE /* DATA imports from DLLs on WIN32 can't be const, because runtime relocations are performed -- see ld's documentation on pseudo-relocs. */ # define LT@&t@_DLSYM_CONST #elif defined __osf__ /* This system does not cope well with relocations in const data. */ # define LT@&t@_DLSYM_CONST #else # define LT@&t@_DLSYM_CONST const #endif #ifdef __cplusplus extern "C" { #endif _LT_EOF # Now generate the symbol file. eval "$lt_cv_sys_global_symbol_to_cdecl"' < "$nlist" | $GREP -v main >> conftest.$ac_ext' cat <<_LT_EOF >> conftest.$ac_ext /* The mapping between symbol names and symbols. */ LT@&t@_DLSYM_CONST struct { const char *name; void *address; } lt__PROGRAM__LTX_preloaded_symbols[[]] = { { "@PROGRAM@", (void *) 0 }, _LT_EOF $SED "s/^$symcode$symcode* .* \(.*\)$/ {\"\1\", (void *) \&\1},/" < "$nlist" | $GREP -v main >> conftest.$ac_ext cat <<\_LT_EOF >> conftest.$ac_ext {0, (void *) 0} }; /* This works around a problem in FreeBSD linker */ #ifdef FREEBSD_WORKAROUND static const void *lt_preloaded_setup() { return lt__PROGRAM__LTX_preloaded_symbols; } #endif #ifdef __cplusplus } #endif _LT_EOF # Now try linking the two files. mv conftest.$ac_objext conftstm.$ac_objext lt_globsym_save_LIBS=$LIBS lt_globsym_save_CFLAGS=$CFLAGS LIBS=conftstm.$ac_objext CFLAGS="$CFLAGS$_LT_TAGVAR(lt_prog_compiler_no_builtin_flag, $1)" if AC_TRY_EVAL(ac_link) && test -s conftest$ac_exeext; then pipe_works=yes fi LIBS=$lt_globsym_save_LIBS CFLAGS=$lt_globsym_save_CFLAGS else echo "cannot find nm_test_func in $nlist" >&AS_MESSAGE_LOG_FD fi else echo "cannot find nm_test_var in $nlist" >&AS_MESSAGE_LOG_FD fi else echo "cannot run $lt_cv_sys_global_symbol_pipe" >&AS_MESSAGE_LOG_FD fi else echo "$progname: failed program was:" >&AS_MESSAGE_LOG_FD cat conftest.$ac_ext >&5 fi rm -rf conftest* conftst* # Do not use the global_symbol_pipe unless it works. if test yes = "$pipe_works"; then break else lt_cv_sys_global_symbol_pipe= fi done ]) if test -z "$lt_cv_sys_global_symbol_pipe"; then lt_cv_sys_global_symbol_to_cdecl= fi if test -z "$lt_cv_sys_global_symbol_pipe$lt_cv_sys_global_symbol_to_cdecl"; then AC_MSG_RESULT(failed) else AC_MSG_RESULT(ok) fi # Response file support. if test "$lt_cv_nm_interface" = "MS dumpbin"; then nm_file_list_spec='@' elif $NM --help 2>/dev/null | grep '[[@]]FILE' >/dev/null; then nm_file_list_spec='@' fi _LT_DECL([global_symbol_pipe], [lt_cv_sys_global_symbol_pipe], [1], [Take the output of nm and produce a listing of raw symbols and C names]) _LT_DECL([global_symbol_to_cdecl], [lt_cv_sys_global_symbol_to_cdecl], [1], [Transform the output of nm in a proper C declaration]) _LT_DECL([global_symbol_to_import], [lt_cv_sys_global_symbol_to_import], [1], [Transform the output of nm into a list of symbols to manually relocate]) _LT_DECL([global_symbol_to_c_name_address], [lt_cv_sys_global_symbol_to_c_name_address], [1], [Transform the output of nm in a C name address pair]) _LT_DECL([global_symbol_to_c_name_address_lib_prefix], [lt_cv_sys_global_symbol_to_c_name_address_lib_prefix], [1], [Transform the output of nm in a C name address pair when lib prefix is needed]) _LT_DECL([nm_interface], [lt_cv_nm_interface], [1], [The name lister interface]) _LT_DECL([], [nm_file_list_spec], [1], [Specify filename containing input files for $NM]) ]) # _LT_CMD_GLOBAL_SYMBOLS # _LT_COMPILER_PIC([TAGNAME]) # --------------------------- m4_defun([_LT_COMPILER_PIC], [m4_require([_LT_TAG_COMPILER])dnl _LT_TAGVAR(lt_prog_compiler_wl, $1)= _LT_TAGVAR(lt_prog_compiler_pic, $1)= _LT_TAGVAR(lt_prog_compiler_static, $1)= m4_if([$1], [CXX], [ # C++ specific cases for pic, static, wl, etc. if test yes = "$GXX"; then _LT_TAGVAR(lt_prog_compiler_wl, $1)='-Wl,' _LT_TAGVAR(lt_prog_compiler_static, $1)='-static' case $host_os in aix*) # All AIX code is PIC. if test ia64 = "$host_cpu"; then # AIX 5 now supports IA64 processor _LT_TAGVAR(lt_prog_compiler_static, $1)='-Bstatic' fi _LT_TAGVAR(lt_prog_compiler_pic, $1)='-fPIC' ;; amigaos*) case $host_cpu in powerpc) # see comment about AmigaOS4 .so support _LT_TAGVAR(lt_prog_compiler_pic, $1)='-fPIC' ;; m68k) # FIXME: we need at least 68020 code to build shared libraries, but # adding the '-m68020' flag to GCC prevents building anything better, # like '-m68040'. _LT_TAGVAR(lt_prog_compiler_pic, $1)='-m68020 -resident32 -malways-restore-a4' ;; esac ;; beos* | irix5* | irix6* | nonstopux* | osf3* | osf4* | osf5*) # PIC is the default for these OSes. ;; mingw* | cygwin* | os2* | pw32* | cegcc*) # This hack is so that the source file can tell whether it is being # built for inclusion in a dll (and should export symbols for example). # Although the cygwin gcc ignores -fPIC, still need this for old-style # (--disable-auto-import) libraries m4_if([$1], [GCJ], [], [_LT_TAGVAR(lt_prog_compiler_pic, $1)='-DDLL_EXPORT']) case $host_os in os2*) _LT_TAGVAR(lt_prog_compiler_static, $1)='$wl-static' ;; esac ;; darwin* | rhapsody*) # PIC is the default on this platform # Common symbols not allowed in MH_DYLIB files _LT_TAGVAR(lt_prog_compiler_pic, $1)='-fno-common' ;; *djgpp*) # DJGPP does not support shared libraries at all _LT_TAGVAR(lt_prog_compiler_pic, $1)= ;; haiku*) # PIC is the default for Haiku. # The "-static" flag exists, but is broken. _LT_TAGVAR(lt_prog_compiler_static, $1)= ;; interix[[3-9]]*) # Interix 3.x gcc -fpic/-fPIC options generate broken code. # Instead, we relocate shared libraries at runtime. ;; sysv4*MP*) if test -d /usr/nec; then _LT_TAGVAR(lt_prog_compiler_pic, $1)=-Kconform_pic fi ;; hpux*) # PIC is the default for 64-bit PA HP-UX, but not for 32-bit # PA HP-UX. On IA64 HP-UX, PIC is the default but the pic flag # sets the default TLS model and affects inlining. case $host_cpu in hppa*64*) ;; *) _LT_TAGVAR(lt_prog_compiler_pic, $1)='-fPIC' ;; esac ;; *qnx* | *nto*) # QNX uses GNU C++, but need to define -shared option too, otherwise # it will coredump. _LT_TAGVAR(lt_prog_compiler_pic, $1)='-fPIC -shared' ;; *) _LT_TAGVAR(lt_prog_compiler_pic, $1)='-fPIC' ;; esac else case $host_os in aix[[4-9]]*) # All AIX code is PIC. if test ia64 = "$host_cpu"; then # AIX 5 now supports IA64 processor _LT_TAGVAR(lt_prog_compiler_static, $1)='-Bstatic' else _LT_TAGVAR(lt_prog_compiler_static, $1)='-bnso -bI:/lib/syscalls.exp' fi ;; chorus*) case $cc_basename in cxch68*) # Green Hills C++ Compiler # _LT_TAGVAR(lt_prog_compiler_static, $1)="--no_auto_instantiation -u __main -u __premain -u _abort -r $COOL_DIR/lib/libOrb.a $MVME_DIR/lib/CC/libC.a $MVME_DIR/lib/classix/libcx.s.a" ;; esac ;; mingw* | cygwin* | os2* | pw32* | cegcc*) # This hack is so that the source file can tell whether it is being # built for inclusion in a dll (and should export symbols for example). m4_if([$1], [GCJ], [], [_LT_TAGVAR(lt_prog_compiler_pic, $1)='-DDLL_EXPORT']) ;; dgux*) case $cc_basename in ec++*) _LT_TAGVAR(lt_prog_compiler_pic, $1)='-KPIC' ;; ghcx*) # Green Hills C++ Compiler _LT_TAGVAR(lt_prog_compiler_pic, $1)='-pic' ;; *) ;; esac ;; freebsd* | dragonfly*) # FreeBSD uses GNU C++ ;; hpux9* | hpux10* | hpux11*) case $cc_basename in CC*) _LT_TAGVAR(lt_prog_compiler_wl, $1)='-Wl,' _LT_TAGVAR(lt_prog_compiler_static, $1)='$wl-a ${wl}archive' if test ia64 != "$host_cpu"; then _LT_TAGVAR(lt_prog_compiler_pic, $1)='+Z' fi ;; aCC*) _LT_TAGVAR(lt_prog_compiler_wl, $1)='-Wl,' _LT_TAGVAR(lt_prog_compiler_static, $1)='$wl-a ${wl}archive' case $host_cpu in hppa*64*|ia64*) # +Z the default ;; *) _LT_TAGVAR(lt_prog_compiler_pic, $1)='+Z' ;; esac ;; *) ;; esac ;; interix*) # This is c89, which is MS Visual C++ (no shared libs) # Anyone wants to do a port? ;; irix5* | irix6* | nonstopux*) case $cc_basename in CC*) _LT_TAGVAR(lt_prog_compiler_wl, $1)='-Wl,' _LT_TAGVAR(lt_prog_compiler_static, $1)='-non_shared' # CC pic flag -KPIC is the default. ;; *) ;; esac ;; linux* | k*bsd*-gnu | kopensolaris*-gnu | gnu*) case $cc_basename in KCC*) # KAI C++ Compiler _LT_TAGVAR(lt_prog_compiler_wl, $1)='--backend -Wl,' _LT_TAGVAR(lt_prog_compiler_pic, $1)='-fPIC' ;; ecpc* ) # old Intel C++ for x86_64, which still supported -KPIC. _LT_TAGVAR(lt_prog_compiler_wl, $1)='-Wl,' _LT_TAGVAR(lt_prog_compiler_pic, $1)='-KPIC' _LT_TAGVAR(lt_prog_compiler_static, $1)='-static' ;; icpc* ) # Intel C++, used to be incompatible with GCC. # ICC 10 doesn't accept -KPIC any more. _LT_TAGVAR(lt_prog_compiler_wl, $1)='-Wl,' _LT_TAGVAR(lt_prog_compiler_pic, $1)='-fPIC' _LT_TAGVAR(lt_prog_compiler_static, $1)='-static' ;; pgCC* | pgcpp*) # Portland Group C++ compiler _LT_TAGVAR(lt_prog_compiler_wl, $1)='-Wl,' _LT_TAGVAR(lt_prog_compiler_pic, $1)='-fpic' _LT_TAGVAR(lt_prog_compiler_static, $1)='-Bstatic' ;; cxx*) # Compaq C++ # Make sure the PIC flag is empty. It appears that all Alpha # Linux and Compaq Tru64 Unix objects are PIC. _LT_TAGVAR(lt_prog_compiler_pic, $1)= _LT_TAGVAR(lt_prog_compiler_static, $1)='-non_shared' ;; xlc* | xlC* | bgxl[[cC]]* | mpixl[[cC]]*) # IBM XL 8.0, 9.0 on PPC and BlueGene _LT_TAGVAR(lt_prog_compiler_wl, $1)='-Wl,' _LT_TAGVAR(lt_prog_compiler_pic, $1)='-qpic' _LT_TAGVAR(lt_prog_compiler_static, $1)='-qstaticlink' ;; *) case `$CC -V 2>&1 | sed 5q` in *Sun\ C*) # Sun C++ 5.9 _LT_TAGVAR(lt_prog_compiler_pic, $1)='-KPIC' _LT_TAGVAR(lt_prog_compiler_static, $1)='-Bstatic' _LT_TAGVAR(lt_prog_compiler_wl, $1)='-Qoption ld ' ;; esac ;; esac ;; lynxos*) ;; m88k*) ;; mvs*) case $cc_basename in cxx*) _LT_TAGVAR(lt_prog_compiler_pic, $1)='-W c,exportall' ;; *) ;; esac ;; netbsd* | netbsdelf*-gnu) ;; *qnx* | *nto*) # QNX uses GNU C++, but need to define -shared option too, otherwise # it will coredump. _LT_TAGVAR(lt_prog_compiler_pic, $1)='-fPIC -shared' ;; osf3* | osf4* | osf5*) case $cc_basename in KCC*) _LT_TAGVAR(lt_prog_compiler_wl, $1)='--backend -Wl,' ;; RCC*) # Rational C++ 2.4.1 _LT_TAGVAR(lt_prog_compiler_pic, $1)='-pic' ;; cxx*) # Digital/Compaq C++ _LT_TAGVAR(lt_prog_compiler_wl, $1)='-Wl,' # Make sure the PIC flag is empty. It appears that all Alpha # Linux and Compaq Tru64 Unix objects are PIC. _LT_TAGVAR(lt_prog_compiler_pic, $1)= _LT_TAGVAR(lt_prog_compiler_static, $1)='-non_shared' ;; *) ;; esac ;; psos*) ;; solaris*) case $cc_basename in CC* | sunCC*) # Sun C++ 4.2, 5.x and Centerline C++ _LT_TAGVAR(lt_prog_compiler_pic, $1)='-KPIC' _LT_TAGVAR(lt_prog_compiler_static, $1)='-Bstatic' _LT_TAGVAR(lt_prog_compiler_wl, $1)='-Qoption ld ' ;; gcx*) # Green Hills C++ Compiler _LT_TAGVAR(lt_prog_compiler_pic, $1)='-PIC' ;; *) ;; esac ;; sunos4*) case $cc_basename in CC*) # Sun C++ 4.x _LT_TAGVAR(lt_prog_compiler_pic, $1)='-pic' _LT_TAGVAR(lt_prog_compiler_static, $1)='-Bstatic' ;; lcc*) # Lucid _LT_TAGVAR(lt_prog_compiler_pic, $1)='-pic' ;; *) ;; esac ;; sysv5* | unixware* | sco3.2v5* | sco5v6* | OpenUNIX*) case $cc_basename in CC*) _LT_TAGVAR(lt_prog_compiler_wl, $1)='-Wl,' _LT_TAGVAR(lt_prog_compiler_pic, $1)='-KPIC' _LT_TAGVAR(lt_prog_compiler_static, $1)='-Bstatic' ;; esac ;; tandem*) case $cc_basename in NCC*) # NonStop-UX NCC 3.20 _LT_TAGVAR(lt_prog_compiler_pic, $1)='-KPIC' ;; *) ;; esac ;; vxworks*) ;; *) _LT_TAGVAR(lt_prog_compiler_can_build_shared, $1)=no ;; esac fi ], [ if test yes = "$GCC"; then _LT_TAGVAR(lt_prog_compiler_wl, $1)='-Wl,' _LT_TAGVAR(lt_prog_compiler_static, $1)='-static' case $host_os in aix*) # All AIX code is PIC. if test ia64 = "$host_cpu"; then # AIX 5 now supports IA64 processor _LT_TAGVAR(lt_prog_compiler_static, $1)='-Bstatic' fi _LT_TAGVAR(lt_prog_compiler_pic, $1)='-fPIC' ;; amigaos*) case $host_cpu in powerpc) # see comment about AmigaOS4 .so support _LT_TAGVAR(lt_prog_compiler_pic, $1)='-fPIC' ;; m68k) # FIXME: we need at least 68020 code to build shared libraries, but # adding the '-m68020' flag to GCC prevents building anything better, # like '-m68040'. _LT_TAGVAR(lt_prog_compiler_pic, $1)='-m68020 -resident32 -malways-restore-a4' ;; esac ;; beos* | irix5* | irix6* | nonstopux* | osf3* | osf4* | osf5*) # PIC is the default for these OSes. ;; mingw* | cygwin* | pw32* | os2* | cegcc*) # This hack is so that the source file can tell whether it is being # built for inclusion in a dll (and should export symbols for example). # Although the cygwin gcc ignores -fPIC, still need this for old-style # (--disable-auto-import) libraries m4_if([$1], [GCJ], [], [_LT_TAGVAR(lt_prog_compiler_pic, $1)='-DDLL_EXPORT']) case $host_os in os2*) _LT_TAGVAR(lt_prog_compiler_static, $1)='$wl-static' ;; esac ;; darwin* | rhapsody*) # PIC is the default on this platform # Common symbols not allowed in MH_DYLIB files _LT_TAGVAR(lt_prog_compiler_pic, $1)='-fno-common' ;; haiku*) # PIC is the default for Haiku. # The "-static" flag exists, but is broken. _LT_TAGVAR(lt_prog_compiler_static, $1)= ;; hpux*) # PIC is the default for 64-bit PA HP-UX, but not for 32-bit # PA HP-UX. On IA64 HP-UX, PIC is the default but the pic flag # sets the default TLS model and affects inlining. case $host_cpu in hppa*64*) # +Z the default ;; *) _LT_TAGVAR(lt_prog_compiler_pic, $1)='-fPIC' ;; esac ;; interix[[3-9]]*) # Interix 3.x gcc -fpic/-fPIC options generate broken code. # Instead, we relocate shared libraries at runtime. ;; msdosdjgpp*) # Just because we use GCC doesn't mean we suddenly get shared libraries # on systems that don't support them. _LT_TAGVAR(lt_prog_compiler_can_build_shared, $1)=no enable_shared=no ;; *nto* | *qnx*) # QNX uses GNU C++, but need to define -shared option too, otherwise # it will coredump. _LT_TAGVAR(lt_prog_compiler_pic, $1)='-fPIC -shared' ;; sysv4*MP*) if test -d /usr/nec; then _LT_TAGVAR(lt_prog_compiler_pic, $1)=-Kconform_pic fi ;; *) _LT_TAGVAR(lt_prog_compiler_pic, $1)='-fPIC' ;; esac case $cc_basename in nvcc*) # Cuda Compiler Driver 2.2 _LT_TAGVAR(lt_prog_compiler_wl, $1)='-Xlinker ' if test -n "$_LT_TAGVAR(lt_prog_compiler_pic, $1)"; then _LT_TAGVAR(lt_prog_compiler_pic, $1)="-Xcompiler $_LT_TAGVAR(lt_prog_compiler_pic, $1)" fi ;; esac else # PORTME Check for flag to pass linker flags through the system compiler. case $host_os in aix*) _LT_TAGVAR(lt_prog_compiler_wl, $1)='-Wl,' if test ia64 = "$host_cpu"; then # AIX 5 now supports IA64 processor _LT_TAGVAR(lt_prog_compiler_static, $1)='-Bstatic' else _LT_TAGVAR(lt_prog_compiler_static, $1)='-bnso -bI:/lib/syscalls.exp' fi ;; darwin* | rhapsody*) # PIC is the default on this platform # Common symbols not allowed in MH_DYLIB files _LT_TAGVAR(lt_prog_compiler_pic, $1)='-fno-common' case $cc_basename in nagfor*) # NAG Fortran compiler _LT_TAGVAR(lt_prog_compiler_wl, $1)='-Wl,-Wl,,' _LT_TAGVAR(lt_prog_compiler_pic, $1)='-PIC' _LT_TAGVAR(lt_prog_compiler_static, $1)='-Bstatic' ;; esac ;; mingw* | cygwin* | pw32* | os2* | cegcc*) # This hack is so that the source file can tell whether it is being # built for inclusion in a dll (and should export symbols for example). m4_if([$1], [GCJ], [], [_LT_TAGVAR(lt_prog_compiler_pic, $1)='-DDLL_EXPORT']) case $host_os in os2*) _LT_TAGVAR(lt_prog_compiler_static, $1)='$wl-static' ;; esac ;; hpux9* | hpux10* | hpux11*) _LT_TAGVAR(lt_prog_compiler_wl, $1)='-Wl,' # PIC is the default for IA64 HP-UX and 64-bit HP-UX, but # not for PA HP-UX. case $host_cpu in hppa*64*|ia64*) # +Z the default ;; *) _LT_TAGVAR(lt_prog_compiler_pic, $1)='+Z' ;; esac # Is there a better lt_prog_compiler_static that works with the bundled CC? _LT_TAGVAR(lt_prog_compiler_static, $1)='$wl-a ${wl}archive' ;; irix5* | irix6* | nonstopux*) _LT_TAGVAR(lt_prog_compiler_wl, $1)='-Wl,' # PIC (with -KPIC) is the default. _LT_TAGVAR(lt_prog_compiler_static, $1)='-non_shared' ;; linux* | k*bsd*-gnu | kopensolaris*-gnu | gnu*) case $cc_basename in # old Intel for x86_64, which still supported -KPIC. ecc*) _LT_TAGVAR(lt_prog_compiler_wl, $1)='-Wl,' _LT_TAGVAR(lt_prog_compiler_pic, $1)='-KPIC' _LT_TAGVAR(lt_prog_compiler_static, $1)='-static' ;; # flang / f18. f95 an alias for gfortran or flang on Debian flang* | f18* | f95*) _LT_TAGVAR(lt_prog_compiler_wl, $1)='-Wl,' _LT_TAGVAR(lt_prog_compiler_pic, $1)='-fPIC' _LT_TAGVAR(lt_prog_compiler_static, $1)='-static' ;; # icc used to be incompatible with GCC. # ICC 10 doesn't accept -KPIC any more. icc* | ifort*) _LT_TAGVAR(lt_prog_compiler_wl, $1)='-Wl,' _LT_TAGVAR(lt_prog_compiler_pic, $1)='-fPIC' _LT_TAGVAR(lt_prog_compiler_static, $1)='-static' ;; # Lahey Fortran 8.1. lf95*) _LT_TAGVAR(lt_prog_compiler_wl, $1)='-Wl,' _LT_TAGVAR(lt_prog_compiler_pic, $1)='--shared' _LT_TAGVAR(lt_prog_compiler_static, $1)='--static' ;; nagfor*) # NAG Fortran compiler _LT_TAGVAR(lt_prog_compiler_wl, $1)='-Wl,-Wl,,' _LT_TAGVAR(lt_prog_compiler_pic, $1)='-PIC' _LT_TAGVAR(lt_prog_compiler_static, $1)='-Bstatic' ;; tcc*) # Fabrice Bellard et al's Tiny C Compiler _LT_TAGVAR(lt_prog_compiler_wl, $1)='-Wl,' _LT_TAGVAR(lt_prog_compiler_pic, $1)='-fPIC' _LT_TAGVAR(lt_prog_compiler_static, $1)='-static' ;; pgcc* | pgf77* | pgf90* | pgf95* | pgfortran*) # Portland Group compilers (*not* the Pentium gcc compiler, # which looks to be a dead project) _LT_TAGVAR(lt_prog_compiler_wl, $1)='-Wl,' _LT_TAGVAR(lt_prog_compiler_pic, $1)='-fpic' _LT_TAGVAR(lt_prog_compiler_static, $1)='-Bstatic' ;; ccc*) _LT_TAGVAR(lt_prog_compiler_wl, $1)='-Wl,' # All Alpha code is PIC. _LT_TAGVAR(lt_prog_compiler_static, $1)='-non_shared' ;; xl* | bgxl* | bgf* | mpixl*) # IBM XL C 8.0/Fortran 10.1, 11.1 on PPC and BlueGene _LT_TAGVAR(lt_prog_compiler_wl, $1)='-Wl,' _LT_TAGVAR(lt_prog_compiler_pic, $1)='-qpic' _LT_TAGVAR(lt_prog_compiler_static, $1)='-qstaticlink' ;; *) case `$CC -V 2>&1 | sed 5q` in *Sun\ Ceres\ Fortran* | *Sun*Fortran*\ [[1-7]].* | *Sun*Fortran*\ 8.[[0-3]]*) # Sun Fortran 8.3 passes all unrecognized flags to the linker _LT_TAGVAR(lt_prog_compiler_pic, $1)='-KPIC' _LT_TAGVAR(lt_prog_compiler_static, $1)='-Bstatic' _LT_TAGVAR(lt_prog_compiler_wl, $1)='' ;; *Sun\ F* | *Sun*Fortran*) _LT_TAGVAR(lt_prog_compiler_pic, $1)='-KPIC' _LT_TAGVAR(lt_prog_compiler_static, $1)='-Bstatic' _LT_TAGVAR(lt_prog_compiler_wl, $1)='-Qoption ld ' ;; *Sun\ C*) # Sun C 5.9 _LT_TAGVAR(lt_prog_compiler_pic, $1)='-KPIC' _LT_TAGVAR(lt_prog_compiler_static, $1)='-Bstatic' _LT_TAGVAR(lt_prog_compiler_wl, $1)='-Wl,' ;; *Intel*\ [[CF]]*Compiler*) _LT_TAGVAR(lt_prog_compiler_wl, $1)='-Wl,' _LT_TAGVAR(lt_prog_compiler_pic, $1)='-fPIC' _LT_TAGVAR(lt_prog_compiler_static, $1)='-static' ;; *Portland\ Group*) _LT_TAGVAR(lt_prog_compiler_wl, $1)='-Wl,' _LT_TAGVAR(lt_prog_compiler_pic, $1)='-fpic' _LT_TAGVAR(lt_prog_compiler_static, $1)='-Bstatic' ;; esac ;; esac ;; newsos6) _LT_TAGVAR(lt_prog_compiler_pic, $1)='-KPIC' _LT_TAGVAR(lt_prog_compiler_static, $1)='-Bstatic' ;; *nto* | *qnx*) # QNX uses GNU C++, but need to define -shared option too, otherwise # it will coredump. _LT_TAGVAR(lt_prog_compiler_pic, $1)='-fPIC -shared' ;; osf3* | osf4* | osf5*) _LT_TAGVAR(lt_prog_compiler_wl, $1)='-Wl,' # All OSF/1 code is PIC. _LT_TAGVAR(lt_prog_compiler_static, $1)='-non_shared' ;; rdos*) _LT_TAGVAR(lt_prog_compiler_static, $1)='-non_shared' ;; solaris*) _LT_TAGVAR(lt_prog_compiler_pic, $1)='-KPIC' _LT_TAGVAR(lt_prog_compiler_static, $1)='-Bstatic' case $cc_basename in f77* | f90* | f95* | sunf77* | sunf90* | sunf95*) _LT_TAGVAR(lt_prog_compiler_wl, $1)='-Qoption ld ';; *) _LT_TAGVAR(lt_prog_compiler_wl, $1)='-Wl,';; esac ;; sunos4*) _LT_TAGVAR(lt_prog_compiler_wl, $1)='-Qoption ld ' _LT_TAGVAR(lt_prog_compiler_pic, $1)='-PIC' _LT_TAGVAR(lt_prog_compiler_static, $1)='-Bstatic' ;; sysv4 | sysv4.2uw2* | sysv4.3*) _LT_TAGVAR(lt_prog_compiler_wl, $1)='-Wl,' _LT_TAGVAR(lt_prog_compiler_pic, $1)='-KPIC' _LT_TAGVAR(lt_prog_compiler_static, $1)='-Bstatic' ;; sysv4*MP*) if test -d /usr/nec; then _LT_TAGVAR(lt_prog_compiler_pic, $1)='-Kconform_pic' _LT_TAGVAR(lt_prog_compiler_static, $1)='-Bstatic' fi ;; sysv5* | unixware* | sco3.2v5* | sco5v6* | OpenUNIX*) _LT_TAGVAR(lt_prog_compiler_wl, $1)='-Wl,' _LT_TAGVAR(lt_prog_compiler_pic, $1)='-KPIC' _LT_TAGVAR(lt_prog_compiler_static, $1)='-Bstatic' ;; unicos*) _LT_TAGVAR(lt_prog_compiler_wl, $1)='-Wl,' _LT_TAGVAR(lt_prog_compiler_can_build_shared, $1)=no ;; uts4*) _LT_TAGVAR(lt_prog_compiler_pic, $1)='-pic' _LT_TAGVAR(lt_prog_compiler_static, $1)='-Bstatic' ;; *) _LT_TAGVAR(lt_prog_compiler_can_build_shared, $1)=no ;; esac fi ]) case $host_os in # For platforms that do not support PIC, -DPIC is meaningless: *djgpp*) _LT_TAGVAR(lt_prog_compiler_pic, $1)= ;; *) _LT_TAGVAR(lt_prog_compiler_pic, $1)="$_LT_TAGVAR(lt_prog_compiler_pic, $1)@&t@m4_if([$1],[],[ -DPIC],[m4_if([$1],[CXX],[ -DPIC],[])])" ;; esac AC_CACHE_CHECK([for $compiler option to produce PIC], [_LT_TAGVAR(lt_cv_prog_compiler_pic, $1)], [_LT_TAGVAR(lt_cv_prog_compiler_pic, $1)=$_LT_TAGVAR(lt_prog_compiler_pic, $1)]) _LT_TAGVAR(lt_prog_compiler_pic, $1)=$_LT_TAGVAR(lt_cv_prog_compiler_pic, $1) # # Check to make sure the PIC flag actually works. # if test -n "$_LT_TAGVAR(lt_prog_compiler_pic, $1)"; then _LT_COMPILER_OPTION([if $compiler PIC flag $_LT_TAGVAR(lt_prog_compiler_pic, $1) works], [_LT_TAGVAR(lt_cv_prog_compiler_pic_works, $1)], [$_LT_TAGVAR(lt_prog_compiler_pic, $1)@&t@m4_if([$1],[],[ -DPIC],[m4_if([$1],[CXX],[ -DPIC],[])])], [], [case $_LT_TAGVAR(lt_prog_compiler_pic, $1) in "" | " "*) ;; *) _LT_TAGVAR(lt_prog_compiler_pic, $1)=" $_LT_TAGVAR(lt_prog_compiler_pic, $1)" ;; esac], [_LT_TAGVAR(lt_prog_compiler_pic, $1)= _LT_TAGVAR(lt_prog_compiler_can_build_shared, $1)=no]) fi _LT_TAGDECL([pic_flag], [lt_prog_compiler_pic], [1], [Additional compiler flags for building library objects]) _LT_TAGDECL([wl], [lt_prog_compiler_wl], [1], [How to pass a linker flag through the compiler]) # # Check to make sure the static flag actually works. # wl=$_LT_TAGVAR(lt_prog_compiler_wl, $1) eval lt_tmp_static_flag=\"$_LT_TAGVAR(lt_prog_compiler_static, $1)\" _LT_LINKER_OPTION([if $compiler static flag $lt_tmp_static_flag works], _LT_TAGVAR(lt_cv_prog_compiler_static_works, $1), $lt_tmp_static_flag, [], [_LT_TAGVAR(lt_prog_compiler_static, $1)=]) _LT_TAGDECL([link_static_flag], [lt_prog_compiler_static], [1], [Compiler flag to prevent dynamic linking]) ])# _LT_COMPILER_PIC # _LT_LINKER_SHLIBS([TAGNAME]) # ---------------------------- # See if the linker supports building shared libraries. m4_defun([_LT_LINKER_SHLIBS], [AC_REQUIRE([LT_PATH_LD])dnl AC_REQUIRE([LT_PATH_NM])dnl m4_require([_LT_PATH_MANIFEST_TOOL])dnl m4_require([_LT_FILEUTILS_DEFAULTS])dnl m4_require([_LT_DECL_EGREP])dnl m4_require([_LT_DECL_SED])dnl m4_require([_LT_CMD_GLOBAL_SYMBOLS])dnl m4_require([_LT_TAG_COMPILER])dnl AC_MSG_CHECKING([whether the $compiler linker ($LD) supports shared libraries]) m4_if([$1], [CXX], [ _LT_TAGVAR(export_symbols_cmds, $1)='$NM $libobjs $convenience | $global_symbol_pipe | $SED '\''s/.* //'\'' | sort | uniq > $export_symbols' _LT_TAGVAR(exclude_expsyms, $1)=['_GLOBAL_OFFSET_TABLE_|_GLOBAL__F[ID]_.*'] case $host_os in aix[[4-9]]*) # If we're using GNU nm, then we don't want the "-C" option. # -C means demangle to GNU nm, but means don't demangle to AIX nm. # Without the "-l" option, or with the "-B" option, AIX nm treats # weak defined symbols like other global defined symbols, whereas # GNU nm marks them as "W". # While the 'weak' keyword is ignored in the Export File, we need # it in the Import File for the 'aix-soname' feature, so we have # to replace the "-B" option with "-P" for AIX nm. if $NM -V 2>&1 | $GREP 'GNU' > /dev/null; then _LT_TAGVAR(export_symbols_cmds, $1)='$NM -Bpg $libobjs $convenience | awk '\''{ if (((\$ 2 == "T") || (\$ 2 == "D") || (\$ 2 == "B") || (\$ 2 == "W")) && ([substr](\$ 3,1,1) != ".")) { if (\$ 2 == "W") { print \$ 3 " weak" } else { print \$ 3 } } }'\'' | sort -u > $export_symbols' else _LT_TAGVAR(export_symbols_cmds, $1)='`func_echo_all $NM | $SED -e '\''s/B\([[^B]]*\)$/P\1/'\''` -PCpgl $libobjs $convenience | awk '\''{ if (((\$ 2 == "T") || (\$ 2 == "D") || (\$ 2 == "B") || (\$ 2 == "W") || (\$ 2 == "V") || (\$ 2 == "Z")) && ([substr](\$ 1,1,1) != ".")) { if ((\$ 2 == "W") || (\$ 2 == "V") || (\$ 2 == "Z")) { print \$ 1 " weak" } else { print \$ 1 } } }'\'' | sort -u > $export_symbols' fi ;; pw32*) _LT_TAGVAR(export_symbols_cmds, $1)=$ltdll_cmds ;; cygwin* | mingw* | cegcc*) case $cc_basename in cl*) _LT_TAGVAR(exclude_expsyms, $1)='_NULL_IMPORT_DESCRIPTOR|_IMPORT_DESCRIPTOR_.*' ;; *) _LT_TAGVAR(export_symbols_cmds, $1)='$NM $libobjs $convenience | $global_symbol_pipe | $SED -e '\''/^[[BCDGRS]][[ ]]/s/.*[[ ]]\([[^ ]]*\)/\1 DATA/;s/^.*[[ ]]__nm__\([[^ ]]*\)[[ ]][[^ ]]*/\1 DATA/;/^I[[ ]]/d;/^[[AITW]][[ ]]/s/.* //'\'' | sort | uniq > $export_symbols' _LT_TAGVAR(exclude_expsyms, $1)=['[_]+GLOBAL_OFFSET_TABLE_|[_]+GLOBAL__[FID]_.*|[_]+head_[A-Za-z0-9_]+_dll|[A-Za-z0-9_]+_dll_iname'] ;; esac ;; linux* | k*bsd*-gnu | gnu*) _LT_TAGVAR(link_all_deplibs, $1)=no ;; *) _LT_TAGVAR(export_symbols_cmds, $1)='$NM $libobjs $convenience | $global_symbol_pipe | $SED '\''s/.* //'\'' | sort | uniq > $export_symbols' ;; esac ], [ runpath_var= _LT_TAGVAR(allow_undefined_flag, $1)= _LT_TAGVAR(always_export_symbols, $1)=no _LT_TAGVAR(archive_cmds, $1)= _LT_TAGVAR(archive_expsym_cmds, $1)= _LT_TAGVAR(compiler_needs_object, $1)=no _LT_TAGVAR(enable_shared_with_static_runtimes, $1)=no _LT_TAGVAR(export_dynamic_flag_spec, $1)= _LT_TAGVAR(export_symbols_cmds, $1)='$NM $libobjs $convenience | $global_symbol_pipe | $SED '\''s/.* //'\'' | sort | uniq > $export_symbols' _LT_TAGVAR(hardcode_automatic, $1)=no _LT_TAGVAR(hardcode_direct, $1)=no _LT_TAGVAR(hardcode_direct_absolute, $1)=no _LT_TAGVAR(hardcode_libdir_flag_spec, $1)= _LT_TAGVAR(hardcode_libdir_separator, $1)= _LT_TAGVAR(hardcode_minus_L, $1)=no _LT_TAGVAR(hardcode_shlibpath_var, $1)=unsupported _LT_TAGVAR(inherit_rpath, $1)=no _LT_TAGVAR(link_all_deplibs, $1)=unknown _LT_TAGVAR(module_cmds, $1)= _LT_TAGVAR(module_expsym_cmds, $1)= _LT_TAGVAR(old_archive_from_new_cmds, $1)= _LT_TAGVAR(old_archive_from_expsyms_cmds, $1)= _LT_TAGVAR(thread_safe_flag_spec, $1)= _LT_TAGVAR(whole_archive_flag_spec, $1)= # include_expsyms should be a list of space-separated symbols to be *always* # included in the symbol list _LT_TAGVAR(include_expsyms, $1)= # exclude_expsyms can be an extended regexp of symbols to exclude # it will be wrapped by ' (' and ')$', so one must not match beginning or # end of line. Example: 'a|bc|.*d.*' will exclude the symbols 'a' and 'bc', # as well as any symbol that contains 'd'. _LT_TAGVAR(exclude_expsyms, $1)=['_GLOBAL_OFFSET_TABLE_|_GLOBAL__F[ID]_.*'] # Although _GLOBAL_OFFSET_TABLE_ is a valid symbol C name, most a.out # platforms (ab)use it in PIC code, but their linkers get confused if # the symbol is explicitly referenced. Since portable code cannot # rely on this symbol name, it's probably fine to never include it in # preloaded symbol tables. # Exclude shared library initialization/finalization symbols. dnl Note also adjust exclude_expsyms for C++ above. extract_expsyms_cmds= case $host_os in cygwin* | mingw* | pw32* | cegcc*) # FIXME: the MSVC++ port hasn't been tested in a loooong time # When not using gcc, we currently assume that we are using # Microsoft Visual C++. if test yes != "$GCC"; then with_gnu_ld=no fi ;; interix*) # we just hope/assume this is gcc and not c89 (= MSVC++) with_gnu_ld=yes ;; openbsd* | bitrig*) with_gnu_ld=no ;; linux* | k*bsd*-gnu | gnu*) _LT_TAGVAR(link_all_deplibs, $1)=no ;; esac _LT_TAGVAR(ld_shlibs, $1)=yes # On some targets, GNU ld is compatible enough with the native linker # that we're better off using the native interface for both. lt_use_gnu_ld_interface=no if test yes = "$with_gnu_ld"; then case $host_os in aix*) # The AIX port of GNU ld has always aspired to compatibility # with the native linker. However, as the warning in the GNU ld # block says, versions before 2.19.5* couldn't really create working # shared libraries, regardless of the interface used. case `$LD -v 2>&1` in *\ \(GNU\ Binutils\)\ 2.19.5*) ;; *\ \(GNU\ Binutils\)\ 2.[[2-9]]*) ;; *\ \(GNU\ Binutils\)\ [[3-9]]*) ;; *) lt_use_gnu_ld_interface=yes ;; esac ;; *) lt_use_gnu_ld_interface=yes ;; esac fi if test yes = "$lt_use_gnu_ld_interface"; then # If archive_cmds runs LD, not CC, wlarc should be empty wlarc='$wl' # Set some defaults for GNU ld with shared library support. These # are reset later if shared libraries are not supported. Putting them # here allows them to be overridden if necessary. runpath_var=LD_RUN_PATH _LT_TAGVAR(hardcode_libdir_flag_spec, $1)='$wl-rpath $wl$libdir' _LT_TAGVAR(export_dynamic_flag_spec, $1)='$wl--export-dynamic' # ancient GNU ld didn't support --whole-archive et. al. if $LD --help 2>&1 | $GREP 'no-whole-archive' > /dev/null; then _LT_TAGVAR(whole_archive_flag_spec, $1)=$wlarc'--whole-archive$convenience '$wlarc'--no-whole-archive' else _LT_TAGVAR(whole_archive_flag_spec, $1)= fi supports_anon_versioning=no case `$LD -v | $SED -e 's/([^)]\+)\s\+//' 2>&1` in *GNU\ gold*) supports_anon_versioning=yes ;; *\ [[01]].* | *\ 2.[[0-9]].* | *\ 2.10.*) ;; # catch versions < 2.11 *\ 2.11.93.0.2\ *) supports_anon_versioning=yes ;; # RH7.3 ... *\ 2.11.92.0.12\ *) supports_anon_versioning=yes ;; # Mandrake 8.2 ... *\ 2.11.*) ;; # other 2.11 versions *) supports_anon_versioning=yes ;; esac # See if GNU ld supports shared libraries. case $host_os in aix[[3-9]]*) # On AIX/PPC, the GNU linker is very broken if test ia64 != "$host_cpu"; then _LT_TAGVAR(ld_shlibs, $1)=no cat <<_LT_EOF 1>&2 *** Warning: the GNU linker, at least up to release 2.19, is reported *** to be unable to reliably create shared libraries on AIX. *** Therefore, libtool is disabling shared libraries support. If you *** really care for shared libraries, you may want to install binutils *** 2.20 or above, or modify your PATH so that a non-GNU linker is found. *** You will then need to restart the configuration process. _LT_EOF fi ;; amigaos*) case $host_cpu in powerpc) # see comment about AmigaOS4 .so support _LT_TAGVAR(archive_cmds, $1)='$CC -shared $libobjs $deplibs $compiler_flags $wl-soname $wl$soname -o $lib' _LT_TAGVAR(archive_expsym_cmds, $1)='' ;; m68k) _LT_TAGVAR(archive_cmds, $1)='$RM $output_objdir/a2ixlibrary.data~$ECHO "#define NAME $libname" > $output_objdir/a2ixlibrary.data~$ECHO "#define LIBRARY_ID 1" >> $output_objdir/a2ixlibrary.data~$ECHO "#define VERSION $major" >> $output_objdir/a2ixlibrary.data~$ECHO "#define REVISION $revision" >> $output_objdir/a2ixlibrary.data~$AR $AR_FLAGS $lib $libobjs~$RANLIB $lib~(cd $output_objdir && a2ixlibrary -32)' _LT_TAGVAR(hardcode_libdir_flag_spec, $1)='-L$libdir' _LT_TAGVAR(hardcode_minus_L, $1)=yes ;; esac ;; beos*) if $LD --help 2>&1 | $GREP ': supported targets:.* elf' > /dev/null; then _LT_TAGVAR(allow_undefined_flag, $1)=unsupported # Joseph Beckenbach says some releases of gcc # support --undefined. This deserves some investigation. FIXME _LT_TAGVAR(archive_cmds, $1)='$CC -nostart $libobjs $deplibs $compiler_flags $wl-soname $wl$soname -o $lib' else _LT_TAGVAR(ld_shlibs, $1)=no fi ;; cygwin* | mingw* | pw32* | cegcc*) # _LT_TAGVAR(hardcode_libdir_flag_spec, $1) is actually meaningless, # as there is no search path for DLLs. _LT_TAGVAR(hardcode_libdir_flag_spec, $1)='-L$libdir' _LT_TAGVAR(export_dynamic_flag_spec, $1)='$wl--export-all-symbols' _LT_TAGVAR(allow_undefined_flag, $1)=unsupported _LT_TAGVAR(always_export_symbols, $1)=no _LT_TAGVAR(enable_shared_with_static_runtimes, $1)=yes _LT_TAGVAR(export_symbols_cmds, $1)='$NM $libobjs $convenience | $global_symbol_pipe | $SED -e '\''/^[[BCDGRS]][[ ]]/s/.*[[ ]]\([[^ ]]*\)/\1 DATA/;s/^.*[[ ]]__nm__\([[^ ]]*\)[[ ]][[^ ]]*/\1 DATA/;/^I[[ ]]/d;/^[[AITW]][[ ]]/s/.* //'\'' | sort | uniq > $export_symbols' _LT_TAGVAR(exclude_expsyms, $1)=['[_]+GLOBAL_OFFSET_TABLE_|[_]+GLOBAL__[FID]_.*|[_]+head_[A-Za-z0-9_]+_dll|[A-Za-z0-9_]+_dll_iname'] if $LD --help 2>&1 | $GREP 'auto-import' > /dev/null; then _LT_TAGVAR(archive_cmds, $1)='$CC -shared $libobjs $deplibs $compiler_flags -o $output_objdir/$soname $wl--enable-auto-image-base -Xlinker --out-implib -Xlinker $lib' # If the export-symbols file already is a .def file, use it as # is; otherwise, prepend EXPORTS... _LT_TAGVAR(archive_expsym_cmds, $1)='if _LT_DLL_DEF_P([$export_symbols]); then cp $export_symbols $output_objdir/$soname.def; else echo EXPORTS > $output_objdir/$soname.def; cat $export_symbols >> $output_objdir/$soname.def; fi~ $CC -shared $output_objdir/$soname.def $libobjs $deplibs $compiler_flags -o $output_objdir/$soname $wl--enable-auto-image-base -Xlinker --out-implib -Xlinker $lib' else _LT_TAGVAR(ld_shlibs, $1)=no fi ;; haiku*) _LT_TAGVAR(archive_cmds, $1)='$CC -shared $libobjs $deplibs $compiler_flags $wl-soname $wl$soname -o $lib' _LT_TAGVAR(link_all_deplibs, $1)=yes ;; os2*) _LT_TAGVAR(hardcode_libdir_flag_spec, $1)='-L$libdir' _LT_TAGVAR(hardcode_minus_L, $1)=yes _LT_TAGVAR(allow_undefined_flag, $1)=unsupported shrext_cmds=.dll _LT_TAGVAR(archive_cmds, $1)='$ECHO "LIBRARY ${soname%$shared_ext} INITINSTANCE TERMINSTANCE" > $output_objdir/$libname.def~ $ECHO "DESCRIPTION \"$libname\"" >> $output_objdir/$libname.def~ $ECHO "DATA MULTIPLE NONSHARED" >> $output_objdir/$libname.def~ $ECHO EXPORTS >> $output_objdir/$libname.def~ emxexp $libobjs | $SED /"_DLL_InitTerm"/d >> $output_objdir/$libname.def~ $CC -Zdll -Zcrtdll -o $output_objdir/$soname $libobjs $deplibs $compiler_flags $output_objdir/$libname.def~ emximp -o $lib $output_objdir/$libname.def' _LT_TAGVAR(archive_expsym_cmds, $1)='$ECHO "LIBRARY ${soname%$shared_ext} INITINSTANCE TERMINSTANCE" > $output_objdir/$libname.def~ $ECHO "DESCRIPTION \"$libname\"" >> $output_objdir/$libname.def~ $ECHO "DATA MULTIPLE NONSHARED" >> $output_objdir/$libname.def~ $ECHO EXPORTS >> $output_objdir/$libname.def~ prefix_cmds="$SED"~ if test EXPORTS = "`$SED 1q $export_symbols`"; then prefix_cmds="$prefix_cmds -e 1d"; fi~ prefix_cmds="$prefix_cmds -e \"s/^\(.*\)$/_\1/g\""~ cat $export_symbols | $prefix_cmds >> $output_objdir/$libname.def~ $CC -Zdll -Zcrtdll -o $output_objdir/$soname $libobjs $deplibs $compiler_flags $output_objdir/$libname.def~ emximp -o $lib $output_objdir/$libname.def' _LT_TAGVAR(old_archive_From_new_cmds, $1)='emximp -o $output_objdir/${libname}_dll.a $output_objdir/$libname.def' _LT_TAGVAR(enable_shared_with_static_runtimes, $1)=yes ;; interix[[3-9]]*) _LT_TAGVAR(hardcode_direct, $1)=no _LT_TAGVAR(hardcode_shlibpath_var, $1)=no _LT_TAGVAR(hardcode_libdir_flag_spec, $1)='$wl-rpath,$libdir' _LT_TAGVAR(export_dynamic_flag_spec, $1)='$wl-E' # Hack: On Interix 3.x, we cannot compile PIC because of a broken gcc. # Instead, shared libraries are loaded at an image base (0x10000000 by # default) and relocated if they conflict, which is a slow very memory # consuming and fragmenting process. To avoid this, we pick a random, # 256 KiB-aligned image base between 0x50000000 and 0x6FFC0000 at link # time. Moving up from 0x10000000 also allows more sbrk(2) space. _LT_TAGVAR(archive_cmds, $1)='$CC -shared $pic_flag $libobjs $deplibs $compiler_flags $wl-h,$soname $wl--image-base,`expr ${RANDOM-$$} % 4096 / 2 \* 262144 + 1342177280` -o $lib' _LT_TAGVAR(archive_expsym_cmds, $1)='sed "s|^|_|" $export_symbols >$output_objdir/$soname.expsym~$CC -shared $pic_flag $libobjs $deplibs $compiler_flags $wl-h,$soname $wl--retain-symbols-file,$output_objdir/$soname.expsym $wl--image-base,`expr ${RANDOM-$$} % 4096 / 2 \* 262144 + 1342177280` -o $lib' ;; gnu* | linux* | tpf* | k*bsd*-gnu | kopensolaris*-gnu) tmp_diet=no if test linux-dietlibc = "$host_os"; then case $cc_basename in diet\ *) tmp_diet=yes;; # linux-dietlibc with static linking (!diet-dyn) esac fi if $LD --help 2>&1 | $EGREP ': supported targets:.* elf' > /dev/null \ && test no = "$tmp_diet" then tmp_addflag=' $pic_flag' tmp_sharedflag='-shared' case $cc_basename,$host_cpu in pgcc*) # Portland Group C compiler _LT_TAGVAR(whole_archive_flag_spec, $1)='$wl--whole-archive`for conv in $convenience\"\"; do test -n \"$conv\" && new_convenience=\"$new_convenience,$conv\"; done; func_echo_all \"$new_convenience\"` $wl--no-whole-archive' tmp_addflag=' $pic_flag' ;; pgf77* | pgf90* | pgf95* | pgfortran*) # Portland Group f77 and f90 compilers _LT_TAGVAR(whole_archive_flag_spec, $1)='$wl--whole-archive`for conv in $convenience\"\"; do test -n \"$conv\" && new_convenience=\"$new_convenience,$conv\"; done; func_echo_all \"$new_convenience\"` $wl--no-whole-archive' tmp_addflag=' $pic_flag -Mnomain' ;; ecc*,ia64* | icc*,ia64*) # Intel C compiler on ia64 tmp_addflag=' -i_dynamic' ;; efc*,ia64* | ifort*,ia64*) # Intel Fortran compiler on ia64 tmp_addflag=' -i_dynamic -nofor_main' ;; ifc* | ifort*) # Intel Fortran compiler tmp_addflag=' -nofor_main' ;; lf95*) # Lahey Fortran 8.1 _LT_TAGVAR(whole_archive_flag_spec, $1)= tmp_sharedflag='--shared' ;; nagfor*) # NAGFOR 5.3 tmp_sharedflag='-Wl,-shared' ;; xl[[cC]]* | bgxl[[cC]]* | mpixl[[cC]]*) # IBM XL C 8.0 on PPC (deal with xlf below) tmp_sharedflag='-qmkshrobj' tmp_addflag= ;; nvcc*) # Cuda Compiler Driver 2.2 _LT_TAGVAR(whole_archive_flag_spec, $1)='$wl--whole-archive`for conv in $convenience\"\"; do test -n \"$conv\" && new_convenience=\"$new_convenience,$conv\"; done; func_echo_all \"$new_convenience\"` $wl--no-whole-archive' _LT_TAGVAR(compiler_needs_object, $1)=yes ;; esac case `$CC -V 2>&1 | sed 5q` in *Sun\ C*) # Sun C 5.9 _LT_TAGVAR(whole_archive_flag_spec, $1)='$wl--whole-archive`new_convenience=; for conv in $convenience\"\"; do test -z \"$conv\" || new_convenience=\"$new_convenience,$conv\"; done; func_echo_all \"$new_convenience\"` $wl--no-whole-archive' _LT_TAGVAR(compiler_needs_object, $1)=yes tmp_sharedflag='-G' ;; *Sun\ F*) # Sun Fortran 8.3 tmp_sharedflag='-G' ;; esac _LT_TAGVAR(archive_cmds, $1)='$CC '"$tmp_sharedflag""$tmp_addflag"' $libobjs $deplibs $compiler_flags $wl-soname $wl$soname -o $lib' if test yes = "$supports_anon_versioning"; then _LT_TAGVAR(archive_expsym_cmds, $1)='echo "{ global:" > $output_objdir/$libname.ver~ cat $export_symbols | sed -e "s/\(.*\)/\1;/" >> $output_objdir/$libname.ver~ echo "local: *; };" >> $output_objdir/$libname.ver~ $CC '"$tmp_sharedflag""$tmp_addflag"' $libobjs $deplibs $compiler_flags $wl-soname $wl$soname $wl-version-script $wl$output_objdir/$libname.ver -o $lib' fi case $cc_basename in tcc*) _LT_TAGVAR(export_dynamic_flag_spec, $1)='-rdynamic' ;; xlf* | bgf* | bgxlf* | mpixlf*) # IBM XL Fortran 10.1 on PPC cannot create shared libs itself _LT_TAGVAR(whole_archive_flag_spec, $1)='--whole-archive$convenience --no-whole-archive' _LT_TAGVAR(hardcode_libdir_flag_spec, $1)='$wl-rpath $wl$libdir' _LT_TAGVAR(archive_cmds, $1)='$LD -shared $libobjs $deplibs $linker_flags -soname $soname -o $lib' if test yes = "$supports_anon_versioning"; then _LT_TAGVAR(archive_expsym_cmds, $1)='echo "{ global:" > $output_objdir/$libname.ver~ cat $export_symbols | sed -e "s/\(.*\)/\1;/" >> $output_objdir/$libname.ver~ echo "local: *; };" >> $output_objdir/$libname.ver~ $LD -shared $libobjs $deplibs $linker_flags -soname $soname -version-script $output_objdir/$libname.ver -o $lib' fi ;; esac else _LT_TAGVAR(ld_shlibs, $1)=no fi ;; netbsd* | netbsdelf*-gnu) if echo __ELF__ | $CC -E - | $GREP __ELF__ >/dev/null; then _LT_TAGVAR(archive_cmds, $1)='$LD -Bshareable $libobjs $deplibs $linker_flags -o $lib' wlarc= else _LT_TAGVAR(archive_cmds, $1)='$CC -shared $pic_flag $libobjs $deplibs $compiler_flags $wl-soname $wl$soname -o $lib' _LT_TAGVAR(archive_expsym_cmds, $1)='$CC -shared $pic_flag $libobjs $deplibs $compiler_flags $wl-soname $wl$soname $wl-retain-symbols-file $wl$export_symbols -o $lib' fi ;; solaris*) if $LD -v 2>&1 | $GREP 'BFD 2\.8' > /dev/null; then _LT_TAGVAR(ld_shlibs, $1)=no cat <<_LT_EOF 1>&2 *** Warning: The releases 2.8.* of the GNU linker cannot reliably *** create shared libraries on Solaris systems. Therefore, libtool *** is disabling shared libraries support. We urge you to upgrade GNU *** binutils to release 2.9.1 or newer. Another option is to modify *** your PATH or compiler configuration so that the native linker is *** used, and then restart. _LT_EOF elif $LD --help 2>&1 | $GREP ': supported targets:.* elf' > /dev/null; then _LT_TAGVAR(archive_cmds, $1)='$CC -shared $pic_flag $libobjs $deplibs $compiler_flags $wl-soname $wl$soname -o $lib' _LT_TAGVAR(archive_expsym_cmds, $1)='$CC -shared $pic_flag $libobjs $deplibs $compiler_flags $wl-soname $wl$soname $wl-retain-symbols-file $wl$export_symbols -o $lib' else _LT_TAGVAR(ld_shlibs, $1)=no fi ;; sysv5* | sco3.2v5* | sco5v6* | unixware* | OpenUNIX*) case `$LD -v 2>&1` in *\ [[01]].* | *\ 2.[[0-9]].* | *\ 2.1[[0-5]].*) _LT_TAGVAR(ld_shlibs, $1)=no cat <<_LT_EOF 1>&2 *** Warning: Releases of the GNU linker prior to 2.16.91.0.3 cannot *** reliably create shared libraries on SCO systems. Therefore, libtool *** is disabling shared libraries support. We urge you to upgrade GNU *** binutils to release 2.16.91.0.3 or newer. Another option is to modify *** your PATH or compiler configuration so that the native linker is *** used, and then restart. _LT_EOF ;; *) # For security reasons, it is highly recommended that you always # use absolute paths for naming shared libraries, and exclude the # DT_RUNPATH tag from executables and libraries. But doing so # requires that you compile everything twice, which is a pain. if $LD --help 2>&1 | $GREP ': supported targets:.* elf' > /dev/null; then _LT_TAGVAR(hardcode_libdir_flag_spec, $1)='$wl-rpath $wl$libdir' _LT_TAGVAR(archive_cmds, $1)='$CC -shared $libobjs $deplibs $compiler_flags $wl-soname $wl$soname -o $lib' _LT_TAGVAR(archive_expsym_cmds, $1)='$CC -shared $libobjs $deplibs $compiler_flags $wl-soname $wl$soname $wl-retain-symbols-file $wl$export_symbols -o $lib' else _LT_TAGVAR(ld_shlibs, $1)=no fi ;; esac ;; sunos4*) _LT_TAGVAR(archive_cmds, $1)='$LD -assert pure-text -Bshareable -o $lib $libobjs $deplibs $linker_flags' wlarc= _LT_TAGVAR(hardcode_direct, $1)=yes _LT_TAGVAR(hardcode_shlibpath_var, $1)=no ;; *) if $LD --help 2>&1 | $GREP ': supported targets:.* elf' > /dev/null; then _LT_TAGVAR(archive_cmds, $1)='$CC -shared $pic_flag $libobjs $deplibs $compiler_flags $wl-soname $wl$soname -o $lib' _LT_TAGVAR(archive_expsym_cmds, $1)='$CC -shared $pic_flag $libobjs $deplibs $compiler_flags $wl-soname $wl$soname $wl-retain-symbols-file $wl$export_symbols -o $lib' else _LT_TAGVAR(ld_shlibs, $1)=no fi ;; esac if test no = "$_LT_TAGVAR(ld_shlibs, $1)"; then runpath_var= _LT_TAGVAR(hardcode_libdir_flag_spec, $1)= _LT_TAGVAR(export_dynamic_flag_spec, $1)= _LT_TAGVAR(whole_archive_flag_spec, $1)= fi else # PORTME fill in a description of your system's linker (not GNU ld) case $host_os in aix3*) _LT_TAGVAR(allow_undefined_flag, $1)=unsupported _LT_TAGVAR(always_export_symbols, $1)=yes _LT_TAGVAR(archive_expsym_cmds, $1)='$LD -o $output_objdir/$soname $libobjs $deplibs $linker_flags -bE:$export_symbols -T512 -H512 -bM:SRE~$AR $AR_FLAGS $lib $output_objdir/$soname' # Note: this linker hardcodes the directories in LIBPATH if there # are no directories specified by -L. _LT_TAGVAR(hardcode_minus_L, $1)=yes if test yes = "$GCC" && test -z "$lt_prog_compiler_static"; then # Neither direct hardcoding nor static linking is supported with a # broken collect2. _LT_TAGVAR(hardcode_direct, $1)=unsupported fi ;; aix[[4-9]]*) if test ia64 = "$host_cpu"; then # On IA64, the linker does run time linking by default, so we don't # have to do anything special. aix_use_runtimelinking=no exp_sym_flag='-Bexport' no_entry_flag= else # If we're using GNU nm, then we don't want the "-C" option. # -C means demangle to GNU nm, but means don't demangle to AIX nm. # Without the "-l" option, or with the "-B" option, AIX nm treats # weak defined symbols like other global defined symbols, whereas # GNU nm marks them as "W". # While the 'weak' keyword is ignored in the Export File, we need # it in the Import File for the 'aix-soname' feature, so we have # to replace the "-B" option with "-P" for AIX nm. if $NM -V 2>&1 | $GREP 'GNU' > /dev/null; then _LT_TAGVAR(export_symbols_cmds, $1)='$NM -Bpg $libobjs $convenience | awk '\''{ if (((\$ 2 == "T") || (\$ 2 == "D") || (\$ 2 == "B") || (\$ 2 == "W")) && ([substr](\$ 3,1,1) != ".")) { if (\$ 2 == "W") { print \$ 3 " weak" } else { print \$ 3 } } }'\'' | sort -u > $export_symbols' else _LT_TAGVAR(export_symbols_cmds, $1)='`func_echo_all $NM | $SED -e '\''s/B\([[^B]]*\)$/P\1/'\''` -PCpgl $libobjs $convenience | awk '\''{ if (((\$ 2 == "T") || (\$ 2 == "D") || (\$ 2 == "B") || (\$ 2 == "W") || (\$ 2 == "V") || (\$ 2 == "Z")) && ([substr](\$ 1,1,1) != ".")) { if ((\$ 2 == "W") || (\$ 2 == "V") || (\$ 2 == "Z")) { print \$ 1 " weak" } else { print \$ 1 } } }'\'' | sort -u > $export_symbols' fi aix_use_runtimelinking=no # Test if we are trying to use run time linking or normal # AIX style linking. If -brtl is somewhere in LDFLAGS, we # have runtime linking enabled, and use it for executables. # For shared libraries, we enable/disable runtime linking # depending on the kind of the shared library created - # when "with_aix_soname,aix_use_runtimelinking" is: # "aix,no" lib.a(lib.so.V) shared, rtl:no, for executables # "aix,yes" lib.so shared, rtl:yes, for executables # lib.a static archive # "both,no" lib.so.V(shr.o) shared, rtl:yes # lib.a(lib.so.V) shared, rtl:no, for executables # "both,yes" lib.so.V(shr.o) shared, rtl:yes, for executables # lib.a(lib.so.V) shared, rtl:no # "svr4,*" lib.so.V(shr.o) shared, rtl:yes, for executables # lib.a static archive case $host_os in aix4.[[23]]|aix4.[[23]].*|aix[[5-9]]*) for ld_flag in $LDFLAGS; do if (test x-brtl = "x$ld_flag" || test x-Wl,-brtl = "x$ld_flag"); then aix_use_runtimelinking=yes break fi done if test svr4,no = "$with_aix_soname,$aix_use_runtimelinking"; then # With aix-soname=svr4, we create the lib.so.V shared archives only, # so we don't have lib.a shared libs to link our executables. # We have to force runtime linking in this case. aix_use_runtimelinking=yes LDFLAGS="$LDFLAGS -Wl,-brtl" fi ;; esac exp_sym_flag='-bexport' no_entry_flag='-bnoentry' fi # When large executables or shared objects are built, AIX ld can # have problems creating the table of contents. If linking a library # or program results in "error TOC overflow" add -mminimal-toc to # CXXFLAGS/CFLAGS for g++/gcc. In the cases where that is not # enough to fix the problem, add -Wl,-bbigtoc to LDFLAGS. _LT_TAGVAR(archive_cmds, $1)='' _LT_TAGVAR(hardcode_direct, $1)=yes _LT_TAGVAR(hardcode_direct_absolute, $1)=yes _LT_TAGVAR(hardcode_libdir_separator, $1)=':' _LT_TAGVAR(link_all_deplibs, $1)=yes _LT_TAGVAR(file_list_spec, $1)='$wl-f,' case $with_aix_soname,$aix_use_runtimelinking in aix,*) ;; # traditional, no import file svr4,* | *,yes) # use import file # The Import File defines what to hardcode. _LT_TAGVAR(hardcode_direct, $1)=no _LT_TAGVAR(hardcode_direct_absolute, $1)=no ;; esac if test yes = "$GCC"; then case $host_os in aix4.[[012]]|aix4.[[012]].*) # We only want to do this on AIX 4.2 and lower, the check # below for broken collect2 doesn't work under 4.3+ collect2name=`$CC -print-prog-name=collect2` if test -f "$collect2name" && strings "$collect2name" | $GREP resolve_lib_name >/dev/null then # We have reworked collect2 : else # We have old collect2 _LT_TAGVAR(hardcode_direct, $1)=unsupported # It fails to find uninstalled libraries when the uninstalled # path is not listed in the libpath. Setting hardcode_minus_L # to unsupported forces relinking _LT_TAGVAR(hardcode_minus_L, $1)=yes _LT_TAGVAR(hardcode_libdir_flag_spec, $1)='-L$libdir' _LT_TAGVAR(hardcode_libdir_separator, $1)= fi ;; esac shared_flag='-shared' if test yes = "$aix_use_runtimelinking"; then shared_flag="$shared_flag "'$wl-G' fi # Need to ensure runtime linking is disabled for the traditional # shared library, or the linker may eventually find shared libraries # /with/ Import File - we do not want to mix them. shared_flag_aix='-shared' shared_flag_svr4='-shared $wl-G' else # not using gcc if test ia64 = "$host_cpu"; then # VisualAge C++, Version 5.5 for AIX 5L for IA-64, Beta 3 Release # chokes on -Wl,-G. The following line is correct: shared_flag='-G' else if test yes = "$aix_use_runtimelinking"; then shared_flag='$wl-G' else shared_flag='$wl-bM:SRE' fi shared_flag_aix='$wl-bM:SRE' shared_flag_svr4='$wl-G' fi fi _LT_TAGVAR(export_dynamic_flag_spec, $1)='$wl-bexpall' # It seems that -bexpall does not export symbols beginning with # underscore (_), so it is better to generate a list of symbols to export. _LT_TAGVAR(always_export_symbols, $1)=yes if test aix,yes = "$with_aix_soname,$aix_use_runtimelinking"; then # Warning - without using the other runtime loading flags (-brtl), # -berok will link without error, but may produce a broken library. _LT_TAGVAR(allow_undefined_flag, $1)='-berok' # Determine the default libpath from the value encoded in an # empty executable. _LT_SYS_MODULE_PATH_AIX([$1]) _LT_TAGVAR(hardcode_libdir_flag_spec, $1)='$wl-blibpath:$libdir:'"$aix_libpath" _LT_TAGVAR(archive_expsym_cmds, $1)='$CC -o $output_objdir/$soname $libobjs $deplibs $wl'$no_entry_flag' $compiler_flags `if test -n "$allow_undefined_flag"; then func_echo_all "$wl$allow_undefined_flag"; else :; fi` $wl'$exp_sym_flag:\$export_symbols' '$shared_flag else if test ia64 = "$host_cpu"; then _LT_TAGVAR(hardcode_libdir_flag_spec, $1)='$wl-R $libdir:/usr/lib:/lib' _LT_TAGVAR(allow_undefined_flag, $1)="-z nodefs" _LT_TAGVAR(archive_expsym_cmds, $1)="\$CC $shared_flag"' -o $output_objdir/$soname $libobjs $deplibs '"\$wl$no_entry_flag"' $compiler_flags $wl$allow_undefined_flag '"\$wl$exp_sym_flag:\$export_symbols" else # Determine the default libpath from the value encoded in an # empty executable. _LT_SYS_MODULE_PATH_AIX([$1]) _LT_TAGVAR(hardcode_libdir_flag_spec, $1)='$wl-blibpath:$libdir:'"$aix_libpath" # Warning - without using the other run time loading flags, # -berok will link without error, but may produce a broken library. _LT_TAGVAR(no_undefined_flag, $1)=' $wl-bernotok' _LT_TAGVAR(allow_undefined_flag, $1)=' $wl-berok' if test yes = "$with_gnu_ld"; then # We only use this code for GNU lds that support --whole-archive. _LT_TAGVAR(whole_archive_flag_spec, $1)='$wl--whole-archive$convenience $wl--no-whole-archive' else # Exported symbols can be pulled into shared objects from archives _LT_TAGVAR(whole_archive_flag_spec, $1)='$convenience' fi _LT_TAGVAR(archive_cmds_need_lc, $1)=yes _LT_TAGVAR(archive_expsym_cmds, $1)='$RM -r $output_objdir/$realname.d~$MKDIR $output_objdir/$realname.d' # -brtl affects multiple linker settings, -berok does not and is overridden later compiler_flags_filtered='`func_echo_all "$compiler_flags " | $SED -e "s%-brtl\\([[, ]]\\)%-berok\\1%g"`' if test svr4 != "$with_aix_soname"; then # This is similar to how AIX traditionally builds its shared libraries. _LT_TAGVAR(archive_expsym_cmds, $1)="$_LT_TAGVAR(archive_expsym_cmds, $1)"'~$CC '$shared_flag_aix' -o $output_objdir/$realname.d/$soname $libobjs $deplibs $wl-bnoentry '$compiler_flags_filtered'$wl-bE:$export_symbols$allow_undefined_flag~$AR $AR_FLAGS $output_objdir/$libname$release.a $output_objdir/$realname.d/$soname' fi if test aix != "$with_aix_soname"; then _LT_TAGVAR(archive_expsym_cmds, $1)="$_LT_TAGVAR(archive_expsym_cmds, $1)"'~$CC '$shared_flag_svr4' -o $output_objdir/$realname.d/$shared_archive_member_spec.o $libobjs $deplibs $wl-bnoentry '$compiler_flags_filtered'$wl-bE:$export_symbols$allow_undefined_flag~$STRIP -e $output_objdir/$realname.d/$shared_archive_member_spec.o~( func_echo_all "#! $soname($shared_archive_member_spec.o)"; if test shr_64 = "$shared_archive_member_spec"; then func_echo_all "# 64"; else func_echo_all "# 32"; fi; cat $export_symbols ) > $output_objdir/$realname.d/$shared_archive_member_spec.imp~$AR $AR_FLAGS $output_objdir/$soname $output_objdir/$realname.d/$shared_archive_member_spec.o $output_objdir/$realname.d/$shared_archive_member_spec.imp' else # used by -dlpreopen to get the symbols _LT_TAGVAR(archive_expsym_cmds, $1)="$_LT_TAGVAR(archive_expsym_cmds, $1)"'~$MV $output_objdir/$realname.d/$soname $output_objdir' fi _LT_TAGVAR(archive_expsym_cmds, $1)="$_LT_TAGVAR(archive_expsym_cmds, $1)"'~$RM -r $output_objdir/$realname.d' fi fi ;; amigaos*) case $host_cpu in powerpc) # see comment about AmigaOS4 .so support _LT_TAGVAR(archive_cmds, $1)='$CC -shared $libobjs $deplibs $compiler_flags $wl-soname $wl$soname -o $lib' _LT_TAGVAR(archive_expsym_cmds, $1)='' ;; m68k) _LT_TAGVAR(archive_cmds, $1)='$RM $output_objdir/a2ixlibrary.data~$ECHO "#define NAME $libname" > $output_objdir/a2ixlibrary.data~$ECHO "#define LIBRARY_ID 1" >> $output_objdir/a2ixlibrary.data~$ECHO "#define VERSION $major" >> $output_objdir/a2ixlibrary.data~$ECHO "#define REVISION $revision" >> $output_objdir/a2ixlibrary.data~$AR $AR_FLAGS $lib $libobjs~$RANLIB $lib~(cd $output_objdir && a2ixlibrary -32)' _LT_TAGVAR(hardcode_libdir_flag_spec, $1)='-L$libdir' _LT_TAGVAR(hardcode_minus_L, $1)=yes ;; esac ;; bsdi[[45]]*) _LT_TAGVAR(export_dynamic_flag_spec, $1)=-rdynamic ;; cygwin* | mingw* | pw32* | cegcc*) # When not using gcc, we currently assume that we are using # Microsoft Visual C++. # hardcode_libdir_flag_spec is actually meaningless, as there is # no search path for DLLs. case $cc_basename in cl*) # Native MSVC _LT_TAGVAR(hardcode_libdir_flag_spec, $1)=' ' _LT_TAGVAR(allow_undefined_flag, $1)=unsupported _LT_TAGVAR(always_export_symbols, $1)=yes _LT_TAGVAR(file_list_spec, $1)='@' # Tell ltmain to make .lib files, not .a files. libext=lib # Tell ltmain to make .dll files, not .so files. shrext_cmds=.dll # FIXME: Setting linknames here is a bad hack. _LT_TAGVAR(archive_cmds, $1)='$CC -o $output_objdir/$soname $libobjs $compiler_flags $deplibs -Wl,-DLL,-IMPLIB:"$tool_output_objdir$libname.dll.lib"~linknames=' _LT_TAGVAR(archive_expsym_cmds, $1)='if _LT_DLL_DEF_P([$export_symbols]); then cp "$export_symbols" "$output_objdir/$soname.def"; echo "$tool_output_objdir$soname.def" > "$output_objdir/$soname.exp"; else $SED -e '\''s/^/-link -EXPORT:/'\'' < $export_symbols > $output_objdir/$soname.exp; fi~ $CC -o $tool_output_objdir$soname $libobjs $compiler_flags $deplibs "@$tool_output_objdir$soname.exp" -Wl,-DLL,-IMPLIB:"$tool_output_objdir$libname.dll.lib"~ linknames=' # The linker will not automatically build a static lib if we build a DLL. # _LT_TAGVAR(old_archive_from_new_cmds, $1)='true' _LT_TAGVAR(enable_shared_with_static_runtimes, $1)=yes _LT_TAGVAR(exclude_expsyms, $1)='_NULL_IMPORT_DESCRIPTOR|_IMPORT_DESCRIPTOR_.*' _LT_TAGVAR(export_symbols_cmds, $1)='$NM $libobjs $convenience | $global_symbol_pipe | $SED -e '\''/^[[BCDGRS]][[ ]]/s/.*[[ ]]\([[^ ]]*\)/\1,DATA/'\'' | $SED -e '\''/^[[AITW]][[ ]]/s/.*[[ ]]//'\'' | sort | uniq > $export_symbols' # Don't use ranlib _LT_TAGVAR(old_postinstall_cmds, $1)='chmod 644 $oldlib' _LT_TAGVAR(postlink_cmds, $1)='lt_outputfile="@OUTPUT@"~ lt_tool_outputfile="@TOOL_OUTPUT@"~ case $lt_outputfile in *.exe|*.EXE) ;; *) lt_outputfile=$lt_outputfile.exe lt_tool_outputfile=$lt_tool_outputfile.exe ;; esac~ if test : != "$MANIFEST_TOOL" && test -f "$lt_outputfile.manifest"; then $MANIFEST_TOOL -manifest "$lt_tool_outputfile.manifest" -outputresource:"$lt_tool_outputfile" || exit 1; $RM "$lt_outputfile.manifest"; fi' ;; *) # Assume MSVC wrapper _LT_TAGVAR(hardcode_libdir_flag_spec, $1)=' ' _LT_TAGVAR(allow_undefined_flag, $1)=unsupported # Tell ltmain to make .lib files, not .a files. libext=lib # Tell ltmain to make .dll files, not .so files. shrext_cmds=.dll # FIXME: Setting linknames here is a bad hack. _LT_TAGVAR(archive_cmds, $1)='$CC -o $lib $libobjs $compiler_flags `func_echo_all "$deplibs" | $SED '\''s/ -lc$//'\''` -link -dll~linknames=' # The linker will automatically build a .lib file if we build a DLL. _LT_TAGVAR(old_archive_from_new_cmds, $1)='true' # FIXME: Should let the user specify the lib program. _LT_TAGVAR(old_archive_cmds, $1)='lib -OUT:$oldlib$oldobjs$old_deplibs' _LT_TAGVAR(enable_shared_with_static_runtimes, $1)=yes ;; esac ;; darwin* | rhapsody*) _LT_DARWIN_LINKER_FEATURES($1) ;; dgux*) _LT_TAGVAR(archive_cmds, $1)='$LD -G -h $soname -o $lib $libobjs $deplibs $linker_flags' _LT_TAGVAR(hardcode_libdir_flag_spec, $1)='-L$libdir' _LT_TAGVAR(hardcode_shlibpath_var, $1)=no ;; # FreeBSD 2.2.[012] allows us to include c++rt0.o to get C++ constructor # support. Future versions do this automatically, but an explicit c++rt0.o # does not break anything, and helps significantly (at the cost of a little # extra space). freebsd2.2*) _LT_TAGVAR(archive_cmds, $1)='$LD -Bshareable -o $lib $libobjs $deplibs $linker_flags /usr/lib/c++rt0.o' _LT_TAGVAR(hardcode_libdir_flag_spec, $1)='-R$libdir' _LT_TAGVAR(hardcode_direct, $1)=yes _LT_TAGVAR(hardcode_shlibpath_var, $1)=no ;; # Unfortunately, older versions of FreeBSD 2 do not have this feature. freebsd2.*) _LT_TAGVAR(archive_cmds, $1)='$LD -Bshareable -o $lib $libobjs $deplibs $linker_flags' _LT_TAGVAR(hardcode_direct, $1)=yes _LT_TAGVAR(hardcode_minus_L, $1)=yes _LT_TAGVAR(hardcode_shlibpath_var, $1)=no ;; # FreeBSD 3 and greater uses gcc -shared to do shared libraries. freebsd* | dragonfly*) _LT_TAGVAR(archive_cmds, $1)='$CC -shared $pic_flag -o $lib $libobjs $deplibs $compiler_flags' _LT_TAGVAR(hardcode_libdir_flag_spec, $1)='-R$libdir' _LT_TAGVAR(hardcode_direct, $1)=yes _LT_TAGVAR(hardcode_shlibpath_var, $1)=no ;; hpux9*) if test yes = "$GCC"; then _LT_TAGVAR(archive_cmds, $1)='$RM $output_objdir/$soname~$CC -shared $pic_flag $wl+b $wl$install_libdir -o $output_objdir/$soname $libobjs $deplibs $compiler_flags~test "x$output_objdir/$soname" = "x$lib" || mv $output_objdir/$soname $lib' else _LT_TAGVAR(archive_cmds, $1)='$RM $output_objdir/$soname~$LD -b +b $install_libdir -o $output_objdir/$soname $libobjs $deplibs $linker_flags~test "x$output_objdir/$soname" = "x$lib" || mv $output_objdir/$soname $lib' fi _LT_TAGVAR(hardcode_libdir_flag_spec, $1)='$wl+b $wl$libdir' _LT_TAGVAR(hardcode_libdir_separator, $1)=: _LT_TAGVAR(hardcode_direct, $1)=yes # hardcode_minus_L: Not really in the search PATH, # but as the default location of the library. _LT_TAGVAR(hardcode_minus_L, $1)=yes _LT_TAGVAR(export_dynamic_flag_spec, $1)='$wl-E' ;; hpux10*) if test yes,no = "$GCC,$with_gnu_ld"; then _LT_TAGVAR(archive_cmds, $1)='$CC -shared $pic_flag $wl+h $wl$soname $wl+b $wl$install_libdir -o $lib $libobjs $deplibs $compiler_flags' else _LT_TAGVAR(archive_cmds, $1)='$LD -b +h $soname +b $install_libdir -o $lib $libobjs $deplibs $linker_flags' fi if test no = "$with_gnu_ld"; then _LT_TAGVAR(hardcode_libdir_flag_spec, $1)='$wl+b $wl$libdir' _LT_TAGVAR(hardcode_libdir_separator, $1)=: _LT_TAGVAR(hardcode_direct, $1)=yes _LT_TAGVAR(hardcode_direct_absolute, $1)=yes _LT_TAGVAR(export_dynamic_flag_spec, $1)='$wl-E' # hardcode_minus_L: Not really in the search PATH, # but as the default location of the library. _LT_TAGVAR(hardcode_minus_L, $1)=yes fi ;; hpux11*) if test yes,no = "$GCC,$with_gnu_ld"; then case $host_cpu in hppa*64*) _LT_TAGVAR(archive_cmds, $1)='$CC -shared $wl+h $wl$soname -o $lib $libobjs $deplibs $compiler_flags' ;; ia64*) _LT_TAGVAR(archive_cmds, $1)='$CC -shared $pic_flag $wl+h $wl$soname $wl+nodefaultrpath -o $lib $libobjs $deplibs $compiler_flags' ;; *) _LT_TAGVAR(archive_cmds, $1)='$CC -shared $pic_flag $wl+h $wl$soname $wl+b $wl$install_libdir -o $lib $libobjs $deplibs $compiler_flags' ;; esac else case $host_cpu in hppa*64*) _LT_TAGVAR(archive_cmds, $1)='$CC -b $wl+h $wl$soname -o $lib $libobjs $deplibs $compiler_flags' ;; ia64*) _LT_TAGVAR(archive_cmds, $1)='$CC -b $wl+h $wl$soname $wl+nodefaultrpath -o $lib $libobjs $deplibs $compiler_flags' ;; *) m4_if($1, [], [ # Older versions of the 11.00 compiler do not understand -b yet # (HP92453-01 A.11.01.20 doesn't, HP92453-01 B.11.X.35175-35176.GP does) _LT_LINKER_OPTION([if $CC understands -b], _LT_TAGVAR(lt_cv_prog_compiler__b, $1), [-b], [_LT_TAGVAR(archive_cmds, $1)='$CC -b $wl+h $wl$soname $wl+b $wl$install_libdir -o $lib $libobjs $deplibs $compiler_flags'], [_LT_TAGVAR(archive_cmds, $1)='$LD -b +h $soname +b $install_libdir -o $lib $libobjs $deplibs $linker_flags'])], [_LT_TAGVAR(archive_cmds, $1)='$CC -b $wl+h $wl$soname $wl+b $wl$install_libdir -o $lib $libobjs $deplibs $compiler_flags']) ;; esac fi if test no = "$with_gnu_ld"; then _LT_TAGVAR(hardcode_libdir_flag_spec, $1)='$wl+b $wl$libdir' _LT_TAGVAR(hardcode_libdir_separator, $1)=: case $host_cpu in hppa*64*|ia64*) _LT_TAGVAR(hardcode_direct, $1)=no _LT_TAGVAR(hardcode_shlibpath_var, $1)=no ;; *) _LT_TAGVAR(hardcode_direct, $1)=yes _LT_TAGVAR(hardcode_direct_absolute, $1)=yes _LT_TAGVAR(export_dynamic_flag_spec, $1)='$wl-E' # hardcode_minus_L: Not really in the search PATH, # but as the default location of the library. _LT_TAGVAR(hardcode_minus_L, $1)=yes ;; esac fi ;; irix5* | irix6* | nonstopux*) if test yes = "$GCC"; then _LT_TAGVAR(archive_cmds, $1)='$CC -shared $pic_flag $libobjs $deplibs $compiler_flags $wl-soname $wl$soname `test -n "$verstring" && func_echo_all "$wl-set_version $wl$verstring"` $wl-update_registry $wl$output_objdir/so_locations -o $lib' # Try to use the -exported_symbol ld option, if it does not # work, assume that -exports_file does not work either and # implicitly export all symbols. # This should be the same for all languages, so no per-tag cache variable. AC_CACHE_CHECK([whether the $host_os linker accepts -exported_symbol], [lt_cv_irix_exported_symbol], [save_LDFLAGS=$LDFLAGS LDFLAGS="$LDFLAGS -shared $wl-exported_symbol ${wl}foo $wl-update_registry $wl/dev/null" AC_LINK_IFELSE( [AC_LANG_SOURCE( [AC_LANG_CASE([C], [[int foo (void) { return 0; }]], [C++], [[int foo (void) { return 0; }]], [Fortran 77], [[ subroutine foo end]], [Fortran], [[ subroutine foo end]])])], [lt_cv_irix_exported_symbol=yes], [lt_cv_irix_exported_symbol=no]) LDFLAGS=$save_LDFLAGS]) if test yes = "$lt_cv_irix_exported_symbol"; then _LT_TAGVAR(archive_expsym_cmds, $1)='$CC -shared $pic_flag $libobjs $deplibs $compiler_flags $wl-soname $wl$soname `test -n "$verstring" && func_echo_all "$wl-set_version $wl$verstring"` $wl-update_registry $wl$output_objdir/so_locations $wl-exports_file $wl$export_symbols -o $lib' fi _LT_TAGVAR(link_all_deplibs, $1)=no else _LT_TAGVAR(archive_cmds, $1)='$CC -shared $libobjs $deplibs $compiler_flags -soname $soname `test -n "$verstring" && func_echo_all "-set_version $verstring"` -update_registry $output_objdir/so_locations -o $lib' _LT_TAGVAR(archive_expsym_cmds, $1)='$CC -shared $libobjs $deplibs $compiler_flags -soname $soname `test -n "$verstring" && func_echo_all "-set_version $verstring"` -update_registry $output_objdir/so_locations -exports_file $export_symbols -o $lib' fi _LT_TAGVAR(archive_cmds_need_lc, $1)='no' _LT_TAGVAR(hardcode_libdir_flag_spec, $1)='$wl-rpath $wl$libdir' _LT_TAGVAR(hardcode_libdir_separator, $1)=: _LT_TAGVAR(inherit_rpath, $1)=yes _LT_TAGVAR(link_all_deplibs, $1)=yes ;; linux*) case $cc_basename in tcc*) # Fabrice Bellard et al's Tiny C Compiler _LT_TAGVAR(ld_shlibs, $1)=yes _LT_TAGVAR(archive_cmds, $1)='$CC -shared $pic_flag -o $lib $libobjs $deplibs $compiler_flags' ;; esac ;; netbsd* | netbsdelf*-gnu) if echo __ELF__ | $CC -E - | $GREP __ELF__ >/dev/null; then _LT_TAGVAR(archive_cmds, $1)='$LD -Bshareable -o $lib $libobjs $deplibs $linker_flags' # a.out else _LT_TAGVAR(archive_cmds, $1)='$LD -shared -o $lib $libobjs $deplibs $linker_flags' # ELF fi _LT_TAGVAR(hardcode_libdir_flag_spec, $1)='-R$libdir' _LT_TAGVAR(hardcode_direct, $1)=yes _LT_TAGVAR(hardcode_shlibpath_var, $1)=no ;; newsos6) _LT_TAGVAR(archive_cmds, $1)='$LD -G -h $soname -o $lib $libobjs $deplibs $linker_flags' _LT_TAGVAR(hardcode_direct, $1)=yes _LT_TAGVAR(hardcode_libdir_flag_spec, $1)='$wl-rpath $wl$libdir' _LT_TAGVAR(hardcode_libdir_separator, $1)=: _LT_TAGVAR(hardcode_shlibpath_var, $1)=no ;; *nto* | *qnx*) ;; openbsd* | bitrig*) if test -f /usr/libexec/ld.so; then _LT_TAGVAR(hardcode_direct, $1)=yes _LT_TAGVAR(hardcode_shlibpath_var, $1)=no _LT_TAGVAR(hardcode_direct_absolute, $1)=yes if test -z "`echo __ELF__ | $CC -E - | $GREP __ELF__`"; then _LT_TAGVAR(archive_cmds, $1)='$CC -shared $pic_flag -o $lib $libobjs $deplibs $compiler_flags' _LT_TAGVAR(archive_expsym_cmds, $1)='$CC -shared $pic_flag -o $lib $libobjs $deplibs $compiler_flags $wl-retain-symbols-file,$export_symbols' _LT_TAGVAR(hardcode_libdir_flag_spec, $1)='$wl-rpath,$libdir' _LT_TAGVAR(export_dynamic_flag_spec, $1)='$wl-E' else _LT_TAGVAR(archive_cmds, $1)='$CC -shared $pic_flag -o $lib $libobjs $deplibs $compiler_flags' _LT_TAGVAR(hardcode_libdir_flag_spec, $1)='$wl-rpath,$libdir' fi else _LT_TAGVAR(ld_shlibs, $1)=no fi ;; os2*) _LT_TAGVAR(hardcode_libdir_flag_spec, $1)='-L$libdir' _LT_TAGVAR(hardcode_minus_L, $1)=yes _LT_TAGVAR(allow_undefined_flag, $1)=unsupported shrext_cmds=.dll _LT_TAGVAR(archive_cmds, $1)='$ECHO "LIBRARY ${soname%$shared_ext} INITINSTANCE TERMINSTANCE" > $output_objdir/$libname.def~ $ECHO "DESCRIPTION \"$libname\"" >> $output_objdir/$libname.def~ $ECHO "DATA MULTIPLE NONSHARED" >> $output_objdir/$libname.def~ $ECHO EXPORTS >> $output_objdir/$libname.def~ emxexp $libobjs | $SED /"_DLL_InitTerm"/d >> $output_objdir/$libname.def~ $CC -Zdll -Zcrtdll -o $output_objdir/$soname $libobjs $deplibs $compiler_flags $output_objdir/$libname.def~ emximp -o $lib $output_objdir/$libname.def' _LT_TAGVAR(archive_expsym_cmds, $1)='$ECHO "LIBRARY ${soname%$shared_ext} INITINSTANCE TERMINSTANCE" > $output_objdir/$libname.def~ $ECHO "DESCRIPTION \"$libname\"" >> $output_objdir/$libname.def~ $ECHO "DATA MULTIPLE NONSHARED" >> $output_objdir/$libname.def~ $ECHO EXPORTS >> $output_objdir/$libname.def~ prefix_cmds="$SED"~ if test EXPORTS = "`$SED 1q $export_symbols`"; then prefix_cmds="$prefix_cmds -e 1d"; fi~ prefix_cmds="$prefix_cmds -e \"s/^\(.*\)$/_\1/g\""~ cat $export_symbols | $prefix_cmds >> $output_objdir/$libname.def~ $CC -Zdll -Zcrtdll -o $output_objdir/$soname $libobjs $deplibs $compiler_flags $output_objdir/$libname.def~ emximp -o $lib $output_objdir/$libname.def' _LT_TAGVAR(old_archive_From_new_cmds, $1)='emximp -o $output_objdir/${libname}_dll.a $output_objdir/$libname.def' _LT_TAGVAR(enable_shared_with_static_runtimes, $1)=yes ;; osf3*) if test yes = "$GCC"; then _LT_TAGVAR(allow_undefined_flag, $1)=' $wl-expect_unresolved $wl\*' _LT_TAGVAR(archive_cmds, $1)='$CC -shared$allow_undefined_flag $libobjs $deplibs $compiler_flags $wl-soname $wl$soname `test -n "$verstring" && func_echo_all "$wl-set_version $wl$verstring"` $wl-update_registry $wl$output_objdir/so_locations -o $lib' else _LT_TAGVAR(allow_undefined_flag, $1)=' -expect_unresolved \*' _LT_TAGVAR(archive_cmds, $1)='$CC -shared$allow_undefined_flag $libobjs $deplibs $compiler_flags -soname $soname `test -n "$verstring" && func_echo_all "-set_version $verstring"` -update_registry $output_objdir/so_locations -o $lib' fi _LT_TAGVAR(archive_cmds_need_lc, $1)='no' _LT_TAGVAR(hardcode_libdir_flag_spec, $1)='$wl-rpath $wl$libdir' _LT_TAGVAR(hardcode_libdir_separator, $1)=: ;; osf4* | osf5*) # as osf3* with the addition of -msym flag if test yes = "$GCC"; then _LT_TAGVAR(allow_undefined_flag, $1)=' $wl-expect_unresolved $wl\*' _LT_TAGVAR(archive_cmds, $1)='$CC -shared$allow_undefined_flag $pic_flag $libobjs $deplibs $compiler_flags $wl-msym $wl-soname $wl$soname `test -n "$verstring" && func_echo_all "$wl-set_version $wl$verstring"` $wl-update_registry $wl$output_objdir/so_locations -o $lib' _LT_TAGVAR(hardcode_libdir_flag_spec, $1)='$wl-rpath $wl$libdir' else _LT_TAGVAR(allow_undefined_flag, $1)=' -expect_unresolved \*' _LT_TAGVAR(archive_cmds, $1)='$CC -shared$allow_undefined_flag $libobjs $deplibs $compiler_flags -msym -soname $soname `test -n "$verstring" && func_echo_all "-set_version $verstring"` -update_registry $output_objdir/so_locations -o $lib' _LT_TAGVAR(archive_expsym_cmds, $1)='for i in `cat $export_symbols`; do printf "%s %s\\n" -exported_symbol "\$i" >> $lib.exp; done; printf "%s\\n" "-hidden">> $lib.exp~ $CC -shared$allow_undefined_flag $wl-input $wl$lib.exp $compiler_flags $libobjs $deplibs -soname $soname `test -n "$verstring" && $ECHO "-set_version $verstring"` -update_registry $output_objdir/so_locations -o $lib~$RM $lib.exp' # Both c and cxx compiler support -rpath directly _LT_TAGVAR(hardcode_libdir_flag_spec, $1)='-rpath $libdir' fi _LT_TAGVAR(archive_cmds_need_lc, $1)='no' _LT_TAGVAR(hardcode_libdir_separator, $1)=: ;; solaris*) _LT_TAGVAR(no_undefined_flag, $1)=' -z defs' if test yes = "$GCC"; then wlarc='$wl' _LT_TAGVAR(archive_cmds, $1)='$CC -shared $pic_flag $wl-z ${wl}text $wl-h $wl$soname -o $lib $libobjs $deplibs $compiler_flags' _LT_TAGVAR(archive_expsym_cmds, $1)='echo "{ global:" > $lib.exp~cat $export_symbols | $SED -e "s/\(.*\)/\1;/" >> $lib.exp~echo "local: *; };" >> $lib.exp~ $CC -shared $pic_flag $wl-z ${wl}text $wl-M $wl$lib.exp $wl-h $wl$soname -o $lib $libobjs $deplibs $compiler_flags~$RM $lib.exp' else case `$CC -V 2>&1` in *"Compilers 5.0"*) wlarc='' _LT_TAGVAR(archive_cmds, $1)='$LD -G$allow_undefined_flag -h $soname -o $lib $libobjs $deplibs $linker_flags' _LT_TAGVAR(archive_expsym_cmds, $1)='echo "{ global:" > $lib.exp~cat $export_symbols | $SED -e "s/\(.*\)/\1;/" >> $lib.exp~echo "local: *; };" >> $lib.exp~ $LD -G$allow_undefined_flag -M $lib.exp -h $soname -o $lib $libobjs $deplibs $linker_flags~$RM $lib.exp' ;; *) wlarc='$wl' _LT_TAGVAR(archive_cmds, $1)='$CC -G$allow_undefined_flag -h $soname -o $lib $libobjs $deplibs $compiler_flags' _LT_TAGVAR(archive_expsym_cmds, $1)='echo "{ global:" > $lib.exp~cat $export_symbols | $SED -e "s/\(.*\)/\1;/" >> $lib.exp~echo "local: *; };" >> $lib.exp~ $CC -G$allow_undefined_flag -M $lib.exp -h $soname -o $lib $libobjs $deplibs $compiler_flags~$RM $lib.exp' ;; esac fi _LT_TAGVAR(hardcode_libdir_flag_spec, $1)='-R$libdir' _LT_TAGVAR(hardcode_shlibpath_var, $1)=no case $host_os in solaris2.[[0-5]] | solaris2.[[0-5]].*) ;; *) # The compiler driver will combine and reorder linker options, # but understands '-z linker_flag'. GCC discards it without '$wl', # but is careful enough not to reorder. # Supported since Solaris 2.6 (maybe 2.5.1?) if test yes = "$GCC"; then _LT_TAGVAR(whole_archive_flag_spec, $1)='$wl-z ${wl}allextract$convenience $wl-z ${wl}defaultextract' else _LT_TAGVAR(whole_archive_flag_spec, $1)='-z allextract$convenience -z defaultextract' fi ;; esac _LT_TAGVAR(link_all_deplibs, $1)=yes ;; sunos4*) if test sequent = "$host_vendor"; then # Use $CC to link under sequent, because it throws in some extra .o # files that make .init and .fini sections work. _LT_TAGVAR(archive_cmds, $1)='$CC -G $wl-h $soname -o $lib $libobjs $deplibs $compiler_flags' else _LT_TAGVAR(archive_cmds, $1)='$LD -assert pure-text -Bstatic -o $lib $libobjs $deplibs $linker_flags' fi _LT_TAGVAR(hardcode_libdir_flag_spec, $1)='-L$libdir' _LT_TAGVAR(hardcode_direct, $1)=yes _LT_TAGVAR(hardcode_minus_L, $1)=yes _LT_TAGVAR(hardcode_shlibpath_var, $1)=no ;; sysv4) case $host_vendor in sni) _LT_TAGVAR(archive_cmds, $1)='$LD -G -h $soname -o $lib $libobjs $deplibs $linker_flags' _LT_TAGVAR(hardcode_direct, $1)=yes # is this really true??? ;; siemens) ## LD is ld it makes a PLAMLIB ## CC just makes a GrossModule. _LT_TAGVAR(archive_cmds, $1)='$LD -G -o $lib $libobjs $deplibs $linker_flags' _LT_TAGVAR(reload_cmds, $1)='$CC -r -o $output$reload_objs' _LT_TAGVAR(hardcode_direct, $1)=no ;; motorola) _LT_TAGVAR(archive_cmds, $1)='$LD -G -h $soname -o $lib $libobjs $deplibs $linker_flags' _LT_TAGVAR(hardcode_direct, $1)=no #Motorola manual says yes, but my tests say they lie ;; esac runpath_var='LD_RUN_PATH' _LT_TAGVAR(hardcode_shlibpath_var, $1)=no ;; sysv4.3*) _LT_TAGVAR(archive_cmds, $1)='$LD -G -h $soname -o $lib $libobjs $deplibs $linker_flags' _LT_TAGVAR(hardcode_shlibpath_var, $1)=no _LT_TAGVAR(export_dynamic_flag_spec, $1)='-Bexport' ;; sysv4*MP*) if test -d /usr/nec; then _LT_TAGVAR(archive_cmds, $1)='$LD -G -h $soname -o $lib $libobjs $deplibs $linker_flags' _LT_TAGVAR(hardcode_shlibpath_var, $1)=no runpath_var=LD_RUN_PATH hardcode_runpath_var=yes _LT_TAGVAR(ld_shlibs, $1)=yes fi ;; sysv4*uw2* | sysv5OpenUNIX* | sysv5UnixWare7.[[01]].[[10]]* | unixware7* | sco3.2v5.0.[[024]]*) _LT_TAGVAR(no_undefined_flag, $1)='$wl-z,text' _LT_TAGVAR(archive_cmds_need_lc, $1)=no _LT_TAGVAR(hardcode_shlibpath_var, $1)=no runpath_var='LD_RUN_PATH' if test yes = "$GCC"; then _LT_TAGVAR(archive_cmds, $1)='$CC -shared $wl-h,$soname -o $lib $libobjs $deplibs $compiler_flags' _LT_TAGVAR(archive_expsym_cmds, $1)='$CC -shared $wl-Bexport:$export_symbols $wl-h,$soname -o $lib $libobjs $deplibs $compiler_flags' else _LT_TAGVAR(archive_cmds, $1)='$CC -G $wl-h,$soname -o $lib $libobjs $deplibs $compiler_flags' _LT_TAGVAR(archive_expsym_cmds, $1)='$CC -G $wl-Bexport:$export_symbols $wl-h,$soname -o $lib $libobjs $deplibs $compiler_flags' fi ;; sysv5* | sco3.2v5* | sco5v6*) # Note: We CANNOT use -z defs as we might desire, because we do not # link with -lc, and that would cause any symbols used from libc to # always be unresolved, which means just about no library would # ever link correctly. If we're not using GNU ld we use -z text # though, which does catch some bad symbols but isn't as heavy-handed # as -z defs. _LT_TAGVAR(no_undefined_flag, $1)='$wl-z,text' _LT_TAGVAR(allow_undefined_flag, $1)='$wl-z,nodefs' _LT_TAGVAR(archive_cmds_need_lc, $1)=no _LT_TAGVAR(hardcode_shlibpath_var, $1)=no _LT_TAGVAR(hardcode_libdir_flag_spec, $1)='$wl-R,$libdir' _LT_TAGVAR(hardcode_libdir_separator, $1)=':' _LT_TAGVAR(link_all_deplibs, $1)=yes _LT_TAGVAR(export_dynamic_flag_spec, $1)='$wl-Bexport' runpath_var='LD_RUN_PATH' if test yes = "$GCC"; then _LT_TAGVAR(archive_cmds, $1)='$CC -shared $wl-h,$soname -o $lib $libobjs $deplibs $compiler_flags' _LT_TAGVAR(archive_expsym_cmds, $1)='$CC -shared $wl-Bexport:$export_symbols $wl-h,$soname -o $lib $libobjs $deplibs $compiler_flags' else _LT_TAGVAR(archive_cmds, $1)='$CC -G $wl-h,$soname -o $lib $libobjs $deplibs $compiler_flags' _LT_TAGVAR(archive_expsym_cmds, $1)='$CC -G $wl-Bexport:$export_symbols $wl-h,$soname -o $lib $libobjs $deplibs $compiler_flags' fi ;; uts4*) _LT_TAGVAR(archive_cmds, $1)='$LD -G -h $soname -o $lib $libobjs $deplibs $linker_flags' _LT_TAGVAR(hardcode_libdir_flag_spec, $1)='-L$libdir' _LT_TAGVAR(hardcode_shlibpath_var, $1)=no ;; *) _LT_TAGVAR(ld_shlibs, $1)=no ;; esac if test sni = "$host_vendor"; then case $host in sysv4 | sysv4.2uw2* | sysv4.3* | sysv5*) _LT_TAGVAR(export_dynamic_flag_spec, $1)='$wl-Blargedynsym' ;; esac fi fi ]) AC_MSG_RESULT([$_LT_TAGVAR(ld_shlibs, $1)]) test no = "$_LT_TAGVAR(ld_shlibs, $1)" && can_build_shared=no _LT_TAGVAR(with_gnu_ld, $1)=$with_gnu_ld _LT_DECL([], [libext], [0], [Old archive suffix (normally "a")])dnl _LT_DECL([], [shrext_cmds], [1], [Shared library suffix (normally ".so")])dnl _LT_DECL([], [extract_expsyms_cmds], [2], [The commands to extract the exported symbol list from a shared archive]) # # Do we need to explicitly link libc? # case "x$_LT_TAGVAR(archive_cmds_need_lc, $1)" in x|xyes) # Assume -lc should be added _LT_TAGVAR(archive_cmds_need_lc, $1)=yes if test yes,yes = "$GCC,$enable_shared"; then case $_LT_TAGVAR(archive_cmds, $1) in *'~'*) # FIXME: we may have to deal with multi-command sequences. ;; '$CC '*) # Test whether the compiler implicitly links with -lc since on some # systems, -lgcc has to come before -lc. If gcc already passes -lc # to ld, don't add -lc before -lgcc. AC_CACHE_CHECK([whether -lc should be explicitly linked in], [lt_cv_]_LT_TAGVAR(archive_cmds_need_lc, $1), [$RM conftest* echo "$lt_simple_compile_test_code" > conftest.$ac_ext if AC_TRY_EVAL(ac_compile) 2>conftest.err; then soname=conftest lib=conftest libobjs=conftest.$ac_objext deplibs= wl=$_LT_TAGVAR(lt_prog_compiler_wl, $1) pic_flag=$_LT_TAGVAR(lt_prog_compiler_pic, $1) compiler_flags=-v linker_flags=-v verstring= output_objdir=. libname=conftest lt_save_allow_undefined_flag=$_LT_TAGVAR(allow_undefined_flag, $1) _LT_TAGVAR(allow_undefined_flag, $1)= if AC_TRY_EVAL(_LT_TAGVAR(archive_cmds, $1) 2\>\&1 \| $GREP \" -lc \" \>/dev/null 2\>\&1) then lt_cv_[]_LT_TAGVAR(archive_cmds_need_lc, $1)=no else lt_cv_[]_LT_TAGVAR(archive_cmds_need_lc, $1)=yes fi _LT_TAGVAR(allow_undefined_flag, $1)=$lt_save_allow_undefined_flag else cat conftest.err 1>&5 fi $RM conftest* ]) _LT_TAGVAR(archive_cmds_need_lc, $1)=$lt_cv_[]_LT_TAGVAR(archive_cmds_need_lc, $1) ;; esac fi ;; esac _LT_TAGDECL([build_libtool_need_lc], [archive_cmds_need_lc], [0], [Whether or not to add -lc for building shared libraries]) _LT_TAGDECL([allow_libtool_libs_with_static_runtimes], [enable_shared_with_static_runtimes], [0], [Whether or not to disallow shared libs when runtime libs are static]) _LT_TAGDECL([], [export_dynamic_flag_spec], [1], [Compiler flag to allow reflexive dlopens]) _LT_TAGDECL([], [whole_archive_flag_spec], [1], [Compiler flag to generate shared objects directly from archives]) _LT_TAGDECL([], [compiler_needs_object], [1], [Whether the compiler copes with passing no objects directly]) _LT_TAGDECL([], [old_archive_from_new_cmds], [2], [Create an old-style archive from a shared archive]) _LT_TAGDECL([], [old_archive_from_expsyms_cmds], [2], [Create a temporary old-style archive to link instead of a shared archive]) _LT_TAGDECL([], [archive_cmds], [2], [Commands used to build a shared archive]) _LT_TAGDECL([], [archive_expsym_cmds], [2]) _LT_TAGDECL([], [module_cmds], [2], [Commands used to build a loadable module if different from building a shared archive.]) _LT_TAGDECL([], [module_expsym_cmds], [2]) _LT_TAGDECL([], [with_gnu_ld], [1], [Whether we are building with GNU ld or not]) _LT_TAGDECL([], [allow_undefined_flag], [1], [Flag that allows shared libraries with undefined symbols to be built]) _LT_TAGDECL([], [no_undefined_flag], [1], [Flag that enforces no undefined symbols]) _LT_TAGDECL([], [hardcode_libdir_flag_spec], [1], [Flag to hardcode $libdir into a binary during linking. This must work even if $libdir does not exist]) _LT_TAGDECL([], [hardcode_libdir_separator], [1], [Whether we need a single "-rpath" flag with a separated argument]) _LT_TAGDECL([], [hardcode_direct], [0], [Set to "yes" if using DIR/libNAME$shared_ext during linking hardcodes DIR into the resulting binary]) _LT_TAGDECL([], [hardcode_direct_absolute], [0], [Set to "yes" if using DIR/libNAME$shared_ext during linking hardcodes DIR into the resulting binary and the resulting library dependency is "absolute", i.e impossible to change by setting $shlibpath_var if the library is relocated]) _LT_TAGDECL([], [hardcode_minus_L], [0], [Set to "yes" if using the -LDIR flag during linking hardcodes DIR into the resulting binary]) _LT_TAGDECL([], [hardcode_shlibpath_var], [0], [Set to "yes" if using SHLIBPATH_VAR=DIR during linking hardcodes DIR into the resulting binary]) _LT_TAGDECL([], [hardcode_automatic], [0], [Set to "yes" if building a shared library automatically hardcodes DIR into the library and all subsequent libraries and executables linked against it]) _LT_TAGDECL([], [inherit_rpath], [0], [Set to yes if linker adds runtime paths of dependent libraries to runtime path list]) _LT_TAGDECL([], [link_all_deplibs], [0], [Whether libtool must link a program against all its dependency libraries]) _LT_TAGDECL([], [always_export_symbols], [0], [Set to "yes" if exported symbols are required]) _LT_TAGDECL([], [export_symbols_cmds], [2], [The commands to list exported symbols]) _LT_TAGDECL([], [exclude_expsyms], [1], [Symbols that should not be listed in the preloaded symbols]) _LT_TAGDECL([], [include_expsyms], [1], [Symbols that must always be exported]) _LT_TAGDECL([], [prelink_cmds], [2], [Commands necessary for linking programs (against libraries) with templates]) _LT_TAGDECL([], [postlink_cmds], [2], [Commands necessary for finishing linking programs]) _LT_TAGDECL([], [file_list_spec], [1], [Specify filename containing input files]) dnl FIXME: Not yet implemented dnl _LT_TAGDECL([], [thread_safe_flag_spec], [1], dnl [Compiler flag to generate thread safe objects]) ])# _LT_LINKER_SHLIBS # _LT_LANG_C_CONFIG([TAG]) # ------------------------ # Ensure that the configuration variables for a C compiler are suitably # defined. These variables are subsequently used by _LT_CONFIG to write # the compiler configuration to 'libtool'. m4_defun([_LT_LANG_C_CONFIG], [m4_require([_LT_DECL_EGREP])dnl lt_save_CC=$CC AC_LANG_PUSH(C) # Source file extension for C test sources. ac_ext=c # Object file extension for compiled C test sources. objext=o _LT_TAGVAR(objext, $1)=$objext # Code to be used in simple compile tests lt_simple_compile_test_code="int some_variable = 0;" # Code to be used in simple link tests lt_simple_link_test_code='int main(){return(0);}' _LT_TAG_COMPILER # Save the default compiler, since it gets overwritten when the other # tags are being tested, and _LT_TAGVAR(compiler, []) is a NOP. compiler_DEFAULT=$CC # save warnings/boilerplate of simple test code _LT_COMPILER_BOILERPLATE _LT_LINKER_BOILERPLATE ## CAVEAT EMPTOR: ## There is no encapsulation within the following macros, do not change ## the running order or otherwise move them around unless you know exactly ## what you are doing... if test -n "$compiler"; then _LT_COMPILER_NO_RTTI($1) _LT_COMPILER_PIC($1) _LT_COMPILER_C_O($1) _LT_COMPILER_FILE_LOCKS($1) _LT_LINKER_SHLIBS($1) _LT_SYS_DYNAMIC_LINKER($1) _LT_LINKER_HARDCODE_LIBPATH($1) LT_SYS_DLOPEN_SELF _LT_CMD_STRIPLIB # Report what library types will actually be built AC_MSG_CHECKING([if libtool supports shared libraries]) AC_MSG_RESULT([$can_build_shared]) AC_MSG_CHECKING([whether to build shared libraries]) test no = "$can_build_shared" && enable_shared=no # On AIX, shared libraries and static libraries use the same namespace, and # are all built from PIC. case $host_os in aix3*) test yes = "$enable_shared" && enable_static=no if test -n "$RANLIB"; then archive_cmds="$archive_cmds~\$RANLIB \$lib" postinstall_cmds='$RANLIB $lib' fi ;; aix[[4-9]]*) if test ia64 != "$host_cpu"; then case $enable_shared,$with_aix_soname,$aix_use_runtimelinking in yes,aix,yes) ;; # shared object as lib.so file only yes,svr4,*) ;; # shared object as lib.so archive member only yes,*) enable_static=no ;; # shared object in lib.a archive as well esac fi ;; esac AC_MSG_RESULT([$enable_shared]) AC_MSG_CHECKING([whether to build static libraries]) # Make sure either enable_shared or enable_static is yes. test yes = "$enable_shared" || enable_static=yes AC_MSG_RESULT([$enable_static]) _LT_CONFIG($1) fi AC_LANG_POP CC=$lt_save_CC ])# _LT_LANG_C_CONFIG # _LT_LANG_CXX_CONFIG([TAG]) # -------------------------- # Ensure that the configuration variables for a C++ compiler are suitably # defined. These variables are subsequently used by _LT_CONFIG to write # the compiler configuration to 'libtool'. m4_defun([_LT_LANG_CXX_CONFIG], [m4_require([_LT_FILEUTILS_DEFAULTS])dnl m4_require([_LT_DECL_EGREP])dnl m4_require([_LT_PATH_MANIFEST_TOOL])dnl if test -n "$CXX" && ( test no != "$CXX" && ( (test g++ = "$CXX" && `g++ -v >/dev/null 2>&1` ) || (test g++ != "$CXX"))); then AC_PROG_CXXCPP else _lt_caught_CXX_error=yes fi AC_LANG_PUSH(C++) _LT_TAGVAR(archive_cmds_need_lc, $1)=no _LT_TAGVAR(allow_undefined_flag, $1)= _LT_TAGVAR(always_export_symbols, $1)=no _LT_TAGVAR(archive_expsym_cmds, $1)= _LT_TAGVAR(compiler_needs_object, $1)=no _LT_TAGVAR(export_dynamic_flag_spec, $1)= _LT_TAGVAR(hardcode_direct, $1)=no _LT_TAGVAR(hardcode_direct_absolute, $1)=no _LT_TAGVAR(hardcode_libdir_flag_spec, $1)= _LT_TAGVAR(hardcode_libdir_separator, $1)= _LT_TAGVAR(hardcode_minus_L, $1)=no _LT_TAGVAR(hardcode_shlibpath_var, $1)=unsupported _LT_TAGVAR(hardcode_automatic, $1)=no _LT_TAGVAR(inherit_rpath, $1)=no _LT_TAGVAR(module_cmds, $1)= _LT_TAGVAR(module_expsym_cmds, $1)= _LT_TAGVAR(link_all_deplibs, $1)=unknown _LT_TAGVAR(old_archive_cmds, $1)=$old_archive_cmds _LT_TAGVAR(reload_flag, $1)=$reload_flag _LT_TAGVAR(reload_cmds, $1)=$reload_cmds _LT_TAGVAR(no_undefined_flag, $1)= _LT_TAGVAR(whole_archive_flag_spec, $1)= _LT_TAGVAR(enable_shared_with_static_runtimes, $1)=no # Source file extension for C++ test sources. ac_ext=cpp # Object file extension for compiled C++ test sources. objext=o _LT_TAGVAR(objext, $1)=$objext # No sense in running all these tests if we already determined that # the CXX compiler isn't working. Some variables (like enable_shared) # are currently assumed to apply to all compilers on this platform, # and will be corrupted by setting them based on a non-working compiler. if test yes != "$_lt_caught_CXX_error"; then # Code to be used in simple compile tests lt_simple_compile_test_code="int some_variable = 0;" # Code to be used in simple link tests lt_simple_link_test_code='int main(int, char *[[]]) { return(0); }' # ltmain only uses $CC for tagged configurations so make sure $CC is set. _LT_TAG_COMPILER # save warnings/boilerplate of simple test code _LT_COMPILER_BOILERPLATE _LT_LINKER_BOILERPLATE # Allow CC to be a program name with arguments. lt_save_CC=$CC lt_save_CFLAGS=$CFLAGS lt_save_LD=$LD lt_save_GCC=$GCC GCC=$GXX lt_save_with_gnu_ld=$with_gnu_ld lt_save_path_LD=$lt_cv_path_LD if test -n "${lt_cv_prog_gnu_ldcxx+set}"; then lt_cv_prog_gnu_ld=$lt_cv_prog_gnu_ldcxx else $as_unset lt_cv_prog_gnu_ld fi if test -n "${lt_cv_path_LDCXX+set}"; then lt_cv_path_LD=$lt_cv_path_LDCXX else $as_unset lt_cv_path_LD fi test -z "${LDCXX+set}" || LD=$LDCXX CC=${CXX-"c++"} CFLAGS=$CXXFLAGS compiler=$CC _LT_TAGVAR(compiler, $1)=$CC _LT_CC_BASENAME([$compiler]) if test -n "$compiler"; then # We don't want -fno-exception when compiling C++ code, so set the # no_builtin_flag separately if test yes = "$GXX"; then _LT_TAGVAR(lt_prog_compiler_no_builtin_flag, $1)=' -fno-builtin' else _LT_TAGVAR(lt_prog_compiler_no_builtin_flag, $1)= fi if test yes = "$GXX"; then # Set up default GNU C++ configuration LT_PATH_LD # Check if GNU C++ uses GNU ld as the underlying linker, since the # archiving commands below assume that GNU ld is being used. if test yes = "$with_gnu_ld"; then _LT_TAGVAR(archive_cmds, $1)='$CC $pic_flag -shared -nostdlib $predep_objects $libobjs $deplibs $postdep_objects $compiler_flags $wl-soname $wl$soname -o $lib' _LT_TAGVAR(archive_expsym_cmds, $1)='$CC $pic_flag -shared -nostdlib $predep_objects $libobjs $deplibs $postdep_objects $compiler_flags $wl-soname $wl$soname $wl-retain-symbols-file $wl$export_symbols -o $lib' _LT_TAGVAR(hardcode_libdir_flag_spec, $1)='$wl-rpath $wl$libdir' _LT_TAGVAR(export_dynamic_flag_spec, $1)='$wl--export-dynamic' # If archive_cmds runs LD, not CC, wlarc should be empty # XXX I think wlarc can be eliminated in ltcf-cxx, but I need to # investigate it a little bit more. (MM) wlarc='$wl' # ancient GNU ld didn't support --whole-archive et. al. if eval "`$CC -print-prog-name=ld` --help 2>&1" | $GREP 'no-whole-archive' > /dev/null; then _LT_TAGVAR(whole_archive_flag_spec, $1)=$wlarc'--whole-archive$convenience '$wlarc'--no-whole-archive' else _LT_TAGVAR(whole_archive_flag_spec, $1)= fi else with_gnu_ld=no wlarc= # A generic and very simple default shared library creation # command for GNU C++ for the case where it uses the native # linker, instead of GNU ld. If possible, this setting should # overridden to take advantage of the native linker features on # the platform it is being used on. _LT_TAGVAR(archive_cmds, $1)='$CC -shared -nostdlib $predep_objects $libobjs $deplibs $postdep_objects $compiler_flags -o $lib' fi # Commands to make compiler produce verbose output that lists # what "hidden" libraries, object files and flags are used when # linking a shared library. output_verbose_link_cmd='$CC -shared $CFLAGS -v conftest.$objext 2>&1 | $GREP -v "^Configured with:" | $GREP " \-L"' else GXX=no with_gnu_ld=no wlarc= fi # PORTME: fill in a description of your system's C++ link characteristics AC_MSG_CHECKING([whether the $compiler linker ($LD) supports shared libraries]) _LT_TAGVAR(ld_shlibs, $1)=yes case $host_os in aix3*) # FIXME: insert proper C++ library support _LT_TAGVAR(ld_shlibs, $1)=no ;; aix[[4-9]]*) if test ia64 = "$host_cpu"; then # On IA64, the linker does run time linking by default, so we don't # have to do anything special. aix_use_runtimelinking=no exp_sym_flag='-Bexport' no_entry_flag= else aix_use_runtimelinking=no # Test if we are trying to use run time linking or normal # AIX style linking. If -brtl is somewhere in LDFLAGS, we # have runtime linking enabled, and use it for executables. # For shared libraries, we enable/disable runtime linking # depending on the kind of the shared library created - # when "with_aix_soname,aix_use_runtimelinking" is: # "aix,no" lib.a(lib.so.V) shared, rtl:no, for executables # "aix,yes" lib.so shared, rtl:yes, for executables # lib.a static archive # "both,no" lib.so.V(shr.o) shared, rtl:yes # lib.a(lib.so.V) shared, rtl:no, for executables # "both,yes" lib.so.V(shr.o) shared, rtl:yes, for executables # lib.a(lib.so.V) shared, rtl:no # "svr4,*" lib.so.V(shr.o) shared, rtl:yes, for executables # lib.a static archive case $host_os in aix4.[[23]]|aix4.[[23]].*|aix[[5-9]]*) for ld_flag in $LDFLAGS; do case $ld_flag in *-brtl*) aix_use_runtimelinking=yes break ;; esac done if test svr4,no = "$with_aix_soname,$aix_use_runtimelinking"; then # With aix-soname=svr4, we create the lib.so.V shared archives only, # so we don't have lib.a shared libs to link our executables. # We have to force runtime linking in this case. aix_use_runtimelinking=yes LDFLAGS="$LDFLAGS -Wl,-brtl" fi ;; esac exp_sym_flag='-bexport' no_entry_flag='-bnoentry' fi # When large executables or shared objects are built, AIX ld can # have problems creating the table of contents. If linking a library # or program results in "error TOC overflow" add -mminimal-toc to # CXXFLAGS/CFLAGS for g++/gcc. In the cases where that is not # enough to fix the problem, add -Wl,-bbigtoc to LDFLAGS. _LT_TAGVAR(archive_cmds, $1)='' _LT_TAGVAR(hardcode_direct, $1)=yes _LT_TAGVAR(hardcode_direct_absolute, $1)=yes _LT_TAGVAR(hardcode_libdir_separator, $1)=':' _LT_TAGVAR(link_all_deplibs, $1)=yes _LT_TAGVAR(file_list_spec, $1)='$wl-f,' case $with_aix_soname,$aix_use_runtimelinking in aix,*) ;; # no import file svr4,* | *,yes) # use import file # The Import File defines what to hardcode. _LT_TAGVAR(hardcode_direct, $1)=no _LT_TAGVAR(hardcode_direct_absolute, $1)=no ;; esac if test yes = "$GXX"; then case $host_os in aix4.[[012]]|aix4.[[012]].*) # We only want to do this on AIX 4.2 and lower, the check # below for broken collect2 doesn't work under 4.3+ collect2name=`$CC -print-prog-name=collect2` if test -f "$collect2name" && strings "$collect2name" | $GREP resolve_lib_name >/dev/null then # We have reworked collect2 : else # We have old collect2 _LT_TAGVAR(hardcode_direct, $1)=unsupported # It fails to find uninstalled libraries when the uninstalled # path is not listed in the libpath. Setting hardcode_minus_L # to unsupported forces relinking _LT_TAGVAR(hardcode_minus_L, $1)=yes _LT_TAGVAR(hardcode_libdir_flag_spec, $1)='-L$libdir' _LT_TAGVAR(hardcode_libdir_separator, $1)= fi esac shared_flag='-shared' if test yes = "$aix_use_runtimelinking"; then shared_flag=$shared_flag' $wl-G' fi # Need to ensure runtime linking is disabled for the traditional # shared library, or the linker may eventually find shared libraries # /with/ Import File - we do not want to mix them. shared_flag_aix='-shared' shared_flag_svr4='-shared $wl-G' else # not using gcc if test ia64 = "$host_cpu"; then # VisualAge C++, Version 5.5 for AIX 5L for IA-64, Beta 3 Release # chokes on -Wl,-G. The following line is correct: shared_flag='-G' else if test yes = "$aix_use_runtimelinking"; then shared_flag='$wl-G' else shared_flag='$wl-bM:SRE' fi shared_flag_aix='$wl-bM:SRE' shared_flag_svr4='$wl-G' fi fi _LT_TAGVAR(export_dynamic_flag_spec, $1)='$wl-bexpall' # It seems that -bexpall does not export symbols beginning with # underscore (_), so it is better to generate a list of symbols to # export. _LT_TAGVAR(always_export_symbols, $1)=yes if test aix,yes = "$with_aix_soname,$aix_use_runtimelinking"; then # Warning - without using the other runtime loading flags (-brtl), # -berok will link without error, but may produce a broken library. # The "-G" linker flag allows undefined symbols. _LT_TAGVAR(no_undefined_flag, $1)='-bernotok' # Determine the default libpath from the value encoded in an empty # executable. _LT_SYS_MODULE_PATH_AIX([$1]) _LT_TAGVAR(hardcode_libdir_flag_spec, $1)='$wl-blibpath:$libdir:'"$aix_libpath" _LT_TAGVAR(archive_expsym_cmds, $1)='$CC -o $output_objdir/$soname $libobjs $deplibs $wl'$no_entry_flag' $compiler_flags `if test -n "$allow_undefined_flag"; then func_echo_all "$wl$allow_undefined_flag"; else :; fi` $wl'$exp_sym_flag:\$export_symbols' '$shared_flag else if test ia64 = "$host_cpu"; then _LT_TAGVAR(hardcode_libdir_flag_spec, $1)='$wl-R $libdir:/usr/lib:/lib' _LT_TAGVAR(allow_undefined_flag, $1)="-z nodefs" _LT_TAGVAR(archive_expsym_cmds, $1)="\$CC $shared_flag"' -o $output_objdir/$soname $libobjs $deplibs '"\$wl$no_entry_flag"' $compiler_flags $wl$allow_undefined_flag '"\$wl$exp_sym_flag:\$export_symbols" else # Determine the default libpath from the value encoded in an # empty executable. _LT_SYS_MODULE_PATH_AIX([$1]) _LT_TAGVAR(hardcode_libdir_flag_spec, $1)='$wl-blibpath:$libdir:'"$aix_libpath" # Warning - without using the other run time loading flags, # -berok will link without error, but may produce a broken library. _LT_TAGVAR(no_undefined_flag, $1)=' $wl-bernotok' _LT_TAGVAR(allow_undefined_flag, $1)=' $wl-berok' if test yes = "$with_gnu_ld"; then # We only use this code for GNU lds that support --whole-archive. _LT_TAGVAR(whole_archive_flag_spec, $1)='$wl--whole-archive$convenience $wl--no-whole-archive' else # Exported symbols can be pulled into shared objects from archives _LT_TAGVAR(whole_archive_flag_spec, $1)='$convenience' fi _LT_TAGVAR(archive_cmds_need_lc, $1)=yes _LT_TAGVAR(archive_expsym_cmds, $1)='$RM -r $output_objdir/$realname.d~$MKDIR $output_objdir/$realname.d' # -brtl affects multiple linker settings, -berok does not and is overridden later compiler_flags_filtered='`func_echo_all "$compiler_flags " | $SED -e "s%-brtl\\([[, ]]\\)%-berok\\1%g"`' if test svr4 != "$with_aix_soname"; then # This is similar to how AIX traditionally builds its shared # libraries. Need -bnortl late, we may have -brtl in LDFLAGS. _LT_TAGVAR(archive_expsym_cmds, $1)="$_LT_TAGVAR(archive_expsym_cmds, $1)"'~$CC '$shared_flag_aix' -o $output_objdir/$realname.d/$soname $libobjs $deplibs $wl-bnoentry '$compiler_flags_filtered'$wl-bE:$export_symbols$allow_undefined_flag~$AR $AR_FLAGS $output_objdir/$libname$release.a $output_objdir/$realname.d/$soname' fi if test aix != "$with_aix_soname"; then _LT_TAGVAR(archive_expsym_cmds, $1)="$_LT_TAGVAR(archive_expsym_cmds, $1)"'~$CC '$shared_flag_svr4' -o $output_objdir/$realname.d/$shared_archive_member_spec.o $libobjs $deplibs $wl-bnoentry '$compiler_flags_filtered'$wl-bE:$export_symbols$allow_undefined_flag~$STRIP -e $output_objdir/$realname.d/$shared_archive_member_spec.o~( func_echo_all "#! $soname($shared_archive_member_spec.o)"; if test shr_64 = "$shared_archive_member_spec"; then func_echo_all "# 64"; else func_echo_all "# 32"; fi; cat $export_symbols ) > $output_objdir/$realname.d/$shared_archive_member_spec.imp~$AR $AR_FLAGS $output_objdir/$soname $output_objdir/$realname.d/$shared_archive_member_spec.o $output_objdir/$realname.d/$shared_archive_member_spec.imp' else # used by -dlpreopen to get the symbols _LT_TAGVAR(archive_expsym_cmds, $1)="$_LT_TAGVAR(archive_expsym_cmds, $1)"'~$MV $output_objdir/$realname.d/$soname $output_objdir' fi _LT_TAGVAR(archive_expsym_cmds, $1)="$_LT_TAGVAR(archive_expsym_cmds, $1)"'~$RM -r $output_objdir/$realname.d' fi fi ;; beos*) if $LD --help 2>&1 | $GREP ': supported targets:.* elf' > /dev/null; then _LT_TAGVAR(allow_undefined_flag, $1)=unsupported # Joseph Beckenbach says some releases of gcc # support --undefined. This deserves some investigation. FIXME _LT_TAGVAR(archive_cmds, $1)='$CC -nostart $libobjs $deplibs $compiler_flags $wl-soname $wl$soname -o $lib' else _LT_TAGVAR(ld_shlibs, $1)=no fi ;; chorus*) case $cc_basename in *) # FIXME: insert proper C++ library support _LT_TAGVAR(ld_shlibs, $1)=no ;; esac ;; cygwin* | mingw* | pw32* | cegcc*) case $GXX,$cc_basename in ,cl* | no,cl*) # Native MSVC # hardcode_libdir_flag_spec is actually meaningless, as there is # no search path for DLLs. _LT_TAGVAR(hardcode_libdir_flag_spec, $1)=' ' _LT_TAGVAR(allow_undefined_flag, $1)=unsupported _LT_TAGVAR(always_export_symbols, $1)=yes _LT_TAGVAR(file_list_spec, $1)='@' # Tell ltmain to make .lib files, not .a files. libext=lib # Tell ltmain to make .dll files, not .so files. shrext_cmds=.dll # FIXME: Setting linknames here is a bad hack. _LT_TAGVAR(archive_cmds, $1)='$CC -o $output_objdir/$soname $libobjs $compiler_flags $deplibs -Wl,-DLL,-IMPLIB:"$tool_output_objdir$libname.dll.lib"~linknames=' _LT_TAGVAR(archive_expsym_cmds, $1)='if _LT_DLL_DEF_P([$export_symbols]); then cp "$export_symbols" "$output_objdir/$soname.def"; echo "$tool_output_objdir$soname.def" > "$output_objdir/$soname.exp"; else $SED -e '\''s/^/-link -EXPORT:/'\'' < $export_symbols > $output_objdir/$soname.exp; fi~ $CC -o $tool_output_objdir$soname $libobjs $compiler_flags $deplibs "@$tool_output_objdir$soname.exp" -Wl,-DLL,-IMPLIB:"$tool_output_objdir$libname.dll.lib"~ linknames=' # The linker will not automatically build a static lib if we build a DLL. # _LT_TAGVAR(old_archive_from_new_cmds, $1)='true' _LT_TAGVAR(enable_shared_with_static_runtimes, $1)=yes # Don't use ranlib _LT_TAGVAR(old_postinstall_cmds, $1)='chmod 644 $oldlib' _LT_TAGVAR(postlink_cmds, $1)='lt_outputfile="@OUTPUT@"~ lt_tool_outputfile="@TOOL_OUTPUT@"~ case $lt_outputfile in *.exe|*.EXE) ;; *) lt_outputfile=$lt_outputfile.exe lt_tool_outputfile=$lt_tool_outputfile.exe ;; esac~ func_to_tool_file "$lt_outputfile"~ if test : != "$MANIFEST_TOOL" && test -f "$lt_outputfile.manifest"; then $MANIFEST_TOOL -manifest "$lt_tool_outputfile.manifest" -outputresource:"$lt_tool_outputfile" || exit 1; $RM "$lt_outputfile.manifest"; fi' ;; *) # g++ # _LT_TAGVAR(hardcode_libdir_flag_spec, $1) is actually meaningless, # as there is no search path for DLLs. _LT_TAGVAR(hardcode_libdir_flag_spec, $1)='-L$libdir' _LT_TAGVAR(export_dynamic_flag_spec, $1)='$wl--export-all-symbols' _LT_TAGVAR(allow_undefined_flag, $1)=unsupported _LT_TAGVAR(always_export_symbols, $1)=no _LT_TAGVAR(enable_shared_with_static_runtimes, $1)=yes if $LD --help 2>&1 | $GREP 'auto-import' > /dev/null; then _LT_TAGVAR(archive_cmds, $1)='$CC -shared -nostdlib $predep_objects $libobjs $deplibs $postdep_objects $compiler_flags -o $output_objdir/$soname $wl--enable-auto-image-base -Xlinker --out-implib -Xlinker $lib' # If the export-symbols file already is a .def file, use it as # is; otherwise, prepend EXPORTS... _LT_TAGVAR(archive_expsym_cmds, $1)='if _LT_DLL_DEF_P([$export_symbols]); then cp $export_symbols $output_objdir/$soname.def; else echo EXPORTS > $output_objdir/$soname.def; cat $export_symbols >> $output_objdir/$soname.def; fi~ $CC -shared -nostdlib $output_objdir/$soname.def $predep_objects $libobjs $deplibs $postdep_objects $compiler_flags -o $output_objdir/$soname $wl--enable-auto-image-base -Xlinker --out-implib -Xlinker $lib' else _LT_TAGVAR(ld_shlibs, $1)=no fi ;; esac ;; darwin* | rhapsody*) _LT_DARWIN_LINKER_FEATURES($1) ;; os2*) _LT_TAGVAR(hardcode_libdir_flag_spec, $1)='-L$libdir' _LT_TAGVAR(hardcode_minus_L, $1)=yes _LT_TAGVAR(allow_undefined_flag, $1)=unsupported shrext_cmds=.dll _LT_TAGVAR(archive_cmds, $1)='$ECHO "LIBRARY ${soname%$shared_ext} INITINSTANCE TERMINSTANCE" > $output_objdir/$libname.def~ $ECHO "DESCRIPTION \"$libname\"" >> $output_objdir/$libname.def~ $ECHO "DATA MULTIPLE NONSHARED" >> $output_objdir/$libname.def~ $ECHO EXPORTS >> $output_objdir/$libname.def~ emxexp $libobjs | $SED /"_DLL_InitTerm"/d >> $output_objdir/$libname.def~ $CC -Zdll -Zcrtdll -o $output_objdir/$soname $libobjs $deplibs $compiler_flags $output_objdir/$libname.def~ emximp -o $lib $output_objdir/$libname.def' _LT_TAGVAR(archive_expsym_cmds, $1)='$ECHO "LIBRARY ${soname%$shared_ext} INITINSTANCE TERMINSTANCE" > $output_objdir/$libname.def~ $ECHO "DESCRIPTION \"$libname\"" >> $output_objdir/$libname.def~ $ECHO "DATA MULTIPLE NONSHARED" >> $output_objdir/$libname.def~ $ECHO EXPORTS >> $output_objdir/$libname.def~ prefix_cmds="$SED"~ if test EXPORTS = "`$SED 1q $export_symbols`"; then prefix_cmds="$prefix_cmds -e 1d"; fi~ prefix_cmds="$prefix_cmds -e \"s/^\(.*\)$/_\1/g\""~ cat $export_symbols | $prefix_cmds >> $output_objdir/$libname.def~ $CC -Zdll -Zcrtdll -o $output_objdir/$soname $libobjs $deplibs $compiler_flags $output_objdir/$libname.def~ emximp -o $lib $output_objdir/$libname.def' _LT_TAGVAR(old_archive_From_new_cmds, $1)='emximp -o $output_objdir/${libname}_dll.a $output_objdir/$libname.def' _LT_TAGVAR(enable_shared_with_static_runtimes, $1)=yes ;; dgux*) case $cc_basename in ec++*) # FIXME: insert proper C++ library support _LT_TAGVAR(ld_shlibs, $1)=no ;; ghcx*) # Green Hills C++ Compiler # FIXME: insert proper C++ library support _LT_TAGVAR(ld_shlibs, $1)=no ;; *) # FIXME: insert proper C++ library support _LT_TAGVAR(ld_shlibs, $1)=no ;; esac ;; freebsd2.*) # C++ shared libraries reported to be fairly broken before # switch to ELF _LT_TAGVAR(ld_shlibs, $1)=no ;; freebsd-elf*) _LT_TAGVAR(archive_cmds_need_lc, $1)=no ;; freebsd* | dragonfly*) # FreeBSD 3 and later use GNU C++ and GNU ld with standard ELF # conventions _LT_TAGVAR(ld_shlibs, $1)=yes ;; haiku*) _LT_TAGVAR(archive_cmds, $1)='$CC -shared $libobjs $deplibs $compiler_flags $wl-soname $wl$soname -o $lib' _LT_TAGVAR(link_all_deplibs, $1)=yes ;; hpux9*) _LT_TAGVAR(hardcode_libdir_flag_spec, $1)='$wl+b $wl$libdir' _LT_TAGVAR(hardcode_libdir_separator, $1)=: _LT_TAGVAR(export_dynamic_flag_spec, $1)='$wl-E' _LT_TAGVAR(hardcode_direct, $1)=yes _LT_TAGVAR(hardcode_minus_L, $1)=yes # Not in the search PATH, # but as the default # location of the library. case $cc_basename in CC*) # FIXME: insert proper C++ library support _LT_TAGVAR(ld_shlibs, $1)=no ;; aCC*) _LT_TAGVAR(archive_cmds, $1)='$RM $output_objdir/$soname~$CC -b $wl+b $wl$install_libdir -o $output_objdir/$soname $predep_objects $libobjs $deplibs $postdep_objects $compiler_flags~test "x$output_objdir/$soname" = "x$lib" || mv $output_objdir/$soname $lib' # Commands to make compiler produce verbose output that lists # what "hidden" libraries, object files and flags are used when # linking a shared library. # # There doesn't appear to be a way to prevent this compiler from # explicitly linking system object files so we need to strip them # from the output so that they don't get included in the library # dependencies. output_verbose_link_cmd='templist=`($CC -b $CFLAGS -v conftest.$objext 2>&1) | $EGREP " \-L"`; list= ; for z in $templist; do case $z in conftest.$objext) list="$list $z";; *.$objext);; *) list="$list $z";;esac; done; func_echo_all "$list"' ;; *) if test yes = "$GXX"; then _LT_TAGVAR(archive_cmds, $1)='$RM $output_objdir/$soname~$CC -shared -nostdlib $pic_flag $wl+b $wl$install_libdir -o $output_objdir/$soname $predep_objects $libobjs $deplibs $postdep_objects $compiler_flags~test "x$output_objdir/$soname" = "x$lib" || mv $output_objdir/$soname $lib' else # FIXME: insert proper C++ library support _LT_TAGVAR(ld_shlibs, $1)=no fi ;; esac ;; hpux10*|hpux11*) if test no = "$with_gnu_ld"; then _LT_TAGVAR(hardcode_libdir_flag_spec, $1)='$wl+b $wl$libdir' _LT_TAGVAR(hardcode_libdir_separator, $1)=: case $host_cpu in hppa*64*|ia64*) ;; *) _LT_TAGVAR(export_dynamic_flag_spec, $1)='$wl-E' ;; esac fi case $host_cpu in hppa*64*|ia64*) _LT_TAGVAR(hardcode_direct, $1)=no _LT_TAGVAR(hardcode_shlibpath_var, $1)=no ;; *) _LT_TAGVAR(hardcode_direct, $1)=yes _LT_TAGVAR(hardcode_direct_absolute, $1)=yes _LT_TAGVAR(hardcode_minus_L, $1)=yes # Not in the search PATH, # but as the default # location of the library. ;; esac case $cc_basename in CC*) # FIXME: insert proper C++ library support _LT_TAGVAR(ld_shlibs, $1)=no ;; aCC*) case $host_cpu in hppa*64*) _LT_TAGVAR(archive_cmds, $1)='$CC -b $wl+h $wl$soname -o $lib $predep_objects $libobjs $deplibs $postdep_objects $compiler_flags' ;; ia64*) _LT_TAGVAR(archive_cmds, $1)='$CC -b $wl+h $wl$soname $wl+nodefaultrpath -o $lib $predep_objects $libobjs $deplibs $postdep_objects $compiler_flags' ;; *) _LT_TAGVAR(archive_cmds, $1)='$CC -b $wl+h $wl$soname $wl+b $wl$install_libdir -o $lib $predep_objects $libobjs $deplibs $postdep_objects $compiler_flags' ;; esac # Commands to make compiler produce verbose output that lists # what "hidden" libraries, object files and flags are used when # linking a shared library. # # There doesn't appear to be a way to prevent this compiler from # explicitly linking system object files so we need to strip them # from the output so that they don't get included in the library # dependencies. output_verbose_link_cmd='templist=`($CC -b $CFLAGS -v conftest.$objext 2>&1) | $GREP " \-L"`; list= ; for z in $templist; do case $z in conftest.$objext) list="$list $z";; *.$objext);; *) list="$list $z";;esac; done; func_echo_all "$list"' ;; *) if test yes = "$GXX"; then if test no = "$with_gnu_ld"; then case $host_cpu in hppa*64*) _LT_TAGVAR(archive_cmds, $1)='$CC -shared -nostdlib -fPIC $wl+h $wl$soname -o $lib $predep_objects $libobjs $deplibs $postdep_objects $compiler_flags' ;; ia64*) _LT_TAGVAR(archive_cmds, $1)='$CC -shared -nostdlib $pic_flag $wl+h $wl$soname $wl+nodefaultrpath -o $lib $predep_objects $libobjs $deplibs $postdep_objects $compiler_flags' ;; *) _LT_TAGVAR(archive_cmds, $1)='$CC -shared -nostdlib $pic_flag $wl+h $wl$soname $wl+b $wl$install_libdir -o $lib $predep_objects $libobjs $deplibs $postdep_objects $compiler_flags' ;; esac fi else # FIXME: insert proper C++ library support _LT_TAGVAR(ld_shlibs, $1)=no fi ;; esac ;; interix[[3-9]]*) _LT_TAGVAR(hardcode_direct, $1)=no _LT_TAGVAR(hardcode_shlibpath_var, $1)=no _LT_TAGVAR(hardcode_libdir_flag_spec, $1)='$wl-rpath,$libdir' _LT_TAGVAR(export_dynamic_flag_spec, $1)='$wl-E' # Hack: On Interix 3.x, we cannot compile PIC because of a broken gcc. # Instead, shared libraries are loaded at an image base (0x10000000 by # default) and relocated if they conflict, which is a slow very memory # consuming and fragmenting process. To avoid this, we pick a random, # 256 KiB-aligned image base between 0x50000000 and 0x6FFC0000 at link # time. Moving up from 0x10000000 also allows more sbrk(2) space. _LT_TAGVAR(archive_cmds, $1)='$CC -shared $pic_flag $libobjs $deplibs $compiler_flags $wl-h,$soname $wl--image-base,`expr ${RANDOM-$$} % 4096 / 2 \* 262144 + 1342177280` -o $lib' _LT_TAGVAR(archive_expsym_cmds, $1)='sed "s|^|_|" $export_symbols >$output_objdir/$soname.expsym~$CC -shared $pic_flag $libobjs $deplibs $compiler_flags $wl-h,$soname $wl--retain-symbols-file,$output_objdir/$soname.expsym $wl--image-base,`expr ${RANDOM-$$} % 4096 / 2 \* 262144 + 1342177280` -o $lib' ;; irix5* | irix6*) case $cc_basename in CC*) # SGI C++ _LT_TAGVAR(archive_cmds, $1)='$CC -shared -all -multigot $predep_objects $libobjs $deplibs $postdep_objects $compiler_flags -soname $soname `test -n "$verstring" && func_echo_all "-set_version $verstring"` -update_registry $output_objdir/so_locations -o $lib' # Archives containing C++ object files must be created using # "CC -ar", where "CC" is the IRIX C++ compiler. This is # necessary to make sure instantiated templates are included # in the archive. _LT_TAGVAR(old_archive_cmds, $1)='$CC -ar -WR,-u -o $oldlib $oldobjs' ;; *) if test yes = "$GXX"; then if test no = "$with_gnu_ld"; then _LT_TAGVAR(archive_cmds, $1)='$CC -shared $pic_flag -nostdlib $predep_objects $libobjs $deplibs $postdep_objects $compiler_flags $wl-soname $wl$soname `test -n "$verstring" && func_echo_all "$wl-set_version $wl$verstring"` $wl-update_registry $wl$output_objdir/so_locations -o $lib' else _LT_TAGVAR(archive_cmds, $1)='$CC -shared $pic_flag -nostdlib $predep_objects $libobjs $deplibs $postdep_objects $compiler_flags $wl-soname $wl$soname `test -n "$verstring" && func_echo_all "$wl-set_version $wl$verstring"` -o $lib' fi fi _LT_TAGVAR(link_all_deplibs, $1)=yes ;; esac _LT_TAGVAR(hardcode_libdir_flag_spec, $1)='$wl-rpath $wl$libdir' _LT_TAGVAR(hardcode_libdir_separator, $1)=: _LT_TAGVAR(inherit_rpath, $1)=yes ;; linux* | k*bsd*-gnu | kopensolaris*-gnu | gnu*) case $cc_basename in KCC*) # Kuck and Associates, Inc. (KAI) C++ Compiler # KCC will only create a shared library if the output file # ends with ".so" (or ".sl" for HP-UX), so rename the library # to its proper name (with version) after linking. _LT_TAGVAR(archive_cmds, $1)='tempext=`echo $shared_ext | $SED -e '\''s/\([[^()0-9A-Za-z{}]]\)/\\\\\1/g'\''`; templib=`echo $lib | $SED -e "s/\$tempext\..*/.so/"`; $CC $predep_objects $libobjs $deplibs $postdep_objects $compiler_flags --soname $soname -o \$templib; mv \$templib $lib' _LT_TAGVAR(archive_expsym_cmds, $1)='tempext=`echo $shared_ext | $SED -e '\''s/\([[^()0-9A-Za-z{}]]\)/\\\\\1/g'\''`; templib=`echo $lib | $SED -e "s/\$tempext\..*/.so/"`; $CC $predep_objects $libobjs $deplibs $postdep_objects $compiler_flags --soname $soname -o \$templib $wl-retain-symbols-file,$export_symbols; mv \$templib $lib' # Commands to make compiler produce verbose output that lists # what "hidden" libraries, object files and flags are used when # linking a shared library. # # There doesn't appear to be a way to prevent this compiler from # explicitly linking system object files so we need to strip them # from the output so that they don't get included in the library # dependencies. output_verbose_link_cmd='templist=`$CC $CFLAGS -v conftest.$objext -o libconftest$shared_ext 2>&1 | $GREP "ld"`; rm -f libconftest$shared_ext; list= ; for z in $templist; do case $z in conftest.$objext) list="$list $z";; *.$objext);; *) list="$list $z";;esac; done; func_echo_all "$list"' _LT_TAGVAR(hardcode_libdir_flag_spec, $1)='$wl-rpath,$libdir' _LT_TAGVAR(export_dynamic_flag_spec, $1)='$wl--export-dynamic' # Archives containing C++ object files must be created using # "CC -Bstatic", where "CC" is the KAI C++ compiler. _LT_TAGVAR(old_archive_cmds, $1)='$CC -Bstatic -o $oldlib $oldobjs' ;; icpc* | ecpc* ) # Intel C++ with_gnu_ld=yes # version 8.0 and above of icpc choke on multiply defined symbols # if we add $predep_objects and $postdep_objects, however 7.1 and # earlier do not add the objects themselves. case `$CC -V 2>&1` in *"Version 7."*) _LT_TAGVAR(archive_cmds, $1)='$CC -shared $predep_objects $libobjs $deplibs $postdep_objects $compiler_flags $wl-soname $wl$soname -o $lib' _LT_TAGVAR(archive_expsym_cmds, $1)='$CC -shared $predep_objects $libobjs $deplibs $postdep_objects $compiler_flags $wl-soname $wl$soname $wl-retain-symbols-file $wl$export_symbols -o $lib' ;; *) # Version 8.0 or newer tmp_idyn= case $host_cpu in ia64*) tmp_idyn=' -i_dynamic';; esac _LT_TAGVAR(archive_cmds, $1)='$CC -shared'"$tmp_idyn"' $libobjs $deplibs $compiler_flags $wl-soname $wl$soname -o $lib' _LT_TAGVAR(archive_expsym_cmds, $1)='$CC -shared'"$tmp_idyn"' $libobjs $deplibs $compiler_flags $wl-soname $wl$soname $wl-retain-symbols-file $wl$export_symbols -o $lib' ;; esac _LT_TAGVAR(archive_cmds_need_lc, $1)=no _LT_TAGVAR(hardcode_libdir_flag_spec, $1)='$wl-rpath,$libdir' _LT_TAGVAR(export_dynamic_flag_spec, $1)='$wl--export-dynamic' _LT_TAGVAR(whole_archive_flag_spec, $1)='$wl--whole-archive$convenience $wl--no-whole-archive' ;; pgCC* | pgcpp*) # Portland Group C++ compiler case `$CC -V` in *pgCC\ [[1-5]].* | *pgcpp\ [[1-5]].*) _LT_TAGVAR(prelink_cmds, $1)='tpldir=Template.dir~ rm -rf $tpldir~ $CC --prelink_objects --instantiation_dir $tpldir $objs $libobjs $compile_deplibs~ compile_command="$compile_command `find $tpldir -name \*.o | sort | $NL2SP`"' _LT_TAGVAR(old_archive_cmds, $1)='tpldir=Template.dir~ rm -rf $tpldir~ $CC --prelink_objects --instantiation_dir $tpldir $oldobjs$old_deplibs~ $AR $AR_FLAGS $oldlib$oldobjs$old_deplibs `find $tpldir -name \*.o | sort | $NL2SP`~ $RANLIB $oldlib' _LT_TAGVAR(archive_cmds, $1)='tpldir=Template.dir~ rm -rf $tpldir~ $CC --prelink_objects --instantiation_dir $tpldir $predep_objects $libobjs $deplibs $convenience $postdep_objects~ $CC -shared $pic_flag $predep_objects $libobjs $deplibs `find $tpldir -name \*.o | sort | $NL2SP` $postdep_objects $compiler_flags $wl-soname $wl$soname -o $lib' _LT_TAGVAR(archive_expsym_cmds, $1)='tpldir=Template.dir~ rm -rf $tpldir~ $CC --prelink_objects --instantiation_dir $tpldir $predep_objects $libobjs $deplibs $convenience $postdep_objects~ $CC -shared $pic_flag $predep_objects $libobjs $deplibs `find $tpldir -name \*.o | sort | $NL2SP` $postdep_objects $compiler_flags $wl-soname $wl$soname $wl-retain-symbols-file $wl$export_symbols -o $lib' ;; *) # Version 6 and above use weak symbols _LT_TAGVAR(archive_cmds, $1)='$CC -shared $pic_flag $predep_objects $libobjs $deplibs $postdep_objects $compiler_flags $wl-soname $wl$soname -o $lib' _LT_TAGVAR(archive_expsym_cmds, $1)='$CC -shared $pic_flag $predep_objects $libobjs $deplibs $postdep_objects $compiler_flags $wl-soname $wl$soname $wl-retain-symbols-file $wl$export_symbols -o $lib' ;; esac _LT_TAGVAR(hardcode_libdir_flag_spec, $1)='$wl--rpath $wl$libdir' _LT_TAGVAR(export_dynamic_flag_spec, $1)='$wl--export-dynamic' _LT_TAGVAR(whole_archive_flag_spec, $1)='$wl--whole-archive`for conv in $convenience\"\"; do test -n \"$conv\" && new_convenience=\"$new_convenience,$conv\"; done; func_echo_all \"$new_convenience\"` $wl--no-whole-archive' ;; cxx*) # Compaq C++ _LT_TAGVAR(archive_cmds, $1)='$CC -shared $predep_objects $libobjs $deplibs $postdep_objects $compiler_flags $wl-soname $wl$soname -o $lib' _LT_TAGVAR(archive_expsym_cmds, $1)='$CC -shared $predep_objects $libobjs $deplibs $postdep_objects $compiler_flags $wl-soname $wl$soname -o $lib $wl-retain-symbols-file $wl$export_symbols' runpath_var=LD_RUN_PATH _LT_TAGVAR(hardcode_libdir_flag_spec, $1)='-rpath $libdir' _LT_TAGVAR(hardcode_libdir_separator, $1)=: # Commands to make compiler produce verbose output that lists # what "hidden" libraries, object files and flags are used when # linking a shared library. # # There doesn't appear to be a way to prevent this compiler from # explicitly linking system object files so we need to strip them # from the output so that they don't get included in the library # dependencies. output_verbose_link_cmd='templist=`$CC -shared $CFLAGS -v conftest.$objext 2>&1 | $GREP "ld"`; templist=`func_echo_all "$templist" | $SED "s/\(^.*ld.*\)\( .*ld .*$\)/\1/"`; list= ; for z in $templist; do case $z in conftest.$objext) list="$list $z";; *.$objext);; *) list="$list $z";;esac; done; func_echo_all "X$list" | $Xsed' ;; xl* | mpixl* | bgxl*) # IBM XL 8.0 on PPC, with GNU ld _LT_TAGVAR(hardcode_libdir_flag_spec, $1)='$wl-rpath $wl$libdir' _LT_TAGVAR(export_dynamic_flag_spec, $1)='$wl--export-dynamic' _LT_TAGVAR(archive_cmds, $1)='$CC -qmkshrobj $libobjs $deplibs $compiler_flags $wl-soname $wl$soname -o $lib' if test yes = "$supports_anon_versioning"; then _LT_TAGVAR(archive_expsym_cmds, $1)='echo "{ global:" > $output_objdir/$libname.ver~ cat $export_symbols | sed -e "s/\(.*\)/\1;/" >> $output_objdir/$libname.ver~ echo "local: *; };" >> $output_objdir/$libname.ver~ $CC -qmkshrobj $libobjs $deplibs $compiler_flags $wl-soname $wl$soname $wl-version-script $wl$output_objdir/$libname.ver -o $lib' fi ;; *) case `$CC -V 2>&1 | sed 5q` in *Sun\ C*) # Sun C++ 5.9 _LT_TAGVAR(no_undefined_flag, $1)=' -zdefs' _LT_TAGVAR(archive_cmds, $1)='$CC -G$allow_undefined_flag -h$soname -o $lib $predep_objects $libobjs $deplibs $postdep_objects $compiler_flags' _LT_TAGVAR(archive_expsym_cmds, $1)='$CC -G$allow_undefined_flag -h$soname -o $lib $predep_objects $libobjs $deplibs $postdep_objects $compiler_flags $wl-retain-symbols-file $wl$export_symbols' _LT_TAGVAR(hardcode_libdir_flag_spec, $1)='-R$libdir' _LT_TAGVAR(whole_archive_flag_spec, $1)='$wl--whole-archive`new_convenience=; for conv in $convenience\"\"; do test -z \"$conv\" || new_convenience=\"$new_convenience,$conv\"; done; func_echo_all \"$new_convenience\"` $wl--no-whole-archive' _LT_TAGVAR(compiler_needs_object, $1)=yes # Not sure whether something based on # $CC $CFLAGS -v conftest.$objext -o libconftest$shared_ext 2>&1 # would be better. output_verbose_link_cmd='func_echo_all' # Archives containing C++ object files must be created using # "CC -xar", where "CC" is the Sun C++ compiler. This is # necessary to make sure instantiated templates are included # in the archive. _LT_TAGVAR(old_archive_cmds, $1)='$CC -xar -o $oldlib $oldobjs' ;; esac ;; esac ;; lynxos*) # FIXME: insert proper C++ library support _LT_TAGVAR(ld_shlibs, $1)=no ;; m88k*) # FIXME: insert proper C++ library support _LT_TAGVAR(ld_shlibs, $1)=no ;; mvs*) case $cc_basename in cxx*) # FIXME: insert proper C++ library support _LT_TAGVAR(ld_shlibs, $1)=no ;; *) # FIXME: insert proper C++ library support _LT_TAGVAR(ld_shlibs, $1)=no ;; esac ;; netbsd*) if echo __ELF__ | $CC -E - | $GREP __ELF__ >/dev/null; then _LT_TAGVAR(archive_cmds, $1)='$LD -Bshareable -o $lib $predep_objects $libobjs $deplibs $postdep_objects $linker_flags' wlarc= _LT_TAGVAR(hardcode_libdir_flag_spec, $1)='-R$libdir' _LT_TAGVAR(hardcode_direct, $1)=yes _LT_TAGVAR(hardcode_shlibpath_var, $1)=no fi # Workaround some broken pre-1.5 toolchains output_verbose_link_cmd='$CC -shared $CFLAGS -v conftest.$objext 2>&1 | $GREP conftest.$objext | $SED -e "s:-lgcc -lc -lgcc::"' ;; *nto* | *qnx*) _LT_TAGVAR(ld_shlibs, $1)=yes ;; openbsd* | bitrig*) if test -f /usr/libexec/ld.so; then _LT_TAGVAR(hardcode_direct, $1)=yes _LT_TAGVAR(hardcode_shlibpath_var, $1)=no _LT_TAGVAR(hardcode_direct_absolute, $1)=yes _LT_TAGVAR(archive_cmds, $1)='$CC -shared $pic_flag $predep_objects $libobjs $deplibs $postdep_objects $compiler_flags -o $lib' _LT_TAGVAR(hardcode_libdir_flag_spec, $1)='$wl-rpath,$libdir' if test -z "`echo __ELF__ | $CC -E - | grep __ELF__`"; then _LT_TAGVAR(archive_expsym_cmds, $1)='$CC -shared $pic_flag $predep_objects $libobjs $deplibs $postdep_objects $compiler_flags $wl-retain-symbols-file,$export_symbols -o $lib' _LT_TAGVAR(export_dynamic_flag_spec, $1)='$wl-E' _LT_TAGVAR(whole_archive_flag_spec, $1)=$wlarc'--whole-archive$convenience '$wlarc'--no-whole-archive' fi output_verbose_link_cmd=func_echo_all else _LT_TAGVAR(ld_shlibs, $1)=no fi ;; osf3* | osf4* | osf5*) case $cc_basename in KCC*) # Kuck and Associates, Inc. (KAI) C++ Compiler # KCC will only create a shared library if the output file # ends with ".so" (or ".sl" for HP-UX), so rename the library # to its proper name (with version) after linking. _LT_TAGVAR(archive_cmds, $1)='tempext=`echo $shared_ext | $SED -e '\''s/\([[^()0-9A-Za-z{}]]\)/\\\\\1/g'\''`; templib=`echo "$lib" | $SED -e "s/\$tempext\..*/.so/"`; $CC $predep_objects $libobjs $deplibs $postdep_objects $compiler_flags --soname $soname -o \$templib; mv \$templib $lib' _LT_TAGVAR(hardcode_libdir_flag_spec, $1)='$wl-rpath,$libdir' _LT_TAGVAR(hardcode_libdir_separator, $1)=: # Archives containing C++ object files must be created using # the KAI C++ compiler. case $host in osf3*) _LT_TAGVAR(old_archive_cmds, $1)='$CC -Bstatic -o $oldlib $oldobjs' ;; *) _LT_TAGVAR(old_archive_cmds, $1)='$CC -o $oldlib $oldobjs' ;; esac ;; RCC*) # Rational C++ 2.4.1 # FIXME: insert proper C++ library support _LT_TAGVAR(ld_shlibs, $1)=no ;; cxx*) case $host in osf3*) _LT_TAGVAR(allow_undefined_flag, $1)=' $wl-expect_unresolved $wl\*' _LT_TAGVAR(archive_cmds, $1)='$CC -shared$allow_undefined_flag $predep_objects $libobjs $deplibs $postdep_objects $compiler_flags $wl-soname $soname `test -n "$verstring" && func_echo_all "$wl-set_version $verstring"` -update_registry $output_objdir/so_locations -o $lib' _LT_TAGVAR(hardcode_libdir_flag_spec, $1)='$wl-rpath $wl$libdir' ;; *) _LT_TAGVAR(allow_undefined_flag, $1)=' -expect_unresolved \*' _LT_TAGVAR(archive_cmds, $1)='$CC -shared$allow_undefined_flag $predep_objects $libobjs $deplibs $postdep_objects $compiler_flags -msym -soname $soname `test -n "$verstring" && func_echo_all "-set_version $verstring"` -update_registry $output_objdir/so_locations -o $lib' _LT_TAGVAR(archive_expsym_cmds, $1)='for i in `cat $export_symbols`; do printf "%s %s\\n" -exported_symbol "\$i" >> $lib.exp; done~ echo "-hidden">> $lib.exp~ $CC -shared$allow_undefined_flag $predep_objects $libobjs $deplibs $postdep_objects $compiler_flags -msym -soname $soname $wl-input $wl$lib.exp `test -n "$verstring" && $ECHO "-set_version $verstring"` -update_registry $output_objdir/so_locations -o $lib~ $RM $lib.exp' _LT_TAGVAR(hardcode_libdir_flag_spec, $1)='-rpath $libdir' ;; esac _LT_TAGVAR(hardcode_libdir_separator, $1)=: # Commands to make compiler produce verbose output that lists # what "hidden" libraries, object files and flags are used when # linking a shared library. # # There doesn't appear to be a way to prevent this compiler from # explicitly linking system object files so we need to strip them # from the output so that they don't get included in the library # dependencies. output_verbose_link_cmd='templist=`$CC -shared $CFLAGS -v conftest.$objext 2>&1 | $GREP "ld" | $GREP -v "ld:"`; templist=`func_echo_all "$templist" | $SED "s/\(^.*ld.*\)\( .*ld.*$\)/\1/"`; list= ; for z in $templist; do case $z in conftest.$objext) list="$list $z";; *.$objext);; *) list="$list $z";;esac; done; func_echo_all "$list"' ;; *) if test yes,no = "$GXX,$with_gnu_ld"; then _LT_TAGVAR(allow_undefined_flag, $1)=' $wl-expect_unresolved $wl\*' case $host in osf3*) _LT_TAGVAR(archive_cmds, $1)='$CC -shared -nostdlib $allow_undefined_flag $predep_objects $libobjs $deplibs $postdep_objects $compiler_flags $wl-soname $wl$soname `test -n "$verstring" && func_echo_all "$wl-set_version $wl$verstring"` $wl-update_registry $wl$output_objdir/so_locations -o $lib' ;; *) _LT_TAGVAR(archive_cmds, $1)='$CC -shared $pic_flag -nostdlib $allow_undefined_flag $predep_objects $libobjs $deplibs $postdep_objects $compiler_flags $wl-msym $wl-soname $wl$soname `test -n "$verstring" && func_echo_all "$wl-set_version $wl$verstring"` $wl-update_registry $wl$output_objdir/so_locations -o $lib' ;; esac _LT_TAGVAR(hardcode_libdir_flag_spec, $1)='$wl-rpath $wl$libdir' _LT_TAGVAR(hardcode_libdir_separator, $1)=: # Commands to make compiler produce verbose output that lists # what "hidden" libraries, object files and flags are used when # linking a shared library. output_verbose_link_cmd='$CC -shared $CFLAGS -v conftest.$objext 2>&1 | $GREP -v "^Configured with:" | $GREP " \-L"' else # FIXME: insert proper C++ library support _LT_TAGVAR(ld_shlibs, $1)=no fi ;; esac ;; psos*) # FIXME: insert proper C++ library support _LT_TAGVAR(ld_shlibs, $1)=no ;; sunos4*) case $cc_basename in CC*) # Sun C++ 4.x # FIXME: insert proper C++ library support _LT_TAGVAR(ld_shlibs, $1)=no ;; lcc*) # Lucid # FIXME: insert proper C++ library support _LT_TAGVAR(ld_shlibs, $1)=no ;; *) # FIXME: insert proper C++ library support _LT_TAGVAR(ld_shlibs, $1)=no ;; esac ;; solaris*) case $cc_basename in CC* | sunCC*) # Sun C++ 4.2, 5.x and Centerline C++ _LT_TAGVAR(archive_cmds_need_lc,$1)=yes _LT_TAGVAR(no_undefined_flag, $1)=' -zdefs' _LT_TAGVAR(archive_cmds, $1)='$CC -G$allow_undefined_flag -h$soname -o $lib $predep_objects $libobjs $deplibs $postdep_objects $compiler_flags' _LT_TAGVAR(archive_expsym_cmds, $1)='echo "{ global:" > $lib.exp~cat $export_symbols | $SED -e "s/\(.*\)/\1;/" >> $lib.exp~echo "local: *; };" >> $lib.exp~ $CC -G$allow_undefined_flag $wl-M $wl$lib.exp -h$soname -o $lib $predep_objects $libobjs $deplibs $postdep_objects $compiler_flags~$RM $lib.exp' _LT_TAGVAR(hardcode_libdir_flag_spec, $1)='-R$libdir' _LT_TAGVAR(hardcode_shlibpath_var, $1)=no case $host_os in solaris2.[[0-5]] | solaris2.[[0-5]].*) ;; *) # The compiler driver will combine and reorder linker options, # but understands '-z linker_flag'. # Supported since Solaris 2.6 (maybe 2.5.1?) _LT_TAGVAR(whole_archive_flag_spec, $1)='-z allextract$convenience -z defaultextract' ;; esac _LT_TAGVAR(link_all_deplibs, $1)=yes output_verbose_link_cmd='func_echo_all' # Archives containing C++ object files must be created using # "CC -xar", where "CC" is the Sun C++ compiler. This is # necessary to make sure instantiated templates are included # in the archive. _LT_TAGVAR(old_archive_cmds, $1)='$CC -xar -o $oldlib $oldobjs' ;; gcx*) # Green Hills C++ Compiler _LT_TAGVAR(archive_cmds, $1)='$CC -shared $predep_objects $libobjs $deplibs $postdep_objects $compiler_flags $wl-h $wl$soname -o $lib' # The C++ compiler must be used to create the archive. _LT_TAGVAR(old_archive_cmds, $1)='$CC $LDFLAGS -archive -o $oldlib $oldobjs' ;; *) # GNU C++ compiler with Solaris linker if test yes,no = "$GXX,$with_gnu_ld"; then _LT_TAGVAR(no_undefined_flag, $1)=' $wl-z ${wl}defs' if $CC --version | $GREP -v '^2\.7' > /dev/null; then _LT_TAGVAR(archive_cmds, $1)='$CC -shared $pic_flag -nostdlib $predep_objects $libobjs $deplibs $postdep_objects $compiler_flags $wl-h $wl$soname -o $lib' _LT_TAGVAR(archive_expsym_cmds, $1)='echo "{ global:" > $lib.exp~cat $export_symbols | $SED -e "s/\(.*\)/\1;/" >> $lib.exp~echo "local: *; };" >> $lib.exp~ $CC -shared $pic_flag -nostdlib $wl-M $wl$lib.exp $wl-h $wl$soname -o $lib $predep_objects $libobjs $deplibs $postdep_objects $compiler_flags~$RM $lib.exp' # Commands to make compiler produce verbose output that lists # what "hidden" libraries, object files and flags are used when # linking a shared library. output_verbose_link_cmd='$CC -shared $CFLAGS -v conftest.$objext 2>&1 | $GREP -v "^Configured with:" | $GREP " \-L"' else # g++ 2.7 appears to require '-G' NOT '-shared' on this # platform. _LT_TAGVAR(archive_cmds, $1)='$CC -G -nostdlib $predep_objects $libobjs $deplibs $postdep_objects $compiler_flags $wl-h $wl$soname -o $lib' _LT_TAGVAR(archive_expsym_cmds, $1)='echo "{ global:" > $lib.exp~cat $export_symbols | $SED -e "s/\(.*\)/\1;/" >> $lib.exp~echo "local: *; };" >> $lib.exp~ $CC -G -nostdlib $wl-M $wl$lib.exp $wl-h $wl$soname -o $lib $predep_objects $libobjs $deplibs $postdep_objects $compiler_flags~$RM $lib.exp' # Commands to make compiler produce verbose output that lists # what "hidden" libraries, object files and flags are used when # linking a shared library. output_verbose_link_cmd='$CC -G $CFLAGS -v conftest.$objext 2>&1 | $GREP -v "^Configured with:" | $GREP " \-L"' fi _LT_TAGVAR(hardcode_libdir_flag_spec, $1)='$wl-R $wl$libdir' case $host_os in solaris2.[[0-5]] | solaris2.[[0-5]].*) ;; *) _LT_TAGVAR(whole_archive_flag_spec, $1)='$wl-z ${wl}allextract$convenience $wl-z ${wl}defaultextract' ;; esac fi ;; esac ;; sysv4*uw2* | sysv5OpenUNIX* | sysv5UnixWare7.[[01]].[[10]]* | unixware7* | sco3.2v5.0.[[024]]*) _LT_TAGVAR(no_undefined_flag, $1)='$wl-z,text' _LT_TAGVAR(archive_cmds_need_lc, $1)=no _LT_TAGVAR(hardcode_shlibpath_var, $1)=no runpath_var='LD_RUN_PATH' case $cc_basename in CC*) _LT_TAGVAR(archive_cmds, $1)='$CC -G $wl-h,$soname -o $lib $libobjs $deplibs $compiler_flags' _LT_TAGVAR(archive_expsym_cmds, $1)='$CC -G $wl-Bexport:$export_symbols $wl-h,$soname -o $lib $libobjs $deplibs $compiler_flags' ;; *) _LT_TAGVAR(archive_cmds, $1)='$CC -shared $wl-h,$soname -o $lib $libobjs $deplibs $compiler_flags' _LT_TAGVAR(archive_expsym_cmds, $1)='$CC -shared $wl-Bexport:$export_symbols $wl-h,$soname -o $lib $libobjs $deplibs $compiler_flags' ;; esac ;; sysv5* | sco3.2v5* | sco5v6*) # Note: We CANNOT use -z defs as we might desire, because we do not # link with -lc, and that would cause any symbols used from libc to # always be unresolved, which means just about no library would # ever link correctly. If we're not using GNU ld we use -z text # though, which does catch some bad symbols but isn't as heavy-handed # as -z defs. _LT_TAGVAR(no_undefined_flag, $1)='$wl-z,text' _LT_TAGVAR(allow_undefined_flag, $1)='$wl-z,nodefs' _LT_TAGVAR(archive_cmds_need_lc, $1)=no _LT_TAGVAR(hardcode_shlibpath_var, $1)=no _LT_TAGVAR(hardcode_libdir_flag_spec, $1)='$wl-R,$libdir' _LT_TAGVAR(hardcode_libdir_separator, $1)=':' _LT_TAGVAR(link_all_deplibs, $1)=yes _LT_TAGVAR(export_dynamic_flag_spec, $1)='$wl-Bexport' runpath_var='LD_RUN_PATH' case $cc_basename in CC*) _LT_TAGVAR(archive_cmds, $1)='$CC -G $wl-h,$soname -o $lib $libobjs $deplibs $compiler_flags' _LT_TAGVAR(archive_expsym_cmds, $1)='$CC -G $wl-Bexport:$export_symbols $wl-h,$soname -o $lib $libobjs $deplibs $compiler_flags' _LT_TAGVAR(old_archive_cmds, $1)='$CC -Tprelink_objects $oldobjs~ '"$_LT_TAGVAR(old_archive_cmds, $1)" _LT_TAGVAR(reload_cmds, $1)='$CC -Tprelink_objects $reload_objs~ '"$_LT_TAGVAR(reload_cmds, $1)" ;; *) _LT_TAGVAR(archive_cmds, $1)='$CC -shared $wl-h,$soname -o $lib $libobjs $deplibs $compiler_flags' _LT_TAGVAR(archive_expsym_cmds, $1)='$CC -shared $wl-Bexport:$export_symbols $wl-h,$soname -o $lib $libobjs $deplibs $compiler_flags' ;; esac ;; tandem*) case $cc_basename in NCC*) # NonStop-UX NCC 3.20 # FIXME: insert proper C++ library support _LT_TAGVAR(ld_shlibs, $1)=no ;; *) # FIXME: insert proper C++ library support _LT_TAGVAR(ld_shlibs, $1)=no ;; esac ;; vxworks*) # FIXME: insert proper C++ library support _LT_TAGVAR(ld_shlibs, $1)=no ;; *) # FIXME: insert proper C++ library support _LT_TAGVAR(ld_shlibs, $1)=no ;; esac AC_MSG_RESULT([$_LT_TAGVAR(ld_shlibs, $1)]) test no = "$_LT_TAGVAR(ld_shlibs, $1)" && can_build_shared=no _LT_TAGVAR(GCC, $1)=$GXX _LT_TAGVAR(LD, $1)=$LD ## CAVEAT EMPTOR: ## There is no encapsulation within the following macros, do not change ## the running order or otherwise move them around unless you know exactly ## what you are doing... _LT_SYS_HIDDEN_LIBDEPS($1) _LT_COMPILER_PIC($1) _LT_COMPILER_C_O($1) _LT_COMPILER_FILE_LOCKS($1) _LT_LINKER_SHLIBS($1) _LT_SYS_DYNAMIC_LINKER($1) _LT_LINKER_HARDCODE_LIBPATH($1) _LT_CONFIG($1) fi # test -n "$compiler" CC=$lt_save_CC CFLAGS=$lt_save_CFLAGS LDCXX=$LD LD=$lt_save_LD GCC=$lt_save_GCC with_gnu_ld=$lt_save_with_gnu_ld lt_cv_path_LDCXX=$lt_cv_path_LD lt_cv_path_LD=$lt_save_path_LD lt_cv_prog_gnu_ldcxx=$lt_cv_prog_gnu_ld lt_cv_prog_gnu_ld=$lt_save_with_gnu_ld fi # test yes != "$_lt_caught_CXX_error" AC_LANG_POP ])# _LT_LANG_CXX_CONFIG # _LT_FUNC_STRIPNAME_CNF # ---------------------- # func_stripname_cnf prefix suffix name # strip PREFIX and SUFFIX off of NAME. # PREFIX and SUFFIX must not contain globbing or regex special # characters, hashes, percent signs, but SUFFIX may contain a leading # dot (in which case that matches only a dot). # # This function is identical to the (non-XSI) version of func_stripname, # except this one can be used by m4 code that may be executed by configure, # rather than the libtool script. m4_defun([_LT_FUNC_STRIPNAME_CNF],[dnl AC_REQUIRE([_LT_DECL_SED]) AC_REQUIRE([_LT_PROG_ECHO_BACKSLASH]) func_stripname_cnf () { case @S|@2 in .*) func_stripname_result=`$ECHO "@S|@3" | $SED "s%^@S|@1%%; s%\\\\@S|@2\$%%"`;; *) func_stripname_result=`$ECHO "@S|@3" | $SED "s%^@S|@1%%; s%@S|@2\$%%"`;; esac } # func_stripname_cnf ])# _LT_FUNC_STRIPNAME_CNF # _LT_SYS_HIDDEN_LIBDEPS([TAGNAME]) # --------------------------------- # Figure out "hidden" library dependencies from verbose # compiler output when linking a shared library. # Parse the compiler output and extract the necessary # objects, libraries and library flags. m4_defun([_LT_SYS_HIDDEN_LIBDEPS], [m4_require([_LT_FILEUTILS_DEFAULTS])dnl AC_REQUIRE([_LT_FUNC_STRIPNAME_CNF])dnl # Dependencies to place before and after the object being linked: _LT_TAGVAR(predep_objects, $1)= _LT_TAGVAR(postdep_objects, $1)= _LT_TAGVAR(predeps, $1)= _LT_TAGVAR(postdeps, $1)= _LT_TAGVAR(compiler_lib_search_path, $1)= dnl we can't use the lt_simple_compile_test_code here, dnl because it contains code intended for an executable, dnl not a library. It's possible we should let each dnl tag define a new lt_????_link_test_code variable, dnl but it's only used here... m4_if([$1], [], [cat > conftest.$ac_ext <<_LT_EOF int a; void foo (void) { a = 0; } _LT_EOF ], [$1], [CXX], [cat > conftest.$ac_ext <<_LT_EOF class Foo { public: Foo (void) { a = 0; } private: int a; }; _LT_EOF ], [$1], [F77], [cat > conftest.$ac_ext <<_LT_EOF subroutine foo implicit none integer*4 a a=0 return end _LT_EOF ], [$1], [FC], [cat > conftest.$ac_ext <<_LT_EOF subroutine foo implicit none integer a a=0 return end _LT_EOF ], [$1], [GCJ], [cat > conftest.$ac_ext <<_LT_EOF public class foo { private int a; public void bar (void) { a = 0; } }; _LT_EOF ], [$1], [GO], [cat > conftest.$ac_ext <<_LT_EOF package foo func foo() { } _LT_EOF ]) _lt_libdeps_save_CFLAGS=$CFLAGS case "$CC $CFLAGS " in #( *\ -flto*\ *) CFLAGS="$CFLAGS -fno-lto" ;; *\ -fwhopr*\ *) CFLAGS="$CFLAGS -fno-whopr" ;; *\ -fuse-linker-plugin*\ *) CFLAGS="$CFLAGS -fno-use-linker-plugin" ;; esac dnl Parse the compiler output and extract the necessary dnl objects, libraries and library flags. if AC_TRY_EVAL(ac_compile); then # Parse the compiler output and extract the necessary # objects, libraries and library flags. # Sentinel used to keep track of whether or not we are before # the conftest object file. pre_test_object_deps_done=no for p in `eval "$output_verbose_link_cmd"`; do case $prev$p in -L* | -R* | -l*) # Some compilers place space between "-{L,R}" and the path. # Remove the space. if test x-L = "$p" || test x-R = "$p"; then prev=$p continue fi # Expand the sysroot to ease extracting the directories later. if test -z "$prev"; then case $p in -L*) func_stripname_cnf '-L' '' "$p"; prev=-L; p=$func_stripname_result ;; -R*) func_stripname_cnf '-R' '' "$p"; prev=-R; p=$func_stripname_result ;; -l*) func_stripname_cnf '-l' '' "$p"; prev=-l; p=$func_stripname_result ;; esac fi case $p in =*) func_stripname_cnf '=' '' "$p"; p=$lt_sysroot$func_stripname_result ;; esac if test no = "$pre_test_object_deps_done"; then case $prev in -L | -R) # Internal compiler library paths should come after those # provided the user. The postdeps already come after the # user supplied libs so there is no need to process them. if test -z "$_LT_TAGVAR(compiler_lib_search_path, $1)"; then _LT_TAGVAR(compiler_lib_search_path, $1)=$prev$p else _LT_TAGVAR(compiler_lib_search_path, $1)="${_LT_TAGVAR(compiler_lib_search_path, $1)} $prev$p" fi ;; # The "-l" case would never come before the object being # linked, so don't bother handling this case. esac else if test -z "$_LT_TAGVAR(postdeps, $1)"; then _LT_TAGVAR(postdeps, $1)=$prev$p else _LT_TAGVAR(postdeps, $1)="${_LT_TAGVAR(postdeps, $1)} $prev$p" fi fi prev= ;; *.lto.$objext) ;; # Ignore GCC LTO objects *.$objext) # This assumes that the test object file only shows up # once in the compiler output. if test "$p" = "conftest.$objext"; then pre_test_object_deps_done=yes continue fi if test no = "$pre_test_object_deps_done"; then if test -z "$_LT_TAGVAR(predep_objects, $1)"; then _LT_TAGVAR(predep_objects, $1)=$p else _LT_TAGVAR(predep_objects, $1)="$_LT_TAGVAR(predep_objects, $1) $p" fi else if test -z "$_LT_TAGVAR(postdep_objects, $1)"; then _LT_TAGVAR(postdep_objects, $1)=$p else _LT_TAGVAR(postdep_objects, $1)="$_LT_TAGVAR(postdep_objects, $1) $p" fi fi ;; *) ;; # Ignore the rest. esac done # Clean up. rm -f a.out a.exe else echo "libtool.m4: error: problem compiling $1 test program" fi $RM -f confest.$objext CFLAGS=$_lt_libdeps_save_CFLAGS # PORTME: override above test on systems where it is broken m4_if([$1], [CXX], [case $host_os in interix[[3-9]]*) # Interix 3.5 installs completely hosed .la files for C++, so rather than # hack all around it, let's just trust "g++" to DTRT. _LT_TAGVAR(predep_objects,$1)= _LT_TAGVAR(postdep_objects,$1)= _LT_TAGVAR(postdeps,$1)= ;; esac ]) case " $_LT_TAGVAR(postdeps, $1) " in *" -lc "*) _LT_TAGVAR(archive_cmds_need_lc, $1)=no ;; esac _LT_TAGVAR(compiler_lib_search_dirs, $1)= if test -n "${_LT_TAGVAR(compiler_lib_search_path, $1)}"; then _LT_TAGVAR(compiler_lib_search_dirs, $1)=`echo " ${_LT_TAGVAR(compiler_lib_search_path, $1)}" | $SED -e 's! -L! !g' -e 's!^ !!'` fi _LT_TAGDECL([], [compiler_lib_search_dirs], [1], [The directories searched by this compiler when creating a shared library]) _LT_TAGDECL([], [predep_objects], [1], [Dependencies to place before and after the objects being linked to create a shared library]) _LT_TAGDECL([], [postdep_objects], [1]) _LT_TAGDECL([], [predeps], [1]) _LT_TAGDECL([], [postdeps], [1]) _LT_TAGDECL([], [compiler_lib_search_path], [1], [The library search path used internally by the compiler when linking a shared library]) ])# _LT_SYS_HIDDEN_LIBDEPS # _LT_LANG_F77_CONFIG([TAG]) # -------------------------- # Ensure that the configuration variables for a Fortran 77 compiler are # suitably defined. These variables are subsequently used by _LT_CONFIG # to write the compiler configuration to 'libtool'. m4_defun([_LT_LANG_F77_CONFIG], [AC_LANG_PUSH(Fortran 77) if test -z "$F77" || test no = "$F77"; then _lt_disable_F77=yes fi _LT_TAGVAR(archive_cmds_need_lc, $1)=no _LT_TAGVAR(allow_undefined_flag, $1)= _LT_TAGVAR(always_export_symbols, $1)=no _LT_TAGVAR(archive_expsym_cmds, $1)= _LT_TAGVAR(export_dynamic_flag_spec, $1)= _LT_TAGVAR(hardcode_direct, $1)=no _LT_TAGVAR(hardcode_direct_absolute, $1)=no _LT_TAGVAR(hardcode_libdir_flag_spec, $1)= _LT_TAGVAR(hardcode_libdir_separator, $1)= _LT_TAGVAR(hardcode_minus_L, $1)=no _LT_TAGVAR(hardcode_automatic, $1)=no _LT_TAGVAR(inherit_rpath, $1)=no _LT_TAGVAR(module_cmds, $1)= _LT_TAGVAR(module_expsym_cmds, $1)= _LT_TAGVAR(link_all_deplibs, $1)=unknown _LT_TAGVAR(old_archive_cmds, $1)=$old_archive_cmds _LT_TAGVAR(reload_flag, $1)=$reload_flag _LT_TAGVAR(reload_cmds, $1)=$reload_cmds _LT_TAGVAR(no_undefined_flag, $1)= _LT_TAGVAR(whole_archive_flag_spec, $1)= _LT_TAGVAR(enable_shared_with_static_runtimes, $1)=no # Source file extension for f77 test sources. ac_ext=f # Object file extension for compiled f77 test sources. objext=o _LT_TAGVAR(objext, $1)=$objext # No sense in running all these tests if we already determined that # the F77 compiler isn't working. Some variables (like enable_shared) # are currently assumed to apply to all compilers on this platform, # and will be corrupted by setting them based on a non-working compiler. if test yes != "$_lt_disable_F77"; then # Code to be used in simple compile tests lt_simple_compile_test_code="\ subroutine t return end " # Code to be used in simple link tests lt_simple_link_test_code="\ program t end " # ltmain only uses $CC for tagged configurations so make sure $CC is set. _LT_TAG_COMPILER # save warnings/boilerplate of simple test code _LT_COMPILER_BOILERPLATE _LT_LINKER_BOILERPLATE # Allow CC to be a program name with arguments. lt_save_CC=$CC lt_save_GCC=$GCC lt_save_CFLAGS=$CFLAGS CC=${F77-"f77"} CFLAGS=$FFLAGS compiler=$CC _LT_TAGVAR(compiler, $1)=$CC _LT_CC_BASENAME([$compiler]) GCC=$G77 if test -n "$compiler"; then AC_MSG_CHECKING([if libtool supports shared libraries]) AC_MSG_RESULT([$can_build_shared]) AC_MSG_CHECKING([whether to build shared libraries]) test no = "$can_build_shared" && enable_shared=no # On AIX, shared libraries and static libraries use the same namespace, and # are all built from PIC. case $host_os in aix3*) test yes = "$enable_shared" && enable_static=no if test -n "$RANLIB"; then archive_cmds="$archive_cmds~\$RANLIB \$lib" postinstall_cmds='$RANLIB $lib' fi ;; aix[[4-9]]*) if test ia64 != "$host_cpu"; then case $enable_shared,$with_aix_soname,$aix_use_runtimelinking in yes,aix,yes) ;; # shared object as lib.so file only yes,svr4,*) ;; # shared object as lib.so archive member only yes,*) enable_static=no ;; # shared object in lib.a archive as well esac fi ;; esac AC_MSG_RESULT([$enable_shared]) AC_MSG_CHECKING([whether to build static libraries]) # Make sure either enable_shared or enable_static is yes. test yes = "$enable_shared" || enable_static=yes AC_MSG_RESULT([$enable_static]) _LT_TAGVAR(GCC, $1)=$G77 _LT_TAGVAR(LD, $1)=$LD ## CAVEAT EMPTOR: ## There is no encapsulation within the following macros, do not change ## the running order or otherwise move them around unless you know exactly ## what you are doing... _LT_COMPILER_PIC($1) _LT_COMPILER_C_O($1) _LT_COMPILER_FILE_LOCKS($1) _LT_LINKER_SHLIBS($1) _LT_SYS_DYNAMIC_LINKER($1) _LT_LINKER_HARDCODE_LIBPATH($1) _LT_CONFIG($1) fi # test -n "$compiler" GCC=$lt_save_GCC CC=$lt_save_CC CFLAGS=$lt_save_CFLAGS fi # test yes != "$_lt_disable_F77" AC_LANG_POP ])# _LT_LANG_F77_CONFIG # _LT_LANG_FC_CONFIG([TAG]) # ------------------------- # Ensure that the configuration variables for a Fortran compiler are # suitably defined. These variables are subsequently used by _LT_CONFIG # to write the compiler configuration to 'libtool'. m4_defun([_LT_LANG_FC_CONFIG], [AC_LANG_PUSH(Fortran) if test -z "$FC" || test no = "$FC"; then _lt_disable_FC=yes fi _LT_TAGVAR(archive_cmds_need_lc, $1)=no _LT_TAGVAR(allow_undefined_flag, $1)= _LT_TAGVAR(always_export_symbols, $1)=no _LT_TAGVAR(archive_expsym_cmds, $1)= _LT_TAGVAR(export_dynamic_flag_spec, $1)= _LT_TAGVAR(hardcode_direct, $1)=no _LT_TAGVAR(hardcode_direct_absolute, $1)=no _LT_TAGVAR(hardcode_libdir_flag_spec, $1)= _LT_TAGVAR(hardcode_libdir_separator, $1)= _LT_TAGVAR(hardcode_minus_L, $1)=no _LT_TAGVAR(hardcode_automatic, $1)=no _LT_TAGVAR(inherit_rpath, $1)=no _LT_TAGVAR(module_cmds, $1)= _LT_TAGVAR(module_expsym_cmds, $1)= _LT_TAGVAR(link_all_deplibs, $1)=unknown _LT_TAGVAR(old_archive_cmds, $1)=$old_archive_cmds _LT_TAGVAR(reload_flag, $1)=$reload_flag _LT_TAGVAR(reload_cmds, $1)=$reload_cmds _LT_TAGVAR(no_undefined_flag, $1)= _LT_TAGVAR(whole_archive_flag_spec, $1)= _LT_TAGVAR(enable_shared_with_static_runtimes, $1)=no # Source file extension for fc test sources. ac_ext=${ac_fc_srcext-f} # Object file extension for compiled fc test sources. objext=o _LT_TAGVAR(objext, $1)=$objext # No sense in running all these tests if we already determined that # the FC compiler isn't working. Some variables (like enable_shared) # are currently assumed to apply to all compilers on this platform, # and will be corrupted by setting them based on a non-working compiler. if test yes != "$_lt_disable_FC"; then # Code to be used in simple compile tests lt_simple_compile_test_code="\ subroutine t return end " # Code to be used in simple link tests lt_simple_link_test_code="\ program t end " # ltmain only uses $CC for tagged configurations so make sure $CC is set. _LT_TAG_COMPILER # save warnings/boilerplate of simple test code _LT_COMPILER_BOILERPLATE _LT_LINKER_BOILERPLATE # Allow CC to be a program name with arguments. lt_save_CC=$CC lt_save_GCC=$GCC lt_save_CFLAGS=$CFLAGS CC=${FC-"f95"} CFLAGS=$FCFLAGS compiler=$CC GCC=$ac_cv_fc_compiler_gnu _LT_TAGVAR(compiler, $1)=$CC _LT_CC_BASENAME([$compiler]) if test -n "$compiler"; then AC_MSG_CHECKING([if libtool supports shared libraries]) AC_MSG_RESULT([$can_build_shared]) AC_MSG_CHECKING([whether to build shared libraries]) test no = "$can_build_shared" && enable_shared=no # On AIX, shared libraries and static libraries use the same namespace, and # are all built from PIC. case $host_os in aix3*) test yes = "$enable_shared" && enable_static=no if test -n "$RANLIB"; then archive_cmds="$archive_cmds~\$RANLIB \$lib" postinstall_cmds='$RANLIB $lib' fi ;; aix[[4-9]]*) if test ia64 != "$host_cpu"; then case $enable_shared,$with_aix_soname,$aix_use_runtimelinking in yes,aix,yes) ;; # shared object as lib.so file only yes,svr4,*) ;; # shared object as lib.so archive member only yes,*) enable_static=no ;; # shared object in lib.a archive as well esac fi ;; esac AC_MSG_RESULT([$enable_shared]) AC_MSG_CHECKING([whether to build static libraries]) # Make sure either enable_shared or enable_static is yes. test yes = "$enable_shared" || enable_static=yes AC_MSG_RESULT([$enable_static]) _LT_TAGVAR(GCC, $1)=$ac_cv_fc_compiler_gnu _LT_TAGVAR(LD, $1)=$LD ## CAVEAT EMPTOR: ## There is no encapsulation within the following macros, do not change ## the running order or otherwise move them around unless you know exactly ## what you are doing... _LT_SYS_HIDDEN_LIBDEPS($1) _LT_COMPILER_PIC($1) _LT_COMPILER_C_O($1) _LT_COMPILER_FILE_LOCKS($1) _LT_LINKER_SHLIBS($1) _LT_SYS_DYNAMIC_LINKER($1) _LT_LINKER_HARDCODE_LIBPATH($1) _LT_CONFIG($1) fi # test -n "$compiler" GCC=$lt_save_GCC CC=$lt_save_CC CFLAGS=$lt_save_CFLAGS fi # test yes != "$_lt_disable_FC" AC_LANG_POP ])# _LT_LANG_FC_CONFIG # _LT_LANG_GCJ_CONFIG([TAG]) # -------------------------- # Ensure that the configuration variables for the GNU Java Compiler compiler # are suitably defined. These variables are subsequently used by _LT_CONFIG # to write the compiler configuration to 'libtool'. m4_defun([_LT_LANG_GCJ_CONFIG], [AC_REQUIRE([LT_PROG_GCJ])dnl AC_LANG_SAVE # Source file extension for Java test sources. ac_ext=java # Object file extension for compiled Java test sources. objext=o _LT_TAGVAR(objext, $1)=$objext # Code to be used in simple compile tests lt_simple_compile_test_code="class foo {}" # Code to be used in simple link tests lt_simple_link_test_code='public class conftest { public static void main(String[[]] argv) {}; }' # ltmain only uses $CC for tagged configurations so make sure $CC is set. _LT_TAG_COMPILER # save warnings/boilerplate of simple test code _LT_COMPILER_BOILERPLATE _LT_LINKER_BOILERPLATE # Allow CC to be a program name with arguments. lt_save_CC=$CC lt_save_CFLAGS=$CFLAGS lt_save_GCC=$GCC GCC=yes CC=${GCJ-"gcj"} CFLAGS=$GCJFLAGS compiler=$CC _LT_TAGVAR(compiler, $1)=$CC _LT_TAGVAR(LD, $1)=$LD _LT_CC_BASENAME([$compiler]) # GCJ did not exist at the time GCC didn't implicitly link libc in. _LT_TAGVAR(archive_cmds_need_lc, $1)=no _LT_TAGVAR(old_archive_cmds, $1)=$old_archive_cmds _LT_TAGVAR(reload_flag, $1)=$reload_flag _LT_TAGVAR(reload_cmds, $1)=$reload_cmds ## CAVEAT EMPTOR: ## There is no encapsulation within the following macros, do not change ## the running order or otherwise move them around unless you know exactly ## what you are doing... if test -n "$compiler"; then _LT_COMPILER_NO_RTTI($1) _LT_COMPILER_PIC($1) _LT_COMPILER_C_O($1) _LT_COMPILER_FILE_LOCKS($1) _LT_LINKER_SHLIBS($1) _LT_LINKER_HARDCODE_LIBPATH($1) _LT_CONFIG($1) fi AC_LANG_RESTORE GCC=$lt_save_GCC CC=$lt_save_CC CFLAGS=$lt_save_CFLAGS ])# _LT_LANG_GCJ_CONFIG # _LT_LANG_GO_CONFIG([TAG]) # -------------------------- # Ensure that the configuration variables for the GNU Go compiler # are suitably defined. These variables are subsequently used by _LT_CONFIG # to write the compiler configuration to 'libtool'. m4_defun([_LT_LANG_GO_CONFIG], [AC_REQUIRE([LT_PROG_GO])dnl AC_LANG_SAVE # Source file extension for Go test sources. ac_ext=go # Object file extension for compiled Go test sources. objext=o _LT_TAGVAR(objext, $1)=$objext # Code to be used in simple compile tests lt_simple_compile_test_code="package main; func main() { }" # Code to be used in simple link tests lt_simple_link_test_code='package main; func main() { }' # ltmain only uses $CC for tagged configurations so make sure $CC is set. _LT_TAG_COMPILER # save warnings/boilerplate of simple test code _LT_COMPILER_BOILERPLATE _LT_LINKER_BOILERPLATE # Allow CC to be a program name with arguments. lt_save_CC=$CC lt_save_CFLAGS=$CFLAGS lt_save_GCC=$GCC GCC=yes CC=${GOC-"gccgo"} CFLAGS=$GOFLAGS compiler=$CC _LT_TAGVAR(compiler, $1)=$CC _LT_TAGVAR(LD, $1)=$LD _LT_CC_BASENAME([$compiler]) # Go did not exist at the time GCC didn't implicitly link libc in. _LT_TAGVAR(archive_cmds_need_lc, $1)=no _LT_TAGVAR(old_archive_cmds, $1)=$old_archive_cmds _LT_TAGVAR(reload_flag, $1)=$reload_flag _LT_TAGVAR(reload_cmds, $1)=$reload_cmds ## CAVEAT EMPTOR: ## There is no encapsulation within the following macros, do not change ## the running order or otherwise move them around unless you know exactly ## what you are doing... if test -n "$compiler"; then _LT_COMPILER_NO_RTTI($1) _LT_COMPILER_PIC($1) _LT_COMPILER_C_O($1) _LT_COMPILER_FILE_LOCKS($1) _LT_LINKER_SHLIBS($1) _LT_LINKER_HARDCODE_LIBPATH($1) _LT_CONFIG($1) fi AC_LANG_RESTORE GCC=$lt_save_GCC CC=$lt_save_CC CFLAGS=$lt_save_CFLAGS ])# _LT_LANG_GO_CONFIG # _LT_LANG_RC_CONFIG([TAG]) # ------------------------- # Ensure that the configuration variables for the Windows resource compiler # are suitably defined. These variables are subsequently used by _LT_CONFIG # to write the compiler configuration to 'libtool'. m4_defun([_LT_LANG_RC_CONFIG], [AC_REQUIRE([LT_PROG_RC])dnl AC_LANG_SAVE # Source file extension for RC test sources. ac_ext=rc # Object file extension for compiled RC test sources. objext=o _LT_TAGVAR(objext, $1)=$objext # Code to be used in simple compile tests lt_simple_compile_test_code='sample MENU { MENUITEM "&Soup", 100, CHECKED }' # Code to be used in simple link tests lt_simple_link_test_code=$lt_simple_compile_test_code # ltmain only uses $CC for tagged configurations so make sure $CC is set. _LT_TAG_COMPILER # save warnings/boilerplate of simple test code _LT_COMPILER_BOILERPLATE _LT_LINKER_BOILERPLATE # Allow CC to be a program name with arguments. lt_save_CC=$CC lt_save_CFLAGS=$CFLAGS lt_save_GCC=$GCC GCC= CC=${RC-"windres"} CFLAGS= compiler=$CC _LT_TAGVAR(compiler, $1)=$CC _LT_CC_BASENAME([$compiler]) _LT_TAGVAR(lt_cv_prog_compiler_c_o, $1)=yes if test -n "$compiler"; then : _LT_CONFIG($1) fi GCC=$lt_save_GCC AC_LANG_RESTORE CC=$lt_save_CC CFLAGS=$lt_save_CFLAGS ])# _LT_LANG_RC_CONFIG # LT_PROG_GCJ # ----------- AC_DEFUN([LT_PROG_GCJ], [m4_ifdef([AC_PROG_GCJ], [AC_PROG_GCJ], [m4_ifdef([A][M_PROG_GCJ], [A][M_PROG_GCJ], [AC_CHECK_TOOL(GCJ, gcj,) test set = "${GCJFLAGS+set}" || GCJFLAGS="-g -O2" AC_SUBST(GCJFLAGS)])])[]dnl ]) # Old name: AU_ALIAS([LT_AC_PROG_GCJ], [LT_PROG_GCJ]) dnl aclocal-1.4 backwards compatibility: dnl AC_DEFUN([LT_AC_PROG_GCJ], []) # LT_PROG_GO # ---------- AC_DEFUN([LT_PROG_GO], [AC_CHECK_TOOL(GOC, gccgo,) ]) # LT_PROG_RC # ---------- AC_DEFUN([LT_PROG_RC], [AC_CHECK_TOOL(RC, windres,) ]) # Old name: AU_ALIAS([LT_AC_PROG_RC], [LT_PROG_RC]) dnl aclocal-1.4 backwards compatibility: dnl AC_DEFUN([LT_AC_PROG_RC], []) # _LT_DECL_EGREP # -------------- # If we don't have a new enough Autoconf to choose the best grep # available, choose the one first in the user's PATH. m4_defun([_LT_DECL_EGREP], [AC_REQUIRE([AC_PROG_EGREP])dnl AC_REQUIRE([AC_PROG_FGREP])dnl test -z "$GREP" && GREP=grep _LT_DECL([], [GREP], [1], [A grep program that handles long lines]) _LT_DECL([], [EGREP], [1], [An ERE matcher]) _LT_DECL([], [FGREP], [1], [A literal string matcher]) dnl Non-bleeding-edge autoconf doesn't subst GREP, so do it here too AC_SUBST([GREP]) ]) # _LT_DECL_OBJDUMP # -------------- # If we don't have a new enough Autoconf to choose the best objdump # available, choose the one first in the user's PATH. m4_defun([_LT_DECL_OBJDUMP], [AC_CHECK_TOOL(OBJDUMP, objdump, false) test -z "$OBJDUMP" && OBJDUMP=objdump _LT_DECL([], [OBJDUMP], [1], [An object symbol dumper]) AC_SUBST([OBJDUMP]) ]) # _LT_DECL_DLLTOOL # ---------------- # Ensure DLLTOOL variable is set. m4_defun([_LT_DECL_DLLTOOL], [AC_CHECK_TOOL(DLLTOOL, dlltool, false) test -z "$DLLTOOL" && DLLTOOL=dlltool _LT_DECL([], [DLLTOOL], [1], [DLL creation program]) AC_SUBST([DLLTOOL]) ]) # _LT_DECL_SED # ------------ # Check for a fully-functional sed program, that truncates # as few characters as possible. Prefer GNU sed if found. m4_defun([_LT_DECL_SED], [AC_PROG_SED test -z "$SED" && SED=sed Xsed="$SED -e 1s/^X//" _LT_DECL([], [SED], [1], [A sed program that does not truncate output]) _LT_DECL([], [Xsed], ["\$SED -e 1s/^X//"], [Sed that helps us avoid accidentally triggering echo(1) options like -n]) ])# _LT_DECL_SED m4_ifndef([AC_PROG_SED], [ ############################################################ # NOTE: This macro has been submitted for inclusion into # # GNU Autoconf as AC_PROG_SED. When it is available in # # a released version of Autoconf we should remove this # # macro and use it instead. # ############################################################ m4_defun([AC_PROG_SED], [AC_MSG_CHECKING([for a sed that does not truncate output]) AC_CACHE_VAL(lt_cv_path_SED, [# Loop through the user's path and test for sed and gsed. # Then use that list of sed's as ones to test for truncation. as_save_IFS=$IFS; IFS=$PATH_SEPARATOR for as_dir in $PATH do IFS=$as_save_IFS test -z "$as_dir" && as_dir=. for lt_ac_prog in sed gsed; do for ac_exec_ext in '' $ac_executable_extensions; do if $as_executable_p "$as_dir/$lt_ac_prog$ac_exec_ext"; then lt_ac_sed_list="$lt_ac_sed_list $as_dir/$lt_ac_prog$ac_exec_ext" fi done done done IFS=$as_save_IFS lt_ac_max=0 lt_ac_count=0 # Add /usr/xpg4/bin/sed as it is typically found on Solaris # along with /bin/sed that truncates output. for lt_ac_sed in $lt_ac_sed_list /usr/xpg4/bin/sed; do test ! -f "$lt_ac_sed" && continue cat /dev/null > conftest.in lt_ac_count=0 echo $ECHO_N "0123456789$ECHO_C" >conftest.in # Check for GNU sed and select it if it is found. if "$lt_ac_sed" --version 2>&1 < /dev/null | grep 'GNU' > /dev/null; then lt_cv_path_SED=$lt_ac_sed break fi while true; do cat conftest.in conftest.in >conftest.tmp mv conftest.tmp conftest.in cp conftest.in conftest.nl echo >>conftest.nl $lt_ac_sed -e 's/a$//' < conftest.nl >conftest.out || break cmp -s conftest.out conftest.nl || break # 10000 chars as input seems more than enough test 10 -lt "$lt_ac_count" && break lt_ac_count=`expr $lt_ac_count + 1` if test "$lt_ac_count" -gt "$lt_ac_max"; then lt_ac_max=$lt_ac_count lt_cv_path_SED=$lt_ac_sed fi done done ]) SED=$lt_cv_path_SED AC_SUBST([SED]) AC_MSG_RESULT([$SED]) ])#AC_PROG_SED ])#m4_ifndef # Old name: AU_ALIAS([LT_AC_PROG_SED], [AC_PROG_SED]) dnl aclocal-1.4 backwards compatibility: dnl AC_DEFUN([LT_AC_PROG_SED], []) # _LT_CHECK_SHELL_FEATURES # ------------------------ # Find out whether the shell is Bourne or XSI compatible, # or has some other useful features. m4_defun([_LT_CHECK_SHELL_FEATURES], [if ( (MAIL=60; unset MAIL) || exit) >/dev/null 2>&1; then lt_unset=unset else lt_unset=false fi _LT_DECL([], [lt_unset], [0], [whether the shell understands "unset"])dnl # test EBCDIC or ASCII case `echo X|tr X '\101'` in A) # ASCII based system # \n is not interpreted correctly by Solaris 8 /usr/ucb/tr lt_SP2NL='tr \040 \012' lt_NL2SP='tr \015\012 \040\040' ;; *) # EBCDIC based system lt_SP2NL='tr \100 \n' lt_NL2SP='tr \r\n \100\100' ;; esac _LT_DECL([SP2NL], [lt_SP2NL], [1], [turn spaces into newlines])dnl _LT_DECL([NL2SP], [lt_NL2SP], [1], [turn newlines into spaces])dnl ])# _LT_CHECK_SHELL_FEATURES # _LT_PATH_CONVERSION_FUNCTIONS # ----------------------------- # Determine what file name conversion functions should be used by # func_to_host_file (and, implicitly, by func_to_host_path). These are needed # for certain cross-compile configurations and native mingw. m4_defun([_LT_PATH_CONVERSION_FUNCTIONS], [AC_REQUIRE([AC_CANONICAL_HOST])dnl AC_REQUIRE([AC_CANONICAL_BUILD])dnl AC_MSG_CHECKING([how to convert $build file names to $host format]) AC_CACHE_VAL(lt_cv_to_host_file_cmd, [case $host in *-*-mingw* ) case $build in *-*-mingw* ) # actually msys lt_cv_to_host_file_cmd=func_convert_file_msys_to_w32 ;; *-*-cygwin* ) lt_cv_to_host_file_cmd=func_convert_file_cygwin_to_w32 ;; * ) # otherwise, assume *nix lt_cv_to_host_file_cmd=func_convert_file_nix_to_w32 ;; esac ;; *-*-cygwin* ) case $build in *-*-mingw* ) # actually msys lt_cv_to_host_file_cmd=func_convert_file_msys_to_cygwin ;; *-*-cygwin* ) lt_cv_to_host_file_cmd=func_convert_file_noop ;; * ) # otherwise, assume *nix lt_cv_to_host_file_cmd=func_convert_file_nix_to_cygwin ;; esac ;; * ) # unhandled hosts (and "normal" native builds) lt_cv_to_host_file_cmd=func_convert_file_noop ;; esac ]) to_host_file_cmd=$lt_cv_to_host_file_cmd AC_MSG_RESULT([$lt_cv_to_host_file_cmd]) _LT_DECL([to_host_file_cmd], [lt_cv_to_host_file_cmd], [0], [convert $build file names to $host format])dnl AC_MSG_CHECKING([how to convert $build file names to toolchain format]) AC_CACHE_VAL(lt_cv_to_tool_file_cmd, [#assume ordinary cross tools, or native build. lt_cv_to_tool_file_cmd=func_convert_file_noop case $host in *-*-mingw* ) case $build in *-*-mingw* ) # actually msys lt_cv_to_tool_file_cmd=func_convert_file_msys_to_w32 ;; esac ;; esac ]) to_tool_file_cmd=$lt_cv_to_tool_file_cmd AC_MSG_RESULT([$lt_cv_to_tool_file_cmd]) _LT_DECL([to_tool_file_cmd], [lt_cv_to_tool_file_cmd], [0], [convert $build files to toolchain format])dnl ])# _LT_PATH_CONVERSION_FUNCTIONS frr-7.2.1/m4/ltoptions.m40000644000000000000000000003426213610377563012040 00000000000000# Helper functions for option handling. -*- Autoconf -*- # # Copyright (C) 2004-2005, 2007-2009, 2011-2015 Free Software # Foundation, Inc. # Written by Gary V. Vaughan, 2004 # # This file is free software; the Free Software Foundation gives # unlimited permission to copy and/or distribute it, with or without # modifications, as long as this notice is preserved. # serial 8 ltoptions.m4 # This is to help aclocal find these macros, as it can't see m4_define. AC_DEFUN([LTOPTIONS_VERSION], [m4_if([1])]) # _LT_MANGLE_OPTION(MACRO-NAME, OPTION-NAME) # ------------------------------------------ m4_define([_LT_MANGLE_OPTION], [[_LT_OPTION_]m4_bpatsubst($1__$2, [[^a-zA-Z0-9_]], [_])]) # _LT_SET_OPTION(MACRO-NAME, OPTION-NAME) # --------------------------------------- # Set option OPTION-NAME for macro MACRO-NAME, and if there is a # matching handler defined, dispatch to it. Other OPTION-NAMEs are # saved as a flag. m4_define([_LT_SET_OPTION], [m4_define(_LT_MANGLE_OPTION([$1], [$2]))dnl m4_ifdef(_LT_MANGLE_DEFUN([$1], [$2]), _LT_MANGLE_DEFUN([$1], [$2]), [m4_warning([Unknown $1 option '$2'])])[]dnl ]) # _LT_IF_OPTION(MACRO-NAME, OPTION-NAME, IF-SET, [IF-NOT-SET]) # ------------------------------------------------------------ # Execute IF-SET if OPTION is set, IF-NOT-SET otherwise. m4_define([_LT_IF_OPTION], [m4_ifdef(_LT_MANGLE_OPTION([$1], [$2]), [$3], [$4])]) # _LT_UNLESS_OPTIONS(MACRO-NAME, OPTION-LIST, IF-NOT-SET) # ------------------------------------------------------- # Execute IF-NOT-SET unless all options in OPTION-LIST for MACRO-NAME # are set. m4_define([_LT_UNLESS_OPTIONS], [m4_foreach([_LT_Option], m4_split(m4_normalize([$2])), [m4_ifdef(_LT_MANGLE_OPTION([$1], _LT_Option), [m4_define([$0_found])])])[]dnl m4_ifdef([$0_found], [m4_undefine([$0_found])], [$3 ])[]dnl ]) # _LT_SET_OPTIONS(MACRO-NAME, OPTION-LIST) # ---------------------------------------- # OPTION-LIST is a space-separated list of Libtool options associated # with MACRO-NAME. If any OPTION has a matching handler declared with # LT_OPTION_DEFINE, dispatch to that macro; otherwise complain about # the unknown option and exit. m4_defun([_LT_SET_OPTIONS], [# Set options m4_foreach([_LT_Option], m4_split(m4_normalize([$2])), [_LT_SET_OPTION([$1], _LT_Option)]) m4_if([$1],[LT_INIT],[ dnl dnl Simply set some default values (i.e off) if boolean options were not dnl specified: _LT_UNLESS_OPTIONS([LT_INIT], [dlopen], [enable_dlopen=no ]) _LT_UNLESS_OPTIONS([LT_INIT], [win32-dll], [enable_win32_dll=no ]) dnl dnl If no reference was made to various pairs of opposing options, then dnl we run the default mode handler for the pair. For example, if neither dnl 'shared' nor 'disable-shared' was passed, we enable building of shared dnl archives by default: _LT_UNLESS_OPTIONS([LT_INIT], [shared disable-shared], [_LT_ENABLE_SHARED]) _LT_UNLESS_OPTIONS([LT_INIT], [static disable-static], [_LT_ENABLE_STATIC]) _LT_UNLESS_OPTIONS([LT_INIT], [pic-only no-pic], [_LT_WITH_PIC]) _LT_UNLESS_OPTIONS([LT_INIT], [fast-install disable-fast-install], [_LT_ENABLE_FAST_INSTALL]) _LT_UNLESS_OPTIONS([LT_INIT], [aix-soname=aix aix-soname=both aix-soname=svr4], [_LT_WITH_AIX_SONAME([aix])]) ]) ])# _LT_SET_OPTIONS ## --------------------------------- ## ## Macros to handle LT_INIT options. ## ## --------------------------------- ## # _LT_MANGLE_DEFUN(MACRO-NAME, OPTION-NAME) # ----------------------------------------- m4_define([_LT_MANGLE_DEFUN], [[_LT_OPTION_DEFUN_]m4_bpatsubst(m4_toupper([$1__$2]), [[^A-Z0-9_]], [_])]) # LT_OPTION_DEFINE(MACRO-NAME, OPTION-NAME, CODE) # ----------------------------------------------- m4_define([LT_OPTION_DEFINE], [m4_define(_LT_MANGLE_DEFUN([$1], [$2]), [$3])[]dnl ])# LT_OPTION_DEFINE # dlopen # ------ LT_OPTION_DEFINE([LT_INIT], [dlopen], [enable_dlopen=yes ]) AU_DEFUN([AC_LIBTOOL_DLOPEN], [_LT_SET_OPTION([LT_INIT], [dlopen]) AC_DIAGNOSE([obsolete], [$0: Remove this warning and the call to _LT_SET_OPTION when you put the 'dlopen' option into LT_INIT's first parameter.]) ]) dnl aclocal-1.4 backwards compatibility: dnl AC_DEFUN([AC_LIBTOOL_DLOPEN], []) # win32-dll # --------- # Declare package support for building win32 dll's. LT_OPTION_DEFINE([LT_INIT], [win32-dll], [enable_win32_dll=yes case $host in *-*-cygwin* | *-*-mingw* | *-*-pw32* | *-*-cegcc*) AC_CHECK_TOOL(AS, as, false) AC_CHECK_TOOL(DLLTOOL, dlltool, false) AC_CHECK_TOOL(OBJDUMP, objdump, false) ;; esac test -z "$AS" && AS=as _LT_DECL([], [AS], [1], [Assembler program])dnl test -z "$DLLTOOL" && DLLTOOL=dlltool _LT_DECL([], [DLLTOOL], [1], [DLL creation program])dnl test -z "$OBJDUMP" && OBJDUMP=objdump _LT_DECL([], [OBJDUMP], [1], [Object dumper program])dnl ])# win32-dll AU_DEFUN([AC_LIBTOOL_WIN32_DLL], [AC_REQUIRE([AC_CANONICAL_HOST])dnl _LT_SET_OPTION([LT_INIT], [win32-dll]) AC_DIAGNOSE([obsolete], [$0: Remove this warning and the call to _LT_SET_OPTION when you put the 'win32-dll' option into LT_INIT's first parameter.]) ]) dnl aclocal-1.4 backwards compatibility: dnl AC_DEFUN([AC_LIBTOOL_WIN32_DLL], []) # _LT_ENABLE_SHARED([DEFAULT]) # ---------------------------- # implement the --enable-shared flag, and supports the 'shared' and # 'disable-shared' LT_INIT options. # DEFAULT is either 'yes' or 'no'. If omitted, it defaults to 'yes'. m4_define([_LT_ENABLE_SHARED], [m4_define([_LT_ENABLE_SHARED_DEFAULT], [m4_if($1, no, no, yes)])dnl AC_ARG_ENABLE([shared], [AS_HELP_STRING([--enable-shared@<:@=PKGS@:>@], [build shared libraries @<:@default=]_LT_ENABLE_SHARED_DEFAULT[@:>@])], [p=${PACKAGE-default} case $enableval in yes) enable_shared=yes ;; no) enable_shared=no ;; *) enable_shared=no # Look at the argument we got. We use all the common list separators. lt_save_ifs=$IFS; IFS=$IFS$PATH_SEPARATOR, for pkg in $enableval; do IFS=$lt_save_ifs if test "X$pkg" = "X$p"; then enable_shared=yes fi done IFS=$lt_save_ifs ;; esac], [enable_shared=]_LT_ENABLE_SHARED_DEFAULT) _LT_DECL([build_libtool_libs], [enable_shared], [0], [Whether or not to build shared libraries]) ])# _LT_ENABLE_SHARED LT_OPTION_DEFINE([LT_INIT], [shared], [_LT_ENABLE_SHARED([yes])]) LT_OPTION_DEFINE([LT_INIT], [disable-shared], [_LT_ENABLE_SHARED([no])]) # Old names: AC_DEFUN([AC_ENABLE_SHARED], [_LT_SET_OPTION([LT_INIT], m4_if([$1], [no], [disable-])[shared]) ]) AC_DEFUN([AC_DISABLE_SHARED], [_LT_SET_OPTION([LT_INIT], [disable-shared]) ]) AU_DEFUN([AM_ENABLE_SHARED], [AC_ENABLE_SHARED($@)]) AU_DEFUN([AM_DISABLE_SHARED], [AC_DISABLE_SHARED($@)]) dnl aclocal-1.4 backwards compatibility: dnl AC_DEFUN([AM_ENABLE_SHARED], []) dnl AC_DEFUN([AM_DISABLE_SHARED], []) # _LT_ENABLE_STATIC([DEFAULT]) # ---------------------------- # implement the --enable-static flag, and support the 'static' and # 'disable-static' LT_INIT options. # DEFAULT is either 'yes' or 'no'. If omitted, it defaults to 'yes'. m4_define([_LT_ENABLE_STATIC], [m4_define([_LT_ENABLE_STATIC_DEFAULT], [m4_if($1, no, no, yes)])dnl AC_ARG_ENABLE([static], [AS_HELP_STRING([--enable-static@<:@=PKGS@:>@], [build static libraries @<:@default=]_LT_ENABLE_STATIC_DEFAULT[@:>@])], [p=${PACKAGE-default} case $enableval in yes) enable_static=yes ;; no) enable_static=no ;; *) enable_static=no # Look at the argument we got. We use all the common list separators. lt_save_ifs=$IFS; IFS=$IFS$PATH_SEPARATOR, for pkg in $enableval; do IFS=$lt_save_ifs if test "X$pkg" = "X$p"; then enable_static=yes fi done IFS=$lt_save_ifs ;; esac], [enable_static=]_LT_ENABLE_STATIC_DEFAULT) _LT_DECL([build_old_libs], [enable_static], [0], [Whether or not to build static libraries]) ])# _LT_ENABLE_STATIC LT_OPTION_DEFINE([LT_INIT], [static], [_LT_ENABLE_STATIC([yes])]) LT_OPTION_DEFINE([LT_INIT], [disable-static], [_LT_ENABLE_STATIC([no])]) # Old names: AC_DEFUN([AC_ENABLE_STATIC], [_LT_SET_OPTION([LT_INIT], m4_if([$1], [no], [disable-])[static]) ]) AC_DEFUN([AC_DISABLE_STATIC], [_LT_SET_OPTION([LT_INIT], [disable-static]) ]) AU_DEFUN([AM_ENABLE_STATIC], [AC_ENABLE_STATIC($@)]) AU_DEFUN([AM_DISABLE_STATIC], [AC_DISABLE_STATIC($@)]) dnl aclocal-1.4 backwards compatibility: dnl AC_DEFUN([AM_ENABLE_STATIC], []) dnl AC_DEFUN([AM_DISABLE_STATIC], []) # _LT_ENABLE_FAST_INSTALL([DEFAULT]) # ---------------------------------- # implement the --enable-fast-install flag, and support the 'fast-install' # and 'disable-fast-install' LT_INIT options. # DEFAULT is either 'yes' or 'no'. If omitted, it defaults to 'yes'. m4_define([_LT_ENABLE_FAST_INSTALL], [m4_define([_LT_ENABLE_FAST_INSTALL_DEFAULT], [m4_if($1, no, no, yes)])dnl AC_ARG_ENABLE([fast-install], [AS_HELP_STRING([--enable-fast-install@<:@=PKGS@:>@], [optimize for fast installation @<:@default=]_LT_ENABLE_FAST_INSTALL_DEFAULT[@:>@])], [p=${PACKAGE-default} case $enableval in yes) enable_fast_install=yes ;; no) enable_fast_install=no ;; *) enable_fast_install=no # Look at the argument we got. We use all the common list separators. lt_save_ifs=$IFS; IFS=$IFS$PATH_SEPARATOR, for pkg in $enableval; do IFS=$lt_save_ifs if test "X$pkg" = "X$p"; then enable_fast_install=yes fi done IFS=$lt_save_ifs ;; esac], [enable_fast_install=]_LT_ENABLE_FAST_INSTALL_DEFAULT) _LT_DECL([fast_install], [enable_fast_install], [0], [Whether or not to optimize for fast installation])dnl ])# _LT_ENABLE_FAST_INSTALL LT_OPTION_DEFINE([LT_INIT], [fast-install], [_LT_ENABLE_FAST_INSTALL([yes])]) LT_OPTION_DEFINE([LT_INIT], [disable-fast-install], [_LT_ENABLE_FAST_INSTALL([no])]) # Old names: AU_DEFUN([AC_ENABLE_FAST_INSTALL], [_LT_SET_OPTION([LT_INIT], m4_if([$1], [no], [disable-])[fast-install]) AC_DIAGNOSE([obsolete], [$0: Remove this warning and the call to _LT_SET_OPTION when you put the 'fast-install' option into LT_INIT's first parameter.]) ]) AU_DEFUN([AC_DISABLE_FAST_INSTALL], [_LT_SET_OPTION([LT_INIT], [disable-fast-install]) AC_DIAGNOSE([obsolete], [$0: Remove this warning and the call to _LT_SET_OPTION when you put the 'disable-fast-install' option into LT_INIT's first parameter.]) ]) dnl aclocal-1.4 backwards compatibility: dnl AC_DEFUN([AC_ENABLE_FAST_INSTALL], []) dnl AC_DEFUN([AM_DISABLE_FAST_INSTALL], []) # _LT_WITH_AIX_SONAME([DEFAULT]) # ---------------------------------- # implement the --with-aix-soname flag, and support the `aix-soname=aix' # and `aix-soname=both' and `aix-soname=svr4' LT_INIT options. DEFAULT # is either `aix', `both' or `svr4'. If omitted, it defaults to `aix'. m4_define([_LT_WITH_AIX_SONAME], [m4_define([_LT_WITH_AIX_SONAME_DEFAULT], [m4_if($1, svr4, svr4, m4_if($1, both, both, aix))])dnl shared_archive_member_spec= case $host,$enable_shared in power*-*-aix[[5-9]]*,yes) AC_MSG_CHECKING([which variant of shared library versioning to provide]) AC_ARG_WITH([aix-soname], [AS_HELP_STRING([--with-aix-soname=aix|svr4|both], [shared library versioning (aka "SONAME") variant to provide on AIX, @<:@default=]_LT_WITH_AIX_SONAME_DEFAULT[@:>@.])], [case $withval in aix|svr4|both) ;; *) AC_MSG_ERROR([Unknown argument to --with-aix-soname]) ;; esac lt_cv_with_aix_soname=$with_aix_soname], [AC_CACHE_VAL([lt_cv_with_aix_soname], [lt_cv_with_aix_soname=]_LT_WITH_AIX_SONAME_DEFAULT) with_aix_soname=$lt_cv_with_aix_soname]) AC_MSG_RESULT([$with_aix_soname]) if test aix != "$with_aix_soname"; then # For the AIX way of multilib, we name the shared archive member # based on the bitwidth used, traditionally 'shr.o' or 'shr_64.o', # and 'shr.imp' or 'shr_64.imp', respectively, for the Import File. # Even when GNU compilers ignore OBJECT_MODE but need '-maix64' flag, # the AIX toolchain works better with OBJECT_MODE set (default 32). if test 64 = "${OBJECT_MODE-32}"; then shared_archive_member_spec=shr_64 else shared_archive_member_spec=shr fi fi ;; *) with_aix_soname=aix ;; esac _LT_DECL([], [shared_archive_member_spec], [0], [Shared archive member basename, for filename based shared library versioning on AIX])dnl ])# _LT_WITH_AIX_SONAME LT_OPTION_DEFINE([LT_INIT], [aix-soname=aix], [_LT_WITH_AIX_SONAME([aix])]) LT_OPTION_DEFINE([LT_INIT], [aix-soname=both], [_LT_WITH_AIX_SONAME([both])]) LT_OPTION_DEFINE([LT_INIT], [aix-soname=svr4], [_LT_WITH_AIX_SONAME([svr4])]) # _LT_WITH_PIC([MODE]) # -------------------- # implement the --with-pic flag, and support the 'pic-only' and 'no-pic' # LT_INIT options. # MODE is either 'yes' or 'no'. If omitted, it defaults to 'both'. m4_define([_LT_WITH_PIC], [AC_ARG_WITH([pic], [AS_HELP_STRING([--with-pic@<:@=PKGS@:>@], [try to use only PIC/non-PIC objects @<:@default=use both@:>@])], [lt_p=${PACKAGE-default} case $withval in yes|no) pic_mode=$withval ;; *) pic_mode=default # Look at the argument we got. We use all the common list separators. lt_save_ifs=$IFS; IFS=$IFS$PATH_SEPARATOR, for lt_pkg in $withval; do IFS=$lt_save_ifs if test "X$lt_pkg" = "X$lt_p"; then pic_mode=yes fi done IFS=$lt_save_ifs ;; esac], [pic_mode=m4_default([$1], [default])]) _LT_DECL([], [pic_mode], [0], [What type of objects to build])dnl ])# _LT_WITH_PIC LT_OPTION_DEFINE([LT_INIT], [pic-only], [_LT_WITH_PIC([yes])]) LT_OPTION_DEFINE([LT_INIT], [no-pic], [_LT_WITH_PIC([no])]) # Old name: AU_DEFUN([AC_LIBTOOL_PICMODE], [_LT_SET_OPTION([LT_INIT], [pic-only]) AC_DIAGNOSE([obsolete], [$0: Remove this warning and the call to _LT_SET_OPTION when you put the 'pic-only' option into LT_INIT's first parameter.]) ]) dnl aclocal-1.4 backwards compatibility: dnl AC_DEFUN([AC_LIBTOOL_PICMODE], []) ## ----------------- ## ## LTDL_INIT Options ## ## ----------------- ## m4_define([_LTDL_MODE], []) LT_OPTION_DEFINE([LTDL_INIT], [nonrecursive], [m4_define([_LTDL_MODE], [nonrecursive])]) LT_OPTION_DEFINE([LTDL_INIT], [recursive], [m4_define([_LTDL_MODE], [recursive])]) LT_OPTION_DEFINE([LTDL_INIT], [subproject], [m4_define([_LTDL_MODE], [subproject])]) m4_define([_LTDL_TYPE], []) LT_OPTION_DEFINE([LTDL_INIT], [installable], [m4_define([_LTDL_TYPE], [installable])]) LT_OPTION_DEFINE([LTDL_INIT], [convenience], [m4_define([_LTDL_TYPE], [convenience])]) frr-7.2.1/m4/ltsugar.m40000644000000000000000000001044013610377563011456 00000000000000# ltsugar.m4 -- libtool m4 base layer. -*-Autoconf-*- # # Copyright (C) 2004-2005, 2007-2008, 2011-2015 Free Software # Foundation, Inc. # Written by Gary V. Vaughan, 2004 # # This file is free software; the Free Software Foundation gives # unlimited permission to copy and/or distribute it, with or without # modifications, as long as this notice is preserved. # serial 6 ltsugar.m4 # This is to help aclocal find these macros, as it can't see m4_define. AC_DEFUN([LTSUGAR_VERSION], [m4_if([0.1])]) # lt_join(SEP, ARG1, [ARG2...]) # ----------------------------- # Produce ARG1SEPARG2...SEPARGn, omitting [] arguments and their # associated separator. # Needed until we can rely on m4_join from Autoconf 2.62, since all earlier # versions in m4sugar had bugs. m4_define([lt_join], [m4_if([$#], [1], [], [$#], [2], [[$2]], [m4_if([$2], [], [], [[$2]_])$0([$1], m4_shift(m4_shift($@)))])]) m4_define([_lt_join], [m4_if([$#$2], [2], [], [m4_if([$2], [], [], [[$1$2]])$0([$1], m4_shift(m4_shift($@)))])]) # lt_car(LIST) # lt_cdr(LIST) # ------------ # Manipulate m4 lists. # These macros are necessary as long as will still need to support # Autoconf-2.59, which quotes differently. m4_define([lt_car], [[$1]]) m4_define([lt_cdr], [m4_if([$#], 0, [m4_fatal([$0: cannot be called without arguments])], [$#], 1, [], [m4_dquote(m4_shift($@))])]) m4_define([lt_unquote], $1) # lt_append(MACRO-NAME, STRING, [SEPARATOR]) # ------------------------------------------ # Redefine MACRO-NAME to hold its former content plus 'SEPARATOR''STRING'. # Note that neither SEPARATOR nor STRING are expanded; they are appended # to MACRO-NAME as is (leaving the expansion for when MACRO-NAME is invoked). # No SEPARATOR is output if MACRO-NAME was previously undefined (different # than defined and empty). # # This macro is needed until we can rely on Autoconf 2.62, since earlier # versions of m4sugar mistakenly expanded SEPARATOR but not STRING. m4_define([lt_append], [m4_define([$1], m4_ifdef([$1], [m4_defn([$1])[$3]])[$2])]) # lt_combine(SEP, PREFIX-LIST, INFIX, SUFFIX1, [SUFFIX2...]) # ---------------------------------------------------------- # Produce a SEP delimited list of all paired combinations of elements of # PREFIX-LIST with SUFFIX1 through SUFFIXn. Each element of the list # has the form PREFIXmINFIXSUFFIXn. # Needed until we can rely on m4_combine added in Autoconf 2.62. m4_define([lt_combine], [m4_if(m4_eval([$# > 3]), [1], [m4_pushdef([_Lt_sep], [m4_define([_Lt_sep], m4_defn([lt_car]))])]]dnl [[m4_foreach([_Lt_prefix], [$2], [m4_foreach([_Lt_suffix], ]m4_dquote(m4_dquote(m4_shift(m4_shift(m4_shift($@)))))[, [_Lt_sep([$1])[]m4_defn([_Lt_prefix])[$3]m4_defn([_Lt_suffix])])])])]) # lt_if_append_uniq(MACRO-NAME, VARNAME, [SEPARATOR], [UNIQ], [NOT-UNIQ]) # ----------------------------------------------------------------------- # Iff MACRO-NAME does not yet contain VARNAME, then append it (delimited # by SEPARATOR if supplied) and expand UNIQ, else NOT-UNIQ. m4_define([lt_if_append_uniq], [m4_ifdef([$1], [m4_if(m4_index([$3]m4_defn([$1])[$3], [$3$2$3]), [-1], [lt_append([$1], [$2], [$3])$4], [$5])], [lt_append([$1], [$2], [$3])$4])]) # lt_dict_add(DICT, KEY, VALUE) # ----------------------------- m4_define([lt_dict_add], [m4_define([$1($2)], [$3])]) # lt_dict_add_subkey(DICT, KEY, SUBKEY, VALUE) # -------------------------------------------- m4_define([lt_dict_add_subkey], [m4_define([$1($2:$3)], [$4])]) # lt_dict_fetch(DICT, KEY, [SUBKEY]) # ---------------------------------- m4_define([lt_dict_fetch], [m4_ifval([$3], m4_ifdef([$1($2:$3)], [m4_defn([$1($2:$3)])]), m4_ifdef([$1($2)], [m4_defn([$1($2)])]))]) # lt_if_dict_fetch(DICT, KEY, [SUBKEY], VALUE, IF-TRUE, [IF-FALSE]) # ----------------------------------------------------------------- m4_define([lt_if_dict_fetch], [m4_if(lt_dict_fetch([$1], [$2], [$3]), [$4], [$5], [$6])]) # lt_dict_filter(DICT, [SUBKEY], VALUE, [SEPARATOR], KEY, [...]) # -------------------------------------------------------------- m4_define([lt_dict_filter], [m4_if([$5], [], [], [lt_join(m4_quote(m4_default([$4], [[, ]])), lt_unquote(m4_split(m4_normalize(m4_foreach(_Lt_key, lt_car([m4_shiftn(4, $@)]), [lt_if_dict_fetch([$1], _Lt_key, [$2], [$3], [_Lt_key ])])))))])[]dnl ]) frr-7.2.1/m4/ltversion.m40000644000000000000000000000127313610377563012026 00000000000000# ltversion.m4 -- version numbers -*- Autoconf -*- # # Copyright (C) 2004, 2011-2015 Free Software Foundation, Inc. # Written by Scott James Remnant, 2004 # # This file is free software; the Free Software Foundation gives # unlimited permission to copy and/or distribute it, with or without # modifications, as long as this notice is preserved. # @configure_input@ # serial 4179 ltversion.m4 # This file is part of GNU Libtool m4_define([LT_PACKAGE_VERSION], [2.4.6]) m4_define([LT_PACKAGE_REVISION], [2.4.6]) AC_DEFUN([LTVERSION_VERSION], [macro_version='2.4.6' macro_revision='2.4.6' _LT_DECL(, macro_version, 0, [Which release of libtool.m4 was used?]) _LT_DECL(, macro_revision, 0) ]) frr-7.2.1/m4/lt~obsolete.m40000644000000000000000000001377413610377563012364 00000000000000# lt~obsolete.m4 -- aclocal satisfying obsolete definitions. -*-Autoconf-*- # # Copyright (C) 2004-2005, 2007, 2009, 2011-2015 Free Software # Foundation, Inc. # Written by Scott James Remnant, 2004. # # This file is free software; the Free Software Foundation gives # unlimited permission to copy and/or distribute it, with or without # modifications, as long as this notice is preserved. # serial 5 lt~obsolete.m4 # These exist entirely to fool aclocal when bootstrapping libtool. # # In the past libtool.m4 has provided macros via AC_DEFUN (or AU_DEFUN), # which have later been changed to m4_define as they aren't part of the # exported API, or moved to Autoconf or Automake where they belong. # # The trouble is, aclocal is a bit thick. It'll see the old AC_DEFUN # in /usr/share/aclocal/libtool.m4 and remember it, then when it sees us # using a macro with the same name in our local m4/libtool.m4 it'll # pull the old libtool.m4 in (it doesn't see our shiny new m4_define # and doesn't know about Autoconf macros at all.) # # So we provide this file, which has a silly filename so it's always # included after everything else. This provides aclocal with the # AC_DEFUNs it wants, but when m4 processes it, it doesn't do anything # because those macros already exist, or will be overwritten later. # We use AC_DEFUN over AU_DEFUN for compatibility with aclocal-1.6. # # Anytime we withdraw an AC_DEFUN or AU_DEFUN, remember to add it here. # Yes, that means every name once taken will need to remain here until # we give up compatibility with versions before 1.7, at which point # we need to keep only those names which we still refer to. # This is to help aclocal find these macros, as it can't see m4_define. AC_DEFUN([LTOBSOLETE_VERSION], [m4_if([1])]) m4_ifndef([AC_LIBTOOL_LINKER_OPTION], [AC_DEFUN([AC_LIBTOOL_LINKER_OPTION])]) m4_ifndef([AC_PROG_EGREP], [AC_DEFUN([AC_PROG_EGREP])]) m4_ifndef([_LT_AC_PROG_ECHO_BACKSLASH], [AC_DEFUN([_LT_AC_PROG_ECHO_BACKSLASH])]) m4_ifndef([_LT_AC_SHELL_INIT], [AC_DEFUN([_LT_AC_SHELL_INIT])]) m4_ifndef([_LT_AC_SYS_LIBPATH_AIX], [AC_DEFUN([_LT_AC_SYS_LIBPATH_AIX])]) m4_ifndef([_LT_PROG_LTMAIN], [AC_DEFUN([_LT_PROG_LTMAIN])]) m4_ifndef([_LT_AC_TAGVAR], [AC_DEFUN([_LT_AC_TAGVAR])]) m4_ifndef([AC_LTDL_ENABLE_INSTALL], [AC_DEFUN([AC_LTDL_ENABLE_INSTALL])]) m4_ifndef([AC_LTDL_PREOPEN], [AC_DEFUN([AC_LTDL_PREOPEN])]) m4_ifndef([_LT_AC_SYS_COMPILER], [AC_DEFUN([_LT_AC_SYS_COMPILER])]) m4_ifndef([_LT_AC_LOCK], [AC_DEFUN([_LT_AC_LOCK])]) m4_ifndef([AC_LIBTOOL_SYS_OLD_ARCHIVE], [AC_DEFUN([AC_LIBTOOL_SYS_OLD_ARCHIVE])]) m4_ifndef([_LT_AC_TRY_DLOPEN_SELF], [AC_DEFUN([_LT_AC_TRY_DLOPEN_SELF])]) m4_ifndef([AC_LIBTOOL_PROG_CC_C_O], [AC_DEFUN([AC_LIBTOOL_PROG_CC_C_O])]) m4_ifndef([AC_LIBTOOL_SYS_HARD_LINK_LOCKS], [AC_DEFUN([AC_LIBTOOL_SYS_HARD_LINK_LOCKS])]) m4_ifndef([AC_LIBTOOL_OBJDIR], [AC_DEFUN([AC_LIBTOOL_OBJDIR])]) m4_ifndef([AC_LTDL_OBJDIR], [AC_DEFUN([AC_LTDL_OBJDIR])]) m4_ifndef([AC_LIBTOOL_PROG_LD_HARDCODE_LIBPATH], [AC_DEFUN([AC_LIBTOOL_PROG_LD_HARDCODE_LIBPATH])]) m4_ifndef([AC_LIBTOOL_SYS_LIB_STRIP], [AC_DEFUN([AC_LIBTOOL_SYS_LIB_STRIP])]) m4_ifndef([AC_PATH_MAGIC], [AC_DEFUN([AC_PATH_MAGIC])]) m4_ifndef([AC_PROG_LD_GNU], [AC_DEFUN([AC_PROG_LD_GNU])]) m4_ifndef([AC_PROG_LD_RELOAD_FLAG], [AC_DEFUN([AC_PROG_LD_RELOAD_FLAG])]) m4_ifndef([AC_DEPLIBS_CHECK_METHOD], [AC_DEFUN([AC_DEPLIBS_CHECK_METHOD])]) m4_ifndef([AC_LIBTOOL_PROG_COMPILER_NO_RTTI], [AC_DEFUN([AC_LIBTOOL_PROG_COMPILER_NO_RTTI])]) m4_ifndef([AC_LIBTOOL_SYS_GLOBAL_SYMBOL_PIPE], [AC_DEFUN([AC_LIBTOOL_SYS_GLOBAL_SYMBOL_PIPE])]) m4_ifndef([AC_LIBTOOL_PROG_COMPILER_PIC], [AC_DEFUN([AC_LIBTOOL_PROG_COMPILER_PIC])]) m4_ifndef([AC_LIBTOOL_PROG_LD_SHLIBS], [AC_DEFUN([AC_LIBTOOL_PROG_LD_SHLIBS])]) m4_ifndef([AC_LIBTOOL_POSTDEP_PREDEP], [AC_DEFUN([AC_LIBTOOL_POSTDEP_PREDEP])]) m4_ifndef([LT_AC_PROG_EGREP], [AC_DEFUN([LT_AC_PROG_EGREP])]) m4_ifndef([LT_AC_PROG_SED], [AC_DEFUN([LT_AC_PROG_SED])]) m4_ifndef([_LT_CC_BASENAME], [AC_DEFUN([_LT_CC_BASENAME])]) m4_ifndef([_LT_COMPILER_BOILERPLATE], [AC_DEFUN([_LT_COMPILER_BOILERPLATE])]) m4_ifndef([_LT_LINKER_BOILERPLATE], [AC_DEFUN([_LT_LINKER_BOILERPLATE])]) m4_ifndef([_AC_PROG_LIBTOOL], [AC_DEFUN([_AC_PROG_LIBTOOL])]) m4_ifndef([AC_LIBTOOL_SETUP], [AC_DEFUN([AC_LIBTOOL_SETUP])]) m4_ifndef([_LT_AC_CHECK_DLFCN], [AC_DEFUN([_LT_AC_CHECK_DLFCN])]) m4_ifndef([AC_LIBTOOL_SYS_DYNAMIC_LINKER], [AC_DEFUN([AC_LIBTOOL_SYS_DYNAMIC_LINKER])]) m4_ifndef([_LT_AC_TAGCONFIG], [AC_DEFUN([_LT_AC_TAGCONFIG])]) m4_ifndef([AC_DISABLE_FAST_INSTALL], [AC_DEFUN([AC_DISABLE_FAST_INSTALL])]) m4_ifndef([_LT_AC_LANG_CXX], [AC_DEFUN([_LT_AC_LANG_CXX])]) m4_ifndef([_LT_AC_LANG_F77], [AC_DEFUN([_LT_AC_LANG_F77])]) m4_ifndef([_LT_AC_LANG_GCJ], [AC_DEFUN([_LT_AC_LANG_GCJ])]) m4_ifndef([AC_LIBTOOL_LANG_C_CONFIG], [AC_DEFUN([AC_LIBTOOL_LANG_C_CONFIG])]) m4_ifndef([_LT_AC_LANG_C_CONFIG], [AC_DEFUN([_LT_AC_LANG_C_CONFIG])]) m4_ifndef([AC_LIBTOOL_LANG_CXX_CONFIG], [AC_DEFUN([AC_LIBTOOL_LANG_CXX_CONFIG])]) m4_ifndef([_LT_AC_LANG_CXX_CONFIG], [AC_DEFUN([_LT_AC_LANG_CXX_CONFIG])]) m4_ifndef([AC_LIBTOOL_LANG_F77_CONFIG], [AC_DEFUN([AC_LIBTOOL_LANG_F77_CONFIG])]) m4_ifndef([_LT_AC_LANG_F77_CONFIG], [AC_DEFUN([_LT_AC_LANG_F77_CONFIG])]) m4_ifndef([AC_LIBTOOL_LANG_GCJ_CONFIG], [AC_DEFUN([AC_LIBTOOL_LANG_GCJ_CONFIG])]) m4_ifndef([_LT_AC_LANG_GCJ_CONFIG], [AC_DEFUN([_LT_AC_LANG_GCJ_CONFIG])]) m4_ifndef([AC_LIBTOOL_LANG_RC_CONFIG], [AC_DEFUN([AC_LIBTOOL_LANG_RC_CONFIG])]) m4_ifndef([_LT_AC_LANG_RC_CONFIG], [AC_DEFUN([_LT_AC_LANG_RC_CONFIG])]) m4_ifndef([AC_LIBTOOL_CONFIG], [AC_DEFUN([AC_LIBTOOL_CONFIG])]) m4_ifndef([_LT_AC_FILE_LTDLL_C], [AC_DEFUN([_LT_AC_FILE_LTDLL_C])]) m4_ifndef([_LT_REQUIRED_DARWIN_CHECKS], [AC_DEFUN([_LT_REQUIRED_DARWIN_CHECKS])]) m4_ifndef([_LT_AC_PROG_CXXCPP], [AC_DEFUN([_LT_AC_PROG_CXXCPP])]) m4_ifndef([_LT_PREPARE_SED_QUOTE_VARS], [AC_DEFUN([_LT_PREPARE_SED_QUOTE_VARS])]) m4_ifndef([_LT_PROG_ECHO_BACKSLASH], [AC_DEFUN([_LT_PROG_ECHO_BACKSLASH])]) m4_ifndef([_LT_PROG_F77], [AC_DEFUN([_LT_PROG_F77])]) m4_ifndef([_LT_PROG_FC], [AC_DEFUN([_LT_PROG_FC])]) m4_ifndef([_LT_PROG_CXX], [AC_DEFUN([_LT_PROG_CXX])]) frr-7.2.1/m4/pkg.m40000644000000000000000000001717413610377563010571 00000000000000# pkg.m4 - Macros to locate and utilise pkg-config. -*- Autoconf -*- # serial 1 (pkg-config-0.24) # # Copyright © 2004 Scott James Remnant . # # This program is free software; you can redistribute it and/or modify # it under the terms of the GNU General Public License as published by # the Free Software Foundation; either version 2 of the License, or # (at your option) any later version. # # This program is distributed in the hope that it will be useful, but # WITHOUT ANY WARRANTY; without even the implied warranty of # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU # General Public License for more details. # # You should have received a copy of the GNU General Public License # along with this program; if not, write to the Free Software # Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. # # As a special exception to the GNU General Public License, if you # distribute this file as part of a program that contains a # configuration script generated by Autoconf, you may include it under # the same distribution terms that you use for the rest of that program. # PKG_PROG_PKG_CONFIG([MIN-VERSION]) # ---------------------------------- AC_DEFUN([PKG_PROG_PKG_CONFIG], [m4_pattern_forbid([^_?PKG_[A-Z_]+$]) m4_pattern_allow([^PKG_CONFIG(_(PATH|LIBDIR|SYSROOT_DIR|ALLOW_SYSTEM_(CFLAGS|LIBS)))?$]) m4_pattern_allow([^PKG_CONFIG_(DISABLE_UNINSTALLED|TOP_BUILD_DIR|DEBUG_SPEW)$]) AC_ARG_VAR([PKG_CONFIG], [path to pkg-config utility]) AC_ARG_VAR([PKG_CONFIG_PATH], [directories to add to pkg-config's search path]) AC_ARG_VAR([PKG_CONFIG_LIBDIR], [path overriding pkg-config's built-in search path]) if test "x$ac_cv_env_PKG_CONFIG_set" != "xset"; then AC_PATH_TOOL([PKG_CONFIG], [pkg-config]) fi if test -n "$PKG_CONFIG"; then _pkg_min_version=m4_default([$1], [0.9.0]) AC_MSG_CHECKING([pkg-config is at least version $_pkg_min_version]) if $PKG_CONFIG --atleast-pkgconfig-version $_pkg_min_version; then AC_MSG_RESULT([yes]) else AC_MSG_RESULT([no]) PKG_CONFIG="" fi fi[]dnl ])# PKG_PROG_PKG_CONFIG # PKG_CHECK_EXISTS(MODULES, [ACTION-IF-FOUND], [ACTION-IF-NOT-FOUND]) # # Check to see whether a particular set of modules exists. Similar # to PKG_CHECK_MODULES(), but does not set variables or print errors. # # Please remember that m4 expands AC_REQUIRE([PKG_PROG_PKG_CONFIG]) # only at the first occurence in configure.ac, so if the first place # it's called might be skipped (such as if it is within an "if", you # have to call PKG_CHECK_EXISTS manually # -------------------------------------------------------------- AC_DEFUN([PKG_CHECK_EXISTS], [AC_REQUIRE([PKG_PROG_PKG_CONFIG])dnl if test -n "$PKG_CONFIG" && \ AC_RUN_LOG([$PKG_CONFIG --exists --print-errors "$1"]); then m4_default([$2], [:]) m4_ifvaln([$3], [else $3])dnl fi]) # _PKG_CONFIG([VARIABLE], [COMMAND], [MODULES]) # --------------------------------------------- m4_define([_PKG_CONFIG], [if test -n "$$1"; then pkg_cv_[]$1="$$1" elif test -n "$PKG_CONFIG"; then PKG_CHECK_EXISTS([$3], [pkg_cv_[]$1=`$PKG_CONFIG --[]$2 "$3" 2>/dev/null` test "x$?" != "x0" && pkg_failed=yes ], [pkg_failed=yes]) else pkg_failed=untried fi[]dnl ])# _PKG_CONFIG # _PKG_SHORT_ERRORS_SUPPORTED # ----------------------------- AC_DEFUN([_PKG_SHORT_ERRORS_SUPPORTED], [AC_REQUIRE([PKG_PROG_PKG_CONFIG]) if $PKG_CONFIG --atleast-pkgconfig-version 0.20; then _pkg_short_errors_supported=yes else _pkg_short_errors_supported=no fi[]dnl ])# _PKG_SHORT_ERRORS_SUPPORTED # PKG_CHECK_MODULES(VARIABLE-PREFIX, MODULES, [ACTION-IF-FOUND], # [ACTION-IF-NOT-FOUND]) # # # Note that if there is a possibility the first call to # PKG_CHECK_MODULES might not happen, you should be sure to include an # explicit call to PKG_PROG_PKG_CONFIG in your configure.ac # # # -------------------------------------------------------------- AC_DEFUN([PKG_CHECK_MODULES], [AC_REQUIRE([PKG_PROG_PKG_CONFIG])dnl AC_ARG_VAR([$1][_CFLAGS], [C compiler flags for $1, overriding pkg-config])dnl AC_ARG_VAR([$1][_LIBS], [linker flags for $1, overriding pkg-config])dnl pkg_failed=no AC_MSG_CHECKING([for $1 ($2)]) _PKG_CONFIG([$1][_CFLAGS], [cflags], [$2]) _PKG_CONFIG([$1][_LIBS], [libs], [$2]) m4_define([_PKG_TEXT], [Alternatively, you may set the environment variables $1[]_CFLAGS and $1[]_LIBS to avoid the need to call pkg-config. See the pkg-config man page for more details.]) if test $pkg_failed = yes; then AC_MSG_RESULT([no]) _PKG_SHORT_ERRORS_SUPPORTED if test $_pkg_short_errors_supported = yes; then $1[]_PKG_ERRORS=`$PKG_CONFIG --short-errors --print-errors --cflags --libs "$2" 2>&1` else $1[]_PKG_ERRORS=`$PKG_CONFIG --print-errors --cflags --libs "$2" 2>&1` fi # Put the nasty error message in config.log where it belongs echo "$$1[]_PKG_ERRORS" >&AS_MESSAGE_LOG_FD m4_default([$4], [AC_MSG_ERROR( [Package requirements ($2) were not met: $$1_PKG_ERRORS Consider adjusting the PKG_CONFIG_PATH environment variable if you installed software in a non-standard prefix. _PKG_TEXT])[]dnl ]) elif test $pkg_failed = untried; then AC_MSG_RESULT([no]) m4_default([$4], [AC_MSG_FAILURE( [The pkg-config script could not be found or is too old. Make sure it is in your PATH or set the PKG_CONFIG environment variable to the full path to pkg-config. _PKG_TEXT To get pkg-config, see .])[]dnl ]) else $1[]_CFLAGS=$pkg_cv_[]$1[]_CFLAGS $1[]_LIBS=$pkg_cv_[]$1[]_LIBS AC_MSG_RESULT([yes]) $3 fi[]dnl ])# PKG_CHECK_MODULES # PKG_INSTALLDIR(DIRECTORY) # ------------------------- # Substitutes the variable pkgconfigdir as the location where a module # should install pkg-config .pc files. By default the directory is # $libdir/pkgconfig, but the default can be changed by passing # DIRECTORY. The user can override through the --with-pkgconfigdir # parameter. AC_DEFUN([PKG_INSTALLDIR], [m4_pushdef([pkg_default], [m4_default([$1], ['${libdir}/pkgconfig'])]) m4_pushdef([pkg_description], [pkg-config installation directory @<:@]pkg_default[@:>@]) AC_ARG_WITH([pkgconfigdir], [AS_HELP_STRING([--with-pkgconfigdir], pkg_description)],, [with_pkgconfigdir=]pkg_default) AC_SUBST([pkgconfigdir], [$with_pkgconfigdir]) m4_popdef([pkg_default]) m4_popdef([pkg_description]) ]) dnl PKG_INSTALLDIR # PKG_NOARCH_INSTALLDIR(DIRECTORY) # ------------------------- # Substitutes the variable noarch_pkgconfigdir as the location where a # module should install arch-independent pkg-config .pc files. By # default the directory is $datadir/pkgconfig, but the default can be # changed by passing DIRECTORY. The user can override through the # --with-noarch-pkgconfigdir parameter. AC_DEFUN([PKG_NOARCH_INSTALLDIR], [m4_pushdef([pkg_default], [m4_default([$1], ['${datadir}/pkgconfig'])]) m4_pushdef([pkg_description], [pkg-config arch-independent installation directory @<:@]pkg_default[@:>@]) AC_ARG_WITH([noarch-pkgconfigdir], [AS_HELP_STRING([--with-noarch-pkgconfigdir], pkg_description)],, [with_noarch_pkgconfigdir=]pkg_default) AC_SUBST([noarch_pkgconfigdir], [$with_noarch_pkgconfigdir]) m4_popdef([pkg_default]) m4_popdef([pkg_description]) ]) dnl PKG_NOARCH_INSTALLDIR # PKG_CHECK_VAR(VARIABLE, MODULE, CONFIG-VARIABLE, # [ACTION-IF-FOUND], [ACTION-IF-NOT-FOUND]) # ------------------------------------------- # Retrieves the value of the pkg-config variable for the given module. AC_DEFUN([PKG_CHECK_VAR], [AC_REQUIRE([PKG_PROG_PKG_CONFIG])dnl AC_ARG_VAR([$1], [value of $3 for $2, overriding pkg-config])dnl _PKG_CONFIG([$1], [variable="][$3]["], [$2]) AS_VAR_COPY([$1], [pkg_cv_][$1]) AS_VAR_IF([$1], [""], [$5], [$4])dnl ])# PKG_CHECK_VAR frr-7.2.1/missing0000755000000000000000000001533613610377563010623 00000000000000#! /bin/sh # Common wrapper for a few potentially missing GNU programs. scriptversion=2018-03-07.03; # UTC # Copyright (C) 1996-2018 Free Software Foundation, Inc. # Originally written by Fran,cois Pinard , 1996. # This program is free software; you can redistribute it and/or modify # it under the terms of the GNU General Public License as published by # the Free Software Foundation; either version 2, or (at your option) # any later version. # This program is distributed in the hope that it will be useful, # but WITHOUT ANY WARRANTY; without even the implied warranty of # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the # GNU General Public License for more details. # You should have received a copy of the GNU General Public License # along with this program. If not, see . # As a special exception to the GNU General Public License, if you # distribute this file as part of a program that contains a # configuration script generated by Autoconf, you may include it under # the same distribution terms that you use for the rest of that program. if test $# -eq 0; then echo 1>&2 "Try '$0 --help' for more information" exit 1 fi case $1 in --is-lightweight) # Used by our autoconf macros to check whether the available missing # script is modern enough. exit 0 ;; --run) # Back-compat with the calling convention used by older automake. shift ;; -h|--h|--he|--hel|--help) echo "\ $0 [OPTION]... PROGRAM [ARGUMENT]... Run 'PROGRAM [ARGUMENT]...', returning a proper advice when this fails due to PROGRAM being missing or too old. Options: -h, --help display this help and exit -v, --version output version information and exit Supported PROGRAM values: aclocal autoconf autoheader autom4te automake makeinfo bison yacc flex lex help2man Version suffixes to PROGRAM as well as the prefixes 'gnu-', 'gnu', and 'g' are ignored when checking the name. Send bug reports to ." exit $? ;; -v|--v|--ve|--ver|--vers|--versi|--versio|--version) echo "missing $scriptversion (GNU Automake)" exit $? ;; -*) echo 1>&2 "$0: unknown '$1' option" echo 1>&2 "Try '$0 --help' for more information" exit 1 ;; esac # Run the given program, remember its exit status. "$@"; st=$? # If it succeeded, we are done. test $st -eq 0 && exit 0 # Also exit now if we it failed (or wasn't found), and '--version' was # passed; such an option is passed most likely to detect whether the # program is present and works. case $2 in --version|--help) exit $st;; esac # Exit code 63 means version mismatch. This often happens when the user # tries to use an ancient version of a tool on a file that requires a # minimum version. if test $st -eq 63; then msg="probably too old" elif test $st -eq 127; then # Program was missing. msg="missing on your system" else # Program was found and executed, but failed. Give up. exit $st fi perl_URL=https://www.perl.org/ flex_URL=https://github.com/westes/flex gnu_software_URL=https://www.gnu.org/software program_details () { case $1 in aclocal|automake) echo "The '$1' program is part of the GNU Automake package:" echo "<$gnu_software_URL/automake>" echo "It also requires GNU Autoconf, GNU m4 and Perl in order to run:" echo "<$gnu_software_URL/autoconf>" echo "<$gnu_software_URL/m4/>" echo "<$perl_URL>" ;; autoconf|autom4te|autoheader) echo "The '$1' program is part of the GNU Autoconf package:" echo "<$gnu_software_URL/autoconf/>" echo "It also requires GNU m4 and Perl in order to run:" echo "<$gnu_software_URL/m4/>" echo "<$perl_URL>" ;; esac } give_advice () { # Normalize program name to check for. normalized_program=`echo "$1" | sed ' s/^gnu-//; t s/^gnu//; t s/^g//; t'` printf '%s\n' "'$1' is $msg." configure_deps="'configure.ac' or m4 files included by 'configure.ac'" case $normalized_program in autoconf*) echo "You should only need it if you modified 'configure.ac'," echo "or m4 files included by it." program_details 'autoconf' ;; autoheader*) echo "You should only need it if you modified 'acconfig.h' or" echo "$configure_deps." program_details 'autoheader' ;; automake*) echo "You should only need it if you modified 'Makefile.am' or" echo "$configure_deps." program_details 'automake' ;; aclocal*) echo "You should only need it if you modified 'acinclude.m4' or" echo "$configure_deps." program_details 'aclocal' ;; autom4te*) echo "You might have modified some maintainer files that require" echo "the 'autom4te' program to be rebuilt." program_details 'autom4te' ;; bison*|yacc*) echo "You should only need it if you modified a '.y' file." echo "You may want to install the GNU Bison package:" echo "<$gnu_software_URL/bison/>" ;; lex*|flex*) echo "You should only need it if you modified a '.l' file." echo "You may want to install the Fast Lexical Analyzer package:" echo "<$flex_URL>" ;; help2man*) echo "You should only need it if you modified a dependency" \ "of a man page." echo "You may want to install the GNU Help2man package:" echo "<$gnu_software_URL/help2man/>" ;; makeinfo*) echo "You should only need it if you modified a '.texi' file, or" echo "any other file indirectly affecting the aspect of the manual." echo "You might want to install the Texinfo package:" echo "<$gnu_software_URL/texinfo/>" echo "The spurious makeinfo call might also be the consequence of" echo "using a buggy 'make' (AIX, DU, IRIX), in which case you might" echo "want to install GNU make:" echo "<$gnu_software_URL/make/>" ;; *) echo "You might have modified some files without having the proper" echo "tools for further handling them. Check the 'README' file, it" echo "often tells you about the needed prerequisites for installing" echo "this package. You may also peek at any GNU archive site, in" echo "case some other package contains this missing '$1' program." ;; esac } give_advice "$1" | sed -e '1s/^/WARNING: /' \ -e '2,$s/^/ /' >&2 # Propagate the correct exit status (expected to be 127 for a program # not found, 63 for a program that failed due to version mismatch). exit $st # Local variables: # eval: (add-hook 'before-save-hook 'time-stamp) # time-stamp-start: "scriptversion=" # time-stamp-format: "%:y-%02m-%02d.%02H" # time-stamp-time-zone: "UTC0" # time-stamp-end: "; # UTC" # End: frr-7.2.1/nhrpd/0000755000000000000000000000000013610377563010407 500000000000000frr-7.2.1/nhrpd/Makefile0000644000000000000000000000022213610377563011763 00000000000000all: ALWAYS @$(MAKE) -s -C .. nhrpd/nhrpd %: ALWAYS @$(MAKE) -s -C .. nhrpd/$@ Makefile: #nothing ALWAYS: .PHONY: ALWAYS makefiles .SUFFIXES: frr-7.2.1/nhrpd/debug.h0000644000000000000000000000234513610377563011572 00000000000000#include "log.h" #if defined(__GNUC__) && (__GNUC__ >= 3) #define likely(_x) __builtin_expect(!!(_x), 1) #define unlikely(_x) __builtin_expect(!!(_x), 0) #else #define likely(_x) !!(_x) #define unlikely(_x) !!(_x) #endif #define NHRP_DEBUG_COMMON (1 << 0) #define NHRP_DEBUG_KERNEL (1 << 1) #define NHRP_DEBUG_IF (1 << 2) #define NHRP_DEBUG_ROUTE (1 << 3) #define NHRP_DEBUG_VICI (1 << 4) #define NHRP_DEBUG_EVENT (1 << 5) #define NHRP_DEBUG_ALL (0xFFFF) extern unsigned int debug_flags; #if defined __STDC_VERSION__ && __STDC_VERSION__ >= 199901L #define debugf(level, ...) \ do { \ if (unlikely(debug_flags & level)) \ zlog_debug(__VA_ARGS__); \ } while (0) #elif defined __GNUC__ #define debugf(level, _args...) \ do { \ if (unlikely(debug_flags & level)) \ zlog_debug(_args); \ } while (0) #else static inline void debugf(int level, const char *format, ...) { } #endif frr-7.2.1/nhrpd/linux.c0000644000000000000000000000630613610377563011637 00000000000000/* NHRP daemon Linux specific glue * Copyright (c) 2014-2015 Timo Teräs * * This file is free software: you may copy, redistribute and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation, either version 2 of the License, or * (at your option) any later version. */ #ifdef HAVE_CONFIG_H #include "config.h" #endif #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include "nhrp_protocol.h" #include "os.h" #include "netlink.h" static int nhrp_socket_fd = -1; int os_socket(void) { if (nhrp_socket_fd < 0) nhrp_socket_fd = socket(PF_PACKET, SOCK_DGRAM, htons(ETH_P_NHRP)); return nhrp_socket_fd; } int os_sendmsg(const uint8_t *buf, size_t len, int ifindex, const uint8_t *addr, size_t addrlen) { struct sockaddr_ll lladdr; struct iovec iov = { .iov_base = (void *)buf, .iov_len = len, }; struct msghdr msg = { .msg_name = &lladdr, .msg_namelen = sizeof(lladdr), .msg_iov = &iov, .msg_iovlen = 1, }; int status; if (addrlen > sizeof(lladdr.sll_addr)) return -1; memset(&lladdr, 0, sizeof(lladdr)); lladdr.sll_family = AF_PACKET; lladdr.sll_protocol = htons(ETH_P_NHRP); lladdr.sll_ifindex = ifindex; lladdr.sll_halen = addrlen; memcpy(lladdr.sll_addr, addr, addrlen); status = sendmsg(nhrp_socket_fd, &msg, 0); if (status < 0) return -1; return 0; } int os_recvmsg(uint8_t *buf, size_t *len, int *ifindex, uint8_t *addr, size_t *addrlen) { struct sockaddr_ll lladdr; struct iovec iov = { .iov_base = buf, .iov_len = *len, }; struct msghdr msg = { .msg_name = &lladdr, .msg_namelen = sizeof(lladdr), .msg_iov = &iov, .msg_iovlen = 1, }; int r; r = recvmsg(nhrp_socket_fd, &msg, MSG_DONTWAIT); if (r < 0) return r; *len = r; *ifindex = lladdr.sll_ifindex; if (*addrlen <= (size_t)lladdr.sll_addr) { if (memcmp(lladdr.sll_addr, "\x00\x00\x00\x00", 4) != 0) { memcpy(addr, lladdr.sll_addr, lladdr.sll_halen); *addrlen = lladdr.sll_halen; } else { *addrlen = 0; } } return 0; } static int linux_configure_arp(const char *iface, int on) { struct ifreq ifr; strncpy(ifr.ifr_name, iface, IFNAMSIZ - 1); if (ioctl(nhrp_socket_fd, SIOCGIFFLAGS, &ifr)) return -1; if (on) ifr.ifr_flags &= ~IFF_NOARP; else ifr.ifr_flags |= IFF_NOARP; if (ioctl(nhrp_socket_fd, SIOCSIFFLAGS, &ifr)) return -1; return 0; } static int linux_icmp_redirect_off(const char *iface) { char fname[256]; int fd, ret = -1; sprintf(fname, "/proc/sys/net/ipv4/conf/%s/send_redirects", iface); fd = open(fname, O_WRONLY); if (fd < 0) return -1; if (write(fd, "0\n", 2) == 2) ret = 0; close(fd); return ret; } int os_configure_dmvpn(unsigned int ifindex, const char *ifname, int af) { int ret = 0; switch (af) { case AF_INET: ret |= linux_icmp_redirect_off("all"); ret |= linux_icmp_redirect_off(ifname); break; } ret |= linux_configure_arp(ifname, 1); ret |= netlink_configure_arp(ifindex, af); return ret; } frr-7.2.1/nhrpd/list.h0000644000000000000000000001371313610377563011460 00000000000000/* Linux kernel style list handling function * * Written from scratch by Timo Teräs , but modeled * after the linux kernel code. * * This file is free software: you may copy, redistribute and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation, either version 2 of the License, or * (at your option) any later version. */ #ifndef LIST_H #define LIST_H #ifndef NULL #define NULL 0L #endif #ifndef offsetof #ifdef __compiler_offsetof #define offsetof(TYPE,MEMBER) __compiler_offsetof(TYPE,MEMBER) #else #define offsetof(TYPE, MEMBER) ((size_t) &((TYPE *)0)->MEMBER) #endif #endif #ifndef container_of #define container_of(ptr, type, member) \ ({ \ const typeof(((type *)0)->member) *__mptr = (ptr); \ (type *)((char *)__mptr - offsetof(type, member)); \ }) #endif struct hlist_head { struct hlist_node *first; }; struct hlist_node { struct hlist_node *next; struct hlist_node **pprev; }; static inline int hlist_empty(const struct hlist_head *h) { return !h->first; } static inline int hlist_hashed(const struct hlist_node *n) { return n->pprev != NULL; } static inline void hlist_del(struct hlist_node *n) { struct hlist_node *next = n->next; struct hlist_node **pprev = n->pprev; *pprev = next; if (next) next->pprev = pprev; n->next = NULL; n->pprev = NULL; } static inline void hlist_add_head(struct hlist_node *n, struct hlist_head *h) { struct hlist_node *first = h->first; n->next = first; if (first) first->pprev = &n->next; n->pprev = &h->first; h->first = n; } static inline void hlist_add_after(struct hlist_node *n, struct hlist_node *prev) { n->next = prev->next; n->pprev = &prev->next; prev->next = n; } static inline struct hlist_node **hlist_tail_ptr(struct hlist_head *h) { struct hlist_node *n = h->first; if (n == NULL) return &h->first; while (n->next != NULL) n = n->next; return &n->next; } #define hlist_entry(ptr, type, member) container_of(ptr,type,member) #define hlist_for_each(pos, head) \ for (pos = (head)->first; pos; pos = pos->next) #define hlist_for_each_safe(pos, n, head) \ for (pos = (head)->first; pos && ({ \ n = pos->next; \ 1; \ }); \ pos = n) #define hlist_for_each_entry(tpos, pos, head, member) \ for (pos = (head)->first; \ pos && ({ \ tpos = hlist_entry(pos, typeof(*tpos), member); \ 1; \ }); \ pos = pos->next) #define hlist_for_each_entry_safe(tpos, pos, n, head, member) \ for (pos = (head)->first; \ pos && ({ \ n = pos->next; \ 1; \ }) \ && ({ \ tpos = hlist_entry(pos, typeof(*tpos), member); \ 1; \ }); \ pos = n) struct list_head { struct list_head *next, *prev; }; #define LIST_INITIALIZER(l) { .next = &l, .prev = &l } static inline void list_init(struct list_head *list) { list->next = list; list->prev = list; } static inline void __list_add(struct list_head *new, struct list_head *prev, struct list_head *next) { next->prev = new; new->next = next; new->prev = prev; prev->next = new; } static inline void list_add(struct list_head *new, struct list_head *head) { __list_add(new, head, head->next); } static inline void list_add_tail(struct list_head *new, struct list_head *head) { __list_add(new, head->prev, head); } static inline void __list_del(struct list_head *prev, struct list_head *next) { next->prev = prev; prev->next = next; } static inline void list_del(struct list_head *entry) { __list_del(entry->prev, entry->next); entry->next = NULL; entry->prev = NULL; } static inline int list_hashed(const struct list_head *n) { return n->next != n && n->next != NULL; } static inline int list_empty(const struct list_head *n) { return !list_hashed(n); } #define list_next(ptr, type, member) \ (list_hashed(ptr) ? container_of((ptr)->next, type, member) : NULL) #define list_entry(ptr, type, member) container_of(ptr,type,member) #define list_for_each(pos, head) \ for (pos = (head)->next; pos != (head); pos = pos->next) #define list_for_each_safe(pos, n, head) \ for (pos = (head)->next, n = pos->next; pos != (head); \ pos = n, n = pos->next) #define list_for_each_entry(pos, head, member) \ for (pos = list_entry((head)->next, typeof(*pos), member); \ &pos->member != (head); \ pos = list_entry(pos->member.next, typeof(*pos), member)) #define list_for_each_entry_safe(pos, n, head, member) \ for (pos = list_entry((head)->next, typeof(*pos), member), \ n = list_entry(pos->member.next, typeof(*pos), member); \ &pos->member != (head); \ pos = n, n = list_entry(n->member.next, typeof(*n), member)) #endif frr-7.2.1/nhrpd/netlink.h0000644000000000000000000000155713610377563012154 00000000000000/* NHRP netlink/neighbor table API * Copyright (c) 2014-2015 Timo Teräs * * This file is free software: you may copy, redistribute and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation, either version 2 of the License, or * (at your option) any later version. */ #include union sockunion; struct interface; extern int netlink_nflog_group; extern int netlink_req_fd; void netlink_init(void); int netlink_configure_arp(unsigned int ifindex, int pf); void netlink_update_binding(struct interface *ifp, union sockunion *proto, union sockunion *nbma); void netlink_set_nflog_group(int nlgroup); void netlink_gre_get_info(unsigned int ifindex, uint32_t *gre_key, unsigned int *link_index, struct in_addr *saddr); void netlink_gre_set_link(unsigned int ifindex, unsigned int link_index); frr-7.2.1/nhrpd/netlink_arp.c0000644000000000000000000001657713610377563013021 00000000000000/* NHRP netlink/neighbor table arpd code * Copyright (c) 2014-2016 Timo Teräs * * This file is free software: you may copy, redistribute and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation, either version 2 of the License, or * (at your option) any later version. */ #ifdef HAVE_CONFIG_H #include "config.h" #endif #include #include #include #include #include #include #include "thread.h" #include "nhrpd.h" #include "netlink.h" #include "znl.h" int netlink_req_fd = -1; int netlink_nflog_group; static int netlink_log_fd = -1; static struct thread *netlink_log_thread; static int netlink_listen_fd = -1; typedef void (*netlink_dispatch_f)(struct nlmsghdr *msg, struct zbuf *zb); void netlink_update_binding(struct interface *ifp, union sockunion *proto, union sockunion *nbma) { struct nlmsghdr *n; struct ndmsg *ndm; struct zbuf *zb = zbuf_alloc(512); n = znl_nlmsg_push(zb, nbma ? RTM_NEWNEIGH : RTM_DELNEIGH, NLM_F_REQUEST | NLM_F_REPLACE | NLM_F_CREATE); ndm = znl_push(zb, sizeof(*ndm)); *ndm = (struct ndmsg){ .ndm_family = sockunion_family(proto), .ndm_ifindex = ifp->ifindex, .ndm_type = RTN_UNICAST, .ndm_state = nbma ? NUD_REACHABLE : NUD_FAILED, }; znl_rta_push(zb, NDA_DST, sockunion_get_addr(proto), family2addrsize(sockunion_family(proto))); if (nbma) znl_rta_push(zb, NDA_LLADDR, sockunion_get_addr(nbma), family2addrsize(sockunion_family(nbma))); znl_nlmsg_complete(zb, n); zbuf_send(zb, netlink_req_fd); zbuf_recv(zb, netlink_req_fd); zbuf_free(zb); } static void netlink_neigh_msg(struct nlmsghdr *msg, struct zbuf *zb) { struct ndmsg *ndm; struct rtattr *rta; struct nhrp_cache *c; struct interface *ifp; struct zbuf payload; union sockunion addr; size_t len; char buf[SU_ADDRSTRLEN]; int state; ndm = znl_pull(zb, sizeof(*ndm)); if (!ndm) return; sockunion_family(&addr) = AF_UNSPEC; while ((rta = znl_rta_pull(zb, &payload)) != NULL) { len = zbuf_used(&payload); switch (rta->rta_type) { case NDA_DST: sockunion_set(&addr, ndm->ndm_family, zbuf_pulln(&payload, len), len); break; } } ifp = if_lookup_by_index(ndm->ndm_ifindex, VRF_DEFAULT); if (!ifp || sockunion_family(&addr) == AF_UNSPEC) return; c = nhrp_cache_get(ifp, &addr, 0); if (!c) return; if (msg->nlmsg_type == RTM_GETNEIGH) { debugf(NHRP_DEBUG_KERNEL, "Netlink: who-has %s dev %s", sockunion2str(&addr, buf, sizeof buf), ifp->name); if (c->cur.type >= NHRP_CACHE_CACHED) { nhrp_cache_set_used(c, 1); netlink_update_binding(ifp, &addr, &c->cur.peer->vc->remote.nbma); } } else { debugf(NHRP_DEBUG_KERNEL, "Netlink: update %s dev %s nud %x", sockunion2str(&addr, buf, sizeof buf), ifp->name, ndm->ndm_state); state = (msg->nlmsg_type == RTM_NEWNEIGH) ? ndm->ndm_state : NUD_FAILED; nhrp_cache_set_used(c, state == NUD_REACHABLE); } } static int netlink_route_recv(struct thread *t) { uint8_t buf[ZNL_BUFFER_SIZE]; int fd = THREAD_FD(t); struct zbuf payload, zb; struct nlmsghdr *n; zbuf_init(&zb, buf, sizeof(buf), 0); while (zbuf_recv(&zb, fd) > 0) { while ((n = znl_nlmsg_pull(&zb, &payload)) != NULL) { debugf(NHRP_DEBUG_KERNEL, "Netlink: Received msg_type %u, msg_flags %u", n->nlmsg_type, n->nlmsg_flags); switch (n->nlmsg_type) { case RTM_GETNEIGH: case RTM_NEWNEIGH: case RTM_DELNEIGH: netlink_neigh_msg(n, &payload); break; } } } thread_add_read(master, netlink_route_recv, 0, fd, NULL); return 0; } static void netlink_log_register(int fd, int group) { struct nlmsghdr *n; struct nfgenmsg *nf; struct nfulnl_msg_config_cmd cmd; struct zbuf *zb = zbuf_alloc(512); n = znl_nlmsg_push(zb, (NFNL_SUBSYS_ULOG << 8) | NFULNL_MSG_CONFIG, NLM_F_REQUEST | NLM_F_ACK); nf = znl_push(zb, sizeof(*nf)); *nf = (struct nfgenmsg){ .nfgen_family = AF_UNSPEC, .version = NFNETLINK_V0, .res_id = htons(group), }; cmd.command = NFULNL_CFG_CMD_BIND; znl_rta_push(zb, NFULA_CFG_CMD, &cmd, sizeof(cmd)); znl_nlmsg_complete(zb, n); zbuf_send(zb, fd); zbuf_free(zb); } static void netlink_log_indication(struct nlmsghdr *msg, struct zbuf *zb) { struct nfgenmsg *nf; struct rtattr *rta; struct zbuf rtapl, pktpl; struct interface *ifp; struct nfulnl_msg_packet_hdr *pkthdr = NULL; uint32_t *in_ndx = NULL; nf = znl_pull(zb, sizeof(*nf)); if (!nf) return; memset(&pktpl, 0, sizeof(pktpl)); while ((rta = znl_rta_pull(zb, &rtapl)) != NULL) { switch (rta->rta_type) { case NFULA_PACKET_HDR: pkthdr = znl_pull(&rtapl, sizeof(*pkthdr)); break; case NFULA_IFINDEX_INDEV: in_ndx = znl_pull(&rtapl, sizeof(*in_ndx)); break; case NFULA_PAYLOAD: pktpl = rtapl; break; /* NFULA_HWHDR exists and is supposed to contain source * hardware address. However, for ip_gre it seems to be * the nexthop destination address if the packet matches * route. */ } } if (!pkthdr || !in_ndx || !zbuf_used(&pktpl)) return; ifp = if_lookup_by_index(htonl(*in_ndx), VRF_DEFAULT); if (!ifp) return; nhrp_peer_send_indication(ifp, htons(pkthdr->hw_protocol), &pktpl); } static int netlink_log_recv(struct thread *t) { uint8_t buf[ZNL_BUFFER_SIZE]; int fd = THREAD_FD(t); struct zbuf payload, zb; struct nlmsghdr *n; netlink_log_thread = NULL; zbuf_init(&zb, buf, sizeof(buf), 0); while (zbuf_recv(&zb, fd) > 0) { while ((n = znl_nlmsg_pull(&zb, &payload)) != NULL) { debugf(NHRP_DEBUG_KERNEL, "Netlink-log: Received msg_type %u, msg_flags %u", n->nlmsg_type, n->nlmsg_flags); switch (n->nlmsg_type) { case (NFNL_SUBSYS_ULOG << 8) | NFULNL_MSG_PACKET: netlink_log_indication(n, &payload); break; } } } thread_add_read(master, netlink_log_recv, 0, netlink_log_fd, &netlink_log_thread); return 0; } void netlink_set_nflog_group(int nlgroup) { if (netlink_log_fd >= 0) { THREAD_OFF(netlink_log_thread); close(netlink_log_fd); netlink_log_fd = -1; } netlink_nflog_group = nlgroup; if (nlgroup) { netlink_log_fd = znl_open(NETLINK_NETFILTER, 0); if (netlink_log_fd < 0) return; netlink_log_register(netlink_log_fd, nlgroup); thread_add_read(master, netlink_log_recv, 0, netlink_log_fd, &netlink_log_thread); } } void netlink_init(void) { netlink_req_fd = znl_open(NETLINK_ROUTE, 0); if (netlink_req_fd < 0) return; netlink_listen_fd = znl_open(NETLINK_ROUTE, RTMGRP_NEIGH); if (netlink_listen_fd < 0) return; thread_add_read(master, netlink_route_recv, 0, netlink_listen_fd, NULL); } int netlink_configure_arp(unsigned int ifindex, int pf) { struct nlmsghdr *n; struct ndtmsg *ndtm; struct rtattr *rta; struct zbuf *zb = zbuf_alloc(512); int r; n = znl_nlmsg_push(zb, RTM_SETNEIGHTBL, NLM_F_REQUEST | NLM_F_REPLACE); ndtm = znl_push(zb, sizeof(*ndtm)); *ndtm = (struct ndtmsg){ .ndtm_family = pf, }; znl_rta_push(zb, NDTA_NAME, pf == AF_INET ? "arp_cache" : "ndisc_cache", 10); rta = znl_rta_nested_push(zb, NDTA_PARMS); znl_rta_push_u32(zb, NDTPA_IFINDEX, ifindex); znl_rta_push_u32(zb, NDTPA_APP_PROBES, 1); znl_rta_push_u32(zb, NDTPA_MCAST_PROBES, 0); znl_rta_push_u32(zb, NDTPA_UCAST_PROBES, 0); znl_rta_nested_complete(zb, rta); znl_nlmsg_complete(zb, n); r = zbuf_send(zb, netlink_req_fd); zbuf_recv(zb, netlink_req_fd); zbuf_free(zb); return r; } frr-7.2.1/nhrpd/netlink_gre.c0000644000000000000000000000707713610377563013007 00000000000000/* NHRP netlink/GRE tunnel configuration code * Copyright (c) 2014-2016 Timo Teräs * * This file is free software: you may copy, redistribute and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation, either version 2 of the License, or * (at your option) any later version. */ #ifdef HAVE_CONFIG_H #include "config.h" #endif #include #include #include #include #include #include #include #include #include "debug.h" #include "netlink.h" #include "znl.h" static int __netlink_gre_get_data(struct zbuf *zb, struct zbuf *data, int ifindex) { struct nlmsghdr *n; struct ifinfomsg *ifi; struct zbuf payload, rtapayload; struct rtattr *rta; debugf(NHRP_DEBUG_KERNEL, "netlink-link-gre: get-info %u", ifindex); n = znl_nlmsg_push(zb, RTM_GETLINK, NLM_F_REQUEST); ifi = znl_push(zb, sizeof(*ifi)); *ifi = (struct ifinfomsg){ .ifi_index = ifindex, }; znl_nlmsg_complete(zb, n); if (zbuf_send(zb, netlink_req_fd) < 0 || zbuf_recv(zb, netlink_req_fd) < 0) return -1; n = znl_nlmsg_pull(zb, &payload); if (!n) return -1; if (n->nlmsg_type != RTM_NEWLINK) return -1; ifi = znl_pull(&payload, sizeof(struct ifinfomsg)); if (!ifi) return -1; debugf(NHRP_DEBUG_KERNEL, "netlink-link-gre: ifindex %u, receive msg_type %u, msg_flags %u", ifi->ifi_index, n->nlmsg_type, n->nlmsg_flags); if (ifi->ifi_index != ifindex) return -1; while ((rta = znl_rta_pull(&payload, &rtapayload)) != NULL) if (rta->rta_type == IFLA_LINKINFO) break; if (!rta) return -1; payload = rtapayload; while ((rta = znl_rta_pull(&payload, &rtapayload)) != NULL) if (rta->rta_type == IFLA_INFO_DATA) break; if (!rta) return -1; *data = rtapayload; return 0; } void netlink_gre_get_info(unsigned int ifindex, uint32_t *gre_key, unsigned int *link_index, struct in_addr *saddr) { struct zbuf *zb = zbuf_alloc(8192), data, rtapl; struct rtattr *rta; *link_index = 0; *gre_key = 0; saddr->s_addr = 0; if (__netlink_gre_get_data(zb, &data, ifindex) < 0) goto err; while ((rta = znl_rta_pull(&data, &rtapl)) != NULL) { switch (rta->rta_type) { case IFLA_GRE_LINK: *link_index = zbuf_get32(&rtapl); break; case IFLA_GRE_IKEY: case IFLA_GRE_OKEY: *gre_key = zbuf_get32(&rtapl); break; case IFLA_GRE_LOCAL: saddr->s_addr = zbuf_get32(&rtapl); break; } } err: zbuf_free(zb); } void netlink_gre_set_link(unsigned int ifindex, unsigned int link_index) { struct nlmsghdr *n; struct ifinfomsg *ifi; struct rtattr *rta_info, *rta_data, *rta; struct zbuf *zr = zbuf_alloc(8192), data, rtapl; struct zbuf *zb = zbuf_alloc(8192); size_t len; if (__netlink_gre_get_data(zr, &data, ifindex) < 0) goto err; n = znl_nlmsg_push(zb, RTM_NEWLINK, NLM_F_REQUEST); ifi = znl_push(zb, sizeof(*ifi)); *ifi = (struct ifinfomsg){ .ifi_index = ifindex, }; rta_info = znl_rta_nested_push(zb, IFLA_LINKINFO); znl_rta_push(zb, IFLA_INFO_KIND, "gre", 3); rta_data = znl_rta_nested_push(zb, IFLA_INFO_DATA); znl_rta_push_u32(zb, IFLA_GRE_LINK, link_index); while ((rta = znl_rta_pull(&data, &rtapl)) != NULL) { if (rta->rta_type == IFLA_GRE_LINK) continue; len = zbuf_used(&rtapl); znl_rta_push(zb, rta->rta_type, zbuf_pulln(&rtapl, len), len); } znl_rta_nested_complete(zb, rta_data); znl_rta_nested_complete(zb, rta_info); znl_nlmsg_complete(zb, n); zbuf_send(zb, netlink_req_fd); zbuf_recv(zb, netlink_req_fd); err: zbuf_free(zb); zbuf_free(zr); } frr-7.2.1/nhrpd/nhrp_cache.c0000644000000000000000000002272213610377563012572 00000000000000/* NHRP cache * Copyright (c) 2014-2015 Timo Teräs * * This file is free software: you may copy, redistribute and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation, either version 2 of the License, or * (at your option) any later version. */ #include "zebra.h" #include "memory.h" #include "thread.h" #include "hash.h" #include "nhrpd.h" #include "netlink.h" DEFINE_MTYPE_STATIC(NHRPD, NHRP_CACHE, "NHRP cache entry") unsigned long nhrp_cache_counts[NHRP_CACHE_NUM_TYPES]; const char *const nhrp_cache_type_str[] = { [NHRP_CACHE_INVALID] = "invalid", [NHRP_CACHE_INCOMPLETE] = "incomplete", [NHRP_CACHE_NEGATIVE] = "negative", [NHRP_CACHE_CACHED] = "cached", [NHRP_CACHE_DYNAMIC] = "dynamic", [NHRP_CACHE_NHS] = "nhs", [NHRP_CACHE_STATIC] = "static", [NHRP_CACHE_LOCAL] = "local", }; static unsigned int nhrp_cache_protocol_key(const void *peer_data) { const struct nhrp_cache *p = peer_data; return sockunion_hash(&p->remote_addr); } static bool nhrp_cache_protocol_cmp(const void *cache_data, const void *key_data) { const struct nhrp_cache *a = cache_data; const struct nhrp_cache *b = key_data; return sockunion_same(&a->remote_addr, &b->remote_addr); } static void *nhrp_cache_alloc(void *data) { struct nhrp_cache *p, *key = data; p = XMALLOC(MTYPE_NHRP_CACHE, sizeof(struct nhrp_cache)); *p = (struct nhrp_cache){ .cur.type = NHRP_CACHE_INVALID, .new.type = NHRP_CACHE_INVALID, .remote_addr = key->remote_addr, .ifp = key->ifp, .notifier_list = NOTIFIER_LIST_INITIALIZER(&p->notifier_list), }; nhrp_cache_counts[p->cur.type]++; return p; } static void nhrp_cache_free(struct nhrp_cache *c) { struct nhrp_interface *nifp = c->ifp->info; zassert(c->cur.type == NHRP_CACHE_INVALID && c->cur.peer == NULL); zassert(c->new.type == NHRP_CACHE_INVALID && c->new.peer == NULL); nhrp_cache_counts[c->cur.type]--; notifier_call(&c->notifier_list, NOTIFY_CACHE_DELETE); zassert(!notifier_active(&c->notifier_list)); hash_release(nifp->cache_hash, c); XFREE(MTYPE_NHRP_CACHE, c); } struct nhrp_cache *nhrp_cache_get(struct interface *ifp, union sockunion *remote_addr, int create) { struct nhrp_interface *nifp = ifp->info; struct nhrp_cache key; if (!nifp->cache_hash) { nifp->cache_hash = hash_create(nhrp_cache_protocol_key, nhrp_cache_protocol_cmp, "NHRP Cache"); if (!nifp->cache_hash) return NULL; } key.remote_addr = *remote_addr; key.ifp = ifp; return hash_get(nifp->cache_hash, &key, create ? nhrp_cache_alloc : NULL); } static int nhrp_cache_do_free(struct thread *t) { struct nhrp_cache *c = THREAD_ARG(t); c->t_timeout = NULL; nhrp_cache_free(c); return 0; } static int nhrp_cache_do_timeout(struct thread *t) { struct nhrp_cache *c = THREAD_ARG(t); c->t_timeout = NULL; if (c->cur.type != NHRP_CACHE_INVALID) nhrp_cache_update_binding(c, c->cur.type, -1, NULL, 0, NULL); return 0; } static void nhrp_cache_update_route(struct nhrp_cache *c) { struct prefix pfx; struct nhrp_peer *p = c->cur.peer; sockunion2hostprefix(&c->remote_addr, &pfx); if (p && nhrp_peer_check(p, 1)) { netlink_update_binding(p->ifp, &c->remote_addr, &p->vc->remote.nbma); nhrp_route_announce(1, c->cur.type, &pfx, c->ifp, NULL, c->cur.mtu); if (c->cur.type >= NHRP_CACHE_DYNAMIC) { nhrp_route_update_nhrp(&pfx, c->ifp); c->nhrp_route_installed = 1; } else if (c->nhrp_route_installed) { nhrp_route_update_nhrp(&pfx, NULL); c->nhrp_route_installed = 0; } if (!c->route_installed) { notifier_call(&c->notifier_list, NOTIFY_CACHE_UP); c->route_installed = 1; } } else { if (c->nhrp_route_installed) { nhrp_route_update_nhrp(&pfx, NULL); c->nhrp_route_installed = 0; } if (c->route_installed) { sockunion2hostprefix(&c->remote_addr, &pfx); notifier_call(&c->notifier_list, NOTIFY_CACHE_DOWN); nhrp_route_announce(0, c->cur.type, &pfx, NULL, NULL, 0); c->route_installed = 0; } } } static void nhrp_cache_peer_notifier(struct notifier_block *n, unsigned long cmd) { struct nhrp_cache *c = container_of(n, struct nhrp_cache, peer_notifier); switch (cmd) { case NOTIFY_PEER_UP: nhrp_cache_update_route(c); break; case NOTIFY_PEER_DOWN: case NOTIFY_PEER_IFCONFIG_CHANGED: notifier_call(&c->notifier_list, NOTIFY_CACHE_DOWN); nhrp_cache_update_binding(c, c->cur.type, -1, NULL, 0, NULL); break; case NOTIFY_PEER_NBMA_CHANGING: if (c->cur.type == NHRP_CACHE_DYNAMIC) c->cur.peer->vc->abort_migration = 1; break; } } static void nhrp_cache_reset_new(struct nhrp_cache *c) { THREAD_OFF(c->t_auth); if (list_hashed(&c->newpeer_notifier.notifier_entry)) nhrp_peer_notify_del(c->new.peer, &c->newpeer_notifier); nhrp_peer_unref(c->new.peer); memset(&c->new, 0, sizeof(c->new)); c->new.type = NHRP_CACHE_INVALID; } static void nhrp_cache_update_timers(struct nhrp_cache *c) { THREAD_OFF(c->t_timeout); switch (c->cur.type) { case NHRP_CACHE_INVALID: if (!c->t_auth) thread_add_timer_msec(master, nhrp_cache_do_free, c, 10, &c->t_timeout); break; default: if (c->cur.expires) thread_add_timer(master, nhrp_cache_do_timeout, c, c->cur.expires - monotime(NULL), &c->t_timeout); break; } } static void nhrp_cache_authorize_binding(struct nhrp_reqid *r, void *arg) { struct nhrp_cache *c = container_of(r, struct nhrp_cache, eventid); char buf[SU_ADDRSTRLEN]; debugf(NHRP_DEBUG_COMMON, "cache: %s %s: %s", c->ifp->name, sockunion2str(&c->remote_addr, buf, sizeof buf), (const char *)arg); nhrp_reqid_free(&nhrp_event_reqid, r); if (arg && strcmp(arg, "accept") == 0) { if (c->cur.peer) { netlink_update_binding(c->cur.peer->ifp, &c->remote_addr, NULL); nhrp_peer_notify_del(c->cur.peer, &c->peer_notifier); nhrp_peer_unref(c->cur.peer); } nhrp_cache_counts[c->cur.type]--; nhrp_cache_counts[c->new.type]++; c->cur = c->new; c->cur.peer = nhrp_peer_ref(c->cur.peer); nhrp_cache_reset_new(c); if (c->cur.peer) nhrp_peer_notify_add(c->cur.peer, &c->peer_notifier, nhrp_cache_peer_notifier); nhrp_cache_update_route(c); notifier_call(&c->notifier_list, NOTIFY_CACHE_BINDING_CHANGE); } else { nhrp_cache_reset_new(c); } nhrp_cache_update_timers(c); } static int nhrp_cache_do_auth_timeout(struct thread *t) { struct nhrp_cache *c = THREAD_ARG(t); c->t_auth = NULL; nhrp_cache_authorize_binding(&c->eventid, (void *)"timeout"); return 0; } static void nhrp_cache_newpeer_notifier(struct notifier_block *n, unsigned long cmd) { struct nhrp_cache *c = container_of(n, struct nhrp_cache, newpeer_notifier); switch (cmd) { case NOTIFY_PEER_UP: if (nhrp_peer_check(c->new.peer, 1)) { evmgr_notify("authorize-binding", c, nhrp_cache_authorize_binding); thread_add_timer(master, nhrp_cache_do_auth_timeout, c, 10, &c->t_auth); } break; case NOTIFY_PEER_DOWN: case NOTIFY_PEER_IFCONFIG_CHANGED: nhrp_cache_reset_new(c); break; } } int nhrp_cache_update_binding(struct nhrp_cache *c, enum nhrp_cache_type type, int holding_time, struct nhrp_peer *p, uint32_t mtu, union sockunion *nbma_oa) { if (c->cur.type > type || c->new.type > type) { nhrp_peer_unref(p); return 0; } /* Sanitize MTU */ switch (sockunion_family(&c->remote_addr)) { case AF_INET: if (mtu < 576 || mtu >= 1500) mtu = 0; /* Opennhrp announces nbma mtu, but we use protocol mtu. * This heuristic tries to fix up it. */ if (mtu > 1420) mtu = (mtu & -16) - 80; break; default: mtu = 0; break; } nhrp_cache_reset_new(c); if (c->cur.type == type && c->cur.peer == p && c->cur.mtu == mtu) { if (holding_time > 0) c->cur.expires = monotime(NULL) + holding_time; if (nbma_oa) c->cur.remote_nbma_natoa = *nbma_oa; else memset(&c->cur.remote_nbma_natoa, 0, sizeof c->cur.remote_nbma_natoa); nhrp_peer_unref(p); } else { c->new.type = type; c->new.peer = p; c->new.mtu = mtu; if (nbma_oa) c->new.remote_nbma_natoa = *nbma_oa; if (holding_time > 0) c->new.expires = monotime(NULL) + holding_time; else if (holding_time < 0) nhrp_cache_reset_new(c); if (c->new.type == NHRP_CACHE_INVALID || c->new.type >= NHRP_CACHE_STATIC || c->map) { nhrp_cache_authorize_binding(&c->eventid, (void *)"accept"); } else { nhrp_peer_notify_add(c->new.peer, &c->newpeer_notifier, nhrp_cache_newpeer_notifier); nhrp_cache_newpeer_notifier(&c->newpeer_notifier, NOTIFY_PEER_UP); thread_add_timer(master, nhrp_cache_do_auth_timeout, c, 60, &c->t_auth); } } nhrp_cache_update_timers(c); return 1; } void nhrp_cache_set_used(struct nhrp_cache *c, int used) { c->used = used; if (c->used) notifier_call(&c->notifier_list, NOTIFY_CACHE_USED); } struct nhrp_cache_iterator_ctx { void (*cb)(struct nhrp_cache *, void *); void *ctx; }; static void nhrp_cache_iterator(struct hash_bucket *b, void *ctx) { struct nhrp_cache_iterator_ctx *ic = ctx; ic->cb(b->data, ic->ctx); } void nhrp_cache_foreach(struct interface *ifp, void (*cb)(struct nhrp_cache *, void *), void *ctx) { struct nhrp_interface *nifp = ifp->info; struct nhrp_cache_iterator_ctx ic = { .cb = cb, .ctx = ctx, }; if (nifp->cache_hash) hash_iterate(nifp->cache_hash, nhrp_cache_iterator, &ic); } void nhrp_cache_notify_add(struct nhrp_cache *c, struct notifier_block *n, notifier_fn_t fn) { notifier_add(n, &c->notifier_list, fn); } void nhrp_cache_notify_del(struct nhrp_cache *c, struct notifier_block *n) { notifier_del(n); } frr-7.2.1/nhrpd/nhrp_errors.c0000644000000000000000000000246313610377563013043 00000000000000/* * NHRP-specific error messages. * Copyright (C) 2018 Cumulus Networks, Inc. * Donald Sharp * * This program is free software; you can redistribute it and/or modify it * under the terms of the 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; see the file COPYING; if not, write to the Free Software * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA */ #include #include "lib/ferr.h" #include "nhrp_errors.h" /* clang-format off */ static struct log_ref ferr_nhrp_err[] = { { .code = EC_NHRP_SWAN, .title = "NHRP Strong Swan Error", .description = "NHRP has detected a error with the Strongswan code", .suggestion = "Ensure that StrongSwan is configured correctly. Restart StrongSwan and FRR" }, { .code = END_FERR, } }; /* clang-format on */ void nhrp_error_init(void) { log_ref_add(ferr_nhrp_err); } frr-7.2.1/nhrpd/nhrp_errors.h0000644000000000000000000000200613610377563013041 00000000000000/* * NHRP-specific error messages. * Copyright (C) 2018 Cumulus Networks, Inc. * Donald Sharp * * This program is free software; you can redistribute it and/or modify it * under the terms of the 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; see the file COPYING; if not, write to the Free Software * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA */ #ifndef __NHRP_ERRORS_H__ #define __NHRP_ERRORS_H__ #include "lib/ferr.h" enum nhrp_log_refs { EC_NHRP_SWAN = NHRP_FERR_START, }; extern void nhrp_error_init(void); #endif frr-7.2.1/nhrpd/nhrp_event.c0000644000000000000000000001545013610377563012650 00000000000000/* NHRP event manager * Copyright (c) 2014-2015 Timo Teräs * * This file is free software: you may copy, redistribute and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation, either version 2 of the License, or * (at your option) any later version. */ #ifdef HAVE_CONFIG_H #include "config.h" #endif #include #include #include #include "thread.h" #include "zbuf.h" #include "log.h" #include "nhrpd.h" const char *nhrp_event_socket_path; struct nhrp_reqid_pool nhrp_event_reqid; struct event_manager { struct thread *t_reconnect, *t_read, *t_write; struct zbuf ibuf; struct zbuf_queue obuf; int fd; uint8_t ibuf_data[4 * 1024]; }; static int evmgr_reconnect(struct thread *t); static void evmgr_connection_error(struct event_manager *evmgr) { THREAD_OFF(evmgr->t_read); THREAD_OFF(evmgr->t_write); zbuf_reset(&evmgr->ibuf); zbufq_reset(&evmgr->obuf); if (evmgr->fd >= 0) close(evmgr->fd); evmgr->fd = -1; if (nhrp_event_socket_path) thread_add_timer_msec(master, evmgr_reconnect, evmgr, 10, &evmgr->t_reconnect); } static void evmgr_recv_message(struct event_manager *evmgr, struct zbuf *zb) { struct zbuf zl; uint32_t eventid = 0; size_t len; char buf[256], result[64] = ""; while (zbuf_may_pull_until(zb, "\n", &zl)) { len = zbuf_used(&zl) - 1; if (len >= sizeof(buf) - 1) continue; memcpy(buf, zbuf_pulln(&zl, len), len); buf[len] = 0; debugf(NHRP_DEBUG_EVENT, "evmgr: msg: %s", buf); if (sscanf(buf, "eventid=%" SCNu32, &eventid) != 1) continue; if (sscanf(buf, "result=%63s", result) != 1) continue; } debugf(NHRP_DEBUG_EVENT, "evmgr: received: eventid=%d result=%s", eventid, result); if (eventid && result[0]) { struct nhrp_reqid *r = nhrp_reqid_lookup(&nhrp_event_reqid, eventid); if (r) r->cb(r, result); } } static int evmgr_read(struct thread *t) { struct event_manager *evmgr = THREAD_ARG(t); struct zbuf *ibuf = &evmgr->ibuf; struct zbuf msg; evmgr->t_read = NULL; if (zbuf_read(ibuf, evmgr->fd, (size_t)-1) < 0) { evmgr_connection_error(evmgr); return 0; } /* Process all messages in buffer */ while (zbuf_may_pull_until(ibuf, "\n\n", &msg)) evmgr_recv_message(evmgr, &msg); thread_add_read(master, evmgr_read, evmgr, evmgr->fd, &evmgr->t_read); return 0; } static int evmgr_write(struct thread *t) { struct event_manager *evmgr = THREAD_ARG(t); int r; evmgr->t_write = NULL; r = zbufq_write(&evmgr->obuf, evmgr->fd); if (r > 0) { thread_add_write(master, evmgr_write, evmgr, evmgr->fd, &evmgr->t_write); } else if (r < 0) { evmgr_connection_error(evmgr); } return 0; } static void evmgr_hexdump(struct zbuf *zb, const uint8_t *val, size_t vallen) { static const char xd[] = "0123456789abcdef"; size_t i; char *ptr; ptr = zbuf_pushn(zb, 2 * vallen); if (!ptr) return; for (i = 0; i < vallen; i++) { uint8_t b = val[i]; *(ptr++) = xd[b >> 4]; *(ptr++) = xd[b & 0xf]; } } static void evmgr_put(struct zbuf *zb, const char *fmt, ...) { const char *pos, *nxt, *str; const uint8_t *bin; const union sockunion *su; int len; va_list va; va_start(va, fmt); for (pos = fmt; (nxt = strchr(pos, '%')) != NULL; pos = nxt + 2) { zbuf_put(zb, pos, nxt - pos); switch (nxt[1]) { case '%': zbuf_put8(zb, '%'); break; case 'u': zb->tail += snprintf((char *)zb->tail, zbuf_tailroom(zb), "%u", va_arg(va, uint32_t)); break; case 's': str = va_arg(va, const char *); zbuf_put(zb, str, strlen(str)); break; case 'U': su = va_arg(va, const union sockunion *); if (sockunion2str(su, (char *)zb->tail, zbuf_tailroom(zb))) zb->tail += strlen((char *)zb->tail); else zbuf_set_werror(zb); break; case 'H': bin = va_arg(va, const uint8_t *); len = va_arg(va, int); evmgr_hexdump(zb, bin, len); break; } } va_end(va); zbuf_put(zb, pos, strlen(pos)); } static void evmgr_submit(struct event_manager *evmgr, struct zbuf *obuf) { if (obuf->error) { zbuf_free(obuf); return; } zbuf_put(obuf, "\n", 1); zbufq_queue(&evmgr->obuf, obuf); if (evmgr->fd >= 0) thread_add_write(master, evmgr_write, evmgr, evmgr->fd, &evmgr->t_write); } static int evmgr_reconnect(struct thread *t) { struct event_manager *evmgr = THREAD_ARG(t); int fd; evmgr->t_reconnect = NULL; if (evmgr->fd >= 0 || !nhrp_event_socket_path) return 0; fd = sock_open_unix(nhrp_event_socket_path); if (fd < 0) { zlog_warn("%s: failure connecting nhrp-event socket: %s", __PRETTY_FUNCTION__, strerror(errno)); zbufq_reset(&evmgr->obuf); thread_add_timer(master, evmgr_reconnect, evmgr, 10, &evmgr->t_reconnect); return 0; } zlog_info("Connected to Event Manager"); evmgr->fd = fd; thread_add_read(master, evmgr_read, evmgr, evmgr->fd, &evmgr->t_read); return 0; } static struct event_manager evmgr_connection; void evmgr_init(void) { struct event_manager *evmgr = &evmgr_connection; evmgr->fd = -1; zbuf_init(&evmgr->ibuf, evmgr->ibuf_data, sizeof(evmgr->ibuf_data), 0); zbufq_init(&evmgr->obuf); thread_add_timer_msec(master, evmgr_reconnect, evmgr, 10, &evmgr->t_reconnect); } void evmgr_set_socket(const char *socket) { if (nhrp_event_socket_path) { free((char *)nhrp_event_socket_path); nhrp_event_socket_path = NULL; } if (socket) nhrp_event_socket_path = strdup(socket); evmgr_connection_error(&evmgr_connection); } void evmgr_terminate(void) { } void evmgr_notify(const char *name, struct nhrp_cache *c, void (*cb)(struct nhrp_reqid *, void *)) { struct event_manager *evmgr = &evmgr_connection; struct nhrp_vc *vc; struct nhrp_interface *nifp = c->ifp->info; struct zbuf *zb; afi_t afi = family2afi(sockunion_family(&c->remote_addr)); if (!nhrp_event_socket_path) { cb(&c->eventid, (void *)"accept"); return; } debugf(NHRP_DEBUG_EVENT, "evmgr: sending event %s", name); vc = c->new.peer ? c->new.peer->vc : NULL; zb = zbuf_alloc( 1024 + (vc ? (vc->local.certlen + vc->remote.certlen) * 2 : 0)); if (cb) { nhrp_reqid_free(&nhrp_event_reqid, &c->eventid); evmgr_put(zb, "eventid=%u\n", nhrp_reqid_alloc(&nhrp_event_reqid, &c->eventid, cb)); } evmgr_put(zb, "event=%s\n" "type=%s\n" "old_type=%s\n" "num_nhs=%u\n" "interface=%s\n" "local_addr=%U\n", name, nhrp_cache_type_str[c->new.type], nhrp_cache_type_str[c->cur.type], (unsigned int)nhrp_cache_counts[NHRP_CACHE_NHS], c->ifp->name, &nifp->afi[afi].addr); if (vc) { evmgr_put(zb, "vc_initiated=%s\n" "local_nbma=%U\n" "local_cert=%H\n" "remote_addr=%U\n" "remote_nbma=%U\n" "remote_cert=%H\n", c->new.peer->requested ? "yes" : "no", &vc->local.nbma, vc->local.cert, vc->local.certlen, &c->remote_addr, &vc->remote.nbma, vc->remote.cert, vc->remote.certlen); } evmgr_submit(evmgr, zb); } frr-7.2.1/nhrpd/nhrp_interface.c0000644000000000000000000002524713610377563013474 00000000000000/* NHRP interface * Copyright (c) 2014-2015 Timo Teräs * * This file is free software: you may copy, redistribute and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation, either version 2 of the License, or * (at your option) any later version. */ #ifdef HAVE_CONFIG_H #include "config.h" #endif #include #include "zebra.h" #include "linklist.h" #include "memory.h" #include "thread.h" #include "nhrpd.h" #include "os.h" #include "netlink.h" DEFINE_MTYPE_STATIC(NHRPD, NHRP_IF, "NHRP interface") static int nhrp_if_new_hook(struct interface *ifp) { struct nhrp_interface *nifp; afi_t afi; nifp = XCALLOC(MTYPE_NHRP_IF, sizeof(struct nhrp_interface)); ifp->info = nifp; nifp->ifp = ifp; notifier_init(&nifp->notifier_list); for (afi = 0; afi < AFI_MAX; afi++) { struct nhrp_afi_data *ad = &nifp->afi[afi]; ad->holdtime = NHRPD_DEFAULT_HOLDTIME; list_init(&ad->nhslist_head); } return 0; } static int nhrp_if_delete_hook(struct interface *ifp) { XFREE(MTYPE_NHRP_IF, ifp->info); return 0; } void nhrp_interface_init(void) { hook_register_prio(if_add, 0, nhrp_if_new_hook); hook_register_prio(if_del, 0, nhrp_if_delete_hook); } void nhrp_interface_update_mtu(struct interface *ifp, afi_t afi) { struct nhrp_interface *nifp = ifp->info; struct nhrp_afi_data *if_ad = &nifp->afi[afi]; unsigned short new_mtu; if (if_ad->configured_mtu < 0) new_mtu = nifp->nbmaifp ? nifp->nbmaifp->mtu : 0; else new_mtu = if_ad->configured_mtu; if (new_mtu >= 1500) new_mtu = 0; if (new_mtu != if_ad->mtu) { debugf(NHRP_DEBUG_IF, "%s: MTU changed to %d", ifp->name, new_mtu); if_ad->mtu = new_mtu; notifier_call(&nifp->notifier_list, NOTIFY_INTERFACE_MTU_CHANGED); } } static void nhrp_interface_update_source(struct interface *ifp) { struct nhrp_interface *nifp = ifp->info; if (!nifp->source || !nifp->nbmaifp || (ifindex_t)nifp->linkidx == nifp->nbmaifp->ifindex) return; nifp->linkidx = nifp->nbmaifp->ifindex; debugf(NHRP_DEBUG_IF, "%s: bound device index changed to %d", ifp->name, nifp->linkidx); netlink_gre_set_link(ifp->ifindex, nifp->linkidx); } static void nhrp_interface_interface_notifier(struct notifier_block *n, unsigned long cmd) { struct nhrp_interface *nifp = container_of(n, struct nhrp_interface, nbmanifp_notifier); struct interface *nbmaifp = nifp->nbmaifp; struct nhrp_interface *nbmanifp = nbmaifp->info; char buf[SU_ADDRSTRLEN]; switch (cmd) { case NOTIFY_INTERFACE_CHANGED: nhrp_interface_update_mtu(nifp->ifp, AFI_IP); nhrp_interface_update_source(nifp->ifp); break; case NOTIFY_INTERFACE_ADDRESS_CHANGED: nifp->nbma = nbmanifp->afi[AFI_IP].addr; nhrp_interface_update(nifp->ifp); notifier_call(&nifp->notifier_list, NOTIFY_INTERFACE_NBMA_CHANGED); debugf(NHRP_DEBUG_IF, "%s: NBMA change: address %s", nifp->ifp->name, sockunion2str(&nifp->nbma, buf, sizeof buf)); break; } } static void nhrp_interface_update_nbma(struct interface *ifp) { struct nhrp_interface *nifp = ifp->info, *nbmanifp = NULL; struct interface *nbmaifp = NULL; union sockunion nbma; sockunion_family(&nbma) = AF_UNSPEC; if (nifp->source) nbmaifp = if_lookup_by_name(nifp->source, VRF_DEFAULT); switch (ifp->ll_type) { case ZEBRA_LLT_IPGRE: { struct in_addr saddr = {0}; netlink_gre_get_info(ifp->ifindex, &nifp->grekey, &nifp->linkidx, &saddr); debugf(NHRP_DEBUG_IF, "%s: GRE: %x %x %x", ifp->name, nifp->grekey, nifp->linkidx, saddr.s_addr); if (saddr.s_addr) sockunion_set(&nbma, AF_INET, (uint8_t *)&saddr.s_addr, sizeof(saddr.s_addr)); else if (!nbmaifp && nifp->linkidx != IFINDEX_INTERNAL) nbmaifp = if_lookup_by_index(nifp->linkidx, VRF_DEFAULT); } break; default: break; } if (nbmaifp) nbmanifp = nbmaifp->info; if (nbmaifp != nifp->nbmaifp) { if (nifp->nbmaifp) notifier_del(&nifp->nbmanifp_notifier); nifp->nbmaifp = nbmaifp; if (nbmaifp) { notifier_add(&nifp->nbmanifp_notifier, &nbmanifp->notifier_list, nhrp_interface_interface_notifier); debugf(NHRP_DEBUG_IF, "%s: bound to %s", ifp->name, nbmaifp->name); } } if (nbmaifp) { if (sockunion_family(&nbma) == AF_UNSPEC) nbma = nbmanifp->afi[AFI_IP].addr; nhrp_interface_update_mtu(ifp, AFI_IP); nhrp_interface_update_source(ifp); } if (!sockunion_same(&nbma, &nifp->nbma)) { nifp->nbma = nbma; nhrp_interface_update(nifp->ifp); debugf(NHRP_DEBUG_IF, "%s: NBMA address changed", ifp->name); notifier_call(&nifp->notifier_list, NOTIFY_INTERFACE_NBMA_CHANGED); } nhrp_interface_update(ifp); } static void nhrp_interface_update_address(struct interface *ifp, afi_t afi, int force) { const int family = afi2family(afi); struct nhrp_interface *nifp = ifp->info; struct nhrp_afi_data *if_ad = &nifp->afi[afi]; struct nhrp_cache *nc; struct connected *c, *best; struct listnode *cnode; union sockunion addr; char buf[PREFIX_STRLEN]; /* Select new best match preferring primary address */ best = NULL; for (ALL_LIST_ELEMENTS_RO(ifp->connected, cnode, c)) { if (PREFIX_FAMILY(c->address) != family) continue; if (best == NULL) { best = c; continue; } if ((best->flags & ZEBRA_IFA_SECONDARY) && !(c->flags & ZEBRA_IFA_SECONDARY)) { best = c; continue; } if (!(best->flags & ZEBRA_IFA_SECONDARY) && (c->flags & ZEBRA_IFA_SECONDARY)) continue; if (best->address->prefixlen > c->address->prefixlen) { best = c; continue; } if (best->address->prefixlen < c->address->prefixlen) continue; } /* On NHRP interfaces a host prefix is required */ if (best && if_ad->configured && best->address->prefixlen != 8 * prefix_blen(best->address)) { zlog_notice("%s: %s is not a host prefix", ifp->name, prefix2str(best->address, buf, sizeof buf)); best = NULL; } /* Update address if it changed */ if (best) prefix2sockunion(best->address, &addr); else memset(&addr, 0, sizeof(addr)); if (!force && sockunion_same(&if_ad->addr, &addr)) return; if (sockunion_family(&if_ad->addr) != AF_UNSPEC) { nc = nhrp_cache_get(ifp, &if_ad->addr, 0); if (nc) nhrp_cache_update_binding(nc, NHRP_CACHE_LOCAL, -1, NULL, 0, NULL); } debugf(NHRP_DEBUG_KERNEL, "%s: IPv%d address changed to %s", ifp->name, afi == AFI_IP ? 4 : 6, best ? prefix2str(best->address, buf, sizeof buf) : "(none)"); if_ad->addr = addr; if (if_ad->configured && sockunion_family(&if_ad->addr) != AF_UNSPEC) { nc = nhrp_cache_get(ifp, &addr, 1); if (nc) nhrp_cache_update_binding(nc, NHRP_CACHE_LOCAL, 0, NULL, 0, NULL); } notifier_call(&nifp->notifier_list, NOTIFY_INTERFACE_ADDRESS_CHANGED); } void nhrp_interface_update(struct interface *ifp) { struct nhrp_interface *nifp = ifp->info; struct nhrp_afi_data *if_ad; afi_t afi; int enabled = 0; notifier_call(&nifp->notifier_list, NOTIFY_INTERFACE_CHANGED); for (afi = 0; afi < AFI_MAX; afi++) { if_ad = &nifp->afi[afi]; if (sockunion_family(&nifp->nbma) == AF_UNSPEC || ifp->ifindex == IFINDEX_INTERNAL || !if_is_up(ifp) || !if_ad->network_id) { if (if_ad->configured) { if_ad->configured = 0; nhrp_interface_update_address(ifp, afi, 1); } continue; } if (!if_ad->configured) { os_configure_dmvpn(ifp->ifindex, ifp->name, afi2family(afi)); if_ad->configured = 1; nhrp_interface_update_address(ifp, afi, 1); } enabled = 1; } if (enabled != nifp->enabled) { nifp->enabled = enabled; notifier_call(&nifp->notifier_list, enabled ? NOTIFY_INTERFACE_UP : NOTIFY_INTERFACE_DOWN); } } int nhrp_interface_add(ZAPI_CALLBACK_ARGS) { struct interface *ifp; /* read and add the interface in the iflist. */ ifp = zebra_interface_add_read(zclient->ibuf, vrf_id); if (ifp == NULL) return 0; debugf(NHRP_DEBUG_IF, "if-add: %s, ifindex: %u, hw_type: %d %s", ifp->name, ifp->ifindex, ifp->ll_type, if_link_type_str(ifp->ll_type)); nhrp_interface_update_nbma(ifp); return 0; } int nhrp_interface_delete(ZAPI_CALLBACK_ARGS) { struct interface *ifp; struct stream *s; s = zclient->ibuf; ifp = zebra_interface_state_read(s, vrf_id); if (ifp == NULL) return 0; debugf(NHRP_DEBUG_IF, "if-delete: %s", ifp->name); nhrp_interface_update(ifp); if_set_index(ifp, IFINDEX_INTERNAL); return 0; } int nhrp_interface_up(ZAPI_CALLBACK_ARGS) { struct interface *ifp; ifp = zebra_interface_state_read(zclient->ibuf, vrf_id); if (ifp == NULL) return 0; debugf(NHRP_DEBUG_IF, "if-up: %s", ifp->name); nhrp_interface_update_nbma(ifp); return 0; } int nhrp_interface_down(ZAPI_CALLBACK_ARGS) { struct interface *ifp; ifp = zebra_interface_state_read(zclient->ibuf, vrf_id); if (ifp == NULL) return 0; debugf(NHRP_DEBUG_IF, "if-down: %s", ifp->name); nhrp_interface_update(ifp); return 0; } int nhrp_interface_address_add(ZAPI_CALLBACK_ARGS) { struct connected *ifc; char buf[PREFIX_STRLEN]; ifc = zebra_interface_address_read(cmd, zclient->ibuf, vrf_id); if (ifc == NULL) return 0; debugf(NHRP_DEBUG_IF, "if-addr-add: %s: %s", ifc->ifp->name, prefix2str(ifc->address, buf, sizeof buf)); nhrp_interface_update_address( ifc->ifp, family2afi(PREFIX_FAMILY(ifc->address)), 0); return 0; } int nhrp_interface_address_delete(ZAPI_CALLBACK_ARGS) { struct connected *ifc; char buf[PREFIX_STRLEN]; ifc = zebra_interface_address_read(cmd, zclient->ibuf, vrf_id); if (ifc == NULL) return 0; debugf(NHRP_DEBUG_IF, "if-addr-del: %s: %s", ifc->ifp->name, prefix2str(ifc->address, buf, sizeof buf)); nhrp_interface_update_address( ifc->ifp, family2afi(PREFIX_FAMILY(ifc->address)), 0); connected_free(ifc); return 0; } void nhrp_interface_notify_add(struct interface *ifp, struct notifier_block *n, notifier_fn_t fn) { struct nhrp_interface *nifp = ifp->info; notifier_add(n, &nifp->notifier_list, fn); } void nhrp_interface_notify_del(struct interface *ifp, struct notifier_block *n) { notifier_del(n); } void nhrp_interface_set_protection(struct interface *ifp, const char *profile, const char *fallback_profile) { struct nhrp_interface *nifp = ifp->info; if (nifp->ipsec_profile) free(nifp->ipsec_profile); nifp->ipsec_profile = profile ? strdup(profile) : NULL; if (nifp->ipsec_fallback_profile) free(nifp->ipsec_fallback_profile); nifp->ipsec_fallback_profile = fallback_profile ? strdup(fallback_profile) : NULL; notifier_call(&nifp->notifier_list, NOTIFY_INTERFACE_ADDRESS_CHANGED); } void nhrp_interface_set_source(struct interface *ifp, const char *ifname) { struct nhrp_interface *nifp = ifp->info; if (nifp->source) free(nifp->source); nifp->source = ifname ? strdup(ifname) : NULL; nhrp_interface_update_nbma(ifp); } frr-7.2.1/nhrpd/nhrp_main.c0000644000000000000000000000611413610377563012450 00000000000000/* NHRP daemon main functions * Copyright (c) 2014-2015 Timo Teräs * * This file is free software: you may copy, redistribute and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation, either version 2 of the License, or * (at your option) any later version. */ #ifdef HAVE_CONFIG_H #include "config.h" #endif #include #include "zebra.h" #include "privs.h" #include "getopt.h" #include "thread.h" #include "sigevent.h" #include "version.h" #include "log.h" #include "memory.h" #include "memory_vty.h" #include "command.h" #include "libfrr.h" #include "nhrpd.h" #include "netlink.h" #include "nhrp_errors.h" DEFINE_MGROUP(NHRPD, "NHRP") unsigned int debug_flags = 0; struct thread_master *master; struct timeval current_time; /* nhrpd options. */ struct option longopts[] = {{0}}; /* nhrpd privileges */ static zebra_capabilities_t _caps_p[] = { ZCAP_NET_RAW, ZCAP_NET_ADMIN, ZCAP_DAC_OVERRIDE, /* for now needed to write to /proc/sys/net/ipv4//send_redirect */ }; struct zebra_privs_t nhrpd_privs = { #if defined(FRR_USER) && defined(FRR_GROUP) .user = FRR_USER, .group = FRR_GROUP, #endif #ifdef VTY_GROUP .vty_group = VTY_GROUP, #endif .caps_p = _caps_p, .cap_num_p = array_size(_caps_p), }; static void parse_arguments(int argc, char **argv) { int opt; while (1) { opt = frr_getopt(argc, argv, 0); if (opt < 0) break; switch (opt) { case 0: break; default: frr_help_exit(1); break; } } } static void nhrp_sigusr1(void) { zlog_rotate(); } static void nhrp_request_stop(void) { debugf(NHRP_DEBUG_COMMON, "Exiting..."); frr_early_fini(); nhrp_shortcut_terminate(); nhrp_nhs_terminate(); nhrp_zebra_terminate(); vici_terminate(); evmgr_terminate(); nhrp_vc_terminate(); vrf_terminate(); debugf(NHRP_DEBUG_COMMON, "Done."); frr_fini(); exit(0); } static struct quagga_signal_t sighandlers[] = { { .signal = SIGUSR1, .handler = &nhrp_sigusr1, }, { .signal = SIGINT, .handler = &nhrp_request_stop, }, { .signal = SIGTERM, .handler = &nhrp_request_stop, }, }; static const struct frr_yang_module_info *nhrpd_yang_modules[] = { &frr_interface_info, }; FRR_DAEMON_INFO(nhrpd, NHRP, .vty_port = NHRP_VTY_PORT, .proghelp = "Implementation of the NHRP routing protocol.", .signals = sighandlers, .n_signals = array_size(sighandlers), .privs = &nhrpd_privs, .yang_modules = nhrpd_yang_modules, .n_yang_modules = array_size(nhrpd_yang_modules), ) int main(int argc, char **argv) { frr_preinit(&nhrpd_di, argc, argv); frr_opt_add("", longopts, ""); parse_arguments(argc, argv); /* Library inits. */ master = frr_init(); nhrp_error_init(); vrf_init(NULL, NULL, NULL, NULL, NULL); nhrp_interface_init(); resolver_init(master); /* Run with elevated capabilities, as for all netlink activity * we need privileges anyway. */ nhrpd_privs.change(ZPRIVS_RAISE); netlink_init(); evmgr_init(); nhrp_vc_init(); nhrp_packet_init(); vici_init(); nhrp_zebra_init(); nhrp_shortcut_init(); nhrp_config_init(); frr_config_fork(); frr_run(master); return 0; } frr-7.2.1/nhrpd/nhrp_nhs.c0000644000000000000000000002647513610377563012330 00000000000000/* NHRP NHC nexthop server functions (registration) * Copyright (c) 2014-2015 Timo Teräs * * This file is free software: you may copy, redistribute and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation, either version 2 of the License, or * (at your option) any later version. */ #include "zebra.h" #include "zbuf.h" #include "memory.h" #include "thread.h" #include "nhrpd.h" #include "nhrp_protocol.h" DEFINE_MTYPE_STATIC(NHRPD, NHRP_NHS, "NHRP next hop server") DEFINE_MTYPE_STATIC(NHRPD, NHRP_REGISTRATION, "NHRP registration entries") static int nhrp_nhs_resolve(struct thread *t); static int nhrp_reg_send_req(struct thread *t); static void nhrp_reg_reply(struct nhrp_reqid *reqid, void *arg) { struct nhrp_packet_parser *p = arg; struct nhrp_registration *r = container_of(reqid, struct nhrp_registration, reqid); struct nhrp_nhs *nhs = r->nhs; struct interface *ifp = nhs->ifp; struct nhrp_interface *nifp = ifp->info; struct nhrp_extension_header *ext; struct nhrp_cie_header *cie; struct nhrp_cache *c; struct zbuf extpl; union sockunion cie_nbma, cie_proto, *proto; char buf[64]; int ok = 0, holdtime; nhrp_reqid_free(&nhrp_packet_reqid, &r->reqid); if (p->hdr->type != NHRP_PACKET_REGISTRATION_REPLY) { debugf(NHRP_DEBUG_COMMON, "NHS: Registration failed"); return; } debugf(NHRP_DEBUG_COMMON, "NHS: Reg.reply received"); ok = 1; while ((cie = nhrp_cie_pull(&p->payload, p->hdr, &cie_nbma, &cie_proto)) != NULL) { proto = sockunion_family(&cie_proto) != AF_UNSPEC ? &cie_proto : &p->src_proto; debugf(NHRP_DEBUG_COMMON, "NHS: CIE registration: %s: %d", sockunion2str(proto, buf, sizeof(buf)), cie->code); if (!((cie->code == NHRP_CODE_SUCCESS) || (cie->code == NHRP_CODE_ADMINISTRATIVELY_PROHIBITED && nhs->hub))) ok = 0; } if (!ok) return; /* Parse extensions */ sockunion_family(&nifp->nat_nbma) = AF_UNSPEC; while ((ext = nhrp_ext_pull(&p->extensions, &extpl)) != NULL) { switch (htons(ext->type) & ~NHRP_EXTENSION_FLAG_COMPULSORY) { case NHRP_EXTENSION_NAT_ADDRESS: /* NHS adds second CIE if NAT is detected */ if (nhrp_cie_pull(&extpl, p->hdr, &cie_nbma, &cie_proto) && nhrp_cie_pull(&extpl, p->hdr, &cie_nbma, &cie_proto)) { nifp->nat_nbma = cie_nbma; debugf(NHRP_DEBUG_IF, "%s: NAT detected, real NBMA address: %s", ifp->name, sockunion2str(&nifp->nbma, buf, sizeof(buf))); } break; } } /* Success - schedule next registration, and route NHS */ r->timeout = 2; holdtime = nifp->afi[nhs->afi].holdtime; THREAD_OFF(r->t_register); /* RFC 2332 5.2.3 - Registration is recommend to be renewed * every one third of holdtime */ thread_add_timer(master, nhrp_reg_send_req, r, holdtime / 3, &r->t_register); r->proto_addr = p->dst_proto; c = nhrp_cache_get(ifp, &p->dst_proto, 1); if (c) nhrp_cache_update_binding(c, NHRP_CACHE_NHS, holdtime, nhrp_peer_ref(r->peer), 0, NULL); } static int nhrp_reg_timeout(struct thread *t) { struct nhrp_registration *r = THREAD_ARG(t); struct nhrp_cache *c; r->t_register = NULL; if (r->timeout >= 16 && sockunion_family(&r->proto_addr) != AF_UNSPEC) { nhrp_reqid_free(&nhrp_packet_reqid, &r->reqid); c = nhrp_cache_get(r->nhs->ifp, &r->proto_addr, 0); if (c) nhrp_cache_update_binding(c, NHRP_CACHE_NHS, -1, NULL, 0, NULL); sockunion_family(&r->proto_addr) = AF_UNSPEC; } r->timeout <<= 1; if (r->timeout > 64) r->timeout = 2; thread_add_timer_msec(master, nhrp_reg_send_req, r, 10, &r->t_register); return 0; } static void nhrp_reg_peer_notify(struct notifier_block *n, unsigned long cmd) { struct nhrp_registration *r = container_of(n, struct nhrp_registration, peer_notifier); char buf[SU_ADDRSTRLEN]; switch (cmd) { case NOTIFY_PEER_UP: case NOTIFY_PEER_DOWN: case NOTIFY_PEER_IFCONFIG_CHANGED: case NOTIFY_PEER_MTU_CHANGED: debugf(NHRP_DEBUG_COMMON, "NHS: Flush timer for %s", sockunion2str(&r->peer->vc->remote.nbma, buf, sizeof buf)); THREAD_TIMER_OFF(r->t_register); thread_add_timer_msec(master, nhrp_reg_send_req, r, 10, &r->t_register); break; } } static int nhrp_reg_send_req(struct thread *t) { struct nhrp_registration *r = THREAD_ARG(t); struct nhrp_nhs *nhs = r->nhs; char buf1[SU_ADDRSTRLEN], buf2[SU_ADDRSTRLEN]; struct interface *ifp = nhs->ifp; struct nhrp_interface *nifp = ifp->info; struct nhrp_afi_data *if_ad = &nifp->afi[nhs->afi]; union sockunion *dst_proto; struct zbuf *zb; struct nhrp_packet_header *hdr; struct nhrp_extension_header *ext; struct nhrp_cie_header *cie; r->t_register = NULL; if (!nhrp_peer_check(r->peer, 2)) { debugf(NHRP_DEBUG_COMMON, "NHS: Waiting link for %s", sockunion2str(&r->peer->vc->remote.nbma, buf1, sizeof buf1)); thread_add_timer(master, nhrp_reg_send_req, r, 120, &r->t_register); return 0; } thread_add_timer(master, nhrp_reg_timeout, r, r->timeout, &r->t_register); /* RFC2332 5.2.3 NHC uses it's own address as dst if NHS is unknown */ dst_proto = &nhs->proto_addr; if (sockunion_family(dst_proto) == AF_UNSPEC) dst_proto = &if_ad->addr; sockunion2str(&if_ad->addr, buf1, sizeof(buf1)); sockunion2str(dst_proto, buf2, sizeof(buf2)); debugf(NHRP_DEBUG_COMMON, "NHS: Register %s -> %s (timeout %d)", buf1, buf2, r->timeout); /* No protocol address configured for tunnel interface */ if (sockunion_family(&if_ad->addr) == AF_UNSPEC) return 0; zb = zbuf_alloc(1400); hdr = nhrp_packet_push(zb, NHRP_PACKET_REGISTRATION_REQUEST, &nifp->nbma, &if_ad->addr, dst_proto); hdr->hop_count = 1; if (!(if_ad->flags & NHRP_IFF_REG_NO_UNIQUE)) hdr->flags |= htons(NHRP_FLAG_REGISTRATION_UNIQUE); hdr->u.request_id = htonl(nhrp_reqid_alloc(&nhrp_packet_reqid, &r->reqid, nhrp_reg_reply)); /* FIXME: push CIE for each local protocol address */ cie = nhrp_cie_push(zb, NHRP_CODE_SUCCESS, NULL, NULL); cie->prefix_length = 0xff; cie->holding_time = htons(if_ad->holdtime); cie->mtu = htons(if_ad->mtu); nhrp_ext_request(zb, hdr, ifp); /* Cisco NAT detection extension */ hdr->flags |= htons(NHRP_FLAG_REGISTRATION_NAT); ext = nhrp_ext_push(zb, hdr, NHRP_EXTENSION_NAT_ADDRESS); cie = nhrp_cie_push(zb, NHRP_CODE_SUCCESS, &nifp->nbma, &if_ad->addr); cie->prefix_length = 8 * sockunion_get_addrlen(&if_ad->addr); nhrp_ext_complete(zb, ext); nhrp_packet_complete(zb, hdr); nhrp_peer_send(r->peer, zb); zbuf_free(zb); return 0; } static void nhrp_reg_delete(struct nhrp_registration *r) { nhrp_peer_notify_del(r->peer, &r->peer_notifier); nhrp_peer_unref(r->peer); list_del(&r->reglist_entry); THREAD_OFF(r->t_register); XFREE(MTYPE_NHRP_REGISTRATION, r); } static struct nhrp_registration * nhrp_reg_by_nbma(struct nhrp_nhs *nhs, const union sockunion *nbma_addr) { struct nhrp_registration *r; list_for_each_entry( r, &nhs->reglist_head, reglist_entry) if (sockunion_same(&r->peer->vc->remote.nbma, nbma_addr)) return r; return NULL; } static void nhrp_nhs_resolve_cb(struct resolver_query *q, int n, union sockunion *addrs) { struct nhrp_nhs *nhs = container_of(q, struct nhrp_nhs, dns_resolve); struct nhrp_interface *nifp = nhs->ifp->info; struct nhrp_registration *reg, *regn; int i; nhs->t_resolve = NULL; if (n < 0) { /* Failed, retry in a moment */ thread_add_timer(master, nhrp_nhs_resolve, nhs, 5, &nhs->t_resolve); return; } thread_add_timer(master, nhrp_nhs_resolve, nhs, 2 * 60 * 60, &nhs->t_resolve); list_for_each_entry(reg, &nhs->reglist_head, reglist_entry) reg->mark = 1; nhs->hub = 0; for (i = 0; i < n; i++) { if (sockunion_same(&addrs[i], &nifp->nbma)) { nhs->hub = 1; continue; } reg = nhrp_reg_by_nbma(nhs, &addrs[i]); if (reg) { reg->mark = 0; continue; } reg = XCALLOC(MTYPE_NHRP_REGISTRATION, sizeof(*reg)); reg->peer = nhrp_peer_get(nhs->ifp, &addrs[i]); reg->nhs = nhs; reg->timeout = 1; list_init(®->reglist_entry); list_add_tail(®->reglist_entry, &nhs->reglist_head); nhrp_peer_notify_add(reg->peer, ®->peer_notifier, nhrp_reg_peer_notify); thread_add_timer_msec(master, nhrp_reg_send_req, reg, 50, ®->t_register); } list_for_each_entry_safe(reg, regn, &nhs->reglist_head, reglist_entry) { if (reg->mark) nhrp_reg_delete(reg); } } static int nhrp_nhs_resolve(struct thread *t) { struct nhrp_nhs *nhs = THREAD_ARG(t); resolver_resolve(&nhs->dns_resolve, AF_INET, nhs->nbma_fqdn, nhrp_nhs_resolve_cb); return 0; } int nhrp_nhs_add(struct interface *ifp, afi_t afi, union sockunion *proto_addr, const char *nbma_fqdn) { struct nhrp_interface *nifp = ifp->info; struct nhrp_nhs *nhs; if (sockunion_family(proto_addr) != AF_UNSPEC && sockunion_family(proto_addr) != afi2family(afi)) return NHRP_ERR_PROTOCOL_ADDRESS_MISMATCH; list_for_each_entry(nhs, &nifp->afi[afi].nhslist_head, nhslist_entry) { if (sockunion_family(&nhs->proto_addr) != AF_UNSPEC && sockunion_family(proto_addr) != AF_UNSPEC && sockunion_same(&nhs->proto_addr, proto_addr)) return NHRP_ERR_ENTRY_EXISTS; if (strcmp(nhs->nbma_fqdn, nbma_fqdn) == 0) return NHRP_ERR_ENTRY_EXISTS; } nhs = XMALLOC(MTYPE_NHRP_NHS, sizeof(struct nhrp_nhs)); *nhs = (struct nhrp_nhs){ .afi = afi, .ifp = ifp, .proto_addr = *proto_addr, .nbma_fqdn = strdup(nbma_fqdn), .reglist_head = LIST_INITIALIZER(nhs->reglist_head), }; list_add_tail(&nhs->nhslist_entry, &nifp->afi[afi].nhslist_head); thread_add_timer_msec(master, nhrp_nhs_resolve, nhs, 1000, &nhs->t_resolve); return NHRP_OK; } int nhrp_nhs_del(struct interface *ifp, afi_t afi, union sockunion *proto_addr, const char *nbma_fqdn) { struct nhrp_interface *nifp = ifp->info; struct nhrp_nhs *nhs, *nnhs; int ret = NHRP_ERR_ENTRY_NOT_FOUND; if (sockunion_family(proto_addr) != AF_UNSPEC && sockunion_family(proto_addr) != afi2family(afi)) return NHRP_ERR_PROTOCOL_ADDRESS_MISMATCH; list_for_each_entry_safe(nhs, nnhs, &nifp->afi[afi].nhslist_head, nhslist_entry) { if (!sockunion_same(&nhs->proto_addr, proto_addr)) continue; if (strcmp(nhs->nbma_fqdn, nbma_fqdn) != 0) continue; nhrp_nhs_free(nhs); ret = NHRP_OK; } return ret; } int nhrp_nhs_free(struct nhrp_nhs *nhs) { struct nhrp_registration *r, *rn; list_for_each_entry_safe(r, rn, &nhs->reglist_head, reglist_entry) nhrp_reg_delete(r); THREAD_OFF(nhs->t_resolve); list_del(&nhs->nhslist_entry); free((void *)nhs->nbma_fqdn); XFREE(MTYPE_NHRP_NHS, nhs); return 0; } void nhrp_nhs_terminate(void) { struct vrf *vrf = vrf_lookup_by_id(VRF_DEFAULT); struct interface *ifp; struct nhrp_interface *nifp; struct nhrp_nhs *nhs, *tmp; afi_t afi; FOR_ALL_INTERFACES (vrf, ifp) { nifp = ifp->info; for (afi = 0; afi < AFI_MAX; afi++) { list_for_each_entry_safe( nhs, tmp, &nifp->afi[afi].nhslist_head, nhslist_entry) nhrp_nhs_free(nhs); } } } void nhrp_nhs_foreach(struct interface *ifp, afi_t afi, void (*cb)(struct nhrp_nhs *, struct nhrp_registration *, void *), void *ctx) { struct nhrp_interface *nifp = ifp->info; struct nhrp_nhs *nhs; struct nhrp_registration *reg; list_for_each_entry(nhs, &nifp->afi[afi].nhslist_head, nhslist_entry) { if (!list_empty(&nhs->reglist_head)) { list_for_each_entry(reg, &nhs->reglist_head, reglist_entry) cb(nhs, reg, ctx); } else cb(nhs, 0, ctx); } } frr-7.2.1/nhrpd/nhrp_packet.c0000644000000000000000000002041213610377563012770 00000000000000/* NHRP packet handling functions * Copyright (c) 2014-2015 Timo Teräs * * This file is free software: you may copy, redistribute and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation, either version 2 of the License, or * (at your option) any later version. */ #ifdef HAVE_CONFIG_H #include "config.h" #endif #include #include "nhrpd.h" #include "zbuf.h" #include "thread.h" #include "hash.h" #include "nhrp_protocol.h" #include "os.h" struct nhrp_reqid_pool nhrp_packet_reqid; static uint16_t family2proto(int family) { switch (family) { case AF_INET: return ETH_P_IP; case AF_INET6: return ETH_P_IPV6; } return 0; } static int proto2family(uint16_t proto) { switch (proto) { case ETH_P_IP: return AF_INET; case ETH_P_IPV6: return AF_INET6; } return AF_UNSPEC; } struct nhrp_packet_header *nhrp_packet_push(struct zbuf *zb, uint8_t type, const union sockunion *src_nbma, const union sockunion *src_proto, const union sockunion *dst_proto) { struct nhrp_packet_header *hdr; hdr = zbuf_push(zb, struct nhrp_packet_header); if (!hdr) return NULL; *hdr = (struct nhrp_packet_header){ .afnum = htons(family2afi(sockunion_family(src_nbma))), .protocol_type = htons(family2proto(sockunion_family(src_proto))), .version = NHRP_VERSION_RFC2332, .type = type, .hop_count = 64, .src_nbma_address_len = sockunion_get_addrlen(src_nbma), .src_protocol_address_len = sockunion_get_addrlen(src_proto), .dst_protocol_address_len = sockunion_get_addrlen(dst_proto), }; zbuf_put(zb, sockunion_get_addr(src_nbma), hdr->src_nbma_address_len); zbuf_put(zb, sockunion_get_addr(src_proto), hdr->src_protocol_address_len); zbuf_put(zb, sockunion_get_addr(dst_proto), hdr->dst_protocol_address_len); return hdr; } struct nhrp_packet_header *nhrp_packet_pull(struct zbuf *zb, union sockunion *src_nbma, union sockunion *src_proto, union sockunion *dst_proto) { struct nhrp_packet_header *hdr; hdr = zbuf_pull(zb, struct nhrp_packet_header); if (!hdr) return NULL; sockunion_set(src_nbma, afi2family(htons(hdr->afnum)), zbuf_pulln(zb, hdr->src_nbma_address_len + hdr->src_nbma_subaddress_len), hdr->src_nbma_address_len + hdr->src_nbma_subaddress_len); sockunion_set(src_proto, proto2family(htons(hdr->protocol_type)), zbuf_pulln(zb, hdr->src_protocol_address_len), hdr->src_protocol_address_len); sockunion_set(dst_proto, proto2family(htons(hdr->protocol_type)), zbuf_pulln(zb, hdr->dst_protocol_address_len), hdr->dst_protocol_address_len); return hdr; } uint16_t nhrp_packet_calculate_checksum(const uint8_t *pdu, uint16_t len) { const uint16_t *pdu16 = (const uint16_t *)pdu; uint32_t csum = 0; int i; for (i = 0; i < len / 2; i++) csum += pdu16[i]; if (len & 1) csum += htons(pdu[len - 1]); while (csum & 0xffff0000) csum = (csum & 0xffff) + (csum >> 16); return (~csum) & 0xffff; } void nhrp_packet_complete(struct zbuf *zb, struct nhrp_packet_header *hdr) { unsigned short size; if (hdr->extension_offset) nhrp_ext_push(zb, hdr, NHRP_EXTENSION_END | NHRP_EXTENSION_FLAG_COMPULSORY); size = zb->tail - (uint8_t *)hdr; hdr->packet_size = htons(size); hdr->checksum = 0; hdr->checksum = nhrp_packet_calculate_checksum((uint8_t *)hdr, size); } struct nhrp_cie_header *nhrp_cie_push(struct zbuf *zb, uint8_t code, const union sockunion *nbma, const union sockunion *proto) { struct nhrp_cie_header *cie; cie = zbuf_push(zb, struct nhrp_cie_header); *cie = (struct nhrp_cie_header){ .code = code, }; if (nbma) { cie->nbma_address_len = sockunion_get_addrlen(nbma); zbuf_put(zb, sockunion_get_addr(nbma), cie->nbma_address_len); } if (proto) { cie->protocol_address_len = sockunion_get_addrlen(proto); zbuf_put(zb, sockunion_get_addr(proto), cie->protocol_address_len); } return cie; } struct nhrp_cie_header *nhrp_cie_pull(struct zbuf *zb, struct nhrp_packet_header *hdr, union sockunion *nbma, union sockunion *proto) { struct nhrp_cie_header *cie; cie = zbuf_pull(zb, struct nhrp_cie_header); if (!cie) return NULL; if (cie->nbma_address_len + cie->nbma_subaddress_len > 0) { sockunion_set(nbma, afi2family(htons(hdr->afnum)), zbuf_pulln(zb, cie->nbma_address_len + cie->nbma_subaddress_len), cie->nbma_address_len + cie->nbma_subaddress_len); } else { sockunion_family(nbma) = AF_UNSPEC; } if (cie->protocol_address_len) { sockunion_set(proto, proto2family(htons(hdr->protocol_type)), zbuf_pulln(zb, cie->protocol_address_len), cie->protocol_address_len); } else { sockunion_family(proto) = AF_UNSPEC; } return cie; } struct nhrp_extension_header * nhrp_ext_push(struct zbuf *zb, struct nhrp_packet_header *hdr, uint16_t type) { struct nhrp_extension_header *ext; ext = zbuf_push(zb, struct nhrp_extension_header); if (!ext) return NULL; if (!hdr->extension_offset) hdr->extension_offset = htons(zb->tail - (uint8_t *)hdr - sizeof(struct nhrp_extension_header)); *ext = (struct nhrp_extension_header){ .type = htons(type), .length = 0, }; return ext; } void nhrp_ext_complete(struct zbuf *zb, struct nhrp_extension_header *ext) { ext->length = htons(zb->tail - (uint8_t *)ext - sizeof(struct nhrp_extension_header)); } struct nhrp_extension_header *nhrp_ext_pull(struct zbuf *zb, struct zbuf *payload) { struct nhrp_extension_header *ext; uint16_t plen; ext = zbuf_pull(zb, struct nhrp_extension_header); if (!ext) return NULL; plen = htons(ext->length); zbuf_init(payload, zbuf_pulln(zb, plen), plen, plen); return ext; } void nhrp_ext_request(struct zbuf *zb, struct nhrp_packet_header *hdr, struct interface *ifp) { /* Place holders for standard extensions */ nhrp_ext_push(zb, hdr, NHRP_EXTENSION_FORWARD_TRANSIT_NHS | NHRP_EXTENSION_FLAG_COMPULSORY); nhrp_ext_push(zb, hdr, NHRP_EXTENSION_REVERSE_TRANSIT_NHS | NHRP_EXTENSION_FLAG_COMPULSORY); nhrp_ext_push(zb, hdr, NHRP_EXTENSION_RESPONDER_ADDRESS | NHRP_EXTENSION_FLAG_COMPULSORY); } int nhrp_ext_reply(struct zbuf *zb, struct nhrp_packet_header *hdr, struct interface *ifp, struct nhrp_extension_header *ext, struct zbuf *extpayload) { struct nhrp_interface *nifp = ifp->info; struct nhrp_afi_data *ad = &nifp->afi[htons(hdr->afnum)]; struct nhrp_extension_header *dst; struct nhrp_cie_header *cie; uint16_t type; type = htons(ext->type) & ~NHRP_EXTENSION_FLAG_COMPULSORY; if (type == NHRP_EXTENSION_END) return 0; dst = nhrp_ext_push(zb, hdr, htons(ext->type)); if (!dst) goto err; switch (type) { case NHRP_EXTENSION_RESPONDER_ADDRESS: cie = nhrp_cie_push(zb, NHRP_CODE_SUCCESS, &nifp->nbma, &ad->addr); if (!cie) goto err; cie->holding_time = htons(ad->holdtime); break; default: if (type & NHRP_EXTENSION_FLAG_COMPULSORY) goto err; /* fallthru */ case NHRP_EXTENSION_FORWARD_TRANSIT_NHS: case NHRP_EXTENSION_REVERSE_TRANSIT_NHS: /* Supported compulsory extensions, and any * non-compulsory that is not explicitly handled, * should be just copied. */ zbuf_copy(zb, extpayload, zbuf_used(extpayload)); break; } nhrp_ext_complete(zb, dst); return 0; err: zbuf_set_werror(zb); return -1; } static int nhrp_packet_recvraw(struct thread *t) { int fd = THREAD_FD(t), ifindex; struct zbuf *zb; struct interface *ifp; struct nhrp_peer *p; union sockunion remote_nbma; uint8_t addr[64]; size_t len, addrlen; thread_add_read(master, nhrp_packet_recvraw, 0, fd, NULL); zb = zbuf_alloc(1500); if (!zb) return 0; len = zbuf_size(zb); addrlen = sizeof(addr); if (os_recvmsg(zb->buf, &len, &ifindex, addr, &addrlen) < 0) goto err; zb->head = zb->buf; zb->tail = zb->buf + len; switch (addrlen) { case 4: sockunion_set(&remote_nbma, AF_INET, addr, addrlen); break; default: goto err; } ifp = if_lookup_by_index(ifindex, VRF_DEFAULT); if (!ifp) goto err; p = nhrp_peer_get(ifp, &remote_nbma); if (!p) goto err; nhrp_peer_recv(p, zb); nhrp_peer_unref(p); return 0; err: zbuf_free(zb); return 0; } int nhrp_packet_init(void) { thread_add_read(master, nhrp_packet_recvraw, 0, os_socket(), NULL); return 0; } frr-7.2.1/nhrpd/nhrp_peer.c0000644000000000000000000006112413610377563012461 00000000000000/* NHRP peer functions * Copyright (c) 2014-2015 Timo Teräs * * This file is free software: you may copy, redistribute and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation, either version 2 of the License, or * (at your option) any later version. */ #ifdef HAVE_CONFIG_H #include "config.h" #endif #include #include "zebra.h" #include "memory.h" #include "thread.h" #include "hash.h" #include "nhrpd.h" #include "nhrp_protocol.h" #include "os.h" DEFINE_MTYPE_STATIC(NHRPD, NHRP_PEER, "NHRP peer entry") struct ipv6hdr { uint8_t priority_version; uint8_t flow_lbl[3]; uint16_t payload_len; uint8_t nexthdr; uint8_t hop_limit; struct in6_addr saddr; struct in6_addr daddr; }; static void nhrp_packet_debug(struct zbuf *zb, const char *dir); static void nhrp_peer_check_delete(struct nhrp_peer *p) { struct nhrp_interface *nifp = p->ifp->info; if (p->ref || notifier_active(&p->notifier_list)) return; THREAD_OFF(p->t_fallback); hash_release(nifp->peer_hash, p); nhrp_interface_notify_del(p->ifp, &p->ifp_notifier); nhrp_vc_notify_del(p->vc, &p->vc_notifier); XFREE(MTYPE_NHRP_PEER, p); } static int nhrp_peer_notify_up(struct thread *t) { struct nhrp_peer *p = THREAD_ARG(t); struct nhrp_vc *vc = p->vc; struct interface *ifp = p->ifp; struct nhrp_interface *nifp = ifp->info; p->t_fallback = NULL; if (nifp->enabled && (!nifp->ipsec_profile || vc->ipsec)) { p->online = 1; nhrp_peer_ref(p); notifier_call(&p->notifier_list, NOTIFY_PEER_UP); nhrp_peer_unref(p); } return 0; } static void __nhrp_peer_check(struct nhrp_peer *p) { struct nhrp_vc *vc = p->vc; struct interface *ifp = p->ifp; struct nhrp_interface *nifp = ifp->info; unsigned online; online = nifp->enabled && (!nifp->ipsec_profile || vc->ipsec); if (p->online != online) { THREAD_OFF(p->t_fallback); if (online && notifier_active(&p->notifier_list)) { /* If we requested the IPsec connection, delay * the up notification a bit to allow things * settle down. This allows IKE to install * SPDs and SAs. */ thread_add_timer_msec(master, nhrp_peer_notify_up, p, 50, &p->t_fallback); } else { nhrp_peer_ref(p); p->online = online; if (online) { notifier_call(&p->notifier_list, NOTIFY_PEER_UP); } else { p->requested = p->fallback_requested = 0; notifier_call(&p->notifier_list, NOTIFY_PEER_DOWN); } nhrp_peer_unref(p); } } } static void nhrp_peer_vc_notify(struct notifier_block *n, unsigned long cmd) { struct nhrp_peer *p = container_of(n, struct nhrp_peer, vc_notifier); switch (cmd) { case NOTIFY_VC_IPSEC_CHANGED: __nhrp_peer_check(p); break; case NOTIFY_VC_IPSEC_UPDATE_NBMA: nhrp_peer_ref(p); notifier_call(&p->notifier_list, NOTIFY_PEER_NBMA_CHANGING); nhrp_peer_unref(p); break; } } static void nhrp_peer_ifp_notify(struct notifier_block *n, unsigned long cmd) { struct nhrp_peer *p = container_of(n, struct nhrp_peer, ifp_notifier); struct nhrp_interface *nifp; struct nhrp_vc *vc; nhrp_peer_ref(p); switch (cmd) { case NOTIFY_INTERFACE_UP: case NOTIFY_INTERFACE_DOWN: __nhrp_peer_check(p); break; case NOTIFY_INTERFACE_NBMA_CHANGED: /* Source NBMA changed, rebind to new VC */ nifp = p->ifp->info; vc = nhrp_vc_get(&nifp->nbma, &p->vc->remote.nbma, 1); if (vc && p->vc != vc) { nhrp_vc_notify_del(p->vc, &p->vc_notifier); p->vc = vc; nhrp_vc_notify_add(p->vc, &p->vc_notifier, nhrp_peer_vc_notify); __nhrp_peer_check(p); } /* fallthru */ /* to post config update */ case NOTIFY_INTERFACE_ADDRESS_CHANGED: notifier_call(&p->notifier_list, NOTIFY_PEER_IFCONFIG_CHANGED); break; case NOTIFY_INTERFACE_MTU_CHANGED: notifier_call(&p->notifier_list, NOTIFY_PEER_MTU_CHANGED); break; } nhrp_peer_unref(p); } static unsigned int nhrp_peer_key(const void *peer_data) { const struct nhrp_peer *p = peer_data; return sockunion_hash(&p->vc->remote.nbma); } static bool nhrp_peer_cmp(const void *cache_data, const void *key_data) { const struct nhrp_peer *a = cache_data; const struct nhrp_peer *b = key_data; return a->ifp == b->ifp && a->vc == b->vc; } static void *nhrp_peer_create(void *data) { struct nhrp_peer *p, *key = data; p = XMALLOC(MTYPE_NHRP_PEER, sizeof(*p)); *p = (struct nhrp_peer){ .ref = 0, .ifp = key->ifp, .vc = key->vc, .notifier_list = NOTIFIER_LIST_INITIALIZER(&p->notifier_list), }; nhrp_vc_notify_add(p->vc, &p->vc_notifier, nhrp_peer_vc_notify); nhrp_interface_notify_add(p->ifp, &p->ifp_notifier, nhrp_peer_ifp_notify); return p; } struct nhrp_peer *nhrp_peer_get(struct interface *ifp, const union sockunion *remote_nbma) { struct nhrp_interface *nifp = ifp->info; struct nhrp_peer key, *p; struct nhrp_vc *vc; if (!nifp->peer_hash) { nifp->peer_hash = hash_create(nhrp_peer_key, nhrp_peer_cmp, "NHRP Peer Hash"); if (!nifp->peer_hash) return NULL; } vc = nhrp_vc_get(&nifp->nbma, remote_nbma, 1); if (!vc) return NULL; key.ifp = ifp; key.vc = vc; p = hash_get(nifp->peer_hash, &key, nhrp_peer_create); nhrp_peer_ref(p); if (p->ref == 1) __nhrp_peer_check(p); return p; } struct nhrp_peer *nhrp_peer_ref(struct nhrp_peer *p) { if (p) p->ref++; return p; } void nhrp_peer_unref(struct nhrp_peer *p) { if (p) { p->ref--; nhrp_peer_check_delete(p); } } static int nhrp_peer_request_timeout(struct thread *t) { struct nhrp_peer *p = THREAD_ARG(t); struct nhrp_vc *vc = p->vc; struct interface *ifp = p->ifp; struct nhrp_interface *nifp = ifp->info; p->t_fallback = NULL; if (p->online) return 0; if (nifp->ipsec_fallback_profile && !p->prio && !p->fallback_requested) { p->fallback_requested = 1; vici_request_vc(nifp->ipsec_fallback_profile, &vc->local.nbma, &vc->remote.nbma, p->prio); thread_add_timer(master, nhrp_peer_request_timeout, p, 30, &p->t_fallback); } else { p->requested = p->fallback_requested = 0; } return 0; } int nhrp_peer_check(struct nhrp_peer *p, int establish) { struct nhrp_vc *vc = p->vc; struct interface *ifp = p->ifp; struct nhrp_interface *nifp = ifp->info; if (p->online) return 1; if (!establish) return 0; if (p->requested) return 0; if (!nifp->ipsec_profile) return 0; if (sockunion_family(&vc->local.nbma) == AF_UNSPEC) return 0; p->prio = establish > 1; p->requested = 1; vici_request_vc(nifp->ipsec_profile, &vc->local.nbma, &vc->remote.nbma, p->prio); thread_add_timer(master, nhrp_peer_request_timeout, p, (nifp->ipsec_fallback_profile && !p->prio) ? 15 : 30, &p->t_fallback); return 0; } void nhrp_peer_notify_add(struct nhrp_peer *p, struct notifier_block *n, notifier_fn_t fn) { notifier_add(n, &p->notifier_list, fn); } void nhrp_peer_notify_del(struct nhrp_peer *p, struct notifier_block *n) { notifier_del(n); nhrp_peer_check_delete(p); } void nhrp_peer_send(struct nhrp_peer *p, struct zbuf *zb) { char buf[2][256]; nhrp_packet_debug(zb, "Send"); if (!p->online) return; debugf(NHRP_DEBUG_KERNEL, "PACKET: Send %s -> %s", sockunion2str(&p->vc->local.nbma, buf[0], sizeof buf[0]), sockunion2str(&p->vc->remote.nbma, buf[1], sizeof buf[1])); os_sendmsg(zb->head, zbuf_used(zb), p->ifp->ifindex, sockunion_get_addr(&p->vc->remote.nbma), sockunion_get_addrlen(&p->vc->remote.nbma)); zbuf_reset(zb); } static void nhrp_handle_resolution_req(struct nhrp_packet_parser *p) { struct zbuf *zb, payload; struct nhrp_packet_header *hdr; struct nhrp_cie_header *cie; struct nhrp_extension_header *ext; struct nhrp_interface *nifp; struct nhrp_peer *peer; if (!(p->if_ad->flags & NHRP_IFF_SHORTCUT)) { debugf(NHRP_DEBUG_COMMON, "Shortcuts disabled"); /* FIXME: Send error indication? */ return; } if (p->if_ad->network_id && p->route_type == NHRP_ROUTE_OFF_NBMA && p->route_prefix.prefixlen < 8) { debugf(NHRP_DEBUG_COMMON, "Shortcut to more generic than /8 dropped"); return; } debugf(NHRP_DEBUG_COMMON, "Parsing and replying to Resolution Req"); if (nhrp_route_address(p->ifp, &p->src_proto, NULL, &peer) != NHRP_ROUTE_NBMA_NEXTHOP) return; #if 0 /* FIXME: Update requestors binding if CIE specifies holding time */ nhrp_cache_update_binding( NHRP_CACHE_CACHED, &p->src_proto, nhrp_peer_get(p->ifp, &p->src_nbma), htons(cie->holding_time)); #endif nifp = peer->ifp->info; /* Create reply */ zb = zbuf_alloc(1500); hdr = nhrp_packet_push(zb, NHRP_PACKET_RESOLUTION_REPLY, &p->src_nbma, &p->src_proto, &p->dst_proto); /* Copied information from request */ hdr->flags = p->hdr->flags & htons(NHRP_FLAG_RESOLUTION_SOURCE_IS_ROUTER | NHRP_FLAG_RESOLUTION_SOURCE_STABLE); hdr->flags |= htons(NHRP_FLAG_RESOLUTION_DESTINATION_STABLE | NHRP_FLAG_RESOLUTION_AUTHORATIVE); hdr->u.request_id = p->hdr->u.request_id; /* CIE payload */ cie = nhrp_cie_push(zb, NHRP_CODE_SUCCESS, &nifp->nbma, &p->if_ad->addr); cie->holding_time = htons(p->if_ad->holdtime); cie->mtu = htons(p->if_ad->mtu); if (p->if_ad->network_id && p->route_type == NHRP_ROUTE_OFF_NBMA) cie->prefix_length = p->route_prefix.prefixlen; else cie->prefix_length = 8 * sockunion_get_addrlen(&p->if_ad->addr); /* Handle extensions */ while ((ext = nhrp_ext_pull(&p->extensions, &payload)) != NULL) { switch (htons(ext->type) & ~NHRP_EXTENSION_FLAG_COMPULSORY) { case NHRP_EXTENSION_NAT_ADDRESS: if (sockunion_family(&nifp->nat_nbma) == AF_UNSPEC) break; ext = nhrp_ext_push(zb, hdr, NHRP_EXTENSION_NAT_ADDRESS); if (!ext) goto err; cie = nhrp_cie_push(zb, NHRP_CODE_SUCCESS, &nifp->nat_nbma, &p->if_ad->addr); if (!cie) goto err; nhrp_ext_complete(zb, ext); break; default: if (nhrp_ext_reply(zb, hdr, p->ifp, ext, &payload) < 0) goto err; break; } } nhrp_packet_complete(zb, hdr); nhrp_peer_send(peer, zb); err: nhrp_peer_unref(peer); zbuf_free(zb); } static void nhrp_handle_registration_request(struct nhrp_packet_parser *p) { struct interface *ifp = p->ifp; struct zbuf *zb, payload; struct nhrp_packet_header *hdr; struct nhrp_cie_header *cie; struct nhrp_extension_header *ext; struct nhrp_cache *c; union sockunion cie_nbma, cie_proto, *proto_addr, *nbma_addr, *nbma_natoa; int holdtime, prefix_len, hostprefix_len, natted = 0; size_t paylen; void *pay; debugf(NHRP_DEBUG_COMMON, "Parsing and replying to Registration Req"); hostprefix_len = 8 * sockunion_get_addrlen(&p->if_ad->addr); if (!sockunion_same(&p->src_nbma, &p->peer->vc->remote.nbma)) natted = 1; /* Create reply */ zb = zbuf_alloc(1500); hdr = nhrp_packet_push(zb, NHRP_PACKET_REGISTRATION_REPLY, &p->src_nbma, &p->src_proto, &p->if_ad->addr); /* Copied information from request */ hdr->flags = p->hdr->flags & htons(NHRP_FLAG_REGISTRATION_UNIQUE | NHRP_FLAG_REGISTRATION_NAT); hdr->u.request_id = p->hdr->u.request_id; /* Copy payload CIEs */ paylen = zbuf_used(&p->payload); pay = zbuf_pushn(zb, paylen); if (!pay) goto err; memcpy(pay, zbuf_pulln(&p->payload, paylen), paylen); zbuf_init(&payload, pay, paylen, paylen); while ((cie = nhrp_cie_pull(&payload, hdr, &cie_nbma, &cie_proto)) != NULL) { prefix_len = cie->prefix_length; if (prefix_len == 0 || prefix_len >= hostprefix_len) prefix_len = hostprefix_len; if (prefix_len != hostprefix_len && !(p->hdr->flags & htons(NHRP_FLAG_REGISTRATION_UNIQUE))) { cie->code = NHRP_CODE_BINDING_NON_UNIQUE; continue; } /* We currently support only unique prefix registrations */ if (prefix_len != hostprefix_len) { cie->code = NHRP_CODE_ADMINISTRATIVELY_PROHIBITED; continue; } proto_addr = (sockunion_family(&cie_proto) == AF_UNSPEC) ? &p->src_proto : &cie_proto; nbma_addr = (sockunion_family(&cie_nbma) == AF_UNSPEC) ? &p->src_nbma : &cie_nbma; nbma_natoa = NULL; if (natted) { nbma_natoa = nbma_addr; } holdtime = htons(cie->holding_time); if (!holdtime) holdtime = p->if_ad->holdtime; c = nhrp_cache_get(ifp, proto_addr, 1); if (!c) { cie->code = NHRP_CODE_INSUFFICIENT_RESOURCES; continue; } if (!nhrp_cache_update_binding(c, NHRP_CACHE_DYNAMIC, holdtime, nhrp_peer_ref(p->peer), htons(cie->mtu), nbma_natoa)) { cie->code = NHRP_CODE_ADMINISTRATIVELY_PROHIBITED; continue; } cie->code = NHRP_CODE_SUCCESS; } /* Handle extensions */ while ((ext = nhrp_ext_pull(&p->extensions, &payload)) != NULL) { switch (htons(ext->type) & ~NHRP_EXTENSION_FLAG_COMPULSORY) { case NHRP_EXTENSION_NAT_ADDRESS: ext = nhrp_ext_push(zb, hdr, NHRP_EXTENSION_NAT_ADDRESS); if (!ext) goto err; zbuf_copy(zb, &payload, zbuf_used(&payload)); if (natted) { nhrp_cie_push(zb, NHRP_CODE_SUCCESS, &p->peer->vc->remote.nbma, &p->src_proto); } nhrp_ext_complete(zb, ext); break; default: if (nhrp_ext_reply(zb, hdr, ifp, ext, &payload) < 0) goto err; break; } } nhrp_packet_complete(zb, hdr); nhrp_peer_send(p->peer, zb); err: zbuf_free(zb); } static int parse_ether_packet(struct zbuf *zb, uint16_t protocol_type, union sockunion *src, union sockunion *dst) { switch (protocol_type) { case ETH_P_IP: { struct iphdr *iph = zbuf_pull(zb, struct iphdr); if (iph) { if (src) sockunion_set(src, AF_INET, (uint8_t *)&iph->saddr, sizeof(iph->saddr)); if (dst) sockunion_set(dst, AF_INET, (uint8_t *)&iph->daddr, sizeof(iph->daddr)); } } break; case ETH_P_IPV6: { struct ipv6hdr *iph = zbuf_pull(zb, struct ipv6hdr); if (iph) { if (src) sockunion_set(src, AF_INET6, (uint8_t *)&iph->saddr, sizeof(iph->saddr)); if (dst) sockunion_set(dst, AF_INET6, (uint8_t *)&iph->daddr, sizeof(iph->daddr)); } } break; default: return 0; } return 1; } void nhrp_peer_send_indication(struct interface *ifp, uint16_t protocol_type, struct zbuf *pkt) { union sockunion dst; struct zbuf *zb, payload; struct nhrp_interface *nifp = ifp->info; struct nhrp_afi_data *if_ad; struct nhrp_packet_header *hdr; struct nhrp_peer *p; char buf[2][SU_ADDRSTRLEN]; if (!nifp->enabled) return; payload = *pkt; if (!parse_ether_packet(&payload, protocol_type, &dst, NULL)) return; if (nhrp_route_address(ifp, &dst, NULL, &p) != NHRP_ROUTE_NBMA_NEXTHOP) return; if_ad = &nifp->afi[family2afi(sockunion_family(&dst))]; if (!(if_ad->flags & NHRP_IFF_REDIRECT)) { debugf(NHRP_DEBUG_COMMON, "Send Traffic Indication to %s about packet to %s ignored", sockunion2str(&p->vc->remote.nbma, buf[0], sizeof buf[0]), sockunion2str(&dst, buf[1], sizeof buf[1])); return; } debugf(NHRP_DEBUG_COMMON, "Send Traffic Indication to %s (online=%d) about packet to %s", sockunion2str(&p->vc->remote.nbma, buf[0], sizeof buf[0]), p->online, sockunion2str(&dst, buf[1], sizeof buf[1])); /* Create reply */ zb = zbuf_alloc(1500); hdr = nhrp_packet_push(zb, NHRP_PACKET_TRAFFIC_INDICATION, &nifp->nbma, &if_ad->addr, &dst); hdr->hop_count = 0; /* Payload is the packet causing indication */ zbuf_copy(zb, pkt, zbuf_used(pkt)); nhrp_packet_complete(zb, hdr); nhrp_peer_send(p, zb); nhrp_peer_unref(p); zbuf_free(zb); } static void nhrp_handle_error_ind(struct nhrp_packet_parser *pp) { struct zbuf origmsg = pp->payload; struct nhrp_packet_header *hdr; struct nhrp_reqid *reqid; union sockunion src_nbma, src_proto, dst_proto; char buf[2][SU_ADDRSTRLEN]; hdr = nhrp_packet_pull(&origmsg, &src_nbma, &src_proto, &dst_proto); if (!hdr) return; debugf(NHRP_DEBUG_COMMON, "Error Indication from %s about packet to %s ignored", sockunion2str(&pp->src_proto, buf[0], sizeof buf[0]), sockunion2str(&dst_proto, buf[1], sizeof buf[1])); reqid = nhrp_reqid_lookup(&nhrp_packet_reqid, htonl(hdr->u.request_id)); if (reqid) reqid->cb(reqid, pp); } static void nhrp_handle_traffic_ind(struct nhrp_packet_parser *p) { union sockunion dst; char buf[2][SU_ADDRSTRLEN]; if (!parse_ether_packet(&p->payload, htons(p->hdr->protocol_type), NULL, &dst)) return; debugf(NHRP_DEBUG_COMMON, "Traffic Indication from %s about packet to %s: %s", sockunion2str(&p->src_proto, buf[0], sizeof buf[0]), sockunion2str(&dst, buf[1], sizeof buf[1]), (p->if_ad->flags & NHRP_IFF_SHORTCUT) ? "trying shortcut" : "ignored"); if (p->if_ad->flags & NHRP_IFF_SHORTCUT) nhrp_shortcut_initiate(&dst); } enum packet_type_t { PACKET_UNKNOWN = 0, PACKET_REQUEST, PACKET_REPLY, PACKET_INDICATION, }; static struct { enum packet_type_t type; const char *name; void (*handler)(struct nhrp_packet_parser *); } packet_types[] = {[0] = { .type = PACKET_UNKNOWN, .name = "UNKNOWN", }, [NHRP_PACKET_RESOLUTION_REQUEST] = { .type = PACKET_REQUEST, .name = "Resolution-Request", .handler = nhrp_handle_resolution_req, }, [NHRP_PACKET_RESOLUTION_REPLY] = { .type = PACKET_REPLY, .name = "Resolution-Reply", }, [NHRP_PACKET_REGISTRATION_REQUEST] = { .type = PACKET_REQUEST, .name = "Registration-Request", .handler = nhrp_handle_registration_request, }, [NHRP_PACKET_REGISTRATION_REPLY] = { .type = PACKET_REPLY, .name = "Registration-Reply", }, [NHRP_PACKET_PURGE_REQUEST] = { .type = PACKET_REQUEST, .name = "Purge-Request", }, [NHRP_PACKET_PURGE_REPLY] = { .type = PACKET_REPLY, .name = "Purge-Reply", }, [NHRP_PACKET_ERROR_INDICATION] = { .type = PACKET_INDICATION, .name = "Error-Indication", .handler = nhrp_handle_error_ind, }, [NHRP_PACKET_TRAFFIC_INDICATION] = { .type = PACKET_INDICATION, .name = "Traffic-Indication", .handler = nhrp_handle_traffic_ind, }}; static void nhrp_peer_forward(struct nhrp_peer *p, struct nhrp_packet_parser *pp) { struct zbuf *zb, extpl; struct nhrp_packet_header *hdr; struct nhrp_extension_header *ext, *dst; struct nhrp_cie_header *cie; struct nhrp_interface *nifp = pp->ifp->info; struct nhrp_afi_data *if_ad = pp->if_ad; union sockunion cie_nbma, cie_protocol; uint16_t type, len; if (pp->hdr->hop_count == 0) return; /* Create forward packet - copy header */ zb = zbuf_alloc(1500); hdr = nhrp_packet_push(zb, pp->hdr->type, &pp->src_nbma, &pp->src_proto, &pp->dst_proto); hdr->flags = pp->hdr->flags; hdr->hop_count = pp->hdr->hop_count - 1; hdr->u.request_id = pp->hdr->u.request_id; /* Copy payload */ zbuf_copy(zb, &pp->payload, zbuf_used(&pp->payload)); /* Copy extensions */ while ((ext = nhrp_ext_pull(&pp->extensions, &extpl)) != NULL) { type = htons(ext->type) & ~NHRP_EXTENSION_FLAG_COMPULSORY; len = htons(ext->length); if (type == NHRP_EXTENSION_END) break; dst = nhrp_ext_push(zb, hdr, htons(ext->type)); if (!dst) goto err; switch (type) { case NHRP_EXTENSION_FORWARD_TRANSIT_NHS: case NHRP_EXTENSION_REVERSE_TRANSIT_NHS: zbuf_put(zb, extpl.head, len); if ((type == NHRP_EXTENSION_REVERSE_TRANSIT_NHS) == (packet_types[hdr->type].type == PACKET_REPLY)) { /* Check NHS list for forwarding loop */ while ((cie = nhrp_cie_pull(&extpl, pp->hdr, &cie_nbma, &cie_protocol)) != NULL) { if (sockunion_same(&p->vc->remote.nbma, &cie_nbma)) goto err; } /* Append our selves to the list */ cie = nhrp_cie_push(zb, NHRP_CODE_SUCCESS, &nifp->nbma, &if_ad->addr); if (!cie) goto err; cie->holding_time = htons(if_ad->holdtime); } break; default: if (htons(ext->type) & NHRP_EXTENSION_FLAG_COMPULSORY) /* FIXME: RFC says to just copy, but not * append our selves to the transit NHS list */ goto err; /* fallthru */ case NHRP_EXTENSION_RESPONDER_ADDRESS: /* Supported compulsory extensions, and any * non-compulsory that is not explicitly handled, * should be just copied. */ zbuf_copy(zb, &extpl, len); break; } nhrp_ext_complete(zb, dst); } nhrp_packet_complete(zb, hdr); nhrp_peer_send(p, zb); zbuf_free(zb); return; err: nhrp_packet_debug(pp->pkt, "FWD-FAIL"); zbuf_free(zb); } static void nhrp_packet_debug(struct zbuf *zb, const char *dir) { char buf[2][SU_ADDRSTRLEN]; union sockunion src_nbma, src_proto, dst_proto; struct nhrp_packet_header *hdr; struct zbuf zhdr; int reply; if (likely(!(debug_flags & NHRP_DEBUG_COMMON))) return; zbuf_init(&zhdr, zb->buf, zb->tail - zb->buf, zb->tail - zb->buf); hdr = nhrp_packet_pull(&zhdr, &src_nbma, &src_proto, &dst_proto); sockunion2str(&src_proto, buf[0], sizeof buf[0]); sockunion2str(&dst_proto, buf[1], sizeof buf[1]); reply = packet_types[hdr->type].type == PACKET_REPLY; debugf(NHRP_DEBUG_COMMON, "%s %s(%d) %s -> %s", dir, (packet_types[hdr->type].name ? packet_types[hdr->type].name : "Unknown"), hdr->type, reply ? buf[1] : buf[0], reply ? buf[0] : buf[1]); } static int proto2afi(uint16_t proto) { switch (proto) { case ETH_P_IP: return AFI_IP; case ETH_P_IPV6: return AFI_IP6; } return AF_UNSPEC; } struct nhrp_route_info { int local; struct interface *ifp; struct nhrp_vc *vc; }; void nhrp_peer_recv(struct nhrp_peer *p, struct zbuf *zb) { char buf[2][SU_ADDRSTRLEN]; struct nhrp_packet_header *hdr; struct nhrp_vc *vc = p->vc; struct interface *ifp = p->ifp; struct nhrp_interface *nifp = ifp->info; struct nhrp_packet_parser pp; struct nhrp_peer *peer = NULL; struct nhrp_reqid *reqid; const char *info = NULL; union sockunion *target_addr; unsigned paylen, extoff, extlen, realsize; afi_t nbma_afi, proto_afi; debugf(NHRP_DEBUG_KERNEL, "PACKET: Recv %s -> %s", sockunion2str(&vc->remote.nbma, buf[0], sizeof buf[0]), sockunion2str(&vc->local.nbma, buf[1], sizeof buf[1])); if (!p->online) { info = "peer not online"; goto drop; } if (nhrp_packet_calculate_checksum(zb->head, zbuf_used(zb)) != 0) { info = "bad checksum"; goto drop; } realsize = zbuf_used(zb); hdr = nhrp_packet_pull(zb, &pp.src_nbma, &pp.src_proto, &pp.dst_proto); if (!hdr) { info = "corrupt header"; goto drop; } pp.ifp = ifp; pp.pkt = zb; pp.hdr = hdr; pp.peer = p; nbma_afi = htons(hdr->afnum); proto_afi = proto2afi(htons(hdr->protocol_type)); if (hdr->type > NHRP_PACKET_MAX || hdr->version != NHRP_VERSION_RFC2332 || nbma_afi >= AFI_MAX || proto_afi == AF_UNSPEC || packet_types[hdr->type].type == PACKET_UNKNOWN || htons(hdr->packet_size) > realsize) { zlog_info( "From %s: error: packet type %d, version %d, AFI %d, proto %x, size %d (real size %d)", sockunion2str(&vc->remote.nbma, buf[0], sizeof buf[0]), (int)hdr->type, (int)hdr->version, (int)nbma_afi, (int)htons(hdr->protocol_type), (int)htons(hdr->packet_size), (int)realsize); goto drop; } pp.if_ad = &((struct nhrp_interface *)ifp->info)->afi[proto_afi]; extoff = htons(hdr->extension_offset); if (extoff) { if (extoff >= realsize) { info = "extoff larger than packet"; goto drop; } paylen = extoff - (zb->head - zb->buf); } else { paylen = zbuf_used(zb); } zbuf_init(&pp.payload, zbuf_pulln(zb, paylen), paylen, paylen); extlen = zbuf_used(zb); zbuf_init(&pp.extensions, zbuf_pulln(zb, extlen), extlen, extlen); if (!nifp->afi[proto_afi].network_id) { info = "nhrp not enabled"; goto drop; } nhrp_packet_debug(zb, "Recv"); /* FIXME: Check authentication here. This extension needs to be * pre-handled. */ /* Figure out if this is local */ target_addr = (packet_types[hdr->type].type == PACKET_REPLY) ? &pp.src_proto : &pp.dst_proto; if (sockunion_same(&pp.src_proto, &pp.dst_proto)) pp.route_type = NHRP_ROUTE_LOCAL; else pp.route_type = nhrp_route_address(pp.ifp, target_addr, &pp.route_prefix, &peer); switch (pp.route_type) { case NHRP_ROUTE_LOCAL: nhrp_packet_debug(zb, "!LOCAL"); if (packet_types[hdr->type].type == PACKET_REPLY) { reqid = nhrp_reqid_lookup(&nhrp_packet_reqid, htonl(hdr->u.request_id)); if (reqid) { reqid->cb(reqid, &pp); break; } else { nhrp_packet_debug(zb, "!UNKNOWN-REQID"); /* FIXME: send error-indication */ } } /* fallthru */ /* FIXME: double check, is this correct? */ case NHRP_ROUTE_OFF_NBMA: if (packet_types[hdr->type].handler) { packet_types[hdr->type].handler(&pp); break; } break; case NHRP_ROUTE_NBMA_NEXTHOP: nhrp_peer_forward(peer, &pp); break; case NHRP_ROUTE_BLACKHOLE: break; } drop: if (info) { zlog_info( "From %s: error: %s", sockunion2str(&vc->remote.nbma, buf[0], sizeof buf[0]), info); } if (peer) nhrp_peer_unref(peer); zbuf_free(zb); } frr-7.2.1/nhrpd/nhrp_protocol.h0000644000000000000000000000674213610377563013401 00000000000000/* nhrp_protocol.h - NHRP protocol definitions * * Copyright (c) 2007-2012 Timo Teräs * * This software is licensed under the MIT License. * See MIT-LICENSE.txt for additional details. */ #ifndef NHRP_PROTOCOL_H #define NHRP_PROTOCOL_H #include /* NHRP Ethernet protocol number */ #define ETH_P_NHRP 0x2001 /* NHRP Version */ #define NHRP_VERSION_RFC2332 1 /* NHRP Packet Types */ #define NHRP_PACKET_RESOLUTION_REQUEST 1 #define NHRP_PACKET_RESOLUTION_REPLY 2 #define NHRP_PACKET_REGISTRATION_REQUEST 3 #define NHRP_PACKET_REGISTRATION_REPLY 4 #define NHRP_PACKET_PURGE_REQUEST 5 #define NHRP_PACKET_PURGE_REPLY 6 #define NHRP_PACKET_ERROR_INDICATION 7 #define NHRP_PACKET_TRAFFIC_INDICATION 8 #define NHRP_PACKET_MAX 8 /* NHRP Extension Types */ #define NHRP_EXTENSION_FLAG_COMPULSORY 0x8000 #define NHRP_EXTENSION_END 0 #define NHRP_EXTENSION_PAYLOAD 0 #define NHRP_EXTENSION_RESPONDER_ADDRESS 3 #define NHRP_EXTENSION_FORWARD_TRANSIT_NHS 4 #define NHRP_EXTENSION_REVERSE_TRANSIT_NHS 5 #define NHRP_EXTENSION_AUTHENTICATION 7 #define NHRP_EXTENSION_VENDOR 8 #define NHRP_EXTENSION_NAT_ADDRESS 9 /* NHRP Error Indication Codes */ #define NHRP_ERROR_UNRECOGNIZED_EXTENSION 1 #define NHRP_ERROR_LOOP_DETECTED 2 #define NHRP_ERROR_PROTOCOL_ADDRESS_UNREACHABLE 6 #define NHRP_ERROR_PROTOCOL_ERROR 7 #define NHRP_ERROR_SDU_SIZE_EXCEEDED 8 #define NHRP_ERROR_INVALID_EXTENSION 9 #define NHRP_ERROR_INVALID_RESOLUTION_REPLY 10 #define NHRP_ERROR_AUTHENTICATION_FAILURE 11 #define NHRP_ERROR_HOP_COUNT_EXCEEDED 15 /* NHRP CIE Codes */ #define NHRP_CODE_SUCCESS 0 #define NHRP_CODE_ADMINISTRATIVELY_PROHIBITED 4 #define NHRP_CODE_INSUFFICIENT_RESOURCES 5 #define NHRP_CODE_NO_BINDING_EXISTS 11 #define NHRP_CODE_BINDING_NON_UNIQUE 13 #define NHRP_CODE_UNIQUE_ADDRESS_REGISTERED 14 /* NHRP Flags for Resolution request/reply */ #define NHRP_FLAG_RESOLUTION_SOURCE_IS_ROUTER 0x8000 #define NHRP_FLAG_RESOLUTION_AUTHORATIVE 0x4000 #define NHRP_FLAG_RESOLUTION_DESTINATION_STABLE 0x2000 #define NHRP_FLAG_RESOLUTION_UNIQUE 0x1000 #define NHRP_FLAG_RESOLUTION_SOURCE_STABLE 0x0800 #define NHRP_FLAG_RESOLUTION_NAT 0x0002 /* NHRP Flags for Registration request/reply */ #define NHRP_FLAG_REGISTRATION_UNIQUE 0x8000 #define NHRP_FLAG_REGISTRATION_NAT 0x0002 /* NHRP Flags for Purge request/reply */ #define NHRP_FLAG_PURGE_NO_REPLY 0x8000 /* NHRP Authentication extension types (ala Cisco) */ #define NHRP_AUTHENTICATION_PLAINTEXT 0x00000001 /* NHRP Packet Structures */ struct nhrp_packet_header { /* Fixed header */ uint16_t afnum; uint16_t protocol_type; uint8_t snap[5]; uint8_t hop_count; uint16_t packet_size; uint16_t checksum; uint16_t extension_offset; uint8_t version; uint8_t type; uint8_t src_nbma_address_len; uint8_t src_nbma_subaddress_len; /* Mandatory header */ uint8_t src_protocol_address_len; uint8_t dst_protocol_address_len; uint16_t flags; union { uint32_t request_id; struct { uint16_t code; uint16_t offset; } error; } u; } __attribute__((packed)); struct nhrp_cie_header { uint8_t code; uint8_t prefix_length; uint16_t unused; uint16_t mtu; uint16_t holding_time; uint8_t nbma_address_len; uint8_t nbma_subaddress_len; uint8_t protocol_address_len; uint8_t preference; } __attribute__((packed)); struct nhrp_extension_header { uint16_t type; uint16_t length; } __attribute__((packed)); struct nhrp_cisco_authentication_extension { uint32_t type; uint8_t secret[8]; } __attribute__((packed)); #endif frr-7.2.1/nhrpd/nhrp_route.c0000644000000000000000000002150513610377563012663 00000000000000/* NHRP routing functions * Copyright (c) 2014-2015 Timo Teräs * * This file is free software: you may copy, redistribute and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation, either version 2 of the License, or * (at your option) any later version. */ #ifdef HAVE_CONFIG_H #include "config.h" #endif #include "nhrpd.h" #include "table.h" #include "memory.h" #include "stream.h" #include "log.h" #include "zclient.h" DEFINE_MTYPE_STATIC(NHRPD, NHRP_ROUTE, "NHRP routing entry") static struct zclient *zclient; static struct route_table *zebra_rib[AFI_MAX]; struct route_info { union sockunion via; struct interface *ifp; struct interface *nhrp_ifp; }; static struct route_node *nhrp_route_update_get(const struct prefix *p, int create) { struct route_node *rn; afi_t afi = family2afi(PREFIX_FAMILY(p)); if (!zebra_rib[afi]) return NULL; if (create) { rn = route_node_get(zebra_rib[afi], p); if (!rn->info) { rn->info = XCALLOC(MTYPE_NHRP_ROUTE, sizeof(struct route_info)); route_lock_node(rn); } return rn; } else { return route_node_lookup(zebra_rib[afi], p); } } static void nhrp_route_update_put(struct route_node *rn) { struct route_info *ri = rn->info; if (!ri->ifp && !ri->nhrp_ifp && sockunion_family(&ri->via) == AF_UNSPEC) { XFREE(MTYPE_NHRP_ROUTE, rn->info); rn->info = NULL; route_unlock_node(rn); } route_unlock_node(rn); } static void nhrp_route_update_zebra(const struct prefix *p, union sockunion *nexthop, struct interface *ifp) { struct route_node *rn; struct route_info *ri; rn = nhrp_route_update_get( p, (sockunion_family(nexthop) != AF_UNSPEC) || ifp); if (rn) { ri = rn->info; ri->via = *nexthop; ri->ifp = ifp; nhrp_route_update_put(rn); } } void nhrp_route_update_nhrp(const struct prefix *p, struct interface *ifp) { struct route_node *rn; struct route_info *ri; rn = nhrp_route_update_get(p, ifp != NULL); if (rn) { ri = rn->info; ri->nhrp_ifp = ifp; nhrp_route_update_put(rn); } } void nhrp_route_announce(int add, enum nhrp_cache_type type, const struct prefix *p, struct interface *ifp, const union sockunion *nexthop, uint32_t mtu) { struct zapi_route api; struct zapi_nexthop *api_nh; if (zclient->sock < 0) return; memset(&api, 0, sizeof(api)); api.type = ZEBRA_ROUTE_NHRP; api.safi = SAFI_UNICAST; api.vrf_id = VRF_DEFAULT; api.prefix = *p; switch (type) { case NHRP_CACHE_NEGATIVE: zapi_route_set_blackhole(&api, BLACKHOLE_REJECT); ifp = NULL; nexthop = NULL; break; case NHRP_CACHE_DYNAMIC: case NHRP_CACHE_NHS: case NHRP_CACHE_STATIC: /* Regular route, so these are announced * to other routing daemons */ break; default: SET_FLAG(api.flags, ZEBRA_FLAG_FIB_OVERRIDE); break; } SET_FLAG(api.flags, ZEBRA_FLAG_ALLOW_RECURSION); SET_FLAG(api.message, ZAPI_MESSAGE_NEXTHOP); api.nexthop_num = 1; api_nh = &api.nexthops[0]; api_nh->vrf_id = VRF_DEFAULT; switch (api.prefix.family) { case AF_INET: if (nexthop) { api_nh->gate.ipv4 = nexthop->sin.sin_addr; api_nh->type = NEXTHOP_TYPE_IPV4; } if (ifp) { api_nh->ifindex = ifp->ifindex; if (api_nh->type == NEXTHOP_TYPE_IPV4) api_nh->type = NEXTHOP_TYPE_IPV4_IFINDEX; else api_nh->type = NEXTHOP_TYPE_IFINDEX; } break; case AF_INET6: if (nexthop) { api_nh->gate.ipv6 = nexthop->sin6.sin6_addr; api_nh->type = NEXTHOP_TYPE_IPV6; } if (ifp) { api_nh->ifindex = ifp->ifindex; if (api_nh->type == NEXTHOP_TYPE_IPV6) api_nh->type = NEXTHOP_TYPE_IPV6_IFINDEX; else api_nh->type = NEXTHOP_TYPE_IFINDEX; } break; } if (mtu) { SET_FLAG(api.message, ZAPI_MESSAGE_MTU); api.mtu = mtu; } if (unlikely(debug_flags & NHRP_DEBUG_ROUTE)) { char buf[2][PREFIX_STRLEN]; prefix2str(&api.prefix, buf[0], sizeof(buf[0])); zlog_debug( "Zebra send: route %s %s nexthop %s metric %u" " count %d dev %s", add ? "add" : "del", buf[0], nexthop ? inet_ntop(api.prefix.family, &api_nh->gate, buf[1], sizeof(buf[1])) : "", api.metric, api.nexthop_num, ifp ? ifp->name : "none"); } zclient_route_send(add ? ZEBRA_ROUTE_ADD : ZEBRA_ROUTE_DELETE, zclient, &api); } int nhrp_route_read(ZAPI_CALLBACK_ARGS) { struct zapi_route api; struct zapi_nexthop *api_nh; struct interface *ifp = NULL; union sockunion nexthop_addr; char buf[2][PREFIX_STRLEN]; int added; if (zapi_route_decode(zclient->ibuf, &api) < 0) return -1; /* we completely ignore srcdest routes for now. */ if (CHECK_FLAG(api.message, ZAPI_MESSAGE_SRCPFX)) return 0; sockunion_family(&nexthop_addr) = AF_UNSPEC; if (CHECK_FLAG(api.message, ZAPI_MESSAGE_NEXTHOP)) { api_nh = &api.nexthops[0]; nexthop_addr.sa.sa_family = api.prefix.family; switch (nexthop_addr.sa.sa_family) { case AF_INET: nexthop_addr.sin.sin_addr = api_nh->gate.ipv4; break; case AF_INET6: nexthop_addr.sin6.sin6_addr = api_nh->gate.ipv6; break; } if (api_nh->ifindex != IFINDEX_INTERNAL) ifp = if_lookup_by_index(api_nh->ifindex, VRF_DEFAULT); } added = (cmd == ZEBRA_REDISTRIBUTE_ROUTE_ADD); debugf(NHRP_DEBUG_ROUTE, "if-route-%s: %s via %s dev %s", added ? "add" : "del", prefix2str(&api.prefix, buf[0], sizeof buf[0]), sockunion2str(&nexthop_addr, buf[1], sizeof buf[1]), ifp ? ifp->name : "(none)"); nhrp_route_update_zebra(&api.prefix, &nexthop_addr, ifp); nhrp_shortcut_prefix_change(&api.prefix, !added); return 0; } int nhrp_route_get_nexthop(const union sockunion *addr, struct prefix *p, union sockunion *via, struct interface **ifp) { struct route_node *rn; struct route_info *ri; struct prefix lookup; afi_t afi = family2afi(sockunion_family(addr)); char buf[PREFIX_STRLEN]; sockunion2hostprefix(addr, &lookup); rn = route_node_match(zebra_rib[afi], &lookup); if (!rn) return 0; ri = rn->info; if (ri->nhrp_ifp) { debugf(NHRP_DEBUG_ROUTE, "lookup %s: nhrp_if=%s", prefix2str(&lookup, buf, sizeof buf), ri->nhrp_ifp->name); if (via) sockunion_family(via) = AF_UNSPEC; if (ifp) *ifp = ri->nhrp_ifp; } else { debugf(NHRP_DEBUG_ROUTE, "lookup %s: zebra route dev %s", prefix2str(&lookup, buf, sizeof buf), ri->ifp ? ri->ifp->name : "(none)"); if (via) *via = ri->via; if (ifp) *ifp = ri->ifp; } if (p) *p = rn->p; route_unlock_node(rn); return 1; } enum nhrp_route_type nhrp_route_address(struct interface *in_ifp, union sockunion *addr, struct prefix *p, struct nhrp_peer **peer) { struct interface *ifp = in_ifp; struct nhrp_interface *nifp; struct nhrp_cache *c; union sockunion via[4]; uint32_t network_id = 0; afi_t afi = family2afi(sockunion_family(addr)); int i; if (ifp) { nifp = ifp->info; network_id = nifp->afi[afi].network_id; c = nhrp_cache_get(ifp, addr, 0); if (c && c->cur.type == NHRP_CACHE_LOCAL) { if (p) memset(p, 0, sizeof(*p)); return NHRP_ROUTE_LOCAL; } } for (i = 0; i < 4; i++) { if (!nhrp_route_get_nexthop(addr, p, &via[i], &ifp)) return NHRP_ROUTE_BLACKHOLE; if (ifp) { /* Departing from nbma network? */ nifp = ifp->info; if (network_id && network_id != nifp->afi[afi].network_id) return NHRP_ROUTE_OFF_NBMA; } if (sockunion_family(&via[i]) == AF_UNSPEC) break; /* Resolve via node, but return the prefix of first match */ addr = &via[i]; p = NULL; } if (ifp) { c = nhrp_cache_get(ifp, addr, 0); if (c && c->cur.type >= NHRP_CACHE_DYNAMIC) { if (p) memset(p, 0, sizeof(*p)); if (c->cur.type == NHRP_CACHE_LOCAL) return NHRP_ROUTE_LOCAL; if (peer) *peer = nhrp_peer_ref(c->cur.peer); return NHRP_ROUTE_NBMA_NEXTHOP; } } return NHRP_ROUTE_BLACKHOLE; } static void nhrp_zebra_connected(struct zclient *zclient) { zclient_send_reg_requests(zclient, VRF_DEFAULT); zebra_redistribute_send(ZEBRA_REDISTRIBUTE_ADD, zclient, AFI_IP, ZEBRA_ROUTE_ALL, 0, VRF_DEFAULT); zebra_redistribute_send(ZEBRA_REDISTRIBUTE_ADD, zclient, AFI_IP6, ZEBRA_ROUTE_ALL, 0, VRF_DEFAULT); } void nhrp_zebra_init(void) { zebra_rib[AFI_IP] = route_table_init(); zebra_rib[AFI_IP6] = route_table_init(); zclient = zclient_new(master, &zclient_options_default); zclient->zebra_connected = nhrp_zebra_connected; zclient->interface_add = nhrp_interface_add; zclient->interface_delete = nhrp_interface_delete; zclient->interface_up = nhrp_interface_up; zclient->interface_down = nhrp_interface_down; zclient->interface_address_add = nhrp_interface_address_add; zclient->interface_address_delete = nhrp_interface_address_delete; zclient->redistribute_route_add = nhrp_route_read; zclient->redistribute_route_del = nhrp_route_read; zclient_init(zclient, ZEBRA_ROUTE_NHRP, 0, &nhrpd_privs); } void nhrp_zebra_terminate(void) { zclient_stop(zclient); zclient_free(zclient); route_table_finish(zebra_rib[AFI_IP]); route_table_finish(zebra_rib[AFI_IP6]); } frr-7.2.1/nhrpd/nhrp_shortcut.c0000644000000000000000000002777213610377563013414 00000000000000/* NHRP shortcut related functions * Copyright (c) 2014-2015 Timo Teräs * * This file is free software: you may copy, redistribute and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation, either version 2 of the License, or * (at your option) any later version. */ #ifdef HAVE_CONFIG_H #include "config.h" #endif #include "nhrpd.h" #include "table.h" #include "memory.h" #include "thread.h" #include "log.h" #include "nhrp_protocol.h" DEFINE_MTYPE_STATIC(NHRPD, NHRP_SHORTCUT, "NHRP shortcut") static struct route_table *shortcut_rib[AFI_MAX]; static int nhrp_shortcut_do_purge(struct thread *t); static void nhrp_shortcut_delete(struct nhrp_shortcut *s); static void nhrp_shortcut_send_resolution_req(struct nhrp_shortcut *s); static void nhrp_shortcut_check_use(struct nhrp_shortcut *s) { char buf[PREFIX_STRLEN]; if (s->expiring && s->cache && s->cache->used) { debugf(NHRP_DEBUG_ROUTE, "Shortcut %s used and expiring", prefix2str(s->p, buf, sizeof buf)); nhrp_shortcut_send_resolution_req(s); } } static int nhrp_shortcut_do_expire(struct thread *t) { struct nhrp_shortcut *s = THREAD_ARG(t); s->t_timer = NULL; thread_add_timer(master, nhrp_shortcut_do_purge, s, s->holding_time / 3, &s->t_timer); s->expiring = 1; nhrp_shortcut_check_use(s); return 0; } static void nhrp_shortcut_cache_notify(struct notifier_block *n, unsigned long cmd) { struct nhrp_shortcut *s = container_of(n, struct nhrp_shortcut, cache_notifier); switch (cmd) { case NOTIFY_CACHE_UP: if (!s->route_installed) { nhrp_route_announce(1, s->type, s->p, NULL, &s->cache->remote_addr, 0); s->route_installed = 1; } break; case NOTIFY_CACHE_USED: nhrp_shortcut_check_use(s); break; case NOTIFY_CACHE_DOWN: case NOTIFY_CACHE_DELETE: if (s->route_installed) { nhrp_route_announce(0, NHRP_CACHE_INVALID, s->p, NULL, NULL, 0); s->route_installed = 0; } if (cmd == NOTIFY_CACHE_DELETE) nhrp_shortcut_delete(s); break; } } static void nhrp_shortcut_update_binding(struct nhrp_shortcut *s, enum nhrp_cache_type type, struct nhrp_cache *c, int holding_time) { s->type = type; if (c != s->cache) { if (s->cache) { nhrp_cache_notify_del(s->cache, &s->cache_notifier); s->cache = NULL; } s->cache = c; if (s->cache) { nhrp_cache_notify_add(s->cache, &s->cache_notifier, nhrp_shortcut_cache_notify); if (s->cache->route_installed) { /* Force renewal of Zebra announce on prefix * change */ s->route_installed = 0; nhrp_shortcut_cache_notify(&s->cache_notifier, NOTIFY_CACHE_UP); } } if (!s->cache || !s->cache->route_installed) nhrp_shortcut_cache_notify(&s->cache_notifier, NOTIFY_CACHE_DOWN); } if (s->type == NHRP_CACHE_NEGATIVE && !s->route_installed) { nhrp_route_announce(1, s->type, s->p, NULL, NULL, 0); s->route_installed = 1; } else if (s->type == NHRP_CACHE_INVALID && s->route_installed) { nhrp_route_announce(0, NHRP_CACHE_INVALID, s->p, NULL, NULL, 0); s->route_installed = 0; } THREAD_OFF(s->t_timer); if (holding_time) { s->expiring = 0; s->holding_time = holding_time; thread_add_timer(master, nhrp_shortcut_do_expire, s, 2 * holding_time / 3, &s->t_timer); } } static void nhrp_shortcut_delete(struct nhrp_shortcut *s) { struct route_node *rn; afi_t afi = family2afi(PREFIX_FAMILY(s->p)); char buf[PREFIX_STRLEN]; THREAD_OFF(s->t_timer); nhrp_reqid_free(&nhrp_packet_reqid, &s->reqid); debugf(NHRP_DEBUG_ROUTE, "Shortcut %s purged", prefix2str(s->p, buf, sizeof buf)); nhrp_shortcut_update_binding(s, NHRP_CACHE_INVALID, NULL, 0); /* Delete node */ rn = route_node_lookup(shortcut_rib[afi], s->p); if (rn) { XFREE(MTYPE_NHRP_SHORTCUT, rn->info); rn->info = NULL; route_unlock_node(rn); route_unlock_node(rn); } } static int nhrp_shortcut_do_purge(struct thread *t) { struct nhrp_shortcut *s = THREAD_ARG(t); s->t_timer = NULL; nhrp_shortcut_delete(s); return 0; } static struct nhrp_shortcut *nhrp_shortcut_get(struct prefix *p) { struct nhrp_shortcut *s; struct route_node *rn; char buf[PREFIX_STRLEN]; afi_t afi = family2afi(PREFIX_FAMILY(p)); if (!shortcut_rib[afi]) return 0; rn = route_node_get(shortcut_rib[afi], p); if (!rn->info) { s = rn->info = XCALLOC(MTYPE_NHRP_SHORTCUT, sizeof(struct nhrp_shortcut)); s->type = NHRP_CACHE_INVALID; s->p = &rn->p; debugf(NHRP_DEBUG_ROUTE, "Shortcut %s created", prefix2str(s->p, buf, sizeof buf)); } else { s = rn->info; route_unlock_node(rn); } return s; } static void nhrp_shortcut_recv_resolution_rep(struct nhrp_reqid *reqid, void *arg) { struct nhrp_packet_parser *pp = arg; struct nhrp_shortcut *s = container_of(reqid, struct nhrp_shortcut, reqid); struct nhrp_shortcut *ps; struct nhrp_extension_header *ext; struct nhrp_cie_header *cie; struct nhrp_cache *c = NULL; union sockunion *proto, cie_proto, *nbma, *nbma_natoa, cie_nbma, nat_nbma; struct prefix prefix, route_prefix; struct zbuf extpl; char bufp[PREFIX_STRLEN], buf[3][SU_ADDRSTRLEN]; int holding_time = pp->if_ad->holdtime; nhrp_reqid_free(&nhrp_packet_reqid, &s->reqid); THREAD_OFF(s->t_timer); thread_add_timer(master, nhrp_shortcut_do_purge, s, 1, &s->t_timer); if (pp->hdr->type != NHRP_PACKET_RESOLUTION_REPLY) { if (pp->hdr->type == NHRP_PACKET_ERROR_INDICATION && pp->hdr->u.error.code == NHRP_ERROR_PROTOCOL_ADDRESS_UNREACHABLE) { debugf(NHRP_DEBUG_COMMON, "Shortcut: Resolution: Protocol address unreachable"); nhrp_shortcut_update_binding(s, NHRP_CACHE_NEGATIVE, NULL, holding_time); } else { debugf(NHRP_DEBUG_COMMON, "Shortcut: Resolution failed"); } return; } /* Parse extensions */ memset(&nat_nbma, 0, sizeof nat_nbma); while ((ext = nhrp_ext_pull(&pp->extensions, &extpl)) != NULL) { switch (htons(ext->type) & ~NHRP_EXTENSION_FLAG_COMPULSORY) { case NHRP_EXTENSION_NAT_ADDRESS: nhrp_cie_pull(&extpl, pp->hdr, &nat_nbma, &cie_proto); break; } } /* Minor sanity check */ prefix2sockunion(s->p, &cie_proto); if (!sockunion_same(&cie_proto, &pp->dst_proto)) { debugf(NHRP_DEBUG_COMMON, "Shortcut: Warning dst_proto altered from %s to %s", sockunion2str(&cie_proto, buf[0], sizeof buf[0]), sockunion2str(&pp->dst_proto, buf[1], sizeof buf[1])); } /* One or more CIEs should be given as reply, we support only one */ cie = nhrp_cie_pull(&pp->payload, pp->hdr, &cie_nbma, &cie_proto); if (!cie || cie->code != NHRP_CODE_SUCCESS) { debugf(NHRP_DEBUG_COMMON, "Shortcut: CIE code %d", cie ? cie->code : -1); return; } proto = sockunion_family(&cie_proto) != AF_UNSPEC ? &cie_proto : &pp->dst_proto; if (cie->holding_time) holding_time = htons(cie->holding_time); prefix = *s->p; prefix.prefixlen = cie->prefix_length; /* Sanity check prefix length */ if (prefix.prefixlen >= 8 * prefix_blen(&prefix) || prefix.prefixlen == 0) { prefix.prefixlen = 8 * prefix_blen(&prefix); } else if (nhrp_route_address(NULL, &pp->dst_proto, &route_prefix, NULL) == NHRP_ROUTE_NBMA_NEXTHOP) { if (prefix.prefixlen < route_prefix.prefixlen) prefix.prefixlen = route_prefix.prefixlen; } debugf(NHRP_DEBUG_COMMON, "Shortcut: %s is at proto %s cie-nbma %s nat-nbma %s cie-holdtime %d", prefix2str(&prefix, bufp, sizeof bufp), sockunion2str(proto, buf[0], sizeof buf[0]), sockunion2str(&cie_nbma, buf[1], sizeof buf[1]), sockunion2str(&nat_nbma, buf[2], sizeof buf[2]), htons(cie->holding_time)); /* Update cache entry for the protocol to nbma binding */ if (sockunion_family(&nat_nbma) != AF_UNSPEC) { nbma = &nat_nbma; nbma_natoa = &cie_nbma; } else { nbma = &cie_nbma; nbma_natoa = NULL; } if (sockunion_family(nbma)) { c = nhrp_cache_get(pp->ifp, proto, 1); if (c) { nhrp_cache_update_binding(c, NHRP_CACHE_CACHED, holding_time, nhrp_peer_get(pp->ifp, nbma), htons(cie->mtu), nbma_natoa); } } /* Update shortcut entry for subnet to protocol gw binding */ if (c && !sockunion_same(proto, &pp->dst_proto)) { ps = nhrp_shortcut_get(&prefix); if (ps) { ps->addr = s->addr; nhrp_shortcut_update_binding(ps, NHRP_CACHE_CACHED, c, holding_time); } } debugf(NHRP_DEBUG_COMMON, "Shortcut: Resolution reply handled"); } static void nhrp_shortcut_send_resolution_req(struct nhrp_shortcut *s) { struct zbuf *zb; struct nhrp_packet_header *hdr; struct interface *ifp; struct nhrp_interface *nifp; struct nhrp_peer *peer; if (nhrp_route_address(NULL, &s->addr, NULL, &peer) != NHRP_ROUTE_NBMA_NEXTHOP) return; if (s->type == NHRP_CACHE_INVALID || s->type == NHRP_CACHE_NEGATIVE) s->type = NHRP_CACHE_INCOMPLETE; ifp = peer->ifp; nifp = ifp->info; /* Create request */ zb = zbuf_alloc(1500); hdr = nhrp_packet_push( zb, NHRP_PACKET_RESOLUTION_REQUEST, &nifp->nbma, &nifp->afi[family2afi(sockunion_family(&s->addr))].addr, &s->addr); hdr->u.request_id = htonl(nhrp_reqid_alloc(&nhrp_packet_reqid, &s->reqid, nhrp_shortcut_recv_resolution_rep)); hdr->flags = htons(NHRP_FLAG_RESOLUTION_SOURCE_IS_ROUTER | NHRP_FLAG_RESOLUTION_AUTHORATIVE | NHRP_FLAG_RESOLUTION_SOURCE_STABLE); /* RFC2332 - One or zero CIEs, if CIE is present contains: * - Prefix length: widest acceptable prefix we accept (if U set, 0xff) * - MTU: MTU of the source station * - Holding Time: Max time to cache the source information * */ /* FIXME: Send holding time, and MTU */ nhrp_ext_request(zb, hdr, ifp); /* Cisco NAT detection extension */ hdr->flags |= htons(NHRP_FLAG_RESOLUTION_NAT); nhrp_ext_push(zb, hdr, NHRP_EXTENSION_NAT_ADDRESS); nhrp_packet_complete(zb, hdr); nhrp_peer_send(peer, zb); nhrp_peer_unref(peer); zbuf_free(zb); } void nhrp_shortcut_initiate(union sockunion *addr) { struct prefix p; struct nhrp_shortcut *s; sockunion2hostprefix(addr, &p); s = nhrp_shortcut_get(&p); if (s && s->type != NHRP_CACHE_INCOMPLETE) { s->addr = *addr; THREAD_OFF(s->t_timer); thread_add_timer(master, nhrp_shortcut_do_purge, s, 30, &s->t_timer); nhrp_shortcut_send_resolution_req(s); } } void nhrp_shortcut_init(void) { shortcut_rib[AFI_IP] = route_table_init(); shortcut_rib[AFI_IP6] = route_table_init(); } void nhrp_shortcut_terminate(void) { route_table_finish(shortcut_rib[AFI_IP]); route_table_finish(shortcut_rib[AFI_IP6]); } void nhrp_shortcut_foreach(afi_t afi, void (*cb)(struct nhrp_shortcut *, void *), void *ctx) { struct route_table *rt = shortcut_rib[afi]; struct route_node *rn; route_table_iter_t iter; if (!rt) return; route_table_iter_init(&iter, rt); while ((rn = route_table_iter_next(&iter)) != NULL) { if (rn->info) cb(rn->info, ctx); } route_table_iter_cleanup(&iter); } struct purge_ctx { const struct prefix *p; int deleted; }; void nhrp_shortcut_purge(struct nhrp_shortcut *s, int force) { THREAD_OFF(s->t_timer); nhrp_reqid_free(&nhrp_packet_reqid, &s->reqid); if (force) { /* Immediate purge on route with draw or pending shortcut */ thread_add_timer_msec(master, nhrp_shortcut_do_purge, s, 5, &s->t_timer); } else { /* Soft expire - force immediate renewal, but purge * in few seconds to make sure stale route is not * used too long. In practice most purges are caused * by hub bgp change, but target usually stays same. * This allows to keep nhrp route up, and to not * cause temporary rerouting via hubs causing latency * jitter. */ thread_add_timer_msec(master, nhrp_shortcut_do_purge, s, 3000, &s->t_timer); s->expiring = 1; nhrp_shortcut_check_use(s); } } static void nhrp_shortcut_purge_prefix(struct nhrp_shortcut *s, void *ctx) { struct purge_ctx *pctx = ctx; if (prefix_match(pctx->p, s->p)) nhrp_shortcut_purge(s, pctx->deleted || !s->cache); } void nhrp_shortcut_prefix_change(const struct prefix *p, int deleted) { struct purge_ctx pctx = { .p = p, .deleted = deleted, }; nhrp_shortcut_foreach(family2afi(PREFIX_FAMILY(p)), nhrp_shortcut_purge_prefix, &pctx); } frr-7.2.1/nhrpd/nhrp_vc.c0000644000000000000000000001162613610377563012140 00000000000000/* NHRP virtual connection * Copyright (c) 2014-2015 Timo Teräs * * This file is free software: you may copy, redistribute and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation, either version 2 of the License, or * (at your option) any later version. */ #include "zebra.h" #include "memory.h" #include "stream.h" #include "hash.h" #include "thread.h" #include "jhash.h" #include "nhrpd.h" #include "os.h" DEFINE_MTYPE_STATIC(NHRPD, NHRP_VC, "NHRP virtual connection") struct child_sa { uint32_t id; struct nhrp_vc *vc; struct list_head childlist_entry; }; static struct hash *nhrp_vc_hash; static struct list_head childlist_head[512]; static unsigned int nhrp_vc_key(const void *peer_data) { const struct nhrp_vc *vc = peer_data; return jhash_2words(sockunion_hash(&vc->local.nbma), sockunion_hash(&vc->remote.nbma), 0); } static bool nhrp_vc_cmp(const void *cache_data, const void *key_data) { const struct nhrp_vc *a = cache_data; const struct nhrp_vc *b = key_data; return sockunion_same(&a->local.nbma, &b->local.nbma) && sockunion_same(&a->remote.nbma, &b->remote.nbma); } static void *nhrp_vc_alloc(void *data) { struct nhrp_vc *vc, *key = data; vc = XMALLOC(MTYPE_NHRP_VC, sizeof(struct nhrp_vc)); *vc = (struct nhrp_vc){ .local.nbma = key->local.nbma, .remote.nbma = key->remote.nbma, .notifier_list = NOTIFIER_LIST_INITIALIZER(&vc->notifier_list), }; return vc; } static void nhrp_vc_free(void *data) { XFREE(MTYPE_NHRP_VC, data); } struct nhrp_vc *nhrp_vc_get(const union sockunion *src, const union sockunion *dst, int create) { struct nhrp_vc key; key.local.nbma = *src; key.remote.nbma = *dst; return hash_get(nhrp_vc_hash, &key, create ? nhrp_vc_alloc : 0); } static void nhrp_vc_check_delete(struct nhrp_vc *vc) { if (vc->updating || vc->ipsec || notifier_active(&vc->notifier_list)) return; hash_release(nhrp_vc_hash, vc); nhrp_vc_free(vc); } static void nhrp_vc_update(struct nhrp_vc *vc, long cmd) { vc->updating = 1; notifier_call(&vc->notifier_list, cmd); vc->updating = 0; nhrp_vc_check_delete(vc); } static void nhrp_vc_ipsec_reset(struct nhrp_vc *vc) { vc->local.id[0] = 0; vc->local.certlen = 0; vc->remote.id[0] = 0; vc->remote.certlen = 0; } int nhrp_vc_ipsec_updown(uint32_t child_id, struct nhrp_vc *vc) { char buf[2][SU_ADDRSTRLEN]; struct child_sa *sa = NULL, *lsa; uint32_t child_hash = child_id % array_size(childlist_head); int abort_migration = 0; list_for_each_entry(lsa, &childlist_head[child_hash], childlist_entry) { if (lsa->id == child_id) { sa = lsa; break; } } if (!sa) { if (!vc) return 0; sa = XMALLOC(MTYPE_NHRP_VC, sizeof(struct child_sa)); *sa = (struct child_sa){ .id = child_id, .childlist_entry = LIST_INITIALIZER(sa->childlist_entry), .vc = NULL, }; list_add_tail(&sa->childlist_entry, &childlist_head[child_hash]); } if (sa->vc == vc) return 0; if (vc) { /* Attach first to new VC */ vc->ipsec++; nhrp_vc_update(vc, NOTIFY_VC_IPSEC_CHANGED); } if (sa->vc && vc) { /* Notify old VC of migration */ sa->vc->abort_migration = 0; debugf(NHRP_DEBUG_COMMON, "IPsec NBMA change of %s to %s", sockunion2str(&sa->vc->remote.nbma, buf[0], sizeof buf[0]), sockunion2str(&vc->remote.nbma, buf[1], sizeof buf[1])); nhrp_vc_update(sa->vc, NOTIFY_VC_IPSEC_UPDATE_NBMA); abort_migration = sa->vc->abort_migration; } if (sa->vc) { /* Deattach old VC */ sa->vc->ipsec--; if (!sa->vc->ipsec) nhrp_vc_ipsec_reset(sa->vc); nhrp_vc_update(sa->vc, NOTIFY_VC_IPSEC_CHANGED); } /* Update */ sa->vc = vc; if (!vc) { list_del(&sa->childlist_entry); XFREE(MTYPE_NHRP_VC, sa); } return abort_migration; } void nhrp_vc_notify_add(struct nhrp_vc *vc, struct notifier_block *n, notifier_fn_t action) { notifier_add(n, &vc->notifier_list, action); } void nhrp_vc_notify_del(struct nhrp_vc *vc, struct notifier_block *n) { notifier_del(n); nhrp_vc_check_delete(vc); } struct nhrp_vc_iterator_ctx { void (*cb)(struct nhrp_vc *, void *); void *ctx; }; static void nhrp_vc_iterator(struct hash_bucket *b, void *ctx) { struct nhrp_vc_iterator_ctx *ic = ctx; ic->cb(b->data, ic->ctx); } void nhrp_vc_foreach(void (*cb)(struct nhrp_vc *, void *), void *ctx) { struct nhrp_vc_iterator_ctx ic = { .cb = cb, .ctx = ctx, }; hash_iterate(nhrp_vc_hash, nhrp_vc_iterator, &ic); } void nhrp_vc_init(void) { size_t i; nhrp_vc_hash = hash_create(nhrp_vc_key, nhrp_vc_cmp, "NHRP VC hash"); for (i = 0; i < array_size(childlist_head); i++) list_init(&childlist_head[i]); } void nhrp_vc_reset(void) { struct child_sa *sa, *n; size_t i; for (i = 0; i < array_size(childlist_head); i++) { list_for_each_entry_safe(sa, n, &childlist_head[i], childlist_entry) nhrp_vc_ipsec_updown(sa->id, 0); } } void nhrp_vc_terminate(void) { nhrp_vc_reset(); hash_clean(nhrp_vc_hash, nhrp_vc_free); } frr-7.2.1/nhrpd/nhrp_vty.c0000644000000000000000000006102513610377563012350 00000000000000/* NHRP vty handling * Copyright (c) 2014-2015 Timo Teräs * * This file is free software: you may copy, redistribute and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation, either version 2 of the License, or * (at your option) any later version. */ #include "zebra.h" #include "command.h" #include "zclient.h" #include "stream.h" #include "filter.h" #include "nhrpd.h" #include "netlink.h" static struct cmd_node zebra_node = { .node = ZEBRA_NODE, .prompt = "%s(config-router)# ", .vtysh = 1, }; static struct cmd_node nhrp_interface_node = { .node = INTERFACE_NODE, .prompt = "%s(config-if)# ", .vtysh = 1, }; #define NHRP_DEBUG_FLAGS_CMD "" #define NHRP_DEBUG_FLAGS_STR \ "All messages\n" \ "Common messages (default)\n" \ "Event manager messages\n" \ "Interface messages\n" \ "Kernel messages\n" \ "Route messages\n" \ "VICI messages\n" static const struct message debug_flags_desc[] = { {NHRP_DEBUG_ALL, "all"}, {NHRP_DEBUG_COMMON, "common"}, {NHRP_DEBUG_IF, "interface"}, {NHRP_DEBUG_KERNEL, "kernel"}, {NHRP_DEBUG_ROUTE, "route"}, {NHRP_DEBUG_VICI, "vici"}, {NHRP_DEBUG_EVENT, "event"}, {0}}; static const struct message interface_flags_desc[] = { {NHRP_IFF_SHORTCUT, "shortcut"}, {NHRP_IFF_REDIRECT, "redirect"}, {NHRP_IFF_REG_NO_UNIQUE, "registration no-unique"}, {0}}; static int nhrp_vty_return(struct vty *vty, int ret) { static const char *const errmsgs[] = { [NHRP_ERR_FAIL] = "Command failed", [NHRP_ERR_NO_MEMORY] = "Out of memory", [NHRP_ERR_UNSUPPORTED_INTERFACE] = "NHRP not supported on this interface", [NHRP_ERR_NHRP_NOT_ENABLED] = "NHRP not enabled (set 'nhrp network-id' first)", [NHRP_ERR_ENTRY_EXISTS] = "Entry exists already", [NHRP_ERR_ENTRY_NOT_FOUND] = "Entry not found", [NHRP_ERR_PROTOCOL_ADDRESS_MISMATCH] = "Protocol address family does not match command (ip/ipv6 mismatch)", }; const char *str = NULL; char buf[256]; if (ret == NHRP_OK) return CMD_SUCCESS; if (ret > 0 && ret <= NHRP_ERR_MAX) if (errmsgs[ret]) str = errmsgs[ret]; if (!str) { str = buf; snprintf(buf, sizeof(buf), "Unknown error %d", ret); } vty_out(vty, "%% %s\n", str); return CMD_WARNING_CONFIG_FAILED; ; } static int toggle_flag(struct vty *vty, const struct message *flag_desc, const char *name, int on_off, unsigned *flags) { int i; for (i = 0; flag_desc[i].str != NULL; i++) { if (strcmp(flag_desc[i].str, name) != 0) continue; if (on_off) *flags |= flag_desc[i].key; else *flags &= ~flag_desc[i].key; return CMD_SUCCESS; } vty_out(vty, "%% Invalid value %s\n", name); return CMD_WARNING_CONFIG_FAILED; ; } #ifndef NO_DEBUG DEFUN_NOSH(show_debugging_nhrp, show_debugging_nhrp_cmd, "show debugging [nhrp]", SHOW_STR "Debugging information\n" "NHRP configuration\n") { int i; vty_out(vty, "NHRP debugging status:\n"); for (i = 0; debug_flags_desc[i].str != NULL; i++) { if (debug_flags_desc[i].key == NHRP_DEBUG_ALL) continue; if (!(debug_flags_desc[i].key & debug_flags)) continue; vty_out(vty, " NHRP %s debugging is on\n", debug_flags_desc[i].str); } return CMD_SUCCESS; } DEFUN(debug_nhrp, debug_nhrp_cmd, "debug nhrp " NHRP_DEBUG_FLAGS_CMD, "Enable debug messages for specific or all parts.\n" "NHRP information\n" NHRP_DEBUG_FLAGS_STR) { return toggle_flag(vty, debug_flags_desc, argv[2]->text, 1, &debug_flags); } DEFUN(no_debug_nhrp, no_debug_nhrp_cmd, "no debug nhrp " NHRP_DEBUG_FLAGS_CMD, NO_STR "Disable debug messages for specific or all parts.\n" "NHRP information\n" NHRP_DEBUG_FLAGS_STR) { return toggle_flag(vty, debug_flags_desc, argv[3]->text, 0, &debug_flags); } #endif /* NO_DEBUG */ static int nhrp_config_write(struct vty *vty) { #ifndef NO_DEBUG if (debug_flags == NHRP_DEBUG_ALL) { vty_out(vty, "debug nhrp all\n"); } else { int i; for (i = 0; debug_flags_desc[i].str != NULL; i++) { if (debug_flags_desc[i].key == NHRP_DEBUG_ALL) continue; if (!(debug_flags & debug_flags_desc[i].key)) continue; vty_out(vty, "debug nhrp %s\n", debug_flags_desc[i].str); } } vty_out(vty, "!\n"); #endif /* NO_DEBUG */ if (nhrp_event_socket_path) { vty_out(vty, "nhrp event socket %s\n", nhrp_event_socket_path); } if (netlink_nflog_group) { vty_out(vty, "nhrp nflog-group %d\n", netlink_nflog_group); } return 0; } #define IP_STR "IP information\n" #define IPV6_STR "IPv6 information\n" #define AFI_CMD "" #define AFI_STR IP_STR IPV6_STR #define NHRP_STR "Next Hop Resolution Protocol functions\n" static afi_t cmd_to_afi(const struct cmd_token *tok) { return strcmp(tok->text, "ipv6") == 0 ? AFI_IP6 : AFI_IP; } static const char *afi_to_cmd(afi_t afi) { if (afi == AFI_IP6) return "ipv6"; return "ip"; } DEFUN(nhrp_event_socket, nhrp_event_socket_cmd, "nhrp event socket SOCKET", NHRP_STR "Event Manager commands\n" "Event Manager unix socket path\n" "Unix path for the socket\n") { evmgr_set_socket(argv[3]->arg); return CMD_SUCCESS; } DEFUN(no_nhrp_event_socket, no_nhrp_event_socket_cmd, "no nhrp event socket [SOCKET]", NO_STR NHRP_STR "Event Manager commands\n" "Event Manager unix socket path\n" "Unix path for the socket\n") { evmgr_set_socket(NULL); return CMD_SUCCESS; } DEFUN(nhrp_nflog_group, nhrp_nflog_group_cmd, "nhrp nflog-group (1-65535)", NHRP_STR "Specify NFLOG group number\n" "NFLOG group number\n") { uint32_t nfgroup; nfgroup = strtoul(argv[2]->arg, NULL, 10); netlink_set_nflog_group(nfgroup); return CMD_SUCCESS; } DEFUN(no_nhrp_nflog_group, no_nhrp_nflog_group_cmd, "no nhrp nflog-group [(1-65535)]", NO_STR NHRP_STR "Specify NFLOG group number\n" "NFLOG group number\n") { netlink_set_nflog_group(0); return CMD_SUCCESS; } DEFUN(tunnel_protection, tunnel_protection_cmd, "tunnel protection vici profile PROFILE [fallback-profile FALLBACK]", "NHRP/GRE integration\n" "IPsec protection\n" "VICI (StrongSwan)\n" "IPsec profile\n" "IPsec profile name\n" "Fallback IPsec profile\n" "Fallback IPsec profile name\n") { VTY_DECLVAR_CONTEXT(interface, ifp); nhrp_interface_set_protection(ifp, argv[4]->arg, argc > 6 ? argv[6]->arg : NULL); return CMD_SUCCESS; } DEFUN(no_tunnel_protection, no_tunnel_protection_cmd, "no tunnel protection", NO_STR "NHRP/GRE integration\n" "IPsec protection\n") { VTY_DECLVAR_CONTEXT(interface, ifp); nhrp_interface_set_protection(ifp, NULL, NULL); return CMD_SUCCESS; } DEFUN(tunnel_source, tunnel_source_cmd, "tunnel source INTERFACE", "NHRP/GRE integration\n" "Tunnel device binding tracking\n" "Interface name\n") { VTY_DECLVAR_CONTEXT(interface, ifp); nhrp_interface_set_source(ifp, argv[2]->arg); return CMD_SUCCESS; } DEFUN(no_tunnel_source, no_tunnel_source_cmd, "no tunnel source", "NHRP/GRE integration\n" "Tunnel device binding tracking\n" "Interface name\n") { VTY_DECLVAR_CONTEXT(interface, ifp); nhrp_interface_set_source(ifp, NULL); return CMD_SUCCESS; } DEFUN(if_nhrp_network_id, if_nhrp_network_id_cmd, AFI_CMD " nhrp network-id (1-4294967295)", AFI_STR NHRP_STR "Enable NHRP and specify network-id\n" "System local ID to specify interface group\n") { VTY_DECLVAR_CONTEXT(interface, ifp); struct nhrp_interface *nifp = ifp->info; afi_t afi = cmd_to_afi(argv[0]); nifp->afi[afi].network_id = strtoul(argv[3]->arg, NULL, 10); nhrp_interface_update(ifp); return CMD_SUCCESS; } DEFUN(if_no_nhrp_network_id, if_no_nhrp_network_id_cmd, "no " AFI_CMD " nhrp network-id [(1-4294967295)]", NO_STR AFI_STR NHRP_STR "Enable NHRP and specify network-id\n" "System local ID to specify interface group\n") { VTY_DECLVAR_CONTEXT(interface, ifp); struct nhrp_interface *nifp = ifp->info; afi_t afi = cmd_to_afi(argv[1]); nifp->afi[afi].network_id = 0; nhrp_interface_update(ifp); return CMD_SUCCESS; } DEFUN(if_nhrp_flags, if_nhrp_flags_cmd, AFI_CMD " nhrp ", AFI_STR NHRP_STR "Allow shortcut establishment\n" "Send redirect notifications\n") { VTY_DECLVAR_CONTEXT(interface, ifp); struct nhrp_interface *nifp = ifp->info; afi_t afi = cmd_to_afi(argv[0]); return toggle_flag(vty, interface_flags_desc, argv[2]->text, 1, &nifp->afi[afi].flags); } DEFUN(if_no_nhrp_flags, if_no_nhrp_flags_cmd, "no " AFI_CMD " nhrp ", NO_STR AFI_STR NHRP_STR "Allow shortcut establishment\n" "Send redirect notifications\n") { VTY_DECLVAR_CONTEXT(interface, ifp); struct nhrp_interface *nifp = ifp->info; afi_t afi = cmd_to_afi(argv[1]); return toggle_flag(vty, interface_flags_desc, argv[3]->text, 0, &nifp->afi[afi].flags); } DEFUN(if_nhrp_reg_flags, if_nhrp_reg_flags_cmd, AFI_CMD " nhrp registration no-unique", AFI_STR NHRP_STR "Registration configuration\n" "Don't set unique flag\n") { VTY_DECLVAR_CONTEXT(interface, ifp); struct nhrp_interface *nifp = ifp->info; afi_t afi = cmd_to_afi(argv[0]); char name[256]; snprintf(name, sizeof(name), "registration %s", argv[3]->text); return toggle_flag(vty, interface_flags_desc, name, 1, &nifp->afi[afi].flags); } DEFUN(if_no_nhrp_reg_flags, if_no_nhrp_reg_flags_cmd, "no " AFI_CMD " nhrp registration no-unique", NO_STR AFI_STR NHRP_STR "Registration configuration\n" "Don't set unique flag\n") { VTY_DECLVAR_CONTEXT(interface, ifp); struct nhrp_interface *nifp = ifp->info; afi_t afi = cmd_to_afi(argv[1]); char name[256]; snprintf(name, sizeof(name), "registration %s", argv[4]->text); return toggle_flag(vty, interface_flags_desc, name, 0, &nifp->afi[afi].flags); } DEFUN(if_nhrp_holdtime, if_nhrp_holdtime_cmd, AFI_CMD " nhrp holdtime (1-65000)", AFI_STR NHRP_STR "Specify NBMA address validity time\n" "Time in seconds that NBMA addresses are advertised valid\n") { VTY_DECLVAR_CONTEXT(interface, ifp); struct nhrp_interface *nifp = ifp->info; afi_t afi = cmd_to_afi(argv[0]); nifp->afi[afi].holdtime = strtoul(argv[3]->arg, NULL, 10); nhrp_interface_update(ifp); return CMD_SUCCESS; } DEFUN(if_no_nhrp_holdtime, if_no_nhrp_holdtime_cmd, "no " AFI_CMD " nhrp holdtime [(1-65000)]", NO_STR AFI_STR NHRP_STR "Specify NBMA address validity time\n" "Time in seconds that NBMA addresses are advertised valid\n") { VTY_DECLVAR_CONTEXT(interface, ifp); struct nhrp_interface *nifp = ifp->info; afi_t afi = cmd_to_afi(argv[1]); nifp->afi[afi].holdtime = NHRPD_DEFAULT_HOLDTIME; nhrp_interface_update(ifp); return CMD_SUCCESS; } DEFUN(if_nhrp_mtu, if_nhrp_mtu_cmd, "ip nhrp mtu <(576-1500)|opennhrp>", IP_STR NHRP_STR "Configure NHRP advertised MTU\n" "MTU value\n" "Advertise bound interface MTU similar to OpenNHRP\n") { VTY_DECLVAR_CONTEXT(interface, ifp); struct nhrp_interface *nifp = ifp->info; if (argv[3]->arg[0] == 'o') { nifp->afi[AFI_IP].configured_mtu = -1; } else { nifp->afi[AFI_IP].configured_mtu = strtoul(argv[3]->arg, NULL, 10); } nhrp_interface_update_mtu(ifp, AFI_IP); return CMD_SUCCESS; } DEFUN(if_no_nhrp_mtu, if_no_nhrp_mtu_cmd, "no ip nhrp mtu [(576-1500)|opennhrp]", NO_STR IP_STR NHRP_STR "Configure NHRP advertised MTU\n" "MTU value\n" "Advertise bound interface MTU similar to OpenNHRP\n") { VTY_DECLVAR_CONTEXT(interface, ifp); struct nhrp_interface *nifp = ifp->info; nifp->afi[AFI_IP].configured_mtu = 0; nhrp_interface_update_mtu(ifp, AFI_IP); return CMD_SUCCESS; } DEFUN(if_nhrp_map, if_nhrp_map_cmd, AFI_CMD " nhrp map ", AFI_STR NHRP_STR "Nexthop Server configuration\n" "IPv4 protocol address\n" "IPv6 protocol address\n" "IPv4 NBMA address\n" "Handle protocol address locally\n") { VTY_DECLVAR_CONTEXT(interface, ifp); afi_t afi = cmd_to_afi(argv[0]); union sockunion proto_addr, nbma_addr; struct nhrp_cache *c; if (str2sockunion(argv[3]->arg, &proto_addr) < 0 || afi2family(afi) != sockunion_family(&proto_addr)) return nhrp_vty_return(vty, NHRP_ERR_PROTOCOL_ADDRESS_MISMATCH); c = nhrp_cache_get(ifp, &proto_addr, 1); if (!c) return nhrp_vty_return(vty, NHRP_ERR_FAIL); c->map = 1; if (strmatch(argv[4]->text, "local")) { nhrp_cache_update_binding(c, NHRP_CACHE_LOCAL, 0, NULL, 0, NULL); } else { if (str2sockunion(argv[4]->arg, &nbma_addr) < 0) return nhrp_vty_return(vty, NHRP_ERR_FAIL); nhrp_cache_update_binding(c, NHRP_CACHE_STATIC, 0, nhrp_peer_get(ifp, &nbma_addr), 0, NULL); } return CMD_SUCCESS; } DEFUN(if_no_nhrp_map, if_no_nhrp_map_cmd, "no " AFI_CMD " nhrp map ", NO_STR AFI_STR NHRP_STR "Nexthop Server configuration\n" "IPv4 protocol address\n" "IPv6 protocol address\n") { VTY_DECLVAR_CONTEXT(interface, ifp); afi_t afi = cmd_to_afi(argv[1]); union sockunion proto_addr, nbma_addr; struct nhrp_cache *c; if (str2sockunion(argv[4]->arg, &proto_addr) < 0 || afi2family(afi) != sockunion_family(&proto_addr)) return nhrp_vty_return(vty, NHRP_ERR_PROTOCOL_ADDRESS_MISMATCH); c = nhrp_cache_get(ifp, &proto_addr, 0); if (!c || !c->map) return nhrp_vty_return(vty, NHRP_ERR_ENTRY_NOT_FOUND); nhrp_cache_update_binding(c, c->cur.type, -1, nhrp_peer_get(ifp, &nbma_addr), 0, NULL); return CMD_SUCCESS; } DEFUN(if_nhrp_nhs, if_nhrp_nhs_cmd, AFI_CMD " nhrp nhs nbma ", AFI_STR NHRP_STR "Nexthop Server configuration\n" "IPv4 protocol address\n" "IPv6 protocol address\n" "Automatic detection of protocol address\n" "NBMA address\n" "IPv4 NBMA address\n" "Fully qualified domain name for NBMA address(es)\n") { VTY_DECLVAR_CONTEXT(interface, ifp); afi_t afi = cmd_to_afi(argv[0]); union sockunion proto_addr; int ret; if (str2sockunion(argv[3]->arg, &proto_addr) < 0) sockunion_family(&proto_addr) = AF_UNSPEC; ret = nhrp_nhs_add(ifp, afi, &proto_addr, argv[5]->arg); return nhrp_vty_return(vty, ret); } DEFUN(if_no_nhrp_nhs, if_no_nhrp_nhs_cmd, "no " AFI_CMD " nhrp nhs nbma ", NO_STR AFI_STR NHRP_STR "Nexthop Server configuration\n" "IPv4 protocol address\n" "IPv6 protocol address\n" "Automatic detection of protocol address\n" "NBMA address\n" "IPv4 NBMA address\n" "Fully qualified domain name for NBMA address(es)\n") { VTY_DECLVAR_CONTEXT(interface, ifp); afi_t afi = cmd_to_afi(argv[1]); union sockunion proto_addr; int ret; if (str2sockunion(argv[4]->arg, &proto_addr) < 0) sockunion_family(&proto_addr) = AF_UNSPEC; ret = nhrp_nhs_del(ifp, afi, &proto_addr, argv[6]->arg); return nhrp_vty_return(vty, ret); } struct info_ctx { struct vty *vty; afi_t afi; int count; }; static void show_ip_nhrp_cache(struct nhrp_cache *c, void *pctx) { struct info_ctx *ctx = pctx; struct vty *vty = ctx->vty; char buf[2][SU_ADDRSTRLEN]; if (ctx->afi != family2afi(sockunion_family(&c->remote_addr))) return; if (!ctx->count) { vty_out(vty, "%-8s %-8s %-24s %-24s %-6s %s\n", "Iface", "Type", "Protocol", "NBMA", "Flags", "Identity"); } ctx->count++; vty_out(ctx->vty, "%-8s %-8s %-24s %-24s %c%c%c %s\n", c->ifp->name, nhrp_cache_type_str[c->cur.type], sockunion2str(&c->remote_addr, buf[0], sizeof buf[0]), c->cur.peer ? sockunion2str(&c->cur.peer->vc->remote.nbma, buf[1], sizeof buf[1]) : "-", c->used ? 'U' : ' ', c->t_timeout ? 'T' : ' ', c->t_auth ? 'A' : ' ', c->cur.peer ? c->cur.peer->vc->remote.id : "-"); } static void show_ip_nhrp_nhs(struct nhrp_nhs *n, struct nhrp_registration *reg, void *pctx) { struct info_ctx *ctx = pctx; struct vty *vty = ctx->vty; char buf[2][SU_ADDRSTRLEN]; if (!ctx->count) { vty_out(vty, "%-8s %-24s %-16s %-16s\n", "Iface", "FQDN", "NBMA", "Protocol"); } ctx->count++; vty_out(vty, "%-8s %-24s %-16s %-16s\n", n->ifp->name, n->nbma_fqdn, (reg && reg->peer) ? sockunion2str(®->peer->vc->remote.nbma, buf[0], sizeof buf[0]) : "-", sockunion2str(reg ? ®->proto_addr : &n->proto_addr, buf[1], sizeof buf[1])); } static void show_ip_nhrp_shortcut(struct nhrp_shortcut *s, void *pctx) { struct info_ctx *ctx = pctx; struct nhrp_cache *c; struct vty *vty = ctx->vty; char buf1[PREFIX_STRLEN], buf2[SU_ADDRSTRLEN]; if (!ctx->count) { vty_out(vty, "%-8s %-24s %-24s %s\n", "Type", "Prefix", "Via", "Identity"); } ctx->count++; c = s->cache; vty_out(ctx->vty, "%-8s %-24s %-24s %s\n", nhrp_cache_type_str[s->type], prefix2str(s->p, buf1, sizeof buf1), c ? sockunion2str(&c->remote_addr, buf2, sizeof buf2) : "", (c && c->cur.peer) ? c->cur.peer->vc->remote.id : ""); } static void show_ip_opennhrp_cache(struct nhrp_cache *c, void *pctx) { struct info_ctx *ctx = pctx; char buf[SU_ADDRSTRLEN]; if (ctx->afi != family2afi(sockunion_family(&c->remote_addr))) return; vty_out(ctx->vty, "Type: %s\n" "Flags:%s%s\n" "Protocol-Address: %s/%zu\n", nhrp_cache_type_str[c->cur.type], (c->cur.peer && c->cur.peer->online) ? " up" : "", c->used ? " used" : "", sockunion2str(&c->remote_addr, buf, sizeof buf), 8 * family2addrsize(sockunion_family(&c->remote_addr))); if (c->cur.peer) { vty_out(ctx->vty, "NBMA-Address: %s\n", sockunion2str(&c->cur.peer->vc->remote.nbma, buf, sizeof buf)); } if (sockunion_family(&c->cur.remote_nbma_natoa) != AF_UNSPEC) { vty_out(ctx->vty, "NBMA-NAT-OA-Address: %s\n", sockunion2str(&c->cur.remote_nbma_natoa, buf, sizeof buf)); } vty_out(ctx->vty, "\n\n"); } DEFUN(show_ip_nhrp, show_ip_nhrp_cmd, "show " AFI_CMD " nhrp [cache|nhs|shortcut|opennhrp]", SHOW_STR AFI_STR "NHRP information\n" "Forwarding cache information\n" "Next hop server information\n" "Shortcut information\n" "opennhrpctl style cache dump\n") { struct vrf *vrf = vrf_lookup_by_id(VRF_DEFAULT); struct interface *ifp; struct info_ctx ctx = { .vty = vty, .afi = cmd_to_afi(argv[1]), }; if (argc <= 3 || argv[3]->text[0] == 'c') { FOR_ALL_INTERFACES (vrf, ifp) nhrp_cache_foreach(ifp, show_ip_nhrp_cache, &ctx); } else if (argv[3]->text[0] == 'n') { FOR_ALL_INTERFACES (vrf, ifp) nhrp_nhs_foreach(ifp, ctx.afi, show_ip_nhrp_nhs, &ctx); } else if (argv[3]->text[0] == 's') { nhrp_shortcut_foreach(ctx.afi, show_ip_nhrp_shortcut, &ctx); } else { vty_out(vty, "Status: ok\n\n"); ctx.count++; FOR_ALL_INTERFACES (vrf, ifp) nhrp_cache_foreach(ifp, show_ip_opennhrp_cache, &ctx); } if (!ctx.count) { vty_out(vty, "%% No entries\n"); return CMD_WARNING; } return CMD_SUCCESS; } static void show_dmvpn_entry(struct nhrp_vc *vc, void *ctx) { struct vty *vty = ctx; char buf[2][SU_ADDRSTRLEN]; vty_out(vty, "%-24s %-24s %c %-4d %-24s\n", sockunion2str(&vc->local.nbma, buf[0], sizeof buf[0]), sockunion2str(&vc->remote.nbma, buf[1], sizeof buf[1]), notifier_active(&vc->notifier_list) ? 'n' : ' ', vc->ipsec, vc->remote.id); } DEFUN(show_dmvpn, show_dmvpn_cmd, "show dmvpn", SHOW_STR "DMVPN information\n") { vty_out(vty, "%-24s %-24s %-6s %-4s %-24s\n", "Src", "Dst", "Flags", "SAs", "Identity"); nhrp_vc_foreach(show_dmvpn_entry, vty); return CMD_SUCCESS; } static void clear_nhrp_cache(struct nhrp_cache *c, void *data) { struct info_ctx *ctx = data; if (c->cur.type <= NHRP_CACHE_CACHED) { nhrp_cache_update_binding(c, c->cur.type, -1, NULL, 0, NULL); ctx->count++; } } static void clear_nhrp_shortcut(struct nhrp_shortcut *s, void *data) { struct info_ctx *ctx = data; nhrp_shortcut_purge(s, 1); ctx->count++; } DEFUN(clear_nhrp, clear_nhrp_cmd, "clear " AFI_CMD " nhrp ", CLEAR_STR AFI_STR NHRP_STR "Dynamic cache entries\n" "Shortcut entries\n") { struct vrf *vrf = vrf_lookup_by_id(VRF_DEFAULT); struct interface *ifp; struct info_ctx ctx = { .vty = vty, .afi = cmd_to_afi(argv[1]), .count = 0, }; if (argc <= 3 || argv[3]->text[0] == 'c') { FOR_ALL_INTERFACES (vrf, ifp) nhrp_cache_foreach(ifp, clear_nhrp_cache, &ctx); } else { nhrp_shortcut_foreach(ctx.afi, clear_nhrp_shortcut, &ctx); } if (!ctx.count) { vty_out(vty, "%% No entries\n"); return CMD_WARNING; } vty_out(vty, "%% %d entries cleared\n", ctx.count); return CMD_SUCCESS; } struct write_map_ctx { struct vty *vty; int family; const char *aficmd; }; static void interface_config_write_nhrp_map(struct nhrp_cache *c, void *data) { struct write_map_ctx *ctx = data; struct vty *vty = ctx->vty; char buf[2][SU_ADDRSTRLEN]; if (!c->map) return; if (sockunion_family(&c->remote_addr) != ctx->family) return; vty_out(vty, " %s nhrp map %s %s\n", ctx->aficmd, sockunion2str(&c->remote_addr, buf[0], sizeof buf[0]), c->cur.type == NHRP_CACHE_LOCAL ? "local" : sockunion2str(&c->cur.peer->vc->remote.nbma, buf[1], sizeof buf[1])); } static int interface_config_write(struct vty *vty) { struct vrf *vrf = vrf_lookup_by_id(VRF_DEFAULT); struct write_map_ctx mapctx; struct interface *ifp; struct nhrp_interface *nifp; struct nhrp_nhs *nhs; const char *aficmd; afi_t afi; char buf[SU_ADDRSTRLEN]; int i; FOR_ALL_INTERFACES (vrf, ifp) { vty_frame(vty, "interface %s\n", ifp->name); if (ifp->desc) vty_out(vty, " description %s\n", ifp->desc); nifp = ifp->info; if (nifp->ipsec_profile) { vty_out(vty, " tunnel protection vici profile %s", nifp->ipsec_profile); if (nifp->ipsec_fallback_profile) vty_out(vty, " fallback-profile %s", nifp->ipsec_fallback_profile); vty_out(vty, "\n"); } if (nifp->source) vty_out(vty, " tunnel source %s\n", nifp->source); for (afi = 0; afi < AFI_MAX; afi++) { struct nhrp_afi_data *ad = &nifp->afi[afi]; aficmd = afi_to_cmd(afi); if (ad->network_id) vty_out(vty, " %s nhrp network-id %u\n", aficmd, ad->network_id); if (ad->holdtime != NHRPD_DEFAULT_HOLDTIME) vty_out(vty, " %s nhrp holdtime %u\n", aficmd, ad->holdtime); if (ad->configured_mtu < 0) vty_out(vty, " %s nhrp mtu opennhrp\n", aficmd); else if (ad->configured_mtu) vty_out(vty, " %s nhrp mtu %u\n", aficmd, ad->configured_mtu); for (i = 0; interface_flags_desc[i].str != NULL; i++) { if (!(ad->flags & interface_flags_desc[i].key)) continue; vty_out(vty, " %s nhrp %s\n", aficmd, interface_flags_desc[i].str); } mapctx = (struct write_map_ctx){ .vty = vty, .family = afi2family(afi), .aficmd = aficmd, }; nhrp_cache_foreach(ifp, interface_config_write_nhrp_map, &mapctx); list_for_each_entry(nhs, &ad->nhslist_head, nhslist_entry) { vty_out(vty, " %s nhrp nhs %s nbma %s\n", aficmd, sockunion_family(&nhs->proto_addr) == AF_UNSPEC ? "dynamic" : sockunion2str( &nhs->proto_addr, buf, sizeof buf), nhs->nbma_fqdn); } } vty_endframe(vty, "!\n"); } return 0; } void nhrp_config_init(void) { install_node(&zebra_node, nhrp_config_write); install_default(ZEBRA_NODE); /* access-list commands */ access_list_init(); /* global commands */ install_element(VIEW_NODE, &show_debugging_nhrp_cmd); install_element(VIEW_NODE, &show_ip_nhrp_cmd); install_element(VIEW_NODE, &show_dmvpn_cmd); install_element(ENABLE_NODE, &clear_nhrp_cmd); install_element(ENABLE_NODE, &debug_nhrp_cmd); install_element(ENABLE_NODE, &no_debug_nhrp_cmd); install_element(CONFIG_NODE, &debug_nhrp_cmd); install_element(CONFIG_NODE, &no_debug_nhrp_cmd); install_element(CONFIG_NODE, &nhrp_event_socket_cmd); install_element(CONFIG_NODE, &no_nhrp_event_socket_cmd); install_element(CONFIG_NODE, &nhrp_nflog_group_cmd); install_element(CONFIG_NODE, &no_nhrp_nflog_group_cmd); /* interface specific commands */ install_node(&nhrp_interface_node, interface_config_write); if_cmd_init(); install_element(INTERFACE_NODE, &tunnel_protection_cmd); install_element(INTERFACE_NODE, &no_tunnel_protection_cmd); install_element(INTERFACE_NODE, &tunnel_source_cmd); install_element(INTERFACE_NODE, &no_tunnel_source_cmd); install_element(INTERFACE_NODE, &if_nhrp_network_id_cmd); install_element(INTERFACE_NODE, &if_no_nhrp_network_id_cmd); install_element(INTERFACE_NODE, &if_nhrp_holdtime_cmd); install_element(INTERFACE_NODE, &if_no_nhrp_holdtime_cmd); install_element(INTERFACE_NODE, &if_nhrp_mtu_cmd); install_element(INTERFACE_NODE, &if_no_nhrp_mtu_cmd); install_element(INTERFACE_NODE, &if_nhrp_flags_cmd); install_element(INTERFACE_NODE, &if_no_nhrp_flags_cmd); install_element(INTERFACE_NODE, &if_nhrp_reg_flags_cmd); install_element(INTERFACE_NODE, &if_no_nhrp_reg_flags_cmd); install_element(INTERFACE_NODE, &if_nhrp_map_cmd); install_element(INTERFACE_NODE, &if_no_nhrp_map_cmd); install_element(INTERFACE_NODE, &if_nhrp_nhs_cmd); install_element(INTERFACE_NODE, &if_no_nhrp_nhs_cmd); } frr-7.2.1/nhrpd/nhrpd.h0000644000000000000000000003054613610377563011623 00000000000000/* NHRP daemon internal structures and function prototypes * Copyright (c) 2014-2015 Timo Teräs * * This file is free software: you may copy, redistribute and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation, either version 2 of the License, or * (at your option) any later version. */ #ifndef NHRPD_H #define NHRPD_H #include "list.h" #include "zbuf.h" #include "zclient.h" #include "debug.h" #include "memory.h" #include "resolver.h" DECLARE_MGROUP(NHRPD) #define NHRPD_DEFAULT_HOLDTIME 7200 #define NHRP_VTY_PORT 2610 #define NHRP_DEFAULT_CONFIG "nhrpd.conf" extern struct thread_master *master; enum { NHRP_OK = 0, NHRP_ERR_FAIL, NHRP_ERR_NO_MEMORY, NHRP_ERR_UNSUPPORTED_INTERFACE, NHRP_ERR_NHRP_NOT_ENABLED, NHRP_ERR_ENTRY_EXISTS, NHRP_ERR_ENTRY_NOT_FOUND, NHRP_ERR_PROTOCOL_ADDRESS_MISMATCH, __NHRP_ERR_MAX }; #define NHRP_ERR_MAX (__NHRP_ERR_MAX - 1) struct notifier_block; typedef void (*notifier_fn_t)(struct notifier_block *, unsigned long); struct notifier_block { struct list_head notifier_entry; notifier_fn_t action; }; struct notifier_list { struct list_head notifier_head; }; #define NOTIFIER_LIST_INITIALIZER(l) \ { \ .notifier_head = LIST_INITIALIZER((l)->notifier_head) \ } static inline void notifier_init(struct notifier_list *l) { list_init(&l->notifier_head); } static inline void notifier_add(struct notifier_block *n, struct notifier_list *l, notifier_fn_t action) { n->action = action; list_add_tail(&n->notifier_entry, &l->notifier_head); } static inline void notifier_del(struct notifier_block *n) { list_del(&n->notifier_entry); } static inline void notifier_call(struct notifier_list *l, int cmd) { struct notifier_block *n, *nn; list_for_each_entry_safe(n, nn, &l->notifier_head, notifier_entry) n->action(n, cmd); } static inline int notifier_active(struct notifier_list *l) { return !list_empty(&l->notifier_head); } void nhrp_zebra_init(void); void nhrp_zebra_terminate(void); struct zbuf; struct nhrp_vc; struct nhrp_cache; struct nhrp_nhs; struct nhrp_interface; #define MAX_ID_LENGTH 64 #define MAX_CERT_LENGTH 2048 enum nhrp_notify_type { NOTIFY_INTERFACE_UP, NOTIFY_INTERFACE_DOWN, NOTIFY_INTERFACE_CHANGED, NOTIFY_INTERFACE_ADDRESS_CHANGED, NOTIFY_INTERFACE_NBMA_CHANGED, NOTIFY_INTERFACE_MTU_CHANGED, NOTIFY_VC_IPSEC_CHANGED, NOTIFY_VC_IPSEC_UPDATE_NBMA, NOTIFY_PEER_UP, NOTIFY_PEER_DOWN, NOTIFY_PEER_IFCONFIG_CHANGED, NOTIFY_PEER_MTU_CHANGED, NOTIFY_PEER_NBMA_CHANGING, NOTIFY_CACHE_UP, NOTIFY_CACHE_DOWN, NOTIFY_CACHE_DELETE, NOTIFY_CACHE_USED, NOTIFY_CACHE_BINDING_CHANGE, }; struct nhrp_vc { struct notifier_list notifier_list; uint8_t ipsec; uint8_t updating; uint8_t abort_migration; struct nhrp_vc_peer { union sockunion nbma; char id[MAX_ID_LENGTH]; uint16_t certlen; uint8_t cert[MAX_CERT_LENGTH]; } local, remote; }; enum nhrp_route_type { NHRP_ROUTE_BLACKHOLE, NHRP_ROUTE_LOCAL, NHRP_ROUTE_NBMA_NEXTHOP, NHRP_ROUTE_OFF_NBMA, }; struct nhrp_peer { unsigned int ref; unsigned online : 1; unsigned requested : 1; unsigned fallback_requested : 1; unsigned prio : 1; struct notifier_list notifier_list; struct interface *ifp; struct nhrp_vc *vc; struct thread *t_fallback; struct notifier_block vc_notifier, ifp_notifier; }; struct nhrp_packet_parser { struct interface *ifp; struct nhrp_afi_data *if_ad; struct nhrp_peer *peer; struct zbuf *pkt; struct zbuf payload; struct zbuf extensions; struct nhrp_packet_header *hdr; enum nhrp_route_type route_type; struct prefix route_prefix; union sockunion src_nbma, src_proto, dst_proto; }; struct nhrp_reqid_pool { struct hash *reqid_hash; uint32_t next_request_id; }; struct nhrp_reqid { uint32_t request_id; void (*cb)(struct nhrp_reqid *, void *); }; extern struct nhrp_reqid_pool nhrp_packet_reqid; extern struct nhrp_reqid_pool nhrp_event_reqid; enum nhrp_cache_type { NHRP_CACHE_INVALID = 0, NHRP_CACHE_INCOMPLETE, NHRP_CACHE_NEGATIVE, NHRP_CACHE_CACHED, NHRP_CACHE_DYNAMIC, NHRP_CACHE_NHS, NHRP_CACHE_STATIC, NHRP_CACHE_LOCAL, NHRP_CACHE_NUM_TYPES }; extern const char *const nhrp_cache_type_str[]; extern unsigned long nhrp_cache_counts[NHRP_CACHE_NUM_TYPES]; struct nhrp_cache { struct interface *ifp; union sockunion remote_addr; unsigned map : 1; unsigned used : 1; unsigned route_installed : 1; unsigned nhrp_route_installed : 1; struct notifier_block peer_notifier; struct notifier_block newpeer_notifier; struct notifier_list notifier_list; struct nhrp_reqid eventid; struct thread *t_timeout; struct thread *t_auth; struct { enum nhrp_cache_type type; union sockunion remote_nbma_natoa; struct nhrp_peer *peer; time_t expires; uint32_t mtu; } cur, new; }; struct nhrp_shortcut { struct prefix *p; union sockunion addr; struct nhrp_reqid reqid; struct thread *t_timer; enum nhrp_cache_type type; unsigned int holding_time; unsigned route_installed : 1; unsigned expiring : 1; struct nhrp_cache *cache; struct notifier_block cache_notifier; }; struct nhrp_nhs { struct interface *ifp; struct list_head nhslist_entry; unsigned hub : 1; afi_t afi; union sockunion proto_addr; const char *nbma_fqdn; /* IP-address or FQDN */ struct thread *t_resolve; struct resolver_query dns_resolve; struct list_head reglist_head; }; struct nhrp_registration { struct list_head reglist_entry; struct thread *t_register; struct nhrp_nhs *nhs; struct nhrp_reqid reqid; unsigned int timeout; unsigned mark : 1; union sockunion proto_addr; struct nhrp_peer *peer; struct notifier_block peer_notifier; }; #define NHRP_IFF_SHORTCUT 0x0001 #define NHRP_IFF_REDIRECT 0x0002 #define NHRP_IFF_REG_NO_UNIQUE 0x0100 struct nhrp_interface { struct interface *ifp; unsigned enabled : 1; char *ipsec_profile, *ipsec_fallback_profile, *source; union sockunion nbma; union sockunion nat_nbma; unsigned int linkidx; uint32_t grekey; struct hash *peer_hash; struct hash *cache_hash; struct notifier_list notifier_list; struct interface *nbmaifp; struct notifier_block nbmanifp_notifier; struct nhrp_afi_data { unsigned flags; unsigned short configured : 1; union sockunion addr; uint32_t network_id; short configured_mtu; unsigned short mtu; unsigned int holdtime; struct list_head nhslist_head; } afi[AFI_MAX]; }; extern struct zebra_privs_t nhrpd_privs; int sock_open_unix(const char *path); void nhrp_interface_init(void); void nhrp_interface_update(struct interface *ifp); void nhrp_interface_update_mtu(struct interface *ifp, afi_t afi); int nhrp_interface_add(ZAPI_CALLBACK_ARGS); int nhrp_interface_delete(ZAPI_CALLBACK_ARGS); int nhrp_interface_up(ZAPI_CALLBACK_ARGS); int nhrp_interface_down(ZAPI_CALLBACK_ARGS); int nhrp_interface_address_add(ZAPI_CALLBACK_ARGS); int nhrp_interface_address_delete(ZAPI_CALLBACK_ARGS); void nhrp_interface_notify_add(struct interface *ifp, struct notifier_block *n, notifier_fn_t fn); void nhrp_interface_notify_del(struct interface *ifp, struct notifier_block *n); void nhrp_interface_set_protection(struct interface *ifp, const char *profile, const char *fallback_profile); void nhrp_interface_set_source(struct interface *ifp, const char *ifname); int nhrp_nhs_add(struct interface *ifp, afi_t afi, union sockunion *proto_addr, const char *nbma_fqdn); int nhrp_nhs_del(struct interface *ifp, afi_t afi, union sockunion *proto_addr, const char *nbma_fqdn); int nhrp_nhs_free(struct nhrp_nhs *nhs); void nhrp_nhs_terminate(void); void nhrp_nhs_foreach(struct interface *ifp, afi_t afi, void (*cb)(struct nhrp_nhs *, struct nhrp_registration *, void *), void *ctx); void nhrp_route_update_nhrp(const struct prefix *p, struct interface *ifp); void nhrp_route_announce(int add, enum nhrp_cache_type type, const struct prefix *p, struct interface *ifp, const union sockunion *nexthop, uint32_t mtu); int nhrp_route_read(ZAPI_CALLBACK_ARGS); int nhrp_route_get_nexthop(const union sockunion *addr, struct prefix *p, union sockunion *via, struct interface **ifp); enum nhrp_route_type nhrp_route_address(struct interface *in_ifp, union sockunion *addr, struct prefix *p, struct nhrp_peer **peer); void nhrp_config_init(void); void nhrp_shortcut_init(void); void nhrp_shortcut_terminate(void); void nhrp_shortcut_initiate(union sockunion *addr); void nhrp_shortcut_foreach(afi_t afi, void (*cb)(struct nhrp_shortcut *, void *), void *ctx); void nhrp_shortcut_purge(struct nhrp_shortcut *s, int force); void nhrp_shortcut_prefix_change(const struct prefix *p, int deleted); struct nhrp_cache *nhrp_cache_get(struct interface *ifp, union sockunion *remote_addr, int create); void nhrp_cache_foreach(struct interface *ifp, void (*cb)(struct nhrp_cache *, void *), void *ctx); void nhrp_cache_set_used(struct nhrp_cache *, int); int nhrp_cache_update_binding(struct nhrp_cache *, enum nhrp_cache_type type, int holding_time, struct nhrp_peer *p, uint32_t mtu, union sockunion *nbma_natoa); void nhrp_cache_notify_add(struct nhrp_cache *c, struct notifier_block *, notifier_fn_t); void nhrp_cache_notify_del(struct nhrp_cache *c, struct notifier_block *); void nhrp_vc_init(void); void nhrp_vc_terminate(void); struct nhrp_vc *nhrp_vc_get(const union sockunion *src, const union sockunion *dst, int create); int nhrp_vc_ipsec_updown(uint32_t child_id, struct nhrp_vc *vc); void nhrp_vc_notify_add(struct nhrp_vc *, struct notifier_block *, notifier_fn_t); void nhrp_vc_notify_del(struct nhrp_vc *, struct notifier_block *); void nhrp_vc_foreach(void (*cb)(struct nhrp_vc *, void *), void *ctx); void nhrp_vc_reset(void); void vici_init(void); void vici_terminate(void); void vici_request_vc(const char *profile, union sockunion *src, union sockunion *dst, int prio); extern const char *nhrp_event_socket_path; void evmgr_init(void); void evmgr_terminate(void); void evmgr_set_socket(const char *socket); void evmgr_notify(const char *name, struct nhrp_cache *c, void (*cb)(struct nhrp_reqid *, void *)); struct nhrp_packet_header *nhrp_packet_push(struct zbuf *zb, uint8_t type, const union sockunion *src_nbma, const union sockunion *src_proto, const union sockunion *dst_proto); void nhrp_packet_complete(struct zbuf *zb, struct nhrp_packet_header *hdr); uint16_t nhrp_packet_calculate_checksum(const uint8_t *pdu, uint16_t len); struct nhrp_packet_header *nhrp_packet_pull(struct zbuf *zb, union sockunion *src_nbma, union sockunion *src_proto, union sockunion *dst_proto); struct nhrp_cie_header *nhrp_cie_push(struct zbuf *zb, uint8_t code, const union sockunion *nbma, const union sockunion *proto); struct nhrp_cie_header *nhrp_cie_pull(struct zbuf *zb, struct nhrp_packet_header *hdr, union sockunion *nbma, union sockunion *proto); struct nhrp_extension_header * nhrp_ext_push(struct zbuf *zb, struct nhrp_packet_header *hdr, uint16_t type); void nhrp_ext_complete(struct zbuf *zb, struct nhrp_extension_header *ext); struct nhrp_extension_header *nhrp_ext_pull(struct zbuf *zb, struct zbuf *payload); void nhrp_ext_request(struct zbuf *zb, struct nhrp_packet_header *hdr, struct interface *); int nhrp_ext_reply(struct zbuf *zb, struct nhrp_packet_header *hdr, struct interface *ifp, struct nhrp_extension_header *ext, struct zbuf *extpayload); uint32_t nhrp_reqid_alloc(struct nhrp_reqid_pool *, struct nhrp_reqid *r, void (*cb)(struct nhrp_reqid *, void *)); void nhrp_reqid_free(struct nhrp_reqid_pool *, struct nhrp_reqid *r); struct nhrp_reqid *nhrp_reqid_lookup(struct nhrp_reqid_pool *, uint32_t reqid); int nhrp_packet_init(void); struct nhrp_peer *nhrp_peer_get(struct interface *ifp, const union sockunion *remote_nbma); struct nhrp_peer *nhrp_peer_ref(struct nhrp_peer *p); void nhrp_peer_unref(struct nhrp_peer *p); int nhrp_peer_check(struct nhrp_peer *p, int establish); void nhrp_peer_notify_add(struct nhrp_peer *p, struct notifier_block *, notifier_fn_t); void nhrp_peer_notify_del(struct nhrp_peer *p, struct notifier_block *); void nhrp_peer_recv(struct nhrp_peer *p, struct zbuf *zb); void nhrp_peer_send(struct nhrp_peer *p, struct zbuf *zb); void nhrp_peer_send_indication(struct interface *ifp, uint16_t, struct zbuf *); #endif frr-7.2.1/nhrpd/os.h0000644000000000000000000000045313610377563011123 00000000000000 int os_socket(void); int os_sendmsg(const uint8_t *buf, size_t len, int ifindex, const uint8_t *addr, size_t addrlen); int os_recvmsg(uint8_t *buf, size_t *len, int *ifindex, uint8_t *addr, size_t *addrlen); int os_configure_dmvpn(unsigned int ifindex, const char *ifname, int af); frr-7.2.1/nhrpd/reqid.c0000644000000000000000000000220313610377563011574 00000000000000#include "zebra.h" #include "hash.h" #include "nhrpd.h" static unsigned int nhrp_reqid_key(const void *data) { const struct nhrp_reqid *r = data; return r->request_id; } static bool nhrp_reqid_cmp(const void *data, const void *key) { const struct nhrp_reqid *a = data, *b = key; return a->request_id == b->request_id; } uint32_t nhrp_reqid_alloc(struct nhrp_reqid_pool *p, struct nhrp_reqid *r, void (*cb)(struct nhrp_reqid *, void *)) { if (!p->reqid_hash) { p->reqid_hash = hash_create(nhrp_reqid_key, nhrp_reqid_cmp, "NHRP reqid Hash"); p->next_request_id = 1; } if (r->cb != cb) { r->request_id = p->next_request_id; if (++p->next_request_id == 0) p->next_request_id = 1; r->cb = cb; hash_get(p->reqid_hash, r, hash_alloc_intern); } return r->request_id; } void nhrp_reqid_free(struct nhrp_reqid_pool *p, struct nhrp_reqid *r) { if (r->cb) { hash_release(p->reqid_hash, r); r->cb = NULL; } } struct nhrp_reqid *nhrp_reqid_lookup(struct nhrp_reqid_pool *p, uint32_t reqid) { struct nhrp_reqid key; if (!p->reqid_hash) return 0; key.request_id = reqid; return hash_lookup(p->reqid_hash, &key); } frr-7.2.1/nhrpd/subdir.am0000644000000000000000000000147213610377563012142 00000000000000# # nhrpd # if NHRPD sbin_PROGRAMS += nhrpd/nhrpd vtysh_scan += $(top_srcdir)/nhrpd/nhrp_vty.c man8 += $(MANBUILD)/frr-nhrpd.8 endif nhrpd_nhrpd_LDADD = lib/libfrr.la lib/libfrrcares.la $(LIBCAP) nhrpd_nhrpd_SOURCES = \ nhrpd/linux.c \ nhrpd/netlink_arp.c \ nhrpd/netlink_gre.c \ nhrpd/nhrp_cache.c \ nhrpd/nhrp_errors.c \ nhrpd/nhrp_event.c \ nhrpd/nhrp_interface.c \ nhrpd/nhrp_main.c \ nhrpd/nhrp_nhs.c \ nhrpd/nhrp_packet.c \ nhrpd/nhrp_peer.c \ nhrpd/nhrp_route.c \ nhrpd/nhrp_shortcut.c \ nhrpd/nhrp_vc.c \ nhrpd/nhrp_vty.c \ nhrpd/reqid.c \ nhrpd/vici.c \ nhrpd/zbuf.c \ nhrpd/znl.c \ # end noinst_HEADERS += \ nhrpd/debug.h \ nhrpd/list.h \ nhrpd/netlink.h \ nhrpd/nhrp_errors.h \ nhrpd/nhrp_protocol.h \ nhrpd/nhrpd.h \ nhrpd/os.h \ nhrpd/vici.h \ nhrpd/zbuf.h \ nhrpd/znl.h \ # end frr-7.2.1/nhrpd/vici.c0000644000000000000000000003317213610377563011433 00000000000000/* strongSwan VICI protocol implementation for NHRP * Copyright (c) 2014-2015 Timo Teräs * * This file is free software: you may copy, redistribute and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation, either version 2 of the License, or * (at your option) any later version. */ #ifdef HAVE_CONFIG_H #include "config.h" #endif #include #include #include #include "thread.h" #include "zbuf.h" #include "log.h" #include "lib_errors.h" #include "nhrpd.h" #include "vici.h" #include "nhrp_errors.h" #define ERRNO_IO_RETRY(EN) (((EN) == EAGAIN) || ((EN) == EWOULDBLOCK) || ((EN) == EINTR)) struct blob { char *ptr; int len; }; static int blob_equal(const struct blob *b, const char *str) { if (!b || b->len != (int)strlen(str)) return 0; return memcmp(b->ptr, str, b->len) == 0; } static int blob2buf(const struct blob *b, char *buf, size_t n) { if (!b || b->len >= (int)n) return 0; memcpy(buf, b->ptr, b->len); buf[b->len] = 0; return 1; } struct vici_conn { struct thread *t_reconnect, *t_read, *t_write; struct zbuf ibuf; struct zbuf_queue obuf; int fd; uint8_t ibuf_data[VICI_MAX_MSGLEN]; }; struct vici_message_ctx { const char *sections[8]; int nsections; }; static int vici_reconnect(struct thread *t); static void vici_submit_request(struct vici_conn *vici, const char *name, ...); static void vici_zbuf_puts(struct zbuf *obuf, const char *str) { size_t len = strlen(str); zbuf_put8(obuf, len); zbuf_put(obuf, str, len); } static void vici_connection_error(struct vici_conn *vici) { nhrp_vc_reset(); THREAD_OFF(vici->t_read); THREAD_OFF(vici->t_write); zbuf_reset(&vici->ibuf); zbufq_reset(&vici->obuf); close(vici->fd); vici->fd = -1; thread_add_timer(master, vici_reconnect, vici, 2, &vici->t_reconnect); } static void vici_parse_message(struct vici_conn *vici, struct zbuf *msg, void (*parser)(struct vici_message_ctx *ctx, enum vici_type_t msgtype, const struct blob *key, const struct blob *val), struct vici_message_ctx *ctx) { uint8_t *type; struct blob key = {0}; struct blob val = {0}; while ((type = zbuf_may_pull(msg, uint8_t)) != NULL) { switch (*type) { case VICI_SECTION_START: key.len = zbuf_get8(msg); key.ptr = zbuf_pulln(msg, key.len); debugf(NHRP_DEBUG_VICI, "VICI: Section start '%.*s'", key.len, key.ptr); parser(ctx, *type, &key, NULL); ctx->nsections++; break; case VICI_SECTION_END: debugf(NHRP_DEBUG_VICI, "VICI: Section end"); parser(ctx, *type, NULL, NULL); ctx->nsections--; break; case VICI_KEY_VALUE: key.len = zbuf_get8(msg); key.ptr = zbuf_pulln(msg, key.len); val.len = zbuf_get_be16(msg); val.ptr = zbuf_pulln(msg, val.len); debugf(NHRP_DEBUG_VICI, "VICI: Key '%.*s'='%.*s'", key.len, key.ptr, val.len, val.ptr); parser(ctx, *type, &key, &val); break; case VICI_LIST_START: key.len = zbuf_get8(msg); key.ptr = zbuf_pulln(msg, key.len); debugf(NHRP_DEBUG_VICI, "VICI: List start '%.*s'", key.len, key.ptr); break; case VICI_LIST_ITEM: val.len = zbuf_get_be16(msg); val.ptr = zbuf_pulln(msg, val.len); debugf(NHRP_DEBUG_VICI, "VICI: List item: '%.*s'", val.len, val.ptr); parser(ctx, *type, &key, &val); break; case VICI_LIST_END: debugf(NHRP_DEBUG_VICI, "VICI: List end"); break; default: debugf(NHRP_DEBUG_VICI, "VICI: Unsupported message component type %d", *type); return; } } } struct handle_sa_ctx { struct vici_message_ctx msgctx; int event; int child_ok; int kill_ikesa; uint32_t child_uniqueid, ike_uniqueid; struct { union sockunion host; struct blob id, cert; } local, remote; }; static void parse_sa_message(struct vici_message_ctx *ctx, enum vici_type_t msgtype, const struct blob *key, const struct blob *val) { struct handle_sa_ctx *sactx = container_of(ctx, struct handle_sa_ctx, msgctx); struct nhrp_vc *vc; char buf[512]; switch (msgtype) { case VICI_SECTION_START: if (ctx->nsections == 3) { /* Begin of child-sa section, reset child vars */ sactx->child_uniqueid = 0; sactx->child_ok = 0; } break; case VICI_SECTION_END: if (ctx->nsections == 3) { /* End of child-sa section, update nhrp_vc */ int up = sactx->child_ok || sactx->event == 1; if (up) { vc = nhrp_vc_get(&sactx->local.host, &sactx->remote.host, up); if (vc) { blob2buf(&sactx->local.id, vc->local.id, sizeof(vc->local.id)); if (blob2buf(&sactx->local.cert, (char *)vc->local.cert, sizeof(vc->local.cert))) vc->local.certlen = sactx->local.cert.len; blob2buf(&sactx->remote.id, vc->remote.id, sizeof(vc->remote.id)); if (blob2buf(&sactx->remote.cert, (char *)vc->remote.cert, sizeof(vc->remote.cert))) vc->remote.certlen = sactx->remote.cert.len; sactx->kill_ikesa |= nhrp_vc_ipsec_updown( sactx->child_uniqueid, vc); } } else { nhrp_vc_ipsec_updown(sactx->child_uniqueid, 0); } } break; default: if (!key || !key->ptr) break; switch (key->ptr[0]) { case 'l': if (blob_equal(key, "local-host") && ctx->nsections == 1) { if (blob2buf(val, buf, sizeof(buf))) if (str2sockunion(buf, &sactx->local.host) < 0) flog_err( EC_NHRP_SWAN, "VICI: bad strongSwan local-host: %s", buf); } else if (blob_equal(key, "local-id") && ctx->nsections == 1) { sactx->local.id = *val; } else if (blob_equal(key, "local-cert-data") && ctx->nsections == 1) { sactx->local.cert = *val; } break; case 'r': if (blob_equal(key, "remote-host") && ctx->nsections == 1) { if (blob2buf(val, buf, sizeof(buf))) if (str2sockunion(buf, &sactx->remote.host) < 0) flog_err( EC_NHRP_SWAN, "VICI: bad strongSwan remote-host: %s", buf); } else if (blob_equal(key, "remote-id") && ctx->nsections == 1) { sactx->remote.id = *val; } else if (blob_equal(key, "remote-cert-data") && ctx->nsections == 1) { sactx->remote.cert = *val; } break; case 'u': if (blob_equal(key, "uniqueid") && blob2buf(val, buf, sizeof(buf))) { if (ctx->nsections == 3) sactx->child_uniqueid = strtoul(buf, NULL, 0); else if (ctx->nsections == 1) sactx->ike_uniqueid = strtoul(buf, NULL, 0); } break; case 's': if (blob_equal(key, "state") && ctx->nsections == 3) { sactx->child_ok = (sactx->event == 0 && (blob_equal(val, "INSTALLED") || blob_equal(val, "REKEYED"))); } break; } break; } } static void parse_cmd_response(struct vici_message_ctx *ctx, enum vici_type_t msgtype, const struct blob *key, const struct blob *val) { char buf[512]; switch (msgtype) { case VICI_KEY_VALUE: if (blob_equal(key, "errmsg") && blob2buf(val, buf, sizeof(buf))) flog_err(EC_NHRP_SWAN, "VICI: strongSwan: %s", buf); break; default: break; } } static void vici_recv_sa(struct vici_conn *vici, struct zbuf *msg, int event) { char buf[32]; struct handle_sa_ctx ctx = { .event = event, .msgctx.nsections = 0 }; vici_parse_message(vici, msg, parse_sa_message, &ctx.msgctx); if (ctx.kill_ikesa && ctx.ike_uniqueid) { debugf(NHRP_DEBUG_COMMON, "VICI: Deleting IKE_SA %u", ctx.ike_uniqueid); snprintf(buf, sizeof buf, "%u", ctx.ike_uniqueid); vici_submit_request(vici, "terminate", VICI_KEY_VALUE, "ike-id", strlen(buf), buf, VICI_END); } } static void vici_recv_message(struct vici_conn *vici, struct zbuf *msg) { uint32_t msglen; uint8_t msgtype; struct blob name; struct vici_message_ctx ctx = { .nsections = 0 }; msglen = zbuf_get_be32(msg); msgtype = zbuf_get8(msg); debugf(NHRP_DEBUG_VICI, "VICI: Message %d, %d bytes", msgtype, msglen); switch (msgtype) { case VICI_EVENT: name.len = zbuf_get8(msg); name.ptr = zbuf_pulln(msg, name.len); debugf(NHRP_DEBUG_VICI, "VICI: Event '%.*s'", name.len, name.ptr); if (blob_equal(&name, "list-sa") || blob_equal(&name, "child-updown") || blob_equal(&name, "child-rekey")) vici_recv_sa(vici, msg, 0); else if (blob_equal(&name, "child-state-installed") || blob_equal(&name, "child-state-rekeyed")) vici_recv_sa(vici, msg, 1); else if (blob_equal(&name, "child-state-destroying")) vici_recv_sa(vici, msg, 2); break; case VICI_CMD_RESPONSE: vici_parse_message(vici, msg, parse_cmd_response, &ctx); break; case VICI_EVENT_UNKNOWN: case VICI_CMD_UNKNOWN: flog_err( EC_NHRP_SWAN, "VICI: StrongSwan does not support mandatory events (unpatched?)"); break; case VICI_EVENT_CONFIRM: break; default: zlog_notice("VICI: Unrecognized message type %d", msgtype); break; } } static int vici_read(struct thread *t) { struct vici_conn *vici = THREAD_ARG(t); struct zbuf *ibuf = &vici->ibuf; struct zbuf pktbuf; vici->t_read = NULL; if (zbuf_read(ibuf, vici->fd, (size_t)-1) < 0) { vici_connection_error(vici); return 0; } /* Process all messages in buffer */ do { uint32_t *hdrlen = zbuf_may_pull(ibuf, uint32_t); if (!hdrlen) break; if (!zbuf_may_pulln(ibuf, ntohl(*hdrlen))) { zbuf_reset_head(ibuf, hdrlen); break; } /* Handle packet */ zbuf_init(&pktbuf, hdrlen, htonl(*hdrlen) + 4, htonl(*hdrlen) + 4); vici_recv_message(vici, &pktbuf); } while (1); thread_add_read(master, vici_read, vici, vici->fd, &vici->t_read); return 0; } static int vici_write(struct thread *t) { struct vici_conn *vici = THREAD_ARG(t); int r; vici->t_write = NULL; r = zbufq_write(&vici->obuf, vici->fd); if (r > 0) { thread_add_write(master, vici_write, vici, vici->fd, &vici->t_write); } else if (r < 0) { vici_connection_error(vici); } return 0; } static void vici_submit(struct vici_conn *vici, struct zbuf *obuf) { if (vici->fd < 0) { zbuf_free(obuf); return; } zbufq_queue(&vici->obuf, obuf); thread_add_write(master, vici_write, vici, vici->fd, &vici->t_write); } static void vici_submit_request(struct vici_conn *vici, const char *name, ...) { struct zbuf *obuf; uint32_t *hdrlen; va_list va; size_t len; int type; obuf = zbuf_alloc(256); if (!obuf) return; hdrlen = zbuf_push(obuf, uint32_t); zbuf_put8(obuf, VICI_CMD_REQUEST); vici_zbuf_puts(obuf, name); va_start(va, name); for (type = va_arg(va, int); type != VICI_END; type = va_arg(va, int)) { zbuf_put8(obuf, type); switch (type) { case VICI_KEY_VALUE: vici_zbuf_puts(obuf, va_arg(va, const char *)); len = va_arg(va, size_t); zbuf_put_be16(obuf, len); zbuf_put(obuf, va_arg(va, void *), len); break; default: break; } } va_end(va); *hdrlen = htonl(zbuf_used(obuf) - 4); vici_submit(vici, obuf); } static void vici_register_event(struct vici_conn *vici, const char *name) { struct zbuf *obuf; uint32_t *hdrlen; uint8_t namelen; namelen = strlen(name); obuf = zbuf_alloc(4 + 1 + 1 + namelen); if (!obuf) return; hdrlen = zbuf_push(obuf, uint32_t); zbuf_put8(obuf, VICI_EVENT_REGISTER); zbuf_put8(obuf, namelen); zbuf_put(obuf, name, namelen); *hdrlen = htonl(zbuf_used(obuf) - 4); vici_submit(vici, obuf); } static int vici_reconnect(struct thread *t) { struct vici_conn *vici = THREAD_ARG(t); int fd; vici->t_reconnect = NULL; if (vici->fd >= 0) return 0; fd = sock_open_unix("/var/run/charon.vici"); if (fd < 0) { debugf(NHRP_DEBUG_VICI, "%s: failure connecting VICI socket: %s", __PRETTY_FUNCTION__, strerror(errno)); thread_add_timer(master, vici_reconnect, vici, 2, &vici->t_reconnect); return 0; } debugf(NHRP_DEBUG_COMMON, "VICI: Connected"); vici->fd = fd; thread_add_read(master, vici_read, vici, vici->fd, &vici->t_read); /* Send event subscribtions */ // vici_register_event(vici, "child-updown"); // vici_register_event(vici, "child-rekey"); vici_register_event(vici, "child-state-installed"); vici_register_event(vici, "child-state-rekeyed"); vici_register_event(vici, "child-state-destroying"); vici_register_event(vici, "list-sa"); vici_submit_request(vici, "list-sas", VICI_END); return 0; } static struct vici_conn vici_connection; void vici_init(void) { struct vici_conn *vici = &vici_connection; vici->fd = -1; zbuf_init(&vici->ibuf, vici->ibuf_data, sizeof(vici->ibuf_data), 0); zbufq_init(&vici->obuf); thread_add_timer_msec(master, vici_reconnect, vici, 10, &vici->t_reconnect); } void vici_terminate(void) { } void vici_request_vc(const char *profile, union sockunion *src, union sockunion *dst, int prio) { struct vici_conn *vici = &vici_connection; char buf[2][SU_ADDRSTRLEN]; sockunion2str(src, buf[0], sizeof buf[0]); sockunion2str(dst, buf[1], sizeof buf[1]); vici_submit_request(vici, "initiate", VICI_KEY_VALUE, "child", strlen(profile), profile, VICI_KEY_VALUE, "timeout", (size_t)2, "-1", VICI_KEY_VALUE, "async", (size_t)1, "1", VICI_KEY_VALUE, "init-limits", (size_t)1, prio ? "0" : "1", VICI_KEY_VALUE, "my-host", strlen(buf[0]), buf[0], VICI_KEY_VALUE, "other-host", strlen(buf[1]), buf[1], VICI_END); } int sock_open_unix(const char *path) { int ret, fd; struct sockaddr_un addr; fd = socket(AF_UNIX, SOCK_STREAM, 0); if (fd < 0) return -1; memset(&addr, 0, sizeof(struct sockaddr_un)); addr.sun_family = AF_UNIX; strlcpy(addr.sun_path, path, sizeof(addr.sun_path)); ret = connect(fd, (struct sockaddr *)&addr, sizeof(addr.sun_family) + strlen(addr.sun_path)); if (ret < 0) { close(fd); return -1; } ret = fcntl(fd, F_SETFL, fcntl(fd, F_GETFL, 0) | O_NONBLOCK); if (ret < 0) { close(fd); return -1; } return fd; } frr-7.2.1/nhrpd/vici.h0000644000000000000000000000063613610377563011437 00000000000000 enum vici_type_t { VICI_START = 0, VICI_SECTION_START = 1, VICI_SECTION_END = 2, VICI_KEY_VALUE = 3, VICI_LIST_START = 4, VICI_LIST_ITEM = 5, VICI_LIST_END = 6, VICI_END = 7 }; enum vici_operation_t { VICI_CMD_REQUEST = 0, VICI_CMD_RESPONSE, VICI_CMD_UNKNOWN, VICI_EVENT_REGISTER, VICI_EVENT_UNREGISTER, VICI_EVENT_CONFIRM, VICI_EVENT_UNKNOWN, VICI_EVENT, }; #define VICI_MAX_MSGLEN (512*1024) frr-7.2.1/nhrpd/zbuf.c0000644000000000000000000001025113610377563011440 00000000000000/* Stream/packet buffer API implementation * Copyright (c) 2014-2015 Timo Teräs * * This file is free software: you may copy, redistribute and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation, either version 2 of the License, or * (at your option) any later version. */ #ifdef HAVE_CONFIG_H #include "config.h" #endif #include #include #include #include "zassert.h" #include "zbuf.h" #include "memory.h" #include "nhrpd.h" #define ERRNO_IO_RETRY(EN) (((EN) == EAGAIN) || ((EN) == EWOULDBLOCK) || ((EN) == EINTR)) DEFINE_MTYPE_STATIC(NHRPD, ZBUF_DATA, "NHRPD zbuf data") struct zbuf *zbuf_alloc(size_t size) { struct zbuf *zb; zb = XMALLOC(MTYPE_ZBUF_DATA, sizeof(*zb) + size); zbuf_init(zb, zb + 1, size, 0); zb->allocated = 1; return zb; } void zbuf_init(struct zbuf *zb, void *buf, size_t len, size_t datalen) { *zb = (struct zbuf){ .buf = buf, .end = (uint8_t *)buf + len, .head = buf, .tail = (uint8_t *)buf + datalen, }; } void zbuf_free(struct zbuf *zb) { if (zb->allocated) XFREE(MTYPE_ZBUF_DATA, zb); } void zbuf_reset(struct zbuf *zb) { zb->head = zb->tail = zb->buf; zb->error = 0; } void zbuf_reset_head(struct zbuf *zb, void *ptr) { zassert((void *)zb->buf <= ptr && ptr <= (void *)zb->tail); zb->head = ptr; } static void zbuf_remove_headroom(struct zbuf *zb) { ssize_t headroom = zbuf_headroom(zb); if (!headroom) return; memmove(zb->buf, zb->head, zbuf_used(zb)); zb->head -= headroom; zb->tail -= headroom; } ssize_t zbuf_read(struct zbuf *zb, int fd, size_t maxlen) { ssize_t r; if (zb->error) return -3; zbuf_remove_headroom(zb); if (maxlen > zbuf_tailroom(zb)) maxlen = zbuf_tailroom(zb); r = read(fd, zb->tail, maxlen); if (r > 0) zb->tail += r; else if (r == 0) r = -2; else if (r < 0 && ERRNO_IO_RETRY(errno)) r = 0; return r; } ssize_t zbuf_write(struct zbuf *zb, int fd) { ssize_t r; if (zb->error) return -3; r = write(fd, zb->head, zbuf_used(zb)); if (r > 0) { zb->head += r; if (zb->head == zb->tail) zbuf_reset(zb); } else if (r == 0) r = -2; else if (r < 0 && ERRNO_IO_RETRY(errno)) r = 0; return r; } ssize_t zbuf_recv(struct zbuf *zb, int fd) { ssize_t r; if (zb->error) return -3; zbuf_remove_headroom(zb); r = recv(fd, zb->tail, zbuf_tailroom(zb), 0); if (r > 0) zb->tail += r; else if (r == 0) r = -2; else if (r < 0 && ERRNO_IO_RETRY(errno)) r = 0; return r; } ssize_t zbuf_send(struct zbuf *zb, int fd) { ssize_t r; if (zb->error) return -3; r = send(fd, zb->head, zbuf_used(zb), 0); if (r >= 0) zbuf_reset(zb); return r; } void *zbuf_may_pull_until(struct zbuf *zb, const char *sep, struct zbuf *msg) { size_t seplen = strlen(sep), len; uint8_t *ptr; ptr = memmem(zb->head, zbuf_used(zb), sep, seplen); if (!ptr) return NULL; len = ptr - zb->head + seplen; zbuf_init(msg, zbuf_pulln(zb, len), len, len); return msg->head; } void zbufq_init(struct zbuf_queue *zbq) { *zbq = (struct zbuf_queue){ .queue_head = LIST_INITIALIZER(zbq->queue_head), }; } void zbufq_reset(struct zbuf_queue *zbq) { struct zbuf *buf, *bufn; list_for_each_entry_safe(buf, bufn, &zbq->queue_head, queue_list) { list_del(&buf->queue_list); zbuf_free(buf); } } void zbufq_queue(struct zbuf_queue *zbq, struct zbuf *zb) { list_add_tail(&zb->queue_list, &zbq->queue_head); } int zbufq_write(struct zbuf_queue *zbq, int fd) { struct iovec iov[16]; struct zbuf *zb, *zbn; ssize_t r; size_t iovcnt = 0; list_for_each_entry_safe(zb, zbn, &zbq->queue_head, queue_list) { iov[iovcnt++] = (struct iovec){ .iov_base = zb->head, .iov_len = zbuf_used(zb), }; if (iovcnt >= array_size(iov)) break; } r = writev(fd, iov, iovcnt); if (r < 0) return r; list_for_each_entry_safe(zb, zbn, &zbq->queue_head, queue_list) { if (r < (ssize_t)zbuf_used(zb)) { zb->head += r; return 1; } r -= zbuf_used(zb); list_del(&zb->queue_list); zbuf_free(zb); } return 0; } void zbuf_copy(struct zbuf *zdst, struct zbuf *zsrc, size_t len) { const void *src; void *dst; dst = zbuf_pushn(zdst, len); src = zbuf_pulln(zsrc, len); if (!dst || !src) return; memcpy(dst, src, len); } frr-7.2.1/nhrpd/zbuf.h0000644000000000000000000001061613610377563011452 00000000000000/* Stream/packet buffer API * Copyright (c) 2014-2015 Timo Teräs * * This file is free software: you may copy, redistribute and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation, either version 2 of the License, or * (at your option) any later version. */ #ifndef ZBUF_H #define ZBUF_H #include #include #include #include #include "zassert.h" #include "list.h" struct zbuf { struct list_head queue_list; unsigned allocated : 1; unsigned error : 1; uint8_t *buf, *end; uint8_t *head, *tail; }; struct zbuf_queue { struct list_head queue_head; }; struct zbuf *zbuf_alloc(size_t size); void zbuf_init(struct zbuf *zb, void *buf, size_t len, size_t datalen); void zbuf_free(struct zbuf *zb); static inline size_t zbuf_size(struct zbuf *zb) { return zb->end - zb->buf; } static inline size_t zbuf_used(struct zbuf *zb) { return zb->tail - zb->head; } static inline size_t zbuf_tailroom(struct zbuf *zb) { return zb->end - zb->tail; } static inline size_t zbuf_headroom(struct zbuf *zb) { return zb->head - zb->buf; } void zbuf_reset(struct zbuf *zb); void zbuf_reset_head(struct zbuf *zb, void *ptr); ssize_t zbuf_read(struct zbuf *zb, int fd, size_t maxlen); ssize_t zbuf_write(struct zbuf *zb, int fd); ssize_t zbuf_recv(struct zbuf *zb, int fd); ssize_t zbuf_send(struct zbuf *zb, int fd); static inline void zbuf_set_rerror(struct zbuf *zb) { zb->error = 1; zb->head = zb->tail; } static inline void zbuf_set_werror(struct zbuf *zb) { zb->error = 1; zb->tail = zb->end; } static inline void *__zbuf_pull(struct zbuf *zb, size_t size, int error) { void *head = zb->head; if (size > zbuf_used(zb)) { if (error) zbuf_set_rerror(zb); return NULL; } zb->head += size; return head; } #define zbuf_pull(zb, type) ((type *)__zbuf_pull(zb, sizeof(type), 1)) #define zbuf_pulln(zb, sz) ((void *)__zbuf_pull(zb, sz, 1)) #define zbuf_may_pull(zb, type) ((type *)__zbuf_pull(zb, sizeof(type), 0)) #define zbuf_may_pulln(zb, sz) ((void *)__zbuf_pull(zb, sz, 0)) void *zbuf_may_pull_until(struct zbuf *zb, const char *sep, struct zbuf *msg); static inline void zbuf_get(struct zbuf *zb, void *dst, size_t len) { void *src = zbuf_pulln(zb, len); if (src) memcpy(dst, src, len); } static inline uint8_t zbuf_get8(struct zbuf *zb) { uint8_t *src = zbuf_pull(zb, uint8_t); if (src) return *src; return 0; } static inline uint32_t zbuf_get32(struct zbuf *zb) { struct unaligned32 { uint32_t value; } __attribute__((packed)); struct unaligned32 *v = zbuf_pull(zb, struct unaligned32); if (v) return v->value; return 0; } static inline uint16_t zbuf_get_be16(struct zbuf *zb) { struct unaligned16 { uint16_t value; } __attribute__((packed)); struct unaligned16 *v = zbuf_pull(zb, struct unaligned16); if (v) return be16toh(v->value); return 0; } static inline uint32_t zbuf_get_be32(struct zbuf *zb) { return be32toh(zbuf_get32(zb)); } static inline void *__zbuf_push(struct zbuf *zb, size_t size, int error) { void *tail = zb->tail; if (size > zbuf_tailroom(zb)) { if (error) zbuf_set_werror(zb); return NULL; } zb->tail += size; return tail; } #define zbuf_push(zb, type) ((type *)__zbuf_push(zb, sizeof(type), 1)) #define zbuf_pushn(zb, sz) ((void *)__zbuf_push(zb, sz, 1)) #define zbuf_may_push(zb, type) ((type *)__zbuf_may_push(zb, sizeof(type), 0)) #define zbuf_may_pushn(zb, sz) ((void *)__zbuf_push(zb, sz, 0)) static inline void zbuf_put(struct zbuf *zb, const void *src, size_t len) { void *dst = zbuf_pushn(zb, len); if (dst) memcpy(dst, src, len); } static inline void zbuf_put8(struct zbuf *zb, uint8_t val) { uint8_t *dst = zbuf_push(zb, uint8_t); if (dst) *dst = val; } static inline void zbuf_put_be16(struct zbuf *zb, uint16_t val) { struct unaligned16 { uint16_t value; } __attribute__((packed)); struct unaligned16 *v = zbuf_push(zb, struct unaligned16); if (v) v->value = htobe16(val); } static inline void zbuf_put_be32(struct zbuf *zb, uint32_t val) { struct unaligned32 { uint32_t value; } __attribute__((packed)); struct unaligned32 *v = zbuf_push(zb, struct unaligned32); if (v) v->value = htobe32(val); } void zbuf_copy(struct zbuf *zb, struct zbuf *src, size_t len); void zbufq_init(struct zbuf_queue *); void zbufq_reset(struct zbuf_queue *); void zbufq_queue(struct zbuf_queue *, struct zbuf *); int zbufq_write(struct zbuf_queue *, int); #endif frr-7.2.1/nhrpd/znl.c0000644000000000000000000000657513610377563011313 00000000000000/* Netlink helpers for zbuf * Copyright (c) 2014-2015 Timo Teräs * * This file is free software: you may copy, redistribute and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation, either version 2 of the License, or * (at your option) any later version. */ #ifdef HAVE_CONFIG_H #include "config.h" #endif #include #include #include #include #include #include #include #include #include "znl.h" #define ZNL_ALIGN(len) (((len)+3) & ~3) void *znl_push(struct zbuf *zb, size_t n) { return zbuf_pushn(zb, ZNL_ALIGN(n)); } void *znl_pull(struct zbuf *zb, size_t n) { return zbuf_pulln(zb, ZNL_ALIGN(n)); } struct nlmsghdr *znl_nlmsg_push(struct zbuf *zb, uint16_t type, uint16_t flags) { struct nlmsghdr *n; n = znl_push(zb, sizeof(*n)); if (!n) return NULL; *n = (struct nlmsghdr){ .nlmsg_type = type, .nlmsg_flags = flags, }; return n; } void znl_nlmsg_complete(struct zbuf *zb, struct nlmsghdr *n) { n->nlmsg_len = zb->tail - (uint8_t *)n; } struct nlmsghdr *znl_nlmsg_pull(struct zbuf *zb, struct zbuf *payload) { struct nlmsghdr *n; size_t plen; n = znl_pull(zb, sizeof(*n)); if (!n) return NULL; plen = n->nlmsg_len - sizeof(*n); zbuf_init(payload, znl_pull(zb, plen), plen, plen); zbuf_may_pulln(zb, ZNL_ALIGN(plen) - plen); return n; } struct rtattr *znl_rta_push(struct zbuf *zb, uint16_t type, const void *val, size_t len) { struct rtattr *rta; uint8_t *dst; rta = znl_push(zb, ZNL_ALIGN(sizeof(*rta)) + ZNL_ALIGN(len)); if (!rta) return NULL; *rta = (struct rtattr){ .rta_type = type, .rta_len = ZNL_ALIGN(sizeof(*rta)) + len, }; dst = (uint8_t *)(rta + 1); memcpy(dst, val, len); memset(dst + len, 0, ZNL_ALIGN(len) - len); return rta; } struct rtattr *znl_rta_push_u32(struct zbuf *zb, uint16_t type, uint32_t val) { return znl_rta_push(zb, type, &val, sizeof(val)); } struct rtattr *znl_rta_nested_push(struct zbuf *zb, uint16_t type) { struct rtattr *rta; rta = znl_push(zb, sizeof(*rta)); if (!rta) return NULL; *rta = (struct rtattr){ .rta_type = type, }; return rta; } void znl_rta_nested_complete(struct zbuf *zb, struct rtattr *rta) { size_t len = zb->tail - (uint8_t *)rta; size_t align = ZNL_ALIGN(len) - len; if (align) { void *dst = zbuf_pushn(zb, align); if (dst) memset(dst, 0, align); } rta->rta_len = len; } struct rtattr *znl_rta_pull(struct zbuf *zb, struct zbuf *payload) { struct rtattr *rta; size_t plen; rta = znl_pull(zb, sizeof(*rta)); if (!rta) return NULL; if (rta->rta_len > sizeof(*rta)) { plen = rta->rta_len - sizeof(*rta); zbuf_init(payload, znl_pull(zb, plen), plen, plen); } else { zbuf_init(payload, NULL, 0, 0); } return rta; } int znl_open(int protocol, int groups) { struct sockaddr_nl addr; int fd, buf = 128 * 1024; fd = socket(AF_NETLINK, SOCK_RAW, protocol); if (fd < 0) return -1; if (fcntl(fd, F_SETFL, fcntl(fd, F_GETFL, 0) | O_NONBLOCK) < 0) goto error; if (fcntl(fd, F_SETFD, FD_CLOEXEC) < 0) goto error; if (setsockopt(fd, SOL_SOCKET, SO_RCVBUF, &buf, sizeof(buf)) < 0) goto error; memset(&addr, 0, sizeof(addr)); addr.nl_family = AF_NETLINK; addr.nl_groups = groups; if (bind(fd, (struct sockaddr *)&addr, sizeof(addr)) < 0) goto error; return fd; error: close(fd); return -1; } frr-7.2.1/nhrpd/znl.h0000644000000000000000000000211513610377563011302 00000000000000/* Netlink helpers for zbuf * Copyright (c) 2014-2015 Timo Teräs * * This file is free software: you may copy, redistribute and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation, either version 2 of the License, or * (at your option) any later version. */ #include "zbuf.h" #define ZNL_BUFFER_SIZE 8192 void *znl_push(struct zbuf *zb, size_t n); void *znl_pull(struct zbuf *zb, size_t n); struct nlmsghdr *znl_nlmsg_push(struct zbuf *zb, uint16_t type, uint16_t flags); void znl_nlmsg_complete(struct zbuf *zb, struct nlmsghdr *n); struct nlmsghdr *znl_nlmsg_pull(struct zbuf *zb, struct zbuf *payload); struct rtattr *znl_rta_push(struct zbuf *zb, uint16_t type, const void *val, size_t len); struct rtattr *znl_rta_push_u32(struct zbuf *zb, uint16_t type, uint32_t val); struct rtattr *znl_rta_nested_push(struct zbuf *zb, uint16_t type); void znl_rta_nested_complete(struct zbuf *zb, struct rtattr *rta); struct rtattr *znl_rta_pull(struct zbuf *zb, struct zbuf *payload); int znl_open(int protocol, int groups); frr-7.2.1/ospf6d/0000755000000000000000000000000013610377563010475 500000000000000frr-7.2.1/ospf6d/Makefile0000644000000000000000000000022513610377563012054 00000000000000all: ALWAYS @$(MAKE) -s -C .. ospf6d/ospf6d %: ALWAYS @$(MAKE) -s -C .. ospf6d/$@ Makefile: #nothing ALWAYS: .PHONY: ALWAYS makefiles .SUFFIXES: frr-7.2.1/ospf6d/ospf6_abr.c0000644000000000000000000012002013610377563012435 00000000000000/* * Area Border Router function. * Copyright (C) 2004 Yasuhiro Ohara * * This file is part of GNU Zebra. * * GNU Zebra is free software; you can redistribute it and/or modify it * under the terms of the GNU General Public License as published by the * Free Software Foundation; either version 2, or (at your option) any * later version. * * GNU Zebra 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; see the file COPYING; if not, write to the Free Software * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA */ #include #include "log.h" #include "prefix.h" #include "table.h" #include "vty.h" #include "linklist.h" #include "command.h" #include "thread.h" #include "plist.h" #include "filter.h" #include "ospf6_proto.h" #include "ospf6_route.h" #include "ospf6_lsa.h" #include "ospf6_route.h" #include "ospf6_lsdb.h" #include "ospf6_message.h" #include "ospf6_zebra.h" #include "ospf6_top.h" #include "ospf6_area.h" #include "ospf6_interface.h" #include "ospf6_neighbor.h" #include "ospf6_flood.h" #include "ospf6_intra.h" #include "ospf6_abr.h" #include "ospf6d.h" unsigned char conf_debug_ospf6_abr; int ospf6_is_router_abr(struct ospf6 *o) { struct listnode *node; struct ospf6_area *oa; int area_count = 0; for (ALL_LIST_ELEMENTS_RO(o->area_list, node, oa)) if (IS_AREA_ENABLED(oa)) area_count++; if (area_count > 1) return 1; return 0; } static int ospf6_abr_nexthops_belong_to_area(struct ospf6_route *route, struct ospf6_area *area) { struct ospf6_interface *oi; oi = ospf6_interface_lookup_by_ifindex( ospf6_route_get_first_nh_index(route)); if (oi && oi->area && oi->area == area) return 1; else return 0; } static void ospf6_abr_delete_route(struct ospf6_route *range, struct ospf6_route *summary, struct ospf6_route_table *summary_table, struct ospf6_lsa *old) { if (summary) { ospf6_route_remove(summary, summary_table); } if (old && !OSPF6_LSA_IS_MAXAGE(old)) ospf6_lsa_purge(old); } void ospf6_abr_enable_area(struct ospf6_area *area) { struct ospf6_area *oa; struct listnode *node, *nnode; for (ALL_LIST_ELEMENTS(area->ospf6->area_list, node, nnode, oa)) /* update B bit for each area */ OSPF6_ROUTER_LSA_SCHEDULE(oa); } void ospf6_abr_disable_area(struct ospf6_area *area) { struct ospf6_area *oa; struct ospf6_route *ro, *nro; struct ospf6_lsa *old; struct listnode *node, *nnode; /* Withdraw all summary prefixes previously originated */ for (ro = ospf6_route_head(area->summary_prefix); ro; ro = nro) { nro = ospf6_route_next(ro); old = ospf6_lsdb_lookup(ro->path.origin.type, ro->path.origin.id, area->ospf6->router_id, area->lsdb); if (old) ospf6_lsa_purge(old); ospf6_route_remove(ro, area->summary_prefix); } /* Withdraw all summary router-routes previously originated */ for (ro = ospf6_route_head(area->summary_router); ro; ro = nro) { nro = ospf6_route_next(ro); old = ospf6_lsdb_lookup(ro->path.origin.type, ro->path.origin.id, area->ospf6->router_id, area->lsdb); if (old) ospf6_lsa_purge(old); ospf6_route_remove(ro, area->summary_router); } /* Schedule Router-LSA for each area (ABR status may change) */ for (ALL_LIST_ELEMENTS(area->ospf6->area_list, node, nnode, oa)) /* update B bit for each area */ OSPF6_ROUTER_LSA_SCHEDULE(oa); } /* RFC 2328 12.4.3. Summary-LSAs */ /* Returns 1 if a summary LSA has been generated for the area */ /* This is used by the area/range logic to add/remove blackhole routes */ int ospf6_abr_originate_summary_to_area(struct ospf6_route *route, struct ospf6_area *area) { struct ospf6_lsa *lsa, *old = NULL; struct ospf6_route *summary, *range = NULL; struct ospf6_area *route_area; char buffer[OSPF6_MAX_LSASIZE]; struct ospf6_lsa_header *lsa_header; caddr_t p; struct ospf6_inter_prefix_lsa *prefix_lsa; struct ospf6_inter_router_lsa *router_lsa; struct ospf6_route_table *summary_table = NULL; uint16_t type; char buf[PREFIX2STR_BUFFER]; int is_debug = 0; /* Only destination type network, range or ASBR are considered */ if (route->type != OSPF6_DEST_TYPE_NETWORK && route->type != OSPF6_DEST_TYPE_RANGE && ((route->type != OSPF6_DEST_TYPE_ROUTER) || !CHECK_FLAG(route->path.router_bits, OSPF6_ROUTER_BIT_E))) { #if 0 zlog_debug( "Route type is none of network, range nor ASBR, ignore"); #endif return 0; } /* AS External routes are never considered */ if (route->path.type == OSPF6_PATH_TYPE_EXTERNAL1 || route->path.type == OSPF6_PATH_TYPE_EXTERNAL2) { #if 0 zlog_debug("Path type is external, skip"); #endif return 0; } /* do not generate if the path's area is the same as target area */ if (route->path.area_id == area->area_id) { #if 0 zlog_debug("The route is in the area itself, ignore"); #endif return 0; } /* do not generate if the nexthops belongs to the target area */ if (ospf6_abr_nexthops_belong_to_area(route, area)) { #if 0 zlog_debug("The route's nexthop is in the same area, ignore"); #endif return 0; } if (route->type == OSPF6_DEST_TYPE_ROUTER) { if (ADV_ROUTER_IN_PREFIX(&route->prefix) == area->ospf6->router_id) { inet_ntop(AF_INET, &(ADV_ROUTER_IN_PREFIX(&route->prefix)), buf, sizeof(buf)); zlog_debug( "%s: Skipping ASBR announcement for ABR (%s)", __func__, buf); return 0; } } if (route->type == OSPF6_DEST_TYPE_ROUTER) { if (IS_OSPF6_DEBUG_ABR || IS_OSPF6_DEBUG_ORIGINATE(INTER_ROUTER)) { is_debug++; inet_ntop(AF_INET, &(ADV_ROUTER_IN_PREFIX(&route->prefix)), buf, sizeof(buf)); zlog_debug("Originating summary in area %s for ASBR %s", area->name, buf); } summary_table = area->summary_router; } else { if (IS_OSPF6_DEBUG_ABR || IS_OSPF6_DEBUG_ORIGINATE(INTER_PREFIX)) is_debug++; if (route->type == OSPF6_DEST_TYPE_NETWORK && route->path.origin.type == htons(OSPF6_LSTYPE_INTER_PREFIX)) { if (!CHECK_FLAG(route->flag, OSPF6_ROUTE_BEST)) { if (is_debug) { inet_ntop(AF_INET, &(ADV_ROUTER_IN_PREFIX( &route->prefix)), buf, sizeof(buf)); zlog_debug( "%s: route %s with cost %u is not best, ignore." , __PRETTY_FUNCTION__, buf, route->path.cost); } return 0; } } if (route->path.origin.type == htons(OSPF6_LSTYPE_INTRA_PREFIX)) { if (!CHECK_FLAG(route->flag, OSPF6_ROUTE_BEST)) { if (is_debug) { prefix2str(&route->prefix, buf, sizeof(buf)); zlog_debug("%s: intra-prefix route %s with cost %u is not best, ignore." , __PRETTY_FUNCTION__, buf, route->path.cost); } return 0; } } if (is_debug) { prefix2str(&route->prefix, buf, sizeof(buf)); zlog_debug("Originating summary in area %s for %s cost %u", area->name, buf, route->path.cost); } summary_table = area->summary_prefix; } summary = ospf6_route_lookup(&route->prefix, summary_table); if (summary) old = ospf6_lsdb_lookup(summary->path.origin.type, summary->path.origin.id, area->ospf6->router_id, area->lsdb); /* if this route has just removed, remove corresponding LSA */ if (CHECK_FLAG(route->flag, OSPF6_ROUTE_REMOVE)) { if (is_debug) zlog_debug( "The route has just removed, purge previous LSA"); if (route->type == OSPF6_DEST_TYPE_RANGE) { /* Whether the route have active longer prefix */ if (!CHECK_FLAG(route->flag, OSPF6_ROUTE_ACTIVE_SUMMARY)) { if (is_debug) zlog_debug( "The range is not active. withdraw"); ospf6_abr_delete_route(route, summary, summary_table, old); } } else if (old) { ospf6_route_remove(summary, summary_table); ospf6_lsa_purge(old); } return 0; } if ((route->type == OSPF6_DEST_TYPE_ROUTER) && IS_AREA_STUB(area)) { if (is_debug) zlog_debug( "Area has been stubbed, purge Inter-Router LSA"); ospf6_abr_delete_route(route, summary, summary_table, old); return 0; } if (area->no_summary && (route->path.subtype != OSPF6_PATH_SUBTYPE_DEFAULT_RT)) { if (is_debug) zlog_debug("Area has been stubbed, purge prefix LSA"); ospf6_abr_delete_route(route, summary, summary_table, old); return 0; } /* do not generate if the route cost is greater or equal to LSInfinity */ if (route->path.cost >= OSPF_LS_INFINITY) { /* When we're clearing the range route because all active * prefixes * under the range are gone, we set the range's cost to * OSPF_AREA_RANGE_COST_UNSPEC, which is > OSPF_LS_INFINITY. We * don't want to trigger the code here for that. This code is * for * handling routes that have gone to infinity. The range removal * happens * elsewhere. */ if ((route->type != OSPF6_DEST_TYPE_RANGE) && (route->path.cost != OSPF_AREA_RANGE_COST_UNSPEC)) { if (is_debug) zlog_debug( "The cost exceeds LSInfinity, withdraw"); if (old) ospf6_lsa_purge(old); return 0; } } /* if this is a route to ASBR */ if (route->type == OSPF6_DEST_TYPE_ROUTER) { /* Only the preferred best path is considered */ if (!CHECK_FLAG(route->flag, OSPF6_ROUTE_BEST)) { if (is_debug) zlog_debug( "This is the secondary path to the ASBR, ignore"); ospf6_abr_delete_route(route, summary, summary_table, old); return 0; } /* Do not generate if the area is stub */ /* XXX */ } /* if this is an intra-area route, this may be suppressed by aggregation */ if (route->type == OSPF6_DEST_TYPE_NETWORK && route->path.type == OSPF6_PATH_TYPE_INTRA) { /* search for configured address range for the route's area */ route_area = ospf6_area_lookup(route->path.area_id, area->ospf6); assert(route_area); range = ospf6_route_lookup_bestmatch(&route->prefix, route_area->range_table); /* ranges are ignored when originate backbone routes to transit area. Otherwise, if ranges are configured, the route is suppressed. */ if (range && !CHECK_FLAG(range->flag, OSPF6_ROUTE_REMOVE) && (route->path.area_id != OSPF_AREA_BACKBONE || !IS_AREA_TRANSIT(area))) { if (is_debug) { prefix2str(&range->prefix, buf, sizeof(buf)); zlog_debug("Suppressed by range %s of area %s", buf, route_area->name); } ospf6_abr_delete_route(route, summary, summary_table, old); return 0; } } /* If this is a configured address range */ if (route->type == OSPF6_DEST_TYPE_RANGE) { /* If DoNotAdvertise is set */ if (CHECK_FLAG(route->flag, OSPF6_ROUTE_DO_NOT_ADVERTISE)) { if (is_debug) zlog_debug( "This is the range with DoNotAdvertise set. ignore"); ospf6_abr_delete_route(route, summary, summary_table, old); return 0; } /* If there are no active prefixes in this range, remove */ if (!CHECK_FLAG(route->flag, OSPF6_ROUTE_ACTIVE_SUMMARY)) { if (is_debug) zlog_debug("The range is not active. withdraw"); ospf6_abr_delete_route(route, summary, summary_table, old); return 0; } } /* Check export list */ if (EXPORT_NAME(area)) { if (EXPORT_LIST(area) == NULL) EXPORT_LIST(area) = access_list_lookup(AFI_IP6, EXPORT_NAME(area)); if (EXPORT_LIST(area)) if (access_list_apply(EXPORT_LIST(area), &route->prefix) == FILTER_DENY) { if (is_debug) { inet_ntop(AF_INET, &(ADV_ROUTER_IN_PREFIX( &route->prefix)), buf, sizeof(buf)); zlog_debug( "prefix %s was denied by export list", buf); } return 0; } } /* Check filter-list */ if (PREFIX_LIST_OUT(area)) if (prefix_list_apply(PREFIX_LIST_OUT(area), &route->prefix) != PREFIX_PERMIT) { if (is_debug) { inet_ntop( AF_INET, &(ADV_ROUTER_IN_PREFIX(&route->prefix)), buf, sizeof(buf)); zlog_debug( "prefix %s was denied by filter-list out", buf); } return 0; } /* the route is going to be originated. store it in area's summary_table */ if (summary == NULL) { summary = ospf6_route_copy(route); summary->path.origin.adv_router = area->ospf6->router_id; if (route->type == OSPF6_DEST_TYPE_ROUTER) { summary->path.origin.type = htons(OSPF6_LSTYPE_INTER_ROUTER); summary->path.origin.id = ADV_ROUTER_IN_PREFIX(&route->prefix); } else { summary->path.origin.type = htons(OSPF6_LSTYPE_INTER_PREFIX); summary->path.origin.id = ospf6_new_ls_id( summary->path.origin.type, summary->path.origin.adv_router, area->lsdb); } summary = ospf6_route_add(summary, summary_table); } else { summary->type = route->type; monotime(&summary->changed); } summary->path.router_bits = route->path.router_bits; summary->path.options[0] = route->path.options[0]; summary->path.options[1] = route->path.options[1]; summary->path.options[2] = route->path.options[2]; summary->path.prefix_options = route->path.prefix_options; summary->path.area_id = area->area_id; summary->path.type = OSPF6_PATH_TYPE_INTER; summary->path.subtype = route->path.subtype; summary->path.cost = route->path.cost; /* summary->nexthop[0] = route->nexthop[0]; */ /* prepare buffer */ memset(buffer, 0, sizeof(buffer)); lsa_header = (struct ospf6_lsa_header *)buffer; if (route->type == OSPF6_DEST_TYPE_ROUTER) { router_lsa = (struct ospf6_inter_router_lsa *)((caddr_t)lsa_header + sizeof(struct ospf6_lsa_header)); p = (caddr_t)router_lsa + sizeof(struct ospf6_inter_router_lsa); /* Fill Inter-Area-Router-LSA */ router_lsa->options[0] = route->path.options[0]; router_lsa->options[1] = route->path.options[1]; router_lsa->options[2] = route->path.options[2]; OSPF6_ABR_SUMMARY_METRIC_SET(router_lsa, route->path.cost); router_lsa->router_id = ADV_ROUTER_IN_PREFIX(&route->prefix); type = htons(OSPF6_LSTYPE_INTER_ROUTER); } else { prefix_lsa = (struct ospf6_inter_prefix_lsa *)((caddr_t)lsa_header + sizeof(struct ospf6_lsa_header)); p = (caddr_t)prefix_lsa + sizeof(struct ospf6_inter_prefix_lsa); /* Fill Inter-Area-Prefix-LSA */ OSPF6_ABR_SUMMARY_METRIC_SET(prefix_lsa, route->path.cost); prefix_lsa->prefix.prefix_length = route->prefix.prefixlen; prefix_lsa->prefix.prefix_options = route->path.prefix_options; /* set Prefix */ memcpy(p, &route->prefix.u.prefix6, OSPF6_PREFIX_SPACE(route->prefix.prefixlen)); ospf6_prefix_apply_mask(&prefix_lsa->prefix); p += OSPF6_PREFIX_SPACE(route->prefix.prefixlen); type = htons(OSPF6_LSTYPE_INTER_PREFIX); } /* Fill LSA Header */ lsa_header->age = 0; lsa_header->type = type; lsa_header->id = summary->path.origin.id; lsa_header->adv_router = area->ospf6->router_id; lsa_header->seqnum = ospf6_new_ls_seqnum(lsa_header->type, lsa_header->id, lsa_header->adv_router, area->lsdb); lsa_header->length = htons((caddr_t)p - (caddr_t)lsa_header); /* LSA checksum */ ospf6_lsa_checksum(lsa_header); /* create LSA */ lsa = ospf6_lsa_create(lsa_header); /* Originate */ ospf6_lsa_originate_area(lsa, area); return 1; } void ospf6_abr_range_reset_cost(struct ospf6 *ospf6) { struct listnode *node, *nnode; struct ospf6_area *oa; struct ospf6_route *range; for (ALL_LIST_ELEMENTS(ospf6->area_list, node, nnode, oa)) for (range = ospf6_route_head(oa->range_table); range; range = ospf6_route_next(range)) OSPF6_ABR_RANGE_CLEAR_COST(range); } static inline uint32_t ospf6_abr_range_compute_cost(struct ospf6_route *range, struct ospf6 *o) { struct ospf6_route *ro; uint32_t cost = 0; for (ro = ospf6_route_match_head(&range->prefix, o->route_table); ro; ro = ospf6_route_match_next(&range->prefix, ro)) { if (ro->path.area_id == range->path.area_id && (ro->path.type == OSPF6_PATH_TYPE_INTRA) && !CHECK_FLAG(ro->flag, OSPF6_ROUTE_REMOVE)) cost = MAX(cost, ro->path.cost); } return cost; } static inline int ospf6_abr_range_summary_needs_update(struct ospf6_route *range, uint32_t cost) { int redo_summary = 0; if (CHECK_FLAG(range->flag, OSPF6_ROUTE_REMOVE)) { UNSET_FLAG(range->flag, OSPF6_ROUTE_ACTIVE_SUMMARY); redo_summary = 1; } else if (CHECK_FLAG(range->flag, OSPF6_ROUTE_DO_NOT_ADVERTISE)) { if (range->path.cost != 0) { range->path.cost = 0; redo_summary = 1; } } else if (cost) { if ((OSPF6_PATH_COST_IS_CONFIGURED(range->path) && range->path.cost != range->path.u.cost_config)) { range->path.cost = range->path.u.cost_config; SET_FLAG(range->flag, OSPF6_ROUTE_ACTIVE_SUMMARY); redo_summary = 1; } else if (!OSPF6_PATH_COST_IS_CONFIGURED(range->path) && range->path.cost != cost) { range->path.cost = cost; SET_FLAG(range->flag, OSPF6_ROUTE_ACTIVE_SUMMARY); redo_summary = 1; } } else if (CHECK_FLAG(range->flag, OSPF6_ROUTE_ACTIVE_SUMMARY)) { /* Cost is zero, meaning no active range */ UNSET_FLAG(range->flag, OSPF6_ROUTE_ACTIVE_SUMMARY); range->path.cost = OSPF_AREA_RANGE_COST_UNSPEC; redo_summary = 1; } return (redo_summary); } static void ospf6_abr_range_update(struct ospf6_route *range) { uint32_t cost = 0; struct listnode *node, *nnode; struct ospf6_area *oa; int summary_orig = 0; assert(range->type == OSPF6_DEST_TYPE_RANGE); /* update range's cost and active flag */ cost = ospf6_abr_range_compute_cost(range, ospf6); /* Non-zero cost is a proxy for active longer prefixes in this range. * If there are active routes covered by this range AND either the * configured * cost has changed or the summarized cost has changed then redo * summaries. * Alternately, if there are no longer active prefixes and there are * summary announcements, withdraw those announcements. * * The don't advertise code relies on the path.cost being set to UNSPEC * to * work the first time. Subsequent times the path.cost is not 0 anyway * if there * were active ranges. */ if (ospf6_abr_range_summary_needs_update(range, cost)) { for (ALL_LIST_ELEMENTS(ospf6->area_list, node, nnode, oa)) summary_orig += ospf6_abr_originate_summary_to_area(range, oa); if (CHECK_FLAG(range->flag, OSPF6_ROUTE_ACTIVE_SUMMARY) && summary_orig) { if (!CHECK_FLAG(range->flag, OSPF6_ROUTE_BLACKHOLE_ADDED)) { if (IS_OSPF6_DEBUG_ABR) zlog_debug("Add discard route"); ospf6_zebra_add_discard(range); } } else { /* Summary removed or no summary generated as no * specifics exist */ if (CHECK_FLAG(range->flag, OSPF6_ROUTE_BLACKHOLE_ADDED)) { if (IS_OSPF6_DEBUG_ABR) zlog_debug("Delete discard route"); ospf6_zebra_delete_discard(range); } } } } void ospf6_abr_originate_summary(struct ospf6_route *route) { struct listnode *node, *nnode; struct ospf6_area *oa; struct ospf6_route *range = NULL; if (route->type == OSPF6_DEST_TYPE_NETWORK) { oa = ospf6_area_lookup(route->path.area_id, ospf6); if (!oa) { zlog_err("OSPFv6 area lookup failed"); return; } range = ospf6_route_lookup_bestmatch(&route->prefix, oa->range_table); if (range) { ospf6_abr_range_update(range); } } for (ALL_LIST_ELEMENTS(ospf6->area_list, node, nnode, oa)) ospf6_abr_originate_summary_to_area(route, oa); } void ospf6_abr_defaults_to_stub(struct ospf6 *o) { struct listnode *node, *nnode; struct ospf6_area *oa; struct ospf6_route *def, *route; if (!o->backbone) return; def = ospf6_route_create(); def->type = OSPF6_DEST_TYPE_NETWORK; def->prefix.family = AF_INET6; def->prefix.prefixlen = 0; memset(&def->prefix.u.prefix6, 0, sizeof(struct in6_addr)); def->type = OSPF6_DEST_TYPE_NETWORK; def->path.type = OSPF6_PATH_TYPE_INTER; def->path.subtype = OSPF6_PATH_SUBTYPE_DEFAULT_RT; def->path.area_id = o->backbone->area_id; for (ALL_LIST_ELEMENTS(ospf6->area_list, node, nnode, oa)) { if (!IS_AREA_STUB(oa)) { /* withdraw defaults when an area switches from stub to * non-stub */ route = ospf6_route_lookup(&def->prefix, oa->summary_prefix); if (route && (route->path.subtype == def->path.subtype)) { if (IS_OSPF6_DEBUG_ABR) zlog_debug( "Withdrawing default route from non-stubby area %s", oa->name); SET_FLAG(def->flag, OSPF6_ROUTE_REMOVE); ospf6_abr_originate_summary_to_area(def, oa); } } else { /* announce defaults to stubby areas */ if (IS_OSPF6_DEBUG_ABR) zlog_debug( "Announcing default route into stubby area %s", oa->name); UNSET_FLAG(def->flag, OSPF6_ROUTE_REMOVE); ospf6_abr_originate_summary_to_area(def, oa); } } ospf6_route_delete(def); } void ospf6_abr_old_path_update(struct ospf6_route *old_route, struct ospf6_route *route, struct ospf6_route_table *table) { struct ospf6_path *o_path = NULL; struct listnode *anode, *anext; struct listnode *nnode, *rnode, *rnext; struct ospf6_nexthop *nh, *rnh; for (ALL_LIST_ELEMENTS(old_route->paths, anode, anext, o_path)) { if (o_path->area_id != route->path.area_id || (memcmp(&(o_path)->origin, &(route)->path.origin, sizeof(struct ospf6_ls_origin)) != 0)) continue; if ((o_path->cost == route->path.cost) && (o_path->u.cost_e2 == route->path.u.cost_e2)) continue; for (ALL_LIST_ELEMENTS_RO(o_path->nh_list, nnode, nh)) { for (ALL_LIST_ELEMENTS(old_route->nh_list, rnode, rnext, rnh)) { if (!ospf6_nexthop_is_same(rnh, nh)) continue; listnode_delete(old_route->nh_list, rnh); ospf6_nexthop_delete(rnh); } } listnode_delete(old_route->paths, o_path); ospf6_path_free(o_path); for (ALL_LIST_ELEMENTS(old_route->paths, anode, anext, o_path)) { ospf6_merge_nexthops(old_route->nh_list, o_path->nh_list); } if (IS_OSPF6_DEBUG_ABR || IS_OSPF6_DEBUG_EXAMIN(INTER_PREFIX)) zlog_debug("%s: paths %u nh %u", __PRETTY_FUNCTION__, old_route->paths ? listcount(old_route->paths) : 0, old_route->nh_list ? listcount(old_route->nh_list) : 0); if (table->hook_add) (*table->hook_add)(old_route); if (old_route->path.origin.id == route->path.origin.id && old_route->path.origin.adv_router == route->path.origin.adv_router) { struct ospf6_path *h_path; h_path = (struct ospf6_path *) listgetdata(listhead(old_route->paths)); old_route->path.origin.type = h_path->origin.type; old_route->path.origin.id = h_path->origin.id; old_route->path.origin.adv_router = h_path->origin.adv_router; } } } void ospf6_abr_old_route_remove(struct ospf6_lsa *lsa, struct ospf6_route *old, struct ospf6_route_table *table) { if (listcount(old->paths) > 1) { struct listnode *anode, *anext, *nnode, *rnode, *rnext; struct ospf6_path *o_path; struct ospf6_nexthop *nh, *rnh; bool nh_updated = false; char buf[PREFIX2STR_BUFFER]; for (ALL_LIST_ELEMENTS(old->paths, anode, anext, o_path)) { if (o_path->origin.adv_router != lsa->header->adv_router && o_path->origin.id != lsa->header->id) continue; for (ALL_LIST_ELEMENTS_RO(o_path->nh_list, nnode, nh)) { for (ALL_LIST_ELEMENTS(old->nh_list, rnode, rnext, rnh)) { if (!ospf6_nexthop_is_same(rnh, nh)) continue; listnode_delete(old->nh_list, rnh); ospf6_nexthop_delete(rnh); } } listnode_delete(old->paths, o_path); ospf6_path_free(o_path); nh_updated = true; } if (nh_updated) { if (listcount(old->paths)) { if (IS_OSPF6_DEBUG_ABR || IS_OSPF6_DEBUG_EXAMIN(INTER_PREFIX)) { prefix2str(&old->prefix, buf, sizeof(buf)); zlog_debug("%s: old %s updated nh %u", __PRETTY_FUNCTION__, buf, old->nh_list ? listcount(old->nh_list) : 0); } if (table->hook_add) (*table->hook_add)(old); if ((old->path.origin.id == lsa->header->id) && (old->path.origin.adv_router == lsa->header->adv_router)) { struct ospf6_path *h_path; h_path = (struct ospf6_path *) listgetdata( listhead(old->paths)); old->path.origin.type = h_path->origin.type; old->path.origin.id = h_path->origin.id; old->path.origin.adv_router = h_path->origin.adv_router; } } else ospf6_route_remove(old, table); } } else ospf6_route_remove(old, table); } /* RFC 2328 16.2. Calculating the inter-area routes */ void ospf6_abr_examin_summary(struct ospf6_lsa *lsa, struct ospf6_area *oa) { struct prefix prefix, abr_prefix; struct ospf6_route_table *table = NULL; struct ospf6_route *range, *route, *old = NULL, *old_route; struct ospf6_route *abr_entry; uint8_t type = 0; char options[3] = {0, 0, 0}; uint8_t prefix_options = 0; uint32_t cost = 0; uint8_t router_bits = 0; char buf[PREFIX2STR_BUFFER]; int is_debug = 0; struct ospf6_inter_prefix_lsa *prefix_lsa = NULL; struct ospf6_inter_router_lsa *router_lsa = NULL; bool old_entry_updated = false; struct ospf6_path *path, *o_path, *ecmp_path; struct listnode *anode; char adv_router[16]; memset(&prefix, 0, sizeof(prefix)); if (lsa->header->type == htons(OSPF6_LSTYPE_INTER_PREFIX)) { if (IS_OSPF6_DEBUG_EXAMIN(INTER_PREFIX)) { is_debug++; zlog_debug("%s: Examin %s in area %s", __PRETTY_FUNCTION__, lsa->name, oa->name); } prefix_lsa = (struct ospf6_inter_prefix_lsa *)OSPF6_LSA_HEADER_END( lsa->header); prefix.family = AF_INET6; prefix.prefixlen = prefix_lsa->prefix.prefix_length; ospf6_prefix_in6_addr(&prefix.u.prefix6, prefix_lsa, &prefix_lsa->prefix); if (is_debug) prefix2str(&prefix, buf, sizeof(buf)); table = oa->ospf6->route_table; type = OSPF6_DEST_TYPE_NETWORK; prefix_options = prefix_lsa->prefix.prefix_options; cost = OSPF6_ABR_SUMMARY_METRIC(prefix_lsa); } else if (lsa->header->type == htons(OSPF6_LSTYPE_INTER_ROUTER)) { if (IS_OSPF6_DEBUG_EXAMIN(INTER_ROUTER)) { is_debug++; zlog_debug("%s: Examin %s in area %s", __PRETTY_FUNCTION__, lsa->name, oa->name); } router_lsa = (struct ospf6_inter_router_lsa *)OSPF6_LSA_HEADER_END( lsa->header); ospf6_linkstate_prefix(router_lsa->router_id, htonl(0), &prefix); if (is_debug) inet_ntop(AF_INET, &router_lsa->router_id, buf, sizeof(buf)); table = oa->ospf6->brouter_table; type = OSPF6_DEST_TYPE_ROUTER; options[0] = router_lsa->options[0]; options[1] = router_lsa->options[1]; options[2] = router_lsa->options[2]; cost = OSPF6_ABR_SUMMARY_METRIC(router_lsa); SET_FLAG(router_bits, OSPF6_ROUTER_BIT_E); } else assert(0); /* Find existing route */ route = ospf6_route_lookup(&prefix, table); if (route) ospf6_route_lock(route); while (route && ospf6_route_is_prefix(&prefix, route)) { if (route->path.area_id == oa->area_id && route->path.origin.type == lsa->header->type && !CHECK_FLAG(route->flag, OSPF6_ROUTE_WAS_REMOVED)) { /* LSA adv. router could be part of route's * paths list. Find the existing path and set * old as the route. */ if (listcount(route->paths) > 1) { for (ALL_LIST_ELEMENTS_RO(route->paths, anode, o_path)) { inet_ntop(AF_INET, &o_path->origin.adv_router, adv_router, sizeof(adv_router)); if (o_path->origin.id == lsa->header->id && o_path->origin.adv_router == lsa->header->adv_router) { old = route; if (is_debug) zlog_debug("%s: old entry found in paths, adv_router %s", __PRETTY_FUNCTION__, adv_router); break; } } } else if (route->path.origin.id == lsa->header->id && route->path.origin.adv_router == lsa->header->adv_router) old = route; } route = ospf6_route_next(route); } if (route) ospf6_route_unlock(route); /* (1) if cost == LSInfinity or if the LSA is MaxAge */ if (cost == OSPF_LS_INFINITY) { if (is_debug) zlog_debug("cost is LS_INFINITY, ignore"); if (old) ospf6_abr_old_route_remove(lsa, old, table); return; } if (OSPF6_LSA_IS_MAXAGE(lsa)) { if (is_debug) zlog_debug("%s: LSA %s is MaxAge, ignore", __PRETTY_FUNCTION__, lsa->name); if (old) ospf6_abr_old_route_remove(lsa, old, table); return; } /* (2) if the LSA is self-originated, ignore */ if (lsa->header->adv_router == oa->ospf6->router_id) { if (is_debug) zlog_debug("LSA %s is self-originated, ignore", lsa->name); if (old) ospf6_route_remove(old, table); return; } /* (3) if the prefix is equal to an active configured address range */ /* or if the NU bit is set in the prefix */ if (lsa->header->type == htons(OSPF6_LSTYPE_INTER_PREFIX)) { /* must have been set in previous block */ assert(prefix_lsa); range = ospf6_route_lookup(&prefix, oa->range_table); if (range) { if (is_debug) zlog_debug( "Prefix is equal to address range, ignore"); if (old) ospf6_route_remove(old, table); return; } if (CHECK_FLAG(prefix_lsa->prefix.prefix_options, OSPF6_PREFIX_OPTION_NU) || CHECK_FLAG(prefix_lsa->prefix.prefix_options, OSPF6_PREFIX_OPTION_LA)) { if (is_debug) zlog_debug("Prefix has NU/LA bit set, ignore"); if (old) ospf6_route_remove(old, table); return; } } if (lsa->header->type == htons(OSPF6_LSTYPE_INTER_ROUTER)) { /* To pass test suites */ assert(router_lsa); if (!OSPF6_OPT_ISSET(router_lsa->options, OSPF6_OPT_R) || !OSPF6_OPT_ISSET(router_lsa->options, OSPF6_OPT_V6)) { if (is_debug) zlog_debug("Prefix has NU/LA bit set, ignore"); if (old) ospf6_route_remove(old, table); return; } /* Avoid infinite recursion if someone has maliciously announced an Inter-Router LSA for an ABR */ if (lsa->header->adv_router == router_lsa->router_id) { if (is_debug) zlog_debug( "Ignorning Inter-Router LSA for an ABR (%s)", buf); if (old) ospf6_route_remove(old, table); return; } } /* (4) if the routing table entry for the ABR does not exist */ ospf6_linkstate_prefix(lsa->header->adv_router, htonl(0), &abr_prefix); abr_entry = ospf6_route_lookup(&abr_prefix, oa->ospf6->brouter_table); if (abr_entry == NULL || abr_entry->path.area_id != oa->area_id || CHECK_FLAG(abr_entry->flag, OSPF6_ROUTE_REMOVE) || !CHECK_FLAG(abr_entry->path.router_bits, OSPF6_ROUTER_BIT_B)) { if (is_debug) zlog_debug("%s: ABR router entry does not exist, ignore", __PRETTY_FUNCTION__); if (old) { if (old->type == OSPF6_DEST_TYPE_ROUTER && oa->intra_brouter_calc) { if (is_debug) zlog_debug( "%s: intra_brouter_calc is on, skip brouter remove: %s (%p)", __PRETTY_FUNCTION__, buf, (void *)old); } else { if (is_debug) zlog_debug("%s: remove old entry: %s %p ", __PRETTY_FUNCTION__, buf, (void *)old); ospf6_route_remove(old, table); } } return; } /* Check import list */ if (IMPORT_NAME(oa)) { if (IMPORT_LIST(oa) == NULL) IMPORT_LIST(oa) = access_list_lookup(AFI_IP6, IMPORT_NAME(oa)); if (IMPORT_LIST(oa)) if (access_list_apply(IMPORT_LIST(oa), &prefix) == FILTER_DENY) { if (is_debug) zlog_debug( "Prefix was denied by import-list"); if (old) ospf6_route_remove(old, table); return; } } /* Check input prefix-list */ if (PREFIX_LIST_IN(oa)) { if (prefix_list_apply(PREFIX_LIST_IN(oa), &prefix) != PREFIX_PERMIT) { if (is_debug) zlog_debug("Prefix was denied by prefix-list"); if (old) ospf6_route_remove(old, table); return; } } /* (5),(6): the path preference is handled by the sorting in the routing table. Always install the path by substituting old route (if any). */ route = ospf6_route_create(); route->type = type; route->prefix = prefix; route->path.origin.type = lsa->header->type; route->path.origin.id = lsa->header->id; route->path.origin.adv_router = lsa->header->adv_router; route->path.router_bits = router_bits; route->path.options[0] = options[0]; route->path.options[1] = options[1]; route->path.options[2] = options[2]; route->path.prefix_options = prefix_options; route->path.area_id = oa->area_id; route->path.type = OSPF6_PATH_TYPE_INTER; route->path.cost = abr_entry->path.cost + cost; /* copy brouter rechable nexthops into the route. */ ospf6_route_copy_nexthops(route, abr_entry); /* (7) If the routes are identical, copy the next hops over to existing route. ospf6's route table implementation will otherwise string both routes, but keep the older one as the best route since the routes are identical. */ old = ospf6_route_lookup(&prefix, table); for (old_route = old; old_route; old_route = old_route->next) { if (!ospf6_route_is_same(old_route, route) || (old_route->type != route->type) || (old_route->path.type != route->path.type)) continue; if ((ospf6_route_cmp(route, old_route) != 0)) { if (is_debug) { prefix2str(&prefix, buf, sizeof(buf)); zlog_debug("%s: old %p %s cost %u new route cost %u are not same", __PRETTY_FUNCTION__, (void *)old_route, buf, old_route->path.cost, route->path.cost); } /* Check new route's adv. router is same in one of * the paths with differed cost, if so remove the * old path as later new route will be added. */ if (listcount(old_route->paths) > 1) ospf6_abr_old_path_update(old_route, route, table); continue; } ospf6_route_merge_nexthops(old_route, route); old_entry_updated = true; for (ALL_LIST_ELEMENTS_RO(old_route->paths, anode, o_path)) { if (o_path->area_id == route->path.area_id && (memcmp(&(o_path)->origin, &(route)->path.origin, sizeof(struct ospf6_ls_origin)) == 0)) break; } /* New adv. router for a existing path add to paths list */ if (o_path == NULL) { ecmp_path = ospf6_path_dup(&route->path); /* Add a nh_list to new ecmp path */ ospf6_copy_nexthops(ecmp_path->nh_list, route->nh_list); /* Add the new path to route's path list */ listnode_add_sort(old_route->paths, ecmp_path); if (is_debug) { prefix2str(&route->prefix, buf, sizeof(buf)); inet_ntop(AF_INET, &ecmp_path->origin.adv_router, adv_router, sizeof(adv_router)); zlog_debug("%s: route %s cost %u another path %s added with nh %u, effective paths %u nh %u", __PRETTY_FUNCTION__, buf, old_route->path.cost, adv_router, listcount(ecmp_path->nh_list), old_route->paths ? listcount(old_route->paths) : 0, listcount(old_route->nh_list)); } } else { /* adv. router exists in the list, update the nhs */ list_delete_all_node(o_path->nh_list); ospf6_copy_nexthops(o_path->nh_list, route->nh_list); } if (is_debug) zlog_debug("%s: Update route: %s %p old cost %u new cost %u nh %u", __PRETTY_FUNCTION__, buf, (void *)old_route, old_route->path.cost, route->path.cost, listcount(route->nh_list)); /* For Inter-Prefix route: Update RIB/FIB, * For Inter-Router trigger summary update */ if (table->hook_add) (*table->hook_add)(old_route); /* Delete new route */ ospf6_route_delete(route); break; } if (old_entry_updated == false) { if (is_debug) { inet_ntop(AF_INET, &route->path.origin.adv_router, adv_router, sizeof(adv_router)); zlog_debug("%s: Install route: %s cost %u nh %u adv_router %s ", __PRETTY_FUNCTION__, buf, route->path.cost, listcount(route->nh_list), adv_router); } path = ospf6_path_dup(&route->path); ospf6_copy_nexthops(path->nh_list, abr_entry->nh_list); listnode_add_sort(route->paths, path); /* ospf6_ia_add_nw_route (table, &prefix, route); */ ospf6_route_add(route, table); } } void ospf6_abr_examin_brouter(uint32_t router_id) { struct ospf6_lsa *lsa; struct ospf6_area *oa; uint16_t type; if (ospf6_is_router_abr(ospf6)) oa = ospf6->backbone; else oa = listgetdata(listhead(ospf6->area_list)); /* * It is possible to designate a non backbone * area first. If that is the case safely * fall out of this function. */ if (oa == NULL) return; type = htons(OSPF6_LSTYPE_INTER_ROUTER); for (ALL_LSDB_TYPED_ADVRTR(oa->lsdb, type, router_id, lsa)) ospf6_abr_examin_summary(lsa, oa); type = htons(OSPF6_LSTYPE_INTER_PREFIX); for (ALL_LSDB_TYPED_ADVRTR(oa->lsdb, type, router_id, lsa)) ospf6_abr_examin_summary(lsa, oa); } void ospf6_abr_reimport(struct ospf6_area *oa) { struct ospf6_lsa *lsa; uint16_t type; type = htons(OSPF6_LSTYPE_INTER_ROUTER); for (ALL_LSDB_TYPED(oa->lsdb, type, lsa)) ospf6_abr_examin_summary(lsa, oa); type = htons(OSPF6_LSTYPE_INTER_PREFIX); for (ALL_LSDB_TYPED(oa->lsdb, type, lsa)) ospf6_abr_examin_summary(lsa, oa); } void ospf6_abr_prefix_resummarize(struct ospf6 *o) { struct ospf6_route *route; if (IS_OSPF6_DEBUG_ABR) zlog_debug("Re-examining Inter-Prefix Summaries"); for (route = ospf6_route_head(o->route_table); route; route = ospf6_route_next(route)) ospf6_abr_originate_summary(route); if (IS_OSPF6_DEBUG_ABR) zlog_debug("Finished re-examining Inter-Prefix Summaries"); } /* Display functions */ static char *ospf6_inter_area_prefix_lsa_get_prefix_str(struct ospf6_lsa *lsa, char *buf, int buflen, int pos) { struct ospf6_inter_prefix_lsa *prefix_lsa; struct in6_addr in6; if (lsa != NULL) { prefix_lsa = (struct ospf6_inter_prefix_lsa *)OSPF6_LSA_HEADER_END( lsa->header); ospf6_prefix_in6_addr(&in6, prefix_lsa, &prefix_lsa->prefix); if (buf) { inet_ntop(AF_INET6, &in6, buf, buflen); sprintf(&buf[strlen(buf)], "/%d", prefix_lsa->prefix.prefix_length); } } return (buf); } static int ospf6_inter_area_prefix_lsa_show(struct vty *vty, struct ospf6_lsa *lsa) { struct ospf6_inter_prefix_lsa *prefix_lsa; char buf[INET6_ADDRSTRLEN]; prefix_lsa = (struct ospf6_inter_prefix_lsa *)OSPF6_LSA_HEADER_END( lsa->header); vty_out(vty, " Metric: %lu\n", (unsigned long)OSPF6_ABR_SUMMARY_METRIC(prefix_lsa)); ospf6_prefix_options_printbuf(prefix_lsa->prefix.prefix_options, buf, sizeof(buf)); vty_out(vty, " Prefix Options: %s\n", buf); vty_out(vty, " Prefix: %s\n", ospf6_inter_area_prefix_lsa_get_prefix_str(lsa, buf, sizeof(buf), 0)); return 0; } static char *ospf6_inter_area_router_lsa_get_prefix_str(struct ospf6_lsa *lsa, char *buf, int buflen, int pos) { struct ospf6_inter_router_lsa *router_lsa; if (lsa != NULL) { router_lsa = (struct ospf6_inter_router_lsa *)OSPF6_LSA_HEADER_END( lsa->header); if (buf) inet_ntop(AF_INET, &router_lsa->router_id, buf, buflen); } return (buf); } static int ospf6_inter_area_router_lsa_show(struct vty *vty, struct ospf6_lsa *lsa) { struct ospf6_inter_router_lsa *router_lsa; char buf[64]; router_lsa = (struct ospf6_inter_router_lsa *)OSPF6_LSA_HEADER_END( lsa->header); ospf6_options_printbuf(router_lsa->options, buf, sizeof(buf)); vty_out(vty, " Options: %s\n", buf); vty_out(vty, " Metric: %lu\n", (unsigned long)OSPF6_ABR_SUMMARY_METRIC(router_lsa)); inet_ntop(AF_INET, &router_lsa->router_id, buf, sizeof(buf)); vty_out(vty, " Destination Router ID: %s\n", buf); return 0; } /* Debug commands */ DEFUN (debug_ospf6_abr, debug_ospf6_abr_cmd, "debug ospf6 abr", DEBUG_STR OSPF6_STR "Debug OSPFv3 ABR function\n" ) { OSPF6_DEBUG_ABR_ON(); return CMD_SUCCESS; } DEFUN (no_debug_ospf6_abr, no_debug_ospf6_abr_cmd, "no debug ospf6 abr", NO_STR DEBUG_STR OSPF6_STR "Debug OSPFv3 ABR function\n" ) { OSPF6_DEBUG_ABR_OFF(); return CMD_SUCCESS; } int config_write_ospf6_debug_abr(struct vty *vty) { if (IS_OSPF6_DEBUG_ABR) vty_out(vty, "debug ospf6 abr\n"); return 0; } void install_element_ospf6_debug_abr(void) { install_element(ENABLE_NODE, &debug_ospf6_abr_cmd); install_element(ENABLE_NODE, &no_debug_ospf6_abr_cmd); install_element(CONFIG_NODE, &debug_ospf6_abr_cmd); install_element(CONFIG_NODE, &no_debug_ospf6_abr_cmd); } struct ospf6_lsa_handler inter_prefix_handler = { .lh_type = OSPF6_LSTYPE_INTER_PREFIX, .lh_name = "Inter-Prefix", .lh_short_name = "IAP", .lh_show = ospf6_inter_area_prefix_lsa_show, .lh_get_prefix_str = ospf6_inter_area_prefix_lsa_get_prefix_str, .lh_debug = 0}; struct ospf6_lsa_handler inter_router_handler = { .lh_type = OSPF6_LSTYPE_INTER_ROUTER, .lh_name = "Inter-Router", .lh_short_name = "IAR", .lh_show = ospf6_inter_area_router_lsa_show, .lh_get_prefix_str = ospf6_inter_area_router_lsa_get_prefix_str, .lh_debug = 0}; void ospf6_abr_init(void) { ospf6_install_lsa_handler(&inter_prefix_handler); ospf6_install_lsa_handler(&inter_router_handler); } frr-7.2.1/ospf6d/ospf6_abr.h0000644000000000000000000000627213610377563012456 00000000000000/* * Copyright (C) 2004 Yasuhiro Ohara * * This file is part of GNU Zebra. * * GNU Zebra is free software; you can redistribute it and/or modify it * under the terms of the GNU General Public License as published by the * Free Software Foundation; either version 2, or (at your option) any * later version. * * GNU Zebra 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; see the file COPYING; if not, write to the Free Software * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA */ #ifndef OSPF6_ABR_H #define OSPF6_ABR_H /* for struct ospf6_route */ #include "ospf6_route.h" /* for struct ospf6_prefix */ #include "ospf6_proto.h" /* Debug option */ extern unsigned char conf_debug_ospf6_abr; #define OSPF6_DEBUG_ABR_ON() (conf_debug_ospf6_abr = 1) #define OSPF6_DEBUG_ABR_OFF() (conf_debug_ospf6_abr = 0) #define IS_OSPF6_DEBUG_ABR (conf_debug_ospf6_abr) /* Inter-Area-Prefix-LSA */ #define OSPF6_INTER_PREFIX_LSA_MIN_SIZE 4U /* w/o IPv6 prefix */ struct ospf6_inter_prefix_lsa { uint32_t metric; struct ospf6_prefix prefix; }; /* Inter-Area-Router-LSA */ #define OSPF6_INTER_ROUTER_LSA_FIX_SIZE 12U struct ospf6_inter_router_lsa { uint8_t mbz; uint8_t options[3]; uint32_t metric; uint32_t router_id; }; #define OSPF6_ABR_SUMMARY_METRIC(E) (ntohl ((E)->metric & htonl (0x00ffffff))) #define OSPF6_ABR_SUMMARY_METRIC_SET(E, C) \ { \ (E)->metric &= htonl(0x00000000); \ (E)->metric |= htonl(0x00ffffff) & htonl(C); \ } #define OSPF6_ABR_RANGE_CLEAR_COST(range) (range->path.cost = OSPF_AREA_RANGE_COST_UNSPEC) extern int ospf6_is_router_abr(struct ospf6 *o); extern void ospf6_abr_enable_area(struct ospf6_area *oa); extern void ospf6_abr_disable_area(struct ospf6_area *oa); extern int ospf6_abr_originate_summary_to_area(struct ospf6_route *route, struct ospf6_area *area); extern void ospf6_abr_originate_summary(struct ospf6_route *route); extern void ospf6_abr_examin_summary(struct ospf6_lsa *lsa, struct ospf6_area *oa); extern void ospf6_abr_defaults_to_stub(struct ospf6 *); extern void ospf6_abr_examin_brouter(uint32_t router_id); extern void ospf6_abr_reimport(struct ospf6_area *oa); extern void ospf6_abr_range_reset_cost(struct ospf6 *ospf6); extern void ospf6_abr_prefix_resummarize(struct ospf6 *ospf6); extern int config_write_ospf6_debug_abr(struct vty *vty); extern void install_element_ospf6_debug_abr(void); extern int ospf6_abr_config_write(struct vty *vty); extern void ospf6_abr_old_route_remove(struct ospf6_lsa *lsa, struct ospf6_route *old, struct ospf6_route_table *table); extern void ospf6_abr_old_path_update(struct ospf6_route *old_route, struct ospf6_route *route, struct ospf6_route_table *table); extern void ospf6_abr_init(void); #endif /*OSPF6_ABR_H*/ frr-7.2.1/ospf6d/ospf6_area.c0000644000000000000000000006705013610377563012616 00000000000000/* * Copyright (C) 2003 Yasuhiro Ohara * * This file is part of GNU Zebra. * * GNU Zebra is free software; you can redistribute it and/or modify it * under the terms of the GNU General Public License as published by the * Free Software Foundation; either version 2, or (at your option) any * later version. * * GNU Zebra 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; see the file COPYING; if not, write to the Free Software * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA */ #include #include "log.h" #include "memory.h" #include "linklist.h" #include "thread.h" #include "vty.h" #include "command.h" #include "if.h" #include "prefix.h" #include "table.h" #include "plist.h" #include "filter.h" #include "ospf6_proto.h" #include "ospf6_lsa.h" #include "ospf6_lsdb.h" #include "ospf6_route.h" #include "ospf6_spf.h" #include "ospf6_top.h" #include "ospf6_area.h" #include "ospf6_interface.h" #include "ospf6_intra.h" #include "ospf6_abr.h" #include "ospf6_asbr.h" #include "ospf6d.h" DEFINE_MTYPE_STATIC(OSPF6D, OSPF6_PLISTNAME, "Prefix list name") int ospf6_area_cmp(void *va, void *vb) { struct ospf6_area *oa = (struct ospf6_area *)va; struct ospf6_area *ob = (struct ospf6_area *)vb; return (ntohl(oa->area_id) < ntohl(ob->area_id) ? -1 : 1); } /* schedule routing table recalculation */ static void ospf6_area_lsdb_hook_add(struct ospf6_lsa *lsa) { switch (ntohs(lsa->header->type)) { case OSPF6_LSTYPE_ROUTER: case OSPF6_LSTYPE_NETWORK: if (IS_OSPF6_DEBUG_EXAMIN_TYPE(lsa->header->type)) { zlog_debug("%s Examin LSA %s", __PRETTY_FUNCTION__, lsa->name); zlog_debug(" Schedule SPF Calculation for %s", OSPF6_AREA(lsa->lsdb->data)->name); } ospf6_spf_schedule( OSPF6_PROCESS(OSPF6_AREA(lsa->lsdb->data)->ospf6), ospf6_lsadd_to_spf_reason(lsa)); break; case OSPF6_LSTYPE_INTRA_PREFIX: ospf6_intra_prefix_lsa_add(lsa); break; case OSPF6_LSTYPE_INTER_PREFIX: case OSPF6_LSTYPE_INTER_ROUTER: ospf6_abr_examin_summary(lsa, (struct ospf6_area *)lsa->lsdb->data); break; default: break; } } static void ospf6_area_lsdb_hook_remove(struct ospf6_lsa *lsa) { switch (ntohs(lsa->header->type)) { case OSPF6_LSTYPE_ROUTER: case OSPF6_LSTYPE_NETWORK: if (IS_OSPF6_DEBUG_EXAMIN_TYPE(lsa->header->type)) { zlog_debug("LSA disappearing: %s", lsa->name); zlog_debug("Schedule SPF Calculation for %s", OSPF6_AREA(lsa->lsdb->data)->name); } ospf6_spf_schedule( OSPF6_PROCESS(OSPF6_AREA(lsa->lsdb->data)->ospf6), ospf6_lsremove_to_spf_reason(lsa)); break; case OSPF6_LSTYPE_INTRA_PREFIX: ospf6_intra_prefix_lsa_remove(lsa); break; case OSPF6_LSTYPE_INTER_PREFIX: case OSPF6_LSTYPE_INTER_ROUTER: ospf6_abr_examin_summary(lsa, (struct ospf6_area *)lsa->lsdb->data); break; default: break; } } static void ospf6_area_route_hook_add(struct ospf6_route *route) { struct ospf6_route *copy; copy = ospf6_route_copy(route); ospf6_route_add(copy, ospf6->route_table); } static void ospf6_area_route_hook_remove(struct ospf6_route *route) { struct ospf6_route *copy; copy = ospf6_route_lookup_identical(route, ospf6->route_table); if (copy) ospf6_route_remove(copy, ospf6->route_table); } static void ospf6_area_stub_update(struct ospf6_area *area) { if (IS_AREA_STUB(area)) { if (IS_OSPF6_DEBUG_ORIGINATE(ROUTER)) zlog_debug("Stubbing out area for if %s", area->name); OSPF6_OPT_CLEAR(area->options, OSPF6_OPT_E); } else if (IS_AREA_ENABLED(area)) { if (IS_OSPF6_DEBUG_ORIGINATE(ROUTER)) zlog_debug("Normal area for if %s", area->name); OSPF6_OPT_SET(area->options, OSPF6_OPT_E); ospf6_asbr_send_externals_to_area(area); } OSPF6_ROUTER_LSA_SCHEDULE(area); } static int ospf6_area_stub_set(struct ospf6 *ospf6, struct ospf6_area *area) { if (!IS_AREA_STUB(area)) { SET_FLAG(area->flag, OSPF6_AREA_STUB); ospf6_area_stub_update(area); } return (1); } static void ospf6_area_stub_unset(struct ospf6 *ospf6, struct ospf6_area *area) { if (IS_AREA_STUB(area)) { UNSET_FLAG(area->flag, OSPF6_AREA_STUB); ospf6_area_stub_update(area); } } static void ospf6_area_no_summary_set(struct ospf6 *ospf6, struct ospf6_area *area) { if (area) { if (!area->no_summary) { area->no_summary = 1; ospf6_abr_range_reset_cost(ospf6); ospf6_abr_prefix_resummarize(ospf6); } } } static void ospf6_area_no_summary_unset(struct ospf6 *ospf6, struct ospf6_area *area) { if (area) { if (area->no_summary) { area->no_summary = 0; ospf6_abr_range_reset_cost(ospf6); ospf6_abr_prefix_resummarize(ospf6); } } } /** * Make new area structure. * * @param area_id - ospf6 area ID * @param o - ospf6 instance * @param df - display format for area ID */ struct ospf6_area *ospf6_area_create(uint32_t area_id, struct ospf6 *o, int df) { struct ospf6_area *oa; oa = XCALLOC(MTYPE_OSPF6_AREA, sizeof(struct ospf6_area)); switch (df) { case OSPF6_AREA_FMT_DECIMAL: snprintf(oa->name, sizeof(oa->name), "%u", ntohl(area_id)); break; default: case OSPF6_AREA_FMT_DOTTEDQUAD: inet_ntop(AF_INET, &area_id, oa->name, sizeof(oa->name)); break; } oa->area_id = area_id; oa->if_list = list_new(); oa->lsdb = ospf6_lsdb_create(oa); oa->lsdb->hook_add = ospf6_area_lsdb_hook_add; oa->lsdb->hook_remove = ospf6_area_lsdb_hook_remove; oa->lsdb_self = ospf6_lsdb_create(oa); oa->temp_router_lsa_lsdb = ospf6_lsdb_create(oa); oa->spf_table = OSPF6_ROUTE_TABLE_CREATE(AREA, SPF_RESULTS); oa->spf_table->scope = oa; oa->route_table = OSPF6_ROUTE_TABLE_CREATE(AREA, ROUTES); oa->route_table->scope = oa; oa->route_table->hook_add = ospf6_area_route_hook_add; oa->route_table->hook_remove = ospf6_area_route_hook_remove; oa->range_table = OSPF6_ROUTE_TABLE_CREATE(AREA, PREFIX_RANGES); oa->range_table->scope = oa; bf_init(oa->range_table->idspace, 32); oa->summary_prefix = OSPF6_ROUTE_TABLE_CREATE(AREA, SUMMARY_PREFIXES); oa->summary_prefix->scope = oa; oa->summary_router = OSPF6_ROUTE_TABLE_CREATE(AREA, SUMMARY_ROUTERS); oa->summary_router->scope = oa; oa->router_lsa_size_limit = 1024 + 256; /* set default options */ if (CHECK_FLAG(o->flag, OSPF6_STUB_ROUTER)) { OSPF6_OPT_CLEAR(oa->options, OSPF6_OPT_V6); OSPF6_OPT_CLEAR(oa->options, OSPF6_OPT_R); } else { OSPF6_OPT_SET(oa->options, OSPF6_OPT_V6); OSPF6_OPT_SET(oa->options, OSPF6_OPT_R); } OSPF6_OPT_SET(oa->options, OSPF6_OPT_E); SET_FLAG(oa->flag, OSPF6_AREA_ACTIVE); SET_FLAG(oa->flag, OSPF6_AREA_ENABLE); oa->ospf6 = o; listnode_add_sort(o->area_list, oa); if (area_id == OSPF_AREA_BACKBONE) { o->backbone = oa; } return oa; } void ospf6_area_delete(struct ospf6_area *oa) { struct listnode *n; struct ospf6_interface *oi; /* The ospf6_interface structs store configuration * information which should not be lost/reset when * deleting an area. * So just detach the interface from the area and * keep it around. */ for (ALL_LIST_ELEMENTS_RO(oa->if_list, n, oi)) oi->area = NULL; list_delete(&oa->if_list); ospf6_lsdb_delete(oa->lsdb); ospf6_lsdb_delete(oa->lsdb_self); ospf6_lsdb_delete(oa->temp_router_lsa_lsdb); ospf6_spf_table_finish(oa->spf_table); ospf6_route_table_delete(oa->spf_table); ospf6_route_table_delete(oa->route_table); ospf6_route_table_delete(oa->range_table); ospf6_route_table_delete(oa->summary_prefix); ospf6_route_table_delete(oa->summary_router); listnode_delete(oa->ospf6->area_list, oa); oa->ospf6 = NULL; /* free area */ XFREE(MTYPE_OSPF6_AREA, oa); } struct ospf6_area *ospf6_area_lookup(uint32_t area_id, struct ospf6 *ospf6) { struct ospf6_area *oa; struct listnode *n; for (ALL_LIST_ELEMENTS_RO(ospf6->area_list, n, oa)) if (oa->area_id == area_id) return oa; return (struct ospf6_area *)NULL; } void ospf6_area_enable(struct ospf6_area *oa) { struct listnode *node, *nnode; struct ospf6_interface *oi; SET_FLAG(oa->flag, OSPF6_AREA_ENABLE); for (ALL_LIST_ELEMENTS(oa->if_list, node, nnode, oi)) ospf6_interface_enable(oi); ospf6_abr_enable_area(oa); } void ospf6_area_disable(struct ospf6_area *oa) { struct listnode *node, *nnode; struct ospf6_interface *oi; UNSET_FLAG(oa->flag, OSPF6_AREA_ENABLE); for (ALL_LIST_ELEMENTS(oa->if_list, node, nnode, oi)) ospf6_interface_disable(oi); ospf6_abr_disable_area(oa); ospf6_lsdb_remove_all(oa->lsdb); ospf6_lsdb_remove_all(oa->lsdb_self); ospf6_spf_table_finish(oa->spf_table); ospf6_route_remove_all(oa->route_table); THREAD_OFF(oa->thread_router_lsa); THREAD_OFF(oa->thread_intra_prefix_lsa); } void ospf6_area_show(struct vty *vty, struct ospf6_area *oa) { struct listnode *i; struct ospf6_interface *oi; unsigned long result; if (!IS_AREA_STUB(oa)) vty_out(vty, " Area %s\n", oa->name); else { if (oa->no_summary) { vty_out(vty, " Area %s[Stub, No Summary]\n", oa->name); } else { vty_out(vty, " Area %s[Stub]\n", oa->name); } } vty_out(vty, " Number of Area scoped LSAs is %u\n", oa->lsdb->count); vty_out(vty, " Interface attached to this area:"); for (ALL_LIST_ELEMENTS_RO(oa->if_list, i, oi)) vty_out(vty, " %s", oi->interface->name); vty_out(vty, "\n"); if (oa->ts_spf.tv_sec || oa->ts_spf.tv_usec) { result = monotime_since(&oa->ts_spf, NULL); if (result / TIMER_SECOND_MICRO > 0) { vty_out(vty, "SPF last executed %ld.%lds ago\n", result / TIMER_SECOND_MICRO, result % TIMER_SECOND_MICRO); } else { vty_out(vty, "SPF last executed %ldus ago\n", result); } } else vty_out(vty, "SPF has not been run\n"); } #define OSPF6_CMD_AREA_GET(str, oa) \ { \ char *ep; \ uint32_t area_id = htonl(strtoul(str, &ep, 10)); \ if (*ep && inet_pton(AF_INET, str, &area_id) != 1) { \ vty_out(vty, "Malformed Area-ID: %s\n", str); \ return CMD_SUCCESS; \ } \ int format = !*ep ? OSPF6_AREA_FMT_DECIMAL \ : OSPF6_AREA_FMT_DOTTEDQUAD; \ oa = ospf6_area_lookup(area_id, ospf6); \ if (oa == NULL) \ oa = ospf6_area_create(area_id, ospf6, format); \ } DEFUN (area_range, area_range_cmd, "area range X:X::X:X/M []", "OSPF6 area parameters\n" "OSPF6 area ID in IP address format\n" "OSPF6 area ID as a decimal value\n" "Configured address range\n" "Specify IPv6 prefix\n" "Advertise\n" "Do not advertise\n" "User specified metric for this range\n" "Advertised metric for this range\n") { int idx_ipv4 = 1; int idx_ipv6_prefixlen = 3; int idx_type = 4; int ret; struct ospf6_area *oa; struct prefix prefix; struct ospf6_route *range; uint32_t cost = OSPF_AREA_RANGE_COST_UNSPEC; OSPF6_CMD_AREA_GET(argv[idx_ipv4]->arg, oa); ret = str2prefix(argv[idx_ipv6_prefixlen]->arg, &prefix); if (ret != 1 || prefix.family != AF_INET6) { vty_out(vty, "Malformed argument: %s\n", argv[idx_ipv6_prefixlen]->arg); return CMD_SUCCESS; } range = ospf6_route_lookup(&prefix, oa->range_table); if (range == NULL) { range = ospf6_route_create(); range->type = OSPF6_DEST_TYPE_RANGE; range->prefix = prefix; range->path.area_id = oa->area_id; range->path.cost = OSPF_AREA_RANGE_COST_UNSPEC; } if (argc > idx_type) { if (strmatch(argv[idx_type]->text, "not-advertise")) { SET_FLAG(range->flag, OSPF6_ROUTE_DO_NOT_ADVERTISE); } else if (strmatch(argv[idx_type]->text, "advertise")) { UNSET_FLAG(range->flag, OSPF6_ROUTE_DO_NOT_ADVERTISE); cost = range->path.u.cost_config; } else { cost = strtoul(argv[5]->arg, NULL, 10); UNSET_FLAG(range->flag, OSPF6_ROUTE_DO_NOT_ADVERTISE); } } range->path.u.cost_config = cost; zlog_debug("%s: for prefix %s, flag = %x", __func__, argv[idx_ipv6_prefixlen]->arg, range->flag); if (range->rnode == NULL) { ospf6_route_add(range, oa->range_table); } if (ospf6_is_router_abr(ospf6)) { /* Redo summaries if required */ ospf6_abr_prefix_resummarize(ospf6); } return CMD_SUCCESS; } DEFUN (no_area_range, no_area_range_cmd, "no area range X:X::X:X/M []", NO_STR "OSPF6 area parameters\n" "OSPF6 area ID in IP address format\n" "OSPF6 area ID as a decimal value\n" "Configured address range\n" "Specify IPv6 prefix\n" "Advertise\n" "Do not advertise\n" "User specified metric for this range\n" "Advertised metric for this range\n") { int idx_ipv4 = 2; int idx_ipv6 = 4; int ret; struct ospf6_area *oa; struct prefix prefix; struct ospf6_route *range, *route; OSPF6_CMD_AREA_GET(argv[idx_ipv4]->arg, oa); ret = str2prefix(argv[idx_ipv6]->arg, &prefix); if (ret != 1 || prefix.family != AF_INET6) { vty_out(vty, "Malformed argument: %s\n", argv[idx_ipv6]->arg); return CMD_SUCCESS; } range = ospf6_route_lookup(&prefix, oa->range_table); if (range == NULL) { vty_out(vty, "Range %s does not exists.\n", argv[idx_ipv6]->arg); return CMD_SUCCESS; } if (ospf6_is_router_abr(oa->ospf6)) { /* Blow away the aggregated LSA and route */ SET_FLAG(range->flag, OSPF6_ROUTE_REMOVE); /* Redo summaries if required */ for (route = ospf6_route_head(ospf6->route_table); route; route = ospf6_route_next(route)) ospf6_abr_originate_summary(route); /* purge the old aggregated summary LSA */ ospf6_abr_originate_summary(range); } ospf6_route_remove(range, oa->range_table); return CMD_SUCCESS; } void ospf6_area_config_write(struct vty *vty) { struct listnode *node; struct ospf6_area *oa; struct ospf6_route *range; char buf[PREFIX2STR_BUFFER]; for (ALL_LIST_ELEMENTS_RO(ospf6->area_list, node, oa)) { for (range = ospf6_route_head(oa->range_table); range; range = ospf6_route_next(range)) { prefix2str(&range->prefix, buf, sizeof(buf)); vty_out(vty, " area %s range %s", oa->name, buf); if (CHECK_FLAG(range->flag, OSPF6_ROUTE_DO_NOT_ADVERTISE)) { vty_out(vty, " not-advertise"); } else { // "advertise" is the default so we do not // display it if (range->path.u.cost_config != OSPF_AREA_RANGE_COST_UNSPEC) vty_out(vty, " cost %d", range->path.u.cost_config); } vty_out(vty, "\n"); } if (IS_AREA_STUB(oa)) { if (oa->no_summary) vty_out(vty, " area %s stub no-summary\n", oa->name); else vty_out(vty, " area %s stub\n", oa->name); } if (PREFIX_NAME_IN(oa)) vty_out(vty, " area %s filter-list prefix %s in\n", oa->name, PREFIX_NAME_IN(oa)); if (PREFIX_NAME_OUT(oa)) vty_out(vty, " area %s filter-list prefix %s out\n", oa->name, PREFIX_NAME_OUT(oa)); if (IMPORT_NAME(oa)) vty_out(vty, " area %s import-list %s\n", oa->name, IMPORT_NAME(oa)); if (EXPORT_NAME(oa)) vty_out(vty, " area %s export-list %s\n", oa->name, EXPORT_NAME(oa)); } } DEFUN (area_filter_list, area_filter_list_cmd, "area filter-list prefix WORD ", "OSPF6 area parameters\n" "OSPF6 area ID in IP address format\n" "OSPF6 area ID as a decimal value\n" "Filter networks between OSPF6 areas\n" "Filter prefixes between OSPF6 areas\n" "Name of an IPv6 prefix-list\n" "Filter networks sent to this area\n" "Filter networks sent from this area\n") { char *inout = argv[argc - 1]->text; char *areaid = argv[1]->arg; char *plistname = argv[4]->arg; struct ospf6_area *area; struct prefix_list *plist; OSPF6_CMD_AREA_GET(areaid, area); plist = prefix_list_lookup(AFI_IP6, plistname); if (strmatch(inout, "in")) { PREFIX_LIST_IN(area) = plist; XFREE(MTYPE_OSPF6_PLISTNAME, PREFIX_NAME_IN(area)); PREFIX_NAME_IN(area) = XSTRDUP(MTYPE_OSPF6_PLISTNAME, plistname); ospf6_abr_reimport(area); } else { PREFIX_LIST_OUT(area) = plist; XFREE(MTYPE_OSPF6_PLISTNAME, PREFIX_NAME_OUT(area)); PREFIX_NAME_OUT(area) = XSTRDUP(MTYPE_OSPF6_PLISTNAME, plistname); ospf6_abr_enable_area(area); } return CMD_SUCCESS; } DEFUN (no_area_filter_list, no_area_filter_list_cmd, "no area filter-list prefix WORD ", NO_STR "OSPF6 area parameters\n" "OSPF6 area ID in IP address format\n" "OSPF6 area ID as a decimal value\n" "Filter networks between OSPF6 areas\n" "Filter prefixes between OSPF6 areas\n" "Name of an IPv6 prefix-list\n" "Filter networks sent to this area\n" "Filter networks sent from this area\n") { char *inout = argv[argc - 1]->text; char *areaid = argv[2]->arg; char *plistname = argv[5]->arg; struct ospf6_area *area; OSPF6_CMD_AREA_GET(areaid, area); if (strmatch(inout, "in")) { if (PREFIX_NAME_IN(area)) if (!strmatch(PREFIX_NAME_IN(area), plistname)) return CMD_SUCCESS; PREFIX_LIST_IN(area) = NULL; XFREE(MTYPE_OSPF6_PLISTNAME, PREFIX_NAME_IN(area)); ospf6_abr_reimport(area); } else { if (PREFIX_NAME_OUT(area)) if (!strmatch(PREFIX_NAME_OUT(area), plistname)) return CMD_SUCCESS; XFREE(MTYPE_OSPF6_PLISTNAME, PREFIX_NAME_OUT(area)); ospf6_abr_enable_area(area); } return CMD_SUCCESS; } void ospf6_area_plist_update(struct prefix_list *plist, int add) { struct ospf6_area *oa; struct listnode *n; const char *name = prefix_list_name(plist); if (!ospf6) return; for (ALL_LIST_ELEMENTS_RO(ospf6->area_list, n, oa)) { if (PREFIX_NAME_IN(oa) && !strcmp(PREFIX_NAME_IN(oa), name)) PREFIX_LIST_IN(oa) = add ? plist : NULL; if (PREFIX_NAME_OUT(oa) && !strcmp(PREFIX_NAME_OUT(oa), name)) PREFIX_LIST_OUT(oa) = add ? plist : NULL; } } DEFUN (area_import_list, area_import_list_cmd, "area import-list NAME", "OSPF6 area parameters\n" "OSPF6 area ID in IP address format\n" "OSPF6 area ID as a decimal value\n" "Set the filter for networks from other areas announced to the specified one\n" "Name of the acess-list\n") { int idx_ipv4 = 1; int idx_name = 3; struct ospf6_area *area; struct access_list *list; OSPF6_CMD_AREA_GET(argv[idx_ipv4]->arg, area); list = access_list_lookup(AFI_IP6, argv[idx_name]->arg); IMPORT_LIST(area) = list; if (IMPORT_NAME(area)) free(IMPORT_NAME(area)); IMPORT_NAME(area) = strdup(argv[idx_name]->arg); ospf6_abr_reimport(area); return CMD_SUCCESS; } DEFUN (no_area_import_list, no_area_import_list_cmd, "no area import-list NAME", NO_STR "OSPF6 area parameters\n" "OSPF6 area ID in IP address format\n" "OSPF6 area ID as a decimal value\n" "Unset the filter for networks announced to other areas\n" "Name of the access-list\n") { int idx_ipv4 = 2; struct ospf6_area *area; OSPF6_CMD_AREA_GET(argv[idx_ipv4]->arg, area); IMPORT_LIST(area) = 0; if (IMPORT_NAME(area)) free(IMPORT_NAME(area)); IMPORT_NAME(area) = NULL; ospf6_abr_reimport(area); return CMD_SUCCESS; } DEFUN (area_export_list, area_export_list_cmd, "area export-list NAME", "OSPF6 area parameters\n" "OSPF6 area ID in IP address format\n" "OSPF6 area ID as a decimal value\n" "Set the filter for networks announced to other areas\n" "Name of the acess-list\n") { int idx_ipv4 = 1; int idx_name = 3; struct ospf6_area *area; struct access_list *list; OSPF6_CMD_AREA_GET(argv[idx_ipv4]->arg, area); list = access_list_lookup(AFI_IP6, argv[idx_name]->arg); EXPORT_LIST(area) = list; if (EXPORT_NAME(area)) free(EXPORT_NAME(area)); EXPORT_NAME(area) = strdup(argv[idx_name]->arg); ospf6_abr_enable_area(area); return CMD_SUCCESS; } DEFUN (no_area_export_list, no_area_export_list_cmd, "no area export-list NAME", NO_STR "OSPF6 area parameters\n" "OSPF6 area ID in IP address format\n" "OSPF6 area ID as a decimal value\n" "Unset the filter for networks announced to other areas\n" "Name of the access-list\n") { int idx_ipv4 = 2; struct ospf6_area *area; OSPF6_CMD_AREA_GET(argv[idx_ipv4]->arg, area); EXPORT_LIST(area) = 0; if (EXPORT_NAME(area)) free(EXPORT_NAME(area)); EXPORT_NAME(area) = NULL; ospf6_abr_enable_area(area); return CMD_SUCCESS; } DEFUN (show_ipv6_ospf6_spf_tree, show_ipv6_ospf6_spf_tree_cmd, "show ipv6 ospf6 spf tree", SHOW_STR IP6_STR OSPF6_STR "Shortest Path First calculation\n" "Show SPF tree\n") { struct listnode *node; struct ospf6_area *oa; struct ospf6_vertex *root; struct ospf6_route *route; struct prefix prefix; OSPF6_CMD_CHECK_RUNNING(); ospf6_linkstate_prefix(ospf6->router_id, htonl(0), &prefix); for (ALL_LIST_ELEMENTS_RO(ospf6->area_list, node, oa)) { route = ospf6_route_lookup(&prefix, oa->spf_table); if (route == NULL) { vty_out(vty, "LS entry for root not found in area %s\n", oa->name); continue; } root = (struct ospf6_vertex *)route->route_option; ospf6_spf_display_subtree(vty, "", 0, root); } return CMD_SUCCESS; } DEFUN (show_ipv6_ospf6_area_spf_tree, show_ipv6_ospf6_area_spf_tree_cmd, "show ipv6 ospf6 area A.B.C.D spf tree", SHOW_STR IP6_STR OSPF6_STR OSPF6_AREA_STR OSPF6_AREA_ID_STR "Shortest Path First calculation\n" "Show SPF tree\n") { int idx_ipv4 = 4; uint32_t area_id; struct ospf6_area *oa; struct ospf6_vertex *root; struct ospf6_route *route; struct prefix prefix; OSPF6_CMD_CHECK_RUNNING(); ospf6_linkstate_prefix(ospf6->router_id, htonl(0), &prefix); if (inet_pton(AF_INET, argv[idx_ipv4]->arg, &area_id) != 1) { vty_out(vty, "Malformed Area-ID: %s\n", argv[idx_ipv4]->arg); return CMD_SUCCESS; } oa = ospf6_area_lookup(area_id, ospf6); if (oa == NULL) { vty_out(vty, "No such Area: %s\n", argv[idx_ipv4]->arg); return CMD_SUCCESS; } route = ospf6_route_lookup(&prefix, oa->spf_table); if (route == NULL) { vty_out(vty, "LS entry for root not found in area %s\n", oa->name); return CMD_SUCCESS; } root = (struct ospf6_vertex *)route->route_option; ospf6_spf_display_subtree(vty, "", 0, root); return CMD_SUCCESS; } DEFUN (show_ipv6_ospf6_simulate_spf_tree_root, show_ipv6_ospf6_simulate_spf_tree_root_cmd, "show ipv6 ospf6 simulate spf-tree A.B.C.D area A.B.C.D", SHOW_STR IP6_STR OSPF6_STR "Shortest Path First calculation\n" "Show SPF tree\n" "Specify root's router-id to calculate another router's SPF tree\n" "OSPF6 area parameters\n" OSPF6_AREA_ID_STR) { int idx_ipv4 = 5; int idx_ipv4_2 = 7; uint32_t area_id; struct ospf6_area *oa; struct ospf6_vertex *root; struct ospf6_route *route; struct prefix prefix; uint32_t router_id; struct ospf6_route_table *spf_table; unsigned char tmp_debug_ospf6_spf = 0; OSPF6_CMD_CHECK_RUNNING(); inet_pton(AF_INET, argv[idx_ipv4]->arg, &router_id); ospf6_linkstate_prefix(router_id, htonl(0), &prefix); if (inet_pton(AF_INET, argv[idx_ipv4_2]->arg, &area_id) != 1) { vty_out(vty, "Malformed Area-ID: %s\n", argv[idx_ipv4_2]->arg); return CMD_SUCCESS; } oa = ospf6_area_lookup(area_id, ospf6); if (oa == NULL) { vty_out(vty, "No such Area: %s\n", argv[idx_ipv4_2]->arg); return CMD_SUCCESS; } tmp_debug_ospf6_spf = conf_debug_ospf6_spf; conf_debug_ospf6_spf = 0; spf_table = OSPF6_ROUTE_TABLE_CREATE(NONE, SPF_RESULTS); ospf6_spf_calculation(router_id, spf_table, oa); conf_debug_ospf6_spf = tmp_debug_ospf6_spf; route = ospf6_route_lookup(&prefix, spf_table); if (route == NULL) { ospf6_spf_table_finish(spf_table); ospf6_route_table_delete(spf_table); return CMD_SUCCESS; } root = (struct ospf6_vertex *)route->route_option; ospf6_spf_display_subtree(vty, "", 0, root); ospf6_spf_table_finish(spf_table); ospf6_route_table_delete(spf_table); return CMD_SUCCESS; } DEFUN (ospf6_area_stub, ospf6_area_stub_cmd, "area stub", "OSPF6 area parameters\n" "OSPF6 area ID in IP address format\n" "OSPF6 area ID as a decimal value\n" "Configure OSPF6 area as stub\n") { int idx_ipv4_number = 1; struct ospf6_area *area; OSPF6_CMD_AREA_GET(argv[idx_ipv4_number]->arg, area); if (!ospf6_area_stub_set(ospf6, area)) { vty_out(vty, "First deconfigure all virtual link through this area\n"); return CMD_WARNING_CONFIG_FAILED; } ospf6_area_no_summary_unset(ospf6, area); return CMD_SUCCESS; } DEFUN (ospf6_area_stub_no_summary, ospf6_area_stub_no_summary_cmd, "area stub no-summary", "OSPF6 stub parameters\n" "OSPF6 area ID in IP address format\n" "OSPF6 area ID as a decimal value\n" "Configure OSPF6 area as stub\n" "Do not inject inter-area routes into stub\n") { int idx_ipv4_number = 1; struct ospf6_area *area; OSPF6_CMD_AREA_GET(argv[idx_ipv4_number]->arg, area); if (!ospf6_area_stub_set(ospf6, area)) { vty_out(vty, "First deconfigure all virtual link through this area\n"); return CMD_WARNING_CONFIG_FAILED; } ospf6_area_no_summary_set(ospf6, area); return CMD_SUCCESS; } DEFUN (no_ospf6_area_stub, no_ospf6_area_stub_cmd, "no area stub", NO_STR "OSPF6 area parameters\n" "OSPF6 area ID in IP address format\n" "OSPF6 area ID as a decimal value\n" "Configure OSPF6 area as stub\n") { int idx_ipv4_number = 2; struct ospf6_area *area; OSPF6_CMD_AREA_GET(argv[idx_ipv4_number]->arg, area); ospf6_area_stub_unset(ospf6, area); ospf6_area_no_summary_unset(ospf6, area); return CMD_SUCCESS; } DEFUN (no_ospf6_area_stub_no_summary, no_ospf6_area_stub_no_summary_cmd, "no area stub no-summary", NO_STR "OSPF6 area parameters\n" "OSPF6 area ID in IP address format\n" "OSPF6 area ID as a decimal value\n" "Configure OSPF6 area as stub\n" "Do not inject inter-area routes into area\n") { int idx_ipv4_number = 2; struct ospf6_area *area; OSPF6_CMD_AREA_GET(argv[idx_ipv4_number]->arg, area); ospf6_area_stub_unset(ospf6, area); ospf6_area_no_summary_unset(ospf6, area); return CMD_SUCCESS; } void ospf6_area_init(void) { install_element(VIEW_NODE, &show_ipv6_ospf6_spf_tree_cmd); install_element(VIEW_NODE, &show_ipv6_ospf6_area_spf_tree_cmd); install_element(VIEW_NODE, &show_ipv6_ospf6_simulate_spf_tree_root_cmd); install_element(OSPF6_NODE, &area_range_cmd); install_element(OSPF6_NODE, &no_area_range_cmd); install_element(OSPF6_NODE, &ospf6_area_stub_no_summary_cmd); install_element(OSPF6_NODE, &ospf6_area_stub_cmd); install_element(OSPF6_NODE, &no_ospf6_area_stub_no_summary_cmd); install_element(OSPF6_NODE, &no_ospf6_area_stub_cmd); install_element(OSPF6_NODE, &area_import_list_cmd); install_element(OSPF6_NODE, &no_area_import_list_cmd); install_element(OSPF6_NODE, &area_export_list_cmd); install_element(OSPF6_NODE, &no_area_export_list_cmd); install_element(OSPF6_NODE, &area_filter_list_cmd); install_element(OSPF6_NODE, &no_area_filter_list_cmd); } void ospf6_area_interface_delete(struct ospf6_interface *oi) { struct ospf6_area *oa; struct listnode *node, *nnode; if (!ospf6) return; for (ALL_LIST_ELEMENTS(ospf6->area_list, node, nnode, oa)) if(listnode_lookup(oa->if_list, oi)) listnode_delete(oa->if_list, oi); } frr-7.2.1/ospf6d/ospf6_area.h0000644000000000000000000000754613610377563012627 00000000000000/* * Copyright (C) 2003 Yasuhiro Ohara * * This file is part of GNU Zebra. * * GNU Zebra is free software; you can redistribute it and/or modify it * under the terms of the GNU General Public License as published by the * Free Software Foundation; either version 2, or (at your option) any * later version. * * GNU Zebra 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; see the file COPYING; if not, write to the Free Software * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA */ #ifndef OSPF_AREA_H #define OSPF_AREA_H #include "ospf6_top.h" struct ospf6_area { /* Reference to Top data structure */ struct ospf6 *ospf6; /* Area-ID */ uint32_t area_id; #define OSPF6_AREA_FMT_DOTTEDQUAD 1 #define OSPF6_AREA_FMT_DECIMAL 2 /* Area-ID string */ char name[16]; /* flag */ uint8_t flag; /* OSPF Option */ uint8_t options[3]; /* Summary routes to be originated (includes Configured Address Ranges) */ struct ospf6_route_table *range_table; struct ospf6_route_table *summary_prefix; struct ospf6_route_table *summary_router; /* Area type */ int no_summary; /* Brouter traversal protection */ int intra_brouter_calc; /* OSPF interface list */ struct list *if_list; struct ospf6_lsdb *lsdb; struct ospf6_lsdb *lsdb_self; struct ospf6_lsdb *temp_router_lsa_lsdb; struct ospf6_route_table *spf_table; struct ospf6_route_table *route_table; uint32_t spf_calculation; /* SPF calculation count */ struct thread *thread_router_lsa; struct thread *thread_intra_prefix_lsa; uint32_t router_lsa_size_limit; /* Area announce list */ struct { char *name; struct access_list *list; } _export; #define EXPORT_NAME(A) (A)->_export.name #define EXPORT_LIST(A) (A)->_export.list /* Area acceptance list */ struct { char *name; struct access_list *list; } import; #define IMPORT_NAME(A) (A)->import.name #define IMPORT_LIST(A) (A)->import.list /* Type 3 LSA Area prefix-list */ struct { char *name; struct prefix_list *list; } plist_in; #define PREFIX_NAME_IN(A) (A)->plist_in.name #define PREFIX_LIST_IN(A) (A)->plist_in.list struct { char *name; struct prefix_list *list; } plist_out; #define PREFIX_NAME_OUT(A) (A)->plist_out.name #define PREFIX_LIST_OUT(A) (A)->plist_out.list /* Time stamps. */ struct timeval ts_spf; /* SPF calculation time stamp. */ uint32_t full_nbrs; /* Fully adjacent neighbors. */ uint8_t intra_prefix_originate; /* Force intra_prefix lsa originate */ }; #define OSPF6_AREA_ENABLE 0x01 #define OSPF6_AREA_ACTIVE 0x02 #define OSPF6_AREA_TRANSIT 0x04 /* TransitCapability */ #define OSPF6_AREA_STUB 0x08 #define IS_AREA_ENABLED(oa) (CHECK_FLAG ((oa)->flag, OSPF6_AREA_ENABLE)) #define IS_AREA_ACTIVE(oa) (CHECK_FLAG ((oa)->flag, OSPF6_AREA_ACTIVE)) #define IS_AREA_TRANSIT(oa) (CHECK_FLAG ((oa)->flag, OSPF6_AREA_TRANSIT)) #define IS_AREA_STUB(oa) (CHECK_FLAG ((oa)->flag, OSPF6_AREA_STUB)) /* prototypes */ extern int ospf6_area_cmp(void *va, void *vb); extern struct ospf6_area *ospf6_area_create(uint32_t, struct ospf6 *, int); extern void ospf6_area_delete(struct ospf6_area *); extern struct ospf6_area *ospf6_area_lookup(uint32_t, struct ospf6 *); extern void ospf6_area_enable(struct ospf6_area *); extern void ospf6_area_disable(struct ospf6_area *); extern void ospf6_area_show(struct vty *, struct ospf6_area *); extern void ospf6_area_plist_update(struct prefix_list *plist, int add); extern void ospf6_area_config_write(struct vty *vty); extern void ospf6_area_init(void); struct ospf6_interface; extern void ospf6_area_interface_delete(struct ospf6_interface *oi); #endif /* OSPF_AREA_H */ frr-7.2.1/ospf6d/ospf6_asbr.c0000644000000000000000000014763513610377563012645 00000000000000/* * Copyright (C) 2003 Yasuhiro Ohara * * This file is part of GNU Zebra. * * GNU Zebra is free software; you can redistribute it and/or modify it * under the terms of the GNU General Public License as published by the * Free Software Foundation; either version 2, or (at your option) any * later version. * * GNU Zebra 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; see the file COPYING; if not, write to the Free Software * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA */ #include #include "log.h" #include "memory.h" #include "prefix.h" #include "command.h" #include "vty.h" #include "routemap.h" #include "table.h" #include "plist.h" #include "thread.h" #include "linklist.h" #include "ospf6_proto.h" #include "ospf6_lsa.h" #include "ospf6_lsdb.h" #include "ospf6_route.h" #include "ospf6_zebra.h" #include "ospf6_message.h" #include "ospf6_top.h" #include "ospf6_area.h" #include "ospf6_interface.h" #include "ospf6_neighbor.h" #include "ospf6_asbr.h" #include "ospf6_intra.h" #include "ospf6_flood.h" #include "ospf6d.h" unsigned char conf_debug_ospf6_asbr = 0; #define ZROUTE_NAME(x) zebra_route_string(x) /* AS External LSA origination */ static void ospf6_as_external_lsa_originate(struct ospf6_route *route) { char buffer[OSPF6_MAX_LSASIZE]; struct ospf6_lsa_header *lsa_header; struct ospf6_lsa *lsa; struct ospf6_external_info *info = route->route_option; struct ospf6_as_external_lsa *as_external_lsa; char buf[PREFIX2STR_BUFFER]; caddr_t p; if (IS_OSPF6_DEBUG_ASBR || IS_OSPF6_DEBUG_ORIGINATE(AS_EXTERNAL)) { prefix2str(&route->prefix, buf, sizeof(buf)); zlog_debug("Originate AS-External-LSA for %s", buf); } /* prepare buffer */ memset(buffer, 0, sizeof(buffer)); lsa_header = (struct ospf6_lsa_header *)buffer; as_external_lsa = (struct ospf6_as_external_lsa *)((caddr_t)lsa_header + sizeof(struct ospf6_lsa_header)); p = (caddr_t)((caddr_t)as_external_lsa + sizeof(struct ospf6_as_external_lsa)); /* Fill AS-External-LSA */ /* Metric type */ if (route->path.metric_type == 2) SET_FLAG(as_external_lsa->bits_metric, OSPF6_ASBR_BIT_E); else UNSET_FLAG(as_external_lsa->bits_metric, OSPF6_ASBR_BIT_E); /* forwarding address */ if (!IN6_IS_ADDR_UNSPECIFIED(&info->forwarding)) SET_FLAG(as_external_lsa->bits_metric, OSPF6_ASBR_BIT_F); else UNSET_FLAG(as_external_lsa->bits_metric, OSPF6_ASBR_BIT_F); /* external route tag */ if (info->tag) SET_FLAG(as_external_lsa->bits_metric, OSPF6_ASBR_BIT_T); else UNSET_FLAG(as_external_lsa->bits_metric, OSPF6_ASBR_BIT_T); /* Set metric */ OSPF6_ASBR_METRIC_SET(as_external_lsa, route->path.cost); /* prefixlen */ as_external_lsa->prefix.prefix_length = route->prefix.prefixlen; /* PrefixOptions */ as_external_lsa->prefix.prefix_options = route->path.prefix_options; /* don't use refer LS-type */ as_external_lsa->prefix.prefix_refer_lstype = htons(0); /* set Prefix */ memcpy(p, &route->prefix.u.prefix6, OSPF6_PREFIX_SPACE(route->prefix.prefixlen)); ospf6_prefix_apply_mask(&as_external_lsa->prefix); p += OSPF6_PREFIX_SPACE(route->prefix.prefixlen); /* Forwarding address */ if (CHECK_FLAG(as_external_lsa->bits_metric, OSPF6_ASBR_BIT_F)) { memcpy(p, &info->forwarding, sizeof(struct in6_addr)); p += sizeof(struct in6_addr); } /* External Route Tag */ if (CHECK_FLAG(as_external_lsa->bits_metric, OSPF6_ASBR_BIT_T)) { route_tag_t network_order = htonl(info->tag); memcpy(p, &network_order, sizeof(network_order)); p += sizeof(network_order); } /* Fill LSA Header */ lsa_header->age = 0; lsa_header->type = htons(OSPF6_LSTYPE_AS_EXTERNAL); lsa_header->id = route->path.origin.id; lsa_header->adv_router = ospf6->router_id; lsa_header->seqnum = ospf6_new_ls_seqnum(lsa_header->type, lsa_header->id, lsa_header->adv_router, ospf6->lsdb); lsa_header->length = htons((caddr_t)p - (caddr_t)lsa_header); /* LSA checksum */ ospf6_lsa_checksum(lsa_header); /* create LSA */ lsa = ospf6_lsa_create(lsa_header); /* Originate */ ospf6_lsa_originate_process(lsa, ospf6); } int ospf6_orig_as_external_lsa(struct thread *thread) { struct ospf6_interface *oi; struct ospf6_lsa *lsa; uint32_t type, adv_router; oi = (struct ospf6_interface *)THREAD_ARG(thread); oi->thread_as_extern_lsa = NULL; if (oi->state == OSPF6_INTERFACE_DOWN) return 0; type = htons(OSPF6_LSTYPE_AS_EXTERNAL); adv_router = oi->area->ospf6->router_id; for (ALL_LSDB_TYPED_ADVRTR(ospf6->lsdb, type, adv_router, lsa)) { if (IS_OSPF6_DEBUG_ASBR) zlog_debug( "%s: Send update of AS-External LSA %s seq 0x%x", __PRETTY_FUNCTION__, lsa->name, ntohl(lsa->header->seqnum)); ospf6_flood_interface(NULL, lsa, oi); } return 0; } static route_tag_t ospf6_as_external_lsa_get_tag(struct ospf6_lsa *lsa) { struct ospf6_as_external_lsa *external; ptrdiff_t tag_offset; route_tag_t network_order; if (!lsa) return 0; external = (struct ospf6_as_external_lsa *)OSPF6_LSA_HEADER_END( lsa->header); if (!CHECK_FLAG(external->bits_metric, OSPF6_ASBR_BIT_T)) return 0; tag_offset = sizeof(*external) + OSPF6_PREFIX_SPACE(external->prefix.prefix_length); if (CHECK_FLAG(external->bits_metric, OSPF6_ASBR_BIT_F)) tag_offset += sizeof(struct in6_addr); memcpy(&network_order, (caddr_t)external + tag_offset, sizeof(network_order)); return ntohl(network_order); } void ospf6_asbr_update_route_ecmp_path(struct ospf6_route *old, struct ospf6_route *route) { struct ospf6_route *old_route; struct ospf6_path *ecmp_path, *o_path = NULL; struct listnode *anode, *anext; struct listnode *nnode, *rnode, *rnext; struct ospf6_nexthop *nh, *rnh; char buf[PREFIX2STR_BUFFER]; bool route_found = false; /* check for old entry match with new route origin, * delete old entry. */ for (old_route = old; old_route; old_route = old_route->next) { bool route_updated = false; if (!ospf6_route_is_same(old_route, route) || (old_route->path.type != route->path.type)) continue; /* Current and New route has same origin, * delete old entry. */ for (ALL_LIST_ELEMENTS(old_route->paths, anode, anext, o_path)) { /* Check old route path and route has same * origin. */ if (o_path->area_id != route->path.area_id || (memcmp(&(o_path)->origin, &(route)->path.origin, sizeof(struct ospf6_ls_origin)) != 0)) continue; /* Cost is not same then delete current path */ if ((o_path->cost == route->path.cost) && (o_path->u.cost_e2 == route->path.u.cost_e2)) continue; if (IS_OSPF6_DEBUG_EXAMIN(AS_EXTERNAL)) { prefix2str(&old_route->prefix, buf, sizeof(buf)); zlog_debug( "%s: route %s cost old %u new %u is not same, replace route", __PRETTY_FUNCTION__, buf, o_path->cost, route->path.cost); } /* Remove selected current rout path's nh from * effective nh list. */ for (ALL_LIST_ELEMENTS_RO(o_path->nh_list, nnode, nh)) { for (ALL_LIST_ELEMENTS(old_route->nh_list, rnode, rnext, rnh)) { if (!ospf6_nexthop_is_same(rnh, nh)) continue; listnode_delete(old_route->nh_list, rnh); ospf6_nexthop_delete(rnh); } } listnode_delete(old_route->paths, o_path); ospf6_path_free(o_path); route_updated = true; /* Current route's path (adv_router info) is similar * to route being added. * Replace current route's path with paths list head. * Update FIB with effective NHs. */ if (listcount(old_route->paths)) { for (ALL_LIST_ELEMENTS(old_route->paths, anode, anext, o_path)) { ospf6_merge_nexthops( old_route->nh_list, o_path->nh_list); } /* Update RIB/FIB with effective * nh_list */ if (ospf6->route_table->hook_add) (*ospf6->route_table->hook_add) (old_route); if (old_route->path.origin.id == route->path.origin.id && old_route->path.origin.adv_router == route->path.origin .adv_router) { struct ospf6_path *h_path; h_path = (struct ospf6_path *) listgetdata(listhead( old_route->paths)); old_route->path.origin.type = h_path->origin.type; old_route->path.origin.id = h_path->origin.id; old_route->path.origin.adv_router = h_path->origin.adv_router; } } else { if (IS_OSPF6_DEBUG_EXAMIN(AS_EXTERNAL)) { prefix2str(&old_route->prefix, buf, sizeof(buf)); zlog_debug( "%s: route %s old cost %u new cost %u, delete old entry.", __PRETTY_FUNCTION__, buf, old_route->path.cost, route->path.cost); } ospf6_route_remove(old_route, ospf6->route_table); } } if (route_updated) break; } /* Add new route */ for (old_route = old; old_route; old_route = old_route->next) { /* Current and New Route prefix or route type * is not same skip this current node. */ if (!ospf6_route_is_same(old_route, route) || (old_route->path.type != route->path.type)) continue; /* Old Route and New Route have Equal Cost, Merge NHs */ if ((old_route->path.cost == route->path.cost) && (old_route->path.u.cost_e2 == route->path.u.cost_e2)) { if (IS_OSPF6_DEBUG_EXAMIN(AS_EXTERNAL)) { prefix2str(&old_route->prefix, buf, sizeof(buf)); zlog_debug( "%s: old route %s path cost %u e2 %u", __PRETTY_FUNCTION__, buf, old_route->path.cost, old_route->path.u.cost_e2); } route_found = true; /* check if this path exists already in * route->paths list, if so, replace nh_list * from asbr_entry. */ for (ALL_LIST_ELEMENTS_RO(old_route->paths, anode, o_path)) { if (o_path->area_id == route->path.area_id && (memcmp(&(o_path)->origin, &(route)->path.origin, sizeof(struct ospf6_ls_origin)) == 0)) break; } /* If path is not found in old_route paths's list, * add a new path to route paths list and merge * nexthops in route->path->nh_list. * Otherwise replace existing path's nh_list. */ if (o_path == NULL) { ecmp_path = ospf6_path_dup(&route->path); /* Add a nh_list to new ecmp path */ ospf6_copy_nexthops(ecmp_path->nh_list, route->nh_list); /* Add the new path to route's path list */ listnode_add_sort(old_route->paths, ecmp_path); if (IS_OSPF6_DEBUG_EXAMIN(AS_EXTERNAL)) { prefix2str(&route->prefix, buf, sizeof(buf)); zlog_debug( "%s: route %s another path added with nh %u, effective paths %u nh %u", __PRETTY_FUNCTION__, buf, listcount(ecmp_path->nh_list), old_route->paths ? listcount( old_route ->paths) : 0, listcount(old_route->nh_list)); } } else { list_delete_all_node(o_path->nh_list); ospf6_copy_nexthops(o_path->nh_list, route->nh_list); } /* Reset nexthop lists, rebuild from brouter table * for each adv. router. */ list_delete_all_node(old_route->nh_list); for (ALL_LIST_ELEMENTS_RO(old_route->paths, anode, o_path)) { struct ospf6_route *asbr_entry; asbr_entry = ospf6_route_lookup( &o_path->ls_prefix, ospf6->brouter_table); if (asbr_entry == NULL) { if (IS_OSPF6_DEBUG_EXAMIN( AS_EXTERNAL)) { prefix2str(&old_route->prefix, buf, sizeof(buf)); zlog_debug("%s: ls_prfix %s asbr_entry not found.", __PRETTY_FUNCTION__, buf); } continue; } ospf6_route_merge_nexthops(old_route, asbr_entry); } if (IS_OSPF6_DEBUG_EXAMIN(AS_EXTERNAL)) { prefix2str(&route->prefix, buf, sizeof(buf)); zlog_debug("%s: route %s with effective paths %u nh %u", __PRETTY_FUNCTION__, buf, old_route->paths ? listcount(old_route->paths) : 0, old_route->nh_list ? listcount(old_route->nh_list) : 0); } /* Update RIB/FIB */ if (ospf6->route_table->hook_add) (*ospf6->route_table->hook_add)(old_route); /* Delete the new route its info added to existing * route. */ ospf6_route_delete(route); break; } } if (!route_found) { /* Add new route to existing node in ospf6 route table. */ ospf6_route_add(route, ospf6->route_table); } } void ospf6_asbr_lsa_add(struct ospf6_lsa *lsa) { struct ospf6_as_external_lsa *external; struct prefix asbr_id; struct ospf6_route *asbr_entry, *route, *old; struct ospf6_path *path; char buf[PREFIX2STR_BUFFER]; external = (struct ospf6_as_external_lsa *)OSPF6_LSA_HEADER_END( lsa->header); if (IS_OSPF6_DEBUG_EXAMIN(AS_EXTERNAL)) zlog_debug("Calculate AS-External route for %s", lsa->name); if (lsa->header->adv_router == ospf6->router_id) { if (IS_OSPF6_DEBUG_EXAMIN(AS_EXTERNAL)) zlog_debug("Ignore self-originated AS-External-LSA"); return; } if (OSPF6_ASBR_METRIC(external) == OSPF_LS_INFINITY) { if (IS_OSPF6_DEBUG_EXAMIN(AS_EXTERNAL)) zlog_debug("Ignore LSA with LSInfinity Metric"); return; } if (CHECK_FLAG(external->prefix.prefix_options, OSPF6_PREFIX_OPTION_NU)) { if (IS_OSPF6_DEBUG_EXAMIN(AS_EXTERNAL)) zlog_debug("Ignore LSA with NU bit set Metric"); return; } ospf6_linkstate_prefix(lsa->header->adv_router, htonl(0), &asbr_id); asbr_entry = ospf6_route_lookup(&asbr_id, ospf6->brouter_table); if (asbr_entry == NULL || !CHECK_FLAG(asbr_entry->path.router_bits, OSPF6_ROUTER_BIT_E)) { if (IS_OSPF6_DEBUG_EXAMIN(AS_EXTERNAL)) { prefix2str(&asbr_id, buf, sizeof(buf)); zlog_debug("ASBR entry not found: %s", buf); } return; } route = ospf6_route_create(); route->type = OSPF6_DEST_TYPE_NETWORK; route->prefix.family = AF_INET6; route->prefix.prefixlen = external->prefix.prefix_length; ospf6_prefix_in6_addr(&route->prefix.u.prefix6, external, &external->prefix); route->path.area_id = asbr_entry->path.area_id; route->path.origin.type = lsa->header->type; route->path.origin.id = lsa->header->id; route->path.origin.adv_router = lsa->header->adv_router; route->path.prefix_options = external->prefix.prefix_options; memcpy(&route->path.ls_prefix, &asbr_id, sizeof(struct prefix)); if (CHECK_FLAG(external->bits_metric, OSPF6_ASBR_BIT_E)) { route->path.type = OSPF6_PATH_TYPE_EXTERNAL2; route->path.metric_type = 2; route->path.cost = asbr_entry->path.cost; route->path.u.cost_e2 = OSPF6_ASBR_METRIC(external); } else { route->path.type = OSPF6_PATH_TYPE_EXTERNAL1; route->path.metric_type = 1; route->path.cost = asbr_entry->path.cost + OSPF6_ASBR_METRIC(external); route->path.u.cost_e2 = 0; } route->path.tag = ospf6_as_external_lsa_get_tag(lsa); ospf6_route_copy_nexthops(route, asbr_entry); path = ospf6_path_dup(&route->path); ospf6_copy_nexthops(path->nh_list, asbr_entry->nh_list); listnode_add_sort(route->paths, path); if (IS_OSPF6_DEBUG_EXAMIN(AS_EXTERNAL)) { prefix2str(&route->prefix, buf, sizeof(buf)); zlog_debug("%s: AS-External %u route add %s cost %u(%u) nh %u", __PRETTY_FUNCTION__, (route->path.type == OSPF6_PATH_TYPE_EXTERNAL1) ? 1 : 2, buf, route->path.cost, route->path.u.cost_e2, listcount(route->nh_list)); } old = ospf6_route_lookup(&route->prefix, ospf6->route_table); if (!old) { /* Add the new route to ospf6 instance route table. */ ospf6_route_add(route, ospf6->route_table); } else { /* RFC 2328 16.4 (6) * ECMP: Keep new equal preference path in current * route's path list, update zebra with new effective * list along with addition of ECMP path. */ ospf6_asbr_update_route_ecmp_path(old, route); } } void ospf6_asbr_lsa_remove(struct ospf6_lsa *lsa, struct ospf6_route *asbr_entry) { struct ospf6_as_external_lsa *external; struct prefix prefix; struct ospf6_route *route, *nroute, *route_to_del; char buf[PREFIX2STR_BUFFER]; external = (struct ospf6_as_external_lsa *)OSPF6_LSA_HEADER_END( lsa->header); if (IS_OSPF6_DEBUG_EXAMIN(AS_EXTERNAL)) zlog_debug("Withdraw AS-External route for %s", lsa->name); if (lsa->header->adv_router == ospf6->router_id) { if (IS_OSPF6_DEBUG_EXAMIN(AS_EXTERNAL)) zlog_debug("Ignore self-originated AS-External-LSA"); return; } route_to_del = ospf6_route_create(); route_to_del->type = OSPF6_DEST_TYPE_NETWORK; route_to_del->prefix.family = AF_INET6; route_to_del->prefix.prefixlen = external->prefix.prefix_length; ospf6_prefix_in6_addr(&route_to_del->prefix.u.prefix6, external, &external->prefix); route_to_del->path.origin.type = lsa->header->type; route_to_del->path.origin.id = lsa->header->id; route_to_del->path.origin.adv_router = lsa->header->adv_router; if (asbr_entry) { route_to_del->path.area_id = asbr_entry->path.area_id; if (CHECK_FLAG(external->bits_metric, OSPF6_ASBR_BIT_E)) { route_to_del->path.type = OSPF6_PATH_TYPE_EXTERNAL2; route_to_del->path.metric_type = 2; route_to_del->path.cost = asbr_entry->path.cost; route_to_del->path.u.cost_e2 = OSPF6_ASBR_METRIC(external); } else { route_to_del->path.type = OSPF6_PATH_TYPE_EXTERNAL1; route_to_del->path.metric_type = 1; route_to_del->path.cost = asbr_entry->path.cost + OSPF6_ASBR_METRIC(external); route_to_del->path.u.cost_e2 = 0; } } memset(&prefix, 0, sizeof(struct prefix)); prefix.family = AF_INET6; prefix.prefixlen = external->prefix.prefix_length; ospf6_prefix_in6_addr(&prefix.u.prefix6, external, &external->prefix); route = ospf6_route_lookup(&prefix, ospf6->route_table); if (route == NULL) { if (IS_OSPF6_DEBUG_EXAMIN(AS_EXTERNAL)) { prefix2str(&prefix, buf, sizeof(buf)); zlog_debug("AS-External route %s not found", buf); } ospf6_route_delete(route_to_del); return; } if (IS_OSPF6_DEBUG_EXAMIN(AS_EXTERNAL)) { prefix2str(&prefix, buf, sizeof(buf)); zlog_debug( "%s: Current route %s cost %u e2 %u, route to del cost %u e2 %u", __PRETTY_FUNCTION__, buf, route->path.cost, route->path.u.cost_e2, route_to_del->path.cost, route_to_del->path.u.cost_e2); } for (ospf6_route_lock(route); route && ospf6_route_is_prefix(&prefix, route); route = nroute) { nroute = ospf6_route_next(route); if (route->type != OSPF6_DEST_TYPE_NETWORK) continue; /* Route has multiple ECMP paths, remove matching * path. Update current route's effective nh list * after removal of one of the path. */ if (listcount(route->paths) > 1) { struct listnode *anode, *anext; struct listnode *nnode, *rnode, *rnext; struct ospf6_nexthop *nh, *rnh; struct ospf6_path *o_path; bool nh_updated = false; /* Iterate all paths of route to find maching with LSA * remove from route path list. If route->path is same, * replace from paths list. */ for (ALL_LIST_ELEMENTS(route->paths, anode, anext, o_path)) { if ((o_path->origin.type != lsa->header->type) || (o_path->origin.adv_router != lsa->header->adv_router) || (o_path->origin.id != lsa->header->id)) continue; /* Compare LSA cost with current * route info. */ if (!asbr_entry && (o_path->cost != route_to_del->path.cost || o_path->u.cost_e2 != route_to_del->path.u .cost_e2)) { if (IS_OSPF6_DEBUG_EXAMIN( AS_EXTERNAL)) { prefix2str(&prefix, buf, sizeof(buf)); zlog_debug( "%s: route %s to delete is not same, cost %u del cost %u. skip", __PRETTY_FUNCTION__, buf, route->path.cost, route_to_del->path .cost); } continue; } if (IS_OSPF6_DEBUG_EXAMIN(AS_EXTERNAL)) { prefix2str(&prefix, buf, sizeof(buf)); zlog_debug( "%s: route %s path found with cost %u nh %u to remove.", __PRETTY_FUNCTION__, buf, route->path.cost, listcount(o_path->nh_list)); } /* Remove found path's nh_list from * the route's nh_list. */ for (ALL_LIST_ELEMENTS_RO(o_path->nh_list, nnode, nh)) { for (ALL_LIST_ELEMENTS(route->nh_list, rnode, rnext, rnh)) { if (!ospf6_nexthop_is_same(rnh, nh)) continue; listnode_delete(route->nh_list, rnh); ospf6_nexthop_delete(rnh); } } /* Delete the path from route's path list */ listnode_delete(route->paths, o_path); ospf6_path_free(o_path); nh_updated = true; } if (nh_updated) { /* Iterate all paths and merge nexthop, * unlesss any of the nexthop similar to * ones deleted as part of path deletion. */ for (ALL_LIST_ELEMENTS(route->paths, anode, anext, o_path)) { ospf6_merge_nexthops(route->nh_list, o_path->nh_list); } if (IS_OSPF6_DEBUG_EXAMIN(AS_EXTERNAL)) { prefix2str(&route->prefix, buf, sizeof(buf)); zlog_debug( "%s: AS-External %u route %s update paths %u nh %u", __PRETTY_FUNCTION__, (route->path.type == OSPF6_PATH_TYPE_EXTERNAL1) ? 1 : 2, buf, listcount(route->paths), route->nh_list ? listcount(route->nh_list) : 0); } if (listcount(route->paths)) { /* Update RIB/FIB with effective * nh_list */ if (ospf6->route_table->hook_add) (*ospf6->route_table->hook_add) (route); /* route's primary path is similar * to LSA, replace route's primary * path with route's paths list head. */ if ((route->path.origin.id == lsa->header->id) && (route->path.origin.adv_router == lsa->header->adv_router)) { struct ospf6_path *h_path; h_path = (struct ospf6_path *) listgetdata( listhead(route->paths)); route->path.origin.type = h_path->origin.type; route->path.origin.id = h_path->origin.id; route->path.origin.adv_router = h_path->origin.adv_router; } } else { ospf6_route_remove(route, ospf6->route_table); } } continue; } else { /* Compare LSA origin and cost with current route info. * if any check fails skip del this route node. */ if (asbr_entry && (!ospf6_route_is_same_origin(route, route_to_del) || (route->path.type != route_to_del->path.type) || (route->path.cost != route_to_del->path.cost) || (route->path.u.cost_e2 != route_to_del->path.u.cost_e2))) { if (IS_OSPF6_DEBUG_EXAMIN(AS_EXTERNAL)) { prefix2str(&prefix, buf, sizeof(buf)); zlog_debug( "%s: route %s to delete is not same, cost %u del cost %u. skip", __PRETTY_FUNCTION__, buf, route->path.cost, route_to_del->path.cost); } continue; } if ((route->path.origin.type != lsa->header->type) || (route->path.origin.adv_router != lsa->header->adv_router) || (route->path.origin.id != lsa->header->id)) continue; } if (IS_OSPF6_DEBUG_EXAMIN(AS_EXTERNAL)) { prefix2str(&route->prefix, buf, sizeof(buf)); zlog_debug( "%s: AS-External %u route remove %s cost %u(%u) nh %u", __PRETTY_FUNCTION__, route->path.type == OSPF6_PATH_TYPE_EXTERNAL1 ? 1 : 2, buf, route->path.cost, route->path.u.cost_e2, listcount(route->nh_list)); } ospf6_route_remove(route, ospf6->route_table); } if (route != NULL) ospf6_route_unlock(route); ospf6_route_delete(route_to_del); } void ospf6_asbr_lsentry_add(struct ospf6_route *asbr_entry) { struct ospf6_lsa *lsa; uint16_t type; uint32_t router; if (!CHECK_FLAG(asbr_entry->flag, OSPF6_ROUTE_BEST)) { char buf[16]; inet_ntop(AF_INET, &ADV_ROUTER_IN_PREFIX(&asbr_entry->prefix), buf, sizeof(buf)); zlog_info("ignore non-best path: lsentry %s add", buf); return; } type = htons(OSPF6_LSTYPE_AS_EXTERNAL); router = ospf6_linkstate_prefix_adv_router(&asbr_entry->prefix); for (ALL_LSDB_TYPED_ADVRTR(ospf6->lsdb, type, router, lsa)) { if (!OSPF6_LSA_IS_MAXAGE(lsa)) ospf6_asbr_lsa_add(lsa); } } void ospf6_asbr_lsentry_remove(struct ospf6_route *asbr_entry) { struct ospf6_lsa *lsa; uint16_t type; uint32_t router; type = htons(OSPF6_LSTYPE_AS_EXTERNAL); router = ospf6_linkstate_prefix_adv_router(&asbr_entry->prefix); for (ALL_LSDB_TYPED_ADVRTR(ospf6->lsdb, type, router, lsa)) ospf6_asbr_lsa_remove(lsa, asbr_entry); } /* redistribute function */ static void ospf6_asbr_routemap_set(int type, const char *mapname) { if (ospf6->rmap[type].name) { route_map_counter_decrement(ospf6->rmap[type].map); free(ospf6->rmap[type].name); } ospf6->rmap[type].name = strdup(mapname); ospf6->rmap[type].map = route_map_lookup_by_name(mapname); route_map_counter_increment(ospf6->rmap[type].map); } static void ospf6_asbr_routemap_unset(int type) { if (ospf6->rmap[type].name) free(ospf6->rmap[type].name); route_map_counter_decrement(ospf6->rmap[type].map); ospf6->rmap[type].name = NULL; ospf6->rmap[type].map = NULL; } static int ospf6_asbr_routemap_update_timer(struct thread *thread) { void **arg; int arg_type; arg = THREAD_ARG(thread); arg_type = (int)(intptr_t)arg[1]; ospf6->t_distribute_update = NULL; if (ospf6->rmap[arg_type].name) ospf6->rmap[arg_type].map = route_map_lookup_by_name(ospf6->rmap[arg_type].name); if (ospf6->rmap[arg_type].map) { if (IS_OSPF6_DEBUG_ASBR) zlog_debug("%s: route-map %s update, reset redist %s", __PRETTY_FUNCTION__, ospf6->rmap[arg_type].name, ZROUTE_NAME(arg_type)); ospf6_zebra_no_redistribute(arg_type); ospf6_zebra_redistribute(arg_type); } XFREE(MTYPE_OSPF6_DIST_ARGS, arg); return 0; } void ospf6_asbr_distribute_list_update(int type) { void **args = NULL; if (ospf6->t_distribute_update) return; args = XCALLOC(MTYPE_OSPF6_DIST_ARGS, sizeof(void *) * 2); args[0] = ospf6; args[1] = (void *)((ptrdiff_t)type); if (IS_OSPF6_DEBUG_ASBR) zlog_debug("%s: trigger redistribute %s reset thread", __PRETTY_FUNCTION__, ZROUTE_NAME(type)); ospf6->t_distribute_update = NULL; thread_add_timer_msec(master, ospf6_asbr_routemap_update_timer, (void **)args, OSPF_MIN_LS_INTERVAL, &ospf6->t_distribute_update); } static void ospf6_asbr_routemap_update(const char *mapname) { int type; if (ospf6 == NULL) return; for (type = 0; type < ZEBRA_ROUTE_MAX; type++) { if (ospf6->rmap[type].name) { ospf6->rmap[type].map = route_map_lookup_by_name( ospf6->rmap[type].name); if (mapname && ospf6->rmap[type].map && (strcmp(ospf6->rmap[type].name, mapname) == 0)) { if (IS_OSPF6_DEBUG_ASBR) zlog_debug( "%s: route-map %s update, reset redist %s", __PRETTY_FUNCTION__, mapname, ZROUTE_NAME(type)); route_map_counter_increment( ospf6->rmap[type].map); ospf6_asbr_distribute_list_update(type); } } else ospf6->rmap[type].map = NULL; } } static void ospf6_asbr_routemap_event(const char *name) { int type; if (ospf6 == NULL) return; for (type = 0; type < ZEBRA_ROUTE_MAX; type++) { if ((ospf6->rmap[type].name) && (strcmp(ospf6->rmap[type].name, name) == 0)) { ospf6_asbr_distribute_list_update(type); } } } int ospf6_asbr_is_asbr(struct ospf6 *o) { return o->external_table->count; } static void ospf6_asbr_redistribute_set(int type) { ospf6_zebra_redistribute(type); } static void ospf6_asbr_redistribute_unset(int type) { struct ospf6_route *route; struct ospf6_external_info *info; ospf6_zebra_no_redistribute(type); for (route = ospf6_route_head(ospf6->external_table); route; route = ospf6_route_next(route)) { info = route->route_option; if (info->type != type) continue; ospf6_asbr_redistribute_remove(info->type, 0, &route->prefix); } ospf6_asbr_routemap_unset(type); } /* When an area is unstubified, flood all the external LSAs in the area */ void ospf6_asbr_send_externals_to_area(struct ospf6_area *oa) { struct ospf6_lsa *lsa; for (ALL_LSDB(oa->ospf6->lsdb, lsa)) { if (ntohs(lsa->header->type) == OSPF6_LSTYPE_AS_EXTERNAL) { zlog_debug("%s: Flooding AS-External LSA %s", __func__, lsa->name); ospf6_flood_area(NULL, lsa, oa); } } } void ospf6_asbr_redistribute_add(int type, ifindex_t ifindex, struct prefix *prefix, unsigned int nexthop_num, struct in6_addr *nexthop, route_tag_t tag) { route_map_result_t ret; struct ospf6_route troute; struct ospf6_external_info tinfo; struct ospf6_route *route, *match; struct ospf6_external_info *info; struct prefix prefix_id; struct route_node *node; char pbuf[PREFIX2STR_BUFFER], ibuf[16]; struct listnode *lnode, *lnnode; struct ospf6_area *oa; if (!ospf6_zebra_is_redistribute(type)) return; memset(&troute, 0, sizeof(troute)); memset(&tinfo, 0, sizeof(tinfo)); if (IS_OSPF6_DEBUG_ASBR) { prefix2str(prefix, pbuf, sizeof(pbuf)); zlog_debug("Redistribute %s (%s)", pbuf, ZROUTE_NAME(type)); } /* if route-map was specified but not found, do not advertise */ if (ospf6->rmap[type].name) { if (ospf6->rmap[type].map == NULL) ospf6_asbr_routemap_update(NULL); if (ospf6->rmap[type].map == NULL) { zlog_warn( "route-map \"%s\" not found, suppress redistributing", ospf6->rmap[type].name); return; } } /* apply route-map */ if (ospf6->rmap[type].map) { troute.route_option = &tinfo; tinfo.ifindex = ifindex; tinfo.tag = tag; ret = route_map_apply(ospf6->rmap[type].map, prefix, RMAP_OSPF6, &troute); if (ret == RMAP_DENYMATCH) { if (IS_OSPF6_DEBUG_ASBR) zlog_debug("Denied by route-map \"%s\"", ospf6->rmap[type].name); return; } } match = ospf6_route_lookup(prefix, ospf6->external_table); if (match) { info = match->route_option; /* copy result of route-map */ if (ospf6->rmap[type].map) { if (troute.path.metric_type) match->path.metric_type = troute.path.metric_type; if (troute.path.cost) match->path.cost = troute.path.cost; if (!IN6_IS_ADDR_UNSPECIFIED(&tinfo.forwarding)) memcpy(&info->forwarding, &tinfo.forwarding, sizeof(struct in6_addr)); info->tag = tinfo.tag; } else { /* If there is no route-map, simply update the tag */ info->tag = tag; } info->type = type; if (nexthop_num && nexthop) ospf6_route_add_nexthop(match, ifindex, nexthop); else ospf6_route_add_nexthop(match, ifindex, NULL); /* create/update binding in external_id_table */ prefix_id.family = AF_INET; prefix_id.prefixlen = 32; prefix_id.u.prefix4.s_addr = htonl(info->id); node = route_node_get(ospf6->external_id_table, &prefix_id); node->info = match; if (IS_OSPF6_DEBUG_ASBR) { inet_ntop(AF_INET, &prefix_id.u.prefix4, ibuf, sizeof(ibuf)); prefix2str(prefix, pbuf, sizeof(pbuf)); zlog_debug( "Advertise as AS-External Id:%s prefix %s metric %u", ibuf, pbuf, match->path.metric_type); } match->path.origin.id = htonl(info->id); ospf6_as_external_lsa_originate(match); return; } /* create new entry */ route = ospf6_route_create(); route->type = OSPF6_DEST_TYPE_NETWORK; memcpy(&route->prefix, prefix, sizeof(struct prefix)); info = (struct ospf6_external_info *)XCALLOC( MTYPE_OSPF6_EXTERNAL_INFO, sizeof(struct ospf6_external_info)); route->route_option = info; info->id = ospf6->external_id++; /* copy result of route-map */ if (ospf6->rmap[type].map) { if (troute.path.metric_type) route->path.metric_type = troute.path.metric_type; if (troute.path.cost) route->path.cost = troute.path.cost; if (!IN6_IS_ADDR_UNSPECIFIED(&tinfo.forwarding)) memcpy(&info->forwarding, &tinfo.forwarding, sizeof(struct in6_addr)); info->tag = tinfo.tag; } else { /* If there is no route-map, simply set the tag */ info->tag = tag; } info->type = type; if (nexthop_num && nexthop) ospf6_route_add_nexthop(route, ifindex, nexthop); else ospf6_route_add_nexthop(route, ifindex, NULL); /* create/update binding in external_id_table */ prefix_id.family = AF_INET; prefix_id.prefixlen = 32; prefix_id.u.prefix4.s_addr = htonl(info->id); node = route_node_get(ospf6->external_id_table, &prefix_id); node->info = route; route = ospf6_route_add(route, ospf6->external_table); route->route_option = info; if (IS_OSPF6_DEBUG_ASBR) { inet_ntop(AF_INET, &prefix_id.u.prefix4, ibuf, sizeof(ibuf)); prefix2str(prefix, pbuf, sizeof(pbuf)); zlog_debug("Advertise as AS-External Id:%s prefix %s metric %u", ibuf, pbuf, route->path.metric_type); } route->path.origin.id = htonl(info->id); ospf6_as_external_lsa_originate(route); /* Router-Bit (ASBR Flag) may have to be updated */ for (ALL_LIST_ELEMENTS(ospf6->area_list, lnode, lnnode, oa)) OSPF6_ROUTER_LSA_SCHEDULE(oa); } void ospf6_asbr_redistribute_remove(int type, ifindex_t ifindex, struct prefix *prefix) { struct ospf6_route *match; struct ospf6_external_info *info = NULL; struct route_node *node; struct ospf6_lsa *lsa; struct prefix prefix_id; char pbuf[PREFIX2STR_BUFFER], ibuf[16]; struct listnode *lnode, *lnnode; struct ospf6_area *oa; match = ospf6_route_lookup(prefix, ospf6->external_table); if (match == NULL) { if (IS_OSPF6_DEBUG_ASBR) { prefix2str(prefix, pbuf, sizeof(pbuf)); zlog_debug("No such route %s to withdraw", pbuf); } return; } info = match->route_option; assert(info); if (info->type != type) { if (IS_OSPF6_DEBUG_ASBR) { prefix2str(prefix, pbuf, sizeof(pbuf)); zlog_debug("Original protocol mismatch: %s", pbuf); } return; } if (IS_OSPF6_DEBUG_ASBR) { prefix2str(prefix, pbuf, sizeof(pbuf)); inet_ntop(AF_INET, &prefix_id.u.prefix4, ibuf, sizeof(ibuf)); zlog_debug("Withdraw %s (AS-External Id:%s)", pbuf, ibuf); } lsa = ospf6_lsdb_lookup(htons(OSPF6_LSTYPE_AS_EXTERNAL), htonl(info->id), ospf6->router_id, ospf6->lsdb); if (lsa) ospf6_lsa_purge(lsa); /* remove binding in external_id_table */ prefix_id.family = AF_INET; prefix_id.prefixlen = 32; prefix_id.u.prefix4.s_addr = htonl(info->id); node = route_node_lookup(ospf6->external_id_table, &prefix_id); assert(node); node->info = NULL; route_unlock_node(node); /* to free the lookup lock */ route_unlock_node(node); /* to free the original lock */ ospf6_route_remove(match, ospf6->external_table); XFREE(MTYPE_OSPF6_EXTERNAL_INFO, info); /* Router-Bit (ASBR Flag) may have to be updated */ for (ALL_LIST_ELEMENTS(ospf6->area_list, lnode, lnnode, oa)) OSPF6_ROUTER_LSA_SCHEDULE(oa); } DEFUN (ospf6_redistribute, ospf6_redistribute_cmd, "redistribute " FRR_REDIST_STR_OSPF6D, "Redistribute\n" FRR_REDIST_HELP_STR_OSPF6D) { int type; char *proto = argv[argc - 1]->text; type = proto_redistnum(AFI_IP6, proto); if (type < 0) return CMD_WARNING_CONFIG_FAILED; ospf6_asbr_redistribute_unset(type); ospf6_asbr_redistribute_set(type); return CMD_SUCCESS; } DEFUN (ospf6_redistribute_routemap, ospf6_redistribute_routemap_cmd, "redistribute " FRR_REDIST_STR_OSPF6D " route-map WORD", "Redistribute\n" FRR_REDIST_HELP_STR_OSPF6D "Route map reference\n" "Route map name\n") { int idx_protocol = 1; int idx_word = 3; int type; char *proto = argv[idx_protocol]->text; type = proto_redistnum(AFI_IP6, proto); if (type < 0) return CMD_WARNING_CONFIG_FAILED; ospf6_asbr_redistribute_unset(type); ospf6_asbr_routemap_set(type, argv[idx_word]->arg); ospf6_asbr_redistribute_set(type); return CMD_SUCCESS; } DEFUN (no_ospf6_redistribute, no_ospf6_redistribute_cmd, "no redistribute " FRR_REDIST_STR_OSPF6D " [route-map WORD]", NO_STR "Redistribute\n" FRR_REDIST_HELP_STR_OSPF6D "Route map reference\n" "Route map name\n") { int idx_protocol = 2; int type; char *proto = argv[idx_protocol]->text; type = proto_redistnum(AFI_IP6, proto); if (type < 0) return CMD_WARNING_CONFIG_FAILED; ospf6_asbr_redistribute_unset(type); return CMD_SUCCESS; } int ospf6_redistribute_config_write(struct vty *vty) { int type; for (type = 0; type < ZEBRA_ROUTE_MAX; type++) { if (type == ZEBRA_ROUTE_OSPF6) continue; if (!ospf6_zebra_is_redistribute(type)) continue; if (ospf6->rmap[type].name) vty_out(vty, " redistribute %s route-map %s\n", ZROUTE_NAME(type), ospf6->rmap[type].name); else vty_out(vty, " redistribute %s\n", ZROUTE_NAME(type)); } return 0; } static void ospf6_redistribute_show_config(struct vty *vty) { int type; int nroute[ZEBRA_ROUTE_MAX]; int total; struct ospf6_route *route; struct ospf6_external_info *info; total = 0; for (type = 0; type < ZEBRA_ROUTE_MAX; type++) nroute[type] = 0; for (route = ospf6_route_head(ospf6->external_table); route; route = ospf6_route_next(route)) { info = route->route_option; nroute[info->type]++; total++; } vty_out(vty, "Redistributing External Routes from:\n"); for (type = 0; type < ZEBRA_ROUTE_MAX; type++) { if (type == ZEBRA_ROUTE_OSPF6) continue; if (!ospf6_zebra_is_redistribute(type)) continue; if (ospf6->rmap[type].name) vty_out(vty, " %d: %s with route-map \"%s\"%s\n", nroute[type], ZROUTE_NAME(type), ospf6->rmap[type].name, (ospf6->rmap[type].map ? "" : " (not found !)")); else vty_out(vty, " %d: %s\n", nroute[type], ZROUTE_NAME(type)); } vty_out(vty, "Total %d routes\n", total); } /* Routemap Functions */ static enum route_map_cmd_result_t ospf6_routemap_rule_match_address_prefixlist(void *rule, const struct prefix *prefix, route_map_object_t type, void *object) { struct prefix_list *plist; if (type != RMAP_OSPF6) return RMAP_NOMATCH; plist = prefix_list_lookup(AFI_IP6, (char *)rule); if (plist == NULL) return RMAP_NOMATCH; return (prefix_list_apply(plist, prefix) == PREFIX_DENY ? RMAP_NOMATCH : RMAP_MATCH); } static void * ospf6_routemap_rule_match_address_prefixlist_compile(const char *arg) { return XSTRDUP(MTYPE_ROUTE_MAP_COMPILED, arg); } static void ospf6_routemap_rule_match_address_prefixlist_free(void *rule) { XFREE(MTYPE_ROUTE_MAP_COMPILED, rule); } struct route_map_rule_cmd ospf6_routemap_rule_match_address_prefixlist_cmd = { "ipv6 address prefix-list", ospf6_routemap_rule_match_address_prefixlist, ospf6_routemap_rule_match_address_prefixlist_compile, ospf6_routemap_rule_match_address_prefixlist_free, }; /* `match interface IFNAME' */ /* Match function should return 1 if match is success else return zero. */ static enum route_map_cmd_result_t ospf6_routemap_rule_match_interface(void *rule, const struct prefix *prefix, route_map_object_t type, void *object) { struct interface *ifp; struct ospf6_external_info *ei; if (type == RMAP_OSPF6) { ei = ((struct ospf6_route *)object)->route_option; ifp = if_lookup_by_name((char *)rule, VRF_DEFAULT); if (ifp != NULL && ei->ifindex == ifp->ifindex) return RMAP_MATCH; } return RMAP_NOMATCH; } /* Route map `interface' match statement. `arg' should be interface name. */ static void *ospf6_routemap_rule_match_interface_compile(const char *arg) { return XSTRDUP(MTYPE_ROUTE_MAP_COMPILED, arg); } /* Free route map's compiled `interface' value. */ static void ospf6_routemap_rule_match_interface_free(void *rule) { XFREE(MTYPE_ROUTE_MAP_COMPILED, rule); } /* Route map commands for interface matching. */ struct route_map_rule_cmd ospf6_routemap_rule_match_interface_cmd = { "interface", ospf6_routemap_rule_match_interface, ospf6_routemap_rule_match_interface_compile, ospf6_routemap_rule_match_interface_free}; /* Match function for matching route tags */ static enum route_map_cmd_result_t ospf6_routemap_rule_match_tag(void *rule, const struct prefix *p, route_map_object_t type, void *object) { route_tag_t *tag = rule; struct ospf6_route *route = object; struct ospf6_external_info *info = route->route_option; if (type == RMAP_OSPF6 && info->tag == *tag) return RMAP_MATCH; return RMAP_NOMATCH; } static struct route_map_rule_cmd ospf6_routemap_rule_match_tag_cmd = { "tag", ospf6_routemap_rule_match_tag, route_map_rule_tag_compile, route_map_rule_tag_free, }; static enum route_map_cmd_result_t ospf6_routemap_rule_set_metric_type(void *rule, const struct prefix *prefix, route_map_object_t type, void *object) { char *metric_type = rule; struct ospf6_route *route = object; if (type != RMAP_OSPF6) return RMAP_OKAY; if (strcmp(metric_type, "type-2") == 0) route->path.metric_type = 2; else route->path.metric_type = 1; return RMAP_OKAY; } static void *ospf6_routemap_rule_set_metric_type_compile(const char *arg) { if (strcmp(arg, "type-2") && strcmp(arg, "type-1")) return NULL; return XSTRDUP(MTYPE_ROUTE_MAP_COMPILED, arg); } static void ospf6_routemap_rule_set_metric_type_free(void *rule) { XFREE(MTYPE_ROUTE_MAP_COMPILED, rule); } struct route_map_rule_cmd ospf6_routemap_rule_set_metric_type_cmd = { "metric-type", ospf6_routemap_rule_set_metric_type, ospf6_routemap_rule_set_metric_type_compile, ospf6_routemap_rule_set_metric_type_free, }; static enum route_map_cmd_result_t ospf6_routemap_rule_set_metric(void *rule, const struct prefix *prefix, route_map_object_t type, void *object) { char *metric = rule; struct ospf6_route *route = object; if (type != RMAP_OSPF6) return RMAP_OKAY; route->path.cost = atoi(metric); return RMAP_OKAY; } static void *ospf6_routemap_rule_set_metric_compile(const char *arg) { uint32_t metric; char *endp; metric = strtoul(arg, &endp, 0); if (metric > OSPF_LS_INFINITY || *endp != '\0') return NULL; return XSTRDUP(MTYPE_ROUTE_MAP_COMPILED, arg); } static void ospf6_routemap_rule_set_metric_free(void *rule) { XFREE(MTYPE_ROUTE_MAP_COMPILED, rule); } struct route_map_rule_cmd ospf6_routemap_rule_set_metric_cmd = { "metric", ospf6_routemap_rule_set_metric, ospf6_routemap_rule_set_metric_compile, ospf6_routemap_rule_set_metric_free, }; static enum route_map_cmd_result_t ospf6_routemap_rule_set_forwarding(void *rule, const struct prefix *prefix, route_map_object_t type, void *object) { char *forwarding = rule; struct ospf6_route *route = object; struct ospf6_external_info *info = route->route_option; if (type != RMAP_OSPF6) return RMAP_OKAY; if (inet_pton(AF_INET6, forwarding, &info->forwarding) != 1) { memset(&info->forwarding, 0, sizeof(struct in6_addr)); return RMAP_ERROR; } return RMAP_OKAY; } static void *ospf6_routemap_rule_set_forwarding_compile(const char *arg) { struct in6_addr a; if (inet_pton(AF_INET6, arg, &a) != 1) return NULL; return XSTRDUP(MTYPE_ROUTE_MAP_COMPILED, arg); } static void ospf6_routemap_rule_set_forwarding_free(void *rule) { XFREE(MTYPE_ROUTE_MAP_COMPILED, rule); } struct route_map_rule_cmd ospf6_routemap_rule_set_forwarding_cmd = { "forwarding-address", ospf6_routemap_rule_set_forwarding, ospf6_routemap_rule_set_forwarding_compile, ospf6_routemap_rule_set_forwarding_free, }; static enum route_map_cmd_result_t ospf6_routemap_rule_set_tag(void *rule, const struct prefix *p, route_map_object_t type, void *object) { route_tag_t *tag = rule; struct ospf6_route *route = object; struct ospf6_external_info *info = route->route_option; if (type != RMAP_OSPF6) return RMAP_OKAY; info->tag = *tag; return RMAP_OKAY; } static struct route_map_rule_cmd ospf6_routemap_rule_set_tag_cmd = { "tag", ospf6_routemap_rule_set_tag, route_map_rule_tag_compile, route_map_rule_tag_free, }; static int route_map_command_status(struct vty *vty, enum rmap_compile_rets ret) { switch (ret) { case RMAP_RULE_MISSING: vty_out(vty, "OSPF6 Can't find rule.\n"); return CMD_WARNING_CONFIG_FAILED; break; case RMAP_COMPILE_ERROR: vty_out(vty, "OSPF6 Argument is malformed.\n"); return CMD_WARNING_CONFIG_FAILED; break; case RMAP_COMPILE_SUCCESS: case RMAP_DUPLICATE_RULE: break; } return CMD_SUCCESS; } /* add "set metric-type" */ DEFUN (ospf6_routemap_set_metric_type, ospf6_routemap_set_metric_type_cmd, "set metric-type ", "Set value\n" "Type of metric\n" "OSPF6 external type 1 metric\n" "OSPF6 external type 2 metric\n") { VTY_DECLVAR_CONTEXT(route_map_index, route_map_index); int idx_external = 2; enum rmap_compile_rets ret = route_map_add_set(route_map_index, "metric-type", argv[idx_external]->arg); return route_map_command_status(vty, ret); } /* delete "set metric-type" */ DEFUN (ospf6_routemap_no_set_metric_type, ospf6_routemap_no_set_metric_type_cmd, "no set metric-type []", NO_STR "Set value\n" "Type of metric\n" "OSPF6 external type 1 metric\n" "OSPF6 external type 2 metric\n") { VTY_DECLVAR_CONTEXT(route_map_index, route_map_index); char *ext = (argc == 4) ? argv[3]->text : NULL; enum rmap_compile_rets ret = route_map_delete_set(route_map_index, "metric-type", ext); return route_map_command_status(vty, ret); } /* add "set forwarding-address" */ DEFUN (ospf6_routemap_set_forwarding, ospf6_routemap_set_forwarding_cmd, "set forwarding-address X:X::X:X", "Set value\n" "Forwarding Address\n" "IPv6 Address\n") { VTY_DECLVAR_CONTEXT(route_map_index, route_map_index); int idx_ipv6 = 2; enum rmap_compile_rets ret = route_map_add_set(route_map_index, "forwarding-address", argv[idx_ipv6]->arg); return route_map_command_status(vty, ret); } /* delete "set forwarding-address" */ DEFUN (ospf6_routemap_no_set_forwarding, ospf6_routemap_no_set_forwarding_cmd, "no set forwarding-address X:X::X:X", NO_STR "Set value\n" "Forwarding Address\n" "IPv6 Address\n") { VTY_DECLVAR_CONTEXT(route_map_index, route_map_index); int idx_ipv6 = 3; enum rmap_compile_rets ret = route_map_delete_set(route_map_index, "forwarding-address", argv[idx_ipv6]->arg); return route_map_command_status(vty, ret); } static void ospf6_routemap_init(void) { route_map_init(); route_map_add_hook(ospf6_asbr_routemap_update); route_map_delete_hook(ospf6_asbr_routemap_update); route_map_event_hook(ospf6_asbr_routemap_event); route_map_set_metric_hook(generic_set_add); route_map_no_set_metric_hook(generic_set_delete); route_map_match_tag_hook(generic_match_add); route_map_no_match_tag_hook(generic_match_delete); route_map_match_ipv6_address_prefix_list_hook(generic_match_add); route_map_no_match_ipv6_address_prefix_list_hook(generic_match_delete); route_map_match_interface_hook(generic_match_add); route_map_no_match_interface_hook(generic_match_delete); route_map_install_match( &ospf6_routemap_rule_match_address_prefixlist_cmd); route_map_install_match(&ospf6_routemap_rule_match_interface_cmd); route_map_install_match(&ospf6_routemap_rule_match_tag_cmd); route_map_install_set(&ospf6_routemap_rule_set_metric_type_cmd); route_map_install_set(&ospf6_routemap_rule_set_metric_cmd); route_map_install_set(&ospf6_routemap_rule_set_forwarding_cmd); route_map_install_set(&ospf6_routemap_rule_set_tag_cmd); /* ASE Metric Type (e.g. Type-1/Type-2) */ install_element(RMAP_NODE, &ospf6_routemap_set_metric_type_cmd); install_element(RMAP_NODE, &ospf6_routemap_no_set_metric_type_cmd); /* ASE Metric */ install_element(RMAP_NODE, &ospf6_routemap_set_forwarding_cmd); install_element(RMAP_NODE, &ospf6_routemap_no_set_forwarding_cmd); } /* Display functions */ static char *ospf6_as_external_lsa_get_prefix_str(struct ospf6_lsa *lsa, char *buf, int buflen, int pos) { struct ospf6_as_external_lsa *external; struct in6_addr in6; int prefix_length = 0; if (lsa) { external = (struct ospf6_as_external_lsa *)OSPF6_LSA_HEADER_END( lsa->header); if (pos == 0) { ospf6_prefix_in6_addr(&in6, external, &external->prefix); prefix_length = external->prefix.prefix_length; } else { in6 = *((struct in6_addr *)((caddr_t)external + sizeof(struct ospf6_as_external_lsa) + OSPF6_PREFIX_SPACE( external->prefix .prefix_length))); } if (buf) { inet_ntop(AF_INET6, &in6, buf, buflen); if (prefix_length) sprintf(&buf[strlen(buf)], "/%d", prefix_length); } } return (buf); } static int ospf6_as_external_lsa_show(struct vty *vty, struct ospf6_lsa *lsa) { struct ospf6_as_external_lsa *external; char buf[64]; assert(lsa->header); external = (struct ospf6_as_external_lsa *)OSPF6_LSA_HEADER_END( lsa->header); /* bits */ snprintf(buf, sizeof(buf), "%c%c%c", (CHECK_FLAG(external->bits_metric, OSPF6_ASBR_BIT_E) ? 'E' : '-'), (CHECK_FLAG(external->bits_metric, OSPF6_ASBR_BIT_F) ? 'F' : '-'), (CHECK_FLAG(external->bits_metric, OSPF6_ASBR_BIT_T) ? 'T' : '-')); vty_out(vty, " Bits: %s\n", buf); vty_out(vty, " Metric: %5lu\n", (unsigned long)OSPF6_ASBR_METRIC(external)); ospf6_prefix_options_printbuf(external->prefix.prefix_options, buf, sizeof(buf)); vty_out(vty, " Prefix Options: %s\n", buf); vty_out(vty, " Referenced LSType: %d\n", ntohs(external->prefix.prefix_refer_lstype)); vty_out(vty, " Prefix: %s\n", ospf6_as_external_lsa_get_prefix_str(lsa, buf, sizeof(buf), 0)); /* Forwarding-Address */ if (CHECK_FLAG(external->bits_metric, OSPF6_ASBR_BIT_F)) { vty_out(vty, " Forwarding-Address: %s\n", ospf6_as_external_lsa_get_prefix_str(lsa, buf, sizeof(buf), 1)); } /* Tag */ if (CHECK_FLAG(external->bits_metric, OSPF6_ASBR_BIT_T)) { vty_out(vty, " Tag: %" ROUTE_TAG_PRI "\n", ospf6_as_external_lsa_get_tag(lsa)); } return 0; } static void ospf6_asbr_external_route_show(struct vty *vty, struct ospf6_route *route) { struct ospf6_external_info *info = route->route_option; char prefix[PREFIX2STR_BUFFER], id[16], forwarding[64]; uint32_t tmp_id; prefix2str(&route->prefix, prefix, sizeof(prefix)); tmp_id = ntohl(info->id); inet_ntop(AF_INET, &tmp_id, id, sizeof(id)); if (!IN6_IS_ADDR_UNSPECIFIED(&info->forwarding)) inet_ntop(AF_INET6, &info->forwarding, forwarding, sizeof(forwarding)); else snprintf(forwarding, sizeof(forwarding), ":: (ifindex %d)", ospf6_route_get_first_nh_index(route)); vty_out(vty, "%c %-32s %-15s type-%d %5lu %s\n", zebra_route_char(info->type), prefix, id, route->path.metric_type, (unsigned long)(route->path.metric_type == 2 ? route->path.u.cost_e2 : route->path.cost), forwarding); } DEFUN (show_ipv6_ospf6_redistribute, show_ipv6_ospf6_redistribute_cmd, "show ipv6 ospf6 redistribute", SHOW_STR IP6_STR OSPF6_STR "redistributing External information\n" ) { struct ospf6_route *route; OSPF6_CMD_CHECK_RUNNING(); ospf6_redistribute_show_config(vty); for (route = ospf6_route_head(ospf6->external_table); route; route = ospf6_route_next(route)) ospf6_asbr_external_route_show(vty, route); return CMD_SUCCESS; } struct ospf6_lsa_handler as_external_handler = { .lh_type = OSPF6_LSTYPE_AS_EXTERNAL, .lh_name = "AS-External", .lh_short_name = "ASE", .lh_show = ospf6_as_external_lsa_show, .lh_get_prefix_str = ospf6_as_external_lsa_get_prefix_str, .lh_debug = 0}; void ospf6_asbr_init(void) { ospf6_routemap_init(); ospf6_install_lsa_handler(&as_external_handler); install_element(VIEW_NODE, &show_ipv6_ospf6_redistribute_cmd); install_element(OSPF6_NODE, &ospf6_redistribute_cmd); install_element(OSPF6_NODE, &ospf6_redistribute_routemap_cmd); install_element(OSPF6_NODE, &no_ospf6_redistribute_cmd); } void ospf6_asbr_redistribute_reset(void) { int type; for (type = 0; type < ZEBRA_ROUTE_MAX; type++) { if (type == ZEBRA_ROUTE_OSPF6) continue; if (ospf6_zebra_is_redistribute(type)) ospf6_asbr_redistribute_unset(type); } } void ospf6_asbr_terminate(void) { /* Cleanup route maps */ route_map_finish(); } DEFUN (debug_ospf6_asbr, debug_ospf6_asbr_cmd, "debug ospf6 asbr", DEBUG_STR OSPF6_STR "Debug OSPFv3 ASBR function\n" ) { OSPF6_DEBUG_ASBR_ON(); return CMD_SUCCESS; } DEFUN (no_debug_ospf6_asbr, no_debug_ospf6_asbr_cmd, "no debug ospf6 asbr", NO_STR DEBUG_STR OSPF6_STR "Debug OSPFv3 ASBR function\n" ) { OSPF6_DEBUG_ASBR_OFF(); return CMD_SUCCESS; } int config_write_ospf6_debug_asbr(struct vty *vty) { if (IS_OSPF6_DEBUG_ASBR) vty_out(vty, "debug ospf6 asbr\n"); return 0; } void install_element_ospf6_debug_asbr(void) { install_element(ENABLE_NODE, &debug_ospf6_asbr_cmd); install_element(ENABLE_NODE, &no_debug_ospf6_asbr_cmd); install_element(CONFIG_NODE, &debug_ospf6_asbr_cmd); install_element(CONFIG_NODE, &no_debug_ospf6_asbr_cmd); } frr-7.2.1/ospf6d/ospf6_asbr.h0000644000000000000000000000656613610377563012647 00000000000000/* * Copyright (C) 2001 Yasuhiro Ohara * * This file is part of GNU Zebra. * * GNU Zebra is free software; you can redistribute it and/or modify it * under the terms of the GNU General Public License as published by the * Free Software Foundation; either version 2, or (at your option) any * later version. * * GNU Zebra 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; see the file COPYING; if not, write to the Free Software * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA */ #ifndef OSPF6_ASBR_H #define OSPF6_ASBR_H /* for struct ospf6_prefix */ #include "ospf6_proto.h" /* for struct ospf6_lsa */ #include "ospf6_lsa.h" /* for struct ospf6_route */ #include "ospf6_route.h" /* Debug option */ extern unsigned char conf_debug_ospf6_asbr; #define OSPF6_DEBUG_ASBR_ON() (conf_debug_ospf6_asbr = 1) #define OSPF6_DEBUG_ASBR_OFF() (conf_debug_ospf6_asbr = 0) #define IS_OSPF6_DEBUG_ASBR (conf_debug_ospf6_asbr) struct ospf6_external_info { /* External route type */ int type; /* Originating Link State ID */ uint32_t id; struct in6_addr forwarding; route_tag_t tag; ifindex_t ifindex; }; /* AS-External-LSA */ #define OSPF6_AS_EXTERNAL_LSA_MIN_SIZE 4U /* w/o IPv6 prefix */ struct ospf6_as_external_lsa { uint32_t bits_metric; struct ospf6_prefix prefix; /* followed by none or one forwarding address */ /* followed by none or one external route tag */ /* followed by none or one referenced LS-ID */ }; #define OSPF6_ASBR_BIT_T ntohl (0x01000000) #define OSPF6_ASBR_BIT_F ntohl (0x02000000) #define OSPF6_ASBR_BIT_E ntohl (0x04000000) #define OSPF6_ASBR_METRIC(E) (ntohl ((E)->bits_metric & htonl (0x00ffffff))) #define OSPF6_ASBR_METRIC_SET(E, C) \ { \ (E)->bits_metric &= htonl(0xff000000); \ (E)->bits_metric |= htonl(0x00ffffff) & htonl(C); \ } extern void ospf6_asbr_lsa_add(struct ospf6_lsa *lsa); extern void ospf6_asbr_lsa_remove(struct ospf6_lsa *lsa, struct ospf6_route *asbr_entry); extern void ospf6_asbr_lsentry_add(struct ospf6_route *asbr_entry); extern void ospf6_asbr_lsentry_remove(struct ospf6_route *asbr_entry); extern int ospf6_asbr_is_asbr(struct ospf6 *o); extern void ospf6_asbr_redistribute_add(int type, ifindex_t ifindex, struct prefix *prefix, unsigned int nexthop_num, struct in6_addr *nexthop, route_tag_t tag); extern void ospf6_asbr_redistribute_remove(int type, ifindex_t ifindex, struct prefix *prefix); extern int ospf6_redistribute_config_write(struct vty *vty); extern void ospf6_asbr_init(void); extern void ospf6_asbr_redistribute_reset(void); extern void ospf6_asbr_terminate(void); extern void ospf6_asbr_send_externals_to_area(struct ospf6_area *); extern int config_write_ospf6_debug_asbr(struct vty *vty); extern void install_element_ospf6_debug_asbr(void); extern void ospf6_asbr_update_route_ecmp_path(struct ospf6_route *old, struct ospf6_route *route); extern void ospf6_asbr_distribute_list_update(int type); #endif /* OSPF6_ASBR_H */ frr-7.2.1/ospf6d/ospf6_bfd.c0000644000000000000000000002523413610377563012437 00000000000000/** * ospf6_bfd.c: IPv6 OSPF BFD handling routines * * @copyright Copyright (C) 2015 Cumulus Networks, Inc. * * This file is part of GNU Zebra. * * GNU Zebra is free software; you can redistribute it and/or modify it * under the terms of the GNU General Public License as published by the * Free Software Foundation; either version 2, or (at your option) any * later version. * * GNU Zebra 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; see the file COPYING; if not, write to the Free Software * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA */ #include #include "command.h" #include "linklist.h" #include "memory.h" #include "prefix.h" #include "thread.h" #include "buffer.h" #include "stream.h" #include "zclient.h" #include "vty.h" #include "table.h" #include "bfd.h" #include "if.h" #include "ospf6d.h" #include "ospf6_message.h" #include "ospf6_neighbor.h" #include "ospf6_interface.h" #include "ospf6_route.h" #include "ospf6_zebra.h" #include "ospf6_bfd.h" extern struct zclient *zclient; /* * ospf6_bfd_info_free - Free BFD info structure */ void ospf6_bfd_info_free(void **bfd_info) { bfd_info_free((struct bfd_info **)bfd_info); } /* * ospf6_bfd_show_info - Show BFD info structure */ void ospf6_bfd_show_info(struct vty *vty, void *bfd_info, int param_only) { if (param_only) bfd_show_param(vty, bfd_info, 1, 0, 0, NULL); else bfd_show_info(vty, bfd_info, 0, 1, 0, NULL); } /* * ospf6_bfd_reg_dereg_nbr - Register/Deregister a neighbor with BFD through * zebra for starting/stopping the monitoring of * the neighbor rechahability. */ void ospf6_bfd_reg_dereg_nbr(struct ospf6_neighbor *on, int command) { struct ospf6_interface *oi = on->ospf6_if; struct interface *ifp = oi->interface; struct bfd_info *bfd_info; char src[64]; int cbit; if (!oi->bfd_info || !on->bfd_info) return; bfd_info = (struct bfd_info *)oi->bfd_info; if (IS_OSPF6_DEBUG_ZEBRA(SEND)) { inet_ntop(AF_INET6, &on->linklocal_addr, src, sizeof(src)); zlog_debug("%s nbr (%s) with BFD", bfd_get_command_dbg_str(command), src); } cbit = CHECK_FLAG(bfd_info->flags, BFD_FLAG_BFD_CBIT_ON); bfd_peer_sendmsg(zclient, bfd_info, AF_INET6, &on->linklocal_addr, on->ospf6_if->linklocal_addr, ifp->name, 0, 0, cbit, command, 0, VRF_DEFAULT); if (command == ZEBRA_BFD_DEST_DEREGISTER) bfd_info_free((struct bfd_info **)&on->bfd_info); } /* * ospf6_bfd_trigger_event - Neighbor is registered/deregistered with BFD when * neighbor state is changed to/from 2way. */ void ospf6_bfd_trigger_event(struct ospf6_neighbor *on, int old_state, int state) { if ((old_state < OSPF6_NEIGHBOR_TWOWAY) && (state >= OSPF6_NEIGHBOR_TWOWAY)) ospf6_bfd_reg_dereg_nbr(on, ZEBRA_BFD_DEST_REGISTER); else if ((old_state >= OSPF6_NEIGHBOR_TWOWAY) && (state < OSPF6_NEIGHBOR_TWOWAY)) ospf6_bfd_reg_dereg_nbr(on, ZEBRA_BFD_DEST_DEREGISTER); } /* * ospf6_bfd_reg_dereg_all_nbr - Register/Deregister all neighbors associated * with a interface with BFD through * zebra for starting/stopping the monitoring of * the neighbor rechahability. */ static void ospf6_bfd_reg_dereg_all_nbr(struct ospf6_interface *oi, int command) { struct ospf6_neighbor *on; struct listnode *node; for (ALL_LIST_ELEMENTS_RO(oi->neighbor_list, node, on)) { if (command == ZEBRA_BFD_DEST_REGISTER) ospf6_bfd_info_nbr_create(oi, on); if (on->state < OSPF6_NEIGHBOR_TWOWAY) { if (command == ZEBRA_BFD_DEST_DEREGISTER) bfd_info_free( (struct bfd_info **)&on->bfd_info); continue; } ospf6_bfd_reg_dereg_nbr(on, command); } } /* * ospf6_bfd_nbr_replay - Replay all the neighbors that have BFD enabled * to zebra */ static int ospf6_bfd_nbr_replay(ZAPI_CALLBACK_ARGS) { struct vrf *vrf = vrf_lookup_by_id(VRF_DEFAULT); struct listnode *node; struct interface *ifp; struct ospf6_interface *oi; struct ospf6_neighbor *on; char dst[64]; if (IS_OSPF6_DEBUG_ZEBRA(RECV)) zlog_debug("Zebra: BFD Dest replay request"); /* Send the client registration */ bfd_client_sendmsg(zclient, ZEBRA_BFD_CLIENT_REGISTER, vrf_id); /* Replay the neighbor, if BFD is enabled on the interface*/ FOR_ALL_INTERFACES (vrf, ifp) { oi = (struct ospf6_interface *)ifp->info; if (!oi || !oi->bfd_info) continue; for (ALL_LIST_ELEMENTS_RO(oi->neighbor_list, node, on)) { if (on->state < OSPF6_NEIGHBOR_TWOWAY) continue; if (IS_OSPF6_DEBUG_ZEBRA(SEND)) { inet_ntop(AF_INET6, &on->linklocal_addr, dst, sizeof(dst)); zlog_debug("Replaying nbr (%s) to BFD", dst); } ospf6_bfd_reg_dereg_nbr(on, ZEBRA_BFD_DEST_UPDATE); } } return 0; } /* * ospf6_bfd_interface_dest_update - Find the neighbor for which the BFD status * has changed and bring down the neighbor * connectivity if BFD down is received. */ static int ospf6_bfd_interface_dest_update(ZAPI_CALLBACK_ARGS) { struct interface *ifp; struct ospf6_interface *oi; struct ospf6_neighbor *on; struct prefix dp; struct prefix sp; struct listnode *node, *nnode; char dst[64]; int status; int old_status; struct bfd_info *bfd_info; struct timeval tv; ifp = bfd_get_peer_info(zclient->ibuf, &dp, &sp, &status, NULL, vrf_id); if ((ifp == NULL) || (dp.family != AF_INET6)) return 0; if (IS_OSPF6_DEBUG_ZEBRA(RECV)) { char buf[PREFIX2STR_BUFFER]; prefix2str(&dp, buf, sizeof(buf)); zlog_debug("Zebra: interface %s bfd destination %s %s", ifp->name, buf, bfd_get_status_str(status)); } oi = (struct ospf6_interface *)ifp->info; if (!oi || !oi->bfd_info) return 0; for (ALL_LIST_ELEMENTS(oi->neighbor_list, node, nnode, on)) { if (memcmp(&(on->linklocal_addr), &dp.u.prefix6, sizeof(struct in6_addr))) continue; if (IS_OSPF6_DEBUG_NEIGHBOR(EVENT)) { inet_ntop(AF_INET6, &on->linklocal_addr, dst, sizeof(dst)); zlog_debug("[%s:%s]: BFD %s", ifp->name, dst, bfd_get_status_str(status)); } if (!on->bfd_info) continue; bfd_info = (struct bfd_info *)on->bfd_info; if (bfd_info->status == status) continue; old_status = bfd_info->status; bfd_info->status = status; monotime(&tv); bfd_info->last_update = tv.tv_sec; if ((status == BFD_STATUS_DOWN) && (old_status == BFD_STATUS_UP)) { THREAD_OFF(on->inactivity_timer); thread_add_event(master, inactivity_timer, on, 0, NULL); } } return 0; } /* * ospf6_bfd_info_nbr_create - Create/update BFD information for a neighbor. */ void ospf6_bfd_info_nbr_create(struct ospf6_interface *oi, struct ospf6_neighbor *on) { struct bfd_info *oi_bfd_info; struct bfd_info *on_bfd_info; if (!oi->bfd_info) return; oi_bfd_info = (struct bfd_info *)oi->bfd_info; if (!on->bfd_info) on->bfd_info = bfd_info_create(); on_bfd_info = (struct bfd_info *)on->bfd_info; on_bfd_info->detect_mult = oi_bfd_info->detect_mult; on_bfd_info->desired_min_tx = oi_bfd_info->desired_min_tx; on_bfd_info->required_min_rx = oi_bfd_info->required_min_rx; } /* * ospf6_bfd_write_config - Write the interface BFD configuration. */ void ospf6_bfd_write_config(struct vty *vty, struct ospf6_interface *oi) { #if HAVE_BFDD == 0 struct bfd_info *bfd_info; #endif /* ! HAVE_BFDD */ if (!oi->bfd_info) return; #if HAVE_BFDD == 0 bfd_info = (struct bfd_info *)oi->bfd_info; if (CHECK_FLAG(bfd_info->flags, BFD_FLAG_PARAM_CFG)) vty_out(vty, " ipv6 ospf6 bfd %d %d %d\n", bfd_info->detect_mult, bfd_info->required_min_rx, bfd_info->desired_min_tx); else #endif /* ! HAVE_BFDD */ vty_out(vty, " ipv6 ospf6 bfd\n"); } /* * ospf6_bfd_if_param_set - Set the configured BFD paramter values for * interface. */ static void ospf6_bfd_if_param_set(struct ospf6_interface *oi, uint32_t min_rx, uint32_t min_tx, uint8_t detect_mult, int defaults) { int command = 0; bfd_set_param((struct bfd_info **)&(oi->bfd_info), min_rx, min_tx, detect_mult, defaults, &command); if (command) ospf6_bfd_reg_dereg_all_nbr(oi, command); } DEFUN (ipv6_ospf6_bfd, ipv6_ospf6_bfd_cmd, "ipv6 ospf6 bfd", IP6_STR OSPF6_STR "Enables BFD support\n" ) { VTY_DECLVAR_CONTEXT(interface, ifp); struct ospf6_interface *oi; assert(ifp); oi = (struct ospf6_interface *)ifp->info; if (oi == NULL) oi = ospf6_interface_create(ifp); assert(oi); ospf6_bfd_if_param_set(oi, BFD_DEF_MIN_RX, BFD_DEF_MIN_TX, BFD_DEF_DETECT_MULT, 1); return CMD_SUCCESS; } #if HAVE_BFDD > 0 DEFUN_HIDDEN( #else DEFUN( #endif /* HAVE_BFDD */ ipv6_ospf6_bfd_param, ipv6_ospf6_bfd_param_cmd, "ipv6 ospf6 bfd (2-255) (50-60000) (50-60000)", IP6_STR OSPF6_STR "Enables BFD support\n" "Detect Multiplier\n" "Required min receive interval\n" "Desired min transmit interval\n") { VTY_DECLVAR_CONTEXT(interface, ifp); int idx_number = 3; int idx_number_2 = 4; int idx_number_3 = 5; struct ospf6_interface *oi; uint32_t rx_val; uint32_t tx_val; uint8_t dm_val; int ret; assert(ifp); oi = (struct ospf6_interface *)ifp->info; if (oi == NULL) oi = ospf6_interface_create(ifp); assert(oi); if ((ret = bfd_validate_param( vty, argv[idx_number]->arg, argv[idx_number_2]->arg, argv[idx_number_3]->arg, &dm_val, &rx_val, &tx_val)) != CMD_SUCCESS) return ret; ospf6_bfd_if_param_set(oi, rx_val, tx_val, dm_val, 0); return CMD_SUCCESS; } DEFUN (no_ipv6_ospf6_bfd, no_ipv6_ospf6_bfd_cmd, "no ipv6 ospf6 bfd", NO_STR IP6_STR OSPF6_STR "Disables BFD support\n" ) { VTY_DECLVAR_CONTEXT(interface, ifp); struct ospf6_interface *oi; assert(ifp); oi = (struct ospf6_interface *)ifp->info; if (oi == NULL) oi = ospf6_interface_create(ifp); assert(oi); if (oi->bfd_info) { ospf6_bfd_reg_dereg_all_nbr(oi, ZEBRA_BFD_DEST_DEREGISTER); bfd_info_free((struct bfd_info **)&(oi->bfd_info)); } return CMD_SUCCESS; } void ospf6_bfd_init(void) { bfd_gbl_init(); /* Initialize BFD client functions */ zclient->interface_bfd_dest_update = ospf6_bfd_interface_dest_update; zclient->bfd_dest_replay = ospf6_bfd_nbr_replay; /* Install BFD command */ install_element(INTERFACE_NODE, &ipv6_ospf6_bfd_cmd); install_element(INTERFACE_NODE, &ipv6_ospf6_bfd_param_cmd); install_element(INTERFACE_NODE, &no_ipv6_ospf6_bfd_cmd); } frr-7.2.1/ospf6d/ospf6_bfd.h0000644000000000000000000000270613610377563012443 00000000000000/** * ospf6_bfd.h: OSPF6 BFD definitions and structures * * @copyright Copyright (C) 2015 Cumulus Networks, Inc. * * This file is part of GNU Zebra. * * GNU Zebra is free software; you can redistribute it and/or modify it * under the terms of the GNU General Public License as published by the * Free Software Foundation; either version 2, or (at your option) any * later version. * * GNU Zebra 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; see the file COPYING; if not, write to the Free Software * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA */ #ifndef OSPF6_BFD_H #define OSPF6_BFD_H extern void ospf6_bfd_init(void); extern void ospf6_bfd_trigger_event(struct ospf6_neighbor *nbr, int old_state, int state); extern void ospf6_bfd_write_config(struct vty *vty, struct ospf6_interface *oi); extern void ospf6_bfd_info_nbr_create(struct ospf6_interface *oi, struct ospf6_neighbor *on); extern void ospf6_bfd_info_free(void **bfd_info); extern void ospf6_bfd_show_info(struct vty *vty, void *bfd_info, int param_only); extern void ospf6_bfd_reg_dereg_nbr(struct ospf6_neighbor *on, int command); #endif /* OSPF6_BFD_H */ frr-7.2.1/ospf6d/ospf6_flood.c0000644000000000000000000007306413610377563013013 00000000000000/* * Copyright (C) 2003 Yasuhiro Ohara * * This file is part of GNU Zebra. * * GNU Zebra is free software; you can redistribute it and/or modify it * under the terms of the GNU General Public License as published by the * Free Software Foundation; either version 2, or (at your option) any * later version. * * GNU Zebra 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; see the file COPYING; if not, write to the Free Software * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA */ #include #include "log.h" #include "thread.h" #include "linklist.h" #include "vty.h" #include "command.h" #include "ospf6d.h" #include "ospf6_proto.h" #include "ospf6_lsa.h" #include "ospf6_lsdb.h" #include "ospf6_message.h" #include "ospf6_route.h" #include "ospf6_spf.h" #include "ospf6_top.h" #include "ospf6_area.h" #include "ospf6_interface.h" #include "ospf6_neighbor.h" #include "ospf6_flood.h" unsigned char conf_debug_ospf6_flooding; struct ospf6_lsdb *ospf6_get_scoped_lsdb(struct ospf6_lsa *lsa) { struct ospf6_lsdb *lsdb = NULL; switch (OSPF6_LSA_SCOPE(lsa->header->type)) { case OSPF6_SCOPE_LINKLOCAL: lsdb = OSPF6_INTERFACE(lsa->lsdb->data)->lsdb; break; case OSPF6_SCOPE_AREA: lsdb = OSPF6_AREA(lsa->lsdb->data)->lsdb; break; case OSPF6_SCOPE_AS: lsdb = OSPF6_PROCESS(lsa->lsdb->data)->lsdb; break; default: assert(0); break; } return lsdb; } struct ospf6_lsdb *ospf6_get_scoped_lsdb_self(struct ospf6_lsa *lsa) { struct ospf6_lsdb *lsdb_self = NULL; switch (OSPF6_LSA_SCOPE(lsa->header->type)) { case OSPF6_SCOPE_LINKLOCAL: lsdb_self = OSPF6_INTERFACE(lsa->lsdb->data)->lsdb_self; break; case OSPF6_SCOPE_AREA: lsdb_self = OSPF6_AREA(lsa->lsdb->data)->lsdb_self; break; case OSPF6_SCOPE_AS: lsdb_self = OSPF6_PROCESS(lsa->lsdb->data)->lsdb_self; break; default: assert(0); break; } return lsdb_self; } void ospf6_lsa_originate(struct ospf6_lsa *lsa) { struct ospf6_lsa *old; struct ospf6_lsdb *lsdb_self; /* find previous LSA */ old = ospf6_lsdb_lookup(lsa->header->type, lsa->header->id, lsa->header->adv_router, lsa->lsdb); /* if the new LSA does not differ from previous, suppress this update of the LSA */ if (old && !OSPF6_LSA_IS_DIFFER(lsa, old)) { if (IS_OSPF6_DEBUG_ORIGINATE_TYPE(lsa->header->type)) zlog_debug("Suppress updating LSA: %s", lsa->name); ospf6_lsa_delete(lsa); return; } /* store it in the LSDB for self-originated LSAs */ lsdb_self = ospf6_get_scoped_lsdb_self(lsa); ospf6_lsdb_add(ospf6_lsa_copy(lsa), lsdb_self); lsa->refresh = NULL; thread_add_timer(master, ospf6_lsa_refresh, lsa, OSPF_LS_REFRESH_TIME, &lsa->refresh); if (IS_OSPF6_DEBUG_LSA_TYPE(lsa->header->type) || IS_OSPF6_DEBUG_ORIGINATE_TYPE(lsa->header->type)) { zlog_debug("LSA Originate:"); ospf6_lsa_header_print(lsa); } ospf6_install_lsa(lsa); ospf6_flood(NULL, lsa); } void ospf6_lsa_originate_process(struct ospf6_lsa *lsa, struct ospf6 *process) { lsa->lsdb = process->lsdb; ospf6_lsa_originate(lsa); } void ospf6_lsa_originate_area(struct ospf6_lsa *lsa, struct ospf6_area *oa) { lsa->lsdb = oa->lsdb; ospf6_lsa_originate(lsa); } void ospf6_lsa_originate_interface(struct ospf6_lsa *lsa, struct ospf6_interface *oi) { lsa->lsdb = oi->lsdb; ospf6_lsa_originate(lsa); } void ospf6_lsa_purge(struct ospf6_lsa *lsa) { struct ospf6_lsa *self; struct ospf6_lsdb *lsdb_self; /* remove it from the LSDB for self-originated LSAs */ lsdb_self = ospf6_get_scoped_lsdb_self(lsa); self = ospf6_lsdb_lookup(lsa->header->type, lsa->header->id, lsa->header->adv_router, lsdb_self); if (self) { THREAD_OFF(self->expire); THREAD_OFF(self->refresh); ospf6_lsdb_remove(self, lsdb_self); } ospf6_lsa_premature_aging(lsa); } /* Puring Multi Link-State IDs LSAs: * Same Advertising Router with Multiple Link-State IDs * LSAs, purging require to traverse all Link-State IDs */ void ospf6_lsa_purge_multi_ls_id(struct ospf6_area *oa, struct ospf6_lsa *lsa) { int ls_id = 0; struct ospf6_lsa *lsa_next; uint16_t type; type = lsa->header->type; ospf6_lsa_purge(lsa); lsa_next = ospf6_lsdb_lookup(type, htonl(++ls_id), oa->ospf6->router_id, oa->lsdb); while (lsa_next) { ospf6_lsa_purge(lsa_next); lsa_next = ospf6_lsdb_lookup(type, htonl(++ls_id), oa->ospf6->router_id, oa->lsdb); } } void ospf6_increment_retrans_count(struct ospf6_lsa *lsa) { /* The LSA must be the original one (see the description in ospf6_decrement_retrans_count () below) */ lsa->retrans_count++; } void ospf6_decrement_retrans_count(struct ospf6_lsa *lsa) { struct ospf6_lsdb *lsdb; struct ospf6_lsa *orig; /* The LSA must be on the retrans-list of a neighbor. It means the "lsa" is a copied one, and we have to decrement the retransmission count of the original one (instead of this "lsa"'s). In order to find the original LSA, first we have to find appropriate LSDB that have the original LSA. */ lsdb = ospf6_get_scoped_lsdb(lsa); /* Find the original LSA of which the retrans_count should be * decremented */ orig = ospf6_lsdb_lookup(lsa->header->type, lsa->header->id, lsa->header->adv_router, lsdb); if (orig) { orig->retrans_count--; assert(orig->retrans_count >= 0); } } /* RFC2328 section 13.2 Installing LSAs in the database */ void ospf6_install_lsa(struct ospf6_lsa *lsa) { struct timeval now; struct ospf6_lsa *old; /* Remove the old instance from all neighbors' Link state retransmission list (RFC2328 13.2 last paragraph) */ old = ospf6_lsdb_lookup(lsa->header->type, lsa->header->id, lsa->header->adv_router, lsa->lsdb); if (old) { THREAD_OFF(old->expire); THREAD_OFF(old->refresh); ospf6_flood_clear(old); } monotime(&now); if (!OSPF6_LSA_IS_MAXAGE(lsa)) { lsa->expire = NULL; thread_add_timer(master, ospf6_lsa_expire, lsa, OSPF_LSA_MAXAGE + lsa->birth.tv_sec - now.tv_sec, &lsa->expire); } else lsa->expire = NULL; if (OSPF6_LSA_IS_SEQWRAP(lsa) && !(CHECK_FLAG(lsa->flag, OSPF6_LSA_SEQWRAPPED) && lsa->header->seqnum == htonl(OSPF_MAX_SEQUENCE_NUMBER))) { if (IS_OSPF6_DEBUG_EXAMIN_TYPE(lsa->header->type)) zlog_debug("lsa install wrapping: sequence 0x%x", ntohl(lsa->header->seqnum)); SET_FLAG(lsa->flag, OSPF6_LSA_SEQWRAPPED); /* in lieu of premature_aging, since we do not want to recreate * this lsa * and/or mess with timers etc, we just want to wrap the * sequence number * and reflood the lsa before continuing. * NOTE: Flood needs to be called right after this function * call, by the * caller */ lsa->header->seqnum = htonl(OSPF_MAX_SEQUENCE_NUMBER); lsa->header->age = htons(OSPF_LSA_MAXAGE); ospf6_lsa_checksum(lsa->header); } if (IS_OSPF6_DEBUG_LSA_TYPE(lsa->header->type) || IS_OSPF6_DEBUG_EXAMIN_TYPE(lsa->header->type)) zlog_debug("%s Install LSA: %s age %d seqnum %x in LSDB.", __PRETTY_FUNCTION__, lsa->name, ntohs(lsa->header->age), ntohl(lsa->header->seqnum)); /* actually install */ lsa->installed = now; ospf6_lsdb_add(lsa, lsa->lsdb); return; } /* RFC2740 section 3.5.2. Sending Link State Update packets */ /* RFC2328 section 13.3 Next step in the flooding procedure */ void ospf6_flood_interface(struct ospf6_neighbor *from, struct ospf6_lsa *lsa, struct ospf6_interface *oi) { struct listnode *node, *nnode; struct ospf6_neighbor *on; struct ospf6_lsa *req; int retrans_added = 0; int is_debug = 0; if (IS_OSPF6_DEBUG_FLOODING || IS_OSPF6_DEBUG_FLOOD_TYPE(lsa->header->type)) { is_debug++; zlog_debug("Flooding on %s: %s", oi->interface->name, lsa->name); } /* (1) For each neighbor */ for (ALL_LIST_ELEMENTS(oi->neighbor_list, node, nnode, on)) { if (is_debug) zlog_debug("To neighbor %s", on->name); /* (a) if neighbor state < Exchange, examin next */ if (on->state < OSPF6_NEIGHBOR_EXCHANGE) { if (is_debug) zlog_debug( "Neighbor state less than ExChange, next neighbor"); continue; } /* (b) if neighbor not yet Full, check request-list */ if (on->state != OSPF6_NEIGHBOR_FULL) { if (is_debug) zlog_debug("Neighbor not yet Full"); req = ospf6_lsdb_lookup( lsa->header->type, lsa->header->id, lsa->header->adv_router, on->request_list); if (req == NULL) { if (is_debug) zlog_debug( "Not on request-list for this neighbor"); /* fall through */ } else { /* If new LSA less recent, examin next neighbor */ if (ospf6_lsa_compare(lsa, req) > 0) { if (is_debug) zlog_debug( "Requesting is older, next neighbor"); continue; } /* If the same instance, delete from request-list and examin next neighbor */ if (ospf6_lsa_compare(lsa, req) == 0) { if (is_debug) zlog_debug( "Requesting the same, remove it, next neighbor"); if (req == on->last_ls_req) { /* sanity check refcount */ assert(req->lock >= 2); ospf6_lsa_unlock(req); on->last_ls_req = NULL; } ospf6_lsdb_remove(req, on->request_list); ospf6_check_nbr_loading(on); continue; } /* If the new LSA is more recent, delete from * request-list */ if (ospf6_lsa_compare(lsa, req) < 0) { if (is_debug) zlog_debug( "Received is newer, remove requesting"); if (req == on->last_ls_req) { ospf6_lsa_unlock(req); on->last_ls_req = NULL; } if (req) ospf6_lsdb_remove(req, on->request_list); ospf6_check_nbr_loading(on); /* fall through */ } } } /* (c) If the new LSA was received from this neighbor, examin next neighbor */ if (from == on) { if (is_debug) zlog_debug( "Received is from the neighbor, next neighbor"); continue; } if (ospf6->inst_shutdown) { if (is_debug) zlog_debug( "%s: Send LSA %s (age %d) update now", __PRETTY_FUNCTION__, lsa->name, ntohs(lsa->header->age)); ospf6_lsupdate_send_neighbor_now(on, lsa); continue; } else { /* (d) add retrans-list, schedule retransmission */ if (is_debug) zlog_debug("Add retrans-list of this neighbor"); ospf6_increment_retrans_count(lsa); ospf6_lsdb_add(ospf6_lsa_copy(lsa), on->retrans_list); thread_add_timer(master, ospf6_lsupdate_send_neighbor, on, on->ospf6_if->rxmt_interval, &on->thread_send_lsupdate); retrans_added++; } } /* (2) examin next interface if not added to retrans-list */ if (retrans_added == 0) { if (is_debug) zlog_debug( "No retransmission scheduled, next interface"); return; } /* (3) If the new LSA was received on this interface, and it was from DR or BDR, examin next interface */ if (from && from->ospf6_if == oi && (from->router_id == oi->drouter || from->router_id == oi->bdrouter)) { if (is_debug) zlog_debug( "Received is from the I/F's DR or BDR, next interface"); return; } /* (4) If the new LSA was received on this interface, and the interface state is BDR, examin next interface */ if (from && from->ospf6_if == oi) { if (oi->state == OSPF6_INTERFACE_BDR) { if (is_debug) zlog_debug( "Received is from the I/F, itself BDR, next interface"); return; } SET_FLAG(lsa->flag, OSPF6_LSA_FLOODBACK); } /* (5) flood the LSA out the interface. */ if (is_debug) zlog_debug("Schedule flooding for the interface"); if ((oi->type == OSPF_IFTYPE_BROADCAST) || (oi->type == OSPF_IFTYPE_POINTOPOINT)) { ospf6_lsdb_add(ospf6_lsa_copy(lsa), oi->lsupdate_list); thread_add_event(master, ospf6_lsupdate_send_interface, oi, 0, &oi->thread_send_lsupdate); } else { /* reschedule retransmissions to all neighbors */ for (ALL_LIST_ELEMENTS(oi->neighbor_list, node, nnode, on)) { THREAD_OFF(on->thread_send_lsupdate); on->thread_send_lsupdate = NULL; thread_add_event(master, ospf6_lsupdate_send_neighbor, on, 0, &on->thread_send_lsupdate); } } } void ospf6_flood_area(struct ospf6_neighbor *from, struct ospf6_lsa *lsa, struct ospf6_area *oa) { struct listnode *node, *nnode; struct ospf6_interface *oi; for (ALL_LIST_ELEMENTS(oa->if_list, node, nnode, oi)) { if (OSPF6_LSA_SCOPE(lsa->header->type) == OSPF6_SCOPE_LINKLOCAL && oi != OSPF6_INTERFACE(lsa->lsdb->data)) continue; #if 0 if (OSPF6_LSA_SCOPE (lsa->header->type) == OSPF6_SCOPE_AS && ospf6_is_interface_virtual_link (oi)) continue; #endif /*0*/ ospf6_flood_interface(from, lsa, oi); } } static void ospf6_flood_process(struct ospf6_neighbor *from, struct ospf6_lsa *lsa, struct ospf6 *process) { struct listnode *node, *nnode; struct ospf6_area *oa; for (ALL_LIST_ELEMENTS(process->area_list, node, nnode, oa)) { if (OSPF6_LSA_SCOPE(lsa->header->type) == OSPF6_SCOPE_AREA && oa != OSPF6_AREA(lsa->lsdb->data)) continue; if (OSPF6_LSA_SCOPE(lsa->header->type) == OSPF6_SCOPE_LINKLOCAL && oa != OSPF6_INTERFACE(lsa->lsdb->data)->area) continue; if (ntohs(lsa->header->type) == OSPF6_LSTYPE_AS_EXTERNAL && IS_AREA_STUB(oa)) continue; ospf6_flood_area(from, lsa, oa); } } void ospf6_flood(struct ospf6_neighbor *from, struct ospf6_lsa *lsa) { ospf6_flood_process(from, lsa, ospf6); } static void ospf6_flood_clear_interface(struct ospf6_lsa *lsa, struct ospf6_interface *oi) { struct listnode *node, *nnode; struct ospf6_neighbor *on; struct ospf6_lsa *rem; for (ALL_LIST_ELEMENTS(oi->neighbor_list, node, nnode, on)) { rem = ospf6_lsdb_lookup(lsa->header->type, lsa->header->id, lsa->header->adv_router, on->retrans_list); if (rem && !ospf6_lsa_compare(rem, lsa)) { if (IS_OSPF6_DEBUG_FLOODING || IS_OSPF6_DEBUG_FLOOD_TYPE(lsa->header->type)) zlog_debug("Remove %s from retrans_list of %s", rem->name, on->name); ospf6_decrement_retrans_count(rem); ospf6_lsdb_remove(rem, on->retrans_list); } } } static void ospf6_flood_clear_area(struct ospf6_lsa *lsa, struct ospf6_area *oa) { struct listnode *node, *nnode; struct ospf6_interface *oi; for (ALL_LIST_ELEMENTS(oa->if_list, node, nnode, oi)) { if (OSPF6_LSA_SCOPE(lsa->header->type) == OSPF6_SCOPE_LINKLOCAL && oi != OSPF6_INTERFACE(lsa->lsdb->data)) continue; #if 0 if (OSPF6_LSA_SCOPE (lsa->header->type) == OSPF6_SCOPE_AS && ospf6_is_interface_virtual_link (oi)) continue; #endif /*0*/ ospf6_flood_clear_interface(lsa, oi); } } static void ospf6_flood_clear_process(struct ospf6_lsa *lsa, struct ospf6 *process) { struct listnode *node, *nnode; struct ospf6_area *oa; for (ALL_LIST_ELEMENTS(process->area_list, node, nnode, oa)) { if (OSPF6_LSA_SCOPE(lsa->header->type) == OSPF6_SCOPE_AREA && oa != OSPF6_AREA(lsa->lsdb->data)) continue; if (OSPF6_LSA_SCOPE(lsa->header->type) == OSPF6_SCOPE_LINKLOCAL && oa != OSPF6_INTERFACE(lsa->lsdb->data)->area) continue; if (ntohs(lsa->header->type) == OSPF6_LSTYPE_AS_EXTERNAL && IS_AREA_STUB(oa)) continue; ospf6_flood_clear_area(lsa, oa); } } void ospf6_flood_clear(struct ospf6_lsa *lsa) { ospf6_flood_clear_process(lsa, ospf6); } /* RFC2328 13.5 (Table 19): Sending link state acknowledgements. */ static void ospf6_acknowledge_lsa_bdrouter(struct ospf6_lsa *lsa, int ismore_recent, struct ospf6_neighbor *from) { struct ospf6_interface *oi; int is_debug = 0; if (IS_OSPF6_DEBUG_FLOODING || IS_OSPF6_DEBUG_FLOOD_TYPE(lsa->header->type)) is_debug++; assert(from && from->ospf6_if); oi = from->ospf6_if; /* LSA is more recent than database copy, but was not flooded back out receiving interface. Delayed acknowledgement sent if advertisement received from Designated Router, otherwide do nothing. */ if (ismore_recent < 0) { if (oi->drouter == from->router_id) { if (is_debug) zlog_debug( "Delayed acknowledgement (BDR & MoreRecent & from DR)"); /* Delayed acknowledgement */ ospf6_lsdb_add(ospf6_lsa_copy(lsa), oi->lsack_list); thread_add_timer(master, ospf6_lsack_send_interface, oi, 3, &oi->thread_send_lsack); } else { if (is_debug) zlog_debug( "No acknowledgement (BDR & MoreRecent & ! from DR)"); } return; } /* LSA is a duplicate, and was treated as an implied acknowledgement. Delayed acknowledgement sent if advertisement received from Designated Router, otherwise do nothing */ if (CHECK_FLAG(lsa->flag, OSPF6_LSA_DUPLICATE) && CHECK_FLAG(lsa->flag, OSPF6_LSA_IMPLIEDACK)) { if (oi->drouter == from->router_id) { if (is_debug) zlog_debug( "Delayed acknowledgement (BDR & Duplicate & ImpliedAck & from DR)"); /* Delayed acknowledgement */ ospf6_lsdb_add(ospf6_lsa_copy(lsa), oi->lsack_list); thread_add_timer(master, ospf6_lsack_send_interface, oi, 3, &oi->thread_send_lsack); } else { if (is_debug) zlog_debug( "No acknowledgement (BDR & Duplicate & ImpliedAck & ! from DR)"); } return; } /* LSA is a duplicate, and was not treated as an implied acknowledgement. Direct acknowledgement sent */ if (CHECK_FLAG(lsa->flag, OSPF6_LSA_DUPLICATE) && !CHECK_FLAG(lsa->flag, OSPF6_LSA_IMPLIEDACK)) { if (is_debug) zlog_debug("Direct acknowledgement (BDR & Duplicate)"); ospf6_lsdb_add(ospf6_lsa_copy(lsa), from->lsack_list); thread_add_event(master, ospf6_lsack_send_neighbor, from, 0, &from->thread_send_lsack); return; } /* LSA's LS age is equal to Maxage, and there is no current instance of the LSA in the link state database, and none of router's neighbors are in states Exchange or Loading */ /* Direct acknowledgement sent, but this case is handled in early of ospf6_receive_lsa () */ } static void ospf6_acknowledge_lsa_allother(struct ospf6_lsa *lsa, int ismore_recent, struct ospf6_neighbor *from) { struct ospf6_interface *oi; int is_debug = 0; if (IS_OSPF6_DEBUG_FLOODING || IS_OSPF6_DEBUG_FLOOD_TYPE(lsa->header->type)) is_debug++; assert(from && from->ospf6_if); oi = from->ospf6_if; /* LSA has been flood back out receiving interface. No acknowledgement sent. */ if (CHECK_FLAG(lsa->flag, OSPF6_LSA_FLOODBACK)) { if (is_debug) zlog_debug("No acknowledgement (AllOther & FloodBack)"); return; } /* LSA is more recent than database copy, but was not flooded back out receiving interface. Delayed acknowledgement sent. */ if (ismore_recent < 0) { if (is_debug) zlog_debug( "Delayed acknowledgement (AllOther & MoreRecent)"); /* Delayed acknowledgement */ ospf6_lsdb_add(ospf6_lsa_copy(lsa), oi->lsack_list); thread_add_timer(master, ospf6_lsack_send_interface, oi, 3, &oi->thread_send_lsack); return; } /* LSA is a duplicate, and was treated as an implied acknowledgement. No acknowledgement sent. */ if (CHECK_FLAG(lsa->flag, OSPF6_LSA_DUPLICATE) && CHECK_FLAG(lsa->flag, OSPF6_LSA_IMPLIEDACK)) { if (is_debug) zlog_debug( "No acknowledgement (AllOther & Duplicate & ImpliedAck)"); return; } /* LSA is a duplicate, and was not treated as an implied acknowledgement. Direct acknowledgement sent */ if (CHECK_FLAG(lsa->flag, OSPF6_LSA_DUPLICATE) && !CHECK_FLAG(lsa->flag, OSPF6_LSA_IMPLIEDACK)) { if (is_debug) zlog_debug( "Direct acknowledgement (AllOther & Duplicate)"); ospf6_lsdb_add(ospf6_lsa_copy(lsa), from->lsack_list); thread_add_event(master, ospf6_lsack_send_neighbor, from, 0, &from->thread_send_lsack); return; } /* LSA's LS age is equal to Maxage, and there is no current instance of the LSA in the link state database, and none of router's neighbors are in states Exchange or Loading */ /* Direct acknowledgement sent, but this case is handled in early of ospf6_receive_lsa () */ } static void ospf6_acknowledge_lsa(struct ospf6_lsa *lsa, int ismore_recent, struct ospf6_neighbor *from) { struct ospf6_interface *oi; assert(from && from->ospf6_if); oi = from->ospf6_if; if (oi->state == OSPF6_INTERFACE_BDR) ospf6_acknowledge_lsa_bdrouter(lsa, ismore_recent, from); else ospf6_acknowledge_lsa_allother(lsa, ismore_recent, from); } /* RFC2328 section 13 (4): if MaxAge LSA and if we have no instance, and no neighbor is in states Exchange or Loading returns 1 if match this case, else returns 0 */ static int ospf6_is_maxage_lsa_drop(struct ospf6_lsa *lsa, struct ospf6_neighbor *from) { struct ospf6_neighbor *on; struct ospf6_interface *oi; struct ospf6_area *oa; struct ospf6 *process = NULL; struct listnode *i, *j, *k; int count = 0; if (!OSPF6_LSA_IS_MAXAGE(lsa)) return 0; if (ospf6_lsdb_lookup(lsa->header->type, lsa->header->id, lsa->header->adv_router, lsa->lsdb)) return 0; process = from->ospf6_if->area->ospf6; for (ALL_LIST_ELEMENTS_RO(process->area_list, i, oa)) for (ALL_LIST_ELEMENTS_RO(oa->if_list, j, oi)) for (ALL_LIST_ELEMENTS_RO(oi->neighbor_list, k, on)) if (on->state == OSPF6_NEIGHBOR_EXCHANGE || on->state == OSPF6_NEIGHBOR_LOADING) count++; if (count == 0) return 1; return 0; } /* RFC2328 section 13 The Flooding Procedure */ void ospf6_receive_lsa(struct ospf6_neighbor *from, struct ospf6_lsa_header *lsa_header) { struct ospf6_lsa *new = NULL, *old = NULL, *rem = NULL; int ismore_recent; int is_debug = 0; unsigned int time_delta_ms; ismore_recent = 1; assert(from); /* make lsa structure for received lsa */ new = ospf6_lsa_create(lsa_header); if (IS_OSPF6_DEBUG_FLOODING || IS_OSPF6_DEBUG_FLOOD_TYPE(new->header->type)) { is_debug++; zlog_debug("LSA Receive from %s", from->name); ospf6_lsa_header_print(new); } /* (1) LSA Checksum */ if (!ospf6_lsa_checksum_valid(new->header)) { if (is_debug) zlog_debug("Wrong LSA Checksum, discard"); ospf6_lsa_delete(new); return; } /* (2) Examine the LSA's LS type. RFC2470 3.5.1. Receiving Link State Update packets */ if (IS_AREA_STUB(from->ospf6_if->area) && OSPF6_LSA_SCOPE(new->header->type) == OSPF6_SCOPE_AS) { if (is_debug) zlog_debug( "AS-External-LSA (or AS-scope LSA) in stub area, discard"); ospf6_lsa_delete(new); return; } /* (3) LSA which have reserved scope is discarded RFC2470 3.5.1. Receiving Link State Update packets */ /* Flooding scope check. LSAs with unknown scope are discarded here. Set appropriate LSDB for the LSA */ switch (OSPF6_LSA_SCOPE(new->header->type)) { case OSPF6_SCOPE_LINKLOCAL: new->lsdb = from->ospf6_if->lsdb; break; case OSPF6_SCOPE_AREA: new->lsdb = from->ospf6_if->area->lsdb; break; case OSPF6_SCOPE_AS: new->lsdb = from->ospf6_if->area->ospf6->lsdb; break; default: if (is_debug) zlog_debug("LSA has reserved scope, discard"); ospf6_lsa_delete(new); return; } /* (4) if MaxAge LSA and if we have no instance, and no neighbor is in states Exchange or Loading */ if (ospf6_is_maxage_lsa_drop(new, from)) { /* log */ if (is_debug) zlog_debug( "Drop MaxAge LSA with direct acknowledgement."); /* a) Acknowledge back to neighbor (Direct acknowledgement, * 13.5) */ ospf6_lsdb_add(ospf6_lsa_copy(new), from->lsack_list); thread_add_event(master, ospf6_lsack_send_neighbor, from, 0, &from->thread_send_lsack); /* b) Discard */ ospf6_lsa_delete(new); return; } /* (5) */ /* lookup the same database copy in lsdb */ old = ospf6_lsdb_lookup(new->header->type, new->header->id, new->header->adv_router, new->lsdb); if (old) { ismore_recent = ospf6_lsa_compare(new, old); if (ntohl(new->header->seqnum) == ntohl(old->header->seqnum)) { if (is_debug) zlog_debug("Received is duplicated LSA"); SET_FLAG(new->flag, OSPF6_LSA_DUPLICATE); } if (old->header->adv_router == from->ospf6_if->area->ospf6->router_id && OSPF6_LSA_IS_MAXAGE(new)) { ospf6_acknowledge_lsa(new, ismore_recent, from); ospf6_lsa_delete(new); if (is_debug) zlog_debug( "%s: Received is self orig MAXAGE LSA %s, discard (ismore_recent %d)", __PRETTY_FUNCTION__, old->name, ismore_recent); return; } } /* if no database copy or received is more recent */ if (old == NULL || ismore_recent < 0) { /* in case we have no database copy */ ismore_recent = -1; /* (a) MinLSArrival check */ if (old) { struct timeval now, res; monotime(&now); timersub(&now, &old->installed, &res); time_delta_ms = (res.tv_sec * 1000) + (int)(res.tv_usec / 1000); if (time_delta_ms < from->ospf6_if->area->ospf6->lsa_minarrival) { if (is_debug) zlog_debug( "LSA can't be updated within MinLSArrival, %dms < %dms, discard", time_delta_ms, from->ospf6_if->area->ospf6 ->lsa_minarrival); ospf6_lsa_delete(new); return; /* examin next lsa */ } } monotime(&new->received); if (is_debug) zlog_debug( "Install, Flood, Possibly acknowledge the received LSA"); /* Remove older copies of this LSA from retx lists */ if (old) ospf6_flood_clear(old); /* (b) immediately flood and (c) remove from all retrans-list */ /* Prevent self-originated LSA to be flooded. this is to make reoriginated instance of the LSA not to be rejected by other routers due to MinLSArrival. */ if (new->header->adv_router != from->ospf6_if->area->ospf6->router_id) ospf6_flood(from, new); /* (d), installing lsdb, which may cause routing table calculation (replacing database copy) */ ospf6_install_lsa(new); if (OSPF6_LSA_IS_MAXAGE(new)) ospf6_maxage_remove(from->ospf6_if->area->ospf6); /* (e) possibly acknowledge */ ospf6_acknowledge_lsa(new, ismore_recent, from); /* (f) Self Originated LSA, section 13.4 */ if (new->header->adv_router == from->ospf6_if->area->ospf6->router_id) { /* Self-originated LSA (newer than ours) is received from another router. We have to make a new instance of the LSA or have to flush this LSA. */ if (is_debug) { zlog_debug( "Newer instance of the self-originated LSA"); zlog_debug("Schedule reorigination"); } new->refresh = NULL; thread_add_event(master, ospf6_lsa_refresh, new, 0, &new->refresh); } return; } /* (6) if there is instance on sending neighbor's request list */ if (ospf6_lsdb_lookup(new->header->type, new->header->id, new->header->adv_router, from->request_list)) { /* if no database copy, should go above state (5) */ assert(old); if (is_debug) { zlog_debug( "Received is not newer, on the neighbor's request-list"); zlog_debug("BadLSReq, discard the received LSA"); } /* BadLSReq */ thread_add_event(master, bad_lsreq, from, 0, NULL); ospf6_lsa_delete(new); return; } /* (7) if neither one is more recent */ if (ismore_recent == 0) { if (is_debug) zlog_debug( "The same instance as database copy (neither recent)"); /* (a) if on retrans-list, Treat this LSA as an Ack: Implied Ack */ rem = ospf6_lsdb_lookup(new->header->type, new->header->id, new->header->adv_router, from->retrans_list); if (rem) { if (is_debug) { zlog_debug( "It is on the neighbor's retrans-list."); zlog_debug( "Treat as an Implied acknowledgement"); } SET_FLAG(new->flag, OSPF6_LSA_IMPLIEDACK); ospf6_decrement_retrans_count(rem); ospf6_lsdb_remove(rem, from->retrans_list); } if (is_debug) zlog_debug("Possibly acknowledge and then discard"); /* (b) possibly acknowledge */ ospf6_acknowledge_lsa(new, ismore_recent, from); ospf6_lsa_delete(new); return; } /* (8) previous database copy is more recent */ { assert(old); /* If database copy is in 'Seqnumber Wrapping', simply discard the received LSA */ if (OSPF6_LSA_IS_MAXAGE(old) && old->header->seqnum == htonl(OSPF_MAX_SEQUENCE_NUMBER)) { if (is_debug) { zlog_debug("The LSA is in Seqnumber Wrapping"); zlog_debug("MaxAge & MaxSeqNum, discard"); } ospf6_lsa_delete(new); return; } /* Otherwise, Send database copy of this LSA to this neighbor */ { if (is_debug) { zlog_debug("Database copy is more recent."); zlog_debug( "Send back directly and then discard"); } /* Neighbor router sent recent age for LSA, * Router could be restarted while current copy is * MAXAGEd and not removed.*/ if (OSPF6_LSA_IS_MAXAGE(old) && !OSPF6_LSA_IS_MAXAGE(new)) { if (is_debug) zlog_debug( "%s: Current copy of LSA %s is MAXAGE, but new has recent Age.", old->name, __PRETTY_FUNCTION__); ospf6_lsa_purge(old); if (new->header->adv_router != from->ospf6_if->area->ospf6->router_id) ospf6_flood(from, new); ospf6_install_lsa(new); return; } /* XXX, MinLSArrival check !? RFC 2328 13 (8) */ ospf6_lsdb_add(ospf6_lsa_copy(old), from->lsupdate_list); thread_add_event(master, ospf6_lsupdate_send_neighbor, from, 0, &from->thread_send_lsupdate); ospf6_lsa_delete(new); return; } return; } } DEFUN (debug_ospf6_flooding, debug_ospf6_flooding_cmd, "debug ospf6 flooding", DEBUG_STR OSPF6_STR "Debug OSPFv3 flooding function\n" ) { OSPF6_DEBUG_FLOODING_ON(); return CMD_SUCCESS; } DEFUN (no_debug_ospf6_flooding, no_debug_ospf6_flooding_cmd, "no debug ospf6 flooding", NO_STR DEBUG_STR OSPF6_STR "Debug OSPFv3 flooding function\n" ) { OSPF6_DEBUG_FLOODING_OFF(); return CMD_SUCCESS; } int config_write_ospf6_debug_flood(struct vty *vty) { if (IS_OSPF6_DEBUG_FLOODING) vty_out(vty, "debug ospf6 flooding\n"); return 0; } void install_element_ospf6_debug_flood(void) { install_element(ENABLE_NODE, &debug_ospf6_flooding_cmd); install_element(ENABLE_NODE, &no_debug_ospf6_flooding_cmd); install_element(CONFIG_NODE, &debug_ospf6_flooding_cmd); install_element(CONFIG_NODE, &no_debug_ospf6_flooding_cmd); } frr-7.2.1/ospf6d/ospf6_flood.h0000644000000000000000000000535513610377563013016 00000000000000/* * Copyright (C) 2003 Yasuhiro Ohara * * This file is part of GNU Zebra. * * GNU Zebra is free software; you can redistribute it and/or modify it * under the terms of the GNU General Public License as published by the * Free Software Foundation; either version 2, or (at your option) any * later version. * * GNU Zebra 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; see the file COPYING; if not, write to the Free Software * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA */ #ifndef OSPF6_FLOOD_H #define OSPF6_FLOOD_H /* Debug option */ extern unsigned char conf_debug_ospf6_flooding; #define OSPF6_DEBUG_FLOODING_ON() (conf_debug_ospf6_flooding = 1) #define OSPF6_DEBUG_FLOODING_OFF() (conf_debug_ospf6_flooding = 0) #define IS_OSPF6_DEBUG_FLOODING (conf_debug_ospf6_flooding) /* Function Prototypes */ extern struct ospf6_lsdb *ospf6_get_scoped_lsdb(struct ospf6_lsa *lsa); extern struct ospf6_lsdb *ospf6_get_scoped_lsdb_self(struct ospf6_lsa *lsa); /* origination & purging */ extern void ospf6_lsa_originate(struct ospf6_lsa *lsa); extern void ospf6_lsa_originate_process(struct ospf6_lsa *lsa, struct ospf6 *process); extern void ospf6_lsa_originate_area(struct ospf6_lsa *lsa, struct ospf6_area *oa); extern void ospf6_lsa_originate_interface(struct ospf6_lsa *lsa, struct ospf6_interface *oi); extern void ospf6_lsa_purge(struct ospf6_lsa *lsa); extern void ospf6_lsa_purge_multi_ls_id(struct ospf6_area *oa, struct ospf6_lsa *lsa); /* access method to retrans_count */ extern void ospf6_increment_retrans_count(struct ospf6_lsa *lsa); extern void ospf6_decrement_retrans_count(struct ospf6_lsa *lsa); /* flooding & clear flooding */ extern void ospf6_flood_clear(struct ospf6_lsa *lsa); extern void ospf6_flood(struct ospf6_neighbor *from, struct ospf6_lsa *lsa); extern void ospf6_flood_area(struct ospf6_neighbor *from, struct ospf6_lsa *lsa, struct ospf6_area *oa); /* receive & install */ extern void ospf6_receive_lsa(struct ospf6_neighbor *from, struct ospf6_lsa_header *header); extern void ospf6_install_lsa(struct ospf6_lsa *lsa); extern int config_write_ospf6_debug_flood(struct vty *vty); extern void install_element_ospf6_debug_flood(void); extern void ospf6_flood_interface(struct ospf6_neighbor *from, struct ospf6_lsa *lsa, struct ospf6_interface *oi); extern int ospf6_lsupdate_send_neighbor_now(struct ospf6_neighbor *on, struct ospf6_lsa *lsa); #endif /* OSPF6_FLOOD_H */ frr-7.2.1/ospf6d/ospf6_interface.c0000644000000000000000000015362313610377563013650 00000000000000/* * Copyright (C) 2003 Yasuhiro Ohara * * This file is part of GNU Zebra. * * GNU Zebra is free software; you can redistribute it and/or modify it * under the terms of the GNU General Public License as published by the * Free Software Foundation; either version 2, or (at your option) any * later version. * * GNU Zebra 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; see the file COPYING; if not, write to the Free Software * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA */ #include #include "memory.h" #include "if.h" #include "log.h" #include "command.h" #include "thread.h" #include "prefix.h" #include "plist.h" #include "zclient.h" #include "ospf6_lsa.h" #include "ospf6_lsdb.h" #include "ospf6_network.h" #include "ospf6_message.h" #include "ospf6_route.h" #include "ospf6_top.h" #include "ospf6_area.h" #include "ospf6_interface.h" #include "ospf6_neighbor.h" #include "ospf6_intra.h" #include "ospf6_spf.h" #include "ospf6d.h" #include "ospf6_bfd.h" DEFINE_MTYPE_STATIC(OSPF6D, CFG_PLIST_NAME, "configured prefix list names") DEFINE_QOBJ_TYPE(ospf6_interface) DEFINE_HOOK(ospf6_interface_change, (struct ospf6_interface * oi, int state, int old_state), (oi, state, old_state)) unsigned char conf_debug_ospf6_interface = 0; const char *ospf6_interface_state_str[] = { "None", "Down", "Loopback", "Waiting", "PointToPoint", "DROther", "BDR", "DR", NULL}; struct ospf6_interface *ospf6_interface_lookup_by_ifindex(ifindex_t ifindex) { struct ospf6_interface *oi; struct interface *ifp; ifp = if_lookup_by_index(ifindex, VRF_DEFAULT); if (ifp == NULL) return (struct ospf6_interface *)NULL; oi = (struct ospf6_interface *)ifp->info; return oi; } /* schedule routing table recalculation */ static void ospf6_interface_lsdb_hook(struct ospf6_lsa *lsa, unsigned int reason) { struct ospf6_interface *oi; if (lsa == NULL) return; oi = lsa->lsdb->data; switch (ntohs(lsa->header->type)) { case OSPF6_LSTYPE_LINK: if (oi->state == OSPF6_INTERFACE_DR) OSPF6_INTRA_PREFIX_LSA_SCHEDULE_TRANSIT(oi); if (oi->area) ospf6_spf_schedule(oi->area->ospf6, reason); break; default: break; } } static void ospf6_interface_lsdb_hook_add(struct ospf6_lsa *lsa) { ospf6_interface_lsdb_hook(lsa, ospf6_lsadd_to_spf_reason(lsa)); } static void ospf6_interface_lsdb_hook_remove(struct ospf6_lsa *lsa) { ospf6_interface_lsdb_hook(lsa, ospf6_lsremove_to_spf_reason(lsa)); } static uint8_t ospf6_default_iftype(struct interface *ifp) { if (if_is_pointopoint(ifp)) return OSPF_IFTYPE_POINTOPOINT; else if (if_is_loopback(ifp)) return OSPF_IFTYPE_LOOPBACK; else return OSPF_IFTYPE_BROADCAST; } static uint32_t ospf6_interface_get_cost(struct ospf6_interface *oi) { /* If all else fails, use default OSPF cost */ uint32_t cost; uint32_t bw, refbw; /* interface speed and bw can be 0 in some platforms, * use ospf default bw. If bw is configured then it would * be used. */ if (!oi->interface->bandwidth && oi->interface->speed) { bw = oi->interface->speed; } else { bw = oi->interface->bandwidth ? oi->interface->bandwidth : OSPF6_INTERFACE_BANDWIDTH; } refbw = ospf6 ? ospf6->ref_bandwidth : OSPF6_REFERENCE_BANDWIDTH; /* A specifed ip ospf cost overrides a calculated one. */ if (CHECK_FLAG(oi->flag, OSPF6_INTERFACE_NOAUTOCOST)) cost = oi->cost; else { cost = (uint32_t)((double)refbw / (double)bw + (double)0.5); if (cost < 1) cost = 1; else if (cost > UINT32_MAX) cost = UINT32_MAX; } return cost; } static void ospf6_interface_force_recalculate_cost(struct ospf6_interface *oi) { /* update cost held in route_connected list in ospf6_interface */ ospf6_interface_connected_route_update(oi->interface); /* execute LSA hooks */ if (oi->area) { OSPF6_LINK_LSA_SCHEDULE(oi); OSPF6_ROUTER_LSA_SCHEDULE(oi->area); OSPF6_NETWORK_LSA_SCHEDULE(oi); OSPF6_INTRA_PREFIX_LSA_SCHEDULE_TRANSIT(oi); OSPF6_INTRA_PREFIX_LSA_SCHEDULE_STUB(oi->area); } } static void ospf6_interface_recalculate_cost(struct ospf6_interface *oi) { uint32_t newcost; newcost = ospf6_interface_get_cost(oi); if (newcost == oi->cost) return; oi->cost = newcost; ospf6_interface_force_recalculate_cost(oi); } /* Create new ospf6 interface structure */ struct ospf6_interface *ospf6_interface_create(struct interface *ifp) { struct ospf6_interface *oi; unsigned int iobuflen; oi = XCALLOC(MTYPE_OSPF6_IF, sizeof(struct ospf6_interface)); oi->area = (struct ospf6_area *)NULL; oi->neighbor_list = list_new(); oi->neighbor_list->cmp = ospf6_neighbor_cmp; oi->linklocal_addr = (struct in6_addr *)NULL; oi->instance_id = OSPF6_INTERFACE_INSTANCE_ID; oi->transdelay = OSPF6_INTERFACE_TRANSDELAY; oi->priority = OSPF6_INTERFACE_PRIORITY; oi->hello_interval = OSPF_HELLO_INTERVAL_DEFAULT; oi->dead_interval = OSPF_ROUTER_DEAD_INTERVAL_DEFAULT; oi->rxmt_interval = OSPF_RETRANSMIT_INTERVAL_DEFAULT; oi->type = ospf6_default_iftype(ifp); oi->state = OSPF6_INTERFACE_DOWN; oi->flag = 0; oi->mtu_ignore = 0; oi->c_ifmtu = 0; /* Try to adjust I/O buffer size with IfMtu */ oi->ifmtu = ifp->mtu6; iobuflen = ospf6_iobuf_size(ifp->mtu6); if (oi->ifmtu > iobuflen) { if (IS_OSPF6_DEBUG_INTERFACE) zlog_debug( "Interface %s: IfMtu is adjusted to I/O buffer size: %d.", ifp->name, iobuflen); oi->ifmtu = iobuflen; } QOBJ_REG(oi, ospf6_interface); oi->lsupdate_list = ospf6_lsdb_create(oi); oi->lsack_list = ospf6_lsdb_create(oi); oi->lsdb = ospf6_lsdb_create(oi); oi->lsdb->hook_add = ospf6_interface_lsdb_hook_add; oi->lsdb->hook_remove = ospf6_interface_lsdb_hook_remove; oi->lsdb_self = ospf6_lsdb_create(oi); oi->route_connected = OSPF6_ROUTE_TABLE_CREATE(INTERFACE, CONNECTED_ROUTES); oi->route_connected->scope = oi; /* link both */ oi->interface = ifp; ifp->info = oi; /* Compute cost. */ oi->cost = ospf6_interface_get_cost(oi); return oi; } void ospf6_interface_delete(struct ospf6_interface *oi) { struct listnode *node, *nnode; struct ospf6_neighbor *on; QOBJ_UNREG(oi); for (ALL_LIST_ELEMENTS(oi->neighbor_list, node, nnode, on)) ospf6_neighbor_delete(on); list_delete(&oi->neighbor_list); THREAD_OFF(oi->thread_send_hello); THREAD_OFF(oi->thread_send_lsupdate); THREAD_OFF(oi->thread_send_lsack); THREAD_OFF(oi->thread_sso); ospf6_lsdb_remove_all(oi->lsdb); ospf6_lsdb_remove_all(oi->lsupdate_list); ospf6_lsdb_remove_all(oi->lsack_list); ospf6_lsdb_delete(oi->lsdb); ospf6_lsdb_delete(oi->lsdb_self); ospf6_lsdb_delete(oi->lsupdate_list); ospf6_lsdb_delete(oi->lsack_list); ospf6_route_table_delete(oi->route_connected); /* cut link */ oi->interface->info = NULL; /* plist_name */ if (oi->plist_name) XFREE(MTYPE_CFG_PLIST_NAME, oi->plist_name); ospf6_bfd_info_free(&(oi->bfd_info)); /* disable from area list if possible */ ospf6_area_interface_delete(oi); XFREE(MTYPE_OSPF6_IF, oi); } void ospf6_interface_enable(struct ospf6_interface *oi) { UNSET_FLAG(oi->flag, OSPF6_INTERFACE_DISABLE); ospf6_interface_state_update(oi->interface); } void ospf6_interface_disable(struct ospf6_interface *oi) { SET_FLAG(oi->flag, OSPF6_INTERFACE_DISABLE); thread_execute(master, interface_down, oi, 0); ospf6_lsdb_remove_all(oi->lsdb); ospf6_lsdb_remove_all(oi->lsdb_self); ospf6_lsdb_remove_all(oi->lsupdate_list); ospf6_lsdb_remove_all(oi->lsack_list); THREAD_OFF(oi->thread_send_hello); THREAD_OFF(oi->thread_send_lsupdate); THREAD_OFF(oi->thread_send_lsack); THREAD_OFF(oi->thread_sso); THREAD_OFF(oi->thread_network_lsa); THREAD_OFF(oi->thread_link_lsa); THREAD_OFF(oi->thread_intra_prefix_lsa); THREAD_OFF(oi->thread_as_extern_lsa); } static struct in6_addr * ospf6_interface_get_linklocal_address(struct interface *ifp) { struct listnode *n; struct connected *c; struct in6_addr *l = (struct in6_addr *)NULL; /* for each connected address */ for (ALL_LIST_ELEMENTS_RO(ifp->connected, n, c)) { /* if family not AF_INET6, ignore */ if (c->address->family != AF_INET6) continue; /* linklocal scope check */ if (IN6_IS_ADDR_LINKLOCAL(&c->address->u.prefix6)) l = &c->address->u.prefix6; } return l; } void ospf6_interface_if_add(struct interface *ifp) { struct ospf6_interface *oi; unsigned int iobuflen; oi = (struct ospf6_interface *)ifp->info; if (oi == NULL) return; /* Try to adjust I/O buffer size with IfMtu */ if (oi->ifmtu == 0) oi->ifmtu = ifp->mtu6; iobuflen = ospf6_iobuf_size(ifp->mtu6); if (oi->ifmtu > iobuflen) { if (IS_OSPF6_DEBUG_INTERFACE) zlog_debug( "Interface %s: IfMtu is adjusted to I/O buffer size: %d.", ifp->name, iobuflen); oi->ifmtu = iobuflen; } /* interface start */ ospf6_interface_state_update(oi->interface); } void ospf6_interface_state_update(struct interface *ifp) { struct ospf6_interface *oi; unsigned int iobuflen; oi = (struct ospf6_interface *)ifp->info; if (oi == NULL) return; if (oi->area == NULL) return; if (CHECK_FLAG(oi->flag, OSPF6_INTERFACE_DISABLE)) return; /* Adjust the mtu values if the kernel told us something new */ if (ifp->mtu6 != oi->ifmtu) { /* If nothing configured, accept it and check for buffer size */ if (!oi->c_ifmtu) { oi->ifmtu = ifp->mtu6; iobuflen = ospf6_iobuf_size(ifp->mtu6); if (oi->ifmtu > iobuflen) { if (IS_OSPF6_DEBUG_INTERFACE) zlog_debug( "Interface %s: IfMtu is adjusted to I/O buffer size: %d.", ifp->name, iobuflen); oi->ifmtu = iobuflen; } } else if (oi->c_ifmtu > ifp->mtu6) { oi->ifmtu = ifp->mtu6; zlog_warn( "Configured mtu %u on %s overridden by kernel %u", oi->c_ifmtu, ifp->name, ifp->mtu6); } else oi->ifmtu = oi->c_ifmtu; } if (if_is_operative(ifp) && (ospf6_interface_get_linklocal_address(oi->interface) || if_is_loopback(oi->interface))) thread_execute(master, interface_up, oi, 0); else thread_execute(master, interface_down, oi, 0); return; } void ospf6_interface_connected_route_update(struct interface *ifp) { struct ospf6_interface *oi; struct ospf6_route *route; struct connected *c; struct listnode *node, *nnode; struct in6_addr nh_addr; oi = (struct ospf6_interface *)ifp->info; if (oi == NULL) return; /* reset linklocal pointer */ oi->linklocal_addr = ospf6_interface_get_linklocal_address(ifp); /* if area is null, do not make connected-route list */ if (oi->area == NULL) return; if (CHECK_FLAG(oi->flag, OSPF6_INTERFACE_DISABLE)) return; /* update "route to advertise" interface route table */ ospf6_route_remove_all(oi->route_connected); for (ALL_LIST_ELEMENTS(oi->interface->connected, node, nnode, c)) { if (c->address->family != AF_INET6) continue; CONTINUE_IF_ADDRESS_LINKLOCAL(IS_OSPF6_DEBUG_INTERFACE, c->address); CONTINUE_IF_ADDRESS_UNSPECIFIED(IS_OSPF6_DEBUG_INTERFACE, c->address); CONTINUE_IF_ADDRESS_LOOPBACK(IS_OSPF6_DEBUG_INTERFACE, c->address); CONTINUE_IF_ADDRESS_V4COMPAT(IS_OSPF6_DEBUG_INTERFACE, c->address); CONTINUE_IF_ADDRESS_V4MAPPED(IS_OSPF6_DEBUG_INTERFACE, c->address); /* apply filter */ if (oi->plist_name) { struct prefix_list *plist; enum prefix_list_type ret; char buf[PREFIX2STR_BUFFER]; prefix2str(c->address, buf, sizeof(buf)); plist = prefix_list_lookup(AFI_IP6, oi->plist_name); ret = prefix_list_apply(plist, (void *)c->address); if (ret == PREFIX_DENY) { if (IS_OSPF6_DEBUG_INTERFACE) zlog_debug( "%s on %s filtered by prefix-list %s ", buf, oi->interface->name, oi->plist_name); continue; } } route = ospf6_route_create(); memcpy(&route->prefix, c->address, sizeof(struct prefix)); apply_mask(&route->prefix); route->type = OSPF6_DEST_TYPE_NETWORK; route->path.area_id = oi->area->area_id; route->path.type = OSPF6_PATH_TYPE_INTRA; route->path.cost = oi->cost; inet_pton(AF_INET6, "::1", &nh_addr); ospf6_route_add_nexthop(route, oi->interface->ifindex, &nh_addr); ospf6_route_add(route, oi->route_connected); } /* create new Link-LSA */ OSPF6_LINK_LSA_SCHEDULE(oi); OSPF6_INTRA_PREFIX_LSA_SCHEDULE_TRANSIT(oi); OSPF6_INTRA_PREFIX_LSA_SCHEDULE_STUB(oi->area); } static void ospf6_interface_state_change(uint8_t next_state, struct ospf6_interface *oi) { uint8_t prev_state; prev_state = oi->state; oi->state = next_state; if (prev_state == next_state) return; /* log */ if (IS_OSPF6_DEBUG_INTERFACE) { zlog_debug("Interface state change %s: %s -> %s", oi->interface->name, ospf6_interface_state_str[prev_state], ospf6_interface_state_str[next_state]); } oi->state_change++; if ((prev_state == OSPF6_INTERFACE_DR || prev_state == OSPF6_INTERFACE_BDR) && (next_state != OSPF6_INTERFACE_DR && next_state != OSPF6_INTERFACE_BDR)) ospf6_sso(oi->interface->ifindex, &alldrouters6, IPV6_LEAVE_GROUP); if ((prev_state != OSPF6_INTERFACE_DR && prev_state != OSPF6_INTERFACE_BDR) && (next_state == OSPF6_INTERFACE_DR || next_state == OSPF6_INTERFACE_BDR)) ospf6_sso(oi->interface->ifindex, &alldrouters6, IPV6_JOIN_GROUP); OSPF6_ROUTER_LSA_SCHEDULE(oi->area); if (next_state == OSPF6_INTERFACE_DOWN) { OSPF6_NETWORK_LSA_EXECUTE(oi); OSPF6_INTRA_PREFIX_LSA_EXECUTE_TRANSIT(oi); OSPF6_INTRA_PREFIX_LSA_SCHEDULE_STUB(oi->area); OSPF6_INTRA_PREFIX_LSA_EXECUTE_TRANSIT(oi); } else if (prev_state == OSPF6_INTERFACE_DR || next_state == OSPF6_INTERFACE_DR) { OSPF6_NETWORK_LSA_SCHEDULE(oi); OSPF6_INTRA_PREFIX_LSA_SCHEDULE_TRANSIT(oi); OSPF6_INTRA_PREFIX_LSA_SCHEDULE_STUB(oi->area); } hook_call(ospf6_interface_change, oi, next_state, prev_state); } /* DR Election, RFC2328 section 9.4 */ #define IS_ELIGIBLE(n) \ ((n)->state >= OSPF6_NEIGHBOR_TWOWAY && (n)->priority != 0) static struct ospf6_neighbor *better_bdrouter(struct ospf6_neighbor *a, struct ospf6_neighbor *b) { if ((a == NULL || !IS_ELIGIBLE(a) || a->drouter == a->router_id) && (b == NULL || !IS_ELIGIBLE(b) || b->drouter == b->router_id)) return NULL; else if (a == NULL || !IS_ELIGIBLE(a) || a->drouter == a->router_id) return b; else if (b == NULL || !IS_ELIGIBLE(b) || b->drouter == b->router_id) return a; if (a->bdrouter == a->router_id && b->bdrouter != b->router_id) return a; if (a->bdrouter != a->router_id && b->bdrouter == b->router_id) return b; if (a->priority > b->priority) return a; if (a->priority < b->priority) return b; if (ntohl(a->router_id) > ntohl(b->router_id)) return a; if (ntohl(a->router_id) < ntohl(b->router_id)) return b; zlog_warn("Router-ID duplicate ?"); return a; } static struct ospf6_neighbor *better_drouter(struct ospf6_neighbor *a, struct ospf6_neighbor *b) { if ((a == NULL || !IS_ELIGIBLE(a) || a->drouter != a->router_id) && (b == NULL || !IS_ELIGIBLE(b) || b->drouter != b->router_id)) return NULL; else if (a == NULL || !IS_ELIGIBLE(a) || a->drouter != a->router_id) return b; else if (b == NULL || !IS_ELIGIBLE(b) || b->drouter != b->router_id) return a; if (a->drouter == a->router_id && b->drouter != b->router_id) return a; if (a->drouter != a->router_id && b->drouter == b->router_id) return b; if (a->priority > b->priority) return a; if (a->priority < b->priority) return b; if (ntohl(a->router_id) > ntohl(b->router_id)) return a; if (ntohl(a->router_id) < ntohl(b->router_id)) return b; zlog_warn("Router-ID duplicate ?"); return a; } static uint8_t dr_election(struct ospf6_interface *oi) { struct listnode *node, *nnode; struct ospf6_neighbor *on, *drouter, *bdrouter, myself; struct ospf6_neighbor *best_drouter, *best_bdrouter; uint8_t next_state = 0; drouter = bdrouter = NULL; best_drouter = best_bdrouter = NULL; /* pseudo neighbor myself, including noting current DR/BDR (1) */ memset(&myself, 0, sizeof(myself)); inet_ntop(AF_INET, &oi->area->ospf6->router_id, myself.name, sizeof(myself.name)); myself.state = OSPF6_NEIGHBOR_TWOWAY; myself.drouter = oi->drouter; myself.bdrouter = oi->bdrouter; myself.priority = oi->priority; myself.router_id = oi->area->ospf6->router_id; /* Electing BDR (2) */ for (ALL_LIST_ELEMENTS(oi->neighbor_list, node, nnode, on)) bdrouter = better_bdrouter(bdrouter, on); best_bdrouter = bdrouter; bdrouter = better_bdrouter(best_bdrouter, &myself); /* Electing DR (3) */ for (ALL_LIST_ELEMENTS(oi->neighbor_list, node, nnode, on)) drouter = better_drouter(drouter, on); best_drouter = drouter; drouter = better_drouter(best_drouter, &myself); if (drouter == NULL) drouter = bdrouter; /* the router itself is newly/no longer DR/BDR (4) */ if ((drouter == &myself && myself.drouter != myself.router_id) || (drouter != &myself && myself.drouter == myself.router_id) || (bdrouter == &myself && myself.bdrouter != myself.router_id) || (bdrouter != &myself && myself.bdrouter == myself.router_id)) { myself.drouter = (drouter ? drouter->router_id : htonl(0)); myself.bdrouter = (bdrouter ? bdrouter->router_id : htonl(0)); /* compatible to Electing BDR (2) */ bdrouter = better_bdrouter(best_bdrouter, &myself); /* compatible to Electing DR (3) */ drouter = better_drouter(best_drouter, &myself); if (drouter == NULL) drouter = bdrouter; } /* Set interface state accordingly (5) */ if (drouter && drouter == &myself) next_state = OSPF6_INTERFACE_DR; else if (bdrouter && bdrouter == &myself) next_state = OSPF6_INTERFACE_BDR; else next_state = OSPF6_INTERFACE_DROTHER; /* If NBMA, schedule Start for each neighbor having priority of 0 (6) */ /* XXX */ /* If DR or BDR change, invoke AdjOK? for each neighbor (7) */ /* RFC 2328 section 12.4. Originating LSAs (3) will be handled accordingly after AdjOK */ if (oi->drouter != (drouter ? drouter->router_id : htonl(0)) || oi->bdrouter != (bdrouter ? bdrouter->router_id : htonl(0))) { if (IS_OSPF6_DEBUG_INTERFACE) zlog_debug("DR Election on %s: DR: %s BDR: %s", oi->interface->name, (drouter ? drouter->name : "0.0.0.0"), (bdrouter ? bdrouter->name : "0.0.0.0")); for (ALL_LIST_ELEMENTS_RO(oi->neighbor_list, node, on)) { if (on->state < OSPF6_NEIGHBOR_TWOWAY) continue; /* Schedule AdjOK. */ thread_add_event(master, adj_ok, on, 0, NULL); } } oi->drouter = (drouter ? drouter->router_id : htonl(0)); oi->bdrouter = (bdrouter ? bdrouter->router_id : htonl(0)); return next_state; } /* Interface State Machine */ int interface_up(struct thread *thread) { struct ospf6_interface *oi; oi = (struct ospf6_interface *)THREAD_ARG(thread); assert(oi && oi->interface); /* * Remove old pointer. If this thread wasn't a timer this * operation won't make a difference, because it is already NULL. */ oi->thread_sso = NULL; if (IS_OSPF6_DEBUG_INTERFACE) zlog_debug("Interface Event %s: [InterfaceUp]", oi->interface->name); /* check physical interface is up */ if (!if_is_operative(oi->interface)) { if (IS_OSPF6_DEBUG_INTERFACE) zlog_debug( "Interface %s is down, can't execute [InterfaceUp]", oi->interface->name); return 0; } /* check interface has a link-local address */ if (!(ospf6_interface_get_linklocal_address(oi->interface) || if_is_loopback(oi->interface))) { if (IS_OSPF6_DEBUG_INTERFACE) zlog_debug( "Interface %s has no link local address, can't execute [InterfaceUp]", oi->interface->name); return 0; } /* Recompute cost */ ospf6_interface_recalculate_cost(oi); /* if already enabled, do nothing */ if (oi->state > OSPF6_INTERFACE_DOWN) { if (IS_OSPF6_DEBUG_INTERFACE) zlog_debug("Interface %s already enabled", oi->interface->name); return 0; } /* If no area assigned, return */ if (oi->area == NULL) { zlog_debug( "%s: Not scheduleing Hello for %s as there is no area assigned yet", __func__, oi->interface->name); return 0; } #ifdef __FreeBSD__ /* * XXX: Schedule IPv6 group join for later, otherwise we might * lose the multicast group registration caused by IPv6 group * leave race. */ if (oi->sso_try_cnt == 0) { oi->sso_try_cnt++; zlog_info("Scheduling %s for sso", oi->interface->name); thread_add_timer(master, interface_up, oi, OSPF6_INTERFACE_SSO_RETRY_INT, &oi->thread_sso); return 0; } #endif /* __FreeBSD__ */ /* Join AllSPFRouters */ if (ospf6_sso(oi->interface->ifindex, &allspfrouters6, IPV6_JOIN_GROUP) < 0) { if (oi->sso_try_cnt++ < OSPF6_INTERFACE_SSO_RETRY_MAX) { zlog_info( "Scheduling %s for sso retry, trial count: %d", oi->interface->name, oi->sso_try_cnt); thread_add_timer(master, interface_up, oi, OSPF6_INTERFACE_SSO_RETRY_INT, &oi->thread_sso); } return 0; } oi->sso_try_cnt = 0; /* Reset on success */ /* Update interface route */ ospf6_interface_connected_route_update(oi->interface); /* Schedule Hello */ if (!CHECK_FLAG(oi->flag, OSPF6_INTERFACE_PASSIVE) && !if_is_loopback(oi->interface)) { oi->thread_send_hello = NULL; thread_add_event(master, ospf6_hello_send, oi, 0, &oi->thread_send_hello); } /* decide next interface state */ if ((if_is_pointopoint(oi->interface)) || (oi->type == OSPF_IFTYPE_POINTOPOINT)) { ospf6_interface_state_change(OSPF6_INTERFACE_POINTTOPOINT, oi); } else if (oi->priority == 0) ospf6_interface_state_change(OSPF6_INTERFACE_DROTHER, oi); else { ospf6_interface_state_change(OSPF6_INTERFACE_WAITING, oi); thread_add_timer(master, wait_timer, oi, oi->dead_interval, NULL); } return 0; } int wait_timer(struct thread *thread) { struct ospf6_interface *oi; oi = (struct ospf6_interface *)THREAD_ARG(thread); assert(oi && oi->interface); if (IS_OSPF6_DEBUG_INTERFACE) zlog_debug("Interface Event %s: [WaitTimer]", oi->interface->name); if (oi->state == OSPF6_INTERFACE_WAITING) ospf6_interface_state_change(dr_election(oi), oi); return 0; } int backup_seen(struct thread *thread) { struct ospf6_interface *oi; oi = (struct ospf6_interface *)THREAD_ARG(thread); assert(oi && oi->interface); if (IS_OSPF6_DEBUG_INTERFACE) zlog_debug("Interface Event %s: [BackupSeen]", oi->interface->name); if (oi->state == OSPF6_INTERFACE_WAITING) ospf6_interface_state_change(dr_election(oi), oi); return 0; } int neighbor_change(struct thread *thread) { struct ospf6_interface *oi; oi = (struct ospf6_interface *)THREAD_ARG(thread); assert(oi && oi->interface); if (IS_OSPF6_DEBUG_INTERFACE) zlog_debug("Interface Event %s: [NeighborChange]", oi->interface->name); if (oi->state == OSPF6_INTERFACE_DROTHER || oi->state == OSPF6_INTERFACE_BDR || oi->state == OSPF6_INTERFACE_DR) ospf6_interface_state_change(dr_election(oi), oi); return 0; } int interface_down(struct thread *thread) { struct ospf6_interface *oi; struct listnode *node, *nnode; struct ospf6_neighbor *on; oi = (struct ospf6_interface *)THREAD_ARG(thread); assert(oi && oi->interface); if (IS_OSPF6_DEBUG_INTERFACE) zlog_debug("Interface Event %s: [InterfaceDown]", oi->interface->name); /* Stop Hellos */ THREAD_OFF(oi->thread_send_hello); /* Stop trying to set socket options. */ THREAD_OFF(oi->thread_sso); /* Leave AllSPFRouters */ if (oi->state > OSPF6_INTERFACE_DOWN) ospf6_sso(oi->interface->ifindex, &allspfrouters6, IPV6_LEAVE_GROUP); ospf6_interface_state_change(OSPF6_INTERFACE_DOWN, oi); for (ALL_LIST_ELEMENTS(oi->neighbor_list, node, nnode, on)) ospf6_neighbor_delete(on); list_delete_all_node(oi->neighbor_list); /* When interface state is reset, also reset information about * DR election, as it is no longer valid. */ oi->drouter = oi->prev_drouter = htonl(0); oi->bdrouter = oi->prev_bdrouter = htonl(0); return 0; } /* show specified interface structure */ static int ospf6_interface_show(struct vty *vty, struct interface *ifp) { struct ospf6_interface *oi; struct connected *c; struct prefix *p; struct listnode *i; char strbuf[PREFIX2STR_BUFFER], drouter[32], bdrouter[32]; const char *type; struct timeval res, now; char duration[32]; struct ospf6_lsa *lsa; /* check physical interface type */ if (if_is_loopback(ifp)) type = "LOOPBACK"; else if (if_is_broadcast(ifp)) type = "BROADCAST"; else if (if_is_pointopoint(ifp)) type = "POINTOPOINT"; else type = "UNKNOWN"; vty_out(vty, "%s is %s, type %s\n", ifp->name, (if_is_operative(ifp) ? "up" : "down"), type); vty_out(vty, " Interface ID: %d\n", ifp->ifindex); if (ifp->info == NULL) { vty_out(vty, " OSPF not enabled on this interface\n"); return 0; } else oi = (struct ospf6_interface *)ifp->info; vty_out(vty, " Internet Address:\n"); for (ALL_LIST_ELEMENTS_RO(ifp->connected, i, c)) { p = c->address; prefix2str(p, strbuf, sizeof(strbuf)); switch (p->family) { case AF_INET: vty_out(vty, " inet : %s\n", strbuf); break; case AF_INET6: vty_out(vty, " inet6: %s\n", strbuf); break; default: vty_out(vty, " ??? : %s\n", strbuf); break; } } if (oi->area) { vty_out(vty, " Instance ID %d, Interface MTU %d (autodetect: %d)\n", oi->instance_id, oi->ifmtu, ifp->mtu6); vty_out(vty, " MTU mismatch detection: %s\n", oi->mtu_ignore ? "disabled" : "enabled"); inet_ntop(AF_INET, &oi->area->area_id, strbuf, sizeof(strbuf)); vty_out(vty, " Area ID %s, Cost %u\n", strbuf, oi->cost); } else vty_out(vty, " Not Attached to Area\n"); vty_out(vty, " State %s, Transmit Delay %d sec, Priority %d\n", ospf6_interface_state_str[oi->state], oi->transdelay, oi->priority); vty_out(vty, " Timer intervals configured:\n"); vty_out(vty, " Hello %d, Dead %d, Retransmit %d\n", oi->hello_interval, oi->dead_interval, oi->rxmt_interval); inet_ntop(AF_INET, &oi->drouter, drouter, sizeof(drouter)); inet_ntop(AF_INET, &oi->bdrouter, bdrouter, sizeof(bdrouter)); vty_out(vty, " DR: %s BDR: %s\n", drouter, bdrouter); vty_out(vty, " Number of I/F scoped LSAs is %u\n", oi->lsdb->count); monotime(&now); timerclear(&res); if (oi->thread_send_lsupdate) timersub(&oi->thread_send_lsupdate->u.sands, &now, &res); timerstring(&res, duration, sizeof(duration)); vty_out(vty, " %d Pending LSAs for LSUpdate in Time %s [thread %s]\n", oi->lsupdate_list->count, duration, (oi->thread_send_lsupdate ? "on" : "off")); for (ALL_LSDB(oi->lsupdate_list, lsa)) vty_out(vty, " %s\n", lsa->name); timerclear(&res); if (oi->thread_send_lsack) timersub(&oi->thread_send_lsack->u.sands, &now, &res); timerstring(&res, duration, sizeof(duration)); vty_out(vty, " %d Pending LSAs for LSAck in Time %s [thread %s]\n", oi->lsack_list->count, duration, (oi->thread_send_lsack ? "on" : "off")); for (ALL_LSDB(oi->lsack_list, lsa)) vty_out(vty, " %s\n", lsa->name); ospf6_bfd_show_info(vty, oi->bfd_info, 1); return 0; } /* show interface */ DEFUN (show_ipv6_ospf6_interface, show_ipv6_ospf6_interface_ifname_cmd, "show ipv6 ospf6 interface [IFNAME]", SHOW_STR IP6_STR OSPF6_STR INTERFACE_STR IFNAME_STR) { struct vrf *vrf = vrf_lookup_by_id(VRF_DEFAULT); int idx_ifname = 4; struct interface *ifp; if (argc == 5) { ifp = if_lookup_by_name(argv[idx_ifname]->arg, VRF_DEFAULT); if (ifp == NULL) { vty_out(vty, "No such Interface: %s\n", argv[idx_ifname]->arg); return CMD_WARNING; } ospf6_interface_show(vty, ifp); } else { FOR_ALL_INTERFACES (vrf, ifp) ospf6_interface_show(vty, ifp); } return CMD_SUCCESS; } static int ospf6_interface_show_traffic(struct vty *vty, uint32_t vrf_id, struct interface *intf_ifp, int display_once) { struct interface *ifp; struct vrf *vrf = NULL; struct ospf6_interface *oi = NULL; vrf = vrf_lookup_by_id(vrf_id); if (!display_once) { vty_out(vty, "\n"); vty_out(vty, "%-12s%-17s%-17s%-17s%-17s%-17s\n", "Interface", " HELLO", " DB-Desc", " LS-Req", " LS-Update", " LS-Ack"); vty_out(vty, "%-10s%-18s%-18s%-17s%-17s%-17s\n", "", " Rx/Tx", " Rx/Tx", " Rx/Tx", " Rx/Tx", " Rx/Tx"); vty_out(vty, "--------------------------------------------------------------------------------------------\n"); } if (intf_ifp == NULL) { FOR_ALL_INTERFACES (vrf, ifp) { if (ifp->info) oi = (struct ospf6_interface *)ifp->info; else continue; vty_out(vty, "%-10s %8u/%-8u %7u/%-7u %7u/%-7u %7u/%-7u %7u/%-7u\n", oi->interface->name, oi->hello_in, oi->hello_out, oi->db_desc_in, oi->db_desc_out, oi->ls_req_in, oi->ls_req_out, oi->ls_upd_in, oi->ls_upd_out, oi->ls_ack_in, oi->ls_ack_out); } } else { oi = intf_ifp->info; if (oi == NULL) return CMD_WARNING; vty_out(vty, "%-10s %8u/%-8u %7u/%-7u %7u/%-7u %7u/%-7u %7u/%-7u\n", oi->interface->name, oi->hello_in, oi->hello_out, oi->db_desc_in, oi->db_desc_out, oi->ls_req_in, oi->ls_req_out, oi->ls_upd_in, oi->ls_upd_out, oi->ls_ack_in, oi->ls_ack_out); } return CMD_SUCCESS; } /* show interface */ DEFUN (show_ipv6_ospf6_interface_traffic, show_ipv6_ospf6_interface_traffic_cmd, "show ipv6 ospf6 interface traffic [IFNAME]", SHOW_STR IP6_STR OSPF6_STR INTERFACE_STR "Protocol Packet counters\n" IFNAME_STR) { int idx_ifname = 0; int display_once = 0; char *intf_name = NULL; struct interface *ifp = NULL; if (argv_find(argv, argc, "IFNAME", &idx_ifname)) { intf_name = argv[idx_ifname]->arg; ifp = if_lookup_by_name(intf_name, VRF_DEFAULT); if (ifp == NULL) { vty_out(vty, "No such Interface: %s\n", intf_name); return CMD_WARNING; } if (ifp->info == NULL) { vty_out(vty, " OSPF not enabled on this interface %s\n", intf_name); return 0; } } ospf6_interface_show_traffic(vty, VRF_DEFAULT, ifp, display_once); return CMD_SUCCESS; } DEFUN (show_ipv6_ospf6_interface_ifname_prefix, show_ipv6_ospf6_interface_ifname_prefix_cmd, "show ipv6 ospf6 interface IFNAME prefix\ [<\ detail\ | []\ >]", SHOW_STR IP6_STR OSPF6_STR INTERFACE_STR IFNAME_STR "Display connected prefixes to advertise\n" "Display details of the prefixes\n" OSPF6_ROUTE_ADDRESS_STR OSPF6_ROUTE_PREFIX_STR OSPF6_ROUTE_MATCH_STR "Display details of the prefixes\n") { int idx_ifname = 4; int idx_prefix = 6; struct interface *ifp; struct ospf6_interface *oi; ifp = if_lookup_by_name(argv[idx_ifname]->arg, VRF_DEFAULT); if (ifp == NULL) { vty_out(vty, "No such Interface: %s\n", argv[idx_ifname]->arg); return CMD_WARNING; } oi = ifp->info; if (oi == NULL) { vty_out(vty, "OSPFv3 is not enabled on %s\n", argv[idx_ifname]->arg); return CMD_WARNING; } ospf6_route_table_show(vty, idx_prefix, argc, argv, oi->route_connected); return CMD_SUCCESS; } DEFUN (show_ipv6_ospf6_interface_prefix, show_ipv6_ospf6_interface_prefix_cmd, "show ipv6 ospf6 interface prefix\ [<\ detail\ | []\ >]", SHOW_STR IP6_STR OSPF6_STR INTERFACE_STR "Display connected prefixes to advertise\n" "Display details of the prefixes\n" OSPF6_ROUTE_ADDRESS_STR OSPF6_ROUTE_PREFIX_STR OSPF6_ROUTE_MATCH_STR "Display details of the prefixes\n") { struct vrf *vrf = vrf_lookup_by_id(VRF_DEFAULT); int idx_prefix = 5; struct ospf6_interface *oi; struct interface *ifp; FOR_ALL_INTERFACES (vrf, ifp) { oi = (struct ospf6_interface *)ifp->info; if (oi == NULL) continue; ospf6_route_table_show(vty, idx_prefix, argc, argv, oi->route_connected); } return CMD_SUCCESS; } /* interface variable set command */ DEFUN (ipv6_ospf6_ifmtu, ipv6_ospf6_ifmtu_cmd, "ipv6 ospf6 ifmtu (1-65535)", IP6_STR OSPF6_STR "Interface MTU\n" "OSPFv3 Interface MTU\n" ) { VTY_DECLVAR_CONTEXT(interface, ifp); int idx_number = 3; struct ospf6_interface *oi; unsigned int ifmtu, iobuflen; struct listnode *node, *nnode; struct ospf6_neighbor *on; assert(ifp); oi = (struct ospf6_interface *)ifp->info; if (oi == NULL) oi = ospf6_interface_create(ifp); assert(oi); ifmtu = strtol(argv[idx_number]->arg, NULL, 10); if (oi->c_ifmtu == ifmtu) return CMD_SUCCESS; if (ifp->mtu6 != 0 && ifp->mtu6 < ifmtu) { vty_out(vty, "%s's ospf6 ifmtu cannot go beyond physical mtu (%d)\n", ifp->name, ifp->mtu6); return CMD_WARNING_CONFIG_FAILED; } if (oi->ifmtu < ifmtu) { iobuflen = ospf6_iobuf_size(ifmtu); if (iobuflen < ifmtu) { vty_out(vty, "%s's ifmtu is adjusted to I/O buffer size (%d).\n", ifp->name, iobuflen); oi->ifmtu = oi->c_ifmtu = iobuflen; } else oi->ifmtu = oi->c_ifmtu = ifmtu; } else oi->ifmtu = oi->c_ifmtu = ifmtu; /* re-establish adjacencies */ for (ALL_LIST_ELEMENTS(oi->neighbor_list, node, nnode, on)) { THREAD_OFF(on->inactivity_timer); thread_add_event(master, inactivity_timer, on, 0, NULL); } return CMD_SUCCESS; } DEFUN (no_ipv6_ospf6_ifmtu, no_ipv6_ospf6_ifmtu_cmd, "no ipv6 ospf6 ifmtu [(1-65535)]", NO_STR IP6_STR OSPF6_STR "Interface MTU\n" "OSPFv3 Interface MTU\n" ) { VTY_DECLVAR_CONTEXT(interface, ifp); struct ospf6_interface *oi; unsigned int iobuflen; struct listnode *node, *nnode; struct ospf6_neighbor *on; assert(ifp); oi = (struct ospf6_interface *)ifp->info; if (oi == NULL) oi = ospf6_interface_create(ifp); assert(oi); if (oi->ifmtu < ifp->mtu) { iobuflen = ospf6_iobuf_size(ifp->mtu); if (iobuflen < ifp->mtu) { vty_out(vty, "%s's ifmtu is adjusted to I/O buffer size (%d).\n", ifp->name, iobuflen); oi->ifmtu = iobuflen; } else oi->ifmtu = ifp->mtu; } else oi->ifmtu = ifp->mtu; oi->c_ifmtu = 0; /* re-establish adjacencies */ for (ALL_LIST_ELEMENTS(oi->neighbor_list, node, nnode, on)) { THREAD_OFF(on->inactivity_timer); thread_add_event(master, inactivity_timer, on, 0, NULL); } return CMD_SUCCESS; } DEFUN (ipv6_ospf6_cost, ipv6_ospf6_cost_cmd, "ipv6 ospf6 cost (1-65535)", IP6_STR OSPF6_STR "Interface cost\n" "Outgoing metric of this interface\n") { VTY_DECLVAR_CONTEXT(interface, ifp); int idx_number = 3; struct ospf6_interface *oi; unsigned long int lcost; assert(ifp); oi = (struct ospf6_interface *)ifp->info; if (oi == NULL) oi = ospf6_interface_create(ifp); assert(oi); lcost = strtol(argv[idx_number]->arg, NULL, 10); if (lcost > UINT32_MAX) { vty_out(vty, "Cost %ld is out of range\n", lcost); return CMD_WARNING_CONFIG_FAILED; } if (oi->cost == lcost) return CMD_SUCCESS; oi->cost = lcost; SET_FLAG(oi->flag, OSPF6_INTERFACE_NOAUTOCOST); ospf6_interface_force_recalculate_cost(oi); return CMD_SUCCESS; } DEFUN (no_ipv6_ospf6_cost, no_ipv6_ospf6_cost_cmd, "no ipv6 ospf6 cost [(1-65535)]", NO_STR IP6_STR OSPF6_STR "Calculate interface cost from bandwidth\n" "Outgoing metric of this interface\n") { VTY_DECLVAR_CONTEXT(interface, ifp); struct ospf6_interface *oi; assert(ifp); oi = (struct ospf6_interface *)ifp->info; if (oi == NULL) oi = ospf6_interface_create(ifp); assert(oi); UNSET_FLAG(oi->flag, OSPF6_INTERFACE_NOAUTOCOST); ospf6_interface_recalculate_cost(oi); return CMD_SUCCESS; } DEFUN (auto_cost_reference_bandwidth, auto_cost_reference_bandwidth_cmd, "auto-cost reference-bandwidth (1-4294967)", "Calculate OSPF interface cost according to bandwidth\n" "Use reference bandwidth method to assign OSPF cost\n" "The reference bandwidth in terms of Mbits per second\n") { VTY_DECLVAR_CONTEXT(ospf6, o); int idx_number = 2; struct ospf6_area *oa; struct ospf6_interface *oi; struct listnode *i, *j; uint32_t refbw; refbw = strtol(argv[idx_number]->arg, NULL, 10); if (refbw < 1 || refbw > 4294967) { vty_out(vty, "reference-bandwidth value is invalid\n"); return CMD_WARNING_CONFIG_FAILED; } /* If reference bandwidth is changed. */ if ((refbw) == o->ref_bandwidth) return CMD_SUCCESS; o->ref_bandwidth = refbw; for (ALL_LIST_ELEMENTS_RO(o->area_list, i, oa)) for (ALL_LIST_ELEMENTS_RO(oa->if_list, j, oi)) ospf6_interface_recalculate_cost(oi); return CMD_SUCCESS; } DEFUN (no_auto_cost_reference_bandwidth, no_auto_cost_reference_bandwidth_cmd, "no auto-cost reference-bandwidth [(1-4294967)]", NO_STR "Calculate OSPF interface cost according to bandwidth\n" "Use reference bandwidth method to assign OSPF cost\n" "The reference bandwidth in terms of Mbits per second\n") { VTY_DECLVAR_CONTEXT(ospf6, o); struct ospf6_area *oa; struct ospf6_interface *oi; struct listnode *i, *j; if (o->ref_bandwidth == OSPF6_REFERENCE_BANDWIDTH) return CMD_SUCCESS; o->ref_bandwidth = OSPF6_REFERENCE_BANDWIDTH; for (ALL_LIST_ELEMENTS_RO(o->area_list, i, oa)) for (ALL_LIST_ELEMENTS_RO(oa->if_list, j, oi)) ospf6_interface_recalculate_cost(oi); return CMD_SUCCESS; } DEFUN (ipv6_ospf6_hellointerval, ipv6_ospf6_hellointerval_cmd, "ipv6 ospf6 hello-interval (1-65535)", IP6_STR OSPF6_STR "Time between HELLO packets\n" SECONDS_STR) { VTY_DECLVAR_CONTEXT(interface, ifp); int idx_number = 3; struct ospf6_interface *oi; assert(ifp); oi = (struct ospf6_interface *)ifp->info; if (oi == NULL) oi = ospf6_interface_create(ifp); assert(oi); oi->hello_interval = strmatch(argv[0]->text, "no") ? OSPF_HELLO_INTERVAL_DEFAULT : strtoul(argv[idx_number]->arg, NULL, 10); return CMD_SUCCESS; } ALIAS (ipv6_ospf6_hellointerval, no_ipv6_ospf6_hellointerval_cmd, "no ipv6 ospf6 hello-interval [(1-65535)]", NO_STR IP6_STR OSPF6_STR "Time between HELLO packets\n" SECONDS_STR) /* interface variable set command */ DEFUN (ipv6_ospf6_deadinterval, ipv6_ospf6_deadinterval_cmd, "ipv6 ospf6 dead-interval (1-65535)", IP6_STR OSPF6_STR "Interval time after which a neighbor is declared down\n" SECONDS_STR) { VTY_DECLVAR_CONTEXT(interface, ifp); int idx_number = 3; struct ospf6_interface *oi; assert(ifp); oi = (struct ospf6_interface *)ifp->info; if (oi == NULL) oi = ospf6_interface_create(ifp); assert(oi); oi->dead_interval = strmatch(argv[0]->arg, "no") ? OSPF_ROUTER_DEAD_INTERVAL_DEFAULT : strtoul(argv[idx_number]->arg, NULL, 10); return CMD_SUCCESS; } ALIAS (ipv6_ospf6_deadinterval, no_ipv6_ospf6_deadinterval_cmd, "no ipv6 ospf6 dead-interval [(1-65535)]", NO_STR IP6_STR OSPF6_STR "Interval time after which a neighbor is declared down\n" SECONDS_STR) /* interface variable set command */ DEFUN (ipv6_ospf6_transmitdelay, ipv6_ospf6_transmitdelay_cmd, "ipv6 ospf6 transmit-delay (1-3600)", IP6_STR OSPF6_STR "Link state transmit delay\n" SECONDS_STR) { VTY_DECLVAR_CONTEXT(interface, ifp); int idx_number = 3; struct ospf6_interface *oi; assert(ifp); oi = (struct ospf6_interface *)ifp->info; if (oi == NULL) oi = ospf6_interface_create(ifp); assert(oi); oi->transdelay = strmatch(argv[0]->text, "no") ? OSPF6_INTERFACE_TRANSDELAY : strtoul(argv[idx_number]->arg, NULL, 10); return CMD_SUCCESS; } ALIAS (ipv6_ospf6_transmitdelay, no_ipv6_ospf6_transmitdelay_cmd, "no ipv6 ospf6 transmit-delay [(1-3600)]", NO_STR IP6_STR OSPF6_STR "Link state transmit delay\n" SECONDS_STR) /* interface variable set command */ DEFUN (ipv6_ospf6_retransmitinterval, ipv6_ospf6_retransmitinterval_cmd, "ipv6 ospf6 retransmit-interval (1-65535)", IP6_STR OSPF6_STR "Time between retransmitting lost link state advertisements\n" SECONDS_STR) { VTY_DECLVAR_CONTEXT(interface, ifp); int idx_number = 3; struct ospf6_interface *oi; assert(ifp); oi = (struct ospf6_interface *)ifp->info; if (oi == NULL) oi = ospf6_interface_create(ifp); assert(oi); oi->rxmt_interval = strmatch(argv[0]->text, "no") ? OSPF_RETRANSMIT_INTERVAL_DEFAULT : strtoul(argv[idx_number]->arg, NULL, 10); return CMD_SUCCESS; } ALIAS (ipv6_ospf6_retransmitinterval, no_ipv6_ospf6_retransmitinterval_cmd, "no ipv6 ospf6 retransmit-interval [(1-65535)]", NO_STR IP6_STR OSPF6_STR "Time between retransmitting lost link state advertisements\n" SECONDS_STR) /* interface variable set command */ DEFUN (ipv6_ospf6_priority, ipv6_ospf6_priority_cmd, "ipv6 ospf6 priority (0-255)", IP6_STR OSPF6_STR "Router priority\n" "Priority value\n") { VTY_DECLVAR_CONTEXT(interface, ifp); int idx_number = 3; struct ospf6_interface *oi; assert(ifp); oi = (struct ospf6_interface *)ifp->info; if (oi == NULL) oi = ospf6_interface_create(ifp); assert(oi); oi->priority = strmatch(argv[0]->text, "no") ? OSPF6_INTERFACE_PRIORITY : strtoul(argv[idx_number]->arg, NULL, 10); if (oi->area && (oi->state == OSPF6_INTERFACE_DROTHER || oi->state == OSPF6_INTERFACE_BDR || oi->state == OSPF6_INTERFACE_DR)) ospf6_interface_state_change(dr_election(oi), oi); return CMD_SUCCESS; } ALIAS (ipv6_ospf6_priority, no_ipv6_ospf6_priority_cmd, "no ipv6 ospf6 priority [(0-255)]", NO_STR IP6_STR OSPF6_STR "Router priority\n" "Priority value\n") DEFUN (ipv6_ospf6_instance, ipv6_ospf6_instance_cmd, "ipv6 ospf6 instance-id (0-255)", IP6_STR OSPF6_STR "Instance ID for this interface\n" "Instance ID value\n") { VTY_DECLVAR_CONTEXT(interface, ifp); int idx_number = 3; struct ospf6_interface *oi; assert(ifp); oi = (struct ospf6_interface *)ifp->info; if (oi == NULL) oi = ospf6_interface_create(ifp); assert(oi); oi->instance_id = strmatch(argv[0]->text, "no") ? OSPF6_INTERFACE_INSTANCE_ID : strtoul(argv[idx_number]->arg, NULL, 10); return CMD_SUCCESS; } ALIAS (ipv6_ospf6_instance, no_ipv6_ospf6_instance_cmd, "no ipv6 ospf6 instance-id [(0-255)]", NO_STR IP6_STR OSPF6_STR "Instance ID for this interface\n" "Instance ID value\n") DEFUN (ipv6_ospf6_passive, ipv6_ospf6_passive_cmd, "ipv6 ospf6 passive", IP6_STR OSPF6_STR "Passive interface; no adjacency will be formed on this interface\n" ) { VTY_DECLVAR_CONTEXT(interface, ifp); struct ospf6_interface *oi; struct listnode *node, *nnode; struct ospf6_neighbor *on; assert(ifp); oi = (struct ospf6_interface *)ifp->info; if (oi == NULL) oi = ospf6_interface_create(ifp); assert(oi); SET_FLAG(oi->flag, OSPF6_INTERFACE_PASSIVE); THREAD_OFF(oi->thread_send_hello); THREAD_OFF(oi->thread_sso); for (ALL_LIST_ELEMENTS(oi->neighbor_list, node, nnode, on)) { THREAD_OFF(on->inactivity_timer); thread_add_event(master, inactivity_timer, on, 0, NULL); } return CMD_SUCCESS; } DEFUN (no_ipv6_ospf6_passive, no_ipv6_ospf6_passive_cmd, "no ipv6 ospf6 passive", NO_STR IP6_STR OSPF6_STR "passive interface: No Adjacency will be formed on this I/F\n" ) { VTY_DECLVAR_CONTEXT(interface, ifp); struct ospf6_interface *oi; assert(ifp); oi = (struct ospf6_interface *)ifp->info; if (oi == NULL) oi = ospf6_interface_create(ifp); assert(oi); UNSET_FLAG(oi->flag, OSPF6_INTERFACE_PASSIVE); THREAD_OFF(oi->thread_send_hello); THREAD_OFF(oi->thread_sso); thread_add_event(master, ospf6_hello_send, oi, 0, &oi->thread_send_hello); return CMD_SUCCESS; } DEFUN (ipv6_ospf6_mtu_ignore, ipv6_ospf6_mtu_ignore_cmd, "ipv6 ospf6 mtu-ignore", IP6_STR OSPF6_STR "Disable MTU mismatch detection on this interface\n" ) { VTY_DECLVAR_CONTEXT(interface, ifp); struct ospf6_interface *oi; assert(ifp); oi = (struct ospf6_interface *)ifp->info; if (oi == NULL) oi = ospf6_interface_create(ifp); assert(oi); oi->mtu_ignore = 1; return CMD_SUCCESS; } DEFUN (no_ipv6_ospf6_mtu_ignore, no_ipv6_ospf6_mtu_ignore_cmd, "no ipv6 ospf6 mtu-ignore", NO_STR IP6_STR OSPF6_STR "Disable MTU mismatch detection on this interface\n" ) { VTY_DECLVAR_CONTEXT(interface, ifp); struct ospf6_interface *oi; assert(ifp); oi = (struct ospf6_interface *)ifp->info; if (oi == NULL) oi = ospf6_interface_create(ifp); assert(oi); oi->mtu_ignore = 0; return CMD_SUCCESS; } DEFUN (ipv6_ospf6_advertise_prefix_list, ipv6_ospf6_advertise_prefix_list_cmd, "ipv6 ospf6 advertise prefix-list WORD", IP6_STR OSPF6_STR "Advertising options\n" "Filter prefix using prefix-list\n" "Prefix list name\n" ) { VTY_DECLVAR_CONTEXT(interface, ifp); int idx_word = 4; struct ospf6_interface *oi; assert(ifp); oi = (struct ospf6_interface *)ifp->info; if (oi == NULL) oi = ospf6_interface_create(ifp); assert(oi); if (oi->plist_name) XFREE(MTYPE_CFG_PLIST_NAME, oi->plist_name); oi->plist_name = XSTRDUP(MTYPE_CFG_PLIST_NAME, argv[idx_word]->arg); ospf6_interface_connected_route_update(oi->interface); if (oi->area) { OSPF6_LINK_LSA_SCHEDULE(oi); if (oi->state == OSPF6_INTERFACE_DR) { OSPF6_NETWORK_LSA_SCHEDULE(oi); OSPF6_INTRA_PREFIX_LSA_SCHEDULE_TRANSIT(oi); } OSPF6_INTRA_PREFIX_LSA_SCHEDULE_STUB(oi->area); } return CMD_SUCCESS; } DEFUN (no_ipv6_ospf6_advertise_prefix_list, no_ipv6_ospf6_advertise_prefix_list_cmd, "no ipv6 ospf6 advertise prefix-list [WORD]", NO_STR IP6_STR OSPF6_STR "Advertising options\n" "Filter prefix using prefix-list\n" "Prefix list name\n") { VTY_DECLVAR_CONTEXT(interface, ifp); struct ospf6_interface *oi; assert(ifp); oi = (struct ospf6_interface *)ifp->info; if (oi == NULL) oi = ospf6_interface_create(ifp); assert(oi); if (oi->plist_name) XFREE(MTYPE_CFG_PLIST_NAME, oi->plist_name); ospf6_interface_connected_route_update(oi->interface); if (oi->area) { OSPF6_LINK_LSA_SCHEDULE(oi); if (oi->state == OSPF6_INTERFACE_DR) { OSPF6_NETWORK_LSA_SCHEDULE(oi); OSPF6_INTRA_PREFIX_LSA_SCHEDULE_TRANSIT(oi); } OSPF6_INTRA_PREFIX_LSA_SCHEDULE_STUB(oi->area); } return CMD_SUCCESS; } DEFUN (ipv6_ospf6_network, ipv6_ospf6_network_cmd, "ipv6 ospf6 network ", IP6_STR OSPF6_STR "Network type\n" "Specify OSPF6 broadcast network\n" "Specify OSPF6 point-to-point network\n" ) { VTY_DECLVAR_CONTEXT(interface, ifp); int idx_network = 3; struct ospf6_interface *oi; assert(ifp); oi = (struct ospf6_interface *)ifp->info; if (oi == NULL) { oi = ospf6_interface_create(ifp); } assert(oi); if (strncmp(argv[idx_network]->arg, "b", 1) == 0) { if (oi->type == OSPF_IFTYPE_BROADCAST) return CMD_SUCCESS; oi->type = OSPF_IFTYPE_BROADCAST; } else if (strncmp(argv[idx_network]->arg, "point-to-p", 10) == 0) { if (oi->type == OSPF_IFTYPE_POINTOPOINT) { return CMD_SUCCESS; } oi->type = OSPF_IFTYPE_POINTOPOINT; } /* Reset the interface */ thread_execute(master, interface_down, oi, 0); thread_execute(master, interface_up, oi, 0); return CMD_SUCCESS; } DEFUN (no_ipv6_ospf6_network, no_ipv6_ospf6_network_cmd, "no ipv6 ospf6 network []", NO_STR IP6_STR OSPF6_STR "Set default network type\n" "Specify OSPF6 broadcast network\n" "Specify OSPF6 point-to-point network\n") { VTY_DECLVAR_CONTEXT(interface, ifp); struct ospf6_interface *oi; int type; assert(ifp); oi = (struct ospf6_interface *)ifp->info; if (oi == NULL) { return CMD_SUCCESS; } type = ospf6_default_iftype(ifp); if (oi->type == type) { return CMD_SUCCESS; } oi->type = type; /* Reset the interface */ thread_execute(master, interface_down, oi, 0); thread_execute(master, interface_up, oi, 0); return CMD_SUCCESS; } static int config_write_ospf6_interface(struct vty *vty) { struct vrf *vrf = vrf_lookup_by_id(VRF_DEFAULT); struct ospf6_interface *oi; struct interface *ifp; FOR_ALL_INTERFACES (vrf, ifp) { oi = (struct ospf6_interface *)ifp->info; if (oi == NULL) continue; vty_frame(vty, "interface %s\n", oi->interface->name); if (ifp->desc) vty_out(vty, " description %s\n", ifp->desc); if (oi->c_ifmtu) vty_out(vty, " ipv6 ospf6 ifmtu %d\n", oi->c_ifmtu); if (CHECK_FLAG(oi->flag, OSPF6_INTERFACE_NOAUTOCOST)) vty_out(vty, " ipv6 ospf6 cost %d\n", oi->cost); if (oi->hello_interval != OSPF6_INTERFACE_HELLO_INTERVAL) vty_out(vty, " ipv6 ospf6 hello-interval %d\n", oi->hello_interval); if (oi->dead_interval != OSPF6_INTERFACE_DEAD_INTERVAL) vty_out(vty, " ipv6 ospf6 dead-interval %d\n", oi->dead_interval); if (oi->rxmt_interval != OSPF6_INTERFACE_RXMT_INTERVAL) vty_out(vty, " ipv6 ospf6 retransmit-interval %d\n", oi->rxmt_interval); if (oi->priority != OSPF6_INTERFACE_PRIORITY) vty_out(vty, " ipv6 ospf6 priority %d\n", oi->priority); if (oi->transdelay != OSPF6_INTERFACE_TRANSDELAY) vty_out(vty, " ipv6 ospf6 transmit-delay %d\n", oi->transdelay); if (oi->instance_id != OSPF6_INTERFACE_INSTANCE_ID) vty_out(vty, " ipv6 ospf6 instance-id %d\n", oi->instance_id); if (oi->plist_name) vty_out(vty, " ipv6 ospf6 advertise prefix-list %s\n", oi->plist_name); if (CHECK_FLAG(oi->flag, OSPF6_INTERFACE_PASSIVE)) vty_out(vty, " ipv6 ospf6 passive\n"); if (oi->mtu_ignore) vty_out(vty, " ipv6 ospf6 mtu-ignore\n"); if (oi->type != ospf6_default_iftype(ifp)) { if (oi->type == OSPF_IFTYPE_POINTOPOINT) vty_out(vty, " ipv6 ospf6 network point-to-point\n"); else if (oi->type == OSPF_IFTYPE_BROADCAST) vty_out(vty, " ipv6 ospf6 network broadcast\n"); } ospf6_bfd_write_config(vty, oi); vty_endframe(vty, "!\n"); } return 0; } static struct cmd_node interface_node = { INTERFACE_NODE, "%s(config-if)# ", 1 /* VTYSH */ }; void ospf6_interface_init(void) { /* Install interface node. */ install_node(&interface_node, config_write_ospf6_interface); if_cmd_init(); install_element(VIEW_NODE, &show_ipv6_ospf6_interface_prefix_cmd); install_element(VIEW_NODE, &show_ipv6_ospf6_interface_ifname_cmd); install_element(VIEW_NODE, &show_ipv6_ospf6_interface_ifname_prefix_cmd); install_element(VIEW_NODE, &show_ipv6_ospf6_interface_traffic_cmd); install_element(INTERFACE_NODE, &ipv6_ospf6_cost_cmd); install_element(INTERFACE_NODE, &no_ipv6_ospf6_cost_cmd); install_element(INTERFACE_NODE, &ipv6_ospf6_ifmtu_cmd); install_element(INTERFACE_NODE, &no_ipv6_ospf6_ifmtu_cmd); install_element(INTERFACE_NODE, &ipv6_ospf6_deadinterval_cmd); install_element(INTERFACE_NODE, &ipv6_ospf6_hellointerval_cmd); install_element(INTERFACE_NODE, &ipv6_ospf6_priority_cmd); install_element(INTERFACE_NODE, &ipv6_ospf6_retransmitinterval_cmd); install_element(INTERFACE_NODE, &ipv6_ospf6_transmitdelay_cmd); install_element(INTERFACE_NODE, &ipv6_ospf6_instance_cmd); install_element(INTERFACE_NODE, &no_ipv6_ospf6_deadinterval_cmd); install_element(INTERFACE_NODE, &no_ipv6_ospf6_hellointerval_cmd); install_element(INTERFACE_NODE, &no_ipv6_ospf6_priority_cmd); install_element(INTERFACE_NODE, &no_ipv6_ospf6_retransmitinterval_cmd); install_element(INTERFACE_NODE, &no_ipv6_ospf6_transmitdelay_cmd); install_element(INTERFACE_NODE, &no_ipv6_ospf6_instance_cmd); install_element(INTERFACE_NODE, &ipv6_ospf6_passive_cmd); install_element(INTERFACE_NODE, &no_ipv6_ospf6_passive_cmd); install_element(INTERFACE_NODE, &ipv6_ospf6_mtu_ignore_cmd); install_element(INTERFACE_NODE, &no_ipv6_ospf6_mtu_ignore_cmd); install_element(INTERFACE_NODE, &ipv6_ospf6_advertise_prefix_list_cmd); install_element(INTERFACE_NODE, &no_ipv6_ospf6_advertise_prefix_list_cmd); install_element(INTERFACE_NODE, &ipv6_ospf6_network_cmd); install_element(INTERFACE_NODE, &no_ipv6_ospf6_network_cmd); /* reference bandwidth commands */ install_element(OSPF6_NODE, &auto_cost_reference_bandwidth_cmd); install_element(OSPF6_NODE, &no_auto_cost_reference_bandwidth_cmd); } /* Clear the specified interface structure */ static void ospf6_interface_clear(struct vty *vty, struct interface *ifp) { struct ospf6_interface *oi; if (!if_is_operative(ifp)) return; if (ifp->info == NULL) return; oi = (struct ospf6_interface *)ifp->info; if (IS_OSPF6_DEBUG_INTERFACE) zlog_debug("Interface %s: clear by reset", ifp->name); /* Reset the interface */ thread_execute(master, interface_down, oi, 0); thread_execute(master, interface_up, oi, 0); } /* Clear interface */ DEFUN (clear_ipv6_ospf6_interface, clear_ipv6_ospf6_interface_cmd, "clear ipv6 ospf6 interface [IFNAME]", CLEAR_STR IP6_STR OSPF6_STR INTERFACE_STR IFNAME_STR ) { struct vrf *vrf = vrf_lookup_by_id(VRF_DEFAULT); int idx_ifname = 4; struct interface *ifp; if (argc == 4) /* Clear all the ospfv3 interfaces. */ { FOR_ALL_INTERFACES (vrf, ifp) ospf6_interface_clear(vty, ifp); } else /* Interface name is specified. */ { if ((ifp = if_lookup_by_name(argv[idx_ifname]->arg, VRF_DEFAULT)) == NULL) { vty_out(vty, "No such Interface: %s\n", argv[idx_ifname]->arg); return CMD_WARNING; } ospf6_interface_clear(vty, ifp); } return CMD_SUCCESS; } void install_element_ospf6_clear_interface(void) { install_element(ENABLE_NODE, &clear_ipv6_ospf6_interface_cmd); } DEFUN (debug_ospf6_interface, debug_ospf6_interface_cmd, "debug ospf6 interface", DEBUG_STR OSPF6_STR "Debug OSPFv3 Interface\n" ) { OSPF6_DEBUG_INTERFACE_ON(); return CMD_SUCCESS; } DEFUN (no_debug_ospf6_interface, no_debug_ospf6_interface_cmd, "no debug ospf6 interface", NO_STR DEBUG_STR OSPF6_STR "Debug OSPFv3 Interface\n" ) { OSPF6_DEBUG_INTERFACE_OFF(); return CMD_SUCCESS; } int config_write_ospf6_debug_interface(struct vty *vty) { if (IS_OSPF6_DEBUG_INTERFACE) vty_out(vty, "debug ospf6 interface\n"); return 0; } void install_element_ospf6_debug_interface(void) { install_element(ENABLE_NODE, &debug_ospf6_interface_cmd); install_element(ENABLE_NODE, &no_debug_ospf6_interface_cmd); install_element(CONFIG_NODE, &debug_ospf6_interface_cmd); install_element(CONFIG_NODE, &no_debug_ospf6_interface_cmd); } frr-7.2.1/ospf6d/ospf6_interface.h0000644000000000000000000001255613610377563013654 00000000000000/* * Copyright (C) 2003 Yasuhiro Ohara * * This file is part of GNU Zebra. * * GNU Zebra is free software; you can redistribute it and/or modify it * under the terms of the GNU General Public License as published by the * Free Software Foundation; either version 2, or (at your option) any * later version. * * GNU Zebra 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; see the file COPYING; if not, write to the Free Software * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA */ #ifndef OSPF6_INTERFACE_H #define OSPF6_INTERFACE_H #include "qobj.h" #include "hook.h" #include "if.h" /* Debug option */ extern unsigned char conf_debug_ospf6_interface; #define OSPF6_DEBUG_INTERFACE_ON() (conf_debug_ospf6_interface = 1) #define OSPF6_DEBUG_INTERFACE_OFF() (conf_debug_ospf6_interface = 0) #define IS_OSPF6_DEBUG_INTERFACE (conf_debug_ospf6_interface) /* Interface structure */ struct ospf6_interface { /* IF info from zebra */ struct interface *interface; /* back pointer */ struct ospf6_area *area; /* list of ospf6 neighbor */ struct list *neighbor_list; /* linklocal address of this I/F */ struct in6_addr *linklocal_addr; /* Interface ID; use interface->ifindex */ /* ospf6 instance id */ uint8_t instance_id; /* I/F transmission delay */ uint32_t transdelay; /* Network Type */ uint8_t type; /* Router Priority */ uint8_t priority; /* Time Interval */ uint16_t hello_interval; uint16_t dead_interval; uint32_t rxmt_interval; uint32_t state_change; /* Cost */ uint32_t cost; /* I/F MTU */ uint32_t ifmtu; /* Configured MTU */ uint32_t c_ifmtu; /* Interface State */ uint8_t state; /* Interface socket setting trial counter, resets on success */ uint8_t sso_try_cnt; struct thread *thread_sso; /* OSPF6 Interface flag */ char flag; /* MTU mismatch check */ uint8_t mtu_ignore; /* Decision of DR Election */ uint32_t drouter; uint32_t bdrouter; uint32_t prev_drouter; uint32_t prev_bdrouter; /* Linklocal LSA Database: includes Link-LSA */ struct ospf6_lsdb *lsdb; struct ospf6_lsdb *lsdb_self; struct ospf6_lsdb *lsupdate_list; struct ospf6_lsdb *lsack_list; /* Ongoing Tasks */ struct thread *thread_send_hello; struct thread *thread_send_lsupdate; struct thread *thread_send_lsack; struct thread *thread_network_lsa; struct thread *thread_link_lsa; struct thread *thread_intra_prefix_lsa; struct thread *thread_as_extern_lsa; struct ospf6_route_table *route_connected; /* prefix-list name to filter connected prefix */ char *plist_name; /* BFD information */ void *bfd_info; /* Statistics Fields */ uint32_t hello_in; uint32_t hello_out; uint32_t db_desc_in; uint32_t db_desc_out; uint32_t ls_req_in; uint32_t ls_req_out; uint32_t ls_upd_in; uint32_t ls_upd_out; uint32_t ls_ack_in; uint32_t ls_ack_out; uint32_t discarded; QOBJ_FIELDS }; DECLARE_QOBJ_TYPE(ospf6_interface) /* interface state */ #define OSPF6_INTERFACE_NONE 0 #define OSPF6_INTERFACE_DOWN 1 #define OSPF6_INTERFACE_LOOPBACK 2 #define OSPF6_INTERFACE_WAITING 3 #define OSPF6_INTERFACE_POINTTOPOINT 4 #define OSPF6_INTERFACE_DROTHER 5 #define OSPF6_INTERFACE_BDR 6 #define OSPF6_INTERFACE_DR 7 #define OSPF6_INTERFACE_MAX 8 extern const char *ospf6_interface_state_str[]; /* flags */ #define OSPF6_INTERFACE_DISABLE 0x01 #define OSPF6_INTERFACE_PASSIVE 0x02 #define OSPF6_INTERFACE_NOAUTOCOST 0x04 /* default values */ #define OSPF6_INTERFACE_HELLO_INTERVAL 10 #define OSPF6_INTERFACE_DEAD_INTERVAL 40 #define OSPF6_INTERFACE_RXMT_INTERVAL 5 #define OSPF6_INTERFACE_COST 1 #define OSPF6_INTERFACE_PRIORITY 1 #define OSPF6_INTERFACE_TRANSDELAY 1 #define OSPF6_INTERFACE_INSTANCE_ID 0 #define OSPF6_INTERFACE_BANDWIDTH 10000 /* Mbps */ #define OSPF6_REFERENCE_BANDWIDTH 100000 /* Mbps */ #define OSPF6_INTERFACE_SSO_RETRY_INT 1 #define OSPF6_INTERFACE_SSO_RETRY_MAX 5 /* Function Prototypes */ extern struct ospf6_interface *ospf6_interface_lookup_by_ifindex(ifindex_t); extern struct ospf6_interface *ospf6_interface_create(struct interface *); extern void ospf6_interface_delete(struct ospf6_interface *); extern void ospf6_interface_enable(struct ospf6_interface *); extern void ospf6_interface_disable(struct ospf6_interface *); extern void ospf6_interface_if_add(struct interface *); extern void ospf6_interface_state_update(struct interface *); extern void ospf6_interface_connected_route_update(struct interface *); /* interface event */ extern int interface_up(struct thread *); extern int interface_down(struct thread *); extern int wait_timer(struct thread *); extern int backup_seen(struct thread *); extern int neighbor_change(struct thread *); extern void ospf6_interface_init(void); extern void install_element_ospf6_clear_interface(void); extern int config_write_ospf6_debug_interface(struct vty *vty); extern void install_element_ospf6_debug_interface(void); DECLARE_HOOK(ospf6_interface_change, (struct ospf6_interface * oi, int state, int old_state), (oi, state, old_state)) #endif /* OSPF6_INTERFACE_H */ frr-7.2.1/ospf6d/ospf6_intra.c0000644000000000000000000021125113610377563013015 00000000000000/* * Copyright (C) 2003 Yasuhiro Ohara * * This file is part of GNU Zebra. * * GNU Zebra is free software; you can redistribute it and/or modify it * under the terms of the GNU General Public License as published by the * Free Software Foundation; either version 2, or (at your option) any * later version. * * GNU Zebra 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; see the file COPYING; if not, write to the Free Software * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA */ #include #include "log.h" #include "linklist.h" #include "thread.h" #include "memory.h" #include "if.h" #include "prefix.h" #include "table.h" #include "vty.h" #include "command.h" #include "vrf.h" #include "ospf6_proto.h" #include "ospf6_message.h" #include "ospf6_route.h" #include "ospf6_lsa.h" #include "ospf6_lsdb.h" #include "ospf6_top.h" #include "ospf6_area.h" #include "ospf6_interface.h" #include "ospf6_neighbor.h" #include "ospf6_intra.h" #include "ospf6_asbr.h" #include "ospf6_abr.h" #include "ospf6_flood.h" #include "ospf6d.h" #include "ospf6_spf.h" unsigned char conf_debug_ospf6_brouter = 0; uint32_t conf_debug_ospf6_brouter_specific_router_id; uint32_t conf_debug_ospf6_brouter_specific_area_id; #define MAX_LSA_PAYLOAD (1024 + 256) /******************************/ /* RFC2740 3.4.3.1 Router-LSA */ /******************************/ static char *ospf6_router_lsa_get_nbr_id(struct ospf6_lsa *lsa, char *buf, int buflen, int pos) { struct ospf6_router_lsa *router_lsa; struct ospf6_router_lsdesc *lsdesc; char *start, *end; char buf1[INET_ADDRSTRLEN], buf2[INET_ADDRSTRLEN]; if (lsa) { router_lsa = (struct ospf6_router_lsa *)((char *)lsa->header + sizeof(struct ospf6_lsa_header)); start = (char *)router_lsa + sizeof(struct ospf6_router_lsa); end = (char *)lsa->header + ntohs(lsa->header->length); lsdesc = (struct ospf6_router_lsdesc *)(start + pos * (sizeof(struct ospf6_router_lsdesc))); if ((char *)lsdesc < end) { if (buf && (buflen > INET_ADDRSTRLEN * 2)) { inet_ntop(AF_INET, &lsdesc->neighbor_interface_id, buf1, sizeof(buf1)); inet_ntop(AF_INET, &lsdesc->neighbor_router_id, buf2, sizeof(buf2)); sprintf(buf, "%s/%s", buf2, buf1); } } else return NULL; } return buf; } static int ospf6_router_lsa_show(struct vty *vty, struct ospf6_lsa *lsa) { char *start, *end, *current; char buf[32], name[32], bits[16], options[32]; struct ospf6_router_lsa *router_lsa; struct ospf6_router_lsdesc *lsdesc; router_lsa = (struct ospf6_router_lsa *)((char *)lsa->header + sizeof(struct ospf6_lsa_header)); ospf6_capability_printbuf(router_lsa->bits, bits, sizeof(bits)); ospf6_options_printbuf(router_lsa->options, options, sizeof(options)); vty_out(vty, " Bits: %s Options: %s\n", bits, options); start = (char *)router_lsa + sizeof(struct ospf6_router_lsa); end = (char *)lsa->header + ntohs(lsa->header->length); for (current = start; current + sizeof(struct ospf6_router_lsdesc) <= end; current += sizeof(struct ospf6_router_lsdesc)) { lsdesc = (struct ospf6_router_lsdesc *)current; if (lsdesc->type == OSPF6_ROUTER_LSDESC_POINTTOPOINT) snprintf(name, sizeof(name), "Point-To-Point"); else if (lsdesc->type == OSPF6_ROUTER_LSDESC_TRANSIT_NETWORK) snprintf(name, sizeof(name), "Transit-Network"); else if (lsdesc->type == OSPF6_ROUTER_LSDESC_STUB_NETWORK) snprintf(name, sizeof(name), "Stub-Network"); else if (lsdesc->type == OSPF6_ROUTER_LSDESC_VIRTUAL_LINK) snprintf(name, sizeof(name), "Virtual-Link"); else snprintf(name, sizeof(name), "Unknown (%#x)", lsdesc->type); vty_out(vty, " Type: %s Metric: %d\n", name, ntohs(lsdesc->metric)); vty_out(vty, " Interface ID: %s\n", inet_ntop(AF_INET, &lsdesc->interface_id, buf, sizeof(buf))); vty_out(vty, " Neighbor Interface ID: %s\n", inet_ntop(AF_INET, &lsdesc->neighbor_interface_id, buf, sizeof(buf))); vty_out(vty, " Neighbor Router ID: %s\n", inet_ntop(AF_INET, &lsdesc->neighbor_router_id, buf, sizeof(buf))); } return 0; } static void ospf6_router_lsa_options_set(struct ospf6_area *oa, struct ospf6_router_lsa *router_lsa) { OSPF6_OPT_CLEAR_ALL(router_lsa->options); memcpy(router_lsa->options, oa->options, 3); if (ospf6_is_router_abr(ospf6)) SET_FLAG(router_lsa->bits, OSPF6_ROUTER_BIT_B); else UNSET_FLAG(router_lsa->bits, OSPF6_ROUTER_BIT_B); if (!IS_AREA_STUB(oa) && ospf6_asbr_is_asbr(oa->ospf6)) { SET_FLAG(router_lsa->bits, OSPF6_ROUTER_BIT_E); } else { UNSET_FLAG(router_lsa->bits, OSPF6_ROUTER_BIT_E); } UNSET_FLAG(router_lsa->bits, OSPF6_ROUTER_BIT_V); UNSET_FLAG(router_lsa->bits, OSPF6_ROUTER_BIT_W); } int ospf6_router_is_stub_router(struct ospf6_lsa *lsa) { struct ospf6_router_lsa *rtr_lsa; if (lsa != NULL && OSPF6_LSA_IS_TYPE(ROUTER, lsa)) { rtr_lsa = (struct ospf6_router_lsa *)((caddr_t)lsa->header + sizeof(struct ospf6_lsa_header)); if (!OSPF6_OPT_ISSET(rtr_lsa->options, OSPF6_OPT_R)) { return (OSPF6_IS_STUB_ROUTER); } else if (!OSPF6_OPT_ISSET(rtr_lsa->options, OSPF6_OPT_V6)) { return (OSPF6_IS_STUB_ROUTER_V6); } } return (OSPF6_NOT_STUB_ROUTER); } int ospf6_router_lsa_originate(struct thread *thread) { struct ospf6_area *oa; char buffer[OSPF6_MAX_LSASIZE]; struct ospf6_lsa_header *lsa_header; struct ospf6_lsa *lsa; uint32_t link_state_id = 0; struct listnode *node, *nnode; struct listnode *j; struct ospf6_interface *oi; struct ospf6_neighbor *on, *drouter = NULL; struct ospf6_router_lsa *router_lsa; struct ospf6_router_lsdesc *lsdesc; uint16_t type; uint32_t router; int count; oa = (struct ospf6_area *)THREAD_ARG(thread); oa->thread_router_lsa = NULL; if (IS_OSPF6_DEBUG_ORIGINATE(ROUTER)) zlog_debug("Originate Router-LSA for Area %s", oa->name); memset(buffer, 0, sizeof(buffer)); lsa_header = (struct ospf6_lsa_header *)buffer; router_lsa = (struct ospf6_router_lsa *)((caddr_t)lsa_header + sizeof(struct ospf6_lsa_header)); ospf6_router_lsa_options_set(oa, router_lsa); /* describe links for each interfaces */ lsdesc = (struct ospf6_router_lsdesc *)((caddr_t)router_lsa + sizeof(struct ospf6_router_lsa)); for (ALL_LIST_ELEMENTS(oa->if_list, node, nnode, oi)) { /* Interfaces in state Down or Loopback are not described */ if (oi->state == OSPF6_INTERFACE_DOWN || oi->state == OSPF6_INTERFACE_LOOPBACK) continue; /* Nor are interfaces without any full adjacencies described */ count = 0; for (ALL_LIST_ELEMENTS_RO(oi->neighbor_list, j, on)) if (on->state == OSPF6_NEIGHBOR_FULL) count++; if (count == 0) continue; /* Multiple Router-LSA instance according to size limit setting */ if ((oa->router_lsa_size_limit != 0) && ((size_t)((char *)lsdesc - buffer) + sizeof(struct ospf6_router_lsdesc) > oa->router_lsa_size_limit)) { if ((caddr_t)lsdesc == (caddr_t)router_lsa + sizeof(struct ospf6_router_lsa)) { if (IS_OSPF6_DEBUG_ORIGINATE(ROUTER)) zlog_debug( "Size limit setting for Router-LSA too short"); return 0; } /* Fill LSA Header */ lsa_header->age = 0; lsa_header->type = htons(OSPF6_LSTYPE_ROUTER); lsa_header->id = htonl(link_state_id); lsa_header->adv_router = oa->ospf6->router_id; lsa_header->seqnum = ospf6_new_ls_seqnum( lsa_header->type, lsa_header->id, lsa_header->adv_router, oa->lsdb); lsa_header->length = htons((caddr_t)lsdesc - (caddr_t)buffer); /* LSA checksum */ ospf6_lsa_checksum(lsa_header); /* create LSA */ lsa = ospf6_lsa_create(lsa_header); /* Originate */ ospf6_lsa_originate_area(lsa, oa); /* Reset Buffer to fill next Router LSA */ memset(buffer, 0, sizeof(buffer)); lsa_header = (struct ospf6_lsa_header *)buffer; router_lsa = (struct ospf6_router_lsa *)((caddr_t)lsa_header + sizeof(struct ospf6_lsa_header)); ospf6_router_lsa_options_set(oa, router_lsa); /* describe links for each interfaces */ lsdesc = (struct ospf6_router_lsdesc *)((caddr_t)router_lsa + sizeof(struct ospf6_router_lsa)); link_state_id++; } /* Point-to-Point interfaces */ if (oi->type == OSPF_IFTYPE_POINTOPOINT) { for (ALL_LIST_ELEMENTS_RO(oi->neighbor_list, j, on)) { if (on->state != OSPF6_NEIGHBOR_FULL) continue; lsdesc->type = OSPF6_ROUTER_LSDESC_POINTTOPOINT; lsdesc->metric = htons(oi->cost); lsdesc->interface_id = htonl(oi->interface->ifindex); lsdesc->neighbor_interface_id = htonl(on->ifindex); lsdesc->neighbor_router_id = on->router_id; lsdesc++; } } /* Broadcast and NBMA interfaces */ else if (oi->type == OSPF_IFTYPE_BROADCAST) { /* If this router is not DR, and If this router not fully adjacent with DR, this interface is not transit yet: ignore. */ if (oi->state != OSPF6_INTERFACE_DR) { drouter = ospf6_neighbor_lookup(oi->drouter, oi); if (drouter == NULL || drouter->state != OSPF6_NEIGHBOR_FULL) continue; } lsdesc->type = OSPF6_ROUTER_LSDESC_TRANSIT_NETWORK; lsdesc->metric = htons(oi->cost); lsdesc->interface_id = htonl(oi->interface->ifindex); if (oi->state != OSPF6_INTERFACE_DR) { lsdesc->neighbor_interface_id = htonl(drouter->ifindex); lsdesc->neighbor_router_id = drouter->router_id; } else { lsdesc->neighbor_interface_id = htonl(oi->interface->ifindex); lsdesc->neighbor_router_id = oi->area->ospf6->router_id; } lsdesc++; } else { assert(0); /* Unknown interface type */ } /* Virtual links */ /* xxx */ /* Point-to-Multipoint interfaces */ /* xxx */ } /* Fill LSA Header */ lsa_header->age = 0; lsa_header->type = htons(OSPF6_LSTYPE_ROUTER); lsa_header->id = htonl(link_state_id); lsa_header->adv_router = oa->ospf6->router_id; lsa_header->seqnum = ospf6_new_ls_seqnum(lsa_header->type, lsa_header->id, lsa_header->adv_router, oa->lsdb); lsa_header->length = htons((caddr_t)lsdesc - (caddr_t)buffer); /* LSA checksum */ ospf6_lsa_checksum(lsa_header); /* create LSA */ lsa = ospf6_lsa_create(lsa_header); /* Originate */ ospf6_lsa_originate_area(lsa, oa); link_state_id++; /* Do premature-aging of rest, undesired Router-LSAs */ type = ntohs(OSPF6_LSTYPE_ROUTER); router = oa->ospf6->router_id; count = 0; for (ALL_LSDB_TYPED_ADVRTR(oa->lsdb, type, router, lsa)) { if (ntohl(lsa->header->id) < link_state_id) continue; ospf6_lsa_purge(lsa); count++; } /* * Waiting till the LSA is actually removed from the database to trigger * SPF delays network convergence. Unlike IPv4, for an ABR, when all * interfaces associated with an area are gone, triggering an SPF right * away * helps convergence with inter-area routes. */ if (count && !link_state_id) ospf6_spf_schedule(oa->ospf6, OSPF6_SPF_FLAGS_ROUTER_LSA_ORIGINATED); return 0; } /*******************************/ /* RFC2740 3.4.3.2 Network-LSA */ /*******************************/ static char *ospf6_network_lsa_get_ar_id(struct ospf6_lsa *lsa, char *buf, int buflen, int pos) { char *start, *end, *current; struct ospf6_network_lsa *network_lsa; struct ospf6_network_lsdesc *lsdesc; if (lsa) { network_lsa = (struct ospf6_network_lsa *)((caddr_t)lsa->header + sizeof(struct ospf6_lsa_header)); start = (char *)network_lsa + sizeof(struct ospf6_network_lsa); end = (char *)lsa->header + ntohs(lsa->header->length); current = start + pos * (sizeof(struct ospf6_network_lsdesc)); if ((current + sizeof(struct ospf6_network_lsdesc)) <= end) { lsdesc = (struct ospf6_network_lsdesc *)current; if (buf) inet_ntop(AF_INET, &lsdesc->router_id, buf, buflen); } else return NULL; } return (buf); } static int ospf6_network_lsa_show(struct vty *vty, struct ospf6_lsa *lsa) { char *start, *end, *current; struct ospf6_network_lsa *network_lsa; struct ospf6_network_lsdesc *lsdesc; char buf[128], options[32]; network_lsa = (struct ospf6_network_lsa *)((caddr_t)lsa->header + sizeof(struct ospf6_lsa_header)); ospf6_options_printbuf(network_lsa->options, options, sizeof(options)); vty_out(vty, " Options: %s\n", options); start = (char *)network_lsa + sizeof(struct ospf6_network_lsa); end = (char *)lsa->header + ntohs(lsa->header->length); for (current = start; current + sizeof(struct ospf6_network_lsdesc) <= end; current += sizeof(struct ospf6_network_lsdesc)) { lsdesc = (struct ospf6_network_lsdesc *)current; inet_ntop(AF_INET, &lsdesc->router_id, buf, sizeof(buf)); vty_out(vty, " Attached Router: %s\n", buf); } return 0; } int ospf6_network_lsa_originate(struct thread *thread) { struct ospf6_interface *oi; char buffer[OSPF6_MAX_LSASIZE]; struct ospf6_lsa_header *lsa_header; int count; struct ospf6_lsa *old, *lsa; struct ospf6_network_lsa *network_lsa; struct ospf6_network_lsdesc *lsdesc; struct ospf6_neighbor *on; struct ospf6_link_lsa *link_lsa; struct listnode *i; uint16_t type; oi = (struct ospf6_interface *)THREAD_ARG(thread); oi->thread_network_lsa = NULL; /* The interface must be enabled until here. A Network-LSA of a disabled interface (but was once enabled) should be flushed by ospf6_lsa_refresh (), and does not come here. */ assert(oi->area); old = ospf6_lsdb_lookup(htons(OSPF6_LSTYPE_NETWORK), htonl(oi->interface->ifindex), oi->area->ospf6->router_id, oi->area->lsdb); /* Do not originate Network-LSA if not DR */ if (oi->state != OSPF6_INTERFACE_DR) { if (old) { ospf6_lsa_purge(old); /* * Waiting till the LSA is actually removed from the * database to * trigger SPF delays network convergence. */ ospf6_spf_schedule( oi->area->ospf6, OSPF6_SPF_FLAGS_NETWORK_LSA_ORIGINATED); } return 0; } if (IS_OSPF6_DEBUG_ORIGINATE(NETWORK)) zlog_debug("Originate Network-LSA for Interface %s", oi->interface->name); /* If none of neighbor is adjacent to us */ count = 0; for (ALL_LIST_ELEMENTS_RO(oi->neighbor_list, i, on)) if (on->state == OSPF6_NEIGHBOR_FULL) count++; if (count == 0) { if (IS_OSPF6_DEBUG_ORIGINATE(NETWORK)) zlog_debug("Interface stub, ignore"); if (old) ospf6_lsa_purge(old); return 0; } /* prepare buffer */ memset(buffer, 0, sizeof(buffer)); lsa_header = (struct ospf6_lsa_header *)buffer; network_lsa = (struct ospf6_network_lsa *)((caddr_t)lsa_header + sizeof(struct ospf6_lsa_header)); /* Collect the interface's Link-LSAs to describe network's optional capabilities */ type = htons(OSPF6_LSTYPE_LINK); for (ALL_LSDB_TYPED(oi->lsdb, type, lsa)) { link_lsa = (struct ospf6_link_lsa *)((caddr_t)lsa->header + sizeof(struct ospf6_lsa_header)); network_lsa->options[0] |= link_lsa->options[0]; network_lsa->options[1] |= link_lsa->options[1]; network_lsa->options[2] |= link_lsa->options[2]; } lsdesc = (struct ospf6_network_lsdesc *)((caddr_t)network_lsa + sizeof(struct ospf6_network_lsa)); /* set Link Description to the router itself */ lsdesc->router_id = oi->area->ospf6->router_id; lsdesc++; /* Walk through the neighbors */ for (ALL_LIST_ELEMENTS_RO(oi->neighbor_list, i, on)) { if (on->state != OSPF6_NEIGHBOR_FULL) continue; /* set this neighbor's Router-ID to LSA */ lsdesc->router_id = on->router_id; lsdesc++; } /* Fill LSA Header */ lsa_header->age = 0; lsa_header->type = htons(OSPF6_LSTYPE_NETWORK); lsa_header->id = htonl(oi->interface->ifindex); lsa_header->adv_router = oi->area->ospf6->router_id; lsa_header->seqnum = ospf6_new_ls_seqnum(lsa_header->type, lsa_header->id, lsa_header->adv_router, oi->area->lsdb); lsa_header->length = htons((caddr_t)lsdesc - (caddr_t)buffer); /* LSA checksum */ ospf6_lsa_checksum(lsa_header); /* create LSA */ lsa = ospf6_lsa_create(lsa_header); /* Originate */ ospf6_lsa_originate_area(lsa, oi->area); return 0; } /****************************/ /* RFC2740 3.4.3.6 Link-LSA */ /****************************/ static char *ospf6_link_lsa_get_prefix_str(struct ospf6_lsa *lsa, char *buf, int buflen, int pos) { char *start, *end, *current; struct ospf6_link_lsa *link_lsa; struct in6_addr in6; struct ospf6_prefix *prefix; int cnt = 0, prefixnum; if (lsa) { link_lsa = (struct ospf6_link_lsa *)((caddr_t)lsa->header + sizeof(struct ospf6_lsa_header)); if (pos == 0) { inet_ntop(AF_INET6, &link_lsa->linklocal_addr, buf, buflen); return (buf); } prefixnum = ntohl(link_lsa->prefix_num); if (pos > prefixnum) return (NULL); start = (char *)link_lsa + sizeof(struct ospf6_link_lsa); end = (char *)lsa->header + ntohs(lsa->header->length); current = start; do { prefix = (struct ospf6_prefix *)current; if (prefix->prefix_length == 0 || current + OSPF6_PREFIX_SIZE(prefix) > end) { return (NULL); } if (cnt < pos) { current = start + pos * OSPF6_PREFIX_SIZE(prefix); cnt++; } else { memset(&in6, 0, sizeof(in6)); memcpy(&in6, OSPF6_PREFIX_BODY(prefix), OSPF6_PREFIX_SPACE( prefix->prefix_length)); inet_ntop(AF_INET6, &in6, buf, buflen); return (buf); } } while (current <= end); } return (NULL); } static int ospf6_link_lsa_show(struct vty *vty, struct ospf6_lsa *lsa) { char *start, *end, *current; struct ospf6_link_lsa *link_lsa; int prefixnum; char buf[128], options[32]; struct ospf6_prefix *prefix; const char *p, *mc, *la, *nu; struct in6_addr in6; link_lsa = (struct ospf6_link_lsa *)((caddr_t)lsa->header + sizeof(struct ospf6_lsa_header)); ospf6_options_printbuf(link_lsa->options, options, sizeof(options)); inet_ntop(AF_INET6, &link_lsa->linklocal_addr, buf, sizeof(buf)); prefixnum = ntohl(link_lsa->prefix_num); vty_out(vty, " Priority: %d Options: %s\n", link_lsa->priority, options); vty_out(vty, " LinkLocal Address: %s\n", buf); vty_out(vty, " Number of Prefix: %d\n", prefixnum); start = (char *)link_lsa + sizeof(struct ospf6_link_lsa); end = (char *)lsa->header + ntohs(lsa->header->length); for (current = start; current < end; current += OSPF6_PREFIX_SIZE(prefix)) { prefix = (struct ospf6_prefix *)current; if (prefix->prefix_length == 0 || current + OSPF6_PREFIX_SIZE(prefix) > end) break; p = (CHECK_FLAG(prefix->prefix_options, OSPF6_PREFIX_OPTION_P) ? "P" : "--"); mc = (CHECK_FLAG(prefix->prefix_options, OSPF6_PREFIX_OPTION_MC) ? "MC" : "--"); la = (CHECK_FLAG(prefix->prefix_options, OSPF6_PREFIX_OPTION_LA) ? "LA" : "--"); nu = (CHECK_FLAG(prefix->prefix_options, OSPF6_PREFIX_OPTION_NU) ? "NU" : "--"); vty_out(vty, " Prefix Options: %s|%s|%s|%s\n", p, mc, la, nu); memset(&in6, 0, sizeof(in6)); memcpy(&in6, OSPF6_PREFIX_BODY(prefix), OSPF6_PREFIX_SPACE(prefix->prefix_length)); inet_ntop(AF_INET6, &in6, buf, sizeof(buf)); vty_out(vty, " Prefix: %s/%d\n", buf, prefix->prefix_length); } return 0; } int ospf6_link_lsa_originate(struct thread *thread) { struct ospf6_interface *oi; char buffer[OSPF6_MAX_LSASIZE]; struct ospf6_lsa_header *lsa_header; struct ospf6_lsa *old, *lsa; struct ospf6_link_lsa *link_lsa; struct ospf6_route *route; struct ospf6_prefix *op; oi = (struct ospf6_interface *)THREAD_ARG(thread); oi->thread_link_lsa = NULL; assert(oi->area); /* find previous LSA */ old = ospf6_lsdb_lookup(htons(OSPF6_LSTYPE_LINK), htonl(oi->interface->ifindex), oi->area->ospf6->router_id, oi->lsdb); if (CHECK_FLAG(oi->flag, OSPF6_INTERFACE_DISABLE)) { if (old) ospf6_lsa_purge(old); return 0; } if (IS_OSPF6_DEBUG_ORIGINATE(LINK)) zlog_debug("Originate Link-LSA for Interface %s", oi->interface->name); /* can't make Link-LSA if linklocal address not set */ if (oi->linklocal_addr == NULL) { if (IS_OSPF6_DEBUG_ORIGINATE(LINK)) zlog_debug( "No Linklocal address on %s, defer originating", oi->interface->name); if (old) ospf6_lsa_purge(old); return 0; } /* prepare buffer */ memset(buffer, 0, sizeof(buffer)); lsa_header = (struct ospf6_lsa_header *)buffer; link_lsa = (struct ospf6_link_lsa *)((caddr_t)lsa_header + sizeof(struct ospf6_lsa_header)); /* Fill Link-LSA */ link_lsa->priority = oi->priority; memcpy(link_lsa->options, oi->area->options, 3); memcpy(&link_lsa->linklocal_addr, oi->linklocal_addr, sizeof(struct in6_addr)); link_lsa->prefix_num = htonl(oi->route_connected->count); op = (struct ospf6_prefix *)((caddr_t)link_lsa + sizeof(struct ospf6_link_lsa)); /* connected prefix to advertise */ for (route = ospf6_route_head(oi->route_connected); route; route = ospf6_route_next(route)) { op->prefix_length = route->prefix.prefixlen; op->prefix_options = route->path.prefix_options; op->prefix_metric = htons(0); memcpy(OSPF6_PREFIX_BODY(op), &route->prefix.u.prefix6, OSPF6_PREFIX_SPACE(op->prefix_length)); op = OSPF6_PREFIX_NEXT(op); } /* Fill LSA Header */ lsa_header->age = 0; lsa_header->type = htons(OSPF6_LSTYPE_LINK); lsa_header->id = htonl(oi->interface->ifindex); lsa_header->adv_router = oi->area->ospf6->router_id; lsa_header->seqnum = ospf6_new_ls_seqnum(lsa_header->type, lsa_header->id, lsa_header->adv_router, oi->lsdb); lsa_header->length = htons((caddr_t)op - (caddr_t)buffer); /* LSA checksum */ ospf6_lsa_checksum(lsa_header); /* create LSA */ lsa = ospf6_lsa_create(lsa_header); /* Originate */ ospf6_lsa_originate_interface(lsa, oi); return 0; } /*****************************************/ /* RFC2740 3.4.3.7 Intra-Area-Prefix-LSA */ /*****************************************/ static char *ospf6_intra_prefix_lsa_get_prefix_str(struct ospf6_lsa *lsa, char *buf, int buflen, int pos) { char *start, *end, *current; struct ospf6_intra_prefix_lsa *intra_prefix_lsa; struct in6_addr in6; int prefixnum, cnt = 0; struct ospf6_prefix *prefix; if (lsa) { intra_prefix_lsa = (struct ospf6_intra_prefix_lsa *)((caddr_t)lsa->header + sizeof(struct ospf6_lsa_header)); prefixnum = ntohs(intra_prefix_lsa->prefix_num); if (pos > prefixnum) return (NULL); start = (char *)intra_prefix_lsa + sizeof(struct ospf6_intra_prefix_lsa); end = (char *)lsa->header + ntohs(lsa->header->length); current = start; do { prefix = (struct ospf6_prefix *)current; if (prefix->prefix_length == 0 || current + OSPF6_PREFIX_SIZE(prefix) > end) { return NULL; } if (cnt < pos) { current = start + pos * OSPF6_PREFIX_SIZE(prefix); cnt++; } else { memset(&in6, 0, sizeof(in6)); memcpy(&in6, OSPF6_PREFIX_BODY(prefix), OSPF6_PREFIX_SPACE( prefix->prefix_length)); inet_ntop(AF_INET6, &in6, buf, buflen); sprintf(&buf[strlen(buf)], "/%d", prefix->prefix_length); return (buf); } } while (current <= end); } return (buf); } static int ospf6_intra_prefix_lsa_show(struct vty *vty, struct ospf6_lsa *lsa) { char *start, *end, *current; struct ospf6_intra_prefix_lsa *intra_prefix_lsa; int prefixnum; char buf[128]; struct ospf6_prefix *prefix; char id[16], adv_router[16]; const char *p, *mc, *la, *nu; struct in6_addr in6; intra_prefix_lsa = (struct ospf6_intra_prefix_lsa *)((caddr_t)lsa->header + sizeof(struct ospf6_lsa_header)); prefixnum = ntohs(intra_prefix_lsa->prefix_num); vty_out(vty, " Number of Prefix: %d\n", prefixnum); inet_ntop(AF_INET, &intra_prefix_lsa->ref_id, id, sizeof(id)); inet_ntop(AF_INET, &intra_prefix_lsa->ref_adv_router, adv_router, sizeof(adv_router)); vty_out(vty, " Reference: %s Id: %s Adv: %s\n", ospf6_lstype_name(intra_prefix_lsa->ref_type), id, adv_router); start = (char *)intra_prefix_lsa + sizeof(struct ospf6_intra_prefix_lsa); end = (char *)lsa->header + ntohs(lsa->header->length); for (current = start; current < end; current += OSPF6_PREFIX_SIZE(prefix)) { prefix = (struct ospf6_prefix *)current; if (prefix->prefix_length == 0 || current + OSPF6_PREFIX_SIZE(prefix) > end) break; p = (CHECK_FLAG(prefix->prefix_options, OSPF6_PREFIX_OPTION_P) ? "P" : "--"); mc = (CHECK_FLAG(prefix->prefix_options, OSPF6_PREFIX_OPTION_MC) ? "MC" : "--"); la = (CHECK_FLAG(prefix->prefix_options, OSPF6_PREFIX_OPTION_LA) ? "LA" : "--"); nu = (CHECK_FLAG(prefix->prefix_options, OSPF6_PREFIX_OPTION_NU) ? "NU" : "--"); vty_out(vty, " Prefix Options: %s|%s|%s|%s\n", p, mc, la, nu); memset(&in6, 0, sizeof(in6)); memcpy(&in6, OSPF6_PREFIX_BODY(prefix), OSPF6_PREFIX_SPACE(prefix->prefix_length)); inet_ntop(AF_INET6, &in6, buf, sizeof(buf)); vty_out(vty, " Prefix: %s/%d\n", buf, prefix->prefix_length); } return 0; } int ospf6_intra_prefix_lsa_originate_stub(struct thread *thread) { struct ospf6_area *oa; char buffer[OSPF6_MAX_LSASIZE]; struct ospf6_lsa_header *lsa_header; struct ospf6_lsa *old, *lsa, *old_next = NULL; struct ospf6_intra_prefix_lsa *intra_prefix_lsa; struct ospf6_interface *oi; struct ospf6_neighbor *on; struct ospf6_route *route; struct ospf6_prefix *op; struct listnode *i, *j; int full_count = 0; unsigned short prefix_num = 0; char buf[PREFIX2STR_BUFFER]; struct ospf6_route_table *route_advertise; int ls_id = 0; oa = (struct ospf6_area *)THREAD_ARG(thread); oa->thread_intra_prefix_lsa = NULL; /* find previous LSA */ old = ospf6_lsdb_lookup(htons(OSPF6_LSTYPE_INTRA_PREFIX), htonl(0), oa->ospf6->router_id, oa->lsdb); if (!IS_AREA_ENABLED(oa)) { if (old) { ospf6_lsa_purge(old); /* find previous LSA */ old_next = ospf6_lsdb_lookup( htons(OSPF6_LSTYPE_INTRA_PREFIX), htonl(++ls_id), oa->ospf6->router_id, oa->lsdb); while (old_next) { ospf6_lsa_purge(old_next); old_next = ospf6_lsdb_lookup( htons(OSPF6_LSTYPE_INTRA_PREFIX), htonl(++ls_id), oa->ospf6->router_id, oa->lsdb); } } return 0; } if (IS_OSPF6_DEBUG_ORIGINATE(INTRA_PREFIX)) zlog_debug( "Originate Intra-Area-Prefix-LSA for area %s's stub prefix", oa->name); /* prepare buffer */ memset(buffer, 0, sizeof(buffer)); lsa_header = (struct ospf6_lsa_header *)buffer; intra_prefix_lsa = (struct ospf6_intra_prefix_lsa *)((caddr_t)lsa_header + sizeof(struct ospf6_lsa_header)); /* Fill Intra-Area-Prefix-LSA */ intra_prefix_lsa->ref_type = htons(OSPF6_LSTYPE_ROUTER); intra_prefix_lsa->ref_id = htonl(0); intra_prefix_lsa->ref_adv_router = oa->ospf6->router_id; route_advertise = ospf6_route_table_create(0, 0); for (ALL_LIST_ELEMENTS_RO(oa->if_list, i, oi)) { if (oi->state == OSPF6_INTERFACE_DOWN) { if (IS_OSPF6_DEBUG_ORIGINATE(INTRA_PREFIX)) zlog_debug(" Interface %s is down, ignore", oi->interface->name); continue; } full_count = 0; for (ALL_LIST_ELEMENTS_RO(oi->neighbor_list, j, on)) if (on->state == OSPF6_NEIGHBOR_FULL) full_count++; if (oi->state != OSPF6_INTERFACE_LOOPBACK && oi->state != OSPF6_INTERFACE_POINTTOPOINT && full_count != 0) { if (IS_OSPF6_DEBUG_ORIGINATE(INTRA_PREFIX)) zlog_debug(" Interface %s is not stub, ignore", oi->interface->name); continue; } if (IS_OSPF6_DEBUG_ORIGINATE(INTRA_PREFIX)) zlog_debug(" Interface %s:", oi->interface->name); /* connected prefix to advertise */ for (route = ospf6_route_head(oi->route_connected); route; route = ospf6_route_best_next(route)) { if (IS_OSPF6_DEBUG_ORIGINATE(INTRA_PREFIX)) { prefix2str(&route->prefix, buf, sizeof(buf)); zlog_debug(" include %s", buf); } ospf6_route_add(ospf6_route_copy(route), route_advertise); } } if (route_advertise->count == 0) { if (old) { ls_id = 0; ospf6_lsa_purge(old); /* find previous LSA */ old_next = ospf6_lsdb_lookup( htons(OSPF6_LSTYPE_INTRA_PREFIX), htonl(++ls_id), oa->ospf6->router_id, oa->lsdb); while (old_next) { ospf6_lsa_purge(old_next); old_next = ospf6_lsdb_lookup( htons(OSPF6_LSTYPE_INTRA_PREFIX), htonl(++ls_id), oa->ospf6->router_id, oa->lsdb); } } ospf6_route_table_delete(route_advertise); return 0; } /* Neighbor change to FULL, if INTRA-AREA-PREFIX LSA * has not change, Flush old LSA and Re-Originate INP, * as ospf6_flood() checks if LSA is same as DB, * it won't be updated to neighbor's DB. */ if (oa->intra_prefix_originate) { if (IS_OSPF6_DEBUG_ORIGINATE(INTRA_PREFIX)) zlog_debug("%s: Re-originate intra prefix LSA, Current full nbrs %u", __PRETTY_FUNCTION__, oa->full_nbrs); if (old) ospf6_lsa_purge_multi_ls_id(oa, old); oa->intra_prefix_originate = 0; } /* put prefixes to advertise */ prefix_num = 0; op = (struct ospf6_prefix *)((caddr_t)intra_prefix_lsa + sizeof(struct ospf6_intra_prefix_lsa)); for (route = ospf6_route_head(route_advertise); route; route = ospf6_route_best_next(route)) { if (((caddr_t)op - (caddr_t)lsa_header) > MAX_LSA_PAYLOAD) { intra_prefix_lsa->prefix_num = htons(prefix_num); /* Fill LSA Header */ lsa_header->age = 0; lsa_header->type = htons(OSPF6_LSTYPE_INTRA_PREFIX); lsa_header->id = htonl(ls_id++); lsa_header->adv_router = oa->ospf6->router_id; lsa_header->seqnum = ospf6_new_ls_seqnum( lsa_header->type, lsa_header->id, lsa_header->adv_router, oa->lsdb); lsa_header->length = htons((caddr_t)op - (caddr_t)lsa_header); /* LSA checksum */ ospf6_lsa_checksum(lsa_header); /* Create LSA */ lsa = ospf6_lsa_create(lsa_header); /* Originate */ ospf6_lsa_originate_area(lsa, oa); /* Prepare next buffer */ memset(buffer, 0, sizeof(buffer)); lsa_header = (struct ospf6_lsa_header *)buffer; intra_prefix_lsa = (struct ospf6_intra_prefix_lsa *)((caddr_t)lsa_header + sizeof(struct ospf6_lsa_header)); /* Fill Intra-Area-Prefix-LSA */ intra_prefix_lsa->ref_type = htons(OSPF6_LSTYPE_ROUTER); intra_prefix_lsa->ref_id = htonl(0); intra_prefix_lsa->ref_adv_router = oa->ospf6->router_id; /* Put next set of prefixes to advertise */ prefix_num = 0; op = (struct ospf6_prefix *)((caddr_t)intra_prefix_lsa + sizeof(struct ospf6_intra_prefix_lsa)); } op->prefix_length = route->prefix.prefixlen; op->prefix_options = route->path.prefix_options; op->prefix_metric = htons(route->path.cost); memcpy(OSPF6_PREFIX_BODY(op), &route->prefix.u.prefix6, OSPF6_PREFIX_SPACE(op->prefix_length)); prefix_num++; op = OSPF6_PREFIX_NEXT(op); } ospf6_route_table_delete(route_advertise); if (prefix_num == 0) { if (IS_OSPF6_DEBUG_ORIGINATE(INTRA_PREFIX)) zlog_debug( "Quit to Advertise Intra-Prefix: no route to advertise"); return 0; } intra_prefix_lsa->prefix_num = htons(prefix_num); /* Fill LSA Header */ lsa_header->age = 0; lsa_header->type = htons(OSPF6_LSTYPE_INTRA_PREFIX); lsa_header->id = htonl(ls_id++); lsa_header->adv_router = oa->ospf6->router_id; lsa_header->seqnum = ospf6_new_ls_seqnum(lsa_header->type, lsa_header->id, lsa_header->adv_router, oa->lsdb); lsa_header->length = htons((caddr_t)op - (caddr_t)lsa_header); /* LSA checksum */ ospf6_lsa_checksum(lsa_header); /* create LSA */ lsa = ospf6_lsa_create(lsa_header); /* Originate */ ospf6_lsa_originate_area(lsa, oa); return 0; } int ospf6_intra_prefix_lsa_originate_transit(struct thread *thread) { struct ospf6_interface *oi; char buffer[OSPF6_MAX_LSASIZE]; struct ospf6_lsa_header *lsa_header; struct ospf6_lsa *old, *lsa; struct ospf6_intra_prefix_lsa *intra_prefix_lsa; struct ospf6_neighbor *on; struct ospf6_route *route; struct ospf6_prefix *op; struct listnode *i; int full_count = 0; unsigned short prefix_num = 0; struct ospf6_route_table *route_advertise; struct ospf6_link_lsa *link_lsa; char *start, *end, *current; uint16_t type; char buf[PREFIX2STR_BUFFER]; oi = (struct ospf6_interface *)THREAD_ARG(thread); oi->thread_intra_prefix_lsa = NULL; assert(oi->area); /* find previous LSA */ old = ospf6_lsdb_lookup(htons(OSPF6_LSTYPE_INTRA_PREFIX), htonl(oi->interface->ifindex), oi->area->ospf6->router_id, oi->area->lsdb); if (CHECK_FLAG(oi->flag, OSPF6_INTERFACE_DISABLE)) { if (old) ospf6_lsa_purge(old); return 0; } if (IS_OSPF6_DEBUG_ORIGINATE(INTRA_PREFIX)) zlog_debug( "Originate Intra-Area-Prefix-LSA for interface %s's prefix", oi->interface->name); /* prepare buffer */ memset(buffer, 0, sizeof(buffer)); lsa_header = (struct ospf6_lsa_header *)buffer; intra_prefix_lsa = (struct ospf6_intra_prefix_lsa *)((caddr_t)lsa_header + sizeof(struct ospf6_lsa_header)); /* Fill Intra-Area-Prefix-LSA */ intra_prefix_lsa->ref_type = htons(OSPF6_LSTYPE_NETWORK); intra_prefix_lsa->ref_id = htonl(oi->interface->ifindex); intra_prefix_lsa->ref_adv_router = oi->area->ospf6->router_id; if (oi->state != OSPF6_INTERFACE_DR) { if (IS_OSPF6_DEBUG_ORIGINATE(INTRA_PREFIX)) zlog_debug(" Interface is not DR"); if (old) ospf6_lsa_purge(old); return 0; } full_count = 0; for (ALL_LIST_ELEMENTS_RO(oi->neighbor_list, i, on)) if (on->state == OSPF6_NEIGHBOR_FULL) full_count++; if (full_count == 0) { if (IS_OSPF6_DEBUG_ORIGINATE(INTRA_PREFIX)) zlog_debug(" Interface is stub"); if (old) ospf6_lsa_purge(old); return 0; } /* connected prefix to advertise */ route_advertise = ospf6_route_table_create(0, 0); type = ntohs(OSPF6_LSTYPE_LINK); for (ALL_LSDB_TYPED(oi->lsdb, type, lsa)) { if (OSPF6_LSA_IS_MAXAGE(lsa)) continue; if (IS_OSPF6_DEBUG_ORIGINATE(INTRA_PREFIX)) zlog_debug(" include prefix from %s", lsa->name); if (lsa->header->adv_router != oi->area->ospf6->router_id) { on = ospf6_neighbor_lookup(lsa->header->adv_router, oi); if (on == NULL || on->state != OSPF6_NEIGHBOR_FULL) { if (IS_OSPF6_DEBUG_ORIGINATE(INTRA_PREFIX)) zlog_debug( " Neighbor not found or not Full, ignore"); continue; } } link_lsa = (struct ospf6_link_lsa *)((caddr_t)lsa->header + sizeof(struct ospf6_lsa_header)); prefix_num = (unsigned short)ntohl(link_lsa->prefix_num); start = (char *)link_lsa + sizeof(struct ospf6_link_lsa); end = (char *)lsa->header + ntohs(lsa->header->length); for (current = start; current < end && prefix_num; current += OSPF6_PREFIX_SIZE(op)) { op = (struct ospf6_prefix *)current; if (op->prefix_length == 0 || current + OSPF6_PREFIX_SIZE(op) > end) break; route = ospf6_route_create(); route->type = OSPF6_DEST_TYPE_NETWORK; route->prefix.family = AF_INET6; route->prefix.prefixlen = op->prefix_length; memset(&route->prefix.u.prefix6, 0, sizeof(struct in6_addr)); memcpy(&route->prefix.u.prefix6, OSPF6_PREFIX_BODY(op), OSPF6_PREFIX_SPACE(op->prefix_length)); route->path.origin.type = lsa->header->type; route->path.origin.id = lsa->header->id; route->path.origin.adv_router = lsa->header->adv_router; route->path.options[0] = link_lsa->options[0]; route->path.options[1] = link_lsa->options[1]; route->path.options[2] = link_lsa->options[2]; route->path.prefix_options = op->prefix_options; route->path.area_id = oi->area->area_id; route->path.type = OSPF6_PATH_TYPE_INTRA; if (IS_OSPF6_DEBUG_ORIGINATE(INTRA_PREFIX)) { prefix2str(&route->prefix, buf, sizeof(buf)); zlog_debug(" include %s", buf); } ospf6_route_add(route, route_advertise); prefix_num--; } if (current != end && IS_OSPF6_DEBUG_ORIGINATE(INTRA_PREFIX)) zlog_debug("Trailing garbage in %s", lsa->name); } op = (struct ospf6_prefix *)((caddr_t)intra_prefix_lsa + sizeof(struct ospf6_intra_prefix_lsa)); prefix_num = 0; for (route = ospf6_route_head(route_advertise); route; route = ospf6_route_best_next(route)) { op->prefix_length = route->prefix.prefixlen; op->prefix_options = route->path.prefix_options; op->prefix_metric = htons(0); memcpy(OSPF6_PREFIX_BODY(op), &route->prefix.u.prefix6, OSPF6_PREFIX_SPACE(op->prefix_length)); op = OSPF6_PREFIX_NEXT(op); prefix_num++; } ospf6_route_table_delete(route_advertise); if (prefix_num == 0) { if (IS_OSPF6_DEBUG_ORIGINATE(INTRA_PREFIX)) zlog_debug( "Quit to Advertise Intra-Prefix: no route to advertise"); return 0; } intra_prefix_lsa->prefix_num = htons(prefix_num); /* Fill LSA Header */ lsa_header->age = 0; lsa_header->type = htons(OSPF6_LSTYPE_INTRA_PREFIX); lsa_header->id = htonl(oi->interface->ifindex); lsa_header->adv_router = oi->area->ospf6->router_id; lsa_header->seqnum = ospf6_new_ls_seqnum(lsa_header->type, lsa_header->id, lsa_header->adv_router, oi->area->lsdb); lsa_header->length = htons((caddr_t)op - (caddr_t)lsa_header); /* LSA checksum */ ospf6_lsa_checksum(lsa_header); /* create LSA */ lsa = ospf6_lsa_create(lsa_header); /* Originate */ ospf6_lsa_originate_area(lsa, oi->area); return 0; } static void ospf6_intra_prefix_update_route_origin(struct ospf6_route *oa_route) { struct ospf6_path *h_path; struct ospf6_route *g_route, *nroute; /* Update Global ospf6 route path */ g_route = ospf6_route_lookup(&oa_route->prefix, ospf6->route_table); assert(g_route); for (ospf6_route_lock(g_route); g_route && ospf6_route_is_prefix(&oa_route->prefix, g_route); g_route = nroute) { nroute = ospf6_route_next(g_route); if (g_route->type != oa_route->type) continue; if (g_route->path.area_id != oa_route->path.area_id) continue; if (g_route->path.type != OSPF6_PATH_TYPE_INTRA) continue; if (g_route->path.cost != oa_route->path.cost) continue; if (ospf6_route_is_same_origin(g_route, oa_route)) { h_path = (struct ospf6_path *)listgetdata( listhead(g_route->paths)); g_route->path.origin.type = h_path->origin.type; g_route->path.origin.id = h_path->origin.id; g_route->path.origin.adv_router = h_path->origin.adv_router; break; } } h_path = (struct ospf6_path *)listgetdata( listhead(oa_route->paths)); oa_route->path.origin.type = h_path->origin.type; oa_route->path.origin.id = h_path->origin.id; oa_route->path.origin.adv_router = h_path->origin.adv_router; } void ospf6_intra_prefix_route_ecmp_path(struct ospf6_area *oa, struct ospf6_route *old, struct ospf6_route *route) { struct ospf6_route *old_route, *ls_entry; struct ospf6_path *ecmp_path, *o_path = NULL; struct listnode *anode, *anext; struct listnode *nnode, *rnode, *rnext; struct ospf6_nexthop *nh, *rnh; char buf[PREFIX2STR_BUFFER]; bool route_found = false; struct interface *ifp; struct ospf6_lsa *lsa; struct ospf6_intra_prefix_lsa *intra_prefix_lsa; /* check for old entry match with new route origin, * delete old entry. */ for (old_route = old; old_route; old_route = old_route->next) { bool route_updated = false; if (!ospf6_route_is_same(old_route, route) || (old_route->path.type != route->path.type)) continue; /* Current and New route has same origin, * delete old entry. */ for (ALL_LIST_ELEMENTS(old_route->paths, anode, anext, o_path)) { /* Check old route path and route has same * origin. */ if (o_path->area_id != route->path.area_id || (memcmp(&(o_path)->origin, &(route)->path.origin, sizeof(struct ospf6_ls_origin)) != 0)) continue; /* Cost is not same then delete current path */ if (o_path->cost == route->path.cost) continue; if (IS_OSPF6_DEBUG_EXAMIN(INTRA_PREFIX)) { prefix2str(&old_route->prefix, buf, sizeof(buf)); zlog_debug("%s: route %s cost old %u new %u is not same, replace route", __PRETTY_FUNCTION__, buf, o_path->cost, route->path.cost); } /* Remove selected current path's nh from * effective nh list. */ for (ALL_LIST_ELEMENTS_RO(o_path->nh_list, nnode, nh)) { for (ALL_LIST_ELEMENTS(old_route->nh_list, rnode, rnext, rnh)) { if (!ospf6_nexthop_is_same(rnh, nh)) continue; listnode_delete(old_route->nh_list, rnh); ospf6_nexthop_delete(rnh); route_updated = true; } } listnode_delete(old_route->paths, o_path); ospf6_path_free(o_path); /* Current route's path (adv_router info) is similar * to route being added. * Replace current route's path with paths list head. * Update FIB with effective NHs. */ if (listcount(old_route->paths)) { if (route_updated) { for (ALL_LIST_ELEMENTS(old_route->paths, anode, anext, o_path)) { ospf6_merge_nexthops( old_route->nh_list, o_path->nh_list); } /* Update ospf6 route table and * RIB/FIB with effective * nh_list */ if (oa->route_table->hook_add) (*oa->route_table->hook_add) (old_route); if (old_route->path.origin.id == route->path.origin.id && old_route->path.origin.adv_router == route->path.origin.adv_router) { ospf6_intra_prefix_update_route_origin( old_route); } break; } } else { if (IS_OSPF6_DEBUG_EXAMIN(INTRA_PREFIX)) { prefix2str(&old_route->prefix, buf, sizeof(buf)); zlog_debug("%s: route %s old cost %u new cost %u, delete old entry.", __PRETTY_FUNCTION__, buf, old_route->path.cost, route->path.cost); } if (oa->route_table->hook_remove) ospf6_route_remove(old_route, oa->route_table); else SET_FLAG(old_route->flag, OSPF6_ROUTE_REMOVE); break; } } if (route_updated) break; } for (old_route = old; old_route; old_route = old_route->next) { if (!ospf6_route_is_same(old_route, route) || (old_route->path.type != route->path.type)) continue; /* Old Route and New Route have Equal Cost, Merge NHs */ if (old_route->path.cost == route->path.cost) { route_found = true; /* check if this path exists already in * route->paths list, if so, replace nh_list. */ for (ALL_LIST_ELEMENTS_RO(old_route->paths, anode, o_path)) { if (o_path->area_id == route->path.area_id && (memcmp(&(o_path)->origin, &(route)->path.origin, sizeof(struct ospf6_ls_origin)) == 0)) break; } /* If path is not found in old_route paths's list, * add a new path to route paths list and merge * nexthops in route->path->nh_list. * Otherwise replace existing path's nh_list. */ if (o_path == NULL) { ecmp_path = ospf6_path_dup(&route->path); /* Add a nh_list to new ecmp path */ ospf6_copy_nexthops(ecmp_path->nh_list, route->nh_list); /* Add the new path to route's path list */ listnode_add_sort(old_route->paths, ecmp_path); if (IS_OSPF6_DEBUG_EXAMIN(INTRA_PREFIX)) { prefix2str(&route->prefix, buf, sizeof(buf)); zlog_debug( "%s: route %s %p another path added with nh %u, effective paths %u nh %u", __PRETTY_FUNCTION__, buf, (void *)old_route, listcount(ecmp_path->nh_list), old_route->paths ? listcount(old_route->paths) : 0, listcount(old_route->nh_list)); } } else { list_delete_all_node(o_path->nh_list); ospf6_copy_nexthops(o_path->nh_list, route->nh_list); } list_delete_all_node(old_route->nh_list); for (ALL_LIST_ELEMENTS_RO(old_route->paths, anode, o_path)) { ls_entry = ospf6_route_lookup( &o_path->ls_prefix, oa->spf_table); if (ls_entry == NULL) { if (IS_OSPF6_DEBUG_EXAMIN(INTRA_PREFIX)) zlog_debug("%s: ls_prfix %s ls_entry not found.", __PRETTY_FUNCTION__, buf); continue; } lsa = ospf6_lsdb_lookup(o_path->origin.type, o_path->origin.id, o_path->origin.adv_router, oa->lsdb); if (lsa == NULL) { if (IS_OSPF6_DEBUG_EXAMIN( INTRA_PREFIX)) { struct prefix adv_prefix; ospf6_linkstate_prefix( o_path->origin.adv_router, o_path->origin.id, &adv_prefix); prefix2str(&adv_prefix, buf, sizeof(buf)); zlog_debug("%s: adv_router %s lsa not found", __PRETTY_FUNCTION__, buf); } continue; } intra_prefix_lsa = (struct ospf6_intra_prefix_lsa *) OSPF6_LSA_HEADER_END(lsa->header); if (intra_prefix_lsa->ref_adv_router == oa->ospf6->router_id) { ifp = if_lookup_prefix( &old_route->prefix, VRF_DEFAULT); if (ifp) ospf6_route_add_nexthop( old_route, ifp->ifindex, NULL); } else { ospf6_route_merge_nexthops(old_route, ls_entry); } } if (IS_OSPF6_DEBUG_EXAMIN(INTRA_PREFIX)) { prefix2str(&route->prefix, buf, sizeof(buf)); zlog_debug("%s: route %s %p with final effective paths %u nh%u", __PRETTY_FUNCTION__, buf, (void *)old_route, old_route->paths ? listcount(old_route->paths) : 0, listcount(old_route->nh_list)); } /* used in intra_route_calculation() to add to * global ospf6 route table. */ UNSET_FLAG(old_route->flag, OSPF6_ROUTE_REMOVE); SET_FLAG(old_route->flag, OSPF6_ROUTE_ADD); /* Update ospf6 route table and RIB/FIB */ if (oa->route_table->hook_add) (*oa->route_table->hook_add)(old_route); /* Delete the new route its info added to existing * route. */ ospf6_route_delete(route); break; } } if (!route_found) { /* Add new route to existing node in ospf6 route table. */ ospf6_route_add(route, oa->route_table); } } void ospf6_intra_prefix_lsa_add(struct ospf6_lsa *lsa) { struct ospf6_area *oa; struct ospf6_intra_prefix_lsa *intra_prefix_lsa; struct prefix ls_prefix; struct ospf6_route *route, *ls_entry, *old; int prefix_num; struct ospf6_prefix *op; char *start, *current, *end; char buf[PREFIX2STR_BUFFER]; struct interface *ifp; int direct_connect = 0; struct ospf6_path *path; if (OSPF6_LSA_IS_MAXAGE(lsa)) return; if (IS_OSPF6_DEBUG_EXAMIN(INTRA_PREFIX)) zlog_debug("%s: LSA %s found", __PRETTY_FUNCTION__, lsa->name); oa = OSPF6_AREA(lsa->lsdb->data); intra_prefix_lsa = (struct ospf6_intra_prefix_lsa *)OSPF6_LSA_HEADER_END( lsa->header); if (intra_prefix_lsa->ref_type == htons(OSPF6_LSTYPE_ROUTER)) ospf6_linkstate_prefix(intra_prefix_lsa->ref_adv_router, intra_prefix_lsa->ref_id, &ls_prefix); else if (intra_prefix_lsa->ref_type == htons(OSPF6_LSTYPE_NETWORK)) ospf6_linkstate_prefix(intra_prefix_lsa->ref_adv_router, intra_prefix_lsa->ref_id, &ls_prefix); else { if (IS_OSPF6_DEBUG_EXAMIN(INTRA_PREFIX)) zlog_debug("Unknown reference LS-type: %#hx", ntohs(intra_prefix_lsa->ref_type)); return; } ls_entry = ospf6_route_lookup(&ls_prefix, oa->spf_table); if (ls_entry == NULL) { if (IS_OSPF6_DEBUG_EXAMIN(INTRA_PREFIX)) { ospf6_linkstate_prefix2str(&ls_prefix, buf, sizeof(buf)); zlog_debug("LS entry does not exist: %s", buf); } return; } if (intra_prefix_lsa->ref_adv_router == oa->ospf6->router_id) { /* the intra-prefix are directly connected */ direct_connect = 1; } prefix_num = ntohs(intra_prefix_lsa->prefix_num); start = (caddr_t)intra_prefix_lsa + sizeof(struct ospf6_intra_prefix_lsa); end = OSPF6_LSA_END(lsa->header); for (current = start; current < end; current += OSPF6_PREFIX_SIZE(op)) { op = (struct ospf6_prefix *)current; if (prefix_num == 0) break; if (end < current + OSPF6_PREFIX_SIZE(op)) break; /* Appendix A.4.1.1 */ if (CHECK_FLAG(op->prefix_options, OSPF6_PREFIX_OPTION_NU)) { if (IS_OSPF6_DEBUG_EXAMIN(INTRA_PREFIX)) { ospf6_linkstate_prefix2str( (struct prefix *)OSPF6_PREFIX_BODY(op), buf, sizeof(buf)); zlog_debug( "%s: Skipping Prefix %s has NU option set", __func__, buf); } continue; } route = ospf6_route_create(); memset(&route->prefix, 0, sizeof(struct prefix)); route->prefix.family = AF_INET6; route->prefix.prefixlen = op->prefix_length; ospf6_prefix_in6_addr(&route->prefix.u.prefix6, intra_prefix_lsa, op); route->type = OSPF6_DEST_TYPE_NETWORK; route->path.origin.type = lsa->header->type; route->path.origin.id = lsa->header->id; route->path.origin.adv_router = lsa->header->adv_router; route->path.prefix_options = op->prefix_options; route->path.area_id = oa->area_id; route->path.type = OSPF6_PATH_TYPE_INTRA; route->path.metric_type = 1; route->path.cost = ls_entry->path.cost + ntohs(op->prefix_metric); memcpy(&route->path.ls_prefix, &ls_prefix, sizeof(struct prefix)); if (direct_connect) { ifp = if_lookup_prefix(&route->prefix, VRF_DEFAULT); if (ifp) ospf6_route_add_nexthop(route, ifp->ifindex, NULL); } else { ospf6_route_copy_nexthops(route, ls_entry); } path = ospf6_path_dup(&route->path); ospf6_copy_nexthops(path->nh_list, route->path.nh_list); listnode_add_sort(route->paths, path); old = ospf6_route_lookup(&route->prefix, oa->route_table); if (old) { if (IS_OSPF6_DEBUG_EXAMIN(INTRA_PREFIX)) { prefix2str(&route->prefix, buf, sizeof(buf)); zlog_debug("%s Update route: %s old cost %u new cost %u paths %u nh %u", __PRETTY_FUNCTION__, buf, old->path.cost, route->path.cost, listcount(route->paths), listcount(route->nh_list)); } ospf6_intra_prefix_route_ecmp_path(oa, old, route); } else { if (IS_OSPF6_DEBUG_EXAMIN(INTRA_PREFIX)) { prefix2str(&route->prefix, buf, sizeof(buf)); zlog_debug("%s route %s add with cost %u paths %u nh %u", __PRETTY_FUNCTION__, buf, route->path.cost, listcount(route->paths), listcount(route->nh_list)); } ospf6_route_add(route, oa->route_table); } prefix_num--; } if (current != end && IS_OSPF6_DEBUG_EXAMIN(INTRA_PREFIX)) zlog_debug("Trailing garbage ignored"); } static void ospf6_intra_prefix_lsa_remove_update_route(struct ospf6_lsa *lsa, struct ospf6_area *oa, struct ospf6_route *route) { struct listnode *anode, *anext; struct listnode *nnode, *rnode, *rnext; struct ospf6_nexthop *nh, *rnh; struct ospf6_path *o_path; bool nh_updated = false; char buf[PREFIX2STR_BUFFER]; /* Iterate all paths of route to find maching * with LSA remove info. * If route->path is same, replace * from paths list. */ for (ALL_LIST_ELEMENTS(route->paths, anode, anext, o_path)) { if ((o_path->origin.type != lsa->header->type) || (o_path->origin.adv_router != lsa->header->adv_router) || (o_path->origin.id != lsa->header->id)) continue; if (IS_OSPF6_DEBUG_EXAMIN(INTRA_PREFIX)) { prefix2str(&route->prefix, buf, sizeof(buf)); zlog_debug( "%s: route %s path found with cost %u nh %u to remove.", __PRETTY_FUNCTION__, buf, o_path->cost, listcount(o_path->nh_list)); } /* Remove found path's nh_list from * the route's nh_list. */ for (ALL_LIST_ELEMENTS_RO(o_path->nh_list, nnode, nh)) { for (ALL_LIST_ELEMENTS(route->nh_list, rnode, rnext, rnh)) { if (!ospf6_nexthop_is_same(rnh, nh)) continue; listnode_delete(route->nh_list, rnh); ospf6_nexthop_delete(rnh); } } /* Delete the path from route's * path list */ listnode_delete(route->paths, o_path); ospf6_path_free(o_path); nh_updated = true; break; } if (nh_updated) { /* Iterate all paths and merge nexthop, * unlesss any of the nexthop similar to * ones deleted as part of path deletion. */ for (ALL_LIST_ELEMENTS(route->paths, anode, anext, o_path)) ospf6_merge_nexthops(route->nh_list, o_path->nh_list); if (IS_OSPF6_DEBUG_EXAMIN(INTRA_PREFIX)) { prefix2str(&route->prefix, buf, sizeof(buf)); zlog_debug("%s: route %s update paths %u nh %u", __PRETTY_FUNCTION__, buf, route->paths ? listcount(route->paths) : 0, route->nh_list ? listcount(route->nh_list) : 0); } /* Update Global Route table and * RIB/FIB with effective * nh_list */ if (oa->route_table->hook_add) (*oa->route_table->hook_add)(route); /* route's primary path is similar * to LSA, replace route's primary * path with route's paths list * head. */ if ((route->path.origin.id == lsa->header->id) && (route->path.origin.adv_router == lsa->header->adv_router)) { ospf6_intra_prefix_update_route_origin(route); } } } void ospf6_intra_prefix_lsa_remove(struct ospf6_lsa *lsa) { struct ospf6_area *oa; struct ospf6_intra_prefix_lsa *intra_prefix_lsa; struct prefix prefix; struct ospf6_route *route, *nroute; int prefix_num; struct ospf6_prefix *op; char *start, *current, *end; char buf[PREFIX2STR_BUFFER]; if (IS_OSPF6_DEBUG_EXAMIN(INTRA_PREFIX)) zlog_debug("%s: %s disappearing", __PRETTY_FUNCTION__, lsa->name); oa = OSPF6_AREA(lsa->lsdb->data); intra_prefix_lsa = (struct ospf6_intra_prefix_lsa *)OSPF6_LSA_HEADER_END( lsa->header); prefix_num = ntohs(intra_prefix_lsa->prefix_num); start = (caddr_t)intra_prefix_lsa + sizeof(struct ospf6_intra_prefix_lsa); end = OSPF6_LSA_END(lsa->header); for (current = start; current < end; current += OSPF6_PREFIX_SIZE(op)) { op = (struct ospf6_prefix *)current; if (prefix_num == 0) break; if (end < current + OSPF6_PREFIX_SIZE(op)) break; prefix_num--; memset(&prefix, 0, sizeof(struct prefix)); prefix.family = AF_INET6; prefix.prefixlen = op->prefix_length; ospf6_prefix_in6_addr(&prefix.u.prefix6, intra_prefix_lsa, op); route = ospf6_route_lookup(&prefix, oa->route_table); if (route == NULL) continue; for (ospf6_route_lock(route); route && ospf6_route_is_prefix(&prefix, route); route = nroute) { nroute = ospf6_route_next(route); if (route->type != OSPF6_DEST_TYPE_NETWORK) continue; if (route->path.area_id != oa->area_id) continue; if (route->path.type != OSPF6_PATH_TYPE_INTRA) continue; /* Route has multiple ECMP paths, remove matching * path. Update current route's effective nh list * after removal of one of the path. */ if (listcount(route->paths) > 1) { ospf6_intra_prefix_lsa_remove_update_route( lsa, oa, route); } else { if (route->path.origin.type != lsa->header->type || route->path.origin.id != lsa->header->id || route->path.origin.adv_router != lsa->header->adv_router) continue; if (IS_OSPF6_DEBUG_EXAMIN(INTRA_PREFIX)) { prefix2str(&route->prefix, buf, sizeof(buf)); zlog_debug("%s: route remove %s with path type %u cost %u paths %u nh %u", __PRETTY_FUNCTION__, buf, route->path.type, route->path.cost, listcount(route->paths), listcount(route->nh_list)); } ospf6_route_remove(route, oa->route_table); } } if (route) ospf6_route_unlock(route); } if (current != end && IS_OSPF6_DEBUG_EXAMIN(INTRA_PREFIX)) zlog_debug("Trailing garbage ignored"); } void ospf6_intra_route_calculation(struct ospf6_area *oa) { struct ospf6_route *route, *nroute; uint16_t type; struct ospf6_lsa *lsa; void (*hook_add)(struct ospf6_route *) = NULL; void (*hook_remove)(struct ospf6_route *) = NULL; if (IS_OSPF6_DEBUG_EXAMIN(INTRA_PREFIX)) zlog_debug("Re-examin intra-routes for area %s", oa->name); hook_add = oa->route_table->hook_add; hook_remove = oa->route_table->hook_remove; oa->route_table->hook_add = NULL; oa->route_table->hook_remove = NULL; for (route = ospf6_route_head(oa->route_table); route; route = ospf6_route_next(route)) route->flag = OSPF6_ROUTE_REMOVE; type = htons(OSPF6_LSTYPE_INTRA_PREFIX); for (ALL_LSDB_TYPED(oa->lsdb, type, lsa)) ospf6_intra_prefix_lsa_add(lsa); oa->route_table->hook_add = hook_add; oa->route_table->hook_remove = hook_remove; for (route = ospf6_route_head(oa->route_table); route; route = nroute) { nroute = ospf6_route_next(route); if (CHECK_FLAG(route->flag, OSPF6_ROUTE_REMOVE) && CHECK_FLAG(route->flag, OSPF6_ROUTE_ADD)) { UNSET_FLAG(route->flag, OSPF6_ROUTE_REMOVE); UNSET_FLAG(route->flag, OSPF6_ROUTE_ADD); } if (CHECK_FLAG(route->flag, OSPF6_ROUTE_REMOVE)) ospf6_route_remove(route, oa->route_table); else if (CHECK_FLAG(route->flag, OSPF6_ROUTE_ADD) || CHECK_FLAG(route->flag, OSPF6_ROUTE_CHANGE)) { if (hook_add) (*hook_add)(route); route->flag = 0; } else { /* Redo the summaries as things might have changed */ ospf6_abr_originate_summary(route); route->flag = 0; } } if (IS_OSPF6_DEBUG_EXAMIN(INTRA_PREFIX)) zlog_debug("Re-examin intra-routes for area %s: Done", oa->name); } static void ospf6_brouter_debug_print(struct ospf6_route *brouter) { uint32_t brouter_id; char brouter_name[16]; char area_name[16]; char destination[64]; char installed[64], changed[64]; struct timeval now, res; char id[16], adv_router[16]; char capa[16], options[16]; brouter_id = ADV_ROUTER_IN_PREFIX(&brouter->prefix); inet_ntop(AF_INET, &brouter_id, brouter_name, sizeof(brouter_name)); inet_ntop(AF_INET, &brouter->path.area_id, area_name, sizeof(area_name)); ospf6_linkstate_prefix2str(&brouter->prefix, destination, sizeof(destination)); monotime(&now); timersub(&now, &brouter->installed, &res); timerstring(&res, installed, sizeof(installed)); monotime(&now); timersub(&now, &brouter->changed, &res); timerstring(&res, changed, sizeof(changed)); inet_ntop(AF_INET, &brouter->path.origin.id, id, sizeof(id)); inet_ntop(AF_INET, &brouter->path.origin.adv_router, adv_router, sizeof(adv_router)); ospf6_options_printbuf(brouter->path.options, options, sizeof(options)); ospf6_capability_printbuf(brouter->path.router_bits, capa, sizeof(capa)); zlog_info("Brouter: %s via area %s", brouter_name, area_name); zlog_info(" memory: prev: %p this: %p next: %p parent rnode: %p", (void *)brouter->prev, (void *)brouter, (void *)brouter->next, (void *)brouter->rnode); zlog_info(" type: %d prefix: %s installed: %s changed: %s", brouter->type, destination, installed, changed); zlog_info(" lock: %d flags: %s%s%s%s", brouter->lock, (CHECK_FLAG(brouter->flag, OSPF6_ROUTE_BEST) ? "B" : "-"), (CHECK_FLAG(brouter->flag, OSPF6_ROUTE_ADD) ? "A" : "-"), (CHECK_FLAG(brouter->flag, OSPF6_ROUTE_REMOVE) ? "R" : "-"), (CHECK_FLAG(brouter->flag, OSPF6_ROUTE_CHANGE) ? "C" : "-")); zlog_info(" path type: %s ls-origin %s id: %s adv-router %s", OSPF6_PATH_TYPE_NAME(brouter->path.type), ospf6_lstype_name(brouter->path.origin.type), id, adv_router); zlog_info(" options: %s router-bits: %s metric-type: %d metric: %d/%d", options, capa, brouter->path.metric_type, brouter->path.cost, brouter->path.u.cost_e2); zlog_info(" paths %u nh %u", listcount(brouter->paths), listcount(brouter->nh_list)); } void ospf6_intra_brouter_calculation(struct ospf6_area *oa) { struct ospf6_route *brouter, *nbrouter, *copy; void (*hook_add)(struct ospf6_route *) = NULL; void (*hook_remove)(struct ospf6_route *) = NULL; uint32_t brouter_id; char brouter_name[16]; if (IS_OSPF6_DEBUG_BROUTER_SPECIFIC_AREA_ID(oa->area_id) || IS_OSPF6_DEBUG_ROUTE(MEMORY)) zlog_info("%s: border-router calculation for area %s", __PRETTY_FUNCTION__, oa->name); hook_add = oa->ospf6->brouter_table->hook_add; hook_remove = oa->ospf6->brouter_table->hook_remove; oa->ospf6->brouter_table->hook_add = NULL; oa->ospf6->brouter_table->hook_remove = NULL; /* withdraw the previous router entries for the area */ for (brouter = ospf6_route_head(oa->ospf6->brouter_table); brouter; brouter = ospf6_route_next(brouter)) { brouter_id = ADV_ROUTER_IN_PREFIX(&brouter->prefix); inet_ntop(AF_INET, &brouter_id, brouter_name, sizeof(brouter_name)); if (brouter->path.area_id != oa->area_id) continue; SET_FLAG(brouter->flag, OSPF6_ROUTE_REMOVE); if (IS_OSPF6_DEBUG_BROUTER_SPECIFIC_ROUTER_ID(brouter_id) || IS_OSPF6_DEBUG_ROUTE(MEMORY)) { zlog_info("%p: mark as removing: area %s brouter %s", (void *)brouter, oa->name, brouter_name); ospf6_brouter_debug_print(brouter); } } for (brouter = ospf6_route_head(oa->spf_table); brouter; brouter = ospf6_route_next(brouter)) { brouter_id = ADV_ROUTER_IN_PREFIX(&brouter->prefix); inet_ntop(AF_INET, &brouter_id, brouter_name, sizeof(brouter_name)); if (brouter->type != OSPF6_DEST_TYPE_LINKSTATE) continue; if (ospf6_linkstate_prefix_id(&brouter->prefix) != htonl(0)) continue; if (!CHECK_FLAG(brouter->path.router_bits, OSPF6_ROUTER_BIT_E) && !CHECK_FLAG(brouter->path.router_bits, OSPF6_ROUTER_BIT_B)) continue; if (!OSPF6_OPT_ISSET(brouter->path.options, OSPF6_OPT_V6) || !OSPF6_OPT_ISSET(brouter->path.options, OSPF6_OPT_R)) continue; copy = ospf6_route_copy(brouter); copy->type = OSPF6_DEST_TYPE_ROUTER; copy->path.area_id = oa->area_id; ospf6_route_add(copy, oa->ospf6->brouter_table); if (IS_OSPF6_DEBUG_BROUTER_SPECIFIC_ROUTER_ID(brouter_id) || IS_OSPF6_DEBUG_ROUTE(MEMORY)) { zlog_info("%p: transfer: area %s brouter %s", (void *)brouter, oa->name, brouter_name); ospf6_brouter_debug_print(brouter); } } oa->ospf6->brouter_table->hook_add = hook_add; oa->ospf6->brouter_table->hook_remove = hook_remove; for (brouter = ospf6_route_head(oa->ospf6->brouter_table); brouter; brouter = nbrouter) { /* * brouter may have been "deleted" in the last loop iteration. * If this is the case there is still 1 final refcount lock * taken by ospf6_route_next, that will be released by the same * call and result in deletion. To avoid heap UAF we must then * skip processing the deleted route. */ if (brouter->lock == 1) { if (IS_OSPF6_DEBUG_ROUTE(MEMORY)) ospf6_brouter_debug_print(brouter); nbrouter = ospf6_route_next(brouter); continue; } else { nbrouter = ospf6_route_next(brouter); } brouter_id = ADV_ROUTER_IN_PREFIX(&brouter->prefix); inet_ntop(AF_INET, &brouter_id, brouter_name, sizeof(brouter_name)); if (brouter->path.area_id != oa->area_id) continue; if (CHECK_FLAG(brouter->flag, OSPF6_ROUTE_WAS_REMOVED)) continue; /* After iterating spf_table for all routers including * intra brouter, clear mark for remove flag for * inter border router if its adv router present in * SPF table. */ if (brouter->path.type == OSPF6_PATH_TYPE_INTER) { struct prefix adv_prefix; ospf6_linkstate_prefix(brouter->path.origin.adv_router, htonl(0), &adv_prefix); if (ospf6_route_lookup(&adv_prefix, oa->spf_table)) { if (IS_OSPF6_DEBUG_BROUTER) { zlog_debug("%s: keep inter brouter %s as adv router 0x%x found in spf", __PRETTY_FUNCTION__, brouter_name, brouter->path.origin.adv_router); ospf6_brouter_debug_print(brouter); } UNSET_FLAG(brouter->flag, OSPF6_ROUTE_REMOVE); } } if (CHECK_FLAG(brouter->flag, OSPF6_ROUTE_REMOVE) && CHECK_FLAG(brouter->flag, OSPF6_ROUTE_ADD)) { UNSET_FLAG(brouter->flag, OSPF6_ROUTE_REMOVE); UNSET_FLAG(brouter->flag, OSPF6_ROUTE_ADD); } if (CHECK_FLAG(brouter->flag, OSPF6_ROUTE_REMOVE)) { if (IS_OSPF6_DEBUG_BROUTER || IS_OSPF6_DEBUG_BROUTER_SPECIFIC_ROUTER_ID( brouter_id) || IS_OSPF6_DEBUG_BROUTER_SPECIFIC_AREA_ID( oa->area_id)) zlog_info("%s: brouter %s disappears via area %s", __PRETTY_FUNCTION__, brouter_name, oa->name); /* This is used to protect nbrouter from removed from * the table. For an example, ospf6_abr_examin_summary, * removes brouters which are marked for remove. */ oa->intra_brouter_calc = 1; ospf6_route_remove(brouter, oa->ospf6->brouter_table); brouter = NULL; } else if (CHECK_FLAG(brouter->flag, OSPF6_ROUTE_ADD) || CHECK_FLAG(brouter->flag, OSPF6_ROUTE_CHANGE)) { if (IS_OSPF6_DEBUG_BROUTER || IS_OSPF6_DEBUG_BROUTER_SPECIFIC_ROUTER_ID( brouter_id) || IS_OSPF6_DEBUG_BROUTER_SPECIFIC_AREA_ID( oa->area_id)) zlog_info("%s: brouter %s appears via area %s", __PRETTY_FUNCTION__, brouter_name, oa->name); /* newly added */ if (hook_add) (*hook_add)(brouter); } else { if (IS_OSPF6_DEBUG_BROUTER_SPECIFIC_ROUTER_ID( brouter_id) || IS_OSPF6_DEBUG_BROUTER_SPECIFIC_AREA_ID( oa->area_id)) zlog_info("brouter %s still exists via area %s", brouter_name, oa->name); /* But re-originate summaries */ ospf6_abr_originate_summary(brouter); } if (brouter) { UNSET_FLAG(brouter->flag, OSPF6_ROUTE_ADD); UNSET_FLAG(brouter->flag, OSPF6_ROUTE_CHANGE); } /* Reset for nbrouter */ oa->intra_brouter_calc = 0; } if (IS_OSPF6_DEBUG_BROUTER_SPECIFIC_AREA_ID(oa->area_id) || IS_OSPF6_DEBUG_ROUTE(MEMORY)) zlog_info("%s: border-router calculation for area %s: done", __PRETTY_FUNCTION__, oa->name); } struct ospf6_lsa_handler router_handler = {.lh_type = OSPF6_LSTYPE_ROUTER, .lh_name = "Router", .lh_short_name = "Rtr", .lh_show = ospf6_router_lsa_show, .lh_get_prefix_str = ospf6_router_lsa_get_nbr_id, .lh_debug = 0}; struct ospf6_lsa_handler network_handler = {.lh_type = OSPF6_LSTYPE_NETWORK, .lh_name = "Network", .lh_short_name = "Net", .lh_show = ospf6_network_lsa_show, .lh_get_prefix_str = ospf6_network_lsa_get_ar_id, .lh_debug = 0}; struct ospf6_lsa_handler link_handler = {.lh_type = OSPF6_LSTYPE_LINK, .lh_name = "Link", .lh_short_name = "Lnk", .lh_show = ospf6_link_lsa_show, .lh_get_prefix_str = ospf6_link_lsa_get_prefix_str, .lh_debug = 0}; struct ospf6_lsa_handler intra_prefix_handler = { .lh_type = OSPF6_LSTYPE_INTRA_PREFIX, .lh_name = "Intra-Prefix", .lh_short_name = "INP", .lh_show = ospf6_intra_prefix_lsa_show, .lh_get_prefix_str = ospf6_intra_prefix_lsa_get_prefix_str, .lh_debug = 0}; void ospf6_intra_init(void) { ospf6_install_lsa_handler(&router_handler); ospf6_install_lsa_handler(&network_handler); ospf6_install_lsa_handler(&link_handler); ospf6_install_lsa_handler(&intra_prefix_handler); } DEFUN (debug_ospf6_brouter, debug_ospf6_brouter_cmd, "debug ospf6 border-routers", DEBUG_STR OSPF6_STR "Debug border router\n" ) { OSPF6_DEBUG_BROUTER_ON(); return CMD_SUCCESS; } DEFUN (no_debug_ospf6_brouter, no_debug_ospf6_brouter_cmd, "no debug ospf6 border-routers", NO_STR DEBUG_STR OSPF6_STR "Debug border router\n" ) { OSPF6_DEBUG_BROUTER_OFF(); return CMD_SUCCESS; } DEFUN (debug_ospf6_brouter_router, debug_ospf6_brouter_router_cmd, "debug ospf6 border-routers router-id A.B.C.D", DEBUG_STR OSPF6_STR "Debug border router\n" "Debug specific border router\n" "Specify border-router's router-id\n" ) { int idx_ipv4 = 4; uint32_t router_id; inet_pton(AF_INET, argv[idx_ipv4]->arg, &router_id); OSPF6_DEBUG_BROUTER_SPECIFIC_ROUTER_ON(router_id); return CMD_SUCCESS; } DEFUN (no_debug_ospf6_brouter_router, no_debug_ospf6_brouter_router_cmd, "no debug ospf6 border-routers router-id", NO_STR DEBUG_STR OSPF6_STR "Debug border router\n" "Debug specific border router\n" ) { OSPF6_DEBUG_BROUTER_SPECIFIC_ROUTER_OFF(); return CMD_SUCCESS; } DEFUN (debug_ospf6_brouter_area, debug_ospf6_brouter_area_cmd, "debug ospf6 border-routers area-id A.B.C.D", DEBUG_STR OSPF6_STR "Debug border router\n" "Debug border routers in specific Area\n" "Specify Area-ID\n" ) { int idx_ipv4 = 4; uint32_t area_id; inet_pton(AF_INET, argv[idx_ipv4]->arg, &area_id); OSPF6_DEBUG_BROUTER_SPECIFIC_AREA_ON(area_id); return CMD_SUCCESS; } DEFUN (no_debug_ospf6_brouter_area, no_debug_ospf6_brouter_area_cmd, "no debug ospf6 border-routers area-id", NO_STR DEBUG_STR OSPF6_STR "Debug border router\n" "Debug border routers in specific Area\n" ) { OSPF6_DEBUG_BROUTER_SPECIFIC_AREA_OFF(); return CMD_SUCCESS; } int config_write_ospf6_debug_brouter(struct vty *vty) { char buf[16]; if (IS_OSPF6_DEBUG_BROUTER) vty_out(vty, "debug ospf6 border-routers\n"); if (IS_OSPF6_DEBUG_BROUTER_SPECIFIC_ROUTER) { inet_ntop(AF_INET, &conf_debug_ospf6_brouter_specific_router_id, buf, sizeof(buf)); vty_out(vty, "debug ospf6 border-routers router-id %s\n", buf); } if (IS_OSPF6_DEBUG_BROUTER_SPECIFIC_AREA) { inet_ntop(AF_INET, &conf_debug_ospf6_brouter_specific_area_id, buf, sizeof(buf)); vty_out(vty, "debug ospf6 border-routers area-id %s\n", buf); } return 0; } void install_element_ospf6_debug_brouter(void) { install_element(ENABLE_NODE, &debug_ospf6_brouter_cmd); install_element(ENABLE_NODE, &debug_ospf6_brouter_router_cmd); install_element(ENABLE_NODE, &debug_ospf6_brouter_area_cmd); install_element(ENABLE_NODE, &no_debug_ospf6_brouter_cmd); install_element(ENABLE_NODE, &no_debug_ospf6_brouter_router_cmd); install_element(ENABLE_NODE, &no_debug_ospf6_brouter_area_cmd); install_element(CONFIG_NODE, &debug_ospf6_brouter_cmd); install_element(CONFIG_NODE, &debug_ospf6_brouter_router_cmd); install_element(CONFIG_NODE, &debug_ospf6_brouter_area_cmd); install_element(CONFIG_NODE, &no_debug_ospf6_brouter_cmd); install_element(CONFIG_NODE, &no_debug_ospf6_brouter_router_cmd); install_element(CONFIG_NODE, &no_debug_ospf6_brouter_area_cmd); } frr-7.2.1/ospf6d/ospf6_intra.h0000644000000000000000000002520013610377563013017 00000000000000/* * Copyright (C) 2003 Yasuhiro Ohara * * This file is part of GNU Zebra. * * GNU Zebra is free software; you can redistribute it and/or modify it * under the terms of the GNU General Public License as published by the * Free Software Foundation; either version 2, or (at your option) any * later version. * * GNU Zebra 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; see the file COPYING; if not, write to the Free Software * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA */ #ifndef OSPF6_INTRA_H #define OSPF6_INTRA_H /* Debug option */ extern unsigned char conf_debug_ospf6_brouter; extern uint32_t conf_debug_ospf6_brouter_specific_router_id; extern uint32_t conf_debug_ospf6_brouter_specific_area_id; #define OSPF6_DEBUG_BROUTER_SUMMARY 0x01 #define OSPF6_DEBUG_BROUTER_SPECIFIC_ROUTER 0x02 #define OSPF6_DEBUG_BROUTER_SPECIFIC_AREA 0x04 #define OSPF6_DEBUG_BROUTER_ON() \ (conf_debug_ospf6_brouter |= OSPF6_DEBUG_BROUTER_SUMMARY) #define OSPF6_DEBUG_BROUTER_OFF() \ (conf_debug_ospf6_brouter &= ~OSPF6_DEBUG_BROUTER_SUMMARY) #define IS_OSPF6_DEBUG_BROUTER \ (conf_debug_ospf6_brouter & OSPF6_DEBUG_BROUTER_SUMMARY) #define OSPF6_DEBUG_BROUTER_SPECIFIC_ROUTER_ON(router_id) \ do { \ conf_debug_ospf6_brouter_specific_router_id = (router_id); \ conf_debug_ospf6_brouter |= \ OSPF6_DEBUG_BROUTER_SPECIFIC_ROUTER; \ } while (0) #define OSPF6_DEBUG_BROUTER_SPECIFIC_ROUTER_OFF() \ do { \ conf_debug_ospf6_brouter_specific_router_id = 0; \ conf_debug_ospf6_brouter &= \ ~OSPF6_DEBUG_BROUTER_SPECIFIC_ROUTER; \ } while (0) #define IS_OSPF6_DEBUG_BROUTER_SPECIFIC_ROUTER \ (conf_debug_ospf6_brouter & OSPF6_DEBUG_BROUTER_SPECIFIC_ROUTER) #define IS_OSPF6_DEBUG_BROUTER_SPECIFIC_ROUTER_ID(router_id) \ (IS_OSPF6_DEBUG_BROUTER_SPECIFIC_ROUTER \ && conf_debug_ospf6_brouter_specific_router_id == (router_id)) #define OSPF6_DEBUG_BROUTER_SPECIFIC_AREA_ON(area_id) \ do { \ conf_debug_ospf6_brouter_specific_area_id = (area_id); \ conf_debug_ospf6_brouter |= OSPF6_DEBUG_BROUTER_SPECIFIC_AREA; \ } while (0) #define OSPF6_DEBUG_BROUTER_SPECIFIC_AREA_OFF() \ do { \ conf_debug_ospf6_brouter_specific_area_id = 0; \ conf_debug_ospf6_brouter &= \ ~OSPF6_DEBUG_BROUTER_SPECIFIC_AREA; \ } while (0) #define IS_OSPF6_DEBUG_BROUTER_SPECIFIC_AREA \ (conf_debug_ospf6_brouter & OSPF6_DEBUG_BROUTER_SPECIFIC_AREA) #define IS_OSPF6_DEBUG_BROUTER_SPECIFIC_AREA_ID(area_id) \ (IS_OSPF6_DEBUG_BROUTER_SPECIFIC_AREA \ && conf_debug_ospf6_brouter_specific_area_id == (area_id)) /* Router-LSA */ #define OSPF6_ROUTER_LSA_MIN_SIZE 4U struct ospf6_router_lsa { uint8_t bits; uint8_t options[3]; /* followed by ospf6_router_lsdesc(s) */ }; /* Link State Description in Router-LSA */ #define OSPF6_ROUTER_LSDESC_FIX_SIZE 16U struct ospf6_router_lsdesc { uint8_t type; uint8_t reserved; uint16_t metric; /* output cost */ uint32_t interface_id; uint32_t neighbor_interface_id; uint32_t neighbor_router_id; }; #define OSPF6_ROUTER_LSDESC_POINTTOPOINT 1 #define OSPF6_ROUTER_LSDESC_TRANSIT_NETWORK 2 #define OSPF6_ROUTER_LSDESC_STUB_NETWORK 3 #define OSPF6_ROUTER_LSDESC_VIRTUAL_LINK 4 enum stub_router_mode { OSPF6_NOT_STUB_ROUTER, OSPF6_IS_STUB_ROUTER, OSPF6_IS_STUB_ROUTER_V6, }; #define ROUTER_LSDESC_IS_TYPE(t, x) \ ((((struct ospf6_router_lsdesc *)(x))->type \ == OSPF6_ROUTER_LSDESC_##t) \ ? 1 \ : 0) #define ROUTER_LSDESC_GET_METRIC(x) \ (ntohs(((struct ospf6_router_lsdesc *)(x))->metric)) #define ROUTER_LSDESC_GET_IFID(x) \ (ntohl(((struct ospf6_router_lsdesc *)(x))->interface_id)) #define ROUTER_LSDESC_GET_NBR_IFID(x) \ (ntohl(((struct ospf6_router_lsdesc *)(x))->neighbor_interface_id)) #define ROUTER_LSDESC_GET_NBR_ROUTERID(x) \ (((struct ospf6_router_lsdesc *)(x))->neighbor_router_id) /* Network-LSA */ #define OSPF6_NETWORK_LSA_MIN_SIZE 4U struct ospf6_network_lsa { uint8_t reserved; uint8_t options[3]; /* followed by ospf6_netowrk_lsd(s) */ }; /* Link State Description in Router-LSA */ #define OSPF6_NETWORK_LSDESC_FIX_SIZE 4U struct ospf6_network_lsdesc { uint32_t router_id; }; #define NETWORK_LSDESC_GET_NBR_ROUTERID(x) \ (((struct ospf6_network_lsdesc *)(x))->router_id) /* Link-LSA */ #define OSPF6_LINK_LSA_MIN_SIZE 24U /* w/o 1st IPv6 prefix */ struct ospf6_link_lsa { uint8_t priority; uint8_t options[3]; struct in6_addr linklocal_addr; uint32_t prefix_num; /* followed by ospf6 prefix(es) */ }; /* Intra-Area-Prefix-LSA */ #define OSPF6_INTRA_PREFIX_LSA_MIN_SIZE 12U /* w/o 1st IPv6 prefix */ struct ospf6_intra_prefix_lsa { uint16_t prefix_num; uint16_t ref_type; uint32_t ref_id; uint32_t ref_adv_router; /* followed by ospf6 prefix(es) */ }; #define OSPF6_ROUTER_LSA_SCHEDULE(oa) \ do { \ if (CHECK_FLAG((oa)->flag, OSPF6_AREA_ENABLE)) \ thread_add_event(master, ospf6_router_lsa_originate, \ oa, 0, &(oa)->thread_router_lsa); \ } while (0) #define OSPF6_NETWORK_LSA_SCHEDULE(oi) \ do { \ if (!CHECK_FLAG((oi)->flag, OSPF6_INTERFACE_DISABLE)) \ thread_add_event(master, ospf6_network_lsa_originate, \ oi, 0, &(oi)->thread_network_lsa); \ } while (0) #define OSPF6_LINK_LSA_SCHEDULE(oi) \ do { \ if (!CHECK_FLAG((oi)->flag, OSPF6_INTERFACE_DISABLE)) \ thread_add_event(master, ospf6_link_lsa_originate, oi, \ 0, &(oi)->thread_link_lsa); \ } while (0) #define OSPF6_INTRA_PREFIX_LSA_SCHEDULE_STUB(oa) \ do { \ if (CHECK_FLAG((oa)->flag, OSPF6_AREA_ENABLE)) \ thread_add_event( \ master, ospf6_intra_prefix_lsa_originate_stub, \ oa, 0, &(oa)->thread_intra_prefix_lsa); \ } while (0) #define OSPF6_INTRA_PREFIX_LSA_SCHEDULE_TRANSIT(oi) \ do { \ if (!CHECK_FLAG((oi)->flag, OSPF6_INTERFACE_DISABLE)) \ thread_add_event( \ master, \ ospf6_intra_prefix_lsa_originate_transit, oi, \ 0, &(oi)->thread_intra_prefix_lsa); \ } while (0) #define OSPF6_AS_EXTERN_LSA_SCHEDULE(oi) \ do { \ if (!CHECK_FLAG((oi)->flag, OSPF6_INTERFACE_DISABLE)) \ thread_add_event(master, ospf6_orig_as_external_lsa, \ oi, 0, &(oi)->thread_as_extern_lsa); \ } while (0) #define OSPF6_NETWORK_LSA_EXECUTE(oi) \ do { \ THREAD_OFF((oi)->thread_network_lsa); \ thread_execute(master, ospf6_network_lsa_originate, oi, 0); \ } while (0) #define OSPF6_INTRA_PREFIX_LSA_EXECUTE_TRANSIT(oi) \ do { \ THREAD_OFF((oi)->thread_intra_prefix_lsa); \ thread_execute(master, \ ospf6_intra_prefix_lsa_originate_transit, oi, \ 0); \ } while (0) #define OSPF6_AS_EXTERN_LSA_EXECUTE(oi) \ do { \ THREAD_OFF((oi)->thread_as_extern_lsa); \ thread_execute(master, ospf6_orig_as_external_lsa, oi, 0); \ } while (0) /* Function Prototypes */ extern char *ospf6_router_lsdesc_lookup(uint8_t type, uint32_t interface_id, uint32_t neighbor_interface_id, uint32_t neighbor_router_id, struct ospf6_lsa *lsa); extern char *ospf6_network_lsdesc_lookup(uint32_t router_id, struct ospf6_lsa *lsa); extern int ospf6_router_is_stub_router(struct ospf6_lsa *lsa); extern int ospf6_router_lsa_originate(struct thread *); extern int ospf6_network_lsa_originate(struct thread *); extern int ospf6_link_lsa_originate(struct thread *); extern int ospf6_intra_prefix_lsa_originate_transit(struct thread *); extern int ospf6_intra_prefix_lsa_originate_stub(struct thread *); extern void ospf6_intra_prefix_lsa_add(struct ospf6_lsa *lsa); extern void ospf6_intra_prefix_lsa_remove(struct ospf6_lsa *lsa); extern int ospf6_orig_as_external_lsa(struct thread *thread); extern void ospf6_intra_route_calculation(struct ospf6_area *oa); extern void ospf6_intra_brouter_calculation(struct ospf6_area *oa); extern void ospf6_intra_prefix_route_ecmp_path(struct ospf6_area *oa, struct ospf6_route *old, struct ospf6_route *route); extern void ospf6_intra_init(void); extern int config_write_ospf6_debug_brouter(struct vty *vty); extern void install_element_ospf6_debug_brouter(void); #endif /* OSPF6_LSA_H */ frr-7.2.1/ospf6d/ospf6_lsa.c0000644000000000000000000006272413610377563012470 00000000000000/* * Copyright (C) 2003 Yasuhiro Ohara * * This file is part of GNU Zebra. * * GNU Zebra is free software; you can redistribute it and/or modify it * under the terms of the GNU General Public License as published by the * Free Software Foundation; either version 2, or (at your option) any * later version. * * GNU Zebra 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; see the file COPYING; if not, write to the Free Software * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA */ #include /* Include other stuffs */ #include "log.h" #include "linklist.h" #include "vector.h" #include "vty.h" #include "command.h" #include "memory.h" #include "thread.h" #include "checksum.h" #include "ospf6_proto.h" #include "ospf6_lsa.h" #include "ospf6_lsdb.h" #include "ospf6_message.h" #include "ospf6_top.h" #include "ospf6_area.h" #include "ospf6_interface.h" #include "ospf6_neighbor.h" #include "ospf6_flood.h" #include "ospf6d.h" vector ospf6_lsa_handler_vector; static int ospf6_unknown_lsa_show(struct vty *vty, struct ospf6_lsa *lsa) { uint8_t *start, *end, *current; char byte[4]; start = (uint8_t *)lsa->header + sizeof(struct ospf6_lsa_header); end = (uint8_t *)lsa->header + ntohs(lsa->header->length); vty_out(vty, " Unknown contents:\n"); for (current = start; current < end; current++) { if ((current - start) % 16 == 0) vty_out(vty, "\n "); else if ((current - start) % 4 == 0) vty_out(vty, " "); snprintf(byte, sizeof(byte), "%02x", *current); vty_out(vty, "%s", byte); } vty_out(vty, "\n\n"); return 0; } static struct ospf6_lsa_handler unknown_handler = { .lh_type = OSPF6_LSTYPE_UNKNOWN, .lh_name = "Unknown", .lh_short_name = "Unk", .lh_show = ospf6_unknown_lsa_show, .lh_get_prefix_str = NULL, .lh_debug = 0 /* No default debug */ }; void ospf6_install_lsa_handler(const struct ospf6_lsa_handler *handler) { /* type in handler is host byte order */ int index = handler->lh_type & OSPF6_LSTYPE_FCODE_MASK; vector_set_index(ospf6_lsa_handler_vector, index, (void *)handler); } const struct ospf6_lsa_handler *ospf6_get_lsa_handler(uint16_t type) { const struct ospf6_lsa_handler *handler = NULL; unsigned int index = ntohs(type) & OSPF6_LSTYPE_FCODE_MASK; if (index >= vector_active(ospf6_lsa_handler_vector)) handler = &unknown_handler; else handler = vector_slot(ospf6_lsa_handler_vector, index); if (handler == NULL) handler = &unknown_handler; return handler; } const char *ospf6_lstype_name(uint16_t type) { static char buf[8]; const struct ospf6_lsa_handler *handler; handler = ospf6_get_lsa_handler(type); if (handler && handler != &unknown_handler) return handler->lh_name; snprintf(buf, sizeof(buf), "0x%04hx", ntohs(type)); return buf; } const char *ospf6_lstype_short_name(uint16_t type) { static char buf[8]; const struct ospf6_lsa_handler *handler; handler = ospf6_get_lsa_handler(type); if (handler && handler != &unknown_handler) return handler->lh_short_name; snprintf(buf, sizeof(buf), "0x%04hx", ntohs(type)); return buf; } uint8_t ospf6_lstype_debug(uint16_t type) { const struct ospf6_lsa_handler *handler; handler = ospf6_get_lsa_handler(type); return handler->lh_debug; } /* RFC2328: Section 13.2 */ int ospf6_lsa_is_differ(struct ospf6_lsa *lsa1, struct ospf6_lsa *lsa2) { int len; assert(OSPF6_LSA_IS_SAME(lsa1, lsa2)); /* XXX, Options ??? */ ospf6_lsa_age_current(lsa1); ospf6_lsa_age_current(lsa2); if (ntohs(lsa1->header->age) == OSPF_LSA_MAXAGE && ntohs(lsa2->header->age) != OSPF_LSA_MAXAGE) return 1; if (ntohs(lsa1->header->age) != OSPF_LSA_MAXAGE && ntohs(lsa2->header->age) == OSPF_LSA_MAXAGE) return 1; /* compare body */ if (ntohs(lsa1->header->length) != ntohs(lsa2->header->length)) return 1; len = ntohs(lsa1->header->length) - sizeof(struct ospf6_lsa_header); return memcmp(lsa1->header + 1, lsa2->header + 1, len); } int ospf6_lsa_is_changed(struct ospf6_lsa *lsa1, struct ospf6_lsa *lsa2) { int length; if (OSPF6_LSA_IS_MAXAGE(lsa1) ^ OSPF6_LSA_IS_MAXAGE(lsa2)) return 1; if (ntohs(lsa1->header->length) != ntohs(lsa2->header->length)) return 1; /* Going beyond LSA headers to compare the payload only makes sense, * when both LSAs aren't header-only. */ if (CHECK_FLAG(lsa1->flag, OSPF6_LSA_HEADERONLY) != CHECK_FLAG(lsa2->flag, OSPF6_LSA_HEADERONLY)) { zlog_warn( "%s: only one of two (%s, %s) LSAs compared is header-only", __func__, lsa1->name, lsa2->name); return 1; } if (CHECK_FLAG(lsa1->flag, OSPF6_LSA_HEADERONLY)) return 0; length = OSPF6_LSA_SIZE(lsa1->header) - sizeof(struct ospf6_lsa_header); /* Once upper layer verifies LSAs received, length underrun should * become a warning. */ if (length <= 0) return 0; return memcmp(OSPF6_LSA_HEADER_END(lsa1->header), OSPF6_LSA_HEADER_END(lsa2->header), length); } /* ospf6 age functions */ /* calculate birth */ void ospf6_lsa_age_set(struct ospf6_lsa *lsa) { struct timeval now; assert(lsa && lsa->header); monotime(&now); lsa->birth.tv_sec = now.tv_sec - ntohs(lsa->header->age); lsa->birth.tv_usec = now.tv_usec; return; } /* this function calculates current age from its birth, then update age field of LSA header. return value is current age */ uint16_t ospf6_lsa_age_current(struct ospf6_lsa *lsa) { struct timeval now; uint32_t ulage; uint16_t age; assert(lsa); assert(lsa->header); /* current time */ monotime(&now); if (ntohs(lsa->header->age) >= OSPF_LSA_MAXAGE) { /* ospf6_lsa_premature_aging () sets age to MAXAGE; when using relative time, we cannot compare against lsa birth time, so we catch this special case here. */ lsa->header->age = htons(OSPF_LSA_MAXAGE); return OSPF_LSA_MAXAGE; } /* calculate age */ ulage = now.tv_sec - lsa->birth.tv_sec; /* if over MAXAGE, set to it */ age = (ulage > OSPF_LSA_MAXAGE ? OSPF_LSA_MAXAGE : ulage); lsa->header->age = htons(age); return age; } /* update age field of LSA header with adding InfTransDelay */ void ospf6_lsa_age_update_to_send(struct ospf6_lsa *lsa, uint32_t transdelay) { unsigned short age; age = ospf6_lsa_age_current(lsa) + transdelay; if (age > OSPF_LSA_MAXAGE) age = OSPF_LSA_MAXAGE; lsa->header->age = htons(age); } void ospf6_lsa_premature_aging(struct ospf6_lsa *lsa) { /* log */ if (IS_OSPF6_DEBUG_LSA_TYPE(lsa->header->type)) zlog_debug("LSA: Premature aging: %s", lsa->name); THREAD_OFF(lsa->expire); THREAD_OFF(lsa->refresh); /* * We clear the LSA from the neighbor retx lists now because it * will not get deleted later. Essentially, changing the age to * MaxAge will prevent this LSA from being matched with its * existing entries in the retx list thereby causing those entries * to be silently replaced with its MaxAged version, but with ever * increasing retx count causing this LSA to remain forever and * for the MaxAge remover thread to be called forever too. * * The reason the previous entry silently disappears is that when * entry is added to a neighbor's retx list, it replaces the existing * entry. But since the ospf6_lsdb_add() routine is generic and not * aware * of the special semantics of retx count, the retx count is not * decremented when its replaced. Attempting to add the incr and decr * retx count routines as the hook_add and hook_remove for the retx * lists * have a problem because the hook_remove routine is called for MaxAge * entries (as will be the case in a traditional LSDB, unlike in this * case * where an LSDB is used as an efficient tree structure to store all * kinds * of data) that are added instead of calling the hook_add routine. */ ospf6_flood_clear(lsa); lsa->header->age = htons(OSPF_LSA_MAXAGE); thread_execute(master, ospf6_lsa_expire, lsa, 0); } /* check which is more recent. if a is more recent, return -1; if the same, return 0; otherwise(b is more recent), return 1 */ int ospf6_lsa_compare(struct ospf6_lsa *a, struct ospf6_lsa *b) { int32_t seqnuma, seqnumb; uint16_t cksuma, cksumb; uint16_t agea, ageb; assert(a && a->header); assert(b && b->header); assert(OSPF6_LSA_IS_SAME(a, b)); seqnuma = (int32_t)ntohl(a->header->seqnum); seqnumb = (int32_t)ntohl(b->header->seqnum); /* compare by sequence number */ if (seqnuma > seqnumb) return -1; if (seqnuma < seqnumb) return 1; /* Checksum */ cksuma = ntohs(a->header->checksum); cksumb = ntohs(b->header->checksum); if (cksuma > cksumb) return -1; if (cksuma < cksumb) return 0; /* Update Age */ agea = ospf6_lsa_age_current(a); ageb = ospf6_lsa_age_current(b); /* MaxAge check */ if (agea == OSPF_LSA_MAXAGE && ageb != OSPF_LSA_MAXAGE) return -1; else if (agea != OSPF_LSA_MAXAGE && ageb == OSPF_LSA_MAXAGE) return 1; /* Age check */ if (agea > ageb && agea - ageb >= OSPF_LSA_MAXAGE_DIFF) return 1; else if (agea < ageb && ageb - agea >= OSPF_LSA_MAXAGE_DIFF) return -1; /* neither recent */ return 0; } char *ospf6_lsa_printbuf(struct ospf6_lsa *lsa, char *buf, int size) { char id[16], adv_router[16]; inet_ntop(AF_INET, &lsa->header->id, id, sizeof(id)); inet_ntop(AF_INET, &lsa->header->adv_router, adv_router, sizeof(adv_router)); snprintf(buf, size, "[%s Id:%s Adv:%s]", ospf6_lstype_name(lsa->header->type), id, adv_router); return buf; } void ospf6_lsa_header_print_raw(struct ospf6_lsa_header *header) { char id[16], adv_router[16]; inet_ntop(AF_INET, &header->id, id, sizeof(id)); inet_ntop(AF_INET, &header->adv_router, adv_router, sizeof(adv_router)); zlog_debug(" [%s Id:%s Adv:%s]", ospf6_lstype_name(header->type), id, adv_router); zlog_debug(" Age: %4hu SeqNum: %#08lx Cksum: %04hx Len: %d", ntohs(header->age), (unsigned long)ntohl(header->seqnum), ntohs(header->checksum), ntohs(header->length)); } void ospf6_lsa_header_print(struct ospf6_lsa *lsa) { ospf6_lsa_age_current(lsa); ospf6_lsa_header_print_raw(lsa->header); } void ospf6_lsa_show_summary_header(struct vty *vty) { vty_out(vty, "%-4s %-15s%-15s%4s %8s %30s\n", "Type", "LSId", "AdvRouter", "Age", "SeqNum", "Payload"); } void ospf6_lsa_show_summary(struct vty *vty, struct ospf6_lsa *lsa) { char adv_router[16], id[16]; int type; const struct ospf6_lsa_handler *handler; char buf[64], tmpbuf[80]; int cnt = 0; assert(lsa); assert(lsa->header); inet_ntop(AF_INET, &lsa->header->id, id, sizeof(id)); inet_ntop(AF_INET, &lsa->header->adv_router, adv_router, sizeof(adv_router)); type = ntohs(lsa->header->type); handler = ospf6_get_lsa_handler(lsa->header->type); if ((type == OSPF6_LSTYPE_INTER_PREFIX) || (type == OSPF6_LSTYPE_INTER_ROUTER) || (type == OSPF6_LSTYPE_AS_EXTERNAL)) { vty_out(vty, "%-4s %-15s%-15s%4hu %8lx %30s\n", ospf6_lstype_short_name(lsa->header->type), id, adv_router, ospf6_lsa_age_current(lsa), (unsigned long)ntohl(lsa->header->seqnum), handler->lh_get_prefix_str(lsa, buf, sizeof(buf), 0)); } else if (type != OSPF6_LSTYPE_UNKNOWN) { sprintf(tmpbuf, "%-4s %-15s%-15s%4hu %8lx", ospf6_lstype_short_name(lsa->header->type), id, adv_router, ospf6_lsa_age_current(lsa), (unsigned long)ntohl(lsa->header->seqnum)); while (handler->lh_get_prefix_str(lsa, buf, sizeof(buf), cnt) != NULL) { vty_out(vty, "%s %30s\n", tmpbuf, buf); cnt++; } } else { vty_out(vty, "%-4s %-15s%-15s%4hu %8lx\n", ospf6_lstype_short_name(lsa->header->type), id, adv_router, ospf6_lsa_age_current(lsa), (unsigned long)ntohl(lsa->header->seqnum)); } } void ospf6_lsa_show_dump(struct vty *vty, struct ospf6_lsa *lsa) { uint8_t *start, *end, *current; char byte[4]; start = (uint8_t *)lsa->header; end = (uint8_t *)lsa->header + ntohs(lsa->header->length); vty_out(vty, "\n"); vty_out(vty, "%s:\n", lsa->name); for (current = start; current < end; current++) { if ((current - start) % 16 == 0) vty_out(vty, "\n "); else if ((current - start) % 4 == 0) vty_out(vty, " "); snprintf(byte, sizeof(byte), "%02x", *current); vty_out(vty, "%s", byte); } vty_out(vty, "\n\n"); return; } void ospf6_lsa_show_internal(struct vty *vty, struct ospf6_lsa *lsa) { char adv_router[64], id[64]; assert(lsa && lsa->header); inet_ntop(AF_INET, &lsa->header->id, id, sizeof(id)); inet_ntop(AF_INET, &lsa->header->adv_router, adv_router, sizeof(adv_router)); vty_out(vty, "\n"); vty_out(vty, "Age: %4hu Type: %s\n", ospf6_lsa_age_current(lsa), ospf6_lstype_name(lsa->header->type)); vty_out(vty, "Link State ID: %s\n", id); vty_out(vty, "Advertising Router: %s\n", adv_router); vty_out(vty, "LS Sequence Number: %#010lx\n", (unsigned long)ntohl(lsa->header->seqnum)); vty_out(vty, "CheckSum: %#06hx Length: %hu\n", ntohs(lsa->header->checksum), ntohs(lsa->header->length)); vty_out(vty, "Flag: %x \n", lsa->flag); vty_out(vty, "Lock: %d \n", lsa->lock); vty_out(vty, "ReTx Count: %d\n", lsa->retrans_count); vty_out(vty, "Threads: Expire: 0x%p, Refresh: 0x%p \n", (void *)lsa->expire, (void *)lsa->refresh); vty_out(vty, "\n"); return; } void ospf6_lsa_show(struct vty *vty, struct ospf6_lsa *lsa) { char adv_router[64], id[64]; const struct ospf6_lsa_handler *handler; struct timeval now, res; char duration[64]; assert(lsa && lsa->header); inet_ntop(AF_INET, &lsa->header->id, id, sizeof(id)); inet_ntop(AF_INET, &lsa->header->adv_router, adv_router, sizeof(adv_router)); monotime(&now); timersub(&now, &lsa->installed, &res); timerstring(&res, duration, sizeof(duration)); vty_out(vty, "Age: %4hu Type: %s\n", ospf6_lsa_age_current(lsa), ospf6_lstype_name(lsa->header->type)); vty_out(vty, "Link State ID: %s\n", id); vty_out(vty, "Advertising Router: %s\n", adv_router); vty_out(vty, "LS Sequence Number: %#010lx\n", (unsigned long)ntohl(lsa->header->seqnum)); vty_out(vty, "CheckSum: %#06hx Length: %hu\n", ntohs(lsa->header->checksum), ntohs(lsa->header->length)); vty_out(vty, "Duration: %s\n", duration); handler = ospf6_get_lsa_handler(lsa->header->type); if (handler->lh_show != NULL) handler->lh_show(vty, lsa); else { assert(unknown_handler.lh_show != NULL); unknown_handler.lh_show(vty, lsa); } vty_out(vty, "\n"); } /* OSPFv3 LSA creation/deletion function */ struct ospf6_lsa *ospf6_lsa_create(struct ospf6_lsa_header *header) { struct ospf6_lsa *lsa = NULL; struct ospf6_lsa_header *new_header = NULL; uint16_t lsa_size = 0; /* size of the entire LSA */ lsa_size = ntohs(header->length); /* XXX vulnerable */ /* allocate memory for this LSA */ new_header = XMALLOC(MTYPE_OSPF6_LSA_HEADER, lsa_size); /* copy LSA from original header */ memcpy(new_header, header, lsa_size); /* LSA information structure */ /* allocate memory */ lsa = XCALLOC(MTYPE_OSPF6_LSA, sizeof(struct ospf6_lsa)); lsa->header = (struct ospf6_lsa_header *)new_header; /* dump string */ ospf6_lsa_printbuf(lsa, lsa->name, sizeof(lsa->name)); /* calculate birth of this lsa */ ospf6_lsa_age_set(lsa); return lsa; } struct ospf6_lsa *ospf6_lsa_create_headeronly(struct ospf6_lsa_header *header) { struct ospf6_lsa *lsa = NULL; struct ospf6_lsa_header *new_header = NULL; /* allocate memory for this LSA */ new_header = XMALLOC(MTYPE_OSPF6_LSA_HEADER, sizeof(struct ospf6_lsa_header)); /* copy LSA from original header */ memcpy(new_header, header, sizeof(struct ospf6_lsa_header)); /* LSA information structure */ /* allocate memory */ lsa = XCALLOC(MTYPE_OSPF6_LSA, sizeof(struct ospf6_lsa)); lsa->header = (struct ospf6_lsa_header *)new_header; SET_FLAG(lsa->flag, OSPF6_LSA_HEADERONLY); /* dump string */ ospf6_lsa_printbuf(lsa, lsa->name, sizeof(lsa->name)); /* calculate birth of this lsa */ ospf6_lsa_age_set(lsa); return lsa; } void ospf6_lsa_delete(struct ospf6_lsa *lsa) { assert(lsa->lock == 0); /* cancel threads */ THREAD_OFF(lsa->expire); THREAD_OFF(lsa->refresh); /* do free */ XFREE(MTYPE_OSPF6_LSA_HEADER, lsa->header); XFREE(MTYPE_OSPF6_LSA, lsa); } struct ospf6_lsa *ospf6_lsa_copy(struct ospf6_lsa *lsa) { struct ospf6_lsa *copy = NULL; ospf6_lsa_age_current(lsa); if (CHECK_FLAG(lsa->flag, OSPF6_LSA_HEADERONLY)) copy = ospf6_lsa_create_headeronly(lsa->header); else copy = ospf6_lsa_create(lsa->header); assert(copy->lock == 0); copy->birth = lsa->birth; copy->originated = lsa->originated; copy->received = lsa->received; copy->installed = lsa->installed; copy->lsdb = lsa->lsdb; copy->rn = NULL; return copy; } /* increment reference counter of struct ospf6_lsa */ void ospf6_lsa_lock(struct ospf6_lsa *lsa) { lsa->lock++; return; } /* decrement reference counter of struct ospf6_lsa */ void ospf6_lsa_unlock(struct ospf6_lsa *lsa) { /* decrement reference counter */ assert(lsa->lock > 0); lsa->lock--; if (lsa->lock != 0) return; ospf6_lsa_delete(lsa); } /* ospf6 lsa expiry */ int ospf6_lsa_expire(struct thread *thread) { struct ospf6_lsa *lsa; lsa = (struct ospf6_lsa *)THREAD_ARG(thread); assert(lsa && lsa->header); assert(OSPF6_LSA_IS_MAXAGE(lsa)); assert(!lsa->refresh); lsa->expire = (struct thread *)NULL; if (IS_OSPF6_DEBUG_LSA_TYPE(lsa->header->type)) { zlog_debug("LSA Expire:"); ospf6_lsa_header_print(lsa); } if (CHECK_FLAG(lsa->flag, OSPF6_LSA_HEADERONLY)) return 0; /* dbexchange will do something ... */ /* reinstall lsa */ ospf6_install_lsa(lsa); /* reflood lsa */ ospf6_flood(NULL, lsa); /* schedule maxage remover */ ospf6_maxage_remove(ospf6); return 0; } int ospf6_lsa_refresh(struct thread *thread) { struct ospf6_lsa *old, *self, *new; struct ospf6_lsdb *lsdb_self; old = (struct ospf6_lsa *)THREAD_ARG(thread); assert(old && old->header); old->refresh = (struct thread *)NULL; lsdb_self = ospf6_get_scoped_lsdb_self(old); self = ospf6_lsdb_lookup(old->header->type, old->header->id, old->header->adv_router, lsdb_self); if (self == NULL) { if (IS_OSPF6_DEBUG_LSA_TYPE(old->header->type)) zlog_debug("Refresh: could not find self LSA, flush %s", old->name); ospf6_lsa_premature_aging(old); return 0; } /* Reset age, increment LS sequence number. */ self->header->age = htons(0); self->header->seqnum = ospf6_new_ls_seqnum(self->header->type, self->header->id, self->header->adv_router, old->lsdb); ospf6_lsa_checksum(self->header); new = ospf6_lsa_create(self->header); new->lsdb = old->lsdb; new->refresh = NULL; thread_add_timer(master, ospf6_lsa_refresh, new, OSPF_LS_REFRESH_TIME, &new->refresh); /* store it in the LSDB for self-originated LSAs */ ospf6_lsdb_add(ospf6_lsa_copy(new), lsdb_self); if (IS_OSPF6_DEBUG_LSA_TYPE(new->header->type)) { zlog_debug("LSA Refresh:"); ospf6_lsa_header_print(new); } ospf6_install_lsa(new); ospf6_flood(NULL, new); return 0; } void ospf6_flush_self_originated_lsas_now(void) { struct listnode *node; struct ospf6_area *oa; struct ospf6_lsa *lsa; const struct route_node *end = NULL; uint32_t type, adv_router; ospf6->inst_shutdown = 1; for (ALL_LIST_ELEMENTS_RO(ospf6->area_list, node, oa)) { end = ospf6_lsdb_head(oa->lsdb_self, 0, 0, ospf6->router_id, &lsa); while (lsa) { /* RFC 2328 (14.1): Set MAXAGE */ lsa->header->age = htons(OSPF_LSA_MAXAGE); /* Flood MAXAGE LSA*/ ospf6_flood(NULL, lsa); lsa = ospf6_lsdb_next(end, lsa); } } type = htons(OSPF6_LSTYPE_AS_EXTERNAL); adv_router = ospf6->router_id; for (ALL_LSDB_TYPED_ADVRTR(ospf6->lsdb, type, adv_router, lsa)) { /* RFC 2328 (14.1): Set MAXAGE */ lsa->header->age = htons(OSPF_LSA_MAXAGE); ospf6_flood(NULL, lsa); } } /* Fletcher Checksum -- Refer to RFC1008. */ /* All the offsets are zero-based. The offsets in the RFC1008 are one-based. */ unsigned short ospf6_lsa_checksum(struct ospf6_lsa_header *lsa_header) { uint8_t *buffer = (uint8_t *)&lsa_header->type; int type_offset = buffer - (uint8_t *)&lsa_header->age; /* should be 2 */ /* Skip the AGE field */ uint16_t len = ntohs(lsa_header->length) - type_offset; /* Checksum offset starts from "type" field, not the beginning of the lsa_header struct. The offset is 14, rather than 16. */ int checksum_offset = (uint8_t *)&lsa_header->checksum - buffer; return (unsigned short)fletcher_checksum(buffer, len, checksum_offset); } int ospf6_lsa_checksum_valid(struct ospf6_lsa_header *lsa_header) { uint8_t *buffer = (uint8_t *)&lsa_header->type; int type_offset = buffer - (uint8_t *)&lsa_header->age; /* should be 2 */ /* Skip the AGE field */ uint16_t len = ntohs(lsa_header->length) - type_offset; return (fletcher_checksum(buffer, len, FLETCHER_CHECKSUM_VALIDATE) == 0); } void ospf6_lsa_init(void) { ospf6_lsa_handler_vector = vector_init(0); ospf6_install_lsa_handler(&unknown_handler); } void ospf6_lsa_terminate(void) { vector_free(ospf6_lsa_handler_vector); } static char *ospf6_lsa_handler_name(const struct ospf6_lsa_handler *h) { static char buf[64]; unsigned int i; unsigned int size = strlen(h->lh_name); if (!strcmp(h->lh_name, "unknown") && h->lh_type != OSPF6_LSTYPE_UNKNOWN) { snprintf(buf, sizeof(buf), "%#04hx", h->lh_type); return buf; } for (i = 0; i < MIN(size, sizeof(buf)); i++) { if (!islower((unsigned char)h->lh_name[i])) buf[i] = tolower((unsigned char)h->lh_name[i]); else buf[i] = h->lh_name[i]; } buf[size] = '\0'; return buf; } DEFUN (debug_ospf6_lsa_type, debug_ospf6_lsa_hex_cmd, "debug ospf6 lsa []", DEBUG_STR OSPF6_STR "Debug Link State Advertisements (LSAs)\n" "Display Router LSAs\n" "Display Network LSAs\n" "Display Inter-Area-Prefix LSAs\n" "Display Inter-Router LSAs\n" "Display As-External LSAs\n" "Display Link LSAs\n" "Display Intra-Area-Prefix LSAs\n" "Display LSAs of unknown origin\n" "Display details of LSAs\n" "Dump LSAs\n" "Display LSA's internal information\n") { int idx_lsa = 3; int idx_type = 4; unsigned int i; struct ospf6_lsa_handler *handler = NULL; for (i = 0; i < vector_active(ospf6_lsa_handler_vector); i++) { handler = vector_slot(ospf6_lsa_handler_vector, i); if (handler == NULL) continue; if (strncmp(argv[idx_lsa]->arg, ospf6_lsa_handler_name(handler), strlen(argv[idx_lsa]->arg)) == 0) break; if (!strcasecmp(argv[idx_lsa]->arg, handler->lh_name)) break; handler = NULL; } if (handler == NULL) handler = &unknown_handler; if (argc == 5) { if (strmatch(argv[idx_type]->text, "originate")) SET_FLAG(handler->lh_debug, OSPF6_LSA_DEBUG_ORIGINATE); else if (strmatch(argv[idx_type]->text, "examine")) SET_FLAG(handler->lh_debug, OSPF6_LSA_DEBUG_EXAMIN); else if (strmatch(argv[idx_type]->text, "flooding")) SET_FLAG(handler->lh_debug, OSPF6_LSA_DEBUG_FLOOD); } else SET_FLAG(handler->lh_debug, OSPF6_LSA_DEBUG); return CMD_SUCCESS; } DEFUN (no_debug_ospf6_lsa_type, no_debug_ospf6_lsa_hex_cmd, "no debug ospf6 lsa []", NO_STR DEBUG_STR OSPF6_STR "Debug Link State Advertisements (LSAs)\n" "Display Router LSAs\n" "Display Network LSAs\n" "Display Inter-Area-Prefix LSAs\n" "Display Inter-Router LSAs\n" "Display As-External LSAs\n" "Display Link LSAs\n" "Display Intra-Area-Prefix LSAs\n" "Display LSAs of unknown origin\n" "Display details of LSAs\n" "Dump LSAs\n" "Display LSA's internal information\n") { int idx_lsa = 4; int idx_type = 5; unsigned int i; struct ospf6_lsa_handler *handler = NULL; for (i = 0; i < vector_active(ospf6_lsa_handler_vector); i++) { handler = vector_slot(ospf6_lsa_handler_vector, i); if (handler == NULL) continue; if (strncmp(argv[idx_lsa]->arg, ospf6_lsa_handler_name(handler), strlen(argv[idx_lsa]->arg)) == 0) break; if (!strcasecmp(argv[idx_lsa]->arg, handler->lh_name)) break; } if (handler == NULL) return CMD_SUCCESS; if (argc == 6) { if (strmatch(argv[idx_type]->text, "originate")) UNSET_FLAG(handler->lh_debug, OSPF6_LSA_DEBUG_ORIGINATE); if (strmatch(argv[idx_type]->text, "examine")) UNSET_FLAG(handler->lh_debug, OSPF6_LSA_DEBUG_EXAMIN); if (strmatch(argv[idx_type]->text, "flooding")) UNSET_FLAG(handler->lh_debug, OSPF6_LSA_DEBUG_FLOOD); } else UNSET_FLAG(handler->lh_debug, OSPF6_LSA_DEBUG); return CMD_SUCCESS; } void install_element_ospf6_debug_lsa(void) { install_element(ENABLE_NODE, &debug_ospf6_lsa_hex_cmd); install_element(ENABLE_NODE, &no_debug_ospf6_lsa_hex_cmd); install_element(CONFIG_NODE, &debug_ospf6_lsa_hex_cmd); install_element(CONFIG_NODE, &no_debug_ospf6_lsa_hex_cmd); } int config_write_ospf6_debug_lsa(struct vty *vty) { unsigned int i; const struct ospf6_lsa_handler *handler; for (i = 0; i < vector_active(ospf6_lsa_handler_vector); i++) { handler = vector_slot(ospf6_lsa_handler_vector, i); if (handler == NULL) continue; if (CHECK_FLAG(handler->lh_debug, OSPF6_LSA_DEBUG)) vty_out(vty, "debug ospf6 lsa %s\n", ospf6_lsa_handler_name(handler)); if (CHECK_FLAG(handler->lh_debug, OSPF6_LSA_DEBUG_ORIGINATE)) vty_out(vty, "debug ospf6 lsa %s originate\n", ospf6_lsa_handler_name(handler)); if (CHECK_FLAG(handler->lh_debug, OSPF6_LSA_DEBUG_EXAMIN)) vty_out(vty, "debug ospf6 lsa %s examine\n", ospf6_lsa_handler_name(handler)); if (CHECK_FLAG(handler->lh_debug, OSPF6_LSA_DEBUG_FLOOD)) vty_out(vty, "debug ospf6 lsa %s flooding\n", ospf6_lsa_handler_name(handler)); } return 0; } frr-7.2.1/ospf6d/ospf6_lsa.h0000644000000000000000000002507013610377563012466 00000000000000/* * Copyright (C) 2003 Yasuhiro Ohara * * This file is part of GNU Zebra. * * GNU Zebra is free software; you can redistribute it and/or modify it * under the terms of the GNU General Public License as published by the * Free Software Foundation; either version 2, or (at your option) any * later version. * * GNU Zebra 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; see the file COPYING; if not, write to the Free Software * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA */ #ifndef OSPF6_LSA_H #define OSPF6_LSA_H /* Debug option */ #define OSPF6_LSA_DEBUG 0x01 #define OSPF6_LSA_DEBUG_ORIGINATE 0x02 #define OSPF6_LSA_DEBUG_EXAMIN 0x04 #define OSPF6_LSA_DEBUG_FLOOD 0x08 #define IS_OSPF6_DEBUG_LSA(name) \ (ospf6_lstype_debug(htons(OSPF6_LSTYPE_##name)) & OSPF6_LSA_DEBUG) #define IS_OSPF6_DEBUG_ORIGINATE(name) \ (ospf6_lstype_debug(htons(OSPF6_LSTYPE_##name)) \ & OSPF6_LSA_DEBUG_ORIGINATE) #define IS_OSPF6_DEBUG_EXAMIN(name) \ (ospf6_lstype_debug(htons(OSPF6_LSTYPE_##name)) \ & OSPF6_LSA_DEBUG_EXAMIN) #define IS_OSPF6_DEBUG_LSA_TYPE(type) \ (ospf6_lstype_debug(type) & OSPF6_LSA_DEBUG) #define IS_OSPF6_DEBUG_ORIGINATE_TYPE(type) \ (ospf6_lstype_debug(type) & OSPF6_LSA_DEBUG_ORIGINATE) #define IS_OSPF6_DEBUG_EXAMIN_TYPE(type) \ (ospf6_lstype_debug(type) & OSPF6_LSA_DEBUG_EXAMIN) #define IS_OSPF6_DEBUG_FLOOD_TYPE(type) \ (ospf6_lstype_debug(type) & OSPF6_LSA_DEBUG_FLOOD) /* LSA definition */ #define OSPF6_MAX_LSASIZE 4096 /* Type */ #define OSPF6_LSTYPE_UNKNOWN 0x0000 #define OSPF6_LSTYPE_ROUTER 0x2001 #define OSPF6_LSTYPE_NETWORK 0x2002 #define OSPF6_LSTYPE_INTER_PREFIX 0x2003 #define OSPF6_LSTYPE_INTER_ROUTER 0x2004 #define OSPF6_LSTYPE_AS_EXTERNAL 0x4005 #define OSPF6_LSTYPE_GROUP_MEMBERSHIP 0x2006 #define OSPF6_LSTYPE_TYPE_7 0x2007 #define OSPF6_LSTYPE_LINK 0x0008 #define OSPF6_LSTYPE_INTRA_PREFIX 0x2009 #define OSPF6_LSTYPE_SIZE 0x000a /* Masks for LS Type : RFC 2740 A.4.2.1 "LS type" */ #define OSPF6_LSTYPE_UBIT_MASK 0x8000 #define OSPF6_LSTYPE_SCOPE_MASK 0x6000 #define OSPF6_LSTYPE_FCODE_MASK 0x1fff /* LSA scope */ #define OSPF6_SCOPE_LINKLOCAL 0x0000 #define OSPF6_SCOPE_AREA 0x2000 #define OSPF6_SCOPE_AS 0x4000 #define OSPF6_SCOPE_RESERVED 0x6000 /* XXX U-bit handling should be treated here */ #define OSPF6_LSA_SCOPE(type) (ntohs(type) & OSPF6_LSTYPE_SCOPE_MASK) /* LSA Header */ #define OSPF6_LSA_HEADER_SIZE 20U struct ospf6_lsa_header { uint16_t age; /* LS age */ uint16_t type; /* LS type */ uint32_t id; /* Link State ID */ uint32_t adv_router; /* Advertising Router */ uint32_t seqnum; /* LS sequence number */ uint16_t checksum; /* LS checksum */ uint16_t length; /* LSA length */ }; #define OSPF6_LSA_HEADER_END(h) ((caddr_t)(h) + sizeof(struct ospf6_lsa_header)) #define OSPF6_LSA_SIZE(h) (ntohs(((struct ospf6_lsa_header *)(h))->length)) #define OSPF6_LSA_END(h) \ ((caddr_t)(h) + ntohs(((struct ospf6_lsa_header *)(h))->length)) #define OSPF6_LSA_IS_TYPE(t, L) \ ((L)->header->type == htons(OSPF6_LSTYPE_##t) ? 1 : 0) #define OSPF6_LSA_IS_SAME(L1, L2) \ ((L1)->header->adv_router == (L2)->header->adv_router \ && (L1)->header->id == (L2)->header->id \ && (L1)->header->type == (L2)->header->type) #define OSPF6_LSA_IS_MATCH(t, i, a, L) \ ((L)->header->adv_router == (a) && (L)->header->id == (i) \ && (L)->header->type == (t)) #define OSPF6_LSA_IS_DIFFER(L1, L2) ospf6_lsa_is_differ (L1, L2) #define OSPF6_LSA_IS_MAXAGE(L) (ospf6_lsa_age_current (L) == OSPF_LSA_MAXAGE) #define OSPF6_LSA_IS_CHANGED(L1, L2) ospf6_lsa_is_changed (L1, L2) #define OSPF6_LSA_IS_SEQWRAP(L) ((L)->header->seqnum == htonl(OSPF_MAX_SEQUENCE_NUMBER + 1)) struct ospf6_lsa { char name[64]; /* dump string */ struct route_node *rn; unsigned char lock; /* reference counter */ unsigned char flag; /* special meaning (e.g. floodback) */ struct timeval birth; /* tv_sec when LS age 0 */ struct timeval originated; /* used by MinLSInterval check */ struct timeval received; /* used by MinLSArrival check */ struct timeval installed; struct thread *expire; struct thread *refresh; /* For self-originated LSA */ int retrans_count; struct ospf6_lsdb *lsdb; /* lsa instance */ struct ospf6_lsa_header *header; }; #define OSPF6_LSA_HEADERONLY 0x01 #define OSPF6_LSA_FLOODBACK 0x02 #define OSPF6_LSA_DUPLICATE 0x04 #define OSPF6_LSA_IMPLIEDACK 0x08 #define OSPF6_LSA_UNAPPROVED 0x10 #define OSPF6_LSA_SEQWRAPPED 0x20 struct ospf6_lsa_handler { uint16_t lh_type; /* host byte order */ const char *lh_name; const char *lh_short_name; int (*lh_show)(struct vty *, struct ospf6_lsa *); char *(*lh_get_prefix_str)(struct ospf6_lsa *, char *buf, int buflen, int pos); uint8_t lh_debug; }; #define OSPF6_LSA_IS_KNOWN(t) \ (ospf6_get_lsa_handler(t)->lh_type != OSPF6_LSTYPE_UNKNOWN ? 1 : 0) extern vector ospf6_lsa_handler_vector; /* Macro for LSA Origination */ /* addr is (struct prefix *) */ #define CONTINUE_IF_ADDRESS_LINKLOCAL(debug, addr) \ if (IN6_IS_ADDR_LINKLOCAL(&(addr)->u.prefix6)) { \ char buf[PREFIX2STR_BUFFER]; \ prefix2str(addr, buf, sizeof(buf)); \ if (debug) \ zlog_debug("Filter out Linklocal: %s", buf); \ continue; \ } #define CONTINUE_IF_ADDRESS_UNSPECIFIED(debug, addr) \ if (IN6_IS_ADDR_UNSPECIFIED(&(addr)->u.prefix6)) { \ char buf[PREFIX2STR_BUFFER]; \ prefix2str(addr, buf, sizeof(buf)); \ if (debug) \ zlog_debug("Filter out Unspecified: %s", buf); \ continue; \ } #define CONTINUE_IF_ADDRESS_LOOPBACK(debug, addr) \ if (IN6_IS_ADDR_LOOPBACK(&(addr)->u.prefix6)) { \ char buf[PREFIX2STR_BUFFER]; \ prefix2str(addr, buf, sizeof(buf)); \ if (debug) \ zlog_debug("Filter out Loopback: %s", buf); \ continue; \ } #define CONTINUE_IF_ADDRESS_V4COMPAT(debug, addr) \ if (IN6_IS_ADDR_V4COMPAT(&(addr)->u.prefix6)) { \ char buf[PREFIX2STR_BUFFER]; \ prefix2str(addr, buf, sizeof(buf)); \ if (debug) \ zlog_debug("Filter out V4Compat: %s", buf); \ continue; \ } #define CONTINUE_IF_ADDRESS_V4MAPPED(debug, addr) \ if (IN6_IS_ADDR_V4MAPPED(&(addr)->u.prefix6)) { \ char buf[PREFIX2STR_BUFFER]; \ prefix2str(addr, buf, sizeof(buf)); \ if (debug) \ zlog_debug("Filter out V4Mapped: %s", buf); \ continue; \ } /* Function Prototypes */ extern const char *ospf6_lstype_name(uint16_t type); extern const char *ospf6_lstype_short_name(uint16_t type); extern uint8_t ospf6_lstype_debug(uint16_t type); extern int ospf6_lsa_is_differ(struct ospf6_lsa *lsa1, struct ospf6_lsa *lsa2); extern int ospf6_lsa_is_changed(struct ospf6_lsa *lsa1, struct ospf6_lsa *lsa2); extern uint16_t ospf6_lsa_age_current(struct ospf6_lsa *); extern void ospf6_lsa_age_update_to_send(struct ospf6_lsa *, uint32_t); extern void ospf6_lsa_premature_aging(struct ospf6_lsa *); extern int ospf6_lsa_compare(struct ospf6_lsa *, struct ospf6_lsa *); extern char *ospf6_lsa_printbuf(struct ospf6_lsa *lsa, char *buf, int size); extern void ospf6_lsa_header_print_raw(struct ospf6_lsa_header *header); extern void ospf6_lsa_header_print(struct ospf6_lsa *lsa); extern void ospf6_lsa_show_summary_header(struct vty *vty); extern void ospf6_lsa_show_summary(struct vty *vty, struct ospf6_lsa *lsa); extern void ospf6_lsa_show_dump(struct vty *vty, struct ospf6_lsa *lsa); extern void ospf6_lsa_show_internal(struct vty *vty, struct ospf6_lsa *lsa); extern void ospf6_lsa_show(struct vty *vty, struct ospf6_lsa *lsa); extern struct ospf6_lsa *ospf6_lsa_create(struct ospf6_lsa_header *header); extern struct ospf6_lsa * ospf6_lsa_create_headeronly(struct ospf6_lsa_header *header); extern void ospf6_lsa_delete(struct ospf6_lsa *lsa); extern struct ospf6_lsa *ospf6_lsa_copy(struct ospf6_lsa *); extern void ospf6_lsa_lock(struct ospf6_lsa *); extern void ospf6_lsa_unlock(struct ospf6_lsa *); extern int ospf6_lsa_expire(struct thread *); extern int ospf6_lsa_refresh(struct thread *); extern unsigned short ospf6_lsa_checksum(struct ospf6_lsa_header *); extern int ospf6_lsa_checksum_valid(struct ospf6_lsa_header *); extern int ospf6_lsa_prohibited_duration(uint16_t type, uint32_t id, uint32_t adv_router, void *scope); extern void ospf6_install_lsa_handler(const struct ospf6_lsa_handler *handler); extern const struct ospf6_lsa_handler *ospf6_get_lsa_handler(uint16_t type); extern void ospf6_lsa_init(void); extern void ospf6_lsa_terminate(void); extern int config_write_ospf6_debug_lsa(struct vty *vty); extern void install_element_ospf6_debug_lsa(void); extern void ospf6_lsa_age_set(struct ospf6_lsa *lsa); extern void ospf6_flush_self_originated_lsas_now(void); #endif /* OSPF6_LSA_H */ frr-7.2.1/ospf6d/ospf6_lsdb.c0000644000000000000000000002445013610377563012627 00000000000000/* * Copyright (C) 2003 Yasuhiro Ohara * * This file is part of GNU Zebra. * * GNU Zebra is free software; you can redistribute it and/or modify it * under the terms of the GNU General Public License as published by the * Free Software Foundation; either version 2, or (at your option) any * later version. * * GNU Zebra 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; see the file COPYING; if not, write to the Free Software * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA */ #include #include "memory.h" #include "log.h" #include "command.h" #include "prefix.h" #include "table.h" #include "vty.h" #include "ospf6_proto.h" #include "ospf6_lsa.h" #include "ospf6_lsdb.h" #include "ospf6_route.h" #include "ospf6d.h" #include "bitfield.h" struct ospf6_lsdb *ospf6_lsdb_create(void *data) { struct ospf6_lsdb *lsdb; lsdb = XCALLOC(MTYPE_OSPF6_LSDB, sizeof(struct ospf6_lsdb)); memset(lsdb, 0, sizeof(struct ospf6_lsdb)); lsdb->data = data; lsdb->table = route_table_init(); return lsdb; } void ospf6_lsdb_delete(struct ospf6_lsdb *lsdb) { if (lsdb != NULL) { ospf6_lsdb_remove_all(lsdb); route_table_finish(lsdb->table); XFREE(MTYPE_OSPF6_LSDB, lsdb); } } static void ospf6_lsdb_set_key(struct prefix_ipv6 *key, const void *value, int len) { assert(key->prefixlen % 8 == 0); memcpy((caddr_t)&key->prefix + key->prefixlen / 8, (caddr_t)value, len); key->family = AF_INET6; key->prefixlen += len * 8; } #ifdef DEBUG static void _lsdb_count_assert(struct ospf6_lsdb *lsdb) { struct ospf6_lsa *debug; unsigned int num = 0; for (ALL_LSDB(lsdb, debug)) num++; if (num == lsdb->count) return; zlog_debug("PANIC !! lsdb[%p]->count = %d, real = %d", lsdb, lsdb->count, num); for (ALL_LSDB(lsdb, debug)) zlog_debug("%s lsdb[%p]", debug->name, debug->lsdb); zlog_debug("DUMP END"); assert(num == lsdb->count); } #define ospf6_lsdb_count_assert(t) (_lsdb_count_assert (t)) #else /*DEBUG*/ #define ospf6_lsdb_count_assert(t) ((void) 0) #endif /*DEBUG*/ void ospf6_lsdb_add(struct ospf6_lsa *lsa, struct ospf6_lsdb *lsdb) { struct prefix_ipv6 key; struct route_node *current; struct ospf6_lsa *old = NULL; memset(&key, 0, sizeof(key)); ospf6_lsdb_set_key(&key, &lsa->header->type, sizeof(lsa->header->type)); ospf6_lsdb_set_key(&key, &lsa->header->adv_router, sizeof(lsa->header->adv_router)); ospf6_lsdb_set_key(&key, &lsa->header->id, sizeof(lsa->header->id)); current = route_node_get(lsdb->table, (struct prefix *)&key); old = current->info; current->info = lsa; lsa->rn = current; ospf6_lsa_lock(lsa); if (!old) { lsdb->count++; if (OSPF6_LSA_IS_MAXAGE(lsa)) { if (lsdb->hook_remove) (*lsdb->hook_remove)(lsa); } else { if (lsdb->hook_add) (*lsdb->hook_add)(lsa); } } else { if (OSPF6_LSA_IS_CHANGED(old, lsa)) { if (OSPF6_LSA_IS_MAXAGE(lsa)) { if (lsdb->hook_remove) { (*lsdb->hook_remove)(old); (*lsdb->hook_remove)(lsa); } } else if (OSPF6_LSA_IS_MAXAGE(old)) { if (lsdb->hook_add) (*lsdb->hook_add)(lsa); } else { if (lsdb->hook_remove) (*lsdb->hook_remove)(old); if (lsdb->hook_add) (*lsdb->hook_add)(lsa); } } /* to free the lookup lock in node get*/ route_unlock_node(current); ospf6_lsa_unlock(old); } ospf6_lsdb_count_assert(lsdb); } void ospf6_lsdb_remove(struct ospf6_lsa *lsa, struct ospf6_lsdb *lsdb) { struct route_node *node; struct prefix_ipv6 key; memset(&key, 0, sizeof(key)); ospf6_lsdb_set_key(&key, &lsa->header->type, sizeof(lsa->header->type)); ospf6_lsdb_set_key(&key, &lsa->header->adv_router, sizeof(lsa->header->adv_router)); ospf6_lsdb_set_key(&key, &lsa->header->id, sizeof(lsa->header->id)); node = route_node_lookup(lsdb->table, (struct prefix *)&key); assert(node && node->info == lsa); node->info = NULL; lsdb->count--; if (lsdb->hook_remove) (*lsdb->hook_remove)(lsa); route_unlock_node(node); /* to free the lookup lock */ route_unlock_node(node); /* to free the original lock */ ospf6_lsa_unlock(lsa); ospf6_lsdb_count_assert(lsdb); } struct ospf6_lsa *ospf6_lsdb_lookup(uint16_t type, uint32_t id, uint32_t adv_router, struct ospf6_lsdb *lsdb) { struct route_node *node; struct prefix_ipv6 key; if (lsdb == NULL) return NULL; memset(&key, 0, sizeof(key)); ospf6_lsdb_set_key(&key, &type, sizeof(type)); ospf6_lsdb_set_key(&key, &adv_router, sizeof(adv_router)); ospf6_lsdb_set_key(&key, &id, sizeof(id)); node = route_node_lookup(lsdb->table, (struct prefix *)&key); if (node == NULL || node->info == NULL) return NULL; route_unlock_node(node); return (struct ospf6_lsa *)node->info; } struct ospf6_lsa *ospf6_lsdb_lookup_next(uint16_t type, uint32_t id, uint32_t adv_router, struct ospf6_lsdb *lsdb) { struct route_node *node; struct prefix_ipv6 key; if (lsdb == NULL) return NULL; memset(&key, 0, sizeof(key)); ospf6_lsdb_set_key(&key, &type, sizeof(type)); ospf6_lsdb_set_key(&key, &adv_router, sizeof(adv_router)); ospf6_lsdb_set_key(&key, &id, sizeof(id)); { char buf[PREFIX2STR_BUFFER]; prefix2str(&key, buf, sizeof(buf)); zlog_debug("lsdb_lookup_next: key: %s", buf); } node = route_table_get_next(lsdb->table, &key); /* skip to real existing entry */ while (node && node->info == NULL) node = route_next(node); if (!node) return NULL; route_unlock_node(node); if (!node->info) return NULL; return (struct ospf6_lsa *)node->info; } const struct route_node *ospf6_lsdb_head(struct ospf6_lsdb *lsdb, int argmode, uint16_t type, uint32_t adv_router, struct ospf6_lsa **lsa) { struct route_node *node, *end; *lsa = NULL; if (argmode > 0) { struct prefix_ipv6 key = {.family = AF_INET6, .prefixlen = 0}; ospf6_lsdb_set_key(&key, &type, sizeof(type)); if (argmode > 1) ospf6_lsdb_set_key(&key, &adv_router, sizeof(adv_router)); node = route_table_get_next(lsdb->table, &key); if (!node || !prefix_match((struct prefix *)&key, &node->p)) return NULL; for (end = node; end && end->parent && end->parent->p.prefixlen >= key.prefixlen; end = end->parent) ; } else { node = route_top(lsdb->table); end = NULL; } while (node && !node->info) node = route_next_until(node, end); if (!node) return NULL; if (!node->info) { route_unlock_node(node); return NULL; } *lsa = node->info; ospf6_lsa_lock(*lsa); return end; } struct ospf6_lsa *ospf6_lsdb_next(const struct route_node *iterend, struct ospf6_lsa *lsa) { struct route_node *node = lsa->rn; ospf6_lsa_unlock(lsa); do node = route_next_until(node, iterend); while (node && !node->info); if (node && node->info) { struct ospf6_lsa *next = node->info; ospf6_lsa_lock(next); return next; } if (node) route_unlock_node(node); return NULL; } void ospf6_lsdb_remove_all(struct ospf6_lsdb *lsdb) { struct ospf6_lsa *lsa; if (lsdb == NULL) return; for (ALL_LSDB(lsdb, lsa)) ospf6_lsdb_remove(lsa, lsdb); } void ospf6_lsdb_lsa_unlock(struct ospf6_lsa *lsa) { if (lsa != NULL) { if (lsa->rn != NULL) route_unlock_node(lsa->rn); ospf6_lsa_unlock(lsa); } } int ospf6_lsdb_maxage_remover(struct ospf6_lsdb *lsdb) { int reschedule = 0; struct ospf6_lsa *lsa; for (ALL_LSDB(lsdb, lsa)) { if (!OSPF6_LSA_IS_MAXAGE(lsa)) continue; if (lsa->retrans_count != 0) { reschedule = 1; continue; } if (IS_OSPF6_DEBUG_LSA_TYPE(lsa->header->type)) zlog_debug("Remove MaxAge %s", lsa->name); if (CHECK_FLAG(lsa->flag, OSPF6_LSA_SEQWRAPPED)) { UNSET_FLAG(lsa->flag, OSPF6_LSA_SEQWRAPPED); /* * lsa->header->age = 0; */ lsa->header->seqnum = htonl(OSPF_MAX_SEQUENCE_NUMBER + 1); ospf6_lsa_checksum(lsa->header); THREAD_OFF(lsa->refresh); thread_execute(master, ospf6_lsa_refresh, lsa, 0); } else { ospf6_lsdb_remove(lsa, lsdb); } } return (reschedule); } void ospf6_lsdb_show(struct vty *vty, enum ospf_lsdb_show_level level, uint16_t *type, uint32_t *id, uint32_t *adv_router, struct ospf6_lsdb *lsdb) { struct ospf6_lsa *lsa; const struct route_node *end = NULL; void (*showfunc)(struct vty *, struct ospf6_lsa *) = NULL; switch (level) { case OSPF6_LSDB_SHOW_LEVEL_DETAIL: showfunc = ospf6_lsa_show; break; case OSPF6_LSDB_SHOW_LEVEL_INTERNAL: showfunc = ospf6_lsa_show_internal; break; case OSPF6_LSDB_SHOW_LEVEL_DUMP: showfunc = ospf6_lsa_show_dump; break; case OSPF6_LSDB_SHOW_LEVEL_NORMAL: default: showfunc = ospf6_lsa_show_summary; } if (type && id && adv_router) { lsa = ospf6_lsdb_lookup(*type, *id, *adv_router, lsdb); if (lsa) { if (level == OSPF6_LSDB_SHOW_LEVEL_NORMAL) ospf6_lsa_show(vty, lsa); else (*showfunc)(vty, lsa); } return; } if (level == OSPF6_LSDB_SHOW_LEVEL_NORMAL) ospf6_lsa_show_summary_header(vty); end = ospf6_lsdb_head(lsdb, !!type + !!(type && adv_router), type ? *type : 0, adv_router ? *adv_router : 0, &lsa); while (lsa) { if ((!adv_router || lsa->header->adv_router == *adv_router) && (!id || lsa->header->id == *id)) (*showfunc)(vty, lsa); lsa = ospf6_lsdb_next(end, lsa); } } uint32_t ospf6_new_ls_id(uint16_t type, uint32_t adv_router, struct ospf6_lsdb *lsdb) { struct ospf6_lsa *lsa; uint32_t id = 1, tmp_id; /* This routine is curently invoked only for Inter-Prefix LSAs for * non-summarized routes (no area/range). */ for (ALL_LSDB_TYPED_ADVRTR(lsdb, type, adv_router, lsa)) { tmp_id = ntohl(lsa->header->id); if (tmp_id < id) continue; if (tmp_id > id) { ospf6_lsdb_lsa_unlock(lsa); break; } id++; } return ((uint32_t)htonl(id)); } /* Decide new LS sequence number to originate. note return value is network byte order */ uint32_t ospf6_new_ls_seqnum(uint16_t type, uint32_t id, uint32_t adv_router, struct ospf6_lsdb *lsdb) { struct ospf6_lsa *lsa; signed long seqnum = 0; /* if current database copy not found, return InitialSequenceNumber */ lsa = ospf6_lsdb_lookup(type, id, adv_router, lsdb); if (lsa == NULL) seqnum = OSPF_INITIAL_SEQUENCE_NUMBER; else seqnum = (signed long)ntohl(lsa->header->seqnum) + 1; return ((uint32_t)htonl(seqnum)); } frr-7.2.1/ospf6d/ospf6_lsdb.h0000644000000000000000000000720413610377563012632 00000000000000/* * Copyright (C) 2003 Yasuhiro Ohara * * This file is part of GNU Zebra. * * GNU Zebra is free software; you can redistribute it and/or modify it * under the terms of the GNU General Public License as published by the * Free Software Foundation; either version 2, or (at your option) any * later version. * * GNU Zebra 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; see the file COPYING; if not, write to the Free Software * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA */ #ifndef OSPF6_LSDB_H #define OSPF6_LSDB_H #include "prefix.h" #include "table.h" #include "ospf6_route.h" struct ospf6_lsdb { void *data; /* data structure that holds this lsdb */ struct route_table *table; uint32_t count; void (*hook_add)(struct ospf6_lsa *); void (*hook_remove)(struct ospf6_lsa *); }; /* Function Prototypes */ extern struct ospf6_lsdb *ospf6_lsdb_create(void *data); extern void ospf6_lsdb_delete(struct ospf6_lsdb *lsdb); extern struct ospf6_lsa *ospf6_lsdb_lookup(uint16_t type, uint32_t id, uint32_t adv_router, struct ospf6_lsdb *lsdb); extern struct ospf6_lsa *ospf6_lsdb_lookup_next(uint16_t type, uint32_t id, uint32_t adv_router, struct ospf6_lsdb *lsdb); extern void ospf6_lsdb_add(struct ospf6_lsa *lsa, struct ospf6_lsdb *lsdb); extern void ospf6_lsdb_remove(struct ospf6_lsa *lsa, struct ospf6_lsdb *lsdb); extern const struct route_node *ospf6_lsdb_head(struct ospf6_lsdb *lsdb, int argmode, uint16_t type, uint32_t adv_router, struct ospf6_lsa **lsa); extern struct ospf6_lsa *ospf6_lsdb_next(const struct route_node *iterend, struct ospf6_lsa *lsa); #define ALL_LSDB_TYPED_ADVRTR(lsdb, type, adv_router, lsa) \ const struct route_node *iterend = \ ospf6_lsdb_head(lsdb, 2, type, adv_router, &lsa); \ lsa; \ lsa = ospf6_lsdb_next(iterend, lsa) #define ALL_LSDB_TYPED(lsdb, type, lsa) \ const struct route_node *iterend = \ ospf6_lsdb_head(lsdb, 1, type, 0, &lsa); \ lsa; \ lsa = ospf6_lsdb_next(iterend, lsa) #define ALL_LSDB(lsdb, lsa) \ const struct route_node *iterend = \ ospf6_lsdb_head(lsdb, 0, 0, 0, &lsa); \ lsa; \ lsa = ospf6_lsdb_next(iterend, lsa) extern void ospf6_lsdb_remove_all(struct ospf6_lsdb *lsdb); extern void ospf6_lsdb_lsa_unlock(struct ospf6_lsa *lsa); enum ospf_lsdb_show_level { OSPF6_LSDB_SHOW_LEVEL_NORMAL = 0, OSPF6_LSDB_SHOW_LEVEL_DETAIL, OSPF6_LSDB_SHOW_LEVEL_INTERNAL, OSPF6_LSDB_SHOW_LEVEL_DUMP, }; extern void ospf6_lsdb_show(struct vty *vty, enum ospf_lsdb_show_level level, uint16_t *type, uint32_t *id, uint32_t *adv_router, struct ospf6_lsdb *lsdb); extern uint32_t ospf6_new_ls_id(uint16_t type, uint32_t adv_router, struct ospf6_lsdb *lsdb); extern uint32_t ospf6_new_ls_seqnum(uint16_t type, uint32_t id, uint32_t adv_router, struct ospf6_lsdb *lsdb); extern int ospf6_lsdb_maxage_remover(struct ospf6_lsdb *lsdb); #endif /* OSPF6_LSDB_H */ frr-7.2.1/ospf6d/ospf6_main.c0000644000000000000000000001104713610377563012625 00000000000000/* * Copyright (C) 1999 Yasuhiro Ohara * * This file is part of GNU Zebra. * * GNU Zebra is free software; you can redistribute it and/or modify it * under the terms of the GNU General Public License as published by the * Free Software Foundation; either version 2, or (at your option) any * later version. * * GNU Zebra 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; see the file COPYING; if not, write to the Free Software * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA */ #include #include #include #include "getopt.h" #include "thread.h" #include "log.h" #include "command.h" #include "vty.h" #include "memory.h" #include "memory_vty.h" #include "if.h" #include "filter.h" #include "prefix.h" #include "plist.h" #include "privs.h" #include "sigevent.h" #include "zclient.h" #include "vrf.h" #include "bfd.h" #include "libfrr.h" #include "ospf6d.h" #include "ospf6_top.h" #include "ospf6_message.h" #include "ospf6_network.h" #include "ospf6_asbr.h" #include "ospf6_lsa.h" #include "ospf6_interface.h" #include "ospf6_zebra.h" /* Default configuration file name for ospf6d. */ #define OSPF6_DEFAULT_CONFIG "ospf6d.conf" /* Default port values. */ #define OSPF6_VTY_PORT 2606 /* ospf6d privileges */ zebra_capabilities_t _caps_p[] = {ZCAP_NET_RAW, ZCAP_BIND}; struct zebra_privs_t ospf6d_privs = { #if defined(FRR_USER) .user = FRR_USER, #endif #if defined FRR_GROUP .group = FRR_GROUP, #endif #ifdef VTY_GROUP .vty_group = VTY_GROUP, #endif .caps_p = _caps_p, .cap_num_p = 2, .cap_num_i = 0}; /* ospf6d options, we use GNU getopt library. */ struct option longopts[] = {{0}}; /* Master of threads. */ struct thread_master *master; static void __attribute__((noreturn)) ospf6_exit(int status) { struct vrf *vrf = vrf_lookup_by_id(VRF_DEFAULT); struct interface *ifp; frr_early_fini(); if (ospf6) { ospf6_delete(ospf6); ospf6 = NULL; } bfd_gbl_exit(); FOR_ALL_INTERFACES (vrf, ifp) if (ifp->info != NULL) ospf6_interface_delete(ifp->info); ospf6_message_terminate(); ospf6_asbr_terminate(); ospf6_lsa_terminate(); ospf6_serv_close(); /* reverse access_list_init */ access_list_reset(); /* reverse prefix_list_init */ prefix_list_add_hook(NULL); prefix_list_delete_hook(NULL); prefix_list_reset(); vrf_terminate(); if (zclient) { zclient_stop(zclient); zclient_free(zclient); } frr_fini(); exit(status); } /* SIGHUP handler. */ static void sighup(void) { zlog_info("SIGHUP received"); } /* SIGINT handler. */ static void sigint(void) { zlog_notice("Terminating on signal SIGINT"); ospf6_exit(0); } /* SIGTERM handler. */ static void sigterm(void) { zlog_notice("Terminating on signal SIGTERM"); ospf6_exit(0); } /* SIGUSR1 handler. */ static void sigusr1(void) { zlog_info("SIGUSR1 received"); zlog_rotate(); } struct quagga_signal_t ospf6_signals[] = { { .signal = SIGHUP, .handler = &sighup, }, { .signal = SIGINT, .handler = &sigint, }, { .signal = SIGTERM, .handler = &sigterm, }, { .signal = SIGUSR1, .handler = &sigusr1, }, }; static const struct frr_yang_module_info *ospf6d_yang_modules[] = { &frr_interface_info, }; FRR_DAEMON_INFO(ospf6d, OSPF6, .vty_port = OSPF6_VTY_PORT, .proghelp = "Implementation of the OSPFv3 routing protocol.", .signals = ospf6_signals, .n_signals = array_size(ospf6_signals), .privs = &ospf6d_privs, .yang_modules = ospf6d_yang_modules, .n_yang_modules = array_size(ospf6d_yang_modules), ) /* Main routine of ospf6d. Treatment of argument and starting ospf finite state machine is handled here. */ int main(int argc, char *argv[], char *envp[]) { int opt; frr_preinit(&ospf6d_di, argc, argv); frr_opt_add("", longopts, ""); /* Command line argument treatment. */ while (1) { opt = frr_getopt(argc, argv, NULL); if (opt == EOF) break; switch (opt) { case 0: break; default: frr_help_exit(1); break; } } if (geteuid() != 0) { errno = EPERM; perror(ospf6d_di.progname); exit(1); } /* OSPF6 master init. */ ospf6_master_init(); /* thread master */ master = frr_init(); vrf_init(NULL, NULL, NULL, NULL, NULL); access_list_init(); prefix_list_init(); /* initialize ospf6 */ ospf6_init(); frr_config_fork(); frr_run(master); /* Not reached. */ ospf6_exit(0); } frr-7.2.1/ospf6d/ospf6_memory.c0000644000000000000000000000353713610377563013216 00000000000000/* ospf6d memory type definitions * * Copyright (C) 2015 David Lamparter * * This file is part of Quagga. * * Quagga is free software; you can redistribute it and/or modify it * under the terms of the GNU General Public License as published by the * Free Software Foundation; either version 2, or (at your option) any * later version. * * Quagga 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; see the file COPYING; if not, write to the Free Software * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA */ #ifdef HAVE_CONFIG_H #include "config.h" #endif #include "ospf6_memory.h" DEFINE_MGROUP(OSPF6D, "ospf6d") DEFINE_MTYPE(OSPF6D, OSPF6_TOP, "OSPF6 top") DEFINE_MTYPE(OSPF6D, OSPF6_AREA, "OSPF6 area") DEFINE_MTYPE(OSPF6D, OSPF6_IF, "OSPF6 interface") DEFINE_MTYPE(OSPF6D, OSPF6_NEIGHBOR, "OSPF6 neighbor") DEFINE_MTYPE(OSPF6D, OSPF6_ROUTE, "OSPF6 route") DEFINE_MTYPE(OSPF6D, OSPF6_PREFIX, "OSPF6 prefix") DEFINE_MTYPE(OSPF6D, OSPF6_MESSAGE, "OSPF6 message") DEFINE_MTYPE(OSPF6D, OSPF6_LSA, "OSPF6 LSA") DEFINE_MTYPE(OSPF6D, OSPF6_LSA_HEADER, "OSPF6 LSA header") DEFINE_MTYPE(OSPF6D, OSPF6_LSA_SUMMARY, "OSPF6 LSA summary") DEFINE_MTYPE(OSPF6D, OSPF6_LSDB, "OSPF6 LSA database") DEFINE_MTYPE(OSPF6D, OSPF6_VERTEX, "OSPF6 vertex") DEFINE_MTYPE(OSPF6D, OSPF6_SPFTREE, "OSPF6 SPF tree") DEFINE_MTYPE(OSPF6D, OSPF6_NEXTHOP, "OSPF6 nexthop") DEFINE_MTYPE(OSPF6D, OSPF6_EXTERNAL_INFO, "OSPF6 ext. info") DEFINE_MTYPE(OSPF6D, OSPF6_PATH, "OSPF6 Path") DEFINE_MTYPE(OSPF6D, OSPF6_DIST_ARGS, "OSPF6 Distribute arguments") DEFINE_MTYPE(OSPF6D, OSPF6_OTHER, "OSPF6 other") frr-7.2.1/ospf6d/ospf6_memory.h0000644000000000000000000000271113610377563013214 00000000000000/* ospf6d memory type declarations * * Copyright (C) 2015 David Lamparter * * This file is part of Quagga. * * Quagga is free software; you can redistribute it and/or modify it * under the terms of the GNU General Public License as published by the * Free Software Foundation; either version 2, or (at your option) any * later version. * * Quagga 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; see the file COPYING; if not, write to the Free Software * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA */ #ifndef _QUAGGA_OSPF6_MEMORY_H #define _QUAGGA_OSPF6_MEMORY_H #include "memory.h" DECLARE_MGROUP(OSPF6D) DECLARE_MTYPE(OSPF6_TOP) DECLARE_MTYPE(OSPF6_AREA) DECLARE_MTYPE(OSPF6_IF) DECLARE_MTYPE(OSPF6_NEIGHBOR) DECLARE_MTYPE(OSPF6_ROUTE) DECLARE_MTYPE(OSPF6_PREFIX) DECLARE_MTYPE(OSPF6_MESSAGE) DECLARE_MTYPE(OSPF6_LSA) DECLARE_MTYPE(OSPF6_LSA_HEADER) DECLARE_MTYPE(OSPF6_LSA_SUMMARY) DECLARE_MTYPE(OSPF6_LSDB) DECLARE_MTYPE(OSPF6_VERTEX) DECLARE_MTYPE(OSPF6_SPFTREE) DECLARE_MTYPE(OSPF6_NEXTHOP) DECLARE_MTYPE(OSPF6_EXTERNAL_INFO) DECLARE_MTYPE(OSPF6_PATH) DECLARE_MTYPE(OSPF6_DIST_ARGS) DECLARE_MTYPE(OSPF6_OTHER) #endif /* _QUAGGA_OSPF6_MEMORY_H */ frr-7.2.1/ospf6d/ospf6_message.c0000644000000000000000000022321613610377563013330 00000000000000/* * Copyright (C) 2003 Yasuhiro Ohara * * This file is part of GNU Zebra. * * GNU Zebra is free software; you can redistribute it and/or modify it * under the terms of the GNU General Public License as published by the * Free Software Foundation; either version 2, or (at your option) any * later version. * * GNU Zebra 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; see the file COPYING; if not, write to the Free Software * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA */ #include #include "memory.h" #include "log.h" #include "vty.h" #include "command.h" #include "thread.h" #include "linklist.h" #include "lib_errors.h" #include "ospf6_proto.h" #include "ospf6_lsa.h" #include "ospf6_lsdb.h" #include "ospf6_network.h" #include "ospf6_message.h" #include "ospf6_top.h" #include "ospf6_area.h" #include "ospf6_neighbor.h" #include "ospf6_interface.h" /* for structures and macros ospf6_lsa_examin() needs */ #include "ospf6_abr.h" #include "ospf6_asbr.h" #include "ospf6_intra.h" #include "ospf6_flood.h" #include "ospf6d.h" #include unsigned char conf_debug_ospf6_message[6] = {0x03, 0, 0, 0, 0, 0}; static const struct message ospf6_message_type_str[] = { {OSPF6_MESSAGE_TYPE_HELLO, "Hello"}, {OSPF6_MESSAGE_TYPE_DBDESC, "DbDesc"}, {OSPF6_MESSAGE_TYPE_LSREQ, "LSReq"}, {OSPF6_MESSAGE_TYPE_LSUPDATE, "LSUpdate"}, {OSPF6_MESSAGE_TYPE_LSACK, "LSAck"}, {0}}; /* Minimum (besides the standard OSPF packet header) lengths for OSPF packets of particular types, offset is the "type" field. */ const uint16_t ospf6_packet_minlen[OSPF6_MESSAGE_TYPE_ALL] = { 0, OSPF6_HELLO_MIN_SIZE, OSPF6_DB_DESC_MIN_SIZE, OSPF6_LS_REQ_MIN_SIZE, OSPF6_LS_UPD_MIN_SIZE, OSPF6_LS_ACK_MIN_SIZE}; /* Minimum (besides the standard LSA header) lengths for LSAs of particular types, offset is the "LSA function code" portion of "LSA type" field. */ const uint16_t ospf6_lsa_minlen[OSPF6_LSTYPE_SIZE] = { 0, /* 0x2001 */ OSPF6_ROUTER_LSA_MIN_SIZE, /* 0x2002 */ OSPF6_NETWORK_LSA_MIN_SIZE, /* 0x2003 */ OSPF6_INTER_PREFIX_LSA_MIN_SIZE, /* 0x2004 */ OSPF6_INTER_ROUTER_LSA_FIX_SIZE, /* 0x4005 */ OSPF6_AS_EXTERNAL_LSA_MIN_SIZE, /* 0x2006 */ 0, /* 0x2007 */ OSPF6_AS_EXTERNAL_LSA_MIN_SIZE, /* 0x0008 */ OSPF6_LINK_LSA_MIN_SIZE, /* 0x2009 */ OSPF6_INTRA_PREFIX_LSA_MIN_SIZE}; /* print functions */ static void ospf6_header_print(struct ospf6_header *oh) { char router_id[16], area_id[16]; inet_ntop(AF_INET, &oh->router_id, router_id, sizeof(router_id)); inet_ntop(AF_INET, &oh->area_id, area_id, sizeof(area_id)); zlog_debug(" OSPFv%d Type:%d Len:%hu Router-ID:%s", oh->version, oh->type, ntohs(oh->length), router_id); zlog_debug(" Area-ID:%s Cksum:%hx Instance-ID:%d", area_id, ntohs(oh->checksum), oh->instance_id); } void ospf6_hello_print(struct ospf6_header *oh) { struct ospf6_hello *hello; char options[16]; char drouter[16], bdrouter[16], neighbor[16]; char *p; ospf6_header_print(oh); assert(oh->type == OSPF6_MESSAGE_TYPE_HELLO); hello = (struct ospf6_hello *)((caddr_t)oh + sizeof(struct ospf6_header)); inet_ntop(AF_INET, &hello->drouter, drouter, sizeof(drouter)); inet_ntop(AF_INET, &hello->bdrouter, bdrouter, sizeof(bdrouter)); ospf6_options_printbuf(hello->options, options, sizeof(options)); zlog_debug(" I/F-Id:%ld Priority:%d Option:%s", (unsigned long)ntohl(hello->interface_id), hello->priority, options); zlog_debug(" HelloInterval:%hu DeadInterval:%hu", ntohs(hello->hello_interval), ntohs(hello->dead_interval)); zlog_debug(" DR:%s BDR:%s", drouter, bdrouter); for (p = (char *)((caddr_t)hello + sizeof(struct ospf6_hello)); p + sizeof(uint32_t) <= OSPF6_MESSAGE_END(oh); p += sizeof(uint32_t)) { inet_ntop(AF_INET, (void *)p, neighbor, sizeof(neighbor)); zlog_debug(" Neighbor: %s", neighbor); } assert(p == OSPF6_MESSAGE_END(oh)); } void ospf6_dbdesc_print(struct ospf6_header *oh) { struct ospf6_dbdesc *dbdesc; char options[16]; char *p; ospf6_header_print(oh); assert(oh->type == OSPF6_MESSAGE_TYPE_DBDESC); dbdesc = (struct ospf6_dbdesc *)((caddr_t)oh + sizeof(struct ospf6_header)); ospf6_options_printbuf(dbdesc->options, options, sizeof(options)); zlog_debug(" MBZ: %#x Option: %s IfMTU: %hu", dbdesc->reserved1, options, ntohs(dbdesc->ifmtu)); zlog_debug(" MBZ: %#x Bits: %s%s%s SeqNum: %#lx", dbdesc->reserved2, (CHECK_FLAG(dbdesc->bits, OSPF6_DBDESC_IBIT) ? "I" : "-"), (CHECK_FLAG(dbdesc->bits, OSPF6_DBDESC_MBIT) ? "M" : "-"), (CHECK_FLAG(dbdesc->bits, OSPF6_DBDESC_MSBIT) ? "m" : "s"), (unsigned long)ntohl(dbdesc->seqnum)); for (p = (char *)((caddr_t)dbdesc + sizeof(struct ospf6_dbdesc)); p + sizeof(struct ospf6_lsa_header) <= OSPF6_MESSAGE_END(oh); p += sizeof(struct ospf6_lsa_header)) ospf6_lsa_header_print_raw((struct ospf6_lsa_header *)p); assert(p == OSPF6_MESSAGE_END(oh)); } void ospf6_lsreq_print(struct ospf6_header *oh) { char id[16], adv_router[16]; char *p; ospf6_header_print(oh); assert(oh->type == OSPF6_MESSAGE_TYPE_LSREQ); for (p = (char *)((caddr_t)oh + sizeof(struct ospf6_header)); p + sizeof(struct ospf6_lsreq_entry) <= OSPF6_MESSAGE_END(oh); p += sizeof(struct ospf6_lsreq_entry)) { struct ospf6_lsreq_entry *e = (struct ospf6_lsreq_entry *)p; inet_ntop(AF_INET, &e->adv_router, adv_router, sizeof(adv_router)); inet_ntop(AF_INET, &e->id, id, sizeof(id)); zlog_debug(" [%s Id:%s Adv:%s]", ospf6_lstype_name(e->type), id, adv_router); } assert(p == OSPF6_MESSAGE_END(oh)); } void ospf6_lsupdate_print(struct ospf6_header *oh) { struct ospf6_lsupdate *lsupdate; unsigned long num; char *p; ospf6_header_print(oh); assert(oh->type == OSPF6_MESSAGE_TYPE_LSUPDATE); lsupdate = (struct ospf6_lsupdate *)((caddr_t)oh + sizeof(struct ospf6_header)); num = ntohl(lsupdate->lsa_number); zlog_debug(" Number of LSA: %ld", num); for (p = (char *)((caddr_t)lsupdate + sizeof(struct ospf6_lsupdate)); p < OSPF6_MESSAGE_END(oh) && p + OSPF6_LSA_SIZE(p) <= OSPF6_MESSAGE_END(oh); p += OSPF6_LSA_SIZE(p)) { ospf6_lsa_header_print_raw((struct ospf6_lsa_header *)p); } assert(p == OSPF6_MESSAGE_END(oh)); } void ospf6_lsack_print(struct ospf6_header *oh) { char *p; ospf6_header_print(oh); assert(oh->type == OSPF6_MESSAGE_TYPE_LSACK); for (p = (char *)((caddr_t)oh + sizeof(struct ospf6_header)); p + sizeof(struct ospf6_lsa_header) <= OSPF6_MESSAGE_END(oh); p += sizeof(struct ospf6_lsa_header)) ospf6_lsa_header_print_raw((struct ospf6_lsa_header *)p); assert(p == OSPF6_MESSAGE_END(oh)); } static void ospf6_hello_recv(struct in6_addr *src, struct in6_addr *dst, struct ospf6_interface *oi, struct ospf6_header *oh) { struct ospf6_hello *hello; struct ospf6_neighbor *on; char *p; int twoway = 0; int neighborchange = 0; int neighbor_ifindex_change = 0; int backupseen = 0; hello = (struct ospf6_hello *)((caddr_t)oh + sizeof(struct ospf6_header)); /* HelloInterval check */ if (ntohs(hello->hello_interval) != oi->hello_interval) { if (IS_OSPF6_DEBUG_MESSAGE(oh->type, RECV)) zlog_debug("HelloInterval mismatch"); return; } /* RouterDeadInterval check */ if (ntohs(hello->dead_interval) != oi->dead_interval) { if (IS_OSPF6_DEBUG_MESSAGE(oh->type, RECV)) zlog_debug("RouterDeadInterval mismatch"); return; } /* E-bit check */ if (OSPF6_OPT_ISSET(hello->options, OSPF6_OPT_E) != OSPF6_OPT_ISSET(oi->area->options, OSPF6_OPT_E)) { if (IS_OSPF6_DEBUG_MESSAGE(oh->type, RECV)) zlog_debug("E-bit mismatch"); return; } /* Find neighbor, create if not exist */ on = ospf6_neighbor_lookup(oh->router_id, oi); if (on == NULL) { on = ospf6_neighbor_create(oh->router_id, oi); on->prev_drouter = on->drouter = hello->drouter; on->prev_bdrouter = on->bdrouter = hello->bdrouter; on->priority = hello->priority; } /* Always override neighbor's source address */ memcpy(&on->linklocal_addr, src, sizeof(struct in6_addr)); /* Neighbor ifindex check */ if (on->ifindex != (ifindex_t)ntohl(hello->interface_id)) { on->ifindex = ntohl(hello->interface_id); neighbor_ifindex_change++; } /* TwoWay check */ for (p = (char *)((caddr_t)hello + sizeof(struct ospf6_hello)); p + sizeof(uint32_t) <= OSPF6_MESSAGE_END(oh); p += sizeof(uint32_t)) { uint32_t *router_id = (uint32_t *)p; if (*router_id == oi->area->ospf6->router_id) twoway++; } assert(p == OSPF6_MESSAGE_END(oh)); /* RouterPriority check */ if (on->priority != hello->priority) { on->priority = hello->priority; neighborchange++; } /* DR check */ if (on->drouter != hello->drouter) { on->prev_drouter = on->drouter; on->drouter = hello->drouter; if (on->prev_drouter == on->router_id || on->drouter == on->router_id) neighborchange++; } /* BDR check */ if (on->bdrouter != hello->bdrouter) { on->prev_bdrouter = on->bdrouter; on->bdrouter = hello->bdrouter; if (on->prev_bdrouter == on->router_id || on->bdrouter == on->router_id) neighborchange++; } /* BackupSeen check */ if (oi->state == OSPF6_INTERFACE_WAITING) { if (hello->bdrouter == on->router_id) backupseen++; else if (hello->drouter == on->router_id && hello->bdrouter == htonl(0)) backupseen++; } oi->hello_in++; /* Execute neighbor events */ thread_execute(master, hello_received, on, 0); if (twoway) thread_execute(master, twoway_received, on, 0); else thread_execute(master, oneway_received, on, 0); /* Schedule interface events */ if (backupseen) thread_add_event(master, backup_seen, oi, 0, NULL); if (neighborchange) thread_add_event(master, neighbor_change, oi, 0, NULL); if (neighbor_ifindex_change && on->state == OSPF6_NEIGHBOR_FULL) OSPF6_ROUTER_LSA_SCHEDULE(oi->area); } static void ospf6_dbdesc_recv_master(struct ospf6_header *oh, struct ospf6_neighbor *on) { struct ospf6_dbdesc *dbdesc; char *p; dbdesc = (struct ospf6_dbdesc *)((caddr_t)oh + sizeof(struct ospf6_header)); if (on->state < OSPF6_NEIGHBOR_INIT) { if (IS_OSPF6_DEBUG_MESSAGE(oh->type, RECV)) zlog_debug("Neighbor state less than Init, ignore"); return; } switch (on->state) { case OSPF6_NEIGHBOR_TWOWAY: if (IS_OSPF6_DEBUG_MESSAGE(oh->type, RECV)) zlog_debug("Neighbor state is 2-Way, ignore"); return; case OSPF6_NEIGHBOR_INIT: thread_execute(master, twoway_received, on, 0); if (on->state != OSPF6_NEIGHBOR_EXSTART) { if (IS_OSPF6_DEBUG_MESSAGE(oh->type, RECV)) zlog_debug( "Neighbor state is not ExStart, ignore"); return; } /* else fall through to ExStart */ /* fallthru */ case OSPF6_NEIGHBOR_EXSTART: /* if neighbor obeys us as our slave, schedule negotiation_done and process LSA Headers. Otherwise, ignore this message */ if (!CHECK_FLAG(dbdesc->bits, OSPF6_DBDESC_MSBIT) && !CHECK_FLAG(dbdesc->bits, OSPF6_DBDESC_IBIT) && ntohl(dbdesc->seqnum) == on->dbdesc_seqnum) { /* execute NegotiationDone */ thread_execute(master, negotiation_done, on, 0); /* Record neighbor options */ memcpy(on->options, dbdesc->options, sizeof(on->options)); } else { if (IS_OSPF6_DEBUG_MESSAGE(oh->type, RECV)) zlog_debug("Negotiation failed"); return; } /* fall through to exchange */ case OSPF6_NEIGHBOR_EXCHANGE: if (!memcmp(dbdesc, &on->dbdesc_last, sizeof(struct ospf6_dbdesc))) { /* Duplicated DatabaseDescription is dropped by master */ if (IS_OSPF6_DEBUG_MESSAGE(oh->type, RECV)) zlog_debug( "Duplicated dbdesc discarded by Master, ignore"); return; } if (CHECK_FLAG(dbdesc->bits, OSPF6_DBDESC_MSBIT)) { if (IS_OSPF6_DEBUG_MESSAGE(oh->type, RECV)) zlog_debug("Master/Slave bit mismatch"); thread_add_event(master, seqnumber_mismatch, on, 0, NULL); return; } if (CHECK_FLAG(dbdesc->bits, OSPF6_DBDESC_IBIT)) { if (IS_OSPF6_DEBUG_MESSAGE(oh->type, RECV)) zlog_debug("Initialize bit mismatch"); thread_add_event(master, seqnumber_mismatch, on, 0, NULL); return; } if (memcmp(on->options, dbdesc->options, sizeof(on->options))) { if (IS_OSPF6_DEBUG_MESSAGE(oh->type, RECV)) zlog_debug("Option field mismatch"); thread_add_event(master, seqnumber_mismatch, on, 0, NULL); return; } if (ntohl(dbdesc->seqnum) != on->dbdesc_seqnum) { if (IS_OSPF6_DEBUG_MESSAGE(oh->type, RECV)) zlog_debug( "Sequence number mismatch (%#lx expected)", (unsigned long)on->dbdesc_seqnum); thread_add_event(master, seqnumber_mismatch, on, 0, NULL); return; } break; case OSPF6_NEIGHBOR_LOADING: case OSPF6_NEIGHBOR_FULL: if (!memcmp(dbdesc, &on->dbdesc_last, sizeof(struct ospf6_dbdesc))) { /* Duplicated DatabaseDescription is dropped by master */ if (IS_OSPF6_DEBUG_MESSAGE(oh->type, RECV)) zlog_debug( "Duplicated dbdesc discarded by Master, ignore"); return; } if (IS_OSPF6_DEBUG_MESSAGE(oh->type, RECV)) zlog_debug("Not duplicate dbdesc in state %s", ospf6_neighbor_state_str[on->state]); thread_add_event(master, seqnumber_mismatch, on, 0, NULL); return; default: assert(0); break; } /* Process LSA headers */ for (p = (char *)((caddr_t)dbdesc + sizeof(struct ospf6_dbdesc)); p + sizeof(struct ospf6_lsa_header) <= OSPF6_MESSAGE_END(oh); p += sizeof(struct ospf6_lsa_header)) { struct ospf6_lsa *his, *mine; struct ospf6_lsdb *lsdb = NULL; his = ospf6_lsa_create_headeronly((struct ospf6_lsa_header *)p); if (IS_OSPF6_DEBUG_MESSAGE(oh->type, RECV)) zlog_debug("%s", his->name); switch (OSPF6_LSA_SCOPE(his->header->type)) { case OSPF6_SCOPE_LINKLOCAL: lsdb = on->ospf6_if->lsdb; break; case OSPF6_SCOPE_AREA: lsdb = on->ospf6_if->area->lsdb; break; case OSPF6_SCOPE_AS: lsdb = on->ospf6_if->area->ospf6->lsdb; break; case OSPF6_SCOPE_RESERVED: if (IS_OSPF6_DEBUG_MESSAGE(oh->type, RECV)) zlog_debug("Ignoring LSA of reserved scope"); ospf6_lsa_delete(his); continue; break; } if (ntohs(his->header->type) == OSPF6_LSTYPE_AS_EXTERNAL && IS_AREA_STUB(on->ospf6_if->area)) { if (IS_OSPF6_DEBUG_MESSAGE(oh->type, RECV)) zlog_debug( "SeqNumMismatch (E-bit mismatch), discard"); ospf6_lsa_delete(his); thread_add_event(master, seqnumber_mismatch, on, 0, NULL); return; } mine = ospf6_lsdb_lookup(his->header->type, his->header->id, his->header->adv_router, lsdb); if (mine == NULL) { if (IS_OSPF6_DEBUG_MESSAGE(oh->type, RECV)) zlog_debug("Add request (No database copy)"); ospf6_lsdb_add(ospf6_lsa_copy(his), on->request_list); } else if (ospf6_lsa_compare(his, mine) < 0) { if (IS_OSPF6_DEBUG_MESSAGE(oh->type, RECV)) zlog_debug("Add request (Received MoreRecent)"); ospf6_lsdb_add(ospf6_lsa_copy(his), on->request_list); } else { if (IS_OSPF6_DEBUG_MESSAGE(oh->type, RECV)) zlog_debug("Discard (Existing MoreRecent)"); } ospf6_lsa_delete(his); } assert(p == OSPF6_MESSAGE_END(oh)); /* Increment sequence number */ on->dbdesc_seqnum++; /* schedule send lsreq */ if (on->request_list->count) thread_add_event(master, ospf6_lsreq_send, on, 0, &on->thread_send_lsreq); THREAD_OFF(on->thread_send_dbdesc); /* More bit check */ if (!CHECK_FLAG(dbdesc->bits, OSPF6_DBDESC_MBIT) && !CHECK_FLAG(on->dbdesc_bits, OSPF6_DBDESC_MBIT)) thread_add_event(master, exchange_done, on, 0, NULL); else { on->thread_send_dbdesc = NULL; thread_add_event(master, ospf6_dbdesc_send_newone, on, 0, &on->thread_send_dbdesc); } /* save last received dbdesc */ memcpy(&on->dbdesc_last, dbdesc, sizeof(struct ospf6_dbdesc)); } static void ospf6_dbdesc_recv_slave(struct ospf6_header *oh, struct ospf6_neighbor *on) { struct ospf6_dbdesc *dbdesc; char *p; dbdesc = (struct ospf6_dbdesc *)((caddr_t)oh + sizeof(struct ospf6_header)); if (on->state < OSPF6_NEIGHBOR_INIT) { if (IS_OSPF6_DEBUG_MESSAGE(oh->type, RECV)) zlog_debug("Neighbor state less than Init, ignore"); return; } switch (on->state) { case OSPF6_NEIGHBOR_TWOWAY: if (IS_OSPF6_DEBUG_MESSAGE(oh->type, RECV)) zlog_debug("Neighbor state is 2-Way, ignore"); return; case OSPF6_NEIGHBOR_INIT: thread_execute(master, twoway_received, on, 0); if (on->state != OSPF6_NEIGHBOR_EXSTART) { if (IS_OSPF6_DEBUG_MESSAGE(oh->type, RECV)) zlog_debug( "Neighbor state is not ExStart, ignore"); return; } /* else fall through to ExStart */ /* fallthru */ case OSPF6_NEIGHBOR_EXSTART: /* If the neighbor is Master, act as Slave. Schedule negotiation_done and process LSA Headers. Otherwise, ignore this message */ if (CHECK_FLAG(dbdesc->bits, OSPF6_DBDESC_IBIT) && CHECK_FLAG(dbdesc->bits, OSPF6_DBDESC_MBIT) && CHECK_FLAG(dbdesc->bits, OSPF6_DBDESC_MSBIT) && ntohs(oh->length) == sizeof(struct ospf6_header) + sizeof(struct ospf6_dbdesc)) { /* set the master/slave bit to slave */ UNSET_FLAG(on->dbdesc_bits, OSPF6_DBDESC_MSBIT); /* set the DD sequence number to one specified by master */ on->dbdesc_seqnum = ntohl(dbdesc->seqnum); /* schedule NegotiationDone */ thread_execute(master, negotiation_done, on, 0); /* Record neighbor options */ memcpy(on->options, dbdesc->options, sizeof(on->options)); } else { if (IS_OSPF6_DEBUG_MESSAGE(oh->type, RECV)) zlog_debug("Negotiation failed"); return; } break; case OSPF6_NEIGHBOR_EXCHANGE: if (!memcmp(dbdesc, &on->dbdesc_last, sizeof(struct ospf6_dbdesc))) { /* Duplicated DatabaseDescription causes slave to * retransmit */ if (IS_OSPF6_DEBUG_MESSAGE(oh->type, RECV)) zlog_debug( "Duplicated dbdesc causes retransmit"); THREAD_OFF(on->thread_send_dbdesc); on->thread_send_dbdesc = NULL; thread_add_event(master, ospf6_dbdesc_send, on, 0, &on->thread_send_dbdesc); return; } if (!CHECK_FLAG(dbdesc->bits, OSPF6_DBDESC_MSBIT)) { if (IS_OSPF6_DEBUG_MESSAGE(oh->type, RECV)) zlog_debug("Master/Slave bit mismatch"); thread_add_event(master, seqnumber_mismatch, on, 0, NULL); return; } if (CHECK_FLAG(dbdesc->bits, OSPF6_DBDESC_IBIT)) { if (IS_OSPF6_DEBUG_MESSAGE(oh->type, RECV)) zlog_debug("Initialize bit mismatch"); thread_add_event(master, seqnumber_mismatch, on, 0, NULL); return; } if (memcmp(on->options, dbdesc->options, sizeof(on->options))) { if (IS_OSPF6_DEBUG_MESSAGE(oh->type, RECV)) zlog_debug("Option field mismatch"); thread_add_event(master, seqnumber_mismatch, on, 0, NULL); return; } if (ntohl(dbdesc->seqnum) != on->dbdesc_seqnum + 1) { if (IS_OSPF6_DEBUG_MESSAGE(oh->type, RECV)) zlog_debug( "Sequence number mismatch (%#lx expected)", (unsigned long)on->dbdesc_seqnum + 1); thread_add_event(master, seqnumber_mismatch, on, 0, NULL); return; } break; case OSPF6_NEIGHBOR_LOADING: case OSPF6_NEIGHBOR_FULL: if (!memcmp(dbdesc, &on->dbdesc_last, sizeof(struct ospf6_dbdesc))) { /* Duplicated DatabaseDescription causes slave to * retransmit */ if (IS_OSPF6_DEBUG_MESSAGE(oh->type, RECV)) zlog_debug( "Duplicated dbdesc causes retransmit"); THREAD_OFF(on->thread_send_dbdesc); thread_add_event(master, ospf6_dbdesc_send, on, 0, &on->thread_send_dbdesc); return; } if (IS_OSPF6_DEBUG_MESSAGE(oh->type, RECV)) zlog_debug("Not duplicate dbdesc in state %s", ospf6_neighbor_state_str[on->state]); thread_add_event(master, seqnumber_mismatch, on, 0, NULL); return; default: assert(0); break; } /* Process LSA headers */ for (p = (char *)((caddr_t)dbdesc + sizeof(struct ospf6_dbdesc)); p + sizeof(struct ospf6_lsa_header) <= OSPF6_MESSAGE_END(oh); p += sizeof(struct ospf6_lsa_header)) { struct ospf6_lsa *his, *mine; struct ospf6_lsdb *lsdb = NULL; his = ospf6_lsa_create_headeronly((struct ospf6_lsa_header *)p); switch (OSPF6_LSA_SCOPE(his->header->type)) { case OSPF6_SCOPE_LINKLOCAL: lsdb = on->ospf6_if->lsdb; break; case OSPF6_SCOPE_AREA: lsdb = on->ospf6_if->area->lsdb; break; case OSPF6_SCOPE_AS: lsdb = on->ospf6_if->area->ospf6->lsdb; break; case OSPF6_SCOPE_RESERVED: if (IS_OSPF6_DEBUG_MESSAGE(oh->type, RECV)) zlog_debug("Ignoring LSA of reserved scope"); ospf6_lsa_delete(his); continue; break; } if (OSPF6_LSA_SCOPE(his->header->type) == OSPF6_SCOPE_AS && IS_AREA_STUB(on->ospf6_if->area)) { if (IS_OSPF6_DEBUG_MESSAGE(oh->type, RECV)) zlog_debug("E-bit mismatch with LSA Headers"); ospf6_lsa_delete(his); thread_add_event(master, seqnumber_mismatch, on, 0, NULL); return; } mine = ospf6_lsdb_lookup(his->header->type, his->header->id, his->header->adv_router, lsdb); if (mine == NULL || ospf6_lsa_compare(his, mine) < 0) { if (IS_OSPF6_DEBUG_MESSAGE(oh->type, RECV)) zlog_debug("Add request-list: %s", his->name); ospf6_lsdb_add(ospf6_lsa_copy(his), on->request_list); } ospf6_lsa_delete(his); } assert(p == OSPF6_MESSAGE_END(oh)); /* Set sequence number to Master's */ on->dbdesc_seqnum = ntohl(dbdesc->seqnum); /* schedule send lsreq */ if (on->request_list->count) thread_add_event(master, ospf6_lsreq_send, on, 0, &on->thread_send_lsreq); THREAD_OFF(on->thread_send_dbdesc); thread_add_event(master, ospf6_dbdesc_send_newone, on, 0, &on->thread_send_dbdesc); /* save last received dbdesc */ memcpy(&on->dbdesc_last, dbdesc, sizeof(struct ospf6_dbdesc)); } static void ospf6_dbdesc_recv(struct in6_addr *src, struct in6_addr *dst, struct ospf6_interface *oi, struct ospf6_header *oh) { struct ospf6_neighbor *on; struct ospf6_dbdesc *dbdesc; on = ospf6_neighbor_lookup(oh->router_id, oi); if (on == NULL) { if (IS_OSPF6_DEBUG_MESSAGE(oh->type, RECV)) zlog_debug("Neighbor not found, ignore"); return; } dbdesc = (struct ospf6_dbdesc *)((caddr_t)oh + sizeof(struct ospf6_header)); /* Interface MTU check */ if (!oi->mtu_ignore && ntohs(dbdesc->ifmtu) != oi->ifmtu) { if (IS_OSPF6_DEBUG_MESSAGE(oh->type, RECV)) zlog_debug("I/F MTU mismatch"); return; } if (dbdesc->reserved1 || dbdesc->reserved2) { if (IS_OSPF6_DEBUG_MESSAGE(oh->type, RECV)) zlog_debug( "Non-0 reserved field in %s's DbDesc, correct", on->name); dbdesc->reserved1 = 0; dbdesc->reserved2 = 0; } oi->db_desc_in++; if (ntohl(oh->router_id) < ntohl(ospf6->router_id)) ospf6_dbdesc_recv_master(oh, on); else if (ntohl(ospf6->router_id) < ntohl(oh->router_id)) ospf6_dbdesc_recv_slave(oh, on); else { if (IS_OSPF6_DEBUG_MESSAGE(oh->type, RECV)) zlog_debug("Can't decide which is master, ignore"); } } static void ospf6_lsreq_recv(struct in6_addr *src, struct in6_addr *dst, struct ospf6_interface *oi, struct ospf6_header *oh) { struct ospf6_neighbor *on; char *p; struct ospf6_lsreq_entry *e; struct ospf6_lsdb *lsdb = NULL; struct ospf6_lsa *lsa; on = ospf6_neighbor_lookup(oh->router_id, oi); if (on == NULL) { if (IS_OSPF6_DEBUG_MESSAGE(oh->type, RECV)) zlog_debug("Neighbor not found, ignore"); return; } if (on->state != OSPF6_NEIGHBOR_EXCHANGE && on->state != OSPF6_NEIGHBOR_LOADING && on->state != OSPF6_NEIGHBOR_FULL) { if (IS_OSPF6_DEBUG_MESSAGE(oh->type, RECV)) zlog_debug("Neighbor state less than Exchange, ignore"); return; } oi->ls_req_in++; /* Process each request */ for (p = (char *)((caddr_t)oh + sizeof(struct ospf6_header)); p + sizeof(struct ospf6_lsreq_entry) <= OSPF6_MESSAGE_END(oh); p += sizeof(struct ospf6_lsreq_entry)) { e = (struct ospf6_lsreq_entry *)p; switch (OSPF6_LSA_SCOPE(e->type)) { case OSPF6_SCOPE_LINKLOCAL: lsdb = on->ospf6_if->lsdb; break; case OSPF6_SCOPE_AREA: lsdb = on->ospf6_if->area->lsdb; break; case OSPF6_SCOPE_AS: lsdb = on->ospf6_if->area->ospf6->lsdb; break; default: if (IS_OSPF6_DEBUG_MESSAGE(oh->type, RECV)) zlog_debug("Ignoring LSA of reserved scope"); continue; break; } /* Find database copy */ lsa = ospf6_lsdb_lookup(e->type, e->id, e->adv_router, lsdb); if (lsa == NULL) { char id[16], adv_router[16]; if (IS_OSPF6_DEBUG_MESSAGE(oh->type, RECV)) { inet_ntop(AF_INET, &e->id, id, sizeof(id)); inet_ntop(AF_INET, &e->adv_router, adv_router, sizeof(adv_router)); zlog_debug( "Can't find requested [%s Id:%s Adv:%s]", ospf6_lstype_name(e->type), id, adv_router); } thread_add_event(master, bad_lsreq, on, 0, NULL); return; } ospf6_lsdb_add(ospf6_lsa_copy(lsa), on->lsupdate_list); } assert(p == OSPF6_MESSAGE_END(oh)); /* schedule send lsupdate */ THREAD_OFF(on->thread_send_lsupdate); thread_add_event(master, ospf6_lsupdate_send_neighbor, on, 0, &on->thread_send_lsupdate); } /* Verify, that the specified memory area contains exactly N valid IPv6 prefixes as specified by RFC5340, A.4.1. */ static unsigned ospf6_prefixes_examin( struct ospf6_prefix *current, /* start of buffer */ unsigned length, const uint32_t req_num_pfxs /* always compared with the actual number of prefixes */ ) { uint8_t requested_pfx_bytes; uint32_t real_num_pfxs = 0; while (length) { if (length < OSPF6_PREFIX_MIN_SIZE) { if (IS_OSPF6_DEBUG_MESSAGE(OSPF6_MESSAGE_TYPE_UNKNOWN, RECV)) zlog_debug("%s: undersized IPv6 prefix header", __func__); return MSG_NG; } /* safe to look deeper */ if (current->prefix_length > IPV6_MAX_BITLEN) { if (IS_OSPF6_DEBUG_MESSAGE(OSPF6_MESSAGE_TYPE_UNKNOWN, RECV)) zlog_debug("%s: invalid PrefixLength (%u bits)", __func__, current->prefix_length); return MSG_NG; } /* covers both fixed- and variable-sized fields */ requested_pfx_bytes = OSPF6_PREFIX_MIN_SIZE + OSPF6_PREFIX_SPACE(current->prefix_length); if (requested_pfx_bytes > length) { if (IS_OSPF6_DEBUG_MESSAGE(OSPF6_MESSAGE_TYPE_UNKNOWN, RECV)) zlog_debug("%s: undersized IPv6 prefix", __func__); return MSG_NG; } /* next prefix */ length -= requested_pfx_bytes; current = (struct ospf6_prefix *)((caddr_t)current + requested_pfx_bytes); real_num_pfxs++; } if (real_num_pfxs != req_num_pfxs) { if (IS_OSPF6_DEBUG_MESSAGE(OSPF6_MESSAGE_TYPE_UNKNOWN, RECV)) zlog_debug( "%s: IPv6 prefix number mismatch (%u required, %u real)", __func__, req_num_pfxs, real_num_pfxs); return MSG_NG; } return MSG_OK; } /* Verify an LSA to have a valid length and dispatch further (where appropriate) to check if the contents, including nested IPv6 prefixes, is properly sized/aligned within the LSA. Note that this function gets LSA type in network byte order, uses in host byte order and passes to ospf6_lstype_name() in network byte order again. */ static unsigned ospf6_lsa_examin(struct ospf6_lsa_header *lsah, const uint16_t lsalen, const uint8_t headeronly) { struct ospf6_intra_prefix_lsa *intra_prefix_lsa; struct ospf6_as_external_lsa *as_external_lsa; struct ospf6_link_lsa *link_lsa; unsigned exp_length; uint8_t ltindex; uint16_t lsatype; /* In case an additional minimum length constraint is defined for current LSA type, make sure that this constraint is met. */ lsatype = ntohs(lsah->type); ltindex = lsatype & OSPF6_LSTYPE_FCODE_MASK; if (ltindex < OSPF6_LSTYPE_SIZE && ospf6_lsa_minlen[ltindex] && lsalen < ospf6_lsa_minlen[ltindex] + OSPF6_LSA_HEADER_SIZE) { if (IS_OSPF6_DEBUG_MESSAGE(OSPF6_MESSAGE_TYPE_UNKNOWN, RECV)) zlog_debug("%s: undersized (%u B) LSA", __func__, lsalen); return MSG_NG; } switch (lsatype) { case OSPF6_LSTYPE_ROUTER: /* RFC5340 A.4.3, LSA header + OSPF6_ROUTER_LSA_MIN_SIZE bytes followed by N>=0 interface descriptions. */ if ((lsalen - OSPF6_LSA_HEADER_SIZE - OSPF6_ROUTER_LSA_MIN_SIZE) % OSPF6_ROUTER_LSDESC_FIX_SIZE) { if (IS_OSPF6_DEBUG_MESSAGE(OSPF6_MESSAGE_TYPE_UNKNOWN, RECV)) zlog_debug( "%s: interface description alignment error", __func__); return MSG_NG; } break; case OSPF6_LSTYPE_NETWORK: /* RFC5340 A.4.4, LSA header + OSPF6_NETWORK_LSA_MIN_SIZE bytes followed by N>=0 attached router descriptions. */ if ((lsalen - OSPF6_LSA_HEADER_SIZE - OSPF6_NETWORK_LSA_MIN_SIZE) % OSPF6_NETWORK_LSDESC_FIX_SIZE) { if (IS_OSPF6_DEBUG_MESSAGE(OSPF6_MESSAGE_TYPE_UNKNOWN, RECV)) zlog_debug( "%s: router description alignment error", __func__); return MSG_NG; } break; case OSPF6_LSTYPE_INTER_PREFIX: /* RFC5340 A.4.5, LSA header + OSPF6_INTER_PREFIX_LSA_MIN_SIZE bytes followed by 3-4 fields of a single IPv6 prefix. */ if (headeronly) break; return ospf6_prefixes_examin( (struct ospf6_prefix *)((caddr_t)lsah + OSPF6_LSA_HEADER_SIZE + OSPF6_INTER_PREFIX_LSA_MIN_SIZE), lsalen - OSPF6_LSA_HEADER_SIZE - OSPF6_INTER_PREFIX_LSA_MIN_SIZE, 1); case OSPF6_LSTYPE_INTER_ROUTER: /* RFC5340 A.4.6, fixed-size LSA. */ if (lsalen > OSPF6_LSA_HEADER_SIZE + OSPF6_INTER_ROUTER_LSA_FIX_SIZE) { if (IS_OSPF6_DEBUG_MESSAGE(OSPF6_MESSAGE_TYPE_UNKNOWN, RECV)) zlog_debug("%s: oversized (%u B) LSA", __func__, lsalen); return MSG_NG; } break; case OSPF6_LSTYPE_AS_EXTERNAL: /* RFC5340 A.4.7, same as A.4.8. */ case OSPF6_LSTYPE_TYPE_7: /* RFC5340 A.4.8, LSA header + OSPF6_AS_EXTERNAL_LSA_MIN_SIZE bytes followed by 3-4 fields of IPv6 prefix and 3 conditional LSA fields: 16 bytes of forwarding address, 4 bytes of external route tag, 4 bytes of referenced link state ID. */ if (headeronly) break; as_external_lsa = (struct ospf6_as_external_lsa *)((caddr_t)lsah + OSPF6_LSA_HEADER_SIZE); exp_length = OSPF6_LSA_HEADER_SIZE + OSPF6_AS_EXTERNAL_LSA_MIN_SIZE; /* To find out if the last optional field (Referenced Link State ID) is assumed in this LSA, we need to access fixed fields of the IPv6 prefix before ospf6_prefix_examin() confirms its sizing. */ if (exp_length + OSPF6_PREFIX_MIN_SIZE > lsalen) { if (IS_OSPF6_DEBUG_MESSAGE(OSPF6_MESSAGE_TYPE_UNKNOWN, RECV)) zlog_debug("%s: undersized (%u B) LSA header", __func__, lsalen); return MSG_NG; } /* forwarding address */ if (CHECK_FLAG(as_external_lsa->bits_metric, OSPF6_ASBR_BIT_F)) exp_length += 16; /* external route tag */ if (CHECK_FLAG(as_external_lsa->bits_metric, OSPF6_ASBR_BIT_T)) exp_length += 4; /* referenced link state ID */ if (as_external_lsa->prefix.u._prefix_referenced_lstype) exp_length += 4; /* All the fixed-size fields (mandatory and optional) must fit. I.e., this check does not include any IPv6 prefix fields. */ if (exp_length > lsalen) { if (IS_OSPF6_DEBUG_MESSAGE(OSPF6_MESSAGE_TYPE_UNKNOWN, RECV)) zlog_debug("%s: undersized (%u B) LSA header", __func__, lsalen); return MSG_NG; } /* The last call completely covers the remainder (IPv6 prefix). */ return ospf6_prefixes_examin( (struct ospf6_prefix *)((caddr_t)as_external_lsa + OSPF6_AS_EXTERNAL_LSA_MIN_SIZE), lsalen - exp_length, 1); case OSPF6_LSTYPE_LINK: /* RFC5340 A.4.9, LSA header + OSPF6_LINK_LSA_MIN_SIZE bytes followed by N>=0 IPv6 prefix blocks (with N declared beforehand). */ if (headeronly) break; link_lsa = (struct ospf6_link_lsa *)((caddr_t)lsah + OSPF6_LSA_HEADER_SIZE); return ospf6_prefixes_examin( (struct ospf6_prefix *)((caddr_t)link_lsa + OSPF6_LINK_LSA_MIN_SIZE), lsalen - OSPF6_LSA_HEADER_SIZE - OSPF6_LINK_LSA_MIN_SIZE, ntohl(link_lsa->prefix_num) /* 32 bits */ ); case OSPF6_LSTYPE_INTRA_PREFIX: /* RFC5340 A.4.10, LSA header + OSPF6_INTRA_PREFIX_LSA_MIN_SIZE bytes followed by N>=0 IPv6 prefixes (with N declared beforehand). */ if (headeronly) break; intra_prefix_lsa = (struct ospf6_intra_prefix_lsa *)((caddr_t)lsah + OSPF6_LSA_HEADER_SIZE); return ospf6_prefixes_examin( (struct ospf6_prefix *)((caddr_t)intra_prefix_lsa + OSPF6_INTRA_PREFIX_LSA_MIN_SIZE), lsalen - OSPF6_LSA_HEADER_SIZE - OSPF6_INTRA_PREFIX_LSA_MIN_SIZE, ntohs(intra_prefix_lsa->prefix_num) /* 16 bits */ ); } /* No additional validation is possible for unknown LSA types, which are themselves valid in OPSFv3, hence the default decision is to accept. */ return MSG_OK; } /* Verify if the provided input buffer is a valid sequence of LSAs. This includes verification of LSA blocks length/alignment and dispatching of deeper-level checks. */ static unsigned ospf6_lsaseq_examin(struct ospf6_lsa_header *lsah, /* start of buffered data */ size_t length, const uint8_t headeronly, /* When declared_num_lsas is not 0, compare it to the real number of LSAs and treat the difference as an error. */ const uint32_t declared_num_lsas) { uint32_t counted_lsas = 0; while (length) { uint16_t lsalen; if (length < OSPF6_LSA_HEADER_SIZE) { if (IS_OSPF6_DEBUG_MESSAGE(OSPF6_MESSAGE_TYPE_UNKNOWN, RECV)) zlog_debug( "%s: undersized (%zu B) trailing (#%u) LSA header", __func__, length, counted_lsas); return MSG_NG; } /* save on ntohs() calls here and in the LSA validator */ lsalen = OSPF6_LSA_SIZE(lsah); if (lsalen < OSPF6_LSA_HEADER_SIZE) { if (IS_OSPF6_DEBUG_MESSAGE(OSPF6_MESSAGE_TYPE_UNKNOWN, RECV)) zlog_debug( "%s: malformed LSA header #%u, declared length is %u B", __func__, counted_lsas, lsalen); return MSG_NG; } if (headeronly) { /* less checks here and in ospf6_lsa_examin() */ if (MSG_OK != ospf6_lsa_examin(lsah, lsalen, 1)) { if (IS_OSPF6_DEBUG_MESSAGE( OSPF6_MESSAGE_TYPE_UNKNOWN, RECV)) zlog_debug( "%s: anomaly in header-only %s LSA #%u", __func__, ospf6_lstype_name(lsah->type), counted_lsas); return MSG_NG; } lsah = (struct ospf6_lsa_header *)((caddr_t)lsah + OSPF6_LSA_HEADER_SIZE); length -= OSPF6_LSA_HEADER_SIZE; } else { /* make sure the input buffer is deep enough before * further checks */ if (lsalen > length) { if (IS_OSPF6_DEBUG_MESSAGE( OSPF6_MESSAGE_TYPE_UNKNOWN, RECV)) zlog_debug( "%s: anomaly in %s LSA #%u: declared length is %u B, buffered length is %zu B", __func__, ospf6_lstype_name(lsah->type), counted_lsas, lsalen, length); return MSG_NG; } if (MSG_OK != ospf6_lsa_examin(lsah, lsalen, 0)) { if (IS_OSPF6_DEBUG_MESSAGE( OSPF6_MESSAGE_TYPE_UNKNOWN, RECV)) zlog_debug( "%s: anomaly in %s LSA #%u", __func__, ospf6_lstype_name(lsah->type), counted_lsas); return MSG_NG; } lsah = (struct ospf6_lsa_header *)((caddr_t)lsah + lsalen); length -= lsalen; } counted_lsas++; } if (declared_num_lsas && counted_lsas != declared_num_lsas) { if (IS_OSPF6_DEBUG_MESSAGE(OSPF6_MESSAGE_TYPE_UNKNOWN, RECV)) zlog_debug( "%s: #LSAs declared (%u) does not match actual (%u)", __func__, declared_num_lsas, counted_lsas); return MSG_NG; } return MSG_OK; } /* Verify a complete OSPF packet for proper sizing/alignment. */ static unsigned ospf6_packet_examin(struct ospf6_header *oh, const unsigned bytesonwire) { struct ospf6_lsupdate *lsupd; unsigned test; /* length, 1st approximation */ if (bytesonwire < OSPF6_HEADER_SIZE) { if (IS_OSPF6_DEBUG_MESSAGE(OSPF6_MESSAGE_TYPE_UNKNOWN, RECV)) zlog_debug("%s: undersized (%u B) packet", __func__, bytesonwire); return MSG_NG; } /* Now it is safe to access header fields. */ if (bytesonwire != ntohs(oh->length)) { if (IS_OSPF6_DEBUG_MESSAGE(OSPF6_MESSAGE_TYPE_UNKNOWN, RECV)) zlog_debug( "%s: %s packet length error (%u real, %u declared)", __func__, lookup_msg(ospf6_message_type_str, oh->type, NULL), bytesonwire, ntohs(oh->length)); return MSG_NG; } /* version check */ if (oh->version != OSPFV3_VERSION) { if (IS_OSPF6_DEBUG_MESSAGE(OSPF6_MESSAGE_TYPE_UNKNOWN, RECV)) zlog_debug("%s: invalid (%u) protocol version", __func__, oh->version); return MSG_NG; } /* length, 2nd approximation */ if (oh->type < OSPF6_MESSAGE_TYPE_ALL && ospf6_packet_minlen[oh->type] && bytesonwire < OSPF6_HEADER_SIZE + ospf6_packet_minlen[oh->type]) { if (IS_OSPF6_DEBUG_MESSAGE(OSPF6_MESSAGE_TYPE_UNKNOWN, RECV)) zlog_debug("%s: undersized (%u B) %s packet", __func__, bytesonwire, lookup_msg(ospf6_message_type_str, oh->type, NULL)); return MSG_NG; } /* type-specific deeper validation */ switch (oh->type) { case OSPF6_MESSAGE_TYPE_HELLO: /* RFC5340 A.3.2, packet header + OSPF6_HELLO_MIN_SIZE bytes followed by N>=0 router-IDs. */ if (0 == (bytesonwire - OSPF6_HEADER_SIZE - OSPF6_HELLO_MIN_SIZE) % 4) return MSG_OK; if (IS_OSPF6_DEBUG_MESSAGE(OSPF6_MESSAGE_TYPE_UNKNOWN, RECV)) zlog_debug("%s: alignment error in %s packet", __func__, lookup_msg(ospf6_message_type_str, oh->type, NULL)); return MSG_NG; case OSPF6_MESSAGE_TYPE_DBDESC: /* RFC5340 A.3.3, packet header + OSPF6_DB_DESC_MIN_SIZE bytes followed by N>=0 header-only LSAs. */ test = ospf6_lsaseq_examin( (struct ospf6_lsa_header *)((caddr_t)oh + OSPF6_HEADER_SIZE + OSPF6_DB_DESC_MIN_SIZE), bytesonwire - OSPF6_HEADER_SIZE - OSPF6_DB_DESC_MIN_SIZE, 1, 0); break; case OSPF6_MESSAGE_TYPE_LSREQ: /* RFC5340 A.3.4, packet header + N>=0 LS description blocks. */ if (0 == (bytesonwire - OSPF6_HEADER_SIZE - OSPF6_LS_REQ_MIN_SIZE) % OSPF6_LSREQ_LSDESC_FIX_SIZE) return MSG_OK; if (IS_OSPF6_DEBUG_MESSAGE(OSPF6_MESSAGE_TYPE_UNKNOWN, RECV)) zlog_debug("%s: alignment error in %s packet", __func__, lookup_msg(ospf6_message_type_str, oh->type, NULL)); return MSG_NG; case OSPF6_MESSAGE_TYPE_LSUPDATE: /* RFC5340 A.3.5, packet header + OSPF6_LS_UPD_MIN_SIZE bytes followed by N>=0 full LSAs (with N declared beforehand). */ lsupd = (struct ospf6_lsupdate *)((caddr_t)oh + OSPF6_HEADER_SIZE); test = ospf6_lsaseq_examin( (struct ospf6_lsa_header *)((caddr_t)lsupd + OSPF6_LS_UPD_MIN_SIZE), bytesonwire - OSPF6_HEADER_SIZE - OSPF6_LS_UPD_MIN_SIZE, 0, ntohl(lsupd->lsa_number) /* 32 bits */ ); break; case OSPF6_MESSAGE_TYPE_LSACK: /* RFC5340 A.3.6, packet header + N>=0 header-only LSAs. */ test = ospf6_lsaseq_examin( (struct ospf6_lsa_header *)((caddr_t)oh + OSPF6_HEADER_SIZE + OSPF6_LS_ACK_MIN_SIZE), bytesonwire - OSPF6_HEADER_SIZE - OSPF6_LS_ACK_MIN_SIZE, 1, 0); break; default: if (IS_OSPF6_DEBUG_MESSAGE(OSPF6_MESSAGE_TYPE_UNKNOWN, RECV)) zlog_debug("%s: invalid (%u) message type", __func__, oh->type); return MSG_NG; } if (test != MSG_OK && IS_OSPF6_DEBUG_MESSAGE(OSPF6_MESSAGE_TYPE_UNKNOWN, RECV)) zlog_debug("%s: anomaly in %s packet", __func__, lookup_msg(ospf6_message_type_str, oh->type, NULL)); return test; } /* Verify particular fields of otherwise correct received OSPF packet to meet the requirements of RFC. */ static int ospf6_rxpacket_examin(struct ospf6_interface *oi, struct ospf6_header *oh, const unsigned bytesonwire) { char buf[2][INET_ADDRSTRLEN]; if (MSG_OK != ospf6_packet_examin(oh, bytesonwire)) return MSG_NG; /* Area-ID check */ if (oh->area_id != oi->area->area_id) { if (IS_OSPF6_DEBUG_MESSAGE(oh->type, RECV)) { if (oh->area_id == OSPF_AREA_BACKBONE) zlog_debug( "%s: Message may be via Virtual Link: not supported", __func__); else zlog_debug( "%s: Area-ID mismatch (my %s, rcvd %s)", __func__, inet_ntop(AF_INET, &oi->area->area_id, buf[0], INET_ADDRSTRLEN), inet_ntop(AF_INET, &oh->area_id, buf[1], INET_ADDRSTRLEN)); } return MSG_NG; } /* Instance-ID check */ if (oh->instance_id != oi->instance_id) { if (IS_OSPF6_DEBUG_MESSAGE(oh->type, RECV)) zlog_debug("%s: Instance-ID mismatch (my %u, rcvd %u)", __func__, oi->instance_id, oh->instance_id); return MSG_NG; } /* Router-ID check */ if (oh->router_id == oi->area->ospf6->router_id) { zlog_warn("%s: Duplicate Router-ID (%s)", __func__, inet_ntop(AF_INET, &oh->router_id, buf[0], INET_ADDRSTRLEN)); return MSG_NG; } return MSG_OK; } static void ospf6_lsupdate_recv(struct in6_addr *src, struct in6_addr *dst, struct ospf6_interface *oi, struct ospf6_header *oh) { struct ospf6_neighbor *on; struct ospf6_lsupdate *lsupdate; char *p; on = ospf6_neighbor_lookup(oh->router_id, oi); if (on == NULL) { if (IS_OSPF6_DEBUG_MESSAGE(oh->type, RECV)) zlog_debug("Neighbor not found, ignore"); return; } if (on->state != OSPF6_NEIGHBOR_EXCHANGE && on->state != OSPF6_NEIGHBOR_LOADING && on->state != OSPF6_NEIGHBOR_FULL) { if (IS_OSPF6_DEBUG_MESSAGE(oh->type, RECV)) zlog_debug("Neighbor state less than Exchange, ignore"); return; } lsupdate = (struct ospf6_lsupdate *)((caddr_t)oh + sizeof(struct ospf6_header)); oi->ls_upd_in++; /* Process LSAs */ for (p = (char *)((caddr_t)lsupdate + sizeof(struct ospf6_lsupdate)); p < OSPF6_MESSAGE_END(oh) && p + OSPF6_LSA_SIZE(p) <= OSPF6_MESSAGE_END(oh); p += OSPF6_LSA_SIZE(p)) { ospf6_receive_lsa(on, (struct ospf6_lsa_header *)p); } assert(p == OSPF6_MESSAGE_END(oh)); } static void ospf6_lsack_recv(struct in6_addr *src, struct in6_addr *dst, struct ospf6_interface *oi, struct ospf6_header *oh) { struct ospf6_neighbor *on; char *p; struct ospf6_lsa *his, *mine; struct ospf6_lsdb *lsdb = NULL; assert(oh->type == OSPF6_MESSAGE_TYPE_LSACK); on = ospf6_neighbor_lookup(oh->router_id, oi); if (on == NULL) { if (IS_OSPF6_DEBUG_MESSAGE(oh->type, RECV)) zlog_debug("Neighbor not found, ignore"); return; } if (on->state != OSPF6_NEIGHBOR_EXCHANGE && on->state != OSPF6_NEIGHBOR_LOADING && on->state != OSPF6_NEIGHBOR_FULL) { if (IS_OSPF6_DEBUG_MESSAGE(oh->type, RECV)) zlog_debug("Neighbor state less than Exchange, ignore"); return; } oi->ls_ack_in++; for (p = (char *)((caddr_t)oh + sizeof(struct ospf6_header)); p + sizeof(struct ospf6_lsa_header) <= OSPF6_MESSAGE_END(oh); p += sizeof(struct ospf6_lsa_header)) { his = ospf6_lsa_create_headeronly((struct ospf6_lsa_header *)p); switch (OSPF6_LSA_SCOPE(his->header->type)) { case OSPF6_SCOPE_LINKLOCAL: lsdb = on->ospf6_if->lsdb; break; case OSPF6_SCOPE_AREA: lsdb = on->ospf6_if->area->lsdb; break; case OSPF6_SCOPE_AS: lsdb = on->ospf6_if->area->ospf6->lsdb; break; case OSPF6_SCOPE_RESERVED: if (IS_OSPF6_DEBUG_MESSAGE(oh->type, RECV)) zlog_debug("Ignoring LSA of reserved scope"); ospf6_lsa_delete(his); continue; break; } if (IS_OSPF6_DEBUG_MESSAGE(oh->type, RECV)) zlog_debug("%s acknowledged by %s", his->name, on->name); /* Find database copy */ mine = ospf6_lsdb_lookup(his->header->type, his->header->id, his->header->adv_router, lsdb); if (mine == NULL) { if (IS_OSPF6_DEBUG_MESSAGE(oh->type, RECV)) zlog_debug("No database copy"); ospf6_lsa_delete(his); continue; } /* Check if the LSA is on his retrans-list */ mine = ospf6_lsdb_lookup(his->header->type, his->header->id, his->header->adv_router, on->retrans_list); if (mine == NULL) { if (IS_OSPF6_DEBUG_MESSAGE(oh->type, RECV)) zlog_debug("Not on %s's retrans-list", on->name); ospf6_lsa_delete(his); continue; } if (ospf6_lsa_compare(his, mine) != 0) { /* Log this questionable acknowledgement, and examine the next one. */ if (IS_OSPF6_DEBUG_MESSAGE(oh->type, RECV)) zlog_debug("Questionable acknowledgement"); ospf6_lsa_delete(his); continue; } if (IS_OSPF6_DEBUG_MESSAGE(oh->type, RECV)) zlog_debug( "Acknowledged, remove from %s's retrans-list", on->name); ospf6_decrement_retrans_count(mine); if (OSPF6_LSA_IS_MAXAGE(mine)) ospf6_maxage_remove(on->ospf6_if->area->ospf6); ospf6_lsdb_remove(mine, on->retrans_list); ospf6_lsa_delete(his); } assert(p == OSPF6_MESSAGE_END(oh)); } static uint8_t *recvbuf = NULL; static uint8_t *sendbuf = NULL; static unsigned int iobuflen = 0; int ospf6_iobuf_size(unsigned int size) { uint8_t *recvnew, *sendnew; if (size <= iobuflen) return iobuflen; recvnew = XMALLOC(MTYPE_OSPF6_MESSAGE, size); sendnew = XMALLOC(MTYPE_OSPF6_MESSAGE, size); XFREE(MTYPE_OSPF6_MESSAGE, recvbuf); XFREE(MTYPE_OSPF6_MESSAGE, sendbuf); recvbuf = recvnew; sendbuf = sendnew; iobuflen = size; return iobuflen; } void ospf6_message_terminate(void) { if (recvbuf) { XFREE(MTYPE_OSPF6_MESSAGE, recvbuf); recvbuf = NULL; } if (sendbuf) { XFREE(MTYPE_OSPF6_MESSAGE, sendbuf); sendbuf = NULL; } iobuflen = 0; } int ospf6_receive(struct thread *thread) { int sockfd; unsigned int len; char srcname[64], dstname[64]; struct in6_addr src, dst; ifindex_t ifindex; struct iovec iovector[2]; struct ospf6_interface *oi; struct ospf6_header *oh; /* add next read thread */ sockfd = THREAD_FD(thread); thread_add_read(master, ospf6_receive, NULL, sockfd, NULL); /* initialize */ memset(&src, 0, sizeof(src)); memset(&dst, 0, sizeof(dst)); ifindex = 0; memset(recvbuf, 0, iobuflen); iovector[0].iov_base = recvbuf; iovector[0].iov_len = iobuflen; iovector[1].iov_base = NULL; iovector[1].iov_len = 0; /* receive message */ len = ospf6_recvmsg(&src, &dst, &ifindex, iovector); if (len > iobuflen) { flog_err(EC_LIB_DEVELOPMENT, "Excess message read"); return 0; } oi = ospf6_interface_lookup_by_ifindex(ifindex); if (oi == NULL || oi->area == NULL || CHECK_FLAG(oi->flag, OSPF6_INTERFACE_DISABLE)) { if (IS_OSPF6_DEBUG_MESSAGE(OSPF6_MESSAGE_TYPE_UNKNOWN, RECV)) zlog_debug("Message received on disabled interface"); return 0; } if (CHECK_FLAG(oi->flag, OSPF6_INTERFACE_PASSIVE)) { if (IS_OSPF6_DEBUG_MESSAGE(OSPF6_MESSAGE_TYPE_UNKNOWN, RECV)) zlog_debug("%s: Ignore message on passive interface %s", __func__, oi->interface->name); return 0; } oh = (struct ospf6_header *)recvbuf; if (ospf6_rxpacket_examin(oi, oh, len) != MSG_OK) return 0; /* Being here means, that no sizing/alignment issues were detected in the input packet. This renders the additional checks performed below and also in the type-specific dispatching functions a dead code, which can be dismissed in a cleanup-focused review round later. */ /* Log */ if (IS_OSPF6_DEBUG_MESSAGE(oh->type, RECV)) { inet_ntop(AF_INET6, &src, srcname, sizeof(srcname)); inet_ntop(AF_INET6, &dst, dstname, sizeof(dstname)); zlog_debug("%s received on %s", lookup_msg(ospf6_message_type_str, oh->type, NULL), oi->interface->name); zlog_debug(" src: %s", srcname); zlog_debug(" dst: %s", dstname); switch (oh->type) { case OSPF6_MESSAGE_TYPE_HELLO: ospf6_hello_print(oh); break; case OSPF6_MESSAGE_TYPE_DBDESC: ospf6_dbdesc_print(oh); break; case OSPF6_MESSAGE_TYPE_LSREQ: ospf6_lsreq_print(oh); break; case OSPF6_MESSAGE_TYPE_LSUPDATE: ospf6_lsupdate_print(oh); break; case OSPF6_MESSAGE_TYPE_LSACK: ospf6_lsack_print(oh); break; default: assert(0); } } switch (oh->type) { case OSPF6_MESSAGE_TYPE_HELLO: ospf6_hello_recv(&src, &dst, oi, oh); break; case OSPF6_MESSAGE_TYPE_DBDESC: ospf6_dbdesc_recv(&src, &dst, oi, oh); break; case OSPF6_MESSAGE_TYPE_LSREQ: ospf6_lsreq_recv(&src, &dst, oi, oh); break; case OSPF6_MESSAGE_TYPE_LSUPDATE: ospf6_lsupdate_recv(&src, &dst, oi, oh); break; case OSPF6_MESSAGE_TYPE_LSACK: ospf6_lsack_recv(&src, &dst, oi, oh); break; default: assert(0); } return 0; } static void ospf6_send(struct in6_addr *src, struct in6_addr *dst, struct ospf6_interface *oi, struct ospf6_header *oh) { unsigned int len; char srcname[64], dstname[64]; struct iovec iovector[2]; /* initialize */ iovector[0].iov_base = (caddr_t)oh; iovector[0].iov_len = ntohs(oh->length); iovector[1].iov_base = NULL; iovector[1].iov_len = 0; /* fill OSPF header */ oh->version = OSPFV3_VERSION; /* message type must be set before */ /* message length must be set before */ oh->router_id = oi->area->ospf6->router_id; oh->area_id = oi->area->area_id; /* checksum is calculated by kernel */ oh->instance_id = oi->instance_id; oh->reserved = 0; /* Log */ if (IS_OSPF6_DEBUG_MESSAGE(oh->type, SEND)) { inet_ntop(AF_INET6, dst, dstname, sizeof(dstname)); if (src) inet_ntop(AF_INET6, src, srcname, sizeof(srcname)); else memset(srcname, 0, sizeof(srcname)); zlog_debug("%s send on %s", lookup_msg(ospf6_message_type_str, oh->type, NULL), oi->interface->name); zlog_debug(" src: %s", srcname); zlog_debug(" dst: %s", dstname); switch (oh->type) { case OSPF6_MESSAGE_TYPE_HELLO: ospf6_hello_print(oh); break; case OSPF6_MESSAGE_TYPE_DBDESC: ospf6_dbdesc_print(oh); break; case OSPF6_MESSAGE_TYPE_LSREQ: ospf6_lsreq_print(oh); break; case OSPF6_MESSAGE_TYPE_LSUPDATE: ospf6_lsupdate_print(oh); break; case OSPF6_MESSAGE_TYPE_LSACK: ospf6_lsack_print(oh); break; default: zlog_debug("Unknown message"); assert(0); break; } } /* send message */ len = ospf6_sendmsg(src, dst, &oi->interface->ifindex, iovector); if (len != ntohs(oh->length)) flog_err(EC_LIB_DEVELOPMENT, "Could not send entire message"); } static uint32_t ospf6_packet_max(struct ospf6_interface *oi) { assert(oi->ifmtu > sizeof(struct ip6_hdr)); return oi->ifmtu - (sizeof(struct ip6_hdr)); } int ospf6_hello_send(struct thread *thread) { struct ospf6_interface *oi; struct ospf6_header *oh; struct ospf6_hello *hello; uint8_t *p; struct listnode *node, *nnode; struct ospf6_neighbor *on; oi = (struct ospf6_interface *)THREAD_ARG(thread); oi->thread_send_hello = (struct thread *)NULL; if (oi->state <= OSPF6_INTERFACE_DOWN) { if (IS_OSPF6_DEBUG_MESSAGE(OSPF6_MESSAGE_TYPE_HELLO, SEND)) zlog_debug("Unable to send Hello on down interface %s", oi->interface->name); return 0; } if (iobuflen == 0) { zlog_debug("Unable to send Hello on interface %s iobuflen is 0", oi->interface->name); return 0; } /* set next thread */ thread_add_timer(master, ospf6_hello_send, oi, oi->hello_interval, &oi->thread_send_hello); memset(sendbuf, 0, iobuflen); oh = (struct ospf6_header *)sendbuf; hello = (struct ospf6_hello *)((caddr_t)oh + sizeof(struct ospf6_header)); hello->interface_id = htonl(oi->interface->ifindex); hello->priority = oi->priority; hello->options[0] = oi->area->options[0]; hello->options[1] = oi->area->options[1]; hello->options[2] = oi->area->options[2]; hello->hello_interval = htons(oi->hello_interval); hello->dead_interval = htons(oi->dead_interval); hello->drouter = oi->drouter; hello->bdrouter = oi->bdrouter; p = (uint8_t *)((caddr_t)hello + sizeof(struct ospf6_hello)); for (ALL_LIST_ELEMENTS(oi->neighbor_list, node, nnode, on)) { if (on->state < OSPF6_NEIGHBOR_INIT) continue; if (p - sendbuf + sizeof(uint32_t) > ospf6_packet_max(oi)) { if (IS_OSPF6_DEBUG_MESSAGE(OSPF6_MESSAGE_TYPE_HELLO, SEND)) zlog_debug( "sending Hello message: exceeds I/F MTU"); break; } memcpy(p, &on->router_id, sizeof(uint32_t)); p += sizeof(uint32_t); } oh->type = OSPF6_MESSAGE_TYPE_HELLO; oh->length = htons(p - sendbuf); oi->hello_out++; ospf6_send(oi->linklocal_addr, &allspfrouters6, oi, oh); return 0; } int ospf6_dbdesc_send(struct thread *thread) { struct ospf6_neighbor *on; struct ospf6_header *oh; struct ospf6_dbdesc *dbdesc; uint8_t *p; struct ospf6_lsa *lsa; struct in6_addr *dst; on = (struct ospf6_neighbor *)THREAD_ARG(thread); on->thread_send_dbdesc = (struct thread *)NULL; if (on->state < OSPF6_NEIGHBOR_EXSTART) { if (IS_OSPF6_DEBUG_MESSAGE(OSPF6_MESSAGE_TYPE_DBDESC, SEND)) zlog_debug( "Quit to send DbDesc to neighbor %s state %s", on->name, ospf6_neighbor_state_str[on->state]); return 0; } /* set next thread if master */ if (CHECK_FLAG(on->dbdesc_bits, OSPF6_DBDESC_MSBIT)) thread_add_timer(master, ospf6_dbdesc_send, on, on->ospf6_if->rxmt_interval, &on->thread_send_dbdesc); memset(sendbuf, 0, iobuflen); oh = (struct ospf6_header *)sendbuf; dbdesc = (struct ospf6_dbdesc *)((caddr_t)oh + sizeof(struct ospf6_header)); /* if this is initial one, initialize sequence number for DbDesc */ if (CHECK_FLAG(on->dbdesc_bits, OSPF6_DBDESC_IBIT) && (on->dbdesc_seqnum == 0)) { on->dbdesc_seqnum = monotime(NULL); } dbdesc->options[0] = on->ospf6_if->area->options[0]; dbdesc->options[1] = on->ospf6_if->area->options[1]; dbdesc->options[2] = on->ospf6_if->area->options[2]; dbdesc->ifmtu = htons(on->ospf6_if->ifmtu); dbdesc->bits = on->dbdesc_bits; dbdesc->seqnum = htonl(on->dbdesc_seqnum); /* if this is not initial one, set LSA headers in dbdesc */ p = (uint8_t *)((caddr_t)dbdesc + sizeof(struct ospf6_dbdesc)); if (!CHECK_FLAG(on->dbdesc_bits, OSPF6_DBDESC_IBIT)) { for (ALL_LSDB(on->dbdesc_list, lsa)) { ospf6_lsa_age_update_to_send(lsa, on->ospf6_if->transdelay); /* MTU check */ if (p - sendbuf + sizeof(struct ospf6_lsa_header) > ospf6_packet_max(on->ospf6_if)) { ospf6_lsdb_lsa_unlock(lsa); break; } memcpy(p, lsa->header, sizeof(struct ospf6_lsa_header)); p += sizeof(struct ospf6_lsa_header); } } oh->type = OSPF6_MESSAGE_TYPE_DBDESC; oh->length = htons(p - sendbuf); if (on->ospf6_if->state == OSPF6_INTERFACE_POINTTOPOINT) dst = &allspfrouters6; else dst = &on->linklocal_addr; on->ospf6_if->db_desc_out++; ospf6_send(on->ospf6_if->linklocal_addr, dst, on->ospf6_if, oh); return 0; } int ospf6_dbdesc_send_newone(struct thread *thread) { struct ospf6_neighbor *on; struct ospf6_lsa *lsa; unsigned int size = 0; on = (struct ospf6_neighbor *)THREAD_ARG(thread); ospf6_lsdb_remove_all(on->dbdesc_list); /* move LSAs from summary_list to dbdesc_list (within neighbor structure) so that ospf6_send_dbdesc () can send those LSAs */ size = sizeof(struct ospf6_lsa_header) + sizeof(struct ospf6_dbdesc); for (ALL_LSDB(on->summary_list, lsa)) { if (size + sizeof(struct ospf6_lsa_header) > ospf6_packet_max(on->ospf6_if)) { ospf6_lsdb_lsa_unlock(lsa); break; } ospf6_lsdb_add(ospf6_lsa_copy(lsa), on->dbdesc_list); ospf6_lsdb_remove(lsa, on->summary_list); size += sizeof(struct ospf6_lsa_header); } if (on->summary_list->count == 0) UNSET_FLAG(on->dbdesc_bits, OSPF6_DBDESC_MBIT); /* If slave, More bit check must be done here */ if (!CHECK_FLAG(on->dbdesc_bits, OSPF6_DBDESC_MSBIT) && /* Slave */ !CHECK_FLAG(on->dbdesc_last.bits, OSPF6_DBDESC_MBIT) && !CHECK_FLAG(on->dbdesc_bits, OSPF6_DBDESC_MBIT)) thread_add_event(master, exchange_done, on, 0, NULL); thread_execute(master, ospf6_dbdesc_send, on, 0); return 0; } int ospf6_lsreq_send(struct thread *thread) { struct ospf6_neighbor *on; struct ospf6_header *oh; struct ospf6_lsreq_entry *e; uint8_t *p; struct ospf6_lsa *lsa, *last_req; on = (struct ospf6_neighbor *)THREAD_ARG(thread); on->thread_send_lsreq = (struct thread *)NULL; /* LSReq will be sent only in ExStart or Loading */ if (on->state != OSPF6_NEIGHBOR_EXCHANGE && on->state != OSPF6_NEIGHBOR_LOADING) { if (IS_OSPF6_DEBUG_MESSAGE(OSPF6_MESSAGE_TYPE_LSREQ, SEND)) zlog_debug("Quit to send LSReq to neighbor %s state %s", on->name, ospf6_neighbor_state_str[on->state]); return 0; } /* schedule loading_done if request list is empty */ if (on->request_list->count == 0) { thread_add_event(master, loading_done, on, 0, NULL); return 0; } memset(sendbuf, 0, iobuflen); oh = (struct ospf6_header *)sendbuf; last_req = NULL; /* set Request entries in lsreq */ p = (uint8_t *)((caddr_t)oh + sizeof(struct ospf6_header)); for (ALL_LSDB(on->request_list, lsa)) { /* MTU check */ if (p - sendbuf + sizeof(struct ospf6_lsreq_entry) > ospf6_packet_max(on->ospf6_if)) { ospf6_lsdb_lsa_unlock(lsa); break; } e = (struct ospf6_lsreq_entry *)p; e->type = lsa->header->type; e->id = lsa->header->id; e->adv_router = lsa->header->adv_router; p += sizeof(struct ospf6_lsreq_entry); last_req = lsa; } if (last_req != NULL) { if (on->last_ls_req != NULL) { ospf6_lsa_unlock(on->last_ls_req); } ospf6_lsa_lock(last_req); on->last_ls_req = last_req; } oh->type = OSPF6_MESSAGE_TYPE_LSREQ; oh->length = htons(p - sendbuf); on->ospf6_if->ls_req_out++; if (on->ospf6_if->state == OSPF6_INTERFACE_POINTTOPOINT) ospf6_send(on->ospf6_if->linklocal_addr, &allspfrouters6, on->ospf6_if, oh); else ospf6_send(on->ospf6_if->linklocal_addr, &on->linklocal_addr, on->ospf6_if, oh); /* set next thread */ if (on->request_list->count != 0) { on->thread_send_lsreq = NULL; thread_add_timer(master, ospf6_lsreq_send, on, on->ospf6_if->rxmt_interval, &on->thread_send_lsreq); } return 0; } static void ospf6_send_lsupdate(struct ospf6_neighbor *on, struct ospf6_interface *oi, struct ospf6_header *oh) { if (on) { on->ospf6_if->ls_upd_out++; if ((on->ospf6_if->state == OSPF6_INTERFACE_POINTTOPOINT) || (on->ospf6_if->state == OSPF6_INTERFACE_DR) || (on->ospf6_if->state == OSPF6_INTERFACE_BDR)) { ospf6_send(on->ospf6_if->linklocal_addr, &allspfrouters6, on->ospf6_if, oh); } else { ospf6_send(on->ospf6_if->linklocal_addr, &on->linklocal_addr, on->ospf6_if, oh); } } else if (oi) { oi->ls_upd_out++; if ((oi->state == OSPF6_INTERFACE_POINTTOPOINT) || (oi->state == OSPF6_INTERFACE_DR) || (oi->state == OSPF6_INTERFACE_BDR)) { ospf6_send(oi->linklocal_addr, &allspfrouters6, oi, oh); } else { ospf6_send(oi->linklocal_addr, &alldrouters6, oi, oh); } } } int ospf6_lsupdate_send_neighbor(struct thread *thread) { struct ospf6_neighbor *on; struct ospf6_header *oh; struct ospf6_lsupdate *lsupdate; uint8_t *p; int lsa_cnt; struct ospf6_lsa *lsa; on = (struct ospf6_neighbor *)THREAD_ARG(thread); on->thread_send_lsupdate = (struct thread *)NULL; if (IS_OSPF6_DEBUG_MESSAGE(OSPF6_MESSAGE_TYPE_LSUPDATE, SEND)) zlog_debug("LSUpdate to neighbor %s", on->name); if (on->state < OSPF6_NEIGHBOR_EXCHANGE) { if (IS_OSPF6_DEBUG_MESSAGE(OSPF6_MESSAGE_TYPE_LSUPDATE, SEND)) zlog_debug("Quit to send (neighbor state %s)", ospf6_neighbor_state_str[on->state]); return 0; } memset(sendbuf, 0, iobuflen); oh = (struct ospf6_header *)sendbuf; lsupdate = (struct ospf6_lsupdate *)((caddr_t)oh + sizeof(struct ospf6_header)); p = (uint8_t *)((caddr_t)lsupdate + sizeof(struct ospf6_lsupdate)); lsa_cnt = 0; /* lsupdate_list lists those LSA which doesn't need to be retransmitted. remove those from the list */ for (ALL_LSDB(on->lsupdate_list, lsa)) { /* MTU check */ if ((p - sendbuf + (unsigned int)OSPF6_LSA_SIZE(lsa->header)) > ospf6_packet_max(on->ospf6_if)) { if (lsa_cnt) { oh->type = OSPF6_MESSAGE_TYPE_LSUPDATE; oh->length = htons(p - sendbuf); lsupdate->lsa_number = htonl(lsa_cnt); ospf6_send_lsupdate(on, NULL, oh); memset(sendbuf, 0, iobuflen); oh = (struct ospf6_header *)sendbuf; lsupdate = (struct ospf6_lsupdate *)((caddr_t)oh + sizeof(struct ospf6_header)); p = (uint8_t *)((caddr_t)lsupdate + sizeof(struct ospf6_lsupdate)); lsa_cnt = 0; } } ospf6_lsa_age_update_to_send(lsa, on->ospf6_if->transdelay); memcpy(p, lsa->header, OSPF6_LSA_SIZE(lsa->header)); p += OSPF6_LSA_SIZE(lsa->header); lsa_cnt++; assert(lsa->lock == 2); ospf6_lsdb_remove(lsa, on->lsupdate_list); } if (lsa_cnt) { oh->type = OSPF6_MESSAGE_TYPE_LSUPDATE; oh->length = htons(p - sendbuf); lsupdate->lsa_number = htonl(lsa_cnt); ospf6_send_lsupdate(on, NULL, oh); } /* The addresses used for retransmissions are different from those sent the first time and so we need to separate them here. */ memset(sendbuf, 0, iobuflen); oh = (struct ospf6_header *)sendbuf; lsupdate = (struct ospf6_lsupdate *)((caddr_t)oh + sizeof(struct ospf6_header)); p = (uint8_t *)((caddr_t)lsupdate + sizeof(struct ospf6_lsupdate)); lsa_cnt = 0; for (ALL_LSDB(on->retrans_list, lsa)) { /* MTU check */ if ((p - sendbuf + (unsigned int)OSPF6_LSA_SIZE(lsa->header)) > ospf6_packet_max(on->ospf6_if)) { if (lsa_cnt) { oh->type = OSPF6_MESSAGE_TYPE_LSUPDATE; oh->length = htons(p - sendbuf); lsupdate->lsa_number = htonl(lsa_cnt); if (on->ospf6_if->state == OSPF6_INTERFACE_POINTTOPOINT) { ospf6_send(on->ospf6_if->linklocal_addr, &allspfrouters6, on->ospf6_if, oh); } else { ospf6_send(on->ospf6_if->linklocal_addr, &on->linklocal_addr, on->ospf6_if, oh); } memset(sendbuf, 0, iobuflen); oh = (struct ospf6_header *)sendbuf; lsupdate = (struct ospf6_lsupdate *)((caddr_t)oh + sizeof(struct ospf6_header)); p = (uint8_t *)((caddr_t)lsupdate + sizeof(struct ospf6_lsupdate)); lsa_cnt = 0; } } ospf6_lsa_age_update_to_send(lsa, on->ospf6_if->transdelay); memcpy(p, lsa->header, OSPF6_LSA_SIZE(lsa->header)); p += OSPF6_LSA_SIZE(lsa->header); lsa_cnt++; } if (lsa_cnt) { oh->type = OSPF6_MESSAGE_TYPE_LSUPDATE; oh->length = htons(p - sendbuf); lsupdate->lsa_number = htonl(lsa_cnt); if (on->ospf6_if->state == OSPF6_INTERFACE_POINTTOPOINT) ospf6_send(on->ospf6_if->linklocal_addr, &allspfrouters6, on->ospf6_if, oh); else ospf6_send(on->ospf6_if->linklocal_addr, &on->linklocal_addr, on->ospf6_if, oh); } if (on->lsupdate_list->count != 0) { on->thread_send_lsupdate = NULL; thread_add_event(master, ospf6_lsupdate_send_neighbor, on, 0, &on->thread_send_lsupdate); } else if (on->retrans_list->count != 0) { on->thread_send_lsupdate = NULL; thread_add_timer(master, ospf6_lsupdate_send_neighbor, on, on->ospf6_if->rxmt_interval, &on->thread_send_lsupdate); } return 0; } int ospf6_lsupdate_send_neighbor_now(struct ospf6_neighbor *on, struct ospf6_lsa *lsa) { struct ospf6_header *oh; struct ospf6_lsupdate *lsupdate; uint8_t *p; int lsa_cnt = 0; memset(sendbuf, 0, iobuflen); oh = (struct ospf6_header *)sendbuf; lsupdate = (struct ospf6_lsupdate *)((caddr_t)oh + sizeof(struct ospf6_header)); p = (uint8_t *)((caddr_t)lsupdate + sizeof(struct ospf6_lsupdate)); ospf6_lsa_age_update_to_send(lsa, on->ospf6_if->transdelay); memcpy(p, lsa->header, OSPF6_LSA_SIZE(lsa->header)); p += OSPF6_LSA_SIZE(lsa->header); lsa_cnt++; oh->type = OSPF6_MESSAGE_TYPE_LSUPDATE; oh->length = htons(p - sendbuf); lsupdate->lsa_number = htonl(lsa_cnt); if (IS_OSPF6_DEBUG_FLOODING || IS_OSPF6_DEBUG_MESSAGE(OSPF6_MESSAGE_TYPE_LSUPDATE, SEND)) zlog_debug("%s: Send lsupdate with lsa %s (age %u)", __PRETTY_FUNCTION__, lsa->name, ntohs(lsa->header->age)); ospf6_send_lsupdate(on, NULL, oh); return 0; } int ospf6_lsupdate_send_interface(struct thread *thread) { struct ospf6_interface *oi; struct ospf6_header *oh; struct ospf6_lsupdate *lsupdate; uint8_t *p; int lsa_cnt; struct ospf6_lsa *lsa; oi = (struct ospf6_interface *)THREAD_ARG(thread); oi->thread_send_lsupdate = (struct thread *)NULL; if (oi->state <= OSPF6_INTERFACE_WAITING) { if (IS_OSPF6_DEBUG_MESSAGE(OSPF6_MESSAGE_TYPE_LSUPDATE, SEND)) zlog_debug( "Quit to send LSUpdate to interface %s state %s", oi->interface->name, ospf6_interface_state_str[oi->state]); return 0; } /* if we have nothing to send, return */ if (oi->lsupdate_list->count == 0) return 0; memset(sendbuf, 0, iobuflen); oh = (struct ospf6_header *)sendbuf; lsupdate = (struct ospf6_lsupdate *)((caddr_t)oh + sizeof(struct ospf6_header)); p = (uint8_t *)((caddr_t)lsupdate + sizeof(struct ospf6_lsupdate)); lsa_cnt = 0; for (ALL_LSDB(oi->lsupdate_list, lsa)) { /* MTU check */ if ((p - sendbuf + ((unsigned int)OSPF6_LSA_SIZE(lsa->header))) > ospf6_packet_max(oi)) { if (lsa_cnt) { oh->type = OSPF6_MESSAGE_TYPE_LSUPDATE; oh->length = htons(p - sendbuf); lsupdate->lsa_number = htonl(lsa_cnt); ospf6_send_lsupdate(NULL, oi, oh); if (IS_OSPF6_DEBUG_MESSAGE( OSPF6_MESSAGE_TYPE_LSUPDATE, SEND)) zlog_debug("%s: LSUpdate length %d", __PRETTY_FUNCTION__, ntohs(oh->length)); memset(sendbuf, 0, iobuflen); oh = (struct ospf6_header *)sendbuf; lsupdate = (struct ospf6_lsupdate *)((caddr_t)oh + sizeof(struct ospf6_header)); p = (uint8_t *)((caddr_t)lsupdate + sizeof(struct ospf6_lsupdate)); lsa_cnt = 0; } } ospf6_lsa_age_update_to_send(lsa, oi->transdelay); memcpy(p, lsa->header, OSPF6_LSA_SIZE(lsa->header)); p += OSPF6_LSA_SIZE(lsa->header); lsa_cnt++; assert(lsa->lock == 2); ospf6_lsdb_remove(lsa, oi->lsupdate_list); } if (lsa_cnt) { oh->type = OSPF6_MESSAGE_TYPE_LSUPDATE; oh->length = htons(p - sendbuf); lsupdate->lsa_number = htonl(lsa_cnt); ospf6_send_lsupdate(NULL, oi, oh); } if (oi->lsupdate_list->count > 0) { oi->thread_send_lsupdate = NULL; thread_add_event(master, ospf6_lsupdate_send_interface, oi, 0, &oi->thread_send_lsupdate); } return 0; } int ospf6_lsack_send_neighbor(struct thread *thread) { struct ospf6_neighbor *on; struct ospf6_header *oh; uint8_t *p; struct ospf6_lsa *lsa; int lsa_cnt = 0; on = (struct ospf6_neighbor *)THREAD_ARG(thread); on->thread_send_lsack = (struct thread *)NULL; if (on->state < OSPF6_NEIGHBOR_EXCHANGE) { if (IS_OSPF6_DEBUG_MESSAGE(OSPF6_MESSAGE_TYPE_LSACK, SEND)) zlog_debug("Quit to send LSAck to neighbor %s state %s", on->name, ospf6_neighbor_state_str[on->state]); return 0; } /* if we have nothing to send, return */ if (on->lsack_list->count == 0) return 0; memset(sendbuf, 0, iobuflen); oh = (struct ospf6_header *)sendbuf; p = (uint8_t *)((caddr_t)oh + sizeof(struct ospf6_header)); for (ALL_LSDB(on->lsack_list, lsa)) { /* MTU check */ if (p - sendbuf + sizeof(struct ospf6_lsa_header) > ospf6_packet_max(on->ospf6_if)) { /* if we run out of packet size/space here, better to try again soon. */ if (lsa_cnt) { oh->type = OSPF6_MESSAGE_TYPE_LSACK; oh->length = htons(p - sendbuf); on->ospf6_if->ls_ack_out++; ospf6_send(on->ospf6_if->linklocal_addr, &on->linklocal_addr, on->ospf6_if, oh); memset(sendbuf, 0, iobuflen); oh = (struct ospf6_header *)sendbuf; p = (uint8_t *)((caddr_t)oh + sizeof(struct ospf6_header)); lsa_cnt = 0; } } ospf6_lsa_age_update_to_send(lsa, on->ospf6_if->transdelay); memcpy(p, lsa->header, sizeof(struct ospf6_lsa_header)); p += sizeof(struct ospf6_lsa_header); assert(lsa->lock == 2); ospf6_lsdb_remove(lsa, on->lsack_list); lsa_cnt++; } if (lsa_cnt) { oh->type = OSPF6_MESSAGE_TYPE_LSACK; oh->length = htons(p - sendbuf); on->ospf6_if->ls_ack_out++; ospf6_send(on->ospf6_if->linklocal_addr, &on->linklocal_addr, on->ospf6_if, oh); } if (on->lsack_list->count > 0) thread_add_event(master, ospf6_lsack_send_neighbor, on, 0, &on->thread_send_lsack); return 0; } int ospf6_lsack_send_interface(struct thread *thread) { struct ospf6_interface *oi; struct ospf6_header *oh; uint8_t *p; struct ospf6_lsa *lsa; int lsa_cnt = 0; oi = (struct ospf6_interface *)THREAD_ARG(thread); oi->thread_send_lsack = (struct thread *)NULL; if (oi->state <= OSPF6_INTERFACE_WAITING) { if (IS_OSPF6_DEBUG_MESSAGE(OSPF6_MESSAGE_TYPE_LSACK, SEND)) zlog_debug( "Quit to send LSAck to interface %s state %s", oi->interface->name, ospf6_interface_state_str[oi->state]); return 0; } /* if we have nothing to send, return */ if (oi->lsack_list->count == 0) return 0; memset(sendbuf, 0, iobuflen); oh = (struct ospf6_header *)sendbuf; p = (uint8_t *)((caddr_t)oh + sizeof(struct ospf6_header)); for (ALL_LSDB(oi->lsack_list, lsa)) { /* MTU check */ if (p - sendbuf + sizeof(struct ospf6_lsa_header) > ospf6_packet_max(oi)) { /* if we run out of packet size/space here, better to try again soon. */ THREAD_OFF(oi->thread_send_lsack); thread_add_event(master, ospf6_lsack_send_interface, oi, 0, &oi->thread_send_lsack); ospf6_lsdb_lsa_unlock(lsa); break; } ospf6_lsa_age_update_to_send(lsa, oi->transdelay); memcpy(p, lsa->header, sizeof(struct ospf6_lsa_header)); p += sizeof(struct ospf6_lsa_header); assert(lsa->lock == 2); ospf6_lsdb_remove(lsa, oi->lsack_list); lsa_cnt++; } if (lsa_cnt) { oh->type = OSPF6_MESSAGE_TYPE_LSACK; oh->length = htons(p - sendbuf); if ((oi->state == OSPF6_INTERFACE_POINTTOPOINT) || (oi->state == OSPF6_INTERFACE_DR) || (oi->state == OSPF6_INTERFACE_BDR)) ospf6_send(oi->linklocal_addr, &allspfrouters6, oi, oh); else ospf6_send(oi->linklocal_addr, &alldrouters6, oi, oh); } if (oi->lsack_list->count > 0) thread_add_event(master, ospf6_lsack_send_interface, oi, 0, &oi->thread_send_lsack); return 0; } /* Commands */ DEFUN (debug_ospf6_message, debug_ospf6_message_cmd, "debug ospf6 message []", DEBUG_STR OSPF6_STR "Debug OSPFv3 message\n" "Debug Unknown message\n" "Debug Hello message\n" "Debug Database Description message\n" "Debug Link State Request message\n" "Debug Link State Update message\n" "Debug Link State Acknowledgement message\n" "Debug All message\n" "Debug only sending message\n" "Debug only receiving message\n") { int idx_packet = 3; int idx_send_recv = 4; unsigned char level = 0; int type = 0; int i; /* check type */ if (!strncmp(argv[idx_packet]->arg, "u", 1)) type = OSPF6_MESSAGE_TYPE_UNKNOWN; else if (!strncmp(argv[idx_packet]->arg, "h", 1)) type = OSPF6_MESSAGE_TYPE_HELLO; else if (!strncmp(argv[idx_packet]->arg, "d", 1)) type = OSPF6_MESSAGE_TYPE_DBDESC; else if (!strncmp(argv[idx_packet]->arg, "lsr", 3)) type = OSPF6_MESSAGE_TYPE_LSREQ; else if (!strncmp(argv[idx_packet]->arg, "lsu", 3)) type = OSPF6_MESSAGE_TYPE_LSUPDATE; else if (!strncmp(argv[idx_packet]->arg, "lsa", 3)) type = OSPF6_MESSAGE_TYPE_LSACK; else if (!strncmp(argv[idx_packet]->arg, "a", 1)) type = OSPF6_MESSAGE_TYPE_ALL; if (argc == 4) level = OSPF6_DEBUG_MESSAGE_SEND | OSPF6_DEBUG_MESSAGE_RECV; else if (!strncmp(argv[idx_send_recv]->arg, "s", 1)) level = OSPF6_DEBUG_MESSAGE_SEND; else if (!strncmp(argv[idx_send_recv]->arg, "r", 1)) level = OSPF6_DEBUG_MESSAGE_RECV; if (type == OSPF6_MESSAGE_TYPE_ALL) { for (i = 0; i < 6; i++) OSPF6_DEBUG_MESSAGE_ON(i, level); } else OSPF6_DEBUG_MESSAGE_ON(type, level); return CMD_SUCCESS; } DEFUN (no_debug_ospf6_message, no_debug_ospf6_message_cmd, "no debug ospf6 message []", NO_STR DEBUG_STR OSPF6_STR "Debug OSPFv3 message\n" "Debug Unknown message\n" "Debug Hello message\n" "Debug Database Description message\n" "Debug Link State Request message\n" "Debug Link State Update message\n" "Debug Link State Acknowledgement message\n" "Debug All message\n" "Debug only sending message\n" "Debug only receiving message\n") { int idx_packet = 4; int idx_send_recv = 5; unsigned char level = 0; int type = 0; int i; /* check type */ if (!strncmp(argv[idx_packet]->arg, "u", 1)) type = OSPF6_MESSAGE_TYPE_UNKNOWN; else if (!strncmp(argv[idx_packet]->arg, "h", 1)) type = OSPF6_MESSAGE_TYPE_HELLO; else if (!strncmp(argv[idx_packet]->arg, "d", 1)) type = OSPF6_MESSAGE_TYPE_DBDESC; else if (!strncmp(argv[idx_packet]->arg, "lsr", 3)) type = OSPF6_MESSAGE_TYPE_LSREQ; else if (!strncmp(argv[idx_packet]->arg, "lsu", 3)) type = OSPF6_MESSAGE_TYPE_LSUPDATE; else if (!strncmp(argv[idx_packet]->arg, "lsa", 3)) type = OSPF6_MESSAGE_TYPE_LSACK; else if (!strncmp(argv[idx_packet]->arg, "a", 1)) type = OSPF6_MESSAGE_TYPE_ALL; if (argc == 5) level = OSPF6_DEBUG_MESSAGE_SEND | OSPF6_DEBUG_MESSAGE_RECV; else if (!strncmp(argv[idx_send_recv]->arg, "s", 1)) level = OSPF6_DEBUG_MESSAGE_SEND; else if (!strncmp(argv[idx_send_recv]->arg, "r", 1)) level = OSPF6_DEBUG_MESSAGE_RECV; if (type == OSPF6_MESSAGE_TYPE_ALL) { for (i = 0; i < 6; i++) OSPF6_DEBUG_MESSAGE_OFF(i, level); } else OSPF6_DEBUG_MESSAGE_OFF(type, level); return CMD_SUCCESS; } int config_write_ospf6_debug_message(struct vty *vty) { const char *type_str[] = {"unknown", "hello", "dbdesc", "lsreq", "lsupdate", "lsack"}; unsigned char s = 0, r = 0; int i; for (i = 0; i < 6; i++) { if (IS_OSPF6_DEBUG_MESSAGE(i, SEND)) s |= 1 << i; if (IS_OSPF6_DEBUG_MESSAGE(i, RECV)) r |= 1 << i; } if (s == 0x3f && r == 0x3f) { vty_out(vty, "debug ospf6 message all\n"); return 0; } if (s == 0x3f && r == 0) { vty_out(vty, "debug ospf6 message all send\n"); return 0; } else if (s == 0 && r == 0x3f) { vty_out(vty, "debug ospf6 message all recv\n"); return 0; } /* Unknown message is logged by default */ if (!IS_OSPF6_DEBUG_MESSAGE(OSPF6_MESSAGE_TYPE_UNKNOWN, SEND) && !IS_OSPF6_DEBUG_MESSAGE(OSPF6_MESSAGE_TYPE_UNKNOWN, RECV)) vty_out(vty, "no debug ospf6 message unknown\n"); else if (!IS_OSPF6_DEBUG_MESSAGE(OSPF6_MESSAGE_TYPE_UNKNOWN, SEND)) vty_out(vty, "no debug ospf6 message unknown send\n"); else if (!IS_OSPF6_DEBUG_MESSAGE(OSPF6_MESSAGE_TYPE_UNKNOWN, RECV)) vty_out(vty, "no debug ospf6 message unknown recv\n"); for (i = 1; i < 6; i++) { if (IS_OSPF6_DEBUG_MESSAGE(i, SEND) && IS_OSPF6_DEBUG_MESSAGE(i, RECV)) vty_out(vty, "debug ospf6 message %s\n", type_str[i]); else if (IS_OSPF6_DEBUG_MESSAGE(i, SEND)) vty_out(vty, "debug ospf6 message %s send\n", type_str[i]); else if (IS_OSPF6_DEBUG_MESSAGE(i, RECV)) vty_out(vty, "debug ospf6 message %s recv\n", type_str[i]); } return 0; } void install_element_ospf6_debug_message(void) { install_element(ENABLE_NODE, &debug_ospf6_message_cmd); install_element(ENABLE_NODE, &no_debug_ospf6_message_cmd); install_element(CONFIG_NODE, &debug_ospf6_message_cmd); install_element(CONFIG_NODE, &no_debug_ospf6_message_cmd); } frr-7.2.1/ospf6d/ospf6_message.h0000644000000000000000000001075413610377563013336 00000000000000/* * Copyright (C) 1999-2003 Yasuhiro Ohara * * This file is part of GNU Zebra. * * GNU Zebra is free software; you can redistribute it and/or modify it * under the terms of the GNU General Public License as published by the * Free Software Foundation; either version 2, or (at your option) any * later version. * * GNU Zebra 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; see the file COPYING; if not, write to the Free Software * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA */ #ifndef OSPF6_MESSAGE_H #define OSPF6_MESSAGE_H #define OSPF6_MESSAGE_BUFSIZ 4096 /* Debug option */ extern unsigned char conf_debug_ospf6_message[]; #define OSPF6_DEBUG_MESSAGE_SEND 0x01 #define OSPF6_DEBUG_MESSAGE_RECV 0x02 #define OSPF6_DEBUG_MESSAGE_ON(type, level) \ (conf_debug_ospf6_message[type] |= (level)) #define OSPF6_DEBUG_MESSAGE_OFF(type, level) \ (conf_debug_ospf6_message[type] &= ~(level)) #define IS_OSPF6_DEBUG_MESSAGE(t, e) \ (conf_debug_ospf6_message[t] & OSPF6_DEBUG_MESSAGE_##e) /* Type */ #define OSPF6_MESSAGE_TYPE_UNKNOWN 0x0 #define OSPF6_MESSAGE_TYPE_HELLO 0x1 /* Discover/maintain neighbors */ #define OSPF6_MESSAGE_TYPE_DBDESC 0x2 /* Summarize database contents */ #define OSPF6_MESSAGE_TYPE_LSREQ 0x3 /* Database download request */ #define OSPF6_MESSAGE_TYPE_LSUPDATE 0x4 /* Database update */ #define OSPF6_MESSAGE_TYPE_LSACK 0x5 /* Flooding acknowledgment */ #define OSPF6_MESSAGE_TYPE_ALL 0x6 /* For debug option */ /* OSPFv3 packet header */ #define OSPF6_HEADER_SIZE 16U struct ospf6_header { uint8_t version; uint8_t type; uint16_t length; uint32_t router_id; uint32_t area_id; uint16_t checksum; uint8_t instance_id; uint8_t reserved; }; #define OSPF6_MESSAGE_END(H) ((caddr_t) (H) + ntohs ((H)->length)) /* Hello */ #define OSPF6_HELLO_MIN_SIZE 20U struct ospf6_hello { ifindex_t interface_id; uint8_t priority; uint8_t options[3]; uint16_t hello_interval; uint16_t dead_interval; uint32_t drouter; uint32_t bdrouter; /* Followed by Router-IDs */ }; /* Database Description */ #define OSPF6_DB_DESC_MIN_SIZE 12U struct ospf6_dbdesc { uint8_t reserved1; uint8_t options[3]; uint16_t ifmtu; uint8_t reserved2; uint8_t bits; uint32_t seqnum; /* Followed by LSA Headers */ }; #define OSPF6_DBDESC_MSBIT (0x01) /* master/slave bit */ #define OSPF6_DBDESC_MBIT (0x02) /* more bit */ #define OSPF6_DBDESC_IBIT (0x04) /* initial bit */ /* Link State Request */ #define OSPF6_LS_REQ_MIN_SIZE 0U /* It is just a sequence of entries below */ #define OSPF6_LSREQ_LSDESC_FIX_SIZE 12U struct ospf6_lsreq_entry { uint16_t reserved; /* Must Be Zero */ uint16_t type; /* LS type */ uint32_t id; /* Link State ID */ uint32_t adv_router; /* Advertising Router */ }; /* Link State Update */ #define OSPF6_LS_UPD_MIN_SIZE 4U struct ospf6_lsupdate { uint32_t lsa_number; /* Followed by LSAs */ }; /* Link State Acknowledgement */ #define OSPF6_LS_ACK_MIN_SIZE 0U /* It is just a sequence of LSA Headers */ /* Function definition */ extern void ospf6_hello_print(struct ospf6_header *); extern void ospf6_dbdesc_print(struct ospf6_header *); extern void ospf6_lsreq_print(struct ospf6_header *); extern void ospf6_lsupdate_print(struct ospf6_header *); extern void ospf6_lsack_print(struct ospf6_header *); extern int ospf6_iobuf_size(unsigned int size); extern void ospf6_message_terminate(void); extern int ospf6_receive(struct thread *thread); extern int ospf6_hello_send(struct thread *thread); extern int ospf6_dbdesc_send(struct thread *thread); extern int ospf6_dbdesc_send_newone(struct thread *thread); extern int ospf6_lsreq_send(struct thread *thread); extern int ospf6_lsupdate_send_interface(struct thread *thread); extern int ospf6_lsupdate_send_neighbor(struct thread *thread); extern int ospf6_lsack_send_interface(struct thread *thread); extern int ospf6_lsack_send_neighbor(struct thread *thread); extern int config_write_ospf6_debug_message(struct vty *); extern void install_element_ospf6_debug_message(void); #endif /* OSPF6_MESSAGE_H */ frr-7.2.1/ospf6d/ospf6_neighbor.c0000644000000000000000000006603013610377563013500 00000000000000/* * Copyright (C) 2003 Yasuhiro Ohara * * This file is part of GNU Zebra. * * GNU Zebra is free software; you can redistribute it and/or modify it * under the terms of the GNU General Public License as published by the * Free Software Foundation; either version 2, or (at your option) any * later version. * * GNU Zebra 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; see the file COPYING; if not, write to the Free Software * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA */ #include #include "log.h" #include "memory.h" #include "thread.h" #include "linklist.h" #include "vty.h" #include "command.h" #include "ospf6_proto.h" #include "ospf6_lsa.h" #include "ospf6_lsdb.h" #include "ospf6_message.h" #include "ospf6_top.h" #include "ospf6_area.h" #include "ospf6_interface.h" #include "ospf6_neighbor.h" #include "ospf6_intra.h" #include "ospf6_flood.h" #include "ospf6d.h" #include "ospf6_bfd.h" #include "ospf6_abr.h" #include "ospf6_asbr.h" #include "ospf6_lsa.h" #include "ospf6_spf.h" #include "ospf6_zebra.h" DEFINE_HOOK(ospf6_neighbor_change, (struct ospf6_neighbor * on, int state, int next_state), (on, state, next_state)) unsigned char conf_debug_ospf6_neighbor = 0; const char *ospf6_neighbor_state_str[] = { "None", "Down", "Attempt", "Init", "Twoway", "ExStart", "ExChange", "Loading", "Full", NULL}; int ospf6_neighbor_cmp(void *va, void *vb) { struct ospf6_neighbor *ona = (struct ospf6_neighbor *)va; struct ospf6_neighbor *onb = (struct ospf6_neighbor *)vb; if (ona->router_id == onb->router_id) return 0; return (ntohl(ona->router_id) < ntohl(onb->router_id)) ? -1 : 1; } struct ospf6_neighbor *ospf6_neighbor_lookup(uint32_t router_id, struct ospf6_interface *oi) { struct listnode *n; struct ospf6_neighbor *on; for (ALL_LIST_ELEMENTS_RO(oi->neighbor_list, n, on)) if (on->router_id == router_id) return on; return (struct ospf6_neighbor *)NULL; } /* create ospf6_neighbor */ struct ospf6_neighbor *ospf6_neighbor_create(uint32_t router_id, struct ospf6_interface *oi) { struct ospf6_neighbor *on; char buf[16]; on = XCALLOC(MTYPE_OSPF6_NEIGHBOR, sizeof(struct ospf6_neighbor)); inet_ntop(AF_INET, &router_id, buf, sizeof(buf)); snprintf(on->name, sizeof(on->name), "%s%%%s", buf, oi->interface->name); on->ospf6_if = oi; on->state = OSPF6_NEIGHBOR_DOWN; on->state_change = 0; monotime(&on->last_changed); on->router_id = router_id; on->summary_list = ospf6_lsdb_create(on); on->request_list = ospf6_lsdb_create(on); on->retrans_list = ospf6_lsdb_create(on); on->dbdesc_list = ospf6_lsdb_create(on); on->lsupdate_list = ospf6_lsdb_create(on); on->lsack_list = ospf6_lsdb_create(on); listnode_add_sort(oi->neighbor_list, on); ospf6_bfd_info_nbr_create(oi, on); return on; } void ospf6_neighbor_delete(struct ospf6_neighbor *on) { struct ospf6_lsa *lsa; ospf6_lsdb_remove_all(on->summary_list); ospf6_lsdb_remove_all(on->request_list); for (ALL_LSDB(on->retrans_list, lsa)) { ospf6_decrement_retrans_count(lsa); ospf6_lsdb_remove(lsa, on->retrans_list); } ospf6_lsdb_remove_all(on->dbdesc_list); ospf6_lsdb_remove_all(on->lsupdate_list); ospf6_lsdb_remove_all(on->lsack_list); ospf6_lsdb_delete(on->summary_list); ospf6_lsdb_delete(on->request_list); ospf6_lsdb_delete(on->retrans_list); ospf6_lsdb_delete(on->dbdesc_list); ospf6_lsdb_delete(on->lsupdate_list); ospf6_lsdb_delete(on->lsack_list); THREAD_OFF(on->inactivity_timer); THREAD_OFF(on->thread_send_dbdesc); THREAD_OFF(on->thread_send_lsreq); THREAD_OFF(on->thread_send_lsupdate); THREAD_OFF(on->thread_send_lsack); ospf6_bfd_reg_dereg_nbr(on, ZEBRA_BFD_DEST_DEREGISTER); XFREE(MTYPE_OSPF6_NEIGHBOR, on); } static void ospf6_neighbor_state_change(uint8_t next_state, struct ospf6_neighbor *on, int event) { uint8_t prev_state; prev_state = on->state; on->state = next_state; if (prev_state == next_state) return; on->state_change++; monotime(&on->last_changed); /* log */ if (IS_OSPF6_DEBUG_NEIGHBOR(STATE)) { zlog_debug("Neighbor state change %s: [%s]->[%s] (%s)", on->name, ospf6_neighbor_state_str[prev_state], ospf6_neighbor_state_str[next_state], ospf6_neighbor_event_string(event)); } /* Optionally notify about adjacency changes */ if (CHECK_FLAG(on->ospf6_if->area->ospf6->config_flags, OSPF6_LOG_ADJACENCY_CHANGES) && (CHECK_FLAG(on->ospf6_if->area->ospf6->config_flags, OSPF6_LOG_ADJACENCY_DETAIL) || (next_state == OSPF6_NEIGHBOR_FULL) || (next_state < prev_state))) zlog_notice("AdjChg: Nbr %s: %s -> %s (%s)", on->name, ospf6_neighbor_state_str[prev_state], ospf6_neighbor_state_str[next_state], ospf6_neighbor_event_string(event)); if (prev_state == OSPF6_NEIGHBOR_FULL || next_state == OSPF6_NEIGHBOR_FULL) { OSPF6_ROUTER_LSA_SCHEDULE(on->ospf6_if->area); if (on->ospf6_if->state == OSPF6_INTERFACE_DR) { OSPF6_NETWORK_LSA_SCHEDULE(on->ospf6_if); OSPF6_INTRA_PREFIX_LSA_SCHEDULE_TRANSIT(on->ospf6_if); } if (next_state == OSPF6_NEIGHBOR_FULL) on->ospf6_if->area->intra_prefix_originate = 1; OSPF6_INTRA_PREFIX_LSA_SCHEDULE_STUB(on->ospf6_if->area); if ((prev_state == OSPF6_NEIGHBOR_LOADING || prev_state == OSPF6_NEIGHBOR_EXCHANGE) && next_state == OSPF6_NEIGHBOR_FULL) { OSPF6_AS_EXTERN_LSA_SCHEDULE(on->ospf6_if); on->ospf6_if->area->full_nbrs++; } if (prev_state == OSPF6_NEIGHBOR_FULL) on->ospf6_if->area->full_nbrs--; } if ((prev_state == OSPF6_NEIGHBOR_EXCHANGE || prev_state == OSPF6_NEIGHBOR_LOADING) && (next_state != OSPF6_NEIGHBOR_EXCHANGE && next_state != OSPF6_NEIGHBOR_LOADING)) ospf6_maxage_remove(on->ospf6_if->area->ospf6); hook_call(ospf6_neighbor_change, on, next_state, prev_state); ospf6_bfd_trigger_event(on, prev_state, next_state); } /* RFC2328 section 10.4 */ static int need_adjacency(struct ospf6_neighbor *on) { if (on->ospf6_if->state == OSPF6_INTERFACE_POINTTOPOINT || on->ospf6_if->state == OSPF6_INTERFACE_DR || on->ospf6_if->state == OSPF6_INTERFACE_BDR) return 1; if (on->ospf6_if->drouter == on->router_id || on->ospf6_if->bdrouter == on->router_id) return 1; return 0; } int hello_received(struct thread *thread) { struct ospf6_neighbor *on; on = (struct ospf6_neighbor *)THREAD_ARG(thread); assert(on); if (IS_OSPF6_DEBUG_NEIGHBOR(EVENT)) zlog_debug("Neighbor Event %s: *HelloReceived*", on->name); /* reset Inactivity Timer */ THREAD_OFF(on->inactivity_timer); on->inactivity_timer = NULL; thread_add_timer(master, inactivity_timer, on, on->ospf6_if->dead_interval, &on->inactivity_timer); if (on->state <= OSPF6_NEIGHBOR_DOWN) ospf6_neighbor_state_change(OSPF6_NEIGHBOR_INIT, on, OSPF6_NEIGHBOR_EVENT_HELLO_RCVD); return 0; } int twoway_received(struct thread *thread) { struct ospf6_neighbor *on; on = (struct ospf6_neighbor *)THREAD_ARG(thread); assert(on); if (on->state > OSPF6_NEIGHBOR_INIT) return 0; if (IS_OSPF6_DEBUG_NEIGHBOR(EVENT)) zlog_debug("Neighbor Event %s: *2Way-Received*", on->name); thread_add_event(master, neighbor_change, on->ospf6_if, 0, NULL); if (!need_adjacency(on)) { ospf6_neighbor_state_change(OSPF6_NEIGHBOR_TWOWAY, on, OSPF6_NEIGHBOR_EVENT_TWOWAY_RCVD); return 0; } ospf6_neighbor_state_change(OSPF6_NEIGHBOR_EXSTART, on, OSPF6_NEIGHBOR_EVENT_TWOWAY_RCVD); SET_FLAG(on->dbdesc_bits, OSPF6_DBDESC_MSBIT); SET_FLAG(on->dbdesc_bits, OSPF6_DBDESC_MBIT); SET_FLAG(on->dbdesc_bits, OSPF6_DBDESC_IBIT); THREAD_OFF(on->thread_send_dbdesc); on->thread_send_dbdesc = NULL; thread_add_event(master, ospf6_dbdesc_send, on, 0, &on->thread_send_dbdesc); return 0; } int negotiation_done(struct thread *thread) { struct ospf6_neighbor *on; struct ospf6_lsa *lsa; on = (struct ospf6_neighbor *)THREAD_ARG(thread); assert(on); if (on->state != OSPF6_NEIGHBOR_EXSTART) return 0; if (IS_OSPF6_DEBUG_NEIGHBOR(EVENT)) zlog_debug("Neighbor Event %s: *NegotiationDone*", on->name); /* clear ls-list */ ospf6_lsdb_remove_all(on->summary_list); ospf6_lsdb_remove_all(on->request_list); for (ALL_LSDB(on->retrans_list, lsa)) { ospf6_decrement_retrans_count(lsa); ospf6_lsdb_remove(lsa, on->retrans_list); } /* Interface scoped LSAs */ for (ALL_LSDB(on->ospf6_if->lsdb, lsa)) { if (OSPF6_LSA_IS_MAXAGE(lsa)) { ospf6_increment_retrans_count(lsa); ospf6_lsdb_add(ospf6_lsa_copy(lsa), on->retrans_list); } else ospf6_lsdb_add(ospf6_lsa_copy(lsa), on->summary_list); } /* Area scoped LSAs */ for (ALL_LSDB(on->ospf6_if->area->lsdb, lsa)) { if (OSPF6_LSA_IS_MAXAGE(lsa)) { ospf6_increment_retrans_count(lsa); ospf6_lsdb_add(ospf6_lsa_copy(lsa), on->retrans_list); } else ospf6_lsdb_add(ospf6_lsa_copy(lsa), on->summary_list); } /* AS scoped LSAs */ for (ALL_LSDB(on->ospf6_if->area->ospf6->lsdb, lsa)) { if (OSPF6_LSA_IS_MAXAGE(lsa)) { ospf6_increment_retrans_count(lsa); ospf6_lsdb_add(ospf6_lsa_copy(lsa), on->retrans_list); } else ospf6_lsdb_add(ospf6_lsa_copy(lsa), on->summary_list); } UNSET_FLAG(on->dbdesc_bits, OSPF6_DBDESC_IBIT); ospf6_neighbor_state_change(OSPF6_NEIGHBOR_EXCHANGE, on, OSPF6_NEIGHBOR_EVENT_NEGOTIATION_DONE); return 0; } int exchange_done(struct thread *thread) { struct ospf6_neighbor *on; on = (struct ospf6_neighbor *)THREAD_ARG(thread); assert(on); if (on->state != OSPF6_NEIGHBOR_EXCHANGE) return 0; if (IS_OSPF6_DEBUG_NEIGHBOR(EVENT)) zlog_debug("Neighbor Event %s: *ExchangeDone*", on->name); THREAD_OFF(on->thread_send_dbdesc); ospf6_lsdb_remove_all(on->dbdesc_list); /* XXX thread_add_timer (master, ospf6_neighbor_last_dbdesc_release, on, on->ospf6_if->dead_interval); */ if (on->request_list->count == 0) ospf6_neighbor_state_change(OSPF6_NEIGHBOR_FULL, on, OSPF6_NEIGHBOR_EVENT_EXCHANGE_DONE); else { ospf6_neighbor_state_change(OSPF6_NEIGHBOR_LOADING, on, OSPF6_NEIGHBOR_EVENT_EXCHANGE_DONE); thread_add_event(master, ospf6_lsreq_send, on, 0, &on->thread_send_lsreq); } return 0; } /* Check loading state. */ void ospf6_check_nbr_loading(struct ospf6_neighbor *on) { /* RFC2328 Section 10.9: When the neighbor responds to these requests with the proper Link State Update packet(s), the Link state request list is truncated and a new Link State Request packet is sent. */ if ((on->state == OSPF6_NEIGHBOR_LOADING) || (on->state == OSPF6_NEIGHBOR_EXCHANGE)) { if (on->request_list->count == 0) thread_add_event(master, loading_done, on, 0, NULL); else if (on->last_ls_req == NULL) { if (on->thread_send_lsreq != NULL) THREAD_OFF(on->thread_send_lsreq); on->thread_send_lsreq = NULL; thread_add_event(master, ospf6_lsreq_send, on, 0, &on->thread_send_lsreq); } } } int loading_done(struct thread *thread) { struct ospf6_neighbor *on; on = (struct ospf6_neighbor *)THREAD_ARG(thread); assert(on); if (on->state != OSPF6_NEIGHBOR_LOADING) return 0; if (IS_OSPF6_DEBUG_NEIGHBOR(EVENT)) zlog_debug("Neighbor Event %s: *LoadingDone*", on->name); assert(on->request_list->count == 0); ospf6_neighbor_state_change(OSPF6_NEIGHBOR_FULL, on, OSPF6_NEIGHBOR_EVENT_LOADING_DONE); return 0; } int adj_ok(struct thread *thread) { struct ospf6_neighbor *on; struct ospf6_lsa *lsa; on = (struct ospf6_neighbor *)THREAD_ARG(thread); assert(on); if (IS_OSPF6_DEBUG_NEIGHBOR(EVENT)) zlog_debug("Neighbor Event %s: *AdjOK?*", on->name); if (on->state == OSPF6_NEIGHBOR_TWOWAY && need_adjacency(on)) { ospf6_neighbor_state_change(OSPF6_NEIGHBOR_EXSTART, on, OSPF6_NEIGHBOR_EVENT_ADJ_OK); SET_FLAG(on->dbdesc_bits, OSPF6_DBDESC_MSBIT); SET_FLAG(on->dbdesc_bits, OSPF6_DBDESC_MBIT); SET_FLAG(on->dbdesc_bits, OSPF6_DBDESC_IBIT); THREAD_OFF(on->thread_send_dbdesc); on->thread_send_dbdesc = NULL; thread_add_event(master, ospf6_dbdesc_send, on, 0, &on->thread_send_dbdesc); } else if (on->state >= OSPF6_NEIGHBOR_EXSTART && !need_adjacency(on)) { ospf6_neighbor_state_change(OSPF6_NEIGHBOR_TWOWAY, on, OSPF6_NEIGHBOR_EVENT_ADJ_OK); ospf6_lsdb_remove_all(on->summary_list); ospf6_lsdb_remove_all(on->request_list); for (ALL_LSDB(on->retrans_list, lsa)) { ospf6_decrement_retrans_count(lsa); ospf6_lsdb_remove(lsa, on->retrans_list); } } return 0; } int seqnumber_mismatch(struct thread *thread) { struct ospf6_neighbor *on; struct ospf6_lsa *lsa; on = (struct ospf6_neighbor *)THREAD_ARG(thread); assert(on); if (on->state < OSPF6_NEIGHBOR_EXCHANGE) return 0; if (IS_OSPF6_DEBUG_NEIGHBOR(EVENT)) zlog_debug("Neighbor Event %s: *SeqNumberMismatch*", on->name); ospf6_neighbor_state_change(OSPF6_NEIGHBOR_EXSTART, on, OSPF6_NEIGHBOR_EVENT_SEQNUMBER_MISMATCH); SET_FLAG(on->dbdesc_bits, OSPF6_DBDESC_MSBIT); SET_FLAG(on->dbdesc_bits, OSPF6_DBDESC_MBIT); SET_FLAG(on->dbdesc_bits, OSPF6_DBDESC_IBIT); ospf6_lsdb_remove_all(on->summary_list); ospf6_lsdb_remove_all(on->request_list); for (ALL_LSDB(on->retrans_list, lsa)) { ospf6_decrement_retrans_count(lsa); ospf6_lsdb_remove(lsa, on->retrans_list); } THREAD_OFF(on->thread_send_dbdesc); on->dbdesc_seqnum++; /* Incr seqnum as per RFC2328, sec 10.3 */ on->thread_send_dbdesc = NULL; thread_add_event(master, ospf6_dbdesc_send, on, 0, &on->thread_send_dbdesc); return 0; } int bad_lsreq(struct thread *thread) { struct ospf6_neighbor *on; struct ospf6_lsa *lsa; on = (struct ospf6_neighbor *)THREAD_ARG(thread); assert(on); if (on->state < OSPF6_NEIGHBOR_EXCHANGE) return 0; if (IS_OSPF6_DEBUG_NEIGHBOR(EVENT)) zlog_debug("Neighbor Event %s: *BadLSReq*", on->name); ospf6_neighbor_state_change(OSPF6_NEIGHBOR_EXSTART, on, OSPF6_NEIGHBOR_EVENT_BAD_LSREQ); SET_FLAG(on->dbdesc_bits, OSPF6_DBDESC_MSBIT); SET_FLAG(on->dbdesc_bits, OSPF6_DBDESC_MBIT); SET_FLAG(on->dbdesc_bits, OSPF6_DBDESC_IBIT); ospf6_lsdb_remove_all(on->summary_list); ospf6_lsdb_remove_all(on->request_list); for (ALL_LSDB(on->retrans_list, lsa)) { ospf6_decrement_retrans_count(lsa); ospf6_lsdb_remove(lsa, on->retrans_list); } THREAD_OFF(on->thread_send_dbdesc); on->dbdesc_seqnum++; /* Incr seqnum as per RFC2328, sec 10.3 */ on->thread_send_dbdesc = NULL; thread_add_event(master, ospf6_dbdesc_send, on, 0, &on->thread_send_dbdesc); return 0; } int oneway_received(struct thread *thread) { struct ospf6_neighbor *on; struct ospf6_lsa *lsa; on = (struct ospf6_neighbor *)THREAD_ARG(thread); assert(on); if (on->state < OSPF6_NEIGHBOR_TWOWAY) return 0; if (IS_OSPF6_DEBUG_NEIGHBOR(EVENT)) zlog_debug("Neighbor Event %s: *1Way-Received*", on->name); ospf6_neighbor_state_change(OSPF6_NEIGHBOR_INIT, on, OSPF6_NEIGHBOR_EVENT_ONEWAY_RCVD); thread_add_event(master, neighbor_change, on->ospf6_if, 0, NULL); ospf6_lsdb_remove_all(on->summary_list); ospf6_lsdb_remove_all(on->request_list); for (ALL_LSDB(on->retrans_list, lsa)) { ospf6_decrement_retrans_count(lsa); ospf6_lsdb_remove(lsa, on->retrans_list); } THREAD_OFF(on->thread_send_dbdesc); THREAD_OFF(on->thread_send_lsreq); THREAD_OFF(on->thread_send_lsupdate); THREAD_OFF(on->thread_send_lsack); return 0; } int inactivity_timer(struct thread *thread) { struct ospf6_neighbor *on; on = (struct ospf6_neighbor *)THREAD_ARG(thread); assert(on); if (IS_OSPF6_DEBUG_NEIGHBOR(EVENT)) zlog_debug("Neighbor Event %s: *InactivityTimer*", on->name); on->inactivity_timer = NULL; on->drouter = on->prev_drouter = 0; on->bdrouter = on->prev_bdrouter = 0; ospf6_neighbor_state_change(OSPF6_NEIGHBOR_DOWN, on, OSPF6_NEIGHBOR_EVENT_INACTIVITY_TIMER); thread_add_event(master, neighbor_change, on->ospf6_if, 0, NULL); listnode_delete(on->ospf6_if->neighbor_list, on); ospf6_neighbor_delete(on); return 0; } /* vty functions */ /* show neighbor structure */ static void ospf6_neighbor_show(struct vty *vty, struct ospf6_neighbor *on) { char router_id[16]; char duration[64]; struct timeval res; char nstate[16]; char deadtime[64]; long h, m, s; /* Router-ID (Name) */ inet_ntop(AF_INET, &on->router_id, router_id, sizeof(router_id)); #ifdef HAVE_GETNAMEINFO { } #endif /*HAVE_GETNAMEINFO*/ /* Dead time */ h = m = s = 0; if (on->inactivity_timer) { s = monotime_until(&on->inactivity_timer->u.sands, NULL) / 1000000LL; h = s / 3600; s -= h * 3600; m = s / 60; s -= m * 60; } snprintf(deadtime, sizeof(deadtime), "%02ld:%02ld:%02ld", h, m, s); /* Neighbor State */ if (if_is_pointopoint(on->ospf6_if->interface)) snprintf(nstate, sizeof(nstate), "PointToPoint"); else { if (on->router_id == on->drouter) snprintf(nstate, sizeof(nstate), "DR"); else if (on->router_id == on->bdrouter) snprintf(nstate, sizeof(nstate), "BDR"); else snprintf(nstate, sizeof(nstate), "DROther"); } /* Duration */ monotime_since(&on->last_changed, &res); timerstring(&res, duration, sizeof(duration)); /* vty_out (vty, "%-15s %3d %11s %6s/%-12s %11s %s[%s]\n", "Neighbor ID", "Pri", "DeadTime", "State", "", "Duration", "I/F", "State"); */ vty_out(vty, "%-15s %3d %11s %8s/%-12s %11s %s[%s]\n", router_id, on->priority, deadtime, ospf6_neighbor_state_str[on->state], nstate, duration, on->ospf6_if->interface->name, ospf6_interface_state_str[on->ospf6_if->state]); } static void ospf6_neighbor_show_drchoice(struct vty *vty, struct ospf6_neighbor *on) { char router_id[16]; char drouter[16], bdrouter[16]; char duration[64]; struct timeval now, res; /* vty_out (vty, "%-15s %6s/%-11s %-15s %-15s %s[%s]\n", "RouterID", "State", "Duration", "DR", "BDR", "I/F", "State"); */ inet_ntop(AF_INET, &on->router_id, router_id, sizeof(router_id)); inet_ntop(AF_INET, &on->drouter, drouter, sizeof(drouter)); inet_ntop(AF_INET, &on->bdrouter, bdrouter, sizeof(bdrouter)); monotime(&now); timersub(&now, &on->last_changed, &res); timerstring(&res, duration, sizeof(duration)); vty_out(vty, "%-15s %8s/%-11s %-15s %-15s %s[%s]\n", router_id, ospf6_neighbor_state_str[on->state], duration, drouter, bdrouter, on->ospf6_if->interface->name, ospf6_interface_state_str[on->ospf6_if->state]); } static void ospf6_neighbor_show_detail(struct vty *vty, struct ospf6_neighbor *on) { char drouter[16], bdrouter[16]; char linklocal_addr[64], duration[32]; struct timeval now, res; struct ospf6_lsa *lsa; inet_ntop(AF_INET6, &on->linklocal_addr, linklocal_addr, sizeof(linklocal_addr)); inet_ntop(AF_INET, &on->drouter, drouter, sizeof(drouter)); inet_ntop(AF_INET, &on->bdrouter, bdrouter, sizeof(bdrouter)); monotime(&now); timersub(&now, &on->last_changed, &res); timerstring(&res, duration, sizeof(duration)); vty_out(vty, " Neighbor %s\n", on->name); vty_out(vty, " Area %s via interface %s (ifindex %d)\n", on->ospf6_if->area->name, on->ospf6_if->interface->name, on->ospf6_if->interface->ifindex); vty_out(vty, " His IfIndex: %d Link-local address: %s\n", on->ifindex, linklocal_addr); vty_out(vty, " State %s for a duration of %s\n", ospf6_neighbor_state_str[on->state], duration); vty_out(vty, " His choice of DR/BDR %s/%s, Priority %d\n", drouter, bdrouter, on->priority); vty_out(vty, " DbDesc status: %s%s%s SeqNum: %#lx\n", (CHECK_FLAG(on->dbdesc_bits, OSPF6_DBDESC_IBIT) ? "Initial " : ""), (CHECK_FLAG(on->dbdesc_bits, OSPF6_DBDESC_MBIT) ? "More " : ""), (CHECK_FLAG(on->dbdesc_bits, OSPF6_DBDESC_MSBIT) ? "Master" : "Slave"), (unsigned long)ntohl(on->dbdesc_seqnum)); vty_out(vty, " Summary-List: %d LSAs\n", on->summary_list->count); for (ALL_LSDB(on->summary_list, lsa)) vty_out(vty, " %s\n", lsa->name); vty_out(vty, " Request-List: %d LSAs\n", on->request_list->count); for (ALL_LSDB(on->request_list, lsa)) vty_out(vty, " %s\n", lsa->name); vty_out(vty, " Retrans-List: %d LSAs\n", on->retrans_list->count); for (ALL_LSDB(on->retrans_list, lsa)) vty_out(vty, " %s\n", lsa->name); timerclear(&res); if (on->thread_send_dbdesc) timersub(&on->thread_send_dbdesc->u.sands, &now, &res); timerstring(&res, duration, sizeof(duration)); vty_out(vty, " %d Pending LSAs for DbDesc in Time %s [thread %s]\n", on->dbdesc_list->count, duration, (on->thread_send_dbdesc ? "on" : "off")); for (ALL_LSDB(on->dbdesc_list, lsa)) vty_out(vty, " %s\n", lsa->name); timerclear(&res); if (on->thread_send_lsreq) timersub(&on->thread_send_lsreq->u.sands, &now, &res); timerstring(&res, duration, sizeof(duration)); vty_out(vty, " %d Pending LSAs for LSReq in Time %s [thread %s]\n", on->request_list->count, duration, (on->thread_send_lsreq ? "on" : "off")); for (ALL_LSDB(on->request_list, lsa)) vty_out(vty, " %s\n", lsa->name); timerclear(&res); if (on->thread_send_lsupdate) timersub(&on->thread_send_lsupdate->u.sands, &now, &res); timerstring(&res, duration, sizeof(duration)); vty_out(vty, " %d Pending LSAs for LSUpdate in Time %s [thread %s]\n", on->lsupdate_list->count, duration, (on->thread_send_lsupdate ? "on" : "off")); for (ALL_LSDB(on->lsupdate_list, lsa)) vty_out(vty, " %s\n", lsa->name); timerclear(&res); if (on->thread_send_lsack) timersub(&on->thread_send_lsack->u.sands, &now, &res); timerstring(&res, duration, sizeof(duration)); vty_out(vty, " %d Pending LSAs for LSAck in Time %s [thread %s]\n", on->lsack_list->count, duration, (on->thread_send_lsack ? "on" : "off")); for (ALL_LSDB(on->lsack_list, lsa)) vty_out(vty, " %s\n", lsa->name); ospf6_bfd_show_info(vty, on->bfd_info, 0); } DEFUN (show_ipv6_ospf6_neighbor, show_ipv6_ospf6_neighbor_cmd, "show ipv6 ospf6 neighbor []", SHOW_STR IP6_STR OSPF6_STR "Neighbor list\n" "Display details\n" "Display DR choices\n") { int idx_type = 4; struct ospf6_neighbor *on; struct ospf6_interface *oi; struct ospf6_area *oa; struct listnode *i, *j, *k; void (*showfunc)(struct vty *, struct ospf6_neighbor *); OSPF6_CMD_CHECK_RUNNING(); showfunc = ospf6_neighbor_show; if (argc == 5) { if (!strncmp(argv[idx_type]->arg, "de", 2)) showfunc = ospf6_neighbor_show_detail; else if (!strncmp(argv[idx_type]->arg, "dr", 2)) showfunc = ospf6_neighbor_show_drchoice; } if (showfunc == ospf6_neighbor_show) vty_out(vty, "%-15s %3s %11s %8s/%-12s %11s %s[%s]\n", "Neighbor ID", "Pri", "DeadTime", "State", "IfState", "Duration", "I/F", "State"); else if (showfunc == ospf6_neighbor_show_drchoice) vty_out(vty, "%-15s %8s/%-11s %-15s %-15s %s[%s]\n", "RouterID", "State", "Duration", "DR", "BDR", "I/F", "State"); for (ALL_LIST_ELEMENTS_RO(ospf6->area_list, i, oa)) for (ALL_LIST_ELEMENTS_RO(oa->if_list, j, oi)) for (ALL_LIST_ELEMENTS_RO(oi->neighbor_list, k, on)) (*showfunc)(vty, on); return CMD_SUCCESS; } DEFUN (show_ipv6_ospf6_neighbor_one, show_ipv6_ospf6_neighbor_one_cmd, "show ipv6 ospf6 neighbor A.B.C.D", SHOW_STR IP6_STR OSPF6_STR "Neighbor list\n" "Specify Router-ID as IPv4 address notation\n" ) { int idx_ipv4 = 4; struct ospf6_neighbor *on; struct ospf6_interface *oi; struct ospf6_area *oa; struct listnode *i, *j, *k; void (*showfunc)(struct vty *, struct ospf6_neighbor *); uint32_t router_id; OSPF6_CMD_CHECK_RUNNING(); showfunc = ospf6_neighbor_show_detail; if ((inet_pton(AF_INET, argv[idx_ipv4]->arg, &router_id)) != 1) { vty_out(vty, "Router-ID is not parsable: %s\n", argv[idx_ipv4]->arg); return CMD_SUCCESS; } for (ALL_LIST_ELEMENTS_RO(ospf6->area_list, i, oa)) for (ALL_LIST_ELEMENTS_RO(oa->if_list, j, oi)) for (ALL_LIST_ELEMENTS_RO(oi->neighbor_list, k, on)) (*showfunc)(vty, on); return CMD_SUCCESS; } void ospf6_neighbor_init(void) { install_element(VIEW_NODE, &show_ipv6_ospf6_neighbor_cmd); install_element(VIEW_NODE, &show_ipv6_ospf6_neighbor_one_cmd); } DEFUN (debug_ospf6_neighbor, debug_ospf6_neighbor_cmd, "debug ospf6 neighbor []", DEBUG_STR OSPF6_STR "Debug OSPFv3 Neighbor\n" "Debug OSPFv3 Neighbor State Change\n" "Debug OSPFv3 Neighbor Event\n") { int idx_type = 3; unsigned char level = 0; if (argc == 4) { if (!strncmp(argv[idx_type]->arg, "s", 1)) level = OSPF6_DEBUG_NEIGHBOR_STATE; else if (!strncmp(argv[idx_type]->arg, "e", 1)) level = OSPF6_DEBUG_NEIGHBOR_EVENT; } else level = OSPF6_DEBUG_NEIGHBOR_STATE | OSPF6_DEBUG_NEIGHBOR_EVENT; OSPF6_DEBUG_NEIGHBOR_ON(level); return CMD_SUCCESS; } DEFUN (no_debug_ospf6_neighbor, no_debug_ospf6_neighbor_cmd, "no debug ospf6 neighbor []", NO_STR DEBUG_STR OSPF6_STR "Debug OSPFv3 Neighbor\n" "Debug OSPFv3 Neighbor State Change\n" "Debug OSPFv3 Neighbor Event\n") { int idx_type = 4; unsigned char level = 0; if (argc == 5) { if (!strncmp(argv[idx_type]->arg, "s", 1)) level = OSPF6_DEBUG_NEIGHBOR_STATE; if (!strncmp(argv[idx_type]->arg, "e", 1)) level = OSPF6_DEBUG_NEIGHBOR_EVENT; } else level = OSPF6_DEBUG_NEIGHBOR_STATE | OSPF6_DEBUG_NEIGHBOR_EVENT; OSPF6_DEBUG_NEIGHBOR_OFF(level); return CMD_SUCCESS; } DEFUN (no_debug_ospf6, no_debug_ospf6_cmd, "no debug ospf6", NO_STR DEBUG_STR OSPF6_STR) { unsigned int i; struct ospf6_lsa_handler *handler = NULL; OSPF6_DEBUG_ABR_OFF(); OSPF6_DEBUG_ASBR_OFF(); OSPF6_DEBUG_BROUTER_OFF(); OSPF6_DEBUG_BROUTER_SPECIFIC_ROUTER_OFF(); OSPF6_DEBUG_BROUTER_SPECIFIC_AREA_OFF(); OSPF6_DEBUG_FLOODING_OFF(); OSPF6_DEBUG_INTERFACE_OFF(); for (i = 0; i < vector_active(ospf6_lsa_handler_vector); i++) { handler = vector_slot(ospf6_lsa_handler_vector, i); if (handler != NULL) { UNSET_FLAG(handler->lh_debug, OSPF6_LSA_DEBUG); } } for (i = 0; i < 6; i++) OSPF6_DEBUG_MESSAGE_OFF(i, OSPF6_DEBUG_NEIGHBOR_STATE | OSPF6_DEBUG_NEIGHBOR_EVENT); OSPF6_DEBUG_NEIGHBOR_OFF(OSPF6_DEBUG_NEIGHBOR_STATE | OSPF6_DEBUG_NEIGHBOR_EVENT); OSPF6_DEBUG_ROUTE_OFF(OSPF6_DEBUG_ROUTE_TABLE); OSPF6_DEBUG_ROUTE_OFF(OSPF6_DEBUG_ROUTE_INTRA); OSPF6_DEBUG_ROUTE_OFF(OSPF6_DEBUG_ROUTE_INTER); OSPF6_DEBUG_ROUTE_OFF(OSPF6_DEBUG_ROUTE_MEMORY); OSPF6_DEBUG_SPF_OFF(OSPF6_DEBUG_SPF_PROCESS); OSPF6_DEBUG_SPF_OFF(OSPF6_DEBUG_SPF_TIME); OSPF6_DEBUG_SPF_OFF(OSPF6_DEBUG_SPF_DATABASE); OSPF6_DEBUG_ZEBRA_OFF(OSPF6_DEBUG_ZEBRA_SEND | OSPF6_DEBUG_ZEBRA_RECV); return CMD_SUCCESS; } int config_write_ospf6_debug_neighbor(struct vty *vty) { if (IS_OSPF6_DEBUG_NEIGHBOR(STATE) && IS_OSPF6_DEBUG_NEIGHBOR(EVENT)) vty_out(vty, "debug ospf6 neighbor\n"); else if (IS_OSPF6_DEBUG_NEIGHBOR(STATE)) vty_out(vty, "debug ospf6 neighbor state\n"); else if (IS_OSPF6_DEBUG_NEIGHBOR(EVENT)) vty_out(vty, "debug ospf6 neighbor event\n"); return 0; } void install_element_ospf6_debug_neighbor(void) { install_element(ENABLE_NODE, &debug_ospf6_neighbor_cmd); install_element(ENABLE_NODE, &no_debug_ospf6_neighbor_cmd); install_element(ENABLE_NODE, &no_debug_ospf6_cmd); install_element(CONFIG_NODE, &debug_ospf6_neighbor_cmd); install_element(CONFIG_NODE, &no_debug_ospf6_neighbor_cmd); install_element(CONFIG_NODE, &no_debug_ospf6_cmd); } frr-7.2.1/ospf6d/ospf6_neighbor.h0000644000000000000000000001255013610377563013503 00000000000000/* * Copyright (C) 2003 Yasuhiro Ohara * * This file is part of GNU Zebra. * * GNU Zebra is free software; you can redistribute it and/or modify it * under the terms of the GNU General Public License as published by the * Free Software Foundation; either version 2, or (at your option) any * later version. * * GNU Zebra 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; see the file COPYING; if not, write to the Free Software * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA */ #ifndef OSPF6_NEIGHBOR_H #define OSPF6_NEIGHBOR_H #include "hook.h" /* Debug option */ extern unsigned char conf_debug_ospf6_neighbor; #define OSPF6_DEBUG_NEIGHBOR_STATE 0x01 #define OSPF6_DEBUG_NEIGHBOR_EVENT 0x02 #define OSPF6_DEBUG_NEIGHBOR_ON(level) (conf_debug_ospf6_neighbor |= (level)) #define OSPF6_DEBUG_NEIGHBOR_OFF(level) (conf_debug_ospf6_neighbor &= ~(level)) #define IS_OSPF6_DEBUG_NEIGHBOR(level) \ (conf_debug_ospf6_neighbor & OSPF6_DEBUG_NEIGHBOR_##level) /* Neighbor structure */ struct ospf6_neighbor { /* Neighbor Router ID String */ char name[36]; /* OSPFv3 Interface this neighbor belongs to */ struct ospf6_interface *ospf6_if; /* Neighbor state */ uint8_t state; /* timestamp of last changing state */ uint32_t state_change; struct timeval last_changed; /* Neighbor Router ID */ uint32_t router_id; /* Neighbor Interface ID */ ifindex_t ifindex; /* Router Priority of this neighbor */ uint8_t priority; uint32_t drouter; uint32_t bdrouter; uint32_t prev_drouter; uint32_t prev_bdrouter; /* Options field (Capability) */ char options[3]; /* IPaddr of I/F on our side link */ struct in6_addr linklocal_addr; /* For Database Exchange */ uint8_t dbdesc_bits; uint32_t dbdesc_seqnum; /* Last received Database Description packet */ struct ospf6_dbdesc dbdesc_last; /* LS-list */ struct ospf6_lsdb *summary_list; struct ospf6_lsdb *request_list; struct ospf6_lsdb *retrans_list; /* LSA list for message transmission */ struct ospf6_lsdb *dbdesc_list; struct ospf6_lsdb *lsreq_list; struct ospf6_lsdb *lsupdate_list; struct ospf6_lsdb *lsack_list; struct ospf6_lsa *last_ls_req; /* Inactivity timer */ struct thread *inactivity_timer; /* Thread for sending message */ struct thread *thread_send_dbdesc; struct thread *thread_send_lsreq; struct thread *thread_send_lsupdate; struct thread *thread_send_lsack; /* BFD information */ void *bfd_info; }; /* Neighbor state */ #define OSPF6_NEIGHBOR_DOWN 1 #define OSPF6_NEIGHBOR_ATTEMPT 2 #define OSPF6_NEIGHBOR_INIT 3 #define OSPF6_NEIGHBOR_TWOWAY 4 #define OSPF6_NEIGHBOR_EXSTART 5 #define OSPF6_NEIGHBOR_EXCHANGE 6 #define OSPF6_NEIGHBOR_LOADING 7 #define OSPF6_NEIGHBOR_FULL 8 /* Neighbor Events */ #define OSPF6_NEIGHBOR_EVENT_NO_EVENT 0 #define OSPF6_NEIGHBOR_EVENT_HELLO_RCVD 1 #define OSPF6_NEIGHBOR_EVENT_TWOWAY_RCVD 2 #define OSPF6_NEIGHBOR_EVENT_NEGOTIATION_DONE 3 #define OSPF6_NEIGHBOR_EVENT_EXCHANGE_DONE 4 #define OSPF6_NEIGHBOR_EVENT_LOADING_DONE 5 #define OSPF6_NEIGHBOR_EVENT_ADJ_OK 6 #define OSPF6_NEIGHBOR_EVENT_SEQNUMBER_MISMATCH 7 #define OSPF6_NEIGHBOR_EVENT_BAD_LSREQ 8 #define OSPF6_NEIGHBOR_EVENT_ONEWAY_RCVD 9 #define OSPF6_NEIGHBOR_EVENT_INACTIVITY_TIMER 10 #define OSPF6_NEIGHBOR_EVENT_MAX_EVENT 11 static const char *ospf6_neighbor_event_str[] = { "NoEvent", "HelloReceived", "2-WayReceived", "NegotiationDone", "ExchangeDone", "LoadingDone", "AdjOK?", "SeqNumberMismatch", "BadLSReq", "1-WayReceived", "InactivityTimer", }; static inline const char *ospf6_neighbor_event_string(int event) { #define OSPF6_NEIGHBOR_UNKNOWN_EVENT_STRING "UnknownEvent" if (event < OSPF6_NEIGHBOR_EVENT_MAX_EVENT) return ospf6_neighbor_event_str[event]; return OSPF6_NEIGHBOR_UNKNOWN_EVENT_STRING; } extern const char *ospf6_neighbor_state_str[]; /* Function Prototypes */ int ospf6_neighbor_cmp(void *va, void *vb); void ospf6_neighbor_dbex_init(struct ospf6_neighbor *on); struct ospf6_neighbor *ospf6_neighbor_lookup(uint32_t, struct ospf6_interface *); struct ospf6_neighbor *ospf6_neighbor_create(uint32_t, struct ospf6_interface *); void ospf6_neighbor_delete(struct ospf6_neighbor *); /* Neighbor event */ extern int hello_received(struct thread *); extern int twoway_received(struct thread *); extern int negotiation_done(struct thread *); extern int exchange_done(struct thread *); extern int loading_done(struct thread *); extern int adj_ok(struct thread *); extern int seqnumber_mismatch(struct thread *); extern int bad_lsreq(struct thread *); extern int oneway_received(struct thread *); extern int inactivity_timer(struct thread *); extern void ospf6_check_nbr_loading(struct ospf6_neighbor *); extern void ospf6_neighbor_init(void); extern int config_write_ospf6_debug_neighbor(struct vty *vty); extern void install_element_ospf6_debug_neighbor(void); DECLARE_HOOK(ospf6_neighbor_change, (struct ospf6_neighbor * on, int state, int next_state), (on, state, next_state)) #endif /* OSPF6_NEIGHBOR_H */ frr-7.2.1/ospf6d/ospf6_network.c0000644000000000000000000001524513610377563013376 00000000000000/* * Copyright (C) 2003 Yasuhiro Ohara * * This file is part of GNU Zebra. * * GNU Zebra is free software; you can redistribute it and/or modify it * under the terms of the GNU General Public License as published by the * Free Software Foundation; either version 2, or (at your option) any * later version. * * GNU Zebra 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; see the file COPYING; if not, write to the Free Software * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA */ #include #include "log.h" #include "memory.h" #include "sockunion.h" #include "sockopt.h" #include "privs.h" #include "lib_errors.h" #include "libospf.h" #include "ospf6_proto.h" #include "ospf6_network.h" #include "ospf6d.h" int ospf6_sock; struct in6_addr allspfrouters6; struct in6_addr alldrouters6; /* setsockopt MulticastLoop to off */ static void ospf6_reset_mcastloop(void) { unsigned int off = 0; if (setsockopt(ospf6_sock, IPPROTO_IPV6, IPV6_MULTICAST_LOOP, &off, sizeof(unsigned int)) < 0) zlog_warn("Network: reset IPV6_MULTICAST_LOOP failed: %s", safe_strerror(errno)); } static void ospf6_set_pktinfo(void) { setsockopt_ipv6_pktinfo(ospf6_sock, 1); } static void ospf6_set_transport_class(void) { #ifdef IPTOS_PREC_INTERNETCONTROL setsockopt_ipv6_tclass(ospf6_sock, IPTOS_PREC_INTERNETCONTROL); #endif } static void ospf6_set_checksum(void) { int offset = 12; #ifndef DISABLE_IPV6_CHECKSUM if (setsockopt(ospf6_sock, IPPROTO_IPV6, IPV6_CHECKSUM, &offset, sizeof(offset)) < 0) zlog_warn("Network: set IPV6_CHECKSUM failed: %s", safe_strerror(errno)); #else zlog_warn("Network: Don't set IPV6_CHECKSUM"); #endif /* DISABLE_IPV6_CHECKSUM */ } void ospf6_serv_close(void) { if (ospf6_sock > 0) { close(ospf6_sock); ospf6_sock = -1; return; } } /* Make ospf6d's server socket. */ int ospf6_serv_sock(void) { frr_with_privs(&ospf6d_privs) { ospf6_sock = socket(AF_INET6, SOCK_RAW, IPPROTO_OSPFIGP); if (ospf6_sock < 0) { zlog_warn("Network: can't create OSPF6 socket."); return -1; } } /* set socket options */ #if 1 sockopt_reuseaddr(ospf6_sock); #else ospf6_set_reuseaddr(); #endif /*1*/ ospf6_reset_mcastloop(); ospf6_set_pktinfo(); ospf6_set_transport_class(); ospf6_set_checksum(); /* setup global in6_addr, allspf6 and alldr6 for later use */ inet_pton(AF_INET6, ALLSPFROUTERS6, &allspfrouters6); inet_pton(AF_INET6, ALLDROUTERS6, &alldrouters6); return 0; } /* ospf6 set socket option */ int ospf6_sso(ifindex_t ifindex, struct in6_addr *group, int option) { struct ipv6_mreq mreq6; int ret; int bufsize = (8 * 1024 * 1024); assert(ifindex); mreq6.ipv6mr_interface = ifindex; memcpy(&mreq6.ipv6mr_multiaddr, group, sizeof(struct in6_addr)); ret = setsockopt(ospf6_sock, IPPROTO_IPV6, option, &mreq6, sizeof(mreq6)); if (ret < 0) { flog_err_sys( EC_LIB_SOCKET, "Network: setsockopt (%d) on ifindex %d failed: %s", option, ifindex, safe_strerror(errno)); return ret; } setsockopt_so_sendbuf(ospf6_sock, bufsize); setsockopt_so_recvbuf(ospf6_sock, bufsize); return 0; } static int iov_count(struct iovec *iov) { int i; for (i = 0; iov[i].iov_base; i++) ; return i; } static int iov_totallen(struct iovec *iov) { int i; int totallen = 0; for (i = 0; iov[i].iov_base; i++) totallen += iov[i].iov_len; return totallen; } int ospf6_sendmsg(struct in6_addr *src, struct in6_addr *dst, ifindex_t *ifindex, struct iovec *message) { int retval; struct msghdr smsghdr; struct cmsghdr *scmsgp; union { struct cmsghdr hdr; uint8_t buf[CMSG_SPACE(sizeof(struct in6_pktinfo))]; } cmsgbuf; struct in6_pktinfo *pktinfo; struct sockaddr_in6 dst_sin6; assert(dst); assert(*ifindex); memset(&cmsgbuf, 0, sizeof(cmsgbuf)); scmsgp = (struct cmsghdr *)&cmsgbuf; pktinfo = (struct in6_pktinfo *)(CMSG_DATA(scmsgp)); memset(&dst_sin6, 0, sizeof(struct sockaddr_in6)); /* source address */ pktinfo->ipi6_ifindex = *ifindex; if (src) memcpy(&pktinfo->ipi6_addr, src, sizeof(struct in6_addr)); else memset(&pktinfo->ipi6_addr, 0, sizeof(struct in6_addr)); /* destination address */ dst_sin6.sin6_family = AF_INET6; #ifdef SIN6_LEN dst_sin6.sin6_len = sizeof(struct sockaddr_in6); #endif /*SIN6_LEN*/ memcpy(&dst_sin6.sin6_addr, dst, sizeof(struct in6_addr)); dst_sin6.sin6_scope_id = *ifindex; /* send control msg */ scmsgp->cmsg_level = IPPROTO_IPV6; scmsgp->cmsg_type = IPV6_PKTINFO; scmsgp->cmsg_len = CMSG_LEN(sizeof(struct in6_pktinfo)); /* scmsgp = CMSG_NXTHDR (&smsghdr, scmsgp); */ /* send msg hdr */ memset(&smsghdr, 0, sizeof(smsghdr)); smsghdr.msg_iov = message; smsghdr.msg_iovlen = iov_count(message); smsghdr.msg_name = (caddr_t)&dst_sin6; smsghdr.msg_namelen = sizeof(struct sockaddr_in6); smsghdr.msg_control = (caddr_t)&cmsgbuf.buf; smsghdr.msg_controllen = sizeof(cmsgbuf.buf); retval = sendmsg(ospf6_sock, &smsghdr, 0); if (retval != iov_totallen(message)) zlog_warn("sendmsg failed: ifindex: %d: %s (%d)", *ifindex, safe_strerror(errno), errno); return retval; } int ospf6_recvmsg(struct in6_addr *src, struct in6_addr *dst, ifindex_t *ifindex, struct iovec *message) { int retval; struct msghdr rmsghdr; struct cmsghdr *rcmsgp; uint8_t cmsgbuf[CMSG_SPACE(sizeof(struct in6_pktinfo))]; struct in6_pktinfo *pktinfo; struct sockaddr_in6 src_sin6; rcmsgp = (struct cmsghdr *)cmsgbuf; pktinfo = (struct in6_pktinfo *)(CMSG_DATA(rcmsgp)); memset(&src_sin6, 0, sizeof(struct sockaddr_in6)); /* receive control msg */ rcmsgp->cmsg_level = IPPROTO_IPV6; rcmsgp->cmsg_type = IPV6_PKTINFO; rcmsgp->cmsg_len = CMSG_LEN(sizeof(struct in6_pktinfo)); /* rcmsgp = CMSG_NXTHDR (&rmsghdr, rcmsgp); */ /* receive msg hdr */ memset(&rmsghdr, 0, sizeof(rmsghdr)); rmsghdr.msg_iov = message; rmsghdr.msg_iovlen = iov_count(message); rmsghdr.msg_name = (caddr_t)&src_sin6; rmsghdr.msg_namelen = sizeof(struct sockaddr_in6); rmsghdr.msg_control = (caddr_t)cmsgbuf; rmsghdr.msg_controllen = sizeof(cmsgbuf); retval = recvmsg(ospf6_sock, &rmsghdr, 0); if (retval < 0) zlog_warn("recvmsg failed: %s", safe_strerror(errno)); else if (retval == iov_totallen(message)) zlog_warn("recvmsg read full buffer size: %d", retval); /* source address */ assert(src); memcpy(src, &src_sin6.sin6_addr, sizeof(struct in6_addr)); /* destination address */ if (ifindex) *ifindex = pktinfo->ipi6_ifindex; if (dst) memcpy(dst, &pktinfo->ipi6_addr, sizeof(struct in6_addr)); return retval; } frr-7.2.1/ospf6d/ospf6_network.h0000644000000000000000000000245013610377563013375 00000000000000/* * Copyright (C) 2003 Yasuhiro Ohara * * This file is part of GNU Zebra. * * GNU Zebra is free software; you can redistribute it and/or modify it * under the terms of the GNU General Public License as published by the * Free Software Foundation; either version 2, or (at your option) any * later version. * * GNU Zebra 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; see the file COPYING; if not, write to the Free Software * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA */ #ifndef OSPF6_NETWORK_H #define OSPF6_NETWORK_H extern int ospf6_sock; extern struct in6_addr allspfrouters6; extern struct in6_addr alldrouters6; extern int ospf6_serv_sock(void); extern void ospf6_serv_close(void); extern int ospf6_sso(ifindex_t ifindex, struct in6_addr *group, int option); extern int ospf6_sendmsg(struct in6_addr *, struct in6_addr *, ifindex_t *, struct iovec *); extern int ospf6_recvmsg(struct in6_addr *, struct in6_addr *, ifindex_t *, struct iovec *); #endif /* OSPF6_NETWORK_H */ frr-7.2.1/ospf6d/ospf6_proto.c0000644000000000000000000000530713610377563013046 00000000000000/* * Copyright (C) 2003 Yasuhiro Ohara * * This file is part of GNU Zebra. * * GNU Zebra is free software; you can redistribute it and/or modify it * under the terms of the GNU General Public License as published by the * Free Software Foundation; either version 2, or (at your option) any * later version. * * GNU Zebra 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; see the file COPYING; if not, write to the Free Software * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA */ #include #include "log.h" #include "ospf6_proto.h" void ospf6_prefix_in6_addr(struct in6_addr *in6, const void *prefix_buf, const struct ospf6_prefix *p) { ptrdiff_t in6_off = (caddr_t)p->addr - (caddr_t)prefix_buf; memset(in6, 0, sizeof(struct in6_addr)); memcpy(in6, (uint8_t *)prefix_buf + in6_off, OSPF6_PREFIX_SPACE(p->prefix_length)); } void ospf6_prefix_apply_mask(struct ospf6_prefix *op) { uint8_t *pnt, mask; int index, offset; pnt = (uint8_t *)((caddr_t)op + sizeof(struct ospf6_prefix)); index = op->prefix_length / 8; offset = op->prefix_length % 8; mask = 0xff << (8 - offset); if (index > 16) { zlog_warn("Prefix length too long: %d", op->prefix_length); return; } /* nonzero mask means no check for this byte because if it contains * prefix bits it must be there for us to write */ if (mask) pnt[index++] &= mask; while (index < OSPF6_PREFIX_SPACE(op->prefix_length)) pnt[index++] = 0; } void ospf6_prefix_options_printbuf(uint8_t prefix_options, char *buf, int size) { snprintf(buf, size, "xxx"); } void ospf6_capability_printbuf(char capability, char *buf, int size) { char w, v, e, b; w = (capability & OSPF6_ROUTER_BIT_W ? 'W' : '-'); v = (capability & OSPF6_ROUTER_BIT_V ? 'V' : '-'); e = (capability & OSPF6_ROUTER_BIT_E ? 'E' : '-'); b = (capability & OSPF6_ROUTER_BIT_B ? 'B' : '-'); snprintf(buf, size, "----%c%c%c%c", w, v, e, b); } void ospf6_options_printbuf(uint8_t *options, char *buf, int size) { const char *dc, *r, *n, *mc, *e, *v6; dc = (OSPF6_OPT_ISSET(options, OSPF6_OPT_DC) ? "DC" : "--"); r = (OSPF6_OPT_ISSET(options, OSPF6_OPT_R) ? "R" : "-"); n = (OSPF6_OPT_ISSET(options, OSPF6_OPT_N) ? "N" : "-"); mc = (OSPF6_OPT_ISSET(options, OSPF6_OPT_MC) ? "MC" : "--"); e = (OSPF6_OPT_ISSET(options, OSPF6_OPT_E) ? "E" : "-"); v6 = (OSPF6_OPT_ISSET(options, OSPF6_OPT_V6) ? "V6" : "--"); snprintf(buf, size, "%s|%s|%s|%s|%s|%s", dc, r, n, mc, e, v6); } frr-7.2.1/ospf6d/ospf6_proto.h0000644000000000000000000000714613610377563013056 00000000000000/* * Common protocol data and data structures. * Copyright (C) 2003 Yasuhiro Ohara * * This file is part of GNU Zebra. * * GNU Zebra is free software; you can redistribute it and/or modify it * under the terms of the GNU General Public License as published by the * Free Software Foundation; either version 2, or (at your option) any * later version. * * GNU Zebra 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; see the file COPYING; if not, write to the Free Software * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA */ #ifndef OSPF6_PROTO_H #define OSPF6_PROTO_H /* OSPF protocol version */ #define OSPFV3_VERSION 3 /* TOS field normaly null */ #define DEFAULT_TOS_VALUE 0x0 #define ALLSPFROUTERS6 "ff02::5" #define ALLDROUTERS6 "ff02::6" #define OSPF6_ROUTER_BIT_W (1 << 3) #define OSPF6_ROUTER_BIT_V (1 << 2) #define OSPF6_ROUTER_BIT_E (1 << 1) #define OSPF6_ROUTER_BIT_B (1 << 0) /* OSPF options */ /* present in HELLO, DD, LSA */ #define OSPF6_OPT_SET(x,opt) ((x)[2] |= (opt)) #define OSPF6_OPT_ISSET(x,opt) ((x)[2] & (opt)) #define OSPF6_OPT_CLEAR(x,opt) ((x)[2] &= ~(opt)) #define OSPF6_OPT_CLEAR_ALL(x) ((x)[0] = (x)[1] = (x)[2] = 0) #define OSPF6_OPT_DC (1 << 5) /* Demand Circuit handling Capability */ #define OSPF6_OPT_R (1 << 4) /* Forwarding Capability (Any Protocol) */ #define OSPF6_OPT_N (1 << 3) /* Handling Type-7 LSA Capability */ #define OSPF6_OPT_MC (1 << 2) /* Multicasting Capability */ #define OSPF6_OPT_E (1 << 1) /* AS External Capability */ #define OSPF6_OPT_V6 (1 << 0) /* IPv6 forwarding Capability */ /* OSPF6 Prefix */ #define OSPF6_PREFIX_MIN_SIZE 4U /* .length == 0 */ struct ospf6_prefix { uint8_t prefix_length; uint8_t prefix_options; union { uint16_t _prefix_metric; uint16_t _prefix_referenced_lstype; } u; #define prefix_metric u._prefix_metric #define prefix_refer_lstype u._prefix_referenced_lstype /* followed by one address_prefix */ struct in6_addr addr[]; }; #define OSPF6_PREFIX_OPTION_NU (1 << 0) /* No Unicast */ #define OSPF6_PREFIX_OPTION_LA (1 << 1) /* Local Address */ #define OSPF6_PREFIX_OPTION_MC (1 << 2) /* MultiCast */ #define OSPF6_PREFIX_OPTION_P (1 << 3) /* Propagate (NSSA) */ /* caddr_t OSPF6_PREFIX_BODY (struct ospf6_prefix *); */ #define OSPF6_PREFIX_BODY(x) ((caddr_t)(x) + sizeof (struct ospf6_prefix)) /* size_t OSPF6_PREFIX_SPACE (int prefixlength); */ #define OSPF6_PREFIX_SPACE(x) ((((x) + 31) / 32) * 4) /* size_t OSPF6_PREFIX_SIZE (struct ospf6_prefix *); */ #define OSPF6_PREFIX_SIZE(x) \ (OSPF6_PREFIX_SPACE((x)->prefix_length) + sizeof(struct ospf6_prefix)) /* struct ospf6_prefix *OSPF6_PREFIX_NEXT (struct ospf6_prefix *); */ #define OSPF6_PREFIX_NEXT(x) \ ((struct ospf6_prefix *)((caddr_t)(x) + OSPF6_PREFIX_SIZE(x))) extern void ospf6_prefix_in6_addr(struct in6_addr *in6, const void *prefix_buf, const struct ospf6_prefix *p); extern void ospf6_prefix_apply_mask(struct ospf6_prefix *op); extern void ospf6_prefix_options_printbuf(uint8_t prefix_options, char *buf, int size); extern void ospf6_capability_printbuf(char capability, char *buf, int size); extern void ospf6_options_printbuf(uint8_t *options, char *buf, int size); #endif /* OSPF6_PROTO_H */ frr-7.2.1/ospf6d/ospf6_route.c0000644000000000000000000012204713610377563013042 00000000000000/* * Copyright (C) 2003 Yasuhiro Ohara * * This file is part of GNU Zebra. * * GNU Zebra is free software; you can redistribute it and/or modify it * under the terms of the GNU General Public License as published by the * Free Software Foundation; either version 2, or (at your option) any * later version. * * GNU Zebra 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; see the file COPYING; if not, write to the Free Software * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA */ #include #include "log.h" #include "memory.h" #include "prefix.h" #include "table.h" #include "vty.h" #include "command.h" #include "linklist.h" #include "ospf6_proto.h" #include "ospf6_lsa.h" #include "ospf6_lsdb.h" #include "ospf6_route.h" #include "ospf6_top.h" #include "ospf6_area.h" #include "ospf6_interface.h" #include "ospf6d.h" #include "ospf6_zebra.h" unsigned char conf_debug_ospf6_route = 0; static char *ospf6_route_table_name(struct ospf6_route_table *table) { static char name[64]; switch (table->scope_type) { case OSPF6_SCOPE_TYPE_GLOBAL: { switch (table->table_type) { case OSPF6_TABLE_TYPE_ROUTES: snprintf(name, sizeof(name), "global route table"); break; case OSPF6_TABLE_TYPE_BORDER_ROUTERS: snprintf(name, sizeof(name), "global brouter table"); break; case OSPF6_TABLE_TYPE_EXTERNAL_ROUTES: snprintf(name, sizeof(name), "global external table"); break; default: snprintf(name, sizeof(name), "global unknown table"); break; } } break; case OSPF6_SCOPE_TYPE_AREA: { struct ospf6_area *oa = (struct ospf6_area *)table->scope; switch (table->table_type) { case OSPF6_TABLE_TYPE_SPF_RESULTS: snprintf(name, sizeof(name), "area %s spf table", oa->name); break; case OSPF6_TABLE_TYPE_ROUTES: snprintf(name, sizeof(name), "area %s route table", oa->name); break; case OSPF6_TABLE_TYPE_PREFIX_RANGES: snprintf(name, sizeof(name), "area %s range table", oa->name); break; case OSPF6_TABLE_TYPE_SUMMARY_PREFIXES: snprintf(name, sizeof(name), "area %s summary prefix table", oa->name); break; case OSPF6_TABLE_TYPE_SUMMARY_ROUTERS: snprintf(name, sizeof(name), "area %s summary router table", oa->name); break; default: snprintf(name, sizeof(name), "area %s unknown table", oa->name); break; } } break; case OSPF6_SCOPE_TYPE_INTERFACE: { struct ospf6_interface *oi = (struct ospf6_interface *)table->scope; switch (table->table_type) { case OSPF6_TABLE_TYPE_CONNECTED_ROUTES: snprintf(name, sizeof(name), "interface %s connected table", oi->interface->name); break; default: snprintf(name, sizeof(name), "interface %s unknown table", oi->interface->name); break; } } break; default: { switch (table->table_type) { case OSPF6_TABLE_TYPE_SPF_RESULTS: snprintf(name, sizeof(name), "temporary spf table"); break; default: snprintf(name, sizeof(name), "temporary unknown table"); break; } } break; } return name; } void ospf6_linkstate_prefix(uint32_t adv_router, uint32_t id, struct prefix *prefix) { memset(prefix, 0, sizeof(struct prefix)); prefix->family = AF_INET6; prefix->prefixlen = 64; memcpy(&prefix->u.prefix6.s6_addr[0], &adv_router, 4); memcpy(&prefix->u.prefix6.s6_addr[4], &id, 4); } void ospf6_linkstate_prefix2str(struct prefix *prefix, char *buf, int size) { uint32_t adv_router, id; char adv_router_str[16], id_str[16]; memcpy(&adv_router, &prefix->u.prefix6.s6_addr[0], 4); memcpy(&id, &prefix->u.prefix6.s6_addr[4], 4); inet_ntop(AF_INET, &adv_router, adv_router_str, sizeof(adv_router_str)); inet_ntop(AF_INET, &id, id_str, sizeof(id_str)); if (ntohl(id)) snprintf(buf, size, "%s Net-ID: %s", adv_router_str, id_str); else snprintf(buf, size, "%s", adv_router_str); } /* Global strings for logging */ const char *ospf6_dest_type_str[OSPF6_DEST_TYPE_MAX] = { "Unknown", "Router", "Network", "Discard", "Linkstate", "AddressRange", }; const char *ospf6_dest_type_substr[OSPF6_DEST_TYPE_MAX] = { "?", "R", "N", "D", "L", "A", }; const char *ospf6_path_type_str[OSPF6_PATH_TYPE_MAX] = { "Unknown", "Intra-Area", "Inter-Area", "External-1", "External-2", }; const char *ospf6_path_type_substr[OSPF6_PATH_TYPE_MAX] = { "??", "IA", "IE", "E1", "E2", }; struct ospf6_nexthop *ospf6_nexthop_create(void) { struct ospf6_nexthop *nh; nh = XCALLOC(MTYPE_OSPF6_NEXTHOP, sizeof(struct ospf6_nexthop)); return nh; } void ospf6_nexthop_delete(struct ospf6_nexthop *nh) { XFREE(MTYPE_OSPF6_NEXTHOP, nh); } void ospf6_clear_nexthops(struct list *nh_list) { struct listnode *node; struct ospf6_nexthop *nh; if (nh_list) { for (ALL_LIST_ELEMENTS_RO(nh_list, node, nh)) ospf6_nexthop_clear(nh); } } static struct ospf6_nexthop * ospf6_route_find_nexthop(struct list *nh_list, struct ospf6_nexthop *nh_match) { struct listnode *node; struct ospf6_nexthop *nh; if (nh_list && nh_match) { for (ALL_LIST_ELEMENTS_RO(nh_list, node, nh)) { if (ospf6_nexthop_is_same(nh, nh_match)) return (nh); } } return (NULL); } void ospf6_copy_nexthops(struct list *dst, struct list *src) { struct ospf6_nexthop *nh_new, *nh; struct listnode *node; if (dst && src) { for (ALL_LIST_ELEMENTS_RO(src, node, nh)) { if (ospf6_nexthop_is_set(nh)) { nh_new = ospf6_nexthop_create(); ospf6_nexthop_copy(nh_new, nh); listnode_add_sort(dst, nh_new); } } } } void ospf6_merge_nexthops(struct list *dst, struct list *src) { struct listnode *node; struct ospf6_nexthop *nh, *nh_new; if (src && dst) { for (ALL_LIST_ELEMENTS_RO(src, node, nh)) { if (!ospf6_route_find_nexthop(dst, nh)) { nh_new = ospf6_nexthop_create(); ospf6_nexthop_copy(nh_new, nh); listnode_add_sort(dst, nh_new); } } } } int ospf6_route_cmp_nexthops(struct ospf6_route *a, struct ospf6_route *b) { struct listnode *anode, *bnode; struct ospf6_nexthop *anh, *bnh; bool identical = false; if (a && b) { if (listcount(a->nh_list) == listcount(b->nh_list)) { for (ALL_LIST_ELEMENTS_RO(a->nh_list, anode, anh)) { identical = false; for (ALL_LIST_ELEMENTS_RO(b->nh_list, bnode, bnh)) { if (ospf6_nexthop_is_same(anh, bnh)) identical = true; } /* Currnet List A element not found List B * Non-Identical lists return */ if (identical == false) return 1; } return 0; } else return 1; } /* One of the routes doesn't exist ? */ return (1); } int ospf6_num_nexthops(struct list *nh_list) { return (listcount(nh_list)); } void ospf6_add_nexthop(struct list *nh_list, int ifindex, struct in6_addr *addr) { struct ospf6_nexthop *nh; struct ospf6_nexthop nh_match; if (nh_list) { nh_match.ifindex = ifindex; if (addr != NULL) memcpy(&nh_match.address, addr, sizeof(struct in6_addr)); else memset(&nh_match.address, 0, sizeof(struct in6_addr)); if (!ospf6_route_find_nexthop(nh_list, &nh_match)) { nh = ospf6_nexthop_create(); ospf6_nexthop_copy(nh, &nh_match); listnode_add(nh_list, nh); } } } void ospf6_route_zebra_copy_nexthops(struct ospf6_route *route, struct zapi_nexthop nexthops[], int entries) { struct ospf6_nexthop *nh; struct listnode *node; char buf[64]; int i; if (route) { i = 0; for (ALL_LIST_ELEMENTS_RO(route->nh_list, node, nh)) { if (IS_OSPF6_DEBUG_ZEBRA(SEND)) { const char *ifname; inet_ntop(AF_INET6, &nh->address, buf, sizeof(buf)); ifname = ifindex2ifname(nh->ifindex, VRF_DEFAULT); zlog_debug(" nexthop: %s%%%.*s(%d)", buf, IFNAMSIZ, ifname, nh->ifindex); } if (i >= entries) return; nexthops[i].vrf_id = VRF_DEFAULT; nexthops[i].ifindex = nh->ifindex; if (!IN6_IS_ADDR_UNSPECIFIED(&nh->address)) { nexthops[i].gate.ipv6 = nh->address; nexthops[i].type = NEXTHOP_TYPE_IPV6_IFINDEX; } else nexthops[i].type = NEXTHOP_TYPE_IFINDEX; i++; } } } int ospf6_route_get_first_nh_index(struct ospf6_route *route) { struct ospf6_nexthop *nh; if (route) { nh = listnode_head(route->nh_list); if (nh) return nh->ifindex; } return (-1); } int ospf6_nexthop_cmp(struct ospf6_nexthop *a, struct ospf6_nexthop *b) { if (a->ifindex < b->ifindex) return -1; else if (a->ifindex > b->ifindex) return 1; else return memcmp(&a->address, &b->address, sizeof(struct in6_addr)); return 0; } static int ospf6_path_cmp(struct ospf6_path *a, struct ospf6_path *b) { if (a->origin.adv_router < b->origin.adv_router) return -1; else if (a->origin.adv_router > b->origin.adv_router) return 1; else return 0; } void ospf6_path_free(struct ospf6_path *op) { if (op->nh_list) list_delete(&op->nh_list); XFREE(MTYPE_OSPF6_PATH, op); } struct ospf6_path *ospf6_path_dup(struct ospf6_path *path) { struct ospf6_path *new; new = XCALLOC(MTYPE_OSPF6_PATH, sizeof(struct ospf6_path)); memcpy(new, path, sizeof(struct ospf6_path)); new->nh_list = list_new(); new->nh_list->cmp = (int (*)(void *, void *))ospf6_nexthop_cmp; new->nh_list->del = (void (*)(void *))ospf6_nexthop_delete; return new; } void ospf6_copy_paths(struct list *dst, struct list *src) { struct ospf6_path *path_new, *path; struct listnode *node; if (dst && src) { for (ALL_LIST_ELEMENTS_RO(src, node, path)) { path_new = ospf6_path_dup(path); ospf6_copy_nexthops(path_new->nh_list, path->nh_list); listnode_add_sort(dst, path_new); } } } struct ospf6_route *ospf6_route_create(void) { struct ospf6_route *route; route = XCALLOC(MTYPE_OSPF6_ROUTE, sizeof(struct ospf6_route)); route->nh_list = list_new(); route->nh_list->cmp = (int (*)(void *, void *))ospf6_nexthop_cmp; route->nh_list->del = (void (*)(void *))ospf6_nexthop_delete; route->paths = list_new(); route->paths->cmp = (int (*)(void *, void *))ospf6_path_cmp; route->paths->del = (void (*)(void *))ospf6_path_free; return route; } void ospf6_route_delete(struct ospf6_route *route) { if (route) { if (route->nh_list) list_delete(&route->nh_list); if (route->paths) list_delete(&route->paths); XFREE(MTYPE_OSPF6_ROUTE, route); } } struct ospf6_route *ospf6_route_copy(struct ospf6_route *route) { struct ospf6_route *new; new = ospf6_route_create(); new->type = route->type; memcpy(&new->prefix, &route->prefix, sizeof(struct prefix)); new->installed = route->installed; new->changed = route->changed; new->flag = route->flag; new->route_option = route->route_option; new->linkstate_id = route->linkstate_id; new->path = route->path; ospf6_copy_nexthops(new->nh_list, route->nh_list); ospf6_copy_paths(new->paths, route->paths); new->rnode = NULL; new->prev = NULL; new->next = NULL; new->table = NULL; new->lock = 0; return new; } void ospf6_route_lock(struct ospf6_route *route) { route->lock++; } void ospf6_route_unlock(struct ospf6_route *route) { assert(route->lock > 0); route->lock--; if (route->lock == 0) { /* Can't detach from the table until here because ospf6_route_next () will use the 'route->table' pointer for logging */ route->table = NULL; ospf6_route_delete(route); } } /* Route compare function. If ra is more preferred, it returns less than 0. If rb is more preferred returns greater than 0. Otherwise (neither one is preferred), returns 0 */ int ospf6_route_cmp(struct ospf6_route *ra, struct ospf6_route *rb) { assert(ospf6_route_is_same(ra, rb)); assert(OSPF6_PATH_TYPE_NONE < ra->path.type && ra->path.type < OSPF6_PATH_TYPE_MAX); assert(OSPF6_PATH_TYPE_NONE < rb->path.type && rb->path.type < OSPF6_PATH_TYPE_MAX); if (ra->type != rb->type) return (ra->type - rb->type); if (ra->path.type != rb->path.type) return (ra->path.type - rb->path.type); if (ra->path.type == OSPF6_PATH_TYPE_EXTERNAL2) { if (ra->path.u.cost_e2 != rb->path.u.cost_e2) return (ra->path.u.cost_e2 - rb->path.u.cost_e2); else return (ra->path.cost - rb->path.cost); } else { if (ra->path.cost != rb->path.cost) return (ra->path.cost - rb->path.cost); } if (ra->path.area_id != rb->path.area_id) return (ntohl(ra->path.area_id) - ntohl(rb->path.area_id)); return 0; } struct ospf6_route *ospf6_route_lookup(struct prefix *prefix, struct ospf6_route_table *table) { struct route_node *node; struct ospf6_route *route; node = route_node_lookup(table->table, prefix); if (node == NULL) return NULL; route = (struct ospf6_route *)node->info; route_unlock_node(node); /* to free the lookup lock */ return route; } struct ospf6_route * ospf6_route_lookup_identical(struct ospf6_route *route, struct ospf6_route_table *table) { struct ospf6_route *target; for (target = ospf6_route_lookup(&route->prefix, table); target; target = target->next) { if (target->type == route->type && (memcmp(&target->prefix, &route->prefix, sizeof(struct prefix)) == 0) && target->path.type == route->path.type && target->path.cost == route->path.cost && target->path.u.cost_e2 == route->path.u.cost_e2 && ospf6_route_cmp_nexthops(target, route) == 0) return target; } return NULL; } struct ospf6_route * ospf6_route_lookup_bestmatch(struct prefix *prefix, struct ospf6_route_table *table) { struct route_node *node; struct ospf6_route *route; node = route_node_match(table->table, prefix); if (node == NULL) return NULL; route_unlock_node(node); route = (struct ospf6_route *)node->info; return route; } #ifdef DEBUG static void route_table_assert(struct ospf6_route_table *table) { struct ospf6_route *prev, *r, *next; char buf[PREFIX2STR_BUFFER]; unsigned int link_error = 0, num = 0; r = ospf6_route_head(table); prev = NULL; while (r) { if (r->prev != prev) link_error++; next = ospf6_route_next(r); if (r->next != next) link_error++; prev = r; r = next; } for (r = ospf6_route_head(table); r; r = ospf6_route_next(r)) num++; if (link_error == 0 && num == table->count) return; flog_err(EC_LIB_DEVELOPMENT, "PANIC !!"); flog_err(EC_LIB_DEVELOPMENT, "Something has gone wrong with ospf6_route_table[%p]", table); zlog_debug("table count = %d, real number = %d", table->count, num); zlog_debug("DUMP START"); for (r = ospf6_route_head(table); r; r = ospf6_route_next(r)) { prefix2str(&r->prefix, buf, sizeof(buf)); zlog_info("%p<-[%p]->%p : %s", r->prev, r, r->next, buf); } zlog_debug("DUMP END"); assert(link_error == 0 && num == table->count); } #define ospf6_route_table_assert(t) (route_table_assert (t)) #else #define ospf6_route_table_assert(t) ((void) 0) #endif /*DEBUG*/ struct ospf6_route *ospf6_route_add(struct ospf6_route *route, struct ospf6_route_table *table) { struct route_node *node, *nextnode, *prevnode; struct ospf6_route *current = NULL; struct ospf6_route *prev = NULL, *old = NULL, *next = NULL; char buf[PREFIX2STR_BUFFER]; struct timeval now; assert(route->rnode == NULL); assert(route->lock == 0); assert(route->next == NULL); assert(route->prev == NULL); if (route->type == OSPF6_DEST_TYPE_LINKSTATE) ospf6_linkstate_prefix2str(&route->prefix, buf, sizeof(buf)); else prefix2str(&route->prefix, buf, sizeof(buf)); if (IS_OSPF6_DEBUG_ROUTE(MEMORY)) zlog_debug("%s %p: route add %p: %s paths %u nh %u", ospf6_route_table_name(table), (void *)table, (void *)route, buf, listcount(route->paths), listcount(route->nh_list)); else if (IS_OSPF6_DEBUG_ROUTE(TABLE)) zlog_debug("%s: route add: %s", ospf6_route_table_name(table), buf); monotime(&now); node = route_node_get(table->table, &route->prefix); route->rnode = node; /* find place to insert */ for (current = node->info; current; current = current->next) { if (!ospf6_route_is_same(current, route)) next = current; else if (current->type != route->type) prev = current; else if (ospf6_route_is_same_origin(current, route)) old = current; else if (ospf6_route_cmp(current, route) > 0) next = current; else prev = current; if (old || next) break; } if (old) { /* if route does not actually change, return unchanged */ if (ospf6_route_is_identical(old, route)) { if (IS_OSPF6_DEBUG_ROUTE(MEMORY)) zlog_debug( "%s %p: route add %p: needless update of %p old cost %u", ospf6_route_table_name(table), (void *)table, (void *)route, (void *)old, old->path.cost); else if (IS_OSPF6_DEBUG_ROUTE(TABLE)) zlog_debug("%s: route add: needless update", ospf6_route_table_name(table)); ospf6_route_delete(route); SET_FLAG(old->flag, OSPF6_ROUTE_ADD); ospf6_route_table_assert(table); /* to free the lookup lock */ route_unlock_node(node); return old; } if (IS_OSPF6_DEBUG_ROUTE(MEMORY)) zlog_debug( "%s %p: route add %p cost %u paths %u nh %u: update of %p cost %u paths %u nh %u", ospf6_route_table_name(table), (void *)table, (void *)route, route->path.cost, listcount(route->paths), listcount(route->nh_list), (void *)old, old->path.cost, listcount(old->paths), listcount(old->nh_list)); else if (IS_OSPF6_DEBUG_ROUTE(TABLE)) zlog_debug("%s: route add: update", ospf6_route_table_name(table)); /* replace old one if exists */ if (node->info == old) { node->info = route; SET_FLAG(route->flag, OSPF6_ROUTE_BEST); } if (old->prev) old->prev->next = route; route->prev = old->prev; if (old->next) old->next->prev = route; route->next = old->next; route->installed = old->installed; route->changed = now; assert(route->table == NULL); route->table = table; ospf6_route_unlock(old); /* will be deleted later */ ospf6_route_lock(route); SET_FLAG(route->flag, OSPF6_ROUTE_CHANGE); ospf6_route_table_assert(table); if (table->hook_add) (*table->hook_add)(route); return route; } /* insert if previous or next node found */ if (prev || next) { if (IS_OSPF6_DEBUG_ROUTE(MEMORY)) zlog_debug( "%s %p: route add %p cost %u: another path: prev %p, next %p node ref %u", ospf6_route_table_name(table), (void *)table, (void *)route, route->path.cost, (void *)prev, (void *)next, node->lock); else if (IS_OSPF6_DEBUG_ROUTE(TABLE)) zlog_debug("%s: route add cost %u: another path found", ospf6_route_table_name(table), route->path.cost); if (prev == NULL) prev = next->prev; if (next == NULL) next = prev->next; if (prev) prev->next = route; route->prev = prev; if (next) next->prev = route; route->next = next; if (node->info == next) { assert(next && next->rnode == node); node->info = route; UNSET_FLAG(next->flag, OSPF6_ROUTE_BEST); SET_FLAG(route->flag, OSPF6_ROUTE_BEST); if (IS_OSPF6_DEBUG_ROUTE(MEMORY)) zlog_info( "%s %p: route add %p cost %u: replacing previous best: %p cost %u", ospf6_route_table_name(table), (void *)table, (void *)route, route->path.cost, (void *)next, next->path.cost); } route->installed = now; route->changed = now; assert(route->table == NULL); route->table = table; ospf6_route_lock(route); table->count++; ospf6_route_table_assert(table); SET_FLAG(route->flag, OSPF6_ROUTE_ADD); if (table->hook_add) (*table->hook_add)(route); return route; } /* Else, this is the brand new route regarding to the prefix */ if (IS_OSPF6_DEBUG_ROUTE(MEMORY)) zlog_debug("%s %p: route add %p %s cost %u: brand new route", ospf6_route_table_name(table), (void *)table, (void *)route, buf, route->path.cost); else if (IS_OSPF6_DEBUG_ROUTE(TABLE)) zlog_debug("%s: route add: brand new route", ospf6_route_table_name(table)); assert(node->info == NULL); node->info = route; SET_FLAG(route->flag, OSPF6_ROUTE_BEST); ospf6_route_lock(route); route->installed = now; route->changed = now; assert(route->table == NULL); route->table = table; /* lookup real existing next route */ nextnode = node; route_lock_node(nextnode); do { nextnode = route_next(nextnode); } while (nextnode && nextnode->info == NULL); /* set next link */ if (nextnode == NULL) route->next = NULL; else { route_unlock_node(nextnode); next = nextnode->info; route->next = next; next->prev = route; } /* lookup real existing prev route */ prevnode = node; route_lock_node(prevnode); do { prevnode = route_prev(prevnode); } while (prevnode && prevnode->info == NULL); /* set prev link */ if (prevnode == NULL) route->prev = NULL; else { route_unlock_node(prevnode); prev = prevnode->info; while (prev->next && ospf6_route_is_same(prev, prev->next)) prev = prev->next; route->prev = prev; prev->next = route; } table->count++; ospf6_route_table_assert(table); SET_FLAG(route->flag, OSPF6_ROUTE_ADD); if (table->hook_add) (*table->hook_add)(route); return route; } void ospf6_route_remove(struct ospf6_route *route, struct ospf6_route_table *table) { struct route_node *node; struct ospf6_route *current; char buf[PREFIX2STR_BUFFER]; if (route->type == OSPF6_DEST_TYPE_LINKSTATE) ospf6_linkstate_prefix2str(&route->prefix, buf, sizeof(buf)); else prefix2str(&route->prefix, buf, sizeof(buf)); if (IS_OSPF6_DEBUG_ROUTE(MEMORY)) zlog_debug("%s %p: route remove %p: %s cost %u refcount %u", ospf6_route_table_name(table), (void *)table, (void *)route, buf, route->path.cost, route->lock); else if (IS_OSPF6_DEBUG_ROUTE(TABLE)) zlog_debug("%s: route remove: %s", ospf6_route_table_name(table), buf); node = route_node_lookup(table->table, &route->prefix); assert(node); /* find the route to remove, making sure that the route pointer is from the route table. */ current = node->info; while (current && current != route) current = current->next; assert(current == route); /* adjust doubly linked list */ if (route->prev) route->prev->next = route->next; if (route->next) route->next->prev = route->prev; if (node->info == route) { if (route->next && route->next->rnode == node) { node->info = route->next; SET_FLAG(route->next->flag, OSPF6_ROUTE_BEST); } else { node->info = NULL; route->rnode = NULL; route_unlock_node(node); /* to free the original lock */ } } route_unlock_node(node); /* to free the lookup lock */ table->count--; ospf6_route_table_assert(table); SET_FLAG(route->flag, OSPF6_ROUTE_WAS_REMOVED); /* Note hook_remove may call ospf6_route_remove */ if (table->hook_remove) (*table->hook_remove)(route); ospf6_route_unlock(route); } struct ospf6_route *ospf6_route_head(struct ospf6_route_table *table) { struct route_node *node; struct ospf6_route *route; node = route_top(table->table); if (node == NULL) return NULL; /* skip to the real existing entry */ while (node && node->info == NULL) node = route_next(node); if (node == NULL) return NULL; route_unlock_node(node); assert(node->info); route = (struct ospf6_route *)node->info; assert(route->prev == NULL); assert(route->table == table); ospf6_route_lock(route); if (IS_OSPF6_DEBUG_ROUTE(MEMORY)) zlog_info("%s %p: route head: %p<-[%p]->%p", ospf6_route_table_name(table), (void *)table, (void *)route->prev, (void *)route, (void *)route->next); return route; } struct ospf6_route *ospf6_route_next(struct ospf6_route *route) { struct ospf6_route *next = route->next; if (IS_OSPF6_DEBUG_ROUTE(MEMORY)) zlog_info("%s %p: route next: %p<-[%p]->%p , route ref count %u", ospf6_route_table_name(route->table), (void *)route->table, (void *)route->prev, (void *)route, (void *)route->next, route->lock); ospf6_route_unlock(route); if (next) ospf6_route_lock(next); return next; } struct ospf6_route *ospf6_route_best_next(struct ospf6_route *route) { struct route_node *rnode; struct ospf6_route *next; ospf6_route_unlock(route); rnode = route->rnode; route_lock_node(rnode); rnode = route_next(rnode); while (rnode && rnode->info == NULL) rnode = route_next(rnode); if (rnode == NULL) return NULL; route_unlock_node(rnode); assert(rnode->info); next = (struct ospf6_route *)rnode->info; ospf6_route_lock(next); return next; } struct ospf6_route *ospf6_route_match_head(struct prefix *prefix, struct ospf6_route_table *table) { struct route_node *node; struct ospf6_route *route; /* Walk down tree. */ node = table->table->top; while (node && node->p.prefixlen < prefix->prefixlen && prefix_match(&node->p, prefix)) node = node->link[prefix_bit(&prefix->u.prefix, node->p.prefixlen)]; if (node) route_lock_node(node); while (node && node->info == NULL) node = route_next(node); if (node == NULL) return NULL; route_unlock_node(node); if (!prefix_match(prefix, &node->p)) return NULL; route = node->info; ospf6_route_lock(route); return route; } struct ospf6_route *ospf6_route_match_next(struct prefix *prefix, struct ospf6_route *route) { struct ospf6_route *next; next = ospf6_route_next(route); if (next && !prefix_match(prefix, &next->prefix)) { ospf6_route_unlock(next); next = NULL; } return next; } void ospf6_route_remove_all(struct ospf6_route_table *table) { struct ospf6_route *route; for (route = ospf6_route_head(table); route; route = ospf6_route_next(route)) ospf6_route_remove(route, table); } struct ospf6_route_table *ospf6_route_table_create(int s, int t) { struct ospf6_route_table *new; new = XCALLOC(MTYPE_OSPF6_ROUTE, sizeof(struct ospf6_route_table)); new->table = route_table_init(); new->scope_type = s; new->table_type = t; return new; } void ospf6_route_table_delete(struct ospf6_route_table *table) { ospf6_route_remove_all(table); bf_free(table->idspace); route_table_finish(table->table); XFREE(MTYPE_OSPF6_ROUTE, table); } /* VTY commands */ void ospf6_route_show(struct vty *vty, struct ospf6_route *route) { int i; char destination[PREFIX2STR_BUFFER], nexthop[64]; char duration[64]; const char *ifname; struct timeval now, res; struct listnode *node; struct ospf6_nexthop *nh; monotime(&now); timersub(&now, &route->changed, &res); timerstring(&res, duration, sizeof(duration)); /* destination */ if (route->type == OSPF6_DEST_TYPE_LINKSTATE) ospf6_linkstate_prefix2str(&route->prefix, destination, sizeof(destination)); else if (route->type == OSPF6_DEST_TYPE_ROUTER) inet_ntop(route->prefix.family, &route->prefix.u.prefix, destination, sizeof(destination)); else prefix2str(&route->prefix, destination, sizeof(destination)); i = 0; for (ALL_LIST_ELEMENTS_RO(route->nh_list, node, nh)) { /* nexthop */ inet_ntop(AF_INET6, &nh->address, nexthop, sizeof(nexthop)); ifname = ifindex2ifname(nh->ifindex, VRF_DEFAULT); if (!i) { vty_out(vty, "%c%1s %2s %-30s %-25s %6.*s %s\n", (ospf6_route_is_best(route) ? '*' : ' '), OSPF6_DEST_TYPE_SUBSTR(route->type), OSPF6_PATH_TYPE_SUBSTR(route->path.type), destination, nexthop, IFNAMSIZ, ifname, duration); i++; } else vty_out(vty, "%c%1s %2s %-30s %-25s %6.*s %s\n", ' ', "", "", "", nexthop, IFNAMSIZ, ifname, ""); } } void ospf6_route_show_detail(struct vty *vty, struct ospf6_route *route) { const char *ifname; char destination[PREFIX2STR_BUFFER], nexthop[64]; char area_id[16], id[16], adv_router[16], capa[16], options[16]; struct timeval now, res; char duration[64]; struct listnode *node; struct ospf6_nexthop *nh; monotime(&now); /* destination */ if (route->type == OSPF6_DEST_TYPE_LINKSTATE) ospf6_linkstate_prefix2str(&route->prefix, destination, sizeof(destination)); else if (route->type == OSPF6_DEST_TYPE_ROUTER) inet_ntop(route->prefix.family, &route->prefix.u.prefix, destination, sizeof(destination)); else prefix2str(&route->prefix, destination, sizeof(destination)); vty_out(vty, "Destination: %s\n", destination); /* destination type */ vty_out(vty, "Destination type: %s\n", OSPF6_DEST_TYPE_NAME(route->type)); /* Time */ timersub(&now, &route->installed, &res); timerstring(&res, duration, sizeof(duration)); vty_out(vty, "Installed Time: %s ago\n", duration); timersub(&now, &route->changed, &res); timerstring(&res, duration, sizeof(duration)); vty_out(vty, " Changed Time: %s ago\n", duration); /* Debugging info */ vty_out(vty, "Lock: %d Flags: %s%s%s%s\n", route->lock, (CHECK_FLAG(route->flag, OSPF6_ROUTE_BEST) ? "B" : "-"), (CHECK_FLAG(route->flag, OSPF6_ROUTE_ADD) ? "A" : "-"), (CHECK_FLAG(route->flag, OSPF6_ROUTE_REMOVE) ? "R" : "-"), (CHECK_FLAG(route->flag, OSPF6_ROUTE_CHANGE) ? "C" : "-")); vty_out(vty, "Memory: prev: %p this: %p next: %p\n", (void *)route->prev, (void *)route, (void *)route->next); /* Path section */ /* Area-ID */ inet_ntop(AF_INET, &route->path.area_id, area_id, sizeof(area_id)); vty_out(vty, "Associated Area: %s\n", area_id); /* Path type */ vty_out(vty, "Path Type: %s\n", OSPF6_PATH_TYPE_NAME(route->path.type)); /* LS Origin */ inet_ntop(AF_INET, &route->path.origin.id, id, sizeof(id)); inet_ntop(AF_INET, &route->path.origin.adv_router, adv_router, sizeof(adv_router)); vty_out(vty, "LS Origin: %s Id: %s Adv: %s\n", ospf6_lstype_name(route->path.origin.type), id, adv_router); /* Options */ ospf6_options_printbuf(route->path.options, options, sizeof(options)); vty_out(vty, "Options: %s\n", options); /* Router Bits */ ospf6_capability_printbuf(route->path.router_bits, capa, sizeof(capa)); vty_out(vty, "Router Bits: %s\n", capa); /* Prefix Options */ vty_out(vty, "Prefix Options: xxx\n"); /* Metrics */ vty_out(vty, "Metric Type: %d\n", route->path.metric_type); vty_out(vty, "Metric: %d (%d)\n", route->path.cost, route->path.u.cost_e2); vty_out(vty, "Paths count: %u\n", route->paths->count); vty_out(vty, "Nexthop count: %u\n", route->nh_list->count); /* Nexthops */ vty_out(vty, "Nexthop:\n"); for (ALL_LIST_ELEMENTS_RO(route->nh_list, node, nh)) { /* nexthop */ inet_ntop(AF_INET6, &nh->address, nexthop, sizeof(nexthop)); ifname = ifindex2ifname(nh->ifindex, VRF_DEFAULT); vty_out(vty, " %s %.*s\n", nexthop, IFNAMSIZ, ifname); } vty_out(vty, "\n"); } static void ospf6_route_show_table_summary(struct vty *vty, struct ospf6_route_table *table) { struct ospf6_route *route, *prev = NULL; int i, pathtype[OSPF6_PATH_TYPE_MAX]; unsigned int number = 0; int nh_count = 0, nhinval = 0, ecmp = 0; int alternative = 0, destination = 0; for (i = 0; i < OSPF6_PATH_TYPE_MAX; i++) pathtype[i] = 0; for (route = ospf6_route_head(table); route; route = ospf6_route_next(route)) { if (prev == NULL || !ospf6_route_is_same(prev, route)) destination++; else alternative++; nh_count = ospf6_num_nexthops(route->nh_list); if (!nh_count) nhinval++; else if (nh_count > 1) ecmp++; pathtype[route->path.type]++; number++; prev = route; } assert(number == table->count); vty_out(vty, "Number of OSPFv3 routes: %d\n", number); vty_out(vty, "Number of Destination: %d\n", destination); vty_out(vty, "Number of Alternative routes: %d\n", alternative); vty_out(vty, "Number of Equal Cost Multi Path: %d\n", ecmp); for (i = OSPF6_PATH_TYPE_INTRA; i <= OSPF6_PATH_TYPE_EXTERNAL2; i++) { vty_out(vty, "Number of %s routes: %d\n", OSPF6_PATH_TYPE_NAME(i), pathtype[i]); } } static void ospf6_route_show_table_prefix(struct vty *vty, struct prefix *prefix, struct ospf6_route_table *table) { struct ospf6_route *route; route = ospf6_route_lookup(prefix, table); if (route == NULL) return; ospf6_route_lock(route); while (route && ospf6_route_is_prefix(prefix, route)) { /* Specifying a prefix will always display details */ ospf6_route_show_detail(vty, route); route = ospf6_route_next(route); } if (route) ospf6_route_unlock(route); } static void ospf6_route_show_table_address(struct vty *vty, struct prefix *prefix, struct ospf6_route_table *table) { struct ospf6_route *route; route = ospf6_route_lookup_bestmatch(prefix, table); if (route == NULL) return; prefix = &route->prefix; ospf6_route_lock(route); while (route && ospf6_route_is_prefix(prefix, route)) { /* Specifying a prefix will always display details */ ospf6_route_show_detail(vty, route); route = ospf6_route_next(route); } if (route) ospf6_route_unlock(route); } static void ospf6_route_show_table_match(struct vty *vty, int detail, struct prefix *prefix, struct ospf6_route_table *table) { struct ospf6_route *route; assert(prefix->family); route = ospf6_route_match_head(prefix, table); while (route) { if (detail) ospf6_route_show_detail(vty, route); else ospf6_route_show(vty, route); route = ospf6_route_match_next(prefix, route); } } static void ospf6_route_show_table_type(struct vty *vty, int detail, uint8_t type, struct ospf6_route_table *table) { struct ospf6_route *route; route = ospf6_route_head(table); while (route) { if (route->path.type == type) { if (detail) ospf6_route_show_detail(vty, route); else ospf6_route_show(vty, route); } route = ospf6_route_next(route); } } static void ospf6_route_show_table(struct vty *vty, int detail, struct ospf6_route_table *table) { struct ospf6_route *route; route = ospf6_route_head(table); while (route) { if (detail) ospf6_route_show_detail(vty, route); else ospf6_route_show(vty, route); route = ospf6_route_next(route); } } int ospf6_route_table_show(struct vty *vty, int argc_start, int argc, struct cmd_token **argv, struct ospf6_route_table *table) { int summary = 0; int match = 0; int detail = 0; int slash = 0; int isprefix = 0; int i, ret; struct prefix prefix; uint8_t type = 0; memset(&prefix, 0, sizeof(struct prefix)); for (i = argc_start; i < argc; i++) { if (strmatch(argv[i]->text, "summary")) { summary++; continue; } if (strmatch(argv[i]->text, "intra-area")) { type = OSPF6_PATH_TYPE_INTRA; continue; } if (strmatch(argv[i]->text, "inter-area")) { type = OSPF6_PATH_TYPE_INTER; continue; } if (strmatch(argv[i]->text, "external-1")) { type = OSPF6_PATH_TYPE_EXTERNAL1; continue; } if (strmatch(argv[i]->text, "external-2")) { type = OSPF6_PATH_TYPE_EXTERNAL2; continue; } if (strmatch(argv[i]->text, "detail")) { detail++; continue; } if (strmatch(argv[i]->text, "match")) { match++; continue; } ret = str2prefix(argv[i]->arg, &prefix); if (ret == 1 && prefix.family == AF_INET6) { isprefix++; if (strchr(argv[i]->arg, '/')) slash++; continue; } vty_out(vty, "Malformed argument: %s\n", argv[i]->arg); return CMD_SUCCESS; } /* Give summary of this route table */ if (summary) { ospf6_route_show_table_summary(vty, table); return CMD_SUCCESS; } /* Give exact prefix-match route */ if (isprefix && !match) { /* If exact address, give best matching route */ if (!slash) ospf6_route_show_table_address(vty, &prefix, table); else ospf6_route_show_table_prefix(vty, &prefix, table); return CMD_SUCCESS; } if (match) ospf6_route_show_table_match(vty, detail, &prefix, table); else if (type) ospf6_route_show_table_type(vty, detail, type, table); else ospf6_route_show_table(vty, detail, table); return CMD_SUCCESS; } static void ospf6_linkstate_show_header(struct vty *vty) { vty_out(vty, "%-7s %-15s %-15s %-8s %-14s %s\n", "Type", "Router-ID", "Net-ID", "Rtr-Bits", "Options", "Cost"); } static void ospf6_linkstate_show(struct vty *vty, struct ospf6_route *route) { uint32_t router, id; char routername[16], idname[16], rbits[16], options[16]; router = ospf6_linkstate_prefix_adv_router(&route->prefix); inet_ntop(AF_INET, &router, routername, sizeof(routername)); id = ospf6_linkstate_prefix_id(&route->prefix); inet_ntop(AF_INET, &id, idname, sizeof(idname)); ospf6_capability_printbuf(route->path.router_bits, rbits, sizeof(rbits)); ospf6_options_printbuf(route->path.options, options, sizeof(options)); if (ntohl(id)) vty_out(vty, "%-7s %-15s %-15s %-8s %-14s %lu\n", "Network", routername, idname, rbits, options, (unsigned long)route->path.cost); else vty_out(vty, "%-7s %-15s %-15s %-8s %-14s %lu\n", "Router", routername, idname, rbits, options, (unsigned long)route->path.cost); } static void ospf6_linkstate_show_table_exact(struct vty *vty, struct prefix *prefix, struct ospf6_route_table *table) { struct ospf6_route *route; route = ospf6_route_lookup(prefix, table); if (route == NULL) return; ospf6_route_lock(route); while (route && ospf6_route_is_prefix(prefix, route)) { /* Specifying a prefix will always display details */ ospf6_route_show_detail(vty, route); route = ospf6_route_next(route); } if (route) ospf6_route_unlock(route); } static void ospf6_linkstate_show_table(struct vty *vty, int detail, struct ospf6_route_table *table) { struct ospf6_route *route; if (!detail) ospf6_linkstate_show_header(vty); route = ospf6_route_head(table); while (route) { if (detail) ospf6_route_show_detail(vty, route); else ospf6_linkstate_show(vty, route); route = ospf6_route_next(route); } } int ospf6_linkstate_table_show(struct vty *vty, int idx_ipv4, int argc, struct cmd_token **argv, struct ospf6_route_table *table) { int detail = 0; int is_id = 0; int is_router = 0; int i, ret; struct prefix router, id, prefix; memset(&router, 0, sizeof(struct prefix)); memset(&id, 0, sizeof(struct prefix)); memset(&prefix, 0, sizeof(struct prefix)); for (i = idx_ipv4; i < argc; i++) { if (strmatch(argv[i]->text, "detail")) { detail++; continue; } if (!is_router) { ret = str2prefix(argv[i]->arg, &router); if (ret == 1 && router.family == AF_INET) { is_router++; continue; } vty_out(vty, "Malformed argument: %s\n", argv[i]->arg); return CMD_SUCCESS; } if (!is_id) { ret = str2prefix(argv[i]->arg, &id); if (ret == 1 && id.family == AF_INET) { is_id++; continue; } vty_out(vty, "Malformed argument: %s\n", argv[i]->arg); return CMD_SUCCESS; } vty_out(vty, "Malformed argument: %s\n", argv[i]->arg); return CMD_SUCCESS; } if (is_router) ospf6_linkstate_prefix(router.u.prefix4.s_addr, id.u.prefix4.s_addr, &prefix); if (prefix.family) ospf6_linkstate_show_table_exact(vty, &prefix, table); else ospf6_linkstate_show_table(vty, detail, table); return CMD_SUCCESS; } void ospf6_brouter_show_header(struct vty *vty) { vty_out(vty, "%-15s %-8s %-14s %-10s %-15s\n", "Router-ID", "Rtr-Bits", "Options", "Path-Type", "Area"); } void ospf6_brouter_show(struct vty *vty, struct ospf6_route *route) { uint32_t adv_router; char adv[16], rbits[16], options[16], area[16]; adv_router = ospf6_linkstate_prefix_adv_router(&route->prefix); inet_ntop(AF_INET, &adv_router, adv, sizeof(adv)); ospf6_capability_printbuf(route->path.router_bits, rbits, sizeof(rbits)); ospf6_options_printbuf(route->path.options, options, sizeof(options)); inet_ntop(AF_INET, &route->path.area_id, area, sizeof(area)); /* vty_out (vty, "%-15s %-8s %-14s %-10s %-15s\n", "Router-ID", "Rtr-Bits", "Options", "Path-Type", "Area"); */ vty_out(vty, "%-15s %-8s %-14s %-10s %-15s\n", adv, rbits, options, OSPF6_PATH_TYPE_NAME(route->path.type), area); } DEFUN (debug_ospf6_route, debug_ospf6_route_cmd, "debug ospf6 route ", DEBUG_STR OSPF6_STR "Debug routes\n" "Debug route table calculation\n" "Debug intra-area route calculation\n" "Debug inter-area route calculation\n" "Debug route memory use\n" ) { int idx_type = 3; unsigned char level = 0; if (!strcmp(argv[idx_type]->text, "table")) level = OSPF6_DEBUG_ROUTE_TABLE; else if (!strcmp(argv[idx_type]->text, "intra-area")) level = OSPF6_DEBUG_ROUTE_INTRA; else if (!strcmp(argv[idx_type]->text, "inter-area")) level = OSPF6_DEBUG_ROUTE_INTER; else if (!strcmp(argv[idx_type]->text, "memory")) level = OSPF6_DEBUG_ROUTE_MEMORY; OSPF6_DEBUG_ROUTE_ON(level); return CMD_SUCCESS; } DEFUN (no_debug_ospf6_route, no_debug_ospf6_route_cmd, "no debug ospf6 route ", NO_STR DEBUG_STR OSPF6_STR "Debug routes\n" "Debug route table calculation\n" "Debug intra-area route calculation\n" "Debug inter-area route calculation\n" "Debug route memory use\n") { int idx_type = 4; unsigned char level = 0; if (!strcmp(argv[idx_type]->text, "table")) level = OSPF6_DEBUG_ROUTE_TABLE; else if (!strcmp(argv[idx_type]->text, "intra-area")) level = OSPF6_DEBUG_ROUTE_INTRA; else if (!strcmp(argv[idx_type]->text, "inter-area")) level = OSPF6_DEBUG_ROUTE_INTER; else if (!strcmp(argv[idx_type]->text, "memory")) level = OSPF6_DEBUG_ROUTE_MEMORY; OSPF6_DEBUG_ROUTE_OFF(level); return CMD_SUCCESS; } int config_write_ospf6_debug_route(struct vty *vty) { if (IS_OSPF6_DEBUG_ROUTE(TABLE)) vty_out(vty, "debug ospf6 route table\n"); if (IS_OSPF6_DEBUG_ROUTE(INTRA)) vty_out(vty, "debug ospf6 route intra-area\n"); if (IS_OSPF6_DEBUG_ROUTE(INTER)) vty_out(vty, "debug ospf6 route inter-area\n"); if (IS_OSPF6_DEBUG_ROUTE(MEMORY)) vty_out(vty, "debug ospf6 route memory\n"); return 0; } void install_element_ospf6_debug_route(void) { install_element(ENABLE_NODE, &debug_ospf6_route_cmd); install_element(ENABLE_NODE, &no_debug_ospf6_route_cmd); install_element(CONFIG_NODE, &debug_ospf6_route_cmd); install_element(CONFIG_NODE, &no_debug_ospf6_route_cmd); } frr-7.2.1/ospf6d/ospf6_route.h0000644000000000000000000003047313610377563013050 00000000000000/* * Copyright (C) 2003 Yasuhiro Ohara * * This file is part of GNU Zebra. * * GNU Zebra is free software; you can redistribute it and/or modify it * under the terms of the GNU General Public License as published by the * Free Software Foundation; either version 2, or (at your option) any * later version. * * GNU Zebra 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; see the file COPYING; if not, write to the Free Software * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA */ #ifndef OSPF6_ROUTE_H #define OSPF6_ROUTE_H #include "command.h" #include "zclient.h" #define OSPF6_MULTI_PATH_LIMIT 4 /* Debug option */ extern unsigned char conf_debug_ospf6_route; #define OSPF6_DEBUG_ROUTE_TABLE 0x01 #define OSPF6_DEBUG_ROUTE_INTRA 0x02 #define OSPF6_DEBUG_ROUTE_INTER 0x04 #define OSPF6_DEBUG_ROUTE_MEMORY 0x80 #define OSPF6_DEBUG_ROUTE_ON(level) (conf_debug_ospf6_route |= (level)) #define OSPF6_DEBUG_ROUTE_OFF(level) (conf_debug_ospf6_route &= ~(level)) #define IS_OSPF6_DEBUG_ROUTE(e) (conf_debug_ospf6_route & OSPF6_DEBUG_ROUTE_##e) /* Nexthop */ struct ospf6_nexthop { /* Interface index */ ifindex_t ifindex; /* IP address, if any */ struct in6_addr address; }; #define ospf6_nexthop_is_set(x) \ ((x)->ifindex || !IN6_IS_ADDR_UNSPECIFIED(&(x)->address)) #define ospf6_nexthop_is_same(a, b) \ ((a)->ifindex == (b)->ifindex \ && IN6_ARE_ADDR_EQUAL(&(a)->address, &(b)->address)) #define ospf6_nexthop_clear(x) \ do { \ (x)->ifindex = 0; \ memset(&(x)->address, 0, sizeof(struct in6_addr)); \ } while (0) #define ospf6_nexthop_copy(a, b) \ do { \ (a)->ifindex = (b)->ifindex; \ memcpy(&(a)->address, &(b)->address, sizeof(struct in6_addr)); \ } while (0) /* Path */ struct ospf6_ls_origin { uint16_t type; uint32_t id; uint32_t adv_router; }; struct ospf6_path { /* Link State Origin */ struct ospf6_ls_origin origin; /* Router bits */ uint8_t router_bits; /* Optional Capabilities */ uint8_t options[3]; /* Prefix Options */ uint8_t prefix_options; /* Associated Area */ uint32_t area_id; /* Path-type */ uint8_t type; uint8_t subtype; /* only used for redistribute i.e ZEBRA_ROUTE_XXX */ /* Cost */ uint8_t metric_type; uint32_t cost; struct prefix ls_prefix; union { uint32_t cost_e2; uint32_t cost_config; } u; uint32_t tag; /* nh list for this path */ struct list *nh_list; }; #define OSPF6_PATH_TYPE_NONE 0 #define OSPF6_PATH_TYPE_INTRA 1 #define OSPF6_PATH_TYPE_INTER 2 #define OSPF6_PATH_TYPE_EXTERNAL1 3 #define OSPF6_PATH_TYPE_EXTERNAL2 4 #define OSPF6_PATH_TYPE_REDISTRIBUTE 5 #define OSPF6_PATH_TYPE_MAX 6 #define OSPF6_PATH_SUBTYPE_DEFAULT_RT 1 #define OSPF6_PATH_COST_IS_CONFIGURED(path) (path.u.cost_config != OSPF_AREA_RANGE_COST_UNSPEC) #include "prefix.h" #include "table.h" #include "bitfield.h" struct ospf6_route { struct route_node *rnode; struct ospf6_route_table *table; struct ospf6_route *prev; struct ospf6_route *next; unsigned int lock; /* Destination Type */ uint8_t type; /* XXX: It would likely be better to use separate struct in_addr's * for the advertising router-ID and prefix IDs, instead of stuffing * them * into one. See also XXX below. */ /* Destination ID */ struct prefix prefix; /* Time */ struct timeval installed; struct timeval changed; /* flag */ uint8_t flag; /* route option */ void *route_option; /* link state id for advertising */ uint32_t linkstate_id; /* path */ struct ospf6_path path; /* List of Paths. */ struct list *paths; /* nexthop */ struct list *nh_list; }; #define OSPF6_DEST_TYPE_NONE 0 #define OSPF6_DEST_TYPE_ROUTER 1 #define OSPF6_DEST_TYPE_NETWORK 2 #define OSPF6_DEST_TYPE_DISCARD 3 #define OSPF6_DEST_TYPE_LINKSTATE 4 #define OSPF6_DEST_TYPE_RANGE 5 #define OSPF6_DEST_TYPE_MAX 6 #define OSPF6_ROUTE_CHANGE 0x01 #define OSPF6_ROUTE_ADD 0x02 #define OSPF6_ROUTE_REMOVE 0x04 #define OSPF6_ROUTE_BEST 0x08 #define OSPF6_ROUTE_ACTIVE_SUMMARY 0x10 #define OSPF6_ROUTE_DO_NOT_ADVERTISE 0x20 #define OSPF6_ROUTE_WAS_REMOVED 0x40 #define OSPF6_ROUTE_BLACKHOLE_ADDED 0x80 struct ospf6_route_table { int scope_type; int table_type; void *scope; /* patricia tree */ struct route_table *table; uint32_t count; bitfield_t idspace; /* hooks */ void (*hook_add)(struct ospf6_route *); void (*hook_change)(struct ospf6_route *); void (*hook_remove)(struct ospf6_route *); }; #define OSPF6_SCOPE_TYPE_NONE 0 #define OSPF6_SCOPE_TYPE_GLOBAL 1 #define OSPF6_SCOPE_TYPE_AREA 2 #define OSPF6_SCOPE_TYPE_INTERFACE 3 #define OSPF6_TABLE_TYPE_NONE 0 #define OSPF6_TABLE_TYPE_ROUTES 1 #define OSPF6_TABLE_TYPE_BORDER_ROUTERS 2 #define OSPF6_TABLE_TYPE_CONNECTED_ROUTES 3 #define OSPF6_TABLE_TYPE_EXTERNAL_ROUTES 4 #define OSPF6_TABLE_TYPE_SPF_RESULTS 5 #define OSPF6_TABLE_TYPE_PREFIX_RANGES 6 #define OSPF6_TABLE_TYPE_SUMMARY_PREFIXES 7 #define OSPF6_TABLE_TYPE_SUMMARY_ROUTERS 8 #define OSPF6_ROUTE_TABLE_CREATE(s, t) \ ospf6_route_table_create(OSPF6_SCOPE_TYPE_##s, OSPF6_TABLE_TYPE_##t) extern const char *ospf6_dest_type_str[OSPF6_DEST_TYPE_MAX]; extern const char *ospf6_dest_type_substr[OSPF6_DEST_TYPE_MAX]; #define OSPF6_DEST_TYPE_NAME(x) \ (0 < (x) && (x) < OSPF6_DEST_TYPE_MAX ? ospf6_dest_type_str[(x)] \ : ospf6_dest_type_str[0]) #define OSPF6_DEST_TYPE_SUBSTR(x) \ (0 < (x) && (x) < OSPF6_DEST_TYPE_MAX ? ospf6_dest_type_substr[(x)] \ : ospf6_dest_type_substr[0]) extern const char *ospf6_path_type_str[OSPF6_PATH_TYPE_MAX]; extern const char *ospf6_path_type_substr[OSPF6_PATH_TYPE_MAX]; #define OSPF6_PATH_TYPE_NAME(x) \ (0 < (x) && (x) < OSPF6_PATH_TYPE_MAX ? ospf6_path_type_str[(x)] \ : ospf6_path_type_str[0]) #define OSPF6_PATH_TYPE_SUBSTR(x) \ (0 < (x) && (x) < OSPF6_PATH_TYPE_MAX ? ospf6_path_type_substr[(x)] \ : ospf6_path_type_substr[0]) #define OSPF6_ROUTE_ADDRESS_STR "Display the route bestmatches the address\n" #define OSPF6_ROUTE_PREFIX_STR "Display the route\n" #define OSPF6_ROUTE_MATCH_STR "Display the route matches the prefix\n" #define ospf6_route_is_prefix(p, r) \ (memcmp(p, &(r)->prefix, sizeof(struct prefix)) == 0) #define ospf6_route_is_same(ra, rb) (prefix_same(&(ra)->prefix, &(rb)->prefix)) #define ospf6_route_is_same_origin(ra, rb) \ ((ra)->path.area_id == (rb)->path.area_id \ && memcmp(&(ra)->path.origin, &(rb)->path.origin, \ sizeof(struct ospf6_ls_origin)) \ == 0) #define ospf6_route_is_identical(ra, rb) \ ((ra)->type == (rb)->type \ && memcmp(&(ra)->prefix, &(rb)->prefix, sizeof(struct prefix)) == 0 \ && memcmp(&(ra)->path, &(rb)->path, sizeof(struct ospf6_path)) == 0 \ && listcount(ra->paths) == listcount(rb->paths) \ && ospf6_route_cmp_nexthops(ra, rb) == 0) #define ospf6_route_is_best(r) (CHECK_FLAG ((r)->flag, OSPF6_ROUTE_BEST)) #define ospf6_linkstate_prefix_adv_router(x) ((x)->u.lp.id.s_addr) #define ospf6_linkstate_prefix_id(x) ((x)->u.lp.adv_router.s_addr) #define ADV_ROUTER_IN_PREFIX(x) ((x)->u.lp.id.s_addr) /* Function prototype */ extern void ospf6_linkstate_prefix(uint32_t adv_router, uint32_t id, struct prefix *prefix); extern void ospf6_linkstate_prefix2str(struct prefix *prefix, char *buf, int size); extern struct ospf6_nexthop *ospf6_nexthop_create(void); extern int ospf6_nexthop_cmp(struct ospf6_nexthop *a, struct ospf6_nexthop *b); extern void ospf6_nexthop_delete(struct ospf6_nexthop *nh); extern void ospf6_clear_nexthops(struct list *nh_list); extern int ospf6_num_nexthops(struct list *nh_list); extern void ospf6_copy_nexthops(struct list *dst, struct list *src); extern void ospf6_merge_nexthops(struct list *dst, struct list *src); extern void ospf6_add_nexthop(struct list *nh_list, int ifindex, struct in6_addr *addr); extern int ospf6_num_nexthops(struct list *nh_list); extern int ospf6_route_cmp_nexthops(struct ospf6_route *a, struct ospf6_route *b); extern void ospf6_route_zebra_copy_nexthops(struct ospf6_route *route, struct zapi_nexthop nexthops[], int entries); extern int ospf6_route_get_first_nh_index(struct ospf6_route *route); /* Hide abstraction of nexthop implementation in route from outsiders */ #define ospf6_route_copy_nexthops(dst, src) ospf6_copy_nexthops(dst->nh_list, src->nh_list) #define ospf6_route_merge_nexthops(dst, src) ospf6_merge_nexthops(dst->nh_list, src->nh_list) #define ospf6_route_num_nexthops(route) ospf6_num_nexthops(route->nh_list) #define ospf6_route_add_nexthop(route, ifindex, addr) \ ospf6_add_nexthop(route->nh_list, ifindex, addr) extern struct ospf6_route *ospf6_route_create(void); extern void ospf6_route_delete(struct ospf6_route *); extern struct ospf6_route *ospf6_route_copy(struct ospf6_route *route); extern int ospf6_route_cmp(struct ospf6_route *ra, struct ospf6_route *rb); extern void ospf6_route_lock(struct ospf6_route *route); extern void ospf6_route_unlock(struct ospf6_route *route); extern struct ospf6_route *ospf6_route_lookup(struct prefix *prefix, struct ospf6_route_table *table); extern struct ospf6_route * ospf6_route_lookup_identical(struct ospf6_route *route, struct ospf6_route_table *table); extern struct ospf6_route * ospf6_route_lookup_bestmatch(struct prefix *prefix, struct ospf6_route_table *table); extern struct ospf6_route *ospf6_route_add(struct ospf6_route *route, struct ospf6_route_table *table); extern void ospf6_route_remove(struct ospf6_route *route, struct ospf6_route_table *table); extern struct ospf6_route *ospf6_route_head(struct ospf6_route_table *table); extern struct ospf6_route *ospf6_route_next(struct ospf6_route *route); extern struct ospf6_route *ospf6_route_best_next(struct ospf6_route *route); extern struct ospf6_route * ospf6_route_match_head(struct prefix *prefix, struct ospf6_route_table *table); extern struct ospf6_route *ospf6_route_match_next(struct prefix *prefix, struct ospf6_route *route); extern void ospf6_route_remove_all(struct ospf6_route_table *); extern struct ospf6_route_table *ospf6_route_table_create(int s, int t); extern void ospf6_route_table_delete(struct ospf6_route_table *); extern void ospf6_route_dump(struct ospf6_route_table *table); extern void ospf6_route_show(struct vty *vty, struct ospf6_route *route); extern void ospf6_route_show_detail(struct vty *vty, struct ospf6_route *route); extern int ospf6_route_table_show(struct vty *, int, int, struct cmd_token **, struct ospf6_route_table *); extern int ospf6_linkstate_table_show(struct vty *vty, int idx_ipv4, int argc, struct cmd_token **argv, struct ospf6_route_table *table); extern void ospf6_brouter_show_header(struct vty *vty); extern void ospf6_brouter_show(struct vty *vty, struct ospf6_route *route); extern int config_write_ospf6_debug_route(struct vty *vty); extern void install_element_ospf6_debug_route(void); extern void ospf6_route_init(void); extern void ospf6_clean(void); extern void ospf6_path_free(struct ospf6_path *op); extern struct ospf6_path *ospf6_path_dup(struct ospf6_path *path); extern void ospf6_copy_paths(struct list *dst, struct list *src); #endif /* OSPF6_ROUTE_H */ frr-7.2.1/ospf6d/ospf6_snmp.c0000644000000000000000000010675513610377563012671 00000000000000/* OSPFv3 SNMP support * Copyright (C) 2004 Yasuhiro Ohara * * This file is part of GNU Zebra. * * GNU Zebra is free software; you can redistribute it and/or modify it * under the terms of the GNU General Public License as published by the * Free Software Foundation; either version 2, or (at your option) any * later version. * * GNU Zebra 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; see the file COPYING; if not, write to the Free Software * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA */ #include #include #include #include "log.h" #include "vty.h" #include "linklist.h" #include "vector.h" #include "vrf.h" #include "smux.h" #include "libfrr.h" #include "version.h" #include "ospf6_proto.h" #include "ospf6_lsa.h" #include "ospf6_lsdb.h" #include "ospf6_route.h" #include "ospf6_top.h" #include "ospf6_area.h" #include "ospf6_interface.h" #include "ospf6_message.h" #include "ospf6_neighbor.h" #include "ospf6_abr.h" #include "ospf6_asbr.h" #include "ospf6d.h" /* OSPFv3-MIB */ #define OSPFv3MIB 1,3,6,1,2,1,191 /* OSPFv3 MIB General Group values. */ #define OSPFv3ROUTERID 1 #define OSPFv3ADMINSTAT 2 #define OSPFv3VERSIONNUMBER 3 #define OSPFv3AREABDRRTRSTATUS 4 #define OSPFv3ASBDRRTRSTATUS 5 #define OSPFv3ASSCOPELSACOUNT 6 #define OSPFv3ASSCOPELSACHECKSUMSUM 7 #define OSPFv3ORIGINATENEWLSAS 8 #define OSPFv3RXNEWLSAS 9 #define OSPFv3EXTLSACOUNT 10 #define OSPFv3EXTAREALSDBLIMIT 11 #define OSPFv3EXITOVERFLOWINTERVAL 12 #define OSPFv3DEMANDEXTENSIONS 13 #define OSPFv3REFERENCEBANDWIDTH 14 #define OSPFv3RESTARTSUPPORT 15 #define OSPFv3RESTARTINTERVAL 16 #define OSPFv3RESTARTSTRICTLSACHECKING 17 #define OSPFv3RESTARTSTATUS 18 #define OSPFv3RESTARTAGE 19 #define OSPFv3RESTARTEXITREASON 20 #define OSPFv3NOTIFICATIONENABLE 21 #define OSPFv3STUBROUTERSUPPORT 22 #define OSPFv3STUBROUTERADVERTISEMENT 23 #define OSPFv3DISCONTINUITYTIME 24 #define OSPFv3RESTARTTIME 25 /* OSPFv3 MIB Area Table values: ospfv3AreaTable */ #define OSPFv3IMPORTASEXTERN 2 #define OSPFv3AREASPFRUNS 3 #define OSPFv3AREABDRRTRCOUNT 4 #define OSPFv3AREAASBDRRTRCOUNT 5 #define OSPFv3AREASCOPELSACOUNT 6 #define OSPFv3AREASCOPELSACKSUMSUM 7 #define OSPFv3AREASUMMARY 8 #define OSPFv3AREAROWSTATUS 9 #define OSPFv3AREASTUBMETRIC 10 #define OSPFv3AREANSSATRANSLATORROLE 11 #define OSPFv3AREANSSATRANSLATORSTATE 12 #define OSPFv3AREANSSATRANSLATORSTABINTERVAL 13 #define OSPFv3AREANSSATRANSLATOREVENTS 14 #define OSPFv3AREASTUBMETRICTYPE 15 #define OSPFv3AREATEENABLED 16 /* OSPFv3 MIB * Lsdb Table values: ospfv3*LsdbTable */ #define OSPFv3WWLSDBSEQUENCE 1 #define OSPFv3WWLSDBAGE 2 #define OSPFv3WWLSDBCHECKSUM 3 #define OSPFv3WWLSDBADVERTISEMENT 4 #define OSPFv3WWLSDBTYPEKNOWN 5 /* Three first bits are to identify column */ #define OSPFv3WWCOLUMN 0x7 /* Then we use other bits to identify table */ #define OSPFv3WWASTABLE (1 << 3) #define OSPFv3WWAREATABLE (1 << 4) #define OSPFv3WWLINKTABLE (1 << 5) #define OSPFv3WWVIRTLINKTABLE (1 << 6) /* OSPFv3 MIB Host Table values: ospfv3HostTable */ #define OSPFv3HOSTMETRIC 3 #define OSPFv3HOSTROWSTATUS 4 #define OSPFv3HOSTAREAID 5 /* OSPFv3 MIB Interface Table values: ospfv3IfTable */ #define OSPFv3IFAREAID 3 #define OSPFv3IFTYPE 4 #define OSPFv3IFADMINSTATUS 5 #define OSPFv3IFRTRPRIORITY 6 #define OSPFv3IFTRANSITDELAY 7 #define OSPFv3IFRETRANSINTERVAL 8 #define OSPFv3IFHELLOINTERVAL 9 #define OSPFv3IFRTRDEADINTERVAL 10 #define OSPFv3IFPOLLINTERVAL 11 #define OSPFv3IFSTATE 12 #define OSPFv3IFDESIGNATEDROUTER 13 #define OSPFv3IFBACKUPDESIGNATEDROUTER 14 #define OSPFv3IFEVENTS 15 #define OSPFv3IFROWSTATUS 16 #define OSPFv3IFDEMAND 17 #define OSPFv3IFMETRICVALUE 18 #define OSPFv3IFLINKSCOPELSACOUNT 19 #define OSPFv3IFLINKLSACKSUMSUM 20 #define OSPFv3IFDEMANDNBRPROBE 21 #define OSPFv3IFDEMANDNBRPROBERETRANSLIMIT 22 #define OSPFv3IFDEMANDNBRPROBEINTERVAL 23 #define OSPFv3IFTEDISABLED 24 #define OSPFv3IFLINKLSASUPPRESSION 25 /* OSPFv3 MIB Virtual Interface Table values: ospfv3VirtIfTable */ #define OSPFv3VIRTIFINDEX 3 #define OSPFv3VIRTIFINSTID 4 #define OSPFv3VIRTIFTRANSITDELAY 5 #define OSPFv3VIRTIFRETRANSINTERVAL 6 #define OSPFv3VIRTIFHELLOINTERVAL 7 #define OSPFv3VIRTIFRTRDEADINTERVAL 8 #define OSPFv3VIRTIFSTATE 9 #define OSPFv3VIRTIFEVENTS 10 #define OSPFv3VIRTIFROWSTATUS 11 #define OSPFv3VIRTIFLINKSCOPELSACOUNT 12 #define OSPFv3VIRTIFLINKLSACKSUMSUM 13 /* OSPFv3 MIB Neighbors Table values: ospfv3NbrTable */ #define OSPFv3NBRADDRESSTYPE 4 #define OSPFv3NBRADDRESS 5 #define OSPFv3NBROPTIONS 6 #define OSPFv3NBRPRIORITY 7 #define OSPFv3NBRSTATE 8 #define OSPFv3NBREVENTS 9 #define OSPFv3NBRLSRETRANSQLEN 10 #define OSPFv3NBRHELLOSUPPRESSED 11 #define OSPFv3NBRIFID 12 #define OSPFv3NBRRESTARTHELPERSTATUS 13 #define OSPFv3NBRRESTARTHELPERAGE 14 #define OSPFv3NBRRESTARTHELPEREXITREASON 15 /* OSPFv3 MIB Configured Neighbors Table values: ospfv3CfgNbrTable */ #define OSPFv3CFGNBRPRIORITY 5 #define OSPFv3CFGNBRROWSTATUS 6 /* OSPFv3 MIB Virtual Neighbors Table values: ospfv3VirtNbrTable */ #define OSPFv3VIRTNBRIFINDEX 3 #define OSPFv3VIRTNBRIFINSTID 4 #define OSPFv3VIRTNBRADDRESSTYPE 5 #define OSPFv3VIRTNBRADDRESS 6 #define OSPFv3VIRTNBROPTIONS 7 #define OSPFv3VIRTNBRSTATE 8 #define OSPFv3VIRTNBREVENTS 9 #define OSPFv3VIRTNBRLSRETRANSQLEN 10 #define OSPFv3VIRTNBRHELLOSUPPRESSED 11 #define OSPFv3VIRTNBRIFID 12 #define OSPFv3VIRTNBRRESTARTHELPERSTATUS 13 #define OSPFv3VIRTNBRRESTARTHELPERAGE 14 #define OSPFv3VIRTNBRRESTARTHELPEREXITREASON 15 /* OSPFv3 MIB Area Aggregate Table values: ospfv3AreaAggregateTable */ #define OSPFv3AREAAGGREGATEROWSTATUS 6 #define OSPFv3AREAAGGREGATEEFFECT 7 #define OSPFv3AREAAGGREGATEROUTETAG 8 /* SYNTAX Status from OSPF-MIB. */ #define OSPF_STATUS_ENABLED 1 #define OSPF_STATUS_DISABLED 2 /* SNMP value hack. */ #define COUNTER ASN_COUNTER #define INTEGER ASN_INTEGER #define GAUGE ASN_GAUGE #define UNSIGNED ASN_UNSIGNED #define TIMETICKS ASN_TIMETICKS #define IPADDRESS ASN_IPADDRESS #define STRING ASN_OCTET_STR /* For return values e.g. SNMP_INTEGER macro */ SNMP_LOCAL_VARIABLES /* OSPFv3-MIB instances. */ static oid ospfv3_oid[] = {OSPFv3MIB}; static oid ospfv3_trap_oid[] = {OSPFv3MIB, 0}; /* Hook functions. */ static uint8_t *ospfv3GeneralGroup(struct variable *, oid *, size_t *, int, size_t *, WriteMethod **); static uint8_t *ospfv3AreaEntry(struct variable *, oid *, size_t *, int, size_t *, WriteMethod **); static uint8_t *ospfv3WwLsdbEntry(struct variable *, oid *, size_t *, int, size_t *, WriteMethod **); static uint8_t *ospfv3NbrEntry(struct variable *, oid *, size_t *, int, size_t *, WriteMethod **); static uint8_t *ospfv3IfEntry(struct variable *, oid *, size_t *, int, size_t *, WriteMethod **); static struct variable ospfv3_variables[] = { /* OSPF general variables */ {OSPFv3ROUTERID, UNSIGNED, RWRITE, ospfv3GeneralGroup, 3, {1, 1, 1}}, {OSPFv3ADMINSTAT, INTEGER, RWRITE, ospfv3GeneralGroup, 3, {1, 1, 2}}, {OSPFv3VERSIONNUMBER, INTEGER, RONLY, ospfv3GeneralGroup, 3, {1, 1, 3}}, {OSPFv3AREABDRRTRSTATUS, INTEGER, RONLY, ospfv3GeneralGroup, 3, {1, 1, 4}}, {OSPFv3ASBDRRTRSTATUS, INTEGER, RWRITE, ospfv3GeneralGroup, 3, {1, 1, 5}}, {OSPFv3ASSCOPELSACOUNT, GAUGE, RONLY, ospfv3GeneralGroup, 3, {1, 1, 6}}, {OSPFv3ASSCOPELSACHECKSUMSUM, UNSIGNED, RONLY, ospfv3GeneralGroup, 3, {1, 1, 7}}, {OSPFv3ORIGINATENEWLSAS, COUNTER, RONLY, ospfv3GeneralGroup, 3, {1, 1, 8}}, {OSPFv3RXNEWLSAS, COUNTER, RONLY, ospfv3GeneralGroup, 3, {1, 1, 9}}, {OSPFv3EXTLSACOUNT, GAUGE, RONLY, ospfv3GeneralGroup, 3, {1, 1, 10}}, {OSPFv3EXTAREALSDBLIMIT, INTEGER, RWRITE, ospfv3GeneralGroup, 3, {1, 1, 11}}, {OSPFv3EXITOVERFLOWINTERVAL, UNSIGNED, RWRITE, ospfv3GeneralGroup, 3, {1, 1, 12}}, {OSPFv3DEMANDEXTENSIONS, INTEGER, RWRITE, ospfv3GeneralGroup, 3, {1, 1, 13}}, {OSPFv3REFERENCEBANDWIDTH, UNSIGNED, RWRITE, ospfv3GeneralGroup, 3, {1, 1, 14}}, {OSPFv3RESTARTSUPPORT, INTEGER, RWRITE, ospfv3GeneralGroup, 3, {1, 1, 15}}, {OSPFv3RESTARTINTERVAL, UNSIGNED, RWRITE, ospfv3GeneralGroup, 3, {1, 1, 16}}, {OSPFv3RESTARTSTRICTLSACHECKING, INTEGER, RWRITE, ospfv3GeneralGroup, 3, {1, 1, 17}}, {OSPFv3RESTARTSTATUS, INTEGER, RONLY, ospfv3GeneralGroup, 3, {1, 1, 18}}, {OSPFv3RESTARTAGE, UNSIGNED, RONLY, ospfv3GeneralGroup, 3, {1, 1, 19}}, {OSPFv3RESTARTEXITREASON, INTEGER, RONLY, ospfv3GeneralGroup, 3, {1, 1, 20}}, {OSPFv3NOTIFICATIONENABLE, INTEGER, RWRITE, ospfv3GeneralGroup, 3, {1, 1, 21}}, {OSPFv3STUBROUTERSUPPORT, INTEGER, RONLY, ospfv3GeneralGroup, 3, {1, 1, 22}}, {OSPFv3STUBROUTERADVERTISEMENT, INTEGER, RWRITE, ospfv3GeneralGroup, 3, {1, 1, 23}}, {OSPFv3DISCONTINUITYTIME, TIMETICKS, RONLY, ospfv3GeneralGroup, 3, {1, 1, 24}}, {OSPFv3RESTARTTIME, TIMETICKS, RONLY, ospfv3GeneralGroup, 3, {1, 1, 25}}, /* OSPFv3 Area Data Structure */ {OSPFv3IMPORTASEXTERN, INTEGER, RWRITE, ospfv3AreaEntry, 4, {1, 2, 1, 2}}, {OSPFv3AREASPFRUNS, COUNTER, RONLY, ospfv3AreaEntry, 4, {1, 2, 1, 3}}, {OSPFv3AREABDRRTRCOUNT, GAUGE, RONLY, ospfv3AreaEntry, 4, {1, 2, 1, 4}}, {OSPFv3AREAASBDRRTRCOUNT, GAUGE, RONLY, ospfv3AreaEntry, 4, {1, 2, 1, 5}}, {OSPFv3AREASCOPELSACOUNT, GAUGE, RONLY, ospfv3AreaEntry, 4, {1, 2, 1, 6}}, {OSPFv3AREASCOPELSACKSUMSUM, UNSIGNED, RONLY, ospfv3AreaEntry, 4, {1, 2, 1, 7}}, {OSPFv3AREASUMMARY, INTEGER, RWRITE, ospfv3AreaEntry, 4, {1, 2, 1, 8}}, {OSPFv3AREAROWSTATUS, INTEGER, RWRITE, ospfv3AreaEntry, 4, {1, 2, 1, 9}}, {OSPFv3AREASTUBMETRIC, INTEGER, RWRITE, ospfv3AreaEntry, 4, {1, 2, 1, 10}}, {OSPFv3AREANSSATRANSLATORROLE, INTEGER, RWRITE, ospfv3AreaEntry, 4, {1, 2, 1, 11}}, {OSPFv3AREANSSATRANSLATORSTATE, INTEGER, RONLY, ospfv3AreaEntry, 4, {1, 2, 1, 12}}, {OSPFv3AREANSSATRANSLATORSTABINTERVAL, UNSIGNED, RWRITE, ospfv3AreaEntry, 4, {1, 2, 1, 13}}, {OSPFv3AREANSSATRANSLATOREVENTS, COUNTER, RONLY, ospfv3AreaEntry, 4, {1, 2, 1, 14}}, {OSPFv3AREASTUBMETRICTYPE, INTEGER, RWRITE, ospfv3AreaEntry, 4, {1, 2, 1, 15}}, {OSPFv3AREATEENABLED, INTEGER, RWRITE, ospfv3AreaEntry, 4, {1, 2, 1, 16}}, /* OSPFv3 AS LSDB */ {OSPFv3WWLSDBSEQUENCE | OSPFv3WWASTABLE, INTEGER, RONLY, ospfv3WwLsdbEntry, 4, {1, 3, 1, 4}}, {OSPFv3WWLSDBAGE | OSPFv3WWASTABLE, UNSIGNED, RONLY, ospfv3WwLsdbEntry, 4, {1, 3, 1, 5}}, {OSPFv3WWLSDBCHECKSUM | OSPFv3WWASTABLE, INTEGER, RONLY, ospfv3WwLsdbEntry, 4, {1, 3, 1, 6}}, {OSPFv3WWLSDBADVERTISEMENT | OSPFv3WWASTABLE, STRING, RONLY, ospfv3WwLsdbEntry, 4, {1, 3, 1, 7}}, {OSPFv3WWLSDBTYPEKNOWN | OSPFv3WWASTABLE, INTEGER, RONLY, ospfv3WwLsdbEntry, 4, {1, 3, 1, 8}}, /* OSPFv3 Area LSDB */ {OSPFv3WWLSDBSEQUENCE | OSPFv3WWAREATABLE, INTEGER, RONLY, ospfv3WwLsdbEntry, 4, {1, 4, 1, 5}}, {OSPFv3WWLSDBAGE | OSPFv3WWAREATABLE, UNSIGNED, RONLY, ospfv3WwLsdbEntry, 4, {1, 4, 1, 6}}, {OSPFv3WWLSDBCHECKSUM | OSPFv3WWAREATABLE, INTEGER, RONLY, ospfv3WwLsdbEntry, 4, {1, 4, 1, 7}}, {OSPFv3WWLSDBADVERTISEMENT | OSPFv3WWAREATABLE, STRING, RONLY, ospfv3WwLsdbEntry, 4, {1, 4, 1, 8}}, {OSPFv3WWLSDBTYPEKNOWN | OSPFv3WWAREATABLE, INTEGER, RONLY, ospfv3WwLsdbEntry, 4, {1, 4, 1, 9}}, /* OSPFv3 Link LSDB */ {OSPFv3WWLSDBSEQUENCE | OSPFv3WWLINKTABLE, INTEGER, RONLY, ospfv3WwLsdbEntry, 4, {1, 5, 1, 6}}, {OSPFv3WWLSDBAGE | OSPFv3WWLINKTABLE, UNSIGNED, RONLY, ospfv3WwLsdbEntry, 4, {1, 5, 1, 7}}, {OSPFv3WWLSDBCHECKSUM | OSPFv3WWLINKTABLE, INTEGER, RONLY, ospfv3WwLsdbEntry, 4, {1, 5, 1, 8}}, {OSPFv3WWLSDBADVERTISEMENT | OSPFv3WWLINKTABLE, STRING, RONLY, ospfv3WwLsdbEntry, 4, {1, 5, 1, 9}}, {OSPFv3WWLSDBTYPEKNOWN | OSPFv3WWLINKTABLE, INTEGER, RONLY, ospfv3WwLsdbEntry, 4, {1, 5, 1, 10}}, /* OSPFv3 interfaces */ {OSPFv3IFAREAID, UNSIGNED, RONLY, ospfv3IfEntry, 4, {1, 7, 1, 3}}, {OSPFv3IFTYPE, INTEGER, RONLY, ospfv3IfEntry, 4, {1, 7, 1, 4}}, {OSPFv3IFADMINSTATUS, INTEGER, RONLY, ospfv3IfEntry, 4, {1, 7, 1, 5}}, {OSPFv3IFRTRPRIORITY, INTEGER, RONLY, ospfv3IfEntry, 4, {1, 7, 1, 6}}, {OSPFv3IFTRANSITDELAY, UNSIGNED, RONLY, ospfv3IfEntry, 4, {1, 7, 1, 7}}, {OSPFv3IFRETRANSINTERVAL, UNSIGNED, RONLY, ospfv3IfEntry, 4, {1, 7, 1, 8}}, {OSPFv3IFHELLOINTERVAL, INTEGER, RONLY, ospfv3IfEntry, 4, {1, 7, 1, 9}}, {OSPFv3IFRTRDEADINTERVAL, UNSIGNED, RONLY, ospfv3IfEntry, 4, {1, 7, 1, 10}}, {OSPFv3IFPOLLINTERVAL, UNSIGNED, RONLY, ospfv3IfEntry, 4, {1, 7, 1, 11}}, {OSPFv3IFSTATE, INTEGER, RONLY, ospfv3IfEntry, 4, {1, 7, 1, 12}}, {OSPFv3IFDESIGNATEDROUTER, UNSIGNED, RONLY, ospfv3IfEntry, 4, {1, 7, 1, 13}}, {OSPFv3IFBACKUPDESIGNATEDROUTER, UNSIGNED, RONLY, ospfv3IfEntry, 4, {1, 7, 1, 14}}, {OSPFv3IFEVENTS, COUNTER, RONLY, ospfv3IfEntry, 4, {1, 7, 1, 15}}, {OSPFv3IFROWSTATUS, INTEGER, RONLY, ospfv3IfEntry, 4, {1, 7, 1, 16}}, {OSPFv3IFDEMAND, INTEGER, RONLY, ospfv3IfEntry, 4, {1, 7, 1, 17}}, {OSPFv3IFMETRICVALUE, INTEGER, RONLY, ospfv3IfEntry, 4, {1, 7, 1, 18}}, {OSPFv3IFLINKSCOPELSACOUNT, GAUGE, RONLY, ospfv3IfEntry, 4, {1, 7, 1, 19}}, {OSPFv3IFLINKLSACKSUMSUM, UNSIGNED, RONLY, ospfv3IfEntry, 4, {1, 7, 1, 20}}, {OSPFv3IFDEMANDNBRPROBE, INTEGER, RONLY, ospfv3IfEntry, 4, {1, 7, 1, 21}}, {OSPFv3IFDEMANDNBRPROBERETRANSLIMIT, UNSIGNED, RONLY, ospfv3IfEntry, 4, {1, 7, 1, 22}}, {OSPFv3IFDEMANDNBRPROBEINTERVAL, UNSIGNED, RONLY, ospfv3IfEntry, 4, {1, 7, 1, 23}}, {OSPFv3IFTEDISABLED, INTEGER, RONLY, ospfv3IfEntry, 4, {1, 7, 1, 24}}, {OSPFv3IFLINKLSASUPPRESSION, INTEGER, RONLY, ospfv3IfEntry, 4, {1, 7, 1, 25}}, /* OSPFv3 neighbors */ {OSPFv3NBRADDRESSTYPE, INTEGER, RONLY, ospfv3NbrEntry, 4, {1, 9, 1, 4}}, {OSPFv3NBRADDRESS, STRING, RONLY, ospfv3NbrEntry, 4, {1, 9, 1, 5}}, {OSPFv3NBROPTIONS, INTEGER, RONLY, ospfv3NbrEntry, 4, {1, 9, 1, 6}}, {OSPFv3NBRPRIORITY, INTEGER, RONLY, ospfv3NbrEntry, 4, {1, 9, 1, 7}}, {OSPFv3NBRSTATE, INTEGER, RONLY, ospfv3NbrEntry, 4, {1, 9, 1, 8}}, {OSPFv3NBREVENTS, COUNTER, RONLY, ospfv3NbrEntry, 4, {1, 9, 1, 9}}, {OSPFv3NBRLSRETRANSQLEN, GAUGE, RONLY, ospfv3NbrEntry, 4, {1, 9, 1, 10}}, {OSPFv3NBRHELLOSUPPRESSED, INTEGER, RONLY, ospfv3NbrEntry, 4, {1, 9, 1, 11}}, {OSPFv3NBRIFID, INTEGER, RONLY, ospfv3NbrEntry, 4, {1, 9, 1, 12}}, {OSPFv3NBRRESTARTHELPERSTATUS, INTEGER, RONLY, ospfv3NbrEntry, 4, {1, 9, 1, 13}}, {OSPFv3NBRRESTARTHELPERAGE, UNSIGNED, RONLY, ospfv3NbrEntry, 4, {1, 9, 1, 14}}, {OSPFv3NBRRESTARTHELPEREXITREASON, INTEGER, RONLY, ospfv3NbrEntry, 4, {1, 9, 1, 15}}, }; static uint8_t *ospfv3GeneralGroup(struct variable *v, oid *name, size_t *length, int exact, size_t *var_len, WriteMethod **write_method) { uint16_t sum; uint32_t count; struct ospf6_lsa *lsa = NULL; /* Check whether the instance identifier is valid */ if (smux_header_generic(v, name, length, exact, var_len, write_method) == MATCH_FAILED) return NULL; /* Return the current value of the variable */ switch (v->magic) { case OSPFv3ROUTERID: /* Router-ID of this OSPF instance. */ if (ospf6) return SNMP_INTEGER(ntohl(ospf6->router_id)); return SNMP_INTEGER(0); case OSPFv3ADMINSTAT: if (ospf6) return SNMP_INTEGER( CHECK_FLAG(ospf6->flag, OSPF6_DISABLED) ? OSPF_STATUS_DISABLED : OSPF_STATUS_ENABLED); return SNMP_INTEGER(OSPF_STATUS_DISABLED); case OSPFv3VERSIONNUMBER: return SNMP_INTEGER(3); case OSPFv3AREABDRRTRSTATUS: if (ospf6) return SNMP_INTEGER(ospf6_is_router_abr(ospf6) ? SNMP_TRUE : SNMP_FALSE); return SNMP_INTEGER(SNMP_FALSE); case OSPFv3ASBDRRTRSTATUS: if (ospf6) return SNMP_INTEGER(ospf6_asbr_is_asbr(ospf6) ? SNMP_TRUE : SNMP_FALSE); return SNMP_INTEGER(SNMP_FALSE); case OSPFv3ASSCOPELSACOUNT: if (ospf6) return SNMP_INTEGER(ospf6->lsdb->count); return SNMP_INTEGER(0); case OSPFv3ASSCOPELSACHECKSUMSUM: if (ospf6) { sum = 0; for (ALL_LSDB(ospf6->lsdb, lsa)) sum += ntohs(lsa->header->checksum); return SNMP_INTEGER(sum); } return SNMP_INTEGER(0); case OSPFv3ORIGINATENEWLSAS: return SNMP_INTEGER( 0); /* Don't know where to get this value... */ case OSPFv3RXNEWLSAS: return SNMP_INTEGER( 0); /* Don't know where to get this value... */ case OSPFv3EXTLSACOUNT: if (ospf6) { count = 0; for (ALL_LSDB_TYPED(ospf6->lsdb, htons(OSPF6_LSTYPE_AS_EXTERNAL), lsa)) count += 1; return SNMP_INTEGER(count); } return SNMP_INTEGER(0); case OSPFv3EXTAREALSDBLIMIT: return SNMP_INTEGER(-1); case OSPFv3EXITOVERFLOWINTERVAL: return SNMP_INTEGER(0); /* Not supported */ case OSPFv3DEMANDEXTENSIONS: return SNMP_INTEGER(0); /* Not supported */ case OSPFv3REFERENCEBANDWIDTH: if (ospf6) return SNMP_INTEGER(ospf6->ref_bandwidth); /* Otherwise, like for "not implemented". */ /* fallthru */ case OSPFv3RESTARTSUPPORT: case OSPFv3RESTARTINTERVAL: case OSPFv3RESTARTSTRICTLSACHECKING: case OSPFv3RESTARTSTATUS: case OSPFv3RESTARTAGE: case OSPFv3RESTARTEXITREASON: case OSPFv3NOTIFICATIONENABLE: case OSPFv3STUBROUTERSUPPORT: case OSPFv3STUBROUTERADVERTISEMENT: case OSPFv3DISCONTINUITYTIME: case OSPFv3RESTARTTIME: /* TODO: Not implemented */ return NULL; } return NULL; } static uint8_t *ospfv3AreaEntry(struct variable *v, oid *name, size_t *length, int exact, size_t *var_len, WriteMethod **write_method) { struct ospf6_area *oa, *area = NULL; struct ospf6_lsa *lsa = NULL; uint32_t area_id = 0; uint32_t count; uint16_t sum; struct listnode *node; unsigned int len; char a[16]; struct ospf6_route *ro; if (ospf6 == NULL) return NULL; if (smux_header_table(v, name, length, exact, var_len, write_method) == MATCH_FAILED) return NULL; len = *length - v->namelen; len = (len >= 1 ? 1 : 0); if (exact && len != 1) return NULL; if (len) area_id = htonl(name[v->namelen]); inet_ntop(AF_INET, &area_id, a, sizeof(a)); zlog_debug("SNMP access by area: %s, exact=%d len=%d length=%lu", a, exact, len, (unsigned long)*length); for (ALL_LIST_ELEMENTS_RO(ospf6->area_list, node, oa)) { if (area == NULL) { if (len == 0) /* return first area entry */ area = oa; else if (exact && ntohl(oa->area_id) == ntohl(area_id)) area = oa; else if (ntohl(oa->area_id) > ntohl(area_id)) area = oa; } } if (area == NULL) return NULL; *length = v->namelen + 1; name[v->namelen] = ntohl(area->area_id); inet_ntop(AF_INET, &area->area_id, a, sizeof(a)); zlog_debug("SNMP found area: %s, exact=%d len=%d length=%lu", a, exact, len, (unsigned long)*length); switch (v->magic) { case OSPFv3IMPORTASEXTERN: /* No NSSA support */ return SNMP_INTEGER(IS_AREA_STUB(area) ? 2 : 1); case OSPFv3AREASPFRUNS: return SNMP_INTEGER(area->spf_calculation); case OSPFv3AREABDRRTRCOUNT: case OSPFv3AREAASBDRRTRCOUNT: count = 0; for (ro = ospf6_route_head(ospf6->brouter_table); ro; ro = ospf6_route_next(ro)) { if (ntohl(ro->path.area_id) != ntohl(area->area_id)) continue; if (v->magic == OSPFv3AREABDRRTRCOUNT && CHECK_FLAG(ro->path.router_bits, OSPF6_ROUTER_BIT_B)) count++; if (v->magic == OSPFv3AREAASBDRRTRCOUNT && CHECK_FLAG(ro->path.router_bits, OSPF6_ROUTER_BIT_E)) count++; } return SNMP_INTEGER(count); case OSPFv3AREASCOPELSACOUNT: return SNMP_INTEGER(area->lsdb->count); case OSPFv3AREASCOPELSACKSUMSUM: sum = 0; for (ALL_LSDB(area->lsdb, lsa)) sum += ntohs(lsa->header->checksum); return SNMP_INTEGER(sum); case OSPFv3AREASUMMARY: return SNMP_INTEGER(2); /* sendAreaSummary */ case OSPFv3AREAROWSTATUS: return SNMP_INTEGER(1); /* Active */ case OSPFv3AREASTUBMETRIC: case OSPFv3AREANSSATRANSLATORROLE: case OSPFv3AREANSSATRANSLATORSTATE: case OSPFv3AREANSSATRANSLATORSTABINTERVAL: case OSPFv3AREANSSATRANSLATOREVENTS: case OSPFv3AREASTUBMETRICTYPE: case OSPFv3AREATEENABLED: /* Not implemented. */ return NULL; } return NULL; } static int if_icmp_func(struct interface *ifp1, struct interface *ifp2) { return (ifp1->ifindex - ifp2->ifindex); } static uint8_t *ospfv3WwLsdbEntry(struct variable *v, oid *name, size_t *length, int exact, size_t *var_len, WriteMethod **write_method) { struct vrf *vrf = vrf_lookup_by_id(VRF_DEFAULT); struct ospf6_lsa *lsa = NULL; ifindex_t ifindex; uint32_t area_id, id, instid, adv_router; uint16_t type; int len; oid *offset; int offsetlen; struct ospf6_area *oa = NULL; struct listnode *node; struct interface *iif; struct ospf6_interface *oi = NULL; struct list *ifslist; if (smux_header_table(v, name, length, exact, var_len, write_method) == MATCH_FAILED) return NULL; instid = ifindex = area_id = type = id = adv_router = 0; /* Check OSPFv3 instance. */ if (ospf6 == NULL) return NULL; /* Get variable length. */ offset = name + v->namelen; offsetlen = *length - v->namelen; if (exact && (v->magic & OSPFv3WWASTABLE) && offsetlen != 3) return NULL; if (exact && (v->magic & OSPFv3WWAREATABLE) && offsetlen != 4) return NULL; if (exact && (v->magic & OSPFv3WWLINKTABLE) && offsetlen != 5) return NULL; if (v->magic & OSPFv3WWLINKTABLE) { /* Parse ifindex */ len = (offsetlen < 1 ? 0 : 1); if (len) ifindex = *offset; offset += len; offsetlen -= len; /* Parse instance ID */ len = (offsetlen < 1 ? 0 : 1); if (len) instid = *offset; offset += len; offsetlen -= len; } else if (v->magic & OSPFv3WWAREATABLE) { /* Parse area-id */ len = (offsetlen < 1 ? 0 : 1); if (len) area_id = htonl(*offset); offset += len; offsetlen -= len; } /* Parse type */ len = (offsetlen < 1 ? 0 : 1); if (len) type = htons(*offset); offset += len; offsetlen -= len; /* Parse Router-ID */ len = (offsetlen < 1 ? 0 : 1); if (len) adv_router = htonl(*offset); offset += len; offsetlen -= len; /* Parse LS-ID */ len = (offsetlen < 1 ? 0 : 1); if (len) id = htonl(*offset); offset += len; // offsetlen -= len; // Add back in if we need it again if (exact) { if (v->magic & OSPFv3WWASTABLE) { lsa = ospf6_lsdb_lookup(type, id, adv_router, ospf6->lsdb); } else if (v->magic & OSPFv3WWAREATABLE) { oa = ospf6_area_lookup(area_id, ospf6); if (!oa) return NULL; lsa = ospf6_lsdb_lookup(type, id, adv_router, oa->lsdb); } else if (v->magic & OSPFv3WWLINKTABLE) { oi = ospf6_interface_lookup_by_ifindex(ifindex); if (!oi || oi->instance_id != instid) return NULL; lsa = ospf6_lsdb_lookup(type, id, adv_router, oi->lsdb); } } else { if (v->magic & OSPFv3WWASTABLE) { if (ospf6->lsdb->count) lsa = ospf6_lsdb_lookup_next( type, id, adv_router, ospf6->lsdb); } else if (v->magic & OSPFv3WWAREATABLE) for (ALL_LIST_ELEMENTS_RO(ospf6->area_list, node, oa)) { if (oa->area_id < area_id) continue; if (oa->lsdb->count) lsa = ospf6_lsdb_lookup_next( type, id, adv_router, oa->lsdb); if (lsa) break; type = 0; id = 0; adv_router = 0; } else if (v->magic & OSPFv3WWLINKTABLE) { /* We build a sorted list of interfaces */ ifslist = list_new(); if (!ifslist) return NULL; ifslist->cmp = (int (*)(void *, void *))if_icmp_func; FOR_ALL_INTERFACES (vrf, iif) listnode_add_sort(ifslist, iif); for (ALL_LIST_ELEMENTS_RO(ifslist, node, iif)) { if (!iif->ifindex) continue; oi = ospf6_interface_lookup_by_ifindex( iif->ifindex); if (!oi) continue; if (iif->ifindex < ifindex) continue; if (oi->instance_id < instid) continue; if (oi->lsdb->count) lsa = ospf6_lsdb_lookup_next( type, id, adv_router, oi->lsdb); if (lsa) break; type = 0; id = 0; adv_router = 0; oi = NULL; } list_delete_all_node(ifslist); } } if (!lsa) return NULL; /* Add indexes */ if (v->magic & OSPFv3WWASTABLE) { *length = v->namelen + 3; offset = name + v->namelen; } else if (v->magic & OSPFv3WWAREATABLE) { *length = v->namelen + 4; offset = name + v->namelen; *offset = ntohl(oa->area_id); offset++; } else if (v->magic & OSPFv3WWLINKTABLE) { *length = v->namelen + 5; offset = name + v->namelen; *offset = oi->interface->ifindex; offset++; *offset = oi->instance_id; offset++; } *offset = ntohs(lsa->header->type); offset++; *offset = ntohl(lsa->header->adv_router); offset++; *offset = ntohl(lsa->header->id); offset++; /* Return the current value of the variable */ switch (v->magic & OSPFv3WWCOLUMN) { case OSPFv3WWLSDBSEQUENCE: return SNMP_INTEGER(ntohl(lsa->header->seqnum)); break; case OSPFv3WWLSDBAGE: ospf6_lsa_age_current(lsa); return SNMP_INTEGER(ntohs(lsa->header->age)); break; case OSPFv3WWLSDBCHECKSUM: return SNMP_INTEGER(ntohs(lsa->header->checksum)); break; case OSPFv3WWLSDBADVERTISEMENT: *var_len = ntohs(lsa->header->length); return (uint8_t *)lsa->header; break; case OSPFv3WWLSDBTYPEKNOWN: return SNMP_INTEGER(OSPF6_LSA_IS_KNOWN(lsa->header->type) ? SNMP_TRUE : SNMP_FALSE); break; } return NULL; } static uint8_t *ospfv3IfEntry(struct variable *v, oid *name, size_t *length, int exact, size_t *var_len, WriteMethod **write_method) { struct vrf *vrf = vrf_lookup_by_id(VRF_DEFAULT); ifindex_t ifindex = 0; unsigned int instid = 0; struct ospf6_interface *oi = NULL; struct ospf6_lsa *lsa = NULL; struct interface *iif; struct listnode *i; struct list *ifslist; oid *offset; int offsetlen, len; uint32_t sum; if (smux_header_table(v, name, length, exact, var_len, write_method) == MATCH_FAILED) return NULL; /* Check OSPFv3 instance. */ if (ospf6 == NULL) return NULL; /* Get variable length. */ offset = name + v->namelen; offsetlen = *length - v->namelen; if (exact && offsetlen != 2) return NULL; /* Parse if index */ len = (offsetlen < 1 ? 0 : 1); if (len) ifindex = *offset; offset += len; offsetlen -= len; /* Parse instance ID */ len = (offsetlen < 1 ? 0 : 1); if (len) instid = *offset; // offset += len; // Add back in if we ever start using again // offsetlen -= len; if (exact) { oi = ospf6_interface_lookup_by_ifindex(ifindex); if (!oi || oi->instance_id != instid) return NULL; } else { /* We build a sorted list of interfaces */ ifslist = list_new(); if (!ifslist) return NULL; ifslist->cmp = (int (*)(void *, void *))if_icmp_func; FOR_ALL_INTERFACES (vrf, iif) listnode_add_sort(ifslist, iif); for (ALL_LIST_ELEMENTS_RO(ifslist, i, iif)) { if (!iif->ifindex) continue; oi = ospf6_interface_lookup_by_ifindex(iif->ifindex); if (!oi) continue; if (iif->ifindex > ifindex || (iif->ifindex == ifindex && (oi->instance_id > instid))) break; oi = NULL; } list_delete_all_node(ifslist); } if (!oi) return NULL; /* Add Index (IfIndex, IfInstId) */ *length = v->namelen + 2; offset = name + v->namelen; *offset = oi->interface->ifindex; offset++; *offset = oi->instance_id; offset++; /* Return the current value of the variable */ switch (v->magic) { case OSPFv3IFAREAID: if (oi->area) return SNMP_INTEGER(ntohl(oi->area->area_id)); break; case OSPFv3IFTYPE: if (if_is_broadcast(oi->interface)) return SNMP_INTEGER(1); else if (if_is_pointopoint(oi->interface)) return SNMP_INTEGER(3); else break; /* Unknown, don't put anything */ case OSPFv3IFADMINSTATUS: if (oi->area) return SNMP_INTEGER(OSPF_STATUS_ENABLED); return SNMP_INTEGER(OSPF_STATUS_DISABLED); case OSPFv3IFRTRPRIORITY: return SNMP_INTEGER(oi->priority); case OSPFv3IFTRANSITDELAY: return SNMP_INTEGER(oi->transdelay); case OSPFv3IFRETRANSINTERVAL: return SNMP_INTEGER(oi->rxmt_interval); case OSPFv3IFHELLOINTERVAL: return SNMP_INTEGER(oi->hello_interval); case OSPFv3IFRTRDEADINTERVAL: return SNMP_INTEGER(oi->dead_interval); case OSPFv3IFPOLLINTERVAL: /* No support for NBMA */ break; case OSPFv3IFSTATE: return SNMP_INTEGER(oi->state); case OSPFv3IFDESIGNATEDROUTER: return SNMP_INTEGER(ntohl(oi->drouter)); case OSPFv3IFBACKUPDESIGNATEDROUTER: return SNMP_INTEGER(ntohl(oi->bdrouter)); case OSPFv3IFEVENTS: return SNMP_INTEGER(oi->state_change); case OSPFv3IFROWSTATUS: return SNMP_INTEGER(1); case OSPFv3IFDEMAND: return SNMP_INTEGER(SNMP_FALSE); case OSPFv3IFMETRICVALUE: return SNMP_INTEGER(oi->cost); case OSPFv3IFLINKSCOPELSACOUNT: return SNMP_INTEGER(oi->lsdb->count); case OSPFv3IFLINKLSACKSUMSUM: sum = 0; for (ALL_LSDB(oi->lsdb, lsa)) sum += ntohs(lsa->header->checksum); return SNMP_INTEGER(sum); case OSPFv3IFDEMANDNBRPROBE: case OSPFv3IFDEMANDNBRPROBERETRANSLIMIT: case OSPFv3IFDEMANDNBRPROBEINTERVAL: case OSPFv3IFTEDISABLED: case OSPFv3IFLINKLSASUPPRESSION: /* Not implemented. Only works if all the last ones are not implemented! */ return NULL; } /* Try an internal getnext. Some columns are missing in this table. */ if (!exact && (name[*length - 1] < MAX_SUBID)) return ospfv3IfEntry(v, name, length, exact, var_len, write_method); return NULL; } static uint8_t *ospfv3NbrEntry(struct variable *v, oid *name, size_t *length, int exact, size_t *var_len, WriteMethod **write_method) { struct vrf *vrf = vrf_lookup_by_id(VRF_DEFAULT); ifindex_t ifindex = 0; unsigned int instid, rtrid; struct ospf6_interface *oi = NULL; struct ospf6_neighbor *on = NULL; struct interface *iif; struct listnode *i, *j; struct list *ifslist; oid *offset; int offsetlen, len; if (smux_header_table(v, name, length, exact, var_len, write_method) == MATCH_FAILED) return NULL; instid = rtrid = 0; /* Check OSPFv3 instance. */ if (ospf6 == NULL) return NULL; /* Get variable length. */ offset = name + v->namelen; offsetlen = *length - v->namelen; if (exact && offsetlen != 3) return NULL; /* Parse if index */ len = (offsetlen < 1 ? 0 : 1); if (len) ifindex = *offset; offset += len; offsetlen -= len; /* Parse instance ID */ len = (offsetlen < 1 ? 0 : 1); if (len) instid = *offset; offset += len; offsetlen -= len; /* Parse router ID */ len = (offsetlen < 1 ? 0 : 1); if (len) rtrid = htonl(*offset); // offset += len; // Add back in if we ever start looking at data // offsetlen -= len; if (exact) { oi = ospf6_interface_lookup_by_ifindex(ifindex); if (!oi || oi->instance_id != instid) return NULL; on = ospf6_neighbor_lookup(rtrid, oi); } else { /* We build a sorted list of interfaces */ ifslist = list_new(); if (!ifslist) return NULL; ifslist->cmp = (int (*)(void *, void *))if_icmp_func; FOR_ALL_INTERFACES (vrf, iif) listnode_add_sort(ifslist, iif); for (ALL_LIST_ELEMENTS_RO(ifslist, i, iif)) { if (!iif->ifindex) continue; oi = ospf6_interface_lookup_by_ifindex(iif->ifindex); if (!oi) continue; for (ALL_LIST_ELEMENTS_RO(oi->neighbor_list, j, on)) { if (iif->ifindex > ifindex || (iif->ifindex == ifindex && (oi->instance_id > instid || (oi->instance_id == instid && ntohl(on->router_id) > ntohl(rtrid))))) break; } if (on) break; oi = NULL; on = NULL; } list_delete_all_node(ifslist); } if (!oi || !on) return NULL; /* Add Index (IfIndex, IfInstId, RtrId) */ *length = v->namelen + 3; offset = name + v->namelen; *offset = oi->interface->ifindex; offset++; *offset = oi->instance_id; offset++; *offset = ntohl(on->router_id); offset++; /* Return the current value of the variable */ switch (v->magic) { case OSPFv3NBRADDRESSTYPE: return SNMP_INTEGER(2); /* IPv6 only */ case OSPFv3NBRADDRESS: *var_len = sizeof(struct in6_addr); return (uint8_t *)&on->linklocal_addr; case OSPFv3NBROPTIONS: return SNMP_INTEGER(on->options[2]); case OSPFv3NBRPRIORITY: return SNMP_INTEGER(on->priority); case OSPFv3NBRSTATE: return SNMP_INTEGER(on->state); case OSPFv3NBREVENTS: return SNMP_INTEGER(on->state_change); case OSPFv3NBRLSRETRANSQLEN: return SNMP_INTEGER(on->retrans_list->count); case OSPFv3NBRHELLOSUPPRESSED: return SNMP_INTEGER(SNMP_FALSE); case OSPFv3NBRIFID: return SNMP_INTEGER(on->ifindex); case OSPFv3NBRRESTARTHELPERSTATUS: case OSPFv3NBRRESTARTHELPERAGE: case OSPFv3NBRRESTARTHELPEREXITREASON: /* Not implemented. Only works if all the last ones are not implemented! */ return NULL; } return NULL; } /* OSPF Traps. */ #define NBRSTATECHANGE 2 #define IFSTATECHANGE 10 static struct trap_object ospf6NbrTrapList[] = { {-3, {1, 1, OSPFv3ROUTERID}}, {4, {1, 9, 1, OSPFv3NBRADDRESSTYPE}}, {4, {1, 9, 1, OSPFv3NBRADDRESS}}, {4, {1, 9, 1, OSPFv3NBRSTATE}}}; static struct trap_object ospf6IfTrapList[] = { {-3, {1, 1, OSPFv3ROUTERID}}, {4, {1, 7, 1, OSPFv3IFSTATE}}, {4, {1, 7, 1, OSPFv3IFADMINSTATUS}}, {4, {1, 7, 1, OSPFv3IFAREAID}}}; static int ospf6TrapNbrStateChange(struct ospf6_neighbor *on, int next_state, int prev_state) { oid index[3]; /* Terminal state or regression */ if ((next_state != OSPF6_NEIGHBOR_FULL) && (next_state != OSPF6_NEIGHBOR_TWOWAY) && (next_state >= prev_state)) return 0; index[0] = on->ospf6_if->interface->ifindex; index[1] = on->ospf6_if->instance_id; index[2] = ntohl(on->router_id); smux_trap(ospfv3_variables, array_size(ospfv3_variables), ospfv3_trap_oid, array_size(ospfv3_trap_oid), ospfv3_oid, sizeof ospfv3_oid / sizeof(oid), index, 3, ospf6NbrTrapList, array_size(ospf6NbrTrapList), NBRSTATECHANGE); return 0; } static int ospf6TrapIfStateChange(struct ospf6_interface *oi, int next_state, int prev_state) { oid index[2]; /* Terminal state or regression */ if ((next_state != OSPF6_INTERFACE_POINTTOPOINT) && (next_state != OSPF6_INTERFACE_DROTHER) && (next_state != OSPF6_INTERFACE_BDR) && (next_state != OSPF6_INTERFACE_DR) && (next_state >= prev_state)) return 0; index[0] = oi->interface->ifindex; index[1] = oi->instance_id; smux_trap(ospfv3_variables, array_size(ospfv3_variables), ospfv3_trap_oid, array_size(ospfv3_trap_oid), ospfv3_oid, sizeof ospfv3_oid / sizeof(oid), index, 2, ospf6IfTrapList, array_size(ospf6IfTrapList), IFSTATECHANGE); return 0; } /* Register OSPFv3-MIB. */ static int ospf6_snmp_init(struct thread_master *master) { smux_init(master); REGISTER_MIB("OSPFv3MIB", ospfv3_variables, variable, ospfv3_oid); return 0; } static int ospf6_snmp_module_init(void) { hook_register(ospf6_interface_change, ospf6TrapIfStateChange); hook_register(ospf6_neighbor_change, ospf6TrapNbrStateChange); hook_register(frr_late_init, ospf6_snmp_init); return 0; } FRR_MODULE_SETUP(.name = "ospf6d_snmp", .version = FRR_VERSION, .description = "ospf6d AgentX SNMP module", .init = ospf6_snmp_module_init, ) frr-7.2.1/ospf6d/ospf6_spf.c0000644000000000000000000007477713610377563012514 00000000000000/* * Copyright (C) 2003 Yasuhiro Ohara * * This file is part of GNU Zebra. * * GNU Zebra is free software; you can redistribute it and/or modify it * under the terms of the GNU General Public License as published by the * Free Software Foundation; either version 2, or (at your option) any * later version. * * GNU Zebra 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; see the file COPYING; if not, write to the Free Software * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA */ /* Shortest Path First calculation for OSPFv3 */ #include #include "log.h" #include "memory.h" #include "command.h" #include "vty.h" #include "prefix.h" #include "linklist.h" #include "thread.h" #include "lib_errors.h" #include "ospf6_lsa.h" #include "ospf6_lsdb.h" #include "ospf6_route.h" #include "ospf6_area.h" #include "ospf6_proto.h" #include "ospf6_abr.h" #include "ospf6_spf.h" #include "ospf6_intra.h" #include "ospf6_interface.h" #include "ospf6d.h" #include "ospf6_abr.h" unsigned char conf_debug_ospf6_spf = 0; static void ospf6_spf_copy_nexthops_to_route(struct ospf6_route *rt, struct ospf6_vertex *v) { if (rt && v) ospf6_copy_nexthops(rt->nh_list, v->nh_list); } static void ospf6_spf_merge_nexthops_to_route(struct ospf6_route *rt, struct ospf6_vertex *v) { if (rt && v) ospf6_merge_nexthops(rt->nh_list, v->nh_list); } static unsigned int ospf6_spf_get_ifindex_from_nh(struct ospf6_vertex *v) { struct ospf6_nexthop *nh; struct listnode *node; if (v) { node = listhead(v->nh_list); if (node) { nh = listgetdata(node); if (nh) return (nh->ifindex); } } return 0; } static int ospf6_vertex_cmp(const struct ospf6_vertex *va, const struct ospf6_vertex *vb) { /* ascending order */ if (va->cost != vb->cost) return (va->cost - vb->cost); if (va->hops != vb->hops) return (va->hops - vb->hops); return 0; } DECLARE_SKIPLIST_NONUNIQ(vertex_pqueue, struct ospf6_vertex, pqi, ospf6_vertex_cmp) static int ospf6_vertex_id_cmp(void *a, void *b) { struct ospf6_vertex *va = (struct ospf6_vertex *)a; struct ospf6_vertex *vb = (struct ospf6_vertex *)b; int ret = 0; ret = ntohl(ospf6_linkstate_prefix_adv_router(&va->vertex_id)) - ntohl(ospf6_linkstate_prefix_adv_router(&vb->vertex_id)); if (ret) return ret; ret = ntohl(ospf6_linkstate_prefix_id(&va->vertex_id)) - ntohl(ospf6_linkstate_prefix_id(&vb->vertex_id)); return ret; } static struct ospf6_vertex *ospf6_vertex_create(struct ospf6_lsa *lsa) { struct ospf6_vertex *v; v = XMALLOC(MTYPE_OSPF6_VERTEX, sizeof(struct ospf6_vertex)); /* type */ if (ntohs(lsa->header->type) == OSPF6_LSTYPE_ROUTER) { v->type = OSPF6_VERTEX_TYPE_ROUTER; /* Router LSA use Link ID 0 as base in vertex_id */ ospf6_linkstate_prefix(lsa->header->adv_router, htonl(0), &v->vertex_id); } else if (ntohs(lsa->header->type) == OSPF6_LSTYPE_NETWORK) { v->type = OSPF6_VERTEX_TYPE_NETWORK; /* vertex_id */ ospf6_linkstate_prefix(lsa->header->adv_router, lsa->header->id, &v->vertex_id); } else assert(0); /* name */ ospf6_linkstate_prefix2str(&v->vertex_id, v->name, sizeof(v->name)); if (IS_OSPF6_DEBUG_SPF(PROCESS)) zlog_debug("%s: Creating vertex %s of type %s (0x%04hx) lsa %s", __func__, v->name, ((ntohs(lsa->header->type) == OSPF6_LSTYPE_ROUTER) ? "Router" : "N/W"), ntohs(lsa->header->type), lsa->name); /* Associated LSA */ v->lsa = lsa; /* capability bits + options */ v->capability = *(uint8_t *)(OSPF6_LSA_HEADER_END(lsa->header)); v->options[0] = *(uint8_t *)(OSPF6_LSA_HEADER_END(lsa->header) + 1); v->options[1] = *(uint8_t *)(OSPF6_LSA_HEADER_END(lsa->header) + 2); v->options[2] = *(uint8_t *)(OSPF6_LSA_HEADER_END(lsa->header) + 3); v->nh_list = list_new(); v->nh_list->cmp = (int (*)(void *, void *))ospf6_nexthop_cmp; v->nh_list->del = (void (*)(void *))ospf6_nexthop_delete; v->parent = NULL; v->child_list = list_new(); v->child_list->cmp = ospf6_vertex_id_cmp; return v; } static void ospf6_vertex_delete(struct ospf6_vertex *v) { list_delete(&v->nh_list); list_delete(&v->child_list); XFREE(MTYPE_OSPF6_VERTEX, v); } static struct ospf6_lsa *ospf6_lsdesc_lsa(caddr_t lsdesc, struct ospf6_vertex *v) { struct ospf6_lsa *lsa = NULL; uint16_t type = 0; uint32_t id = 0, adv_router = 0; if (VERTEX_IS_TYPE(NETWORK, v)) { type = htons(OSPF6_LSTYPE_ROUTER); id = htonl(0); adv_router = NETWORK_LSDESC_GET_NBR_ROUTERID(lsdesc); } else { if (ROUTER_LSDESC_IS_TYPE(POINTTOPOINT, lsdesc)) { type = htons(OSPF6_LSTYPE_ROUTER); id = htonl(0); adv_router = ROUTER_LSDESC_GET_NBR_ROUTERID(lsdesc); } else if (ROUTER_LSDESC_IS_TYPE(TRANSIT_NETWORK, lsdesc)) { type = htons(OSPF6_LSTYPE_NETWORK); id = htonl(ROUTER_LSDESC_GET_NBR_IFID(lsdesc)); adv_router = ROUTER_LSDESC_GET_NBR_ROUTERID(lsdesc); } } if (type == htons(OSPF6_LSTYPE_NETWORK)) lsa = ospf6_lsdb_lookup(type, id, adv_router, v->area->lsdb); else lsa = ospf6_create_single_router_lsa(v->area, v->area->lsdb, adv_router); if (IS_OSPF6_DEBUG_SPF(PROCESS)) { char ibuf[16], abuf[16]; inet_ntop(AF_INET, &id, ibuf, sizeof(ibuf)); inet_ntop(AF_INET, &adv_router, abuf, sizeof(abuf)); if (lsa) zlog_debug(" Link to: %s len %u, V %s", lsa->name, ntohs(lsa->header->length), v->name); else zlog_debug(" Link to: [%s Id:%s Adv:%s] No LSA , V %s", ospf6_lstype_name(type), ibuf, abuf, v->name); } return lsa; } static char *ospf6_lsdesc_backlink(struct ospf6_lsa *lsa, caddr_t lsdesc, struct ospf6_vertex *v) { caddr_t backlink, found = NULL; int size; size = (OSPF6_LSA_IS_TYPE(ROUTER, lsa) ? sizeof(struct ospf6_router_lsdesc) : sizeof(struct ospf6_network_lsdesc)); for (backlink = OSPF6_LSA_HEADER_END(lsa->header) + 4; backlink + size <= OSPF6_LSA_END(lsa->header); backlink += size) { assert(!(OSPF6_LSA_IS_TYPE(NETWORK, lsa) && VERTEX_IS_TYPE(NETWORK, v))); if (OSPF6_LSA_IS_TYPE(NETWORK, lsa) && NETWORK_LSDESC_GET_NBR_ROUTERID(backlink) == v->lsa->header->adv_router) found = backlink; else if (VERTEX_IS_TYPE(NETWORK, v) && ROUTER_LSDESC_IS_TYPE(TRANSIT_NETWORK, backlink) && ROUTER_LSDESC_GET_NBR_ROUTERID(backlink) == v->lsa->header->adv_router && ROUTER_LSDESC_GET_NBR_IFID(backlink) == ntohl(v->lsa->header->id)) found = backlink; else { if (!ROUTER_LSDESC_IS_TYPE(POINTTOPOINT, backlink) || !ROUTER_LSDESC_IS_TYPE(POINTTOPOINT, lsdesc)) continue; if (ROUTER_LSDESC_GET_NBR_IFID(backlink) != ROUTER_LSDESC_GET_IFID(lsdesc) || ROUTER_LSDESC_GET_NBR_IFID(lsdesc) != ROUTER_LSDESC_GET_IFID(backlink)) continue; if (ROUTER_LSDESC_GET_NBR_ROUTERID(backlink) != v->lsa->header->adv_router || ROUTER_LSDESC_GET_NBR_ROUTERID(lsdesc) != lsa->header->adv_router) continue; found = backlink; } } if (IS_OSPF6_DEBUG_SPF(PROCESS)) zlog_debug("Vertex %s Lsa %s Backlink %s", v->name, lsa->name, (found ? "OK" : "FAIL")); return found; } static void ospf6_nexthop_calc(struct ospf6_vertex *w, struct ospf6_vertex *v, caddr_t lsdesc) { int i; ifindex_t ifindex; struct ospf6_interface *oi; uint16_t type; uint32_t adv_router; struct ospf6_lsa *lsa; struct ospf6_link_lsa *link_lsa; char buf[64]; assert(VERTEX_IS_TYPE(ROUTER, w)); ifindex = (VERTEX_IS_TYPE(NETWORK, v) ? ospf6_spf_get_ifindex_from_nh(v) : ROUTER_LSDESC_GET_IFID(lsdesc)); if (ifindex == 0) { flog_err(EC_LIB_DEVELOPMENT, "No nexthop ifindex at vertex %s", v->name); return; } oi = ospf6_interface_lookup_by_ifindex(ifindex); if (oi == NULL) { if (IS_OSPF6_DEBUG_SPF(PROCESS)) zlog_debug("Can't find interface in SPF: ifindex %d", ifindex); return; } type = htons(OSPF6_LSTYPE_LINK); adv_router = (VERTEX_IS_TYPE(NETWORK, v) ? NETWORK_LSDESC_GET_NBR_ROUTERID(lsdesc) : ROUTER_LSDESC_GET_NBR_ROUTERID(lsdesc)); i = 0; for (ALL_LSDB_TYPED_ADVRTR(oi->lsdb, type, adv_router, lsa)) { if (VERTEX_IS_TYPE(ROUTER, v) && htonl(ROUTER_LSDESC_GET_NBR_IFID(lsdesc)) != lsa->header->id) continue; link_lsa = (struct ospf6_link_lsa *)OSPF6_LSA_HEADER_END( lsa->header); if (IS_OSPF6_DEBUG_SPF(PROCESS)) { inet_ntop(AF_INET6, &link_lsa->linklocal_addr, buf, sizeof(buf)); zlog_debug(" nexthop %s from %s", buf, lsa->name); } ospf6_add_nexthop(w->nh_list, ifindex, &link_lsa->linklocal_addr); i++; } if (i == 0 && IS_OSPF6_DEBUG_SPF(PROCESS)) zlog_debug("No nexthop for %s found", w->name); } static int ospf6_spf_install(struct ospf6_vertex *v, struct ospf6_route_table *result_table) { struct ospf6_route *route, *parent_route; struct ospf6_vertex *prev; char pbuf[PREFIX2STR_BUFFER]; if (IS_OSPF6_DEBUG_SPF(PROCESS)) zlog_debug("SPF install %s (lsa %s) hops %d cost %d", v->name, v->lsa->name, v->hops, v->cost); route = ospf6_route_lookup(&v->vertex_id, result_table); if (route && route->path.cost < v->cost) { if (IS_OSPF6_DEBUG_SPF(PROCESS)) zlog_debug( " already installed with lower cost (%d), ignore", route->path.cost); ospf6_vertex_delete(v); return -1; } else if (route && route->path.cost == v->cost) { if (IS_OSPF6_DEBUG_SPF(PROCESS)) { prefix2str(&route->prefix, pbuf, sizeof(pbuf)); zlog_debug( " another path found to route %s lsa %s, merge", pbuf, v->lsa->name); } ospf6_spf_merge_nexthops_to_route(route, v); prev = (struct ospf6_vertex *)route->route_option; assert(prev->hops <= v->hops); if ((VERTEX_IS_TYPE(ROUTER, v) && route->path.origin.id != v->lsa->header->id)) { if (IS_OSPF6_DEBUG_SPF(PROCESS)) { zlog_debug( "%s: V lsa %s id %u, route id %u are different", __PRETTY_FUNCTION__, v->lsa->name, ntohl(v->lsa->header->id), ntohl(route->path.origin.id)); } return 0; } ospf6_vertex_delete(v); return -1; } /* There should be no case where candidate being installed (variable "v") is closer than the one in the SPF tree (variable "route"). In the case something has gone wrong with the behavior of Priority-Queue. */ /* the case where the route exists already is handled and returned up to here. */ assert(route == NULL); route = ospf6_route_create(); memcpy(&route->prefix, &v->vertex_id, sizeof(struct prefix)); route->type = OSPF6_DEST_TYPE_LINKSTATE; route->path.type = OSPF6_PATH_TYPE_INTRA; route->path.origin.type = v->lsa->header->type; route->path.origin.id = v->lsa->header->id; route->path.origin.adv_router = v->lsa->header->adv_router; route->path.metric_type = 1; route->path.cost = v->cost; route->path.u.cost_e2 = v->hops; route->path.router_bits = v->capability; route->path.options[0] = v->options[0]; route->path.options[1] = v->options[1]; route->path.options[2] = v->options[2]; ospf6_spf_copy_nexthops_to_route(route, v); /* * The SPF logic implementation does not transfer the multipathing * properties * of a parent to a child node. Thus if there was a 3-way multipath to a * node's parent and a single hop from the parent to the child, the * logic of * creating new vertices and computing next hops prevents there from * being 3 * paths to the child node. This is primarily because the resolution of * multipath is done in this routine, not in the main spf loop. * * The following logic addresses that problem by merging the parent's * nexthop * information with the child's, if the parent is not the root of the * tree. * This is based on the assumption that before a node's route is * installed, * its parent's route's nexthops have already been installed. */ if (v->parent && v->parent->hops) { parent_route = ospf6_route_lookup(&v->parent->vertex_id, result_table); if (parent_route) { ospf6_route_merge_nexthops(route, parent_route); } } if (v->parent) listnode_add_sort(v->parent->child_list, v); route->route_option = v; ospf6_route_add(route, result_table); return 0; } void ospf6_spf_table_finish(struct ospf6_route_table *result_table) { struct ospf6_route *route, *nroute; struct ospf6_vertex *v; for (route = ospf6_route_head(result_table); route; route = nroute) { nroute = ospf6_route_next(route); v = (struct ospf6_vertex *)route->route_option; ospf6_vertex_delete(v); ospf6_route_remove(route, result_table); } } static const char *ospf6_spf_reason_str[] = { "R+", "R-", "N+", "N-", "L+", "L-", "R*", "N*", }; void ospf6_spf_reason_string(unsigned int reason, char *buf, int size) { unsigned int bit; int len = 0; if (!buf) return; for (bit = 0; bit < array_size(ospf6_spf_reason_str); bit++) { if ((reason & (1 << bit)) && (len < size)) { len += snprintf((buf + len), (size - len), "%s%s", (len > 0) ? ", " : "", ospf6_spf_reason_str[bit]); } } } /* RFC2328 16.1. Calculating the shortest-path tree for an area */ /* RFC2740 3.8.1. Calculating the shortest path tree for an area */ void ospf6_spf_calculation(uint32_t router_id, struct ospf6_route_table *result_table, struct ospf6_area *oa) { struct vertex_pqueue_head candidate_list; struct ospf6_vertex *root, *v, *w; int size; caddr_t lsdesc; struct ospf6_lsa *lsa; struct in6_addr address; ospf6_spf_table_finish(result_table); /* Install the calculating router itself as the root of the SPF tree */ /* construct root vertex */ lsa = ospf6_create_single_router_lsa(oa, oa->lsdb_self, router_id); if (lsa == NULL) { if (IS_OSPF6_DEBUG_SPF(PROCESS)) zlog_debug("%s: No router LSA for area %s", __func__, oa->name); return; } /* initialize */ vertex_pqueue_init(&candidate_list); root = ospf6_vertex_create(lsa); root->area = oa; root->cost = 0; root->hops = 0; root->link_id = lsa->header->id; inet_pton(AF_INET6, "::1", &address); /* Actually insert root to the candidate-list as the only candidate */ vertex_pqueue_add(&candidate_list, root); /* Iterate until candidate-list becomes empty */ while ((v = vertex_pqueue_pop(&candidate_list))) { /* installing may result in merging or rejecting of the vertex */ if (ospf6_spf_install(v, result_table) < 0) continue; /* Skip overloaded routers */ if ((OSPF6_LSA_IS_TYPE(ROUTER, v->lsa) && ospf6_router_is_stub_router(v->lsa))) continue; /* For each LS description in the just-added vertex V's LSA */ size = (VERTEX_IS_TYPE(ROUTER, v) ? sizeof(struct ospf6_router_lsdesc) : sizeof(struct ospf6_network_lsdesc)); for (lsdesc = OSPF6_LSA_HEADER_END(v->lsa->header) + 4; lsdesc + size <= OSPF6_LSA_END(v->lsa->header); lsdesc += size) { lsa = ospf6_lsdesc_lsa(lsdesc, v); if (lsa == NULL) continue; if (OSPF6_LSA_IS_MAXAGE(lsa)) continue; if (!ospf6_lsdesc_backlink(lsa, lsdesc, v)) continue; w = ospf6_vertex_create(lsa); w->area = oa; w->parent = v; if (VERTEX_IS_TYPE(ROUTER, v)) { w->cost = v->cost + ROUTER_LSDESC_GET_METRIC(lsdesc); w->hops = v->hops + (VERTEX_IS_TYPE(NETWORK, w) ? 0 : 1); } else { /* NETWORK */ w->cost = v->cost; w->hops = v->hops + 1; } /* nexthop calculation */ if (w->hops == 0) ospf6_add_nexthop( w->nh_list, ROUTER_LSDESC_GET_IFID(lsdesc), NULL); else if (w->hops == 1 && v->hops == 0) ospf6_nexthop_calc(w, v, lsdesc); else ospf6_copy_nexthops(w->nh_list, v->nh_list); /* add new candidate to the candidate_list */ if (IS_OSPF6_DEBUG_SPF(PROCESS)) zlog_debug( " New candidate: %s hops %d cost %d", w->name, w->hops, w->cost); vertex_pqueue_add(&candidate_list, w); } } //vertex_pqueue_fini(&candidate_list); ospf6_remove_temp_router_lsa(oa); oa->spf_calculation++; } static void ospf6_spf_log_database(struct ospf6_area *oa) { char *p, *end, buffer[256]; struct listnode *node; struct ospf6_interface *oi; p = buffer; end = buffer + sizeof(buffer); snprintf(p, end - p, "SPF on DB (#LSAs):"); p = (buffer + strlen(buffer) < end ? buffer + strlen(buffer) : end); snprintf(p, end - p, " Area %s: %d", oa->name, oa->lsdb->count); p = (buffer + strlen(buffer) < end ? buffer + strlen(buffer) : end); for (ALL_LIST_ELEMENTS_RO(oa->if_list, node, oi)) { snprintf(p, end - p, " I/F %s: %d", oi->interface->name, oi->lsdb->count); p = (buffer + strlen(buffer) < end ? buffer + strlen(buffer) : end); } zlog_debug("%s", buffer); } static int ospf6_spf_calculation_thread(struct thread *t) { struct ospf6_area *oa; struct ospf6 *ospf6; struct timeval start, end, runtime; struct listnode *node; int areas_processed = 0; char rbuf[32]; ospf6 = (struct ospf6 *)THREAD_ARG(t); ospf6->t_spf_calc = NULL; /* execute SPF calculation */ monotime(&start); ospf6->ts_spf = start; if (ospf6_is_router_abr(ospf6)) ospf6_abr_range_reset_cost(ospf6); for (ALL_LIST_ELEMENTS_RO(ospf6->area_list, node, oa)) { if (oa == ospf6->backbone) continue; monotime(&oa->ts_spf); if (IS_OSPF6_DEBUG_SPF(PROCESS)) zlog_debug("SPF calculation for Area %s", oa->name); if (IS_OSPF6_DEBUG_SPF(DATABASE)) ospf6_spf_log_database(oa); ospf6_spf_calculation(ospf6->router_id, oa->spf_table, oa); ospf6_intra_route_calculation(oa); ospf6_intra_brouter_calculation(oa); areas_processed++; } if (ospf6->backbone) { monotime(&ospf6->backbone->ts_spf); if (IS_OSPF6_DEBUG_SPF(PROCESS)) zlog_debug("SPF calculation for Backbone area %s", ospf6->backbone->name); if (IS_OSPF6_DEBUG_SPF(DATABASE)) ospf6_spf_log_database(ospf6->backbone); ospf6_spf_calculation(ospf6->router_id, ospf6->backbone->spf_table, ospf6->backbone); ospf6_intra_route_calculation(ospf6->backbone); ospf6_intra_brouter_calculation(ospf6->backbone); areas_processed++; } if (ospf6_is_router_abr(ospf6)) ospf6_abr_defaults_to_stub(ospf6); monotime(&end); timersub(&end, &start, &runtime); ospf6->ts_spf_duration = runtime; ospf6_spf_reason_string(ospf6->spf_reason, rbuf, sizeof(rbuf)); if (IS_OSPF6_DEBUG_SPF(PROCESS) || IS_OSPF6_DEBUG_SPF(TIME)) zlog_debug("SPF runtime: %lld sec %lld usec", (long long)runtime.tv_sec, (long long)runtime.tv_usec); zlog_info( "SPF processing: # Areas: %d, SPF runtime: %lld sec %lld usec, " "Reason: %s\n", areas_processed, (long long)runtime.tv_sec, (long long)runtime.tv_usec, rbuf); ospf6->last_spf_reason = ospf6->spf_reason; ospf6_reset_spf_reason(ospf6); return 0; } /* Add schedule for SPF calculation. To avoid frequenst SPF calc, we set timer for SPF calc. */ void ospf6_spf_schedule(struct ospf6 *ospf6, unsigned int reason) { unsigned long delay, elapsed, ht; /* OSPF instance does not exist. */ if (ospf6 == NULL) return; ospf6_set_spf_reason(ospf6, reason); if (IS_OSPF6_DEBUG_SPF(PROCESS) || IS_OSPF6_DEBUG_SPF(TIME)) { char rbuf[32]; ospf6_spf_reason_string(reason, rbuf, sizeof(rbuf)); zlog_debug("SPF: calculation timer scheduled (reason %s)", rbuf); } /* SPF calculation timer is already scheduled. */ if (ospf6->t_spf_calc) { if (IS_OSPF6_DEBUG_SPF(PROCESS) || IS_OSPF6_DEBUG_SPF(TIME)) zlog_debug( "SPF: calculation timer is already scheduled: %p", (void *)ospf6->t_spf_calc); return; } elapsed = monotime_since(&ospf6->ts_spf, NULL) / 1000LL; ht = ospf6->spf_holdtime * ospf6->spf_hold_multiplier; if (ht > ospf6->spf_max_holdtime) ht = ospf6->spf_max_holdtime; /* Get SPF calculation delay time. */ if (elapsed < ht) { /* Got an event within the hold time of last SPF. We need to * increase the hold_multiplier, if it's not already at/past * maximum value, and wasn't already increased.. */ if (ht < ospf6->spf_max_holdtime) ospf6->spf_hold_multiplier++; /* always honour the SPF initial delay */ if ((ht - elapsed) < ospf6->spf_delay) delay = ospf6->spf_delay; else delay = ht - elapsed; } else { /* Event is past required hold-time of last SPF */ delay = ospf6->spf_delay; ospf6->spf_hold_multiplier = 1; } if (IS_OSPF6_DEBUG_SPF(PROCESS) || IS_OSPF6_DEBUG_SPF(TIME)) zlog_debug("SPF: calculation timer delay = %ld", delay); zlog_info("SPF: Scheduled in %ld msec", delay); ospf6->t_spf_calc = NULL; thread_add_timer_msec(master, ospf6_spf_calculation_thread, ospf6, delay, &ospf6->t_spf_calc); } void ospf6_spf_display_subtree(struct vty *vty, const char *prefix, int rest, struct ospf6_vertex *v) { struct listnode *node, *nnode; struct ospf6_vertex *c; char *next_prefix; int len; int restnum; /* "prefix" is the space prefix of the display line */ vty_out(vty, "%s+-%s [%d]\n", prefix, v->name, v->cost); len = strlen(prefix) + 4; next_prefix = (char *)malloc(len); if (next_prefix == NULL) { vty_out(vty, "malloc failed\n"); return; } snprintf(next_prefix, len, "%s%s", prefix, (rest ? "| " : " ")); restnum = listcount(v->child_list); for (ALL_LIST_ELEMENTS(v->child_list, node, nnode, c)) { restnum--; ospf6_spf_display_subtree(vty, next_prefix, restnum, c); } free(next_prefix); } DEFUN (debug_ospf6_spf_process, debug_ospf6_spf_process_cmd, "debug ospf6 spf process", DEBUG_STR OSPF6_STR "Debug SPF Calculation\n" "Debug Detailed SPF Process\n" ) { unsigned char level = 0; level = OSPF6_DEBUG_SPF_PROCESS; OSPF6_DEBUG_SPF_ON(level); return CMD_SUCCESS; } DEFUN (debug_ospf6_spf_time, debug_ospf6_spf_time_cmd, "debug ospf6 spf time", DEBUG_STR OSPF6_STR "Debug SPF Calculation\n" "Measure time taken by SPF Calculation\n" ) { unsigned char level = 0; level = OSPF6_DEBUG_SPF_TIME; OSPF6_DEBUG_SPF_ON(level); return CMD_SUCCESS; } DEFUN (debug_ospf6_spf_database, debug_ospf6_spf_database_cmd, "debug ospf6 spf database", DEBUG_STR OSPF6_STR "Debug SPF Calculation\n" "Log number of LSAs at SPF Calculation time\n" ) { unsigned char level = 0; level = OSPF6_DEBUG_SPF_DATABASE; OSPF6_DEBUG_SPF_ON(level); return CMD_SUCCESS; } DEFUN (no_debug_ospf6_spf_process, no_debug_ospf6_spf_process_cmd, "no debug ospf6 spf process", NO_STR DEBUG_STR OSPF6_STR "Quit Debugging SPF Calculation\n" "Quit Debugging Detailed SPF Process\n" ) { unsigned char level = 0; level = OSPF6_DEBUG_SPF_PROCESS; OSPF6_DEBUG_SPF_OFF(level); return CMD_SUCCESS; } DEFUN (no_debug_ospf6_spf_time, no_debug_ospf6_spf_time_cmd, "no debug ospf6 spf time", NO_STR DEBUG_STR OSPF6_STR "Quit Debugging SPF Calculation\n" "Quit Measuring time taken by SPF Calculation\n" ) { unsigned char level = 0; level = OSPF6_DEBUG_SPF_TIME; OSPF6_DEBUG_SPF_OFF(level); return CMD_SUCCESS; } DEFUN (no_debug_ospf6_spf_database, no_debug_ospf6_spf_database_cmd, "no debug ospf6 spf database", NO_STR DEBUG_STR OSPF6_STR "Debug SPF Calculation\n" "Quit Logging number of LSAs at SPF Calculation time\n" ) { unsigned char level = 0; level = OSPF6_DEBUG_SPF_DATABASE; OSPF6_DEBUG_SPF_OFF(level); return CMD_SUCCESS; } static int ospf6_timers_spf_set(struct vty *vty, unsigned int delay, unsigned int hold, unsigned int max) { VTY_DECLVAR_CONTEXT(ospf6, ospf); ospf->spf_delay = delay; ospf->spf_holdtime = hold; ospf->spf_max_holdtime = max; return CMD_SUCCESS; } DEFUN (ospf6_timers_throttle_spf, ospf6_timers_throttle_spf_cmd, "timers throttle spf (0-600000) (0-600000) (0-600000)", "Adjust routing timers\n" "Throttling adaptive timer\n" "OSPF6 SPF timers\n" "Delay (msec) from first change received till SPF calculation\n" "Initial hold time (msec) between consecutive SPF calculations\n" "Maximum hold time (msec)\n") { int idx_number = 3; int idx_number_2 = 4; int idx_number_3 = 5; unsigned int delay, hold, max; delay = strtoul(argv[idx_number]->arg, NULL, 10); hold = strtoul(argv[idx_number_2]->arg, NULL, 10); max = strtoul(argv[idx_number_3]->arg, NULL, 10); return ospf6_timers_spf_set(vty, delay, hold, max); } DEFUN (no_ospf6_timers_throttle_spf, no_ospf6_timers_throttle_spf_cmd, "no timers throttle spf [(0-600000) (0-600000) (0-600000)]", NO_STR "Adjust routing timers\n" "Throttling adaptive timer\n" "OSPF6 SPF timers\n" "Delay (msec) from first change received till SPF calculation\n" "Initial hold time (msec) between consecutive SPF calculations\n" "Maximum hold time (msec)\n") { return ospf6_timers_spf_set(vty, OSPF_SPF_DELAY_DEFAULT, OSPF_SPF_HOLDTIME_DEFAULT, OSPF_SPF_MAX_HOLDTIME_DEFAULT); } int config_write_ospf6_debug_spf(struct vty *vty) { if (IS_OSPF6_DEBUG_SPF(PROCESS)) vty_out(vty, "debug ospf6 spf process\n"); if (IS_OSPF6_DEBUG_SPF(TIME)) vty_out(vty, "debug ospf6 spf time\n"); if (IS_OSPF6_DEBUG_SPF(DATABASE)) vty_out(vty, "debug ospf6 spf database\n"); return 0; } void ospf6_spf_config_write(struct vty *vty) { if (ospf6->spf_delay != OSPF_SPF_DELAY_DEFAULT || ospf6->spf_holdtime != OSPF_SPF_HOLDTIME_DEFAULT || ospf6->spf_max_holdtime != OSPF_SPF_MAX_HOLDTIME_DEFAULT) vty_out(vty, " timers throttle spf %d %d %d\n", ospf6->spf_delay, ospf6->spf_holdtime, ospf6->spf_max_holdtime); } void install_element_ospf6_debug_spf(void) { install_element(ENABLE_NODE, &debug_ospf6_spf_process_cmd); install_element(ENABLE_NODE, &debug_ospf6_spf_time_cmd); install_element(ENABLE_NODE, &debug_ospf6_spf_database_cmd); install_element(ENABLE_NODE, &no_debug_ospf6_spf_process_cmd); install_element(ENABLE_NODE, &no_debug_ospf6_spf_time_cmd); install_element(ENABLE_NODE, &no_debug_ospf6_spf_database_cmd); install_element(CONFIG_NODE, &debug_ospf6_spf_process_cmd); install_element(CONFIG_NODE, &debug_ospf6_spf_time_cmd); install_element(CONFIG_NODE, &debug_ospf6_spf_database_cmd); install_element(CONFIG_NODE, &no_debug_ospf6_spf_process_cmd); install_element(CONFIG_NODE, &no_debug_ospf6_spf_time_cmd); install_element(CONFIG_NODE, &no_debug_ospf6_spf_database_cmd); } void ospf6_spf_init(void) { install_element(OSPF6_NODE, &ospf6_timers_throttle_spf_cmd); install_element(OSPF6_NODE, &no_ospf6_timers_throttle_spf_cmd); } /* Create Aggregated Large Router-LSA from multiple Link-State IDs * RFC 5340 A 4.3: * When more than one router-LSA is received from a single router, * the links are processed as if concatenated into a single LSA.*/ struct ospf6_lsa *ospf6_create_single_router_lsa(struct ospf6_area *area, struct ospf6_lsdb *lsdb, uint32_t adv_router) { struct ospf6_lsa *lsa = NULL; struct ospf6_lsa *rtr_lsa = NULL; struct ospf6_lsa_header *lsa_header = NULL; uint8_t *new_header = NULL; const struct route_node *end = NULL; uint16_t lsa_length, total_lsa_length = 0, num_lsa = 0; uint16_t type = 0; char ifbuf[16]; uint32_t interface_id; caddr_t lsd; lsa_length = sizeof(struct ospf6_lsa_header) + sizeof(struct ospf6_router_lsa); total_lsa_length = lsa_length; type = htons(OSPF6_LSTYPE_ROUTER); /* First check Aggregated LSA formed earlier in Cache */ lsa = ospf6_lsdb_lookup(type, htonl(0), adv_router, area->temp_router_lsa_lsdb); if (lsa) return lsa; inet_ntop(AF_INET, &adv_router, ifbuf, sizeof(ifbuf)); /* Determine total LSA length from all link state ids */ end = ospf6_lsdb_head(lsdb, 2, type, adv_router, &rtr_lsa); while (rtr_lsa) { lsa = rtr_lsa; if (OSPF6_LSA_IS_MAXAGE(rtr_lsa)) { rtr_lsa = ospf6_lsdb_next(end, rtr_lsa); continue; } lsa_header = (struct ospf6_lsa_header *)rtr_lsa->header; total_lsa_length += (ntohs(lsa_header->length) - lsa_length); num_lsa++; rtr_lsa = ospf6_lsdb_next(end, rtr_lsa); } if (IS_OSPF6_DEBUG_SPF(PROCESS)) zlog_debug("%s: adv_router %s num_lsa %u to convert.", __PRETTY_FUNCTION__, ifbuf, num_lsa); if (num_lsa == 1) return lsa; if (num_lsa == 0) { if (IS_OSPF6_DEBUG_SPF(PROCESS)) zlog_debug("%s: adv_router %s not found in LSDB.", __PRETTY_FUNCTION__, ifbuf); return NULL; } /* Allocate memory for this LSA */ new_header = XMALLOC(MTYPE_OSPF6_LSA_HEADER, total_lsa_length); /* LSA information structure */ lsa = XCALLOC(MTYPE_OSPF6_LSA, sizeof(struct ospf6_lsa)); lsa->header = (struct ospf6_lsa_header *)new_header; lsa->lsdb = area->temp_router_lsa_lsdb; /* Fill Larger LSA Payload */ end = ospf6_lsdb_head(lsdb, 2, type, adv_router, &rtr_lsa); /* * We assume at this point in time that rtr_lsa is * a valid pointer. */ assert(rtr_lsa); if (!OSPF6_LSA_IS_MAXAGE(rtr_lsa)) { /* Append first Link State ID LSA */ lsa_header = (struct ospf6_lsa_header *)rtr_lsa->header; memcpy(new_header, lsa_header, ntohs(lsa_header->length)); /* Assign new lsa length as aggregated length. */ ((struct ospf6_lsa_header *)new_header)->length = htons(total_lsa_length); new_header += ntohs(lsa_header->length); num_lsa--; } /* Print LSA Name */ ospf6_lsa_printbuf(lsa, lsa->name, sizeof(lsa->name)); rtr_lsa = ospf6_lsdb_next(end, rtr_lsa); while (rtr_lsa) { if (OSPF6_LSA_IS_MAXAGE(rtr_lsa)) { rtr_lsa = ospf6_lsdb_next(end, rtr_lsa); continue; } if (IS_OSPF6_DEBUG_SPF(PROCESS)) { lsd = OSPF6_LSA_HEADER_END(rtr_lsa->header) + 4; interface_id = ROUTER_LSDESC_GET_IFID(lsd); inet_ntop(AF_INET, &interface_id, ifbuf, sizeof(ifbuf)); zlog_debug( "%s: Next Router LSA %s to aggreat with len %u interface_id %s", __PRETTY_FUNCTION__, rtr_lsa->name, ntohs(lsa_header->length), ifbuf); } /* Append Next Link State ID LSA */ lsa_header = (struct ospf6_lsa_header *)rtr_lsa->header; memcpy(new_header, (OSPF6_LSA_HEADER_END(rtr_lsa->header) + 4), (ntohs(lsa_header->length) - lsa_length)); new_header += (ntohs(lsa_header->length) - lsa_length); num_lsa--; rtr_lsa = ospf6_lsdb_next(end, rtr_lsa); } /* Calculate birth of this lsa */ ospf6_lsa_age_set(lsa); /* Store Aggregated LSA into area temp lsdb */ ospf6_lsdb_add(lsa, area->temp_router_lsa_lsdb); if (IS_OSPF6_DEBUG_SPF(PROCESS)) zlog_debug("%s: LSA %s id %u type 0%x len %u num_lsa %u", __PRETTY_FUNCTION__, lsa->name, ntohl(lsa->header->id), ntohs(lsa->header->type), ntohs(lsa->header->length), num_lsa); return lsa; } void ospf6_remove_temp_router_lsa(struct ospf6_area *area) { struct ospf6_lsa *lsa = NULL; for (ALL_LSDB(area->temp_router_lsa_lsdb, lsa)) { if (IS_OSPF6_DEBUG_SPF(PROCESS)) zlog_debug( "%s Remove LSA %s lsa->lock %u lsdb count %u", __PRETTY_FUNCTION__, lsa->name, lsa->lock, area->temp_router_lsa_lsdb->count); ospf6_lsdb_remove(lsa, area->temp_router_lsa_lsdb); } } frr-7.2.1/ospf6d/ospf6_spf.h0000644000000000000000000001107213610377563012474 00000000000000/* * Copyright (C) 2003 Yasuhiro Ohara * * This file is part of GNU Zebra. * * GNU Zebra is free software; you can redistribute it and/or modify it * under the terms of the GNU General Public License as published by the * Free Software Foundation; either version 2, or (at your option) any * later version. * * GNU Zebra 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; see the file COPYING; if not, write to the Free Software * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA */ #ifndef OSPF6_SPF_H #define OSPF6_SPF_H #include "typesafe.h" #include "ospf6_top.h" /* Debug option */ extern unsigned char conf_debug_ospf6_spf; #define OSPF6_DEBUG_SPF_PROCESS 0x01 #define OSPF6_DEBUG_SPF_TIME 0x02 #define OSPF6_DEBUG_SPF_DATABASE 0x04 #define OSPF6_DEBUG_SPF_ON(level) (conf_debug_ospf6_spf |= (level)) #define OSPF6_DEBUG_SPF_OFF(level) (conf_debug_ospf6_spf &= ~(level)) #define IS_OSPF6_DEBUG_SPF(level) \ (conf_debug_ospf6_spf & OSPF6_DEBUG_SPF_##level) PREDECL_SKIPLIST_NONUNIQ(vertex_pqueue) /* Transit Vertex */ struct ospf6_vertex { /* type of this vertex */ uint8_t type; /* Vertex Identifier */ struct prefix vertex_id; struct vertex_pqueue_item pqi; /* Identifier String */ char name[128]; /* Associated Area */ struct ospf6_area *area; /* Associated LSA */ struct ospf6_lsa *lsa; /* Distance from Root (i.e. Cost) */ uint32_t cost; /* Router hops to this node */ uint8_t hops; /* capability bits */ uint8_t capability; /* Optional capabilities */ uint8_t options[3]; /* For tree display */ struct ospf6_vertex *parent; struct list *child_list; /* nexthops to this node */ struct list *nh_list; uint32_t link_id; }; #define OSPF6_VERTEX_TYPE_ROUTER 0x01 #define OSPF6_VERTEX_TYPE_NETWORK 0x02 #define VERTEX_IS_TYPE(t, v) ((v)->type == OSPF6_VERTEX_TYPE_##t ? 1 : 0) /* What triggered the SPF? */ #define OSPF6_SPF_FLAGS_ROUTER_LSA_ADDED (1 << 0) #define OSPF6_SPF_FLAGS_ROUTER_LSA_REMOVED (1 << 1) #define OSPF6_SPF_FLAGS_NETWORK_LSA_ADDED (1 << 2) #define OSPF6_SPF_FLAGS_NETWORK_LSA_REMOVED (1 << 3) #define OSPF6_SPF_FLAGS_LINK_LSA_ADDED (1 << 4) #define OSPF6_SPF_FLAGS_LINK_LSA_REMOVED (1 << 5) #define OSPF6_SPF_FLAGS_ROUTER_LSA_ORIGINATED (1 << 6) #define OSPF6_SPF_FLAGS_NETWORK_LSA_ORIGINATED (1 << 7) static inline void ospf6_set_spf_reason(struct ospf6 *ospf, unsigned int reason) { ospf->spf_reason |= reason; } static inline void ospf6_reset_spf_reason(struct ospf6 *ospf) { ospf->spf_reason = 0; } static inline unsigned int ospf6_lsadd_to_spf_reason(struct ospf6_lsa *lsa) { unsigned int reason = 0; switch (ntohs(lsa->header->type)) { case OSPF6_LSTYPE_ROUTER: reason = OSPF6_SPF_FLAGS_ROUTER_LSA_ADDED; break; case OSPF6_LSTYPE_NETWORK: reason = OSPF6_SPF_FLAGS_NETWORK_LSA_ADDED; break; case OSPF6_LSTYPE_LINK: reason = OSPF6_SPF_FLAGS_LINK_LSA_ADDED; break; default: break; } return (reason); } static inline unsigned int ospf6_lsremove_to_spf_reason(struct ospf6_lsa *lsa) { unsigned int reason = 0; switch (ntohs(lsa->header->type)) { case OSPF6_LSTYPE_ROUTER: reason = OSPF6_SPF_FLAGS_ROUTER_LSA_REMOVED; break; case OSPF6_LSTYPE_NETWORK: reason = OSPF6_SPF_FLAGS_NETWORK_LSA_REMOVED; break; case OSPF6_LSTYPE_LINK: reason = OSPF6_SPF_FLAGS_LINK_LSA_REMOVED; break; default: break; } return (reason); } extern void ospf6_spf_table_finish(struct ospf6_route_table *result_table); extern void ospf6_spf_calculation(uint32_t router_id, struct ospf6_route_table *result_table, struct ospf6_area *oa); extern void ospf6_spf_schedule(struct ospf6 *ospf, unsigned int reason); extern void ospf6_spf_display_subtree(struct vty *vty, const char *prefix, int rest, struct ospf6_vertex *v); extern void ospf6_spf_config_write(struct vty *vty); extern int config_write_ospf6_debug_spf(struct vty *vty); extern void install_element_ospf6_debug_spf(void); extern void ospf6_spf_init(void); extern void ospf6_spf_reason_string(unsigned int reason, char *buf, int size); extern struct ospf6_lsa *ospf6_create_single_router_lsa(struct ospf6_area *area, struct ospf6_lsdb *lsdb, uint32_t adv_router); extern void ospf6_remove_temp_router_lsa(struct ospf6_area *area); #endif /* OSPF6_SPF_H */ frr-7.2.1/ospf6d/ospf6_top.c0000644000000000000000000007410413610377563012506 00000000000000/* * Copyright (C) 2003 Yasuhiro Ohara * * This file is part of GNU Zebra. * * GNU Zebra is free software; you can redistribute it and/or modify it * under the terms of the GNU General Public License as published by the * Free Software Foundation; either version 2, or (at your option) any * later version. * * GNU Zebra 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; see the file COPYING; if not, write to the Free Software * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA */ #include #include "log.h" #include "memory.h" #include "vty.h" #include "linklist.h" #include "prefix.h" #include "table.h" #include "thread.h" #include "command.h" #include "defaults.h" #include "ospf6_proto.h" #include "ospf6_message.h" #include "ospf6_lsa.h" #include "ospf6_lsdb.h" #include "ospf6_route.h" #include "ospf6_zebra.h" #include "ospf6_top.h" #include "ospf6_area.h" #include "ospf6_interface.h" #include "ospf6_neighbor.h" #include "ospf6_flood.h" #include "ospf6_asbr.h" #include "ospf6_abr.h" #include "ospf6_intra.h" #include "ospf6_spf.h" #include "ospf6d.h" DEFINE_QOBJ_TYPE(ospf6) /* global ospf6d variable */ struct ospf6 *ospf6; static struct ospf6_master ospf6_master; struct ospf6_master *om6; static void ospf6_disable(struct ospf6 *o); static void ospf6_top_lsdb_hook_add(struct ospf6_lsa *lsa) { switch (ntohs(lsa->header->type)) { case OSPF6_LSTYPE_AS_EXTERNAL: ospf6_asbr_lsa_add(lsa); break; default: break; } } static void ospf6_top_lsdb_hook_remove(struct ospf6_lsa *lsa) { switch (ntohs(lsa->header->type)) { case OSPF6_LSTYPE_AS_EXTERNAL: ospf6_asbr_lsa_remove(lsa, NULL); break; default: break; } } static void ospf6_top_route_hook_add(struct ospf6_route *route) { ospf6_abr_originate_summary(route); ospf6_zebra_route_update_add(route); } static void ospf6_top_route_hook_remove(struct ospf6_route *route) { route->flag |= OSPF6_ROUTE_REMOVE; ospf6_abr_originate_summary(route); ospf6_zebra_route_update_remove(route); } static void ospf6_top_brouter_hook_add(struct ospf6_route *route) { if (IS_OSPF6_DEBUG_EXAMIN(AS_EXTERNAL) || IS_OSPF6_DEBUG_BROUTER) { uint32_t brouter_id; char brouter_name[16]; brouter_id = ADV_ROUTER_IN_PREFIX(&route->prefix); inet_ntop(AF_INET, &brouter_id, brouter_name, sizeof(brouter_name)); zlog_debug("%s: brouter %s add with adv router %x nh count %u", __PRETTY_FUNCTION__, brouter_name, route->path.origin.adv_router, listcount(route->nh_list)); } ospf6_abr_examin_brouter(ADV_ROUTER_IN_PREFIX(&route->prefix)); ospf6_asbr_lsentry_add(route); ospf6_abr_originate_summary(route); } static void ospf6_top_brouter_hook_remove(struct ospf6_route *route) { if (IS_OSPF6_DEBUG_EXAMIN(AS_EXTERNAL) || IS_OSPF6_DEBUG_BROUTER) { uint32_t brouter_id; char brouter_name[16]; brouter_id = ADV_ROUTER_IN_PREFIX(&route->prefix); inet_ntop(AF_INET, &brouter_id, brouter_name, sizeof(brouter_name)); zlog_debug("%s: brouter %p %s del with adv router %x nh %u", __PRETTY_FUNCTION__, (void *)route, brouter_name, route->path.origin.adv_router, listcount(route->nh_list)); } route->flag |= OSPF6_ROUTE_REMOVE; ospf6_abr_examin_brouter(ADV_ROUTER_IN_PREFIX(&route->prefix)); ospf6_asbr_lsentry_remove(route); ospf6_abr_originate_summary(route); } static struct ospf6 *ospf6_create(void) { struct ospf6 *o; o = XCALLOC(MTYPE_OSPF6_TOP, sizeof(struct ospf6)); /* initialize */ monotime(&o->starttime); o->area_list = list_new(); o->area_list->cmp = ospf6_area_cmp; o->lsdb = ospf6_lsdb_create(o); o->lsdb_self = ospf6_lsdb_create(o); o->lsdb->hook_add = ospf6_top_lsdb_hook_add; o->lsdb->hook_remove = ospf6_top_lsdb_hook_remove; o->spf_delay = OSPF_SPF_DELAY_DEFAULT; o->spf_holdtime = OSPF_SPF_HOLDTIME_DEFAULT; o->spf_max_holdtime = OSPF_SPF_MAX_HOLDTIME_DEFAULT; o->spf_hold_multiplier = 1; /* LSA timers value init */ o->lsa_minarrival = OSPF_MIN_LS_ARRIVAL; o->route_table = OSPF6_ROUTE_TABLE_CREATE(GLOBAL, ROUTES); o->route_table->scope = o; o->route_table->hook_add = ospf6_top_route_hook_add; o->route_table->hook_remove = ospf6_top_route_hook_remove; o->brouter_table = OSPF6_ROUTE_TABLE_CREATE(GLOBAL, BORDER_ROUTERS); o->brouter_table->scope = o; o->brouter_table->hook_add = ospf6_top_brouter_hook_add; o->brouter_table->hook_remove = ospf6_top_brouter_hook_remove; o->external_table = OSPF6_ROUTE_TABLE_CREATE(GLOBAL, EXTERNAL_ROUTES); o->external_table->scope = o; o->external_id_table = route_table_init(); o->ref_bandwidth = OSPF6_REFERENCE_BANDWIDTH; o->distance_table = route_table_init(); /* Enable "log-adjacency-changes" */ #if DFLT_OSPF6_LOG_ADJACENCY_CHANGES SET_FLAG(o->config_flags, OSPF6_LOG_ADJACENCY_CHANGES); #endif QOBJ_REG(o, ospf6); return o; } void ospf6_delete(struct ospf6 *o) { struct listnode *node, *nnode; struct ospf6_area *oa; QOBJ_UNREG(o); ospf6_flush_self_originated_lsas_now(); ospf6_disable(ospf6); for (ALL_LIST_ELEMENTS(o->area_list, node, nnode, oa)) ospf6_area_delete(oa); list_delete(&o->area_list); ospf6_lsdb_delete(o->lsdb); ospf6_lsdb_delete(o->lsdb_self); ospf6_route_table_delete(o->route_table); ospf6_route_table_delete(o->brouter_table); ospf6_route_table_delete(o->external_table); route_table_finish(o->external_id_table); ospf6_distance_reset(o); route_table_finish(o->distance_table); XFREE(MTYPE_OSPF6_TOP, o); } static void ospf6_disable(struct ospf6 *o) { struct listnode *node, *nnode; struct ospf6_area *oa; if (!CHECK_FLAG(o->flag, OSPF6_DISABLED)) { SET_FLAG(o->flag, OSPF6_DISABLED); for (ALL_LIST_ELEMENTS(o->area_list, node, nnode, oa)) ospf6_area_disable(oa); /* XXX: This also changes persistent settings */ ospf6_asbr_redistribute_reset(); ospf6_lsdb_remove_all(o->lsdb); ospf6_route_remove_all(o->route_table); ospf6_route_remove_all(o->brouter_table); THREAD_OFF(o->maxage_remover); THREAD_OFF(o->t_spf_calc); THREAD_OFF(o->t_ase_calc); THREAD_OFF(o->t_distribute_update); } } void ospf6_master_init(void) { memset(&ospf6_master, 0, sizeof(struct ospf6_master)); om6 = &ospf6_master; } static int ospf6_maxage_remover(struct thread *thread) { struct ospf6 *o = (struct ospf6 *)THREAD_ARG(thread); struct ospf6_area *oa; struct ospf6_interface *oi; struct ospf6_neighbor *on; struct listnode *i, *j, *k; int reschedule = 0; o->maxage_remover = (struct thread *)NULL; for (ALL_LIST_ELEMENTS_RO(o->area_list, i, oa)) { for (ALL_LIST_ELEMENTS_RO(oa->if_list, j, oi)) { for (ALL_LIST_ELEMENTS_RO(oi->neighbor_list, k, on)) { if (on->state != OSPF6_NEIGHBOR_EXCHANGE && on->state != OSPF6_NEIGHBOR_LOADING) continue; ospf6_maxage_remove(o); return 0; } } } for (ALL_LIST_ELEMENTS_RO(o->area_list, i, oa)) { for (ALL_LIST_ELEMENTS_RO(oa->if_list, j, oi)) { if (ospf6_lsdb_maxage_remover(oi->lsdb)) { reschedule = 1; } } if (ospf6_lsdb_maxage_remover(oa->lsdb)) { reschedule = 1; } } if (ospf6_lsdb_maxage_remover(o->lsdb)) { reschedule = 1; } if (reschedule) { ospf6_maxage_remove(o); } return 0; } void ospf6_maxage_remove(struct ospf6 *o) { if (o) thread_add_timer(master, ospf6_maxage_remover, o, OSPF_LSA_MAXAGE_REMOVE_DELAY_DEFAULT, &o->maxage_remover); } void ospf6_router_id_update(void) { if (!ospf6) return; if (ospf6->router_id_static != 0) ospf6->router_id = ospf6->router_id_static; else ospf6->router_id = om6->zebra_router_id; } /* start ospf6 */ DEFUN_NOSH (router_ospf6, router_ospf6_cmd, "router ospf6", ROUTER_STR OSPF6_STR) { if (ospf6 == NULL) { ospf6 = ospf6_create(); if (ospf6->router_id == 0) ospf6_router_id_update(); } /* set current ospf point. */ VTY_PUSH_CONTEXT(OSPF6_NODE, ospf6); return CMD_SUCCESS; } /* stop ospf6 */ DEFUN (no_router_ospf6, no_router_ospf6_cmd, "no router ospf6", NO_STR ROUTER_STR OSPF6_STR) { if (ospf6 == NULL) vty_out(vty, "OSPFv3 is not configured\n"); else { ospf6_delete(ospf6); ospf6 = NULL; } /* return to config node . */ VTY_PUSH_CONTEXT_NULL(CONFIG_NODE); return CMD_SUCCESS; } /* change Router_ID commands. */ DEFUN(ospf6_router_id, ospf6_router_id_cmd, "ospf6 router-id A.B.C.D", OSPF6_STR "Configure OSPF6 Router-ID\n" V4NOTATION_STR) { VTY_DECLVAR_CONTEXT(ospf6, o); int idx = 0; int ret; const char *router_id_str; uint32_t router_id; struct ospf6_area *oa; struct listnode *node; argv_find(argv, argc, "A.B.C.D", &idx); router_id_str = argv[idx]->arg; ret = inet_pton(AF_INET, router_id_str, &router_id); if (ret == 0) { vty_out(vty, "malformed OSPF Router-ID: %s\n", router_id_str); return CMD_SUCCESS; } o->router_id_static = router_id; for (ALL_LIST_ELEMENTS_RO(o->area_list, node, oa)) { if (oa->full_nbrs) { vty_out(vty, "For this router-id change to take effect," " save config and restart ospf6d\n"); return CMD_SUCCESS; } } o->router_id = router_id; return CMD_SUCCESS; } DEFUN(no_ospf6_router_id, no_ospf6_router_id_cmd, "no ospf6 router-id [A.B.C.D]", NO_STR OSPF6_STR "Configure OSPF6 Router-ID\n" V4NOTATION_STR) { VTY_DECLVAR_CONTEXT(ospf6, o); struct ospf6_area *oa; struct listnode *node; o->router_id_static = 0; for (ALL_LIST_ELEMENTS_RO(o->area_list, node, oa)) { if (oa->full_nbrs) { vty_out(vty, "For this router-id change to take effect," " save config and restart ospf6d\n"); return CMD_SUCCESS; } } o->router_id = 0; if (o->router_id_zebra.s_addr) o->router_id = (uint32_t)o->router_id_zebra.s_addr; return CMD_SUCCESS; } DEFUN (ospf6_log_adjacency_changes, ospf6_log_adjacency_changes_cmd, "log-adjacency-changes", "Log changes in adjacency state\n") { VTY_DECLVAR_CONTEXT(ospf6, ospf6); SET_FLAG(ospf6->config_flags, OSPF6_LOG_ADJACENCY_CHANGES); UNSET_FLAG(ospf6->config_flags, OSPF6_LOG_ADJACENCY_DETAIL); return CMD_SUCCESS; } DEFUN (ospf6_log_adjacency_changes_detail, ospf6_log_adjacency_changes_detail_cmd, "log-adjacency-changes detail", "Log changes in adjacency state\n" "Log all state changes\n") { VTY_DECLVAR_CONTEXT(ospf6, ospf6); SET_FLAG(ospf6->config_flags, OSPF6_LOG_ADJACENCY_CHANGES); SET_FLAG(ospf6->config_flags, OSPF6_LOG_ADJACENCY_DETAIL); return CMD_SUCCESS; } DEFUN (no_ospf6_log_adjacency_changes, no_ospf6_log_adjacency_changes_cmd, "no log-adjacency-changes", NO_STR "Log changes in adjacency state\n") { VTY_DECLVAR_CONTEXT(ospf6, ospf6); UNSET_FLAG(ospf6->config_flags, OSPF6_LOG_ADJACENCY_DETAIL); UNSET_FLAG(ospf6->config_flags, OSPF6_LOG_ADJACENCY_CHANGES); return CMD_SUCCESS; } DEFUN (no_ospf6_log_adjacency_changes_detail, no_ospf6_log_adjacency_changes_detail_cmd, "no log-adjacency-changes detail", NO_STR "Log changes in adjacency state\n" "Log all state changes\n") { VTY_DECLVAR_CONTEXT(ospf6, ospf6); UNSET_FLAG(ospf6->config_flags, OSPF6_LOG_ADJACENCY_DETAIL); return CMD_SUCCESS; } DEFUN (ospf6_timers_lsa, ospf6_timers_lsa_cmd, "timers lsa min-arrival (0-600000)", "Adjust routing timers\n" "OSPF6 LSA timers\n" "Minimum delay in receiving new version of a LSA\n" "Delay in milliseconds\n") { VTY_DECLVAR_CONTEXT(ospf6, ospf); int idx_number = 3; unsigned int minarrival; minarrival = strtoul(argv[idx_number]->arg, NULL, 10); ospf->lsa_minarrival = minarrival; return CMD_SUCCESS; } DEFUN (no_ospf6_timers_lsa, no_ospf6_timers_lsa_cmd, "no timers lsa min-arrival [(0-600000)]", NO_STR "Adjust routing timers\n" "OSPF6 LSA timers\n" "Minimum delay in receiving new version of a LSA\n" "Delay in milliseconds\n") { VTY_DECLVAR_CONTEXT(ospf6, ospf); int idx_number = 4; unsigned int minarrival; if (argc == 5) { minarrival = strtoul(argv[idx_number]->arg, NULL, 10); if (ospf->lsa_minarrival != minarrival || minarrival == OSPF_MIN_LS_ARRIVAL) return CMD_SUCCESS; } ospf->lsa_minarrival = OSPF_MIN_LS_ARRIVAL; return CMD_SUCCESS; } DEFUN (ospf6_distance, ospf6_distance_cmd, "distance (1-255)", "Administrative distance\n" "OSPF6 Administrative distance\n") { VTY_DECLVAR_CONTEXT(ospf6, o); o->distance_all = atoi(argv[1]->arg); return CMD_SUCCESS; } DEFUN (no_ospf6_distance, no_ospf6_distance_cmd, "no distance (1-255)", NO_STR "Administrative distance\n" "OSPF6 Administrative distance\n") { VTY_DECLVAR_CONTEXT(ospf6, o); o->distance_all = 0; return CMD_SUCCESS; } DEFUN (ospf6_distance_ospf6, ospf6_distance_ospf6_cmd, "distance ospf6 {intra-area (1-255)|inter-area (1-255)|external (1-255)}", "Administrative distance\n" "OSPF6 administrative distance\n" "Intra-area routes\n" "Distance for intra-area routes\n" "Inter-area routes\n" "Distance for inter-area routes\n" "External routes\n" "Distance for external routes\n") { VTY_DECLVAR_CONTEXT(ospf6, o); int idx = 0; o->distance_intra = 0; o->distance_inter = 0; o->distance_external = 0; if (argv_find(argv, argc, "intra-area", &idx)) o->distance_intra = atoi(argv[idx + 1]->arg); idx = 0; if (argv_find(argv, argc, "inter-area", &idx)) o->distance_inter = atoi(argv[idx + 1]->arg); idx = 0; if (argv_find(argv, argc, "external", &idx)) o->distance_external = atoi(argv[idx + 1]->arg); return CMD_SUCCESS; } DEFUN (no_ospf6_distance_ospf6, no_ospf6_distance_ospf6_cmd, "no distance ospf6 [{intra-area [(1-255)]|inter-area [(1-255)]|external [(1-255)]}]", NO_STR "Administrative distance\n" "OSPF6 distance\n" "Intra-area routes\n" "Distance for intra-area routes\n" "Inter-area routes\n" "Distance for inter-area routes\n" "External routes\n" "Distance for external routes\n") { VTY_DECLVAR_CONTEXT(ospf6, o); int idx = 0; if (argv_find(argv, argc, "intra-area", &idx) || argc == 3) idx = o->distance_intra = 0; if (argv_find(argv, argc, "inter-area", &idx) || argc == 3) idx = o->distance_inter = 0; if (argv_find(argv, argc, "external", &idx) || argc == 3) o->distance_external = 0; return CMD_SUCCESS; } #if 0 DEFUN (ospf6_distance_source, ospf6_distance_source_cmd, "distance (1-255) X:X::X:X/M [WORD]", "Administrative distance\n" "Distance value\n" "IP source prefix\n" "Access list name\n") { VTY_DECLVAR_CONTEXT(ospf6, o); char *alname = (argc == 4) ? argv[3]->arg : NULL; ospf6_distance_set (vty, o, argv[1]->arg, argv[2]->arg, alname); return CMD_SUCCESS; } DEFUN (no_ospf6_distance_source, no_ospf6_distance_source_cmd, "no distance (1-255) X:X::X:X/M [WORD]", NO_STR "Administrative distance\n" "Distance value\n" "IP source prefix\n" "Access list name\n") { VTY_DECLVAR_CONTEXT(ospf6, o); char *alname = (argc == 5) ? argv[4]->arg : NULL; ospf6_distance_unset (vty, o, argv[2]->arg, argv[3]->arg, alname); return CMD_SUCCESS; } #endif DEFUN (ospf6_interface_area, ospf6_interface_area_cmd, "interface IFNAME area A.B.C.D", "Enable routing on an IPv6 interface\n" IFNAME_STR "Specify the OSPF6 area ID\n" "OSPF6 area ID in IPv4 address notation\n" ) { VTY_DECLVAR_CONTEXT(ospf6, o); int idx_ifname = 1; int idx_ipv4 = 3; struct ospf6_area *oa; struct ospf6_interface *oi; struct interface *ifp; uint32_t area_id; /* find/create ospf6 interface */ ifp = if_get_by_name(argv[idx_ifname]->arg, VRF_DEFAULT); oi = (struct ospf6_interface *)ifp->info; if (oi == NULL) oi = ospf6_interface_create(ifp); if (oi->area) { vty_out(vty, "%s already attached to Area %s\n", oi->interface->name, oi->area->name); return CMD_SUCCESS; } /* parse Area-ID */ if (inet_pton(AF_INET, argv[idx_ipv4]->arg, &area_id) != 1) { vty_out(vty, "Invalid Area-ID: %s\n", argv[idx_ipv4]->arg); return CMD_SUCCESS; } /* find/create ospf6 area */ oa = ospf6_area_lookup(area_id, o); if (oa == NULL) oa = ospf6_area_create(area_id, o, OSPF6_AREA_FMT_DOTTEDQUAD); /* attach interface to area */ listnode_add(oa->if_list, oi); /* sort ?? */ oi->area = oa; SET_FLAG(oa->flag, OSPF6_AREA_ENABLE); /* ospf6 process is currently disabled, not much more to do */ if (CHECK_FLAG(o->flag, OSPF6_DISABLED)) return CMD_SUCCESS; /* start up */ ospf6_interface_enable(oi); /* If the router is ABR, originate summary routes */ if (ospf6_is_router_abr(o)) ospf6_abr_enable_area(oa); return CMD_SUCCESS; } DEFUN (no_ospf6_interface_area, no_ospf6_interface_area_cmd, "no interface IFNAME area A.B.C.D", NO_STR "Disable routing on an IPv6 interface\n" IFNAME_STR "Specify the OSPF6 area ID\n" "OSPF6 area ID in IPv4 address notation\n" ) { int idx_ifname = 2; int idx_ipv4 = 4; struct ospf6_interface *oi; struct ospf6_area *oa; struct interface *ifp; uint32_t area_id; ifp = if_lookup_by_name(argv[idx_ifname]->arg, VRF_DEFAULT); if (ifp == NULL) { vty_out(vty, "No such interface %s\n", argv[idx_ifname]->arg); return CMD_SUCCESS; } oi = (struct ospf6_interface *)ifp->info; if (oi == NULL) { vty_out(vty, "Interface %s not enabled\n", ifp->name); return CMD_SUCCESS; } /* parse Area-ID */ if (inet_pton(AF_INET, argv[idx_ipv4]->arg, &area_id) != 1) { vty_out(vty, "Invalid Area-ID: %s\n", argv[idx_ipv4]->arg); return CMD_SUCCESS; } /* Verify Area */ if (oi->area == NULL) { vty_out(vty, "No such Area-ID: %s\n", argv[idx_ipv4]->arg); return CMD_SUCCESS; } if (oi->area->area_id != area_id) { vty_out(vty, "Wrong Area-ID: %s is attached to area %s\n", oi->interface->name, oi->area->name); return CMD_SUCCESS; } thread_execute(master, interface_down, oi, 0); oa = oi->area; listnode_delete(oi->area->if_list, oi); oi->area = (struct ospf6_area *)NULL; /* Withdraw inter-area routes from this area, if necessary */ if (oa->if_list->count == 0) { UNSET_FLAG(oa->flag, OSPF6_AREA_ENABLE); ospf6_abr_disable_area(oa); } return CMD_SUCCESS; } DEFUN (ospf6_stub_router_admin, ospf6_stub_router_admin_cmd, "stub-router administrative", "Make router a stub router\n" "Administratively applied, for an indefinite period\n") { struct listnode *node; struct ospf6_area *oa; if (!CHECK_FLAG(ospf6->flag, OSPF6_STUB_ROUTER)) { for (ALL_LIST_ELEMENTS_RO(ospf6->area_list, node, oa)) { OSPF6_OPT_CLEAR(oa->options, OSPF6_OPT_V6); OSPF6_OPT_CLEAR(oa->options, OSPF6_OPT_R); OSPF6_ROUTER_LSA_SCHEDULE(oa); } SET_FLAG(ospf6->flag, OSPF6_STUB_ROUTER); } return CMD_SUCCESS; } DEFUN (no_ospf6_stub_router_admin, no_ospf6_stub_router_admin_cmd, "no stub-router administrative", NO_STR "Make router a stub router\n" "Administratively applied, for an indefinite period\n") { struct listnode *node; struct ospf6_area *oa; if (CHECK_FLAG(ospf6->flag, OSPF6_STUB_ROUTER)) { for (ALL_LIST_ELEMENTS_RO(ospf6->area_list, node, oa)) { OSPF6_OPT_SET(oa->options, OSPF6_OPT_V6); OSPF6_OPT_SET(oa->options, OSPF6_OPT_R); OSPF6_ROUTER_LSA_SCHEDULE(oa); } UNSET_FLAG(ospf6->flag, OSPF6_STUB_ROUTER); } return CMD_SUCCESS; } #if 0 DEFUN (ospf6_stub_router_startup, ospf6_stub_router_startup_cmd, "stub-router on-startup (5-86400)", "Make router a stub router\n" "Advertise inability to be a transit router\n" "Automatically advertise as stub-router on startup of OSPF6\n" "Time (seconds) to advertise self as stub-router\n") { return CMD_SUCCESS; } DEFUN (no_ospf6_stub_router_startup, no_ospf6_stub_router_startup_cmd, "no stub-router on-startup", NO_STR "Make router a stub router\n" "Advertise inability to be a transit router\n" "Automatically advertise as stub-router on startup of OSPF6\n" "Time (seconds) to advertise self as stub-router\n") { return CMD_SUCCESS; } DEFUN (ospf6_stub_router_shutdown, ospf6_stub_router_shutdown_cmd, "stub-router on-shutdown (5-86400)", "Make router a stub router\n" "Advertise inability to be a transit router\n" "Automatically advertise as stub-router before shutdown\n" "Time (seconds) to advertise self as stub-router\n") { return CMD_SUCCESS; } DEFUN (no_ospf6_stub_router_shutdown, no_ospf6_stub_router_shutdown_cmd, "no stub-router on-shutdown", NO_STR "Make router a stub router\n" "Advertise inability to be a transit router\n" "Automatically advertise as stub-router before shutdown\n" "Time (seconds) to advertise self as stub-router\n") { return CMD_SUCCESS; } #endif static void ospf6_show(struct vty *vty, struct ospf6 *o) { struct listnode *n; struct ospf6_area *oa; char router_id[16], duration[32]; struct timeval now, running, result; char buf[32], rbuf[32]; /* process id, router id */ inet_ntop(AF_INET, &o->router_id, router_id, sizeof(router_id)); vty_out(vty, " OSPFv3 Routing Process (0) with Router-ID %s\n", router_id); /* running time */ monotime(&now); timersub(&now, &o->starttime, &running); timerstring(&running, duration, sizeof(duration)); vty_out(vty, " Running %s\n", duration); /* Redistribute configuration */ /* XXX */ vty_out(vty, " LSA minimum arrival %d msecs\n", o->lsa_minarrival); /* Show SPF parameters */ vty_out(vty, " Initial SPF scheduling delay %d millisec(s)\n" " Minimum hold time between consecutive SPFs %d millsecond(s)\n" " Maximum hold time between consecutive SPFs %d millsecond(s)\n" " Hold time multiplier is currently %d\n", o->spf_delay, o->spf_holdtime, o->spf_max_holdtime, o->spf_hold_multiplier); vty_out(vty, " SPF algorithm "); if (o->ts_spf.tv_sec || o->ts_spf.tv_usec) { timersub(&now, &o->ts_spf, &result); timerstring(&result, buf, sizeof(buf)); ospf6_spf_reason_string(o->last_spf_reason, rbuf, sizeof(rbuf)); vty_out(vty, "last executed %s ago, reason %s\n", buf, rbuf); vty_out(vty, " Last SPF duration %lld sec %lld usec\n", (long long)o->ts_spf_duration.tv_sec, (long long)o->ts_spf_duration.tv_usec); } else vty_out(vty, "has not been run\n"); threadtimer_string(now, o->t_spf_calc, buf, sizeof(buf)); vty_out(vty, " SPF timer %s%s\n", (o->t_spf_calc ? "due in " : "is "), buf); if (CHECK_FLAG(o->flag, OSPF6_STUB_ROUTER)) vty_out(vty, " Router Is Stub Router\n"); /* LSAs */ vty_out(vty, " Number of AS scoped LSAs is %u\n", o->lsdb->count); /* Areas */ vty_out(vty, " Number of areas in this router is %u\n", listcount(o->area_list)); if (CHECK_FLAG(o->config_flags, OSPF6_LOG_ADJACENCY_CHANGES)) { if (CHECK_FLAG(o->config_flags, OSPF6_LOG_ADJACENCY_DETAIL)) vty_out(vty, " All adjacency changes are logged\n"); else vty_out(vty, " Adjacency changes are logged\n"); } vty_out(vty, "\n"); for (ALL_LIST_ELEMENTS_RO(o->area_list, n, oa)) ospf6_area_show(vty, oa); } /* show top level structures */ DEFUN (show_ipv6_ospf6, show_ipv6_ospf6_cmd, "show ipv6 ospf6", SHOW_STR IP6_STR OSPF6_STR) { OSPF6_CMD_CHECK_RUNNING(); ospf6_show(vty, ospf6); return CMD_SUCCESS; } DEFUN (show_ipv6_ospf6_route, show_ipv6_ospf6_route_cmd, "show ipv6 ospf6 route []", SHOW_STR IP6_STR OSPF6_STR ROUTE_STR "Display Intra-Area routes\n" "Display Inter-Area routes\n" "Display Type-1 External routes\n" "Display Type-2 External routes\n" "Specify IPv6 address\n" "Specify IPv6 prefix\n" "Detailed information\n" "Summary of route table\n") { OSPF6_CMD_CHECK_RUNNING(); ospf6_route_table_show(vty, 4, argc, argv, ospf6->route_table); return CMD_SUCCESS; } DEFUN (show_ipv6_ospf6_route_match, show_ipv6_ospf6_route_match_cmd, "show ipv6 ospf6 route X:X::X:X/M ", SHOW_STR IP6_STR OSPF6_STR ROUTE_STR "Specify IPv6 prefix\n" "Display routes which match the specified route\n" "Display routes longer than the specified route\n") { OSPF6_CMD_CHECK_RUNNING(); ospf6_route_table_show(vty, 4, argc, argv, ospf6->route_table); return CMD_SUCCESS; } DEFUN (show_ipv6_ospf6_route_match_detail, show_ipv6_ospf6_route_match_detail_cmd, "show ipv6 ospf6 route X:X::X:X/M match detail", SHOW_STR IP6_STR OSPF6_STR ROUTE_STR "Specify IPv6 prefix\n" "Display routes which match the specified route\n" "Detailed information\n" ) { OSPF6_CMD_CHECK_RUNNING(); ospf6_route_table_show(vty, 4, argc, argv, ospf6->route_table); return CMD_SUCCESS; } DEFUN (show_ipv6_ospf6_route_type_detail, show_ipv6_ospf6_route_type_detail_cmd, "show ipv6 ospf6 route detail", SHOW_STR IP6_STR OSPF6_STR ROUTE_STR "Display Intra-Area routes\n" "Display Inter-Area routes\n" "Display Type-1 External routes\n" "Display Type-2 External routes\n" "Detailed information\n" ) { OSPF6_CMD_CHECK_RUNNING(); ospf6_route_table_show(vty, 4, argc, argv, ospf6->route_table); return CMD_SUCCESS; } static void ospf6_stub_router_config_write(struct vty *vty) { if (CHECK_FLAG(ospf6->flag, OSPF6_STUB_ROUTER)) { vty_out(vty, " stub-router administrative\n"); } return; } static int ospf6_distance_config_write(struct vty *vty) { struct route_node *rn; struct ospf6_distance *odistance; if (ospf6->distance_all) vty_out(vty, " distance %u\n", ospf6->distance_all); if (ospf6->distance_intra || ospf6->distance_inter || ospf6->distance_external) { vty_out(vty, " distance ospf6"); if (ospf6->distance_intra) vty_out(vty, " intra-area %u", ospf6->distance_intra); if (ospf6->distance_inter) vty_out(vty, " inter-area %u", ospf6->distance_inter); if (ospf6->distance_external) vty_out(vty, " external %u", ospf6->distance_external); vty_out(vty, "\n"); } for (rn = route_top(ospf6->distance_table); rn; rn = route_next(rn)) if ((odistance = rn->info) != NULL) { char buf[PREFIX_STRLEN]; vty_out(vty, " distance %u %s %s\n", odistance->distance, prefix2str(&rn->p, buf, sizeof(buf)), odistance->access_list ? odistance->access_list : ""); } return 0; } /* OSPF configuration write function. */ static int config_write_ospf6(struct vty *vty) { char router_id[16]; struct listnode *j, *k; struct ospf6_area *oa; struct ospf6_interface *oi; /* OSPFv3 configuration. */ if (ospf6 == NULL) return CMD_SUCCESS; inet_ntop(AF_INET, &ospf6->router_id_static, router_id, sizeof(router_id)); vty_out(vty, "router ospf6\n"); if (ospf6->router_id_static != 0) vty_out(vty, " ospf6 router-id %s\n", router_id); /* log-adjacency-changes flag print. */ if (CHECK_FLAG(ospf6->config_flags, OSPF6_LOG_ADJACENCY_CHANGES)) { if (CHECK_FLAG(ospf6->config_flags, OSPF6_LOG_ADJACENCY_DETAIL)) vty_out(vty, " log-adjacency-changes detail\n"); else if (!DFLT_OSPF6_LOG_ADJACENCY_CHANGES) vty_out(vty, " log-adjacency-changes\n"); } else if (DFLT_OSPF6_LOG_ADJACENCY_CHANGES) { vty_out(vty, " no log-adjacency-changes\n"); } if (ospf6->ref_bandwidth != OSPF6_REFERENCE_BANDWIDTH) vty_out(vty, " auto-cost reference-bandwidth %d\n", ospf6->ref_bandwidth); /* LSA timers print. */ if (ospf6->lsa_minarrival != OSPF_MIN_LS_ARRIVAL) vty_out(vty, " timers lsa min-arrival %d\n", ospf6->lsa_minarrival); ospf6_stub_router_config_write(vty); ospf6_redistribute_config_write(vty); ospf6_area_config_write(vty); ospf6_spf_config_write(vty); ospf6_distance_config_write(vty); for (ALL_LIST_ELEMENTS_RO(ospf6->area_list, j, oa)) { for (ALL_LIST_ELEMENTS_RO(oa->if_list, k, oi)) vty_out(vty, " interface %s area %s\n", oi->interface->name, oa->name); } vty_out(vty, "!\n"); return 0; } /* OSPF6 node structure. */ static struct cmd_node ospf6_node = { OSPF6_NODE, "%s(config-ospf6)# ", 1 /* VTYSH */ }; /* Install ospf related commands. */ void ospf6_top_init(void) { /* Install ospf6 top node. */ install_node(&ospf6_node, config_write_ospf6); install_element(VIEW_NODE, &show_ipv6_ospf6_cmd); install_element(CONFIG_NODE, &router_ospf6_cmd); install_element(CONFIG_NODE, &no_router_ospf6_cmd); install_element(VIEW_NODE, &show_ipv6_ospf6_route_cmd); install_element(VIEW_NODE, &show_ipv6_ospf6_route_match_cmd); install_element(VIEW_NODE, &show_ipv6_ospf6_route_match_detail_cmd); install_element(VIEW_NODE, &show_ipv6_ospf6_route_type_detail_cmd); install_default(OSPF6_NODE); install_element(OSPF6_NODE, &ospf6_router_id_cmd); install_element(OSPF6_NODE, &no_ospf6_router_id_cmd); install_element(OSPF6_NODE, &ospf6_log_adjacency_changes_cmd); install_element(OSPF6_NODE, &ospf6_log_adjacency_changes_detail_cmd); install_element(OSPF6_NODE, &no_ospf6_log_adjacency_changes_cmd); install_element(OSPF6_NODE, &no_ospf6_log_adjacency_changes_detail_cmd); /* LSA timers commands */ install_element(OSPF6_NODE, &ospf6_timers_lsa_cmd); install_element(OSPF6_NODE, &no_ospf6_timers_lsa_cmd); install_element(OSPF6_NODE, &ospf6_interface_area_cmd); install_element(OSPF6_NODE, &no_ospf6_interface_area_cmd); install_element(OSPF6_NODE, &ospf6_stub_router_admin_cmd); install_element(OSPF6_NODE, &no_ospf6_stub_router_admin_cmd); /* For a later time */ #if 0 install_element (OSPF6_NODE, &ospf6_stub_router_startup_cmd); install_element (OSPF6_NODE, &no_ospf6_stub_router_startup_cmd); install_element (OSPF6_NODE, &ospf6_stub_router_shutdown_cmd); install_element (OSPF6_NODE, &no_ospf6_stub_router_shutdown_cmd); #endif install_element(OSPF6_NODE, &ospf6_distance_cmd); install_element(OSPF6_NODE, &no_ospf6_distance_cmd); install_element(OSPF6_NODE, &ospf6_distance_ospf6_cmd); install_element(OSPF6_NODE, &no_ospf6_distance_ospf6_cmd); #if 0 install_element (OSPF6_NODE, &ospf6_distance_source_cmd); install_element (OSPF6_NODE, &no_ospf6_distance_source_cmd); #endif } frr-7.2.1/ospf6d/ospf6_top.h0000644000000000000000000000652013610377563012510 00000000000000/* * Copyright (C) 2003 Yasuhiro Ohara * * This file is part of GNU Zebra. * * GNU Zebra is free software; you can redistribute it and/or modify it * under the terms of the GNU General Public License as published by the * Free Software Foundation; either version 2, or (at your option) any * later version. * * GNU Zebra 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; see the file COPYING; if not, write to the Free Software * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA */ #ifndef OSPF6_TOP_H #define OSPF6_TOP_H #include "qobj.h" #include "routemap.h" struct ospf6_master { uint32_t zebra_router_id; }; /* OSPFv3 top level data structure */ struct ospf6 { /* my router id */ uint32_t router_id; /* static router id */ uint32_t router_id_static; struct in_addr router_id_zebra; /* start time */ struct timeval starttime; /* list of areas */ struct list *area_list; struct ospf6_area *backbone; /* AS scope link state database */ struct ospf6_lsdb *lsdb; struct ospf6_lsdb *lsdb_self; struct ospf6_route_table *route_table; struct ospf6_route_table *brouter_table; struct ospf6_route_table *external_table; struct route_table *external_id_table; uint32_t external_id; /* redistribute route-map */ struct { char *name; struct route_map *map; } rmap[ZEBRA_ROUTE_MAX]; uint8_t flag; /* Configured flags */ uint8_t config_flags; #define OSPF6_LOG_ADJACENCY_CHANGES (1 << 0) #define OSPF6_LOG_ADJACENCY_DETAIL (1 << 1) /* LSA timer parameters */ unsigned int lsa_minarrival; /* LSA minimum arrival in milliseconds. */ /* SPF parameters */ unsigned int spf_delay; /* SPF delay time. */ unsigned int spf_holdtime; /* SPF hold time. */ unsigned int spf_max_holdtime; /* SPF maximum-holdtime */ unsigned int spf_hold_multiplier; /* Adaptive multiplier for hold time */ unsigned int spf_reason; /* reason bits while scheduling SPF */ struct timeval ts_spf; /* SPF calculation time stamp. */ struct timeval ts_spf_duration; /* Execution time of last SPF */ unsigned int last_spf_reason; /* Last SPF reason */ /* Threads */ struct thread *t_spf_calc; /* SPF calculation timer. */ struct thread *t_ase_calc; /* ASE calculation timer. */ struct thread *maxage_remover; struct thread *t_distribute_update; /* Distirbute update timer. */ uint32_t ref_bandwidth; /* Distance parameters */ uint8_t distance_all; uint8_t distance_intra; uint8_t distance_inter; uint8_t distance_external; struct route_table *distance_table; /* Used during ospf instance going down send LSDB * update to neighbors immediatly */ uint8_t inst_shutdown; QOBJ_FIELDS }; DECLARE_QOBJ_TYPE(ospf6) #define OSPF6_DISABLED 0x01 #define OSPF6_STUB_ROUTER 0x02 /* global pointer for OSPF top data structure */ extern struct ospf6 *ospf6; extern struct ospf6_master *om6; /* prototypes */ extern void ospf6_master_init(void); extern void ospf6_top_init(void); extern void ospf6_delete(struct ospf6 *o); extern void ospf6_router_id_update(void); extern void ospf6_maxage_remove(struct ospf6 *o); #endif /* OSPF6_TOP_H */ frr-7.2.1/ospf6d/ospf6_zebra.c0000644000000000000000000004243713610377563013013 00000000000000/* * Copyright (C) 2003 Yasuhiro Ohara * * This file is part of GNU Zebra. * * GNU Zebra is free software; you can redistribute it and/or modify it * under the terms of the GNU General Public License as published by the * Free Software Foundation; either version 2, or (at your option) any * later version. * * GNU Zebra 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; see the file COPYING; if not, write to the Free Software * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA */ #include #include "log.h" #include "vty.h" #include "command.h" #include "prefix.h" #include "stream.h" #include "zclient.h" #include "memory.h" #include "lib/bfd.h" #include "lib_errors.h" #include "ospf6_proto.h" #include "ospf6_top.h" #include "ospf6_interface.h" #include "ospf6_route.h" #include "ospf6_lsa.h" #include "ospf6_lsdb.h" #include "ospf6_asbr.h" #include "ospf6_zebra.h" #include "ospf6d.h" DEFINE_MTYPE_STATIC(OSPF6D, OSPF6_DISTANCE, "OSPF6 distance") unsigned char conf_debug_ospf6_zebra = 0; /* information about zebra. */ struct zclient *zclient = NULL; /* Router-id update message from zebra. */ static int ospf6_router_id_update_zebra(ZAPI_CALLBACK_ARGS) { struct prefix router_id; struct ospf6 *o = ospf6; zebra_router_id_update_read(zclient->ibuf, &router_id); om6->zebra_router_id = router_id.u.prefix4.s_addr; if (o == NULL) return 0; o->router_id_zebra = router_id.u.prefix4; if (IS_OSPF6_DEBUG_ZEBRA(RECV)) { char buf[INET_ADDRSTRLEN]; zlog_debug("%s: zebra router-id %s update", __PRETTY_FUNCTION__, inet_ntop(AF_INET, &router_id.u.prefix4, buf, INET_ADDRSTRLEN)); } ospf6_router_id_update(); return 0; } /* redistribute function */ void ospf6_zebra_redistribute(int type) { if (vrf_bitmap_check(zclient->redist[AFI_IP6][type], VRF_DEFAULT)) return; vrf_bitmap_set(zclient->redist[AFI_IP6][type], VRF_DEFAULT); if (zclient->sock > 0) zebra_redistribute_send(ZEBRA_REDISTRIBUTE_ADD, zclient, AFI_IP6, type, 0, VRF_DEFAULT); } void ospf6_zebra_no_redistribute(int type) { if (!vrf_bitmap_check(zclient->redist[AFI_IP6][type], VRF_DEFAULT)) return; vrf_bitmap_unset(zclient->redist[AFI_IP6][type], VRF_DEFAULT); if (zclient->sock > 0) zebra_redistribute_send(ZEBRA_REDISTRIBUTE_DELETE, zclient, AFI_IP6, type, 0, VRF_DEFAULT); } /* Inteface addition message from zebra. */ static int ospf6_zebra_if_add(ZAPI_CALLBACK_ARGS) { struct interface *ifp; ifp = zebra_interface_add_read(zclient->ibuf, vrf_id); if (IS_OSPF6_DEBUG_ZEBRA(RECV)) zlog_debug("Zebra Interface add: %s index %d mtu %d", ifp->name, ifp->ifindex, ifp->mtu6); ospf6_interface_if_add(ifp); return 0; } static int ospf6_zebra_if_del(ZAPI_CALLBACK_ARGS) { struct interface *ifp; if (!(ifp = zebra_interface_state_read(zclient->ibuf, vrf_id))) return 0; if (if_is_up(ifp)) zlog_warn("Zebra: got delete of %s, but interface is still up", ifp->name); if (IS_OSPF6_DEBUG_ZEBRA(RECV)) zlog_debug("Zebra Interface delete: %s index %d mtu %d", ifp->name, ifp->ifindex, ifp->mtu6); if_set_index(ifp, IFINDEX_INTERNAL); return 0; } static int ospf6_zebra_if_state_update(ZAPI_CALLBACK_ARGS) { struct interface *ifp; ifp = zebra_interface_state_read(zclient->ibuf, vrf_id); if (ifp == NULL) return 0; if (IS_OSPF6_DEBUG_ZEBRA(RECV)) zlog_debug( "Zebra Interface state change: " "%s index %d flags %llx metric %d mtu %d bandwidth %d", ifp->name, ifp->ifindex, (unsigned long long)ifp->flags, ifp->metric, ifp->mtu6, ifp->bandwidth); ospf6_interface_state_update(ifp); return 0; } static int ospf6_zebra_if_address_update_add(ZAPI_CALLBACK_ARGS) { struct connected *c; char buf[128]; c = zebra_interface_address_read(ZEBRA_INTERFACE_ADDRESS_ADD, zclient->ibuf, vrf_id); if (c == NULL) return 0; if (IS_OSPF6_DEBUG_ZEBRA(RECV)) zlog_debug("Zebra Interface address add: %s %5s %s/%d", c->ifp->name, prefix_family_str(c->address), inet_ntop(c->address->family, &c->address->u.prefix, buf, sizeof(buf)), c->address->prefixlen); if (c->address->family == AF_INET6) { ospf6_interface_state_update(c->ifp); ospf6_interface_connected_route_update(c->ifp); } return 0; } static int ospf6_zebra_if_address_update_delete(ZAPI_CALLBACK_ARGS) { struct connected *c; char buf[128]; c = zebra_interface_address_read(ZEBRA_INTERFACE_ADDRESS_DELETE, zclient->ibuf, vrf_id); if (c == NULL) return 0; if (IS_OSPF6_DEBUG_ZEBRA(RECV)) zlog_debug("Zebra Interface address delete: %s %5s %s/%d", c->ifp->name, prefix_family_str(c->address), inet_ntop(c->address->family, &c->address->u.prefix, buf, sizeof(buf)), c->address->prefixlen); if (c->address->family == AF_INET6) { ospf6_interface_connected_route_update(c->ifp); ospf6_interface_state_update(c->ifp); } connected_free(c); return 0; } static int ospf6_zebra_read_route(ZAPI_CALLBACK_ARGS) { struct zapi_route api; unsigned long ifindex; struct in6_addr *nexthop; if (ospf6 == NULL) return 0; if (zapi_route_decode(zclient->ibuf, &api) < 0) return -1; /* we completely ignore srcdest routes for now. */ if (CHECK_FLAG(api.message, ZAPI_MESSAGE_SRCPFX)) return 0; if (IN6_IS_ADDR_LINKLOCAL(&api.prefix.u.prefix6)) return 0; ifindex = api.nexthops[0].ifindex; nexthop = &api.nexthops[0].gate.ipv6; if (IS_OSPF6_DEBUG_ZEBRA(RECV)) { char prefixstr[PREFIX2STR_BUFFER], nexthopstr[128]; prefix2str((struct prefix *)&api.prefix, prefixstr, sizeof(prefixstr)); inet_ntop(AF_INET6, nexthop, nexthopstr, sizeof(nexthopstr)); zlog_debug( "Zebra Receive route %s: %s %s nexthop %s ifindex %ld tag %" ROUTE_TAG_PRI, (cmd == ZEBRA_REDISTRIBUTE_ROUTE_ADD ? "add" : "delete"), zebra_route_string(api.type), prefixstr, nexthopstr, ifindex, api.tag); } if (cmd == ZEBRA_REDISTRIBUTE_ROUTE_ADD) ospf6_asbr_redistribute_add(api.type, ifindex, &api.prefix, api.nexthop_num, nexthop, api.tag); else ospf6_asbr_redistribute_remove(api.type, ifindex, &api.prefix); return 0; } DEFUN (show_zebra, show_ospf6_zebra_cmd, "show ipv6 ospf6 zebra", SHOW_STR IPV6_STR OSPF6_STR ZEBRA_STR) { int i; if (zclient == NULL) { vty_out(vty, "Not connected to zebra\n"); return CMD_SUCCESS; } vty_out(vty, "Zebra Information\n"); vty_out(vty, " fail: %d\n", zclient->fail); vty_out(vty, " redistribute default: %d\n", vrf_bitmap_check(zclient->default_information[AFI_IP6], VRF_DEFAULT)); vty_out(vty, " redistribute:"); for (i = 0; i < ZEBRA_ROUTE_MAX; i++) { if (vrf_bitmap_check(zclient->redist[AFI_IP6][i], VRF_DEFAULT)) vty_out(vty, " %s", zebra_route_string(i)); } vty_out(vty, "\n"); return CMD_SUCCESS; } #define ADD 0 #define REM 1 static void ospf6_zebra_route_update(int type, struct ospf6_route *request) { struct zapi_route api; char buf[PREFIX2STR_BUFFER]; int nhcount; int ret = 0; struct prefix *dest; if (IS_OSPF6_DEBUG_ZEBRA(SEND)) { prefix2str(&request->prefix, buf, sizeof(buf)); zlog_debug("Send %s route: %s", (type == REM ? "remove" : "add"), buf); } if (zclient->sock < 0) { if (IS_OSPF6_DEBUG_ZEBRA(SEND)) zlog_debug(" Not connected to Zebra"); return; } if (request->path.origin.adv_router == ospf6->router_id && (request->path.type == OSPF6_PATH_TYPE_EXTERNAL1 || request->path.type == OSPF6_PATH_TYPE_EXTERNAL2)) { if (IS_OSPF6_DEBUG_ZEBRA(SEND)) zlog_debug(" Ignore self-originated external route"); return; } /* If removing is the best path and if there's another path, treat this request as add the secondary path */ if (type == REM && ospf6_route_is_best(request) && request->next && ospf6_route_is_same(request, request->next)) { if (IS_OSPF6_DEBUG_ZEBRA(SEND)) zlog_debug( " Best-path removal resulted Sencondary addition"); type = ADD; request = request->next; } /* Only the best path will be sent to zebra. */ if (!ospf6_route_is_best(request)) { /* this is not preferred best route, ignore */ if (IS_OSPF6_DEBUG_ZEBRA(SEND)) zlog_debug(" Ignore non-best route"); return; } nhcount = ospf6_route_num_nexthops(request); if (nhcount == 0) { if (IS_OSPF6_DEBUG_ZEBRA(SEND)) zlog_debug(" No nexthop, ignore"); return; } dest = &request->prefix; memset(&api, 0, sizeof(api)); api.vrf_id = VRF_DEFAULT; api.type = ZEBRA_ROUTE_OSPF6; api.safi = SAFI_UNICAST; api.prefix = *dest; SET_FLAG(api.message, ZAPI_MESSAGE_NEXTHOP); api.nexthop_num = MIN(nhcount, MULTIPATH_NUM); ospf6_route_zebra_copy_nexthops(request, api.nexthops, api.nexthop_num); SET_FLAG(api.message, ZAPI_MESSAGE_METRIC); api.metric = (request->path.metric_type == 2 ? request->path.u.cost_e2 : request->path.cost); if (request->path.tag) { SET_FLAG(api.message, ZAPI_MESSAGE_TAG); api.tag = request->path.tag; } SET_FLAG(api.message, ZAPI_MESSAGE_DISTANCE); api.distance = ospf6_distance_apply((struct prefix_ipv6 *)dest, request); if (type == REM) ret = zclient_route_send(ZEBRA_ROUTE_DELETE, zclient, &api); else ret = zclient_route_send(ZEBRA_ROUTE_ADD, zclient, &api); if (ret < 0) flog_err(EC_LIB_ZAPI_SOCKET, "zclient_route_send() %s failed: %s", (type == REM ? "delete" : "add"), safe_strerror(errno)); return; } void ospf6_zebra_route_update_add(struct ospf6_route *request) { ospf6_zebra_route_update(ADD, request); } void ospf6_zebra_route_update_remove(struct ospf6_route *request) { ospf6_zebra_route_update(REM, request); } void ospf6_zebra_add_discard(struct ospf6_route *request) { struct zapi_route api; char buf[INET6_ADDRSTRLEN]; struct prefix *dest = &request->prefix; if (!CHECK_FLAG(request->flag, OSPF6_ROUTE_BLACKHOLE_ADDED)) { memset(&api, 0, sizeof(api)); api.vrf_id = VRF_DEFAULT; api.type = ZEBRA_ROUTE_OSPF6; api.safi = SAFI_UNICAST; api.prefix = *dest; zapi_route_set_blackhole(&api, BLACKHOLE_NULL); zclient_route_send(ZEBRA_ROUTE_ADD, zclient, &api); if (IS_OSPF6_DEBUG_ZEBRA(SEND)) zlog_debug("Zebra: Route add discard %s/%d", inet_ntop(AF_INET6, &dest->u.prefix6, buf, INET6_ADDRSTRLEN), dest->prefixlen); SET_FLAG(request->flag, OSPF6_ROUTE_BLACKHOLE_ADDED); } else { if (IS_OSPF6_DEBUG_ZEBRA(SEND)) zlog_debug( "Zebra: Blackhole route present already %s/%d", inet_ntop(AF_INET6, &dest->u.prefix6, buf, INET6_ADDRSTRLEN), dest->prefixlen); } } void ospf6_zebra_delete_discard(struct ospf6_route *request) { struct zapi_route api; char buf[INET6_ADDRSTRLEN]; struct prefix *dest = &request->prefix; if (CHECK_FLAG(request->flag, OSPF6_ROUTE_BLACKHOLE_ADDED)) { memset(&api, 0, sizeof(api)); api.vrf_id = VRF_DEFAULT; api.type = ZEBRA_ROUTE_OSPF6; api.safi = SAFI_UNICAST; api.prefix = *dest; zapi_route_set_blackhole(&api, BLACKHOLE_NULL); zclient_route_send(ZEBRA_ROUTE_DELETE, zclient, &api); if (IS_OSPF6_DEBUG_ZEBRA(SEND)) zlog_debug("Zebra: Route delete discard %s/%d", inet_ntop(AF_INET6, &dest->u.prefix6, buf, INET6_ADDRSTRLEN), dest->prefixlen); UNSET_FLAG(request->flag, OSPF6_ROUTE_BLACKHOLE_ADDED); } else { if (IS_OSPF6_DEBUG_ZEBRA(SEND)) zlog_debug( "Zebra: Blackhole route already deleted %s/%d", inet_ntop(AF_INET6, &dest->u.prefix6, buf, INET6_ADDRSTRLEN), dest->prefixlen); } } static struct ospf6_distance *ospf6_distance_new(void) { return XCALLOC(MTYPE_OSPF6_DISTANCE, sizeof(struct ospf6_distance)); } static void ospf6_distance_free(struct ospf6_distance *odistance) { XFREE(MTYPE_OSPF6_DISTANCE, odistance); } int ospf6_distance_set(struct vty *vty, struct ospf6 *o, const char *distance_str, const char *ip_str, const char *access_list_str) { int ret; struct prefix_ipv6 p; uint8_t distance; struct route_node *rn; struct ospf6_distance *odistance; ret = str2prefix_ipv6(ip_str, &p); if (ret == 0) { vty_out(vty, "Malformed prefix\n"); return CMD_WARNING_CONFIG_FAILED; } distance = atoi(distance_str); /* Get OSPF6 distance node. */ rn = route_node_get(o->distance_table, (struct prefix *)&p); if (rn->info) { odistance = rn->info; route_unlock_node(rn); } else { odistance = ospf6_distance_new(); rn->info = odistance; } /* Set distance value. */ odistance->distance = distance; /* Reset access-list configuration. */ if (odistance->access_list) { free(odistance->access_list); odistance->access_list = NULL; } if (access_list_str) odistance->access_list = strdup(access_list_str); return CMD_SUCCESS; } int ospf6_distance_unset(struct vty *vty, struct ospf6 *o, const char *distance_str, const char *ip_str, const char *access_list_str) { int ret; struct prefix_ipv6 p; struct route_node *rn; struct ospf6_distance *odistance; ret = str2prefix_ipv6(ip_str, &p); if (ret == 0) { vty_out(vty, "Malformed prefix\n"); return CMD_WARNING_CONFIG_FAILED; } rn = route_node_lookup(o->distance_table, (struct prefix *)&p); if (!rn) { vty_out(vty, "Cant't find specified prefix\n"); return CMD_WARNING_CONFIG_FAILED; } odistance = rn->info; if (odistance->access_list) free(odistance->access_list); ospf6_distance_free(odistance); rn->info = NULL; route_unlock_node(rn); route_unlock_node(rn); return CMD_SUCCESS; } void ospf6_distance_reset(struct ospf6 *o) { struct route_node *rn; struct ospf6_distance *odistance; for (rn = route_top(o->distance_table); rn; rn = route_next(rn)) if ((odistance = rn->info) != NULL) { if (odistance->access_list) free(odistance->access_list); ospf6_distance_free(odistance); rn->info = NULL; route_unlock_node(rn); } } uint8_t ospf6_distance_apply(struct prefix_ipv6 *p, struct ospf6_route * or) { struct ospf6 *o; o = ospf6; if (o == NULL) return 0; if (o->distance_intra) if (or->path.type == OSPF6_PATH_TYPE_INTRA) return o->distance_intra; if (o->distance_inter) if (or->path.type == OSPF6_PATH_TYPE_INTER) return o->distance_inter; if (o->distance_external) if (or->path.type == OSPF6_PATH_TYPE_EXTERNAL1 || or->path.type == OSPF6_PATH_TYPE_EXTERNAL2) return o->distance_external; if (o->distance_all) return o->distance_all; return 0; } static void ospf6_zebra_connected(struct zclient *zclient) { /* Send the client registration */ bfd_client_sendmsg(zclient, ZEBRA_BFD_CLIENT_REGISTER, VRF_DEFAULT); zclient_send_reg_requests(zclient, VRF_DEFAULT); } void ospf6_zebra_init(struct thread_master *master) { /* Allocate zebra structure. */ zclient = zclient_new(master, &zclient_options_default); zclient_init(zclient, ZEBRA_ROUTE_OSPF6, 0, &ospf6d_privs); zclient->zebra_connected = ospf6_zebra_connected; zclient->router_id_update = ospf6_router_id_update_zebra; zclient->interface_add = ospf6_zebra_if_add; zclient->interface_delete = ospf6_zebra_if_del; zclient->interface_up = ospf6_zebra_if_state_update; zclient->interface_down = ospf6_zebra_if_state_update; zclient->interface_address_add = ospf6_zebra_if_address_update_add; zclient->interface_address_delete = ospf6_zebra_if_address_update_delete; zclient->redistribute_route_add = ospf6_zebra_read_route; zclient->redistribute_route_del = ospf6_zebra_read_route; /* Install command element for zebra node. */ install_element(VIEW_NODE, &show_ospf6_zebra_cmd); } /* Debug */ DEFUN (debug_ospf6_zebra_sendrecv, debug_ospf6_zebra_sendrecv_cmd, "debug ospf6 zebra []", DEBUG_STR OSPF6_STR "Debug connection between zebra\n" "Debug Sending zebra\n" "Debug Receiving zebra\n" ) { int idx_send_recv = 3; unsigned char level = 0; if (argc == 4) { if (strmatch(argv[idx_send_recv]->text, "send")) level = OSPF6_DEBUG_ZEBRA_SEND; else if (strmatch(argv[idx_send_recv]->text, "recv")) level = OSPF6_DEBUG_ZEBRA_RECV; } else level = OSPF6_DEBUG_ZEBRA_SEND | OSPF6_DEBUG_ZEBRA_RECV; OSPF6_DEBUG_ZEBRA_ON(level); return CMD_SUCCESS; } DEFUN (no_debug_ospf6_zebra_sendrecv, no_debug_ospf6_zebra_sendrecv_cmd, "no debug ospf6 zebra []", NO_STR DEBUG_STR OSPF6_STR "Debug connection between zebra\n" "Debug Sending zebra\n" "Debug Receiving zebra\n" ) { int idx_send_recv = 4; unsigned char level = 0; if (argc == 5) { if (strmatch(argv[idx_send_recv]->text, "send")) level = OSPF6_DEBUG_ZEBRA_SEND; else if (strmatch(argv[idx_send_recv]->text, "recv")) level = OSPF6_DEBUG_ZEBRA_RECV; } else level = OSPF6_DEBUG_ZEBRA_SEND | OSPF6_DEBUG_ZEBRA_RECV; OSPF6_DEBUG_ZEBRA_OFF(level); return CMD_SUCCESS; } int config_write_ospf6_debug_zebra(struct vty *vty) { if (IS_OSPF6_DEBUG_ZEBRA(SEND) && IS_OSPF6_DEBUG_ZEBRA(RECV)) vty_out(vty, "debug ospf6 zebra\n"); else { if (IS_OSPF6_DEBUG_ZEBRA(SEND)) vty_out(vty, "debug ospf6 zebra send\n"); if (IS_OSPF6_DEBUG_ZEBRA(RECV)) vty_out(vty, "debug ospf6 zebra recv\n"); } return 0; } void install_element_ospf6_debug_zebra(void) { install_element(ENABLE_NODE, &debug_ospf6_zebra_sendrecv_cmd); install_element(ENABLE_NODE, &no_debug_ospf6_zebra_sendrecv_cmd); install_element(CONFIG_NODE, &debug_ospf6_zebra_sendrecv_cmd); install_element(CONFIG_NODE, &no_debug_ospf6_zebra_sendrecv_cmd); } frr-7.2.1/ospf6d/ospf6_zebra.h0000644000000000000000000000472613610377563013017 00000000000000/* * Copyright (C) 2003 Yasuhiro Ohara * * This file is part of GNU Zebra. * * GNU Zebra is free software; you can redistribute it and/or modify it * under the terms of the GNU General Public License as published by the * Free Software Foundation; either version 2, or (at your option) any * later version. * * GNU Zebra 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; see the file COPYING; if not, write to the Free Software * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA */ #ifndef OSPF6_ZEBRA_H #define OSPF6_ZEBRA_H #include "zclient.h" /* Debug option */ extern unsigned char conf_debug_ospf6_zebra; #define OSPF6_DEBUG_ZEBRA_SEND 0x01 #define OSPF6_DEBUG_ZEBRA_RECV 0x02 #define OSPF6_DEBUG_ZEBRA_ON(level) (conf_debug_ospf6_zebra |= level) #define OSPF6_DEBUG_ZEBRA_OFF(level) (conf_debug_ospf6_zebra &= ~(level)) #define IS_OSPF6_DEBUG_ZEBRA(e) (conf_debug_ospf6_zebra & OSPF6_DEBUG_ZEBRA_##e) /* OSPF6 distance */ struct ospf6_distance { /* Distance value for the IP source prefix */ uint8_t distance; /* Name of the access-list to be matched */ char *access_list; }; extern struct zclient *zclient; extern void ospf6_zebra_route_update_add(struct ospf6_route *request); extern void ospf6_zebra_route_update_remove(struct ospf6_route *request); extern void ospf6_zebra_redistribute(int); extern void ospf6_zebra_no_redistribute(int); #define ospf6_zebra_is_redistribute(type) \ vrf_bitmap_check(zclient->redist[AFI_IP6][type], VRF_DEFAULT) extern void ospf6_zebra_init(struct thread_master *); extern void ospf6_zebra_add_discard(struct ospf6_route *request); extern void ospf6_zebra_delete_discard(struct ospf6_route *request); struct ospf6; extern void ospf6_distance_reset(struct ospf6 *); extern uint8_t ospf6_distance_apply(struct prefix_ipv6 *, struct ospf6_route *); extern int ospf6_distance_set(struct vty *, struct ospf6 *, const char *, const char *, const char *); extern int ospf6_distance_unset(struct vty *, struct ospf6 *, const char *, const char *, const char *); extern int config_write_ospf6_debug_zebra(struct vty *vty); extern void install_element_ospf6_debug_zebra(void); #endif /*OSPF6_ZEBRA_H*/ frr-7.2.1/ospf6d/ospf6d.c0000644000000000000000000010554413610377563011773 00000000000000/* * Copyright (C) 2003 Yasuhiro Ohara * * This file is part of GNU Zebra. * * GNU Zebra is free software; you can redistribute it and/or modify it * under the terms of the GNU General Public License as published by the * Free Software Foundation; either version 2, or (at your option) any * later version. * * GNU Zebra 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; see the file COPYING; if not, write to the Free Software * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA */ #include #include "thread.h" #include "linklist.h" #include "vty.h" #include "command.h" #include "plist.h" #include "ospf6_proto.h" #include "ospf6_network.h" #include "ospf6_lsa.h" #include "ospf6_lsdb.h" #include "ospf6_message.h" #include "ospf6_route.h" #include "ospf6_zebra.h" #include "ospf6_spf.h" #include "ospf6_top.h" #include "ospf6_area.h" #include "ospf6_interface.h" #include "ospf6_neighbor.h" #include "ospf6_intra.h" #include "ospf6_asbr.h" #include "ospf6_abr.h" #include "ospf6_flood.h" #include "ospf6d.h" #include "ospf6_bfd.h" struct route_node *route_prev(struct route_node *node) { struct route_node *end; struct route_node *prev = NULL; end = node; node = node->parent; if (node) route_lock_node(node); while (node) { prev = node; node = route_next(node); if (node == end) { route_unlock_node(node); node = NULL; } } route_unlock_node(end); if (prev) route_lock_node(prev); return prev; } static struct cmd_node debug_node = { DEBUG_NODE, "", 1 /* VTYSH */ }; static int config_write_ospf6_debug(struct vty *vty) { config_write_ospf6_debug_message(vty); config_write_ospf6_debug_lsa(vty); config_write_ospf6_debug_zebra(vty); config_write_ospf6_debug_interface(vty); config_write_ospf6_debug_neighbor(vty); config_write_ospf6_debug_spf(vty); config_write_ospf6_debug_route(vty); config_write_ospf6_debug_brouter(vty); config_write_ospf6_debug_asbr(vty); config_write_ospf6_debug_abr(vty); config_write_ospf6_debug_flood(vty); return 0; } DEFUN_NOSH (show_debugging_ospf6, show_debugging_ospf6_cmd, "show debugging [ospf6]", SHOW_STR DEBUG_STR OSPF6_STR) { vty_out(vty, "OSPF6 debugging status:\n"); config_write_ospf6_debug(vty); return CMD_SUCCESS; } #define AREA_LSDB_TITLE_FORMAT \ "\n Area Scoped Link State Database (Area %s)\n\n" #define IF_LSDB_TITLE_FORMAT \ "\n I/F Scoped Link State Database (I/F %s in Area %s)\n\n" #define AS_LSDB_TITLE_FORMAT "\n AS Scoped Link State Database\n\n" static int parse_show_level(int idx_level, int argc, struct cmd_token **argv) { int level = OSPF6_LSDB_SHOW_LEVEL_NORMAL; if (argc > idx_level) { if (strmatch(argv[idx_level]->text, "detail")) level = OSPF6_LSDB_SHOW_LEVEL_DETAIL; else if (strmatch(argv[idx_level]->text, "dump")) level = OSPF6_LSDB_SHOW_LEVEL_DUMP; else if (strmatch(argv[idx_level]->text, "internal")) level = OSPF6_LSDB_SHOW_LEVEL_INTERNAL; } return level; } static uint16_t parse_type_spec(int idx_lsa, int argc, struct cmd_token **argv) { uint16_t type = 0; if (argc > idx_lsa) { if (strmatch(argv[idx_lsa]->text, "router")) type = htons(OSPF6_LSTYPE_ROUTER); else if (strmatch(argv[idx_lsa]->text, "network")) type = htons(OSPF6_LSTYPE_NETWORK); else if (strmatch(argv[idx_lsa]->text, "as-external")) type = htons(OSPF6_LSTYPE_AS_EXTERNAL); else if (strmatch(argv[idx_lsa]->text, "intra-prefix")) type = htons(OSPF6_LSTYPE_INTRA_PREFIX); else if (strmatch(argv[idx_lsa]->text, "inter-router")) type = htons(OSPF6_LSTYPE_INTER_ROUTER); else if (strmatch(argv[idx_lsa]->text, "inter-prefix")) type = htons(OSPF6_LSTYPE_INTER_PREFIX); else if (strmatch(argv[idx_lsa]->text, "link")) type = htons(OSPF6_LSTYPE_LINK); } return type; } DEFUN (show_ipv6_ospf6_database, show_ipv6_ospf6_database_cmd, "show ipv6 ospf6 database []", SHOW_STR IPV6_STR OSPF6_STR "Display Link state database\n" "Display details of LSAs\n" "Dump LSAs\n" "Display LSA's internal information\n") { int idx_level = 4; int level; struct listnode *i, *j; struct ospf6 *o = ospf6; struct ospf6_area *oa; struct ospf6_interface *oi; OSPF6_CMD_CHECK_RUNNING(); level = parse_show_level(idx_level, argc, argv); for (ALL_LIST_ELEMENTS_RO(o->area_list, i, oa)) { vty_out(vty, AREA_LSDB_TITLE_FORMAT, oa->name); ospf6_lsdb_show(vty, level, NULL, NULL, NULL, oa->lsdb); } for (ALL_LIST_ELEMENTS_RO(o->area_list, i, oa)) { for (ALL_LIST_ELEMENTS_RO(oa->if_list, j, oi)) { vty_out(vty, IF_LSDB_TITLE_FORMAT, oi->interface->name, oa->name); ospf6_lsdb_show(vty, level, NULL, NULL, NULL, oi->lsdb); } } vty_out(vty, AS_LSDB_TITLE_FORMAT); ospf6_lsdb_show(vty, level, NULL, NULL, NULL, o->lsdb); vty_out(vty, "\n"); return CMD_SUCCESS; } DEFUN (show_ipv6_ospf6_database_type, show_ipv6_ospf6_database_type_cmd, "show ipv6 ospf6 database []", SHOW_STR IPV6_STR OSPF6_STR "Display Link state database\n" "Display Router LSAs\n" "Display Network LSAs\n" "Display Inter-Area-Prefix LSAs\n" "Display Inter-Area-Router LSAs\n" "Display As-External LSAs\n" "Display Group-Membership LSAs\n" "Display Type-7 LSAs\n" "Display Link LSAs\n" "Display Intra-Area-Prefix LSAs\n" "Display details of LSAs\n" "Dump LSAs\n" "Display LSA's internal information\n" ) { int idx_lsa = 4; int idx_level = 5; int level; struct listnode *i, *j; struct ospf6 *o = ospf6; struct ospf6_area *oa; struct ospf6_interface *oi; uint16_t type = 0; OSPF6_CMD_CHECK_RUNNING(); type = parse_type_spec(idx_lsa, argc, argv); level = parse_show_level(idx_level, argc, argv); switch (OSPF6_LSA_SCOPE(type)) { case OSPF6_SCOPE_AREA: for (ALL_LIST_ELEMENTS_RO(o->area_list, i, oa)) { vty_out(vty, AREA_LSDB_TITLE_FORMAT, oa->name); ospf6_lsdb_show(vty, level, &type, NULL, NULL, oa->lsdb); } break; case OSPF6_SCOPE_LINKLOCAL: for (ALL_LIST_ELEMENTS_RO(o->area_list, i, oa)) { for (ALL_LIST_ELEMENTS_RO(oa->if_list, j, oi)) { vty_out(vty, IF_LSDB_TITLE_FORMAT, oi->interface->name, oa->name); ospf6_lsdb_show(vty, level, &type, NULL, NULL, oi->lsdb); } } break; case OSPF6_SCOPE_AS: vty_out(vty, AS_LSDB_TITLE_FORMAT); ospf6_lsdb_show(vty, level, &type, NULL, NULL, o->lsdb); break; default: assert(0); break; } vty_out(vty, "\n"); return CMD_SUCCESS; } DEFUN (show_ipv6_ospf6_database_id, show_ipv6_ospf6_database_id_cmd, "show ipv6 ospf6 database <*|linkstate-id> A.B.C.D []", SHOW_STR IPV6_STR OSPF6_STR "Display Link state database\n" "Any Link state Type\n" "Search by Link state ID\n" "Specify Link state ID as IPv4 address notation\n" "Display details of LSAs\n" "Dump LSAs\n" "Display LSA's internal information\n") { int idx_ipv4 = 5; int idx_level = 6; int level; struct listnode *i, *j; struct ospf6 *o = ospf6; struct ospf6_area *oa; struct ospf6_interface *oi; uint32_t id = 0; OSPF6_CMD_CHECK_RUNNING(); if (argv[idx_ipv4]->type == IPV4_TKN) inet_pton(AF_INET, argv[idx_ipv4]->arg, &id); level = parse_show_level(idx_level, argc, argv); for (ALL_LIST_ELEMENTS_RO(o->area_list, i, oa)) { vty_out(vty, AREA_LSDB_TITLE_FORMAT, oa->name); ospf6_lsdb_show(vty, level, NULL, &id, NULL, oa->lsdb); } for (ALL_LIST_ELEMENTS_RO(o->area_list, i, oa)) { for (ALL_LIST_ELEMENTS_RO(oa->if_list, j, oi)) { vty_out(vty, IF_LSDB_TITLE_FORMAT, oi->interface->name, oa->name); ospf6_lsdb_show(vty, level, NULL, &id, NULL, oi->lsdb); } } vty_out(vty, AS_LSDB_TITLE_FORMAT); ospf6_lsdb_show(vty, level, NULL, &id, NULL, o->lsdb); vty_out(vty, "\n"); return CMD_SUCCESS; } DEFUN (show_ipv6_ospf6_database_router, show_ipv6_ospf6_database_router_cmd, "show ipv6 ospf6 database <*|adv-router> * A.B.C.D ", SHOW_STR IPV6_STR OSPF6_STR "Display Link state database\n" "Any Link state Type\n" "Search by Advertising Router\n" "Any Link state ID\n" "Specify Advertising Router as IPv4 address notation\n" "Display details of LSAs\n" "Dump LSAs\n" "Display LSA's internal information\n") { int idx_ipv4 = 6; int idx_level = 7; int level; struct listnode *i, *j; struct ospf6 *o = ospf6; struct ospf6_area *oa; struct ospf6_interface *oi; uint32_t adv_router = 0; OSPF6_CMD_CHECK_RUNNING(); inet_pton(AF_INET, argv[idx_ipv4]->arg, &adv_router); level = parse_show_level(idx_level, argc, argv); for (ALL_LIST_ELEMENTS_RO(o->area_list, i, oa)) { vty_out(vty, AREA_LSDB_TITLE_FORMAT, oa->name); ospf6_lsdb_show(vty, level, NULL, NULL, &adv_router, oa->lsdb); } for (ALL_LIST_ELEMENTS_RO(o->area_list, i, oa)) { for (ALL_LIST_ELEMENTS_RO(oa->if_list, j, oi)) { vty_out(vty, IF_LSDB_TITLE_FORMAT, oi->interface->name, oa->name); ospf6_lsdb_show(vty, level, NULL, NULL, &adv_router, oi->lsdb); } } vty_out(vty, AS_LSDB_TITLE_FORMAT); ospf6_lsdb_show(vty, level, NULL, NULL, &adv_router, o->lsdb); vty_out(vty, "\n"); return CMD_SUCCESS; } DEFUN_HIDDEN (show_ipv6_ospf6_database_aggr_router, show_ipv6_ospf6_database_aggr_router_cmd, "show ipv6 ospf6 database aggr adv-router A.B.C.D", SHOW_STR IPV6_STR OSPF6_STR "Display Link state database\n" "Aggregated Router LSA\n" "Search by Advertising Router\n" "Specify Advertising Router as IPv4 address notation\n") { int level = OSPF6_LSDB_SHOW_LEVEL_DETAIL; uint16_t type = htons(OSPF6_LSTYPE_ROUTER); int idx_ipv4 = 6; struct listnode *i; struct ospf6 *o = ospf6; struct ospf6_area *oa; struct ospf6_lsdb *lsdb; uint32_t adv_router = 0; inet_pton(AF_INET, argv[idx_ipv4]->arg, &adv_router); for (ALL_LIST_ELEMENTS_RO(o->area_list, i, oa)) { if (adv_router == o->router_id) lsdb = oa->lsdb_self; else lsdb = oa->lsdb; if (ospf6_create_single_router_lsa(oa, lsdb, adv_router) == NULL) { vty_out(vty, "Adv router is not found in LSDB."); return CMD_SUCCESS; } ospf6_lsdb_show(vty, level, &type, NULL, NULL, oa->temp_router_lsa_lsdb); /* Remove the temp cache */ ospf6_remove_temp_router_lsa(oa); } vty_out(vty, "\n"); return CMD_SUCCESS; } DEFUN (show_ipv6_ospf6_database_type_id, show_ipv6_ospf6_database_type_id_cmd, "show ipv6 ospf6 database linkstate-id A.B.C.D []", SHOW_STR IPV6_STR OSPF6_STR "Display Link state database\n" "Display Router LSAs\n" "Display Network LSAs\n" "Display Inter-Area-Prefix LSAs\n" "Display Inter-Area-Router LSAs\n" "Display As-External LSAs\n" "Display Group-Membership LSAs\n" "Display Type-7 LSAs\n" "Display Link LSAs\n" "Display Intra-Area-Prefix LSAs\n" "Search by Link state ID\n" "Specify Link state ID as IPv4 address notation\n" "Display details of LSAs\n" "Dump LSAs\n" "Display LSA's internal information\n" ) { int idx_lsa = 4; int idx_ipv4 = 6; int idx_level = 7; int level; struct listnode *i, *j; struct ospf6 *o = ospf6; struct ospf6_area *oa; struct ospf6_interface *oi; uint16_t type = 0; uint32_t id = 0; OSPF6_CMD_CHECK_RUNNING(); type = parse_type_spec(idx_lsa, argc, argv); inet_pton(AF_INET, argv[idx_ipv4]->arg, &id); level = parse_show_level(idx_level, argc, argv); switch (OSPF6_LSA_SCOPE(type)) { case OSPF6_SCOPE_AREA: for (ALL_LIST_ELEMENTS_RO(o->area_list, i, oa)) { vty_out(vty, AREA_LSDB_TITLE_FORMAT, oa->name); ospf6_lsdb_show(vty, level, &type, &id, NULL, oa->lsdb); } break; case OSPF6_SCOPE_LINKLOCAL: for (ALL_LIST_ELEMENTS_RO(o->area_list, i, oa)) { for (ALL_LIST_ELEMENTS_RO(oa->if_list, j, oi)) { vty_out(vty, IF_LSDB_TITLE_FORMAT, oi->interface->name, oa->name); ospf6_lsdb_show(vty, level, &type, &id, NULL, oi->lsdb); } } break; case OSPF6_SCOPE_AS: vty_out(vty, AS_LSDB_TITLE_FORMAT); ospf6_lsdb_show(vty, level, &type, &id, NULL, o->lsdb); break; default: assert(0); break; } vty_out(vty, "\n"); return CMD_SUCCESS; } DEFUN (show_ipv6_ospf6_database_type_router, show_ipv6_ospf6_database_type_router_cmd, "show ipv6 ospf6 database <*|adv-router> A.B.C.D []", SHOW_STR IPV6_STR OSPF6_STR "Display Link state database\n" "Display Router LSAs\n" "Display Network LSAs\n" "Display Inter-Area-Prefix LSAs\n" "Display Inter-Area-Router LSAs\n" "Display As-External LSAs\n" "Display Group-Membership LSAs\n" "Display Type-7 LSAs\n" "Display Link LSAs\n" "Display Intra-Area-Prefix LSAs\n" "Any Link state ID\n" "Search by Advertising Router\n" "Specify Advertising Router as IPv4 address notation\n" "Display details of LSAs\n" "Dump LSAs\n" "Display LSA's internal information\n" ) { int idx_lsa = 4; int idx_ipv4 = 6; int idx_level = 7; int level; struct listnode *i, *j; struct ospf6 *o = ospf6; struct ospf6_area *oa; struct ospf6_interface *oi; uint16_t type = 0; uint32_t adv_router = 0; OSPF6_CMD_CHECK_RUNNING(); type = parse_type_spec(idx_lsa, argc, argv); inet_pton(AF_INET, argv[idx_ipv4]->arg, &adv_router); level = parse_show_level(idx_level, argc, argv); switch (OSPF6_LSA_SCOPE(type)) { case OSPF6_SCOPE_AREA: for (ALL_LIST_ELEMENTS_RO(o->area_list, i, oa)) { vty_out(vty, AREA_LSDB_TITLE_FORMAT, oa->name); ospf6_lsdb_show(vty, level, &type, NULL, &adv_router, oa->lsdb); } break; case OSPF6_SCOPE_LINKLOCAL: for (ALL_LIST_ELEMENTS_RO(o->area_list, i, oa)) { for (ALL_LIST_ELEMENTS_RO(oa->if_list, j, oi)) { vty_out(vty, IF_LSDB_TITLE_FORMAT, oi->interface->name, oa->name); ospf6_lsdb_show(vty, level, &type, NULL, &adv_router, oi->lsdb); } } break; case OSPF6_SCOPE_AS: vty_out(vty, AS_LSDB_TITLE_FORMAT); ospf6_lsdb_show(vty, level, &type, NULL, &adv_router, o->lsdb); break; default: assert(0); break; } vty_out(vty, "\n"); return CMD_SUCCESS; } DEFUN (show_ipv6_ospf6_database_id_router, show_ipv6_ospf6_database_id_router_cmd, "show ipv6 ospf6 database * A.B.C.D A.B.C.D []", SHOW_STR IPV6_STR OSPF6_STR "Display Link state database\n" "Any Link state Type\n" "Specify Link state ID as IPv4 address notation\n" "Specify Advertising Router as IPv4 address notation\n" "Display details of LSAs\n" "Dump LSAs\n" "Display LSA's internal information\n" ) { int idx_ls_id = 5; int idx_adv_rtr = 6; int idx_level = 7; int level; struct listnode *i, *j; struct ospf6 *o = ospf6; struct ospf6_area *oa; struct ospf6_interface *oi; uint32_t id = 0; uint32_t adv_router = 0; OSPF6_CMD_CHECK_RUNNING(); inet_pton(AF_INET, argv[idx_ls_id]->arg, &id); inet_pton(AF_INET, argv[idx_adv_rtr]->arg, &adv_router); level = parse_show_level(idx_level, argc, argv); for (ALL_LIST_ELEMENTS_RO(o->area_list, i, oa)) { vty_out(vty, AREA_LSDB_TITLE_FORMAT, oa->name); ospf6_lsdb_show(vty, level, NULL, &id, &adv_router, oa->lsdb); } for (ALL_LIST_ELEMENTS_RO(o->area_list, i, oa)) { for (ALL_LIST_ELEMENTS_RO(oa->if_list, j, oi)) { vty_out(vty, IF_LSDB_TITLE_FORMAT, oi->interface->name, oa->name); ospf6_lsdb_show(vty, level, NULL, &id, &adv_router, oi->lsdb); } } vty_out(vty, AS_LSDB_TITLE_FORMAT); ospf6_lsdb_show(vty, level, NULL, &id, &adv_router, o->lsdb); vty_out(vty, "\n"); return CMD_SUCCESS; } DEFUN (show_ipv6_ospf6_database_adv_router_linkstate_id, show_ipv6_ospf6_database_adv_router_linkstate_id_cmd, "show ipv6 ospf6 database adv-router A.B.C.D linkstate-id A.B.C.D []", SHOW_STR IPV6_STR OSPF6_STR "Display Link state database\n" "Search by Advertising Router\n" "Specify Advertising Router as IPv4 address notation\n" "Search by Link state ID\n" "Specify Link state ID as IPv4 address notation\n" "Display details of LSAs\n" "Dump LSAs\n" "Display LSA's internal information\n") { int idx_adv_rtr = 5; int idx_ls_id = 7; int idx_level = 8; int level; struct listnode *i, *j; struct ospf6 *o = ospf6; struct ospf6_area *oa; struct ospf6_interface *oi; uint32_t id = 0; uint32_t adv_router = 0; OSPF6_CMD_CHECK_RUNNING(); inet_pton(AF_INET, argv[idx_adv_rtr]->arg, &adv_router); inet_pton(AF_INET, argv[idx_ls_id]->arg, &id); level = parse_show_level(idx_level, argc, argv); for (ALL_LIST_ELEMENTS_RO(o->area_list, i, oa)) { vty_out(vty, AREA_LSDB_TITLE_FORMAT, oa->name); ospf6_lsdb_show(vty, level, NULL, &id, &adv_router, oa->lsdb); } for (ALL_LIST_ELEMENTS_RO(o->area_list, i, oa)) { for (ALL_LIST_ELEMENTS_RO(oa->if_list, j, oi)) { vty_out(vty, IF_LSDB_TITLE_FORMAT, oi->interface->name, oa->name); ospf6_lsdb_show(vty, level, NULL, &id, &adv_router, oi->lsdb); } } vty_out(vty, AS_LSDB_TITLE_FORMAT); ospf6_lsdb_show(vty, level, NULL, &id, &adv_router, o->lsdb); vty_out(vty, "\n"); return CMD_SUCCESS; } DEFUN (show_ipv6_ospf6_database_type_id_router, show_ipv6_ospf6_database_type_id_router_cmd, "show ipv6 ospf6 database A.B.C.D A.B.C.D []", SHOW_STR IPV6_STR OSPF6_STR "Display Link state database\n" "Display Router LSAs\n" "Display Network LSAs\n" "Display Inter-Area-Prefix LSAs\n" "Display Inter-Area-Router LSAs\n" "Display As-External LSAs\n" "Display Group-Membership LSAs\n" "Display Type-7 LSAs\n" "Display Link LSAs\n" "Display Intra-Area-Prefix LSAs\n" "Specify Link state ID as IPv4 address notation\n" "Specify Advertising Router as IPv4 address notation\n" "Dump LSAs\n" "Display LSA's internal information\n") { int idx_lsa = 4; int idx_ls_id = 5; int idx_adv_rtr = 6; int idx_level = 7; int level; struct listnode *i, *j; struct ospf6 *o = ospf6; struct ospf6_area *oa; struct ospf6_interface *oi; uint16_t type = 0; uint32_t id = 0; uint32_t adv_router = 0; OSPF6_CMD_CHECK_RUNNING(); type = parse_type_spec(idx_lsa, argc, argv); inet_pton(AF_INET, argv[idx_ls_id]->arg, &id); inet_pton(AF_INET, argv[idx_adv_rtr]->arg, &adv_router); level = parse_show_level(idx_level, argc, argv); switch (OSPF6_LSA_SCOPE(type)) { case OSPF6_SCOPE_AREA: for (ALL_LIST_ELEMENTS_RO(o->area_list, i, oa)) { vty_out(vty, AREA_LSDB_TITLE_FORMAT, oa->name); ospf6_lsdb_show(vty, level, &type, &id, &adv_router, oa->lsdb); } break; case OSPF6_SCOPE_LINKLOCAL: for (ALL_LIST_ELEMENTS_RO(o->area_list, i, oa)) { for (ALL_LIST_ELEMENTS_RO(oa->if_list, j, oi)) { vty_out(vty, IF_LSDB_TITLE_FORMAT, oi->interface->name, oa->name); ospf6_lsdb_show(vty, level, &type, &id, &adv_router, oi->lsdb); } } break; case OSPF6_SCOPE_AS: vty_out(vty, AS_LSDB_TITLE_FORMAT); ospf6_lsdb_show(vty, level, &type, &id, &adv_router, o->lsdb); break; default: assert(0); break; } vty_out(vty, "\n"); return CMD_SUCCESS; } DEFUN (show_ipv6_ospf6_database_type_adv_router_linkstate_id, show_ipv6_ospf6_database_type_adv_router_linkstate_id_cmd, "show ipv6 ospf6 database adv-router A.B.C.D linkstate-id A.B.C.D []", SHOW_STR IPV6_STR OSPF6_STR "Display Link state database\n" "Display Router LSAs\n" "Display Network LSAs\n" "Display Inter-Area-Prefix LSAs\n" "Display Inter-Area-Router LSAs\n" "Display As-External LSAs\n" "Display Group-Membership LSAs\n" "Display Type-7 LSAs\n" "Display Link LSAs\n" "Display Intra-Area-Prefix LSAs\n" "Search by Advertising Router\n" "Specify Advertising Router as IPv4 address notation\n" "Search by Link state ID\n" "Specify Link state ID as IPv4 address notation\n" "Dump LSAs\n" "Display LSA's internal information\n") { int idx_lsa = 4; int idx_adv_rtr = 6; int idx_ls_id = 8; int idx_level = 9; int level; struct listnode *i, *j; struct ospf6 *o = ospf6; struct ospf6_area *oa; struct ospf6_interface *oi; uint16_t type = 0; uint32_t id = 0; uint32_t adv_router = 0; OSPF6_CMD_CHECK_RUNNING(); type = parse_type_spec(idx_lsa, argc, argv); inet_pton(AF_INET, argv[idx_adv_rtr]->arg, &adv_router); inet_pton(AF_INET, argv[idx_ls_id]->arg, &id); level = parse_show_level(idx_level, argc, argv); switch (OSPF6_LSA_SCOPE(type)) { case OSPF6_SCOPE_AREA: for (ALL_LIST_ELEMENTS_RO(o->area_list, i, oa)) { vty_out(vty, AREA_LSDB_TITLE_FORMAT, oa->name); ospf6_lsdb_show(vty, level, &type, &id, &adv_router, oa->lsdb); } break; case OSPF6_SCOPE_LINKLOCAL: for (ALL_LIST_ELEMENTS_RO(o->area_list, i, oa)) { for (ALL_LIST_ELEMENTS_RO(oa->if_list, j, oi)) { vty_out(vty, IF_LSDB_TITLE_FORMAT, oi->interface->name, oa->name); ospf6_lsdb_show(vty, level, &type, &id, &adv_router, oi->lsdb); } } break; case OSPF6_SCOPE_AS: vty_out(vty, AS_LSDB_TITLE_FORMAT); ospf6_lsdb_show(vty, level, &type, &id, &adv_router, o->lsdb); break; default: assert(0); break; } vty_out(vty, "\n"); return CMD_SUCCESS; } DEFUN (show_ipv6_ospf6_database_self_originated, show_ipv6_ospf6_database_self_originated_cmd, "show ipv6 ospf6 database self-originated []", SHOW_STR IPV6_STR OSPF6_STR "Display Link state database\n" "Display Self-originated LSAs\n" "Display details of LSAs\n" "Dump LSAs\n" "Display LSA's internal information\n") { int idx_level = 5; int level; struct listnode *i, *j; struct ospf6 *o = ospf6; struct ospf6_area *oa; struct ospf6_interface *oi; uint32_t adv_router = 0; OSPF6_CMD_CHECK_RUNNING(); level = parse_show_level(idx_level, argc, argv); adv_router = o->router_id; for (ALL_LIST_ELEMENTS_RO(o->area_list, i, oa)) { vty_out(vty, AREA_LSDB_TITLE_FORMAT, oa->name); ospf6_lsdb_show(vty, level, NULL, NULL, &adv_router, oa->lsdb); } for (ALL_LIST_ELEMENTS_RO(o->area_list, i, oa)) { for (ALL_LIST_ELEMENTS_RO(oa->if_list, j, oi)) { vty_out(vty, IF_LSDB_TITLE_FORMAT, oi->interface->name, oa->name); ospf6_lsdb_show(vty, level, NULL, NULL, &adv_router, oi->lsdb); } } vty_out(vty, AS_LSDB_TITLE_FORMAT); ospf6_lsdb_show(vty, level, NULL, NULL, &adv_router, o->lsdb); vty_out(vty, "\n"); return CMD_SUCCESS; } DEFUN (show_ipv6_ospf6_database_type_self_originated, show_ipv6_ospf6_database_type_self_originated_cmd, "show ipv6 ospf6 database self-originated []", SHOW_STR IPV6_STR OSPF6_STR "Display Link state database\n" "Display Router LSAs\n" "Display Network LSAs\n" "Display Inter-Area-Prefix LSAs\n" "Display Inter-Area-Router LSAs\n" "Display As-External LSAs\n" "Display Group-Membership LSAs\n" "Display Type-7 LSAs\n" "Display Link LSAs\n" "Display Intra-Area-Prefix LSAs\n" "Display Self-originated LSAs\n" "Display details of LSAs\n" "Dump LSAs\n" "Display LSA's internal information\n") { int idx_lsa = 4; int idx_level = 6; int level; struct listnode *i, *j; struct ospf6 *o = ospf6; struct ospf6_area *oa; struct ospf6_interface *oi; uint16_t type = 0; uint32_t adv_router = 0; OSPF6_CMD_CHECK_RUNNING(); type = parse_type_spec(idx_lsa, argc, argv); level = parse_show_level(idx_level, argc, argv); adv_router = o->router_id; switch (OSPF6_LSA_SCOPE(type)) { case OSPF6_SCOPE_AREA: for (ALL_LIST_ELEMENTS_RO(o->area_list, i, oa)) { vty_out(vty, AREA_LSDB_TITLE_FORMAT, oa->name); ospf6_lsdb_show(vty, level, &type, NULL, &adv_router, oa->lsdb); } break; case OSPF6_SCOPE_LINKLOCAL: for (ALL_LIST_ELEMENTS_RO(o->area_list, i, oa)) { for (ALL_LIST_ELEMENTS_RO(oa->if_list, j, oi)) { vty_out(vty, IF_LSDB_TITLE_FORMAT, oi->interface->name, oa->name); ospf6_lsdb_show(vty, level, &type, NULL, &adv_router, oi->lsdb); } } break; case OSPF6_SCOPE_AS: vty_out(vty, AS_LSDB_TITLE_FORMAT); ospf6_lsdb_show(vty, level, &type, NULL, &adv_router, o->lsdb); break; default: assert(0); break; } vty_out(vty, "\n"); return CMD_SUCCESS; } DEFUN (show_ipv6_ospf6_database_type_self_originated_linkstate_id, show_ipv6_ospf6_database_type_self_originated_linkstate_id_cmd, "show ipv6 ospf6 database self-originated linkstate-id A.B.C.D []", SHOW_STR IPV6_STR OSPF6_STR "Display Link state database\n" "Display Router LSAs\n" "Display Network LSAs\n" "Display Inter-Area-Prefix LSAs\n" "Display Inter-Area-Router LSAs\n" "Display As-External LSAs\n" "Display Group-Membership LSAs\n" "Display Type-7 LSAs\n" "Display Link LSAs\n" "Display Intra-Area-Prefix LSAs\n" "Display Self-originated LSAs\n" "Search by Link state ID\n" "Specify Link state ID as IPv4 address notation\n" "Display details of LSAs\n" "Dump LSAs\n" "Display LSA's internal information\n") { int idx_lsa = 4; int idx_ls_id = 7; int idx_level = 8; int level; struct listnode *i, *j; struct ospf6 *o = ospf6; struct ospf6_area *oa; struct ospf6_interface *oi; uint16_t type = 0; uint32_t adv_router = 0; uint32_t id = 0; OSPF6_CMD_CHECK_RUNNING(); type = parse_type_spec(idx_lsa, argc, argv); inet_pton(AF_INET, argv[idx_ls_id]->arg, &id); level = parse_show_level(idx_level, argc, argv); adv_router = o->router_id; switch (OSPF6_LSA_SCOPE(type)) { case OSPF6_SCOPE_AREA: for (ALL_LIST_ELEMENTS_RO(o->area_list, i, oa)) { vty_out(vty, AREA_LSDB_TITLE_FORMAT, oa->name); ospf6_lsdb_show(vty, level, &type, &id, &adv_router, oa->lsdb); } break; case OSPF6_SCOPE_LINKLOCAL: for (ALL_LIST_ELEMENTS_RO(o->area_list, i, oa)) { for (ALL_LIST_ELEMENTS_RO(oa->if_list, j, oi)) { vty_out(vty, IF_LSDB_TITLE_FORMAT, oi->interface->name, oa->name); ospf6_lsdb_show(vty, level, &type, &id, &adv_router, oi->lsdb); } } break; case OSPF6_SCOPE_AS: vty_out(vty, AS_LSDB_TITLE_FORMAT); ospf6_lsdb_show(vty, level, &type, &id, &adv_router, o->lsdb); break; default: assert(0); break; } vty_out(vty, "\n"); return CMD_SUCCESS; } DEFUN (show_ipv6_ospf6_database_type_id_self_originated, show_ipv6_ospf6_database_type_id_self_originated_cmd, "show ipv6 ospf6 database A.B.C.D self-originated []", SHOW_STR IPV6_STR OSPF6_STR "Display Link state database\n" "Display Router LSAs\n" "Display Network LSAs\n" "Display Inter-Area-Prefix LSAs\n" "Display Inter-Area-Router LSAs\n" "Display As-External LSAs\n" "Display Group-Membership LSAs\n" "Display Type-7 LSAs\n" "Display Link LSAs\n" "Display Intra-Area-Prefix LSAs\n" "Specify Link state ID as IPv4 address notation\n" "Display Self-originated LSAs\n" "Display details of LSAs\n" "Dump LSAs\n" "Display LSA's internal information\n") { int idx_lsa = 4; int idx_ls_id = 5; int idx_level = 7; int level; struct listnode *i, *j; struct ospf6 *o = ospf6; struct ospf6_area *oa; struct ospf6_interface *oi; uint16_t type = 0; uint32_t adv_router = 0; uint32_t id = 0; OSPF6_CMD_CHECK_RUNNING(); type = parse_type_spec(idx_lsa, argc, argv); inet_pton(AF_INET, argv[idx_ls_id]->arg, &id); level = parse_show_level(idx_level, argc, argv); adv_router = o->router_id; switch (OSPF6_LSA_SCOPE(type)) { case OSPF6_SCOPE_AREA: for (ALL_LIST_ELEMENTS_RO(o->area_list, i, oa)) { vty_out(vty, AREA_LSDB_TITLE_FORMAT, oa->name); ospf6_lsdb_show(vty, level, &type, &id, &adv_router, oa->lsdb); } break; case OSPF6_SCOPE_LINKLOCAL: for (ALL_LIST_ELEMENTS_RO(o->area_list, i, oa)) { for (ALL_LIST_ELEMENTS_RO(oa->if_list, j, oi)) { vty_out(vty, IF_LSDB_TITLE_FORMAT, oi->interface->name, oa->name); ospf6_lsdb_show(vty, level, &type, &id, &adv_router, oi->lsdb); } } break; case OSPF6_SCOPE_AS: vty_out(vty, AS_LSDB_TITLE_FORMAT); ospf6_lsdb_show(vty, level, &type, &id, &adv_router, o->lsdb); break; default: assert(0); break; } vty_out(vty, "\n"); return CMD_SUCCESS; } DEFUN (show_ipv6_ospf6_border_routers, show_ipv6_ospf6_border_routers_cmd, "show ipv6 ospf6 border-routers []", SHOW_STR IP6_STR OSPF6_STR "Display routing table for ABR and ASBR\n" "Router ID\n" "Show detailed output\n") { int idx_ipv4 = 4; uint32_t adv_router; struct ospf6_route *ro; struct prefix prefix; OSPF6_CMD_CHECK_RUNNING(); if (argc == 5) { if (strmatch(argv[idx_ipv4]->text, "detail")) { for (ro = ospf6_route_head(ospf6->brouter_table); ro; ro = ospf6_route_next(ro)) ospf6_route_show_detail(vty, ro); } else { inet_pton(AF_INET, argv[idx_ipv4]->arg, &adv_router); ospf6_linkstate_prefix(adv_router, 0, &prefix); ro = ospf6_route_lookup(&prefix, ospf6->brouter_table); if (!ro) { vty_out(vty, "No Route found for Router ID: %s\n", argv[4]->arg); return CMD_SUCCESS; } ospf6_route_show_detail(vty, ro); return CMD_SUCCESS; } } else { ospf6_brouter_show_header(vty); for (ro = ospf6_route_head(ospf6->brouter_table); ro; ro = ospf6_route_next(ro)) ospf6_brouter_show(vty, ro); } return CMD_SUCCESS; } DEFUN (show_ipv6_ospf6_linkstate, show_ipv6_ospf6_linkstate_cmd, "show ipv6 ospf6 linkstate ", SHOW_STR IP6_STR OSPF6_STR "Display linkstate routing table\n" "Display Router Entry\n" "Specify Router ID as IPv4 address notation\n" "Display Network Entry\n" "Specify Router ID as IPv4 address notation\n" "Specify Link state ID as IPv4 address notation\n") { int idx_ipv4 = 5; struct listnode *node; struct ospf6_area *oa; OSPF6_CMD_CHECK_RUNNING(); for (ALL_LIST_ELEMENTS_RO(ospf6->area_list, node, oa)) { vty_out(vty, "\n SPF Result in Area %s\n\n", oa->name); ospf6_linkstate_table_show(vty, idx_ipv4, argc, argv, oa->spf_table); } vty_out(vty, "\n"); return CMD_SUCCESS; } DEFUN (show_ipv6_ospf6_linkstate_detail, show_ipv6_ospf6_linkstate_detail_cmd, "show ipv6 ospf6 linkstate detail", SHOW_STR IP6_STR OSPF6_STR "Display linkstate routing table\n" "Display detailed information\n") { int idx_detail = 4; struct listnode *node; struct ospf6_area *oa; OSPF6_CMD_CHECK_RUNNING(); for (ALL_LIST_ELEMENTS_RO(ospf6->area_list, node, oa)) { vty_out(vty, "\n SPF Result in Area %s\n\n", oa->name); ospf6_linkstate_table_show(vty, idx_detail, argc, argv, oa->spf_table); } vty_out(vty, "\n"); return CMD_SUCCESS; } static void ospf6_plist_add(struct prefix_list *plist) { if (prefix_list_afi(plist) != AFI_IP6) return; ospf6_area_plist_update(plist, 1); } static void ospf6_plist_del(struct prefix_list *plist) { if (prefix_list_afi(plist) != AFI_IP6) return; ospf6_area_plist_update(plist, 0); } /* Install ospf related commands. */ void ospf6_init(void) { ospf6_top_init(); ospf6_area_init(); ospf6_interface_init(); ospf6_neighbor_init(); ospf6_zebra_init(master); ospf6_lsa_init(); ospf6_spf_init(); ospf6_intra_init(); ospf6_asbr_init(); ospf6_abr_init(); prefix_list_add_hook(ospf6_plist_add); prefix_list_delete_hook(ospf6_plist_del); ospf6_bfd_init(); install_node(&debug_node, config_write_ospf6_debug); install_element_ospf6_debug_message(); install_element_ospf6_debug_lsa(); install_element_ospf6_debug_interface(); install_element_ospf6_debug_neighbor(); install_element_ospf6_debug_zebra(); install_element_ospf6_debug_spf(); install_element_ospf6_debug_route(); install_element_ospf6_debug_brouter(); install_element_ospf6_debug_asbr(); install_element_ospf6_debug_abr(); install_element_ospf6_debug_flood(); install_element_ospf6_clear_interface(); install_element(VIEW_NODE, &show_debugging_ospf6_cmd); install_element(VIEW_NODE, &show_ipv6_ospf6_border_routers_cmd); install_element(VIEW_NODE, &show_ipv6_ospf6_linkstate_cmd); install_element(VIEW_NODE, &show_ipv6_ospf6_linkstate_detail_cmd); install_element(VIEW_NODE, &show_ipv6_ospf6_database_cmd); install_element(VIEW_NODE, &show_ipv6_ospf6_database_type_cmd); install_element(VIEW_NODE, &show_ipv6_ospf6_database_id_cmd); install_element(VIEW_NODE, &show_ipv6_ospf6_database_router_cmd); install_element(VIEW_NODE, &show_ipv6_ospf6_database_type_id_cmd); install_element(VIEW_NODE, &show_ipv6_ospf6_database_type_router_cmd); install_element(VIEW_NODE, &show_ipv6_ospf6_database_adv_router_linkstate_id_cmd); install_element(VIEW_NODE, &show_ipv6_ospf6_database_id_router_cmd); install_element(VIEW_NODE, &show_ipv6_ospf6_database_type_id_router_cmd); install_element( VIEW_NODE, &show_ipv6_ospf6_database_type_adv_router_linkstate_id_cmd); install_element(VIEW_NODE, &show_ipv6_ospf6_database_self_originated_cmd); install_element(VIEW_NODE, &show_ipv6_ospf6_database_type_self_originated_cmd); install_element(VIEW_NODE, &show_ipv6_ospf6_database_type_id_self_originated_cmd); install_element( VIEW_NODE, &show_ipv6_ospf6_database_type_self_originated_linkstate_id_cmd); install_element(VIEW_NODE, &show_ipv6_ospf6_database_aggr_router_cmd); /* Make ospf protocol socket. */ ospf6_serv_sock(); thread_add_read(master, ospf6_receive, NULL, ospf6_sock, NULL); } void ospf6_clean(void) { if (!ospf6) return; if (ospf6->route_table) ospf6_route_remove_all(ospf6->route_table); if (ospf6->brouter_table) ospf6_route_remove_all(ospf6->brouter_table); } frr-7.2.1/ospf6d/ospf6d.conf.sample0000644000000000000000000000212613610377563013746 00000000000000! ! Zebra configuration saved from vty ! 2003/11/28 00:49:49 ! hostname ospf6d@plant password zebra log stdout service advanced-vty ! debug ospf6 neighbor state ! interface fxp0 ipv6 ospf6 cost 1 ipv6 ospf6 hello-interval 10 ipv6 ospf6 dead-interval 40 ipv6 ospf6 retransmit-interval 5 ipv6 ospf6 priority 0 ipv6 ospf6 transmit-delay 1 ipv6 ospf6 instance-id 0 ! interface lo0 ipv6 ospf6 cost 1 ipv6 ospf6 hello-interval 10 ipv6 ospf6 dead-interval 40 ipv6 ospf6 retransmit-interval 5 ipv6 ospf6 priority 1 ipv6 ospf6 transmit-delay 1 ipv6 ospf6 instance-id 0 ! router ospf6 router-id 255.1.1.1 redistribute static route-map static-ospf6 interface fxp0 area 0.0.0.0 ! access-list access4 permit 127.0.0.1/32 ! ipv6 access-list access6 permit 3ffe:501::/32 ipv6 access-list access6 permit 2001:200::/48 ipv6 access-list access6 permit ::1/128 ! ipv6 prefix-list test-prefix seq 1000 deny any ! route-map static-ospf6 permit 10 match ipv6 address prefix-list test-prefix set metric-type type-2 set metric 2000 ! line vty access-class access4 ipv6 access-class access6 exec-timeout 0 0 ! frr-7.2.1/ospf6d/ospf6d.h0000644000000000000000000000763613610377563012003 00000000000000/* * Copyright (C) 2003 Yasuhiro Ohara * * This file is part of GNU Zebra. * * GNU Zebra is free software; you can redistribute it and/or modify it * under the terms of the GNU General Public License as published by the * Free Software Foundation; either version 2, or (at your option) any * later version. * * GNU Zebra 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; see the file COPYING; if not, write to the Free Software * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA */ #ifndef OSPF6D_H #define OSPF6D_H #include "libospf.h" #include "thread.h" #include "ospf6_memory.h" /* global variables */ extern struct thread_master *master; /* Historical for KAME. */ #ifndef IPV6_JOIN_GROUP #ifdef IPV6_ADD_MEMBERSHIP #define IPV6_JOIN_GROUP IPV6_ADD_MEMBERSHIP #endif /* IPV6_ADD_MEMBERSHIP. */ #ifdef IPV6_JOIN_MEMBERSHIP #define IPV6_JOIN_GROUP IPV6_JOIN_MEMBERSHIP #endif /* IPV6_JOIN_MEMBERSHIP. */ #endif /* ! IPV6_JOIN_GROUP*/ #ifndef IPV6_LEAVE_GROUP #ifdef IPV6_DROP_MEMBERSHIP #define IPV6_LEAVE_GROUP IPV6_DROP_MEMBERSHIP #endif /* IPV6_DROP_MEMBERSHIP */ #endif /* ! IPV6_LEAVE_GROUP */ #define MSG_OK 0 #define MSG_NG 1 /* cast macro: XXX - these *must* die, ick ick. */ #define OSPF6_PROCESS(x) ((struct ospf6 *) (x)) #define OSPF6_AREA(x) ((struct ospf6_area *) (x)) #define OSPF6_INTERFACE(x) ((struct ospf6_interface *) (x)) #define OSPF6_NEIGHBOR(x) ((struct ospf6_neighbor *) (x)) /* operation on timeval structure */ #define timerstring(tv, buf, size) \ do { \ if ((tv)->tv_sec / 60 / 60 / 24) \ snprintf(buf, size, "%lldd%02lld:%02lld:%02lld", \ (tv)->tv_sec / 60LL / 60 / 24, \ (tv)->tv_sec / 60LL / 60 % 24, \ (tv)->tv_sec / 60LL % 60, \ (tv)->tv_sec % 60LL); \ else \ snprintf(buf, size, "%02lld:%02lld:%02lld", \ (tv)->tv_sec / 60LL / 60 % 24, \ (tv)->tv_sec / 60LL % 60, \ (tv)->tv_sec % 60LL); \ } while (0) #define threadtimer_string(now, t, buf, size) \ do { \ struct timeval _result; \ if (!t) \ snprintf(buf, size, "inactive"); \ else { \ timersub(&t->u.sands, &now, &_result); \ timerstring(&_result, buf, size); \ } \ } while (0) /* for commands */ #define OSPF6_AREA_STR "Area information\n" #define OSPF6_AREA_ID_STR "Area ID (as an IPv4 notation)\n" #define OSPF6_SPF_STR "Shortest Path First tree information\n" #define OSPF6_ROUTER_ID_STR "Specify Router-ID\n" #define OSPF6_LS_ID_STR "Specify Link State ID\n" #define OSPF6_CMD_CHECK_RUNNING() \ if (ospf6 == NULL) { \ vty_out(vty, "OSPFv3 is not running\n"); \ return CMD_SUCCESS; \ } extern struct zebra_privs_t ospf6d_privs; /* Function Prototypes */ extern struct route_node *route_prev(struct route_node *node); extern void ospf6_debug(void); extern void ospf6_init(void); #endif /* OSPF6D_H */ frr-7.2.1/ospf6d/subdir.am0000644000000000000000000000415613610377563012232 00000000000000# # ospf6d # if OSPF6D noinst_LIBRARIES += ospf6d/libospf6.a sbin_PROGRAMS += ospf6d/ospf6d dist_examples_DATA += ospf6d/ospf6d.conf.sample vtysh_scan += \ $(top_srcdir)/ospf6d/ospf6_abr.c \ $(top_srcdir)/ospf6d/ospf6_asbr.c \ $(top_srcdir)/ospf6d/ospf6_area.c \ $(top_srcdir)/ospf6d/ospf6_bfd.c \ $(top_srcdir)/ospf6d/ospf6_flood.c \ $(top_srcdir)/ospf6d/ospf6_interface.c \ $(top_srcdir)/ospf6d/ospf6_intra.c \ $(top_srcdir)/ospf6d/ospf6_lsa.c \ $(top_srcdir)/ospf6d/ospf6_message.c \ $(top_srcdir)/ospf6d/ospf6_neighbor.c \ $(top_srcdir)/ospf6d/ospf6_route.c \ $(top_srcdir)/ospf6d/ospf6_spf.c \ $(top_srcdir)/ospf6d/ospf6_top.c \ $(top_srcdir)/ospf6d/ospf6_zebra.c \ $(top_srcdir)/ospf6d/ospf6d.c \ # end if SNMP module_LTLIBRARIES += ospf6d/ospf6d_snmp.la endif man8 += $(MANBUILD)/frr-ospf6d.8 endif ospf6d_libospf6_a_SOURCES = \ ospf6d/ospf6_abr.c \ ospf6d/ospf6_area.c \ ospf6d/ospf6_asbr.c \ ospf6d/ospf6_bfd.c \ ospf6d/ospf6_flood.c \ ospf6d/ospf6_interface.c \ ospf6d/ospf6_intra.c \ ospf6d/ospf6_lsa.c \ ospf6d/ospf6_lsdb.c \ ospf6d/ospf6_memory.c \ ospf6d/ospf6_message.c \ ospf6d/ospf6_neighbor.c \ ospf6d/ospf6_network.c \ ospf6d/ospf6_proto.c \ ospf6d/ospf6_route.c \ ospf6d/ospf6_spf.c \ ospf6d/ospf6_top.c \ ospf6d/ospf6_zebra.c \ ospf6d/ospf6d.c \ # end noinst_HEADERS += \ ospf6d/ospf6_abr.h \ ospf6d/ospf6_area.h \ ospf6d/ospf6_asbr.h \ ospf6d/ospf6_bfd.h \ ospf6d/ospf6_flood.h \ ospf6d/ospf6_interface.h \ ospf6d/ospf6_intra.h \ ospf6d/ospf6_lsa.h \ ospf6d/ospf6_lsdb.h \ ospf6d/ospf6_memory.h \ ospf6d/ospf6_message.h \ ospf6d/ospf6_neighbor.h \ ospf6d/ospf6_network.h \ ospf6d/ospf6_proto.h \ ospf6d/ospf6_route.h \ ospf6d/ospf6_spf.h \ ospf6d/ospf6_top.h \ ospf6d/ospf6_zebra.h \ ospf6d/ospf6d.h \ # end ospf6d_ospf6d_LDADD = ospf6d/libospf6.a lib/libfrr.la $(LIBCAP) ospf6d_ospf6d_SOURCES = \ ospf6d/ospf6_main.c \ # end ospf6d_ospf6d_snmp_la_SOURCES = ospf6d/ospf6_snmp.c ospf6d_ospf6d_snmp_la_CFLAGS = $(WERROR) $(SNMP_CFLAGS) -std=gnu99 ospf6d_ospf6d_snmp_la_LDFLAGS = -avoid-version -module -shared -export-dynamic ospf6d_ospf6d_snmp_la_LIBADD = lib/libfrrsnmp.la frr-7.2.1/ospfclient/0000755000000000000000000000000013610377563011442 500000000000000frr-7.2.1/ospfclient/Makefile0000644000000000000000000000024113610377563013017 00000000000000all: ALWAYS @$(MAKE) -s -C .. ospfclient/ospfclient %: ALWAYS @$(MAKE) -s -C .. ospfclient/$@ Makefile: #nothing ALWAYS: .PHONY: ALWAYS makefiles .SUFFIXES: frr-7.2.1/ospfclient/ospf_apiclient.c0000644000000000000000000004440313610377563014532 00000000000000/* * Client side of OSPF API. * Copyright (C) 2001, 2002, 2003 Ralph Keller * * This file is part of GNU Zebra. * * GNU Zebra is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License as published * by the Free Software Foundation; either version 2, or (at your * option) any later version. * * GNU Zebra 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; see the file COPYING; if not, write to the Free Software * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA */ #include #include #include "getopt.h" #include "thread.h" #include "prefix.h" #include "linklist.h" #include "if.h" #include "vector.h" #include "vty.h" #include "command.h" #include "filter.h" #include "stream.h" #include "log.h" #include "memory.h" /* work around gcc bug 69981, disable MTYPEs in libospf */ #define _QUAGGA_OSPF_MEMORY_H #include "ospfd/ospfd.h" #include "ospfd/ospf_interface.h" #include "ospfd/ospf_asbr.h" #include "ospfd/ospf_lsa.h" #include "ospfd/ospf_opaque.h" #include "ospfd/ospf_lsdb.h" #include "ospfd/ospf_neighbor.h" #include "ospfd/ospf_dump.h" #include "ospfd/ospf_route.h" #include "ospfd/ospf_zebra.h" #include "ospfd/ospf_api.h" #include "ospf_apiclient.h" /* *sigh* ... can't find a better way to hammer this into automake */ #include "ospfd/ospf_dump_api.c" #include "ospfd/ospf_api.c" DEFINE_MGROUP(OSPFCLIENT, "libospfapiclient") DEFINE_MTYPE_STATIC(OSPFCLIENT, OSPF_APICLIENT, "OSPF-API client") /* Backlog for listen */ #define BACKLOG 5 /* ----------------------------------------------------------- * Forward declarations * ----------------------------------------------------------- */ void ospf_apiclient_handle_reply(struct ospf_apiclient *oclient, struct msg *msg); void ospf_apiclient_handle_update_notify(struct ospf_apiclient *oclient, struct msg *msg); void ospf_apiclient_handle_delete_notify(struct ospf_apiclient *oclient, struct msg *msg); /* ----------------------------------------------------------- * Initialization * ----------------------------------------------------------- */ static unsigned short ospf_apiclient_getport(void) { struct servent *sp = getservbyname("ospfapi", "tcp"); return sp ? ntohs(sp->s_port) : OSPF_API_SYNC_PORT; } /* ----------------------------------------------------------- * Followings are functions for connection management * ----------------------------------------------------------- */ struct ospf_apiclient *ospf_apiclient_connect(char *host, int syncport) { struct sockaddr_in myaddr_sync; struct sockaddr_in myaddr_async; struct sockaddr_in peeraddr; struct hostent *hp; struct ospf_apiclient *new; int size = 0; unsigned int peeraddrlen; int async_server_sock; int fd1, fd2; int ret; int on = 1; /* There are two connections between the client and the server. First the client opens a connection for synchronous requests/replies to the server. The server will accept this connection and as a reaction open a reverse connection channel for asynchronous messages. */ async_server_sock = socket(AF_INET, SOCK_STREAM, 0); if (async_server_sock < 0) { fprintf(stderr, "ospf_apiclient_connect: creating async socket failed\n"); return NULL; } /* Prepare socket for asynchronous messages */ /* Initialize async address structure */ memset(&myaddr_async, 0, sizeof(struct sockaddr_in)); myaddr_async.sin_family = AF_INET; myaddr_async.sin_addr.s_addr = htonl(INADDR_ANY); myaddr_async.sin_port = htons(syncport + 1); size = sizeof(struct sockaddr_in); #ifdef HAVE_STRUCT_SOCKADDR_IN_SIN_LEN myaddr_async.sin_len = size; #endif /* HAVE_STRUCT_SOCKADDR_IN_SIN_LEN */ /* This is a server socket, reuse addr and port */ ret = setsockopt(async_server_sock, SOL_SOCKET, SO_REUSEADDR, (void *)&on, sizeof(on)); if (ret < 0) { fprintf(stderr, "ospf_apiclient_connect: SO_REUSEADDR failed\n"); close(async_server_sock); return NULL; } #ifdef SO_REUSEPORT ret = setsockopt(async_server_sock, SOL_SOCKET, SO_REUSEPORT, (void *)&on, sizeof(on)); if (ret < 0) { fprintf(stderr, "ospf_apiclient_connect: SO_REUSEPORT failed\n"); close(async_server_sock); return NULL; } #endif /* SO_REUSEPORT */ /* Bind socket to address structure */ ret = bind(async_server_sock, (struct sockaddr *)&myaddr_async, size); if (ret < 0) { fprintf(stderr, "ospf_apiclient_connect: bind async socket failed\n"); close(async_server_sock); return NULL; } /* Wait for reverse channel connection establishment from server */ ret = listen(async_server_sock, BACKLOG); if (ret < 0) { fprintf(stderr, "ospf_apiclient_connect: listen: %s\n", safe_strerror(errno)); close(async_server_sock); return NULL; } /* Make connection for synchronous requests and connect to server */ /* Resolve address of server */ hp = gethostbyname(host); if (!hp) { fprintf(stderr, "ospf_apiclient_connect: no such host %s\n", host); close(async_server_sock); return NULL; } fd1 = socket(AF_INET, SOCK_STREAM, 0); if (fd1 < 0) { close(async_server_sock); fprintf(stderr, "ospf_apiclient_connect: creating sync socket failed\n"); return NULL; } /* Reuse addr and port */ ret = setsockopt(fd1, SOL_SOCKET, SO_REUSEADDR, (void *)&on, sizeof(on)); if (ret < 0) { fprintf(stderr, "ospf_apiclient_connect: SO_REUSEADDR failed\n"); close(fd1); close(async_server_sock); return NULL; } #ifdef SO_REUSEPORT ret = setsockopt(fd1, SOL_SOCKET, SO_REUSEPORT, (void *)&on, sizeof(on)); if (ret < 0) { fprintf(stderr, "ospf_apiclient_connect: SO_REUSEPORT failed\n"); close(fd1); close(async_server_sock); return NULL; } #endif /* SO_REUSEPORT */ /* Bind sync socket to address structure. This is needed since we want the sync port number on a fixed port number. The reverse async channel will be at this port+1 */ memset(&myaddr_sync, 0, sizeof(struct sockaddr_in)); myaddr_sync.sin_family = AF_INET; myaddr_sync.sin_port = htons(syncport); #ifdef HAVE_STRUCT_SOCKADDR_IN_SIN_LEN myaddr_sync.sin_len = sizeof(struct sockaddr_in); #endif /* HAVE_STRUCT_SOCKADDR_IN_SIN_LEN */ ret = bind(fd1, (struct sockaddr *)&myaddr_sync, size); if (ret < 0) { fprintf(stderr, "ospf_apiclient_connect: bind sync socket failed\n"); close(fd1); close(async_server_sock); return NULL; } /* Prepare address structure for connect */ memcpy(&myaddr_sync.sin_addr, hp->h_addr, hp->h_length); myaddr_sync.sin_family = AF_INET; myaddr_sync.sin_port = htons(ospf_apiclient_getport()); #ifdef HAVE_STRUCT_SOCKADDR_IN_SIN_LEN myaddr_sync.sin_len = sizeof(struct sockaddr_in); #endif /* HAVE_STRUCT_SOCKADDR_IN_SIN_LEN */ /* Now establish synchronous channel with OSPF daemon */ ret = connect(fd1, (struct sockaddr *)&myaddr_sync, sizeof(struct sockaddr_in)); if (ret < 0) { fprintf(stderr, "ospf_apiclient_connect: sync connect failed\n"); close(async_server_sock); close(fd1); return NULL; } /* Accept reverse connection */ peeraddrlen = sizeof(struct sockaddr_in); memset(&peeraddr, 0, peeraddrlen); fd2 = accept(async_server_sock, (struct sockaddr *)&peeraddr, &peeraddrlen); if (fd2 < 0) { fprintf(stderr, "ospf_apiclient_connect: accept async failed\n"); close(async_server_sock); close(fd1); close(fd2); return NULL; } /* Server socket is not needed anymore since we are not accepting more connections */ close(async_server_sock); /* Create new client-side instance */ new = XCALLOC(MTYPE_OSPF_APICLIENT, sizeof(struct ospf_apiclient)); /* Initialize socket descriptors for sync and async channels */ new->fd_sync = fd1; new->fd_async = fd2; return new; } int ospf_apiclient_close(struct ospf_apiclient *oclient) { if (oclient->fd_sync >= 0) { close(oclient->fd_sync); } if (oclient->fd_async >= 0) { close(oclient->fd_async); } /* Free client structure */ XFREE(MTYPE_OSPF_APICLIENT, oclient); return 0; } /* ----------------------------------------------------------- * Followings are functions to send a request to OSPFd * ----------------------------------------------------------- */ /* Send synchronous request, wait for reply */ static int ospf_apiclient_send_request(struct ospf_apiclient *oclient, struct msg *msg) { uint32_t reqseq; struct msg_reply *msgreply; int rc; /* NB: Given "msg" is freed inside this function. */ /* Remember the sequence number of the request */ reqseq = ntohl(msg->hdr.msgseq); /* Write message to OSPFd */ rc = msg_write(oclient->fd_sync, msg); msg_free(msg); if (rc < 0) { return -1; } /* Wait for reply */ /* NB: New "msg" is allocated by "msg_read()". */ msg = msg_read(oclient->fd_sync); if (!msg) return -1; assert(msg->hdr.msgtype == MSG_REPLY); assert(ntohl(msg->hdr.msgseq) == reqseq); msgreply = (struct msg_reply *)STREAM_DATA(msg->s); rc = msgreply->errcode; msg_free(msg); return rc; } /* ----------------------------------------------------------- * Helper functions * ----------------------------------------------------------- */ static uint32_t ospf_apiclient_get_seqnr(void) { static uint32_t seqnr = MIN_SEQ; uint32_t tmp; tmp = seqnr; /* Increment sequence number */ if (seqnr < MAX_SEQ) { seqnr++; } else { seqnr = MIN_SEQ; } return tmp; } /* ----------------------------------------------------------- * API to access OSPF daemon by client applications. * ----------------------------------------------------------- */ /* * Synchronous request to register opaque type. */ int ospf_apiclient_register_opaque_type(struct ospf_apiclient *cl, uint8_t ltype, uint8_t otype) { struct msg *msg; int rc; /* just put 1 as a sequence number. */ msg = new_msg_register_opaque_type(ospf_apiclient_get_seqnr(), ltype, otype); if (!msg) { fprintf(stderr, "new_msg_register_opaque_type failed\n"); return -1; } rc = ospf_apiclient_send_request(cl, msg); return rc; } /* * Synchronous request to synchronize with OSPF's LSDB. * Two steps required: register_event in order to get * dynamic updates and LSDB_Sync. */ int ospf_apiclient_sync_lsdb(struct ospf_apiclient *oclient) { struct msg *msg; int rc; struct lsa_filter_type filter; filter.typemask = 0xFFFF; /* all LSAs */ filter.origin = ANY_ORIGIN; filter.num_areas = 0; /* all Areas. */ msg = new_msg_register_event(ospf_apiclient_get_seqnr(), &filter); if (!msg) { fprintf(stderr, "new_msg_register_event failed\n"); return -1; } rc = ospf_apiclient_send_request(oclient, msg); if (rc != 0) goto out; msg = new_msg_sync_lsdb(ospf_apiclient_get_seqnr(), &filter); if (!msg) { fprintf(stderr, "new_msg_sync_lsdb failed\n"); return -1; } rc = ospf_apiclient_send_request(oclient, msg); out: return rc; } /* * Synchronous request to originate or update an LSA. */ int ospf_apiclient_lsa_originate(struct ospf_apiclient *oclient, struct in_addr ifaddr, struct in_addr area_id, uint8_t lsa_type, uint8_t opaque_type, uint32_t opaque_id, void *opaquedata, int opaquelen) { struct msg *msg; int rc; uint8_t buf[OSPF_MAX_LSA_SIZE]; struct lsa_header *lsah; uint32_t tmp; /* We can only originate opaque LSAs */ if (!IS_OPAQUE_LSA(lsa_type)) { fprintf(stderr, "Cannot originate non-opaque LSA type %d\n", lsa_type); return OSPF_API_ILLEGALLSATYPE; } /* Make a new LSA from parameters */ lsah = (struct lsa_header *)buf; lsah->ls_age = 0; lsah->options = 0; lsah->type = lsa_type; tmp = SET_OPAQUE_LSID(opaque_type, opaque_id); lsah->id.s_addr = htonl(tmp); lsah->adv_router.s_addr = 0; lsah->ls_seqnum = 0; lsah->checksum = 0; lsah->length = htons(sizeof(struct lsa_header) + opaquelen); memcpy(((uint8_t *)lsah) + sizeof(struct lsa_header), opaquedata, opaquelen); msg = new_msg_originate_request(ospf_apiclient_get_seqnr(), ifaddr, area_id, lsah); if (!msg) { fprintf(stderr, "new_msg_originate_request failed\n"); return OSPF_API_NOMEMORY; } rc = ospf_apiclient_send_request(oclient, msg); return rc; } int ospf_apiclient_lsa_delete(struct ospf_apiclient *oclient, struct in_addr area_id, uint8_t lsa_type, uint8_t opaque_type, uint32_t opaque_id) { struct msg *msg; int rc; /* Only opaque LSA can be deleted */ if (!IS_OPAQUE_LSA(lsa_type)) { fprintf(stderr, "Cannot delete non-opaque LSA type %d\n", lsa_type); return OSPF_API_ILLEGALLSATYPE; } /* opaque_id is in host byte order and will be converted * to network byte order by new_msg_delete_request */ msg = new_msg_delete_request(ospf_apiclient_get_seqnr(), area_id, lsa_type, opaque_type, opaque_id); rc = ospf_apiclient_send_request(oclient, msg); return rc; } /* ----------------------------------------------------------- * Followings are handlers for messages from OSPF daemon * ----------------------------------------------------------- */ static void ospf_apiclient_handle_ready(struct ospf_apiclient *oclient, struct msg *msg) { struct msg_ready_notify *r; r = (struct msg_ready_notify *)STREAM_DATA(msg->s); /* Invoke registered callback function. */ if (oclient->ready_notify) { (oclient->ready_notify)(r->lsa_type, r->opaque_type, r->addr); } } static void ospf_apiclient_handle_new_if(struct ospf_apiclient *oclient, struct msg *msg) { struct msg_new_if *n; n = (struct msg_new_if *)STREAM_DATA(msg->s); /* Invoke registered callback function. */ if (oclient->new_if) { (oclient->new_if)(n->ifaddr, n->area_id); } } static void ospf_apiclient_handle_del_if(struct ospf_apiclient *oclient, struct msg *msg) { struct msg_del_if *d; d = (struct msg_del_if *)STREAM_DATA(msg->s); /* Invoke registered callback function. */ if (oclient->del_if) { (oclient->del_if)(d->ifaddr); } } static void ospf_apiclient_handle_ism_change(struct ospf_apiclient *oclient, struct msg *msg) { struct msg_ism_change *m; m = (struct msg_ism_change *)STREAM_DATA(msg->s); /* Invoke registered callback function. */ if (oclient->ism_change) { (oclient->ism_change)(m->ifaddr, m->area_id, m->status); } } static void ospf_apiclient_handle_nsm_change(struct ospf_apiclient *oclient, struct msg *msg) { struct msg_nsm_change *m; m = (struct msg_nsm_change *)STREAM_DATA(msg->s); /* Invoke registered callback function. */ if (oclient->nsm_change) { (oclient->nsm_change)(m->ifaddr, m->nbraddr, m->router_id, m->status); } } static void ospf_apiclient_handle_lsa_update(struct ospf_apiclient *oclient, struct msg *msg) { struct msg_lsa_change_notify *cn; struct lsa_header *lsa; int lsalen; cn = (struct msg_lsa_change_notify *)STREAM_DATA(msg->s); /* Extract LSA from message */ lsalen = ntohs(cn->data.length); lsa = XMALLOC(MTYPE_OSPF_APICLIENT, lsalen); memcpy(lsa, &(cn->data), lsalen); /* Invoke registered update callback function */ if (oclient->update_notify) { (oclient->update_notify)(cn->ifaddr, cn->area_id, cn->is_self_originated, lsa); } /* free memory allocated by ospf apiclient library */ XFREE(MTYPE_OSPF_APICLIENT, lsa); } static void ospf_apiclient_handle_lsa_delete(struct ospf_apiclient *oclient, struct msg *msg) { struct msg_lsa_change_notify *cn; struct lsa_header *lsa; int lsalen; cn = (struct msg_lsa_change_notify *)STREAM_DATA(msg->s); /* Extract LSA from message */ lsalen = ntohs(cn->data.length); lsa = XMALLOC(MTYPE_OSPF_APICLIENT, lsalen); memcpy(lsa, &(cn->data), lsalen); /* Invoke registered update callback function */ if (oclient->delete_notify) { (oclient->delete_notify)(cn->ifaddr, cn->area_id, cn->is_self_originated, lsa); } /* free memory allocated by ospf apiclient library */ XFREE(MTYPE_OSPF_APICLIENT, lsa); } static void ospf_apiclient_msghandle(struct ospf_apiclient *oclient, struct msg *msg) { /* Call message handler function. */ switch (msg->hdr.msgtype) { case MSG_READY_NOTIFY: ospf_apiclient_handle_ready(oclient, msg); break; case MSG_NEW_IF: ospf_apiclient_handle_new_if(oclient, msg); break; case MSG_DEL_IF: ospf_apiclient_handle_del_if(oclient, msg); break; case MSG_ISM_CHANGE: ospf_apiclient_handle_ism_change(oclient, msg); break; case MSG_NSM_CHANGE: ospf_apiclient_handle_nsm_change(oclient, msg); break; case MSG_LSA_UPDATE_NOTIFY: ospf_apiclient_handle_lsa_update(oclient, msg); break; case MSG_LSA_DELETE_NOTIFY: ospf_apiclient_handle_lsa_delete(oclient, msg); break; default: fprintf(stderr, "ospf_apiclient_read: Unknown message type: %d\n", msg->hdr.msgtype); break; } } /* ----------------------------------------------------------- * Callback handler registration * ----------------------------------------------------------- */ void ospf_apiclient_register_callback( struct ospf_apiclient *oclient, void (*ready_notify)(uint8_t lsa_type, uint8_t opaque_type, struct in_addr addr), void (*new_if)(struct in_addr ifaddr, struct in_addr area_id), void (*del_if)(struct in_addr ifaddr), void (*ism_change)(struct in_addr ifaddr, struct in_addr area_id, uint8_t status), void (*nsm_change)(struct in_addr ifaddr, struct in_addr nbraddr, struct in_addr router_id, uint8_t status), void (*update_notify)(struct in_addr ifaddr, struct in_addr area_id, uint8_t self_origin, struct lsa_header *lsa), void (*delete_notify)(struct in_addr ifaddr, struct in_addr area_id, uint8_t self_origin, struct lsa_header *lsa)) { assert(oclient); assert(update_notify); /* Register callback function */ oclient->ready_notify = ready_notify; oclient->new_if = new_if; oclient->del_if = del_if; oclient->ism_change = ism_change; oclient->nsm_change = nsm_change; oclient->update_notify = update_notify; oclient->delete_notify = delete_notify; } /* ----------------------------------------------------------- * Asynchronous message handling * ----------------------------------------------------------- */ int ospf_apiclient_handle_async(struct ospf_apiclient *oclient) { struct msg *msg; /* Get a message */ msg = msg_read(oclient->fd_async); if (!msg) { /* Connection broke down */ return -1; } /* Handle message */ ospf_apiclient_msghandle(oclient, msg); /* Don't forget to free this message */ msg_free(msg); return 0; } frr-7.2.1/ospfclient/ospf_apiclient.h0000644000000000000000000000772313610377563014543 00000000000000/* * Client side of OSPF API. * Copyright (C) 2001, 2002, 2003 Ralph Keller * * This file is part of GNU Zebra. * * GNU Zebra is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License as published * by the Free Software Foundation; either version 2, or (at your * option) any later version. * * GNU Zebra 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; see the file COPYING; if not, write to the Free Software * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA */ #ifndef _OSPF_APICLIENT_H #define _OSPF_APICLIENT_H /* Structure for the OSPF API client */ struct ospf_apiclient { /* Sockets for sync requests and async notifications */ int fd_sync; int fd_async; /* Pointer to callback functions */ void (*ready_notify)(uint8_t lsa_type, uint8_t opaque_type, struct in_addr addr); void (*new_if)(struct in_addr ifaddr, struct in_addr area_id); void (*del_if)(struct in_addr ifaddr); void (*ism_change)(struct in_addr ifaddr, struct in_addr area_id, uint8_t status); void (*nsm_change)(struct in_addr ifaddr, struct in_addr nbraddr, struct in_addr router_id, uint8_t status); void (*update_notify)(struct in_addr ifaddr, struct in_addr area_id, uint8_t self_origin, struct lsa_header *lsa); void (*delete_notify)(struct in_addr ifaddr, struct in_addr area_id, uint8_t self_origin, struct lsa_header *lsa); }; /* --------------------------------------------------------- * API function prototypes. * --------------------------------------------------------- */ /* Open connection to OSPF daemon. Two ports will be allocated on client, sync channel at syncport and reverse channel at syncport+1 */ struct ospf_apiclient *ospf_apiclient_connect(char *host, int syncport); /* Shutdown connection to OSPF daemon. */ int ospf_apiclient_close(struct ospf_apiclient *oclient); /* Synchronous request to register opaque type. */ int ospf_apiclient_register_opaque_type(struct ospf_apiclient *oclient, uint8_t ltype, uint8_t otype); /* Synchronous request to register event mask. */ int ospf_apiclient_register_events(struct ospf_apiclient *oclient, uint32_t mask); /* Register callback functions.*/ void ospf_apiclient_register_callback( struct ospf_apiclient *oclient, void (*ready_notify)(uint8_t lsa_type, uint8_t opaque_type, struct in_addr addr), void (*new_if)(struct in_addr ifaddr, struct in_addr area_id), void (*del_if)(struct in_addr ifaddr), void (*ism_change)(struct in_addr ifaddr, struct in_addr area_id, uint8_t status), void (*nsm_change)(struct in_addr ifaddr, struct in_addr nbraddr, struct in_addr router_id, uint8_t status), void (*update_notify)(struct in_addr ifaddr, struct in_addr area_id, uint8_t selforig, struct lsa_header *lsa), void (*delete_notify)(struct in_addr ifaddr, struct in_addr area_id, uint8_t selforig, struct lsa_header *lsa)); /* Synchronous request to synchronize LSDB. */ int ospf_apiclient_sync_lsdb(struct ospf_apiclient *oclient); /* Synchronous request to originate or update opaque LSA. */ int ospf_apiclient_lsa_originate(struct ospf_apiclient *oclient, struct in_addr ifaddr, struct in_addr area_id, uint8_t lsa_type, uint8_t opaque_type, uint32_t opaque_id, void *opaquedata, int opaquelen); /* Synchronous request to delete opaque LSA. Parameter opaque_id is in host byte order */ int ospf_apiclient_lsa_delete(struct ospf_apiclient *oclient, struct in_addr area_id, uint8_t lsa_type, uint8_t opaque_type, uint32_t opaque_id); /* Fetch async message and handle it */ int ospf_apiclient_handle_async(struct ospf_apiclient *oclient); #endif /* _OSPF_APICLIENT_H */ frr-7.2.1/ospfclient/ospfclient.c0000644000000000000000000002314413610377563013700 00000000000000/* This file is part of Quagga. * * Quagga is free software; you can redistribute it and/or modify it * under the terms of the GNU General Public License as published by the * Free Software Foundation; either version 2, or (at your option) any * later version. * * Quagga 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; see the file COPYING; if not, write to the Free Software * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA */ /* * Simple program to demonstrate how OSPF API can be used. This * application retrieves the LSDB from the OSPF daemon and then * originates, updates and finally deletes an application-specific * opaque LSA. You can use this application as a template when writing * your own application. */ /* The following includes are needed in all OSPF API client applications. */ #include #include "prefix.h" /* needed by ospf_asbr.h */ #include "privs.h" #include "log.h" /* work around gcc bug 69981, disable MTYPEs in libospf */ #define _QUAGGA_OSPF_MEMORY_H #include "ospfd/ospfd.h" #include "ospfd/ospf_asbr.h" #include "ospfd/ospf_lsa.h" #include "ospfd/ospf_opaque.h" #include "ospfd/ospf_api.h" #include "ospf_apiclient.h" /* privileges struct. * set cap_num_* and uid/gid to nothing to use NULL privs * as ospfapiclient links in libospf.a which uses privs. */ struct zebra_privs_t ospfd_privs = {.user = NULL, .group = NULL, .cap_num_p = 0, .cap_num_i = 0}; /* The following includes are specific to this application. For example it uses threads from libfrr, however your application is free to use any thread library (like pthreads). */ #include "ospfd/ospf_dump.h" /* for ospf_lsa_header_dump */ #include "thread.h" #include "log.h" /* Local portnumber for async channel. Note that OSPF API library will also allocate a sync channel at ASYNCPORT+1. */ #define ASYNCPORT 4000 /* Master thread */ struct thread_master *master; /* Global variables */ struct ospf_apiclient *oclient; char **args; /* Our opaque LSAs have the following format. */ struct my_opaque_lsa { struct lsa_header hdr; /* include common LSA header */ uint8_t data[4]; /* our own data format then follows here */ }; /* --------------------------------------------------------- * Threads for asynchronous messages and LSA update/delete * --------------------------------------------------------- */ static int lsa_delete(struct thread *t) { struct ospf_apiclient *oclient; struct in_addr area_id; int rc; oclient = THREAD_ARG(t); rc = inet_aton(args[6], &area_id); if (rc <= 0) { printf("Address Specified: %s is invalid\n", args[6]); return rc; } printf("Deleting LSA... "); rc = ospf_apiclient_lsa_delete(oclient, area_id, atoi(args[2]), /* lsa type */ atoi(args[3]), /* opaque type */ atoi(args[4])); /* opaque ID */ printf("done, return code is = %d\n", rc); return rc; } static int lsa_inject(struct thread *t) { struct ospf_apiclient *cl; struct in_addr ifaddr; struct in_addr area_id; uint8_t lsa_type; uint8_t opaque_type; uint32_t opaque_id; void *opaquedata; int opaquelen; static uint32_t counter = 1; /* Incremented each time invoked */ int rc; cl = THREAD_ARG(t); rc = inet_aton(args[5], &ifaddr); if (rc <= 0) { printf("Ifaddr specified %s is invalid\n", args[5]); return rc; } rc = inet_aton(args[6], &area_id); if (rc <= 0) { printf("Area ID specified %s is invalid\n", args[6]); return rc; } lsa_type = atoi(args[2]); opaque_type = atoi(args[3]); opaque_id = atoi(args[4]); opaquedata = &counter; opaquelen = sizeof(uint32_t); printf("Originating/updating LSA with counter=%d... ", counter); rc = ospf_apiclient_lsa_originate(cl, ifaddr, area_id, lsa_type, opaque_type, opaque_id, opaquedata, opaquelen); printf("done, return code is %d\n", rc); counter++; return 0; } /* This thread handles asynchronous messages coming in from the OSPF API server */ static int lsa_read(struct thread *thread) { struct ospf_apiclient *oclient; int fd; int ret; printf("lsa_read called\n"); oclient = THREAD_ARG(thread); fd = THREAD_FD(thread); /* Handle asynchronous message */ ret = ospf_apiclient_handle_async(oclient); if (ret < 0) { printf("Connection closed, exiting..."); exit(0); } /* Reschedule read thread */ thread_add_read(master, lsa_read, oclient, fd, NULL); return 0; } /* --------------------------------------------------------- * Callback functions for asynchronous events * --------------------------------------------------------- */ static void lsa_update_callback(struct in_addr ifaddr, struct in_addr area_id, uint8_t is_self_originated, struct lsa_header *lsa) { printf("lsa_update_callback: "); printf("ifaddr: %s ", inet_ntoa(ifaddr)); printf("area: %s\n", inet_ntoa(area_id)); printf("is_self_origin: %u\n", is_self_originated); /* It is important to note that lsa_header does indeed include the header and the LSA payload. To access the payload, first check the LSA type and then typecast lsa into the corresponding type, e.g.: if (lsa->type == OSPF_ROUTER_LSA) { struct router_lsa *rl = (struct router_lsa) lsa; ... uint16_t links = rl->links; ... } */ ospf_lsa_header_dump(lsa); } static void lsa_delete_callback(struct in_addr ifaddr, struct in_addr area_id, uint8_t is_self_originated, struct lsa_header *lsa) { printf("lsa_delete_callback: "); printf("ifaddr: %s ", inet_ntoa(ifaddr)); printf("area: %s\n", inet_ntoa(area_id)); printf("is_self_origin: %u\n", is_self_originated); ospf_lsa_header_dump(lsa); } static void ready_callback(uint8_t lsa_type, uint8_t opaque_type, struct in_addr addr) { printf("ready_callback: lsa_type: %d opaque_type: %d addr=%s\n", lsa_type, opaque_type, inet_ntoa(addr)); /* Schedule opaque LSA originate in 5 secs */ thread_add_timer(master, lsa_inject, oclient, 5, NULL); /* Schedule opaque LSA update with new value */ thread_add_timer(master, lsa_inject, oclient, 10, NULL); /* Schedule delete */ thread_add_timer(master, lsa_delete, oclient, 30, NULL); } static void new_if_callback(struct in_addr ifaddr, struct in_addr area_id) { printf("new_if_callback: ifaddr: %s ", inet_ntoa(ifaddr)); printf("area_id: %s\n", inet_ntoa(area_id)); } static void del_if_callback(struct in_addr ifaddr) { printf("new_if_callback: ifaddr: %s\n ", inet_ntoa(ifaddr)); } static void ism_change_callback(struct in_addr ifaddr, struct in_addr area_id, uint8_t state) { printf("ism_change: ifaddr: %s ", inet_ntoa(ifaddr)); printf("area_id: %s\n", inet_ntoa(area_id)); printf("state: %d [%s]\n", state, lookup_msg(ospf_ism_state_msg, state, NULL)); } static void nsm_change_callback(struct in_addr ifaddr, struct in_addr nbraddr, struct in_addr router_id, uint8_t state) { printf("nsm_change: ifaddr: %s ", inet_ntoa(ifaddr)); printf("nbraddr: %s\n", inet_ntoa(nbraddr)); printf("router_id: %s\n", inet_ntoa(router_id)); printf("state: %d [%s]\n", state, lookup_msg(ospf_nsm_state_msg, state, NULL)); } /* --------------------------------------------------------- * Main program * --------------------------------------------------------- */ static int usage(void) { printf("Usage: ospfclient \n"); printf("where ospfd : router where API-enabled OSPF daemon is running\n"); printf(" lsatype : either 9, 10, or 11 depending on flooding scope\n"); printf(" opaquetype: 0-255 (e.g., experimental applications use > 128)\n"); printf(" opaqueid : arbitrary application instance (24 bits)\n"); printf(" ifaddr : interface IP address (for type 9) otherwise ignored\n"); printf(" areaid : area in IP address format (for type 10) otherwise ignored\n"); exit(1); } int main(int argc, char *argv[]) { struct thread thread; args = argv; /* ospfclient should be started with the following arguments: * * (1) host (2) lsa_type (3) opaque_type (4) opaque_id (5) if_addr * (6) area_id * * host: name or IP of host where ospfd is running * lsa_type: 9, 10, or 11 * opaque_type: 0-255 (e.g., experimental applications use > 128) * opaque_id: arbitrary application instance (24 bits) * if_addr: interface IP address (for type 9) otherwise ignored * area_id: area in IP address format (for type 10) otherwise ignored */ if (argc != 7) { usage(); } /* Initialization */ zprivs_preinit(&ospfd_privs); zprivs_init(&ospfd_privs); master = thread_master_create(NULL); /* Open connection to OSPF daemon */ oclient = ospf_apiclient_connect(args[1], ASYNCPORT); if (!oclient) { printf("Connecting to OSPF daemon on %s failed!\n", args[1]); exit(1); } /* Register callback functions. */ ospf_apiclient_register_callback( oclient, ready_callback, new_if_callback, del_if_callback, ism_change_callback, nsm_change_callback, lsa_update_callback, lsa_delete_callback); /* Register LSA type and opaque type. */ ospf_apiclient_register_opaque_type(oclient, atoi(args[2]), atoi(args[3])); /* Synchronize database with OSPF daemon. */ ospf_apiclient_sync_lsdb(oclient); /* Schedule thread that handles asynchronous messages */ thread_add_read(master, lsa_read, oclient, oclient->fd_async, NULL); /* Now connection is established, run loop */ while (1) { thread_fetch(master, &thread); thread_call(&thread); } /* Never reached */ return 0; } frr-7.2.1/ospfclient/subdir.am0000644000000000000000000000221713610377563013173 00000000000000# # ospfclient # if OSPFCLIENT lib_LTLIBRARIES += ospfclient/libfrrospfapiclient.la noinst_PROGRAMS += ospfclient/ospfclient #man8 += $(MANBUILD)/frr-ospfclient.8 endif ospfclient_libfrrospfapiclient_la_LDFLAGS = -version-info 0:0:0 ospfclient_libfrrospfapiclient_la_LIBADD = lib/libfrr.la ospfclient_libfrrospfapiclient_la_SOURCES = \ ospfclient/ospf_apiclient.c \ # end if OSPFCLIENT ospfapiheaderdir = $(pkgincludedir)/ospfapi ospfapiheader_HEADERS = \ ospfclient/ospf_apiclient.h \ # end endif ospfclient_ospfclient_LDADD = \ ospfclient/libfrrospfapiclient.la \ $(LIBCAP) \ # end if STATIC_BIN # libfrr is linked in through libfrrospfapiclient. If we list it here too, # it gets linked twice and we get a ton of symbol collisions. else # !STATIC_BIN # For most systems we don't need this, except Debian, who patch their linker # to disallow transitive references *while* *als* not patching their libtool # to work appropriately. RedHat has the same linker behaviour, but things # work as expected since they also patch libtool. ospfclient_ospfclient_LDADD += lib/libfrr.la endif ospfclient_ospfclient_SOURCES = \ ospfclient/ospfclient.c \ # end frr-7.2.1/ospfd/0000755000000000000000000000000013610377563010407 500000000000000frr-7.2.1/ospfd/ChangeLog.opaque.txt0000644000000000000000000002305013610377563014210 00000000000000----- * ----- * ----- * ----- * ----- * ----- * ----- * ----- * ----- * ----- Changes 2002.12.20 1. Bug fixes 1.1 When an opaque LSA is being removed from (or added to) the LSDB, it does not mean a change in network topology. Therefore, SPF recalculation should not be triggered in that case. There was an assertion failure problem "assert (rn && rn->info)" inside the function "ospf_ase_incremental_update()", because the upper function "ospf_lsa_maxage_walker_remover()" called it when a type-11 opaque LSA is removed due to MaxAge. 1.2 Type-9 LSA is defined to have "link-local" flooding scope. In the Database exchange procedure with a new neighbor, a type-9 LSA was added in the database summary of a DD message, even if the link is different from the one that have bound to. 2. Feature enhancements 2.1 Though a "wildcard" concept to handle type-9/10/11 LSAs altogether has introduced about a year ago, it was only a symbol definition and actual handling mechanism was not implemented. Now it works. ----- * ----- * ----- * ----- * ----- * ----- * ----- * ----- * ----- * ----- Changes 2002.7.8 1. Bug fixes 1.1 When "ospf_delete_opaque_functab()" is called, internal structure "oipt" remain unfreed. If register/delete functab is repeated, illegal memory access happens due to this "oipt". 1.2 In "free_opaque_info_per_id()", there was a crucial typo which ignores a condition test. "if (oipi->lsa != NULL);" <-- semicolon! 2. Feature enhancements None. ----- * ----- * ----- * ----- * ----- * ----- * ----- * ----- * ----- * ----- Changes 2001.12.03 1. Bug fixes 1.1 Though a new member "oi" has added to "struct ospf_lsa" to control flooding scope of type-9 Opaque-LSAs, the value was always NULL because no one set it. 1.2 In the function "show_ip_ospf_database_summary()" and "show_lsa_ detail_adv_router()", VTY output for type-11 Opaque-LSAs did not work properly. 1.3 URL for the opaque-type assignment reference has changed. 1.4 In the file "ospf_mpls_te.c", printf formats have changed to avoid compiler warning messages; "%lu" -> "%u", "%lx" -> "%x". Note that this hack depends on OS, compiler and their versions. 1.5 One of attached documentation "opaque_lsa.txt" has changed to reflect the latest coding. 2. Feature enhancements 2.1 Knowing that it is an ugly hack, an "officially unallocated" opaque-type value 0 has newly introduced as a "wildcard", which matches to all opaque-type. This value must not be flooded to the network, of course. 2.2 The Opaque-core module makes use of newly introduced hooks to dispatch every LSDB change (LSA installation and deletion) to preregistered opaque users. Therefore, by providing appropriate callback functions as new parameters of "ospf_register_opaque_functab()", an opaque user can refer to every LSA instance to be installed into, or to be deleted from, the LSDB. ----- * ----- * ----- * ----- * ----- * ----- * ----- * ----- * ----- * ----- Changes 2001.10.31 1. Bug fixes 1.1 Since each LSA has their own lifetime, they will remain in a routing domain (being stored in LSDB of each router), until their age naturally reach to MaxAge or explicitly being flushed by the originated router. Therefore, if a router restarted with a short downtime, it is possible that previously flooded self-originated LSAs might received if the NSM status is not less than Exchange. There were some problems in the way of handling self-originated Opaque-LSAs if they are contained in a received LSUpd message, but not installed to the local LSDB yet. Regardless of some conditions to start originating Opaque-LSAs (there should be at least one opaque-capable full-state neighbor), the function "ospf_flood()" will be called to flood and install this brand-new looking LSA. As the result, when the NSM of an opaque-capable neighbor gets full, internal state inconsistency happens; a user of Opaque-LSA such as MPLS-TE can refer to self-originated LSAs in the local LSDB, but cannot modify their contents... Above problems have fixed with a policy "flush it from the whole routing domain and keep silent until the flushing completed". By using this sweeping technique, we can be free from confusion caused by self-originated LSAs received via network. 1.2 The function "ospf_opaque_type_name()" contained massive ifdefs corresponding to each "opaque-type". These unnecessary ifdefs are removed completely. 1.3 In the function "ospf_delete_opaque_functab()", there was an improper loop control that causes illegal memory access. Original coding was "next = nextnode (node)". 1.4 The function "ospf_mpls_te_ism_change()" could not handle the case when the ISM changes from Waiting to DR/BDR/Other. So, there was a case that even if one of an ISM become operational and MPLS-TE module has started, the corresponding Opaque-LSA cannot be originated. 1.5 The function "ospf_opaque_lsa_reoriginate_schedule()" did not allow to be called multiple times, simply because handling module for the given "lsa-type & opaque-type" already exists. But this assumption seems to be wrong. Change the policy to allow this function to be called multiple times and let the caller to decide what should do when the corresponding callback function "(* functab->lsa_originator)()" is called. 2. Feature enhancements 2.1 The global bitmap "opaque" has introduced instead of former flag "OpaqueCapable", to store complex conditions to handle Opaque-LSAs. 2.2 The MPLS-TE module now referes to "draft-katz-yeung-ospf-traffic -06.txt", no significant changes with 05 version, though. ----- * ----- * ----- * ----- * ----- * ----- * ----- * ----- * ----- * ----- Changes 2001.08.03 1. Bug fixes 1.1 Even if the ospfd started with opaque capability enabled, when the ospfd receives an unknown opaque-type (unregistered by the function "ospf_register_opaque_functab()" beforehand), the LSA was discarded. As the result, only the opaque-LSAs that have commonly registered by opaque-capable ospf routers can be flooded in a routing domain. This behavior has fixed so that arbitrary opaque-type LSAs can be flooded among opaque-capable ospf routers. If the ospfd has opaque-LSA capability but disabled at runtime, received opaque-LSAs can be accepted and registered to LSDB as is, but not be flooded to the network; those opaque LSAs will remain in LSDB until explicitly flushed by incoming LSUpd messages with MaxAge, or their age naturally reaches to MaxAge. 1.2 The function "ospf_register_opaque_functab()" did not check if the entry corresponding to the given "lsa-type, opaque-type" combination already exists or not. This problem has fixed not to allow multiple registration. 1.3 Since type-11 (AS external) LSAs will be flooded beyond areas, there is little relationship between "struct lsa" and "struct area". More specifically, the pointer address "lsa->area" can be NULL if the lsa-type is 11, thus an illegal memory access will happen. This problem has fixed. 1.4 When self-originated opaque-LSAs are received via network and if the corresponding opaque-type functions are not available (they have already deleted) at that time, those LSAs were dropped due to "unknown opaque-type" error. After the problem 1.1 has fixed, those "self-originated" LSAs were registered to LSDB and then flooded to the network, even if the processing functions did not exist... After all, this problem has fixed so that those LSAs should explicitly be flushed from the routing domain immediately, if the processing functions cannot find at that time. 1.5 Some typo have fixed. --- EXAMPLE --- static int opaque_lsa_originate_callback (list funclist, void *lsa_type_dependent) ^^^^^ --- EXAMPLE --- 2. Feature enhancements 2.1 According to the description of rfc2328 in section 10.8, any change in the router's optional capabilities should trigger the option re-negotiation procedures with neighbors. --- EXCERPT --- If for some reason the router's optional capabilities change, the Database Exchange procedure should be restarted by reverting to neighbor state ExStart. --- EXCERPT --- For the opaque-capability changes, this feature has implemented. More specifically, if "ospf opaque-lsa" or "no ospf opaque-lsa" VTY command is given at runtime, all self-originated LSAs will be flushed immediately and then all neighbor status will be forced to ExStart by generating SeqNumberMismatch events. 2.1 When we change opaque-capability dynamically (ON -> OFF -> ON), there was no trigger at "OFF->ON" timing to reactivate opaque LSA handling modules (such as MPLS-TE) that have once forcibly stopped at "ON->OFF" timing. Now this dynamic reactivation feature has added. 2.2 The MPLS-TE module now referes to "draft-katz-yeung-ospf-traffic -05.txt", no significant changes with 04 version, though. ----- * ----- * ----- * ----- * ----- * ----- * ----- * ----- * ----- * ----- Changes 2001.03.28 Initial release of Opaque-LSA/MPLS-TE extensions for the zebra/ospfd. frr-7.2.1/ospfd/Makefile0000644000000000000000000000022213610377563011763 00000000000000all: ALWAYS @$(MAKE) -s -C .. ospfd/ospfd %: ALWAYS @$(MAKE) -s -C .. ospfd/$@ Makefile: #nothing ALWAYS: .PHONY: ALWAYS makefiles .SUFFIXES: frr-7.2.1/ospfd/ospf_abr.c0000644000000000000000000013647213610377563012303 00000000000000/* * OSPF ABR functions. * Copyright (C) 1999, 2000 Alex Zinin, Toshiaki Takada * * This file is part of GNU Zebra. * * GNU Zebra is free software; you can redistribute it and/or modify it * under the terms of the GNU General Public License as published by the * Free Software Foundation; either version 2, or (at your option) any * later version. * * GNU Zebra 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; see the file COPYING; if not, write to the Free Software * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA */ #include #include "thread.h" #include "memory.h" #include "linklist.h" #include "prefix.h" #include "if.h" #include "table.h" #include "vty.h" #include "filter.h" #include "plist.h" #include "log.h" #include "ospfd/ospfd.h" #include "ospfd/ospf_interface.h" #include "ospfd/ospf_ism.h" #include "ospfd/ospf_asbr.h" #include "ospfd/ospf_lsa.h" #include "ospfd/ospf_lsdb.h" #include "ospfd/ospf_neighbor.h" #include "ospfd/ospf_nsm.h" #include "ospfd/ospf_spf.h" #include "ospfd/ospf_route.h" #include "ospfd/ospf_ia.h" #include "ospfd/ospf_flood.h" #include "ospfd/ospf_abr.h" #include "ospfd/ospf_ase.h" #include "ospfd/ospf_zebra.h" #include "ospfd/ospf_dump.h" #include "ospfd/ospf_errors.h" static struct ospf_area_range *ospf_area_range_new(struct prefix_ipv4 *p) { struct ospf_area_range *range; range = XCALLOC(MTYPE_OSPF_AREA_RANGE, sizeof(struct ospf_area_range)); range->addr = p->prefix; range->masklen = p->prefixlen; range->cost_config = OSPF_AREA_RANGE_COST_UNSPEC; return range; } static void ospf_area_range_free(struct ospf_area_range *range) { XFREE(MTYPE_OSPF_AREA_RANGE, range); } static void ospf_area_range_add(struct ospf_area *area, struct ospf_area_range *range) { struct route_node *rn; struct prefix_ipv4 p; p.family = AF_INET; p.prefixlen = range->masklen; p.prefix = range->addr; apply_mask_ipv4(&p); rn = route_node_get(area->ranges, (struct prefix *)&p); if (rn->info) route_unlock_node(rn); else rn->info = range; } static void ospf_area_range_delete(struct ospf_area *area, struct route_node *rn) { struct ospf_area_range *range = rn->info; if (range->specifics != 0) ospf_delete_discard_route(area->ospf, area->ospf->new_table, (struct prefix_ipv4 *)&rn->p); ospf_area_range_free(range); rn->info = NULL; route_unlock_node(rn); route_unlock_node(rn); } struct ospf_area_range *ospf_area_range_lookup(struct ospf_area *area, struct prefix_ipv4 *p) { struct route_node *rn; rn = route_node_lookup(area->ranges, (struct prefix *)p); if (rn) { route_unlock_node(rn); return rn->info; } return NULL; } struct ospf_area_range *ospf_area_range_lookup_next(struct ospf_area *area, struct in_addr *range_net, int first) { struct route_node *rn; struct prefix_ipv4 p; struct ospf_area_range *find; p.family = AF_INET; p.prefixlen = IPV4_MAX_BITLEN; p.prefix = *range_net; apply_mask_ipv4(&p); if (first) rn = route_top(area->ranges); else { rn = route_node_get(area->ranges, (struct prefix *)&p); rn = route_next(rn); } for (; rn; rn = route_next(rn)) if (rn->info) break; if (rn && rn->info) { find = rn->info; *range_net = rn->p.u.prefix4; route_unlock_node(rn); return find; } return NULL; } static struct ospf_area_range *ospf_area_range_match(struct ospf_area *area, struct prefix_ipv4 *p) { struct route_node *node; node = route_node_match(area->ranges, (struct prefix *)p); if (node) { route_unlock_node(node); return node->info; } return NULL; } struct ospf_area_range *ospf_area_range_match_any(struct ospf *ospf, struct prefix_ipv4 *p) { struct ospf_area_range *range; struct ospf_area *area; struct listnode *node; for (ALL_LIST_ELEMENTS_RO(ospf->areas, node, area)) if ((range = ospf_area_range_match(area, p))) return range; return NULL; } int ospf_area_range_active(struct ospf_area_range *range) { return range->specifics; } static int ospf_area_actively_attached(struct ospf_area *area) { return area->act_ints; } int ospf_area_range_set(struct ospf *ospf, struct in_addr area_id, struct prefix_ipv4 *p, int advertise) { struct ospf_area *area; struct ospf_area_range *range; area = ospf_area_get(ospf, area_id); if (area == NULL) return 0; range = ospf_area_range_lookup(area, p); if (range != NULL) { if (!CHECK_FLAG(advertise, OSPF_AREA_RANGE_ADVERTISE)) range->cost_config = OSPF_AREA_RANGE_COST_UNSPEC; if ((CHECK_FLAG(range->flags, OSPF_AREA_RANGE_ADVERTISE) && !CHECK_FLAG(advertise, OSPF_AREA_RANGE_ADVERTISE)) || (!CHECK_FLAG(range->flags, OSPF_AREA_RANGE_ADVERTISE) && CHECK_FLAG(advertise, OSPF_AREA_RANGE_ADVERTISE))) ospf_schedule_abr_task(ospf); } else { range = ospf_area_range_new(p); ospf_area_range_add(area, range); ospf_schedule_abr_task(ospf); } if (CHECK_FLAG(advertise, OSPF_AREA_RANGE_ADVERTISE)) SET_FLAG(range->flags, OSPF_AREA_RANGE_ADVERTISE); else { UNSET_FLAG(range->flags, OSPF_AREA_RANGE_ADVERTISE); range->cost_config = OSPF_AREA_RANGE_COST_UNSPEC; } return 1; } int ospf_area_range_cost_set(struct ospf *ospf, struct in_addr area_id, struct prefix_ipv4 *p, uint32_t cost) { struct ospf_area *area; struct ospf_area_range *range; area = ospf_area_get(ospf, area_id); if (area == NULL) return 0; range = ospf_area_range_lookup(area, p); if (range == NULL) return 0; if (range->cost_config != cost) { range->cost_config = cost; if (ospf_area_range_active(range)) ospf_schedule_abr_task(ospf); } return 1; } int ospf_area_range_unset(struct ospf *ospf, struct in_addr area_id, struct prefix_ipv4 *p) { struct ospf_area *area; struct route_node *rn; area = ospf_area_lookup_by_area_id(ospf, area_id); if (area == NULL) return 0; rn = route_node_lookup(area->ranges, (struct prefix *)p); if (rn == NULL) return 0; if (ospf_area_range_active(rn->info)) ospf_schedule_abr_task(ospf); ospf_area_range_delete(area, rn); return 1; } int ospf_area_range_substitute_set(struct ospf *ospf, struct in_addr area_id, struct prefix_ipv4 *p, struct prefix_ipv4 *s) { struct ospf_area *area; struct ospf_area_range *range; area = ospf_area_get(ospf, area_id); range = ospf_area_range_lookup(area, p); if (range != NULL) { if (!CHECK_FLAG(range->flags, OSPF_AREA_RANGE_ADVERTISE) || !CHECK_FLAG(range->flags, OSPF_AREA_RANGE_SUBSTITUTE)) ospf_schedule_abr_task(ospf); } else { range = ospf_area_range_new(p); ospf_area_range_add(area, range); ospf_schedule_abr_task(ospf); } SET_FLAG(range->flags, OSPF_AREA_RANGE_ADVERTISE); SET_FLAG(range->flags, OSPF_AREA_RANGE_SUBSTITUTE); range->subst_addr = s->prefix; range->subst_masklen = s->prefixlen; return 1; } int ospf_area_range_substitute_unset(struct ospf *ospf, struct in_addr area_id, struct prefix_ipv4 *p) { struct ospf_area *area; struct ospf_area_range *range; area = ospf_area_lookup_by_area_id(ospf, area_id); if (area == NULL) return 0; range = ospf_area_range_lookup(area, p); if (range == NULL) return 0; if (CHECK_FLAG(range->flags, OSPF_AREA_RANGE_SUBSTITUTE)) if (ospf_area_range_active(range)) ospf_schedule_abr_task(ospf); UNSET_FLAG(range->flags, OSPF_AREA_RANGE_SUBSTITUTE); range->subst_addr.s_addr = 0; range->subst_masklen = 0; return 1; } int ospf_act_bb_connection(struct ospf *ospf) { if (ospf->backbone == NULL) return 0; return ospf->backbone->full_nbrs; } /* Determine whether this router is elected translator or not for area */ static int ospf_abr_nssa_am_elected(struct ospf_area *area) { struct route_node *rn; struct ospf_lsa *lsa; struct router_lsa *rlsa; struct in_addr *best = NULL; LSDB_LOOP (ROUTER_LSDB(area), rn, lsa) { /* sanity checks */ if (!lsa || (lsa->data->type != OSPF_ROUTER_LSA) || IS_LSA_SELF(lsa)) continue; rlsa = (struct router_lsa *)lsa->data; /* ignore non-ABR routers */ if (!IS_ROUTER_LSA_BORDER(rlsa)) continue; /* Router has Nt flag - always translate */ if (IS_ROUTER_LSA_NT(rlsa)) { if (IS_DEBUG_OSPF_NSSA) zlog_debug( "ospf_abr_nssa_am_elected: " "router %s asserts Nt", inet_ntoa(lsa->data->id)); return 0; } if (best == NULL) best = &lsa->data->id; else if (IPV4_ADDR_CMP(&best->s_addr, &lsa->data->id.s_addr) < 0) best = &lsa->data->id; } if (IS_DEBUG_OSPF_NSSA) zlog_debug( "ospf_abr_nssa_am_elected: best electable ABR is: %s", (best) ? inet_ntoa(*best) : ""); if (best == NULL) return 1; if (IPV4_ADDR_CMP(&best->s_addr, &area->ospf->router_id.s_addr) < 0) return 1; else return 0; } /* Check NSSA ABR status * assumes there are nssa areas */ static void ospf_abr_nssa_check_status(struct ospf *ospf) { struct ospf_area *area; struct listnode *lnode, *nnode; for (ALL_LIST_ELEMENTS(ospf->areas, lnode, nnode, area)) { uint8_t old_state = area->NSSATranslatorState; if (area->external_routing != OSPF_AREA_NSSA) continue; if (IS_DEBUG_OSPF(nssa, NSSA)) zlog_debug( "ospf_abr_nssa_check_status: " "checking area %s", inet_ntoa(area->area_id)); if (!IS_OSPF_ABR(area->ospf)) { if (IS_DEBUG_OSPF(nssa, NSSA)) zlog_debug( "ospf_abr_nssa_check_status: " "not ABR"); area->NSSATranslatorState = OSPF_NSSA_TRANSLATE_DISABLED; } else { switch (area->NSSATranslatorRole) { case OSPF_NSSA_ROLE_NEVER: /* We never Translate Type-7 LSA. */ /* TODO: check previous state and flush? */ if (IS_DEBUG_OSPF(nssa, NSSA)) zlog_debug( "ospf_abr_nssa_check_status: " "never translate"); area->NSSATranslatorState = OSPF_NSSA_TRANSLATE_DISABLED; break; case OSPF_NSSA_ROLE_ALWAYS: /* We always translate if we are an ABR * TODO: originate new LSAs if state change? * or let the nssa abr task take care of it? */ if (IS_DEBUG_OSPF(nssa, NSSA)) zlog_debug( "ospf_abr_nssa_check_status: " "translate always"); area->NSSATranslatorState = OSPF_NSSA_TRANSLATE_ENABLED; break; case OSPF_NSSA_ROLE_CANDIDATE: /* We are a candidate for Translation */ if (ospf_abr_nssa_am_elected(area) > 0) { area->NSSATranslatorState = OSPF_NSSA_TRANSLATE_ENABLED; if (IS_DEBUG_OSPF(nssa, NSSA)) zlog_debug( "ospf_abr_nssa_check_status: " "elected translator"); } else { area->NSSATranslatorState = OSPF_NSSA_TRANSLATE_DISABLED; if (IS_DEBUG_OSPF(nssa, NSSA)) zlog_debug( "ospf_abr_nssa_check_status: " "not elected"); } break; } } /* RFC3101, 3.1: * All NSSA border routers must set the E-bit in the Type-1 * router-LSAs * of their directly attached non-stub areas, even when they are * not * translating. */ if (old_state != area->NSSATranslatorState) { if (old_state == OSPF_NSSA_TRANSLATE_DISABLED) ospf_asbr_status_update(ospf, ++ospf->redistribute); else if (area->NSSATranslatorState == OSPF_NSSA_TRANSLATE_DISABLED) ospf_asbr_status_update(ospf, --ospf->redistribute); } } } /* Check area border router status. */ void ospf_check_abr_status(struct ospf *ospf) { struct ospf_area *area; struct listnode *node, *nnode; int bb_configured = 0; int bb_act_attached = 0; int areas_configured = 0; int areas_act_attached = 0; uint8_t new_flags = ospf->flags; if (IS_DEBUG_OSPF_EVENT) zlog_debug("ospf_check_abr_status(): Start"); for (ALL_LIST_ELEMENTS(ospf->areas, node, nnode, area)) { if (listcount(area->oiflist)) { areas_configured++; if (OSPF_IS_AREA_BACKBONE(area)) bb_configured = 1; } if (ospf_area_actively_attached(area)) { areas_act_attached++; if (OSPF_IS_AREA_BACKBONE(area)) bb_act_attached = 1; } } if (IS_DEBUG_OSPF_EVENT) { zlog_debug("ospf_check_abr_status(): looked through areas"); zlog_debug("ospf_check_abr_status(): bb_configured: %d", bb_configured); zlog_debug("ospf_check_abr_status(): bb_act_attached: %d", bb_act_attached); zlog_debug("ospf_check_abr_status(): areas_configured: %d", areas_configured); zlog_debug("ospf_check_abr_status(): areas_act_attached: %d", areas_act_attached); } switch (ospf->abr_type) { case OSPF_ABR_SHORTCUT: case OSPF_ABR_STAND: if (areas_act_attached > 1) SET_FLAG(new_flags, OSPF_FLAG_ABR); else UNSET_FLAG(new_flags, OSPF_FLAG_ABR); break; case OSPF_ABR_IBM: if ((areas_act_attached > 1) && bb_configured) SET_FLAG(new_flags, OSPF_FLAG_ABR); else UNSET_FLAG(new_flags, OSPF_FLAG_ABR); break; case OSPF_ABR_CISCO: if ((areas_configured > 1) && bb_act_attached) SET_FLAG(new_flags, OSPF_FLAG_ABR); else UNSET_FLAG(new_flags, OSPF_FLAG_ABR); break; default: break; } if (new_flags != ospf->flags) { ospf_spf_calculate_schedule(ospf, SPF_FLAG_ABR_STATUS_CHANGE); if (IS_DEBUG_OSPF_EVENT) zlog_debug( "ospf_check_abr_status(): new router flags: %x", new_flags); ospf->flags = new_flags; ospf_router_lsa_update(ospf); } } static void ospf_abr_update_aggregate(struct ospf_area_range *range, struct ospf_route * or, struct ospf_area *area) { if (IS_DEBUG_OSPF_EVENT) zlog_debug("ospf_abr_update_aggregate(): Start"); if (CHECK_FLAG(area->stub_router_state, OSPF_AREA_IS_STUB_ROUTED) && (range->cost != OSPF_STUB_MAX_METRIC_SUMMARY_COST)) { range->cost = OSPF_STUB_MAX_METRIC_SUMMARY_COST; if (IS_DEBUG_OSPF_EVENT) zlog_debug( "ospf_abr_update_aggregate(): use summary max-metric 0x%08x", range->cost); } else if (range->cost_config != OSPF_AREA_RANGE_COST_UNSPEC) { if (IS_DEBUG_OSPF_EVENT) zlog_debug( "ospf_abr_update_aggregate(): use configured cost %d", range->cost_config); range->cost = range->cost_config; } else { if (range->specifics == 0) { if (IS_DEBUG_OSPF_EVENT) zlog_debug( "ospf_abr_update_aggregate(): use or->cost %d", or->cost); range->cost = or->cost; /* 1st time get 1st cost */ } if (or->cost > range->cost) { if (IS_DEBUG_OSPF_EVENT) zlog_debug( "ospf_abr_update_aggregate(): update to %d", or->cost); range->cost = or->cost; } } range->specifics++; } static void set_metric(struct ospf_lsa *lsa, uint32_t metric) { struct summary_lsa *header; uint8_t *mp; metric = htonl(metric); mp = (uint8_t *)&metric; mp++; header = (struct summary_lsa *)lsa->data; memcpy(header->metric, mp, 3); } /* ospf_abr_translate_nssa */ static int ospf_abr_translate_nssa(struct ospf_area *area, struct ospf_lsa *lsa) { /* Incoming Type-7 or later aggregated Type-7 * * LSA is skipped if P-bit is off. * LSA is aggregated if within range. * * The Type-7 is translated, Installed/Approved as a Type-5 into * global LSDB, then Flooded through AS * * Later, any Unapproved Translated Type-5's are flushed/discarded */ struct ospf_lsa *old = NULL, *new = NULL; struct as_external_lsa *ext7; struct prefix_ipv4 p; if (!CHECK_FLAG(lsa->data->options, OSPF_OPTION_NP)) { if (IS_DEBUG_OSPF_NSSA) zlog_debug( "ospf_abr_translate_nssa(): LSA Id %s, P-bit off, NO Translation", inet_ntoa(lsa->data->id)); return 1; } if (IS_DEBUG_OSPF_NSSA) zlog_debug( "ospf_abr_translate_nssa(): LSA Id %s, TRANSLATING 7 to 5", inet_ntoa(lsa->data->id)); ext7 = (struct as_external_lsa *)(lsa->data); p.prefix = lsa->data->id; p.prefixlen = ip_masklen(ext7->mask); if (ext7->e[0].fwd_addr.s_addr == OSPF_DEFAULT_DESTINATION) { if (IS_DEBUG_OSPF_NSSA) zlog_debug( "ospf_abr_translate_nssa(): LSA Id %s, " "Forward address is 0, NO Translation", inet_ntoa(lsa->data->id)); return 1; } /* try find existing AS-External LSA for this prefix */ old = ospf_external_info_find_lsa(area->ospf, &p); if (old) { if (IS_DEBUG_OSPF_NSSA) zlog_debug( "ospf_abr_translate_nssa(): " "found old translated LSA Id %s, refreshing", inet_ntoa(old->data->id)); /* refresh */ new = ospf_translated_nssa_refresh(area->ospf, lsa, old); if (!new) { if (IS_DEBUG_OSPF_NSSA) zlog_debug( "ospf_abr_translate_nssa(): " "could not refresh translated LSA Id %s", inet_ntoa(old->data->id)); } } else { /* no existing external route for this LSA Id * originate translated LSA */ if ((new = ospf_translated_nssa_originate(area->ospf, lsa)) == NULL) { if (IS_DEBUG_OSPF_NSSA) zlog_debug( "ospf_abr_translate_nssa(): Could not translate " "Type-7 for %s to Type-5", inet_ntoa(lsa->data->id)); return 1; } } /* Area where Aggregate testing will be inserted, just like summary advertisements */ /* ospf_abr_check_nssa_range (p_arg, lsa-> cost, lsa -> area); */ return 0; } static void ospf_abr_translate_nssa_range(struct prefix_ipv4 *p, uint32_t cost) { /* The Type-7 is created from the aggregated prefix and forwarded for lsa installation and flooding... to be added... */ } void ospf_abr_announce_network_to_area(struct prefix_ipv4 *p, uint32_t cost, struct ospf_area *area) { struct ospf_lsa *lsa, *old = NULL; struct summary_lsa *sl = NULL; uint32_t full_cost; if (IS_DEBUG_OSPF_EVENT) zlog_debug("ospf_abr_announce_network_to_area(): Start"); if (CHECK_FLAG(area->stub_router_state, OSPF_AREA_IS_STUB_ROUTED)) full_cost = OSPF_STUB_MAX_METRIC_SUMMARY_COST; else full_cost = cost; old = ospf_lsa_lookup_by_prefix(area->lsdb, OSPF_SUMMARY_LSA, (struct prefix_ipv4 *)p, area->ospf->router_id); if (old) { if (IS_DEBUG_OSPF_EVENT) zlog_debug( "ospf_abr_announce_network_to_area(): old summary found"); sl = (struct summary_lsa *)old->data; if (IS_DEBUG_OSPF_EVENT) zlog_debug( "ospf_abr_announce_network_to_area(): " "old metric: %d, new metric: %d", GET_METRIC(sl->metric), cost); if ((GET_METRIC(sl->metric) == full_cost) && ((old->flags & OSPF_LSA_IN_MAXAGE) == 0)) { /* unchanged. simply reapprove it */ if (IS_DEBUG_OSPF_EVENT) zlog_debug( "ospf_abr_announce_network_to_area(): " "old summary approved"); SET_FLAG(old->flags, OSPF_LSA_APPROVED); } else { /* LSA is changed, refresh it */ if (IS_DEBUG_OSPF_EVENT) zlog_debug( "ospf_abr_announce_network_to_area(): " "refreshing summary"); set_metric(old, full_cost); lsa = ospf_lsa_refresh(area->ospf, old); if (!lsa) { char buf[PREFIX2STR_BUFFER]; prefix2str((struct prefix *)p, buf, sizeof(buf)); flog_warn(EC_OSPF_LSA_MISSING, "%s: Could not refresh %s to %s", __func__, buf, inet_ntoa(area->area_id)); return; } SET_FLAG(lsa->flags, OSPF_LSA_APPROVED); /* This will flood through area. */ } } else { if (IS_DEBUG_OSPF_EVENT) zlog_debug( "ospf_abr_announce_network_to_area(): " "creating new summary"); lsa = ospf_summary_lsa_originate((struct prefix_ipv4 *)p, full_cost, area); /* This will flood through area. */ if (!lsa) { char buf[PREFIX2STR_BUFFER]; prefix2str((struct prefix *)p, buf, sizeof(buf)); flog_warn(EC_OSPF_LSA_MISSING, "%s: Could not originate %s to %s", __func__, buf, inet_ntoa(area->area_id)); return; } SET_FLAG(lsa->flags, OSPF_LSA_APPROVED); if (IS_DEBUG_OSPF_EVENT) zlog_debug( "ospf_abr_announce_network_to_area(): " "flooding new version of summary"); } if (IS_DEBUG_OSPF_EVENT) zlog_debug("ospf_abr_announce_network_to_area(): Stop"); } static int ospf_abr_nexthops_belong_to_area(struct ospf_route * or, struct ospf_area *area) { struct listnode *node, *nnode; struct ospf_path *path; struct ospf_interface *oi; for (ALL_LIST_ELEMENTS_RO(or->paths, node, path)) for (ALL_LIST_ELEMENTS_RO(area->oiflist, nnode, oi)) if (oi->ifp && oi->ifp->ifindex == path->ifindex) return 1; return 0; } static int ospf_abr_should_accept(struct prefix_ipv4 *p, struct ospf_area *area) { if (IMPORT_NAME(area)) { if (IMPORT_LIST(area) == NULL) IMPORT_LIST(area) = access_list_lookup(AFI_IP, IMPORT_NAME(area)); if (IMPORT_LIST(area)) if (access_list_apply(IMPORT_LIST(area), p) == FILTER_DENY) return 0; } return 1; } static int ospf_abr_plist_in_check(struct ospf_area *area, struct ospf_route * or, struct prefix_ipv4 *p) { if (PREFIX_NAME_IN(area)) { if (PREFIX_LIST_IN(area) == NULL) PREFIX_LIST_IN(area) = prefix_list_lookup( AFI_IP, PREFIX_NAME_IN(area)); if (PREFIX_LIST_IN(area)) if (prefix_list_apply(PREFIX_LIST_IN(area), p) != PREFIX_PERMIT) return 0; } return 1; } static int ospf_abr_plist_out_check(struct ospf_area *area, struct ospf_route * or, struct prefix_ipv4 *p) { if (PREFIX_NAME_OUT(area)) { if (PREFIX_LIST_OUT(area) == NULL) PREFIX_LIST_OUT(area) = prefix_list_lookup( AFI_IP, PREFIX_NAME_OUT(area)); if (PREFIX_LIST_OUT(area)) if (prefix_list_apply(PREFIX_LIST_OUT(area), p) != PREFIX_PERMIT) return 0; } return 1; } static void ospf_abr_announce_network(struct ospf *ospf, struct prefix_ipv4 *p, struct ospf_route * or) { struct ospf_area_range *range; struct ospf_area *area, *or_area; struct listnode *node; if (IS_DEBUG_OSPF_EVENT) zlog_debug("ospf_abr_announce_network(): Start"); or_area = ospf_area_lookup_by_area_id(ospf, or->u.std.area_id); assert(or_area); for (ALL_LIST_ELEMENTS_RO(ospf->areas, node, area)) { if (IS_DEBUG_OSPF_EVENT) zlog_debug( "ospf_abr_announce_network(): looking at area %s", inet_ntoa(area->area_id)); if (IPV4_ADDR_SAME(& or->u.std.area_id, &area->area_id)) continue; if (ospf_abr_nexthops_belong_to_area(or, area)) continue; if (!ospf_abr_should_accept(p, area)) { if (IS_DEBUG_OSPF_EVENT) zlog_debug( "ospf_abr_announce_network(): " "prefix %s/%d was denied by import-list", inet_ntoa(p->prefix), p->prefixlen); continue; } if (!ospf_abr_plist_in_check(area, or, p)) { if (IS_DEBUG_OSPF_EVENT) zlog_debug( "ospf_abr_announce_network(): " "prefix %s/%d was denied by prefix-list", inet_ntoa(p->prefix), p->prefixlen); continue; } if (area->external_routing != OSPF_AREA_DEFAULT && area->no_summary) { if (IS_DEBUG_OSPF_EVENT) zlog_debug( "ospf_abr_announce_network(): " "area %s is stub and no_summary", inet_ntoa(area->area_id)); continue; } if (or->path_type == OSPF_PATH_INTER_AREA) { if (IS_DEBUG_OSPF_EVENT) zlog_debug( "ospf_abr_announce_network(): this is " "inter-area route to %s/%d", inet_ntoa(p->prefix), p->prefixlen); if (!OSPF_IS_AREA_BACKBONE(area)) ospf_abr_announce_network_to_area(p, or->cost, area); } if (or->path_type == OSPF_PATH_INTRA_AREA) { if (IS_DEBUG_OSPF_EVENT) zlog_debug( "ospf_abr_announce_network(): " "this is intra-area route to %s/%d", inet_ntoa(p->prefix), p->prefixlen); if ((range = ospf_area_range_match(or_area, p)) && !ospf_area_is_transit(area)) ospf_abr_update_aggregate(range, or, area); else ospf_abr_announce_network_to_area(p, or->cost, area); } } } static int ospf_abr_should_announce(struct ospf *ospf, struct prefix_ipv4 *p, struct ospf_route * or) { struct ospf_area *area; area = ospf_area_lookup_by_area_id(ospf, or->u.std.area_id); assert(area); if (EXPORT_NAME(area)) { if (EXPORT_LIST(area) == NULL) EXPORT_LIST(area) = access_list_lookup(AFI_IP, EXPORT_NAME(area)); if (EXPORT_LIST(area)) if (access_list_apply(EXPORT_LIST(area), p) == FILTER_DENY) return 0; } return 1; } static void ospf_abr_process_nssa_translates(struct ospf *ospf) { /* Scan through all NSSA_LSDB records for all areas; If P-bit is on, translate all Type-7's to 5's and aggregate or flood install as approved in Type-5 LSDB with XLATE Flag on later, do same for all aggregates... At end, DISCARD all remaining UNAPPROVED Type-5's (Aggregate is for future ) */ struct listnode *node; struct ospf_area *area; struct route_node *rn; struct ospf_lsa *lsa; if (IS_DEBUG_OSPF_NSSA) zlog_debug("ospf_abr_process_nssa_translates(): Start"); for (ALL_LIST_ELEMENTS_RO(ospf->areas, node, area)) { if (!area->NSSATranslatorState) continue; /* skip if not translator */ if (area->external_routing != OSPF_AREA_NSSA) continue; /* skip if not Nssa Area */ if (IS_DEBUG_OSPF_NSSA) zlog_debug( "ospf_abr_process_nssa_translates(): " "looking at area %s", inet_ntoa(area->area_id)); LSDB_LOOP (NSSA_LSDB(area), rn, lsa) ospf_abr_translate_nssa(area, lsa); } if (IS_DEBUG_OSPF_NSSA) zlog_debug("ospf_abr_process_nssa_translates(): Stop"); } static void ospf_abr_process_network_rt(struct ospf *ospf, struct route_table *rt) { struct ospf_area *area; struct ospf_route * or ; struct route_node *rn; if (IS_DEBUG_OSPF_EVENT) zlog_debug("ospf_abr_process_network_rt(): Start"); for (rn = route_top(rt); rn; rn = route_next(rn)) { if ((or = rn->info) == NULL) continue; if (!(area = ospf_area_lookup_by_area_id(ospf, or->u.std.area_id))) { if (IS_DEBUG_OSPF_EVENT) zlog_debug( "ospf_abr_process_network_rt(): area %s no longer exists", inet_ntoa(or->u.std.area_id)); continue; } if (IS_DEBUG_OSPF_EVENT) zlog_debug( "ospf_abr_process_network_rt(): this is a route to %s/%d", inet_ntoa(rn->p.u.prefix4), rn->p.prefixlen); if (or->path_type >= OSPF_PATH_TYPE1_EXTERNAL) { if (IS_DEBUG_OSPF_EVENT) zlog_debug( "ospf_abr_process_network_rt(): " "this is an External router, skipping"); continue; } if (or->cost >= OSPF_LS_INFINITY) { if (IS_DEBUG_OSPF_EVENT) zlog_debug( "ospf_abr_process_network_rt():" " this route's cost is infinity, skipping"); continue; } if (or->type == OSPF_DESTINATION_DISCARD) { if (IS_DEBUG_OSPF_EVENT) zlog_debug( "ospf_abr_process_network_rt():" " this is a discard entry, skipping"); continue; } if ( or->path_type == OSPF_PATH_INTRA_AREA && !ospf_abr_should_announce( ospf, (struct prefix_ipv4 *)&rn->p, or)) { if (IS_DEBUG_OSPF_EVENT) zlog_debug( "ospf_abr_process_network_rt(): denied by export-list"); continue; } if ( or->path_type == OSPF_PATH_INTRA_AREA && !ospf_abr_plist_out_check( area, or, (struct prefix_ipv4 *)&rn->p)) { if (IS_DEBUG_OSPF_EVENT) zlog_debug( "ospf_abr_process_network_rt(): denied by prefix-list"); continue; } if ((or->path_type == OSPF_PATH_INTER_AREA) && !OSPF_IS_AREA_ID_BACKBONE(or->u.std.area_id)) { if (IS_DEBUG_OSPF_EVENT) zlog_debug( "ospf_abr_process_network_rt():" " this is route is not backbone one, skipping"); continue; } if ((ospf->abr_type == OSPF_ABR_CISCO) || (ospf->abr_type == OSPF_ABR_IBM)) if (!ospf_act_bb_connection(ospf) && or->path_type != OSPF_PATH_INTRA_AREA) { if (IS_DEBUG_OSPF_EVENT) zlog_debug( "ospf_abr_process_network_rt(): ALT ABR: " "No BB connection, skip not intra-area routes"); continue; } if (IS_DEBUG_OSPF_EVENT) zlog_debug("ospf_abr_process_network_rt(): announcing"); ospf_abr_announce_network(ospf, (struct prefix_ipv4 *)&rn->p, or); } if (IS_DEBUG_OSPF_EVENT) zlog_debug("ospf_abr_process_network_rt(): Stop"); } static void ospf_abr_announce_rtr_to_area(struct prefix_ipv4 *p, uint32_t cost, struct ospf_area *area) { struct ospf_lsa *lsa, *old = NULL; struct summary_lsa *slsa = NULL; if (IS_DEBUG_OSPF_EVENT) zlog_debug("ospf_abr_announce_rtr_to_area(): Start"); old = ospf_lsa_lookup_by_prefix(area->lsdb, OSPF_ASBR_SUMMARY_LSA, p, area->ospf->router_id); if (old) { if (IS_DEBUG_OSPF_EVENT) zlog_debug( "ospf_abr_announce_rtr_to_area(): old summary found"); slsa = (struct summary_lsa *)old->data; if (IS_DEBUG_OSPF_EVENT) zlog_debug( "ospf_abr_announce_network_to_area(): " "old metric: %d, new metric: %d", GET_METRIC(slsa->metric), cost); } if (old && (GET_METRIC(slsa->metric) == cost) && ((old->flags & OSPF_LSA_IN_MAXAGE) == 0)) { if (IS_DEBUG_OSPF_EVENT) zlog_debug( "ospf_abr_announce_rtr_to_area(): old summary approved"); SET_FLAG(old->flags, OSPF_LSA_APPROVED); } else { if (IS_DEBUG_OSPF_EVENT) zlog_debug("ospf_abr_announce_rtr_to_area(): 2.2"); if (old) { set_metric(old, cost); lsa = ospf_lsa_refresh(area->ospf, old); } else lsa = ospf_summary_asbr_lsa_originate(p, cost, area); if (!lsa) { char buf[PREFIX2STR_BUFFER]; prefix2str((struct prefix *)p, buf, sizeof(buf)); flog_warn(EC_OSPF_LSA_MISSING, "%s: Could not refresh/originate %s to %s", __func__, buf, inet_ntoa(area->area_id)); return; } if (IS_DEBUG_OSPF_EVENT) zlog_debug( "ospf_abr_announce_rtr_to_area(): " "flooding new version of summary"); /* zlog_info ("ospf_abr_announce_rtr_to_area(): creating new summary"); lsa = ospf_summary_asbr_lsa (p, cost, area, old); */ SET_FLAG(lsa->flags, OSPF_LSA_APPROVED); /* ospf_flood_through_area (area, NULL, lsa);*/ } if (IS_DEBUG_OSPF_EVENT) zlog_debug("ospf_abr_announce_rtr_to_area(): Stop"); } static void ospf_abr_announce_rtr(struct ospf *ospf, struct prefix_ipv4 *p, struct ospf_route * or) { struct listnode *node; struct ospf_area *area; if (IS_DEBUG_OSPF_EVENT) zlog_debug("ospf_abr_announce_rtr(): Start"); for (ALL_LIST_ELEMENTS_RO(ospf->areas, node, area)) { if (IS_DEBUG_OSPF_EVENT) zlog_debug( "ospf_abr_announce_rtr(): looking at area %s", inet_ntoa(area->area_id)); if (IPV4_ADDR_SAME(& or->u.std.area_id, &area->area_id)) continue; if (ospf_abr_nexthops_belong_to_area(or, area)) continue; if (area->external_routing != OSPF_AREA_DEFAULT) { if (IS_DEBUG_OSPF_EVENT) zlog_debug( "ospf_abr_announce_rtr(): " "area %s doesn't support external routing", inet_ntoa(area->area_id)); continue; } if (or->path_type == OSPF_PATH_INTER_AREA) { if (IS_DEBUG_OSPF_EVENT) zlog_debug( "ospf_abr_announce_rtr(): " "this is inter-area route to %s", inet_ntoa(p->prefix)); if (!OSPF_IS_AREA_BACKBONE(area)) ospf_abr_announce_rtr_to_area(p, or->cost, area); } if (or->path_type == OSPF_PATH_INTRA_AREA) { if (IS_DEBUG_OSPF_EVENT) zlog_debug( "ospf_abr_announce_rtr(): " "this is intra-area route to %s", inet_ntoa(p->prefix)); ospf_abr_announce_rtr_to_area(p, or->cost, area); } } if (IS_DEBUG_OSPF_EVENT) zlog_debug("ospf_abr_announce_rtr(): Stop"); } static void ospf_abr_process_router_rt(struct ospf *ospf, struct route_table *rt) { struct ospf_route * or ; struct route_node *rn; struct list *l; if (IS_DEBUG_OSPF_EVENT) zlog_debug("ospf_abr_process_router_rt(): Start"); for (rn = route_top(rt); rn; rn = route_next(rn)) { struct listnode *node, *nnode; char flag = 0; struct ospf_route *best = NULL; if (rn->info == NULL) continue; l = rn->info; if (IS_DEBUG_OSPF_EVENT) zlog_debug( "ospf_abr_process_router_rt(): this is a route to %s", inet_ntoa(rn->p.u.prefix4)); for (ALL_LIST_ELEMENTS(l, node, nnode, or)) { if (!ospf_area_lookup_by_area_id(ospf, or->u.std.area_id)) { if (IS_DEBUG_OSPF_EVENT) zlog_debug( "ospf_abr_process_router_rt(): area %s no longer exists", inet_ntoa(or->u.std.area_id)); continue; } if (!CHECK_FLAG(or->u.std.flags, ROUTER_LSA_EXTERNAL)) { if (IS_DEBUG_OSPF_EVENT) zlog_debug( "ospf_abr_process_router_rt(): " "This is not an ASBR, skipping"); continue; } if (!flag) { best = ospf_find_asbr_route( ospf, rt, (struct prefix_ipv4 *)&rn->p); flag = 1; } if (best == NULL) continue; if (or != best) { if (IS_DEBUG_OSPF_EVENT) zlog_debug( "ospf_abr_process_router_rt(): " "This route is not the best among possible, skipping"); continue; } if ( or->path_type == OSPF_PATH_INTER_AREA && !OSPF_IS_AREA_ID_BACKBONE( or->u.std.area_id)) { if (IS_DEBUG_OSPF_EVENT) zlog_debug( "ospf_abr_process_router_rt(): " "This route is not a backbone one, skipping"); continue; } if (or->cost >= OSPF_LS_INFINITY) { if (IS_DEBUG_OSPF_EVENT) zlog_debug( "ospf_abr_process_router_rt(): " "This route has LS_INFINITY metric, skipping"); continue; } if (ospf->abr_type == OSPF_ABR_CISCO || ospf->abr_type == OSPF_ABR_IBM) if (!ospf_act_bb_connection(ospf) && or->path_type != OSPF_PATH_INTRA_AREA) { if (IS_DEBUG_OSPF_EVENT) zlog_debug( "ospf_abr_process_network_rt(): ALT ABR: " "No BB connection, skip not intra-area routes"); continue; } ospf_abr_announce_rtr(ospf, (struct prefix_ipv4 *)&rn->p, or); } } if (IS_DEBUG_OSPF_EVENT) zlog_debug("ospf_abr_process_router_rt(): Stop"); } static void ospf_abr_unapprove_translates(struct ospf *ospf) /* For NSSA Translations */ { struct ospf_lsa *lsa; struct route_node *rn; if (IS_DEBUG_OSPF_NSSA) zlog_debug("ospf_abr_unapprove_translates(): Start"); /* NSSA Translator is not checked, because it may have gone away, and we would want to flush any residuals anyway */ LSDB_LOOP (EXTERNAL_LSDB(ospf), rn, lsa) if (CHECK_FLAG(lsa->flags, OSPF_LSA_LOCAL_XLT)) { UNSET_FLAG(lsa->flags, OSPF_LSA_APPROVED); if (IS_DEBUG_OSPF_NSSA) zlog_debug( "ospf_abr_unapprove_translates(): " "approved unset on link id %s", inet_ntoa(lsa->data->id)); } if (IS_DEBUG_OSPF_NSSA) zlog_debug("ospf_abr_unapprove_translates(): Stop"); } static void ospf_abr_unapprove_summaries(struct ospf *ospf) { struct listnode *node; struct ospf_area *area; struct route_node *rn; struct ospf_lsa *lsa; if (IS_DEBUG_OSPF_EVENT) zlog_debug("ospf_abr_unapprove_summaries(): Start"); for (ALL_LIST_ELEMENTS_RO(ospf->areas, node, area)) { if (IS_DEBUG_OSPF_EVENT) zlog_debug( "ospf_abr_unapprove_summaries(): " "considering area %s", inet_ntoa(area->area_id)); LSDB_LOOP (SUMMARY_LSDB(area), rn, lsa) if (ospf_lsa_is_self_originated(ospf, lsa)) { if (IS_DEBUG_OSPF_EVENT) zlog_debug( "ospf_abr_unapprove_summaries(): " "approved unset on summary link id %s", inet_ntoa(lsa->data->id)); UNSET_FLAG(lsa->flags, OSPF_LSA_APPROVED); } LSDB_LOOP (ASBR_SUMMARY_LSDB(area), rn, lsa) if (ospf_lsa_is_self_originated(ospf, lsa)) { if (IS_DEBUG_OSPF_EVENT) zlog_debug( "ospf_abr_unapprove_summaries(): " "approved unset on asbr-summary link id %s", inet_ntoa(lsa->data->id)); UNSET_FLAG(lsa->flags, OSPF_LSA_APPROVED); } } if (IS_DEBUG_OSPF_EVENT) zlog_debug("ospf_abr_unapprove_summaries(): Stop"); } static void ospf_abr_prepare_aggregates(struct ospf *ospf) { struct listnode *node; struct route_node *rn; struct ospf_area_range *range; struct ospf_area *area; if (IS_DEBUG_OSPF_EVENT) zlog_debug("ospf_abr_prepare_aggregates(): Start"); for (ALL_LIST_ELEMENTS_RO(ospf->areas, node, area)) { for (rn = route_top(area->ranges); rn; rn = route_next(rn)) if ((range = rn->info) != NULL) { range->cost = 0; range->specifics = 0; } } if (IS_DEBUG_OSPF_EVENT) zlog_debug("ospf_abr_prepare_aggregates(): Stop"); } static void ospf_abr_announce_aggregates(struct ospf *ospf) { struct ospf_area *area, *ar; struct ospf_area_range *range; struct route_node *rn; struct prefix p; struct listnode *node, *n; if (IS_DEBUG_OSPF_EVENT) zlog_debug("ospf_abr_announce_aggregates(): Start"); for (ALL_LIST_ELEMENTS_RO(ospf->areas, node, area)) { if (IS_DEBUG_OSPF_EVENT) zlog_debug( "ospf_abr_announce_aggregates(): looking at area %s", inet_ntoa(area->area_id)); for (rn = route_top(area->ranges); rn; rn = route_next(rn)) if ((range = rn->info)) { if (!CHECK_FLAG(range->flags, OSPF_AREA_RANGE_ADVERTISE)) { if (IS_DEBUG_OSPF_EVENT) zlog_debug( "ospf_abr_announce_aggregates():" " discarding suppress-ranges"); continue; } p.family = AF_INET; p.u.prefix4 = range->addr; p.prefixlen = range->masklen; if (IS_DEBUG_OSPF_EVENT) zlog_debug( "ospf_abr_announce_aggregates():" " this is range: %s/%d", inet_ntoa(p.u.prefix4), p.prefixlen); if (CHECK_FLAG(range->flags, OSPF_AREA_RANGE_SUBSTITUTE)) { p.family = AF_INET; p.u.prefix4 = range->subst_addr; p.prefixlen = range->subst_masklen; } if (range->specifics) { if (IS_DEBUG_OSPF_EVENT) zlog_debug( "ospf_abr_announce_aggregates(): active range"); for (ALL_LIST_ELEMENTS_RO(ospf->areas, n, ar)) { if (ar == area) continue; /* We do not check nexthops here, because intra-area routes can be associated with one area only */ /* backbone routes are not summarized when announced into transit areas */ if (ospf_area_is_transit(ar) && OSPF_IS_AREA_BACKBONE( area)) { if (IS_DEBUG_OSPF_EVENT) zlog_debug( "ospf_abr_announce_aggregates(): Skipping " "announcement of BB aggregate into" " a transit area"); continue; } ospf_abr_announce_network_to_area( (struct prefix_ipv4 *)&p, range->cost, ar); } } } } if (IS_DEBUG_OSPF_EVENT) zlog_debug("ospf_abr_announce_aggregates(): Stop"); } static void ospf_abr_send_nssa_aggregates(struct ospf *ospf) /* temporarily turned off */ { struct listnode *node; /*, n; */ struct ospf_area *area; /*, *ar; */ struct route_node *rn; struct ospf_area_range *range; struct prefix_ipv4 p; if (IS_DEBUG_OSPF_NSSA) zlog_debug("ospf_abr_send_nssa_aggregates(): Start"); for (ALL_LIST_ELEMENTS_RO(ospf->areas, node, area)) { if (!area->NSSATranslatorState) continue; if (IS_DEBUG_OSPF_NSSA) zlog_debug( "ospf_abr_send_nssa_aggregates(): looking at area %s", inet_ntoa(area->area_id)); for (rn = route_top(area->ranges); rn; rn = route_next(rn)) { if (rn->info == NULL) continue; range = rn->info; if (!CHECK_FLAG(range->flags, OSPF_AREA_RANGE_ADVERTISE)) { if (IS_DEBUG_OSPF_NSSA) zlog_debug( "ospf_abr_send_nssa_aggregates():" " discarding suppress-ranges"); continue; } p.family = AF_INET; p.prefix = range->addr; p.prefixlen = range->masklen; if (IS_DEBUG_OSPF_NSSA) zlog_debug( "ospf_abr_send_nssa_aggregates():" " this is range: %s/%d", inet_ntoa(p.prefix), p.prefixlen); if (CHECK_FLAG(range->flags, OSPF_AREA_RANGE_SUBSTITUTE)) { p.family = AF_INET; p.prefix = range->subst_addr; p.prefixlen = range->subst_masklen; } if (range->specifics) { if (IS_DEBUG_OSPF_NSSA) zlog_debug( "ospf_abr_send_nssa_aggregates(): active range"); /* Fetch LSA-Type-7 from aggregate prefix, and * then * translate, Install (as Type-5), Approve, and * Flood */ ospf_abr_translate_nssa_range(&p, range->cost); } } /* all area ranges*/ } /* all areas */ if (IS_DEBUG_OSPF_NSSA) zlog_debug("ospf_abr_send_nssa_aggregates(): Stop"); } static void ospf_abr_announce_stub_defaults(struct ospf *ospf) { struct listnode *node; struct ospf_area *area; struct prefix_ipv4 p; if (!IS_OSPF_ABR(ospf)) return; if (IS_DEBUG_OSPF_EVENT) zlog_debug("ospf_abr_announce_stub_defaults(): Start"); p.family = AF_INET; p.prefix.s_addr = OSPF_DEFAULT_DESTINATION; p.prefixlen = 0; for (ALL_LIST_ELEMENTS_RO(ospf->areas, node, area)) { if (IS_DEBUG_OSPF_EVENT) zlog_debug( "ospf_abr_announce_stub_defaults(): looking at area %s", inet_ntoa(area->area_id)); if ((area->external_routing != OSPF_AREA_STUB) && (area->external_routing != OSPF_AREA_NSSA)) continue; if (OSPF_IS_AREA_BACKBONE(area)) continue; /* Sanity Check */ if (IS_DEBUG_OSPF_EVENT) zlog_debug( "ospf_abr_announce_stub_defaults(): " "announcing 0.0.0.0/0 to area %s", inet_ntoa(area->area_id)); ospf_abr_announce_network_to_area(&p, area->default_cost, area); } if (IS_DEBUG_OSPF_EVENT) zlog_debug("ospf_abr_announce_stub_defaults(): Stop"); } static int ospf_abr_remove_unapproved_translates_apply(struct ospf *ospf, struct ospf_lsa *lsa) { if (CHECK_FLAG(lsa->flags, OSPF_LSA_LOCAL_XLT) && !CHECK_FLAG(lsa->flags, OSPF_LSA_APPROVED)) { zlog_info( "ospf_abr_remove_unapproved_translates(): " "removing unapproved translates, ID: %s", inet_ntoa(lsa->data->id)); /* FLUSH THROUGHOUT AS */ ospf_lsa_flush_as(ospf, lsa); /* DISCARD from LSDB */ } return 0; } static void ospf_abr_remove_unapproved_translates(struct ospf *ospf) { struct route_node *rn; struct ospf_lsa *lsa; /* All AREA PROCESS should have APPROVED necessary LSAs */ /* Remove any left over and not APPROVED */ if (IS_DEBUG_OSPF_NSSA) zlog_debug("ospf_abr_remove_unapproved_translates(): Start"); LSDB_LOOP (EXTERNAL_LSDB(ospf), rn, lsa) ospf_abr_remove_unapproved_translates_apply(ospf, lsa); if (IS_DEBUG_OSPF_NSSA) zlog_debug("ospf_abr_remove_unapproved_translates(): Stop"); } static void ospf_abr_remove_unapproved_summaries(struct ospf *ospf) { struct listnode *node; struct ospf_area *area; struct route_node *rn; struct ospf_lsa *lsa; if (IS_DEBUG_OSPF_EVENT) zlog_debug("ospf_abr_remove_unapproved_summaries(): Start"); for (ALL_LIST_ELEMENTS_RO(ospf->areas, node, area)) { if (IS_DEBUG_OSPF_EVENT) zlog_debug( "ospf_abr_remove_unapproved_summaries(): " "looking at area %s", inet_ntoa(area->area_id)); LSDB_LOOP (SUMMARY_LSDB(area), rn, lsa) if (ospf_lsa_is_self_originated(ospf, lsa)) if (!CHECK_FLAG(lsa->flags, OSPF_LSA_APPROVED)) ospf_lsa_flush_area(lsa, area); LSDB_LOOP (ASBR_SUMMARY_LSDB(area), rn, lsa) if (ospf_lsa_is_self_originated(ospf, lsa)) if (!CHECK_FLAG(lsa->flags, OSPF_LSA_APPROVED)) ospf_lsa_flush_area(lsa, area); } if (IS_DEBUG_OSPF_EVENT) zlog_debug("ospf_abr_remove_unapproved_summaries(): Stop"); } static void ospf_abr_manage_discard_routes(struct ospf *ospf) { struct listnode *node, *nnode; struct route_node *rn; struct ospf_area *area; struct ospf_area_range *range; for (ALL_LIST_ELEMENTS(ospf->areas, node, nnode, area)) for (rn = route_top(area->ranges); rn; rn = route_next(rn)) if ((range = rn->info) != NULL) if (CHECK_FLAG(range->flags, OSPF_AREA_RANGE_ADVERTISE)) { if (range->specifics) ospf_add_discard_route( ospf, ospf->new_table, area, (struct prefix_ipv4 *)&rn->p); else ospf_delete_discard_route( ospf, ospf->new_table, (struct prefix_ipv4 *)&rn->p); } } /* This is the function taking care about ABR NSSA, i.e. NSSA Translator, -LSA aggregation and flooding. For all NSSAs Any SELF-AS-LSA is in the Type-5 LSDB and Type-7 LSDB. These LSA's are refreshed from the Type-5 LSDB, installed into the Type-7 LSDB with the P-bit set. Any received Type-5s are legal for an ABR, else illegal for IR. Received Type-7s are installed, by area, with incoming P-bit. They are flooded; if the Elected NSSA Translator, then P-bit off. Additionally, this ABR will place "translated type-7's" into the Type-5 LSDB in order to keep track of APPROVAL or not. It will scan through every area, looking for Type-7 LSAs with P-Bit SET. The Type-7's are either AS-FLOODED & 5-INSTALLED or AGGREGATED. Later, the AGGREGATED LSAs are AS-FLOODED & 5-INSTALLED. 5-INSTALLED is into the Type-5 LSDB; Any UNAPPROVED Type-5 LSAs left over are FLUSHED and DISCARDED. For External Calculations, any NSSA areas use the Type-7 AREA-LSDB, any ABR-non-NSSA areas use the Type-5 GLOBAL-LSDB. */ static void ospf_abr_nssa_task(struct ospf *ospf) /* called only if any_nssa */ { if (IS_DEBUG_OSPF_NSSA) zlog_debug("Check for NSSA-ABR Tasks():"); if (!IS_OSPF_ABR(ospf)) return; if (!ospf->anyNSSA) return; /* Each area must confirm TranslatorRole */ if (IS_DEBUG_OSPF_NSSA) zlog_debug("ospf_abr_nssa_task(): Start"); /* For all Global Entries flagged "local-translate", unset APPROVED */ if (IS_DEBUG_OSPF_NSSA) zlog_debug("ospf_abr_nssa_task(): unapprove translates"); ospf_abr_unapprove_translates(ospf); /* RESET all Ranges in every Area, same as summaries */ if (IS_DEBUG_OSPF_NSSA) zlog_debug("ospf_abr_nssa_task(): NSSA initialize aggregates"); ospf_abr_prepare_aggregates(ospf); /*TURNED OFF just for now */ /* For all NSSAs, Type-7s, translate to 5's, INSTALL/FLOOD, or * Aggregate as Type-7 * Install or Approve in Type-5 Global LSDB */ if (IS_DEBUG_OSPF_NSSA) zlog_debug("ospf_abr_nssa_task(): process translates"); ospf_abr_process_nssa_translates(ospf); /* Translate/Send any "ranged" aggregates, and also 5-Install and * Approve * Scan Type-7's for aggregates, translate to Type-5's, * Install/Flood/Approve */ if (IS_DEBUG_OSPF_NSSA) zlog_debug("ospf_abr_nssa_task(): send NSSA aggregates"); ospf_abr_send_nssa_aggregates(ospf); /*TURNED OFF FOR NOW */ /* Send any NSSA defaults as Type-5 *if (IS_DEBUG_OSPF_NSSA) * zlog_debug ("ospf_abr_nssa_task(): announce nssa defaults"); *ospf_abr_announce_nssa_defaults (ospf); * havnt a clue what above is supposed to do. */ /* Flush any unapproved previous translates from Global Data Base */ if (IS_DEBUG_OSPF_NSSA) zlog_debug( "ospf_abr_nssa_task(): remove unapproved translates"); ospf_abr_remove_unapproved_translates(ospf); ospf_abr_manage_discard_routes(ospf); /* same as normal...discard */ if (IS_DEBUG_OSPF_NSSA) zlog_debug("ospf_abr_nssa_task(): Stop"); } /* This is the function taking care about ABR stuff, i.e. summary-LSA origination and flooding. */ void ospf_abr_task(struct ospf *ospf) { if (IS_DEBUG_OSPF_EVENT) zlog_debug("ospf_abr_task(): Start"); if (ospf->new_table == NULL || ospf->new_rtrs == NULL) { if (IS_DEBUG_OSPF_EVENT) zlog_debug( "ospf_abr_task(): Routing tables are not yet ready"); return; } if (IS_DEBUG_OSPF_EVENT) zlog_debug("ospf_abr_task(): unapprove summaries"); ospf_abr_unapprove_summaries(ospf); if (IS_DEBUG_OSPF_EVENT) zlog_debug("ospf_abr_task(): prepare aggregates"); ospf_abr_prepare_aggregates(ospf); if (IS_OSPF_ABR(ospf)) { if (IS_DEBUG_OSPF_EVENT) zlog_debug("ospf_abr_task(): process network RT"); ospf_abr_process_network_rt(ospf, ospf->new_table); if (IS_DEBUG_OSPF_EVENT) zlog_debug("ospf_abr_task(): process router RT"); ospf_abr_process_router_rt(ospf, ospf->new_rtrs); if (IS_DEBUG_OSPF_EVENT) zlog_debug("ospf_abr_task(): announce aggregates"); ospf_abr_announce_aggregates(ospf); if (IS_DEBUG_OSPF_EVENT) zlog_debug("ospf_abr_task(): announce stub defaults"); ospf_abr_announce_stub_defaults(ospf); } if (IS_DEBUG_OSPF_EVENT) zlog_debug("ospf_abr_task(): remove unapproved summaries"); ospf_abr_remove_unapproved_summaries(ospf); ospf_abr_manage_discard_routes(ospf); if (IS_DEBUG_OSPF_EVENT) zlog_debug("ospf_abr_task(): Stop"); } static int ospf_abr_task_timer(struct thread *thread) { struct ospf *ospf = THREAD_ARG(thread); ospf->t_abr_task = 0; if (IS_DEBUG_OSPF_EVENT) zlog_debug("Running ABR task on timer"); ospf_check_abr_status(ospf); ospf_abr_nssa_check_status(ospf); ospf_abr_task(ospf); ospf_abr_nssa_task(ospf); /* if nssa-abr, then scan Type-7 LSDB */ return 0; } void ospf_schedule_abr_task(struct ospf *ospf) { if (IS_DEBUG_OSPF_EVENT) zlog_debug("Scheduling ABR task"); thread_add_timer(master, ospf_abr_task_timer, ospf, OSPF_ABR_TASK_DELAY, &ospf->t_abr_task); } frr-7.2.1/ospfd/ospf_abr.h0000644000000000000000000000530613610377563012277 00000000000000/* * OSPF ABR functions. * Copyright (C) 1999 Alex Zinin * * This file is part of GNU Zebra. * * GNU Zebra is free software; you can redistribute it and/or modify it * under the terms of the GNU General Public License as published by the * Free Software Foundation; either version 2, or (at your option) any * later version. * * GNU Zebra 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; see the file COPYING; if not, write to the Free Software * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA */ #ifndef _ZEBRA_OSPF_ABR_H #define _ZEBRA_OSPF_ABR_H #define OSPF_ABR_TASK_DELAY 7 #define OSPF_AREA_RANGE_ADVERTISE (1 << 0) #define OSPF_AREA_RANGE_SUBSTITUTE (1 << 1) /* Area range. */ struct ospf_area_range { /* Area range address. */ struct in_addr addr; /* Area range masklen. */ uint8_t masklen; /* Flags. */ uint8_t flags; /* Number of more specific prefixes. */ int specifics; /* Addr and masklen to substitute. */ struct in_addr subst_addr; uint8_t subst_masklen; /* Range cost. */ uint32_t cost; /* Configured range cost. */ uint32_t cost_config; }; /* Prototypes. */ extern struct ospf_area_range *ospf_area_range_lookup(struct ospf_area *, struct prefix_ipv4 *); extern struct ospf_area_range *ospf_some_area_range_match(struct prefix_ipv4 *); extern struct ospf_area_range * ospf_area_range_lookup_next(struct ospf_area *, struct in_addr *, int); extern int ospf_area_range_set(struct ospf *, struct in_addr, struct prefix_ipv4 *, int); extern int ospf_area_range_cost_set(struct ospf *, struct in_addr, struct prefix_ipv4 *, uint32_t); extern int ospf_area_range_unset(struct ospf *, struct in_addr, struct prefix_ipv4 *); extern int ospf_area_range_substitute_set(struct ospf *, struct in_addr, struct prefix_ipv4 *, struct prefix_ipv4 *); extern int ospf_area_range_substitute_unset(struct ospf *, struct in_addr, struct prefix_ipv4 *); extern struct ospf_area_range *ospf_area_range_match_any(struct ospf *, struct prefix_ipv4 *); extern int ospf_area_range_active(struct ospf_area_range *); extern int ospf_act_bb_connection(struct ospf *); extern void ospf_check_abr_status(struct ospf *); extern void ospf_abr_task(struct ospf *); extern void ospf_schedule_abr_task(struct ospf *); extern void ospf_abr_announce_network_to_area(struct prefix_ipv4 *, uint32_t, struct ospf_area *); #endif /* _ZEBRA_OSPF_ABR_H */ frr-7.2.1/ospfd/ospf_api.c0000644000000000000000000003527413610377563012306 00000000000000/* * API message handling module for OSPF daemon and client. * Copyright (C) 2001, 2002 Ralph Keller * * This file is part of GNU Zebra. * * GNU Zebra is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License as published * by the Free Software Foundation; either version 2, or (at your * option) any later version. * * GNU Zebra 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; see the file COPYING; if not, write to the Free Software * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA */ #include #ifdef SUPPORT_OSPF_API #include "linklist.h" #include "prefix.h" #include "if.h" #include "table.h" #include "memory.h" #include "command.h" #include "vty.h" #include "stream.h" #include "log.h" #include "thread.h" #include "hash.h" #include "sockunion.h" /* for inet_aton() */ #include "buffer.h" #include "network.h" #include "ospfd/ospfd.h" #include "ospfd/ospf_interface.h" #include "ospfd/ospf_ism.h" #include "ospfd/ospf_asbr.h" #include "ospfd/ospf_lsa.h" #include "ospfd/ospf_lsdb.h" #include "ospfd/ospf_neighbor.h" #include "ospfd/ospf_nsm.h" #include "ospfd/ospf_flood.h" #include "ospfd/ospf_packet.h" #include "ospfd/ospf_spf.h" #include "ospfd/ospf_dump.h" #include "ospfd/ospf_route.h" #include "ospfd/ospf_ase.h" #include "ospfd/ospf_zebra.h" #include "ospfd/ospf_api.h" /* For debugging only, will be removed */ void api_opaque_lsa_print(struct lsa_header *data) { struct opaque_lsa { struct lsa_header header; uint8_t mydata[]; }; struct opaque_lsa *olsa; int opaquelen; int i; ospf_lsa_header_dump(data); olsa = (struct opaque_lsa *)data; opaquelen = ntohs(data->length) - OSPF_LSA_HEADER_SIZE; zlog_debug("apiserver_lsa_print: opaquelen=%d", opaquelen); for (i = 0; i < opaquelen; i++) { zlog_debug("0x%x ", olsa->mydata[i]); } zlog_debug(" "); } /* ----------------------------------------------------------- * Generic messages * ----------------------------------------------------------- */ struct msg *msg_new(uint8_t msgtype, void *msgbody, uint32_t seqnum, uint16_t msglen) { struct msg *new; new = XCALLOC(MTYPE_OSPF_API_MSG, sizeof(struct msg)); new->hdr.version = OSPF_API_VERSION; new->hdr.msgtype = msgtype; new->hdr.msglen = htons(msglen); new->hdr.msgseq = htonl(seqnum); new->s = stream_new(msglen); assert(new->s); stream_put(new->s, msgbody, msglen); return new; } /* Duplicate a message by copying content. */ struct msg *msg_dup(struct msg *msg) { struct msg *new; assert(msg); new = msg_new(msg->hdr.msgtype, STREAM_DATA(msg->s), ntohl(msg->hdr.msgseq), ntohs(msg->hdr.msglen)); return new; } /* XXX only for testing, will be removed */ struct nametab { int value; const char *name; }; const char *ospf_api_typename(int msgtype) { struct nametab NameTab[] = { { MSG_REGISTER_OPAQUETYPE, "Register opaque-type", }, { MSG_UNREGISTER_OPAQUETYPE, "Unregister opaque-type", }, { MSG_REGISTER_EVENT, "Register event", }, { MSG_SYNC_LSDB, "Sync LSDB", }, { MSG_ORIGINATE_REQUEST, "Originate request", }, { MSG_DELETE_REQUEST, "Delete request", }, { MSG_REPLY, "Reply", }, { MSG_READY_NOTIFY, "Ready notify", }, { MSG_LSA_UPDATE_NOTIFY, "LSA update notify", }, { MSG_LSA_DELETE_NOTIFY, "LSA delete notify", }, { MSG_NEW_IF, "New interface", }, { MSG_DEL_IF, "Del interface", }, { MSG_ISM_CHANGE, "ISM change", }, { MSG_NSM_CHANGE, "NSM change", }, }; int i, n = array_size(NameTab); const char *name = NULL; for (i = 0; i < n; i++) { if (NameTab[i].value == msgtype) { name = NameTab[i].name; break; } } return name ? name : "?"; } const char *ospf_api_errname(int errcode) { struct nametab NameTab[] = { { OSPF_API_OK, "OK", }, { OSPF_API_NOSUCHINTERFACE, "No such interface", }, { OSPF_API_NOSUCHAREA, "No such area", }, { OSPF_API_NOSUCHLSA, "No such LSA", }, { OSPF_API_ILLEGALLSATYPE, "Illegal LSA type", }, { OSPF_API_OPAQUETYPEINUSE, "Opaque type in use", }, { OSPF_API_OPAQUETYPENOTREGISTERED, "Opaque type not registered", }, { OSPF_API_NOTREADY, "Not ready", }, { OSPF_API_NOMEMORY, "No memory", }, { OSPF_API_ERROR, "Other error", }, { OSPF_API_UNDEF, "Undefined", }, }; int i, n = array_size(NameTab); const char *name = NULL; for (i = 0; i < n; i++) { if (NameTab[i].value == errcode) { name = NameTab[i].name; break; } } return name ? name : "?"; } void msg_print(struct msg *msg) { if (!msg) { zlog_debug("msg_print msg=NULL!"); return; } #ifdef ORIGINAL_CODING zlog_debug( "msg=%p msgtype=%d msglen=%d msgseq=%d streamdata=%p streamsize=%lu\n", msg, msg->hdr.msgtype, ntohs(msg->hdr.msglen), ntohl(msg->hdr.msgseq), STREAM_DATA(msg->s), STREAM_SIZE(msg->s)); #else /* ORIGINAL_CODING */ /* API message common header part. */ zlog_debug("API-msg [%s]: type(%d),len(%d),seq(%lu),data(%p),size(%zd)", ospf_api_typename(msg->hdr.msgtype), msg->hdr.msgtype, ntohs(msg->hdr.msglen), (unsigned long)ntohl(msg->hdr.msgseq), STREAM_DATA(msg->s), STREAM_SIZE(msg->s)); /* API message body part. */ #ifdef ndef /* Generic Hex/Ascii dump */ DumpBuf(STREAM_DATA(msg->s), STREAM_SIZE(msg->s)); /* Sorry, deleted! */ #else /* ndef */ /* Message-type dependent dump function. */ #endif /* ndef */ return; #endif /* ORIGINAL_CODING */ } void msg_free(struct msg *msg) { if (msg->s) stream_free(msg->s); XFREE(MTYPE_OSPF_API_MSG, msg); } /* Set sequence number of message */ void msg_set_seq(struct msg *msg, uint32_t seqnr) { assert(msg); msg->hdr.msgseq = htonl(seqnr); } /* Get sequence number of message */ uint32_t msg_get_seq(struct msg *msg) { assert(msg); return ntohl(msg->hdr.msgseq); } /* ----------------------------------------------------------- * Message fifo queues * ----------------------------------------------------------- */ struct msg_fifo *msg_fifo_new(void) { return XCALLOC(MTYPE_OSPF_API_FIFO, sizeof(struct msg_fifo)); } /* Add new message to fifo. */ void msg_fifo_push(struct msg_fifo *fifo, struct msg *msg) { if (fifo->tail) fifo->tail->next = msg; else fifo->head = msg; fifo->tail = msg; fifo->count++; } /* Remove first message from fifo. */ struct msg *msg_fifo_pop(struct msg_fifo *fifo) { struct msg *msg; msg = fifo->head; if (msg) { fifo->head = msg->next; if (fifo->head == NULL) fifo->tail = NULL; fifo->count--; } return msg; } /* Return first fifo entry but do not remove it. */ struct msg *msg_fifo_head(struct msg_fifo *fifo) { return fifo->head; } /* Flush message fifo. */ void msg_fifo_flush(struct msg_fifo *fifo) { struct msg *op; struct msg *next; for (op = fifo->head; op; op = next) { next = op->next; msg_free(op); } fifo->head = fifo->tail = NULL; fifo->count = 0; } /* Free API message fifo. */ void msg_fifo_free(struct msg_fifo *fifo) { msg_fifo_flush(fifo); XFREE(MTYPE_OSPF_API_FIFO, fifo); } struct msg *msg_read(int fd) { struct msg *msg; struct apimsghdr hdr; uint8_t buf[OSPF_API_MAX_MSG_SIZE]; int bodylen; int rlen; /* Read message header */ rlen = readn(fd, (uint8_t *)&hdr, sizeof(struct apimsghdr)); if (rlen < 0) { zlog_warn("msg_read: readn %s", safe_strerror(errno)); return NULL; } else if (rlen == 0) { zlog_warn("msg_read: Connection closed by peer"); return NULL; } else if (rlen != sizeof(struct apimsghdr)) { zlog_warn("msg_read: Cannot read message header!"); return NULL; } /* Check version of API protocol */ if (hdr.version != OSPF_API_VERSION) { zlog_warn("msg_read: OSPF API protocol version mismatch"); return NULL; } /* Determine body length. */ bodylen = ntohs(hdr.msglen); if (bodylen > 0) { /* Read message body */ rlen = readn(fd, buf, bodylen); if (rlen < 0) { zlog_warn("msg_read: readn %s", safe_strerror(errno)); return NULL; } else if (rlen == 0) { zlog_warn("msg_read: Connection closed by peer"); return NULL; } else if (rlen != bodylen) { zlog_warn("msg_read: Cannot read message body!"); return NULL; } } /* Allocate new message */ msg = msg_new(hdr.msgtype, buf, ntohl(hdr.msgseq), ntohs(hdr.msglen)); return msg; } int msg_write(int fd, struct msg *msg) { uint8_t buf[OSPF_API_MAX_MSG_SIZE]; int l; int wlen; assert(msg); assert(msg->s); /* Length of message including header */ l = sizeof(struct apimsghdr) + ntohs(msg->hdr.msglen); /* Make contiguous memory buffer for message */ memcpy(buf, &msg->hdr, sizeof(struct apimsghdr)); memcpy(buf + sizeof(struct apimsghdr), STREAM_DATA(msg->s), ntohs(msg->hdr.msglen)); wlen = writen(fd, buf, l); if (wlen < 0) { zlog_warn("msg_write: writen %s", safe_strerror(errno)); return -1; } else if (wlen == 0) { zlog_warn("msg_write: Connection closed by peer"); return -1; } else if (wlen != l) { zlog_warn("msg_write: Cannot write API message"); return -1; } return 0; } /* ----------------------------------------------------------- * Specific messages * ----------------------------------------------------------- */ struct msg *new_msg_register_opaque_type(uint32_t seqnum, uint8_t ltype, uint8_t otype) { struct msg_register_opaque_type rmsg; rmsg.lsatype = ltype; rmsg.opaquetype = otype; memset(&rmsg.pad, 0, sizeof(rmsg.pad)); return msg_new(MSG_REGISTER_OPAQUETYPE, &rmsg, seqnum, sizeof(struct msg_register_opaque_type)); } struct msg *new_msg_register_event(uint32_t seqnum, struct lsa_filter_type *filter) { uint8_t buf[OSPF_API_MAX_MSG_SIZE]; struct msg_register_event *emsg; unsigned int len; emsg = (struct msg_register_event *)buf; len = sizeof(struct msg_register_event) + filter->num_areas * sizeof(struct in_addr); emsg->filter.typemask = htons(filter->typemask); emsg->filter.origin = filter->origin; emsg->filter.num_areas = filter->num_areas; if (len > sizeof(buf)) len = sizeof(buf); /* API broken - missing memcpy to fill data */ return msg_new(MSG_REGISTER_EVENT, emsg, seqnum, len); } struct msg *new_msg_sync_lsdb(uint32_t seqnum, struct lsa_filter_type *filter) { uint8_t buf[OSPF_API_MAX_MSG_SIZE]; struct msg_sync_lsdb *smsg; unsigned int len; smsg = (struct msg_sync_lsdb *)buf; len = sizeof(struct msg_sync_lsdb) + filter->num_areas * sizeof(struct in_addr); smsg->filter.typemask = htons(filter->typemask); smsg->filter.origin = filter->origin; smsg->filter.num_areas = filter->num_areas; if (len > sizeof(buf)) len = sizeof(buf); /* API broken - missing memcpy to fill data */ return msg_new(MSG_SYNC_LSDB, smsg, seqnum, len); } struct msg *new_msg_originate_request(uint32_t seqnum, struct in_addr ifaddr, struct in_addr area_id, struct lsa_header *data) { struct msg_originate_request *omsg; unsigned int omsglen; char buf[OSPF_API_MAX_MSG_SIZE]; size_t off_data = offsetof(struct msg_originate_request, data); size_t data_maxs = sizeof(buf) - off_data; struct lsa_header *omsg_data = (struct lsa_header *)&buf[off_data]; omsg = (struct msg_originate_request *)buf; omsg->ifaddr = ifaddr; omsg->area_id = area_id; omsglen = ntohs(data->length); if (omsglen > data_maxs) omsglen = data_maxs; memcpy(omsg_data, data, omsglen); omsglen += sizeof(struct msg_originate_request) - sizeof(struct lsa_header); return msg_new(MSG_ORIGINATE_REQUEST, omsg, seqnum, omsglen); } struct msg *new_msg_delete_request(uint32_t seqnum, struct in_addr area_id, uint8_t lsa_type, uint8_t opaque_type, uint32_t opaque_id) { struct msg_delete_request dmsg; dmsg.area_id = area_id; dmsg.lsa_type = lsa_type; dmsg.opaque_type = opaque_type; dmsg.opaque_id = htonl(opaque_id); memset(&dmsg.pad, 0, sizeof(dmsg.pad)); return msg_new(MSG_DELETE_REQUEST, &dmsg, seqnum, sizeof(struct msg_delete_request)); } struct msg *new_msg_reply(uint32_t seqnr, uint8_t rc) { struct msg *msg; struct msg_reply rmsg; /* Set return code */ rmsg.errcode = rc; memset(&rmsg.pad, 0, sizeof(rmsg.pad)); msg = msg_new(MSG_REPLY, &rmsg, seqnr, sizeof(struct msg_reply)); return msg; } struct msg *new_msg_ready_notify(uint32_t seqnr, uint8_t lsa_type, uint8_t opaque_type, struct in_addr addr) { struct msg_ready_notify rmsg; rmsg.lsa_type = lsa_type; rmsg.opaque_type = opaque_type; memset(&rmsg.pad, 0, sizeof(rmsg.pad)); rmsg.addr = addr; return msg_new(MSG_READY_NOTIFY, &rmsg, seqnr, sizeof(struct msg_ready_notify)); } struct msg *new_msg_new_if(uint32_t seqnr, struct in_addr ifaddr, struct in_addr area_id) { struct msg_new_if nmsg; nmsg.ifaddr = ifaddr; nmsg.area_id = area_id; return msg_new(MSG_NEW_IF, &nmsg, seqnr, sizeof(struct msg_new_if)); } struct msg *new_msg_del_if(uint32_t seqnr, struct in_addr ifaddr) { struct msg_del_if dmsg; dmsg.ifaddr = ifaddr; return msg_new(MSG_DEL_IF, &dmsg, seqnr, sizeof(struct msg_del_if)); } struct msg *new_msg_ism_change(uint32_t seqnr, struct in_addr ifaddr, struct in_addr area_id, uint8_t status) { struct msg_ism_change imsg; imsg.ifaddr = ifaddr; imsg.area_id = area_id; imsg.status = status; memset(&imsg.pad, 0, sizeof(imsg.pad)); return msg_new(MSG_ISM_CHANGE, &imsg, seqnr, sizeof(struct msg_ism_change)); } struct msg *new_msg_nsm_change(uint32_t seqnr, struct in_addr ifaddr, struct in_addr nbraddr, struct in_addr router_id, uint8_t status) { struct msg_nsm_change nmsg; nmsg.ifaddr = ifaddr; nmsg.nbraddr = nbraddr; nmsg.router_id = router_id; nmsg.status = status; memset(&nmsg.pad, 0, sizeof(nmsg.pad)); return msg_new(MSG_NSM_CHANGE, &nmsg, seqnr, sizeof(struct msg_nsm_change)); } struct msg *new_msg_lsa_change_notify(uint8_t msgtype, uint32_t seqnum, struct in_addr ifaddr, struct in_addr area_id, uint8_t is_self_originated, struct lsa_header *data) { uint8_t buf[OSPF_API_MAX_MSG_SIZE]; struct msg_lsa_change_notify *nmsg; unsigned int len; size_t off_data = offsetof(struct msg_lsa_change_notify, data); size_t data_maxs = sizeof(buf) - off_data; struct lsa_header *nmsg_data = (struct lsa_header *)&buf[off_data]; assert(data); nmsg = (struct msg_lsa_change_notify *)buf; nmsg->ifaddr = ifaddr; nmsg->area_id = area_id; nmsg->is_self_originated = is_self_originated; memset(&nmsg->pad, 0, sizeof(nmsg->pad)); len = ntohs(data->length); if (len > data_maxs) len = data_maxs; memcpy(nmsg_data, data, len); len += sizeof(struct msg_lsa_change_notify) - sizeof(struct lsa_header); return msg_new(msgtype, nmsg, seqnum, len); } #endif /* SUPPORT_OSPF_API */ frr-7.2.1/ospfd/ospf_api.h0000644000000000000000000002470713610377563012312 00000000000000/* * API message handling module for OSPF daemon and client. * Copyright (C) 2001, 2002 Ralph Keller * * This file is part of GNU Zebra. * * GNU Zebra is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License as published * by the Free Software Foundation; either version 2, or (at your * option) any later version. * * GNU Zebra 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; see the file COPYING; if not, write to the Free Software * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA */ /* This file is used both by the OSPFd and client applications to define message formats used for communication. */ #ifndef _OSPF_API_H #define _OSPF_API_H #define OSPF_API_VERSION 1 /* MTYPE definition is not reflected to "memory.h". */ #define MTYPE_OSPF_API_MSG MTYPE_TMP #define MTYPE_OSPF_API_FIFO MTYPE_TMP /* Default API server port to accept connection request from client-side. */ /* This value could be overridden by "ospfapi" entry in "/etc/services". */ #define OSPF_API_SYNC_PORT 2607 /* ----------------------------------------------------------- * Generic messages * ----------------------------------------------------------- */ /* Message header structure, fields are in network byte order and aligned to four octets. */ struct apimsghdr { uint8_t version; /* OSPF API protocol version */ uint8_t msgtype; /* Type of message */ uint16_t msglen; /* Length of message w/o header */ uint32_t msgseq; /* Sequence number */ }; /* Message representation with header and body */ struct msg { struct msg *next; /* to link into fifo */ /* Message header */ struct apimsghdr hdr; /* Message body */ struct stream *s; }; /* Prototypes for generic messages. */ extern struct msg *msg_new(uint8_t msgtype, void *msgbody, uint32_t seqnum, uint16_t msglen); extern struct msg *msg_dup(struct msg *msg); extern void msg_print(struct msg *msg); /* XXX debug only */ extern void msg_free(struct msg *msg); struct msg *msg_read(int fd); extern int msg_write(int fd, struct msg *msg); /* For requests, the message sequence number is between MIN_SEQ and MAX_SEQ. For notifications, the sequence number is 0. */ #define MIN_SEQ 1 #define MAX_SEQ 2147483647 extern void msg_set_seq(struct msg *msg, uint32_t seqnr); extern uint32_t msg_get_seq(struct msg *msg); /* ----------------------------------------------------------- * Message fifo queues * ----------------------------------------------------------- */ /* Message queue structure. */ struct msg_fifo { unsigned long count; struct msg *head; struct msg *tail; }; /* Prototype for message fifo queues. */ extern struct msg_fifo *msg_fifo_new(void); extern void msg_fifo_push(struct msg_fifo *, struct msg *msg); extern struct msg *msg_fifo_pop(struct msg_fifo *fifo); extern struct msg *msg_fifo_head(struct msg_fifo *fifo); extern void msg_fifo_flush(struct msg_fifo *fifo); extern void msg_fifo_free(struct msg_fifo *fifo); /* ----------------------------------------------------------- * Specific message type and format definitions * ----------------------------------------------------------- */ /* Messages to OSPF daemon. */ #define MSG_REGISTER_OPAQUETYPE 1 #define MSG_UNREGISTER_OPAQUETYPE 2 #define MSG_REGISTER_EVENT 3 #define MSG_SYNC_LSDB 4 #define MSG_ORIGINATE_REQUEST 5 #define MSG_DELETE_REQUEST 6 /* Messages from OSPF daemon. */ #define MSG_REPLY 10 #define MSG_READY_NOTIFY 11 #define MSG_LSA_UPDATE_NOTIFY 12 #define MSG_LSA_DELETE_NOTIFY 13 #define MSG_NEW_IF 14 #define MSG_DEL_IF 15 #define MSG_ISM_CHANGE 16 #define MSG_NSM_CHANGE 17 struct msg_register_opaque_type { uint8_t lsatype; uint8_t opaquetype; uint8_t pad[2]; /* padding */ }; struct msg_unregister_opaque_type { uint8_t lsatype; uint8_t opaquetype; uint8_t pad[2]; /* padding */ }; /* Power2 is needed to convert LSA types into bit positions, * see typemask below. Type definition starts at 1, so * Power2[0] is not used. */ #ifdef ORIGINAL_CODING static const uint16_t Power2[] = {0x0, 0x1, 0x2, 0x4, 0x8, 0x10, 0x20, 0x40, 0x80, 0x100, 0x200, 0x400, 0x800, 0x1000, 0x2000, 0x4000, 0x8000}; #else static const uint16_t Power2[] = { 0, (1 << 0), (1 << 1), (1 << 2), (1 << 3), (1 << 4), (1 << 5), (1 << 6), (1 << 7), (1 << 8), (1 << 9), (1 << 10), (1 << 11), (1 << 12), (1 << 13), (1 << 14), (1 << 15)}; #endif /* ORIGINAL_CODING */ struct lsa_filter_type { uint16_t typemask; /* bitmask for selecting LSA types (1..16) */ uint8_t origin; /* selects according to origin. */ #define NON_SELF_ORIGINATED 0 #define SELF_ORIGINATED (OSPF_LSA_SELF) #define ANY_ORIGIN 2 uint8_t num_areas; /* number of areas in the filter. */ /* areas, if any, go here. */ }; struct msg_register_event { struct lsa_filter_type filter; }; struct msg_sync_lsdb { struct lsa_filter_type filter; }; struct msg_originate_request { /* Used for LSA type 9 otherwise ignored */ struct in_addr ifaddr; /* Used for LSA type 10 otherwise ignored */ struct in_addr area_id; /* LSA header and LSA-specific part */ struct lsa_header data; }; struct msg_delete_request { struct in_addr area_id; /* "0.0.0.0" for AS-external opaque LSAs */ uint8_t lsa_type; uint8_t opaque_type; uint8_t pad[2]; /* padding */ uint32_t opaque_id; }; struct msg_reply { signed char errcode; #define OSPF_API_OK 0 #define OSPF_API_NOSUCHINTERFACE (-1) #define OSPF_API_NOSUCHAREA (-2) #define OSPF_API_NOSUCHLSA (-3) #define OSPF_API_ILLEGALLSATYPE (-4) #define OSPF_API_OPAQUETYPEINUSE (-5) #define OSPF_API_OPAQUETYPENOTREGISTERED (-6) #define OSPF_API_NOTREADY (-7) #define OSPF_API_NOMEMORY (-8) #define OSPF_API_ERROR (-9) #define OSPF_API_UNDEF (-10) uint8_t pad[3]; /* padding to four byte alignment */ }; /* Message to tell client application that it ospf daemon is * ready to accept opaque LSAs for a given interface or area. */ struct msg_ready_notify { uint8_t lsa_type; uint8_t opaque_type; uint8_t pad[2]; /* padding */ struct in_addr addr; /* interface address or area address */ }; /* These messages have a dynamic length depending on the embodied LSA. They are aligned to four octets. msg_lsa_change_notify is used for both LSA update and LSAs delete. */ struct msg_lsa_change_notify { /* Used for LSA type 9 otherwise ignored */ struct in_addr ifaddr; /* Area ID. Not valid for AS-External and Opaque11 LSAs. */ struct in_addr area_id; uint8_t is_self_originated; /* 1 if self originated. */ uint8_t pad[3]; struct lsa_header data; }; struct msg_new_if { struct in_addr ifaddr; /* interface IP address */ struct in_addr area_id; /* area this interface belongs to */ }; struct msg_del_if { struct in_addr ifaddr; /* interface IP address */ }; struct msg_ism_change { struct in_addr ifaddr; /* interface IP address */ struct in_addr area_id; /* area this interface belongs to */ uint8_t status; /* interface status (up/down) */ uint8_t pad[3]; /* not used */ }; struct msg_nsm_change { struct in_addr ifaddr; /* attached interface */ struct in_addr nbraddr; /* Neighbor interface address */ struct in_addr router_id; /* Router ID of neighbor */ uint8_t status; /* NSM status */ uint8_t pad[3]; }; /* We make use of a union to define a structure that covers all possible API messages. This allows us to find out how much memory needs to be reserved for the largest API message. */ struct apimsg { struct apimsghdr hdr; union { struct msg_register_opaque_type register_opaque_type; struct msg_register_event register_event; struct msg_sync_lsdb sync_lsdb; struct msg_originate_request originate_request; struct msg_delete_request delete_request; struct msg_reply reply; struct msg_ready_notify ready_notify; struct msg_new_if new_if; struct msg_del_if del_if; struct msg_ism_change ism_change; struct msg_nsm_change nsm_change; struct msg_lsa_change_notify lsa_change_notify; } u; }; #define OSPF_API_MAX_MSG_SIZE (sizeof(struct apimsg) + OSPF_MAX_LSA_SIZE) /* ----------------------------------------------------------- * Prototypes for specific messages * ----------------------------------------------------------- */ /* For debugging only. */ extern void api_opaque_lsa_print(struct lsa_header *data); /* Messages sent by client */ extern struct msg *new_msg_register_opaque_type(uint32_t seqnum, uint8_t ltype, uint8_t otype); extern struct msg *new_msg_register_event(uint32_t seqnum, struct lsa_filter_type *filter); extern struct msg *new_msg_sync_lsdb(uint32_t seqnum, struct lsa_filter_type *filter); extern struct msg *new_msg_originate_request(uint32_t seqnum, struct in_addr ifaddr, struct in_addr area_id, struct lsa_header *data); extern struct msg *new_msg_delete_request(uint32_t seqnum, struct in_addr area_id, uint8_t lsa_type, uint8_t opaque_type, uint32_t opaque_id); /* Messages sent by OSPF daemon */ extern struct msg *new_msg_reply(uint32_t seqnum, uint8_t rc); extern struct msg *new_msg_ready_notify(uint32_t seqnr, uint8_t lsa_type, uint8_t opaque_type, struct in_addr addr); extern struct msg *new_msg_new_if(uint32_t seqnr, struct in_addr ifaddr, struct in_addr area); extern struct msg *new_msg_del_if(uint32_t seqnr, struct in_addr ifaddr); extern struct msg *new_msg_ism_change(uint32_t seqnr, struct in_addr ifaddr, struct in_addr area, uint8_t status); extern struct msg *new_msg_nsm_change(uint32_t seqnr, struct in_addr ifaddr, struct in_addr nbraddr, struct in_addr router_id, uint8_t status); /* msgtype is MSG_LSA_UPDATE_NOTIFY or MSG_LSA_DELETE_NOTIFY */ extern struct msg *new_msg_lsa_change_notify(uint8_t msgtype, uint32_t seqnum, struct in_addr ifaddr, struct in_addr area_id, uint8_t is_self_originated, struct lsa_header *data); /* string printing functions */ extern const char *ospf_api_errname(int errcode); extern const char *ospf_api_typename(int msgtype); #endif /* _OSPF_API_H */ frr-7.2.1/ospfd/ospf_apiserver.c0000644000000000000000000017476713610377563013550 00000000000000/* * Server side of OSPF API. * Copyright (C) 2001, 2002 Ralph Keller * * This file is part of GNU Zebra. * * GNU Zebra is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License as published * by the Free Software Foundation; either version 2, or (at your * option) any later version. * * GNU Zebra 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; see the file COPYING; if not, write to the Free Software * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA */ #include #ifdef SUPPORT_OSPF_API #include "linklist.h" #include "prefix.h" #include "if.h" #include "table.h" #include "memory.h" #include "command.h" #include "vty.h" #include "stream.h" #include "log.h" #include "thread.h" #include "hash.h" #include "sockunion.h" /* for inet_aton() */ #include "buffer.h" #include #include "ospfd/ospfd.h" /* for "struct thread_master" */ #include "ospfd/ospf_interface.h" #include "ospfd/ospf_ism.h" #include "ospfd/ospf_asbr.h" #include "ospfd/ospf_lsa.h" #include "ospfd/ospf_lsdb.h" #include "ospfd/ospf_neighbor.h" #include "ospfd/ospf_nsm.h" #include "ospfd/ospf_flood.h" #include "ospfd/ospf_packet.h" #include "ospfd/ospf_spf.h" #include "ospfd/ospf_dump.h" #include "ospfd/ospf_route.h" #include "ospfd/ospf_ase.h" #include "ospfd/ospf_zebra.h" #include "ospfd/ospf_errors.h" #include "ospfd/ospf_api.h" #include "ospfd/ospf_apiserver.h" /* This is an implementation of an API to the OSPF daemon that allows * external applications to access the OSPF daemon through socket * connections. The application can use this API to inject its own * opaque LSAs and flood them to other OSPF daemons. Other OSPF * daemons then receive these LSAs and inform applications through the * API by sending a corresponding message. The application can also * register to receive all LSA types (in addition to opaque types) and * use this information to reconstruct the OSPF's LSDB. The OSPF * daemon supports multiple applications concurrently. */ /* List of all active connections. */ struct list *apiserver_list; /* ----------------------------------------------------------- * Functions to lookup interfaces * ----------------------------------------------------------- */ struct ospf_interface *ospf_apiserver_if_lookup_by_addr(struct in_addr address) { struct listnode *node, *nnode; struct ospf_interface *oi; struct ospf *ospf = NULL; ospf = ospf_lookup_by_vrf_id(VRF_DEFAULT); if (!ospf) return NULL; for (ALL_LIST_ELEMENTS(ospf->oiflist, node, nnode, oi)) if (oi->type != OSPF_IFTYPE_VIRTUALLINK) if (IPV4_ADDR_SAME(&address, &oi->address->u.prefix4)) return oi; return NULL; } struct ospf_interface *ospf_apiserver_if_lookup_by_ifp(struct interface *ifp) { struct listnode *node, *nnode; struct ospf_interface *oi; struct ospf *ospf = NULL; ospf = ospf_lookup_by_vrf_id(VRF_DEFAULT); if (!ospf) return NULL; for (ALL_LIST_ELEMENTS(ospf->oiflist, node, nnode, oi)) if (oi->ifp == ifp) return oi; return NULL; } /* ----------------------------------------------------------- * Initialization * ----------------------------------------------------------- */ unsigned short ospf_apiserver_getport(void) { struct servent *sp = getservbyname("ospfapi", "tcp"); return sp ? ntohs(sp->s_port) : OSPF_API_SYNC_PORT; } /* Initialize OSPF API module. Invoked from ospf_opaque_init() */ int ospf_apiserver_init(void) { int fd; int rc = -1; /* Create new socket for synchronous messages. */ fd = ospf_apiserver_serv_sock_family(ospf_apiserver_getport(), AF_INET); if (fd < 0) goto out; /* Schedule new thread that handles accepted connections. */ ospf_apiserver_event(OSPF_APISERVER_ACCEPT, fd, NULL); /* Initialize list that keeps track of all connections. */ apiserver_list = list_new(); /* Register opaque-independent call back functions. These functions are invoked on ISM, NSM changes and LSA update and LSA deletes */ rc = ospf_register_opaque_functab( 0 /* all LSAs */, 0 /* all opaque types */, ospf_apiserver_new_if, ospf_apiserver_del_if, ospf_apiserver_ism_change, ospf_apiserver_nsm_change, NULL, NULL, NULL, NULL, /* ospf_apiserver_show_info */ NULL, /* originator_func */ NULL, /* ospf_apiserver_lsa_refresher */ ospf_apiserver_lsa_update, ospf_apiserver_lsa_delete); if (rc != 0) { flog_warn( EC_OSPF_OPAQUE_REGISTRATION, "ospf_apiserver_init: Failed to register opaque type [0/0]"); } rc = 0; out: return rc; } /* Terminate OSPF API module. */ void ospf_apiserver_term(void) { struct ospf_apiserver *apiserv; /* Unregister wildcard [0/0] type */ ospf_delete_opaque_functab(0 /* all LSAs */, 0 /* all opaque types */); /* * Free all client instances. ospf_apiserver_free removes the node * from the list, so we examine the head of the list anew each time. */ while (apiserver_list && (apiserv = listgetdata(listhead(apiserver_list))) != NULL) ospf_apiserver_free(apiserv); /* Free client list itself */ if (apiserver_list) list_delete(&apiserver_list); /* Free wildcard list */ /* XXX */ } static struct ospf_apiserver *lookup_apiserver(uint8_t lsa_type, uint8_t opaque_type) { struct listnode *n1, *n2; struct registered_opaque_type *r; struct ospf_apiserver *apiserv, *found = NULL; /* XXX: this approaches O(n**2) */ for (ALL_LIST_ELEMENTS_RO(apiserver_list, n1, apiserv)) { for (ALL_LIST_ELEMENTS_RO(apiserv->opaque_types, n2, r)) if (r->lsa_type == lsa_type && r->opaque_type == opaque_type) { found = apiserv; goto out; } } out: return found; } static struct ospf_apiserver *lookup_apiserver_by_lsa(struct ospf_lsa *lsa) { struct lsa_header *lsah = lsa->data; struct ospf_apiserver *found = NULL; if (IS_OPAQUE_LSA(lsah->type)) { found = lookup_apiserver( lsah->type, GET_OPAQUE_TYPE(ntohl(lsah->id.s_addr))); } return found; } /* ----------------------------------------------------------- * Followings are functions to manage client connections. * ----------------------------------------------------------- */ static int ospf_apiserver_new_lsa_hook(struct ospf_lsa *lsa) { if (IS_DEBUG_OSPF_EVENT) zlog_debug("API: Put LSA(%p)[%s] into reserve, total=%ld", (void *)lsa, dump_lsa_key(lsa), lsa->lsdb->total); return 0; } static int ospf_apiserver_del_lsa_hook(struct ospf_lsa *lsa) { if (IS_DEBUG_OSPF_EVENT) zlog_debug("API: Get LSA(%p)[%s] from reserve, total=%ld", (void *)lsa, dump_lsa_key(lsa), lsa->lsdb->total); return 0; } /* Allocate new connection structure. */ struct ospf_apiserver *ospf_apiserver_new(int fd_sync, int fd_async) { struct ospf_apiserver *new = XMALLOC(MTYPE_OSPF_APISERVER, sizeof(struct ospf_apiserver)); new->filter = XMALLOC(MTYPE_OSPF_APISERVER_MSGFILTER, sizeof(struct lsa_filter_type)); new->fd_sync = fd_sync; new->fd_async = fd_async; /* list of registered opaque types that application uses */ new->opaque_types = list_new(); /* Initialize temporary strage for LSA instances to be refreshed. */ memset(&new->reserve, 0, sizeof(struct ospf_lsdb)); ospf_lsdb_init(&new->reserve); new->reserve.new_lsa_hook = ospf_apiserver_new_lsa_hook; /* debug */ new->reserve.del_lsa_hook = ospf_apiserver_del_lsa_hook; /* debug */ new->out_sync_fifo = msg_fifo_new(); new->out_async_fifo = msg_fifo_new(); new->t_sync_read = NULL; #ifdef USE_ASYNC_READ new->t_async_read = NULL; #endif /* USE_ASYNC_READ */ new->t_sync_write = NULL; new->t_async_write = NULL; new->filter->typemask = 0; /* filter all LSAs */ new->filter->origin = ANY_ORIGIN; new->filter->num_areas = 0; return new; } void ospf_apiserver_event(enum event event, int fd, struct ospf_apiserver *apiserv) { switch (event) { case OSPF_APISERVER_ACCEPT: (void)thread_add_read(master, ospf_apiserver_accept, apiserv, fd, NULL); break; case OSPF_APISERVER_SYNC_READ: apiserv->t_sync_read = NULL; thread_add_read(master, ospf_apiserver_read, apiserv, fd, &apiserv->t_sync_read); break; #ifdef USE_ASYNC_READ case OSPF_APISERVER_ASYNC_READ: apiserv->t_async_read = NULL; thread_add_read(master, ospf_apiserver_read, apiserv, fd, &apiserv->t_async_read); break; #endif /* USE_ASYNC_READ */ case OSPF_APISERVER_SYNC_WRITE: thread_add_write(master, ospf_apiserver_sync_write, apiserv, fd, &apiserv->t_sync_write); break; case OSPF_APISERVER_ASYNC_WRITE: thread_add_write(master, ospf_apiserver_async_write, apiserv, fd, &apiserv->t_async_write); break; } } /* Free instance. First unregister all opaque types used by application, flush opaque LSAs injected by application from network and close connection. */ void ospf_apiserver_free(struct ospf_apiserver *apiserv) { struct listnode *node; /* Cancel read and write threads. */ if (apiserv->t_sync_read) { thread_cancel(apiserv->t_sync_read); } #ifdef USE_ASYNC_READ if (apiserv->t_async_read) { thread_cancel(apiserv->t_async_read); } #endif /* USE_ASYNC_READ */ if (apiserv->t_sync_write) { thread_cancel(apiserv->t_sync_write); } if (apiserv->t_async_write) { thread_cancel(apiserv->t_async_write); } /* Unregister all opaque types that application registered and flush opaque LSAs if still in LSDB. */ while ((node = listhead(apiserv->opaque_types)) != NULL) { struct registered_opaque_type *regtype = listgetdata(node); ospf_apiserver_unregister_opaque_type( apiserv, regtype->lsa_type, regtype->opaque_type); } /* Close connections to OSPFd. */ if (apiserv->fd_sync > 0) { close(apiserv->fd_sync); } if (apiserv->fd_async > 0) { close(apiserv->fd_async); } /* Free fifos */ msg_fifo_free(apiserv->out_sync_fifo); msg_fifo_free(apiserv->out_async_fifo); /* Clear temporary strage for LSA instances to be refreshed. */ ospf_lsdb_delete_all(&apiserv->reserve); ospf_lsdb_cleanup(&apiserv->reserve); /* Remove from the list of active clients. */ listnode_delete(apiserver_list, apiserv); if (IS_DEBUG_OSPF_EVENT) zlog_debug("API: Delete apiserv(%p), total#(%d)", (void *)apiserv, apiserver_list->count); /* And free instance. */ XFREE(MTYPE_OSPF_APISERVER, apiserv); } int ospf_apiserver_read(struct thread *thread) { struct ospf_apiserver *apiserv; struct msg *msg; int fd; int rc = -1; enum event event; apiserv = THREAD_ARG(thread); fd = THREAD_FD(thread); if (fd == apiserv->fd_sync) { event = OSPF_APISERVER_SYNC_READ; apiserv->t_sync_read = NULL; if (IS_DEBUG_OSPF_EVENT) zlog_debug("API: ospf_apiserver_read: Peer: %s/%u", inet_ntoa(apiserv->peer_sync.sin_addr), ntohs(apiserv->peer_sync.sin_port)); } #ifdef USE_ASYNC_READ else if (fd == apiserv->fd_async) { event = OSPF_APISERVER_ASYNC_READ; apiserv->t_async_read = NULL; if (IS_DEBUG_OSPF_EVENT) zlog_debug("API: ospf_apiserver_read: Peer: %s/%u", inet_ntoa(apiserv->peer_async.sin_addr), ntohs(apiserv->peer_async.sin_port)); } #endif /* USE_ASYNC_READ */ else { zlog_warn("ospf_apiserver_read: Unknown fd(%d)", fd); ospf_apiserver_free(apiserv); goto out; } /* Read message from fd. */ msg = msg_read(fd); if (msg == NULL) { zlog_warn( "ospf_apiserver_read: read failed on fd=%d, closing connection", fd); /* Perform cleanup. */ ospf_apiserver_free(apiserv); goto out; } if (IS_DEBUG_OSPF_EVENT) msg_print(msg); /* Dispatch to corresponding message handler. */ rc = ospf_apiserver_handle_msg(apiserv, msg); /* Prepare for next message, add read thread. */ ospf_apiserver_event(event, fd, apiserv); msg_free(msg); out: return rc; } int ospf_apiserver_sync_write(struct thread *thread) { struct ospf_apiserver *apiserv; struct msg *msg; int fd; int rc = -1; apiserv = THREAD_ARG(thread); assert(apiserv); fd = THREAD_FD(thread); apiserv->t_sync_write = NULL; /* Sanity check */ if (fd != apiserv->fd_sync) { zlog_warn("ospf_apiserver_sync_write: Unknown fd=%d", fd); goto out; } if (IS_DEBUG_OSPF_EVENT) zlog_debug("API: ospf_apiserver_sync_write: Peer: %s/%u", inet_ntoa(apiserv->peer_sync.sin_addr), ntohs(apiserv->peer_sync.sin_port)); /* Check whether there is really a message in the fifo. */ msg = msg_fifo_pop(apiserv->out_sync_fifo); if (!msg) { zlog_warn( "API: ospf_apiserver_sync_write: No message in Sync-FIFO?"); return 0; } if (IS_DEBUG_OSPF_EVENT) msg_print(msg); rc = msg_write(fd, msg); /* Once a message is dequeued, it should be freed anyway. */ msg_free(msg); if (rc < 0) { zlog_warn("ospf_apiserver_sync_write: write failed on fd=%d", fd); goto out; } /* If more messages are in sync message fifo, schedule write thread. */ if (msg_fifo_head(apiserv->out_sync_fifo)) { ospf_apiserver_event(OSPF_APISERVER_SYNC_WRITE, apiserv->fd_sync, apiserv); } out: if (rc < 0) { /* Perform cleanup and disconnect with peer */ ospf_apiserver_free(apiserv); } return rc; } int ospf_apiserver_async_write(struct thread *thread) { struct ospf_apiserver *apiserv; struct msg *msg; int fd; int rc = -1; apiserv = THREAD_ARG(thread); assert(apiserv); fd = THREAD_FD(thread); apiserv->t_async_write = NULL; /* Sanity check */ if (fd != apiserv->fd_async) { zlog_warn("ospf_apiserver_async_write: Unknown fd=%d", fd); goto out; } if (IS_DEBUG_OSPF_EVENT) zlog_debug("API: ospf_apiserver_async_write: Peer: %s/%u", inet_ntoa(apiserv->peer_async.sin_addr), ntohs(apiserv->peer_async.sin_port)); /* Check whether there is really a message in the fifo. */ msg = msg_fifo_pop(apiserv->out_async_fifo); if (!msg) { zlog_warn( "API: ospf_apiserver_async_write: No message in Async-FIFO?"); return 0; } if (IS_DEBUG_OSPF_EVENT) msg_print(msg); rc = msg_write(fd, msg); /* Once a message is dequeued, it should be freed anyway. */ msg_free(msg); if (rc < 0) { zlog_warn("ospf_apiserver_async_write: write failed on fd=%d", fd); goto out; } /* If more messages are in async message fifo, schedule write thread. */ if (msg_fifo_head(apiserv->out_async_fifo)) { ospf_apiserver_event(OSPF_APISERVER_ASYNC_WRITE, apiserv->fd_async, apiserv); } out: if (rc < 0) { /* Perform cleanup and disconnect with peer */ ospf_apiserver_free(apiserv); } return rc; } int ospf_apiserver_serv_sock_family(unsigned short port, int family) { union sockunion su; int accept_sock; int rc; memset(&su, 0, sizeof(union sockunion)); su.sa.sa_family = family; /* Make new socket */ accept_sock = sockunion_stream_socket(&su); if (accept_sock < 0) return accept_sock; /* This is a server, so reuse address and port */ sockopt_reuseaddr(accept_sock); sockopt_reuseport(accept_sock); /* Bind socket to address and given port. */ rc = sockunion_bind(accept_sock, &su, port, NULL); if (rc < 0) { close(accept_sock); /* Close socket */ return rc; } /* Listen socket under queue length 3. */ rc = listen(accept_sock, 3); if (rc < 0) { zlog_warn("ospf_apiserver_serv_sock_family: listen: %s", safe_strerror(errno)); close(accept_sock); /* Close socket */ return rc; } return accept_sock; } /* Accept connection request from external applications. For each accepted connection allocate own connection instance. */ int ospf_apiserver_accept(struct thread *thread) { int accept_sock; int new_sync_sock; int new_async_sock; union sockunion su; struct ospf_apiserver *apiserv; struct sockaddr_in peer_async; struct sockaddr_in peer_sync; unsigned int peerlen; int ret; /* THREAD_ARG (thread) is NULL */ accept_sock = THREAD_FD(thread); /* Keep hearing on socket for further connections. */ ospf_apiserver_event(OSPF_APISERVER_ACCEPT, accept_sock, NULL); memset(&su, 0, sizeof(union sockunion)); /* Accept connection for synchronous messages */ new_sync_sock = sockunion_accept(accept_sock, &su); if (new_sync_sock < 0) { zlog_warn("ospf_apiserver_accept: accept: %s", safe_strerror(errno)); return -1; } /* Get port address and port number of peer to make reverse connection. The reverse channel uses the port number of the peer port+1. */ memset(&peer_sync, 0, sizeof(struct sockaddr_in)); peerlen = sizeof(struct sockaddr_in); ret = getpeername(new_sync_sock, (struct sockaddr *)&peer_sync, &peerlen); if (ret < 0) { zlog_warn("ospf_apiserver_accept: getpeername: %s", safe_strerror(errno)); close(new_sync_sock); return -1; } if (IS_DEBUG_OSPF_EVENT) zlog_debug("API: ospf_apiserver_accept: New peer: %s/%u", inet_ntoa(peer_sync.sin_addr), ntohs(peer_sync.sin_port)); /* Create new socket for asynchronous messages. */ peer_async = peer_sync; peer_async.sin_port = htons(ntohs(peer_sync.sin_port) + 1); /* Check if remote port number to make reverse connection is valid one. */ if (ntohs(peer_async.sin_port) == ospf_apiserver_getport()) { zlog_warn( "API: ospf_apiserver_accept: Peer(%s/%u): Invalid async port number?", inet_ntoa(peer_async.sin_addr), ntohs(peer_async.sin_port)); close(new_sync_sock); return -1; } new_async_sock = socket(AF_INET, SOCK_STREAM, 0); if (new_async_sock < 0) { zlog_warn("ospf_apiserver_accept: socket: %s", safe_strerror(errno)); close(new_sync_sock); return -1; } ret = connect(new_async_sock, (struct sockaddr *)&peer_async, sizeof(struct sockaddr_in)); if (ret < 0) { zlog_warn("ospf_apiserver_accept: connect: %s", safe_strerror(errno)); close(new_sync_sock); close(new_async_sock); return -1; } #ifdef USE_ASYNC_READ #else /* USE_ASYNC_READ */ /* Make the asynchronous channel write-only. */ ret = shutdown(new_async_sock, SHUT_RD); if (ret < 0) { zlog_warn("ospf_apiserver_accept: shutdown: %s", safe_strerror(errno)); close(new_sync_sock); close(new_async_sock); return -1; } #endif /* USE_ASYNC_READ */ /* Allocate new server-side connection structure */ apiserv = ospf_apiserver_new(new_sync_sock, new_async_sock); /* Add to active connection list */ listnode_add(apiserver_list, apiserv); apiserv->peer_sync = peer_sync; apiserv->peer_async = peer_async; /* And add read threads for new connection */ ospf_apiserver_event(OSPF_APISERVER_SYNC_READ, new_sync_sock, apiserv); #ifdef USE_ASYNC_READ ospf_apiserver_event(OSPF_APISERVER_ASYNC_READ, new_async_sock, apiserv); #endif /* USE_ASYNC_READ */ if (IS_DEBUG_OSPF_EVENT) zlog_debug("API: New apiserv(%p), total#(%d)", (void *)apiserv, apiserver_list->count); return 0; } /* ----------------------------------------------------------- * Send reply with return code to client application * ----------------------------------------------------------- */ static int ospf_apiserver_send_msg(struct ospf_apiserver *apiserv, struct msg *msg) { struct msg_fifo *fifo; struct msg *msg2; enum event event; int fd; switch (msg->hdr.msgtype) { case MSG_REPLY: fifo = apiserv->out_sync_fifo; fd = apiserv->fd_sync; event = OSPF_APISERVER_SYNC_WRITE; break; case MSG_READY_NOTIFY: case MSG_LSA_UPDATE_NOTIFY: case MSG_LSA_DELETE_NOTIFY: case MSG_NEW_IF: case MSG_DEL_IF: case MSG_ISM_CHANGE: case MSG_NSM_CHANGE: fifo = apiserv->out_async_fifo; fd = apiserv->fd_async; event = OSPF_APISERVER_ASYNC_WRITE; break; default: zlog_warn("ospf_apiserver_send_msg: Unknown message type %d", msg->hdr.msgtype); return -1; } /* Make a copy of the message and put in the fifo. Once the fifo gets drained by the write thread, the message will be freed. */ /* NB: Given "msg" is untouched in this function. */ msg2 = msg_dup(msg); /* Enqueue message into corresponding fifo queue */ msg_fifo_push(fifo, msg2); /* Schedule write thread */ ospf_apiserver_event(event, fd, apiserv); return 0; } int ospf_apiserver_send_reply(struct ospf_apiserver *apiserv, uint32_t seqnr, uint8_t rc) { struct msg *msg = new_msg_reply(seqnr, rc); int ret; if (!msg) { zlog_warn("ospf_apiserver_send_reply: msg_new failed"); #ifdef NOTYET /* Cannot allocate new message. What should we do? */ ospf_apiserver_free(apiserv); #endif return -1; } ret = ospf_apiserver_send_msg(apiserv, msg); msg_free(msg); return ret; } /* ----------------------------------------------------------- * Generic message dispatching handler function * ----------------------------------------------------------- */ int ospf_apiserver_handle_msg(struct ospf_apiserver *apiserv, struct msg *msg) { int rc; /* Call corresponding message handler function. */ switch (msg->hdr.msgtype) { case MSG_REGISTER_OPAQUETYPE: rc = ospf_apiserver_handle_register_opaque_type(apiserv, msg); break; case MSG_UNREGISTER_OPAQUETYPE: rc = ospf_apiserver_handle_unregister_opaque_type(apiserv, msg); break; case MSG_REGISTER_EVENT: rc = ospf_apiserver_handle_register_event(apiserv, msg); break; case MSG_SYNC_LSDB: rc = ospf_apiserver_handle_sync_lsdb(apiserv, msg); break; case MSG_ORIGINATE_REQUEST: rc = ospf_apiserver_handle_originate_request(apiserv, msg); break; case MSG_DELETE_REQUEST: rc = ospf_apiserver_handle_delete_request(apiserv, msg); break; default: zlog_warn("ospf_apiserver_handle_msg: Unknown message type: %d", msg->hdr.msgtype); rc = -1; } return rc; } /* ----------------------------------------------------------- * Following are functions for opaque type registration * ----------------------------------------------------------- */ int ospf_apiserver_register_opaque_type(struct ospf_apiserver *apiserv, uint8_t lsa_type, uint8_t opaque_type) { struct registered_opaque_type *regtype; int (*originator_func)(void *arg); int rc; switch (lsa_type) { case OSPF_OPAQUE_LINK_LSA: originator_func = ospf_apiserver_lsa9_originator; break; case OSPF_OPAQUE_AREA_LSA: originator_func = ospf_apiserver_lsa10_originator; break; case OSPF_OPAQUE_AS_LSA: originator_func = ospf_apiserver_lsa11_originator; break; default: zlog_warn("ospf_apiserver_register_opaque_type: lsa_type(%d)", lsa_type); return OSPF_API_ILLEGALLSATYPE; } /* Register opaque function table */ /* NB: Duplicated registration will be detected inside the function. */ rc = ospf_register_opaque_functab( lsa_type, opaque_type, NULL, /* ospf_apiserver_new_if */ NULL, /* ospf_apiserver_del_if */ NULL, /* ospf_apiserver_ism_change */ NULL, /* ospf_apiserver_nsm_change */ NULL, NULL, NULL, ospf_apiserver_show_info, originator_func, ospf_apiserver_lsa_refresher, NULL, /* ospf_apiserver_lsa_update */ NULL /* ospf_apiserver_lsa_delete */); if (rc != 0) { flog_warn(EC_OSPF_OPAQUE_REGISTRATION, "Failed to register opaque type [%d/%d]", lsa_type, opaque_type); return OSPF_API_OPAQUETYPEINUSE; } /* Remember the opaque type that application registers so when connection shuts down, we can flush all LSAs of this opaque type. */ regtype = XCALLOC(MTYPE_OSPF_APISERVER, sizeof(struct registered_opaque_type)); regtype->lsa_type = lsa_type; regtype->opaque_type = opaque_type; /* Add to list of registered opaque types */ listnode_add(apiserv->opaque_types, regtype); if (IS_DEBUG_OSPF_EVENT) zlog_debug( "API: Add LSA-type(%d)/Opaque-type(%d) into" " apiserv(%p), total#(%d)", lsa_type, opaque_type, (void *)apiserv, listcount(apiserv->opaque_types)); return 0; } int ospf_apiserver_unregister_opaque_type(struct ospf_apiserver *apiserv, uint8_t lsa_type, uint8_t opaque_type) { struct listnode *node, *nnode; struct registered_opaque_type *regtype; for (ALL_LIST_ELEMENTS(apiserv->opaque_types, node, nnode, regtype)) { /* Check if we really registered this opaque type */ if (regtype->lsa_type == lsa_type && regtype->opaque_type == opaque_type) { /* Yes, we registered this opaque type. Flush all existing opaque LSAs of this type */ ospf_apiserver_flush_opaque_lsa(apiserv, lsa_type, opaque_type); ospf_delete_opaque_functab(lsa_type, opaque_type); /* Remove from list of registered opaque types */ listnode_delete(apiserv->opaque_types, regtype); if (IS_DEBUG_OSPF_EVENT) zlog_debug( "API: Del LSA-type(%d)/Opaque-type(%d)" " from apiserv(%p), total#(%d)", lsa_type, opaque_type, (void *)apiserv, listcount(apiserv->opaque_types)); return 0; } } /* Opaque type is not registered */ zlog_warn("Failed to unregister opaque type [%d/%d]", lsa_type, opaque_type); return OSPF_API_OPAQUETYPENOTREGISTERED; } static int apiserver_is_opaque_type_registered(struct ospf_apiserver *apiserv, uint8_t lsa_type, uint8_t opaque_type) { struct listnode *node, *nnode; struct registered_opaque_type *regtype; /* XXX: how many types are there? if few, why not just a bitmap? */ for (ALL_LIST_ELEMENTS(apiserv->opaque_types, node, nnode, regtype)) { /* Check if we really registered this opaque type */ if (regtype->lsa_type == lsa_type && regtype->opaque_type == opaque_type) { /* Yes registered */ return 1; } } /* Not registered */ return 0; } int ospf_apiserver_handle_register_opaque_type(struct ospf_apiserver *apiserv, struct msg *msg) { struct msg_register_opaque_type *rmsg; uint8_t lsa_type; uint8_t opaque_type; int rc = 0; /* Extract parameters from register opaque type message */ rmsg = (struct msg_register_opaque_type *)STREAM_DATA(msg->s); lsa_type = rmsg->lsatype; opaque_type = rmsg->opaquetype; rc = ospf_apiserver_register_opaque_type(apiserv, lsa_type, opaque_type); /* Send a reply back to client including return code */ rc = ospf_apiserver_send_reply(apiserv, ntohl(msg->hdr.msgseq), rc); if (rc < 0) goto out; /* Now inform application about opaque types that are ready */ switch (lsa_type) { case OSPF_OPAQUE_LINK_LSA: ospf_apiserver_notify_ready_type9(apiserv); break; case OSPF_OPAQUE_AREA_LSA: ospf_apiserver_notify_ready_type10(apiserv); break; case OSPF_OPAQUE_AS_LSA: ospf_apiserver_notify_ready_type11(apiserv); break; } out: return rc; } /* Notify specific client about all opaque types 9 that are ready. */ void ospf_apiserver_notify_ready_type9(struct ospf_apiserver *apiserv) { struct listnode *node, *nnode; struct listnode *node2, *nnode2; struct ospf *ospf; struct ospf_interface *oi; struct registered_opaque_type *r; ospf = ospf_lookup_by_vrf_id(VRF_DEFAULT); for (ALL_LIST_ELEMENTS(ospf->oiflist, node, nnode, oi)) { /* Check if this interface is indeed ready for type 9 */ if (!ospf_apiserver_is_ready_type9(oi)) continue; /* Check for registered opaque type 9 types */ /* XXX: loop-de-loop - optimise me */ for (ALL_LIST_ELEMENTS(apiserv->opaque_types, node2, nnode2, r)) { struct msg *msg; if (r->lsa_type == OSPF_OPAQUE_LINK_LSA) { /* Yes, this opaque type is ready */ msg = new_msg_ready_notify( 0, OSPF_OPAQUE_LINK_LSA, r->opaque_type, oi->address->u.prefix4); if (!msg) { zlog_warn( "apiserver_notify_ready_type9: msg_new failed"); #ifdef NOTYET /* Cannot allocate new message. What * should we do? */ ospf_apiserver_free(apiserv); #endif goto out; } ospf_apiserver_send_msg(apiserv, msg); msg_free(msg); } } } out: return; } /* Notify specific client about all opaque types 10 that are ready. */ void ospf_apiserver_notify_ready_type10(struct ospf_apiserver *apiserv) { struct listnode *node, *nnode; struct listnode *node2, *nnode2; struct ospf *ospf; struct ospf_area *area; ospf = ospf_lookup_by_vrf_id(VRF_DEFAULT); for (ALL_LIST_ELEMENTS(ospf->areas, node, nnode, area)) { struct registered_opaque_type *r; if (!ospf_apiserver_is_ready_type10(area)) { continue; } /* Check for registered opaque type 10 types */ /* XXX: loop in loop - optimise me */ for (ALL_LIST_ELEMENTS(apiserv->opaque_types, node2, nnode2, r)) { struct msg *msg; if (r->lsa_type == OSPF_OPAQUE_AREA_LSA) { /* Yes, this opaque type is ready */ msg = new_msg_ready_notify( 0, OSPF_OPAQUE_AREA_LSA, r->opaque_type, area->area_id); if (!msg) { zlog_warn( "apiserver_notify_ready_type10: msg_new failed"); #ifdef NOTYET /* Cannot allocate new message. What * should we do? */ ospf_apiserver_free(apiserv); #endif goto out; } ospf_apiserver_send_msg(apiserv, msg); msg_free(msg); } } } out: return; } /* Notify specific client about all opaque types 11 that are ready */ void ospf_apiserver_notify_ready_type11(struct ospf_apiserver *apiserv) { struct listnode *node, *nnode; struct ospf *ospf; struct registered_opaque_type *r; ospf = ospf_lookup_by_vrf_id(VRF_DEFAULT); /* Can type 11 be originated? */ if (!ospf_apiserver_is_ready_type11(ospf)) goto out; /* Check for registered opaque type 11 types */ for (ALL_LIST_ELEMENTS(apiserv->opaque_types, node, nnode, r)) { struct msg *msg; struct in_addr noarea_id = {.s_addr = 0L}; if (r->lsa_type == OSPF_OPAQUE_AS_LSA) { /* Yes, this opaque type is ready */ msg = new_msg_ready_notify(0, OSPF_OPAQUE_AS_LSA, r->opaque_type, noarea_id); if (!msg) { zlog_warn( "apiserver_notify_ready_type11: msg_new failed"); #ifdef NOTYET /* Cannot allocate new message. What should we * do? */ ospf_apiserver_free(apiserv); #endif goto out; } ospf_apiserver_send_msg(apiserv, msg); msg_free(msg); } } out: return; } int ospf_apiserver_handle_unregister_opaque_type(struct ospf_apiserver *apiserv, struct msg *msg) { struct msg_unregister_opaque_type *umsg; uint8_t ltype; uint8_t otype; int rc = 0; /* Extract parameters from unregister opaque type message */ umsg = (struct msg_unregister_opaque_type *)STREAM_DATA(msg->s); ltype = umsg->lsatype; otype = umsg->opaquetype; rc = ospf_apiserver_unregister_opaque_type(apiserv, ltype, otype); /* Send a reply back to client including return code */ rc = ospf_apiserver_send_reply(apiserv, ntohl(msg->hdr.msgseq), rc); return rc; } /* ----------------------------------------------------------- * Following are functions for event (filter) registration. * ----------------------------------------------------------- */ int ospf_apiserver_handle_register_event(struct ospf_apiserver *apiserv, struct msg *msg) { struct msg_register_event *rmsg; int rc; uint32_t seqnum; rmsg = (struct msg_register_event *)STREAM_DATA(msg->s); /* Get request sequence number */ seqnum = msg_get_seq(msg); /* Free existing filter in apiserv. */ XFREE(MTYPE_OSPF_APISERVER_MSGFILTER, apiserv->filter); /* Alloc new space for filter. */ apiserv->filter = XMALLOC(MTYPE_OSPF_APISERVER_MSGFILTER, ntohs(msg->hdr.msglen)); /* copy it over. */ memcpy(apiserv->filter, &rmsg->filter, ntohs(msg->hdr.msglen)); rc = OSPF_API_OK; /* Send a reply back to client with return code */ rc = ospf_apiserver_send_reply(apiserv, seqnum, rc); return rc; } /* ----------------------------------------------------------- * Followings are functions for LSDB synchronization. * ----------------------------------------------------------- */ static int apiserver_sync_callback(struct ospf_lsa *lsa, void *p_arg, int int_arg) { struct ospf_apiserver *apiserv; int seqnum; struct msg *msg; struct param_t { struct ospf_apiserver *apiserv; struct lsa_filter_type *filter; } * param; int rc = -1; /* Sanity check */ assert(lsa->data); assert(p_arg); param = (struct param_t *)p_arg; apiserv = param->apiserv; seqnum = (uint32_t)int_arg; /* Check origin in filter. */ if ((param->filter->origin == ANY_ORIGIN) || (param->filter->origin == (lsa->flags & OSPF_LSA_SELF))) { /* Default area for AS-External and Opaque11 LSAs */ struct in_addr area_id = {.s_addr = 0L}; /* Default interface for non Opaque9 LSAs */ struct in_addr ifaddr = {.s_addr = 0L}; if (lsa->area) { area_id = lsa->area->area_id; } if (lsa->data->type == OSPF_OPAQUE_LINK_LSA) { ifaddr = lsa->oi->address->u.prefix4; } msg = new_msg_lsa_change_notify( MSG_LSA_UPDATE_NOTIFY, seqnum, ifaddr, area_id, lsa->flags & OSPF_LSA_SELF, lsa->data); if (!msg) { zlog_warn( "apiserver_sync_callback: new_msg_update failed"); #ifdef NOTYET /* Cannot allocate new message. What should we do? */ /* ospf_apiserver_free (apiserv);*/ /* Do nothing here XXX */ #endif goto out; } /* Send LSA */ ospf_apiserver_send_msg(apiserv, msg); msg_free(msg); } rc = 0; out: return rc; } int ospf_apiserver_handle_sync_lsdb(struct ospf_apiserver *apiserv, struct msg *msg) { struct listnode *node, *nnode; uint32_t seqnum; int rc = 0; struct msg_sync_lsdb *smsg; struct ospf_apiserver_param_t { struct ospf_apiserver *apiserv; struct lsa_filter_type *filter; } param; uint16_t mask; struct route_node *rn; struct ospf_lsa *lsa; struct ospf *ospf; struct ospf_area *area; ospf = ospf_lookup_by_vrf_id(VRF_DEFAULT); /* Get request sequence number */ seqnum = msg_get_seq(msg); /* Set sync msg. */ smsg = (struct msg_sync_lsdb *)STREAM_DATA(msg->s); /* Set parameter struct. */ param.apiserv = apiserv; param.filter = &smsg->filter; /* Remember mask. */ mask = ntohs(smsg->filter.typemask); /* Iterate over all areas. */ for (ALL_LIST_ELEMENTS(ospf->areas, node, nnode, area)) { int i; uint32_t *area_id = NULL; /* Compare area_id with area_ids in sync request. */ if ((i = smsg->filter.num_areas) > 0) { /* Let area_id point to the list of area IDs, * which is at the end of smsg->filter. */ area_id = (uint32_t *)(&smsg->filter + 1); while (i) { if (*area_id == area->area_id.s_addr) { break; } i--; area_id++; } } else { i = 1; } /* If area was found, then i>0 here. */ if (i) { /* Check msg type. */ if (mask & Power2[OSPF_ROUTER_LSA]) LSDB_LOOP (ROUTER_LSDB(area), rn, lsa) apiserver_sync_callback( lsa, (void *)¶m, seqnum); if (mask & Power2[OSPF_NETWORK_LSA]) LSDB_LOOP (NETWORK_LSDB(area), rn, lsa) apiserver_sync_callback( lsa, (void *)¶m, seqnum); if (mask & Power2[OSPF_SUMMARY_LSA]) LSDB_LOOP (SUMMARY_LSDB(area), rn, lsa) apiserver_sync_callback( lsa, (void *)¶m, seqnum); if (mask & Power2[OSPF_ASBR_SUMMARY_LSA]) LSDB_LOOP (ASBR_SUMMARY_LSDB(area), rn, lsa) apiserver_sync_callback( lsa, (void *)¶m, seqnum); if (mask & Power2[OSPF_OPAQUE_LINK_LSA]) LSDB_LOOP (OPAQUE_LINK_LSDB(area), rn, lsa) apiserver_sync_callback( lsa, (void *)¶m, seqnum); if (mask & Power2[OSPF_OPAQUE_AREA_LSA]) LSDB_LOOP (OPAQUE_AREA_LSDB(area), rn, lsa) apiserver_sync_callback( lsa, (void *)¶m, seqnum); } } /* For AS-external LSAs */ if (ospf->lsdb) { if (mask & Power2[OSPF_AS_EXTERNAL_LSA]) LSDB_LOOP (EXTERNAL_LSDB(ospf), rn, lsa) apiserver_sync_callback(lsa, (void *)¶m, seqnum); } /* For AS-external opaque LSAs */ if (ospf->lsdb) { if (mask & Power2[OSPF_OPAQUE_AS_LSA]) LSDB_LOOP (OPAQUE_AS_LSDB(ospf), rn, lsa) apiserver_sync_callback(lsa, (void *)¶m, seqnum); } /* Send a reply back to client with return code */ rc = ospf_apiserver_send_reply(apiserv, seqnum, rc); return rc; } /* ----------------------------------------------------------- * Followings are functions to originate or update LSA * from an application. * ----------------------------------------------------------- */ /* Create a new internal opaque LSA by taking prototype and filling in missing fields such as age, sequence number, advertising router, checksum and so on. The interface parameter is used for type 9 LSAs, area parameter for type 10. Type 11 LSAs do neither need area nor interface. */ struct ospf_lsa *ospf_apiserver_opaque_lsa_new(struct ospf_area *area, struct ospf_interface *oi, struct lsa_header *protolsa) { struct stream *s; struct lsa_header *newlsa; struct ospf_lsa *new = NULL; uint8_t options = 0x0; uint16_t length; struct ospf *ospf; if (oi && oi->ospf) ospf = oi->ospf; else ospf = ospf_lookup_by_vrf_id(VRF_DEFAULT); assert(ospf); /* Create a stream for internal opaque LSA */ if ((s = stream_new(OSPF_MAX_LSA_SIZE)) == NULL) { zlog_warn("ospf_apiserver_opaque_lsa_new: stream_new failed"); return NULL; } newlsa = (struct lsa_header *)STREAM_DATA(s); /* XXX If this is a link-local LSA or an AS-external LSA, how do we have to set options? */ if (area) { options = LSA_OPTIONS_GET(area); options |= LSA_OPTIONS_NSSA_GET(area); } options |= OSPF_OPTION_O; /* Don't forget to set option bit */ if (IS_DEBUG_OSPF(lsa, LSA_GENERATE)) { zlog_debug("LSA[Type%d:%s]: Creating an Opaque-LSA instance", protolsa->type, inet_ntoa(protolsa->id)); } /* Set opaque-LSA header fields. */ lsa_header_set(s, options, protolsa->type, protolsa->id, ospf->router_id); /* Set opaque-LSA body fields. */ stream_put(s, ((uint8_t *)protolsa) + sizeof(struct lsa_header), ntohs(protolsa->length) - sizeof(struct lsa_header)); /* Determine length of LSA. */ length = stream_get_endp(s); newlsa->length = htons(length); /* Create OSPF LSA. */ new = ospf_lsa_new_and_data(length); new->area = area; new->oi = oi; new->vrf_id = ospf->vrf_id; SET_FLAG(new->flags, OSPF_LSA_SELF); memcpy(new->data, newlsa, length); stream_free(s); return new; } int ospf_apiserver_is_ready_type9(struct ospf_interface *oi) { /* Type 9 opaque LSA can be originated if there is at least one active opaque-capable neighbor attached to the outgoing interface. */ return (ospf_nbr_count_opaque_capable(oi) > 0); } int ospf_apiserver_is_ready_type10(struct ospf_area *area) { /* Type 10 opaque LSA can be originated if there is at least one interface belonging to the area that has an active opaque-capable neighbor. */ struct listnode *node, *nnode; struct ospf_interface *oi; for (ALL_LIST_ELEMENTS(area->oiflist, node, nnode, oi)) /* Is there an active neighbor attached to this interface? */ if (ospf_apiserver_is_ready_type9(oi)) return 1; /* No active neighbor in area */ return 0; } int ospf_apiserver_is_ready_type11(struct ospf *ospf) { /* Type 11 opaque LSA can be originated if there is at least one interface that has an active opaque-capable neighbor. */ struct listnode *node, *nnode; struct ospf_interface *oi; for (ALL_LIST_ELEMENTS(ospf->oiflist, node, nnode, oi)) /* Is there an active neighbor attached to this interface? */ if (ospf_apiserver_is_ready_type9(oi)) return 1; /* No active neighbor at all */ return 0; } int ospf_apiserver_handle_originate_request(struct ospf_apiserver *apiserv, struct msg *msg) { struct msg_originate_request *omsg; struct lsa_header *data; struct ospf_lsa *new; struct ospf_lsa *old; struct ospf_area *area = NULL; struct ospf_interface *oi = NULL; struct ospf_lsdb *lsdb = NULL; struct ospf *ospf; int lsa_type, opaque_type; int ready = 0; int rc = 0; ospf = ospf_lookup_by_vrf_id(VRF_DEFAULT); /* Extract opaque LSA data from message */ omsg = (struct msg_originate_request *)STREAM_DATA(msg->s); data = &omsg->data; /* Determine interface for type9 or area for type10 LSAs. */ switch (data->type) { case OSPF_OPAQUE_LINK_LSA: oi = ospf_apiserver_if_lookup_by_addr(omsg->ifaddr); if (!oi) { zlog_warn("apiserver_originate: unknown interface %s", inet_ntoa(omsg->ifaddr)); rc = OSPF_API_NOSUCHINTERFACE; goto out; } area = oi->area; lsdb = area->lsdb; break; case OSPF_OPAQUE_AREA_LSA: area = ospf_area_lookup_by_area_id(ospf, omsg->area_id); if (!area) { zlog_warn("apiserver_originate: unknown area %s", inet_ntoa(omsg->area_id)); rc = OSPF_API_NOSUCHAREA; goto out; } lsdb = area->lsdb; break; case OSPF_OPAQUE_AS_LSA: lsdb = ospf->lsdb; break; default: /* We can only handle opaque types here */ zlog_warn( "apiserver_originate: Cannot originate non-opaque LSA type %d", data->type); rc = OSPF_API_ILLEGALLSATYPE; goto out; } /* Check if we registered this opaque type */ lsa_type = data->type; opaque_type = GET_OPAQUE_TYPE(ntohl(data->id.s_addr)); if (!apiserver_is_opaque_type_registered(apiserv, lsa_type, opaque_type)) { zlog_warn( "apiserver_originate: LSA-type(%d)/Opaque-type(%d): Not registered", lsa_type, opaque_type); rc = OSPF_API_OPAQUETYPENOTREGISTERED; goto out; } /* Make sure that the neighbors are ready before we can originate */ switch (data->type) { case OSPF_OPAQUE_LINK_LSA: ready = ospf_apiserver_is_ready_type9(oi); break; case OSPF_OPAQUE_AREA_LSA: ready = ospf_apiserver_is_ready_type10(area); break; case OSPF_OPAQUE_AS_LSA: ready = ospf_apiserver_is_ready_type11(ospf); break; default: break; } if (!ready) { zlog_warn("Neighbors not ready to originate type %d", data->type); rc = OSPF_API_NOTREADY; goto out; } /* Create OSPF's internal opaque LSA representation */ new = ospf_apiserver_opaque_lsa_new(area, oi, data); if (!new) { rc = OSPF_API_NOMEMORY; /* XXX */ goto out; } /* Determine if LSA is new or an update for an existing one. */ old = ospf_lsdb_lookup(lsdb, new); if (!old) { /* New LSA install in LSDB. */ rc = ospf_apiserver_originate1(new); } else { /* * Keep the new LSA instance in the "waiting place" until the * next * refresh timing. If several LSA update requests for the same * LSID * have issued by peer, the last one takes effect. */ new->lsdb = &apiserv->reserve; ospf_lsdb_add(&apiserv->reserve, new); /* Kick the scheduler function. */ ospf_opaque_lsa_refresh_schedule(old); } out: /* Send a reply back to client with return code */ rc = ospf_apiserver_send_reply(apiserv, ntohl(msg->hdr.msgseq), rc); return rc; } /* ----------------------------------------------------------- * Flood an LSA within its flooding scope. * ----------------------------------------------------------- */ /* XXX We can probably use ospf_flood_through instead of this function but then we need the neighbor parameter. If we set nbr to NULL then ospf_flood_through crashes due to dereferencing NULL. */ void ospf_apiserver_flood_opaque_lsa(struct ospf_lsa *lsa) { assert(lsa); switch (lsa->data->type) { case OSPF_OPAQUE_LINK_LSA: /* Increment counters? XXX */ /* Flood LSA through local network. */ ospf_flood_through_area(lsa->area, NULL /*nbr */, lsa); break; case OSPF_OPAQUE_AREA_LSA: /* Update LSA origination count. */ assert(lsa->area); lsa->area->ospf->lsa_originate_count++; /* Flood LSA through area. */ ospf_flood_through_area(lsa->area, NULL /*nbr */, lsa); break; case OSPF_OPAQUE_AS_LSA: { struct ospf *ospf; ospf = ospf_lookup_by_vrf_id(VRF_DEFAULT); assert(ospf); /* Increment counters? XXX */ /* Flood LSA through AS. */ ospf_flood_through_as(ospf, NULL /*nbr */, lsa); break; } } } int ospf_apiserver_originate1(struct ospf_lsa *lsa) { struct ospf *ospf; ospf = ospf_lookup_by_vrf_id(VRF_DEFAULT); assert(ospf); /* Install this LSA into LSDB. */ if (ospf_lsa_install(ospf, lsa->oi, lsa) == NULL) { flog_warn(EC_OSPF_LSA_INSTALL_FAILURE, "ospf_apiserver_originate1: ospf_lsa_install failed"); return -1; } /* Flood LSA within scope */ #ifdef NOTYET /* * NB: Modified version of "ospf_flood_though ()" accepts NULL "inbr" * parameter, and thus it does not cause SIGSEGV error. */ ospf_flood_through(NULL /*nbr */, lsa); #else /* NOTYET */ ospf_apiserver_flood_opaque_lsa(lsa); #endif /* NOTYET */ return 0; } /* Opaque LSAs of type 9 on a specific interface can now be originated. Tell clients that registered type 9. */ int ospf_apiserver_lsa9_originator(void *arg) { struct ospf_interface *oi; oi = (struct ospf_interface *)arg; if (listcount(apiserver_list) > 0) { ospf_apiserver_clients_notify_ready_type9(oi); } return 0; } int ospf_apiserver_lsa10_originator(void *arg) { struct ospf_area *area; area = (struct ospf_area *)arg; if (listcount(apiserver_list) > 0) { ospf_apiserver_clients_notify_ready_type10(area); } return 0; } int ospf_apiserver_lsa11_originator(void *arg) { struct ospf *ospf; ospf = (struct ospf *)arg; if (listcount(apiserver_list) > 0) { ospf_apiserver_clients_notify_ready_type11(ospf); } return 0; } /* Periodically refresh opaque LSAs so that they do not expire in other routers. */ struct ospf_lsa *ospf_apiserver_lsa_refresher(struct ospf_lsa *lsa) { struct ospf_apiserver *apiserv; struct ospf_lsa *new = NULL; struct ospf *ospf; assert(lsa); ospf = ospf_lookup_by_vrf_id(VRF_DEFAULT); assert(ospf); apiserv = lookup_apiserver_by_lsa(lsa); if (!apiserv) { zlog_warn( "ospf_apiserver_lsa_refresher: LSA[%s]: No apiserver?", dump_lsa_key(lsa)); lsa->data->ls_age = htons(OSPF_LSA_MAXAGE); /* Flush it anyway. */ goto out; } if (IS_LSA_MAXAGE(lsa)) { ospf_opaque_lsa_flush_schedule(lsa); goto out; } /* Check if updated version of LSA instance has already prepared. */ new = ospf_lsdb_lookup(&apiserv->reserve, lsa); if (!new) { /* This is a periodic refresh, driven by core OSPF mechanism. */ new = ospf_apiserver_opaque_lsa_new(lsa->area, lsa->oi, lsa->data); if (!new) { zlog_warn( "ospf_apiserver_lsa_refresher: Cannot create a new LSA?"); goto out; } } else { /* This is a forcible refresh, requested by OSPF-API client. */ ospf_lsdb_delete(&apiserv->reserve, new); new->lsdb = NULL; } /* Increment sequence number */ new->data->ls_seqnum = lsa_seqnum_increment(lsa); /* New LSA is in same area. */ new->area = lsa->area; SET_FLAG(new->flags, OSPF_LSA_SELF); /* Install LSA into LSDB. */ if (ospf_lsa_install(ospf, new->oi, new) == NULL) { flog_warn( EC_OSPF_LSA_INSTALL_FAILURE, "ospf_apiserver_lsa_refresher: ospf_lsa_install failed"); ospf_lsa_unlock(&new); goto out; } /* Flood updated LSA through interface, area or AS */ #ifdef NOTYET ospf_flood_through(NULL /*nbr */, new); #endif /* NOTYET */ ospf_apiserver_flood_opaque_lsa(new); /* Debug logging. */ if (IS_DEBUG_OSPF(lsa, LSA_GENERATE)) { zlog_debug("LSA[Type%d:%s]: Refresh Opaque LSA", new->data->type, inet_ntoa(new->data->id)); ospf_lsa_header_dump(new->data); } out: return new; } /* ----------------------------------------------------------- * Followings are functions to delete LSAs * ----------------------------------------------------------- */ int ospf_apiserver_handle_delete_request(struct ospf_apiserver *apiserv, struct msg *msg) { struct msg_delete_request *dmsg; struct ospf_lsa *old; struct ospf_area *area = NULL; struct in_addr id; int lsa_type, opaque_type; int rc = 0; struct ospf *ospf; ospf = ospf_lookup_by_vrf_id(VRF_DEFAULT); assert(ospf); /* Extract opaque LSA from message */ dmsg = (struct msg_delete_request *)STREAM_DATA(msg->s); /* Lookup area for link-local and area-local opaque LSAs */ switch (dmsg->lsa_type) { case OSPF_OPAQUE_LINK_LSA: case OSPF_OPAQUE_AREA_LSA: area = ospf_area_lookup_by_area_id(ospf, dmsg->area_id); if (!area) { zlog_warn("ospf_apiserver_lsa_delete: unknown area %s", inet_ntoa(dmsg->area_id)); rc = OSPF_API_NOSUCHAREA; goto out; } break; case OSPF_OPAQUE_AS_LSA: /* AS-external opaque LSAs have no designated area */ area = NULL; break; default: zlog_warn( "ospf_apiserver_lsa_delete: Cannot delete non-opaque LSA type %d", dmsg->lsa_type); rc = OSPF_API_ILLEGALLSATYPE; goto out; } /* Check if we registered this opaque type */ lsa_type = dmsg->lsa_type; opaque_type = dmsg->opaque_type; if (!apiserver_is_opaque_type_registered(apiserv, lsa_type, opaque_type)) { zlog_warn( "ospf_apiserver_lsa_delete: LSA-type(%d)/Opaque-type(%d): Not registered", lsa_type, opaque_type); rc = OSPF_API_OPAQUETYPENOTREGISTERED; goto out; } /* opaque_id is in network byte order */ id.s_addr = htonl( SET_OPAQUE_LSID(dmsg->opaque_type, ntohl(dmsg->opaque_id))); /* * Even if the target LSA has once scheduled to flush, it remains in * the LSDB until it is finally handled by the maxage remover thread. * Therefore, the lookup function below may return non-NULL result. */ old = ospf_lsa_lookup(ospf, area, dmsg->lsa_type, id, ospf->router_id); if (!old) { zlog_warn( "ospf_apiserver_lsa_delete: LSA[Type%d:%s] not in LSDB", dmsg->lsa_type, inet_ntoa(id)); rc = OSPF_API_NOSUCHLSA; goto out; } /* Schedule flushing of LSA from LSDB */ /* NB: Multiple scheduling will produce a warning message, but harmless. */ ospf_opaque_lsa_flush_schedule(old); out: /* Send reply back to client including return code */ rc = ospf_apiserver_send_reply(apiserv, ntohl(msg->hdr.msgseq), rc); return rc; } /* Flush self-originated opaque LSA */ static int apiserver_flush_opaque_type_callback(struct ospf_lsa *lsa, void *p_arg, int int_arg) { struct param_t { struct ospf_apiserver *apiserv; uint8_t lsa_type; uint8_t opaque_type; } * param; /* Sanity check */ assert(lsa->data); assert(p_arg); param = (struct param_t *)p_arg; /* If LSA matches type and opaque type then delete it */ if (IS_LSA_SELF(lsa) && lsa->data->type == param->lsa_type && GET_OPAQUE_TYPE(ntohl(lsa->data->id.s_addr)) == param->opaque_type) { ospf_opaque_lsa_flush_schedule(lsa); } return 0; } /* Delete self-originated opaque LSAs of a given opaque type. This function is called when an application unregisters a given opaque type or a connection to an application closes and all those opaque LSAs need to be flushed the LSDB. */ void ospf_apiserver_flush_opaque_lsa(struct ospf_apiserver *apiserv, uint8_t lsa_type, uint8_t opaque_type) { struct param_t { struct ospf_apiserver *apiserv; uint8_t lsa_type; uint8_t opaque_type; } param; struct listnode *node, *nnode; struct ospf *ospf; struct ospf_area *area; ospf = ospf_lookup_by_vrf_id(VRF_DEFAULT); assert(ospf); /* Set parameter struct. */ param.apiserv = apiserv; param.lsa_type = lsa_type; param.opaque_type = opaque_type; switch (lsa_type) { struct route_node *rn; struct ospf_lsa *lsa; case OSPF_OPAQUE_LINK_LSA: for (ALL_LIST_ELEMENTS(ospf->areas, node, nnode, area)) LSDB_LOOP (OPAQUE_LINK_LSDB(area), rn, lsa) apiserver_flush_opaque_type_callback( lsa, (void *)¶m, 0); break; case OSPF_OPAQUE_AREA_LSA: for (ALL_LIST_ELEMENTS(ospf->areas, node, nnode, area)) LSDB_LOOP (OPAQUE_AREA_LSDB(area), rn, lsa) apiserver_flush_opaque_type_callback( lsa, (void *)¶m, 0); break; case OSPF_OPAQUE_AS_LSA: LSDB_LOOP (OPAQUE_LINK_LSDB(ospf), rn, lsa) apiserver_flush_opaque_type_callback(lsa, (void *)¶m, 0); break; default: break; } return; } /* ----------------------------------------------------------- * Followings are callback functions to handle opaque types * ----------------------------------------------------------- */ int ospf_apiserver_new_if(struct interface *ifp) { struct ospf_interface *oi; /* For some strange reason it seems possible that we are invoked with an interface that has no name. This seems to happen during initialization. Return if this happens */ if (ifp->name[0] == '\0') { /* interface has empty name */ zlog_warn("ospf_apiserver_new_if: interface has no name?"); return 0; } /* zlog_warn for debugging */ zlog_warn("ospf_apiserver_new_if"); zlog_warn("ifp name=%s status=%d index=%d", ifp->name, ifp->status, ifp->ifindex); if (ifp->name[0] == '\0') { /* interface has empty name */ zlog_warn("ospf_apiserver_new_if: interface has no name?"); return 0; } oi = ospf_apiserver_if_lookup_by_ifp(ifp); if (!oi) { /* This interface is known to Zebra but not to OSPF daemon yet. */ zlog_warn( "ospf_apiserver_new_if: interface %s not known to OSPFd?", ifp->name); return 0; } assert(oi); /* New interface added to OSPF, tell clients about it */ if (listcount(apiserver_list) > 0) { ospf_apiserver_clients_notify_new_if(oi); } return 0; } int ospf_apiserver_del_if(struct interface *ifp) { struct ospf_interface *oi; /* zlog_warn for debugging */ zlog_warn("ospf_apiserver_del_if"); zlog_warn("ifp name=%s status=%d index=%d", ifp->name, ifp->status, ifp->ifindex); oi = ospf_apiserver_if_lookup_by_ifp(ifp); if (!oi) { /* This interface is known to Zebra but not to OSPF daemon anymore. No need to tell clients about it */ return 0; } /* Interface deleted, tell clients about it */ if (listcount(apiserver_list) > 0) { ospf_apiserver_clients_notify_del_if(oi); } return 0; } void ospf_apiserver_ism_change(struct ospf_interface *oi, int old_state) { /* Tell clients about interface change */ /* zlog_warn for debugging */ zlog_warn("ospf_apiserver_ism_change"); if (listcount(apiserver_list) > 0) { ospf_apiserver_clients_notify_ism_change(oi); } zlog_warn("oi->ifp->name=%s", oi->ifp->name); zlog_warn("old_state=%d", old_state); zlog_warn("oi->state=%d", oi->state); } void ospf_apiserver_nsm_change(struct ospf_neighbor *nbr, int old_status) { /* Neighbor status changed, tell clients about it */ zlog_warn("ospf_apiserver_nsm_change"); if (listcount(apiserver_list) > 0) { ospf_apiserver_clients_notify_nsm_change(nbr); } } void ospf_apiserver_show_info(struct vty *vty, struct ospf_lsa *lsa) { struct opaque_lsa { struct lsa_header header; uint8_t data[1]; /* opaque data have variable length. This is start address */ }; struct opaque_lsa *olsa; int opaquelen; olsa = (struct opaque_lsa *)lsa->data; if (VALID_OPAQUE_INFO_LEN(lsa->data)) opaquelen = ntohs(lsa->data->length) - OSPF_LSA_HEADER_SIZE; else opaquelen = 0; /* Output information about opaque LSAs */ if (vty != NULL) { int i; vty_out(vty, " Added using OSPF API: %u octets of opaque data %s\n", opaquelen, VALID_OPAQUE_INFO_LEN(lsa->data) ? "" : "(Invalid length?)"); vty_out(vty, " Opaque data: "); for (i = 0; i < opaquelen; i++) { vty_out(vty, "0x%x ", olsa->data[i]); } vty_out(vty, "\n"); } else { int i; zlog_debug( " Added using OSPF API: %u octets of opaque data %s", opaquelen, VALID_OPAQUE_INFO_LEN(lsa->data) ? "" : "(Invalid length?)"); zlog_debug(" Opaque data: "); for (i = 0; i < opaquelen; i++) { zlog_debug("0x%x ", olsa->data[i]); } } return; } /* ----------------------------------------------------------- * Followings are functions to notify clients about events * ----------------------------------------------------------- */ /* Send a message to all clients. This is useful for messages that need to be notified to all clients (such as interface changes) */ void ospf_apiserver_clients_notify_all(struct msg *msg) { struct listnode *node, *nnode; struct ospf_apiserver *apiserv; /* Send message to all clients */ for (ALL_LIST_ELEMENTS(apiserver_list, node, nnode, apiserv)) ospf_apiserver_send_msg(apiserv, msg); } /* An interface is now ready to accept opaque LSAs. Notify all clients that registered to use this opaque type */ void ospf_apiserver_clients_notify_ready_type9(struct ospf_interface *oi) { struct listnode *node, *nnode; struct msg *msg; struct ospf_apiserver *apiserv; assert(oi); if (!oi->address) { zlog_warn("Interface has no address?"); return; } if (!ospf_apiserver_is_ready_type9(oi)) { zlog_warn("Interface not ready for type 9?"); return; } for (ALL_LIST_ELEMENTS(apiserver_list, node, nnode, apiserv)) { struct listnode *node2, *nnode2; struct registered_opaque_type *r; for (ALL_LIST_ELEMENTS(apiserv->opaque_types, node2, nnode2, r)) { if (r->lsa_type == OSPF_OPAQUE_LINK_LSA) { msg = new_msg_ready_notify( 0, OSPF_OPAQUE_LINK_LSA, r->opaque_type, oi->address->u.prefix4); if (!msg) { zlog_warn( "ospf_apiserver_clients_notify_ready_type9: new_msg_ready_notify failed"); #ifdef NOTYET /* Cannot allocate new message. What * should we do? */ ospf_apiserver_free(apiserv); #endif goto out; } ospf_apiserver_send_msg(apiserv, msg); msg_free(msg); } } } out: return; } void ospf_apiserver_clients_notify_ready_type10(struct ospf_area *area) { struct listnode *node, *nnode; struct msg *msg; struct ospf_apiserver *apiserv; assert(area); if (!ospf_apiserver_is_ready_type10(area)) { zlog_warn("Area not ready for type 10?"); return; } for (ALL_LIST_ELEMENTS(apiserver_list, node, nnode, apiserv)) { struct listnode *node2, *nnode2; struct registered_opaque_type *r; for (ALL_LIST_ELEMENTS(apiserv->opaque_types, node2, nnode2, r)) { if (r->lsa_type == OSPF_OPAQUE_AREA_LSA) { msg = new_msg_ready_notify( 0, OSPF_OPAQUE_AREA_LSA, r->opaque_type, area->area_id); if (!msg) { zlog_warn( "ospf_apiserver_clients_notify_ready_type10: new_msg_ready_nofity failed"); #ifdef NOTYET /* Cannot allocate new message. What * should we do? */ ospf_apiserver_free(apiserv); #endif goto out; } ospf_apiserver_send_msg(apiserv, msg); msg_free(msg); } } } out: return; } void ospf_apiserver_clients_notify_ready_type11(struct ospf *top) { struct listnode *node, *nnode; struct msg *msg; struct in_addr id_null = {.s_addr = 0L}; struct ospf_apiserver *apiserv; assert(top); if (!ospf_apiserver_is_ready_type11(top)) { zlog_warn("AS not ready for type 11?"); return; } for (ALL_LIST_ELEMENTS(apiserver_list, node, nnode, apiserv)) { struct listnode *node2, *nnode2; struct registered_opaque_type *r; for (ALL_LIST_ELEMENTS(apiserv->opaque_types, node2, nnode2, r)) { if (r->lsa_type == OSPF_OPAQUE_AS_LSA) { msg = new_msg_ready_notify( 0, OSPF_OPAQUE_AS_LSA, r->opaque_type, id_null); if (!msg) { zlog_warn( "ospf_apiserver_clients_notify_ready_type11: new_msg_ready_notify failed"); #ifdef NOTYET /* Cannot allocate new message. What * should we do? */ ospf_apiserver_free(apiserv); #endif goto out; } ospf_apiserver_send_msg(apiserv, msg); msg_free(msg); } } } out: return; } void ospf_apiserver_clients_notify_new_if(struct ospf_interface *oi) { struct msg *msg; msg = new_msg_new_if(0, oi->address->u.prefix4, oi->area->area_id); if (msg != NULL) { ospf_apiserver_clients_notify_all(msg); msg_free(msg); } } void ospf_apiserver_clients_notify_del_if(struct ospf_interface *oi) { struct msg *msg; msg = new_msg_del_if(0, oi->address->u.prefix4); if (msg != NULL) { ospf_apiserver_clients_notify_all(msg); msg_free(msg); } } void ospf_apiserver_clients_notify_ism_change(struct ospf_interface *oi) { struct msg *msg; struct in_addr ifaddr = {.s_addr = 0L}; struct in_addr area_id = {.s_addr = 0L}; assert(oi); assert(oi->ifp); if (oi->address) { ifaddr = oi->address->u.prefix4; } if (oi->area) { area_id = oi->area->area_id; } msg = new_msg_ism_change(0, ifaddr, area_id, oi->state); if (!msg) { zlog_warn( "apiserver_clients_notify_ism_change: msg_new failed"); return; } ospf_apiserver_clients_notify_all(msg); msg_free(msg); } void ospf_apiserver_clients_notify_nsm_change(struct ospf_neighbor *nbr) { struct msg *msg; struct in_addr ifaddr = {.s_addr = 0L}; struct in_addr nbraddr; assert(nbr); if (nbr->oi) { ifaddr = nbr->oi->address->u.prefix4; } nbraddr = nbr->address.u.prefix4; msg = new_msg_nsm_change(0, ifaddr, nbraddr, nbr->router_id, nbr->state); if (!msg) { zlog_warn( "apiserver_clients_notify_nsm_change: msg_new failed"); return; } ospf_apiserver_clients_notify_all(msg); msg_free(msg); } static void apiserver_clients_lsa_change_notify(uint8_t msgtype, struct ospf_lsa *lsa) { struct msg *msg; struct listnode *node, *nnode; struct ospf_apiserver *apiserv; /* Default area for AS-External and Opaque11 LSAs */ struct in_addr area_id = {.s_addr = 0L}; /* Default interface for non Opaque9 LSAs */ struct in_addr ifaddr = {.s_addr = 0L}; if (lsa->area) { area_id = lsa->area->area_id; } if (lsa->data->type == OSPF_OPAQUE_LINK_LSA) { assert(lsa->oi); ifaddr = lsa->oi->address->u.prefix4; } /* Prepare message that can be sent to clients that have a matching filter */ msg = new_msg_lsa_change_notify(msgtype, 0L, /* no sequence number */ ifaddr, area_id, lsa->flags & OSPF_LSA_SELF, lsa->data); if (!msg) { zlog_warn( "apiserver_clients_lsa_change_notify: msg_new failed"); return; } /* Now send message to all clients with a matching filter */ for (ALL_LIST_ELEMENTS(apiserver_list, node, nnode, apiserv)) { struct lsa_filter_type *filter; uint16_t mask; uint32_t *area; int i; /* Check filter for this client. */ filter = apiserv->filter; /* Check area IDs in case of non AS-E LSAs. * If filter has areas (num_areas > 0), * then one of the areas must match the area ID of this LSA. */ i = filter->num_areas; if ((lsa->data->type == OSPF_AS_EXTERNAL_LSA) || (lsa->data->type == OSPF_OPAQUE_AS_LSA)) { i = 0; } if (i > 0) { area = (uint32_t *)(filter + 1); while (i) { if (*area == area_id.s_addr) { break; } i--; area++; } } else { i = 1; } if (i > 0) { /* Area match. Check LSA type. */ mask = ntohs(filter->typemask); if (mask & Power2[lsa->data->type]) { /* Type also matches. Check origin. */ if ((filter->origin == ANY_ORIGIN) || (filter->origin == IS_LSA_SELF(lsa))) { ospf_apiserver_send_msg(apiserv, msg); } } } } /* Free message since it is not used anymore */ msg_free(msg); } /* ------------------------------------------------------------- * Followings are hooks invoked when LSAs are updated or deleted * ------------------------------------------------------------- */ static int apiserver_notify_clients_lsa(uint8_t msgtype, struct ospf_lsa *lsa) { struct msg *msg; /* default area for AS-External and Opaque11 LSAs */ struct in_addr area_id = {.s_addr = 0L}; /* default interface for non Opaque9 LSAs */ struct in_addr ifaddr = {.s_addr = 0L}; /* Only notify this update if the LSA's age is smaller than MAXAGE. Otherwise clients would see LSA updates with max age just before they are deleted from the LSDB. LSA delete messages have MAXAGE too but should not be filtered. */ if (IS_LSA_MAXAGE(lsa) && (msgtype == MSG_LSA_UPDATE_NOTIFY)) { return 0; } if (lsa->area) { area_id = lsa->area->area_id; } if (lsa->data->type == OSPF_OPAQUE_LINK_LSA) { ifaddr = lsa->oi->address->u.prefix4; } msg = new_msg_lsa_change_notify(msgtype, 0L, /* no sequence number */ ifaddr, area_id, lsa->flags & OSPF_LSA_SELF, lsa->data); if (!msg) { zlog_warn("notify_clients_lsa: msg_new failed"); return -1; } /* Notify all clients that new LSA is added/updated */ apiserver_clients_lsa_change_notify(msgtype, lsa); /* Clients made their own copies of msg so we can free msg here */ msg_free(msg); return 0; } int ospf_apiserver_lsa_update(struct ospf_lsa *lsa) { return apiserver_notify_clients_lsa(MSG_LSA_UPDATE_NOTIFY, lsa); } int ospf_apiserver_lsa_delete(struct ospf_lsa *lsa) { return apiserver_notify_clients_lsa(MSG_LSA_DELETE_NOTIFY, lsa); } #endif /* SUPPORT_OSPF_API */ frr-7.2.1/ospfd/ospf_apiserver.h0000644000000000000000000001751313610377563013536 00000000000000/* * Server side of OSPF API. * Copyright (C) 2001, 2002 Ralph Keller * * This file is part of GNU Zebra. * * GNU Zebra is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License as published * by the Free Software Foundation; either version 2, or (at your * option) any later version. * * GNU Zebra 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; see the file COPYING; if not, write to the Free Software * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA */ #ifndef _OSPF_APISERVER_H #define _OSPF_APISERVER_H /* MTYPE definition is not reflected to "memory.h". */ #define MTYPE_OSPF_APISERVER MTYPE_TMP #define MTYPE_OSPF_APISERVER_MSGFILTER MTYPE_TMP /* List of opaque types that application registered */ struct registered_opaque_type { uint8_t lsa_type; uint8_t opaque_type; }; /* Server instance for each accepted client connection. */ struct ospf_apiserver { /* Socket connections for synchronous commands and asynchronous notifications */ int fd_sync; /* synchronous requests */ struct sockaddr_in peer_sync; int fd_async; /* asynchronous notifications */ struct sockaddr_in peer_async; /* List of all opaque types that application registers to use. Using a single connection with the OSPF daemon, multiple pairs can be registered. However, each combination can only be registered once by all applications. */ struct list *opaque_types; /* of type registered_opaque_type */ /* Temporary storage for LSA instances to be refreshed. */ struct ospf_lsdb reserve; /* filter for LSA update/delete notifies */ struct lsa_filter_type *filter; /* Fifo buffers for outgoing messages */ struct msg_fifo *out_sync_fifo; struct msg_fifo *out_async_fifo; /* Read and write threads */ struct thread *t_sync_read; #ifdef USE_ASYNC_READ struct thread *t_async_read; #endif /* USE_ASYNC_READ */ struct thread *t_sync_write; struct thread *t_async_write; }; enum event { OSPF_APISERVER_ACCEPT, OSPF_APISERVER_SYNC_READ, #ifdef USE_ASYNC_READ OSPF_APISERVER_ASYNC_READ, #endif /* USE_ASYNC_READ */ OSPF_APISERVER_SYNC_WRITE, OSPF_APISERVER_ASYNC_WRITE }; /* ----------------------------------------------------------- * Followings are functions to manage client connections. * ----------------------------------------------------------- */ extern unsigned short ospf_apiserver_getport(void); extern int ospf_apiserver_init(void); extern void ospf_apiserver_term(void); extern struct ospf_apiserver *ospf_apiserver_new(int fd_sync, int fd_async); extern void ospf_apiserver_free(struct ospf_apiserver *apiserv); extern void ospf_apiserver_event(enum event event, int fd, struct ospf_apiserver *apiserv); extern int ospf_apiserver_serv_sock_family(unsigned short port, int family); extern int ospf_apiserver_accept(struct thread *thread); extern int ospf_apiserver_read(struct thread *thread); extern int ospf_apiserver_sync_write(struct thread *thread); extern int ospf_apiserver_async_write(struct thread *thread); extern int ospf_apiserver_send_reply(struct ospf_apiserver *apiserv, uint32_t seqnr, uint8_t rc); /* ----------------------------------------------------------- * Followings are message handler functions * ----------------------------------------------------------- */ extern int ospf_apiserver_lsa9_originator(void *arg); extern int ospf_apiserver_lsa10_originator(void *arg); extern int ospf_apiserver_lsa11_originator(void *arg); extern void ospf_apiserver_clients_notify_all(struct msg *msg); extern void ospf_apiserver_clients_notify_ready_type9(struct ospf_interface *oi); extern void ospf_apiserver_clients_notify_ready_type10(struct ospf_area *area); extern void ospf_apiserver_clients_notify_ready_type11(struct ospf *top); extern void ospf_apiserver_clients_notify_new_if(struct ospf_interface *oi); extern void ospf_apiserver_clients_notify_del_if(struct ospf_interface *oi); extern void ospf_apiserver_clients_notify_ism_change(struct ospf_interface *oi); extern void ospf_apiserver_clients_notify_nsm_change(struct ospf_neighbor *nbr); extern int ospf_apiserver_is_ready_type9(struct ospf_interface *oi); extern int ospf_apiserver_is_ready_type10(struct ospf_area *area); extern int ospf_apiserver_is_ready_type11(struct ospf *ospf); extern void ospf_apiserver_notify_ready_type9(struct ospf_apiserver *apiserv); extern void ospf_apiserver_notify_ready_type10(struct ospf_apiserver *apiserv); extern void ospf_apiserver_notify_ready_type11(struct ospf_apiserver *apiserv); extern int ospf_apiserver_handle_msg(struct ospf_apiserver *apiserv, struct msg *msg); extern int ospf_apiserver_handle_register_opaque_type(struct ospf_apiserver *apiserv, struct msg *msg); extern int ospf_apiserver_handle_unregister_opaque_type(struct ospf_apiserver *apiserv, struct msg *msg); extern int ospf_apiserver_handle_register_event(struct ospf_apiserver *apiserv, struct msg *msg); extern int ospf_apiserver_handle_originate_request(struct ospf_apiserver *apiserv, struct msg *msg); extern int ospf_apiserver_handle_delete_request(struct ospf_apiserver *apiserv, struct msg *msg); extern int ospf_apiserver_handle_sync_lsdb(struct ospf_apiserver *apiserv, struct msg *msg); /* ----------------------------------------------------------- * Followings are functions for LSA origination/deletion * ----------------------------------------------------------- */ extern int ospf_apiserver_register_opaque_type(struct ospf_apiserver *apiserver, uint8_t lsa_type, uint8_t opaque_type); extern int ospf_apiserver_unregister_opaque_type(struct ospf_apiserver *apiserver, uint8_t lsa_type, uint8_t opaque_type); extern struct ospf_lsa * ospf_apiserver_opaque_lsa_new(struct ospf_area *area, struct ospf_interface *oi, struct lsa_header *protolsa); extern struct ospf_interface * ospf_apiserver_if_lookup_by_addr(struct in_addr address); extern struct ospf_interface * ospf_apiserver_if_lookup_by_ifp(struct interface *ifp); extern int ospf_apiserver_originate1(struct ospf_lsa *lsa); extern void ospf_apiserver_flood_opaque_lsa(struct ospf_lsa *lsa); /* ----------------------------------------------------------- * Followings are callback functions to handle opaque types * ----------------------------------------------------------- */ extern int ospf_apiserver_new_if(struct interface *ifp); extern int ospf_apiserver_del_if(struct interface *ifp); extern void ospf_apiserver_ism_change(struct ospf_interface *oi, int old_status); extern void ospf_apiserver_nsm_change(struct ospf_neighbor *nbr, int old_status); extern void ospf_apiserver_config_write_router(struct vty *vty); extern void ospf_apiserver_config_write_if(struct vty *vty, struct interface *ifp); extern void ospf_apiserver_show_info(struct vty *vty, struct ospf_lsa *lsa); extern int ospf_ospf_apiserver_lsa_originator(void *arg); extern struct ospf_lsa *ospf_apiserver_lsa_refresher(struct ospf_lsa *lsa); extern void ospf_apiserver_flush_opaque_lsa(struct ospf_apiserver *apiserv, uint8_t lsa_type, uint8_t opaque_type); /* ----------------------------------------------------------- * Followings are hooks when LSAs are updated or deleted * ----------------------------------------------------------- */ /* Hooks that are invoked from ospf opaque module */ extern int ospf_apiserver_lsa_update(struct ospf_lsa *lsa); extern int ospf_apiserver_lsa_delete(struct ospf_lsa *lsa); extern void ospf_apiserver_clients_lsa_change_notify(uint8_t msgtype, struct ospf_lsa *lsa); #endif /* _OSPF_APISERVER_H */ frr-7.2.1/ospfd/ospf_asbr.c0000644000000000000000000001700413610377563012453 00000000000000/* * OSPF AS Boundary Router functions. * Copyright (C) 1999, 2000 Kunihiro Ishiguro, Toshiaki Takada * * This file is part of GNU Zebra. * * GNU Zebra is free software; you can redistribute it and/or modify it * under the terms of the GNU General Public License as published by the * Free Software Foundation; either version 2, or (at your option) any * later version. * * GNU Zebra 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; see the file COPYING; if not, write to the Free Software * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA */ #include #include "thread.h" #include "memory.h" #include "linklist.h" #include "prefix.h" #include "if.h" #include "table.h" #include "vty.h" #include "filter.h" #include "log.h" #include "ospfd/ospfd.h" #include "ospfd/ospf_interface.h" #include "ospfd/ospf_asbr.h" #include "ospfd/ospf_lsa.h" #include "ospfd/ospf_lsdb.h" #include "ospfd/ospf_neighbor.h" #include "ospfd/ospf_spf.h" #include "ospfd/ospf_flood.h" #include "ospfd/ospf_route.h" #include "ospfd/ospf_zebra.h" #include "ospfd/ospf_dump.h" /* Remove external route. */ void ospf_external_route_remove(struct ospf *ospf, struct prefix_ipv4 *p) { struct route_node *rn; struct ospf_route * or ; rn = route_node_lookup(ospf->old_external_route, (struct prefix *)p); if (rn) if ((or = rn->info)) { zlog_info("Route[%s/%d]: external path deleted", inet_ntoa(p->prefix), p->prefixlen); /* Remove route from zebra. */ if (or->type == OSPF_DESTINATION_NETWORK) ospf_zebra_delete( ospf, (struct prefix_ipv4 *)&rn->p, or); ospf_route_free(or); rn->info = NULL; route_unlock_node(rn); route_unlock_node(rn); return; } zlog_info("Route[%s/%d]: no such external path", inet_ntoa(p->prefix), p->prefixlen); } /* Add an External info for AS-external-LSA. */ struct external_info *ospf_external_info_new(uint8_t type, unsigned short instance) { struct external_info *new; new = XCALLOC(MTYPE_OSPF_EXTERNAL_INFO, sizeof(struct external_info)); new->type = type; new->instance = instance; ospf_reset_route_map_set_values(&new->route_map_set); return new; } static void ospf_external_info_free(struct external_info *ei) { XFREE(MTYPE_OSPF_EXTERNAL_INFO, ei); } void ospf_reset_route_map_set_values(struct route_map_set_values *values) { values->metric = -1; values->metric_type = -1; } int ospf_route_map_set_compare(struct route_map_set_values *values1, struct route_map_set_values *values2) { return values1->metric == values2->metric && values1->metric_type == values2->metric_type; } /* Add an External info for AS-external-LSA. */ struct external_info * ospf_external_info_add(struct ospf *ospf, uint8_t type, unsigned short instance, struct prefix_ipv4 p, ifindex_t ifindex, struct in_addr nexthop, route_tag_t tag) { struct external_info *new; struct route_node *rn; struct ospf_external *ext; char inetbuf[INET6_BUFSIZ]; ext = ospf_external_lookup(ospf, type, instance); if (!ext) ext = ospf_external_add(ospf, type, instance); rn = route_node_get(EXTERNAL_INFO(ext), (struct prefix *)&p); /* If old info exists, -- discard new one or overwrite with new one? */ if (rn) if (rn->info) { new = rn->info; if ((new->ifindex == ifindex) && (new->nexthop.s_addr == nexthop.s_addr) && (new->tag == tag)) { route_unlock_node(rn); return NULL; /* NULL => no LSA to refresh */ } inet_ntop(AF_INET, (void *)&nexthop.s_addr, inetbuf, INET6_BUFSIZ); if (IS_DEBUG_OSPF(lsa, LSA_GENERATE)) zlog_debug( "Redistribute[%s][%d][%u]: %s/%d discarding old info with NH %s.", ospf_redist_string(type), instance, ospf->vrf_id, inet_ntoa(p.prefix), p.prefixlen, inetbuf); XFREE(MTYPE_OSPF_EXTERNAL_INFO, rn->info); rn->info = NULL; } /* Create new External info instance. */ new = ospf_external_info_new(type, instance); new->p = p; new->ifindex = ifindex; new->nexthop = nexthop; new->tag = tag; /* we don't unlock rn from the get() because we're attaching the info */ if (rn) rn->info = new; if (IS_DEBUG_OSPF(lsa, LSA_GENERATE)) { inet_ntop(AF_INET, (void *)&nexthop.s_addr, inetbuf, INET6_BUFSIZ); zlog_debug( "Redistribute[%s][%u]: %s/%d external info created, with NH %s", ospf_redist_string(type), ospf->vrf_id, inet_ntoa(p.prefix), p.prefixlen, inetbuf); } return new; } void ospf_external_info_delete(struct ospf *ospf, uint8_t type, unsigned short instance, struct prefix_ipv4 p) { struct route_node *rn; struct ospf_external *ext; ext = ospf_external_lookup(ospf, type, instance); if (!ext) return; rn = route_node_lookup(EXTERNAL_INFO(ext), (struct prefix *)&p); if (rn) { ospf_external_info_free(rn->info); rn->info = NULL; route_unlock_node(rn); route_unlock_node(rn); } } struct external_info *ospf_external_info_lookup(struct ospf *ospf, uint8_t type, unsigned short instance, struct prefix_ipv4 *p) { struct route_node *rn; struct ospf_external *ext; ext = ospf_external_lookup(ospf, type, instance); if (!ext) return NULL; rn = route_node_lookup(EXTERNAL_INFO(ext), (struct prefix *)p); if (rn) { route_unlock_node(rn); if (rn->info) return rn->info; } return NULL; } struct ospf_lsa *ospf_external_info_find_lsa(struct ospf *ospf, struct prefix_ipv4 *p) { struct ospf_lsa *lsa; struct as_external_lsa *al; struct in_addr mask, id; lsa = ospf_lsdb_lookup_by_id(ospf->lsdb, OSPF_AS_EXTERNAL_LSA, p->prefix, ospf->router_id); if (!lsa) return NULL; al = (struct as_external_lsa *)lsa->data; masklen2ip(p->prefixlen, &mask); if (mask.s_addr != al->mask.s_addr) { id.s_addr = p->prefix.s_addr | (~mask.s_addr); lsa = ospf_lsdb_lookup_by_id(ospf->lsdb, OSPF_AS_EXTERNAL_LSA, id, ospf->router_id); if (!lsa) return NULL; } return lsa; } /* Update ASBR status. */ void ospf_asbr_status_update(struct ospf *ospf, uint8_t status) { zlog_info("ASBR[Status:%d]: Update", status); /* ASBR on. */ if (status) { /* Already ASBR. */ if (IS_OSPF_ASBR(ospf)) { zlog_info("ASBR[Status:%d]: Already ASBR", status); return; } SET_FLAG(ospf->flags, OSPF_FLAG_ASBR); } else { /* Already non ASBR. */ if (!IS_OSPF_ASBR(ospf)) { zlog_info("ASBR[Status:%d]: Already non ASBR", status); return; } UNSET_FLAG(ospf->flags, OSPF_FLAG_ASBR); } /* Transition from/to status ASBR, schedule timer. */ ospf_spf_calculate_schedule(ospf, SPF_FLAG_ASBR_STATUS_CHANGE); ospf_router_lsa_update(ospf); } void ospf_redistribute_withdraw(struct ospf *ospf, uint8_t type, unsigned short instance) { struct route_node *rn; struct external_info *ei; struct ospf_external *ext; ext = ospf_external_lookup(ospf, type, instance); if (!ext) return; /* Delete external info for specified type. */ if (EXTERNAL_INFO(ext)) for (rn = route_top(EXTERNAL_INFO(ext)); rn; rn = route_next(rn)) if ((ei = rn->info)) if (ospf_external_info_find_lsa(ospf, &ei->p)) { if (is_prefix_default(&ei->p) && ospf->default_originate != DEFAULT_ORIGINATE_NONE) continue; ospf_external_lsa_flush( ospf, type, &ei->p, ei->ifindex /*, ei->nexthop */); ospf_external_info_free(ei); route_unlock_node(rn); rn->info = NULL; } } frr-7.2.1/ospfd/ospf_asbr.h0000644000000000000000000000523713610377563012465 00000000000000/* * OSPF AS Boundary Router functions. * Copyright (C) 1999, 2000 Kunihiro Ishiguro, Toshiaki Takada * * This file is part of GNU Zebra. * * GNU Zebra is free software; you can redistribute it and/or modify it * under the terms of the GNU General Public License as published by the * Free Software Foundation; either version 2, or (at your option) any * later version. * * GNU Zebra 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; see the file COPYING; if not, write to the Free Software * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA */ #ifndef _ZEBRA_OSPF_ASBR_H #define _ZEBRA_OSPF_ASBR_H struct route_map_set_values { int32_t metric; int32_t metric_type; }; /* Redistributed external information. */ struct external_info { /* Type of source protocol. */ uint8_t type; unsigned short instance; /* Prefix. */ struct prefix_ipv4 p; /* Interface index. */ ifindex_t ifindex; /* Nexthop address. */ struct in_addr nexthop; /* Additional Route tag. */ route_tag_t tag; struct route_map_set_values route_map_set; #define ROUTEMAP_METRIC(E) (E)->route_map_set.metric #define ROUTEMAP_METRIC_TYPE(E) (E)->route_map_set.metric_type }; #define OSPF_ASBR_CHECK_DELAY 30 extern void ospf_external_route_remove(struct ospf *, struct prefix_ipv4 *); extern struct external_info *ospf_external_info_new(uint8_t, unsigned short); extern void ospf_reset_route_map_set_values(struct route_map_set_values *); extern int ospf_route_map_set_compare(struct route_map_set_values *, struct route_map_set_values *); extern struct external_info *ospf_external_info_add(struct ospf *, uint8_t, unsigned short, struct prefix_ipv4, ifindex_t, struct in_addr, route_tag_t); extern void ospf_external_info_delete(struct ospf *, uint8_t, unsigned short, struct prefix_ipv4); extern struct external_info *ospf_external_info_lookup(struct ospf *, uint8_t, unsigned short, struct prefix_ipv4 *); extern void ospf_asbr_status_update(struct ospf *, uint8_t); extern void ospf_redistribute_withdraw(struct ospf *, uint8_t, unsigned short); extern void ospf_asbr_check(void); extern void ospf_schedule_asbr_check(void); extern void ospf_asbr_route_install_lsa(struct ospf_lsa *); extern struct ospf_lsa *ospf_external_info_find_lsa(struct ospf *, struct prefix_ipv4 *p); #endif /* _ZEBRA_OSPF_ASBR_H */ frr-7.2.1/ospfd/ospf_ase.c0000644000000000000000000005644413610377563012307 00000000000000/* * OSPF AS external route calculation. * Copyright (C) 1999, 2000 Alex Zinin, Toshiaki Takada * * This file is part of GNU Zebra. * * GNU Zebra is free software; you can redistribute it and/or modify it * under the terms of the GNU General Public License as published by the * Free Software Foundation; either version 2, or (at your option) any * later version. * * GNU Zebra 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; see the file COPYING; if not, write to the Free Software * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA */ #include #include "thread.h" #include "memory.h" #include "hash.h" #include "linklist.h" #include "prefix.h" #include "if.h" #include "table.h" #include "vty.h" #include "log.h" #include "ospfd/ospfd.h" #include "ospfd/ospf_interface.h" #include "ospfd/ospf_ism.h" #include "ospfd/ospf_asbr.h" #include "ospfd/ospf_lsa.h" #include "ospfd/ospf_lsdb.h" #include "ospfd/ospf_neighbor.h" #include "ospfd/ospf_nsm.h" #include "ospfd/ospf_spf.h" #include "ospfd/ospf_route.h" #include "ospfd/ospf_ase.h" #include "ospfd/ospf_zebra.h" #include "ospfd/ospf_dump.h" struct ospf_route *ospf_find_asbr_route(struct ospf *ospf, struct route_table *rtrs, struct prefix_ipv4 *asbr) { struct route_node *rn; struct ospf_route * or, *best = NULL; struct listnode *node; struct list *chosen; /* Sanity check. */ if (rtrs == NULL) return NULL; rn = route_node_lookup(rtrs, (struct prefix *)asbr); if (!rn) return NULL; route_unlock_node(rn); chosen = list_new(); /* First try to find intra-area non-bb paths. */ if (!CHECK_FLAG(ospf->config, OSPF_RFC1583_COMPATIBLE)) for (ALL_LIST_ELEMENTS_RO((struct list *)rn->info, node, or)) if (or->cost < OSPF_LS_INFINITY) if (!OSPF_IS_AREA_ID_BACKBONE(or->u.std.area_id) && or->path_type == OSPF_PATH_INTRA_AREA) listnode_add(chosen, or); /* If none is found -- look through all. */ if (listcount(chosen) == 0) { list_delete(&chosen); chosen = rn->info; } /* Now find the route with least cost. */ for (ALL_LIST_ELEMENTS_RO(chosen, node, or)) if (or->cost < OSPF_LS_INFINITY) { if (best == NULL) best = or ; else if (best->cost > or->cost) best = or ; else if (best->cost == or->cost && IPV4_ADDR_CMP( &best->u.std.area_id, & or->u.std.area_id) < 0) best = or ; } if (chosen != rn->info) list_delete(&chosen); return best; } struct ospf_route *ospf_find_asbr_route_through_area(struct route_table *rtrs, struct prefix_ipv4 *asbr, struct ospf_area *area) { struct route_node *rn; /* Sanity check. */ if (rtrs == NULL) return NULL; rn = route_node_lookup(rtrs, (struct prefix *)asbr); if (rn) { struct listnode *node; struct ospf_route * or ; route_unlock_node(rn); for (ALL_LIST_ELEMENTS_RO((struct list *)rn->info, node, or)) if (IPV4_ADDR_SAME(& or->u.std.area_id, &area->area_id)) return or ; } return NULL; } static void ospf_ase_complete_direct_routes(struct ospf_route *ro, struct in_addr nexthop) { struct listnode *node; struct ospf_path *op; for (ALL_LIST_ELEMENTS_RO(ro->paths, node, op)) if (op->nexthop.s_addr == 0) op->nexthop.s_addr = nexthop.s_addr; } static int ospf_ase_forward_address_check(struct ospf *ospf, struct in_addr fwd_addr) { struct listnode *ifn; struct ospf_interface *oi; for (ALL_LIST_ELEMENTS_RO(ospf->oiflist, ifn, oi)) if (if_is_operative(oi->ifp)) if (oi->type != OSPF_IFTYPE_VIRTUALLINK) if (IPV4_ADDR_SAME(&oi->address->u.prefix4, &fwd_addr)) return 0; return 1; } #if 0 /* Calculate ASBR route. */ static struct ospf_route * ospf_ase_calculate_asbr_route (struct ospf *ospf, struct route_table *rt_network, struct route_table *rt_router, struct as_external_lsa *al) { struct prefix_ipv4 asbr; struct ospf_route *asbr_route; struct route_node *rn; /* Find ASBR route from Router routing table. */ asbr.family = AF_INET; asbr.prefix = al->header.adv_router; asbr.prefixlen = IPV4_MAX_BITLEN; apply_mask_ipv4 (&asbr); asbr_route = ospf_find_asbr_route (ospf, rt_router, &asbr); if (asbr_route == NULL) { if (IS_DEBUG_OSPF (lsa, LSA)) zlog_debug ("ospf_ase_calculate(): Route to ASBR %s not found", inet_ntoa (asbr.prefix)); return NULL; } if (!(asbr_route->u.std.flags & ROUTER_LSA_EXTERNAL)) { if (IS_DEBUG_OSPF (lsa, LSA)) zlog_debug ("ospf_ase_calculate(): Originating router is not an ASBR"); return NULL; } if (al->e[0].fwd_addr.s_addr != 0) { if (IS_DEBUG_OSPF (lsa, LSA)) zlog_debug ("ospf_ase_calculate(): " "Forwarding address is not 0.0.0.0."); if (! ospf_ase_forward_address_check (ospf, al->e[0].fwd_addr)) { if (IS_DEBUG_OSPF (lsa, LSA)) zlog_debug ("ospf_ase_calculate(): " "Forwarding address is one of our addresses, Ignore."); return NULL; } if (IS_DEBUG_OSPF (lsa, LSA)) zlog_debug ("ospf_ase_calculate(): " "Looking up in the Network Routing Table."); /* Looking up the path to the fwd_addr from Network route. */ asbr.family = AF_INET; asbr.prefix = al->e[0].fwd_addr; asbr.prefixlen = IPV4_MAX_BITLEN; rn = route_node_match (rt_network, (struct prefix *) &asbr); if (rn == NULL) { if (IS_DEBUG_OSPF (lsa, LSA)) zlog_debug ("ospf_ase_calculate(): " "Couldn't find a route to the forwarding address."); return NULL; } route_unlock_node (rn); if ((asbr_route = rn->info) == NULL) { if (IS_DEBUG_OSPF (lsa, LSA)) zlog_debug ("ospf_ase_calculate(): " "Somehow OSPF route to ASBR is lost"); return NULL; } } return asbr_route; } #endif static struct ospf_route * ospf_ase_calculate_new_route(struct ospf_lsa *lsa, struct ospf_route *asbr_route, uint32_t metric) { struct as_external_lsa *al; struct ospf_route *new; al = (struct as_external_lsa *)lsa->data; new = ospf_route_new(); /* Set redistributed type -- does make sense? */ /* new->type = type; */ new->id = al->header.id; new->mask = al->mask; if (!IS_EXTERNAL_METRIC(al->e[0].tos)) { if (IS_DEBUG_OSPF(lsa, LSA)) zlog_debug("Route[External]: type-1 created."); new->path_type = OSPF_PATH_TYPE1_EXTERNAL; new->cost = asbr_route->cost + metric; /* X + Y */ } else { if (IS_DEBUG_OSPF(lsa, LSA)) zlog_debug("Route[External]: type-2 created."); new->path_type = OSPF_PATH_TYPE2_EXTERNAL; new->cost = asbr_route->cost; /* X */ new->u.ext.type2_cost = metric; /* Y */ } new->type = OSPF_DESTINATION_NETWORK; new->u.ext.origin = lsa; new->u.ext.tag = ntohl(al->e[0].route_tag); new->u.ext.asbr = asbr_route; assert(new != asbr_route); return new; } #define OSPF_ASE_CALC_INTERVAL 1 int ospf_ase_calculate_route(struct ospf *ospf, struct ospf_lsa *lsa) { uint32_t metric; struct as_external_lsa *al; struct ospf_route *asbr_route; struct prefix_ipv4 asbr, p; struct route_node *rn; struct ospf_route *new, * or ; char buf1[INET_ADDRSTRLEN]; int ret; assert(lsa); al = (struct as_external_lsa *)lsa->data; if (lsa->data->type == OSPF_AS_NSSA_LSA) if (IS_DEBUG_OSPF_NSSA) zlog_debug("ospf_ase_calc(): Processing Type-7"); /* Stay away from any Local Translated Type-7 LSAs */ if (CHECK_FLAG(lsa->flags, OSPF_LSA_LOCAL_XLT)) { if (IS_DEBUG_OSPF_NSSA) zlog_debug("ospf_ase_calc(): Rejecting Local Xlt'd"); return 0; } if (IS_DEBUG_OSPF(lsa, LSA)) { snprintf(buf1, INET_ADDRSTRLEN, "%s", inet_ntoa(al->header.adv_router)); zlog_debug( "Route[External]: Calculate AS-external-LSA to %s/%d adv_router %s", inet_ntoa(al->header.id), ip_masklen(al->mask), buf1); } /* (1) If the cost specified by the LSA is LSInfinity, or if the LSA's LS age is equal to MaxAge, then examine the next LSA. */ if ((metric = GET_METRIC(al->e[0].metric)) >= OSPF_LS_INFINITY) { if (IS_DEBUG_OSPF(lsa, LSA)) zlog_debug( "Route[External]: Metric is OSPF_LS_INFINITY"); return 0; } if (IS_LSA_MAXAGE(lsa)) { if (IS_DEBUG_OSPF(lsa, LSA)) zlog_debug( "Route[External]: AS-external-LSA is MAXAGE"); return 0; } /* (2) If the LSA was originated by the calculating router itself, examine the next LSA. */ if (IS_LSA_SELF(lsa)) { if (IS_DEBUG_OSPF(lsa, LSA)) zlog_debug( "Route[External]: AS-external-LSA is self originated"); return 0; } /* (3) Call the destination described by the LSA N. N's address is obtained by masking the LSA's Link State ID with the network/subnet mask contained in the body of the LSA. Look up the routing table entries (potentially one per attached area) for the AS boundary router (ASBR) that originated the LSA. If no entries exist for router ASBR (i.e., ASBR is unreachable), do nothing with this LSA and consider the next in the list. */ asbr.family = AF_INET; asbr.prefix = al->header.adv_router; asbr.prefixlen = IPV4_MAX_BITLEN; apply_mask_ipv4(&asbr); asbr_route = ospf_find_asbr_route(ospf, ospf->new_rtrs, &asbr); if (asbr_route == NULL) { if (IS_DEBUG_OSPF(lsa, LSA)) zlog_debug( "Route[External]: Can't find originating ASBR route"); return 0; } if (!(asbr_route->u.std.flags & ROUTER_LSA_EXTERNAL)) { if (IS_DEBUG_OSPF(lsa, LSA)) zlog_debug( "Route[External]: Originating router is not an ASBR"); return 0; } /* Else, this LSA describes an AS external path to destination N. Examine the forwarding address specified in the AS- external-LSA. This indicates the IP address to which packets for the destination should be forwarded. */ if (al->e[0].fwd_addr.s_addr == 0) { /* If the forwarding address is set to 0.0.0.0, packets should be sent to the ASBR itself. Among the multiple routing table entries for the ASBR, select the preferred entry as follows. If RFC1583Compatibility is set to "disabled", prune the set of routing table entries for the ASBR as described in Section 16.4.1. In any case, among the remaining routing table entries, select the routing table entry with the least cost; when there are multiple least cost routing table entries the entry whose associated area has the largest OSPF Area ID (when considered as an unsigned 32-bit integer) is chosen. */ /* asbr_route already contains the requested route */ } else { /* If the forwarding address is non-zero, look up the forwarding address in the routing table.[24] The matching routing table entry must specify an intra-area or inter-area path; if no such path exists, do nothing with the LSA and consider the next in the list. */ if (!ospf_ase_forward_address_check(ospf, al->e[0].fwd_addr)) { if (IS_DEBUG_OSPF(lsa, LSA)) zlog_debug( "Route[External]: Forwarding address is our router " "address"); return 0; } asbr.family = AF_INET; asbr.prefix = al->e[0].fwd_addr; asbr.prefixlen = IPV4_MAX_BITLEN; rn = route_node_match(ospf->new_table, (struct prefix *)&asbr); if (rn == NULL || (asbr_route = rn->info) == NULL) { if (IS_DEBUG_OSPF(lsa, LSA)) zlog_debug( "Route[External]: Can't find route to forwarding " "address"); if (rn) route_unlock_node(rn); return 0; } route_unlock_node(rn); } /* (4) Let X be the cost specified by the preferred routing table entry for the ASBR/forwarding address, and Y the cost specified in the LSA. X is in terms of the link state metric, and Y is a type 1 or 2 external metric. */ /* (5) Look up the routing table entry for the destination N. If no entry exists for N, install the AS external path to N, with next hop equal to the list of next hops to the forwarding address, and advertising router equal to ASBR. If the external metric type is 1, then the path-type is set to type 1 external and the cost is equal to X+Y. If the external metric type is 2, the path-type is set to type 2 external, the link state component of the route's cost is X, and the type 2 cost is Y. */ new = ospf_ase_calculate_new_route(lsa, asbr_route, metric); /* (6) Compare the AS external path described by the LSA with the existing paths in N's routing table entry, as follows. If the new path is preferred, it replaces the present paths in N's routing table entry. If the new path is of equal preference, it is added to N's routing table entry's list of paths. */ /* Set prefix. */ p.family = AF_INET; p.prefix = al->header.id; p.prefixlen = ip_masklen(al->mask); /* if there is a Intra/Inter area route to the N do not install external route */ if ((rn = route_node_lookup(ospf->new_table, (struct prefix *)&p))) { route_unlock_node(rn); if (rn->info == NULL) zlog_info("Route[External]: rn->info NULL"); if (new) ospf_route_free(new); return 0; } /* Find a route to the same dest */ /* If there is no route, create new one. */ if ((rn = route_node_lookup(ospf->new_external_route, (struct prefix *)&p))) route_unlock_node(rn); if (!rn || (or = rn->info) == NULL) { if (IS_DEBUG_OSPF(lsa, LSA)) zlog_debug("Route[External]: Adding a new route %s/%d with paths %u", inet_ntoa(p.prefix), p.prefixlen, listcount(asbr_route->paths)); ospf_route_add(ospf->new_external_route, &p, new, asbr_route); if (al->e[0].fwd_addr.s_addr) ospf_ase_complete_direct_routes(new, al->e[0].fwd_addr); return 0; } else { /* (a) Intra-area and inter-area paths are always preferred over AS external paths. (b) Type 1 external paths are always preferred over type 2 external paths. When all paths are type 2 external paths, the paths with the smallest advertised type 2 metric are always preferred. */ ret = ospf_route_cmp(ospf, new, or); /* (c) If the new AS external path is still indistinguishable from the current paths in the N's routing table entry, and RFC1583Compatibility is set to "disabled", select the preferred paths based on the intra-AS paths to the ASBR/forwarding addresses, as specified in Section 16.4.1. (d) If the new AS external path is still indistinguishable from the current paths in the N's routing table entry, select the preferred path based on a least cost comparison. Type 1 external paths are compared by looking at the sum of the distance to the forwarding address and the advertised type 1 metric (X+Y). Type 2 external paths advertising equal type 2 metrics are compared by looking at the distance to the forwarding addresses. */ /* New route is better */ if (ret < 0) { if (IS_DEBUG_OSPF(lsa, LSA)) zlog_debug( "Route[External]: New route is better"); ospf_route_subst(rn, new, asbr_route); if (al->e[0].fwd_addr.s_addr) ospf_ase_complete_direct_routes( new, al->e[0].fwd_addr); or = new; new = NULL; } /* Old route is better */ else if (ret > 0) { if (IS_DEBUG_OSPF(lsa, LSA)) zlog_debug( "Route[External]: Old route is better"); /* do nothing */ } /* Routes are equal */ else { if (IS_DEBUG_OSPF(lsa, LSA)) zlog_debug("Route[External]: Routes are equal"); ospf_route_copy_nexthops(or, asbr_route->paths); if (al->e[0].fwd_addr.s_addr) ospf_ase_complete_direct_routes( or, al->e[0].fwd_addr); } } /* Make sure setting newly calculated ASBR route.*/ or->u.ext.asbr = asbr_route; if (new) ospf_route_free(new); lsa->route = or ; return 0; } static int ospf_ase_route_match_same(struct route_table *rt, struct prefix *prefix, struct ospf_route *newor) { struct route_node *rn; struct ospf_route *or; struct ospf_path *op; struct ospf_path *newop; struct listnode *n1; struct listnode *n2; if (!rt || !prefix) return 0; rn = route_node_lookup(rt, prefix); if (!rn) return 0; route_unlock_node(rn); or = rn->info; assert(or); if (or->path_type != newor->path_type) return 0; switch (or->path_type) { case OSPF_PATH_TYPE1_EXTERNAL: if (or->cost != newor->cost) return 0; break; case OSPF_PATH_TYPE2_EXTERNAL: if ((or->cost != newor->cost) || (or->u.ext.type2_cost != newor->u.ext.type2_cost)) return 0; break; default: assert(0); return 0; } assert(or->paths); if (or->paths->count != newor->paths->count) return 0; /* Check each path. */ for (n1 = listhead(or->paths), n2 = listhead(newor->paths); n1 && n2; n1 = listnextnode_unchecked(n1), n2 = listnextnode_unchecked(n2)) { op = listgetdata(n1); newop = listgetdata(n2); if (!IPV4_ADDR_SAME(&op->nexthop, &newop->nexthop)) return 0; if (op->ifindex != newop->ifindex) return 0; } if (or->u.ext.tag != newor->u.ext.tag) return 0; return 1; } static int ospf_ase_compare_tables(struct ospf *ospf, struct route_table *new_external_route, struct route_table *old_external_route) { struct route_node *rn, *new_rn; struct ospf_route * or ; /* Remove deleted routes */ for (rn = route_top(old_external_route); rn; rn = route_next(rn)) if ((or = rn->info)) { if (!(new_rn = route_node_lookup(new_external_route, &rn->p))) ospf_zebra_delete( ospf, (struct prefix_ipv4 *)&rn->p, or); else route_unlock_node(new_rn); } /* Install new routes */ for (rn = route_top(new_external_route); rn; rn = route_next(rn)) if ((or = rn->info) != NULL) if (!ospf_ase_route_match_same(old_external_route, &rn->p, or)) ospf_zebra_add( ospf, (struct prefix_ipv4 *)&rn->p, or); return 0; } static int ospf_ase_calculate_timer(struct thread *t) { struct ospf *ospf; struct ospf_lsa *lsa; struct route_node *rn; struct listnode *node; struct ospf_area *area; struct timeval start_time, stop_time; ospf = THREAD_ARG(t); ospf->t_ase_calc = NULL; if (ospf->ase_calc) { ospf->ase_calc = 0; monotime(&start_time); /* Calculate external route for each AS-external-LSA */ LSDB_LOOP (EXTERNAL_LSDB(ospf), rn, lsa) ospf_ase_calculate_route(ospf, lsa); /* This version simple adds to the table all NSSA areas */ if (ospf->anyNSSA) for (ALL_LIST_ELEMENTS_RO(ospf->areas, node, area)) { if (IS_DEBUG_OSPF_NSSA) zlog_debug( "ospf_ase_calculate_timer(): looking at area %s", inet_ntoa(area->area_id)); if (area->external_routing == OSPF_AREA_NSSA) LSDB_LOOP (NSSA_LSDB(area), rn, lsa) ospf_ase_calculate_route(ospf, lsa); } /* kevinm: And add the NSSA routes in ospf_top */ LSDB_LOOP (NSSA_LSDB(ospf), rn, lsa) ospf_ase_calculate_route(ospf, lsa); /* Compare old and new external routing table and install the difference info zebra/kernel */ ospf_ase_compare_tables(ospf, ospf->new_external_route, ospf->old_external_route); /* Delete old external routing table */ ospf_route_table_free(ospf->old_external_route); ospf->old_external_route = ospf->new_external_route; ospf->new_external_route = route_table_init(); monotime(&stop_time); if (IS_DEBUG_OSPF_EVENT) zlog_info( "SPF Processing Time(usecs): External Routes: %lld\n", (stop_time.tv_sec - start_time.tv_sec) * 1000000LL + (stop_time.tv_usec - start_time.tv_usec)); } return 0; } void ospf_ase_calculate_schedule(struct ospf *ospf) { if (ospf == NULL) return; ospf->ase_calc = 1; } void ospf_ase_calculate_timer_add(struct ospf *ospf) { if (ospf == NULL) return; thread_add_timer(master, ospf_ase_calculate_timer, ospf, OSPF_ASE_CALC_INTERVAL, &ospf->t_ase_calc); } void ospf_ase_register_external_lsa(struct ospf_lsa *lsa, struct ospf *top) { struct route_node *rn; struct prefix_ipv4 p; struct list *lst; struct as_external_lsa *al; al = (struct as_external_lsa *)lsa->data; p.family = AF_INET; p.prefix = lsa->data->id; p.prefixlen = ip_masklen(al->mask); apply_mask_ipv4(&p); rn = route_node_get(top->external_lsas, (struct prefix *)&p); if ((lst = rn->info) == NULL) rn->info = lst = list_new(); else route_unlock_node(rn); /* We assume that if LSA is deleted from DB is is also deleted from this RT */ listnode_add(lst, ospf_lsa_lock(lsa)); /* external_lsas lst */ } void ospf_ase_unregister_external_lsa(struct ospf_lsa *lsa, struct ospf *top) { struct route_node *rn; struct prefix_ipv4 p; struct list *lst; struct as_external_lsa *al; al = (struct as_external_lsa *)lsa->data; p.family = AF_INET; p.prefix = lsa->data->id; p.prefixlen = ip_masklen(al->mask); apply_mask_ipv4(&p); rn = route_node_lookup(top->external_lsas, (struct prefix *)&p); if (rn) { lst = rn->info; listnode_delete(lst, lsa); ospf_lsa_unlock(&lsa); /* external_lsas list */ route_unlock_node(rn); } } void ospf_ase_external_lsas_finish(struct route_table *rt) { struct route_node *rn; struct ospf_lsa *lsa; struct list *lst; struct listnode *node, *nnode; for (rn = route_top(rt); rn; rn = route_next(rn)) if ((lst = rn->info) != NULL) { for (ALL_LIST_ELEMENTS(lst, node, nnode, lsa)) ospf_lsa_unlock(&lsa); /* external_lsas lst */ list_delete(&lst); } route_table_finish(rt); } void ospf_ase_incremental_update(struct ospf *ospf, struct ospf_lsa *lsa) { struct list *lsas; struct listnode *node; struct route_node *rn, *rn2; struct prefix_ipv4 p; struct route_table *tmp_old; struct as_external_lsa *al; al = (struct as_external_lsa *)lsa->data; p.family = AF_INET; p.prefix = lsa->data->id; p.prefixlen = ip_masklen(al->mask); apply_mask_ipv4(&p); /* if new_table is NULL, there was no spf calculation, thus incremental update is unneeded */ if (!ospf->new_table) return; /* If there is already an intra-area or inter-area route to the destination, no recalculation is necessary (internal routes take precedence). */ rn = route_node_lookup(ospf->new_table, (struct prefix *)&p); if (rn) { route_unlock_node(rn); if (rn->info) return; } rn = route_node_lookup(ospf->external_lsas, (struct prefix *)&p); assert(rn); assert(rn->info); lsas = rn->info; route_unlock_node(rn); for (ALL_LIST_ELEMENTS_RO(lsas, node, lsa)) ospf_ase_calculate_route(ospf, lsa); /* prepare temporary old routing table for compare */ tmp_old = route_table_init(); rn = route_node_lookup(ospf->old_external_route, (struct prefix *)&p); if (rn && rn->info) { rn2 = route_node_get(tmp_old, (struct prefix *)&p); rn2->info = rn->info; route_unlock_node(rn); } /* install changes to zebra */ ospf_ase_compare_tables(ospf, ospf->new_external_route, tmp_old); /* update ospf->old_external_route table */ if (rn && rn->info) ospf_route_free((struct ospf_route *)rn->info); rn2 = route_node_lookup(ospf->new_external_route, (struct prefix *)&p); /* if new route exists, install it to ospf->old_external_route */ if (rn2 && rn2->info) { if (!rn) rn = route_node_get(ospf->old_external_route, (struct prefix *)&p); rn->info = rn2->info; } else { /* remove route node from ospf->old_external_route */ if (rn) { rn->info = NULL; route_unlock_node(rn); } } if (rn2) { /* rn2->info is stored in route node of ospf->old_external_route */ rn2->info = NULL; route_unlock_node(rn2); route_unlock_node(rn2); } route_table_finish(tmp_old); } frr-7.2.1/ospfd/ospf_ase.h0000644000000000000000000000320213610377563012274 00000000000000/* * OSPF AS External route calculation. * Copyright (C) 1999, 2000 Alex Zinin, Toshiaki Takada * * This file is part of GNU Zebra. * * GNU Zebra is free software; you can redistribute it and/or modify it * under the terms of the GNU General Public License as published by the * Free Software Foundation; either version 2, or (at your option) any * later version. * * GNU Zebra 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; see the file COPYING; if not, write to the Free Software * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA */ #ifndef _ZEBRA_OSPF_ASE_H #define _ZEBRA_OSPF_ASE_H extern struct ospf_route * ospf_find_asbr_route(struct ospf *, struct route_table *, struct prefix_ipv4 *); extern struct ospf_route * ospf_find_asbr_route_through_area(struct route_table *, struct prefix_ipv4 *, struct ospf_area *); extern int ospf_ase_calculate_route(struct ospf *, struct ospf_lsa *); extern void ospf_ase_calculate_schedule(struct ospf *); extern void ospf_ase_calculate_timer_add(struct ospf *); extern void ospf_ase_external_lsas_finish(struct route_table *); extern void ospf_ase_incremental_update(struct ospf *, struct ospf_lsa *); extern void ospf_ase_register_external_lsa(struct ospf_lsa *, struct ospf *); extern void ospf_ase_unregister_external_lsa(struct ospf_lsa *, struct ospf *); #endif /* _ZEBRA_OSPF_ASE_H */ frr-7.2.1/ospfd/ospf_bfd.c0000644000000000000000000003102213610377563012253 00000000000000/** * ospf_bfd.c: OSPF BFD handling routines * * @copyright Copyright (C) 2015 Cumulus Networks, Inc. * * This file is part of GNU Zebra. * * GNU Zebra is free software; you can redistribute it and/or modify it * under the terms of the GNU General Public License as published by the * Free Software Foundation; either version 2, or (at your option) any * later version. * * GNU Zebra 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; see the file COPYING; if not, write to the Free Software * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA */ #include #include "command.h" #include "linklist.h" #include "memory.h" #include "prefix.h" #include "thread.h" #include "buffer.h" #include "stream.h" #include "zclient.h" #include "vty.h" #include "table.h" #include "bfd.h" #include "ospfd.h" #include "ospf_asbr.h" #include "ospf_lsa.h" #include "ospf_lsdb.h" #include "ospf_neighbor.h" #include "ospf_interface.h" #include "ospf_nsm.h" #include "ospf_bfd.h" #include "ospf_dump.h" #include "ospf_vty.h" extern struct zclient *zclient; /* * ospf_bfd_info_free - Free BFD info structure */ void ospf_bfd_info_free(void **bfd_info) { bfd_info_free((struct bfd_info **)bfd_info); } /* * ospf_bfd_reg_dereg_nbr - Register/Deregister a neighbor with BFD through * zebra for starting/stopping the monitoring of * the neighbor rechahability. */ static void ospf_bfd_reg_dereg_nbr(struct ospf_neighbor *nbr, int command) { struct ospf_interface *oi = nbr->oi; struct interface *ifp = oi->ifp; struct ospf_if_params *params; struct bfd_info *bfd_info; int cbit; /* Check if BFD is enabled */ params = IF_DEF_PARAMS(ifp); /* Check if BFD is enabled */ if (!params->bfd_info) return; bfd_info = (struct bfd_info *)params->bfd_info; if (IS_DEBUG_OSPF(zebra, ZEBRA_INTERFACE)) zlog_debug("%s nbr (%s) with BFD. OSPF vrf %s", bfd_get_command_dbg_str(command), inet_ntoa(nbr->src), ospf_vrf_id_to_name(oi->ospf->vrf_id)); cbit = CHECK_FLAG(bfd_info->flags, BFD_FLAG_BFD_CBIT_ON); bfd_peer_sendmsg(zclient, bfd_info, AF_INET, &nbr->src, NULL, ifp->name, 0, 0, cbit, command, 0, oi->ospf->vrf_id); } /* * ospf_bfd_trigger_event - Neighbor is registered/deregistered with BFD when * neighbor state is changed to/from 2way. */ void ospf_bfd_trigger_event(struct ospf_neighbor *nbr, int old_state, int state) { if ((old_state < NSM_TwoWay) && (state >= NSM_TwoWay)) ospf_bfd_reg_dereg_nbr(nbr, ZEBRA_BFD_DEST_REGISTER); else if ((old_state >= NSM_TwoWay) && (state < NSM_TwoWay)) ospf_bfd_reg_dereg_nbr(nbr, ZEBRA_BFD_DEST_DEREGISTER); } /* * ospf_bfd_reg_dereg_all_nbr - Register/Deregister all neighbors associated * with a interface with BFD through * zebra for starting/stopping the monitoring of * the neighbor rechahability. */ static int ospf_bfd_reg_dereg_all_nbr(struct interface *ifp, int command) { struct ospf_interface *oi; struct route_table *nbrs; struct ospf_neighbor *nbr; struct route_node *irn; struct route_node *nrn; for (irn = route_top(IF_OIFS(ifp)); irn; irn = route_next(irn)) { if ((oi = irn->info) == NULL) continue; if ((nbrs = oi->nbrs) == NULL) continue; for (nrn = route_top(nbrs); nrn; nrn = route_next(nrn)) { if ((nbr = nrn->info) == NULL || nbr == oi->nbr_self) continue; if (command != ZEBRA_BFD_DEST_DEREGISTER) ospf_bfd_info_nbr_create(oi, nbr); else bfd_info_free( (struct bfd_info **)&nbr->bfd_info); if (nbr->state < NSM_TwoWay) continue; ospf_bfd_reg_dereg_nbr(nbr, command); } } return 0; } /* * ospf_bfd_nbr_replay - Replay all the neighbors that have BFD enabled * to zebra */ static int ospf_bfd_nbr_replay(ZAPI_CALLBACK_ARGS) { struct listnode *inode, *node, *onode; struct ospf *ospf; struct ospf_interface *oi; struct route_table *nbrs; struct route_node *rn; struct ospf_neighbor *nbr; struct ospf_if_params *params; if (IS_DEBUG_OSPF(zebra, ZEBRA_INTERFACE)) { zlog_debug("Zebra: BFD Dest replay request"); } /* Send the client registration */ bfd_client_sendmsg(zclient, ZEBRA_BFD_CLIENT_REGISTER, vrf_id); /* Replay the neighbor, if BFD is enabled in OSPF */ for (ALL_LIST_ELEMENTS(om->ospf, node, onode, ospf)) { for (ALL_LIST_ELEMENTS_RO(ospf->oiflist, inode, oi)) { if ((nbrs = oi->nbrs) == NULL) continue; params = IF_DEF_PARAMS(oi->ifp); if (!params->bfd_info) continue; for (rn = route_top(nbrs); rn; rn = route_next(rn)) { if ((nbr = rn->info) == NULL || nbr == oi->nbr_self) continue; if (nbr->state < NSM_TwoWay) continue; if (IS_DEBUG_OSPF(zebra, ZEBRA_INTERFACE)) zlog_debug("Replaying nbr (%s) to BFD", inet_ntoa(nbr->src)); ospf_bfd_reg_dereg_nbr(nbr, ZEBRA_BFD_DEST_UPDATE); } } } return 0; } /* * ospf_bfd_interface_dest_update - Find the neighbor for which the BFD status * has changed and bring down the neighbor * connectivity if the BFD status changed to * down. */ static int ospf_bfd_interface_dest_update(ZAPI_CALLBACK_ARGS) { struct interface *ifp; struct ospf_interface *oi; struct ospf_if_params *params; struct ospf_neighbor *nbr = NULL; struct route_node *node; struct route_node *n_node; struct prefix p; int status; int old_status; struct bfd_info *bfd_info; struct timeval tv; ifp = bfd_get_peer_info(zclient->ibuf, &p, NULL, &status, NULL, vrf_id); if ((ifp == NULL) || (p.family != AF_INET)) return 0; if (IS_DEBUG_OSPF(zebra, ZEBRA_INTERFACE)) { char buf[PREFIX2STR_BUFFER]; prefix2str(&p, buf, sizeof(buf)); zlog_debug("Zebra: interface %s bfd destination %s %s", ifp->name, buf, bfd_get_status_str(status)); } params = IF_DEF_PARAMS(ifp); if (!params->bfd_info) return 0; for (node = route_top(IF_OIFS(ifp)); node; node = route_next(node)) { if ((oi = node->info) == NULL) continue; /* walk the neighbor list for point-to-point network */ if (oi->type == OSPF_IFTYPE_POINTOPOINT) { for (n_node = route_top(oi->nbrs); n_node; n_node = route_next(n_node)) { nbr = n_node->info; if (nbr) { /* skip myself */ if (nbr == oi->nbr_self) { nbr = NULL; continue; } /* Found the matching neighbor */ if (nbr->src.s_addr == p.u.prefix4.s_addr) break; } } } else { nbr = ospf_nbr_lookup_by_addr(oi->nbrs, &p.u.prefix4); } if (!nbr || !nbr->bfd_info) continue; bfd_info = (struct bfd_info *)nbr->bfd_info; if (bfd_info->status == status) continue; old_status = bfd_info->status; bfd_info->status = status; monotime(&tv); bfd_info->last_update = tv.tv_sec; if ((status == BFD_STATUS_DOWN) && (old_status == BFD_STATUS_UP)) { if (IS_DEBUG_OSPF(nsm, NSM_EVENTS)) zlog_debug("NSM[%s:%s]: BFD Down", IF_NAME(nbr->oi), inet_ntoa(nbr->address.u.prefix4)); OSPF_NSM_EVENT_SCHEDULE(nbr, NSM_InactivityTimer); } if ((status == BFD_STATUS_UP) && (old_status == BFD_STATUS_DOWN)) { if (IS_DEBUG_OSPF(nsm, NSM_EVENTS)) zlog_debug("NSM[%s:%s]: BFD Up", IF_NAME(nbr->oi), inet_ntoa(nbr->address.u.prefix4)); } } return 0; } /* * ospf_bfd_info_nbr_create - Create/update BFD information for a neighbor. */ void ospf_bfd_info_nbr_create(struct ospf_interface *oi, struct ospf_neighbor *nbr) { struct bfd_info *oi_bfd_info; struct bfd_info *nbr_bfd_info; struct interface *ifp = oi->ifp; struct ospf_if_params *params; /* Check if BFD is enabled */ params = IF_DEF_PARAMS(ifp); /* Check if BFD is enabled */ if (!params->bfd_info) return; oi_bfd_info = (struct bfd_info *)params->bfd_info; if (!nbr->bfd_info) nbr->bfd_info = bfd_info_create(); nbr_bfd_info = (struct bfd_info *)nbr->bfd_info; nbr_bfd_info->detect_mult = oi_bfd_info->detect_mult; nbr_bfd_info->desired_min_tx = oi_bfd_info->desired_min_tx; nbr_bfd_info->required_min_rx = oi_bfd_info->required_min_rx; } /* * ospf_bfd_write_config - Write the interface BFD configuration. */ void ospf_bfd_write_config(struct vty *vty, struct ospf_if_params *params) { #if HAVE_BFDD == 0 struct bfd_info *bfd_info; #endif /* ! HAVE_BFDD */ if (!params->bfd_info) return; #if HAVE_BFDD == 0 bfd_info = (struct bfd_info *)params->bfd_info; if (CHECK_FLAG(bfd_info->flags, BFD_FLAG_PARAM_CFG)) vty_out(vty, " ip ospf bfd %d %d %d\n", bfd_info->detect_mult, bfd_info->required_min_rx, bfd_info->desired_min_tx); else #endif /* ! HAVE_BFDD */ vty_out(vty, " ip ospf bfd\n"); } /* * ospf_bfd_show_info - Show BFD info structure */ void ospf_bfd_show_info(struct vty *vty, void *bfd_info, json_object *json_obj, bool use_json, int param_only) { if (param_only) bfd_show_param(vty, (struct bfd_info *)bfd_info, 1, 0, use_json, json_obj); else bfd_show_info(vty, (struct bfd_info *)bfd_info, 0, 1, use_json, json_obj); } /* * ospf_bfd_interface_show - Show the interface BFD configuration. */ void ospf_bfd_interface_show(struct vty *vty, struct interface *ifp, json_object *json_interface_sub, bool use_json) { struct ospf_if_params *params; params = IF_DEF_PARAMS(ifp); ospf_bfd_show_info(vty, params->bfd_info, json_interface_sub, use_json, 1); } /* * ospf_bfd_if_param_set - Set the configured BFD paramter values for * interface. */ static void ospf_bfd_if_param_set(struct interface *ifp, uint32_t min_rx, uint32_t min_tx, uint8_t detect_mult, int defaults) { struct ospf_if_params *params; int command = 0; params = IF_DEF_PARAMS(ifp); bfd_set_param((struct bfd_info **)&(params->bfd_info), min_rx, min_tx, detect_mult, defaults, &command); if (command) ospf_bfd_reg_dereg_all_nbr(ifp, command); } DEFUN (ip_ospf_bfd, ip_ospf_bfd_cmd, "ip ospf bfd", "IP Information\n" "OSPF interface commands\n" "Enables BFD support\n") { VTY_DECLVAR_CONTEXT(interface, ifp); struct ospf_if_params *params; struct bfd_info *bfd_info; assert(ifp); params = IF_DEF_PARAMS(ifp); bfd_info = params->bfd_info; if (!bfd_info || !CHECK_FLAG(bfd_info->flags, BFD_FLAG_PARAM_CFG)) ospf_bfd_if_param_set(ifp, BFD_DEF_MIN_RX, BFD_DEF_MIN_TX, BFD_DEF_DETECT_MULT, 1); return CMD_SUCCESS; } #if HAVE_BFDD > 0 DEFUN_HIDDEN( #else DEFUN( #endif /* HAVE_BFDD */ ip_ospf_bfd_param, ip_ospf_bfd_param_cmd, "ip ospf bfd (2-255) (50-60000) (50-60000)", "IP Information\n" "OSPF interface commands\n" "Enables BFD support\n" "Detect Multiplier\n" "Required min receive interval\n" "Desired min transmit interval\n") { VTY_DECLVAR_CONTEXT(interface, ifp); int idx_number = 3; int idx_number_2 = 4; int idx_number_3 = 5; uint32_t rx_val; uint32_t tx_val; uint8_t dm_val; int ret; assert(ifp); if ((ret = bfd_validate_param( vty, argv[idx_number]->arg, argv[idx_number_2]->arg, argv[idx_number_3]->arg, &dm_val, &rx_val, &tx_val)) != CMD_SUCCESS) return ret; ospf_bfd_if_param_set(ifp, rx_val, tx_val, dm_val, 0); return CMD_SUCCESS; } DEFUN (no_ip_ospf_bfd, no_ip_ospf_bfd_cmd, #if HAVE_BFDD > 0 "no ip ospf bfd", #else "no ip ospf bfd [(2-255) (50-60000) (50-60000)]", #endif /* HAVE_BFDD */ NO_STR "IP Information\n" "OSPF interface commands\n" "Disables BFD support\n" #if HAVE_BFDD == 0 "Detect Multiplier\n" "Required min receive interval\n" "Desired min transmit interval\n" #endif /* !HAVE_BFDD */ ) { VTY_DECLVAR_CONTEXT(interface, ifp); struct ospf_if_params *params; assert(ifp); params = IF_DEF_PARAMS(ifp); if (params->bfd_info) { ospf_bfd_reg_dereg_all_nbr(ifp, ZEBRA_BFD_DEST_DEREGISTER); bfd_info_free(&(params->bfd_info)); } return CMD_SUCCESS; } void ospf_bfd_init(void) { bfd_gbl_init(); /* Initialize BFD client functions */ zclient->interface_bfd_dest_update = ospf_bfd_interface_dest_update; zclient->bfd_dest_replay = ospf_bfd_nbr_replay; /* Install BFD command */ install_element(INTERFACE_NODE, &ip_ospf_bfd_cmd); install_element(INTERFACE_NODE, &ip_ospf_bfd_param_cmd); install_element(INTERFACE_NODE, &no_ip_ospf_bfd_cmd); } frr-7.2.1/ospfd/ospf_bfd.h0000644000000000000000000000313713610377563012266 00000000000000/** * ospf_bfd.h: OSPF BFD definitions and structures * * @copyright Copyright (C) 2015 Cumulus Networks, Inc. * * This file is part of GNU Zebra. * * GNU Zebra is free software; you can redistribute it and/or modify it * under the terms of the GNU General Public License as published by the * Free Software Foundation; either version 2, or (at your option) any * later version. * * GNU Zebra 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; see the file COPYING; if not, write to the Free Software * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA */ #ifndef _ZEBRA_OSPF_BFD_H #define _ZEBRA_OSPF_BFD_H #include "json.h" extern void ospf_bfd_init(void); extern void ospf_bfd_write_config(struct vty *vty, struct ospf_if_params *params); extern void ospf_bfd_trigger_event(struct ospf_neighbor *nbr, int old_state, int state); extern void ospf_bfd_interface_show(struct vty *vty, struct interface *ifp, json_object *json_interface_sub, bool use_json); extern void ospf_bfd_info_nbr_create(struct ospf_interface *oi, struct ospf_neighbor *nbr); extern void ospf_bfd_show_info(struct vty *vty, void *bfd_info, json_object *json_obj, bool use_json, int param_only); extern void ospf_bfd_info_free(void **bfd_info); #endif /* _ZEBRA_OSPF_BFD_H */ frr-7.2.1/ospfd/ospf_dump.c0000644000000000000000000014241713610377563012500 00000000000000/* * OSPFd dump routine. * Copyright (C) 1999, 2000 Toshiaki Takada * * This file is part of GNU Zebra. * * GNU Zebra is free software; you can redistribute it and/or modify it * under the terms of the GNU General Public License as published by the * Free Software Foundation; either version 2, or (at your option) any * later version. * * GNU Zebra 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; see the file COPYING; if not, write to the Free Software * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA */ #include #include "monotime.h" #include "linklist.h" #include "thread.h" #include "prefix.h" #include "command.h" #include "stream.h" #include "log.h" #include "sockopt.h" #include "ospfd/ospfd.h" #include "ospfd/ospf_interface.h" #include "ospfd/ospf_ism.h" #include "ospfd/ospf_asbr.h" #include "ospfd/ospf_lsa.h" #include "ospfd/ospf_lsdb.h" #include "ospfd/ospf_neighbor.h" #include "ospfd/ospf_nsm.h" #include "ospfd/ospf_dump.h" #include "ospfd/ospf_packet.h" #include "ospfd/ospf_network.h" /* Configuration debug option variables. */ unsigned long conf_debug_ospf_packet[5] = {0, 0, 0, 0, 0}; unsigned long conf_debug_ospf_event = 0; unsigned long conf_debug_ospf_ism = 0; unsigned long conf_debug_ospf_nsm = 0; unsigned long conf_debug_ospf_lsa = 0; unsigned long conf_debug_ospf_zebra = 0; unsigned long conf_debug_ospf_nssa = 0; unsigned long conf_debug_ospf_te = 0; unsigned long conf_debug_ospf_ext = 0; unsigned long conf_debug_ospf_sr = 0; /* Enable debug option variables -- valid only session. */ unsigned long term_debug_ospf_packet[5] = {0, 0, 0, 0, 0}; unsigned long term_debug_ospf_event = 0; unsigned long term_debug_ospf_ism = 0; unsigned long term_debug_ospf_nsm = 0; unsigned long term_debug_ospf_lsa = 0; unsigned long term_debug_ospf_zebra = 0; unsigned long term_debug_ospf_nssa = 0; unsigned long term_debug_ospf_te = 0; unsigned long term_debug_ospf_ext = 0; unsigned long term_debug_ospf_sr = 0; const char *ospf_redist_string(unsigned int route_type) { return (route_type == ZEBRA_ROUTE_MAX) ? "Default" : zebra_route_string(route_type); } #define OSPF_AREA_STRING_MAXLEN 16 const char *ospf_area_name_string(struct ospf_area *area) { static char buf[OSPF_AREA_STRING_MAXLEN] = ""; uint32_t area_id; if (!area) return "-"; area_id = ntohl(area->area_id.s_addr); snprintf(buf, OSPF_AREA_STRING_MAXLEN, "%d.%d.%d.%d", (area_id >> 24) & 0xff, (area_id >> 16) & 0xff, (area_id >> 8) & 0xff, area_id & 0xff); return buf; } #define OSPF_AREA_DESC_STRING_MAXLEN 23 const char *ospf_area_desc_string(struct ospf_area *area) { static char buf[OSPF_AREA_DESC_STRING_MAXLEN] = ""; uint8_t type; if (!area) return "(incomplete)"; type = area->external_routing; switch (type) { case OSPF_AREA_NSSA: snprintf(buf, OSPF_AREA_DESC_STRING_MAXLEN, "%s [NSSA]", ospf_area_name_string(area)); break; case OSPF_AREA_STUB: snprintf(buf, OSPF_AREA_DESC_STRING_MAXLEN, "%s [Stub]", ospf_area_name_string(area)); break; default: return ospf_area_name_string(area); } return buf; } #define OSPF_IF_STRING_MAXLEN 40 const char *ospf_if_name_string(struct ospf_interface *oi) { static char buf[OSPF_IF_STRING_MAXLEN] = ""; uint32_t ifaddr; if (!oi || !oi->address) return "inactive"; if (oi->type == OSPF_IFTYPE_VIRTUALLINK) return oi->ifp->name; ifaddr = ntohl(oi->address->u.prefix4.s_addr); snprintf(buf, OSPF_IF_STRING_MAXLEN, "%s:%d.%d.%d.%d", oi->ifp->name, (ifaddr >> 24) & 0xff, (ifaddr >> 16) & 0xff, (ifaddr >> 8) & 0xff, ifaddr & 0xff); return buf; } void ospf_nbr_state_message(struct ospf_neighbor *nbr, char *buf, size_t size) { int state; struct ospf_interface *oi = nbr->oi; if (IPV4_ADDR_SAME(&DR(oi), &nbr->address.u.prefix4)) state = ISM_DR; else if (IPV4_ADDR_SAME(&BDR(oi), &nbr->address.u.prefix4)) state = ISM_Backup; else state = ISM_DROther; snprintf(buf, size, "%s/%s", lookup_msg(ospf_nsm_state_msg, nbr->state, NULL), lookup_msg(ospf_ism_state_msg, state, NULL)); } const char *ospf_timeval_dump(struct timeval *t, char *buf, size_t size) { /* Making formatted timer strings. */ #define MINUTE_IN_SECONDS 60 #define HOUR_IN_SECONDS (60*MINUTE_IN_SECONDS) #define DAY_IN_SECONDS (24*HOUR_IN_SECONDS) #define WEEK_IN_SECONDS (7*DAY_IN_SECONDS) unsigned long w, d, h, m, s, ms, us; if (!t) return "inactive"; w = d = h = m = s = ms = us = 0; memset(buf, 0, size); us = t->tv_usec; if (us >= 1000) { ms = us / 1000; us %= 1000; (void)us; /* unused */ } if (ms >= 1000) { t->tv_sec += ms / 1000; ms %= 1000; } if (t->tv_sec > WEEK_IN_SECONDS) { w = t->tv_sec / WEEK_IN_SECONDS; t->tv_sec -= w * WEEK_IN_SECONDS; } if (t->tv_sec > DAY_IN_SECONDS) { d = t->tv_sec / DAY_IN_SECONDS; t->tv_sec -= d * DAY_IN_SECONDS; } if (t->tv_sec >= HOUR_IN_SECONDS) { h = t->tv_sec / HOUR_IN_SECONDS; t->tv_sec -= h * HOUR_IN_SECONDS; } if (t->tv_sec >= MINUTE_IN_SECONDS) { m = t->tv_sec / MINUTE_IN_SECONDS; t->tv_sec -= m * MINUTE_IN_SECONDS; } if (w > 99) snprintf(buf, size, "%luw%1lud", w, d); else if (w) snprintf(buf, size, "%luw%1lud%02luh", w, d, h); else if (d) snprintf(buf, size, "%1lud%02luh%02lum", d, h, m); else if (h) snprintf(buf, size, "%luh%02lum%02lds", h, m, (long)t->tv_sec); else if (m) snprintf(buf, size, "%lum%02lds", m, (long)t->tv_sec); else if (ms) snprintf(buf, size, "%ld.%03lus", (long)t->tv_sec, ms); else snprintf(buf, size, "%ld usecs", (long)t->tv_usec); return buf; } const char *ospf_timer_dump(struct thread *t, char *buf, size_t size) { struct timeval result; if (!t) return "inactive"; monotime_until(&t->u.sands, &result); return ospf_timeval_dump(&result, buf, size); } static void ospf_packet_hello_dump(struct stream *s, uint16_t length) { struct ospf_hello *hello; int i; hello = (struct ospf_hello *)stream_pnt(s); zlog_debug("Hello"); zlog_debug(" NetworkMask %s", inet_ntoa(hello->network_mask)); zlog_debug(" HelloInterval %d", ntohs(hello->hello_interval)); zlog_debug(" Options %d (%s)", hello->options, ospf_options_dump(hello->options)); zlog_debug(" RtrPriority %d", hello->priority); zlog_debug(" RtrDeadInterval %ld", (unsigned long)ntohl(hello->dead_interval)); zlog_debug(" DRouter %s", inet_ntoa(hello->d_router)); zlog_debug(" BDRouter %s", inet_ntoa(hello->bd_router)); length -= OSPF_HEADER_SIZE + OSPF_HELLO_MIN_SIZE; zlog_debug(" # Neighbors %d", length / 4); for (i = 0; length > 0; i++, length -= sizeof(struct in_addr)) zlog_debug(" Neighbor %s", inet_ntoa(hello->neighbors[i])); } static char *ospf_dd_flags_dump(uint8_t flags, char *buf, size_t size) { snprintf(buf, size, "%s|%s|%s", (flags & OSPF_DD_FLAG_I) ? "I" : "-", (flags & OSPF_DD_FLAG_M) ? "M" : "-", (flags & OSPF_DD_FLAG_MS) ? "MS" : "-"); return buf; } static char *ospf_router_lsa_flags_dump(uint8_t flags, char *buf, size_t size) { snprintf(buf, size, "%s|%s|%s", (flags & ROUTER_LSA_VIRTUAL) ? "V" : "-", (flags & ROUTER_LSA_EXTERNAL) ? "E" : "-", (flags & ROUTER_LSA_BORDER) ? "B" : "-"); return buf; } static void ospf_router_lsa_dump(struct stream *s, uint16_t length) { char buf[BUFSIZ]; struct router_lsa *rl; int i, len; rl = (struct router_lsa *)stream_pnt(s); zlog_debug(" Router-LSA"); zlog_debug(" flags %s", ospf_router_lsa_flags_dump(rl->flags, buf, BUFSIZ)); zlog_debug(" # links %d", ntohs(rl->links)); len = ntohs(rl->header.length) - OSPF_LSA_HEADER_SIZE - 4; for (i = 0; len > 0; i++) { zlog_debug(" Link ID %s", inet_ntoa(rl->link[i].link_id)); zlog_debug(" Link Data %s", inet_ntoa(rl->link[i].link_data)); zlog_debug(" Type %d", (uint8_t)rl->link[i].type); zlog_debug(" TOS %d", (uint8_t)rl->link[i].tos); zlog_debug(" metric %d", ntohs(rl->link[i].metric)); len -= 12; } } static void ospf_network_lsa_dump(struct stream *s, uint16_t length) { struct network_lsa *nl; int i, cnt; nl = (struct network_lsa *)stream_pnt(s); cnt = (ntohs(nl->header.length) - (OSPF_LSA_HEADER_SIZE + 4)) / 4; zlog_debug(" Network-LSA"); /* zlog_debug ("LSA total size %d", ntohs (nl->header.length)); zlog_debug ("Network-LSA size %d", ntohs (nl->header.length) - OSPF_LSA_HEADER_SIZE); */ zlog_debug(" Network Mask %s", inet_ntoa(nl->mask)); zlog_debug(" # Attached Routers %d", cnt); for (i = 0; i < cnt; i++) zlog_debug(" Attached Router %s", inet_ntoa(nl->routers[i])); } static void ospf_summary_lsa_dump(struct stream *s, uint16_t length) { struct summary_lsa *sl; int size; int i; sl = (struct summary_lsa *)stream_pnt(s); zlog_debug(" Summary-LSA"); zlog_debug(" Network Mask %s", inet_ntoa(sl->mask)); size = ntohs(sl->header.length) - OSPF_LSA_HEADER_SIZE - 4; for (i = 0; size > 0; size -= 4, i++) zlog_debug(" TOS=%d metric %d", sl->tos, GET_METRIC(sl->metric)); } static void ospf_as_external_lsa_dump(struct stream *s, uint16_t length) { struct as_external_lsa *al; int size; int i; al = (struct as_external_lsa *)stream_pnt(s); zlog_debug(" %s", ospf_lsa_type_msg[al->header.type].str); zlog_debug(" Network Mask %s", inet_ntoa(al->mask)); size = ntohs(al->header.length) - OSPF_LSA_HEADER_SIZE - 4; for (i = 0; size > 0; size -= 12, i++) { zlog_debug(" bit %s TOS=%d metric %d", IS_EXTERNAL_METRIC(al->e[i].tos) ? "E" : "-", al->e[i].tos & 0x7f, GET_METRIC(al->e[i].metric)); zlog_debug(" Forwarding address %s", inet_ntoa(al->e[i].fwd_addr)); zlog_debug(" External Route Tag %" ROUTE_TAG_PRI, al->e[i].route_tag); } } static void ospf_lsa_header_list_dump(struct stream *s, uint16_t length) { struct lsa_header *lsa; zlog_debug(" # LSA Headers %d", length / OSPF_LSA_HEADER_SIZE); /* LSA Headers. */ while (length > 0) { lsa = (struct lsa_header *)stream_pnt(s); ospf_lsa_header_dump(lsa); stream_forward_getp(s, OSPF_LSA_HEADER_SIZE); length -= OSPF_LSA_HEADER_SIZE; } } static void ospf_packet_db_desc_dump(struct stream *s, uint16_t length) { struct ospf_db_desc *dd; char dd_flags[8]; uint32_t gp; gp = stream_get_getp(s); dd = (struct ospf_db_desc *)stream_pnt(s); zlog_debug("Database Description"); zlog_debug(" Interface MTU %d", ntohs(dd->mtu)); zlog_debug(" Options %d (%s)", dd->options, ospf_options_dump(dd->options)); zlog_debug(" Flags %d (%s)", dd->flags, ospf_dd_flags_dump(dd->flags, dd_flags, sizeof dd_flags)); zlog_debug(" Sequence Number 0x%08lx", (unsigned long)ntohl(dd->dd_seqnum)); length -= OSPF_HEADER_SIZE + OSPF_DB_DESC_MIN_SIZE; stream_forward_getp(s, OSPF_DB_DESC_MIN_SIZE); ospf_lsa_header_list_dump(s, length); stream_set_getp(s, gp); } static void ospf_packet_ls_req_dump(struct stream *s, uint16_t length) { uint32_t sp; uint32_t ls_type; struct in_addr ls_id; struct in_addr adv_router; sp = stream_get_getp(s); length -= OSPF_HEADER_SIZE; zlog_debug("Link State Request"); zlog_debug(" # Requests %d", length / 12); for (; length > 0; length -= 12) { ls_type = stream_getl(s); ls_id.s_addr = stream_get_ipv4(s); adv_router.s_addr = stream_get_ipv4(s); zlog_debug(" LS type %d", ls_type); zlog_debug(" Link State ID %s", inet_ntoa(ls_id)); zlog_debug(" Advertising Router %s", inet_ntoa(adv_router)); } stream_set_getp(s, sp); } static void ospf_packet_ls_upd_dump(struct stream *s, uint16_t length) { uint32_t sp; struct lsa_header *lsa; int lsa_len; uint32_t count; length -= OSPF_HEADER_SIZE; sp = stream_get_getp(s); count = stream_getl(s); length -= 4; zlog_debug("Link State Update"); zlog_debug(" # LSAs %d", count); while (length > 0 && count > 0) { if (length < OSPF_HEADER_SIZE || length % 4 != 0) { zlog_debug(" Remaining %d bytes; Incorrect length.", length); break; } lsa = (struct lsa_header *)stream_pnt(s); lsa_len = ntohs(lsa->length); ospf_lsa_header_dump(lsa); switch (lsa->type) { case OSPF_ROUTER_LSA: ospf_router_lsa_dump(s, length); break; case OSPF_NETWORK_LSA: ospf_network_lsa_dump(s, length); break; case OSPF_SUMMARY_LSA: case OSPF_ASBR_SUMMARY_LSA: ospf_summary_lsa_dump(s, length); break; case OSPF_AS_EXTERNAL_LSA: ospf_as_external_lsa_dump(s, length); break; case OSPF_AS_NSSA_LSA: ospf_as_external_lsa_dump(s, length); break; case OSPF_OPAQUE_LINK_LSA: case OSPF_OPAQUE_AREA_LSA: case OSPF_OPAQUE_AS_LSA: ospf_opaque_lsa_dump(s, length); break; default: break; } stream_forward_getp(s, lsa_len); length -= lsa_len; count--; } stream_set_getp(s, sp); } static void ospf_packet_ls_ack_dump(struct stream *s, uint16_t length) { uint32_t sp; length -= OSPF_HEADER_SIZE; sp = stream_get_getp(s); zlog_debug("Link State Acknowledgment"); ospf_lsa_header_list_dump(s, length); stream_set_getp(s, sp); } /* Expects header to be in host order */ void ospf_ip_header_dump(struct ip *iph) { /* IP Header dump. */ zlog_debug("ip_v %d", iph->ip_v); zlog_debug("ip_hl %d", iph->ip_hl); zlog_debug("ip_tos %d", iph->ip_tos); zlog_debug("ip_len %d", iph->ip_len); zlog_debug("ip_id %u", (uint32_t)iph->ip_id); zlog_debug("ip_off %u", (uint32_t)iph->ip_off); zlog_debug("ip_ttl %d", iph->ip_ttl); zlog_debug("ip_p %d", iph->ip_p); zlog_debug("ip_sum 0x%x", (uint32_t)iph->ip_sum); zlog_debug("ip_src %s", inet_ntoa(iph->ip_src)); zlog_debug("ip_dst %s", inet_ntoa(iph->ip_dst)); } static void ospf_header_dump(struct ospf_header *ospfh) { char buf[9]; uint16_t auth_type = ntohs(ospfh->auth_type); zlog_debug("Header"); zlog_debug(" Version %d", ospfh->version); zlog_debug(" Type %d (%s)", ospfh->type, lookup_msg(ospf_packet_type_str, ospfh->type, NULL)); zlog_debug(" Packet Len %d", ntohs(ospfh->length)); zlog_debug(" Router ID %s", inet_ntoa(ospfh->router_id)); zlog_debug(" Area ID %s", inet_ntoa(ospfh->area_id)); zlog_debug(" Checksum 0x%x", ntohs(ospfh->checksum)); zlog_debug(" AuType %s", lookup_msg(ospf_auth_type_str, auth_type, NULL)); switch (auth_type) { case OSPF_AUTH_NULL: break; case OSPF_AUTH_SIMPLE: strlcpy(buf, (char *)ospfh->u.auth_data, sizeof(buf)); zlog_debug(" Simple Password %s", buf); break; case OSPF_AUTH_CRYPTOGRAPHIC: zlog_debug(" Cryptographic Authentication"); zlog_debug(" Key ID %d", ospfh->u.crypt.key_id); zlog_debug(" Auth Data Len %d", ospfh->u.crypt.auth_data_len); zlog_debug(" Sequence number %ld", (unsigned long)ntohl(ospfh->u.crypt.crypt_seqnum)); break; default: zlog_debug("* This is not supported authentication type"); break; } } void ospf_packet_dump(struct stream *s) { struct ospf_header *ospfh; unsigned long gp; /* Preserve pointer. */ gp = stream_get_getp(s); /* OSPF Header dump. */ ospfh = (struct ospf_header *)stream_pnt(s); /* Until detail flag is set, return. */ if (!(term_debug_ospf_packet[ospfh->type - 1] & OSPF_DEBUG_DETAIL)) return; /* Show OSPF header detail. */ ospf_header_dump(ospfh); stream_forward_getp(s, OSPF_HEADER_SIZE); switch (ospfh->type) { case OSPF_MSG_HELLO: ospf_packet_hello_dump(s, ntohs(ospfh->length)); break; case OSPF_MSG_DB_DESC: ospf_packet_db_desc_dump(s, ntohs(ospfh->length)); break; case OSPF_MSG_LS_REQ: ospf_packet_ls_req_dump(s, ntohs(ospfh->length)); break; case OSPF_MSG_LS_UPD: ospf_packet_ls_upd_dump(s, ntohs(ospfh->length)); break; case OSPF_MSG_LS_ACK: ospf_packet_ls_ack_dump(s, ntohs(ospfh->length)); break; default: break; } stream_set_getp(s, gp); } DEFUN (debug_ospf_packet, debug_ospf_packet_cmd, "debug ospf [(1-65535)] packet []", DEBUG_STR OSPF_STR "Instance ID\n" "OSPF packets\n" "OSPF Hello\n" "OSPF Database Description\n" "OSPF Link State Request\n" "OSPF Link State Update\n" "OSPF Link State Acknowledgment\n" "OSPF all packets\n" "Packet sent\n" "Detail Information\n" "Packet received\n" "Detail Information\n" "Detail Information\n") { int inst = (argv[2]->type == RANGE_TKN) ? 1 : 0; int detail = strmatch(argv[argc - 1]->text, "detail"); int send = strmatch(argv[argc - (1 + detail)]->text, "send"); int recv = strmatch(argv[argc - (1 + detail)]->text, "recv"); char *packet = argv[3 + inst]->text; if (inst) // user passed instance ID { if (!ospf_lookup_instance(strtoul(argv[2]->arg, NULL, 10))) return CMD_NOT_MY_INSTANCE; } int type = 0; int flag = 0; int i; /* Check packet type. */ if (strmatch(packet, "hello")) type = OSPF_DEBUG_HELLO; else if (strmatch(packet, "dd")) type = OSPF_DEBUG_DB_DESC; else if (strmatch(packet, "ls-request")) type = OSPF_DEBUG_LS_REQ; else if (strmatch(packet, "ls-update")) type = OSPF_DEBUG_LS_UPD; else if (strmatch(packet, "ls-ack")) type = OSPF_DEBUG_LS_ACK; else if (strmatch(packet, "all")) type = OSPF_DEBUG_ALL; /* Cases: * (none) = send + recv * detail = send + recv + detail * recv = recv * send = send * recv detail = recv + detail * send detail = send + detail */ if (!send && !recv) send = recv = 1; flag |= (send) ? OSPF_DEBUG_SEND : 0; flag |= (recv) ? OSPF_DEBUG_RECV : 0; flag |= (detail) ? OSPF_DEBUG_DETAIL : 0; for (i = 0; i < 5; i++) if (type & (0x01 << i)) { if (vty->node == CONFIG_NODE) DEBUG_PACKET_ON(i, flag); else TERM_DEBUG_PACKET_ON(i, flag); } return CMD_SUCCESS; } DEFUN (no_debug_ospf_packet, no_debug_ospf_packet_cmd, "no debug ospf [(1-65535)] packet []", NO_STR DEBUG_STR OSPF_STR "Instance ID\n" "OSPF packets\n" "OSPF Hello\n" "OSPF Database Description\n" "OSPF Link State Request\n" "OSPF Link State Update\n" "OSPF Link State Acknowledgment\n" "OSPF all packets\n" "Packet sent\n" "Detail Information\n" "Packet received\n" "Detail Information\n" "Detail Information\n") { int inst = (argv[3]->type == RANGE_TKN) ? 1 : 0; int detail = strmatch(argv[argc - 1]->text, "detail"); int send = strmatch(argv[argc - (1 + detail)]->text, "send"); int recv = strmatch(argv[argc - (1 + detail)]->text, "recv"); char *packet = argv[4 + inst]->text; if (inst) // user passed instance ID { if (!ospf_lookup_instance(strtoul(argv[3]->arg, NULL, 10))) return CMD_NOT_MY_INSTANCE; } int type = 0; int flag = 0; int i; /* Check packet type. */ if (strmatch(packet, "hello")) type = OSPF_DEBUG_HELLO; else if (strmatch(packet, "dd")) type = OSPF_DEBUG_DB_DESC; else if (strmatch(packet, "ls-request")) type = OSPF_DEBUG_LS_REQ; else if (strmatch(packet, "ls-update")) type = OSPF_DEBUG_LS_UPD; else if (strmatch(packet, "ls-ack")) type = OSPF_DEBUG_LS_ACK; else if (strmatch(packet, "all")) type = OSPF_DEBUG_ALL; /* Cases: * (none) = send + recv * detail = send + recv + detail * recv = recv * send = send * recv detail = recv + detail * send detail = send + detail */ if (!send && !recv) send = recv = 1; flag |= (send) ? OSPF_DEBUG_SEND : 0; flag |= (recv) ? OSPF_DEBUG_RECV : 0; flag |= (detail) ? OSPF_DEBUG_DETAIL : 0; for (i = 0; i < 5; i++) if (type & (0x01 << i)) { if (vty->node == CONFIG_NODE) DEBUG_PACKET_OFF(i, flag); else TERM_DEBUG_PACKET_OFF(i, flag); } #ifdef DEBUG /* for (i = 0; i < 5; i++) zlog_debug ("flag[%d] = %d", i, ospf_debug_packet[i]); */ #endif /* DEBUG */ return CMD_SUCCESS; } DEFUN (debug_ospf_ism, debug_ospf_ism_cmd, "debug ospf [(1-65535)] ism []", DEBUG_STR OSPF_STR "Instance ID\n" "OSPF Interface State Machine\n" "ISM Status Information\n" "ISM Event Information\n" "ISM TImer Information\n") { int inst = (argv[2]->type == RANGE_TKN); char *dbgparam = (argc == 4 + inst) ? argv[argc - 1]->text : NULL; if (inst) // user passed instance ID { if (!ospf_lookup_instance(strtoul(argv[2]->arg, NULL, 10))) return CMD_NOT_MY_INSTANCE; } if (vty->node == CONFIG_NODE) { if (!dbgparam) DEBUG_ON(ism, ISM); else { if (strmatch(dbgparam, "status")) DEBUG_ON(ism, ISM_STATUS); else if (strmatch(dbgparam, "events")) DEBUG_ON(ism, ISM_EVENTS); else if (strmatch(dbgparam, "timers")) DEBUG_ON(ism, ISM_TIMERS); } return CMD_SUCCESS; } /* ENABLE_NODE. */ if (!dbgparam) TERM_DEBUG_ON(ism, ISM); else { if (strmatch(dbgparam, "status")) TERM_DEBUG_ON(ism, ISM_STATUS); else if (strmatch(dbgparam, "events")) TERM_DEBUG_ON(ism, ISM_EVENTS); else if (strmatch(dbgparam, "timers")) TERM_DEBUG_ON(ism, ISM_TIMERS); } return CMD_SUCCESS; } DEFUN (no_debug_ospf_ism, no_debug_ospf_ism_cmd, "no debug ospf [(1-65535)] ism []", NO_STR DEBUG_STR OSPF_STR "Instance ID\n" "OSPF Interface State Machine\n" "ISM Status Information\n" "ISM Event Information\n" "ISM TImer Information\n") { int inst = (argv[3]->type == RANGE_TKN); char *dbgparam = (argc == 5 + inst) ? argv[argc - 1]->text : NULL; if (inst) // user passed instance ID { if (!ospf_lookup_instance(strtoul(argv[3]->arg, NULL, 10))) return CMD_NOT_MY_INSTANCE; } if (vty->node == CONFIG_NODE) { if (!dbgparam) DEBUG_OFF(ism, ISM); else { if (strmatch(dbgparam, "status")) DEBUG_OFF(ism, ISM_STATUS); else if (strmatch(dbgparam, "events")) DEBUG_OFF(ism, ISM_EVENTS); else if (strmatch(dbgparam, "timers")) DEBUG_OFF(ism, ISM_TIMERS); } return CMD_SUCCESS; } /* ENABLE_NODE. */ if (!dbgparam) TERM_DEBUG_OFF(ism, ISM); else { if (strmatch(dbgparam, "status")) TERM_DEBUG_OFF(ism, ISM_STATUS); else if (strmatch(dbgparam, "events")) TERM_DEBUG_OFF(ism, ISM_EVENTS); else if (strmatch(dbgparam, "timers")) TERM_DEBUG_OFF(ism, ISM_TIMERS); } return CMD_SUCCESS; } static int debug_ospf_nsm_common(struct vty *vty, int arg_base, int argc, struct cmd_token **argv) { if (vty->node == CONFIG_NODE) { if (argc == arg_base + 0) DEBUG_ON(nsm, NSM); else if (argc == arg_base + 1) { if (strmatch(argv[arg_base]->text, "status")) DEBUG_ON(nsm, NSM_STATUS); else if (strmatch(argv[arg_base]->text, "events")) DEBUG_ON(nsm, NSM_EVENTS); else if (strmatch(argv[arg_base]->text, "timers")) DEBUG_ON(nsm, NSM_TIMERS); } return CMD_SUCCESS; } /* ENABLE_NODE. */ if (argc == arg_base + 0) TERM_DEBUG_ON(nsm, NSM); else if (argc == arg_base + 1) { if (strmatch(argv[arg_base]->text, "status")) TERM_DEBUG_ON(nsm, NSM_STATUS); else if (strmatch(argv[arg_base]->text, "events")) TERM_DEBUG_ON(nsm, NSM_EVENTS); else if (strmatch(argv[arg_base]->text, "timers")) TERM_DEBUG_ON(nsm, NSM_TIMERS); } return CMD_SUCCESS; } DEFUN (debug_ospf_nsm, debug_ospf_nsm_cmd, "debug ospf nsm []", DEBUG_STR OSPF_STR "OSPF Neighbor State Machine\n" "NSM Status Information\n" "NSM Event Information\n" "NSM Timer Information\n") { return debug_ospf_nsm_common(vty, 3, argc, argv); } DEFUN (debug_ospf_instance_nsm, debug_ospf_instance_nsm_cmd, "debug ospf (1-65535) nsm []", DEBUG_STR OSPF_STR "Instance ID\n" "OSPF Neighbor State Machine\n" "NSM Status Information\n" "NSM Event Information\n" "NSM Timer Information\n") { int idx_number = 2; unsigned short instance = 0; instance = strtoul(argv[idx_number]->arg, NULL, 10); if (!ospf_lookup_instance(instance)) return CMD_SUCCESS; return debug_ospf_nsm_common(vty, 4, argc, argv); } static int no_debug_ospf_nsm_common(struct vty *vty, int arg_base, int argc, struct cmd_token **argv) { /* XXX qlyoung */ if (vty->node == CONFIG_NODE) { if (argc == arg_base + 0) DEBUG_OFF(nsm, NSM); else if (argc == arg_base + 1) { if (strmatch(argv[arg_base]->text, "status")) DEBUG_OFF(nsm, NSM_STATUS); else if (strmatch(argv[arg_base]->text, "events")) DEBUG_OFF(nsm, NSM_EVENTS); else if (strmatch(argv[arg_base]->text, "timers")) DEBUG_OFF(nsm, NSM_TIMERS); } return CMD_SUCCESS; } /* ENABLE_NODE. */ if (argc == arg_base + 0) TERM_DEBUG_OFF(nsm, NSM); else if (argc == arg_base + 1) { if (strmatch(argv[arg_base]->text, "status")) TERM_DEBUG_OFF(nsm, NSM_STATUS); else if (strmatch(argv[arg_base]->text, "events")) TERM_DEBUG_OFF(nsm, NSM_EVENTS); else if (strmatch(argv[arg_base]->text, "timers")) TERM_DEBUG_OFF(nsm, NSM_TIMERS); } return CMD_SUCCESS; } DEFUN (no_debug_ospf_nsm, no_debug_ospf_nsm_cmd, "no debug ospf nsm []", NO_STR DEBUG_STR OSPF_STR "OSPF Neighbor State Machine\n" "NSM Status Information\n" "NSM Event Information\n" "NSM Timer Information\n") { return no_debug_ospf_nsm_common(vty, 4, argc, argv); } DEFUN (no_debug_ospf_instance_nsm, no_debug_ospf_instance_nsm_cmd, "no debug ospf (1-65535) nsm []", NO_STR DEBUG_STR OSPF_STR "Instance ID\n" "OSPF Neighbor State Machine\n" "NSM Status Information\n" "NSM Event Information\n" "NSM Timer Information\n") { int idx_number = 3; unsigned short instance = 0; instance = strtoul(argv[idx_number]->arg, NULL, 10); if (!ospf_lookup_instance(instance)) return CMD_NOT_MY_INSTANCE; return no_debug_ospf_nsm_common(vty, 5, argc, argv); } static int debug_ospf_lsa_common(struct vty *vty, int arg_base, int argc, struct cmd_token **argv) { if (vty->node == CONFIG_NODE) { if (argc == arg_base + 0) DEBUG_ON(lsa, LSA); else if (argc == arg_base + 1) { if (strmatch(argv[arg_base]->text, "generate")) DEBUG_ON(lsa, LSA_GENERATE); else if (strmatch(argv[arg_base]->text, "flooding")) DEBUG_ON(lsa, LSA_FLOODING); else if (strmatch(argv[arg_base]->text, "install")) DEBUG_ON(lsa, LSA_INSTALL); else if (strmatch(argv[arg_base]->text, "refresh")) DEBUG_ON(lsa, LSA_REFRESH); } return CMD_SUCCESS; } /* ENABLE_NODE. */ if (argc == arg_base + 0) TERM_DEBUG_ON(lsa, LSA); else if (argc == arg_base + 1) { if (strmatch(argv[arg_base]->text, "generate")) TERM_DEBUG_ON(lsa, LSA_GENERATE); else if (strmatch(argv[arg_base]->text, "flooding")) TERM_DEBUG_ON(lsa, LSA_FLOODING); else if (strmatch(argv[arg_base]->text, "install")) TERM_DEBUG_ON(lsa, LSA_INSTALL); else if (strmatch(argv[arg_base]->text, "refresh")) TERM_DEBUG_ON(lsa, LSA_REFRESH); } return CMD_SUCCESS; } DEFUN (debug_ospf_lsa, debug_ospf_lsa_cmd, "debug ospf lsa []", DEBUG_STR OSPF_STR "OSPF Link State Advertisement\n" "LSA Generation\n" "LSA Flooding\n" "LSA Install/Delete\n" "LSA Refresh\n") { return debug_ospf_lsa_common(vty, 3, argc, argv); } DEFUN (debug_ospf_instance_lsa, debug_ospf_instance_lsa_cmd, "debug ospf (1-65535) lsa []", DEBUG_STR OSPF_STR "Instance ID\n" "OSPF Link State Advertisement\n" "LSA Generation\n" "LSA Flooding\n" "LSA Install/Delete\n" "LSA Refresh\n") { int idx_number = 2; unsigned short instance = 0; instance = strtoul(argv[idx_number]->arg, NULL, 10); if (!ospf_lookup_instance(instance)) return CMD_NOT_MY_INSTANCE; return debug_ospf_lsa_common(vty, 4, argc, argv); } static int no_debug_ospf_lsa_common(struct vty *vty, int arg_base, int argc, struct cmd_token **argv) { if (vty->node == CONFIG_NODE) { if (argc == arg_base + 0) DEBUG_OFF(lsa, LSA); else if (argc == arg_base + 1) { if (strmatch(argv[arg_base]->text, "generate")) DEBUG_OFF(lsa, LSA_GENERATE); else if (strmatch(argv[arg_base]->text, "flooding")) DEBUG_OFF(lsa, LSA_FLOODING); else if (strmatch(argv[arg_base]->text, "install")) DEBUG_OFF(lsa, LSA_INSTALL); else if (strmatch(argv[arg_base]->text, "refresh")) DEBUG_OFF(lsa, LSA_REFRESH); } return CMD_SUCCESS; } /* ENABLE_NODE. */ if (argc == arg_base + 0) TERM_DEBUG_OFF(lsa, LSA); else if (argc == arg_base + 1) { if (strmatch(argv[arg_base]->text, "generate")) TERM_DEBUG_OFF(lsa, LSA_GENERATE); else if (strmatch(argv[arg_base]->text, "flooding")) TERM_DEBUG_OFF(lsa, LSA_FLOODING); else if (strmatch(argv[arg_base]->text, "install")) TERM_DEBUG_OFF(lsa, LSA_INSTALL); else if (strmatch(argv[arg_base]->text, "refresh")) TERM_DEBUG_OFF(lsa, LSA_REFRESH); } return CMD_SUCCESS; } DEFUN (no_debug_ospf_lsa, no_debug_ospf_lsa_cmd, "no debug ospf lsa []", NO_STR DEBUG_STR OSPF_STR "OSPF Link State Advertisement\n" "LSA Generation\n" "LSA Flooding\n" "LSA Install/Delete\n" "LSA Refres\n") { return no_debug_ospf_lsa_common(vty, 4, argc, argv); } DEFUN (no_debug_ospf_instance_lsa, no_debug_ospf_instance_lsa_cmd, "no debug ospf (1-65535) lsa []", NO_STR DEBUG_STR OSPF_STR "Instance ID\n" "OSPF Link State Advertisement\n" "LSA Generation\n" "LSA Flooding\n" "LSA Install/Delete\n" "LSA Refres\n") { int idx_number = 3; unsigned short instance = 0; instance = strtoul(argv[idx_number]->arg, NULL, 10); if (!ospf_lookup_instance(instance)) return CMD_NOT_MY_INSTANCE; return no_debug_ospf_lsa_common(vty, 5, argc, argv); } static int debug_ospf_zebra_common(struct vty *vty, int arg_base, int argc, struct cmd_token **argv) { if (vty->node == CONFIG_NODE) { if (argc == arg_base + 0) DEBUG_ON(zebra, ZEBRA); else if (argc == arg_base + 1) { if (strmatch(argv[arg_base]->text, "interface")) DEBUG_ON(zebra, ZEBRA_INTERFACE); else if (strmatch(argv[arg_base]->text, "redistribute")) DEBUG_ON(zebra, ZEBRA_REDISTRIBUTE); } return CMD_SUCCESS; } /* ENABLE_NODE. */ if (argc == arg_base + 0) TERM_DEBUG_ON(zebra, ZEBRA); else if (argc == arg_base + 1) { if (strmatch(argv[arg_base]->text, "interface")) TERM_DEBUG_ON(zebra, ZEBRA_INTERFACE); else if (strmatch(argv[arg_base]->text, "redistribute")) TERM_DEBUG_ON(zebra, ZEBRA_REDISTRIBUTE); } return CMD_SUCCESS; } DEFUN (debug_ospf_zebra, debug_ospf_zebra_cmd, "debug ospf zebra []", DEBUG_STR OSPF_STR ZEBRA_STR "Zebra interface\n" "Zebra redistribute\n") { return debug_ospf_zebra_common(vty, 3, argc, argv); } DEFUN (debug_ospf_instance_zebra, debug_ospf_instance_zebra_cmd, "debug ospf (1-65535) zebra []", DEBUG_STR OSPF_STR "Instance ID\n" ZEBRA_STR "Zebra interface\n" "Zebra redistribute\n") { int idx_number = 2; unsigned short instance = 0; instance = strtoul(argv[idx_number]->arg, NULL, 10); if (!ospf_lookup_instance(instance)) return CMD_NOT_MY_INSTANCE; return debug_ospf_zebra_common(vty, 4, argc, argv); } static int no_debug_ospf_zebra_common(struct vty *vty, int arg_base, int argc, struct cmd_token **argv) { if (vty->node == CONFIG_NODE) { if (argc == arg_base + 0) DEBUG_OFF(zebra, ZEBRA); else if (argc == arg_base + 1) { if (strmatch(argv[arg_base]->text, "interface")) DEBUG_OFF(zebra, ZEBRA_INTERFACE); else if (strmatch(argv[arg_base]->text, "redistribute")) DEBUG_OFF(zebra, ZEBRA_REDISTRIBUTE); } return CMD_SUCCESS; } /* ENABLE_NODE. */ if (argc == arg_base + 0) TERM_DEBUG_OFF(zebra, ZEBRA); else if (argc == arg_base + 1) { if (strmatch(argv[arg_base]->text, "interface")) TERM_DEBUG_OFF(zebra, ZEBRA_INTERFACE); else if (strmatch(argv[arg_base]->text, "redistribute")) TERM_DEBUG_OFF(zebra, ZEBRA_REDISTRIBUTE); } return CMD_SUCCESS; } DEFUN (no_debug_ospf_zebra, no_debug_ospf_zebra_cmd, "no debug ospf zebra []", NO_STR DEBUG_STR OSPF_STR ZEBRA_STR "Zebra interface\n" "Zebra redistribute\n") { return no_debug_ospf_zebra_common(vty, 4, argc, argv); } DEFUN (no_debug_ospf_instance_zebra, no_debug_ospf_instance_zebra_cmd, "no debug ospf (1-65535) zebra []", NO_STR DEBUG_STR OSPF_STR "Instance ID\n" ZEBRA_STR "Zebra interface\n" "Zebra redistribute\n") { int idx_number = 3; unsigned short instance = 0; instance = strtoul(argv[idx_number]->arg, NULL, 10); if (!ospf_lookup_instance(instance)) return CMD_SUCCESS; return no_debug_ospf_zebra_common(vty, 5, argc, argv); } DEFUN (debug_ospf_event, debug_ospf_event_cmd, "debug ospf event", DEBUG_STR OSPF_STR "OSPF event information\n") { if (vty->node == CONFIG_NODE) CONF_DEBUG_ON(event, EVENT); TERM_DEBUG_ON(event, EVENT); return CMD_SUCCESS; } DEFUN (no_debug_ospf_event, no_debug_ospf_event_cmd, "no debug ospf event", NO_STR DEBUG_STR OSPF_STR "OSPF event information\n") { if (vty->node == CONFIG_NODE) CONF_DEBUG_OFF(event, EVENT); TERM_DEBUG_OFF(event, EVENT); return CMD_SUCCESS; } DEFUN (debug_ospf_instance_event, debug_ospf_instance_event_cmd, "debug ospf (1-65535) event", DEBUG_STR OSPF_STR "Instance ID\n" "OSPF event information\n") { int idx_number = 2; unsigned short instance = 0; instance = strtoul(argv[idx_number]->arg, NULL, 10); if (!ospf_lookup_instance(instance)) return CMD_SUCCESS; if (vty->node == CONFIG_NODE) CONF_DEBUG_ON(event, EVENT); TERM_DEBUG_ON(event, EVENT); return CMD_SUCCESS; } DEFUN (no_debug_ospf_instance_event, no_debug_ospf_instance_event_cmd, "no debug ospf (1-65535) event", NO_STR DEBUG_STR OSPF_STR "Instance ID\n" "OSPF event information\n") { int idx_number = 3; unsigned short instance = 0; instance = strtoul(argv[idx_number]->arg, NULL, 10); if (!ospf_lookup_instance(instance)) return CMD_SUCCESS; if (vty->node == CONFIG_NODE) CONF_DEBUG_OFF(event, EVENT); TERM_DEBUG_OFF(event, EVENT); return CMD_SUCCESS; } DEFUN (debug_ospf_nssa, debug_ospf_nssa_cmd, "debug ospf nssa", DEBUG_STR OSPF_STR "OSPF nssa information\n") { if (vty->node == CONFIG_NODE) CONF_DEBUG_ON(nssa, NSSA); TERM_DEBUG_ON(nssa, NSSA); return CMD_SUCCESS; } DEFUN (no_debug_ospf_nssa, no_debug_ospf_nssa_cmd, "no debug ospf nssa", NO_STR DEBUG_STR OSPF_STR "OSPF nssa information\n") { if (vty->node == CONFIG_NODE) CONF_DEBUG_OFF(nssa, NSSA); TERM_DEBUG_OFF(nssa, NSSA); return CMD_SUCCESS; } DEFUN (debug_ospf_instance_nssa, debug_ospf_instance_nssa_cmd, "debug ospf (1-65535) nssa", DEBUG_STR OSPF_STR "Instance ID\n" "OSPF nssa information\n") { int idx_number = 2; unsigned short instance = 0; instance = strtoul(argv[idx_number]->arg, NULL, 10); if (!ospf_lookup_instance(instance)) return CMD_SUCCESS; if (vty->node == CONFIG_NODE) CONF_DEBUG_ON(nssa, NSSA); TERM_DEBUG_ON(nssa, NSSA); return CMD_SUCCESS; } DEFUN (no_debug_ospf_instance_nssa, no_debug_ospf_instance_nssa_cmd, "no debug ospf (1-65535) nssa", NO_STR DEBUG_STR OSPF_STR "Instance ID\n" "OSPF nssa information\n") { int idx_number = 3; unsigned short instance = 0; instance = strtoul(argv[idx_number]->arg, NULL, 10); if (!ospf_lookup_instance(instance)) return CMD_SUCCESS; if (vty->node == CONFIG_NODE) CONF_DEBUG_OFF(nssa, NSSA); TERM_DEBUG_OFF(nssa, NSSA); return CMD_SUCCESS; } DEFUN (debug_ospf_te, debug_ospf_te_cmd, "debug ospf te", DEBUG_STR OSPF_STR "OSPF-TE information\n") { if (vty->node == CONFIG_NODE) CONF_DEBUG_ON(te, TE); TERM_DEBUG_ON(te, TE); return CMD_SUCCESS; } DEFUN (no_debug_ospf_te, no_debug_ospf_te_cmd, "no debug ospf te", NO_STR DEBUG_STR OSPF_STR "OSPF-TE information\n") { if (vty->node == CONFIG_NODE) CONF_DEBUG_OFF(te, TE); TERM_DEBUG_OFF(te, TE); return CMD_SUCCESS; } DEFUN (debug_ospf_sr, debug_ospf_sr_cmd, "debug ospf sr", DEBUG_STR OSPF_STR "OSPF-SR information\n") { if (vty->node == CONFIG_NODE) CONF_DEBUG_ON(sr, SR); TERM_DEBUG_ON(sr, SR); return CMD_SUCCESS; } DEFUN (no_debug_ospf_sr, no_debug_ospf_sr_cmd, "no debug ospf sr", NO_STR DEBUG_STR OSPF_STR "OSPF-SR information\n") { if (vty->node == CONFIG_NODE) CONF_DEBUG_OFF(sr, SR); TERM_DEBUG_OFF(sr, SR); return CMD_SUCCESS; } DEFUN (no_debug_ospf, no_debug_ospf_cmd, "no debug ospf", NO_STR DEBUG_STR OSPF_STR) { int flag = OSPF_DEBUG_SEND | OSPF_DEBUG_RECV | OSPF_DEBUG_DETAIL; int i; if (vty->node == CONFIG_NODE) { CONF_DEBUG_OFF(event, EVENT); CONF_DEBUG_OFF(nssa, NSSA); DEBUG_OFF(ism, ISM_EVENTS); DEBUG_OFF(ism, ISM_STATUS); DEBUG_OFF(ism, ISM_TIMERS); DEBUG_OFF(lsa, LSA); DEBUG_OFF(lsa, LSA_FLOODING); DEBUG_OFF(lsa, LSA_GENERATE); DEBUG_OFF(lsa, LSA_INSTALL); DEBUG_OFF(lsa, LSA_REFRESH); DEBUG_OFF(nsm, NSM); DEBUG_OFF(nsm, NSM_EVENTS); DEBUG_OFF(nsm, NSM_STATUS); DEBUG_OFF(nsm, NSM_TIMERS); DEBUG_OFF(zebra, ZEBRA); DEBUG_OFF(zebra, ZEBRA_INTERFACE); DEBUG_OFF(zebra, ZEBRA_REDISTRIBUTE); for (i = 0; i < 5; i++) DEBUG_PACKET_OFF(i, flag); } for (i = 0; i < 5; i++) TERM_DEBUG_PACKET_OFF(i, flag); TERM_DEBUG_OFF(event, EVENT); TERM_DEBUG_OFF(ism, ISM); TERM_DEBUG_OFF(ism, ISM_EVENTS); TERM_DEBUG_OFF(ism, ISM_STATUS); TERM_DEBUG_OFF(ism, ISM_TIMERS); TERM_DEBUG_OFF(lsa, LSA); TERM_DEBUG_OFF(lsa, LSA_FLOODING); TERM_DEBUG_OFF(lsa, LSA_GENERATE); TERM_DEBUG_OFF(lsa, LSA_INSTALL); TERM_DEBUG_OFF(lsa, LSA_REFRESH); TERM_DEBUG_OFF(nsm, NSM); TERM_DEBUG_OFF(nsm, NSM_EVENTS); TERM_DEBUG_OFF(nsm, NSM_STATUS); TERM_DEBUG_OFF(nsm, NSM_TIMERS); TERM_DEBUG_OFF(nssa, NSSA); TERM_DEBUG_OFF(zebra, ZEBRA); TERM_DEBUG_OFF(zebra, ZEBRA_INTERFACE); TERM_DEBUG_OFF(zebra, ZEBRA_REDISTRIBUTE); return CMD_SUCCESS; } static int show_debugging_ospf_common(struct vty *vty, struct ospf *ospf) { int i; if (ospf->instance) vty_out(vty, "\nOSPF Instance: %d\n\n", ospf->instance); vty_out(vty, "OSPF debugging status:\n"); /* Show debug status for events. */ if (IS_DEBUG_OSPF(event, EVENT)) vty_out(vty, " OSPF event debugging is on\n"); /* Show debug status for ISM. */ if (IS_DEBUG_OSPF(ism, ISM) == OSPF_DEBUG_ISM) vty_out(vty, " OSPF ISM debugging is on\n"); else { if (IS_DEBUG_OSPF(ism, ISM_STATUS)) vty_out(vty, " OSPF ISM status debugging is on\n"); if (IS_DEBUG_OSPF(ism, ISM_EVENTS)) vty_out(vty, " OSPF ISM event debugging is on\n"); if (IS_DEBUG_OSPF(ism, ISM_TIMERS)) vty_out(vty, " OSPF ISM timer debugging is on\n"); } /* Show debug status for NSM. */ if (IS_DEBUG_OSPF(nsm, NSM) == OSPF_DEBUG_NSM) vty_out(vty, " OSPF NSM debugging is on\n"); else { if (IS_DEBUG_OSPF(nsm, NSM_STATUS)) vty_out(vty, " OSPF NSM status debugging is on\n"); if (IS_DEBUG_OSPF(nsm, NSM_EVENTS)) vty_out(vty, " OSPF NSM event debugging is on\n"); if (IS_DEBUG_OSPF(nsm, NSM_TIMERS)) vty_out(vty, " OSPF NSM timer debugging is on\n"); } /* Show debug status for OSPF Packets. */ for (i = 0; i < 5; i++) if (IS_DEBUG_OSPF_PACKET(i, SEND) && IS_DEBUG_OSPF_PACKET(i, RECV)) { vty_out(vty, " OSPF packet %s%s debugging is on\n", lookup_msg(ospf_packet_type_str, i + 1, NULL), IS_DEBUG_OSPF_PACKET(i, DETAIL) ? " detail" : ""); } else { if (IS_DEBUG_OSPF_PACKET(i, SEND)) vty_out(vty, " OSPF packet %s send%s debugging is on\n", lookup_msg(ospf_packet_type_str, i + 1, NULL), IS_DEBUG_OSPF_PACKET(i, DETAIL) ? " detail" : ""); if (IS_DEBUG_OSPF_PACKET(i, RECV)) vty_out(vty, " OSPF packet %s receive%s debugging is on\n", lookup_msg(ospf_packet_type_str, i + 1, NULL), IS_DEBUG_OSPF_PACKET(i, DETAIL) ? " detail" : ""); } /* Show debug status for OSPF LSAs. */ if (IS_DEBUG_OSPF(lsa, LSA) == OSPF_DEBUG_LSA) vty_out(vty, " OSPF LSA debugging is on\n"); else { if (IS_DEBUG_OSPF(lsa, LSA_GENERATE)) vty_out(vty, " OSPF LSA generation debugging is on\n"); if (IS_DEBUG_OSPF(lsa, LSA_FLOODING)) vty_out(vty, " OSPF LSA flooding debugging is on\n"); if (IS_DEBUG_OSPF(lsa, LSA_INSTALL)) vty_out(vty, " OSPF LSA install debugging is on\n"); if (IS_DEBUG_OSPF(lsa, LSA_REFRESH)) vty_out(vty, " OSPF LSA refresh debugging is on\n"); } /* Show debug status for Zebra. */ if (IS_DEBUG_OSPF(zebra, ZEBRA) == OSPF_DEBUG_ZEBRA) vty_out(vty, " OSPF Zebra debugging is on\n"); else { if (IS_DEBUG_OSPF(zebra, ZEBRA_INTERFACE)) vty_out(vty, " OSPF Zebra interface debugging is on\n"); if (IS_DEBUG_OSPF(zebra, ZEBRA_REDISTRIBUTE)) vty_out(vty, " OSPF Zebra redistribute debugging is on\n"); } /* Show debug status for NSSA. */ if (IS_DEBUG_OSPF(nssa, NSSA) == OSPF_DEBUG_NSSA) vty_out(vty, " OSPF NSSA debugging is on\n"); vty_out(vty, "\n"); return CMD_SUCCESS; } DEFUN_NOSH (show_debugging_ospf, show_debugging_ospf_cmd, "show debugging [ospf]", SHOW_STR DEBUG_STR OSPF_STR) { struct ospf *ospf = NULL; ospf = ospf_lookup_by_vrf_id(VRF_DEFAULT); if (ospf == NULL) return CMD_SUCCESS; return show_debugging_ospf_common(vty, ospf); } DEFUN_NOSH (show_debugging_ospf_instance, show_debugging_ospf_instance_cmd, "show debugging ospf (1-65535)", SHOW_STR DEBUG_STR OSPF_STR "Instance ID\n") { int idx_number = 3; struct ospf *ospf; unsigned short instance = 0; instance = strtoul(argv[idx_number]->arg, NULL, 10); if ((ospf = ospf_lookup_instance(instance)) == NULL) return CMD_SUCCESS; return show_debugging_ospf_common(vty, ospf); } /* Debug node. */ static struct cmd_node debug_node = { DEBUG_NODE, "", 1 /* VTYSH */ }; static int config_write_debug(struct vty *vty) { int write = 0; int i, r; const char *type_str[] = {"hello", "dd", "ls-request", "ls-update", "ls-ack"}; const char *detail_str[] = { "", " send", " recv", "", " detail", " send detail", " recv detail", " detail"}; struct ospf *ospf; char str[16]; memset(str, 0, 16); ospf = ospf_lookup_by_vrf_id(VRF_DEFAULT); if (ospf == NULL) return CMD_SUCCESS; if (ospf->instance) sprintf(str, " %u", ospf->instance); /* debug ospf ism (status|events|timers). */ if (IS_CONF_DEBUG_OSPF(ism, ISM) == OSPF_DEBUG_ISM) vty_out(vty, "debug ospf%s ism\n", str); else { if (IS_CONF_DEBUG_OSPF(ism, ISM_STATUS)) vty_out(vty, "debug ospf%s ism status\n", str); if (IS_CONF_DEBUG_OSPF(ism, ISM_EVENTS)) vty_out(vty, "debug ospf%s ism event\n", str); if (IS_CONF_DEBUG_OSPF(ism, ISM_TIMERS)) vty_out(vty, "debug ospf%s ism timer\n", str); } /* debug ospf nsm (status|events|timers). */ if (IS_CONF_DEBUG_OSPF(nsm, NSM) == OSPF_DEBUG_NSM) vty_out(vty, "debug ospf%s nsm\n", str); else { if (IS_CONF_DEBUG_OSPF(nsm, NSM_STATUS)) vty_out(vty, "debug ospf%s nsm status\n", str); if (IS_CONF_DEBUG_OSPF(nsm, NSM_EVENTS)) vty_out(vty, "debug ospf%s nsm event\n", str); if (IS_CONF_DEBUG_OSPF(nsm, NSM_TIMERS)) vty_out(vty, "debug ospf%s nsm timer\n", str); } /* debug ospf lsa (generate|flooding|install|refresh). */ if (IS_CONF_DEBUG_OSPF(lsa, LSA) == OSPF_DEBUG_LSA) vty_out(vty, "debug ospf%s lsa\n", str); else { if (IS_CONF_DEBUG_OSPF(lsa, LSA_GENERATE)) vty_out(vty, "debug ospf%s lsa generate\n", str); if (IS_CONF_DEBUG_OSPF(lsa, LSA_FLOODING)) vty_out(vty, "debug ospf%s lsa flooding\n", str); if (IS_CONF_DEBUG_OSPF(lsa, LSA_INSTALL)) vty_out(vty, "debug ospf%s lsa install\n", str); if (IS_CONF_DEBUG_OSPF(lsa, LSA_REFRESH)) vty_out(vty, "debug ospf%s lsa refresh\n", str); write = 1; } /* debug ospf zebra (interface|redistribute). */ if (IS_CONF_DEBUG_OSPF(zebra, ZEBRA) == OSPF_DEBUG_ZEBRA) vty_out(vty, "debug ospf%s zebra\n", str); else { if (IS_CONF_DEBUG_OSPF(zebra, ZEBRA_INTERFACE)) vty_out(vty, "debug ospf%s zebra interface\n", str); if (IS_CONF_DEBUG_OSPF(zebra, ZEBRA_REDISTRIBUTE)) vty_out(vty, "debug ospf%s zebra redistribute\n", str); write = 1; } /* debug ospf event. */ if (IS_CONF_DEBUG_OSPF(event, EVENT) == OSPF_DEBUG_EVENT) { vty_out(vty, "debug ospf%s event\n", str); write = 1; } /* debug ospf nssa. */ if (IS_CONF_DEBUG_OSPF(nssa, NSSA) == OSPF_DEBUG_NSSA) { vty_out(vty, "debug ospf%s nssa\n", str); write = 1; } /* debug ospf packet all detail. */ r = OSPF_DEBUG_SEND_RECV | OSPF_DEBUG_DETAIL; for (i = 0; i < 5; i++) r &= conf_debug_ospf_packet[i] & (OSPF_DEBUG_SEND_RECV | OSPF_DEBUG_DETAIL); if (r == (OSPF_DEBUG_SEND_RECV | OSPF_DEBUG_DETAIL)) { vty_out(vty, "debug ospf%s packet all detail\n", str); return 1; } /* debug ospf packet all. */ r = OSPF_DEBUG_SEND_RECV; for (i = 0; i < 5; i++) r &= conf_debug_ospf_packet[i] & OSPF_DEBUG_SEND_RECV; if (r == OSPF_DEBUG_SEND_RECV) { vty_out(vty, "debug ospf%s packet all\n", str); for (i = 0; i < 5; i++) if (conf_debug_ospf_packet[i] & OSPF_DEBUG_DETAIL) vty_out(vty, "debug ospf%s packet %s detail\n", str, type_str[i]); return 1; } /* debug ospf packet (hello|dd|ls-request|ls-update|ls-ack) (send|recv) (detail). */ for (i = 0; i < 5; i++) { if (conf_debug_ospf_packet[i] == 0) continue; vty_out(vty, "debug ospf%s packet %s%s\n", str, type_str[i], detail_str[conf_debug_ospf_packet[i]]); write = 1; } /* debug ospf te */ if (IS_CONF_DEBUG_OSPF(te, TE) == OSPF_DEBUG_TE) { vty_out(vty, "debug ospf%s te\n", str); write = 1; } /* debug ospf sr */ if (IS_CONF_DEBUG_OSPF(sr, SR) == OSPF_DEBUG_SR) { vty_out(vty, "debug ospf%s sr\n", str); write = 1; } return write; } /* Initialize debug commands. */ void ospf_debug_init(void) { install_node(&debug_node, config_write_debug); install_element(ENABLE_NODE, &show_debugging_ospf_cmd); install_element(ENABLE_NODE, &debug_ospf_ism_cmd); install_element(ENABLE_NODE, &debug_ospf_nsm_cmd); install_element(ENABLE_NODE, &debug_ospf_lsa_cmd); install_element(ENABLE_NODE, &debug_ospf_zebra_cmd); install_element(ENABLE_NODE, &debug_ospf_event_cmd); install_element(ENABLE_NODE, &debug_ospf_nssa_cmd); install_element(ENABLE_NODE, &debug_ospf_te_cmd); install_element(ENABLE_NODE, &debug_ospf_sr_cmd); install_element(ENABLE_NODE, &no_debug_ospf_ism_cmd); install_element(ENABLE_NODE, &no_debug_ospf_nsm_cmd); install_element(ENABLE_NODE, &no_debug_ospf_lsa_cmd); install_element(ENABLE_NODE, &no_debug_ospf_zebra_cmd); install_element(ENABLE_NODE, &no_debug_ospf_event_cmd); install_element(ENABLE_NODE, &no_debug_ospf_nssa_cmd); install_element(ENABLE_NODE, &no_debug_ospf_te_cmd); install_element(ENABLE_NODE, &no_debug_ospf_sr_cmd); install_element(ENABLE_NODE, &show_debugging_ospf_instance_cmd); install_element(ENABLE_NODE, &debug_ospf_packet_cmd); install_element(ENABLE_NODE, &no_debug_ospf_packet_cmd); install_element(ENABLE_NODE, &debug_ospf_instance_nsm_cmd); install_element(ENABLE_NODE, &debug_ospf_instance_lsa_cmd); install_element(ENABLE_NODE, &debug_ospf_instance_zebra_cmd); install_element(ENABLE_NODE, &debug_ospf_instance_event_cmd); install_element(ENABLE_NODE, &debug_ospf_instance_nssa_cmd); install_element(ENABLE_NODE, &no_debug_ospf_instance_nsm_cmd); install_element(ENABLE_NODE, &no_debug_ospf_instance_lsa_cmd); install_element(ENABLE_NODE, &no_debug_ospf_instance_zebra_cmd); install_element(ENABLE_NODE, &no_debug_ospf_instance_event_cmd); install_element(ENABLE_NODE, &no_debug_ospf_instance_nssa_cmd); install_element(ENABLE_NODE, &no_debug_ospf_cmd); install_element(CONFIG_NODE, &debug_ospf_packet_cmd); install_element(CONFIG_NODE, &no_debug_ospf_packet_cmd); install_element(CONFIG_NODE, &debug_ospf_ism_cmd); install_element(CONFIG_NODE, &no_debug_ospf_ism_cmd); install_element(CONFIG_NODE, &debug_ospf_nsm_cmd); install_element(CONFIG_NODE, &debug_ospf_lsa_cmd); install_element(CONFIG_NODE, &debug_ospf_zebra_cmd); install_element(CONFIG_NODE, &debug_ospf_event_cmd); install_element(CONFIG_NODE, &debug_ospf_nssa_cmd); install_element(CONFIG_NODE, &debug_ospf_te_cmd); install_element(CONFIG_NODE, &debug_ospf_sr_cmd); install_element(CONFIG_NODE, &no_debug_ospf_nsm_cmd); install_element(CONFIG_NODE, &no_debug_ospf_lsa_cmd); install_element(CONFIG_NODE, &no_debug_ospf_zebra_cmd); install_element(CONFIG_NODE, &no_debug_ospf_event_cmd); install_element(CONFIG_NODE, &no_debug_ospf_nssa_cmd); install_element(CONFIG_NODE, &no_debug_ospf_te_cmd); install_element(CONFIG_NODE, &no_debug_ospf_sr_cmd); install_element(CONFIG_NODE, &debug_ospf_instance_nsm_cmd); install_element(CONFIG_NODE, &debug_ospf_instance_lsa_cmd); install_element(CONFIG_NODE, &debug_ospf_instance_zebra_cmd); install_element(CONFIG_NODE, &debug_ospf_instance_event_cmd); install_element(CONFIG_NODE, &debug_ospf_instance_nssa_cmd); install_element(CONFIG_NODE, &no_debug_ospf_instance_nsm_cmd); install_element(CONFIG_NODE, &no_debug_ospf_instance_lsa_cmd); install_element(CONFIG_NODE, &no_debug_ospf_instance_zebra_cmd); install_element(CONFIG_NODE, &no_debug_ospf_instance_event_cmd); install_element(CONFIG_NODE, &no_debug_ospf_instance_nssa_cmd); install_element(CONFIG_NODE, &no_debug_ospf_cmd); } frr-7.2.1/ospfd/ospf_dump.h0000644000000000000000000001334013610377563012475 00000000000000/* * OSPFd dump routine. * Copyright (C) 1999 Toshiaki Takada * * This file is part of GNU Zebra. * * GNU Zebra is free software; you can redistribute it and/or modify it * under the terms of the GNU General Public License as published by the * Free Software Foundation; either version 2, or (at your option) any * later version. * * GNU Zebra 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; see the file COPYING; if not, write to the Free Software * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA */ #ifndef _ZEBRA_OSPF_DUMP_H #define _ZEBRA_OSPF_DUMP_H /* Debug Flags. */ #define OSPF_DEBUG_HELLO 0x01 #define OSPF_DEBUG_DB_DESC 0x02 #define OSPF_DEBUG_LS_REQ 0x04 #define OSPF_DEBUG_LS_UPD 0x08 #define OSPF_DEBUG_LS_ACK 0x10 #define OSPF_DEBUG_ALL 0x1f #define OSPF_DEBUG_SEND 0x01 #define OSPF_DEBUG_RECV 0x02 #define OSPF_DEBUG_SEND_RECV 0x03 #define OSPF_DEBUG_DETAIL 0x04 #define OSPF_DEBUG_ISM_STATUS 0x01 #define OSPF_DEBUG_ISM_EVENTS 0x02 #define OSPF_DEBUG_ISM_TIMERS 0x04 #define OSPF_DEBUG_ISM 0x07 #define OSPF_DEBUG_NSM_STATUS 0x01 #define OSPF_DEBUG_NSM_EVENTS 0x02 #define OSPF_DEBUG_NSM_TIMERS 0x04 #define OSPF_DEBUG_NSM 0x07 #define OSPF_DEBUG_LSA_GENERATE 0x01 #define OSPF_DEBUG_LSA_FLOODING 0x02 #define OSPF_DEBUG_LSA_INSTALL 0x04 #define OSPF_DEBUG_LSA_REFRESH 0x08 #define OSPF_DEBUG_LSA 0x0F #define OSPF_DEBUG_ZEBRA_INTERFACE 0x01 #define OSPF_DEBUG_ZEBRA_REDISTRIBUTE 0x02 #define OSPF_DEBUG_ZEBRA 0x03 #define OSPF_DEBUG_EVENT 0x01 #define OSPF_DEBUG_NSSA 0x02 #define OSPF_DEBUG_TE 0x04 #define OSPF_DEBUG_EXT 0x08 #define OSPF_DEBUG_SR 0x10 /* Macro for setting debug option. */ #define CONF_DEBUG_PACKET_ON(a, b) conf_debug_ospf_packet[a] |= (b) #define CONF_DEBUG_PACKET_OFF(a, b) conf_debug_ospf_packet[a] &= ~(b) #define TERM_DEBUG_PACKET_ON(a, b) term_debug_ospf_packet[a] |= (b) #define TERM_DEBUG_PACKET_OFF(a, b) term_debug_ospf_packet[a] &= ~(b) #define DEBUG_PACKET_ON(a, b) \ do { \ CONF_DEBUG_PACKET_ON(a, b); \ TERM_DEBUG_PACKET_ON(a, b); \ } while (0) #define DEBUG_PACKET_OFF(a, b) \ do { \ CONF_DEBUG_PACKET_OFF(a, b); \ TERM_DEBUG_PACKET_OFF(a, b); \ } while (0) #define CONF_DEBUG_ON(a, b) conf_debug_ospf_ ## a |= (OSPF_DEBUG_ ## b) #define CONF_DEBUG_OFF(a, b) conf_debug_ospf_ ## a &= ~(OSPF_DEBUG_ ## b) #define TERM_DEBUG_ON(a, b) term_debug_ospf_ ## a |= (OSPF_DEBUG_ ## b) #define TERM_DEBUG_OFF(a, b) term_debug_ospf_ ## a &= ~(OSPF_DEBUG_ ## b) #define DEBUG_ON(a, b) \ do { \ CONF_DEBUG_ON(a, b); \ TERM_DEBUG_ON(a, b); \ } while (0) #define DEBUG_OFF(a, b) \ do { \ CONF_DEBUG_OFF(a, b); \ TERM_DEBUG_OFF(a, b); \ } while (0) /* Macro for checking debug option. */ #define IS_DEBUG_OSPF_PACKET(a, b) (term_debug_ospf_packet[a] & OSPF_DEBUG_##b) #define IS_DEBUG_OSPF(a, b) (term_debug_ospf_##a & OSPF_DEBUG_##b) #define IS_DEBUG_OSPF_EVENT IS_DEBUG_OSPF(event, EVENT) #define IS_DEBUG_OSPF_NSSA IS_DEBUG_OSPF(nssa, NSSA) #define IS_DEBUG_OSPF_TE IS_DEBUG_OSPF(te, TE) #define IS_DEBUG_OSPF_EXT IS_DEBUG_OSPF(ext, EXT) #define IS_DEBUG_OSPF_SR IS_DEBUG_OSPF(sr, SR) #define IS_CONF_DEBUG_OSPF_PACKET(a, b) \ (conf_debug_ospf_packet[a] & OSPF_DEBUG_##b) #define IS_CONF_DEBUG_OSPF(a, b) (conf_debug_ospf_##a & OSPF_DEBUG_##b) #ifdef ORIGINAL_CODING #else /* ORIGINAL_CODING */ struct stream; #endif /* ORIGINAL_CODING */ #define AREA_NAME(A) ospf_area_name_string ((A)) #define IF_NAME(I) ospf_if_name_string ((I)) /* Extern debug flag. */ extern unsigned long term_debug_ospf_packet[]; extern unsigned long term_debug_ospf_event; extern unsigned long term_debug_ospf_ism; extern unsigned long term_debug_ospf_nsm; extern unsigned long term_debug_ospf_lsa; extern unsigned long term_debug_ospf_zebra; extern unsigned long term_debug_ospf_nssa; extern unsigned long term_debug_ospf_te; extern unsigned long term_debug_ospf_ext; extern unsigned long term_debug_ospf_sr; /* Message Strings. */ extern char *ospf_lsa_type_str[]; /* Prototypes. */ extern const char *ospf_area_name_string(struct ospf_area *); extern const char *ospf_area_desc_string(struct ospf_area *); extern const char *ospf_if_name_string(struct ospf_interface *); extern void ospf_nbr_state_message(struct ospf_neighbor *, char *, size_t); extern const char *ospf_timer_dump(struct thread *, char *, size_t); extern const char *ospf_timeval_dump(struct timeval *, char *, size_t); extern void ospf_ip_header_dump(struct ip *); extern void ospf_packet_dump(struct stream *); extern void ospf_debug_init(void); /* Appropriate buffer size to use with ospf_timer_dump and ospf_timeval_dump: */ #define OSPF_TIME_DUMP_SIZE 16 #endif /* _ZEBRA_OSPF_DUMP_H */ frr-7.2.1/ospfd/ospf_dump_api.c0000644000000000000000000001075413610377563013327 00000000000000/* * OSPFd dump routine (parts used by ospfclient). * Copyright (C) 1999, 2000 Toshiaki Takada * * This file is part of FRRouting (FRR). * * FRR is free software; you can redistribute it and/or modify it under the * terms of the GNU General Public License as published by the Free Software * Foundation; either version 2, or (at your option) any later version. * * FRR 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; see the file COPYING; if not, write to the Free Software * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA */ #include #include "log.h" #include "prefix.h" #include "ospf_dump_api.h" #include "ospfd.h" #include "ospf_asbr.h" #include "ospf_lsa.h" #include "ospf_nsm.h" #include "ospf_ism.h" const struct message ospf_ism_state_msg[] = { {ISM_DependUpon, "DependUpon"}, {ISM_Down, "Down"}, {ISM_Loopback, "Loopback"}, {ISM_Waiting, "Waiting"}, {ISM_PointToPoint, "Point-To-Point"}, {ISM_DROther, "DROther"}, {ISM_Backup, "Backup"}, {ISM_DR, "DR"}, {0}}; const struct message ospf_nsm_state_msg[] = {{NSM_DependUpon, "DependUpon"}, {NSM_Deleted, "Deleted"}, {NSM_Down, "Down"}, {NSM_Attempt, "Attempt"}, {NSM_Init, "Init"}, {NSM_TwoWay, "2-Way"}, {NSM_ExStart, "ExStart"}, {NSM_Exchange, "Exchange"}, {NSM_Loading, "Loading"}, {NSM_Full, "Full"}, {0}}; const struct message ospf_lsa_type_msg[] = { {OSPF_UNKNOWN_LSA, "unknown"}, {OSPF_ROUTER_LSA, "router-LSA"}, {OSPF_NETWORK_LSA, "network-LSA"}, {OSPF_SUMMARY_LSA, "summary-LSA"}, {OSPF_ASBR_SUMMARY_LSA, "summary-LSA"}, {OSPF_AS_EXTERNAL_LSA, "AS-external-LSA"}, {OSPF_GROUP_MEMBER_LSA, "GROUP MEMBER LSA"}, {OSPF_AS_NSSA_LSA, "NSSA-LSA"}, {8, "Type-8 LSA"}, {OSPF_OPAQUE_LINK_LSA, "Link-Local Opaque-LSA"}, {OSPF_OPAQUE_AREA_LSA, "Area-Local Opaque-LSA"}, {OSPF_OPAQUE_AS_LSA, "AS-external Opaque-LSA"}, {0}}; const struct message ospf_link_state_id_type_msg[] = { {OSPF_UNKNOWN_LSA, "(unknown)"}, {OSPF_ROUTER_LSA, ""}, {OSPF_NETWORK_LSA, "(address of Designated Router)"}, {OSPF_SUMMARY_LSA, "(summary Network Number)"}, {OSPF_ASBR_SUMMARY_LSA, "(AS Boundary Router address)"}, {OSPF_AS_EXTERNAL_LSA, "(External Network Number)"}, {OSPF_GROUP_MEMBER_LSA, "(Group membership information)"}, {OSPF_AS_NSSA_LSA, "(External Network Number for NSSA)"}, {8, "(Type-8 LSID)"}, {OSPF_OPAQUE_LINK_LSA, "(Link-Local Opaque-Type/ID)"}, {OSPF_OPAQUE_AREA_LSA, "(Area-Local Opaque-Type/ID)"}, {OSPF_OPAQUE_AS_LSA, "(AS-external Opaque-Type/ID)"}, {0}}; const struct message ospf_network_type_msg[] = { {OSPF_IFTYPE_NONE, "NONE"}, {OSPF_IFTYPE_POINTOPOINT, "Point-to-Point"}, {OSPF_IFTYPE_BROADCAST, "Broadcast"}, {OSPF_IFTYPE_NBMA, "NBMA"}, {OSPF_IFTYPE_POINTOMULTIPOINT, "Point-to-MultiPoint"}, {OSPF_IFTYPE_VIRTUALLINK, "Virtual-Link"}, {0}}; /* AuType */ const struct message ospf_auth_type_str[] = { {OSPF_AUTH_NULL, "Null"}, {OSPF_AUTH_SIMPLE, "Simple"}, {OSPF_AUTH_CRYPTOGRAPHIC, "Cryptographic"}, {0}}; #define OSPF_OPTION_STR_MAXLEN 24 char *ospf_options_dump(uint8_t options) { static char buf[OSPF_OPTION_STR_MAXLEN]; snprintf(buf, OSPF_OPTION_STR_MAXLEN, "*|%s|%s|%s|%s|%s|%s|%s", (options & OSPF_OPTION_O) ? "O" : "-", (options & OSPF_OPTION_DC) ? "DC" : "-", (options & OSPF_OPTION_EA) ? "EA" : "-", (options & OSPF_OPTION_NP) ? "N/P" : "-", (options & OSPF_OPTION_MC) ? "MC" : "-", (options & OSPF_OPTION_E) ? "E" : "-", (options & OSPF_OPTION_MT) ? "M/T" : "-"); return buf; } void ospf_lsa_header_dump(struct lsa_header *lsah) { const char *lsah_type = lookup_msg(ospf_lsa_type_msg, lsah->type, NULL); zlog_debug(" LSA Header"); zlog_debug(" LS age %d", ntohs(lsah->ls_age)); zlog_debug(" Options %d (%s)", lsah->options, ospf_options_dump(lsah->options)); zlog_debug(" LS type %d (%s)", lsah->type, (lsah->type ? lsah_type : "unknown type")); zlog_debug(" Link State ID %s", inet_ntoa(lsah->id)); zlog_debug(" Advertising Router %s", inet_ntoa(lsah->adv_router)); zlog_debug(" LS sequence number 0x%lx", (unsigned long)ntohl(lsah->ls_seqnum)); zlog_debug(" LS checksum 0x%x", ntohs(lsah->checksum)); zlog_debug(" length %d", ntohs(lsah->length)); } frr-7.2.1/ospfd/ospf_dump_api.h0000644000000000000000000000315613610377563013332 00000000000000/* * OSPFd dump routine (parts used by ospfclient). * Copyright (C) 1999 Toshiaki Takada * * This file is part of FRRouting (FRR). * * FRR is free software; you can redistribute it and/or modify it under the * terms of the GNU General Public License as published by the Free Software * Foundation; either version 2, or (at your option) any later version. * * FRR 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; see the file COPYING; if not, write to the Free Software * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA */ #ifndef _ZEBRA_OSPF_DUMP_API_H #define _ZEBRA_OSPF_DUMP_API_H #include "log.h" struct lsa_header; extern const struct message ospf_ism_state_msg[]; extern const struct message ospf_nsm_state_msg[]; extern const struct message ospf_lsa_type_msg[]; extern const struct message ospf_link_state_id_type_msg[]; extern const struct message ospf_network_type_msg[]; extern const struct message ospf_auth_type_str[]; extern const int ospf_ism_state_msg_max; extern const int ospf_nsm_state_msg_max; extern const int ospf_lsa_type_msg_max; extern const int ospf_link_state_id_type_msg_max; extern const int ospf_network_type_msg_max; extern const size_t ospf_auth_type_str_max; extern char *ospf_options_dump(uint8_t); extern void ospf_lsa_header_dump(struct lsa_header *); #endif /* _ZEBRA_OSPF_DUMP_API_H */ frr-7.2.1/ospfd/ospf_errors.c0000644000000000000000000001751213610377563013044 00000000000000/* * OSPF-specific error messages. * Copyright (C) 2018 Cumulus Networks, Inc. * Chirag Shah * * This program is free software; you can redistribute it and/or modify it * under the terms of the 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; see the file COPYING; if not, write to the Free Software * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA */ #include #include "lib/ferr.h" #include "ospf_errors.h" /* clang-format off */ static struct log_ref ferr_ospf_warn[] = { { .code = EC_OSPF_SET_METRIC_PLUS, .title = "OSPF does not support `set metric +rtt/-rtt`", .description = "This implementation of OSPF does not currently support `set metric +rtt/-rtt`", .suggestion = "Do not use this particular set command for an ospf route-map", }, { .code = EC_OSPF_MD5, .title = "OSPF has noticed a MD5 issue", .description = "Something has gone wrong with the calculation of the MD5 data", .suggestion = "Ensure your key is correct, gather log data from this side as well as peer and open an Issue", }, { .code = EC_OSPF_PACKET, .title = "OSPF has detected packet information mismatch", .description = "OSPF has detected that packet information received is incorrect", .suggestion = "Ensure interface configuration is correct, gather log files from here and the peer and open an Issue", }, { .code = EC_OSPF_LARGE_LSA, .title = "OSPF has determined that a LSA packet will be too large", .description = "OSPF has received data that is causing it to recalculate how large packets should be and has discovered that the packet size needed would be too large and we will need to fragment packets", .suggestion = "Further divide up your network with areas", }, { .code = EC_OSPF_LSA_UNEXPECTED, .title = "OSPF has received a LSA-type that it was not expecting", .description = "OSPF has received a LSA-type that it was not expecting during processing", .suggestion = "Gather log data from this machine and it's peer and open an Issue", }, { .code = EC_OSPF_LSA, .title = "OSPF has discovered inconsistent internal state for a LSA", .description = "During handling of a LSA, OSPF has discovered that the LSA's internal state is inconsistent", .suggestion = "Gather log data and open an Issue", }, { .code = EC_OSPF_OPAQUE_REGISTRATION, .title = "OSPF has failed to properly register Opaque Handler", .description = "During initialization OSPF has detected a failure to install an opaque handler", .suggestion = "Gather log data and open an Issue", }, { .code = EC_OSPF_TE_UNEXPECTED, .title = "OSPF has received TE information that it was not expecting", .description = "OSPF has received TE information that it was not expecting during normal processing of data", .suggestion = "Gather log data from this machine and it's peer and open an Issue", }, { .code = EC_OSPF_LSA_INSTALL_FAILURE, .title = "OSPF was unable to save the LSA into it's database", .description = "During processing of a new lsa and attempting to save the lsa into the OSPF database, this process failed.", .suggestion = "Gather log data from this machine and open an Issue", }, { .code = EC_OSPF_LSA_NULL, .title = "OSPF has received a NULL lsa pointer", .description = "When processing a LSA update we have found and noticed an internal error where we are passing around a NULL pointer", .suggestion = "Gather data from this machine and it's peer and open an Issue", }, { .code = EC_OSPF_EXT_LSA_UNEXPECTED, .title = "OSPF has received EXT information that leaves it in an unexpected state", .description = "While processing EXT LSA information, OSPF has noticed that the internal state of OSPF has gotten inconsistent", .suggestion = "Gather data from this machine and it's peer and open an Issue", }, { .code = EC_OSPF_LSA_MISSING, .title = "OSPF attempted to look up a LSA and it was not found", .description = "During processing of new LSA information, we attempted to look up an old LSA and it was not found", .suggestion = "Gather data from this machine and open an Issue", }, { .code = EC_OSPF_PTP_NEIGHBOR, .title = "OSPF has detected more than 1 neighbor on a P2P interface", .description = "If you configure a ospf interface as P2P we should not detect more than one neighbor on the interface", .suggestion = "Fix your config", }, { .code = EC_OSPF_LSA_SIZE, .title = "OSPF has detected an invalid LSA size", .description = "OSPF has detected a state where we are attempting to grow a LSA but the LSA has reached it's maximum size", .suggestion = "Gather data and open an Issue", }, { .code = END_FERR, } }; static struct log_ref ferr_ospf_err[] = { { .code = EC_OSPF_PKT_PROCESS, .title = "Failure to process a packet", .description = "OSPF attempted to process a received packet but could not", .suggestion = "Most likely a bug. If the problem persists, report the problem for troubleshooting" }, { .code = EC_OSPF_ROUTER_LSA_MISMATCH, .title = "Failure to process Router LSA", .description = "OSPF attempted to process a Router LSA but Advertising ID mismatch with link id", .suggestion = "Check OSPF network config for any config issue, If the problem persists, report the problem for troubleshooting" }, { .code = EC_OSPF_DOMAIN_CORRUPT, .title = "OSPF Domain Corruption", .description = "OSPF attempted to process a Router LSA but Advertising ID mismatch with link id", .suggestion = "Check OSPF network Database for corrupted LSA, If the problem persists, shutdown OSPF domain and report the problem for troubleshooting" }, { .code = EC_OSPF_INIT_FAIL, .title = "OSPF Initialization failure", .description = "OSPF failed to initialized OSPF default insance", .suggestion = "Ensure there is adequate memory on the device. If the problem persists, report the problem for troubleshooting" }, { .code = EC_OSPF_SR_INVALID_DB, .title = "OSPF SR Invalid DB", .description = "OSPF Segment Routing Database is invalid", .suggestion = "Most likely a bug. If the problem persists, report the problem for troubleshooting" }, { .code = EC_OSPF_SR_NODE_CREATE, .title = "OSPF SR hash node creation failed", .description = "OSPF Segment Routing node creation failed", .suggestion = "Most likely a bug. If the problem persists, report the problem for troubleshooting" }, { .code = EC_OSPF_SR_INVALID_LSA_ID, .title = "OSPF SR Invalid LSA ID", .description = "OSPF Segment Routing invalid lsa id", .suggestion = "Restart OSPF instance, If the problem persists, report the problem for troubleshooting" }, { .code = EC_OSPF_INVALID_ALGORITHM, .title = "OSPF SR Invalid Algorithm", .description = "OSPF Segment Routing invalid Algorithm", .suggestion = "Most likely a bug. If the problem persists, report the problem for troubleshooting" }, { .code = EC_OSPF_FSM_INVALID_STATE, .title = "OSPF FSM invalid state detected", .description = "OSPF has attempted to change states when it should not be able to", .suggestion = "Gather log files and open an issue", }, { .code = EC_OSPF_LARGE_HELLO, .title = "OSPF Encountered a Large Hello", .description = "OSPF attempted to send a Hello larger than MTU but did not", .suggestion = "Too many neighbors configured on a single interface. Suggestion is to decrease the number of neighbors on a single interface/subnet" }, { .code = END_FERR, } }; /* clang-format on */ void ospf_error_init(void) { log_ref_add(ferr_ospf_warn); log_ref_add(ferr_ospf_err); } frr-7.2.1/ospfd/ospf_errors.h0000644000000000000000000000303513610377563013044 00000000000000/* * OSPF-specific error messages. * Copyright (C) 2018 Cumulus Networks, Inc. * Chirag Shah * * This program is free software; you can redistribute it and/or modify it * under the terms of the 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; see the file COPYING; if not, write to the Free Software * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA */ #ifndef __OSPF_ERRORS_H__ #define __OSPF_ERRORS_H__ #include "lib/ferr.h" enum ospf_log_refs { EC_OSPF_PKT_PROCESS = OSPF_FERR_START, EC_OSPF_ROUTER_LSA_MISMATCH, EC_OSPF_DOMAIN_CORRUPT, EC_OSPF_INIT_FAIL, EC_OSPF_SR_INVALID_DB, EC_OSPF_SR_NODE_CREATE, EC_OSPF_SR_INVALID_LSA_ID, EC_OSPF_INVALID_ALGORITHM, EC_OSPF_FSM_INVALID_STATE, EC_OSPF_SET_METRIC_PLUS, EC_OSPF_MD5, EC_OSPF_PACKET, EC_OSPF_LARGE_LSA, EC_OSPF_LSA_UNEXPECTED, EC_OSPF_LSA, EC_OSPF_OPAQUE_REGISTRATION, EC_OSPF_TE_UNEXPECTED, EC_OSPF_LSA_INSTALL_FAILURE, EC_OSPF_LSA_NULL, EC_OSPF_EXT_LSA_UNEXPECTED, EC_OSPF_LSA_MISSING, EC_OSPF_PTP_NEIGHBOR, EC_OSPF_LSA_SIZE, EC_OSPF_LARGE_HELLO, }; extern void ospf_error_init(void); #endif frr-7.2.1/ospfd/ospf_ext.c0000644000000000000000000014277613610377563012343 00000000000000/* * This is an implementation of RFC7684 OSPFv2 Prefix/Link Attribute * Advertisement * * Module name: Extended Prefix/Link Opaque LSA * * Author: Olivier Dugeon * Author: Anselme Sawadogo * * Copyright (C) 2016 - 2018 Orange Labs http://www.orange.com * * This program is free software; you can redistribute it and/or modify it * under the terms of the GNU General Public License as published by the Free * Software Foundation; either version 2 of the License, or (at your option) * any later version. * * This program is distributed in the hope that it will be useful, but WITHOUT * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for * more details. * * You should have received a copy of the GNU General Public License along * with this program; see the file COPYING; if not, write to the Free Software * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA */ #include #include #include #include #include "linklist.h" #include "prefix.h" #include "if.h" #include "table.h" #include "memory.h" #include "command.h" #include "vty.h" #include "stream.h" #include "log.h" #include "thread.h" #include "hash.h" #include "sockunion.h" /* for inet_aton() */ #include "network.h" #include "if.h" #include "libospf.h" /* for ospf interface types */ #include "ospfd/ospfd.h" #include "ospfd/ospf_interface.h" #include "ospfd/ospf_ism.h" #include "ospfd/ospf_asbr.h" #include "ospfd/ospf_lsa.h" #include "ospfd/ospf_lsdb.h" #include "ospfd/ospf_neighbor.h" #include "ospfd/ospf_nsm.h" #include "ospfd/ospf_flood.h" #include "ospfd/ospf_packet.h" #include "ospfd/ospf_spf.h" #include "ospfd/ospf_dump.h" #include "ospfd/ospf_route.h" #include "ospfd/ospf_ase.h" #include "ospfd/ospf_zebra.h" #include "ospfd/ospf_sr.h" #include "ospfd/ospf_ext.h" #include "ospfd/ospf_errors.h" /* Following structure are internal use only. */ /* * Global variable to manage Extended Prefix/Link Opaque LSA on this node. * Note that all parameter values are stored in network byte order. */ static struct ospf_ext_lp OspfEXT; /* * ----------------------------------------------------------------------- * Followings are initialize/terminate functions for Extended Prefix/Link * Opaque LSA handling. * ----------------------------------------------------------------------- */ /* Extended Prefix Opaque LSA related callback functions */ static void ospf_ext_pref_ism_change(struct ospf_interface *oi, int old_status); static void ospf_ext_pref_show_info(struct vty *vty, struct ospf_lsa *lsa); static int ospf_ext_pref_lsa_originate(void *arg); static struct ospf_lsa *ospf_ext_pref_lsa_refresh(struct ospf_lsa *lsa); static void ospf_ext_pref_lsa_schedule(struct ext_itf *exti, enum lsa_opcode opcode); /* Extended Link Opaque LSA related callback functions */ static int ospf_ext_link_new_if(struct interface *ifp); static int ospf_ext_link_del_if(struct interface *ifp); static void ospf_ext_link_ism_change(struct ospf_interface *oi, int old_status); static void ospf_ext_link_nsm_change(struct ospf_neighbor *nbr, int old_status); static void ospf_ext_link_show_info(struct vty *vty, struct ospf_lsa *lsa); static int ospf_ext_link_lsa_originate(void *arg); static struct ospf_lsa *ospf_ext_link_lsa_refresh(struct ospf_lsa *lsa); static void ospf_ext_link_lsa_schedule(struct ext_itf *exti, enum lsa_opcode opcode); static void ospf_ext_lsa_schedule(struct ext_itf *exti, enum lsa_opcode op); static int ospf_ext_link_lsa_update(struct ospf_lsa *lsa); static int ospf_ext_pref_lsa_update(struct ospf_lsa *lsa); static void del_ext_info(void *val); /* * Extended Link/Prefix initialization * * @param - none * * @return - 0 if OK, <> 0 otherwise */ int ospf_ext_init(void) { int rc = 0; memset(&OspfEXT, 0, sizeof(struct ospf_ext_lp)); OspfEXT.enabled = false; /* Only Area flooding is supported yet */ OspfEXT.scope = OSPF_OPAQUE_AREA_LSA; /* Initialize interface list */ OspfEXT.iflist = list_new(); OspfEXT.iflist->del = del_ext_info; zlog_info("EXT (%s): Register Extended Link Opaque LSA", __func__); rc = ospf_register_opaque_functab( OSPF_OPAQUE_AREA_LSA, OPAQUE_TYPE_EXTENDED_LINK_LSA, ospf_ext_link_new_if, /* new if */ ospf_ext_link_del_if, /* del if */ ospf_ext_link_ism_change, /* ism change */ ospf_ext_link_nsm_change, /* nsm change */ NULL, /* Write router config. */ NULL, /* Write interface conf. */ NULL, /* Write debug config. */ ospf_ext_link_show_info, /* Show LSA info */ ospf_ext_link_lsa_originate, /* Originate LSA */ ospf_ext_link_lsa_refresh, /* Refresh LSA */ ospf_ext_link_lsa_update, /* new_lsa_hook */ NULL); /* del_lsa_hook */ if (rc != 0) { flog_warn(EC_OSPF_OPAQUE_REGISTRATION, "EXT (%s): Failed to register Extended Link LSA", __func__); return rc; } zlog_info("EXT (%s): Register Extended Prefix Opaque LSA", __func__); rc = ospf_register_opaque_functab( OspfEXT.scope, OPAQUE_TYPE_EXTENDED_PREFIX_LSA, NULL, /* new if handle by link */ NULL, /* del if handle by link */ ospf_ext_pref_ism_change, /* ism change */ NULL, /* nsm change */ ospf_sr_config_write_router, /* Write router config. */ NULL, /* Write interface conf. */ NULL, /* Write debug config. */ ospf_ext_pref_show_info, /* Show LSA info */ ospf_ext_pref_lsa_originate, /* Originate LSA */ ospf_ext_pref_lsa_refresh, /* Refresh LSA */ ospf_ext_pref_lsa_update, /* new_lsa_hook */ NULL); /* del_lsa_hook */ if (rc != 0) { flog_warn(EC_OSPF_OPAQUE_REGISTRATION, "EXT (%s): Failed to register Extended Prefix LSA", __func__); return rc; } return rc; } /* * Extended Link/Prefix termination function * * @param - none * @return - none */ void ospf_ext_term(void) { if ((OspfEXT.scope == OSPF_OPAQUE_AREA_LSA) || (OspfEXT.scope == OSPF_OPAQUE_AS_LSA)) ospf_delete_opaque_functab(OspfEXT.scope, OPAQUE_TYPE_EXTENDED_PREFIX_LSA); ospf_delete_opaque_functab(OSPF_OPAQUE_AREA_LSA, OPAQUE_TYPE_EXTENDED_LINK_LSA); list_delete(&OspfEXT.iflist); OspfEXT.scope = 0; OspfEXT.enabled = false; return; } /* * Extended Link/Prefix finish function * * @param - none * @return - none */ void ospf_ext_finish(void) { // list_delete_all_node(OspfEXT.iflist); OspfEXT.enabled = false; } /* * --------------------------------------------------------------------- * Followings are control functions for Extended Prefix/Link Opaque LSA * parameters management. * --------------------------------------------------------------------- */ /* Functions to free memory space */ static void del_ext_info(void *val) { XFREE(MTYPE_OSPF_EXT_PARAMS, val); } /* Increment instance value for Extended Prefix Opaque LSAs Opaque ID field */ static uint32_t get_ext_pref_instance_value(void) { static uint32_t seqno = 0; if (seqno < MAX_LEGAL_EXT_INSTANCE_NUM) seqno += 1; else seqno = 1; /* Avoid zero. */ return seqno; } /* Increment instance value for Extended Link Opaque LSAs Opaque ID field */ static uint32_t get_ext_link_instance_value(void) { static uint32_t seqno = 0; if (seqno < MAX_LEGAL_EXT_INSTANCE_NUM) seqno += 1; else seqno = 1; /* Avoid zero. */ return seqno; } /* Lookup Extended Prefix/Links by ifp from OspfEXT struct iflist */ static struct ext_itf *lookup_ext_by_ifp(struct interface *ifp) { struct listnode *node, *nnode; struct ext_itf *exti; for (ALL_LIST_ELEMENTS(OspfEXT.iflist, node, nnode, exti)) if (exti->ifp == ifp) return exti; return NULL; } /* Lookup Extended Prefix/Links by LSA ID from OspfEXT struct iflist */ static struct ext_itf *lookup_ext_by_instance(struct ospf_lsa *lsa) { struct listnode *node; struct ext_itf *exti; uint32_t key = GET_OPAQUE_ID(ntohl(lsa->data->id.s_addr)); uint8_t type = GET_OPAQUE_TYPE(ntohl(lsa->data->id.s_addr)); for (ALL_LIST_ELEMENTS_RO(OspfEXT.iflist, node, exti)) if ((exti->instance == key) && (exti->type == type)) return exti; return NULL; } /* * ---------------------------------------------------------------------- * The underlying subsection defines setters and unsetters to create and * delete tlvs and subtlvs * ---------------------------------------------------------------------- */ /* Extended Prefix TLV - RFC7684 section 2.1 */ static void set_ext_prefix(struct ext_itf *exti, uint8_t route_type, uint8_t flags, struct prefix_ipv4 p) { TLV_TYPE(exti->prefix) = htons(EXT_TLV_PREFIX); /* Warning: Size must be adjust depending of subTLV's */ TLV_LEN(exti->prefix) = htons(EXT_TLV_PREFIX_SIZE); exti->prefix.route_type = route_type; exti->prefix.flags = flags; /* Only Address Family Ipv4 (0) is defined in RFC 7684 */ exti->prefix.af = 0; exti->prefix.pref_length = p.prefixlen; exti->prefix.address = p.prefix; } /* Extended Link TLV - RFC7684 section 3.1 */ static void set_ext_link(struct ext_itf *exti, uint8_t type, struct in_addr id, struct in_addr data) { TLV_TYPE(exti->link) = htons(EXT_TLV_LINK); /* Warning: Size must be adjust depending of subTLV's */ TLV_LEN(exti->link) = htons(EXT_TLV_LINK_SIZE); exti->link.link_type = type; exti->link.link_id = id; exti->link.link_data = data; } /* Prefix SID SubTLV - section 5 */ static void set_prefix_sid(struct ext_itf *exti, uint8_t algorithm, uint32_t value, int value_type, uint8_t flags) { if ((algorithm != SR_ALGORITHM_SPF) && (algorithm != SR_ALGORITHM_STRICT_SPF)) { flog_err(EC_OSPF_INVALID_ALGORITHM, "EXT (%s): unrecognized algorithm, not SPF or S-SPF", __func__); return; } /* Update flags according to the type of value field: label or index */ if (value_type == SID_LABEL) SET_FLAG(flags, EXT_SUBTLV_PREFIX_SID_VFLG); /* set prefix sid subtlv for an extended prefix tlv */ TLV_TYPE(exti->node_sid) = htons(EXT_SUBTLV_PREFIX_SID); exti->node_sid.algorithm = algorithm; exti->node_sid.flags = flags; exti->node_sid.mtid = 0; /* Multi-Topology is not supported */ /* Set Label or Index value */ if (value_type == SID_LABEL) { TLV_LEN(exti->node_sid) = htons(SID_LABEL_SIZE(EXT_SUBTLV_PREFIX_SID_SIZE)); exti->node_sid.value = htonl(SET_LABEL(value)); } else { TLV_LEN(exti->node_sid) = htons(SID_INDEX_SIZE(EXT_SUBTLV_PREFIX_SID_SIZE)); exti->node_sid.value = htonl(value); } } /* Adjacency SID SubTLV - section 6.1 */ static void set_adj_sid(struct ext_itf *exti, bool backup, uint32_t value, int value_type) { int index; uint8_t flags; /* Determine which ADJ_SID must be set: nominal or backup */ if (backup) { flags = EXT_SUBTLV_LINK_ADJ_SID_BFLG; index = 1; } else { index = 0; flags = 0; } /* Set Header */ TLV_TYPE(exti->adj_sid[index]) = htons(EXT_SUBTLV_ADJ_SID); /* Only Local ADJ-SID is supported for the moment */ SET_FLAG(flags, EXT_SUBTLV_LINK_ADJ_SID_LFLG); exti->adj_sid[index].mtid = 0; /* Multi-Topology is not supported */ /* Adjust Length, Flags and Value depending on the type of Label */ if (value_type == SID_LABEL) { SET_FLAG(flags, EXT_SUBTLV_LINK_ADJ_SID_VFLG); TLV_LEN(exti->adj_sid[index]) = htons(SID_LABEL_SIZE(EXT_SUBTLV_ADJ_SID_SIZE)); exti->adj_sid[index].value = htonl(SET_LABEL(value)); } else { UNSET_FLAG(flags, EXT_SUBTLV_LINK_ADJ_SID_VFLG); TLV_LEN(exti->adj_sid[index]) = htons(SID_INDEX_SIZE(EXT_SUBTLV_ADJ_SID_SIZE)); exti->adj_sid[index].value = htonl(value); } exti->adj_sid[index].flags = flags; /* Set computed flags */ exti->adj_sid[index].mtid = 0; /* Multi-Topology is not supported */ exti->adj_sid[index].weight = 0; /* Load-Balancing is not supported */ } /* LAN Adjacency SID SubTLV - section 6.2 */ static void set_lan_adj_sid(struct ext_itf *exti, bool backup, uint32_t value, int value_type, struct in_addr neighbor_id) { int index; uint8_t flags; /* Determine which ADJ_SID must be set: nominal or backup */ if (backup) { flags = EXT_SUBTLV_LINK_ADJ_SID_BFLG; index = 1; } else { index = 0; flags = 0; } /* Set Header */ TLV_TYPE(exti->lan_sid[index]) = htons(EXT_SUBTLV_LAN_ADJ_SID); /* Only Local ADJ-SID is supported for the moment */ SET_FLAG(flags, EXT_SUBTLV_LINK_ADJ_SID_LFLG); /* Adjust Length, Flags and Value depending on the type of Label */ if (value_type == SID_LABEL) { SET_FLAG(flags, EXT_SUBTLV_LINK_ADJ_SID_VFLG); TLV_LEN(exti->lan_sid[index]) = htons(SID_LABEL_SIZE(EXT_SUBTLV_PREFIX_RANGE_SIZE)); exti->lan_sid[index].value = htonl(SET_LABEL(value)); } else { UNSET_FLAG(flags, EXT_SUBTLV_LINK_ADJ_SID_VFLG); TLV_LEN(exti->lan_sid[index]) = htons(SID_INDEX_SIZE(EXT_SUBTLV_PREFIX_RANGE_SIZE)); exti->lan_sid[index].value = htonl(value); } exti->lan_sid[index].flags = flags; /* Set computed flags */ exti->lan_sid[index].mtid = 0; /* Multi-Topology is not supported */ exti->lan_sid[index].weight = 0; /* Load-Balancing is not supported */ exti->lan_sid[index].neighbor_id = neighbor_id; } /* Experimental SubTLV from Cisco */ static void set_rmt_itf_addr(struct ext_itf *exti, struct in_addr rmtif) { TLV_TYPE(exti->rmt_itf_addr) = htons(EXT_SUBTLV_RMT_ITF_ADDR); TLV_LEN(exti->rmt_itf_addr) = htons(sizeof(struct in_addr)); exti->rmt_itf_addr.value = rmtif; } /* * Update Extended prefix SID index for Loopback interface type * * @param ifname - Loopback interface name * @param index - new value for the prefix SID of this interface * @param p - prefix for this interface or NULL if Extended Prefix * should be remove * * @return instance number if update is OK, 0 otherwise */ uint32_t ospf_ext_schedule_prefix_index(struct interface *ifp, uint32_t index, struct prefix_ipv4 *p, uint8_t flags) { int rc = 0; struct ext_itf *exti; /* Find Extended Prefix interface */ exti = lookup_ext_by_ifp(ifp); if (exti == NULL) return rc; if (p != NULL) { if (IS_DEBUG_OSPF_SR) zlog_debug( "EXT (%s): Schedule new prefix %s/%u with " "index %u on interface %s", __func__, inet_ntoa(p->prefix), p->prefixlen, index, ifp->name); /* Set first Extended Prefix then the Prefix SID information */ set_ext_prefix(exti, OSPF_PATH_INTRA_AREA, EXT_TLV_PREF_NFLG, *p); set_prefix_sid(exti, SR_ALGORITHM_SPF, index, SID_INDEX, flags); /* Try to Schedule LSA */ SET_FLAG(exti->flags, EXT_LPFLG_LSA_ACTIVE); if (CHECK_FLAG(exti->flags, EXT_LPFLG_LSA_ENGAGED)) ospf_ext_pref_lsa_schedule(exti, REFRESH_THIS_LSA); else ospf_ext_pref_lsa_schedule(exti, REORIGINATE_THIS_LSA); } else { if (IS_DEBUG_OSPF_SR) zlog_debug("EXT (%s): Remove prefix for interface %s", __func__, ifp->name); if (CHECK_FLAG(exti->flags, EXT_LPFLG_LSA_ENGAGED)) { ospf_ext_pref_lsa_schedule(exti, FLUSH_THIS_LSA); UNSET_FLAG(exti->flags, EXT_LPFLG_LSA_ENGAGED); UNSET_FLAG(exti->flags, EXT_LPFLG_LSA_ACTIVE); } } return SET_OPAQUE_LSID(exti->type, exti->instance); } /* * Used by Segment Routing to activate/deactivate Extended Link/Prefix flooding * * @param enable To activate or not Segment Routing Extended LSA flooding * * @return none */ void ospf_ext_update_sr(bool enable) { struct listnode *node; struct ext_itf *exti; if (IS_DEBUG_OSPF_SR) zlog_debug("EXT (%s): %s Extended LSAs for Segment Routing ", __func__, enable ? "Enable" : "Disable"); if (enable) { OspfEXT.enabled = true; /* Refresh LSAs if already engaged or originate */ for (ALL_LIST_ELEMENTS_RO(OspfEXT.iflist, node, exti)) if (CHECK_FLAG(exti->flags, EXT_LPFLG_LSA_ENGAGED)) ospf_ext_lsa_schedule(exti, REFRESH_THIS_LSA); else ospf_ext_lsa_schedule(exti, REORIGINATE_THIS_LSA); } else { /* Start by Flushing engaged LSAs */ for (ALL_LIST_ELEMENTS_RO(OspfEXT.iflist, node, exti)) if (CHECK_FLAG(exti->flags, EXT_LPFLG_LSA_ENGAGED)) ospf_ext_lsa_schedule(exti, FLUSH_THIS_LSA); /* And then disable Extended Link/Prefix */ OspfEXT.enabled = false; } } /* * ----------------------------------------------------------------------- * Followings are callback functions against generic Opaque-LSAs handling * ----------------------------------------------------------------------- */ /* Add new Interface in Extended Interface List */ static int ospf_ext_link_new_if(struct interface *ifp) { struct ext_itf *new; int rc = -1; if (lookup_ext_by_ifp(ifp) != NULL) { rc = 0; /* Do nothing here. */ return rc; } new = XCALLOC(MTYPE_OSPF_EXT_PARAMS, sizeof(struct ext_itf)); /* initialize new information and link back the interface */ new->ifp = ifp; new->flags = EXT_LPFLG_LSA_INACTIVE; listnode_add(OspfEXT.iflist, new); rc = 0; return rc; } /* Remove existing Interface from Extended Interface List */ static int ospf_ext_link_del_if(struct interface *ifp) { struct ext_itf *exti; int rc = -1; exti = lookup_ext_by_ifp(ifp); if (exti != NULL) { struct list *iflist = OspfEXT.iflist; /* Dequeue listnode entry from the list. */ listnode_delete(iflist, exti); XFREE(MTYPE_OSPF_EXT_PARAMS, exti); rc = 0; } else { flog_warn(EC_OSPF_EXT_LSA_UNEXPECTED, "EXT (%s): interface %s is not found", __func__, ifp ? ifp->name : "-"); } return rc; } /* * Determine if an Interface belongs to an Extended Link Adjacency or LAN Adj. * type and allocate new instance value accordingly */ static void ospf_ext_link_ism_change(struct ospf_interface *oi, int old_status) { struct ext_itf *exti; /* Get interface information for Segment Routing */ exti = lookup_ext_by_ifp(oi->ifp); if (exti == NULL) return; /* Determine if interface is related to Adjacency or LAN Adj. SID */ if (oi->type != OSPF_IFTYPE_LOOPBACK) { if (oi->state == ISM_DR) exti->stype = LAN_ADJ_SID; else exti->stype = ADJ_SID; exti->instance = get_ext_link_instance_value(); exti->type = OPAQUE_TYPE_EXTENDED_LINK_LSA; zlog_debug("EXT (%s): Set %s SID to interface %s ", __func__, exti->stype == ADJ_SID ? "Adj." : "LAN Adj.", oi->ifp->name); } } /* * Determine if an Interface belongs to an Extended Prefix and * allocate new instance value accordingly */ static void ospf_ext_pref_ism_change(struct ospf_interface *oi, int old_status) { struct ext_itf *exti; /* Get interface information for Segment Routing */ exti = lookup_ext_by_ifp(oi->ifp); if (exti == NULL) { flog_warn(EC_OSPF_EXT_LSA_UNEXPECTED, "EXT (%s): Cannot get Extended info. from OI(%s)", __func__, IF_NAME(oi)); return; } /* Determine if interface is related to a Node SID */ if (oi->type == OSPF_IFTYPE_LOOPBACK) { exti->stype = PREF_SID; exti->instance = get_ext_pref_instance_value(); exti->type = OPAQUE_TYPE_EXTENDED_PREFIX_LSA; zlog_debug("EXT (%s): Set Node SID to interface %s ", __func__, oi->ifp->name); /* Complete SRDB if the interface belongs to a Prefix */ if (OspfEXT.enabled) ospf_sr_update_prefix(oi->ifp, oi->address); } } /* * Finish Extended Link configuration and flood corresponding LSA * when OSPF adjacency on this link fire up */ static void ospf_ext_link_nsm_change(struct ospf_neighbor *nbr, int old_status) { struct ospf_interface *oi = nbr->oi; struct ext_itf *exti; uint32_t label; /* Process Neighbor only when its state is NSM Full */ if (nbr->state != NSM_Full) return; /* Get interface information for Segment Routing */ exti = lookup_ext_by_ifp(oi->ifp); if (exti == NULL) { flog_warn(EC_OSPF_EXT_LSA_UNEXPECTED, "EXT (%s): Cannot get Extended info. from OI(%s)", __func__, IF_NAME(oi)); return; } if (oi->area == NULL || oi->area->ospf == NULL) { flog_warn(EC_OSPF_EXT_LSA_UNEXPECTED, "EXT (%s): Cannot refer to OSPF from OI(%s)", __func__, IF_NAME(oi)); return; } /* Keep Area information in combination with SR info. */ exti->area = oi->area; OspfEXT.area = oi->area; /* Process only Adjacency/LAN SID */ if (exti->stype == PREF_SID) return; switch (oi->state) { case ISM_PointToPoint: /* Segment ID is an Adjacency one */ exti->stype = ADJ_SID; /* Set Extended Link TLV with link_id == Nbr Router ID */ set_ext_link(exti, OSPF_IFTYPE_POINTOPOINT, nbr->router_id, oi->address->u.prefix4); /* Set Extended Link Adjacency SubTLVs, backup first */ label = get_ext_link_label_value(); set_adj_sid(exti, true, label, SID_LABEL); label = get_ext_link_label_value(); set_adj_sid(exti, false, label, SID_LABEL); /* And Remote Interface address */ set_rmt_itf_addr(exti, nbr->address.u.prefix4); break; case ISM_DR: /* Segment ID is a LAN Adjacency for the DR only */ exti->stype = LAN_ADJ_SID; /* Set Extended Link TLV with link_id == DR */ set_ext_link(exti, OSPF_IFTYPE_BROADCAST, DR(oi), oi->address->u.prefix4); /* Set Extended Link Adjacency SubTLVs, backup first */ label = get_ext_link_label_value(); set_lan_adj_sid(exti, true, label, SID_LABEL, nbr->router_id); label = get_ext_link_label_value(); set_lan_adj_sid(exti, false, label, SID_LABEL, nbr->router_id); break; case ISM_DROther: case ISM_Backup: /* Segment ID is an Adjacency if not the DR */ exti->stype = ADJ_SID; /* Set Extended Link TLV with link_id == DR */ set_ext_link(exti, OSPF_IFTYPE_BROADCAST, DR(oi), oi->address->u.prefix4); /* Set Extended Link Adjacency SubTLVs, backup first */ label = get_ext_link_label_value(); set_adj_sid(exti, true, label, SID_LABEL); label = get_ext_link_label_value(); set_adj_sid(exti, false, label, SID_LABEL); break; default: if (CHECK_FLAG(exti->flags, EXT_LPFLG_LSA_ENGAGED)) { ospf_ext_link_lsa_schedule(exti, FLUSH_THIS_LSA); UNSET_FLAG(exti->flags, EXT_LPFLG_LSA_ENGAGED); UNSET_FLAG(exti->flags, EXT_LPFLG_LSA_ACTIVE); } return; } if (IS_DEBUG_OSPF_SR) zlog_debug("EXT (%s): Complete %s SID to interface %s ", __func__, exti->stype == ADJ_SID ? "Adj." : "LAN Adj.", oi->ifp->name); /* flood this links params if everything is ok */ SET_FLAG(exti->flags, EXT_LPFLG_LSA_ACTIVE); if (OspfEXT.enabled) { if (CHECK_FLAG(exti->flags, EXT_LPFLG_LSA_ENGAGED)) ospf_ext_link_lsa_schedule(exti, REFRESH_THIS_LSA); else ospf_ext_link_lsa_schedule(exti, REORIGINATE_THIS_LSA); } } /* Callbacks to handle Extended Link Segment Routing LSA information */ static int ospf_ext_link_lsa_update(struct ospf_lsa *lsa) { /* Sanity Check */ if (lsa == NULL) { flog_warn(EC_OSPF_LSA_NULL, "EXT (%s): Abort! LSA is NULL", __func__); return -1; } /* Process only Opaque LSA */ if ((lsa->data->type != OSPF_OPAQUE_AREA_LSA) && (lsa->data->type != OSPF_OPAQUE_AS_LSA)) return 0; /* Process only Extended Link LSA */ if (GET_OPAQUE_TYPE(ntohl(lsa->data->id.s_addr)) != OPAQUE_TYPE_EXTENDED_LINK_LSA) return 0; /* Check if Extended is enable */ if (!OspfEXT.enabled) return 0; /* Call Segment Routing LSA update or deletion */ if (!IS_LSA_MAXAGE(lsa)) ospf_sr_ext_link_lsa_update(lsa); else ospf_sr_ext_link_lsa_delete(lsa); return 0; } /* Callbacks to handle Extended Prefix Segment Routing LSA information */ static int ospf_ext_pref_lsa_update(struct ospf_lsa *lsa) { /* Sanity Check */ if (lsa == NULL) { flog_warn(EC_OSPF_LSA_NULL, "EXT (%s): Abort! LSA is NULL", __func__); return -1; } /* Process only Opaque LSA */ if ((lsa->data->type != OSPF_OPAQUE_AREA_LSA) && (lsa->data->type != OSPF_OPAQUE_AS_LSA)) return 0; /* Process only Extended Prefix LSA */ if (GET_OPAQUE_TYPE(ntohl(lsa->data->id.s_addr)) != OPAQUE_TYPE_EXTENDED_PREFIX_LSA) return 0; /* Check if it is not my LSA */ if (IS_LSA_SELF(lsa)) return 0; /* Check if Extended is enable */ if (!OspfEXT.enabled) return 0; /* Call Segment Routing LSA update or deletion */ if (!IS_LSA_MAXAGE(lsa)) ospf_sr_ext_prefix_lsa_update(lsa); else ospf_sr_ext_prefix_lsa_delete(lsa); return 0; } /* * ------------------------------------------------------- * Followings are OSPF protocol processing functions for * Extended Prefix/Link Opaque LSA * ------------------------------------------------------- */ static void build_tlv_header(struct stream *s, struct tlv_header *tlvh) { stream_put(s, tlvh, sizeof(struct tlv_header)); } static void build_tlv(struct stream *s, struct tlv_header *tlvh) { if ((tlvh != NULL) && (ntohs(tlvh->type) != 0)) { build_tlv_header(s, tlvh); stream_put(s, TLV_DATA(tlvh), TLV_BODY_SIZE(tlvh)); } } /* Build an Extended Prefix Opaque LSA body for extended prefix TLV */ static void ospf_ext_pref_lsa_body_set(struct stream *s, struct ext_itf *exti) { /* Sanity check */ if ((exti == NULL) || (exti->stype != PREF_SID)) return; /* Adjust Extended Prefix TLV size */ TLV_LEN(exti->prefix) = htons(ntohs(TLV_LEN(exti->node_sid)) + EXT_TLV_PREFIX_SIZE + TLV_HDR_SIZE); /* Build LSA body for an Extended Prefix TLV */ build_tlv_header(s, &exti->prefix.header); stream_put(s, TLV_DATA(&exti->prefix.header), EXT_TLV_PREFIX_SIZE); /* Then add Prefix SID SubTLV */ build_tlv(s, &exti->node_sid.header); } /* Build an Extended Link Opaque LSA body for extended link TLV */ static void ospf_ext_link_lsa_body_set(struct stream *s, struct ext_itf *exti) { size_t size; /* Sanity check */ if ((exti == NULL) || ((exti->stype != ADJ_SID) && (exti->stype != LAN_ADJ_SID))) return; if (exti->stype == ADJ_SID) { /* Adjust Extended Link TLV size for Adj. SID */ size = EXT_TLV_LINK_SIZE + 2 * EXT_SUBTLV_ADJ_SID_SIZE + 2 * TLV_HDR_SIZE; if (ntohs(TLV_TYPE(exti->rmt_itf_addr)) != 0) size = size + EXT_SUBTLV_RMT_ITF_ADDR_SIZE + TLV_HDR_SIZE; TLV_LEN(exti->link) = htons(size); /* Build LSA body for an Extended Link TLV with Adj. SID */ build_tlv_header(s, &exti->link.header); stream_put(s, TLV_DATA(&exti->link.header), EXT_TLV_LINK_SIZE); /* then add Adjacency SubTLVs */ build_tlv(s, &exti->adj_sid[1].header); build_tlv(s, &exti->adj_sid[0].header); /* Add Cisco experimental SubTLV if interface is PtoP */ if (ntohs(TLV_TYPE(exti->rmt_itf_addr)) != 0) build_tlv(s, &exti->rmt_itf_addr.header); } else { /* Adjust Extended Link TLV size for LAN SID */ size = EXT_TLV_LINK_SIZE + 2 * (EXT_SUBTLV_LAN_ADJ_SID_SIZE + TLV_HDR_SIZE); TLV_LEN(exti->link) = htons(size); /* Build LSA body for an Extended Link TLV with LAN SID */ build_tlv_header(s, &exti->link.header); stream_put(s, TLV_DATA(&exti->link.header), EXT_TLV_LINK_SIZE); /* then add LAN-Adjacency SubTLVs */ build_tlv(s, &exti->lan_sid[1].header); build_tlv(s, &exti->lan_sid[0].header); } } /* Create new Extended Prefix opaque-LSA for every extended prefix */ static struct ospf_lsa *ospf_ext_pref_lsa_new(struct ospf_area *area, struct ext_itf *exti) { struct stream *s; struct lsa_header *lsah; struct ospf_lsa *new = NULL; struct ospf *top; uint8_t options, lsa_type; struct in_addr lsa_id; struct in_addr router_id; uint32_t tmp; uint16_t length; /* Sanity Check */ if (exti == NULL) return NULL; /* Create a stream for LSA. */ s = stream_new(OSPF_MAX_LSA_SIZE); /* Prepare LSA Header */ lsah = (struct lsa_header *)STREAM_DATA(s); lsa_type = OspfEXT.scope; /* * LSA ID is a variable number identifying different instances of * Extended Prefix Opaque LSA from the same router see RFC 7684 */ tmp = SET_OPAQUE_LSID(OPAQUE_TYPE_EXTENDED_PREFIX_LSA, exti->instance); lsa_id.s_addr = htonl(tmp); options = OSPF_OPTION_O; /* Don't forget this :-) */ /* Fix Options and Router ID depending of the flooding scope */ if ((OspfEXT.scope == OSPF_OPAQUE_AS_LSA) || (area == NULL)) { options = OSPF_OPTION_E; top = ospf_lookup_by_vrf_id(VRF_DEFAULT); router_id.s_addr = top ? top->router_id.s_addr : 0; } else { options |= LSA_OPTIONS_GET(area); /* Get area default option */ options |= LSA_OPTIONS_NSSA_GET(area); router_id = area->ospf->router_id; } /* Set opaque-LSA header fields. */ lsa_header_set(s, options, lsa_type, lsa_id, router_id); if (IS_DEBUG_OSPF(lsa, LSA_GENERATE)) zlog_debug( "EXT (%s): LSA[Type%u:%s]: Create an Opaque-LSA " "Extended Prefix Opaque LSA instance", __func__, lsa_type, inet_ntoa(lsa_id)); /* Set opaque-LSA body fields. */ ospf_ext_pref_lsa_body_set(s, exti); /* Set length. */ length = stream_get_endp(s); lsah->length = htons(length); /* Now, create an OSPF LSA instance. */ new = ospf_lsa_new_and_data(length); /* Segment Routing belongs only to default VRF */ new->vrf_id = VRF_DEFAULT; new->area = area; SET_FLAG(new->flags, OSPF_LSA_SELF); memcpy(new->data, lsah, length); stream_free(s); return new; } /* Create new Extended Link opaque-LSA for every extended link TLV */ static struct ospf_lsa *ospf_ext_link_lsa_new(struct ospf_area *area, struct ext_itf *exti) { struct stream *s; struct lsa_header *lsah; struct ospf_lsa *new = NULL; uint8_t options, lsa_type; struct in_addr lsa_id; uint32_t tmp; uint16_t length; /* Sanity Check */ if (exti == NULL) return NULL; /* Create a stream for LSA. */ s = stream_new(OSPF_MAX_LSA_SIZE); lsah = (struct lsa_header *)STREAM_DATA(s); options = OSPF_OPTION_O; /* Don't forget this :-) */ options |= LSA_OPTIONS_GET(area); /* Get area default option */ options |= LSA_OPTIONS_NSSA_GET(area); /* Extended Link Opaque LSA are only flooded within an area */ lsa_type = OSPF_OPAQUE_AREA_LSA; /* * LSA ID is a variable number identifying different instances of * Extended Link Opaque LSA from the same router see RFC 7684 */ tmp = SET_OPAQUE_LSID(OPAQUE_TYPE_EXTENDED_LINK_LSA, exti->instance); lsa_id.s_addr = htonl(tmp); if (IS_DEBUG_OSPF(lsa, LSA_GENERATE)) zlog_debug( "EXT (%s) LSA[Type%u:%s]: Create an Opaque-LSA " "Extended Link Opaque LSA instance", __func__, lsa_type, inet_ntoa(lsa_id)); /* Set opaque-LSA header fields. */ lsa_header_set(s, options, lsa_type, lsa_id, area->ospf->router_id); /* Set opaque-LSA body fields. */ ospf_ext_link_lsa_body_set(s, exti); /* Set length. */ length = stream_get_endp(s); lsah->length = htons(length); /* Now, create an OSPF LSA instance. */ new = ospf_lsa_new_and_data(length); /* Segment Routing belongs only to default VRF */ new->vrf_id = VRF_DEFAULT; new->area = area; SET_FLAG(new->flags, OSPF_LSA_SELF); memcpy(new->data, lsah, length); stream_free(s); return new; } /* * Process the origination of an Extended Prefix Opaque LSA * for every extended prefix TLV */ static int ospf_ext_pref_lsa_originate1(struct ospf_area *area, struct ext_itf *exti) { struct ospf_lsa *new; int rc = -1; /* Create new Opaque-LSA/Extended Prefix Opaque LSA instance. */ new = ospf_ext_pref_lsa_new(area, exti); if (new == NULL) { flog_warn(EC_OSPF_EXT_LSA_UNEXPECTED, "EXT (%s): ospf_ext_pref_lsa_new() error", __func__); return rc; } /* Install this LSA into LSDB. */ if (ospf_lsa_install(area->ospf, NULL /*oi */, new) == NULL) { flog_warn(EC_OSPF_LSA_INSTALL_FAILURE, "EXT (%s): ospf_lsa_install() error", __func__); ospf_lsa_unlock(&new); return rc; } /* Now this Extended Prefix Opaque LSA info parameter entry has * associated LSA. */ SET_FLAG(exti->flags, EXT_LPFLG_LSA_ENGAGED); /* Update new LSA origination count. */ area->ospf->lsa_originate_count++; /* Flood new LSA through area. */ ospf_flood_through_area(area, NULL /*nbr */, new); if (IS_DEBUG_OSPF(lsa, LSA_GENERATE)) { char area_id[INET_ADDRSTRLEN]; inet_ntop(AF_INET, &area->area_id, area_id, sizeof(area_id)); zlog_debug( "EXT (%s): LSA[Type%u:%s]: Originate Opaque-LSA " "Extended Prefix Opaque LSA: Area(%s), Link(%s)", __func__, new->data->type, inet_ntoa(new->data->id), area_id, exti->ifp->name); ospf_lsa_header_dump(new->data); } rc = 0; return rc; } /* * Process the origination of an Extended Link Opaque LSA * for every extended link TLV */ static int ospf_ext_link_lsa_originate1(struct ospf_area *area, struct ext_itf *exti) { struct ospf_lsa *new; int rc = -1; /* Create new Opaque-LSA/Extended Link Opaque LSA instance. */ new = ospf_ext_link_lsa_new(area, exti); if (new == NULL) { flog_warn(EC_OSPF_EXT_LSA_UNEXPECTED, "EXT (%s): ospf_ext_link_lsa_new() error", __func__); return rc; } /* Install this LSA into LSDB. */ if (ospf_lsa_install(area->ospf, NULL /*oi */, new) == NULL) { flog_warn(EC_OSPF_LSA_INSTALL_FAILURE, "EXT (%s): ospf_lsa_install() error", __func__); ospf_lsa_unlock(&new); return rc; } /* Now this link-parameter entry has associated LSA. */ SET_FLAG(exti->flags, EXT_LPFLG_LSA_ENGAGED); /* Update new LSA origination count. */ area->ospf->lsa_originate_count++; /* Flood new LSA through area. */ ospf_flood_through_area(area, NULL /*nbr */, new); if (IS_DEBUG_OSPF(lsa, LSA_GENERATE)) { char area_id[INET_ADDRSTRLEN]; inet_ntop(AF_INET, &area->area_id, area_id, sizeof(area_id)); zlog_debug( "EXT (%s): LSA[Type%u:%s]: Originate Opaque-LSA " "Extended Link Opaque LSA: Area(%s), Link(%s)", __func__, new->data->type, inet_ntoa(new->data->id), area_id, exti->ifp->name); ospf_lsa_header_dump(new->data); } rc = 0; return rc; } /* Trigger the origination of Extended Prefix Opaque LSAs */ static int ospf_ext_pref_lsa_originate(void *arg) { struct ospf_area *area = (struct ospf_area *)arg; struct listnode *node; struct ext_itf *exti; int rc = -1; if (!OspfEXT.enabled) { zlog_info( "EXT (%s): Segment Routing " "functionality is Disabled now", __func__); rc = 0; /* This is not an error case. */ return rc; } if (IS_DEBUG_OSPF_SR) zlog_debug("EXT (%s): Start Originate Prefix LSA for area %s", __func__, inet_ntoa(area->area_id)); /* Check if Extended Prefix Opaque LSA is already engaged */ for (ALL_LIST_ELEMENTS_RO(OspfEXT.iflist, node, exti)) { /* Process only Prefix SID */ if (exti->stype != PREF_SID) continue; /* Process only Extended Prefix with valid Area ID */ if ((exti->area == NULL) || (!IPV4_ADDR_SAME(&exti->area->area_id, &area->area_id))) continue; if (CHECK_FLAG(exti->flags, EXT_LPFLG_LSA_ENGAGED)) { if (CHECK_FLAG(exti->flags, EXT_LPFLG_LSA_FORCED_REFRESH)) { flog_warn( EC_OSPF_EXT_LSA_UNEXPECTED, "EXT (%s): Refresh instead of Originate", __func__); UNSET_FLAG(exti->flags, EXT_LPFLG_LSA_FORCED_REFRESH); ospf_ext_pref_lsa_schedule(exti, REFRESH_THIS_LSA); } continue; } /* Ok, let's try to originate an LSA */ if (IS_DEBUG_OSPF_SR) zlog_debug( "EXT (%s): Let's finally reoriginate the " "LSA 7.0.0.%u for Itf %s", __func__, exti->instance, exti->ifp ? exti->ifp->name : ""); ospf_ext_pref_lsa_originate1(area, exti); } rc = 0; return rc; } /* Trigger the origination of Extended Link Opaque LSAs */ static int ospf_ext_link_lsa_originate(void *arg) { struct ospf_area *area = (struct ospf_area *)arg; struct listnode *node; struct ext_itf *exti; int rc = -1; if (!OspfEXT.enabled) { zlog_info( "EXT (%s): Segment Routing " "functionality is Disabled now", __func__); rc = 0; /* This is not an error case. */ return rc; } /* Check if Extended Prefix Opaque LSA is already engaged */ for (ALL_LIST_ELEMENTS_RO(OspfEXT.iflist, node, exti)) { /* Process only Adjacency or LAN SID */ if (exti->stype == PREF_SID) continue; /* Process only Extended Link with valid Area ID */ if ((exti->area == NULL) || (!IPV4_ADDR_SAME(&exti->area->area_id, &area->area_id))) continue; /* Check if LSA not already engaged */ if (CHECK_FLAG(exti->flags, EXT_LPFLG_LSA_ENGAGED)) { if (CHECK_FLAG(exti->flags, EXT_LPFLG_LSA_FORCED_REFRESH)) { flog_warn( EC_OSPF_EXT_LSA_UNEXPECTED, "EXT (%s): Refresh instead of Originate", __func__); UNSET_FLAG(exti->flags, EXT_LPFLG_LSA_FORCED_REFRESH); ospf_ext_link_lsa_schedule(exti, REFRESH_THIS_LSA); } continue; } /* Ok, let's try to originate an LSA */ if (IS_DEBUG_OSPF_SR) zlog_debug( "EXT (%s): Let's finally reoriginate the " "LSA 8.0.0.%u for Itf %s through the Area %s", __func__, exti->instance, exti->ifp ? exti->ifp->name : "-", inet_ntoa(area->area_id)); ospf_ext_link_lsa_originate1(area, exti); } rc = 0; return rc; } /* Refresh an Extended Prefix Opaque LSA */ static struct ospf_lsa *ospf_ext_pref_lsa_refresh(struct ospf_lsa *lsa) { struct ospf_lsa *new = NULL; struct ospf_area *area = lsa->area; struct ospf *top; struct ext_itf *exti; if (!OspfEXT.enabled) { /* * This LSA must have flushed before due to Extended Prefix * Opaque LSA status change. * It seems a slip among routers in the routing domain. */ zlog_info( "EXT (%s): Segment Routing functionality is " "Disabled", __func__); /* Flush it anyway. */ lsa->data->ls_age = htons(OSPF_LSA_MAXAGE); } /* Lookup this lsa corresponding Extended parameters */ exti = lookup_ext_by_instance(lsa); if (exti == NULL) { flog_warn(EC_OSPF_EXT_LSA_UNEXPECTED, "EXT (%s): Invalid parameter LSA ID", __func__); /* Flush it anyway. */ lsa->data->ls_age = htons(OSPF_LSA_MAXAGE); } /* Check if Interface was not disable in the interval */ if ((exti != NULL) && !CHECK_FLAG(exti->flags, EXT_LPFLG_LSA_ACTIVE)) { flog_warn(EC_OSPF_EXT_LSA_UNEXPECTED, "EXT (%s): Interface was Disabled: Flush it!", __func__); /* Flush it anyway. */ lsa->data->ls_age = htons(OSPF_LSA_MAXAGE); } /* If the lsa's age reached to MaxAge, start flushing procedure. */ if (IS_LSA_MAXAGE(lsa)) { if (exti) UNSET_FLAG(exti->flags, EXT_LPFLG_LSA_ENGAGED); ospf_opaque_lsa_flush_schedule(lsa); return NULL; } /* Create new Opaque-LSA/Extended Prefix Opaque LSA instance. */ new = ospf_ext_pref_lsa_new(area, exti); if (new == NULL) { flog_warn(EC_OSPF_EXT_LSA_UNEXPECTED, "EXT (%s): ospf_ext_pref_lsa_new() error", __func__); return NULL; } new->data->ls_seqnum = lsa_seqnum_increment(lsa); /* * Install this LSA into LSDB * Given "lsa" will be freed in the next function * As area could be NULL i.e. when using OPAQUE_LSA_AS, we prefer to use * ospf_lookup() to get ospf instance */ if (area) top = area->ospf; else top = ospf_lookup_by_vrf_id(VRF_DEFAULT); if (ospf_lsa_install(top, NULL /*oi */, new) == NULL) { flog_warn(EC_OSPF_LSA_INSTALL_FAILURE, "EXT (%s): ospf_lsa_install() error", __func__); ospf_lsa_unlock(&new); return NULL; } /* Flood updated LSA through the Prefix Area according to the RFC7684 */ ospf_flood_through_area(area, NULL /*nbr */, new); /* Debug logging. */ if (IS_DEBUG_OSPF(lsa, LSA_GENERATE)) { zlog_debug( "EXT (%s): LSA[Type%u:%s] Refresh Extended Prefix LSA", __func__, new->data->type, inet_ntoa(new->data->id)); ospf_lsa_header_dump(new->data); } return new; } /* Refresh an Extended Link Opaque LSA */ static struct ospf_lsa *ospf_ext_link_lsa_refresh(struct ospf_lsa *lsa) { struct ext_itf *exti; struct ospf_area *area = lsa->area; struct ospf *top = area->ospf; struct ospf_lsa *new = NULL; if (!OspfEXT.enabled) { /* * This LSA must have flushed before due to OSPF-SR status * change. It seems a slip among routers in the routing domain. */ zlog_info("EXT (%s): Segment Routing functionality is Disabled", __func__); /* Flush it anyway. */ lsa->data->ls_age = htons(OSPF_LSA_MAXAGE); } /* Lookup this LSA corresponding Extended parameters */ exti = lookup_ext_by_instance(lsa); if (exti == NULL) { flog_warn(EC_OSPF_EXT_LSA_UNEXPECTED, "EXT (%s): Invalid parameter LSA ID", __func__); /* Flush it anyway. */ lsa->data->ls_age = htons(OSPF_LSA_MAXAGE); } /* Check if Interface was not disable in the interval */ if ((exti != NULL) && !CHECK_FLAG(exti->flags, EXT_LPFLG_LSA_ACTIVE)) { flog_warn(EC_OSPF_EXT_LSA_UNEXPECTED, "EXT (%s): Interface was Disabled: Flush it!", __func__); lsa->data->ls_age = htons(OSPF_LSA_MAXAGE); } /* If the lsa's age reached to MaxAge, start flushing procedure */ if (IS_LSA_MAXAGE(lsa)) { if (exti) UNSET_FLAG(exti->flags, EXT_LPFLG_LSA_ENGAGED); ospf_opaque_lsa_flush_schedule(lsa); return NULL; } /* Create new Opaque-LSA/Extended Link instance */ new = ospf_ext_link_lsa_new(area, exti); if (new == NULL) { flog_warn(EC_OSPF_EXT_LSA_UNEXPECTED, "EXT (%s): Error creating new LSA", __func__); return NULL; } new->data->ls_seqnum = lsa_seqnum_increment(lsa); /* Install this LSA into LSDB. */ /* Given "lsa" will be freed in the next function */ if (ospf_lsa_install(top, NULL /*oi */, new) == NULL) { flog_warn(EC_OSPF_LSA_INSTALL_FAILURE, "EXT (%s): Error installing new LSA", __func__); ospf_lsa_unlock(&new); return NULL; } /* Flood updated LSA through the link Area according to the RFC7684 */ ospf_flood_through_area(area, NULL /*nbr */, new); /* Debug logging. */ if (IS_DEBUG_OSPF(lsa, LSA_GENERATE)) { zlog_debug( "EXT (%s): LSA[Type%u:%s]: Refresh Extended Link LSA", __func__, new->data->type, inet_ntoa(new->data->id)); ospf_lsa_header_dump(new->data); } return new; } /* Schedule Extended Prefix Opaque LSA origination/refreshment/flushing */ static void ospf_ext_pref_lsa_schedule(struct ext_itf *exti, enum lsa_opcode opcode) { struct ospf_lsa lsa; struct lsa_header lsah; struct ospf *top; uint32_t tmp; memset(&lsa, 0, sizeof(lsa)); memset(&lsah, 0, sizeof(lsah)); /* Sanity Check */ if (exti == NULL) return; /* Check if the corresponding link is ready to be flooded */ if (!(CHECK_FLAG(exti->flags, EXT_LPFLG_LSA_ACTIVE))) return; zlog_debug("EXT (%s): Schedule %s%s%s LSA for interface %s", __func__, opcode == REORIGINATE_THIS_LSA ? "Re-Originate" : "", opcode == REFRESH_THIS_LSA ? "Refresh" : "", opcode == FLUSH_THIS_LSA ? "Flush" : "", exti->ifp ? exti->ifp->name : "-"); /* Set LSA header information */ if (exti->area == NULL) { flog_warn( EC_OSPF_EXT_LSA_UNEXPECTED, "EXT (%s): Flooding is Area scope but area is not yet set", __func__); if (OspfEXT.area == NULL) { top = ospf_lookup_by_vrf_id(VRF_DEFAULT); OspfEXT.area = ospf_area_lookup_by_area_id( top, OspfEXT.area_id); } exti->area = OspfEXT.area; } lsa.area = exti->area; lsa.data = &lsah; lsah.type = OSPF_OPAQUE_AREA_LSA; tmp = SET_OPAQUE_LSID(OPAQUE_TYPE_EXTENDED_PREFIX_LSA, exti->instance); lsah.id.s_addr = htonl(tmp); switch (opcode) { case REORIGINATE_THIS_LSA: ospf_opaque_lsa_reoriginate_schedule( (void *)exti->area, OSPF_OPAQUE_AREA_LSA, OPAQUE_TYPE_EXTENDED_PREFIX_LSA); break; case REFRESH_THIS_LSA: ospf_opaque_lsa_refresh_schedule(&lsa); break; case FLUSH_THIS_LSA: UNSET_FLAG(exti->flags, EXT_LPFLG_LSA_ENGAGED); ospf_opaque_lsa_flush_schedule(&lsa); break; } } /* Schedule Extended Link Opaque LSA origination/refreshment/flushing */ static void ospf_ext_link_lsa_schedule(struct ext_itf *exti, enum lsa_opcode opcode) { struct ospf_lsa lsa; struct lsa_header lsah; struct ospf *top; uint32_t tmp; memset(&lsa, 0, sizeof(lsa)); memset(&lsah, 0, sizeof(lsah)); /* Sanity Check */ if (exti == NULL) return; /* Check if the corresponding link is ready to be flooded */ if (!(CHECK_FLAG(exti->flags, EXT_LPFLG_LSA_ACTIVE))) return; zlog_debug("EXT (%s): Schedule %s%s%s LSA for interface %s", __func__, opcode == REORIGINATE_THIS_LSA ? "Re-Originate" : "", opcode == REFRESH_THIS_LSA ? "Refresh" : "", opcode == FLUSH_THIS_LSA ? "Flush" : "", exti->ifp ? exti->ifp->name : "-"); /* Set LSA header information */ if (exti->area == NULL) { flog_warn( EC_OSPF_EXT_LSA_UNEXPECTED, "EXT (%s): Flooding is Area scope but area is not yet set", __func__); if (OspfEXT.area == NULL) { top = ospf_lookup_by_vrf_id(VRF_DEFAULT); OspfEXT.area = ospf_area_lookup_by_area_id( top, OspfEXT.area_id); } exti->area = OspfEXT.area; } lsa.area = exti->area; lsa.data = &lsah; lsah.type = OSPF_OPAQUE_AREA_LSA; tmp = SET_OPAQUE_LSID(OPAQUE_TYPE_EXTENDED_LINK_LSA, exti->instance); lsah.id.s_addr = htonl(tmp); switch (opcode) { case REORIGINATE_THIS_LSA: ospf_opaque_lsa_reoriginate_schedule( (void *)exti->area, OSPF_OPAQUE_AREA_LSA, OPAQUE_TYPE_EXTENDED_LINK_LSA); break; case REFRESH_THIS_LSA: ospf_opaque_lsa_refresh_schedule(&lsa); break; case FLUSH_THIS_LSA: UNSET_FLAG(exti->flags, EXT_LPFLG_LSA_ENGAGED); ospf_opaque_lsa_flush_schedule(&lsa); break; } } /* Schedule Extended Link or Prefix depending of the Type of LSA */ static void ospf_ext_lsa_schedule(struct ext_itf *exti, enum lsa_opcode op) { if (exti->stype == PREF_SID) ospf_ext_pref_lsa_schedule(exti, op); else ospf_ext_link_lsa_schedule(exti, op); } /* * ------------------------------------ * Followings are vty show functions. * ------------------------------------ */ /* Cisco experimental SubTLV */ static uint16_t show_vty_ext_link_rmt_itf_addr(struct vty *vty, struct tlv_header *tlvh) { struct ext_subtlv_rmt_itf_addr *top; top = (struct ext_subtlv_rmt_itf_addr *)tlvh; vty_out(vty, " Remote Interface Address Sub-TLV: Length %u\n " "Address: %s\n", ntohs(top->header.length), inet_ntoa(top->value)); return TLV_SIZE(tlvh); } /* Adjacency SID SubTLV */ static uint16_t show_vty_ext_link_adj_sid(struct vty *vty, struct tlv_header *tlvh) { struct ext_subtlv_adj_sid *top = (struct ext_subtlv_adj_sid *)tlvh; vty_out(vty, " Adj-SID Sub-TLV: Length %u\n\tFlags: " "0x%x\n\tMT-ID:0x%x\n\tWeight: 0x%x\n\t%s: %u\n", ntohs(top->header.length), top->flags, top->mtid, top->weight, CHECK_FLAG(top->flags, EXT_SUBTLV_LINK_ADJ_SID_VFLG) ? "Label" : "Index", CHECK_FLAG(top->flags, EXT_SUBTLV_LINK_ADJ_SID_VFLG) ? GET_LABEL(ntohl(top->value)) : ntohl(top->value)); return TLV_SIZE(tlvh); } /* LAN Adjacency SubTLV */ static uint16_t show_vty_ext_link_lan_adj_sid(struct vty *vty, struct tlv_header *tlvh) { struct ext_subtlv_lan_adj_sid *top = (struct ext_subtlv_lan_adj_sid *)tlvh; vty_out(vty, " LAN-Adj-SID Sub-TLV: Length %u\n\tFlags: " "0x%x\n\tMT-ID:0x%x\n\tWeight: 0x%x\n\tNeighbor ID: " "%s\n\t%s: %u\n", ntohs(top->header.length), top->flags, top->mtid, top->weight, inet_ntoa(top->neighbor_id), CHECK_FLAG(top->flags, EXT_SUBTLV_LINK_ADJ_SID_VFLG) ? "Label" : "Index", CHECK_FLAG(top->flags, EXT_SUBTLV_LINK_ADJ_SID_VFLG) ? GET_LABEL(ntohl(top->value)) : ntohl(top->value)); return TLV_SIZE(tlvh); } static uint16_t show_vty_unknown_tlv(struct vty *vty, struct tlv_header *tlvh) { vty_out(vty, " Unknown TLV: [type(0x%x), length(0x%x)]\n", ntohs(tlvh->type), ntohs(tlvh->length)); return TLV_SIZE(tlvh); } /* Extended Link Sub TLVs */ static uint16_t show_vty_link_info(struct vty *vty, struct tlv_header *ext) { struct ext_tlv_link *top = (struct ext_tlv_link *)ext; struct tlv_header *tlvh; uint16_t length = ntohs(top->header.length) - 3 * sizeof(uint32_t); uint16_t sum = 0; vty_out(vty, " Extended Link TLV: Length %u\n Link Type: 0x%x\n" " Link ID: %s\n", ntohs(top->header.length), top->link_type, inet_ntoa(top->link_id)); vty_out(vty, " Link data: %s\n", inet_ntoa(top->link_data)); tlvh = (struct tlv_header *)((char *)(ext) + TLV_HDR_SIZE + EXT_TLV_LINK_SIZE); for (; sum < length; tlvh = TLV_HDR_NEXT(tlvh)) { switch (ntohs(tlvh->type)) { case EXT_SUBTLV_ADJ_SID: sum += show_vty_ext_link_adj_sid(vty, tlvh); break; case EXT_SUBTLV_LAN_ADJ_SID: sum += show_vty_ext_link_lan_adj_sid(vty, tlvh); break; case EXT_SUBTLV_RMT_ITF_ADDR: sum += show_vty_ext_link_rmt_itf_addr(vty, tlvh); break; default: sum += show_vty_unknown_tlv(vty, tlvh); break; } } return sum + sizeof(struct ext_tlv_link); } /* Extended Link TLVs */ static void ospf_ext_link_show_info(struct vty *vty, struct ospf_lsa *lsa) { struct lsa_header *lsah = (struct lsa_header *)lsa->data; struct tlv_header *tlvh; uint16_t length = 0, sum = 0; /* Initialize TLV browsing */ length = ntohs(lsah->length) - OSPF_LSA_HEADER_SIZE; for (tlvh = TLV_HDR_TOP(lsah); sum < length; tlvh = TLV_HDR_NEXT(tlvh)) { switch (ntohs(tlvh->type)) { case EXT_TLV_LINK: sum += show_vty_link_info(vty, tlvh); break; default: sum += show_vty_unknown_tlv(vty, tlvh); break; } } } /* Prefix SID SubTLV */ static uint16_t show_vty_ext_pref_pref_sid(struct vty *vty, struct tlv_header *tlvh) { struct ext_subtlv_prefix_sid *top = (struct ext_subtlv_prefix_sid *)tlvh; vty_out(vty, " Prefix SID Sub-TLV: Length %u\n\tAlgorithm: " "%u\n\tFlags: 0x%x\n\tMT-ID:0x%x\n\t%s: %u\n", ntohs(top->header.length), top->algorithm, top->flags, top->mtid, CHECK_FLAG(top->flags, EXT_SUBTLV_PREFIX_SID_VFLG) ? "Label" : "Index", CHECK_FLAG(top->flags, EXT_SUBTLV_PREFIX_SID_VFLG) ? GET_LABEL(ntohl(top->value)) : ntohl(top->value)); return TLV_SIZE(tlvh); } /* Extended Prefix SubTLVs */ static uint16_t show_vty_pref_info(struct vty *vty, struct tlv_header *ext) { struct ext_tlv_prefix *top = (struct ext_tlv_prefix *)ext; struct tlv_header *tlvh; uint16_t length = ntohs(top->header.length) - 2 * sizeof(uint32_t); uint16_t sum = 0; vty_out(vty, " Extended Prefix TLV: Length %u\n\tRoute Type: %u\n" "\tAddress Family: 0x%x\n\tFlags: 0x%x\n\tAddress: %s/%u\n", ntohs(top->header.length), top->route_type, top->af, top->flags, inet_ntoa(top->address), top->pref_length); tlvh = (struct tlv_header *)((char *)(ext) + TLV_HDR_SIZE + EXT_TLV_PREFIX_SIZE); for (; sum < length; tlvh = TLV_HDR_NEXT(tlvh)) { switch (ntohs(tlvh->type)) { case EXT_SUBTLV_PREFIX_SID: sum += show_vty_ext_pref_pref_sid(vty, tlvh); break; default: sum += show_vty_unknown_tlv(vty, tlvh); break; } } return sum + sizeof(struct ext_tlv_prefix); } /* Extended Prefix TLVs */ static void ospf_ext_pref_show_info(struct vty *vty, struct ospf_lsa *lsa) { struct lsa_header *lsah = (struct lsa_header *)lsa->data; struct tlv_header *tlvh; uint16_t length = 0, sum = 0; /* Initialize TLV browsing */ length = ntohs(lsah->length) - OSPF_LSA_HEADER_SIZE; for (tlvh = TLV_HDR_TOP(lsah); sum < length; tlvh = TLV_HDR_NEXT(tlvh)) { switch (ntohs(tlvh->type)) { case EXT_TLV_PREFIX: sum += show_vty_pref_info(vty, tlvh); break; default: sum += show_vty_unknown_tlv(vty, tlvh); break; } } } frr-7.2.1/ospfd/ospf_ext.h0000644000000000000000000001436713610377563012342 00000000000000/* * This is an implementation of RFC7684 OSPFv2 Prefix/Link Attribute * Advertisement * * Module name: Extended Prefix/Link Opaque LSA header definition * * Author: Olivier Dugeon * Author: Anselme Sawadogo * * Copyright (C) 2016 - 2018 Orange Labs http://www.orange.com * * This program is free software; you can redistribute it and/or modify it * under the terms of the GNU General Public License as published by the Free * Software Foundation; either version 2 of the License, or (at your option) * any later version. * * This program is distributed in the hope that it will be useful, but WITHOUT * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for * more details. * * You should have received a copy of the GNU General Public License along * with this program; see the file COPYING; if not, write to the Free Software * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA */ #ifndef _FRR_OSPF_EXT_PREF_H_ #define _FRR_OSPF_EXT_PREF_H_ /* * Opaque LSA's link state ID for Extended Prefix/Link is * structured as follows. * * 24 16 8 0 * +--------+--------+--------+--------+ * | 7/8 |........|........|........| * +--------+--------+--------+--------+ * |<-Type->|<------- Instance ------->| * * * Type: IANA has assigned '7' for Extended Prefix Opaque LSA * and '8' for Extended Link Opaque LSA * Instance: User may select arbitrary 24-bit values to identify * different instances of Extended Prefix/Link Opaque LSA * */ /* * 24 16 8 0 * +--------+--------+--------+--------+ --- * | LS age |Options | 10,11 | A * +--------+--------+--------+--------+ | Standard (Opaque) LSA header; * | 7/8 | Instance | | * +--------+--------+--------+--------+ | Type 10 or 11 are used for Extended * | Advertising router | | Prefix Opaque LSA * +--------+--------+--------+--------+ | * | LS sequence number | | Type 10 only is used for Extended * +--------+--------+--------+--------+ | Link Opaque LSA * | LS checksum | Length | V * +--------+--------+--------+--------+ --- * | Type | Length | A * +--------+--------+--------+--------+ | TLV part for Extended Prefix/Link * | | | Opaque LSA; * ~ Values ... ~ | Values might be structured as a set * | | V of sub-TLVs. * +--------+--------+--------+--------+ --- */ /* Global use constant numbers */ #define MAX_LEGAL_EXT_INSTANCE_NUM (0xffff) #define LEGAL_EXT_INSTANCE_RANGE(i) (0 <= (i) && (i) <= 0xffff) /* Flags to manage Extended Link/Prefix Opaque LSA */ #define EXT_LPFLG_LSA_INACTIVE 0x00 #define EXT_LPFLG_LSA_ACTIVE 0x01 #define EXT_LPFLG_LSA_ENGAGED 0x02 #define EXT_LPFLG_LSA_LOOKUP_DONE 0x04 #define EXT_LPFLG_LSA_FORCED_REFRESH 0x08 #define EXT_LPFLG_FIB_ENTRY_SET 0x10 /* * Following section defines TLV (tag, length, value) structures, * used in Extended Prefix/Link Opaque LSA. */ /* Extended Prefix TLV Route Types */ #define EXT_TLV_PREF_ROUTE_UNSPEC 0 #define EXT_TLV_PREF_ROUTE_INTRA_AREA 1 #define EXT_TLV_PREF_ROUTE_INTER_AREA 3 #define EXT_TLV_PREF_ROUTE_AS_EXT 5 #define EXT_TLV_PREF_ROUTE_NSSA_EXT 7 /* * Extended Prefix and Extended Prefix Range TLVs' * Address family flag for IPv4 */ #define EXT_TLV_PREF_AF_IPV4 0 /* Extended Prefix TLV Flags */ #define EXT_TLV_PREF_AFLG 0x80 #define EXT_TLV_PREF_NFLG 0x40 /* Extended Prefix Range TLV Flags */ #define EXT_TLV_PREF_RANGE_IAFLG 0x80 /* ERO subtlvs Flags */ #define EXT_SUBTLV_ERO_LFLG 0x80 /* Extended Prefix TLV see RFC 7684 section 2.1 */ #define EXT_TLV_PREFIX 1 #define EXT_TLV_PREFIX_SIZE 8 struct ext_tlv_prefix { struct tlv_header header; uint8_t route_type; uint8_t pref_length; uint8_t af; uint8_t flags; struct in_addr address; }; /* Extended Link TLV see RFC 7684 section 3.1 */ #define EXT_TLV_LINK 1 #define EXT_TLV_LINK_SIZE 12 struct ext_tlv_link { struct tlv_header header; uint8_t link_type; uint8_t reserved[3]; struct in_addr link_id; struct in_addr link_data; }; /* Remote Interface Address Sub-TLV, Cisco experimental use Sub-TLV */ #define EXT_SUBTLV_RMT_ITF_ADDR 32768 #define EXT_SUBTLV_RMT_ITF_ADDR_SIZE 4 struct ext_subtlv_rmt_itf_addr { struct tlv_header header; struct in_addr value; }; /* Internal structure to manage Extended Link/Prefix Opaque LSA */ struct ospf_ext_lp { bool enabled; /* Flags to manage this Extended Prefix/Link Opaque LSA */ uint32_t flags; /* * Scope is area Opaque Type 10 or AS Opaque LSA Type 11 for * Extended Prefix and area Opaque Type 10 for Extended Link */ uint8_t scope; /* area pointer if flooding is Type 10 Null if flooding is AS scope */ struct ospf_area *area; struct in_addr area_id; /* List of interface with Segment Routing enable */ struct list *iflist; }; /* Structure to aggregate interfaces information for Extended Prefix/Link */ struct ext_itf { /* 24-bit Opaque-ID field value according to RFC 7684 specification */ uint32_t instance; uint8_t type; /* Extended Prefix (7) or Link (8) */ /* Reference pointer to a Zebra-interface. */ struct interface *ifp; /* Area info in which this SR link belongs to. */ struct ospf_area *area; /* Flags to manage this link parameters. */ uint32_t flags; /* SID type: Node, Adjacency or LAN Adjacency */ enum sid_type stype; /* extended link/prefix TLV information */ struct ext_tlv_prefix prefix; struct ext_subtlv_prefix_sid node_sid; struct ext_tlv_link link; struct ext_subtlv_adj_sid adj_sid[2]; struct ext_subtlv_lan_adj_sid lan_sid[2]; /* cisco experimental subtlv */ struct ext_subtlv_rmt_itf_addr rmt_itf_addr; }; /* Prototypes. */ extern int ospf_ext_init(void); extern void ospf_ext_term(void); extern void ospf_ext_finish(void); extern void ospf_ext_update_sr(bool enable); extern uint32_t ospf_ext_schedule_prefix_index(struct interface *ifp, uint32_t index, struct prefix_ipv4 *p, uint8_t flags); #endif /* _FRR_OSPF_EXT_PREF_H_ */ frr-7.2.1/ospfd/ospf_flood.c0000644000000000000000000007323513610377563012637 00000000000000/* * OSPF Flooding -- RFC2328 Section 13. * Copyright (C) 1999, 2000 Toshiaki Takada * * This file is part of GNU Zebra. * * GNU Zebra is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License as published * by the Free Software Foundation; either version 2, or (at your * option) any later version. * * GNU Zebra 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; see the file COPYING; if not, write to the Free Software * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA */ #include #include "monotime.h" #include "linklist.h" #include "prefix.h" #include "if.h" #include "command.h" #include "table.h" #include "thread.h" #include "memory.h" #include "log.h" #include "zclient.h" #include "ospfd/ospfd.h" #include "ospfd/ospf_interface.h" #include "ospfd/ospf_ism.h" #include "ospfd/ospf_asbr.h" #include "ospfd/ospf_lsa.h" #include "ospfd/ospf_lsdb.h" #include "ospfd/ospf_neighbor.h" #include "ospfd/ospf_nsm.h" #include "ospfd/ospf_spf.h" #include "ospfd/ospf_flood.h" #include "ospfd/ospf_packet.h" #include "ospfd/ospf_abr.h" #include "ospfd/ospf_route.h" #include "ospfd/ospf_zebra.h" #include "ospfd/ospf_dump.h" extern struct zclient *zclient; /* Do the LSA acking specified in table 19, Section 13.5, row 2 * This get called from ospf_flood_out_interface. Declared inline * for speed. */ static void ospf_flood_delayed_lsa_ack(struct ospf_neighbor *inbr, struct ospf_lsa *lsa) { /* LSA is more recent than database copy, but was not flooded back out receiving interface. Delayed acknowledgment sent. If interface is in Backup state delayed acknowledgment sent only if advertisement received from Designated Router, otherwise do nothing See RFC 2328 Section 13.5 */ /* Whether LSA is more recent or not, and whether this is in response to the LSA being sent out recieving interface has been worked out previously */ /* Deal with router as BDR */ if (inbr->oi->state == ISM_Backup && !NBR_IS_DR(inbr)) return; /* Schedule a delayed LSA Ack to be sent */ listnode_add(inbr->oi->ls_ack, ospf_lsa_lock(lsa)); /* delayed LSA Ack */ } /* Check LSA is related to external info. */ struct external_info *ospf_external_info_check(struct ospf *ospf, struct ospf_lsa *lsa) { struct as_external_lsa *al; struct prefix_ipv4 p; struct route_node *rn; struct list *ext_list; struct listnode *node; struct ospf_external *ext; int type; al = (struct as_external_lsa *)lsa->data; p.family = AF_INET; p.prefix = lsa->data->id; p.prefixlen = ip_masklen(al->mask); for (type = 0; type < ZEBRA_ROUTE_MAX; type++) { int redist_on = 0; redist_on = is_prefix_default(&p) ? vrf_bitmap_check( zclient->default_information[AFI_IP], ospf->vrf_id) : (zclient->mi_redist[AFI_IP][type].enabled || vrf_bitmap_check( zclient->redist[AFI_IP][type], ospf->vrf_id)); // Pending: check for MI above. if (redist_on) { ext_list = ospf->external[type]; if (!ext_list) continue; for (ALL_LIST_ELEMENTS_RO(ext_list, node, ext)) { rn = NULL; if (ext->external_info) rn = route_node_lookup( ext->external_info, (struct prefix *)&p); if (rn) { route_unlock_node(rn); if (rn->info != NULL) return (struct external_info *) rn->info; } } } } if (is_prefix_default(&p) && ospf->external[DEFAULT_ROUTE]) { ext_list = ospf->external[DEFAULT_ROUTE]; for (ALL_LIST_ELEMENTS_RO(ext_list, node, ext)) { if (!ext->external_info) continue; rn = route_node_lookup(ext->external_info, (struct prefix *)&p); if (!rn) continue; route_unlock_node(rn); if (rn->info != NULL) return (struct external_info *)rn->info; } } return NULL; } static void ospf_process_self_originated_lsa(struct ospf *ospf, struct ospf_lsa *new, struct ospf_area *area) { struct ospf_interface *oi; struct external_info *ei; struct listnode *node; if (IS_DEBUG_OSPF_EVENT) zlog_debug( "LSA[Type%d:%s]: Process self-originated LSA seq 0x%x", new->data->type, inet_ntoa(new->data->id), ntohl(new->data->ls_seqnum)); /* If we're here, we installed a self-originated LSA that we received from a neighbor, i.e. it's more recent. We must see whether we want to originate it. If yes, we should use this LSA's sequence number and reoriginate a new instance. if not --- we must flush this LSA from the domain. */ switch (new->data->type) { case OSPF_ROUTER_LSA: /* Originate a new instance and schedule flooding */ if (area->router_lsa_self) area->router_lsa_self->data->ls_seqnum = new->data->ls_seqnum; ospf_router_lsa_update_area(area); return; case OSPF_NETWORK_LSA: case OSPF_OPAQUE_LINK_LSA: /* We must find the interface the LSA could belong to. If the interface is no more a broadcast type or we are no more the DR, we flush the LSA otherwise -- create the new instance and schedule flooding. */ /* Look through all interfaces, not just area, since interface could be moved from one area to another. */ for (ALL_LIST_ELEMENTS_RO(ospf->oiflist, node, oi)) /* These are sanity check. */ if (IPV4_ADDR_SAME(&oi->address->u.prefix4, &new->data->id)) { if (oi->area != area || oi->type != OSPF_IFTYPE_BROADCAST || !IPV4_ADDR_SAME(&oi->address->u.prefix4, &DR(oi))) { ospf_schedule_lsa_flush_area(area, new); return; } if (new->data->type == OSPF_OPAQUE_LINK_LSA) { ospf_opaque_lsa_refresh(new); return; } if (oi->network_lsa_self) oi->network_lsa_self->data->ls_seqnum = new->data->ls_seqnum; /* Schedule network-LSA origination. */ ospf_network_lsa_update(oi); return; } break; case OSPF_SUMMARY_LSA: case OSPF_ASBR_SUMMARY_LSA: ospf_schedule_abr_task(ospf); break; case OSPF_AS_EXTERNAL_LSA: case OSPF_AS_NSSA_LSA: if ((new->data->type == OSPF_AS_EXTERNAL_LSA) && CHECK_FLAG(new->flags, OSPF_LSA_LOCAL_XLT)) { ospf_translated_nssa_refresh(ospf, NULL, new); return; } ei = ospf_external_info_check(ospf, new); if (ei) ospf_external_lsa_refresh(ospf, new, ei, LSA_REFRESH_FORCE); else ospf_lsa_flush_as(ospf, new); break; case OSPF_OPAQUE_AREA_LSA: ospf_opaque_lsa_refresh(new); break; case OSPF_OPAQUE_AS_LSA: ospf_opaque_lsa_refresh(new); /* Reconsideration may needed. */ /* XXX */ break; default: break; } } /* OSPF LSA flooding -- RFC2328 Section 13.(5). */ /* Now Updated for NSSA operation, as follows: Type-5's have no change. Blocked to STUB or NSSA. Type-7's can be received, and if a DR they will also flood the local NSSA Area as Type-7's If a Self-Originated LSA (now an ASBR), The LSDB will be updated as Type-5's, (for continual re-fresh) If an NSSA-IR it is installed/flooded as Type-7, P-bit on. if an NSSA-ABR it is installed/flooded as Type-7, P-bit off. Later, during the ABR TASK, if the ABR is the Elected NSSA translator, then All Type-7s (with P-bit ON) are Translated to Type-5's and flooded to all non-NSSA/STUB areas. During ASE Calculations, non-ABRs calculate external routes from Type-7's ABRs calculate external routes from Type-5's and non-self Type-7s */ int ospf_flood(struct ospf *ospf, struct ospf_neighbor *nbr, struct ospf_lsa *current, struct ospf_lsa *new) { struct ospf_interface *oi; int lsa_ack_flag; /* Type-7 LSA's will be flooded throughout their native NSSA area, but will also be flooded as Type-5's into ABR capable links. */ if (IS_DEBUG_OSPF_EVENT) zlog_debug( "LSA[Flooding]: start, NBR %s (%s), cur(%p), New-LSA[%s]", inet_ntoa(nbr->router_id), lookup_msg(ospf_nsm_state_msg, nbr->state, NULL), (void *)current, dump_lsa_key(new)); oi = nbr->oi; /* If there is already a database copy, and if the database copy was received via flooding and installed less than MinLSArrival seconds ago, discard the new LSA (without acknowledging it). */ if (current != NULL) /* -- endo. */ { if (IS_LSA_SELF(current) && (ntohs(current->data->ls_age) == 0 && ntohl(current->data->ls_seqnum) == OSPF_INITIAL_SEQUENCE_NUMBER)) { if (IS_DEBUG_OSPF_EVENT) zlog_debug( "LSA[Flooding]: Got a self-originated LSA, " "while local one is initial instance."); ; /* Accept this LSA for quick LSDB resynchronization. */ } else if (monotime_since(¤t->tv_recv, NULL) < ospf->min_ls_arrival * 1000LL) { if (IS_DEBUG_OSPF_EVENT) zlog_debug( "LSA[Flooding]: LSA is received recently."); return -1; } } /* Flood the new LSA out some subset of the router's interfaces. In some cases (e.g., the state of the receiving interface is DR and the LSA was received from a router other than the Backup DR) the LSA will be flooded back out the receiving interface. */ lsa_ack_flag = ospf_flood_through(ospf, nbr, new); /* Remove the current database copy from all neighbors' Link state retransmission lists. AS_EXTERNAL and AS_EXTERNAL_OPAQUE does ^^^^^^^^^^^^^^^^^^^^^^^ not have area ID. All other (even NSSA's) do have area ID. */ if (current) { switch (current->data->type) { case OSPF_AS_EXTERNAL_LSA: case OSPF_OPAQUE_AS_LSA: ospf_ls_retransmit_delete_nbr_as(ospf, current); break; default: ospf_ls_retransmit_delete_nbr_area(nbr->oi->area, current); break; } } /* Do some internal house keeping that is needed here */ SET_FLAG(new->flags, OSPF_LSA_RECEIVED); (void)ospf_lsa_is_self_originated(ospf, new); /* Let it set the flag */ /* Install the new LSA in the link state database (replacing the current database copy). This may cause the routing table calculation to be scheduled. In addition, timestamp the new LSA with the current time. The flooding procedure cannot overwrite the newly installed LSA until MinLSArrival seconds have elapsed. */ if (!(new = ospf_lsa_install(ospf, nbr->oi, new))) return -1; /* unknown LSA type or any other error condition */ /* Acknowledge the receipt of the LSA by sending a Link State Acknowledgment packet back out the receiving interface. */ if (lsa_ack_flag) ospf_flood_delayed_lsa_ack(nbr, new); /* If this new LSA indicates that it was originated by the receiving router itself, the router must take special action, either updating the LSA or in some cases flushing it from the routing domain. */ if (ospf_lsa_is_self_originated(ospf, new)) ospf_process_self_originated_lsa(ospf, new, oi->area); else /* Update statistics value for OSPF-MIB. */ ospf->rx_lsa_count++; return 0; } /* OSPF LSA flooding -- RFC2328 Section 13.3. */ static int ospf_flood_through_interface(struct ospf_interface *oi, struct ospf_neighbor *inbr, struct ospf_lsa *lsa) { struct ospf_neighbor *onbr; struct route_node *rn; int retx_flag; if (IS_DEBUG_OSPF_EVENT) zlog_debug( "ospf_flood_through_interface(): " "considering int %s, INBR(%s), LSA[%s] AGE %u", IF_NAME(oi), inbr ? inet_ntoa(inbr->router_id) : "NULL", dump_lsa_key(lsa), ntohs(lsa->data->ls_age)); if (!ospf_if_is_enable(oi)) return 0; /* Remember if new LSA is aded to a retransmit list. */ retx_flag = 0; /* Each of the neighbors attached to this interface are examined, to determine whether they must receive the new LSA. The following steps are executed for each neighbor: */ for (rn = route_top(oi->nbrs); rn; rn = route_next(rn)) { struct ospf_lsa *ls_req; if (rn->info == NULL) continue; onbr = rn->info; if (IS_DEBUG_OSPF_EVENT) zlog_debug( "ospf_flood_through_interface(): considering nbr %s (%s)", inet_ntoa(onbr->router_id), lookup_msg(ospf_nsm_state_msg, onbr->state, NULL)); /* If the neighbor is in a lesser state than Exchange, it does not participate in flooding, and the next neighbor should be examined. */ if (onbr->state < NSM_Exchange) continue; /* If the adjacency is not yet full (neighbor state is Exchange or Loading), examine the Link state request list associated with this adjacency. If there is an instance of the new LSA on the list, it indicates that the neighboring router has an instance of the LSA already. Compare the new LSA to the neighbor's copy: */ if (onbr->state < NSM_Full) { if (IS_DEBUG_OSPF_EVENT) zlog_debug( "ospf_flood_through_interface(): nbr adj is not Full"); ls_req = ospf_ls_request_lookup(onbr, lsa); if (ls_req != NULL) { int ret; ret = ospf_lsa_more_recent(ls_req, lsa); /* The new LSA is less recent. */ if (ret > 0) continue; /* The two copies are the same instance, then delete the LSA from the Link state request list. */ else if (ret == 0) { ospf_ls_request_delete(onbr, ls_req); ospf_check_nbr_loading(onbr); continue; } /* The new LSA is more recent. Delete the LSA from the Link state request list. */ else { ospf_ls_request_delete(onbr, ls_req); ospf_check_nbr_loading(onbr); } } } if (IS_OPAQUE_LSA(lsa->data->type)) { if (!CHECK_FLAG(onbr->options, OSPF_OPTION_O)) { if (IS_DEBUG_OSPF(lsa, LSA_FLOODING)) zlog_debug( "Skip this neighbor: Not Opaque-capable."); continue; } } /* If the new LSA was received from this neighbor, examine the next neighbor. */ #ifdef ORIGINAL_CODING if (inbr) if (IPV4_ADDR_SAME(&inbr->router_id, &onbr->router_id)) continue; #else /* ORIGINAL_CODING */ if (inbr) { /* * Triggered by LSUpd message parser "ospf_ls_upd ()". * E.g., all LSAs handling here is received via network. */ if (IPV4_ADDR_SAME(&inbr->router_id, &onbr->router_id)) { if (IS_DEBUG_OSPF(lsa, LSA_FLOODING)) zlog_debug( "Skip this neighbor: inbr == onbr"); continue; } } else { /* * Triggered by MaxAge remover, so far. * NULL "inbr" means flooding starts from this node. */ if (IPV4_ADDR_SAME(&lsa->data->adv_router, &onbr->router_id)) { if (IS_DEBUG_OSPF(lsa, LSA_FLOODING)) zlog_debug( "Skip this neighbor: lsah->adv_router == onbr"); continue; } } #endif /* ORIGINAL_CODING */ /* Add the new LSA to the Link state retransmission list for the adjacency. The LSA will be retransmitted at intervals until an acknowledgment is seen from the neighbor. */ ospf_ls_retransmit_add(onbr, lsa); retx_flag = 1; } /* If in the previous step, the LSA was NOT added to any of the Link state retransmission lists, there is no need to flood the LSA out the interface. */ if (retx_flag == 0) { return (inbr && inbr->oi == oi); } /* if we've received the lsa on this interface we need to perform additional checking */ if (inbr && (inbr->oi == oi)) { /* If the new LSA was received on this interface, and it was received from either the Designated Router or the Backup Designated Router, chances are that all the neighbors have received the LSA already. */ if (NBR_IS_DR(inbr) || NBR_IS_BDR(inbr)) { if (IS_DEBUG_OSPF_NSSA) zlog_debug( "ospf_flood_through_interface(): " "DR/BDR NOT SEND to int %s", IF_NAME(oi)); return 1; } /* If the new LSA was received on this interface, and the interface state is Backup, examine the next interface. The Designated Router will do the flooding on this interface. However, if the Designated Router fails the router will end up retransmitting the updates. */ if (oi->state == ISM_Backup) { if (IS_DEBUG_OSPF_NSSA) zlog_debug( "ospf_flood_through_interface(): " "ISM_Backup NOT SEND to int %s", IF_NAME(oi)); return 1; } } /* The LSA must be flooded out the interface. Send a Link State Update packet (including the new LSA as contents) out the interface. The LSA's LS age must be incremented by InfTransDelay (which must be > 0) when it is copied into the outgoing Link State Update packet (until the LS age field reaches the maximum value of MaxAge). */ /* XXX HASSO: Is this IS_DEBUG_OSPF_NSSA really correct? */ if (IS_DEBUG_OSPF_NSSA) zlog_debug( "ospf_flood_through_interface(): " "DR/BDR sending upd to int %s", IF_NAME(oi)); /* RFC2328 Section 13.3 On non-broadcast networks, separate Link State Update packets must be sent, as unicasts, to each adjacent neighbor (i.e., those in state Exchange or greater). The destination IP addresses for these packets are the neighbors' IP addresses. */ if (oi->type == OSPF_IFTYPE_NBMA) { struct ospf_neighbor *nbr; for (rn = route_top(oi->nbrs); rn; rn = route_next(rn)) if ((nbr = rn->info) != NULL) if (nbr != oi->nbr_self && nbr->state >= NSM_Exchange) ospf_ls_upd_send_lsa( nbr, lsa, OSPF_SEND_PACKET_DIRECT); } else ospf_ls_upd_send_lsa(oi->nbr_self, lsa, OSPF_SEND_PACKET_INDIRECT); return 0; } int ospf_flood_through_area(struct ospf_area *area, struct ospf_neighbor *inbr, struct ospf_lsa *lsa) { struct listnode *node, *nnode; struct ospf_interface *oi; int lsa_ack_flag = 0; assert(area); /* All other types are specific to a single area (Area A). The eligible interfaces are all those interfaces attaching to the Area A. If Area A is the backbone, this includes all the virtual links. */ for (ALL_LIST_ELEMENTS(area->oiflist, node, nnode, oi)) { if (area->area_id.s_addr != OSPF_AREA_BACKBONE && oi->type == OSPF_IFTYPE_VIRTUALLINK) continue; if ((lsa->data->type == OSPF_OPAQUE_LINK_LSA) && (lsa->oi != oi)) { /* * Link local scoped Opaque-LSA should only be flooded * for the link on which the LSA has received. */ if (IS_DEBUG_OSPF(lsa, LSA_FLOODING)) zlog_debug( "Type-9 Opaque-LSA: lsa->oi(%p) != oi(%p)", (void *)lsa->oi, (void *)oi); continue; } if (ospf_flood_through_interface(oi, inbr, lsa)) lsa_ack_flag = 1; } return (lsa_ack_flag); } int ospf_flood_through_as(struct ospf *ospf, struct ospf_neighbor *inbr, struct ospf_lsa *lsa) { struct listnode *node; struct ospf_area *area; int lsa_ack_flag; lsa_ack_flag = 0; /* The incoming LSA is type 5 or type 7 (AS-EXTERNAL or AS-NSSA ) Divert the Type-5 LSA's to all non-NSSA/STUB areas Divert the Type-7 LSA's to all NSSA areas AS-external-LSAs are flooded throughout the entire AS, with the exception of stub areas (see Section 3.6). The eligible interfaces are all the router's interfaces, excluding virtual links and those interfaces attaching to stub areas. */ if (CHECK_FLAG(lsa->flags, OSPF_LSA_LOCAL_XLT)) /* Translated from 7 */ if (IS_DEBUG_OSPF_NSSA) zlog_debug("Flood/AS: NSSA TRANSLATED LSA"); for (ALL_LIST_ELEMENTS_RO(ospf->areas, node, area)) { int continue_flag = 0; struct listnode *if_node; struct ospf_interface *oi; switch (area->external_routing) { /* Don't send AS externals into stub areas. Various types of support for partial stub areas can be implemented here. NSSA's will receive Type-7's that have areas matching the originl LSA. */ case OSPF_AREA_NSSA: /* Sending Type 5 or 7 into NSSA area */ /* Type-7, flood NSSA area */ if (lsa->data->type == OSPF_AS_NSSA_LSA && area == lsa->area) /* We will send it. */ continue_flag = 0; else continue_flag = 1; /* Skip this NSSA area for Type-5's et al */ break; case OSPF_AREA_TYPE_MAX: case OSPF_AREA_STUB: continue_flag = 1; /* Skip this area. */ break; case OSPF_AREA_DEFAULT: default: /* No Type-7 into normal area */ if (lsa->data->type == OSPF_AS_NSSA_LSA) continue_flag = 1; /* skip Type-7 */ else continue_flag = 0; /* Do this area. */ break; } /* Do continue for above switch. Saves a big if then mess */ if (continue_flag) continue; /* main for-loop */ /* send to every interface in this area */ for (ALL_LIST_ELEMENTS_RO(area->oiflist, if_node, oi)) { /* Skip virtual links */ if (oi->type != OSPF_IFTYPE_VIRTUALLINK) if (ospf_flood_through_interface(oi, inbr, lsa)) /* lsa */ lsa_ack_flag = 1; } } /* main area for-loop */ return (lsa_ack_flag); } int ospf_flood_through(struct ospf *ospf, struct ospf_neighbor *inbr, struct ospf_lsa *lsa) { int lsa_ack_flag = 0; /* Type-7 LSA's for NSSA are flooded throughout the AS here, and upon return are updated in the LSDB for Type-7's. Later, re-fresh will re-send them (and also, if ABR, packet code will translate to Type-5's) As usual, Type-5 LSA's (if not DISCARDED because we are STUB or NSSA) are flooded throughout the AS, and are updated in the global table. */ #ifdef ORIGINAL_CODING switch (lsa->data->type) { case OSPF_ROUTER_LSA: case OSPF_NETWORK_LSA: case OSPF_SUMMARY_LSA: case OSPF_ASBR_SUMMARY_LSA: case OSPF_OPAQUE_LINK_LSA: /* ospf_flood_through_interface ? */ case OSPF_OPAQUE_AREA_LSA: lsa_ack_flag = ospf_flood_through_area(inbr->oi->area, inbr, lsa); break; case OSPF_AS_EXTERNAL_LSA: /* Type-5 */ case OSPF_OPAQUE_AS_LSA: lsa_ack_flag = ospf_flood_through_as(ospf, inbr, lsa); break; /* Type-7 Only received within NSSA, then flooded */ case OSPF_AS_NSSA_LSA: /* Any P-bit was installed with the Type-7. */ lsa_ack_flag = ospf_flood_through_area(inbr->oi->area, inbr, lsa); if (IS_DEBUG_OSPF_NSSA) zlog_debug( "ospf_flood_through: LOCAL NSSA FLOOD of Type-7."); break; default: break; } #else /* ORIGINAL_CODING */ /* * At the common sub-sub-function "ospf_flood_through_interface()", * a parameter "inbr" will be used to distinguish the called context * whether the given LSA was received from the neighbor, or the * flooding for the LSA starts from this node (e.g. the LSA was self- * originated, or the LSA is going to be flushed from routing domain). * * So, for consistency reasons, this function "ospf_flood_through()" * should also allow the usage that the given "inbr" parameter to be * NULL. If we do so, corresponding AREA parameter should be referred * by "lsa->area", instead of "inbr->oi->area". */ switch (lsa->data->type) { case OSPF_AS_EXTERNAL_LSA: /* Type-5 */ case OSPF_OPAQUE_AS_LSA: lsa_ack_flag = ospf_flood_through_as(ospf, inbr, lsa); break; /* Type-7 Only received within NSSA, then flooded */ case OSPF_AS_NSSA_LSA: /* Any P-bit was installed with the Type-7. */ if (IS_DEBUG_OSPF_NSSA) zlog_debug( "ospf_flood_through: LOCAL NSSA FLOOD of Type-7."); /* Fallthrough */ default: lsa_ack_flag = ospf_flood_through_area(lsa->area, inbr, lsa); break; } #endif /* ORIGINAL_CODING */ return (lsa_ack_flag); } /* Management functions for neighbor's Link State Request list. */ void ospf_ls_request_add(struct ospf_neighbor *nbr, struct ospf_lsa *lsa) { /* * We cannot make use of the newly introduced callback function * "lsdb->new_lsa_hook" to replace debug output below, just because * it seems no simple and smart way to pass neighbor information to * the common function "ospf_lsdb_add()" -- endo. */ if (IS_DEBUG_OSPF(lsa, LSA_FLOODING)) zlog_debug("RqstL(%lu)++, NBR(%s), LSA[%s]", ospf_ls_request_count(nbr), inet_ntoa(nbr->router_id), dump_lsa_key(lsa)); ospf_lsdb_add(&nbr->ls_req, lsa); } unsigned long ospf_ls_request_count(struct ospf_neighbor *nbr) { return ospf_lsdb_count_all(&nbr->ls_req); } int ospf_ls_request_isempty(struct ospf_neighbor *nbr) { return ospf_lsdb_isempty(&nbr->ls_req); } /* Remove LSA from neighbor's ls-request list. */ void ospf_ls_request_delete(struct ospf_neighbor *nbr, struct ospf_lsa *lsa) { if (nbr->ls_req_last == lsa) { ospf_lsa_unlock(&nbr->ls_req_last); nbr->ls_req_last = NULL; } if (IS_DEBUG_OSPF(lsa, LSA_FLOODING)) /* -- endo. */ zlog_debug("RqstL(%lu)--, NBR(%s), LSA[%s]", ospf_ls_request_count(nbr), inet_ntoa(nbr->router_id), dump_lsa_key(lsa)); ospf_lsdb_delete(&nbr->ls_req, lsa); } /* Remove all LSA from neighbor's ls-requenst list. */ void ospf_ls_request_delete_all(struct ospf_neighbor *nbr) { ospf_lsa_unlock(&nbr->ls_req_last); nbr->ls_req_last = NULL; ospf_lsdb_delete_all(&nbr->ls_req); } /* Lookup LSA from neighbor's ls-request list. */ struct ospf_lsa *ospf_ls_request_lookup(struct ospf_neighbor *nbr, struct ospf_lsa *lsa) { return ospf_lsdb_lookup(&nbr->ls_req, lsa); } struct ospf_lsa *ospf_ls_request_new(struct lsa_header *lsah) { struct ospf_lsa *new; new = ospf_lsa_new_and_data(OSPF_LSA_HEADER_SIZE); memcpy(new->data, lsah, OSPF_LSA_HEADER_SIZE); return new; } /* Management functions for neighbor's ls-retransmit list. */ unsigned long ospf_ls_retransmit_count(struct ospf_neighbor *nbr) { return ospf_lsdb_count_all(&nbr->ls_rxmt); } unsigned long ospf_ls_retransmit_count_self(struct ospf_neighbor *nbr, int lsa_type) { return ospf_lsdb_count_self(&nbr->ls_rxmt, lsa_type); } int ospf_ls_retransmit_isempty(struct ospf_neighbor *nbr) { return ospf_lsdb_isempty(&nbr->ls_rxmt); } /* Add LSA to be retransmitted to neighbor's ls-retransmit list. */ void ospf_ls_retransmit_add(struct ospf_neighbor *nbr, struct ospf_lsa *lsa) { struct ospf_lsa *old; old = ospf_ls_retransmit_lookup(nbr, lsa); if (ospf_lsa_more_recent(old, lsa) < 0) { if (old) { old->retransmit_counter--; ospf_lsdb_delete(&nbr->ls_rxmt, old); } lsa->retransmit_counter++; /* * We cannot make use of the newly introduced callback function * "lsdb->new_lsa_hook" to replace debug output below, just * because * it seems no simple and smart way to pass neighbor information * to * the common function "ospf_lsdb_add()" -- endo. */ if (IS_DEBUG_OSPF(lsa, LSA_FLOODING)) zlog_debug("RXmtL(%lu)++, NBR(%s), LSA[%s]", ospf_ls_retransmit_count(nbr), inet_ntoa(nbr->router_id), dump_lsa_key(lsa)); ospf_lsdb_add(&nbr->ls_rxmt, lsa); } } /* Remove LSA from neibghbor's ls-retransmit list. */ void ospf_ls_retransmit_delete(struct ospf_neighbor *nbr, struct ospf_lsa *lsa) { if (ospf_ls_retransmit_lookup(nbr, lsa)) { lsa->retransmit_counter--; if (IS_DEBUG_OSPF(lsa, LSA_FLOODING)) /* -- endo. */ zlog_debug("RXmtL(%lu)--, NBR(%s), LSA[%s]", ospf_ls_retransmit_count(nbr), inet_ntoa(nbr->router_id), dump_lsa_key(lsa)); ospf_lsdb_delete(&nbr->ls_rxmt, lsa); } } /* Clear neighbor's ls-retransmit list. */ void ospf_ls_retransmit_clear(struct ospf_neighbor *nbr) { struct ospf_lsdb *lsdb; int i; lsdb = &nbr->ls_rxmt; for (i = OSPF_MIN_LSA; i < OSPF_MAX_LSA; i++) { struct route_table *table = lsdb->type[i].db; struct route_node *rn; struct ospf_lsa *lsa; for (rn = route_top(table); rn; rn = route_next(rn)) if ((lsa = rn->info) != NULL) ospf_ls_retransmit_delete(nbr, lsa); } ospf_lsa_unlock(&nbr->ls_req_last); nbr->ls_req_last = NULL; } /* Lookup LSA from neighbor's ls-retransmit list. */ struct ospf_lsa *ospf_ls_retransmit_lookup(struct ospf_neighbor *nbr, struct ospf_lsa *lsa) { return ospf_lsdb_lookup(&nbr->ls_rxmt, lsa); } static void ospf_ls_retransmit_delete_nbr_if(struct ospf_interface *oi, struct ospf_lsa *lsa) { struct route_node *rn; struct ospf_neighbor *nbr; struct ospf_lsa *lsr; if (ospf_if_is_enable(oi)) for (rn = route_top(oi->nbrs); rn; rn = route_next(rn)) /* If LSA find in LS-retransmit list, then remove it. */ if ((nbr = rn->info) != NULL) { lsr = ospf_ls_retransmit_lookup(nbr, lsa); /* If LSA find in ls-retransmit list, remove it. */ if (lsr != NULL && lsr->data->ls_seqnum == lsa->data->ls_seqnum) ospf_ls_retransmit_delete(nbr, lsr); } } void ospf_ls_retransmit_delete_nbr_area(struct ospf_area *area, struct ospf_lsa *lsa) { struct listnode *node, *nnode; struct ospf_interface *oi; for (ALL_LIST_ELEMENTS(area->oiflist, node, nnode, oi)) ospf_ls_retransmit_delete_nbr_if(oi, lsa); } void ospf_ls_retransmit_delete_nbr_as(struct ospf *ospf, struct ospf_lsa *lsa) { struct listnode *node, *nnode; struct ospf_interface *oi; for (ALL_LIST_ELEMENTS(ospf->oiflist, node, nnode, oi)) ospf_ls_retransmit_delete_nbr_if(oi, lsa); } /* Sets ls_age to MaxAge and floods throu the area. When we implement ASE routing, there will be anothe function flushing an LSA from the whole domain. */ void ospf_lsa_flush_area(struct ospf_lsa *lsa, struct ospf_area *area) { /* Reset the lsa origination time such that it gives more time for the ACK to be received and avoid retransmissions */ lsa->data->ls_age = htons(OSPF_LSA_MAXAGE); if (IS_DEBUG_OSPF_EVENT) zlog_debug("%s: MAXAGE set to LSA %s", __PRETTY_FUNCTION__, inet_ntoa(lsa->data->id)); monotime(&lsa->tv_recv); lsa->tv_orig = lsa->tv_recv; ospf_flood_through_area(area, NULL, lsa); ospf_lsa_maxage(area->ospf, lsa); } void ospf_lsa_flush_as(struct ospf *ospf, struct ospf_lsa *lsa) { /* Reset the lsa origination time such that it gives more time for the ACK to be received and avoid retransmissions */ lsa->data->ls_age = htons(OSPF_LSA_MAXAGE); monotime(&lsa->tv_recv); lsa->tv_orig = lsa->tv_recv; ospf_flood_through_as(ospf, NULL, lsa); ospf_lsa_maxage(ospf, lsa); } void ospf_lsa_flush(struct ospf *ospf, struct ospf_lsa *lsa) { lsa->data->ls_age = htons(OSPF_LSA_MAXAGE); switch (lsa->data->type) { case OSPF_ROUTER_LSA: case OSPF_NETWORK_LSA: case OSPF_SUMMARY_LSA: case OSPF_ASBR_SUMMARY_LSA: case OSPF_AS_NSSA_LSA: case OSPF_OPAQUE_LINK_LSA: case OSPF_OPAQUE_AREA_LSA: ospf_lsa_flush_area(lsa, lsa->area); break; case OSPF_AS_EXTERNAL_LSA: case OSPF_OPAQUE_AS_LSA: ospf_lsa_flush_as(ospf, lsa); break; default: zlog_info("%s: Unknown LSA type %u", __func__, lsa->data->type); break; } } frr-7.2.1/ospfd/ospf_flood.h0000644000000000000000000000621613610377563012637 00000000000000/* * OSPF Flooding -- RFC2328 Section 13. * Copyright (C) 1999, 2000 Toshiaki Takada * * This file is part of GNU Zebra. * * GNU Zebra is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License as published * by the Free Software Foundation; either version 2, or (at your * option) any later version. * * GNU Zebra 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; see the file COPYING; if not, write to the Free Software * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA */ #ifndef _ZEBRA_OSPF_FLOOD_H #define _ZEBRA_OSPF_FLOOD_H extern int ospf_flood(struct ospf *, struct ospf_neighbor *, struct ospf_lsa *, struct ospf_lsa *); extern int ospf_flood_through(struct ospf *, struct ospf_neighbor *, struct ospf_lsa *); extern int ospf_flood_through_area(struct ospf_area *, struct ospf_neighbor *, struct ospf_lsa *); extern int ospf_flood_through_as(struct ospf *, struct ospf_neighbor *, struct ospf_lsa *); extern unsigned long ospf_ls_request_count(struct ospf_neighbor *); extern int ospf_ls_request_isempty(struct ospf_neighbor *); extern struct ospf_lsa *ospf_ls_request_new(struct lsa_header *); extern void ospf_ls_request_free(struct ospf_lsa *); extern void ospf_ls_request_add(struct ospf_neighbor *, struct ospf_lsa *); extern void ospf_ls_request_delete(struct ospf_neighbor *, struct ospf_lsa *); extern void ospf_ls_request_delete_all(struct ospf_neighbor *); extern struct ospf_lsa *ospf_ls_request_lookup(struct ospf_neighbor *, struct ospf_lsa *); extern unsigned long ospf_ls_retransmit_count(struct ospf_neighbor *); extern unsigned long ospf_ls_retransmit_count_self(struct ospf_neighbor *, int); extern int ospf_ls_retransmit_isempty(struct ospf_neighbor *); extern void ospf_ls_retransmit_add(struct ospf_neighbor *, struct ospf_lsa *); extern void ospf_ls_retransmit_delete(struct ospf_neighbor *, struct ospf_lsa *); extern void ospf_ls_retransmit_clear(struct ospf_neighbor *); extern struct ospf_lsa *ospf_ls_retransmit_lookup(struct ospf_neighbor *, struct ospf_lsa *); extern void ospf_ls_retransmit_delete_nbr_area(struct ospf_area *, struct ospf_lsa *); extern void ospf_ls_retransmit_delete_nbr_as(struct ospf *, struct ospf_lsa *); extern void ospf_ls_retransmit_add_nbr_all(struct ospf_interface *, struct ospf_lsa *); extern void ospf_flood_lsa_area(struct ospf_lsa *, struct ospf_area *); extern void ospf_flood_lsa_as(struct ospf_lsa *); extern void ospf_lsa_flush_area(struct ospf_lsa *, struct ospf_area *); extern void ospf_lsa_flush_as(struct ospf *, struct ospf_lsa *); extern void ospf_lsa_flush(struct ospf *, struct ospf_lsa *); extern struct external_info *ospf_external_info_check(struct ospf *, struct ospf_lsa *); extern void ospf_lsdb_init(struct ospf_lsdb *); #endif /* _ZEBRA_OSPF_FLOOD_H */ frr-7.2.1/ospfd/ospf_ia.c0000644000000000000000000004416013610377563012120 00000000000000/* * OSPF inter-area routing. * Copyright (C) 1999, 2000 Alex Zinin, Toshiaki Takada * * This file is part of GNU Zebra. * * GNU Zebra is free software; you can redistribute it and/or modify it * under the terms of the GNU General Public License as published by the * Free Software Foundation; either version 2, or (at your option) any * later version. * * GNU Zebra 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; see the file COPYING; if not, write to the Free Software * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA */ #include #include "thread.h" #include "memory.h" #include "hash.h" #include "linklist.h" #include "prefix.h" #include "table.h" #include "log.h" #include "ospfd/ospfd.h" #include "ospfd/ospf_interface.h" #include "ospfd/ospf_ism.h" #include "ospfd/ospf_asbr.h" #include "ospfd/ospf_lsa.h" #include "ospfd/ospf_lsdb.h" #include "ospfd/ospf_neighbor.h" #include "ospfd/ospf_nsm.h" #include "ospfd/ospf_spf.h" #include "ospfd/ospf_route.h" #include "ospfd/ospf_ase.h" #include "ospfd/ospf_abr.h" #include "ospfd/ospf_ia.h" #include "ospfd/ospf_dump.h" static struct ospf_route *ospf_find_abr_route(struct route_table *rtrs, struct prefix_ipv4 *abr, struct ospf_area *area) { struct route_node *rn; struct ospf_route * or ; struct listnode *node; if ((rn = route_node_lookup(rtrs, (struct prefix *)abr)) == NULL) return NULL; route_unlock_node(rn); for (ALL_LIST_ELEMENTS_RO((struct list *)rn->info, node, or)) if (IPV4_ADDR_SAME(& or->u.std.area_id, &area->area_id) && (or->u.std.flags & ROUTER_LSA_BORDER)) return or ; return NULL; } static void ospf_ia_network_route(struct ospf *ospf, struct route_table *rt, struct prefix_ipv4 *p, struct ospf_route *new_or, struct ospf_route *abr_or) { struct route_node *rn1; struct ospf_route * or ; if (IS_DEBUG_OSPF_EVENT) zlog_debug( "ospf_ia_network_route(): processing summary route to %s/%d", inet_ntoa(p->prefix), p->prefixlen); /* Find a route to the same dest */ if ((rn1 = route_node_lookup(rt, (struct prefix *)p))) { int res; route_unlock_node(rn1); if ((or = rn1->info)) { if (IS_DEBUG_OSPF_EVENT) zlog_debug( "ospf_ia_network_route(): " "Found a route to the same network"); /* Check the existing route. */ if ((res = ospf_route_cmp(ospf, new_or, or)) < 0) { /* New route is better, so replace old one. */ ospf_route_subst(rn1, new_or, abr_or); } else if (res == 0) { /* New and old route are equal, so next hops can * be added. */ route_lock_node(rn1); ospf_route_copy_nexthops(or, abr_or->paths); route_unlock_node(rn1); /* new route can be deleted, because existing * route has been updated. */ ospf_route_free(new_or); } else { /* New route is worse, so free it. */ ospf_route_free(new_or); return; } } /* if (or)*/ } /*if (rn1)*/ else { /* no route */ if (IS_DEBUG_OSPF_EVENT) zlog_debug( "ospf_ia_network_route(): add new route to %s/%d", inet_ntoa(p->prefix), p->prefixlen); ospf_route_add(rt, p, new_or, abr_or); } } static void ospf_ia_router_route(struct ospf *ospf, struct route_table *rtrs, struct prefix_ipv4 *p, struct ospf_route *new_or, struct ospf_route *abr_or) { struct ospf_route * or = NULL; struct route_node *rn; int ret; if (IS_DEBUG_OSPF_EVENT) zlog_debug("ospf_ia_router_route(): considering %s/%d", inet_ntoa(p->prefix), p->prefixlen); /* Find a route to the same dest */ rn = route_node_get(rtrs, (struct prefix *)p); if (rn->info == NULL) /* This is a new route */ rn->info = list_new(); else { struct ospf_area *or_area; or_area = ospf_area_lookup_by_area_id(ospf, new_or->u.std.area_id); assert(or_area); /* This is an additional route */ route_unlock_node(rn); or = ospf_find_asbr_route_through_area(rtrs, p, or_area); } if (or) { if (IS_DEBUG_OSPF_EVENT) zlog_debug( "ospf_ia_router_route(): " "a route to the same ABR through the same area exists"); /* New route is better */ if ((ret = ospf_route_cmp(ospf, new_or, or)) < 0) { listnode_delete(rn->info, or); ospf_route_free(or); /* proceed down */ } /* Routes are the same */ else if (ret == 0) { if (IS_DEBUG_OSPF_EVENT) zlog_debug( "ospf_ia_router_route(): merging the new route"); ospf_route_copy_nexthops(or, abr_or->paths); ospf_route_free(new_or); return; } /* New route is worse */ else { if (IS_DEBUG_OSPF_EVENT) zlog_debug( "ospf_ia_router_route(): skipping the new route"); ospf_route_free(new_or); return; } } ospf_route_copy_nexthops(new_or, abr_or->paths); if (IS_DEBUG_OSPF_EVENT) zlog_debug("ospf_ia_router_route(): adding the new route"); listnode_add(rn->info, new_or); } static int process_summary_lsa(struct ospf_area *area, struct route_table *rt, struct route_table *rtrs, struct ospf_lsa *lsa) { struct ospf *ospf = area->ospf; struct ospf_area_range *range; struct ospf_route *abr_or, *new_or; struct summary_lsa *sl; struct prefix_ipv4 p, abr; uint32_t metric; if (lsa == NULL) return 0; sl = (struct summary_lsa *)lsa->data; if (IS_DEBUG_OSPF_EVENT) zlog_debug("process_summary_lsa(): LS ID: %s", inet_ntoa(sl->header.id)); metric = GET_METRIC(sl->metric); if (metric == OSPF_LS_INFINITY) return 0; if (IS_LSA_MAXAGE(lsa)) return 0; if (ospf_lsa_is_self_originated(area->ospf, lsa)) return 0; p.family = AF_INET; p.prefix = sl->header.id; if (sl->header.type == OSPF_SUMMARY_LSA) p.prefixlen = ip_masklen(sl->mask); else p.prefixlen = IPV4_MAX_BITLEN; apply_mask_ipv4(&p); if (sl->header.type == OSPF_SUMMARY_LSA && (range = ospf_area_range_match_any(ospf, &p)) && ospf_area_range_active(range)) return 0; /* XXX: This check seems dubious to me. If an ABR has already decided * to consider summaries received in this area, then why would one wish * to exclude default? */ if (IS_OSPF_ABR(ospf) && ospf->abr_type != OSPF_ABR_STAND && area->external_routing != OSPF_AREA_DEFAULT && p.prefix.s_addr == OSPF_DEFAULT_DESTINATION && p.prefixlen == 0) return 0; /* Ignore summary default from a stub area */ abr.family = AF_INET; abr.prefix = sl->header.adv_router; abr.prefixlen = IPV4_MAX_BITLEN; apply_mask_ipv4(&abr); abr_or = ospf_find_abr_route(rtrs, &abr, area); if (abr_or == NULL) return 0; new_or = ospf_route_new(); new_or->type = OSPF_DESTINATION_NETWORK; new_or->id = sl->header.id; new_or->mask = sl->mask; new_or->u.std.options = sl->header.options; new_or->u.std.origin = (struct lsa_header *)sl; new_or->cost = abr_or->cost + metric; new_or->u.std.area_id = area->area_id; new_or->u.std.external_routing = area->external_routing; new_or->path_type = OSPF_PATH_INTER_AREA; if (sl->header.type == OSPF_SUMMARY_LSA) ospf_ia_network_route(ospf, rt, &p, new_or, abr_or); else { new_or->type = OSPF_DESTINATION_ROUTER; new_or->u.std.flags = ROUTER_LSA_EXTERNAL; ospf_ia_router_route(ospf, rtrs, &p, new_or, abr_or); } return 0; } static void ospf_examine_summaries(struct ospf_area *area, struct route_table *lsdb_rt, struct route_table *rt, struct route_table *rtrs) { struct ospf_lsa *lsa; struct route_node *rn; LSDB_LOOP (lsdb_rt, rn, lsa) process_summary_lsa(area, rt, rtrs, lsa); } int ospf_area_is_transit(struct ospf_area *area) { return (area->transit == OSPF_TRANSIT_TRUE) || ospf_full_virtual_nbrs( area); /* Cisco forgets to set the V-bit :( */ } static void ospf_update_network_route(struct ospf *ospf, struct route_table *rt, struct route_table *rtrs, struct summary_lsa *lsa, struct prefix_ipv4 *p, struct ospf_area *area) { struct route_node *rn; struct ospf_route * or, *abr_or, *new_or; struct prefix_ipv4 abr; uint32_t cost; abr.family = AF_INET; abr.prefix = lsa->header.adv_router; abr.prefixlen = IPV4_MAX_BITLEN; apply_mask_ipv4(&abr); abr_or = ospf_find_abr_route(rtrs, &abr, area); if (abr_or == NULL) { if (IS_DEBUG_OSPF_EVENT) zlog_debug( "ospf_update_network_route(): can't find a route to the ABR"); return; } cost = abr_or->cost + GET_METRIC(lsa->metric); rn = route_node_lookup(rt, (struct prefix *)p); if (!rn) { if (ospf->abr_type != OSPF_ABR_SHORTCUT) return; /* Standard ABR can update only already installed backbone paths */ if (IS_DEBUG_OSPF_EVENT) zlog_debug( "ospf_update_network_route(): " "Allowing Shortcut ABR to add new route"); new_or = ospf_route_new(); new_or->type = OSPF_DESTINATION_NETWORK; new_or->id = lsa->header.id; new_or->mask = lsa->mask; new_or->u.std.options = lsa->header.options; new_or->u.std.origin = (struct lsa_header *)lsa; new_or->cost = cost; new_or->u.std.area_id = area->area_id; new_or->u.std.external_routing = area->external_routing; new_or->path_type = OSPF_PATH_INTER_AREA; ospf_route_add(rt, p, new_or, abr_or); return; } else { route_unlock_node(rn); if (rn->info == NULL) return; } or = rn->info; if (or->path_type != OSPF_PATH_INTRA_AREA && or->path_type != OSPF_PATH_INTER_AREA) { if (IS_DEBUG_OSPF_EVENT) zlog_debug( "ospf_update_network_route(): ERR: path type is wrong"); return; } if (ospf->abr_type == OSPF_ABR_SHORTCUT) { if ( or->path_type == OSPF_PATH_INTRA_AREA && !OSPF_IS_AREA_ID_BACKBONE( or->u.std.area_id)) { if (IS_DEBUG_OSPF_EVENT) zlog_debug( "ospf_update_network_route(): Shortcut: " "this intra-area path is not backbone"); return; } } else /* Not Shortcut ABR */ { if (!OSPF_IS_AREA_ID_BACKBONE(or->u.std.area_id)) { if (IS_DEBUG_OSPF_EVENT) zlog_debug( "ospf_update_network_route(): " "route is not BB-associated"); return; /* We can update only BB routes */ } } if (or->cost < cost) { if (IS_DEBUG_OSPF_EVENT) zlog_debug( "ospf_update_network_route(): new route is worse"); return; } if (or->cost == cost) { if (IS_DEBUG_OSPF_EVENT) zlog_debug( "ospf_update_network_route(): " "new route is same distance, adding nexthops"); ospf_route_copy_nexthops(or, abr_or->paths); } if (or->cost > cost) { if (IS_DEBUG_OSPF_EVENT) zlog_debug( "ospf_update_network_route(): " "new route is better, overriding nexthops"); ospf_route_subst_nexthops(or, abr_or->paths); or->cost = cost; if ((ospf->abr_type == OSPF_ABR_SHORTCUT) && !OSPF_IS_AREA_ID_BACKBONE(or->u.std.area_id)) { or->path_type = OSPF_PATH_INTER_AREA; or->u.std.area_id = area->area_id; or->u.std.external_routing = area->external_routing; /* Note that we can do this only in Shortcut ABR mode, because standard ABR must leave the route type and area unchanged */ } } } static void ospf_update_router_route(struct ospf *ospf, struct route_table *rtrs, struct summary_lsa *lsa, struct prefix_ipv4 *p, struct ospf_area *area) { struct ospf_route * or, *abr_or, *new_or; struct prefix_ipv4 abr; uint32_t cost; abr.family = AF_INET; abr.prefix = lsa->header.adv_router; abr.prefixlen = IPV4_MAX_BITLEN; apply_mask_ipv4(&abr); abr_or = ospf_find_abr_route(rtrs, &abr, area); if (abr_or == NULL) { if (IS_DEBUG_OSPF_EVENT) zlog_debug( "ospf_update_router_route(): can't find a route to the ABR"); return; } cost = abr_or->cost + GET_METRIC(lsa->metric); /* First try to find a backbone path, because standard ABR can update only BB-associated paths */ if ((ospf->backbone == NULL) && (ospf->abr_type != OSPF_ABR_SHORTCUT)) return; /* no BB area, not Shortcut ABR, exiting */ /* find the backbone route, if possible */ if ((ospf->backbone == NULL) || !(or = ospf_find_asbr_route_through_area(rtrs, p, ospf->backbone))) { if (ospf->abr_type != OSPF_ABR_SHORTCUT) /* route to ASBR through the BB not found the router is not Shortcut ABR, exiting */ return; else /* We're a Shortcut ABR*/ { /* Let it either add a new router or update the route through the same (non-BB) area. */ new_or = ospf_route_new(); new_or->type = OSPF_DESTINATION_ROUTER; new_or->id = lsa->header.id; new_or->mask = lsa->mask; new_or->u.std.options = lsa->header.options; new_or->u.std.origin = (struct lsa_header *)lsa; new_or->cost = cost; new_or->u.std.area_id = area->area_id; new_or->u.std.external_routing = area->external_routing; new_or->path_type = OSPF_PATH_INTER_AREA; new_or->u.std.flags = ROUTER_LSA_EXTERNAL; ospf_ia_router_route(ospf, rtrs, p, new_or, abr_or); return; } } /* At this point the "or" is always bb-associated */ if (!(or->u.std.flags & ROUTER_LSA_EXTERNAL)) { if (IS_DEBUG_OSPF_EVENT) zlog_debug( "ospf_upd_router_route(): the remote router is not an ASBR"); return; } if (or->path_type != OSPF_PATH_INTRA_AREA && or->path_type != OSPF_PATH_INTER_AREA) return; if (or->cost < cost) return; else if (or->cost == cost) ospf_route_copy_nexthops(or, abr_or->paths); else if (or->cost > cost) { ospf_route_subst_nexthops(or, abr_or->paths); or->cost = cost; /* Even if the ABR runs in Shortcut mode, we can't change the path type and area, because the "or" is always bb-associated at this point and even Shortcut ABR can't change these attributes */ } } static int process_transit_summary_lsa(struct ospf_area *area, struct route_table *rt, struct route_table *rtrs, struct ospf_lsa *lsa) { struct ospf *ospf = area->ospf; struct summary_lsa *sl; struct prefix_ipv4 p; uint32_t metric; if (lsa == NULL) return 0; sl = (struct summary_lsa *)lsa->data; if (IS_DEBUG_OSPF_EVENT) zlog_debug("process_transit_summaries(): LS ID: %s", inet_ntoa(lsa->data->id)); metric = GET_METRIC(sl->metric); if (metric == OSPF_LS_INFINITY) { if (IS_DEBUG_OSPF_EVENT) zlog_debug( "process_transit_summaries(): metric is infinity, skip"); return 0; } if (IS_LSA_MAXAGE(lsa)) { if (IS_DEBUG_OSPF_EVENT) zlog_debug( "process_transit_summaries(): This LSA is too old"); return 0; } if (ospf_lsa_is_self_originated(area->ospf, lsa)) { if (IS_DEBUG_OSPF_EVENT) zlog_debug( "process_transit_summaries(): This LSA is mine, skip"); return 0; } p.family = AF_INET; p.prefix = sl->header.id; if (sl->header.type == OSPF_SUMMARY_LSA) p.prefixlen = ip_masklen(sl->mask); else p.prefixlen = IPV4_MAX_BITLEN; apply_mask_ipv4(&p); if (sl->header.type == OSPF_SUMMARY_LSA) ospf_update_network_route(ospf, rt, rtrs, sl, &p, area); else ospf_update_router_route(ospf, rtrs, sl, &p, area); return 0; } static void ospf_examine_transit_summaries(struct ospf_area *area, struct route_table *lsdb_rt, struct route_table *rt, struct route_table *rtrs) { struct ospf_lsa *lsa; struct route_node *rn; LSDB_LOOP (lsdb_rt, rn, lsa) process_transit_summary_lsa(area, rt, rtrs, lsa); } void ospf_ia_routing(struct ospf *ospf, struct route_table *rt, struct route_table *rtrs) { struct listnode *node; struct ospf_area *area; if (IS_DEBUG_OSPF_EVENT) zlog_debug("ospf_ia_routing():start"); if (IS_OSPF_ABR(ospf)) { switch (ospf->abr_type) { case OSPF_ABR_STAND: if (IS_DEBUG_OSPF_EVENT) zlog_debug("ospf_ia_routing():Standard ABR"); if ((area = ospf->backbone)) { if (IS_DEBUG_OSPF_EVENT) { zlog_debug( "ospf_ia_routing():backbone area found"); zlog_debug( "ospf_ia_routing():examining summaries"); } OSPF_EXAMINE_SUMMARIES_ALL(area, rt, rtrs); for (ALL_LIST_ELEMENTS_RO(ospf->areas, node, area)) if (area != ospf->backbone) if (ospf_area_is_transit(area)) OSPF_EXAMINE_TRANSIT_SUMMARIES_ALL( area, rt, rtrs); } else if (IS_DEBUG_OSPF_EVENT) zlog_debug( "ospf_ia_routing():backbone area NOT found"); break; case OSPF_ABR_IBM: case OSPF_ABR_CISCO: if (IS_DEBUG_OSPF_EVENT) zlog_debug( "ospf_ia_routing():Alternative Cisco/IBM ABR"); area = ospf->backbone; /* Find the BB */ /* If we have an active BB connection */ if (area && ospf_act_bb_connection(ospf)) { if (IS_DEBUG_OSPF_EVENT) { zlog_debug( "ospf_ia_routing(): backbone area found"); zlog_debug( "ospf_ia_routing(): examining BB summaries"); } OSPF_EXAMINE_SUMMARIES_ALL(area, rt, rtrs); for (ALL_LIST_ELEMENTS_RO(ospf->areas, node, area)) if (area != ospf->backbone) if (ospf_area_is_transit(area)) OSPF_EXAMINE_TRANSIT_SUMMARIES_ALL( area, rt, rtrs); } else { /* No active BB connection--consider all areas */ if (IS_DEBUG_OSPF_EVENT) zlog_debug( "ospf_ia_routing(): " "Active BB connection not found"); for (ALL_LIST_ELEMENTS_RO(ospf->areas, node, area)) OSPF_EXAMINE_SUMMARIES_ALL(area, rt, rtrs); } break; case OSPF_ABR_SHORTCUT: if (IS_DEBUG_OSPF_EVENT) zlog_debug( "ospf_ia_routing():Alternative Shortcut"); area = ospf->backbone; /* Find the BB */ /* If we have an active BB connection */ if (area && ospf_act_bb_connection(ospf)) { if (IS_DEBUG_OSPF_EVENT) { zlog_debug( "ospf_ia_routing(): backbone area found"); zlog_debug( "ospf_ia_routing(): examining BB summaries"); } OSPF_EXAMINE_SUMMARIES_ALL(area, rt, rtrs); } for (ALL_LIST_ELEMENTS_RO(ospf->areas, node, area)) if (area != ospf->backbone) if (ospf_area_is_transit(area) || ((area->shortcut_configured != OSPF_SHORTCUT_DISABLE) && ((ospf->backbone == NULL) || ((area->shortcut_configured == OSPF_SHORTCUT_ENABLE) && area->shortcut_capability)))) OSPF_EXAMINE_TRANSIT_SUMMARIES_ALL( area, rt, rtrs); break; default: break; } } else { if (IS_DEBUG_OSPF_EVENT) zlog_debug( "ospf_ia_routing():not ABR, considering all areas"); for (ALL_LIST_ELEMENTS_RO(ospf->areas, node, area)) OSPF_EXAMINE_SUMMARIES_ALL(area, rt, rtrs); } } frr-7.2.1/ospfd/ospf_ia.h0000644000000000000000000000335013610377563012121 00000000000000/* * OSPF inter-area routing. * Copyright (C) 1999, 2000 Alex Zinin, Toshiaki Takada * * This file is part of GNU Zebra. * * GNU Zebra is free software; you can redistribute it and/or modify it * under the terms of the GNU General Public License as published by the * Free Software Foundation; either version 2, or (at your option) any * later version. * * GNU Zebra 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; see the file COPYING; if not, write to the Free Software * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA */ #ifndef _ZEBRA_OSPF_IA_H #define _ZEBRA_OSPF_IA_H /* Macros. */ #define OSPF_EXAMINE_SUMMARIES_ALL(A, N, R) \ { \ ospf_examine_summaries((A), SUMMARY_LSDB((A)), (N), (R)); \ ospf_examine_summaries((A), ASBR_SUMMARY_LSDB((A)), (N), (R)); \ } #define OSPF_EXAMINE_TRANSIT_SUMMARIES_ALL(A, N, R) \ { \ ospf_examine_transit_summaries((A), SUMMARY_LSDB((A)), (N), \ (R)); \ ospf_examine_transit_summaries((A), ASBR_SUMMARY_LSDB((A)), \ (N), (R)); \ } extern void ospf_ia_routing(struct ospf *, struct route_table *, struct route_table *); extern int ospf_area_is_transit(struct ospf_area *); #endif /* _ZEBRA_OSPF_IA_H */ frr-7.2.1/ospfd/ospf_interface.c0000644000000000000000000007323213610377563013471 00000000000000/* * OSPF Interface functions. * Copyright (C) 1999, 2000 Toshiaki Takada * * This file is part of GNU Zebra. * * GNU Zebra is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License as published * by the Free Software Foundation; either version 2, or (at your * option) any later version. * * GNU Zebra 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; see the file COPYING; if not, write to the Free Software * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA */ #include #include "thread.h" #include "linklist.h" #include "prefix.h" #include "if.h" #include "table.h" #include "memory.h" #include "command.h" #include "stream.h" #include "log.h" #include "zclient.h" #include "bfd.h" #include "ospfd/ospfd.h" #include "ospfd/ospf_spf.h" #include "ospfd/ospf_interface.h" #include "ospfd/ospf_ism.h" #include "ospfd/ospf_asbr.h" #include "ospfd/ospf_lsa.h" #include "ospfd/ospf_lsdb.h" #include "ospfd/ospf_neighbor.h" #include "ospfd/ospf_nsm.h" #include "ospfd/ospf_packet.h" #include "ospfd/ospf_abr.h" #include "ospfd/ospf_network.h" #include "ospfd/ospf_dump.h" DEFINE_QOBJ_TYPE(ospf_interface) DEFINE_HOOK(ospf_vl_add, (struct ospf_vl_data * vd), (vd)) DEFINE_HOOK(ospf_vl_delete, (struct ospf_vl_data * vd), (vd)) int ospf_interface_neighbor_count(struct ospf_interface *oi) { int count = 0; struct route_node *rn; struct ospf_neighbor *nbr = NULL; for (rn = route_top(oi->nbrs); rn; rn = route_next(rn)) { nbr = rn->info; if (nbr) { /* Do not show myself. */ if (nbr == oi->nbr_self) continue; /* Down state is not shown. */ if (nbr->state == NSM_Down) continue; count++; } } return count; } int ospf_if_get_output_cost(struct ospf_interface *oi) { /* If all else fails, use default OSPF cost */ uint32_t cost; uint32_t bw, refbw; /* ifp speed and bw can be 0 in some platforms, use ospf default bw if bw is configured under interface it would be used. */ if (!oi->ifp->bandwidth && oi->ifp->speed) bw = oi->ifp->speed; else bw = oi->ifp->bandwidth ? oi->ifp->bandwidth : OSPF_DEFAULT_BANDWIDTH; refbw = oi->ospf->ref_bandwidth; /* A specifed ip ospf cost overrides a calculated one. */ if (OSPF_IF_PARAM_CONFIGURED(IF_DEF_PARAMS(oi->ifp), output_cost_cmd) || OSPF_IF_PARAM_CONFIGURED(oi->params, output_cost_cmd)) cost = OSPF_IF_PARAM(oi, output_cost_cmd); /* See if a cost can be calculated from the zebra processes interface bandwidth field. */ else { cost = (uint32_t)((double)refbw / (double)bw + (double)0.5); if (cost < 1) cost = 1; else if (cost > 65535) cost = 65535; } return cost; } void ospf_if_recalculate_output_cost(struct interface *ifp) { uint32_t newcost; struct route_node *rn; for (rn = route_top(IF_OIFS(ifp)); rn; rn = route_next(rn)) { struct ospf_interface *oi; if ((oi = rn->info) == NULL) continue; newcost = ospf_if_get_output_cost(oi); /* Is actual output cost changed? */ if (oi->output_cost != newcost) { oi->output_cost = newcost; ospf_router_lsa_update_area(oi->area); } } } /* Simulate down/up on the interface. This is needed, for example, when the MTU changes. */ void ospf_if_reset(struct interface *ifp) { struct route_node *rn; for (rn = route_top(IF_OIFS(ifp)); rn; rn = route_next(rn)) { struct ospf_interface *oi; if ((oi = rn->info) == NULL) continue; ospf_if_down(oi); ospf_if_up(oi); } } void ospf_if_reset_variables(struct ospf_interface *oi) { /* Set default values. */ /* don't clear this flag. oi->flag = OSPF_IF_DISABLE; */ if (oi->vl_data) oi->type = OSPF_IFTYPE_VIRTUALLINK; else /* preserve network-type */ if (oi->type != OSPF_IFTYPE_NBMA) oi->type = OSPF_IFTYPE_BROADCAST; oi->state = ISM_Down; oi->crypt_seqnum = 0; /* This must be short, (less than RxmtInterval) - RFC 2328 Section 13.5 para 3. Set to 1 second to avoid Acks being held back for too long - MAG */ oi->v_ls_ack = 1; } /* lookup oi for specified prefix/ifp */ struct ospf_interface *ospf_if_table_lookup(struct interface *ifp, struct prefix *prefix) { struct prefix p; struct route_node *rn; struct ospf_interface *rninfo = NULL; p = *prefix; p.prefixlen = IPV4_MAX_PREFIXLEN; /* route_node_get implicitely locks */ if ((rn = route_node_lookup(IF_OIFS(ifp), &p))) { rninfo = (struct ospf_interface *)rn->info; route_unlock_node(rn); } return rninfo; } static void ospf_add_to_if(struct interface *ifp, struct ospf_interface *oi) { struct route_node *rn; struct prefix p; p = *oi->address; p.prefixlen = IPV4_MAX_PREFIXLEN; apply_mask(&p); rn = route_node_get(IF_OIFS(ifp), &p); /* rn->info should either be NULL or equal to this oi * as route_node_get may return an existing node */ assert(!rn->info || rn->info == oi); rn->info = oi; } static void ospf_delete_from_if(struct interface *ifp, struct ospf_interface *oi) { struct route_node *rn; struct prefix p; p = *oi->address; p.prefixlen = IPV4_MAX_PREFIXLEN; rn = route_node_lookup(IF_OIFS(oi->ifp), &p); assert(rn); assert(rn->info); rn->info = NULL; route_unlock_node(rn); route_unlock_node(rn); } struct ospf_interface *ospf_if_new(struct ospf *ospf, struct interface *ifp, struct prefix *p) { struct ospf_interface *oi; oi = ospf_if_table_lookup(ifp, p); if (oi) return oi; oi = XCALLOC(MTYPE_OSPF_IF, sizeof(struct ospf_interface)); oi->obuf = ospf_fifo_new(); /* Set zebra interface pointer. */ oi->ifp = ifp; oi->address = p; ospf_add_to_if(ifp, oi); listnode_add(ospf->oiflist, oi); /* Initialize neighbor list. */ oi->nbrs = route_table_init(); /* Initialize static neighbor list. */ oi->nbr_nbma = list_new(); /* Initialize Link State Acknowledgment list. */ oi->ls_ack = list_new(); oi->ls_ack_direct.ls_ack = list_new(); /* Set default values. */ ospf_if_reset_variables(oi); /* Set pseudo neighbor to Null */ oi->nbr_self = NULL; oi->ls_upd_queue = route_table_init(); oi->t_ls_upd_event = NULL; oi->t_ls_ack_direct = NULL; oi->crypt_seqnum = time(NULL); ospf_opaque_type9_lsa_init(oi); oi->ospf = ospf; QOBJ_REG(oi, ospf_interface); if (IS_DEBUG_OSPF_EVENT) zlog_debug("%s: ospf interface %s vrf %s id %u created", __PRETTY_FUNCTION__, ifp->name, ospf_vrf_id_to_name(ospf->vrf_id), ospf->vrf_id); return oi; } /* Restore an interface to its pre UP state Used from ism_interface_down only */ void ospf_if_cleanup(struct ospf_interface *oi) { struct route_node *rn; struct listnode *node, *nnode; struct ospf_neighbor *nbr; struct ospf_nbr_nbma *nbr_nbma; struct ospf_lsa *lsa; /* oi->nbrs and oi->nbr_nbma should be deleted on InterfaceDown event */ /* delete all static neighbors attached to this interface */ for (ALL_LIST_ELEMENTS(oi->nbr_nbma, node, nnode, nbr_nbma)) { OSPF_POLL_TIMER_OFF(nbr_nbma->t_poll); if (nbr_nbma->nbr) { nbr_nbma->nbr->nbr_nbma = NULL; nbr_nbma->nbr = NULL; } nbr_nbma->oi = NULL; listnode_delete(oi->nbr_nbma, nbr_nbma); } /* send Neighbor event KillNbr to all associated neighbors. */ for (rn = route_top(oi->nbrs); rn; rn = route_next(rn)) if ((nbr = rn->info) != NULL) if (nbr != oi->nbr_self) OSPF_NSM_EVENT_EXECUTE(nbr, NSM_KillNbr); /* Cleanup Link State Acknowlegdment list. */ for (ALL_LIST_ELEMENTS(oi->ls_ack, node, nnode, lsa)) ospf_lsa_unlock(&lsa); /* oi->ls_ack */ list_delete_all_node(oi->ls_ack); oi->crypt_seqnum = 0; /* Empty link state update queue */ ospf_ls_upd_queue_empty(oi); /* Reset pseudo neighbor. */ ospf_nbr_self_reset(oi, oi->ospf->router_id); } void ospf_if_free(struct ospf_interface *oi) { ospf_if_down(oi); ospf_fifo_free(oi->obuf); assert(oi->state == ISM_Down); ospf_opaque_type9_lsa_term(oi); QOBJ_UNREG(oi); /* Free Pseudo Neighbour */ ospf_nbr_delete(oi->nbr_self); route_table_finish(oi->nbrs); route_table_finish(oi->ls_upd_queue); /* Free any lists that should be freed */ list_delete(&oi->nbr_nbma); list_delete(&oi->ls_ack); list_delete(&oi->ls_ack_direct.ls_ack); if (IS_DEBUG_OSPF_EVENT) zlog_debug("%s: ospf interface %s vrf %s id %u deleted", __PRETTY_FUNCTION__, oi->ifp->name, ospf_vrf_id_to_name(oi->ifp->vrf_id), oi->ifp->vrf_id); ospf_delete_from_if(oi->ifp, oi); listnode_delete(oi->ospf->oiflist, oi); listnode_delete(oi->area->oiflist, oi); thread_cancel_event(master, oi); memset(oi, 0, sizeof(*oi)); XFREE(MTYPE_OSPF_IF, oi); } int ospf_if_is_up(struct ospf_interface *oi) { return if_is_up(oi->ifp); } struct ospf_interface *ospf_if_exists(struct ospf_interface *oic) { struct listnode *node; struct ospf *ospf; struct ospf_interface *oi; if (!oic) return NULL; ospf = oic->ospf; if (ospf == NULL) return NULL; for (ALL_LIST_ELEMENTS_RO(ospf->oiflist, node, oi)) if (oi == oic) return oi; return NULL; } /* Lookup OSPF interface by router LSA posistion */ struct ospf_interface *ospf_if_lookup_by_lsa_pos(struct ospf_area *area, int lsa_pos) { struct listnode *node; struct ospf_interface *oi; for (ALL_LIST_ELEMENTS_RO(area->oiflist, node, oi)) { if (lsa_pos >= oi->lsa_pos_beg && lsa_pos < oi->lsa_pos_end) return oi; } return NULL; } struct ospf_interface *ospf_if_lookup_by_local_addr(struct ospf *ospf, struct interface *ifp, struct in_addr address) { struct listnode *node; struct ospf_interface *oi; for (ALL_LIST_ELEMENTS_RO(ospf->oiflist, node, oi)) if (oi->type != OSPF_IFTYPE_VIRTUALLINK) { if (ifp && oi->ifp != ifp) continue; if (IPV4_ADDR_SAME(&address, &oi->address->u.prefix4)) return oi; } return NULL; } struct ospf_interface *ospf_if_lookup_by_prefix(struct ospf *ospf, struct prefix_ipv4 *p) { struct listnode *node; struct ospf_interface *oi; /* Check each Interface. */ for (ALL_LIST_ELEMENTS_RO(ospf->oiflist, node, oi)) { if (oi->type != OSPF_IFTYPE_VIRTUALLINK) { struct prefix ptmp; prefix_copy(&ptmp, CONNECTED_PREFIX(oi->connected)); apply_mask(&ptmp); if (prefix_same(&ptmp, (struct prefix *)p)) return oi; } } return NULL; } /* determine receiving interface by ifp and source address */ struct ospf_interface *ospf_if_lookup_recv_if(struct ospf *ospf, struct in_addr src, struct interface *ifp) { struct route_node *rn; struct prefix_ipv4 addr; struct ospf_interface *oi, *match; addr.family = AF_INET; addr.prefix = src; addr.prefixlen = IPV4_MAX_BITLEN; match = NULL; for (rn = route_top(IF_OIFS(ifp)); rn; rn = route_next(rn)) { oi = rn->info; if (!oi) /* oi can be NULL for PtP aliases */ continue; if (oi->type == OSPF_IFTYPE_VIRTUALLINK) continue; if (if_is_loopback(oi->ifp) || if_is_vrf(oi->ifp)) continue; if (CHECK_FLAG(oi->connected->flags, ZEBRA_IFA_UNNUMBERED)) match = oi; else if (prefix_match(CONNECTED_PREFIX(oi->connected), (struct prefix *)&addr)) { if ((match == NULL) || (match->address->prefixlen < oi->address->prefixlen)) match = oi; } } return match; } static void ospf_if_reset_stats(struct ospf_interface *oi) { oi->hello_in = oi->hello_out = 0; oi->db_desc_in = oi->db_desc_out = 0; oi->ls_req_in = oi->ls_req_out = 0; oi->ls_upd_in = oi->ls_upd_out = 0; oi->ls_ack_in = oi->ls_ack_out = 0; } void ospf_if_stream_unset(struct ospf_interface *oi) { struct ospf *ospf = oi->ospf; /* flush the interface packet queue */ ospf_fifo_flush(oi->obuf); /*reset protocol stats */ ospf_if_reset_stats(oi); if (oi->on_write_q) { listnode_delete(ospf->oi_write_q, oi); if (list_isempty(ospf->oi_write_q)) OSPF_TIMER_OFF(ospf->t_write); oi->on_write_q = 0; } } static struct ospf_if_params *ospf_new_if_params(void) { struct ospf_if_params *oip; oip = XCALLOC(MTYPE_OSPF_IF_PARAMS, sizeof(struct ospf_if_params)); UNSET_IF_PARAM(oip, output_cost_cmd); UNSET_IF_PARAM(oip, transmit_delay); UNSET_IF_PARAM(oip, retransmit_interval); UNSET_IF_PARAM(oip, passive_interface); UNSET_IF_PARAM(oip, v_hello); UNSET_IF_PARAM(oip, fast_hello); UNSET_IF_PARAM(oip, v_wait); UNSET_IF_PARAM(oip, priority); UNSET_IF_PARAM(oip, type); UNSET_IF_PARAM(oip, auth_simple); UNSET_IF_PARAM(oip, auth_crypt); UNSET_IF_PARAM(oip, auth_type); oip->auth_crypt = list_new(); oip->network_lsa_seqnum = htonl(OSPF_INITIAL_SEQUENCE_NUMBER); return oip; } void ospf_del_if_params(struct ospf_if_params *oip) { list_delete(&oip->auth_crypt); bfd_info_free(&(oip->bfd_info)); XFREE(MTYPE_OSPF_IF_PARAMS, oip); } void ospf_free_if_params(struct interface *ifp, struct in_addr addr) { struct ospf_if_params *oip; struct prefix_ipv4 p; struct route_node *rn; p.family = AF_INET; p.prefixlen = IPV4_MAX_PREFIXLEN; p.prefix = addr; rn = route_node_lookup(IF_OIFS_PARAMS(ifp), (struct prefix *)&p); if (!rn || !rn->info) return; oip = rn->info; route_unlock_node(rn); if (!OSPF_IF_PARAM_CONFIGURED(oip, output_cost_cmd) && !OSPF_IF_PARAM_CONFIGURED(oip, transmit_delay) && !OSPF_IF_PARAM_CONFIGURED(oip, retransmit_interval) && !OSPF_IF_PARAM_CONFIGURED(oip, passive_interface) && !OSPF_IF_PARAM_CONFIGURED(oip, v_hello) && !OSPF_IF_PARAM_CONFIGURED(oip, fast_hello) && !OSPF_IF_PARAM_CONFIGURED(oip, v_wait) && !OSPF_IF_PARAM_CONFIGURED(oip, priority) && !OSPF_IF_PARAM_CONFIGURED(oip, type) && !OSPF_IF_PARAM_CONFIGURED(oip, auth_simple) && !OSPF_IF_PARAM_CONFIGURED(oip, auth_type) && listcount(oip->auth_crypt) == 0 && ntohl(oip->network_lsa_seqnum) != OSPF_INITIAL_SEQUENCE_NUMBER) { ospf_del_if_params(oip); rn->info = NULL; route_unlock_node(rn); } } struct ospf_if_params *ospf_lookup_if_params(struct interface *ifp, struct in_addr addr) { struct prefix_ipv4 p; struct route_node *rn; p.family = AF_INET; p.prefixlen = IPV4_MAX_PREFIXLEN; p.prefix = addr; rn = route_node_lookup(IF_OIFS_PARAMS(ifp), (struct prefix *)&p); if (rn) { route_unlock_node(rn); return rn->info; } return NULL; } struct ospf_if_params *ospf_get_if_params(struct interface *ifp, struct in_addr addr) { struct prefix_ipv4 p; struct route_node *rn; p.family = AF_INET; p.prefixlen = IPV4_MAX_PREFIXLEN; p.prefix = addr; apply_mask_ipv4(&p); rn = route_node_get(IF_OIFS_PARAMS(ifp), (struct prefix *)&p); if (rn->info == NULL) rn->info = ospf_new_if_params(); else route_unlock_node(rn); return rn->info; } void ospf_if_update_params(struct interface *ifp, struct in_addr addr) { struct route_node *rn; struct ospf_interface *oi; for (rn = route_top(IF_OIFS(ifp)); rn; rn = route_next(rn)) { if ((oi = rn->info) == NULL) continue; if (IPV4_ADDR_SAME(&oi->address->u.prefix4, &addr)) oi->params = ospf_lookup_if_params( ifp, oi->address->u.prefix4); } } int ospf_if_new_hook(struct interface *ifp) { int rc = 0; ifp->info = XCALLOC(MTYPE_OSPF_IF_INFO, sizeof(struct ospf_if_info)); IF_OIFS(ifp) = route_table_init(); IF_OIFS_PARAMS(ifp) = route_table_init(); IF_DEF_PARAMS(ifp) = ospf_new_if_params(); SET_IF_PARAM(IF_DEF_PARAMS(ifp), transmit_delay); IF_DEF_PARAMS(ifp)->transmit_delay = OSPF_TRANSMIT_DELAY_DEFAULT; SET_IF_PARAM(IF_DEF_PARAMS(ifp), retransmit_interval); IF_DEF_PARAMS(ifp)->retransmit_interval = OSPF_RETRANSMIT_INTERVAL_DEFAULT; SET_IF_PARAM(IF_DEF_PARAMS(ifp), priority); IF_DEF_PARAMS(ifp)->priority = OSPF_ROUTER_PRIORITY_DEFAULT; IF_DEF_PARAMS(ifp)->mtu_ignore = OSPF_MTU_IGNORE_DEFAULT; SET_IF_PARAM(IF_DEF_PARAMS(ifp), v_hello); IF_DEF_PARAMS(ifp)->v_hello = OSPF_HELLO_INTERVAL_DEFAULT; SET_IF_PARAM(IF_DEF_PARAMS(ifp), fast_hello); IF_DEF_PARAMS(ifp)->fast_hello = OSPF_FAST_HELLO_DEFAULT; SET_IF_PARAM(IF_DEF_PARAMS(ifp), v_wait); IF_DEF_PARAMS(ifp)->v_wait = OSPF_ROUTER_DEAD_INTERVAL_DEFAULT; SET_IF_PARAM(IF_DEF_PARAMS(ifp), auth_simple); memset(IF_DEF_PARAMS(ifp)->auth_simple, 0, OSPF_AUTH_SIMPLE_SIZE); SET_IF_PARAM(IF_DEF_PARAMS(ifp), auth_type); IF_DEF_PARAMS(ifp)->auth_type = OSPF_AUTH_NOTSET; rc = ospf_opaque_new_if(ifp); return rc; } static int ospf_if_delete_hook(struct interface *ifp) { int rc = 0; struct route_node *rn; rc = ospf_opaque_del_if(ifp); route_table_finish(IF_OIFS(ifp)); for (rn = route_top(IF_OIFS_PARAMS(ifp)); rn; rn = route_next(rn)) if (rn->info) ospf_del_if_params(rn->info); route_table_finish(IF_OIFS_PARAMS(ifp)); ospf_del_if_params((struct ospf_if_params *)IF_DEF_PARAMS(ifp)); XFREE(MTYPE_OSPF_IF_INFO, ifp->info); ifp->info = NULL; return rc; } int ospf_if_is_enable(struct ospf_interface *oi) { if (!(if_is_loopback(oi->ifp) || if_is_vrf(oi->ifp))) if (if_is_up(oi->ifp)) return 1; return 0; } void ospf_if_set_multicast(struct ospf_interface *oi) { if ((oi->state > ISM_Loopback) && (oi->type != OSPF_IFTYPE_LOOPBACK) && (oi->type != OSPF_IFTYPE_VIRTUALLINK) && (OSPF_IF_PASSIVE_STATUS(oi) == OSPF_IF_ACTIVE)) { /* The interface should belong to the OSPF-all-routers group. */ if (!OI_MEMBER_CHECK(oi, MEMBER_ALLROUTERS) && (ospf_if_add_allspfrouters(oi->ospf, oi->address, oi->ifp->ifindex) >= 0)) /* Set the flag only if the system call to join * succeeded. */ OI_MEMBER_JOINED(oi, MEMBER_ALLROUTERS); } else { /* The interface should NOT belong to the OSPF-all-routers * group. */ if (OI_MEMBER_CHECK(oi, MEMBER_ALLROUTERS)) { /* Only actually drop if this is the last reference */ if (OI_MEMBER_COUNT(oi, MEMBER_ALLROUTERS) == 1) ospf_if_drop_allspfrouters(oi->ospf, oi->address, oi->ifp->ifindex); /* Unset the flag regardless of whether the system call to leave the group succeeded, since it's much safer to assume that we are not a member. */ OI_MEMBER_LEFT(oi, MEMBER_ALLROUTERS); } } if (((oi->type == OSPF_IFTYPE_BROADCAST) || (oi->type == OSPF_IFTYPE_POINTOPOINT)) && ((oi->state == ISM_DR) || (oi->state == ISM_Backup)) && (OSPF_IF_PASSIVE_STATUS(oi) == OSPF_IF_ACTIVE)) { /* The interface should belong to the OSPF-designated-routers * group. */ if (!OI_MEMBER_CHECK(oi, MEMBER_DROUTERS) && (ospf_if_add_alldrouters(oi->ospf, oi->address, oi->ifp->ifindex) >= 0)) /* Set the flag only if the system call to join * succeeded. */ OI_MEMBER_JOINED(oi, MEMBER_DROUTERS); } else { /* The interface should NOT belong to the * OSPF-designated-routers group */ if (OI_MEMBER_CHECK(oi, MEMBER_DROUTERS)) { /* drop only if last reference */ if (OI_MEMBER_COUNT(oi, MEMBER_DROUTERS) == 1) ospf_if_drop_alldrouters(oi->ospf, oi->address, oi->ifp->ifindex); /* Unset the flag regardless of whether the system call to leave the group succeeded, since it's much safer to assume that we are not a member. */ OI_MEMBER_LEFT(oi, MEMBER_DROUTERS); } } } int ospf_if_up(struct ospf_interface *oi) { if (oi == NULL) return 0; if (oi->type == OSPF_IFTYPE_LOOPBACK) OSPF_ISM_EVENT_SCHEDULE(oi, ISM_LoopInd); else { OSPF_ISM_EVENT_SCHEDULE(oi, ISM_InterfaceUp); } return 1; } int ospf_if_down(struct ospf_interface *oi) { if (oi == NULL) return 0; OSPF_ISM_EVENT_EXECUTE(oi, ISM_InterfaceDown); /* delete position in router LSA */ oi->lsa_pos_beg = 0; oi->lsa_pos_end = 0; /* Shutdown packet reception and sending */ ospf_if_stream_unset(oi); return 1; } /* Virtual Link related functions. */ struct ospf_vl_data *ospf_vl_data_new(struct ospf_area *area, struct in_addr vl_peer) { struct ospf_vl_data *vl_data; vl_data = XCALLOC(MTYPE_OSPF_VL_DATA, sizeof(struct ospf_vl_data)); vl_data->vl_peer.s_addr = vl_peer.s_addr; vl_data->vl_area_id = area->area_id; vl_data->vl_area_id_fmt = area->area_id_fmt; return vl_data; } void ospf_vl_data_free(struct ospf_vl_data *vl_data) { XFREE(MTYPE_OSPF_VL_DATA, vl_data); } unsigned int vlink_count = 0; struct ospf_interface *ospf_vl_new(struct ospf *ospf, struct ospf_vl_data *vl_data) { struct ospf_interface *voi; struct interface *vi; char ifname[INTERFACE_NAMSIZ]; struct ospf_area *area; struct in_addr area_id; struct connected *co; struct prefix_ipv4 *p; if (IS_DEBUG_OSPF_EVENT) zlog_debug("ospf_vl_new(): Start"); if (vlink_count == OSPF_VL_MAX_COUNT) { if (IS_DEBUG_OSPF_EVENT) zlog_debug( "ospf_vl_new(): Alarm: " "cannot create more than OSPF_MAX_VL_COUNT virtual links"); return NULL; } if (IS_DEBUG_OSPF_EVENT) zlog_debug( "ospf_vl_new(): creating pseudo zebra interface vrf id %u", ospf->vrf_id); snprintf(ifname, sizeof(ifname), "VLINK%u", vlink_count); vi = if_create(ifname, ospf->vrf_id); /* * if_create sets ZEBRA_INTERFACE_LINKDETECTION * virtual links don't need this. */ UNSET_FLAG(vi->status, ZEBRA_INTERFACE_LINKDETECTION); co = connected_new(); co->ifp = vi; listnode_add(vi->connected, co); p = prefix_ipv4_new(); p->family = AF_INET; p->prefix.s_addr = 0; p->prefixlen = 0; co->address = (struct prefix *)p; voi = ospf_if_new(ospf, vi, co->address); if (voi == NULL) { if (IS_DEBUG_OSPF_EVENT) zlog_debug( "ospf_vl_new(): Alarm: OSPF int structure is not created"); return NULL; } voi->connected = co; voi->vl_data = vl_data; voi->ifp->mtu = OSPF_VL_MTU; voi->type = OSPF_IFTYPE_VIRTUALLINK; vlink_count++; if (IS_DEBUG_OSPF_EVENT) zlog_debug("ospf_vl_new(): Created name: %s", ifname); if (IS_DEBUG_OSPF_EVENT) zlog_debug("ospf_vl_new(): set if->name to %s", vi->name); area_id.s_addr = 0; area = ospf_area_get(ospf, area_id); voi->area = area; if (IS_DEBUG_OSPF_EVENT) zlog_debug( "ospf_vl_new(): set associated area to the backbone"); /* Add pseudo neighbor. */ ospf_nbr_self_reset(voi, voi->ospf->router_id); ospf_area_add_if(voi->area, voi); if (IS_DEBUG_OSPF_EVENT) zlog_debug("ospf_vl_new(): Stop"); return voi; } static void ospf_vl_if_delete(struct ospf_vl_data *vl_data) { struct interface *ifp = vl_data->vl_oi->ifp; vl_data->vl_oi->address->u.prefix4.s_addr = 0; vl_data->vl_oi->address->prefixlen = 0; ospf_if_free(vl_data->vl_oi); if_delete(ifp); vlink_count--; } /* for a defined area, count the number of configured vl */ int ospf_vl_count(struct ospf *ospf, struct ospf_area *area) { int count = 0; struct ospf_vl_data *vl_data; struct listnode *node; for (ALL_LIST_ELEMENTS_RO(ospf->vlinks, node, vl_data)) { if (area && !IPV4_ADDR_SAME(&vl_data->vl_area_id, &area->area_id)) continue; count++; } return count; } /* Look up vl_data for given peer, optionally qualified to be in the * specified area. NULL area returns first found.. */ struct ospf_vl_data *ospf_vl_lookup(struct ospf *ospf, struct ospf_area *area, struct in_addr vl_peer) { struct ospf_vl_data *vl_data; struct listnode *node; if (IS_DEBUG_OSPF_EVENT) { zlog_debug("%s: Looking for %s", __func__, inet_ntoa(vl_peer)); if (area) zlog_debug("%s: in area %s", __func__, inet_ntoa(area->area_id)); } for (ALL_LIST_ELEMENTS_RO(ospf->vlinks, node, vl_data)) { if (IS_DEBUG_OSPF_EVENT) zlog_debug("%s: VL %s, peer %s", __func__, vl_data->vl_oi->ifp->name, inet_ntoa(vl_data->vl_peer)); if (area && !IPV4_ADDR_SAME(&vl_data->vl_area_id, &area->area_id)) continue; if (IPV4_ADDR_SAME(&vl_data->vl_peer, &vl_peer)) return vl_data; } return NULL; } static void ospf_vl_shutdown(struct ospf_vl_data *vl_data) { struct ospf_interface *oi; if ((oi = vl_data->vl_oi) == NULL) return; oi->address->u.prefix4.s_addr = 0; oi->address->prefixlen = 0; UNSET_FLAG(oi->ifp->flags, IFF_UP); /* OSPF_ISM_EVENT_SCHEDULE (oi, ISM_InterfaceDown); */ OSPF_ISM_EVENT_EXECUTE(oi, ISM_InterfaceDown); } void ospf_vl_add(struct ospf *ospf, struct ospf_vl_data *vl_data) { listnode_add(ospf->vlinks, vl_data); hook_call(ospf_vl_add, vl_data); } void ospf_vl_delete(struct ospf *ospf, struct ospf_vl_data *vl_data) { ospf_vl_shutdown(vl_data); ospf_vl_if_delete(vl_data); hook_call(ospf_vl_delete, vl_data); listnode_delete(ospf->vlinks, vl_data); ospf_vl_data_free(vl_data); } static int ospf_vl_set_params(struct ospf_vl_data *vl_data, struct vertex *v) { int changed = 0; struct ospf_interface *voi; struct listnode *node; struct vertex_parent *vp = NULL; unsigned int i; struct router_lsa *rl; voi = vl_data->vl_oi; if (voi->output_cost != v->distance) { voi->output_cost = v->distance; changed = 1; } for (ALL_LIST_ELEMENTS_RO(v->parents, node, vp)) { vl_data->nexthop.oi = vp->nexthop->oi; vl_data->nexthop.router = vp->nexthop->router; if (!IPV4_ADDR_SAME(&voi->address->u.prefix4, &vl_data->nexthop.oi->address->u.prefix4)) changed = 1; voi->address->u.prefix4 = vl_data->nexthop.oi->address->u.prefix4; voi->address->prefixlen = vl_data->nexthop.oi->address->prefixlen; break; /* We take the first interface. */ } rl = (struct router_lsa *)v->lsa; /* use SPF determined backlink index in struct vertex * for virtual link destination address */ if (vp && vp->backlink >= 0) { if (!IPV4_ADDR_SAME(&vl_data->peer_addr, &rl->link[vp->backlink].link_data)) changed = 1; vl_data->peer_addr = rl->link[vp->backlink].link_data; } else { /* This is highly odd, there is no backlink index * there should be due to the ospf_spf_has_link() check * in SPF. Lets warn and try pick a link anyway. */ zlog_info("ospf_vl_set_params: No backlink for %s!", vl_data->vl_oi->ifp->name); for (i = 0; i < ntohs(rl->links); i++) { switch (rl->link[i].type) { case LSA_LINK_TYPE_VIRTUALLINK: if (IS_DEBUG_OSPF_EVENT) zlog_debug( "found back link through VL"); /* fallthru */ case LSA_LINK_TYPE_TRANSIT: case LSA_LINK_TYPE_POINTOPOINT: if (!IPV4_ADDR_SAME(&vl_data->peer_addr, &rl->link[i].link_data)) changed = 1; vl_data->peer_addr = rl->link[i].link_data; } } } if (IS_DEBUG_OSPF_EVENT) zlog_debug("%s: %s peer address: %s, cost: %d,%schanged", __func__, vl_data->vl_oi->ifp->name, inet_ntoa(vl_data->peer_addr), voi->output_cost, (changed ? " " : " un")); return changed; } void ospf_vl_up_check(struct ospf_area *area, struct in_addr rid, struct vertex *v) { struct ospf *ospf = area->ospf; struct listnode *node; struct ospf_vl_data *vl_data; struct ospf_interface *oi; if (IS_DEBUG_OSPF_EVENT) { zlog_debug("ospf_vl_up_check(): Start"); zlog_debug("ospf_vl_up_check(): Router ID is %s", inet_ntoa(rid)); zlog_debug("ospf_vl_up_check(): Area is %s", inet_ntoa(area->area_id)); } for (ALL_LIST_ELEMENTS_RO(ospf->vlinks, node, vl_data)) { if (IS_DEBUG_OSPF_EVENT) { zlog_debug("%s: considering VL, %s in area %s", __func__, vl_data->vl_oi->ifp->name, inet_ntoa(vl_data->vl_area_id)); zlog_debug("%s: peer ID: %s", __func__, inet_ntoa(vl_data->vl_peer)); } if (IPV4_ADDR_SAME(&vl_data->vl_peer, &rid) && IPV4_ADDR_SAME(&vl_data->vl_area_id, &area->area_id)) { oi = vl_data->vl_oi; SET_FLAG(vl_data->flags, OSPF_VL_FLAG_APPROVED); if (IS_DEBUG_OSPF_EVENT) zlog_debug( "ospf_vl_up_check(): this VL matched"); if (oi->state == ISM_Down) { if (IS_DEBUG_OSPF_EVENT) zlog_debug( "ospf_vl_up_check(): VL is down, waking it up"); SET_FLAG(oi->ifp->flags, IFF_UP); OSPF_ISM_EVENT_EXECUTE(oi, ISM_InterfaceUp); } if (ospf_vl_set_params(vl_data, v)) { if (IS_DEBUG_OSPF(ism, ISM_EVENTS)) zlog_debug( "ospf_vl_up_check: VL cost change," " scheduling router lsa refresh"); if (ospf->backbone) ospf_router_lsa_update_area( ospf->backbone); else if (IS_DEBUG_OSPF(ism, ISM_EVENTS)) zlog_debug( "ospf_vl_up_check: VL cost change, no backbone!"); } } } } void ospf_vl_unapprove(struct ospf *ospf) { struct listnode *node; struct ospf_vl_data *vl_data; for (ALL_LIST_ELEMENTS_RO(ospf->vlinks, node, vl_data)) UNSET_FLAG(vl_data->flags, OSPF_VL_FLAG_APPROVED); } void ospf_vl_shut_unapproved(struct ospf *ospf) { struct listnode *node, *nnode; struct ospf_vl_data *vl_data; for (ALL_LIST_ELEMENTS(ospf->vlinks, node, nnode, vl_data)) if (!CHECK_FLAG(vl_data->flags, OSPF_VL_FLAG_APPROVED)) ospf_vl_shutdown(vl_data); } int ospf_full_virtual_nbrs(struct ospf_area *area) { if (IS_DEBUG_OSPF_EVENT) { zlog_debug( "counting fully adjacent virtual neighbors in area %s", inet_ntoa(area->area_id)); zlog_debug("there are %d of them", area->full_vls); } return area->full_vls; } int ospf_vls_in_area(struct ospf_area *area) { struct listnode *node; struct ospf_vl_data *vl_data; int c = 0; for (ALL_LIST_ELEMENTS_RO(area->ospf->vlinks, node, vl_data)) if (IPV4_ADDR_SAME(&vl_data->vl_area_id, &area->area_id)) c++; return c; } struct crypt_key *ospf_crypt_key_new(void) { return XCALLOC(MTYPE_OSPF_CRYPT_KEY, sizeof(struct crypt_key)); } void ospf_crypt_key_add(struct list *crypt, struct crypt_key *ck) { listnode_add(crypt, ck); } struct crypt_key *ospf_crypt_key_lookup(struct list *auth_crypt, uint8_t key_id) { struct listnode *node; struct crypt_key *ck; for (ALL_LIST_ELEMENTS_RO(auth_crypt, node, ck)) if (ck->key_id == key_id) return ck; return NULL; } int ospf_crypt_key_delete(struct list *auth_crypt, uint8_t key_id) { struct listnode *node, *nnode; struct crypt_key *ck; for (ALL_LIST_ELEMENTS(auth_crypt, node, nnode, ck)) { if (ck->key_id == key_id) { listnode_delete(auth_crypt, ck); XFREE(MTYPE_OSPF_CRYPT_KEY, ck); return 1; } } return 0; } uint8_t ospf_default_iftype(struct interface *ifp) { if (if_is_pointopoint(ifp)) return OSPF_IFTYPE_POINTOPOINT; else if (if_is_loopback(ifp) || if_is_vrf(ifp)) return OSPF_IFTYPE_LOOPBACK; else return OSPF_IFTYPE_BROADCAST; } void ospf_if_init(void) { /* Initialize Zebra interface data structure. */ hook_register_prio(if_add, 0, ospf_if_new_hook); hook_register_prio(if_del, 0, ospf_if_delete_hook); } frr-7.2.1/ospfd/ospf_interface.h0000644000000000000000000003004313610377563013467 00000000000000/* * OSPF Interface functions. * Copyright (C) 1999 Toshiaki Takada * * This file is part of GNU Zebra. * * GNU Zebra is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License as published * by the Free Software Foundation; either version 2, or (at your * option) any later version. * * GNU Zebra 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; see the file COPYING; if not, write to the Free Software * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA */ #ifndef _ZEBRA_OSPF_INTERFACE_H #define _ZEBRA_OSPF_INTERFACE_H #include "qobj.h" #include "hook.h" #include "ospfd/ospf_packet.h" #include "ospfd/ospf_spf.h" #define IF_OSPF_IF_INFO(I) ((struct ospf_if_info *)((I)->info)) #define IF_DEF_PARAMS(I) (IF_OSPF_IF_INFO (I)->def_params) #define IF_OIFS(I) (IF_OSPF_IF_INFO (I)->oifs) #define IF_OIFS_PARAMS(I) (IF_OSPF_IF_INFO (I)->params) /* Despite the name, this macro probably is for specialist use only */ #define OSPF_IF_PARAM_CONFIGURED(S, P) ((S) && (S)->P##__config) /* Test whether an OSPF interface parameter is set, generally, given some * existing ospf interface */ #define OSPF_IF_PARAM_IS_SET(O, P) \ (OSPF_IF_PARAM_CONFIGURED((O)->params, P) \ || OSPF_IF_PARAM_CONFIGURED(IF_DEF_PARAMS((O)->ifp)->P)) #define OSPF_IF_PARAM(O, P) \ (OSPF_IF_PARAM_CONFIGURED((O)->params, P) \ ? (O)->params->P \ : IF_DEF_PARAMS((O)->ifp)->P) #define DECLARE_IF_PARAM(T, P) \ T P; \ uint8_t P##__config : 1 #define UNSET_IF_PARAM(S, P) ((S)->P##__config) = 0 #define SET_IF_PARAM(S, P) ((S)->P##__config) = 1 struct ospf_if_params { DECLARE_IF_PARAM(uint32_t, transmit_delay); /* Interface Transmisson Delay */ DECLARE_IF_PARAM(uint32_t, output_cost_cmd); /* Command Interface Output Cost */ DECLARE_IF_PARAM(uint32_t, retransmit_interval); /* Retransmission Interval */ DECLARE_IF_PARAM(uint8_t, passive_interface); /* OSPF Interface is passive: no sending or receiving (no need to join multicast groups) */ DECLARE_IF_PARAM(uint8_t, priority); /* OSPF Interface priority */ /* Enable OSPF on this interface with area if_area */ DECLARE_IF_PARAM(struct in_addr, if_area); uint32_t if_area_id_fmt; DECLARE_IF_PARAM(uint8_t, type); /* type of interface */ #define OSPF_IF_ACTIVE 0 #define OSPF_IF_PASSIVE 1 #define OSPF_IF_PASSIVE_STATUS(O) \ (OSPF_IF_PARAM_CONFIGURED((O)->params, passive_interface) \ ? (O)->params->passive_interface \ : (OSPF_IF_PARAM_CONFIGURED(IF_DEF_PARAMS((O)->ifp), \ passive_interface) \ ? IF_DEF_PARAMS((O)->ifp)->passive_interface \ : (O)->ospf->passive_interface_default)) DECLARE_IF_PARAM(uint32_t, v_hello); /* Hello Interval */ DECLARE_IF_PARAM(uint32_t, v_wait); /* Router Dead Interval */ /* MTU mismatch check (see RFC2328, chap 10.6) */ DECLARE_IF_PARAM(uint8_t, mtu_ignore); /* Fast-Hellos */ DECLARE_IF_PARAM(uint8_t, fast_hello); /* Authentication data. */ uint8_t auth_simple[OSPF_AUTH_SIMPLE_SIZE + 1]; /* Simple password. */ uint8_t auth_simple__config : 1; DECLARE_IF_PARAM(struct list *, auth_crypt); /* List of Auth cryptographic data. */ DECLARE_IF_PARAM(int, auth_type); /* OSPF authentication type */ /* Other, non-configuration state */ uint32_t network_lsa_seqnum; /* Network LSA seqnum */ /* BFD configuration */ struct bfd_info *bfd_info; }; enum { MEMBER_ALLROUTERS = 0, MEMBER_DROUTERS, MEMBER_MAX, }; struct ospf_if_info { struct ospf_if_params *def_params; struct route_table *params; struct route_table *oifs; unsigned int membership_counts[MEMBER_MAX]; /* multicast group refcnts */ }; struct ospf_interface; struct ospf_vl_data { struct in_addr vl_peer; /* Router-ID of the peer */ struct in_addr vl_area_id; /* Transit area */ int vl_area_id_fmt; /* Area ID format */ struct ospf_interface *vl_oi; /* Interface data structure */ struct vertex_nexthop nexthop; /* Nexthop router and oi to use */ struct in_addr peer_addr; /* Address used to reach the peer */ uint8_t flags; }; #define OSPF_VL_MAX_COUNT 256 #define OSPF_VL_MTU 1500 #define OSPF_VL_FLAG_APPROVED 0x01 struct crypt_key { uint8_t key_id; uint8_t auth_key[OSPF_AUTH_MD5_SIZE + 1]; }; /* OSPF interface structure. */ struct ospf_interface { /* This interface's parent ospf instance. */ struct ospf *ospf; /* OSPF Area. */ struct ospf_area *area; /* Position range in Router LSA */ uint16_t lsa_pos_beg; /* inclusive, >= */ uint16_t lsa_pos_end; /* exclusive, < */ /* Interface data from zebra. */ struct interface *ifp; struct ospf_vl_data *vl_data; /* Data for Virtual Link */ /* Packet send buffer. */ struct ospf_fifo *obuf; /* Output queue */ /* OSPF Network Type. */ uint8_t type; /* State of Interface State Machine. */ uint8_t state; /* To which multicast groups do we currently belong? */ uint8_t multicast_memberships; #define OI_MEMBER_FLAG(M) (1 << (M)) #define OI_MEMBER_COUNT(O,M) (IF_OSPF_IF_INFO(oi->ifp)->membership_counts[(M)]) #define OI_MEMBER_CHECK(O, M) \ (CHECK_FLAG((O)->multicast_memberships, OI_MEMBER_FLAG(M))) #define OI_MEMBER_JOINED(O, M) \ do { \ SET_FLAG((O)->multicast_memberships, OI_MEMBER_FLAG(M)); \ IF_OSPF_IF_INFO((O)->ifp)->membership_counts[(M)]++; \ } while (0) #define OI_MEMBER_LEFT(O, M) \ do { \ UNSET_FLAG((O)->multicast_memberships, OI_MEMBER_FLAG(M)); \ IF_OSPF_IF_INFO((O)->ifp)->membership_counts[(M)]--; \ } while (0) struct prefix *address; /* Interface prefix */ struct connected *connected; /* Pointer to connected */ /* Configured varables. */ struct ospf_if_params *params; uint32_t crypt_seqnum; /* Cryptographic Sequence Number */ uint32_t output_cost; /* Acutual Interface Output Cost */ /* Neighbor information. */ struct route_table *nbrs; /* OSPF Neighbor List */ struct ospf_neighbor *nbr_self; /* Neighbor Self */ #define DR(I) ((I)->nbr_self->d_router) #define BDR(I) ((I)->nbr_self->bd_router) #define OPTIONS(I) ((I)->nbr_self->options) #define PRIORITY(I) ((I)->nbr_self->priority) /* List of configured NBMA neighbor. */ struct list *nbr_nbma; /* self-originated LSAs. */ struct ospf_lsa *network_lsa_self; /* network-LSA. */ struct list *opaque_lsa_self; /* Type-9 Opaque-LSAs */ struct route_table *ls_upd_queue; struct list *ls_ack; /* Link State Acknowledgment list. */ struct { struct list *ls_ack; struct in_addr dst; } ls_ack_direct; /* Timer values. */ uint32_t v_ls_ack; /* Delayed Link State Acknowledgment */ /* Threads. */ struct thread *t_hello; /* timer */ struct thread *t_wait; /* timer */ struct thread *t_ls_ack; /* timer */ struct thread *t_ls_ack_direct; /* event */ struct thread *t_ls_upd_event; /* event */ struct thread *t_opaque_lsa_self; /* Type-9 Opaque-LSAs */ int on_write_q; /* Statistics fields. */ uint32_t hello_in; /* Hello message input count. */ uint32_t hello_out; /* Hello message output count. */ uint32_t db_desc_in; /* database desc. message input count. */ uint32_t db_desc_out; /* database desc. message output count. */ uint32_t ls_req_in; /* LS request message input count. */ uint32_t ls_req_out; /* LS request message output count. */ uint32_t ls_upd_in; /* LS update message input count. */ uint32_t ls_upd_out; /* LS update message output count. */ uint32_t ls_ack_in; /* LS Ack message input count. */ uint32_t ls_ack_out; /* LS Ack message output count. */ uint32_t discarded; /* discarded input count by error. */ uint32_t state_change; /* Number of status change. */ uint32_t full_nbrs; QOBJ_FIELDS }; DECLARE_QOBJ_TYPE(ospf_interface) /* Prototypes. */ extern char *ospf_if_name(struct ospf_interface *); extern struct ospf_interface *ospf_if_new(struct ospf *, struct interface *, struct prefix *); extern void ospf_if_cleanup(struct ospf_interface *); extern void ospf_if_free(struct ospf_interface *); extern int ospf_if_up(struct ospf_interface *); extern int ospf_if_down(struct ospf_interface *); extern int ospf_if_is_up(struct ospf_interface *); extern struct ospf_interface *ospf_if_exists(struct ospf_interface *); extern struct ospf_interface *ospf_if_lookup_by_lsa_pos(struct ospf_area *, int); extern struct ospf_interface * ospf_if_lookup_by_local_addr(struct ospf *, struct interface *, struct in_addr); extern struct ospf_interface *ospf_if_lookup_by_prefix(struct ospf *, struct prefix_ipv4 *); extern struct ospf_interface *ospf_if_table_lookup(struct interface *, struct prefix *); extern struct ospf_interface *ospf_if_addr_local(struct in_addr); extern struct ospf_interface * ospf_if_lookup_recv_if(struct ospf *, struct in_addr, struct interface *); extern struct ospf_interface *ospf_if_is_configured(struct ospf *, struct in_addr *); extern struct ospf_if_params *ospf_lookup_if_params(struct interface *, struct in_addr); extern struct ospf_if_params *ospf_get_if_params(struct interface *, struct in_addr); extern void ospf_del_if_params(struct ospf_if_params *); extern void ospf_free_if_params(struct interface *, struct in_addr); extern void ospf_if_update_params(struct interface *, struct in_addr); extern int ospf_if_new_hook(struct interface *); extern void ospf_if_init(void); extern void ospf_if_stream_unset(struct ospf_interface *); extern void ospf_if_reset_variables(struct ospf_interface *); extern int ospf_if_is_enable(struct ospf_interface *); extern int ospf_if_get_output_cost(struct ospf_interface *); extern void ospf_if_recalculate_output_cost(struct interface *); /* Simulate down/up on the interface. */ extern void ospf_if_reset(struct interface *); extern struct ospf_interface *ospf_vl_new(struct ospf *, struct ospf_vl_data *); extern struct ospf_vl_data *ospf_vl_data_new(struct ospf_area *, struct in_addr); extern struct ospf_vl_data *ospf_vl_lookup(struct ospf *, struct ospf_area *, struct in_addr); extern int ospf_vl_count(struct ospf *ospf, struct ospf_area *area); extern void ospf_vl_data_free(struct ospf_vl_data *); extern void ospf_vl_add(struct ospf *, struct ospf_vl_data *); extern void ospf_vl_delete(struct ospf *, struct ospf_vl_data *); extern void ospf_vl_up_check(struct ospf_area *, struct in_addr, struct vertex *); extern void ospf_vl_unapprove(struct ospf *); extern void ospf_vl_shut_unapproved(struct ospf *); extern int ospf_full_virtual_nbrs(struct ospf_area *); extern int ospf_vls_in_area(struct ospf_area *); extern struct crypt_key *ospf_crypt_key_lookup(struct list *, uint8_t); extern struct crypt_key *ospf_crypt_key_new(void); extern void ospf_crypt_key_add(struct list *, struct crypt_key *); extern int ospf_crypt_key_delete(struct list *, uint8_t); extern uint8_t ospf_default_iftype(struct interface *ifp); extern int ospf_interface_neighbor_count(struct ospf_interface *oi); /* Set all multicast memberships appropriately based on the type and state of the interface. */ extern void ospf_if_set_multicast(struct ospf_interface *); DECLARE_HOOK(ospf_vl_add, (struct ospf_vl_data * vd), (vd)) DECLARE_HOOK(ospf_vl_delete, (struct ospf_vl_data * vd), (vd)) #endif /* _ZEBRA_OSPF_INTERFACE_H */ frr-7.2.1/ospfd/ospf_ism.c0000644000000000000000000004061513610377563012320 00000000000000/* * OSPF version 2 Interface State Machine * From RFC2328 [OSPF Version 2] * Copyright (C) 1999, 2000 Toshiaki Takada * * This file is part of GNU Zebra. * * GNU Zebra is free software; you can redistribute it and/or modify it * under the terms of the GNU General Public License as published by the * Free Software Foundation; either version 2, or (at your option) any * later version. * * GNU Zebra 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; see the file COPYING; if not, write to the Free Software * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA */ #include #include "thread.h" #include "linklist.h" #include "prefix.h" #include "if.h" #include "table.h" #include "log.h" #include "ospfd/ospfd.h" #include "ospfd/ospf_interface.h" #include "ospfd/ospf_ism.h" #include "ospfd/ospf_asbr.h" #include "ospfd/ospf_lsa.h" #include "ospfd/ospf_lsdb.h" #include "ospfd/ospf_neighbor.h" #include "ospfd/ospf_nsm.h" #include "ospfd/ospf_network.h" #include "ospfd/ospf_dump.h" #include "ospfd/ospf_packet.h" #include "ospfd/ospf_flood.h" #include "ospfd/ospf_abr.h" DEFINE_HOOK(ospf_ism_change, (struct ospf_interface * oi, int state, int oldstate), (oi, state, oldstate)) /* elect DR and BDR. Refer to RFC2319 section 9.4 */ static struct ospf_neighbor *ospf_dr_election_sub(struct list *routers) { struct listnode *node; struct ospf_neighbor *nbr, *max = NULL; /* Choose highest router priority. In case of tie, choose highest Router ID. */ for (ALL_LIST_ELEMENTS_RO(routers, node, nbr)) { if (max == NULL) max = nbr; else { if (max->priority < nbr->priority) max = nbr; else if (max->priority == nbr->priority) if (IPV4_ADDR_CMP(&max->router_id, &nbr->router_id) < 0) max = nbr; } } return max; } static struct ospf_neighbor *ospf_elect_dr(struct ospf_interface *oi, struct list *el_list) { struct list *dr_list; struct listnode *node; struct ospf_neighbor *nbr, *dr = NULL, *bdr = NULL; dr_list = list_new(); /* Add neighbors to the list. */ for (ALL_LIST_ELEMENTS_RO(el_list, node, nbr)) { /* neighbor declared to be DR. */ if (NBR_IS_DR(nbr)) listnode_add(dr_list, nbr); /* Preserve neighbor BDR. */ if (IPV4_ADDR_SAME(&BDR(oi), &nbr->address.u.prefix4)) bdr = nbr; } /* Elect Designated Router. */ if (listcount(dr_list) > 0) dr = ospf_dr_election_sub(dr_list); else dr = bdr; /* Set DR to interface. */ if (dr) DR(oi) = dr->address.u.prefix4; else DR(oi).s_addr = 0; list_delete(&dr_list); return dr; } static struct ospf_neighbor *ospf_elect_bdr(struct ospf_interface *oi, struct list *el_list) { struct list *bdr_list, *no_dr_list; struct listnode *node; struct ospf_neighbor *nbr, *bdr = NULL; bdr_list = list_new(); no_dr_list = list_new(); /* Add neighbors to the list. */ for (ALL_LIST_ELEMENTS_RO(el_list, node, nbr)) { /* neighbor declared to be DR. */ if (NBR_IS_DR(nbr)) continue; /* neighbor declared to be BDR. */ if (NBR_IS_BDR(nbr)) listnode_add(bdr_list, nbr); listnode_add(no_dr_list, nbr); } /* Elect Backup Designated Router. */ if (listcount(bdr_list) > 0) bdr = ospf_dr_election_sub(bdr_list); else bdr = ospf_dr_election_sub(no_dr_list); /* Set BDR to interface. */ if (bdr) BDR(oi) = bdr->address.u.prefix4; else BDR(oi).s_addr = 0; list_delete(&bdr_list); list_delete(&no_dr_list); return bdr; } static int ospf_ism_state(struct ospf_interface *oi) { if (IPV4_ADDR_SAME(&DR(oi), &oi->address->u.prefix4)) return ISM_DR; else if (IPV4_ADDR_SAME(&BDR(oi), &oi->address->u.prefix4)) return ISM_Backup; else return ISM_DROther; } static void ospf_dr_eligible_routers(struct route_table *nbrs, struct list *el_list) { struct route_node *rn; struct ospf_neighbor *nbr; for (rn = route_top(nbrs); rn; rn = route_next(rn)) if ((nbr = rn->info) != NULL) /* Ignore 0.0.0.0 node*/ if (nbr->router_id.s_addr != 0) /* Is neighbor eligible? */ if (nbr->priority > 0) /* Is neighbor upper 2-Way? */ if (nbr->state >= NSM_TwoWay) listnode_add(el_list, nbr); } /* Generate AdjOK? NSM event. */ static void ospf_dr_change(struct ospf *ospf, struct route_table *nbrs) { struct route_node *rn; struct ospf_neighbor *nbr; for (rn = route_top(nbrs); rn; rn = route_next(rn)) if ((nbr = rn->info) != NULL) /* Ignore 0.0.0.0 node*/ if (nbr->router_id.s_addr != 0) /* Is neighbor upper 2-Way? */ if (nbr->state >= NSM_TwoWay) /* Ignore myself. */ if (!IPV4_ADDR_SAME(&nbr->router_id, &ospf->router_id)) OSPF_NSM_EVENT_SCHEDULE( nbr, NSM_AdjOK); } static int ospf_dr_election(struct ospf_interface *oi) { struct in_addr old_dr, old_bdr; int old_state, new_state; struct list *el_list; /* backup current values. */ old_dr = DR(oi); old_bdr = BDR(oi); old_state = oi->state; el_list = list_new(); /* List eligible routers. */ ospf_dr_eligible_routers(oi->nbrs, el_list); /* First election of DR and BDR. */ ospf_elect_bdr(oi, el_list); ospf_elect_dr(oi, el_list); new_state = ospf_ism_state(oi); zlog_debug("DR-Election[1st]: Backup %s", inet_ntoa(BDR(oi))); zlog_debug("DR-Election[1st]: DR %s", inet_ntoa(DR(oi))); if (new_state != old_state && !(new_state == ISM_DROther && old_state < ISM_DROther)) { ospf_elect_bdr(oi, el_list); ospf_elect_dr(oi, el_list); new_state = ospf_ism_state(oi); zlog_debug("DR-Election[2nd]: Backup %s", inet_ntoa(BDR(oi))); zlog_debug("DR-Election[2nd]: DR %s", inet_ntoa(DR(oi))); } list_delete(&el_list); /* if DR or BDR changes, cause AdjOK? neighbor event. */ if (!IPV4_ADDR_SAME(&old_dr, &DR(oi)) || !IPV4_ADDR_SAME(&old_bdr, &BDR(oi))) ospf_dr_change(oi->ospf, oi->nbrs); return new_state; } int ospf_hello_timer(struct thread *thread) { struct ospf_interface *oi; oi = THREAD_ARG(thread); oi->t_hello = NULL; if (IS_DEBUG_OSPF(ism, ISM_TIMERS)) zlog_debug("ISM[%s]: Timer (Hello timer expire)", IF_NAME(oi)); /* Sending hello packet. */ ospf_hello_send(oi); /* Hello timer set. */ OSPF_HELLO_TIMER_ON(oi); return 0; } static int ospf_wait_timer(struct thread *thread) { struct ospf_interface *oi; oi = THREAD_ARG(thread); oi->t_wait = NULL; if (IS_DEBUG_OSPF(ism, ISM_TIMERS)) zlog_debug("ISM[%s]: Timer (Wait timer expire)", IF_NAME(oi)); OSPF_ISM_EVENT_SCHEDULE(oi, ISM_WaitTimer); return 0; } /* Hook function called after ospf ISM event is occurred. And vty's network command invoke this function after making interface structure. */ static void ism_timer_set(struct ospf_interface *oi) { switch (oi->state) { case ISM_Down: /* First entry point of ospf interface state machine. In this state interface parameters must be set to initial values, and timers are reset also. */ OSPF_ISM_TIMER_OFF(oi->t_hello); OSPF_ISM_TIMER_OFF(oi->t_wait); OSPF_ISM_TIMER_OFF(oi->t_ls_ack); break; case ISM_Loopback: /* In this state, the interface may be looped back and will be unavailable for regular data traffic. */ OSPF_ISM_TIMER_OFF(oi->t_hello); OSPF_ISM_TIMER_OFF(oi->t_wait); OSPF_ISM_TIMER_OFF(oi->t_ls_ack); break; case ISM_Waiting: /* The router is trying to determine the identity of DRouter and BDRouter. The router begin to receive and send Hello Packets. */ /* send first hello immediately */ OSPF_ISM_TIMER_MSEC_ON(oi->t_hello, ospf_hello_timer, 1); OSPF_ISM_TIMER_ON(oi->t_wait, ospf_wait_timer, OSPF_IF_PARAM(oi, v_wait)); OSPF_ISM_TIMER_OFF(oi->t_ls_ack); break; case ISM_PointToPoint: /* The interface connects to a physical Point-to-point network or virtual link. The router attempts to form an adjacency with neighboring router. Hello packets are also sent. */ /* send first hello immediately */ OSPF_ISM_TIMER_MSEC_ON(oi->t_hello, ospf_hello_timer, 1); OSPF_ISM_TIMER_OFF(oi->t_wait); OSPF_ISM_TIMER_ON(oi->t_ls_ack, ospf_ls_ack_timer, oi->v_ls_ack); break; case ISM_DROther: /* The network type of the interface is broadcast or NBMA network, and the router itself is neither Designated Router nor Backup Designated Router. */ OSPF_HELLO_TIMER_ON(oi); OSPF_ISM_TIMER_OFF(oi->t_wait); OSPF_ISM_TIMER_ON(oi->t_ls_ack, ospf_ls_ack_timer, oi->v_ls_ack); break; case ISM_Backup: /* The network type of the interface is broadcast os NBMA network, and the router is Backup Designated Router. */ OSPF_HELLO_TIMER_ON(oi); OSPF_ISM_TIMER_OFF(oi->t_wait); OSPF_ISM_TIMER_ON(oi->t_ls_ack, ospf_ls_ack_timer, oi->v_ls_ack); break; case ISM_DR: /* The network type of the interface is broadcast or NBMA network, and the router is Designated Router. */ OSPF_HELLO_TIMER_ON(oi); OSPF_ISM_TIMER_OFF(oi->t_wait); OSPF_ISM_TIMER_ON(oi->t_ls_ack, ospf_ls_ack_timer, oi->v_ls_ack); break; } } static int ism_interface_up(struct ospf_interface *oi) { int next_state = 0; /* if network type is point-to-point, Point-to-MultiPoint or virtual link, the state transitions to Point-to-Point. */ if (oi->type == OSPF_IFTYPE_POINTOPOINT || oi->type == OSPF_IFTYPE_POINTOMULTIPOINT || oi->type == OSPF_IFTYPE_VIRTUALLINK) next_state = ISM_PointToPoint; /* Else if the router is not eligible to DR, the state transitions to DROther. */ else if (PRIORITY(oi) == 0) /* router is eligible? */ next_state = ISM_DROther; else /* Otherwise, the state transitions to Waiting. */ next_state = ISM_Waiting; if (oi->type == OSPF_IFTYPE_NBMA) ospf_nbr_nbma_if_update(oi->ospf, oi); /* ospf_ism_event (t); */ return next_state; } static int ism_loop_ind(struct ospf_interface *oi) { /* call ism_interface_down. */ /* ret = ism_interface_down (oi); */ return 0; } /* Interface down event handler. */ static int ism_interface_down(struct ospf_interface *oi) { ospf_if_cleanup(oi); return 0; } static int ism_backup_seen(struct ospf_interface *oi) { return ospf_dr_election(oi); } static int ism_wait_timer(struct ospf_interface *oi) { return ospf_dr_election(oi); } static int ism_neighbor_change(struct ospf_interface *oi) { return ospf_dr_election(oi); } static int ism_ignore(struct ospf_interface *oi) { if (IS_DEBUG_OSPF(ism, ISM_EVENTS)) zlog_debug("ISM[%s]: ism_ignore called", IF_NAME(oi)); return 0; } /* Interface State Machine */ struct { int (*func)(struct ospf_interface *); int next_state; } ISM[OSPF_ISM_STATE_MAX][OSPF_ISM_EVENT_MAX] = { { /* DependUpon: dummy state. */ {ism_ignore, ISM_DependUpon}, /* NoEvent */ {ism_ignore, ISM_DependUpon}, /* InterfaceUp */ {ism_ignore, ISM_DependUpon}, /* WaitTimer */ {ism_ignore, ISM_DependUpon}, /* BackupSeen */ {ism_ignore, ISM_DependUpon}, /* NeighborChange */ {ism_ignore, ISM_DependUpon}, /* LoopInd */ {ism_ignore, ISM_DependUpon}, /* UnloopInd */ {ism_ignore, ISM_DependUpon}, /* InterfaceDown */ }, { /* Down:*/ {ism_ignore, ISM_DependUpon}, /* NoEvent */ {ism_interface_up, ISM_DependUpon}, /* InterfaceUp */ {ism_ignore, ISM_Down}, /* WaitTimer */ {ism_ignore, ISM_Down}, /* BackupSeen */ {ism_ignore, ISM_Down}, /* NeighborChange */ {ism_loop_ind, ISM_Loopback}, /* LoopInd */ {ism_ignore, ISM_Down}, /* UnloopInd */ {ism_interface_down, ISM_Down}, /* InterfaceDown */ }, { /* Loopback: */ {ism_ignore, ISM_DependUpon}, /* NoEvent */ {ism_ignore, ISM_Loopback}, /* InterfaceUp */ {ism_ignore, ISM_Loopback}, /* WaitTimer */ {ism_ignore, ISM_Loopback}, /* BackupSeen */ {ism_ignore, ISM_Loopback}, /* NeighborChange */ {ism_ignore, ISM_Loopback}, /* LoopInd */ {ism_ignore, ISM_Down}, /* UnloopInd */ {ism_interface_down, ISM_Down}, /* InterfaceDown */ }, { /* Waiting: */ {ism_ignore, ISM_DependUpon}, /* NoEvent */ {ism_ignore, ISM_Waiting}, /* InterfaceUp */ {ism_wait_timer, ISM_DependUpon}, /* WaitTimer */ {ism_backup_seen, ISM_DependUpon}, /* BackupSeen */ {ism_ignore, ISM_Waiting}, /* NeighborChange */ {ism_loop_ind, ISM_Loopback}, /* LoopInd */ {ism_ignore, ISM_Waiting}, /* UnloopInd */ {ism_interface_down, ISM_Down}, /* InterfaceDown */ }, { /* Point-to-Point: */ {ism_ignore, ISM_DependUpon}, /* NoEvent */ {ism_ignore, ISM_PointToPoint}, /* InterfaceUp */ {ism_ignore, ISM_PointToPoint}, /* WaitTimer */ {ism_ignore, ISM_PointToPoint}, /* BackupSeen */ {ism_ignore, ISM_PointToPoint}, /* NeighborChange */ {ism_loop_ind, ISM_Loopback}, /* LoopInd */ {ism_ignore, ISM_PointToPoint}, /* UnloopInd */ {ism_interface_down, ISM_Down}, /* InterfaceDown */ }, { /* DROther: */ {ism_ignore, ISM_DependUpon}, /* NoEvent */ {ism_ignore, ISM_DROther}, /* InterfaceUp */ {ism_ignore, ISM_DROther}, /* WaitTimer */ {ism_ignore, ISM_DROther}, /* BackupSeen */ {ism_neighbor_change, ISM_DependUpon}, /* NeighborChange */ {ism_loop_ind, ISM_Loopback}, /* LoopInd */ {ism_ignore, ISM_DROther}, /* UnloopInd */ {ism_interface_down, ISM_Down}, /* InterfaceDown */ }, { /* Backup: */ {ism_ignore, ISM_DependUpon}, /* NoEvent */ {ism_ignore, ISM_Backup}, /* InterfaceUp */ {ism_ignore, ISM_Backup}, /* WaitTimer */ {ism_ignore, ISM_Backup}, /* BackupSeen */ {ism_neighbor_change, ISM_DependUpon}, /* NeighborChange */ {ism_loop_ind, ISM_Loopback}, /* LoopInd */ {ism_ignore, ISM_Backup}, /* UnloopInd */ {ism_interface_down, ISM_Down}, /* InterfaceDown */ }, { /* DR: */ {ism_ignore, ISM_DependUpon}, /* NoEvent */ {ism_ignore, ISM_DR}, /* InterfaceUp */ {ism_ignore, ISM_DR}, /* WaitTimer */ {ism_ignore, ISM_DR}, /* BackupSeen */ {ism_neighbor_change, ISM_DependUpon}, /* NeighborChange */ {ism_loop_ind, ISM_Loopback}, /* LoopInd */ {ism_ignore, ISM_DR}, /* UnloopInd */ {ism_interface_down, ISM_Down}, /* InterfaceDown */ }, }; static const char *ospf_ism_event_str[] = { "NoEvent", "InterfaceUp", "WaitTimer", "BackupSeen", "NeighborChange", "LoopInd", "UnLoopInd", "InterfaceDown", }; static void ism_change_state(struct ospf_interface *oi, int state) { int old_state; struct ospf_lsa *lsa; /* Logging change of state. */ if (IS_DEBUG_OSPF(ism, ISM_STATUS)) zlog_debug("ISM[%s]: State change %s -> %s", IF_NAME(oi), lookup_msg(ospf_ism_state_msg, oi->state, NULL), lookup_msg(ospf_ism_state_msg, state, NULL)); old_state = oi->state; oi->state = state; oi->state_change++; hook_call(ospf_ism_change, oi, state, old_state); /* Set multicast memberships appropriately for new state. */ ospf_if_set_multicast(oi); if (old_state == ISM_Down || state == ISM_Down) ospf_check_abr_status(oi->ospf); /* Originate router-LSA. */ if (state == ISM_Down) { if (oi->area->act_ints > 0) oi->area->act_ints--; } else if (old_state == ISM_Down) oi->area->act_ints++; /* schedule router-LSA originate. */ ospf_router_lsa_update_area(oi->area); /* Originate network-LSA. */ if (old_state != ISM_DR && state == ISM_DR) ospf_network_lsa_update(oi); else if (old_state == ISM_DR && state != ISM_DR) { /* Free self originated network LSA. */ lsa = oi->network_lsa_self; if (lsa) ospf_lsa_flush_area(lsa, oi->area); ospf_lsa_unlock(&oi->network_lsa_self); oi->network_lsa_self = NULL; } ospf_opaque_ism_change(oi, old_state); /* Check area border status. */ ospf_check_abr_status(oi->ospf); } /* Execute ISM event process. */ int ospf_ism_event(struct thread *thread) { int event; int next_state; struct ospf_interface *oi; oi = THREAD_ARG(thread); event = THREAD_VAL(thread); /* Call function. */ next_state = (*(ISM[oi->state][event].func))(oi); if (!next_state) next_state = ISM[oi->state][event].next_state; if (IS_DEBUG_OSPF(ism, ISM_EVENTS)) zlog_debug("ISM[%s]: %s (%s)", IF_NAME(oi), lookup_msg(ospf_ism_state_msg, oi->state, NULL), ospf_ism_event_str[event]); /* If state is changed. */ if (next_state != oi->state) ism_change_state(oi, next_state); /* Make sure timer is set. */ ism_timer_set(oi); return 0; } frr-7.2.1/ospfd/ospf_ism.h0000644000000000000000000001061613610377563012323 00000000000000/* * OSPF version 2 Interface State Machine. * From RFC2328 [OSPF Version 2] * Copyright (C) 1999 Toshiaki Takada * * This file is part of GNU Zebra. * * GNU Zebra is free software; you can redistribute it and/or modify it * under the terms of the GNU General Public License as published by the * Free Software Foundation; either version 2, or (at your option) any * later version. * * GNU Zebra 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; see the file COPYING; if not, write to the Free Software * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA */ #ifndef _ZEBRA_OSPF_ISM_H #define _ZEBRA_OSPF_ISM_H #include "hook.h" /* OSPF Interface State Machine Status. */ #define ISM_DependUpon 0 #define ISM_Down 1 #define ISM_Loopback 2 #define ISM_Waiting 3 #define ISM_PointToPoint 4 #define ISM_DROther 5 #define ISM_Backup 6 #define ISM_DR 7 #define OSPF_ISM_STATE_MAX 8 /* OSPF Interface State Machine Event. */ #define ISM_NoEvent 0 #define ISM_InterfaceUp 1 #define ISM_WaitTimer 2 #define ISM_BackupSeen 3 #define ISM_NeighborChange 4 #define ISM_LoopInd 5 #define ISM_UnloopInd 6 #define ISM_InterfaceDown 7 #define OSPF_ISM_EVENT_MAX 8 #define OSPF_ISM_WRITE_ON(O) \ do { \ if (oi->on_write_q == 0) { \ listnode_add((O)->oi_write_q, oi); \ oi->on_write_q = 1; \ } \ if (!list_isempty((O)->oi_write_q)) \ thread_add_write(master, ospf_write, (O), (O)->fd, \ &(O)->t_write); \ } while (0) /* Macro for OSPF ISM timer turn on. */ #define OSPF_ISM_TIMER_ON(T, F, V) thread_add_timer(master, (F), oi, (V), &(T)) #define OSPF_ISM_TIMER_MSEC_ON(T, F, V) \ thread_add_timer_msec(master, (F), oi, (V), &(T)) /* convenience macro to set hello timer correctly, according to * whether fast-hello is set or not */ #define OSPF_HELLO_TIMER_ON(O) \ do { \ if (OSPF_IF_PARAM((O), fast_hello)) \ OSPF_ISM_TIMER_MSEC_ON( \ (O)->t_hello, ospf_hello_timer, \ 1000 / OSPF_IF_PARAM((O), fast_hello)); \ else \ OSPF_ISM_TIMER_ON((O)->t_hello, ospf_hello_timer, \ OSPF_IF_PARAM((O), v_hello)); \ } while (0) /* Macro for OSPF ISM timer turn off. */ #define OSPF_ISM_TIMER_OFF(X) \ do { \ if (X) { \ thread_cancel(X); \ (X) = NULL; \ } \ } while (0) /* Macro for OSPF schedule event. */ #define OSPF_ISM_EVENT_SCHEDULE(I, E) \ thread_add_event(master, ospf_ism_event, (I), (E), NULL) /* Macro for OSPF execute event. */ #define OSPF_ISM_EVENT_EXECUTE(I, E) \ thread_execute(master, ospf_ism_event, (I), (E)) /* Prototypes. */ extern int ospf_ism_event(struct thread *); extern void ism_change_status(struct ospf_interface *, int); extern int ospf_hello_timer(struct thread *thread); DECLARE_HOOK(ospf_ism_change, (struct ospf_interface * oi, int state, int oldstate), (oi, state, oldstate)) #endif /* _ZEBRA_OSPF_ISM_H */ frr-7.2.1/ospfd/ospf_lsa.c0000644000000000000000000027747313610377563012325 00000000000000/* * OSPF Link State Advertisement * Copyright (C) 1999, 2000 Toshiaki Takada * * This file is part of GNU Zebra. * * GNU Zebra is free software; you can redistribute it and/or modify it * under the terms of the GNU General Public License as published by the * Free Software Foundation; either version 2, or (at your option) any * later version. * * GNU Zebra 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; see the file COPYING; if not, write to the Free Software * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA */ #include #include "monotime.h" #include "linklist.h" #include "prefix.h" #include "if.h" #include "table.h" #include "memory.h" #include "stream.h" #include "log.h" #include "thread.h" #include "hash.h" #include "sockunion.h" /* for inet_aton() */ #include "checksum.h" #include "ospfd/ospfd.h" #include "ospfd/ospf_interface.h" #include "ospfd/ospf_ism.h" #include "ospfd/ospf_asbr.h" #include "ospfd/ospf_lsa.h" #include "ospfd/ospf_lsdb.h" #include "ospfd/ospf_neighbor.h" #include "ospfd/ospf_nsm.h" #include "ospfd/ospf_flood.h" #include "ospfd/ospf_packet.h" #include "ospfd/ospf_spf.h" #include "ospfd/ospf_dump.h" #include "ospfd/ospf_route.h" #include "ospfd/ospf_ase.h" #include "ospfd/ospf_zebra.h" #include "ospfd/ospf_abr.h" #include "ospfd/ospf_errors.h" uint32_t get_metric(uint8_t *metric) { uint32_t m; m = metric[0]; m = (m << 8) + metric[1]; m = (m << 8) + metric[2]; return m; } struct timeval int2tv(int a) { struct timeval ret; ret.tv_sec = a; ret.tv_usec = 0; return ret; } struct timeval msec2tv(int a) { struct timeval ret; ret.tv_sec = a / 1000; ret.tv_usec = (a % 1000) * 1000; return ret; } int ospf_lsa_refresh_delay(struct ospf_lsa *lsa) { struct timeval delta; int delay = 0; if (monotime_since(&lsa->tv_orig, &delta) < OSPF_MIN_LS_INTERVAL * 1000LL) { struct timeval minv = msec2tv(OSPF_MIN_LS_INTERVAL); timersub(&minv, &delta, &minv); /* TBD: remove padding to full sec, return timeval instead */ delay = minv.tv_sec + !!minv.tv_usec; if (IS_DEBUG_OSPF(lsa, LSA_GENERATE)) zlog_debug( "LSA[Type%d:%s]: Refresh timer delay %d seconds", lsa->data->type, inet_ntoa(lsa->data->id), delay); assert(delay > 0); } return delay; } int get_age(struct ospf_lsa *lsa) { struct timeval rel; monotime_since(&lsa->tv_recv, &rel); return ntohs(lsa->data->ls_age) + rel.tv_sec; } /* Fletcher Checksum -- Refer to RFC1008. */ /* All the offsets are zero-based. The offsets in the RFC1008 are one-based. */ uint16_t ospf_lsa_checksum(struct lsa_header *lsa) { uint8_t *buffer = (uint8_t *)&lsa->options; int options_offset = buffer - (uint8_t *)&lsa->ls_age; /* should be 2 */ /* Skip the AGE field */ uint16_t len = ntohs(lsa->length) - options_offset; /* Checksum offset starts from "options" field, not the beginning of the lsa_header struct. The offset is 14, rather than 16. */ int checksum_offset = (uint8_t *)&lsa->checksum - buffer; return fletcher_checksum(buffer, len, checksum_offset); } int ospf_lsa_checksum_valid(struct lsa_header *lsa) { uint8_t *buffer = (uint8_t *)&lsa->options; int options_offset = buffer - (uint8_t *)&lsa->ls_age; /* should be 2 */ /* Skip the AGE field */ uint16_t len = ntohs(lsa->length) - options_offset; return (fletcher_checksum(buffer, len, FLETCHER_CHECKSUM_VALIDATE) == 0); } /* Create OSPF LSA. */ struct ospf_lsa *ospf_lsa_new(void) { struct ospf_lsa *new; new = XCALLOC(MTYPE_OSPF_LSA, sizeof(struct ospf_lsa)); new->flags = 0; new->lock = 1; new->retransmit_counter = 0; monotime(&new->tv_recv); new->tv_orig = new->tv_recv; new->refresh_list = -1; new->vrf_id = VRF_DEFAULT; return new; } struct ospf_lsa *ospf_lsa_new_and_data(size_t size) { struct ospf_lsa *new; new = ospf_lsa_new(); new->data = ospf_lsa_data_new(size); return new; } /* Duplicate OSPF LSA. */ struct ospf_lsa *ospf_lsa_dup(struct ospf_lsa *lsa) { struct ospf_lsa *new; if (lsa == NULL) return NULL; new = XCALLOC(MTYPE_OSPF_LSA, sizeof(struct ospf_lsa)); memcpy(new, lsa, sizeof(struct ospf_lsa)); UNSET_FLAG(new->flags, OSPF_LSA_DISCARD); new->lock = 1; new->retransmit_counter = 0; new->data = ospf_lsa_data_dup(lsa->data); /* kevinm: Clear the refresh_list, otherwise there are going to be problems when we try to remove the LSA from the queue (which it's not a member of.) XXX: Should we add the LSA to the refresh_list queue? */ new->refresh_list = -1; if (IS_DEBUG_OSPF(lsa, LSA)) zlog_debug("LSA: duplicated %p (new: %p)", (void *)lsa, (void *)new); return new; } /* Free OSPF LSA. */ void ospf_lsa_free(struct ospf_lsa *lsa) { assert(lsa->lock == 0); if (IS_DEBUG_OSPF(lsa, LSA)) zlog_debug("LSA: freed %p", (void *)lsa); /* Delete LSA data. */ if (lsa->data != NULL) ospf_lsa_data_free(lsa->data); assert(lsa->refresh_list < 0); memset(lsa, 0, sizeof(struct ospf_lsa)); XFREE(MTYPE_OSPF_LSA, lsa); } /* Lock LSA. */ struct ospf_lsa *ospf_lsa_lock(struct ospf_lsa *lsa) { lsa->lock++; return lsa; } /* Unlock LSA. */ void ospf_lsa_unlock(struct ospf_lsa **lsa) { /* This is sanity check. */ if (!lsa || !*lsa) return; (*lsa)->lock--; assert((*lsa)->lock >= 0); if ((*lsa)->lock == 0) { assert(CHECK_FLAG((*lsa)->flags, OSPF_LSA_DISCARD)); ospf_lsa_free(*lsa); *lsa = NULL; } } /* Check discard flag. */ void ospf_lsa_discard(struct ospf_lsa *lsa) { if (!CHECK_FLAG(lsa->flags, OSPF_LSA_DISCARD)) { SET_FLAG(lsa->flags, OSPF_LSA_DISCARD); ospf_lsa_unlock(&lsa); } } /* Create LSA data. */ struct lsa_header *ospf_lsa_data_new(size_t size) { return XCALLOC(MTYPE_OSPF_LSA_DATA, size); } /* Duplicate LSA data. */ struct lsa_header *ospf_lsa_data_dup(struct lsa_header *lsah) { struct lsa_header *new; new = ospf_lsa_data_new(ntohs(lsah->length)); memcpy(new, lsah, ntohs(lsah->length)); return new; } /* Free LSA data. */ void ospf_lsa_data_free(struct lsa_header *lsah) { if (IS_DEBUG_OSPF(lsa, LSA)) zlog_debug("LSA[Type%d:%s]: data freed %p", lsah->type, inet_ntoa(lsah->id), (void *)lsah); XFREE(MTYPE_OSPF_LSA_DATA, lsah); } /* LSA general functions. */ const char *dump_lsa_key(struct ospf_lsa *lsa) { static char buf[] = {"Type255,id(255.255.255.255),ar(255.255.255.255)"}; struct lsa_header *lsah; if (lsa != NULL && (lsah = lsa->data) != NULL) { char id[INET_ADDRSTRLEN], ar[INET_ADDRSTRLEN]; strlcpy(id, inet_ntoa(lsah->id), sizeof(id)); strlcpy(ar, inet_ntoa(lsah->adv_router), sizeof(ar)); sprintf(buf, "Type%d,id(%s),ar(%s)", lsah->type, id, ar); } else strlcpy(buf, "NULL", sizeof(buf)); return buf; } uint32_t lsa_seqnum_increment(struct ospf_lsa *lsa) { uint32_t seqnum; seqnum = ntohl(lsa->data->ls_seqnum) + 1; return htonl(seqnum); } void lsa_header_set(struct stream *s, uint8_t options, uint8_t type, struct in_addr id, struct in_addr router_id) { struct lsa_header *lsah; lsah = (struct lsa_header *)STREAM_DATA(s); lsah->ls_age = htons(OSPF_LSA_INITIAL_AGE); lsah->options = options; lsah->type = type; lsah->id = id; lsah->adv_router = router_id; lsah->ls_seqnum = htonl(OSPF_INITIAL_SEQUENCE_NUMBER); stream_forward_endp(s, OSPF_LSA_HEADER_SIZE); } /* router-LSA related functions. */ /* Get router-LSA flags. */ static uint8_t router_lsa_flags(struct ospf_area *area) { uint8_t flags; flags = area->ospf->flags; /* Set virtual link flag. */ if (ospf_full_virtual_nbrs(area)) SET_FLAG(flags, ROUTER_LSA_VIRTUAL); else /* Just sanity check */ UNSET_FLAG(flags, ROUTER_LSA_VIRTUAL); /* Set Shortcut ABR behabiour flag. */ UNSET_FLAG(flags, ROUTER_LSA_SHORTCUT); if (area->ospf->abr_type == OSPF_ABR_SHORTCUT) if (!OSPF_IS_AREA_BACKBONE(area)) if ((area->shortcut_configured == OSPF_SHORTCUT_DEFAULT && area->ospf->backbone == NULL) || area->shortcut_configured == OSPF_SHORTCUT_ENABLE) SET_FLAG(flags, ROUTER_LSA_SHORTCUT); /* ASBR can't exit in stub area. */ if (area->external_routing == OSPF_AREA_STUB) UNSET_FLAG(flags, ROUTER_LSA_EXTERNAL); /* If ASBR set External flag */ else if (IS_OSPF_ASBR(area->ospf)) SET_FLAG(flags, ROUTER_LSA_EXTERNAL); /* Set ABR dependent flags */ if (IS_OSPF_ABR(area->ospf)) { SET_FLAG(flags, ROUTER_LSA_BORDER); /* If Area is NSSA and we are both ABR and unconditional * translator, * set Nt bit to inform other routers. */ if ((area->external_routing == OSPF_AREA_NSSA) && (area->NSSATranslatorRole == OSPF_NSSA_ROLE_ALWAYS)) SET_FLAG(flags, ROUTER_LSA_NT); } return flags; } /* Lookup neighbor other than myself. And check neighbor count, Point-to-Point link must have only 1 neighbor. */ struct ospf_neighbor *ospf_nbr_lookup_ptop(struct ospf_interface *oi) { struct ospf_neighbor *nbr = NULL; struct route_node *rn; /* Search neighbor, there must be one of two nbrs. */ for (rn = route_top(oi->nbrs); rn; rn = route_next(rn)) if ((nbr = rn->info)) if (!IPV4_ADDR_SAME(&nbr->router_id, &oi->ospf->router_id)) if (nbr->state == NSM_Full) { route_unlock_node(rn); break; } /* PtoP link must have only 1 neighbor. */ if (ospf_nbr_count(oi, 0) > 1) flog_warn(EC_OSPF_PTP_NEIGHBOR, "Point-to-Point link has more than 1 neighobrs."); return nbr; } /* Determine cost of link, taking RFC3137 stub-router support into * consideration */ static uint16_t ospf_link_cost(struct ospf_interface *oi) { /* RFC3137 stub router support */ if (!CHECK_FLAG(oi->area->stub_router_state, OSPF_AREA_IS_STUB_ROUTED)) return oi->output_cost; else return OSPF_OUTPUT_COST_INFINITE; } /* Set a link information. */ static char link_info_set(struct stream **s, struct in_addr id, struct in_addr data, uint8_t type, uint8_t tos, uint16_t cost) { /* LSA stream is initially allocated to OSPF_MAX_LSA_SIZE, suits * vast majority of cases. Some rare routers with lots of links need * more. * we try accomodate those here. */ if (STREAM_WRITEABLE(*s) < OSPF_ROUTER_LSA_LINK_SIZE) { size_t ret = OSPF_MAX_LSA_SIZE; /* Can we enlarge the stream still? */ if (STREAM_SIZE(*s) == OSPF_MAX_LSA_SIZE) { /* we futz the size here for simplicity, really we need * to account * for just: * IP Header - (sizeof (struct ip)) * OSPF Header - OSPF_HEADER_SIZE * LSA Header - OSPF_LSA_HEADER_SIZE * MD5 auth data, if MD5 is configured - * OSPF_AUTH_MD5_SIZE. * * Simpler just to subtract OSPF_MAX_LSA_SIZE though. */ ret = stream_resize_inplace( s, OSPF_MAX_PACKET_SIZE - OSPF_MAX_LSA_SIZE); } if (ret == OSPF_MAX_LSA_SIZE) { flog_warn( EC_OSPF_LSA_SIZE, "%s: Out of space in LSA stream, left %zd, size %zd", __func__, STREAM_WRITEABLE(*s), STREAM_SIZE(*s)); return 0; } } /* TOS based routing is not supported. */ stream_put_ipv4(*s, id.s_addr); /* Link ID. */ stream_put_ipv4(*s, data.s_addr); /* Link Data. */ stream_putc(*s, type); /* Link Type. */ stream_putc(*s, tos); /* TOS = 0. */ stream_putw(*s, cost); /* Link Cost. */ return 1; } /* Describe Point-to-Point link (Section 12.4.1.1). */ static int lsa_link_ptop_set(struct stream **s, struct ospf_interface *oi) { int links = 0; struct ospf_neighbor *nbr; struct in_addr id, mask, data; uint16_t cost = ospf_link_cost(oi); if (IS_DEBUG_OSPF(lsa, LSA_GENERATE)) zlog_debug("LSA[Type1]: Set link Point-to-Point"); if ((nbr = ospf_nbr_lookup_ptop(oi))) if (nbr->state == NSM_Full) { if (CHECK_FLAG(oi->connected->flags, ZEBRA_IFA_UNNUMBERED)) { /* For unnumbered point-to-point networks, the Link Data field should specify the interface's MIB-II ifIndex value. */ data.s_addr = htonl(oi->ifp->ifindex); links += link_info_set( s, nbr->router_id, data, LSA_LINK_TYPE_POINTOPOINT, 0, cost); } else { links += link_info_set( s, nbr->router_id, oi->address->u.prefix4, LSA_LINK_TYPE_POINTOPOINT, 0, cost); } } /* no need for a stub link for unnumbered interfaces */ if (!CHECK_FLAG(oi->connected->flags, ZEBRA_IFA_UNNUMBERED)) { /* Regardless of the state of the neighboring router, we must add a Type 3 link (stub network). N.B. Options 1 & 2 share basically the same logic. */ masklen2ip(oi->address->prefixlen, &mask); id.s_addr = CONNECTED_PREFIX(oi->connected)->u.prefix4.s_addr & mask.s_addr; links += link_info_set(s, id, mask, LSA_LINK_TYPE_STUB, 0, oi->output_cost); } return links; } /* Describe Broadcast Link. */ static int lsa_link_broadcast_set(struct stream **s, struct ospf_interface *oi) { struct ospf_neighbor *dr; struct in_addr id, mask; uint16_t cost = ospf_link_cost(oi); /* Describe Type 3 Link. */ if (oi->state == ISM_Waiting) { if (IS_DEBUG_OSPF(lsa, LSA_GENERATE)) zlog_debug( "LSA[Type1]: Interface %s is in state Waiting. " "Adding stub interface", oi->ifp->name); masklen2ip(oi->address->prefixlen, &mask); id.s_addr = oi->address->u.prefix4.s_addr & mask.s_addr; return link_info_set(s, id, mask, LSA_LINK_TYPE_STUB, 0, oi->output_cost); } dr = ospf_nbr_lookup_by_addr(oi->nbrs, &DR(oi)); /* Describe Type 2 link. */ if (dr && (dr->state == NSM_Full || IPV4_ADDR_SAME(&oi->address->u.prefix4, &DR(oi))) && ospf_nbr_count(oi, NSM_Full) > 0) { if (IS_DEBUG_OSPF(lsa, LSA_GENERATE)) zlog_debug( "LSA[Type1]: Interface %s has a DR. " "Adding transit interface", oi->ifp->name); return link_info_set(s, DR(oi), oi->address->u.prefix4, LSA_LINK_TYPE_TRANSIT, 0, cost); } /* Describe type 3 link. */ else { if (IS_DEBUG_OSPF(lsa, LSA_GENERATE)) zlog_debug( "LSA[Type1]: Interface %s has no DR. " "Adding stub interface", oi->ifp->name); masklen2ip(oi->address->prefixlen, &mask); id.s_addr = oi->address->u.prefix4.s_addr & mask.s_addr; return link_info_set(s, id, mask, LSA_LINK_TYPE_STUB, 0, oi->output_cost); } } static int lsa_link_loopback_set(struct stream **s, struct ospf_interface *oi) { struct in_addr id, mask; /* Describe Type 3 Link. */ if (oi->state != ISM_Loopback) return 0; mask.s_addr = 0xffffffff; id.s_addr = oi->address->u.prefix4.s_addr; return link_info_set(s, id, mask, LSA_LINK_TYPE_STUB, 0, 0); } /* Describe Virtual Link. */ static int lsa_link_virtuallink_set(struct stream **s, struct ospf_interface *oi) { struct ospf_neighbor *nbr; uint16_t cost = ospf_link_cost(oi); if (oi->state == ISM_PointToPoint) if ((nbr = ospf_nbr_lookup_ptop(oi))) if (nbr->state == NSM_Full) { return link_info_set(s, nbr->router_id, oi->address->u.prefix4, LSA_LINK_TYPE_VIRTUALLINK, 0, cost); } return 0; } #define lsa_link_nbma_set(S,O) lsa_link_broadcast_set (S, O) /* this function add for support point-to-multipoint ,see rfc2328 12.4.1.4.*/ /* from "edward rrr" http://marc.theaimsgroup.com/?l=zebra&m=100739222210507&w=2 */ static int lsa_link_ptomp_set(struct stream **s, struct ospf_interface *oi) { int links = 0; struct route_node *rn; struct ospf_neighbor *nbr = NULL; struct in_addr id, mask; uint16_t cost = ospf_link_cost(oi); mask.s_addr = 0xffffffff; id.s_addr = oi->address->u.prefix4.s_addr; links += link_info_set(s, id, mask, LSA_LINK_TYPE_STUB, 0, 0); if (IS_DEBUG_OSPF(lsa, LSA_GENERATE)) zlog_debug("PointToMultipoint: running ptomultip_set"); /* Search neighbor, */ for (rn = route_top(oi->nbrs); rn; rn = route_next(rn)) if ((nbr = rn->info) != NULL) /* Ignore myself. */ if (!IPV4_ADDR_SAME(&nbr->router_id, &oi->ospf->router_id)) if (nbr->state == NSM_Full) { links += link_info_set( s, nbr->router_id, oi->address->u.prefix4, LSA_LINK_TYPE_POINTOPOINT, 0, cost); if (IS_DEBUG_OSPF(lsa, LSA_GENERATE)) zlog_debug( "PointToMultipoint: set link to %s", inet_ntoa( oi->address->u .prefix4)); } return links; } /* Set router-LSA link information. */ static int router_lsa_link_set(struct stream **s, struct ospf_area *area) { struct listnode *node; struct ospf_interface *oi; int links = 0; for (ALL_LIST_ELEMENTS_RO(area->oiflist, node, oi)) { struct interface *ifp = oi->ifp; /* Check interface is up, OSPF is enable. */ if (if_is_operative(ifp)) { if (oi->state != ISM_Down) { oi->lsa_pos_beg = links; /* Describe each link. */ switch (oi->type) { case OSPF_IFTYPE_POINTOPOINT: links += lsa_link_ptop_set(s, oi); break; case OSPF_IFTYPE_BROADCAST: links += lsa_link_broadcast_set(s, oi); break; case OSPF_IFTYPE_NBMA: links += lsa_link_nbma_set(s, oi); break; case OSPF_IFTYPE_POINTOMULTIPOINT: links += lsa_link_ptomp_set(s, oi); break; case OSPF_IFTYPE_VIRTUALLINK: links += lsa_link_virtuallink_set(s, oi); break; case OSPF_IFTYPE_LOOPBACK: links += lsa_link_loopback_set(s, oi); } oi->lsa_pos_end = links; } } } return links; } /* Set router-LSA body. */ static void ospf_router_lsa_body_set(struct stream **s, struct ospf_area *area) { unsigned long putp; uint16_t cnt; /* Set flags. */ stream_putc(*s, router_lsa_flags(area)); /* Set Zero fields. */ stream_putc(*s, 0); /* Keep pointer to # links. */ putp = stream_get_endp(*s); /* Forward word */ stream_putw(*s, 0); /* Set all link information. */ cnt = router_lsa_link_set(s, area); /* Set # of links here. */ stream_putw_at(*s, putp, cnt); } static int ospf_stub_router_timer(struct thread *t) { struct ospf_area *area = THREAD_ARG(t); area->t_stub_router = NULL; SET_FLAG(area->stub_router_state, OSPF_AREA_WAS_START_STUB_ROUTED); /* clear stub route state and generate router-lsa refresh, don't * clobber an administratively set stub-router state though. */ if (CHECK_FLAG(area->stub_router_state, OSPF_AREA_ADMIN_STUB_ROUTED)) return 0; UNSET_FLAG(area->stub_router_state, OSPF_AREA_IS_STUB_ROUTED); ospf_router_lsa_update_area(area); return 0; } static void ospf_stub_router_check(struct ospf_area *area) { /* area must either be administratively configured to be stub * or startup-time stub-router must be configured and we must in a * pre-stub * state. */ if (CHECK_FLAG(area->stub_router_state, OSPF_AREA_ADMIN_STUB_ROUTED)) { SET_FLAG(area->stub_router_state, OSPF_AREA_IS_STUB_ROUTED); return; } /* not admin-stubbed, check whether startup stubbing is configured and * whether it's not been done yet */ if (CHECK_FLAG(area->stub_router_state, OSPF_AREA_WAS_START_STUB_ROUTED)) return; if (area->ospf->stub_router_startup_time == OSPF_STUB_ROUTER_UNCONFIGURED) { /* stub-router is hence done forever for this area, even if * someone * tries configure it (take effect next restart). */ SET_FLAG(area->stub_router_state, OSPF_AREA_WAS_START_STUB_ROUTED); return; } /* startup stub-router configured and not yet done */ SET_FLAG(area->stub_router_state, OSPF_AREA_IS_STUB_ROUTED); OSPF_AREA_TIMER_ON(area->t_stub_router, ospf_stub_router_timer, area->ospf->stub_router_startup_time); } /* Create new router-LSA. */ static struct ospf_lsa *ospf_router_lsa_new(struct ospf_area *area) { struct ospf *ospf = area->ospf; struct stream *s; struct lsa_header *lsah; struct ospf_lsa *new; int length; if (IS_DEBUG_OSPF(lsa, LSA_GENERATE)) zlog_debug("LSA[Type1]: Create router-LSA instance"); /* check whether stub-router is desired, and if this is the first * router LSA. */ ospf_stub_router_check(area); /* Create a stream for LSA. */ s = stream_new(OSPF_MAX_LSA_SIZE); /* Set LSA common header fields. */ lsa_header_set(s, LSA_OPTIONS_GET(area) | LSA_OPTIONS_NSSA_GET(area), OSPF_ROUTER_LSA, ospf->router_id, ospf->router_id); /* Set router-LSA body fields. */ ospf_router_lsa_body_set(&s, area); /* Set length. */ length = stream_get_endp(s); lsah = (struct lsa_header *)STREAM_DATA(s); lsah->length = htons(length); /* Now, create OSPF LSA instance. */ new = ospf_lsa_new_and_data(length); new->area = area; SET_FLAG(new->flags, OSPF_LSA_SELF | OSPF_LSA_SELF_CHECKED); new->vrf_id = area->ospf->vrf_id; /* Copy LSA data to store, discard stream. */ memcpy(new->data, lsah, length); stream_free(s); return new; } /* Originate Router-LSA. */ static struct ospf_lsa *ospf_router_lsa_originate(struct ospf_area *area) { struct ospf_lsa *new; /* Create new router-LSA instance. */ if ((new = ospf_router_lsa_new(area)) == NULL) { zlog_err("%s: ospf_router_lsa_new returned NULL", __func__); return NULL; } /* Sanity check. */ if (new->data->adv_router.s_addr == 0) { if (IS_DEBUG_OSPF_EVENT) zlog_debug("LSA[Type1]: AdvRouter is 0, discard"); ospf_lsa_discard(new); return NULL; } /* Install LSA to LSDB. */ new = ospf_lsa_install(area->ospf, NULL, new); /* Update LSA origination count. */ area->ospf->lsa_originate_count++; /* Flooding new LSA through area. */ ospf_flood_through_area(area, NULL, new); if (IS_DEBUG_OSPF(lsa, LSA_GENERATE)) { zlog_debug("LSA[Type%d:%s]: Originate router-LSA %p", new->data->type, inet_ntoa(new->data->id), (void *)new); ospf_lsa_header_dump(new->data); } return new; } /* Refresh router-LSA. */ static struct ospf_lsa *ospf_router_lsa_refresh(struct ospf_lsa *lsa) { struct ospf_area *area = lsa->area; struct ospf_lsa *new; /* Sanity check. */ assert(lsa->data); /* Delete LSA from neighbor retransmit-list. */ ospf_ls_retransmit_delete_nbr_area(area, lsa); /* Unregister LSA from refresh-list */ ospf_refresher_unregister_lsa(area->ospf, lsa); /* Create new router-LSA instance. */ if ((new = ospf_router_lsa_new(area)) == NULL) { zlog_err("%s: ospf_router_lsa_new returned NULL", __func__); return NULL; } new->data->ls_seqnum = lsa_seqnum_increment(lsa); ospf_lsa_install(area->ospf, NULL, new); /* Flood LSA through area. */ ospf_flood_through_area(area, NULL, new); /* Debug logging. */ if (IS_DEBUG_OSPF(lsa, LSA_GENERATE)) { zlog_debug("LSA[Type%d:%s]: router-LSA refresh", new->data->type, inet_ntoa(new->data->id)); ospf_lsa_header_dump(new->data); } return NULL; } int ospf_router_lsa_update_area(struct ospf_area *area) { if (IS_DEBUG_OSPF_EVENT) zlog_debug("[router-LSA]: (router-LSA area update)"); /* Now refresh router-LSA. */ if (area->router_lsa_self) ospf_lsa_refresh(area->ospf, area->router_lsa_self); /* Newly originate router-LSA. */ else ospf_router_lsa_originate(area); return 0; } int ospf_router_lsa_update(struct ospf *ospf) { struct listnode *node, *nnode; struct ospf_area *area; if (IS_DEBUG_OSPF(lsa, LSA_GENERATE)) zlog_debug("Timer[router-LSA Update]: (timer expire)"); for (ALL_LIST_ELEMENTS(ospf->areas, node, nnode, area)) { struct ospf_lsa *lsa = area->router_lsa_self; struct router_lsa *rl; const char *area_str; /* Keep Area ID string. */ area_str = AREA_NAME(area); /* If LSA not exist in this Area, originate new. */ if (lsa == NULL) { if (IS_DEBUG_OSPF(lsa, LSA_GENERATE)) zlog_debug( "LSA[Type1]: Create router-LSA for Area %s", area_str); ospf_router_lsa_originate(area); } /* If router-ID is changed, Link ID must change. First flush old LSA, then originate new. */ else if (!IPV4_ADDR_SAME(&lsa->data->id, &ospf->router_id)) { if (IS_DEBUG_OSPF(lsa, LSA_GENERATE)) zlog_debug( "LSA[Type%d:%s]: Refresh router-LSA for Area %s", lsa->data->type, inet_ntoa(lsa->data->id), area_str); ospf_refresher_unregister_lsa(ospf, lsa); ospf_lsa_flush_area(lsa, area); ospf_lsa_unlock(&area->router_lsa_self); area->router_lsa_self = NULL; /* Refresh router-LSA, (not install) and flood through * area. */ ospf_router_lsa_update_area(area); } else { rl = (struct router_lsa *)lsa->data; /* Refresh router-LSA, (not install) and flood through * area. */ if (rl->flags != ospf->flags) ospf_router_lsa_update_area(area); } } return 0; } /* network-LSA related functions. */ /* Originate Network-LSA. */ static void ospf_network_lsa_body_set(struct stream *s, struct ospf_interface *oi) { struct in_addr mask; struct route_node *rn; struct ospf_neighbor *nbr; masklen2ip(oi->address->prefixlen, &mask); stream_put_ipv4(s, mask.s_addr); /* The network-LSA lists those routers that are fully adjacent to the Designated Router; each fully adjacent router is identified by its OSPF Router ID. The Designated Router includes itself in this list. RFC2328, Section 12.4.2 */ for (rn = route_top(oi->nbrs); rn; rn = route_next(rn)) if ((nbr = rn->info) != NULL) if (nbr->state == NSM_Full || nbr == oi->nbr_self) stream_put_ipv4(s, nbr->router_id.s_addr); } static struct ospf_lsa *ospf_network_lsa_new(struct ospf_interface *oi) { struct stream *s; struct ospf_lsa *new; struct lsa_header *lsah; struct ospf_if_params *oip; int length; /* If there are no neighbours on this network (the net is stub), the router does not originate network-LSA (see RFC 12.4.2) */ if (oi->full_nbrs == 0) return NULL; if (IS_DEBUG_OSPF(lsa, LSA_GENERATE)) zlog_debug("LSA[Type2]: Create network-LSA instance"); /* Create new stream for LSA. */ s = stream_new(OSPF_MAX_LSA_SIZE); lsah = (struct lsa_header *)STREAM_DATA(s); lsa_header_set(s, (OPTIONS(oi) | LSA_OPTIONS_GET(oi->area)), OSPF_NETWORK_LSA, DR(oi), oi->ospf->router_id); /* Set network-LSA body fields. */ ospf_network_lsa_body_set(s, oi); /* Set length. */ length = stream_get_endp(s); lsah->length = htons(length); /* Create OSPF LSA instance. */ new = ospf_lsa_new_and_data(length); new->area = oi->area; SET_FLAG(new->flags, OSPF_LSA_SELF | OSPF_LSA_SELF_CHECKED); new->vrf_id = oi->ospf->vrf_id; /* Copy LSA to store. */ memcpy(new->data, lsah, length); stream_free(s); /* Remember prior network LSA sequence numbers, even if we stop * originating one for this oi, to try avoid re-originating LSAs with a * prior sequence number, and thus speed up adjency forming & * convergence. */ if ((oip = ospf_lookup_if_params(oi->ifp, oi->address->u.prefix4))) { new->data->ls_seqnum = oip->network_lsa_seqnum; new->data->ls_seqnum = lsa_seqnum_increment(new); } else { oip = ospf_get_if_params(oi->ifp, oi->address->u.prefix4); ospf_if_update_params(oi->ifp, oi->address->u.prefix4); } oip->network_lsa_seqnum = new->data->ls_seqnum; return new; } /* Originate network-LSA. */ void ospf_network_lsa_update(struct ospf_interface *oi) { struct ospf_lsa *new; if (oi->network_lsa_self != NULL) { ospf_lsa_refresh(oi->ospf, oi->network_lsa_self); return; } /* Create new network-LSA instance. */ new = ospf_network_lsa_new(oi); if (new == NULL) return; /* Install LSA to LSDB. */ new = ospf_lsa_install(oi->ospf, oi, new); /* Update LSA origination count. */ oi->ospf->lsa_originate_count++; /* Flooding new LSA through area. */ ospf_flood_through_area(oi->area, NULL, new); if (IS_DEBUG_OSPF(lsa, LSA_GENERATE)) { zlog_debug("LSA[Type%d:%s]: Originate network-LSA %p", new->data->type, inet_ntoa(new->data->id), (void *)new); ospf_lsa_header_dump(new->data); } return; } static struct ospf_lsa *ospf_network_lsa_refresh(struct ospf_lsa *lsa) { struct ospf_area *area = lsa->area; struct ospf_lsa *new, *new2; struct ospf_if_params *oip; struct ospf_interface *oi; assert(lsa->data); /* Retrieve the oi for the network LSA */ oi = ospf_if_lookup_by_local_addr(area->ospf, NULL, lsa->data->id); if (oi == NULL) { if (IS_DEBUG_OSPF(lsa, LSA_GENERATE)) { zlog_debug( "LSA[Type%d:%s]: network-LSA refresh: " "no oi found, ick, ignoring.", lsa->data->type, inet_ntoa(lsa->data->id)); ospf_lsa_header_dump(lsa->data); } return NULL; } /* Delete LSA from neighbor retransmit-list. */ ospf_ls_retransmit_delete_nbr_area(area, lsa); /* Unregister LSA from refresh-list */ ospf_refresher_unregister_lsa(area->ospf, lsa); /* Create new network-LSA instance. */ new = ospf_network_lsa_new(oi); if (new == NULL) return NULL; oip = ospf_lookup_if_params(oi->ifp, oi->address->u.prefix4); assert(oip != NULL); oip->network_lsa_seqnum = new->data->ls_seqnum = lsa_seqnum_increment(lsa); new2 = ospf_lsa_install(area->ospf, oi, new); assert(new2 == new); /* Flood LSA through aera. */ ospf_flood_through_area(area, NULL, new); if (IS_DEBUG_OSPF(lsa, LSA_GENERATE)) { zlog_debug("LSA[Type%d:%s]: network-LSA refresh", new->data->type, inet_ntoa(new->data->id)); ospf_lsa_header_dump(new->data); } return new; } static void stream_put_ospf_metric(struct stream *s, uint32_t metric_value) { uint32_t metric; char *mp; /* Put 0 metric. TOS metric is not supported. */ metric = htonl(metric_value); mp = (char *)&metric; mp++; stream_put(s, mp, 3); } /* summary-LSA related functions. */ static void ospf_summary_lsa_body_set(struct stream *s, struct prefix *p, uint32_t metric) { struct in_addr mask; masklen2ip(p->prefixlen, &mask); /* Put Network Mask. */ stream_put_ipv4(s, mask.s_addr); /* Set # TOS. */ stream_putc(s, (uint8_t)0); /* Set metric. */ stream_put_ospf_metric(s, metric); } static struct ospf_lsa *ospf_summary_lsa_new(struct ospf_area *area, struct prefix *p, uint32_t metric, struct in_addr id) { struct stream *s; struct ospf_lsa *new; struct lsa_header *lsah; int length; if (id.s_addr == 0xffffffff) { /* Maybe Link State ID not available. */ if (IS_DEBUG_OSPF(lsa, LSA_GENERATE)) zlog_debug( "LSA[Type%d]: Link ID not available, can't originate", OSPF_SUMMARY_LSA); return NULL; } if (IS_DEBUG_OSPF(lsa, LSA_GENERATE)) zlog_debug("LSA[Type3]: Create summary-LSA instance"); /* Create new stream for LSA. */ s = stream_new(OSPF_MAX_LSA_SIZE); lsah = (struct lsa_header *)STREAM_DATA(s); lsa_header_set(s, LSA_OPTIONS_GET(area), OSPF_SUMMARY_LSA, id, area->ospf->router_id); /* Set summary-LSA body fields. */ ospf_summary_lsa_body_set(s, p, metric); /* Set length. */ length = stream_get_endp(s); lsah->length = htons(length); /* Create OSPF LSA instance. */ new = ospf_lsa_new_and_data(length); new->area = area; SET_FLAG(new->flags, OSPF_LSA_SELF | OSPF_LSA_SELF_CHECKED); new->vrf_id = area->ospf->vrf_id; /* Copy LSA to store. */ memcpy(new->data, lsah, length); stream_free(s); return new; } /* Originate Summary-LSA. */ struct ospf_lsa *ospf_summary_lsa_originate(struct prefix_ipv4 *p, uint32_t metric, struct ospf_area *area) { struct ospf_lsa *new; struct in_addr id; id = ospf_lsa_unique_id(area->ospf, area->lsdb, OSPF_SUMMARY_LSA, p); if (id.s_addr == 0xffffffff) { /* Maybe Link State ID not available. */ if (IS_DEBUG_OSPF(lsa, LSA_GENERATE)) zlog_debug( "LSA[Type%d]: Link ID not available, can't originate", OSPF_SUMMARY_LSA); return NULL; } /* Create new summary-LSA instance. */ if (!(new = ospf_summary_lsa_new(area, (struct prefix *)p, metric, id))) return NULL; /* Instlal LSA to LSDB. */ new = ospf_lsa_install(area->ospf, NULL, new); /* Update LSA origination count. */ area->ospf->lsa_originate_count++; /* Flooding new LSA through area. */ ospf_flood_through_area(area, NULL, new); if (IS_DEBUG_OSPF(lsa, LSA_GENERATE)) { zlog_debug("LSA[Type%d:%s]: Originate summary-LSA %p", new->data->type, inet_ntoa(new->data->id), (void *)new); ospf_lsa_header_dump(new->data); } return new; } static struct ospf_lsa *ospf_summary_lsa_refresh(struct ospf *ospf, struct ospf_lsa *lsa) { struct ospf_lsa *new; struct summary_lsa *sl; struct prefix p; /* Sanity check. */ assert(lsa->data); sl = (struct summary_lsa *)lsa->data; p.prefixlen = ip_masklen(sl->mask); new = ospf_summary_lsa_new(lsa->area, &p, GET_METRIC(sl->metric), sl->header.id); if (!new) return NULL; new->data->ls_seqnum = lsa_seqnum_increment(lsa); ospf_lsa_install(ospf, NULL, new); /* Flood LSA through AS. */ ospf_flood_through_area(new->area, NULL, new); /* Debug logging. */ if (IS_DEBUG_OSPF(lsa, LSA_GENERATE)) { zlog_debug("LSA[Type%d:%s]: summary-LSA refresh", new->data->type, inet_ntoa(new->data->id)); ospf_lsa_header_dump(new->data); } return new; } /* summary-ASBR-LSA related functions. */ static void ospf_summary_asbr_lsa_body_set(struct stream *s, struct prefix *p, uint32_t metric) { /* Put Network Mask. */ stream_put_ipv4(s, (uint32_t)0); /* Set # TOS. */ stream_putc(s, (uint8_t)0); /* Set metric. */ stream_put_ospf_metric(s, metric); } static struct ospf_lsa *ospf_summary_asbr_lsa_new(struct ospf_area *area, struct prefix *p, uint32_t metric, struct in_addr id) { struct stream *s; struct ospf_lsa *new; struct lsa_header *lsah; int length; if (id.s_addr == 0xffffffff) { /* Maybe Link State ID not available. */ if (IS_DEBUG_OSPF(lsa, LSA_GENERATE)) zlog_debug( "LSA[Type%d]: Link ID not available, can't originate", OSPF_ASBR_SUMMARY_LSA); return NULL; } if (IS_DEBUG_OSPF(lsa, LSA_GENERATE)) zlog_debug("LSA[Type3]: Create summary-LSA instance"); /* Create new stream for LSA. */ s = stream_new(OSPF_MAX_LSA_SIZE); lsah = (struct lsa_header *)STREAM_DATA(s); lsa_header_set(s, LSA_OPTIONS_GET(area), OSPF_ASBR_SUMMARY_LSA, id, area->ospf->router_id); /* Set summary-LSA body fields. */ ospf_summary_asbr_lsa_body_set(s, p, metric); /* Set length. */ length = stream_get_endp(s); lsah->length = htons(length); /* Create OSPF LSA instance. */ new = ospf_lsa_new_and_data(length); new->area = area; SET_FLAG(new->flags, OSPF_LSA_SELF | OSPF_LSA_SELF_CHECKED); new->vrf_id = area->ospf->vrf_id; /* Copy LSA to store. */ memcpy(new->data, lsah, length); stream_free(s); return new; } /* Originate summary-ASBR-LSA. */ struct ospf_lsa *ospf_summary_asbr_lsa_originate(struct prefix_ipv4 *p, uint32_t metric, struct ospf_area *area) { struct ospf_lsa *new; struct in_addr id; id = ospf_lsa_unique_id(area->ospf, area->lsdb, OSPF_ASBR_SUMMARY_LSA, p); if (id.s_addr == 0xffffffff) { /* Maybe Link State ID not available. */ if (IS_DEBUG_OSPF(lsa, LSA_GENERATE)) zlog_debug( "LSA[Type%d]: Link ID not available, can't originate", OSPF_ASBR_SUMMARY_LSA); return NULL; } /* Create new summary-LSA instance. */ new = ospf_summary_asbr_lsa_new(area, (struct prefix *)p, metric, id); if (!new) return NULL; /* Install LSA to LSDB. */ new = ospf_lsa_install(area->ospf, NULL, new); /* Update LSA origination count. */ area->ospf->lsa_originate_count++; /* Flooding new LSA through area. */ ospf_flood_through_area(area, NULL, new); if (IS_DEBUG_OSPF(lsa, LSA_GENERATE)) { zlog_debug("LSA[Type%d:%s]: Originate summary-ASBR-LSA %p", new->data->type, inet_ntoa(new->data->id), (void *)new); ospf_lsa_header_dump(new->data); } return new; } static struct ospf_lsa *ospf_summary_asbr_lsa_refresh(struct ospf *ospf, struct ospf_lsa *lsa) { struct ospf_lsa *new; struct summary_lsa *sl; struct prefix p; /* Sanity check. */ assert(lsa->data); sl = (struct summary_lsa *)lsa->data; p.prefixlen = ip_masklen(sl->mask); new = ospf_summary_asbr_lsa_new(lsa->area, &p, GET_METRIC(sl->metric), sl->header.id); if (!new) return NULL; new->data->ls_seqnum = lsa_seqnum_increment(lsa); ospf_lsa_install(ospf, NULL, new); /* Flood LSA through area. */ ospf_flood_through_area(new->area, NULL, new); if (IS_DEBUG_OSPF(lsa, LSA_GENERATE)) { zlog_debug("LSA[Type%d:%s]: summary-ASBR-LSA refresh", new->data->type, inet_ntoa(new->data->id)); ospf_lsa_header_dump(new->data); } return new; } /* AS-external-LSA related functions. */ /* Get nexthop for AS-external-LSAs. Return nexthop if its interface is connected, else 0*/ static struct in_addr ospf_external_lsa_nexthop_get(struct ospf *ospf, struct in_addr nexthop) { struct in_addr fwd; struct prefix nh; struct listnode *node; struct ospf_interface *oi; fwd.s_addr = 0; if (!nexthop.s_addr) return fwd; /* Check whether nexthop is covered by OSPF network. */ nh.family = AF_INET; nh.u.prefix4 = nexthop; nh.prefixlen = IPV4_MAX_BITLEN; /* XXX/SCALE: If there were a lot of oi's on an ifp, then it'd be * better to make use of the per-ifp table of ois. */ for (ALL_LIST_ELEMENTS_RO(ospf->oiflist, node, oi)) if (if_is_operative(oi->ifp)) if (oi->address->family == AF_INET) if (prefix_match(oi->address, &nh)) return nexthop; return fwd; } /* NSSA-external-LSA related functions. */ /* Get 1st IP connection for Forward Addr */ struct in_addr ospf_get_ip_from_ifp(struct ospf_interface *oi) { struct in_addr fwd; fwd.s_addr = 0; if (if_is_operative(oi->ifp)) return oi->address->u.prefix4; return fwd; } /* Get 1st IP connection for Forward Addr */ struct in_addr ospf_get_nssa_ip(struct ospf_area *area) { struct in_addr fwd; struct in_addr best_default; struct listnode *node; struct ospf_interface *oi; fwd.s_addr = 0; best_default.s_addr = 0; for (ALL_LIST_ELEMENTS_RO(area->ospf->oiflist, node, oi)) { if (if_is_operative(oi->ifp)) if (oi->area->external_routing == OSPF_AREA_NSSA) if (oi->address && oi->address->family == AF_INET) { if (best_default.s_addr == 0) best_default = oi->address->u.prefix4; if (oi->area == area) return oi->address->u.prefix4; } } if (best_default.s_addr != 0) return best_default; if (best_default.s_addr != 0) return best_default; return fwd; } int metric_type(struct ospf *ospf, uint8_t src, unsigned short instance) { struct ospf_redist *red; red = ospf_redist_lookup(ospf, src, instance); return ((!red || red->dmetric.type < 0) ? DEFAULT_METRIC_TYPE : red->dmetric.type); } int metric_value(struct ospf *ospf, uint8_t src, unsigned short instance) { struct ospf_redist *red; red = ospf_redist_lookup(ospf, src, instance); if (!red || red->dmetric.value < 0) { if (src == DEFAULT_ROUTE) { if (ospf->default_originate == DEFAULT_ORIGINATE_ZEBRA) return DEFAULT_DEFAULT_ORIGINATE_METRIC; else return DEFAULT_DEFAULT_ALWAYS_METRIC; } else if (ospf->default_metric < 0) return DEFAULT_DEFAULT_METRIC; else return ospf->default_metric; } return red->dmetric.value; } /* Set AS-external-LSA body. */ static void ospf_external_lsa_body_set(struct stream *s, struct external_info *ei, struct ospf *ospf) { struct prefix_ipv4 *p = &ei->p; struct in_addr mask, fwd_addr; uint32_t mvalue; int mtype; int type; unsigned short instance; /* Put Network Mask. */ masklen2ip(p->prefixlen, &mask); stream_put_ipv4(s, mask.s_addr); /* If prefix is default, specify DEFAULT_ROUTE. */ type = is_prefix_default(&ei->p) ? DEFAULT_ROUTE : ei->type; instance = is_prefix_default(&ei->p) ? 0 : ei->instance; mtype = (ROUTEMAP_METRIC_TYPE(ei) != -1) ? ROUTEMAP_METRIC_TYPE(ei) : metric_type(ospf, type, instance); mvalue = (ROUTEMAP_METRIC(ei) != -1) ? ROUTEMAP_METRIC(ei) : metric_value(ospf, type, instance); /* Put type of external metric. */ stream_putc(s, (mtype == EXTERNAL_METRIC_TYPE_2 ? 0x80 : 0)); /* Put 0 metric. TOS metric is not supported. */ stream_put_ospf_metric(s, mvalue); /* Get forwarding address to nexthop if on the Connection List, else 0. */ fwd_addr = ospf_external_lsa_nexthop_get(ospf, ei->nexthop); /* Put forwarding address. */ stream_put_ipv4(s, fwd_addr.s_addr); /* Put route tag */ stream_putl(s, ei->tag); } /* Create new external-LSA. */ static struct ospf_lsa *ospf_external_lsa_new(struct ospf *ospf, struct external_info *ei, struct in_addr *old_id) { struct stream *s; struct lsa_header *lsah; struct ospf_lsa *new; struct in_addr id; int length; if (ei == NULL) { if (IS_DEBUG_OSPF(lsa, LSA_GENERATE)) zlog_debug( "LSA[Type5]: External info is NULL, can't originate"); return NULL; } if (IS_DEBUG_OSPF(lsa, LSA_GENERATE)) zlog_debug("LSA[Type5]: Originate AS-external-LSA instance"); /* If old Link State ID is specified, refresh LSA with same ID. */ if (old_id) id = *old_id; /* Get Link State with unique ID. */ else { id = ospf_lsa_unique_id(ospf, ospf->lsdb, OSPF_AS_EXTERNAL_LSA, &ei->p); if (id.s_addr == 0xffffffff) { /* Maybe Link State ID not available. */ if (IS_DEBUG_OSPF(lsa, LSA_GENERATE)) zlog_debug( "LSA[Type5]: Link ID not available, can't originate"); return NULL; } } /* Create new stream for LSA. */ s = stream_new(OSPF_MAX_LSA_SIZE); lsah = (struct lsa_header *)STREAM_DATA(s); /* Set LSA common header fields. */ lsa_header_set(s, OSPF_OPTION_E, OSPF_AS_EXTERNAL_LSA, id, ospf->router_id); /* Set AS-external-LSA body fields. */ ospf_external_lsa_body_set(s, ei, ospf); /* Set length. */ length = stream_get_endp(s); lsah->length = htons(length); /* Now, create OSPF LSA instance. */ new = ospf_lsa_new_and_data(length); new->area = NULL; SET_FLAG(new->flags, OSPF_LSA_SELF | OSPF_LSA_APPROVED | OSPF_LSA_SELF_CHECKED); new->vrf_id = ospf->vrf_id; /* Copy LSA data to store, discard stream. */ memcpy(new->data, lsah, length); stream_free(s); return new; } /* As Type-7 */ static void ospf_install_flood_nssa(struct ospf *ospf, struct ospf_lsa *lsa, struct external_info *ei) { struct ospf_lsa *new; struct as_external_lsa *extlsa; struct ospf_area *area; struct listnode *node, *nnode; /* LSA may be a Type-5 originated via translation of a Type-7 LSA * which originated from an NSSA area. In which case it should not be * flooded back to NSSA areas. */ if (CHECK_FLAG(lsa->flags, OSPF_LSA_LOCAL_XLT)) return; /* NSSA Originate or Refresh (If anyNSSA) LSA is self-originated. And just installed as Type-5. Additionally, install as Type-7 LSDB for every attached NSSA. P-Bit controls which ABR performs translation to outside world; If we are an ABR....do not set the P-bit, because we send the Type-5, not as the ABR Translator, but as the ASBR owner within the AS! If we are NOT ABR, Flood through NSSA as Type-7 w/P-bit set. The elected ABR Translator will see the P-bit, Translate, and re-flood. Later, ABR_TASK and P-bit will scan Type-7 LSDB and translate to Type-5's to non-NSSA Areas. (it will also attempt a re-install) */ for (ALL_LIST_ELEMENTS(ospf->areas, node, nnode, area)) { /* Don't install Type-7 LSA's into nonNSSA area */ if (area->external_routing != OSPF_AREA_NSSA) continue; /* make lsa duplicate, lock=1 */ new = ospf_lsa_dup(lsa); new->area = area; new->data->type = OSPF_AS_NSSA_LSA; /* set P-bit if not ABR */ if (!IS_OSPF_ABR(ospf)) { SET_FLAG(new->data->options, OSPF_OPTION_NP); /* set non-zero FWD ADDR draft-ietf-ospf-nssa-update-09.txt if the network between the NSSA AS boundary router and the adjacent AS is advertised into OSPF as an internal OSPF route, the forwarding address should be the next op address as is cu currently done with type-5 LSAs. If the intervening network is not adversited into OSPF as an internal OSPF route and the type-7 LSA's P-bit is set a forwarding address should be selected from one of the router's active OSPF interface addresses which belong to the NSSA. If no such addresses exist, then no type-7 LSA's with the P-bit set should originate from this router. */ /* kevinm: not updating lsa anymore, just new */ extlsa = (struct as_external_lsa *)(new->data); if (extlsa->e[0].fwd_addr.s_addr == 0) extlsa->e[0].fwd_addr = ospf_get_nssa_ip( area); /* this NSSA area in ifp */ if (extlsa->e[0].fwd_addr.s_addr == 0) { if (IS_DEBUG_OSPF_NSSA) zlog_debug( "LSA[Type-7]: Could not build FWD-ADDR"); ospf_lsa_discard(new); return; } } /* install also as Type-7 */ ospf_lsa_install(ospf, NULL, new); /* Remove Old, Lock New = 2 */ /* will send each copy, lock=2+n */ ospf_flood_through_as( ospf, NULL, new); /* all attached NSSA's, no AS/STUBs */ } } static struct ospf_lsa *ospf_lsa_translated_nssa_new(struct ospf *ospf, struct ospf_lsa *type7) { struct ospf_lsa *new; struct as_external_lsa *ext, *extnew; struct external_info ei; ext = (struct as_external_lsa *)(type7->data); /* need external_info struct, fill in bare minimum */ ei.p.family = AF_INET; ei.p.prefix = type7->data->id; ei.p.prefixlen = ip_masklen(ext->mask); ei.type = ZEBRA_ROUTE_OSPF; ei.nexthop = ext->header.adv_router; ei.route_map_set.metric = -1; ei.route_map_set.metric_type = -1; ei.tag = 0; ei.instance = 0; if ((new = ospf_external_lsa_new(ospf, &ei, &type7->data->id)) == NULL) { if (IS_DEBUG_OSPF_NSSA) zlog_debug( "ospf_nssa_translate_originate(): Could not originate " "Translated Type-5 for %s", inet_ntoa(ei.p.prefix)); return NULL; } extnew = (struct as_external_lsa *)(new->data); /* copy over Type-7 data to new */ extnew->e[0].tos = ext->e[0].tos; extnew->e[0].route_tag = ext->e[0].route_tag; extnew->e[0].fwd_addr.s_addr = ext->e[0].fwd_addr.s_addr; new->data->ls_seqnum = type7->data->ls_seqnum; /* add translated flag, checksum and lock new lsa */ SET_FLAG(new->flags, OSPF_LSA_LOCAL_XLT); /* Translated from 7 */ new = ospf_lsa_lock(new); return new; } /* Originate Translated Type-5 for supplied Type-7 NSSA LSA */ struct ospf_lsa *ospf_translated_nssa_originate(struct ospf *ospf, struct ospf_lsa *type7) { struct ospf_lsa *new; struct as_external_lsa *extnew; /* we cant use ospf_external_lsa_originate() as we need to set * the OSPF_LSA_LOCAL_XLT flag, must originate by hand */ if ((new = ospf_lsa_translated_nssa_new(ospf, type7)) == NULL) { if (IS_DEBUG_OSPF_NSSA) zlog_debug( "ospf_translated_nssa_originate(): Could not translate " "Type-7, Id %s, to Type-5", inet_ntoa(type7->data->id)); return NULL; } extnew = (struct as_external_lsa *)new; if (IS_DEBUG_OSPF_NSSA) { zlog_debug( "ospf_translated_nssa_originate(): " "translated Type 7, installed:"); ospf_lsa_header_dump(new->data); zlog_debug(" Network mask: %d", ip_masklen(extnew->mask)); zlog_debug(" Forward addr: %s", inet_ntoa(extnew->e[0].fwd_addr)); } if ((new = ospf_lsa_install(ospf, NULL, new)) == NULL) { flog_warn(EC_OSPF_LSA_INSTALL_FAILURE, "ospf_lsa_translated_nssa_originate(): " "Could not install LSA " "id %s", inet_ntoa(type7->data->id)); return NULL; } ospf->lsa_originate_count++; ospf_flood_through_as(ospf, NULL, new); return new; } /* Refresh Translated from NSSA AS-external-LSA. */ struct ospf_lsa *ospf_translated_nssa_refresh(struct ospf *ospf, struct ospf_lsa *type7, struct ospf_lsa *type5) { struct ospf_lsa *new = NULL; /* Sanity checks. */ assert(type7 || type5); if (!(type7 || type5)) return NULL; if (type7) assert(type7->data); if (type5) assert(type5->data); assert(ospf->anyNSSA); /* get required data according to what has been given */ if (type7 && type5 == NULL) { /* find the translated Type-5 for this Type-7 */ struct as_external_lsa *ext = (struct as_external_lsa *)(type7->data); struct prefix_ipv4 p = { .prefix = type7->data->id, .prefixlen = ip_masklen(ext->mask), .family = AF_INET, }; type5 = ospf_external_info_find_lsa(ospf, &p); } else if (type5 && type7 == NULL) { /* find the type-7 from which supplied type-5 was translated, * ie find first type-7 with same LSA Id. */ struct listnode *ln, *lnn; struct route_node *rn; struct ospf_lsa *lsa; struct ospf_area *area; for (ALL_LIST_ELEMENTS(ospf->areas, ln, lnn, area)) { if (area->external_routing != OSPF_AREA_NSSA && !type7) continue; LSDB_LOOP (NSSA_LSDB(area), rn, lsa) { if (lsa->data->id.s_addr == type5->data->id.s_addr) { type7 = lsa; break; } } } } /* do we have type7? */ if (!type7) { if (IS_DEBUG_OSPF_NSSA) zlog_debug( "ospf_translated_nssa_refresh(): no Type-7 found for " "Type-5 LSA Id %s", inet_ntoa(type5->data->id)); return NULL; } /* do we have valid translated type5? */ if (type5 == NULL || !CHECK_FLAG(type5->flags, OSPF_LSA_LOCAL_XLT)) { if (IS_DEBUG_OSPF_NSSA) zlog_debug( "ospf_translated_nssa_refresh(): No translated Type-5 " "found for Type-7 with Id %s", inet_ntoa(type7->data->id)); return NULL; } /* Delete LSA from neighbor retransmit-list. */ ospf_ls_retransmit_delete_nbr_as(ospf, type5); /* create new translated LSA */ if ((new = ospf_lsa_translated_nssa_new(ospf, type7)) == NULL) { if (IS_DEBUG_OSPF_NSSA) zlog_debug( "ospf_translated_nssa_refresh(): Could not translate " "Type-7 for %s to Type-5", inet_ntoa(type7->data->id)); return NULL; } if (!(new = ospf_lsa_install(ospf, NULL, new))) { flog_warn( EC_OSPF_LSA_INSTALL_FAILURE, "ospf_translated_nssa_refresh(): Could not install translated LSA, Id %s", inet_ntoa(type7->data->id)); return NULL; } /* Flood LSA through area. */ ospf_flood_through_as(ospf, NULL, new); return new; } int is_prefix_default(struct prefix_ipv4 *p) { struct prefix_ipv4 q; q.family = AF_INET; q.prefix.s_addr = 0; q.prefixlen = 0; return prefix_same((struct prefix *)p, (struct prefix *)&q); } /* Originate an AS-external-LSA, install and flood. */ struct ospf_lsa *ospf_external_lsa_originate(struct ospf *ospf, struct external_info *ei) { struct ospf_lsa *new; /* Added for NSSA project.... External LSAs are originated in ASBRs as usual, but for NSSA systems. there is the global Type-5 LSDB and a Type-7 LSDB installed for every area. The Type-7's are flooded to every IR and every ABR; We install the Type-5 LSDB so that the normal "refresh" code operates as usual, and flag them as not used during ASE calculations. The Type-7 LSDB is used for calculations. Each Type-7 has a Forwarding Address of non-zero. If an ABR is the elected NSSA translator, following SPF and during the ABR task it will translate all the scanned Type-7's, with P-bit ON and not-self generated, and translate to Type-5's throughout the non-NSSA/STUB AS. A difference in operation depends whether this ASBR is an ABR or not. If not an ABR, the P-bit is ON, to indicate that any elected NSSA-ABR can perform its translation. If an ABR, the P-bit is OFF; No ABR will perform translation and this ASBR will flood the Type-5 LSA as usual. For the case where this ASBR is not an ABR, the ASE calculations are based on the Type-5 LSDB; The Type-7 LSDB exists just to demonstrate to the user that there are LSA's that belong to any attached NSSA. Finally, it just so happens that when the ABR is translating every Type-7 into Type-5, it installs it into the Type-5 LSDB as an approved Type-5 (translated from Type-7); at the end of translation if any Translated Type-5's remain unapproved, then they must be flushed from the AS. */ if (ospf->router_id.s_addr == 0) { if (IS_DEBUG_OSPF_EVENT) zlog_debug("LSA[Type5:%pI4]: deferring AS-external-LSA origination, router ID is zero", &ei->p.prefix); return NULL; } /* Check the AS-external-LSA should be originated. */ if (!ospf_redistribute_check(ospf, ei, NULL)) return NULL; /* Create new AS-external-LSA instance. */ if ((new = ospf_external_lsa_new(ospf, ei, NULL)) == NULL) { if (IS_DEBUG_OSPF_EVENT) zlog_debug( "LSA[Type5:%s]: Could not originate AS-external-LSA", inet_ntoa(ei->p.prefix)); return NULL; } /* Install newly created LSA into Type-5 LSDB, lock = 1. */ ospf_lsa_install(ospf, NULL, new); /* Update LSA origination count. */ ospf->lsa_originate_count++; /* Flooding new LSA. only to AS (non-NSSA/STUB) */ ospf_flood_through_as(ospf, NULL, new); /* If there is any attached NSSA, do special handling */ if (ospf->anyNSSA && /* stay away from translated LSAs! */ !(CHECK_FLAG(new->flags, OSPF_LSA_LOCAL_XLT))) ospf_install_flood_nssa( ospf, new, ei); /* Install/Flood Type-7 to all NSSAs */ /* Debug logging. */ if (IS_DEBUG_OSPF(lsa, LSA_GENERATE)) { zlog_debug("LSA[Type%d:%s]: Originate AS-external-LSA %p", new->data->type, inet_ntoa(new->data->id), (void *)new); ospf_lsa_header_dump(new->data); } return new; } static struct external_info *ospf_default_external_info(struct ospf *ospf) { int type; struct route_node *rn; struct prefix_ipv4 p; p.family = AF_INET; p.prefix.s_addr = 0; p.prefixlen = 0; /* First, lookup redistributed default route. */ for (type = 0; type <= ZEBRA_ROUTE_MAX; type++) { struct list *ext_list; struct listnode *node; struct ospf_external *ext; if (type == ZEBRA_ROUTE_OSPF) continue; ext_list = ospf->external[type]; if (!ext_list) continue; for (ALL_LIST_ELEMENTS_RO(ext_list, node, ext)) { rn = route_node_lookup(ext->external_info, (struct prefix *)&p); if (rn != NULL) { route_unlock_node(rn); assert(rn->info); if (ospf_redistribute_check(ospf, rn->info, NULL)) return rn->info; } } } return NULL; } void ospf_external_lsa_rid_change(struct ospf *ospf) { struct external_info *ei; int type; for (type = 0; type < ZEBRA_ROUTE_MAX; type++) { struct route_node *rn; struct route_table *rt; struct list *ext_list; struct listnode *node; struct ospf_external *ext; ext_list = ospf->external[type]; if (!ext_list) continue; for (ALL_LIST_ELEMENTS_RO(ext_list, node, ext)) { /* Originate As-external-LSA from all type of * distribute source. */ rt = ext->external_info; if (!rt) continue; for (rn = route_top(rt); rn; rn = route_next(rn)) { ei = rn->info; if (!ei) continue; if (is_prefix_default( (struct prefix_ipv4 *)&ei->p)) continue; if (!ospf_external_lsa_originate(ospf, ei)) flog_warn(EC_OSPF_LSA_INSTALL_FAILURE, "LSA: AS-external-LSA was not originated."); } } } ei = ospf_default_external_info(ospf); if (ei && !ospf_external_lsa_originate(ospf, ei)) { flog_warn(EC_OSPF_LSA_INSTALL_FAILURE, "LSA: AS-external-LSA for default route was not originated."); } } /* Flush any NSSA LSAs for given prefix */ void ospf_nssa_lsa_flush(struct ospf *ospf, struct prefix_ipv4 *p) { struct listnode *node, *nnode; struct ospf_lsa *lsa = NULL; struct ospf_area *area; for (ALL_LIST_ELEMENTS(ospf->areas, node, nnode, area)) { if (area->external_routing == OSPF_AREA_NSSA) { lsa = ospf_lsa_lookup(ospf, area, OSPF_AS_NSSA_LSA, p->prefix, ospf->router_id); if (!lsa) { if (IS_DEBUG_OSPF(lsa, LSA_FLOODING)) zlog_debug( "LSA: There is no such AS-NSSA-LSA %s/%d in LSDB", inet_ntoa(p->prefix), p->prefixlen); continue; } ospf_ls_retransmit_delete_nbr_area(area, lsa); if (!IS_LSA_MAXAGE(lsa)) { ospf_refresher_unregister_lsa(ospf, lsa); ospf_lsa_flush_area(lsa, area); } } } } /* Flush an AS-external-LSA from LSDB and routing domain. */ void ospf_external_lsa_flush(struct ospf *ospf, uint8_t type, struct prefix_ipv4 *p, ifindex_t ifindex /*, struct in_addr nexthop */) { struct ospf_lsa *lsa; if (IS_DEBUG_OSPF(lsa, LSA_FLOODING)) zlog_debug("LSA: Flushing AS-external-LSA %s/%d", inet_ntoa(p->prefix), p->prefixlen); /* First lookup LSA from LSDB. */ if (!(lsa = ospf_external_info_find_lsa(ospf, p))) { if (IS_DEBUG_OSPF(lsa, LSA_FLOODING)) zlog_debug( "LSA: There is no such AS-external-LSA %s/%d in LSDB", inet_ntoa(p->prefix), p->prefixlen); return; } /* If LSA is selforiginated, not a translated LSA, and there is * NSSA area, flush Type-7 LSA's at first. */ if (IS_LSA_SELF(lsa) && (ospf->anyNSSA) && !(CHECK_FLAG(lsa->flags, OSPF_LSA_LOCAL_XLT))) ospf_nssa_lsa_flush(ospf, p); /* Sweep LSA from Link State Retransmit List. */ ospf_ls_retransmit_delete_nbr_as(ospf, lsa); /* There must be no self-originated LSA in rtrs_external. */ #if 0 /* Remove External route from Zebra. */ ospf_zebra_delete ((struct prefix_ipv4 *) p, &nexthop); #endif if (!IS_LSA_MAXAGE(lsa)) { /* Unregister LSA from Refresh queue. */ ospf_refresher_unregister_lsa(ospf, lsa); /* Flush AS-external-LSA through AS. */ ospf_lsa_flush_as(ospf, lsa); } if (IS_DEBUG_OSPF(lsa, LSA_FLOODING)) zlog_debug("ospf_external_lsa_flush(): stop"); } void ospf_external_lsa_refresh_default(struct ospf *ospf) { struct prefix_ipv4 p; struct external_info *ei; struct ospf_lsa *lsa; p.family = AF_INET; p.prefixlen = 0; p.prefix.s_addr = 0; ei = ospf_default_external_info(ospf); lsa = ospf_external_info_find_lsa(ospf, &p); if (ei && lsa) { if (IS_DEBUG_OSPF_EVENT) zlog_debug("LSA[Type5:0.0.0.0]: Refresh AS-external-LSA %p", (void *)lsa); ospf_external_lsa_refresh(ospf, lsa, ei, LSA_REFRESH_FORCE); } else if (ei && !lsa) { if (IS_DEBUG_OSPF_EVENT) zlog_debug( "LSA[Type5:0.0.0.0]: Originate AS-external-LSA"); ospf_external_lsa_originate(ospf, ei); } else if (lsa) { if (IS_DEBUG_OSPF_EVENT) zlog_debug("LSA[Type5:0.0.0.0]: Flush AS-external-LSA"); ospf_external_lsa_flush(ospf, DEFAULT_ROUTE, &p, 0); } } void ospf_external_lsa_refresh_type(struct ospf *ospf, uint8_t type, unsigned short instance, int force) { struct route_node *rn; struct external_info *ei; struct ospf_external *ext; if (type == DEFAULT_ROUTE) return; ext = ospf_external_lookup(ospf, type, instance); if (ext && EXTERNAL_INFO(ext)) { /* Refresh each redistributed AS-external-LSAs. */ for (rn = route_top(EXTERNAL_INFO(ext)); rn; rn = route_next(rn)) { ei = rn->info; if (ei) { if (!is_prefix_default(&ei->p)) { struct ospf_lsa *lsa; lsa = ospf_external_info_find_lsa( ospf, &ei->p); if (lsa) ospf_external_lsa_refresh( ospf, lsa, ei, force); else ospf_external_lsa_originate( ospf, ei); } } } } } /* Refresh AS-external-LSA. */ struct ospf_lsa *ospf_external_lsa_refresh(struct ospf *ospf, struct ospf_lsa *lsa, struct external_info *ei, int force) { struct ospf_lsa *new; int changed; /* Check the AS-external-LSA should be originated. */ if (!ospf_redistribute_check(ospf, ei, &changed)) { if (IS_DEBUG_OSPF(lsa, LSA_GENERATE)) zlog_debug( "LSA[Type%d:%s]: Could not be refreshed, " "redist check fail", lsa->data->type, inet_ntoa(lsa->data->id)); ospf_external_lsa_flush(ospf, ei->type, &ei->p, ei->ifindex /*, ei->nexthop */); return NULL; } if (!changed && !force) { if (IS_DEBUG_OSPF(lsa, LSA_GENERATE)) zlog_debug( "LSA[Type%d:%s]: Not refreshed, not changed/forced", lsa->data->type, inet_ntoa(lsa->data->id)); return NULL; } /* Delete LSA from neighbor retransmit-list. */ ospf_ls_retransmit_delete_nbr_as(ospf, lsa); /* Unregister AS-external-LSA from refresh-list. */ ospf_refresher_unregister_lsa(ospf, lsa); new = ospf_external_lsa_new(ospf, ei, &lsa->data->id); if (new == NULL) { if (IS_DEBUG_OSPF(lsa, LSA_GENERATE)) zlog_debug("LSA[Type%d:%s]: Could not be refreshed", lsa->data->type, inet_ntoa(lsa->data->id)); return NULL; } new->data->ls_seqnum = lsa_seqnum_increment(lsa); ospf_lsa_install(ospf, NULL, new); /* As type-5. */ /* Flood LSA through AS. */ ospf_flood_through_as(ospf, NULL, new); /* If any attached NSSA, install as Type-7, flood to all NSSA Areas */ if (ospf->anyNSSA && !(CHECK_FLAG(new->flags, OSPF_LSA_LOCAL_XLT))) ospf_install_flood_nssa(ospf, new, ei); /* Install/Flood per new rules */ /* Register self-originated LSA to refresh queue. * Translated LSAs should not be registered, but refreshed upon * refresh of the Type-7 */ if (!CHECK_FLAG(new->flags, OSPF_LSA_LOCAL_XLT)) ospf_refresher_register_lsa(ospf, new); /* Debug logging. */ if (IS_DEBUG_OSPF(lsa, LSA_GENERATE)) { zlog_debug("LSA[Type%d:%s]: AS-external-LSA refresh", new->data->type, inet_ntoa(new->data->id)); ospf_lsa_header_dump(new->data); } return new; } /* LSA installation functions. */ /* Install router-LSA to an area. */ static struct ospf_lsa * ospf_router_lsa_install(struct ospf *ospf, struct ospf_lsa *new, int rt_recalc) { struct ospf_area *area = new->area; /* RFC 2328 Section 13.2 Router-LSAs and network-LSAs The entire routing table must be recalculated, starting with the shortest path calculations for each area (not just the area whose link-state database has changed). */ if (IS_LSA_SELF(new)) { /* Only install LSA if it is originated/refreshed by us. * If LSA was received by flooding, the RECEIVED flag is set so * do * not link the LSA */ if (CHECK_FLAG(new->flags, OSPF_LSA_RECEIVED)) return new; /* ignore stale LSA */ /* Set self-originated router-LSA. */ ospf_lsa_unlock(&area->router_lsa_self); area->router_lsa_self = ospf_lsa_lock(new); ospf_refresher_register_lsa(ospf, new); } if (rt_recalc) ospf_spf_calculate_schedule(ospf, SPF_FLAG_ROUTER_LSA_INSTALL); return new; } #define OSPF_INTERFACE_TIMER_ON(T, F, V) \ if (!(T)) \ (T) = thread_add_timer(master, (F), oi, (V)) /* Install network-LSA to an area. */ static struct ospf_lsa *ospf_network_lsa_install(struct ospf *ospf, struct ospf_interface *oi, struct ospf_lsa *new, int rt_recalc) { /* RFC 2328 Section 13.2 Router-LSAs and network-LSAs The entire routing table must be recalculated, starting with the shortest path calculations for each area (not just the area whose link-state database has changed). */ if (IS_LSA_SELF(new)) { /* We supposed that when LSA is originated by us, we pass the int for which it was originated. If LSA was received by flooding, the RECEIVED flag is set, so we do not link the LSA to the int. */ if (CHECK_FLAG(new->flags, OSPF_LSA_RECEIVED)) return new; /* ignore stale LSA */ ospf_lsa_unlock(&oi->network_lsa_self); oi->network_lsa_self = ospf_lsa_lock(new); ospf_refresher_register_lsa(ospf, new); } if (rt_recalc) ospf_spf_calculate_schedule(ospf, SPF_FLAG_NETWORK_LSA_INSTALL); return new; } /* Install summary-LSA to an area. */ static struct ospf_lsa * ospf_summary_lsa_install(struct ospf *ospf, struct ospf_lsa *new, int rt_recalc) { if (rt_recalc && !IS_LSA_SELF(new)) { /* RFC 2328 Section 13.2 Summary-LSAs The best route to the destination described by the summary- LSA must be recalculated (see Section 16.5). If this destination is an AS boundary router, it may also be necessary to re-examine all the AS-external-LSAs. */ #if 0 /* This doesn't exist yet... */ ospf_summary_incremental_update(new); */ #else /* #if 0 */ ospf_spf_calculate_schedule(ospf, SPF_FLAG_SUMMARY_LSA_INSTALL); #endif /* #if 0 */ } if (IS_LSA_SELF(new)) ospf_refresher_register_lsa(ospf, new); return new; } /* Install ASBR-summary-LSA to an area. */ static struct ospf_lsa *ospf_summary_asbr_lsa_install(struct ospf *ospf, struct ospf_lsa *new, int rt_recalc) { if (rt_recalc && !IS_LSA_SELF(new)) { /* RFC 2328 Section 13.2 Summary-LSAs The best route to the destination described by the summary- LSA must be recalculated (see Section 16.5). If this destination is an AS boundary router, it may also be necessary to re-examine all the AS-external-LSAs. */ #if 0 /* These don't exist yet... */ ospf_summary_incremental_update(new); /* Isn't this done by the above call? - RFC 2328 Section 16.5 implies it should be */ /* ospf_ase_calculate_schedule(); */ #else /* #if 0 */ ospf_spf_calculate_schedule(ospf, SPF_FLAG_ASBR_SUMMARY_LSA_INSTALL); #endif /* #if 0 */ } /* register LSA to refresh-list. */ if (IS_LSA_SELF(new)) ospf_refresher_register_lsa(ospf, new); return new; } /* Install AS-external-LSA. */ static struct ospf_lsa *ospf_external_lsa_install(struct ospf *ospf, struct ospf_lsa *new, int rt_recalc) { ospf_ase_register_external_lsa(new, ospf); /* If LSA is not self-originated, calculate an external route. */ if (rt_recalc) { /* RFC 2328 Section 13.2 AS-external-LSAs The best route to the destination described by the AS- external-LSA must be recalculated (see Section 16.6). */ if (!IS_LSA_SELF(new)) ospf_ase_incremental_update(ospf, new); } if (new->data->type == OSPF_AS_NSSA_LSA) { /* There is no point to register selforiginate Type-7 LSA for * refreshing. We rely on refreshing Type-5 LSA's */ if (IS_LSA_SELF(new)) return new; else { /* Try refresh type-5 translated LSA for this LSA, if * one exists. * New translations will be taken care of by the * abr_task. */ ospf_translated_nssa_refresh(ospf, new, NULL); ospf_schedule_abr_task(ospf); } } /* Register self-originated LSA to refresh queue. * Leave Translated LSAs alone if NSSA is enabled */ if (IS_LSA_SELF(new) && !CHECK_FLAG(new->flags, OSPF_LSA_LOCAL_XLT)) ospf_refresher_register_lsa(ospf, new); return new; } void ospf_discard_from_db(struct ospf *ospf, struct ospf_lsdb *lsdb, struct ospf_lsa *lsa) { struct ospf_lsa *old; if (!lsdb) return; old = ospf_lsdb_lookup(lsdb, lsa); if (!old) return; if (old->refresh_list >= 0) ospf_refresher_unregister_lsa(ospf, old); switch (old->data->type) { case OSPF_AS_EXTERNAL_LSA: ospf_ase_unregister_external_lsa(old, ospf); ospf_ls_retransmit_delete_nbr_as(ospf, old); break; case OSPF_OPAQUE_AS_LSA: ospf_ls_retransmit_delete_nbr_as(ospf, old); break; case OSPF_AS_NSSA_LSA: ospf_ls_retransmit_delete_nbr_area(old->area, old); ospf_ase_unregister_external_lsa(old, ospf); break; default: ospf_ls_retransmit_delete_nbr_area(old->area, old); break; } ospf_lsa_maxage_delete(ospf, old); ospf_lsa_discard(old); } struct ospf_lsa *ospf_lsa_install(struct ospf *ospf, struct ospf_interface *oi, struct ospf_lsa *lsa) { struct ospf_lsa *new = NULL; struct ospf_lsa *old = NULL; struct ospf_lsdb *lsdb = NULL; int rt_recalc; /* Set LSDB. */ switch (lsa->data->type) { /* kevinm */ case OSPF_AS_NSSA_LSA: if (lsa->area) lsdb = lsa->area->lsdb; else lsdb = ospf->lsdb; break; case OSPF_AS_EXTERNAL_LSA: case OSPF_OPAQUE_AS_LSA: lsdb = ospf->lsdb; break; default: if (lsa->area) lsdb = lsa->area->lsdb; break; } assert(lsdb); /* RFC 2328 13.2. Installing LSAs in the database Installing a new LSA in the database, either as the result of flooding or a newly self-originated LSA, may cause the OSPF routing table structure to be recalculated. The contents of the new LSA should be compared to the old instance, if present. If there is no difference, there is no need to recalculate the routing table. When comparing an LSA to its previous instance, the following are all considered to be differences in contents: o The LSA's Options field has changed. o One of the LSA instances has LS age set to MaxAge, and the other does not. o The length field in the LSA header has changed. o The body of the LSA (i.e., anything outside the 20-byte LSA header) has changed. Note that this excludes changes in LS Sequence Number and LS Checksum. */ /* Look up old LSA and determine if any SPF calculation or incremental update is needed */ old = ospf_lsdb_lookup(lsdb, lsa); /* Do comparision and record if recalc needed. */ rt_recalc = 0; if (old == NULL || ospf_lsa_different(old, lsa)) rt_recalc = 1; /* Sequence number check (Section 14.1 of rfc 2328) "Premature aging is used when it is time for a self-originated LSA's sequence number field to wrap. At this point, the current LSA instance (having LS sequence number MaxSequenceNumber) must be prematurely aged and flushed from the routing domain before a new instance with sequence number equal to InitialSequenceNumber can be originated. " */ if (ntohl(lsa->data->ls_seqnum) - 1 == OSPF_MAX_SEQUENCE_NUMBER) { if (ospf_lsa_is_self_originated(ospf, lsa)) { lsa->data->ls_seqnum = htonl(OSPF_MAX_SEQUENCE_NUMBER); if (!IS_LSA_MAXAGE(lsa)) lsa->flags |= OSPF_LSA_PREMATURE_AGE; lsa->data->ls_age = htons(OSPF_LSA_MAXAGE); if (IS_DEBUG_OSPF(lsa, LSA_REFRESH)) { zlog_debug( "ospf_lsa_install() Premature Aging " "lsa 0x%p, seqnum 0x%x", (void *)lsa, ntohl(lsa->data->ls_seqnum)); ospf_lsa_header_dump(lsa->data); } } else { if (IS_DEBUG_OSPF(lsa, LSA_GENERATE)) { zlog_debug( "ospf_lsa_install() got an lsa with seq 0x80000000 " "that was not self originated. Ignoring\n"); ospf_lsa_header_dump(lsa->data); } return old; } } /* discard old LSA from LSDB */ if (old != NULL) ospf_discard_from_db(ospf, lsdb, lsa); /* Calculate Checksum if self-originated?. */ if (IS_LSA_SELF(lsa)) ospf_lsa_checksum(lsa->data); /* Insert LSA to LSDB. */ ospf_lsdb_add(lsdb, lsa); lsa->lsdb = lsdb; /* Do LSA specific installation process. */ switch (lsa->data->type) { case OSPF_ROUTER_LSA: new = ospf_router_lsa_install(ospf, lsa, rt_recalc); break; case OSPF_NETWORK_LSA: assert(oi); new = ospf_network_lsa_install(ospf, oi, lsa, rt_recalc); break; case OSPF_SUMMARY_LSA: new = ospf_summary_lsa_install(ospf, lsa, rt_recalc); break; case OSPF_ASBR_SUMMARY_LSA: new = ospf_summary_asbr_lsa_install(ospf, lsa, rt_recalc); break; case OSPF_AS_EXTERNAL_LSA: new = ospf_external_lsa_install(ospf, lsa, rt_recalc); break; case OSPF_OPAQUE_LINK_LSA: if (IS_LSA_SELF(lsa)) lsa->oi = oi; /* Specify outgoing ospf-interface for this LSA. */ else { /* Incoming "oi" for this LSA has set at LSUpd * reception. */ } /* Fallthrough */ case OSPF_OPAQUE_AREA_LSA: case OSPF_OPAQUE_AS_LSA: new = ospf_opaque_lsa_install(lsa, rt_recalc); break; case OSPF_AS_NSSA_LSA: new = ospf_external_lsa_install(ospf, lsa, rt_recalc); default: /* type-6,8,9....nothing special */ break; } if (new == NULL) return new; /* Installation failed, cannot proceed further -- endo. */ /* Debug logs. */ if (IS_DEBUG_OSPF(lsa, LSA_INSTALL)) { char area_str[INET_ADDRSTRLEN]; switch (lsa->data->type) { case OSPF_AS_EXTERNAL_LSA: case OSPF_OPAQUE_AS_LSA: case OSPF_AS_NSSA_LSA: zlog_debug("LSA[%s]: Install %s", dump_lsa_key(new), lookup_msg(ospf_lsa_type_msg, new->data->type, NULL)); break; default: strlcpy(area_str, inet_ntoa(new->area->area_id), sizeof(area_str)); zlog_debug("LSA[%s]: Install %s to Area %s", dump_lsa_key(new), lookup_msg(ospf_lsa_type_msg, new->data->type, NULL), area_str); break; } } /* If received LSA' ls_age is MaxAge, or lsa is being prematurely aged (it's getting flushed out of the area), set LSA on MaxAge LSA list. */ if (IS_LSA_MAXAGE(new)) { if (IS_DEBUG_OSPF(lsa, LSA_INSTALL)) zlog_debug("LSA[Type%d:%s]: Install LSA 0x%p, MaxAge", new->data->type, inet_ntoa(new->data->id), (void *)lsa); ospf_lsa_maxage(ospf, lsa); } return new; } int ospf_check_nbr_status(struct ospf *ospf) { struct listnode *node, *nnode; struct ospf_interface *oi; for (ALL_LIST_ELEMENTS(ospf->oiflist, node, nnode, oi)) { struct route_node *rn; struct ospf_neighbor *nbr; if (ospf_if_is_enable(oi)) for (rn = route_top(oi->nbrs); rn; rn = route_next(rn)) if ((nbr = rn->info) != NULL) if (nbr->state == NSM_Exchange || nbr->state == NSM_Loading) { route_unlock_node(rn); return 0; } } return 1; } static int ospf_maxage_lsa_remover(struct thread *thread) { struct ospf *ospf = THREAD_ARG(thread); struct ospf_lsa *lsa; struct route_node *rn; int reschedule = 0; ospf->t_maxage = NULL; if (IS_DEBUG_OSPF(lsa, LSA_FLOODING)) zlog_debug("LSA[MaxAge]: remover Start"); reschedule = !ospf_check_nbr_status(ospf); if (!reschedule) for (rn = route_top(ospf->maxage_lsa); rn; rn = route_next(rn)) { if ((lsa = rn->info) == NULL) { continue; } /* There is at least one neighbor from which we still * await an ack * for that LSA, so we are not allowed to remove it from * our lsdb yet * as per RFC 2328 section 14 para 4 a) */ if (lsa->retransmit_counter > 0) { reschedule = 1; continue; } /* TODO: maybe convert this function to a work-queue */ if (thread_should_yield(thread)) { OSPF_TIMER_ON(ospf->t_maxage, ospf_maxage_lsa_remover, 0); route_unlock_node( rn); /* route_top/route_next */ return 0; } /* Remove LSA from the LSDB */ if (IS_LSA_SELF(lsa)) if (IS_DEBUG_OSPF(lsa, LSA_FLOODING)) zlog_debug( "LSA[Type%d:%s]: LSA 0x%lx is self-originated: ", lsa->data->type, inet_ntoa(lsa->data->id), (unsigned long)lsa); if (IS_DEBUG_OSPF(lsa, LSA_FLOODING)) zlog_debug( "LSA[Type%d:%s]: MaxAge LSA removed from list", lsa->data->type, inet_ntoa(lsa->data->id)); if (CHECK_FLAG(lsa->flags, OSPF_LSA_PREMATURE_AGE)) { if (IS_DEBUG_OSPF(lsa, LSA_FLOODING)) zlog_debug( "originating new lsa for lsa 0x%p\n", (void *)lsa); ospf_lsa_refresh(ospf, lsa); } /* Remove from lsdb. */ if (lsa->lsdb) { ospf_discard_from_db(ospf, lsa->lsdb, lsa); ospf_lsdb_delete(lsa->lsdb, lsa); } else { if (IS_DEBUG_OSPF(lsa, LSA_FLOODING)) zlog_debug( "%s: LSA[Type%d:%s]: No associated LSDB!", __func__, lsa->data->type, inet_ntoa(lsa->data->id)); } } /* A MaxAge LSA must be removed immediately from the router's link state database as soon as both a) it is no longer contained on any neighbor Link state retransmission lists and b) none of the router's neighbors are in states Exchange or Loading. */ if (reschedule) OSPF_TIMER_ON(ospf->t_maxage, ospf_maxage_lsa_remover, ospf->maxage_delay); return 0; } void ospf_lsa_maxage_delete(struct ospf *ospf, struct ospf_lsa *lsa) { struct route_node *rn; struct prefix lsa_prefix; memset(&lsa_prefix, 0, sizeof(struct prefix)); lsa_prefix.family = 0; lsa_prefix.prefixlen = sizeof(lsa_prefix.u.ptr) * CHAR_BIT; lsa_prefix.u.ptr = (uintptr_t)lsa; if ((rn = route_node_lookup(ospf->maxage_lsa, (struct prefix *)&lsa_prefix))) { if (rn->info == lsa) { UNSET_FLAG(lsa->flags, OSPF_LSA_IN_MAXAGE); ospf_lsa_unlock(&lsa); /* maxage_lsa */ rn->info = NULL; route_unlock_node( rn); /* unlock node because lsa is deleted */ } route_unlock_node(rn); /* route_node_lookup */ } else { if (IS_DEBUG_OSPF_EVENT) zlog_debug("%s: lsa %s is not found in maxage db.", __PRETTY_FUNCTION__, dump_lsa_key(lsa)); } } /* Add LSA onto the MaxAge list, and schedule for removal. * This does *not* lead to the LSA being flooded, that must be taken * care of elsewhere, see, e.g., ospf_lsa_flush* (which are callers of this * function). */ void ospf_lsa_maxage(struct ospf *ospf, struct ospf_lsa *lsa) { struct prefix lsa_prefix; struct route_node *rn; /* When we saw a MaxAge LSA flooded to us, we put it on the list and schedule the MaxAge LSA remover. */ if (CHECK_FLAG(lsa->flags, OSPF_LSA_IN_MAXAGE)) { if (IS_DEBUG_OSPF(lsa, LSA_FLOODING)) zlog_debug( "LSA[Type%d:%s]: %p already exists on MaxAge LSA list", lsa->data->type, inet_ntoa(lsa->data->id), (void *)lsa); return; } memset(&lsa_prefix, 0, sizeof(struct prefix)); lsa_prefix.family = 0; lsa_prefix.prefixlen = sizeof(lsa_prefix.u.ptr) * CHAR_BIT; lsa_prefix.u.ptr = (uintptr_t)lsa; rn = route_node_get(ospf->maxage_lsa, (struct prefix *)&lsa_prefix); if (rn->info != NULL) { if (IS_DEBUG_OSPF(lsa, LSA_FLOODING)) zlog_debug( "LSA[%s]: found LSA (%p) in table for LSA %p %d", dump_lsa_key(lsa), rn->info, (void *)lsa, lsa_prefix.prefixlen); route_unlock_node(rn); } else { rn->info = ospf_lsa_lock(lsa); SET_FLAG(lsa->flags, OSPF_LSA_IN_MAXAGE); } if (IS_DEBUG_OSPF(lsa, LSA_FLOODING)) zlog_debug("LSA[%s]: MaxAge LSA remover scheduled.", dump_lsa_key(lsa)); OSPF_TIMER_ON(ospf->t_maxage, ospf_maxage_lsa_remover, ospf->maxage_delay); } static int ospf_lsa_maxage_walker_remover(struct ospf *ospf, struct ospf_lsa *lsa) { /* Stay away from any Local Translated Type-7 LSAs */ if (CHECK_FLAG(lsa->flags, OSPF_LSA_LOCAL_XLT)) return 0; if (IS_LSA_MAXAGE(lsa)) /* Self-originated LSAs should NOT time-out instead, they're flushed and submitted to the max_age list explicitly. */ if (!ospf_lsa_is_self_originated(ospf, lsa)) { if (IS_DEBUG_OSPF(lsa, LSA_FLOODING)) zlog_debug("LSA[%s]: is MaxAge", dump_lsa_key(lsa)); switch (lsa->data->type) { case OSPF_OPAQUE_LINK_LSA: case OSPF_OPAQUE_AREA_LSA: case OSPF_OPAQUE_AS_LSA: /* * As a general rule, whenever network topology * has changed * (due to an LSA removal in this case), routing * recalculation * should be triggered. However, this is not * true for opaque * LSAs. Even if an opaque LSA instance is going * to be removed * from the routing domain, it does not mean a * change in network * topology, and thus, routing recalculation is * not needed here. */ break; case OSPF_AS_EXTERNAL_LSA: case OSPF_AS_NSSA_LSA: ospf_ase_incremental_update(ospf, lsa); break; default: ospf_spf_calculate_schedule(ospf, SPF_FLAG_MAXAGE); break; } ospf_lsa_maxage(ospf, lsa); } if (IS_LSA_MAXAGE(lsa) && !ospf_lsa_is_self_originated(ospf, lsa)) if (LS_AGE(lsa) > OSPF_LSA_MAXAGE + 30) printf("Eek! Shouldn't happen!\n"); return 0; } /* Periodical check of MaxAge LSA. */ int ospf_lsa_maxage_walker(struct thread *thread) { struct ospf *ospf = THREAD_ARG(thread); struct route_node *rn; struct ospf_lsa *lsa; struct ospf_area *area; struct listnode *node, *nnode; ospf->t_maxage_walker = NULL; for (ALL_LIST_ELEMENTS(ospf->areas, node, nnode, area)) { LSDB_LOOP (ROUTER_LSDB(area), rn, lsa) ospf_lsa_maxage_walker_remover(ospf, lsa); LSDB_LOOP (NETWORK_LSDB(area), rn, lsa) ospf_lsa_maxage_walker_remover(ospf, lsa); LSDB_LOOP (SUMMARY_LSDB(area), rn, lsa) ospf_lsa_maxage_walker_remover(ospf, lsa); LSDB_LOOP (ASBR_SUMMARY_LSDB(area), rn, lsa) ospf_lsa_maxage_walker_remover(ospf, lsa); LSDB_LOOP (OPAQUE_AREA_LSDB(area), rn, lsa) ospf_lsa_maxage_walker_remover(ospf, lsa); LSDB_LOOP (OPAQUE_LINK_LSDB(area), rn, lsa) ospf_lsa_maxage_walker_remover(ospf, lsa); LSDB_LOOP (NSSA_LSDB(area), rn, lsa) ospf_lsa_maxage_walker_remover(ospf, lsa); } /* for AS-external-LSAs. */ if (ospf->lsdb) { LSDB_LOOP (EXTERNAL_LSDB(ospf), rn, lsa) ospf_lsa_maxage_walker_remover(ospf, lsa); LSDB_LOOP (OPAQUE_AS_LSDB(ospf), rn, lsa) ospf_lsa_maxage_walker_remover(ospf, lsa); } OSPF_TIMER_ON(ospf->t_maxage_walker, ospf_lsa_maxage_walker, OSPF_LSA_MAXAGE_CHECK_INTERVAL); return 0; } struct ospf_lsa *ospf_lsa_lookup_by_prefix(struct ospf_lsdb *lsdb, uint8_t type, struct prefix_ipv4 *p, struct in_addr router_id) { struct ospf_lsa *lsa; struct in_addr mask, id; struct lsa_header_mask { struct lsa_header header; struct in_addr mask; } * hmask; lsa = ospf_lsdb_lookup_by_id(lsdb, type, p->prefix, router_id); if (lsa == NULL) return NULL; masklen2ip(p->prefixlen, &mask); hmask = (struct lsa_header_mask *)lsa->data; if (mask.s_addr != hmask->mask.s_addr) { id.s_addr = p->prefix.s_addr | (~mask.s_addr); lsa = ospf_lsdb_lookup_by_id(lsdb, type, id, router_id); if (!lsa) return NULL; } return lsa; } struct ospf_lsa *ospf_lsa_lookup(struct ospf *ospf, struct ospf_area *area, uint32_t type, struct in_addr id, struct in_addr adv_router) { if (!ospf) return NULL; switch (type) { case OSPF_ROUTER_LSA: case OSPF_NETWORK_LSA: case OSPF_SUMMARY_LSA: case OSPF_ASBR_SUMMARY_LSA: case OSPF_AS_NSSA_LSA: case OSPF_OPAQUE_LINK_LSA: case OSPF_OPAQUE_AREA_LSA: return ospf_lsdb_lookup_by_id(area->lsdb, type, id, adv_router); case OSPF_AS_EXTERNAL_LSA: case OSPF_OPAQUE_AS_LSA: return ospf_lsdb_lookup_by_id(ospf->lsdb, type, id, adv_router); default: break; } return NULL; } struct ospf_lsa *ospf_lsa_lookup_by_id(struct ospf_area *area, uint32_t type, struct in_addr id) { struct ospf_lsa *lsa; struct route_node *rn; switch (type) { case OSPF_ROUTER_LSA: return ospf_lsdb_lookup_by_id(area->lsdb, type, id, id); case OSPF_NETWORK_LSA: for (rn = route_top(NETWORK_LSDB(area)); rn; rn = route_next(rn)) if ((lsa = rn->info)) if (IPV4_ADDR_SAME(&lsa->data->id, &id)) { route_unlock_node(rn); return lsa; } break; case OSPF_SUMMARY_LSA: case OSPF_ASBR_SUMMARY_LSA: /* Currently not used. */ assert(1); return ospf_lsdb_lookup_by_id(area->lsdb, type, id, id); case OSPF_AS_EXTERNAL_LSA: case OSPF_AS_NSSA_LSA: case OSPF_OPAQUE_LINK_LSA: case OSPF_OPAQUE_AREA_LSA: case OSPF_OPAQUE_AS_LSA: /* Currently not used. */ break; default: break; } return NULL; } struct ospf_lsa *ospf_lsa_lookup_by_header(struct ospf_area *area, struct lsa_header *lsah) { struct ospf_lsa *match; /* * Strictly speaking, the LSA-ID field for Opaque-LSAs (type-9/10/11) * is redefined to have two subfields; opaque-type and opaque-id. * However, it is harmless to treat the two sub fields together, as if * they two were forming a unique LSA-ID. */ match = ospf_lsa_lookup(area->ospf, area, lsah->type, lsah->id, lsah->adv_router); if (match == NULL) if (IS_DEBUG_OSPF(lsa, LSA) == OSPF_DEBUG_LSA) zlog_debug("LSA[Type%d:%s]: Lookup by header, NO MATCH", lsah->type, inet_ntoa(lsah->id)); return match; } /* return +n, l1 is more recent. return -n, l2 is more recent. return 0, l1 and l2 is identical. */ int ospf_lsa_more_recent(struct ospf_lsa *l1, struct ospf_lsa *l2) { int r; int x, y; if (l1 == NULL && l2 == NULL) return 0; if (l1 == NULL) return -1; if (l2 == NULL) return 1; /* compare LS sequence number. */ x = (int)ntohl(l1->data->ls_seqnum); y = (int)ntohl(l2->data->ls_seqnum); if (x > y) return 1; if (x < y) return -1; /* compare LS checksum. */ r = ntohs(l1->data->checksum) - ntohs(l2->data->checksum); if (r) return r; /* compare LS age. */ if (IS_LSA_MAXAGE(l1) && !IS_LSA_MAXAGE(l2)) return 1; else if (!IS_LSA_MAXAGE(l1) && IS_LSA_MAXAGE(l2)) return -1; /* compare LS age with MaxAgeDiff. */ if (LS_AGE(l1) - LS_AGE(l2) > OSPF_LSA_MAXAGE_DIFF) return -1; else if (LS_AGE(l2) - LS_AGE(l1) > OSPF_LSA_MAXAGE_DIFF) return 1; /* LSAs are identical. */ return 0; } /* If two LSAs are different, return 1, otherwise return 0. */ int ospf_lsa_different(struct ospf_lsa *l1, struct ospf_lsa *l2) { char *p1, *p2; assert(l1); assert(l2); assert(l1->data); assert(l2->data); if (l1->data->options != l2->data->options) return 1; if (IS_LSA_MAXAGE(l1) && !IS_LSA_MAXAGE(l2)) return 1; if (IS_LSA_MAXAGE(l2) && !IS_LSA_MAXAGE(l1)) return 1; if (l1->data->length != l2->data->length) return 1; if (l1->data->length == 0) return 1; if (CHECK_FLAG((l1->flags ^ l2->flags), OSPF_LSA_RECEIVED)) return 1; /* May be a stale LSA in the LSBD */ assert(ntohs(l1->data->length) > OSPF_LSA_HEADER_SIZE); p1 = (char *)l1->data; p2 = (char *)l2->data; if (memcmp(p1 + OSPF_LSA_HEADER_SIZE, p2 + OSPF_LSA_HEADER_SIZE, ntohs(l1->data->length) - OSPF_LSA_HEADER_SIZE) != 0) return 1; return 0; } #ifdef ORIGINAL_CODING void ospf_lsa_flush_self_originated(struct ospf_neighbor *nbr, struct ospf_lsa *self, struct ospf_lsa *new) { uint32_t seqnum; /* Adjust LS Sequence Number. */ seqnum = ntohl(new->data->ls_seqnum) + 1; self->data->ls_seqnum = htonl(seqnum); /* Recalculate LSA checksum. */ ospf_lsa_checksum(self->data); /* Reflooding LSA. */ /* RFC2328 Section 13.3 On non-broadcast networks, separate Link State Update packets must be sent, as unicasts, to each adjacent neighbor (i.e., those in state Exchange or greater). The destination IP addresses for these packets are the neighbors' IP addresses. */ if (nbr->oi->type == OSPF_IFTYPE_NBMA) { struct route_node *rn; struct ospf_neighbor *onbr; for (rn = route_top(nbr->oi->nbrs); rn; rn = route_next(rn)) if ((onbr = rn->info) != NULL) if (onbr != nbr->oi->nbr_self && onbr->status >= NSM_Exchange) ospf_ls_upd_send_lsa( onbr, self, OSPF_SEND_PACKET_DIRECT); } else ospf_ls_upd_send_lsa(nbr, self, OSPF_SEND_PACKET_INDIRECT); if (IS_DEBUG_OSPF(lsa, LSA_GENERATE)) zlog_debug("LSA[Type%d:%s]: Flush self-originated LSA", self->data->type, inet_ntoa(self->data->id)); } #else /* ORIGINAL_CODING */ int ospf_lsa_flush_schedule(struct ospf *ospf, struct ospf_lsa *lsa) { if (lsa == NULL || !IS_LSA_SELF(lsa)) return 0; if (IS_DEBUG_OSPF_EVENT) zlog_debug( "LSA[Type%d:%s]: Schedule self-originated LSA to FLUSH", lsa->data->type, inet_ntoa(lsa->data->id)); /* Force given lsa's age to MaxAge. */ lsa->data->ls_age = htons(OSPF_LSA_MAXAGE); switch (lsa->data->type) { /* Opaque wants to be notified of flushes */ case OSPF_OPAQUE_LINK_LSA: case OSPF_OPAQUE_AREA_LSA: case OSPF_OPAQUE_AS_LSA: ospf_opaque_lsa_refresh(lsa); break; default: ospf_refresher_unregister_lsa(ospf, lsa); ospf_lsa_flush(ospf, lsa); break; } return 0; } void ospf_flush_self_originated_lsas_now(struct ospf *ospf) { struct listnode *node, *nnode; struct listnode *node2, *nnode2; struct ospf_area *area; struct ospf_interface *oi; struct ospf_lsa *lsa; struct route_node *rn; int need_to_flush_ase = 0; ospf->inst_shutdown = 1; for (ALL_LIST_ELEMENTS(ospf->areas, node, nnode, area)) { if ((lsa = area->router_lsa_self) != NULL) { if (IS_DEBUG_OSPF_EVENT) zlog_debug( "LSA[Type%d:%s]: Schedule self-originated LSA to FLUSH", lsa->data->type, inet_ntoa(lsa->data->id)); ospf_refresher_unregister_lsa(ospf, lsa); ospf_lsa_flush_area(lsa, area); ospf_lsa_unlock(&area->router_lsa_self); area->router_lsa_self = NULL; } for (ALL_LIST_ELEMENTS(area->oiflist, node2, nnode2, oi)) { if ((lsa = oi->network_lsa_self) != NULL && oi->state == ISM_DR && oi->full_nbrs > 0) { if (IS_DEBUG_OSPF_EVENT) zlog_debug( "LSA[Type%d:%s]: Schedule self-originated LSA to FLUSH", lsa->data->type, inet_ntoa(lsa->data->id)); ospf_refresher_unregister_lsa( ospf, oi->network_lsa_self); ospf_lsa_flush_area(oi->network_lsa_self, area); ospf_lsa_unlock(&oi->network_lsa_self); oi->network_lsa_self = NULL; } if (oi->type != OSPF_IFTYPE_VIRTUALLINK && area->external_routing == OSPF_AREA_DEFAULT) need_to_flush_ase = 1; } LSDB_LOOP (SUMMARY_LSDB(area), rn, lsa) ospf_lsa_flush_schedule(ospf, lsa); LSDB_LOOP (ASBR_SUMMARY_LSDB(area), rn, lsa) ospf_lsa_flush_schedule(ospf, lsa); LSDB_LOOP (OPAQUE_LINK_LSDB(area), rn, lsa) ospf_lsa_flush_schedule(ospf, lsa); LSDB_LOOP (OPAQUE_AREA_LSDB(area), rn, lsa) ospf_lsa_flush_schedule(ospf, lsa); } if (need_to_flush_ase) { LSDB_LOOP (EXTERNAL_LSDB(ospf), rn, lsa) ospf_lsa_flush_schedule(ospf, lsa); LSDB_LOOP (OPAQUE_AS_LSDB(ospf), rn, lsa) ospf_lsa_flush_schedule(ospf, lsa); } /* * Make sure that the MaxAge LSA remover is executed immediately, * without conflicting to other threads. */ if (ospf->t_maxage != NULL) { OSPF_TIMER_OFF(ospf->t_maxage); thread_execute(master, ospf_maxage_lsa_remover, ospf, 0); } return; } #endif /* ORIGINAL_CODING */ /* If there is self-originated LSA, then return 1, otherwise return 0. */ /* An interface-independent version of ospf_lsa_is_self_originated */ int ospf_lsa_is_self_originated(struct ospf *ospf, struct ospf_lsa *lsa) { struct listnode *node; struct ospf_interface *oi; /* This LSA is already checked. */ if (CHECK_FLAG(lsa->flags, OSPF_LSA_SELF_CHECKED)) return IS_LSA_SELF(lsa); /* Make sure LSA is self-checked. */ SET_FLAG(lsa->flags, OSPF_LSA_SELF_CHECKED); /* AdvRouter and Router ID is the same. */ if (IPV4_ADDR_SAME(&lsa->data->adv_router, &ospf->router_id)) SET_FLAG(lsa->flags, OSPF_LSA_SELF); /* LSA is router-LSA. */ else if (lsa->data->type == OSPF_ROUTER_LSA && IPV4_ADDR_SAME(&lsa->data->id, &ospf->router_id)) SET_FLAG(lsa->flags, OSPF_LSA_SELF); /* LSA is network-LSA. Compare Link ID with all interfaces. */ else if (lsa->data->type == OSPF_NETWORK_LSA) for (ALL_LIST_ELEMENTS_RO(ospf->oiflist, node, oi)) { /* Ignore virtual link. */ if (oi->type != OSPF_IFTYPE_VIRTUALLINK) if (oi->address->family == AF_INET) if (IPV4_ADDR_SAME( &lsa->data->id, &oi->address->u.prefix4)) { /* to make it easier later */ SET_FLAG(lsa->flags, OSPF_LSA_SELF); return IS_LSA_SELF(lsa); } } return IS_LSA_SELF(lsa); } /* Get unique Link State ID. */ struct in_addr ospf_lsa_unique_id(struct ospf *ospf, struct ospf_lsdb *lsdb, uint8_t type, struct prefix_ipv4 *p) { struct ospf_lsa *lsa; struct in_addr mask, id; id = p->prefix; /* Check existence of LSA instance. */ lsa = ospf_lsdb_lookup_by_id(lsdb, type, id, ospf->router_id); if (lsa) { struct as_external_lsa *al = (struct as_external_lsa *)lsa->data; if (ip_masklen(al->mask) == p->prefixlen) { if (IS_DEBUG_OSPF(lsa, LSA_GENERATE)) zlog_debug( "ospf_lsa_unique_id(): " "Can't get Link State ID for %s/%d", inet_ntoa(p->prefix), p->prefixlen); /* id.s_addr = 0; */ id.s_addr = 0xffffffff; return id; } /* Masklen differs, then apply wildcard mask to Link State ID. */ else { masklen2ip(p->prefixlen, &mask); id.s_addr = p->prefix.s_addr | (~mask.s_addr); lsa = ospf_lsdb_lookup_by_id(ospf->lsdb, type, id, ospf->router_id); if (lsa) { if (IS_DEBUG_OSPF(lsa, LSA_GENERATE)) zlog_debug( "ospf_lsa_unique_id(): " "Can't get Link State ID for %s/%d", inet_ntoa(p->prefix), p->prefixlen); /* id.s_addr = 0; */ id.s_addr = 0xffffffff; return id; } } } return id; } #define LSA_ACTION_FLOOD_AREA 1 #define LSA_ACTION_FLUSH_AREA 2 struct lsa_action { uint8_t action; struct ospf_area *area; struct ospf_lsa *lsa; }; static int ospf_lsa_action(struct thread *t) { struct lsa_action *data; data = THREAD_ARG(t); if (IS_DEBUG_OSPF(lsa, LSA) == OSPF_DEBUG_LSA) zlog_debug("LSA[Action]: Performing scheduled LSA action: %d", data->action); switch (data->action) { case LSA_ACTION_FLOOD_AREA: ospf_flood_through_area(data->area, NULL, data->lsa); break; case LSA_ACTION_FLUSH_AREA: ospf_lsa_flush_area(data->lsa, data->area); break; } ospf_lsa_unlock(&data->lsa); /* Message */ XFREE(MTYPE_OSPF_MESSAGE, data); return 0; } void ospf_schedule_lsa_flood_area(struct ospf_area *area, struct ospf_lsa *lsa) { struct lsa_action *data; data = XCALLOC(MTYPE_OSPF_MESSAGE, sizeof(struct lsa_action)); data->action = LSA_ACTION_FLOOD_AREA; data->area = area; data->lsa = ospf_lsa_lock(lsa); /* Message / Flood area */ thread_add_event(master, ospf_lsa_action, data, 0, NULL); } void ospf_schedule_lsa_flush_area(struct ospf_area *area, struct ospf_lsa *lsa) { struct lsa_action *data; data = XCALLOC(MTYPE_OSPF_MESSAGE, sizeof(struct lsa_action)); data->action = LSA_ACTION_FLUSH_AREA; data->area = area; data->lsa = ospf_lsa_lock(lsa); /* Message / Flush area */ thread_add_event(master, ospf_lsa_action, data, 0, NULL); } /* LSA Refreshment functions. */ struct ospf_lsa *ospf_lsa_refresh(struct ospf *ospf, struct ospf_lsa *lsa) { struct external_info *ei; struct ospf_lsa *new = NULL; assert(CHECK_FLAG(lsa->flags, OSPF_LSA_SELF)); assert(IS_LSA_SELF(lsa)); assert(lsa->lock > 0); switch (lsa->data->type) { /* Router and Network LSAs are processed differently. */ case OSPF_ROUTER_LSA: new = ospf_router_lsa_refresh(lsa); break; case OSPF_NETWORK_LSA: new = ospf_network_lsa_refresh(lsa); break; case OSPF_SUMMARY_LSA: new = ospf_summary_lsa_refresh(ospf, lsa); break; case OSPF_ASBR_SUMMARY_LSA: new = ospf_summary_asbr_lsa_refresh(ospf, lsa); break; case OSPF_AS_EXTERNAL_LSA: /* Translated from NSSA Type-5s are refreshed when * from refresh of Type-7 - do not refresh these directly. */ if (CHECK_FLAG(lsa->flags, OSPF_LSA_LOCAL_XLT)) break; ei = ospf_external_info_check(ospf, lsa); if (ei) new = ospf_external_lsa_refresh(ospf, lsa, ei, LSA_REFRESH_FORCE); else ospf_lsa_flush_as(ospf, lsa); break; case OSPF_OPAQUE_LINK_LSA: case OSPF_OPAQUE_AREA_LSA: case OSPF_OPAQUE_AS_LSA: new = ospf_opaque_lsa_refresh(lsa); break; default: break; } return new; } void ospf_refresher_register_lsa(struct ospf *ospf, struct ospf_lsa *lsa) { uint16_t index, current_index; assert(lsa->lock > 0); assert(IS_LSA_SELF(lsa)); if (lsa->refresh_list < 0) { int delay; int min_delay = OSPF_LS_REFRESH_TIME - (2 * OSPF_LS_REFRESH_JITTER); int max_delay = OSPF_LS_REFRESH_TIME - OSPF_LS_REFRESH_JITTER; /* We want to refresh the LSA within OSPF_LS_REFRESH_TIME which * is * 1800s. Use jitter so that we send the LSA sometime between * 1680s * and 1740s. */ delay = (random() % (max_delay - min_delay)) + min_delay; current_index = ospf->lsa_refresh_queue.index + (monotime(NULL) - ospf->lsa_refresher_started) / OSPF_LSA_REFRESHER_GRANULARITY; index = (current_index + delay / OSPF_LSA_REFRESHER_GRANULARITY) % (OSPF_LSA_REFRESHER_SLOTS); if (IS_DEBUG_OSPF(lsa, LSA_REFRESH)) zlog_debug( "LSA[Refresh:Type%d:%s]: age %d, added to index %d", lsa->data->type, inet_ntoa(lsa->data->id), LS_AGE(lsa), index); if (!ospf->lsa_refresh_queue.qs[index]) ospf->lsa_refresh_queue.qs[index] = list_new(); listnode_add(ospf->lsa_refresh_queue.qs[index], ospf_lsa_lock(lsa)); /* lsa_refresh_queue */ lsa->refresh_list = index; if (IS_DEBUG_OSPF(lsa, LSA_REFRESH)) zlog_debug( "LSA[Refresh:Type%d:%s]: ospf_refresher_register_lsa(): " "setting refresh_list on lsa %p (slod %d)", lsa->data->type, inet_ntoa(lsa->data->id), (void *)lsa, index); } } void ospf_refresher_unregister_lsa(struct ospf *ospf, struct ospf_lsa *lsa) { assert(lsa->lock > 0); assert(IS_LSA_SELF(lsa)); if (lsa->refresh_list >= 0) { struct list *refresh_list = ospf->lsa_refresh_queue.qs[lsa->refresh_list]; listnode_delete(refresh_list, lsa); if (!listcount(refresh_list)) { list_delete(&refresh_list); ospf->lsa_refresh_queue.qs[lsa->refresh_list] = NULL; } lsa->refresh_list = -1; ospf_lsa_unlock(&lsa); /* lsa_refresh_queue */ } } int ospf_lsa_refresh_walker(struct thread *t) { struct list *refresh_list; struct listnode *node, *nnode; struct ospf *ospf = THREAD_ARG(t); struct ospf_lsa *lsa; int i; struct list *lsa_to_refresh = list_new(); if (IS_DEBUG_OSPF(lsa, LSA_REFRESH)) zlog_debug("LSA[Refresh]: ospf_lsa_refresh_walker(): start"); i = ospf->lsa_refresh_queue.index; /* Note: if clock has jumped backwards, then time change could be negative, so we are careful to cast the expression to unsigned before taking modulus. */ ospf->lsa_refresh_queue.index = ((unsigned long)(ospf->lsa_refresh_queue.index + (monotime(NULL) - ospf->lsa_refresher_started) / OSPF_LSA_REFRESHER_GRANULARITY)) % OSPF_LSA_REFRESHER_SLOTS; if (IS_DEBUG_OSPF(lsa, LSA_REFRESH)) zlog_debug( "LSA[Refresh]: ospf_lsa_refresh_walker(): next index %d", ospf->lsa_refresh_queue.index); for (; i != ospf->lsa_refresh_queue.index; i = (i + 1) % OSPF_LSA_REFRESHER_SLOTS) { if (IS_DEBUG_OSPF(lsa, LSA_REFRESH)) zlog_debug( "LSA[Refresh]: ospf_lsa_refresh_walker(): " "refresh index %d", i); refresh_list = ospf->lsa_refresh_queue.qs[i]; assert(i >= 0); ospf->lsa_refresh_queue.qs[i] = NULL; if (refresh_list) { for (ALL_LIST_ELEMENTS(refresh_list, node, nnode, lsa)) { if (IS_DEBUG_OSPF(lsa, LSA_REFRESH)) zlog_debug( "LSA[Refresh:Type%d:%s]: ospf_lsa_refresh_walker(): " "refresh lsa %p (slot %d)", lsa->data->type, inet_ntoa(lsa->data->id), (void *)lsa, i); assert(lsa->lock > 0); list_delete_node(refresh_list, node); lsa->refresh_list = -1; listnode_add(lsa_to_refresh, lsa); } list_delete(&refresh_list); } } ospf->t_lsa_refresher = NULL; thread_add_timer(master, ospf_lsa_refresh_walker, ospf, ospf->lsa_refresh_interval, &ospf->t_lsa_refresher); ospf->lsa_refresher_started = monotime(NULL); for (ALL_LIST_ELEMENTS(lsa_to_refresh, node, nnode, lsa)) { ospf_lsa_refresh(ospf, lsa); assert(lsa->lock > 0); ospf_lsa_unlock( &lsa); /* lsa_refresh_queue & temp for lsa_to_refresh*/ } list_delete(&lsa_to_refresh); if (IS_DEBUG_OSPF(lsa, LSA_REFRESH)) zlog_debug("LSA[Refresh]: ospf_lsa_refresh_walker(): end"); return 0; } frr-7.2.1/ospfd/ospf_lsa.h0000644000000000000000000002643613610377563012321 00000000000000/* * OSPF Link State Advertisement * Copyright (C) 1999, 2000 Toshiaki Takada * * This file is part of GNU Zebra. * * GNU Zebra is free software; you can redistribute it and/or modify it * under the terms of the GNU General Public License as published by the * Free Software Foundation; either version 2, or (at your option) any * later version. * * GNU Zebra 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; see the file COPYING; if not, write to the Free Software * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA */ #ifndef _ZEBRA_OSPF_LSA_H #define _ZEBRA_OSPF_LSA_H #include "stream.h" /* OSPF LSA Default metric values */ #define DEFAULT_DEFAULT_METRIC 20 #define DEFAULT_DEFAULT_ORIGINATE_METRIC 10 #define DEFAULT_DEFAULT_ALWAYS_METRIC 1 #define DEFAULT_METRIC_TYPE EXTERNAL_METRIC_TYPE_2 /* OSPF LSA Range definition. */ #define OSPF_MIN_LSA 1 /* begin range here */ #define OSPF_MAX_LSA 12 /* OSPF LSA Type definition. */ #define OSPF_UNKNOWN_LSA 0 #define OSPF_ROUTER_LSA 1 #define OSPF_NETWORK_LSA 2 #define OSPF_SUMMARY_LSA 3 #define OSPF_ASBR_SUMMARY_LSA 4 #define OSPF_AS_EXTERNAL_LSA 5 #define OSPF_GROUP_MEMBER_LSA 6 /* Not supported. */ #define OSPF_AS_NSSA_LSA 7 #define OSPF_EXTERNAL_ATTRIBUTES_LSA 8 /* Not supported. */ #define OSPF_OPAQUE_LINK_LSA 9 #define OSPF_OPAQUE_AREA_LSA 10 #define OSPF_OPAQUE_AS_LSA 11 #define OSPF_LSA_HEADER_SIZE 20U #define OSPF_ROUTER_LSA_LINK_SIZE 12U #define OSPF_ROUTER_LSA_TOS_SIZE 4U #define OSPF_MAX_LSA_SIZE 1500U /* AS-external-LSA refresh method. */ #define LSA_REFRESH_IF_CHANGED 0 #define LSA_REFRESH_FORCE 1 /* OSPF LSA header. */ struct lsa_header { uint16_t ls_age; uint8_t options; uint8_t type; struct in_addr id; struct in_addr adv_router; uint32_t ls_seqnum; uint16_t checksum; uint16_t length; }; struct vertex; /* OSPF LSA. */ struct ospf_lsa { /* LSA origination flag. */ uint8_t flags; #define OSPF_LSA_SELF 0x01 #define OSPF_LSA_SELF_CHECKED 0x02 #define OSPF_LSA_RECEIVED 0x04 #define OSPF_LSA_APPROVED 0x08 #define OSPF_LSA_DISCARD 0x10 #define OSPF_LSA_LOCAL_XLT 0x20 #define OSPF_LSA_PREMATURE_AGE 0x40 #define OSPF_LSA_IN_MAXAGE 0x80 /* LSA data. */ struct lsa_header *data; /* Received time stamp. */ struct timeval tv_recv; /* Last time it was originated */ struct timeval tv_orig; /* All of reference count, also lock to remove. */ int lock; /* Flags for the SPF calculation. */ struct vertex *stat; /* References to this LSA in neighbor retransmission lists*/ int retransmit_counter; /* Area the LSA belongs to, may be NULL if AS-external-LSA. */ struct ospf_area *area; /* Parent LSDB. */ struct ospf_lsdb *lsdb; /* Related Route. */ void *route; /* Refreshement List or Queue */ int refresh_list; /* For Type-9 Opaque-LSAs */ struct ospf_interface *oi; /* VRF Id */ vrf_id_t vrf_id; }; /* OSPF LSA Link Type. */ #define LSA_LINK_TYPE_POINTOPOINT 1 #define LSA_LINK_TYPE_TRANSIT 2 #define LSA_LINK_TYPE_STUB 3 #define LSA_LINK_TYPE_VIRTUALLINK 4 /* OSPF Router LSA Flag. */ #define ROUTER_LSA_BORDER 0x01 /* The router is an ABR */ #define ROUTER_LSA_EXTERNAL 0x02 /* The router is an ASBR */ #define ROUTER_LSA_VIRTUAL 0x04 /* The router has a VL in this area */ #define ROUTER_LSA_NT 0x10 /* The routers always translates Type-7 */ #define ROUTER_LSA_SHORTCUT 0x20 /* Shortcut-ABR specific flag */ #define IS_ROUTER_LSA_VIRTUAL(x) ((x)->flags & ROUTER_LSA_VIRTUAL) #define IS_ROUTER_LSA_EXTERNAL(x) ((x)->flags & ROUTER_LSA_EXTERNAL) #define IS_ROUTER_LSA_BORDER(x) ((x)->flags & ROUTER_LSA_BORDER) #define IS_ROUTER_LSA_SHORTCUT(x) ((x)->flags & ROUTER_LSA_SHORTCUT) #define IS_ROUTER_LSA_NT(x) ((x)->flags & ROUTER_LSA_NT) /* OSPF Router-LSA Link information. */ struct router_lsa_link { struct in_addr link_id; struct in_addr link_data; struct { uint8_t type; uint8_t tos_count; uint16_t metric; } m[1]; }; /* OSPF Router-LSAs structure. */ #define OSPF_ROUTER_LSA_MIN_SIZE 4U /* w/0 link descriptors */ /* There is an edge case, when number of links in a Router-LSA may be 0 without breaking the specification. A router, which has no other links to backbone area besides one virtual link, will not put any VL descriptor blocks into the Router-LSA generated for area 0 until a full adjacency over the VL is reached (RFC2328 12.4.1.3). In this case the Router-LSA initially received by the other end of the VL will have 0 link descriptor blocks, but soon will be replaced with the next revision having 1 descriptor block. */ struct router_lsa { struct lsa_header header; uint8_t flags; uint8_t zero; uint16_t links; struct { struct in_addr link_id; struct in_addr link_data; uint8_t type; uint8_t tos; uint16_t metric; } link[1]; }; /* OSPF Network-LSAs structure. */ #define OSPF_NETWORK_LSA_MIN_SIZE 8U /* w/1 router-ID */ struct network_lsa { struct lsa_header header; struct in_addr mask; struct in_addr routers[1]; }; /* OSPF Summary-LSAs structure. */ #define OSPF_SUMMARY_LSA_MIN_SIZE 8U /* w/1 TOS metric block */ struct summary_lsa { struct lsa_header header; struct in_addr mask; uint8_t tos; uint8_t metric[3]; }; /* OSPF AS-external-LSAs structure. */ #define OSPF_AS_EXTERNAL_LSA_MIN_SIZE 16U /* w/1 TOS forwarding block */ struct as_external_lsa { struct lsa_header header; struct in_addr mask; struct { uint8_t tos; uint8_t metric[3]; struct in_addr fwd_addr; uint32_t route_tag; } e[1]; }; #include "ospfd/ospf_opaque.h" /* Macros. */ #define GET_METRIC(x) get_metric(x) #define IS_EXTERNAL_METRIC(x) ((x) & 0x80) #define GET_AGE(x) (ntohs ((x)->data->ls_age) + time (NULL) - (x)->tv_recv) #define LS_AGE(x) (OSPF_LSA_MAXAGE < get_age(x) ? OSPF_LSA_MAXAGE : get_age(x)) #define IS_LSA_SELF(L) (CHECK_FLAG ((L)->flags, OSPF_LSA_SELF)) #define IS_LSA_MAXAGE(L) (LS_AGE ((L)) == OSPF_LSA_MAXAGE) #define OSPF_LSA_UPDATE_DELAY 2 #define OSPF_LSA_UPDATE_TIMER_ON(T, F) \ if (!(T)) \ (T) = thread_add_timer(master, (F), 0, 2) /* Prototypes. */ /* XXX: Eek, time functions, similar are in lib/thread.c */ extern struct timeval int2tv(int); extern struct timeval msec2tv(int); extern int get_age(struct ospf_lsa *); extern uint16_t ospf_lsa_checksum(struct lsa_header *); extern int ospf_lsa_checksum_valid(struct lsa_header *); extern int ospf_lsa_refresh_delay(struct ospf_lsa *); extern const char *dump_lsa_key(struct ospf_lsa *); extern uint32_t lsa_seqnum_increment(struct ospf_lsa *); extern void lsa_header_set(struct stream *, uint8_t, uint8_t, struct in_addr, struct in_addr); extern struct ospf_neighbor *ospf_nbr_lookup_ptop(struct ospf_interface *); extern int ospf_check_nbr_status(struct ospf *); /* Prototype for LSA primitive. */ extern struct ospf_lsa *ospf_lsa_new(void); extern struct ospf_lsa *ospf_lsa_new_and_data(size_t size); extern struct ospf_lsa *ospf_lsa_dup(struct ospf_lsa *); extern void ospf_lsa_free(struct ospf_lsa *); extern struct ospf_lsa *ospf_lsa_lock(struct ospf_lsa *); extern void ospf_lsa_unlock(struct ospf_lsa **); extern void ospf_lsa_discard(struct ospf_lsa *); extern int ospf_lsa_flush_schedule(struct ospf *, struct ospf_lsa *); extern struct lsa_header *ospf_lsa_data_new(size_t); extern struct lsa_header *ospf_lsa_data_dup(struct lsa_header *); extern void ospf_lsa_data_free(struct lsa_header *); /* Prototype for various LSAs */ extern int ospf_router_lsa_update(struct ospf *); extern int ospf_router_lsa_update_area(struct ospf_area *); extern void ospf_network_lsa_update(struct ospf_interface *); extern struct ospf_lsa * ospf_summary_lsa_originate(struct prefix_ipv4 *, uint32_t, struct ospf_area *); extern struct ospf_lsa *ospf_summary_asbr_lsa_originate(struct prefix_ipv4 *, uint32_t, struct ospf_area *); extern struct ospf_lsa *ospf_lsa_install(struct ospf *, struct ospf_interface *, struct ospf_lsa *); extern void ospf_nssa_lsa_flush(struct ospf *ospf, struct prefix_ipv4 *p); extern void ospf_external_lsa_flush(struct ospf *, uint8_t, struct prefix_ipv4 *, ifindex_t /* , struct in_addr nexthop */); extern struct in_addr ospf_get_ip_from_ifp(struct ospf_interface *); extern struct ospf_lsa *ospf_external_lsa_originate(struct ospf *, struct external_info *); extern void ospf_external_lsa_rid_change(struct ospf *ospf); extern struct ospf_lsa *ospf_lsa_lookup(struct ospf *ospf, struct ospf_area *, uint32_t, struct in_addr, struct in_addr); extern struct ospf_lsa *ospf_lsa_lookup_by_id(struct ospf_area *, uint32_t, struct in_addr); extern struct ospf_lsa *ospf_lsa_lookup_by_header(struct ospf_area *, struct lsa_header *); extern int ospf_lsa_more_recent(struct ospf_lsa *, struct ospf_lsa *); extern int ospf_lsa_different(struct ospf_lsa *, struct ospf_lsa *); extern void ospf_flush_self_originated_lsas_now(struct ospf *); extern int ospf_lsa_is_self_originated(struct ospf *, struct ospf_lsa *); extern struct ospf_lsa *ospf_lsa_lookup_by_prefix(struct ospf_lsdb *, uint8_t, struct prefix_ipv4 *, struct in_addr); extern void ospf_lsa_maxage(struct ospf *, struct ospf_lsa *); extern uint32_t get_metric(uint8_t *); extern int ospf_lsa_maxage_walker(struct thread *); extern struct ospf_lsa *ospf_lsa_refresh(struct ospf *, struct ospf_lsa *); extern void ospf_external_lsa_refresh_default(struct ospf *); extern void ospf_external_lsa_refresh_type(struct ospf *, uint8_t, unsigned short, int); extern struct ospf_lsa *ospf_external_lsa_refresh(struct ospf *, struct ospf_lsa *, struct external_info *, int); extern struct in_addr ospf_lsa_unique_id(struct ospf *, struct ospf_lsdb *, uint8_t, struct prefix_ipv4 *); extern void ospf_schedule_lsa_flood_area(struct ospf_area *, struct ospf_lsa *); extern void ospf_schedule_lsa_flush_area(struct ospf_area *, struct ospf_lsa *); extern void ospf_refresher_register_lsa(struct ospf *, struct ospf_lsa *); extern void ospf_refresher_unregister_lsa(struct ospf *, struct ospf_lsa *); extern int ospf_lsa_refresh_walker(struct thread *); extern void ospf_lsa_maxage_delete(struct ospf *, struct ospf_lsa *); extern void ospf_discard_from_db(struct ospf *, struct ospf_lsdb *, struct ospf_lsa *); extern int is_prefix_default(struct prefix_ipv4 *); extern int metric_type(struct ospf *, uint8_t, unsigned short); extern int metric_value(struct ospf *, uint8_t, unsigned short); extern struct in_addr ospf_get_nssa_ip(struct ospf_area *); extern int ospf_translated_nssa_compare(struct ospf_lsa *, struct ospf_lsa *); extern struct ospf_lsa *ospf_translated_nssa_refresh(struct ospf *, struct ospf_lsa *, struct ospf_lsa *); extern struct ospf_lsa *ospf_translated_nssa_originate(struct ospf *, struct ospf_lsa *); #endif /* _ZEBRA_OSPF_LSA_H */ frr-7.2.1/ospfd/ospf_lsdb.c0000644000000000000000000001421613610377563012452 00000000000000/* * OSPF LSDB support. * Copyright (C) 1999, 2000 Alex Zinin, Kunihiro Ishiguro, Toshiaki Takada * * This file is part of GNU Zebra. * * GNU Zebra is free software; you can redistribute it and/or modify it * under the terms of the GNU General Public License as published by the * Free Software Foundation; either version 2, or (at your option) any * later version. * * GNU Zebra 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; see the file COPYING; if not, write to the Free Software * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA */ #include #include "prefix.h" #include "table.h" #include "memory.h" #include "log.h" #include "ospfd/ospfd.h" #include "ospfd/ospf_asbr.h" #include "ospfd/ospf_lsa.h" #include "ospfd/ospf_lsdb.h" struct ospf_lsdb *ospf_lsdb_new(void) { struct ospf_lsdb *new; new = XCALLOC(MTYPE_OSPF_LSDB, sizeof(struct ospf_lsdb)); ospf_lsdb_init(new); return new; } void ospf_lsdb_init(struct ospf_lsdb *lsdb) { int i; for (i = OSPF_MIN_LSA; i < OSPF_MAX_LSA; i++) lsdb->type[i].db = route_table_init(); } void ospf_lsdb_free(struct ospf_lsdb *lsdb) { ospf_lsdb_cleanup(lsdb); XFREE(MTYPE_OSPF_LSDB, lsdb); } void ospf_lsdb_cleanup(struct ospf_lsdb *lsdb) { int i; assert(lsdb); assert(lsdb->total == 0); ospf_lsdb_delete_all(lsdb); for (i = OSPF_MIN_LSA; i < OSPF_MAX_LSA; i++) route_table_finish(lsdb->type[i].db); } void ls_prefix_set(struct prefix_ls *lp, struct ospf_lsa *lsa) { if (lp && lsa && lsa->data) { lp->family = 0; lp->prefixlen = 64; lp->id = lsa->data->id; lp->adv_router = lsa->data->adv_router; } } static void ospf_lsdb_delete_entry(struct ospf_lsdb *lsdb, struct route_node *rn) { struct ospf_lsa *lsa = rn->info; if (!lsa) return; assert(rn->table == lsdb->type[lsa->data->type].db); if (IS_LSA_SELF(lsa)) lsdb->type[lsa->data->type].count_self--; lsdb->type[lsa->data->type].count--; lsdb->type[lsa->data->type].checksum -= ntohs(lsa->data->checksum); lsdb->total--; rn->info = NULL; route_unlock_node(rn); #ifdef MONITOR_LSDB_CHANGE if (lsdb->del_lsa_hook != NULL) (*lsdb->del_lsa_hook)(lsa); #endif /* MONITOR_LSDB_CHANGE */ ospf_lsa_unlock(&lsa); /* lsdb */ return; } /* Add new LSA to lsdb. */ void ospf_lsdb_add(struct ospf_lsdb *lsdb, struct ospf_lsa *lsa) { struct route_table *table; struct prefix_ls lp; struct route_node *rn; table = lsdb->type[lsa->data->type].db; ls_prefix_set(&lp, lsa); rn = route_node_get(table, (struct prefix *)&lp); /* nothing to do? */ if (rn->info && rn->info == lsa) { route_unlock_node(rn); return; } /* purge old entry? */ if (rn->info) ospf_lsdb_delete_entry(lsdb, rn); if (IS_LSA_SELF(lsa)) lsdb->type[lsa->data->type].count_self++; lsdb->type[lsa->data->type].count++; lsdb->total++; #ifdef MONITOR_LSDB_CHANGE if (lsdb->new_lsa_hook != NULL) (*lsdb->new_lsa_hook)(lsa); #endif /* MONITOR_LSDB_CHANGE */ lsdb->type[lsa->data->type].checksum += ntohs(lsa->data->checksum); rn->info = ospf_lsa_lock(lsa); /* lsdb */ } void ospf_lsdb_delete(struct ospf_lsdb *lsdb, struct ospf_lsa *lsa) { struct route_table *table; struct prefix_ls lp; struct route_node *rn; if (!lsdb || !lsa) return; assert(lsa->data->type < OSPF_MAX_LSA); table = lsdb->type[lsa->data->type].db; ls_prefix_set(&lp, lsa); if ((rn = route_node_lookup(table, (struct prefix *)&lp))) { if (rn->info == lsa) ospf_lsdb_delete_entry(lsdb, rn); route_unlock_node(rn); /* route_node_lookup */ } } void ospf_lsdb_delete_all(struct ospf_lsdb *lsdb) { struct route_table *table; struct route_node *rn; int i; for (i = OSPF_MIN_LSA; i < OSPF_MAX_LSA; i++) { table = lsdb->type[i].db; for (rn = route_top(table); rn; rn = route_next(rn)) if (rn->info != NULL) ospf_lsdb_delete_entry(lsdb, rn); } } struct ospf_lsa *ospf_lsdb_lookup(struct ospf_lsdb *lsdb, struct ospf_lsa *lsa) { struct route_table *table; struct prefix_ls lp; struct route_node *rn; struct ospf_lsa *find; table = lsdb->type[lsa->data->type].db; ls_prefix_set(&lp, lsa); rn = route_node_lookup(table, (struct prefix *)&lp); if (rn) { find = rn->info; route_unlock_node(rn); return find; } return NULL; } struct ospf_lsa *ospf_lsdb_lookup_by_id(struct ospf_lsdb *lsdb, uint8_t type, struct in_addr id, struct in_addr adv_router) { struct route_table *table; struct prefix_ls lp; struct route_node *rn; struct ospf_lsa *find; table = lsdb->type[type].db; memset(&lp, 0, sizeof(struct prefix_ls)); lp.family = 0; lp.prefixlen = 64; lp.id = id; lp.adv_router = adv_router; rn = route_node_lookup(table, (struct prefix *)&lp); if (rn) { find = rn->info; route_unlock_node(rn); return find; } return NULL; } struct ospf_lsa *ospf_lsdb_lookup_by_id_next(struct ospf_lsdb *lsdb, uint8_t type, struct in_addr id, struct in_addr adv_router, int first) { struct route_table *table; struct prefix_ls lp; struct route_node *rn; struct ospf_lsa *find; table = lsdb->type[type].db; memset(&lp, 0, sizeof(struct prefix_ls)); lp.family = 0; lp.prefixlen = 64; lp.id = id; lp.adv_router = adv_router; if (first) rn = route_top(table); else { if ((rn = route_node_lookup(table, (struct prefix *)&lp)) == NULL) return NULL; rn = route_next(rn); } for (; rn; rn = route_next(rn)) if (rn->info) break; if (rn && rn->info) { find = rn->info; route_unlock_node(rn); return find; } return NULL; } unsigned long ospf_lsdb_count_all(struct ospf_lsdb *lsdb) { return lsdb->total; } unsigned long ospf_lsdb_count(struct ospf_lsdb *lsdb, int type) { return lsdb->type[type].count; } unsigned long ospf_lsdb_count_self(struct ospf_lsdb *lsdb, int type) { return lsdb->type[type].count_self; } unsigned int ospf_lsdb_checksum(struct ospf_lsdb *lsdb, int type) { return lsdb->type[type].checksum; } unsigned long ospf_lsdb_isempty(struct ospf_lsdb *lsdb) { return (lsdb->total == 0); } frr-7.2.1/ospfd/ospf_lsdb.h0000644000000000000000000000674213610377563012464 00000000000000/* * OSPF LSDB support. * Copyright (C) 1999, 2000 Alex Zinin, Kunihiro Ishiguro, Toshiaki Takada * * This file is part of GNU Zebra. * * GNU Zebra is free software; you can redistribute it and/or modify it * under the terms of the GNU General Public License as published by the * Free Software Foundation; either version 2, or (at your option) any * later version. * * GNU Zebra 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; see the file COPYING; if not, write to the Free Software * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA */ #ifndef _ZEBRA_OSPF_LSDB_H #define _ZEBRA_OSPF_LSDB_H /* OSPF LSDB structure. */ struct ospf_lsdb { struct { unsigned long count; unsigned long count_self; unsigned int checksum; struct route_table *db; } type[OSPF_MAX_LSA]; unsigned long total; #define MONITOR_LSDB_CHANGE 1 /* XXX */ #ifdef MONITOR_LSDB_CHANGE /* Hooks for callback functions to catch every add/del event. */ int (*new_lsa_hook)(struct ospf_lsa *); int (*del_lsa_hook)(struct ospf_lsa *); #endif /* MONITOR_LSDB_CHANGE */ }; /* Macros. */ #define LSDB_LOOP(T, N, L) \ if ((T) != NULL) \ for ((N) = route_top((T)); ((N)); ((N)) = route_next((N))) \ if (((L) = (N)->info)) #define ROUTER_LSDB(A) ((A)->lsdb->type[OSPF_ROUTER_LSA].db) #define NETWORK_LSDB(A) ((A)->lsdb->type[OSPF_NETWORK_LSA].db) #define SUMMARY_LSDB(A) ((A)->lsdb->type[OSPF_SUMMARY_LSA].db) #define ASBR_SUMMARY_LSDB(A) ((A)->lsdb->type[OSPF_ASBR_SUMMARY_LSA].db) #define EXTERNAL_LSDB(O) ((O)->lsdb->type[OSPF_AS_EXTERNAL_LSA].db) #define NSSA_LSDB(A) ((A)->lsdb->type[OSPF_AS_NSSA_LSA].db) #define OPAQUE_LINK_LSDB(A) ((A)->lsdb->type[OSPF_OPAQUE_LINK_LSA].db) #define OPAQUE_AREA_LSDB(A) ((A)->lsdb->type[OSPF_OPAQUE_AREA_LSA].db) #define OPAQUE_AS_LSDB(O) ((O)->lsdb->type[OSPF_OPAQUE_AS_LSA].db) #define AREA_LSDB(A,T) ((A)->lsdb->type[(T)].db) #define AS_LSDB(O,T) ((O)->lsdb->type[(T)].db) /* OSPF LSDB related functions. */ extern struct ospf_lsdb *ospf_lsdb_new(void); extern void ospf_lsdb_init(struct ospf_lsdb *); extern void ospf_lsdb_free(struct ospf_lsdb *); extern void ospf_lsdb_cleanup(struct ospf_lsdb *); extern void ls_prefix_set(struct prefix_ls *lp, struct ospf_lsa *lsa); extern void ospf_lsdb_add(struct ospf_lsdb *, struct ospf_lsa *); extern void ospf_lsdb_delete(struct ospf_lsdb *, struct ospf_lsa *); extern void ospf_lsdb_delete_all(struct ospf_lsdb *); extern struct ospf_lsa *ospf_lsdb_lookup(struct ospf_lsdb *, struct ospf_lsa *); extern struct ospf_lsa *ospf_lsdb_lookup_by_id(struct ospf_lsdb *, uint8_t, struct in_addr, struct in_addr); extern struct ospf_lsa *ospf_lsdb_lookup_by_id_next(struct ospf_lsdb *, uint8_t, struct in_addr, struct in_addr, int); extern unsigned long ospf_lsdb_count_all(struct ospf_lsdb *); extern unsigned long ospf_lsdb_count(struct ospf_lsdb *, int); extern unsigned long ospf_lsdb_count_self(struct ospf_lsdb *, int); extern unsigned int ospf_lsdb_checksum(struct ospf_lsdb *, int); extern unsigned long ospf_lsdb_isempty(struct ospf_lsdb *); #endif /* _ZEBRA_OSPF_LSDB_H */ frr-7.2.1/ospfd/ospf_main.c0000644000000000000000000001171113610377563012447 00000000000000/* * OSPFd main routine. * Copyright (C) 1998, 99 Kunihiro Ishiguro, Toshiaki Takada * * This file is part of GNU Zebra. * * GNU Zebra is free software; you can redistribute it and/or modify it * under the terms of the GNU General Public License as published by the * Free Software Foundation; either version 2, or (at your option) any * later version. * * GNU Zebra 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; see the file COPYING; if not, write to the Free Software * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA */ #include #include #include "getopt.h" #include "thread.h" #include "prefix.h" #include "linklist.h" #include "if.h" #include "vector.h" #include "vty.h" #include "command.h" #include "filter.h" #include "plist.h" #include "stream.h" #include "log.h" #include "memory.h" #include "memory_vty.h" #include "privs.h" #include "sigevent.h" #include "zclient.h" #include "vrf.h" #include "libfrr.h" #include "ospfd/ospfd.h" #include "ospfd/ospf_interface.h" #include "ospfd/ospf_asbr.h" #include "ospfd/ospf_lsa.h" #include "ospfd/ospf_lsdb.h" #include "ospfd/ospf_neighbor.h" #include "ospfd/ospf_dump.h" #include "ospfd/ospf_route.h" #include "ospfd/ospf_zebra.h" #include "ospfd/ospf_vty.h" #include "ospfd/ospf_bfd.h" #include "ospfd/ospf_errors.h" /* ospfd privileges */ zebra_capabilities_t _caps_p[] = {ZCAP_NET_RAW, ZCAP_BIND, ZCAP_NET_ADMIN, ZCAP_SYS_ADMIN}; struct zebra_privs_t ospfd_privs = { #if defined(FRR_USER) && defined(FRR_GROUP) .user = FRR_USER, .group = FRR_GROUP, #endif #if defined(VTY_GROUP) .vty_group = VTY_GROUP, #endif .caps_p = _caps_p, .cap_num_p = array_size(_caps_p), .cap_num_i = 0}; /* OSPFd options. */ struct option longopts[] = {{"instance", required_argument, NULL, 'n'}, {"apiserver", no_argument, NULL, 'a'}, {0}}; /* OSPFd program name */ /* Master of threads. */ struct thread_master *master; #ifdef SUPPORT_OSPF_API extern int ospf_apiserver_enable; #endif /* SUPPORT_OSPF_API */ /* SIGHUP handler. */ static void sighup(void) { zlog_info("SIGHUP received"); } /* SIGINT / SIGTERM handler. */ static void sigint(void) { zlog_notice("Terminating on signal"); ospf_terminate(); } /* SIGUSR1 handler. */ static void sigusr1(void) { zlog_rotate(); } struct quagga_signal_t ospf_signals[] = { { .signal = SIGHUP, .handler = &sighup, }, { .signal = SIGUSR1, .handler = &sigusr1, }, { .signal = SIGINT, .handler = &sigint, }, { .signal = SIGTERM, .handler = &sigint, }, }; static const struct frr_yang_module_info *ospfd_yang_modules[] = { &frr_interface_info, }; FRR_DAEMON_INFO(ospfd, OSPF, .vty_port = OSPF_VTY_PORT, .proghelp = "Implementation of the OSPFv2 routing protocol.", .signals = ospf_signals, .n_signals = array_size(ospf_signals), .privs = &ospfd_privs, .yang_modules = ospfd_yang_modules, .n_yang_modules = array_size(ospfd_yang_modules), ) /* OSPFd main routine. */ int main(int argc, char **argv) { unsigned short instance = 0; #ifdef SUPPORT_OSPF_API /* OSPF apiserver is disabled by default. */ ospf_apiserver_enable = 0; #endif /* SUPPORT_OSPF_API */ frr_preinit(&ospfd_di, argc, argv); frr_opt_add("n:a", longopts, " -n, --instance Set the instance id\n" " -a, --apiserver Enable OSPF apiserver\n"); while (1) { int opt; opt = frr_getopt(argc, argv, NULL); if (opt == EOF) break; switch (opt) { case 'n': ospfd_di.instance = instance = atoi(optarg); if (instance < 1) exit(0); break; case 0: break; #ifdef SUPPORT_OSPF_API case 'a': ospf_apiserver_enable = 1; break; #endif /* SUPPORT_OSPF_API */ default: frr_help_exit(1); break; } } /* Invoked by a priviledged user? -- endo. */ if (geteuid() != 0) { errno = EPERM; perror(ospfd_di.progname); exit(1); } /* OSPF master init. */ ospf_master_init(frr_init()); /* Initializations. */ master = om->master; /* Library inits. */ ospf_debug_init(); ospf_vrf_init(); access_list_init(); prefix_list_init(); /* OSPFd inits. */ ospf_if_init(); ospf_zebra_init(master, instance); /* OSPF vty inits. */ ospf_vty_init(); ospf_vty_show_init(); ospf_vty_clear_init(); /* OSPF BFD init */ ospf_bfd_init(); ospf_route_map_init(); ospf_opaque_init(); /* OSPF errors init */ ospf_error_init(); /* Need to initialize the default ospf structure, so the interface mode commands can be duly processed if they are received before 'router ospf', when quagga(ospfd) is restarted */ if (!ospf_get_instance(instance)) { flog_err(EC_OSPF_INIT_FAIL, "OSPF instance init failed: %s", strerror(errno)); exit(1); } frr_config_fork(); frr_run(master); /* Not reached. */ return (0); } frr-7.2.1/ospfd/ospf_memory.c0000644000000000000000000000471113610377563013035 00000000000000/* ospfd memory type definitions * * Copyright (C) 2015 David Lamparter * * This file is part of Quagga. * * Quagga is free software; you can redistribute it and/or modify it * under the terms of the GNU General Public License as published by the * Free Software Foundation; either version 2, or (at your option) any * later version. * * Quagga 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; see the file COPYING; if not, write to the Free Software * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA */ #ifdef HAVE_CONFIG_H #include "config.h" #endif #include "ospf_memory.h" DEFINE_MGROUP(OSPFD, "ospfd") DEFINE_MTYPE(OSPFD, OSPF_TOP, "OSPF top") DEFINE_MTYPE(OSPFD, OSPF_AREA, "OSPF area") DEFINE_MTYPE(OSPFD, OSPF_AREA_RANGE, "OSPF area range") DEFINE_MTYPE(OSPFD, OSPF_NETWORK, "OSPF network") DEFINE_MTYPE(OSPFD, OSPF_NEIGHBOR_STATIC, "OSPF static nbr") DEFINE_MTYPE(OSPFD, OSPF_IF, "OSPF interface") DEFINE_MTYPE(OSPFD, OSPF_NEIGHBOR, "OSPF neighbor") DEFINE_MTYPE(OSPFD, OSPF_ROUTE, "OSPF route") DEFINE_MTYPE(OSPFD, OSPF_TMP, "OSPF tmp mem") DEFINE_MTYPE(OSPFD, OSPF_LSA, "OSPF LSA") DEFINE_MTYPE(OSPFD, OSPF_LSA_DATA, "OSPF LSA data") DEFINE_MTYPE(OSPFD, OSPF_LSDB, "OSPF LSDB") DEFINE_MTYPE(OSPFD, OSPF_PACKET, "OSPF packet") DEFINE_MTYPE(OSPFD, OSPF_FIFO, "OSPF FIFO queue") DEFINE_MTYPE(OSPFD, OSPF_VERTEX, "OSPF vertex") DEFINE_MTYPE(OSPFD, OSPF_VERTEX_PARENT, "OSPF vertex parent") DEFINE_MTYPE(OSPFD, OSPF_NEXTHOP, "OSPF nexthop") DEFINE_MTYPE(OSPFD, OSPF_PATH, "OSPF path") DEFINE_MTYPE(OSPFD, OSPF_VL_DATA, "OSPF VL data") DEFINE_MTYPE(OSPFD, OSPF_CRYPT_KEY, "OSPF crypt key") DEFINE_MTYPE(OSPFD, OSPF_EXTERNAL_INFO, "OSPF ext. info") DEFINE_MTYPE(OSPFD, OSPF_DISTANCE, "OSPF distance") DEFINE_MTYPE(OSPFD, OSPF_IF_INFO, "OSPF if info") DEFINE_MTYPE(OSPFD, OSPF_IF_PARAMS, "OSPF if params") DEFINE_MTYPE(OSPFD, OSPF_MESSAGE, "OSPF message") DEFINE_MTYPE(OSPFD, OSPF_MPLS_TE, "OSPF MPLS parameters") DEFINE_MTYPE(OSPFD, OSPF_ROUTER_INFO, "OSPF Router Info parameters") DEFINE_MTYPE(OSPFD, OSPF_PCE_PARAMS, "OSPF PCE parameters") DEFINE_MTYPE(OSPFD, OSPF_EXT_PARAMS, "OSPF Extended parameters") DEFINE_MTYPE(OSPFD, OSPF_SR_PARAMS, "OSPF Segment Routing parameters") frr-7.2.1/ospfd/ospf_memory.h0000644000000000000000000000342713610377563013045 00000000000000/* ospfd memory type declarations * * Copyright (C) 2015 David Lamparter * * This file is part of Quagga. * * Quagga is free software; you can redistribute it and/or modify it * under the terms of the GNU General Public License as published by the * Free Software Foundation; either version 2, or (at your option) any * later version. * * Quagga 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; see the file COPYING; if not, write to the Free Software * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA */ #ifndef _QUAGGA_OSPF_MEMORY_H #define _QUAGGA_OSPF_MEMORY_H #include "memory.h" DECLARE_MGROUP(OSPFD) DECLARE_MTYPE(OSPF_TOP) DECLARE_MTYPE(OSPF_AREA) DECLARE_MTYPE(OSPF_AREA_RANGE) DECLARE_MTYPE(OSPF_NETWORK) DECLARE_MTYPE(OSPF_NEIGHBOR_STATIC) DECLARE_MTYPE(OSPF_IF) DECLARE_MTYPE(OSPF_NEIGHBOR) DECLARE_MTYPE(OSPF_ROUTE) DECLARE_MTYPE(OSPF_TMP) DECLARE_MTYPE(OSPF_LSA) DECLARE_MTYPE(OSPF_LSA_DATA) DECLARE_MTYPE(OSPF_LSDB) DECLARE_MTYPE(OSPF_PACKET) DECLARE_MTYPE(OSPF_FIFO) DECLARE_MTYPE(OSPF_VERTEX) DECLARE_MTYPE(OSPF_VERTEX_PARENT) DECLARE_MTYPE(OSPF_NEXTHOP) DECLARE_MTYPE(OSPF_PATH) DECLARE_MTYPE(OSPF_VL_DATA) DECLARE_MTYPE(OSPF_CRYPT_KEY) DECLARE_MTYPE(OSPF_EXTERNAL_INFO) DECLARE_MTYPE(OSPF_DISTANCE) DECLARE_MTYPE(OSPF_IF_INFO) DECLARE_MTYPE(OSPF_IF_PARAMS) DECLARE_MTYPE(OSPF_MESSAGE) DECLARE_MTYPE(OSPF_MPLS_TE) DECLARE_MTYPE(OSPF_ROUTER_INFO) DECLARE_MTYPE(OSPF_PCE_PARAMS) DECLARE_MTYPE(OSPF_SR_PARAMS) DECLARE_MTYPE(OSPF_EXT_PARAMS) #endif /* _QUAGGA_OSPF_MEMORY_H */ frr-7.2.1/ospfd/ospf_neighbor.c0000644000000000000000000003003613610377563013321 00000000000000/* * OSPF Neighbor functions. * Copyright (C) 1999, 2000 Toshiaki Takada * * This file is part of GNU Zebra. * * GNU Zebra is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License as published * by the Free Software Foundation; either version 2, or (at your * option) any later version. * * GNU Zebra 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; see the file COPYING; if not, write to the Free Software * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA */ #include #include "linklist.h" #include "prefix.h" #include "memory.h" #include "command.h" #include "thread.h" #include "stream.h" #include "table.h" #include "log.h" #include "json.h" #include "ospfd/ospfd.h" #include "ospfd/ospf_interface.h" #include "ospfd/ospf_asbr.h" #include "ospfd/ospf_lsa.h" #include "ospfd/ospf_lsdb.h" #include "ospfd/ospf_neighbor.h" #include "ospfd/ospf_nsm.h" #include "ospfd/ospf_packet.h" #include "ospfd/ospf_network.h" #include "ospfd/ospf_flood.h" #include "ospfd/ospf_dump.h" #include "ospfd/ospf_bfd.h" /* Fill in the the 'key' as appropriate to retrieve the entry for nbr * from the ospf_interface's nbrs table. Indexed by interface address * for all cases except Virtual-link and PointToPoint interfaces, where * neighbours are indexed by router-ID instead. */ static void ospf_nbr_key(struct ospf_interface *oi, struct ospf_neighbor *nbr, struct prefix *key) { key->family = AF_INET; key->prefixlen = IPV4_MAX_BITLEN; /* vlinks are indexed by router-id */ if (oi->type == OSPF_IFTYPE_VIRTUALLINK || oi->type == OSPF_IFTYPE_POINTOPOINT) key->u.prefix4 = nbr->router_id; else key->u.prefix4 = nbr->src; return; } struct ospf_neighbor *ospf_nbr_new(struct ospf_interface *oi) { struct ospf_neighbor *nbr; /* Allcate new neighbor. */ nbr = XCALLOC(MTYPE_OSPF_NEIGHBOR, sizeof(struct ospf_neighbor)); /* Relate neighbor to the interface. */ nbr->oi = oi; /* Set default values. */ nbr->state = NSM_Down; /* Set inheritance values. */ nbr->v_inactivity = OSPF_IF_PARAM(oi, v_wait); nbr->v_db_desc = OSPF_IF_PARAM(oi, retransmit_interval); nbr->v_ls_req = OSPF_IF_PARAM(oi, retransmit_interval); nbr->v_ls_upd = OSPF_IF_PARAM(oi, retransmit_interval); nbr->priority = -1; /* DD flags. */ nbr->dd_flags = OSPF_DD_FLAG_MS | OSPF_DD_FLAG_M | OSPF_DD_FLAG_I; /* Last received and sent DD. */ nbr->last_send = NULL; nbr->nbr_nbma = NULL; ospf_lsdb_init(&nbr->db_sum); ospf_lsdb_init(&nbr->ls_rxmt); ospf_lsdb_init(&nbr->ls_req); nbr->crypt_seqnum = 0; ospf_bfd_info_nbr_create(oi, nbr); return nbr; } void ospf_nbr_free(struct ospf_neighbor *nbr) { /* Free DB summary list. */ if (ospf_db_summary_count(nbr)) ospf_db_summary_clear(nbr); /* ospf_db_summary_delete_all (nbr); */ /* Free ls request list. */ if (ospf_ls_request_count(nbr)) ospf_ls_request_delete_all(nbr); /* Free retransmit list. */ if (ospf_ls_retransmit_count(nbr)) ospf_ls_retransmit_clear(nbr); /* Cleanup LSDBs. */ ospf_lsdb_cleanup(&nbr->db_sum); ospf_lsdb_cleanup(&nbr->ls_req); ospf_lsdb_cleanup(&nbr->ls_rxmt); /* Clear last send packet. */ if (nbr->last_send) ospf_packet_free(nbr->last_send); if (nbr->nbr_nbma) { nbr->nbr_nbma->nbr = NULL; nbr->nbr_nbma = NULL; } /* Cancel all timers. */ OSPF_NSM_TIMER_OFF(nbr->t_inactivity); OSPF_NSM_TIMER_OFF(nbr->t_db_desc); OSPF_NSM_TIMER_OFF(nbr->t_ls_req); OSPF_NSM_TIMER_OFF(nbr->t_ls_upd); /* Cancel all events. */ /* Thread lookup cost would be negligible. */ thread_cancel_event(master, nbr); ospf_bfd_info_free(&nbr->bfd_info); XFREE(MTYPE_OSPF_NEIGHBOR, nbr); } /* Delete specified OSPF neighbor from interface. */ void ospf_nbr_delete(struct ospf_neighbor *nbr) { struct ospf_interface *oi; struct route_node *rn; struct prefix p; oi = nbr->oi; /* get appropriate prefix 'key' */ ospf_nbr_key(oi, nbr, &p); rn = route_node_lookup(oi->nbrs, &p); if (rn) { /* If lookup for a NBR succeeds, the leaf route_node could * only exist because there is (or was) a nbr there. * If the nbr was deleted, the leaf route_node should have * lost its last refcount too, and be deleted. * Therefore a looked-up leaf route_node in nbrs table * should never have NULL info. */ assert(rn->info); if (rn->info) { rn->info = NULL; route_unlock_node(rn); } else zlog_info("Can't find neighbor %s in the interface %s", inet_ntoa(nbr->src), IF_NAME(oi)); route_unlock_node(rn); } else { /* * This neighbor was not found, but before we move on and * free the neighbor structre, make sure that it was not * indexed incorrectly and ended up in the "worng" place */ /* Reverse the lookup rules */ if (oi->type == OSPF_IFTYPE_VIRTUALLINK || oi->type == OSPF_IFTYPE_POINTOPOINT) p.u.prefix4 = nbr->src; else p.u.prefix4 = nbr->router_id; rn = route_node_lookup(oi->nbrs, &p); if (rn) { /* We found the neighbor! * Now make sure it is not the exact same neighbor * structure that we are about to free */ if (nbr == rn->info) { /* Same neighbor, drop the reference to it */ rn->info = NULL; route_unlock_node(rn); } route_unlock_node(rn); } } /* Free ospf_neighbor structure. */ ospf_nbr_free(nbr); } /* Check myself is in the neighbor list. */ int ospf_nbr_bidirectional(struct in_addr *router_id, struct in_addr *neighbors, int size) { int i; int max; max = size / sizeof(struct in_addr); for (i = 0; i < max; i++) if (IPV4_ADDR_SAME(router_id, &neighbors[i])) return 1; return 0; } /* reset nbr_self */ void ospf_nbr_self_reset(struct ospf_interface *oi, struct in_addr router_id) { if (oi->nbr_self) ospf_nbr_delete(oi->nbr_self); oi->nbr_self = ospf_nbr_new(oi); ospf_nbr_add_self(oi, router_id); } /* Add self to nbr list. */ void ospf_nbr_add_self(struct ospf_interface *oi, struct in_addr router_id) { struct prefix p; struct route_node *rn; if (!oi->nbr_self) oi->nbr_self = ospf_nbr_new(oi); /* Initial state */ oi->nbr_self->address = *oi->address; oi->nbr_self->priority = OSPF_IF_PARAM(oi, priority); oi->nbr_self->router_id = router_id; oi->nbr_self->src = oi->address->u.prefix4; oi->nbr_self->state = NSM_TwoWay; switch (oi->area->external_routing) { case OSPF_AREA_DEFAULT: SET_FLAG(oi->nbr_self->options, OSPF_OPTION_E); break; case OSPF_AREA_STUB: UNSET_FLAG(oi->nbr_self->options, OSPF_OPTION_E); break; case OSPF_AREA_NSSA: UNSET_FLAG(oi->nbr_self->options, OSPF_OPTION_E); SET_FLAG(oi->nbr_self->options, OSPF_OPTION_NP); break; } /* Add nbr_self to nbrs table */ ospf_nbr_key(oi, oi->nbr_self, &p); rn = route_node_get(oi->nbrs, &p); if (rn->info) { /* There is already pseudo neighbor. */ if (IS_DEBUG_OSPF_EVENT) zlog_debug( "router_id %s already present in neighbor table. node refcount %u", inet_ntoa(router_id), rn->lock); route_unlock_node(rn); } else rn->info = oi->nbr_self; } /* Get neighbor count by status. Specify status = 0, get all neighbor other than myself. */ int ospf_nbr_count(struct ospf_interface *oi, int state) { struct ospf_neighbor *nbr; struct route_node *rn; int count = 0; for (rn = route_top(oi->nbrs); rn; rn = route_next(rn)) if ((nbr = rn->info)) if (!IPV4_ADDR_SAME(&nbr->router_id, &oi->ospf->router_id)) if (state == 0 || nbr->state == state) count++; return count; } int ospf_nbr_count_opaque_capable(struct ospf_interface *oi) { struct ospf_neighbor *nbr; struct route_node *rn; int count = 0; for (rn = route_top(oi->nbrs); rn; rn = route_next(rn)) if ((nbr = rn->info)) if (!IPV4_ADDR_SAME(&nbr->router_id, &oi->ospf->router_id)) if (nbr->state == NSM_Full) if (CHECK_FLAG(nbr->options, OSPF_OPTION_O)) count++; return count; } /* lookup nbr by address - use this only if you know you must * otherwise use the ospf_nbr_lookup() wrapper, which deals * with virtual link and PointToPoint neighbours */ struct ospf_neighbor *ospf_nbr_lookup_by_addr(struct route_table *nbrs, struct in_addr *addr) { struct prefix p; struct route_node *rn; struct ospf_neighbor *nbr; p.family = AF_INET; p.prefixlen = IPV4_MAX_BITLEN; p.u.prefix4 = *addr; rn = route_node_lookup(nbrs, &p); if (!rn) return NULL; /* See comment in ospf_nbr_delete */ assert(rn->info); if (rn->info == NULL) { route_unlock_node(rn); return NULL; } nbr = (struct ospf_neighbor *)rn->info; route_unlock_node(rn); return nbr; } struct ospf_neighbor *ospf_nbr_lookup_by_routerid(struct route_table *nbrs, struct in_addr *id) { struct route_node *rn; struct ospf_neighbor *nbr; for (rn = route_top(nbrs); rn; rn = route_next(rn)) if ((nbr = rn->info) != NULL) if (IPV4_ADDR_SAME(&nbr->router_id, id)) { route_unlock_node(rn); return nbr; } return NULL; } void ospf_renegotiate_optional_capabilities(struct ospf *top) { struct listnode *node; struct ospf_interface *oi; struct route_table *nbrs; struct route_node *rn; struct ospf_neighbor *nbr; /* At first, flush self-originated LSAs from routing domain. */ ospf_flush_self_originated_lsas_now(top); /* Revert all neighbor status to ExStart. */ for (ALL_LIST_ELEMENTS_RO(top->oiflist, node, oi)) { if ((nbrs = oi->nbrs) == NULL) continue; for (rn = route_top(nbrs); rn; rn = route_next(rn)) { if ((nbr = rn->info) == NULL || nbr == oi->nbr_self) continue; if (nbr->state < NSM_ExStart) continue; if (IS_DEBUG_OSPF_EVENT) zlog_debug( "Renegotiate optional capabilities with neighbor(%s)", inet_ntoa(nbr->router_id)); OSPF_NSM_EVENT_SCHEDULE(nbr, NSM_SeqNumberMismatch); } } return; } struct ospf_neighbor *ospf_nbr_lookup(struct ospf_interface *oi, struct ip *iph, struct ospf_header *ospfh) { struct in_addr srcaddr = iph->ip_src; if (oi->type == OSPF_IFTYPE_VIRTUALLINK || oi->type == OSPF_IFTYPE_POINTOPOINT) return (ospf_nbr_lookup_by_routerid(oi->nbrs, &ospfh->router_id)); else return (ospf_nbr_lookup_by_addr(oi->nbrs, &srcaddr)); } static struct ospf_neighbor *ospf_nbr_add(struct ospf_interface *oi, struct ospf_header *ospfh, struct prefix *p) { struct ospf_neighbor *nbr; nbr = ospf_nbr_new(oi); nbr->state = NSM_Down; nbr->src = p->u.prefix4; memcpy(&nbr->address, p, sizeof(struct prefix)); nbr->nbr_nbma = NULL; if (oi->type == OSPF_IFTYPE_NBMA) { struct ospf_nbr_nbma *nbr_nbma; struct listnode *node; for (ALL_LIST_ELEMENTS_RO(oi->nbr_nbma, node, nbr_nbma)) { if (IPV4_ADDR_SAME(&nbr_nbma->addr, &nbr->src)) { nbr_nbma->nbr = nbr; nbr->nbr_nbma = nbr_nbma; if (nbr_nbma->t_poll) OSPF_POLL_TIMER_OFF(nbr_nbma->t_poll); nbr->state_change = nbr_nbma->state_change + 1; } } } /* New nbr, save the crypto sequence number if necessary */ if (ntohs(ospfh->auth_type) == OSPF_AUTH_CRYPTOGRAPHIC) nbr->crypt_seqnum = ospfh->u.crypt.crypt_seqnum; if (IS_DEBUG_OSPF_EVENT) zlog_debug("NSM[%s:%s]: start", IF_NAME(nbr->oi), inet_ntoa(nbr->router_id)); return nbr; } struct ospf_neighbor *ospf_nbr_get(struct ospf_interface *oi, struct ospf_header *ospfh, struct ip *iph, struct prefix *p) { struct route_node *rn; struct prefix key; struct ospf_neighbor *nbr; key.family = AF_INET; key.prefixlen = IPV4_MAX_BITLEN; if (oi->type == OSPF_IFTYPE_VIRTUALLINK || oi->type == OSPF_IFTYPE_POINTOPOINT) key.u.prefix4 = ospfh->router_id; /* index vlink and ptp nbrs by router-id */ else key.u.prefix4 = iph->ip_src; rn = route_node_get(oi->nbrs, &key); if (rn->info) { route_unlock_node(rn); nbr = rn->info; if (oi->type == OSPF_IFTYPE_NBMA && nbr->state == NSM_Attempt) { nbr->src = iph->ip_src; memcpy(&nbr->address, p, sizeof(struct prefix)); } } else { rn->info = nbr = ospf_nbr_add(oi, ospfh, p); } nbr->router_id = ospfh->router_id; return nbr; } frr-7.2.1/ospfd/ospf_neighbor.h0000644000000000000000000000767713610377563013345 00000000000000/* * OSPF Neighbor functions. * Copyright (C) 1999, 2000 Toshiaki Takada * * This file is part of GNU Zebra. * * GNU Zebra is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License as published * by the Free Software Foundation; either version 2, or (at your * option) any later version. * * GNU Zebra 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; see the file COPYING; if not, write to the Free Software * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA */ #ifndef _ZEBRA_OSPF_NEIGHBOR_H #define _ZEBRA_OSPF_NEIGHBOR_H #include /* Neighbor Data Structure */ struct ospf_neighbor { /* This neighbor's parent ospf interface. */ struct ospf_interface *oi; /* OSPF neighbor Information */ uint8_t state; /* NSM status. */ uint8_t dd_flags; /* DD bit flags. */ uint32_t dd_seqnum; /* DD Sequence Number. */ /* Neighbor Information from Hello. */ struct prefix address; /* Neighbor Interface Address. */ struct in_addr src; /* Src address. */ struct in_addr router_id; /* Router ID. */ uint8_t options; /* Options. */ int priority; /* Router Priority. */ struct in_addr d_router; /* Designated Router. */ struct in_addr bd_router; /* Backup Designated Router. */ /* Last sent Database Description packet. */ struct ospf_packet *last_send; /* Timestemp when last Database Description packet was sent */ struct timeval last_send_ts; /* Last received Databse Description packet. */ struct { uint8_t options; uint8_t flags; uint32_t dd_seqnum; } last_recv; /* LSA data. */ struct ospf_lsdb ls_rxmt; struct ospf_lsdb db_sum; struct ospf_lsdb ls_req; struct ospf_lsa *ls_req_last; uint32_t crypt_seqnum; /* Cryptographic Sequence Number. */ /* Timer values. */ uint32_t v_inactivity; uint32_t v_db_desc; uint32_t v_ls_req; uint32_t v_ls_upd; /* Threads. */ struct thread *t_inactivity; struct thread *t_db_desc; struct thread *t_ls_req; struct thread *t_ls_upd; struct thread *t_hello_reply; /* NBMA configured neighbour */ struct ospf_nbr_nbma *nbr_nbma; /* Statistics */ struct timeval ts_last_progress; /* last advance of NSM */ struct timeval ts_last_regress; /* last regressive NSM change */ const char *last_regress_str; /* Event which last regressed NSM */ uint32_t state_change; /* NSM state change counter */ /* BFD information */ void *bfd_info; }; /* Macros. */ #define NBR_IS_DR(n) IPV4_ADDR_SAME (&n->address.u.prefix4, &n->d_router) #define NBR_IS_BDR(n) IPV4_ADDR_SAME (&n->address.u.prefix4, &n->bd_router) /* Prototypes. */ extern struct ospf_neighbor *ospf_nbr_new(struct ospf_interface *); extern void ospf_nbr_free(struct ospf_neighbor *); extern void ospf_nbr_delete(struct ospf_neighbor *); extern int ospf_nbr_bidirectional(struct in_addr *, struct in_addr *, int); extern void ospf_nbr_self_reset(struct ospf_interface *, struct in_addr); extern void ospf_nbr_add_self(struct ospf_interface *, struct in_addr); extern int ospf_nbr_count(struct ospf_interface *, int); extern int ospf_nbr_count_opaque_capable(struct ospf_interface *); extern struct ospf_neighbor *ospf_nbr_get(struct ospf_interface *, struct ospf_header *, struct ip *, struct prefix *); extern struct ospf_neighbor *ospf_nbr_lookup(struct ospf_interface *, struct ip *, struct ospf_header *); extern struct ospf_neighbor *ospf_nbr_lookup_by_addr(struct route_table *, struct in_addr *); extern struct ospf_neighbor *ospf_nbr_lookup_by_routerid(struct route_table *, struct in_addr *); extern void ospf_renegotiate_optional_capabilities(struct ospf *top); #endif /* _ZEBRA_OSPF_NEIGHBOR_H */ frr-7.2.1/ospfd/ospf_network.c0000644000000000000000000001523213610377563013216 00000000000000/* * OSPF network related functions * Copyright (C) 1999 Toshiaki Takada * * This file is part of GNU Zebra. * * GNU Zebra is free software; you can redistribute it and/or modify it * under the terms of the GNU General Public License as published by the * Free Software Foundation; either version 2, or (at your option) any * later version. * * GNU Zebra 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; see the file COPYING; if not, write to the Free Software * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA */ #include #include "thread.h" #include "linklist.h" #include "prefix.h" #include "if.h" #include "sockunion.h" #include "log.h" #include "sockopt.h" #include "privs.h" #include "lib_errors.h" #include "ospfd/ospfd.h" #include "ospfd/ospf_network.h" #include "ospfd/ospf_interface.h" #include "ospfd/ospf_asbr.h" #include "ospfd/ospf_lsa.h" #include "ospfd/ospf_lsdb.h" #include "ospfd/ospf_neighbor.h" #include "ospfd/ospf_packet.h" #include "ospfd/ospf_dump.h" /* Join to the OSPF ALL SPF ROUTERS multicast group. */ int ospf_if_add_allspfrouters(struct ospf *top, struct prefix *p, ifindex_t ifindex) { int ret; ret = setsockopt_ipv4_multicast(top->fd, IP_ADD_MEMBERSHIP, p->u.prefix4, htonl(OSPF_ALLSPFROUTERS), ifindex); if (ret < 0) flog_err( EC_LIB_SOCKET, "can't setsockopt IP_ADD_MEMBERSHIP (fd %d, addr %s, " "ifindex %u, AllSPFRouters): %s; perhaps a kernel limit " "on # of multicast group memberships has been exceeded?", top->fd, inet_ntoa(p->u.prefix4), ifindex, safe_strerror(errno)); else { if (IS_DEBUG_OSPF_EVENT) zlog_debug( "interface %s [%u] join AllSPFRouters Multicast group.", inet_ntoa(p->u.prefix4), ifindex); } return ret; } int ospf_if_drop_allspfrouters(struct ospf *top, struct prefix *p, ifindex_t ifindex) { int ret; ret = setsockopt_ipv4_multicast(top->fd, IP_DROP_MEMBERSHIP, p->u.prefix4, htonl(OSPF_ALLSPFROUTERS), ifindex); if (ret < 0) flog_err(EC_LIB_SOCKET, "can't setsockopt IP_DROP_MEMBERSHIP (fd %d, addr %s, " "ifindex %u, AllSPFRouters): %s", top->fd, inet_ntoa(p->u.prefix4), ifindex, safe_strerror(errno)); else { if (IS_DEBUG_OSPF_EVENT) zlog_debug( "interface %s [%u] leave AllSPFRouters Multicast group.", inet_ntoa(p->u.prefix4), ifindex); } return ret; } /* Join to the OSPF ALL Designated ROUTERS multicast group. */ int ospf_if_add_alldrouters(struct ospf *top, struct prefix *p, ifindex_t ifindex) { int ret; ret = setsockopt_ipv4_multicast(top->fd, IP_ADD_MEMBERSHIP, p->u.prefix4, htonl(OSPF_ALLDROUTERS), ifindex); if (ret < 0) flog_err( EC_LIB_SOCKET, "can't setsockopt IP_ADD_MEMBERSHIP (fd %d, addr %s, " "ifindex %u, AllDRouters): %s; perhaps a kernel limit " "on # of multicast group memberships has been exceeded?", top->fd, inet_ntoa(p->u.prefix4), ifindex, safe_strerror(errno)); else zlog_debug( "interface %s [%u] join AllDRouters Multicast group.", inet_ntoa(p->u.prefix4), ifindex); return ret; } int ospf_if_drop_alldrouters(struct ospf *top, struct prefix *p, ifindex_t ifindex) { int ret; ret = setsockopt_ipv4_multicast(top->fd, IP_DROP_MEMBERSHIP, p->u.prefix4, htonl(OSPF_ALLDROUTERS), ifindex); if (ret < 0) flog_err(EC_LIB_SOCKET, "can't setsockopt IP_DROP_MEMBERSHIP (fd %d, addr %s, " "ifindex %u, AllDRouters): %s", top->fd, inet_ntoa(p->u.prefix4), ifindex, safe_strerror(errno)); else zlog_debug( "interface %s [%u] leave AllDRouters Multicast group.", inet_ntoa(p->u.prefix4), ifindex); return ret; } int ospf_if_ipmulticast(struct ospf *top, struct prefix *p, ifindex_t ifindex) { uint8_t val; int ret, len; /* Prevent receiving self-origined multicast packets. */ ret = setsockopt_ipv4_multicast_loop(top->fd, 0); if (ret < 0) flog_err(EC_LIB_SOCKET, "can't setsockopt IP_MULTICAST_LOOP(0) for fd %d: %s", top->fd, safe_strerror(errno)); /* Explicitly set multicast ttl to 1 -- endo. */ val = 1; len = sizeof(val); ret = setsockopt(top->fd, IPPROTO_IP, IP_MULTICAST_TTL, (void *)&val, len); if (ret < 0) flog_err(EC_LIB_SOCKET, "can't setsockopt IP_MULTICAST_TTL(1) for fd %d: %s", top->fd, safe_strerror(errno)); #ifndef GNU_LINUX /* For GNU LINUX ospf_write uses IP_PKTINFO, in_pktinfo to send * packet out of ifindex. Below would be used Non Linux system. */ ret = setsockopt_ipv4_multicast_if(top->fd, p->u.prefix4, ifindex); if (ret < 0) flog_err(EC_LIB_SOCKET, "can't setsockopt IP_MULTICAST_IF(fd %d, addr %s, " "ifindex %u): %s", top->fd, inet_ntoa(p->u.prefix4), ifindex, safe_strerror(errno)); #endif return ret; } int ospf_sock_init(struct ospf *ospf) { int ospf_sock; int ret, hincl = 1; int bufsize = (8 * 1024 * 1024); /* silently ignore. already done */ if (ospf->fd > 0) return -1; if (ospf->vrf_id == VRF_UNKNOWN) { /* silently return since VRF is not ready */ return -1; } frr_with_privs(&ospfd_privs) { ospf_sock = vrf_socket(AF_INET, SOCK_RAW, IPPROTO_OSPFIGP, ospf->vrf_id, ospf->name); if (ospf_sock < 0) { flog_err(EC_LIB_SOCKET, "ospf_read_sock_init: socket: %s", safe_strerror(errno)); exit(1); } #ifdef IP_HDRINCL /* we will include IP header with packet */ ret = setsockopt(ospf_sock, IPPROTO_IP, IP_HDRINCL, &hincl, sizeof(hincl)); if (ret < 0) { flog_err(EC_LIB_SOCKET, "Can't set IP_HDRINCL option for fd %d: %s", ospf_sock, safe_strerror(errno)); close(ospf_sock); break; } #elif defined(IPTOS_PREC_INTERNETCONTROL) #warning "IP_HDRINCL not available on this system" #warning "using IPTOS_PREC_INTERNETCONTROL" ret = setsockopt_ipv4_tos(ospf_sock, IPTOS_PREC_INTERNETCONTROL); if (ret < 0) { flog_err(EC_LIB_SOCKET, "can't set sockopt IP_TOS %d to socket %d: %s", tos, ospf_sock, safe_strerror(errno)); close(ospf_sock); /* Prevent sd leak. */ break; } #else /* !IPTOS_PREC_INTERNETCONTROL */ #warning "IP_HDRINCL not available, nor is IPTOS_PREC_INTERNETCONTROL" flog_err(EC_LIB_UNAVAILABLE, "IP_HDRINCL option not available"); #endif /* IP_HDRINCL */ ret = setsockopt_ifindex(AF_INET, ospf_sock, 1); if (ret < 0) flog_err(EC_LIB_SOCKET, "Can't set pktinfo option for fd %d", ospf_sock); } setsockopt_so_sendbuf(ospf_sock, bufsize); setsockopt_so_recvbuf(ospf_sock, bufsize); ospf->fd = ospf_sock; return ret; } frr-7.2.1/ospfd/ospf_network.h0000644000000000000000000000257513610377563013231 00000000000000/* * OSPF network related functions. * Copyright (C) 1999 Toshiaki Takada * * This file is part of GNU Zebra. * * GNU Zebra is free software; you can redistribute it and/or modify it * under the terms of the GNU General Public License as published by the * Free Software Foundation; either version 2, or (at your option) any * later version. * * GNU Zebra 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; see the file COPYING; if not, write to the Free Software * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA */ #ifndef _ZEBRA_OSPF_NETWORK_H #define _ZEBRA_OSPF_NETWORK_H /* Prototypes. */ extern int ospf_if_add_allspfrouters(struct ospf *, struct prefix *, ifindex_t); extern int ospf_if_drop_allspfrouters(struct ospf *, struct prefix *, ifindex_t); extern int ospf_if_add_alldrouters(struct ospf *, struct prefix *, ifindex_t); extern int ospf_if_drop_alldrouters(struct ospf *, struct prefix *, ifindex_t); extern int ospf_if_ipmulticast(struct ospf *, struct prefix *, ifindex_t); extern int ospf_sock_init(struct ospf *ospf); #endif /* _ZEBRA_OSPF_NETWORK_H */ frr-7.2.1/ospfd/ospf_nsm.c0000644000000000000000000006115613610377563012330 00000000000000/* * OSPF version 2 Neighbor State Machine * From RFC2328 [OSPF Version 2] * Copyright (C) 1999, 2000 Toshiaki Takada * * This file is part of GNU Zebra. * * GNU Zebra is free software; you can redistribute it and/or modify it * under the terms of the GNU General Public License as published by the * Free Software Foundation; either version 2, or (at your option) any * later version. * * GNU Zebra 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; see the file COPYING; if not, write to the Free Software * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA */ #include #include "thread.h" #include "memory.h" #include "hash.h" #include "linklist.h" #include "prefix.h" #include "if.h" #include "table.h" #include "stream.h" #include "table.h" #include "log.h" #include "command.h" #include "ospfd/ospfd.h" #include "ospfd/ospf_interface.h" #include "ospfd/ospf_ism.h" #include "ospfd/ospf_asbr.h" #include "ospfd/ospf_lsa.h" #include "ospfd/ospf_lsdb.h" #include "ospfd/ospf_neighbor.h" #include "ospfd/ospf_nsm.h" #include "ospfd/ospf_network.h" #include "ospfd/ospf_packet.h" #include "ospfd/ospf_dump.h" #include "ospfd/ospf_flood.h" #include "ospfd/ospf_abr.h" #include "ospfd/ospf_bfd.h" #include "ospfd/ospf_errors.h" DEFINE_HOOK(ospf_nsm_change, (struct ospf_neighbor * on, int state, int oldstate), (on, state, oldstate)) static void nsm_clear_adj(struct ospf_neighbor *); /* OSPF NSM Timer functions. */ static int ospf_inactivity_timer(struct thread *thread) { struct ospf_neighbor *nbr; nbr = THREAD_ARG(thread); nbr->t_inactivity = NULL; if (IS_DEBUG_OSPF(nsm, NSM_TIMERS)) zlog_debug("NSM[%s:%s]: Timer (Inactivity timer expire)", IF_NAME(nbr->oi), inet_ntoa(nbr->router_id)); OSPF_NSM_EVENT_SCHEDULE(nbr, NSM_InactivityTimer); return 0; } static int ospf_db_desc_timer(struct thread *thread) { struct ospf_neighbor *nbr; nbr = THREAD_ARG(thread); nbr->t_db_desc = NULL; if (IS_DEBUG_OSPF(nsm, NSM_TIMERS)) zlog_debug("NSM[%s:%s]: Timer (DD Retransmit timer expire)", IF_NAME(nbr->oi), inet_ntoa(nbr->src)); /* resent last send DD packet. */ assert(nbr->last_send); ospf_db_desc_resend(nbr); /* DD Retransmit timer set. */ OSPF_NSM_TIMER_ON(nbr->t_db_desc, ospf_db_desc_timer, nbr->v_db_desc); return 0; } /* Hook function called after ospf NSM event is occurred. * * Set/clear any timers whose condition is implicit to the neighbour * state. There may be other timers which are set/unset according to other * state. * * We rely on this function to properly clear timers in lower states, * particularly before deleting a neighbour. */ static void nsm_timer_set(struct ospf_neighbor *nbr) { switch (nbr->state) { case NSM_Deleted: case NSM_Down: OSPF_NSM_TIMER_OFF(nbr->t_inactivity); OSPF_NSM_TIMER_OFF(nbr->t_hello_reply); /* fallthru */ case NSM_Attempt: case NSM_Init: case NSM_TwoWay: OSPF_NSM_TIMER_OFF(nbr->t_db_desc); OSPF_NSM_TIMER_OFF(nbr->t_ls_upd); OSPF_NSM_TIMER_OFF(nbr->t_ls_req); break; case NSM_ExStart: OSPF_NSM_TIMER_ON(nbr->t_db_desc, ospf_db_desc_timer, nbr->v_db_desc); OSPF_NSM_TIMER_OFF(nbr->t_ls_upd); OSPF_NSM_TIMER_OFF(nbr->t_ls_req); break; case NSM_Exchange: OSPF_NSM_TIMER_ON(nbr->t_ls_upd, ospf_ls_upd_timer, nbr->v_ls_upd); if (!IS_SET_DD_MS(nbr->dd_flags)) OSPF_NSM_TIMER_OFF(nbr->t_db_desc); break; case NSM_Loading: case NSM_Full: default: OSPF_NSM_TIMER_OFF(nbr->t_db_desc); break; } } /* 10.4 of RFC2328, indicate whether an adjacency is appropriate with * the given neighbour */ static int nsm_should_adj(struct ospf_neighbor *nbr) { struct ospf_interface *oi = nbr->oi; /* These network types must always form adjacencies. */ if (oi->type == OSPF_IFTYPE_POINTOPOINT || oi->type == OSPF_IFTYPE_POINTOMULTIPOINT || oi->type == OSPF_IFTYPE_VIRTUALLINK /* Router itself is the DRouter or the BDRouter. */ || IPV4_ADDR_SAME(&oi->address->u.prefix4, &DR(oi)) || IPV4_ADDR_SAME(&oi->address->u.prefix4, &BDR(oi)) /* Neighboring Router is the DRouter or the BDRouter. */ || IPV4_ADDR_SAME(&nbr->address.u.prefix4, &DR(oi)) || IPV4_ADDR_SAME(&nbr->address.u.prefix4, &BDR(oi))) return 1; return 0; } /* OSPF NSM functions. */ static int nsm_packet_received(struct ospf_neighbor *nbr) { /* Start or Restart Inactivity Timer. */ OSPF_NSM_TIMER_OFF(nbr->t_inactivity); OSPF_NSM_TIMER_ON(nbr->t_inactivity, ospf_inactivity_timer, nbr->v_inactivity); if (nbr->oi->type == OSPF_IFTYPE_NBMA && nbr->nbr_nbma) OSPF_POLL_TIMER_OFF(nbr->nbr_nbma->t_poll); /* Send proactive ARP requests */ if (nbr->state < NSM_Exchange) ospf_proactively_arp(nbr); return 0; } static int nsm_start(struct ospf_neighbor *nbr) { if (nbr->nbr_nbma) OSPF_POLL_TIMER_OFF(nbr->nbr_nbma->t_poll); OSPF_NSM_TIMER_OFF(nbr->t_inactivity); OSPF_NSM_TIMER_ON(nbr->t_inactivity, ospf_inactivity_timer, nbr->v_inactivity); /* Send proactive ARP requests */ ospf_proactively_arp(nbr); return 0; } static int nsm_twoway_received(struct ospf_neighbor *nbr) { int adj = nsm_should_adj(nbr); /* Send proactive ARP requests */ if (adj) ospf_proactively_arp(nbr); return (adj ? NSM_ExStart : NSM_TwoWay); } int ospf_db_summary_count(struct ospf_neighbor *nbr) { return ospf_lsdb_count_all(&nbr->db_sum); } int ospf_db_summary_isempty(struct ospf_neighbor *nbr) { return ospf_lsdb_isempty(&nbr->db_sum); } static int ospf_db_summary_add(struct ospf_neighbor *nbr, struct ospf_lsa *lsa) { switch (lsa->data->type) { case OSPF_OPAQUE_LINK_LSA: /* Exclude type-9 LSAs that does not have the same "oi" with * "nbr". */ if (nbr->oi && ospf_if_exists(lsa->oi) != nbr->oi) return 0; break; case OSPF_OPAQUE_AREA_LSA: /* * It is assured by the caller function "nsm_negotiation_done()" * that every given LSA belongs to the same area with "nbr". */ break; case OSPF_OPAQUE_AS_LSA: default: break; } /* Stay away from any Local Translated Type-7 LSAs */ if (CHECK_FLAG(lsa->flags, OSPF_LSA_LOCAL_XLT)) return 0; if (IS_LSA_MAXAGE(lsa)) ospf_ls_retransmit_add(nbr, lsa); else ospf_lsdb_add(&nbr->db_sum, lsa); return 0; } void ospf_db_summary_clear(struct ospf_neighbor *nbr) { struct ospf_lsdb *lsdb; int i; lsdb = &nbr->db_sum; for (i = OSPF_MIN_LSA; i < OSPF_MAX_LSA; i++) { struct route_table *table = lsdb->type[i].db; struct route_node *rn; for (rn = route_top(table); rn; rn = route_next(rn)) if (rn->info) ospf_lsdb_delete(&nbr->db_sum, rn->info); } } /* The area link state database consists of the router-LSAs, network-LSAs and summary-LSAs contained in the area structure, along with the AS-external-LSAs contained in the global structure. AS-external-LSAs are omitted from a virtual neighbor's Database summary list. AS-external-LSAs are omitted from the Database summary list if the area has been configured as a stub. */ static int nsm_negotiation_done(struct ospf_neighbor *nbr) { struct ospf_area *area = nbr->oi->area; struct ospf_lsa *lsa; struct route_node *rn; /* Send proactive ARP requests */ ospf_proactively_arp(nbr); LSDB_LOOP (ROUTER_LSDB(area), rn, lsa) ospf_db_summary_add(nbr, lsa); LSDB_LOOP (NETWORK_LSDB(area), rn, lsa) ospf_db_summary_add(nbr, lsa); LSDB_LOOP (SUMMARY_LSDB(area), rn, lsa) ospf_db_summary_add(nbr, lsa); LSDB_LOOP (ASBR_SUMMARY_LSDB(area), rn, lsa) ospf_db_summary_add(nbr, lsa); /* Process only if the neighbor is opaque capable. */ if (CHECK_FLAG(nbr->options, OSPF_OPTION_O)) { LSDB_LOOP (OPAQUE_LINK_LSDB(area), rn, lsa) ospf_db_summary_add(nbr, lsa); LSDB_LOOP (OPAQUE_AREA_LSDB(area), rn, lsa) ospf_db_summary_add(nbr, lsa); } if (CHECK_FLAG(nbr->options, OSPF_OPTION_NP)) { LSDB_LOOP (NSSA_LSDB(area), rn, lsa) ospf_db_summary_add(nbr, lsa); } if (nbr->oi->type != OSPF_IFTYPE_VIRTUALLINK && area->external_routing == OSPF_AREA_DEFAULT) LSDB_LOOP (EXTERNAL_LSDB(nbr->oi->ospf), rn, lsa) ospf_db_summary_add(nbr, lsa); if (CHECK_FLAG(nbr->options, OSPF_OPTION_O) && (nbr->oi->type != OSPF_IFTYPE_VIRTUALLINK && area->external_routing == OSPF_AREA_DEFAULT)) LSDB_LOOP (OPAQUE_AS_LSDB(nbr->oi->ospf), rn, lsa) ospf_db_summary_add(nbr, lsa); return 0; } static int nsm_exchange_done(struct ospf_neighbor *nbr) { if (ospf_ls_request_isempty(nbr)) return NSM_Full; /* Send Link State Request. */ if (nbr->t_ls_req == NULL) ospf_ls_req_send(nbr); return NSM_Loading; } static int nsm_adj_ok(struct ospf_neighbor *nbr) { int next_state = nbr->state; int adj = nsm_should_adj(nbr); if (nbr->state == NSM_TwoWay && adj == 1) { next_state = NSM_ExStart; /* Send proactive ARP requests */ ospf_proactively_arp(nbr); } else if (nbr->state >= NSM_ExStart && adj == 0) next_state = NSM_TwoWay; return next_state; } /* Clear adjacency related state for a neighbour, intended where nbr * transitions from > ExStart (i.e. a Full or forming adjacency) * to <= ExStart. */ static void nsm_clear_adj(struct ospf_neighbor *nbr) { /* Clear Database Summary list. */ if (!ospf_db_summary_isempty(nbr)) ospf_db_summary_clear(nbr); /* Clear Link State Request list. */ if (!ospf_ls_request_isempty(nbr)) ospf_ls_request_delete_all(nbr); /* Clear Link State Retransmission list. */ if (!ospf_ls_retransmit_isempty(nbr)) ospf_ls_retransmit_clear(nbr); if (CHECK_FLAG(nbr->options, OSPF_OPTION_O)) UNSET_FLAG(nbr->options, OSPF_OPTION_O); } static int nsm_kill_nbr(struct ospf_neighbor *nbr) { /* killing nbr_self is invalid */ if (nbr == nbr->oi->nbr_self) { assert(nbr != nbr->oi->nbr_self); return 0; } if (nbr->oi->type == OSPF_IFTYPE_NBMA && nbr->nbr_nbma != NULL) { struct ospf_nbr_nbma *nbr_nbma = nbr->nbr_nbma; nbr_nbma->nbr = NULL; nbr_nbma->state_change = nbr->state_change; nbr->nbr_nbma = NULL; OSPF_POLL_TIMER_ON(nbr_nbma->t_poll, ospf_poll_timer, nbr_nbma->v_poll); if (IS_DEBUG_OSPF(nsm, NSM_EVENTS)) zlog_debug( "NSM[%s:%s]: Down (PollIntervalTimer scheduled)", IF_NAME(nbr->oi), inet_ntoa(nbr->address.u.prefix4)); } return 0; } /* Neighbor State Machine */ struct { int (*func)(struct ospf_neighbor *); int next_state; } NSM[OSPF_NSM_STATE_MAX][OSPF_NSM_EVENT_MAX] = { { /* DependUpon: dummy state. */ {NULL, NSM_DependUpon}, /* NoEvent */ {NULL, NSM_DependUpon}, /* PacketReceived */ {NULL, NSM_DependUpon}, /* Start */ {NULL, NSM_DependUpon}, /* 2-WayReceived */ {NULL, NSM_DependUpon}, /* NegotiationDone */ {NULL, NSM_DependUpon}, /* ExchangeDone */ {NULL, NSM_DependUpon}, /* BadLSReq */ {NULL, NSM_DependUpon}, /* LoadingDone */ {NULL, NSM_DependUpon}, /* AdjOK? */ {NULL, NSM_DependUpon}, /* SeqNumberMismatch */ {NULL, NSM_DependUpon}, /* 1-WayReceived */ {NULL, NSM_DependUpon}, /* KillNbr */ {NULL, NSM_DependUpon}, /* InactivityTimer */ {NULL, NSM_DependUpon}, /* LLDown */ }, { /* Deleted: dummy state. */ {NULL, NSM_Deleted}, /* NoEvent */ {NULL, NSM_Deleted}, /* PacketReceived */ {NULL, NSM_Deleted}, /* Start */ {NULL, NSM_Deleted}, /* 2-WayReceived */ {NULL, NSM_Deleted}, /* NegotiationDone */ {NULL, NSM_Deleted}, /* ExchangeDone */ {NULL, NSM_Deleted}, /* BadLSReq */ {NULL, NSM_Deleted}, /* LoadingDone */ {NULL, NSM_Deleted}, /* AdjOK? */ {NULL, NSM_Deleted}, /* SeqNumberMismatch */ {NULL, NSM_Deleted}, /* 1-WayReceived */ {NULL, NSM_Deleted}, /* KillNbr */ {NULL, NSM_Deleted}, /* InactivityTimer */ {NULL, NSM_Deleted}, /* LLDown */ }, { /* Down: */ {NULL, NSM_DependUpon}, /* NoEvent */ {nsm_packet_received, NSM_Init}, /* PacketReceived */ {nsm_start, NSM_Attempt}, /* Start */ {NULL, NSM_Down}, /* 2-WayReceived */ {NULL, NSM_Down}, /* NegotiationDone */ {NULL, NSM_Down}, /* ExchangeDone */ {NULL, NSM_Down}, /* BadLSReq */ {NULL, NSM_Down}, /* LoadingDone */ {NULL, NSM_Down}, /* AdjOK? */ {NULL, NSM_Down}, /* SeqNumberMismatch */ {NULL, NSM_Down}, /* 1-WayReceived */ {nsm_kill_nbr, NSM_Deleted}, /* KillNbr */ {nsm_kill_nbr, NSM_Deleted}, /* InactivityTimer */ {nsm_kill_nbr, NSM_Deleted}, /* LLDown */ }, { /* Attempt: */ {NULL, NSM_DependUpon}, /* NoEvent */ {nsm_packet_received, NSM_Init}, /* PacketReceived */ {NULL, NSM_Attempt}, /* Start */ {NULL, NSM_Attempt}, /* 2-WayReceived */ {NULL, NSM_Attempt}, /* NegotiationDone */ {NULL, NSM_Attempt}, /* ExchangeDone */ {NULL, NSM_Attempt}, /* BadLSReq */ {NULL, NSM_Attempt}, /* LoadingDone */ {NULL, NSM_Attempt}, /* AdjOK? */ {NULL, NSM_Attempt}, /* SeqNumberMismatch */ {NULL, NSM_Attempt}, /* 1-WayReceived */ {nsm_kill_nbr, NSM_Deleted}, /* KillNbr */ {nsm_kill_nbr, NSM_Deleted}, /* InactivityTimer */ {nsm_kill_nbr, NSM_Deleted}, /* LLDown */ }, { /* Init: */ {NULL, NSM_DependUpon}, /* NoEvent */ {nsm_packet_received, NSM_Init}, /* PacketReceived */ {NULL, NSM_Init}, /* Start */ {nsm_twoway_received, NSM_DependUpon}, /* 2-WayReceived */ {NULL, NSM_Init}, /* NegotiationDone */ {NULL, NSM_Init}, /* ExchangeDone */ {NULL, NSM_Init}, /* BadLSReq */ {NULL, NSM_Init}, /* LoadingDone */ {NULL, NSM_Init}, /* AdjOK? */ {NULL, NSM_Init}, /* SeqNumberMismatch */ {NULL, NSM_Init}, /* 1-WayReceived */ {nsm_kill_nbr, NSM_Deleted}, /* KillNbr */ {nsm_kill_nbr, NSM_Deleted}, /* InactivityTimer */ {nsm_kill_nbr, NSM_Deleted}, /* LLDown */ }, { /* 2-Way: */ {NULL, NSM_DependUpon}, /* NoEvent */ {nsm_packet_received, NSM_TwoWay}, /* HelloReceived */ {NULL, NSM_TwoWay}, /* Start */ {NULL, NSM_TwoWay}, /* 2-WayReceived */ {NULL, NSM_TwoWay}, /* NegotiationDone */ {NULL, NSM_TwoWay}, /* ExchangeDone */ {NULL, NSM_TwoWay}, /* BadLSReq */ {NULL, NSM_TwoWay}, /* LoadingDone */ {nsm_adj_ok, NSM_DependUpon}, /* AdjOK? */ {NULL, NSM_TwoWay}, /* SeqNumberMismatch */ {NULL, NSM_Init}, /* 1-WayReceived */ {nsm_kill_nbr, NSM_Deleted}, /* KillNbr */ {nsm_kill_nbr, NSM_Deleted}, /* InactivityTimer */ {nsm_kill_nbr, NSM_Deleted}, /* LLDown */ }, { /* ExStart: */ {NULL, NSM_DependUpon}, /* NoEvent */ {nsm_packet_received, NSM_ExStart}, /* PacaketReceived */ {NULL, NSM_ExStart}, /* Start */ {NULL, NSM_ExStart}, /* 2-WayReceived */ {nsm_negotiation_done, NSM_Exchange}, /* NegotiationDone */ {NULL, NSM_ExStart}, /* ExchangeDone */ {NULL, NSM_ExStart}, /* BadLSReq */ {NULL, NSM_ExStart}, /* LoadingDone */ {nsm_adj_ok, NSM_DependUpon}, /* AdjOK? */ {NULL, NSM_ExStart}, /* SeqNumberMismatch */ {NULL, NSM_Init}, /* 1-WayReceived */ {nsm_kill_nbr, NSM_Deleted}, /* KillNbr */ {nsm_kill_nbr, NSM_Deleted}, /* InactivityTimer */ {nsm_kill_nbr, NSM_Deleted}, /* LLDown */ }, { /* Exchange: */ {NULL, NSM_DependUpon}, /* NoEvent */ {nsm_packet_received, NSM_Exchange}, /* PacketReceived */ {NULL, NSM_Exchange}, /* Start */ {NULL, NSM_Exchange}, /* 2-WayReceived */ {NULL, NSM_Exchange}, /* NegotiationDone */ {nsm_exchange_done, NSM_DependUpon}, /* ExchangeDone */ {NULL, NSM_ExStart}, /* BadLSReq */ {NULL, NSM_Exchange}, /* LoadingDone */ {nsm_adj_ok, NSM_DependUpon}, /* AdjOK? */ {NULL, NSM_ExStart}, /* SeqNumberMismatch */ {NULL, NSM_Init}, /* 1-WayReceived */ {nsm_kill_nbr, NSM_Deleted}, /* KillNbr */ {nsm_kill_nbr, NSM_Deleted}, /* InactivityTimer */ {nsm_kill_nbr, NSM_Deleted}, /* LLDown */ }, { /* Loading: */ {NULL, NSM_DependUpon}, /* NoEvent */ {nsm_packet_received, NSM_Loading}, /* PacketReceived */ {NULL, NSM_Loading}, /* Start */ {NULL, NSM_Loading}, /* 2-WayReceived */ {NULL, NSM_Loading}, /* NegotiationDone */ {NULL, NSM_Loading}, /* ExchangeDone */ {NULL, NSM_ExStart}, /* BadLSReq */ {NULL, NSM_Full}, /* LoadingDone */ {nsm_adj_ok, NSM_DependUpon}, /* AdjOK? */ {NULL, NSM_ExStart}, /* SeqNumberMismatch */ {NULL, NSM_Init}, /* 1-WayReceived */ {nsm_kill_nbr, NSM_Deleted}, /* KillNbr */ {nsm_kill_nbr, NSM_Deleted}, /* InactivityTimer */ {nsm_kill_nbr, NSM_Deleted}, /* LLDown */ }, { /* Full: */ {NULL, NSM_DependUpon}, /* NoEvent */ {nsm_packet_received, NSM_Full}, /* PacketReceived */ {NULL, NSM_Full}, /* Start */ {NULL, NSM_Full}, /* 2-WayReceived */ {NULL, NSM_Full}, /* NegotiationDone */ {NULL, NSM_Full}, /* ExchangeDone */ {NULL, NSM_ExStart}, /* BadLSReq */ {NULL, NSM_Full}, /* LoadingDone */ {nsm_adj_ok, NSM_DependUpon}, /* AdjOK? */ {NULL, NSM_ExStart}, /* SeqNumberMismatch */ {NULL, NSM_Init}, /* 1-WayReceived */ {nsm_kill_nbr, NSM_Deleted}, /* KillNbr */ {nsm_kill_nbr, NSM_Deleted}, /* InactivityTimer */ {nsm_kill_nbr, NSM_Deleted}, /* LLDown */ }, }; static const char *ospf_nsm_event_str[] = { "NoEvent", "PacketReceived", "Start", "2-WayReceived", "NegotiationDone", "ExchangeDone", "BadLSReq", "LoadingDone", "AdjOK?", "SeqNumberMismatch", "1-WayReceived", "KillNbr", "InactivityTimer", "LLDown", }; static void nsm_notice_state_change(struct ospf_neighbor *nbr, int next_state, int event) { /* Logging change of status. */ if (IS_DEBUG_OSPF(nsm, NSM_STATUS)) zlog_debug("NSM[%s:%s]: State change %s -> %s (%s)", IF_NAME(nbr->oi), inet_ntoa(nbr->router_id), lookup_msg(ospf_nsm_state_msg, nbr->state, NULL), lookup_msg(ospf_nsm_state_msg, next_state, NULL), ospf_nsm_event_str[event]); /* Optionally notify about adjacency changes */ if (CHECK_FLAG(nbr->oi->ospf->config, OSPF_LOG_ADJACENCY_CHANGES) && (CHECK_FLAG(nbr->oi->ospf->config, OSPF_LOG_ADJACENCY_DETAIL) || (next_state == NSM_Full) || (next_state < nbr->state))) zlog_notice("AdjChg: Nbr %s on %s: %s -> %s (%s)", inet_ntoa(nbr->router_id), IF_NAME(nbr->oi), lookup_msg(ospf_nsm_state_msg, nbr->state, NULL), lookup_msg(ospf_nsm_state_msg, next_state, NULL), ospf_nsm_event_str[event]); /* Advance in NSM */ if (next_state > nbr->state) monotime(&nbr->ts_last_progress); else /* regression in NSM */ { monotime(&nbr->ts_last_regress); nbr->last_regress_str = ospf_nsm_event_str[event]; } } static void nsm_change_state(struct ospf_neighbor *nbr, int state) { struct ospf_interface *oi = nbr->oi; struct ospf_area *vl_area = NULL; uint8_t old_state; /* Preserve old status. */ old_state = nbr->state; /* Change to new status. */ nbr->state = state; /* Statistics. */ nbr->state_change++; if (oi->type == OSPF_IFTYPE_VIRTUALLINK) vl_area = ospf_area_lookup_by_area_id(oi->ospf, oi->vl_data->vl_area_id); /* Generate NeighborChange ISM event. * * In response to NeighborChange, DR election is rerun. The information * from the election process is required by the router-lsa construction. * * Therefore, trigger the event prior to refreshing the LSAs. */ switch (oi->state) { case ISM_DROther: case ISM_Backup: case ISM_DR: if ((old_state < NSM_TwoWay && state >= NSM_TwoWay) || (old_state >= NSM_TwoWay && state < NSM_TwoWay)) OSPF_ISM_EVENT_EXECUTE(oi, ISM_NeighborChange); break; default: /* ISM_PointToPoint -> ISM_Down, ISM_Loopback -> ISM_Down, etc. */ break; } /* One of the neighboring routers changes to/from the FULL state. */ if ((old_state != NSM_Full && state == NSM_Full) || (old_state == NSM_Full && state != NSM_Full)) { if (state == NSM_Full) { oi->full_nbrs++; oi->area->full_nbrs++; ospf_check_abr_status(oi->ospf); if (oi->type == OSPF_IFTYPE_VIRTUALLINK && vl_area) if (++vl_area->full_vls == 1) ospf_schedule_abr_task(oi->ospf); } else { oi->full_nbrs--; oi->area->full_nbrs--; ospf_check_abr_status(oi->ospf); if (oi->type == OSPF_IFTYPE_VIRTUALLINK && vl_area) if (vl_area->full_vls > 0) if (--vl_area->full_vls == 0) ospf_schedule_abr_task( oi->ospf); } if (CHECK_FLAG(oi->ospf->config, OSPF_LOG_ADJACENCY_DETAIL)) zlog_info( "%s:(%s, %s -> %s): " "scheduling new router-LSA origination", __PRETTY_FUNCTION__, inet_ntoa(nbr->router_id), lookup_msg(ospf_nsm_state_msg, old_state, NULL), lookup_msg(ospf_nsm_state_msg, state, NULL)); ospf_router_lsa_update_area(oi->area); if (oi->type == OSPF_IFTYPE_VIRTUALLINK) { vl_area = ospf_area_lookup_by_area_id( oi->ospf, oi->vl_data->vl_area_id); if (vl_area) ospf_router_lsa_update_area(vl_area); } /* Originate network-LSA. */ if (oi->state == ISM_DR) { if (oi->network_lsa_self && oi->full_nbrs == 0) { ospf_lsa_flush_area(oi->network_lsa_self, oi->area); ospf_lsa_unlock(&oi->network_lsa_self); oi->network_lsa_self = NULL; } else ospf_network_lsa_update(oi); } } ospf_opaque_nsm_change(nbr, old_state); /* State changes from > ExStart to <= ExStart should clear any Exchange * or Full/LSA Update related lists and state. * Potential causal events: BadLSReq, SeqNumberMismatch, AdjOK? */ if ((old_state > NSM_ExStart) && (state <= NSM_ExStart)) nsm_clear_adj(nbr); /* Start DD exchange protocol */ if (state == NSM_ExStart) { if (nbr->dd_seqnum == 0) nbr->dd_seqnum = (uint32_t)random(); else nbr->dd_seqnum++; nbr->dd_flags = OSPF_DD_FLAG_I | OSPF_DD_FLAG_M | OSPF_DD_FLAG_MS; ospf_db_desc_send(nbr); } /* clear cryptographic sequence number */ if (state == NSM_Down) nbr->crypt_seqnum = 0; ospf_bfd_trigger_event(nbr, old_state, state); /* Preserve old status? */ } /* Execute NSM event process. */ int ospf_nsm_event(struct thread *thread) { int event; int next_state; struct ospf_neighbor *nbr; nbr = THREAD_ARG(thread); event = THREAD_VAL(thread); if (IS_DEBUG_OSPF(nsm, NSM_EVENTS)) zlog_debug("NSM[%s:%s]: %s (%s)", IF_NAME(nbr->oi), inet_ntoa(nbr->router_id), lookup_msg(ospf_nsm_state_msg, nbr->state, NULL), ospf_nsm_event_str[event]); next_state = NSM[nbr->state][event].next_state; /* Call function. */ if (NSM[nbr->state][event].func != NULL) { int func_state = (*(NSM[nbr->state][event].func))(nbr); if (NSM[nbr->state][event].next_state == NSM_DependUpon) next_state = func_state; else if (func_state) { /* There's a mismatch between the FSM tables and what an * FSM * action/state-change function returned. State changes * which * do not have conditional/DependUpon next-states should * not * try set next_state. */ flog_err( EC_OSPF_FSM_INVALID_STATE, "NSM[%s:%s]: %s (%s): " "Warning: action tried to change next_state to %s", IF_NAME(nbr->oi), inet_ntoa(nbr->router_id), lookup_msg(ospf_nsm_state_msg, nbr->state, NULL), ospf_nsm_event_str[event], lookup_msg(ospf_nsm_state_msg, func_state, NULL)); } } assert(next_state != NSM_DependUpon); /* If state is changed. */ if (next_state != nbr->state) { int old_state = nbr->state; nsm_notice_state_change(nbr, next_state, event); nsm_change_state(nbr, next_state); hook_call(ospf_nsm_change, nbr, next_state, old_state); } /* Make sure timer is set. */ nsm_timer_set(nbr); /* When event is NSM_KillNbr, InactivityTimer or LLDown, the neighbor * is deleted. * * Rather than encode knowledge here of which events lead to NBR * delete, we take our cue from the NSM table, via the dummy * 'Deleted' neighbour state. */ if (nbr->state == NSM_Deleted) ospf_nbr_delete(nbr); return 0; } /* Check loading state. */ void ospf_check_nbr_loading(struct ospf_neighbor *nbr) { if (nbr->state == NSM_Loading) { if (ospf_ls_request_isempty(nbr)) OSPF_NSM_EVENT_SCHEDULE(nbr, NSM_LoadingDone); else if (nbr->ls_req_last == NULL) ospf_ls_req_event(nbr); } } frr-7.2.1/ospfd/ospf_nsm.h0000644000000000000000000000612513610377563012330 00000000000000/* * OSPF version 2 Neighbor State Machine * From RFC2328 [OSPF Version 2] * Copyright (C) 1999 Toshiaki Takada * * This file is part of GNU Zebra. * * GNU Zebra is free software; you can redistribute it and/or modify it * under the terms of the GNU General Public License as published by the * Free Software Foundation; either version 2, or (at your option) any * later version. * * GNU Zebra 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; see the file COPYING; if not, write to the Free Software * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA */ #ifndef _ZEBRA_OSPF_NSM_H #define _ZEBRA_OSPF_NSM_H #include "hook.h" /* OSPF Neighbor State Machine State. */ #define NSM_DependUpon 0 #define NSM_Deleted 1 #define NSM_Down 2 #define NSM_Attempt 3 #define NSM_Init 4 #define NSM_TwoWay 5 #define NSM_ExStart 6 #define NSM_Exchange 7 #define NSM_Loading 8 #define NSM_Full 9 #define OSPF_NSM_STATE_MAX 10 /* OSPF Neighbor State Machine Event. */ #define NSM_NoEvent 0 #define NSM_PacketReceived 1 /* HelloReceived in the protocol */ #define NSM_Start 2 #define NSM_TwoWayReceived 3 #define NSM_NegotiationDone 4 #define NSM_ExchangeDone 5 #define NSM_BadLSReq 6 #define NSM_LoadingDone 7 #define NSM_AdjOK 8 #define NSM_SeqNumberMismatch 9 #define NSM_OneWayReceived 10 #define NSM_KillNbr 11 #define NSM_InactivityTimer 12 #define NSM_LLDown 13 #define OSPF_NSM_EVENT_MAX 14 /* Macro for OSPF NSM timer turn on. */ #define OSPF_NSM_TIMER_ON(T,F,V) thread_add_timer (master, (F), nbr, (V), &(T)) /* Macro for OSPF NSM timer turn off. */ #define OSPF_NSM_TIMER_OFF(X) \ do { \ if (X) { \ thread_cancel(X); \ (X) = NULL; \ } \ } while (0) /* Macro for OSPF NSM schedule event. */ #define OSPF_NSM_EVENT_SCHEDULE(N, E) \ thread_add_event(master, ospf_nsm_event, (N), (E), NULL) /* Macro for OSPF NSM execute event. */ #define OSPF_NSM_EVENT_EXECUTE(N, E) \ thread_execute(master, ospf_nsm_event, (N), (E)) /* Prototypes. */ extern int ospf_nsm_event(struct thread *); extern void ospf_check_nbr_loading(struct ospf_neighbor *); extern int ospf_db_summary_isempty(struct ospf_neighbor *); extern int ospf_db_summary_count(struct ospf_neighbor *); extern void ospf_db_summary_clear(struct ospf_neighbor *); DECLARE_HOOK(ospf_nsm_change, (struct ospf_neighbor * on, int state, int oldstate), (on, state, oldstate)) #endif /* _ZEBRA_OSPF_NSM_H */ frr-7.2.1/ospfd/ospf_opaque.c0000644000000000000000000016464113610377563013030 00000000000000/* * This is an implementation of rfc2370. * Copyright (C) 2001 KDD R&D Laboratories, Inc. * http://www.kddlabs.co.jp/ * * This file is part of GNU Zebra. * * GNU Zebra is free software; you can redistribute it and/or modify it * under the terms of the GNU General Public License as published by the * Free Software Foundation; either version 2, or (at your option) any * later version. * * GNU Zebra 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; see the file COPYING; if not, write to the Free Software * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA */ #include #include "linklist.h" #include "prefix.h" #include "if.h" #include "table.h" #include "memory.h" #include "command.h" #include "vty.h" #include "stream.h" #include "log.h" #include "thread.h" #include "hash.h" #include "sockunion.h" /* for inet_aton() */ #include "ospfd/ospfd.h" #include "ospfd/ospf_interface.h" #include "ospfd/ospf_ism.h" #include "ospfd/ospf_asbr.h" #include "ospfd/ospf_lsa.h" #include "ospfd/ospf_lsdb.h" #include "ospfd/ospf_neighbor.h" #include "ospfd/ospf_nsm.h" #include "ospfd/ospf_flood.h" #include "ospfd/ospf_packet.h" #include "ospfd/ospf_spf.h" #include "ospfd/ospf_dump.h" #include "ospfd/ospf_route.h" #include "ospfd/ospf_ase.h" #include "ospfd/ospf_zebra.h" #include "ospfd/ospf_te.h" #include "ospfd/ospf_sr.h" #include "ospfd/ospf_ri.h" #include "ospfd/ospf_ext.h" #include "ospfd/ospf_errors.h" DEFINE_MTYPE_STATIC(OSPFD, OSPF_OPAQUE_FUNCTAB, "OSPF opaque function table") DEFINE_MTYPE_STATIC(OSPFD, OPAQUE_INFO_PER_TYPE, "OSPF opaque per-type info") DEFINE_MTYPE_STATIC(OSPFD, OPAQUE_INFO_PER_ID, "OSPF opaque per-ID info") /*------------------------------------------------------------------------* * Followings are initialize/terminate functions for Opaque-LSAs handling. *------------------------------------------------------------------------*/ #ifdef SUPPORT_OSPF_API int ospf_apiserver_init(void); void ospf_apiserver_term(void); /* Init apiserver? It's disabled by default. */ int ospf_apiserver_enable; #endif /* SUPPORT_OSPF_API */ static void ospf_opaque_register_vty(void); static void ospf_opaque_funclist_init(void); static void ospf_opaque_funclist_term(void); static void free_opaque_info_per_type(void *val); static void free_opaque_info_per_id(void *val); static void free_opaque_info_owner(void *val); static int ospf_opaque_lsa_install_hook(struct ospf_lsa *lsa); static int ospf_opaque_lsa_delete_hook(struct ospf_lsa *lsa); void ospf_opaque_init(void) { ospf_opaque_register_vty(); ospf_opaque_funclist_init(); if (ospf_mpls_te_init() != 0) exit(1); /* Segment Routing init */ if (ospf_sr_init() != 0) exit(1); if (ospf_router_info_init() != 0) exit(1); if (ospf_ext_init() != 0) exit(1); #ifdef SUPPORT_OSPF_API if ((ospf_apiserver_enable) && (ospf_apiserver_init() != 0)) exit(1); #endif /* SUPPORT_OSPF_API */ return; } void ospf_opaque_term(void) { ospf_mpls_te_term(); ospf_router_info_term(); ospf_ext_term(); ospf_sr_term(); #ifdef SUPPORT_OSPF_API ospf_apiserver_term(); #endif /* SUPPORT_OSPF_API */ ospf_opaque_funclist_term(); return; } void ospf_opaque_finish(void) { ospf_router_info_finish(); ospf_ext_finish(); ospf_sr_finish(); } int ospf_opaque_type9_lsa_init(struct ospf_interface *oi) { if (oi->opaque_lsa_self != NULL) list_delete(&oi->opaque_lsa_self); oi->opaque_lsa_self = list_new(); oi->opaque_lsa_self->del = free_opaque_info_per_type; oi->t_opaque_lsa_self = NULL; return 0; } void ospf_opaque_type9_lsa_term(struct ospf_interface *oi) { OSPF_TIMER_OFF(oi->t_opaque_lsa_self); if (oi->opaque_lsa_self != NULL) list_delete(&oi->opaque_lsa_self); oi->opaque_lsa_self = NULL; return; } int ospf_opaque_type10_lsa_init(struct ospf_area *area) { if (area->opaque_lsa_self != NULL) list_delete(&area->opaque_lsa_self); area->opaque_lsa_self = list_new(); area->opaque_lsa_self->del = free_opaque_info_per_type; area->t_opaque_lsa_self = NULL; #ifdef MONITOR_LSDB_CHANGE area->lsdb->new_lsa_hook = ospf_opaque_lsa_install_hook; area->lsdb->del_lsa_hook = ospf_opaque_lsa_delete_hook; #endif /* MONITOR_LSDB_CHANGE */ return 0; } void ospf_opaque_type10_lsa_term(struct ospf_area *area) { #ifdef MONITOR_LSDB_CHANGE area->lsdb->new_lsa_hook = area->lsdb->del_lsa_hook = NULL; #endif /* MONITOR_LSDB_CHANGE */ OSPF_TIMER_OFF(area->t_opaque_lsa_self); if (area->opaque_lsa_self != NULL) list_delete(&area->opaque_lsa_self); return; } int ospf_opaque_type11_lsa_init(struct ospf *top) { if (top->opaque_lsa_self != NULL) list_delete(&top->opaque_lsa_self); top->opaque_lsa_self = list_new(); top->opaque_lsa_self->del = free_opaque_info_per_type; top->t_opaque_lsa_self = NULL; #ifdef MONITOR_LSDB_CHANGE top->lsdb->new_lsa_hook = ospf_opaque_lsa_install_hook; top->lsdb->del_lsa_hook = ospf_opaque_lsa_delete_hook; #endif /* MONITOR_LSDB_CHANGE */ return 0; } void ospf_opaque_type11_lsa_term(struct ospf *top) { #ifdef MONITOR_LSDB_CHANGE top->lsdb->new_lsa_hook = top->lsdb->del_lsa_hook = NULL; #endif /* MONITOR_LSDB_CHANGE */ OSPF_TIMER_OFF(top->t_opaque_lsa_self); if (top->opaque_lsa_self != NULL) list_delete(&top->opaque_lsa_self); return; } static const char *ospf_opaque_type_name(uint8_t opaque_type) { const char *name = "Unknown"; switch (opaque_type) { case OPAQUE_TYPE_WILDCARD: /* This is a special assignment! */ name = "Wildcard"; break; case OPAQUE_TYPE_TRAFFIC_ENGINEERING_LSA: name = "Traffic Engineering LSA"; break; case OPAQUE_TYPE_SYCAMORE_OPTICAL_TOPOLOGY_DESC: name = "Sycamore optical topology description"; break; case OPAQUE_TYPE_GRACE_LSA: name = "Grace-LSA"; break; case OPAQUE_TYPE_INTER_AS_LSA: name = "Inter-AS TE-v2 LSA"; break; case OPAQUE_TYPE_ROUTER_INFORMATION_LSA: name = "Router Information LSA"; break; case OPAQUE_TYPE_EXTENDED_PREFIX_LSA: name = "Extended Prefix Opaque LSA"; break; case OPAQUE_TYPE_EXTENDED_LINK_LSA: name = "Extended Link Opaque LSA"; break; default: if (OPAQUE_TYPE_RANGE_UNASSIGNED(opaque_type)) name = "Unassigned"; else { uint32_t bigger_range = opaque_type; /* * Get around type-limits warning: comparison is always * true due to limited range of data type */ if (OPAQUE_TYPE_RANGE_RESERVED(bigger_range)) name = "Private/Experimental"; } break; } return name; } /*------------------------------------------------------------------------* * Followings are management functions to store user specified callbacks. *------------------------------------------------------------------------*/ struct opaque_info_per_type; /* Forward declaration. */ struct ospf_opaque_functab { uint8_t opaque_type; struct opaque_info_per_type *oipt; int (*new_if_hook)(struct interface *ifp); int (*del_if_hook)(struct interface *ifp); void (*ism_change_hook)(struct ospf_interface *oi, int old_status); void (*nsm_change_hook)(struct ospf_neighbor *nbr, int old_status); void (*config_write_router)(struct vty *vty); void (*config_write_if)(struct vty *vty, struct interface *ifp); void (*config_write_debug)(struct vty *vty); void (*show_opaque_info)(struct vty *vty, struct ospf_lsa *lsa); int (*lsa_originator)(void *arg); struct ospf_lsa *(*lsa_refresher)(struct ospf_lsa *lsa); int (*new_lsa_hook)(struct ospf_lsa *lsa); int (*del_lsa_hook)(struct ospf_lsa *lsa); }; /* Handle LSA-9/10/11 altogether. */ static struct list *ospf_opaque_wildcard_funclist; static struct list *ospf_opaque_type9_funclist; static struct list *ospf_opaque_type10_funclist; static struct list *ospf_opaque_type11_funclist; static void ospf_opaque_del_functab(void *val) { XFREE(MTYPE_OSPF_OPAQUE_FUNCTAB, val); return; } static void ospf_opaque_funclist_init(void) { struct list *funclist; funclist = ospf_opaque_wildcard_funclist = list_new(); funclist->del = ospf_opaque_del_functab; funclist = ospf_opaque_type9_funclist = list_new(); funclist->del = ospf_opaque_del_functab; funclist = ospf_opaque_type10_funclist = list_new(); funclist->del = ospf_opaque_del_functab; funclist = ospf_opaque_type11_funclist = list_new(); funclist->del = ospf_opaque_del_functab; return; } static void ospf_opaque_funclist_term(void) { struct list *funclist; funclist = ospf_opaque_wildcard_funclist; list_delete(&funclist); funclist = ospf_opaque_type9_funclist; list_delete(&funclist); funclist = ospf_opaque_type10_funclist; list_delete(&funclist); funclist = ospf_opaque_type11_funclist; list_delete(&funclist); return; } static struct list *ospf_get_opaque_funclist(uint8_t lsa_type) { struct list *funclist = NULL; switch (lsa_type) { case OPAQUE_TYPE_WILDCARD: /* XXX * This is an ugly trick to handle type-9/10/11 LSA altogether. * Yes, "OPAQUE_TYPE_WILDCARD (value 0)" is not an LSA-type, nor * an officially assigned opaque-type. * Though it is possible that the value might be officially used * in the future, we use it internally as a special label, for * now. */ funclist = ospf_opaque_wildcard_funclist; break; case OSPF_OPAQUE_LINK_LSA: funclist = ospf_opaque_type9_funclist; break; case OSPF_OPAQUE_AREA_LSA: funclist = ospf_opaque_type10_funclist; break; case OSPF_OPAQUE_AS_LSA: funclist = ospf_opaque_type11_funclist; break; default: flog_warn(EC_OSPF_LSA_UNEXPECTED, "ospf_get_opaque_funclist: Unexpected LSA-type(%u)", lsa_type); break; } return funclist; } /* XXX: such a huge argument list can /not/ be healthy... */ int ospf_register_opaque_functab( uint8_t lsa_type, uint8_t opaque_type, int (*new_if_hook)(struct interface *ifp), int (*del_if_hook)(struct interface *ifp), void (*ism_change_hook)(struct ospf_interface *oi, int old_status), void (*nsm_change_hook)(struct ospf_neighbor *nbr, int old_status), void (*config_write_router)(struct vty *vty), void (*config_write_if)(struct vty *vty, struct interface *ifp), void (*config_write_debug)(struct vty *vty), void (*show_opaque_info)(struct vty *vty, struct ospf_lsa *lsa), int (*lsa_originator)(void *arg), struct ospf_lsa *(*lsa_refresher)(struct ospf_lsa *lsa), int (*new_lsa_hook)(struct ospf_lsa *lsa), int (*del_lsa_hook)(struct ospf_lsa *lsa)) { struct list *funclist; struct ospf_opaque_functab *new; if ((funclist = ospf_get_opaque_funclist(lsa_type)) == NULL) return -1; struct listnode *node, *nnode; struct ospf_opaque_functab *functab; for (ALL_LIST_ELEMENTS(funclist, node, nnode, functab)) if (functab->opaque_type == opaque_type) { flog_warn( EC_OSPF_LSA, "ospf_register_opaque_functab: Duplicated entry?: lsa_type(%u), opaque_type(%u)", lsa_type, opaque_type); return -1; } new = XCALLOC(MTYPE_OSPF_OPAQUE_FUNCTAB, sizeof(struct ospf_opaque_functab)); new->opaque_type = opaque_type; new->oipt = NULL; new->new_if_hook = new_if_hook; new->del_if_hook = del_if_hook; new->ism_change_hook = ism_change_hook; new->nsm_change_hook = nsm_change_hook; new->config_write_router = config_write_router; new->config_write_if = config_write_if; new->config_write_debug = config_write_debug; new->show_opaque_info = show_opaque_info; new->lsa_originator = lsa_originator; new->lsa_refresher = lsa_refresher; new->new_lsa_hook = new_lsa_hook; new->del_lsa_hook = del_lsa_hook; listnode_add(funclist, new); return 0; } void ospf_delete_opaque_functab(uint8_t lsa_type, uint8_t opaque_type) { struct list *funclist; struct listnode *node, *nnode; struct ospf_opaque_functab *functab; if ((funclist = ospf_get_opaque_funclist(lsa_type)) != NULL) for (ALL_LIST_ELEMENTS(funclist, node, nnode, functab)) { if (functab->opaque_type == opaque_type) { /* Cleanup internal control information, if it * still remains. */ if (functab->oipt != NULL) { free_opaque_info_per_type( functab->oipt); free_opaque_info_owner(functab->oipt); } /* Dequeue listnode entry from the list. */ listnode_delete(funclist, functab); XFREE(MTYPE_OSPF_OPAQUE_FUNCTAB, functab); break; } } return; } static struct ospf_opaque_functab * ospf_opaque_functab_lookup(struct ospf_lsa *lsa) { struct list *funclist; struct listnode *node; struct ospf_opaque_functab *functab; uint8_t key = GET_OPAQUE_TYPE(ntohl(lsa->data->id.s_addr)); if ((funclist = ospf_get_opaque_funclist(lsa->data->type)) != NULL) for (ALL_LIST_ELEMENTS_RO(funclist, node, functab)) if (functab->opaque_type == key) return functab; return NULL; } /*------------------------------------------------------------------------* * Followings are management functions for self-originated LSA entries. *------------------------------------------------------------------------*/ /* * Opaque-LSA control information per opaque-type. * Single Opaque-Type may have multiple instances; each of them will be * identified by their opaque-id. */ struct opaque_info_per_type { uint8_t lsa_type; uint8_t opaque_type; enum { PROC_NORMAL, PROC_SUSPEND } status; /* * Thread for (re-)origination scheduling for this opaque-type. * * Initial origination of Opaque-LSAs is controlled by generic * Opaque-LSA handling module so that same opaque-type entries are * called all at once when certain conditions are met. * However, there might be cases that some Opaque-LSA clients need * to (re-)originate their own Opaque-LSAs out-of-sync with others. * This thread is prepared for that specific purpose. */ struct thread *t_opaque_lsa_self; /* * Backpointer to an "owner" which is LSA-type dependent. * type-9: struct ospf_interface * type-10: struct ospf_area * type-11: struct ospf */ void *owner; /* Collection of callback functions for this opaque-type. */ struct ospf_opaque_functab *functab; /* List of Opaque-LSA control information per opaque-id. */ struct list *id_list; }; /* Opaque-LSA control information per opaque-id. */ struct opaque_info_per_id { uint32_t opaque_id; /* Thread for refresh/flush scheduling for this opaque-type/id. */ struct thread *t_opaque_lsa_self; /* Backpointer to Opaque-LSA control information per opaque-type. */ struct opaque_info_per_type *opqctl_type; /* Here comes an actual Opaque-LSA entry for this opaque-type/id. */ struct ospf_lsa *lsa; }; static struct opaque_info_per_type * register_opaque_info_per_type(struct ospf_opaque_functab *functab, struct ospf_lsa *new); static struct opaque_info_per_type * lookup_opaque_info_by_type(struct ospf_lsa *lsa); static struct opaque_info_per_id * register_opaque_info_per_id(struct opaque_info_per_type *oipt, struct ospf_lsa *new); static struct opaque_info_per_id * lookup_opaque_info_by_id(struct opaque_info_per_type *oipt, struct ospf_lsa *lsa); static struct opaque_info_per_id *register_opaque_lsa(struct ospf_lsa *new); static struct opaque_info_per_type * register_opaque_info_per_type(struct ospf_opaque_functab *functab, struct ospf_lsa *new) { struct ospf *top; struct opaque_info_per_type *oipt; oipt = XCALLOC(MTYPE_OPAQUE_INFO_PER_TYPE, sizeof(struct opaque_info_per_type)); switch (new->data->type) { case OSPF_OPAQUE_LINK_LSA: oipt->owner = new->oi; listnode_add(new->oi->opaque_lsa_self, oipt); break; case OSPF_OPAQUE_AREA_LSA: oipt->owner = new->area; listnode_add(new->area->opaque_lsa_self, oipt); break; case OSPF_OPAQUE_AS_LSA: top = ospf_lookup_by_vrf_id(new->vrf_id); if (new->area != NULL && (top = new->area->ospf) == NULL) { free_opaque_info_per_type((void *)oipt); free_opaque_info_owner(oipt); oipt = NULL; goto out; /* This case may not exist. */ } oipt->owner = top; listnode_add(top->opaque_lsa_self, oipt); break; default: flog_warn( EC_OSPF_LSA_UNEXPECTED, "register_opaque_info_per_type: Unexpected LSA-type(%u)", new->data->type); free_opaque_info_per_type((void *)oipt); free_opaque_info_owner(oipt); oipt = NULL; goto out; /* This case may not exist. */ } oipt->lsa_type = new->data->type; oipt->opaque_type = GET_OPAQUE_TYPE(ntohl(new->data->id.s_addr)); oipt->status = PROC_NORMAL; oipt->t_opaque_lsa_self = NULL; oipt->functab = functab; functab->oipt = oipt; oipt->id_list = list_new(); oipt->id_list->del = free_opaque_info_per_id; out: return oipt; } /* Remove "oipt" from its owner's self-originated LSA list. */ static void free_opaque_info_owner(void *val) { struct opaque_info_per_type *oipt = (struct opaque_info_per_type *)val; switch (oipt->lsa_type) { case OSPF_OPAQUE_LINK_LSA: { struct ospf_interface *oi = (struct ospf_interface *)(oipt->owner); listnode_delete(oi->opaque_lsa_self, oipt); break; } case OSPF_OPAQUE_AREA_LSA: { struct ospf_area *area = (struct ospf_area *)(oipt->owner); listnode_delete(area->opaque_lsa_self, oipt); break; } case OSPF_OPAQUE_AS_LSA: { struct ospf *top = (struct ospf *)(oipt->owner); listnode_delete(top->opaque_lsa_self, oipt); break; } default: flog_warn(EC_OSPF_LSA_UNEXPECTED, "free_opaque_info_owner: Unexpected LSA-type(%u)", oipt->lsa_type); break; /* This case may not exist. */ } } static void free_opaque_info_per_type(void *val) { struct opaque_info_per_type *oipt = (struct opaque_info_per_type *)val; struct opaque_info_per_id *oipi; struct ospf_lsa *lsa; struct listnode *node, *nnode; /* Control information per opaque-id may still exist. */ for (ALL_LIST_ELEMENTS(oipt->id_list, node, nnode, oipi)) { if ((lsa = oipi->lsa) == NULL) continue; if (IS_LSA_MAXAGE(lsa)) continue; ospf_opaque_lsa_flush_schedule(lsa); } OSPF_TIMER_OFF(oipt->t_opaque_lsa_self); list_delete(&oipt->id_list); XFREE(MTYPE_OPAQUE_INFO_PER_TYPE, oipt); return; } static struct opaque_info_per_type * lookup_opaque_info_by_type(struct ospf_lsa *lsa) { struct ospf *top; struct ospf_area *area; struct ospf_interface *oi; struct list *listtop = NULL; struct listnode *node, *nnode; struct opaque_info_per_type *oipt = NULL; uint8_t key = GET_OPAQUE_TYPE(ntohl(lsa->data->id.s_addr)); switch (lsa->data->type) { case OSPF_OPAQUE_LINK_LSA: if ((oi = lsa->oi) != NULL) listtop = oi->opaque_lsa_self; else flog_warn( EC_OSPF_LSA, "Type-9 Opaque-LSA: Reference to OI is missing?"); break; case OSPF_OPAQUE_AREA_LSA: if ((area = lsa->area) != NULL) listtop = area->opaque_lsa_self; else flog_warn( EC_OSPF_LSA, "Type-10 Opaque-LSA: Reference to AREA is missing?"); break; case OSPF_OPAQUE_AS_LSA: top = ospf_lookup_by_vrf_id(lsa->vrf_id); if ((area = lsa->area) != NULL && (top = area->ospf) == NULL) { flog_warn( EC_OSPF_LSA, "Type-11 Opaque-LSA: Reference to OSPF is missing?"); break; /* Unlikely to happen. */ } listtop = top->opaque_lsa_self; break; default: flog_warn(EC_OSPF_LSA_UNEXPECTED, "lookup_opaque_info_by_type: Unexpected LSA-type(%u)", lsa->data->type); break; } if (listtop != NULL) for (ALL_LIST_ELEMENTS(listtop, node, nnode, oipt)) if (oipt->opaque_type == key) return oipt; return NULL; } static struct opaque_info_per_id * register_opaque_info_per_id(struct opaque_info_per_type *oipt, struct ospf_lsa *new) { struct opaque_info_per_id *oipi; oipi = XCALLOC(MTYPE_OPAQUE_INFO_PER_ID, sizeof(struct opaque_info_per_id)); oipi->opaque_id = GET_OPAQUE_ID(ntohl(new->data->id.s_addr)); oipi->t_opaque_lsa_self = NULL; oipi->opqctl_type = oipt; oipi->lsa = ospf_lsa_lock(new); listnode_add(oipt->id_list, oipi); return oipi; } static void free_opaque_info_per_id(void *val) { struct opaque_info_per_id *oipi = (struct opaque_info_per_id *)val; OSPF_TIMER_OFF(oipi->t_opaque_lsa_self); if (oipi->lsa != NULL) ospf_lsa_unlock(&oipi->lsa); XFREE(MTYPE_OPAQUE_INFO_PER_ID, oipi); return; } static struct opaque_info_per_id * lookup_opaque_info_by_id(struct opaque_info_per_type *oipt, struct ospf_lsa *lsa) { struct listnode *node, *nnode; struct opaque_info_per_id *oipi; uint32_t key = GET_OPAQUE_ID(ntohl(lsa->data->id.s_addr)); for (ALL_LIST_ELEMENTS(oipt->id_list, node, nnode, oipi)) if (oipi->opaque_id == key) return oipi; return NULL; } static struct opaque_info_per_id *register_opaque_lsa(struct ospf_lsa *new) { struct ospf_opaque_functab *functab; struct opaque_info_per_type *oipt; struct opaque_info_per_id *oipi = NULL; if ((functab = ospf_opaque_functab_lookup(new)) == NULL) goto out; if ((oipt = lookup_opaque_info_by_type(new)) == NULL && (oipt = register_opaque_info_per_type(functab, new)) == NULL) goto out; if ((oipi = register_opaque_info_per_id(oipt, new)) == NULL) goto out; out: return oipi; } /*------------------------------------------------------------------------* * Followings are (vty) configuration functions for Opaque-LSAs handling. *------------------------------------------------------------------------*/ DEFUN (capability_opaque, capability_opaque_cmd, "capability opaque", "Enable specific OSPF feature\n" "Opaque LSA\n") { VTY_DECLVAR_INSTANCE_CONTEXT(ospf, ospf); /* Turn on the "master switch" of opaque-lsa capability. */ if (!CHECK_FLAG(ospf->config, OSPF_OPAQUE_CAPABLE)) { if (IS_DEBUG_OSPF_EVENT) zlog_debug("Opaque capability: OFF -> ON"); SET_FLAG(ospf->config, OSPF_OPAQUE_CAPABLE); ospf_renegotiate_optional_capabilities(ospf); } return CMD_SUCCESS; } DEFUN (ospf_opaque, ospf_opaque_cmd, "ospf opaque-lsa", "OSPF specific commands\n" "Enable the Opaque-LSA capability (rfc2370)\n") { return capability_opaque(self, vty, argc, argv); } DEFUN (no_capability_opaque, no_capability_opaque_cmd, "no capability opaque", NO_STR "Enable specific OSPF feature\n" "Opaque LSA\n") { VTY_DECLVAR_INSTANCE_CONTEXT(ospf, ospf); /* Turn off the "master switch" of opaque-lsa capability. */ if (CHECK_FLAG(ospf->config, OSPF_OPAQUE_CAPABLE)) { if (IS_DEBUG_OSPF_EVENT) zlog_debug("Opaque capability: ON -> OFF"); UNSET_FLAG(ospf->config, OSPF_OPAQUE_CAPABLE); ospf_renegotiate_optional_capabilities(ospf); } return CMD_SUCCESS; } DEFUN (no_ospf_opaque, no_ospf_opaque_cmd, "no ospf opaque-lsa", NO_STR "OSPF specific commands\n" "Enable the Opaque-LSA capability (rfc2370)\n") { return no_capability_opaque(self, vty, argc, argv); } static void ospf_opaque_register_vty(void) { install_element(OSPF_NODE, &capability_opaque_cmd); install_element(OSPF_NODE, &no_capability_opaque_cmd); install_element(OSPF_NODE, &ospf_opaque_cmd); install_element(OSPF_NODE, &no_ospf_opaque_cmd); return; } /*------------------------------------------------------------------------* * Followings are collection of user-registered function callers. *------------------------------------------------------------------------*/ static int opaque_lsa_new_if_callback(struct list *funclist, struct interface *ifp) { struct listnode *node, *nnode; struct ospf_opaque_functab *functab; int rc = -1; for (ALL_LIST_ELEMENTS(funclist, node, nnode, functab)) if (functab->new_if_hook != NULL) if ((*functab->new_if_hook)(ifp) != 0) goto out; rc = 0; out: return rc; } static int opaque_lsa_del_if_callback(struct list *funclist, struct interface *ifp) { struct listnode *node, *nnode; struct ospf_opaque_functab *functab; int rc = -1; for (ALL_LIST_ELEMENTS(funclist, node, nnode, functab)) if (functab->del_if_hook != NULL) if ((*functab->del_if_hook)(ifp) != 0) goto out; rc = 0; out: return rc; } static void opaque_lsa_ism_change_callback(struct list *funclist, struct ospf_interface *oi, int old_status) { struct listnode *node, *nnode; struct ospf_opaque_functab *functab; for (ALL_LIST_ELEMENTS(funclist, node, nnode, functab)) if (functab->ism_change_hook != NULL) (*functab->ism_change_hook)(oi, old_status); return; } static void opaque_lsa_nsm_change_callback(struct list *funclist, struct ospf_neighbor *nbr, int old_status) { struct listnode *node, *nnode; struct ospf_opaque_functab *functab; for (ALL_LIST_ELEMENTS(funclist, node, nnode, functab)) if (functab->nsm_change_hook != NULL) (*functab->nsm_change_hook)(nbr, old_status); return; } static void opaque_lsa_config_write_router_callback(struct list *funclist, struct vty *vty) { struct listnode *node, *nnode; struct ospf_opaque_functab *functab; for (ALL_LIST_ELEMENTS(funclist, node, nnode, functab)) if (functab->config_write_router != NULL) (*functab->config_write_router)(vty); return; } static void opaque_lsa_config_write_if_callback(struct list *funclist, struct vty *vty, struct interface *ifp) { struct listnode *node, *nnode; struct ospf_opaque_functab *functab; for (ALL_LIST_ELEMENTS(funclist, node, nnode, functab)) if (functab->config_write_if != NULL) (*functab->config_write_if)(vty, ifp); return; } static void opaque_lsa_config_write_debug_callback(struct list *funclist, struct vty *vty) { struct listnode *node, *nnode; struct ospf_opaque_functab *functab; for (ALL_LIST_ELEMENTS(funclist, node, nnode, functab)) if (functab->config_write_debug != NULL) (*functab->config_write_debug)(vty); return; } static int opaque_lsa_originate_callback(struct list *funclist, void *lsa_type_dependent) { struct listnode *node, *nnode; struct ospf_opaque_functab *functab; int rc = -1; for (ALL_LIST_ELEMENTS(funclist, node, nnode, functab)) if (functab->lsa_originator != NULL) if ((*functab->lsa_originator)(lsa_type_dependent) != 0) goto out; rc = 0; out: return rc; } static int new_lsa_callback(struct list *funclist, struct ospf_lsa *lsa) { struct listnode *node, *nnode; struct ospf_opaque_functab *functab; int rc = -1; /* This function handles ALL types of LSAs, not only opaque ones. */ for (ALL_LIST_ELEMENTS(funclist, node, nnode, functab)) if (functab->new_lsa_hook != NULL) if ((*functab->new_lsa_hook)(lsa) != 0) goto out; rc = 0; out: return rc; } static int del_lsa_callback(struct list *funclist, struct ospf_lsa *lsa) { struct listnode *node, *nnode; struct ospf_opaque_functab *functab; int rc = -1; /* This function handles ALL types of LSAs, not only opaque ones. */ for (ALL_LIST_ELEMENTS(funclist, node, nnode, functab)) if (functab->del_lsa_hook != NULL) if ((*functab->del_lsa_hook)(lsa) != 0) goto out; rc = 0; out: return rc; } /*------------------------------------------------------------------------* * Followings are glue functions to call Opaque-LSA specific processing. *------------------------------------------------------------------------*/ int ospf_opaque_new_if(struct interface *ifp) { struct list *funclist; int rc = -1; funclist = ospf_opaque_wildcard_funclist; if (opaque_lsa_new_if_callback(funclist, ifp) != 0) goto out; funclist = ospf_opaque_type9_funclist; if (opaque_lsa_new_if_callback(funclist, ifp) != 0) goto out; funclist = ospf_opaque_type10_funclist; if (opaque_lsa_new_if_callback(funclist, ifp) != 0) goto out; funclist = ospf_opaque_type11_funclist; if (opaque_lsa_new_if_callback(funclist, ifp) != 0) goto out; rc = 0; out: return rc; } int ospf_opaque_del_if(struct interface *ifp) { struct list *funclist; int rc = -1; funclist = ospf_opaque_wildcard_funclist; if (opaque_lsa_del_if_callback(funclist, ifp) != 0) goto out; funclist = ospf_opaque_type9_funclist; if (opaque_lsa_del_if_callback(funclist, ifp) != 0) goto out; funclist = ospf_opaque_type10_funclist; if (opaque_lsa_del_if_callback(funclist, ifp) != 0) goto out; funclist = ospf_opaque_type11_funclist; if (opaque_lsa_del_if_callback(funclist, ifp) != 0) goto out; rc = 0; out: return rc; } void ospf_opaque_ism_change(struct ospf_interface *oi, int old_status) { struct list *funclist; funclist = ospf_opaque_wildcard_funclist; opaque_lsa_ism_change_callback(funclist, oi, old_status); funclist = ospf_opaque_type9_funclist; opaque_lsa_ism_change_callback(funclist, oi, old_status); funclist = ospf_opaque_type10_funclist; opaque_lsa_ism_change_callback(funclist, oi, old_status); funclist = ospf_opaque_type11_funclist; opaque_lsa_ism_change_callback(funclist, oi, old_status); return; } void ospf_opaque_nsm_change(struct ospf_neighbor *nbr, int old_state) { struct ospf *top; struct list *funclist; if ((top = oi_to_top(nbr->oi)) == NULL) goto out; if (old_state != NSM_Full && nbr->state == NSM_Full) { if (CHECK_FLAG(nbr->options, OSPF_OPTION_O)) { if (!CHECK_FLAG(top->opaque, OPAQUE_OPERATION_READY_BIT)) { if (IS_DEBUG_OSPF_EVENT) zlog_debug( "Opaque-LSA: Now get operational!"); SET_FLAG(top->opaque, OPAQUE_OPERATION_READY_BIT); } ospf_opaque_lsa_originate_schedule(nbr->oi, NULL); } } else if (old_state == NSM_Full && nbr->state != NSM_Full) { #ifdef NOTYET /* * If no more opaque-capable full-state neighbor remains in the * flooding scope which corresponds to Opaque-LSA type, periodic * LS flooding should be stopped. */ #endif /* NOTYET */ ; } funclist = ospf_opaque_wildcard_funclist; opaque_lsa_nsm_change_callback(funclist, nbr, old_state); funclist = ospf_opaque_type9_funclist; opaque_lsa_nsm_change_callback(funclist, nbr, old_state); funclist = ospf_opaque_type10_funclist; opaque_lsa_nsm_change_callback(funclist, nbr, old_state); funclist = ospf_opaque_type11_funclist; opaque_lsa_nsm_change_callback(funclist, nbr, old_state); out: return; } void ospf_opaque_config_write_router(struct vty *vty, struct ospf *ospf) { struct list *funclist; if (CHECK_FLAG(ospf->config, OSPF_OPAQUE_CAPABLE)) vty_out(vty, " capability opaque\n"); funclist = ospf_opaque_wildcard_funclist; opaque_lsa_config_write_router_callback(funclist, vty); funclist = ospf_opaque_type9_funclist; opaque_lsa_config_write_router_callback(funclist, vty); funclist = ospf_opaque_type10_funclist; opaque_lsa_config_write_router_callback(funclist, vty); funclist = ospf_opaque_type11_funclist; opaque_lsa_config_write_router_callback(funclist, vty); return; } void ospf_opaque_config_write_if(struct vty *vty, struct interface *ifp) { struct list *funclist; funclist = ospf_opaque_wildcard_funclist; opaque_lsa_config_write_if_callback(funclist, vty, ifp); funclist = ospf_opaque_type9_funclist; opaque_lsa_config_write_if_callback(funclist, vty, ifp); funclist = ospf_opaque_type10_funclist; opaque_lsa_config_write_if_callback(funclist, vty, ifp); funclist = ospf_opaque_type11_funclist; opaque_lsa_config_write_if_callback(funclist, vty, ifp); return; } void ospf_opaque_config_write_debug(struct vty *vty) { struct list *funclist; funclist = ospf_opaque_wildcard_funclist; opaque_lsa_config_write_debug_callback(funclist, vty); funclist = ospf_opaque_type9_funclist; opaque_lsa_config_write_debug_callback(funclist, vty); funclist = ospf_opaque_type10_funclist; opaque_lsa_config_write_debug_callback(funclist, vty); funclist = ospf_opaque_type11_funclist; opaque_lsa_config_write_debug_callback(funclist, vty); return; } void show_opaque_info_detail(struct vty *vty, struct ospf_lsa *lsa) { struct lsa_header *lsah = (struct lsa_header *)lsa->data; uint32_t lsid = ntohl(lsah->id.s_addr); uint8_t opaque_type = GET_OPAQUE_TYPE(lsid); uint32_t opaque_id = GET_OPAQUE_ID(lsid); struct ospf_opaque_functab *functab; /* Switch output functionality by vty address. */ if (vty != NULL) { vty_out(vty, " Opaque-Type %u (%s)\n", opaque_type, ospf_opaque_type_name(opaque_type)); vty_out(vty, " Opaque-ID 0x%x\n", opaque_id); vty_out(vty, " Opaque-Info: %u octets of data%s\n", ntohs(lsah->length) - OSPF_LSA_HEADER_SIZE, VALID_OPAQUE_INFO_LEN(lsah) ? "" : "(Invalid length?)"); } else { zlog_debug(" Opaque-Type %u (%s)", opaque_type, ospf_opaque_type_name(opaque_type)); zlog_debug(" Opaque-ID 0x%x", opaque_id); zlog_debug(" Opaque-Info: %u octets of data%s", ntohs(lsah->length) - OSPF_LSA_HEADER_SIZE, VALID_OPAQUE_INFO_LEN(lsah) ? "" : "(Invalid length?)"); } /* Call individual output functions. */ if ((functab = ospf_opaque_functab_lookup(lsa)) != NULL) if (functab->show_opaque_info != NULL) (*functab->show_opaque_info)(vty, lsa); return; } void ospf_opaque_lsa_dump(struct stream *s, uint16_t length) { struct ospf_lsa lsa; lsa.data = (struct lsa_header *)stream_pnt(s); show_opaque_info_detail(NULL, &lsa); return; } static int ospf_opaque_lsa_install_hook(struct ospf_lsa *lsa) { struct list *funclist; int rc = -1; /* * Some Opaque-LSA user may want to monitor every LSA installation * into the LSDB, regardless with target LSA type. */ funclist = ospf_opaque_wildcard_funclist; if (new_lsa_callback(funclist, lsa) != 0) goto out; funclist = ospf_opaque_type9_funclist; if (new_lsa_callback(funclist, lsa) != 0) goto out; funclist = ospf_opaque_type10_funclist; if (new_lsa_callback(funclist, lsa) != 0) goto out; funclist = ospf_opaque_type11_funclist; if (new_lsa_callback(funclist, lsa) != 0) goto out; rc = 0; out: return rc; } static int ospf_opaque_lsa_delete_hook(struct ospf_lsa *lsa) { struct list *funclist; int rc = -1; /* * Some Opaque-LSA user may want to monitor every LSA deletion * from the LSDB, regardless with target LSA type. */ funclist = ospf_opaque_wildcard_funclist; if (del_lsa_callback(funclist, lsa) != 0) goto out; funclist = ospf_opaque_type9_funclist; if (del_lsa_callback(funclist, lsa) != 0) goto out; funclist = ospf_opaque_type10_funclist; if (del_lsa_callback(funclist, lsa) != 0) goto out; funclist = ospf_opaque_type11_funclist; if (del_lsa_callback(funclist, lsa) != 0) goto out; rc = 0; out: return rc; } /*------------------------------------------------------------------------* * Followings are Opaque-LSA origination/refresh management functions. *------------------------------------------------------------------------*/ static int ospf_opaque_type9_lsa_originate(struct thread *t); static int ospf_opaque_type10_lsa_originate(struct thread *t); static int ospf_opaque_type11_lsa_originate(struct thread *t); static void ospf_opaque_lsa_reoriginate_resume(struct list *listtop, void *arg); void ospf_opaque_lsa_originate_schedule(struct ospf_interface *oi, int *delay0) { struct ospf *top; struct ospf_area *area; struct listnode *node, *nnode; struct opaque_info_per_type *oipt; int delay = 0; if ((top = oi_to_top(oi)) == NULL || (area = oi->area) == NULL) { if (IS_DEBUG_OSPF_EVENT) zlog_debug( "ospf_opaque_lsa_originate_schedule: Invalid argument?"); return; } /* It may not a right time to schedule origination now. */ if (!CHECK_FLAG(top->opaque, OPAQUE_OPERATION_READY_BIT)) { if (IS_DEBUG_OSPF_EVENT) zlog_debug( "ospf_opaque_lsa_originate_schedule: Not operational."); return; /* This is not an error. */ } if (delay0 != NULL) delay = *delay0; /* * There might be some entries that have been waiting for triggering * of per opaque-type re-origination get resumed. */ ospf_opaque_lsa_reoriginate_resume(oi->opaque_lsa_self, (void *)oi); ospf_opaque_lsa_reoriginate_resume(area->opaque_lsa_self, (void *)area); ospf_opaque_lsa_reoriginate_resume(top->opaque_lsa_self, (void *)top); /* * Now, schedule origination of all Opaque-LSAs per opaque-type. */ if (!list_isempty(ospf_opaque_type9_funclist) && list_isempty(oi->opaque_lsa_self) && oi->t_opaque_lsa_self == NULL) { if (IS_DEBUG_OSPF_EVENT) zlog_debug( "Schedule Type-9 Opaque-LSA origination in %d ms later.", delay); oi->t_opaque_lsa_self = NULL; thread_add_timer_msec(master, ospf_opaque_type9_lsa_originate, oi, delay, &oi->t_opaque_lsa_self); delay += top->min_ls_interval; } if (!list_isempty(ospf_opaque_type10_funclist) && list_isempty(area->opaque_lsa_self) && area->t_opaque_lsa_self == NULL) { /* * One AREA may contain multiple OIs, but above 2nd and 3rd * conditions prevent from scheduling the originate function * again and again. */ if (IS_DEBUG_OSPF_EVENT) zlog_debug( "Schedule Type-10 Opaque-LSA origination in %d ms later.", delay); area->t_opaque_lsa_self = NULL; thread_add_timer_msec(master, ospf_opaque_type10_lsa_originate, area, delay, &area->t_opaque_lsa_self); delay += top->min_ls_interval; } if (!list_isempty(ospf_opaque_type11_funclist) && list_isempty(top->opaque_lsa_self) && top->t_opaque_lsa_self == NULL) { /* * One OSPF may contain multiple AREAs, but above 2nd and 3rd * conditions prevent from scheduling the originate function * again and again. */ if (IS_DEBUG_OSPF_EVENT) zlog_debug( "Schedule Type-11 Opaque-LSA origination in %d ms later.", delay); top->t_opaque_lsa_self = NULL; thread_add_timer_msec(master, ospf_opaque_type11_lsa_originate, top, delay, &top->t_opaque_lsa_self); delay += top->min_ls_interval; } /* * Following section treats a special situation that this node's * opaque capability has changed as "ON -> OFF -> ON". */ if (!list_isempty(ospf_opaque_type9_funclist) && !list_isempty(oi->opaque_lsa_self)) { for (ALL_LIST_ELEMENTS(oi->opaque_lsa_self, node, nnode, oipt)) { /* * removed the test for * (! list_isempty (oipt->id_list)) * Handler is * already active. * * because opaque cababilities ON -> OFF -> ON result in * list_isempty (oipt->id_list) * not being empty. */ if (oipt->t_opaque_lsa_self != NULL /* Waiting for a thread call. */ || oipt->status == PROC_SUSPEND) /* Cannot originate now. */ continue; ospf_opaque_lsa_reoriginate_schedule( (void *)oi, OSPF_OPAQUE_LINK_LSA, oipt->opaque_type); } } if (!list_isempty(ospf_opaque_type10_funclist) && !list_isempty(area->opaque_lsa_self)) { for (ALL_LIST_ELEMENTS(area->opaque_lsa_self, node, nnode, oipt)) { /* * removed the test for * (! list_isempty (oipt->id_list)) * Handler is * already active. * * because opaque cababilities ON -> OFF -> ON result in * list_isempty (oipt->id_list) * not being empty. */ if (oipt->t_opaque_lsa_self != NULL /* Waiting for a thread call. */ || oipt->status == PROC_SUSPEND) /* Cannot originate now. */ continue; ospf_opaque_lsa_reoriginate_schedule( (void *)area, OSPF_OPAQUE_AREA_LSA, oipt->opaque_type); } } if (!list_isempty(ospf_opaque_type11_funclist) && !list_isempty(top->opaque_lsa_self)) { for (ALL_LIST_ELEMENTS(top->opaque_lsa_self, node, nnode, oipt)) { /* * removed the test for * (! list_isempty (oipt->id_list)) * Handler is * already active. * * because opaque cababilities ON -> OFF -> ON result in * list_isempty (oipt->id_list) * not being empty. */ if (oipt->t_opaque_lsa_self != NULL /* Waiting for a thread call. */ || oipt->status == PROC_SUSPEND) /* Cannot originate now. */ continue; ospf_opaque_lsa_reoriginate_schedule((void *)top, OSPF_OPAQUE_AS_LSA, oipt->opaque_type); } } if (delay0 != NULL) *delay0 = delay; } static int ospf_opaque_type9_lsa_originate(struct thread *t) { struct ospf_interface *oi; int rc; oi = THREAD_ARG(t); oi->t_opaque_lsa_self = NULL; if (IS_DEBUG_OSPF_EVENT) zlog_debug("Timer[Type9-LSA]: Originate Opaque-LSAs for OI %s", IF_NAME(oi)); rc = opaque_lsa_originate_callback(ospf_opaque_type9_funclist, oi); return rc; } static int ospf_opaque_type10_lsa_originate(struct thread *t) { struct ospf_area *area; int rc; area = THREAD_ARG(t); area->t_opaque_lsa_self = NULL; if (IS_DEBUG_OSPF_EVENT) zlog_debug( "Timer[Type10-LSA]: Originate Opaque-LSAs for Area %s", inet_ntoa(area->area_id)); rc = opaque_lsa_originate_callback(ospf_opaque_type10_funclist, area); return rc; } static int ospf_opaque_type11_lsa_originate(struct thread *t) { struct ospf *top; int rc; top = THREAD_ARG(t); top->t_opaque_lsa_self = NULL; if (IS_DEBUG_OSPF_EVENT) zlog_debug( "Timer[Type11-LSA]: Originate AS-External Opaque-LSAs"); rc = opaque_lsa_originate_callback(ospf_opaque_type11_funclist, top); return rc; } static void ospf_opaque_lsa_reoriginate_resume(struct list *listtop, void *arg) { struct listnode *node, *nnode; struct opaque_info_per_type *oipt; struct ospf_opaque_functab *functab; if (listtop == NULL) goto out; /* * Pickup oipt entries those which in SUSPEND status, and give * them a chance to start re-origination now. */ for (ALL_LIST_ELEMENTS(listtop, node, nnode, oipt)) { if (oipt->status != PROC_SUSPEND) continue; oipt->status = PROC_NORMAL; if ((functab = oipt->functab) == NULL || functab->lsa_originator == NULL) continue; if ((*functab->lsa_originator)(arg) != 0) { flog_warn( EC_OSPF_LSA, "ospf_opaque_lsa_reoriginate_resume: Failed (opaque-type=%u)", oipt->opaque_type); continue; } } out: return; } struct ospf_lsa *ospf_opaque_lsa_install(struct ospf_lsa *lsa, int rt_recalc) { struct ospf_lsa *new = NULL; struct opaque_info_per_type *oipt; struct opaque_info_per_id *oipi; struct ospf *top; /* Don't take "rt_recalc" into consideration for now. */ /* XXX */ if (!IS_LSA_SELF(lsa)) { new = lsa; /* Don't touch this LSA. */ goto out; } if (IS_DEBUG_OSPF(lsa, LSA_INSTALL)) zlog_debug( "Install Type-%u Opaque-LSA: [opaque-type=%u, opaque-id=%x]", lsa->data->type, GET_OPAQUE_TYPE(ntohl(lsa->data->id.s_addr)), GET_OPAQUE_ID(ntohl(lsa->data->id.s_addr))); /* Replace the existing lsa with the new one. */ if ((oipt = lookup_opaque_info_by_type(lsa)) != NULL && (oipi = lookup_opaque_info_by_id(oipt, lsa)) != NULL) { ospf_lsa_unlock(&oipi->lsa); oipi->lsa = ospf_lsa_lock(lsa); } /* Register the new lsa entry and get its control info. */ else if ((oipi = register_opaque_lsa(lsa)) == NULL) { flog_warn(EC_OSPF_LSA, "ospf_opaque_lsa_install: register_opaque_lsa() ?"); goto out; } /* * Make use of a common mechanism (ospf_lsa_refresh_walker) * for periodic refresh of self-originated Opaque-LSAs. */ switch (lsa->data->type) { case OSPF_OPAQUE_LINK_LSA: if ((top = oi_to_top(lsa->oi)) == NULL) { /* Above conditions must have passed. */ flog_warn(EC_OSPF_LSA, "ospf_opaque_lsa_install: Something wrong?"); goto out; } break; case OSPF_OPAQUE_AREA_LSA: if (lsa->area == NULL || (top = lsa->area->ospf) == NULL) { /* Above conditions must have passed. */ flog_warn(EC_OSPF_LSA, "ospf_opaque_lsa_install: Something wrong?"); goto out; } break; case OSPF_OPAQUE_AS_LSA: top = ospf_lookup_by_vrf_id(lsa->vrf_id); if (lsa->area != NULL && (top = lsa->area->ospf) == NULL) { /* Above conditions must have passed. */ flog_warn(EC_OSPF_LSA, "ospf_opaque_lsa_install: Something wrong?"); goto out; } break; default: flog_warn(EC_OSPF_LSA_UNEXPECTED, "ospf_opaque_lsa_install: Unexpected LSA-type(%u)", lsa->data->type); goto out; } ospf_refresher_register_lsa(top, lsa); new = lsa; out: return new; } struct ospf_lsa *ospf_opaque_lsa_refresh(struct ospf_lsa *lsa) { struct ospf *ospf; struct ospf_opaque_functab *functab; struct ospf_lsa *new = NULL; ospf = ospf_lookup_by_vrf_id(lsa->vrf_id); if ((functab = ospf_opaque_functab_lookup(lsa)) == NULL || functab->lsa_refresher == NULL) { /* * Though this LSA seems to have originated on this node, the * handling module for this "lsa-type and opaque-type" was * already deleted sometime ago. * Anyway, this node still has a responsibility to flush this * LSA from the routing domain. */ if (IS_DEBUG_OSPF_EVENT) zlog_debug("LSA[Type%d:%s]: Flush stray Opaque-LSA", lsa->data->type, inet_ntoa(lsa->data->id)); lsa->data->ls_age = htons(OSPF_LSA_MAXAGE); ospf_lsa_flush(ospf, lsa); } else new = (*functab->lsa_refresher)(lsa); return new; } /*------------------------------------------------------------------------* * Followings are re-origination/refresh/flush operations of Opaque-LSAs, * triggered by external interventions (vty session, signaling, etc). *------------------------------------------------------------------------*/ #define OSPF_OPAQUE_TIMER_ON(T,F,L,V) thread_add_timer_msec (master, (F), (L), (V), &(T)) static struct ospf_lsa *pseudo_lsa(struct ospf_interface *oi, struct ospf_area *area, uint8_t lsa_type, uint8_t opaque_type); static int ospf_opaque_type9_lsa_reoriginate_timer(struct thread *t); static int ospf_opaque_type10_lsa_reoriginate_timer(struct thread *t); static int ospf_opaque_type11_lsa_reoriginate_timer(struct thread *t); static int ospf_opaque_lsa_refresh_timer(struct thread *t); void ospf_opaque_lsa_reoriginate_schedule(void *lsa_type_dependent, uint8_t lsa_type, uint8_t opaque_type) { struct ospf *top = NULL; struct ospf_area dummy, *area = NULL; struct ospf_interface *oi = NULL; struct ospf_lsa *lsa; struct opaque_info_per_type *oipt; int (*func)(struct thread * t) = NULL; int delay; switch (lsa_type) { case OSPF_OPAQUE_LINK_LSA: if ((oi = (struct ospf_interface *)lsa_type_dependent) == NULL) { flog_warn( EC_OSPF_LSA, "ospf_opaque_lsa_reoriginate_schedule: Type-9 Opaque-LSA: Invalid parameter?"); goto out; } if ((top = oi_to_top(oi)) == NULL) { flog_warn( EC_OSPF_LSA, "ospf_opaque_lsa_reoriginate_schedule: OI(%s) -> TOP?", IF_NAME(oi)); goto out; } if (!list_isempty(ospf_opaque_type9_funclist) && list_isempty(oi->opaque_lsa_self) && oi->t_opaque_lsa_self != NULL) { flog_warn( EC_OSPF_LSA, "Type-9 Opaque-LSA (opaque_type=%u): Common origination for OI(%s) has already started", opaque_type, IF_NAME(oi)); goto out; } func = ospf_opaque_type9_lsa_reoriginate_timer; break; case OSPF_OPAQUE_AREA_LSA: if ((area = (struct ospf_area *)lsa_type_dependent) == NULL) { flog_warn( EC_OSPF_LSA, "ospf_opaque_lsa_reoriginate_schedule: Type-10 Opaque-LSA: Invalid parameter?"); goto out; } if ((top = area->ospf) == NULL) { flog_warn( EC_OSPF_LSA, "ospf_opaque_lsa_reoriginate_schedule: AREA(%s) -> TOP?", inet_ntoa(area->area_id)); goto out; } if (!list_isempty(ospf_opaque_type10_funclist) && list_isempty(area->opaque_lsa_self) && area->t_opaque_lsa_self != NULL) { flog_warn( EC_OSPF_LSA, "Type-10 Opaque-LSA (opaque_type=%u): Common origination for AREA(%s) has already started", opaque_type, inet_ntoa(area->area_id)); goto out; } func = ospf_opaque_type10_lsa_reoriginate_timer; break; case OSPF_OPAQUE_AS_LSA: if ((top = (struct ospf *)lsa_type_dependent) == NULL) { flog_warn( EC_OSPF_LSA, "ospf_opaque_lsa_reoriginate_schedule: Type-11 Opaque-LSA: Invalid parameter?"); goto out; } if (!list_isempty(ospf_opaque_type11_funclist) && list_isempty(top->opaque_lsa_self) && top->t_opaque_lsa_self != NULL) { flog_warn( EC_OSPF_LSA, "Type-11 Opaque-LSA (opaque_type=%u): Common origination has already started", opaque_type); goto out; } /* Fake "area" to pass "ospf" to a lookup function later. */ dummy.ospf = top; area = &dummy; func = ospf_opaque_type11_lsa_reoriginate_timer; break; default: flog_warn( EC_OSPF_LSA_UNEXPECTED, "ospf_opaque_lsa_reoriginate_schedule: Unexpected LSA-type(%u)", lsa_type); goto out; } /* It may not a right time to schedule reorigination now. */ if (!CHECK_FLAG(top->opaque, OPAQUE_OPERATION_READY_BIT)) { if (IS_DEBUG_OSPF_EVENT) zlog_debug( "ospf_opaque_lsa_reoriginate_schedule: Not operational."); goto out; /* This is not an error. */ } /* Generate a dummy lsa to be passed for a lookup function. */ lsa = pseudo_lsa(oi, area, lsa_type, opaque_type); lsa->vrf_id = top->vrf_id; if ((oipt = lookup_opaque_info_by_type(lsa)) == NULL) { struct ospf_opaque_functab *functab; if ((functab = ospf_opaque_functab_lookup(lsa)) == NULL) { flog_warn( EC_OSPF_LSA, "ospf_opaque_lsa_reoriginate_schedule: No associated function?: lsa_type(%u), opaque_type(%u)", lsa_type, opaque_type); goto out; } if ((oipt = register_opaque_info_per_type(functab, lsa)) == NULL) { flog_warn( EC_OSPF_LSA, "ospf_opaque_lsa_reoriginate_schedule: Cannot get a control info?: lsa_type(%u), opaque_type(%u)", lsa_type, opaque_type); goto out; } } if (oipt->t_opaque_lsa_self != NULL) { if (IS_DEBUG_OSPF_EVENT) zlog_debug( "Type-%u Opaque-LSA has already scheduled to" " RE-ORIGINATE: [opaque-type=%u]", lsa_type, GET_OPAQUE_TYPE(ntohl(lsa->data->id.s_addr))); goto out; } /* * Different from initial origination time, in which various conditions * (opaque capability, neighbor status etc) are assured by caller of * the originating function "ospf_opaque_lsa_originate_schedule ()", * it is highly possible that these conditions might not be satisfied * at the time of re-origination function is to be called. */ delay = top->min_ls_interval; /* XXX */ if (IS_DEBUG_OSPF_EVENT) zlog_debug( "Schedule Type-%u Opaque-LSA to RE-ORIGINATE in %d" " ms later: [opaque-type=%u]", lsa_type, delay, GET_OPAQUE_TYPE(ntohl(lsa->data->id.s_addr))); OSPF_OPAQUE_TIMER_ON(oipt->t_opaque_lsa_self, func, oipt, delay); out: return; } static struct ospf_lsa *pseudo_lsa(struct ospf_interface *oi, struct ospf_area *area, uint8_t lsa_type, uint8_t opaque_type) { static struct ospf_lsa lsa = {0}; static struct lsa_header lsah = {0}; uint32_t tmp; lsa.oi = oi; lsa.area = area; lsa.data = &lsah; lsa.vrf_id = VRF_DEFAULT; lsah.type = lsa_type; tmp = SET_OPAQUE_LSID(opaque_type, 0); /* Opaque-ID is unused here. */ lsah.id.s_addr = htonl(tmp); return &lsa; } static int ospf_opaque_type9_lsa_reoriginate_timer(struct thread *t) { struct opaque_info_per_type *oipt; struct ospf_opaque_functab *functab; struct ospf *top; struct ospf_interface *oi; int rc = -1; oipt = THREAD_ARG(t); oipt->t_opaque_lsa_self = NULL; if ((functab = oipt->functab) == NULL || functab->lsa_originator == NULL) { flog_warn( EC_OSPF_LSA, "ospf_opaque_type9_lsa_reoriginate_timer: No associated function?"); goto out; } oi = (struct ospf_interface *)oipt->owner; if ((top = oi_to_top(oi)) == NULL) { flog_warn( EC_OSPF_LSA, "ospf_opaque_type9_lsa_reoriginate_timer: Something wrong?"); goto out; } if (!CHECK_FLAG(top->config, OSPF_OPAQUE_CAPABLE) || !ospf_if_is_enable(oi) || ospf_nbr_count_opaque_capable(oi) == 0) { if (IS_DEBUG_OSPF_EVENT) zlog_debug( "Suspend re-origination of Type-9 Opaque-LSAs (opaque-type=%u) for a while...", oipt->opaque_type); oipt->status = PROC_SUSPEND; rc = 0; goto out; } if (IS_DEBUG_OSPF_EVENT) zlog_debug( "Timer[Type9-LSA]: Re-originate Opaque-LSAs (opaque-type=%u) for OI (%s)", oipt->opaque_type, IF_NAME(oi)); rc = (*functab->lsa_originator)(oi); out: return rc; } static int ospf_opaque_type10_lsa_reoriginate_timer(struct thread *t) { struct opaque_info_per_type *oipt; struct ospf_opaque_functab *functab; struct listnode *node, *nnode; struct ospf *top; struct ospf_area *area; struct ospf_interface *oi; int n, rc = -1; oipt = THREAD_ARG(t); oipt->t_opaque_lsa_self = NULL; if ((functab = oipt->functab) == NULL || functab->lsa_originator == NULL) { flog_warn( EC_OSPF_LSA, "ospf_opaque_type10_lsa_reoriginate_timer: No associated function?"); goto out; } area = (struct ospf_area *)oipt->owner; if (area == NULL || (top = area->ospf) == NULL) { flog_warn( EC_OSPF_LSA, "ospf_opaque_type10_lsa_reoriginate_timer: Something wrong?"); goto out; } /* There must be at least one "opaque-capable, full-state" neighbor. */ n = 0; for (ALL_LIST_ELEMENTS(area->oiflist, node, nnode, oi)) { if ((n = ospf_nbr_count_opaque_capable(oi)) > 0) break; } if (n == 0 || !CHECK_FLAG(top->config, OSPF_OPAQUE_CAPABLE)) { if (IS_DEBUG_OSPF_EVENT) zlog_debug( "Suspend re-origination of Type-10 Opaque-LSAs" " (opaque-type=%u) for a while...", oipt->opaque_type); oipt->status = PROC_SUSPEND; rc = 0; goto out; } if (IS_DEBUG_OSPF_EVENT) zlog_debug( "Timer[Type10-LSA]: Re-originate Opaque-LSAs" " (opaque-type=%u) for Area %s", oipt->opaque_type, inet_ntoa(area->area_id)); rc = (*functab->lsa_originator)(area); out: return rc; } static int ospf_opaque_type11_lsa_reoriginate_timer(struct thread *t) { struct opaque_info_per_type *oipt; struct ospf_opaque_functab *functab; struct ospf *top; int rc = -1; oipt = THREAD_ARG(t); oipt->t_opaque_lsa_self = NULL; if ((functab = oipt->functab) == NULL || functab->lsa_originator == NULL) { flog_warn( EC_OSPF_LSA, "ospf_opaque_type11_lsa_reoriginate_timer: No associated function?"); goto out; } if ((top = (struct ospf *)oipt->owner) == NULL) { flog_warn( EC_OSPF_LSA, "ospf_opaque_type11_lsa_reoriginate_timer: Something wrong?"); goto out; } if (!CHECK_FLAG(top->config, OSPF_OPAQUE_CAPABLE)) { if (IS_DEBUG_OSPF_EVENT) zlog_debug( "Suspend re-origination of Type-11 Opaque-LSAs (opaque-type=%u) for a while...", oipt->opaque_type); oipt->status = PROC_SUSPEND; rc = 0; goto out; } if (IS_DEBUG_OSPF_EVENT) zlog_debug( "Timer[Type11-LSA]: Re-originate Opaque-LSAs (opaque-type=%u).", oipt->opaque_type); rc = (*functab->lsa_originator)(top); out: return rc; } void ospf_opaque_lsa_refresh_schedule(struct ospf_lsa *lsa0) { struct opaque_info_per_type *oipt; struct opaque_info_per_id *oipi; struct ospf_lsa *lsa; struct ospf *top; int delay; if ((oipt = lookup_opaque_info_by_type(lsa0)) == NULL || (oipi = lookup_opaque_info_by_id(oipt, lsa0)) == NULL) { flog_warn( EC_OSPF_LSA, "ospf_opaque_lsa_refresh_schedule: Invalid parameter?"); goto out; } /* Given "lsa0" and current "oipi->lsa" may different, but harmless. */ if ((lsa = oipi->lsa) == NULL) { flog_warn(EC_OSPF_LSA, "ospf_opaque_lsa_refresh_schedule: Something wrong?"); goto out; } if (oipi->t_opaque_lsa_self != NULL) { if (IS_DEBUG_OSPF_EVENT) zlog_debug( "Type-%u Opaque-LSA has already scheduled to REFRESH: [opaque-type=%u, opaque-id=%x]", lsa->data->type, GET_OPAQUE_TYPE(ntohl(lsa->data->id.s_addr)), GET_OPAQUE_ID(ntohl(lsa->data->id.s_addr))); goto out; } /* Delete this lsa from neighbor retransmit-list. */ switch (lsa->data->type) { case OSPF_OPAQUE_LINK_LSA: case OSPF_OPAQUE_AREA_LSA: ospf_ls_retransmit_delete_nbr_area(lsa->area, lsa); break; case OSPF_OPAQUE_AS_LSA: top = ospf_lookup_by_vrf_id(lsa0->vrf_id); if ((lsa0->area != NULL) && (lsa0->area->ospf != NULL)) top = lsa0->area->ospf; ospf_ls_retransmit_delete_nbr_as(top, lsa); break; default: flog_warn( EC_OSPF_LSA_UNEXPECTED, "ospf_opaque_lsa_refresh_schedule: Unexpected LSA-type(%u)", lsa->data->type); goto out; } delay = ospf_lsa_refresh_delay(lsa); if (IS_DEBUG_OSPF_EVENT) zlog_debug( "Schedule Type-%u Opaque-LSA to REFRESH in %d sec later: [opaque-type=%u, opaque-id=%x]", lsa->data->type, delay, GET_OPAQUE_TYPE(ntohl(lsa->data->id.s_addr)), GET_OPAQUE_ID(ntohl(lsa->data->id.s_addr))); OSPF_OPAQUE_TIMER_ON(oipi->t_opaque_lsa_self, ospf_opaque_lsa_refresh_timer, oipi, delay * 1000); out: return; } static int ospf_opaque_lsa_refresh_timer(struct thread *t) { struct opaque_info_per_id *oipi; struct ospf_opaque_functab *functab; struct ospf_lsa *lsa; if (IS_DEBUG_OSPF_EVENT) zlog_debug("Timer[Opaque-LSA]: (Opaque-LSA Refresh expire)"); oipi = THREAD_ARG(t); oipi->t_opaque_lsa_self = NULL; if ((lsa = oipi->lsa) != NULL) if ((functab = oipi->opqctl_type->functab) != NULL) if (functab->lsa_refresher != NULL) (*functab->lsa_refresher)(lsa); return 0; } void ospf_opaque_lsa_flush_schedule(struct ospf_lsa *lsa0) { struct opaque_info_per_type *oipt; struct opaque_info_per_id *oipi; struct ospf_lsa *lsa; struct ospf *top; top = ospf_lookup_by_vrf_id(lsa0->vrf_id); if ((oipt = lookup_opaque_info_by_type(lsa0)) == NULL || (oipi = lookup_opaque_info_by_id(oipt, lsa0)) == NULL) { flog_warn(EC_OSPF_LSA, "ospf_opaque_lsa_flush_schedule: Invalid parameter?"); goto out; } /* Given "lsa0" and current "oipi->lsa" may different, but harmless. */ if ((lsa = oipi->lsa) == NULL) { flog_warn(EC_OSPF_LSA, "ospf_opaque_lsa_flush_schedule: Something wrong?"); goto out; } /* Delete this lsa from neighbor retransmit-list. */ switch (lsa->data->type) { case OSPF_OPAQUE_LINK_LSA: case OSPF_OPAQUE_AREA_LSA: ospf_ls_retransmit_delete_nbr_area(lsa->area, lsa); break; case OSPF_OPAQUE_AS_LSA: if ((lsa0->area != NULL) && (lsa0->area->ospf != NULL)) top = lsa0->area->ospf; ospf_ls_retransmit_delete_nbr_as(top, lsa); break; default: flog_warn( EC_OSPF_LSA_UNEXPECTED, "ospf_opaque_lsa_flush_schedule: Unexpected LSA-type(%u)", lsa->data->type); goto out; } /* Dequeue listnode entry from the list. */ listnode_delete(oipt->id_list, oipi); /* Disassociate internal control information with the given lsa. */ free_opaque_info_per_id((void *)oipi); /* Force given lsa's age to MaxAge. */ lsa->data->ls_age = htons(OSPF_LSA_MAXAGE); if (IS_DEBUG_OSPF_EVENT) zlog_debug( "Schedule Type-%u Opaque-LSA to FLUSH: [opaque-type=%u, opaque-id=%x]", lsa->data->type, GET_OPAQUE_TYPE(ntohl(lsa->data->id.s_addr)), GET_OPAQUE_ID(ntohl(lsa->data->id.s_addr))); /* This lsa will be flushed and removed eventually. */ ospf_lsa_flush(top, lsa); out: return; } void ospf_opaque_self_originated_lsa_received(struct ospf_neighbor *nbr, struct ospf_lsa *lsa) { struct ospf *top; if ((top = oi_to_top(nbr->oi)) == NULL) return; /* * Since these LSA entries are not yet installed into corresponding * LSDB, just flush them without calling ospf_ls_maxage() afterward. */ lsa->data->ls_age = htons(OSPF_LSA_MAXAGE); switch (lsa->data->type) { case OSPF_OPAQUE_LINK_LSA: ospf_flood_through_area(nbr->oi->area, NULL /*inbr*/, lsa); break; case OSPF_OPAQUE_AREA_LSA: ospf_flood_through_area(nbr->oi->area, NULL /*inbr*/, lsa); break; case OSPF_OPAQUE_AS_LSA: ospf_flood_through_as(top, NULL /*inbr*/, lsa); break; default: flog_warn( EC_OSPF_LSA_UNEXPECTED, "ospf_opaque_self_originated_lsa_received: Unexpected LSA-type(%u)", lsa->data->type); return; } ospf_lsa_discard(lsa); /* List "lsas" will be deleted by caller. */ } /*------------------------------------------------------------------------* * Followings are util functions; probably be used by Opaque-LSAs only... *------------------------------------------------------------------------*/ struct ospf *oi_to_top(struct ospf_interface *oi) { struct ospf *top = NULL; struct ospf_area *area; if (oi == NULL || (area = oi->area) == NULL || (top = area->ospf) == NULL) flog_warn(EC_OSPF_LSA, "Broken relationship for \"OI -> AREA -> OSPF\"?"); return top; } frr-7.2.1/ospfd/ospf_opaque.h0000644000000000000000000001563213610377563013030 00000000000000/* * This is an implementation of rfc2370. * Copyright (C) 2001 KDD R&D Laboratories, Inc. * http://www.kddlabs.co.jp/ * * This file is part of GNU Zebra. * * GNU Zebra is free software; you can redistribute it and/or modify it * under the terms of the GNU General Public License as published by the * Free Software Foundation; either version 2, or (at your option) any * later version. * * GNU Zebra 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; see the file COPYING; if not, write to the Free Software * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA */ #ifndef _ZEBRA_OSPF_OPAQUE_H #define _ZEBRA_OSPF_OPAQUE_H #include "vty.h" #define IS_OPAQUE_LSA(type) \ ((type) == OSPF_OPAQUE_LINK_LSA || (type) == OSPF_OPAQUE_AREA_LSA \ || (type) == OSPF_OPAQUE_AS_LSA) /* * Opaque LSA's link state ID is redefined as follows. * * 24 16 8 0 * +--------+--------+--------+--------+ * |tttttttt|........|........|........| * +--------+--------+--------+--------+ * |<-Type->|<------- Opaque ID ------>| */ #define LSID_OPAQUE_TYPE_MASK 0xff000000 /* 8 bits */ #define LSID_OPAQUE_ID_MASK 0x00ffffff /* 24 bits */ #define GET_OPAQUE_TYPE(lsid) (((uint32_t)(lsid)&LSID_OPAQUE_TYPE_MASK) >> 24) #define GET_OPAQUE_ID(lsid) ((uint32_t)(lsid)&LSID_OPAQUE_ID_MASK) #define SET_OPAQUE_LSID(type, id) \ ((((unsigned)(type) << 24) & LSID_OPAQUE_TYPE_MASK) \ | ((id)&LSID_OPAQUE_ID_MASK)) /* * Opaque LSA types will be assigned by IANA. * */ #define OPAQUE_TYPE_TRAFFIC_ENGINEERING_LSA 1 #define OPAQUE_TYPE_SYCAMORE_OPTICAL_TOPOLOGY_DESC 2 #define OPAQUE_TYPE_GRACE_LSA 3 #define OPAQUE_TYPE_L1VPN_LSA 5 #define OPAQUE_TYPE_ROUTER_INFORMATION_LSA 4 #define OPAQUE_TYPE_INTER_AS_LSA 6 #define OPAQUE_TYPE_EXTENDED_PREFIX_LSA 7 #define OPAQUE_TYPE_EXTENDED_LINK_LSA 8 #define OPAQUE_TYPE_MAX 8 /* Followings types are proposed in internet-draft documents. */ #define OPAQUE_TYPE_8021_QOSPF 129 #define OPAQUE_TYPE_SECONDARY_NEIGHBOR_DISCOVERY 224 #define OPAQUE_TYPE_FLOODGATE 225 /* Ugly hack to make use of an unallocated value for wildcard matching! */ #define OPAQUE_TYPE_WILDCARD 0 #define OPAQUE_TYPE_RANGE_UNASSIGNED(type) \ (OPAQUE_TYPE_MAX <= (type) && (type) <= 127) #define OPAQUE_TYPE_RANGE_RESERVED(type) (127 < (type) && (type) <= 255) #define VALID_OPAQUE_INFO_LEN(lsahdr) \ ((ntohs((lsahdr)->length) >= sizeof(struct lsa_header)) \ && ((ntohs((lsahdr)->length) % sizeof(uint32_t)) == 0)) /* * Following section defines generic TLV (type, length, value) macros, * used for various LSA opaque usage e.g. Traffic Engineering. */ struct tlv_header { uint16_t type; /* Type of Value */ uint16_t length; /* Length of Value portion only, in bytes */ }; #define TLV_HDR_SIZE (sizeof(struct tlv_header)) #define TLV_BODY_SIZE(tlvh) (ROUNDUP(ntohs((tlvh)->length), sizeof(uint32_t))) #define TLV_SIZE(tlvh) (TLV_HDR_SIZE + TLV_BODY_SIZE(tlvh)) #define TLV_HDR_TOP(lsah) \ (struct tlv_header *)((char *)(lsah) + OSPF_LSA_HEADER_SIZE) #define TLV_HDR_NEXT(tlvh) \ (struct tlv_header *)((char *)(tlvh) + TLV_SIZE(tlvh)) #define TLV_HDR_SUBTLV(tlvh) \ (struct tlv_header *)((char *)(tlvh) + TLV_HDR_SIZE) #define TLV_DATA(tlvh) (void *)((char *)(tlvh) + TLV_HDR_SIZE) #define TLV_TYPE(tlvh) tlvh.header.type #define TLV_LEN(tlvh) tlvh.header.length #define TLV_HDR(tlvh) tlvh.header /* Following declaration concerns the Opaque LSA management */ enum lsa_opcode { REORIGINATE_THIS_LSA, REFRESH_THIS_LSA, FLUSH_THIS_LSA }; /* Prototypes. */ extern void ospf_opaque_init(void); extern void ospf_opaque_term(void); extern void ospf_opaque_finish(void); extern int ospf_opaque_type9_lsa_init(struct ospf_interface *oi); extern void ospf_opaque_type9_lsa_term(struct ospf_interface *oi); extern int ospf_opaque_type10_lsa_init(struct ospf_area *area); extern void ospf_opaque_type10_lsa_term(struct ospf_area *area); extern int ospf_opaque_type11_lsa_init(struct ospf *ospf); extern void ospf_opaque_type11_lsa_term(struct ospf *ospf); extern int ospf_register_opaque_functab( uint8_t lsa_type, uint8_t opaque_type, int (*new_if_hook)(struct interface *ifp), int (*del_if_hook)(struct interface *ifp), void (*ism_change_hook)(struct ospf_interface *oi, int old_status), void (*nsm_change_hook)(struct ospf_neighbor *nbr, int old_status), void (*config_write_router)(struct vty *vty), void (*config_write_if)(struct vty *vty, struct interface *ifp), void (*config_write_debug)(struct vty *vty), void (*show_opaque_info)(struct vty *vty, struct ospf_lsa *lsa), int (*lsa_originator)(void *arg), struct ospf_lsa *(*lsa_refresher)(struct ospf_lsa *lsa), int (*new_lsa_hook)(struct ospf_lsa *lsa), int (*del_lsa_hook)(struct ospf_lsa *lsa)); extern void ospf_delete_opaque_functab(uint8_t lsa_type, uint8_t opaque_type); extern int ospf_opaque_new_if(struct interface *ifp); extern int ospf_opaque_del_if(struct interface *ifp); extern void ospf_opaque_ism_change(struct ospf_interface *oi, int old_status); extern void ospf_opaque_nsm_change(struct ospf_neighbor *nbr, int old_status); extern void ospf_opaque_config_write_router(struct vty *vty, struct ospf *ospf); extern void ospf_opaque_config_write_if(struct vty *vty, struct interface *ifp); extern void ospf_opaque_config_write_debug(struct vty *vty); extern void show_opaque_info_detail(struct vty *vty, struct ospf_lsa *lsa); extern void ospf_opaque_lsa_dump(struct stream *s, uint16_t length); extern void ospf_opaque_lsa_originate_schedule(struct ospf_interface *oi, int *init_delay); extern struct ospf_lsa *ospf_opaque_lsa_install(struct ospf_lsa *lsa, int rt_recalc); extern struct ospf_lsa *ospf_opaque_lsa_refresh(struct ospf_lsa *lsa); extern void ospf_opaque_lsa_reoriginate_schedule(void *lsa_type_dependent, uint8_t lsa_type, uint8_t opaque_type); extern void ospf_opaque_lsa_refresh_schedule(struct ospf_lsa *lsa); extern void ospf_opaque_lsa_flush_schedule(struct ospf_lsa *lsa); extern void ospf_opaque_self_originated_lsa_received(struct ospf_neighbor *nbr, struct ospf_lsa *lsa); extern struct ospf *oi_to_top(struct ospf_interface *oi); #endif /* _ZEBRA_OSPF_OPAQUE_H */ frr-7.2.1/ospfd/ospf_packet.c0000644000000000000000000035252613610377563013006 00000000000000/* * OSPF Sending and Receiving OSPF Packets. * Copyright (C) 1999, 2000 Toshiaki Takada * * This file is part of GNU Zebra. * * GNU Zebra is free software; you can redistribute it and/or modify it * under the terms of the GNU General Public License as published by the * Free Software Foundation; either version 2, or (at your option) any * later version. * * GNU Zebra 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; see the file COPYING; if not, write to the Free Software * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA */ #include #include "monotime.h" #include "thread.h" #include "memory.h" #include "linklist.h" #include "prefix.h" #include "if.h" #include "table.h" #include "sockunion.h" #include "stream.h" #include "log.h" #include "sockopt.h" #include "checksum.h" #ifdef CRYPTO_INTERNAL #include "md5.h" #endif #include "vrf.h" #include "lib_errors.h" #include "ospfd/ospfd.h" #include "ospfd/ospf_network.h" #include "ospfd/ospf_interface.h" #include "ospfd/ospf_ism.h" #include "ospfd/ospf_asbr.h" #include "ospfd/ospf_lsa.h" #include "ospfd/ospf_lsdb.h" #include "ospfd/ospf_neighbor.h" #include "ospfd/ospf_nsm.h" #include "ospfd/ospf_packet.h" #include "ospfd/ospf_spf.h" #include "ospfd/ospf_flood.h" #include "ospfd/ospf_dump.h" #include "ospfd/ospf_errors.h" /* * OSPF Fragmentation / fragmented writes * * ospfd can support writing fragmented packets, for cases where * kernel will not fragment IP_HDRINCL and/or multicast destined * packets (ie TTBOMK all kernels, BSD, SunOS, Linux). However, * SunOS, probably BSD too, clobber the user supplied IP ID and IP * flags fields, hence user-space fragmentation will not work. * Only Linux is known to leave IP header unmolested. * Further, fragmentation really should be done the kernel, which already * supports it, and which avoids nasty IP ID state problems. * * Fragmentation of OSPF packets can be required on networks with router * with many many interfaces active in one area, or on networks with links * with low MTUs. */ #ifdef GNU_LINUX #define WANT_OSPF_WRITE_FRAGMENT #endif /* Packet Type String. */ const struct message ospf_packet_type_str[] = { {OSPF_MSG_HELLO, "Hello"}, {OSPF_MSG_DB_DESC, "Database Description"}, {OSPF_MSG_LS_REQ, "Link State Request"}, {OSPF_MSG_LS_UPD, "Link State Update"}, {OSPF_MSG_LS_ACK, "Link State Acknowledgment"}, {0}}; /* Minimum (besides OSPF_HEADER_SIZE) lengths for OSPF packets of particular types, offset is the "type" field of a packet. */ static const uint16_t ospf_packet_minlen[] = { 0, OSPF_HELLO_MIN_SIZE, OSPF_DB_DESC_MIN_SIZE, OSPF_LS_REQ_MIN_SIZE, OSPF_LS_UPD_MIN_SIZE, OSPF_LS_ACK_MIN_SIZE, }; /* Minimum (besides OSPF_LSA_HEADER_SIZE) lengths for LSAs of particular types, offset is the "LSA type" field. */ static const uint16_t ospf_lsa_minlen[] = { 0, OSPF_ROUTER_LSA_MIN_SIZE, OSPF_NETWORK_LSA_MIN_SIZE, OSPF_SUMMARY_LSA_MIN_SIZE, OSPF_SUMMARY_LSA_MIN_SIZE, OSPF_AS_EXTERNAL_LSA_MIN_SIZE, 0, OSPF_AS_EXTERNAL_LSA_MIN_SIZE, 0, 0, 0, 0, }; /* for ospf_check_auth() */ static int ospf_check_sum(struct ospf_header *); /* OSPF authentication checking function */ static int ospf_auth_type(struct ospf_interface *oi) { int auth_type; if (OSPF_IF_PARAM(oi, auth_type) == OSPF_AUTH_NOTSET) auth_type = oi->area->auth_type; else auth_type = OSPF_IF_PARAM(oi, auth_type); /* Handle case where MD5 key list is not configured aka Cisco */ if (auth_type == OSPF_AUTH_CRYPTOGRAPHIC && list_isempty(OSPF_IF_PARAM(oi, auth_crypt))) return OSPF_AUTH_NULL; return auth_type; } static struct ospf_packet *ospf_packet_new(size_t size) { struct ospf_packet *new; new = XCALLOC(MTYPE_OSPF_PACKET, sizeof(struct ospf_packet)); new->s = stream_new(size); return new; } void ospf_packet_free(struct ospf_packet *op) { if (op->s) stream_free(op->s); XFREE(MTYPE_OSPF_PACKET, op); } struct ospf_fifo *ospf_fifo_new(void) { struct ospf_fifo *new; new = XCALLOC(MTYPE_OSPF_FIFO, sizeof(struct ospf_fifo)); return new; } /* Add new packet to fifo. */ void ospf_fifo_push(struct ospf_fifo *fifo, struct ospf_packet *op) { if (fifo->tail) fifo->tail->next = op; else fifo->head = op; fifo->tail = op; fifo->count++; } /* Add new packet to head of fifo. */ static void ospf_fifo_push_head(struct ospf_fifo *fifo, struct ospf_packet *op) { op->next = fifo->head; if (fifo->tail == NULL) fifo->tail = op; fifo->head = op; fifo->count++; } /* Delete first packet from fifo. */ struct ospf_packet *ospf_fifo_pop(struct ospf_fifo *fifo) { struct ospf_packet *op; op = fifo->head; if (op) { fifo->head = op->next; if (fifo->head == NULL) fifo->tail = NULL; fifo->count--; } return op; } /* Return first fifo entry. */ struct ospf_packet *ospf_fifo_head(struct ospf_fifo *fifo) { return fifo->head; } /* Flush ospf packet fifo. */ void ospf_fifo_flush(struct ospf_fifo *fifo) { struct ospf_packet *op; struct ospf_packet *next; for (op = fifo->head; op; op = next) { next = op->next; ospf_packet_free(op); } fifo->head = fifo->tail = NULL; fifo->count = 0; } /* Free ospf packet fifo. */ void ospf_fifo_free(struct ospf_fifo *fifo) { ospf_fifo_flush(fifo); XFREE(MTYPE_OSPF_FIFO, fifo); } static void ospf_packet_add(struct ospf_interface *oi, struct ospf_packet *op) { /* Add packet to end of queue. */ ospf_fifo_push(oi->obuf, op); /* Debug of packet fifo*/ /* ospf_fifo_debug (oi->obuf); */ } static void ospf_packet_add_top(struct ospf_interface *oi, struct ospf_packet *op) { /* Add packet to head of queue. */ ospf_fifo_push_head(oi->obuf, op); /* Debug of packet fifo*/ /* ospf_fifo_debug (oi->obuf); */ } static void ospf_packet_delete(struct ospf_interface *oi) { struct ospf_packet *op; op = ospf_fifo_pop(oi->obuf); if (op) ospf_packet_free(op); } static struct ospf_packet *ospf_packet_dup(struct ospf_packet *op) { struct ospf_packet *new; if (stream_get_endp(op->s) != op->length) /* XXX size_t */ zlog_debug( "ospf_packet_dup stream %lu ospf_packet %u size mismatch", (unsigned long)STREAM_SIZE(op->s), op->length); /* Reserve space for MD5 authentication that may be added later. */ new = ospf_packet_new(stream_get_endp(op->s) + OSPF_AUTH_MD5_SIZE); stream_copy(new->s, op->s); new->dst = op->dst; new->length = op->length; return new; } /* XXX inline */ static unsigned int ospf_packet_authspace(struct ospf_interface *oi) { int auth = 0; if (ospf_auth_type(oi) == OSPF_AUTH_CRYPTOGRAPHIC) auth = OSPF_AUTH_MD5_SIZE; return auth; } static unsigned int ospf_packet_max(struct ospf_interface *oi) { int max; max = oi->ifp->mtu - ospf_packet_authspace(oi); max -= (OSPF_HEADER_SIZE + sizeof(struct ip)); return max; } static int ospf_check_md5_digest(struct ospf_interface *oi, struct ospf_header *ospfh) { #ifdef CRYPTO_OPENSSL EVP_MD_CTX *ctx; #elif CRYPTO_INTERNAL MD5_CTX ctx; #endif unsigned char digest[OSPF_AUTH_MD5_SIZE]; struct crypt_key *ck; struct ospf_neighbor *nbr; uint16_t length = ntohs(ospfh->length); /* Get secret key. */ ck = ospf_crypt_key_lookup(OSPF_IF_PARAM(oi, auth_crypt), ospfh->u.crypt.key_id); if (ck == NULL) { flog_warn(EC_OSPF_MD5, "interface %s: ospf_check_md5 no key %d", IF_NAME(oi), ospfh->u.crypt.key_id); return 0; } /* check crypto seqnum. */ nbr = ospf_nbr_lookup_by_routerid(oi->nbrs, &ospfh->router_id); if (nbr && ntohl(nbr->crypt_seqnum) > ntohl(ospfh->u.crypt.crypt_seqnum)) { flog_warn( EC_OSPF_MD5, "interface %s: ospf_check_md5 bad sequence %d (expect %d)", IF_NAME(oi), ntohl(ospfh->u.crypt.crypt_seqnum), ntohl(nbr->crypt_seqnum)); return 0; } /* Generate a digest for the ospf packet - their digest + our digest. */ #ifdef CRYPTO_OPENSSL unsigned int md5_size = OSPF_AUTH_MD5_SIZE; ctx = EVP_MD_CTX_new(); EVP_DigestInit(ctx, EVP_md5()); EVP_DigestUpdate(ctx, ospfh, length); EVP_DigestUpdate(ctx, ck->auth_key, OSPF_AUTH_MD5_SIZE); EVP_DigestFinal(ctx, digest, &md5_size); EVP_MD_CTX_free(ctx); #elif CRYPTO_INTERNAL memset(&ctx, 0, sizeof(ctx)); MD5Init(&ctx); MD5Update(&ctx, ospfh, length); MD5Update(&ctx, ck->auth_key, OSPF_AUTH_MD5_SIZE); MD5Final(digest, &ctx); #endif /* compare the two */ if (memcmp((caddr_t)ospfh + length, digest, OSPF_AUTH_MD5_SIZE)) { flog_warn(EC_OSPF_MD5, "interface %s: ospf_check_md5 checksum mismatch", IF_NAME(oi)); return 0; } /* save neighbor's crypt_seqnum */ if (nbr) nbr->crypt_seqnum = ospfh->u.crypt.crypt_seqnum; return 1; } /* This function is called from ospf_write(), it will detect the authentication scheme and if it is MD5, it will change the sequence and update the MD5 digest. */ static int ospf_make_md5_digest(struct ospf_interface *oi, struct ospf_packet *op) { struct ospf_header *ospfh; unsigned char digest[OSPF_AUTH_MD5_SIZE] = {0}; #ifdef CRYPTO_OPENSSL EVP_MD_CTX *ctx; #elif CRYPTO_INTERNAL MD5_CTX ctx; #endif void *ibuf; uint32_t t; struct crypt_key *ck; const uint8_t *auth_key; ibuf = STREAM_DATA(op->s); ospfh = (struct ospf_header *)ibuf; if (ntohs(ospfh->auth_type) != OSPF_AUTH_CRYPTOGRAPHIC) return 0; /* We do this here so when we dup a packet, we don't have to waste CPU rewriting other headers. Note that quagga_time /deliberately/ is not used here */ t = (time(NULL) & 0xFFFFFFFF); if (t > oi->crypt_seqnum) oi->crypt_seqnum = t; else oi->crypt_seqnum++; ospfh->u.crypt.crypt_seqnum = htonl(oi->crypt_seqnum); /* Get MD5 Authentication key from auth_key list. */ if (list_isempty(OSPF_IF_PARAM(oi, auth_crypt))) auth_key = (const uint8_t *)digest; else { ck = listgetdata(listtail(OSPF_IF_PARAM(oi, auth_crypt))); auth_key = ck->auth_key; } /* Generate a digest for the entire packet + our secret key. */ #ifdef CRYPTO_OPENSSL unsigned int md5_size = OSPF_AUTH_MD5_SIZE; ctx = EVP_MD_CTX_new(); EVP_DigestInit(ctx, EVP_md5()); EVP_DigestUpdate(ctx, ibuf, ntohs(ospfh->length)); EVP_DigestUpdate(ctx, auth_key, OSPF_AUTH_MD5_SIZE); EVP_DigestFinal(ctx, digest, &md5_size); EVP_MD_CTX_free(ctx); #elif CRYPTO_INTERNAL memset(&ctx, 0, sizeof(ctx)); MD5Init(&ctx); MD5Update(&ctx, ibuf, ntohs(ospfh->length)); MD5Update(&ctx, auth_key, OSPF_AUTH_MD5_SIZE); MD5Final(digest, &ctx); #endif /* Append md5 digest to the end of the stream. */ stream_put(op->s, digest, OSPF_AUTH_MD5_SIZE); /* We do *NOT* increment the OSPF header length. */ op->length = ntohs(ospfh->length) + OSPF_AUTH_MD5_SIZE; if (stream_get_endp(op->s) != op->length) /* XXX size_t */ flog_warn( EC_OSPF_MD5, "ospf_make_md5_digest: length mismatch stream %lu ospf_packet %u", (unsigned long)stream_get_endp(op->s), op->length); return OSPF_AUTH_MD5_SIZE; } static int ospf_ls_req_timer(struct thread *thread) { struct ospf_neighbor *nbr; nbr = THREAD_ARG(thread); nbr->t_ls_req = NULL; /* Send Link State Request. */ if (ospf_ls_request_count(nbr)) ospf_ls_req_send(nbr); /* Set Link State Request retransmission timer. */ OSPF_NSM_TIMER_ON(nbr->t_ls_req, ospf_ls_req_timer, nbr->v_ls_req); return 0; } void ospf_ls_req_event(struct ospf_neighbor *nbr) { if (nbr->t_ls_req) { thread_cancel(nbr->t_ls_req); nbr->t_ls_req = NULL; } nbr->t_ls_req = NULL; thread_add_event(master, ospf_ls_req_timer, nbr, 0, &nbr->t_ls_req); } /* Cyclic timer function. Fist registered in ospf_nbr_new () in ospf_neighbor.c */ int ospf_ls_upd_timer(struct thread *thread) { struct ospf_neighbor *nbr; nbr = THREAD_ARG(thread); nbr->t_ls_upd = NULL; /* Send Link State Update. */ if (ospf_ls_retransmit_count(nbr) > 0) { struct list *update; struct ospf_lsdb *lsdb; int i; int retransmit_interval; retransmit_interval = OSPF_IF_PARAM(nbr->oi, retransmit_interval); lsdb = &nbr->ls_rxmt; update = list_new(); for (i = OSPF_MIN_LSA; i < OSPF_MAX_LSA; i++) { struct route_table *table = lsdb->type[i].db; struct route_node *rn; for (rn = route_top(table); rn; rn = route_next(rn)) { struct ospf_lsa *lsa; if ((lsa = rn->info) != NULL) { /* Don't retransmit an LSA if we received it within the last RxmtInterval seconds - this is to allow the neighbour a chance to acknowledge the LSA as it may have ben just received before the retransmit timer fired. This is a small tweak to what is in the RFC, but it will cut out out a lot of retransmit traffic - MAG */ if (monotime_since(&lsa->tv_recv, NULL) >= retransmit_interval * 1000000LL) listnode_add(update, rn->info); } } } if (listcount(update) > 0) ospf_ls_upd_send(nbr, update, OSPF_SEND_PACKET_DIRECT, 0); list_delete(&update); } /* Set LS Update retransmission timer. */ OSPF_NSM_TIMER_ON(nbr->t_ls_upd, ospf_ls_upd_timer, nbr->v_ls_upd); return 0; } int ospf_ls_ack_timer(struct thread *thread) { struct ospf_interface *oi; oi = THREAD_ARG(thread); oi->t_ls_ack = NULL; /* Send Link State Acknowledgment. */ if (listcount(oi->ls_ack) > 0) ospf_ls_ack_send_delayed(oi); /* Set LS Ack timer. */ OSPF_ISM_TIMER_ON(oi->t_ls_ack, ospf_ls_ack_timer, oi->v_ls_ack); return 0; } #ifdef WANT_OSPF_WRITE_FRAGMENT static void ospf_write_frags(int fd, struct ospf_packet *op, struct ip *iph, struct msghdr *msg, unsigned int maxdatasize, unsigned int mtu, int flags, uint8_t type) { #define OSPF_WRITE_FRAG_SHIFT 3 uint16_t offset; struct iovec *iovp; int ret; assert(op->length == stream_get_endp(op->s)); assert(msg->msg_iovlen == 2); /* we can but try. * * SunOS, BSD and BSD derived kernels likely will clear ip_id, as * well as the IP_MF flag, making this all quite pointless. * * However, for a system on which IP_MF is left alone, and ip_id left * alone or else which sets same ip_id for each fragment this might * work, eg linux. * * XXX-TODO: It would be much nicer to have the kernel's use their * existing fragmentation support to do this for us. Bugs/RFEs need to * be raised against the various kernels. */ /* set More Frag */ iph->ip_off |= IP_MF; /* ip frag offset is expressed in units of 8byte words */ offset = maxdatasize >> OSPF_WRITE_FRAG_SHIFT; iovp = &msg->msg_iov[1]; while ((stream_get_endp(op->s) - stream_get_getp(op->s)) > maxdatasize) { /* data length of this frag is to next offset value */ iovp->iov_len = offset << OSPF_WRITE_FRAG_SHIFT; iph->ip_len = iovp->iov_len + sizeof(struct ip); assert(iph->ip_len <= mtu); sockopt_iphdrincl_swab_htosys(iph); ret = sendmsg(fd, msg, flags); sockopt_iphdrincl_swab_systoh(iph); if (ret < 0) flog_err( EC_LIB_SOCKET, "*** ospf_write_frags: sendmsg failed to %s," " id %d, off %d, len %d, mtu %u failed with %s", inet_ntoa(iph->ip_dst), iph->ip_id, iph->ip_off, iph->ip_len, mtu, safe_strerror(errno)); if (IS_DEBUG_OSPF_PACKET(type - 1, SEND)) { zlog_debug( "ospf_write_frags: sent id %d, off %d, len %d to %s\n", iph->ip_id, iph->ip_off, iph->ip_len, inet_ntoa(iph->ip_dst)); if (IS_DEBUG_OSPF_PACKET(type - 1, DETAIL)) { zlog_debug( "-----------------IP Header Dump----------------------"); ospf_ip_header_dump(iph); zlog_debug( "-----------------------------------------------------"); } } iph->ip_off += offset; stream_forward_getp(op->s, iovp->iov_len); iovp->iov_base = stream_pnt(op->s); } /* setup for final fragment */ iovp->iov_len = stream_get_endp(op->s) - stream_get_getp(op->s); iph->ip_len = iovp->iov_len + sizeof(struct ip); iph->ip_off &= (~IP_MF); } #endif /* WANT_OSPF_WRITE_FRAGMENT */ static int ospf_write(struct thread *thread) { struct ospf *ospf = THREAD_ARG(thread); struct ospf_interface *oi; struct ospf_interface *last_serviced_oi = NULL; struct ospf_packet *op; struct sockaddr_in sa_dst; struct ip iph; struct msghdr msg; struct iovec iov[2]; uint8_t type; int ret; int flags = 0; struct listnode *node; #ifdef WANT_OSPF_WRITE_FRAGMENT static uint16_t ipid = 0; uint16_t maxdatasize; #endif /* WANT_OSPF_WRITE_FRAGMENT */ #define OSPF_WRITE_IPHL_SHIFT 2 int pkt_count = 0; #ifdef GNU_LINUX unsigned char cmsgbuf[64] = {}; struct cmsghdr *cm = (struct cmsghdr *)cmsgbuf; struct in_pktinfo *pi; #endif if (ospf->fd < 0 || ospf->oi_running == 0) { if (IS_DEBUG_OSPF_EVENT) zlog_debug( "ospf_write failed to send, fd %d, instance %u" ,ospf->fd, ospf->oi_running); return -1; } node = listhead(ospf->oi_write_q); assert(node); oi = listgetdata(node); #ifdef WANT_OSPF_WRITE_FRAGMENT /* seed ipid static with low order bits of time */ if (ipid == 0) ipid = (time(NULL) & 0xffff); #endif /* WANT_OSPF_WRITE_FRAGMENT */ while ((pkt_count < ospf->write_oi_count) && oi && (last_serviced_oi != oi)) { /* If there is only packet in the queue, the oi is removed from write-q, so fix up the last interface that was serviced */ if (last_serviced_oi == NULL) { last_serviced_oi = oi; } pkt_count++; #ifdef WANT_OSPF_WRITE_FRAGMENT /* convenience - max OSPF data per packet */ maxdatasize = oi->ifp->mtu - sizeof(struct ip); #endif /* WANT_OSPF_WRITE_FRAGMENT */ /* Get one packet from queue. */ op = ospf_fifo_head(oi->obuf); assert(op); assert(op->length >= OSPF_HEADER_SIZE); if (op->dst.s_addr == htonl(OSPF_ALLSPFROUTERS) || op->dst.s_addr == htonl(OSPF_ALLDROUTERS)) ospf_if_ipmulticast(ospf, oi->address, oi->ifp->ifindex); /* Rewrite the md5 signature & update the seq */ ospf_make_md5_digest(oi, op); /* Retrieve OSPF packet type. */ stream_set_getp(op->s, 1); type = stream_getc(op->s); /* reset get pointer */ stream_set_getp(op->s, 0); memset(&iph, 0, sizeof(struct ip)); memset(&sa_dst, 0, sizeof(sa_dst)); sa_dst.sin_family = AF_INET; #ifdef HAVE_STRUCT_SOCKADDR_IN_SIN_LEN sa_dst.sin_len = sizeof(sa_dst); #endif /* HAVE_STRUCT_SOCKADDR_IN_SIN_LEN */ sa_dst.sin_addr = op->dst; sa_dst.sin_port = htons(0); /* Set DONTROUTE flag if dst is unicast. */ if (oi->type != OSPF_IFTYPE_VIRTUALLINK) if (!IN_MULTICAST(htonl(op->dst.s_addr))) flags = MSG_DONTROUTE; iph.ip_hl = sizeof(struct ip) >> OSPF_WRITE_IPHL_SHIFT; /* it'd be very strange for header to not be 4byte-word aligned * but.. */ if (sizeof(struct ip) > (unsigned int)(iph.ip_hl << OSPF_WRITE_IPHL_SHIFT)) iph.ip_hl++; /* we presume sizeof struct ip cant overflow ip_hl.. */ iph.ip_v = IPVERSION; iph.ip_tos = IPTOS_PREC_INTERNETCONTROL; iph.ip_len = (iph.ip_hl << OSPF_WRITE_IPHL_SHIFT) + op->length; #if defined(__DragonFly__) /* * DragonFly's raw socket expects ip_len/ip_off in network byte * order. */ iph.ip_len = htons(iph.ip_len); #endif #ifdef WANT_OSPF_WRITE_FRAGMENT /* XXX-MT: not thread-safe at all.. * XXX: this presumes this is only programme sending OSPF * packets * otherwise, no guarantee ipid will be unique */ iph.ip_id = ++ipid; #endif /* WANT_OSPF_WRITE_FRAGMENT */ iph.ip_off = 0; if (oi->type == OSPF_IFTYPE_VIRTUALLINK) iph.ip_ttl = OSPF_VL_IP_TTL; else iph.ip_ttl = OSPF_IP_TTL; iph.ip_p = IPPROTO_OSPFIGP; iph.ip_sum = 0; iph.ip_src.s_addr = oi->address->u.prefix4.s_addr; iph.ip_dst.s_addr = op->dst.s_addr; memset(&msg, 0, sizeof(msg)); msg.msg_name = (caddr_t)&sa_dst; msg.msg_namelen = sizeof(sa_dst); msg.msg_iov = iov; msg.msg_iovlen = 2; iov[0].iov_base = (char *)&iph; iov[0].iov_len = iph.ip_hl << OSPF_WRITE_IPHL_SHIFT; iov[1].iov_base = stream_pnt(op->s); iov[1].iov_len = op->length; #ifdef GNU_LINUX msg.msg_control = (caddr_t)cm; cm->cmsg_level = SOL_IP; cm->cmsg_type = IP_PKTINFO; cm->cmsg_len = CMSG_LEN(sizeof(struct in_pktinfo)); pi = (struct in_pktinfo *)CMSG_DATA(cm); pi->ipi_ifindex = oi->ifp->ifindex; msg.msg_controllen = cm->cmsg_len; #endif /* Sadly we can not rely on kernels to fragment packets * because of either IP_HDRINCL and/or multicast * destination being set. */ #ifdef WANT_OSPF_WRITE_FRAGMENT if (op->length > maxdatasize) ospf_write_frags(ospf->fd, op, &iph, &msg, maxdatasize, oi->ifp->mtu, flags, type); #endif /* WANT_OSPF_WRITE_FRAGMENT */ /* send final fragment (could be first) */ sockopt_iphdrincl_swab_htosys(&iph); ret = sendmsg(ospf->fd, &msg, flags); sockopt_iphdrincl_swab_systoh(&iph); if (IS_DEBUG_OSPF_EVENT) zlog_debug( "ospf_write to %s, " "id %d, off %d, len %d, interface %s, mtu %u:", inet_ntoa(iph.ip_dst), iph.ip_id, iph.ip_off, iph.ip_len, oi->ifp->name, oi->ifp->mtu); if (ret < 0) flog_err( EC_LIB_SOCKET, "*** sendmsg in ospf_write failed to %s, " "id %d, off %d, len %d, interface %s, mtu %u: %s", inet_ntoa(iph.ip_dst), iph.ip_id, iph.ip_off, iph.ip_len, oi->ifp->name, oi->ifp->mtu, safe_strerror(errno)); /* Show debug sending packet. */ if (IS_DEBUG_OSPF_PACKET(type - 1, SEND)) { if (IS_DEBUG_OSPF_PACKET(type - 1, DETAIL)) { zlog_debug( "-----------------------------------------------------"); ospf_ip_header_dump(&iph); stream_set_getp(op->s, 0); ospf_packet_dump(op->s); } zlog_debug("%s sent to [%s] via [%s].", lookup_msg(ospf_packet_type_str, type, NULL), inet_ntoa(op->dst), IF_NAME(oi)); if (IS_DEBUG_OSPF_PACKET(type - 1, DETAIL)) zlog_debug( "-----------------------------------------------------"); } switch (type) { case OSPF_MSG_HELLO: oi->hello_out++; break; case OSPF_MSG_DB_DESC: oi->db_desc_out++; break; case OSPF_MSG_LS_REQ: oi->ls_req_out++; break; case OSPF_MSG_LS_UPD: oi->ls_upd_out++; break; case OSPF_MSG_LS_ACK: oi->ls_ack_out++; break; default: break; } /* Now delete packet from queue. */ ospf_packet_delete(oi); /* Move this interface to the tail of write_q to serve everyone in a round robin fashion */ list_delete_node(ospf->oi_write_q, node); if (ospf_fifo_head(oi->obuf) == NULL) { oi->on_write_q = 0; last_serviced_oi = NULL; oi = NULL; } else { listnode_add(ospf->oi_write_q, oi); } /* Setup to service from the head of the queue again */ if (!list_isempty(ospf->oi_write_q)) { node = listhead(ospf->oi_write_q); oi = listgetdata(node); } } /* If packets still remain in queue, call write thread. */ if (!list_isempty(ospf->oi_write_q)) thread_add_write(master, ospf_write, ospf, ospf->fd, &ospf->t_write); return 0; } /* OSPF Hello message read -- RFC2328 Section 10.5. */ static void ospf_hello(struct ip *iph, struct ospf_header *ospfh, struct stream *s, struct ospf_interface *oi, int size) { struct ospf_hello *hello; struct ospf_neighbor *nbr; int old_state; struct prefix p; /* increment statistics. */ oi->hello_in++; hello = (struct ospf_hello *)stream_pnt(s); /* If Hello is myself, silently discard. */ if (IPV4_ADDR_SAME(&ospfh->router_id, &oi->ospf->router_id)) { if (IS_DEBUG_OSPF_PACKET(ospfh->type - 1, RECV)) { zlog_debug( "ospf_header[%s/%s]: selforiginated, " "dropping.", lookup_msg(ospf_packet_type_str, ospfh->type, NULL), inet_ntoa(iph->ip_src)); } return; } /* get neighbor prefix. */ p.family = AF_INET; p.prefixlen = ip_masklen(hello->network_mask); p.u.prefix4 = iph->ip_src; /* Compare network mask. */ /* Checking is ignored for Point-to-Point and Virtual link. */ if (oi->type != OSPF_IFTYPE_POINTOPOINT && oi->type != OSPF_IFTYPE_VIRTUALLINK) if (oi->address->prefixlen != p.prefixlen) { flog_warn( EC_OSPF_PACKET, "Packet %s [Hello:RECV]: NetworkMask mismatch on %s (configured prefix length is %d, but hello packet indicates %d).", inet_ntoa(ospfh->router_id), IF_NAME(oi), (int)oi->address->prefixlen, (int)p.prefixlen); return; } /* Compare Router Dead Interval. */ if (OSPF_IF_PARAM(oi, v_wait) != ntohl(hello->dead_interval)) { flog_warn(EC_OSPF_PACKET, "Packet %s [Hello:RECV]: RouterDeadInterval mismatch " "(expected %u, but received %u).", inet_ntoa(ospfh->router_id), OSPF_IF_PARAM(oi, v_wait), ntohl(hello->dead_interval)); return; } /* Compare Hello Interval - ignored if fast-hellos are set. */ if (OSPF_IF_PARAM(oi, fast_hello) == 0) { if (OSPF_IF_PARAM(oi, v_hello) != ntohs(hello->hello_interval)) { flog_warn( EC_OSPF_PACKET, "Packet %s [Hello:RECV]: HelloInterval mismatch " "(expected %u, but received %u).", inet_ntoa(ospfh->router_id), OSPF_IF_PARAM(oi, v_hello), ntohs(hello->hello_interval)); return; } } if (IS_DEBUG_OSPF_EVENT) zlog_debug("Packet %s [Hello:RECV]: Options %s vrf %s", inet_ntoa(ospfh->router_id), ospf_options_dump(hello->options), ospf_vrf_id_to_name(oi->ospf->vrf_id)); /* Compare options. */ #define REJECT_IF_TBIT_ON 1 /* XXX */ #ifdef REJECT_IF_TBIT_ON if (CHECK_FLAG(hello->options, OSPF_OPTION_MT)) { /* * This router does not support non-zero TOS. * Drop this Hello packet not to establish neighbor * relationship. */ flog_warn(EC_OSPF_PACKET, "Packet %s [Hello:RECV]: T-bit on, drop it.", inet_ntoa(ospfh->router_id)); return; } #endif /* REJECT_IF_TBIT_ON */ if (CHECK_FLAG(oi->ospf->config, OSPF_OPAQUE_CAPABLE) && CHECK_FLAG(hello->options, OSPF_OPTION_O)) { /* * This router does know the correct usage of O-bit * the bit should be set in DD packet only. */ flog_warn(EC_OSPF_PACKET, "Packet %s [Hello:RECV]: O-bit abuse?", inet_ntoa(ospfh->router_id)); #ifdef STRICT_OBIT_USAGE_CHECK return; /* Reject this packet. */ #else /* STRICT_OBIT_USAGE_CHECK */ UNSET_FLAG(hello->options, OSPF_OPTION_O); /* Ignore O-bit. */ #endif /* STRICT_OBIT_USAGE_CHECK */ } /* new for NSSA is to ensure that NP is on and E is off */ if (oi->area->external_routing == OSPF_AREA_NSSA) { if (!(CHECK_FLAG(OPTIONS(oi), OSPF_OPTION_NP) && CHECK_FLAG(hello->options, OSPF_OPTION_NP) && !CHECK_FLAG(OPTIONS(oi), OSPF_OPTION_E) && !CHECK_FLAG(hello->options, OSPF_OPTION_E))) { flog_warn( EC_OSPF_PACKET, "NSSA-Packet-%s[Hello:RECV]: my options: %x, his options %x", inet_ntoa(ospfh->router_id), OPTIONS(oi), hello->options); return; } if (IS_DEBUG_OSPF_NSSA) zlog_debug("NSSA-Hello:RECV:Packet from %s:", inet_ntoa(ospfh->router_id)); } else /* The setting of the E-bit found in the Hello Packet's Options field must match this area's ExternalRoutingCapability A mismatch causes processing to stop and the packet to be dropped. The setting of the rest of the bits in the Hello Packet's Options field should be ignored. */ if (CHECK_FLAG(OPTIONS(oi), OSPF_OPTION_E) != CHECK_FLAG(hello->options, OSPF_OPTION_E)) { flog_warn( EC_OSPF_PACKET, "Packet %s [Hello:RECV]: my options: %x, his options %x", inet_ntoa(ospfh->router_id), OPTIONS(oi), hello->options); return; } /* get neighbour struct */ nbr = ospf_nbr_get(oi, ospfh, iph, &p); /* neighbour must be valid, ospf_nbr_get creates if none existed */ assert(nbr); old_state = nbr->state; /* Add event to thread. */ OSPF_NSM_EVENT_SCHEDULE(nbr, NSM_PacketReceived); /* RFC2328 Section 9.5.1 If the router is not eligible to become Designated Router, (snip) It must also send an Hello Packet in reply to an Hello Packet received from any eligible neighbor (other than the current Designated Router and Backup Designated Router). */ if (oi->type == OSPF_IFTYPE_NBMA) if (PRIORITY(oi) == 0 && hello->priority > 0 && IPV4_ADDR_CMP(&DR(oi), &iph->ip_src) && IPV4_ADDR_CMP(&BDR(oi), &iph->ip_src)) OSPF_NSM_TIMER_ON(nbr->t_hello_reply, ospf_hello_reply_timer, OSPF_HELLO_REPLY_DELAY); /* on NBMA network type, it happens to receive bidirectional Hello packet without advance 1-Way Received event. To avoid incorrect DR-seletion, raise 1-Way Received event.*/ if (oi->type == OSPF_IFTYPE_NBMA && (old_state == NSM_Down || old_state == NSM_Attempt)) { OSPF_NSM_EVENT_SCHEDULE(nbr, NSM_OneWayReceived); nbr->priority = hello->priority; nbr->d_router = hello->d_router; nbr->bd_router = hello->bd_router; return; } if (ospf_nbr_bidirectional(&oi->ospf->router_id, hello->neighbors, size - OSPF_HELLO_MIN_SIZE)) { OSPF_NSM_EVENT_SCHEDULE(nbr, NSM_TwoWayReceived); nbr->options |= hello->options; } else { OSPF_NSM_EVENT_SCHEDULE(nbr, NSM_OneWayReceived); /* Set neighbor information. */ nbr->priority = hello->priority; nbr->d_router = hello->d_router; nbr->bd_router = hello->bd_router; return; } /* If neighbor itself declares DR and no BDR exists, cause event BackupSeen */ if (IPV4_ADDR_SAME(&nbr->address.u.prefix4, &hello->d_router)) if (hello->bd_router.s_addr == 0 && oi->state == ISM_Waiting) OSPF_ISM_EVENT_SCHEDULE(oi, ISM_BackupSeen); /* neighbor itself declares BDR. */ if (oi->state == ISM_Waiting && IPV4_ADDR_SAME(&nbr->address.u.prefix4, &hello->bd_router)) OSPF_ISM_EVENT_SCHEDULE(oi, ISM_BackupSeen); /* had not previously. */ if ((IPV4_ADDR_SAME(&nbr->address.u.prefix4, &hello->d_router) && IPV4_ADDR_CMP(&nbr->address.u.prefix4, &nbr->d_router)) || (IPV4_ADDR_CMP(&nbr->address.u.prefix4, &hello->d_router) && IPV4_ADDR_SAME(&nbr->address.u.prefix4, &nbr->d_router))) OSPF_ISM_EVENT_SCHEDULE(oi, ISM_NeighborChange); /* had not previously. */ if ((IPV4_ADDR_SAME(&nbr->address.u.prefix4, &hello->bd_router) && IPV4_ADDR_CMP(&nbr->address.u.prefix4, &nbr->bd_router)) || (IPV4_ADDR_CMP(&nbr->address.u.prefix4, &hello->bd_router) && IPV4_ADDR_SAME(&nbr->address.u.prefix4, &nbr->bd_router))) OSPF_ISM_EVENT_SCHEDULE(oi, ISM_NeighborChange); /* Neighbor priority check. */ if (nbr->priority >= 0 && nbr->priority != hello->priority) OSPF_ISM_EVENT_SCHEDULE(oi, ISM_NeighborChange); /* Set neighbor information. */ nbr->priority = hello->priority; nbr->d_router = hello->d_router; nbr->bd_router = hello->bd_router; } /* Save DD flags/options/Seqnum received. */ static void ospf_db_desc_save_current(struct ospf_neighbor *nbr, struct ospf_db_desc *dd) { nbr->last_recv.flags = dd->flags; nbr->last_recv.options = dd->options; nbr->last_recv.dd_seqnum = ntohl(dd->dd_seqnum); } /* Process rest of DD packet. */ static void ospf_db_desc_proc(struct stream *s, struct ospf_interface *oi, struct ospf_neighbor *nbr, struct ospf_db_desc *dd, uint16_t size) { struct ospf_lsa *new, *find; struct lsa_header *lsah; stream_forward_getp(s, OSPF_DB_DESC_MIN_SIZE); for (size -= OSPF_DB_DESC_MIN_SIZE; size >= OSPF_LSA_HEADER_SIZE; size -= OSPF_LSA_HEADER_SIZE) { lsah = (struct lsa_header *)stream_pnt(s); stream_forward_getp(s, OSPF_LSA_HEADER_SIZE); /* Unknown LS type. */ if (lsah->type < OSPF_MIN_LSA || lsah->type >= OSPF_MAX_LSA) { flog_warn(EC_OSPF_PACKET, "Packet [DD:RECV]: Unknown LS type %d.", lsah->type); OSPF_NSM_EVENT_SCHEDULE(nbr, NSM_SeqNumberMismatch); return; } if (IS_OPAQUE_LSA(lsah->type) && !CHECK_FLAG(nbr->options, OSPF_OPTION_O)) { flog_warn(EC_OSPF_PACKET, "LSA[Type%d:%s]: Opaque capability mismatch?", lsah->type, inet_ntoa(lsah->id)); OSPF_NSM_EVENT_SCHEDULE(nbr, NSM_SeqNumberMismatch); return; } switch (lsah->type) { case OSPF_AS_EXTERNAL_LSA: case OSPF_OPAQUE_AS_LSA: /* Check for stub area. Reject if AS-External from stub but allow if from NSSA. */ if (oi->area->external_routing == OSPF_AREA_STUB) { flog_warn( EC_OSPF_PACKET, "Packet [DD:RECV]: LSA[Type%d:%s] from %s area.", lsah->type, inet_ntoa(lsah->id), (oi->area->external_routing == OSPF_AREA_STUB) ? "STUB" : "NSSA"); OSPF_NSM_EVENT_SCHEDULE(nbr, NSM_SeqNumberMismatch); return; } break; default: break; } /* Create LS-request object. */ new = ospf_ls_request_new(lsah); /* Lookup received LSA, then add LS request list. */ find = ospf_lsa_lookup_by_header(oi->area, lsah); /* ospf_lsa_more_recent is fine with NULL pointers */ switch (ospf_lsa_more_recent(find, new)) { case -1: /* Neighbour has a more recent LSA, we must request it */ ospf_ls_request_add(nbr, new); /* fallthru */ case 0: /* If we have a copy of this LSA, it's either less * recent * and we're requesting it from neighbour (the case * above), or * it's as recent and we both have same copy (this * case). * * In neither of these two cases is there any point in * describing our copy of the LSA to the neighbour in a * DB-Summary packet, if we're still intending to do so. * * See: draft-ogier-ospf-dbex-opt-00.txt, describing the * backward compatible optimisation to OSPF DB Exchange * / * DB Description process implemented here. */ if (find) ospf_lsdb_delete(&nbr->db_sum, find); ospf_lsa_discard(new); break; default: /* We have the more recent copy, nothing specific to do: * - no need to request neighbours stale copy * - must leave DB summary list copy alone */ if (IS_DEBUG_OSPF_EVENT) zlog_debug( "Packet [DD:RECV]: LSA received Type %d, " "ID %s is not recent.", lsah->type, inet_ntoa(lsah->id)); ospf_lsa_discard(new); } } /* Master */ if (IS_SET_DD_MS(nbr->dd_flags)) { nbr->dd_seqnum++; /* Both sides have no More, then we're done with Exchange */ if (!IS_SET_DD_M(dd->flags) && !IS_SET_DD_M(nbr->dd_flags)) OSPF_NSM_EVENT_SCHEDULE(nbr, NSM_ExchangeDone); else ospf_db_desc_send(nbr); } /* Slave */ else { nbr->dd_seqnum = ntohl(dd->dd_seqnum); /* Send DD packet in reply. * * Must be done to acknowledge the Master's DD, regardless of * whether we have more LSAs ourselves to describe. * * This function will clear the 'More' bit, if after this DD * we have no more LSAs to describe to the master.. */ ospf_db_desc_send(nbr); /* Slave can raise ExchangeDone now, if master is also done */ if (!IS_SET_DD_M(dd->flags) && !IS_SET_DD_M(nbr->dd_flags)) OSPF_NSM_EVENT_SCHEDULE(nbr, NSM_ExchangeDone); } /* Save received neighbor values from DD. */ ospf_db_desc_save_current(nbr, dd); if (!nbr->t_ls_req) ospf_ls_req_send(nbr); } static int ospf_db_desc_is_dup(struct ospf_db_desc *dd, struct ospf_neighbor *nbr) { /* Is DD duplicated? */ if (dd->options == nbr->last_recv.options && dd->flags == nbr->last_recv.flags && dd->dd_seqnum == htonl(nbr->last_recv.dd_seqnum)) return 1; return 0; } /* OSPF Database Description message read -- RFC2328 Section 10.6. */ static void ospf_db_desc(struct ip *iph, struct ospf_header *ospfh, struct stream *s, struct ospf_interface *oi, uint16_t size) { struct ospf_db_desc *dd; struct ospf_neighbor *nbr; /* Increment statistics. */ oi->db_desc_in++; dd = (struct ospf_db_desc *)stream_pnt(s); nbr = ospf_nbr_lookup(oi, iph, ospfh); if (nbr == NULL) { flog_warn(EC_OSPF_PACKET, "Packet[DD]: Unknown Neighbor %s", inet_ntoa(ospfh->router_id)); return; } /* Check MTU. */ if ((OSPF_IF_PARAM(oi, mtu_ignore) == 0) && (ntohs(dd->mtu) > oi->ifp->mtu)) { flog_warn( EC_OSPF_PACKET, "Packet[DD]: Neighbor %s MTU %u is larger than [%s]'s MTU %u", inet_ntoa(nbr->router_id), ntohs(dd->mtu), IF_NAME(oi), oi->ifp->mtu); return; } /* * XXX HACK by Hasso Tepper. Setting N/P bit in NSSA area DD packets is * not * required. In fact at least JunOS sends DD packets with P bit clear. * Until proper solution is developped, this hack should help. * * Update: According to the RFCs, N bit is specified /only/ for Hello * options, unfortunately its use in DD options is not specified. Hence * some * implementations follow E-bit semantics and set it in DD options, and * some * treat it as unspecified and hence follow the directive "default for * options is clear", ie unset. * * Reset the flag, as ospfd follows E-bit semantics. */ if ((oi->area->external_routing == OSPF_AREA_NSSA) && (CHECK_FLAG(nbr->options, OSPF_OPTION_NP)) && (!CHECK_FLAG(dd->options, OSPF_OPTION_NP))) { if (IS_DEBUG_OSPF_EVENT) zlog_debug( "Packet[DD]: Neighbour %s: Has NSSA capability, sends with N bit clear in DD options", inet_ntoa(nbr->router_id)); SET_FLAG(dd->options, OSPF_OPTION_NP); } #ifdef REJECT_IF_TBIT_ON if (CHECK_FLAG(dd->options, OSPF_OPTION_MT)) { /* * In Hello protocol, optional capability must have checked * to prevent this T-bit enabled router be my neighbor. */ flog_warn(EC_OSPF_PACKET, "Packet[DD]: Neighbor %s: T-bit on?", inet_ntoa(nbr->router_id)); return; } #endif /* REJECT_IF_TBIT_ON */ if (CHECK_FLAG(dd->options, OSPF_OPTION_O) && !CHECK_FLAG(oi->ospf->config, OSPF_OPAQUE_CAPABLE)) { /* * This node is not configured to handle O-bit, for now. * Clear it to ignore unsupported capability proposed by * neighbor. */ UNSET_FLAG(dd->options, OSPF_OPTION_O); } /* Add event to thread. */ OSPF_NSM_EVENT_SCHEDULE(nbr, NSM_PacketReceived); /* Process DD packet by neighbor status. */ switch (nbr->state) { case NSM_Down: case NSM_Attempt: case NSM_TwoWay: flog_warn( EC_OSPF_PACKET, "Packet[DD]: Neighbor %s state is %s, packet discarded.", inet_ntoa(nbr->router_id), lookup_msg(ospf_nsm_state_msg, nbr->state, NULL)); break; case NSM_Init: OSPF_NSM_EVENT_EXECUTE(nbr, NSM_TwoWayReceived); /* If the new state is ExStart, the processing of the current packet should then continue in this new state by falling through to case ExStart below. */ if (nbr->state != NSM_ExStart) break; /* fallthru */ case NSM_ExStart: /* Initial DBD */ if ((IS_SET_DD_ALL(dd->flags) == OSPF_DD_FLAG_ALL) && (size == OSPF_DB_DESC_MIN_SIZE)) { if (IPV4_ADDR_CMP(&nbr->router_id, &oi->ospf->router_id) > 0) { /* We're Slave---obey */ if (CHECK_FLAG(oi->ospf->config, OSPF_LOG_ADJACENCY_DETAIL)) zlog_info( "Packet[DD]: Neighbor %s Negotiation done (Slave).", inet_ntoa(nbr->router_id)); nbr->dd_seqnum = ntohl(dd->dd_seqnum); /* Reset I/MS */ UNSET_FLAG(nbr->dd_flags, (OSPF_DD_FLAG_MS | OSPF_DD_FLAG_I)); } else { /* We're Master, ignore the initial DBD from * Slave */ if (CHECK_FLAG(oi->ospf->config, OSPF_LOG_ADJACENCY_DETAIL)) zlog_info( "Packet[DD]: Neighbor %s: Initial DBD from Slave, " "ignoring.", inet_ntoa(nbr->router_id)); break; } } /* Ack from the Slave */ else if (!IS_SET_DD_MS(dd->flags) && !IS_SET_DD_I(dd->flags) && ntohl(dd->dd_seqnum) == nbr->dd_seqnum && IPV4_ADDR_CMP(&nbr->router_id, &oi->ospf->router_id) < 0) { zlog_info( "Packet[DD]: Neighbor %s Negotiation done (Master).", inet_ntoa(nbr->router_id)); /* Reset I, leaving MS */ UNSET_FLAG(nbr->dd_flags, OSPF_DD_FLAG_I); } else { flog_warn(EC_OSPF_PACKET, "Packet[DD]: Neighbor %s Negotiation fails.", inet_ntoa(nbr->router_id)); break; } /* This is where the real Options are saved */ nbr->options = dd->options; if (CHECK_FLAG(oi->ospf->config, OSPF_OPAQUE_CAPABLE)) { if (IS_DEBUG_OSPF_EVENT) zlog_debug( "Neighbor[%s] is %sOpaque-capable.", inet_ntoa(nbr->router_id), CHECK_FLAG(nbr->options, OSPF_OPTION_O) ? "" : "NOT "); if (!CHECK_FLAG(nbr->options, OSPF_OPTION_O) && IPV4_ADDR_SAME(&DR(oi), &nbr->address.u.prefix4)) { flog_warn( EC_OSPF_PACKET, "DR-neighbor[%s] is NOT opaque-capable; Opaque-LSAs cannot be reliably advertised in this network.", inet_ntoa(nbr->router_id)); /* This situation is undesirable, but not a real * error. */ } } OSPF_NSM_EVENT_EXECUTE(nbr, NSM_NegotiationDone); /* continue processing rest of packet. */ ospf_db_desc_proc(s, oi, nbr, dd, size); break; case NSM_Exchange: if (ospf_db_desc_is_dup(dd, nbr)) { if (IS_SET_DD_MS(nbr->dd_flags)) /* Master: discard duplicated DD packet. */ zlog_info( "Packet[DD] (Master): Neighbor %s packet duplicated.", inet_ntoa(nbr->router_id)); else /* Slave: cause to retransmit the last Database Description. */ { zlog_info( "Packet[DD] [Slave]: Neighbor %s packet duplicated.", inet_ntoa(nbr->router_id)); ospf_db_desc_resend(nbr); } break; } /* Otherwise DD packet should be checked. */ /* Check Master/Slave bit mismatch */ if (IS_SET_DD_MS(dd->flags) != IS_SET_DD_MS(nbr->last_recv.flags)) { flog_warn(EC_OSPF_PACKET, "Packet[DD]: Neighbor %s MS-bit mismatch.", inet_ntoa(nbr->router_id)); OSPF_NSM_EVENT_SCHEDULE(nbr, NSM_SeqNumberMismatch); if (IS_DEBUG_OSPF_EVENT) zlog_debug( "Packet[DD]: dd->flags=%d, nbr->dd_flags=%d", dd->flags, nbr->dd_flags); break; } /* Check initialize bit is set. */ if (IS_SET_DD_I(dd->flags)) { zlog_info("Packet[DD]: Neighbor %s I-bit set.", inet_ntoa(nbr->router_id)); OSPF_NSM_EVENT_SCHEDULE(nbr, NSM_SeqNumberMismatch); break; } /* Check DD Options. */ if (dd->options != nbr->options) { #ifdef ORIGINAL_CODING /* Save the new options for debugging */ nbr->options = dd->options; #endif /* ORIGINAL_CODING */ flog_warn(EC_OSPF_PACKET, "Packet[DD]: Neighbor %s options mismatch.", inet_ntoa(nbr->router_id)); OSPF_NSM_EVENT_SCHEDULE(nbr, NSM_SeqNumberMismatch); break; } /* Check DD sequence number. */ if ((IS_SET_DD_MS(nbr->dd_flags) && ntohl(dd->dd_seqnum) != nbr->dd_seqnum) || (!IS_SET_DD_MS(nbr->dd_flags) && ntohl(dd->dd_seqnum) != nbr->dd_seqnum + 1)) { flog_warn( EC_OSPF_PACKET, "Packet[DD]: Neighbor %s sequence number mismatch.", inet_ntoa(nbr->router_id)); OSPF_NSM_EVENT_SCHEDULE(nbr, NSM_SeqNumberMismatch); break; } /* Continue processing rest of packet. */ ospf_db_desc_proc(s, oi, nbr, dd, size); break; case NSM_Loading: case NSM_Full: if (ospf_db_desc_is_dup(dd, nbr)) { if (IS_SET_DD_MS(nbr->dd_flags)) { /* Master should discard duplicate DD packet. */ zlog_info( "Packet[DD]: Neighbor %s duplicated, " "packet discarded.", inet_ntoa(nbr->router_id)); break; } else { if (monotime_since(&nbr->last_send_ts, NULL) < nbr->v_inactivity * 1000000LL) { /* In states Loading and Full the slave must resend its last Database Description packet in response to duplicate Database Description packets received from the master. For this reason the slave must wait RouterDeadInterval seconds before freeing the last Database Description packet. Reception of a Database Description packet from the master after this interval will generate a SeqNumberMismatch neighbor event. RFC2328 Section 10.8 */ ospf_db_desc_resend(nbr); break; } } } OSPF_NSM_EVENT_SCHEDULE(nbr, NSM_SeqNumberMismatch); break; default: flog_warn(EC_OSPF_PACKET, "Packet[DD]: Neighbor %s NSM illegal status %u.", inet_ntoa(nbr->router_id), nbr->state); break; } } #define OSPF_LSA_KEY_SIZE 12 /* type(4) + id(4) + ar(4) */ /* OSPF Link State Request Read -- RFC2328 Section 10.7. */ static void ospf_ls_req(struct ip *iph, struct ospf_header *ospfh, struct stream *s, struct ospf_interface *oi, uint16_t size) { struct ospf_neighbor *nbr; uint32_t ls_type; struct in_addr ls_id; struct in_addr adv_router; struct ospf_lsa *find; struct list *ls_upd; unsigned int length; /* Increment statistics. */ oi->ls_req_in++; nbr = ospf_nbr_lookup(oi, iph, ospfh); if (nbr == NULL) { flog_warn(EC_OSPF_PACKET, "Link State Request: Unknown Neighbor %s.", inet_ntoa(ospfh->router_id)); return; } /* Add event to thread. */ OSPF_NSM_EVENT_SCHEDULE(nbr, NSM_PacketReceived); /* Neighbor State should be Exchange or later. */ if (nbr->state != NSM_Exchange && nbr->state != NSM_Loading && nbr->state != NSM_Full) { flog_warn( EC_OSPF_PACKET, "Link State Request received from %s: Neighbor state is %s, packet discarded.", inet_ntoa(ospfh->router_id), lookup_msg(ospf_nsm_state_msg, nbr->state, NULL)); return; } /* Send Link State Update for ALL requested LSAs. */ ls_upd = list_new(); length = OSPF_HEADER_SIZE + OSPF_LS_UPD_MIN_SIZE; while (size >= OSPF_LSA_KEY_SIZE) { /* Get one slice of Link State Request. */ ls_type = stream_getl(s); ls_id.s_addr = stream_get_ipv4(s); adv_router.s_addr = stream_get_ipv4(s); /* Verify LSA type. */ if (ls_type < OSPF_MIN_LSA || ls_type >= OSPF_MAX_LSA) { OSPF_NSM_EVENT_SCHEDULE(nbr, NSM_BadLSReq); list_delete(&ls_upd); return; } /* Search proper LSA in LSDB. */ find = ospf_lsa_lookup(oi->ospf, oi->area, ls_type, ls_id, adv_router); if (find == NULL) { OSPF_NSM_EVENT_SCHEDULE(nbr, NSM_BadLSReq); list_delete(&ls_upd); return; } /* Packet overflows MTU size, send immediately. */ if (length + ntohs(find->data->length) > ospf_packet_max(oi)) { if (oi->type == OSPF_IFTYPE_NBMA) ospf_ls_upd_send(nbr, ls_upd, OSPF_SEND_PACKET_DIRECT, 0); else ospf_ls_upd_send(nbr, ls_upd, OSPF_SEND_PACKET_INDIRECT, 0); /* Only remove list contents. Keep ls_upd. */ list_delete_all_node(ls_upd); length = OSPF_HEADER_SIZE + OSPF_LS_UPD_MIN_SIZE; } /* Append LSA to update list. */ listnode_add(ls_upd, find); length += ntohs(find->data->length); size -= OSPF_LSA_KEY_SIZE; } /* Send rest of Link State Update. */ if (listcount(ls_upd) > 0) { if (oi->type == OSPF_IFTYPE_NBMA) ospf_ls_upd_send(nbr, ls_upd, OSPF_SEND_PACKET_DIRECT, 0); else ospf_ls_upd_send(nbr, ls_upd, OSPF_SEND_PACKET_INDIRECT, 0); list_delete(&ls_upd); } else list_delete(&ls_upd); } /* Get the list of LSAs from Link State Update packet. And process some validation -- RFC2328 Section 13. (1)-(2). */ static struct list *ospf_ls_upd_list_lsa(struct ospf_neighbor *nbr, struct stream *s, struct ospf_interface *oi, size_t size) { uint16_t count, sum; uint32_t length; struct lsa_header *lsah; struct ospf_lsa *lsa; struct list *lsas; lsas = list_new(); count = stream_getl(s); size -= OSPF_LS_UPD_MIN_SIZE; /* # LSAs */ for (; size >= OSPF_LSA_HEADER_SIZE && count > 0; size -= length, stream_forward_getp(s, length), count--) { lsah = (struct lsa_header *)stream_pnt(s); length = ntohs(lsah->length); if (length > size) { flog_warn( EC_OSPF_PACKET, "Link State Update: LSA length exceeds packet size."); break; } /* Validate the LSA's LS checksum. */ sum = lsah->checksum; if (!ospf_lsa_checksum_valid(lsah)) { /* (bug #685) more details in a one-line message make it * possible * to identify problem source on the one hand and to * have a better * chance to compress repeated messages in syslog on the * other */ flog_warn( EC_OSPF_PACKET, "Link State Update: LSA checksum error %x/%x, ID=%s from: nbr %s, router ID %s, adv router %s", sum, lsah->checksum, inet_ntoa(lsah->id), inet_ntoa(nbr->src), inet_ntoa(nbr->router_id), inet_ntoa(lsah->adv_router)); continue; } /* Examine the LSA's LS type. */ if (lsah->type < OSPF_MIN_LSA || lsah->type >= OSPF_MAX_LSA) { flog_warn(EC_OSPF_PACKET, "Link State Update: Unknown LS type %d", lsah->type); continue; } /* * What if the received LSA's age is greater than MaxAge? * Treat it as a MaxAge case -- endo. */ if (ntohs(lsah->ls_age) > OSPF_LSA_MAXAGE) lsah->ls_age = htons(OSPF_LSA_MAXAGE); if (CHECK_FLAG(nbr->options, OSPF_OPTION_O)) { #ifdef STRICT_OBIT_USAGE_CHECK if ((IS_OPAQUE_LSA(lsah->type) && !CHECK_FLAG(lsah->options, OSPF_OPTION_O)) || (!IS_OPAQUE_LSA(lsah->type) && CHECK_FLAG(lsah->options, OSPF_OPTION_O))) { /* * This neighbor must know the exact usage of * O-bit; * the bit will be set in Type-9,10,11 LSAs * only. */ flog_warn(EC_OSPF_PACKET, "LSA[Type%d:%s]: O-bit abuse?", lsah->type, inet_ntoa(lsah->id)); continue; } #endif /* STRICT_OBIT_USAGE_CHECK */ /* Do not take in AS External Opaque-LSAs if we are a * stub. */ if (lsah->type == OSPF_OPAQUE_AS_LSA && nbr->oi->area->external_routing != OSPF_AREA_DEFAULT) { if (IS_DEBUG_OSPF_EVENT) zlog_debug( "LSA[Type%d:%s]: We are a stub, don't take this LSA.", lsah->type, inet_ntoa(lsah->id)); continue; } } else if (IS_OPAQUE_LSA(lsah->type)) { flog_warn(EC_OSPF_PACKET, "LSA[Type%d:%s]: Opaque capability mismatch?", lsah->type, inet_ntoa(lsah->id)); continue; } /* Create OSPF LSA instance. */ lsa = ospf_lsa_new_and_data(length); lsa->vrf_id = oi->ospf->vrf_id; /* We may wish to put some error checking if type NSSA comes in and area not in NSSA mode */ switch (lsah->type) { case OSPF_AS_EXTERNAL_LSA: case OSPF_OPAQUE_AS_LSA: lsa->area = NULL; break; case OSPF_OPAQUE_LINK_LSA: lsa->oi = oi; /* Remember incoming interface for flooding control. */ /* Fallthrough */ default: lsa->area = oi->area; break; } memcpy(lsa->data, lsah, length); if (IS_DEBUG_OSPF_EVENT) zlog_debug( "LSA[Type%d:%s]: %p new LSA created with Link State Update", lsa->data->type, inet_ntoa(lsa->data->id), (void *)lsa); listnode_add(lsas, lsa); } return lsas; } /* Cleanup Update list. */ static void ospf_upd_list_clean(struct list *lsas) { struct listnode *node, *nnode; struct ospf_lsa *lsa; for (ALL_LIST_ELEMENTS(lsas, node, nnode, lsa)) ospf_lsa_discard(lsa); list_delete(&lsas); } /* OSPF Link State Update message read -- RFC2328 Section 13. */ static void ospf_ls_upd(struct ospf *ospf, struct ip *iph, struct ospf_header *ospfh, struct stream *s, struct ospf_interface *oi, uint16_t size) { struct ospf_neighbor *nbr; struct list *lsas; struct listnode *node, *nnode; struct ospf_lsa *lsa = NULL; /* unsigned long ls_req_found = 0; */ /* Dis-assemble the stream, update each entry, re-encapsulate for * flooding */ /* Increment statistics. */ oi->ls_upd_in++; /* Check neighbor. */ nbr = ospf_nbr_lookup(oi, iph, ospfh); if (nbr == NULL) { flog_warn(EC_OSPF_PACKET, "Link State Update: Unknown Neighbor %s on int: %s", inet_ntoa(ospfh->router_id), IF_NAME(oi)); return; } /* Add event to thread. */ OSPF_NSM_EVENT_SCHEDULE(nbr, NSM_PacketReceived); /* Check neighbor state. */ if (nbr->state < NSM_Exchange) { if (IS_DEBUG_OSPF(nsm, NSM_EVENTS)) zlog_debug( "Link State Update: " "Neighbor[%s] state %s is less than Exchange", inet_ntoa(ospfh->router_id), lookup_msg(ospf_nsm_state_msg, nbr->state, NULL)); return; } /* Get list of LSAs from Link State Update packet. - Also perorms Stages * 1 (validate LSA checksum) and 2 (check for LSA consistent type) * of section 13. */ lsas = ospf_ls_upd_list_lsa(nbr, s, oi, size); if (lsas == NULL) return; #define DISCARD_LSA(L, N) \ { \ if (IS_DEBUG_OSPF_EVENT) \ zlog_debug( \ "ospf_lsa_discard() in ospf_ls_upd() point %d: lsa %p" \ " Type-%d", \ N, (void *)lsa, (int)lsa->data->type); \ ospf_lsa_discard(L); \ continue; \ } /* Process each LSA received in the one packet. * * Numbers in parentheses, e.g. (1), (2), etc., and the corresponding * text below are from the steps in RFC 2328, Section 13. */ for (ALL_LIST_ELEMENTS(lsas, node, nnode, lsa)) { struct ospf_lsa *ls_ret, *current; int ret = 1; if (IS_DEBUG_OSPF_NSSA) { char buf1[INET_ADDRSTRLEN]; char buf2[INET_ADDRSTRLEN]; char buf3[INET_ADDRSTRLEN]; zlog_debug("LSA Type-%d from %s, ID: %s, ADV: %s", lsa->data->type, inet_ntop(AF_INET, &ospfh->router_id, buf1, INET_ADDRSTRLEN), inet_ntop(AF_INET, &lsa->data->id, buf2, INET_ADDRSTRLEN), inet_ntop(AF_INET, &lsa->data->adv_router, buf3, INET_ADDRSTRLEN)); } listnode_delete(lsas, lsa); /* We don't need it in list anymore */ /* (1) Validate Checksum - Done above by ospf_ls_upd_list_lsa() */ /* (2) LSA Type - Done above by ospf_ls_upd_list_lsa() */ /* (3) Do not take in AS External LSAs if we are a stub or NSSA. */ /* Do not take in AS NSSA if this neighbor and we are not NSSA */ /* Do take in Type-7's if we are an NSSA */ /* If we are also an ABR, later translate them to a Type-5 * packet */ /* Later, an NSSA Re-fresh can Re-fresh Type-7's and an ABR will translate them to a separate Type-5 packet. */ if (lsa->data->type == OSPF_AS_EXTERNAL_LSA) /* Reject from STUB or NSSA */ if (nbr->oi->area->external_routing != OSPF_AREA_DEFAULT) { if (IS_DEBUG_OSPF_NSSA) zlog_debug( "Incoming External LSA Discarded: We are NSSA/STUB Area"); DISCARD_LSA(lsa, 1); } if (lsa->data->type == OSPF_AS_NSSA_LSA) if (nbr->oi->area->external_routing != OSPF_AREA_NSSA) { if (IS_DEBUG_OSPF_NSSA) zlog_debug( "Incoming NSSA LSA Discarded: Not NSSA Area"); DISCARD_LSA(lsa, 2); } /* VU229804: Router-LSA Adv-ID must be equal to LS-ID */ if (lsa->data->type == OSPF_ROUTER_LSA) if (!IPV4_ADDR_SAME(&lsa->data->id, &lsa->data->adv_router)) { char buf1[INET_ADDRSTRLEN]; char buf2[INET_ADDRSTRLEN]; char buf3[INET_ADDRSTRLEN]; flog_err(EC_OSPF_ROUTER_LSA_MISMATCH, "Incoming Router-LSA from %s with " "Adv-ID[%s] != LS-ID[%s]", inet_ntop(AF_INET, &ospfh->router_id, buf1, INET_ADDRSTRLEN), inet_ntop(AF_INET, &lsa->data->id, buf2, INET_ADDRSTRLEN), inet_ntop(AF_INET, &lsa->data->adv_router, buf3, INET_ADDRSTRLEN)); flog_err( EC_OSPF_DOMAIN_CORRUPT, "OSPF domain compromised by attack or corruption. " "Verify correct operation of -ALL- OSPF routers."); DISCARD_LSA(lsa, 0); } /* Find the LSA in the current database. */ current = ospf_lsa_lookup_by_header(oi->area, lsa->data); /* (4) If the LSA's LS age is equal to MaxAge, and there is currently no instance of the LSA in the router's link state database, and none of router's neighbors are in states Exchange or Loading, then take the following actions: */ if (IS_LSA_MAXAGE(lsa) && !current && ospf_check_nbr_status(oi->ospf)) { /* (4a) Response Link State Acknowledgment. */ ospf_ls_ack_send(nbr, lsa); /* (4b) Discard LSA. */ if (IS_DEBUG_OSPF(lsa, LSA)) { zlog_debug( "Link State Update[%s]: LS age is equal to MaxAge.", dump_lsa_key(lsa)); } DISCARD_LSA(lsa, 3); } if (IS_OPAQUE_LSA(lsa->data->type) && IPV4_ADDR_SAME(&lsa->data->adv_router, &oi->ospf->router_id)) { /* * Even if initial flushing seems to be completed, there * might * be a case that self-originated LSA with MaxAge still * remain * in the routing domain. * Just send an LSAck message to cease retransmission. */ if (IS_LSA_MAXAGE(lsa)) { zlog_info("LSA[%s]: Boomerang effect?", dump_lsa_key(lsa)); ospf_ls_ack_send(nbr, lsa); ospf_lsa_discard(lsa); if (current != NULL && !IS_LSA_MAXAGE(current)) ospf_opaque_lsa_refresh_schedule( current); continue; } /* * If an instance of self-originated Opaque-LSA is not * found * in the LSDB, there are some possible cases here. * * 1) This node lost opaque-capability after restart. * 2) Else, a part of opaque-type is no more supported. * 3) Else, a part of opaque-id is no more supported. * * Anyway, it is still this node's responsibility to * flush it. * Otherwise, the LSA instance remains in the routing * domain * until its age reaches to MaxAge. */ /* XXX: We should deal with this for *ALL* LSAs, not * just opaque */ if (current == NULL) { if (IS_DEBUG_OSPF_EVENT) zlog_debug( "LSA[%s]: Previously originated Opaque-LSA," "not found in the LSDB.", dump_lsa_key(lsa)); SET_FLAG(lsa->flags, OSPF_LSA_SELF); ospf_opaque_self_originated_lsa_received(nbr, lsa); ospf_ls_ack_send(nbr, lsa); continue; } } /* It might be happen that received LSA is self-originated * network LSA, but * router ID is changed. So, we should check if LSA is a * network-LSA whose * Link State ID is one of the router's own IP interface * addresses but whose * Advertising Router is not equal to the router's own Router ID * According to RFC 2328 12.4.2 and 13.4 this LSA should be * flushed. */ if (lsa->data->type == OSPF_NETWORK_LSA) { struct listnode *oinode, *oinnode; struct ospf_interface *out_if; int Flag = 0; for (ALL_LIST_ELEMENTS(oi->ospf->oiflist, oinode, oinnode, out_if)) { if (out_if == NULL) break; if ((IPV4_ADDR_SAME(&out_if->address->u.prefix4, &lsa->data->id)) && (!(IPV4_ADDR_SAME( &oi->ospf->router_id, &lsa->data->adv_router)))) { if (out_if->network_lsa_self) { ospf_lsa_flush_area( lsa, out_if->area); if (IS_DEBUG_OSPF_EVENT) zlog_debug( "ospf_lsa_discard() in ospf_ls_upd() point 9: lsa %p Type-%d", (void *)lsa, (int)lsa->data ->type); ospf_lsa_discard(lsa); Flag = 1; } break; } } if (Flag) continue; } /* (5) Find the instance of this LSA that is currently contained in the router's link state database. If there is no database copy, or the received LSA is more recent than the database copy the following steps must be performed. (The sub steps from RFC 2328 section 13 step (5) will be performed in ospf_flood() ) */ if (current == NULL || (ret = ospf_lsa_more_recent(current, lsa)) < 0) { /* CVE-2017-3224 */ if (current && (lsa->data->ls_seqnum == htonl(OSPF_MAX_SEQUENCE_NUMBER) && !IS_LSA_MAXAGE(lsa))) { zlog_debug( "Link State Update[%s]: has Max Seq but not MaxAge. Dropping it", dump_lsa_key(lsa)); DISCARD_LSA(lsa, 4); } /* Actual flooding procedure. */ if (ospf_flood(oi->ospf, nbr, current, lsa) < 0) /* Trap NSSA later. */ DISCARD_LSA(lsa, 5); continue; } /* (6) Else, If there is an instance of the LSA on the sending neighbor's Link state request list, an error has occurred in the Database Exchange process. In this case, restart the Database Exchange process by generating the neighbor event BadLSReq for the sending neighbor and stop processing the Link State Update packet. */ if (ospf_ls_request_lookup(nbr, lsa)) { OSPF_NSM_EVENT_SCHEDULE(nbr, NSM_BadLSReq); flog_warn( EC_OSPF_PACKET, "LSA[%s] instance exists on Link state request list", dump_lsa_key(lsa)); /* Clean list of LSAs. */ ospf_upd_list_clean(lsas); /* this lsa is not on lsas list already. */ ospf_lsa_discard(lsa); return; } /* If the received LSA is the same instance as the database copy (i.e., neither one is more recent) the following two steps should be performed: */ if (ret == 0) { /* If the LSA is listed in the Link state retransmission list for the receiving adjacency, the router itself is expecting an acknowledgment for this LSA. The router should treat the received LSA as an acknowledgment by removing the LSA from the Link state retransmission list. This is termed an "implied acknowledgment". */ ls_ret = ospf_ls_retransmit_lookup(nbr, lsa); if (ls_ret != NULL) { ospf_ls_retransmit_delete(nbr, ls_ret); /* Delayed acknowledgment sent if advertisement received from Designated Router, otherwise do nothing. */ if (oi->state == ISM_Backup) if (NBR_IS_DR(nbr)) listnode_add( oi->ls_ack, ospf_lsa_lock(lsa)); DISCARD_LSA(lsa, 6); } else /* Acknowledge the receipt of the LSA by sending a Link State Acknowledgment packet back out the receiving interface. */ { ospf_ls_ack_send(nbr, lsa); DISCARD_LSA(lsa, 7); } } /* The database copy is more recent. If the database copy has LS age equal to MaxAge and LS sequence number equal to MaxSequenceNumber, simply discard the received LSA without acknowledging it. (In this case, the LSA's LS sequence number is wrapping, and the MaxSequenceNumber LSA must be completely flushed before any new LSA instance can be introduced). */ else if (ret > 0) /* Database copy is more recent */ { if (IS_LSA_MAXAGE(current) && current->data->ls_seqnum == htonl(OSPF_MAX_SEQUENCE_NUMBER)) { DISCARD_LSA(lsa, 8); } /* Otherwise, as long as the database copy has not been sent in a Link State Update within the last MinLSArrival seconds, send the database copy back to the sending neighbor, encapsulated within a Link State Update Packet. The Link State Update Packet should be sent directly to the neighbor. In so doing, do not put the database copy of the LSA on the neighbor's link state retransmission list, and do not acknowledge the received (less recent) LSA instance. */ else { if (monotime_since(¤t->tv_orig, NULL) >= ospf->min_ls_arrival * 1000LL) /* Trap NSSA type later.*/ ospf_ls_upd_send_lsa( nbr, current, OSPF_SEND_PACKET_DIRECT); DISCARD_LSA(lsa, 9); } } } #undef DISCARD_LSA assert(listcount(lsas) == 0); list_delete(&lsas); } /* OSPF Link State Acknowledgment message read -- RFC2328 Section 13.7. */ static void ospf_ls_ack(struct ip *iph, struct ospf_header *ospfh, struct stream *s, struct ospf_interface *oi, uint16_t size) { struct ospf_neighbor *nbr; /* increment statistics. */ oi->ls_ack_in++; nbr = ospf_nbr_lookup(oi, iph, ospfh); if (nbr == NULL) { flog_warn(EC_OSPF_PACKET, "Link State Acknowledgment: Unknown Neighbor %s.", inet_ntoa(ospfh->router_id)); return; } /* Add event to thread. */ OSPF_NSM_EVENT_SCHEDULE(nbr, NSM_PacketReceived); if (nbr->state < NSM_Exchange) { if (IS_DEBUG_OSPF(nsm, NSM_EVENTS)) zlog_debug( "Link State Acknowledgment: " "Neighbor[%s] state %s is less than Exchange", inet_ntoa(ospfh->router_id), lookup_msg(ospf_nsm_state_msg, nbr->state, NULL)); return; } while (size >= OSPF_LSA_HEADER_SIZE) { struct ospf_lsa *lsa, *lsr; lsa = ospf_lsa_new(); lsa->data = (struct lsa_header *)stream_pnt(s); lsa->vrf_id = oi->ospf->vrf_id; /* lsah = (struct lsa_header *) stream_pnt (s); */ size -= OSPF_LSA_HEADER_SIZE; stream_forward_getp(s, OSPF_LSA_HEADER_SIZE); if (lsa->data->type < OSPF_MIN_LSA || lsa->data->type >= OSPF_MAX_LSA) { lsa->data = NULL; ospf_lsa_discard(lsa); continue; } lsr = ospf_ls_retransmit_lookup(nbr, lsa); if (lsr != NULL && ospf_lsa_more_recent(lsr, lsa) == 0) ospf_ls_retransmit_delete(nbr, lsr); lsa->data = NULL; ospf_lsa_discard(lsa); } return; } static struct stream *ospf_recv_packet(struct ospf *ospf, int fd, struct interface **ifp, struct stream *ibuf) { int ret; struct ip *iph; uint16_t ip_len; ifindex_t ifindex = 0; struct iovec iov; /* Header and data both require alignment. */ char buff[CMSG_SPACE(SOPT_SIZE_CMSG_IFINDEX_IPV4())]; struct msghdr msgh; memset(&msgh, 0, sizeof(struct msghdr)); msgh.msg_iov = &iov; msgh.msg_iovlen = 1; msgh.msg_control = (caddr_t)buff; msgh.msg_controllen = sizeof(buff); ret = stream_recvmsg(ibuf, fd, &msgh, 0, OSPF_MAX_PACKET_SIZE + 1); if (ret < 0) { flog_warn(EC_OSPF_PACKET, "stream_recvmsg failed: %s", safe_strerror(errno)); return NULL; } if ((unsigned int)ret < sizeof(struct ip)) { flog_warn( EC_OSPF_PACKET, "ospf_recv_packet: discarding runt packet of length %d " "(ip header size is %u)", ret, (unsigned int)sizeof(iph)); return NULL; } /* Note that there should not be alignment problems with this assignment because this is at the beginning of the stream data buffer. */ iph = (struct ip *)STREAM_DATA(ibuf); sockopt_iphdrincl_swab_systoh(iph); ip_len = iph->ip_len; #if !defined(GNU_LINUX) && (OpenBSD < 200311) && (__FreeBSD_version < 1000000) /* * Kernel network code touches incoming IP header parameters, * before protocol specific processing. * * 1) Convert byteorder to host representation. * --> ip_len, ip_id, ip_off * * 2) Adjust ip_len to strip IP header size! * --> If user process receives entire IP packet via RAW * socket, it must consider adding IP header size to * the "ip_len" field of "ip" structure. * * For more details, see . */ ip_len = ip_len + (iph->ip_hl << 2); #endif #if defined(__DragonFly__) /* * in DragonFly's raw socket, ip_len/ip_off are read * in network byte order. * As OpenBSD < 200311 adjust ip_len to strip IP header size! */ ip_len = ntohs(iph->ip_len) + (iph->ip_hl << 2); #endif ifindex = getsockopt_ifindex(AF_INET, &msgh); *ifp = if_lookup_by_index(ifindex, ospf->vrf_id); if (ret != ip_len) { flog_warn( EC_OSPF_PACKET, "ospf_recv_packet read length mismatch: ip_len is %d, " "but recvmsg returned %d", ip_len, ret); return NULL; } return ibuf; } static struct ospf_interface * ospf_associate_packet_vl(struct ospf *ospf, struct interface *ifp, struct ip *iph, struct ospf_header *ospfh) { struct ospf_interface *rcv_oi; struct ospf_vl_data *vl_data; struct ospf_area *vl_area; struct listnode *node; if (IN_MULTICAST(ntohl(iph->ip_dst.s_addr)) || !OSPF_IS_AREA_BACKBONE(ospfh)) return NULL; /* look for local OSPF interface matching the destination * to determine Area ID. We presume therefore the destination address * is unique, or at least (for "unnumbered" links), not used in other * areas */ if ((rcv_oi = ospf_if_lookup_by_local_addr(ospf, NULL, iph->ip_dst)) == NULL) return NULL; for (ALL_LIST_ELEMENTS_RO(ospf->vlinks, node, vl_data)) { vl_area = ospf_area_lookup_by_area_id(ospf, vl_data->vl_area_id); if (!vl_area) continue; if (OSPF_AREA_SAME(&vl_area, &rcv_oi->area) && IPV4_ADDR_SAME(&vl_data->vl_peer, &ospfh->router_id)) { if (IS_DEBUG_OSPF_EVENT) zlog_debug("associating packet with %s", IF_NAME(vl_data->vl_oi)); if (!CHECK_FLAG(vl_data->vl_oi->ifp->flags, IFF_UP)) { if (IS_DEBUG_OSPF_EVENT) zlog_debug( "This VL is not up yet, sorry"); return NULL; } return vl_data->vl_oi; } } if (IS_DEBUG_OSPF_EVENT) zlog_debug("couldn't find any VL to associate the packet with"); return NULL; } static int ospf_check_area_id(struct ospf_interface *oi, struct ospf_header *ospfh) { /* Check match the Area ID of the receiving interface. */ if (OSPF_AREA_SAME(&oi->area, &ospfh)) return 1; return 0; } /* Unbound socket will accept any Raw IP packets if proto is matched. To prevent it, compare src IP address and i/f address with masking i/f network mask. */ static int ospf_check_network_mask(struct ospf_interface *oi, struct in_addr ip_src) { struct in_addr mask, me, him; if (oi->type == OSPF_IFTYPE_POINTOPOINT || oi->type == OSPF_IFTYPE_VIRTUALLINK) return 1; masklen2ip(oi->address->prefixlen, &mask); me.s_addr = oi->address->u.prefix4.s_addr & mask.s_addr; him.s_addr = ip_src.s_addr & mask.s_addr; if (IPV4_ADDR_SAME(&me, &him)) return 1; return 0; } /* Return 1, if the packet is properly authenticated and checksummed, 0 otherwise. In particular, check that AuType header field is valid and matches the locally configured AuType, and that D.5 requirements are met. */ static int ospf_check_auth(struct ospf_interface *oi, struct ospf_header *ospfh) { struct crypt_key *ck; uint16_t iface_auth_type; uint16_t pkt_auth_type = ntohs(ospfh->auth_type); switch (pkt_auth_type) { case OSPF_AUTH_NULL: /* RFC2328 D.5.1 */ if (OSPF_AUTH_NULL != (iface_auth_type = ospf_auth_type(oi))) { if (IS_DEBUG_OSPF_PACKET(ospfh->type - 1, RECV)) flog_warn( EC_OSPF_PACKET, "interface %s: auth-type mismatch, local %s, rcvd Null", IF_NAME(oi), lookup_msg(ospf_auth_type_str, iface_auth_type, NULL)); return 0; } if (!ospf_check_sum(ospfh)) { if (IS_DEBUG_OSPF_PACKET(ospfh->type - 1, RECV)) flog_warn( EC_OSPF_PACKET, "interface %s: Null auth OK, but checksum error, Router-ID %s", IF_NAME(oi), inet_ntoa(ospfh->router_id)); return 0; } return 1; case OSPF_AUTH_SIMPLE: /* RFC2328 D.5.2 */ if (OSPF_AUTH_SIMPLE != (iface_auth_type = ospf_auth_type(oi))) { if (IS_DEBUG_OSPF_PACKET(ospfh->type - 1, RECV)) flog_warn( EC_OSPF_PACKET, "interface %s: auth-type mismatch, local %s, rcvd Simple", IF_NAME(oi), lookup_msg(ospf_auth_type_str, iface_auth_type, NULL)); return 0; } if (memcmp(OSPF_IF_PARAM(oi, auth_simple), ospfh->u.auth_data, OSPF_AUTH_SIMPLE_SIZE)) { if (IS_DEBUG_OSPF_PACKET(ospfh->type - 1, RECV)) flog_warn(EC_OSPF_PACKET, "interface %s: Simple auth failed", IF_NAME(oi)); return 0; } if (!ospf_check_sum(ospfh)) { if (IS_DEBUG_OSPF_PACKET(ospfh->type - 1, RECV)) flog_warn( EC_OSPF_PACKET, "interface %s: Simple auth OK, checksum error, Router-ID %s", IF_NAME(oi), inet_ntoa(ospfh->router_id)); return 0; } return 1; case OSPF_AUTH_CRYPTOGRAPHIC: /* RFC2328 D.5.3 */ if (OSPF_AUTH_CRYPTOGRAPHIC != (iface_auth_type = ospf_auth_type(oi))) { if (IS_DEBUG_OSPF_PACKET(ospfh->type - 1, RECV)) flog_warn( EC_OSPF_PACKET, "interface %s: auth-type mismatch, local %s, rcvd Cryptographic", IF_NAME(oi), lookup_msg(ospf_auth_type_str, iface_auth_type, NULL)); return 0; } if (ospfh->checksum) { if (IS_DEBUG_OSPF_PACKET(ospfh->type - 1, RECV)) flog_warn( EC_OSPF_PACKET, "interface %s: OSPF header checksum is not 0", IF_NAME(oi)); return 0; } /* only MD5 crypto method can pass ospf_packet_examin() */ if (NULL == (ck = listgetdata( listtail(OSPF_IF_PARAM(oi, auth_crypt)))) || ospfh->u.crypt.key_id != ck->key_id || /* Condition above uses the last key ID on the list, which is different from what ospf_crypt_key_lookup() does. A bug? */ !ospf_check_md5_digest(oi, ospfh)) { if (IS_DEBUG_OSPF_PACKET(ospfh->type - 1, RECV)) flog_warn(EC_OSPF_MD5, "interface %s: MD5 auth failed", IF_NAME(oi)); return 0; } return 1; default: if (IS_DEBUG_OSPF_PACKET(ospfh->type - 1, RECV)) flog_warn( EC_OSPF_PACKET, "interface %s: invalid packet auth-type (%02x)", IF_NAME(oi), pkt_auth_type); return 0; } } static int ospf_check_sum(struct ospf_header *ospfh) { uint32_t ret; uint16_t sum; /* clear auth_data for checksum. */ memset(ospfh->u.auth_data, 0, OSPF_AUTH_SIMPLE_SIZE); /* keep checksum and clear. */ sum = ospfh->checksum; memset(&ospfh->checksum, 0, sizeof(uint16_t)); /* calculate checksum. */ ret = in_cksum(ospfh, ntohs(ospfh->length)); if (ret != sum) { zlog_info("ospf_check_sum(): checksum mismatch, my %X, his %X", ret, sum); return 0; } return 1; } /* Verify, that given link/TOS records are properly sized/aligned and match Router-LSA "# links" and "# TOS" fields as specified in RFC2328 A.4.2. */ static unsigned ospf_router_lsa_links_examin(struct router_lsa_link *link, uint16_t linkbytes, const uint16_t num_links) { unsigned counted_links = 0, thislinklen; while (linkbytes) { thislinklen = OSPF_ROUTER_LSA_LINK_SIZE + 4 * link->m[0].tos_count; if (thislinklen > linkbytes) { if (IS_DEBUG_OSPF_PACKET(0, RECV)) zlog_debug("%s: length error in link block #%u", __func__, counted_links); return MSG_NG; } link = (struct router_lsa_link *)((caddr_t)link + thislinklen); linkbytes -= thislinklen; counted_links++; } if (counted_links != num_links) { if (IS_DEBUG_OSPF_PACKET(0, RECV)) zlog_debug("%s: %u link blocks declared, %u present", __func__, num_links, counted_links); return MSG_NG; } return MSG_OK; } /* Verify, that the given LSA is properly sized/aligned (including type-specific minimum length constraint). */ static unsigned ospf_lsa_examin(struct lsa_header *lsah, const uint16_t lsalen, const uint8_t headeronly) { unsigned ret; struct router_lsa *rlsa; if (lsah->type < OSPF_MAX_LSA && ospf_lsa_minlen[lsah->type] && lsalen < OSPF_LSA_HEADER_SIZE + ospf_lsa_minlen[lsah->type]) { if (IS_DEBUG_OSPF_PACKET(0, RECV)) zlog_debug("%s: undersized (%u B) %s", __func__, lsalen, lookup_msg(ospf_lsa_type_msg, lsah->type, NULL)); return MSG_NG; } switch (lsah->type) { case OSPF_ROUTER_LSA: /* RFC2328 A.4.2, LSA header + 4 bytes followed by N>=1 * (12+)-byte link blocks */ if (headeronly) { ret = (lsalen - OSPF_LSA_HEADER_SIZE - OSPF_ROUTER_LSA_MIN_SIZE) % 4 ? MSG_NG : MSG_OK; break; } rlsa = (struct router_lsa *)lsah; ret = ospf_router_lsa_links_examin( (struct router_lsa_link *)rlsa->link, lsalen - OSPF_LSA_HEADER_SIZE - 4, /* skip: basic header, "flags", 0, "# links" */ ntohs(rlsa->links) /* 16 bits */ ); break; case OSPF_AS_EXTERNAL_LSA: /* RFC2328 A.4.5, LSA header + 4 bytes followed by N>=1 12-bytes long * blocks */ case OSPF_AS_NSSA_LSA: /* RFC3101 C, idem */ ret = (lsalen - OSPF_LSA_HEADER_SIZE - OSPF_AS_EXTERNAL_LSA_MIN_SIZE) % 12 ? MSG_NG : MSG_OK; break; /* Following LSA types are considered OK length-wise as soon as their * minimum * length constraint is met and length of the whole LSA is a multiple of * 4 * (basic LSA header size is already a multiple of 4). */ case OSPF_NETWORK_LSA: /* RFC2328 A.4.3, LSA header + 4 bytes followed by N>=1 router-IDs */ case OSPF_SUMMARY_LSA: case OSPF_ASBR_SUMMARY_LSA: /* RFC2328 A.4.4, LSA header + 4 bytes followed by N>=1 4-bytes TOS * blocks */ case OSPF_OPAQUE_LINK_LSA: case OSPF_OPAQUE_AREA_LSA: case OSPF_OPAQUE_AS_LSA: /* RFC5250 A.2, "some number of octets (of application-specific * data) padded to 32-bit alignment." This is considered * equivalent * to 4-byte alignment of all other LSA types, see * OSPF-ALIGNMENT.txt * file for the detailed analysis of this passage. */ ret = lsalen % 4 ? MSG_NG : MSG_OK; break; default: if (IS_DEBUG_OSPF_PACKET(0, RECV)) zlog_debug("%s: unsupported LSA type 0x%02x", __func__, lsah->type); return MSG_NG; } if (ret != MSG_OK && IS_DEBUG_OSPF_PACKET(0, RECV)) zlog_debug("%s: alignment error in %s", __func__, lookup_msg(ospf_lsa_type_msg, lsah->type, NULL)); return ret; } /* Verify if the provided input buffer is a valid sequence of LSAs. This includes verification of LSA blocks length/alignment and dispatching of deeper-level checks. */ static unsigned ospf_lsaseq_examin(struct lsa_header *lsah, /* start of buffered data */ size_t length, const uint8_t headeronly, /* When declared_num_lsas is not 0, compare it to the real number of LSAs and treat the difference as an error. */ const uint32_t declared_num_lsas) { uint32_t counted_lsas = 0; while (length) { uint16_t lsalen; if (length < OSPF_LSA_HEADER_SIZE) { if (IS_DEBUG_OSPF_PACKET(0, RECV)) zlog_debug( "%s: undersized (%zu B) trailing (#%u) LSA header", __func__, length, counted_lsas); return MSG_NG; } /* save on ntohs() calls here and in the LSA validator */ lsalen = ntohs(lsah->length); if (lsalen < OSPF_LSA_HEADER_SIZE) { if (IS_DEBUG_OSPF_PACKET(0, RECV)) zlog_debug( "%s: malformed LSA header #%u, declared length is %u B", __func__, counted_lsas, lsalen); return MSG_NG; } if (headeronly) { /* less checks here and in ospf_lsa_examin() */ if (MSG_OK != ospf_lsa_examin(lsah, lsalen, 1)) { if (IS_DEBUG_OSPF_PACKET(0, RECV)) zlog_debug( "%s: malformed header-only LSA #%u", __func__, counted_lsas); return MSG_NG; } lsah = (struct lsa_header *)((caddr_t)lsah + OSPF_LSA_HEADER_SIZE); length -= OSPF_LSA_HEADER_SIZE; } else { /* make sure the input buffer is deep enough before * further checks */ if (lsalen > length) { if (IS_DEBUG_OSPF_PACKET(0, RECV)) zlog_debug( "%s: anomaly in LSA #%u: declared length is %u B, buffered length is %zu B", __func__, counted_lsas, lsalen, length); return MSG_NG; } if (MSG_OK != ospf_lsa_examin(lsah, lsalen, 0)) { if (IS_DEBUG_OSPF_PACKET(0, RECV)) zlog_debug("%s: malformed LSA #%u", __func__, counted_lsas); return MSG_NG; } lsah = (struct lsa_header *)((caddr_t)lsah + lsalen); length -= lsalen; } counted_lsas++; } if (declared_num_lsas && counted_lsas != declared_num_lsas) { if (IS_DEBUG_OSPF_PACKET(0, RECV)) zlog_debug( "%s: #LSAs declared (%u) does not match actual (%u)", __func__, declared_num_lsas, counted_lsas); return MSG_NG; } return MSG_OK; } /* Verify a complete OSPF packet for proper sizing/alignment. */ static unsigned ospf_packet_examin(struct ospf_header *oh, const unsigned bytesonwire) { uint16_t bytesdeclared, bytesauth; unsigned ret; struct ospf_ls_update *lsupd; /* Length, 1st approximation. */ if (bytesonwire < OSPF_HEADER_SIZE) { if (IS_DEBUG_OSPF_PACKET(0, RECV)) zlog_debug("%s: undersized (%u B) packet", __func__, bytesonwire); return MSG_NG; } /* Now it is safe to access header fields. Performing length check, * allow * for possible extra bytes of crypto auth/padding, which are not * counted * in the OSPF header "length" field. */ if (oh->version != OSPF_VERSION) { if (IS_DEBUG_OSPF_PACKET(0, RECV)) zlog_debug("%s: invalid (%u) protocol version", __func__, oh->version); return MSG_NG; } bytesdeclared = ntohs(oh->length); if (ntohs(oh->auth_type) != OSPF_AUTH_CRYPTOGRAPHIC) bytesauth = 0; else { if (oh->u.crypt.auth_data_len != OSPF_AUTH_MD5_SIZE) { if (IS_DEBUG_OSPF_PACKET(0, RECV)) zlog_debug( "%s: unsupported crypto auth length (%u B)", __func__, oh->u.crypt.auth_data_len); return MSG_NG; } bytesauth = OSPF_AUTH_MD5_SIZE; } if (bytesdeclared + bytesauth > bytesonwire) { if (IS_DEBUG_OSPF_PACKET(0, RECV)) zlog_debug( "%s: packet length error (%u real, %u+%u declared)", __func__, bytesonwire, bytesdeclared, bytesauth); return MSG_NG; } /* Length, 2nd approximation. The type-specific constraint is checked against declared length, not amount of bytes on wire. */ if (oh->type >= OSPF_MSG_HELLO && oh->type <= OSPF_MSG_LS_ACK && bytesdeclared < OSPF_HEADER_SIZE + ospf_packet_minlen[oh->type]) { if (IS_DEBUG_OSPF_PACKET(0, RECV)) zlog_debug("%s: undersized (%u B) %s packet", __func__, bytesdeclared, lookup_msg(ospf_packet_type_str, oh->type, NULL)); return MSG_NG; } switch (oh->type) { case OSPF_MSG_HELLO: /* RFC2328 A.3.2, packet header + OSPF_HELLO_MIN_SIZE bytes followed by N>=0 router-IDs. */ ret = (bytesdeclared - OSPF_HEADER_SIZE - OSPF_HELLO_MIN_SIZE) % 4 ? MSG_NG : MSG_OK; break; case OSPF_MSG_DB_DESC: /* RFC2328 A.3.3, packet header + OSPF_DB_DESC_MIN_SIZE bytes followed by N>=0 header-only LSAs. */ ret = ospf_lsaseq_examin( (struct lsa_header *)((caddr_t)oh + OSPF_HEADER_SIZE + OSPF_DB_DESC_MIN_SIZE), bytesdeclared - OSPF_HEADER_SIZE - OSPF_DB_DESC_MIN_SIZE, 1, /* header-only LSAs */ 0); break; case OSPF_MSG_LS_REQ: /* RFC2328 A.3.4, packet header followed by N>=0 12-bytes * request blocks. */ ret = (bytesdeclared - OSPF_HEADER_SIZE - OSPF_LS_REQ_MIN_SIZE) % OSPF_LSA_KEY_SIZE ? MSG_NG : MSG_OK; break; case OSPF_MSG_LS_UPD: /* RFC2328 A.3.5, packet header + OSPF_LS_UPD_MIN_SIZE bytes followed by N>=0 full LSAs (with N declared beforehand). */ lsupd = (struct ospf_ls_update *)((caddr_t)oh + OSPF_HEADER_SIZE); ret = ospf_lsaseq_examin( (struct lsa_header *)((caddr_t)lsupd + OSPF_LS_UPD_MIN_SIZE), bytesdeclared - OSPF_HEADER_SIZE - OSPF_LS_UPD_MIN_SIZE, 0, /* full LSAs */ ntohl(lsupd->num_lsas) /* 32 bits */ ); break; case OSPF_MSG_LS_ACK: /* RFC2328 A.3.6, packet header followed by N>=0 header-only * LSAs. */ ret = ospf_lsaseq_examin( (struct lsa_header *)((caddr_t)oh + OSPF_HEADER_SIZE + OSPF_LS_ACK_MIN_SIZE), bytesdeclared - OSPF_HEADER_SIZE - OSPF_LS_ACK_MIN_SIZE, 1, /* header-only LSAs */ 0); break; default: if (IS_DEBUG_OSPF_PACKET(0, RECV)) zlog_debug("%s: invalid packet type 0x%02x", __func__, oh->type); return MSG_NG; } if (ret != MSG_OK && IS_DEBUG_OSPF_PACKET(0, RECV)) zlog_debug("%s: malformed %s packet", __func__, lookup_msg(ospf_packet_type_str, oh->type, NULL)); return ret; } /* OSPF Header verification. */ static int ospf_verify_header(struct stream *ibuf, struct ospf_interface *oi, struct ip *iph, struct ospf_header *ospfh) { /* Check Area ID. */ if (!ospf_check_area_id(oi, ospfh)) { flog_warn(EC_OSPF_PACKET, "interface %s: ospf_read invalid Area ID %s.", IF_NAME(oi), inet_ntoa(ospfh->area_id)); return -1; } /* Check network mask, Silently discarded. */ if (!ospf_check_network_mask(oi, iph->ip_src)) { flog_warn( EC_OSPF_PACKET, "interface %s: ospf_read network address is not same [%s]", IF_NAME(oi), inet_ntoa(iph->ip_src)); return -1; } /* Check authentication. The function handles logging actions, where * required. */ if (!ospf_check_auth(oi, ospfh)) return -1; return 0; } /* Starting point of packet process function. */ int ospf_read(struct thread *thread) { int ret; struct stream *ibuf; struct ospf *ospf; struct ospf_interface *oi; struct ip *iph; struct ospf_header *ospfh; uint16_t length; struct interface *ifp = NULL; struct connected *c; /* first of all get interface pointer. */ ospf = THREAD_ARG(thread); /* prepare for next packet. */ ospf->t_read = NULL; thread_add_read(master, ospf_read, ospf, ospf->fd, &ospf->t_read); stream_reset(ospf->ibuf); ibuf = ospf_recv_packet(ospf, ospf->fd, &ifp, ospf->ibuf); if (ibuf == NULL) return -1; /* This raw packet is known to be at least as big as its IP header. */ /* Note that there should not be alignment problems with this assignment because this is at the beginning of the stream data buffer. */ iph = (struct ip *)STREAM_DATA(ibuf); /* Note that sockopt_iphdrincl_swab_systoh was called in * ospf_recv_packet. */ if (ifp == NULL) { /* Handle cases where the platform does not support retrieving the ifindex, and also platforms (such as Solaris 8) that claim to support ifindex retrieval but do not. */ c = if_lookup_address((void *)&iph->ip_src, AF_INET, ospf->vrf_id); if (c) ifp = c->ifp; if (ifp == NULL) return 0; } /* IP Header dump. */ if (IS_DEBUG_OSPF_PACKET(0, RECV)) ospf_ip_header_dump(iph); /* Self-originated packet should be discarded silently. */ if (ospf_if_lookup_by_local_addr(ospf, NULL, iph->ip_src)) { if (IS_DEBUG_OSPF_PACKET(0, RECV)) { zlog_debug( "ospf_read[%s]: Dropping self-originated packet", inet_ntoa(iph->ip_src)); } return 0; } /* Check that we have enough for an IP header */ if ((unsigned int)(iph->ip_hl << 2) >= STREAM_READABLE(ibuf)) { if ((unsigned int)(iph->ip_hl << 2) == STREAM_READABLE(ibuf)) { flog_warn( EC_OSPF_PACKET, "Rx'd IP packet with OSPF protocol number but no payload"); } else { flog_warn( EC_OSPF_PACKET, "IP header length field claims header is %u bytes, but we only have %zu", (unsigned int)(iph->ip_hl << 2), STREAM_READABLE(ibuf)); } return -1; } stream_forward_getp(ibuf, iph->ip_hl << 2); ospfh = (struct ospf_header *)stream_pnt(ibuf); if (MSG_OK != ospf_packet_examin( ospfh, stream_get_endp(ibuf) - stream_get_getp(ibuf))) return -1; /* Now it is safe to access all fields of OSPF packet header. */ /* associate packet with ospf interface */ oi = ospf_if_lookup_recv_if(ospf, iph->ip_src, ifp); /* ospf_verify_header() relies on a valid "oi" and thus can be called only after the passive/backbone/other checks below are passed. These checks in turn access the fields of unverified "ospfh" structure for their own purposes and must remain very accurate in doing this. */ /* If incoming interface is passive one, ignore it. */ if (oi && OSPF_IF_PASSIVE_STATUS(oi) == OSPF_IF_PASSIVE) { char buf[3][INET_ADDRSTRLEN]; if (IS_DEBUG_OSPF_EVENT) zlog_debug( "ignoring packet from router %s sent to %s, " "received on a passive interface, %s", inet_ntop(AF_INET, &ospfh->router_id, buf[0], sizeof(buf[0])), inet_ntop(AF_INET, &iph->ip_dst, buf[1], sizeof(buf[1])), inet_ntop(AF_INET, &oi->address->u.prefix4, buf[2], sizeof(buf[2]))); if (iph->ip_dst.s_addr == htonl(OSPF_ALLSPFROUTERS)) { /* Try to fix multicast membership. * Some OS:es may have problems in this area, * make sure it is removed. */ OI_MEMBER_JOINED(oi, MEMBER_ALLROUTERS); ospf_if_set_multicast(oi); } return 0; } /* if no local ospf_interface, * or header area is backbone but ospf_interface is not * check for VLINK interface */ if ((oi == NULL) || (OSPF_IS_AREA_ID_BACKBONE(ospfh->area_id) && !OSPF_IS_AREA_ID_BACKBONE(oi->area->area_id))) { if ((oi = ospf_associate_packet_vl(ospf, ifp, iph, ospfh)) == NULL) { if (!ospf->instance && IS_DEBUG_OSPF_EVENT) zlog_debug( "Packet from [%s] received on link %s" " but no ospf_interface", inet_ntoa(iph->ip_src), ifp->name); return 0; } } /* else it must be a local ospf interface, check it was received on * correct link */ else if (oi->ifp != ifp) { if (IS_DEBUG_OSPF_EVENT) flog_warn(EC_OSPF_PACKET, "Packet from [%s] received on wrong link %s", inet_ntoa(iph->ip_src), ifp->name); return 0; } else if (oi->state == ISM_Down) { char buf[2][INET_ADDRSTRLEN]; flog_warn( EC_OSPF_PACKET, "Ignoring packet from %s to %s received on interface that is " "down [%s]; interface flags are %s", inet_ntop(AF_INET, &iph->ip_src, buf[0], sizeof(buf[0])), inet_ntop(AF_INET, &iph->ip_dst, buf[1], sizeof(buf[1])), ifp->name, if_flag_dump(ifp->flags)); /* Fix multicast memberships? */ if (iph->ip_dst.s_addr == htonl(OSPF_ALLSPFROUTERS)) OI_MEMBER_JOINED(oi, MEMBER_ALLROUTERS); else if (iph->ip_dst.s_addr == htonl(OSPF_ALLDROUTERS)) OI_MEMBER_JOINED(oi, MEMBER_DROUTERS); if (oi->multicast_memberships) ospf_if_set_multicast(oi); return 0; } /* * If the received packet is destined for AllDRouters, the packet * should be accepted only if the received ospf interface state is * either DR or Backup -- endo. */ if (iph->ip_dst.s_addr == htonl(OSPF_ALLDROUTERS) && (oi->state != ISM_DR && oi->state != ISM_Backup)) { flog_warn( EC_OSPF_PACKET, "Dropping packet for AllDRouters from [%s] via [%s] (ISM: %s)", inet_ntoa(iph->ip_src), IF_NAME(oi), lookup_msg(ospf_ism_state_msg, oi->state, NULL)); /* Try to fix multicast membership. */ SET_FLAG(oi->multicast_memberships, MEMBER_DROUTERS); ospf_if_set_multicast(oi); return 0; } /* Verify more OSPF header fields. */ ret = ospf_verify_header(ibuf, oi, iph, ospfh); if (ret < 0) { if (IS_DEBUG_OSPF_PACKET(0, RECV)) zlog_debug( "ospf_read[%s]: Header check failed, " "dropping.", inet_ntoa(iph->ip_src)); return ret; } /* Show debug receiving packet. */ if (IS_DEBUG_OSPF_PACKET(ospfh->type - 1, RECV)) { if (IS_DEBUG_OSPF_PACKET(ospfh->type - 1, DETAIL)) { zlog_debug( "-----------------------------------------------------"); ospf_packet_dump(ibuf); } zlog_debug("%s received from [%s] via [%s]", lookup_msg(ospf_packet_type_str, ospfh->type, NULL), inet_ntoa(ospfh->router_id), IF_NAME(oi)); zlog_debug(" src [%s],", inet_ntoa(iph->ip_src)); zlog_debug(" dst [%s]", inet_ntoa(iph->ip_dst)); if (IS_DEBUG_OSPF_PACKET(ospfh->type - 1, DETAIL)) zlog_debug( "-----------------------------------------------------"); } stream_forward_getp(ibuf, OSPF_HEADER_SIZE); /* Adjust size to message length. */ length = ntohs(ospfh->length) - OSPF_HEADER_SIZE; /* Read rest of the packet and call each sort of packet routine. */ switch (ospfh->type) { case OSPF_MSG_HELLO: ospf_hello(iph, ospfh, ibuf, oi, length); break; case OSPF_MSG_DB_DESC: ospf_db_desc(iph, ospfh, ibuf, oi, length); break; case OSPF_MSG_LS_REQ: ospf_ls_req(iph, ospfh, ibuf, oi, length); break; case OSPF_MSG_LS_UPD: ospf_ls_upd(ospf, iph, ospfh, ibuf, oi, length); break; case OSPF_MSG_LS_ACK: ospf_ls_ack(iph, ospfh, ibuf, oi, length); break; default: flog_warn(EC_OSPF_PACKET, "interface %s: OSPF packet header type %d is illegal", IF_NAME(oi), ospfh->type); break; } return 0; } /* Make OSPF header. */ static void ospf_make_header(int type, struct ospf_interface *oi, struct stream *s) { struct ospf_header *ospfh; ospfh = (struct ospf_header *)STREAM_DATA(s); ospfh->version = (uint8_t)OSPF_VERSION; ospfh->type = (uint8_t)type; ospfh->router_id = oi->ospf->router_id; ospfh->checksum = 0; ospfh->area_id = oi->area->area_id; ospfh->auth_type = htons(ospf_auth_type(oi)); memset(ospfh->u.auth_data, 0, OSPF_AUTH_SIMPLE_SIZE); stream_forward_endp(s, OSPF_HEADER_SIZE); } /* Make Authentication Data. */ static int ospf_make_auth(struct ospf_interface *oi, struct ospf_header *ospfh) { struct crypt_key *ck; switch (ospf_auth_type(oi)) { case OSPF_AUTH_NULL: /* memset (ospfh->u.auth_data, 0, sizeof (ospfh->u.auth_data)); */ break; case OSPF_AUTH_SIMPLE: memcpy(ospfh->u.auth_data, OSPF_IF_PARAM(oi, auth_simple), OSPF_AUTH_SIMPLE_SIZE); break; case OSPF_AUTH_CRYPTOGRAPHIC: /* If key is not set, then set 0. */ if (list_isempty(OSPF_IF_PARAM(oi, auth_crypt))) { ospfh->u.crypt.zero = 0; ospfh->u.crypt.key_id = 0; ospfh->u.crypt.auth_data_len = OSPF_AUTH_MD5_SIZE; } else { ck = listgetdata( listtail(OSPF_IF_PARAM(oi, auth_crypt))); ospfh->u.crypt.zero = 0; ospfh->u.crypt.key_id = ck->key_id; ospfh->u.crypt.auth_data_len = OSPF_AUTH_MD5_SIZE; } /* note: the seq is done in ospf_make_md5_digest() */ break; default: /* memset (ospfh->u.auth_data, 0, sizeof (ospfh->u.auth_data)); */ break; } return 0; } /* Fill rest of OSPF header. */ static void ospf_fill_header(struct ospf_interface *oi, struct stream *s, uint16_t length) { struct ospf_header *ospfh; ospfh = (struct ospf_header *)STREAM_DATA(s); /* Fill length. */ ospfh->length = htons(length); /* Calculate checksum. */ if (ntohs(ospfh->auth_type) != OSPF_AUTH_CRYPTOGRAPHIC) ospfh->checksum = in_cksum(ospfh, length); else ospfh->checksum = 0; /* Add Authentication Data. */ ospf_make_auth(oi, ospfh); } static int ospf_make_hello(struct ospf_interface *oi, struct stream *s) { struct ospf_neighbor *nbr; struct route_node *rn; uint16_t length = OSPF_HELLO_MIN_SIZE; struct in_addr mask; unsigned long p; int flag = 0; /* Set netmask of interface. */ if (!(CHECK_FLAG(oi->connected->flags, ZEBRA_IFA_UNNUMBERED) && oi->type == OSPF_IFTYPE_POINTOPOINT) && oi->type != OSPF_IFTYPE_VIRTUALLINK) masklen2ip(oi->address->prefixlen, &mask); else memset((char *)&mask, 0, sizeof(struct in_addr)); stream_put_ipv4(s, mask.s_addr); /* Set Hello Interval. */ if (OSPF_IF_PARAM(oi, fast_hello) == 0) stream_putw(s, OSPF_IF_PARAM(oi, v_hello)); else stream_putw(s, 0); /* hello-interval of 0 for fast-hellos */ if (IS_DEBUG_OSPF_EVENT) zlog_debug("make_hello: options: %x, int: %s", OPTIONS(oi), IF_NAME(oi)); /* Set Options. */ stream_putc(s, OPTIONS(oi)); /* Set Router Priority. */ stream_putc(s, PRIORITY(oi)); /* Set Router Dead Interval. */ stream_putl(s, OSPF_IF_PARAM(oi, v_wait)); /* Set Designated Router. */ stream_put_ipv4(s, DR(oi).s_addr); p = stream_get_endp(s); /* Set Backup Designated Router. */ stream_put_ipv4(s, BDR(oi).s_addr); /* Add neighbor seen. */ for (rn = route_top(oi->nbrs); rn; rn = route_next(rn)) if ((nbr = rn->info)) if (nbr->router_id.s_addr != 0) /* Ignore 0.0.0.0 node. */ if (nbr->state != NSM_Attempt) /* Ignore Down neighbor. */ if (nbr->state != NSM_Down) /* This is myself for DR election. */ if (!IPV4_ADDR_SAME( &nbr->router_id, &oi->ospf->router_id)) { /* Check neighbor is * sane? */ if (nbr->d_router.s_addr != 0 && IPV4_ADDR_SAME( &nbr->d_router, &oi->address ->u .prefix4) && IPV4_ADDR_SAME( &nbr->bd_router, &oi->address ->u .prefix4)) flag = 1; /* Hello packet overflows interface MTU. */ if (length + sizeof(uint32_t) > ospf_packet_max(oi)) { flog_err( EC_OSPF_LARGE_HELLO, "Oversized Hello packet! Larger than MTU. Not sending it out"); return 0; } stream_put_ipv4( s, nbr->router_id .s_addr); length += 4; } /* Let neighbor generate BackupSeen. */ if (flag == 1) stream_putl_at(s, p, 0); /* ipv4 address, normally */ return length; } static int ospf_make_db_desc(struct ospf_interface *oi, struct ospf_neighbor *nbr, struct stream *s) { struct ospf_lsa *lsa; uint16_t length = OSPF_DB_DESC_MIN_SIZE; uint8_t options; unsigned long pp; int i; struct ospf_lsdb *lsdb; /* Set Interface MTU. */ if (oi->type == OSPF_IFTYPE_VIRTUALLINK) stream_putw(s, 0); else stream_putw(s, oi->ifp->mtu); /* Set Options. */ options = OPTIONS(oi); if (CHECK_FLAG(oi->ospf->config, OSPF_OPAQUE_CAPABLE)) SET_FLAG(options, OSPF_OPTION_O); stream_putc(s, options); /* DD flags */ pp = stream_get_endp(s); stream_putc(s, nbr->dd_flags); /* Set DD Sequence Number. */ stream_putl(s, nbr->dd_seqnum); /* shortcut unneeded walk of (empty) summary LSDBs */ if (ospf_db_summary_isempty(nbr)) goto empty; /* Describe LSA Header from Database Summary List. */ lsdb = &nbr->db_sum; for (i = OSPF_MIN_LSA; i < OSPF_MAX_LSA; i++) { struct route_table *table = lsdb->type[i].db; struct route_node *rn; for (rn = route_top(table); rn; rn = route_next(rn)) if ((lsa = rn->info) != NULL) { if (IS_OPAQUE_LSA(lsa->data->type) && (!CHECK_FLAG(options, OSPF_OPTION_O))) { /* Suppress advertising * opaque-information. */ /* Remove LSA from DB summary list. */ ospf_lsdb_delete(lsdb, lsa); continue; } if (!CHECK_FLAG(lsa->flags, OSPF_LSA_DISCARD)) { struct lsa_header *lsah; uint16_t ls_age; /* DD packet overflows interface MTU. */ if (length + OSPF_LSA_HEADER_SIZE > ospf_packet_max(oi)) break; /* Keep pointer to LS age. */ lsah = (struct lsa_header *)(STREAM_DATA(s) + stream_get_endp( s)); /* Proceed stream pointer. */ stream_put(s, lsa->data, OSPF_LSA_HEADER_SIZE); length += OSPF_LSA_HEADER_SIZE; /* Set LS age. */ ls_age = LS_AGE(lsa); lsah->ls_age = htons(ls_age); } /* Remove LSA from DB summary list. */ ospf_lsdb_delete(lsdb, lsa); } } /* Update 'More' bit */ if (ospf_db_summary_isempty(nbr)) { empty: if (nbr->state >= NSM_Exchange) { UNSET_FLAG(nbr->dd_flags, OSPF_DD_FLAG_M); /* Rewrite DD flags */ stream_putc_at(s, pp, nbr->dd_flags); } else { assert(IS_SET_DD_M(nbr->dd_flags)); } } return length; } static int ospf_make_ls_req_func(struct stream *s, uint16_t *length, unsigned long delta, struct ospf_neighbor *nbr, struct ospf_lsa *lsa) { struct ospf_interface *oi; oi = nbr->oi; /* LS Request packet overflows interface MTU * delta is just number of bytes required for 1 LS Req * ospf_packet_max will return the number of bytes can * be accomodated without ospf header. So length+delta * can be compared to ospf_packet_max * to check if it can fit another lsreq in the same packet. */ if (*length + delta > ospf_packet_max(oi)) return 0; stream_putl(s, lsa->data->type); stream_put_ipv4(s, lsa->data->id.s_addr); stream_put_ipv4(s, lsa->data->adv_router.s_addr); ospf_lsa_unlock(&nbr->ls_req_last); nbr->ls_req_last = ospf_lsa_lock(lsa); *length += 12; return 1; } static int ospf_make_ls_req(struct ospf_neighbor *nbr, struct stream *s) { struct ospf_lsa *lsa; uint16_t length = OSPF_LS_REQ_MIN_SIZE; unsigned long delta = 12; struct route_table *table; struct route_node *rn; int i; struct ospf_lsdb *lsdb; lsdb = &nbr->ls_req; for (i = OSPF_MIN_LSA; i < OSPF_MAX_LSA; i++) { table = lsdb->type[i].db; for (rn = route_top(table); rn; rn = route_next(rn)) if ((lsa = (rn->info)) != NULL) if (ospf_make_ls_req_func(s, &length, delta, nbr, lsa) == 0) { route_unlock_node(rn); break; } } return length; } static int ls_age_increment(struct ospf_lsa *lsa, int delay) { int age; age = IS_LSA_MAXAGE(lsa) ? OSPF_LSA_MAXAGE : LS_AGE(lsa) + delay; return (age > OSPF_LSA_MAXAGE ? OSPF_LSA_MAXAGE : age); } static int ospf_make_ls_upd(struct ospf_interface *oi, struct list *update, struct stream *s) { struct ospf_lsa *lsa; struct listnode *node; uint16_t length = 0; unsigned int size_noauth; unsigned long delta = stream_get_endp(s); unsigned long pp; int count = 0; if (IS_DEBUG_OSPF_EVENT) zlog_debug("ospf_make_ls_upd: Start"); pp = stream_get_endp(s); stream_forward_endp(s, OSPF_LS_UPD_MIN_SIZE); length += OSPF_LS_UPD_MIN_SIZE; /* Calculate amount of packet usable for data. */ size_noauth = stream_get_size(s) - ospf_packet_authspace(oi); while ((node = listhead(update)) != NULL) { struct lsa_header *lsah; uint16_t ls_age; if (IS_DEBUG_OSPF_EVENT) zlog_debug("ospf_make_ls_upd: List Iteration %d", count); lsa = listgetdata(node); assert(lsa->data); /* Will it fit? Minimum it has to fit atleast one */ if ((length + delta + ntohs(lsa->data->length) > size_noauth) && (count > 0)) break; /* Keep pointer to LS age. */ lsah = (struct lsa_header *)(STREAM_DATA(s) + stream_get_endp(s)); /* Put LSA to Link State Request. */ stream_put(s, lsa->data, ntohs(lsa->data->length)); /* Set LS age. */ /* each hop must increment an lsa_age by transmit_delay of OSPF interface */ ls_age = ls_age_increment(lsa, OSPF_IF_PARAM(oi, transmit_delay)); lsah->ls_age = htons(ls_age); length += ntohs(lsa->data->length); count++; list_delete_node(update, node); ospf_lsa_unlock(&lsa); /* oi->ls_upd_queue */ } /* Now set #LSAs. */ stream_putl_at(s, pp, count); if (IS_DEBUG_OSPF_EVENT) zlog_debug("ospf_make_ls_upd: Stop"); return length; } static int ospf_make_ls_ack(struct ospf_interface *oi, struct list *ack, struct stream *s) { struct listnode *node, *nnode; uint16_t length = OSPF_LS_ACK_MIN_SIZE; unsigned long delta = OSPF_LSA_HEADER_SIZE; struct ospf_lsa *lsa; for (ALL_LIST_ELEMENTS(ack, node, nnode, lsa)) { assert(lsa); /* LS Ack packet overflows interface MTU * delta is just number of bytes required for * 1 LS Ack(1 LS Hdr) ospf_packet_max will return * the number of bytes can be accomodated without * ospf header. So length+delta can be compared * against ospf_packet_max to check if it can fit * another ls header in the same packet. */ if ((length + delta) > ospf_packet_max(oi)) break; stream_put(s, lsa->data, OSPF_LSA_HEADER_SIZE); length += OSPF_LSA_HEADER_SIZE; listnode_delete(ack, lsa); ospf_lsa_unlock(&lsa); /* oi->ls_ack_direct.ls_ack */ } return length; } static void ospf_hello_send_sub(struct ospf_interface *oi, in_addr_t addr) { struct ospf_packet *op; uint16_t length = OSPF_HEADER_SIZE; op = ospf_packet_new(oi->ifp->mtu); /* Prepare OSPF common header. */ ospf_make_header(OSPF_MSG_HELLO, oi, op->s); /* Prepare OSPF Hello body. */ length += ospf_make_hello(oi, op->s); if (length == OSPF_HEADER_SIZE) { /* Hello overshooting MTU */ ospf_packet_free(op); return; } /* Fill OSPF header. */ ospf_fill_header(oi, op->s, length); /* Set packet length. */ op->length = length; op->dst.s_addr = addr; if (IS_DEBUG_OSPF_EVENT) { if (oi->ospf->vrf_id) zlog_debug( "%s: Hello Tx interface %s ospf vrf %s id %u", __PRETTY_FUNCTION__, oi->ifp->name, ospf_vrf_id_to_name(oi->ospf->vrf_id), oi->ospf->vrf_id); } /* Add packet to the top of the interface output queue, so that they * can't get delayed by things like long queues of LS Update packets */ ospf_packet_add_top(oi, op); /* Hook thread to write packet. */ OSPF_ISM_WRITE_ON(oi->ospf); } static void ospf_poll_send(struct ospf_nbr_nbma *nbr_nbma) { struct ospf_interface *oi; oi = nbr_nbma->oi; assert(oi); /* If this is passive interface, do not send OSPF Hello. */ if (OSPF_IF_PASSIVE_STATUS(oi) == OSPF_IF_PASSIVE) return; if (oi->type != OSPF_IFTYPE_NBMA) return; if (nbr_nbma->nbr != NULL && nbr_nbma->nbr->state != NSM_Down) return; if (PRIORITY(oi) == 0) return; if (nbr_nbma->priority == 0 && oi->state != ISM_DR && oi->state != ISM_Backup) return; ospf_hello_send_sub(oi, nbr_nbma->addr.s_addr); } int ospf_poll_timer(struct thread *thread) { struct ospf_nbr_nbma *nbr_nbma; nbr_nbma = THREAD_ARG(thread); nbr_nbma->t_poll = NULL; if (IS_DEBUG_OSPF(nsm, NSM_TIMERS)) zlog_debug("NSM[%s:%s]: Timer (Poll timer expire)", IF_NAME(nbr_nbma->oi), inet_ntoa(nbr_nbma->addr)); ospf_poll_send(nbr_nbma); if (nbr_nbma->v_poll > 0) OSPF_POLL_TIMER_ON(nbr_nbma->t_poll, ospf_poll_timer, nbr_nbma->v_poll); return 0; } int ospf_hello_reply_timer(struct thread *thread) { struct ospf_neighbor *nbr; nbr = THREAD_ARG(thread); nbr->t_hello_reply = NULL; assert(nbr->oi); if (IS_DEBUG_OSPF(nsm, NSM_TIMERS)) zlog_debug("NSM[%s:%s]: Timer (hello-reply timer expire)", IF_NAME(nbr->oi), inet_ntoa(nbr->router_id)); ospf_hello_send_sub(nbr->oi, nbr->address.u.prefix4.s_addr); return 0; } /* Send OSPF Hello. */ void ospf_hello_send(struct ospf_interface *oi) { /* If this is passive interface, do not send OSPF Hello. */ if (OSPF_IF_PASSIVE_STATUS(oi) == OSPF_IF_PASSIVE) return; if (oi->type == OSPF_IFTYPE_NBMA) { struct ospf_neighbor *nbr; struct route_node *rn; for (rn = route_top(oi->nbrs); rn; rn = route_next(rn)) if ((nbr = rn->info)) if (nbr != oi->nbr_self) if (nbr->state != NSM_Down) { /* RFC 2328 Section 9.5.1 If the router is not eligible to become Designated Router, it must periodically send Hello Packets to both the Designated Router and the Backup Designated Router (if they exist). */ if (PRIORITY(oi) == 0 && IPV4_ADDR_CMP( &DR(oi), &nbr->address.u .prefix4) && IPV4_ADDR_CMP( &BDR(oi), &nbr->address.u .prefix4)) continue; /* If the router is eligible to become Designated Router, it must periodically send Hello Packets to all neighbors that are also eligible. In addition, if the router is itself the Designated Router or Backup Designated Router, it must also send periodic Hello Packets to all other neighbors. */ if (nbr->priority == 0 && oi->state == ISM_DROther) continue; /* if oi->state == Waiting, send * hello to all neighbors */ ospf_hello_send_sub( oi, nbr->address.u.prefix4 .s_addr); } } else { /* Decide destination address. */ if (oi->type == OSPF_IFTYPE_VIRTUALLINK) ospf_hello_send_sub(oi, oi->vl_data->peer_addr.s_addr); else ospf_hello_send_sub(oi, htonl(OSPF_ALLSPFROUTERS)); } } /* Send OSPF Database Description. */ void ospf_db_desc_send(struct ospf_neighbor *nbr) { struct ospf_interface *oi; struct ospf_packet *op; uint16_t length = OSPF_HEADER_SIZE; oi = nbr->oi; op = ospf_packet_new(oi->ifp->mtu); /* Prepare OSPF common header. */ ospf_make_header(OSPF_MSG_DB_DESC, oi, op->s); /* Prepare OSPF Database Description body. */ length += ospf_make_db_desc(oi, nbr, op->s); /* Fill OSPF header. */ ospf_fill_header(oi, op->s, length); /* Set packet length. */ op->length = length; /* Decide destination address. */ if (oi->type == OSPF_IFTYPE_POINTOPOINT) op->dst.s_addr = htonl(OSPF_ALLSPFROUTERS); else op->dst = nbr->address.u.prefix4; /* Add packet to the interface output queue. */ ospf_packet_add(oi, op); /* Hook thread to write packet. */ OSPF_ISM_WRITE_ON(oi->ospf); /* Remove old DD packet, then copy new one and keep in neighbor * structure. */ if (nbr->last_send) ospf_packet_free(nbr->last_send); nbr->last_send = ospf_packet_dup(op); monotime(&nbr->last_send_ts); } /* Re-send Database Description. */ void ospf_db_desc_resend(struct ospf_neighbor *nbr) { struct ospf_interface *oi; oi = nbr->oi; /* Add packet to the interface output queue. */ ospf_packet_add(oi, ospf_packet_dup(nbr->last_send)); /* Hook thread to write packet. */ OSPF_ISM_WRITE_ON(oi->ospf); } /* Send Link State Request. */ void ospf_ls_req_send(struct ospf_neighbor *nbr) { struct ospf_interface *oi; struct ospf_packet *op; uint16_t length = OSPF_HEADER_SIZE; oi = nbr->oi; op = ospf_packet_new(oi->ifp->mtu); /* Prepare OSPF common header. */ ospf_make_header(OSPF_MSG_LS_REQ, oi, op->s); /* Prepare OSPF Link State Request body. */ length += ospf_make_ls_req(nbr, op->s); if (length == OSPF_HEADER_SIZE) { ospf_packet_free(op); return; } /* Fill OSPF header. */ ospf_fill_header(oi, op->s, length); /* Set packet length. */ op->length = length; /* Decide destination address. */ if (oi->type == OSPF_IFTYPE_POINTOPOINT) op->dst.s_addr = htonl(OSPF_ALLSPFROUTERS); else op->dst = nbr->address.u.prefix4; /* Add packet to the interface output queue. */ ospf_packet_add(oi, op); /* Hook thread to write packet. */ OSPF_ISM_WRITE_ON(oi->ospf); /* Add Link State Request Retransmission Timer. */ OSPF_NSM_TIMER_ON(nbr->t_ls_req, ospf_ls_req_timer, nbr->v_ls_req); } /* Send Link State Update with an LSA. */ void ospf_ls_upd_send_lsa(struct ospf_neighbor *nbr, struct ospf_lsa *lsa, int flag) { struct list *update; update = list_new(); listnode_add(update, lsa); /*ospf instance is going down, send self originated * MAXAGE LSA update to neighbors to remove from LSDB */ if (nbr->oi->ospf->inst_shutdown && IS_LSA_MAXAGE(lsa)) ospf_ls_upd_send(nbr, update, flag, 1); else ospf_ls_upd_send(nbr, update, flag, 0); list_delete(&update); } /* Determine size for packet. Must be at least big enough to accomodate next * LSA on list, which may be bigger than MTU size. * * Return pointer to new ospf_packet * NULL if we can not allocate, eg because LSA is bigger than imposed limit * on packet sizes (in which case offending LSA is deleted from update list) */ static struct ospf_packet *ospf_ls_upd_packet_new(struct list *update, struct ospf_interface *oi) { struct ospf_lsa *lsa; struct listnode *ln; size_t size; static char warned = 0; lsa = listgetdata((ln = listhead(update))); assert(lsa->data); if ((OSPF_LS_UPD_MIN_SIZE + ntohs(lsa->data->length)) > ospf_packet_max(oi)) { if (!warned) { flog_warn( EC_OSPF_LARGE_LSA, "ospf_ls_upd_packet_new: oversized LSA encountered!" "will need to fragment. Not optimal. Try divide up" " your network with areas. Use 'debug ospf packet send'" " to see details, or look at 'show ip ospf database ..'"); warned = 1; } if (IS_DEBUG_OSPF_PACKET(0, SEND)) zlog_debug( "ospf_ls_upd_packet_new: oversized LSA id:%s," " %d bytes originated by %s, will be fragmented!", inet_ntoa(lsa->data->id), ntohs(lsa->data->length), inet_ntoa(lsa->data->adv_router)); /* * Allocate just enough to fit this LSA only, to avoid including * other * LSAs in fragmented LSA Updates. */ size = ntohs(lsa->data->length) + (oi->ifp->mtu - ospf_packet_max(oi)) + OSPF_LS_UPD_MIN_SIZE; } else size = oi->ifp->mtu; if (size > OSPF_MAX_PACKET_SIZE) { flog_warn(EC_OSPF_LARGE_LSA, "ospf_ls_upd_packet_new: oversized LSA id:%s too big," " %d bytes, packet size %ld, dropping it completely." " OSPF routing is broken!", inet_ntoa(lsa->data->id), ntohs(lsa->data->length), (long int)size); list_delete_node(update, ln); return NULL; } /* IP header is built up separately by ospf_write(). This means, that we * must * reduce the "affordable" size just calculated by length of an IP * header. * This makes sure, that even if we manage to fill the payload with LSA * data * completely, the final packet (our data plus IP header) still fits * into * outgoing interface MTU. This correction isn't really meaningful for * an * oversized LSA, but for consistency the correction is done for both * cases. * * P.S. OSPF_MAX_PACKET_SIZE above already includes IP header size */ return ospf_packet_new(size - sizeof(struct ip)); } static void ospf_ls_upd_queue_send(struct ospf_interface *oi, struct list *update, struct in_addr addr, int send_lsupd_now) { struct ospf_packet *op; uint16_t length = OSPF_HEADER_SIZE; if (IS_DEBUG_OSPF_EVENT) zlog_debug("listcount = %d, [%s]dst %s", listcount(update), IF_NAME(oi), inet_ntoa(addr)); /* Check that we have really something to process */ if (listcount(update) == 0) return; op = ospf_ls_upd_packet_new(update, oi); /* Prepare OSPF common header. */ ospf_make_header(OSPF_MSG_LS_UPD, oi, op->s); /* Prepare OSPF Link State Update body. * Includes Type-7 translation. */ length += ospf_make_ls_upd(oi, update, op->s); /* Fill OSPF header. */ ospf_fill_header(oi, op->s, length); /* Set packet length. */ op->length = length; /* Decide destination address. */ if (oi->type == OSPF_IFTYPE_POINTOPOINT) op->dst.s_addr = htonl(OSPF_ALLSPFROUTERS); else op->dst.s_addr = addr.s_addr; /* Add packet to the interface output queue. */ ospf_packet_add(oi, op); /* Call ospf_write() right away to send ospf packets to neighbors */ if (send_lsupd_now) { struct thread os_packet_thd; os_packet_thd.arg = (void *)oi->ospf; if (oi->on_write_q == 0) { listnode_add(oi->ospf->oi_write_q, oi); oi->on_write_q = 1; } ospf_write(&os_packet_thd); /* * We are fake calling ospf_write with a fake * thread. Imagine that we have oi_a already * enqueued and we have turned on the write * thread(t_write). * Now this function calls this for oi_b * so the on_write_q has oi_a and oi_b on * it, ospf_write runs and clears the packets * for both oi_a and oi_b. Removing them from * the on_write_q. After this thread of execution * finishes we will execute the t_write thread * with nothing in the on_write_q causing an * assert. So just make sure that the t_write * is actually turned off. */ if (list_isempty(oi->ospf->oi_write_q)) OSPF_TIMER_OFF(oi->ospf->t_write); } else { /* Hook thread to write packet. */ OSPF_ISM_WRITE_ON(oi->ospf); } } static int ospf_ls_upd_send_queue_event(struct thread *thread) { struct ospf_interface *oi = THREAD_ARG(thread); struct route_node *rn; struct route_node *rnext; struct list *update; char again = 0; oi->t_ls_upd_event = NULL; if (IS_DEBUG_OSPF_EVENT) zlog_debug("ospf_ls_upd_send_queue start"); for (rn = route_top(oi->ls_upd_queue); rn; rn = rnext) { rnext = route_next(rn); if (rn->info == NULL) continue; update = (struct list *)rn->info; ospf_ls_upd_queue_send(oi, update, rn->p.u.prefix4, 0); /* list might not be empty. */ if (listcount(update) == 0) { list_delete((struct list **)&rn->info); route_unlock_node(rn); } else again = 1; } if (again != 0) { if (IS_DEBUG_OSPF_EVENT) zlog_debug( "ospf_ls_upd_send_queue: update lists not cleared," " %d nodes to try again, raising new event", again); oi->t_ls_upd_event = NULL; thread_add_event(master, ospf_ls_upd_send_queue_event, oi, 0, &oi->t_ls_upd_event); } if (IS_DEBUG_OSPF_EVENT) zlog_debug("ospf_ls_upd_send_queue stop"); return 0; } void ospf_ls_upd_send(struct ospf_neighbor *nbr, struct list *update, int flag, int send_lsupd_now) { struct ospf_interface *oi; struct ospf_lsa *lsa; struct prefix_ipv4 p; struct route_node *rn; struct listnode *node; oi = nbr->oi; p.family = AF_INET; p.prefixlen = IPV4_MAX_BITLEN; /* Decide destination address. */ if (oi->type == OSPF_IFTYPE_VIRTUALLINK) p.prefix = oi->vl_data->peer_addr; else if (oi->type == OSPF_IFTYPE_POINTOPOINT) p.prefix.s_addr = htonl(OSPF_ALLSPFROUTERS); else if (flag == OSPF_SEND_PACKET_DIRECT) p.prefix = nbr->address.u.prefix4; else if (oi->state == ISM_DR || oi->state == ISM_Backup) p.prefix.s_addr = htonl(OSPF_ALLSPFROUTERS); else if (oi->type == OSPF_IFTYPE_POINTOMULTIPOINT) p.prefix.s_addr = htonl(OSPF_ALLSPFROUTERS); else p.prefix.s_addr = htonl(OSPF_ALLDROUTERS); if (oi->type == OSPF_IFTYPE_NBMA) { if (flag == OSPF_SEND_PACKET_INDIRECT) flog_warn( EC_OSPF_PACKET, "* LS-Update is directly sent on NBMA network."); if (IPV4_ADDR_SAME(&oi->address->u.prefix4, &p.prefix)) flog_warn(EC_OSPF_PACKET, "* LS-Update is sent to myself."); } rn = route_node_get(oi->ls_upd_queue, (struct prefix *)&p); if (rn->info == NULL) rn->info = list_new(); else route_unlock_node(rn); for (ALL_LIST_ELEMENTS_RO(update, node, lsa)) listnode_add(rn->info, ospf_lsa_lock(lsa)); /* oi->ls_upd_queue */ if (send_lsupd_now) { struct list *send_update_list; struct route_node *rnext; for (rn = route_top(oi->ls_upd_queue); rn; rn = rnext) { rnext = route_next(rn); if (rn->info == NULL) continue; send_update_list = (struct list *)rn->info; ospf_ls_upd_queue_send(oi, send_update_list, rn->p.u.prefix4, 1); } } else thread_add_event(master, ospf_ls_upd_send_queue_event, oi, 0, &oi->t_ls_upd_event); } static void ospf_ls_ack_send_list(struct ospf_interface *oi, struct list *ack, struct in_addr dst) { struct ospf_packet *op; uint16_t length = OSPF_HEADER_SIZE; op = ospf_packet_new(oi->ifp->mtu); /* Prepare OSPF common header. */ ospf_make_header(OSPF_MSG_LS_ACK, oi, op->s); /* Prepare OSPF Link State Acknowledgment body. */ length += ospf_make_ls_ack(oi, ack, op->s); /* Fill OSPF header. */ ospf_fill_header(oi, op->s, length); /* Set packet length. */ op->length = length; /* Decide destination address. */ if (oi->type == OSPF_IFTYPE_POINTOPOINT) op->dst.s_addr = htonl(OSPF_ALLSPFROUTERS); else op->dst.s_addr = dst.s_addr; /* Add packet to the interface output queue. */ ospf_packet_add(oi, op); /* Hook thread to write packet. */ OSPF_ISM_WRITE_ON(oi->ospf); } static int ospf_ls_ack_send_event(struct thread *thread) { struct ospf_interface *oi = THREAD_ARG(thread); oi->t_ls_ack_direct = NULL; while (listcount(oi->ls_ack_direct.ls_ack)) ospf_ls_ack_send_list(oi, oi->ls_ack_direct.ls_ack, oi->ls_ack_direct.dst); return 0; } void ospf_ls_ack_send(struct ospf_neighbor *nbr, struct ospf_lsa *lsa) { struct ospf_interface *oi = nbr->oi; if (listcount(oi->ls_ack_direct.ls_ack) == 0) oi->ls_ack_direct.dst = nbr->address.u.prefix4; listnode_add(oi->ls_ack_direct.ls_ack, ospf_lsa_lock(lsa)); thread_add_event(master, ospf_ls_ack_send_event, oi, 0, &oi->t_ls_ack_direct); } /* Send Link State Acknowledgment delayed. */ void ospf_ls_ack_send_delayed(struct ospf_interface *oi) { struct in_addr dst; /* Decide destination address. */ /* RFC2328 Section 13.5 On non-broadcast networks, delayed Link State Acknowledgment packets must be unicast separately over each adjacency (i.e., neighbor whose state is >= Exchange). */ if (oi->type == OSPF_IFTYPE_NBMA) { struct ospf_neighbor *nbr; struct route_node *rn; for (rn = route_top(oi->nbrs); rn; rn = route_next(rn)) if ((nbr = rn->info) != NULL) if (nbr != oi->nbr_self && nbr->state >= NSM_Exchange) while (listcount(oi->ls_ack)) ospf_ls_ack_send_list( oi, oi->ls_ack, nbr->address.u.prefix4); return; } if (oi->type == OSPF_IFTYPE_VIRTUALLINK) dst.s_addr = oi->vl_data->peer_addr.s_addr; else if (oi->state == ISM_DR || oi->state == ISM_Backup) dst.s_addr = htonl(OSPF_ALLSPFROUTERS); else if (oi->type == OSPF_IFTYPE_POINTOPOINT) dst.s_addr = htonl(OSPF_ALLSPFROUTERS); else if (oi->type == OSPF_IFTYPE_POINTOMULTIPOINT) dst.s_addr = htonl(OSPF_ALLSPFROUTERS); else dst.s_addr = htonl(OSPF_ALLDROUTERS); while (listcount(oi->ls_ack)) ospf_ls_ack_send_list(oi, oi->ls_ack, dst); } /* * On pt-to-pt links, all OSPF control packets are sent to the multicast * address. As a result, the kernel does not need to learn the interface * MAC of the OSPF neighbor. However, in our world, this will delay * convergence. Take the case when due to a link flap, all routes now * want to use an interface which was deemed to be costlier prior to this * event. For routes that will be installed, the missing MAC will have * punt-to-CPU set on them. This may overload the CPU control path that * can be avoided if the MAC was known apriori. */ #define OSPF_PING_NBR_STR_MAX (BUFSIZ) void ospf_proactively_arp(struct ospf_neighbor *nbr) { char ping_nbr[OSPF_PING_NBR_STR_MAX]; int ret; if (!nbr || !nbr->oi || !nbr->oi->ifp) return; snprintf(ping_nbr, sizeof(ping_nbr), "ping -c 1 -I %s %s > /dev/null 2>&1 &", nbr->oi->ifp->name, inet_ntoa(nbr->address.u.prefix4)); ret = system(ping_nbr); if (IS_DEBUG_OSPF_EVENT) zlog_debug("Executed %s %s", ping_nbr, ((ret == 0) ? "successfully" : "but failed")); } frr-7.2.1/ospfd/ospf_packet.h0000644000000000000000000001242213610377563012777 00000000000000/* * OSPF Sending and Receiving OSPF Packets. * Copyright (C) 1999 Toshiaki Takada * * This file is part of GNU Zebra. * * GNU Zebra is free software; you can redistribute it and/or modify it * under the terms of the GNU General Public License as published by the * Free Software Foundation; either version 2, or (at your option) any * later version. * * GNU Zebra 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; see the file COPYING; if not, write to the Free Software * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA */ #ifndef _ZEBRA_OSPF_PACKET_H #define _ZEBRA_OSPF_PACKET_H #define OSPF_HEADER_SIZE 24U #define OSPF_AUTH_SIMPLE_SIZE 8U #define OSPF_AUTH_MD5_SIZE 16U #define OSPF_MAX_PACKET_SIZE 65535U /* includes IP Header size. */ #define OSPF_HELLO_MIN_SIZE 20U /* not including neighbors */ #define OSPF_DB_DESC_MIN_SIZE 8U #define OSPF_LS_REQ_MIN_SIZE 0U #define OSPF_LS_UPD_MIN_SIZE 4U #define OSPF_LS_ACK_MIN_SIZE 0U #define OSPF_MSG_HELLO 1 /* OSPF Hello Message. */ #define OSPF_MSG_DB_DESC 2 /* OSPF Database Descriptoin Message. */ #define OSPF_MSG_LS_REQ 3 /* OSPF Link State Request Message. */ #define OSPF_MSG_LS_UPD 4 /* OSPF Link State Update Message. */ #define OSPF_MSG_LS_ACK 5 /* OSPF Link State Acknoledgement Message. */ #define OSPF_SEND_PACKET_DIRECT 1 #define OSPF_SEND_PACKET_INDIRECT 2 #define OSPF_SEND_PACKET_LOOP 3 #define OSPF_HELLO_REPLY_DELAY 1 /* Return values of functions involved in packet verification, see ospf6d. */ #define MSG_OK 0 #define MSG_NG 1 struct ospf_packet { struct ospf_packet *next; /* Pointer to data stream. */ struct stream *s; /* IP destination address. */ struct in_addr dst; /* OSPF packet length. */ uint16_t length; }; /* OSPF packet queue structure. */ struct ospf_fifo { unsigned long count; struct ospf_packet *head; struct ospf_packet *tail; }; /* OSPF packet header structure. */ struct ospf_header { uint8_t version; /* OSPF Version. */ uint8_t type; /* Packet Type. */ uint16_t length; /* Packet Length. */ struct in_addr router_id; /* Router ID. */ struct in_addr area_id; /* Area ID. */ uint16_t checksum; /* Check Sum. */ uint16_t auth_type; /* Authentication Type. */ /* Authentication Data. */ union { /* Simple Authentication. */ uint8_t auth_data[OSPF_AUTH_SIMPLE_SIZE]; /* Cryptographic Authentication. */ struct { uint16_t zero; /* Should be 0. */ uint8_t key_id; /* Key ID. */ uint8_t auth_data_len; /* Auth Data Length. */ uint32_t crypt_seqnum; /* Cryptographic Sequence Number. */ } crypt; } u; }; /* OSPF Hello body format. */ struct ospf_hello { struct in_addr network_mask; uint16_t hello_interval; uint8_t options; uint8_t priority; uint32_t dead_interval; struct in_addr d_router; struct in_addr bd_router; struct in_addr neighbors[1]; }; /* OSPF Database Description body format. */ struct ospf_db_desc { uint16_t mtu; uint8_t options; uint8_t flags; uint32_t dd_seqnum; }; struct ospf_ls_update { uint32_t num_lsas; }; /* Macros. */ /* XXX Perhaps obsolete; function in ospf_packet.c */ #define OSPF_PACKET_MAX(oi) ospf_packet_max (oi) #define OSPF_OUTPUT_PNT(S) ((S)->data + (S)->putp) #define OSPF_OUTPUT_LENGTH(S) ((S)->endp) #define IS_SET_DD_MS(X) ((X) & OSPF_DD_FLAG_MS) #define IS_SET_DD_M(X) ((X) & OSPF_DD_FLAG_M) #define IS_SET_DD_I(X) ((X) & OSPF_DD_FLAG_I) #define IS_SET_DD_ALL(X) ((X) & OSPF_DD_FLAG_ALL) /* Prototypes. */ extern void ospf_packet_free(struct ospf_packet *); extern struct ospf_fifo *ospf_fifo_new(void); extern void ospf_fifo_push(struct ospf_fifo *, struct ospf_packet *); extern struct ospf_packet *ospf_fifo_pop(struct ospf_fifo *); extern struct ospf_packet *ospf_fifo_head(struct ospf_fifo *); extern void ospf_fifo_flush(struct ospf_fifo *); extern void ospf_fifo_free(struct ospf_fifo *); extern int ospf_read(struct thread *); extern void ospf_hello_send(struct ospf_interface *); extern void ospf_db_desc_send(struct ospf_neighbor *); extern void ospf_db_desc_resend(struct ospf_neighbor *); extern void ospf_ls_req_send(struct ospf_neighbor *); extern void ospf_ls_upd_send_lsa(struct ospf_neighbor *, struct ospf_lsa *, int); extern void ospf_ls_upd_send(struct ospf_neighbor *, struct list *, int, int); extern void ospf_ls_ack_send(struct ospf_neighbor *, struct ospf_lsa *); extern void ospf_ls_ack_send_delayed(struct ospf_interface *); extern void ospf_ls_retransmit(struct ospf_interface *, struct ospf_lsa *); extern void ospf_ls_req_event(struct ospf_neighbor *); extern int ospf_ls_upd_timer(struct thread *); extern int ospf_ls_ack_timer(struct thread *); extern int ospf_poll_timer(struct thread *); extern int ospf_hello_reply_timer(struct thread *); extern const struct message ospf_packet_type_str[]; extern const size_t ospf_packet_type_str_max; extern void ospf_proactively_arp(struct ospf_neighbor *); #endif /* _ZEBRA_OSPF_PACKET_H */ frr-7.2.1/ospfd/ospf_ri.c0000644000000000000000000014675613610377563012157 00000000000000/* * This is an implementation of RFC4970 Router Information * with support of RFC5088 PCE Capabilites announcement * * Module name: Router Information * Author: Olivier Dugeon * Copyright (C) 2012 - 2017 Orange Labs http://www.orange.com/ * * This file is part of GNU Quagga. * * GNU Zebra is free software; you can redistribute it and/or modify it * under the terms of the GNU General Public License as published by the * Free Software Foundation; either version 2, or (at your option) any * later version. * * GNU Quagga 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; see the file COPYING; if not, write to the Free Software * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA */ #include #include #include "linklist.h" #include "prefix.h" #include "if.h" #include "table.h" #include "memory.h" #include "command.h" #include "vty.h" #include "stream.h" #include "log.h" #include "thread.h" #include "hash.h" #include "sockunion.h" /* for inet_aton() */ #include "mpls.h" #include "ospfd/ospfd.h" #include "ospfd/ospf_interface.h" #include "ospfd/ospf_ism.h" #include "ospfd/ospf_asbr.h" #include "ospfd/ospf_lsa.h" #include "ospfd/ospf_lsdb.h" #include "ospfd/ospf_neighbor.h" #include "ospfd/ospf_nsm.h" #include "ospfd/ospf_flood.h" #include "ospfd/ospf_packet.h" #include "ospfd/ospf_spf.h" #include "ospfd/ospf_dump.h" #include "ospfd/ospf_route.h" #include "ospfd/ospf_ase.h" #include "ospfd/ospf_zebra.h" #include "ospfd/ospf_sr.h" #include "ospfd/ospf_ri.h" #include "ospfd/ospf_errors.h" /* * Global variable to manage Opaque-LSA/Router Information on this node. * Note that all parameter values are stored in network byte order. */ static struct ospf_router_info OspfRI; /*------------------------------------------------------------------------------* * Followings are initialize/terminate functions for Router Information *handling. *------------------------------------------------------------------------------*/ static void ospf_router_info_ism_change(struct ospf_interface *oi, int old_status); static void ospf_router_info_config_write_router(struct vty *vty); static void ospf_router_info_show_info(struct vty *vty, struct ospf_lsa *lsa); static int ospf_router_info_lsa_originate(void *arg); static struct ospf_lsa *ospf_router_info_lsa_refresh(struct ospf_lsa *lsa); static void ospf_router_info_lsa_schedule(struct ospf_ri_area_info *ai, enum lsa_opcode opcode); static void ospf_router_info_register_vty(void); static int ospf_router_info_lsa_update(struct ospf_lsa *lsa); static void del_area_info(void *val); static void del_pce_info(void *val); int ospf_router_info_init(void) { zlog_info("RI (%s): Initialize Router Information", __func__); memset(&OspfRI, 0, sizeof(struct ospf_router_info)); OspfRI.enabled = false; OspfRI.registered = 0; OspfRI.scope = OSPF_OPAQUE_AS_LSA; OspfRI.as_flags = RIFLG_LSA_INACTIVE; OspfRI.area_info = list_new(); OspfRI.area_info->del = del_area_info; /* Initialize pce domain and neighbor list */ OspfRI.pce_info.enabled = false; OspfRI.pce_info.pce_domain = list_new(); OspfRI.pce_info.pce_domain->del = del_pce_info; OspfRI.pce_info.pce_neighbor = list_new(); OspfRI.pce_info.pce_neighbor->del = del_pce_info; /* Initialize Segment Routing information structure */ OspfRI.sr_info.enabled = false; ospf_router_info_register_vty(); return 0; } static int ospf_router_info_register(uint8_t scope) { int rc = 0; if (OspfRI.registered) return rc; zlog_info("RI (%s): Register Router Information with scope %s(%d)", __func__, scope == OSPF_OPAQUE_AREA_LSA ? "Area" : "AS", scope); rc = ospf_register_opaque_functab( scope, OPAQUE_TYPE_ROUTER_INFORMATION_LSA, NULL, /* new interface */ NULL, /* del interface */ ospf_router_info_ism_change, NULL, /* NSM change */ ospf_router_info_config_write_router, NULL, /* Config. write interface */ NULL, /* Config. write debug */ ospf_router_info_show_info, ospf_router_info_lsa_originate, ospf_router_info_lsa_refresh, ospf_router_info_lsa_update, NULL); /* del_lsa_hook */ if (rc != 0) { flog_warn( EC_OSPF_OPAQUE_REGISTRATION, "RI (%s): Failed to register functions", __func__); return rc; } OspfRI.registered = 1; OspfRI.scope = scope; return rc; } static int ospf_router_info_unregister(void) { if ((OspfRI.scope != OSPF_OPAQUE_AS_LSA) && (OspfRI.scope != OSPF_OPAQUE_AREA_LSA)) { assert("Unable to unregister Router Info functions: Wrong scope!" == NULL); return -1; } ospf_delete_opaque_functab(OspfRI.scope, OPAQUE_TYPE_ROUTER_INFORMATION_LSA); OspfRI.registered = 0; return 0; } void ospf_router_info_term(void) { list_delete(&OspfRI.pce_info.pce_domain); list_delete(&OspfRI.pce_info.pce_neighbor); OspfRI.enabled = false; ospf_router_info_unregister(); return; } void ospf_router_info_finish(void) { list_delete_all_node(OspfRI.pce_info.pce_domain); list_delete_all_node(OspfRI.pce_info.pce_neighbor); OspfRI.enabled = false; } static void del_area_info(void *val) { XFREE(MTYPE_OSPF_ROUTER_INFO, val); } static void del_pce_info(void *val) { XFREE(MTYPE_OSPF_PCE_PARAMS, val); } /* Catch RI LSA flooding Scope for ospf_ext.[h,c] code */ struct scope_info ospf_router_info_get_flooding_scope(void) { struct scope_info flooding_scope; if (OspfRI.scope == OSPF_OPAQUE_AS_LSA) { flooding_scope.scope = OSPF_OPAQUE_AS_LSA; flooding_scope.areas = NULL; return flooding_scope; } flooding_scope.scope = OSPF_OPAQUE_AREA_LSA; flooding_scope.areas = OspfRI.area_info; return flooding_scope; } static struct ospf_ri_area_info *lookup_by_area(struct ospf_area *area) { struct listnode *node, *nnode; struct ospf_ri_area_info *ai; for (ALL_LIST_ELEMENTS(OspfRI.area_info, node, nnode, ai)) if (ai->area == area) return ai; return NULL; } /*------------------------------------------------------------------------* * Followings are control functions for ROUTER INFORMATION parameters *management. *------------------------------------------------------------------------*/ static void set_router_info_capabilities(struct ri_tlv_router_cap *ric, uint32_t cap) { ric->header.type = htons(RI_TLV_CAPABILITIES); ric->header.length = htons(RI_TLV_LENGTH); ric->value = htonl(cap); return; } static int set_pce_header(struct ospf_pce_info *pce) { uint16_t length = 0; struct listnode *node; struct ri_pce_subtlv_domain *domain; struct ri_pce_subtlv_neighbor *neighbor; /* PCE Address */ if (ntohs(pce->pce_address.header.type) != 0) length += TLV_SIZE(&pce->pce_address.header); /* PCE Path Scope */ if (ntohs(pce->pce_scope.header.type) != 0) length += TLV_SIZE(&pce->pce_scope.header); /* PCE Domain */ for (ALL_LIST_ELEMENTS_RO(pce->pce_domain, node, domain)) { if (ntohs(domain->header.type) != 0) length += TLV_SIZE(&domain->header); } /* PCE Neighbor */ for (ALL_LIST_ELEMENTS_RO(pce->pce_neighbor, node, neighbor)) { if (ntohs(neighbor->header.type) != 0) length += TLV_SIZE(&neighbor->header); } /* PCE Capabilities */ if (ntohs(pce->pce_cap_flag.header.type) != 0) length += TLV_SIZE(&pce->pce_cap_flag.header); if (length != 0) { pce->pce_header.header.type = htons(RI_TLV_PCE); pce->pce_header.header.length = htons(length); pce->enabled = true; } else { pce->pce_header.header.type = 0; pce->pce_header.header.length = 0; pce->enabled = false; } return length; } static void set_pce_address(struct in_addr ipv4, struct ospf_pce_info *pce) { /* Enable PCE Info */ pce->pce_header.header.type = htons(RI_TLV_PCE); /* Set PCE Address */ pce->pce_address.header.type = htons(RI_PCE_SUBTLV_ADDRESS); pce->pce_address.header.length = htons(PCE_ADDRESS_LENGTH_IPV4); pce->pce_address.address.type = htons(PCE_ADDRESS_TYPE_IPV4); pce->pce_address.address.value = ipv4; return; } static void set_pce_path_scope(uint32_t scope, struct ospf_pce_info *pce) { /* Set PCE Scope */ pce->pce_scope.header.type = htons(RI_PCE_SUBTLV_PATH_SCOPE); pce->pce_scope.header.length = htons(RI_TLV_LENGTH); pce->pce_scope.value = htonl(scope); return; } static void set_pce_domain(uint16_t type, uint32_t domain, struct ospf_pce_info *pce) { struct ri_pce_subtlv_domain *new; /* Create new domain info */ new = XCALLOC(MTYPE_OSPF_PCE_PARAMS, sizeof(struct ri_pce_subtlv_domain)); new->header.type = htons(RI_PCE_SUBTLV_DOMAIN); new->header.length = htons(PCE_ADDRESS_LENGTH_IPV4); new->type = htons(type); new->value = htonl(domain); /* Add new domain to the list */ listnode_add(pce->pce_domain, new); return; } static void unset_pce_domain(uint16_t type, uint32_t domain, struct ospf_pce_info *pce) { struct listnode *node; struct ri_pce_subtlv_domain *old = NULL; int found = 0; /* Search the corresponding node */ for (ALL_LIST_ELEMENTS_RO(pce->pce_domain, node, old)) { if ((old->type == htons(type)) && (old->value == htonl(domain))) { found = 1; break; } } /* if found remove it */ if (found) { listnode_delete(pce->pce_domain, old); /* Finally free the old domain */ XFREE(MTYPE_OSPF_PCE_PARAMS, old); } } static void set_pce_neighbor(uint16_t type, uint32_t domain, struct ospf_pce_info *pce) { struct ri_pce_subtlv_neighbor *new; /* Create new neighbor info */ new = XCALLOC(MTYPE_OSPF_PCE_PARAMS, sizeof(struct ri_pce_subtlv_neighbor)); new->header.type = htons(RI_PCE_SUBTLV_NEIGHBOR); new->header.length = htons(PCE_ADDRESS_LENGTH_IPV4); new->type = htons(type); new->value = htonl(domain); /* Add new domain to the list */ listnode_add(pce->pce_neighbor, new); return; } static void unset_pce_neighbor(uint16_t type, uint32_t domain, struct ospf_pce_info *pce) { struct listnode *node; struct ri_pce_subtlv_neighbor *old = NULL; int found = 0; /* Search the corresponding node */ for (ALL_LIST_ELEMENTS_RO(pce->pce_neighbor, node, old)) { if ((old->type == htons(type)) && (old->value == htonl(domain))) { found = 1; break; } } /* if found remove it */ if (found) { listnode_delete(pce->pce_neighbor, old); /* Finally free the old domain */ XFREE(MTYPE_OSPF_PCE_PARAMS, old); } } static void set_pce_cap_flag(uint32_t cap, struct ospf_pce_info *pce) { /* Set PCE Capabilities flag */ pce->pce_cap_flag.header.type = htons(RI_PCE_SUBTLV_CAP_FLAG); pce->pce_cap_flag.header.length = htons(RI_TLV_LENGTH); pce->pce_cap_flag.value = htonl(cap); return; } /* Segment Routing TLV setter */ /* Algorithm SubTLV - section 3.1 */ static void set_sr_algorithm(uint8_t algo) { OspfRI.sr_info.algo.value[0] = algo; for (int i = 1; i < ALGORITHM_COUNT; i++) OspfRI.sr_info.algo.value[i] = SR_ALGORITHM_UNSET; /* Set TLV type and length == only 1 Algorithm */ TLV_TYPE(OspfRI.sr_info.algo) = htons(RI_SR_TLV_SR_ALGORITHM); TLV_LEN(OspfRI.sr_info.algo) = htons(sizeof(uint8_t)); } /* unset Aglogithm SubTLV */ static void unset_sr_algorithm(uint8_t algo) { for (int i = 0; i < ALGORITHM_COUNT; i++) OspfRI.sr_info.algo.value[i] = SR_ALGORITHM_UNSET; /* Unset TLV type and length */ TLV_TYPE(OspfRI.sr_info.algo) = htons(0); TLV_LEN(OspfRI.sr_info.algo) = htons(0); } /* Segment Routing Global Block SubTLV - section 3.2 */ static void set_sr_sid_label_range(struct sr_srgb srgb) { /* Set Header */ TLV_TYPE(OspfRI.sr_info.range) = htons(RI_SR_TLV_SID_LABEL_RANGE); TLV_LEN(OspfRI.sr_info.range) = htons(SUBTLV_SID_LABEL_SIZE + sizeof(uint32_t)); /* Set Range Size */ OspfRI.sr_info.range.size = htonl(SET_RANGE_SIZE(srgb.range_size)); /* Set Lower bound label SubTLV */ TLV_TYPE(OspfRI.sr_info.range.lower) = htons(SUBTLV_SID_LABEL); TLV_LEN(OspfRI.sr_info.range.lower) = htons(SID_RANGE_LABEL_LENGTH); OspfRI.sr_info.range.lower.value = htonl(SET_LABEL(srgb.lower_bound)); } /* Unset this SRGB SubTLV */ static void unset_sr_sid_label_range(void) { TLV_TYPE(OspfRI.sr_info.range) = htons(0); TLV_LEN(OspfRI.sr_info.range) = htons(0); TLV_TYPE(OspfRI.sr_info.range.lower) = htons(0); TLV_LEN(OspfRI.sr_info.range.lower) = htons(0); } /* Set Maximum Stack Depth for this router */ static void set_sr_node_msd(uint8_t msd) { TLV_TYPE(OspfRI.sr_info.msd) = htons(RI_SR_TLV_NODE_MSD); TLV_LEN(OspfRI.sr_info.msd) = htons(sizeof(uint32_t)); OspfRI.sr_info.msd.value = msd; } /* Unset this router MSD */ static void unset_sr_node_msd(void) { TLV_TYPE(OspfRI.sr_info.msd) = htons(0); TLV_LEN(OspfRI.sr_info.msd) = htons(0); } static void unset_param(void *tlv_buffer) { struct tlv_header *tlv = (struct tlv_header *)tlv_buffer; tlv->type = 0; /* Fill the Value to 0 */ memset(TLV_DATA(tlv_buffer), 0, TLV_BODY_SIZE(tlv)); tlv->length = 0; return; } static void initialize_params(struct ospf_router_info *ori) { uint32_t cap = 0; struct ospf *top; struct listnode *node, *nnode; struct ospf_area *area; struct ospf_ri_area_info *new; /* * Initialize default Router Information Capabilities. */ cap = RI_TE_SUPPORT; set_router_info_capabilities(&ori->router_cap, cap); /* If Area address is not null and exist, retrieve corresponding * structure */ top = ospf_lookup_by_vrf_id(VRF_DEFAULT); zlog_info("RI (%s): Initialize Router Info for %s scope", __func__, OspfRI.scope == OSPF_OPAQUE_AREA_LSA ? "Area" : "AS"); /* Try to get available Area's context from ospf at this step. * Do it latter if not available */ if (OspfRI.scope == OSPF_OPAQUE_AREA_LSA) { for (ALL_LIST_ELEMENTS(top->areas, node, nnode, area)) { zlog_debug("RI (%s): Add area %s to Router Information", __func__, inet_ntoa(area->area_id)); new = XCALLOC(MTYPE_OSPF_ROUTER_INFO, sizeof(struct ospf_ri_area_info)); new->area = area; new->flags = RIFLG_LSA_INACTIVE; listnode_add(OspfRI.area_info, new); } } /* * Initialize default PCE Information values */ /* PCE address == OSPF Router ID */ set_pce_address(top->router_id, &ori->pce_info); /* PCE scope */ cap = 7; /* Set L, R and Rd bits to one = intra & inter-area path computation */ set_pce_path_scope(cap, &ori->pce_info); /* PCE Capabilities */ cap = PCE_CAP_BIDIRECTIONAL | PCE_CAP_DIVERSE_PATH | PCE_CAP_OBJECTIVES | PCE_CAP_ADDITIVE | PCE_CAP_MULTIPLE_REQ; set_pce_cap_flag(cap, &ori->pce_info); return; } static int is_mandated_params_set(struct ospf_router_info ori) { int rc = 0; if (ntohs(ori.router_cap.header.type) == 0) return rc; if ((ntohs(ori.pce_info.pce_header.header.type) == RI_TLV_PCE) && (ntohs(ori.pce_info.pce_address.header.type) == 0) && (ntohs(ori.pce_info.pce_cap_flag.header.type) == 0)) return rc; if ((ori.sr_info.enabled) && (ntohs(TLV_TYPE(ori.sr_info.algo)) == 0) && (ntohs(TLV_TYPE(ori.sr_info.range)) == 0)) return rc; rc = 1; return rc; } /* * Used by Segment Routing to set new TLVs and Sub-TLVs values * * @param enable To activate or not Segment Routing router Information flooding * @param size Size of Label Range i.e. SRGB size * @param lower Lower bound of the Label Range i.e. SRGB first label * @param msd Maximum label Stack Depth supported by the router * * @return none */ void ospf_router_info_update_sr(bool enable, struct sr_srgb srgb, uint8_t msd) { struct listnode *node, *nnode; struct ospf_ri_area_info *ai; /* First, check if Router Information is registered or not */ if (!OspfRI.registered) ospf_router_info_register(OSPF_OPAQUE_AREA_LSA); /* Verify that scope is AREA */ if (OspfRI.scope != OSPF_OPAQUE_AREA_LSA) { zlog_err( "RI (%s): Router Info is %s flooding: Change scope to Area flooding for Segment Routing", __func__, OspfRI.scope == OSPF_OPAQUE_AREA_LSA ? "Area" : "AS"); return; } /* Then, activate and initialize Router Information if necessary */ if (!OspfRI.enabled) { OspfRI.enabled = true; initialize_params(&OspfRI); } if (IS_DEBUG_OSPF_SR) zlog_debug("RI (%s): %s Routing Information for Segment Routing", __func__, enable ? "Enable" : "Disable"); /* Unset or Set SR parameters */ if (!enable) { unset_sr_algorithm(SR_ALGORITHM_SPF); unset_sr_sid_label_range(); unset_sr_node_msd(); OspfRI.sr_info.enabled = false; } else { // Only SR_ALGORITHM_SPF is supported set_sr_algorithm(SR_ALGORITHM_SPF); set_sr_sid_label_range(srgb); if (msd != 0) set_sr_node_msd(msd); else unset_sr_node_msd(); OspfRI.sr_info.enabled = true; } /* Refresh if already engaged or originate RI LSA */ for (ALL_LIST_ELEMENTS(OspfRI.area_info, node, nnode, ai)) { if (CHECK_FLAG(ai->flags, RIFLG_LSA_ENGAGED)) ospf_router_info_lsa_schedule(ai, REFRESH_THIS_LSA); else ospf_router_info_lsa_schedule(ai, REORIGINATE_THIS_LSA); } } /*------------------------------------------------------------------------* * Followings are callback functions against generic Opaque-LSAs handling. *------------------------------------------------------------------------*/ static void ospf_router_info_ism_change(struct ospf_interface *oi, int old_state) { struct ospf_ri_area_info *ai; /* Collect area information */ ai = lookup_by_area(oi->area); /* Check if area is not yet registered */ if (ai != NULL) return; /* Add this new area to the list */ ai = XCALLOC(MTYPE_OSPF_ROUTER_INFO, sizeof(struct ospf_ri_area_info)); ai->area = oi->area; ai->flags = RIFLG_LSA_INACTIVE; listnode_add(OspfRI.area_info, ai); return; } /*------------------------------------------------------------------------* * Followings are OSPF protocol processing functions for ROUTER INFORMATION *------------------------------------------------------------------------*/ static void build_tlv_header(struct stream *s, struct tlv_header *tlvh) { stream_put(s, tlvh, sizeof(struct tlv_header)); return; } static void build_tlv(struct stream *s, struct tlv_header *tlvh) { if (ntohs(tlvh->type) != 0) { build_tlv_header(s, tlvh); stream_put(s, TLV_DATA(tlvh), TLV_BODY_SIZE(tlvh)); } return; } static void ospf_router_info_lsa_body_set(struct stream *s) { struct listnode *node; struct ri_pce_subtlv_domain *domain; struct ri_pce_subtlv_neighbor *neighbor; /* Build Router Information TLV */ build_tlv(s, &OspfRI.router_cap.header); /* Build Segment Routing TLVs if enabled */ if (OspfRI.sr_info.enabled) { /* Build Algorithm TLV */ build_tlv(s, &TLV_HDR(OspfRI.sr_info.algo)); /* Build SRGB TLV */ build_tlv(s, &TLV_HDR(OspfRI.sr_info.range)); /* Build MSD TLV */ build_tlv(s, &TLV_HDR(OspfRI.sr_info.msd)); } /* Add RI PCE TLV if it is set */ if (OspfRI.pce_info.enabled) { /* Compute PCE Info header first */ set_pce_header(&OspfRI.pce_info); /* Build PCE TLV */ build_tlv_header(s, &OspfRI.pce_info.pce_header.header); /* Build PCE address sub-tlv */ build_tlv(s, &OspfRI.pce_info.pce_address.header); /* Build PCE path scope sub-tlv */ build_tlv(s, &OspfRI.pce_info.pce_scope.header); /* Build PCE domain sub-tlv */ for (ALL_LIST_ELEMENTS_RO(OspfRI.pce_info.pce_domain, node, domain)) build_tlv(s, &domain->header); /* Build PCE neighbor sub-tlv */ for (ALL_LIST_ELEMENTS_RO(OspfRI.pce_info.pce_neighbor, node, neighbor)) build_tlv(s, &neighbor->header); /* Build PCE cap flag sub-tlv */ build_tlv(s, &OspfRI.pce_info.pce_cap_flag.header); } return; } /* Create new opaque-LSA. */ static struct ospf_lsa *ospf_router_info_lsa_new(struct ospf_area *area) { struct ospf *top; struct stream *s; struct lsa_header *lsah; struct ospf_lsa *new = NULL; uint8_t options, lsa_type; struct in_addr lsa_id; uint32_t tmp; uint16_t length; /* Create a stream for LSA. */ s = stream_new(OSPF_MAX_LSA_SIZE); lsah = (struct lsa_header *)STREAM_DATA(s); options = OSPF_OPTION_E; /* Enable AS external as we flood RI with Opaque Type 11 */ options |= OSPF_OPTION_O; /* Don't forget this :-) */ lsa_type = OspfRI.scope; /* LSA ID == 0 for Router Information see RFC 4970 */ tmp = SET_OPAQUE_LSID(OPAQUE_TYPE_ROUTER_INFORMATION_LSA, 0); lsa_id.s_addr = htonl(tmp); if (IS_DEBUG_OSPF(lsa, LSA_GENERATE)) zlog_debug( "LSA[Type%d:%s]: Create an Opaque-LSA/ROUTER INFORMATION instance", lsa_type, inet_ntoa(lsa_id)); top = ospf_lookup_by_vrf_id(VRF_DEFAULT); /* Set opaque-LSA header fields. */ lsa_header_set(s, options, lsa_type, lsa_id, top->router_id); /* Set opaque-LSA body fields. */ ospf_router_info_lsa_body_set(s); /* Set length. */ length = stream_get_endp(s); lsah->length = htons(length); /* Now, create an OSPF LSA instance. */ new = ospf_lsa_new_and_data(length); new->area = area; if (new->area && new->area->ospf) new->vrf_id = new->area->ospf->vrf_id; else new->vrf_id = VRF_DEFAULT; SET_FLAG(new->flags, OSPF_LSA_SELF); memcpy(new->data, lsah, length); stream_free(s); return new; } static int ospf_router_info_lsa_originate_as(void *arg) { struct ospf_lsa *new; struct ospf *top; int rc = -1; vrf_id_t vrf_id = VRF_DEFAULT; /* Sanity Check */ if (OspfRI.scope == OSPF_OPAQUE_AREA_LSA) { flog_warn( EC_OSPF_LSA_INSTALL_FAILURE, "RI (%s): wrong flooding scope AREA instead of AS ?", __func__); return rc; } /* Create new Opaque-LSA/ROUTER INFORMATION instance. */ new = ospf_router_info_lsa_new(NULL); new->vrf_id = VRF_DEFAULT; top = (struct ospf *)arg; /* Check ospf info */ if (top == NULL) { zlog_debug("RI (%s): ospf instance not found for vrf id %u", __func__, vrf_id); ospf_lsa_unlock(&new); return rc; } /* Install this LSA into LSDB. */ if (ospf_lsa_install(top, NULL /*oi */, new) == NULL) { flog_warn( EC_OSPF_LSA_INSTALL_FAILURE, "RI (%s): ospf_lsa_install() ?", __func__); ospf_lsa_unlock(&new); return rc; } /* Update new LSA origination count. */ top->lsa_originate_count++; /* Flood new LSA through AREA or AS. */ SET_FLAG(OspfRI.as_flags, RIFLG_LSA_ENGAGED); ospf_flood_through_as(top, NULL /*nbr */, new); if (IS_DEBUG_OSPF(lsa, LSA_GENERATE)) { zlog_debug( "LSA[Type%d:%s]: Originate Opaque-LSA/ROUTER INFORMATION", new->data->type, inet_ntoa(new->data->id)); ospf_lsa_header_dump(new->data); } rc = 0; return rc; } static int ospf_router_info_lsa_originate_area(void *arg) { struct ospf_lsa *new; struct ospf *top; struct ospf_ri_area_info *ai = NULL; int rc = -1; vrf_id_t vrf_id = VRF_DEFAULT; /* Sanity Check */ if (OspfRI.scope == OSPF_OPAQUE_AS_LSA) { flog_warn( EC_OSPF_LSA_INSTALL_FAILURE, "RI (%s): wrong flooding scope AS instead of AREA ?", __func__); return rc; } /* Create new Opaque-LSA/ROUTER INFORMATION instance. */ ai = lookup_by_area((struct ospf_area *)arg); if (ai == NULL) { zlog_debug( "RI (%s): There is no context for this Router Information. Stop processing", __func__); return rc; } if (ai->area->ospf) { vrf_id = ai->area->ospf->vrf_id; top = ai->area->ospf; } else { top = ospf_lookup_by_vrf_id(vrf_id); } new = ospf_router_info_lsa_new(ai->area); new->vrf_id = vrf_id; /* Check ospf info */ if (top == NULL) { zlog_debug("RI (%s): ospf instance not found for vrf id %u", __func__, vrf_id); ospf_lsa_unlock(&new); return rc; } /* Install this LSA into LSDB. */ if (ospf_lsa_install(top, NULL /*oi */, new) == NULL) { flog_warn( EC_OSPF_LSA_INSTALL_FAILURE, "RI (%s): ospf_lsa_install() ?", __func__); ospf_lsa_unlock(&new); return rc; } /* Update new LSA origination count. */ top->lsa_originate_count++; /* Flood new LSA through AREA or AS. */ SET_FLAG(ai->flags, RIFLG_LSA_ENGAGED); ospf_flood_through_area(ai->area, NULL /*nbr */, new); if (IS_DEBUG_OSPF(lsa, LSA_GENERATE)) { zlog_debug( "LSA[Type%d:%s]: Originate Opaque-LSA/ROUTER INFORMATION", new->data->type, inet_ntoa(new->data->id)); ospf_lsa_header_dump(new->data); } rc = 0; return rc; } static int ospf_router_info_lsa_originate(void *arg) { struct ospf_ri_area_info *ai; int rc = -1; if (!OspfRI.enabled) { zlog_info("RI (%s): ROUTER INFORMATION is disabled now.", __func__); rc = 0; /* This is not an error case. */ return rc; } /* Check if Router Information LSA is already engaged */ if (OspfRI.scope == OSPF_OPAQUE_AS_LSA) { if ((CHECK_FLAG(OspfRI.as_flags, RIFLG_LSA_ENGAGED)) && (CHECK_FLAG(OspfRI.as_flags, RIFLG_LSA_FORCED_REFRESH))) { UNSET_FLAG(OspfRI.as_flags, RIFLG_LSA_FORCED_REFRESH); ospf_router_info_lsa_schedule(NULL, REFRESH_THIS_LSA); rc = 0; return rc; } } else { ai = lookup_by_area((struct ospf_area *)arg); if (ai == NULL) { flog_warn( EC_OSPF_LSA, "RI (%s): Missing area information", __func__); return rc; } if ((CHECK_FLAG(ai->flags, RIFLG_LSA_ENGAGED)) && (CHECK_FLAG(ai->flags, RIFLG_LSA_FORCED_REFRESH))) { UNSET_FLAG(ai->flags, RIFLG_LSA_FORCED_REFRESH); ospf_router_info_lsa_schedule(ai, REFRESH_THIS_LSA); rc = 0; return rc; } } /* Router Information is not yet Engaged, check parameters */ if (!is_mandated_params_set(OspfRI)) flog_warn( EC_OSPF_LSA, "RI (%s): lacks mandated ROUTER INFORMATION parameters", __func__); /* Ok, let's try to originate an LSA */ if (OspfRI.scope == OSPF_OPAQUE_AS_LSA) rc = ospf_router_info_lsa_originate_as(arg); else rc = ospf_router_info_lsa_originate_area(arg); return rc; } static struct ospf_lsa *ospf_router_info_lsa_refresh(struct ospf_lsa *lsa) { struct ospf_ri_area_info *ai = NULL; struct ospf_lsa *new = NULL; struct ospf *top; if (!OspfRI.enabled) { /* * This LSA must have flushed before due to ROUTER INFORMATION * status change. * It seems a slip among routers in the routing domain. */ zlog_info("RI (%s): ROUTER INFORMATION is disabled now.", __func__); lsa->data->ls_age = htons(OSPF_LSA_MAXAGE); /* Flush it anyway. */ } /* Verify that the Router Information ID is supported */ if (GET_OPAQUE_ID(ntohl(lsa->data->id.s_addr)) != 0) { flog_warn( EC_OSPF_LSA, "RI (%s): Unsupported Router Information ID", __func__); return NULL; } /* Process LSA depending of the flooding scope */ if (OspfRI.scope == OSPF_OPAQUE_AREA_LSA) { /* Get context AREA context */ ai = lookup_by_area(lsa->area); if (ai == NULL) { flog_warn( EC_OSPF_LSA, "RI (%s): No associated Area", __func__); return NULL; } /* Flush LSA, if the lsa's age reached to MaxAge. */ if (IS_LSA_MAXAGE(lsa)) { UNSET_FLAG(ai->flags, RIFLG_LSA_ENGAGED); ospf_opaque_lsa_flush_schedule(lsa); return NULL; } /* Create new Opaque-LSA/ROUTER INFORMATION instance. */ new = ospf_router_info_lsa_new(ai->area); new->data->ls_seqnum = lsa_seqnum_increment(lsa); new->vrf_id = lsa->vrf_id; /* Install this LSA into LSDB. */ /* Given "lsa" will be freed in the next function. */ top = ospf_lookup_by_vrf_id(lsa->vrf_id); if (ospf_lsa_install(top, NULL /*oi */, new) == NULL) { flog_warn(EC_OSPF_LSA_INSTALL_FAILURE, "RI (%s): ospf_lsa_install() ?", __func__); ospf_lsa_unlock(&new); return new; } /* Flood updated LSA through AREA */ ospf_flood_through_area(ai->area, NULL /*nbr */, new); } else { /* AS Flooding scope */ /* Flush LSA, if the lsa's age reached to MaxAge. */ if (IS_LSA_MAXAGE(lsa)) { UNSET_FLAG(OspfRI.as_flags, RIFLG_LSA_ENGAGED); ospf_opaque_lsa_flush_schedule(lsa); return NULL; } /* Create new Opaque-LSA/ROUTER INFORMATION instance. */ new = ospf_router_info_lsa_new(NULL); new->data->ls_seqnum = lsa_seqnum_increment(lsa); new->vrf_id = lsa->vrf_id; /* Install this LSA into LSDB. */ /* Given "lsa" will be freed in the next function. */ top = ospf_lookup_by_vrf_id(lsa->vrf_id); if (ospf_lsa_install(top, NULL /*oi */, new) == NULL) { flog_warn(EC_OSPF_LSA_INSTALL_FAILURE, "RI (%s): ospf_lsa_install() ?", __func__); ospf_lsa_unlock(&new); return new; } /* Flood updated LSA through AS */ ospf_flood_through_as(top, NULL /*nbr */, new); } /* Debug logging. */ if (IS_DEBUG_OSPF(lsa, LSA_GENERATE)) { zlog_debug( "LSA[Type%d:%s]: Refresh Opaque-LSA/ROUTER INFORMATION", new->data->type, inet_ntoa(new->data->id)); ospf_lsa_header_dump(new->data); } return new; } static void ospf_router_info_lsa_schedule(struct ospf_ri_area_info *ai, enum lsa_opcode opcode) { struct ospf_lsa lsa; struct lsa_header lsah; struct ospf *top; uint32_t tmp; memset(&lsa, 0, sizeof(lsa)); memset(&lsah, 0, sizeof(lsah)); zlog_debug("RI (%s): LSA schedule %s%s%s", __func__, opcode == REORIGINATE_THIS_LSA ? "Re-Originate" : "", opcode == REFRESH_THIS_LSA ? "Refresh" : "", opcode == FLUSH_THIS_LSA ? "Flush" : ""); /* Check LSA flags state coherence and collect area information */ if (OspfRI.scope == OSPF_OPAQUE_AREA_LSA) { if ((ai == NULL) || (ai->area == NULL)) { flog_warn( EC_OSPF_LSA, "RI (%s): Router Info is Area scope flooding but area is not set", __func__); return; } if (!CHECK_FLAG(ai->flags, RIFLG_LSA_ENGAGED) && (opcode != REORIGINATE_THIS_LSA)) return; if (CHECK_FLAG(ai->flags, RIFLG_LSA_ENGAGED) && (opcode == REORIGINATE_THIS_LSA)) opcode = REFRESH_THIS_LSA; lsa.area = ai->area; top = ai->area->ospf; } else { if (!CHECK_FLAG(OspfRI.as_flags, RIFLG_LSA_ENGAGED) && (opcode != REORIGINATE_THIS_LSA)) return; if (CHECK_FLAG(OspfRI.as_flags, RIFLG_LSA_ENGAGED) && (opcode == REORIGINATE_THIS_LSA)) opcode = REFRESH_THIS_LSA; top = ospf_lookup_by_vrf_id(VRF_DEFAULT); lsa.area = NULL; } lsa.data = &lsah; lsah.type = OspfRI.scope; /* LSA ID is set to 0 for the Router Information. See RFC 4970 */ tmp = SET_OPAQUE_LSID(OPAQUE_TYPE_ROUTER_INFORMATION_LSA, 0); lsah.id.s_addr = htonl(tmp); switch (opcode) { case REORIGINATE_THIS_LSA: if (OspfRI.scope == OSPF_OPAQUE_AREA_LSA) ospf_opaque_lsa_reoriginate_schedule( (void *)ai->area, OSPF_OPAQUE_AREA_LSA, OPAQUE_TYPE_ROUTER_INFORMATION_LSA); else ospf_opaque_lsa_reoriginate_schedule( (void *)top, OSPF_OPAQUE_AS_LSA, OPAQUE_TYPE_ROUTER_INFORMATION_LSA); break; case REFRESH_THIS_LSA: ospf_opaque_lsa_refresh_schedule(&lsa); break; case FLUSH_THIS_LSA: if (OspfRI.scope == OSPF_OPAQUE_AREA_LSA) UNSET_FLAG(ai->flags, RIFLG_LSA_ENGAGED); else UNSET_FLAG(OspfRI.as_flags, RIFLG_LSA_ENGAGED); ospf_opaque_lsa_flush_schedule(&lsa); break; } return; } /* Callback to handle Segment Routing information */ static int ospf_router_info_lsa_update(struct ospf_lsa *lsa) { /* Sanity Check */ if (lsa == NULL) { flog_warn(EC_OSPF_LSA, "RI (%s): Abort! LSA is NULL", __func__); return -1; } /* Process only Opaque LSA */ if ((lsa->data->type != OSPF_OPAQUE_AREA_LSA) && (lsa->data->type != OSPF_OPAQUE_AS_LSA)) return 0; /* Process only Router Information LSA */ if (GET_OPAQUE_TYPE(ntohl(lsa->data->id.s_addr)) != OPAQUE_TYPE_ROUTER_INFORMATION_LSA) return 0; /* Check if it is not my LSA */ if (IS_LSA_SELF(lsa)) return 0; /* Check if Router Info & Segment Routing are enable */ if (!OspfRI.enabled || !OspfRI.sr_info.enabled) return 0; /* Call Segment Routing LSA update or deletion */ if (!IS_LSA_MAXAGE(lsa)) ospf_sr_ri_lsa_update(lsa); else ospf_sr_ri_lsa_delete(lsa); return 0; } /*------------------------------------------------------------------------* * Followings are vty session control functions. *------------------------------------------------------------------------*/ static uint16_t show_vty_router_cap(struct vty *vty, struct tlv_header *tlvh) { struct ri_tlv_router_cap *top = (struct ri_tlv_router_cap *)tlvh; if (vty != NULL) vty_out(vty, " Router Capabilities: 0x%x\n", ntohl(top->value)); else zlog_debug(" Router Capabilities: 0x%x", ntohl(top->value)); return TLV_SIZE(tlvh); } static uint16_t show_vty_pce_subtlv_address(struct vty *vty, struct tlv_header *tlvh) { struct ri_pce_subtlv_address *top = (struct ri_pce_subtlv_address *)tlvh; if (ntohs(top->address.type) == PCE_ADDRESS_TYPE_IPV4) { if (vty != NULL) vty_out(vty, " PCE Address: %s\n", inet_ntoa(top->address.value)); else zlog_debug(" PCE Address: %s", inet_ntoa(top->address.value)); } else { /* TODO: Add support to IPv6 with inet_ntop() */ if (vty != NULL) vty_out(vty, " PCE Address: 0x%x\n", ntohl(top->address.value.s_addr)); else zlog_debug(" PCE Address: 0x%x", ntohl(top->address.value.s_addr)); } return TLV_SIZE(tlvh); } static uint16_t show_vty_pce_subtlv_path_scope(struct vty *vty, struct tlv_header *tlvh) { struct ri_pce_subtlv_path_scope *top = (struct ri_pce_subtlv_path_scope *)tlvh; if (vty != NULL) vty_out(vty, " PCE Path Scope: 0x%x\n", ntohl(top->value)); else zlog_debug(" PCE Path Scope: 0x%x", ntohl(top->value)); return TLV_SIZE(tlvh); } static uint16_t show_vty_pce_subtlv_domain(struct vty *vty, struct tlv_header *tlvh) { struct ri_pce_subtlv_domain *top = (struct ri_pce_subtlv_domain *)tlvh; struct in_addr tmp; if (ntohs(top->type) == PCE_DOMAIN_TYPE_AREA) { tmp.s_addr = top->value; if (vty != NULL) vty_out(vty, " PCE domain Area: %s\n", inet_ntoa(tmp)); else zlog_debug(" PCE domain Area: %s", inet_ntoa(tmp)); } else { if (vty != NULL) vty_out(vty, " PCE domain AS: %d\n", ntohl(top->value)); else zlog_debug(" PCE domain AS: %d", ntohl(top->value)); } return TLV_SIZE(tlvh); } static uint16_t show_vty_pce_subtlv_neighbor(struct vty *vty, struct tlv_header *tlvh) { struct ri_pce_subtlv_neighbor *top = (struct ri_pce_subtlv_neighbor *)tlvh; struct in_addr tmp; if (ntohs(top->type) == PCE_DOMAIN_TYPE_AREA) { tmp.s_addr = top->value; if (vty != NULL) vty_out(vty, " PCE neighbor Area: %s\n", inet_ntoa(tmp)); else zlog_debug(" PCE neighbor Area: %s", inet_ntoa(tmp)); } else { if (vty != NULL) vty_out(vty, " PCE neighbor AS: %d\n", ntohl(top->value)); else zlog_debug(" PCE neighbor AS: %d", ntohl(top->value)); } return TLV_SIZE(tlvh); } static uint16_t show_vty_pce_subtlv_cap_flag(struct vty *vty, struct tlv_header *tlvh) { struct ri_pce_subtlv_cap_flag *top = (struct ri_pce_subtlv_cap_flag *)tlvh; if (vty != NULL) vty_out(vty, " PCE Capabilities Flag: 0x%x\n", ntohl(top->value)); else zlog_debug(" PCE Capabilities Flag: 0x%x", ntohl(top->value)); return TLV_SIZE(tlvh); } static uint16_t show_vty_unknown_tlv(struct vty *vty, struct tlv_header *tlvh) { if (vty != NULL) vty_out(vty, " Unknown TLV: [type(0x%x), length(0x%x)]\n", ntohs(tlvh->type), ntohs(tlvh->length)); else zlog_debug(" Unknown TLV: [type(0x%x), length(0x%x)]", ntohs(tlvh->type), ntohs(tlvh->length)); return TLV_SIZE(tlvh); } static uint16_t show_vty_pce_info(struct vty *vty, struct tlv_header *ri, uint32_t total) { struct tlv_header *tlvh; uint16_t sum = 0; for (tlvh = ri; sum < total; tlvh = TLV_HDR_NEXT(tlvh)) { switch (ntohs(tlvh->type)) { case RI_PCE_SUBTLV_ADDRESS: sum += show_vty_pce_subtlv_address(vty, tlvh); break; case RI_PCE_SUBTLV_PATH_SCOPE: sum += show_vty_pce_subtlv_path_scope(vty, tlvh); break; case RI_PCE_SUBTLV_DOMAIN: sum += show_vty_pce_subtlv_domain(vty, tlvh); break; case RI_PCE_SUBTLV_NEIGHBOR: sum += show_vty_pce_subtlv_neighbor(vty, tlvh); break; case RI_PCE_SUBTLV_CAP_FLAG: sum += show_vty_pce_subtlv_cap_flag(vty, tlvh); break; default: sum += show_vty_unknown_tlv(vty, tlvh); break; } } return sum; } /* Display Segment Routing Algorithm TLV information */ static uint16_t show_vty_sr_algorithm(struct vty *vty, struct tlv_header *tlvh) { struct ri_sr_tlv_sr_algorithm *algo = (struct ri_sr_tlv_sr_algorithm *)tlvh; int i; if (vty != NULL) { vty_out(vty, " Segment Routing Algorithm TLV:\n"); for (i = 0; i < ntohs(algo->header.length); i++) { switch (algo->value[i]) { case 0: vty_out(vty, " Algorithm %d: SPF\n", i); break; case 1: vty_out(vty, " Algorithm %d: Strict SPF\n", i); break; default: vty_out(vty, " Algorithm %d: Unknown value %d\n", i, algo->value[i]); break; } } } else { zlog_debug(" Segment Routing Algorithm TLV:"); for (i = 0; i < ntohs(algo->header.length); i++) switch (algo->value[i]) { case 0: zlog_debug(" Algorithm %d: SPF", i); break; case 1: zlog_debug(" Algorithm %d: Strict SPF", i); break; default: zlog_debug( " Algorithm %d: Unknown value %d\n", i, algo->value[i]); break; } } return TLV_SIZE(tlvh); } /* Display Segment Routing SID/Label Range TLV information */ static uint16_t show_vty_sr_range(struct vty *vty, struct tlv_header *tlvh) { struct ri_sr_tlv_sid_label_range *range = (struct ri_sr_tlv_sid_label_range *)tlvh; if (vty != NULL) { vty_out(vty, " Segment Routing Range TLV:\n" " Range Size = %d\n" " SID Label = %d\n\n", GET_RANGE_SIZE(ntohl(range->size)), GET_LABEL(ntohl(range->lower.value))); } else { zlog_debug( " Segment Routing Range TLV:\n" " Range Size = %d\n" " SID Label = %d\n\n", GET_RANGE_SIZE(ntohl(range->size)), GET_LABEL(ntohl(range->lower.value))); } return TLV_SIZE(tlvh); } /* Display Segment Routing Maximum Stack Depth TLV information */ static uint16_t show_vty_sr_msd(struct vty *vty, struct tlv_header *tlvh) { struct ri_sr_tlv_node_msd *msd = (struct ri_sr_tlv_node_msd *)tlvh; if (vty != NULL) { vty_out(vty, " Segment Routing MSD TLV:\n" " Node Maximum Stack Depth = %d\n", msd->value); } else { zlog_debug( " Segment Routing MSD TLV:\n" " Node Maximum Stack Depth = %d\n", msd->value); } return TLV_SIZE(tlvh); } static void ospf_router_info_show_info(struct vty *vty, struct ospf_lsa *lsa) { struct lsa_header *lsah = (struct lsa_header *)lsa->data; struct tlv_header *tlvh; uint16_t length = 0, sum = 0; /* Initialize TLV browsing */ length = ntohs(lsah->length) - OSPF_LSA_HEADER_SIZE; for (tlvh = TLV_HDR_TOP(lsah); sum < length; tlvh = TLV_HDR_NEXT(tlvh)) { switch (ntohs(tlvh->type)) { case RI_TLV_CAPABILITIES: sum += show_vty_router_cap(vty, tlvh); break; case RI_TLV_PCE: tlvh++; sum += TLV_HDR_SIZE; sum += show_vty_pce_info(vty, tlvh, length - sum); break; case RI_SR_TLV_SR_ALGORITHM: sum += show_vty_sr_algorithm(vty, tlvh); break; case RI_SR_TLV_SID_LABEL_RANGE: sum += show_vty_sr_range(vty, tlvh); break; case RI_SR_TLV_NODE_MSD: sum += show_vty_sr_msd(vty, tlvh); break; default: sum += show_vty_unknown_tlv(vty, tlvh); break; } } return; } static void ospf_router_info_config_write_router(struct vty *vty) { struct ospf_pce_info *pce = &OspfRI.pce_info; struct listnode *node; struct ri_pce_subtlv_domain *domain; struct ri_pce_subtlv_neighbor *neighbor; struct in_addr tmp; if (!OspfRI.enabled) return; if (OspfRI.scope == OSPF_OPAQUE_AS_LSA) vty_out(vty, " router-info as\n"); else vty_out(vty, " router-info area\n"); if (OspfRI.pce_info.enabled) { if (pce->pce_address.header.type != 0) vty_out(vty, " pce address %s\n", inet_ntoa(pce->pce_address.address.value)); if (pce->pce_cap_flag.header.type != 0) vty_out(vty, " pce flag 0x%x\n", ntohl(pce->pce_cap_flag.value)); for (ALL_LIST_ELEMENTS_RO(pce->pce_domain, node, domain)) { if (domain->header.type != 0) { if (domain->type == PCE_DOMAIN_TYPE_AREA) { tmp.s_addr = domain->value; vty_out(vty, " pce domain area %s\n", inet_ntoa(tmp)); } else { vty_out(vty, " pce domain as %d\n", ntohl(domain->value)); } } } for (ALL_LIST_ELEMENTS_RO(pce->pce_neighbor, node, neighbor)) { if (neighbor->header.type != 0) { if (neighbor->type == PCE_DOMAIN_TYPE_AREA) { tmp.s_addr = neighbor->value; vty_out(vty, " pce neighbor area %s\n", inet_ntoa(tmp)); } else { vty_out(vty, " pce neighbor as %d\n", ntohl(neighbor->value)); } } } if (pce->pce_scope.header.type != 0) vty_out(vty, " pce scope 0x%x\n", ntohl(OspfRI.pce_info.pce_scope.value)); } return; } /*------------------------------------------------------------------------* * Followings are vty command functions. *------------------------------------------------------------------------*/ /* Simple wrapper schedule RI LSA action in function of the scope */ static void ospf_router_info_schedule(enum lsa_opcode opcode) { struct listnode *node, *nnode; struct ospf_ri_area_info *ai; if (OspfRI.scope == OSPF_OPAQUE_AS_LSA) { if (CHECK_FLAG(OspfRI.as_flags, RIFLG_LSA_ENGAGED)) ospf_router_info_lsa_schedule(NULL, opcode); else if (opcode == REORIGINATE_THIS_LSA) ospf_router_info_lsa_schedule(NULL, opcode); } else { for (ALL_LIST_ELEMENTS(OspfRI.area_info, node, nnode, ai)) { if (CHECK_FLAG(ai->flags, RIFLG_LSA_ENGAGED)) ospf_router_info_lsa_schedule(ai, opcode); } } } DEFUN (router_info, router_info_area_cmd, "router-info ", OSPF_RI_STR "Enable the Router Information functionality with AS flooding scope\n" "Enable the Router Information functionality with Area flooding scope\n" "OSPF area ID in IP format (deprecated)\n") { int idx_mode = 1; uint8_t scope; if (OspfRI.enabled) return CMD_SUCCESS; /* Check and get Area value if present */ if (strncmp(argv[idx_mode]->arg, "as", 2) == 0) scope = OSPF_OPAQUE_AS_LSA; else scope = OSPF_OPAQUE_AREA_LSA; /* First start to register Router Information callbacks */ if (!OspfRI.registered && (ospf_router_info_register(scope)) != 0) { vty_out(vty, "%% Unable to register Router Information callbacks."); flog_err( EC_OSPF_INIT_FAIL, "RI (%s): Unable to register Router Information callbacks. Abort!", __func__); return CMD_WARNING_CONFIG_FAILED; } OspfRI.enabled = true; if (IS_DEBUG_OSPF_EVENT) zlog_debug("RI-> Router Information (%s flooding): OFF -> ON", OspfRI.scope == OSPF_OPAQUE_AREA_LSA ? "Area" : "AS"); /* * Following code is intended to handle two cases; * * 1) Router Information was disabled at startup time, but now become * enabled. * 2) Router Information was once enabled then disabled, and now enabled * again. */ initialize_params(&OspfRI); /* Originate or Refresh RI LSA if already engaged */ ospf_router_info_schedule(REORIGINATE_THIS_LSA); return CMD_SUCCESS; } DEFUN (no_router_info, no_router_info_cmd, "no router-info", NO_STR "Disable the Router Information functionality\n") { if (!OspfRI.enabled) return CMD_SUCCESS; if (IS_DEBUG_OSPF_EVENT) zlog_debug("RI-> Router Information: ON -> OFF"); ospf_router_info_schedule(FLUSH_THIS_LSA); OspfRI.enabled = false; return CMD_SUCCESS; } static int ospf_ri_enabled(struct vty *vty) { if (OspfRI.enabled) return 1; if (vty) vty_out(vty, "%% OSPF RI is not turned on\n"); return 0; } DEFUN (pce_address, pce_address_cmd, "pce address A.B.C.D", PCE_STR "Stable IP address of the PCE\n" "PCE address in IPv4 address format\n") { int idx_ipv4 = 2; struct in_addr value; struct ospf_pce_info *pi = &OspfRI.pce_info; if (!ospf_ri_enabled(vty)) return CMD_WARNING_CONFIG_FAILED; if (!inet_aton(argv[idx_ipv4]->arg, &value)) { vty_out(vty, "Please specify PCE Address by A.B.C.D\n"); return CMD_WARNING_CONFIG_FAILED; } if (ntohs(pi->pce_address.header.type) == 0 || ntohl(pi->pce_address.address.value.s_addr) != ntohl(value.s_addr)) { set_pce_address(value, pi); /* Refresh RI LSA if already engaged */ ospf_router_info_schedule(REFRESH_THIS_LSA); } return CMD_SUCCESS; } DEFUN (no_pce_address, no_pce_address_cmd, "no pce address [A.B.C.D]", NO_STR PCE_STR "Disable PCE address\n" "PCE address in IPv4 address format\n") { unset_param(&OspfRI.pce_info.pce_address); /* Refresh RI LSA if already engaged */ ospf_router_info_schedule(REFRESH_THIS_LSA); return CMD_SUCCESS; } DEFUN (pce_path_scope, pce_path_scope_cmd, "pce scope BITPATTERN", PCE_STR "Path scope visibilities of the PCE for path computation\n" "32-bit Hexadecimal value\n") { int idx_bitpattern = 2; uint32_t scope; struct ospf_pce_info *pi = &OspfRI.pce_info; if (!ospf_ri_enabled(vty)) return CMD_WARNING_CONFIG_FAILED; if (sscanf(argv[idx_bitpattern]->arg, "0x%x", &scope) != 1) { vty_out(vty, "pce_path_scope: fscanf: %s\n", safe_strerror(errno)); return CMD_WARNING_CONFIG_FAILED; } if (ntohl(pi->pce_scope.header.type) == 0 || scope != pi->pce_scope.value) { set_pce_path_scope(scope, pi); /* Refresh RI LSA if already engaged */ ospf_router_info_schedule(REFRESH_THIS_LSA); } return CMD_SUCCESS; } DEFUN (no_pce_path_scope, no_pce_path_scope_cmd, "no pce scope [BITPATTERN]", NO_STR PCE_STR "Disable PCE path scope\n" "32-bit Hexadecimal value\n") { unset_param(&OspfRI.pce_info.pce_address); /* Refresh RI LSA if already engaged */ ospf_router_info_schedule(REFRESH_THIS_LSA); return CMD_SUCCESS; } DEFUN (pce_domain, pce_domain_cmd, "pce domain as (0-65535)", PCE_STR "Configure PCE domain AS number\n" "AS number where the PCE as visibilities for path computation\n" "AS number in decimal <0-65535>\n") { int idx_number = 3; uint32_t as; struct ospf_pce_info *pce = &OspfRI.pce_info; struct listnode *node; struct ri_pce_subtlv_domain *domain; if (!ospf_ri_enabled(vty)) return CMD_WARNING_CONFIG_FAILED; if (sscanf(argv[idx_number]->arg, "%" SCNu32, &as) != 1) { vty_out(vty, "pce_domain: fscanf: %s\n", safe_strerror(errno)); return CMD_WARNING_CONFIG_FAILED; } /* Check if the domain is not already in the domain list */ for (ALL_LIST_ELEMENTS_RO(pce->pce_domain, node, domain)) { if (ntohl(domain->header.type) == 0 && as == domain->value) return CMD_SUCCESS; } /* Create new domain if not found */ set_pce_domain(PCE_DOMAIN_TYPE_AS, as, pce); /* Refresh RI LSA if already engaged */ ospf_router_info_schedule(REFRESH_THIS_LSA); return CMD_SUCCESS; } DEFUN (no_pce_domain, no_pce_domain_cmd, "no pce domain as (0-65535)", NO_STR PCE_STR "Disable PCE domain AS number\n" "AS number where the PCE as visibilities for path computation\n" "AS number in decimal <0-65535>\n") { int idx_number = 4; uint32_t as; struct ospf_pce_info *pce = &OspfRI.pce_info; if (sscanf(argv[idx_number]->arg, "%" SCNu32, &as) != 1) { vty_out(vty, "no_pce_domain: fscanf: %s\n", safe_strerror(errno)); return CMD_WARNING_CONFIG_FAILED; } /* Unset corresponding PCE domain */ unset_pce_domain(PCE_DOMAIN_TYPE_AS, as, pce); /* Refresh RI LSA if already engaged */ ospf_router_info_schedule(REFRESH_THIS_LSA); return CMD_SUCCESS; } DEFUN (pce_neigbhor, pce_neighbor_cmd, "pce neighbor as (0-65535)", PCE_STR "Configure PCE neighbor domain AS number\n" "AS number of PCE neighbors\n" "AS number in decimal <0-65535>\n") { int idx_number = 3; uint32_t as; struct ospf_pce_info *pce = &OspfRI.pce_info; struct listnode *node; struct ri_pce_subtlv_neighbor *neighbor; if (!ospf_ri_enabled(vty)) return CMD_WARNING_CONFIG_FAILED; if (sscanf(argv[idx_number]->arg, "%" SCNu32, &as) != 1) { vty_out(vty, "pce_neighbor: fscanf: %s\n", safe_strerror(errno)); return CMD_WARNING_CONFIG_FAILED; } /* Check if the domain is not already in the domain list */ for (ALL_LIST_ELEMENTS_RO(pce->pce_neighbor, node, neighbor)) { if (ntohl(neighbor->header.type) == 0 && as == neighbor->value) return CMD_SUCCESS; } /* Create new domain if not found */ set_pce_neighbor(PCE_DOMAIN_TYPE_AS, as, pce); /* Refresh RI LSA if already engaged */ ospf_router_info_schedule(REFRESH_THIS_LSA); return CMD_SUCCESS; } DEFUN (no_pce_neighbor, no_pce_neighbor_cmd, "no pce neighbor as (0-65535)", NO_STR PCE_STR "Disable PCE neighbor AS number\n" "AS number of PCE neighbor\n" "AS number in decimal <0-65535>\n") { int idx_number = 4; uint32_t as; struct ospf_pce_info *pce = &OspfRI.pce_info; if (sscanf(argv[idx_number]->arg, "%" SCNu32, &as) != 1) { vty_out(vty, "no_pce_neighbor: fscanf: %s\n", safe_strerror(errno)); return CMD_WARNING_CONFIG_FAILED; } /* Unset corresponding PCE domain */ unset_pce_neighbor(PCE_DOMAIN_TYPE_AS, as, pce); /* Refresh RI LSA if already engaged */ ospf_router_info_schedule(REFRESH_THIS_LSA); return CMD_SUCCESS; } DEFUN (pce_cap_flag, pce_cap_flag_cmd, "pce flag BITPATTERN", PCE_STR "Capabilities of the PCE for path computation\n" "32-bit Hexadecimal value\n") { int idx_bitpattern = 2; uint32_t cap; struct ospf_pce_info *pce = &OspfRI.pce_info; if (!ospf_ri_enabled(vty)) return CMD_WARNING_CONFIG_FAILED; if (sscanf(argv[idx_bitpattern]->arg, "0x%x", &cap) != 1) { vty_out(vty, "pce_cap_flag: fscanf: %s\n", safe_strerror(errno)); return CMD_WARNING_CONFIG_FAILED; } if (ntohl(pce->pce_cap_flag.header.type) == 0 || cap != pce->pce_cap_flag.value) { set_pce_cap_flag(cap, pce); /* Refresh RI LSA if already engaged */ ospf_router_info_schedule(REFRESH_THIS_LSA); } return CMD_SUCCESS; } DEFUN (no_pce_cap_flag, no_pce_cap_flag_cmd, "no pce flag", NO_STR PCE_STR "Disable PCE capabilities\n") { unset_param(&OspfRI.pce_info.pce_cap_flag); /* Refresh RI LSA if already engaged */ ospf_router_info_schedule(REFRESH_THIS_LSA); return CMD_SUCCESS; } DEFUN (show_ip_ospf_router_info, show_ip_ospf_router_info_cmd, "show ip ospf router-info", SHOW_STR IP_STR OSPF_STR "Router Information\n") { if (OspfRI.enabled) { vty_out(vty, "--- Router Information parameters ---\n"); show_vty_router_cap(vty, &OspfRI.router_cap.header); } else { if (vty != NULL) vty_out(vty, " Router Information is disabled on this router\n"); } return CMD_SUCCESS; } DEFUN (show_ip_opsf_router_info_pce, show_ip_ospf_router_info_pce_cmd, "show ip ospf router-info pce", SHOW_STR IP_STR OSPF_STR "Router Information\n" "PCE information\n") { struct ospf_pce_info *pce = &OspfRI.pce_info; struct listnode *node; struct ri_pce_subtlv_domain *domain; struct ri_pce_subtlv_neighbor *neighbor; if ((OspfRI.enabled) && (OspfRI.pce_info.enabled)) { vty_out(vty, "--- PCE parameters ---\n"); if (pce->pce_address.header.type != 0) show_vty_pce_subtlv_address(vty, &pce->pce_address.header); if (pce->pce_scope.header.type != 0) show_vty_pce_subtlv_path_scope(vty, &pce->pce_scope.header); for (ALL_LIST_ELEMENTS_RO(pce->pce_domain, node, domain)) { if (domain->header.type != 0) show_vty_pce_subtlv_domain(vty, &domain->header); } for (ALL_LIST_ELEMENTS_RO(pce->pce_neighbor, node, neighbor)) { if (neighbor->header.type != 0) show_vty_pce_subtlv_neighbor(vty, &neighbor->header); } if (pce->pce_cap_flag.header.type != 0) show_vty_pce_subtlv_cap_flag(vty, &pce->pce_cap_flag.header); } else { vty_out(vty, " PCE info is disabled on this router\n"); } return CMD_SUCCESS; } /* Install new CLI commands */ static void ospf_router_info_register_vty(void) { install_element(VIEW_NODE, &show_ip_ospf_router_info_cmd); install_element(VIEW_NODE, &show_ip_ospf_router_info_pce_cmd); install_element(OSPF_NODE, &router_info_area_cmd); install_element(OSPF_NODE, &no_router_info_cmd); install_element(OSPF_NODE, &pce_address_cmd); install_element(OSPF_NODE, &no_pce_address_cmd); install_element(OSPF_NODE, &pce_path_scope_cmd); install_element(OSPF_NODE, &no_pce_path_scope_cmd); install_element(OSPF_NODE, &pce_domain_cmd); install_element(OSPF_NODE, &no_pce_domain_cmd); install_element(OSPF_NODE, &pce_neighbor_cmd); install_element(OSPF_NODE, &no_pce_neighbor_cmd); install_element(OSPF_NODE, &pce_cap_flag_cmd); install_element(OSPF_NODE, &no_pce_cap_flag_cmd); return; } frr-7.2.1/ospfd/ospf_ri.h0000644000000000000000000001650613610377563012151 00000000000000/* * This is an implementation of RFC4970 Router Information * with support of RFC5088 PCE Capabilites announcement * and support of draft-ietf-ospf-segment-routing-extensions-18 * for Segment Routing Capabilities announcement * * * Module name: Router Information * Author: Olivier Dugeon * Copyright (C) 2012 - 2017 Orange Labs http://www.orange.com/ * * This file is part of GNU Zebra. * * GNU Zebra is free software; you can redistribute it and/or modify it * under the terms of the GNU General Public License as published by the * Free Software Foundation; either version 2, or (at your option) any * later version. * * GNU Zebra 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; see the file COPYING; if not, write to the Free Software * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA */ #ifndef _ZEBRA_OSPF_ROUTER_INFO_H #define _ZEBRA_OSPF_ROUTER_INFO_H /* * Opaque LSA's link state ID for Router Information is * structured as follows. * * 24 16 8 0 * +--------+--------+--------+--------+ * | 4 | MBZ |........|........| * +--------+--------+--------+--------+ * |<-Type->||<-- Instance --->| * * * Type: IANA has assigned '4' for Router Information. * MBZ: Reserved, must be set to zero. * Instance: User may select an arbitrary 16-bit value. * */ /* * 24 16 8 0 * +--------+--------+--------+--------+ --- * | LS age |Options | 9,10,11| A * +--------+--------+--------+--------+ | * | 4 | 0 | Instance | | * +--------+--------+--------+--------+ | * | Advertising router | | Standard (Opaque) LSA header; * +--------+--------+--------+--------+ | Type 9,10 or 11 are used. * | LS sequence number | | * +--------+--------+--------+--------+ | * | LS checksum | Length | V * +--------+--------+--------+--------+ --- * | Type | Length | A TLV part for Router Information; * +--------+--------+--------+--------+ | Values might be * | Values ... | V structured as a set of sub-TLVs. * +--------+--------+--------+--------+ --- */ /* * Following section defines TLV body parts. */ /* Up to now, 11 code points have been assigned to Router Information */ /* Only type 1 Router Capabilities and 6 PCE are supported with this code */ #define RI_IANA_MAX_TYPE 11 /* RFC4970: Router Information Capabilities TLV */ /* Mandatory */ #define RI_TLV_CAPABILITIES 1 struct ri_tlv_router_cap { struct tlv_header header; /* Value length is 4 bytes. */ uint32_t value; }; /* Capabilities bits are left align */ #define RI_GRACE_RESTART 0x80000000 #define RI_GRACE_HELPER 0x40000000 #define RI_STUB_SUPPORT 0x20000000 #define RI_TE_SUPPORT 0x10000000 #define RI_P2P_OVER_LAN 0x08000000 #define RI_TE_EXPERIMENTAL 0x04000000 #define RI_TLV_LENGTH 4 /* RFC5088: PCE Capabilities TLV */ /* Optional */ /* RI PCE TLV */ #define RI_TLV_PCE 6 struct ri_tlv_pce { struct tlv_header header; /* A set of PCE-sub-TLVs will follow. */ }; /* PCE Address Sub-TLV */ /* Mandatory */ #define RI_PCE_SUBTLV_ADDRESS 1 struct ri_pce_subtlv_address { /* Type = 1; Length is 8 (IPv4) or 20 (IPv6) bytes. */ struct tlv_header header; #define PCE_ADDRESS_LENGTH_IPV4 8 #define PCE_ADDRESS_LENGTH_IPV6 20 struct { uint16_t type; /* Address type: 1 = IPv4, 2 = IPv6 */ #define PCE_ADDRESS_TYPE_IPV4 1 #define PCE_ADDRESS_TYPE_IPV6 2 uint16_t reserved; struct in_addr value; /* PCE address */ } address; }; /* PCE Path-Scope Sub-TLV */ /* Mandatory */ #define RI_PCE_SUBTLV_PATH_SCOPE 2 struct ri_pce_subtlv_path_scope { struct tlv_header header; /* Type = 2; Length = 4 bytes. */ /* * L, R, Rd, S, Sd, Y, PrefL, PrefR, PrefS and PrefY bits: * see RFC5088 page 9 */ uint32_t value; }; /* PCE Domain Sub-TLV */ /* Optional */ #define RI_PCE_SUBTLV_DOMAIN 3 #define PCE_DOMAIN_TYPE_AREA 1 #define PCE_DOMAIN_TYPE_AS 2 struct ri_pce_subtlv_domain { struct tlv_header header; /* Type = 3; Length = 8 bytes. */ uint16_t type; /* Domain type: 1 = OSPF Area ID, 2 = AS Number */ uint16_t reserved; uint32_t value; }; /* PCE Neighbor Sub-TLV */ /* Mandatory if R or S bit is set */ #define RI_PCE_SUBTLV_NEIGHBOR 4 struct ri_pce_subtlv_neighbor { struct tlv_header header; /* Type = 4; Length = 8 bytes. */ uint16_t type; /* Domain type: 1 = OSPF Area ID, 2 = AS Number */ uint16_t reserved; uint32_t value; }; /* PCE Capabilities Flags Sub-TLV */ /* Optional */ #define RI_PCE_SUBTLV_CAP_FLAG 5 #define PCE_CAP_GMPLS_LINK 0x0001 #define PCE_CAP_BIDIRECTIONAL 0x0002 #define PCE_CAP_DIVERSE_PATH 0x0004 #define PCE_CAP_LOAD_BALANCE 0x0008 #define PCE_CAP_SYNCHRONIZED 0x0010 #define PCE_CAP_OBJECTIVES 0x0020 #define PCE_CAP_ADDITIVE 0x0040 #define PCE_CAP_PRIORIZATION 0x0080 #define PCE_CAP_MULTIPLE_REQ 0x0100 struct ri_pce_subtlv_cap_flag { struct tlv_header header; /* Type = 5; Length = n x 4 bytes. */ uint32_t value; }; /* Structure to share flooding scope info for Segment Routing */ struct scope_info { uint8_t scope; struct list *areas; }; /* Flags to manage the Router Information LSA. */ #define RIFLG_LSA_INACTIVE 0x0 #define RIFLG_LSA_ENGAGED 0x1 #define RIFLG_LSA_FORCED_REFRESH 0x2 /* Store Router Information PCE TLV and SubTLV in network byte order. */ struct ospf_pce_info { bool enabled; struct ri_tlv_pce pce_header; struct ri_pce_subtlv_address pce_address; struct ri_pce_subtlv_path_scope pce_scope; struct list *pce_domain; struct list *pce_neighbor; struct ri_pce_subtlv_cap_flag pce_cap_flag; }; /* * Store Router Information Segment Routing TLV and SubTLV * in network byte order */ struct ospf_ri_sr_info { bool enabled; /* Algorithms supported by the node */ struct ri_sr_tlv_sr_algorithm algo; /* * Segment Routing Global Block i.e. label range * Only one range supported in this code */ struct ri_sr_tlv_sid_label_range range; /* Maximum SID Depth supported by the node */ struct ri_sr_tlv_node_msd msd; }; /* Store area information to flood LSA per area */ struct ospf_ri_area_info { uint32_t flags; /* area pointer if flooding is Type 10 Null if flooding is AS scope */ struct ospf_area *area; }; /* Following structure are internal use only. */ struct ospf_router_info { bool enabled; uint8_t registered; uint8_t scope; /* LSA flags are only used when scope is AS flooding */ uint32_t as_flags; /* List of area info to flood RI LSA */ struct list *area_info; /* Store Router Information Capabilities LSA */ struct ri_tlv_router_cap router_cap; /* Store PCE capability LSA */ struct ospf_pce_info pce_info; /* Store SR capability LSA */ struct ospf_ri_sr_info sr_info; }; /* Prototypes. */ extern int ospf_router_info_init(void); extern void ospf_router_info_term(void); extern void ospf_router_info_finish(void); extern int ospf_router_info_enable(void); extern void ospf_router_info_update_sr(bool enable, struct sr_srgb srgb, uint8_t msd); extern struct scope_info ospf_router_info_get_flooding_scope(void); #endif /* _ZEBRA_OSPF_ROUTER_INFO_H */ frr-7.2.1/ospfd/ospf_route.c0000644000000000000000000006500713610377563012670 00000000000000/* * OSPF routing table. * Copyright (C) 1999, 2000 Toshiaki Takada * * This file is part of GNU Zebra. * * GNU Zebra is free software; you can redistribute it and/or modify it * under the terms of the GNU General Public License as published by the * Free Software Foundation; either version 2, or (at your option) any * later version. * * GNU Zebra 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; see the file COPYING; if not, write to the Free Software * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA */ #include #include "prefix.h" #include "table.h" #include "memory.h" #include "linklist.h" #include "log.h" #include "if.h" #include "command.h" #include "sockunion.h" #include "ospfd/ospfd.h" #include "ospfd/ospf_interface.h" #include "ospfd/ospf_asbr.h" #include "ospfd/ospf_lsa.h" #include "ospfd/ospf_route.h" #include "ospfd/ospf_spf.h" #include "ospfd/ospf_zebra.h" #include "ospfd/ospf_dump.h" struct ospf_route *ospf_route_new(void) { struct ospf_route *new; new = XCALLOC(MTYPE_OSPF_ROUTE, sizeof(struct ospf_route)); new->paths = list_new(); new->paths->del = (void (*)(void *))ospf_path_free; return new; } void ospf_route_free(struct ospf_route *or) { if (or->paths) list_delete(& or->paths); XFREE(MTYPE_OSPF_ROUTE, or); } struct ospf_path *ospf_path_new(void) { struct ospf_path *new; new = XCALLOC(MTYPE_OSPF_PATH, sizeof(struct ospf_path)); return new; } static struct ospf_path *ospf_path_dup(struct ospf_path *path) { struct ospf_path *new; new = ospf_path_new(); memcpy(new, path, sizeof(struct ospf_path)); return new; } void ospf_path_free(struct ospf_path *op) { XFREE(MTYPE_OSPF_PATH, op); } void ospf_route_delete(struct ospf *ospf, struct route_table *rt) { struct route_node *rn; struct ospf_route * or ; for (rn = route_top(rt); rn; rn = route_next(rn)) if ((or = rn->info) != NULL) { if (or->type == OSPF_DESTINATION_NETWORK) ospf_zebra_delete( ospf, (struct prefix_ipv4 *)&rn->p, or); else if (or->type == OSPF_DESTINATION_DISCARD) ospf_zebra_delete_discard( ospf, (struct prefix_ipv4 *)&rn->p); } } void ospf_route_table_free(struct route_table *rt) { struct route_node *rn; struct ospf_route * or ; for (rn = route_top(rt); rn; rn = route_next(rn)) if ((or = rn->info) != NULL) { ospf_route_free(or); rn->info = NULL; route_unlock_node(rn); } route_table_finish(rt); } /* If a prefix exists in the new routing table, then return 1, otherwise return 0. Since the ZEBRA-RIB does an implicit withdraw, it is not necessary to send a delete, an add later will act like an implicit delete. */ static int ospf_route_exist_new_table(struct route_table *rt, struct prefix_ipv4 *prefix) { struct route_node *rn; assert(rt); assert(prefix); rn = route_node_lookup(rt, (struct prefix *)prefix); if (!rn) { return 0; } route_unlock_node(rn); if (!rn->info) { return 0; } return 1; } /* If a prefix and a nexthop match any route in the routing table, then return 1, otherwise return 0. */ int ospf_route_match_same(struct route_table *rt, struct prefix_ipv4 *prefix, struct ospf_route *newor) { struct route_node *rn; struct ospf_route * or ; struct ospf_path *op; struct ospf_path *newop; struct listnode *n1; struct listnode *n2; if (!rt || !prefix) return 0; rn = route_node_lookup(rt, (struct prefix *)prefix); if (!rn || !rn->info) return 0; route_unlock_node(rn); or = rn->info; if (or->type == newor->type && or->cost == newor->cost) { if (or->type == OSPF_DESTINATION_NETWORK) { if (or->paths->count != newor->paths->count) return 0; /* Check each path. */ for (n1 = listhead(or->paths), n2 = listhead(newor->paths); n1 && n2; n1 = listnextnode_unchecked(n1), n2 = listnextnode_unchecked(n2)) { op = listgetdata(n1); newop = listgetdata(n2); if (!IPV4_ADDR_SAME(&op->nexthop, &newop->nexthop)) return 0; if (op->ifindex != newop->ifindex) return 0; } return 1; } else if (prefix_same(&rn->p, (struct prefix *)prefix)) return 1; } return 0; } /* delete routes generated from AS-External routes if there is a inter/intra * area route */ static void ospf_route_delete_same_ext(struct ospf *ospf, struct route_table *external_routes, struct route_table *routes) { struct route_node *rn, *ext_rn; if ((external_routes == NULL) || (routes == NULL)) return; /* Remove deleted routes */ for (rn = route_top(routes); rn; rn = route_next(rn)) { if (rn && rn->info) { struct prefix_ipv4 *p = (struct prefix_ipv4 *)(&rn->p); if ((ext_rn = route_node_lookup(external_routes, (struct prefix *)p))) { if (ext_rn->info) { ospf_zebra_delete(ospf, p, ext_rn->info); ospf_route_free(ext_rn->info); ext_rn->info = NULL; } route_unlock_node(ext_rn); } } } } /* rt: Old, cmprt: New */ static void ospf_route_delete_uniq(struct ospf *ospf, struct route_table *rt, struct route_table *cmprt) { struct route_node *rn; struct ospf_route * or ; for (rn = route_top(rt); rn; rn = route_next(rn)) if ((or = rn->info) != NULL) if (or->path_type == OSPF_PATH_INTRA_AREA || or->path_type == OSPF_PATH_INTER_AREA) { if (or->type == OSPF_DESTINATION_NETWORK) { if (!ospf_route_exist_new_table( cmprt, (struct prefix_ipv4 *)&rn ->p)) ospf_zebra_delete( ospf, (struct prefix_ipv4 *)&rn->p, or); } else if (or->type == OSPF_DESTINATION_DISCARD) if (!ospf_route_exist_new_table( cmprt, (struct prefix_ipv4 *)&rn ->p)) ospf_zebra_delete_discard( ospf, (struct prefix_ipv4 *)&rn->p); } } /* Install routes to table. */ void ospf_route_install(struct ospf *ospf, struct route_table *rt) { struct route_node *rn; struct ospf_route * or ; /* rt contains new routing table, new_table contains an old one. updating pointers */ if (ospf->old_table) ospf_route_table_free(ospf->old_table); ospf->old_table = ospf->new_table; ospf->new_table = rt; /* Delete old routes. */ if (ospf->old_table) ospf_route_delete_uniq(ospf, ospf->old_table, rt); if (ospf->old_external_route) ospf_route_delete_same_ext(ospf, ospf->old_external_route, rt); /* Install new routes. */ for (rn = route_top(rt); rn; rn = route_next(rn)) if ((or = rn->info) != NULL) { if (or->type == OSPF_DESTINATION_NETWORK) { if (!ospf_route_match_same( ospf->old_table, (struct prefix_ipv4 *)&rn->p, or)) ospf_zebra_add( ospf, (struct prefix_ipv4 *)&rn->p, or); } else if (or->type == OSPF_DESTINATION_DISCARD) if (!ospf_route_match_same( ospf->old_table, (struct prefix_ipv4 *)&rn->p, or)) ospf_zebra_add_discard( ospf, (struct prefix_ipv4 *)&rn->p); } } /* RFC2328 16.1. (4). For "router". */ void ospf_intra_add_router(struct route_table *rt, struct vertex *v, struct ospf_area *area) { struct route_node *rn; struct ospf_route * or ; struct prefix_ipv4 p; struct router_lsa *lsa; if (IS_DEBUG_OSPF_EVENT) zlog_debug("ospf_intra_add_router: Start"); lsa = (struct router_lsa *)v->lsa; if (IS_DEBUG_OSPF_EVENT) zlog_debug("ospf_intra_add_router: LS ID: %s", inet_ntoa(lsa->header.id)); if (!OSPF_IS_AREA_BACKBONE(area)) ospf_vl_up_check(area, lsa->header.id, v); if (!CHECK_FLAG(lsa->flags, ROUTER_LSA_SHORTCUT)) area->shortcut_capability = 0; /* If the newly added vertex is an area border router or AS boundary router, a routing table entry is added whose destination type is "router". */ if (!IS_ROUTER_LSA_BORDER(lsa) && !IS_ROUTER_LSA_EXTERNAL(lsa)) { if (IS_DEBUG_OSPF_EVENT) zlog_debug( "ospf_intra_add_router: " "this router is neither ASBR nor ABR, skipping it"); return; } /* Update ABR and ASBR count in this area. */ if (IS_ROUTER_LSA_BORDER(lsa)) area->abr_count++; if (IS_ROUTER_LSA_EXTERNAL(lsa)) area->asbr_count++; /* The Options field found in the associated router-LSA is copied into the routing table entry's Optional capabilities field. Call the newly added vertex Router X. */ or = ospf_route_new(); or->id = v->id; or->u.std.area_id = area->area_id; or->u.std.external_routing = area->external_routing; or->path_type = OSPF_PATH_INTRA_AREA; or->cost = v->distance; or->type = OSPF_DESTINATION_ROUTER; or->u.std.origin = (struct lsa_header *)lsa; or->u.std.options = lsa->header.options; or->u.std.flags = lsa->flags; /* If Router X is the endpoint of one of the calculating router's virtual links, and the virtual link uses Area A as Transit area: the virtual link is declared up, the IP address of the virtual interface is set to the IP address of the outgoing interface calculated above for Router X, and the virtual neighbor's IP address is set to Router X's interface address (contained in Router X's router-LSA) that points back to the root of the shortest- path tree; equivalently, this is the interface that points back to Router X's parent vertex on the shortest-path tree (similar to the calculation in Section 16.1.1). */ p.family = AF_INET; p.prefix = v->id; p.prefixlen = IPV4_MAX_BITLEN; apply_mask_ipv4(&p); if (IS_DEBUG_OSPF_EVENT) zlog_debug("ospf_intra_add_router: talking about %s/%d", inet_ntoa(p.prefix), p.prefixlen); rn = route_node_get(rt, (struct prefix *)&p); /* Note that we keep all routes to ABRs and ASBRs, not only the best */ if (rn->info == NULL) rn->info = list_new(); else route_unlock_node(rn); ospf_route_copy_nexthops_from_vertex(or, v); listnode_add(rn->info, or); if (IS_DEBUG_OSPF_EVENT) zlog_debug("ospf_intra_add_router: Stop"); } /* RFC2328 16.1. (4). For transit network. */ void ospf_intra_add_transit(struct route_table *rt, struct vertex *v, struct ospf_area *area) { struct route_node *rn; struct ospf_route * or ; struct prefix_ipv4 p; struct network_lsa *lsa; lsa = (struct network_lsa *)v->lsa; /* If the newly added vertex is a transit network, the routing table entry for the network is located. The entry's Destination ID is the IP network number, which can be obtained by masking the Vertex ID (Link State ID) with its associated subnet mask (found in the body of the associated network-LSA). */ p.family = AF_INET; p.prefix = v->id; p.prefixlen = ip_masklen(lsa->mask); apply_mask_ipv4(&p); rn = route_node_get(rt, (struct prefix *)&p); /* If the routing table entry already exists (i.e., there is already an intra-area route to the destination installed in the routing table), multiple vertices have mapped to the same IP network. For example, this can occur when a new Designated Router is being established. In this case, the current routing table entry should be overwritten if and only if the newly found path is just as short and the current routing table entry's Link State Origin has a smaller Link State ID than the newly added vertex' LSA. */ if (rn->info) { struct ospf_route *cur_or; route_unlock_node(rn); cur_or = rn->info; if (v->distance > cur_or->cost || IPV4_ADDR_CMP(&cur_or->u.std.origin->id, &lsa->header.id) > 0) return; ospf_route_free(rn->info); } or = ospf_route_new(); or->id = v->id; or->u.std.area_id = area->area_id; or->u.std.external_routing = area->external_routing; or->path_type = OSPF_PATH_INTRA_AREA; or->cost = v->distance; or->type = OSPF_DESTINATION_NETWORK; or->u.std.origin = (struct lsa_header *)lsa; ospf_route_copy_nexthops_from_vertex(or, v); rn->info = or ; } /* RFC2328 16.1. second stage. */ void ospf_intra_add_stub(struct route_table *rt, struct router_lsa_link *link, struct vertex *v, struct ospf_area *area, int parent_is_root, int lsa_pos) { uint32_t cost; struct route_node *rn; struct ospf_route * or ; struct prefix_ipv4 p; struct router_lsa *lsa; struct ospf_interface *oi; struct ospf_path *path; if (IS_DEBUG_OSPF_EVENT) zlog_debug("ospf_intra_add_stub(): Start"); lsa = (struct router_lsa *)v->lsa; p.family = AF_INET; p.prefix = link->link_id; p.prefixlen = ip_masklen(link->link_data); apply_mask_ipv4(&p); if (IS_DEBUG_OSPF_EVENT) zlog_debug("ospf_intra_add_stub(): processing route to %s/%d", inet_ntoa(p.prefix), p.prefixlen); /* (1) Calculate the distance D of stub network from the root. D is equal to the distance from the root to the router vertex (calculated in stage 1), plus the stub network link's advertised cost. */ cost = v->distance + ntohs(link->m[0].metric); if (IS_DEBUG_OSPF_EVENT) zlog_debug( "ospf_intra_add_stub(): calculated cost is %d + %d = %d", v->distance, ntohs(link->m[0].metric), cost); /* PtP links with /32 masks adds host routes to remote, directly * connected hosts, see RFC 2328, 12.4.1.1, Option 1. * Such routes can just be ignored for the sake of tidyness. */ if (parent_is_root && link->link_data.s_addr == 0xffffffff && ospf_if_lookup_by_local_addr(area->ospf, NULL, link->link_id)) { if (IS_DEBUG_OSPF_EVENT) zlog_debug("%s: ignoring host route %s/32 to self.", __func__, inet_ntoa(link->link_id)); return; } rn = route_node_get(rt, (struct prefix *)&p); /* Lookup current routing table. */ if (rn->info) { struct ospf_route *cur_or; route_unlock_node(rn); cur_or = rn->info; if (IS_DEBUG_OSPF_EVENT) zlog_debug( "ospf_intra_add_stub(): " "another route to the same prefix found with cost %u", cur_or->cost); /* Compare this distance to the current best cost to the stub network. This is done by looking up the stub network's current routing table entry. If the calculated distance D is larger, go on to examine the next stub network link in the LSA. */ if (cost > cur_or->cost) { if (IS_DEBUG_OSPF_EVENT) zlog_debug( "ospf_intra_add_stub(): old route is better, exit"); return; } /* (2) If this step is reached, the stub network's routing table entry must be updated. Calculate the set of next hops that would result from using the stub network link. This calculation is shown in Section 16.1.1; input to this calculation is the destination (the stub network) and the parent vertex (the router vertex). If the distance D is the same as the current routing table cost, simply add this set of next hops to the routing table entry's list of next hops. In this case, the routing table already has a Link State Origin. If this Link State Origin is a router-LSA whose Link State ID is smaller than V's Router ID, reset the Link State Origin to V's router-LSA. */ if (cost == cur_or->cost) { if (IS_DEBUG_OSPF_EVENT) zlog_debug( "ospf_intra_add_stub(): routes are equal, merge"); ospf_route_copy_nexthops_from_vertex(cur_or, v); if (IPV4_ADDR_CMP(&cur_or->u.std.origin->id, &lsa->header.id) < 0) cur_or->u.std.origin = (struct lsa_header *)lsa; return; } /* Otherwise D is smaller than the routing table cost. Overwrite the current routing table entry by setting the routing table entry's cost to D, and by setting the entry's list of next hops to the newly calculated set. Set the routing table entry's Link State Origin to V's router-LSA. Then go on to examine the next stub network link. */ if (cost < cur_or->cost) { if (IS_DEBUG_OSPF_EVENT) zlog_debug( "ospf_intra_add_stub(): new route is better, set it"); cur_or->cost = cost; list_delete_all_node(cur_or->paths); ospf_route_copy_nexthops_from_vertex(cur_or, v); cur_or->u.std.origin = (struct lsa_header *)lsa; return; } } if (IS_DEBUG_OSPF_EVENT) zlog_debug("ospf_intra_add_stub(): installing new route"); or = ospf_route_new(); or->id = v->id; or->u.std.area_id = area->area_id; or->u.std.external_routing = area->external_routing; or->path_type = OSPF_PATH_INTRA_AREA; or->cost = cost; or->type = OSPF_DESTINATION_NETWORK; or->u.std.origin = (struct lsa_header *)lsa; /* Nexthop is depend on connection type. */ if (v != area->spf) { if (IS_DEBUG_OSPF_EVENT) zlog_debug( "ospf_intra_add_stub(): this network is on remote router"); ospf_route_copy_nexthops_from_vertex(or, v); } else { if (IS_DEBUG_OSPF_EVENT) zlog_debug( "ospf_intra_add_stub(): this network is on this router"); if ((oi = ospf_if_lookup_by_lsa_pos(area, lsa_pos))) { if (IS_DEBUG_OSPF_EVENT) zlog_debug( "ospf_intra_add_stub(): the interface is %s", IF_NAME(oi)); path = ospf_path_new(); path->nexthop.s_addr = 0; path->ifindex = oi->ifp->ifindex; if (CHECK_FLAG(oi->connected->flags, ZEBRA_IFA_UNNUMBERED)) path->unnumbered = 1; listnode_add(or->paths, path); } else { if (IS_DEBUG_OSPF_EVENT) zlog_debug( "ospf_intra_add_stub(): where's the interface ?"); } } rn->info = or ; if (IS_DEBUG_OSPF_EVENT) zlog_debug("ospf_intra_add_stub(): Stop"); } const char *ospf_path_type_str[] = {"unknown-type", "intra-area", "inter-area", "type1-external", "type2-external"}; void ospf_route_table_dump(struct route_table *rt) { struct route_node *rn; struct ospf_route * or ; char buf1[BUFSIZ]; char buf2[BUFSIZ]; struct listnode *pnode; struct ospf_path *path; #if 0 zlog_debug ("Type Dest Area Path Type Cost Next Adv."); zlog_debug (" Hop(s) Router(s)"); #endif /* 0 */ zlog_debug("========== OSPF routing table =========="); for (rn = route_top(rt); rn; rn = route_next(rn)) if ((or = rn->info) != NULL) { if (or->type == OSPF_DESTINATION_NETWORK) { zlog_debug("N %s/%d\t%s\t%s\t%d", inet_ntop(AF_INET, &rn->p.u.prefix4, buf1, BUFSIZ), rn->p.prefixlen, inet_ntop(AF_INET, & or->u.std.area_id, buf2, BUFSIZ), ospf_path_type_str[or->path_type], or->cost); for (ALL_LIST_ELEMENTS_RO(or->paths, pnode, path)) zlog_debug(" -> %s", inet_ntoa(path->nexthop)); } else zlog_debug("R %s\t%s\t%s\t%d", inet_ntop(AF_INET, &rn->p.u.prefix4, buf1, BUFSIZ), inet_ntop(AF_INET, & or->u.std.area_id, buf2, BUFSIZ), ospf_path_type_str[or->path_type], or->cost); } zlog_debug("========================================"); } /* This is 16.4.1 implementation. o Intra-area paths using non-backbone areas are always the most preferred. o The other paths, intra-area backbone paths and inter-area paths, are of equal preference. */ static int ospf_asbr_route_cmp(struct ospf *ospf, struct ospf_route *r1, struct ospf_route *r2) { uint8_t r1_type, r2_type; r1_type = r1->path_type; r2_type = r2->path_type; /* r1/r2 itself is backbone, and it's Inter-area path. */ if (OSPF_IS_AREA_ID_BACKBONE(r1->u.std.area_id)) r1_type = OSPF_PATH_INTER_AREA; if (OSPF_IS_AREA_ID_BACKBONE(r2->u.std.area_id)) r2_type = OSPF_PATH_INTER_AREA; return (r1_type - r2_type); } /* Compare two routes. ret < 0 -- r1 is better. ret == 0 -- r1 and r2 are the same. ret > 0 -- r2 is better. */ int ospf_route_cmp(struct ospf *ospf, struct ospf_route *r1, struct ospf_route *r2) { int ret = 0; /* Path types of r1 and r2 are not the same. */ if ((ret = (r1->path_type - r2->path_type))) return ret; if (IS_DEBUG_OSPF_EVENT) zlog_debug("Route[Compare]: Path types are the same."); /* Path types are the same, compare any cost. */ switch (r1->path_type) { case OSPF_PATH_INTRA_AREA: case OSPF_PATH_INTER_AREA: break; case OSPF_PATH_TYPE1_EXTERNAL: if (!CHECK_FLAG(ospf->config, OSPF_RFC1583_COMPATIBLE)) { ret = ospf_asbr_route_cmp(ospf, r1->u.ext.asbr, r2->u.ext.asbr); if (ret != 0) return ret; } break; case OSPF_PATH_TYPE2_EXTERNAL: if ((ret = (r1->u.ext.type2_cost - r2->u.ext.type2_cost))) return ret; if (!CHECK_FLAG(ospf->config, OSPF_RFC1583_COMPATIBLE)) { ret = ospf_asbr_route_cmp(ospf, r1->u.ext.asbr, r2->u.ext.asbr); if (ret != 0) return ret; } break; } /* Anyway, compare the costs. */ return (r1->cost - r2->cost); } static int ospf_path_exist(struct list *plist, struct in_addr nexthop, struct ospf_interface *oi) { struct listnode *node, *nnode; struct ospf_path *path; for (ALL_LIST_ELEMENTS(plist, node, nnode, path)) if (IPV4_ADDR_SAME(&path->nexthop, &nexthop) && path->ifindex == oi->ifp->ifindex) return 1; return 0; } void ospf_route_copy_nexthops_from_vertex(struct ospf_route *to, struct vertex *v) { struct listnode *node; struct ospf_path *path; struct vertex_nexthop *nexthop; struct vertex_parent *vp; assert(to->paths); for (ALL_LIST_ELEMENTS_RO(v->parents, node, vp)) { nexthop = vp->nexthop; if (nexthop->oi != NULL) { if (!ospf_path_exist(to->paths, nexthop->router, nexthop->oi)) { path = ospf_path_new(); path->nexthop = nexthop->router; path->ifindex = nexthop->oi->ifp->ifindex; if (CHECK_FLAG(nexthop->oi->connected->flags, ZEBRA_IFA_UNNUMBERED)) path->unnumbered = 1; listnode_add(to->paths, path); } } } } struct ospf_path *ospf_path_lookup(struct list *plist, struct ospf_path *path) { struct listnode *node; struct ospf_path *op; for (ALL_LIST_ELEMENTS_RO(plist, node, op)) { if (!IPV4_ADDR_SAME(&op->nexthop, &path->nexthop)) continue; if (!IPV4_ADDR_SAME(&op->adv_router, &path->adv_router)) continue; if (op->ifindex != path->ifindex) continue; return op; } return NULL; } void ospf_route_copy_nexthops(struct ospf_route *to, struct list *from) { struct listnode *node, *nnode; struct ospf_path *path; assert(to->paths); for (ALL_LIST_ELEMENTS(from, node, nnode, path)) /* The same routes are just discarded. */ if (!ospf_path_lookup(to->paths, path)) listnode_add(to->paths, ospf_path_dup(path)); } void ospf_route_subst_nexthops(struct ospf_route *to, struct list *from) { list_delete_all_node(to->paths); ospf_route_copy_nexthops(to, from); } void ospf_route_subst(struct route_node *rn, struct ospf_route *new_or, struct ospf_route *over) { route_lock_node(rn); ospf_route_free(rn->info); ospf_route_copy_nexthops(new_or, over->paths); rn->info = new_or; route_unlock_node(rn); } void ospf_route_add(struct route_table *rt, struct prefix_ipv4 *p, struct ospf_route *new_or, struct ospf_route *over) { struct route_node *rn; rn = route_node_get(rt, (struct prefix *)p); ospf_route_copy_nexthops(new_or, over->paths); if (rn->info) { if (IS_DEBUG_OSPF_EVENT) zlog_debug("ospf_route_add(): something's wrong !"); route_unlock_node(rn); return; } rn->info = new_or; } void ospf_prune_unreachable_networks(struct route_table *rt) { struct route_node *rn, *next; struct ospf_route * or ; if (IS_DEBUG_OSPF_EVENT) zlog_debug("Pruning unreachable networks"); for (rn = route_top(rt); rn; rn = next) { next = route_next(rn); if (rn->info != NULL) { or = rn->info; if (listcount(or->paths) == 0) { if (IS_DEBUG_OSPF_EVENT) zlog_debug("Pruning route to %s/%d", inet_ntoa(rn->p.u.prefix4), rn->p.prefixlen); ospf_route_free(or); rn->info = NULL; route_unlock_node(rn); } } } } void ospf_prune_unreachable_routers(struct route_table *rtrs) { struct route_node *rn, *next; struct ospf_route * or ; struct listnode *node, *nnode; struct list *paths; if (IS_DEBUG_OSPF_EVENT) zlog_debug("Pruning unreachable routers"); for (rn = route_top(rtrs); rn; rn = next) { next = route_next(rn); if ((paths = rn->info) == NULL) continue; for (ALL_LIST_ELEMENTS(paths, node, nnode, or)) { if (listcount(or->paths) == 0) { if (IS_DEBUG_OSPF_EVENT) { zlog_debug("Pruning route to rtr %s", inet_ntoa(rn->p.u.prefix4)); zlog_debug( " via area %s", inet_ntoa(or->u.std.area_id)); } listnode_delete(paths, or); ospf_route_free(or); } } if (listcount(paths) == 0) { if (IS_DEBUG_OSPF_EVENT) zlog_debug("Pruning router node %s", inet_ntoa(rn->p.u.prefix4)); list_delete(&paths); rn->info = NULL; route_unlock_node(rn); } } } int ospf_add_discard_route(struct ospf *ospf, struct route_table *rt, struct ospf_area *area, struct prefix_ipv4 *p) { struct route_node *rn; struct ospf_route * or, *new_or; rn = route_node_get(rt, (struct prefix *)p); if (rn == NULL) { if (IS_DEBUG_OSPF_EVENT) zlog_debug( "ospf_add_discard_route(): router installation error"); return 0; } if (rn->info) /* If the route to the same destination is found */ { route_unlock_node(rn); or = rn->info; if (or->path_type == OSPF_PATH_INTRA_AREA) { if (IS_DEBUG_OSPF_EVENT) zlog_debug( "ospf_add_discard_route(): " "an intra-area route exists"); return 0; } if (or->type == OSPF_DESTINATION_DISCARD) { if (IS_DEBUG_OSPF_EVENT) zlog_debug( "ospf_add_discard_route(): " "discard entry already installed"); return 0; } ospf_route_free(rn->info); } if (IS_DEBUG_OSPF_EVENT) zlog_debug( "ospf_add_discard_route(): " "adding %s/%d", inet_ntoa(p->prefix), p->prefixlen); new_or = ospf_route_new(); new_or->type = OSPF_DESTINATION_DISCARD; new_or->id.s_addr = 0; new_or->cost = 0; new_or->u.std.area_id = area->area_id; new_or->u.std.external_routing = area->external_routing; new_or->path_type = OSPF_PATH_INTER_AREA; rn->info = new_or; ospf_zebra_add_discard(ospf, p); return 1; } void ospf_delete_discard_route(struct ospf *ospf, struct route_table *rt, struct prefix_ipv4 *p) { struct route_node *rn; struct ospf_route * or ; if (IS_DEBUG_OSPF_EVENT) zlog_debug( "ospf_delete_discard_route(): " "deleting %s/%d", inet_ntoa(p->prefix), p->prefixlen); rn = route_node_lookup(rt, (struct prefix *)p); if (rn == NULL) { if (IS_DEBUG_OSPF_EVENT) zlog_debug( "ospf_delete_discard_route(): no route found"); return; } or = rn->info; if (or->path_type == OSPF_PATH_INTRA_AREA) { if (IS_DEBUG_OSPF_EVENT) zlog_debug( "ospf_delete_discard_route(): " "an intra-area route exists"); return; } if (or->type != OSPF_DESTINATION_DISCARD) { if (IS_DEBUG_OSPF_EVENT) zlog_debug( "ospf_delete_discard_route(): " "not a discard entry"); return; } /* free the route entry and the route node */ ospf_route_free(rn->info); rn->info = NULL; route_unlock_node(rn); route_unlock_node(rn); /* remove the discard entry from the rib */ ospf_zebra_delete_discard(ospf, p); return; } frr-7.2.1/ospfd/ospf_route.h0000644000000000000000000001110013610377563012656 00000000000000/* * OSPF routing table. * Copyright (C) 1999, 2000 Toshiaki Takada * * This file is part of GNU Zebra. * * GNU Zebra is free software; you can redistribute it and/or modify it * under the terms of the GNU General Public License as published by the * Free Software Foundation; either version 2, or (at your option) any * later version. * * GNU Zebra 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; see the file COPYING; if not, write to the Free Software * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA */ #ifndef _ZEBRA_OSPF_ROUTE_H #define _ZEBRA_OSPF_ROUTE_H #define OSPF_DESTINATION_ROUTER 1 #define OSPF_DESTINATION_NETWORK 2 #define OSPF_DESTINATION_DISCARD 3 #define OSPF_PATH_MIN 0 #define OSPF_PATH_INTRA_AREA 1 #define OSPF_PATH_INTER_AREA 2 #define OSPF_PATH_TYPE1_EXTERNAL 3 #define OSPF_PATH_TYPE2_EXTERNAL 4 #define OSPF_PATH_MAX 5 /* OSPF Path. */ struct ospf_path { struct in_addr nexthop; struct in_addr adv_router; ifindex_t ifindex; unsigned char unnumbered; }; /* Below is the structure linked to every route node. Note that for Network routing entries a single ospf_route is kept, while for ABRs and ASBRs (Router routing entries), we link an instance of ospf_router_route where a list of paths is maintained, so nr->info is a (struct ospf_route *) for OSPF_DESTINATION_NETWORK but nr->info is a (struct ospf_router_route *) for OSPF_DESTINATION_ROUTER */ struct route_standard { /* Link Sate Origin. */ struct lsa_header *origin; /* Associated Area. */ struct in_addr area_id; /* The area the route belongs to */ /* Area Type */ int external_routing; /* Optional Capability. */ uint8_t options; /* Get from LSA header. */ /* */ uint8_t flags; /* From router-LSA */ }; struct route_external { /* Link State Origin. */ struct ospf_lsa *origin; /* Link State Cost Type2. */ uint32_t type2_cost; /* Tag value. */ uint32_t tag; /* ASBR route. */ struct ospf_route *asbr; }; struct ospf_route { /* Destination Type. */ uint8_t type; /* Destination ID. */ /* i.e. Link State ID. */ struct in_addr id; /* Address Mask. */ struct in_addr mask; /* Only valid for networks. */ /* Path Type. */ uint8_t path_type; /* List of Paths. */ struct list *paths; /* Link State Cost. */ uint32_t cost; /* i.e. metric. */ /* Route specific info. */ union { struct route_standard std; struct route_external ext; } u; }; extern struct ospf_path *ospf_path_new(void); extern void ospf_path_free(struct ospf_path *); extern struct ospf_path *ospf_path_lookup(struct list *, struct ospf_path *); extern struct ospf_route *ospf_route_new(void); extern void ospf_route_free(struct ospf_route *); extern void ospf_route_delete(struct ospf *, struct route_table *); extern void ospf_route_table_free(struct route_table *); extern void ospf_route_install(struct ospf *, struct route_table *); extern void ospf_route_table_dump(struct route_table *); extern void ospf_intra_add_router(struct route_table *, struct vertex *, struct ospf_area *); extern void ospf_intra_add_transit(struct route_table *, struct vertex *, struct ospf_area *); extern void ospf_intra_add_stub(struct route_table *, struct router_lsa_link *, struct vertex *, struct ospf_area *, int parent_is_root, int); extern int ospf_route_cmp(struct ospf *, struct ospf_route *, struct ospf_route *); extern void ospf_route_copy_nexthops(struct ospf_route *, struct list *); extern void ospf_route_copy_nexthops_from_vertex(struct ospf_route *, struct vertex *); extern void ospf_route_subst(struct route_node *, struct ospf_route *, struct ospf_route *); extern void ospf_route_add(struct route_table *, struct prefix_ipv4 *, struct ospf_route *, struct ospf_route *); extern void ospf_route_subst_nexthops(struct ospf_route *, struct list *); extern void ospf_prune_unreachable_networks(struct route_table *); extern void ospf_prune_unreachable_routers(struct route_table *); extern int ospf_add_discard_route(struct ospf *, struct route_table *, struct ospf_area *, struct prefix_ipv4 *); extern void ospf_delete_discard_route(struct ospf *, struct route_table *, struct prefix_ipv4 *); extern int ospf_route_match_same(struct route_table *, struct prefix_ipv4 *, struct ospf_route *); #endif /* _ZEBRA_OSPF_ROUTE_H */ frr-7.2.1/ospfd/ospf_routemap.c0000644000000000000000000004052513610377563013364 00000000000000/* * Route map function of ospfd. * Copyright (C) 2000 IP Infusion Inc. * * Written by Toshiaki Takada. * * This file is part of GNU Zebra. * * GNU Zebra is free software; you can redistribute it and/or modify it * under the terms of the GNU General Public License as published by the * Free Software Foundation; either version 2, or (at your option) any * later version. * * GNU Zebra 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; see the file COPYING; if not, write to the Free Software * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA */ #include #include "memory.h" #include "prefix.h" #include "table.h" #include "vty.h" #include "routemap.h" #include "command.h" #include "log.h" #include "plist.h" #include "vrf.h" #include "frrstr.h" #include "ospfd/ospfd.h" #include "ospfd/ospf_asbr.h" #include "ospfd/ospf_interface.h" #include "ospfd/ospf_lsa.h" #include "ospfd/ospf_route.h" #include "ospfd/ospf_zebra.h" #include "ospfd/ospf_errors.h" /* Hook function for updating route_map assignment. */ static void ospf_route_map_update(const char *name) { struct ospf *ospf; int type; struct listnode *n1 = NULL; /* If OSPF instatnce does not exist, return right now. */ if (listcount(om->ospf) == 0) return; for (ALL_LIST_ELEMENTS_RO(om->ospf, n1, ospf)) { /* Update route-map */ for (type = 0; type <= ZEBRA_ROUTE_MAX; type++) { struct list *red_list; struct listnode *node; struct ospf_redist *red; red_list = ospf->redist[type]; if (!red_list) continue; for (ALL_LIST_ELEMENTS_RO(red_list, node, red)) { if (ROUTEMAP_NAME(red) && strcmp(ROUTEMAP_NAME(red), name) == 0) { /* Keep old route-map. */ struct route_map *old = ROUTEMAP(red); if (!old) { /* Route-map creation */ /* Update route-map. */ ROUTEMAP(red) = route_map_lookup_by_name( ROUTEMAP_NAME(red)); route_map_counter_increment( ROUTEMAP(red)); } else { /* Route-map deletion */ ROUTEMAP(red) = NULL; } /* No update for this distribute type. */ if (old == NULL && ROUTEMAP(red) == NULL) continue; ospf_distribute_list_update( ospf, type, red->instance); } } } } } static void ospf_route_map_event(const char *name) { struct ospf *ospf; int type; struct listnode *n1 = NULL; for (ALL_LIST_ELEMENTS_RO(om->ospf, n1, ospf)) { for (type = 0; type <= ZEBRA_ROUTE_MAX; type++) { struct list *red_list; struct listnode *node; struct ospf_redist *red; red_list = ospf->redist[type]; if (!red_list) continue; for (ALL_LIST_ELEMENTS_RO(red_list, node, red)) { if (ROUTEMAP_NAME(red) && ROUTEMAP(red) && !strcmp(ROUTEMAP_NAME(red), name)) { ospf_distribute_list_update( ospf, type, red->instance); } } } } } /* `match ip netxthop ' */ /* Match function return 1 if match is success else return zero. */ static enum route_map_cmd_result_t route_match_ip_nexthop(void *rule, const struct prefix *prefix, route_map_object_t type, void *object) { struct access_list *alist; struct external_info *ei = object; struct prefix_ipv4 p; if (type == RMAP_OSPF) { p.family = AF_INET; p.prefix = ei->nexthop; p.prefixlen = IPV4_MAX_BITLEN; alist = access_list_lookup(AFI_IP, (char *)rule); if (alist == NULL) return RMAP_NOMATCH; return (access_list_apply(alist, &p) == FILTER_DENY ? RMAP_NOMATCH : RMAP_MATCH); } return RMAP_NOMATCH; } /* Route map `ip next-hop' match statement. `arg' should be access-list name. */ static void *route_match_ip_nexthop_compile(const char *arg) { return XSTRDUP(MTYPE_ROUTE_MAP_COMPILED, arg); } /* Free route map's compiled `ip address' value. */ static void route_match_ip_nexthop_free(void *rule) { XFREE(MTYPE_ROUTE_MAP_COMPILED, rule); } /* Route map commands for metric matching. */ struct route_map_rule_cmd route_match_ip_nexthop_cmd = { "ip next-hop", route_match_ip_nexthop, route_match_ip_nexthop_compile, route_match_ip_nexthop_free}; /* `match ip next-hop prefix-list PREFIX_LIST' */ static enum route_map_cmd_result_t route_match_ip_next_hop_prefix_list(void *rule, const struct prefix *prefix, route_map_object_t type, void *object) { struct prefix_list *plist; struct external_info *ei = object; struct prefix_ipv4 p; if (type == RMAP_OSPF) { p.family = AF_INET; p.prefix = ei->nexthop; p.prefixlen = IPV4_MAX_BITLEN; plist = prefix_list_lookup(AFI_IP, (char *)rule); if (plist == NULL) return RMAP_NOMATCH; return (prefix_list_apply(plist, &p) == PREFIX_DENY ? RMAP_NOMATCH : RMAP_MATCH); } return RMAP_NOMATCH; } static void *route_match_ip_next_hop_prefix_list_compile(const char *arg) { return XSTRDUP(MTYPE_ROUTE_MAP_COMPILED, arg); } static void route_match_ip_next_hop_prefix_list_free(void *rule) { XFREE(MTYPE_ROUTE_MAP_COMPILED, rule); } struct route_map_rule_cmd route_match_ip_next_hop_prefix_list_cmd = { "ip next-hop prefix-list", route_match_ip_next_hop_prefix_list, route_match_ip_next_hop_prefix_list_compile, route_match_ip_next_hop_prefix_list_free}; /* `match ip next-hop type ' */ static enum route_map_cmd_result_t route_match_ip_next_hop_type(void *rule, const struct prefix *prefix, route_map_object_t type, void *object) { struct external_info *ei = object; if (type == RMAP_OSPF && prefix->family == AF_INET) { ei = (struct external_info *)object; if (!ei) return RMAP_NOMATCH; if (ei->nexthop.s_addr == INADDR_ANY && !ei->ifindex) return RMAP_MATCH; } return RMAP_NOMATCH; } static void *route_match_ip_next_hop_type_compile(const char *arg) { return XSTRDUP(MTYPE_ROUTE_MAP_COMPILED, arg); } static void route_match_ip_next_hop_type_free(void *rule) { XFREE(MTYPE_ROUTE_MAP_COMPILED, rule); } static struct route_map_rule_cmd route_match_ip_next_hop_type_cmd = { "ip next-hop type", route_match_ip_next_hop_type, route_match_ip_next_hop_type_compile, route_match_ip_next_hop_type_free}; /* `match ip address IP_ACCESS_LIST' */ /* Match function should return 1 if match is success else return zero. */ static enum route_map_cmd_result_t route_match_ip_address(void *rule, const struct prefix *prefix, route_map_object_t type, void *object) { struct access_list *alist; /* struct prefix_ipv4 match; */ if (type == RMAP_OSPF) { alist = access_list_lookup(AFI_IP, (char *)rule); if (alist == NULL) return RMAP_NOMATCH; return (access_list_apply(alist, prefix) == FILTER_DENY ? RMAP_NOMATCH : RMAP_MATCH); } return RMAP_NOMATCH; } /* Route map `ip address' match statement. `arg' should be access-list name. */ static void *route_match_ip_address_compile(const char *arg) { return XSTRDUP(MTYPE_ROUTE_MAP_COMPILED, arg); } /* Free route map's compiled `ip address' value. */ static void route_match_ip_address_free(void *rule) { XFREE(MTYPE_ROUTE_MAP_COMPILED, rule); } /* Route map commands for ip address matching. */ struct route_map_rule_cmd route_match_ip_address_cmd = { "ip address", route_match_ip_address, route_match_ip_address_compile, route_match_ip_address_free}; /* `match ip address prefix-list PREFIX_LIST' */ static enum route_map_cmd_result_t route_match_ip_address_prefix_list(void *rule, const struct prefix *prefix, route_map_object_t type, void *object) { struct prefix_list *plist; if (type == RMAP_OSPF) { plist = prefix_list_lookup(AFI_IP, (char *)rule); if (plist == NULL) return RMAP_NOMATCH; return (prefix_list_apply(plist, prefix) == PREFIX_DENY ? RMAP_NOMATCH : RMAP_MATCH); } return RMAP_NOMATCH; } static void *route_match_ip_address_prefix_list_compile(const char *arg) { return XSTRDUP(MTYPE_ROUTE_MAP_COMPILED, arg); } static void route_match_ip_address_prefix_list_free(void *rule) { XFREE(MTYPE_ROUTE_MAP_COMPILED, rule); } struct route_map_rule_cmd route_match_ip_address_prefix_list_cmd = { "ip address prefix-list", route_match_ip_address_prefix_list, route_match_ip_address_prefix_list_compile, route_match_ip_address_prefix_list_free}; /* `match interface IFNAME' */ /* Match function should return 1 if match is success else return zero. */ static enum route_map_cmd_result_t route_match_interface(void *rule, const struct prefix *prefix, route_map_object_t type, void *object) { struct interface *ifp; struct external_info *ei; if (type == RMAP_OSPF) { ei = object; ifp = if_lookup_by_name_all_vrf((char *)rule); if (ifp == NULL || ifp->ifindex != ei->ifindex) return RMAP_NOMATCH; return RMAP_MATCH; } return RMAP_NOMATCH; } /* Route map `interface' match statement. `arg' should be interface name. */ static void *route_match_interface_compile(const char *arg) { return XSTRDUP(MTYPE_ROUTE_MAP_COMPILED, arg); } /* Free route map's compiled `interface' value. */ static void route_match_interface_free(void *rule) { XFREE(MTYPE_ROUTE_MAP_COMPILED, rule); } /* Route map commands for ip address matching. */ struct route_map_rule_cmd route_match_interface_cmd = { "interface", route_match_interface, route_match_interface_compile, route_match_interface_free}; /* Match function return 1 if match is success else return zero. */ static enum route_map_cmd_result_t route_match_tag(void *rule, const struct prefix *prefix, route_map_object_t type, void *object) { route_tag_t *tag; struct external_info *ei; if (type == RMAP_OSPF) { tag = rule; ei = object; return ((ei->tag == *tag) ? RMAP_MATCH : RMAP_NOMATCH); } return RMAP_NOMATCH; } /* Route map commands for tag matching. */ static struct route_map_rule_cmd route_match_tag_cmd = { "tag", route_match_tag, route_map_rule_tag_compile, route_map_rule_tag_free, }; struct ospf_metric { enum { metric_increment, metric_decrement, metric_absolute } type; bool used; uint32_t metric; }; /* `set metric METRIC' */ /* Set metric to attribute. */ static enum route_map_cmd_result_t route_set_metric(void *rule, const struct prefix *prefix, route_map_object_t type, void *object) { struct ospf_metric *metric; struct external_info *ei; if (type == RMAP_OSPF) { /* Fetch routemap's rule information. */ metric = rule; ei = object; /* Set metric out value. */ if (!metric->used) return RMAP_OKAY; ei->route_map_set.metric = DEFAULT_DEFAULT_METRIC; if (metric->type == metric_increment) ei->route_map_set.metric += metric->metric; else if (metric->type == metric_decrement) ei->route_map_set.metric -= metric->metric; else if (metric->type == metric_absolute) ei->route_map_set.metric = metric->metric; if (ei->route_map_set.metric > OSPF_LS_INFINITY) ei->route_map_set.metric = OSPF_LS_INFINITY; } return RMAP_OKAY; } /* set metric compilation. */ static void *route_set_metric_compile(const char *arg) { struct ospf_metric *metric; metric = XCALLOC(MTYPE_ROUTE_MAP_COMPILED, sizeof(uint32_t)); metric->used = false; if (all_digit(arg)) metric->type = metric_absolute; if (strmatch(arg, "+rtt") || strmatch(arg, "-rtt")) { flog_warn(EC_OSPF_SET_METRIC_PLUS, "OSPF does not support 'set metric +rtt / -rtt'"); return metric; } if ((arg[0] == '+') && all_digit(arg + 1)) { metric->type = metric_increment; arg++; } if ((arg[0] == '-') && all_digit(arg + 1)) { metric->type = metric_decrement; arg++; } metric->metric = strtoul(arg, NULL, 10); if (metric->metric) metric->used = true; return metric; } /* Free route map's compiled `set metric' value. */ static void route_set_metric_free(void *rule) { XFREE(MTYPE_ROUTE_MAP_COMPILED, rule); } /* Set metric rule structure. */ struct route_map_rule_cmd route_set_metric_cmd = { "metric", route_set_metric, route_set_metric_compile, route_set_metric_free, }; /* `set metric-type TYPE' */ /* Set metric-type to attribute. */ static enum route_map_cmd_result_t route_set_metric_type(void *rule, const struct prefix *prefix, route_map_object_t type, void *object) { uint32_t *metric_type; struct external_info *ei; if (type == RMAP_OSPF) { /* Fetch routemap's rule information. */ metric_type = rule; ei = object; /* Set metric out value. */ ei->route_map_set.metric_type = *metric_type; } return RMAP_OKAY; } /* set metric-type compilation. */ static void *route_set_metric_type_compile(const char *arg) { uint32_t *metric_type; metric_type = XCALLOC(MTYPE_ROUTE_MAP_COMPILED, sizeof(uint32_t)); if (strcmp(arg, "type-1") == 0) *metric_type = EXTERNAL_METRIC_TYPE_1; else if (strcmp(arg, "type-2") == 0) *metric_type = EXTERNAL_METRIC_TYPE_2; if (*metric_type == EXTERNAL_METRIC_TYPE_1 || *metric_type == EXTERNAL_METRIC_TYPE_2) return metric_type; XFREE(MTYPE_ROUTE_MAP_COMPILED, metric_type); return NULL; } /* Free route map's compiled `set metric-type' value. */ static void route_set_metric_type_free(void *rule) { XFREE(MTYPE_ROUTE_MAP_COMPILED, rule); } /* Set metric rule structure. */ struct route_map_rule_cmd route_set_metric_type_cmd = { "metric-type", route_set_metric_type, route_set_metric_type_compile, route_set_metric_type_free, }; static enum route_map_cmd_result_t route_set_tag(void *rule, const struct prefix *prefix, route_map_object_t type, void *object) { route_tag_t *tag; struct external_info *ei; if (type == RMAP_OSPF) { tag = rule; ei = object; /* Set tag value */ ei->tag = *tag; } return RMAP_OKAY; } /* Route map commands for tag set. */ static struct route_map_rule_cmd route_set_tag_cmd = { "tag", route_set_tag, route_map_rule_tag_compile, route_map_rule_tag_free, }; DEFUN (set_metric_type, set_metric_type_cmd, "set metric-type ", SET_STR "Type of metric for destination routing protocol\n" "OSPF[6] external type 1 metric\n" "OSPF[6] external type 2 metric\n") { char *ext = argv[2]->text; return generic_set_add(vty, VTY_GET_CONTEXT(route_map_index), "metric-type", ext); } DEFUN (no_set_metric_type, no_set_metric_type_cmd, "no set metric-type []", NO_STR SET_STR "Type of metric for destination routing protocol\n" "OSPF[6] external type 1 metric\n" "OSPF[6] external type 2 metric\n") { char *ext = (argc == 4) ? argv[3]->text : NULL; return generic_set_delete(vty, VTY_GET_CONTEXT(route_map_index), "metric-type", ext); } /* Route-map init */ void ospf_route_map_init(void) { route_map_init(); route_map_add_hook(ospf_route_map_update); route_map_delete_hook(ospf_route_map_update); route_map_event_hook(ospf_route_map_event); route_map_set_metric_hook(generic_set_add); route_map_no_set_metric_hook(generic_set_delete); route_map_match_ip_next_hop_hook(generic_match_add); route_map_no_match_ip_next_hop_hook(generic_match_delete); route_map_match_interface_hook(generic_match_add); route_map_no_match_interface_hook(generic_match_delete); route_map_match_ip_address_hook(generic_match_add); route_map_no_match_ip_address_hook(generic_match_delete); route_map_match_ip_address_prefix_list_hook(generic_match_add); route_map_no_match_ip_address_prefix_list_hook(generic_match_delete); route_map_match_ip_next_hop_prefix_list_hook(generic_match_add); route_map_no_match_ip_next_hop_prefix_list_hook(generic_match_delete); route_map_match_ip_next_hop_type_hook(generic_match_add); route_map_no_match_ip_next_hop_type_hook(generic_match_delete); route_map_match_tag_hook(generic_match_add); route_map_no_match_tag_hook(generic_match_delete); route_map_set_metric_hook(generic_set_add); route_map_no_set_metric_hook(generic_set_delete); route_map_set_tag_hook(generic_set_add); route_map_no_set_tag_hook(generic_set_delete); route_map_install_match(&route_match_ip_nexthop_cmd); route_map_install_match(&route_match_ip_next_hop_prefix_list_cmd); route_map_install_match(&route_match_ip_address_cmd); route_map_install_match(&route_match_ip_address_prefix_list_cmd); route_map_install_match(&route_match_ip_next_hop_type_cmd); route_map_install_match(&route_match_interface_cmd); route_map_install_match(&route_match_tag_cmd); route_map_install_set(&route_set_metric_cmd); route_map_install_set(&route_set_metric_type_cmd); route_map_install_set(&route_set_tag_cmd); install_element(RMAP_NODE, &set_metric_type_cmd); install_element(RMAP_NODE, &no_set_metric_type_cmd); } frr-7.2.1/ospfd/ospf_snmp.c0000644000000000000000000021074613610377563012511 00000000000000/* OSPFv2 SNMP support * Copyright (C) 2005 6WIND * Copyright (C) 2000 IP Infusion Inc. * * Written by Kunihiro Ishiguro * * This file is part of GNU Zebra. * * GNU Zebra is free software; you can redistribute it and/or modify it * under the terms of the GNU General Public License as published by the * Free Software Foundation; either version 2, or (at your option) any * later version. * * GNU Zebra 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; see the file COPYING; if not, write to the Free Software * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA */ #include #include #include #include "if.h" #include "log.h" #include "prefix.h" #include "table.h" #include "command.h" #include "memory.h" #include "smux.h" #include "libfrr.h" #include "version.h" #include "ospfd/ospfd.h" #include "ospfd/ospf_interface.h" #include "ospfd/ospf_asbr.h" #include "ospfd/ospf_lsa.h" #include "ospfd/ospf_lsdb.h" #include "ospfd/ospf_abr.h" #include "ospfd/ospf_neighbor.h" #include "ospfd/ospf_nsm.h" #include "ospfd/ospf_flood.h" #include "ospfd/ospf_ism.h" #include "ospfd/ospf_dump.h" #include "ospfd/ospf_route.h" #include "ospfd/ospf_zebra.h" /* OSPF2-MIB. */ #define OSPF2MIB 1,3,6,1,2,1,14 /* OSPF MIB General Group values. */ #define OSPFROUTERID 1 #define OSPFADMINSTAT 2 #define OSPFVERSIONNUMBER 3 #define OSPFAREABDRRTRSTATUS 4 #define OSPFASBDRRTRSTATUS 5 #define OSPFEXTERNLSACOUNT 6 #define OSPFEXTERNLSACKSUMSUM 7 #define OSPFTOSSUPPORT 8 #define OSPFORIGINATENEWLSAS 9 #define OSPFRXNEWLSAS 10 #define OSPFEXTLSDBLIMIT 11 #define OSPFMULTICASTEXTENSIONS 12 #define OSPFEXITOVERFLOWINTERVAL 13 #define OSPFDEMANDEXTENSIONS 14 /* OSPF MIB ospfAreaTable. */ #define OSPFAREAID 1 #define OSPFAUTHTYPE 2 #define OSPFIMPORTASEXTERN 3 #define OSPFSPFRUNS 4 #define OSPFAREABDRRTRCOUNT 5 #define OSPFASBDRRTRCOUNT 6 #define OSPFAREALSACOUNT 7 #define OSPFAREALSACKSUMSUM 8 #define OSPFAREASUMMARY 9 #define OSPFAREASTATUS 10 /* OSPF MIB ospfStubAreaTable. */ #define OSPFSTUBAREAID 1 #define OSPFSTUBTOS 2 #define OSPFSTUBMETRIC 3 #define OSPFSTUBSTATUS 4 #define OSPFSTUBMETRICTYPE 5 /* OSPF MIB ospfLsdbTable. */ #define OSPFLSDBAREAID 1 #define OSPFLSDBTYPE 2 #define OSPFLSDBLSID 3 #define OSPFLSDBROUTERID 4 #define OSPFLSDBSEQUENCE 5 #define OSPFLSDBAGE 6 #define OSPFLSDBCHECKSUM 7 #define OSPFLSDBADVERTISEMENT 8 /* OSPF MIB ospfAreaRangeTable. */ #define OSPFAREARANGEAREAID 1 #define OSPFAREARANGENET 2 #define OSPFAREARANGEMASK 3 #define OSPFAREARANGESTATUS 4 #define OSPFAREARANGEEFFECT 5 /* OSPF MIB ospfHostTable. */ #define OSPFHOSTIPADDRESS 1 #define OSPFHOSTTOS 2 #define OSPFHOSTMETRIC 3 #define OSPFHOSTSTATUS 4 #define OSPFHOSTAREAID 5 /* OSPF MIB ospfIfTable. */ #define OSPFIFIPADDRESS 1 #define OSPFADDRESSLESSIF 2 #define OSPFIFAREAID 3 #define OSPFIFTYPE 4 #define OSPFIFADMINSTAT 5 #define OSPFIFRTRPRIORITY 6 #define OSPFIFTRANSITDELAY 7 #define OSPFIFRETRANSINTERVAL 8 #define OSPFIFHELLOINTERVAL 9 #define OSPFIFRTRDEADINTERVAL 10 #define OSPFIFPOLLINTERVAL 11 #define OSPFIFSTATE 12 #define OSPFIFDESIGNATEDROUTER 13 #define OSPFIFBACKUPDESIGNATEDROUTER 14 #define OSPFIFEVENTS 15 #define OSPFIFAUTHKEY 16 #define OSPFIFSTATUS 17 #define OSPFIFMULTICASTFORWARDING 18 #define OSPFIFDEMAND 19 #define OSPFIFAUTHTYPE 20 /* OSPF MIB ospfIfMetricTable. */ #define OSPFIFMETRICIPADDRESS 1 #define OSPFIFMETRICADDRESSLESSIF 2 #define OSPFIFMETRICTOS 3 #define OSPFIFMETRICVALUE 4 #define OSPFIFMETRICSTATUS 5 /* OSPF MIB ospfVirtIfTable. */ #define OSPFVIRTIFAREAID 1 #define OSPFVIRTIFNEIGHBOR 2 #define OSPFVIRTIFTRANSITDELAY 3 #define OSPFVIRTIFRETRANSINTERVAL 4 #define OSPFVIRTIFHELLOINTERVAL 5 #define OSPFVIRTIFRTRDEADINTERVAL 6 #define OSPFVIRTIFSTATE 7 #define OSPFVIRTIFEVENTS 8 #define OSPFVIRTIFAUTHKEY 9 #define OSPFVIRTIFSTATUS 10 #define OSPFVIRTIFAUTHTYPE 11 /* OSPF MIB ospfNbrTable. */ #define OSPFNBRIPADDR 1 #define OSPFNBRADDRESSLESSINDEX 2 #define OSPFNBRRTRID 3 #define OSPFNBROPTIONS 4 #define OSPFNBRPRIORITY 5 #define OSPFNBRSTATE 6 #define OSPFNBREVENTS 7 #define OSPFNBRLSRETRANSQLEN 8 #define OSPFNBMANBRSTATUS 9 #define OSPFNBMANBRPERMANENCE 10 #define OSPFNBRHELLOSUPPRESSED 11 /* OSPF MIB ospfVirtNbrTable. */ #define OSPFVIRTNBRAREA 1 #define OSPFVIRTNBRRTRID 2 #define OSPFVIRTNBRIPADDR 3 #define OSPFVIRTNBROPTIONS 4 #define OSPFVIRTNBRSTATE 5 #define OSPFVIRTNBREVENTS 6 #define OSPFVIRTNBRLSRETRANSQLEN 7 #define OSPFVIRTNBRHELLOSUPPRESSED 8 /* OSPF MIB ospfExtLsdbTable. */ #define OSPFEXTLSDBTYPE 1 #define OSPFEXTLSDBLSID 2 #define OSPFEXTLSDBROUTERID 3 #define OSPFEXTLSDBSEQUENCE 4 #define OSPFEXTLSDBAGE 5 #define OSPFEXTLSDBCHECKSUM 6 #define OSPFEXTLSDBADVERTISEMENT 7 /* OSPF MIB ospfAreaAggregateTable. */ #define OSPFAREAAGGREGATEAREAID 1 #define OSPFAREAAGGREGATELSDBTYPE 2 #define OSPFAREAAGGREGATENET 3 #define OSPFAREAAGGREGATEMASK 4 #define OSPFAREAAGGREGATESTATUS 5 #define OSPFAREAAGGREGATEEFFECT 6 /* SYNTAX Status from OSPF-MIB. */ #define OSPF_STATUS_ENABLED 1 #define OSPF_STATUS_DISABLED 2 /* SNMP value hack. */ #define COUNTER ASN_COUNTER #define INTEGER ASN_INTEGER #define GAUGE ASN_GAUGE #define TIMETICKS ASN_TIMETICKS #define IPADDRESS ASN_IPADDRESS #define STRING ASN_OCTET_STR /* Because DR/DROther values are exhanged wrt RFC */ #define ISM_SNMP(x) \ (((x) == ISM_DROther) ? ISM_DR : ((x) == ISM_DR) ? ISM_DROther : (x)) /* Declare static local variables for convenience. */ SNMP_LOCAL_VARIABLES /* OSPF-MIB instances. */ static oid ospf_oid[] = {OSPF2MIB}; static oid ospf_trap_oid[] = {OSPF2MIB, 16, 2}; /* Not reverse mappable! */ /* IP address 0.0.0.0. */ static struct in_addr ospf_empty_addr = {.s_addr = 0}; /* Hook functions. */ static uint8_t *ospfGeneralGroup(struct variable *, oid *, size_t *, int, size_t *, WriteMethod **); static uint8_t *ospfAreaEntry(struct variable *, oid *, size_t *, int, size_t *, WriteMethod **); static uint8_t *ospfStubAreaEntry(struct variable *, oid *, size_t *, int, size_t *, WriteMethod **); static uint8_t *ospfLsdbEntry(struct variable *, oid *, size_t *, int, size_t *, WriteMethod **); static uint8_t *ospfAreaRangeEntry(struct variable *, oid *, size_t *, int, size_t *, WriteMethod **); static uint8_t *ospfHostEntry(struct variable *, oid *, size_t *, int, size_t *, WriteMethod **); static uint8_t *ospfIfEntry(struct variable *, oid *, size_t *, int, size_t *, WriteMethod **); static uint8_t *ospfIfMetricEntry(struct variable *, oid *, size_t *, int, size_t *, WriteMethod **); static uint8_t *ospfVirtIfEntry(struct variable *, oid *, size_t *, int, size_t *, WriteMethod **); static uint8_t *ospfNbrEntry(struct variable *, oid *, size_t *, int, size_t *, WriteMethod **); static uint8_t *ospfVirtNbrEntry(struct variable *, oid *, size_t *, int, size_t *, WriteMethod **); static uint8_t *ospfExtLsdbEntry(struct variable *, oid *, size_t *, int, size_t *, WriteMethod **); static uint8_t *ospfAreaAggregateEntry(struct variable *, oid *, size_t *, int, size_t *, WriteMethod **); static struct variable ospf_variables[] = { /* OSPF general variables */ {OSPFROUTERID, IPADDRESS, RWRITE, ospfGeneralGroup, 2, {1, 1}}, {OSPFADMINSTAT, INTEGER, RWRITE, ospfGeneralGroup, 2, {1, 2}}, {OSPFVERSIONNUMBER, INTEGER, RONLY, ospfGeneralGroup, 2, {1, 3}}, {OSPFAREABDRRTRSTATUS, INTEGER, RONLY, ospfGeneralGroup, 2, {1, 4}}, {OSPFASBDRRTRSTATUS, INTEGER, RWRITE, ospfGeneralGroup, 2, {1, 5}}, {OSPFEXTERNLSACOUNT, GAUGE, RONLY, ospfGeneralGroup, 2, {1, 6}}, {OSPFEXTERNLSACKSUMSUM, INTEGER, RONLY, ospfGeneralGroup, 2, {1, 7}}, {OSPFTOSSUPPORT, INTEGER, RWRITE, ospfGeneralGroup, 2, {1, 8}}, {OSPFORIGINATENEWLSAS, COUNTER, RONLY, ospfGeneralGroup, 2, {1, 9}}, {OSPFRXNEWLSAS, COUNTER, RONLY, ospfGeneralGroup, 2, {1, 10}}, {OSPFEXTLSDBLIMIT, INTEGER, RWRITE, ospfGeneralGroup, 2, {1, 11}}, {OSPFMULTICASTEXTENSIONS, INTEGER, RWRITE, ospfGeneralGroup, 2, {1, 12}}, {OSPFEXITOVERFLOWINTERVAL, INTEGER, RWRITE, ospfGeneralGroup, 2, {1, 13}}, {OSPFDEMANDEXTENSIONS, INTEGER, RWRITE, ospfGeneralGroup, 2, {1, 14}}, /* OSPF area data structure. */ {OSPFAREAID, IPADDRESS, RONLY, ospfAreaEntry, 3, {2, 1, 1}}, {OSPFAUTHTYPE, INTEGER, RWRITE, ospfAreaEntry, 3, {2, 1, 2}}, {OSPFIMPORTASEXTERN, INTEGER, RWRITE, ospfAreaEntry, 3, {2, 1, 3}}, {OSPFSPFRUNS, COUNTER, RONLY, ospfAreaEntry, 3, {2, 1, 4}}, {OSPFAREABDRRTRCOUNT, GAUGE, RONLY, ospfAreaEntry, 3, {2, 1, 5}}, {OSPFASBDRRTRCOUNT, GAUGE, RONLY, ospfAreaEntry, 3, {2, 1, 6}}, {OSPFAREALSACOUNT, GAUGE, RONLY, ospfAreaEntry, 3, {2, 1, 7}}, {OSPFAREALSACKSUMSUM, INTEGER, RONLY, ospfAreaEntry, 3, {2, 1, 8}}, {OSPFAREASUMMARY, INTEGER, RWRITE, ospfAreaEntry, 3, {2, 1, 9}}, {OSPFAREASTATUS, INTEGER, RWRITE, ospfAreaEntry, 3, {2, 1, 10}}, /* OSPF stub area information. */ {OSPFSTUBAREAID, IPADDRESS, RONLY, ospfStubAreaEntry, 3, {3, 1, 1}}, {OSPFSTUBTOS, INTEGER, RONLY, ospfStubAreaEntry, 3, {3, 1, 2}}, {OSPFSTUBMETRIC, INTEGER, RWRITE, ospfStubAreaEntry, 3, {3, 1, 3}}, {OSPFSTUBSTATUS, INTEGER, RWRITE, ospfStubAreaEntry, 3, {3, 1, 4}}, {OSPFSTUBMETRICTYPE, INTEGER, RWRITE, ospfStubAreaEntry, 3, {3, 1, 5}}, /* OSPF link state database. */ {OSPFLSDBAREAID, IPADDRESS, RONLY, ospfLsdbEntry, 3, {4, 1, 1}}, {OSPFLSDBTYPE, INTEGER, RONLY, ospfLsdbEntry, 3, {4, 1, 2}}, {OSPFLSDBLSID, IPADDRESS, RONLY, ospfLsdbEntry, 3, {4, 1, 3}}, {OSPFLSDBROUTERID, IPADDRESS, RONLY, ospfLsdbEntry, 3, {4, 1, 4}}, {OSPFLSDBSEQUENCE, INTEGER, RONLY, ospfLsdbEntry, 3, {4, 1, 5}}, {OSPFLSDBAGE, INTEGER, RONLY, ospfLsdbEntry, 3, {4, 1, 6}}, {OSPFLSDBCHECKSUM, INTEGER, RONLY, ospfLsdbEntry, 3, {4, 1, 7}}, {OSPFLSDBADVERTISEMENT, STRING, RONLY, ospfLsdbEntry, 3, {4, 1, 8}}, /* Area range table. */ {OSPFAREARANGEAREAID, IPADDRESS, RONLY, ospfAreaRangeEntry, 3, {5, 1, 1}}, {OSPFAREARANGENET, IPADDRESS, RONLY, ospfAreaRangeEntry, 3, {5, 1, 2}}, {OSPFAREARANGEMASK, IPADDRESS, RWRITE, ospfAreaRangeEntry, 3, {5, 1, 3}}, {OSPFAREARANGESTATUS, INTEGER, RWRITE, ospfAreaRangeEntry, 3, {5, 1, 4}}, {OSPFAREARANGEEFFECT, INTEGER, RWRITE, ospfAreaRangeEntry, 3, {5, 1, 5}}, /* OSPF host table. */ {OSPFHOSTIPADDRESS, IPADDRESS, RONLY, ospfHostEntry, 3, {6, 1, 1}}, {OSPFHOSTTOS, INTEGER, RONLY, ospfHostEntry, 3, {6, 1, 2}}, {OSPFHOSTMETRIC, INTEGER, RWRITE, ospfHostEntry, 3, {6, 1, 3}}, {OSPFHOSTSTATUS, INTEGER, RWRITE, ospfHostEntry, 3, {6, 1, 4}}, {OSPFHOSTAREAID, IPADDRESS, RONLY, ospfHostEntry, 3, {6, 1, 5}}, /* OSPF interface table. */ {OSPFIFIPADDRESS, IPADDRESS, RONLY, ospfIfEntry, 3, {7, 1, 1}}, {OSPFADDRESSLESSIF, INTEGER, RONLY, ospfIfEntry, 3, {7, 1, 2}}, {OSPFIFAREAID, IPADDRESS, RWRITE, ospfIfEntry, 3, {7, 1, 3}}, {OSPFIFTYPE, INTEGER, RWRITE, ospfIfEntry, 3, {7, 1, 4}}, {OSPFIFADMINSTAT, INTEGER, RWRITE, ospfIfEntry, 3, {7, 1, 5}}, {OSPFIFRTRPRIORITY, INTEGER, RWRITE, ospfIfEntry, 3, {7, 1, 6}}, {OSPFIFTRANSITDELAY, INTEGER, RWRITE, ospfIfEntry, 3, {7, 1, 7}}, {OSPFIFRETRANSINTERVAL, INTEGER, RWRITE, ospfIfEntry, 3, {7, 1, 8}}, {OSPFIFHELLOINTERVAL, INTEGER, RWRITE, ospfIfEntry, 3, {7, 1, 9}}, {OSPFIFRTRDEADINTERVAL, INTEGER, RWRITE, ospfIfEntry, 3, {7, 1, 10}}, {OSPFIFPOLLINTERVAL, INTEGER, RWRITE, ospfIfEntry, 3, {7, 1, 11}}, {OSPFIFSTATE, INTEGER, RONLY, ospfIfEntry, 3, {7, 1, 12}}, {OSPFIFDESIGNATEDROUTER, IPADDRESS, RONLY, ospfIfEntry, 3, {7, 1, 13}}, {OSPFIFBACKUPDESIGNATEDROUTER, IPADDRESS, RONLY, ospfIfEntry, 3, {7, 1, 14}}, {OSPFIFEVENTS, COUNTER, RONLY, ospfIfEntry, 3, {7, 1, 15}}, {OSPFIFAUTHKEY, STRING, RWRITE, ospfIfEntry, 3, {7, 1, 16}}, {OSPFIFSTATUS, INTEGER, RWRITE, ospfIfEntry, 3, {7, 1, 17}}, {OSPFIFMULTICASTFORWARDING, INTEGER, RWRITE, ospfIfEntry, 3, {7, 1, 18}}, {OSPFIFDEMAND, INTEGER, RWRITE, ospfIfEntry, 3, {7, 1, 19}}, {OSPFIFAUTHTYPE, INTEGER, RWRITE, ospfIfEntry, 3, {7, 1, 20}}, /* OSPF interface metric table. */ {OSPFIFMETRICIPADDRESS, IPADDRESS, RONLY, ospfIfMetricEntry, 3, {8, 1, 1}}, {OSPFIFMETRICADDRESSLESSIF, INTEGER, RONLY, ospfIfMetricEntry, 3, {8, 1, 2}}, {OSPFIFMETRICTOS, INTEGER, RONLY, ospfIfMetricEntry, 3, {8, 1, 3}}, {OSPFIFMETRICVALUE, INTEGER, RWRITE, ospfIfMetricEntry, 3, {8, 1, 4}}, {OSPFIFMETRICSTATUS, INTEGER, RWRITE, ospfIfMetricEntry, 3, {8, 1, 5}}, /* OSPF virtual interface table. */ {OSPFVIRTIFAREAID, IPADDRESS, RONLY, ospfVirtIfEntry, 3, {9, 1, 1}}, {OSPFVIRTIFNEIGHBOR, IPADDRESS, RONLY, ospfVirtIfEntry, 3, {9, 1, 2}}, {OSPFVIRTIFTRANSITDELAY, INTEGER, RWRITE, ospfVirtIfEntry, 3, {9, 1, 3}}, {OSPFVIRTIFRETRANSINTERVAL, INTEGER, RWRITE, ospfVirtIfEntry, 3, {9, 1, 4}}, {OSPFVIRTIFHELLOINTERVAL, INTEGER, RWRITE, ospfVirtIfEntry, 3, {9, 1, 5}}, {OSPFVIRTIFRTRDEADINTERVAL, INTEGER, RWRITE, ospfVirtIfEntry, 3, {9, 1, 6}}, {OSPFVIRTIFSTATE, INTEGER, RONLY, ospfVirtIfEntry, 3, {9, 1, 7}}, {OSPFVIRTIFEVENTS, COUNTER, RONLY, ospfVirtIfEntry, 3, {9, 1, 8}}, {OSPFVIRTIFAUTHKEY, STRING, RWRITE, ospfVirtIfEntry, 3, {9, 1, 9}}, {OSPFVIRTIFSTATUS, INTEGER, RWRITE, ospfVirtIfEntry, 3, {9, 1, 10}}, {OSPFVIRTIFAUTHTYPE, INTEGER, RWRITE, ospfVirtIfEntry, 3, {9, 1, 11}}, /* OSPF neighbor table. */ {OSPFNBRIPADDR, IPADDRESS, RONLY, ospfNbrEntry, 3, {10, 1, 1}}, {OSPFNBRADDRESSLESSINDEX, INTEGER, RONLY, ospfNbrEntry, 3, {10, 1, 2}}, {OSPFNBRRTRID, IPADDRESS, RONLY, ospfNbrEntry, 3, {10, 1, 3}}, {OSPFNBROPTIONS, INTEGER, RONLY, ospfNbrEntry, 3, {10, 1, 4}}, {OSPFNBRPRIORITY, INTEGER, RWRITE, ospfNbrEntry, 3, {10, 1, 5}}, {OSPFNBRSTATE, INTEGER, RONLY, ospfNbrEntry, 3, {10, 1, 6}}, {OSPFNBREVENTS, COUNTER, RONLY, ospfNbrEntry, 3, {10, 1, 7}}, {OSPFNBRLSRETRANSQLEN, GAUGE, RONLY, ospfNbrEntry, 3, {10, 1, 8}}, {OSPFNBMANBRSTATUS, INTEGER, RWRITE, ospfNbrEntry, 3, {10, 1, 9}}, {OSPFNBMANBRPERMANENCE, INTEGER, RONLY, ospfNbrEntry, 3, {10, 1, 10}}, {OSPFNBRHELLOSUPPRESSED, INTEGER, RONLY, ospfNbrEntry, 3, {10, 1, 11}}, /* OSPF virtual neighbor table. */ {OSPFVIRTNBRAREA, IPADDRESS, RONLY, ospfVirtNbrEntry, 3, {11, 1, 1}}, {OSPFVIRTNBRRTRID, IPADDRESS, RONLY, ospfVirtNbrEntry, 3, {11, 1, 2}}, {OSPFVIRTNBRIPADDR, IPADDRESS, RONLY, ospfVirtNbrEntry, 3, {11, 1, 3}}, {OSPFVIRTNBROPTIONS, INTEGER, RONLY, ospfVirtNbrEntry, 3, {11, 1, 4}}, {OSPFVIRTNBRSTATE, INTEGER, RONLY, ospfVirtNbrEntry, 3, {11, 1, 5}}, {OSPFVIRTNBREVENTS, COUNTER, RONLY, ospfVirtNbrEntry, 3, {11, 1, 6}}, {OSPFVIRTNBRLSRETRANSQLEN, INTEGER, RONLY, ospfVirtNbrEntry, 3, {11, 1, 7}}, {OSPFVIRTNBRHELLOSUPPRESSED, INTEGER, RONLY, ospfVirtNbrEntry, 3, {11, 1, 8}}, /* OSPF link state database, external. */ {OSPFEXTLSDBTYPE, INTEGER, RONLY, ospfExtLsdbEntry, 3, {12, 1, 1}}, {OSPFEXTLSDBLSID, IPADDRESS, RONLY, ospfExtLsdbEntry, 3, {12, 1, 2}}, {OSPFEXTLSDBROUTERID, IPADDRESS, RONLY, ospfExtLsdbEntry, 3, {12, 1, 3}}, {OSPFEXTLSDBSEQUENCE, INTEGER, RONLY, ospfExtLsdbEntry, 3, {12, 1, 4}}, {OSPFEXTLSDBAGE, INTEGER, RONLY, ospfExtLsdbEntry, 3, {12, 1, 5}}, {OSPFEXTLSDBCHECKSUM, INTEGER, RONLY, ospfExtLsdbEntry, 3, {12, 1, 6}}, {OSPFEXTLSDBADVERTISEMENT, STRING, RONLY, ospfExtLsdbEntry, 3, {12, 1, 7}}, /* OSPF area aggregate table. */ {OSPFAREAAGGREGATEAREAID, IPADDRESS, RONLY, ospfAreaAggregateEntry, 3, {14, 1, 1}}, {OSPFAREAAGGREGATELSDBTYPE, INTEGER, RONLY, ospfAreaAggregateEntry, 3, {14, 1, 2}}, {OSPFAREAAGGREGATENET, IPADDRESS, RONLY, ospfAreaAggregateEntry, 3, {14, 1, 3}}, {OSPFAREAAGGREGATEMASK, IPADDRESS, RONLY, ospfAreaAggregateEntry, 3, {14, 1, 4}}, {OSPFAREAAGGREGATESTATUS, INTEGER, RWRITE, ospfAreaAggregateEntry, 3, {14, 1, 5}}, {OSPFAREAAGGREGATEEFFECT, INTEGER, RWRITE, ospfAreaAggregateEntry, 3, {14, 1, 6}}}; /* The administrative status of OSPF. When OSPF is enbled on at least one interface return 1. */ static int ospf_admin_stat(struct ospf *ospf) { struct listnode *node; struct ospf_interface *oi; if (ospf == NULL) return 0; for (ALL_LIST_ELEMENTS_RO(ospf->oiflist, node, oi)) if (oi && oi->address) return 1; return 0; } static uint8_t *ospfGeneralGroup(struct variable *v, oid *name, size_t *length, int exact, size_t *var_len, WriteMethod **write_method) { struct ospf *ospf; ospf = ospf_lookup_by_vrf_id(VRF_DEFAULT); /* Check whether the instance identifier is valid */ if (smux_header_generic(v, name, length, exact, var_len, write_method) == MATCH_FAILED) return NULL; /* Return the current value of the variable */ switch (v->magic) { case OSPFROUTERID: /* 1 */ /* Router-ID of this OSPF instance. */ if (ospf) return SNMP_IPADDRESS(ospf->router_id); else return SNMP_IPADDRESS(ospf_empty_addr); break; case OSPFADMINSTAT: /* 2 */ /* The administrative status of OSPF in the router. */ if (ospf_admin_stat(ospf)) return SNMP_INTEGER(OSPF_STATUS_ENABLED); else return SNMP_INTEGER(OSPF_STATUS_DISABLED); break; case OSPFVERSIONNUMBER: /* 3 */ /* OSPF version 2. */ return SNMP_INTEGER(OSPF_VERSION); break; case OSPFAREABDRRTRSTATUS: /* 4 */ /* Area Border router status. */ if (ospf && CHECK_FLAG(ospf->flags, OSPF_FLAG_ABR)) return SNMP_INTEGER(SNMP_TRUE); else return SNMP_INTEGER(SNMP_FALSE); break; case OSPFASBDRRTRSTATUS: /* 5 */ /* AS Border router status. */ if (ospf && CHECK_FLAG(ospf->flags, OSPF_FLAG_ASBR)) return SNMP_INTEGER(SNMP_TRUE); else return SNMP_INTEGER(SNMP_FALSE); break; case OSPFEXTERNLSACOUNT: /* 6 */ /* External LSA counts. */ if (ospf) return SNMP_INTEGER(ospf_lsdb_count_all(ospf->lsdb)); else return SNMP_INTEGER(0); break; case OSPFEXTERNLSACKSUMSUM: /* 7 */ /* External LSA checksum. */ return SNMP_INTEGER(0); break; case OSPFTOSSUPPORT: /* 8 */ /* TOS is not supported. */ return SNMP_INTEGER(SNMP_FALSE); break; case OSPFORIGINATENEWLSAS: /* 9 */ /* The number of new link-state advertisements. */ if (ospf) return SNMP_INTEGER(ospf->lsa_originate_count); else return SNMP_INTEGER(0); break; case OSPFRXNEWLSAS: /* 10 */ /* The number of link-state advertisements received determined to be new instantiations. */ if (ospf) return SNMP_INTEGER(ospf->rx_lsa_count); else return SNMP_INTEGER(0); break; case OSPFEXTLSDBLIMIT: /* 11 */ /* There is no limit for the number of non-default AS-external-LSAs. */ return SNMP_INTEGER(-1); break; case OSPFMULTICASTEXTENSIONS: /* 12 */ /* Multicast Extensions to OSPF is not supported. */ return SNMP_INTEGER(0); break; case OSPFEXITOVERFLOWINTERVAL: /* 13 */ /* Overflow is not supported. */ return SNMP_INTEGER(0); break; case OSPFDEMANDEXTENSIONS: /* 14 */ /* Demand routing is not supported. */ return SNMP_INTEGER(SNMP_FALSE); break; default: return NULL; } return NULL; } static struct ospf_area * ospf_area_lookup_next(struct ospf *ospf, struct in_addr *area_id, int first) { struct ospf_area *area; struct listnode *node; if (ospf == NULL) return NULL; if (first) { node = listhead(ospf->areas); if (node) { area = listgetdata(node); *area_id = area->area_id; return area; } return NULL; } for (ALL_LIST_ELEMENTS_RO(ospf->areas, node, area)) { if (ntohl(area->area_id.s_addr) > ntohl(area_id->s_addr)) { *area_id = area->area_id; return area; } } return NULL; } static struct ospf_area *ospfAreaLookup(struct variable *v, oid name[], size_t *length, struct in_addr *addr, int exact) { struct ospf *ospf; struct ospf_area *area; int len; ospf = ospf_lookup_by_vrf_id(VRF_DEFAULT); if (ospf == NULL) return NULL; if (exact) { /* Length is insufficient to lookup OSPF area. */ if (*length - v->namelen != sizeof(struct in_addr)) return NULL; oid2in_addr(name + v->namelen, sizeof(struct in_addr), addr); area = ospf_area_lookup_by_area_id(ospf, *addr); return area; } else { len = *length - v->namelen; if (len > 4) len = 4; oid2in_addr(name + v->namelen, len, addr); area = ospf_area_lookup_next(ospf, addr, len == 0 ? 1 : 0); if (area == NULL) return NULL; oid_copy_addr(name + v->namelen, addr, sizeof(struct in_addr)); *length = sizeof(struct in_addr) + v->namelen; return area; } return NULL; } static uint8_t *ospfAreaEntry(struct variable *v, oid *name, size_t *length, int exact, size_t *var_len, WriteMethod **write_method) { struct ospf_area *area; struct in_addr addr; if (smux_header_table(v, name, length, exact, var_len, write_method) == MATCH_FAILED) return NULL; memset(&addr, 0, sizeof(struct in_addr)); area = ospfAreaLookup(v, name, length, &addr, exact); if (!area) return NULL; /* Return the current value of the variable */ switch (v->magic) { case OSPFAREAID: /* 1 */ return SNMP_IPADDRESS(area->area_id); break; case OSPFAUTHTYPE: /* 2 */ return SNMP_INTEGER(area->auth_type); break; case OSPFIMPORTASEXTERN: /* 3 */ return SNMP_INTEGER(area->external_routing + 1); break; case OSPFSPFRUNS: /* 4 */ return SNMP_INTEGER(area->spf_calculation); break; case OSPFAREABDRRTRCOUNT: /* 5 */ return SNMP_INTEGER(area->abr_count); break; case OSPFASBDRRTRCOUNT: /* 6 */ return SNMP_INTEGER(area->asbr_count); break; case OSPFAREALSACOUNT: /* 7 */ return SNMP_INTEGER(area->lsdb->total); break; case OSPFAREALSACKSUMSUM: /* 8 */ return SNMP_INTEGER(0); break; case OSPFAREASUMMARY: /* 9 */ #define OSPF_noAreaSummary 1 #define OSPF_sendAreaSummary 2 if (area->no_summary) return SNMP_INTEGER(OSPF_noAreaSummary); else return SNMP_INTEGER(OSPF_sendAreaSummary); break; case OSPFAREASTATUS: /* 10 */ return SNMP_INTEGER(SNMP_VALID); break; default: return NULL; break; } return NULL; } static struct ospf_area *ospf_stub_area_lookup_next(struct in_addr *area_id, int first) { struct ospf_area *area; struct listnode *node; struct ospf *ospf; ospf = ospf_lookup_by_vrf_id(VRF_DEFAULT); if (ospf == NULL) return NULL; for (ALL_LIST_ELEMENTS_RO(ospf->areas, node, area)) { if (area->external_routing == OSPF_AREA_STUB) { if (first) { *area_id = area->area_id; return area; } else if (ntohl(area->area_id.s_addr) > ntohl(area_id->s_addr)) { *area_id = area->area_id; return area; } } } return NULL; } static struct ospf_area *ospfStubAreaLookup(struct variable *v, oid name[], size_t *length, struct in_addr *addr, int exact) { struct ospf *ospf; struct ospf_area *area; int len; ospf = ospf_lookup_by_vrf_id(VRF_DEFAULT); if (ospf == NULL) return NULL; /* Exact lookup. */ if (exact) { /* ospfStubAreaID + ospfStubTOS. */ if (*length != v->namelen + sizeof(struct in_addr) + 1) return NULL; /* Check ospfStubTOS is zero. */ if (name[*length - 1] != 0) return NULL; oid2in_addr(name + v->namelen, sizeof(struct in_addr), addr); area = ospf_area_lookup_by_area_id(ospf, *addr); if (area->external_routing == OSPF_AREA_STUB) return area; else return NULL; } else { len = *length - v->namelen; if (len > 4) len = 4; oid2in_addr(name + v->namelen, len, addr); area = ospf_stub_area_lookup_next(addr, len == 0 ? 1 : 0); if (area == NULL) return NULL; oid_copy_addr(name + v->namelen, addr, sizeof(struct in_addr)); /* Set TOS 0. */ name[v->namelen + sizeof(struct in_addr)] = 0; *length = v->namelen + sizeof(struct in_addr) + 1; return area; } return NULL; } static uint8_t *ospfStubAreaEntry(struct variable *v, oid *name, size_t *length, int exact, size_t *var_len, WriteMethod **write_method) { struct ospf_area *area; struct in_addr addr; if (smux_header_table(v, name, length, exact, var_len, write_method) == MATCH_FAILED) return NULL; memset(&addr, 0, sizeof(struct in_addr)); area = ospfStubAreaLookup(v, name, length, &addr, exact); if (!area) return NULL; /* Return the current value of the variable */ switch (v->magic) { case OSPFSTUBAREAID: /* 1 */ /* OSPF stub area id. */ return SNMP_IPADDRESS(area->area_id); break; case OSPFSTUBTOS: /* 2 */ /* TOS value is not supported. */ return SNMP_INTEGER(0); break; case OSPFSTUBMETRIC: /* 3 */ /* Default cost to stub area. */ return SNMP_INTEGER(area->default_cost); break; case OSPFSTUBSTATUS: /* 4 */ /* Status of the stub area. */ return SNMP_INTEGER(SNMP_VALID); break; case OSPFSTUBMETRICTYPE: /* 5 */ /* OSPF Metric type. */ #define OSPF_ospfMetric 1 #define OSPF_comparableCost 2 #define OSPF_nonComparable 3 return SNMP_INTEGER(OSPF_ospfMetric); break; default: return NULL; break; } return NULL; } static struct ospf_lsa *lsdb_lookup_next(struct ospf_area *area, uint8_t *type, int type_next, struct in_addr *ls_id, int ls_id_next, struct in_addr *router_id, int router_id_next) { struct ospf_lsa *lsa; int i; if (type_next) i = OSPF_MIN_LSA; else i = *type; /* Sanity check, if LSA type unknwon merley skip any LSA */ if ((i < OSPF_MIN_LSA) || (i >= OSPF_MAX_LSA)) { zlog_debug("Strange request with LSA type %d", i); return NULL; } for (; i < OSPF_MAX_LSA; i++) { *type = i; lsa = ospf_lsdb_lookup_by_id_next(area->lsdb, *type, *ls_id, *router_id, ls_id_next); if (lsa) return lsa; ls_id_next = 1; } return NULL; } static struct ospf_lsa *ospfLsdbLookup(struct variable *v, oid *name, size_t *length, struct in_addr *area_id, uint8_t *type, struct in_addr *ls_id, struct in_addr *router_id, int exact) { struct ospf *ospf; struct ospf_area *area; struct ospf_lsa *lsa; int len; int type_next; int ls_id_next; int router_id_next; oid *offset; int offsetlen; ospf = ospf_lookup_by_vrf_id(VRF_DEFAULT); #define OSPF_LSDB_ENTRY_OFFSET (IN_ADDR_SIZE + 1 + IN_ADDR_SIZE + IN_ADDR_SIZE) if (exact) { /* Area ID + Type + LS ID + Router ID. */ if (*length - v->namelen != OSPF_LSDB_ENTRY_OFFSET) return NULL; /* Set OID offset for Area ID. */ offset = name + v->namelen; /* Lookup area first. */ oid2in_addr(offset, IN_ADDR_SIZE, area_id); area = ospf_area_lookup_by_area_id(ospf, *area_id); if (!area) return NULL; offset += IN_ADDR_SIZE; /* Type. */ *type = *offset; offset++; /* LS ID. */ oid2in_addr(offset, IN_ADDR_SIZE, ls_id); offset += IN_ADDR_SIZE; /* Router ID. */ oid2in_addr(offset, IN_ADDR_SIZE, router_id); /* Lookup LSDB. */ return ospf_lsdb_lookup_by_id(area->lsdb, *type, *ls_id, *router_id); } else { /* Get variable length. */ offset = name + v->namelen; offsetlen = *length - v->namelen; len = offsetlen; if (len > (int)IN_ADDR_SIZE) len = IN_ADDR_SIZE; oid2in_addr(offset, len, area_id); /* First we search area. */ if (len == IN_ADDR_SIZE) area = ospf_area_lookup_by_area_id(ospf, *area_id); else area = ospf_area_lookup_next(ospf, area_id, 1); if (area == NULL) return NULL; do { /* Next we lookup type. */ offset += len; offsetlen -= len; len = offsetlen; if (len <= 0) type_next = 1; else { type_next = 0; *type = *offset; } /* LS ID. */ offset++; offsetlen--; len = offsetlen; if (len <= 0) ls_id_next = 1; else { ls_id_next = 0; if (len > (int)IN_ADDR_SIZE) len = IN_ADDR_SIZE; oid2in_addr(offset, len, ls_id); } /* Router ID. */ offset += IN_ADDR_SIZE; offsetlen -= IN_ADDR_SIZE; len = offsetlen; if (len <= 0) router_id_next = 1; else { router_id_next = 0; if (len > (int)IN_ADDR_SIZE) len = IN_ADDR_SIZE; oid2in_addr(offset, len, router_id); } lsa = lsdb_lookup_next(area, type, type_next, ls_id, ls_id_next, router_id, router_id_next); if (lsa) { /* Fill in length. */ *length = v->namelen + OSPF_LSDB_ENTRY_OFFSET; /* Fill in value. */ offset = name + v->namelen; oid_copy_addr(offset, area_id, IN_ADDR_SIZE); offset += IN_ADDR_SIZE; *offset = lsa->data->type; offset++; oid_copy_addr(offset, &lsa->data->id, IN_ADDR_SIZE); offset += IN_ADDR_SIZE; oid_copy_addr(offset, &lsa->data->adv_router, IN_ADDR_SIZE); return lsa; } } while ((area = ospf_area_lookup_next(ospf, area_id, 0)) != NULL); } return NULL; } static uint8_t *ospfLsdbEntry(struct variable *v, oid *name, size_t *length, int exact, size_t *var_len, WriteMethod **write_method) { struct ospf_lsa *lsa; struct lsa_header *lsah; struct in_addr area_id; uint8_t type; struct in_addr ls_id; struct in_addr router_id; struct ospf *ospf; if (smux_header_table(v, name, length, exact, var_len, write_method) == MATCH_FAILED) return NULL; /* INDEX { ospfLsdbAreaId, ospfLsdbType, ospfLsdbLsid, ospfLsdbRouterId } */ memset(&area_id, 0, sizeof(struct in_addr)); type = 0; memset(&ls_id, 0, sizeof(struct in_addr)); memset(&router_id, 0, sizeof(struct in_addr)); /* Check OSPF instance. */ ospf = ospf_lookup_by_vrf_id(VRF_DEFAULT); if (ospf == NULL) return NULL; lsa = ospfLsdbLookup(v, name, length, &area_id, &type, &ls_id, &router_id, exact); if (!lsa) return NULL; lsah = lsa->data; /* Return the current value of the variable */ switch (v->magic) { case OSPFLSDBAREAID: /* 1 */ return SNMP_IPADDRESS(lsa->area->area_id); break; case OSPFLSDBTYPE: /* 2 */ return SNMP_INTEGER(lsah->type); break; case OSPFLSDBLSID: /* 3 */ return SNMP_IPADDRESS(lsah->id); break; case OSPFLSDBROUTERID: /* 4 */ return SNMP_IPADDRESS(lsah->adv_router); break; case OSPFLSDBSEQUENCE: /* 5 */ return SNMP_INTEGER(lsah->ls_seqnum); break; case OSPFLSDBAGE: /* 6 */ return SNMP_INTEGER(lsah->ls_age); break; case OSPFLSDBCHECKSUM: /* 7 */ return SNMP_INTEGER(lsah->checksum); break; case OSPFLSDBADVERTISEMENT: /* 8 */ *var_len = ntohs(lsah->length); return (uint8_t *)lsah; break; default: return NULL; break; } return NULL; } static struct ospf_area_range *ospfAreaRangeLookup(struct variable *v, oid *name, size_t *length, struct in_addr *area_id, struct in_addr *range_net, int exact) { oid *offset; int offsetlen; int len; struct ospf *ospf; struct ospf_area *area; struct ospf_area_range *range; struct prefix_ipv4 p; p.family = AF_INET; p.prefixlen = IPV4_MAX_BITLEN; ospf = ospf_lookup_by_vrf_id(VRF_DEFAULT); if (exact) { /* Area ID + Range Network. */ if (v->namelen + IN_ADDR_SIZE + IN_ADDR_SIZE != *length) return NULL; /* Set OID offset for Area ID. */ offset = name + v->namelen; /* Lookup area first. */ oid2in_addr(offset, IN_ADDR_SIZE, area_id); area = ospf_area_lookup_by_area_id(ospf, *area_id); if (!area) return NULL; offset += IN_ADDR_SIZE; /* Lookup area range. */ oid2in_addr(offset, IN_ADDR_SIZE, range_net); p.prefix = *range_net; return ospf_area_range_lookup(area, &p); } else { /* Set OID offset for Area ID. */ offset = name + v->namelen; offsetlen = *length - v->namelen; len = offsetlen; if (len > (int)IN_ADDR_SIZE) len = IN_ADDR_SIZE; oid2in_addr(offset, len, area_id); /* First we search area. */ if (len == IN_ADDR_SIZE) area = ospf_area_lookup_by_area_id(ospf, *area_id); else area = ospf_area_lookup_next(ospf, area_id, len == 0 ? 1 : 0); if (area == NULL) return NULL; do { offset += IN_ADDR_SIZE; offsetlen -= IN_ADDR_SIZE; len = offsetlen; if (len < 0) len = 0; if (len > (int)IN_ADDR_SIZE) len = IN_ADDR_SIZE; oid2in_addr(offset, len, range_net); range = ospf_area_range_lookup_next(area, range_net, len == 0 ? 1 : 0); if (range) { /* Fill in length. */ *length = v->namelen + IN_ADDR_SIZE + IN_ADDR_SIZE; /* Fill in value. */ offset = name + v->namelen; oid_copy_addr(offset, area_id, IN_ADDR_SIZE); offset += IN_ADDR_SIZE; oid_copy_addr(offset, range_net, IN_ADDR_SIZE); return range; } } while ((area = ospf_area_lookup_next(ospf, area_id, 0)) != NULL); } return NULL; } static uint8_t *ospfAreaRangeEntry(struct variable *v, oid *name, size_t *length, int exact, size_t *var_len, WriteMethod **write_method) { struct ospf_area_range *range; struct in_addr area_id; struct in_addr range_net; struct in_addr mask; struct ospf *ospf; if (smux_header_table(v, name, length, exact, var_len, write_method) == MATCH_FAILED) return NULL; /* Check OSPF instance. */ ospf = ospf_lookup_by_vrf_id(VRF_DEFAULT); if (ospf == NULL) return NULL; memset(&area_id, 0, IN_ADDR_SIZE); memset(&range_net, 0, IN_ADDR_SIZE); range = ospfAreaRangeLookup(v, name, length, &area_id, &range_net, exact); if (!range) return NULL; /* Convert prefixlen to network mask format. */ masklen2ip(range->subst_masklen, &mask); /* Return the current value of the variable */ switch (v->magic) { case OSPFAREARANGEAREAID: /* 1 */ return SNMP_IPADDRESS(area_id); break; case OSPFAREARANGENET: /* 2 */ return SNMP_IPADDRESS(range_net); break; case OSPFAREARANGEMASK: /* 3 */ return SNMP_IPADDRESS(mask); break; case OSPFAREARANGESTATUS: /* 4 */ return SNMP_INTEGER(SNMP_VALID); break; case OSPFAREARANGEEFFECT: /* 5 */ #define OSPF_advertiseMatching 1 #define OSPF_doNotAdvertiseMatching 2 return SNMP_INTEGER(OSPF_advertiseMatching); break; default: return NULL; break; } return NULL; } static struct ospf_nbr_nbma *ospfHostLookup(struct variable *v, oid *name, size_t *length, struct in_addr *addr, int exact) { int len; struct ospf_nbr_nbma *nbr_nbma; struct ospf *ospf; ospf = ospf_lookup_by_vrf_id(VRF_DEFAULT); if (ospf == NULL) return NULL; if (exact) { /* INDEX { ospfHostIpAddress, ospfHostTOS } */ if (*length != v->namelen + IN_ADDR_SIZE + 1) return NULL; /* Check ospfHostTOS. */ if (name[*length - 1] != 0) return NULL; oid2in_addr(name + v->namelen, IN_ADDR_SIZE, addr); nbr_nbma = ospf_nbr_nbma_lookup(ospf, *addr); return nbr_nbma; } else { len = *length - v->namelen; if (len > 4) len = 4; oid2in_addr(name + v->namelen, len, addr); nbr_nbma = ospf_nbr_nbma_lookup_next(ospf, addr, len == 0 ? 1 : 0); if (nbr_nbma == NULL) return NULL; oid_copy_addr(name + v->namelen, addr, IN_ADDR_SIZE); /* Set TOS 0. */ name[v->namelen + IN_ADDR_SIZE] = 0; *length = v->namelen + IN_ADDR_SIZE + 1; return nbr_nbma; } return NULL; } static uint8_t *ospfHostEntry(struct variable *v, oid *name, size_t *length, int exact, size_t *var_len, WriteMethod **write_method) { struct ospf_nbr_nbma *nbr_nbma; struct ospf_interface *oi; struct in_addr addr; struct ospf *ospf; if (smux_header_table(v, name, length, exact, var_len, write_method) == MATCH_FAILED) return NULL; /* Check OSPF instance. */ ospf = ospf_lookup_by_vrf_id(VRF_DEFAULT); if (ospf == NULL) return NULL; memset(&addr, 0, sizeof(struct in_addr)); nbr_nbma = ospfHostLookup(v, name, length, &addr, exact); if (nbr_nbma == NULL) return NULL; oi = nbr_nbma->oi; /* Return the current value of the variable */ switch (v->magic) { case OSPFHOSTIPADDRESS: /* 1 */ return SNMP_IPADDRESS(nbr_nbma->addr); break; case OSPFHOSTTOS: /* 2 */ return SNMP_INTEGER(0); break; case OSPFHOSTMETRIC: /* 3 */ if (oi) return SNMP_INTEGER(oi->output_cost); else return SNMP_INTEGER(1); break; case OSPFHOSTSTATUS: /* 4 */ return SNMP_INTEGER(SNMP_VALID); break; case OSPFHOSTAREAID: /* 5 */ if (oi && oi->area) return SNMP_IPADDRESS(oi->area->area_id); else return SNMP_IPADDRESS(ospf_empty_addr); break; default: return NULL; break; } return NULL; } static struct list *ospf_snmp_iflist; struct ospf_snmp_if { struct in_addr addr; ifindex_t ifindex; struct interface *ifp; }; static struct ospf_snmp_if *ospf_snmp_if_new(void) { return XCALLOC(MTYPE_TMP, sizeof(struct ospf_snmp_if)); } static void ospf_snmp_if_free(struct ospf_snmp_if *osif) { XFREE(MTYPE_TMP, osif); } static int ospf_snmp_if_delete(struct interface *ifp) { struct listnode *node, *nnode; struct ospf_snmp_if *osif; for (ALL_LIST_ELEMENTS(ospf_snmp_iflist, node, nnode, osif)) { if (osif->ifp == ifp) { list_delete_node(ospf_snmp_iflist, node); ospf_snmp_if_free(osif); break; } } return 0; } static int ospf_snmp_if_update(struct interface *ifp) { struct listnode *node; struct listnode *pn; struct connected *ifc; struct prefix *p; struct ospf_snmp_if *osif; struct in_addr *addr; ifindex_t ifindex; ospf_snmp_if_delete(ifp); p = NULL; addr = NULL; ifindex = 0; /* Lookup first IPv4 address entry. */ for (ALL_LIST_ELEMENTS_RO(ifp->connected, node, ifc)) { p = CONNECTED_ID(ifc); if (p->family == AF_INET) { addr = &p->u.prefix4; break; } } if (!addr) ifindex = ifp->ifindex; /* Add interface to the list. */ pn = NULL; for (ALL_LIST_ELEMENTS_RO(ospf_snmp_iflist, node, osif)) { if (addr) { /* Usual interfaces --> Sort them based on interface * IPv4 addresses */ if (ntohl(osif->addr.s_addr) > ntohl(addr->s_addr)) break; } else { /* Unnumbered interfaces --> Sort them based on * interface indexes */ if (osif->addr.s_addr != 0 || osif->ifindex > ifindex) break; } pn = node; } osif = ospf_snmp_if_new(); if (addr) /* Usual interface */ { osif->addr = *addr; /* This field is used for storing ospfAddressLessIf OID value, * conform to RFC1850 OSPF-MIB specification, it must be 0 for * usual interface */ osif->ifindex = 0; } else /* Unnumbered interface */ osif->ifindex = ifindex; osif->ifp = ifp; listnode_add_after(ospf_snmp_iflist, pn, osif); return 0; } static int ospf_snmp_is_if_have_addr(struct interface *ifp) { struct listnode *nn; struct connected *ifc; /* Is this interface having any connected IPv4 address ? */ for (ALL_LIST_ELEMENTS_RO(ifp->connected, nn, ifc)) { if (CONNECTED_PREFIX(ifc)->family == AF_INET) return 1; } return 0; } static struct ospf_interface *ospf_snmp_if_lookup(struct in_addr *ifaddr, ifindex_t *ifindex) { struct listnode *node; struct ospf_snmp_if *osif; struct ospf_interface *oi = NULL; struct ospf *ospf = ospf_lookup_by_vrf_id(VRF_DEFAULT); for (ALL_LIST_ELEMENTS_RO(ospf_snmp_iflist, node, osif)) { if (ifaddr->s_addr) { if (IPV4_ADDR_SAME(&osif->addr, ifaddr)) oi = ospf_if_lookup_by_local_addr( ospf, osif->ifp, *ifaddr); } else { if (osif->ifindex == *ifindex) oi = ospf_if_lookup_by_local_addr( ospf, osif->ifp, *ifaddr); } } return oi; } static struct ospf_interface *ospf_snmp_if_lookup_next(struct in_addr *ifaddr, ifindex_t *ifindex, int ifaddr_next, ifindex_t ifindex_next) { struct ospf_snmp_if *osif; struct listnode *nn; struct ospf *ospf = ospf_lookup_by_vrf_id(VRF_DEFAULT); struct ospf_interface *oi = NULL; if (ospf == NULL) return NULL; /* No instance is specified --> Return the first OSPF interface */ if (ifaddr_next) { for (ALL_LIST_ELEMENTS_RO(ospf_snmp_iflist, nn, osif)) { osif = listgetdata(nn); *ifaddr = osif->addr; *ifindex = osif->ifindex; /* Because no instance is specified, we don't care about * the kind of * interface (usual or unnumbered), just returning the * first valid * OSPF interface */ oi = ospf_if_lookup_by_local_addr(ospf, osif->ifp, *ifaddr); if (oi) return (oi); } return NULL; } /* An instance is specified --> Return the next OSPF interface */ for (ALL_LIST_ELEMENTS_RO(ospf_snmp_iflist, nn, osif)) { /* Usual interface */ if (ifaddr->s_addr) { /* The interface must have valid AF_INET connected * address */ /* it must have lager IPv4 address value than the lookup * entry */ if ((ospf_snmp_is_if_have_addr(osif->ifp)) && (ntohl(osif->addr.s_addr) > ntohl(ifaddr->s_addr))) { *ifaddr = osif->addr; *ifindex = osif->ifindex; /* and it must be an OSPF interface */ oi = ospf_if_lookup_by_local_addr( ospf, osif->ifp, *ifaddr); if (oi) return oi; } } /* Unnumbered interface */ else /* The interface must NOT have valid AF_INET connected address */ /* it must have lager interface index than the lookup entry */ if ((!ospf_snmp_is_if_have_addr(osif->ifp)) && (osif->ifindex > *ifindex)) { *ifaddr = osif->addr; *ifindex = osif->ifindex; /* and it must be an OSPF interface */ oi = ospf_if_lookup_by_local_addr(ospf, osif->ifp, *ifaddr); if (oi) return oi; } } return NULL; } static int ospf_snmp_iftype(struct interface *ifp) { #define ospf_snmp_iftype_broadcast 1 #define ospf_snmp_iftype_nbma 2 #define ospf_snmp_iftype_pointToPoint 3 #define ospf_snmp_iftype_pointToMultipoint 5 if (if_is_broadcast(ifp)) return ospf_snmp_iftype_broadcast; if (if_is_pointopoint(ifp)) return ospf_snmp_iftype_pointToPoint; return ospf_snmp_iftype_broadcast; } static struct ospf_interface *ospfIfLookup(struct variable *v, oid *name, size_t *length, struct in_addr *ifaddr, ifindex_t *ifindex, int exact) { unsigned int len; int ifaddr_next = 0; ifindex_t ifindex_next = 0; struct ospf_interface *oi; oid *offset; if (exact) { if (*length != v->namelen + IN_ADDR_SIZE + 1) return NULL; oid2in_addr(name + v->namelen, IN_ADDR_SIZE, ifaddr); *ifindex = name[v->namelen + IN_ADDR_SIZE]; return ospf_snmp_if_lookup(ifaddr, ifindex); } else { len = *length - v->namelen; if (len >= IN_ADDR_SIZE) len = IN_ADDR_SIZE; if (len == 0) ifaddr_next = 1; oid2in_addr(name + v->namelen, len, ifaddr); len = *length - v->namelen - IN_ADDR_SIZE; if (len >= 1) len = 1; else ifindex_next = 1; if (len == 1) *ifindex = name[v->namelen + IN_ADDR_SIZE]; oi = ospf_snmp_if_lookup_next(ifaddr, ifindex, ifaddr_next, ifindex_next); if (oi) { *length = v->namelen + IN_ADDR_SIZE + 1; offset = name + v->namelen; oid_copy_addr(offset, ifaddr, IN_ADDR_SIZE); offset += IN_ADDR_SIZE; *offset = *ifindex; return oi; } } return NULL; } static uint8_t *ospfIfEntry(struct variable *v, oid *name, size_t *length, int exact, size_t *var_len, WriteMethod **write_method) { ifindex_t ifindex; struct in_addr ifaddr; struct ospf_interface *oi; struct ospf *ospf; if (smux_header_table(v, name, length, exact, var_len, write_method) == MATCH_FAILED) return NULL; ifindex = 0; memset(&ifaddr, 0, sizeof(struct in_addr)); /* Check OSPF instance. */ ospf = ospf_lookup_by_vrf_id(VRF_DEFAULT); if (ospf == NULL) return NULL; oi = ospfIfLookup(v, name, length, &ifaddr, &ifindex, exact); if (oi == NULL) return NULL; /* Return the current value of the variable */ switch (v->magic) { case OSPFIFIPADDRESS: /* 1 */ return SNMP_IPADDRESS(ifaddr); break; case OSPFADDRESSLESSIF: /* 2 */ return SNMP_INTEGER(ifindex); break; case OSPFIFAREAID: /* 3 */ if (oi->area) return SNMP_IPADDRESS(oi->area->area_id); else return SNMP_IPADDRESS(ospf_empty_addr); break; case OSPFIFTYPE: /* 4 */ return SNMP_INTEGER(ospf_snmp_iftype(oi->ifp)); break; case OSPFIFADMINSTAT: /* 5 */ if (oi) return SNMP_INTEGER(OSPF_STATUS_ENABLED); else return SNMP_INTEGER(OSPF_STATUS_DISABLED); break; case OSPFIFRTRPRIORITY: /* 6 */ return SNMP_INTEGER(PRIORITY(oi)); break; case OSPFIFTRANSITDELAY: /* 7 */ return SNMP_INTEGER(OSPF_IF_PARAM(oi, transmit_delay)); break; case OSPFIFRETRANSINTERVAL: /* 8 */ return SNMP_INTEGER(OSPF_IF_PARAM(oi, retransmit_interval)); break; case OSPFIFHELLOINTERVAL: /* 9 */ return SNMP_INTEGER(OSPF_IF_PARAM(oi, v_hello)); break; case OSPFIFRTRDEADINTERVAL: /* 10 */ return SNMP_INTEGER(OSPF_IF_PARAM(oi, v_wait)); break; case OSPFIFPOLLINTERVAL: /* 11 */ return SNMP_INTEGER(OSPF_POLL_INTERVAL_DEFAULT); break; case OSPFIFSTATE: /* 12 */ return SNMP_INTEGER(ISM_SNMP(oi->state)); break; case OSPFIFDESIGNATEDROUTER: /* 13 */ return SNMP_IPADDRESS(DR(oi)); break; case OSPFIFBACKUPDESIGNATEDROUTER: /* 14 */ return SNMP_IPADDRESS(BDR(oi)); break; case OSPFIFEVENTS: /* 15 */ return SNMP_INTEGER(oi->state_change); break; case OSPFIFAUTHKEY: /* 16 */ *var_len = 0; return (uint8_t *)OSPF_IF_PARAM(oi, auth_simple); break; case OSPFIFSTATUS: /* 17 */ return SNMP_INTEGER(SNMP_VALID); break; case OSPFIFMULTICASTFORWARDING: /* 18 */ #define ospf_snmp_multiforward_blocked 1 #define ospf_snmp_multiforward_multicast 2 #define ospf_snmp_multiforward_unicast 3 return SNMP_INTEGER(ospf_snmp_multiforward_blocked); break; case OSPFIFDEMAND: /* 19 */ return SNMP_INTEGER(SNMP_FALSE); break; case OSPFIFAUTHTYPE: /* 20 */ if (oi->area) return SNMP_INTEGER(oi->area->auth_type); else return SNMP_INTEGER(0); break; default: return NULL; break; } return NULL; } #define OSPF_SNMP_METRIC_VALUE 1 static struct ospf_interface *ospfIfMetricLookup(struct variable *v, oid *name, size_t *length, struct in_addr *ifaddr, ifindex_t *ifindex, int exact) { unsigned int len; int ifaddr_next = 0; ifindex_t ifindex_next = 0; struct ospf_interface *oi; oid *offset; int metric; if (exact) { if (*length != v->namelen + IN_ADDR_SIZE + 1 + 1) return NULL; oid2in_addr(name + v->namelen, IN_ADDR_SIZE, ifaddr); *ifindex = name[v->namelen + IN_ADDR_SIZE]; metric = name[v->namelen + IN_ADDR_SIZE + 1]; if (metric != OSPF_SNMP_METRIC_VALUE) return NULL; return ospf_snmp_if_lookup(ifaddr, ifindex); } else { len = *length - v->namelen; if (len >= IN_ADDR_SIZE) len = IN_ADDR_SIZE; else ifaddr_next = 1; oid2in_addr(name + v->namelen, len, ifaddr); len = *length - v->namelen - IN_ADDR_SIZE; if (len >= 1) len = 1; else ifindex_next = 1; if (len == 1) *ifindex = name[v->namelen + IN_ADDR_SIZE]; oi = ospf_snmp_if_lookup_next(ifaddr, ifindex, ifaddr_next, ifindex_next); if (oi) { *length = v->namelen + IN_ADDR_SIZE + 1 + 1; offset = name + v->namelen; oid_copy_addr(offset, ifaddr, IN_ADDR_SIZE); offset += IN_ADDR_SIZE; *offset = *ifindex; offset++; *offset = OSPF_SNMP_METRIC_VALUE; return oi; } } return NULL; } static uint8_t *ospfIfMetricEntry(struct variable *v, oid *name, size_t *length, int exact, size_t *var_len, WriteMethod **write_method) { /* Currently we support metric 1 only. */ ifindex_t ifindex; struct in_addr ifaddr; struct ospf_interface *oi; struct ospf *ospf; if (smux_header_table(v, name, length, exact, var_len, write_method) == MATCH_FAILED) return NULL; ifindex = 0; memset(&ifaddr, 0, sizeof(struct in_addr)); /* Check OSPF instance. */ ospf = ospf_lookup_by_vrf_id(VRF_DEFAULT); if (ospf == NULL) return NULL; oi = ospfIfMetricLookup(v, name, length, &ifaddr, &ifindex, exact); if (oi == NULL) return NULL; /* Return the current value of the variable */ switch (v->magic) { case OSPFIFMETRICIPADDRESS: return SNMP_IPADDRESS(ifaddr); break; case OSPFIFMETRICADDRESSLESSIF: return SNMP_INTEGER(ifindex); break; case OSPFIFMETRICTOS: return SNMP_INTEGER(0); break; case OSPFIFMETRICVALUE: return SNMP_INTEGER(OSPF_SNMP_METRIC_VALUE); break; case OSPFIFMETRICSTATUS: return SNMP_INTEGER(1); break; default: return NULL; break; } return NULL; } static struct route_table *ospf_snmp_vl_table; static int ospf_snmp_vl_add(struct ospf_vl_data *vl_data) { struct prefix_ls lp; struct route_node *rn; memset(&lp, 0, sizeof(struct prefix_ls)); lp.family = 0; lp.prefixlen = 64; lp.id = vl_data->vl_area_id; lp.adv_router = vl_data->vl_peer; rn = route_node_get(ospf_snmp_vl_table, (struct prefix *)&lp); if (rn->info) route_unlock_node(rn); rn->info = vl_data; return 0; } static int ospf_snmp_vl_delete(struct ospf_vl_data *vl_data) { struct prefix_ls lp; struct route_node *rn; memset(&lp, 0, sizeof(struct prefix_ls)); lp.family = 0; lp.prefixlen = 64; lp.id = vl_data->vl_area_id; lp.adv_router = vl_data->vl_peer; rn = route_node_lookup(ospf_snmp_vl_table, (struct prefix *)&lp); if (!rn) return 0; rn->info = NULL; route_unlock_node(rn); route_unlock_node(rn); return 0; } static struct ospf_vl_data *ospf_snmp_vl_lookup(struct in_addr *area_id, struct in_addr *neighbor) { struct prefix_ls lp; struct route_node *rn; struct ospf_vl_data *vl_data; memset(&lp, 0, sizeof(struct prefix_ls)); lp.family = 0; lp.prefixlen = 64; lp.id = *area_id; lp.adv_router = *neighbor; rn = route_node_lookup(ospf_snmp_vl_table, (struct prefix *)&lp); if (rn) { vl_data = rn->info; route_unlock_node(rn); return vl_data; } return NULL; } static struct ospf_vl_data *ospf_snmp_vl_lookup_next(struct in_addr *area_id, struct in_addr *neighbor, int first) { struct prefix_ls lp; struct route_node *rn; struct ospf_vl_data *vl_data; memset(&lp, 0, sizeof(struct prefix_ls)); lp.family = 0; lp.prefixlen = 64; lp.id = *area_id; lp.adv_router = *neighbor; if (first) rn = route_top(ospf_snmp_vl_table); else { rn = route_node_get(ospf_snmp_vl_table, (struct prefix *)&lp); rn = route_next(rn); } for (; rn; rn = route_next(rn)) if (rn->info) break; if (rn && rn->info) { vl_data = rn->info; *area_id = vl_data->vl_area_id; *neighbor = vl_data->vl_peer; route_unlock_node(rn); return vl_data; } return NULL; } static struct ospf_vl_data * ospfVirtIfLookup(struct variable *v, oid *name, size_t *length, struct in_addr *area_id, struct in_addr *neighbor, int exact) { int first; unsigned int len; struct ospf_vl_data *vl_data; if (exact) { if (*length != v->namelen + IN_ADDR_SIZE + IN_ADDR_SIZE) return NULL; oid2in_addr(name + v->namelen, IN_ADDR_SIZE, area_id); oid2in_addr(name + v->namelen + IN_ADDR_SIZE, IN_ADDR_SIZE, neighbor); return ospf_snmp_vl_lookup(area_id, neighbor); } else { first = 0; len = *length - v->namelen; if (len == 0) first = 1; if (len > IN_ADDR_SIZE) len = IN_ADDR_SIZE; oid2in_addr(name + v->namelen, len, area_id); len = *length - v->namelen - IN_ADDR_SIZE; if (len > IN_ADDR_SIZE) len = IN_ADDR_SIZE; oid2in_addr(name + v->namelen + IN_ADDR_SIZE, len, neighbor); vl_data = ospf_snmp_vl_lookup_next(area_id, neighbor, first); if (vl_data) { *length = v->namelen + IN_ADDR_SIZE + IN_ADDR_SIZE; oid_copy_addr(name + v->namelen, area_id, IN_ADDR_SIZE); oid_copy_addr(name + v->namelen + IN_ADDR_SIZE, neighbor, IN_ADDR_SIZE); return vl_data; } } return NULL; } static uint8_t *ospfVirtIfEntry(struct variable *v, oid *name, size_t *length, int exact, size_t *var_len, WriteMethod **write_method) { struct ospf_vl_data *vl_data; struct ospf_interface *oi; struct in_addr area_id; struct in_addr neighbor; if (smux_header_table(v, name, length, exact, var_len, write_method) == MATCH_FAILED) return NULL; memset(&area_id, 0, sizeof(struct in_addr)); memset(&neighbor, 0, sizeof(struct in_addr)); vl_data = ospfVirtIfLookup(v, name, length, &area_id, &neighbor, exact); if (!vl_data) return NULL; oi = vl_data->vl_oi; if (!oi) return NULL; /* Return the current value of the variable */ switch (v->magic) { case OSPFVIRTIFAREAID: return SNMP_IPADDRESS(area_id); break; case OSPFVIRTIFNEIGHBOR: return SNMP_IPADDRESS(neighbor); break; case OSPFVIRTIFTRANSITDELAY: return SNMP_INTEGER(OSPF_IF_PARAM(oi, transmit_delay)); break; case OSPFVIRTIFRETRANSINTERVAL: return SNMP_INTEGER(OSPF_IF_PARAM(oi, retransmit_interval)); break; case OSPFVIRTIFHELLOINTERVAL: return SNMP_INTEGER(OSPF_IF_PARAM(oi, v_hello)); break; case OSPFVIRTIFRTRDEADINTERVAL: return SNMP_INTEGER(OSPF_IF_PARAM(oi, v_wait)); break; case OSPFVIRTIFSTATE: return SNMP_INTEGER(oi->state); break; case OSPFVIRTIFEVENTS: return SNMP_INTEGER(oi->state_change); break; case OSPFVIRTIFAUTHKEY: *var_len = 0; return (uint8_t *)OSPF_IF_PARAM(oi, auth_simple); break; case OSPFVIRTIFSTATUS: return SNMP_INTEGER(SNMP_VALID); break; case OSPFVIRTIFAUTHTYPE: if (oi->area) return SNMP_INTEGER(oi->area->auth_type); else return SNMP_INTEGER(0); break; default: return NULL; break; } return NULL; } static struct ospf_neighbor *ospf_snmp_nbr_lookup(struct ospf *ospf, struct in_addr *nbr_addr, ifindex_t *ifindex) { struct listnode *node, *nnode; struct ospf_interface *oi; struct ospf_neighbor *nbr; struct route_node *rn; for (ALL_LIST_ELEMENTS(ospf->oiflist, node, nnode, oi)) { for (rn = route_top(oi->nbrs); rn; rn = route_next(rn)) if ((nbr = rn->info) != NULL && nbr != oi->nbr_self /* If EXACT match is needed, provide ALL entry found && nbr->state != NSM_Down */ && nbr->src.s_addr != 0) { if (IPV4_ADDR_SAME(&nbr->src, nbr_addr)) { route_unlock_node(rn); return nbr; } } } return NULL; } static struct ospf_neighbor *ospf_snmp_nbr_lookup_next(struct in_addr *nbr_addr, ifindex_t *ifindex, int first) { struct listnode *nn; struct ospf_interface *oi; struct ospf_neighbor *nbr; struct route_node *rn; struct ospf_neighbor *min = NULL; struct ospf *ospf; ospf = ospf_lookup_by_vrf_id(VRF_DEFAULT); for (ALL_LIST_ELEMENTS_RO(ospf->oiflist, nn, oi)) { for (rn = route_top(oi->nbrs); rn; rn = route_next(rn)) if ((nbr = rn->info) != NULL && nbr != oi->nbr_self && nbr->state != NSM_Down && nbr->src.s_addr != 0) { if (first) { if (!min) min = nbr; else if (ntohl(nbr->src.s_addr) < ntohl(min->src.s_addr)) min = nbr; } else if (ntohl(nbr->src.s_addr) > ntohl(nbr_addr->s_addr)) { if (!min) min = nbr; else if (ntohl(nbr->src.s_addr) < ntohl(min->src.s_addr)) min = nbr; } } } if (min) { *nbr_addr = min->src; *ifindex = 0; return min; } return NULL; } static struct ospf_neighbor *ospfNbrLookup(struct variable *v, oid *name, size_t *length, struct in_addr *nbr_addr, ifindex_t *ifindex, int exact) { unsigned int len; int first; struct ospf_neighbor *nbr; struct ospf *ospf; ospf = ospf_lookup_by_vrf_id(VRF_DEFAULT); if (!ospf) return NULL; if (exact) { if (*length != v->namelen + IN_ADDR_SIZE + 1) return NULL; oid2in_addr(name + v->namelen, IN_ADDR_SIZE, nbr_addr); *ifindex = name[v->namelen + IN_ADDR_SIZE]; return ospf_snmp_nbr_lookup(ospf, nbr_addr, ifindex); } else { first = 0; len = *length - v->namelen; if (len == 0) first = 1; if (len > IN_ADDR_SIZE) len = IN_ADDR_SIZE; oid2in_addr(name + v->namelen, len, nbr_addr); len = *length - v->namelen - IN_ADDR_SIZE; if (len >= 1) *ifindex = name[v->namelen + IN_ADDR_SIZE]; nbr = ospf_snmp_nbr_lookup_next(nbr_addr, ifindex, first); if (nbr) { *length = v->namelen + IN_ADDR_SIZE + 1; oid_copy_addr(name + v->namelen, nbr_addr, IN_ADDR_SIZE); name[v->namelen + IN_ADDR_SIZE] = *ifindex; return nbr; } } return NULL; } /* map internal quagga neighbor states to official MIB values: ospfNbrState OBJECT-TYPE SYNTAX INTEGER { down (1), attempt (2), init (3), twoWay (4), exchangeStart (5), exchange (6), loading (7), full (8) } */ static int32_t ospf_snmp_neighbor_state(uint8_t nst) { switch (nst) { case NSM_Attempt: return 2; case NSM_Init: return 3; case NSM_TwoWay: return 4; case NSM_ExStart: return 5; case NSM_Exchange: return 6; case NSM_Loading: return 7; case NSM_Full: return 8; default: return 1; /* down */ } } static uint8_t *ospfNbrEntry(struct variable *v, oid *name, size_t *length, int exact, size_t *var_len, WriteMethod **write_method) { struct in_addr nbr_addr; ifindex_t ifindex; struct ospf_neighbor *nbr; struct ospf_interface *oi; if (smux_header_table(v, name, length, exact, var_len, write_method) == MATCH_FAILED) return NULL; memset(&nbr_addr, 0, sizeof(struct in_addr)); ifindex = 0; nbr = ospfNbrLookup(v, name, length, &nbr_addr, &ifindex, exact); if (!nbr) return NULL; oi = nbr->oi; if (!oi) return NULL; /* Return the current value of the variable */ switch (v->magic) { case OSPFNBRIPADDR: return SNMP_IPADDRESS(nbr_addr); break; case OSPFNBRADDRESSLESSINDEX: return SNMP_INTEGER(ifindex); break; case OSPFNBRRTRID: return SNMP_IPADDRESS(nbr->router_id); break; case OSPFNBROPTIONS: return SNMP_INTEGER(oi->nbr_self->options); break; case OSPFNBRPRIORITY: return SNMP_INTEGER(nbr->priority); break; case OSPFNBRSTATE: return SNMP_INTEGER(ospf_snmp_neighbor_state(nbr->state)); break; case OSPFNBREVENTS: return SNMP_INTEGER(nbr->state_change); break; case OSPFNBRLSRETRANSQLEN: return SNMP_INTEGER(ospf_ls_retransmit_count(nbr)); break; case OSPFNBMANBRSTATUS: return SNMP_INTEGER(SNMP_VALID); break; case OSPFNBMANBRPERMANENCE: return SNMP_INTEGER(2); break; case OSPFNBRHELLOSUPPRESSED: return SNMP_INTEGER(SNMP_FALSE); break; default: return NULL; break; } return NULL; } static uint8_t *ospfVirtNbrEntry(struct variable *v, oid *name, size_t *length, int exact, size_t *var_len, WriteMethod **write_method) { struct ospf_vl_data *vl_data; struct in_addr area_id; struct in_addr neighbor; struct ospf *ospf; if (smux_header_table(v, name, length, exact, var_len, write_method) == MATCH_FAILED) return NULL; memset(&area_id, 0, sizeof(struct in_addr)); memset(&neighbor, 0, sizeof(struct in_addr)); /* Check OSPF instance. */ ospf = ospf_lookup_by_vrf_id(VRF_DEFAULT); if (ospf == NULL) return NULL; vl_data = ospfVirtIfLookup(v, name, length, &area_id, &neighbor, exact); if (!vl_data) return NULL; /* Return the current value of the variable */ switch (v->magic) { case OSPFVIRTNBRAREA: return (uint8_t *)NULL; break; case OSPFVIRTNBRRTRID: return (uint8_t *)NULL; break; case OSPFVIRTNBRIPADDR: return (uint8_t *)NULL; break; case OSPFVIRTNBROPTIONS: return (uint8_t *)NULL; break; case OSPFVIRTNBRSTATE: return (uint8_t *)NULL; break; case OSPFVIRTNBREVENTS: return (uint8_t *)NULL; break; case OSPFVIRTNBRLSRETRANSQLEN: return (uint8_t *)NULL; break; case OSPFVIRTNBRHELLOSUPPRESSED: return (uint8_t *)NULL; break; default: return NULL; break; } return NULL; } static struct ospf_lsa *ospfExtLsdbLookup(struct variable *v, oid *name, size_t *length, uint8_t *type, struct in_addr *ls_id, struct in_addr *router_id, int exact) { int first; oid *offset; int offsetlen; uint8_t lsa_type; unsigned int len; struct ospf_lsa *lsa; struct ospf *ospf; ospf = ospf_lookup_by_vrf_id(VRF_DEFAULT); if (exact) { if (*length != v->namelen + 1 + IN_ADDR_SIZE + IN_ADDR_SIZE) return NULL; offset = name + v->namelen; /* Make it sure given value match to type. */ lsa_type = *offset; offset++; if (lsa_type != *type) return NULL; /* LS ID. */ oid2in_addr(offset, IN_ADDR_SIZE, ls_id); offset += IN_ADDR_SIZE; /* Router ID. */ oid2in_addr(offset, IN_ADDR_SIZE, router_id); return ospf_lsdb_lookup_by_id(ospf->lsdb, *type, *ls_id, *router_id); } else { /* Get variable length. */ first = 0; offset = name + v->namelen; offsetlen = *length - v->namelen; /* LSA type value. */ lsa_type = *offset; offset++; offsetlen--; if (offsetlen <= 0 || lsa_type < OSPF_AS_EXTERNAL_LSA) first = 1; /* LS ID. */ len = offsetlen; if (len > IN_ADDR_SIZE) len = IN_ADDR_SIZE; oid2in_addr(offset, len, ls_id); offset += IN_ADDR_SIZE; offsetlen -= IN_ADDR_SIZE; /* Router ID. */ len = offsetlen; if (len > IN_ADDR_SIZE) len = IN_ADDR_SIZE; oid2in_addr(offset, len, router_id); lsa = ospf_lsdb_lookup_by_id_next(ospf->lsdb, *type, *ls_id, *router_id, first); if (lsa) { /* Fill in length. */ *length = v->namelen + 1 + IN_ADDR_SIZE + IN_ADDR_SIZE; /* Fill in value. */ offset = name + v->namelen; *offset = OSPF_AS_EXTERNAL_LSA; offset++; oid_copy_addr(offset, &lsa->data->id, IN_ADDR_SIZE); offset += IN_ADDR_SIZE; oid_copy_addr(offset, &lsa->data->adv_router, IN_ADDR_SIZE); return lsa; } } return NULL; } static uint8_t *ospfExtLsdbEntry(struct variable *v, oid *name, size_t *length, int exact, size_t *var_len, WriteMethod **write_method) { struct ospf_lsa *lsa; struct lsa_header *lsah; uint8_t type; struct in_addr ls_id; struct in_addr router_id; struct ospf *ospf; if (smux_header_table(v, name, length, exact, var_len, write_method) == MATCH_FAILED) return NULL; type = OSPF_AS_EXTERNAL_LSA; memset(&ls_id, 0, sizeof(struct in_addr)); memset(&router_id, 0, sizeof(struct in_addr)); /* Check OSPF instance. */ ospf = ospf_lookup_by_vrf_id(VRF_DEFAULT); if (ospf == NULL) return NULL; lsa = ospfExtLsdbLookup(v, name, length, &type, &ls_id, &router_id, exact); if (!lsa) return NULL; lsah = lsa->data; /* Return the current value of the variable */ switch (v->magic) { case OSPFEXTLSDBTYPE: return SNMP_INTEGER(OSPF_AS_EXTERNAL_LSA); break; case OSPFEXTLSDBLSID: return SNMP_IPADDRESS(lsah->id); break; case OSPFEXTLSDBROUTERID: return SNMP_IPADDRESS(lsah->adv_router); break; case OSPFEXTLSDBSEQUENCE: return SNMP_INTEGER(lsah->ls_seqnum); break; case OSPFEXTLSDBAGE: return SNMP_INTEGER(lsah->ls_age); break; case OSPFEXTLSDBCHECKSUM: return SNMP_INTEGER(lsah->checksum); break; case OSPFEXTLSDBADVERTISEMENT: *var_len = ntohs(lsah->length); return (uint8_t *)lsah; break; default: return NULL; break; } return NULL; } static uint8_t *ospfAreaAggregateEntry(struct variable *v, oid *name, size_t *length, int exact, size_t *var_len, WriteMethod **write_method) { if (smux_header_table(v, name, length, exact, var_len, write_method) == MATCH_FAILED) return NULL; /* Return the current value of the variable */ switch (v->magic) { case OSPFAREAAGGREGATEAREAID: return (uint8_t *)NULL; break; case OSPFAREAAGGREGATELSDBTYPE: return (uint8_t *)NULL; break; case OSPFAREAAGGREGATENET: return (uint8_t *)NULL; break; case OSPFAREAAGGREGATEMASK: return (uint8_t *)NULL; break; case OSPFAREAAGGREGATESTATUS: return (uint8_t *)NULL; break; case OSPFAREAAGGREGATEEFFECT: return (uint8_t *)NULL; break; default: return NULL; break; } return NULL; } /* OSPF Traps. */ #define IFSTATECHANGE 16 #define VIRTIFSTATECHANGE 1 #define NBRSTATECHANGE 2 #define VIRTNBRSTATECHANGE 3 static struct trap_object ospfNbrTrapList[] = {{-2, {1, OSPFROUTERID}}, {3, {10, 1, OSPFNBRIPADDR}}, {3, {10, 1, OSPFNBRRTRID}}, {3, {10, 1, OSPFNBRSTATE}}}; static struct trap_object ospfVirtNbrTrapList[] = { {-2, {1, 1}}, {3, {11, 1, OSPFVIRTNBRAREA}}, {3, {11, 1, OSPFVIRTNBRRTRID}}, {3, {11, 1, OSPFVIRTNBRSTATE}}}; static struct trap_object ospfIfTrapList[] = {{-2, {1, OSPFROUTERID}}, {3, {7, 1, OSPFIFIPADDRESS}}, {3, {7, 1, OSPFADDRESSLESSIF}}, {3, {7, 1, OSPFIFSTATE}}}; static struct trap_object ospfVirtIfTrapList[] = { {-2, {1, OSPFROUTERID}}, {3, {9, 1, OSPFVIRTIFAREAID}}, {3, {9, 1, OSPFVIRTIFNEIGHBOR}}, {3, {9, 1, OSPFVIRTIFSTATE}}}; static void ospfTrapNbrStateChange(struct ospf_neighbor *on) { oid index[sizeof(oid) * (IN_ADDR_SIZE + 1)]; char msgbuf[16]; ospf_nbr_state_message(on, msgbuf, sizeof(msgbuf)); if (IS_DEBUG_OSPF_EVENT) zlog_info("%s: trap sent: %s now %s", __PRETTY_FUNCTION__, inet_ntoa(on->address.u.prefix4), msgbuf); oid_copy_addr(index, &(on->address.u.prefix4), IN_ADDR_SIZE); index[IN_ADDR_SIZE] = 0; smux_trap(ospf_variables, array_size(ospf_variables), ospf_trap_oid, array_size(ospf_trap_oid), ospf_oid, sizeof ospf_oid / sizeof(oid), index, IN_ADDR_SIZE + 1, ospfNbrTrapList, array_size(ospfNbrTrapList), NBRSTATECHANGE); } static void ospfTrapVirtNbrStateChange(struct ospf_neighbor *on) { oid index[sizeof(oid) * (IN_ADDR_SIZE + 1)]; zlog_info("ospfTrapVirtNbrStateChange trap sent"); oid_copy_addr(index, &(on->address.u.prefix4), IN_ADDR_SIZE); index[IN_ADDR_SIZE] = 0; smux_trap(ospf_variables, array_size(ospf_variables), ospf_trap_oid, array_size(ospf_trap_oid), ospf_oid, sizeof ospf_oid / sizeof(oid), index, IN_ADDR_SIZE + 1, ospfVirtNbrTrapList, array_size(ospfVirtNbrTrapList), VIRTNBRSTATECHANGE); } static int ospf_snmp_nsm_change(struct ospf_neighbor *nbr, int next_state, int old_state) { /* Terminal state or regression */ if ((next_state == NSM_Full) || (next_state == NSM_TwoWay) || (next_state < old_state)) { /* ospfVirtNbrStateChange */ if (nbr->oi->type == OSPF_IFTYPE_VIRTUALLINK) ospfTrapVirtNbrStateChange(nbr); /* ospfNbrStateChange trap */ else /* To/From FULL, only managed by DR */ if (((next_state != NSM_Full) && (nbr->state != NSM_Full)) || (nbr->oi->state == ISM_DR)) ospfTrapNbrStateChange(nbr); } return 0; } static void ospfTrapIfStateChange(struct ospf_interface *oi) { oid index[sizeof(oid) * (IN_ADDR_SIZE + 1)]; if (IS_DEBUG_OSPF_EVENT) zlog_info("%s: trap sent: %s now %s", __PRETTY_FUNCTION__, inet_ntoa(oi->address->u.prefix4), lookup_msg(ospf_ism_state_msg, oi->state, NULL)); oid_copy_addr(index, &(oi->address->u.prefix4), IN_ADDR_SIZE); index[IN_ADDR_SIZE] = 0; smux_trap(ospf_variables, array_size(ospf_variables), ospf_trap_oid, array_size(ospf_trap_oid), ospf_oid, sizeof ospf_oid / sizeof(oid), index, IN_ADDR_SIZE + 1, ospfIfTrapList, array_size(ospfIfTrapList), IFSTATECHANGE); } static void ospfTrapVirtIfStateChange(struct ospf_interface *oi) { oid index[sizeof(oid) * (IN_ADDR_SIZE + 1)]; zlog_info("ospfTrapVirtIfStateChange trap sent"); oid_copy_addr(index, &(oi->address->u.prefix4), IN_ADDR_SIZE); index[IN_ADDR_SIZE] = 0; smux_trap(ospf_variables, array_size(ospf_variables), ospf_trap_oid, array_size(ospf_trap_oid), ospf_oid, sizeof ospf_oid / sizeof(oid), index, IN_ADDR_SIZE + 1, ospfVirtIfTrapList, array_size(ospfVirtIfTrapList), VIRTIFSTATECHANGE); } static int ospf_snmp_ism_change(struct ospf_interface *oi, int state, int old_state) { /* Terminal state or regression */ if ((state == ISM_DR) || (state == ISM_Backup) || (state == ISM_DROther) || (state == ISM_PointToPoint) || (state < old_state)) { /* ospfVirtIfStateChange */ if (oi->type == OSPF_IFTYPE_VIRTUALLINK) ospfTrapVirtIfStateChange(oi); /* ospfIfStateChange */ else ospfTrapIfStateChange(oi); } return 0; } /* Register OSPF2-MIB. */ static int ospf_snmp_init(struct thread_master *tm) { ospf_snmp_iflist = list_new(); ospf_snmp_vl_table = route_table_init(); smux_init(tm); REGISTER_MIB("mibII/ospf", ospf_variables, variable, ospf_oid); return 0; } static int ospf_snmp_module_init(void) { hook_register(ospf_if_update, ospf_snmp_if_update); hook_register(ospf_if_delete, ospf_snmp_if_delete); hook_register(ospf_vl_add, ospf_snmp_vl_add); hook_register(ospf_vl_delete, ospf_snmp_vl_delete); hook_register(ospf_ism_change, ospf_snmp_ism_change); hook_register(ospf_nsm_change, ospf_snmp_nsm_change); hook_register(frr_late_init, ospf_snmp_init); return 0; } FRR_MODULE_SETUP(.name = "ospfd_snmp", .version = FRR_VERSION, .description = "ospfd AgentX SNMP module", .init = ospf_snmp_module_init, ) frr-7.2.1/ospfd/ospf_spf.c0000644000000000000000000012076013610377563012320 00000000000000/* OSPF SPF calculation. * Copyright (C) 1999, 2000 Kunihiro Ishiguro, Toshiaki Takada * * This file is part of GNU Zebra. * * GNU Zebra is free software; you can redistribute it and/or modify it * under the terms of the GNU General Public License as published by the * Free Software Foundation; either version 2, or (at your option) any * later version. * * GNU Zebra 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; see the file COPYING; if not, write to the Free Software * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA */ #include #include "monotime.h" #include "thread.h" #include "memory.h" #include "hash.h" #include "linklist.h" #include "prefix.h" #include "if.h" #include "table.h" #include "log.h" #include "sockunion.h" /* for inet_ntop () */ #include "ospfd/ospfd.h" #include "ospfd/ospf_interface.h" #include "ospfd/ospf_ism.h" #include "ospfd/ospf_asbr.h" #include "ospfd/ospf_lsa.h" #include "ospfd/ospf_lsdb.h" #include "ospfd/ospf_neighbor.h" #include "ospfd/ospf_nsm.h" #include "ospfd/ospf_spf.h" #include "ospfd/ospf_route.h" #include "ospfd/ospf_ia.h" #include "ospfd/ospf_ase.h" #include "ospfd/ospf_abr.h" #include "ospfd/ospf_dump.h" #include "ospfd/ospf_sr.h" #include "ospfd/ospf_errors.h" /* Variables to ensure a SPF scheduled log message is printed only once */ static unsigned int spf_reason_flags = 0; /* dummy vertex to flag "in spftree" */ static const struct vertex vertex_in_spftree = {}; #define LSA_SPF_IN_SPFTREE (struct vertex *)&vertex_in_spftree #define LSA_SPF_NOT_EXPLORED NULL static void ospf_clear_spf_reason_flags(void) { spf_reason_flags = 0; } static void ospf_spf_set_reason(ospf_spf_reason_t reason) { spf_reason_flags |= 1 << reason; } static void ospf_vertex_free(void *); /* List of allocated vertices, to simplify cleanup of SPF. * Not thread-safe obviously. If it ever needs to be, it'd have to be * dynamically allocated at begin of ospf_spf_calculate */ static struct list vertex_list = {.del = ospf_vertex_free}; /* Heap related functions, for the managment of the candidates, to * be used with pqueue. */ static int vertex_cmp(const struct vertex *v1, const struct vertex *v2) { if (v1->distance != v2->distance) return v1->distance - v2->distance; if (v1->type != v2->type) { switch (v1->type) { case OSPF_VERTEX_NETWORK: return -1; case OSPF_VERTEX_ROUTER: return 1; } } return 0; } DECLARE_SKIPLIST_NONUNIQ(vertex_pqueue, struct vertex, pqi, vertex_cmp) static void lsdb_clean_stat(struct ospf_lsdb *lsdb) { struct route_table *table; struct route_node *rn; struct ospf_lsa *lsa; int i; for (i = OSPF_MIN_LSA; i < OSPF_MAX_LSA; i++) { table = lsdb->type[i].db; for (rn = route_top(table); rn; rn = route_next(rn)) if ((lsa = (rn->info)) != NULL) lsa->stat = LSA_SPF_NOT_EXPLORED; } } static struct vertex_nexthop *vertex_nexthop_new(void) { return XCALLOC(MTYPE_OSPF_NEXTHOP, sizeof(struct vertex_nexthop)); } static void vertex_nexthop_free(struct vertex_nexthop *nh) { XFREE(MTYPE_OSPF_NEXTHOP, nh); } /* Free the canonical nexthop objects for an area, ie the nexthop objects * attached to the first-hop router vertices, and any intervening network * vertices. */ static void ospf_canonical_nexthops_free(struct vertex *root) { struct listnode *node, *nnode; struct vertex *child; for (ALL_LIST_ELEMENTS(root->children, node, nnode, child)) { struct listnode *n2, *nn2; struct vertex_parent *vp; /* router vertices through an attached network each * have a distinct (canonical / not inherited) nexthop * which must be freed. * * A network vertex can only have router vertices as its * children, so only one level of recursion is possible. */ if (child->type == OSPF_VERTEX_NETWORK) ospf_canonical_nexthops_free(child); /* Free child nexthops pointing back to this root vertex */ for (ALL_LIST_ELEMENTS(child->parents, n2, nn2, vp)) if (vp->parent == root && vp->nexthop) { vertex_nexthop_free(vp->nexthop); vp->nexthop = NULL; } } } /* TODO: Parent list should be excised, in favour of maintaining only * vertex_nexthop, with refcounts. */ static struct vertex_parent *vertex_parent_new(struct vertex *v, int backlink, struct vertex_nexthop *hop) { struct vertex_parent *new; new = XMALLOC(MTYPE_OSPF_VERTEX_PARENT, sizeof(struct vertex_parent)); new->parent = v; new->backlink = backlink; new->nexthop = hop; return new; } static void vertex_parent_free(void *p) { XFREE(MTYPE_OSPF_VERTEX_PARENT, p); } static int vertex_parent_cmp(void *aa, void *bb) { struct vertex_parent *a = aa, *b = bb; return IPV4_ADDR_CMP(&a->nexthop->router, &b->nexthop->router); } static struct vertex *ospf_vertex_new(struct ospf_lsa *lsa) { struct vertex *new; new = XCALLOC(MTYPE_OSPF_VERTEX, sizeof(struct vertex)); new->flags = 0; new->type = lsa->data->type; new->id = lsa->data->id; new->lsa = lsa->data; new->children = list_new(); new->parents = list_new(); new->parents->del = vertex_parent_free; new->parents->cmp = vertex_parent_cmp; new->lsa_p = lsa; lsa->stat = new; listnode_add(&vertex_list, new); if (IS_DEBUG_OSPF_EVENT) zlog_debug("%s: Created %s vertex %s", __func__, new->type == OSPF_VERTEX_ROUTER ? "Router" : "Network", inet_ntoa(new->lsa->id)); return new; } static void ospf_vertex_free(void *data) { struct vertex *v = data; if (IS_DEBUG_OSPF_EVENT) zlog_debug("%s: Free %s vertex %s", __func__, v->type == OSPF_VERTEX_ROUTER ? "Router" : "Network", inet_ntoa(v->lsa->id)); /* There should be no parents potentially holding references to this * vertex * Children however may still be there, but presumably referenced by * other * vertices */ // assert (listcount (v->parents) == 0); if (v->children) list_delete(&v->children); if (v->parents) list_delete(&v->parents); v->lsa = NULL; XFREE(MTYPE_OSPF_VERTEX, v); } static void ospf_vertex_dump(const char *msg, struct vertex *v, int print_parents, int print_children) { if (!IS_DEBUG_OSPF_EVENT) return; zlog_debug("%s %s vertex %s distance %u flags %u", msg, v->type == OSPF_VERTEX_ROUTER ? "Router" : "Network", inet_ntoa(v->lsa->id), v->distance, (unsigned int)v->flags); if (print_parents) { struct listnode *node; struct vertex_parent *vp; for (ALL_LIST_ELEMENTS_RO(v->parents, node, vp)) { char buf1[BUFSIZ]; if (vp) { zlog_debug( "parent %s backlink %d nexthop %s interface %s", inet_ntoa(vp->parent->lsa->id), vp->backlink, inet_ntop(AF_INET, &vp->nexthop->router, buf1, BUFSIZ), vp->nexthop->oi ? IF_NAME(vp->nexthop->oi) : "NULL"); } } } if (print_children) { struct listnode *cnode; struct vertex *cv; for (ALL_LIST_ELEMENTS_RO(v->children, cnode, cv)) ospf_vertex_dump(" child:", cv, 0, 0); } } /* Add a vertex to the list of children in each of its parents. */ static void ospf_vertex_add_parent(struct vertex *v) { struct vertex_parent *vp; struct listnode *node; assert(v && v->parents); for (ALL_LIST_ELEMENTS_RO(v->parents, node, vp)) { assert(vp->parent && vp->parent->children); /* No need to add two links from the same parent. */ if (listnode_lookup(vp->parent->children, v) == NULL) listnode_add(vp->parent->children, v); } } static void ospf_spf_init(struct ospf_area *area) { struct vertex *v; /* Create root node. */ v = ospf_vertex_new(area->router_lsa_self); area->spf = v; /* Reset ABR and ASBR router counts. */ area->abr_count = 0; area->asbr_count = 0; } /* return index of link back to V from W, or -1 if no link found */ static int ospf_lsa_has_link(struct lsa_header *w, struct lsa_header *v) { unsigned int i, length; struct router_lsa *rl; struct network_lsa *nl; /* In case of W is Network LSA. */ if (w->type == OSPF_NETWORK_LSA) { if (v->type == OSPF_NETWORK_LSA) return -1; nl = (struct network_lsa *)w; length = (ntohs(w->length) - OSPF_LSA_HEADER_SIZE - 4) / 4; for (i = 0; i < length; i++) if (IPV4_ADDR_SAME(&nl->routers[i], &v->id)) return i; return -1; } /* In case of W is Router LSA. */ if (w->type == OSPF_ROUTER_LSA) { rl = (struct router_lsa *)w; length = ntohs(w->length); for (i = 0; i < ntohs(rl->links) && length >= sizeof(struct router_lsa); i++, length -= 12) { switch (rl->link[i].type) { case LSA_LINK_TYPE_POINTOPOINT: case LSA_LINK_TYPE_VIRTUALLINK: /* Router LSA ID. */ if (v->type == OSPF_ROUTER_LSA && IPV4_ADDR_SAME(&rl->link[i].link_id, &v->id)) { return i; } break; case LSA_LINK_TYPE_TRANSIT: /* Network LSA ID. */ if (v->type == OSPF_NETWORK_LSA && IPV4_ADDR_SAME(&rl->link[i].link_id, &v->id)) { return i; } break; case LSA_LINK_TYPE_STUB: /* Stub can't lead anywhere, carry on */ continue; default: break; } } } return -1; } /* Find the next link after prev_link from v to w. If prev_link is * NULL, return the first link from v to w. Ignore stub and virtual links; * these link types will never be returned. */ static struct router_lsa_link * ospf_get_next_link(struct vertex *v, struct vertex *w, struct router_lsa_link *prev_link) { uint8_t *p; uint8_t *lim; uint8_t lsa_type = LSA_LINK_TYPE_TRANSIT; struct router_lsa_link *l; if (w->type == OSPF_VERTEX_ROUTER) lsa_type = LSA_LINK_TYPE_POINTOPOINT; if (prev_link == NULL) p = ((uint8_t *)v->lsa) + OSPF_LSA_HEADER_SIZE + 4; else { p = (uint8_t *)prev_link; p += (OSPF_ROUTER_LSA_LINK_SIZE + (prev_link->m[0].tos_count * OSPF_ROUTER_LSA_TOS_SIZE)); } lim = ((uint8_t *)v->lsa) + ntohs(v->lsa->length); while (p < lim) { l = (struct router_lsa_link *)p; p += (OSPF_ROUTER_LSA_LINK_SIZE + (l->m[0].tos_count * OSPF_ROUTER_LSA_TOS_SIZE)); if (l->m[0].type != lsa_type) continue; if (IPV4_ADDR_SAME(&l->link_id, &w->id)) return l; } return NULL; } static void ospf_spf_flush_parents(struct vertex *w) { struct vertex_parent *vp; struct listnode *ln, *nn; /* delete the existing nexthops */ for (ALL_LIST_ELEMENTS(w->parents, ln, nn, vp)) { list_delete_node(w->parents, ln); vertex_parent_free(vp); } } /* * Consider supplied next-hop for inclusion to the supplied list of * equal-cost next-hops, adjust list as neccessary. */ static void ospf_spf_add_parent(struct vertex *v, struct vertex *w, struct vertex_nexthop *newhop, unsigned int distance) { struct vertex_parent *vp, *wp; struct listnode *node; /* we must have a newhop, and a distance */ assert(v && w && newhop); assert(distance); /* IFF w has already been assigned a distance, then we shouldn't get * here * unless callers have determined V(l)->W is shortest / equal-shortest * path (0 is a special case distance (no distance yet assigned)). */ if (w->distance) assert(distance <= w->distance); else w->distance = distance; if (IS_DEBUG_OSPF_EVENT) { char buf[2][INET_ADDRSTRLEN]; zlog_debug( "%s: Adding %s as parent of %s", __func__, inet_ntop(AF_INET, &v->lsa->id, buf[0], sizeof(buf[0])), inet_ntop(AF_INET, &w->lsa->id, buf[1], sizeof(buf[1]))); } /* Adding parent for a new, better path: flush existing parents from W. */ if (distance < w->distance) { if (IS_DEBUG_OSPF_EVENT) zlog_debug( "%s: distance %d better than %d, flushing existing parents", __func__, distance, w->distance); ospf_spf_flush_parents(w); w->distance = distance; } /* new parent is <= existing parents, add it to parent list (if nexthop * not on parent list) */ for (ALL_LIST_ELEMENTS_RO(w->parents, node, wp)) { if (memcmp(newhop, wp->nexthop, sizeof(*newhop)) == 0) { if (IS_DEBUG_OSPF_EVENT) zlog_debug( "%s: ... nexthop already on parent list, skipping add", __func__); return; } } vp = vertex_parent_new(v, ospf_lsa_has_link(w->lsa, v->lsa), newhop); listnode_add_sort(w->parents, vp); return; } /* 16.1.1. Calculate nexthop from root through V (parent) to * vertex W (destination), with given distance from root->W. * * The link must be supplied if V is the root vertex. In all other cases * it may be NULL. * * Note that this function may fail, hence the state of the destination * vertex, W, should /not/ be modified in a dependent manner until * this function returns. This function will update the W vertex with the * provided distance as appropriate. */ static unsigned int ospf_nexthop_calculation(struct ospf_area *area, struct vertex *v, struct vertex *w, struct router_lsa_link *l, unsigned int distance, int lsa_pos) { struct listnode *node, *nnode; struct vertex_nexthop *nh; struct vertex_parent *vp; struct ospf_interface *oi = NULL; unsigned int added = 0; char buf1[BUFSIZ]; char buf2[BUFSIZ]; if (IS_DEBUG_OSPF_EVENT) { zlog_debug("ospf_nexthop_calculation(): Start"); ospf_vertex_dump("V (parent):", v, 1, 1); ospf_vertex_dump("W (dest) :", w, 1, 1); zlog_debug("V->W distance: %d", distance); } if (v == area->spf) { /* 16.1.1 para 4. In the first case, the parent vertex (V) is the root (the calculating router itself). This means that the destination is either a directly connected network or directly connected router. The outgoing interface in this case is simply the OSPF interface connecting to the destination network/router. */ /* we *must* be supplied with the link data */ assert(l != NULL); oi = ospf_if_lookup_by_lsa_pos(area, lsa_pos); if (!oi) { zlog_debug( "%s: OI not found in LSA: lsa_pos:%d link_id:%s link_data:%s", __func__, lsa_pos, inet_ntop(AF_INET, &l->link_id, buf1, BUFSIZ), inet_ntop(AF_INET, &l->link_data, buf2, BUFSIZ)); return 0; } if (IS_DEBUG_OSPF_EVENT) { zlog_debug( "%s: considering link:%s " "type:%d link_id:%s link_data:%s", __func__, oi->ifp->name, l->m[0].type, inet_ntop(AF_INET, &l->link_id, buf1, BUFSIZ), inet_ntop(AF_INET, &l->link_data, buf2, BUFSIZ)); } if (w->type == OSPF_VERTEX_ROUTER) { /* l is a link from v to w * l2 will be link from w to v */ struct router_lsa_link *l2 = NULL; if (l->m[0].type == LSA_LINK_TYPE_POINTOPOINT) { struct in_addr nexthop = {.s_addr = 0}; /* If the destination is a router which connects to the calculating router via a Point-to-MultiPoint network, the destination's next hop IP address(es) can be determined by examining the destination's router-LSA: each link pointing back to the calculating router and having a Link Data field belonging to the Point-to-MultiPoint network provides an IP address of the next hop router. At this point l is a link from V to W, and V is the root ("us"). If it is a point-to-multipoint interface, then look through the links in the opposite direction (W to V). If any of them have an address that lands within the subnet declared by the PtMP link, then that link is a constituent of the PtMP link, and its address is a nexthop address for V. */ if (oi->type == OSPF_IFTYPE_POINTOPOINT) { /* Having nexthop = 0 is tempting, but NOT acceptable. It breaks AS-External routes with a forwarding address, since ospf_ase_complete_direct_routes() will mistakenly assume we've reached the last hop and should place the forwarding address as nexthop. Also, users may configure multi-access links in p2p mode, so we need the IP to ARP the nexthop. */ struct ospf_neighbor *nbr_w; nbr_w = ospf_nbr_lookup_by_routerid( oi->nbrs, &l->link_id); if (nbr_w != NULL) { added = 1; nexthop = nbr_w->src; } } else if (oi->type == OSPF_IFTYPE_POINTOMULTIPOINT) { struct prefix_ipv4 la; la.family = AF_INET; la.prefixlen = oi->address->prefixlen; /* V links to W on PtMP interface - find the interface address on W */ while ((l2 = ospf_get_next_link(w, v, l2))) { la.prefix = l2->link_data; if (prefix_cmp((struct prefix *)&la, oi->address) != 0) continue; /* link_data is on our PtMP * network */ added = 1; nexthop = l2->link_data; break; } } if (added) { /* found all necessary info to build * nexthop */ nh = vertex_nexthop_new(); nh->oi = oi; nh->router = nexthop; ospf_spf_add_parent(v, w, nh, distance); return 1; } else zlog_info( "%s: could not determine nexthop for link %s", __func__, oi->ifp->name); } /* end point-to-point link from V to W */ else if (l->m[0].type == LSA_LINK_TYPE_VIRTUALLINK) { struct ospf_vl_data *vl_data; /* VLink implementation limitations: * a) vl_data can only reference one nexthop, so * no ECMP * to backbone through VLinks. Though * transit-area * summaries may be considered, and those can * be ECMP. * b) We can only use /one/ VLink, even if * multiple ones * exist this router through multiple * transit-areas. */ vl_data = ospf_vl_lookup(area->ospf, NULL, l->link_id); if (vl_data && CHECK_FLAG(vl_data->flags, OSPF_VL_FLAG_APPROVED)) { nh = vertex_nexthop_new(); nh->oi = vl_data->nexthop.oi; nh->router = vl_data->nexthop.router; ospf_spf_add_parent(v, w, nh, distance); return 1; } else zlog_info( "ospf_nexthop_calculation(): " "vl_data for VL link not found"); } /* end virtual-link from V to W */ return 0; } /* end W is a Router vertex */ else { assert(w->type == OSPF_VERTEX_NETWORK); nh = vertex_nexthop_new(); nh->oi = oi; nh->router.s_addr = 0; /* Nexthop not required */ ospf_spf_add_parent(v, w, nh, distance); return 1; } } /* end V is the root */ /* Check if W's parent is a network connected to root. */ else if (v->type == OSPF_VERTEX_NETWORK) { /* See if any of V's parents are the root. */ for (ALL_LIST_ELEMENTS(v->parents, node, nnode, vp)) { if (vp->parent == area->spf) /* connects to root? */ { /* 16.1.1 para 5. ...the parent vertex is a * network that * directly connects the calculating router to * the destination * router. The list of next hops is then * determined by * examining the destination's router-LSA... */ assert(w->type == OSPF_VERTEX_ROUTER); while ((l = ospf_get_next_link(w, v, l))) { /* ...For each link in the router-LSA * that points back to the * parent network, the link's Link Data * field provides the IP * address of a next hop router. The * outgoing interface to * use can then be derived from the next * hop IP address (or * it can be inherited from the parent * network). */ nh = vertex_nexthop_new(); nh->oi = vp->nexthop->oi; nh->router = l->link_data; added = 1; ospf_spf_add_parent(v, w, nh, distance); } /* Note lack of return is deliberate. See next * comment. */ } } /* NB: This code is non-trivial. * * E.g. it is not enough to know that V connects to the root. It * is * also important that the while above, looping through all * links from * W->V found at least one link, so that we know there is * bi-directional connectivity between V and W (which need not * be the * case, e.g. when OSPF has not yet converged fully). * Otherwise, if * we /always/ return here, without having checked that * root->V->-W * actually resulted in a valid nexthop being created, then we * we will * prevent SPF from finding/using higher cost paths. * * It is important, if root->V->W has not been added, that we * continue * through to the intervening-router nexthop code below. So as * to * ensure other paths to V may be used. This avoids unnecessary * blackholes while OSPF is convergening. * * I.e. we may have arrived at this function, examining V -> W, * via * workable paths other than root -> V, and it's important to * avoid * getting "confused" by non-working root->V->W path - it's * important * to *not* lose the working non-root paths, just because of a * non-viable root->V->W. * * See also bug #330 (required reading!), and: * * http://blogs.oracle.com/paulj/entry/the_difference_a_line_makes */ if (added) return added; } /* 16.1.1 para 4. If there is at least one intervening router in the * current shortest path between the destination and the root, the * destination simply inherits the set of next hops from the * parent. */ if (IS_DEBUG_OSPF_EVENT) zlog_debug("%s: Intervening routers, adding parent(s)", __func__); for (ALL_LIST_ELEMENTS(v->parents, node, nnode, vp)) { added = 1; ospf_spf_add_parent(v, w, vp->nexthop, distance); } return added; } /* RFC2328 Section 16.1 (2). * v is on the SPF tree. Examine the links in v's LSA. Update the list * of candidates with any vertices not already on the list. If a lower-cost * path is found to a vertex already on the candidate list, store the new cost. */ static void ospf_spf_next(struct vertex *v, struct ospf *ospf, struct ospf_area *area, struct vertex_pqueue_head *candidate) { struct ospf_lsa *w_lsa = NULL; uint8_t *p; uint8_t *lim; struct router_lsa_link *l = NULL; struct in_addr *r; int type = 0, lsa_pos = -1, lsa_pos_next = 0; /* If this is a router-LSA, and bit V of the router-LSA (see Section A.4.2:RFC2328) is set, set Area A's TransitCapability to true. */ if (v->type == OSPF_VERTEX_ROUTER) { if (IS_ROUTER_LSA_VIRTUAL((struct router_lsa *)v->lsa)) area->transit = OSPF_TRANSIT_TRUE; } if (IS_DEBUG_OSPF_EVENT) zlog_debug("%s: Next vertex of %s vertex %s", __func__, v->type == OSPF_VERTEX_ROUTER ? "Router" : "Network", inet_ntoa(v->lsa->id)); p = ((uint8_t *)v->lsa) + OSPF_LSA_HEADER_SIZE + 4; lim = ((uint8_t *)v->lsa) + ntohs(v->lsa->length); while (p < lim) { struct vertex *w; unsigned int distance; /* In case of V is Router-LSA. */ if (v->lsa->type == OSPF_ROUTER_LSA) { l = (struct router_lsa_link *)p; lsa_pos = lsa_pos_next; /* LSA link position */ lsa_pos_next++; p += (OSPF_ROUTER_LSA_LINK_SIZE + (l->m[0].tos_count * OSPF_ROUTER_LSA_TOS_SIZE)); /* (a) If this is a link to a stub network, examine the next link in V's LSA. Links to stub networks will be considered in the second stage of the shortest path calculation. */ if ((type = l->m[0].type) == LSA_LINK_TYPE_STUB) continue; /* (b) Otherwise, W is a transit vertex (router or transit network). Look up the vertex W's LSA (router-LSA or network-LSA) in Area A's link state database. */ switch (type) { case LSA_LINK_TYPE_POINTOPOINT: case LSA_LINK_TYPE_VIRTUALLINK: if (type == LSA_LINK_TYPE_VIRTUALLINK) { if (IS_DEBUG_OSPF_EVENT) zlog_debug( "looking up LSA through VL: %s", inet_ntoa(l->link_id)); } w_lsa = ospf_lsa_lookup(ospf, area, OSPF_ROUTER_LSA, l->link_id, l->link_id); if (w_lsa) { if (IS_DEBUG_OSPF_EVENT) zlog_debug( "found Router LSA %s", inet_ntoa(l->link_id)); } break; case LSA_LINK_TYPE_TRANSIT: if (IS_DEBUG_OSPF_EVENT) zlog_debug( "Looking up Network LSA, ID: %s", inet_ntoa(l->link_id)); w_lsa = ospf_lsa_lookup_by_id( area, OSPF_NETWORK_LSA, l->link_id); if (w_lsa) if (IS_DEBUG_OSPF_EVENT) zlog_debug("found the LSA"); break; default: flog_warn(EC_OSPF_LSA, "Invalid LSA link type %d", type); continue; } /* step (d) below */ distance = v->distance + ntohs(l->m[0].metric); } else { /* In case of V is Network-LSA. */ r = (struct in_addr *)p; p += sizeof(struct in_addr); /* Lookup the vertex W's LSA. */ w_lsa = ospf_lsa_lookup_by_id(area, OSPF_ROUTER_LSA, *r); if (w_lsa) { if (IS_DEBUG_OSPF_EVENT) zlog_debug("found Router LSA %s", inet_ntoa(w_lsa->data->id)); } /* step (d) below */ distance = v->distance; } /* (b cont.) If the LSA does not exist, or its LS age is equal to MaxAge, or it does not have a link back to vertex V, examine the next link in V's LSA.[23] */ if (w_lsa == NULL) { if (IS_DEBUG_OSPF_EVENT) zlog_debug("No LSA found"); continue; } if (IS_LSA_MAXAGE(w_lsa)) { if (IS_DEBUG_OSPF_EVENT) zlog_debug("LSA is MaxAge"); continue; } if (ospf_lsa_has_link(w_lsa->data, v->lsa) < 0) { if (IS_DEBUG_OSPF_EVENT) zlog_debug("The LSA doesn't have a link back"); continue; } /* (c) If vertex W is already on the shortest-path tree, examine the next link in the LSA. */ if (w_lsa->stat == LSA_SPF_IN_SPFTREE) { if (IS_DEBUG_OSPF_EVENT) zlog_debug("The LSA is already in SPF"); continue; } /* (d) Calculate the link state cost D of the resulting path from the root to vertex W. D is equal to the sum of the link state cost of the (already calculated) shortest path to vertex V and the advertised cost of the link between vertices V and W. If D is: */ /* calculate link cost D -- moved above */ /* Is there already vertex W in candidate list? */ if (w_lsa->stat == LSA_SPF_NOT_EXPLORED) { /* prepare vertex W. */ w = ospf_vertex_new(w_lsa); /* Calculate nexthop to W. */ if (ospf_nexthop_calculation(area, v, w, l, distance, lsa_pos)) vertex_pqueue_add(candidate, w); else if (IS_DEBUG_OSPF_EVENT) zlog_debug("Nexthop Calc failed"); } else if (w_lsa->stat != LSA_SPF_IN_SPFTREE) { w = w_lsa->stat; /* if D is greater than. */ if (w->distance < distance) { continue; } /* equal to. */ else if (w->distance == distance) { /* Found an equal-cost path to W. * Calculate nexthop of to W from V. */ ospf_nexthop_calculation(area, v, w, l, distance, lsa_pos); } /* less than. */ else { /* Found a lower-cost path to W. * nexthop_calculation is conditional, if it * finds * valid nexthop it will call spf_add_parents, * which * will flush the old parents */ vertex_pqueue_del(candidate, w); ospf_nexthop_calculation(area, v, w, l, distance, lsa_pos); vertex_pqueue_add(candidate, w); } } /* end W is already on the candidate list */ } /* end loop over the links in V's LSA */ } static void ospf_spf_dump(struct vertex *v, int i) { struct listnode *cnode; struct listnode *nnode; struct vertex_parent *parent; if (v->type == OSPF_VERTEX_ROUTER) { if (IS_DEBUG_OSPF_EVENT) zlog_debug("SPF Result: %d [R] %s", i, inet_ntoa(v->lsa->id)); } else { struct network_lsa *lsa = (struct network_lsa *)v->lsa; if (IS_DEBUG_OSPF_EVENT) zlog_debug("SPF Result: %d [N] %s/%d", i, inet_ntoa(v->lsa->id), ip_masklen(lsa->mask)); } if (IS_DEBUG_OSPF_EVENT) for (ALL_LIST_ELEMENTS_RO(v->parents, nnode, parent)) { zlog_debug(" nexthop %p %s %s", (void *)parent->nexthop, inet_ntoa(parent->nexthop->router), parent->nexthop->oi ? IF_NAME(parent->nexthop->oi) : "NULL"); } i++; for (ALL_LIST_ELEMENTS_RO(v->children, cnode, v)) ospf_spf_dump(v, i); } /* Second stage of SPF calculation. */ static void ospf_spf_process_stubs(struct ospf_area *area, struct vertex *v, struct route_table *rt, int parent_is_root) { struct listnode *cnode, *cnnode; struct vertex *child; if (IS_DEBUG_OSPF_EVENT) zlog_debug("ospf_process_stub():processing stubs for area %s", inet_ntoa(area->area_id)); if (v->type == OSPF_VERTEX_ROUTER) { uint8_t *p; uint8_t *lim; struct router_lsa_link *l; struct router_lsa *rlsa; int lsa_pos = 0; if (IS_DEBUG_OSPF_EVENT) zlog_debug( "ospf_process_stubs():processing router LSA, id: %s", inet_ntoa(v->lsa->id)); rlsa = (struct router_lsa *)v->lsa; if (IS_DEBUG_OSPF_EVENT) zlog_debug( "ospf_process_stubs(): we have %d links to process", ntohs(rlsa->links)); p = ((uint8_t *)v->lsa) + OSPF_LSA_HEADER_SIZE + 4; lim = ((uint8_t *)v->lsa) + ntohs(v->lsa->length); while (p < lim) { l = (struct router_lsa_link *)p; p += (OSPF_ROUTER_LSA_LINK_SIZE + (l->m[0].tos_count * OSPF_ROUTER_LSA_TOS_SIZE)); if (l->m[0].type == LSA_LINK_TYPE_STUB) ospf_intra_add_stub(rt, l, v, area, parent_is_root, lsa_pos); lsa_pos++; } } ospf_vertex_dump("ospf_process_stubs(): after examining links: ", v, 1, 1); for (ALL_LIST_ELEMENTS(v->children, cnode, cnnode, child)) { if (CHECK_FLAG(child->flags, OSPF_VERTEX_PROCESSED)) continue; /* the first level of routers connected to the root * should have 'parent_is_root' set, including those * connected via a network vertex. */ if (area->spf == v) parent_is_root = 1; else if (v->type == OSPF_VERTEX_ROUTER) parent_is_root = 0; ospf_spf_process_stubs(area, child, rt, parent_is_root); SET_FLAG(child->flags, OSPF_VERTEX_PROCESSED); } } void ospf_rtrs_free(struct route_table *rtrs) { struct route_node *rn; struct list *or_list; struct ospf_route * or ; struct listnode *node, *nnode; if (IS_DEBUG_OSPF_EVENT) zlog_debug("Route: Router Routing Table free"); for (rn = route_top(rtrs); rn; rn = route_next(rn)) if ((or_list = rn->info) != NULL) { for (ALL_LIST_ELEMENTS(or_list, node, nnode, or)) ospf_route_free(or); list_delete(&or_list); /* Unlock the node. */ rn->info = NULL; route_unlock_node(rn); } route_table_finish(rtrs); } #if 0 static void ospf_rtrs_print (struct route_table *rtrs) { struct route_node *rn; struct list *or_list; struct listnode *ln; struct listnode *pnode; struct ospf_route *or; struct ospf_path *path; char buf1[BUFSIZ]; char buf2[BUFSIZ]; if (IS_DEBUG_OSPF_EVENT) zlog_debug ("ospf_rtrs_print() start"); for (rn = route_top (rtrs); rn; rn = route_next (rn)) if ((or_list = rn->info) != NULL) for (ALL_LIST_ELEMENTS_RO (or_list, ln, or)) { switch (or->path_type) { case OSPF_PATH_INTRA_AREA: if (IS_DEBUG_OSPF_EVENT) zlog_debug ("%s [%d] area: %s", inet_ntop (AF_INET, &or->id, buf1, BUFSIZ), or->cost, inet_ntop (AF_INET, &or->u.std.area_id, buf2, BUFSIZ)); break; case OSPF_PATH_INTER_AREA: if (IS_DEBUG_OSPF_EVENT) zlog_debug ("%s IA [%d] area: %s", inet_ntop (AF_INET, &or->id, buf1, BUFSIZ), or->cost, inet_ntop (AF_INET, &or->u.std.area_id, buf2, BUFSIZ)); break; default: break; } for (ALL_LIST_ELEMENTS_RO (or->paths, pnode, path)) { if (path->nexthop.s_addr == 0) { if (IS_DEBUG_OSPF_EVENT) zlog_debug (" directly attached to %s\r", ifindex2ifname (path->ifindex), VRF_DEFAULT); } else { if (IS_DEBUG_OSPF_EVENT) zlog_debug (" via %s, %s\r", inet_ntoa (path->nexthop), ifindex2ifname (path->ifindex), VRF_DEFAULT); } } } zlog_debug ("ospf_rtrs_print() end"); } #endif /* Calculating the shortest-path tree for an area. */ static void ospf_spf_calculate(struct ospf *ospf, struct ospf_area *area, struct route_table *new_table, struct route_table *new_rtrs) { struct vertex_pqueue_head candidate; struct vertex *v; if (IS_DEBUG_OSPF_EVENT) { zlog_debug("ospf_spf_calculate: Start"); zlog_debug("ospf_spf_calculate: running Dijkstra for area %s", inet_ntoa(area->area_id)); } /* Check router-lsa-self. If self-router-lsa is not yet allocated, return this area's calculation. */ if (!area->router_lsa_self) { if (IS_DEBUG_OSPF_EVENT) zlog_debug( "ospf_spf_calculate: " "Skip area %s's calculation due to empty router_lsa_self", inet_ntoa(area->area_id)); return; } /* RFC2328 16.1. (1). */ /* Initialize the algorithm's data structures. */ /* This function scans all the LSA database and set the stat field to * LSA_SPF_NOT_EXPLORED. */ lsdb_clean_stat(area->lsdb); /* Create a new heap for the candidates. */ vertex_pqueue_init(&candidate); /* Initialize the shortest-path tree to only the root (which is the router doing the calculation). */ ospf_spf_init(area); v = area->spf; /* Set LSA position to LSA_SPF_IN_SPFTREE. This vertex is the root of * the * spanning tree. */ v->lsa_p->stat = LSA_SPF_IN_SPFTREE; /* Set Area A's TransitCapability to false. */ area->transit = OSPF_TRANSIT_FALSE; area->shortcut_capability = 1; for (;;) { /* RFC2328 16.1. (2). */ ospf_spf_next(v, ospf, area, &candidate); /* RFC2328 16.1. (3). */ /* If at this step the candidate list is empty, the shortest- path tree (of transit vertices) has been completely built and this stage of the procedure terminates. */ /* Otherwise, choose the vertex belonging to the candidate list that is closest to the root, and add it to the shortest-path tree (removing it from the candidate list in the process). */ /* Extract from the candidates the node with the lower key. */ v = vertex_pqueue_pop(&candidate); if (!v) break; /* Update stat field in vertex. */ v->lsa_p->stat = LSA_SPF_IN_SPFTREE; ospf_vertex_add_parent(v); /* RFC2328 16.1. (4). */ if (v->type == OSPF_VERTEX_ROUTER) ospf_intra_add_router(new_rtrs, v, area); else ospf_intra_add_transit(new_table, v, area); /* RFC2328 16.1. (5). */ /* Iterate the algorithm by returning to Step 2. */ } /* end loop until no more candidate vertices */ if (IS_DEBUG_OSPF_EVENT) { ospf_spf_dump(area->spf, 0); ospf_route_table_dump(new_table); } /* Second stage of SPF calculation procedure's */ ospf_spf_process_stubs(area, area->spf, new_table, 0); /* Free candidate queue. */ //vertex_pqueue_fini(&candidate); ospf_vertex_dump(__func__, area->spf, 0, 1); /* Free nexthop information, canonical versions of which are attached * the first level of router vertices attached to the root vertex, see * ospf_nexthop_calculation. */ ospf_canonical_nexthops_free(area->spf); /* Increment SPF Calculation Counter. */ area->spf_calculation++; monotime(&area->ospf->ts_spf); area->ts_spf = area->ospf->ts_spf; if (IS_DEBUG_OSPF_EVENT) zlog_debug("ospf_spf_calculate: Stop. %zd vertices", mtype_stats_alloc(MTYPE_OSPF_VERTEX)); /* Free SPF vertices, but not the list. List has ospf_vertex_free * as deconstructor. */ list_delete_all_node(&vertex_list); } /* Timer for SPF calculation. */ static int ospf_spf_calculate_timer(struct thread *thread) { struct ospf *ospf = THREAD_ARG(thread); struct route_table *new_table, *new_rtrs; struct ospf_area *area; struct listnode *node, *nnode; struct timeval start_time, spf_start_time; int areas_processed = 0; unsigned long ia_time, prune_time, rt_time; unsigned long abr_time, total_spf_time, spf_time; char rbuf[32]; /* reason_buf */ if (IS_DEBUG_OSPF_EVENT) zlog_debug("SPF: Timer (SPF calculation expire)"); ospf->t_spf_calc = NULL; monotime(&spf_start_time); /* Allocate new table tree. */ new_table = route_table_init(); new_rtrs = route_table_init(); ospf_vl_unapprove(ospf); /* Calculate SPF for each area. */ for (ALL_LIST_ELEMENTS(ospf->areas, node, nnode, area)) { /* Do backbone last, so as to first discover intra-area paths * for any back-bone virtual-links */ if (ospf->backbone && ospf->backbone == area) continue; ospf_spf_calculate(ospf, area, new_table, new_rtrs); areas_processed++; } /* SPF for backbone, if required */ if (ospf->backbone) { ospf_spf_calculate(ospf, ospf->backbone, new_table, new_rtrs); areas_processed++; } spf_time = monotime_since(&spf_start_time, NULL); ospf_vl_shut_unapproved(ospf); monotime(&start_time); ospf_ia_routing(ospf, new_table, new_rtrs); ia_time = monotime_since(&start_time, NULL); monotime(&start_time); ospf_prune_unreachable_networks(new_table); ospf_prune_unreachable_routers(new_rtrs); prune_time = monotime_since(&start_time, NULL); /* AS-external-LSA calculation should not be performed here. */ /* If new Router Route is installed, then schedule re-calculate External routes. */ if (1) ospf_ase_calculate_schedule(ospf); ospf_ase_calculate_timer_add(ospf); if (IS_DEBUG_OSPF_EVENT) zlog_debug( "%s: ospf install new route, vrf %s id %u new_table count %lu", __PRETTY_FUNCTION__, ospf_vrf_id_to_name(ospf->vrf_id), ospf->vrf_id, new_table->count); /* Update routing table. */ monotime(&start_time); ospf_route_install(ospf, new_table); rt_time = monotime_since(&start_time, NULL); /* Update ABR/ASBR routing table */ if (ospf->old_rtrs) { /* old_rtrs's node holds linked list of ospf_route. --kunihiro. */ /* ospf_route_delete (ospf->old_rtrs); */ ospf_rtrs_free(ospf->old_rtrs); } ospf->old_rtrs = ospf->new_rtrs; ospf->new_rtrs = new_rtrs; monotime(&start_time); if (IS_OSPF_ABR(ospf)) ospf_abr_task(ospf); abr_time = monotime_since(&start_time, NULL); /* Schedule Segment Routing update */ ospf_sr_update_timer_add(ospf); total_spf_time = monotime_since(&spf_start_time, &ospf->ts_spf_duration); rbuf[0] = '\0'; if (spf_reason_flags) { if (spf_reason_flags & SPF_FLAG_ROUTER_LSA_INSTALL) strncat(rbuf, "R, ", sizeof(rbuf) - strlen(rbuf) - 1); if (spf_reason_flags & SPF_FLAG_NETWORK_LSA_INSTALL) strncat(rbuf, "N, ", sizeof(rbuf) - strlen(rbuf) - 1); if (spf_reason_flags & SPF_FLAG_SUMMARY_LSA_INSTALL) strncat(rbuf, "S, ", sizeof(rbuf) - strlen(rbuf) - 1); if (spf_reason_flags & SPF_FLAG_ASBR_SUMMARY_LSA_INSTALL) strncat(rbuf, "AS, ", sizeof(rbuf) - strlen(rbuf) - 1); if (spf_reason_flags & SPF_FLAG_ABR_STATUS_CHANGE) strncat(rbuf, "ABR, ", sizeof(rbuf) - strlen(rbuf) - 1); if (spf_reason_flags & SPF_FLAG_ASBR_STATUS_CHANGE) strncat(rbuf, "ASBR, ", sizeof(rbuf) - strlen(rbuf) - 1); if (spf_reason_flags & SPF_FLAG_MAXAGE) strncat(rbuf, "M, ", sizeof(rbuf) - strlen(rbuf) - 1); size_t rbuflen = strlen(rbuf); if (rbuflen >= 2) rbuf[rbuflen - 2] = '\0'; /* skip the last ", " */ else rbuf[0] = '\0'; } if (IS_DEBUG_OSPF_EVENT) { zlog_info("SPF Processing Time(usecs): %ld", total_spf_time); zlog_info("\t SPF Time: %ld", spf_time); zlog_info("\t InterArea: %ld", ia_time); zlog_info("\t Prune: %ld", prune_time); zlog_info("\tRouteInstall: %ld", rt_time); if (IS_OSPF_ABR(ospf)) zlog_info("\t ABR: %ld (%d areas)", abr_time, areas_processed); zlog_info("Reason(s) for SPF: %s", rbuf); } ospf_clear_spf_reason_flags(); return 0; } /* Add schedule for SPF calculation. To avoid frequenst SPF calc, we set timer for SPF calc. */ void ospf_spf_calculate_schedule(struct ospf *ospf, ospf_spf_reason_t reason) { unsigned long delay, elapsed, ht; if (IS_DEBUG_OSPF_EVENT) zlog_debug("SPF: calculation timer scheduled"); /* OSPF instance does not exist. */ if (ospf == NULL) return; ospf_spf_set_reason(reason); /* SPF calculation timer is already scheduled. */ if (ospf->t_spf_calc) { if (IS_DEBUG_OSPF_EVENT) zlog_debug( "SPF: calculation timer is already scheduled: %p", (void *)ospf->t_spf_calc); return; } elapsed = monotime_since(&ospf->ts_spf, NULL) / 1000; ht = ospf->spf_holdtime * ospf->spf_hold_multiplier; if (ht > ospf->spf_max_holdtime) ht = ospf->spf_max_holdtime; /* Get SPF calculation delay time. */ if (elapsed < ht) { /* Got an event within the hold time of last SPF. We need to * increase the hold_multiplier, if it's not already at/past * maximum value, and wasn't already increased.. */ if (ht < ospf->spf_max_holdtime) ospf->spf_hold_multiplier++; /* always honour the SPF initial delay */ if ((ht - elapsed) < ospf->spf_delay) delay = ospf->spf_delay; else delay = ht - elapsed; } else { /* Event is past required hold-time of last SPF */ delay = ospf->spf_delay; ospf->spf_hold_multiplier = 1; } if (IS_DEBUG_OSPF_EVENT) zlog_debug("SPF: calculation timer delay = %ld msec", delay); ospf->t_spf_calc = NULL; thread_add_timer_msec(master, ospf_spf_calculate_timer, ospf, delay, &ospf->t_spf_calc); } frr-7.2.1/ospfd/ospf_spf.h0000644000000000000000000000505313610377563012322 00000000000000/* * OSPF calculation. * Copyright (C) 1999 Kunihiro Ishiguro * * This file is part of GNU Zebra. * * GNU Zebra is free software; you can redistribute it and/or modify it * under the terms of the GNU General Public License as published by the * Free Software Foundation; either version 2, or (at your option) any * later version. * * GNU Zebra 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; see the file COPYING; if not, write to the Free Software * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA */ #ifndef _QUAGGA_OSPF_SPF_H #define _QUAGGA_OSPF_SPF_H #include "typesafe.h" /* values for vertex->type */ #define OSPF_VERTEX_ROUTER 1 /* for a Router-LSA */ #define OSPF_VERTEX_NETWORK 2 /* for a Network-LSA */ /* values for vertex->flags */ #define OSPF_VERTEX_PROCESSED 0x01 /* The "root" is the node running the SPF calculation */ PREDECL_SKIPLIST_NONUNIQ(vertex_pqueue) /* A router or network in an area */ struct vertex { struct vertex_pqueue_item pqi; uint8_t flags; uint8_t type; /* copied from LSA header */ struct in_addr id; /* copied from LSA header */ struct ospf_lsa *lsa_p; struct lsa_header *lsa; /* Router or Network LSA */ uint32_t distance; /* from root to this vertex */ struct list *parents; /* list of parents in SPF tree */ struct list *children; /* list of children in SPF tree*/ }; /* A nexthop taken on the root node to get to this (parent) vertex */ struct vertex_nexthop { struct ospf_interface *oi; /* output intf on root node */ struct in_addr router; /* router address to send to */ }; struct vertex_parent { struct vertex_nexthop *nexthop; /* link to nexthop info for this parent */ struct vertex *parent; /* parent vertex */ int backlink; /* index back to parent for router-lsa's */ }; /* What triggered the SPF ? */ typedef enum { SPF_FLAG_ROUTER_LSA_INSTALL = 1, SPF_FLAG_NETWORK_LSA_INSTALL, SPF_FLAG_SUMMARY_LSA_INSTALL, SPF_FLAG_ASBR_SUMMARY_LSA_INSTALL, SPF_FLAG_MAXAGE, SPF_FLAG_ABR_STATUS_CHANGE, SPF_FLAG_ASBR_STATUS_CHANGE, SPF_FLAG_CONFIG_CHANGE, } ospf_spf_reason_t; extern void ospf_spf_calculate_schedule(struct ospf *, ospf_spf_reason_t); extern void ospf_rtrs_free(struct route_table *); /* void ospf_spf_calculate_timer_add (); */ #endif /* _QUAGGA_OSPF_SPF_H */ frr-7.2.1/ospfd/ospf_sr.c0000644000000000000000000020024713610377563012153 00000000000000/* * This is an implementation of Segment Routing * as per draft draft-ietf-ospf-segment-routing-extensions-24 * * Module name: Segment Routing * * Author: Olivier Dugeon * Author: Anselme Sawadogo * * Copyright (C) 2016 - 2018 Orange Labs http://www.orange.com * * This program is free software; you can redistribute it and/or modify it * under the terms of the GNU General Public License as published by the Free * Software Foundation; either version 2 of the License, or (at your option) * any later version. * * This program is distributed in the hope that it will be useful, but WITHOUT * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for * more details. * * You should have received a copy of the GNU General Public License along * with this program; see the file COPYING; if not, write to the Free Software * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA */ #ifdef HAVE_CONFIG_H #include "config.h" #endif #include #include #include #include #include "command.h" #include "hash.h" #include "if.h" #include "if.h" #include "jhash.h" #include "libospf.h" /* for ospf interface types */ #include "linklist.h" #include "log.h" #include "memory.h" #include "monotime.h" #include "network.h" #include "prefix.h" #include "sockunion.h" /* for inet_aton() */ #include "stream.h" #include "table.h" #include "thread.h" #include "vty.h" #include "zclient.h" #include #include "ospf_errors.h" #include "ospfd/ospfd.h" #include "ospfd/ospf_interface.h" #include "ospfd/ospf_ism.h" #include "ospfd/ospf_asbr.h" #include "ospfd/ospf_lsa.h" #include "ospfd/ospf_lsdb.h" #include "ospfd/ospf_neighbor.h" #include "ospfd/ospf_nsm.h" #include "ospfd/ospf_flood.h" #include "ospfd/ospf_packet.h" #include "ospfd/ospf_spf.h" #include "ospfd/ospf_dump.h" #include "ospfd/ospf_route.h" #include "ospfd/ospf_ase.h" #include "ospfd/ospf_sr.h" #include "ospfd/ospf_ri.h" #include "ospfd/ospf_ext.h" #include "ospfd/ospf_zebra.h" /* * Global variable to manage Segment Routing on this node. * Note that all parameter values are stored in network byte order. */ static struct ospf_sr_db OspfSR; static void ospf_sr_register_vty(void); static inline void del_sid_nhlfe(struct sr_nhlfe nhlfe); /* * Segment Routing Data Base functions */ /* Hash function for Segment Routing entry */ static unsigned int sr_hash(const void *p) { const struct in_addr *rid = p; return jhash_1word(rid->s_addr, 0); } /* Compare 2 Router ID hash entries based on SR Node */ static bool sr_cmp(const void *p1, const void *p2) { const struct sr_node *srn = p1; const struct in_addr *rid = p2; return IPV4_ADDR_SAME(&srn->adv_router, rid); } /* Functions to remove an SR Link */ static void del_sr_link(void *val) { struct sr_link *srl = (struct sr_link *)val; del_sid_nhlfe(srl->nhlfe[0]); del_sid_nhlfe(srl->nhlfe[1]); XFREE(MTYPE_OSPF_SR_PARAMS, val); } /* Functions to remove an SR Prefix */ static void del_sr_pref(void *val) { struct sr_prefix *srp = (struct sr_prefix *)val; del_sid_nhlfe(srp->nhlfe); XFREE(MTYPE_OSPF_SR_PARAMS, val); } /* Allocate new Segment Routine node */ static struct sr_node *sr_node_new(struct in_addr *rid) { if (rid == NULL) return NULL; struct sr_node *new; /* Allocate Segment Routing node memory */ new = XCALLOC(MTYPE_OSPF_SR_PARAMS, sizeof(struct sr_node)); /* Default Algorithm, SRGB and MSD */ for (int i = 0; i < ALGORITHM_COUNT; i++) new->algo[i] = SR_ALGORITHM_UNSET; new->srgb.range_size = 0; new->srgb.lower_bound = 0; new->msd = 0; /* Create Link, Prefix and Range TLVs list */ new->ext_link = list_new(); new->ext_prefix = list_new(); new->ext_link->del = del_sr_link; new->ext_prefix->del = del_sr_pref; IPV4_ADDR_COPY(&new->adv_router, rid); new->neighbor = NULL; new->instance = 0; if (IS_DEBUG_OSPF_SR) zlog_debug(" |- Created new SR node for %s", inet_ntoa(new->adv_router)); return new; } /* Delete Segment Routing node */ static void sr_node_del(struct sr_node *srn) { /* Sanity Check */ if (srn == NULL) return; /* Clean Extended Link */ list_delete(&srn->ext_link); /* Clean Prefix List */ list_delete(&srn->ext_prefix); XFREE(MTYPE_OSPF_SR_PARAMS, srn); } /* Get SR Node for a given nexthop */ static struct sr_node *get_sr_node_by_nexthop(struct ospf *ospf, struct in_addr nexthop) { struct ospf_interface *oi = NULL; struct ospf_neighbor *nbr = NULL; struct listnode *node; struct route_node *rn; struct sr_node *srn; bool found; /* Sanity check */ if (OspfSR.neighbors == NULL) return NULL; if (IS_DEBUG_OSPF_SR) zlog_debug(" |- Search SR-Node for nexthop %s", inet_ntoa(nexthop)); /* First, search neighbor Router ID for this nexthop */ found = false; for (ALL_LIST_ELEMENTS_RO(ospf->oiflist, node, oi)) { for (rn = route_top(oi->nbrs); rn; rn = route_next(rn)) { nbr = rn->info; if ((nbr) && (IPV4_ADDR_SAME(&nexthop, &nbr->src))) { found = true; break; } } if (found) break; } if (!found) return NULL; if (IS_DEBUG_OSPF_SR) zlog_debug(" |- Found nexthop Router ID %s", inet_ntoa(nbr->router_id)); /* Then, search SR Node */ srn = (struct sr_node *)hash_lookup(OspfSR.neighbors, &nbr->router_id); return srn; } /* * Segment Routing Initialization functions */ /* Segment Routing starter function */ static int ospf_sr_start(struct ospf *ospf) { struct route_node *rn; struct ospf_lsa *lsa; struct sr_node *srn; int rc = 0; if (IS_DEBUG_OSPF_SR) zlog_debug("SR (%s): Start Segment Routing", __func__); /* Initialize self SR Node */ srn = hash_get(OspfSR.neighbors, (void *)&(ospf->router_id), (void *)sr_node_new); /* Complete & Store self SR Node */ srn->srgb.range_size = OspfSR.srgb.range_size; srn->srgb.lower_bound = OspfSR.srgb.lower_bound; srn->algo[0] = OspfSR.algo[0]; srn->msd = OspfSR.msd; OspfSR.self = srn; if (IS_DEBUG_OSPF_EVENT) zlog_debug("SR (%s): Update SR-DB from LSDB", __func__); /* Start by looking to Router Info & Extended LSA in lsdb */ if ((ospf != NULL) && (ospf->backbone != NULL)) { LSDB_LOOP (OPAQUE_AREA_LSDB(ospf->backbone), rn, lsa) { if (IS_LSA_MAXAGE(lsa) || IS_LSA_SELF(lsa)) continue; int lsa_id = GET_OPAQUE_TYPE(ntohl(lsa->data->id.s_addr)); switch (lsa_id) { case OPAQUE_TYPE_ROUTER_INFORMATION_LSA: ospf_sr_ri_lsa_update(lsa); break; case OPAQUE_TYPE_EXTENDED_PREFIX_LSA: ospf_sr_ext_prefix_lsa_update(lsa); break; case OPAQUE_TYPE_EXTENDED_LINK_LSA: ospf_sr_ext_link_lsa_update(lsa); break; default: break; } } } rc = 1; return rc; } /* Stop Segment Routing */ static void ospf_sr_stop(void) { if (IS_DEBUG_OSPF_SR) zlog_debug("SR (%s): Stop Segment Routing", __func__); /* * Remove all SR Nodes from the Hash table. Prefix and Link SID will * be remove though list_delete() call. See sr_node_del() */ hash_clean(OspfSR.neighbors, (void *)sr_node_del); } /* * Segment Routing initialize function * * @param - nothing * * @return 0 if OK, -1 otherwise */ int ospf_sr_init(void) { int rc = -1; if (IS_DEBUG_OSPF_SR) zlog_info("SR (%s): Initialize SR Data Base", __func__); memset(&OspfSR, 0, sizeof(struct ospf_sr_db)); OspfSR.enabled = false; /* Only AREA flooding is supported in this release */ OspfSR.scope = OSPF_OPAQUE_AREA_LSA; /* Initialize SRGB, Algorithms and MSD TLVs */ /* Only Algorithm SPF is supported */ OspfSR.algo[0] = SR_ALGORITHM_SPF; for (int i = 1; i < ALGORITHM_COUNT; i++) OspfSR.algo[i] = SR_ALGORITHM_UNSET; OspfSR.srgb.range_size = MPLS_DEFAULT_MAX_SRGB_SIZE; OspfSR.srgb.lower_bound = MPLS_DEFAULT_MIN_SRGB_LABEL; OspfSR.msd = 0; /* Initialize Hash table for neighbor SR nodes */ OspfSR.neighbors = hash_create(sr_hash, sr_cmp, "OSPF_SR"); if (OspfSR.neighbors == NULL) return rc; /* Initialize Route Table for prefix */ OspfSR.prefix = route_table_init(); if (OspfSR.prefix == NULL) return rc; /* Register Segment Routing VTY command */ ospf_sr_register_vty(); rc = 0; return rc; } /* * Segment Routing termination function * * @param - nothing * @return - nothing */ void ospf_sr_term(void) { /* Stop Segment Routing */ ospf_sr_stop(); /* Clear SR Node Table */ if (OspfSR.neighbors) hash_free(OspfSR.neighbors); /* Clear Prefix Table */ if (OspfSR.prefix) route_table_finish(OspfSR.prefix); OspfSR.enabled = false; OspfSR.self = NULL; } /* * Segment Routing finish function * * @param - nothing * @return - nothing */ void ospf_sr_finish(void) { /* Stop Segment Routing */ ospf_sr_stop(); OspfSR.enabled = false; } /* * Following functions are used to manipulate the * Next Hop Label Forwarding entry (NHLFE) */ /* Compute label from index */ static mpls_label_t index2label(uint32_t index, struct sr_srgb srgb) { mpls_label_t label; label = srgb.lower_bound + index; if (label > (srgb.lower_bound + srgb.range_size)) return MPLS_INVALID_LABEL; else return label; } /* Get neighbor full structure from address */ static struct ospf_neighbor *get_neighbor_by_addr(struct ospf *top, struct in_addr addr) { struct ospf_neighbor *nbr; struct ospf_interface *oi; struct listnode *node; struct route_node *rn; /* Sanity Check */ if (top == NULL) return NULL; for (ALL_LIST_ELEMENTS_RO(top->oiflist, node, oi)) for (rn = route_top(oi->nbrs); rn; rn = route_next(rn)) { nbr = rn->info; if (nbr) if (IPV4_ADDR_SAME(&nbr->address.u.prefix4, &addr) || IPV4_ADDR_SAME(&nbr->router_id, &addr)) { route_unlock_node(rn); return nbr; } } return NULL; } /* Get OSPF Path from address */ static struct ospf_path *get_nexthop_by_addr(struct ospf *top, struct prefix_ipv4 p) { struct ospf_route * or ; struct ospf_path *path; struct listnode *node; struct route_node *rn; /* Sanity Check */ if (top == NULL) return NULL; if (IS_DEBUG_OSPF_SR) zlog_debug(" |- Search Nexthop for prefix %s/%u", inet_ntoa(p.prefix), p.prefixlen); rn = route_node_lookup(top->new_table, (struct prefix *)&p); /* * Check if we found an OSPF route. May be NULL if SPF has not * yet populate routing table for this prefix. */ if (rn == NULL) return NULL; route_unlock_node(rn); or = rn->info; if (or == NULL) return NULL; /* Then search path from this route */ for (ALL_LIST_ELEMENTS_RO(or->paths, node, path)) if (path->nexthop.s_addr != INADDR_ANY || path->ifindex != 0) return path; return NULL; } /* Compute NHLFE entry for Extended Link */ static int compute_link_nhlfe(struct sr_link *srl) { struct ospf *top = ospf_lookup_by_vrf_id(VRF_DEFAULT); struct ospf_neighbor *nh; int rc = 0; if (IS_DEBUG_OSPF_SR) zlog_debug(" |- Compute NHLFE for link %s/%u", inet_ntoa(srl->nhlfe[0].prefv4.prefix), srl->nhlfe[0].prefv4.prefixlen); /* First determine the OSPF Neighbor */ nh = get_neighbor_by_addr(top, srl->nhlfe[0].nexthop); /* Neighbor could be not found when OSPF Adjacency just fire up * because SPF don't yet populate routing table. This NHLFE will * be fixed later when SR SPF schedule will be called. */ if (nh == NULL) return rc; if (IS_DEBUG_OSPF_SR) zlog_debug(" |- Found nexthop NHLFE %s", inet_ntoa(nh->router_id)); /* Set ifindex for this neighbor */ srl->nhlfe[0].ifindex = nh->oi->ifp->ifindex; srl->nhlfe[1].ifindex = nh->oi->ifp->ifindex; /* Update neighbor address for LAN_ADJ_SID */ if (srl->type == LAN_ADJ_SID) { IPV4_ADDR_COPY(&srl->nhlfe[0].nexthop, &nh->src); IPV4_ADDR_COPY(&srl->nhlfe[1].nexthop, &nh->src); } /* Set Input & Output Label */ if (CHECK_FLAG(srl->flags[0], EXT_SUBTLV_LINK_ADJ_SID_VFLG)) srl->nhlfe[0].label_in = srl->sid[0]; else srl->nhlfe[0].label_in = index2label(srl->sid[0], srl->srn->srgb); if (CHECK_FLAG(srl->flags[1], EXT_SUBTLV_LINK_ADJ_SID_VFLG)) srl->nhlfe[1].label_in = srl->sid[1]; else srl->nhlfe[1].label_in = index2label(srl->sid[1], srl->srn->srgb); srl->nhlfe[0].label_out = MPLS_LABEL_IMPLICIT_NULL; srl->nhlfe[1].label_out = MPLS_LABEL_IMPLICIT_NULL; rc = 1; return rc; } /* * Compute NHLFE entry for Extended Prefix * * @param srp - Segment Routing Prefix * * @return -1 if next hop is not found, 0 if nexthop has not changed * and 1 if success */ static int compute_prefix_nhlfe(struct sr_prefix *srp) { struct ospf *top = ospf_lookup_by_vrf_id(VRF_DEFAULT); struct ospf_path *nh = NULL; struct sr_node *srnext; int rc = -1; if (IS_DEBUG_OSPF_SR) zlog_debug(" |- Compute NHLFE for prefix %s/%u", inet_ntoa(srp->nhlfe.prefv4.prefix), srp->nhlfe.prefv4.prefixlen); /* First determine the nexthop */ nh = get_nexthop_by_addr(top, srp->nhlfe.prefv4); /* Nexthop could be not found when OSPF Adjacency just fire up * because SPF don't yet populate routing table. This NHLFE will * be fixed later when SR SPF schedule will be called. */ if (nh == NULL) return rc; /* Check if NextHop has changed when call after running a new SPF */ if (IPV4_ADDR_SAME(&nh->nexthop, &srp->nhlfe.nexthop) && (nh->ifindex == srp->nhlfe.ifindex)) return 0; if (IS_DEBUG_OSPF_SR) zlog_debug(" |- Found new next hop for this NHLFE: %s", inet_ntoa(nh->nexthop)); /* * Get SR-Node for this nexthop. Could be not yet available * as Extende Link / Prefix and Router Information are flooded * after LSA Type 1 & 2 which populate the OSPF Route Table */ srnext = get_sr_node_by_nexthop(top, nh->nexthop); if (srnext == NULL) return rc; /* And store this information for later update if SR Node is found */ srnext->neighbor = OspfSR.self; if (IPV4_ADDR_SAME(&srnext->adv_router, &srp->adv_router)) srp->nexthop = NULL; else srp->nexthop = srnext; /* * SR Node could be known, but SRGB could be not initialize * This is due to the fact that Extended Link / Prefix could * be received before corresponding Router Information LSA */ if ((srnext == NULL) || (srnext->srgb.lower_bound == 0) || (srnext->srgb.range_size == 0)) return rc; if (IS_DEBUG_OSPF_SR) zlog_debug(" |- Found SRGB %u/%u for next hop SR-Node %s", srnext->srgb.range_size, srnext->srgb.lower_bound, inet_ntoa(srnext->adv_router)); /* Set ip addr & ifindex for this neighbor */ IPV4_ADDR_COPY(&srp->nhlfe.nexthop, &nh->nexthop); srp->nhlfe.ifindex = nh->ifindex; /* Compute Input Label with self SRGB */ srp->nhlfe.label_in = index2label(srp->sid, OspfSR.srgb); /* * and Output Label with Next hop SR Node SRGB or Implicit Null label * if next hop is the destination and request PHP */ if ((srp->nexthop == NULL) && (!CHECK_FLAG(srp->flags, EXT_SUBTLV_PREFIX_SID_NPFLG))) srp->nhlfe.label_out = MPLS_LABEL_IMPLICIT_NULL; else if (CHECK_FLAG(srp->flags, EXT_SUBTLV_PREFIX_SID_VFLG)) srp->nhlfe.label_out = srp->sid; else srp->nhlfe.label_out = index2label(srp->sid, srnext->srgb); if (IS_DEBUG_OSPF_SR) zlog_debug(" |- Computed new labels in: %u out: %u", srp->nhlfe.label_in, srp->nhlfe.label_out); rc = 1; return rc; } /* Send MPLS Label entry to Zebra for installation or deletion */ static int ospf_zebra_send_mpls_labels(int cmd, struct sr_nhlfe nhlfe) { struct stream *s; /* Reset stream. */ s = zclient->obuf; stream_reset(s); zclient_create_header(s, cmd, VRF_DEFAULT); stream_putc(s, ZEBRA_LSP_SR); /* OSPF Segment Routing currently support only IPv4 */ stream_putl(s, nhlfe.prefv4.family); stream_put_in_addr(s, &nhlfe.prefv4.prefix); stream_putc(s, nhlfe.prefv4.prefixlen); stream_put_in_addr(s, &nhlfe.nexthop); stream_putl(s, nhlfe.ifindex); stream_putc(s, OSPF_SR_PRIORITY_DEFAULT); stream_putl(s, nhlfe.label_in); stream_putl(s, nhlfe.label_out); /* Put length at the first point of the stream. */ stream_putw_at(s, 0, stream_get_endp(s)); if (IS_DEBUG_OSPF_SR) zlog_debug(" |- %s LSP %u/%u for %s/%u via %u", cmd == ZEBRA_MPLS_LABELS_ADD ? "Add" : "Delete", nhlfe.label_in, nhlfe.label_out, inet_ntoa(nhlfe.prefv4.prefix), nhlfe.prefv4.prefixlen, nhlfe.ifindex); return zclient_send_message(zclient); } /* Request zebra to install/remove FEC in FIB */ static int ospf_zebra_send_mpls_ftn(int cmd, struct sr_nhlfe nhlfe) { struct zapi_route api; struct zapi_nexthop *api_nh; /* Support only IPv4 */ if (nhlfe.prefv4.family != AF_INET) return -1; memset(&api, 0, sizeof(api)); api.vrf_id = VRF_DEFAULT; api.type = ZEBRA_ROUTE_OSPF; api.safi = SAFI_UNICAST; memcpy(&api.prefix, &nhlfe.prefv4, sizeof(struct prefix_ipv4)); if (cmd == ZEBRA_ROUTE_ADD) { /* Metric value. */ SET_FLAG(api.message, ZAPI_MESSAGE_METRIC); api.metric = OSPF_SR_DEFAULT_METRIC; /* Nexthop */ SET_FLAG(api.message, ZAPI_MESSAGE_NEXTHOP); api_nh = &api.nexthops[0]; IPV4_ADDR_COPY(&api_nh->gate.ipv4, &nhlfe.nexthop); api_nh->type = NEXTHOP_TYPE_IPV4_IFINDEX; api_nh->ifindex = nhlfe.ifindex; /* MPLS labels */ SET_FLAG(api.message, ZAPI_MESSAGE_LABEL); api_nh->labels[0] = nhlfe.label_out; api_nh->label_num = 1; api_nh->vrf_id = VRF_DEFAULT; api.nexthop_num = 1; } if (IS_DEBUG_OSPF_SR) zlog_debug(" |- %s FEC %u for %s/%u via %u", cmd == ZEBRA_ROUTE_ADD ? "Add" : "Delete", nhlfe.label_out, inet_ntoa(nhlfe.prefv4.prefix), nhlfe.prefv4.prefixlen, nhlfe.ifindex); return zclient_route_send(cmd, zclient, &api); } /* Add new NHLFE entry for SID */ static inline void add_sid_nhlfe(struct sr_nhlfe nhlfe) { if ((nhlfe.label_in != 0) && (nhlfe.label_out != 0)) { ospf_zebra_send_mpls_labels(ZEBRA_MPLS_LABELS_ADD, nhlfe); if (nhlfe.label_out != MPLS_LABEL_IMPLICIT_NULL) ospf_zebra_send_mpls_ftn(ZEBRA_ROUTE_ADD, nhlfe); } } /* Remove NHLFE entry for SID */ static inline void del_sid_nhlfe(struct sr_nhlfe nhlfe) { if ((nhlfe.label_in != 0) && (nhlfe.label_out != 0)) { ospf_zebra_send_mpls_labels(ZEBRA_MPLS_LABELS_DELETE, nhlfe); if (nhlfe.label_out != MPLS_LABEL_IMPLICIT_NULL) ospf_zebra_send_mpls_ftn(ZEBRA_ROUTE_DELETE, nhlfe); } } /* Update NHLFE entry for SID */ static inline void update_sid_nhlfe(struct sr_nhlfe n1, struct sr_nhlfe n2) { del_sid_nhlfe(n1); add_sid_nhlfe(n2); } /* * Functions to parse and get Extended Link / Prefix * TLVs and SubTLVs */ /* Extended Link SubTLVs Getter */ static struct sr_link *get_ext_link_sid(struct tlv_header *tlvh) { struct sr_link *srl; struct ext_tlv_link *link = (struct ext_tlv_link *)tlvh; struct ext_subtlv_adj_sid *adj_sid; struct ext_subtlv_lan_adj_sid *lan_sid; struct ext_subtlv_rmt_itf_addr *rmt_itf; struct tlv_header *sub_tlvh; uint16_t length = 0, sum = 0, i = 0; srl = XCALLOC(MTYPE_OSPF_SR_PARAMS, sizeof(struct sr_link)); /* Initialize TLV browsing */ length = ntohs(tlvh->length) - EXT_TLV_LINK_SIZE; sub_tlvh = (struct tlv_header *)((char *)(tlvh) + TLV_HDR_SIZE + EXT_TLV_LINK_SIZE); for (; sum < length; sub_tlvh = TLV_HDR_NEXT(sub_tlvh)) { switch (ntohs(sub_tlvh->type)) { case EXT_SUBTLV_ADJ_SID: adj_sid = (struct ext_subtlv_adj_sid *)sub_tlvh; srl->type = ADJ_SID; i = CHECK_FLAG(adj_sid->flags, EXT_SUBTLV_LINK_ADJ_SID_BFLG) ? 1 : 0; srl->flags[i] = adj_sid->flags; if (CHECK_FLAG(adj_sid->flags, EXT_SUBTLV_LINK_ADJ_SID_VFLG)) srl->sid[i] = GET_LABEL(ntohl(adj_sid->value)); else srl->sid[i] = ntohl(adj_sid->value); IPV4_ADDR_COPY(&srl->nhlfe[i].nexthop, &link->link_id); break; case EXT_SUBTLV_LAN_ADJ_SID: lan_sid = (struct ext_subtlv_lan_adj_sid *)sub_tlvh; srl->type = LAN_ADJ_SID; i = CHECK_FLAG(lan_sid->flags, EXT_SUBTLV_LINK_ADJ_SID_BFLG) ? 1 : 0; srl->flags[i] = lan_sid->flags; if (CHECK_FLAG(lan_sid->flags, EXT_SUBTLV_LINK_ADJ_SID_VFLG)) srl->sid[i] = GET_LABEL(ntohl(lan_sid->value)); else srl->sid[i] = ntohl(lan_sid->value); IPV4_ADDR_COPY(&srl->nhlfe[i].nexthop, &lan_sid->neighbor_id); break; case EXT_SUBTLV_RMT_ITF_ADDR: rmt_itf = (struct ext_subtlv_rmt_itf_addr *)sub_tlvh; IPV4_ADDR_COPY(&srl->nhlfe[0].nexthop, &rmt_itf->value); IPV4_ADDR_COPY(&srl->nhlfe[1].nexthop, &rmt_itf->value); break; default: break; } sum += TLV_SIZE(sub_tlvh); } IPV4_ADDR_COPY(&srl->nhlfe[0].prefv4.prefix, &link->link_data); srl->nhlfe[0].prefv4.prefixlen = IPV4_MAX_PREFIXLEN; srl->nhlfe[0].prefv4.family = AF_INET; apply_mask_ipv4(&srl->nhlfe[0].prefv4); IPV4_ADDR_COPY(&srl->nhlfe[1].prefv4.prefix, &link->link_data); srl->nhlfe[1].prefv4.prefixlen = IPV4_MAX_PREFIXLEN; srl->nhlfe[1].prefv4.family = AF_INET; apply_mask_ipv4(&srl->nhlfe[1].prefv4); if (IS_DEBUG_OSPF_SR) { zlog_debug(" |- Found primary Adj/Lan Sid %u for %s/%u", srl->sid[0], inet_ntoa(srl->nhlfe[0].prefv4.prefix), srl->nhlfe[0].prefv4.prefixlen); zlog_debug(" |- Found backup Adj/Lan Sid %u for %s/%u", srl->sid[1], inet_ntoa(srl->nhlfe[1].prefv4.prefix), srl->nhlfe[1].prefv4.prefixlen); } return srl; } /* Extended Prefix SubTLVs Getter */ static struct sr_prefix *get_ext_prefix_sid(struct tlv_header *tlvh) { struct sr_prefix *srp; struct ext_tlv_prefix *pref = (struct ext_tlv_prefix *)tlvh; struct ext_subtlv_prefix_sid *psid; struct tlv_header *sub_tlvh; uint16_t length = 0, sum = 0; srp = XCALLOC(MTYPE_OSPF_SR_PARAMS, sizeof(struct sr_prefix)); /* Initialize TLV browsing */ length = ntohs(tlvh->length) - EXT_TLV_PREFIX_SIZE; sub_tlvh = (struct tlv_header *)((char *)(tlvh) + TLV_HDR_SIZE + EXT_TLV_PREFIX_SIZE); for (; sum < length; sub_tlvh = TLV_HDR_NEXT(sub_tlvh)) { switch (ntohs(sub_tlvh->type)) { case EXT_SUBTLV_PREFIX_SID: psid = (struct ext_subtlv_prefix_sid *)sub_tlvh; if (psid->algorithm != SR_ALGORITHM_SPF) { flog_err(EC_OSPF_INVALID_ALGORITHM, "SR (%s): Unsupported Algorithm", __func__); XFREE(MTYPE_OSPF_SR_PARAMS, srp); return NULL; } srp->type = PREF_SID; srp->flags = psid->flags; if (CHECK_FLAG(psid->flags, EXT_SUBTLV_PREFIX_SID_VFLG)) srp->sid = GET_LABEL(ntohl(psid->value)); else srp->sid = ntohl(psid->value); IPV4_ADDR_COPY(&srp->nhlfe.prefv4.prefix, &pref->address); srp->nhlfe.prefv4.prefixlen = pref->pref_length; srp->nhlfe.prefv4.family = AF_INET; apply_mask_ipv4(&srp->nhlfe.prefv4); break; default: break; } sum += TLV_SIZE(sub_tlvh); } if (IS_DEBUG_OSPF_SR) zlog_debug(" |- Found SID %u for prefix %s/%u", srp->sid, inet_ntoa(srp->nhlfe.prefv4.prefix), srp->nhlfe.prefv4.prefixlen); return srp; } /* * Functions to manipulate Segment Routing Link & Prefix structures */ /* Compare two Segment Link: return 0 if equal, 1 otherwise */ static inline int sr_link_cmp(struct sr_link *srl1, struct sr_link *srl2) { if ((srl1->sid[0] == srl2->sid[0]) && (srl1->sid[1] == srl2->sid[1]) && (srl1->type == srl2->type) && (srl1->flags[0] == srl2->flags[0]) && (srl1->flags[1] == srl2->flags[1])) return 0; else return 1; } /* Compare two Segment Prefix: return 0 if equal, 1 otherwise */ static inline int sr_prefix_cmp(struct sr_prefix *srp1, struct sr_prefix *srp2) { if ((srp1->sid == srp2->sid) && (srp1->flags == srp2->flags)) return 0; else return 1; } /* Update Segment Link of given Segment Routing Node */ static void update_ext_link_sid(struct sr_node *srn, struct sr_link *srl, uint8_t lsa_flags) { struct listnode *node; struct sr_link *lk; bool found = false; /* Sanity check */ if ((srn == NULL) || (srl == NULL)) return; if (IS_DEBUG_OSPF_SR) zlog_debug(" |- Process Extended Link Adj/Lan-SID"); /* Process only Local Adj/Lan_Adj SID coming from LSA SELF */ if (!CHECK_FLAG(srl->flags[0], EXT_SUBTLV_LINK_ADJ_SID_LFLG) || !CHECK_FLAG(srl->flags[1], EXT_SUBTLV_LINK_ADJ_SID_LFLG) || !CHECK_FLAG(lsa_flags, OSPF_LSA_SELF)) return; /* Search for existing Segment Link */ for (ALL_LIST_ELEMENTS_RO(srn->ext_link, node, lk)) if (lk->instance == srl->instance) { found = true; break; } if (IS_DEBUG_OSPF_SR) zlog_debug(" |- %s SR Link 8.0.0.%u for SR node %s", found ? "Update" : "Add", GET_OPAQUE_ID(srl->instance), inet_ntoa(srn->adv_router)); /* if not found, add new Segment Link and install NHLFE */ if (!found) { /* Complete SR-Link and add it to SR-Node list */ srl->srn = srn; IPV4_ADDR_COPY(&srl->adv_router, &srn->adv_router); listnode_add(srn->ext_link, srl); /* Try to set MPLS table */ if (compute_link_nhlfe(srl)) { add_sid_nhlfe(srl->nhlfe[0]); add_sid_nhlfe(srl->nhlfe[1]); } } else { if (sr_link_cmp(lk, srl)) { if (compute_link_nhlfe(srl)) { update_sid_nhlfe(lk->nhlfe[0], srl->nhlfe[0]); update_sid_nhlfe(lk->nhlfe[1], srl->nhlfe[1]); /* Replace Segment List */ listnode_delete(srn->ext_link, lk); XFREE(MTYPE_OSPF_SR_PARAMS, lk); srl->srn = srn; IPV4_ADDR_COPY(&srl->adv_router, &srn->adv_router); listnode_add(srn->ext_link, srl); } else { /* New NHLFE was not found. * Just free the SR Link */ XFREE(MTYPE_OSPF_SR_PARAMS, srl); } } else { /* * This is just an LSA refresh. * Stop processing and free SR Link */ XFREE(MTYPE_OSPF_SR_PARAMS, srl); } } } /* Update Segment Prefix of given Segment Routing Node */ static void update_ext_prefix_sid(struct sr_node *srn, struct sr_prefix *srp) { struct listnode *node; struct sr_prefix *pref; bool found = false; /* Sanity check */ if (srn == NULL || srp == NULL) return; if (IS_DEBUG_OSPF_SR) zlog_debug(" |- Process Extended Prefix SID %u", srp->sid); /* Process only Global Prefix SID */ if (CHECK_FLAG(srp->flags, EXT_SUBTLV_PREFIX_SID_LFLG)) return; /* Search for existing Segment Prefix */ for (ALL_LIST_ELEMENTS_RO(srn->ext_prefix, node, pref)) if (pref->instance == srp->instance) { found = true; break; } if (IS_DEBUG_OSPF_SR) zlog_debug(" |- %s SR LSA ID 7.0.0.%u for SR node %s", found ? "Update" : "Add", GET_OPAQUE_ID(srp->instance), inet_ntoa(srn->adv_router)); /* if not found, add new Segment Prefix and install NHLFE */ if (!found) { /* Complete SR-Prefix and add it to SR-Node list */ srp->srn = srn; IPV4_ADDR_COPY(&srp->adv_router, &srn->adv_router); listnode_add(srn->ext_prefix, srp); /* Try to set MPLS table */ if (compute_prefix_nhlfe(srp) == 1) add_sid_nhlfe(srp->nhlfe); } else { if (sr_prefix_cmp(pref, srp)) { if (compute_prefix_nhlfe(srp) == 1) { update_sid_nhlfe(pref->nhlfe, srp->nhlfe); /* Replace Segment Prefix */ listnode_delete(srn->ext_prefix, pref); XFREE(MTYPE_OSPF_SR_PARAMS, pref); srp->srn = srn; IPV4_ADDR_COPY(&srp->adv_router, &srn->adv_router); listnode_add(srn->ext_prefix, srp); } else { /* New NHLFE was not found. * Just free the SR Prefix */ XFREE(MTYPE_OSPF_SR_PARAMS, srp); } } else { /* This is just an LSA refresh. * Stop processing and free SR Prefix */ XFREE(MTYPE_OSPF_SR_PARAMS, srp); } } } /* * When change the FRR Self SRGB, update the NHLFE Input Label * for all Extended Prefix with SID index through hash_iterate() */ static void update_in_nhlfe(struct hash_bucket *bucket, void *args) { struct listnode *node; struct sr_node *srn = (struct sr_node *)bucket->data; struct sr_prefix *srp; struct sr_nhlfe new; /* Process Every Extended Prefix for this SR-Node */ for (ALL_LIST_ELEMENTS_RO(srn->ext_prefix, node, srp)) { /* Process Self SRN only if NO-PHP is requested */ if ((srn == OspfSR.self) && !CHECK_FLAG(srp->flags, EXT_SUBTLV_PREFIX_SID_NPFLG)) continue; /* Process only SID Index */ if (CHECK_FLAG(srp->flags, EXT_SUBTLV_PREFIX_SID_VFLG)) continue; /* OK. Compute new NHLFE */ memcpy(&new, &srp->nhlfe, sizeof(struct sr_nhlfe)); new.label_in = index2label(srp->sid, OspfSR.srgb); /* Update MPLS LFIB */ update_sid_nhlfe(srp->nhlfe, new); /* Finally update Input Label */ srp->nhlfe.label_in = new.label_in; } } /* * When SRGB has changed, update NHLFE Output Label for all Extended Prefix * with SID index which use the given SR-Node as nexthop though hash_iterate() */ static void update_out_nhlfe(struct hash_bucket *bucket, void *args) { struct listnode *node; struct sr_node *srn = (struct sr_node *)bucket->data; struct sr_node *srnext = (struct sr_node *)args; struct sr_prefix *srp; struct sr_nhlfe new; for (ALL_LIST_ELEMENTS_RO(srn->ext_prefix, node, srp)) { /* Process only SID Index for next hop without PHP */ if ((srp->nexthop == NULL) && (!CHECK_FLAG(srp->flags, EXT_SUBTLV_PREFIX_SID_NPFLG))) continue; memcpy(&new, &srp->nhlfe, sizeof(struct sr_nhlfe)); new.label_out = index2label(srp->sid, srnext->srgb); update_sid_nhlfe(srp->nhlfe, new); srp->nhlfe.label_out = new.label_out; } } /* * Following functions are call when new Segment Routing LSA are received * - Router Information: ospf_sr_ri_lsa_update() & ospf_sr_ri_lsa_delete() * - Extended Link: ospf_sr_ext_link_update() & ospf_sr_ext_link_delete() * - Extended Prefix: ospf_ext_prefix_update() & ospf_sr_ext_prefix_delete() */ /* Update Segment Routing from Router Information LSA */ void ospf_sr_ri_lsa_update(struct ospf_lsa *lsa) { struct sr_node *srn; struct tlv_header *tlvh; struct lsa_header *lsah = (struct lsa_header *)lsa->data; struct ri_sr_tlv_sid_label_range *ri_srgb; struct ri_sr_tlv_sr_algorithm *algo; struct sr_srgb srgb; uint16_t length = 0, sum = 0; if (IS_DEBUG_OSPF_SR) zlog_debug( "SR (%s): Process Router " "Information LSA 4.0.0.%u from %s", __func__, GET_OPAQUE_ID(ntohl(lsah->id.s_addr)), inet_ntoa(lsah->adv_router)); /* Sanity check */ if (IS_LSA_SELF(lsa)) return; if (OspfSR.neighbors == NULL) { flog_err(EC_OSPF_SR_INVALID_DB, "SR (%s): Abort! no valid SR DataBase", __func__); return; } /* Get SR Node in hash table from Router ID */ srn = hash_get(OspfSR.neighbors, (void *)&(lsah->adv_router), (void *)sr_node_new); /* Sanity check */ if (srn == NULL) { flog_err(EC_OSPF_SR_NODE_CREATE, "SR (%s): Abort! can't create SR node in hash table", __func__); return; } if ((srn->instance != 0) && (srn->instance != ntohl(lsah->id.s_addr))) { flog_err(EC_OSPF_SR_INVALID_LSA_ID, "SR (%s): Abort! Wrong " "LSA ID 4.0.0.%u for SR node %s/%u", __func__, GET_OPAQUE_ID(ntohl(lsah->id.s_addr)), inet_ntoa(lsah->adv_router), srn->instance); return; } /* Collect Router Information Sub TLVs */ /* Initialize TLV browsing */ length = ntohs(lsah->length) - OSPF_LSA_HEADER_SIZE; srgb.range_size = 0; srgb.lower_bound = 0; for (tlvh = TLV_HDR_TOP(lsah); (sum < length) && (tlvh != NULL); tlvh = TLV_HDR_NEXT(tlvh)) { switch (ntohs(tlvh->type)) { case RI_SR_TLV_SR_ALGORITHM: algo = (struct ri_sr_tlv_sr_algorithm *)tlvh; int i; for (i = 0; i < ntohs(algo->header.length); i++) srn->algo[i] = algo->value[0]; for (; i < ALGORITHM_COUNT; i++) srn->algo[i] = SR_ALGORITHM_UNSET; sum += TLV_SIZE(tlvh); break; case RI_SR_TLV_SID_LABEL_RANGE: ri_srgb = (struct ri_sr_tlv_sid_label_range *)tlvh; srgb.range_size = GET_RANGE_SIZE(ntohl(ri_srgb->size)); srgb.lower_bound = GET_LABEL(ntohl(ri_srgb->lower.value)); sum += TLV_SIZE(tlvh); break; case RI_SR_TLV_NODE_MSD: srn->msd = ((struct ri_sr_tlv_node_msd *)(tlvh))->value; sum += TLV_SIZE(tlvh); break; default: sum += TLV_SIZE(tlvh); break; } } /* Check that we collect mandatory parameters */ if (srn->algo[0] == SR_ALGORITHM_UNSET || srgb.range_size == 0 || srgb.lower_bound == 0) { flog_err(EC_OSPF_SR_NODE_CREATE, "SR (%s): Missing mandatory parameters. Abort!", __func__); hash_release(OspfSR.neighbors, &(srn->adv_router)); XFREE(MTYPE_OSPF_SR_PARAMS, srn); return; } /* Check if it is a new SR Node or not */ if (srn->instance == 0) { /* update LSA ID */ srn->instance = ntohl(lsah->id.s_addr); /* Copy SRGB */ srn->srgb.range_size = srgb.range_size; srn->srgb.lower_bound = srgb.lower_bound; } /* Check if SRGB has changed */ if ((srn->srgb.range_size != srgb.range_size) || (srn->srgb.lower_bound != srgb.lower_bound)) { srn->srgb.range_size = srgb.range_size; srn->srgb.lower_bound = srgb.lower_bound; /* Update NHLFE if it is a neighbor SR node */ if (srn->neighbor == OspfSR.self) hash_iterate(OspfSR.neighbors, (void (*)(struct hash_bucket *, void *))update_out_nhlfe, (void *)srn); } } /* * Delete SR Node entry in hash table information corresponding to an expired * Router Information LSA */ void ospf_sr_ri_lsa_delete(struct ospf_lsa *lsa) { struct sr_node *srn; struct lsa_header *lsah = (struct lsa_header *)lsa->data; if (IS_DEBUG_OSPF_SR) zlog_debug("SR (%s): Remove SR node %s from lsa_id 4.0.0.%u", __func__, inet_ntoa(lsah->adv_router), GET_OPAQUE_ID(ntohl(lsah->id.s_addr))); /* Sanity check */ if (OspfSR.neighbors == NULL) { flog_err(EC_OSPF_SR_INVALID_DB, "SR (%s): Abort! no valid SR Data Base", __func__); return; } /* Release Router ID entry in SRDB hash table */ srn = hash_release(OspfSR.neighbors, &(lsah->adv_router)); /* Sanity check */ if (srn == NULL) { flog_err(EC_OSPF_SR_NODE_CREATE, "SR (%s): Abort! no entry in SRDB for SR Node %s", __func__, inet_ntoa(lsah->adv_router)); return; } if ((srn->instance != 0) && (srn->instance != ntohl(lsah->id.s_addr))) { flog_err(EC_OSPF_SR_INVALID_LSA_ID, "SR (%s): Abort! Wrong LSA ID 4.0.0.%u for SR node %s", __func__, GET_OPAQUE_ID(ntohl(lsah->id.s_addr)), inet_ntoa(lsah->adv_router)); return; } /* Remove SR node */ sr_node_del(srn); } /* Update Segment Routing from Extended Link LSA */ void ospf_sr_ext_link_lsa_update(struct ospf_lsa *lsa) { struct sr_node *srn; struct tlv_header *tlvh; struct lsa_header *lsah = (struct lsa_header *)lsa->data; struct sr_link *srl; uint16_t length, sum; if (IS_DEBUG_OSPF_SR) zlog_debug( "SR (%s): Process Extended Link LSA 8.0.0.%u from %s", __func__, GET_OPAQUE_ID(ntohl(lsah->id.s_addr)), inet_ntoa(lsah->adv_router)); /* Sanity check */ if (OspfSR.neighbors == NULL) { flog_err(EC_OSPF_SR_INVALID_DB, "SR (%s): Abort! no valid SR DataBase", __func__); return; } /* Get SR Node in hash table from Router ID */ srn = (struct sr_node *)hash_get(OspfSR.neighbors, (void *)&(lsah->adv_router), (void *)sr_node_new); /* Sanity check */ if (srn == NULL) { flog_err(EC_OSPF_SR_NODE_CREATE, "SR (%s): Abort! can't create SR node in hash table", __func__); return; } /* Initialize TLV browsing */ length = ntohs(lsah->length) - OSPF_LSA_HEADER_SIZE; sum = 0; for (tlvh = TLV_HDR_TOP(lsah); (sum < length) && (tlvh != NULL); tlvh = TLV_HDR_NEXT(tlvh)) { if (ntohs(tlvh->type) == EXT_TLV_LINK) { /* Got Extended Link information */ srl = get_ext_link_sid(tlvh); /* Update SID if not null */ if (srl != NULL) { srl->instance = ntohl(lsah->id.s_addr); update_ext_link_sid(srn, srl, lsa->flags); } } sum += TLV_SIZE(tlvh); } } /* Delete Segment Routing from Extended Link LSA */ void ospf_sr_ext_link_lsa_delete(struct ospf_lsa *lsa) { struct listnode *node; struct sr_link *srl; struct sr_node *srn; struct lsa_header *lsah = (struct lsa_header *)lsa->data; uint32_t instance = ntohl(lsah->id.s_addr); if (IS_DEBUG_OSPF_SR) zlog_debug("SR (%s): Remove Extended Link LSA 8.0.0.%u from %s", __func__, GET_OPAQUE_ID(ntohl(lsah->id.s_addr)), inet_ntoa(lsah->adv_router)); /* Sanity check */ if (OspfSR.neighbors == NULL) { flog_err(EC_OSPF_SR_INVALID_DB, "SR (%s): Abort! no valid SR DataBase", __func__); return; } /* Search SR Node in hash table from Router ID */ srn = (struct sr_node *)hash_lookup(OspfSR.neighbors, (void *)&(lsah->adv_router)); /* * SR-Node may be NULL if it has been remove previously when * processing Router Information LSA deletion */ if (srn == NULL) { flog_err(EC_OSPF_SR_INVALID_DB, "SR (%s): Stop! no entry in SRDB for SR Node %s", __func__, inet_ntoa(lsah->adv_router)); return; } /* Search for corresponding Segment Link */ for (ALL_LIST_ELEMENTS_RO(srn->ext_link, node, srl)) if (srl->instance == instance) break; /* Remove Segment Link if found */ if ((srl != NULL) && (srl->instance == instance)) { del_sid_nhlfe(srl->nhlfe[0]); del_sid_nhlfe(srl->nhlfe[1]); listnode_delete(srn->ext_link, srl); XFREE(MTYPE_OSPF_SR_PARAMS, srl); } else { flog_err(EC_OSPF_SR_INVALID_DB, "SR (%s): Didn't found corresponding SR Link 8.0.0.%u " "for SR Node %s", __func__, GET_OPAQUE_ID(ntohl(lsah->id.s_addr)), inet_ntoa(lsah->adv_router)); } } /* Update Segment Routing from Extended Prefix LSA */ void ospf_sr_ext_prefix_lsa_update(struct ospf_lsa *lsa) { struct sr_node *srn; struct tlv_header *tlvh; struct lsa_header *lsah = (struct lsa_header *)lsa->data; struct sr_prefix *srp; uint16_t length, sum; if (IS_DEBUG_OSPF_SR) zlog_debug( "SR (%s): Process Extended Prefix LSA " "7.0.0.%u from %s", __func__, GET_OPAQUE_ID(ntohl(lsah->id.s_addr)), inet_ntoa(lsah->adv_router)); /* Sanity check */ if (OspfSR.neighbors == NULL) { flog_err(EC_OSPF_SR_INVALID_DB, "SR (%s): Abort! no valid SR DataBase", __func__); return; } /* Get SR Node in hash table from Router ID */ srn = (struct sr_node *)hash_get(OspfSR.neighbors, (void *)&(lsah->adv_router), (void *)sr_node_new); /* Sanity check */ if (srn == NULL) { flog_err(EC_OSPF_SR_NODE_CREATE, "SR (%s): Abort! can't create SR node in hash table", __func__); return; } /* Initialize TLV browsing */ length = ntohs(lsah->length) - OSPF_LSA_HEADER_SIZE; sum = 0; for (tlvh = TLV_HDR_TOP(lsah); sum < length; tlvh = TLV_HDR_NEXT(tlvh)) { if (ntohs(tlvh->type) == EXT_TLV_LINK) { /* Got Extended Link information */ srp = get_ext_prefix_sid(tlvh); /* Update SID if not null */ if (srp != NULL) { srp->instance = ntohl(lsah->id.s_addr); update_ext_prefix_sid(srn, srp); } } sum += TLV_SIZE(tlvh); } } /* Delete Segment Routing from Extended Prefix LSA */ void ospf_sr_ext_prefix_lsa_delete(struct ospf_lsa *lsa) { struct listnode *node; struct sr_prefix *srp; struct sr_node *srn; struct lsa_header *lsah = (struct lsa_header *)lsa->data; uint32_t instance = ntohl(lsah->id.s_addr); if (IS_DEBUG_OSPF_SR) zlog_debug( "SR (%s): Remove Extended Prefix LSA 7.0.0.%u from %s", __func__, GET_OPAQUE_ID(ntohl(lsah->id.s_addr)), inet_ntoa(lsah->adv_router)); /* Sanity check */ if (OspfSR.neighbors == NULL) { flog_err(EC_OSPF_SR_INVALID_DB, "SR (%s): Abort! no valid SR DataBase", __func__); return; } /* Search SR Node in hash table from Router ID */ srn = (struct sr_node *)hash_lookup(OspfSR.neighbors, (void *)&(lsah->adv_router)); /* * SR-Node may be NULL if it has been remove previously when * processing Router Information LSA deletion */ if (srn == NULL) { flog_err(EC_OSPF_SR_INVALID_DB, "SR (%s): Stop! no entry in SRDB for SR Node %s", __func__, inet_ntoa(lsah->adv_router)); return; } /* Search for corresponding Segment Link */ for (ALL_LIST_ELEMENTS_RO(srn->ext_prefix, node, srp)) if (srp->instance == instance) break; /* Remove Segment Link if found */ if ((srp != NULL) && (srp->instance == instance)) { del_sid_nhlfe(srp->nhlfe); listnode_delete(srn->ext_link, srp); XFREE(MTYPE_OSPF_SR_PARAMS, srp); } else { flog_err( EC_OSPF_SR_INVALID_DB, "SR (%s): Didn't found corresponding SR Prefix 7.0.0.%u for SR Node %s", __func__, GET_OPAQUE_ID(ntohl(lsah->id.s_addr)), inet_ntoa(lsah->adv_router)); } } /* Get Label for Extended Link SID */ /* TODO: To be replace by Zebra Label Manager */ uint32_t get_ext_link_label_value(void) { static uint32_t label = ADJ_SID_MIN - 1; if (label < ADJ_SID_MAX) label += 1; return label; } /* * Update Prefix SID. Call by ospf_ext_pref_ism_change to * complete initial CLI command at startutp. * * @param ifp - Loopback interface * @param pref - Prefix address of this interface * * @return - void */ void ospf_sr_update_prefix(struct interface *ifp, struct prefix *p) { struct listnode *node; struct sr_prefix *srp; /* Sanity Check */ if ((ifp == NULL) || (p == NULL)) return; /* * Search if there is a Segment Prefix that correspond to this * interface or prefix, and update it if found */ for (ALL_LIST_ELEMENTS_RO(OspfSR.self->ext_prefix, node, srp)) { if ((srp->nhlfe.ifindex == ifp->ifindex) || ((IPV4_ADDR_SAME(&srp->nhlfe.prefv4.prefix, &p->u.prefix4)) && (srp->nhlfe.prefv4.prefixlen == p->prefixlen))) { /* Update Interface & Prefix info */ srp->nhlfe.ifindex = ifp->ifindex; IPV4_ADDR_COPY(&srp->nhlfe.prefv4.prefix, &p->u.prefix4); srp->nhlfe.prefv4.prefixlen = p->prefixlen; srp->nhlfe.prefv4.family = p->family; IPV4_ADDR_COPY(&srp->nhlfe.nexthop, &p->u.prefix4); /* OK. Let's Schedule Extended Prefix LSA */ srp->instance = ospf_ext_schedule_prefix_index( ifp, srp->sid, &srp->nhlfe.prefv4, srp->flags); /* Install NHLFE if NO-PHP is requested */ if (CHECK_FLAG(srp->flags, EXT_SUBTLV_PREFIX_SID_NPFLG)) { srp->nhlfe.label_in = index2label( srp->sid, OspfSR.self->srgb); srp->nhlfe.label_out = MPLS_LABEL_IMPLICIT_NULL; add_sid_nhlfe(srp->nhlfe); } } } } /* * Following functions are used to update MPLS LFIB after a SPF run */ static void ospf_sr_nhlfe_update(struct hash_bucket *bucket, void *args) { struct sr_node *srn = (struct sr_node *)bucket->data; struct listnode *node; struct sr_prefix *srp; struct sr_nhlfe old; int rc; if (IS_DEBUG_OSPF_SR) zlog_debug(" |- Update Prefix for SR Node %s", inet_ntoa(srn->adv_router)); /* Skip Self SR Node */ if (srn == OspfSR.self) return; /* Update Extended Prefix */ for (ALL_LIST_ELEMENTS_RO(srn->ext_prefix, node, srp)) { /* Backup current NHLFE */ memcpy(&old, &srp->nhlfe, sizeof(struct sr_nhlfe)); /* Compute the new NHLFE */ rc = compute_prefix_nhlfe(srp); /* Check computation result */ switch (rc) { /* next hop is not know, remove old NHLFE to avoid loop */ case -1: del_sid_nhlfe(srp->nhlfe); break; /* next hop has not changed, skip it */ case 0: break; /* there is a new next hop, update NHLFE */ case 1: update_sid_nhlfe(old, srp->nhlfe); break; default: break; } } } static int ospf_sr_update_schedule(struct thread *t) { struct ospf *ospf; struct timeval start_time, stop_time; ospf = THREAD_ARG(t); ospf->t_sr_update = NULL; if (!OspfSR.update) return 0; monotime(&start_time); if (IS_DEBUG_OSPF_SR) zlog_debug("SR (%s): Start SPF update", __func__); hash_iterate(OspfSR.neighbors, (void (*)(struct hash_bucket *, void *))ospf_sr_nhlfe_update, NULL); monotime(&stop_time); if (IS_DEBUG_OSPF_SR) zlog_debug("SR (%s): SPF Processing Time(usecs): %lld", __func__, (stop_time.tv_sec - start_time.tv_sec) * 1000000LL + (stop_time.tv_usec - start_time.tv_usec)); OspfSR.update = false; return 1; } #define OSPF_SR_UPDATE_INTERVAL 1 void ospf_sr_update_timer_add(struct ospf *ospf) { if (ospf == NULL) return; /* Check if an update is not alreday engage */ if (OspfSR.update) return; OspfSR.update = true; thread_add_timer(master, ospf_sr_update_schedule, ospf, OSPF_SR_UPDATE_INTERVAL, &ospf->t_sr_update); } /* * -------------------------------------- * Followings are vty command functions. * -------------------------------------- */ /* * Segment Routing Router configuration * * Must be centralize as it concerns both Extended Link/Prefix LSA * and Router Information LSA. Choose to call it from Extended Prefix * write_config() call back. * * @param vty VTY output * * @return none */ void ospf_sr_config_write_router(struct vty *vty) { struct listnode *node; struct sr_prefix *srp; if (OspfSR.enabled) { vty_out(vty, " segment-routing on\n"); if ((OspfSR.srgb.lower_bound != MPLS_DEFAULT_MIN_SRGB_LABEL) || (OspfSR.srgb.range_size != MPLS_DEFAULT_MAX_SRGB_SIZE)) { vty_out(vty, " segment-routing global-block %u %u\n", OspfSR.srgb.lower_bound, OspfSR.srgb.lower_bound + OspfSR.srgb.range_size - 1); } if (OspfSR.msd != 0) vty_out(vty, " segment-routing node-msd %u\n", OspfSR.msd); if (OspfSR.self != NULL) { for (ALL_LIST_ELEMENTS_RO(OspfSR.self->ext_prefix, node, srp)) { vty_out(vty, " segment-routing prefix %s/%u " "index %u%s\n", inet_ntoa(srp->nhlfe.prefv4.prefix), srp->nhlfe.prefv4.prefixlen, srp->sid, CHECK_FLAG(srp->flags, EXT_SUBTLV_PREFIX_SID_NPFLG) ? " no-php-flag" : ""); } } } } DEFUN(ospf_sr_enable, ospf_sr_enable_cmd, "segment-routing on", SR_STR "Enable Segment Routing\n") { VTY_DECLVAR_INSTANCE_CONTEXT(ospf, ospf); if (OspfSR.enabled) return CMD_SUCCESS; if (ospf->vrf_id != VRF_DEFAULT) { vty_out(vty, "Segment Routing is only supported in default " "VRF\n"); return CMD_WARNING_CONFIG_FAILED; } if (IS_DEBUG_OSPF_EVENT) zlog_debug("SR: Segment Routing: OFF -> ON"); /* Start Segment Routing */ OspfSR.enabled = true; ospf_sr_start(ospf); /* Set Router Information SR parameters */ if (IS_DEBUG_OSPF_EVENT) zlog_debug("SR: Activate SR for Router Information LSA"); ospf_router_info_update_sr(true, OspfSR.srgb, OspfSR.msd); /* Update Ext LSA */ if (IS_DEBUG_OSPF_EVENT) zlog_debug("SR: Activate SR for Extended Link/Prefix LSA"); ospf_ext_update_sr(true); return CMD_SUCCESS; } DEFUN (no_ospf_sr_enable, no_ospf_sr_enable_cmd, "no segment-routing [on]", NO_STR SR_STR "Disable Segment Routing\n") { if (!OspfSR.enabled) return CMD_SUCCESS; if (IS_DEBUG_OSPF_EVENT) zlog_debug("SR: Segment Routing: ON -> OFF"); /* Start by Disabling Extended Link & Prefix LSA */ ospf_ext_update_sr(false); /* then, disable Router Information SR parameters */ ospf_router_info_update_sr(false, OspfSR.srgb, OspfSR.msd); /* Finally, stop Segment Routing */ ospf_sr_stop(); OspfSR.enabled = false; return CMD_SUCCESS; } static int ospf_sr_enabled(struct vty *vty) { if (OspfSR.enabled) return 1; if (vty) vty_out(vty, "%% OSPF SR is not turned on\n"); return 0; } DEFUN (sr_sid_label_range, sr_sid_label_range_cmd, "segment-routing global-block (0-1048575) (0-1048575)", SR_STR "Segment Routing Global Block label range\n" "Lower-bound range in decimal (0-1048575)\n" "Upper-bound range in decimal (0-1048575)\n") { uint32_t upper; uint32_t lower; uint32_t size; int idx_low = 2; int idx_up = 3; if (!ospf_sr_enabled(vty)) return CMD_WARNING_CONFIG_FAILED; /* Get lower and upper bound */ lower = strtoul(argv[idx_low]->arg, NULL, 10); upper = strtoul(argv[idx_up]->arg, NULL, 10); size = upper - lower + 1; if (size > MPLS_DEFAULT_MAX_SRGB_SIZE || size <= 0) { vty_out(vty, "Range size cannot be less than 0 or more than %u\n", MPLS_DEFAULT_MAX_SRGB_SIZE); return CMD_WARNING_CONFIG_FAILED; } if (upper > MPLS_DEFAULT_MAX_SRGB_LABEL) { vty_out(vty, "Upper-bound cannot exceed %u\n", MPLS_DEFAULT_MAX_SRGB_LABEL); return CMD_WARNING_CONFIG_FAILED; } if (upper < MPLS_DEFAULT_MIN_SRGB_LABEL) { vty_out(vty, "Upper-bound cannot be lower than %u\n", MPLS_DEFAULT_MIN_SRGB_LABEL); return CMD_WARNING_CONFIG_FAILED; } /* Check if values have changed */ if ((OspfSR.srgb.range_size == size) && (OspfSR.srgb.lower_bound == lower)) return CMD_SUCCESS; /* Set SID/Label range SRGB */ OspfSR.srgb.range_size = size; OspfSR.srgb.lower_bound = lower; if (OspfSR.self != NULL) { OspfSR.self->srgb.range_size = size; OspfSR.self->srgb.lower_bound = lower; } /* Set Router Information SR parameters */ ospf_router_info_update_sr(true, OspfSR.srgb, OspfSR.msd); /* Update NHLFE entries */ hash_iterate(OspfSR.neighbors, (void (*)(struct hash_bucket *, void *))update_in_nhlfe, NULL); return CMD_SUCCESS; } DEFUN (no_sr_sid_label_range, no_sr_sid_label_range_cmd, "no segment-routing global-block [(0-1048575) (0-1048575)]", NO_STR SR_STR "Segment Routing Global Block label range\n" "Lower-bound range in decimal (0-1048575)\n" "Upper-bound range in decimal (0-1048575)\n") { if (!ospf_sr_enabled(vty)) return CMD_WARNING_CONFIG_FAILED; /* Revert to default SRGB value */ OspfSR.srgb.range_size = MPLS_DEFAULT_MIN_SRGB_SIZE; OspfSR.srgb.lower_bound = MPLS_DEFAULT_MIN_SRGB_LABEL; if (OspfSR.self != NULL) { OspfSR.self->srgb.range_size = OspfSR.srgb.range_size; OspfSR.self->srgb.lower_bound = OspfSR.srgb.lower_bound; } /* Set Router Information SR parameters */ ospf_router_info_update_sr(true, OspfSR.srgb, OspfSR.msd); /* Update NHLFE entries */ hash_iterate(OspfSR.neighbors, (void (*)(struct hash_bucket *, void *))update_in_nhlfe, NULL); return CMD_SUCCESS; } DEFUN (sr_node_msd, sr_node_msd_cmd, "segment-routing node-msd (1-16)", SR_STR "Maximum Stack Depth for this router\n" "Maximum number of label that could be stack (1-16)\n") { uint32_t msd; int idx = 1; if (!ospf_sr_enabled(vty)) return CMD_WARNING_CONFIG_FAILED; /* Get MSD */ argv_find(argv, argc, "(1-16)", &idx); msd = strtoul(argv[idx]->arg, NULL, 10); if (msd < 1 || msd > MPLS_MAX_LABELS) { vty_out(vty, "MSD must be comprise between 1 and %u\n", MPLS_MAX_LABELS); return CMD_WARNING_CONFIG_FAILED; } /* Check if value has changed */ if (OspfSR.msd == msd) return CMD_SUCCESS; /* Set this router MSD */ OspfSR.msd = msd; if (OspfSR.self != NULL) OspfSR.self->msd = msd; /* Set Router Information SR parameters */ ospf_router_info_update_sr(true, OspfSR.srgb, OspfSR.msd); return CMD_SUCCESS; } DEFUN (no_sr_node_msd, no_sr_node_msd_cmd, "no segment-routing node-msd [(1-16)]", NO_STR SR_STR "Maximum Stack Depth for this router\n" "Maximum number of label that could be stack (1-16)\n") { if (!ospf_sr_enabled(vty)) return CMD_WARNING_CONFIG_FAILED; /* unset this router MSD */ OspfSR.msd = 0; if (OspfSR.self != NULL) OspfSR.self->msd = 0; /* Set Router Information SR parameters */ ospf_router_info_update_sr(true, OspfSR.srgb, 0); return CMD_SUCCESS; } DEFUN (sr_prefix_sid, sr_prefix_sid_cmd, "segment-routing prefix A.B.C.D/M index (0-65535) [no-php-flag]", SR_STR "Prefix SID\n" "IPv4 Prefix as A.B.C.D/M\n" "SID index for this prefix in decimal (0-65535)\n" "Index value inside SRGB (lower_bound < index < upper_bound)\n" "Don't request Penultimate Hop Popping (PHP)\n") { int idx = 0; struct prefix p; uint32_t index; struct listnode *node; struct sr_prefix *srp, *new; struct interface *ifp; if (!ospf_sr_enabled(vty)) return CMD_WARNING_CONFIG_FAILED; /* Get network prefix */ argv_find(argv, argc, "A.B.C.D/M", &idx); if (!str2prefix(argv[idx]->arg, &p)) { vty_out(vty, "Invalid prefix format %s\n", argv[idx]->arg); return CMD_WARNING_CONFIG_FAILED; } /* Get & verify index value */ argv_find(argv, argc, "(0-65535)", &idx); index = strtoul(argv[idx]->arg, NULL, 10); if (index > OspfSR.srgb.range_size - 1) { vty_out(vty, "Index %u must be lower than range size %u\n", index, OspfSR.srgb.range_size); return CMD_WARNING_CONFIG_FAILED; } /* check that the index is not already used */ for (ALL_LIST_ELEMENTS_RO(OspfSR.self->ext_prefix, node, srp)) { if (srp->sid == index) { vty_out(vty, "Index %u is already used\n", index); return CMD_WARNING_CONFIG_FAILED; } } /* Create new Extended Prefix to SRDB if not found */ new = XCALLOC(MTYPE_OSPF_SR_PARAMS, sizeof(struct sr_prefix)); IPV4_ADDR_COPY(&new->nhlfe.prefv4.prefix, &p.u.prefix4); IPV4_ADDR_COPY(&new->nhlfe.nexthop, &p.u.prefix4); new->nhlfe.prefv4.prefixlen = p.prefixlen; new->nhlfe.prefv4.family = p.family; new->sid = index; /* Set NO PHP flag if present and compute NHLFE */ if (argv_find(argv, argc, "no-php-flag", &idx)) { SET_FLAG(new->flags, EXT_SUBTLV_PREFIX_SID_NPFLG); new->nhlfe.label_in = index2label(new->sid, OspfSR.self->srgb); new->nhlfe.label_out = MPLS_LABEL_IMPLICIT_NULL; } if (IS_DEBUG_OSPF_SR) zlog_debug("SR (%s): Add new index %u to Prefix %s/%u", __func__, index, inet_ntoa(new->nhlfe.prefv4.prefix), new->nhlfe.prefv4.prefixlen); /* Get Interface and check if it is a Loopback */ ifp = if_lookup_prefix(&p, VRF_DEFAULT); if (ifp == NULL) { /* * Interface could be not yet available i.e. when this * command is in the configuration file, OSPF is not yet * ready. In this case, store the prefix SID for latter * update of this Extended Prefix */ listnode_add(OspfSR.self->ext_prefix, new); zlog_info( "Interface for prefix %s/%u not found. Deferred LSA " "flooding", inet_ntoa(p.u.prefix4), p.prefixlen); return CMD_SUCCESS; } if (!if_is_loopback(ifp)) { vty_out(vty, "interface %s is not a Loopback\n", ifp->name); XFREE(MTYPE_OSPF_SR_PARAMS, new); return CMD_WARNING_CONFIG_FAILED; } new->nhlfe.ifindex = ifp->ifindex; /* Search if this prefix already exist */ for (ALL_LIST_ELEMENTS_RO(OspfSR.self->ext_prefix, node, srp)) { if ((IPV4_ADDR_SAME(&srp->nhlfe.prefv4.prefix, &p.u.prefix4) && srp->nhlfe.prefv4.prefixlen == p.prefixlen)) break; else srp = NULL; } /* Update or Add this new SR Prefix */ if (srp) { update_sid_nhlfe(srp->nhlfe, new->nhlfe); listnode_delete(OspfSR.self->ext_prefix, srp); listnode_add(OspfSR.self->ext_prefix, new); } else { listnode_add(OspfSR.self->ext_prefix, new); add_sid_nhlfe(new->nhlfe); } /* Finally, update Extended Prefix LSA */ new->instance = ospf_ext_schedule_prefix_index( ifp, new->sid, &new->nhlfe.prefv4, new->flags); if (new->instance == 0) { vty_out(vty, "Unable to set index %u for prefix %s/%u\n", index, inet_ntoa(p.u.prefix4), p.prefixlen); return CMD_WARNING; } return CMD_SUCCESS; } DEFUN (no_sr_prefix_sid, no_sr_prefix_sid_cmd, "no segment-routing prefix A.B.C.D/M [index (0-65535) no-php-flag]", NO_STR SR_STR "Prefix SID\n" "IPv4 Prefix as A.B.C.D/M\n" "SID index for this prefix in decimal (0-65535)\n" "Index value inside SRGB (lower_bound < index < upper_bound)\n" "Don't request Penultimate Hop Popping (PHP)\n") { int idx = 0; struct prefix p; struct listnode *node; struct sr_prefix *srp; struct interface *ifp; bool found = false; int rc; if (!ospf_sr_enabled(vty)) return CMD_WARNING_CONFIG_FAILED; /* Get network prefix */ argv_find(argv, argc, "A.B.C.D/M", &idx); rc = str2prefix(argv[idx]->arg, &p); if (!rc) { vty_out(vty, "Invalid prefix format %s\n", argv[idx]->arg); return CMD_WARNING_CONFIG_FAILED; } /* check that the prefix is already set */ for (ALL_LIST_ELEMENTS_RO(OspfSR.self->ext_prefix, node, srp)) if (IPV4_ADDR_SAME(&srp->nhlfe.prefv4.prefix, &p.u.prefix4) && (srp->nhlfe.prefv4.prefixlen == p.prefixlen)) { found = true; break; } if (!found) { vty_out(vty, "Prefix %s is not found. Abort!\n", argv[idx]->arg); return CMD_WARNING_CONFIG_FAILED; } /* Get Interface */ ifp = if_lookup_by_index(srp->nhlfe.ifindex, VRF_DEFAULT); if (ifp == NULL) { vty_out(vty, "interface for prefix %s not found.\n", argv[idx]->arg); return CMD_WARNING_CONFIG_FAILED; } /* Update Extended Prefix LSA */ if (!ospf_ext_schedule_prefix_index(ifp, 0, NULL, 0)) { vty_out(vty, "No corresponding loopback interface. Abort!\n"); return CMD_WARNING; } if (IS_DEBUG_OSPF_SR) zlog_debug("SR (%s): Remove Prefix %s/%u with index %u", __func__, inet_ntoa(srp->nhlfe.prefv4.prefix), srp->nhlfe.prefv4.prefixlen, srp->sid); /* Delete NHLFE is NO-PHP is set */ if (CHECK_FLAG(srp->flags, EXT_SUBTLV_PREFIX_SID_NPFLG)) del_sid_nhlfe(srp->nhlfe); /* OK, all is clean, remove SRP from SRDB */ listnode_delete(OspfSR.self->ext_prefix, srp); XFREE(MTYPE_OSPF_SR_PARAMS, srp); return CMD_SUCCESS; } static void show_sr_node(struct vty *vty, struct json_object *json, struct sr_node *srn) { struct listnode *node; struct sr_link *srl; struct sr_prefix *srp; struct interface *itf; char pref[19]; char sid[22]; char label[8]; json_object *json_node = NULL, *json_algo, *json_obj; json_object *json_prefix = NULL, *json_link = NULL; /* Sanity Check */ if (srn == NULL) return; if (json) { json_node = json_object_new_object(); json_object_string_add(json_node, "routerID", inet_ntoa(srn->adv_router)); json_object_int_add(json_node, "srgbSize", srn->srgb.range_size); json_object_int_add(json_node, "srgbLabel", srn->srgb.lower_bound); json_algo = json_object_new_array(); json_object_object_add(json_node, "algorithms", json_algo); for (int i = 0; i < ALGORITHM_COUNT; i++) { if (srn->algo[i] == SR_ALGORITHM_UNSET) continue; json_obj = json_object_new_object(); char tmp[2]; snprintf(tmp, 2, "%u", i); json_object_string_add(json_obj, tmp, srn->algo[i] == SR_ALGORITHM_SPF ? "SPF" : "S-SPF"); json_object_array_add(json_algo, json_obj); } if (srn->msd != 0) json_object_int_add(json_node, "nodeMsd", srn->msd); } else { vty_out(vty, "SR-Node: %s", inet_ntoa(srn->adv_router)); vty_out(vty, "\tSRGB (Size/Label): %u/%u", srn->srgb.range_size, srn->srgb.lower_bound); vty_out(vty, "\tAlgorithm(s): %s", srn->algo[0] == SR_ALGORITHM_SPF ? "SPF" : "S-SPF"); for (int i = 1; i < ALGORITHM_COUNT; i++) { if (srn->algo[i] == SR_ALGORITHM_UNSET) continue; vty_out(vty, "/%s", srn->algo[i] == SR_ALGORITHM_SPF ? "SPF" : "S-SPF"); } if (srn->msd != 0) vty_out(vty, "\tMSD: %u", srn->msd); } if (!json) { vty_out(vty, "\n\n Prefix or Link Label In Label Out " "Node or Adj. SID Interface Nexthop\n"); vty_out(vty, "------------------ -------- --------- " "--------------------- --------- ---------------\n"); } for (ALL_LIST_ELEMENTS_RO(srn->ext_prefix, node, srp)) { snprintf(pref, 19, "%s/%u", inet_ntoa(srp->nhlfe.prefv4.prefix), srp->nhlfe.prefv4.prefixlen); snprintf(sid, 22, "SR Pfx (idx %u)", srp->sid); if (srp->nhlfe.label_out == MPLS_LABEL_IMPLICIT_NULL) sprintf(label, "pop"); else sprintf(label, "%u", srp->nhlfe.label_out); itf = if_lookup_by_index(srp->nhlfe.ifindex, VRF_DEFAULT); if (json) { if (!json_prefix) { json_prefix = json_object_new_array(); json_object_object_add(json_node, "extendedPrefix", json_prefix); } json_obj = json_object_new_object(); json_object_string_add(json_obj, "prefix", pref); json_object_int_add(json_obj, "sid", srp->sid); json_object_int_add(json_obj, "inputLabel", srp->nhlfe.label_in); json_object_string_add(json_obj, "outputLabel", label); json_object_string_add(json_obj, "interface", itf ? itf->name : "-"); json_object_string_add(json_obj, "nexthop", inet_ntoa(srp->nhlfe.nexthop)); json_object_array_add(json_prefix, json_obj); } else { vty_out(vty, "%18s %8u %9s %21s %9s %15s\n", pref, srp->nhlfe.label_in, label, sid, itf ? itf->name : "-", inet_ntoa(srp->nhlfe.nexthop)); } } for (ALL_LIST_ELEMENTS_RO(srn->ext_link, node, srl)) { snprintf(pref, 19, "%s/%u", inet_ntoa(srl->nhlfe[0].prefv4.prefix), srl->nhlfe[0].prefv4.prefixlen); snprintf(sid, 22, "SR Adj. (lbl %u)", srl->sid[0]); if (srl->nhlfe[0].label_out == MPLS_LABEL_IMPLICIT_NULL) sprintf(label, "pop"); else sprintf(label, "%u", srl->nhlfe[0].label_out); itf = if_lookup_by_index(srl->nhlfe[0].ifindex, VRF_DEFAULT); if (json) { if (!json_link) { json_link = json_object_new_array(); json_object_object_add( json_node, "extendedLink", json_link); } /* Primary Link */ json_obj = json_object_new_object(); json_object_string_add(json_obj, "prefix", pref); json_object_int_add(json_obj, "sid", srl->sid[0]); json_object_int_add(json_obj, "inputLabel", srl->nhlfe[0].label_in); json_object_string_add(json_obj, "outputLabel", label); json_object_string_add(json_obj, "interface", itf ? itf->name : "-"); json_object_string_add( json_obj, "nexthop", inet_ntoa(srl->nhlfe[0].nexthop)); json_object_array_add(json_link, json_obj); /* Backup Link */ json_obj = json_object_new_object(); snprintf(sid, 22, "SR Adj. (lbl %u)", srl->sid[1]); if (srl->nhlfe[1].label_out == MPLS_LABEL_IMPLICIT_NULL) sprintf(label, "pop"); else sprintf(label, "%u", srl->nhlfe[0].label_out); json_object_string_add(json_obj, "prefix", pref); json_object_int_add(json_obj, "sid", srl->sid[1]); json_object_int_add(json_obj, "inputLabel", srl->nhlfe[1].label_in); json_object_string_add(json_obj, "outputLabel", label); json_object_string_add(json_obj, "interface", itf ? itf->name : "-"); json_object_string_add( json_obj, "nexthop", inet_ntoa(srl->nhlfe[1].nexthop)); json_object_array_add(json_link, json_obj); } else { vty_out(vty, "%18s %8u %9s %21s %9s %15s\n", pref, srl->nhlfe[0].label_in, label, sid, itf ? itf->name : "-", inet_ntoa(srl->nhlfe[0].nexthop)); snprintf(sid, 22, "SR Adj. (lbl %u)", srl->sid[1]); if (srl->nhlfe[1].label_out == MPLS_LABEL_IMPLICIT_NULL) sprintf(label, "pop"); else sprintf(label, "%u", srl->nhlfe[1].label_out); vty_out(vty, "%18s %8u %9s %21s %9s %15s\n", pref, srl->nhlfe[1].label_in, label, sid, itf ? itf->name : "-", inet_ntoa(srl->nhlfe[1].nexthop)); } } if (json) json_object_array_add(json, json_node); else vty_out(vty, "\n"); } static void show_vty_srdb(struct hash_bucket *bucket, void *args) { struct vty *vty = (struct vty *)args; struct sr_node *srn = (struct sr_node *)bucket->data; show_sr_node(vty, NULL, srn); } static void show_json_srdb(struct hash_bucket *bucket, void *args) { struct json_object *json = (struct json_object *)args; struct sr_node *srn = (struct sr_node *)bucket->data; show_sr_node(NULL, json, srn); } DEFUN (show_ip_opsf_srdb, show_ip_ospf_srdb_cmd, "show ip ospf database segment-routing [adv-router A.B.C.D|self-originate] [json]", SHOW_STR IP_STR OSPF_STR "Database summary\n" "Show Segment Routing Data Base\n" "Advertising SR node\n" "Advertising SR node ID (as an IP address)\n" "Self-originated SR node\n" JSON_STR) { int idx = 0; struct in_addr rid; struct sr_node *srn; bool uj = use_json(argc, argv); json_object *json = NULL, *json_node_array = NULL; if (!OspfSR.enabled) { vty_out(vty, "Segment Routing is disabled on this router\n"); return CMD_WARNING; } if (uj) { json = json_object_new_object(); json_node_array = json_object_new_array(); json_object_string_add(json, "srdbID", inet_ntoa(OspfSR.self->adv_router)); json_object_object_add(json, "srNodes", json_node_array); } else { vty_out(vty, "\n\t\tOSPF Segment Routing database for ID %s\n\n", inet_ntoa(OspfSR.self->adv_router)); } if (argv_find(argv, argc, "self-originate", &idx)) { srn = OspfSR.self; show_sr_node(vty, json_node_array, srn); if (uj) { vty_out(vty, "%s\n", json_object_to_json_string_ext( json, JSON_C_TO_STRING_PRETTY)); json_object_free(json); } return CMD_SUCCESS; } if (argv_find(argv, argc, "A.B.C.D", &idx)) { if (!inet_aton(argv[idx]->arg, &rid)) { vty_out(vty, "Specified Router ID %s is invalid\n", argv[idx]->arg); return CMD_WARNING_CONFIG_FAILED; } /* Get the SR Node from the SRDB */ srn = (struct sr_node *)hash_lookup(OspfSR.neighbors, (void *)&rid); show_sr_node(vty, json_node_array, srn); if (uj) { vty_out(vty, "%s\n", json_object_to_json_string_ext( json, JSON_C_TO_STRING_PRETTY)); json_object_free(json); } return CMD_SUCCESS; } /* No parameters have been provided, Iterate through all the SRDB */ if (uj) { hash_iterate(OspfSR.neighbors, (void (*)(struct hash_bucket *, void *))show_json_srdb, (void *)json_node_array); vty_out(vty, "%s\n", json_object_to_json_string_ext( json, JSON_C_TO_STRING_PRETTY)); json_object_free(json); } else { hash_iterate(OspfSR.neighbors, (void (*)(struct hash_bucket *, void *))show_vty_srdb, (void *)vty); } return CMD_SUCCESS; } /* Install new CLI commands */ void ospf_sr_register_vty(void) { install_element(VIEW_NODE, &show_ip_ospf_srdb_cmd); install_element(OSPF_NODE, &ospf_sr_enable_cmd); install_element(OSPF_NODE, &no_ospf_sr_enable_cmd); install_element(OSPF_NODE, &sr_sid_label_range_cmd); install_element(OSPF_NODE, &no_sr_sid_label_range_cmd); install_element(OSPF_NODE, &sr_node_msd_cmd); install_element(OSPF_NODE, &no_sr_node_msd_cmd); install_element(OSPF_NODE, &sr_prefix_sid_cmd); install_element(OSPF_NODE, &no_sr_prefix_sid_cmd); } frr-7.2.1/ospfd/ospf_sr.h0000644000000000000000000002236213610377563012160 00000000000000/* * This is an implementation of Segment Routing * as per draft draft-ietf-ospf-segment-routing-extensions-24 * * Module name: Segment Routing header definitions * * Author: Olivier Dugeon * Author: Anselme Sawadogo * * Copyright (C) 2016 - 2018 Orange Labs http://www.orange.com * * This program is free software; you can redistribute it and/or modify it * under the terms of the GNU General Public License as published by the Free * Software Foundation; either version 2 of the License, or (at your option) * any later version. * * This program is distributed in the hope that it will be useful, but WITHOUT * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for * more details. * * You should have received a copy of the GNU General Public License along * with this program; see the file COPYING; if not, write to the Free Software * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA */ #ifndef _FRR_OSPF_SR_H #define _FRR_OSPF_SR_H /* Default Route priority for OSPF Segment Routing */ #define OSPF_SR_PRIORITY_DEFAULT 10 /* macros and constants for segment routing */ #define SET_RANGE_SIZE_MASK 0xffffff00 #define GET_RANGE_SIZE_MASK 0x00ffffff #define SET_LABEL_MASK 0xffffff00 #define GET_LABEL_MASK 0x00ffffff #define SET_RANGE_SIZE(range_size) ((range_size << 8) & SET_RANGE_SIZE_MASK) #define GET_RANGE_SIZE(range_size) ((range_size >> 8) & GET_RANGE_SIZE_MASK) #define SET_LABEL(label) ((label << 8) & SET_LABEL_MASK) #define GET_LABEL(label) ((label >> 8) & GET_LABEL_MASK) /* Label range for Adj-SID attribution purpose. Start just right after SRGB */ #define ADJ_SID_MIN MPLS_DEFAULT_MAX_SRGB_LABEL #define ADJ_SID_MAX (MPLS_DEFAULT_MAX_SRGB_LABEL + 1000) #define OSPF_SR_DEFAULT_METRIC 1 /* Segment Routing TLVs as per draft-ietf-ospf-segment-routing-extensions-19 */ /* Segment ID could be a Label (3 bytes) or an Index (4 bytes) */ #define SID_LABEL 3 #define SID_LABEL_SIZE(U) (U - 1) #define SID_INDEX 4 #define SID_INDEX_SIZE(U) (U) /* SID/Label Sub TLV - section 2.1 */ #define SUBTLV_SID_LABEL 1 #define SUBTLV_SID_LABEL_SIZE 8 struct subtlv_sid_label { /* Length is 3 (20 rightmost bits MPLS label) or 4 (32 bits SID) */ struct tlv_header header; uint32_t value; }; /* * Following section defines Segment Routing TLV (tag, length, value) * structures, used in Router Information Opaque LSA. */ /* RI SR-Algorithm TLV - section 3.1 */ #define RI_SR_TLV_SR_ALGORITHM 8 struct ri_sr_tlv_sr_algorithm { struct tlv_header header; #define SR_ALGORITHM_SPF 0 #define SR_ALGORITHM_STRICT_SPF 1 #define SR_ALGORITHM_UNSET 255 #define ALGORITHM_COUNT 4 /* Only 4 algorithms supported in this code */ uint8_t value[ALGORITHM_COUNT]; }; /* RI SID/Label Range TLV - section 3.2 */ #define RI_SR_TLV_SID_LABEL_RANGE 9 struct ri_sr_tlv_sid_label_range { struct tlv_header header; /* Only 24 upper most bits are significant */ #define SID_RANGE_LABEL_LENGTH 3 uint32_t size; /* A SID/Label sub-TLV will follow. */ struct subtlv_sid_label lower; }; /* RI Node/MSD TLV as per draft-ietf-ospf-segment-routing-msd-05 */ #define RI_SR_TLV_NODE_MSD 12 struct ri_sr_tlv_node_msd { struct tlv_header header; uint8_t subtype; /* always = 1 */ uint8_t value; uint16_t padding; }; /* * Following section defines Segment Routing TLV (tag, length, value) * structures, used in Extended Prefix/Link Opaque LSA. */ /* Adj-SID and LAN-Ajd-SID subtlvs' flags */ #define EXT_SUBTLV_LINK_ADJ_SID_BFLG 0x80 #define EXT_SUBTLV_LINK_ADJ_SID_VFLG 0x40 #define EXT_SUBTLV_LINK_ADJ_SID_LFLG 0x20 #define EXT_SUBTLV_LINK_ADJ_SID_SFLG 0x10 /* Prefix SID subtlv Flags */ #define EXT_SUBTLV_PREFIX_SID_NPFLG 0x40 #define EXT_SUBTLV_PREFIX_SID_MFLG 0x20 #define EXT_SUBTLV_PREFIX_SID_EFLG 0x10 #define EXT_SUBTLV_PREFIX_SID_VFLG 0x08 #define EXT_SUBTLV_PREFIX_SID_LFLG 0x04 /* SID/Label Binding subtlv Flags */ #define EXT_SUBTLV_SID_BINDING_MFLG 0x80 /* Extended Prefix Range TLV - section 4 */ #define EXT_TLV_PREF_RANGE 2 #define EXT_SUBTLV_PREFIX_RANGE_SIZE 12 struct ext_tlv_prefix_range { struct tlv_header header; uint8_t pref_length; uint8_t af; uint16_t range_size; uint8_t flags; uint8_t reserved[3]; struct in_addr address; }; /* Prefix SID Sub-TLV - section 5 */ #define EXT_SUBTLV_PREFIX_SID 2 #define EXT_SUBTLV_PREFIX_SID_SIZE 8 struct ext_subtlv_prefix_sid { struct tlv_header header; uint8_t flags; uint8_t reserved; uint8_t mtid; uint8_t algorithm; uint32_t value; }; /* Adj-SID Sub-TLV - section 6.1 */ #define EXT_SUBTLV_ADJ_SID 2 #define EXT_SUBTLV_ADJ_SID_SIZE 8 struct ext_subtlv_adj_sid { struct tlv_header header; uint8_t flags; uint8_t reserved; uint8_t mtid; uint8_t weight; uint32_t value; }; /* LAN Adj-SID Sub-TLV - section 6.2 */ #define EXT_SUBTLV_LAN_ADJ_SID 3 #define EXT_SUBTLV_LAN_ADJ_SID_SIZE 12 struct ext_subtlv_lan_adj_sid { struct tlv_header header; uint8_t flags; uint8_t reserved; uint8_t mtid; uint8_t weight; struct in_addr neighbor_id; uint32_t value; }; /* * Following section define structure used to manage Segment Routing * information and TLVs / SubTLVs */ /* Structure aggregating SRGB info retrieved from an lsa */ struct sr_srgb { uint32_t range_size; uint32_t lower_bound; }; /* SID type to make difference between loopback interfaces and others */ enum sid_type { PREF_SID, ADJ_SID, LAN_ADJ_SID }; /* Structure aggregating all OSPF Segment Routing information for the node */ struct ospf_sr_db { /* Status of Segment Routing: enable or disable */ bool enabled; /* Ongoing Update following an OSPF SPF */ bool update; /* Flooding Scope: Area = 10 or AS = 11 */ uint8_t scope; /* FRR SR node */ struct sr_node *self; /* List of neighbour SR nodes */ struct hash *neighbors; /* List of SR prefix */ struct route_table *prefix; /* Local SR info announced in Router Info LSA */ /* Algorithms supported by the node */ uint8_t algo[ALGORITHM_COUNT]; /* * Segment Routing Global Block i.e. label range * Only one range supported in this code */ struct sr_srgb srgb; /* Maximum SID Depth supported by the node */ uint8_t msd; }; /* Structure aggregating all received SR info from LSAs by node */ struct sr_node { struct in_addr adv_router; /* used to identify sender of LSA */ /* 24-bit Opaque-ID field value according to RFC 7684 specification */ uint32_t instance; uint8_t algo[ALGORITHM_COUNT]; /* Algorithms supported by the node */ /* Segment Routing Global Block i.e. label range */ struct sr_srgb srgb; uint8_t msd; /* Maximum SID Depth */ /* List of Prefix & Link advertise by this node */ struct list *ext_prefix; /* For Node SID */ struct list *ext_link; /* For Adj and LAN SID */ /* Pointer to FRR SR-Node or NULL if it is not a neighbor */ struct sr_node *neighbor; }; /* Segment Routing - NHLFE info: support IPv4 Only */ struct sr_nhlfe { struct prefix_ipv4 prefv4; struct in_addr nexthop; ifindex_t ifindex; mpls_label_t label_in; mpls_label_t label_out; }; /* Structure aggregating all Segment Routing Link information */ /* Link are generally advertised by pair: primary + backup */ struct sr_link { struct in_addr adv_router; /* used to identify sender of LSA */ /* 24-bit Opaque-ID field value according to RFC 7684 specification */ uint32_t instance; /* Flags to manage this link parameters. */ uint8_t flags[2]; /* Segment Routing ID */ uint32_t sid[2]; enum sid_type type; /* SR NHLFE for this link */ struct sr_nhlfe nhlfe[2]; /* Back pointer to SR Node which advertise this Link */ struct sr_node *srn; }; /* Structure aggregating all Segment Routing Prefix information */ struct sr_prefix { struct in_addr adv_router; /* used to identify sender of LSA */ /* 24-bit Opaque-ID field value according to RFC 7684 specification */ uint32_t instance; /* Flags to manage this prefix parameters. */ uint8_t flags; /* Segment Routing ID */ uint32_t sid; enum sid_type type; /* SR NHLFE for this prefix */ struct sr_nhlfe nhlfe; /* Back pointer to SR Node which advertise this Prefix */ struct sr_node *srn; /* * Pointer to SR Node which is the next hop for this Prefix * or NULL if next hop is the destination of the prefix */ struct sr_node *nexthop; }; /* Prototypes definition */ /* Segment Routing initialisation functions */ extern int ospf_sr_init(void); extern void ospf_sr_term(void); extern void ospf_sr_finish(void); /* Segment Routing LSA update & delete functions */ extern void ospf_sr_ri_lsa_update(struct ospf_lsa *lsa); extern void ospf_sr_ri_lsa_delete(struct ospf_lsa *lsa); extern void ospf_sr_ext_link_lsa_update(struct ospf_lsa *lsa); extern void ospf_sr_ext_link_lsa_delete(struct ospf_lsa *lsa); extern void ospf_sr_ext_prefix_lsa_update(struct ospf_lsa *lsa); extern void ospf_sr_ext_prefix_lsa_delete(struct ospf_lsa *lsa); /* Segment Routing configuration functions */ extern uint32_t get_ext_link_label_value(void); extern void ospf_sr_config_write_router(struct vty *vty); extern void ospf_sr_update_prefix(struct interface *ifp, struct prefix *p); /* Segment Routing re-routing function */ extern void ospf_sr_update_timer_add(struct ospf *ospf); #endif /* _FRR_OSPF_SR_H */ frr-7.2.1/ospfd/ospf_te.c0000644000000000000000000021075213610377563012141 00000000000000/* * This is an implementation of RFC3630 * Copyright (C) 2001 KDD R&D Laboratories, Inc. * http://www.kddlabs.co.jp/ * * Copyright (C) 2012 Orange Labs * http://www.orange.com * * This file is part of GNU Zebra. * * GNU Zebra is free software; you can redistribute it and/or modify it * under the terms of the GNU General Public License as published by the * Free Software Foundation; either version 2, or (at your option) any * later version. * * GNU Zebra 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; see the file COPYING; if not, write to the Free Software * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA */ /* Add support of RFC7471 */ /* Add support of RFC5392, RFC6827 */ #include #include #include "linklist.h" #include "prefix.h" #include "vrf.h" #include "if.h" #include "table.h" #include "memory.h" #include "command.h" #include "vty.h" #include "stream.h" #include "log.h" #include "thread.h" #include "hash.h" #include "sockunion.h" /* for inet_aton() */ #include "network.h" #include "ospfd/ospfd.h" #include "ospfd/ospf_interface.h" #include "ospfd/ospf_ism.h" #include "ospfd/ospf_asbr.h" #include "ospfd/ospf_lsa.h" #include "ospfd/ospf_lsdb.h" #include "ospfd/ospf_neighbor.h" #include "ospfd/ospf_nsm.h" #include "ospfd/ospf_flood.h" #include "ospfd/ospf_packet.h" #include "ospfd/ospf_spf.h" #include "ospfd/ospf_dump.h" #include "ospfd/ospf_route.h" #include "ospfd/ospf_ase.h" #include "ospfd/ospf_zebra.h" #include "ospfd/ospf_te.h" #include "ospfd/ospf_vty.h" #include "ospfd/ospf_errors.h" /* * Global variable to manage Opaque-LSA/MPLS-TE on this node. * Note that all parameter values are stored in network byte order. */ struct ospf_mpls_te OspfMplsTE; const char *mode2text[] = {"Off", "AS", "Area"}; /*------------------------------------------------------------------------* * Followings are initialize/terminate functions for MPLS-TE handling. *------------------------------------------------------------------------*/ static int ospf_mpls_te_new_if(struct interface *ifp); static int ospf_mpls_te_del_if(struct interface *ifp); static void ospf_mpls_te_ism_change(struct ospf_interface *oi, int old_status); static void ospf_mpls_te_nsm_change(struct ospf_neighbor *nbr, int old_status); static void ospf_mpls_te_config_write_router(struct vty *vty); static void ospf_mpls_te_show_info(struct vty *vty, struct ospf_lsa *lsa); static int ospf_mpls_te_lsa_originate_area(void *arg); static int ospf_mpls_te_lsa_originate_as(void *arg); static struct ospf_lsa *ospf_mpls_te_lsa_refresh(struct ospf_lsa *lsa); static void del_mpls_te_link(void *val); static void ospf_mpls_te_register_vty(void); int ospf_mpls_te_init(void) { int rc; rc = ospf_register_opaque_functab( OSPF_OPAQUE_AREA_LSA, OPAQUE_TYPE_TRAFFIC_ENGINEERING_LSA, ospf_mpls_te_new_if, ospf_mpls_te_del_if, ospf_mpls_te_ism_change, ospf_mpls_te_nsm_change, ospf_mpls_te_config_write_router, NULL, /*ospf_mpls_te_config_write_if */ NULL, /* ospf_mpls_te_config_write_debug */ ospf_mpls_te_show_info, ospf_mpls_te_lsa_originate_area, ospf_mpls_te_lsa_refresh, NULL, /* ospf_mpls_te_new_lsa_hook */ NULL /* ospf_mpls_te_del_lsa_hook */); if (rc != 0) { flog_warn( EC_OSPF_OPAQUE_REGISTRATION, "ospf_mpls_te_init: Failed to register Traffic Engineering functions"); return rc; } memset(&OspfMplsTE, 0, sizeof(struct ospf_mpls_te)); OspfMplsTE.enabled = false; OspfMplsTE.inter_as = Off; OspfMplsTE.iflist = list_new(); OspfMplsTE.iflist->del = del_mpls_te_link; ospf_mpls_te_register_vty(); return rc; } /* Additional register for RFC5392 support */ static int ospf_mpls_te_register(enum inter_as_mode mode) { int rc = 0; uint8_t scope; if (OspfMplsTE.inter_as != Off) return rc; if (mode == AS) scope = OSPF_OPAQUE_AS_LSA; else scope = OSPF_OPAQUE_AREA_LSA; rc = ospf_register_opaque_functab(scope, OPAQUE_TYPE_INTER_AS_LSA, NULL, NULL, NULL, NULL, NULL, NULL, NULL, ospf_mpls_te_show_info, ospf_mpls_te_lsa_originate_as, ospf_mpls_te_lsa_refresh, NULL, NULL); if (rc != 0) { flog_warn( EC_OSPF_OPAQUE_REGISTRATION, "ospf_router_info_init: Failed to register Inter-AS functions"); return rc; } return rc; } static int ospf_mpls_te_unregister(void) { uint8_t scope; if (OspfMplsTE.inter_as == Off) return 0; if (OspfMplsTE.inter_as == AS) scope = OSPF_OPAQUE_AS_LSA; else scope = OSPF_OPAQUE_AREA_LSA; ospf_delete_opaque_functab(scope, OPAQUE_TYPE_INTER_AS_LSA); return 0; } void ospf_mpls_te_term(void) { list_delete(&OspfMplsTE.iflist); ospf_delete_opaque_functab(OSPF_OPAQUE_AREA_LSA, OPAQUE_TYPE_TRAFFIC_ENGINEERING_LSA); OspfMplsTE.enabled = false; ospf_mpls_te_unregister(); OspfMplsTE.inter_as = Off; return; } void ospf_mpls_te_finish(void) { // list_delete_all_node(OspfMplsTE.iflist); OspfMplsTE.enabled = false; OspfMplsTE.inter_as = Off; } /*------------------------------------------------------------------------* * Followings are control functions for MPLS-TE parameters management. *------------------------------------------------------------------------*/ static void del_mpls_te_link(void *val) { XFREE(MTYPE_OSPF_MPLS_TE, val); return; } static uint32_t get_mpls_te_instance_value(void) { static uint32_t seqno = 0; if (seqno < MAX_LEGAL_TE_INSTANCE_NUM) seqno += 1; else seqno = 1; /* Avoid zero. */ return seqno; } static struct mpls_te_link *lookup_linkparams_by_ifp(struct interface *ifp) { struct listnode *node, *nnode; struct mpls_te_link *lp; for (ALL_LIST_ELEMENTS(OspfMplsTE.iflist, node, nnode, lp)) if (lp->ifp == ifp) return lp; return NULL; } static struct mpls_te_link *lookup_linkparams_by_instance(struct ospf_lsa *lsa) { struct listnode *node; struct mpls_te_link *lp; unsigned int key = GET_OPAQUE_ID(ntohl(lsa->data->id.s_addr)); for (ALL_LIST_ELEMENTS_RO(OspfMplsTE.iflist, node, lp)) if (lp->instance == key) return lp; zlog_info("lookup_linkparams_by_instance: Entry not found: key(%x)", key); return NULL; } static void ospf_mpls_te_foreach_area( void (*func)(struct mpls_te_link *lp, enum lsa_opcode sched_opcode), enum lsa_opcode sched_opcode) { struct listnode *node, *nnode; struct listnode *node2; struct mpls_te_link *lp; struct ospf_area *area; for (ALL_LIST_ELEMENTS(OspfMplsTE.iflist, node, nnode, lp)) { /* Skip Inter-AS TEv2 Links */ if (IS_INTER_AS(lp->type)) continue; if ((area = lp->area) == NULL) continue; if (CHECK_FLAG(lp->flags, LPFLG_LOOKUP_DONE)) continue; if (func != NULL) (*func)(lp, sched_opcode); for (node2 = listnextnode(node); node2; node2 = listnextnode(node2)) if ((lp = listgetdata(node2)) != NULL) if (lp->area != NULL) if (IPV4_ADDR_SAME(&lp->area->area_id, &area->area_id)) SET_FLAG(lp->flags, LPFLG_LOOKUP_DONE); } for (ALL_LIST_ELEMENTS_RO(OspfMplsTE.iflist, node, lp)) if (lp->area != NULL) UNSET_FLAG(lp->flags, LPFLG_LOOKUP_DONE); return; } static void set_mpls_te_router_addr(struct in_addr ipv4) { OspfMplsTE.router_addr.header.type = htons(TE_TLV_ROUTER_ADDR); OspfMplsTE.router_addr.header.length = htons(TE_LINK_SUBTLV_DEF_SIZE); OspfMplsTE.router_addr.value = ipv4; return; } static void set_linkparams_link_header(struct mpls_te_link *lp) { uint16_t length = 0; /* TE_LINK_SUBTLV_LINK_TYPE */ if (ntohs(lp->link_type.header.type) != 0) length += TLV_SIZE(&lp->link_type.header); /* TE_LINK_SUBTLV_LINK_ID */ if (ntohs(lp->link_id.header.type) != 0) length += TLV_SIZE(&lp->link_id.header); /* TE_LINK_SUBTLV_LCLIF_IPADDR */ if (lp->lclif_ipaddr.header.type != 0) length += TLV_SIZE(&lp->lclif_ipaddr.header); /* TE_LINK_SUBTLV_RMTIF_IPADDR */ if (lp->rmtif_ipaddr.header.type != 0) length += TLV_SIZE(&lp->rmtif_ipaddr.header); /* TE_LINK_SUBTLV_TE_METRIC */ if (ntohs(lp->te_metric.header.type) != 0) length += TLV_SIZE(&lp->te_metric.header); /* TE_LINK_SUBTLV_MAX_BW */ if (ntohs(lp->max_bw.header.type) != 0) length += TLV_SIZE(&lp->max_bw.header); /* TE_LINK_SUBTLV_MAX_RSV_BW */ if (ntohs(lp->max_rsv_bw.header.type) != 0) length += TLV_SIZE(&lp->max_rsv_bw.header); /* TE_LINK_SUBTLV_UNRSV_BW */ if (ntohs(lp->unrsv_bw.header.type) != 0) length += TLV_SIZE(&lp->unrsv_bw.header); /* TE_LINK_SUBTLV_RSC_CLSCLR */ if (ntohs(lp->rsc_clsclr.header.type) != 0) length += TLV_SIZE(&lp->rsc_clsclr.header); /* TE_LINK_SUBTLV_LLRI */ if (ntohs(lp->llri.header.type) != 0) length += TLV_SIZE(&lp->llri.header); /* TE_LINK_SUBTLV_RIP */ if (ntohs(lp->rip.header.type) != 0) length += TLV_SIZE(&lp->rip.header); /* TE_LINK_SUBTLV_RAS */ if (ntohs(lp->ras.header.type) != 0) length += TLV_SIZE(&lp->ras.header); /* TE_LINK_SUBTLV_LRRID */ if (ntohs(lp->lrrid.header.type) != 0) length += TLV_SIZE(&lp->lrrid.header); /* TE_LINK_SUBTLV_AV_DELAY */ if (ntohs(lp->av_delay.header.type) != 0) length += TLV_SIZE(&lp->av_delay.header); /* TE_LINK_SUBTLV_MM_DELAY */ if (ntohs(lp->mm_delay.header.type) != 0) length += TLV_SIZE(&lp->mm_delay.header); /* TE_LINK_SUBTLV_DELAY_VAR */ if (ntohs(lp->delay_var.header.type) != 0) length += TLV_SIZE(&lp->delay_var.header); /* TE_LINK_SUBTLV_PKT_LOSS */ if (ntohs(lp->pkt_loss.header.type) != 0) length += TLV_SIZE(&lp->pkt_loss.header); /* TE_LINK_SUBTLV_RES_BW */ if (ntohs(lp->res_bw.header.type) != 0) length += TLV_SIZE(&lp->res_bw.header); /* TE_LINK_SUBTLV_AVA_BW */ if (ntohs(lp->ava_bw.header.type) != 0) length += TLV_SIZE(&lp->ava_bw.header); /* TE_LINK_SUBTLV_USE_BW */ if (ntohs(lp->use_bw.header.type) != 0) length += TLV_SIZE(&lp->use_bw.header); lp->link_header.header.type = htons(TE_TLV_LINK); lp->link_header.header.length = htons(length); return; } static void set_linkparams_link_type(struct ospf_interface *oi, struct mpls_te_link *lp) { lp->link_type.header.type = htons(TE_LINK_SUBTLV_LINK_TYPE); lp->link_type.header.length = htons(TE_LINK_SUBTLV_TYPE_SIZE); switch (oi->type) { case OSPF_IFTYPE_POINTOPOINT: lp->link_type.link_type.value = LINK_TYPE_SUBTLV_VALUE_PTP; break; case OSPF_IFTYPE_BROADCAST: case OSPF_IFTYPE_NBMA: lp->link_type.link_type.value = LINK_TYPE_SUBTLV_VALUE_MA; break; default: /* Not supported yet. */ /* XXX */ lp->link_type.header.type = htons(0); break; } return; } static void set_linkparams_link_id(struct mpls_te_link *lp, struct in_addr link_id) { lp->link_id.header.type = htons(TE_LINK_SUBTLV_LINK_ID); lp->link_id.header.length = htons(TE_LINK_SUBTLV_DEF_SIZE); lp->link_id.value = link_id; return; } static void set_linkparams_lclif_ipaddr(struct mpls_te_link *lp, struct in_addr lclif) { lp->lclif_ipaddr.header.type = htons(TE_LINK_SUBTLV_LCLIF_IPADDR); lp->lclif_ipaddr.header.length = htons(TE_LINK_SUBTLV_DEF_SIZE); lp->lclif_ipaddr.value[0] = lclif; return; } static void set_linkparams_rmtif_ipaddr(struct mpls_te_link *lp, struct in_addr rmtif) { lp->rmtif_ipaddr.header.type = htons(TE_LINK_SUBTLV_RMTIF_IPADDR); lp->rmtif_ipaddr.header.length = htons(TE_LINK_SUBTLV_DEF_SIZE); lp->rmtif_ipaddr.value[0] = rmtif; return; } static void set_linkparams_te_metric(struct mpls_te_link *lp, uint32_t te_metric) { lp->te_metric.header.type = htons(TE_LINK_SUBTLV_TE_METRIC); lp->te_metric.header.length = htons(TE_LINK_SUBTLV_DEF_SIZE); lp->te_metric.value = htonl(te_metric); return; } static void set_linkparams_max_bw(struct mpls_te_link *lp, float fp) { lp->max_bw.header.type = htons(TE_LINK_SUBTLV_MAX_BW); lp->max_bw.header.length = htons(TE_LINK_SUBTLV_DEF_SIZE); lp->max_bw.value = htonf(fp); return; } static void set_linkparams_max_rsv_bw(struct mpls_te_link *lp, float fp) { lp->max_rsv_bw.header.type = htons(TE_LINK_SUBTLV_MAX_RSV_BW); lp->max_rsv_bw.header.length = htons(TE_LINK_SUBTLV_DEF_SIZE); lp->max_rsv_bw.value = htonf(fp); return; } static void set_linkparams_unrsv_bw(struct mpls_te_link *lp, int priority, float fp) { /* Note that TLV-length field is the size of array. */ lp->unrsv_bw.header.type = htons(TE_LINK_SUBTLV_UNRSV_BW); lp->unrsv_bw.header.length = htons(TE_LINK_SUBTLV_UNRSV_SIZE); lp->unrsv_bw.value[priority] = htonf(fp); return; } static void set_linkparams_rsc_clsclr(struct mpls_te_link *lp, uint32_t classcolor) { lp->rsc_clsclr.header.type = htons(TE_LINK_SUBTLV_RSC_CLSCLR); lp->rsc_clsclr.header.length = htons(TE_LINK_SUBTLV_DEF_SIZE); lp->rsc_clsclr.value = htonl(classcolor); return; } static void set_linkparams_inter_as(struct mpls_te_link *lp, struct in_addr addr, uint32_t as) { /* Set the Remote ASBR IP address and then the associated AS number */ lp->rip.header.type = htons(TE_LINK_SUBTLV_RIP); lp->rip.header.length = htons(TE_LINK_SUBTLV_DEF_SIZE); lp->rip.value = addr; lp->ras.header.type = htons(TE_LINK_SUBTLV_RAS); lp->ras.header.length = htons(TE_LINK_SUBTLV_DEF_SIZE); lp->ras.value = htonl(as); } static void unset_linkparams_inter_as(struct mpls_te_link *lp) { /* Reset the Remote ASBR IP address and then the associated AS number */ lp->rip.header.type = htons(0); lp->rip.header.length = htons(0); lp->rip.value.s_addr = htonl(0); lp->ras.header.type = htons(0); lp->ras.header.length = htons(0); lp->ras.value = htonl(0); } void set_linkparams_llri(struct mpls_te_link *lp, uint32_t local, uint32_t remote) { lp->llri.header.type = htons(TE_LINK_SUBTLV_LLRI); lp->llri.header.length = htons(TE_LINK_SUBTLV_LLRI_SIZE); lp->llri.local = htonl(local); lp->llri.remote = htonl(remote); } void set_linkparams_lrrid(struct mpls_te_link *lp, struct in_addr local, struct in_addr remote) { lp->lrrid.header.type = htons(TE_LINK_SUBTLV_LRRID); lp->lrrid.header.length = htons(TE_LINK_SUBTLV_LRRID_SIZE); lp->lrrid.local.s_addr = local.s_addr; lp->lrrid.remote.s_addr = remote.s_addr; } static void set_linkparams_av_delay(struct mpls_te_link *lp, uint32_t delay, uint8_t anormal) { uint32_t tmp; /* Note that TLV-length field is the size of array. */ lp->av_delay.header.type = htons(TE_LINK_SUBTLV_AV_DELAY); lp->av_delay.header.length = htons(TE_LINK_SUBTLV_DEF_SIZE); tmp = delay & TE_EXT_MASK; if (anormal) tmp |= TE_EXT_ANORMAL; lp->av_delay.value = htonl(tmp); return; } static void set_linkparams_mm_delay(struct mpls_te_link *lp, uint32_t low, uint32_t high, uint8_t anormal) { uint32_t tmp; /* Note that TLV-length field is the size of array. */ lp->mm_delay.header.type = htons(TE_LINK_SUBTLV_MM_DELAY); lp->mm_delay.header.length = htons(TE_LINK_SUBTLV_MM_DELAY_SIZE); tmp = low & TE_EXT_MASK; if (anormal) tmp |= TE_EXT_ANORMAL; lp->mm_delay.low = htonl(tmp); lp->mm_delay.high = htonl(high); return; } static void set_linkparams_delay_var(struct mpls_te_link *lp, uint32_t jitter) { /* Note that TLV-length field is the size of array. */ lp->delay_var.header.type = htons(TE_LINK_SUBTLV_DELAY_VAR); lp->delay_var.header.length = htons(TE_LINK_SUBTLV_DEF_SIZE); lp->delay_var.value = htonl(jitter & TE_EXT_MASK); return; } static void set_linkparams_pkt_loss(struct mpls_te_link *lp, uint32_t loss, uint8_t anormal) { uint32_t tmp; /* Note that TLV-length field is the size of array. */ lp->pkt_loss.header.type = htons(TE_LINK_SUBTLV_PKT_LOSS); lp->pkt_loss.header.length = htons(TE_LINK_SUBTLV_DEF_SIZE); tmp = loss & TE_EXT_MASK; if (anormal) tmp |= TE_EXT_ANORMAL; lp->pkt_loss.value = htonl(tmp); return; } static void set_linkparams_res_bw(struct mpls_te_link *lp, float fp) { /* Note that TLV-length field is the size of array. */ lp->res_bw.header.type = htons(TE_LINK_SUBTLV_RES_BW); lp->res_bw.header.length = htons(TE_LINK_SUBTLV_DEF_SIZE); lp->res_bw.value = htonf(fp); return; } static void set_linkparams_ava_bw(struct mpls_te_link *lp, float fp) { /* Note that TLV-length field is the size of array. */ lp->ava_bw.header.type = htons(TE_LINK_SUBTLV_AVA_BW); lp->ava_bw.header.length = htons(TE_LINK_SUBTLV_DEF_SIZE); lp->ava_bw.value = htonf(fp); return; } static void set_linkparams_use_bw(struct mpls_te_link *lp, float fp) { /* Note that TLV-length field is the size of array. */ lp->use_bw.header.type = htons(TE_LINK_SUBTLV_USE_BW); lp->use_bw.header.length = htons(TE_LINK_SUBTLV_DEF_SIZE); lp->use_bw.value = htonf(fp); return; } /* Update TE parameters from Interface */ static void update_linkparams(struct mpls_te_link *lp) { int i; struct interface *ifp; /* Get the Interface structure */ if ((ifp = lp->ifp) == NULL) { if (IS_DEBUG_OSPF_TE) zlog_debug( "OSPF MPLS-TE: Abort update TE parameters: no interface associated to Link Parameters"); return; } if (!HAS_LINK_PARAMS(ifp)) { if (IS_DEBUG_OSPF_TE) zlog_debug( "OSPF MPLS-TE: Abort update TE parameters: no Link Parameters for interface"); return; } /* RFC3630 metrics */ if (IS_PARAM_SET(ifp->link_params, LP_ADM_GRP)) set_linkparams_rsc_clsclr(lp, ifp->link_params->admin_grp); else TLV_TYPE(lp->rsc_clsclr) = 0; if (IS_PARAM_SET(ifp->link_params, LP_MAX_BW)) set_linkparams_max_bw(lp, ifp->link_params->max_bw); else TLV_TYPE(lp->max_bw) = 0; if (IS_PARAM_SET(ifp->link_params, LP_MAX_RSV_BW)) set_linkparams_max_rsv_bw(lp, ifp->link_params->max_rsv_bw); else TLV_TYPE(lp->max_rsv_bw) = 0; if (IS_PARAM_SET(ifp->link_params, LP_UNRSV_BW)) for (i = 0; i < MAX_CLASS_TYPE; i++) set_linkparams_unrsv_bw(lp, i, ifp->link_params->unrsv_bw[i]); else TLV_TYPE(lp->unrsv_bw) = 0; if (IS_PARAM_SET(ifp->link_params, LP_TE_METRIC)) set_linkparams_te_metric(lp, ifp->link_params->te_metric); else TLV_TYPE(lp->te_metric) = 0; /* TE metric Extensions */ if (IS_PARAM_SET(ifp->link_params, LP_DELAY)) set_linkparams_av_delay(lp, ifp->link_params->av_delay, 0); else TLV_TYPE(lp->av_delay) = 0; if (IS_PARAM_SET(ifp->link_params, LP_MM_DELAY)) set_linkparams_mm_delay(lp, ifp->link_params->min_delay, ifp->link_params->max_delay, 0); else TLV_TYPE(lp->mm_delay) = 0; if (IS_PARAM_SET(ifp->link_params, LP_DELAY_VAR)) set_linkparams_delay_var(lp, ifp->link_params->delay_var); else TLV_TYPE(lp->delay_var) = 0; if (IS_PARAM_SET(ifp->link_params, LP_PKT_LOSS)) set_linkparams_pkt_loss(lp, ifp->link_params->pkt_loss, 0); else TLV_TYPE(lp->pkt_loss) = 0; if (IS_PARAM_SET(ifp->link_params, LP_RES_BW)) set_linkparams_res_bw(lp, ifp->link_params->res_bw); else TLV_TYPE(lp->res_bw) = 0; if (IS_PARAM_SET(ifp->link_params, LP_AVA_BW)) set_linkparams_ava_bw(lp, ifp->link_params->ava_bw); else TLV_TYPE(lp->ava_bw) = 0; if (IS_PARAM_SET(ifp->link_params, LP_USE_BW)) set_linkparams_use_bw(lp, ifp->link_params->use_bw); else TLV_TYPE(lp->use_bw) = 0; /* RFC5392 */ if (IS_PARAM_SET(ifp->link_params, LP_RMT_AS)) { /* Flush LSA if it engaged and was previously a STD_TE one */ if (IS_STD_TE(lp->type) && CHECK_FLAG(lp->flags, LPFLG_LSA_ENGAGED)) { if (IS_DEBUG_OSPF_TE) zlog_debug( "OSPF MPLS-TE Update IF: Switch from Standard LSA to INTER-AS for %s[%d/%d]", ifp->name, lp->flags, lp->type); ospf_mpls_te_lsa_schedule(lp, FLUSH_THIS_LSA); /* Then, switch it to INTER-AS */ if (OspfMplsTE.inter_as == AS) lp->flags = INTER_AS | FLOOD_AS; else { lp->flags = INTER_AS | FLOOD_AREA; lp->area = ospf_area_lookup_by_area_id( ospf_lookup_by_vrf_id(VRF_DEFAULT), OspfMplsTE.interas_areaid); } } set_linkparams_inter_as(lp, ifp->link_params->rmt_ip, ifp->link_params->rmt_as); } else { if (IS_DEBUG_OSPF_TE) zlog_debug( "OSPF MPLS-TE Update IF: Switch from INTER-AS LSA to Standard for %s[%d/%d]", ifp->name, lp->flags, lp->type); /* reset inter-as TE params */ /* Flush LSA if it engaged and was previously an INTER_AS one */ if (IS_INTER_AS(lp->type) && CHECK_FLAG(lp->flags, LPFLG_LSA_ENGAGED)) { ospf_mpls_te_lsa_schedule(lp, FLUSH_THIS_LSA); /* Then, switch it to Standard TE */ lp->flags = STD_TE | FLOOD_AREA; } unset_linkparams_inter_as(lp); } } static void initialize_linkparams(struct mpls_te_link *lp) { struct interface *ifp = lp->ifp; struct ospf_interface *oi = NULL; struct route_node *rn; if (IS_DEBUG_OSPF_TE) zlog_debug( "MPLS-TE(initialize_linkparams) Initialize Link Parameters for interface %s", ifp->name); /* Search OSPF Interface parameters for this interface */ for (rn = route_top(IF_OIFS(ifp)); rn; rn = route_next(rn)) { if ((oi = rn->info) == NULL) continue; if (oi->ifp == ifp) break; } if ((oi == NULL) || (oi->ifp != ifp)) { if (IS_DEBUG_OSPF_TE) zlog_debug( "MPLS-TE(initialize_linkparams) Could not find corresponding OSPF Interface for %s", ifp->name); return; } /* * Try to set initial values those can be derived from * zebra-interface information. */ set_linkparams_link_type(oi, lp); /* Set local IP addr */ set_linkparams_lclif_ipaddr(lp, oi->address->u.prefix4); /* Set Remote IP addr if Point to Point Interface */ if (oi->type == OSPF_IFTYPE_POINTOPOINT) { struct prefix *pref = CONNECTED_PREFIX(oi->connected); if (pref != NULL) set_linkparams_rmtif_ipaddr(lp, pref->u.prefix4); } /* Keep Area information in combination with link parameters. */ lp->area = oi->area; return; } static int is_mandated_params_set(struct mpls_te_link *lp) { int rc = 0; if (ntohs(OspfMplsTE.router_addr.header.type) == 0) { flog_warn( EC_OSPF_TE_UNEXPECTED, "MPLS-TE(is_mandated_params_set) Missing Router Address"); return rc; } if (ntohs(lp->link_type.header.type) == 0) { flog_warn(EC_OSPF_TE_UNEXPECTED, "MPLS-TE(is_mandated_params_set) Missing Link Type"); return rc; } if (!IS_INTER_AS(lp->type) && (ntohs(lp->link_id.header.type) == 0)) { flog_warn(EC_OSPF_TE_UNEXPECTED, "MPLS-TE(is_mandated_params_set) Missing Link ID"); return rc; } rc = 1; return rc; } /*------------------------------------------------------------------------* * Followings are callback functions against generic Opaque-LSAs handling. *------------------------------------------------------------------------*/ static int ospf_mpls_te_new_if(struct interface *ifp) { struct mpls_te_link *new; if (IS_DEBUG_OSPF_TE) zlog_debug( "MPLS-TE(ospf_mpls_te_new_if) Add new %s interface %s to MPLS-TE list", ifp->link_params ? "Active" : "Inactive", ifp->name); if (lookup_linkparams_by_ifp(ifp) != NULL) return 0; new = XCALLOC(MTYPE_OSPF_MPLS_TE, sizeof(struct mpls_te_link)); new->instance = get_mpls_te_instance_value(); new->ifp = ifp; /* By default TE-Link is RFC3630 compatible flooding in Area and not * active */ /* This default behavior will be adapted with call to * ospf_mpls_te_update_if() */ new->type = STD_TE | FLOOD_AREA; new->flags = LPFLG_LSA_INACTIVE; /* Initialize Link Parameters from Interface */ initialize_linkparams(new); /* Set TE Parameters from Interface */ update_linkparams(new); /* Add Link Parameters structure to the list */ listnode_add(OspfMplsTE.iflist, new); if (IS_DEBUG_OSPF_TE) zlog_debug( "OSPF MPLS-TE New IF: Add new LP context for %s[%d/%d]", ifp->name, new->flags, new->type); /* Schedule Opaque-LSA refresh. */ /* XXX */ return 0; } static int ospf_mpls_te_del_if(struct interface *ifp) { struct mpls_te_link *lp; int rc = -1; if ((lp = lookup_linkparams_by_ifp(ifp)) != NULL) { struct list *iflist = OspfMplsTE.iflist; /* Dequeue listnode entry from the list. */ listnode_delete(iflist, lp); XFREE(MTYPE_OSPF_MPLS_TE, lp); } /* Schedule Opaque-LSA refresh. */ /* XXX */ rc = 0; return rc; } /* Main initialization / update function of the MPLS TE Link context */ /* Call when interface TE Link parameters are modified */ void ospf_mpls_te_update_if(struct interface *ifp) { struct mpls_te_link *lp; if (IS_DEBUG_OSPF_TE) zlog_debug( "OSPF MPLS-TE: Update LSA parameters for interface %s [%s]", ifp->name, HAS_LINK_PARAMS(ifp) ? "ON" : "OFF"); /* Get Link context from interface */ if ((lp = lookup_linkparams_by_ifp(ifp)) == NULL) { flog_warn( EC_OSPF_TE_UNEXPECTED, "OSPF MPLS-TE Update: Did not find Link Parameters context for interface %s", ifp->name); return; } /* Fulfill MPLS-TE Link TLV from Interface TE Link parameters */ if (HAS_LINK_PARAMS(ifp)) { SET_FLAG(lp->flags, LPFLG_LSA_ACTIVE); /* Update TE parameters */ update_linkparams(lp); /* Finally Re-Originate or Refresh Opaque LSA if MPLS_TE is * enabled */ if (OspfMplsTE.enabled) if (lp->area != NULL) { if (CHECK_FLAG(lp->flags, LPFLG_LSA_ENGAGED)) ospf_mpls_te_lsa_schedule( lp, REFRESH_THIS_LSA); else ospf_mpls_te_lsa_schedule( lp, REORIGINATE_THIS_LSA); } } else { /* If MPLS TE is disable on this interface, flush LSA if it is * already engaged */ if (CHECK_FLAG(lp->flags, LPFLG_LSA_ENGAGED)) ospf_mpls_te_lsa_schedule(lp, FLUSH_THIS_LSA); else /* Reset Activity flag */ lp->flags = LPFLG_LSA_INACTIVE; } return; } /* * Just add interface and set available information. Other information * and flooding of LSA will be done later when adjacency will be up * See ospf_mpls_te_nsm_change() after */ static void ospf_mpls_te_ism_change(struct ospf_interface *oi, int old_state) { struct mpls_te_link *lp; lp = lookup_linkparams_by_ifp(oi->ifp); if (lp == NULL) { flog_warn( EC_OSPF_TE_UNEXPECTED, "MPLS-TE (%s): Cannot get linkparams from OI(%s)?", __func__, IF_NAME(oi)); return; } if (oi->area == NULL || oi->area->ospf == NULL) { flog_warn( EC_OSPF_TE_UNEXPECTED, "MPLS-TE (%s): Cannot refer to OSPF from OI(%s)?", __func__, IF_NAME(oi)); return; } /* Keep Area information in combination with linkparams. */ lp->area = oi->area; /* Keep interface MPLS-TE status */ lp->flags = HAS_LINK_PARAMS(oi->ifp); switch (oi->state) { case ISM_PointToPoint: case ISM_DROther: case ISM_Backup: case ISM_DR: /* Set Link type and Local IP addr */ set_linkparams_link_type(oi, lp); set_linkparams_lclif_ipaddr(lp, oi->address->u.prefix4); break; default: /* State is undefined: Flush LSA if engaged */ if (CHECK_FLAG(lp->flags, LPFLG_LSA_ENGAGED)) ospf_mpls_te_lsa_schedule(lp, FLUSH_THIS_LSA); break; } if (IS_DEBUG_OSPF_TE) zlog_debug( "MPLS-TE(%s): Update Link parameters for interface %s", __func__, IF_NAME(oi)); return; } /* * Complete TE info and schedule LSA flooding * Link-ID and Remote IP address must be set with neighbor info * which are only valid once NSM state is FULL */ static void ospf_mpls_te_nsm_change(struct ospf_neighbor *nbr, int old_state) { struct ospf_interface *oi = nbr->oi; struct mpls_te_link *lp; /* Process Neighbor only when its state is NSM Full */ if (nbr->state != NSM_Full) return; /* Get interface information for Traffic Engineering */ lp = lookup_linkparams_by_ifp(oi->ifp); if (lp == NULL) { flog_warn( EC_OSPF_TE_UNEXPECTED, "MPLS-TE (%s): Cannot get linkparams from OI(%s)?", __func__, IF_NAME(oi)); return; } if (oi->area == NULL || oi->area->ospf == NULL) { flog_warn( EC_OSPF_TE_UNEXPECTED, "MPLS-TE (%s): Cannot refer to OSPF from OI(%s)?", __func__, IF_NAME(oi)); return; } /* Keep Area information in combination with SR info. */ lp->area = oi->area; /* Keep interface MPLS-TE status */ lp->flags = HAS_LINK_PARAMS(oi->ifp); /* * The Link ID is identical to the contents of the Link ID field * in the Router LSA for these link types. */ switch (oi->state) { case ISM_PointToPoint: /* Set Link ID with neighbor Router ID */ set_linkparams_link_id(lp, nbr->router_id); /* Set Remote IP address */ set_linkparams_rmtif_ipaddr(lp, nbr->address.u.prefix4); break; case ISM_DR: case ISM_DROther: case ISM_Backup: /* Set Link ID with the Designated Router ID */ set_linkparams_link_id(lp, DR(oi)); break; default: /* State is undefined: Flush LSA if engaged */ if (OspfMplsTE.enabled && CHECK_FLAG(lp->flags, LPFLG_LSA_ENGAGED)) ospf_mpls_te_lsa_schedule(lp, FLUSH_THIS_LSA); return; } if (IS_DEBUG_OSPF_TE) zlog_debug( "MPLS-TE (%s): Add Link-ID %s for interface %s ", __func__, inet_ntoa(lp->link_id.value), oi->ifp->name); /* Try to Schedule LSA */ if (OspfMplsTE.enabled) { if (CHECK_FLAG(lp->flags, LPFLG_LSA_ENGAGED)) ospf_mpls_te_lsa_schedule(lp, REFRESH_THIS_LSA); else ospf_mpls_te_lsa_schedule(lp, REORIGINATE_THIS_LSA); } return; } /*------------------------------------------------------------------------* * Followings are OSPF protocol processing functions for MPLS-TE. *------------------------------------------------------------------------*/ static void build_tlv_header(struct stream *s, struct tlv_header *tlvh) { stream_put(s, tlvh, sizeof(struct tlv_header)); return; } static void build_router_tlv(struct stream *s) { struct tlv_header *tlvh = &OspfMplsTE.router_addr.header; if (ntohs(tlvh->type) != 0) { build_tlv_header(s, tlvh); stream_put(s, TLV_DATA(tlvh), TLV_BODY_SIZE(tlvh)); } return; } static void build_link_subtlv(struct stream *s, struct tlv_header *tlvh) { if ((tlvh != NULL) && (ntohs(tlvh->type) != 0)) { build_tlv_header(s, tlvh); stream_put(s, TLV_DATA(tlvh), TLV_BODY_SIZE(tlvh)); } return; } static void build_link_tlv(struct stream *s, struct mpls_te_link *lp) { set_linkparams_link_header(lp); build_tlv_header(s, &lp->link_header.header); build_link_subtlv(s, &lp->link_type.header); build_link_subtlv(s, &lp->link_id.header); build_link_subtlv(s, &lp->lclif_ipaddr.header); build_link_subtlv(s, &lp->rmtif_ipaddr.header); build_link_subtlv(s, &lp->te_metric.header); build_link_subtlv(s, &lp->max_bw.header); build_link_subtlv(s, &lp->max_rsv_bw.header); build_link_subtlv(s, &lp->unrsv_bw.header); build_link_subtlv(s, &lp->rsc_clsclr.header); build_link_subtlv(s, &lp->lrrid.header); build_link_subtlv(s, &lp->llri.header); build_link_subtlv(s, &lp->rip.header); build_link_subtlv(s, &lp->ras.header); build_link_subtlv(s, &lp->av_delay.header); build_link_subtlv(s, &lp->mm_delay.header); build_link_subtlv(s, &lp->delay_var.header); build_link_subtlv(s, &lp->pkt_loss.header); build_link_subtlv(s, &lp->res_bw.header); build_link_subtlv(s, &lp->ava_bw.header); build_link_subtlv(s, &lp->use_bw.header); return; } static void ospf_mpls_te_lsa_body_set(struct stream *s, struct mpls_te_link *lp) { /* * The router address TLV is type 1, and ... * It must appear in exactly one * Traffic Engineering LSA originated by a router. */ build_router_tlv(s); /* * Only one Link TLV shall be carried in each LSA, allowing for fine * granularity changes in topology. */ build_link_tlv(s, lp); return; } /* Create new opaque-LSA. */ static struct ospf_lsa *ospf_mpls_te_lsa_new(struct ospf *ospf, struct ospf_area *area, struct mpls_te_link *lp) { struct stream *s; struct lsa_header *lsah; struct ospf_lsa *new = NULL; uint8_t options, lsa_type = 0; struct in_addr lsa_id; uint32_t tmp; uint16_t length; /* Create a stream for LSA. */ s = stream_new(OSPF_MAX_LSA_SIZE); lsah = (struct lsa_header *)STREAM_DATA(s); options = OSPF_OPTION_O; /* Don't forget this :-) */ /* Set opaque-LSA header fields depending of the type of RFC */ if (IS_INTER_AS(lp->type)) { if (IS_FLOOD_AS(lp->type)) { /* Enable AS external as we flood Inter-AS with Opaque * Type 11 */ options |= OSPF_OPTION_E; lsa_type = OSPF_OPAQUE_AS_LSA; } else { options |= LSA_OPTIONS_GET( area); /* Get area default option */ options |= LSA_OPTIONS_NSSA_GET(area); lsa_type = OSPF_OPAQUE_AREA_LSA; } tmp = SET_OPAQUE_LSID(OPAQUE_TYPE_INTER_AS_LSA, lp->instance); lsa_id.s_addr = htonl(tmp); if (!ospf) { stream_free(s); return NULL; } lsa_header_set(s, options, lsa_type, lsa_id, ospf->router_id); } else { options |= LSA_OPTIONS_GET(area); /* Get area default option */ options |= LSA_OPTIONS_NSSA_GET(area); lsa_type = OSPF_OPAQUE_AREA_LSA; tmp = SET_OPAQUE_LSID(OPAQUE_TYPE_TRAFFIC_ENGINEERING_LSA, lp->instance); lsa_id.s_addr = htonl(tmp); lsa_header_set(s, options, lsa_type, lsa_id, area->ospf->router_id); } if (IS_DEBUG_OSPF(lsa, LSA_GENERATE)) zlog_debug( "LSA[Type%d:%s]: Create an Opaque-LSA/MPLS-TE instance", lsa_type, inet_ntoa(lsa_id)); /* Set opaque-LSA body fields. */ ospf_mpls_te_lsa_body_set(s, lp); /* Set length. */ length = stream_get_endp(s); lsah->length = htons(length); /* Now, create an OSPF LSA instance. */ new = ospf_lsa_new_and_data(length); new->vrf_id = ospf->vrf_id; if (area && area->ospf) new->vrf_id = area->ospf->vrf_id; new->area = area; SET_FLAG(new->flags, OSPF_LSA_SELF); memcpy(new->data, lsah, length); stream_free(s); return new; } static int ospf_mpls_te_lsa_originate1(struct ospf_area *area, struct mpls_te_link *lp) { struct ospf_lsa *new = NULL; int rc = -1; /* Create new Opaque-LSA/MPLS-TE instance. */ new = ospf_mpls_te_lsa_new(area->ospf, area, lp); if (new == NULL) { flog_warn( EC_OSPF_TE_UNEXPECTED, "ospf_mpls_te_lsa_originate1: ospf_mpls_te_lsa_new() ?"); return rc; } /* Install this LSA into LSDB. */ if (ospf_lsa_install(area->ospf, NULL /*oi*/, new) == NULL) { flog_warn(EC_OSPF_LSA_INSTALL_FAILURE, "ospf_mpls_te_lsa_originate1: ospf_lsa_install() ?"); ospf_lsa_unlock(&new); return rc; } /* Now this link-parameter entry has associated LSA. */ SET_FLAG(lp->flags, LPFLG_LSA_ENGAGED); /* Update new LSA origination count. */ area->ospf->lsa_originate_count++; /* Flood new LSA through area. */ ospf_flood_through_area(area, NULL /*nbr*/, new); if (IS_DEBUG_OSPF(lsa, LSA_GENERATE)) { char area_id[INET_ADDRSTRLEN]; strlcpy(area_id, inet_ntoa(area->area_id), sizeof(area_id)); zlog_debug( "LSA[Type%d:%s]: Originate Opaque-LSA/MPLS-TE: Area(%s), Link(%s)", new->data->type, inet_ntoa(new->data->id), area_id, lp->ifp->name); ospf_lsa_header_dump(new->data); } rc = 0; return rc; } static int ospf_mpls_te_lsa_originate_area(void *arg) { struct ospf_area *area = (struct ospf_area *)arg; struct listnode *node, *nnode; struct mpls_te_link *lp; int rc = -1; if (!OspfMplsTE.enabled) { zlog_info( "ospf_mpls_te_lsa_originate_area: MPLS-TE is disabled now."); rc = 0; /* This is not an error case. */ return rc; } for (ALL_LIST_ELEMENTS(OspfMplsTE.iflist, node, nnode, lp)) { /* Process only enabled LSA with area scope flooding */ if (!CHECK_FLAG(lp->flags, LPFLG_LSA_ACTIVE) || IS_FLOOD_AS(lp->type)) continue; if (lp->area == NULL) continue; if (!IPV4_ADDR_SAME(&lp->area->area_id, &area->area_id)) continue; if (CHECK_FLAG(lp->flags, LPFLG_LSA_ENGAGED)) { if (CHECK_FLAG(lp->flags, LPFLG_LSA_FORCED_REFRESH)) { UNSET_FLAG(lp->flags, LPFLG_LSA_FORCED_REFRESH); zlog_info( "OSPF MPLS-TE (ospf_mpls_te_lsa_originate_area): Refresh instead of Originate"); ospf_mpls_te_lsa_schedule(lp, REFRESH_THIS_LSA); } continue; } if (!is_mandated_params_set(lp)) { zlog_info( "ospf_mpls_te_lsa_originate_area: Link(%s) lacks some mandated MPLS-TE parameters.", lp->ifp ? lp->ifp->name : "?"); continue; } /* Ok, let's try to originate an LSA for this area and Link. */ if (IS_DEBUG_OSPF_TE) zlog_debug( "MPLS-TE(ospf_mpls_te_lsa_originate_area) Let's finally reoriginate the LSA %d through the Area %s for Link %s", lp->instance, inet_ntoa(area->area_id), lp->ifp ? lp->ifp->name : "?"); if (ospf_mpls_te_lsa_originate1(area, lp) != 0) return rc; } rc = 0; return rc; } static int ospf_mpls_te_lsa_originate2(struct ospf *top, struct mpls_te_link *lp) { struct ospf_lsa *new; int rc = -1; /* Create new Opaque-LSA/Inter-AS instance. */ new = ospf_mpls_te_lsa_new(top, NULL, lp); if (new == NULL) { flog_warn( EC_OSPF_LSA_UNEXPECTED, "ospf_mpls_te_lsa_originate2: ospf_router_info_lsa_new() ?"); return rc; } new->vrf_id = top->vrf_id; /* Install this LSA into LSDB. */ if (ospf_lsa_install(top, NULL /*oi */, new) == NULL) { flog_warn(EC_OSPF_LSA_INSTALL_FAILURE, "ospf_mpls_te_lsa_originate2: ospf_lsa_install() ?"); ospf_lsa_unlock(&new); return rc; } /* Now this Router Info parameter entry has associated LSA. */ SET_FLAG(lp->flags, LPFLG_LSA_ENGAGED); /* Update new LSA origination count. */ top->lsa_originate_count++; /* Flood new LSA through AS. */ ospf_flood_through_as(top, NULL /*nbr */, new); if (IS_DEBUG_OSPF(lsa, LSA_GENERATE)) { zlog_debug( "LSA[Type%d:%s]: Originate Opaque-LSA/MPLS-TE Inter-AS", new->data->type, inet_ntoa(new->data->id)); ospf_lsa_header_dump(new->data); } rc = 0; return rc; } static int ospf_mpls_te_lsa_originate_as(void *arg) { struct ospf *top; struct ospf_area *area; struct listnode *node, *nnode; struct mpls_te_link *lp; int rc = -1; if ((!OspfMplsTE.enabled) || (OspfMplsTE.inter_as == Off)) { zlog_info( "ospf_mpls_te_lsa_originate_as: MPLS-TE Inter-AS is disabled for now."); rc = 0; /* This is not an error case. */ return rc; } for (ALL_LIST_ELEMENTS(OspfMplsTE.iflist, node, nnode, lp)) { /* Process only enabled INTER_AS Links or Pseudo-Links */ if (!CHECK_FLAG(lp->flags, LPFLG_LSA_ACTIVE) || !IS_INTER_AS(lp->type)) continue; if (CHECK_FLAG(lp->flags, LPFLG_LSA_ENGAGED)) { if (CHECK_FLAG(lp->flags, LPFLG_LSA_FORCED_REFRESH)) { UNSET_FLAG(lp->flags, LPFLG_LSA_FORCED_REFRESH); ospf_mpls_te_lsa_schedule(lp, REFRESH_THIS_LSA); } continue; } if (!is_mandated_params_set(lp)) { flog_warn( EC_OSPF_TE_UNEXPECTED, "ospf_mpls_te_lsa_originate_as: Link(%s) lacks some mandated MPLS-TE parameters.", lp->ifp ? lp->ifp->name : "?"); continue; } /* Ok, let's try to originate an LSA for this AS and Link. */ if (IS_DEBUG_OSPF_TE) zlog_debug( "MPLS-TE(ospf_mpls_te_lsa_originate_as) Let's finally re-originate the Inter-AS LSA %d through the %s for Link %s", lp->instance, IS_FLOOD_AS(lp->type) ? "AS" : "Area", lp->ifp ? lp->ifp->name : "Unknown"); if (IS_FLOOD_AS(lp->type)) { top = (struct ospf *)arg; ospf_mpls_te_lsa_originate2(top, lp); } else { area = (struct ospf_area *)arg; ospf_mpls_te_lsa_originate1(area, lp); } } rc = 0; return rc; } static struct ospf_lsa *ospf_mpls_te_lsa_refresh(struct ospf_lsa *lsa) { struct mpls_te_link *lp; struct ospf_area *area = lsa->area; struct ospf *top; struct ospf_lsa *new = NULL; if (!OspfMplsTE.enabled) { /* * This LSA must have flushed before due to MPLS-TE status * change. * It seems a slip among routers in the routing domain. */ zlog_info("ospf_mpls_te_lsa_refresh: MPLS-TE is disabled now."); lsa->data->ls_age = htons(OSPF_LSA_MAXAGE); /* Flush it anyway. */ } /* At first, resolve lsa/lp relationship. */ if ((lp = lookup_linkparams_by_instance(lsa)) == NULL) { flog_warn(EC_OSPF_TE_UNEXPECTED, "ospf_mpls_te_lsa_refresh: Invalid parameter?"); lsa->data->ls_age = htons(OSPF_LSA_MAXAGE); /* Flush it anyway. */ ospf_opaque_lsa_flush_schedule(lsa); return NULL; } /* Check if lp was not disable in the interval */ if (!CHECK_FLAG(lp->flags, LPFLG_LSA_ACTIVE)) { flog_warn( EC_OSPF_TE_UNEXPECTED, "ospf_mpls_te_lsa_refresh: lp was disabled: Flush it!"); lsa->data->ls_age = htons(OSPF_LSA_MAXAGE); /* Flush it anyway. */ } /* If the lsa's age reached to MaxAge, start flushing procedure. */ if (IS_LSA_MAXAGE(lsa)) { UNSET_FLAG(lp->flags, LPFLG_LSA_ENGAGED); ospf_opaque_lsa_flush_schedule(lsa); return NULL; } top = ospf_lookup_by_vrf_id(lsa->vrf_id); /* Create new Opaque-LSA/MPLS-TE instance. */ new = ospf_mpls_te_lsa_new(top, area, lp); if (new == NULL) { flog_warn(EC_OSPF_TE_UNEXPECTED, "ospf_mpls_te_lsa_refresh: ospf_mpls_te_lsa_new() ?"); return NULL; } new->data->ls_seqnum = lsa_seqnum_increment(lsa); /* Install this LSA into LSDB. */ /* Given "lsa" will be freed in the next function. */ /* As area could be NULL i.e. when using OPAQUE_LSA_AS, we prefer to use * ospf_lookup() to get ospf instance */ if (area) top = area->ospf; if (ospf_lsa_install(top, NULL /*oi */, new) == NULL) { flog_warn(EC_OSPF_LSA_INSTALL_FAILURE, "ospf_mpls_te_lsa_refresh: ospf_lsa_install() ?"); ospf_lsa_unlock(&new); return NULL; } /* Flood updated LSA through AS or Area depending of the RFC of the link */ if (IS_FLOOD_AS(lp->type)) ospf_flood_through_as(top, NULL, new); else ospf_flood_through_area(area, NULL /*nbr*/, new); /* Debug logging. */ if (IS_DEBUG_OSPF(lsa, LSA_GENERATE)) { zlog_debug("LSA[Type%d:%s]: Refresh Opaque-LSA/MPLS-TE", new->data->type, inet_ntoa(new->data->id)); ospf_lsa_header_dump(new->data); } return new; } void ospf_mpls_te_lsa_schedule(struct mpls_te_link *lp, enum lsa_opcode opcode) { struct ospf_lsa lsa; struct lsa_header lsah; struct ospf *top; uint32_t tmp; memset(&lsa, 0, sizeof(lsa)); memset(&lsah, 0, sizeof(lsah)); top = ospf_lookup_by_vrf_id(VRF_DEFAULT); /* Check if the pseudo link is ready to flood */ if (!(CHECK_FLAG(lp->flags, LPFLG_LSA_ACTIVE)) || !(IS_FLOOD_AREA(lp->type) || IS_FLOOD_AS(lp->type))) { return; } lsa.area = lp->area; lsa.data = &lsah; if (IS_FLOOD_AS(lp->type)) { lsah.type = OSPF_OPAQUE_AS_LSA; tmp = SET_OPAQUE_LSID(OPAQUE_TYPE_INTER_AS_LSA, lp->instance); lsah.id.s_addr = htonl(tmp); } else { lsah.type = OSPF_OPAQUE_AREA_LSA; if (IS_INTER_AS(lp->type)) { /* Set the area context if not know */ if (lp->area == NULL) lp->area = ospf_area_lookup_by_area_id( top, OspfMplsTE.interas_areaid); /* Unable to set the area context. Abort! */ if (lp->area == NULL) { flog_warn( EC_OSPF_TE_UNEXPECTED, "MPLS-TE(ospf_mpls_te_lsa_schedule) Area context is null. Abort !"); return; } tmp = SET_OPAQUE_LSID(OPAQUE_TYPE_INTER_AS_LSA, lp->instance); } else tmp = SET_OPAQUE_LSID( OPAQUE_TYPE_TRAFFIC_ENGINEERING_LSA, lp->instance); lsah.id.s_addr = htonl(tmp); } switch (opcode) { case REORIGINATE_THIS_LSA: if (IS_FLOOD_AS(lp->type)) { ospf_opaque_lsa_reoriginate_schedule( (void *)top, OSPF_OPAQUE_AS_LSA, OPAQUE_TYPE_INTER_AS_LSA); break; } if (IS_FLOOD_AREA(lp->type)) { if (IS_INTER_AS(lp->type)) ospf_opaque_lsa_reoriginate_schedule( (void *)lp->area, OSPF_OPAQUE_AREA_LSA, OPAQUE_TYPE_INTER_AS_LSA); else ospf_opaque_lsa_reoriginate_schedule( (void *)lp->area, OSPF_OPAQUE_AREA_LSA, OPAQUE_TYPE_TRAFFIC_ENGINEERING_LSA); break; } break; case REFRESH_THIS_LSA: ospf_opaque_lsa_refresh_schedule(&lsa); break; case FLUSH_THIS_LSA: /* Reset Activity flag */ lp->flags = LPFLG_LSA_INACTIVE; ospf_opaque_lsa_flush_schedule(&lsa); break; default: flog_warn(EC_OSPF_TE_UNEXPECTED, "ospf_mpls_te_lsa_schedule: Unknown opcode (%u)", opcode); break; } return; } /*------------------------------------------------------------------------* * Followings are vty session control functions. *------------------------------------------------------------------------*/ static uint16_t show_vty_router_addr(struct vty *vty, struct tlv_header *tlvh) { struct te_tlv_router_addr *top = (struct te_tlv_router_addr *)tlvh; if (vty != NULL) vty_out(vty, " Router-Address: %s\n", inet_ntoa(top->value)); else zlog_debug(" Router-Address: %s", inet_ntoa(top->value)); return TLV_SIZE(tlvh); } static uint16_t show_vty_link_header(struct vty *vty, struct tlv_header *tlvh) { struct te_tlv_link *top = (struct te_tlv_link *)tlvh; if (vty != NULL) vty_out(vty, " Link: %u octets of data\n", ntohs(top->header.length)); else zlog_debug(" Link: %u octets of data", ntohs(top->header.length)); return TLV_HDR_SIZE; /* Here is special, not "TLV_SIZE". */ } static uint16_t show_vty_link_subtlv_link_type(struct vty *vty, struct tlv_header *tlvh) { struct te_link_subtlv_link_type *top; const char *cp = "Unknown"; top = (struct te_link_subtlv_link_type *)tlvh; switch (top->link_type.value) { case LINK_TYPE_SUBTLV_VALUE_PTP: cp = "Point-to-point"; break; case LINK_TYPE_SUBTLV_VALUE_MA: cp = "Multiaccess"; break; default: break; } if (vty != NULL) vty_out(vty, " Link-Type: %s (%u)\n", cp, top->link_type.value); else zlog_debug(" Link-Type: %s (%u)", cp, top->link_type.value); return TLV_SIZE(tlvh); } static uint16_t show_vty_link_subtlv_link_id(struct vty *vty, struct tlv_header *tlvh) { struct te_link_subtlv_link_id *top; top = (struct te_link_subtlv_link_id *)tlvh; if (vty != NULL) vty_out(vty, " Link-ID: %s\n", inet_ntoa(top->value)); else zlog_debug(" Link-ID: %s", inet_ntoa(top->value)); return TLV_SIZE(tlvh); } static uint16_t show_vty_link_subtlv_lclif_ipaddr(struct vty *vty, struct tlv_header *tlvh) { struct te_link_subtlv_lclif_ipaddr *top; int i, n; top = (struct te_link_subtlv_lclif_ipaddr *)tlvh; n = ntohs(tlvh->length) / sizeof(top->value[0]); if (vty != NULL) vty_out(vty, " Local Interface IP Address(es): %d\n", n); else zlog_debug(" Local Interface IP Address(es): %d", n); for (i = 0; i < n; i++) { if (vty != NULL) vty_out(vty, " #%d: %s\n", i, inet_ntoa(top->value[i])); else zlog_debug(" #%d: %s", i, inet_ntoa(top->value[i])); } return TLV_SIZE(tlvh); } static uint16_t show_vty_link_subtlv_rmtif_ipaddr(struct vty *vty, struct tlv_header *tlvh) { struct te_link_subtlv_rmtif_ipaddr *top; int i, n; top = (struct te_link_subtlv_rmtif_ipaddr *)tlvh; n = ntohs(tlvh->length) / sizeof(top->value[0]); if (vty != NULL) vty_out(vty, " Remote Interface IP Address(es): %d\n", n); else zlog_debug(" Remote Interface IP Address(es): %d", n); for (i = 0; i < n; i++) { if (vty != NULL) vty_out(vty, " #%d: %s\n", i, inet_ntoa(top->value[i])); else zlog_debug(" #%d: %s", i, inet_ntoa(top->value[i])); } return TLV_SIZE(tlvh); } static uint16_t show_vty_link_subtlv_te_metric(struct vty *vty, struct tlv_header *tlvh) { struct te_link_subtlv_te_metric *top; top = (struct te_link_subtlv_te_metric *)tlvh; if (vty != NULL) vty_out(vty, " Traffic Engineering Metric: %u\n", (uint32_t)ntohl(top->value)); else zlog_debug(" Traffic Engineering Metric: %u", (uint32_t)ntohl(top->value)); return TLV_SIZE(tlvh); } static uint16_t show_vty_link_subtlv_max_bw(struct vty *vty, struct tlv_header *tlvh) { struct te_link_subtlv_max_bw *top; float fval; top = (struct te_link_subtlv_max_bw *)tlvh; fval = ntohf(top->value); if (vty != NULL) vty_out(vty, " Maximum Bandwidth: %g (Bytes/sec)\n", fval); else zlog_debug(" Maximum Bandwidth: %g (Bytes/sec)", fval); return TLV_SIZE(tlvh); } static uint16_t show_vty_link_subtlv_max_rsv_bw(struct vty *vty, struct tlv_header *tlvh) { struct te_link_subtlv_max_rsv_bw *top; float fval; top = (struct te_link_subtlv_max_rsv_bw *)tlvh; fval = ntohf(top->value); if (vty != NULL) vty_out(vty, " Maximum Reservable Bandwidth: %g (Bytes/sec)\n", fval); else zlog_debug(" Maximum Reservable Bandwidth: %g (Bytes/sec)", fval); return TLV_SIZE(tlvh); } static uint16_t show_vty_link_subtlv_unrsv_bw(struct vty *vty, struct tlv_header *tlvh) { struct te_link_subtlv_unrsv_bw *top; float fval1, fval2; int i; top = (struct te_link_subtlv_unrsv_bw *)tlvh; if (vty != NULL) vty_out(vty, " Unreserved Bandwidth per Class Type in Byte/s:\n"); else zlog_debug( " Unreserved Bandwidth per Class Type in Byte/s:"); for (i = 0; i < MAX_CLASS_TYPE; i += 2) { fval1 = ntohf(top->value[i]); fval2 = ntohf(top->value[i + 1]); if (vty != NULL) vty_out(vty, " [%d]: %g (Bytes/sec),\t[%d]: %g (Bytes/sec)\n", i, fval1, i + 1, fval2); else zlog_debug( " [%d]: %g (Bytes/sec),\t[%d]: %g (Bytes/sec)", i, fval1, i + 1, fval2); } return TLV_SIZE(tlvh); } static uint16_t show_vty_link_subtlv_rsc_clsclr(struct vty *vty, struct tlv_header *tlvh) { struct te_link_subtlv_rsc_clsclr *top; top = (struct te_link_subtlv_rsc_clsclr *)tlvh; if (vty != NULL) vty_out(vty, " Resource class/color: 0x%x\n", (uint32_t)ntohl(top->value)); else zlog_debug(" Resource Class/Color: 0x%x", (uint32_t)ntohl(top->value)); return TLV_SIZE(tlvh); } static uint16_t show_vty_link_subtlv_lrrid(struct vty *vty, struct tlv_header *tlvh) { struct te_link_subtlv_lrrid *top; top = (struct te_link_subtlv_lrrid *)tlvh; if (vty != NULL) { vty_out(vty, " Local TE Router ID: %s\n", inet_ntoa(top->local)); vty_out(vty, " Remote TE Router ID: %s\n", inet_ntoa(top->remote)); } else { zlog_debug(" Local TE Router ID: %s", inet_ntoa(top->local)); zlog_debug(" Remote TE Router ID: %s", inet_ntoa(top->remote)); } return TLV_SIZE(tlvh); } static uint16_t show_vty_link_subtlv_llri(struct vty *vty, struct tlv_header *tlvh) { struct te_link_subtlv_llri *top; top = (struct te_link_subtlv_llri *)tlvh; if (vty != NULL) { vty_out(vty, " Link Local ID: %d\n", (uint32_t)ntohl(top->local)); vty_out(vty, " Link Remote ID: %d\n", (uint32_t)ntohl(top->remote)); } else { zlog_debug(" Link Local ID: %d", (uint32_t)ntohl(top->local)); zlog_debug(" Link Remote ID: %d", (uint32_t)ntohl(top->remote)); } return TLV_SIZE(tlvh); } static uint16_t show_vty_link_subtlv_rip(struct vty *vty, struct tlv_header *tlvh) { struct te_link_subtlv_rip *top; top = (struct te_link_subtlv_rip *)tlvh; if (vty != NULL) vty_out(vty, " Inter-AS TE Remote ASBR IP address: %s\n", inet_ntoa(top->value)); else zlog_debug(" Inter-AS TE Remote ASBR IP address: %s", inet_ntoa(top->value)); return TLV_SIZE(tlvh); } static uint16_t show_vty_link_subtlv_ras(struct vty *vty, struct tlv_header *tlvh) { struct te_link_subtlv_ras *top; top = (struct te_link_subtlv_ras *)tlvh; if (vty != NULL) vty_out(vty, " Inter-AS TE Remote AS number: %u\n", ntohl(top->value)); else zlog_debug(" Inter-AS TE Remote AS number: %u", ntohl(top->value)); return TLV_SIZE(tlvh); } static uint16_t show_vty_link_subtlv_av_delay(struct vty *vty, struct tlv_header *tlvh) { struct te_link_subtlv_av_delay *top; uint32_t delay; uint32_t anomalous; top = (struct te_link_subtlv_av_delay *)tlvh; delay = (uint32_t)ntohl(top->value) & TE_EXT_MASK; anomalous = (uint32_t)ntohl(top->value) & TE_EXT_ANORMAL; if (vty != NULL) vty_out(vty, " %s Average Link Delay: %d (micro-sec)\n", anomalous ? "Anomalous" : "Normal", delay); else zlog_debug(" %s Average Link Delay: %d (micro-sec)", anomalous ? "Anomalous" : "Normal", delay); return TLV_SIZE(tlvh); } static uint16_t show_vty_link_subtlv_mm_delay(struct vty *vty, struct tlv_header *tlvh) { struct te_link_subtlv_mm_delay *top; uint32_t low, high; uint32_t anomalous; top = (struct te_link_subtlv_mm_delay *)tlvh; low = (uint32_t)ntohl(top->low) & TE_EXT_MASK; anomalous = (uint32_t)ntohl(top->low) & TE_EXT_ANORMAL; high = (uint32_t)ntohl(top->high); if (vty != NULL) vty_out(vty, " %s Min/Max Link Delay: %d/%d (micro-sec)\n", anomalous ? "Anomalous" : "Normal", low, high); else zlog_debug(" %s Min/Max Link Delay: %d/%d (micro-sec)", anomalous ? "Anomalous" : "Normal", low, high); return TLV_SIZE(tlvh); } static uint16_t show_vty_link_subtlv_delay_var(struct vty *vty, struct tlv_header *tlvh) { struct te_link_subtlv_delay_var *top; uint32_t jitter; top = (struct te_link_subtlv_delay_var *)tlvh; jitter = (uint32_t)ntohl(top->value) & TE_EXT_MASK; if (vty != NULL) vty_out(vty, " Delay Variation: %d (micro-sec)\n", jitter); else zlog_debug(" Delay Variation: %d (micro-sec)", jitter); return TLV_SIZE(tlvh); } static uint16_t show_vty_link_subtlv_pkt_loss(struct vty *vty, struct tlv_header *tlvh) { struct te_link_subtlv_pkt_loss *top; uint32_t loss; uint32_t anomalous; float fval; top = (struct te_link_subtlv_pkt_loss *)tlvh; loss = (uint32_t)ntohl(top->value) & TE_EXT_MASK; fval = (float)(loss * LOSS_PRECISION); anomalous = (uint32_t)ntohl(top->value) & TE_EXT_ANORMAL; if (vty != NULL) vty_out(vty, " %s Link Loss: %g (%%)\n", anomalous ? "Anomalous" : "Normal", fval); else zlog_debug(" %s Link Loss: %g (%%)", anomalous ? "Anomalous" : "Normal", fval); return TLV_SIZE(tlvh); } static uint16_t show_vty_link_subtlv_res_bw(struct vty *vty, struct tlv_header *tlvh) { struct te_link_subtlv_res_bw *top; float fval; top = (struct te_link_subtlv_res_bw *)tlvh; fval = ntohf(top->value); if (vty != NULL) vty_out(vty, " Unidirectional Residual Bandwidth: %g (Bytes/sec)\n", fval); else zlog_debug( " Unidirectional Residual Bandwidth: %g (Bytes/sec)", fval); return TLV_SIZE(tlvh); } static uint16_t show_vty_link_subtlv_ava_bw(struct vty *vty, struct tlv_header *tlvh) { struct te_link_subtlv_ava_bw *top; float fval; top = (struct te_link_subtlv_ava_bw *)tlvh; fval = ntohf(top->value); if (vty != NULL) vty_out(vty, " Unidirectional Available Bandwidth: %g (Bytes/sec)\n", fval); else zlog_debug( " Unidirectional Available Bandwidth: %g (Bytes/sec)", fval); return TLV_SIZE(tlvh); } static uint16_t show_vty_link_subtlv_use_bw(struct vty *vty, struct tlv_header *tlvh) { struct te_link_subtlv_use_bw *top; float fval; top = (struct te_link_subtlv_use_bw *)tlvh; fval = ntohf(top->value); if (vty != NULL) vty_out(vty, " Unidirectional Utilized Bandwidth: %g (Bytes/sec)\n", fval); else zlog_debug( " Unidirectional Utilized Bandwidth: %g (Bytes/sec)", fval); return TLV_SIZE(tlvh); } static uint16_t show_vty_unknown_tlv(struct vty *vty, struct tlv_header *tlvh) { if (vty != NULL) vty_out(vty, " Unknown TLV: [type(0x%x), length(0x%x)]\n", ntohs(tlvh->type), ntohs(tlvh->length)); else zlog_debug(" Unknown TLV: [type(0x%x), length(0x%x)]", ntohs(tlvh->type), ntohs(tlvh->length)); return TLV_SIZE(tlvh); } static uint16_t ospf_mpls_te_show_link_subtlv(struct vty *vty, struct tlv_header *tlvh0, uint16_t subtotal, uint16_t total) { struct tlv_header *tlvh; uint16_t sum = subtotal; for (tlvh = tlvh0; sum < total; tlvh = TLV_HDR_NEXT(tlvh)) { switch (ntohs(tlvh->type)) { case TE_LINK_SUBTLV_LINK_TYPE: sum += show_vty_link_subtlv_link_type(vty, tlvh); break; case TE_LINK_SUBTLV_LINK_ID: sum += show_vty_link_subtlv_link_id(vty, tlvh); break; case TE_LINK_SUBTLV_LCLIF_IPADDR: sum += show_vty_link_subtlv_lclif_ipaddr(vty, tlvh); break; case TE_LINK_SUBTLV_RMTIF_IPADDR: sum += show_vty_link_subtlv_rmtif_ipaddr(vty, tlvh); break; case TE_LINK_SUBTLV_TE_METRIC: sum += show_vty_link_subtlv_te_metric(vty, tlvh); break; case TE_LINK_SUBTLV_MAX_BW: sum += show_vty_link_subtlv_max_bw(vty, tlvh); break; case TE_LINK_SUBTLV_MAX_RSV_BW: sum += show_vty_link_subtlv_max_rsv_bw(vty, tlvh); break; case TE_LINK_SUBTLV_UNRSV_BW: sum += show_vty_link_subtlv_unrsv_bw(vty, tlvh); break; case TE_LINK_SUBTLV_RSC_CLSCLR: sum += show_vty_link_subtlv_rsc_clsclr(vty, tlvh); break; case TE_LINK_SUBTLV_LRRID: sum += show_vty_link_subtlv_lrrid(vty, tlvh); break; case TE_LINK_SUBTLV_LLRI: sum += show_vty_link_subtlv_llri(vty, tlvh); break; case TE_LINK_SUBTLV_RIP: sum += show_vty_link_subtlv_rip(vty, tlvh); break; case TE_LINK_SUBTLV_RAS: sum += show_vty_link_subtlv_ras(vty, tlvh); break; case TE_LINK_SUBTLV_AV_DELAY: sum += show_vty_link_subtlv_av_delay(vty, tlvh); break; case TE_LINK_SUBTLV_MM_DELAY: sum += show_vty_link_subtlv_mm_delay(vty, tlvh); break; case TE_LINK_SUBTLV_DELAY_VAR: sum += show_vty_link_subtlv_delay_var(vty, tlvh); break; case TE_LINK_SUBTLV_PKT_LOSS: sum += show_vty_link_subtlv_pkt_loss(vty, tlvh); break; case TE_LINK_SUBTLV_RES_BW: sum += show_vty_link_subtlv_res_bw(vty, tlvh); break; case TE_LINK_SUBTLV_AVA_BW: sum += show_vty_link_subtlv_ava_bw(vty, tlvh); break; case TE_LINK_SUBTLV_USE_BW: sum += show_vty_link_subtlv_use_bw(vty, tlvh); break; default: sum += show_vty_unknown_tlv(vty, tlvh); break; } } return sum; } static void ospf_mpls_te_show_info(struct vty *vty, struct ospf_lsa *lsa) { struct lsa_header *lsah = (struct lsa_header *)lsa->data; struct tlv_header *tlvh, *next; uint16_t sum, total; uint16_t (*subfunc)(struct vty * vty, struct tlv_header * tlvh, uint16_t subtotal, uint16_t total) = NULL; sum = 0; total = ntohs(lsah->length) - OSPF_LSA_HEADER_SIZE; for (tlvh = TLV_HDR_TOP(lsah); sum < total; tlvh = (next ? next : TLV_HDR_NEXT(tlvh))) { if (subfunc != NULL) { sum = (*subfunc)(vty, tlvh, sum, total); next = (struct tlv_header *)((char *)tlvh + sum); subfunc = NULL; continue; } next = NULL; switch (ntohs(tlvh->type)) { case TE_TLV_ROUTER_ADDR: sum += show_vty_router_addr(vty, tlvh); break; case TE_TLV_LINK: sum += show_vty_link_header(vty, tlvh); subfunc = ospf_mpls_te_show_link_subtlv; next = TLV_DATA(tlvh); break; default: sum += show_vty_unknown_tlv(vty, tlvh); break; } } return; } static void ospf_mpls_te_config_write_router(struct vty *vty) { if (OspfMplsTE.enabled) { vty_out(vty, " mpls-te on\n"); vty_out(vty, " mpls-te router-address %s\n", inet_ntoa(OspfMplsTE.router_addr.value)); } if (OspfMplsTE.inter_as == AS) vty_out(vty, " mpls-te inter-as as\n"); if (OspfMplsTE.inter_as == Area) vty_out(vty, " mpls-te inter-as area %s \n", inet_ntoa(OspfMplsTE.interas_areaid)); return; } /*------------------------------------------------------------------------* * Followings are vty command functions. *------------------------------------------------------------------------*/ DEFUN (ospf_mpls_te_on, ospf_mpls_te_on_cmd, "mpls-te on", MPLS_TE_STR "Enable the MPLS-TE functionality\n") { VTY_DECLVAR_INSTANCE_CONTEXT(ospf, ospf); struct listnode *node; struct mpls_te_link *lp; if (OspfMplsTE.enabled) return CMD_SUCCESS; if (IS_DEBUG_OSPF_EVENT) zlog_debug("MPLS-TE: OFF -> ON"); OspfMplsTE.enabled = true; /* Reoriginate RFC3630 & RFC6827 Links */ ospf_mpls_te_foreach_area(ospf_mpls_te_lsa_schedule, REORIGINATE_THIS_LSA); /* Reoriginate LSA if INTER-AS is always on */ if (OspfMplsTE.inter_as != Off) { for (ALL_LIST_ELEMENTS_RO(OspfMplsTE.iflist, node, lp)) { if (IS_INTER_AS(lp->type)) { ospf_mpls_te_lsa_schedule(lp, REORIGINATE_THIS_LSA); } } } return CMD_SUCCESS; } DEFUN (no_ospf_mpls_te, no_ospf_mpls_te_cmd, "no mpls-te [on]", NO_STR MPLS_TE_STR "Disable the MPLS-TE functionality\n") { VTY_DECLVAR_INSTANCE_CONTEXT(ospf, ospf); struct listnode *node, *nnode; struct mpls_te_link *lp; if (!OspfMplsTE.enabled) return CMD_SUCCESS; if (IS_DEBUG_OSPF_EVENT) zlog_debug("MPLS-TE: ON -> OFF"); OspfMplsTE.enabled = false; for (ALL_LIST_ELEMENTS(OspfMplsTE.iflist, node, nnode, lp)) if (CHECK_FLAG(lp->flags, LPFLG_LSA_ENGAGED)) ospf_mpls_te_lsa_schedule(lp, FLUSH_THIS_LSA); return CMD_SUCCESS; } DEFUN (ospf_mpls_te_router_addr, ospf_mpls_te_router_addr_cmd, "mpls-te router-address A.B.C.D", MPLS_TE_STR "Stable IP address of the advertising router\n" "MPLS-TE router address in IPv4 address format\n") { VTY_DECLVAR_INSTANCE_CONTEXT(ospf, ospf); int idx_ipv4 = 2; struct te_tlv_router_addr *ra = &OspfMplsTE.router_addr; struct in_addr value; if (!inet_aton(argv[idx_ipv4]->arg, &value)) { vty_out(vty, "Please specify Router-Addr by A.B.C.D\n"); return CMD_WARNING; } if (ntohs(ra->header.type) == 0 || ntohl(ra->value.s_addr) != ntohl(value.s_addr)) { struct listnode *node, *nnode; struct mpls_te_link *lp; int need_to_reoriginate = 0; set_mpls_te_router_addr(value); if (!OspfMplsTE.enabled) return CMD_SUCCESS; for (ALL_LIST_ELEMENTS(OspfMplsTE.iflist, node, nnode, lp)) { if ((lp->area == NULL) || IS_FLOOD_AS(lp->type)) continue; if (!CHECK_FLAG(lp->flags, LPFLG_LSA_ENGAGED)) { need_to_reoriginate = 1; break; } } for (ALL_LIST_ELEMENTS(OspfMplsTE.iflist, node, nnode, lp)) { if ((lp->area == NULL) || IS_FLOOD_AS(lp->type)) continue; if (need_to_reoriginate) SET_FLAG(lp->flags, LPFLG_LSA_FORCED_REFRESH); else ospf_mpls_te_lsa_schedule(lp, REFRESH_THIS_LSA); } if (need_to_reoriginate) ospf_mpls_te_foreach_area(ospf_mpls_te_lsa_schedule, REORIGINATE_THIS_LSA); } return CMD_SUCCESS; } static int set_inter_as_mode(struct vty *vty, const char *mode_name, const char *area_id) { enum inter_as_mode mode; struct listnode *node; struct mpls_te_link *lp; int format; if (OspfMplsTE.enabled) { /* Read and Check inter_as mode */ if (strcmp(mode_name, "as") == 0) mode = AS; else if (strcmp(mode_name, "area") == 0) { mode = Area; VTY_GET_OSPF_AREA_ID(OspfMplsTE.interas_areaid, format, area_id); } else { vty_out(vty, "Unknown mode. Please choose between as or area\n"); return CMD_WARNING; } if (IS_DEBUG_OSPF_EVENT) zlog_debug( "MPLS-TE: Inter-AS enable with %s flooding support", mode2text[mode]); /* Register new callbacks regarding the flooding scope (AS or * Area) */ if (ospf_mpls_te_register(mode) < 0) { vty_out(vty, "Internal error: Unable to register Inter-AS functions\n"); return CMD_WARNING; } /* Enable mode and re-originate LSA if needed */ if ((OspfMplsTE.inter_as == Off) && (mode != OspfMplsTE.inter_as)) { OspfMplsTE.inter_as = mode; /* Re-originate all InterAS-TEv2 LSA */ for (ALL_LIST_ELEMENTS_RO(OspfMplsTE.iflist, node, lp)) { if (IS_INTER_AS(lp->type)) { if (mode == AS) lp->type |= FLOOD_AS; else lp->type |= FLOOD_AREA; ospf_mpls_te_lsa_schedule( lp, REORIGINATE_THIS_LSA); } } } else { vty_out(vty, "Please change Inter-AS support to disable first before going to mode %s\n", mode2text[mode]); return CMD_WARNING; } } else { vty_out(vty, "mpls-te has not been turned on\n"); return CMD_WARNING; } return CMD_SUCCESS; } DEFUN (ospf_mpls_te_inter_as_as, ospf_mpls_te_inter_as_cmd, "mpls-te inter-as as", MPLS_TE_STR "Configure MPLS-TE Inter-AS support\n" "AS native mode self originate INTER_AS LSA with Type 11 (as flooding scope)\n") { return set_inter_as_mode(vty, "as", ""); } DEFUN (ospf_mpls_te_inter_as_area, ospf_mpls_te_inter_as_area_cmd, "mpls-te inter-as area ", MPLS_TE_STR "Configure MPLS-TE Inter-AS support\n" "AREA native mode self originate INTER_AS LSA with Type 10 (area flooding scope)\n" "OSPF area ID in IP format\n" "OSPF area ID as decimal value\n") { int idx_ipv4_number = 3; return set_inter_as_mode(vty, "area", argv[idx_ipv4_number]->arg); } DEFUN (no_ospf_mpls_te_inter_as, no_ospf_mpls_te_inter_as_cmd, "no mpls-te inter-as", NO_STR MPLS_TE_STR "Disable MPLS-TE Inter-AS support\n") { struct listnode *node, *nnode; struct mpls_te_link *lp; if (IS_DEBUG_OSPF_EVENT) zlog_debug("MPLS-TE: Inter-AS support OFF"); if ((OspfMplsTE.enabled) && (OspfMplsTE.inter_as != Off)) { /* Flush all Inter-AS LSA */ for (ALL_LIST_ELEMENTS(OspfMplsTE.iflist, node, nnode, lp)) if (IS_INTER_AS(lp->type) && CHECK_FLAG(lp->flags, LPFLG_LSA_ENGAGED)) ospf_mpls_te_lsa_schedule(lp, FLUSH_THIS_LSA); /* Deregister the Callbacks for Inter-AS support */ ospf_mpls_te_unregister(); OspfMplsTE.inter_as = Off; } return CMD_SUCCESS; } DEFUN (show_ip_ospf_mpls_te_router, show_ip_ospf_mpls_te_router_cmd, "show ip ospf mpls-te router", SHOW_STR IP_STR OSPF_STR "MPLS-TE information\n" "MPLS-TE Router parameters\n") { if (OspfMplsTE.enabled) { vty_out(vty, "--- MPLS-TE router parameters ---\n"); if (ntohs(OspfMplsTE.router_addr.header.type) != 0) show_vty_router_addr(vty, &OspfMplsTE.router_addr.header); else vty_out(vty, " N/A\n"); } return CMD_SUCCESS; } static void show_mpls_te_link_sub(struct vty *vty, struct interface *ifp) { struct mpls_te_link *lp; if ((OspfMplsTE.enabled) && HAS_LINK_PARAMS(ifp) && !if_is_loopback(ifp) && if_is_up(ifp) && ((lp = lookup_linkparams_by_ifp(ifp)) != NULL)) { /* Continue only if interface is not passive or support Inter-AS * TEv2 */ if (!(ospf_oi_count(ifp) > 0)) { if (IS_INTER_AS(lp->type)) { vty_out(vty, "-- Inter-AS TEv2 link parameters for %s --\n", ifp->name); } else { /* MPLS-TE is not activate on this interface */ /* or this interface is passive and Inter-AS * TEv2 is not activate */ vty_out(vty, " %s: MPLS-TE is disabled on this interface\n", ifp->name); return; } } else { vty_out(vty, "-- MPLS-TE link parameters for %s --\n", ifp->name); } if (TLV_TYPE(lp->link_type) != 0) show_vty_link_subtlv_link_type(vty, &lp->link_type.header); if (TLV_TYPE(lp->link_id) != 0) show_vty_link_subtlv_link_id(vty, &lp->link_id.header); if (TLV_TYPE(lp->lclif_ipaddr) != 0) show_vty_link_subtlv_lclif_ipaddr( vty, &lp->lclif_ipaddr.header); if (TLV_TYPE(lp->rmtif_ipaddr) != 0) show_vty_link_subtlv_rmtif_ipaddr( vty, &lp->rmtif_ipaddr.header); if (TLV_TYPE(lp->rip) != 0) show_vty_link_subtlv_rip(vty, &lp->rip.header); if (TLV_TYPE(lp->ras) != 0) show_vty_link_subtlv_ras(vty, &lp->ras.header); if (TLV_TYPE(lp->te_metric) != 0) show_vty_link_subtlv_te_metric(vty, &lp->te_metric.header); if (TLV_TYPE(lp->max_bw) != 0) show_vty_link_subtlv_max_bw(vty, &lp->max_bw.header); if (TLV_TYPE(lp->max_rsv_bw) != 0) show_vty_link_subtlv_max_rsv_bw(vty, &lp->max_rsv_bw.header); if (TLV_TYPE(lp->unrsv_bw) != 0) show_vty_link_subtlv_unrsv_bw(vty, &lp->unrsv_bw.header); if (TLV_TYPE(lp->rsc_clsclr) != 0) show_vty_link_subtlv_rsc_clsclr(vty, &lp->rsc_clsclr.header); if (TLV_TYPE(lp->av_delay) != 0) show_vty_link_subtlv_av_delay(vty, &lp->av_delay.header); if (TLV_TYPE(lp->mm_delay) != 0) show_vty_link_subtlv_mm_delay(vty, &lp->mm_delay.header); if (TLV_TYPE(lp->delay_var) != 0) show_vty_link_subtlv_delay_var(vty, &lp->delay_var.header); if (TLV_TYPE(lp->pkt_loss) != 0) show_vty_link_subtlv_pkt_loss(vty, &lp->pkt_loss.header); if (TLV_TYPE(lp->res_bw) != 0) show_vty_link_subtlv_res_bw(vty, &lp->res_bw.header); if (TLV_TYPE(lp->ava_bw) != 0) show_vty_link_subtlv_ava_bw(vty, &lp->ava_bw.header); if (TLV_TYPE(lp->use_bw) != 0) show_vty_link_subtlv_use_bw(vty, &lp->use_bw.header); vty_out(vty, "---------------\n\n"); } else { vty_out(vty, " %s: MPLS-TE is disabled on this interface\n", ifp->name); } return; } DEFUN (show_ip_ospf_mpls_te_link, show_ip_ospf_mpls_te_link_cmd, "show ip ospf [vrf ] mpls-te interface [INTERFACE]", SHOW_STR IP_STR OSPF_STR VRF_CMD_HELP_STR "All VRFs\n" "MPLS-TE information\n" "Interface information\n" "Interface name\n") { struct vrf *vrf; int idx_interface = 0; struct interface *ifp = NULL; struct listnode *node; char *vrf_name = NULL; bool all_vrf = false; int inst = 0; int idx_vrf = 0; struct ospf *ospf = NULL; if (argv_find(argv, argc, "vrf", &idx_vrf)) { vrf_name = argv[idx_vrf + 1]->arg; all_vrf = strmatch(vrf_name, "all"); } argv_find(argv, argc, "INTERFACE", &idx_interface); /* vrf input is provided could be all or specific vrf*/ if (vrf_name) { if (all_vrf) { for (ALL_LIST_ELEMENTS_RO(om->ospf, node, ospf)) { if (!ospf->oi_running) continue; vrf = vrf_lookup_by_id(ospf->vrf_id); FOR_ALL_INTERFACES (vrf, ifp) show_mpls_te_link_sub(vty, ifp); } return CMD_SUCCESS; } ospf = ospf_lookup_by_inst_name(inst, vrf_name); } else ospf = ospf_lookup_by_vrf_id(VRF_DEFAULT); if (ospf == NULL || !ospf->oi_running) return CMD_SUCCESS; vrf = vrf_lookup_by_id(ospf->vrf_id); if (!vrf) return CMD_SUCCESS; if (idx_interface) { ifp = if_lookup_by_name( argv[idx_interface]->arg, ospf->vrf_id); if (ifp == NULL) { vty_out(vty, "No such interface name in vrf %s\n", vrf->name); return CMD_SUCCESS; } } if (!ifp) { FOR_ALL_INTERFACES (vrf, ifp) show_mpls_te_link_sub(vty, ifp); return CMD_SUCCESS; } show_mpls_te_link_sub(vty, ifp); return CMD_SUCCESS; } static void ospf_mpls_te_register_vty(void) { install_element(VIEW_NODE, &show_ip_ospf_mpls_te_router_cmd); install_element(VIEW_NODE, &show_ip_ospf_mpls_te_link_cmd); install_element(OSPF_NODE, &ospf_mpls_te_on_cmd); install_element(OSPF_NODE, &no_ospf_mpls_te_cmd); install_element(OSPF_NODE, &ospf_mpls_te_router_addr_cmd); install_element(OSPF_NODE, &ospf_mpls_te_inter_as_cmd); install_element(OSPF_NODE, &ospf_mpls_te_inter_as_area_cmd); install_element(OSPF_NODE, &no_ospf_mpls_te_inter_as_cmd); return; } frr-7.2.1/ospfd/ospf_te.h0000644000000000000000000003143613610377563012146 00000000000000/* * This is an implementation of RFC3630, RFC5392 & RFC6827 * Copyright (C) 2001 KDD R&D Laboratories, Inc. * http://www.kddlabs.co.jp/ * * Copyright (C) 2012 Orange Labs * http://www.orange.com * * This file is part of GNU Zebra. * * GNU Zebra is free software; you can redistribute it and/or modify it * under the terms of the GNU General Public License as published by the * Free Software Foundation; either version 2, or (at your option) any * later version. * * GNU Zebra 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; see the file COPYING; if not, write to the Free Software * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA */ /* Add support of RFC7471 */ /* Add support of RFC5392 */ /* Add support of RFC6827 (partial) */ #ifndef _ZEBRA_OSPF_MPLS_TE_H #define _ZEBRA_OSPF_MPLS_TE_H /* * Opaque LSA's link state ID for Traffic Engineering is * structured as follows. * * 24 16 8 0 * +--------+--------+--------+--------+ * | 1 | MBZ |........|........| * +--------+--------+--------+--------+ * |<-Type->||<-- Instance --->| * * * Type: IANA has assigned '1' for Traffic Engineering. * MBZ: Reserved, must be set to zero. * Instance: User may select an arbitrary 16-bit value. * */ #define MAX_LEGAL_TE_INSTANCE_NUM (0xffff) #define LEGAL_TE_INSTANCE_RANGE(i) (0 <= (i) && (i) <= 0xffff) /* * 24 16 8 0 * +--------+--------+--------+--------+ --- * | LS age |Options | 10 | A * +--------+--------+--------+--------+ | * | 1 | 0 | Instance | | * +--------+--------+--------+--------+ | * | Advertising router | | Standard (Opaque) LSA header; * +--------+--------+--------+--------+ | Only type-10 is used. * | LS sequence number | | * +--------+--------+--------+--------+ | * | LS checksum | Length | V * +--------+--------+--------+--------+ --- * | Type | Length | A * +--------+--------+--------+--------+ | TLV part for TE; Values might be * | Values ... | V structured as a set of sub-TLVs. * +--------+--------+--------+--------+ --- */ /* Following define the type of TE link regarding the various RFC */ #define STD_TE 0x01 #define GMPLS 0x02 #define INTER_AS 0x04 #define PSEUDO_TE 0x08 #define FLOOD_AREA 0x10 #define FLOOD_AS 0x20 #define EMULATED 0x80 #define IS_STD_TE(x) (x & STD_TE) #define IS_PSEUDO_TE(x) (x & PSEUDO_TE) #define IS_INTER_AS(x) (x & INTER_AS) #define IS_EMULATED(x) (x & EMULATED) #define IS_FLOOD_AREA(x) (x & FLOOD_AREA) #define IS_FLOOD_AS(x) (x & FLOOD_AS) #define IS_INTER_AS_EMU(x) (x & INTER_AS & EMULATED) #define IS_INTER_AS_AS(x) (x & INTER_AS & FLOOD_AS) /* Flags to manage TE Link LSA */ #define LPFLG_LSA_INACTIVE 0x0 #define LPFLG_LSA_ACTIVE 0x1 #define LPFLG_LSA_ENGAGED 0x2 #define LPFLG_LOOKUP_DONE 0x4 #define LPFLG_LSA_FORCED_REFRESH 0x8 /* * Following section defines TLV body parts. */ /* Router Address TLV */ /* Mandatory */ #define TE_TLV_ROUTER_ADDR 1 struct te_tlv_router_addr { struct tlv_header header; /* Value length is 4 octets. */ struct in_addr value; }; /* Link TLV */ #define TE_TLV_LINK 2 struct te_tlv_link { struct tlv_header header; /* A set of link-sub-TLVs will follow. */ }; /* Default TE TLV size */ #define TE_LINK_SUBTLV_DEF_SIZE 4 /* Link Type Sub-TLV */ /* Mandatory */ #define TE_LINK_SUBTLV_LINK_TYPE 1 #define TE_LINK_SUBTLV_TYPE_SIZE 1 struct te_link_subtlv_link_type { struct tlv_header header; /* Value length is 1 octet. */ struct { #define LINK_TYPE_SUBTLV_VALUE_PTP 1 #define LINK_TYPE_SUBTLV_VALUE_MA 2 uint8_t value; uint8_t padding[3]; } link_type; }; /* Link Sub-TLV: Link ID */ /* Mandatory */ #define TE_LINK_SUBTLV_LINK_ID 2 struct te_link_subtlv_link_id { struct tlv_header header; /* Value length is 4 octets. */ struct in_addr value; /* Same as router-lsa's link-id. */ }; /* Link Sub-TLV: Local Interface IP Address */ /* Optional */ #define TE_LINK_SUBTLV_LCLIF_IPADDR 3 struct te_link_subtlv_lclif_ipaddr { struct tlv_header header; /* Value length is 4 x N octets. */ struct in_addr value[1]; /* Local IP address(es). */ }; /* Link Sub-TLV: Remote Interface IP Address */ /* Optional */ #define TE_LINK_SUBTLV_RMTIF_IPADDR 4 struct te_link_subtlv_rmtif_ipaddr { struct tlv_header header; /* Value length is 4 x N octets. */ struct in_addr value[1]; /* Neighbor's IP address(es). */ }; /* Link Sub-TLV: Traffic Engineering Metric */ /* Optional */ #define TE_LINK_SUBTLV_TE_METRIC 5 struct te_link_subtlv_te_metric { struct tlv_header header; /* Value length is 4 octets. */ uint32_t value; /* Link metric for TE purpose. */ }; /* Link Sub-TLV: Maximum Bandwidth */ /* Optional */ #define TE_LINK_SUBTLV_MAX_BW 6 struct te_link_subtlv_max_bw { struct tlv_header header; /* Value length is 4 octets. */ float value; /* bytes/sec */ }; /* Link Sub-TLV: Maximum Reservable Bandwidth */ /* Optional */ #define TE_LINK_SUBTLV_MAX_RSV_BW 7 struct te_link_subtlv_max_rsv_bw { struct tlv_header header; /* Value length is 4 octets. */ float value; /* bytes/sec */ }; /* Link Sub-TLV: Unreserved Bandwidth */ /* Optional */ #define TE_LINK_SUBTLV_UNRSV_BW 8 #define TE_LINK_SUBTLV_UNRSV_SIZE 32 struct te_link_subtlv_unrsv_bw { struct tlv_header header; /* Value length is 32 octets. */ float value[MAX_CLASS_TYPE]; /* One for each priority level. */ }; /* Link Sub-TLV: Resource Class/Color */ /* Optional */ #define TE_LINK_SUBTLV_RSC_CLSCLR 9 struct te_link_subtlv_rsc_clsclr { struct tlv_header header; /* Value length is 4 octets. */ uint32_t value; /* Admin. group membership. */ }; /* For RFC6827 */ /* Local and Remote TE Router ID */ #define TE_LINK_SUBTLV_LRRID 10 #define TE_LINK_SUBTLV_LRRID_SIZE 8 struct te_link_subtlv_lrrid { struct tlv_header header; /* Value length is 8 octets. */ struct in_addr local; /* Local TE Router Identifier */ struct in_addr remote; /* Remote TE Router Identifier */ }; /* RFC4203: Link Local/Remote Identifiers */ #define TE_LINK_SUBTLV_LLRI 11 #define TE_LINK_SUBTLV_LLRI_SIZE 8 struct te_link_subtlv_llri { struct tlv_header header; /* Value length is 8 octets. */ uint32_t local; /* Link Local Identifier */ uint32_t remote; /* Link Remote Identifier */ }; /* Inter-RA Export Upward sub-TLV (12) and Inter-RA Export Downward sub-TLV (13) * (RFC6827bis) are not yet supported */ /* SUBTLV 14-16 (RFC4203) are not yet supported */ /* Bandwidth Constraints sub-TLV (17) (RFC4124) is not yet supported */ /* SUBLV 18-20 are for OSPFv3 TE (RFC5329). see ospf6d */ /* For RFC 5392 */ /* Remote AS Number sub-TLV */ #define TE_LINK_SUBTLV_RAS 21 struct te_link_subtlv_ras { struct tlv_header header; /* Value length is 4 octets. */ uint32_t value; /* Remote AS number */ }; /* IPv4 Remote ASBR ID Sub-TLV */ #define TE_LINK_SUBTLV_RIP 22 struct te_link_subtlv_rip { struct tlv_header header; /* Value length is 4 octets. */ struct in_addr value; /* Remote ASBR IP address */ }; /* SUBTLV 24 is IPv6 Remote ASBR ID (RFC5392). see ospf6d */ /* SUBTLV 23 (RFC5330) and 25 (RFC6001) are not yet supported */ /* SUBTLV 26 (RFC7308) is not yet supported */ /* RFC7471 */ /* Link Sub-TLV: Average Link Delay */ /* Optional */ #define TE_LINK_SUBTLV_AV_DELAY 27 struct te_link_subtlv_av_delay { struct tlv_header header; /* Value length is 4 bytes. */ /* * delay in micro-seconds only 24 bits => 0 ... 16777215 * with Anomalous Bit as Upper most bit */ uint32_t value; }; /* Link Sub-TLV: Low/High Link Delay */ #define TE_LINK_SUBTLV_MM_DELAY 28 #define TE_LINK_SUBTLV_MM_DELAY_SIZE 8 struct te_link_subtlv_mm_delay { struct tlv_header header; /* Value length is 8 bytes. */ /* * low delay in micro-seconds only 24 bits => 0 ... 16777215 * with Anomalous Bit (A) as Upper most bit */ uint32_t low; /* high delay in micro-seconds only 24 bits => 0 ... 16777215 */ uint32_t high; }; /* Link Sub-TLV: Link Delay Variation i.e. Jitter */ #define TE_LINK_SUBTLV_DELAY_VAR 29 struct te_link_subtlv_delay_var { struct tlv_header header; /* Value length is 4 bytes. */ /* interval in micro-seconds only 24 bits => 0 ... 16777215 */ uint32_t value; }; /* Link Sub-TLV: Routine Unidirectional Link Packet Loss */ #define TE_LINK_SUBTLV_PKT_LOSS 30 struct te_link_subtlv_pkt_loss { struct tlv_header header; /* Value length is 4 bytes. */ /* * in percentage of total traffic only 24 bits (2^24 - 2) * with Anomalous Bit as Upper most bit */ uint32_t value; }; /* Link Sub-TLV: Unidirectional Residual Bandwidth */ /* Optional */ #define TE_LINK_SUBTLV_RES_BW 31 struct te_link_subtlv_res_bw { struct tlv_header header; /* Value length is 4 bytes. */ /* bandwidth in IEEE floating point format with units in bytes/second */ float value; }; /* Link Sub-TLV: Unidirectional Available Bandwidth */ /* Optional */ #define TE_LINK_SUBTLV_AVA_BW 32 struct te_link_subtlv_ava_bw { struct tlv_header header; /* Value length is 4 octets. */ /* bandwidth in IEEE floating point format with units in bytes/second */ float value; }; /* Link Sub-TLV: Unidirectional Utilized Bandwidth */ /* Optional */ #define TE_LINK_SUBTLV_USE_BW 33 struct te_link_subtlv_use_bw { struct tlv_header header; /* Value length is 4 octets. */ /* bandwidth in IEEE floating point format with units in bytes/second */ float value; }; #define TE_LINK_SUBTLV_MAX 34 /* Last SUBTLV + 1 */ /* Here are "non-official" architectural constants. */ #define MPLS_TE_MINIMUM_BANDWIDTH 1.0 /* Reasonable? *//* XXX */ /* Mode for Inter-AS Opaque-LSA */ enum inter_as_mode { Off, AS, Area }; struct te_link_subtlv { struct tlv_header header; union { uint32_t link_type; struct in_addr link_id; struct in_addr lclif; struct in_addr rmtif; uint32_t te_metric; float max_bw; float max_rsv_bw; float unrsv[8]; uint32_t rsc_clsclr; uint32_t llri[2]; uint32_t ras; struct in_addr rip; struct in_addr lrrid[2]; uint32_t av_delay; uint32_t mm_delay; uint32_t delay_var; uint32_t pkt_loss; float res_bw; float ava_bw; float use_bw; } value; }; /* Following structure are internal use only. */ struct ospf_mpls_te { /* Status of MPLS-TE: enable or disbale */ bool enabled; /* RFC5392 */ enum inter_as_mode inter_as; struct in_addr interas_areaid; /* List elements are zebra-interfaces (ifp), not ospf-interfaces (oi). */ struct list *iflist; /* Store Router-TLV in network byte order. */ struct te_tlv_router_addr router_addr; }; struct mpls_te_link { /* * According to MPLS-TE (draft) specification, 24-bit Opaque-ID field * is subdivided into 8-bit "unused" field and 16-bit "instance" field. * In this implementation, each Link-TLV has its own instance. */ uint32_t instance; /* Reference pointer to a Zebra-interface. */ struct interface *ifp; /* Area info in which this MPLS-TE link belongs to. */ struct ospf_area *area; /* Flags to manage this link parameters. */ uint32_t flags; /* Type of MPLS-TE link: RFC3630, RFC5392, RFC5392 emulated, RFC6827 */ uint8_t type; /* Store Link-TLV in network byte order. */ /* RFC3630 & RFC6827 / RFC 6827 */ struct te_tlv_link link_header; struct te_link_subtlv_link_type link_type; struct te_link_subtlv_link_id link_id; struct te_link_subtlv_lclif_ipaddr lclif_ipaddr; struct te_link_subtlv_rmtif_ipaddr rmtif_ipaddr; struct te_link_subtlv_te_metric te_metric; struct te_link_subtlv_max_bw max_bw; struct te_link_subtlv_max_rsv_bw max_rsv_bw; struct te_link_subtlv_unrsv_bw unrsv_bw; struct te_link_subtlv_rsc_clsclr rsc_clsclr; /* RFC4203 */ struct te_link_subtlv_llri llri; /* RFC5392 */ struct te_link_subtlv_ras ras; struct te_link_subtlv_rip rip; /* RFC6827 */ struct te_link_subtlv_lrrid lrrid; /* RFC7471 */ struct te_link_subtlv_av_delay av_delay; struct te_link_subtlv_mm_delay mm_delay; struct te_link_subtlv_delay_var delay_var; struct te_link_subtlv_pkt_loss pkt_loss; struct te_link_subtlv_res_bw res_bw; struct te_link_subtlv_ava_bw ava_bw; struct te_link_subtlv_use_bw use_bw; struct in_addr adv_router; struct in_addr id; }; /* Prototypes. */ extern int ospf_mpls_te_init(void); extern void ospf_mpls_te_term(void); extern void ospf_mpls_te_finish(void); extern struct ospf_mpls_te *get_ospf_mpls_te(void); extern void ospf_mpls_te_update_if(struct interface *); extern void ospf_mpls_te_lsa_schedule(struct mpls_te_link *, enum lsa_opcode); extern void set_linkparams_llri(struct mpls_te_link *, uint32_t, uint32_t); extern void set_linkparams_lrrid(struct mpls_te_link *, struct in_addr, struct in_addr); #endif /* _ZEBRA_OSPF_MPLS_TE_H */ frr-7.2.1/ospfd/ospf_vty.c0000644000000000000000000110646713610377563012363 00000000000000/* OSPF VTY interface. * Copyright (C) 2005 6WIND * Copyright (C) 2000 Toshiaki Takada * * This file is part of GNU Zebra. * * GNU Zebra is free software; you can redistribute it and/or modify it * under the terms of the GNU General Public License as published by the * Free Software Foundation; either version 2, or (at your option) any * later version. * * GNU Zebra 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; see the file COPYING; if not, write to the Free Software * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA */ #include #include #include "monotime.h" #include "memory.h" #include "thread.h" #include "prefix.h" #include "table.h" #include "vty.h" #include "command.h" #include "plist.h" #include "log.h" #include "zclient.h" #include #include "defaults.h" #include "ospfd/ospfd.h" #include "ospfd/ospf_asbr.h" #include "ospfd/ospf_lsa.h" #include "ospfd/ospf_lsdb.h" #include "ospfd/ospf_ism.h" #include "ospfd/ospf_interface.h" #include "ospfd/ospf_nsm.h" #include "ospfd/ospf_neighbor.h" #include "ospfd/ospf_flood.h" #include "ospfd/ospf_abr.h" #include "ospfd/ospf_spf.h" #include "ospfd/ospf_route.h" #include "ospfd/ospf_zebra.h" /*#include "ospfd/ospf_routemap.h" */ #include "ospfd/ospf_vty.h" #include "ospfd/ospf_dump.h" #include "ospfd/ospf_bfd.h" static const char *ospf_network_type_str[] = { "Null", "POINTOPOINT", "BROADCAST", "NBMA", "POINTOMULTIPOINT", "VIRTUALLINK", "LOOPBACK"}; /* Utility functions. */ int str2area_id(const char *str, struct in_addr *area_id, int *area_id_fmt) { char *ep; area_id->s_addr = htonl(strtoul(str, &ep, 10)); if (*ep && !inet_aton(str, area_id)) return -1; *area_id_fmt = *ep ? OSPF_AREA_ID_FMT_DOTTEDQUAD : OSPF_AREA_ID_FMT_DECIMAL; return 0; } static void area_id2str(char *buf, int length, struct in_addr *area_id, int area_id_fmt) { if (area_id_fmt == OSPF_AREA_ID_FMT_DOTTEDQUAD) inet_ntop(AF_INET, area_id, buf, length); else sprintf(buf, "%lu", (unsigned long)ntohl(area_id->s_addr)); } static int str2metric(const char *str, int *metric) { /* Sanity check. */ if (str == NULL) return 0; *metric = strtol(str, NULL, 10); if (*metric < 0 && *metric > 16777214) { /* vty_out (vty, "OSPF metric value is invalid\n"); */ return 0; } return 1; } static int str2metric_type(const char *str, int *metric_type) { /* Sanity check. */ if (str == NULL) return 0; if (strncmp(str, "1", 1) == 0) *metric_type = EXTERNAL_METRIC_TYPE_1; else if (strncmp(str, "2", 1) == 0) *metric_type = EXTERNAL_METRIC_TYPE_2; else return 0; return 1; } int ospf_oi_count(struct interface *ifp) { struct route_node *rn; int i = 0; for (rn = route_top(IF_OIFS(ifp)); rn; rn = route_next(rn)) if (rn->info) i++; return i; } #define OSPF_FIND_VRF_ARGS(argv, argc, idx_vrf, vrf_name, all_vrf) \ if (argv_find(argv, argc, "vrf", &idx_vrf)) { \ vrf_name = argv[idx_vrf + 1]->arg; \ all_vrf = strmatch(vrf_name, "all"); \ } static struct ospf *ospf_cmd_lookup_ospf(struct vty *vty, struct cmd_token *argv[], const int argc, uint32_t enable, unsigned short *instance) { struct ospf *ospf = NULL; int idx_vrf = 0, idx_inst = 0; const char *vrf_name = NULL; *instance = 0; if (argv_find(argv, argc, "(1-65535)", &idx_inst)) *instance = strtoul(argv[idx_inst]->arg, NULL, 10); if (argv_find(argv, argc, "vrf", &idx_vrf)) { vrf_name = argv[idx_vrf + 1]->arg; if (vrf_name == NULL || strmatch(vrf_name, VRF_DEFAULT_NAME)) vrf_name = NULL; if (enable) { /* Allocate VRF aware instance */ ospf = ospf_get(*instance, vrf_name); } else { ospf = ospf_lookup_by_inst_name(*instance, vrf_name); } } else { if (enable) { ospf = ospf_get(*instance, NULL); } else { ospf = ospf_lookup_instance(*instance); } } return ospf; } static void ospf_show_vrf_name(struct ospf *ospf, struct vty *vty, json_object *json, uint8_t use_vrf) { if (use_vrf) { if (json) { if (ospf->vrf_id == VRF_DEFAULT) json_object_string_add(json, "vrfName", "default"); else json_object_string_add(json, "vrfName", ospf->name); json_object_int_add(json, "vrfId", ospf->vrf_id); } else { if (ospf->vrf_id == VRF_DEFAULT) vty_out(vty, "VRF Name: %s\n", "default"); else if (ospf->name) vty_out(vty, "VRF Name: %s\n", ospf->name); } } } #ifndef VTYSH_EXTRACT_PL #include "ospfd/ospf_vty_clippy.c" #endif DEFUN_NOSH (router_ospf, router_ospf_cmd, "router ospf [{(1-65535)|vrf NAME}]", "Enable a routing process\n" "Start OSPF configuration\n" "Instance ID\n" VRF_CMD_HELP_STR) { struct ospf *ospf = NULL; int ret = CMD_SUCCESS; unsigned short instance = 0; struct vrf *vrf = NULL; struct route_node *rn; struct interface *ifp; ospf = ospf_cmd_lookup_ospf(vty, argv, argc, 1, &instance); if (!ospf) return CMD_WARNING_CONFIG_FAILED; /* The following logic to set the vty qobj index is in place to be able to ignore the commands which dont belong to this instance. */ if (ospf->instance != instance) { VTY_PUSH_CONTEXT_NULL(OSPF_NODE); ret = CMD_NOT_MY_INSTANCE; } else { if (ospf->vrf_id != VRF_UNKNOWN) ospf->oi_running = 1; if (IS_DEBUG_OSPF_EVENT) zlog_debug( "Config command 'router ospf %d' received, vrf %s id %u oi_running %u", instance, ospf->name ? ospf->name : "NIL", ospf->vrf_id, ospf->oi_running); VTY_PUSH_CONTEXT(OSPF_NODE, ospf); /* Activate 'ip ospf area x' configured interfaces for given * vrf. Activate area on vrf x aware interfaces. * vrf_enable callback calls router_id_update which * internally will call ospf_if_update to trigger * network_run_state */ vrf = vrf_lookup_by_id(ospf->vrf_id); FOR_ALL_INTERFACES (vrf, ifp) { struct ospf_if_params *params; params = IF_DEF_PARAMS(ifp); if (OSPF_IF_PARAM_CONFIGURED(params, if_area)) { for (rn = route_top(ospf->networks); rn; rn = route_next(rn)) { if (rn->info != NULL) { vty_out(vty, "Interface %s has area config but please remove all network commands first.\n", ifp->name); return ret; } } if (!ospf_interface_area_is_already_set(ospf, ifp)) { ospf_interface_area_set(ospf, ifp); ospf->if_ospf_cli_count++; } } } ospf_router_id_update(ospf); } return ret; } DEFUN (no_router_ospf, no_router_ospf_cmd, "no router ospf [{(1-65535)|vrf NAME}]", NO_STR "Enable a routing process\n" "Start OSPF configuration\n" "Instance ID\n" VRF_CMD_HELP_STR) { struct ospf *ospf; unsigned short instance = 0; ospf = ospf_cmd_lookup_ospf(vty, argv, argc, 0, &instance); if (ospf == NULL) { if (instance) return CMD_NOT_MY_INSTANCE; else return CMD_WARNING; } ospf_finish(ospf); return CMD_SUCCESS; } DEFPY (ospf_router_id, ospf_router_id_cmd, "ospf router-id A.B.C.D", "OSPF specific commands\n" "router-id for the OSPF process\n" "OSPF router-id in IP address format\n") { VTY_DECLVAR_INSTANCE_CONTEXT(ospf, ospf); struct listnode *node; struct ospf_area *area; ospf->router_id_static = router_id; for (ALL_LIST_ELEMENTS_RO(ospf->areas, node, area)) if (area->full_nbrs) { vty_out(vty, "For this router-id change to take effect," " save config and restart ospfd\n"); return CMD_SUCCESS; } ospf_router_id_update(ospf); return CMD_SUCCESS; } DEFUN_HIDDEN (ospf_router_id_old, ospf_router_id_old_cmd, "router-id A.B.C.D", "router-id for the OSPF process\n" "OSPF router-id in IP address format\n") { VTY_DECLVAR_INSTANCE_CONTEXT(ospf, ospf); int idx_ipv4 = 1; struct listnode *node; struct ospf_area *area; struct in_addr router_id; int ret; ret = inet_aton(argv[idx_ipv4]->arg, &router_id); if (!ret) { vty_out(vty, "Please specify Router ID by A.B.C.D\n"); return CMD_WARNING_CONFIG_FAILED; } ospf->router_id_static = router_id; for (ALL_LIST_ELEMENTS_RO(ospf->areas, node, area)) if (area->full_nbrs) { vty_out(vty, "For this router-id change to take effect," " save config and restart ospfd\n"); return CMD_SUCCESS; } ospf_router_id_update(ospf); return CMD_SUCCESS; } DEFPY (no_ospf_router_id, no_ospf_router_id_cmd, "no ospf router-id [A.B.C.D]", NO_STR "OSPF specific commands\n" "router-id for the OSPF process\n" "OSPF router-id in IP address format\n") { VTY_DECLVAR_INSTANCE_CONTEXT(ospf, ospf); struct listnode *node; struct ospf_area *area; if (router_id_str) { if (!IPV4_ADDR_SAME(&ospf->router_id_static, &router_id)) { vty_out(vty, "%% OSPF router-id doesn't match\n"); return CMD_WARNING_CONFIG_FAILED; } } ospf->router_id_static.s_addr = 0; for (ALL_LIST_ELEMENTS_RO(ospf->areas, node, area)) if (area->full_nbrs) { vty_out(vty, "For this router-id change to take effect," " save config and restart ospfd\n"); return CMD_SUCCESS; } ospf_router_id_update(ospf); return CMD_SUCCESS; } static void ospf_passive_interface_default(struct ospf *ospf, uint8_t newval) { struct vrf *vrf = vrf_lookup_by_id(ospf->vrf_id); struct listnode *ln; struct interface *ifp; struct ospf_interface *oi; ospf->passive_interface_default = newval; FOR_ALL_INTERFACES (vrf, ifp) { if (ifp && OSPF_IF_PARAM_CONFIGURED(IF_DEF_PARAMS(ifp), passive_interface)) UNSET_IF_PARAM(IF_DEF_PARAMS(ifp), passive_interface); } for (ALL_LIST_ELEMENTS_RO(ospf->oiflist, ln, oi)) { if (OSPF_IF_PARAM_CONFIGURED(oi->params, passive_interface)) UNSET_IF_PARAM(oi->params, passive_interface); /* update multicast memberships */ ospf_if_set_multicast(oi); } } static void ospf_passive_interface_update_addr(struct ospf *ospf, struct interface *ifp, struct ospf_if_params *params, uint8_t value, struct in_addr addr) { uint8_t dflt; params->passive_interface = value; if (params != IF_DEF_PARAMS(ifp)) { if (OSPF_IF_PARAM_CONFIGURED(IF_DEF_PARAMS(ifp), passive_interface)) dflt = IF_DEF_PARAMS(ifp)->passive_interface; else dflt = ospf->passive_interface_default; if (value != dflt) SET_IF_PARAM(params, passive_interface); else UNSET_IF_PARAM(params, passive_interface); ospf_free_if_params(ifp, addr); ospf_if_update_params(ifp, addr); } } static void ospf_passive_interface_update(struct ospf *ospf, struct interface *ifp, struct ospf_if_params *params, uint8_t value) { params->passive_interface = value; if (params == IF_DEF_PARAMS(ifp)) { if (value != ospf->passive_interface_default) SET_IF_PARAM(params, passive_interface); else UNSET_IF_PARAM(params, passive_interface); } } DEFUN (ospf_passive_interface, ospf_passive_interface_addr_cmd, "passive-interface ", "Suppress routing updates on an interface\n" "Interface's name\n" "IPv4 address\n" "Suppress routing updates on interfaces by default\n") { VTY_DECLVAR_INSTANCE_CONTEXT(ospf, ospf); int idx_ipv4 = 2; struct interface *ifp = NULL; struct in_addr addr = {.s_addr = INADDR_ANY}; int ret; struct ospf_if_params *params; struct route_node *rn; if (strmatch(argv[1]->text, "default")) { ospf_passive_interface_default(ospf, OSPF_IF_PASSIVE); return CMD_SUCCESS; } if (ospf->vrf_id != VRF_UNKNOWN) ifp = if_get_by_name(argv[1]->arg, ospf->vrf_id); if (ifp == NULL) { vty_out(vty, "interface %s not found.\n", (char *)argv[1]->arg); return CMD_WARNING_CONFIG_FAILED; } params = IF_DEF_PARAMS(ifp); if (argc == 3) { ret = inet_aton(argv[idx_ipv4]->arg, &addr); if (!ret) { vty_out(vty, "Please specify interface address by A.B.C.D\n"); return CMD_WARNING_CONFIG_FAILED; } params = ospf_get_if_params(ifp, addr); ospf_if_update_params(ifp, addr); ospf_passive_interface_update_addr(ospf, ifp, params, OSPF_IF_PASSIVE, addr); } ospf_passive_interface_update(ospf, ifp, params, OSPF_IF_PASSIVE); /* XXX We should call ospf_if_set_multicast on exactly those * interfaces for which the passive property changed. It is too much * work to determine this set, so we do this for every interface. * This is safe and reasonable because ospf_if_set_multicast uses a * record of joined groups to avoid systems calls if the desired * memberships match the current memership. */ for (rn = route_top(IF_OIFS(ifp)); rn; rn = route_next(rn)) { struct ospf_interface *oi = rn->info; if (oi && (OSPF_IF_PARAM(oi, passive_interface) == OSPF_IF_PASSIVE)) ospf_if_set_multicast(oi); } /* * XXX It is not clear what state transitions the interface needs to * undergo when going from active to passive. Fixing this will * require precise identification of interfaces having such a * transition. */ return CMD_SUCCESS; } DEFUN (no_ospf_passive_interface, no_ospf_passive_interface_addr_cmd, "no passive-interface ", NO_STR "Allow routing updates on an interface\n" "Interface's name\n" "IPv4 address\n" "Allow routing updates on interfaces by default\n") { VTY_DECLVAR_INSTANCE_CONTEXT(ospf, ospf); int idx_ipv4 = 3; struct interface *ifp = NULL; struct in_addr addr = {.s_addr = INADDR_ANY}; struct ospf_if_params *params; int ret; struct route_node *rn; if (strmatch(argv[2]->text, "default")) { ospf_passive_interface_default(ospf, OSPF_IF_ACTIVE); return CMD_SUCCESS; } if (ospf->vrf_id != VRF_UNKNOWN) ifp = if_get_by_name(argv[2]->arg, ospf->vrf_id); if (ifp == NULL) { vty_out(vty, "interface %s not found.\n", (char *)argv[2]->arg); return CMD_WARNING_CONFIG_FAILED; } params = IF_DEF_PARAMS(ifp); if (argc == 4) { ret = inet_aton(argv[idx_ipv4]->arg, &addr); if (!ret) { vty_out(vty, "Please specify interface address by A.B.C.D\n"); return CMD_WARNING_CONFIG_FAILED; } params = ospf_lookup_if_params(ifp, addr); if (params == NULL) return CMD_SUCCESS; ospf_passive_interface_update_addr(ospf, ifp, params, OSPF_IF_ACTIVE, addr); } ospf_passive_interface_update(ospf, ifp, params, OSPF_IF_ACTIVE); /* XXX We should call ospf_if_set_multicast on exactly those * interfaces for which the passive property changed. It is too much * work to determine this set, so we do this for every interface. * This is safe and reasonable because ospf_if_set_multicast uses a * record of joined groups to avoid systems calls if the desired * memberships match the current memership. */ for (rn = route_top(IF_OIFS(ifp)); rn; rn = route_next(rn)) { struct ospf_interface *oi = rn->info; if (oi && (OSPF_IF_PARAM(oi, passive_interface) == OSPF_IF_ACTIVE)) ospf_if_set_multicast(oi); } return CMD_SUCCESS; } DEFUN (ospf_network_area, ospf_network_area_cmd, "network A.B.C.D/M area ", "Enable routing on an IP network\n" "OSPF network prefix\n" "Set the OSPF area ID\n" "OSPF area ID in IP address format\n" "OSPF area ID as a decimal value\n") { VTY_DECLVAR_INSTANCE_CONTEXT(ospf, ospf); int idx_ipv4_prefixlen = 1; int idx_ipv4_number = 3; struct prefix_ipv4 p; struct in_addr area_id; int ret, format; if (ospf->instance) { vty_out(vty, "The network command is not supported in multi-instance ospf\n"); return CMD_WARNING_CONFIG_FAILED; } if (ospf->if_ospf_cli_count > 0) { vty_out(vty, "Please remove all ip ospf area x.x.x.x commands first.\n"); if (IS_DEBUG_OSPF_EVENT) zlog_debug( "%s ospf vrf %s num of %u ip osp area x config", __PRETTY_FUNCTION__, ospf->name ? ospf->name : "NIL", ospf->if_ospf_cli_count); return CMD_WARNING_CONFIG_FAILED; } /* Get network prefix and Area ID. */ str2prefix_ipv4(argv[idx_ipv4_prefixlen]->arg, &p); VTY_GET_OSPF_AREA_ID(area_id, format, argv[idx_ipv4_number]->arg); ret = ospf_network_set(ospf, &p, area_id, format); if (ret == 0) { vty_out(vty, "There is already same network statement.\n"); return CMD_WARNING_CONFIG_FAILED; } return CMD_SUCCESS; } DEFUN (no_ospf_network_area, no_ospf_network_area_cmd, "no network A.B.C.D/M area ", NO_STR "Enable routing on an IP network\n" "OSPF network prefix\n" "Set the OSPF area ID\n" "OSPF area ID in IP address format\n" "OSPF area ID as a decimal value\n") { VTY_DECLVAR_INSTANCE_CONTEXT(ospf, ospf); int idx_ipv4_prefixlen = 2; int idx_ipv4_number = 4; struct prefix_ipv4 p; struct in_addr area_id; int ret, format; if (ospf->instance) { vty_out(vty, "The network command is not supported in multi-instance ospf\n"); return CMD_WARNING_CONFIG_FAILED; } /* Get network prefix and Area ID. */ str2prefix_ipv4(argv[idx_ipv4_prefixlen]->arg, &p); VTY_GET_OSPF_AREA_ID(area_id, format, argv[idx_ipv4_number]->arg); ret = ospf_network_unset(ospf, &p, area_id); if (ret == 0) { vty_out(vty, "Can't find specified network area configuration.\n"); return CMD_WARNING_CONFIG_FAILED; } return CMD_SUCCESS; } DEFUN (ospf_area_range, ospf_area_range_cmd, "area range A.B.C.D/M [advertise [cost (0-16777215)]]", "OSPF area parameters\n" "OSPF area ID in IP address format\n" "OSPF area ID as a decimal value\n" "Summarize routes matching address/mask (border routers only)\n" "Area range prefix\n" "Advertise this range (default)\n" "User specified metric for this range\n" "Advertised metric for this range\n") { VTY_DECLVAR_INSTANCE_CONTEXT(ospf, ospf); int idx_ipv4_number = 1; int idx_ipv4_prefixlen = 3; int idx_cost = 6; struct prefix_ipv4 p; struct in_addr area_id; int format; uint32_t cost; VTY_GET_OSPF_AREA_ID(area_id, format, argv[idx_ipv4_number]->arg); str2prefix_ipv4(argv[idx_ipv4_prefixlen]->arg, &p); ospf_area_range_set(ospf, area_id, &p, OSPF_AREA_RANGE_ADVERTISE); if (argc > 5) { cost = strtoul(argv[idx_cost]->arg, NULL, 10); ospf_area_range_cost_set(ospf, area_id, &p, cost); } return CMD_SUCCESS; } DEFUN (ospf_area_range_cost, ospf_area_range_cost_cmd, "area range A.B.C.D/M cost (0-16777215)", "OSPF area parameters\n" "OSPF area ID in IP address format\n" "OSPF area ID as a decimal value\n" "Summarize routes matching address/mask (border routers only)\n" "Area range prefix\n" "User specified metric for this range\n" "Advertised metric for this range\n") { VTY_DECLVAR_INSTANCE_CONTEXT(ospf, ospf); int idx_ipv4_number = 1; int idx_ipv4_prefixlen = 3; int idx_cost = 5; struct prefix_ipv4 p; struct in_addr area_id; int format; uint32_t cost; VTY_GET_OSPF_AREA_ID(area_id, format, argv[idx_ipv4_number]->arg); str2prefix_ipv4(argv[idx_ipv4_prefixlen]->arg, &p); ospf_area_range_set(ospf, area_id, &p, OSPF_AREA_RANGE_ADVERTISE); ospf_area_display_format_set(ospf, ospf_area_get(ospf, area_id), format); cost = strtoul(argv[idx_cost]->arg, NULL, 10); ospf_area_range_cost_set(ospf, area_id, &p, cost); return CMD_SUCCESS; } DEFUN (ospf_area_range_not_advertise, ospf_area_range_not_advertise_cmd, "area range A.B.C.D/M not-advertise", "OSPF area parameters\n" "OSPF area ID in IP address format\n" "OSPF area ID as a decimal value\n" "Summarize routes matching address/mask (border routers only)\n" "Area range prefix\n" "DoNotAdvertise this range\n") { VTY_DECLVAR_INSTANCE_CONTEXT(ospf, ospf); int idx_ipv4_number = 1; int idx_ipv4_prefixlen = 3; struct prefix_ipv4 p; struct in_addr area_id; int format; VTY_GET_OSPF_AREA_ID(area_id, format, argv[idx_ipv4_number]->arg); str2prefix_ipv4(argv[idx_ipv4_prefixlen]->arg, &p); ospf_area_range_set(ospf, area_id, &p, 0); ospf_area_display_format_set(ospf, ospf_area_get(ospf, area_id), format); ospf_area_range_substitute_unset(ospf, area_id, &p); return CMD_SUCCESS; } DEFUN (no_ospf_area_range, no_ospf_area_range_cmd, "no area range A.B.C.D/M []", NO_STR "OSPF area parameters\n" "OSPF area ID in IP address format\n" "OSPF area ID as a decimal value\n" "Summarize routes matching address/mask (border routers only)\n" "Area range prefix\n" "User specified metric for this range\n" "Advertised metric for this range\n" "Advertise this range (default)\n" "User specified metric for this range\n" "Advertised metric for this range\n" "DoNotAdvertise this range\n") { VTY_DECLVAR_INSTANCE_CONTEXT(ospf, ospf); int idx_ipv4_number = 2; int idx_ipv4_prefixlen = 4; struct prefix_ipv4 p; struct in_addr area_id; int format; VTY_GET_OSPF_AREA_ID(area_id, format, argv[idx_ipv4_number]->arg); str2prefix_ipv4(argv[idx_ipv4_prefixlen]->arg, &p); ospf_area_range_unset(ospf, area_id, &p); return CMD_SUCCESS; } DEFUN (ospf_area_range_substitute, ospf_area_range_substitute_cmd, "area range A.B.C.D/M substitute A.B.C.D/M", "OSPF area parameters\n" "OSPF area ID in IP address format\n" "OSPF area ID as a decimal value\n" "Summarize routes matching address/mask (border routers only)\n" "Area range prefix\n" "Announce area range as another prefix\n" "Network prefix to be announced instead of range\n") { VTY_DECLVAR_INSTANCE_CONTEXT(ospf, ospf); int idx_ipv4_number = 1; int idx_ipv4_prefixlen = 3; int idx_ipv4_prefixlen_2 = 5; struct prefix_ipv4 p, s; struct in_addr area_id; int format; VTY_GET_OSPF_AREA_ID(area_id, format, argv[idx_ipv4_number]->arg); str2prefix_ipv4(argv[idx_ipv4_prefixlen]->arg, &p); str2prefix_ipv4(argv[idx_ipv4_prefixlen_2]->arg, &s); ospf_area_range_substitute_set(ospf, area_id, &p, &s); ospf_area_display_format_set(ospf, ospf_area_get(ospf, area_id), format); return CMD_SUCCESS; } DEFUN (no_ospf_area_range_substitute, no_ospf_area_range_substitute_cmd, "no area range A.B.C.D/M substitute A.B.C.D/M", NO_STR "OSPF area parameters\n" "OSPF area ID in IP address format\n" "OSPF area ID as a decimal value\n" "Summarize routes matching address/mask (border routers only)\n" "Area range prefix\n" "Announce area range as another prefix\n" "Network prefix to be announced instead of range\n") { VTY_DECLVAR_INSTANCE_CONTEXT(ospf, ospf); int idx_ipv4_number = 2; int idx_ipv4_prefixlen = 4; int idx_ipv4_prefixlen_2 = 6; struct prefix_ipv4 p, s; struct in_addr area_id; int format; VTY_GET_OSPF_AREA_ID(area_id, format, argv[idx_ipv4_number]->arg); str2prefix_ipv4(argv[idx_ipv4_prefixlen]->arg, &p); str2prefix_ipv4(argv[idx_ipv4_prefixlen_2]->arg, &s); ospf_area_range_substitute_unset(ospf, area_id, &p); return CMD_SUCCESS; } /* Command Handler Logic in VLink stuff is delicate!! ALTER AT YOUR OWN RISK!!!! Various dummy values are used to represent 'NoChange' state for VLink configuration NOT being changed by a VLink command, and special syntax is used within the command strings so that the typed in command verbs can be seen in the configuration command bacckend handler. This is to drastically reduce the verbeage required to coe up with a reasonably compatible Cisco VLink command - Matthew Grant Wed, 21 Feb 2001 15:13:52 +1300 */ /* Configuration data for virtual links */ struct ospf_vl_config_data { struct vty *vty; /* vty stuff */ struct in_addr area_id; /* area ID from command line */ int area_id_fmt; /* command line area ID format */ struct in_addr vl_peer; /* command line vl_peer */ int auth_type; /* Authehntication type, if given */ char *auth_key; /* simple password if present */ int crypto_key_id; /* Cryptographic key ID */ char *md5_key; /* MD5 authentication key */ int hello_interval; /* Obvious what these are... */ int retransmit_interval; int transmit_delay; int dead_interval; }; static void ospf_vl_config_data_init(struct ospf_vl_config_data *vl_config, struct vty *vty) { memset(vl_config, 0, sizeof(struct ospf_vl_config_data)); vl_config->auth_type = OSPF_AUTH_CMD_NOTSEEN; vl_config->vty = vty; } static struct ospf_vl_data * ospf_find_vl_data(struct ospf *ospf, struct ospf_vl_config_data *vl_config) { struct ospf_area *area; struct ospf_vl_data *vl_data; struct vty *vty; struct in_addr area_id; vty = vl_config->vty; area_id = vl_config->area_id; if (area_id.s_addr == OSPF_AREA_BACKBONE) { vty_out(vty, "Configuring VLs over the backbone is not allowed\n"); return NULL; } area = ospf_area_get(ospf, area_id); ospf_area_display_format_set(ospf, area, vl_config->area_id_fmt); if (area->external_routing != OSPF_AREA_DEFAULT) { if (vl_config->area_id_fmt == OSPF_AREA_ID_FMT_DOTTEDQUAD) vty_out(vty, "Area %s is %s\n", inet_ntoa(area_id), area->external_routing == OSPF_AREA_NSSA ? "nssa" : "stub"); else vty_out(vty, "Area %ld is %s\n", (unsigned long)ntohl(area_id.s_addr), area->external_routing == OSPF_AREA_NSSA ? "nssa" : "stub"); return NULL; } if ((vl_data = ospf_vl_lookup(ospf, area, vl_config->vl_peer)) == NULL) { vl_data = ospf_vl_data_new(area, vl_config->vl_peer); if (vl_data->vl_oi == NULL) { vl_data->vl_oi = ospf_vl_new(ospf, vl_data); ospf_vl_add(ospf, vl_data); ospf_spf_calculate_schedule(ospf, SPF_FLAG_CONFIG_CHANGE); } } return vl_data; } static int ospf_vl_set_security(struct ospf_vl_data *vl_data, struct ospf_vl_config_data *vl_config) { struct crypt_key *ck; struct vty *vty; struct interface *ifp = vl_data->vl_oi->ifp; vty = vl_config->vty; if (vl_config->auth_type != OSPF_AUTH_CMD_NOTSEEN) { SET_IF_PARAM(IF_DEF_PARAMS(ifp), auth_type); IF_DEF_PARAMS(ifp)->auth_type = vl_config->auth_type; } if (vl_config->auth_key) { memset(IF_DEF_PARAMS(ifp)->auth_simple, 0, OSPF_AUTH_SIMPLE_SIZE + 1); strlcpy((char *)IF_DEF_PARAMS(ifp)->auth_simple, vl_config->auth_key, sizeof(IF_DEF_PARAMS(ifp)->auth_simple)); } else if (vl_config->md5_key) { if (ospf_crypt_key_lookup(IF_DEF_PARAMS(ifp)->auth_crypt, vl_config->crypto_key_id) != NULL) { vty_out(vty, "OSPF: Key %d already exists\n", vl_config->crypto_key_id); return CMD_WARNING; } ck = ospf_crypt_key_new(); ck->key_id = vl_config->crypto_key_id; memset(ck->auth_key, 0, OSPF_AUTH_MD5_SIZE + 1); strlcpy((char *)ck->auth_key, vl_config->md5_key, sizeof(ck->auth_key)); ospf_crypt_key_add(IF_DEF_PARAMS(ifp)->auth_crypt, ck); } else if (vl_config->crypto_key_id != 0) { /* Delete a key */ if (ospf_crypt_key_lookup(IF_DEF_PARAMS(ifp)->auth_crypt, vl_config->crypto_key_id) == NULL) { vty_out(vty, "OSPF: Key %d does not exist\n", vl_config->crypto_key_id); return CMD_WARNING_CONFIG_FAILED; } ospf_crypt_key_delete(IF_DEF_PARAMS(ifp)->auth_crypt, vl_config->crypto_key_id); } return CMD_SUCCESS; } static int ospf_vl_set_timers(struct ospf_vl_data *vl_data, struct ospf_vl_config_data *vl_config) { struct interface *ifp = vl_data->vl_oi->ifp; /* Virtual Link data initialised to defaults, so only set if a value given */ if (vl_config->hello_interval) { SET_IF_PARAM(IF_DEF_PARAMS(ifp), v_hello); IF_DEF_PARAMS(ifp)->v_hello = vl_config->hello_interval; } if (vl_config->dead_interval) { SET_IF_PARAM(IF_DEF_PARAMS(ifp), v_wait); IF_DEF_PARAMS(ifp)->v_wait = vl_config->dead_interval; } if (vl_config->retransmit_interval) { SET_IF_PARAM(IF_DEF_PARAMS(ifp), retransmit_interval); IF_DEF_PARAMS(ifp)->retransmit_interval = vl_config->retransmit_interval; } if (vl_config->transmit_delay) { SET_IF_PARAM(IF_DEF_PARAMS(ifp), transmit_delay); IF_DEF_PARAMS(ifp)->transmit_delay = vl_config->transmit_delay; } return CMD_SUCCESS; } /* The business end of all of the above */ static int ospf_vl_set(struct ospf *ospf, struct ospf_vl_config_data *vl_config) { struct ospf_vl_data *vl_data; int ret; vl_data = ospf_find_vl_data(ospf, vl_config); if (!vl_data) return CMD_WARNING_CONFIG_FAILED; /* Process this one first as it can have a fatal result, which can only logically occur if the virtual link exists already Thus a command error does not result in a change to the running configuration such as unexpectedly altered timer values etc.*/ ret = ospf_vl_set_security(vl_data, vl_config); if (ret != CMD_SUCCESS) return ret; /* Set any time based parameters, these area already range checked */ ret = ospf_vl_set_timers(vl_data, vl_config); if (ret != CMD_SUCCESS) return ret; return CMD_SUCCESS; } /* This stuff exists to make specifying all the alias commands A LOT simpler */ #define VLINK_HELPSTR_IPADDR \ "OSPF area parameters\n" \ "OSPF area ID in IP address format\n" \ "OSPF area ID as a decimal value\n" \ "Configure a virtual link\n" \ "Router ID of the remote ABR\n" #define VLINK_HELPSTR_AUTHTYPE_SIMPLE \ "Enable authentication on this virtual link\n" \ "dummy string \n" #define VLINK_HELPSTR_AUTHTYPE_ALL \ VLINK_HELPSTR_AUTHTYPE_SIMPLE \ "Use null authentication\n" \ "Use message-digest authentication\n" #define VLINK_HELPSTR_TIME_PARAM \ "Time between HELLO packets\n" \ "Seconds\n" \ "Time between retransmitting lost link state advertisements\n" \ "Seconds\n" \ "Link state transmit delay\n" \ "Seconds\n" \ "Interval time after which a neighbor is declared down\n" \ "Seconds\n" #define VLINK_HELPSTR_AUTH_SIMPLE \ "Authentication password (key)\n" \ "The OSPF password (key)\n" #define VLINK_HELPSTR_AUTH_MD5 \ "Message digest authentication password (key)\n" \ "Key ID\n" \ "Use MD5 algorithm\n" \ "The OSPF password (key)\n" DEFUN (ospf_area_vlink, ospf_area_vlink_cmd, "area virtual-link A.B.C.D [authentication []] []", VLINK_HELPSTR_IPADDR "Enable authentication on this virtual link\n" "Use message-digest authentication\n" "Use null authentication\n" VLINK_HELPSTR_AUTH_MD5 VLINK_HELPSTR_AUTH_SIMPLE) { VTY_DECLVAR_INSTANCE_CONTEXT(ospf, ospf); int idx_ipv4_number = 1; int idx_ipv4 = 3; struct ospf_vl_config_data vl_config; char auth_key[OSPF_AUTH_SIMPLE_SIZE + 1]; char md5_key[OSPF_AUTH_MD5_SIZE + 1]; int ret; int idx = 0; ospf_vl_config_data_init(&vl_config, vty); /* Read off first 2 parameters and check them */ ret = str2area_id(argv[idx_ipv4_number]->arg, &vl_config.area_id, &vl_config.area_id_fmt); if (ret < 0) { vty_out(vty, "OSPF area ID is invalid\n"); return CMD_WARNING_CONFIG_FAILED; } ret = inet_aton(argv[idx_ipv4]->arg, &vl_config.vl_peer); if (!ret) { vty_out(vty, "Please specify valid Router ID as a.b.c.d\n"); return CMD_WARNING_CONFIG_FAILED; } if (argc <= 4) { /* Thats all folks! - BUGS B. strikes again!!!*/ return ospf_vl_set(ospf, &vl_config); } if (argv_find(argv, argc, "authentication", &idx)) { /* authentication - this option can only occur at start of command line */ vl_config.auth_type = OSPF_AUTH_SIMPLE; } if (argv_find(argv, argc, "message-digest", &idx)) { /* authentication message-digest */ vl_config.auth_type = OSPF_AUTH_CRYPTOGRAPHIC; } else if (argv_find(argv, argc, "null", &idx)) { /* "authentication null" */ vl_config.auth_type = OSPF_AUTH_NULL; } if (argv_find(argv, argc, "message-digest-key", &idx)) { vl_config.md5_key = NULL; vl_config.crypto_key_id = strtol(argv[idx + 1]->arg, NULL, 10); if (vl_config.crypto_key_id < 0) return CMD_WARNING_CONFIG_FAILED; strlcpy(md5_key, argv[idx + 3]->arg, sizeof(md5_key)); vl_config.md5_key = md5_key; } if (argv_find(argv, argc, "authentication-key", &idx)) { strlcpy(auth_key, argv[idx + 1]->arg, sizeof(auth_key)); vl_config.auth_key = auth_key; } /* Action configuration */ return ospf_vl_set(ospf, &vl_config); } DEFUN (no_ospf_area_vlink, no_ospf_area_vlink_cmd, "no area virtual-link A.B.C.D [authentication []] []", NO_STR VLINK_HELPSTR_IPADDR "Enable authentication on this virtual link\n" \ "Use message-digest authentication\n" \ "Use null authentication\n" \ VLINK_HELPSTR_AUTH_MD5 VLINK_HELPSTR_AUTH_SIMPLE) { VTY_DECLVAR_INSTANCE_CONTEXT(ospf, ospf); int idx_ipv4_number = 2; int idx_ipv4 = 4; struct ospf_area *area; struct ospf_vl_config_data vl_config; struct ospf_vl_data *vl_data = NULL; char auth_key[OSPF_AUTH_SIMPLE_SIZE + 1]; int idx = 0; int ret, format; ospf_vl_config_data_init(&vl_config, vty); ret = str2area_id(argv[idx_ipv4_number]->arg, &vl_config.area_id, &format); if (ret < 0) { vty_out(vty, "OSPF area ID is invalid\n"); return CMD_WARNING_CONFIG_FAILED; } area = ospf_area_lookup_by_area_id(ospf, vl_config.area_id); if (!area) { vty_out(vty, "Area does not exist\n"); return CMD_WARNING_CONFIG_FAILED; } ret = inet_aton(argv[idx_ipv4]->arg, &vl_config.vl_peer); if (!ret) { vty_out(vty, "Please specify valid Router ID as a.b.c.d\n"); return CMD_WARNING_CONFIG_FAILED; } vl_data = ospf_vl_lookup(ospf, area, vl_config.vl_peer); if (!vl_data) { vty_out(vty, "Virtual link does not exist\n"); return CMD_WARNING_CONFIG_FAILED; } if (argc <= 5) { /* Basic VLink no command */ /* Thats all folks! - BUGS B. strikes again!!!*/ ospf_vl_delete(ospf, vl_data); ospf_area_check_free(ospf, vl_config.area_id); return CMD_SUCCESS; } /* If we are down here, we are reseting parameters */ /* Deal with other parameters */ if (argv_find(argv, argc, "authentication", &idx)) { /* authentication - this option can only occur at start of command line */ vl_config.auth_type = OSPF_AUTH_NOTSET; } if (argv_find(argv, argc, "message-digest-key", &idx)) { vl_config.md5_key = NULL; vl_config.crypto_key_id = strtol(argv[idx + 1]->arg, NULL, 10); if (vl_config.crypto_key_id < 0) return CMD_WARNING_CONFIG_FAILED; } if (argv_find(argv, argc, "authentication-key", &idx)) { /* Reset authentication-key to 0 */ memset(auth_key, 0, OSPF_AUTH_SIMPLE_SIZE + 1); vl_config.auth_key = auth_key; } /* Action configuration */ return ospf_vl_set(ospf, &vl_config); } DEFUN (ospf_area_vlink_intervals, ospf_area_vlink_intervals_cmd, "area virtual-link A.B.C.D {hello-interval (1-65535)|retransmit-interval (1-65535)|transmit-delay (1-65535)|dead-interval (1-65535)}", VLINK_HELPSTR_IPADDR VLINK_HELPSTR_TIME_PARAM) { VTY_DECLVAR_INSTANCE_CONTEXT(ospf, ospf); struct ospf_vl_config_data vl_config; int ret = 0; ospf_vl_config_data_init(&vl_config, vty); char *area_id = argv[1]->arg; char *router_id = argv[3]->arg; ret = str2area_id(area_id, &vl_config.area_id, &vl_config.area_id_fmt); if (ret < 0) { vty_out(vty, "OSPF area ID is invalid\n"); return CMD_WARNING_CONFIG_FAILED; } ret = inet_aton(router_id, &vl_config.vl_peer); if (!ret) { vty_out(vty, "Please specify valid Router ID as a.b.c.d\n"); return CMD_WARNING_CONFIG_FAILED; } for (int idx = 4; idx < argc; idx++) { if (strmatch(argv[idx]->text, "hello-interval")) vl_config.hello_interval = strtol(argv[++idx]->arg, NULL, 10); else if (strmatch(argv[idx]->text, "retransmit-interval")) vl_config.retransmit_interval = strtol(argv[++idx]->arg, NULL, 10); else if (strmatch(argv[idx]->text, "transmit-delay")) vl_config.transmit_delay = strtol(argv[++idx]->arg, NULL, 10); else if (strmatch(argv[idx]->text, "dead-interval")) vl_config.dead_interval = strtol(argv[++idx]->arg, NULL, 10); } /* Action configuration */ return ospf_vl_set(ospf, &vl_config); } DEFUN (no_ospf_area_vlink_intervals, no_ospf_area_vlink_intervals_cmd, "no area virtual-link A.B.C.D {hello-interval (1-65535)|retransmit-interval (1-65535)|transmit-delay (1-65535)|dead-interval (1-65535)}", NO_STR VLINK_HELPSTR_IPADDR VLINK_HELPSTR_TIME_PARAM) { VTY_DECLVAR_INSTANCE_CONTEXT(ospf, ospf); struct ospf_vl_config_data vl_config; int ret = 0; ospf_vl_config_data_init(&vl_config, vty); char *area_id = argv[2]->arg; char *router_id = argv[4]->arg; ret = str2area_id(area_id, &vl_config.area_id, &vl_config.area_id_fmt); if (ret < 0) { vty_out(vty, "OSPF area ID is invalid\n"); return CMD_WARNING_CONFIG_FAILED; } ret = inet_aton(router_id, &vl_config.vl_peer); if (!ret) { vty_out(vty, "Please specify valid Router ID as a.b.c.d\n"); return CMD_WARNING_CONFIG_FAILED; } for (int idx = 5; idx < argc; idx++) { if (strmatch(argv[idx]->text, "hello-interval")) vl_config.hello_interval = OSPF_HELLO_INTERVAL_DEFAULT; else if (strmatch(argv[idx]->text, "retransmit-interval")) vl_config.retransmit_interval = OSPF_RETRANSMIT_INTERVAL_DEFAULT; else if (strmatch(argv[idx]->text, "transmit-delay")) vl_config.transmit_delay = OSPF_TRANSMIT_DELAY_DEFAULT; else if (strmatch(argv[idx]->text, "dead-interval")) vl_config.dead_interval = OSPF_ROUTER_DEAD_INTERVAL_DEFAULT; } /* Action configuration */ return ospf_vl_set(ospf, &vl_config); } DEFUN (ospf_area_shortcut, ospf_area_shortcut_cmd, "area shortcut ", "OSPF area parameters\n" "OSPF area ID in IP address format\n" "OSPF area ID as a decimal value\n" "Configure the area's shortcutting mode\n" "Set default shortcutting behavior\n" "Enable shortcutting through the area\n" "Disable shortcutting through the area\n") { VTY_DECLVAR_INSTANCE_CONTEXT(ospf, ospf); int idx_ipv4_number = 1; int idx_enable_disable = 3; struct ospf_area *area; struct in_addr area_id; int mode; int format; VTY_GET_OSPF_AREA_ID_NO_BB("shortcut", area_id, format, argv[idx_ipv4_number]->arg); area = ospf_area_get(ospf, area_id); ospf_area_display_format_set(ospf, area, format); if (strncmp(argv[idx_enable_disable]->arg, "de", 2) == 0) mode = OSPF_SHORTCUT_DEFAULT; else if (strncmp(argv[idx_enable_disable]->arg, "di", 2) == 0) mode = OSPF_SHORTCUT_DISABLE; else if (strncmp(argv[idx_enable_disable]->arg, "e", 1) == 0) mode = OSPF_SHORTCUT_ENABLE; else return CMD_WARNING_CONFIG_FAILED; ospf_area_shortcut_set(ospf, area, mode); if (ospf->abr_type != OSPF_ABR_SHORTCUT) vty_out(vty, "Shortcut area setting will take effect " "only when the router is configured as Shortcut ABR\n"); return CMD_SUCCESS; } DEFUN (no_ospf_area_shortcut, no_ospf_area_shortcut_cmd, "no area shortcut ", NO_STR "OSPF area parameters\n" "OSPF area ID in IP address format\n" "OSPF area ID as a decimal value\n" "Deconfigure the area's shortcutting mode\n" "Deconfigure enabled shortcutting through the area\n" "Deconfigure disabled shortcutting through the area\n") { VTY_DECLVAR_INSTANCE_CONTEXT(ospf, ospf); int idx_ipv4_number = 2; struct ospf_area *area; struct in_addr area_id; int format; VTY_GET_OSPF_AREA_ID_NO_BB("shortcut", area_id, format, argv[idx_ipv4_number]->arg); area = ospf_area_lookup_by_area_id(ospf, area_id); if (!area) return CMD_SUCCESS; ospf_area_shortcut_unset(ospf, area); return CMD_SUCCESS; } DEFUN (ospf_area_stub, ospf_area_stub_cmd, "area stub", "OSPF area parameters\n" "OSPF area ID in IP address format\n" "OSPF area ID as a decimal value\n" "Configure OSPF area as stub\n") { VTY_DECLVAR_INSTANCE_CONTEXT(ospf, ospf); int idx_ipv4_number = 1; struct in_addr area_id; int ret, format; VTY_GET_OSPF_AREA_ID_NO_BB("stub", area_id, format, argv[idx_ipv4_number]->arg); ret = ospf_area_stub_set(ospf, area_id); ospf_area_display_format_set(ospf, ospf_area_get(ospf, area_id), format); if (ret == 0) { vty_out(vty, "First deconfigure all virtual link through this area\n"); return CMD_WARNING_CONFIG_FAILED; } ospf_area_no_summary_unset(ospf, area_id); return CMD_SUCCESS; } DEFUN (ospf_area_stub_no_summary, ospf_area_stub_no_summary_cmd, "area stub no-summary", "OSPF stub parameters\n" "OSPF area ID in IP address format\n" "OSPF area ID as a decimal value\n" "Configure OSPF area as stub\n" "Do not inject inter-area routes into stub\n") { VTY_DECLVAR_INSTANCE_CONTEXT(ospf, ospf); int idx_ipv4_number = 1; struct in_addr area_id; int ret, format; VTY_GET_OSPF_AREA_ID_NO_BB("stub", area_id, format, argv[idx_ipv4_number]->arg); ret = ospf_area_stub_set(ospf, area_id); ospf_area_display_format_set(ospf, ospf_area_get(ospf, area_id), format); if (ret == 0) { vty_out(vty, "%% Area cannot be stub as it contains a virtual link\n"); return CMD_WARNING_CONFIG_FAILED; } ospf_area_no_summary_set(ospf, area_id); return CMD_SUCCESS; } DEFUN (no_ospf_area_stub, no_ospf_area_stub_cmd, "no area stub", NO_STR "OSPF area parameters\n" "OSPF area ID in IP address format\n" "OSPF area ID as a decimal value\n" "Configure OSPF area as stub\n") { VTY_DECLVAR_INSTANCE_CONTEXT(ospf, ospf); int idx_ipv4_number = 2; struct in_addr area_id; int format; VTY_GET_OSPF_AREA_ID_NO_BB("stub", area_id, format, argv[idx_ipv4_number]->arg); ospf_area_stub_unset(ospf, area_id); ospf_area_no_summary_unset(ospf, area_id); return CMD_SUCCESS; } DEFUN (no_ospf_area_stub_no_summary, no_ospf_area_stub_no_summary_cmd, "no area stub no-summary", NO_STR "OSPF area parameters\n" "OSPF area ID in IP address format\n" "OSPF area ID as a decimal value\n" "Configure OSPF area as stub\n" "Do not inject inter-area routes into area\n") { VTY_DECLVAR_INSTANCE_CONTEXT(ospf, ospf); int idx_ipv4_number = 2; struct in_addr area_id; int format; VTY_GET_OSPF_AREA_ID_NO_BB("stub", area_id, format, argv[idx_ipv4_number]->arg); ospf_area_no_summary_unset(ospf, area_id); return CMD_SUCCESS; } static int ospf_area_nssa_cmd_handler(struct vty *vty, int argc, struct cmd_token **argv, int cfg_nosum, int nosum) { VTY_DECLVAR_INSTANCE_CONTEXT(ospf, ospf); struct in_addr area_id; int ret, format; VTY_GET_OSPF_AREA_ID_NO_BB("NSSA", area_id, format, argv[1]->arg); ret = ospf_area_nssa_set(ospf, area_id); ospf_area_display_format_set(ospf, ospf_area_get(ospf, area_id), format); if (ret == 0) { vty_out(vty, "%% Area cannot be nssa as it contains a virtual link\n"); return CMD_WARNING_CONFIG_FAILED; } if (argc > 3) { if (strncmp(argv[3]->text, "translate-c", 11) == 0) ospf_area_nssa_translator_role_set( ospf, area_id, OSPF_NSSA_ROLE_CANDIDATE); else if (strncmp(argv[3]->text, "translate-n", 11) == 0) ospf_area_nssa_translator_role_set( ospf, area_id, OSPF_NSSA_ROLE_NEVER); else if (strncmp(argv[3]->text, "translate-a", 11) == 0) ospf_area_nssa_translator_role_set( ospf, area_id, OSPF_NSSA_ROLE_ALWAYS); } else { ospf_area_nssa_translator_role_set(ospf, area_id, OSPF_NSSA_ROLE_CANDIDATE); } if (cfg_nosum) { if (nosum) ospf_area_no_summary_set(ospf, area_id); else ospf_area_no_summary_unset(ospf, area_id); } ospf_schedule_abr_task(ospf); return CMD_SUCCESS; } DEFUN (ospf_area_nssa_translate, ospf_area_nssa_translate_cmd, "area nssa ", "OSPF area parameters\n" "OSPF area ID in IP address format\n" "OSPF area ID as a decimal value\n" "Configure OSPF area as nssa\n" "Configure NSSA-ABR for translate election (default)\n" "Configure NSSA-ABR to never translate\n" "Configure NSSA-ABR to always translate\n") { return ospf_area_nssa_cmd_handler(vty, argc, argv, 0, 0); } DEFUN (ospf_area_nssa, ospf_area_nssa_cmd, "area nssa", "OSPF area parameters\n" "OSPF area ID in IP address format\n" "OSPF area ID as a decimal value\n" "Configure OSPF area as nssa\n") { return ospf_area_nssa_cmd_handler(vty, argc, argv, 0, 0); } DEFUN (ospf_area_nssa_no_summary, ospf_area_nssa_no_summary_cmd, "area nssa no-summary", "OSPF area parameters\n" "OSPF area ID in IP address format\n" "OSPF area ID as a decimal value\n" "Configure OSPF area as nssa\n" "Do not inject inter-area routes into nssa\n") { int idx_ipv4_number = 1; struct in_addr area_id; int format; VTY_DECLVAR_INSTANCE_CONTEXT(ospf, ospf); VTY_GET_OSPF_AREA_ID_NO_BB("NSSA", area_id, format, argv[idx_ipv4_number]->arg); ospf_area_display_format_set(ospf, ospf_area_get(ospf, area_id), format); ospf_area_nssa_no_summary_set(ospf, area_id); ospf_schedule_abr_task(ospf); return CMD_SUCCESS; } DEFUN (no_ospf_area_nssa_no_summary, no_ospf_area_nssa_no_summary_cmd, "no area nssa no-summary", NO_STR "OSPF area parameters\n" "OSPF area ID in IP address format\n" "OSPF area ID as a decimal value\n" "Configure OSPF area as nssa\n" "Do not inject inter-area routes into nssa\n") { int idx_ipv4_number = 2; struct in_addr area_id; int format; VTY_DECLVAR_INSTANCE_CONTEXT(ospf, ospf); VTY_GET_OSPF_AREA_ID_NO_BB("nssa", area_id, format, argv[idx_ipv4_number]->arg); ospf_area_display_format_set(ospf, ospf_area_get(ospf, area_id), format); ospf_area_no_summary_unset(ospf, area_id); ospf_schedule_abr_task(ospf); return CMD_SUCCESS; } DEFUN (no_ospf_area_nssa, no_ospf_area_nssa_cmd, "no area nssa []", NO_STR "OSPF area parameters\n" "OSPF area ID in IP address format\n" "OSPF area ID as a decimal value\n" "Configure OSPF area as nssa\n" "Configure NSSA-ABR for translate election (default)\n" "Configure NSSA-ABR to never translate\n" "Configure NSSA-ABR to always translate\n") { VTY_DECLVAR_INSTANCE_CONTEXT(ospf, ospf); int idx_ipv4_number = 2; struct in_addr area_id; int format; VTY_GET_OSPF_AREA_ID_NO_BB("NSSA", area_id, format, argv[idx_ipv4_number]->arg); ospf_area_nssa_unset(ospf, area_id, argc); ospf_schedule_abr_task(ospf); return CMD_SUCCESS; } DEFUN (ospf_area_default_cost, ospf_area_default_cost_cmd, "area default-cost (0-16777215)", "OSPF area parameters\n" "OSPF area ID in IP address format\n" "OSPF area ID as a decimal value\n" "Set the summary-default cost of a NSSA or stub area\n" "Stub's advertised default summary cost\n") { VTY_DECLVAR_INSTANCE_CONTEXT(ospf, ospf); int idx_ipv4_number = 1; int idx_number = 3; struct ospf_area *area; struct in_addr area_id; uint32_t cost; int format; struct prefix_ipv4 p; VTY_GET_OSPF_AREA_ID_NO_BB("default-cost", area_id, format, argv[idx_ipv4_number]->arg); cost = strtoul(argv[idx_number]->arg, NULL, 10); area = ospf_area_get(ospf, area_id); ospf_area_display_format_set(ospf, area, format); if (area->external_routing == OSPF_AREA_DEFAULT) { vty_out(vty, "The area is neither stub, nor NSSA\n"); return CMD_WARNING_CONFIG_FAILED; } area->default_cost = cost; p.family = AF_INET; p.prefix.s_addr = OSPF_DEFAULT_DESTINATION; p.prefixlen = 0; if (IS_DEBUG_OSPF_EVENT) zlog_debug( "ospf_abr_announce_stub_defaults(): " "announcing 0.0.0.0/0 to area %s", inet_ntoa(area->area_id)); ospf_abr_announce_network_to_area(&p, area->default_cost, area); return CMD_SUCCESS; } DEFUN (no_ospf_area_default_cost, no_ospf_area_default_cost_cmd, "no area default-cost (0-16777215)", NO_STR "OSPF area parameters\n" "OSPF area ID in IP address format\n" "OSPF area ID as a decimal value\n" "Set the summary-default cost of a NSSA or stub area\n" "Stub's advertised default summary cost\n") { VTY_DECLVAR_INSTANCE_CONTEXT(ospf, ospf); int idx_ipv4_number = 2; struct ospf_area *area; struct in_addr area_id; int format; struct prefix_ipv4 p; VTY_GET_OSPF_AREA_ID_NO_BB("default-cost", area_id, format, argv[idx_ipv4_number]->arg); area = ospf_area_lookup_by_area_id(ospf, area_id); if (area == NULL) return CMD_SUCCESS; if (area->external_routing == OSPF_AREA_DEFAULT) { vty_out(vty, "The area is neither stub, nor NSSA\n"); return CMD_WARNING_CONFIG_FAILED; } area->default_cost = 1; p.family = AF_INET; p.prefix.s_addr = OSPF_DEFAULT_DESTINATION; p.prefixlen = 0; if (IS_DEBUG_OSPF_EVENT) zlog_debug( "ospf_abr_announce_stub_defaults(): " "announcing 0.0.0.0/0 to area %s", inet_ntoa(area->area_id)); ospf_abr_announce_network_to_area(&p, area->default_cost, area); ospf_area_check_free(ospf, area_id); return CMD_SUCCESS; } DEFUN (ospf_area_export_list, ospf_area_export_list_cmd, "area export-list NAME", "OSPF area parameters\n" "OSPF area ID in IP address format\n" "OSPF area ID as a decimal value\n" "Set the filter for networks announced to other areas\n" "Name of the access-list\n") { VTY_DECLVAR_INSTANCE_CONTEXT(ospf, ospf); int idx_ipv4_number = 1; struct ospf_area *area; struct in_addr area_id; int format; VTY_GET_OSPF_AREA_ID(area_id, format, argv[idx_ipv4_number]->arg); area = ospf_area_get(ospf, area_id); ospf_area_display_format_set(ospf, area, format); ospf_area_export_list_set(ospf, area, argv[3]->arg); return CMD_SUCCESS; } DEFUN (no_ospf_area_export_list, no_ospf_area_export_list_cmd, "no area export-list NAME", NO_STR "OSPF area parameters\n" "OSPF area ID in IP address format\n" "OSPF area ID as a decimal value\n" "Unset the filter for networks announced to other areas\n" "Name of the access-list\n") { VTY_DECLVAR_INSTANCE_CONTEXT(ospf, ospf); int idx_ipv4_number = 2; struct ospf_area *area; struct in_addr area_id; int format; VTY_GET_OSPF_AREA_ID(area_id, format, argv[idx_ipv4_number]->arg); area = ospf_area_lookup_by_area_id(ospf, area_id); if (area == NULL) return CMD_SUCCESS; ospf_area_export_list_unset(ospf, area); return CMD_SUCCESS; } DEFUN (ospf_area_import_list, ospf_area_import_list_cmd, "area import-list NAME", "OSPF area parameters\n" "OSPF area ID in IP address format\n" "OSPF area ID as a decimal value\n" "Set the filter for networks from other areas announced to the specified one\n" "Name of the access-list\n") { VTY_DECLVAR_INSTANCE_CONTEXT(ospf, ospf); int idx_ipv4_number = 1; struct ospf_area *area; struct in_addr area_id; int format; VTY_GET_OSPF_AREA_ID(area_id, format, argv[idx_ipv4_number]->arg); area = ospf_area_get(ospf, area_id); ospf_area_display_format_set(ospf, area, format); ospf_area_import_list_set(ospf, area, argv[3]->arg); return CMD_SUCCESS; } DEFUN (no_ospf_area_import_list, no_ospf_area_import_list_cmd, "no area import-list NAME", NO_STR "OSPF area parameters\n" "OSPF area ID in IP address format\n" "OSPF area ID as a decimal value\n" "Unset the filter for networks announced to other areas\n" "Name of the access-list\n") { VTY_DECLVAR_INSTANCE_CONTEXT(ospf, ospf); int idx_ipv4_number = 2; struct ospf_area *area; struct in_addr area_id; int format; VTY_GET_OSPF_AREA_ID(area_id, format, argv[idx_ipv4_number]->arg); area = ospf_area_lookup_by_area_id(ospf, area_id); if (area == NULL) return CMD_SUCCESS; ospf_area_import_list_unset(ospf, area); return CMD_SUCCESS; } DEFUN (ospf_area_filter_list, ospf_area_filter_list_cmd, "area filter-list prefix WORD ", "OSPF area parameters\n" "OSPF area ID in IP address format\n" "OSPF area ID as a decimal value\n" "Filter networks between OSPF areas\n" "Filter prefixes between OSPF areas\n" "Name of an IP prefix-list\n" "Filter networks sent to this area\n" "Filter networks sent from this area\n") { VTY_DECLVAR_INSTANCE_CONTEXT(ospf, ospf); int idx_ipv4_number = 1; int idx_word = 4; int idx_in_out = 5; struct ospf_area *area; struct in_addr area_id; struct prefix_list *plist; int format; VTY_GET_OSPF_AREA_ID(area_id, format, argv[idx_ipv4_number]->arg); area = ospf_area_get(ospf, area_id); ospf_area_display_format_set(ospf, area, format); plist = prefix_list_lookup(AFI_IP, argv[idx_word]->arg); if (strncmp(argv[idx_in_out]->arg, "in", 2) == 0) { PREFIX_LIST_IN(area) = plist; if (PREFIX_NAME_IN(area)) free(PREFIX_NAME_IN(area)); PREFIX_NAME_IN(area) = strdup(argv[idx_word]->arg); ospf_schedule_abr_task(ospf); } else { PREFIX_LIST_OUT(area) = plist; if (PREFIX_NAME_OUT(area)) free(PREFIX_NAME_OUT(area)); PREFIX_NAME_OUT(area) = strdup(argv[idx_word]->arg); ospf_schedule_abr_task(ospf); } return CMD_SUCCESS; } DEFUN (no_ospf_area_filter_list, no_ospf_area_filter_list_cmd, "no area filter-list prefix WORD ", NO_STR "OSPF area parameters\n" "OSPF area ID in IP address format\n" "OSPF area ID as a decimal value\n" "Filter networks between OSPF areas\n" "Filter prefixes between OSPF areas\n" "Name of an IP prefix-list\n" "Filter networks sent to this area\n" "Filter networks sent from this area\n") { VTY_DECLVAR_INSTANCE_CONTEXT(ospf, ospf); int idx_ipv4_number = 2; int idx_word = 5; int idx_in_out = 6; struct ospf_area *area; struct in_addr area_id; int format; VTY_GET_OSPF_AREA_ID(area_id, format, argv[idx_ipv4_number]->arg); if ((area = ospf_area_lookup_by_area_id(ospf, area_id)) == NULL) return CMD_SUCCESS; if (strncmp(argv[idx_in_out]->arg, "in", 2) == 0) { if (PREFIX_NAME_IN(area)) if (strcmp(PREFIX_NAME_IN(area), argv[idx_word]->arg) != 0) return CMD_SUCCESS; PREFIX_LIST_IN(area) = NULL; if (PREFIX_NAME_IN(area)) free(PREFIX_NAME_IN(area)); PREFIX_NAME_IN(area) = NULL; ospf_schedule_abr_task(ospf); } else { if (PREFIX_NAME_OUT(area)) if (strcmp(PREFIX_NAME_OUT(area), argv[idx_word]->arg) != 0) return CMD_SUCCESS; PREFIX_LIST_OUT(area) = NULL; if (PREFIX_NAME_OUT(area)) free(PREFIX_NAME_OUT(area)); PREFIX_NAME_OUT(area) = NULL; ospf_schedule_abr_task(ospf); } return CMD_SUCCESS; } DEFUN (ospf_area_authentication_message_digest, ospf_area_authentication_message_digest_cmd, "[no] area authentication message-digest", NO_STR "OSPF area parameters\n" "OSPF area ID in IP address format\n" "OSPF area ID as a decimal value\n" "Enable authentication\n" "Use message-digest authentication\n") { VTY_DECLVAR_INSTANCE_CONTEXT(ospf, ospf); int idx = 0; struct ospf_area *area; struct in_addr area_id; int format; argv_find(argv, argc, "area", &idx); VTY_GET_OSPF_AREA_ID(area_id, format, argv[idx + 1]->arg); area = ospf_area_get(ospf, area_id); ospf_area_display_format_set(ospf, area, format); area->auth_type = strmatch(argv[0]->text, "no") ? OSPF_AUTH_NULL : OSPF_AUTH_CRYPTOGRAPHIC; return CMD_SUCCESS; } DEFUN (ospf_area_authentication, ospf_area_authentication_cmd, "area authentication", "OSPF area parameters\n" "OSPF area ID in IP address format\n" "OSPF area ID as a decimal value\n" "Enable authentication\n") { VTY_DECLVAR_INSTANCE_CONTEXT(ospf, ospf); int idx_ipv4_number = 1; struct ospf_area *area; struct in_addr area_id; int format; VTY_GET_OSPF_AREA_ID(area_id, format, argv[idx_ipv4_number]->arg); area = ospf_area_get(ospf, area_id); ospf_area_display_format_set(ospf, area, format); area->auth_type = OSPF_AUTH_SIMPLE; return CMD_SUCCESS; } DEFUN (no_ospf_area_authentication, no_ospf_area_authentication_cmd, "no area authentication", NO_STR "OSPF area parameters\n" "OSPF area ID in IP address format\n" "OSPF area ID as a decimal value\n" "Enable authentication\n") { VTY_DECLVAR_INSTANCE_CONTEXT(ospf, ospf); int idx_ipv4_number = 2; struct ospf_area *area; struct in_addr area_id; int format; VTY_GET_OSPF_AREA_ID(area_id, format, argv[idx_ipv4_number]->arg); area = ospf_area_lookup_by_area_id(ospf, area_id); if (area == NULL) return CMD_SUCCESS; area->auth_type = OSPF_AUTH_NULL; ospf_area_check_free(ospf, area_id); return CMD_SUCCESS; } DEFUN (ospf_abr_type, ospf_abr_type_cmd, "ospf abr-type ", "OSPF specific commands\n" "Set OSPF ABR type\n" "Alternative ABR, cisco implementation\n" "Alternative ABR, IBM implementation\n" "Shortcut ABR\n" "Standard behavior (RFC2328)\n") { VTY_DECLVAR_INSTANCE_CONTEXT(ospf, ospf); int idx_vendor = 2; uint8_t abr_type = OSPF_ABR_UNKNOWN; if (strncmp(argv[idx_vendor]->arg, "c", 1) == 0) abr_type = OSPF_ABR_CISCO; else if (strncmp(argv[idx_vendor]->arg, "i", 1) == 0) abr_type = OSPF_ABR_IBM; else if (strncmp(argv[idx_vendor]->arg, "sh", 2) == 0) abr_type = OSPF_ABR_SHORTCUT; else if (strncmp(argv[idx_vendor]->arg, "st", 2) == 0) abr_type = OSPF_ABR_STAND; else return CMD_WARNING_CONFIG_FAILED; /* If ABR type value is changed, schedule ABR task. */ if (ospf->abr_type != abr_type) { ospf->abr_type = abr_type; ospf_schedule_abr_task(ospf); } return CMD_SUCCESS; } DEFUN (no_ospf_abr_type, no_ospf_abr_type_cmd, "no ospf abr-type ", NO_STR "OSPF specific commands\n" "Set OSPF ABR type\n" "Alternative ABR, cisco implementation\n" "Alternative ABR, IBM implementation\n" "Shortcut ABR\n" "Standard ABR\n") { VTY_DECLVAR_INSTANCE_CONTEXT(ospf, ospf); int idx_vendor = 3; uint8_t abr_type = OSPF_ABR_UNKNOWN; if (strncmp(argv[idx_vendor]->arg, "c", 1) == 0) abr_type = OSPF_ABR_CISCO; else if (strncmp(argv[idx_vendor]->arg, "i", 1) == 0) abr_type = OSPF_ABR_IBM; else if (strncmp(argv[idx_vendor]->arg, "sh", 2) == 0) abr_type = OSPF_ABR_SHORTCUT; else if (strncmp(argv[idx_vendor]->arg, "st", 2) == 0) abr_type = OSPF_ABR_STAND; else return CMD_WARNING_CONFIG_FAILED; /* If ABR type value is changed, schedule ABR task. */ if (ospf->abr_type == abr_type) { ospf->abr_type = OSPF_ABR_DEFAULT; ospf_schedule_abr_task(ospf); } return CMD_SUCCESS; } DEFUN (ospf_log_adjacency_changes, ospf_log_adjacency_changes_cmd, "log-adjacency-changes", "Log changes in adjacency state\n") { VTY_DECLVAR_INSTANCE_CONTEXT(ospf, ospf); SET_FLAG(ospf->config, OSPF_LOG_ADJACENCY_CHANGES); UNSET_FLAG(ospf->config, OSPF_LOG_ADJACENCY_DETAIL); return CMD_SUCCESS; } DEFUN (ospf_log_adjacency_changes_detail, ospf_log_adjacency_changes_detail_cmd, "log-adjacency-changes detail", "Log changes in adjacency state\n" "Log all state changes\n") { VTY_DECLVAR_INSTANCE_CONTEXT(ospf, ospf); SET_FLAG(ospf->config, OSPF_LOG_ADJACENCY_CHANGES); SET_FLAG(ospf->config, OSPF_LOG_ADJACENCY_DETAIL); return CMD_SUCCESS; } DEFUN (no_ospf_log_adjacency_changes, no_ospf_log_adjacency_changes_cmd, "no log-adjacency-changes", NO_STR "Log changes in adjacency state\n") { VTY_DECLVAR_INSTANCE_CONTEXT(ospf, ospf); UNSET_FLAG(ospf->config, OSPF_LOG_ADJACENCY_DETAIL); UNSET_FLAG(ospf->config, OSPF_LOG_ADJACENCY_CHANGES); return CMD_SUCCESS; } DEFUN (no_ospf_log_adjacency_changes_detail, no_ospf_log_adjacency_changes_detail_cmd, "no log-adjacency-changes detail", NO_STR "Log changes in adjacency state\n" "Log all state changes\n") { VTY_DECLVAR_INSTANCE_CONTEXT(ospf, ospf); UNSET_FLAG(ospf->config, OSPF_LOG_ADJACENCY_DETAIL); return CMD_SUCCESS; } DEFUN (ospf_compatible_rfc1583, ospf_compatible_rfc1583_cmd, "compatible rfc1583", "OSPF compatibility list\n" "compatible with RFC 1583\n") { VTY_DECLVAR_INSTANCE_CONTEXT(ospf, ospf); if (!CHECK_FLAG(ospf->config, OSPF_RFC1583_COMPATIBLE)) { SET_FLAG(ospf->config, OSPF_RFC1583_COMPATIBLE); ospf_spf_calculate_schedule(ospf, SPF_FLAG_CONFIG_CHANGE); } return CMD_SUCCESS; } DEFUN (no_ospf_compatible_rfc1583, no_ospf_compatible_rfc1583_cmd, "no compatible rfc1583", NO_STR "OSPF compatibility list\n" "compatible with RFC 1583\n") { VTY_DECLVAR_INSTANCE_CONTEXT(ospf, ospf); if (CHECK_FLAG(ospf->config, OSPF_RFC1583_COMPATIBLE)) { UNSET_FLAG(ospf->config, OSPF_RFC1583_COMPATIBLE); ospf_spf_calculate_schedule(ospf, SPF_FLAG_CONFIG_CHANGE); } return CMD_SUCCESS; } ALIAS(ospf_compatible_rfc1583, ospf_rfc1583_flag_cmd, "ospf rfc1583compatibility", "OSPF specific commands\n" "Enable the RFC1583Compatibility flag\n") ALIAS(no_ospf_compatible_rfc1583, no_ospf_rfc1583_flag_cmd, "no ospf rfc1583compatibility", NO_STR "OSPF specific commands\n" "Disable the RFC1583Compatibility flag\n") static int ospf_timers_spf_set(struct vty *vty, unsigned int delay, unsigned int hold, unsigned int max) { VTY_DECLVAR_INSTANCE_CONTEXT(ospf, ospf); ospf->spf_delay = delay; ospf->spf_holdtime = hold; ospf->spf_max_holdtime = max; return CMD_SUCCESS; } DEFUN (ospf_timers_min_ls_interval, ospf_timers_min_ls_interval_cmd, "timers throttle lsa all (0-5000)", "Adjust routing timers\n" "Throttling adaptive timer\n" "LSA delay between transmissions\n" "All LSA types\n" "Delay (msec) between sending LSAs\n") { VTY_DECLVAR_INSTANCE_CONTEXT(ospf, ospf); int idx_number = 4; unsigned int interval; if (argc < 5) { vty_out(vty, "Insufficient arguments\n"); return CMD_WARNING_CONFIG_FAILED; } interval = strtoul(argv[idx_number]->arg, NULL, 10); ospf->min_ls_interval = interval; return CMD_SUCCESS; } DEFUN (no_ospf_timers_min_ls_interval, no_ospf_timers_min_ls_interval_cmd, "no timers throttle lsa all [(0-5000)]", NO_STR "Adjust routing timers\n" "Throttling adaptive timer\n" "LSA delay between transmissions\n" "All LSA types\n" "Delay (msec) between sending LSAs\n") { VTY_DECLVAR_INSTANCE_CONTEXT(ospf, ospf); ospf->min_ls_interval = OSPF_MIN_LS_INTERVAL; return CMD_SUCCESS; } DEFUN (ospf_timers_throttle_spf, ospf_timers_throttle_spf_cmd, "timers throttle spf (0-600000) (0-600000) (0-600000)", "Adjust routing timers\n" "Throttling adaptive timer\n" "OSPF SPF timers\n" "Delay (msec) from first change received till SPF calculation\n" "Initial hold time (msec) between consecutive SPF calculations\n" "Maximum hold time (msec)\n") { int idx_number = 3; int idx_number_2 = 4; int idx_number_3 = 5; unsigned int delay, hold, max; if (argc < 6) { vty_out(vty, "Insufficient arguments\n"); return CMD_WARNING_CONFIG_FAILED; } delay = strtoul(argv[idx_number]->arg, NULL, 10); hold = strtoul(argv[idx_number_2]->arg, NULL, 10); max = strtoul(argv[idx_number_3]->arg, NULL, 10); return ospf_timers_spf_set(vty, delay, hold, max); } DEFUN (no_ospf_timers_throttle_spf, no_ospf_timers_throttle_spf_cmd, "no timers throttle spf [(0-600000)(0-600000)(0-600000)]", NO_STR "Adjust routing timers\n" "Throttling adaptive timer\n" "OSPF SPF timers\n" "Delay (msec) from first change received till SPF calculation\n" "Initial hold time (msec) between consecutive SPF calculations\n" "Maximum hold time (msec)\n") { return ospf_timers_spf_set(vty, OSPF_SPF_DELAY_DEFAULT, OSPF_SPF_HOLDTIME_DEFAULT, OSPF_SPF_MAX_HOLDTIME_DEFAULT); } DEFUN (ospf_timers_lsa_min_arrival, ospf_timers_lsa_min_arrival_cmd, "timers lsa min-arrival (0-600000)", "Adjust routing timers\n" "OSPF LSA timers\n" "Minimum delay in receiving new version of a LSA\n" "Delay in milliseconds\n") { VTY_DECLVAR_INSTANCE_CONTEXT(ospf, ospf); ospf->min_ls_arrival = strtoul(argv[argc - 1]->arg, NULL, 10); return CMD_SUCCESS; } DEFUN (no_ospf_timers_lsa_min_arrival, no_ospf_timers_lsa_min_arrival_cmd, "no timers lsa min-arrival [(0-600000)]", NO_STR "Adjust routing timers\n" "OSPF LSA timers\n" "Minimum delay in receiving new version of a LSA\n" "Delay in milliseconds\n") { VTY_DECLVAR_INSTANCE_CONTEXT(ospf, ospf); unsigned int minarrival; if (argc > 4) { minarrival = strtoul(argv[argc - 1]->arg, NULL, 10); if (ospf->min_ls_arrival != minarrival || minarrival == OSPF_MIN_LS_ARRIVAL) return CMD_SUCCESS; } ospf->min_ls_arrival = OSPF_MIN_LS_ARRIVAL; return CMD_SUCCESS; } DEFUN (ospf_neighbor, ospf_neighbor_cmd, "neighbor A.B.C.D [priority (0-255) [poll-interval (1-65535)]]", NEIGHBOR_STR "Neighbor IP address\n" "Neighbor Priority\n" "Priority\n" "Dead Neighbor Polling interval\n" "Seconds\n") { VTY_DECLVAR_INSTANCE_CONTEXT(ospf, ospf); int idx_ipv4 = 1; int idx_pri = 3; int idx_poll = 5; struct in_addr nbr_addr; unsigned int priority = OSPF_NEIGHBOR_PRIORITY_DEFAULT; unsigned int interval = OSPF_POLL_INTERVAL_DEFAULT; if (!inet_aton(argv[idx_ipv4]->arg, &nbr_addr)) { vty_out(vty, "Please specify Neighbor ID by A.B.C.D\n"); return CMD_WARNING_CONFIG_FAILED; } if (argc > 2) priority = strtoul(argv[idx_pri]->arg, NULL, 10); if (argc > 4) interval = strtoul(argv[idx_poll]->arg, NULL, 10); ospf_nbr_nbma_set(ospf, nbr_addr); if (argc > 2) ospf_nbr_nbma_priority_set(ospf, nbr_addr, priority); if (argc > 4) ospf_nbr_nbma_poll_interval_set(ospf, nbr_addr, interval); return CMD_SUCCESS; } DEFUN (ospf_neighbor_poll_interval, ospf_neighbor_poll_interval_cmd, "neighbor A.B.C.D poll-interval (1-65535) [priority (0-255)]", NEIGHBOR_STR "Neighbor IP address\n" "Dead Neighbor Polling interval\n" "Seconds\n" "OSPF priority of non-broadcast neighbor\n" "Priority\n") { VTY_DECLVAR_INSTANCE_CONTEXT(ospf, ospf); int idx_ipv4 = 1; int idx_poll = 3; int idx_pri = 5; struct in_addr nbr_addr; unsigned int priority; unsigned int interval; if (!inet_aton(argv[idx_ipv4]->arg, &nbr_addr)) { vty_out(vty, "Please specify Neighbor ID by A.B.C.D\n"); return CMD_WARNING_CONFIG_FAILED; } interval = strtoul(argv[idx_poll]->arg, NULL, 10); priority = argc > 4 ? strtoul(argv[idx_pri]->arg, NULL, 10) : OSPF_NEIGHBOR_PRIORITY_DEFAULT; ospf_nbr_nbma_set(ospf, nbr_addr); ospf_nbr_nbma_poll_interval_set(ospf, nbr_addr, interval); if (argc > 4) ospf_nbr_nbma_priority_set(ospf, nbr_addr, priority); return CMD_SUCCESS; } DEFUN (no_ospf_neighbor, no_ospf_neighbor_cmd, "no neighbor A.B.C.D [priority (0-255) [poll-interval (1-65525)]]", NO_STR NEIGHBOR_STR "Neighbor IP address\n" "Neighbor Priority\n" "Priority\n" "Dead Neighbor Polling interval\n" "Seconds\n") { VTY_DECLVAR_INSTANCE_CONTEXT(ospf, ospf); int idx_ipv4 = 2; struct in_addr nbr_addr; if (!inet_aton(argv[idx_ipv4]->arg, &nbr_addr)) { vty_out(vty, "Please specify Neighbor ID by A.B.C.D\n"); return CMD_WARNING_CONFIG_FAILED; } (void)ospf_nbr_nbma_unset(ospf, nbr_addr); return CMD_SUCCESS; } DEFUN (no_ospf_neighbor_poll, no_ospf_neighbor_poll_cmd, "no neighbor A.B.C.D poll-interval (1-65535) [priority (0-255)]", NO_STR NEIGHBOR_STR "Neighbor IP address\n" "Dead Neighbor Polling interval\n" "Seconds\n" "Neighbor Priority\n" "Priority\n") { VTY_DECLVAR_INSTANCE_CONTEXT(ospf, ospf); int idx_ipv4 = 2; struct in_addr nbr_addr; if (!inet_aton(argv[idx_ipv4]->arg, &nbr_addr)) { vty_out(vty, "Please specify Neighbor ID by A.B.C.D\n"); return CMD_WARNING_CONFIG_FAILED; } (void)ospf_nbr_nbma_unset(ospf, nbr_addr); return CMD_SUCCESS; } DEFUN (ospf_refresh_timer, ospf_refresh_timer_cmd, "refresh timer (10-1800)", "Adjust refresh parameters\n" "Set refresh timer\n" "Timer value in seconds\n") { VTY_DECLVAR_INSTANCE_CONTEXT(ospf, ospf); int idx_number = 2; unsigned int interval; interval = strtoul(argv[idx_number]->arg, NULL, 10); interval = (interval / OSPF_LSA_REFRESHER_GRANULARITY) * OSPF_LSA_REFRESHER_GRANULARITY; ospf_timers_refresh_set(ospf, interval); return CMD_SUCCESS; } DEFUN (no_ospf_refresh_timer, no_ospf_refresh_timer_val_cmd, "no refresh timer [(10-1800)]", NO_STR "Adjust refresh parameters\n" "Unset refresh timer\n" "Timer value in seconds\n") { VTY_DECLVAR_INSTANCE_CONTEXT(ospf, ospf); int idx_number = 3; unsigned int interval; if (argc == 1) { interval = strtoul(argv[idx_number]->arg, NULL, 10); if (ospf->lsa_refresh_interval != interval || interval == OSPF_LSA_REFRESH_INTERVAL_DEFAULT) return CMD_SUCCESS; } ospf_timers_refresh_unset(ospf); return CMD_SUCCESS; } DEFUN (ospf_auto_cost_reference_bandwidth, ospf_auto_cost_reference_bandwidth_cmd, "auto-cost reference-bandwidth (1-4294967)", "Calculate OSPF interface cost according to bandwidth\n" "Use reference bandwidth method to assign OSPF cost\n" "The reference bandwidth in terms of Mbits per second\n") { VTY_DECLVAR_INSTANCE_CONTEXT(ospf, ospf); struct vrf *vrf = vrf_lookup_by_id(ospf->vrf_id); int idx_number = 2; uint32_t refbw; struct interface *ifp; refbw = strtol(argv[idx_number]->arg, NULL, 10); if (refbw < 1 || refbw > 4294967) { vty_out(vty, "reference-bandwidth value is invalid\n"); return CMD_WARNING_CONFIG_FAILED; } /* If reference bandwidth is changed. */ if ((refbw) == ospf->ref_bandwidth) return CMD_SUCCESS; ospf->ref_bandwidth = refbw; FOR_ALL_INTERFACES (vrf, ifp) ospf_if_recalculate_output_cost(ifp); return CMD_SUCCESS; } DEFUN (no_ospf_auto_cost_reference_bandwidth, no_ospf_auto_cost_reference_bandwidth_cmd, "no auto-cost reference-bandwidth [(1-4294967)]", NO_STR "Calculate OSPF interface cost according to bandwidth\n" "Use reference bandwidth method to assign OSPF cost\n" "The reference bandwidth in terms of Mbits per second\n") { VTY_DECLVAR_INSTANCE_CONTEXT(ospf, ospf); struct vrf *vrf = vrf_lookup_by_id(ospf->vrf_id); struct interface *ifp; if (ospf->ref_bandwidth == OSPF_DEFAULT_REF_BANDWIDTH) return CMD_SUCCESS; ospf->ref_bandwidth = OSPF_DEFAULT_REF_BANDWIDTH; vty_out(vty, "%% OSPF: Reference bandwidth is changed.\n"); vty_out(vty, " Please ensure reference bandwidth is consistent across all routers\n"); FOR_ALL_INTERFACES (vrf, ifp) ospf_if_recalculate_output_cost(ifp); return CMD_SUCCESS; } DEFUN (ospf_write_multiplier, ospf_write_multiplier_cmd, "ospf write-multiplier (1-100)", "OSPF specific commands\n" "Write multiplier\n" "Maximum number of interface serviced per write\n") { VTY_DECLVAR_INSTANCE_CONTEXT(ospf, ospf); int idx_number; uint32_t write_oi_count; if (argc == 3) idx_number = 2; else idx_number = 1; write_oi_count = strtol(argv[idx_number]->arg, NULL, 10); if (write_oi_count < 1 || write_oi_count > 100) { vty_out(vty, "write-multiplier value is invalid\n"); return CMD_WARNING_CONFIG_FAILED; } ospf->write_oi_count = write_oi_count; return CMD_SUCCESS; } ALIAS(ospf_write_multiplier, write_multiplier_cmd, "write-multiplier (1-100)", "Write multiplier\n" "Maximum number of interface serviced per write\n") DEFUN (no_ospf_write_multiplier, no_ospf_write_multiplier_cmd, "no ospf write-multiplier (1-100)", NO_STR "OSPF specific commands\n" "Write multiplier\n" "Maximum number of interface serviced per write\n") { VTY_DECLVAR_INSTANCE_CONTEXT(ospf, ospf); ospf->write_oi_count = OSPF_WRITE_INTERFACE_COUNT_DEFAULT; return CMD_SUCCESS; } ALIAS(no_ospf_write_multiplier, no_write_multiplier_cmd, "no write-multiplier (1-100)", NO_STR "Write multiplier\n" "Maximum number of interface serviced per write\n") const char *ospf_abr_type_descr_str[] = {"Unknown", "Standard (RFC2328)", "Alternative IBM", "Alternative Cisco", "Alternative Shortcut"}; const char *ospf_shortcut_mode_descr_str[] = {"Default", "Enabled", "Disabled"}; static void show_ip_ospf_area(struct vty *vty, struct ospf_area *area, json_object *json_areas, bool use_json) { json_object *json_area = NULL; if (use_json) json_area = json_object_new_object(); /* Show Area ID. */ if (!use_json) vty_out(vty, " Area ID: %s", inet_ntoa(area->area_id)); /* Show Area type/mode. */ if (OSPF_IS_AREA_BACKBONE(area)) { if (use_json) json_object_boolean_true_add(json_area, "backbone"); else vty_out(vty, " (Backbone)\n"); } else { if (use_json) { if (area->external_routing == OSPF_AREA_STUB) { if (area->no_summary) json_object_boolean_true_add( json_area, "stubNoSummary"); if (area->shortcut_configured) json_object_boolean_true_add( json_area, "stubShortcut"); } else if (area->external_routing == OSPF_AREA_NSSA) { if (area->no_summary) json_object_boolean_true_add( json_area, "nssaNoSummary"); if (area->shortcut_configured) json_object_boolean_true_add( json_area, "nssaShortcut"); } json_object_string_add( json_area, "shortcuttingMode", ospf_shortcut_mode_descr_str [area->shortcut_configured]); if (area->shortcut_capability) json_object_boolean_true_add(json_area, "sBitConcensus"); } else { if (area->external_routing == OSPF_AREA_STUB) vty_out(vty, " (Stub%s%s)", area->no_summary ? ", no summary" : "", area->shortcut_configured ? "; " : ""); else if (area->external_routing == OSPF_AREA_NSSA) vty_out(vty, " (NSSA%s%s)", area->no_summary ? ", no summary" : "", area->shortcut_configured ? "; " : ""); vty_out(vty, "\n"); vty_out(vty, " Shortcutting mode: %s", ospf_shortcut_mode_descr_str [area->shortcut_configured]); vty_out(vty, ", S-bit consensus: %s\n", area->shortcut_capability ? "ok" : "no"); } } /* Show number of interfaces */ if (use_json) { json_object_int_add(json_area, "areaIfTotalCounter", listcount(area->oiflist)); json_object_int_add(json_area, "areaIfActiveCounter", area->act_ints); } else vty_out(vty, " Number of interfaces in this area: Total: %d, " "Active: %d\n", listcount(area->oiflist), area->act_ints); if (area->external_routing == OSPF_AREA_NSSA) { if (use_json) { json_object_boolean_true_add(json_area, "nssa"); if (!IS_OSPF_ABR(area->ospf)) json_object_boolean_false_add(json_area, "abr"); else if (area->NSSATranslatorState) { json_object_boolean_true_add(json_area, "abr"); if (area->NSSATranslatorRole == OSPF_NSSA_ROLE_CANDIDATE) json_object_boolean_true_add( json_area, "nssaTranslatorElected"); else if (area->NSSATranslatorRole == OSPF_NSSA_ROLE_ALWAYS) json_object_boolean_true_add( json_area, "nssaTranslatorAlways"); } else { json_object_boolean_true_add(json_area, "abr"); if (area->NSSATranslatorRole == OSPF_NSSA_ROLE_CANDIDATE) json_object_boolean_false_add( json_area, "nssaTranslatorElected"); else json_object_boolean_true_add( json_area, "nssaTranslatorNever"); } } else { vty_out(vty, " It is an NSSA configuration. \n Elected NSSA/ABR performs type-7/type-5 LSA translation. \n"); if (!IS_OSPF_ABR(area->ospf)) vty_out(vty, " It is not ABR, therefore not Translator. \n"); else if (area->NSSATranslatorState) { vty_out(vty, " We are an ABR and "); if (area->NSSATranslatorRole == OSPF_NSSA_ROLE_CANDIDATE) vty_out(vty, "the NSSA Elected Translator. \n"); else if (area->NSSATranslatorRole == OSPF_NSSA_ROLE_ALWAYS) vty_out(vty, "always an NSSA Translator. \n"); } else { vty_out(vty, " We are an ABR, but "); if (area->NSSATranslatorRole == OSPF_NSSA_ROLE_CANDIDATE) vty_out(vty, "not the NSSA Elected Translator. \n"); else vty_out(vty, "never an NSSA Translator. \n"); } } } /* Stub-router state for this area */ if (CHECK_FLAG(area->stub_router_state, OSPF_AREA_IS_STUB_ROUTED)) { char timebuf[OSPF_TIME_DUMP_SIZE]; if (use_json) { json_object_boolean_true_add( json_area, "originStubMaxDistRouterLsa"); if (CHECK_FLAG(area->stub_router_state, OSPF_AREA_ADMIN_STUB_ROUTED)) json_object_boolean_true_add( json_area, "indefiniteActiveAdmin"); if (area->t_stub_router) { long time_store; time_store = monotime_until( &area->t_stub_router->u.sands, NULL) / 1000LL; json_object_int_add( json_area, "activeStartupRemainderMsecs", time_store); } } else { vty_out(vty, " Originating stub / maximum-distance Router-LSA\n"); if (CHECK_FLAG(area->stub_router_state, OSPF_AREA_ADMIN_STUB_ROUTED)) vty_out(vty, " Administratively activated (indefinitely)\n"); if (area->t_stub_router) vty_out(vty, " Active from startup, %s remaining\n", ospf_timer_dump(area->t_stub_router, timebuf, sizeof(timebuf))); } } if (use_json) { /* Show number of fully adjacent neighbors. */ json_object_int_add(json_area, "nbrFullAdjacentCounter", area->full_nbrs); /* Show authentication type. */ if (area->auth_type == OSPF_AUTH_NULL) json_object_string_add(json_area, "authentication", "authenticationNone"); else if (area->auth_type == OSPF_AUTH_SIMPLE) json_object_string_add(json_area, "authentication", "authenticationSimplePassword"); else if (area->auth_type == OSPF_AUTH_CRYPTOGRAPHIC) json_object_string_add(json_area, "authentication", "authenticationMessageDigest"); if (!OSPF_IS_AREA_BACKBONE(area)) json_object_int_add(json_area, "virtualAdjacenciesPassingCounter", area->full_vls); /* Show SPF calculation times. */ json_object_int_add(json_area, "spfExecutedCounter", area->spf_calculation); json_object_int_add(json_area, "lsaNumber", area->lsdb->total); json_object_int_add( json_area, "lsaRouterNumber", ospf_lsdb_count(area->lsdb, OSPF_ROUTER_LSA)); json_object_int_add( json_area, "lsaRouterChecksum", ospf_lsdb_checksum(area->lsdb, OSPF_ROUTER_LSA)); json_object_int_add( json_area, "lsaNetworkNumber", ospf_lsdb_count(area->lsdb, OSPF_NETWORK_LSA)); json_object_int_add( json_area, "lsaNetworkChecksum", ospf_lsdb_checksum(area->lsdb, OSPF_NETWORK_LSA)); json_object_int_add( json_area, "lsaSummaryNumber", ospf_lsdb_count(area->lsdb, OSPF_SUMMARY_LSA)); json_object_int_add( json_area, "lsaSummaryChecksum", ospf_lsdb_checksum(area->lsdb, OSPF_SUMMARY_LSA)); json_object_int_add( json_area, "lsaAsbrNumber", ospf_lsdb_count(area->lsdb, OSPF_ASBR_SUMMARY_LSA)); json_object_int_add( json_area, "lsaAsbrChecksum", ospf_lsdb_checksum(area->lsdb, OSPF_ASBR_SUMMARY_LSA)); json_object_int_add( json_area, "lsaNssaNumber", ospf_lsdb_count(area->lsdb, OSPF_AS_NSSA_LSA)); json_object_int_add( json_area, "lsaNssaChecksum", ospf_lsdb_checksum(area->lsdb, OSPF_AS_NSSA_LSA)); } else { /* Show number of fully adjacent neighbors. */ vty_out(vty, " Number of fully adjacent neighbors in this area:" " %d\n", area->full_nbrs); /* Show authentication type. */ vty_out(vty, " Area has "); if (area->auth_type == OSPF_AUTH_NULL) vty_out(vty, "no authentication\n"); else if (area->auth_type == OSPF_AUTH_SIMPLE) vty_out(vty, "simple password authentication\n"); else if (area->auth_type == OSPF_AUTH_CRYPTOGRAPHIC) vty_out(vty, "message digest authentication\n"); if (!OSPF_IS_AREA_BACKBONE(area)) vty_out(vty, " Number of full virtual adjacencies going through" " this area: %d\n", area->full_vls); /* Show SPF calculation times. */ vty_out(vty, " SPF algorithm executed %d times\n", area->spf_calculation); /* Show number of LSA. */ vty_out(vty, " Number of LSA %ld\n", area->lsdb->total); vty_out(vty, " Number of router LSA %ld. Checksum Sum 0x%08x\n", ospf_lsdb_count(area->lsdb, OSPF_ROUTER_LSA), ospf_lsdb_checksum(area->lsdb, OSPF_ROUTER_LSA)); vty_out(vty, " Number of network LSA %ld. Checksum Sum 0x%08x\n", ospf_lsdb_count(area->lsdb, OSPF_NETWORK_LSA), ospf_lsdb_checksum(area->lsdb, OSPF_NETWORK_LSA)); vty_out(vty, " Number of summary LSA %ld. Checksum Sum 0x%08x\n", ospf_lsdb_count(area->lsdb, OSPF_SUMMARY_LSA), ospf_lsdb_checksum(area->lsdb, OSPF_SUMMARY_LSA)); vty_out(vty, " Number of ASBR summary LSA %ld. Checksum Sum 0x%08x\n", ospf_lsdb_count(area->lsdb, OSPF_ASBR_SUMMARY_LSA), ospf_lsdb_checksum(area->lsdb, OSPF_ASBR_SUMMARY_LSA)); vty_out(vty, " Number of NSSA LSA %ld. Checksum Sum 0x%08x\n", ospf_lsdb_count(area->lsdb, OSPF_AS_NSSA_LSA), ospf_lsdb_checksum(area->lsdb, OSPF_AS_NSSA_LSA)); } if (use_json) { json_object_int_add( json_area, "lsaOpaqueLinkNumber", ospf_lsdb_count(area->lsdb, OSPF_OPAQUE_LINK_LSA)); json_object_int_add( json_area, "lsaOpaqueLinkChecksum", ospf_lsdb_checksum(area->lsdb, OSPF_OPAQUE_LINK_LSA)); json_object_int_add( json_area, "lsaOpaqueAreaNumber", ospf_lsdb_count(area->lsdb, OSPF_OPAQUE_AREA_LSA)); json_object_int_add( json_area, "lsaOpaqueAreaChecksum", ospf_lsdb_checksum(area->lsdb, OSPF_OPAQUE_AREA_LSA)); } else { vty_out(vty, " Number of opaque link LSA %ld. Checksum Sum 0x%08x\n", ospf_lsdb_count(area->lsdb, OSPF_OPAQUE_LINK_LSA), ospf_lsdb_checksum(area->lsdb, OSPF_OPAQUE_LINK_LSA)); vty_out(vty, " Number of opaque area LSA %ld. Checksum Sum 0x%08x\n", ospf_lsdb_count(area->lsdb, OSPF_OPAQUE_AREA_LSA), ospf_lsdb_checksum(area->lsdb, OSPF_OPAQUE_AREA_LSA)); } if (use_json) json_object_object_add(json_areas, inet_ntoa(area->area_id), json_area); else vty_out(vty, "\n"); } static int show_ip_ospf_common(struct vty *vty, struct ospf *ospf, json_object *json, uint8_t use_vrf) { struct listnode *node, *nnode; struct ospf_area *area; struct timeval result; char timebuf[OSPF_TIME_DUMP_SIZE]; json_object *json_vrf = NULL; json_object *json_areas = NULL; if (json) { if (use_vrf) json_vrf = json_object_new_object(); else json_vrf = json; json_areas = json_object_new_object(); } if (ospf->instance) { if (json) { json_object_int_add(json, "ospfInstance", ospf->instance); } else { vty_out(vty, "\nOSPF Instance: %d\n\n", ospf->instance); } } ospf_show_vrf_name(ospf, vty, json_vrf, use_vrf); /* Show Router ID. */ if (json) { json_object_string_add(json_vrf, "routerId", inet_ntoa(ospf->router_id)); } else { vty_out(vty, " OSPF Routing Process, Router ID: %s\n", inet_ntoa(ospf->router_id)); } /* Graceful shutdown */ if (ospf->t_deferred_shutdown) { if (json) { long time_store; time_store = monotime_until( &ospf->t_deferred_shutdown->u.sands, NULL) / 1000LL; json_object_int_add(json_vrf, "deferredShutdownMsecs", time_store); } else { vty_out(vty, " Deferred shutdown in progress, %s remaining\n", ospf_timer_dump(ospf->t_deferred_shutdown, timebuf, sizeof(timebuf))); } } /* Show capability. */ if (json) { json_object_boolean_true_add(json_vrf, "tosRoutesOnly"); json_object_boolean_true_add(json_vrf, "rfc2328Conform"); if (CHECK_FLAG(ospf->config, OSPF_RFC1583_COMPATIBLE)) { json_object_boolean_true_add(json_vrf, "rfc1583Compatibility"); } } else { vty_out(vty, " Supports only single TOS (TOS0) routes\n"); vty_out(vty, " This implementation conforms to RFC2328\n"); vty_out(vty, " RFC1583Compatibility flag is %s\n", CHECK_FLAG(ospf->config, OSPF_RFC1583_COMPATIBLE) ? "enabled" : "disabled"); } if (json) { if (CHECK_FLAG(ospf->config, OSPF_OPAQUE_CAPABLE)) { json_object_boolean_true_add(json_vrf, "opaqueCapable"); } } else { vty_out(vty, " OpaqueCapability flag is %s\n", CHECK_FLAG(ospf->config, OSPF_OPAQUE_CAPABLE) ? "enabled" : "disabled"); } /* Show stub-router configuration */ if (ospf->stub_router_startup_time != OSPF_STUB_ROUTER_UNCONFIGURED || ospf->stub_router_shutdown_time != OSPF_STUB_ROUTER_UNCONFIGURED) { if (json) { json_object_boolean_true_add(json_vrf, "stubAdvertisement"); if (ospf->stub_router_startup_time != OSPF_STUB_ROUTER_UNCONFIGURED) json_object_int_add( json_vrf, "postStartEnabledSecs", ospf->stub_router_startup_time); if (ospf->stub_router_shutdown_time != OSPF_STUB_ROUTER_UNCONFIGURED) json_object_int_add( json_vrf, "preShutdownEnabledSecs", ospf->stub_router_shutdown_time); } else { vty_out(vty, " Stub router advertisement is configured\n"); if (ospf->stub_router_startup_time != OSPF_STUB_ROUTER_UNCONFIGURED) vty_out(vty, " Enabled for %us after start-up\n", ospf->stub_router_startup_time); if (ospf->stub_router_shutdown_time != OSPF_STUB_ROUTER_UNCONFIGURED) vty_out(vty, " Enabled for %us prior to full shutdown\n", ospf->stub_router_shutdown_time); } } /* Show SPF timers. */ if (json) { json_object_int_add(json_vrf, "spfScheduleDelayMsecs", ospf->spf_delay); json_object_int_add(json_vrf, "holdtimeMinMsecs", ospf->spf_holdtime); json_object_int_add(json_vrf, "holdtimeMaxMsecs", ospf->spf_max_holdtime); json_object_int_add(json_vrf, "holdtimeMultplier", ospf->spf_hold_multiplier); } else { vty_out(vty, " Initial SPF scheduling delay %d millisec(s)\n" " Minimum hold time between consecutive SPFs %d millisec(s)\n" " Maximum hold time between consecutive SPFs %d millisec(s)\n" " Hold time multiplier is currently %d\n", ospf->spf_delay, ospf->spf_holdtime, ospf->spf_max_holdtime, ospf->spf_hold_multiplier); } if (json) { if (ospf->ts_spf.tv_sec || ospf->ts_spf.tv_usec) { long time_store = 0; time_store = monotime_since(&ospf->ts_spf, NULL) / 1000LL; json_object_int_add(json_vrf, "spfLastExecutedMsecs", time_store); time_store = (1000 * ospf->ts_spf_duration.tv_sec) + (ospf->ts_spf_duration.tv_usec / 1000); json_object_int_add(json_vrf, "spfLastDurationMsecs", time_store); } else json_object_boolean_true_add(json_vrf, "spfHasNotRun"); } else { vty_out(vty, " SPF algorithm "); if (ospf->ts_spf.tv_sec || ospf->ts_spf.tv_usec) { monotime_since(&ospf->ts_spf, &result); vty_out(vty, "last executed %s ago\n", ospf_timeval_dump(&result, timebuf, sizeof(timebuf))); vty_out(vty, " Last SPF duration %s\n", ospf_timeval_dump(&ospf->ts_spf_duration, timebuf, sizeof(timebuf))); } else vty_out(vty, "has not been run\n"); } if (json) { if (ospf->t_spf_calc) { long time_store; time_store = monotime_until(&ospf->t_spf_calc->u.sands, NULL) / 1000LL; json_object_int_add(json_vrf, "spfTimerDueInMsecs", time_store); } json_object_int_add(json_vrf, "lsaMinIntervalMsecs", ospf->min_ls_interval); json_object_int_add(json_vrf, "lsaMinArrivalMsecs", ospf->min_ls_arrival); /* Show write multiplier values */ json_object_int_add(json_vrf, "writeMultiplier", ospf->write_oi_count); /* Show refresh parameters. */ json_object_int_add(json_vrf, "refreshTimerMsecs", ospf->lsa_refresh_interval * 1000); } else { vty_out(vty, " SPF timer %s%s\n", (ospf->t_spf_calc ? "due in " : "is "), ospf_timer_dump(ospf->t_spf_calc, timebuf, sizeof(timebuf))); vty_out(vty, " LSA minimum interval %d msecs\n", ospf->min_ls_interval); vty_out(vty, " LSA minimum arrival %d msecs\n", ospf->min_ls_arrival); /* Show write multiplier values */ vty_out(vty, " Write Multiplier set to %d \n", ospf->write_oi_count); /* Show refresh parameters. */ vty_out(vty, " Refresh timer %d secs\n", ospf->lsa_refresh_interval); } /* Show ABR/ASBR flags. */ if (CHECK_FLAG(ospf->flags, OSPF_FLAG_ABR)) { if (json) json_object_string_add( json_vrf, "abrType", ospf_abr_type_descr_str[ospf->abr_type]); else vty_out(vty, " This router is an ABR, ABR type is: %s\n", ospf_abr_type_descr_str[ospf->abr_type]); } if (CHECK_FLAG(ospf->flags, OSPF_FLAG_ASBR)) { if (json) json_object_string_add( json_vrf, "asbrRouter", "injectingExternalRoutingInformation"); else vty_out(vty, " This router is an ASBR " "(injecting external routing information)\n"); } /* Show Number of AS-external-LSAs. */ if (json) { json_object_int_add( json_vrf, "lsaExternalCounter", ospf_lsdb_count(ospf->lsdb, OSPF_AS_EXTERNAL_LSA)); json_object_int_add( json_vrf, "lsaExternalChecksum", ospf_lsdb_checksum(ospf->lsdb, OSPF_AS_EXTERNAL_LSA)); } else { vty_out(vty, " Number of external LSA %ld. Checksum Sum 0x%08x\n", ospf_lsdb_count(ospf->lsdb, OSPF_AS_EXTERNAL_LSA), ospf_lsdb_checksum(ospf->lsdb, OSPF_AS_EXTERNAL_LSA)); } if (json) { json_object_int_add( json_vrf, "lsaAsopaqueCounter", ospf_lsdb_count(ospf->lsdb, OSPF_OPAQUE_AS_LSA)); json_object_int_add( json_vrf, "lsaAsOpaqueChecksum", ospf_lsdb_checksum(ospf->lsdb, OSPF_OPAQUE_AS_LSA)); } else { vty_out(vty, " Number of opaque AS LSA %ld. Checksum Sum 0x%08x\n", ospf_lsdb_count(ospf->lsdb, OSPF_OPAQUE_AS_LSA), ospf_lsdb_checksum(ospf->lsdb, OSPF_OPAQUE_AS_LSA)); } /* Show number of areas attached. */ if (json) json_object_int_add(json_vrf, "attachedAreaCounter", listcount(ospf->areas)); else vty_out(vty, " Number of areas attached to this router: %d\n", listcount(ospf->areas)); if (CHECK_FLAG(ospf->config, OSPF_LOG_ADJACENCY_CHANGES)) { if (CHECK_FLAG(ospf->config, OSPF_LOG_ADJACENCY_DETAIL)) { if (json) json_object_boolean_true_add( json_vrf, "adjacencyChangesLoggedAll"); else vty_out(vty, " All adjacency changes are logged\n"); } else { if (json) json_object_boolean_true_add( json_vrf, "adjacencyChangesLogged"); else vty_out(vty, " Adjacency changes are logged\n"); } } /* Show each area status. */ for (ALL_LIST_ELEMENTS(ospf->areas, node, nnode, area)) show_ip_ospf_area(vty, area, json_areas, json ? 1 : 0); if (json) { if (use_vrf) { json_object_object_add(json_vrf, "areas", json_areas); if (ospf->vrf_id == VRF_DEFAULT) json_object_object_add(json, "default", json_vrf); else json_object_object_add(json, ospf->name, json_vrf); } else { json_object_object_add(json, "areas", json_areas); } } else vty_out(vty, "\n"); return CMD_SUCCESS; } DEFUN (show_ip_ospf, show_ip_ospf_cmd, "show ip ospf [vrf ] [json]", SHOW_STR IP_STR "OSPF information\n" VRF_CMD_HELP_STR "All VRFs\n" JSON_STR) { struct ospf *ospf; bool uj = use_json(argc, argv); struct listnode *node = NULL; char *vrf_name = NULL; bool all_vrf = false; int ret = CMD_SUCCESS; int inst = 0; int idx_vrf = 0; json_object *json = NULL; uint8_t use_vrf = 0; if (listcount(om->ospf) == 0) return CMD_SUCCESS; OSPF_FIND_VRF_ARGS(argv, argc, idx_vrf, vrf_name, all_vrf); if (uj) json = json_object_new_object(); /* vrf input is provided could be all or specific vrf*/ if (vrf_name) { bool ospf_output = false; use_vrf = 1; if (all_vrf) { for (ALL_LIST_ELEMENTS_RO(om->ospf, node, ospf)) { if (!ospf->oi_running) continue; ospf_output = true; ret = show_ip_ospf_common(vty, ospf, json, use_vrf); } if (uj) { vty_out(vty, "%s\n", json_object_to_json_string_ext( json, JSON_C_TO_STRING_PRETTY)); json_object_free(json); } else if (!ospf_output) vty_out(vty, "%% OSPF instance not found\n"); return ret; } ospf = ospf_lookup_by_inst_name(inst, vrf_name); if ((ospf == NULL) || !ospf->oi_running) { if (uj) { vty_out(vty, "%s\n", json_object_to_json_string_ext( json, JSON_C_TO_STRING_PRETTY)); json_object_free(json); } else vty_out(vty, "%% OSPF instance not found\n"); return CMD_SUCCESS; } } else { ospf = ospf_lookup_by_vrf_id(VRF_DEFAULT); /* Display default ospf (instance 0) info */ if (ospf == NULL || !ospf->oi_running) { if (uj) { vty_out(vty, "%s\n", json_object_to_json_string_ext( json, JSON_C_TO_STRING_PRETTY)); json_object_free(json); } else vty_out(vty, "%% OSPF instance not found\n"); return CMD_SUCCESS; } } if (ospf) { show_ip_ospf_common(vty, ospf, json, use_vrf); if (uj) vty_out(vty, "%s\n", json_object_to_json_string_ext( json, JSON_C_TO_STRING_PRETTY)); } if (uj) json_object_free(json); return ret; } DEFUN (show_ip_ospf_instance, show_ip_ospf_instance_cmd, "show ip ospf (1-65535) [json]", SHOW_STR IP_STR "OSPF information\n" "Instance ID\n" JSON_STR) { int idx_number = 3; struct ospf *ospf; unsigned short instance = 0; bool uj = use_json(argc, argv); int ret = CMD_SUCCESS; json_object *json = NULL; instance = strtoul(argv[idx_number]->arg, NULL, 10); ospf = ospf_lookup_instance(instance); if (ospf == NULL) return CMD_NOT_MY_INSTANCE; if (!ospf->oi_running) return CMD_SUCCESS; if (uj) json = json_object_new_object(); ret = show_ip_ospf_common(vty, ospf, json, 0); if (uj) { vty_out(vty, "%s\n", json_object_to_json_string_ext( json, JSON_C_TO_STRING_PRETTY)); json_object_free(json); } return ret; } static void show_ip_ospf_interface_sub(struct vty *vty, struct ospf *ospf, struct interface *ifp, json_object *json_interface_sub, bool use_json) { int is_up; struct ospf_neighbor *nbr; struct route_node *rn; uint32_t bandwidth = ifp->bandwidth ? ifp->bandwidth : ifp->speed; /* Is interface up? */ if (use_json) { is_up = if_is_operative(ifp); if (is_up) json_object_boolean_true_add(json_interface_sub, "ifUp"); else json_object_boolean_false_add(json_interface_sub, "ifDown"); json_object_int_add(json_interface_sub, "ifIndex", ifp->ifindex); json_object_int_add(json_interface_sub, "mtuBytes", ifp->mtu); json_object_int_add(json_interface_sub, "bandwidthMbit", bandwidth); json_object_string_add(json_interface_sub, "ifFlags", if_flag_dump(ifp->flags)); } else { vty_out(vty, "%s is %s\n", ifp->name, ((is_up = if_is_operative(ifp)) ? "up" : "down")); vty_out(vty, " ifindex %u, MTU %u bytes, BW %u Mbit %s\n", ifp->ifindex, ifp->mtu, bandwidth, if_flag_dump(ifp->flags)); } /* Is interface OSPF enabled? */ if (use_json) { if (ospf_oi_count(ifp) == 0) { json_object_boolean_false_add(json_interface_sub, "ospfEnabled"); return; } else if (!is_up) { json_object_boolean_false_add(json_interface_sub, "ospfRunning"); return; } else json_object_boolean_true_add(json_interface_sub, "ospfEnabled"); } else { if (ospf_oi_count(ifp) == 0) { vty_out(vty, " OSPF not enabled on this interface\n"); return; } else if (!is_up) { vty_out(vty, " OSPF is enabled, but not running on this interface\n"); return; } } for (rn = route_top(IF_OIFS(ifp)); rn; rn = route_next(rn)) { struct ospf_interface *oi = rn->info; if (oi == NULL) continue; if (CHECK_FLAG(oi->connected->flags, ZEBRA_IFA_UNNUMBERED)) { if (use_json) json_object_boolean_true_add(json_interface_sub, "ifUnnumbered"); else vty_out(vty, " This interface is UNNUMBERED,"); } else { struct in_addr dest; const char *dstr; /* Show OSPF interface information. */ if (use_json) { json_object_string_add( json_interface_sub, "ipAddress", inet_ntoa(oi->address->u.prefix4)); json_object_int_add(json_interface_sub, "ipAddressPrefixlen", oi->address->prefixlen); } else vty_out(vty, " Internet Address %s/%d,", inet_ntoa(oi->address->u.prefix4), oi->address->prefixlen); /* For Vlinks, showing the peer address is * probably more informative than the local * interface that is being used */ if (oi->type == OSPF_IFTYPE_VIRTUALLINK) { dstr = "Peer"; dest = oi->vl_data->peer_addr; } else if (CONNECTED_PEER(oi->connected) && oi->connected->destination) { dstr = "Peer"; dest = oi->connected->destination->u.prefix4; } else { dstr = "Broadcast"; dest.s_addr = ipv4_broadcast_addr( oi->connected->address->u.prefix4.s_addr, oi->connected->address->prefixlen); } if (use_json) { json_object_string_add( json_interface_sub, "ospfIfType", dstr); if (oi->type == OSPF_IFTYPE_VIRTUALLINK) json_object_string_add( json_interface_sub, "vlinkPeer", inet_ntoa(dest)); else json_object_string_add( json_interface_sub, "localIfUsed", inet_ntoa(dest)); } else vty_out(vty, " %s %s,", dstr, inet_ntoa(dest)); } if (use_json) { json_object_string_add(json_interface_sub, "area", ospf_area_desc_string(oi->area)); if (OSPF_IF_PARAM(oi, mtu_ignore)) json_object_boolean_true_add( json_interface_sub, "mtuMismatchDetect"); json_object_string_add(json_interface_sub, "routerId", inet_ntoa(ospf->router_id)); json_object_string_add(json_interface_sub, "networkType", ospf_network_type_str[oi->type]); json_object_int_add(json_interface_sub, "cost", oi->output_cost); json_object_int_add( json_interface_sub, "transmitDelaySecs", OSPF_IF_PARAM(oi, transmit_delay)); json_object_string_add(json_interface_sub, "state", lookup_msg(ospf_ism_state_msg, oi->state, NULL)); json_object_int_add(json_interface_sub, "priority", PRIORITY(oi)); } else { vty_out(vty, " Area %s\n", ospf_area_desc_string(oi->area)); vty_out(vty, " MTU mismatch detection: %s\n", OSPF_IF_PARAM(oi, mtu_ignore) ? "disabled" : "enabled"); vty_out(vty, " Router ID %s, Network Type %s, Cost: %d\n", inet_ntoa(ospf->router_id), ospf_network_type_str[oi->type], oi->output_cost); vty_out(vty, " Transmit Delay is %d sec, State %s, Priority %d\n", OSPF_IF_PARAM(oi, transmit_delay), lookup_msg(ospf_ism_state_msg, oi->state, NULL), PRIORITY(oi)); } /* Show DR information. */ if (DR(oi).s_addr == 0) { if (!use_json) vty_out(vty, " No backup designated router on this network\n"); } else { nbr = ospf_nbr_lookup_by_addr(oi->nbrs, &BDR(oi)); if (nbr == NULL) { if (!use_json) vty_out(vty, " No backup designated router on this network\n"); } else { if (use_json) { json_object_string_add( json_interface_sub, "bdrId", inet_ntoa(nbr->router_id)); json_object_string_add( json_interface_sub, "bdrAddress", inet_ntoa(nbr->address.u .prefix4)); } else { vty_out(vty, " Backup Designated Router (ID) %s,", inet_ntoa(nbr->router_id)); vty_out(vty, " Interface Address %s\n", inet_ntoa(nbr->address.u .prefix4)); } } } /* Next network-LSA sequence number we'll use, if we're elected * DR */ if (oi->params && ntohl(oi->params->network_lsa_seqnum) != OSPF_INITIAL_SEQUENCE_NUMBER) { if (use_json) json_object_int_add( json_interface_sub, "networkLsaSequence", ntohl(oi->params->network_lsa_seqnum)); else vty_out(vty, " Saved Network-LSA sequence number 0x%x\n", ntohl(oi->params->network_lsa_seqnum)); } if (use_json) { if (OI_MEMBER_CHECK(oi, MEMBER_ALLROUTERS) || OI_MEMBER_CHECK(oi, MEMBER_DROUTERS)) { if (OI_MEMBER_CHECK(oi, MEMBER_ALLROUTERS)) json_object_boolean_true_add( json_interface_sub, "mcastMemberOspfAllRouters"); if (OI_MEMBER_CHECK(oi, MEMBER_DROUTERS)) json_object_boolean_true_add( json_interface_sub, "mcastMemberOspfDesignatedRouters"); } } else { vty_out(vty, " Multicast group memberships:"); if (OI_MEMBER_CHECK(oi, MEMBER_ALLROUTERS) || OI_MEMBER_CHECK(oi, MEMBER_DROUTERS)) { if (OI_MEMBER_CHECK(oi, MEMBER_ALLROUTERS)) vty_out(vty, " OSPFAllRouters"); if (OI_MEMBER_CHECK(oi, MEMBER_DROUTERS)) vty_out(vty, " OSPFDesignatedRouters"); } else vty_out(vty, " "); vty_out(vty, "\n"); } if (use_json) { if (OSPF_IF_PARAM(oi, fast_hello) == 0) json_object_int_add( json_interface_sub, "timerMsecs", OSPF_IF_PARAM(oi, v_hello) * 1000); else json_object_int_add( json_interface_sub, "timerMsecs", 1000 / OSPF_IF_PARAM(oi, fast_hello)); json_object_int_add(json_interface_sub, "timerDeadSecs", OSPF_IF_PARAM(oi, v_wait)); json_object_int_add(json_interface_sub, "timerWaitSecs", OSPF_IF_PARAM(oi, v_wait)); json_object_int_add( json_interface_sub, "timerRetransmitSecs", OSPF_IF_PARAM(oi, retransmit_interval)); } else { vty_out(vty, " Timer intervals configured,"); vty_out(vty, " Hello "); if (OSPF_IF_PARAM(oi, fast_hello) == 0) vty_out(vty, "%ds,", OSPF_IF_PARAM(oi, v_hello)); else vty_out(vty, "%dms,", 1000 / OSPF_IF_PARAM(oi, fast_hello)); vty_out(vty, " Dead %ds, Wait %ds, Retransmit %d\n", OSPF_IF_PARAM(oi, v_wait), OSPF_IF_PARAM(oi, v_wait), OSPF_IF_PARAM(oi, retransmit_interval)); } if (OSPF_IF_PASSIVE_STATUS(oi) == OSPF_IF_ACTIVE) { char timebuf[OSPF_TIME_DUMP_SIZE]; if (use_json) { long time_store = 0; if (oi->t_hello) time_store = monotime_until( &oi->t_hello->u.sands, NULL) / 1000LL; json_object_int_add(json_interface_sub, "timerHelloInMsecs", time_store); } else vty_out(vty, " Hello due in %s\n", ospf_timer_dump(oi->t_hello, timebuf, sizeof(timebuf))); } else /* passive-interface is set */ { if (use_json) json_object_boolean_true_add( json_interface_sub, "timerPassiveIface"); else vty_out(vty, " No Hellos (Passive interface)\n"); } if (use_json) { json_object_int_add(json_interface_sub, "nbrCount", ospf_nbr_count(oi, 0)); json_object_int_add(json_interface_sub, "nbrAdjacentCount", ospf_nbr_count(oi, NSM_Full)); } else vty_out(vty, " Neighbor Count is %d, Adjacent neighbor count is %d\n", ospf_nbr_count(oi, 0), ospf_nbr_count(oi, NSM_Full)); ospf_bfd_interface_show(vty, ifp, json_interface_sub, use_json); } } static int show_ip_ospf_interface_common(struct vty *vty, struct ospf *ospf, char *intf_name, uint8_t use_vrf, json_object *json, bool use_json) { struct interface *ifp; struct vrf *vrf = vrf_lookup_by_id(ospf->vrf_id); json_object *json_vrf = NULL; json_object *json_interface_sub = NULL, *json_interface = NULL; if (use_json) { if (use_vrf) json_vrf = json_object_new_object(); else json_vrf = json; json_interface = json_object_new_object(); } if (ospf->instance) { if (use_json) json_object_int_add(json, "ospfInstance", ospf->instance); else vty_out(vty, "\nOSPF Instance: %d\n\n", ospf->instance); } ospf_show_vrf_name(ospf, vty, json_vrf, use_vrf); if (intf_name == NULL) { /* Show All Interfaces.*/ FOR_ALL_INTERFACES (vrf, ifp) { if (ospf_oi_count(ifp)) { if (use_json) { json_interface_sub = json_object_new_object(); } show_ip_ospf_interface_sub(vty, ospf, ifp, json_interface_sub, use_json); if (use_json) { json_object_object_add( json_interface, ifp->name, json_interface_sub); } } } if (use_json) json_object_object_add(json_vrf, "interfaces", json_interface); } else { /* Interface name is specified. */ ifp = if_lookup_by_name(intf_name, ospf->vrf_id); if (ifp == NULL) { if (use_json) json_object_boolean_true_add(json_vrf, "noSuchIface"); else vty_out(vty, "No such interface name\n"); } else { if (use_json) { json_interface_sub = json_object_new_object(); json_interface = json_object_new_object(); } show_ip_ospf_interface_sub( vty, ospf, ifp, json_interface_sub, use_json); if (use_json) { json_object_object_add(json_interface, ifp->name, json_interface_sub); json_object_object_add(json_vrf, "interfaces", json_interface); } } } if (use_json) { if (use_vrf) { if (ospf->vrf_id == VRF_DEFAULT) json_object_object_add(json, "default", json_vrf); else json_object_object_add(json, ospf->name, json_vrf); } } else vty_out(vty, "\n"); return CMD_SUCCESS; } static void show_ip_ospf_interface_traffic_sub(struct vty *vty, struct ospf_interface *oi, json_object *json_interface_sub, bool use_json) { if (use_json) { json_object_int_add(json_interface_sub, "ifIndex", oi->ifp->ifindex); json_object_int_add(json_interface_sub, "helloIn", oi->hello_in); json_object_int_add(json_interface_sub, "helloOut", oi->hello_out); json_object_int_add(json_interface_sub, "dbDescIn", oi->db_desc_in); json_object_int_add(json_interface_sub, "dbDescOut", oi->db_desc_out); json_object_int_add(json_interface_sub, "lsReqIn", oi->ls_req_in); json_object_int_add(json_interface_sub, "lsReqOut", oi->ls_req_out); json_object_int_add(json_interface_sub, "lsUpdIn", oi->ls_upd_in); json_object_int_add(json_interface_sub, "lsUpdOut", oi->ls_upd_out); json_object_int_add(json_interface_sub, "lsAckIn", oi->ls_ack_in); json_object_int_add(json_interface_sub, "lsAckOut", oi->ls_ack_out); } else { vty_out(vty, "%-10s %8u/%-8u %7u/%-7u %7u/%-7u %7u/%-7u %7u/%-7u\n", oi->ifp->name, oi->hello_in, oi->hello_out, oi->db_desc_in, oi->db_desc_out, oi->ls_req_in, oi->ls_req_out, oi->ls_upd_in, oi->ls_upd_out, oi->ls_ack_in, oi->ls_ack_out); } } /* OSPFv2 Packet Counters */ static int show_ip_ospf_interface_traffic_common( struct vty *vty, struct ospf *ospf, char *intf_name, json_object *json, int display_once, uint8_t use_vrf, bool use_json) { struct vrf *vrf = NULL; struct interface *ifp = NULL; json_object *json_vrf = NULL; json_object *json_interface_sub = NULL; if (!use_json && !display_once) { vty_out(vty, "\n"); vty_out(vty, "%-12s%-17s%-17s%-17s%-17s%-17s\n", "Interface", " HELLO", " DB-Desc", " LS-Req", " LS-Update", " LS-Ack"); vty_out(vty, "%-10s%-18s%-18s%-17s%-17s%-17s\n", "", " Rx/Tx", " Rx/Tx", " Rx/Tx", " Rx/Tx", " Rx/Tx"); vty_out(vty, "--------------------------------------------------------------------------------------------\n"); } else if (use_json) { if (use_vrf) json_vrf = json_object_new_object(); else json_vrf = json; } ospf_show_vrf_name(ospf, vty, json_vrf, use_vrf); if (intf_name == NULL) { vrf = vrf_lookup_by_id(ospf->vrf_id); FOR_ALL_INTERFACES (vrf, ifp) { struct route_node *rn; struct ospf_interface *oi; if (ospf_oi_count(ifp) == 0) continue; for (rn = route_top(IF_OIFS(ifp)); rn; rn = route_next(rn)) { oi = rn->info; if (oi == NULL) continue; if (use_json) { json_interface_sub = json_object_new_object(); } show_ip_ospf_interface_traffic_sub( vty, oi, json_interface_sub, use_json); if (use_json) { json_object_object_add( json_vrf, ifp->name, json_interface_sub); } } } } else { /* Interface name is specified. */ ifp = if_lookup_by_name(intf_name, ospf->vrf_id); if (ifp != NULL) { struct route_node *rn; struct ospf_interface *oi; if (ospf_oi_count(ifp) == 0) { vty_out(vty, " OSPF not enabled on this interface %s\n", ifp->name); return CMD_SUCCESS; } for (rn = route_top(IF_OIFS(ifp)); rn; rn = route_next(rn)) { oi = rn->info; if (use_json) { json_interface_sub = json_object_new_object(); } show_ip_ospf_interface_traffic_sub( vty, oi, json_interface_sub, use_json); if (use_json) { json_object_object_add( json_vrf, ifp->name, json_interface_sub); } } } } if (use_json) { if (use_vrf) { if (ospf->vrf_id == VRF_DEFAULT) json_object_object_add(json, "default", json_vrf); else json_object_object_add(json, ospf->name, json_vrf); } } else vty_out(vty, "\n"); return CMD_SUCCESS; } DEFUN (show_ip_ospf_interface, show_ip_ospf_interface_cmd, "show ip ospf [vrf ] interface [INTERFACE] [json]", SHOW_STR IP_STR "OSPF information\n" VRF_CMD_HELP_STR "All VRFs\n" "Interface information\n" "Interface name\n" JSON_STR) { struct ospf *ospf; bool uj = use_json(argc, argv); struct listnode *node = NULL; char *vrf_name = NULL, *intf_name = NULL; bool all_vrf = false; int ret = CMD_SUCCESS; int inst = 0; int idx_vrf = 0, idx_intf = 0; uint8_t use_vrf = 0; json_object *json = NULL; OSPF_FIND_VRF_ARGS(argv, argc, idx_vrf, vrf_name, all_vrf); if (argv_find(argv, argc, "INTERFACE", &idx_intf)) intf_name = argv[idx_intf]->arg; if (uj) json = json_object_new_object(); /* vrf input is provided could be all or specific vrf*/ if (vrf_name) { use_vrf = 1; if (all_vrf) { for (ALL_LIST_ELEMENTS_RO(om->ospf, node, ospf)) { if (!ospf->oi_running) continue; ret = show_ip_ospf_interface_common( vty, ospf, intf_name, use_vrf, json, uj); } if (uj) { vty_out(vty, "%s\n", json_object_to_json_string_ext( json, JSON_C_TO_STRING_PRETTY)); json_object_free(json); } else if (!ospf) vty_out(vty, "%% OSPF instance not found\n"); return ret; } ospf = ospf_lookup_by_inst_name(inst, vrf_name); if (ospf == NULL || !ospf->oi_running) { if (uj) { vty_out(vty, "%s\n", json_object_to_json_string_ext( json, JSON_C_TO_STRING_PRETTY)); json_object_free(json); } else vty_out(vty, "%% OSPF instance not found\n"); return CMD_SUCCESS; } ret = show_ip_ospf_interface_common(vty, ospf, intf_name, use_vrf, json, uj); } else { /* Display default ospf (instance 0) info */ ospf = ospf_lookup_by_vrf_id(VRF_DEFAULT); if (ospf == NULL || !ospf->oi_running) { if (uj) { vty_out(vty, "%s\n", json_object_to_json_string_ext( json, JSON_C_TO_STRING_PRETTY)); json_object_free(json); } else vty_out(vty, "%% OSPF instance not found\n"); return CMD_SUCCESS; } ret = show_ip_ospf_interface_common(vty, ospf, intf_name, use_vrf, json, uj); } if (uj) { vty_out(vty, "%s\n", json_object_to_json_string_ext( json, JSON_C_TO_STRING_PRETTY)); json_object_free(json); } return ret; } DEFUN (show_ip_ospf_instance_interface, show_ip_ospf_instance_interface_cmd, "show ip ospf (1-65535) interface [INTERFACE] [json]", SHOW_STR IP_STR "OSPF information\n" "Instance ID\n" "Interface information\n" "Interface name\n" JSON_STR) { int idx_number = 3; int idx_intf = 0; struct ospf *ospf; unsigned short instance = 0; bool uj = use_json(argc, argv); char *intf_name = NULL; int ret = CMD_SUCCESS; json_object *json = NULL; instance = strtoul(argv[idx_number]->arg, NULL, 10); ospf = ospf_lookup_instance(instance); if (ospf == NULL) return CMD_NOT_MY_INSTANCE; if (!ospf->oi_running) return CMD_SUCCESS; if (uj) json = json_object_new_object(); if (argv_find(argv, argc, "INTERFACE", &idx_intf)) intf_name = argv[idx_intf]->arg; ret = show_ip_ospf_interface_common(vty, ospf, intf_name, 0, json, uj); if (uj) { vty_out(vty, "%s\n", json_object_to_json_string_ext( json, JSON_C_TO_STRING_PRETTY)); json_object_free(json); } return ret; } DEFUN (show_ip_ospf_interface_traffic, show_ip_ospf_interface_traffic_cmd, "show ip ospf [vrf ] interface traffic [INTERFACE] [json]", SHOW_STR IP_STR "OSPF information\n" VRF_CMD_HELP_STR "All VRFs\n" "Interface information\n" "Protocol Packet counters\n" "Interface name\n" JSON_STR) { struct ospf *ospf = NULL; struct listnode *node = NULL; char *vrf_name = NULL, *intf_name = NULL; bool all_vrf = false; int inst = 0; int idx_vrf = 0, idx_intf = 0; bool uj = use_json(argc, argv); json_object *json = NULL; int ret = CMD_SUCCESS; int display_once = 0; uint8_t use_vrf = 0; OSPF_FIND_VRF_ARGS(argv, argc, idx_vrf, vrf_name, all_vrf); if (argv_find(argv, argc, "INTERFACE", &idx_intf)) intf_name = argv[idx_intf]->arg; if (uj) json = json_object_new_object(); if (vrf_name) { use_vrf = 1; if (all_vrf) { for (ALL_LIST_ELEMENTS_RO(om->ospf, node, ospf)) { if (!ospf->oi_running) continue; ret = show_ip_ospf_interface_traffic_common( vty, ospf, intf_name, json, display_once, use_vrf, uj); display_once = 1; } if (uj) { vty_out(vty, "%s\n", json_object_to_json_string_ext( json, JSON_C_TO_STRING_PRETTY)); json_object_free(json); } return ret; } ospf = ospf_lookup_by_inst_name(inst, vrf_name); if (ospf == NULL || !ospf->oi_running) { if (uj) json_object_free(json); return CMD_SUCCESS; } ret = show_ip_ospf_interface_traffic_common( vty, ospf, intf_name, json, display_once, use_vrf, uj); } else { ospf = ospf_lookup_by_vrf_id(VRF_DEFAULT); if (ospf == NULL || !ospf->oi_running) { if (uj) json_object_free(json); return CMD_SUCCESS; } ret = show_ip_ospf_interface_traffic_common( vty, ospf, intf_name, json, display_once, use_vrf, uj); } if (uj) { vty_out(vty, "%s\n", json_object_to_json_string_ext( json, JSON_C_TO_STRING_PRETTY)); json_object_free(json); } return ret; } static void show_ip_ospf_neighbour_header(struct vty *vty) { vty_out(vty, "\n%-15s %3s %-15s %9s %-15s %-20s %5s %5s %5s\n", "Neighbor ID", "Pri", "State", "Dead Time", "Address", "Interface", "RXmtL", "RqstL", "DBsmL"); } static void show_ip_ospf_neighbor_sub(struct vty *vty, struct ospf_interface *oi, json_object *json, bool use_json) { struct route_node *rn; struct ospf_neighbor *nbr, *prev_nbr = NULL; char msgbuf[16]; char timebuf[OSPF_TIME_DUMP_SIZE]; json_object *json_neighbor = NULL, *json_neigh_array = NULL; for (rn = route_top(oi->nbrs); rn; rn = route_next(rn)) { if ((nbr = rn->info)) { /* Do not show myself. */ if (nbr == oi->nbr_self) continue; /* Down state is not shown. */ if (nbr->state == NSM_Down) continue; if (use_json) { char neigh_str[INET_ADDRSTRLEN]; if (prev_nbr && !IPV4_ADDR_SAME(&prev_nbr->src, &nbr->src)) { /* Start new neigh list */ json_neigh_array = NULL; } if (nbr->state == NSM_Attempt && nbr->router_id.s_addr == 0) strlcpy(neigh_str, "neighbor", sizeof(neigh_str)); else strlcpy(neigh_str, inet_ntoa(nbr->router_id), sizeof(neigh_str)); json_object_object_get_ex(json, neigh_str, &json_neigh_array); if (!json_neigh_array) { json_neigh_array = json_object_new_array(); json_object_object_add( json, neigh_str, json_neigh_array); } json_neighbor = json_object_new_object(); ospf_nbr_state_message(nbr, msgbuf, 16); long time_store; time_store = monotime_until( &nbr->t_inactivity->u.sands, NULL) / 1000LL; json_object_int_add(json_neighbor, "priority", nbr->priority); json_object_string_add(json_neighbor, "state", msgbuf); json_object_int_add(json_neighbor, "deadTimeMsecs", time_store); json_object_string_add(json_neighbor, "address", inet_ntoa(nbr->src)); json_object_string_add(json_neighbor, "ifaceName", IF_NAME(oi)); json_object_int_add( json_neighbor, "retransmitCounter", ospf_ls_retransmit_count(nbr)); json_object_int_add(json_neighbor, "requestCounter", ospf_ls_request_count(nbr)); json_object_int_add(json_neighbor, "dbSummaryCounter", ospf_db_summary_count(nbr)); json_object_array_add(json_neigh_array, json_neighbor); } else { ospf_nbr_state_message(nbr, msgbuf, 16); if (nbr->state == NSM_Attempt && nbr->router_id.s_addr == 0) vty_out(vty, "%-15s %3d %-15s ", "-", nbr->priority, msgbuf); else vty_out(vty, "%-15s %3d %-15s ", inet_ntoa(nbr->router_id), nbr->priority, msgbuf); vty_out(vty, "%9s ", ospf_timer_dump(nbr->t_inactivity, timebuf, sizeof(timebuf))); vty_out(vty, "%-15s ", inet_ntoa(nbr->src)); vty_out(vty, "%-20s %5ld %5ld %5d\n", IF_NAME(oi), ospf_ls_retransmit_count(nbr), ospf_ls_request_count(nbr), ospf_db_summary_count(nbr)); } prev_nbr = nbr; } } } static int show_ip_ospf_neighbor_common(struct vty *vty, struct ospf *ospf, json_object *json, bool use_json, uint8_t use_vrf) { struct ospf_interface *oi; struct listnode *node; json_object *json_vrf = NULL; json_object *json_nbr_sub = NULL; if (use_json) { if (use_vrf) json_vrf = json_object_new_object(); else json_vrf = json; json_nbr_sub = json_object_new_object(); } if (ospf->instance) { if (use_json) json_object_int_add(json, "ospfInstance", ospf->instance); else vty_out(vty, "\nOSPF Instance: %d\n\n", ospf->instance); } ospf_show_vrf_name(ospf, vty, json_vrf, use_vrf); if (!use_json) show_ip_ospf_neighbour_header(vty); for (ALL_LIST_ELEMENTS_RO(ospf->oiflist, node, oi)) { if (ospf_interface_neighbor_count(oi) == 0) continue; show_ip_ospf_neighbor_sub(vty, oi, json_nbr_sub, use_json); } if (use_json) { json_object_object_add(json_vrf, "neighbors", json_nbr_sub); if (use_vrf) { if (ospf->vrf_id == VRF_DEFAULT) json_object_object_add(json, "default", json_vrf); else json_object_object_add(json, ospf->name, json_vrf); } } else vty_out(vty, "\n"); return CMD_SUCCESS; } DEFUN (show_ip_ospf_neighbor, show_ip_ospf_neighbor_cmd, "show ip ospf [vrf ] neighbor [json]", SHOW_STR IP_STR "OSPF information\n" VRF_CMD_HELP_STR "All VRFs\n" "Neighbor list\n" JSON_STR) { struct ospf *ospf; bool uj = use_json(argc, argv); struct listnode *node = NULL; char *vrf_name = NULL; bool all_vrf = false; int ret = CMD_SUCCESS; int inst = 0; int idx_vrf = 0; uint8_t use_vrf = 0; json_object *json = NULL; OSPF_FIND_VRF_ARGS(argv, argc, idx_vrf, vrf_name, all_vrf); if (uj) json = json_object_new_object(); /* vrf input is provided could be all or specific vrf*/ if (vrf_name) { use_vrf = 1; if (all_vrf) { for (ALL_LIST_ELEMENTS_RO(om->ospf, node, ospf)) { if (!ospf->oi_running) continue; ret = show_ip_ospf_neighbor_common( vty, ospf, json, uj, use_vrf); } if (uj) { vty_out(vty, "%s\n", json_object_to_json_string_ext( json, JSON_C_TO_STRING_PRETTY)); json_object_free(json); } else if (!ospf) vty_out(vty, "OSPF instance not found\n"); return ret; } ospf = ospf_lookup_by_inst_name(inst, vrf_name); if (ospf == NULL || !ospf->oi_running) { if (uj) { vty_out(vty, "%s\n", json_object_to_json_string_ext( json, JSON_C_TO_STRING_PRETTY)); json_object_free(json); } else vty_out(vty, "%% OSPF instance not found\n"); return CMD_SUCCESS; } } else { /* Display default ospf (instance 0) info */ ospf = ospf_lookup_by_vrf_id(VRF_DEFAULT); if (ospf == NULL || !ospf->oi_running) { if (uj) { vty_out(vty, "%s\n", json_object_to_json_string_ext( json, JSON_C_TO_STRING_PRETTY)); json_object_free(json); } else vty_out(vty, "%% OSPF instance not found\n"); return CMD_SUCCESS; } } if (ospf) { ret = show_ip_ospf_neighbor_common(vty, ospf, json, uj, use_vrf); if (uj) { vty_out(vty, "%s\n", json_object_to_json_string_ext( json, JSON_C_TO_STRING_PRETTY)); } } if (uj) json_object_free(json); return ret; } DEFUN (show_ip_ospf_instance_neighbor, show_ip_ospf_instance_neighbor_cmd, "show ip ospf (1-65535) neighbor [json]", SHOW_STR IP_STR "OSPF information\n" "Instance ID\n" "Neighbor list\n" JSON_STR) { int idx_number = 3; struct ospf *ospf; unsigned short instance = 0; bool uj = use_json(argc, argv); json_object *json = NULL; int ret = CMD_SUCCESS; instance = strtoul(argv[idx_number]->arg, NULL, 10); ospf = ospf_lookup_instance(instance); if (ospf == NULL) return CMD_NOT_MY_INSTANCE; if (!ospf->oi_running) return CMD_SUCCESS; if (uj) json = json_object_new_object(); ret = show_ip_ospf_neighbor_common(vty, ospf, json, uj, 0); if (uj) { vty_out(vty, "%s\n", json_object_to_json_string_ext( json, JSON_C_TO_STRING_PRETTY)); json_object_free(json); } return ret; } static int show_ip_ospf_neighbor_all_common(struct vty *vty, struct ospf *ospf, json_object *json, bool use_json, uint8_t use_vrf) { struct listnode *node; struct ospf_interface *oi; json_object *json_vrf = NULL; json_object *json_neighbor_sub = NULL; if (use_json) { if (use_vrf) json_vrf = json_object_new_object(); else json_vrf = json; json_neighbor_sub = json_object_new_object(); } ospf_show_vrf_name(ospf, vty, json_vrf, use_vrf); if (!use_json) show_ip_ospf_neighbour_header(vty); if (ospf->instance) { if (use_json) json_object_int_add(json_vrf, "ospfInstance", ospf->instance); else vty_out(vty, "\nOSPF Instance: %d\n\n", ospf->instance); } for (ALL_LIST_ELEMENTS_RO(ospf->oiflist, node, oi)) { struct listnode *nbr_node; struct ospf_nbr_nbma *nbr_nbma; show_ip_ospf_neighbor_sub(vty, oi, json_vrf, use_json); /* print Down neighbor status */ for (ALL_LIST_ELEMENTS_RO(oi->nbr_nbma, nbr_node, nbr_nbma)) { if (nbr_nbma->nbr == NULL || nbr_nbma->nbr->state == NSM_Down) { if (use_json) { json_object_int_add(json_neighbor_sub, "nbrNbmaPriority", nbr_nbma->priority); json_object_boolean_true_add( json_neighbor_sub, "nbrNbmaDown"); json_object_string_add( json_neighbor_sub, "nbrNbmaIfaceName", IF_NAME(oi)); json_object_int_add( json_neighbor_sub, "nbrNbmaRetransmitCounter", 0); json_object_int_add( json_neighbor_sub, "nbrNbmaRequestCounter", 0); json_object_int_add( json_neighbor_sub, "nbrNbmaDbSummaryCounter", 0); json_object_object_add( json_vrf, inet_ntoa(nbr_nbma->addr), json_neighbor_sub); } else { vty_out(vty, "%-15s %3d %-15s %9s ", "-", nbr_nbma->priority, "Down", "-"); vty_out(vty, "%-15s %-20s %5d %5d %5d\n", inet_ntoa(nbr_nbma->addr), IF_NAME(oi), 0, 0, 0); } } } } if (use_json) { if (use_vrf) { if (ospf->vrf_id == VRF_DEFAULT) json_object_object_add(json, "default", json_vrf); else json_object_object_add(json, ospf->name, json_vrf); } } else vty_out(vty, "\n"); return CMD_SUCCESS; } DEFUN (show_ip_ospf_neighbor_all, show_ip_ospf_neighbor_all_cmd, "show ip ospf [vrf ] neighbor all [json]", SHOW_STR IP_STR "OSPF information\n" VRF_CMD_HELP_STR "All VRFs\n" "Neighbor list\n" "include down status neighbor\n" JSON_STR) { struct ospf *ospf; bool uj = use_json(argc, argv); struct listnode *node = NULL; char *vrf_name = NULL; bool all_vrf = false; int ret = CMD_SUCCESS; int inst = 0; int idx_vrf = 0; uint8_t use_vrf = 0; json_object *json = NULL; OSPF_FIND_VRF_ARGS(argv, argc, idx_vrf, vrf_name, all_vrf); if (uj) json = json_object_new_object(); /* vrf input is provided could be all or specific vrf*/ if (vrf_name) { use_vrf = 1; if (all_vrf) { for (ALL_LIST_ELEMENTS_RO(om->ospf, node, ospf)) { if (!ospf->oi_running) continue; ret = show_ip_ospf_neighbor_all_common( vty, ospf, json, uj, use_vrf); } if (uj) { vty_out(vty, "%s\n", json_object_to_json_string_ext( json, JSON_C_TO_STRING_PRETTY)); json_object_free(json); } return ret; } ospf = ospf_lookup_by_inst_name(inst, vrf_name); if (ospf == NULL || !ospf->oi_running) { if (uj) json_object_free(json); return CMD_SUCCESS; } } else { /* Display default ospf (instance 0) info */ ospf = ospf_lookup_by_vrf_id(VRF_DEFAULT); if (ospf == NULL || !ospf->oi_running) { if (uj) json_object_free(json); return CMD_SUCCESS; } } if (ospf) { ret = show_ip_ospf_neighbor_all_common(vty, ospf, json, uj, use_vrf); if (uj) { vty_out(vty, "%s\n", json_object_to_json_string_ext( json, JSON_C_TO_STRING_PRETTY)); } } if (uj) json_object_free(json); return ret; } DEFUN (show_ip_ospf_instance_neighbor_all, show_ip_ospf_instance_neighbor_all_cmd, "show ip ospf (1-65535) neighbor all [json]", SHOW_STR IP_STR "OSPF information\n" "Instance ID\n" "Neighbor list\n" "include down status neighbor\n" JSON_STR) { int idx_number = 3; struct ospf *ospf; unsigned short instance = 0; bool uj = use_json(argc, argv); json_object *json = NULL; int ret = CMD_SUCCESS; instance = strtoul(argv[idx_number]->arg, NULL, 10); ospf = ospf_lookup_instance(instance); if (ospf == NULL) return CMD_NOT_MY_INSTANCE; if (!ospf->oi_running) return CMD_SUCCESS; if (uj) json = json_object_new_object(); ret = show_ip_ospf_neighbor_all_common(vty, ospf, json, uj, 0); if (uj) { vty_out(vty, "%s\n", json_object_to_json_string_ext( json, JSON_C_TO_STRING_PRETTY)); json_object_free(json); } return ret; } static int show_ip_ospf_neighbor_int_common(struct vty *vty, struct ospf *ospf, int arg_base, struct cmd_token **argv, bool use_json, uint8_t use_vrf) { struct interface *ifp; struct route_node *rn; json_object *json = NULL; if (use_json) json = json_object_new_object(); if (ospf->instance) { if (use_json) json_object_int_add(json, "ospfInstance", ospf->instance); else vty_out(vty, "\nOSPF Instance: %d\n\n", ospf->instance); } ospf_show_vrf_name(ospf, vty, json, use_vrf); ifp = if_lookup_by_name(argv[arg_base]->arg, ospf->vrf_id); if (!ifp) { if (use_json) json_object_boolean_true_add(json, "noSuchIface"); else vty_out(vty, "No such interface.\n"); return CMD_WARNING; } for (rn = route_top(IF_OIFS(ifp)); rn; rn = route_next(rn)) { struct ospf_interface *oi = rn->info; if (oi == NULL) continue; show_ip_ospf_neighbor_sub(vty, oi, json, use_json); } if (use_json) { vty_out(vty, "%s\n", json_object_to_json_string_ext( json, JSON_C_TO_STRING_PRETTY)); json_object_free(json); } else vty_out(vty, "\n"); return CMD_SUCCESS; } DEFUN (show_ip_ospf_neighbor_int, show_ip_ospf_neighbor_int_cmd, "show ip ospf [vrf ] neighbor IFNAME [json]", SHOW_STR IP_STR "OSPF information\n" VRF_CMD_HELP_STR "Neighbor list\n" "Interface name\n" JSON_STR) { struct ospf *ospf; int idx_ifname = 0; int idx_vrf = 0; bool uj = use_json(argc, argv); int ret = CMD_SUCCESS; struct interface *ifp = NULL; char *vrf_name = NULL; vrf_id_t vrf_id = VRF_DEFAULT; struct vrf *vrf = NULL; if (argv_find(argv, argc, "vrf", &idx_vrf)) vrf_name = argv[idx_vrf + 1]->arg; if (vrf_name && strmatch(vrf_name, VRF_DEFAULT_NAME)) vrf_name = NULL; if (vrf_name) { vrf = vrf_lookup_by_name(vrf_name); if (vrf) vrf_id = vrf->vrf_id; } ospf = ospf_lookup_by_vrf_id(vrf_id); if (!ospf || !ospf->oi_running) return ret; if (!uj) show_ip_ospf_neighbour_header(vty); argv_find(argv, argc, "IFNAME", &idx_ifname); ifp = if_lookup_by_name(argv[idx_ifname]->arg, vrf_id); if (!ifp) return ret; ret = show_ip_ospf_neighbor_int_common(vty, ospf, idx_ifname, argv, uj, 0); return ret; } DEFUN (show_ip_ospf_instance_neighbor_int, show_ip_ospf_instance_neighbor_int_cmd, "show ip ospf (1-65535) neighbor IFNAME [json]", SHOW_STR IP_STR "OSPF information\n" "Instance ID\n" "Neighbor list\n" "Interface name\n" JSON_STR) { int idx_number = 3; int idx_ifname = 5; struct ospf *ospf; unsigned short instance = 0; bool uj = use_json(argc, argv); if (!uj) show_ip_ospf_neighbour_header(vty); instance = strtoul(argv[idx_number]->arg, NULL, 10); ospf = ospf_lookup_instance(instance); if (ospf == NULL) return CMD_NOT_MY_INSTANCE; if (!ospf->oi_running) return CMD_SUCCESS; if (!uj) show_ip_ospf_neighbour_header(vty); return show_ip_ospf_neighbor_int_common(vty, ospf, idx_ifname, argv, uj, 0); } static void show_ip_ospf_nbr_nbma_detail_sub(struct vty *vty, struct ospf_interface *oi, struct ospf_nbr_nbma *nbr_nbma, bool use_json, json_object *json) { char timebuf[OSPF_TIME_DUMP_SIZE]; json_object *json_sub = NULL; if (use_json) json_sub = json_object_new_object(); else /* Show neighbor ID. */ vty_out(vty, " Neighbor %s,", "-"); /* Show interface address. */ if (use_json) json_object_string_add(json_sub, "ifaceAddress", inet_ntoa(nbr_nbma->addr)); else vty_out(vty, " interface address %s\n", inet_ntoa(nbr_nbma->addr)); /* Show Area ID. */ if (use_json) { json_object_string_add(json_sub, "areaId", ospf_area_desc_string(oi->area)); json_object_string_add(json_sub, "iface", IF_NAME(oi)); } else vty_out(vty, " In the area %s via interface %s\n", ospf_area_desc_string(oi->area), IF_NAME(oi)); /* Show neighbor priority and state. */ if (use_json) { json_object_int_add(json_sub, "nbrPriority", nbr_nbma->priority); json_object_string_add(json_sub, "nbrState", "down"); } else vty_out(vty, " Neighbor priority is %d, State is %s,", nbr_nbma->priority, "Down"); /* Show state changes. */ if (use_json) json_object_int_add(json_sub, "stateChangeCounter", nbr_nbma->state_change); else vty_out(vty, " %d state changes\n", nbr_nbma->state_change); /* Show PollInterval */ if (use_json) json_object_int_add(json_sub, "pollInterval", nbr_nbma->v_poll); else vty_out(vty, " Poll interval %d\n", nbr_nbma->v_poll); /* Show poll-interval timer. */ if (nbr_nbma->t_poll) { if (use_json) { long time_store; time_store = monotime_until(&nbr_nbma->t_poll->u.sands, NULL) / 1000LL; json_object_int_add(json_sub, "pollIntervalTimerDueMsec", time_store); } else vty_out(vty, " Poll timer due in %s\n", ospf_timer_dump(nbr_nbma->t_poll, timebuf, sizeof(timebuf))); } /* Show poll-interval timer thread. */ if (use_json) { if (nbr_nbma->t_poll != NULL) json_object_string_add(json_sub, "pollIntervalTimerThread", "on"); } else vty_out(vty, " Thread Poll Timer %s\n", nbr_nbma->t_poll != NULL ? "on" : "off"); if (use_json) json_object_object_add(json, "noNbrId", json_sub); } static void show_ip_ospf_neighbor_detail_sub(struct vty *vty, struct ospf_interface *oi, struct ospf_neighbor *nbr, struct ospf_neighbor *prev_nbr, json_object *json, bool use_json) { char timebuf[OSPF_TIME_DUMP_SIZE]; json_object *json_neigh = NULL, *json_neigh_array = NULL; char neigh_str[INET_ADDRSTRLEN] = {0}; if (use_json) { if (prev_nbr && !IPV4_ADDR_SAME(&prev_nbr->src, &nbr->src)) { json_neigh_array = NULL; } if (nbr->state == NSM_Attempt && nbr->router_id.s_addr == 0) strlcpy(neigh_str, "noNbrId", sizeof(neigh_str)); else strlcpy(neigh_str, inet_ntoa(nbr->router_id), sizeof(neigh_str)); json_object_object_get_ex(json, neigh_str, &json_neigh_array); if (!json_neigh_array) { json_neigh_array = json_object_new_array(); json_object_object_add(json, neigh_str, json_neigh_array); } json_neigh = json_object_new_object(); } else { /* Show neighbor ID. */ if (nbr->state == NSM_Attempt && nbr->router_id.s_addr == 0) vty_out(vty, " Neighbor %s,", "-"); else vty_out(vty, " Neighbor %s,", inet_ntoa(nbr->router_id)); } /* Show interface address. */ if (use_json) json_object_string_add(json_neigh, "ifaceAddress", inet_ntoa(nbr->address.u.prefix4)); else vty_out(vty, " interface address %s\n", inet_ntoa(nbr->address.u.prefix4)); /* Show Area ID. */ if (use_json) { json_object_string_add(json_neigh, "areaId", ospf_area_desc_string(oi->area)); json_object_string_add(json_neigh, "ifaceName", oi->ifp->name); } else vty_out(vty, " In the area %s via interface %s\n", ospf_area_desc_string(oi->area), oi->ifp->name); /* Show neighbor priority and state. */ if (use_json) { json_object_int_add(json_neigh, "nbrPriority", nbr->priority); json_object_string_add( json_neigh, "nbrState", lookup_msg(ospf_nsm_state_msg, nbr->state, NULL)); } else vty_out(vty, " Neighbor priority is %d, State is %s,", nbr->priority, lookup_msg(ospf_nsm_state_msg, nbr->state, NULL)); /* Show state changes. */ if (use_json) json_object_int_add(json_neigh, "stateChangeCounter", nbr->state_change); else vty_out(vty, " %d state changes\n", nbr->state_change); if (nbr->ts_last_progress.tv_sec || nbr->ts_last_progress.tv_usec) { struct timeval res; long time_store; time_store = monotime_since(&nbr->ts_last_progress, &res) / 1000LL; if (use_json) { json_object_int_add(json_neigh, "lastPrgrsvChangeMsec", time_store); } else { vty_out(vty, " Most recent state change statistics:\n"); vty_out(vty, " Progressive change %s ago\n", ospf_timeval_dump(&res, timebuf, sizeof(timebuf))); } } if (nbr->ts_last_regress.tv_sec || nbr->ts_last_regress.tv_usec) { struct timeval res; long time_store; time_store = monotime_since(&nbr->ts_last_regress, &res) / 1000LL; if (use_json) { json_object_int_add(json_neigh, "lastRegressiveChangeMsec", time_store); if (nbr->last_regress_str) json_object_string_add( json_neigh, "lastRegressiveChangeReason", nbr->last_regress_str); } else { vty_out(vty, " Regressive change %s ago, due to %s\n", ospf_timeval_dump(&res, timebuf, sizeof(timebuf)), (nbr->last_regress_str ? nbr->last_regress_str : "??")); } } /* Show Designated Rotuer ID. */ if (use_json) json_object_string_add(json_neigh, "routerDesignatedId", inet_ntoa(nbr->d_router)); else vty_out(vty, " DR is %s,", inet_ntoa(nbr->d_router)); /* Show Backup Designated Rotuer ID. */ if (use_json) json_object_string_add(json_neigh, "routerDesignatedBackupId", inet_ntoa(nbr->bd_router)); else vty_out(vty, " BDR is %s\n", inet_ntoa(nbr->bd_router)); /* Show options. */ if (use_json) { json_object_int_add(json_neigh, "optionsCounter", nbr->options); json_object_string_add(json_neigh, "optionsList", ospf_options_dump(nbr->options)); } else vty_out(vty, " Options %d %s\n", nbr->options, ospf_options_dump(nbr->options)); /* Show Router Dead interval timer. */ if (use_json) { if (nbr->t_inactivity) { long time_store; time_store = monotime_until(&nbr->t_inactivity->u.sands, NULL) / 1000LL; json_object_int_add(json_neigh, "routerDeadIntervalTimerDueMsec", time_store); } else json_object_int_add( json_neigh, "routerDeadIntervalTimerDueMsec", -1); } else vty_out(vty, " Dead timer due in %s\n", ospf_timer_dump(nbr->t_inactivity, timebuf, sizeof(timebuf))); /* Show Database Summary list. */ if (use_json) json_object_int_add(json_neigh, "databaseSummaryListCounter", ospf_db_summary_count(nbr)); else vty_out(vty, " Database Summary List %d\n", ospf_db_summary_count(nbr)); /* Show Link State Request list. */ if (use_json) json_object_int_add(json_neigh, "linkStateRequestListCounter", ospf_ls_request_count(nbr)); else vty_out(vty, " Link State Request List %ld\n", ospf_ls_request_count(nbr)); /* Show Link State Retransmission list. */ if (use_json) json_object_int_add(json_neigh, "linkStateRetransmissionListCounter", ospf_ls_retransmit_count(nbr)); else vty_out(vty, " Link State Retransmission List %ld\n", ospf_ls_retransmit_count(nbr)); /* Show inactivity timer thread. */ if (use_json) { if (nbr->t_inactivity != NULL) json_object_string_add(json_neigh, "threadInactivityTimer", "on"); } else vty_out(vty, " Thread Inactivity Timer %s\n", nbr->t_inactivity != NULL ? "on" : "off"); /* Show Database Description retransmission thread. */ if (use_json) { if (nbr->t_db_desc != NULL) json_object_string_add( json_neigh, "threadDatabaseDescriptionRetransmission", "on"); } else vty_out(vty, " Thread Database Description Retransmision %s\n", nbr->t_db_desc != NULL ? "on" : "off"); /* Show Link State Request Retransmission thread. */ if (use_json) { if (nbr->t_ls_req != NULL) json_object_string_add( json_neigh, "threadLinkStateRequestRetransmission", "on"); } else vty_out(vty, " Thread Link State Request Retransmission %s\n", nbr->t_ls_req != NULL ? "on" : "off"); /* Show Link State Update Retransmission thread. */ if (use_json) { if (nbr->t_ls_upd != NULL) json_object_string_add( json_neigh, "threadLinkStateUpdateRetransmission", "on"); } else vty_out(vty, " Thread Link State Update Retransmission %s\n\n", nbr->t_ls_upd != NULL ? "on" : "off"); ospf_bfd_show_info(vty, nbr->bfd_info, json_neigh, use_json, 0); if (use_json) json_object_array_add(json_neigh_array, json_neigh); } static int show_ip_ospf_neighbor_id_common(struct vty *vty, struct ospf *ospf, struct in_addr *router_id, bool use_json, uint8_t use_vrf) { struct listnode *node; struct ospf_neighbor *nbr; struct ospf_interface *oi; json_object *json = NULL; if (use_json) json = json_object_new_object(); if (ospf->instance) { if (use_json) json_object_int_add(json, "ospfInstance", ospf->instance); else vty_out(vty, "\nOSPF Instance: %d\n\n", ospf->instance); } ospf_show_vrf_name(ospf, vty, json, use_vrf); for (ALL_LIST_ELEMENTS_RO(ospf->oiflist, node, oi)) { if ((nbr = ospf_nbr_lookup_by_routerid(oi->nbrs, router_id))) { show_ip_ospf_neighbor_detail_sub(vty, oi, nbr, NULL, json, use_json); } } if (use_json) { vty_out(vty, "%s\n", json_object_to_json_string_ext( json, JSON_C_TO_STRING_PRETTY)); json_object_free(json); } else vty_out(vty, "\n"); return CMD_SUCCESS; } DEFPY (show_ip_ospf_neighbor_id, show_ip_ospf_neighbor_id_cmd, "show ip ospf neighbor A.B.C.D$router_id [json$json]", SHOW_STR IP_STR "OSPF information\n" "Neighbor list\n" "Neighbor ID\n" JSON_STR) { struct ospf *ospf; struct listnode *node; int ret = CMD_SUCCESS; for (ALL_LIST_ELEMENTS_RO(om->ospf, node, ospf)) { if (!ospf->oi_running) continue; ret = show_ip_ospf_neighbor_id_common(vty, ospf, &router_id, !!json, 0); } return ret; } DEFPY (show_ip_ospf_instance_neighbor_id, show_ip_ospf_instance_neighbor_id_cmd, "show ip ospf (1-65535)$instance neighbor A.B.C.D$router_id [json$json]", SHOW_STR IP_STR "OSPF information\n" "Instance ID\n" "Neighbor list\n" "Neighbor ID\n" JSON_STR) { struct ospf *ospf; ospf = ospf_lookup_instance(instance); if (ospf == NULL) return CMD_NOT_MY_INSTANCE; if (!ospf->oi_running) return CMD_SUCCESS; return show_ip_ospf_neighbor_id_common(vty, ospf, &router_id, !!json, 0); } static int show_ip_ospf_neighbor_detail_common(struct vty *vty, struct ospf *ospf, json_object *json, bool use_json, uint8_t use_vrf) { struct ospf_interface *oi; struct listnode *node; json_object *json_vrf = NULL; json_object *json_nbr_sub = NULL; if (use_json) { if (use_vrf) json_vrf = json_object_new_object(); else json_vrf = json; json_nbr_sub = json_object_new_object(); } if (ospf->instance) { if (use_json) json_object_int_add(json, "ospfInstance", ospf->instance); else vty_out(vty, "\nOSPF Instance: %d\n\n", ospf->instance); } ospf_show_vrf_name(ospf, vty, json_vrf, use_vrf); for (ALL_LIST_ELEMENTS_RO(ospf->oiflist, node, oi)) { struct route_node *rn; struct ospf_neighbor *nbr, *prev_nbr = NULL; for (rn = route_top(oi->nbrs); rn; rn = route_next(rn)) { if ((nbr = rn->info)) { if (nbr != oi->nbr_self) { if (nbr->state != NSM_Down) { show_ip_ospf_neighbor_detail_sub( vty, oi, nbr, prev_nbr, json_nbr_sub, use_json); } } prev_nbr = nbr; } } } if (use_json) { json_object_object_add(json_vrf, "neighbors", json_nbr_sub); if (use_vrf) { if (ospf->vrf_id == VRF_DEFAULT) json_object_object_add(json, "default", json_vrf); else json_object_object_add(json, ospf->name, json_vrf); } } else vty_out(vty, "\n"); return CMD_SUCCESS; } DEFUN (show_ip_ospf_neighbor_detail, show_ip_ospf_neighbor_detail_cmd, "show ip ospf [vrf ] neighbor detail [json]", SHOW_STR IP_STR "OSPF information\n" VRF_CMD_HELP_STR "All VRFs\n" "Neighbor list\n" "detail of all neighbors\n" JSON_STR) { struct ospf *ospf; bool uj = use_json(argc, argv); struct listnode *node = NULL; char *vrf_name = NULL; bool all_vrf = false; int ret = CMD_SUCCESS; int inst = 0; int idx_vrf = 0; uint8_t use_vrf = 0; json_object *json = NULL; OSPF_FIND_VRF_ARGS(argv, argc, idx_vrf, vrf_name, all_vrf); if (uj) json = json_object_new_object(); /* vrf input is provided could be all or specific vrf*/ if (vrf_name) { use_vrf = 1; if (all_vrf) { for (ALL_LIST_ELEMENTS_RO(om->ospf, node, ospf)) { if (!ospf->oi_running) continue; ret = show_ip_ospf_neighbor_detail_common( vty, ospf, json, uj, use_vrf); } if (uj) { vty_out(vty, "%s\n", json_object_to_json_string_ext( json, JSON_C_TO_STRING_PRETTY)); json_object_free(json); } return ret; } ospf = ospf_lookup_by_inst_name(inst, vrf_name); if (ospf == NULL || !ospf->oi_running) { if (uj) json_object_free(json); return CMD_SUCCESS; } } else { /* Display default ospf (instance 0) info */ ospf = ospf_lookup_by_vrf_id(VRF_DEFAULT); if (ospf == NULL || !ospf->oi_running) { if (uj) json_object_free(json); return CMD_SUCCESS; } } if (ospf) { ret = show_ip_ospf_neighbor_detail_common(vty, ospf, json, uj, use_vrf); if (uj) { vty_out(vty, "%s\n", json_object_to_json_string_ext( json, JSON_C_TO_STRING_PRETTY)); } } if (uj) json_object_free(json); return ret; } DEFUN (show_ip_ospf_instance_neighbor_detail, show_ip_ospf_instance_neighbor_detail_cmd, "show ip ospf (1-65535) neighbor detail [json]", SHOW_STR IP_STR "OSPF information\n" "Instance ID\n" "Neighbor list\n" "detail of all neighbors\n" JSON_STR) { int idx_number = 3; struct ospf *ospf; unsigned short instance = 0; bool uj = use_json(argc, argv); json_object *json = NULL; int ret = CMD_SUCCESS; instance = strtoul(argv[idx_number]->arg, NULL, 10); ospf = ospf_lookup_instance(instance); if (ospf == NULL) return CMD_NOT_MY_INSTANCE; if (!ospf->oi_running) return CMD_SUCCESS; if (uj) json = json_object_new_object(); ret = show_ip_ospf_neighbor_detail_common(vty, ospf, json, uj, 0); if (uj) { vty_out(vty, "%s\n", json_object_to_json_string_ext( json, JSON_C_TO_STRING_PRETTY)); json_object_free(json); } return ret; } static int show_ip_ospf_neighbor_detail_all_common(struct vty *vty, struct ospf *ospf, json_object *json, bool use_json, uint8_t use_vrf) { struct listnode *node; struct ospf_interface *oi; json_object *json_vrf = NULL; if (use_json) { if (use_vrf) json_vrf = json_object_new_object(); else json_vrf = json; } if (ospf->instance) { if (use_json) json_object_int_add(json, "ospfInstance", ospf->instance); else vty_out(vty, "\nOSPF Instance: %d\n\n", ospf->instance); } ospf_show_vrf_name(ospf, vty, json_vrf, use_vrf); for (ALL_LIST_ELEMENTS_RO(ospf->oiflist, node, oi)) { struct route_node *rn; struct ospf_neighbor *nbr, *prev_nbr = NULL; struct ospf_nbr_nbma *nbr_nbma; for (rn = route_top(oi->nbrs); rn; rn = route_next(rn)) { if ((nbr = rn->info)) { if (nbr != oi->nbr_self) if (nbr->state != NSM_Down) show_ip_ospf_neighbor_detail_sub( vty, oi, rn->info, prev_nbr, json_vrf, use_json); prev_nbr = nbr; } } if (oi->type == OSPF_IFTYPE_NBMA) { struct listnode *nd; for (ALL_LIST_ELEMENTS_RO(oi->nbr_nbma, nd, nbr_nbma)) { if (nbr_nbma->nbr == NULL || nbr_nbma->nbr->state == NSM_Down) show_ip_ospf_nbr_nbma_detail_sub( vty, oi, nbr_nbma, use_json, json_vrf); } } } if (use_json) { if (use_vrf) { if (ospf->vrf_id == VRF_DEFAULT) json_object_object_add(json, "default", json_vrf); else json_object_object_add(json, ospf->name, json_vrf); } } else { vty_out(vty, "\n"); } return CMD_SUCCESS; } DEFUN (show_ip_ospf_neighbor_detail_all, show_ip_ospf_neighbor_detail_all_cmd, "show ip ospf [vrf ] neighbor detail all [json]", SHOW_STR IP_STR "OSPF information\n" VRF_CMD_HELP_STR "All VRFs\n" "Neighbor list\n" "detail of all neighbors\n" "include down status neighbor\n" JSON_STR) { struct ospf *ospf; bool uj = use_json(argc, argv); struct listnode *node = NULL; char *vrf_name = NULL; bool all_vrf = false; int ret = CMD_SUCCESS; int inst = 0; int idx_vrf = 0; uint8_t use_vrf = 0; json_object *json = NULL; OSPF_FIND_VRF_ARGS(argv, argc, idx_vrf, vrf_name, all_vrf); if (uj) json = json_object_new_object(); /* vrf input is provided could be all or specific vrf*/ if (vrf_name) { use_vrf = 1; if (all_vrf) { for (ALL_LIST_ELEMENTS_RO(om->ospf, node, ospf)) { if (!ospf->oi_running) continue; ret = show_ip_ospf_neighbor_detail_all_common( vty, ospf, json, uj, use_vrf); } if (uj) { vty_out(vty, "%s\n", json_object_to_json_string_ext( json, JSON_C_TO_STRING_PRETTY)); json_object_free(json); } return ret; } ospf = ospf_lookup_by_inst_name(inst, vrf_name); if (ospf == NULL || !ospf->oi_running) { if (uj) json_object_free(json); return CMD_SUCCESS; } } else { /* Display default ospf (instance 0) info */ ospf = ospf_lookup_by_vrf_id(VRF_DEFAULT); if (ospf == NULL || !ospf->oi_running) { if (uj) json_object_free(json); return CMD_SUCCESS; } } if (ospf) { ret = show_ip_ospf_neighbor_detail_all_common(vty, ospf, json, uj, use_vrf); if (uj) { vty_out(vty, "%s\n", json_object_to_json_string_ext( json, JSON_C_TO_STRING_PRETTY)); } } if (uj) json_object_free(json); return ret; } DEFUN (show_ip_ospf_instance_neighbor_detail_all, show_ip_ospf_instance_neighbor_detail_all_cmd, "show ip ospf (1-65535) neighbor detail all [json]", SHOW_STR IP_STR "OSPF information\n" "Instance ID\n" "Neighbor list\n" "detail of all neighbors\n" "include down status neighbor\n" JSON_STR) { int idx_number = 3; struct ospf *ospf; unsigned short instance = 0; bool uj = use_json(argc, argv); json_object *json = NULL; int ret = CMD_SUCCESS; instance = strtoul(argv[idx_number]->arg, NULL, 10); ospf = ospf_lookup_instance(instance); if (ospf == NULL) return CMD_NOT_MY_INSTANCE; if (!ospf->oi_running) return CMD_SUCCESS; if (uj) json = json_object_new_object(); ret = show_ip_ospf_neighbor_detail_all_common(vty, ospf, json, uj, 0); if (uj) { vty_out(vty, "%s\n", json_object_to_json_string_ext( json, JSON_C_TO_STRING_PRETTY)); json_object_free(json); } return ret; } static int show_ip_ospf_neighbor_int_detail_common(struct vty *vty, struct ospf *ospf, int arg_base, struct cmd_token **argv, bool use_json) { struct ospf_interface *oi; struct interface *ifp; struct route_node *rn, *nrn; struct ospf_neighbor *nbr; json_object *json = NULL; if (use_json) json = json_object_new_object(); if (ospf->instance) { if (use_json) json_object_int_add(json, "ospfInstance", ospf->instance); else vty_out(vty, "\nOSPF Instance: %d\n\n", ospf->instance); } ifp = if_lookup_by_name(argv[arg_base]->arg, ospf->vrf_id); if (!ifp) { if (!use_json) vty_out(vty, "No such interface.\n"); else { vty_out(vty, "{}\n"); json_object_free(json); } return CMD_WARNING; } for (rn = route_top(IF_OIFS(ifp)); rn; rn = route_next(rn)) { if ((oi = rn->info)) { for (nrn = route_top(oi->nbrs); nrn; nrn = route_next(nrn)) { if ((nbr = nrn->info)) { if (nbr != oi->nbr_self) { if (nbr->state != NSM_Down) show_ip_ospf_neighbor_detail_sub( vty, oi, nbr, NULL, json, use_json); } } } } } if (use_json) { vty_out(vty, "%s\n", json_object_to_json_string_ext( json, JSON_C_TO_STRING_PRETTY)); json_object_free(json); } else vty_out(vty, "\n"); return CMD_SUCCESS; } DEFUN (show_ip_ospf_neighbor_int_detail, show_ip_ospf_neighbor_int_detail_cmd, "show ip ospf neighbor IFNAME detail [json]", SHOW_STR IP_STR "OSPF information\n" "Neighbor list\n" "Interface name\n" "detail of all neighbors\n" JSON_STR) { struct ospf *ospf; bool uj = use_json(argc, argv); struct listnode *node = NULL; int ret = CMD_SUCCESS; bool ospf_output = false; for (ALL_LIST_ELEMENTS_RO(om->ospf, node, ospf)) { if (!ospf->oi_running) continue; ospf_output = true; ret = show_ip_ospf_neighbor_int_detail_common(vty, ospf, 4, argv, uj); } if (!ospf_output) vty_out(vty, "%% OSPF instance not found\n"); return ret; } DEFUN (show_ip_ospf_instance_neighbor_int_detail, show_ip_ospf_instance_neighbor_int_detail_cmd, "show ip ospf (1-65535) neighbor IFNAME detail [json]", SHOW_STR IP_STR "OSPF information\n" "Instance ID\n" "Neighbor list\n" "Interface name\n" "detail of all neighbors\n" JSON_STR) { int idx_number = 3; int idx_ifname = 5; struct ospf *ospf; unsigned short instance = 0; bool uj = use_json(argc, argv); instance = strtoul(argv[idx_number]->arg, NULL, 10); ospf = ospf_lookup_instance(instance); if (ospf == NULL) return CMD_NOT_MY_INSTANCE; if (!ospf->oi_running) return CMD_SUCCESS; return show_ip_ospf_neighbor_int_detail_common(vty, ospf, idx_ifname, argv, uj); } /* Show functions */ static int show_lsa_summary(struct vty *vty, struct ospf_lsa *lsa, int self) { struct router_lsa *rl; struct summary_lsa *sl; struct as_external_lsa *asel; struct prefix_ipv4 p; if (lsa != NULL) /* If self option is set, check LSA self flag. */ if (self == 0 || IS_LSA_SELF(lsa)) { /* LSA common part show. */ vty_out(vty, "%-15s ", inet_ntoa(lsa->data->id)); vty_out(vty, "%-15s %4d 0x%08lx 0x%04x", inet_ntoa(lsa->data->adv_router), LS_AGE(lsa), (unsigned long)ntohl(lsa->data->ls_seqnum), ntohs(lsa->data->checksum)); /* LSA specific part show. */ switch (lsa->data->type) { case OSPF_ROUTER_LSA: rl = (struct router_lsa *)lsa->data; vty_out(vty, " %-d", ntohs(rl->links)); break; case OSPF_SUMMARY_LSA: sl = (struct summary_lsa *)lsa->data; p.family = AF_INET; p.prefix = sl->header.id; p.prefixlen = ip_masklen(sl->mask); apply_mask_ipv4(&p); vty_out(vty, " %s/%d", inet_ntoa(p.prefix), p.prefixlen); break; case OSPF_AS_EXTERNAL_LSA: case OSPF_AS_NSSA_LSA: asel = (struct as_external_lsa *)lsa->data; p.family = AF_INET; p.prefix = asel->header.id; p.prefixlen = ip_masklen(asel->mask); apply_mask_ipv4(&p); vty_out(vty, " %s %s/%d [0x%lx]", IS_EXTERNAL_METRIC(asel->e[0].tos) ? "E2" : "E1", inet_ntoa(p.prefix), p.prefixlen, (unsigned long)ntohl( asel->e[0].route_tag)); break; case OSPF_NETWORK_LSA: case OSPF_ASBR_SUMMARY_LSA: case OSPF_OPAQUE_LINK_LSA: case OSPF_OPAQUE_AREA_LSA: case OSPF_OPAQUE_AS_LSA: default: break; } vty_out(vty, "\n"); } return 0; } static const char *show_database_desc[] = { "unknown", "Router Link States", "Net Link States", "Summary Link States", "ASBR-Summary Link States", "AS External Link States", "Group Membership LSA", "NSSA-external Link States", "Type-8 LSA", "Link-Local Opaque-LSA", "Area-Local Opaque-LSA", "AS-external Opaque-LSA", }; static const char *show_database_header[] = { "", "Link ID ADV Router Age Seq# CkSum Link count", "Link ID ADV Router Age Seq# CkSum", "Link ID ADV Router Age Seq# CkSum Route", "Link ID ADV Router Age Seq# CkSum", "Link ID ADV Router Age Seq# CkSum Route", " --- header for Group Member ----", "Link ID ADV Router Age Seq# CkSum Route", " --- type-8 ---", "Opaque-Type/Id ADV Router Age Seq# CkSum", "Opaque-Type/Id ADV Router Age Seq# CkSum", "Opaque-Type/Id ADV Router Age Seq# CkSum", }; static void show_ip_ospf_database_header(struct vty *vty, struct ospf_lsa *lsa) { struct router_lsa *rlsa = (struct router_lsa *)lsa->data; vty_out(vty, " LS age: %d\n", LS_AGE(lsa)); vty_out(vty, " Options: 0x%-2x : %s\n", lsa->data->options, ospf_options_dump(lsa->data->options)); vty_out(vty, " LS Flags: 0x%-2x %s\n", lsa->flags, ((lsa->flags & OSPF_LSA_LOCAL_XLT) ? "(Translated from Type-7)" : "")); if (lsa->data->type == OSPF_ROUTER_LSA) { vty_out(vty, " Flags: 0x%x", rlsa->flags); if (rlsa->flags) vty_out(vty, " :%s%s%s%s", IS_ROUTER_LSA_BORDER(rlsa) ? " ABR" : "", IS_ROUTER_LSA_EXTERNAL(rlsa) ? " ASBR" : "", IS_ROUTER_LSA_VIRTUAL(rlsa) ? " VL-endpoint" : "", IS_ROUTER_LSA_SHORTCUT(rlsa) ? " Shortcut" : ""); vty_out(vty, "\n"); } vty_out(vty, " LS Type: %s\n", lookup_msg(ospf_lsa_type_msg, lsa->data->type, NULL)); vty_out(vty, " Link State ID: %s %s\n", inet_ntoa(lsa->data->id), lookup_msg(ospf_link_state_id_type_msg, lsa->data->type, NULL)); vty_out(vty, " Advertising Router: %s\n", inet_ntoa(lsa->data->adv_router)); vty_out(vty, " LS Seq Number: %08lx\n", (unsigned long)ntohl(lsa->data->ls_seqnum)); vty_out(vty, " Checksum: 0x%04x\n", ntohs(lsa->data->checksum)); vty_out(vty, " Length: %d\n\n", ntohs(lsa->data->length)); } const char *link_type_desc[] = { "(null)", "another Router (point-to-point)", "a Transit Network", "Stub Network", "a Virtual Link", }; const char *link_id_desc[] = { "(null)", "Neighboring Router ID", "Designated Router address", "Net", "Neighboring Router ID", }; const char *link_data_desc[] = { "(null)", "Router Interface address", "Router Interface address", "Network Mask", "Router Interface address", }; /* Show router-LSA each Link information. */ static void show_ip_ospf_database_router_links(struct vty *vty, struct router_lsa *rl) { int len, type; unsigned int i; len = ntohs(rl->header.length) - 4; for (i = 0; i < ntohs(rl->links) && len > 0; len -= 12, i++) { type = rl->link[i].type; vty_out(vty, " Link connected to: %s\n", link_type_desc[type]); vty_out(vty, " (Link ID) %s: %s\n", link_id_desc[type], inet_ntoa(rl->link[i].link_id)); vty_out(vty, " (Link Data) %s: %s\n", link_data_desc[type], inet_ntoa(rl->link[i].link_data)); vty_out(vty, " Number of TOS metrics: 0\n"); vty_out(vty, " TOS 0 Metric: %d\n", ntohs(rl->link[i].metric)); vty_out(vty, "\n"); } } /* Show router-LSA detail information. */ static int show_router_lsa_detail(struct vty *vty, struct ospf_lsa *lsa) { if (lsa != NULL) { struct router_lsa *rl = (struct router_lsa *)lsa->data; show_ip_ospf_database_header(vty, lsa); vty_out(vty, " Number of Links: %d\n\n", ntohs(rl->links)); show_ip_ospf_database_router_links(vty, rl); vty_out(vty, "\n"); } return 0; } /* Show network-LSA detail information. */ static int show_network_lsa_detail(struct vty *vty, struct ospf_lsa *lsa) { int length, i; if (lsa != NULL) { struct network_lsa *nl = (struct network_lsa *)lsa->data; show_ip_ospf_database_header(vty, lsa); vty_out(vty, " Network Mask: /%d\n", ip_masklen(nl->mask)); length = ntohs(lsa->data->length) - OSPF_LSA_HEADER_SIZE - 4; for (i = 0; length > 0; i++, length -= 4) vty_out(vty, " Attached Router: %s\n", inet_ntoa(nl->routers[i])); vty_out(vty, "\n"); } return 0; } /* Show summary-LSA detail information. */ static int show_summary_lsa_detail(struct vty *vty, struct ospf_lsa *lsa) { if (lsa != NULL) { struct summary_lsa *sl = (struct summary_lsa *)lsa->data; show_ip_ospf_database_header(vty, lsa); vty_out(vty, " Network Mask: /%d\n", ip_masklen(sl->mask)); vty_out(vty, " TOS: 0 Metric: %d\n", GET_METRIC(sl->metric)); vty_out(vty, "\n"); } return 0; } /* Show summary-ASBR-LSA detail information. */ static int show_summary_asbr_lsa_detail(struct vty *vty, struct ospf_lsa *lsa) { if (lsa != NULL) { struct summary_lsa *sl = (struct summary_lsa *)lsa->data; show_ip_ospf_database_header(vty, lsa); vty_out(vty, " Network Mask: /%d\n", ip_masklen(sl->mask)); vty_out(vty, " TOS: 0 Metric: %d\n", GET_METRIC(sl->metric)); vty_out(vty, "\n"); } return 0; } /* Show AS-external-LSA detail information. */ static int show_as_external_lsa_detail(struct vty *vty, struct ospf_lsa *lsa) { if (lsa != NULL) { struct as_external_lsa *al = (struct as_external_lsa *)lsa->data; show_ip_ospf_database_header(vty, lsa); vty_out(vty, " Network Mask: /%d\n", ip_masklen(al->mask)); vty_out(vty, " Metric Type: %s\n", IS_EXTERNAL_METRIC(al->e[0].tos) ? "2 (Larger than any link state path)" : "1"); vty_out(vty, " TOS: 0\n"); vty_out(vty, " Metric: %d\n", GET_METRIC(al->e[0].metric)); vty_out(vty, " Forward Address: %s\n", inet_ntoa(al->e[0].fwd_addr)); vty_out(vty, " External Route Tag: %" ROUTE_TAG_PRI "\n\n", (route_tag_t)ntohl(al->e[0].route_tag)); } return 0; } #if 0 static int show_as_external_lsa_stdvty (struct ospf_lsa *lsa) { struct as_external_lsa *al = (struct as_external_lsa *) lsa->data; /* show_ip_ospf_database_header (vty, lsa); */ zlog_debug( " Network Mask: /%d%s", ip_masklen (al->mask), "\n"); zlog_debug( " Metric Type: %s%s", IS_EXTERNAL_METRIC (al->e[0].tos) ? "2 (Larger than any link state path)" : "1", "\n"); zlog_debug( " TOS: 0%s", "\n"); zlog_debug( " Metric: %d%s", GET_METRIC (al->e[0].metric), "\n"); zlog_debug( " Forward Address: %s%s", inet_ntoa (al->e[0].fwd_addr), "\n"); zlog_debug( " External Route Tag: %"ROUTE_TAG_PRI"%s%s", (route_tag_t)ntohl (al->e[0].route_tag), "\n", "\n"); return 0; } #endif /* Show AS-NSSA-LSA detail information. */ static int show_as_nssa_lsa_detail(struct vty *vty, struct ospf_lsa *lsa) { if (lsa != NULL) { struct as_external_lsa *al = (struct as_external_lsa *)lsa->data; show_ip_ospf_database_header(vty, lsa); vty_out(vty, " Network Mask: /%d\n", ip_masklen(al->mask)); vty_out(vty, " Metric Type: %s\n", IS_EXTERNAL_METRIC(al->e[0].tos) ? "2 (Larger than any link state path)" : "1"); vty_out(vty, " TOS: 0\n"); vty_out(vty, " Metric: %d\n", GET_METRIC(al->e[0].metric)); vty_out(vty, " NSSA: Forward Address: %s\n", inet_ntoa(al->e[0].fwd_addr)); vty_out(vty, " External Route Tag: %" ROUTE_TAG_PRI "\n\n", (route_tag_t)ntohl(al->e[0].route_tag)); } return 0; } static int show_func_dummy(struct vty *vty, struct ospf_lsa *lsa) { return 0; } static int show_opaque_lsa_detail(struct vty *vty, struct ospf_lsa *lsa) { if (lsa != NULL) { show_ip_ospf_database_header(vty, lsa); show_opaque_info_detail(vty, lsa); vty_out(vty, "\n"); } return 0; } int (*show_function[])(struct vty *, struct ospf_lsa *) = { NULL, show_router_lsa_detail, show_network_lsa_detail, show_summary_lsa_detail, show_summary_asbr_lsa_detail, show_as_external_lsa_detail, show_func_dummy, show_as_nssa_lsa_detail, /* almost same as external */ NULL, /* type-8 */ show_opaque_lsa_detail, show_opaque_lsa_detail, show_opaque_lsa_detail, }; static void show_lsa_prefix_set(struct vty *vty, struct prefix_ls *lp, struct in_addr *id, struct in_addr *adv_router) { memset(lp, 0, sizeof(struct prefix_ls)); lp->family = 0; if (id == NULL) lp->prefixlen = 0; else if (adv_router == NULL) { lp->prefixlen = 32; lp->id = *id; } else { lp->prefixlen = 64; lp->id = *id; lp->adv_router = *adv_router; } } static void show_lsa_detail_proc(struct vty *vty, struct route_table *rt, struct in_addr *id, struct in_addr *adv_router) { struct prefix_ls lp; struct route_node *rn, *start; struct ospf_lsa *lsa; show_lsa_prefix_set(vty, &lp, id, adv_router); start = route_node_get(rt, (struct prefix *)&lp); if (start) { route_lock_node(start); for (rn = start; rn; rn = route_next_until(rn, start)) if ((lsa = rn->info)) { if (show_function[lsa->data->type] != NULL) show_function[lsa->data->type](vty, lsa); } route_unlock_node(start); } } /* Show detail LSA information -- if id is NULL then show all LSAs. */ static void show_lsa_detail(struct vty *vty, struct ospf *ospf, int type, struct in_addr *id, struct in_addr *adv_router) { struct listnode *node; struct ospf_area *area; switch (type) { case OSPF_AS_EXTERNAL_LSA: case OSPF_OPAQUE_AS_LSA: vty_out(vty, " %s \n\n", show_database_desc[type]); show_lsa_detail_proc(vty, AS_LSDB(ospf, type), id, adv_router); break; default: for (ALL_LIST_ELEMENTS_RO(ospf->areas, node, area)) { vty_out(vty, "\n %s (Area %s)\n\n", show_database_desc[type], ospf_area_desc_string(area)); show_lsa_detail_proc(vty, AREA_LSDB(area, type), id, adv_router); } break; } } static void show_lsa_detail_adv_router_proc(struct vty *vty, struct route_table *rt, struct in_addr *adv_router) { struct route_node *rn; struct ospf_lsa *lsa; for (rn = route_top(rt); rn; rn = route_next(rn)) if ((lsa = rn->info)) if (IPV4_ADDR_SAME(adv_router, &lsa->data->adv_router)) { if (CHECK_FLAG(lsa->flags, OSPF_LSA_LOCAL_XLT)) continue; if (show_function[lsa->data->type] != NULL) show_function[lsa->data->type](vty, lsa); } } /* Show detail LSA information. */ static void show_lsa_detail_adv_router(struct vty *vty, struct ospf *ospf, int type, struct in_addr *adv_router) { struct listnode *node; struct ospf_area *area; switch (type) { case OSPF_AS_EXTERNAL_LSA: case OSPF_OPAQUE_AS_LSA: vty_out(vty, " %s \n\n", show_database_desc[type]); show_lsa_detail_adv_router_proc(vty, AS_LSDB(ospf, type), adv_router); break; default: for (ALL_LIST_ELEMENTS_RO(ospf->areas, node, area)) { vty_out(vty, "\n %s (Area %s)\n\n", show_database_desc[type], ospf_area_desc_string(area)); show_lsa_detail_adv_router_proc( vty, AREA_LSDB(area, type), adv_router); } break; } } static void show_ip_ospf_database_summary(struct vty *vty, struct ospf *ospf, int self) { struct ospf_lsa *lsa; struct route_node *rn; struct ospf_area *area; struct listnode *node; int type; for (ALL_LIST_ELEMENTS_RO(ospf->areas, node, area)) { for (type = OSPF_MIN_LSA; type < OSPF_MAX_LSA; type++) { switch (type) { case OSPF_AS_EXTERNAL_LSA: case OSPF_OPAQUE_AS_LSA: continue; default: break; } if (ospf_lsdb_count_self(area->lsdb, type) > 0 || (!self && ospf_lsdb_count(area->lsdb, type) > 0)) { vty_out(vty, " %s (Area %s)\n\n", show_database_desc[type], ospf_area_desc_string(area)); vty_out(vty, "%s\n", show_database_header[type]); LSDB_LOOP (AREA_LSDB(area, type), rn, lsa) show_lsa_summary(vty, lsa, self); vty_out(vty, "\n"); } } } for (type = OSPF_MIN_LSA; type < OSPF_MAX_LSA; type++) { switch (type) { case OSPF_AS_EXTERNAL_LSA: case OSPF_OPAQUE_AS_LSA: break; default: continue; } if (ospf_lsdb_count_self(ospf->lsdb, type) || (!self && ospf_lsdb_count(ospf->lsdb, type))) { vty_out(vty, " %s\n\n", show_database_desc[type]); vty_out(vty, "%s\n", show_database_header[type]); LSDB_LOOP (AS_LSDB(ospf, type), rn, lsa) show_lsa_summary(vty, lsa, self); vty_out(vty, "\n"); } } vty_out(vty, "\n"); } static void show_ip_ospf_database_maxage(struct vty *vty, struct ospf *ospf) { struct route_node *rn; vty_out(vty, "\n MaxAge Link States:\n\n"); for (rn = route_top(ospf->maxage_lsa); rn; rn = route_next(rn)) { struct ospf_lsa *lsa; if ((lsa = rn->info) != NULL) { vty_out(vty, "Link type: %d\n", lsa->data->type); vty_out(vty, "Link State ID: %s\n", inet_ntoa(lsa->data->id)); vty_out(vty, "Advertising Router: %s\n", inet_ntoa(lsa->data->adv_router)); vty_out(vty, "LSA lock count: %d\n", lsa->lock); vty_out(vty, "\n"); } } } #define OSPF_LSA_TYPE_NSSA_DESC "NSSA external link state\n" #define OSPF_LSA_TYPE_NSSA_CMD_STR "|nssa-external" #define OSPF_LSA_TYPE_OPAQUE_LINK_DESC "Link local Opaque-LSA\n" #define OSPF_LSA_TYPE_OPAQUE_AREA_DESC "Link area Opaque-LSA\n" #define OSPF_LSA_TYPE_OPAQUE_AS_DESC "Link AS Opaque-LSA\n" #define OSPF_LSA_TYPE_OPAQUE_CMD_STR "|opaque-link|opaque-area|opaque-as" #define OSPF_LSA_TYPES_DESC \ "ASBR summary link states\n" \ "External link states\n" \ "Network link states\n" \ "Router link states\n" \ "Network summary link states\n" OSPF_LSA_TYPE_NSSA_DESC \ OSPF_LSA_TYPE_OPAQUE_LINK_DESC OSPF_LSA_TYPE_OPAQUE_AREA_DESC \ OSPF_LSA_TYPE_OPAQUE_AS_DESC static int show_ip_ospf_database_common(struct vty *vty, struct ospf *ospf, int arg_base, int argc, struct cmd_token **argv, uint8_t use_vrf) { int idx_type = 4; int type, ret; struct in_addr id, adv_router; if (ospf->instance) vty_out(vty, "\nOSPF Instance: %d\n", ospf->instance); ospf_show_vrf_name(ospf, vty, NULL, use_vrf); vty_out(vty, "\n OSPF Router with ID (%s)\n\n", inet_ntoa(ospf->router_id)); /* Show all LSA. */ if (argc == arg_base + 4) { show_ip_ospf_database_summary(vty, ospf, 0); return CMD_SUCCESS; } /* Set database type to show. */ if (strncmp(argv[arg_base + idx_type]->text, "r", 1) == 0) type = OSPF_ROUTER_LSA; else if (strncmp(argv[arg_base + idx_type]->text, "ne", 2) == 0) type = OSPF_NETWORK_LSA; else if (strncmp(argv[arg_base + idx_type]->text, "ns", 2) == 0) type = OSPF_AS_NSSA_LSA; else if (strncmp(argv[arg_base + idx_type]->text, "su", 2) == 0) type = OSPF_SUMMARY_LSA; else if (strncmp(argv[arg_base + idx_type]->text, "a", 1) == 0) type = OSPF_ASBR_SUMMARY_LSA; else if (strncmp(argv[arg_base + idx_type]->text, "e", 1) == 0) type = OSPF_AS_EXTERNAL_LSA; else if (strncmp(argv[arg_base + idx_type]->text, "se", 2) == 0) { show_ip_ospf_database_summary(vty, ospf, 1); return CMD_SUCCESS; } else if (strncmp(argv[arg_base + idx_type]->text, "m", 1) == 0) { show_ip_ospf_database_maxage(vty, ospf); return CMD_SUCCESS; } else if (strncmp(argv[arg_base + idx_type]->text, "opaque-l", 8) == 0) type = OSPF_OPAQUE_LINK_LSA; else if (strncmp(argv[arg_base + idx_type]->text, "opaque-ar", 9) == 0) type = OSPF_OPAQUE_AREA_LSA; else if (strncmp(argv[arg_base + idx_type]->text, "opaque-as", 9) == 0) type = OSPF_OPAQUE_AS_LSA; else return CMD_WARNING; /* `show ip ospf database LSA'. */ if (argc == arg_base + 5) show_lsa_detail(vty, ospf, type, NULL, NULL); else if (argc >= arg_base + 6) { ret = inet_aton(argv[arg_base + 5]->arg, &id); if (!ret) return CMD_WARNING; /* `show ip ospf database LSA ID'. */ if (argc == arg_base + 6) show_lsa_detail(vty, ospf, type, &id, NULL); /* `show ip ospf database LSA ID adv-router ADV_ROUTER'. */ else if (argc == arg_base + 7) { if (strncmp(argv[arg_base + 6]->text, "s", 1) == 0) adv_router = ospf->router_id; else { ret = inet_aton(argv[arg_base + 7]->arg, &adv_router); if (!ret) return CMD_WARNING; } show_lsa_detail(vty, ospf, type, &id, &adv_router); } } return CMD_SUCCESS; } DEFUN (show_ip_ospf_database_max, show_ip_ospf_database_max_cmd, "show ip ospf [vrf ] database ", SHOW_STR IP_STR "OSPF information\n" VRF_CMD_HELP_STR "All VRFs\n" "Database summary\n" "LSAs in MaxAge list\n" "Self-originated link states\n") { struct ospf *ospf = NULL; struct listnode *node = NULL; char *vrf_name = NULL; bool all_vrf = false; int ret = CMD_SUCCESS; int inst = 0; int idx_vrf = 0; uint8_t use_vrf = 0; OSPF_FIND_VRF_ARGS(argv, argc, idx_vrf, vrf_name, all_vrf); if (vrf_name) { bool ospf_output = false; use_vrf = 1; if (all_vrf) { for (ALL_LIST_ELEMENTS_RO(om->ospf, node, ospf)) { if (!ospf->oi_running) continue; ospf_output = true; ret = show_ip_ospf_database_common( vty, ospf, idx_vrf ? 2 : 0, argc, argv, use_vrf); } if (!ospf_output) vty_out(vty, "%% OSPF instance not found\n"); } else { ospf = ospf_lookup_by_inst_name(inst, vrf_name); if (ospf == NULL || !ospf->oi_running) { vty_out(vty, "%% OSPF instance not found\n"); return CMD_SUCCESS; } ret = (show_ip_ospf_database_common( vty, ospf, idx_vrf ? 2 : 0, argc, argv, use_vrf)); } } else { /* Display default ospf (instance 0) info */ ospf = ospf_lookup_by_vrf_id(VRF_DEFAULT); if (ospf == NULL || !ospf->oi_running) { vty_out(vty, "%% OSPF instance not found\n"); return CMD_SUCCESS; } ret = show_ip_ospf_database_common(vty, ospf, 0, argc, argv, use_vrf); } return ret; } DEFUN (show_ip_ospf_instance_database, show_ip_ospf_instance_database_cmd, "show ip ospf [{(1-65535)|vrf NAME}] database [ [A.B.C.D []]]", SHOW_STR IP_STR "OSPF information\n" "Instance ID\n" VRF_CMD_HELP_STR "Database summary\n" OSPF_LSA_TYPES_DESC "Link State ID (as an IP address)\n" "Self-originated link states\n" "Advertising Router link states\n" "Advertising Router (as an IP address)\n") { struct ospf *ospf; unsigned short instance = 0; struct listnode *node = NULL; char *vrf_name = NULL; bool all_vrf = false; int ret = CMD_SUCCESS; int inst = 0; int idx = 0; uint8_t use_vrf = 0; if (argv_find(argv, argc, "(1-65535)", &idx)) { instance = strtoul(argv[idx]->arg, NULL, 10); ospf = ospf_lookup_instance(instance); if (ospf == NULL) return CMD_NOT_MY_INSTANCE; if (!ospf->oi_running) return CMD_SUCCESS; return (show_ip_ospf_database_common(vty, ospf, idx ? 1 : 0, argc, argv, use_vrf)); } else if (argv_find(argv, argc, "vrf", &idx)) { vrf_name = argv[++idx]->arg; all_vrf = strmatch(vrf_name, "all"); } if (vrf_name) { use_vrf = 1; if (all_vrf) { for (ALL_LIST_ELEMENTS_RO(om->ospf, node, ospf)) { if (!ospf->oi_running) continue; ret = (show_ip_ospf_database_common( vty, ospf, idx ? 2 : 0, argc, argv, use_vrf)); } } else { ospf = ospf_lookup_by_inst_name(inst, vrf_name); if ((ospf == NULL) || !ospf->oi_running) { vty_out(vty, "%% OSPF instance not found\n"); return CMD_SUCCESS; } ret = (show_ip_ospf_database_common( vty, ospf, idx ? 2 : 0, argc, argv, use_vrf)); } } else { /* Display default ospf (instance 0) info */ ospf = ospf_lookup_by_vrf_id(VRF_DEFAULT); if (ospf == NULL || !ospf->oi_running) { vty_out(vty, "%% OSPF instance not found\n"); return CMD_SUCCESS; } ret = (show_ip_ospf_database_common(vty, ospf, 0, argc, argv, use_vrf)); } return ret; } DEFUN (show_ip_ospf_instance_database_max, show_ip_ospf_instance_database_max_cmd, "show ip ospf (1-65535) database ", SHOW_STR IP_STR "OSPF information\n" "Instance ID\n" "Database summary\n" "LSAs in MaxAge list\n" "Self-originated link states\n") { int idx_number = 3; struct ospf *ospf; unsigned short instance = 0; instance = strtoul(argv[idx_number]->arg, NULL, 10); ospf = ospf_lookup_instance(instance); if (ospf == NULL) return CMD_NOT_MY_INSTANCE; if (!ospf->oi_running) { vty_out(vty, "%% OSPF instance not found\n"); return CMD_SUCCESS; } return show_ip_ospf_database_common(vty, ospf, 1, argc, argv, 0); } static int show_ip_ospf_database_type_adv_router_common(struct vty *vty, struct ospf *ospf, int arg_base, int argc, struct cmd_token **argv, uint8_t use_vrf) { int idx_type = 4; int type, ret; struct in_addr adv_router; if (ospf->instance) vty_out(vty, "\nOSPF Instance: %d\n", ospf->instance); ospf_show_vrf_name(ospf, vty, NULL, use_vrf); vty_out(vty, "\n OSPF Router with ID (%s)\n\n", inet_ntoa(ospf->router_id)); /* Set database type to show. */ if (strncmp(argv[arg_base + idx_type]->text, "r", 1) == 0) type = OSPF_ROUTER_LSA; else if (strncmp(argv[arg_base + idx_type]->text, "ne", 2) == 0) type = OSPF_NETWORK_LSA; else if (strncmp(argv[arg_base + idx_type]->text, "ns", 2) == 0) type = OSPF_AS_NSSA_LSA; else if (strncmp(argv[arg_base + idx_type]->text, "s", 1) == 0) type = OSPF_SUMMARY_LSA; else if (strncmp(argv[arg_base + idx_type]->text, "a", 1) == 0) type = OSPF_ASBR_SUMMARY_LSA; else if (strncmp(argv[arg_base + idx_type]->text, "e", 1) == 0) type = OSPF_AS_EXTERNAL_LSA; else if (strncmp(argv[arg_base + idx_type]->text, "opaque-l", 8) == 0) type = OSPF_OPAQUE_LINK_LSA; else if (strncmp(argv[arg_base + idx_type]->text, "opaque-ar", 9) == 0) type = OSPF_OPAQUE_AREA_LSA; else if (strncmp(argv[arg_base + idx_type]->text, "opaque-as", 9) == 0) type = OSPF_OPAQUE_AS_LSA; else return CMD_WARNING; /* `show ip ospf database LSA adv-router ADV_ROUTER'. */ if (strncmp(argv[arg_base + 5]->text, "s", 1) == 0) adv_router = ospf->router_id; else { ret = inet_aton(argv[arg_base + 6]->arg, &adv_router); if (!ret) return CMD_WARNING; } show_lsa_detail_adv_router(vty, ospf, type, &adv_router); return CMD_SUCCESS; } DEFUN (show_ip_ospf_instance_database_type_adv_router, show_ip_ospf_instance_database_type_adv_router_cmd, "show ip ospf [{(1-65535)|vrf NAME}] database ", SHOW_STR IP_STR "OSPF information\n" "Instance ID\n" VRF_CMD_HELP_STR "Database summary\n" OSPF_LSA_TYPES_DESC "Advertising Router link states\n" "Advertising Router (as an IP address)\n" "Self-originated link states\n") { struct ospf *ospf = NULL; unsigned short instance = 0; struct listnode *node = NULL; char *vrf_name = NULL; bool all_vrf = false; int ret = CMD_SUCCESS; int inst = 0; int idx = 0, idx_vrf = 0; uint8_t use_vrf = 0; if (argv_find(argv, argc, "(1-65535)", &idx)) { instance = strtoul(argv[idx]->arg, NULL, 10); ospf = ospf_lookup_instance(instance); if (ospf == NULL) return CMD_NOT_MY_INSTANCE; if (!ospf->oi_running) { vty_out(vty, "%% OSPF instance not found\n"); return CMD_SUCCESS; } return (show_ip_ospf_database_type_adv_router_common( vty, ospf, idx ? 1 : 0, argc, argv, use_vrf)); } OSPF_FIND_VRF_ARGS(argv, argc, idx_vrf, vrf_name, all_vrf); if (vrf_name) { bool ospf_output = false; use_vrf = 1; if (all_vrf) { for (ALL_LIST_ELEMENTS_RO(om->ospf, node, ospf)) { if (!ospf->oi_running) continue; ospf_output = true; ret = show_ip_ospf_database_type_adv_router_common( vty, ospf, idx ? 1 : 0, argc, argv, use_vrf); } if (!ospf_output) vty_out(vty, "%% OSPF instance not found\n"); } else { ospf = ospf_lookup_by_inst_name(inst, vrf_name); if ((ospf == NULL) || !ospf->oi_running) { vty_out(vty, "%% OSPF instance not found\n"); return CMD_SUCCESS; } ret = show_ip_ospf_database_type_adv_router_common( vty, ospf, idx ? 1 : 0, argc, argv, use_vrf); } } else { /* Display default ospf (instance 0) info */ ospf = ospf_lookup_by_vrf_id(VRF_DEFAULT); if (ospf == NULL || !ospf->oi_running) { vty_out(vty, "%% OSPF instance not found\n"); return CMD_SUCCESS; } ret = show_ip_ospf_database_type_adv_router_common( vty, ospf, idx ? 1 : 0, argc, argv, use_vrf); } return ret; /*return (show_ip_ospf_database_type_adv_router_common( vty, ospf, idx ? 1 : 0, argc, argv));*/ } DEFUN (ip_ospf_authentication_args, ip_ospf_authentication_args_addr_cmd, "ip ospf authentication [A.B.C.D]", "IP Information\n" "OSPF interface commands\n" "Enable authentication on this interface\n" "Use null authentication\n" "Use message-digest authentication\n" "Address of interface\n") { VTY_DECLVAR_CONTEXT(interface, ifp); int idx_encryption = 3; int idx_ipv4 = 4; struct in_addr addr; int ret; struct ospf_if_params *params; params = IF_DEF_PARAMS(ifp); if (argc == 5) { ret = inet_aton(argv[idx_ipv4]->arg, &addr); if (!ret) { vty_out(vty, "Please specify interface address by A.B.C.D\n"); return CMD_WARNING_CONFIG_FAILED; } params = ospf_get_if_params(ifp, addr); ospf_if_update_params(ifp, addr); } /* Handle null authentication */ if (argv[idx_encryption]->arg[0] == 'n') { SET_IF_PARAM(params, auth_type); params->auth_type = OSPF_AUTH_NULL; return CMD_SUCCESS; } /* Handle message-digest authentication */ if (argv[idx_encryption]->arg[0] == 'm') { SET_IF_PARAM(params, auth_type); params->auth_type = OSPF_AUTH_CRYPTOGRAPHIC; return CMD_SUCCESS; } vty_out(vty, "You shouldn't get here!\n"); return CMD_WARNING_CONFIG_FAILED; } DEFUN (ip_ospf_authentication, ip_ospf_authentication_addr_cmd, "ip ospf authentication [A.B.C.D]", "IP Information\n" "OSPF interface commands\n" "Enable authentication on this interface\n" "Address of interface\n") { VTY_DECLVAR_CONTEXT(interface, ifp); int idx_ipv4 = 3; struct in_addr addr; int ret; struct ospf_if_params *params; params = IF_DEF_PARAMS(ifp); if (argc == 4) { ret = inet_aton(argv[idx_ipv4]->arg, &addr); if (!ret) { vty_out(vty, "Please specify interface address by A.B.C.D\n"); return CMD_WARNING_CONFIG_FAILED; } params = ospf_get_if_params(ifp, addr); ospf_if_update_params(ifp, addr); } SET_IF_PARAM(params, auth_type); params->auth_type = OSPF_AUTH_SIMPLE; return CMD_SUCCESS; } DEFUN (no_ip_ospf_authentication_args, no_ip_ospf_authentication_args_addr_cmd, "no ip ospf authentication [A.B.C.D]", NO_STR "IP Information\n" "OSPF interface commands\n" "Enable authentication on this interface\n" "Use null authentication\n" "Use message-digest authentication\n" "Address of interface\n") { VTY_DECLVAR_CONTEXT(interface, ifp); int idx_encryption = 4; int idx_ipv4 = 5; struct in_addr addr; int ret; struct ospf_if_params *params; struct route_node *rn; int auth_type; params = IF_DEF_PARAMS(ifp); if (argc == 6) { ret = inet_aton(argv[idx_ipv4]->arg, &addr); if (!ret) { vty_out(vty, "Please specify interface address by A.B.C.D\n"); return CMD_WARNING_CONFIG_FAILED; } params = ospf_lookup_if_params(ifp, addr); if (params == NULL) { vty_out(vty, "Ip Address specified is unknown\n"); return CMD_WARNING_CONFIG_FAILED; } params->auth_type = OSPF_AUTH_NOTSET; UNSET_IF_PARAM(params, auth_type); if (params != IF_DEF_PARAMS(ifp)) { ospf_free_if_params(ifp, addr); ospf_if_update_params(ifp, addr); } } else { if (argv[idx_encryption]->arg[0] == 'n') { auth_type = OSPF_AUTH_NULL; } else if (argv[idx_encryption]->arg[0] == 'm') { auth_type = OSPF_AUTH_CRYPTOGRAPHIC; } else { vty_out(vty, "Unexpected input encountered\n"); return CMD_WARNING_CONFIG_FAILED; } /* * Here we have a case where the user has entered * 'no ip ospf authentication (null | message_digest )' * we need to find if we have any ip addresses underneath it * that * correspond to the associated type. */ if (params->auth_type == auth_type) { params->auth_type = OSPF_AUTH_NOTSET; UNSET_IF_PARAM(params, auth_type); } for (rn = route_top(IF_OIFS_PARAMS(ifp)); rn; rn = route_next(rn)) { if ((params = rn->info)) { if (params->auth_type == auth_type) { params->auth_type = OSPF_AUTH_NOTSET; UNSET_IF_PARAM(params, auth_type); if (params != IF_DEF_PARAMS(ifp)) { ospf_free_if_params( ifp, rn->p.u.prefix4); ospf_if_update_params( ifp, rn->p.u.prefix4); } } } } } return CMD_SUCCESS; } DEFUN (no_ip_ospf_authentication, no_ip_ospf_authentication_addr_cmd, "no ip ospf authentication [A.B.C.D]", NO_STR "IP Information\n" "OSPF interface commands\n" "Enable authentication on this interface\n" "Address of interface\n") { VTY_DECLVAR_CONTEXT(interface, ifp); int idx_ipv4 = 4; struct in_addr addr; int ret; struct ospf_if_params *params; struct route_node *rn; params = IF_DEF_PARAMS(ifp); if (argc == 5) { ret = inet_aton(argv[idx_ipv4]->arg, &addr); if (!ret) { vty_out(vty, "Please specify interface address by A.B.C.D\n"); return CMD_WARNING_CONFIG_FAILED; } params = ospf_lookup_if_params(ifp, addr); if (params == NULL) { vty_out(vty, "Ip Address specified is unknown\n"); return CMD_WARNING_CONFIG_FAILED; } params->auth_type = OSPF_AUTH_NOTSET; UNSET_IF_PARAM(params, auth_type); if (params != IF_DEF_PARAMS(ifp)) { ospf_free_if_params(ifp, addr); ospf_if_update_params(ifp, addr); } } else { /* * When a user enters 'no ip ospf authentication' * We should remove all authentication types from * the interface. */ if ((params->auth_type == OSPF_AUTH_NULL) || (params->auth_type == OSPF_AUTH_CRYPTOGRAPHIC) || (params->auth_type == OSPF_AUTH_SIMPLE)) { params->auth_type = OSPF_AUTH_NOTSET; UNSET_IF_PARAM(params, auth_type); } for (rn = route_top(IF_OIFS_PARAMS(ifp)); rn; rn = route_next(rn)) { if ((params = rn->info)) { if ((params->auth_type == OSPF_AUTH_NULL) || (params->auth_type == OSPF_AUTH_CRYPTOGRAPHIC) || (params->auth_type == OSPF_AUTH_SIMPLE)) { params->auth_type = OSPF_AUTH_NOTSET; UNSET_IF_PARAM(params, auth_type); if (params != IF_DEF_PARAMS(ifp)) { ospf_free_if_params( ifp, rn->p.u.prefix4); ospf_if_update_params( ifp, rn->p.u.prefix4); } } } } } return CMD_SUCCESS; } DEFUN (ip_ospf_authentication_key, ip_ospf_authentication_key_addr_cmd, "ip ospf authentication-key AUTH_KEY [A.B.C.D]", "IP Information\n" "OSPF interface commands\n" "Authentication password (key)\n" "The OSPF password (key)\n" "Address of interface\n") { VTY_DECLVAR_CONTEXT(interface, ifp); int idx = 0; struct in_addr addr; struct ospf_if_params *params; params = IF_DEF_PARAMS(ifp); if (argv_find(argv, argc, "A.B.C.D", &idx)) { if (!inet_aton(argv[idx]->arg, &addr)) { vty_out(vty, "Please specify interface address by A.B.C.D\n"); return CMD_WARNING_CONFIG_FAILED; } params = ospf_get_if_params(ifp, addr); ospf_if_update_params(ifp, addr); } strlcpy((char *)params->auth_simple, argv[3]->arg, sizeof(params->auth_simple)); SET_IF_PARAM(params, auth_simple); return CMD_SUCCESS; } DEFUN_HIDDEN (ospf_authentication_key, ospf_authentication_key_cmd, "ospf authentication-key AUTH_KEY [A.B.C.D]", "OSPF interface commands\n" VLINK_HELPSTR_AUTH_SIMPLE "Address of interface\n") { return ip_ospf_authentication_key(self, vty, argc, argv); } DEFUN (no_ip_ospf_authentication_key, no_ip_ospf_authentication_key_authkey_addr_cmd, "no ip ospf authentication-key [AUTH_KEY [A.B.C.D]]", NO_STR "IP Information\n" "OSPF interface commands\n" VLINK_HELPSTR_AUTH_SIMPLE "Address of interface\n") { VTY_DECLVAR_CONTEXT(interface, ifp); int idx = 0; struct in_addr addr; struct ospf_if_params *params; params = IF_DEF_PARAMS(ifp); if (argv_find(argv, argc, "A.B.C.D", &idx)) { if (!inet_aton(argv[idx]->arg, &addr)) { vty_out(vty, "Please specify interface address by A.B.C.D\n"); return CMD_WARNING_CONFIG_FAILED; } params = ospf_lookup_if_params(ifp, addr); if (params == NULL) return CMD_SUCCESS; } memset(params->auth_simple, 0, OSPF_AUTH_SIMPLE_SIZE); UNSET_IF_PARAM(params, auth_simple); if (params != IF_DEF_PARAMS(ifp)) { ospf_free_if_params(ifp, addr); ospf_if_update_params(ifp, addr); } return CMD_SUCCESS; } DEFUN_HIDDEN (no_ospf_authentication_key, no_ospf_authentication_key_authkey_addr_cmd, "no ospf authentication-key [AUTH_KEY [A.B.C.D]]", NO_STR "OSPF interface commands\n" VLINK_HELPSTR_AUTH_SIMPLE "Address of interface\n") { return no_ip_ospf_authentication_key(self, vty, argc, argv); } DEFUN (ip_ospf_message_digest_key, ip_ospf_message_digest_key_cmd, "ip ospf message-digest-key (1-255) md5 KEY [A.B.C.D]", "IP Information\n" "OSPF interface commands\n" "Message digest authentication password (key)\n" "Key ID\n" "Use MD5 algorithm\n" "The OSPF password (key)\n" "Address of interface\n") { VTY_DECLVAR_CONTEXT(interface, ifp); struct crypt_key *ck; uint8_t key_id; struct in_addr addr; struct ospf_if_params *params; params = IF_DEF_PARAMS(ifp); int idx = 0; argv_find(argv, argc, "(1-255)", &idx); char *keyid = argv[idx]->arg; argv_find(argv, argc, "KEY", &idx); char *cryptkey = argv[idx]->arg; if (argv_find(argv, argc, "A.B.C.D", &idx)) { if (!inet_aton(argv[idx]->arg, &addr)) { vty_out(vty, "Please specify interface address by A.B.C.D\n"); return CMD_WARNING_CONFIG_FAILED; } params = ospf_get_if_params(ifp, addr); ospf_if_update_params(ifp, addr); } key_id = strtol(keyid, NULL, 10); if (ospf_crypt_key_lookup(params->auth_crypt, key_id) != NULL) { vty_out(vty, "OSPF: Key %d already exists\n", key_id); return CMD_WARNING; } ck = ospf_crypt_key_new(); ck->key_id = (uint8_t)key_id; strlcpy((char *)ck->auth_key, cryptkey, sizeof(ck->auth_key)); ospf_crypt_key_add(params->auth_crypt, ck); SET_IF_PARAM(params, auth_crypt); return CMD_SUCCESS; } DEFUN_HIDDEN (ospf_message_digest_key, ospf_message_digest_key_cmd, "ospf message-digest-key (1-255) md5 KEY [A.B.C.D]", "OSPF interface commands\n" "Message digest authentication password (key)\n" "Key ID\n" "Use MD5 algorithm\n" "The OSPF password (key)\n" "Address of interface\n") { return ip_ospf_message_digest_key(self, vty, argc, argv); } DEFUN (no_ip_ospf_message_digest_key, no_ip_ospf_message_digest_key_cmd, "no ip ospf message-digest-key (1-255) [md5 KEY] [A.B.C.D]", NO_STR "IP Information\n" "OSPF interface commands\n" "Message digest authentication password (key)\n" "Key ID\n" "Use MD5 algorithm\n" "The OSPF password (key)\n" "Address of interface\n") { VTY_DECLVAR_CONTEXT(interface, ifp); int idx = 0; struct crypt_key *ck; int key_id; struct in_addr addr; struct ospf_if_params *params; params = IF_DEF_PARAMS(ifp); argv_find(argv, argc, "(1-255)", &idx); char *keyid = argv[idx]->arg; if (argv_find(argv, argc, "A.B.C.D", &idx)) { if (!inet_aton(argv[idx]->arg, &addr)) { vty_out(vty, "Please specify interface address by A.B.C.D\n"); return CMD_WARNING_CONFIG_FAILED; } params = ospf_lookup_if_params(ifp, addr); if (params == NULL) return CMD_SUCCESS; } key_id = strtol(keyid, NULL, 10); ck = ospf_crypt_key_lookup(params->auth_crypt, key_id); if (ck == NULL) { vty_out(vty, "OSPF: Key %d does not exist\n", key_id); return CMD_WARNING_CONFIG_FAILED; } ospf_crypt_key_delete(params->auth_crypt, key_id); if (params != IF_DEF_PARAMS(ifp)) { ospf_free_if_params(ifp, addr); ospf_if_update_params(ifp, addr); } return CMD_SUCCESS; } DEFUN_HIDDEN (no_ospf_message_digest_key, no_ospf_message_digest_key_cmd, "no ospf message-digest-key (1-255) [md5 KEY] [A.B.C.D]", NO_STR "OSPF interface commands\n" "Message digest authentication password (key)\n" "Key ID\n" "Use MD5 algorithm\n" "The OSPF password (key)\n" "Address of interface\n") { return no_ip_ospf_message_digest_key(self, vty, argc, argv); } DEFUN (ip_ospf_cost, ip_ospf_cost_cmd, "ip ospf cost (1-65535) [A.B.C.D]", "IP Information\n" "OSPF interface commands\n" "Interface cost\n" "Cost\n" "Address of interface\n") { VTY_DECLVAR_CONTEXT(interface, ifp); int idx = 0; uint32_t cost = OSPF_OUTPUT_COST_DEFAULT; struct in_addr addr; struct ospf_if_params *params; params = IF_DEF_PARAMS(ifp); // get arguments char *coststr = NULL, *ifaddr = NULL; argv_find(argv, argc, "(1-65535)", &idx); coststr = argv[idx]->arg; cost = strtol(coststr, NULL, 10); ifaddr = argv_find(argv, argc, "A.B.C.D", &idx) ? argv[idx]->arg : NULL; if (ifaddr) { if (!inet_aton(ifaddr, &addr)) { vty_out(vty, "Please specify interface address by A.B.C.D\n"); return CMD_WARNING_CONFIG_FAILED; } params = ospf_get_if_params(ifp, addr); ospf_if_update_params(ifp, addr); } SET_IF_PARAM(params, output_cost_cmd); params->output_cost_cmd = cost; ospf_if_recalculate_output_cost(ifp); return CMD_SUCCESS; } DEFUN_HIDDEN (ospf_cost, ospf_cost_cmd, "ospf cost (1-65535) [A.B.C.D]", "OSPF interface commands\n" "Interface cost\n" "Cost\n" "Address of interface\n") { return ip_ospf_cost(self, vty, argc, argv); } DEFUN (no_ip_ospf_cost, no_ip_ospf_cost_cmd, "no ip ospf cost [(1-65535)] [A.B.C.D]", NO_STR "IP Information\n" "OSPF interface commands\n" "Interface cost\n" "Cost\n" "Address of interface\n") { VTY_DECLVAR_CONTEXT(interface, ifp); int idx = 0; struct in_addr addr; struct ospf_if_params *params; params = IF_DEF_PARAMS(ifp); // get arguments char *ifaddr = NULL; ifaddr = argv_find(argv, argc, "A.B.C.D", &idx) ? argv[idx]->arg : NULL; /* According to the semantics we are mimicking "no ip ospf cost N" is * always treated as "no ip ospf cost" regardless of the actual value * of N already configured for the interface. Thus ignore cost. */ if (ifaddr) { if (!inet_aton(ifaddr, &addr)) { vty_out(vty, "Please specify interface address by A.B.C.D\n"); return CMD_WARNING_CONFIG_FAILED; } params = ospf_lookup_if_params(ifp, addr); if (params == NULL) return CMD_SUCCESS; } UNSET_IF_PARAM(params, output_cost_cmd); if (params != IF_DEF_PARAMS(ifp)) { ospf_free_if_params(ifp, addr); ospf_if_update_params(ifp, addr); } ospf_if_recalculate_output_cost(ifp); return CMD_SUCCESS; } DEFUN_HIDDEN (no_ospf_cost, no_ospf_cost_cmd, "no ospf cost [(1-65535)] [A.B.C.D]", NO_STR "OSPF interface commands\n" "Interface cost\n" "Cost\n" "Address of interface\n") { return no_ip_ospf_cost(self, vty, argc, argv); } static void ospf_nbr_timer_update(struct ospf_interface *oi) { struct route_node *rn; struct ospf_neighbor *nbr; for (rn = route_top(oi->nbrs); rn; rn = route_next(rn)) if ((nbr = rn->info)) { nbr->v_inactivity = OSPF_IF_PARAM(oi, v_wait); nbr->v_db_desc = OSPF_IF_PARAM(oi, retransmit_interval); nbr->v_ls_req = OSPF_IF_PARAM(oi, retransmit_interval); nbr->v_ls_upd = OSPF_IF_PARAM(oi, retransmit_interval); } } static int ospf_vty_dead_interval_set(struct vty *vty, const char *interval_str, const char *nbr_str, const char *fast_hello_str) { VTY_DECLVAR_CONTEXT(interface, ifp); uint32_t seconds; uint8_t hellomult; struct in_addr addr; int ret; struct ospf_if_params *params; struct ospf_interface *oi; struct route_node *rn; params = IF_DEF_PARAMS(ifp); if (nbr_str) { ret = inet_aton(nbr_str, &addr); if (!ret) { vty_out(vty, "Please specify interface address by A.B.C.D\n"); return CMD_WARNING_CONFIG_FAILED; } params = ospf_get_if_params(ifp, addr); ospf_if_update_params(ifp, addr); } if (interval_str) { seconds = strtoul(interval_str, NULL, 10); /* reset fast_hello too, just to be sure */ UNSET_IF_PARAM(params, fast_hello); params->fast_hello = OSPF_FAST_HELLO_DEFAULT; } else if (fast_hello_str) { hellomult = strtoul(fast_hello_str, NULL, 10); /* 1s dead-interval with sub-second hellos desired */ seconds = OSPF_ROUTER_DEAD_INTERVAL_MINIMAL; SET_IF_PARAM(params, fast_hello); params->fast_hello = hellomult; } else { vty_out(vty, "Please specify dead-interval or hello-multiplier\n"); return CMD_WARNING_CONFIG_FAILED; } SET_IF_PARAM(params, v_wait); params->v_wait = seconds; /* Update timer values in neighbor structure. */ if (nbr_str) { struct ospf *ospf = NULL; ospf = ospf_lookup_by_vrf_id(ifp->vrf_id); if (ospf) { oi = ospf_if_lookup_by_local_addr(ospf, ifp, addr); if (oi) ospf_nbr_timer_update(oi); } } else { for (rn = route_top(IF_OIFS(ifp)); rn; rn = route_next(rn)) if ((oi = rn->info)) ospf_nbr_timer_update(oi); } return CMD_SUCCESS; } DEFUN (ip_ospf_dead_interval, ip_ospf_dead_interval_cmd, "ip ospf dead-interval (1-65535) [A.B.C.D]", "IP Information\n" "OSPF interface commands\n" "Interval time after which a neighbor is declared down\n" "Seconds\n" "Address of interface\n") { int idx = 0; char *interval = argv_find(argv, argc, "(1-65535)", &idx) ? argv[idx]->arg : NULL; char *ifaddr = argv_find(argv, argc, "A.B.C.D", &idx) ? argv[idx]->arg : NULL; return ospf_vty_dead_interval_set(vty, interval, ifaddr, NULL); } DEFUN_HIDDEN (ospf_dead_interval, ospf_dead_interval_cmd, "ospf dead-interval (1-65535) [A.B.C.D]", "OSPF interface commands\n" "Interval time after which a neighbor is declared down\n" "Seconds\n" "Address of interface\n") { return ip_ospf_dead_interval(self, vty, argc, argv); } DEFUN (ip_ospf_dead_interval_minimal, ip_ospf_dead_interval_minimal_addr_cmd, "ip ospf dead-interval minimal hello-multiplier (1-10) [A.B.C.D]", "IP Information\n" "OSPF interface commands\n" "Interval time after which a neighbor is declared down\n" "Minimal 1s dead-interval with fast sub-second hellos\n" "Hello multiplier factor\n" "Number of Hellos to send each second\n" "Address of interface\n") { int idx_number = 5; int idx_ipv4 = 6; if (argc == 7) return ospf_vty_dead_interval_set( vty, NULL, argv[idx_ipv4]->arg, argv[idx_number]->arg); else return ospf_vty_dead_interval_set(vty, NULL, NULL, argv[idx_number]->arg); } DEFUN (no_ip_ospf_dead_interval, no_ip_ospf_dead_interval_cmd, "no ip ospf dead-interval [<(1-65535)|minimal hello-multiplier (1-10)> [A.B.C.D]]", NO_STR "IP Information\n" "OSPF interface commands\n" "Interval time after which a neighbor is declared down\n" "Seconds\n" "Minimal 1s dead-interval with fast sub-second hellos\n" "Hello multiplier factor\n" "Number of Hellos to send each second\n" "Address of interface\n") { VTY_DECLVAR_CONTEXT(interface, ifp); int idx_ipv4 = argc - 1; struct in_addr addr = {.s_addr = 0L}; int ret; struct ospf_if_params *params; struct ospf_interface *oi; struct route_node *rn; params = IF_DEF_PARAMS(ifp); if (argv[idx_ipv4]->type == IPV4_TKN) { ret = inet_aton(argv[idx_ipv4]->arg, &addr); if (!ret) { vty_out(vty, "Please specify interface address by A.B.C.D\n"); return CMD_WARNING_CONFIG_FAILED; } params = ospf_lookup_if_params(ifp, addr); if (params == NULL) return CMD_SUCCESS; } UNSET_IF_PARAM(params, v_wait); params->v_wait = OSPF_ROUTER_DEAD_INTERVAL_DEFAULT; UNSET_IF_PARAM(params, fast_hello); params->fast_hello = OSPF_FAST_HELLO_DEFAULT; if (params != IF_DEF_PARAMS(ifp)) { ospf_free_if_params(ifp, addr); ospf_if_update_params(ifp, addr); } /* Update timer values in neighbor structure. */ if (argc == 1) { struct ospf *ospf = NULL; ospf = ospf_lookup_by_vrf_id(ifp->vrf_id); if (ospf) { oi = ospf_if_lookup_by_local_addr(ospf, ifp, addr); if (oi) ospf_nbr_timer_update(oi); } } else { for (rn = route_top(IF_OIFS(ifp)); rn; rn = route_next(rn)) if ((oi = rn->info)) ospf_nbr_timer_update(oi); } return CMD_SUCCESS; } DEFUN_HIDDEN (no_ospf_dead_interval, no_ospf_dead_interval_cmd, "no ospf dead-interval [<(1-65535)|minimal hello-multiplier (1-10)> [A.B.C.D]]", NO_STR "OSPF interface commands\n" "Interval time after which a neighbor is declared down\n" "Seconds\n" "Minimal 1s dead-interval with fast sub-second hellos\n" "Hello multiplier factor\n" "Number of Hellos to send each second\n" "Address of interface\n") { return no_ip_ospf_dead_interval(self, vty, argc, argv); } DEFUN (ip_ospf_hello_interval, ip_ospf_hello_interval_cmd, "ip ospf hello-interval (1-65535) [A.B.C.D]", "IP Information\n" "OSPF interface commands\n" "Time between HELLO packets\n" "Seconds\n" "Address of interface\n") { VTY_DECLVAR_CONTEXT(interface, ifp); int idx = 0; struct in_addr addr; struct ospf_if_params *params; params = IF_DEF_PARAMS(ifp); uint32_t seconds = 0; argv_find(argv, argc, "(1-65535)", &idx); seconds = strtol(argv[idx]->arg, NULL, 10); if (argv_find(argv, argc, "A.B.C.D", &idx)) { if (!inet_aton(argv[idx]->arg, &addr)) { vty_out(vty, "Please specify interface address by A.B.C.D\n"); return CMD_WARNING_CONFIG_FAILED; } params = ospf_get_if_params(ifp, addr); ospf_if_update_params(ifp, addr); } SET_IF_PARAM(params, v_hello); params->v_hello = seconds; return CMD_SUCCESS; } DEFUN_HIDDEN (ospf_hello_interval, ospf_hello_interval_cmd, "ospf hello-interval (1-65535) [A.B.C.D]", "OSPF interface commands\n" "Time between HELLO packets\n" "Seconds\n" "Address of interface\n") { return ip_ospf_hello_interval(self, vty, argc, argv); } DEFUN (no_ip_ospf_hello_interval, no_ip_ospf_hello_interval_cmd, "no ip ospf hello-interval [(1-65535) [A.B.C.D]]", NO_STR "IP Information\n" "OSPF interface commands\n" "Time between HELLO packets\n" // ignored "Seconds\n" "Address of interface\n") { VTY_DECLVAR_CONTEXT(interface, ifp); int idx = 0; struct in_addr addr; struct ospf_if_params *params; params = IF_DEF_PARAMS(ifp); if (argv_find(argv, argc, "A.B.C.D", &idx)) { if (!inet_aton(argv[idx]->arg, &addr)) { vty_out(vty, "Please specify interface address by A.B.C.D\n"); return CMD_WARNING_CONFIG_FAILED; } params = ospf_lookup_if_params(ifp, addr); if (params == NULL) return CMD_SUCCESS; } UNSET_IF_PARAM(params, v_hello); params->v_hello = OSPF_HELLO_INTERVAL_DEFAULT; if (params != IF_DEF_PARAMS(ifp)) { ospf_free_if_params(ifp, addr); ospf_if_update_params(ifp, addr); } return CMD_SUCCESS; } DEFUN_HIDDEN (no_ospf_hello_interval, no_ospf_hello_interval_cmd, "no ospf hello-interval [(1-65535) [A.B.C.D]]", NO_STR "OSPF interface commands\n" "Time between HELLO packets\n" // ignored "Seconds\n" "Address of interface\n") { return no_ip_ospf_hello_interval(self, vty, argc, argv); } DEFUN (ip_ospf_network, ip_ospf_network_cmd, "ip ospf network ", "IP Information\n" "OSPF interface commands\n" "Network type\n" "Specify OSPF broadcast multi-access network\n" "Specify OSPF NBMA network\n" "Specify OSPF point-to-multipoint network\n" "Specify OSPF point-to-point network\n") { VTY_DECLVAR_CONTEXT(interface, ifp); int idx = 0; int old_type = IF_DEF_PARAMS(ifp)->type; struct route_node *rn; if (old_type == OSPF_IFTYPE_LOOPBACK) { vty_out(vty, "This is a loopback interface. Can't set network type.\n"); return CMD_WARNING_CONFIG_FAILED; } if (argv_find(argv, argc, "broadcast", &idx)) IF_DEF_PARAMS(ifp)->type = OSPF_IFTYPE_BROADCAST; else if (argv_find(argv, argc, "non-broadcast", &idx)) IF_DEF_PARAMS(ifp)->type = OSPF_IFTYPE_NBMA; else if (argv_find(argv, argc, "point-to-multipoint", &idx)) IF_DEF_PARAMS(ifp)->type = OSPF_IFTYPE_POINTOMULTIPOINT; else if (argv_find(argv, argc, "point-to-point", &idx)) IF_DEF_PARAMS(ifp)->type = OSPF_IFTYPE_POINTOPOINT; if (IF_DEF_PARAMS(ifp)->type == old_type) return CMD_SUCCESS; SET_IF_PARAM(IF_DEF_PARAMS(ifp), type); for (rn = route_top(IF_OIFS(ifp)); rn; rn = route_next(rn)) { struct ospf_interface *oi = rn->info; if (!oi) continue; oi->type = IF_DEF_PARAMS(ifp)->type; if (oi->state > ISM_Down) { OSPF_ISM_EVENT_EXECUTE(oi, ISM_InterfaceDown); OSPF_ISM_EVENT_EXECUTE(oi, ISM_InterfaceUp); } } return CMD_SUCCESS; } DEFUN_HIDDEN (ospf_network, ospf_network_cmd, "ospf network ", "OSPF interface commands\n" "Network type\n" "Specify OSPF broadcast multi-access network\n" "Specify OSPF NBMA network\n" "Specify OSPF point-to-multipoint network\n" "Specify OSPF point-to-point network\n") { return ip_ospf_network(self, vty, argc, argv); } DEFUN (no_ip_ospf_network, no_ip_ospf_network_cmd, "no ip ospf network []", NO_STR "IP Information\n" "OSPF interface commands\n" "Network type\n" "Specify OSPF broadcast multi-access network\n" "Specify OSPF NBMA network\n" "Specify OSPF point-to-multipoint network\n" "Specify OSPF point-to-point network\n") { VTY_DECLVAR_CONTEXT(interface, ifp); int old_type = IF_DEF_PARAMS(ifp)->type; struct route_node *rn; IF_DEF_PARAMS(ifp)->type = ospf_default_iftype(ifp); if (IF_DEF_PARAMS(ifp)->type == old_type) return CMD_SUCCESS; for (rn = route_top(IF_OIFS(ifp)); rn; rn = route_next(rn)) { struct ospf_interface *oi = rn->info; if (!oi) continue; oi->type = IF_DEF_PARAMS(ifp)->type; if (oi->state > ISM_Down) { OSPF_ISM_EVENT_EXECUTE(oi, ISM_InterfaceDown); OSPF_ISM_EVENT_EXECUTE(oi, ISM_InterfaceUp); } } return CMD_SUCCESS; } DEFUN_HIDDEN (no_ospf_network, no_ospf_network_cmd, "no ospf network []", NO_STR "OSPF interface commands\n" "Network type\n" "Specify OSPF broadcast multi-access network\n" "Specify OSPF NBMA network\n" "Specify OSPF point-to-multipoint network\n" "Specify OSPF point-to-point network\n") { return no_ip_ospf_network(self, vty, argc, argv); } DEFUN (ip_ospf_priority, ip_ospf_priority_cmd, "ip ospf priority (0-255) [A.B.C.D]", "IP Information\n" "OSPF interface commands\n" "Router priority\n" "Priority\n" "Address of interface\n") { VTY_DECLVAR_CONTEXT(interface, ifp); int idx = 0; long priority; struct route_node *rn; struct in_addr addr; struct ospf_if_params *params; params = IF_DEF_PARAMS(ifp); argv_find(argv, argc, "(0-255)", &idx); priority = strtol(argv[idx]->arg, NULL, 10); if (argv_find(argv, argc, "A.B.C.D", &idx)) { if (!inet_aton(argv[idx]->arg, &addr)) { vty_out(vty, "Please specify interface address by A.B.C.D\n"); return CMD_WARNING_CONFIG_FAILED; } params = ospf_get_if_params(ifp, addr); ospf_if_update_params(ifp, addr); } SET_IF_PARAM(params, priority); params->priority = priority; for (rn = route_top(IF_OIFS(ifp)); rn; rn = route_next(rn)) { struct ospf_interface *oi = rn->info; if (!oi) continue; if (PRIORITY(oi) != OSPF_IF_PARAM(oi, priority)) { PRIORITY(oi) = OSPF_IF_PARAM(oi, priority); OSPF_ISM_EVENT_SCHEDULE(oi, ISM_NeighborChange); } } return CMD_SUCCESS; } DEFUN_HIDDEN (ospf_priority, ospf_priority_cmd, "ospf priority (0-255) [A.B.C.D]", "OSPF interface commands\n" "Router priority\n" "Priority\n" "Address of interface\n") { return ip_ospf_priority(self, vty, argc, argv); } DEFUN (no_ip_ospf_priority, no_ip_ospf_priority_cmd, "no ip ospf priority [(0-255) [A.B.C.D]]", NO_STR "IP Information\n" "OSPF interface commands\n" "Router priority\n" // ignored "Priority\n" "Address of interface\n") { VTY_DECLVAR_CONTEXT(interface, ifp); int idx = 0; struct route_node *rn; struct in_addr addr; struct ospf_if_params *params; params = IF_DEF_PARAMS(ifp); if (argv_find(argv, argc, "A.B.C.D", &idx)) { if (!inet_aton(argv[idx]->arg, &addr)) { vty_out(vty, "Please specify interface address by A.B.C.D\n"); return CMD_WARNING_CONFIG_FAILED; } params = ospf_lookup_if_params(ifp, addr); if (params == NULL) return CMD_SUCCESS; } UNSET_IF_PARAM(params, priority); params->priority = OSPF_ROUTER_PRIORITY_DEFAULT; if (params != IF_DEF_PARAMS(ifp)) { ospf_free_if_params(ifp, addr); ospf_if_update_params(ifp, addr); } for (rn = route_top(IF_OIFS(ifp)); rn; rn = route_next(rn)) { struct ospf_interface *oi = rn->info; if (!oi) continue; if (PRIORITY(oi) != OSPF_IF_PARAM(oi, priority)) { PRIORITY(oi) = OSPF_IF_PARAM(oi, priority); OSPF_ISM_EVENT_SCHEDULE(oi, ISM_NeighborChange); } } return CMD_SUCCESS; } DEFUN_HIDDEN (no_ospf_priority, no_ospf_priority_cmd, "no ospf priority [(0-255) [A.B.C.D]]", NO_STR "OSPF interface commands\n" "Router priority\n" "Priority\n" "Address of interface\n") { return no_ip_ospf_priority(self, vty, argc, argv); } DEFUN (ip_ospf_retransmit_interval, ip_ospf_retransmit_interval_addr_cmd, "ip ospf retransmit-interval (3-65535) [A.B.C.D]", "IP Information\n" "OSPF interface commands\n" "Time between retransmitting lost link state advertisements\n" "Seconds\n" "Address of interface\n") { VTY_DECLVAR_CONTEXT(interface, ifp); int idx = 0; uint32_t seconds; struct in_addr addr; struct ospf_if_params *params; params = IF_DEF_PARAMS(ifp); argv_find(argv, argc, "(3-65535)", &idx); seconds = strtol(argv[idx]->arg, NULL, 10); if (argv_find(argv, argc, "A.B.C.D", &idx)) { if (!inet_aton(argv[idx]->arg, &addr)) { vty_out(vty, "Please specify interface address by A.B.C.D\n"); return CMD_WARNING_CONFIG_FAILED; } params = ospf_get_if_params(ifp, addr); ospf_if_update_params(ifp, addr); } SET_IF_PARAM(params, retransmit_interval); params->retransmit_interval = seconds; return CMD_SUCCESS; } DEFUN_HIDDEN (ospf_retransmit_interval, ospf_retransmit_interval_cmd, "ospf retransmit-interval (3-65535) [A.B.C.D]", "OSPF interface commands\n" "Time between retransmitting lost link state advertisements\n" "Seconds\n" "Address of interface\n") { return ip_ospf_retransmit_interval(self, vty, argc, argv); } DEFUN (no_ip_ospf_retransmit_interval, no_ip_ospf_retransmit_interval_addr_cmd, "no ip ospf retransmit-interval [(3-65535)] [A.B.C.D]", NO_STR "IP Information\n" "OSPF interface commands\n" "Time between retransmitting lost link state advertisements\n" "Seconds\n" "Address of interface\n") { VTY_DECLVAR_CONTEXT(interface, ifp); int idx = 0; struct in_addr addr; struct ospf_if_params *params; params = IF_DEF_PARAMS(ifp); if (argv_find(argv, argc, "A.B.C.D", &idx)) { if (!inet_aton(argv[idx]->arg, &addr)) { vty_out(vty, "Please specify interface address by A.B.C.D\n"); return CMD_WARNING_CONFIG_FAILED; } params = ospf_lookup_if_params(ifp, addr); if (params == NULL) return CMD_SUCCESS; } UNSET_IF_PARAM(params, retransmit_interval); params->retransmit_interval = OSPF_RETRANSMIT_INTERVAL_DEFAULT; if (params != IF_DEF_PARAMS(ifp)) { ospf_free_if_params(ifp, addr); ospf_if_update_params(ifp, addr); } return CMD_SUCCESS; } DEFUN_HIDDEN (no_ospf_retransmit_interval, no_ospf_retransmit_interval_cmd, "no ospf retransmit-interval [(3-65535)] [A.B.C.D]", NO_STR "OSPF interface commands\n" "Time between retransmitting lost link state advertisements\n" "Seconds\n" "Address of interface\n") { return no_ip_ospf_retransmit_interval(self, vty, argc, argv); } DEFUN (ip_ospf_transmit_delay, ip_ospf_transmit_delay_addr_cmd, "ip ospf transmit-delay (1-65535) [A.B.C.D]", "IP Information\n" "OSPF interface commands\n" "Link state transmit delay\n" "Seconds\n" "Address of interface\n") { VTY_DECLVAR_CONTEXT(interface, ifp); int idx = 0; uint32_t seconds; struct in_addr addr; struct ospf_if_params *params; params = IF_DEF_PARAMS(ifp); argv_find(argv, argc, "(1-65535)", &idx); seconds = strtol(argv[idx]->arg, NULL, 10); if (argv_find(argv, argc, "A.B.C.D", &idx)) { if (!inet_aton(argv[idx]->arg, &addr)) { vty_out(vty, "Please specify interface address by A.B.C.D\n"); return CMD_WARNING_CONFIG_FAILED; } params = ospf_get_if_params(ifp, addr); ospf_if_update_params(ifp, addr); } SET_IF_PARAM(params, transmit_delay); params->transmit_delay = seconds; return CMD_SUCCESS; } DEFUN_HIDDEN (ospf_transmit_delay, ospf_transmit_delay_cmd, "ospf transmit-delay (1-65535) [A.B.C.D]", "OSPF interface commands\n" "Link state transmit delay\n" "Seconds\n" "Address of interface\n") { return ip_ospf_transmit_delay(self, vty, argc, argv); } DEFUN (no_ip_ospf_transmit_delay, no_ip_ospf_transmit_delay_addr_cmd, "no ip ospf transmit-delay [(1-65535)] [A.B.C.D]", NO_STR "IP Information\n" "OSPF interface commands\n" "Link state transmit delay\n" "Seconds\n" "Address of interface\n") { VTY_DECLVAR_CONTEXT(interface, ifp); int idx = 0; struct in_addr addr; struct ospf_if_params *params; params = IF_DEF_PARAMS(ifp); if (argv_find(argv, argc, "A.B.C.D", &idx)) { if (!inet_aton(argv[idx]->arg, &addr)) { vty_out(vty, "Please specify interface address by A.B.C.D\n"); return CMD_WARNING_CONFIG_FAILED; } params = ospf_lookup_if_params(ifp, addr); if (params == NULL) return CMD_SUCCESS; } UNSET_IF_PARAM(params, transmit_delay); params->transmit_delay = OSPF_TRANSMIT_DELAY_DEFAULT; if (params != IF_DEF_PARAMS(ifp)) { ospf_free_if_params(ifp, addr); ospf_if_update_params(ifp, addr); } return CMD_SUCCESS; } DEFUN_HIDDEN (no_ospf_transmit_delay, no_ospf_transmit_delay_cmd, "no ospf transmit-delay [(1-65535) [A.B.C.D]]", NO_STR "OSPF interface commands\n" "Link state transmit delay\n" "Seconds\n" "Address of interface\n") { return no_ip_ospf_transmit_delay(self, vty, argc, argv); } DEFUN (ip_ospf_area, ip_ospf_area_cmd, "ip ospf [(1-65535)] area [A.B.C.D]", "IP Information\n" "OSPF interface commands\n" "Instance ID\n" "Enable OSPF on this interface\n" "OSPF area ID in IP address format\n" "OSPF area ID as a decimal value\n" "Address of interface\n") { VTY_DECLVAR_CONTEXT(interface, ifp); int idx = 0; int format, ret; struct in_addr area_id; struct in_addr addr; struct ospf_if_params *params = NULL; struct route_node *rn; struct ospf *ospf = NULL; unsigned short instance = 0; char *areaid; if (argv_find(argv, argc, "(1-65535)", &idx)) instance = strtol(argv[idx]->arg, NULL, 10); argv_find(argv, argc, "area", &idx); areaid = argv[idx + 1]->arg; if (ifp->vrf_id && !instance) ospf = ospf_lookup_by_vrf_id(ifp->vrf_id); else ospf = ospf_lookup_instance(instance); if (instance && ospf == NULL) { params = IF_DEF_PARAMS(ifp); if (OSPF_IF_PARAM_CONFIGURED(params, if_area)) { UNSET_IF_PARAM(params, if_area); ospf = ospf_lookup_by_vrf_id(VRF_DEFAULT); ospf_interface_area_unset(ospf, ifp); ospf->if_ospf_cli_count--; } return CMD_NOT_MY_INSTANCE; } ret = str2area_id(areaid, &area_id, &format); if (ret < 0) { vty_out(vty, "Please specify area by A.B.C.D|<0-4294967295>\n"); return CMD_WARNING_CONFIG_FAILED; } if (memcmp(ifp->name, "VLINK", 5) == 0) { vty_out(vty, "Cannot enable OSPF on a virtual link.\n"); return CMD_WARNING_CONFIG_FAILED; } params = IF_DEF_PARAMS(ifp); if (OSPF_IF_PARAM_CONFIGURED(params, if_area) && !IPV4_ADDR_SAME(¶ms->if_area, &area_id)) { vty_out(vty, "Must remove previous area config before changing ospf area \n"); return CMD_WARNING_CONFIG_FAILED; } // Check if we have an address arg and proccess it if (argc == idx + 3) { if (!inet_aton(argv[idx + 2]->arg, &addr)) { vty_out(vty, "Please specify Intf Address by A.B.C.D\n"); return CMD_WARNING_CONFIG_FAILED; } // update/create address-level params params = ospf_get_if_params((ifp), (addr)); if (OSPF_IF_PARAM_CONFIGURED(params, if_area)) { vty_out(vty, "Must remove previous area/address config before changing ospf area"); return CMD_WARNING_CONFIG_FAILED; } ospf_if_update_params((ifp), (addr)); } if (ospf) { for (rn = route_top(ospf->networks); rn; rn = route_next(rn)) { if (rn->info != NULL) { vty_out(vty, "Please remove all network commands first.\n"); return CMD_WARNING_CONFIG_FAILED; } } } /* enable ospf on this interface with area_id */ if (params) { SET_IF_PARAM(params, if_area); params->if_area = area_id; params->if_area_id_fmt = format; } if (ospf) { ospf_interface_area_set(ospf, ifp); ospf->if_ospf_cli_count++; } return CMD_SUCCESS; } DEFUN (no_ip_ospf_area, no_ip_ospf_area_cmd, "no ip ospf [(1-65535)] area [ [A.B.C.D]]", NO_STR "IP Information\n" "OSPF interface commands\n" "Instance ID\n" "Disable OSPF on this interface\n" "OSPF area ID in IP address format\n" "OSPF area ID as a decimal value\n" "Address of interface\n") { VTY_DECLVAR_CONTEXT(interface, ifp); int idx = 0; struct ospf *ospf; struct ospf_if_params *params; unsigned short instance = 0; struct in_addr addr; if (argv_find(argv, argc, "(1-65535)", &idx)) instance = strtol(argv[idx]->arg, NULL, 10); if (ifp->vrf_id && !instance) ospf = ospf_lookup_by_vrf_id(ifp->vrf_id); else ospf = ospf_lookup_instance(instance); if (ospf == NULL) return CMD_NOT_MY_INSTANCE; argv_find(argv, argc, "area", &idx); // Check if we have an address arg and proccess it if (argc == idx + 3) { if (!inet_aton(argv[idx + 2]->arg, &addr)) { vty_out(vty, "Please specify Intf Address by A.B.C.D\n"); return CMD_WARNING_CONFIG_FAILED; } params = ospf_lookup_if_params(ifp, addr); if ((params) == NULL) return CMD_SUCCESS; } else params = IF_DEF_PARAMS(ifp); if (!OSPF_IF_PARAM_CONFIGURED(params, if_area)) { vty_out(vty, "Can't find specified interface area configuration.\n"); return CMD_WARNING_CONFIG_FAILED; } UNSET_IF_PARAM(params, if_area); if (params != IF_DEF_PARAMS((ifp))) { ospf_free_if_params((ifp), (addr)); ospf_if_update_params((ifp), (addr)); } ospf_interface_area_unset(ospf, ifp); ospf->if_ospf_cli_count--; return CMD_SUCCESS; } DEFUN (ospf_redistribute_source, ospf_redistribute_source_cmd, "redistribute " FRR_REDIST_STR_OSPFD " [{metric (0-16777214)|metric-type (1-2)|route-map WORD}]", REDIST_STR FRR_REDIST_HELP_STR_OSPFD "Metric for redistributed routes\n" "OSPF default metric\n" "OSPF exterior metric type for redistributed routes\n" "Set OSPF External Type 1/2 metrics\n" "Route map reference\n" "Pointer to route-map entries\n") { VTY_DECLVAR_INSTANCE_CONTEXT(ospf, ospf); int idx_protocol = 1; int source; int type = -1; int metric = -1; struct ospf_redist *red; int idx = 0; /* Get distribute source. */ source = proto_redistnum(AFI_IP, argv[idx_protocol]->text); if (source < 0) return CMD_WARNING_CONFIG_FAILED; red = ospf_redist_add(ospf, source, 0); /* Get metric value. */ if (argv_find(argv, argc, "(0-16777214)", &idx)) { if (!str2metric(argv[idx]->arg, &metric)) return CMD_WARNING_CONFIG_FAILED; } idx = 1; /* Get metric type. */ if (argv_find(argv, argc, "(1-2)", &idx)) { if (!str2metric_type(argv[idx]->arg, &type)) return CMD_WARNING_CONFIG_FAILED; } idx = 1; /* Get route-map */ if (argv_find(argv, argc, "WORD", &idx)) { ospf_routemap_set(red, argv[idx]->arg); } else ospf_routemap_unset(red); return ospf_redistribute_set(ospf, source, 0, type, metric); } DEFUN (no_ospf_redistribute_source, no_ospf_redistribute_source_cmd, "no redistribute " FRR_REDIST_STR_OSPFD " [{metric (0-16777214)|metric-type (1-2)|route-map WORD}]", NO_STR REDIST_STR FRR_REDIST_HELP_STR_OSPFD "Metric for redistributed routes\n" "OSPF default metric\n" "OSPF exterior metric type for redistributed routes\n" "Set OSPF External Type 1/2 metrics\n" "Route map reference\n" "Pointer to route-map entries\n") { VTY_DECLVAR_INSTANCE_CONTEXT(ospf, ospf); int idx_protocol = 2; int source; struct ospf_redist *red; source = proto_redistnum(AFI_IP, argv[idx_protocol]->text); if (source < 0) return CMD_WARNING_CONFIG_FAILED; red = ospf_redist_lookup(ospf, source, 0); if (!red) return CMD_SUCCESS; ospf_routemap_unset(red); ospf_redist_del(ospf, source, 0); return ospf_redistribute_unset(ospf, source, 0); } DEFUN (ospf_redistribute_instance_source, ospf_redistribute_instance_source_cmd, "redistribute (1-65535) [{metric (0-16777214)|metric-type (1-2)|route-map WORD}]", REDIST_STR "Open Shortest Path First\n" "Non-main Kernel Routing Table\n" "Instance ID/Table ID\n" "Metric for redistributed routes\n" "OSPF default metric\n" "OSPF exterior metric type for redistributed routes\n" "Set OSPF External Type 1/2 metrics\n" "Route map reference\n" "Pointer to route-map entries\n") { VTY_DECLVAR_INSTANCE_CONTEXT(ospf, ospf); int idx_ospf_table = 1; int idx_number = 2; int idx = 3; int source; int type = -1; int metric = -1; unsigned short instance; struct ospf_redist *red; source = proto_redistnum(AFI_IP, argv[idx_ospf_table]->text); if (source < 0) { vty_out(vty, "Unknown instance redistribution\n"); return CMD_WARNING_CONFIG_FAILED; } instance = strtoul(argv[idx_number]->arg, NULL, 10); if ((source == ZEBRA_ROUTE_OSPF) && !ospf->instance) { vty_out(vty, "Instance redistribution in non-instanced OSPF not allowed\n"); return CMD_WARNING_CONFIG_FAILED; } if ((source == ZEBRA_ROUTE_OSPF) && (ospf->instance == instance)) { vty_out(vty, "Same instance OSPF redistribution not allowed\n"); return CMD_WARNING_CONFIG_FAILED; } /* Get metric value. */ if (argv_find(argv, argc, "metric", &idx)) if (!str2metric(argv[idx + 1]->arg, &metric)) return CMD_WARNING_CONFIG_FAILED; idx = 3; /* Get metric type. */ if (argv_find(argv, argc, "metric-type", &idx)) if (!str2metric_type(argv[idx + 1]->arg, &type)) return CMD_WARNING_CONFIG_FAILED; red = ospf_redist_add(ospf, source, instance); idx = 3; if (argv_find(argv, argc, "route-map", &idx)) ospf_routemap_set(red, argv[idx + 1]->arg); else ospf_routemap_unset(red); return ospf_redistribute_set(ospf, source, instance, type, metric); } DEFUN (no_ospf_redistribute_instance_source, no_ospf_redistribute_instance_source_cmd, "no redistribute (1-65535) [{metric (0-16777214)|metric-type (1-2)|route-map WORD}]", NO_STR REDIST_STR "Open Shortest Path First\n" "Non-main Kernel Routing Table\n" "Instance ID/Table Id\n" "Metric for redistributed routes\n" "OSPF default metric\n" "OSPF exterior metric type for redistributed routes\n" "Set OSPF External Type 1/2 metrics\n" "Route map reference\n" "Pointer to route-map entries\n") { VTY_DECLVAR_INSTANCE_CONTEXT(ospf, ospf); int idx_ospf_table = 2; int idx_number = 3; unsigned int instance; struct ospf_redist *red; int source; if (strncmp(argv[idx_ospf_table]->arg, "o", 1) == 0) source = ZEBRA_ROUTE_OSPF; else source = ZEBRA_ROUTE_TABLE; instance = strtoul(argv[idx_number]->arg, NULL, 10); if ((source == ZEBRA_ROUTE_OSPF) && !ospf->instance) { vty_out(vty, "Instance redistribution in non-instanced OSPF not allowed\n"); return CMD_WARNING_CONFIG_FAILED; } if ((source == ZEBRA_ROUTE_OSPF) && (ospf->instance == instance)) { vty_out(vty, "Same instance OSPF redistribution not allowed\n"); return CMD_WARNING_CONFIG_FAILED; } red = ospf_redist_lookup(ospf, source, instance); if (!red) return CMD_SUCCESS; ospf_routemap_unset(red); ospf_redist_del(ospf, source, instance); return ospf_redistribute_unset(ospf, source, instance); } DEFUN (ospf_distribute_list_out, ospf_distribute_list_out_cmd, "distribute-list WORD out " FRR_REDIST_STR_OSPFD, "Filter networks in routing updates\n" "Access-list name\n" OUT_STR FRR_REDIST_HELP_STR_OSPFD) { VTY_DECLVAR_INSTANCE_CONTEXT(ospf, ospf); int idx_word = 1; int source; char *proto = argv[argc - 1]->text; /* Get distribute source. */ source = proto_redistnum(AFI_IP, proto); if (source < 0) return CMD_WARNING_CONFIG_FAILED; return ospf_distribute_list_out_set(ospf, source, argv[idx_word]->arg); } DEFUN (no_ospf_distribute_list_out, no_ospf_distribute_list_out_cmd, "no distribute-list WORD out " FRR_REDIST_STR_OSPFD, NO_STR "Filter networks in routing updates\n" "Access-list name\n" OUT_STR FRR_REDIST_HELP_STR_OSPFD) { VTY_DECLVAR_INSTANCE_CONTEXT(ospf, ospf); int idx_word = 2; int source; char *proto = argv[argc - 1]->text; source = proto_redistnum(AFI_IP, proto); if (source < 0) return CMD_WARNING_CONFIG_FAILED; return ospf_distribute_list_out_unset(ospf, source, argv[idx_word]->arg); } /* Default information originate. */ DEFUN (ospf_default_information_originate, ospf_default_information_originate_cmd, "default-information originate [{always|metric (0-16777214)|metric-type (1-2)|route-map WORD}]", "Control distribution of default information\n" "Distribute a default route\n" "Always advertise default route\n" "OSPF default metric\n" "OSPF metric\n" "OSPF metric type for default routes\n" "Set OSPF External Type 1/2 metrics\n" "Route map reference\n" "Pointer to route-map entries\n") { VTY_DECLVAR_INSTANCE_CONTEXT(ospf, ospf); int default_originate = DEFAULT_ORIGINATE_ZEBRA; int type = -1; int metric = -1; struct ospf_redist *red; int idx = 0; int cur_originate = ospf->default_originate; int sameRtmap = 0; char *rtmap = NULL; red = ospf_redist_add(ospf, DEFAULT_ROUTE, 0); /* Check whether "always" was specified */ if (argv_find(argv, argc, "always", &idx)) default_originate = DEFAULT_ORIGINATE_ALWAYS; idx = 1; /* Get metric value */ if (argv_find(argv, argc, "(0-16777214)", &idx)) { if (!str2metric(argv[idx]->arg, &metric)) return CMD_WARNING_CONFIG_FAILED; } idx = 1; /* Get metric type. */ if (argv_find(argv, argc, "(1-2)", &idx)) { if (!str2metric_type(argv[idx]->arg, &type)) return CMD_WARNING_CONFIG_FAILED; } idx = 1; /* Get route-map */ if (argv_find(argv, argc, "WORD", &idx)) rtmap = argv[idx]->arg; /* To check ,if user is providing same route map */ if ((rtmap == ROUTEMAP_NAME(red)) || (rtmap && ROUTEMAP_NAME(red) && (strcmp(rtmap, ROUTEMAP_NAME(red)) == 0))) sameRtmap = 1; /* Don't allow if the same lsa is aleardy originated. */ if ((sameRtmap) && (red->dmetric.type == type) && (red->dmetric.value == metric) && (cur_originate == default_originate)) return CMD_SUCCESS; /* Updating Metric details */ red->dmetric.type = type; red->dmetric.value = metric; /* updating route map details */ if (rtmap) ospf_routemap_set(red, rtmap); else ospf_routemap_unset(red); return ospf_redistribute_default_set(ospf, default_originate, type, metric); } DEFUN (no_ospf_default_information_originate, no_ospf_default_information_originate_cmd, "no default-information originate [{always|metric (0-16777214)|metric-type (1-2)|route-map WORD}]", NO_STR "Control distribution of default information\n" "Distribute a default route\n" "Always advertise default route\n" "OSPF default metric\n" "OSPF metric\n" "OSPF metric type for default routes\n" "Set OSPF External Type 1/2 metrics\n" "Route map reference\n" "Pointer to route-map entries\n") { VTY_DECLVAR_INSTANCE_CONTEXT(ospf, ospf); struct ospf_redist *red; red = ospf_redist_lookup(ospf, DEFAULT_ROUTE, 0); if (!red) return CMD_SUCCESS; ospf_routemap_unset(red); ospf_redist_del(ospf, DEFAULT_ROUTE, 0); return ospf_redistribute_default_set(ospf, DEFAULT_ORIGINATE_NONE, 0, 0); } DEFUN (ospf_default_metric, ospf_default_metric_cmd, "default-metric (0-16777214)", "Set metric of redistributed routes\n" "Default metric\n") { VTY_DECLVAR_INSTANCE_CONTEXT(ospf, ospf); int idx_number = 1; int metric = -1; if (!str2metric(argv[idx_number]->arg, &metric)) return CMD_WARNING_CONFIG_FAILED; ospf->default_metric = metric; return CMD_SUCCESS; } DEFUN (no_ospf_default_metric, no_ospf_default_metric_cmd, "no default-metric [(0-16777214)]", NO_STR "Set metric of redistributed routes\n" "Default metric\n") { VTY_DECLVAR_INSTANCE_CONTEXT(ospf, ospf); ospf->default_metric = -1; return CMD_SUCCESS; } DEFUN (ospf_distance, ospf_distance_cmd, "distance (1-255)", "Administrative distance\n" "OSPF Administrative distance\n") { VTY_DECLVAR_INSTANCE_CONTEXT(ospf, ospf); int idx_number = 1; ospf->distance_all = atoi(argv[idx_number]->arg); return CMD_SUCCESS; } DEFUN (no_ospf_distance, no_ospf_distance_cmd, "no distance (1-255)", NO_STR "Administrative distance\n" "OSPF Administrative distance\n") { VTY_DECLVAR_INSTANCE_CONTEXT(ospf, ospf); ospf->distance_all = 0; return CMD_SUCCESS; } DEFUN (no_ospf_distance_ospf, no_ospf_distance_ospf_cmd, "no distance ospf [{intra-area [(1-255)]|inter-area [(1-255)]|external [(1-255)]}]", NO_STR "Administrative distance\n" "OSPF administrative distance\n" "Intra-area routes\n" "Distance for intra-area routes\n" "Inter-area routes\n" "Distance for inter-area routes\n" "External routes\n" "Distance for external routes\n") { VTY_DECLVAR_INSTANCE_CONTEXT(ospf, ospf); int idx = 0; if (argv_find(argv, argc, "intra-area", &idx) || argc == 3) idx = ospf->distance_intra = 0; if (argv_find(argv, argc, "inter-area", &idx) || argc == 3) idx = ospf->distance_inter = 0; if (argv_find(argv, argc, "external", &idx) || argc == 3) ospf->distance_external = 0; return CMD_SUCCESS; } DEFUN (ospf_distance_ospf, ospf_distance_ospf_cmd, "distance ospf {intra-area (1-255)|inter-area (1-255)|external (1-255)}", "Administrative distance\n" "OSPF administrative distance\n" "Intra-area routes\n" "Distance for intra-area routes\n" "Inter-area routes\n" "Distance for inter-area routes\n" "External routes\n" "Distance for external routes\n") { VTY_DECLVAR_INSTANCE_CONTEXT(ospf, ospf); int idx = 0; ospf->distance_intra = 0; ospf->distance_inter = 0; ospf->distance_external = 0; if (argv_find(argv, argc, "intra-area", &idx)) ospf->distance_intra = atoi(argv[idx + 1]->arg); idx = 0; if (argv_find(argv, argc, "inter-area", &idx)) ospf->distance_inter = atoi(argv[idx + 1]->arg); idx = 0; if (argv_find(argv, argc, "external", &idx)) ospf->distance_external = atoi(argv[idx + 1]->arg); return CMD_SUCCESS; } #if 0 DEFUN (ospf_distance_source, ospf_distance_source_cmd, "distance (1-255) A.B.C.D/M", "Administrative distance\n" "Distance value\n" "IP source prefix\n") { VTY_DECLVAR_CONTEXT(ospf, ospf); int idx_number = 1; int idx_ipv4_prefixlen = 2; ospf_distance_set (vty, ospf, argv[idx_number]->arg, argv[idx_ipv4_prefixlen]->arg, NULL); return CMD_SUCCESS; } DEFUN (no_ospf_distance_source, no_ospf_distance_source_cmd, "no distance (1-255) A.B.C.D/M", NO_STR "Administrative distance\n" "Distance value\n" "IP source prefix\n") { VTY_DECLVAR_CONTEXT(ospf, ospf); int idx_number = 2; int idx_ipv4_prefixlen = 3; ospf_distance_unset (vty, ospf, argv[idx_number]->arg, argv[idx_ipv4_prefixlen]->arg, NULL); return CMD_SUCCESS; } DEFUN (ospf_distance_source_access_list, ospf_distance_source_access_list_cmd, "distance (1-255) A.B.C.D/M WORD", "Administrative distance\n" "Distance value\n" "IP source prefix\n" "Access list name\n") { VTY_DECLVAR_CONTEXT(ospf, ospf); int idx_number = 1; int idx_ipv4_prefixlen = 2; int idx_word = 3; ospf_distance_set (vty, ospf, argv[idx_number]->arg, argv[idx_ipv4_prefixlen]->arg, argv[idx_word]->arg); return CMD_SUCCESS; } DEFUN (no_ospf_distance_source_access_list, no_ospf_distance_source_access_list_cmd, "no distance (1-255) A.B.C.D/M WORD", NO_STR "Administrative distance\n" "Distance value\n" "IP source prefix\n" "Access list name\n") { VTY_DECLVAR_CONTEXT(ospf, ospf); int idx_number = 2; int idx_ipv4_prefixlen = 3; int idx_word = 4; ospf_distance_unset (vty, ospf, argv[idx_number]->arg, argv[idx_ipv4_prefixlen]->arg, argv[idx_word]->arg); return CMD_SUCCESS; } #endif DEFUN (ip_ospf_mtu_ignore, ip_ospf_mtu_ignore_addr_cmd, "ip ospf mtu-ignore [A.B.C.D]", "IP Information\n" "OSPF interface commands\n" "Disable MTU mismatch detection on this interface\n" "Address of interface\n") { VTY_DECLVAR_CONTEXT(interface, ifp); int idx_ipv4 = 3; struct in_addr addr; int ret; struct ospf_if_params *params; params = IF_DEF_PARAMS(ifp); if (argc == 4) { ret = inet_aton(argv[idx_ipv4]->arg, &addr); if (!ret) { vty_out(vty, "Please specify interface address by A.B.C.D\n"); return CMD_WARNING_CONFIG_FAILED; } params = ospf_get_if_params(ifp, addr); ospf_if_update_params(ifp, addr); } params->mtu_ignore = 1; if (params->mtu_ignore != OSPF_MTU_IGNORE_DEFAULT) SET_IF_PARAM(params, mtu_ignore); else { UNSET_IF_PARAM(params, mtu_ignore); if (params != IF_DEF_PARAMS(ifp)) { ospf_free_if_params(ifp, addr); ospf_if_update_params(ifp, addr); } } return CMD_SUCCESS; } DEFUN (no_ip_ospf_mtu_ignore, no_ip_ospf_mtu_ignore_addr_cmd, "no ip ospf mtu-ignore [A.B.C.D]", NO_STR "IP Information\n" "OSPF interface commands\n" "Disable MTU mismatch detection on this interface\n" "Address of interface\n") { VTY_DECLVAR_CONTEXT(interface, ifp); int idx_ipv4 = 4; struct in_addr addr; int ret; struct ospf_if_params *params; params = IF_DEF_PARAMS(ifp); if (argc == 5) { ret = inet_aton(argv[idx_ipv4]->arg, &addr); if (!ret) { vty_out(vty, "Please specify interface address by A.B.C.D\n"); return CMD_WARNING_CONFIG_FAILED; } params = ospf_get_if_params(ifp, addr); ospf_if_update_params(ifp, addr); } params->mtu_ignore = 0; if (params->mtu_ignore != OSPF_MTU_IGNORE_DEFAULT) SET_IF_PARAM(params, mtu_ignore); else { UNSET_IF_PARAM(params, mtu_ignore); if (params != IF_DEF_PARAMS(ifp)) { ospf_free_if_params(ifp, addr); ospf_if_update_params(ifp, addr); } } return CMD_SUCCESS; } DEFUN (ospf_max_metric_router_lsa_admin, ospf_max_metric_router_lsa_admin_cmd, "max-metric router-lsa administrative", "OSPF maximum / infinite-distance metric\n" "Advertise own Router-LSA with infinite distance (stub router)\n" "Administratively applied, for an indefinite period\n") { VTY_DECLVAR_INSTANCE_CONTEXT(ospf, ospf); struct listnode *ln; struct ospf_area *area; for (ALL_LIST_ELEMENTS_RO(ospf->areas, ln, area)) { SET_FLAG(area->stub_router_state, OSPF_AREA_ADMIN_STUB_ROUTED); if (!CHECK_FLAG(area->stub_router_state, OSPF_AREA_IS_STUB_ROUTED)) ospf_router_lsa_update_area(area); } /* Allows for areas configured later to get the property */ ospf->stub_router_admin_set = OSPF_STUB_ROUTER_ADMINISTRATIVE_SET; return CMD_SUCCESS; } DEFUN (no_ospf_max_metric_router_lsa_admin, no_ospf_max_metric_router_lsa_admin_cmd, "no max-metric router-lsa administrative", NO_STR "OSPF maximum / infinite-distance metric\n" "Advertise own Router-LSA with infinite distance (stub router)\n" "Administratively applied, for an indefinite period\n") { VTY_DECLVAR_INSTANCE_CONTEXT(ospf, ospf); struct listnode *ln; struct ospf_area *area; for (ALL_LIST_ELEMENTS_RO(ospf->areas, ln, area)) { UNSET_FLAG(area->stub_router_state, OSPF_AREA_ADMIN_STUB_ROUTED); /* Don't trample on the start-up stub timer */ if (CHECK_FLAG(area->stub_router_state, OSPF_AREA_IS_STUB_ROUTED) && !area->t_stub_router) { UNSET_FLAG(area->stub_router_state, OSPF_AREA_IS_STUB_ROUTED); ospf_router_lsa_update_area(area); } } ospf->stub_router_admin_set = OSPF_STUB_ROUTER_ADMINISTRATIVE_UNSET; return CMD_SUCCESS; } DEFUN (ospf_max_metric_router_lsa_startup, ospf_max_metric_router_lsa_startup_cmd, "max-metric router-lsa on-startup (5-86400)", "OSPF maximum / infinite-distance metric\n" "Advertise own Router-LSA with infinite distance (stub router)\n" "Automatically advertise stub Router-LSA on startup of OSPF\n" "Time (seconds) to advertise self as stub-router\n") { VTY_DECLVAR_INSTANCE_CONTEXT(ospf, ospf); int idx_number = 3; unsigned int seconds; if (argc < 4) { vty_out(vty, "%% Must supply stub-router period"); return CMD_WARNING_CONFIG_FAILED; } seconds = strtoul(argv[idx_number]->arg, NULL, 10); ospf->stub_router_startup_time = seconds; return CMD_SUCCESS; } DEFUN (no_ospf_max_metric_router_lsa_startup, no_ospf_max_metric_router_lsa_startup_cmd, "no max-metric router-lsa on-startup [(5-86400)]", NO_STR "OSPF maximum / infinite-distance metric\n" "Advertise own Router-LSA with infinite distance (stub router)\n" "Automatically advertise stub Router-LSA on startup of OSPF\n" "Time (seconds) to advertise self as stub-router\n") { VTY_DECLVAR_INSTANCE_CONTEXT(ospf, ospf); struct listnode *ln; struct ospf_area *area; ospf->stub_router_startup_time = OSPF_STUB_ROUTER_UNCONFIGURED; for (ALL_LIST_ELEMENTS_RO(ospf->areas, ln, area)) { SET_FLAG(area->stub_router_state, OSPF_AREA_WAS_START_STUB_ROUTED); OSPF_TIMER_OFF(area->t_stub_router); /* Don't trample on admin stub routed */ if (!CHECK_FLAG(area->stub_router_state, OSPF_AREA_ADMIN_STUB_ROUTED)) { UNSET_FLAG(area->stub_router_state, OSPF_AREA_IS_STUB_ROUTED); ospf_router_lsa_update_area(area); } } return CMD_SUCCESS; } DEFUN (ospf_max_metric_router_lsa_shutdown, ospf_max_metric_router_lsa_shutdown_cmd, "max-metric router-lsa on-shutdown (5-100)", "OSPF maximum / infinite-distance metric\n" "Advertise own Router-LSA with infinite distance (stub router)\n" "Advertise stub-router prior to full shutdown of OSPF\n" "Time (seconds) to wait till full shutdown\n") { VTY_DECLVAR_INSTANCE_CONTEXT(ospf, ospf); int idx_number = 3; unsigned int seconds; if (argc < 4) { vty_out(vty, "%% Must supply stub-router shutdown period"); return CMD_WARNING_CONFIG_FAILED; } seconds = strtoul(argv[idx_number]->arg, NULL, 10); ospf->stub_router_shutdown_time = seconds; return CMD_SUCCESS; } DEFUN (no_ospf_max_metric_router_lsa_shutdown, no_ospf_max_metric_router_lsa_shutdown_cmd, "no max-metric router-lsa on-shutdown [(5-100)]", NO_STR "OSPF maximum / infinite-distance metric\n" "Advertise own Router-LSA with infinite distance (stub router)\n" "Advertise stub-router prior to full shutdown of OSPF\n" "Time (seconds) to wait till full shutdown\n") { VTY_DECLVAR_INSTANCE_CONTEXT(ospf, ospf); ospf->stub_router_shutdown_time = OSPF_STUB_ROUTER_UNCONFIGURED; return CMD_SUCCESS; } static void config_write_stub_router(struct vty *vty, struct ospf *ospf) { struct listnode *ln; struct ospf_area *area; if (ospf->stub_router_startup_time != OSPF_STUB_ROUTER_UNCONFIGURED) vty_out(vty, " max-metric router-lsa on-startup %u\n", ospf->stub_router_startup_time); if (ospf->stub_router_shutdown_time != OSPF_STUB_ROUTER_UNCONFIGURED) vty_out(vty, " max-metric router-lsa on-shutdown %u\n", ospf->stub_router_shutdown_time); for (ALL_LIST_ELEMENTS_RO(ospf->areas, ln, area)) { if (CHECK_FLAG(area->stub_router_state, OSPF_AREA_ADMIN_STUB_ROUTED)) { vty_out(vty, " max-metric router-lsa administrative\n"); break; } } return; } static void show_ip_ospf_route_network(struct vty *vty, struct ospf *ospf, struct route_table *rt, json_object *json) { struct route_node *rn; struct ospf_route * or ; struct listnode *pnode, *pnnode; struct ospf_path *path; json_object *json_route = NULL, *json_nexthop_array = NULL, *json_nexthop = NULL; if (!json) vty_out(vty, "============ OSPF network routing table ============\n"); for (rn = route_top(rt); rn; rn = route_next(rn)) { if ((or = rn->info) == NULL) continue; char buf1[PREFIX2STR_BUFFER]; memset(buf1, 0, sizeof(buf1)); prefix2str(&rn->p, buf1, sizeof(buf1)); json_route = json_object_new_object(); if (json) { json_object_object_add(json, buf1, json_route); json_object_to_json_string_ext( json, JSON_C_TO_STRING_NOSLASHESCAPE); } switch (or->path_type) { case OSPF_PATH_INTER_AREA: if (or->type == OSPF_DESTINATION_NETWORK) { if (json) { json_object_string_add(json_route, "routeType", "N IA"); json_object_int_add(json_route, "cost", or->cost); json_object_string_add( json_route, "area", inet_ntoa(or->u.std.area_id)); } else { vty_out(vty, "N IA %-18s [%d] area: %s\n", buf1, or->cost, inet_ntoa(or->u.std.area_id)); } } else if (or->type == OSPF_DESTINATION_DISCARD) { if (json) { json_object_string_add(json_route, "routeType", "D IA"); } else { vty_out(vty, "D IA %-18s Discard entry\n", buf1); } } break; case OSPF_PATH_INTRA_AREA: if (json) { json_object_string_add(json_route, "routeType", "N"); json_object_int_add(json_route, "cost", or->cost); json_object_string_add( json_route, "area", inet_ntoa(or->u.std.area_id)); } else { vty_out(vty, "N %-18s [%d] area: %s\n", buf1, or->cost, inet_ntoa(or->u.std.area_id)); } break; default: break; } if (or->type == OSPF_DESTINATION_NETWORK) { if (json) { json_nexthop_array = json_object_new_array(); json_object_object_add(json_route, "nexthops", json_nexthop_array); } for (ALL_LIST_ELEMENTS(or->paths, pnode, pnnode, path)) { if (json) { json_nexthop = json_object_new_object(); json_object_array_add( json_nexthop_array, json_nexthop); } if (if_lookup_by_index(path->ifindex, ospf->vrf_id)) { if (path->nexthop.s_addr == 0) { if (json) { json_object_string_add( json_nexthop, "ip", " "); json_object_string_add( json_nexthop, "directly attached to", ifindex2ifname( path->ifindex, ospf->vrf_id)); } else { vty_out(vty, "%24s directly attached to %s\n", "", ifindex2ifname( path->ifindex, ospf->vrf_id)); } } else { if (json) { json_object_string_add( json_nexthop, "ip", inet_ntoa( path->nexthop)); json_object_string_add( json_nexthop, "via", ifindex2ifname( path->ifindex, ospf->vrf_id)); } else { vty_out(vty, "%24s via %s, %s\n", "", inet_ntoa( path->nexthop), ifindex2ifname( path->ifindex, ospf->vrf_id)); } } } } } if (!json) json_object_free(json_route); } if (!json) vty_out(vty, "\n"); } static void show_ip_ospf_route_router(struct vty *vty, struct ospf *ospf, struct route_table *rtrs, json_object *json) { struct route_node *rn; struct ospf_route * or ; struct listnode *pnode; struct listnode *node; struct ospf_path *path; json_object *json_route = NULL, *json_nexthop_array = NULL, *json_nexthop = NULL; if (!json) vty_out(vty, "============ OSPF router routing table =============\n"); for (rn = route_top(rtrs); rn; rn = route_next(rn)) { if (rn->info == NULL) continue; int flag = 0; json_route = json_object_new_object(); if (json) { json_object_object_add(json, inet_ntoa(rn->p.u.prefix4), json_route); json_object_string_add(json_route, "routeType", "R "); } else { vty_out(vty, "R %-15s ", inet_ntoa(rn->p.u.prefix4)); } for (ALL_LIST_ELEMENTS_RO((struct list *)rn->info, node, or)) { if (flag++) { if (!json) vty_out(vty, "%24s", ""); } /* Show path. */ if (json) { json_object_int_add(json_route, "cost", or->cost); json_object_string_add( json_route, "area", inet_ntoa(or->u.std.area_id)); if (or->path_type == OSPF_PATH_INTER_AREA) json_object_boolean_true_add(json_route, "IA"); if (or->u.std.flags & ROUTER_LSA_BORDER) json_object_string_add(json_route, "routerType", "abr"); else if (or->u.std.flags & ROUTER_LSA_EXTERNAL) json_object_string_add(json_route, "routerType", "asbr"); } else { vty_out(vty, "%s [%d] area: %s", (or->path_type == OSPF_PATH_INTER_AREA ? "IA" : " "), or->cost, inet_ntoa(or->u.std.area_id)); /* Show flags. */ vty_out(vty, "%s%s\n", (or->u.std.flags & ROUTER_LSA_BORDER ? ", ABR" : ""), (or->u.std.flags & ROUTER_LSA_EXTERNAL ? ", ASBR" : "")); } if (json) { json_nexthop_array = json_object_new_array(); json_object_object_add(json_route, "nexthops", json_nexthop_array); } for (ALL_LIST_ELEMENTS_RO(or->paths, pnode, path)) { if (json) { json_nexthop = json_object_new_object(); json_object_array_add( json_nexthop_array, json_nexthop); } if (if_lookup_by_index(path->ifindex, ospf->vrf_id)) { if (path->nexthop.s_addr == 0) { if (json) { json_object_string_add( json_nexthop, "ip", " "); json_object_string_add( json_nexthop, "directly attached to", ifindex2ifname( path->ifindex, ospf->vrf_id)); } else { vty_out(vty, "%24s directly attached to %s\n", "", ifindex2ifname( path->ifindex, ospf->vrf_id)); } } else { if (json) { json_object_string_add( json_nexthop, "ip", inet_ntoa( path->nexthop)); json_object_string_add( json_nexthop, "via", ifindex2ifname( path->ifindex, ospf->vrf_id)); } else { vty_out(vty, "%24s via %s, %s\n", "", inet_ntoa( path->nexthop), ifindex2ifname( path->ifindex, ospf->vrf_id)); } } } } } if (!json) json_object_free(json_route); } if (!json) vty_out(vty, "\n"); } static void show_ip_ospf_route_external(struct vty *vty, struct ospf *ospf, struct route_table *rt, json_object *json) { struct route_node *rn; struct ospf_route *er; struct listnode *pnode, *pnnode; struct ospf_path *path; json_object *json_route = NULL, *json_nexthop_array = NULL, *json_nexthop = NULL; if (!json) vty_out(vty, "============ OSPF external routing table ===========\n"); for (rn = route_top(rt); rn; rn = route_next(rn)) { if ((er = rn->info) == NULL) continue; char buf1[19]; snprintf(buf1, 19, "%s/%d", inet_ntoa(rn->p.u.prefix4), rn->p.prefixlen); json_route = json_object_new_object(); if (json) { json_object_object_add(json, buf1, json_route); json_object_to_json_string_ext( json, JSON_C_TO_STRING_NOSLASHESCAPE); } switch (er->path_type) { case OSPF_PATH_TYPE1_EXTERNAL: if (json) { json_object_string_add(json_route, "routeType", "N E1"); json_object_int_add(json_route, "cost", er->cost); } else { vty_out(vty, "N E1 %-18s [%d] tag: %" ROUTE_TAG_PRI "\n", buf1, er->cost, er->u.ext.tag); } break; case OSPF_PATH_TYPE2_EXTERNAL: if (json) { json_object_string_add(json_route, "routeType", "N E2"); json_object_int_add(json_route, "cost", er->cost); } else { vty_out(vty, "N E2 %-18s [%d/%d] tag: %" ROUTE_TAG_PRI "\n", buf1, er->cost, er->u.ext.type2_cost, er->u.ext.tag); } break; } if (json) { json_nexthop_array = json_object_new_array(); json_object_object_add(json_route, "nexthops", json_nexthop_array); } for (ALL_LIST_ELEMENTS(er->paths, pnode, pnnode, path)) { if (json) { json_nexthop = json_object_new_object(); json_object_array_add(json_nexthop_array, json_nexthop); } if (if_lookup_by_index(path->ifindex, ospf->vrf_id)) { if (path->nexthop.s_addr == 0) { if (json) { json_object_string_add( json_nexthop, "ip", " "); json_object_string_add( json_nexthop, "directly attached to", ifindex2ifname( path->ifindex, ospf->vrf_id)); } else { vty_out(vty, "%24s directly attached to %s\n", "", ifindex2ifname( path->ifindex, ospf->vrf_id)); } } else { if (json) { json_object_string_add( json_nexthop, "ip", inet_ntoa( path->nexthop)); json_object_string_add( json_nexthop, "via", ifindex2ifname( path->ifindex, ospf->vrf_id)); } else { vty_out(vty, "%24s via %s, %s\n", "", inet_ntoa( path->nexthop), ifindex2ifname( path->ifindex, ospf->vrf_id)); } } } } if (!json) json_object_free(json_route); } if (!json) vty_out(vty, "\n"); } static int show_ip_ospf_border_routers_common(struct vty *vty, struct ospf *ospf, uint8_t use_vrf) { if (ospf->instance) vty_out(vty, "\nOSPF Instance: %d\n\n", ospf->instance); ospf_show_vrf_name(ospf, vty, NULL, use_vrf); if (ospf->new_table == NULL) { vty_out(vty, "No OSPF routing information exist\n"); return CMD_SUCCESS; } /* Show Network routes. show_ip_ospf_route_network (vty, ospf->new_table); */ /* Show Router routes. */ show_ip_ospf_route_router(vty, ospf, ospf->new_rtrs, NULL); vty_out(vty, "\n"); return CMD_SUCCESS; } DEFUN (show_ip_ospf_border_routers, show_ip_ospf_border_routers_cmd, "show ip ospf [vrf ] border-routers", SHOW_STR IP_STR "OSPF information\n" VRF_CMD_HELP_STR "All VRFs\n" "Show all the ABR's and ASBR's\n") { struct ospf *ospf = NULL; struct listnode *node = NULL; char *vrf_name = NULL; bool all_vrf = false; int ret = CMD_SUCCESS; int inst = 0; int idx_vrf = 0; uint8_t use_vrf = 0; OSPF_FIND_VRF_ARGS(argv, argc, idx_vrf, vrf_name, all_vrf); if (vrf_name) { bool ospf_output = false; use_vrf = 1; if (all_vrf) { for (ALL_LIST_ELEMENTS_RO(om->ospf, node, ospf)) { if (!ospf->oi_running) continue; ospf_output = true; ret = show_ip_ospf_border_routers_common( vty, ospf, use_vrf); } if (!ospf_output) vty_out(vty, "%% OSPF instance not found\n"); } else { ospf = ospf_lookup_by_inst_name(inst, vrf_name); if (ospf == NULL || !ospf->oi_running) { vty_out(vty, "%% OSPF instance not found\n"); return CMD_SUCCESS; } ret = show_ip_ospf_border_routers_common(vty, ospf, use_vrf); } } else { /* Display default ospf (instance 0) info */ ospf = ospf_lookup_by_vrf_id(VRF_DEFAULT); if (ospf == NULL || !ospf->oi_running) { vty_out(vty, "%% OSPF instance not found\n"); return CMD_SUCCESS; } ret = show_ip_ospf_border_routers_common(vty, ospf, use_vrf); } return ret; } DEFUN (show_ip_ospf_instance_border_routers, show_ip_ospf_instance_border_routers_cmd, "show ip ospf (1-65535) border-routers", SHOW_STR IP_STR "OSPF information\n" "Instance ID\n" "Show all the ABR's and ASBR's\n") { int idx_number = 3; struct ospf *ospf; unsigned short instance = 0; instance = strtoul(argv[idx_number]->arg, NULL, 10); ospf = ospf_lookup_instance(instance); if (ospf == NULL) return CMD_NOT_MY_INSTANCE; if (!ospf->oi_running) return CMD_SUCCESS; return show_ip_ospf_border_routers_common(vty, ospf, 0); } static int show_ip_ospf_route_common(struct vty *vty, struct ospf *ospf, json_object *json, uint8_t use_vrf) { json_object *json_vrf = NULL; if (ospf->instance) vty_out(vty, "\nOSPF Instance: %d\n\n", ospf->instance); if (json) { if (use_vrf) json_vrf = json_object_new_object(); else json_vrf = json; } ospf_show_vrf_name(ospf, vty, json_vrf, use_vrf); if (ospf->new_table == NULL) { vty_out(vty, "No OSPF routing information exist\n"); return CMD_SUCCESS; } /* Show Network routes. */ show_ip_ospf_route_network(vty, ospf, ospf->new_table, json_vrf); /* Show Router routes. */ show_ip_ospf_route_router(vty, ospf, ospf->new_rtrs, json_vrf); /* Show AS External routes. */ show_ip_ospf_route_external(vty, ospf, ospf->old_external_route, json_vrf); if (json) { if (use_vrf) { // json_object_object_add(json_vrf, "areas", // json_areas); if (ospf->vrf_id == VRF_DEFAULT) json_object_object_add(json, "default", json_vrf); else json_object_object_add(json, ospf->name, json_vrf); } } else { vty_out(vty, "\n"); } return CMD_SUCCESS; } DEFUN (show_ip_ospf_route, show_ip_ospf_route_cmd, "show ip ospf [vrf ] route [json]", SHOW_STR IP_STR "OSPF information\n" VRF_CMD_HELP_STR "All VRFs\n" "OSPF routing table\n" JSON_STR) { struct ospf *ospf = NULL; struct listnode *node = NULL; char *vrf_name = NULL; bool all_vrf = false; int ret = CMD_SUCCESS; int inst = 0; int idx_vrf = 0; uint8_t use_vrf = 0; bool uj = use_json(argc, argv); json_object *json = NULL; if (uj) json = json_object_new_object(); OSPF_FIND_VRF_ARGS(argv, argc, idx_vrf, vrf_name, all_vrf); /* vrf input is provided could be all or specific vrf*/ if (vrf_name) { bool ospf_output = false; use_vrf = 1; if (all_vrf) { for (ALL_LIST_ELEMENTS_RO(om->ospf, node, ospf)) { if (!ospf->oi_running) continue; ospf_output = true; ret = show_ip_ospf_route_common(vty, ospf, json, use_vrf); } if (uj) { /* Keep Non-pretty format */ vty_out(vty, "%s\n", json_object_to_json_string(json)); json_object_free(json); } else if (!ospf_output) vty_out(vty, "%% OSPF instance not found\n"); return ret; } ospf = ospf_lookup_by_inst_name(inst, vrf_name); if (ospf == NULL || !ospf->oi_running) { if (uj) { vty_out(vty, "%s\n", json_object_to_json_string_ext( json, JSON_C_TO_STRING_PRETTY)); json_object_free(json); } else vty_out(vty, "%% OSPF instance not found\n"); return CMD_SUCCESS; } } else { /* Display default ospf (instance 0) info */ ospf = ospf_lookup_by_vrf_id(VRF_DEFAULT); if (ospf == NULL || !ospf->oi_running) { if (uj) { vty_out(vty, "%s\n", json_object_to_json_string_ext( json, JSON_C_TO_STRING_PRETTY)); json_object_free(json); } else vty_out(vty, "%% OSPF instance not found\n"); return CMD_SUCCESS; } } if (ospf) { ret = show_ip_ospf_route_common(vty, ospf, json, use_vrf); /* Keep Non-pretty format */ if (uj) vty_out(vty, "%s\n", json_object_to_json_string(json)); } if (uj) json_object_free(json); return ret; } DEFUN (show_ip_ospf_instance_route, show_ip_ospf_instance_route_cmd, "show ip ospf (1-65535) route", SHOW_STR IP_STR "OSPF information\n" "Instance ID\n" "OSPF routing table\n") { int idx_number = 3; struct ospf *ospf; unsigned short instance = 0; instance = strtoul(argv[idx_number]->arg, NULL, 10); ospf = ospf_lookup_instance(instance); if (ospf == NULL) return CMD_NOT_MY_INSTANCE; if (!ospf->oi_running) return CMD_SUCCESS; return show_ip_ospf_route_common(vty, ospf, NULL, 0); } DEFUN (show_ip_ospf_vrfs, show_ip_ospf_vrfs_cmd, "show ip ospf vrfs [json]", SHOW_STR IP_STR "OSPF information\n" "Show OSPF VRFs \n" JSON_STR) { bool uj = use_json(argc, argv); json_object *json = NULL; json_object *json_vrfs = NULL; struct ospf *ospf = NULL; struct listnode *node = NULL; int count = 0; static char header[] = "Name Id RouterId "; if (uj) { json = json_object_new_object(); json_vrfs = json_object_new_object(); } for (ALL_LIST_ELEMENTS_RO(om->ospf, node, ospf)) { json_object *json_vrf = NULL; const char *name = NULL; int64_t vrf_id_ui = 0; count++; if (!uj && count == 1) vty_out(vty, "%s\n", header); if (uj) json_vrf = json_object_new_object(); if (ospf->vrf_id == 0) name = VRF_DEFAULT_NAME; else name = ospf->name; vrf_id_ui = (ospf->vrf_id == VRF_UNKNOWN) ? -1 : (int64_t)ospf->vrf_id; if (uj) { json_object_int_add(json_vrf, "vrfId", vrf_id_ui); json_object_string_add(json_vrf, "routerId", inet_ntoa(ospf->router_id)); json_object_object_add(json_vrfs, name, json_vrf); } else { vty_out(vty, "%-25s %-5d %-16s \n", name, ospf->vrf_id, inet_ntoa(ospf->router_id)); } } if (uj) { json_object_object_add(json, "vrfs", json_vrfs); json_object_int_add(json, "totalVrfs", count); vty_out(vty, "%s\n", json_object_to_json_string_ext( json, JSON_C_TO_STRING_PRETTY)); json_object_free(json); } else { if (count) vty_out(vty, "\nTotal number of OSPF VRFs: %d\n", count); } return CMD_SUCCESS; } const char *ospf_abr_type_str[] = {"unknown", "standard", "ibm", "cisco", "shortcut"}; const char *ospf_shortcut_mode_str[] = {"default", "enable", "disable"}; const char *ospf_int_type_str[] = {"unknown", /* should never be used. */ "point-to-point", "broadcast", "non-broadcast", "point-to-multipoint", "virtual-link", /* should never be used. */ "loopback"}; static int config_write_interface_one(struct vty *vty, struct vrf *vrf) { struct listnode *node; struct interface *ifp; struct crypt_key *ck; struct route_node *rn = NULL; struct ospf_if_params *params; int write = 0; struct ospf *ospf = vrf->info; FOR_ALL_INTERFACES (vrf, ifp) { if (memcmp(ifp->name, "VLINK", 5) == 0) continue; vty_frame(vty, "!\n"); if (ifp->vrf_id == VRF_DEFAULT) vty_frame(vty, "interface %s\n", ifp->name); else vty_frame(vty, "interface %s vrf %s\n", ifp->name, vrf->name); if (ifp->desc) vty_out(vty, " description %s\n", ifp->desc); write++; params = IF_DEF_PARAMS(ifp); do { /* Interface Network print. */ if (OSPF_IF_PARAM_CONFIGURED(params, type) && params->type != OSPF_IFTYPE_LOOPBACK) { if (params->type != ospf_default_iftype(ifp)) { vty_out(vty, " ip ospf network %s", ospf_int_type_str [params->type]); if (params != IF_DEF_PARAMS(ifp) && rn) vty_out(vty, " %s", inet_ntoa( rn->p.u.prefix4)); vty_out(vty, "\n"); } } /* OSPF interface authentication print */ if (OSPF_IF_PARAM_CONFIGURED(params, auth_type) && params->auth_type != OSPF_AUTH_NOTSET) { const char *auth_str; /* Translation tables are not that much help * here due to syntax * of the simple option */ switch (params->auth_type) { case OSPF_AUTH_NULL: auth_str = " null"; break; case OSPF_AUTH_SIMPLE: auth_str = ""; break; case OSPF_AUTH_CRYPTOGRAPHIC: auth_str = " message-digest"; break; default: auth_str = ""; break; } vty_out(vty, " ip ospf authentication%s", auth_str); if (params != IF_DEF_PARAMS(ifp) && rn) vty_out(vty, " %s", inet_ntoa(rn->p.u.prefix4)); vty_out(vty, "\n"); } /* Simple Authentication Password print. */ if (OSPF_IF_PARAM_CONFIGURED(params, auth_simple) && params->auth_simple[0] != '\0') { vty_out(vty, " ip ospf authentication-key %s", params->auth_simple); if (params != IF_DEF_PARAMS(ifp) && rn) vty_out(vty, " %s", inet_ntoa(rn->p.u.prefix4)); vty_out(vty, "\n"); } /* Cryptographic Authentication Key print. */ if (params && params->auth_crypt) { for (ALL_LIST_ELEMENTS_RO(params->auth_crypt, node, ck)) { vty_out(vty, " ip ospf message-digest-key %d md5 %s", ck->key_id, ck->auth_key); if (params != IF_DEF_PARAMS(ifp) && rn) vty_out(vty, " %s", inet_ntoa( rn->p.u.prefix4)); vty_out(vty, "\n"); } } /* Interface Output Cost print. */ if (OSPF_IF_PARAM_CONFIGURED(params, output_cost_cmd)) { vty_out(vty, " ip ospf cost %u", params->output_cost_cmd); if (params != IF_DEF_PARAMS(ifp) && rn) vty_out(vty, " %s", inet_ntoa(rn->p.u.prefix4)); vty_out(vty, "\n"); } /* Hello Interval print. */ if (OSPF_IF_PARAM_CONFIGURED(params, v_hello) && params->v_hello != OSPF_HELLO_INTERVAL_DEFAULT) { vty_out(vty, " ip ospf hello-interval %u", params->v_hello); if (params != IF_DEF_PARAMS(ifp) && rn) vty_out(vty, " %s", inet_ntoa(rn->p.u.prefix4)); vty_out(vty, "\n"); } /* Router Dead Interval print. */ if (OSPF_IF_PARAM_CONFIGURED(params, v_wait) && params->v_wait != OSPF_ROUTER_DEAD_INTERVAL_DEFAULT) { vty_out(vty, " ip ospf dead-interval "); /* fast hello ? */ if (OSPF_IF_PARAM_CONFIGURED(params, fast_hello)) vty_out(vty, "minimal hello-multiplier %d", params->fast_hello); else vty_out(vty, "%u", params->v_wait); if (params != IF_DEF_PARAMS(ifp) && rn) vty_out(vty, " %s", inet_ntoa(rn->p.u.prefix4)); vty_out(vty, "\n"); } /* Router Priority print. */ if (OSPF_IF_PARAM_CONFIGURED(params, priority) && params->priority != OSPF_ROUTER_PRIORITY_DEFAULT) { vty_out(vty, " ip ospf priority %u", params->priority); if (params != IF_DEF_PARAMS(ifp) && rn) vty_out(vty, " %s", inet_ntoa(rn->p.u.prefix4)); vty_out(vty, "\n"); } /* Retransmit Interval print. */ if (OSPF_IF_PARAM_CONFIGURED(params, retransmit_interval) && params->retransmit_interval != OSPF_RETRANSMIT_INTERVAL_DEFAULT) { vty_out(vty, " ip ospf retransmit-interval %u", params->retransmit_interval); if (params != IF_DEF_PARAMS(ifp) && rn) vty_out(vty, " %s", inet_ntoa(rn->p.u.prefix4)); vty_out(vty, "\n"); } /* Transmit Delay print. */ if (OSPF_IF_PARAM_CONFIGURED(params, transmit_delay) && params->transmit_delay != OSPF_TRANSMIT_DELAY_DEFAULT) { vty_out(vty, " ip ospf transmit-delay %u", params->transmit_delay); if (params != IF_DEF_PARAMS(ifp) && rn) vty_out(vty, " %s", inet_ntoa(rn->p.u.prefix4)); vty_out(vty, "\n"); } /* Area print. */ if (OSPF_IF_PARAM_CONFIGURED(params, if_area)) { if (ospf && ospf->instance) vty_out(vty, " ip ospf %d", ospf->instance); else vty_out(vty, " ip ospf"); char buf[INET_ADDRSTRLEN]; area_id2str(buf, sizeof(buf), ¶ms->if_area, params->if_area_id_fmt); vty_out(vty, " area %s", buf); if (params != IF_DEF_PARAMS(ifp) && rn) vty_out(vty, " %s", inet_ntoa(rn->p.u.prefix4)); vty_out(vty, "\n"); } /* bfd print. */ if (params && params->bfd_info) ospf_bfd_write_config(vty, params); /* MTU ignore print. */ if (OSPF_IF_PARAM_CONFIGURED(params, mtu_ignore) && params->mtu_ignore != OSPF_MTU_IGNORE_DEFAULT) { if (params->mtu_ignore == 0) vty_out(vty, " no ip ospf mtu-ignore"); else vty_out(vty, " ip ospf mtu-ignore"); if (params != IF_DEF_PARAMS(ifp) && rn) vty_out(vty, " %s", inet_ntoa(rn->p.u.prefix4)); vty_out(vty, "\n"); } while (1) { if (rn == NULL) rn = route_top(IF_OIFS_PARAMS(ifp)); else rn = route_next(rn); if (rn == NULL) break; params = rn->info; if (params != NULL) break; } } while (rn); ospf_opaque_config_write_if(vty, ifp); vty_endframe(vty, NULL); } return write; } /* Configuration write function for ospfd. */ static int config_write_interface(struct vty *vty) { int write = 0; struct vrf *vrf = NULL; /* Display all VRF aware OSPF interface configuration */ RB_FOREACH (vrf, vrf_name_head, &vrfs_by_name) { write += config_write_interface_one(vty, vrf); } return write; } static int config_write_network_area(struct vty *vty, struct ospf *ospf) { struct route_node *rn; uint8_t buf[INET_ADDRSTRLEN]; /* `network area' print. */ for (rn = route_top(ospf->networks); rn; rn = route_next(rn)) if (rn->info) { struct ospf_network *n = rn->info; /* Create Area ID string by specified Area ID format. */ if (n->area_id_fmt == OSPF_AREA_ID_FMT_DOTTEDQUAD) inet_ntop(AF_INET, &n->area_id, (char *)buf, sizeof(buf)); else sprintf((char *)buf, "%lu", (unsigned long int)ntohl( n->area_id.s_addr)); /* Network print. */ vty_out(vty, " network %s/%d area %s\n", inet_ntoa(rn->p.u.prefix4), rn->p.prefixlen, buf); } return 0; } static int config_write_ospf_area(struct vty *vty, struct ospf *ospf) { struct listnode *node; struct ospf_area *area; uint8_t buf[INET_ADDRSTRLEN]; /* Area configuration print. */ for (ALL_LIST_ELEMENTS_RO(ospf->areas, node, area)) { struct route_node *rn1; area_id2str((char *)buf, sizeof(buf), &area->area_id, area->area_id_fmt); if (area->auth_type != OSPF_AUTH_NULL) { if (area->auth_type == OSPF_AUTH_SIMPLE) vty_out(vty, " area %s authentication\n", buf); else vty_out(vty, " area %s authentication message-digest\n", buf); } if (area->shortcut_configured != OSPF_SHORTCUT_DEFAULT) vty_out(vty, " area %s shortcut %s\n", buf, ospf_shortcut_mode_str [area->shortcut_configured]); if ((area->external_routing == OSPF_AREA_STUB) || (area->external_routing == OSPF_AREA_NSSA)) { if (area->external_routing == OSPF_AREA_STUB) { vty_out(vty, " area %s stub", buf); if (area->no_summary) vty_out(vty, " no-summary\n"); vty_out(vty, "\n"); } else if (area->external_routing == OSPF_AREA_NSSA) { switch (area->NSSATranslatorRole) { case OSPF_NSSA_ROLE_NEVER: vty_out(vty, " area %s nssa translate-never\n", buf); break; case OSPF_NSSA_ROLE_ALWAYS: vty_out(vty, " area %s nssa translate-always\n", buf); break; case OSPF_NSSA_ROLE_CANDIDATE: vty_out(vty, " area %s nssa \n", buf); break; } if (area->no_summary) vty_out(vty, " area %s nssa no-summary\n", buf); } if (area->default_cost != 1) vty_out(vty, " area %s default-cost %d\n", buf, area->default_cost); } for (rn1 = route_top(area->ranges); rn1; rn1 = route_next(rn1)) if (rn1->info) { struct ospf_area_range *range = rn1->info; vty_out(vty, " area %s range %s/%d", buf, inet_ntoa(rn1->p.u.prefix4), rn1->p.prefixlen); if (range->cost_config != OSPF_AREA_RANGE_COST_UNSPEC) vty_out(vty, " cost %d", range->cost_config); if (!CHECK_FLAG(range->flags, OSPF_AREA_RANGE_ADVERTISE)) vty_out(vty, " not-advertise"); if (CHECK_FLAG(range->flags, OSPF_AREA_RANGE_SUBSTITUTE)) vty_out(vty, " substitute %s/%d", inet_ntoa(range->subst_addr), range->subst_masklen); vty_out(vty, "\n"); } if (EXPORT_NAME(area)) vty_out(vty, " area %s export-list %s\n", buf, EXPORT_NAME(area)); if (IMPORT_NAME(area)) vty_out(vty, " area %s import-list %s\n", buf, IMPORT_NAME(area)); if (PREFIX_NAME_IN(area)) vty_out(vty, " area %s filter-list prefix %s in\n", buf, PREFIX_NAME_IN(area)); if (PREFIX_NAME_OUT(area)) vty_out(vty, " area %s filter-list prefix %s out\n", buf, PREFIX_NAME_OUT(area)); } return 0; } static int config_write_ospf_nbr_nbma(struct vty *vty, struct ospf *ospf) { struct ospf_nbr_nbma *nbr_nbma; struct route_node *rn; /* Static Neighbor configuration print. */ for (rn = route_top(ospf->nbr_nbma); rn; rn = route_next(rn)) if ((nbr_nbma = rn->info)) { vty_out(vty, " neighbor %s", inet_ntoa(nbr_nbma->addr)); if (nbr_nbma->priority != OSPF_NEIGHBOR_PRIORITY_DEFAULT) vty_out(vty, " priority %d", nbr_nbma->priority); if (nbr_nbma->v_poll != OSPF_POLL_INTERVAL_DEFAULT) vty_out(vty, " poll-interval %d", nbr_nbma->v_poll); vty_out(vty, "\n"); } return 0; } static int config_write_virtual_link(struct vty *vty, struct ospf *ospf) { struct listnode *node; struct ospf_vl_data *vl_data; char buf[INET_ADDRSTRLEN]; /* Virtual-Link print */ for (ALL_LIST_ELEMENTS_RO(ospf->vlinks, node, vl_data)) { struct listnode *n2; struct crypt_key *ck; struct ospf_interface *oi; if (vl_data != NULL) { area_id2str(buf, sizeof(buf), &vl_data->vl_area_id, vl_data->vl_area_id_fmt); oi = vl_data->vl_oi; /* timers */ if (OSPF_IF_PARAM(oi, v_hello) != OSPF_HELLO_INTERVAL_DEFAULT || OSPF_IF_PARAM(oi, v_wait) != OSPF_ROUTER_DEAD_INTERVAL_DEFAULT || OSPF_IF_PARAM(oi, retransmit_interval) != OSPF_RETRANSMIT_INTERVAL_DEFAULT || OSPF_IF_PARAM(oi, transmit_delay) != OSPF_TRANSMIT_DELAY_DEFAULT) vty_out(vty, " area %s virtual-link %s hello-interval %d retransmit-interval %d transmit-delay %d dead-interval %d\n", buf, inet_ntoa(vl_data->vl_peer), OSPF_IF_PARAM(oi, v_hello), OSPF_IF_PARAM(oi, retransmit_interval), OSPF_IF_PARAM(oi, transmit_delay), OSPF_IF_PARAM(oi, v_wait)); else vty_out(vty, " area %s virtual-link %s\n", buf, inet_ntoa(vl_data->vl_peer)); /* Auth key */ if (IF_DEF_PARAMS(vl_data->vl_oi->ifp)->auth_simple[0] != '\0') vty_out(vty, " area %s virtual-link %s authentication-key %s\n", buf, inet_ntoa(vl_data->vl_peer), IF_DEF_PARAMS(vl_data->vl_oi->ifp) ->auth_simple); /* md5 keys */ for (ALL_LIST_ELEMENTS_RO( IF_DEF_PARAMS(vl_data->vl_oi->ifp) ->auth_crypt, n2, ck)) vty_out(vty, " area %s virtual-link %s" " message-digest-key %d md5 %s\n", buf, inet_ntoa(vl_data->vl_peer), ck->key_id, ck->auth_key); } } return 0; } static int config_write_ospf_redistribute(struct vty *vty, struct ospf *ospf) { int type; /* redistribute print. */ for (type = 0; type < ZEBRA_ROUTE_MAX; type++) { struct list *red_list; struct listnode *node; struct ospf_redist *red; red_list = ospf->redist[type]; if (!red_list) continue; for (ALL_LIST_ELEMENTS_RO(red_list, node, red)) { vty_out(vty, " redistribute %s", zebra_route_string(type)); if (red->instance) vty_out(vty, " %d", red->instance); if (red->dmetric.value >= 0) vty_out(vty, " metric %d", red->dmetric.value); if (red->dmetric.type == EXTERNAL_METRIC_TYPE_1) vty_out(vty, " metric-type 1"); if (ROUTEMAP_NAME(red)) vty_out(vty, " route-map %s", ROUTEMAP_NAME(red)); vty_out(vty, "\n"); } } return 0; } static int config_write_ospf_default_metric(struct vty *vty, struct ospf *ospf) { if (ospf->default_metric != -1) vty_out(vty, " default-metric %d\n", ospf->default_metric); return 0; } static int config_write_ospf_distribute(struct vty *vty, struct ospf *ospf) { int type; struct ospf_redist *red; if (ospf) { /* distribute-list print. */ for (type = 0; type < ZEBRA_ROUTE_MAX; type++) if (DISTRIBUTE_NAME(ospf, type)) vty_out(vty, " distribute-list %s out %s\n", DISTRIBUTE_NAME(ospf, type), zebra_route_string(type)); /* default-information print. */ if (ospf->default_originate != DEFAULT_ORIGINATE_NONE) { vty_out(vty, " default-information originate"); if (ospf->default_originate == DEFAULT_ORIGINATE_ALWAYS) vty_out(vty, " always"); red = ospf_redist_lookup(ospf, DEFAULT_ROUTE, 0); if (red) { if (red->dmetric.value >= 0) vty_out(vty, " metric %d", red->dmetric.value); if (red->dmetric.type == EXTERNAL_METRIC_TYPE_1) vty_out(vty, " metric-type 1"); if (ROUTEMAP_NAME(red)) vty_out(vty, " route-map %s", ROUTEMAP_NAME(red)); } vty_out(vty, "\n"); } } return 0; } static int config_write_ospf_distance(struct vty *vty, struct ospf *ospf) { struct route_node *rn; struct ospf_distance *odistance; if (ospf->distance_all) vty_out(vty, " distance %d\n", ospf->distance_all); if (ospf->distance_intra || ospf->distance_inter || ospf->distance_external) { vty_out(vty, " distance ospf"); if (ospf->distance_intra) vty_out(vty, " intra-area %d", ospf->distance_intra); if (ospf->distance_inter) vty_out(vty, " inter-area %d", ospf->distance_inter); if (ospf->distance_external) vty_out(vty, " external %d", ospf->distance_external); vty_out(vty, "\n"); } for (rn = route_top(ospf->distance_table); rn; rn = route_next(rn)) if ((odistance = rn->info) != NULL) { vty_out(vty, " distance %d %s/%d %s\n", odistance->distance, inet_ntoa(rn->p.u.prefix4), rn->p.prefixlen, odistance->access_list ? odistance->access_list : ""); } return 0; } static int ospf_config_write_one(struct vty *vty, struct ospf *ospf) { struct vrf *vrf = vrf_lookup_by_id(ospf->vrf_id); struct interface *ifp; struct ospf_interface *oi; struct listnode *node = NULL; int write = 0; /* `router ospf' print. */ if (ospf->instance && ospf->name) { vty_out(vty, "router ospf %d vrf %s\n", ospf->instance, ospf->name); } else if (ospf->instance) { vty_out(vty, "router ospf %d\n", ospf->instance); } else if (ospf->name) { vty_out(vty, "router ospf vrf %s\n", ospf->name); } else vty_out(vty, "router ospf\n"); if (!ospf->networks) { write++; return write; } /* Router ID print. */ if (ospf->router_id_static.s_addr != 0) vty_out(vty, " ospf router-id %s\n", inet_ntoa(ospf->router_id_static)); /* ABR type print. */ if (ospf->abr_type != OSPF_ABR_DEFAULT) vty_out(vty, " ospf abr-type %s\n", ospf_abr_type_str[ospf->abr_type]); /* log-adjacency-changes flag print. */ if (CHECK_FLAG(ospf->config, OSPF_LOG_ADJACENCY_CHANGES)) { if (CHECK_FLAG(ospf->config, OSPF_LOG_ADJACENCY_DETAIL)) vty_out(vty, " log-adjacency-changes detail\n"); else if (!DFLT_OSPF_LOG_ADJACENCY_CHANGES) vty_out(vty, " log-adjacency-changes\n"); } else if (DFLT_OSPF_LOG_ADJACENCY_CHANGES) { vty_out(vty, " no log-adjacency-changes\n"); } /* RFC1583 compatibility flag print -- Compatible with CISCO * 12.1. */ if (CHECK_FLAG(ospf->config, OSPF_RFC1583_COMPATIBLE)) vty_out(vty, " compatible rfc1583\n"); /* auto-cost reference-bandwidth configuration. */ if (ospf->ref_bandwidth != OSPF_DEFAULT_REF_BANDWIDTH) { vty_out(vty, "! Important: ensure reference bandwidth " "is consistent across all routers\n"); vty_out(vty, " auto-cost reference-bandwidth %d\n", ospf->ref_bandwidth); } /* SPF timers print. */ if (ospf->spf_delay != OSPF_SPF_DELAY_DEFAULT || ospf->spf_holdtime != OSPF_SPF_HOLDTIME_DEFAULT || ospf->spf_max_holdtime != OSPF_SPF_MAX_HOLDTIME_DEFAULT) vty_out(vty, " timers throttle spf %d %d %d\n", ospf->spf_delay, ospf->spf_holdtime, ospf->spf_max_holdtime); /* LSA timers print. */ if (ospf->min_ls_interval != OSPF_MIN_LS_INTERVAL) vty_out(vty, " timers throttle lsa all %d\n", ospf->min_ls_interval); if (ospf->min_ls_arrival != OSPF_MIN_LS_ARRIVAL) vty_out(vty, " timers lsa min-arrival %d\n", ospf->min_ls_arrival); /* Write multiplier print. */ if (ospf->write_oi_count != OSPF_WRITE_INTERFACE_COUNT_DEFAULT) vty_out(vty, " ospf write-multiplier %d\n", ospf->write_oi_count); /* Max-metric router-lsa print */ config_write_stub_router(vty, ospf); /* SPF refresh parameters print. */ if (ospf->lsa_refresh_interval != OSPF_LSA_REFRESH_INTERVAL_DEFAULT) vty_out(vty, " refresh timer %d\n", ospf->lsa_refresh_interval); /* Redistribute information print. */ config_write_ospf_redistribute(vty, ospf); /* passive-interface print. */ if (ospf->passive_interface_default == OSPF_IF_PASSIVE) vty_out(vty, " passive-interface default\n"); FOR_ALL_INTERFACES (vrf, ifp) if (OSPF_IF_PARAM_CONFIGURED(IF_DEF_PARAMS(ifp), passive_interface) && IF_DEF_PARAMS(ifp)->passive_interface != ospf->passive_interface_default) { vty_out(vty, " %spassive-interface %s\n", IF_DEF_PARAMS(ifp)->passive_interface ? "" : "no ", ifp->name); } for (ALL_LIST_ELEMENTS_RO(ospf->oiflist, node, oi)) { if (!OSPF_IF_PARAM_CONFIGURED(oi->params, passive_interface)) continue; if (OSPF_IF_PARAM_CONFIGURED(IF_DEF_PARAMS(oi->ifp), passive_interface)) { if (oi->params->passive_interface == IF_DEF_PARAMS(oi->ifp)->passive_interface) continue; } else if (oi->params->passive_interface == ospf->passive_interface_default) continue; vty_out(vty, " %spassive-interface %s %s\n", oi->params->passive_interface ? "" : "no ", oi->ifp->name, inet_ntoa(oi->address->u.prefix4)); } /* Network area print. */ config_write_network_area(vty, ospf); /* Area config print. */ config_write_ospf_area(vty, ospf); /* static neighbor print. */ config_write_ospf_nbr_nbma(vty, ospf); /* Virtual-Link print. */ config_write_virtual_link(vty, ospf); /* Default metric configuration. */ config_write_ospf_default_metric(vty, ospf); /* Distribute-list and default-information print. */ config_write_ospf_distribute(vty, ospf); /* Distance configuration. */ config_write_ospf_distance(vty, ospf); ospf_opaque_config_write_router(vty, ospf); write++; return write; } /* OSPF configuration write function. */ static int ospf_config_write(struct vty *vty) { struct ospf *ospf; struct listnode *ospf_node = NULL; int write = 0; if (listcount(om->ospf) == 0) return write; for (ALL_LIST_ELEMENTS_RO(om->ospf, ospf_node, ospf)) { /* VRF Default check if it is running. * Upon daemon start, there could be default instance * in absence of 'router ospf'/oi_running is disabled. */ if (ospf->vrf_id == VRF_DEFAULT && ospf->oi_running) write += ospf_config_write_one(vty, ospf); /* For Non-Default VRF simply display the configuration, * even if it is not oi_running. */ else if (ospf->vrf_id != VRF_DEFAULT) write += ospf_config_write_one(vty, ospf); } return write; } void ospf_vty_show_init(void) { /* "show ip ospf" commands. */ install_element(VIEW_NODE, &show_ip_ospf_cmd); install_element(VIEW_NODE, &show_ip_ospf_instance_cmd); /* "show ip ospf database" commands. */ install_element(VIEW_NODE, &show_ip_ospf_database_max_cmd); install_element(VIEW_NODE, &show_ip_ospf_instance_database_type_adv_router_cmd); install_element(VIEW_NODE, &show_ip_ospf_instance_database_cmd); install_element(VIEW_NODE, &show_ip_ospf_instance_database_max_cmd); /* "show ip ospf interface" commands. */ install_element(VIEW_NODE, &show_ip_ospf_interface_cmd); install_element(VIEW_NODE, &show_ip_ospf_instance_interface_cmd); /* "show ip ospf interface traffic */ install_element(VIEW_NODE, &show_ip_ospf_interface_traffic_cmd); /* "show ip ospf neighbor" commands. */ install_element(VIEW_NODE, &show_ip_ospf_neighbor_int_detail_cmd); install_element(VIEW_NODE, &show_ip_ospf_neighbor_int_cmd); install_element(VIEW_NODE, &show_ip_ospf_neighbor_id_cmd); install_element(VIEW_NODE, &show_ip_ospf_neighbor_detail_all_cmd); install_element(VIEW_NODE, &show_ip_ospf_neighbor_detail_cmd); install_element(VIEW_NODE, &show_ip_ospf_neighbor_cmd); install_element(VIEW_NODE, &show_ip_ospf_neighbor_all_cmd); install_element(VIEW_NODE, &show_ip_ospf_instance_neighbor_int_detail_cmd); install_element(VIEW_NODE, &show_ip_ospf_instance_neighbor_int_cmd); install_element(VIEW_NODE, &show_ip_ospf_instance_neighbor_id_cmd); install_element(VIEW_NODE, &show_ip_ospf_instance_neighbor_detail_all_cmd); install_element(VIEW_NODE, &show_ip_ospf_instance_neighbor_detail_cmd); install_element(VIEW_NODE, &show_ip_ospf_instance_neighbor_cmd); install_element(VIEW_NODE, &show_ip_ospf_instance_neighbor_all_cmd); /* "show ip ospf route" commands. */ install_element(VIEW_NODE, &show_ip_ospf_route_cmd); install_element(VIEW_NODE, &show_ip_ospf_border_routers_cmd); install_element(VIEW_NODE, &show_ip_ospf_instance_route_cmd); install_element(VIEW_NODE, &show_ip_ospf_instance_border_routers_cmd); /* "show ip ospf vrfs" commands. */ install_element(VIEW_NODE, &show_ip_ospf_vrfs_cmd); } /* ospfd's interface node. */ static struct cmd_node interface_node = {INTERFACE_NODE, "%s(config-if)# ", 1}; /* Initialization of OSPF interface. */ static void ospf_vty_if_init(void) { /* Install interface node. */ install_node(&interface_node, config_write_interface); if_cmd_init(); /* "ip ospf authentication" commands. */ install_element(INTERFACE_NODE, &ip_ospf_authentication_args_addr_cmd); install_element(INTERFACE_NODE, &ip_ospf_authentication_addr_cmd); install_element(INTERFACE_NODE, &no_ip_ospf_authentication_args_addr_cmd); install_element(INTERFACE_NODE, &no_ip_ospf_authentication_addr_cmd); install_element(INTERFACE_NODE, &ip_ospf_authentication_key_addr_cmd); install_element(INTERFACE_NODE, &no_ip_ospf_authentication_key_authkey_addr_cmd); install_element(INTERFACE_NODE, &no_ospf_authentication_key_authkey_addr_cmd); /* "ip ospf message-digest-key" commands. */ install_element(INTERFACE_NODE, &ip_ospf_message_digest_key_cmd); install_element(INTERFACE_NODE, &no_ip_ospf_message_digest_key_cmd); /* "ip ospf cost" commands. */ install_element(INTERFACE_NODE, &ip_ospf_cost_cmd); install_element(INTERFACE_NODE, &no_ip_ospf_cost_cmd); /* "ip ospf mtu-ignore" commands. */ install_element(INTERFACE_NODE, &ip_ospf_mtu_ignore_addr_cmd); install_element(INTERFACE_NODE, &no_ip_ospf_mtu_ignore_addr_cmd); /* "ip ospf dead-interval" commands. */ install_element(INTERFACE_NODE, &ip_ospf_dead_interval_cmd); install_element(INTERFACE_NODE, &ip_ospf_dead_interval_minimal_addr_cmd); install_element(INTERFACE_NODE, &no_ip_ospf_dead_interval_cmd); /* "ip ospf hello-interval" commands. */ install_element(INTERFACE_NODE, &ip_ospf_hello_interval_cmd); install_element(INTERFACE_NODE, &no_ip_ospf_hello_interval_cmd); /* "ip ospf network" commands. */ install_element(INTERFACE_NODE, &ip_ospf_network_cmd); install_element(INTERFACE_NODE, &no_ip_ospf_network_cmd); /* "ip ospf priority" commands. */ install_element(INTERFACE_NODE, &ip_ospf_priority_cmd); install_element(INTERFACE_NODE, &no_ip_ospf_priority_cmd); /* "ip ospf retransmit-interval" commands. */ install_element(INTERFACE_NODE, &ip_ospf_retransmit_interval_addr_cmd); install_element(INTERFACE_NODE, &no_ip_ospf_retransmit_interval_addr_cmd); /* "ip ospf transmit-delay" commands. */ install_element(INTERFACE_NODE, &ip_ospf_transmit_delay_addr_cmd); install_element(INTERFACE_NODE, &no_ip_ospf_transmit_delay_addr_cmd); /* "ip ospf area" commands. */ install_element(INTERFACE_NODE, &ip_ospf_area_cmd); install_element(INTERFACE_NODE, &no_ip_ospf_area_cmd); /* These commands are compatibitliy for previous version. */ install_element(INTERFACE_NODE, &ospf_authentication_key_cmd); install_element(INTERFACE_NODE, &ospf_message_digest_key_cmd); install_element(INTERFACE_NODE, &no_ospf_message_digest_key_cmd); install_element(INTERFACE_NODE, &ospf_dead_interval_cmd); install_element(INTERFACE_NODE, &no_ospf_dead_interval_cmd); install_element(INTERFACE_NODE, &ospf_hello_interval_cmd); install_element(INTERFACE_NODE, &no_ospf_hello_interval_cmd); install_element(INTERFACE_NODE, &ospf_cost_cmd); install_element(INTERFACE_NODE, &no_ospf_cost_cmd); install_element(INTERFACE_NODE, &ospf_network_cmd); install_element(INTERFACE_NODE, &no_ospf_network_cmd); install_element(INTERFACE_NODE, &ospf_priority_cmd); install_element(INTERFACE_NODE, &no_ospf_priority_cmd); install_element(INTERFACE_NODE, &ospf_retransmit_interval_cmd); install_element(INTERFACE_NODE, &no_ospf_retransmit_interval_cmd); install_element(INTERFACE_NODE, &ospf_transmit_delay_cmd); install_element(INTERFACE_NODE, &no_ospf_transmit_delay_cmd); } static void ospf_vty_zebra_init(void) { install_element(OSPF_NODE, &ospf_redistribute_source_cmd); install_element(OSPF_NODE, &no_ospf_redistribute_source_cmd); install_element(OSPF_NODE, &ospf_redistribute_instance_source_cmd); install_element(OSPF_NODE, &no_ospf_redistribute_instance_source_cmd); install_element(OSPF_NODE, &ospf_distribute_list_out_cmd); install_element(OSPF_NODE, &no_ospf_distribute_list_out_cmd); install_element(OSPF_NODE, &ospf_default_information_originate_cmd); install_element(OSPF_NODE, &no_ospf_default_information_originate_cmd); install_element(OSPF_NODE, &ospf_default_metric_cmd); install_element(OSPF_NODE, &no_ospf_default_metric_cmd); install_element(OSPF_NODE, &ospf_distance_cmd); install_element(OSPF_NODE, &no_ospf_distance_cmd); install_element(OSPF_NODE, &no_ospf_distance_ospf_cmd); install_element(OSPF_NODE, &ospf_distance_ospf_cmd); #if 0 install_element (OSPF_NODE, &ospf_distance_source_cmd); install_element (OSPF_NODE, &no_ospf_distance_source_cmd); install_element (OSPF_NODE, &ospf_distance_source_access_list_cmd); install_element (OSPF_NODE, &no_ospf_distance_source_access_list_cmd); #endif /* 0 */ } static struct cmd_node ospf_node = {OSPF_NODE, "%s(config-router)# ", 1}; static void ospf_interface_clear(struct interface *ifp) { if (!if_is_operative(ifp)) return; if (IS_DEBUG_OSPF(ism, ISM_EVENTS)) zlog_debug("ISM[%s]: clear by reset", ifp->name); ospf_if_reset(ifp); } DEFUN (clear_ip_ospf_interface, clear_ip_ospf_interface_cmd, "clear ip ospf [vrf ] interface [IFNAME]", CLEAR_STR IP_STR "OSPF information\n" VRF_CMD_HELP_STR "Interface information\n" "Interface name\n") { int idx_ifname = 0; int idx_vrf = 0; struct interface *ifp; struct listnode *node; struct ospf *ospf = NULL; char *vrf_name = NULL; vrf_id_t vrf_id = VRF_DEFAULT; struct vrf *vrf = NULL; if (argv_find(argv, argc, "vrf", &idx_vrf)) vrf_name = argv[idx_vrf + 1]->arg; if (vrf_name && strmatch(vrf_name, VRF_DEFAULT_NAME)) vrf_name = NULL; if (vrf_name) { vrf = vrf_lookup_by_name(vrf_name); if (vrf) vrf_id = vrf->vrf_id; } if (!argv_find(argv, argc, "IFNAME", &idx_ifname)) { /* Clear all the ospfv2 interfaces. */ for (ALL_LIST_ELEMENTS_RO(om->ospf, node, ospf)) { if (vrf_id != ospf->vrf_id) continue; if (!vrf) vrf = vrf_lookup_by_id(ospf->vrf_id); FOR_ALL_INTERFACES (vrf, ifp) ospf_interface_clear(ifp); } } else { /* Interface name is specified. */ ifp = if_lookup_by_name(argv[idx_ifname]->arg, vrf_id); if (ifp == NULL) vty_out(vty, "No such interface name\n"); else ospf_interface_clear(ifp); } return CMD_SUCCESS; } void ospf_vty_clear_init(void) { install_element(ENABLE_NODE, &clear_ip_ospf_interface_cmd); } /* Install OSPF related vty commands. */ void ospf_vty_init(void) { /* Install ospf top node. */ install_node(&ospf_node, ospf_config_write); /* "router ospf" commands. */ install_element(CONFIG_NODE, &router_ospf_cmd); install_element(CONFIG_NODE, &no_router_ospf_cmd); install_default(OSPF_NODE); /* "ospf router-id" commands. */ install_element(OSPF_NODE, &ospf_router_id_cmd); install_element(OSPF_NODE, &ospf_router_id_old_cmd); install_element(OSPF_NODE, &no_ospf_router_id_cmd); /* "passive-interface" commands. */ install_element(OSPF_NODE, &ospf_passive_interface_addr_cmd); install_element(OSPF_NODE, &no_ospf_passive_interface_addr_cmd); /* "ospf abr-type" commands. */ install_element(OSPF_NODE, &ospf_abr_type_cmd); install_element(OSPF_NODE, &no_ospf_abr_type_cmd); /* "ospf log-adjacency-changes" commands. */ install_element(OSPF_NODE, &ospf_log_adjacency_changes_cmd); install_element(OSPF_NODE, &ospf_log_adjacency_changes_detail_cmd); install_element(OSPF_NODE, &no_ospf_log_adjacency_changes_cmd); install_element(OSPF_NODE, &no_ospf_log_adjacency_changes_detail_cmd); /* "ospf rfc1583-compatible" commands. */ install_element(OSPF_NODE, &ospf_compatible_rfc1583_cmd); install_element(OSPF_NODE, &no_ospf_compatible_rfc1583_cmd); install_element(OSPF_NODE, &ospf_rfc1583_flag_cmd); install_element(OSPF_NODE, &no_ospf_rfc1583_flag_cmd); /* "network area" commands. */ install_element(OSPF_NODE, &ospf_network_area_cmd); install_element(OSPF_NODE, &no_ospf_network_area_cmd); /* "area authentication" commands. */ install_element(OSPF_NODE, &ospf_area_authentication_message_digest_cmd); install_element(OSPF_NODE, &ospf_area_authentication_cmd); install_element(OSPF_NODE, &no_ospf_area_authentication_cmd); /* "area range" commands. */ install_element(OSPF_NODE, &ospf_area_range_cmd); install_element(OSPF_NODE, &ospf_area_range_cost_cmd); install_element(OSPF_NODE, &ospf_area_range_not_advertise_cmd); install_element(OSPF_NODE, &no_ospf_area_range_cmd); install_element(OSPF_NODE, &ospf_area_range_substitute_cmd); install_element(OSPF_NODE, &no_ospf_area_range_substitute_cmd); /* "area virtual-link" commands. */ install_element(OSPF_NODE, &ospf_area_vlink_cmd); install_element(OSPF_NODE, &ospf_area_vlink_intervals_cmd); install_element(OSPF_NODE, &no_ospf_area_vlink_cmd); install_element(OSPF_NODE, &no_ospf_area_vlink_intervals_cmd); /* "area stub" commands. */ install_element(OSPF_NODE, &ospf_area_stub_no_summary_cmd); install_element(OSPF_NODE, &ospf_area_stub_cmd); install_element(OSPF_NODE, &no_ospf_area_stub_no_summary_cmd); install_element(OSPF_NODE, &no_ospf_area_stub_cmd); /* "area nssa" commands. */ install_element(OSPF_NODE, &ospf_area_nssa_cmd); install_element(OSPF_NODE, &ospf_area_nssa_translate_cmd); install_element(OSPF_NODE, &ospf_area_nssa_no_summary_cmd); install_element(OSPF_NODE, &no_ospf_area_nssa_no_summary_cmd); install_element(OSPF_NODE, &no_ospf_area_nssa_cmd); install_element(OSPF_NODE, &ospf_area_default_cost_cmd); install_element(OSPF_NODE, &no_ospf_area_default_cost_cmd); install_element(OSPF_NODE, &ospf_area_shortcut_cmd); install_element(OSPF_NODE, &no_ospf_area_shortcut_cmd); install_element(OSPF_NODE, &ospf_area_export_list_cmd); install_element(OSPF_NODE, &no_ospf_area_export_list_cmd); install_element(OSPF_NODE, &ospf_area_filter_list_cmd); install_element(OSPF_NODE, &no_ospf_area_filter_list_cmd); install_element(OSPF_NODE, &ospf_area_import_list_cmd); install_element(OSPF_NODE, &no_ospf_area_import_list_cmd); /* SPF timer commands */ install_element(OSPF_NODE, &ospf_timers_throttle_spf_cmd); install_element(OSPF_NODE, &no_ospf_timers_throttle_spf_cmd); /* LSA timers commands */ install_element(OSPF_NODE, &ospf_timers_min_ls_interval_cmd); install_element(OSPF_NODE, &no_ospf_timers_min_ls_interval_cmd); install_element(OSPF_NODE, &ospf_timers_lsa_min_arrival_cmd); install_element(OSPF_NODE, &no_ospf_timers_lsa_min_arrival_cmd); /* refresh timer commands */ install_element(OSPF_NODE, &ospf_refresh_timer_cmd); install_element(OSPF_NODE, &no_ospf_refresh_timer_val_cmd); /* max-metric commands */ install_element(OSPF_NODE, &ospf_max_metric_router_lsa_admin_cmd); install_element(OSPF_NODE, &no_ospf_max_metric_router_lsa_admin_cmd); install_element(OSPF_NODE, &ospf_max_metric_router_lsa_startup_cmd); install_element(OSPF_NODE, &no_ospf_max_metric_router_lsa_startup_cmd); install_element(OSPF_NODE, &ospf_max_metric_router_lsa_shutdown_cmd); install_element(OSPF_NODE, &no_ospf_max_metric_router_lsa_shutdown_cmd); /* reference bandwidth commands */ install_element(OSPF_NODE, &ospf_auto_cost_reference_bandwidth_cmd); install_element(OSPF_NODE, &no_ospf_auto_cost_reference_bandwidth_cmd); /* "neighbor" commands. */ install_element(OSPF_NODE, &ospf_neighbor_cmd); install_element(OSPF_NODE, &ospf_neighbor_poll_interval_cmd); install_element(OSPF_NODE, &no_ospf_neighbor_cmd); install_element(OSPF_NODE, &no_ospf_neighbor_poll_cmd); /* write multiplier commands */ install_element(OSPF_NODE, &ospf_write_multiplier_cmd); install_element(OSPF_NODE, &write_multiplier_cmd); install_element(OSPF_NODE, &no_ospf_write_multiplier_cmd); install_element(OSPF_NODE, &no_write_multiplier_cmd); /* Init interface related vty commands. */ ospf_vty_if_init(); /* Init zebra related vty commands. */ ospf_vty_zebra_init(); } frr-7.2.1/ospfd/ospf_vty.h0000644000000000000000000000500713610377563012353 00000000000000/* OSPF VTY interface. * Copyright (C) 2000 Toshiaki Takada * * This file is part of GNU Zebra. * * GNU Zebra is free software; you can redistribute it and/or modify it * under the terms of the GNU General Public License as published by the * Free Software Foundation; either version 2, or (at your option) any * later version. * * GNU Zebra 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; see the file COPYING; if not, write to the Free Software * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA */ #ifndef _QUAGGA_OSPF_VTY_H #define _QUAGGA_OSPF_VTY_H /* Macros. */ #define VTY_GET_OSPF_AREA_ID(V, F, STR) \ { \ int retv; \ retv = str2area_id((STR), &(V), &(F)); \ if (retv < 0) { \ vty_out(vty, "%% Invalid OSPF area ID\n"); \ return CMD_WARNING; \ } \ } #define VTY_GET_OSPF_AREA_ID_NO_BB(NAME, V, F, STR) \ { \ int retv; \ retv = str2area_id((STR), &(V), &(F)); \ if (retv < 0) { \ vty_out(vty, "%% Invalid OSPF area ID\n"); \ return CMD_WARNING; \ } \ if (OSPF_IS_AREA_ID_BACKBONE((V))) { \ vty_out(vty, \ "%% You can't configure %s to backbone\n", \ NAME); \ return CMD_WARNING; \ } \ } /* Prototypes. */ extern void ospf_vty_init(void); extern void ospf_vty_show_init(void); extern void ospf_vty_clear_init(void); extern int str2area_id(const char *, struct in_addr *, int *); #endif /* _QUAGGA_OSPF_VTY_H */ frr-7.2.1/ospfd/ospf_zebra.c0000644000000000000000000011467013610377563012636 00000000000000/* * Zebra connect library for OSPFd * Copyright (C) 1997, 98, 99, 2000 Kunihiro Ishiguro, Toshiaki Takada * * This file is part of GNU Zebra. * * GNU Zebra is free software; you can redistribute it and/or modify it * under the terms of the GNU General Public License as published by the * Free Software Foundation; either version 2, or (at your option) any * later version. * * GNU Zebra 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; see the file COPYING; if not, write to the Free Software * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA */ #include #include "thread.h" #include "command.h" #include "network.h" #include "prefix.h" #include "routemap.h" #include "table.h" #include "stream.h" #include "memory.h" #include "zclient.h" #include "filter.h" #include "plist.h" #include "log.h" #include "lib/bfd.h" #include "nexthop.h" #include "ospfd/ospfd.h" #include "ospfd/ospf_interface.h" #include "ospfd/ospf_ism.h" #include "ospfd/ospf_asbr.h" #include "ospfd/ospf_asbr.h" #include "ospfd/ospf_abr.h" #include "ospfd/ospf_lsa.h" #include "ospfd/ospf_dump.h" #include "ospfd/ospf_route.h" #include "ospfd/ospf_lsdb.h" #include "ospfd/ospf_neighbor.h" #include "ospfd/ospf_nsm.h" #include "ospfd/ospf_zebra.h" #include "ospfd/ospf_te.h" DEFINE_MTYPE_STATIC(OSPFD, OSPF_EXTERNAL, "OSPF External route table") DEFINE_MTYPE_STATIC(OSPFD, OSPF_REDISTRIBUTE, "OSPF Redistriute") DEFINE_MTYPE_STATIC(OSPFD, OSPF_DIST_ARGS, "OSPF Distribute arguments") DEFINE_HOOK(ospf_if_update, (struct interface * ifp), (ifp)) DEFINE_HOOK(ospf_if_delete, (struct interface * ifp), (ifp)) /* Zebra structure to hold current status. */ struct zclient *zclient = NULL; /* For registering threads. */ extern struct thread_master *master; /* Router-id update message from zebra. */ static int ospf_router_id_update_zebra(ZAPI_CALLBACK_ARGS) { struct ospf *ospf = NULL; struct prefix router_id; zebra_router_id_update_read(zclient->ibuf, &router_id); if (IS_DEBUG_OSPF(zebra, ZEBRA_INTERFACE)) { char buf[PREFIX2STR_BUFFER]; prefix2str(&router_id, buf, sizeof(buf)); zlog_debug("Zebra rcvd: router id update %s vrf %s id %u", buf, ospf_vrf_id_to_name(vrf_id), vrf_id); } ospf = ospf_lookup_by_vrf_id(vrf_id); if (ospf != NULL) { ospf->router_id_zebra = router_id.u.prefix4; ospf_router_id_update(ospf); } else { if (IS_DEBUG_OSPF_EVENT) { char buf[PREFIX2STR_BUFFER]; prefix2str(&router_id, buf, sizeof(buf)); zlog_debug( "%s: ospf instance not found for vrf %s id %u router_id %s", __PRETTY_FUNCTION__, ospf_vrf_id_to_name(vrf_id), vrf_id, buf); } } return 0; } /* Inteface addition message from zebra. */ static int ospf_interface_add(ZAPI_CALLBACK_ARGS) { struct interface *ifp = NULL; struct ospf *ospf = NULL; ifp = zebra_interface_add_read(zclient->ibuf, vrf_id); if (ifp == NULL) return 0; if (IS_DEBUG_OSPF(zebra, ZEBRA_INTERFACE)) zlog_debug( "Zebra: interface add %s vrf %s[%u] index %d flags %llx metric %d mtu %d speed %u", ifp->name, ospf_vrf_id_to_name(ifp->vrf_id), ifp->vrf_id, ifp->ifindex, (unsigned long long)ifp->flags, ifp->metric, ifp->mtu, ifp->speed); assert(ifp->info); if (IF_DEF_PARAMS(ifp) && !OSPF_IF_PARAM_CONFIGURED(IF_DEF_PARAMS(ifp), type)) { SET_IF_PARAM(IF_DEF_PARAMS(ifp), type); IF_DEF_PARAMS(ifp)->type = ospf_default_iftype(ifp); } ospf = ospf_lookup_by_vrf_id(vrf_id); if (!ospf) return 0; ospf_if_recalculate_output_cost(ifp); ospf_if_update(ospf, ifp); hook_call(ospf_if_update, ifp); return 0; } static int ospf_interface_delete(ZAPI_CALLBACK_ARGS) { struct interface *ifp; struct stream *s; struct route_node *rn; s = zclient->ibuf; /* zebra_interface_state_read() updates interface structure in iflist */ ifp = zebra_interface_state_read(s, vrf_id); if (ifp == NULL) return 0; if (IS_DEBUG_OSPF(zebra, ZEBRA_INTERFACE)) zlog_debug( "Zebra: interface delete %s vrf %s[%u] index %d flags %llx metric %d mtu %d", ifp->name, ospf_vrf_id_to_name(ifp->vrf_id), ifp->vrf_id, ifp->ifindex, (unsigned long long)ifp->flags, ifp->metric, ifp->mtu); hook_call(ospf_if_delete, ifp); for (rn = route_top(IF_OIFS(ifp)); rn; rn = route_next(rn)) if (rn->info) ospf_if_free((struct ospf_interface *)rn->info); if_set_index(ifp, IFINDEX_INTERNAL); return 0; } static struct interface *zebra_interface_if_lookup(struct stream *s, vrf_id_t vrf_id) { char ifname_tmp[INTERFACE_NAMSIZ]; /* Read interface name. */ stream_get(ifname_tmp, s, INTERFACE_NAMSIZ); /* And look it up. */ return if_lookup_by_name(ifname_tmp, vrf_id); } static int ospf_interface_state_up(ZAPI_CALLBACK_ARGS) { struct interface *ifp; struct ospf_interface *oi; struct route_node *rn; ifp = zebra_interface_if_lookup(zclient->ibuf, vrf_id); if (ifp == NULL) return 0; /* Interface is already up. */ if (if_is_operative(ifp)) { /* Temporarily keep ifp values. */ struct interface if_tmp; memcpy(&if_tmp, ifp, sizeof(struct interface)); zebra_interface_if_set_value(zclient->ibuf, ifp); if (IS_DEBUG_OSPF(zebra, ZEBRA_INTERFACE)) zlog_debug( "Zebra: Interface[%s] state update speed %u -> %u, bw %d -> %d", ifp->name, if_tmp.speed, ifp->speed, if_tmp.bandwidth, ifp->bandwidth); ospf_if_recalculate_output_cost(ifp); if (if_tmp.mtu != ifp->mtu) { if (IS_DEBUG_OSPF(zebra, ZEBRA_INTERFACE)) zlog_debug( "Zebra: Interface[%s] MTU change %u -> %u.", ifp->name, if_tmp.mtu, ifp->mtu); /* Must reset the interface (simulate down/up) when MTU * changes. */ ospf_if_reset(ifp); } return 0; } zebra_interface_if_set_value(zclient->ibuf, ifp); if (IS_DEBUG_OSPF(zebra, ZEBRA_INTERFACE)) zlog_debug("Zebra: Interface[%s] state change to up.", ifp->name); for (rn = route_top(IF_OIFS(ifp)); rn; rn = route_next(rn)) { if ((oi = rn->info) == NULL) continue; ospf_if_up(oi); } return 0; } static int ospf_interface_state_down(ZAPI_CALLBACK_ARGS) { struct interface *ifp; struct ospf_interface *oi; struct route_node *node; ifp = zebra_interface_state_read(zclient->ibuf, vrf_id); if (ifp == NULL) return 0; if (IS_DEBUG_OSPF(zebra, ZEBRA_INTERFACE)) zlog_debug("Zebra: Interface[%s] state change to down.", ifp->name); for (node = route_top(IF_OIFS(ifp)); node; node = route_next(node)) { if ((oi = node->info) == NULL) continue; ospf_if_down(oi); } return 0; } static int ospf_interface_address_add(ZAPI_CALLBACK_ARGS) { struct connected *c; struct ospf *ospf = NULL; c = zebra_interface_address_read(cmd, zclient->ibuf, vrf_id); if (c == NULL) return 0; if (IS_DEBUG_OSPF(zebra, ZEBRA_INTERFACE)) { char buf[PREFIX2STR_BUFFER]; prefix2str(c->address, buf, sizeof(buf)); zlog_debug("Zebra: interface %s address add %s vrf %s id %u", c->ifp->name, buf, ospf_vrf_id_to_name(vrf_id), vrf_id); } ospf = ospf_lookup_by_vrf_id(vrf_id); if (!ospf) return 0; ospf_if_update(ospf, c->ifp); hook_call(ospf_if_update, c->ifp); return 0; } static int ospf_interface_address_delete(ZAPI_CALLBACK_ARGS) { struct connected *c; struct interface *ifp; struct ospf_interface *oi; struct route_node *rn; struct prefix p; c = zebra_interface_address_read(cmd, zclient->ibuf, vrf_id); if (c == NULL) return 0; if (IS_DEBUG_OSPF(zebra, ZEBRA_INTERFACE)) { char buf[PREFIX2STR_BUFFER]; prefix2str(c->address, buf, sizeof(buf)); zlog_debug("Zebra: interface %s address delete %s", c->ifp->name, buf); } ifp = c->ifp; p = *c->address; p.prefixlen = IPV4_MAX_PREFIXLEN; rn = route_node_lookup(IF_OIFS(ifp), &p); if (!rn) { connected_free(c); return 0; } assert(rn->info); oi = rn->info; route_unlock_node(rn); /* Call interface hook functions to clean up */ ospf_if_free(oi); hook_call(ospf_if_update, c->ifp); connected_free(c); return 0; } static int ospf_interface_link_params(ZAPI_CALLBACK_ARGS) { struct interface *ifp; ifp = zebra_interface_link_params_read(zclient->ibuf, vrf_id); if (ifp == NULL) return 0; /* Update TE TLV */ ospf_mpls_te_update_if(ifp); return 0; } /* VRF update for an interface. */ static int ospf_interface_vrf_update(ZAPI_CALLBACK_ARGS) { struct interface *ifp = NULL; vrf_id_t new_vrf_id; ifp = zebra_interface_vrf_update_read(zclient->ibuf, vrf_id, &new_vrf_id); if (!ifp) return 0; if (IS_DEBUG_OSPF_EVENT) zlog_debug( "%s: Rx Interface %s VRF change vrf_id %u New vrf %s id %u", __PRETTY_FUNCTION__, ifp->name, vrf_id, ospf_vrf_id_to_name(new_vrf_id), new_vrf_id); /*if_update(ifp, ifp->name, strlen(ifp->name), new_vrf_id);*/ if_update_to_new_vrf(ifp, new_vrf_id); return 0; } void ospf_zebra_add(struct ospf *ospf, struct prefix_ipv4 *p, struct ospf_route * or) { struct zapi_route api; struct zapi_nexthop *api_nh; uint8_t distance; struct ospf_path *path; struct listnode *node; int count = 0; memset(&api, 0, sizeof(api)); api.vrf_id = ospf->vrf_id; api.type = ZEBRA_ROUTE_OSPF; api.instance = ospf->instance; api.safi = SAFI_UNICAST; memcpy(&api.prefix, p, sizeof(*p)); SET_FLAG(api.message, ZAPI_MESSAGE_NEXTHOP); /* Metric value. */ SET_FLAG(api.message, ZAPI_MESSAGE_METRIC); if (or->path_type == OSPF_PATH_TYPE1_EXTERNAL) api.metric = or->cost + or->u.ext.type2_cost; else if (or->path_type == OSPF_PATH_TYPE2_EXTERNAL) api.metric = or->u.ext.type2_cost; else api.metric = or->cost; /* Check if path type is ASE */ if (((or->path_type == OSPF_PATH_TYPE1_EXTERNAL) || (or->path_type == OSPF_PATH_TYPE2_EXTERNAL)) && (or->u.ext.tag > 0) && (or->u.ext.tag <= ROUTE_TAG_MAX)) { SET_FLAG(api.message, ZAPI_MESSAGE_TAG); api.tag = or->u.ext.tag; } /* Distance value. */ distance = ospf_distance_apply(ospf, p, or); if (distance) { SET_FLAG(api.message, ZAPI_MESSAGE_DISTANCE); api.distance = distance; } /* Nexthop, ifindex, distance and metric information. */ for (ALL_LIST_ELEMENTS_RO(or->paths, node, path)) { if (count >= MULTIPATH_NUM) break; api_nh = &api.nexthops[count]; #ifdef HAVE_NETLINK if (path->unnumbered || (path->nexthop.s_addr != INADDR_ANY && path->ifindex != 0)) { #else /* HAVE_NETLINK */ if (path->nexthop.s_addr != INADDR_ANY && path->ifindex != 0) { #endif /* HAVE_NETLINK */ api_nh->gate.ipv4 = path->nexthop; api_nh->ifindex = path->ifindex; api_nh->type = NEXTHOP_TYPE_IPV4_IFINDEX; } else if (path->nexthop.s_addr != INADDR_ANY) { api_nh->gate.ipv4 = path->nexthop; api_nh->type = NEXTHOP_TYPE_IPV4; } else { api_nh->ifindex = path->ifindex; api_nh->type = NEXTHOP_TYPE_IFINDEX; } api_nh->vrf_id = ospf->vrf_id; count++; if (IS_DEBUG_OSPF(zebra, ZEBRA_REDISTRIBUTE)) { char buf[2][INET_ADDRSTRLEN]; struct interface *ifp; ifp = if_lookup_by_index(path->ifindex, ospf->vrf_id); zlog_debug( "Zebra: Route add %s nexthop %s, ifindex=%d %s", prefix2str(p, buf[0], sizeof(buf[0])), inet_ntop(AF_INET, &path->nexthop, buf[1], sizeof(buf[1])), path->ifindex, ifp ? ifp->name : " "); } } api.nexthop_num = count; zclient_route_send(ZEBRA_ROUTE_ADD, zclient, &api); } void ospf_zebra_delete(struct ospf *ospf, struct prefix_ipv4 *p, struct ospf_route * or) { struct zapi_route api; memset(&api, 0, sizeof(api)); api.vrf_id = ospf->vrf_id; api.type = ZEBRA_ROUTE_OSPF; api.instance = ospf->instance; api.safi = SAFI_UNICAST; memcpy(&api.prefix, p, sizeof(*p)); if (IS_DEBUG_OSPF(zebra, ZEBRA_REDISTRIBUTE)) { char buf[PREFIX2STR_BUFFER]; zlog_debug("Zebra: Route delete %s", prefix2str(p, buf, sizeof(buf))); } zclient_route_send(ZEBRA_ROUTE_DELETE, zclient, &api); } void ospf_zebra_add_discard(struct ospf *ospf, struct prefix_ipv4 *p) { struct zapi_route api; memset(&api, 0, sizeof(api)); api.vrf_id = ospf->vrf_id; api.type = ZEBRA_ROUTE_OSPF; api.instance = ospf->instance; api.safi = SAFI_UNICAST; memcpy(&api.prefix, p, sizeof(*p)); zapi_route_set_blackhole(&api, BLACKHOLE_NULL); zclient_route_send(ZEBRA_ROUTE_ADD, zclient, &api); if (IS_DEBUG_OSPF(zebra, ZEBRA_REDISTRIBUTE)) { char buf[PREFIX2STR_BUFFER]; zlog_debug("Zebra: Route add discard %s", prefix2str(p, buf, sizeof(buf))); } } void ospf_zebra_delete_discard(struct ospf *ospf, struct prefix_ipv4 *p) { struct zapi_route api; memset(&api, 0, sizeof(api)); api.vrf_id = ospf->vrf_id; api.type = ZEBRA_ROUTE_OSPF; api.instance = ospf->instance; api.safi = SAFI_UNICAST; memcpy(&api.prefix, p, sizeof(*p)); zapi_route_set_blackhole(&api, BLACKHOLE_NULL); zclient_route_send(ZEBRA_ROUTE_DELETE, zclient, &api); if (IS_DEBUG_OSPF(zebra, ZEBRA_REDISTRIBUTE)) { char buf[PREFIX2STR_BUFFER]; zlog_debug("Zebra: Route delete discard %s", prefix2str(p, buf, sizeof(buf))); } } struct ospf_external *ospf_external_lookup(struct ospf *ospf, uint8_t type, unsigned short instance) { struct list *ext_list; struct listnode *node; struct ospf_external *ext; ext_list = ospf->external[type]; if (!ext_list) return (NULL); for (ALL_LIST_ELEMENTS_RO(ext_list, node, ext)) if (ext->instance == instance) return ext; return NULL; } struct ospf_external *ospf_external_add(struct ospf *ospf, uint8_t type, unsigned short instance) { struct list *ext_list; struct ospf_external *ext; ext = ospf_external_lookup(ospf, type, instance); if (ext) return ext; if (!ospf->external[type]) ospf->external[type] = list_new(); ext_list = ospf->external[type]; ext = XCALLOC(MTYPE_OSPF_EXTERNAL, sizeof(struct ospf_external)); ext->instance = instance; EXTERNAL_INFO(ext) = route_table_init(); listnode_add(ext_list, ext); return ext; } void ospf_external_del(struct ospf *ospf, uint8_t type, unsigned short instance) { struct ospf_external *ext; ext = ospf_external_lookup(ospf, type, instance); if (ext) { if (EXTERNAL_INFO(ext)) route_table_finish(EXTERNAL_INFO(ext)); listnode_delete(ospf->external[type], ext); if (!ospf->external[type]->count) list_delete(&ospf->external[type]); XFREE(MTYPE_OSPF_EXTERNAL, ext); } } struct ospf_redist *ospf_redist_lookup(struct ospf *ospf, uint8_t type, unsigned short instance) { struct list *red_list; struct listnode *node; struct ospf_redist *red; red_list = ospf->redist[type]; if (!red_list) return (NULL); for (ALL_LIST_ELEMENTS_RO(red_list, node, red)) if (red->instance == instance) return red; return NULL; } struct ospf_redist *ospf_redist_add(struct ospf *ospf, uint8_t type, unsigned short instance) { struct list *red_list; struct ospf_redist *red; red = ospf_redist_lookup(ospf, type, instance); if (red) return red; if (!ospf->redist[type]) ospf->redist[type] = list_new(); red_list = ospf->redist[type]; red = XCALLOC(MTYPE_OSPF_REDISTRIBUTE, sizeof(struct ospf_redist)); red->instance = instance; red->dmetric.type = -1; red->dmetric.value = -1; ROUTEMAP_NAME(red) = NULL; ROUTEMAP(red) = NULL; listnode_add(red_list, red); return red; } void ospf_redist_del(struct ospf *ospf, uint8_t type, unsigned short instance) { struct ospf_redist *red; red = ospf_redist_lookup(ospf, type, instance); if (red) { listnode_delete(ospf->redist[type], red); if (!ospf->redist[type]->count) { list_delete(&ospf->redist[type]); } ospf_routemap_unset(red); XFREE(MTYPE_OSPF_REDISTRIBUTE, red); } } int ospf_is_type_redistributed(struct ospf *ospf, int type, unsigned short instance) { return (DEFAULT_ROUTE_TYPE(type) ? vrf_bitmap_check(zclient->default_information[AFI_IP], ospf->vrf_id) : ((instance && redist_check_instance( &zclient->mi_redist[AFI_IP][type], instance)) || (!instance && vrf_bitmap_check( zclient->redist[AFI_IP][type], ospf->vrf_id)))); } int ospf_redistribute_set(struct ospf *ospf, int type, unsigned short instance, int mtype, int mvalue) { int force = 0; struct ospf_redist *red; red = ospf_redist_lookup(ospf, type, instance); if (red == NULL) { zlog_err( "Redistribute[%s][%d]: Lookup failed Type[%d] , Metric[%d]", ospf_redist_string(type), instance, metric_type(ospf, type, instance), metric_value(ospf, type, instance)); return CMD_WARNING_CONFIG_FAILED; } if (ospf_is_type_redistributed(ospf, type, instance)) { if (mtype != red->dmetric.type) { red->dmetric.type = mtype; force = LSA_REFRESH_FORCE; } if (mvalue != red->dmetric.value) { red->dmetric.value = mvalue; force = LSA_REFRESH_FORCE; } ospf_external_lsa_refresh_type(ospf, type, instance, force); if (IS_DEBUG_OSPF(zebra, ZEBRA_REDISTRIBUTE)) zlog_debug( "Redistribute[%s][%d]: Refresh Type[%d], Metric[%d]", ospf_redist_string(type), instance, metric_type(ospf, type, instance), metric_value(ospf, type, instance)); return CMD_SUCCESS; } red->dmetric.type = mtype; red->dmetric.value = mvalue; ospf_external_add(ospf, type, instance); zclient_redistribute(ZEBRA_REDISTRIBUTE_ADD, zclient, AFI_IP, type, instance, ospf->vrf_id); if (IS_DEBUG_OSPF(zebra, ZEBRA_REDISTRIBUTE)) zlog_debug( "Redistribute[%s][%d] vrf id %u: Start Type[%d], Metric[%d]", ospf_redist_string(type), instance, ospf->vrf_id, metric_type(ospf, type, instance), metric_value(ospf, type, instance)); ospf_asbr_status_update(ospf, ++ospf->redistribute); return CMD_SUCCESS; } int ospf_redistribute_unset(struct ospf *ospf, int type, unsigned short instance) { if (type == zclient->redist_default && instance == zclient->instance) return CMD_SUCCESS; if (!ospf_is_type_redistributed(ospf, type, instance)) return CMD_SUCCESS; zclient_redistribute(ZEBRA_REDISTRIBUTE_DELETE, zclient, AFI_IP, type, instance, ospf->vrf_id); if (IS_DEBUG_OSPF(zebra, ZEBRA_REDISTRIBUTE)) zlog_debug("Redistribute[%s][%d] vrf id %u: Stop", ospf_redist_string(type), instance, ospf->vrf_id); /* Remove the routes from OSPF table. */ ospf_redistribute_withdraw(ospf, type, instance); ospf_external_del(ospf, type, instance); ospf_asbr_status_update(ospf, --ospf->redistribute); return CMD_SUCCESS; } int ospf_redistribute_default_set(struct ospf *ospf, int originate, int mtype, int mvalue) { struct prefix_ipv4 p; struct in_addr nexthop; int cur_originate = ospf->default_originate; const char *type_str = NULL; nexthop.s_addr = 0; p.family = AF_INET; p.prefix.s_addr = 0; p.prefixlen = 0; ospf->default_originate = originate; if (cur_originate == originate) { /* Refresh the lsa since metric might different */ if (IS_DEBUG_OSPF(zebra, ZEBRA_REDISTRIBUTE)) zlog_debug( "Redistribute[%s]: Refresh Type[%d], Metric[%d]", ospf_redist_string(DEFAULT_ROUTE), metric_type(ospf, DEFAULT_ROUTE, 0), metric_value(ospf, DEFAULT_ROUTE, 0)); ospf_external_lsa_refresh_default(ospf); return CMD_SUCCESS; } switch (cur_originate) { case DEFAULT_ORIGINATE_NONE: break; case DEFAULT_ORIGINATE_ZEBRA: zclient_redistribute_default(ZEBRA_REDISTRIBUTE_DEFAULT_DELETE, zclient, AFI_IP, ospf->vrf_id); ospf->redistribute--; break; case DEFAULT_ORIGINATE_ALWAYS: ospf_external_info_delete(ospf, DEFAULT_ROUTE, 0, p); ospf_external_del(ospf, DEFAULT_ROUTE, 0); ospf->redistribute--; break; } switch (originate) { case DEFAULT_ORIGINATE_NONE: type_str = "none"; break; case DEFAULT_ORIGINATE_ZEBRA: type_str = "normal"; ospf->redistribute++; zclient_redistribute_default(ZEBRA_REDISTRIBUTE_DEFAULT_ADD, zclient, AFI_IP, ospf->vrf_id); break; case DEFAULT_ORIGINATE_ALWAYS: type_str = "always"; ospf->redistribute++; ospf_external_add(ospf, DEFAULT_ROUTE, 0); ospf_external_info_add(ospf, DEFAULT_ROUTE, 0, p, 0, nexthop, 0); break; } if (IS_DEBUG_OSPF(zebra, ZEBRA_REDISTRIBUTE)) zlog_debug("Redistribute[DEFAULT]: %s Type[%d], Metric[%d]", type_str, metric_type(ospf, DEFAULT_ROUTE, 0), metric_value(ospf, DEFAULT_ROUTE, 0)); ospf_external_lsa_refresh_default(ospf); ospf_asbr_status_update(ospf, ospf->redistribute); return CMD_SUCCESS; } static int ospf_external_lsa_originate_check(struct ospf *ospf, struct external_info *ei) { /* If prefix is multicast, then do not originate LSA. */ if (IN_MULTICAST(htonl(ei->p.prefix.s_addr))) { zlog_info( "LSA[Type5:%s]: Not originate AS-external-LSA, " "Prefix belongs multicast", inet_ntoa(ei->p.prefix)); return 0; } /* Take care of default-originate. */ if (is_prefix_default(&ei->p)) if (ospf->default_originate == DEFAULT_ORIGINATE_NONE) { zlog_info( "LSA[Type5:0.0.0.0]: Not originate AS-external-LSA " "for default"); return 0; } return 1; } /* If connected prefix is OSPF enable interface, then do not announce. */ int ospf_distribute_check_connected(struct ospf *ospf, struct external_info *ei) { struct listnode *node; struct ospf_interface *oi; for (ALL_LIST_ELEMENTS_RO(ospf->oiflist, node, oi)) if (prefix_match(oi->address, (struct prefix *)&ei->p)) return 0; return 1; } /* return 1 if external LSA must be originated, 0 otherwise */ int ospf_redistribute_check(struct ospf *ospf, struct external_info *ei, int *changed) { struct route_map_set_values save_values; struct prefix_ipv4 *p = &ei->p; struct ospf_redist *red; uint8_t type = is_prefix_default(&ei->p) ? DEFAULT_ROUTE : ei->type; unsigned short instance = is_prefix_default(&ei->p) ? 0 : ei->instance; if (changed) *changed = 0; if (!ospf_external_lsa_originate_check(ospf, ei)) return 0; /* Take care connected route. */ if (type == ZEBRA_ROUTE_CONNECT && !ospf_distribute_check_connected(ospf, ei)) return 0; if (!DEFAULT_ROUTE_TYPE(type) && DISTRIBUTE_NAME(ospf, type)) /* distirbute-list exists, but access-list may not? */ if (DISTRIBUTE_LIST(ospf, type)) if (access_list_apply(DISTRIBUTE_LIST(ospf, type), p) == FILTER_DENY) { if (IS_DEBUG_OSPF(zebra, ZEBRA_REDISTRIBUTE)) { char buf[PREFIX2STR_BUFFER]; zlog_debug( "Redistribute[%s]: %s filtered by distribute-list.", ospf_redist_string(type), prefix2str(p, buf, sizeof(buf))); } return 0; } save_values = ei->route_map_set; ospf_reset_route_map_set_values(&ei->route_map_set); /* apply route-map if needed */ red = ospf_redist_lookup(ospf, type, instance); if (red && ROUTEMAP_NAME(red)) { route_map_result_t ret; ret = route_map_apply(ROUTEMAP(red), (struct prefix *)p, RMAP_OSPF, ei); if (ret == RMAP_DENYMATCH) { ei->route_map_set = save_values; if (IS_DEBUG_OSPF(zebra, ZEBRA_REDISTRIBUTE)) { char buf[PREFIX2STR_BUFFER]; zlog_debug( "Redistribute[%s]: %s filtered by route-map.", ospf_redist_string(type), prefix2str(p, buf, sizeof(buf))); } return 0; } /* check if 'route-map set' changed something */ if (changed) *changed = !ospf_route_map_set_compare( &ei->route_map_set, &save_values); } return 1; } /* OSPF route-map set for redistribution */ void ospf_routemap_set(struct ospf_redist *red, const char *name) { if (ROUTEMAP_NAME(red)) { route_map_counter_decrement(ROUTEMAP(red)); free(ROUTEMAP_NAME(red)); } ROUTEMAP_NAME(red) = strdup(name); ROUTEMAP(red) = route_map_lookup_by_name(name); route_map_counter_increment(ROUTEMAP(red)); } void ospf_routemap_unset(struct ospf_redist *red) { if (ROUTEMAP_NAME(red)) { route_map_counter_decrement(ROUTEMAP(red)); free(ROUTEMAP_NAME(red)); } ROUTEMAP_NAME(red) = NULL; ROUTEMAP(red) = NULL; } /* Zebra route add and delete treatment. */ static int ospf_zebra_read_route(ZAPI_CALLBACK_ARGS) { struct zapi_route api; struct prefix_ipv4 p; unsigned long ifindex; struct in_addr nexthop; struct external_info *ei; struct ospf *ospf; int i; uint8_t rt_type; ospf = ospf_lookup_by_vrf_id(vrf_id); if (ospf == NULL) return 0; if (zapi_route_decode(zclient->ibuf, &api) < 0) return -1; ifindex = api.nexthops[0].ifindex; nexthop = api.nexthops[0].gate.ipv4; rt_type = api.type; memcpy(&p, &api.prefix, sizeof(p)); if (IPV4_NET127(ntohl(p.prefix.s_addr))) return 0; /* Re-destributed route is default route. * Here, route type is used as 'ZEBRA_ROUTE_KERNEL' for * updating ex-info. But in resetting (no default-info * originate)ZEBRA_ROUTE_MAX is used to delete the ex-info. * Resolved this inconsistency by maintaining same route type. */ if (is_prefix_default(&p)) rt_type = DEFAULT_ROUTE; if (IS_DEBUG_OSPF(zebra, ZEBRA_REDISTRIBUTE)) { char buf_prefix[PREFIX_STRLEN]; prefix2str(&api.prefix, buf_prefix, sizeof(buf_prefix)); zlog_debug("%s: cmd %s from client %s: vrf_id %d, p %s", __func__, zserv_command_string(cmd), zebra_route_string(api.type), vrf_id, buf_prefix); } if (cmd == ZEBRA_REDISTRIBUTE_ROUTE_ADD) { /* XXX|HACK|TODO|FIXME: * Maybe we should ignore reject/blackhole routes? Testing * shows that there is no problems though and this is only way * to "summarize" routes in ASBR at the moment. Maybe we need * just a better generalised solution for these types? */ /* Protocol tag overwrites all other tag value sent by zebra */ if (ospf->dtag[rt_type] > 0) api.tag = ospf->dtag[rt_type]; /* * Given zebra sends update for a prefix via ADD message, it * should * be considered as an implicit DEL for that prefix with other * source * types. */ for (i = 0; i <= ZEBRA_ROUTE_MAX; i++) if (i != rt_type) ospf_external_info_delete(ospf, i, api.instance, p); ei = ospf_external_info_add(ospf, rt_type, api.instance, p, ifindex, nexthop, api.tag); if (ei == NULL) { /* Nothing has changed, so nothing to do; return */ return 0; } if (ospf->router_id.s_addr != 0) { if (ei) { if (is_prefix_default(&p)) ospf_external_lsa_refresh_default(ospf); else { struct ospf_lsa *current; current = ospf_external_info_find_lsa( ospf, &ei->p); if (!current) ospf_external_lsa_originate( ospf, ei); else { if (IS_DEBUG_OSPF( zebra, ZEBRA_REDISTRIBUTE)) zlog_debug( "ospf_zebra_read_route() : %s refreshing LSA", inet_ntoa( p.prefix)); ospf_external_lsa_refresh( ospf, current, ei, LSA_REFRESH_FORCE); } } } } } else /* if (cmd == ZEBRA_REDISTRIBUTE_ROUTE_DEL) */ { ospf_external_info_delete(ospf, rt_type, api.instance, p); if (is_prefix_default(&p)) ospf_external_lsa_refresh_default(ospf); else ospf_external_lsa_flush(ospf, rt_type, &p, ifindex /*, nexthop */); } return 0; } int ospf_distribute_list_out_set(struct ospf *ospf, int type, const char *name) { /* Lookup access-list for distribute-list. */ DISTRIBUTE_LIST(ospf, type) = access_list_lookup(AFI_IP, name); /* Clear previous distribute-name. */ if (DISTRIBUTE_NAME(ospf, type)) free(DISTRIBUTE_NAME(ospf, type)); /* Set distribute-name. */ DISTRIBUTE_NAME(ospf, type) = strdup(name); /* If access-list have been set, schedule update timer. */ if (DISTRIBUTE_LIST(ospf, type)) ospf_distribute_list_update(ospf, type, 0); return CMD_SUCCESS; } int ospf_distribute_list_out_unset(struct ospf *ospf, int type, const char *name) { /* Schedule update timer. */ if (DISTRIBUTE_LIST(ospf, type)) ospf_distribute_list_update(ospf, type, 0); /* Unset distribute-list. */ DISTRIBUTE_LIST(ospf, type) = NULL; /* Clear distribute-name. */ if (DISTRIBUTE_NAME(ospf, type)) free(DISTRIBUTE_NAME(ospf, type)); DISTRIBUTE_NAME(ospf, type) = NULL; return CMD_SUCCESS; } /* distribute-list update timer. */ static int ospf_distribute_list_update_timer(struct thread *thread) { struct route_node *rn; struct external_info *ei; struct route_table *rt; struct ospf_lsa *lsa; int type, default_refresh = 0, arg_type; struct ospf *ospf = NULL; void **arg = THREAD_ARG(thread); ospf = (struct ospf *)arg[0]; arg_type = (int)(intptr_t)arg[1]; if (ospf == NULL) return 0; ospf->t_distribute_update = NULL; zlog_info("Zebra[Redistribute]: distribute-list update timer fired!"); if (IS_DEBUG_OSPF_EVENT) { zlog_debug( "%s: ospf distribute-list update arg_type %d vrf %s id %d", __PRETTY_FUNCTION__, arg_type, ospf_vrf_id_to_name(ospf->vrf_id), ospf->vrf_id); } /* foreach all external info. */ for (type = 0; type <= ZEBRA_ROUTE_MAX; type++) { struct list *ext_list; struct listnode *node; struct ospf_external *ext; ext_list = ospf->external[type]; if (!ext_list) continue; for (ALL_LIST_ELEMENTS_RO(ext_list, node, ext)) { rt = ext->external_info; if (!rt) continue; for (rn = route_top(rt); rn; rn = route_next(rn)) if ((ei = rn->info) != NULL) { if (is_prefix_default(&ei->p)) default_refresh = 1; else if ( (lsa = ospf_external_info_find_lsa( ospf, &ei->p))) ospf_external_lsa_refresh( ospf, lsa, ei, LSA_REFRESH_IF_CHANGED); else ospf_external_lsa_originate( ospf, ei); } } } if (default_refresh) ospf_external_lsa_refresh_default(ospf); XFREE(MTYPE_OSPF_DIST_ARGS, arg); return 0; } /* Update distribute-list and set timer to apply access-list. */ void ospf_distribute_list_update(struct ospf *ospf, int type, unsigned short instance) { struct route_table *rt; struct ospf_external *ext; void **args = XCALLOC(MTYPE_OSPF_DIST_ARGS, sizeof(void *) * 2); args[0] = ospf; args[1] = (void *)((ptrdiff_t)type); /* External info does not exist. */ ext = ospf_external_lookup(ospf, type, instance); if (!ext || !(rt = EXTERNAL_INFO(ext))) { XFREE(MTYPE_OSPF_DIST_ARGS, args); return; } /* If exists previously invoked thread, then let it continue. */ if (ospf->t_distribute_update) { XFREE(MTYPE_OSPF_DIST_ARGS, args); return; } /* Set timer. */ ospf->t_distribute_update = NULL; thread_add_timer_msec(master, ospf_distribute_list_update_timer, (void **)args, ospf->min_ls_interval, &ospf->t_distribute_update); } /* If access-list is updated, apply some check. */ static void ospf_filter_update(struct access_list *access) { struct ospf *ospf; int type; int abr_inv = 0; struct ospf_area *area; struct listnode *node, *n1; /* If OSPF instance does not exist, return right now. */ if (listcount(om->ospf) == 0) return; /* Iterate all ospf [VRF] instances */ for (ALL_LIST_ELEMENTS_RO(om->ospf, n1, ospf)) { /* Update distribute-list, and apply filter. */ for (type = 0; type <= ZEBRA_ROUTE_MAX; type++) { struct list *red_list; struct ospf_redist *red; red_list = ospf->redist[type]; if (red_list) for (ALL_LIST_ELEMENTS_RO(red_list, node, red)) { if (ROUTEMAP(red)) { /* if route-map is not NULL it * may be * using this access list */ ospf_distribute_list_update( ospf, type, red->instance); } } /* There is place for route-map for default-information * (ZEBRA_ROUTE_MAX), * but no distribute list. */ if (type == ZEBRA_ROUTE_MAX) break; if (DISTRIBUTE_NAME(ospf, type)) { /* Keep old access-list for distribute-list. */ struct access_list *old = DISTRIBUTE_LIST(ospf, type); /* Update access-list for distribute-list. */ DISTRIBUTE_LIST(ospf, type) = access_list_lookup( AFI_IP, DISTRIBUTE_NAME(ospf, type)); /* No update for this distribute type. */ if (old == NULL && DISTRIBUTE_LIST(ospf, type) == NULL) continue; /* Schedule distribute-list update timer. */ if (DISTRIBUTE_LIST(ospf, type) == NULL || strcmp(DISTRIBUTE_NAME(ospf, type), access->name) == 0) ospf_distribute_list_update(ospf, type, 0); } } /* Update Area access-list. */ for (ALL_LIST_ELEMENTS_RO(ospf->areas, node, area)) { if (EXPORT_NAME(area)) { EXPORT_LIST(area) = NULL; abr_inv++; } if (IMPORT_NAME(area)) { IMPORT_LIST(area) = NULL; abr_inv++; } } /* Schedule ABR tasks -- this will be changed -- takada. */ if (IS_OSPF_ABR(ospf) && abr_inv) ospf_schedule_abr_task(ospf); } } /* If prefix-list is updated, do some updates. */ void ospf_prefix_list_update(struct prefix_list *plist) { struct ospf *ospf = NULL; int type; int abr_inv = 0; struct ospf_area *area; struct listnode *node, *n1; /* If OSPF instatnce does not exist, return right now. */ if (listcount(om->ospf) == 0) return; /* Iterate all ospf [VRF] instances */ for (ALL_LIST_ELEMENTS_RO(om->ospf, n1, ospf)) { /* Update all route-maps which are used * as redistribution filters. * They might use prefix-list. */ for (type = 0; type <= ZEBRA_ROUTE_MAX; type++) { struct list *red_list; struct ospf_redist *red; red_list = ospf->redist[type]; if (red_list) { for (ALL_LIST_ELEMENTS_RO(red_list, node, red)) { if (ROUTEMAP(red)) { /* if route-map is not NULL * it may be using * this prefix list */ ospf_distribute_list_update( ospf, type, red->instance); } } } } /* Update area filter-lists. */ for (ALL_LIST_ELEMENTS_RO(ospf->areas, node, area)) { /* Update filter-list in. */ if (PREFIX_NAME_IN(area)) if (strcmp(PREFIX_NAME_IN(area), prefix_list_name(plist)) == 0) { PREFIX_LIST_IN(area) = prefix_list_lookup( AFI_IP, PREFIX_NAME_IN(area)); abr_inv++; } /* Update filter-list out. */ if (PREFIX_NAME_OUT(area)) if (strcmp(PREFIX_NAME_OUT(area), prefix_list_name(plist)) == 0) { PREFIX_LIST_IN(area) = prefix_list_lookup( AFI_IP, PREFIX_NAME_OUT(area)); abr_inv++; } } /* Schedule ABR task. */ if (IS_OSPF_ABR(ospf) && abr_inv) ospf_schedule_abr_task(ospf); } } static struct ospf_distance *ospf_distance_new(void) { return XCALLOC(MTYPE_OSPF_DISTANCE, sizeof(struct ospf_distance)); } static void ospf_distance_free(struct ospf_distance *odistance) { XFREE(MTYPE_OSPF_DISTANCE, odistance); } int ospf_distance_set(struct vty *vty, struct ospf *ospf, const char *distance_str, const char *ip_str, const char *access_list_str) { int ret; struct prefix_ipv4 p; uint8_t distance; struct route_node *rn; struct ospf_distance *odistance; ret = str2prefix_ipv4(ip_str, &p); if (ret == 0) { vty_out(vty, "Malformed prefix\n"); return CMD_WARNING_CONFIG_FAILED; } distance = atoi(distance_str); /* Get OSPF distance node. */ rn = route_node_get(ospf->distance_table, (struct prefix *)&p); if (rn->info) { odistance = rn->info; route_unlock_node(rn); } else { odistance = ospf_distance_new(); rn->info = odistance; } /* Set distance value. */ odistance->distance = distance; /* Reset access-list configuration. */ if (odistance->access_list) { free(odistance->access_list); odistance->access_list = NULL; } if (access_list_str) odistance->access_list = strdup(access_list_str); return CMD_SUCCESS; } int ospf_distance_unset(struct vty *vty, struct ospf *ospf, const char *distance_str, const char *ip_str, char const *access_list_str) { int ret; struct prefix_ipv4 p; struct route_node *rn; struct ospf_distance *odistance; ret = str2prefix_ipv4(ip_str, &p); if (ret == 0) { vty_out(vty, "Malformed prefix\n"); return CMD_WARNING_CONFIG_FAILED; } rn = route_node_lookup(ospf->distance_table, (struct prefix *)&p); if (!rn) { vty_out(vty, "Can't find specified prefix\n"); return CMD_WARNING_CONFIG_FAILED; } odistance = rn->info; if (odistance->access_list) free(odistance->access_list); ospf_distance_free(odistance); rn->info = NULL; route_unlock_node(rn); route_unlock_node(rn); return CMD_SUCCESS; } void ospf_distance_reset(struct ospf *ospf) { struct route_node *rn; struct ospf_distance *odistance; for (rn = route_top(ospf->distance_table); rn; rn = route_next(rn)) if ((odistance = rn->info) != NULL) { if (odistance->access_list) free(odistance->access_list); ospf_distance_free(odistance); rn->info = NULL; route_unlock_node(rn); } } uint8_t ospf_distance_apply(struct ospf *ospf, struct prefix_ipv4 *p, struct ospf_route * or) { if (ospf == NULL) return 0; if (ospf->distance_intra) if (or->path_type == OSPF_PATH_INTRA_AREA) return ospf->distance_intra; if (ospf->distance_inter) if (or->path_type == OSPF_PATH_INTER_AREA) return ospf->distance_inter; if (ospf->distance_external) if (or->path_type == OSPF_PATH_TYPE1_EXTERNAL || or->path_type == OSPF_PATH_TYPE2_EXTERNAL) return ospf->distance_external; if (ospf->distance_all) return ospf->distance_all; return 0; } void ospf_zebra_vrf_register(struct ospf *ospf) { if (!zclient || zclient->sock < 0 || !ospf) return; if (ospf->vrf_id != VRF_UNKNOWN) { if (IS_DEBUG_OSPF_EVENT) zlog_debug("%s: Register VRF %s id %u", __PRETTY_FUNCTION__, ospf_vrf_id_to_name(ospf->vrf_id), ospf->vrf_id); zclient_send_reg_requests(zclient, ospf->vrf_id); } } void ospf_zebra_vrf_deregister(struct ospf *ospf) { if (!zclient || zclient->sock < 0 || !ospf) return; if (ospf->vrf_id != VRF_DEFAULT && ospf->vrf_id != VRF_UNKNOWN) { if (IS_DEBUG_OSPF_EVENT) zlog_debug("%s: De-Register VRF %s id %u to Zebra.", __PRETTY_FUNCTION__, ospf_vrf_id_to_name(ospf->vrf_id), ospf->vrf_id); /* Deregister for router-id, interfaces, * redistributed routes. */ zclient_send_dereg_requests(zclient, ospf->vrf_id); } } static void ospf_zebra_connected(struct zclient *zclient) { /* Send the client registration */ bfd_client_sendmsg(zclient, ZEBRA_BFD_CLIENT_REGISTER, VRF_DEFAULT); zclient_send_reg_requests(zclient, VRF_DEFAULT); } void ospf_zebra_init(struct thread_master *master, unsigned short instance) { /* Allocate zebra structure. */ zclient = zclient_new(master, &zclient_options_default); zclient_init(zclient, ZEBRA_ROUTE_OSPF, instance, &ospfd_privs); zclient->zebra_connected = ospf_zebra_connected; zclient->router_id_update = ospf_router_id_update_zebra; zclient->interface_add = ospf_interface_add; zclient->interface_delete = ospf_interface_delete; zclient->interface_up = ospf_interface_state_up; zclient->interface_down = ospf_interface_state_down; zclient->interface_address_add = ospf_interface_address_add; zclient->interface_address_delete = ospf_interface_address_delete; zclient->interface_link_params = ospf_interface_link_params; zclient->interface_vrf_update = ospf_interface_vrf_update; zclient->redistribute_route_add = ospf_zebra_read_route; zclient->redistribute_route_del = ospf_zebra_read_route; access_list_add_hook(ospf_filter_update); access_list_delete_hook(ospf_filter_update); prefix_list_add_hook(ospf_prefix_list_update); prefix_list_delete_hook(ospf_prefix_list_update); } frr-7.2.1/ospfd/ospf_zebra.h0000644000000000000000000000736513610377563012645 00000000000000/* * Zebra connect library for OSPFd * Copyright (C) 1997, 98, 99, 2000 Kunihiro Ishiguro, Toshiaki Takada * * This file is part of GNU Zebra. * * GNU Zebra is free software; you can redistribute it and/or modify it * under the terms of the GNU General Public License as published by the * Free Software Foundation; either version 2, or (at your option) any * later version. * * GNU Zebra 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; see the file COPYING; if not, write to the Free Software * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA */ #ifndef _ZEBRA_OSPF_ZEBRA_H #define _ZEBRA_OSPF_ZEBRA_H #include "vty.h" #include "hook.h" #define EXTERNAL_METRIC_TYPE_1 0 #define EXTERNAL_METRIC_TYPE_2 1 #define DEFAULT_ROUTE ZEBRA_ROUTE_MAX #define DEFAULT_ROUTE_TYPE(T) ((T) == DEFAULT_ROUTE) /* OSPF distance. */ struct ospf_distance { /* Distance value for the IP source prefix. */ uint8_t distance; /* Name of the access-list to be matched. */ char *access_list; }; /* Prototypes */ extern void ospf_zebra_add(struct ospf *ospf, struct prefix_ipv4 *, struct ospf_route *); extern void ospf_zebra_delete(struct ospf *ospf, struct prefix_ipv4 *, struct ospf_route *); extern void ospf_zebra_add_discard(struct ospf *ospf, struct prefix_ipv4 *); extern void ospf_zebra_delete_discard(struct ospf *ospf, struct prefix_ipv4 *); extern int ospf_redistribute_check(struct ospf *, struct external_info *, int *); extern int ospf_distribute_check_connected(struct ospf *, struct external_info *); extern void ospf_distribute_list_update(struct ospf *, int, unsigned short); extern int ospf_is_type_redistributed(struct ospf *, int, unsigned short); extern void ospf_distance_reset(struct ospf *); extern uint8_t ospf_distance_apply(struct ospf *ospf, struct prefix_ipv4 *, struct ospf_route *); extern struct ospf_external *ospf_external_lookup(struct ospf *, uint8_t, unsigned short); extern struct ospf_external *ospf_external_add(struct ospf *, uint8_t, unsigned short); extern void ospf_external_del(struct ospf *, uint8_t, unsigned short); extern struct ospf_redist *ospf_redist_lookup(struct ospf *, uint8_t, unsigned short); extern struct ospf_redist *ospf_redist_add(struct ospf *, uint8_t, unsigned short); extern void ospf_redist_del(struct ospf *, uint8_t, unsigned short); extern int ospf_redistribute_set(struct ospf *, int, unsigned short, int, int); extern int ospf_redistribute_unset(struct ospf *, int, unsigned short); extern int ospf_redistribute_default_set(struct ospf *, int, int, int); extern int ospf_redistribute_default_unset(struct ospf *); extern int ospf_distribute_list_out_set(struct ospf *, int, const char *); extern int ospf_distribute_list_out_unset(struct ospf *, int, const char *); extern void ospf_routemap_set(struct ospf_redist *, const char *); extern void ospf_routemap_unset(struct ospf_redist *); extern int ospf_distance_set(struct vty *, struct ospf *, const char *, const char *, const char *); extern int ospf_distance_unset(struct vty *, struct ospf *, const char *, const char *, const char *); extern void ospf_zebra_init(struct thread_master *, unsigned short); extern void ospf_zebra_vrf_register(struct ospf *ospf); extern void ospf_zebra_vrf_deregister(struct ospf *ospf); DECLARE_HOOK(ospf_if_update, (struct interface * ifp), (ifp)) DECLARE_HOOK(ospf_if_delete, (struct interface * ifp), (ifp)) #endif /* _ZEBRA_OSPF_ZEBRA_H */ frr-7.2.1/ospfd/ospfd.c0000644000000000000000000015011413610377563011610 00000000000000/* OSPF version 2 daemon program. * Copyright (C) 1999, 2000 Toshiaki Takada * * This file is part of GNU Zebra. * * GNU Zebra is free software; you can redistribute it and/or modify it * under the terms of the GNU General Public License as published by the * Free Software Foundation; either version 2, or (at your option) any * later version. * * GNU Zebra 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; see the file COPYING; if not, write to the Free Software * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA */ #include #include "thread.h" #include "vty.h" #include "command.h" #include "linklist.h" #include "prefix.h" #include "table.h" #include "if.h" #include "memory.h" #include "stream.h" #include "log.h" #include "sockunion.h" /* for inet_aton () */ #include "zclient.h" #include "routemap.h" #include "plist.h" #include "sockopt.h" #include "bfd.h" #include "libfrr.h" #include "defaults.h" #include "lib_errors.h" #include "ospfd/ospfd.h" #include "ospfd/ospf_network.h" #include "ospfd/ospf_interface.h" #include "ospfd/ospf_ism.h" #include "ospfd/ospf_asbr.h" #include "ospfd/ospf_lsa.h" #include "ospfd/ospf_lsdb.h" #include "ospfd/ospf_neighbor.h" #include "ospfd/ospf_nsm.h" #include "ospfd/ospf_spf.h" #include "ospfd/ospf_packet.h" #include "ospfd/ospf_dump.h" #include "ospfd/ospf_route.h" #include "ospfd/ospf_zebra.h" #include "ospfd/ospf_abr.h" #include "ospfd/ospf_flood.h" #include "ospfd/ospf_ase.h" DEFINE_QOBJ_TYPE(ospf) /* OSPF process wide configuration. */ static struct ospf_master ospf_master; /* OSPF process wide configuration pointer to export. */ struct ospf_master *om; extern struct zclient *zclient; static void ospf_remove_vls_through_area(struct ospf *, struct ospf_area *); static void ospf_network_free(struct ospf *, struct ospf_network *); static void ospf_area_free(struct ospf_area *); static void ospf_network_run(struct prefix *, struct ospf_area *); static void ospf_network_run_interface(struct ospf *, struct interface *, struct prefix *, struct ospf_area *); static void ospf_network_run_subnet(struct ospf *, struct connected *, struct prefix *, struct ospf_area *); static int ospf_network_match_iface(const struct connected *, const struct prefix *); static void ospf_finish_final(struct ospf *); #define OSPF_EXTERNAL_LSA_ORIGINATE_DELAY 1 void ospf_router_id_update(struct ospf *ospf) { struct vrf *vrf = vrf_lookup_by_id(ospf->vrf_id); struct in_addr router_id, router_id_old; struct ospf_interface *oi; struct interface *ifp; struct listnode *node; if (!ospf->oi_running) { if (IS_DEBUG_OSPF_EVENT) zlog_debug( "Router ospf not configured -- Router-ID update postponed"); return; } if (IS_DEBUG_OSPF_EVENT) zlog_debug("Router-ID[OLD:%s]: Update", inet_ntoa(ospf->router_id)); router_id_old = ospf->router_id; /* Select the router ID based on these priorities: 1. Statically assigned router ID is always the first choice. 2. If there is no statically assigned router ID, then try to stick with the most recent value, since changing router ID's is very disruptive. 3. Last choice: just go with whatever the zebra daemon recommends. */ if (ospf->router_id_static.s_addr != 0) router_id = ospf->router_id_static; else if (ospf->router_id.s_addr != 0) router_id = ospf->router_id; else router_id = ospf->router_id_zebra; if (IS_DEBUG_OSPF_EVENT) zlog_debug("Router-ID[OLD:%s]: Update to %s", inet_ntoa(ospf->router_id), inet_ntoa(router_id)); if (!IPV4_ADDR_SAME(&router_id_old, &router_id)) { for (ALL_LIST_ELEMENTS_RO(ospf->oiflist, node, oi)) { /* Some nbrs are identified by router_id, these needs * to be rebuilt. Possible optimization would be to do * oi->nbr_self->router_id = router_id for * !(virtual | ptop) links */ ospf_nbr_self_reset(oi, router_id); } /* Flush (inline) all external LSAs based on the OSPF_LSA_SELF * flag */ if (ospf->lsdb) { struct route_node *rn; struct ospf_lsa *lsa; LSDB_LOOP (EXTERNAL_LSDB(ospf), rn, lsa) if (IS_LSA_SELF(lsa)) ospf_lsa_flush_schedule(ospf, lsa); } ospf->router_id = router_id; if (IS_DEBUG_OSPF_EVENT) zlog_debug("Router-ID[NEW:%s]: Update", inet_ntoa(ospf->router_id)); /* Flush (inline) all external LSAs which now match the new router-id, need to adjust the OSPF_LSA_SELF flag, so the flush doesn't hit asserts in ospf_refresher_unregister_lsa(). This step is needed because the current quagga code does look-up for self-originated LSAs based on the self router-id alone but expects OSPF_LSA_SELF to be properly set */ if (ospf->lsdb) { struct route_node *rn; struct ospf_lsa *lsa; LSDB_LOOP (EXTERNAL_LSDB(ospf), rn, lsa) { /* AdvRouter and Router ID is the same. */ if (IPV4_ADDR_SAME(&lsa->data->adv_router, &ospf->router_id)) { SET_FLAG(lsa->flags, OSPF_LSA_SELF_CHECKED); SET_FLAG(lsa->flags, OSPF_LSA_SELF); ospf_lsa_flush_schedule(ospf, lsa); } } } /* update router-lsa's for each area */ ospf_router_lsa_update(ospf); /* update ospf_interface's */ FOR_ALL_INTERFACES (vrf, ifp) ospf_if_update(ospf, ifp); ospf_external_lsa_rid_change(ospf); } } /* For OSPF area sort by area id. */ static int ospf_area_id_cmp(struct ospf_area *a1, struct ospf_area *a2) { if (ntohl(a1->area_id.s_addr) > ntohl(a2->area_id.s_addr)) return 1; if (ntohl(a1->area_id.s_addr) < ntohl(a2->area_id.s_addr)) return -1; return 0; } /* Allocate new ospf structure. */ static struct ospf *ospf_new(unsigned short instance, const char *name) { int i; struct vrf *vrf = NULL; struct ospf *new = XCALLOC(MTYPE_OSPF_TOP, sizeof(struct ospf)); new->instance = instance; new->router_id.s_addr = htonl(0); new->router_id_static.s_addr = htonl(0); if (name) { vrf = vrf_lookup_by_name(name); if (vrf) new->vrf_id = vrf->vrf_id; else new->vrf_id = VRF_UNKNOWN; /* Freed in ospf_finish_final */ new->name = XSTRDUP(MTYPE_OSPF_TOP, name); if (IS_DEBUG_OSPF_EVENT) zlog_debug( "%s: Create new ospf instance with vrf_name %s vrf_id %u", __PRETTY_FUNCTION__, name, new->vrf_id); } else { new->vrf_id = VRF_DEFAULT; vrf = vrf_lookup_by_id(VRF_DEFAULT); } if (vrf) ospf_vrf_link(new, vrf); ospf_zebra_vrf_register(new); new->abr_type = OSPF_ABR_DEFAULT; new->oiflist = list_new(); new->vlinks = list_new(); new->areas = list_new(); new->areas->cmp = (int (*)(void *, void *))ospf_area_id_cmp; new->networks = route_table_init(); new->nbr_nbma = route_table_init(); new->lsdb = ospf_lsdb_new(); new->default_originate = DEFAULT_ORIGINATE_NONE; new->passive_interface_default = OSPF_IF_ACTIVE; new->new_external_route = route_table_init(); new->old_external_route = route_table_init(); new->external_lsas = route_table_init(); new->stub_router_startup_time = OSPF_STUB_ROUTER_UNCONFIGURED; new->stub_router_shutdown_time = OSPF_STUB_ROUTER_UNCONFIGURED; new->stub_router_admin_set = OSPF_STUB_ROUTER_ADMINISTRATIVE_UNSET; /* Distribute parameter init. */ for (i = 0; i <= ZEBRA_ROUTE_MAX; i++) { new->dtag[i] = 0; } new->default_metric = -1; new->ref_bandwidth = OSPF_DEFAULT_REF_BANDWIDTH; /* LSA timers */ new->min_ls_interval = OSPF_MIN_LS_INTERVAL; new->min_ls_arrival = OSPF_MIN_LS_ARRIVAL; /* SPF timer value init. */ new->spf_delay = OSPF_SPF_DELAY_DEFAULT; new->spf_holdtime = OSPF_SPF_HOLDTIME_DEFAULT; new->spf_max_holdtime = OSPF_SPF_MAX_HOLDTIME_DEFAULT; new->spf_hold_multiplier = 1; /* MaxAge init. */ new->maxage_delay = OSPF_LSA_MAXAGE_REMOVE_DELAY_DEFAULT; new->maxage_lsa = route_table_init(); new->t_maxage_walker = NULL; thread_add_timer(master, ospf_lsa_maxage_walker, new, OSPF_LSA_MAXAGE_CHECK_INTERVAL, &new->t_maxage_walker); /* Distance table init. */ new->distance_table = route_table_init(); new->lsa_refresh_queue.index = 0; new->lsa_refresh_interval = OSPF_LSA_REFRESH_INTERVAL_DEFAULT; new->t_lsa_refresher = NULL; thread_add_timer(master, ospf_lsa_refresh_walker, new, new->lsa_refresh_interval, &new->t_lsa_refresher); new->lsa_refresher_started = monotime(NULL); new->ibuf = stream_new(OSPF_MAX_PACKET_SIZE + 1); new->t_read = NULL; new->oi_write_q = list_new(); new->write_oi_count = OSPF_WRITE_INTERFACE_COUNT_DEFAULT; /* Enable "log-adjacency-changes" */ #if DFLT_OSPF_LOG_ADJACENCY_CHANGES SET_FLAG(new->config, OSPF_LOG_ADJACENCY_CHANGES); #endif QOBJ_REG(new, ospf); new->fd = -1; if ((ospf_sock_init(new)) < 0) { if (new->vrf_id != VRF_UNKNOWN) flog_err( EC_LIB_SOCKET, "%s: ospf_sock_init is unable to open a socket", __func__); return new; } thread_add_read(master, ospf_read, new, new->fd, &new->t_read); return new; } struct ospf *ospf_lookup_instance(unsigned short instance) { struct ospf *ospf; struct listnode *node, *nnode; if (listcount(om->ospf) == 0) return NULL; for (ALL_LIST_ELEMENTS(om->ospf, node, nnode, ospf)) if ((ospf->instance == 0 && instance == 0) || (ospf->instance && instance && ospf->instance == instance)) return ospf; return NULL; } static int ospf_is_ready(struct ospf *ospf) { /* OSPF must be on and Router-ID must be configured. */ if (!ospf || ospf->router_id.s_addr == 0) return 0; return 1; } static void ospf_add(struct ospf *ospf) { listnode_add(om->ospf, ospf); } static void ospf_delete(struct ospf *ospf) { listnode_delete(om->ospf, ospf); } struct ospf *ospf_lookup_by_inst_name(unsigned short instance, const char *name) { struct ospf *ospf = NULL; struct listnode *node, *nnode; if (name == NULL || strmatch(name, VRF_DEFAULT_NAME)) return ospf_lookup_by_vrf_id(VRF_DEFAULT); for (ALL_LIST_ELEMENTS(om->ospf, node, nnode, ospf)) { if ((ospf->instance == instance) && ((ospf->name == NULL && name == NULL) || (ospf->name && name && strcmp(ospf->name, name) == 0))) return ospf; } return NULL; } struct ospf *ospf_get(unsigned short instance, const char *name) { struct ospf *ospf; /* vrf name provided call inst and name based api * in case of no name pass default ospf instance */ if (name) ospf = ospf_lookup_by_inst_name(instance, name); else ospf = ospf_lookup_by_vrf_id(VRF_DEFAULT); if (ospf == NULL) { ospf = ospf_new(instance, name); ospf_add(ospf); if (ospf->router_id_static.s_addr == 0) ospf_router_id_update(ospf); ospf_opaque_type11_lsa_init(ospf); } return ospf; } struct ospf *ospf_get_instance(unsigned short instance) { struct ospf *ospf; ospf = ospf_lookup_instance(instance); if (ospf == NULL) { ospf = ospf_new(instance, NULL /* VRF_DEFAULT*/); ospf_add(ospf); if (ospf->router_id_static.s_addr == 0) { if (vrf_lookup_by_id(ospf->vrf_id)) ospf_router_id_update(ospf); else { if (IS_DEBUG_OSPF_EVENT) zlog_debug( "%s: ospf VRF (id %d) is not active yet, skip router id update", __PRETTY_FUNCTION__, ospf->vrf_id); } ospf_router_id_update(ospf); } ospf_opaque_type11_lsa_init(ospf); } return ospf; } struct ospf *ospf_lookup_by_vrf_id(vrf_id_t vrf_id) { struct vrf *vrf = NULL; vrf = vrf_lookup_by_id(vrf_id); if (!vrf) return NULL; return (vrf->info) ? (struct ospf *)vrf->info : NULL; } /* It should only be used when processing incoming info update from zebra. * Other situations, it is not sufficient to lookup the ospf instance by * vrf_name only without using the instance number. */ static struct ospf *ospf_lookup_by_name(const char *vrf_name) { struct ospf *ospf = NULL; struct listnode *node, *nnode; for (ALL_LIST_ELEMENTS(om->ospf, node, nnode, ospf)) if ((ospf->name == NULL && vrf_name == NULL) || (ospf->name && vrf_name && strcmp(ospf->name, vrf_name) == 0)) return ospf; return NULL; } /* Handle the second half of deferred shutdown. This is called either * from the deferred-shutdown timer thread, or directly through * ospf_deferred_shutdown_check. * * Function is to cleanup G-R state, if required then call ospf_finish_final * to complete shutdown of this ospf instance. Possibly exit if the * whole process is being shutdown and this was the last OSPF instance. */ static void ospf_deferred_shutdown_finish(struct ospf *ospf) { ospf->stub_router_shutdown_time = OSPF_STUB_ROUTER_UNCONFIGURED; OSPF_TIMER_OFF(ospf->t_deferred_shutdown); ospf_finish_final(ospf); /* *ospf is now invalid */ /* ospfd being shut-down? If so, was this the last ospf instance? */ if (CHECK_FLAG(om->options, OSPF_MASTER_SHUTDOWN) && (listcount(om->ospf) == 0)) { exit(0); } return; } /* Timer thread for G-R */ static int ospf_deferred_shutdown_timer(struct thread *t) { struct ospf *ospf = THREAD_ARG(t); ospf_deferred_shutdown_finish(ospf); return 0; } /* Check whether deferred-shutdown must be scheduled, otherwise call * down directly into second-half of instance shutdown. */ static void ospf_deferred_shutdown_check(struct ospf *ospf) { unsigned long timeout; struct listnode *ln; struct ospf_area *area; /* deferred shutdown already running? */ if (ospf->t_deferred_shutdown) return; /* Should we try push out max-metric LSAs? */ if (ospf->stub_router_shutdown_time != OSPF_STUB_ROUTER_UNCONFIGURED) { for (ALL_LIST_ELEMENTS_RO(ospf->areas, ln, area)) { SET_FLAG(area->stub_router_state, OSPF_AREA_ADMIN_STUB_ROUTED); if (!CHECK_FLAG(area->stub_router_state, OSPF_AREA_IS_STUB_ROUTED)) ospf_router_lsa_update_area(area); } timeout = ospf->stub_router_shutdown_time; } else { /* No timer needed */ ospf_deferred_shutdown_finish(ospf); return; } OSPF_TIMER_ON(ospf->t_deferred_shutdown, ospf_deferred_shutdown_timer, timeout); return; } /* Shut down the entire process */ void ospf_terminate(void) { struct ospf *ospf; struct listnode *node, *nnode; /* shutdown already in progress */ if (CHECK_FLAG(om->options, OSPF_MASTER_SHUTDOWN)) return; SET_FLAG(om->options, OSPF_MASTER_SHUTDOWN); /* exit immediately if OSPF not actually running */ if (listcount(om->ospf) == 0) exit(0); bfd_gbl_exit(); for (ALL_LIST_ELEMENTS(om->ospf, node, nnode, ospf)) ospf_finish(ospf); /* Cleanup route maps */ route_map_finish(); /* reverse prefix_list_init */ prefix_list_add_hook(NULL); prefix_list_delete_hook(NULL); prefix_list_reset(); /* Cleanup vrf info */ ospf_vrf_terminate(); /* Deliberately go back up, hopefully to thread scheduler, as * One or more ospf_finish()'s may have deferred shutdown to a timer * thread */ zclient_stop(zclient); zclient_free(zclient); frr_fini(); } void ospf_finish(struct ospf *ospf) { /* let deferred shutdown decide */ ospf_deferred_shutdown_check(ospf); /* if ospf_deferred_shutdown returns, then ospf_finish_final is * deferred to expiry of G-S timer thread. Return back up, hopefully * to thread scheduler. */ return; } /* Final cleanup of ospf instance */ static void ospf_finish_final(struct ospf *ospf) { struct vrf *vrf = vrf_lookup_by_id(ospf->vrf_id); struct route_node *rn; struct ospf_nbr_nbma *nbr_nbma; struct ospf_lsa *lsa; struct interface *ifp; struct ospf_interface *oi; struct ospf_area *area; struct ospf_vl_data *vl_data; struct listnode *node, *nnode; int i; unsigned short instance = 0; QOBJ_UNREG(ospf); ospf_opaque_type11_lsa_term(ospf); ospf_opaque_finish(); ospf_flush_self_originated_lsas_now(ospf); /* Unregister redistribution */ for (i = 0; i < ZEBRA_ROUTE_MAX; i++) { struct list *red_list; struct ospf_redist *red; red_list = ospf->redist[i]; if (!red_list) continue; for (ALL_LIST_ELEMENTS(red_list, node, nnode, red)) { ospf_redistribute_unset(ospf, i, red->instance); ospf_redist_del(ospf, i, red->instance); } } ospf_redistribute_default_set(ospf, DEFAULT_ORIGINATE_NONE, 0, 0); for (ALL_LIST_ELEMENTS(ospf->areas, node, nnode, area)) ospf_remove_vls_through_area(ospf, area); for (ALL_LIST_ELEMENTS(ospf->vlinks, node, nnode, vl_data)) ospf_vl_delete(ospf, vl_data); list_delete(&ospf->vlinks); /* Remove any ospf interface config params */ FOR_ALL_INTERFACES (vrf, ifp) { struct ospf_if_params *params; params = IF_DEF_PARAMS(ifp); if (OSPF_IF_PARAM_CONFIGURED(params, if_area)) UNSET_IF_PARAM(params, if_area); } /* Reset interface. */ for (ALL_LIST_ELEMENTS(ospf->oiflist, node, nnode, oi)) ospf_if_free(oi); list_delete(&ospf->oiflist); ospf->oi_running = 0; /* De-Register VRF */ ospf_zebra_vrf_deregister(ospf); /* Clear static neighbors */ for (rn = route_top(ospf->nbr_nbma); rn; rn = route_next(rn)) if ((nbr_nbma = rn->info)) { OSPF_POLL_TIMER_OFF(nbr_nbma->t_poll); if (nbr_nbma->nbr) { nbr_nbma->nbr->nbr_nbma = NULL; nbr_nbma->nbr = NULL; } if (nbr_nbma->oi) { listnode_delete(nbr_nbma->oi->nbr_nbma, nbr_nbma); nbr_nbma->oi = NULL; } XFREE(MTYPE_OSPF_NEIGHBOR_STATIC, nbr_nbma); } route_table_finish(ospf->nbr_nbma); /* Clear networks and Areas. */ for (rn = route_top(ospf->networks); rn; rn = route_next(rn)) { struct ospf_network *network; if ((network = rn->info) != NULL) { ospf_network_free(ospf, network); rn->info = NULL; route_unlock_node(rn); } } route_table_finish(ospf->networks); for (ALL_LIST_ELEMENTS(ospf->areas, node, nnode, area)) { listnode_delete(ospf->areas, area); ospf_area_free(area); } /* Cancel all timers. */ OSPF_TIMER_OFF(ospf->t_read); OSPF_TIMER_OFF(ospf->t_write); OSPF_TIMER_OFF(ospf->t_spf_calc); OSPF_TIMER_OFF(ospf->t_ase_calc); OSPF_TIMER_OFF(ospf->t_maxage); OSPF_TIMER_OFF(ospf->t_maxage_walker); OSPF_TIMER_OFF(ospf->t_abr_task); OSPF_TIMER_OFF(ospf->t_asbr_check); OSPF_TIMER_OFF(ospf->t_distribute_update); OSPF_TIMER_OFF(ospf->t_lsa_refresher); OSPF_TIMER_OFF(ospf->t_opaque_lsa_self); OSPF_TIMER_OFF(ospf->t_sr_update); LSDB_LOOP (OPAQUE_AS_LSDB(ospf), rn, lsa) ospf_discard_from_db(ospf, ospf->lsdb, lsa); LSDB_LOOP (EXTERNAL_LSDB(ospf), rn, lsa) ospf_discard_from_db(ospf, ospf->lsdb, lsa); ospf_lsdb_delete_all(ospf->lsdb); ospf_lsdb_free(ospf->lsdb); for (rn = route_top(ospf->maxage_lsa); rn; rn = route_next(rn)) { if ((lsa = rn->info) != NULL) { ospf_lsa_unlock(&lsa); rn->info = NULL; } route_unlock_node(rn); } route_table_finish(ospf->maxage_lsa); if (ospf->old_table) ospf_route_table_free(ospf->old_table); if (ospf->new_table) { ospf_route_delete(ospf, ospf->new_table); ospf_route_table_free(ospf->new_table); } if (ospf->old_rtrs) ospf_rtrs_free(ospf->old_rtrs); if (ospf->new_rtrs) ospf_rtrs_free(ospf->new_rtrs); if (ospf->new_external_route) { ospf_route_delete(ospf, ospf->new_external_route); ospf_route_table_free(ospf->new_external_route); } if (ospf->old_external_route) { ospf_route_delete(ospf, ospf->old_external_route); ospf_route_table_free(ospf->old_external_route); } if (ospf->external_lsas) { ospf_ase_external_lsas_finish(ospf->external_lsas); } for (i = ZEBRA_ROUTE_SYSTEM; i <= ZEBRA_ROUTE_MAX; i++) { struct list *ext_list; struct ospf_external *ext; ext_list = ospf->external[i]; if (!ext_list) continue; for (ALL_LIST_ELEMENTS(ext_list, node, nnode, ext)) { if (ext->external_info) for (rn = route_top(ext->external_info); rn; rn = route_next(rn)) { if (rn->info == NULL) continue; XFREE(MTYPE_OSPF_EXTERNAL_INFO, rn->info); rn->info = NULL; route_unlock_node(rn); } ospf_external_del(ospf, i, ext->instance); } } ospf_distance_reset(ospf); route_table_finish(ospf->distance_table); if (!CHECK_FLAG(om->options, OSPF_MASTER_SHUTDOWN)) instance = ospf->instance; list_delete(&ospf->areas); list_delete(&ospf->oi_write_q); close(ospf->fd); stream_free(ospf->ibuf); ospf->fd = -1; ospf_delete(ospf); if (ospf->name) { vrf = vrf_lookup_by_name(ospf->name); if (vrf) ospf_vrf_unlink(ospf, vrf); XFREE(MTYPE_OSPF_TOP, ospf->name); } else { vrf = vrf_lookup_by_id(VRF_DEFAULT); if (vrf) ospf_vrf_unlink(ospf, vrf); } XFREE(MTYPE_OSPF_TOP, ospf); if (!CHECK_FLAG(om->options, OSPF_MASTER_SHUTDOWN)) ospf_get_instance(instance); } /* allocate new OSPF Area object */ static struct ospf_area *ospf_area_new(struct ospf *ospf, struct in_addr area_id) { struct ospf_area *new; /* Allocate new config_network. */ new = XCALLOC(MTYPE_OSPF_AREA, sizeof(struct ospf_area)); new->ospf = ospf; new->area_id = area_id; new->area_id_fmt = OSPF_AREA_ID_FMT_DOTTEDQUAD; new->external_routing = OSPF_AREA_DEFAULT; new->default_cost = 1; new->auth_type = OSPF_AUTH_NULL; /* New LSDB init. */ new->lsdb = ospf_lsdb_new(); /* Self-originated LSAs initialize. */ new->router_lsa_self = NULL; ospf_opaque_type10_lsa_init(new); new->oiflist = list_new(); new->ranges = route_table_init(); if (area_id.s_addr == OSPF_AREA_BACKBONE) ospf->backbone = new; return new; } static void ospf_area_free(struct ospf_area *area) { struct route_node *rn; struct ospf_lsa *lsa; ospf_opaque_type10_lsa_term(area); /* Free LSDBs. */ LSDB_LOOP (ROUTER_LSDB(area), rn, lsa) ospf_discard_from_db(area->ospf, area->lsdb, lsa); LSDB_LOOP (NETWORK_LSDB(area), rn, lsa) ospf_discard_from_db(area->ospf, area->lsdb, lsa); LSDB_LOOP (SUMMARY_LSDB(area), rn, lsa) ospf_discard_from_db(area->ospf, area->lsdb, lsa); LSDB_LOOP (ASBR_SUMMARY_LSDB(area), rn, lsa) ospf_discard_from_db(area->ospf, area->lsdb, lsa); LSDB_LOOP (NSSA_LSDB(area), rn, lsa) ospf_discard_from_db(area->ospf, area->lsdb, lsa); LSDB_LOOP (OPAQUE_AREA_LSDB(area), rn, lsa) ospf_discard_from_db(area->ospf, area->lsdb, lsa); LSDB_LOOP (OPAQUE_LINK_LSDB(area), rn, lsa) ospf_discard_from_db(area->ospf, area->lsdb, lsa); ospf_lsdb_delete_all(area->lsdb); ospf_lsdb_free(area->lsdb); ospf_lsa_unlock(&area->router_lsa_self); route_table_finish(area->ranges); list_delete(&area->oiflist); if (EXPORT_NAME(area)) free(EXPORT_NAME(area)); if (IMPORT_NAME(area)) free(IMPORT_NAME(area)); /* Cancel timer. */ OSPF_TIMER_OFF(area->t_stub_router); OSPF_TIMER_OFF(area->t_opaque_lsa_self); if (OSPF_IS_AREA_BACKBONE(area)) area->ospf->backbone = NULL; XFREE(MTYPE_OSPF_AREA, area); } void ospf_area_check_free(struct ospf *ospf, struct in_addr area_id) { struct ospf_area *area; area = ospf_area_lookup_by_area_id(ospf, area_id); if (area && listcount(area->oiflist) == 0 && area->ranges->top == NULL && !ospf_vl_count(ospf, area) && area->shortcut_configured == OSPF_SHORTCUT_DEFAULT && area->external_routing == OSPF_AREA_DEFAULT && area->no_summary == 0 && area->default_cost == 1 && EXPORT_NAME(area) == NULL && IMPORT_NAME(area) == NULL && area->auth_type == OSPF_AUTH_NULL) { listnode_delete(ospf->areas, area); ospf_area_free(area); } } struct ospf_area *ospf_area_get(struct ospf *ospf, struct in_addr area_id) { struct ospf_area *area; area = ospf_area_lookup_by_area_id(ospf, area_id); if (!area) { area = ospf_area_new(ospf, area_id); listnode_add_sort(ospf->areas, area); ospf_check_abr_status(ospf); if (ospf->stub_router_admin_set == OSPF_STUB_ROUTER_ADMINISTRATIVE_SET) { SET_FLAG(area->stub_router_state, OSPF_AREA_ADMIN_STUB_ROUTED); } } return area; } struct ospf_area *ospf_area_lookup_by_area_id(struct ospf *ospf, struct in_addr area_id) { struct ospf_area *area; struct listnode *node; for (ALL_LIST_ELEMENTS_RO(ospf->areas, node, area)) if (IPV4_ADDR_SAME(&area->area_id, &area_id)) return area; return NULL; } void ospf_area_add_if(struct ospf_area *area, struct ospf_interface *oi) { listnode_add(area->oiflist, oi); } void ospf_area_del_if(struct ospf_area *area, struct ospf_interface *oi) { listnode_delete(area->oiflist, oi); } static void add_ospf_interface(struct connected *co, struct ospf_area *area) { struct ospf_interface *oi; oi = ospf_if_new(area->ospf, co->ifp, co->address); oi->connected = co; oi->area = area; oi->params = ospf_lookup_if_params(co->ifp, oi->address->u.prefix4); oi->output_cost = ospf_if_get_output_cost(oi); /* Relate ospf interface to ospf instance. */ oi->ospf = area->ospf; /* update network type as interface flag */ /* If network type is specified previously, skip network type setting. */ oi->type = IF_DEF_PARAMS(co->ifp)->type; /* Add pseudo neighbor. */ ospf_nbr_self_reset(oi, oi->ospf->router_id); ospf_area_add_if(oi->area, oi); /* * if router_id is not configured, dont bring up * interfaces. * ospf_router_id_update() will call ospf_if_update * whenever r-id is configured instead. */ if ((area->ospf->router_id.s_addr != 0) && if_is_operative(co->ifp)) ospf_if_up(oi); } static void update_redistributed(struct ospf *ospf, int add_to_ospf) { struct route_node *rn; struct external_info *ei; struct ospf_external *ext; if (ospf_is_type_redistributed(ospf, ZEBRA_ROUTE_CONNECT, 0)) { ext = ospf_external_lookup(ospf, ZEBRA_ROUTE_CONNECT, 0); if ((ext) && EXTERNAL_INFO(ext)) { for (rn = route_top(EXTERNAL_INFO(ext)); rn; rn = route_next(rn)) { ei = rn->info; if (ei == NULL) continue; if (add_to_ospf) { if (ospf_external_info_find_lsa(ospf, &ei->p)) if (!ospf_distribute_check_connected( ospf, ei)) ospf_external_lsa_flush( ospf, ei->type, &ei->p, ei->ifindex /*, ei->nexthop */); } else { if (!ospf_external_info_find_lsa( ospf, &ei->p)) if (ospf_distribute_check_connected( ospf, ei)) ospf_external_lsa_originate( ospf, ei); } } } } } /* Config network statement related functions. */ static struct ospf_network *ospf_network_new(struct in_addr area_id) { struct ospf_network *new; new = XCALLOC(MTYPE_OSPF_NETWORK, sizeof(struct ospf_network)); new->area_id = area_id; new->area_id_fmt = OSPF_AREA_ID_FMT_DOTTEDQUAD; return new; } static void ospf_network_free(struct ospf *ospf, struct ospf_network *network) { ospf_area_check_free(ospf, network->area_id); ospf_schedule_abr_task(ospf); XFREE(MTYPE_OSPF_NETWORK, network); } int ospf_network_set(struct ospf *ospf, struct prefix_ipv4 *p, struct in_addr area_id, int df) { struct ospf_network *network; struct ospf_area *area; struct route_node *rn; rn = route_node_get(ospf->networks, (struct prefix *)p); if (rn->info) { network = rn->info; route_unlock_node(rn); if (IPV4_ADDR_SAME(&area_id, &network->area_id)) { return 1; } else { /* There is already same network statement. */ return 0; } } rn->info = network = ospf_network_new(area_id); network->area_id_fmt = df; area = ospf_area_get(ospf, area_id); ospf_area_display_format_set(ospf, area, df); /* Run network config now. */ ospf_network_run((struct prefix *)p, area); /* Update connected redistribute. */ update_redistributed(ospf, 1); /* interfaces possibly added */ ospf_area_check_free(ospf, area_id); return 1; } int ospf_network_unset(struct ospf *ospf, struct prefix_ipv4 *p, struct in_addr area_id) { struct route_node *rn; struct ospf_network *network; struct listnode *node, *nnode; struct ospf_interface *oi; rn = route_node_lookup(ospf->networks, (struct prefix *)p); if (rn == NULL) return 0; network = rn->info; route_unlock_node(rn); if (!IPV4_ADDR_SAME(&area_id, &network->area_id)) return 0; ospf_network_free(ospf, rn->info); rn->info = NULL; route_unlock_node(rn); /* initial reference */ /* Find interfaces that are not configured already. */ for (ALL_LIST_ELEMENTS(ospf->oiflist, node, nnode, oi)) { if (oi->type == OSPF_IFTYPE_VIRTUALLINK) continue; ospf_network_run_subnet(ospf, oi->connected, NULL, NULL); } /* Update connected redistribute. */ update_redistributed(ospf, 0); /* interfaces possibly removed */ ospf_area_check_free(ospf, area_id); return 1; } /* Ensure there's an OSPF instance, as "ip ospf area" enabled OSPF means * there might not be any 'router ospf' config. * * Otherwise, doesn't do anything different to ospf_if_update for now */ void ospf_interface_area_set(struct ospf *ospf, struct interface *ifp) { if (!ospf) return; ospf_if_update(ospf, ifp); /* if_update does a update_redistributed */ return; } void ospf_interface_area_unset(struct ospf *ospf, struct interface *ifp) { struct route_node *rn_oi; if (!ospf) return; /* Ospf not ready yet */ /* Find interfaces that may need to be removed. */ for (rn_oi = route_top(IF_OIFS(ifp)); rn_oi; rn_oi = route_next(rn_oi)) { struct ospf_interface *oi = NULL; if ((oi = rn_oi->info) == NULL) continue; if (oi->type == OSPF_IFTYPE_VIRTUALLINK) continue; ospf_network_run_subnet(ospf, oi->connected, NULL, NULL); } /* Update connected redistribute. */ update_redistributed(ospf, 0); /* interfaces possibly removed */ } bool ospf_interface_area_is_already_set(struct ospf *ospf, struct interface *ifp) { struct route_node *rn_oi; if (!ospf) return false; /* Ospf not ready yet */ /* Find interfaces that may need to be removed. */ for (rn_oi = route_top(IF_OIFS(ifp)); rn_oi; rn_oi = route_next(rn_oi)) { struct ospf_interface *oi = rn_oi->info; if (oi == NULL) continue; if (oi->type == OSPF_IFTYPE_VIRTUALLINK) continue; /* at least one route covered by interface * that implies already done */ return true; } return false; } /* Check whether interface matches given network * returns: 1, true. 0, false */ static int ospf_network_match_iface(const struct connected *co, const struct prefix *net) { /* new approach: more elegant and conceptually clean */ return prefix_match_network_statement(net, CONNECTED_PREFIX(co)); } static void ospf_update_interface_area(struct connected *co, struct ospf_area *area) { struct ospf_interface *oi = ospf_if_table_lookup(co->ifp, co->address); /* nothing to be done case */ if (oi && oi->area == area) { return; } if (oi) ospf_if_free(oi); add_ospf_interface(co, area); } /* Run OSPF for the given subnet, taking into account the following * possible sources of area configuration, in the given order of preference: * * - Whether there is interface+address specific area configuration * - Whether there is a default area for the interface * - Whether there is an area given as a parameter. * - If no specific network prefix/area is supplied, whether there's * a matching network configured. */ static void ospf_network_run_subnet(struct ospf *ospf, struct connected *co, struct prefix *p, struct ospf_area *given_area) { struct ospf_interface *oi; struct ospf_if_params *params; struct ospf_area *area = NULL; struct route_node *rn; int configed = 0; if (CHECK_FLAG(co->flags, ZEBRA_IFA_SECONDARY)) return; if (co->address->family != AF_INET) return; /* Try determine the appropriate area for this interface + address * Start by checking interface config */ params = ospf_lookup_if_params(co->ifp, co->address->u.prefix4); if (params && OSPF_IF_PARAM_CONFIGURED(params, if_area)) area = ospf_area_get(ospf, params->if_area); else { params = IF_DEF_PARAMS(co->ifp); if (OSPF_IF_PARAM_CONFIGURED(params, if_area)) area = ospf_area_get(ospf, params->if_area); } /* If we've found an interface and/or addr specific area, then we're * done */ if (area) { ospf_update_interface_area(co, area); return; } /* Otherwise, only remaining possibility is a matching network statement */ if (p) { assert(given_area != NULL); /* Which either was supplied as a parameter.. (e.g. cause a new * network/area was just added).. */ if (p->family == co->address->family && ospf_network_match_iface(co, p)) ospf_update_interface_area(co, given_area); return; } /* Else we have to search the existing network/area config to see * if any match.. */ for (rn = route_top(ospf->networks); rn; rn = route_next(rn)) if (rn->info != NULL && ospf_network_match_iface(co, &rn->p)) { struct ospf_network *network = (struct ospf_network *)rn->info; area = ospf_area_get(ospf, network->area_id); ospf_update_interface_area(co, area); configed = 1; } /* If the subnet isn't in any area, deconfigure */ if (!configed && (oi = ospf_if_table_lookup(co->ifp, co->address))) ospf_if_free(oi); } static void ospf_network_run_interface(struct ospf *ospf, struct interface *ifp, struct prefix *p, struct ospf_area *given_area) { struct listnode *cnode; struct connected *co; if (memcmp(ifp->name, "VLINK", 5) == 0) return; /* Network prefix without area is nonsensical */ if (p) assert(given_area != NULL); /* if interface prefix is match specified prefix, then create socket and join multicast group. */ for (ALL_LIST_ELEMENTS_RO(ifp->connected, cnode, co)) ospf_network_run_subnet(ospf, co, p, given_area); } static void ospf_network_run(struct prefix *p, struct ospf_area *area) { struct vrf *vrf = vrf_lookup_by_id(area->ospf->vrf_id); struct interface *ifp; /* Schedule Router ID Update. */ if (area->ospf->router_id.s_addr == 0) ospf_router_id_update(area->ospf); /* Get target interface. */ FOR_ALL_INTERFACES (vrf, ifp) ospf_network_run_interface(area->ospf, ifp, p, area); } void ospf_ls_upd_queue_empty(struct ospf_interface *oi) { struct route_node *rn; struct listnode *node, *nnode; struct list *lst; struct ospf_lsa *lsa; /* empty ls update queue */ for (rn = route_top(oi->ls_upd_queue); rn; rn = route_next(rn)) if ((lst = (struct list *)rn->info)) { for (ALL_LIST_ELEMENTS(lst, node, nnode, lsa)) ospf_lsa_unlock(&lsa); /* oi->ls_upd_queue */ list_delete(&lst); rn->info = NULL; } /* remove update event */ if (oi->t_ls_upd_event) { thread_cancel(oi->t_ls_upd_event); oi->t_ls_upd_event = NULL; } } void ospf_if_update(struct ospf *ospf, struct interface *ifp) { if (!ospf) return; if (IS_DEBUG_OSPF_EVENT) zlog_debug( "%s: interface %s ifp->vrf_id %u ospf vrf %s vrf_id %u router_id %s", __PRETTY_FUNCTION__, ifp->name, ifp->vrf_id, ospf_vrf_id_to_name(ospf->vrf_id), ospf->vrf_id, inet_ntoa(ospf->router_id)); /* OSPF must be ready. */ if (!ospf_is_ready(ospf)) return; ospf_network_run_interface(ospf, ifp, NULL, NULL); /* Update connected redistribute. */ update_redistributed(ospf, 1); } void ospf_remove_vls_through_area(struct ospf *ospf, struct ospf_area *area) { struct listnode *node, *nnode; struct ospf_vl_data *vl_data; for (ALL_LIST_ELEMENTS(ospf->vlinks, node, nnode, vl_data)) if (IPV4_ADDR_SAME(&vl_data->vl_area_id, &area->area_id)) ospf_vl_delete(ospf, vl_data); } static const struct message ospf_area_type_msg[] = { {OSPF_AREA_DEFAULT, "Default"}, {OSPF_AREA_STUB, "Stub"}, {OSPF_AREA_NSSA, "NSSA"}, {0}}; static void ospf_area_type_set(struct ospf_area *area, int type) { struct listnode *node; struct ospf_interface *oi; if (area->external_routing == type) { if (IS_DEBUG_OSPF_EVENT) zlog_debug("Area[%s]: Types are the same, ignored.", inet_ntoa(area->area_id)); return; } area->external_routing = type; if (IS_DEBUG_OSPF_EVENT) zlog_debug("Area[%s]: Configured as %s", inet_ntoa(area->area_id), lookup_msg(ospf_area_type_msg, type, NULL)); switch (area->external_routing) { case OSPF_AREA_DEFAULT: for (ALL_LIST_ELEMENTS_RO(area->oiflist, node, oi)) if (oi->nbr_self != NULL) { UNSET_FLAG(oi->nbr_self->options, OSPF_OPTION_NP); SET_FLAG(oi->nbr_self->options, OSPF_OPTION_E); } break; case OSPF_AREA_STUB: for (ALL_LIST_ELEMENTS_RO(area->oiflist, node, oi)) if (oi->nbr_self != NULL) { if (IS_DEBUG_OSPF_EVENT) zlog_debug( "setting options on %s accordingly", IF_NAME(oi)); UNSET_FLAG(oi->nbr_self->options, OSPF_OPTION_NP); UNSET_FLAG(oi->nbr_self->options, OSPF_OPTION_E); if (IS_DEBUG_OSPF_EVENT) zlog_debug("options set on %s: %x", IF_NAME(oi), OPTIONS(oi)); } break; case OSPF_AREA_NSSA: for (ALL_LIST_ELEMENTS_RO(area->oiflist, node, oi)) if (oi->nbr_self != NULL) { zlog_debug( "setting nssa options on %s accordingly", IF_NAME(oi)); UNSET_FLAG(oi->nbr_self->options, OSPF_OPTION_E); SET_FLAG(oi->nbr_self->options, OSPF_OPTION_NP); zlog_debug("options set on %s: %x", IF_NAME(oi), OPTIONS(oi)); } break; default: break; } ospf_router_lsa_update_area(area); ospf_schedule_abr_task(area->ospf); } int ospf_area_shortcut_set(struct ospf *ospf, struct ospf_area *area, int mode) { if (area->shortcut_configured == mode) return 0; area->shortcut_configured = mode; ospf_router_lsa_update_area(area); ospf_schedule_abr_task(ospf); ospf_area_check_free(ospf, area->area_id); return 1; } int ospf_area_shortcut_unset(struct ospf *ospf, struct ospf_area *area) { area->shortcut_configured = OSPF_SHORTCUT_DEFAULT; ospf_router_lsa_update_area(area); ospf_area_check_free(ospf, area->area_id); ospf_schedule_abr_task(ospf); return 1; } static int ospf_area_vlink_count(struct ospf *ospf, struct ospf_area *area) { struct ospf_vl_data *vl; struct listnode *node; int count = 0; for (ALL_LIST_ELEMENTS_RO(ospf->vlinks, node, vl)) if (IPV4_ADDR_SAME(&vl->vl_area_id, &area->area_id)) count++; return count; } int ospf_area_display_format_set(struct ospf *ospf, struct ospf_area *area, int df) { area->area_id_fmt = df; return 1; } int ospf_area_stub_set(struct ospf *ospf, struct in_addr area_id) { struct ospf_area *area; area = ospf_area_get(ospf, area_id); if (ospf_area_vlink_count(ospf, area)) return 0; if (area->external_routing != OSPF_AREA_STUB) ospf_area_type_set(area, OSPF_AREA_STUB); return 1; } int ospf_area_stub_unset(struct ospf *ospf, struct in_addr area_id) { struct ospf_area *area; area = ospf_area_lookup_by_area_id(ospf, area_id); if (area == NULL) return 1; if (area->external_routing == OSPF_AREA_STUB) ospf_area_type_set(area, OSPF_AREA_DEFAULT); ospf_area_check_free(ospf, area_id); return 1; } int ospf_area_no_summary_set(struct ospf *ospf, struct in_addr area_id) { struct ospf_area *area; area = ospf_area_get(ospf, area_id); area->no_summary = 1; return 1; } int ospf_area_no_summary_unset(struct ospf *ospf, struct in_addr area_id) { struct ospf_area *area; area = ospf_area_lookup_by_area_id(ospf, area_id); if (area == NULL) return 0; area->no_summary = 0; ospf_area_check_free(ospf, area_id); return 1; } int ospf_area_nssa_no_summary_set(struct ospf *ospf, struct in_addr area_id) { struct ospf_area *area; area = ospf_area_get(ospf, area_id); if (ospf_area_vlink_count(ospf, area)) return 0; if (area->external_routing != OSPF_AREA_NSSA) { ospf_area_type_set(area, OSPF_AREA_NSSA); ospf->anyNSSA++; area->NSSATranslatorRole = OSPF_NSSA_ROLE_CANDIDATE; } ospf_area_no_summary_set(ospf, area_id); return 1; } int ospf_area_nssa_set(struct ospf *ospf, struct in_addr area_id) { struct ospf_area *area; area = ospf_area_get(ospf, area_id); if (ospf_area_vlink_count(ospf, area)) return 0; if (area->external_routing != OSPF_AREA_NSSA) { ospf_area_type_set(area, OSPF_AREA_NSSA); ospf->anyNSSA++; /* set NSSA area defaults */ area->no_summary = 0; area->NSSATranslatorRole = OSPF_NSSA_ROLE_CANDIDATE; area->NSSATranslatorState = OSPF_NSSA_TRANSLATE_DISABLED; area->NSSATranslatorStabilityInterval = OSPF_NSSA_TRANS_STABLE_DEFAULT; } return 1; } int ospf_area_nssa_unset(struct ospf *ospf, struct in_addr area_id, int argc) { struct ospf_area *area; area = ospf_area_lookup_by_area_id(ospf, area_id); if (area == NULL) return 0; /* argc < 5 -> 'no area x nssa' */ if (argc < 5 && area->external_routing == OSPF_AREA_NSSA) { ospf->anyNSSA--; /* set NSSA area defaults */ area->no_summary = 0; area->NSSATranslatorRole = OSPF_NSSA_ROLE_CANDIDATE; area->NSSATranslatorState = OSPF_NSSA_TRANSLATE_DISABLED; area->NSSATranslatorStabilityInterval = OSPF_NSSA_TRANS_STABLE_DEFAULT; ospf_area_type_set(area, OSPF_AREA_DEFAULT); } else { area->NSSATranslatorRole = OSPF_NSSA_ROLE_CANDIDATE; } ospf_area_check_free(ospf, area_id); return 1; } int ospf_area_nssa_translator_role_set(struct ospf *ospf, struct in_addr area_id, int role) { struct ospf_area *area; area = ospf_area_lookup_by_area_id(ospf, area_id); if (area == NULL) return 0; area->NSSATranslatorRole = role; return 1; } #if 0 /* XXX: unused? Leave for symmetry? */ static int ospf_area_nssa_translator_role_unset (struct ospf *ospf, struct in_addr area_id) { struct ospf_area *area; area = ospf_area_lookup_by_area_id (ospf, area_id); if (area == NULL) return 0; area->NSSATranslatorRole = OSPF_NSSA_ROLE_CANDIDATE; ospf_area_check_free (ospf, area_id); return 1; } #endif int ospf_area_export_list_set(struct ospf *ospf, struct ospf_area *area, const char *list_name) { struct access_list *list; list = access_list_lookup(AFI_IP, list_name); EXPORT_LIST(area) = list; if (EXPORT_NAME(area)) free(EXPORT_NAME(area)); EXPORT_NAME(area) = strdup(list_name); ospf_schedule_abr_task(ospf); return 1; } int ospf_area_export_list_unset(struct ospf *ospf, struct ospf_area *area) { EXPORT_LIST(area) = 0; if (EXPORT_NAME(area)) free(EXPORT_NAME(area)); EXPORT_NAME(area) = NULL; ospf_area_check_free(ospf, area->area_id); ospf_schedule_abr_task(ospf); return 1; } int ospf_area_import_list_set(struct ospf *ospf, struct ospf_area *area, const char *name) { struct access_list *list; list = access_list_lookup(AFI_IP, name); IMPORT_LIST(area) = list; if (IMPORT_NAME(area)) free(IMPORT_NAME(area)); IMPORT_NAME(area) = strdup(name); ospf_schedule_abr_task(ospf); return 1; } int ospf_area_import_list_unset(struct ospf *ospf, struct ospf_area *area) { IMPORT_LIST(area) = 0; if (IMPORT_NAME(area)) free(IMPORT_NAME(area)); IMPORT_NAME(area) = NULL; ospf_area_check_free(ospf, area->area_id); ospf_schedule_abr_task(ospf); return 1; } int ospf_timers_refresh_set(struct ospf *ospf, int interval) { int time_left; if (ospf->lsa_refresh_interval == interval) return 1; time_left = ospf->lsa_refresh_interval - (monotime(NULL) - ospf->lsa_refresher_started); if (time_left > interval) { OSPF_TIMER_OFF(ospf->t_lsa_refresher); thread_add_timer(master, ospf_lsa_refresh_walker, ospf, interval, &ospf->t_lsa_refresher); } ospf->lsa_refresh_interval = interval; return 1; } int ospf_timers_refresh_unset(struct ospf *ospf) { int time_left; time_left = ospf->lsa_refresh_interval - (monotime(NULL) - ospf->lsa_refresher_started); if (time_left > OSPF_LSA_REFRESH_INTERVAL_DEFAULT) { OSPF_TIMER_OFF(ospf->t_lsa_refresher); ospf->t_lsa_refresher = NULL; thread_add_timer(master, ospf_lsa_refresh_walker, ospf, OSPF_LSA_REFRESH_INTERVAL_DEFAULT, &ospf->t_lsa_refresher); } ospf->lsa_refresh_interval = OSPF_LSA_REFRESH_INTERVAL_DEFAULT; return 1; } static struct ospf_nbr_nbma *ospf_nbr_nbma_new(void) { struct ospf_nbr_nbma *nbr_nbma; nbr_nbma = XCALLOC(MTYPE_OSPF_NEIGHBOR_STATIC, sizeof(struct ospf_nbr_nbma)); nbr_nbma->priority = OSPF_NEIGHBOR_PRIORITY_DEFAULT; nbr_nbma->v_poll = OSPF_POLL_INTERVAL_DEFAULT; return nbr_nbma; } static void ospf_nbr_nbma_free(struct ospf_nbr_nbma *nbr_nbma) { XFREE(MTYPE_OSPF_NEIGHBOR_STATIC, nbr_nbma); } static void ospf_nbr_nbma_delete(struct ospf *ospf, struct ospf_nbr_nbma *nbr_nbma) { struct route_node *rn; struct prefix_ipv4 p; p.family = AF_INET; p.prefix = nbr_nbma->addr; p.prefixlen = IPV4_MAX_BITLEN; rn = route_node_lookup(ospf->nbr_nbma, (struct prefix *)&p); if (rn) { ospf_nbr_nbma_free(rn->info); rn->info = NULL; route_unlock_node(rn); route_unlock_node(rn); } } static void ospf_nbr_nbma_down(struct ospf_nbr_nbma *nbr_nbma) { OSPF_TIMER_OFF(nbr_nbma->t_poll); if (nbr_nbma->nbr) { nbr_nbma->nbr->nbr_nbma = NULL; OSPF_NSM_EVENT_EXECUTE(nbr_nbma->nbr, NSM_KillNbr); } if (nbr_nbma->oi) listnode_delete(nbr_nbma->oi->nbr_nbma, nbr_nbma); } static void ospf_nbr_nbma_add(struct ospf_nbr_nbma *nbr_nbma, struct ospf_interface *oi) { struct ospf_neighbor *nbr; struct route_node *rn; struct prefix p; if (oi->type != OSPF_IFTYPE_NBMA) return; if (nbr_nbma->nbr != NULL) return; if (IPV4_ADDR_SAME(&oi->nbr_self->address.u.prefix4, &nbr_nbma->addr)) return; nbr_nbma->oi = oi; listnode_add(oi->nbr_nbma, nbr_nbma); /* Get neighbor information from table. */ p.family = AF_INET; p.prefixlen = IPV4_MAX_BITLEN; p.u.prefix4 = nbr_nbma->addr; rn = route_node_get(oi->nbrs, (struct prefix *)&p); if (rn->info) { nbr = rn->info; nbr->nbr_nbma = nbr_nbma; nbr_nbma->nbr = nbr; route_unlock_node(rn); } else { nbr = rn->info = ospf_nbr_new(oi); nbr->state = NSM_Down; nbr->src = nbr_nbma->addr; nbr->nbr_nbma = nbr_nbma; nbr->priority = nbr_nbma->priority; nbr->address = p; nbr_nbma->nbr = nbr; OSPF_NSM_EVENT_EXECUTE(nbr, NSM_Start); } } void ospf_nbr_nbma_if_update(struct ospf *ospf, struct ospf_interface *oi) { struct ospf_nbr_nbma *nbr_nbma; struct route_node *rn; struct prefix_ipv4 p; if (oi->type != OSPF_IFTYPE_NBMA) return; for (rn = route_top(ospf->nbr_nbma); rn; rn = route_next(rn)) if ((nbr_nbma = rn->info)) if (nbr_nbma->oi == NULL && nbr_nbma->nbr == NULL) { p.family = AF_INET; p.prefix = nbr_nbma->addr; p.prefixlen = IPV4_MAX_BITLEN; if (prefix_match(oi->address, (struct prefix *)&p)) ospf_nbr_nbma_add(nbr_nbma, oi); } } struct ospf_nbr_nbma *ospf_nbr_nbma_lookup(struct ospf *ospf, struct in_addr nbr_addr) { struct route_node *rn; struct prefix_ipv4 p; p.family = AF_INET; p.prefix = nbr_addr; p.prefixlen = IPV4_MAX_BITLEN; rn = route_node_lookup(ospf->nbr_nbma, (struct prefix *)&p); if (rn) { route_unlock_node(rn); return rn->info; } return NULL; } struct ospf_nbr_nbma *ospf_nbr_nbma_lookup_next(struct ospf *ospf, struct in_addr *addr, int first) { #if 0 struct ospf_nbr_nbma *nbr_nbma; struct listnode *node; #endif if (ospf == NULL) return NULL; #if 0 for (ALL_LIST_ELEMENTS_RO (ospf->nbr_nbma, node, nbr_nbma)) { if (first) { *addr = nbr_nbma->addr; return nbr_nbma; } else if (ntohl (nbr_nbma->addr.s_addr) > ntohl (addr->s_addr)) { *addr = nbr_nbma->addr; return nbr_nbma; } } #endif return NULL; } int ospf_nbr_nbma_set(struct ospf *ospf, struct in_addr nbr_addr) { struct ospf_nbr_nbma *nbr_nbma; struct ospf_interface *oi; struct prefix_ipv4 p; struct route_node *rn; struct listnode *node; nbr_nbma = ospf_nbr_nbma_lookup(ospf, nbr_addr); if (nbr_nbma) return 0; nbr_nbma = ospf_nbr_nbma_new(); nbr_nbma->addr = nbr_addr; p.family = AF_INET; p.prefix = nbr_addr; p.prefixlen = IPV4_MAX_BITLEN; rn = route_node_get(ospf->nbr_nbma, (struct prefix *)&p); if (rn->info) route_unlock_node(rn); rn->info = nbr_nbma; for (ALL_LIST_ELEMENTS_RO(ospf->oiflist, node, oi)) { if (oi->type == OSPF_IFTYPE_NBMA) if (prefix_match(oi->address, (struct prefix *)&p)) { ospf_nbr_nbma_add(nbr_nbma, oi); break; } } return 1; } int ospf_nbr_nbma_unset(struct ospf *ospf, struct in_addr nbr_addr) { struct ospf_nbr_nbma *nbr_nbma; nbr_nbma = ospf_nbr_nbma_lookup(ospf, nbr_addr); if (nbr_nbma == NULL) return 0; ospf_nbr_nbma_down(nbr_nbma); ospf_nbr_nbma_delete(ospf, nbr_nbma); return 1; } int ospf_nbr_nbma_priority_set(struct ospf *ospf, struct in_addr nbr_addr, uint8_t priority) { struct ospf_nbr_nbma *nbr_nbma; nbr_nbma = ospf_nbr_nbma_lookup(ospf, nbr_addr); if (nbr_nbma == NULL) return 0; if (nbr_nbma->priority != priority) nbr_nbma->priority = priority; return 1; } int ospf_nbr_nbma_priority_unset(struct ospf *ospf, struct in_addr nbr_addr) { struct ospf_nbr_nbma *nbr_nbma; nbr_nbma = ospf_nbr_nbma_lookup(ospf, nbr_addr); if (nbr_nbma == NULL) return 0; if (nbr_nbma != OSPF_NEIGHBOR_PRIORITY_DEFAULT) nbr_nbma->priority = OSPF_NEIGHBOR_PRIORITY_DEFAULT; return 1; } int ospf_nbr_nbma_poll_interval_set(struct ospf *ospf, struct in_addr nbr_addr, unsigned int interval) { struct ospf_nbr_nbma *nbr_nbma; nbr_nbma = ospf_nbr_nbma_lookup(ospf, nbr_addr); if (nbr_nbma == NULL) return 0; if (nbr_nbma->v_poll != interval) { nbr_nbma->v_poll = interval; if (nbr_nbma->oi && ospf_if_is_up(nbr_nbma->oi)) { OSPF_TIMER_OFF(nbr_nbma->t_poll); OSPF_POLL_TIMER_ON(nbr_nbma->t_poll, ospf_poll_timer, nbr_nbma->v_poll); } } return 1; } int ospf_nbr_nbma_poll_interval_unset(struct ospf *ospf, struct in_addr addr) { struct ospf_nbr_nbma *nbr_nbma; nbr_nbma = ospf_nbr_nbma_lookup(ospf, addr); if (nbr_nbma == NULL) return 0; if (nbr_nbma->v_poll != OSPF_POLL_INTERVAL_DEFAULT) nbr_nbma->v_poll = OSPF_POLL_INTERVAL_DEFAULT; return 1; } void ospf_master_init(struct thread_master *master) { memset(&ospf_master, 0, sizeof(struct ospf_master)); om = &ospf_master; om->ospf = list_new(); om->master = master; } /* Link OSPF instance to VRF. */ void ospf_vrf_link(struct ospf *ospf, struct vrf *vrf) { ospf->vrf_id = vrf->vrf_id; if (vrf->info != (void *)ospf) vrf->info = (void *)ospf; } /* Unlink OSPF instance from VRF. */ void ospf_vrf_unlink(struct ospf *ospf, struct vrf *vrf) { if (vrf->info == (void *)ospf) vrf->info = NULL; ospf->vrf_id = VRF_UNKNOWN; } /* This is hook function for vrf create called as part of vrf_init */ static int ospf_vrf_new(struct vrf *vrf) { if (IS_DEBUG_OSPF_EVENT) zlog_debug("%s: VRF Created: %s(%u)", __PRETTY_FUNCTION__, vrf->name, vrf->vrf_id); return 0; } /* This is hook function for vrf delete call as part of vrf_init */ static int ospf_vrf_delete(struct vrf *vrf) { if (IS_DEBUG_OSPF_EVENT) zlog_debug("%s: VRF Deletion: %s(%u)", __PRETTY_FUNCTION__, vrf->name, vrf->vrf_id); return 0; } static void ospf_set_redist_vrf_bitmaps(struct ospf *ospf) { int type; struct list *red_list; for (type = 0; type < ZEBRA_ROUTE_MAX; type++) { red_list = ospf->redist[type]; if (!red_list) continue; if (IS_DEBUG_OSPF_EVENT) zlog_debug( "%s: setting redist vrf %d bitmap for type %d", __func__, ospf->vrf_id, type); vrf_bitmap_set(zclient->redist[AFI_IP][type], ospf->vrf_id); } } /* Enable OSPF VRF instance */ static int ospf_vrf_enable(struct vrf *vrf) { struct ospf *ospf = NULL; vrf_id_t old_vrf_id; int ret = 0; if (IS_DEBUG_OSPF_EVENT) zlog_debug("%s: VRF %s id %u enabled", __PRETTY_FUNCTION__, vrf->name, vrf->vrf_id); ospf = ospf_lookup_by_name(vrf->name); if (ospf) { if (ospf->name && strmatch(vrf->name, VRF_DEFAULT_NAME)) { XFREE(MTYPE_OSPF_TOP, ospf->name); ospf->name = NULL; } old_vrf_id = ospf->vrf_id; /* We have instance configured, link to VRF and make it "up". */ ospf_vrf_link(ospf, vrf); if (IS_DEBUG_OSPF_EVENT) zlog_debug( "%s: ospf linked to vrf %s vrf_id %u (old id %u)", __PRETTY_FUNCTION__, vrf->name, ospf->vrf_id, old_vrf_id); if (old_vrf_id != ospf->vrf_id) { frr_with_privs(&ospfd_privs) { /* stop zebra redist to us for old vrf */ zclient_send_dereg_requests(zclient, old_vrf_id); ospf_set_redist_vrf_bitmaps(ospf); /* start zebra redist to us for new vrf */ ospf_zebra_vrf_register(ospf); ret = ospf_sock_init(ospf); } if (ret < 0 || ospf->fd <= 0) return 0; thread_add_read(master, ospf_read, ospf, ospf->fd, &ospf->t_read); ospf->oi_running = 1; ospf_router_id_update(ospf); } } return 0; } /* Disable OSPF VRF instance */ static int ospf_vrf_disable(struct vrf *vrf) { struct ospf *ospf = NULL; vrf_id_t old_vrf_id = VRF_UNKNOWN; if (vrf->vrf_id == VRF_DEFAULT) return 0; if (IS_DEBUG_OSPF_EVENT) zlog_debug("%s: VRF %s id %d disabled.", __PRETTY_FUNCTION__, vrf->name, vrf->vrf_id); ospf = ospf_lookup_by_name(vrf->name); if (ospf) { old_vrf_id = ospf->vrf_id; /* We have instance configured, unlink * from VRF and make it "down". */ ospf_vrf_unlink(ospf, vrf); ospf->oi_running = 0; if (IS_DEBUG_OSPF_EVENT) zlog_debug("%s: ospf old_vrf_id %d unlinked", __PRETTY_FUNCTION__, old_vrf_id); thread_cancel(ospf->t_read); close(ospf->fd); ospf->fd = -1; } /* Note: This is a callback, the VRF will be deleted by the caller. */ return 0; } void ospf_vrf_init(void) { vrf_init(ospf_vrf_new, ospf_vrf_enable, ospf_vrf_disable, ospf_vrf_delete, NULL); } void ospf_vrf_terminate(void) { vrf_terminate(); } const char *ospf_vrf_id_to_name(vrf_id_t vrf_id) { struct vrf *vrf = vrf_lookup_by_id(vrf_id); return vrf ? vrf->name : "NIL"; } frr-7.2.1/ospfd/ospfd.conf.sample0000644000000000000000000000026613610377563013575 00000000000000! -*- ospf -*- ! ! OSPFd sample configuration file ! ! hostname ospfd password zebra !enable password please-set-at-here ! !router ospf ! network 192.168.1.0/24 area 0 ! log stdout frr-7.2.1/ospfd/ospfd.h0000644000000000000000000004607213610377563011624 00000000000000/* * OSPFd main header. * Copyright (C) 1998, 99, 2000 Kunihiro Ishiguro, Toshiaki Takada * * This file is part of GNU Zebra. * * GNU Zebra is free software; you can redistribute it and/or modify it * under the terms of the GNU General Public License as published by the * Free Software Foundation; either version 2, or (at your option) any * later version. * * GNU Zebra 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; see the file COPYING; if not, write to the Free Software * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA */ #ifndef _ZEBRA_OSPFD_H #define _ZEBRA_OSPFD_H #include #include "qobj.h" #include "libospf.h" #include "filter.h" #include "log.h" #include "vrf.h" #include "ospf_memory.h" #include "ospf_dump_api.h" #define OSPF_VERSION 2 /* VTY port number. */ #define OSPF_VTY_PORT 2604 /* IP TTL for OSPF protocol. */ #define OSPF_IP_TTL 1 #define OSPF_VL_IP_TTL 100 /* Default configuration file name for ospfd. */ #define OSPF_DEFAULT_CONFIG "ospfd.conf" #define OSPF_NSSA_TRANS_STABLE_DEFAULT 40 #define OSPF_ALLSPFROUTERS 0xe0000005 /* 224.0.0.5 */ #define OSPF_ALLDROUTERS 0xe0000006 /* 224.0.0.6 */ /* OSPF Authentication Type. */ #define OSPF_AUTH_NULL 0 #define OSPF_AUTH_SIMPLE 1 #define OSPF_AUTH_CRYPTOGRAPHIC 2 /* For Interface authentication setting default */ #define OSPF_AUTH_NOTSET -1 /* For the consumption and sanity of the command handler */ /* DO NIOT REMOVE!!! Need to detect whether a value has been given or not in VLink command handlers */ #define OSPF_AUTH_CMD_NOTSEEN -2 /* OSPF options. */ #define OSPF_OPTION_MT 0x01 /* M/T */ #define OSPF_OPTION_E 0x02 #define OSPF_OPTION_MC 0x04 #define OSPF_OPTION_NP 0x08 #define OSPF_OPTION_EA 0x10 #define OSPF_OPTION_DC 0x20 #define OSPF_OPTION_O 0x40 #define OSPF_OPTION_DN 0x80 /* OSPF Database Description flags. */ #define OSPF_DD_FLAG_MS 0x01 #define OSPF_DD_FLAG_M 0x02 #define OSPF_DD_FLAG_I 0x04 #define OSPF_DD_FLAG_ALL 0x07 #define OSPF_LS_REFRESH_SHIFT (60 * 15) #define OSPF_LS_REFRESH_JITTER 60 struct ospf_external { unsigned short instance; struct route_table *external_info; }; /* OSPF master for system wide configuration and variables. */ struct ospf_master { /* OSPF instance. */ struct list *ospf; /* OSPF thread master. */ struct thread_master *master; /* Various OSPF global configuration. */ uint8_t options; #define OSPF_MASTER_SHUTDOWN (1 << 0) /* deferred-shutdown */ }; struct ospf_redist { unsigned short instance; /* Redistribute metric info. */ struct { int type; /* External metric type (E1 or E2). */ int value; /* Value for static metric (24-bit). -1 means metric value is not set. */ } dmetric; /* For redistribute route map. */ struct { char *name; struct route_map *map; } route_map; /* +1 is for default-information */ #define ROUTEMAP_NAME(R) (R->route_map.name) #define ROUTEMAP(R) (R->route_map.map) }; /* OSPF instance structure. */ struct ospf { /* OSPF's running state based on the '[no] router ospf []' * config. */ uint8_t oi_running; /* OSPF instance ID */ unsigned short instance; /* OSPF Router ID. */ struct in_addr router_id; /* Configured automatically. */ struct in_addr router_id_static; /* Configured manually. */ struct in_addr router_id_zebra; vrf_id_t vrf_id; /* VRF Id */ char *name; /* VRF name */ /* ABR/ASBR internal flags. */ uint8_t flags; #define OSPF_FLAG_ABR 0x0001 #define OSPF_FLAG_ASBR 0x0002 /* ABR type. */ uint8_t abr_type; #define OSPF_ABR_UNKNOWN 0 #define OSPF_ABR_STAND 1 #define OSPF_ABR_IBM 2 #define OSPF_ABR_CISCO 3 #define OSPF_ABR_SHORTCUT 4 #define OSPF_ABR_DEFAULT OSPF_ABR_CISCO /* NSSA ABR */ uint8_t anyNSSA; /* Bump for every NSSA attached. */ /* Configured variables. */ uint8_t config; #define OSPF_RFC1583_COMPATIBLE (1 << 0) #define OSPF_OPAQUE_CAPABLE (1 << 2) #define OSPF_LOG_ADJACENCY_CHANGES (1 << 3) #define OSPF_LOG_ADJACENCY_DETAIL (1 << 4) /* Opaque-LSA administrative flags. */ uint8_t opaque; #define OPAQUE_OPERATION_READY_BIT (1 << 0) /* RFC3137 stub router. Configured time to stay stub / max-metric */ unsigned int stub_router_startup_time; /* seconds */ unsigned int stub_router_shutdown_time; /* seconds */ #define OSPF_STUB_ROUTER_UNCONFIGURED 0 uint8_t stub_router_admin_set; #define OSPF_STUB_ROUTER_ADMINISTRATIVE_SET 1 #define OSPF_STUB_ROUTER_ADMINISTRATIVE_UNSET 0 #define OSPF_STUB_MAX_METRIC_SUMMARY_COST 0x00ff0000 /* LSA timers */ unsigned int min_ls_interval; /* minimum delay between LSAs (in msec) */ unsigned int min_ls_arrival; /* minimum interarrival time between LSAs (in msec) */ /* SPF parameters */ unsigned int spf_delay; /* SPF delay time. */ unsigned int spf_holdtime; /* SPF hold time. */ unsigned int spf_max_holdtime; /* SPF maximum-holdtime */ unsigned int spf_hold_multiplier; /* Adaptive multiplier for hold time */ int default_originate; /* Default information originate. */ #define DEFAULT_ORIGINATE_NONE 0 #define DEFAULT_ORIGINATE_ZEBRA 1 #define DEFAULT_ORIGINATE_ALWAYS 2 uint32_t ref_bandwidth; /* Reference Bandwidth (Kbps). */ struct route_table *networks; /* OSPF config networks. */ struct list *vlinks; /* Configured Virtual-Links. */ struct list *areas; /* OSPF areas. */ struct route_table *nbr_nbma; struct ospf_area *backbone; /* Pointer to the Backbone Area. */ struct list *oiflist; /* ospf interfaces */ uint8_t passive_interface_default; /* passive-interface default */ /* LSDB of AS-external-LSAs. */ struct ospf_lsdb *lsdb; /* Flags. */ int ase_calc; /* ASE calculation flag. */ struct list *opaque_lsa_self; /* Type-11 Opaque-LSAs */ /* Routing tables. */ struct route_table *old_table; /* Old routing table. */ struct route_table *new_table; /* Current routing table. */ struct route_table *old_rtrs; /* Old ABR/ASBR RT. */ struct route_table *new_rtrs; /* New ABR/ASBR RT. */ struct route_table *new_external_route; /* New External Route. */ struct route_table *old_external_route; /* Old External Route. */ struct route_table *external_lsas; /* Database of external LSAs, prefix is LSA's adv. network*/ /* Time stamps */ struct timeval ts_spf; /* SPF calculation time stamp. */ struct timeval ts_spf_duration; /* Execution time of last SPF */ struct route_table *maxage_lsa; /* List of MaxAge LSA for deletion. */ int redistribute; /* Num of redistributed protocols. */ /* Threads. */ struct thread *t_abr_task; /* ABR task timer. */ struct thread *t_asbr_check; /* ASBR check timer. */ struct thread *t_distribute_update; /* Distirbute list update timer. */ struct thread *t_spf_calc; /* SPF calculation timer. */ struct thread *t_ase_calc; /* ASE calculation timer. */ struct thread *t_opaque_lsa_self; /* Type-11 Opaque-LSAs origin event. */ struct thread *t_sr_update; /* Segment Routing update timer */ unsigned int maxage_delay; /* Delay on Maxage remover timer, sec */ struct thread *t_maxage; /* MaxAge LSA remover timer. */ struct thread *t_maxage_walker; /* MaxAge LSA checking timer. */ struct thread *t_deferred_shutdown; /* deferred/stub-router shutdown timer*/ struct thread *t_write; #define OSPF_WRITE_INTERFACE_COUNT_DEFAULT 20 int write_oi_count; /* Num of packets sent per thread invocation */ struct thread *t_read; int fd; struct stream *ibuf; struct list *oi_write_q; /* Distribute lists out of other route sources. */ struct { char *name; struct access_list *list; } dlist[ZEBRA_ROUTE_MAX]; #define DISTRIBUTE_NAME(O,T) (O)->dlist[T].name #define DISTRIBUTE_LIST(O,T) (O)->dlist[T].list /* OSPF redistribute configuration */ struct list *redist[ZEBRA_ROUTE_MAX + 1]; /* Redistribute tag info. */ route_tag_t dtag[ZEBRA_ROUTE_MAX + 1]; // Pending: cant configure as of now int default_metric; /* Default metric for redistribute. */ #define OSPF_LSA_REFRESHER_GRANULARITY 10 #define OSPF_LSA_REFRESHER_SLOTS \ ((OSPF_LS_REFRESH_TIME + OSPF_LS_REFRESH_SHIFT) \ / OSPF_LSA_REFRESHER_GRANULARITY \ + 1) struct { uint16_t index; struct list *qs[OSPF_LSA_REFRESHER_SLOTS]; } lsa_refresh_queue; struct thread *t_lsa_refresher; time_t lsa_refresher_started; #define OSPF_LSA_REFRESH_INTERVAL_DEFAULT 10 uint16_t lsa_refresh_interval; /* Distance parameter. */ uint8_t distance_all; uint8_t distance_intra; uint8_t distance_inter; uint8_t distance_external; /* Statistics for LSA origination. */ uint32_t lsa_originate_count; /* Statistics for LSA used for new instantiation. */ uint32_t rx_lsa_count; /* Counter of "ip ospf area x.x.x.x" used * for multual exclusion of network command under * router ospf or ip ospf area x under interface. */ uint32_t if_ospf_cli_count; struct route_table *distance_table; /* Used during ospf instance going down send LSDB * update to neighbors immediatly */ uint8_t inst_shutdown; /* Redistributed external information. */ struct list *external[ZEBRA_ROUTE_MAX + 1]; #define EXTERNAL_INFO(E) (E->external_info) QOBJ_FIELDS }; DECLARE_QOBJ_TYPE(ospf) /* OSPF area structure. */ struct ospf_area { /* OSPF instance. */ struct ospf *ospf; /* Zebra interface list belonging to the area. */ struct list *oiflist; /* Area ID. */ struct in_addr area_id; /* Area ID format. */ int area_id_fmt; #define OSPF_AREA_ID_FMT_DOTTEDQUAD 1 #define OSPF_AREA_ID_FMT_DECIMAL 2 /* Address range. */ struct list *address_range; /* Configured variables. */ int external_routing; /* ExternalRoutingCapability. */ int no_summary; /* Don't inject summaries into stub.*/ int shortcut_configured; /* Area configured as shortcut. */ #define OSPF_SHORTCUT_DEFAULT 0 #define OSPF_SHORTCUT_ENABLE 1 #define OSPF_SHORTCUT_DISABLE 2 int shortcut_capability; /* Other ABRs agree on S-bit */ uint32_t default_cost; /* StubDefaultCost. */ int auth_type; /* Authentication type. */ uint8_t NSSATranslatorRole; /* NSSA configured role */ #define OSPF_NSSA_ROLE_NEVER 0 #define OSPF_NSSA_ROLE_CANDIDATE 1 #define OSPF_NSSA_ROLE_ALWAYS 2 uint8_t NSSATranslatorState; /* NSSA operational role */ #define OSPF_NSSA_TRANSLATE_DISABLED 0 #define OSPF_NSSA_TRANSLATE_ENABLED 1 int NSSATranslatorStabilityInterval; uint8_t transit; /* TransitCapability. */ #define OSPF_TRANSIT_FALSE 0 #define OSPF_TRANSIT_TRUE 1 struct route_table *ranges; /* Configured Area Ranges. */ /* RFC3137 stub router state flags for area */ uint8_t stub_router_state; #define OSPF_AREA_ADMIN_STUB_ROUTED (1 << 0) /* admin stub-router set */ #define OSPF_AREA_IS_STUB_ROUTED (1 << 1) /* stub-router active */ #define OSPF_AREA_WAS_START_STUB_ROUTED (1 << 2) /* startup SR was done */ /* Area related LSDBs[Type1-4]. */ struct ospf_lsdb *lsdb; /* Self-originated LSAs. */ struct ospf_lsa *router_lsa_self; struct list *opaque_lsa_self; /* Type-10 Opaque-LSAs */ /* Area announce list. */ struct { char *name; struct access_list *list; } _export; #define EXPORT_NAME(A) (A)->_export.name #define EXPORT_LIST(A) (A)->_export.list /* Area acceptance list. */ struct { char *name; struct access_list *list; } import; #define IMPORT_NAME(A) (A)->import.name #define IMPORT_LIST(A) (A)->import.list /* Type 3 LSA Area prefix-list. */ struct { char *name; struct prefix_list *list; } plist_in; #define PREFIX_LIST_IN(A) (A)->plist_in.list #define PREFIX_NAME_IN(A) (A)->plist_in.name struct { char *name; struct prefix_list *list; } plist_out; #define PREFIX_LIST_OUT(A) (A)->plist_out.list #define PREFIX_NAME_OUT(A) (A)->plist_out.name /* Shortest Path Tree. */ struct vertex *spf; /* Threads. */ struct thread *t_stub_router; /* Stub-router timer */ struct thread *t_opaque_lsa_self; /* Type-10 Opaque-LSAs origin. */ /* Statistics field. */ uint32_t spf_calculation; /* SPF Calculation Count. */ /* Time stamps. */ struct timeval ts_spf; /* SPF calculation time stamp. */ /* Router count. */ uint32_t abr_count; /* ABR router in this area. */ uint32_t asbr_count; /* ASBR router in this area. */ /* Counters. */ uint32_t act_ints; /* Active interfaces. */ uint32_t full_nbrs; /* Fully adjacent neighbors. */ uint32_t full_vls; /* Fully adjacent virtual neighbors. */ }; /* OSPF config network structure. */ struct ospf_network { /* Area ID. */ struct in_addr area_id; int area_id_fmt; }; /* OSPF NBMA neighbor structure. */ struct ospf_nbr_nbma { /* Neighbor IP address. */ struct in_addr addr; /* OSPF interface. */ struct ospf_interface *oi; /* OSPF neighbor structure. */ struct ospf_neighbor *nbr; /* Neighbor priority. */ uint8_t priority; /* Poll timer value. */ uint32_t v_poll; /* Poll timer thread. */ struct thread *t_poll; /* State change. */ uint32_t state_change; }; /* Macro. */ #define OSPF_AREA_SAME(X, Y) \ (memcmp((X->area_id), (Y->area_id), IPV4_MAX_BYTELEN) == 0) #define IS_OSPF_ABR(O) ((O)->flags & OSPF_FLAG_ABR) #define IS_OSPF_ASBR(O) ((O)->flags & OSPF_FLAG_ASBR) #define OSPF_IS_AREA_ID_BACKBONE(I) ((I).s_addr == OSPF_AREA_BACKBONE) #define OSPF_IS_AREA_BACKBONE(A) OSPF_IS_AREA_ID_BACKBONE ((A)->area_id) #ifdef roundup # define ROUNDUP(val, gran) roundup(val, gran) #else /* roundup */ # define ROUNDUP(val, gran) (((val) - 1 | (gran) - 1) + 1) #endif /* roundup */ #define LSA_OPTIONS_GET(area) \ (((area)->external_routing == OSPF_AREA_DEFAULT) ? OSPF_OPTION_E : 0) #define LSA_OPTIONS_NSSA_GET(area) \ (((area)->external_routing == OSPF_AREA_NSSA) ? OSPF_OPTION_NP : 0) #define OSPF_TIMER_ON(T,F,V) thread_add_timer (master,(F),ospf,(V),&(T)) #define OSPF_AREA_TIMER_ON(T,F,V) thread_add_timer (master, (F), area, (V), &(T)) #define OSPF_POLL_TIMER_ON(T,F,V) thread_add_timer (master, (F), nbr_nbma, (V), &(T)) #define OSPF_POLL_TIMER_OFF(X) OSPF_TIMER_OFF((X)) #define OSPF_TIMER_OFF(X) \ do { \ if (X) { \ thread_cancel(X); \ (X) = NULL; \ } \ } while (0) /* Extern variables. */ extern struct ospf_master *om; extern const int ospf_redistributed_proto_max; extern struct zclient *zclient; extern struct thread_master *master; extern int ospf_zlog; extern struct zebra_privs_t ospfd_privs; /* Prototypes. */ extern const char *ospf_redist_string(unsigned int route_type); extern struct ospf *ospf_lookup_instance(unsigned short); extern struct ospf *ospf_get(unsigned short instance, const char *name); extern struct ospf *ospf_get_instance(unsigned short); extern struct ospf *ospf_lookup_by_inst_name(unsigned short instance, const char *name); extern struct ospf *ospf_lookup_by_vrf_id(vrf_id_t vrf_id); extern void ospf_finish(struct ospf *); extern void ospf_router_id_update(struct ospf *ospf); extern int ospf_network_set(struct ospf *, struct prefix_ipv4 *, struct in_addr, int); extern int ospf_network_unset(struct ospf *, struct prefix_ipv4 *, struct in_addr); extern int ospf_area_display_format_set(struct ospf *, struct ospf_area *area, int df); extern int ospf_area_stub_set(struct ospf *, struct in_addr); extern int ospf_area_stub_unset(struct ospf *, struct in_addr); extern int ospf_area_no_summary_set(struct ospf *, struct in_addr); extern int ospf_area_no_summary_unset(struct ospf *, struct in_addr); extern int ospf_area_nssa_set(struct ospf *, struct in_addr); extern int ospf_area_nssa_unset(struct ospf *, struct in_addr, int); extern int ospf_area_nssa_translator_role_set(struct ospf *, struct in_addr, int); extern int ospf_area_export_list_set(struct ospf *, struct ospf_area *, const char *); extern int ospf_area_export_list_unset(struct ospf *, struct ospf_area *); extern int ospf_area_import_list_set(struct ospf *, struct ospf_area *, const char *); extern int ospf_area_import_list_unset(struct ospf *, struct ospf_area *); extern int ospf_area_shortcut_set(struct ospf *, struct ospf_area *, int); extern int ospf_area_shortcut_unset(struct ospf *, struct ospf_area *); extern int ospf_timers_refresh_set(struct ospf *, int); extern int ospf_timers_refresh_unset(struct ospf *); extern int ospf_nbr_nbma_set(struct ospf *, struct in_addr); extern int ospf_nbr_nbma_unset(struct ospf *, struct in_addr); extern int ospf_nbr_nbma_priority_set(struct ospf *, struct in_addr, uint8_t); extern int ospf_nbr_nbma_priority_unset(struct ospf *, struct in_addr); extern int ospf_nbr_nbma_poll_interval_set(struct ospf *, struct in_addr, unsigned int); extern int ospf_nbr_nbma_poll_interval_unset(struct ospf *, struct in_addr); extern void ospf_prefix_list_update(struct prefix_list *); extern void ospf_init(void); extern void ospf_if_update(struct ospf *, struct interface *); extern void ospf_ls_upd_queue_empty(struct ospf_interface *); extern void ospf_terminate(void); extern void ospf_nbr_nbma_if_update(struct ospf *, struct ospf_interface *); extern struct ospf_nbr_nbma *ospf_nbr_nbma_lookup(struct ospf *, struct in_addr); extern struct ospf_nbr_nbma *ospf_nbr_nbma_lookup_next(struct ospf *, struct in_addr *, int); extern int ospf_oi_count(struct interface *); extern struct ospf_area *ospf_area_get(struct ospf *, struct in_addr); extern void ospf_area_check_free(struct ospf *, struct in_addr); extern struct ospf_area *ospf_area_lookup_by_area_id(struct ospf *, struct in_addr); extern void ospf_area_add_if(struct ospf_area *, struct ospf_interface *); extern void ospf_area_del_if(struct ospf_area *, struct ospf_interface *); extern void ospf_interface_area_set(struct ospf *, struct interface *); extern void ospf_interface_area_unset(struct ospf *, struct interface *); extern bool ospf_interface_area_is_already_set(struct ospf *ospf, struct interface *ifp); extern void ospf_route_map_init(void); extern void ospf_master_init(struct thread_master *master); extern void ospf_vrf_init(void); extern void ospf_vrf_terminate(void); extern void ospf_vrf_link(struct ospf *ospf, struct vrf *vrf); extern void ospf_vrf_unlink(struct ospf *ospf, struct vrf *vrf); const char *ospf_vrf_id_to_name(vrf_id_t vrf_id); int ospf_area_nssa_no_summary_set(struct ospf *, struct in_addr); #endif /* _ZEBRA_OSPFD_H */ frr-7.2.1/ospfd/subdir.am0000644000000000000000000000466513610377563012151 00000000000000# # ospfd # if OSPFD noinst_LIBRARIES += ospfd/libfrrospf.a sbin_PROGRAMS += ospfd/ospfd dist_examples_DATA += ospfd/ospfd.conf.sample vtysh_scan += \ $(top_srcdir)/ospfd/ospf_bfd.c \ $(top_srcdir)/ospfd/ospf_dump.c \ $(top_srcdir)/ospfd/ospf_opaque.c \ $(top_srcdir)/ospfd/ospf_ri.c \ $(top_srcdir)/ospfd/ospf_routemap.c \ $(top_srcdir)/ospfd/ospf_te.c \ $(top_srcdir)/ospfd/ospf_sr.c \ $(top_srcdir)/ospfd/ospf_vty.c \ # end if SNMP module_LTLIBRARIES += ospfd/ospfd_snmp.la endif man8 += $(MANBUILD)/frr-ospfd.8 endif ospfd_libfrrospf_a_SOURCES = \ ospfd/ospf_abr.c \ ospfd/ospf_api.c \ ospfd/ospf_apiserver.c \ ospfd/ospf_asbr.c \ ospfd/ospf_ase.c \ ospfd/ospf_bfd.c \ ospfd/ospf_dump.c \ ospfd/ospf_dump_api.c \ ospfd/ospf_errors.c \ ospfd/ospf_ext.c \ ospfd/ospf_flood.c \ ospfd/ospf_ia.c \ ospfd/ospf_interface.c \ ospfd/ospf_ism.c \ ospfd/ospf_lsa.c \ ospfd/ospf_lsdb.c \ ospfd/ospf_memory.c \ ospfd/ospf_neighbor.c \ ospfd/ospf_network.c \ ospfd/ospf_nsm.c \ ospfd/ospf_opaque.c \ ospfd/ospf_packet.c \ ospfd/ospf_ri.c \ ospfd/ospf_route.c \ ospfd/ospf_routemap.c \ ospfd/ospf_spf.c \ ospfd/ospf_sr.c \ ospfd/ospf_te.c \ ospfd/ospf_vty.c \ ospfd/ospf_zebra.c \ ospfd/ospfd.c \ # end if OSPFD ospfdheaderdir = $(pkgincludedir)/ospfd ospfdheader_HEADERS = \ ospfd/ospf_api.h \ ospfd/ospf_asbr.h \ ospfd/ospf_dump.h \ ospfd/ospf_dump_api.h \ ospfd/ospf_ism.h \ ospfd/ospf_lsa.h \ ospfd/ospf_lsdb.h \ ospfd/ospf_nsm.h \ ospfd/ospf_opaque.h \ ospfd/ospfd.h \ # end endif ospfd/ospf_vty_clippy.c: $(CLIPPY_DEPS) ospfd/ospf_vty.$(OBJEXT): ospfd/ospf_vty_clippy.c noinst_HEADERS += \ ospfd/ospf_abr.h \ ospfd/ospf_apiserver.h \ ospfd/ospf_ase.h \ ospfd/ospf_bfd.h \ ospfd/ospf_errors.h \ ospfd/ospf_ext.h \ ospfd/ospf_flood.h \ ospfd/ospf_ia.h \ ospfd/ospf_interface.h \ ospfd/ospf_memory.h \ ospfd/ospf_neighbor.h \ ospfd/ospf_network.h \ ospfd/ospf_packet.h \ ospfd/ospf_ri.h \ ospfd/ospf_route.h \ ospfd/ospf_spf.h \ ospfd/ospf_sr.h \ ospfd/ospf_te.h \ ospfd/ospf_vty.h \ ospfd/ospf_zebra.h \ # end ospfd_ospfd_LDADD = ospfd/libfrrospf.a lib/libfrr.la $(LIBCAP) $(LIBM) ospfd_ospfd_SOURCES = ospfd/ospf_main.c ospfd_ospfd_snmp_la_SOURCES = ospfd/ospf_snmp.c ospfd_ospfd_snmp_la_CFLAGS = $(WERROR) $(SNMP_CFLAGS) -std=gnu99 ospfd_ospfd_snmp_la_LDFLAGS = -avoid-version -module -shared -export-dynamic ospfd_ospfd_snmp_la_LIBADD = lib/libfrrsnmp.la EXTRA_DIST += \ ospfd/ChangeLog.opaque.txt \ # end frr-7.2.1/pbrd/0000755000000000000000000000000013610377563010223 500000000000000frr-7.2.1/pbrd/Makefile0000644000000000000000000000021713610377563011603 00000000000000all: ALWAYS @$(MAKE) -s -C .. pbrd/pbrd %: ALWAYS @$(MAKE) -s -C .. pbrd/$@ Makefile: #nothing ALWAYS: .PHONY: ALWAYS makefiles .SUFFIXES: frr-7.2.1/pbrd/pbr_debug.c0000644000000000000000000000440613610377563012244 00000000000000/* * PBR - debugging * Copyright (C) 2018 Cumulus Networks, Inc. * Quentin Young * * This program is free software; you can redistribute it and/or modify it * under the terms of the 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; see the file COPYING; if not, write to the Free Software * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA */ #include #include "debug.h" #include "command.h" #include "vector.h" #ifndef VTYSH_EXTRACT_PL #include "pbrd/pbr_debug_clippy.c" #endif #include "pbrd/pbr_debug.h" struct debug pbr_dbg_map = {0, "PBR map"}; struct debug pbr_dbg_zebra = {0, "PBR Zebra communications"}; struct debug pbr_dbg_nht = {0, "PBR nexthop tracking"}; struct debug pbr_dbg_event = {0, "PBR events"}; struct debug *pbr_debugs[] = {&pbr_dbg_map, &pbr_dbg_zebra, &pbr_dbg_nht, &pbr_dbg_event}; const char *pbr_debugs_conflines[] = { "debug pbr map", "debug pbr zebra", "debug pbr nht", "debug pbr events", }; void pbr_debug_set_all(uint32_t flags, bool set) { for (unsigned int i = 0; i < array_size(pbr_debugs); i++) { DEBUG_FLAGS_SET(pbr_debugs[i], flags, set); /* if all modes have been turned off, don't preserve options */ if (!DEBUG_MODE_CHECK(pbr_debugs[i], DEBUG_MODE_ALL)) DEBUG_CLEAR(pbr_debugs[i]); } } int pbr_debug_config_write_helper(struct vty *vty, bool config) { uint32_t mode = DEBUG_MODE_ALL; if (config) mode = DEBUG_MODE_CONF; for (unsigned int i = 0; i < array_size(pbr_debugs); i++) if (DEBUG_MODE_CHECK(pbr_debugs[i], mode)) vty_out(vty, "%s\n", pbr_debugs_conflines[i]); return 0; } int pbr_debug_config_write(struct vty *vty) { return pbr_debug_config_write_helper(vty, true); } struct debug_callbacks pbr_dbg_cbs = {.debug_set_all = pbr_debug_set_all}; void pbr_debug_init(void) { debug_init(&pbr_dbg_cbs); } frr-7.2.1/pbrd/pbr_debug.h0000644000000000000000000000345313610377563012252 00000000000000/* * PBR - debugging * Copyright (C) 2018 Cumulus Networks, Inc. * Quentin Young * * This program is free software; you can redistribute it and/or modify it * under the terms of the 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; see the file COPYING; if not, write to the Free Software * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA */ #ifndef __PBR_DEBUG_H__ #define __PBR_DEBUG_H__ #include #include "debug.h" /* PBR debugging records */ extern struct debug pbr_dbg_map; extern struct debug pbr_dbg_zebra; extern struct debug pbr_dbg_nht; extern struct debug pbr_dbg_event; /* * Initialize PBR debugging. * * Installs VTY commands and registers callbacks. */ void pbr_debug_init(void); /* * Set or unset flags on all debugs for pbrd. * * flags * The flags to set * * set * Whether to set or unset the specified flags */ void pbr_debug_set_all(uint32_t flags, bool set); /* * Config write helper. * * vty * Vty to write to * * config * Whether we are writing to show run or saving config file * * Returns: * 0 for convenience */ int pbr_debug_config_write_helper(struct vty *vty, bool config); /* * Print PBR debugging configuration. * * vty * VTY to print debugging configuration to. */ int pbr_debug_config_write(struct vty *vty); #endif /* __PBR_DEBUG_H__ */ frr-7.2.1/pbrd/pbr_main.c0000644000000000000000000000671213610377563012104 00000000000000/* * PBR - main code * Copyright (C) 2018 Cumulus Networks, Inc. * Donald Sharp * * FRR is free software; you can redistribute it and/or modify it * under the terms of the GNU General Public License as published by the * Free Software Foundation; either version 2, or (at your option) any * later version. * * FRR 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; see the file COPYING; if not, write to the Free Software * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA */ #include #include #include "getopt.h" #include "thread.h" #include "prefix.h" #include "linklist.h" #include "if.h" #include "vector.h" #include "vty.h" #include "command.h" #include "filter.h" #include "plist.h" #include "stream.h" #include "log.h" #include "memory.h" #include "privs.h" #include "sigevent.h" #include "zclient.h" #include "keychain.h" #include "distribute.h" #include "libfrr.h" #include "routemap.h" #include "nexthop.h" #include "nexthop_group.h" #include "pbr_nht.h" #include "pbr_map.h" #include "pbr_zebra.h" #include "pbr_vty.h" #include "pbr_debug.h" zebra_capabilities_t _caps_p[] = { ZCAP_NET_RAW, ZCAP_BIND, ZCAP_NET_ADMIN, }; struct zebra_privs_t pbr_privs = { #if defined(FRR_USER) && defined(FRR_GROUP) .user = FRR_USER, .group = FRR_GROUP, #endif #if defined(VTY_GROUP) .vty_group = VTY_GROUP, #endif .caps_p = _caps_p, .cap_num_p = array_size(_caps_p), .cap_num_i = 0}; struct option longopts[] = { { 0 } }; /* Master of threads. */ struct thread_master *master; /* SIGHUP handler. */ static void sighup(void) { zlog_info("SIGHUP received"); } /* SIGINT / SIGTERM handler. */ static void sigint(void) { zlog_notice("Terminating on signal"); exit(0); } /* SIGUSR1 handler. */ static void sigusr1(void) { zlog_rotate(); } struct quagga_signal_t pbr_signals[] = { { .signal = SIGHUP, .handler = &sighup, }, { .signal = SIGUSR1, .handler = &sigusr1, }, { .signal = SIGINT, .handler = &sigint, }, { .signal = SIGTERM, .handler = &sigint, }, }; #define PBR_VTY_PORT 2615 static const struct frr_yang_module_info *pbrd_yang_modules[] = { &frr_interface_info, }; FRR_DAEMON_INFO(pbrd, PBR, .vty_port = PBR_VTY_PORT, .proghelp = "Implementation of PBR.", .signals = pbr_signals, .n_signals = array_size(pbr_signals), .privs = &pbr_privs, .yang_modules = pbrd_yang_modules, .n_yang_modules = array_size(pbrd_yang_modules), ) int main(int argc, char **argv, char **envp) { frr_preinit(&pbrd_di, argc, argv); frr_opt_add("", longopts, ""); while (1) { int opt; opt = frr_getopt(argc, argv, NULL); if (opt == EOF) break; switch (opt) { case 0: break; default: frr_help_exit(1); break; } } master = frr_init(); pbr_debug_init(); vrf_init(NULL, NULL, NULL, NULL, NULL); nexthop_group_init(pbr_nhgroup_add_cb, pbr_nhgroup_add_nexthop_cb, pbr_nhgroup_del_nexthop_cb, pbr_nhgroup_delete_cb); /* * So we safely ignore these commands since * we are getting them at this point in time */ access_list_init(); pbr_nht_init(); pbr_map_init(); pbr_zebra_init(); pbr_vty_init(); frr_config_fork(); frr_run(master); /* Not reached. */ return 0; } frr-7.2.1/pbrd/pbr_map.c0000644000000000000000000003413413610377563011734 00000000000000/* * PBR-map Code * Copyright (C) 2018 Cumulus Networks, Inc. * Donald Sharp * * FRR is free software; you can redistribute it and/or modify it * under the terms of the GNU General Public License as published by the * Free Software Foundation; either version 2, or (at your option) any * later version. * * FRR 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; see the file COPYING; if not, write to the Free Software * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA */ #include #include "thread.h" #include "linklist.h" #include "prefix.h" #include "table.h" #include "vrf.h" #include "nexthop.h" #include "nexthop_group.h" #include "memory.h" #include "log.h" #include "vty.h" #include "pbr_nht.h" #include "pbr_map.h" #include "pbr_zebra.h" #include "pbr_memory.h" #include "pbr_debug.h" DEFINE_MTYPE_STATIC(PBRD, PBR_MAP, "PBR Map") DEFINE_MTYPE_STATIC(PBRD, PBR_MAP_SEQNO, "PBR Map Sequence") DEFINE_MTYPE_STATIC(PBRD, PBR_MAP_INTERFACE, "PBR Map Interface") static uint32_t pbr_map_sequence_unique; static inline int pbr_map_compare(const struct pbr_map *pbrmap1, const struct pbr_map *pbrmap2); RB_GENERATE(pbr_map_entry_head, pbr_map, pbr_map_entry, pbr_map_compare) struct pbr_map_entry_head pbr_maps = RB_INITIALIZER(&pbr_maps); DEFINE_QOBJ_TYPE(pbr_map_sequence) static inline int pbr_map_compare(const struct pbr_map *pbrmap1, const struct pbr_map *pbrmap2) { return strcmp(pbrmap1->name, pbrmap2->name); } static int pbr_map_sequence_compare(const struct pbr_map_sequence *pbrms1, const struct pbr_map_sequence *pbrms2) { if (pbrms1->seqno == pbrms2->seqno) return 0; if (pbrms1->seqno < pbrms2->seqno) return -1; return 1; } static void pbr_map_sequence_delete(struct pbr_map_sequence *pbrms) { XFREE(MTYPE_TMP, pbrms->internal_nhg_name); XFREE(MTYPE_PBR_MAP_SEQNO, pbrms); } static int pbr_map_interface_compare(const struct pbr_map_interface *pmi1, const struct pbr_map_interface *pmi2) { return strcmp(pmi1->ifp->name, pmi2->ifp->name); } static void pbr_map_interface_list_delete(struct pbr_map_interface *pmi) { struct pbr_map_interface *pmi_int; struct listnode *node, *nnode; struct pbr_map *pbrm; RB_FOREACH (pbrm, pbr_map_entry_head, &pbr_maps) { for (ALL_LIST_ELEMENTS(pbrm->incoming, node, nnode, pmi_int)) { if (pmi == pmi_int) { pbr_map_policy_delete(pbrm, pmi); return; } } } } static const char *pbr_map_reason_str[] = { "Invalid NH-group", "Invalid NH", "No Nexthops", "Both NH and NH-Group", "Invalid Src or Dst", "Deleting Sequence", }; void pbr_map_reason_string(unsigned int reason, char *buf, int size) { unsigned int bit; int len = 0; if (!buf) return; for (bit = 0; bit < array_size(pbr_map_reason_str); bit++) { if ((reason & (1 << bit)) && (len < size)) { len += snprintf((buf + len), (size - len), "%s%s", (len > 0) ? ", " : "", pbr_map_reason_str[bit]); } } } void pbr_map_final_interface_deletion(struct pbr_map *pbrm, struct pbr_map_interface *pmi) { if (pmi->delete == true) { listnode_delete(pbrm->incoming, pmi); pmi->pbrm = NULL; bf_release_index(pbrm->ifi_bitfield, pmi->install_bit); XFREE(MTYPE_PBR_MAP_INTERFACE, pmi); } } void pbr_map_interface_delete(struct pbr_map *pbrm, struct interface *ifp_del) { struct listnode *node; struct pbr_map_interface *pmi; for (ALL_LIST_ELEMENTS_RO(pbrm->incoming, node, pmi)) { if (ifp_del == pmi->ifp) break; } if (pmi) pbr_map_policy_delete(pbrm, pmi); } void pbr_map_add_interface(struct pbr_map *pbrm, struct interface *ifp_add) { struct listnode *node; struct pbr_map_interface *pmi; for (ALL_LIST_ELEMENTS_RO(pbrm->incoming, node, pmi)) { if (ifp_add == pmi->ifp) return; } pmi = XCALLOC(MTYPE_PBR_MAP_INTERFACE, sizeof(*pmi)); pmi->ifp = ifp_add; pmi->pbrm = pbrm; listnode_add_sort(pbrm->incoming, pmi); bf_assign_index(pbrm->ifi_bitfield, pmi->install_bit); pbr_map_check_valid(pbrm->name); if (pbrm->valid) pbr_map_install(pbrm); } void pbr_map_write_interfaces(struct vty *vty, struct interface *ifp) { struct pbr_interface *pbr_ifp = ifp->info; if (pbr_ifp && strncmp(pbr_ifp->mapname, "", sizeof(pbr_ifp->mapname)) != 0) vty_out(vty, " pbr-policy %s\n", pbr_ifp->mapname); } struct pbr_map *pbrm_find(const char *name) { struct pbr_map pbrm; strlcpy(pbrm.name, name, sizeof(pbrm.name)); return RB_FIND(pbr_map_entry_head, &pbr_maps, &pbrm); } extern void pbr_map_delete(struct pbr_map_sequence *pbrms) { struct pbr_map *pbrm; struct listnode *inode; struct pbr_map_interface *pmi; pbrm = pbrms->parent; for (ALL_LIST_ELEMENTS_RO(pbrm->incoming, inode, pmi)) pbr_send_pbr_map(pbrms, pmi, false); if (pbrms->nhg) pbr_nht_delete_individual_nexthop(pbrms); listnode_delete(pbrm->seqnumbers, pbrms); if (pbrm->seqnumbers->count == 0) { RB_REMOVE(pbr_map_entry_head, &pbr_maps, pbrm); bf_free(pbrm->ifi_bitfield); XFREE(MTYPE_PBR_MAP, pbrm); } } void pbr_map_delete_nexthop_group(struct pbr_map_sequence *pbrms) { struct pbr_map *pbrm = pbrms->parent; struct listnode *node; struct pbr_map_interface *pmi; if (pbrm->valid && pbrms->nhs_installed && pbrm->incoming->count) { for (ALL_LIST_ELEMENTS_RO(pbrm->incoming, node, pmi)) pbr_send_pbr_map(pbrms, pmi, false); } pbrm->valid = false; pbrms->nhs_installed = false; pbrms->reason |= PBR_MAP_INVALID_NO_NEXTHOPS; pbrms->nhgrp_name = NULL; } struct pbr_map_sequence *pbrms_lookup_unique(uint32_t unique, ifindex_t ifindex, struct pbr_map_interface **ppmi) { struct pbr_map_sequence *pbrms; struct listnode *snode, *inode; struct pbr_map_interface *pmi; struct pbr_map *pbrm; RB_FOREACH (pbrm, pbr_map_entry_head, &pbr_maps) { for (ALL_LIST_ELEMENTS_RO(pbrm->incoming, inode, pmi)) { if (pmi->ifp->ifindex != ifindex) continue; if (ppmi) *ppmi = pmi; for (ALL_LIST_ELEMENTS_RO(pbrm->seqnumbers, snode, pbrms)) { DEBUGD(&pbr_dbg_map, "%s: Comparing %u to %u", __PRETTY_FUNCTION__, pbrms->unique, unique); if (pbrms->unique == unique) return pbrms; } } } return NULL; } static void pbr_map_add_interfaces(struct pbr_map *pbrm) { struct interface *ifp; struct pbr_interface *pbr_ifp; struct vrf *vrf; RB_FOREACH (vrf, vrf_name_head, &vrfs_by_name) { FOR_ALL_INTERFACES (vrf, ifp) { if (ifp->info) { pbr_ifp = ifp->info; if (strcmp(pbrm->name, pbr_ifp->mapname) == 0) pbr_map_add_interface(pbrm, ifp); } } } } struct pbr_map_sequence *pbrms_get(const char *name, uint32_t seqno) { struct pbr_map *pbrm; struct pbr_map_sequence *pbrms; struct listnode *node; pbrm = pbrm_find(name); if (!pbrm) { pbrm = XCALLOC(MTYPE_PBR_MAP, sizeof(*pbrm)); snprintf(pbrm->name, sizeof(pbrm->name), "%s", name); pbrm->seqnumbers = list_new(); pbrm->seqnumbers->cmp = (int (*)(void *, void *))pbr_map_sequence_compare; pbrm->seqnumbers->del = (void (*)(void *))pbr_map_sequence_delete; pbrm->incoming = list_new(); pbrm->incoming->cmp = (int (*)(void *, void *))pbr_map_interface_compare; pbrm->incoming->del = (void (*)(void *))pbr_map_interface_list_delete; RB_INSERT(pbr_map_entry_head, &pbr_maps, pbrm); bf_init(pbrm->ifi_bitfield, 64); pbr_map_add_interfaces(pbrm); } for (ALL_LIST_ELEMENTS_RO(pbrm->seqnumbers, node, pbrms)) { if (pbrms->seqno == seqno) break; } if (!pbrms) { pbrms = XCALLOC(MTYPE_PBR_MAP_SEQNO, sizeof(*pbrms)); pbrms->unique = pbr_map_sequence_unique++; pbrms->seqno = seqno; pbrms->ruleno = pbr_nht_get_next_rule(seqno); pbrms->parent = pbrm; pbrms->reason = PBR_MAP_INVALID_EMPTY | PBR_MAP_INVALID_NO_NEXTHOPS; QOBJ_REG(pbrms, pbr_map_sequence); listnode_add_sort(pbrm->seqnumbers, pbrms); } return pbrms; } static void pbr_map_sequence_check_nexthops_valid(struct pbr_map_sequence *pbrms) { /* * Check validness of the nexthop or nexthop-group */ if (!pbrms->nhg && !pbrms->nhgrp_name) pbrms->reason |= PBR_MAP_INVALID_NO_NEXTHOPS; if (pbrms->nhg && pbrms->nhgrp_name) pbrms->reason |= PBR_MAP_INVALID_BOTH_NHANDGRP; if (pbrms->nhg && !pbr_nht_nexthop_group_valid(pbrms->internal_nhg_name)) pbrms->reason |= PBR_MAP_INVALID_NEXTHOP; if (pbrms->nhgrp_name) { if (!pbr_nht_nexthop_group_valid(pbrms->nhgrp_name)) pbrms->reason |= PBR_MAP_INVALID_NEXTHOP_GROUP; else pbrms->nhs_installed = true; } } static void pbr_map_sequence_check_not_empty(struct pbr_map_sequence *pbrms) { if (!pbrms->src && !pbrms->dst && !pbrms->mark) pbrms->reason |= PBR_MAP_INVALID_EMPTY; } /* * Checks to see if we think that the pbmrs is valid. If we think * the config is valid return true. */ static void pbr_map_sequence_check_valid(struct pbr_map_sequence *pbrms) { pbr_map_sequence_check_nexthops_valid(pbrms); pbr_map_sequence_check_not_empty(pbrms); } static bool pbr_map_check_valid_internal(struct pbr_map *pbrm) { struct pbr_map_sequence *pbrms; struct listnode *node; pbrm->valid = true; for (ALL_LIST_ELEMENTS_RO(pbrm->seqnumbers, node, pbrms)) { pbrms->reason = 0; pbr_map_sequence_check_valid(pbrms); /* * A pbr_map_sequence that is invalid causes * the whole shebang to be invalid */ if (pbrms->reason != 0) pbrm->valid = false; } return pbrm->valid; } /* * For a given PBR-MAP check to see if we think it is a * valid config or not. If so note that it is and return * that we are valid. */ bool pbr_map_check_valid(const char *name) { struct pbr_map *pbrm; pbrm = pbrm_find(name); if (!pbrm) { DEBUGD(&pbr_dbg_map, "%s: Specified PBR-MAP(%s) does not exist?", __PRETTY_FUNCTION__, name); return false; } pbr_map_check_valid_internal(pbrm); return pbrm->valid; } void pbr_map_schedule_policy_from_nhg(const char *nh_group) { struct pbr_map_sequence *pbrms; struct pbr_map *pbrm; struct listnode *node; RB_FOREACH (pbrm, pbr_map_entry_head, &pbr_maps) { DEBUGD(&pbr_dbg_map, "%s: Looking at %s", __PRETTY_FUNCTION__, pbrm->name); for (ALL_LIST_ELEMENTS_RO(pbrm->seqnumbers, node, pbrms)) { DEBUGD(&pbr_dbg_map, "\tNH Grp name: %s", pbrms->nhgrp_name ? pbrms->nhgrp_name : pbrms->internal_nhg_name); if (pbrms->nhgrp_name && (strcmp(nh_group, pbrms->nhgrp_name) == 0)) { pbrms->nhs_installed = true; pbr_map_check(pbrms); } if (pbrms->nhg && (strcmp(nh_group, pbrms->internal_nhg_name) == 0)) { pbrms->nhs_installed = true; pbr_map_check(pbrms); } } } } void pbr_map_policy_install(const char *name) { struct pbr_map_sequence *pbrms; struct pbr_map *pbrm; struct listnode *node, *inode; struct pbr_map_interface *pmi; DEBUGD(&pbr_dbg_map, "%s: for %s", __PRETTY_FUNCTION__, name); pbrm = pbrm_find(name); if (!pbrm) return; for (ALL_LIST_ELEMENTS_RO(pbrm->seqnumbers, node, pbrms)) { DEBUGD(&pbr_dbg_map, "%s: Looking at what to install %s(%u) %d %d", __PRETTY_FUNCTION__, name, pbrms->seqno, pbrm->valid, pbrms->nhs_installed); if (pbrm->valid && pbrms->nhs_installed && pbrm->incoming->count) { DEBUGD(&pbr_dbg_map, "\tInstalling %s %u", pbrm->name, pbrms->seqno); for (ALL_LIST_ELEMENTS_RO(pbrm->incoming, inode, pmi)) pbr_send_pbr_map(pbrms, pmi, true); } } } void pbr_map_policy_delete(struct pbr_map *pbrm, struct pbr_map_interface *pmi) { struct listnode *node; struct pbr_map_sequence *pbrms; for (ALL_LIST_ELEMENTS_RO(pbrm->seqnumbers, node, pbrms)) pbr_send_pbr_map(pbrms, pmi, false); pmi->delete = true; } /* * For a nexthop group specified, see if any of the pbr-maps * are using it and if so, check to see that we are still * valid for usage. If we are valid then schedule the installation/deletion * of the pbr-policy. */ void pbr_map_check_nh_group_change(const char *nh_group) { struct pbr_map_sequence *pbrms; struct pbr_map *pbrm; struct listnode *node, *inode; struct pbr_map_interface *pmi; bool found_name; RB_FOREACH (pbrm, pbr_map_entry_head, &pbr_maps) { for (ALL_LIST_ELEMENTS_RO(pbrm->seqnumbers, node, pbrms)) { found_name = false; if (pbrms->nhgrp_name) found_name = !strcmp(nh_group, pbrms->nhgrp_name); else if (pbrms->nhg) found_name = !strcmp(nh_group, pbrms->internal_nhg_name); if (found_name) { bool original = pbrm->valid; pbr_map_check_valid_internal(pbrm); if (pbrm->valid && (original != pbrm->valid)) pbr_map_install(pbrm); if (pbrm->valid == false) for (ALL_LIST_ELEMENTS_RO( pbrm->incoming, inode, pmi)) pbr_send_pbr_map(pbrms, pmi, false); } } } } void pbr_map_check(struct pbr_map_sequence *pbrms) { struct pbr_map *pbrm; struct listnode *inode; struct pbr_map_interface *pmi; bool install; pbrm = pbrms->parent; DEBUGD(&pbr_dbg_map, "%s: for %s(%u)", __PRETTY_FUNCTION__, pbrm->name, pbrms->seqno); if (pbr_map_check_valid(pbrm->name)) DEBUGD(&pbr_dbg_map, "We are totally valid %s", pbrm->name); if (pbrms->reason == PBR_MAP_VALID_SEQUENCE_NUMBER) { install = true; DEBUGD(&pbr_dbg_map, "%s: Installing %s(%u) reason: %" PRIu64, __PRETTY_FUNCTION__, pbrm->name, pbrms->seqno, pbrms->reason); DEBUGD(&pbr_dbg_map, "\tSending PBR_MAP_POLICY_INSTALL event"); } else { install = false; DEBUGD(&pbr_dbg_map, "%s: Removing %s(%u) reason: %" PRIu64, __PRETTY_FUNCTION__, pbrm->name, pbrms->seqno, pbrms->reason); } for (ALL_LIST_ELEMENTS_RO(pbrm->incoming, inode, pmi)) { pbr_send_pbr_map(pbrms, pmi, install); } } void pbr_map_install(struct pbr_map *pbrm) { struct listnode *node, *inode; struct pbr_map_sequence *pbrms; struct pbr_map_interface *pmi; if (!pbrm->incoming->count) return; for (ALL_LIST_ELEMENTS_RO(pbrm->seqnumbers, node, pbrms)) for (ALL_LIST_ELEMENTS_RO(pbrm->incoming, inode, pmi)) pbr_send_pbr_map(pbrms, pmi, true); } void pbr_map_init(void) { RB_INIT(pbr_map_entry_head, &pbr_maps); pbr_map_sequence_unique = 1; } frr-7.2.1/pbrd/pbr_map.h0000644000000000000000000001030313610377563011731 00000000000000/* * PBR-map Header * Copyright (C) 2018 Cumulus Networks, Inc. * Donald Sharp * * FRR is free software; you can redistribute it and/or modify it * under the terms of the GNU General Public License as published by the * Free Software Foundation; either version 2, or (at your option) any * later version. * * FRR 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; see the file COPYING; if not, write to the Free Software * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA */ #ifndef __PBR_MAP_H__ #define __PBR_MAP_H__ #include struct pbr_map { /* * RB Tree of the pbr_maps */ RB_ENTRY(pbr_map) pbr_map_entry; /* * The name of the PBR_MAP */ #define PBR_MAP_NAMELEN 100 char name[PBR_MAP_NAMELEN]; struct list *seqnumbers; /* * The list of incoming interfaces that * we will apply this policy map onto */ struct list *incoming; bitfield_t ifi_bitfield; /* * If valid is true we think the pbr_map is valid, * If false, look in individual pbrms to see * what we think is the invalid reason */ bool valid; }; RB_HEAD(pbr_map_entry_head, pbr_map); RB_PROTOTYPE(pbr_map_entry_head, pbr_map, pbr_map_entry, pbr_map_compare) struct pbr_map_interface { uint32_t install_bit; struct interface *ifp; struct pbr_map *pbrm; bool delete; }; struct pbr_map_sequence { struct pbr_map *parent; /* * The Unique identifier of this specific pbrms */ uint32_t unique; /* * The sequence of where we are for display */ uint32_t seqno; /* * The rule number to install into */ uint32_t ruleno; /* * Our policy Catchers */ struct prefix *src; struct prefix *dst; uint32_t mark; /* * Family of the src/dst. Needed when deleting since we clear them */ unsigned char family; /* * The nexthop group we auto create * for when the user specifies a individual * nexthop */ struct nexthop_group *nhg; char *internal_nhg_name; /* * The name of the nexthop group * configured in the pbr-map */ char *nhgrp_name; /* * Do we think are nexthops are installed */ bool nhs_installed; /* * Are we installed */ uint64_t installed; /* * A reason of 0 means we think the pbr_map_sequence is good to go * We can accumuluate multiple failure states */ #define PBR_MAP_VALID_SEQUENCE_NUMBER 0 #define PBR_MAP_INVALID_NEXTHOP_GROUP (1 << 0) #define PBR_MAP_INVALID_NEXTHOP (1 << 1) #define PBR_MAP_INVALID_NO_NEXTHOPS (1 << 2) #define PBR_MAP_INVALID_BOTH_NHANDGRP (1 << 3) #define PBR_MAP_INVALID_EMPTY (1 << 4) uint64_t reason; QOBJ_FIELDS }; DECLARE_QOBJ_TYPE(pbr_map_sequence) extern struct pbr_map_entry_head pbr_maps; extern struct pbr_map_sequence *pbrms_get(const char *name, uint32_t seqno); extern struct pbr_map_sequence * pbrms_lookup_unique(uint32_t unique, ifindex_t ifindex, struct pbr_map_interface **ppmi); extern struct pbr_map *pbrm_find(const char *name); extern void pbr_map_delete(struct pbr_map_sequence *pbrms); extern void pbr_map_delete_nexthop_group(struct pbr_map_sequence *pbrms); extern void pbr_map_add_interface(struct pbr_map *pbrm, struct interface *ifp); extern void pbr_map_interface_delete(struct pbr_map *pbrm, struct interface *ifp); extern void pbr_map_final_interface_deletion(struct pbr_map *pbrm, struct pbr_map_interface *pmi); extern void pbr_map_write_interfaces(struct vty *vty, struct interface *ifp); extern void pbr_map_init(void); extern bool pbr_map_check_valid(const char *name); extern void pbr_map_check(struct pbr_map_sequence *pbrms); extern void pbr_map_check_nh_group_change(const char *nh_group); extern void pbr_map_reason_string(unsigned int reason, char *buf, int size); extern void pbr_map_schedule_policy_from_nhg(const char *nh_group); extern void pbr_map_install(struct pbr_map *pbrm); extern void pbr_map_policy_install(const char *name); extern void pbr_map_policy_delete(struct pbr_map *pbrm, struct pbr_map_interface *pmi); #endif frr-7.2.1/pbrd/pbr_memory.c0000644000000000000000000000161213610377563012462 00000000000000/* * PBR memory code. * Copyright (C) 2018 Cumulus Networks, Inc. * Donald Sharp * * FRR is free software; you can redistribute it and/or modify it * under the terms of the GNU General Public License as published by the * Free Software Foundation; either version 2, or (at your option) any * later version. * * FRR 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; see the file COPYING; if not, write to the Free Software * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA */ #include #include #include "pbrd/pbr_memory.h" DEFINE_MGROUP(PBRD, "pbrd") frr-7.2.1/pbrd/pbr_memory.h0000644000000000000000000000153513610377563012473 00000000000000/* * pbr memory code. * Copyright (C) 2018 Cumulus Networks, Inc. * Donald Sharp * * FRR is free software; you can redistribute it and/or modify it * under the terms of the GNU General Public License as published by the * Free Software Foundation; either version 2, or (at your option) any * later version. * * FRR 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; see the file COPYING; if not, write to the Free Software * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA */ #ifndef __PBR_MEMORY_H__ DECLARE_MGROUP(PBRD) #endif frr-7.2.1/pbrd/pbr_nht.c0000644000000000000000000006006613610377563011753 00000000000000/* * PBR-nht Code * Copyright (C) 2018 Cumulus Networks, Inc. * Donald Sharp * * FRR is free software; you can redistribute it and/or modify it * under the terms of the GNU General Public License as published by the * Free Software Foundation; either version 2, or (at your option) any * later version. * * FRR 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; see the file COPYING; if not, write to the Free Software * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA */ #include #include #include #include "nexthop_group.h" #include "nexthop_group_private.h" #include #include #include #include #include #include "pbrd/pbr_nht.h" #include "pbrd/pbr_map.h" #include "pbrd/pbr_zebra.h" #include "pbrd/pbr_memory.h" #include "pbrd/pbr_debug.h" DEFINE_MTYPE_STATIC(PBRD, PBR_NHG, "PBR Nexthop Groups") static struct hash *pbr_nhg_hash; static struct hash *pbr_nhrc_hash; static uint32_t pbr_nhg_low_table; static uint32_t pbr_nhg_high_table; static uint32_t pbr_nhg_low_rule; static uint32_t pbr_nhg_high_rule; static bool nhg_tableid[65535]; static void pbr_nht_install_nexthop_group(struct pbr_nexthop_group_cache *pnhgc, struct nexthop_group nhg); static void pbr_nht_uninstall_nexthop_group(struct pbr_nexthop_group_cache *pnhgc, struct nexthop_group nhg, enum nexthop_types_t nh_type); /* * Nexthop refcount. */ struct nhrc { struct nexthop nexthop; unsigned int refcount; }; /* Hash functions for pbr_nhrc_hash ---------------------------------------- */ static void *pbr_nhrc_hash_alloc(void *p) { struct nhrc *nhrc = XCALLOC(MTYPE_PBR_NHG, sizeof(struct nhrc)); nhrc->nexthop = *(struct nexthop *)p; nhrc->nexthop.next = NULL; nhrc->nexthop.prev = NULL; return nhrc; } static bool pbr_nhrc_hash_equal(const void *arg1, const void *arg2) { const struct nexthop *nh1, *nh2; nh1 = arg1; nh2 = arg2; return nexthop_same(nh1, nh2); } /* ------------------------------------------------------------------------- */ static void *pbr_nh_alloc(void *p) { struct pbr_nexthop_cache *new; struct pbr_nexthop_cache *pnhc = (struct pbr_nexthop_cache *)p; struct nhrc *nhrc; new = XCALLOC(MTYPE_PBR_NHG, sizeof(*new)); nhrc = hash_get(pbr_nhrc_hash, pnhc->nexthop, pbr_nhrc_hash_alloc); new->nexthop = &nhrc->nexthop; /* Decremented again in pbr_nh_delete */ ++nhrc->refcount; DEBUGD(&pbr_dbg_nht, "%s: Sending nexthop to Zebra", __PRETTY_FUNCTION__); pbr_send_rnh(new->nexthop, true); new->valid = false; return new; } static void pbr_nh_delete(struct pbr_nexthop_cache **pnhc) { struct nhrc *nhrc; nhrc = hash_lookup(pbr_nhrc_hash, (*pnhc)->nexthop); if (nhrc) --nhrc->refcount; if (!nhrc || nhrc->refcount == 0) { DEBUGD(&pbr_dbg_nht, "%s: Removing nexthop from Zebra", __PRETTY_FUNCTION__); pbr_send_rnh((*pnhc)->nexthop, false); } if (nhrc && nhrc->refcount == 0) { hash_release(pbr_nhrc_hash, nhrc); XFREE(MTYPE_PBR_NHG, nhrc); } XFREE(MTYPE_PBR_NHG, *pnhc); } static void pbr_nh_delete_iterate(struct hash_bucket *b, void *p) { pbr_nh_delete((struct pbr_nexthop_cache **)&b->data); } static uint32_t pbr_nh_hash_key(const void *arg) { uint32_t key; const struct pbr_nexthop_cache *pbrnc = arg; key = nexthop_hash(pbrnc->nexthop); return key; } static bool pbr_nh_hash_equal(const void *arg1, const void *arg2) { const struct pbr_nexthop_cache *pbrnc1 = (const struct pbr_nexthop_cache *)arg1; const struct pbr_nexthop_cache *pbrnc2 = (const struct pbr_nexthop_cache *)arg2; if (pbrnc1->nexthop->vrf_id != pbrnc2->nexthop->vrf_id) return false; if (pbrnc1->nexthop->ifindex != pbrnc2->nexthop->ifindex) return false; if (pbrnc1->nexthop->type != pbrnc2->nexthop->type) return false; switch (pbrnc1->nexthop->type) { case NEXTHOP_TYPE_IFINDEX: return pbrnc1->nexthop->ifindex == pbrnc2->nexthop->ifindex; case NEXTHOP_TYPE_IPV4_IFINDEX: case NEXTHOP_TYPE_IPV4: return pbrnc1->nexthop->gate.ipv4.s_addr == pbrnc2->nexthop->gate.ipv4.s_addr; case NEXTHOP_TYPE_IPV6_IFINDEX: case NEXTHOP_TYPE_IPV6: return !memcmp(&pbrnc1->nexthop->gate.ipv6, &pbrnc2->nexthop->gate.ipv6, 16); case NEXTHOP_TYPE_BLACKHOLE: return pbrnc1->nexthop->bh_type == pbrnc2->nexthop->bh_type; } /* * We should not get here */ return false; } static void pbr_nhgc_delete(struct pbr_nexthop_group_cache *p) { hash_iterate(p->nhh, pbr_nh_delete_iterate, NULL); hash_free(p->nhh); XFREE(MTYPE_PBR_NHG, p); } static void *pbr_nhgc_alloc(void *p) { struct pbr_nexthop_group_cache *new; struct pbr_nexthop_group_cache *pnhgc = (struct pbr_nexthop_group_cache *)p; new = XCALLOC(MTYPE_PBR_NHG, sizeof(*new)); strlcpy(new->name, pnhgc->name, sizeof(pnhgc->name)); new->table_id = pbr_nht_get_next_tableid(false); DEBUGD(&pbr_dbg_nht, "%s: NHT: %s assigned Table ID: %u", __PRETTY_FUNCTION__, new->name, new->table_id); new->nhh = hash_create_size(8, pbr_nh_hash_key, pbr_nh_hash_equal, "PBR NH Cache Hash"); return new; } void pbr_nhgroup_add_cb(const char *name) { struct pbr_nexthop_group_cache *pnhgc; struct nexthop_group_cmd *nhgc; nhgc = nhgc_find(name); if (!nhgc) { DEBUGD(&pbr_dbg_nht, "%s: Could not find nhgc with name: %s\n", __PRETTY_FUNCTION__, name); return; } pnhgc = pbr_nht_add_group(name); if (!pnhgc) return; DEBUGD(&pbr_dbg_nht, "%s: Added nexthop-group %s", __PRETTY_FUNCTION__, name); pbr_map_check_nh_group_change(name); } void pbr_nhgroup_add_nexthop_cb(const struct nexthop_group_cmd *nhgc, const struct nexthop *nhop) { char debugstr[256]; struct pbr_nexthop_group_cache pnhgc_find = {}; struct pbr_nexthop_group_cache *pnhgc; struct pbr_nexthop_cache pnhc_find = {}; struct pbr_nexthop_cache *pnhc; if (!pbr_nht_get_next_tableid(true)) { zlog_warn( "%s: Exhausted all table identifiers; cannot create nexthop-group cache for nexthop-group '%s'", __PRETTY_FUNCTION__, nhgc->name); return; } /* find pnhgc by name */ strlcpy(pnhgc_find.name, nhgc->name, sizeof(pnhgc_find.name)); pnhgc = hash_get(pbr_nhg_hash, &pnhgc_find, pbr_nhgc_alloc); /* create & insert new pnhc into pnhgc->nhh */ pnhc_find.nexthop = (struct nexthop *)nhop; pnhc = hash_get(pnhgc->nhh, &pnhc_find, pbr_nh_alloc); pnhc_find.nexthop = NULL; /* set parent pnhgc */ pnhc->parent = pnhgc; if (DEBUG_MODE_CHECK(&pbr_dbg_nht, DEBUG_MODE_ALL)) { nexthop2str(nhop, debugstr, sizeof(debugstr)); DEBUGD(&pbr_dbg_nht, "%s: Added %s to nexthop-group %s", __PRETTY_FUNCTION__, debugstr, nhgc->name); } pbr_nht_install_nexthop_group(pnhgc, nhgc->nhg); pbr_map_check_nh_group_change(nhgc->name); if (nhop->type == NEXTHOP_TYPE_IFINDEX) { struct interface *ifp; ifp = if_lookup_by_index(nhop->ifindex, nhop->vrf_id); if (ifp) pbr_nht_nexthop_interface_update(ifp); } } void pbr_nhgroup_del_nexthop_cb(const struct nexthop_group_cmd *nhgc, const struct nexthop *nhop) { char debugstr[256]; struct pbr_nexthop_group_cache pnhgc_find = {}; struct pbr_nexthop_group_cache *pnhgc; struct pbr_nexthop_cache pnhc_find = {}; struct pbr_nexthop_cache *pnhc; enum nexthop_types_t nh_type = nhop->type; /* find pnhgc by name */ strlcpy(pnhgc_find.name, nhgc->name, sizeof(pnhgc_find.name)); pnhgc = hash_lookup(pbr_nhg_hash, &pnhgc_find); /* delete pnhc from pnhgc->nhh */ pnhc_find.nexthop = (struct nexthop *)nhop; pnhc = hash_release(pnhgc->nhh, &pnhc_find); /* delete pnhc */ pbr_nh_delete(&pnhc); if (DEBUG_MODE_CHECK(&pbr_dbg_nht, DEBUG_MODE_ALL)) { nexthop2str(nhop, debugstr, sizeof(debugstr)); DEBUGD(&pbr_dbg_nht, "%s: Removed %s from nexthop-group %s", __PRETTY_FUNCTION__, debugstr, nhgc->name); } if (pnhgc->nhh->count) pbr_nht_install_nexthop_group(pnhgc, nhgc->nhg); else pbr_nht_uninstall_nexthop_group(pnhgc, nhgc->nhg, nh_type); pbr_map_check_nh_group_change(nhgc->name); } void pbr_nhgroup_delete_cb(const char *name) { DEBUGD(&pbr_dbg_nht, "%s: Removed nexthop-group %s", __PRETTY_FUNCTION__, name); /* delete group from all pbrms's */ pbr_nht_delete_group(name); pbr_map_check_nh_group_change(name); } #if 0 static struct pbr_nexthop_cache *pbr_nht_lookup_nexthop(struct nexthop *nexthop) { return NULL; } #endif static void pbr_nht_find_nhg_from_table_install(struct hash_bucket *b, void *data) { struct pbr_nexthop_group_cache *pnhgc = (struct pbr_nexthop_group_cache *)b->data; uint32_t *table_id = (uint32_t *)data; if (pnhgc->table_id == *table_id) { DEBUGD(&pbr_dbg_nht, "%s: Table ID (%u) matches %s", __PRETTY_FUNCTION__, *table_id, pnhgc->name); /* * If the table has been re-handled by zebra * and we are already installed no need to do * anything here. */ if (!pnhgc->installed) { pnhgc->installed = true; pbr_map_schedule_policy_from_nhg(pnhgc->name); } } } void pbr_nht_route_installed_for_table(uint32_t table_id) { hash_iterate(pbr_nhg_hash, pbr_nht_find_nhg_from_table_install, &table_id); } static void pbr_nht_find_nhg_from_table_remove(struct hash_bucket *b, void *data) { ; } void pbr_nht_route_removed_for_table(uint32_t table_id) { hash_iterate(pbr_nhg_hash, pbr_nht_find_nhg_from_table_remove, &table_id); } /* * Loop through all nexthops in a nexthop group to check that they are all the * same. If they are not all the same, log this peculiarity. * * nhg * The nexthop group to check * * Returns: * - AFI of last nexthop in the group * - AFI_MAX on error */ static afi_t pbr_nht_which_afi(struct nexthop_group nhg, enum nexthop_types_t nh_type) { struct nexthop *nexthop; afi_t install_afi = AFI_MAX; bool v6, v4, bh; if (nh_type) { switch (nh_type) { case NEXTHOP_TYPE_IPV4: case NEXTHOP_TYPE_IPV4_IFINDEX: return AFI_IP; case NEXTHOP_TYPE_IPV6: case NEXTHOP_TYPE_IPV6_IFINDEX: return AFI_IP6; case NEXTHOP_TYPE_IFINDEX: case NEXTHOP_TYPE_BLACKHOLE: return AFI_MAX; } } v6 = v4 = bh = false; for (ALL_NEXTHOPS(nhg, nexthop)) { nh_type = nexthop->type; switch (nh_type) { case NEXTHOP_TYPE_IFINDEX: break; case NEXTHOP_TYPE_IPV4: case NEXTHOP_TYPE_IPV4_IFINDEX: v6 = true; install_afi = AFI_IP; break; case NEXTHOP_TYPE_IPV6: case NEXTHOP_TYPE_IPV6_IFINDEX: v4 = true; install_afi = AFI_IP6; break; case NEXTHOP_TYPE_BLACKHOLE: bh = true; break; } } /* Interface and/or blackhole nexthops only. */ if (!v4 && !v6) install_afi = AFI_MAX; if (!bh && v6 && v4) DEBUGD(&pbr_dbg_nht, "%s: Saw both V6 and V4 nexthops...using %s", __PRETTY_FUNCTION__, afi2str(install_afi)); if (bh && (v6 || v4)) DEBUGD(&pbr_dbg_nht, "%s: Saw blackhole nexthop(s) with %s%s%s nexthop(s), using AFI_MAX.", __PRETTY_FUNCTION__, v4 ? "v4" : "", (v4 && v6) ? " and " : "", v6 ? "v6" : ""); return install_afi; } static void pbr_nht_install_nexthop_group(struct pbr_nexthop_group_cache *pnhgc, struct nexthop_group nhg) { afi_t install_afi; enum nexthop_types_t nh_type = 0; install_afi = pbr_nht_which_afi(nhg, nh_type); route_add(pnhgc, nhg, install_afi); } static void pbr_nht_uninstall_nexthop_group(struct pbr_nexthop_group_cache *pnhgc, struct nexthop_group nhg, enum nexthop_types_t nh_type) { afi_t install_afi; install_afi = pbr_nht_which_afi(nhg, nh_type); pnhgc->installed = false; pnhgc->valid = false; route_delete(pnhgc, install_afi); } void pbr_nht_change_group(const char *name) { struct nexthop_group_cmd *nhgc; struct pbr_nexthop_group_cache *pnhgc; struct pbr_nexthop_group_cache find; struct nexthop *nhop; nhgc = nhgc_find(name); if (!nhgc) return; memset(&find, 0, sizeof(find)); snprintf(find.name, sizeof(find.name), "%s", name); pnhgc = hash_lookup(pbr_nhg_hash, &find); if (!pnhgc) { DEBUGD(&pbr_dbg_nht, "%s: Could not find nexthop-group cache w/ name '%s'", __PRETTY_FUNCTION__, name); return; } for (ALL_NEXTHOPS(nhgc->nhg, nhop)) { struct pbr_nexthop_cache lookup; struct pbr_nexthop_cache *pnhc; lookup.nexthop = nhop; pnhc = hash_lookup(pnhgc->nhh, &lookup); if (!pnhc) { pnhc = hash_get(pnhgc->nhh, &lookup, pbr_nh_alloc); pnhc->parent = pnhgc; } } pbr_nht_install_nexthop_group(pnhgc, nhgc->nhg); } char *pbr_nht_nexthop_make_name(char *name, size_t l, uint32_t seqno, char *buffer) { snprintf(buffer, l, "%s%u", name, seqno); return buffer; } void pbr_nht_add_individual_nexthop(struct pbr_map_sequence *pbrms) { struct pbr_nexthop_group_cache *pnhgc; struct pbr_nexthop_group_cache find; struct pbr_nexthop_cache *pnhc; struct pbr_nexthop_cache lookup; memset(&find, 0, sizeof(find)); pbr_nht_nexthop_make_name(pbrms->parent->name, PBR_NHC_NAMELEN, pbrms->seqno, find.name); if (!pbr_nht_get_next_tableid(true)) { zlog_warn( "%s: Exhausted all table identifiers; cannot create nexthop-group cache for nexthop-group '%s'", __PRETTY_FUNCTION__, find.name); return; } if (!pbrms->internal_nhg_name) pbrms->internal_nhg_name = XSTRDUP(MTYPE_TMP, find.name); pnhgc = hash_get(pbr_nhg_hash, &find, pbr_nhgc_alloc); lookup.nexthop = pbrms->nhg->nexthop; pnhc = hash_get(pnhgc->nhh, &lookup, pbr_nh_alloc); pnhc->parent = pnhgc; pbr_nht_install_nexthop_group(pnhgc, *pbrms->nhg); } void pbr_nht_delete_individual_nexthop(struct pbr_map_sequence *pbrms) { struct pbr_nexthop_group_cache *pnhgc; struct pbr_nexthop_group_cache find; struct pbr_nexthop_cache *pnhc; struct pbr_nexthop_cache lup; struct pbr_map *pbrm = pbrms->parent; struct listnode *node; struct pbr_map_interface *pmi; struct nexthop *nh; enum nexthop_types_t nh_type = 0; if (pbrm->valid && pbrms->nhs_installed && pbrm->incoming->count) { for (ALL_LIST_ELEMENTS_RO(pbrm->incoming, node, pmi)) pbr_send_pbr_map(pbrms, pmi, false); } pbrm->valid = false; pbrms->nhs_installed = false; pbrms->reason |= PBR_MAP_INVALID_NO_NEXTHOPS; memset(&find, 0, sizeof(find)); snprintf(find.name, sizeof(find.name), "%s", pbrms->internal_nhg_name); pnhgc = hash_lookup(pbr_nhg_hash, &find); nh = pbrms->nhg->nexthop; nh_type = nh->type; lup.nexthop = nh; pnhc = hash_lookup(pnhgc->nhh, &lup); pnhc->parent = NULL; hash_release(pnhgc->nhh, pnhc); pbr_nh_delete(&pnhc); pbr_nht_uninstall_nexthop_group(pnhgc, *pbrms->nhg, nh_type); hash_release(pbr_nhg_hash, pnhgc); _nexthop_del(pbrms->nhg, nh); nexthop_free(nh); nexthop_group_delete(&pbrms->nhg); XFREE(MTYPE_TMP, pbrms->internal_nhg_name); } struct pbr_nexthop_group_cache *pbr_nht_add_group(const char *name) { struct nexthop *nhop; struct nexthop_group_cmd *nhgc; struct pbr_nexthop_group_cache *pnhgc; struct pbr_nexthop_group_cache lookup; if (!pbr_nht_get_next_tableid(true)) { zlog_warn( "%s: Exhausted all table identifiers; cannot create nexthop-group cache for nexthop-group '%s'", __PRETTY_FUNCTION__, name); return NULL; } nhgc = nhgc_find(name); if (!nhgc) { DEBUGD(&pbr_dbg_nht, "%s: Could not find nhgc with name: %s\n", __PRETTY_FUNCTION__, name); return NULL; } snprintf(lookup.name, sizeof(lookup.name), "%s", name); pnhgc = hash_get(pbr_nhg_hash, &lookup, pbr_nhgc_alloc); DEBUGD(&pbr_dbg_nht, "%s: Retrieved NHGC @ %p", __PRETTY_FUNCTION__, pnhgc); for (ALL_NEXTHOPS(nhgc->nhg, nhop)) { struct pbr_nexthop_cache lookupc; struct pbr_nexthop_cache *pnhc; lookupc.nexthop = nhop; pnhc = hash_lookup(pnhgc->nhh, &lookupc); if (!pnhc) { pnhc = hash_get(pnhgc->nhh, &lookupc, pbr_nh_alloc); pnhc->parent = pnhgc; } } return pnhgc; } void pbr_nht_delete_group(const char *name) { struct pbr_map_sequence *pbrms; struct listnode *snode; struct pbr_map *pbrm; struct pbr_nexthop_group_cache pnhgc_find; struct pbr_nexthop_group_cache *pnhgc; RB_FOREACH (pbrm, pbr_map_entry_head, &pbr_maps) { for (ALL_LIST_ELEMENTS_RO(pbrm->seqnumbers, snode, pbrms)) { if (pbrms->nhgrp_name && strmatch(pbrms->nhgrp_name, name)) { pbrms->reason |= PBR_MAP_INVALID_NO_NEXTHOPS; nexthop_group_delete(&pbrms->nhg); pbrms->nhg = NULL; pbrms->internal_nhg_name = NULL; pbrm->valid = false; } } } strlcpy(pnhgc_find.name, name, sizeof(pnhgc_find.name)); pnhgc = hash_release(pbr_nhg_hash, &pnhgc_find); pbr_nhgc_delete(pnhgc); } bool pbr_nht_nexthop_valid(struct nexthop_group *nhg) { DEBUGD(&pbr_dbg_nht, "%s: %p", __PRETTY_FUNCTION__, nhg); return true; } bool pbr_nht_nexthop_group_valid(const char *name) { struct pbr_nexthop_group_cache *pnhgc; struct pbr_nexthop_group_cache lookup; DEBUGD(&pbr_dbg_nht, "%s: %s", __PRETTY_FUNCTION__, name); snprintf(lookup.name, sizeof(lookup.name), "%s", name); pnhgc = hash_get(pbr_nhg_hash, &lookup, NULL); if (!pnhgc) return false; DEBUGD(&pbr_dbg_nht, "%s: \t%d %d", __PRETTY_FUNCTION__, pnhgc->valid, pnhgc->installed); if (pnhgc->valid && pnhgc->installed) return true; return false; } struct pbr_nht_individual { struct zapi_route *nhr; struct interface *ifp; uint32_t valid; }; static void pbr_nht_individual_nexthop_update_lookup(struct hash_bucket *b, void *data) { struct pbr_nexthop_cache *pnhc = b->data; struct pbr_nht_individual *pnhi = data; char buf[PREFIX_STRLEN]; bool old_valid; old_valid = pnhc->valid; switch (pnhi->nhr->prefix.family) { case AF_INET: if (pnhc->nexthop->gate.ipv4.s_addr == pnhi->nhr->prefix.u.prefix4.s_addr) pnhc->valid = !!pnhi->nhr->nexthop_num; break; case AF_INET6: if (memcmp(&pnhc->nexthop->gate.ipv6, &pnhi->nhr->prefix.u.prefix6, 16) == 0) pnhc->valid = !!pnhi->nhr->nexthop_num; break; } DEBUGD(&pbr_dbg_nht, "\tFound %s: old: %d new: %d", prefix2str(&pnhi->nhr->prefix, buf, sizeof(buf)), old_valid, pnhc->valid); if (pnhc->valid) pnhi->valid += 1; } static void pbr_nexthop_group_cache_iterate_to_group(struct hash_bucket *b, void *data) { struct pbr_nexthop_cache *pnhc = b->data; struct nexthop_group *nhg = data; struct nexthop *nh = NULL; copy_nexthops(&nh, pnhc->nexthop, NULL); _nexthop_add(&nhg->nexthop, nh); } static void pbr_nexthop_group_cache_to_nexthop_group(struct nexthop_group *nhg, struct pbr_nexthop_group_cache *pnhgc) { hash_iterate(pnhgc->nhh, pbr_nexthop_group_cache_iterate_to_group, nhg); } static void pbr_nht_nexthop_update_lookup(struct hash_bucket *b, void *data) { struct pbr_nexthop_group_cache *pnhgc = b->data; struct pbr_nht_individual pnhi; struct nexthop_group nhg = {}; bool old_valid; old_valid = pnhgc->valid; pnhi.nhr = (struct zapi_route *)data; pnhi.valid = 0; hash_iterate(pnhgc->nhh, pbr_nht_individual_nexthop_update_lookup, &pnhi); /* * If any of the specified nexthops are valid we are valid */ pnhgc->valid = !!pnhi.valid; if (pnhgc->valid) { pbr_nexthop_group_cache_to_nexthop_group(&nhg, pnhgc); pbr_nht_install_nexthop_group(pnhgc, nhg); /* Don't need copied nexthops anymore */ nexthops_free(nhg.nexthop); } if (old_valid != pnhgc->valid) pbr_map_check_nh_group_change(pnhgc->name); } void pbr_nht_nexthop_update(struct zapi_route *nhr) { hash_iterate(pbr_nhg_hash, pbr_nht_nexthop_update_lookup, nhr); } static void pbr_nht_individual_nexthop_interface_update_lookup(struct hash_backet *b, void *data) { struct pbr_nexthop_cache *pnhc = b->data; struct pbr_nht_individual *pnhi = data; bool old_valid; old_valid = pnhc->valid; if (pnhc->nexthop->type == NEXTHOP_TYPE_IFINDEX && pnhc->nexthop->ifindex == pnhi->ifp->ifindex) pnhc->valid = !!if_is_up(pnhi->ifp); DEBUGD(&pbr_dbg_nht, "\tFound %s: old: %d new: %d", pnhi->ifp->name, old_valid, pnhc->valid); if (pnhc->valid) pnhi->valid += 1; } static void pbr_nht_nexthop_interface_update_lookup(struct hash_backet *b, void *data) { struct pbr_nexthop_group_cache *pnhgc = b->data; struct pbr_nht_individual pnhi; bool old_valid; old_valid = pnhgc->valid; pnhi.ifp = data; pnhi.valid = 0; hash_iterate(pnhgc->nhh, pbr_nht_individual_nexthop_interface_update_lookup, &pnhi); /* * If any of the specified nexthops are valid we are valid */ pnhgc->valid = !!pnhi.valid; if (old_valid != pnhgc->valid) pbr_map_check_nh_group_change(pnhgc->name); } void pbr_nht_nexthop_interface_update(struct interface *ifp) { hash_iterate(pbr_nhg_hash, pbr_nht_nexthop_interface_update_lookup, ifp); } static uint32_t pbr_nhg_hash_key(const void *arg) { const struct pbr_nexthop_group_cache *nhgc = arg; return jhash(&nhgc->name, strlen(nhgc->name), 0x52c34a96); } static bool pbr_nhg_hash_equal(const void *arg1, const void *arg2) { const struct pbr_nexthop_group_cache *nhgc1 = (const struct pbr_nexthop_group_cache *)arg1; const struct pbr_nexthop_group_cache *nhgc2 = (const struct pbr_nexthop_group_cache *)arg2; return !strcmp(nhgc1->name, nhgc2->name); } uint32_t pbr_nht_get_next_tableid(bool peek) { uint32_t i; bool found = false; for (i = pbr_nhg_low_table; i <= pbr_nhg_high_table; i++) { if (!nhg_tableid[i]) { found = true; break; } } if (found) { nhg_tableid[i] = !peek; return i; } else return 0; } void pbr_nht_set_tableid_range(uint32_t low, uint32_t high) { pbr_nhg_low_table = low; pbr_nhg_high_table = high; } void pbr_nht_write_table_range(struct vty *vty) { if (pbr_nhg_low_table != PBR_NHT_DEFAULT_LOW_TABLEID || pbr_nhg_high_table != PBR_NHT_DEFAULT_HIGH_TABLEID) { vty_out(vty, "pbr table range %u %u\n", pbr_nhg_low_table, pbr_nhg_high_table); } } uint32_t pbr_nht_get_next_rule(uint32_t seqno) { return seqno + pbr_nhg_low_rule - 1; } void pbr_nht_set_rule_range(uint32_t low, uint32_t high) { pbr_nhg_low_rule = low; pbr_nhg_high_rule = high; } void pbr_nht_write_rule_range(struct vty *vty) { if (pbr_nhg_low_rule != PBR_NHT_DEFAULT_LOW_RULE || pbr_nhg_high_rule != PBR_NHT_DEFAULT_HIGH_RULE) { vty_out(vty, "pbr rule range %u %u\n", pbr_nhg_low_rule, pbr_nhg_high_rule); } } uint32_t pbr_nht_get_table(const char *name) { struct pbr_nexthop_group_cache find; struct pbr_nexthop_group_cache *pnhgc; memset(&find, 0, sizeof(find)); snprintf(find.name, sizeof(find.name), "%s", name); pnhgc = hash_lookup(pbr_nhg_hash, &find); if (!pnhgc) { DEBUGD(&pbr_dbg_nht, "%s: Could not find nexthop-group cache w/ name '%s'", __PRETTY_FUNCTION__, name); return 5000; } return pnhgc->table_id; } bool pbr_nht_get_installed(const char *name) { struct pbr_nexthop_group_cache find; struct pbr_nexthop_group_cache *pnhgc; memset(&find, 0, sizeof(find)); snprintf(find.name, sizeof(find.name), "%s", name); pnhgc = hash_lookup(pbr_nhg_hash, &find); if (!pnhgc) return false; return pnhgc->installed; } static void pbr_nht_show_nhg_nexthops(struct hash_bucket *b, void *data) { struct pbr_nexthop_cache *pnhc = b->data; struct vty *vty = data; vty_out(vty, "\tValid: %d ", pnhc->valid); nexthop_group_write_nexthop(vty, pnhc->nexthop); } struct pbr_nht_show { struct vty *vty; const char *name; }; static void pbr_nht_show_nhg(struct hash_bucket *b, void *data) { struct pbr_nexthop_group_cache *pnhgc = b->data; struct pbr_nht_show *pns = data; struct vty *vty; if (pns->name && strcmp(pns->name, pnhgc->name) != 0) return; vty = pns->vty; vty_out(vty, "Nexthop-Group: %s Table: %u Valid: %d Installed: %d\n", pnhgc->name, pnhgc->table_id, pnhgc->valid, pnhgc->installed); hash_iterate(pnhgc->nhh, pbr_nht_show_nhg_nexthops, vty); } void pbr_nht_show_nexthop_group(struct vty *vty, const char *name) { struct pbr_nht_show pns; pns.vty = vty; pns.name = name; hash_iterate(pbr_nhg_hash, pbr_nht_show_nhg, &pns); } void pbr_nht_init(void) { pbr_nhg_hash = hash_create_size( 16, pbr_nhg_hash_key, pbr_nhg_hash_equal, "PBR NHG Cache Hash"); pbr_nhrc_hash = hash_create_size(16, (unsigned int (*)(const void *))nexthop_hash, pbr_nhrc_hash_equal, "PBR NH Hash"); pbr_nhg_low_table = PBR_NHT_DEFAULT_LOW_TABLEID; pbr_nhg_high_table = PBR_NHT_DEFAULT_HIGH_TABLEID; pbr_nhg_low_rule = PBR_NHT_DEFAULT_LOW_RULE; pbr_nhg_high_rule = PBR_NHT_DEFAULT_HIGH_RULE; memset(&nhg_tableid, 0, 65535 * sizeof(uint8_t)); } frr-7.2.1/pbrd/pbr_nht.h0000644000000000000000000000736013610377563011756 00000000000000/* * PBR-nht Header * Copyright (C) 2018 Cumulus Networks, Inc. * Donald Sharp * * FRR is free software; you can redistribute it and/or modify it * under the terms of the GNU General Public License as published by the * Free Software Foundation; either version 2, or (at your option) any * later version. * * FRR 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; see the file COPYING; if not, write to the Free Software * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA */ #ifndef __PBR_NHT_H__ #define __PBR_NHT_H__ #include #include #include "pbr_map.h" #define PBR_NHC_NAMELEN PBR_MAP_NAMELEN + 10 struct pbr_nexthop_group_cache { char name[PBR_NHC_NAMELEN]; uint32_t table_id; struct hash *nhh; /* * If all nexthops are considered valid */ bool valid; bool installed; }; struct pbr_nexthop_cache { struct pbr_nexthop_group_cache *parent; struct nexthop *nexthop; bool valid; }; extern void pbr_nht_write_table_range(struct vty *vty); #define PBR_NHT_DEFAULT_LOW_TABLEID 10000 #define PBR_NHT_DEFAULT_HIGH_TABLEID 11000 extern void pbr_nht_set_tableid_range(uint32_t low, uint32_t high); /* * Get the next tableid to use for installation. * * peek * If set to true, retrieves the next ID without marking it used. The next * call will return the same ID. */ extern uint32_t pbr_nht_get_next_tableid(bool peek); /* * Get the next rule number to use for installation */ extern void pbr_nht_write_rule_range(struct vty *vty); #define PBR_NHT_DEFAULT_LOW_RULE 300 #define PBR_NHT_DEFAULT_HIGH_RULE 1300 extern void pbr_nht_set_rule_range(uint32_t low, uint32_t high); extern uint32_t pbr_nht_get_next_rule(uint32_t seqno); extern void pbr_nhgroup_add_cb(const char *name); extern void pbr_nhgroup_add_nexthop_cb(const struct nexthop_group_cmd *nhg, const struct nexthop *nhop); extern void pbr_nhgroup_del_nexthop_cb(const struct nexthop_group_cmd *nhg, const struct nexthop *nhop); extern void pbr_nhgroup_delete_cb(const char *name); extern bool pbr_nht_nexthop_valid(struct nexthop_group *nhg); extern bool pbr_nht_nexthop_group_valid(const char *name); extern struct pbr_nexthop_group_cache *pbr_nht_add_group(const char *name); extern void pbr_nht_change_group(const char *name); extern void pbr_nht_delete_group(const char *name); extern void pbr_nht_add_individual_nexthop(struct pbr_map_sequence *pbrms); extern void pbr_nht_delete_individual_nexthop(struct pbr_map_sequence *pbrms); /* * Given the tableid of the installed default * route, find the nexthop-group associated with * it, then find all pbr-maps that use it and * install/delete them as well. */ extern void pbr_nht_route_installed_for_table(uint32_t table_id); extern void pbr_nht_route_removed_for_table(uint32_t table_id); /* * Given the nexthop group name, lookup the associated * tableid with it */ extern uint32_t pbr_nht_get_table(const char *name); extern bool pbr_nht_get_installed(const char *name); extern char *pbr_nht_nexthop_make_name(char *name, size_t l, uint32_t seqno, char *buffer); extern void pbr_nht_show_nexthop_group(struct vty *vty, const char *name); /* * When we get a callback from zebra about a nexthop changing */ extern void pbr_nht_nexthop_update(struct zapi_route *nhr); /* * When we get a callback from zebra about an interface status update. */ extern void pbr_nht_nexthop_interface_update(struct interface *ifp); extern void pbr_nht_init(void); #endif frr-7.2.1/pbrd/pbr_vty.c0000644000000000000000000004252613610377563012005 00000000000000/* * PBR - vty code * Copyright (C) 2018 Cumulus Networks, Inc. * Donald Sharp * * FRR is free software; you can redistribute it and/or modify it * under the terms of the GNU General Public License as published by the * Free Software Foundation; either version 2, or (at your option) any * later version. * * FRR 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; see the file COPYING; if not, write to the Free Software * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA */ #include #include "vty.h" #include "command.h" #include "prefix.h" #include "vrf.h" #include "nexthop.h" #include "nexthop_group.h" #include "nexthop_group_private.h" #include "log.h" #include "debug.h" #include "pbr.h" #include "pbrd/pbr_nht.h" #include "pbrd/pbr_map.h" #include "pbrd/pbr_zebra.h" #include "pbrd/pbr_vty.h" #include "pbrd/pbr_debug.h" #ifndef VTYSH_EXTRACT_PL #include "pbrd/pbr_vty_clippy.c" #endif DEFUN_NOSH(pbr_map, pbr_map_cmd, "pbr-map PBRMAP seq (1-700)", "Create pbr-map or enter pbr-map command mode\n" "The name of the PBR MAP\n" "Sequence to insert in existing pbr-map entry\n" "Sequence number\n") { const char *pbrm_name = argv[1]->arg; uint32_t seqno = atoi(argv[3]->arg); struct pbr_map_sequence *pbrms; pbrms = pbrms_get(pbrm_name, seqno); VTY_PUSH_CONTEXT(PBRMAP_NODE, pbrms); return CMD_SUCCESS; } DEFUN_NOSH(no_pbr_map, no_pbr_map_cmd, "no pbr-map PBRMAP [seq (1-700)]", NO_STR "Delete pbr-map\n" "The name of the PBR MAP\n" "Sequence to delete from existing pbr-map entry\n" "Sequence number\n") { const char *pbrm_name = argv[2]->arg; uint32_t seqno = 0; struct pbr_map *pbrm = pbrm_find(pbrm_name); struct pbr_map_sequence *pbrms; struct listnode *node, *next_node; if (argc > 3) seqno = atoi(argv[4]->arg); if (!pbrm) { vty_out(vty, "pbr-map %s not found\n", pbrm_name); return CMD_SUCCESS; } for (ALL_LIST_ELEMENTS(pbrm->seqnumbers, node, next_node, pbrms)) { if (seqno && pbrms->seqno != seqno) continue; pbr_map_delete(pbrms); } return CMD_SUCCESS; } DEFPY(pbr_set_table_range, pbr_set_table_range_cmd, "[no] pbr table range (10000-4294966272)$lb (10000-4294966272)$ub", NO_STR PBR_STR "Set table ID range\n" "Set table ID range\n" "Lower bound for table ID range\n" "Upper bound for table ID range\n") { /* upper bound is 2^32 - 2^10 */ int ret = CMD_WARNING; const int minrange = 1000; /* validate given bounds */ if (lb > ub) vty_out(vty, "%% Lower bound must be less than upper bound\n"); else if (ub - lb < minrange) vty_out(vty, "%% Range breadth must be at least %d\n", minrange); else { ret = CMD_SUCCESS; pbr_nht_set_tableid_range((uint32_t) lb, (uint32_t) ub); } return ret; } DEFPY(pbr_map_match_src, pbr_map_match_src_cmd, "[no] match src-ip $prefix", NO_STR "Match the rest of the command\n" "Choose the src ip or ipv6 prefix to use\n" "v4 Prefix\n" "v6 Prefix\n") { struct pbr_map_sequence *pbrms = VTY_GET_CONTEXT(pbr_map_sequence); pbrms->family = prefix->family; if (!no) { if (prefix_same(pbrms->src, prefix)) return CMD_SUCCESS; if (!pbrms->src) pbrms->src = prefix_new(); prefix_copy(pbrms->src, prefix); } else { prefix_free(pbrms->src); pbrms->src = 0; } pbr_map_check(pbrms); return CMD_SUCCESS; } DEFPY(pbr_map_match_dst, pbr_map_match_dst_cmd, "[no] match dst-ip $prefix", NO_STR "Match the rest of the command\n" "Choose the src ip or ipv6 prefix to use\n" "v4 Prefix\n" "v6 Prefix\n") { struct pbr_map_sequence *pbrms = VTY_GET_CONTEXT(pbr_map_sequence); pbrms->family = prefix->family; if (!no) { if (prefix_same(pbrms->dst, prefix)) return CMD_SUCCESS; if (!pbrms->dst) pbrms->dst = prefix_new(); prefix_copy(pbrms->dst, prefix); } else { prefix_free(pbrms->dst); pbrms->dst = NULL; } pbr_map_check(pbrms); return CMD_SUCCESS; } DEFPY(pbr_map_match_mark, pbr_map_match_mark_cmd, "[no] match mark (1-4294967295)$mark", NO_STR "Match the rest of the command\n" "Choose the mark value to use\n" "mark\n") { struct pbr_map_sequence *pbrms = VTY_GET_CONTEXT(pbr_map_sequence); #ifndef GNU_LINUX vty_out(vty, "pbr marks are not supported on this platform"); return CMD_WARNING_CONFIG_FAILED; #endif if (!no) { if (pbrms->mark == (uint32_t) mark) return CMD_SUCCESS; pbrms->mark = (uint32_t) mark; } else { pbrms->mark = 0; } pbr_map_check(pbrms); return CMD_SUCCESS; } DEFPY(pbr_map_nexthop_group, pbr_map_nexthop_group_cmd, "[no] set nexthop-group NHGNAME$name", NO_STR "Set for the PBR-MAP\n" "nexthop-group to use\n" "The name of the nexthop-group\n") { struct pbr_map_sequence *pbrms = VTY_GET_CONTEXT(pbr_map_sequence); struct nexthop_group_cmd *nhgc; if (pbrms->nhg) { vty_out(vty, "A `set nexthop XX` command already exists, please remove that first\n"); return CMD_WARNING_CONFIG_FAILED; } nhgc = nhgc_find(name); if (!nhgc) { vty_out(vty, "Specified nexthop-group %s does not exist\n", name); vty_out(vty, "PBR-MAP will not be applied until it is created\n"); } if (no) { if (pbrms->nhgrp_name && strcmp(name, pbrms->nhgrp_name) == 0) pbr_map_delete_nexthop_group(pbrms); else { vty_out(vty, "Nexthop Group specified: %s does not exist to remove", name); return CMD_WARNING_CONFIG_FAILED; } } else { if (pbrms->nhgrp_name) { if (strcmp(name, pbrms->nhgrp_name) != 0) { vty_out(vty, "Please delete current nexthop group before modifying current one"); return CMD_WARNING_CONFIG_FAILED; } return CMD_SUCCESS; } pbrms->nhgrp_name = XSTRDUP(MTYPE_TMP, name); pbr_map_check(pbrms); } return CMD_SUCCESS; } DEFPY(pbr_map_nexthop, pbr_map_nexthop_cmd, "[no] set nexthop\ <\ $addr [INTERFACE$intf]\ |INTERFACE$intf\ >\ [nexthop-vrf NAME$vrf_name]", NO_STR "Set for the PBR-MAP\n" "Specify one of the nexthops in this map\n" "v4 Address\n" "v6 Address\n" "Interface to use\n" "Interface to use\n" "If the nexthop is in a different vrf tell us\n" "The nexthop-vrf Name\n") { struct pbr_map_sequence *pbrms = VTY_GET_CONTEXT(pbr_map_sequence); struct vrf *vrf; struct nexthop nhop; struct nexthop *nh; if (pbrms->nhgrp_name) { vty_out(vty, "Please unconfigure the nexthop group before adding an individual nexthop"); return CMD_WARNING_CONFIG_FAILED; } if (vrf_name) vrf = vrf_lookup_by_name(vrf_name); else vrf = vrf_lookup_by_id(VRF_DEFAULT); if (!vrf) { vty_out(vty, "Specified: %s is non-existent\n", vrf_name); return CMD_WARNING_CONFIG_FAILED; } memset(&nhop, 0, sizeof(nhop)); nhop.vrf_id = vrf->vrf_id; if (intf) { nhop.ifindex = ifname2ifindex(intf, vrf->vrf_id); if (nhop.ifindex == IFINDEX_INTERNAL) { vty_out(vty, "Specified Intf %s does not exist in vrf: %s\n", intf, vrf->name); return CMD_WARNING_CONFIG_FAILED; } } if (addr) { if (addr->sa.sa_family == AF_INET) { nhop.gate.ipv4.s_addr = addr->sin.sin_addr.s_addr; if (intf) nhop.type = NEXTHOP_TYPE_IPV4_IFINDEX; else nhop.type = NEXTHOP_TYPE_IPV4; } else { nhop.gate.ipv6 = addr->sin6.sin6_addr; if (intf) nhop.type = NEXTHOP_TYPE_IPV6_IFINDEX; else { if (IN6_IS_ADDR_LINKLOCAL(&nhop.gate.ipv6)) { vty_out(vty, "Specified a v6 LL with no interface, rejecting\n"); return CMD_WARNING_CONFIG_FAILED; } nhop.type = NEXTHOP_TYPE_IPV6; } } } else nhop.type = NEXTHOP_TYPE_IFINDEX; if (pbrms->nhg) nh = nexthop_exists(pbrms->nhg, &nhop); else { char buf[PBR_NHC_NAMELEN]; if (no) { vty_out(vty, "No nexthops to delete\n"); return CMD_WARNING_CONFIG_FAILED; } pbrms->nhg = nexthop_group_new(); pbrms->internal_nhg_name = XSTRDUP(MTYPE_TMP, pbr_nht_nexthop_make_name(pbrms->parent->name, PBR_NHC_NAMELEN, pbrms->seqno, buf)); nh = NULL; } if (no) { if (nh) pbr_nht_delete_individual_nexthop(pbrms); } else if (!nh) { if (pbrms->nhg->nexthop) { vty_out(vty, "If you would like more than one nexthop please use nexthop-groups"); return CMD_WARNING_CONFIG_FAILED; } /* must be adding new nexthop since !no and !nexthop_exists */ nh = nexthop_new(); memcpy(nh, &nhop, sizeof(nhop)); _nexthop_add(&pbrms->nhg->nexthop, nh); pbr_nht_add_individual_nexthop(pbrms); pbr_map_check(pbrms); } if (nhop.type == NEXTHOP_TYPE_IFINDEX) { struct interface *ifp; ifp = if_lookup_by_index(nhop.ifindex, nhop.vrf_id); if (ifp) pbr_nht_nexthop_interface_update(ifp); } return CMD_SUCCESS; } DEFPY (pbr_policy, pbr_policy_cmd, "[no] pbr-policy PBRMAP$mapname", NO_STR "Policy to use\n" "Name of the pbr-map to apply\n") { VTY_DECLVAR_CONTEXT(interface, ifp); struct pbr_map *pbrm, *old_pbrm; struct pbr_interface *pbr_ifp = ifp->info; old_pbrm = NULL; pbrm = pbrm_find(mapname); if (!pbr_ifp) { /* we don't want one and we don't have one, so... */ if (no) return CMD_SUCCESS; /* Some one could have fat fingered the interface name */ pbr_ifp = pbr_if_new(ifp); } if (no) { if (strcmp(pbr_ifp->mapname, mapname) == 0) { pbr_ifp->mapname[0] = '\0'; if (pbrm) pbr_map_interface_delete(pbrm, ifp); } } else { if (strcmp(pbr_ifp->mapname, "") != 0) { old_pbrm = pbrm_find(pbr_ifp->mapname); /* * So if we have an old pbrm we should only * delete it if we are actually deleting and * moving to a new pbrm */ if (old_pbrm && old_pbrm != pbrm) pbr_map_interface_delete(old_pbrm, ifp); } snprintf(pbr_ifp->mapname, sizeof(pbr_ifp->mapname), "%s", mapname); /* * So only reinstall if the old_pbrm and this pbrm are * different. */ if (pbrm && pbrm != old_pbrm) pbr_map_add_interface(pbrm, ifp); } return CMD_SUCCESS; } DEFPY (show_pbr, show_pbr_cmd, "show pbr", SHOW_STR PBR_STR) { pbr_nht_write_table_range(vty); pbr_nht_write_rule_range(vty); return CMD_SUCCESS; } DEFPY (show_pbr_map, show_pbr_map_cmd, "show pbr map [NAME$name] [detail$detail]", SHOW_STR PBR_STR "PBR Map\n" "PBR Map Name\n" "Detailed information\n") { struct pbr_map_sequence *pbrms; struct pbr_map *pbrm; struct listnode *node; char buf[PREFIX_STRLEN]; char rbuf[64]; RB_FOREACH (pbrm, pbr_map_entry_head, &pbr_maps) { if (name && strcmp(name, pbrm->name) != 0) continue; vty_out(vty, " pbr-map %s valid: %d\n", pbrm->name, pbrm->valid); for (ALL_LIST_ELEMENTS_RO(pbrm->seqnumbers, node, pbrms)) { if (pbrms->reason) pbr_map_reason_string(pbrms->reason, rbuf, sizeof(rbuf)); vty_out(vty, " Seq: %u rule: %u Installed: %" PRIu64 "(%u) Reason: %s\n", pbrms->seqno, pbrms->ruleno, pbrms->installed, pbrms->unique, pbrms->reason ? rbuf : "Valid"); if (pbrms->src) vty_out(vty, "\tSRC Match: %s\n", prefix2str(pbrms->src, buf, sizeof(buf))); if (pbrms->dst) vty_out(vty, "\tDST Match: %s\n", prefix2str(pbrms->dst, buf, sizeof(buf))); if (pbrms->mark) vty_out(vty, "\tMARK Match: %u\n", pbrms->mark); if (pbrms->nhgrp_name) { vty_out(vty, "\tNexthop-Group: %s(%u) Installed: %u(%d)\n", pbrms->nhgrp_name, pbr_nht_get_table(pbrms->nhgrp_name), pbrms->nhs_installed, pbr_nht_get_installed( pbrms->nhgrp_name)); } else if (pbrms->nhg) { vty_out(vty, " "); nexthop_group_write_nexthop( vty, pbrms->nhg->nexthop); vty_out(vty, "\tInstalled: %u(%d) Tableid: %d\n", pbrms->nhs_installed, pbr_nht_get_installed( pbrms->internal_nhg_name), pbr_nht_get_table( pbrms->internal_nhg_name)); } else { vty_out(vty, "\tNexthop-Group: Unknown Installed: 0(0)\n"); } } } return CMD_SUCCESS; } DEFPY(show_pbr_nexthop_group, show_pbr_nexthop_group_cmd, "show pbr nexthop-groups [WORD$word]", SHOW_STR PBR_STR "Nexthop Groups\n" "Optional Name of the nexthop group\n") { pbr_nht_show_nexthop_group(vty, word); return CMD_SUCCESS; } DEFPY (show_pbr_interface, show_pbr_interface_cmd, "show pbr interface [NAME$name]", SHOW_STR PBR_STR "PBR Interface\n" "PBR Interface Name\n") { struct interface *ifp; struct vrf *vrf; struct pbr_interface *pbr_ifp; RB_FOREACH(vrf, vrf_name_head, &vrfs_by_name) { FOR_ALL_INTERFACES(vrf, ifp) { struct pbr_map *pbrm; if (!ifp->info) continue; if (name && strcmp(ifp->name, name) != 0) continue; pbr_ifp = ifp->info; if (strcmp(pbr_ifp->mapname, "") == 0) continue; pbrm = pbrm_find(pbr_ifp->mapname); vty_out(vty, " %s(%d) with pbr-policy %s", ifp->name, ifp->ifindex, pbr_ifp->mapname); if (!pbrm) vty_out(vty, " (map doesn't exist)"); vty_out(vty, "\n"); } } return CMD_SUCCESS; } /* PBR debugging CLI ------------------------------------------------------- */ static struct cmd_node debug_node = {DEBUG_NODE, "", 1}; DEFPY(debug_pbr, debug_pbr_cmd, "[no] debug pbr [{map$map|zebra$zebra|nht$nht|events$events}]", NO_STR DEBUG_STR PBR_STR "Policy maps\n" "PBRD <-> Zebra communications\n" "Nexthop tracking\n" "Events\n") { uint32_t mode = DEBUG_NODE2MODE(vty->node); if (map) DEBUG_MODE_SET(&pbr_dbg_map, mode, !no); if (zebra) DEBUG_MODE_SET(&pbr_dbg_zebra, mode, !no); if (nht) DEBUG_MODE_SET(&pbr_dbg_nht, mode, !no); if (events) DEBUG_MODE_SET(&pbr_dbg_event, mode, !no); /* no specific debug --> act on all of them */ if (strmatch(argv[argc - 1]->text, "pbr")) pbr_debug_set_all(mode, !no); return CMD_SUCCESS; } DEFUN_NOSH(show_debugging_pbr, show_debugging_pbr_cmd, "show debugging [pbr]", SHOW_STR DEBUG_STR PBR_STR) { vty_out(vty, "PBR debugging status:\n"); pbr_debug_config_write_helper(vty, false); return CMD_SUCCESS; } /* ------------------------------------------------------------------------- */ static struct cmd_node interface_node = { INTERFACE_NODE, "%s(config-if)# ", 1 /* vtysh ? yes */ }; static int pbr_interface_config_write(struct vty *vty) { struct interface *ifp; struct vrf *vrf; RB_FOREACH (vrf, vrf_name_head, &vrfs_by_name) { FOR_ALL_INTERFACES (vrf, ifp) { if (vrf->vrf_id == VRF_DEFAULT) vty_frame(vty, "interface %s\n", ifp->name); else vty_frame(vty, "interface %s vrf %s\n", ifp->name, vrf->name); if (ifp->desc) vty_out(vty, " description %s\n", ifp->desc); pbr_map_write_interfaces(vty, ifp); vty_endframe(vty, "!\n"); } } return 1; } /* PBR map node structure. */ static struct cmd_node pbr_map_node = {PBRMAP_NODE, "%s(config-pbr-map)# ", 1}; static int pbr_vty_map_config_write_sequence(struct vty *vty, struct pbr_map *pbrm, struct pbr_map_sequence *pbrms) { char buff[PREFIX_STRLEN]; vty_out(vty, "pbr-map %s seq %u\n", pbrm->name, pbrms->seqno); if (pbrms->src) vty_out(vty, " match src-ip %s\n", prefix2str(pbrms->src, buff, sizeof(buff))); if (pbrms->dst) vty_out(vty, " match dst-ip %s\n", prefix2str(pbrms->dst, buff, sizeof(buff))); if (pbrms->mark) vty_out(vty, " match mark %u\n", pbrms->mark); if (pbrms->nhgrp_name) vty_out(vty, " set nexthop-group %s\n", pbrms->nhgrp_name); if (pbrms->nhg) { vty_out(vty, " set "); nexthop_group_write_nexthop(vty, pbrms->nhg->nexthop); } vty_out(vty, "!\n"); return 1; } static int pbr_vty_map_config_write(struct vty *vty) { struct pbr_map *pbrm; pbr_nht_write_table_range(vty); pbr_nht_write_rule_range(vty); RB_FOREACH(pbrm, pbr_map_entry_head, &pbr_maps) { struct pbr_map_sequence *pbrms; struct listnode *node; for (ALL_LIST_ELEMENTS_RO(pbrm->seqnumbers, node, pbrms)) pbr_vty_map_config_write_sequence(vty, pbrm, pbrms); } return 1; } static void pbr_map_completer(vector comps, struct cmd_token *token) { struct pbr_map *pbrm; RB_FOREACH (pbrm, pbr_map_entry_head, &pbr_maps) vector_set(comps, XSTRDUP(MTYPE_COMPLETION, pbrm->name)); } static const struct cmd_variable_handler pbr_map_name[] = { { .tokenname = "PBRMAP", .completions = pbr_map_completer, }, { .completions = NULL } }; void pbr_vty_init(void) { cmd_variable_handler_register(pbr_map_name); install_node(&interface_node, pbr_interface_config_write); if_cmd_init(); install_node(&pbr_map_node, pbr_vty_map_config_write); /* debug */ install_node(&debug_node, pbr_debug_config_write); install_element(VIEW_NODE, &debug_pbr_cmd); install_element(CONFIG_NODE, &debug_pbr_cmd); install_element(VIEW_NODE, &show_debugging_pbr_cmd); install_default(PBRMAP_NODE); install_element(CONFIG_NODE, &pbr_map_cmd); install_element(CONFIG_NODE, &no_pbr_map_cmd); install_element(CONFIG_NODE, &pbr_set_table_range_cmd); install_element(INTERFACE_NODE, &pbr_policy_cmd); install_element(PBRMAP_NODE, &pbr_map_match_src_cmd); install_element(PBRMAP_NODE, &pbr_map_match_dst_cmd); install_element(PBRMAP_NODE, &pbr_map_match_mark_cmd); install_element(PBRMAP_NODE, &pbr_map_nexthop_group_cmd); install_element(PBRMAP_NODE, &pbr_map_nexthop_cmd); install_element(VIEW_NODE, &show_pbr_cmd); install_element(VIEW_NODE, &show_pbr_map_cmd); install_element(VIEW_NODE, &show_pbr_interface_cmd); install_element(VIEW_NODE, &show_pbr_nexthop_group_cmd); } frr-7.2.1/pbrd/pbr_vty.h0000644000000000000000000000157513610377563012011 00000000000000/* * VTY library for PBR * Copyright (C) 2018 Cumulus Networks, Inc. * Donald Sharp * * FRR is free software; you can redistribute it and/or modify it * under the terms of the GNU General Public License as published by the * Free Software Foundation; either version 2, or (at your option) any * later version. * * FRR 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; see the file COPYING; if not, write to the Free Software * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA */ #ifndef __PBR_VTY_H__ #define __PBR_VTY_H__ extern void pbr_vty_init(void); #endif frr-7.2.1/pbrd/pbr_zebra.c0000644000000000000000000003424213610377563012262 00000000000000/* * Zebra connect code. * Copyright (C) 2018 Cumulus Networks, Inc. * Donald Sharp * * FRR is free software; you can redistribute it and/or modify it * under the terms of the GNU General Public License as published by the * Free Software Foundation; either version 2, or (at your option) any * later version. * * FRR 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; see the file COPYING; if not, write to the Free Software * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA */ #include #include "thread.h" #include "command.h" #include "network.h" #include "prefix.h" #include "routemap.h" #include "table.h" #include "stream.h" #include "memory.h" #include "zclient.h" #include "filter.h" #include "plist.h" #include "log.h" #include "nexthop.h" #include "nexthop_group.h" #include "pbr_nht.h" #include "pbr_map.h" #include "pbr_memory.h" #include "pbr_zebra.h" #include "pbr_debug.h" DEFINE_MTYPE_STATIC(PBRD, PBR_INTERFACE, "PBR Interface") /* Zebra structure to hold current status. */ struct zclient *zclient; struct pbr_interface *pbr_if_new(struct interface *ifp) { struct pbr_interface *pbr_ifp; zassert(ifp); zassert(!ifp->info); pbr_ifp = XCALLOC(MTYPE_PBR_INTERFACE, sizeof(*pbr_ifp)); ifp->info = pbr_ifp; return pbr_ifp; } /* Inteface addition message from zebra. */ static int interface_add(ZAPI_CALLBACK_ARGS) { struct interface *ifp; ifp = zebra_interface_add_read(zclient->ibuf, vrf_id); if (!ifp) return 0; DEBUGD(&pbr_dbg_zebra, "%s: %s", __PRETTY_FUNCTION__, ifp->name); if (!ifp->info) pbr_if_new(ifp); pbr_nht_nexthop_interface_update(ifp); return 0; } static int interface_delete(ZAPI_CALLBACK_ARGS) { struct interface *ifp; struct stream *s; s = zclient->ibuf; /* zebra_interface_state_read () updates interface structure in iflist */ ifp = zebra_interface_state_read(s, vrf_id); if (ifp == NULL) return 0; DEBUGD(&pbr_dbg_zebra, "%s: %s", __PRETTY_FUNCTION__, ifp->name); if_set_index(ifp, IFINDEX_INTERNAL); return 0; } static int interface_address_add(ZAPI_CALLBACK_ARGS) { struct connected *c; char buf[PREFIX_STRLEN]; c = zebra_interface_address_read(cmd, zclient->ibuf, vrf_id); DEBUGD(&pbr_dbg_zebra, "%s: %s added %s", __PRETTY_FUNCTION__, c ? c->ifp->name : "Unknown", c ? prefix2str(c->address, buf, sizeof(buf)) : "Unknown"); return 0; } static int interface_address_delete(ZAPI_CALLBACK_ARGS) { struct connected *c; char buf[PREFIX_STRLEN]; c = zebra_interface_address_read(cmd, zclient->ibuf, vrf_id); if (!c) return 0; DEBUGD(&pbr_dbg_zebra, "%s: %s deleted %s", __PRETTY_FUNCTION__, c->ifp->name, prefix2str(c->address, buf, sizeof(buf))); connected_free(c); return 0; } static int interface_state_up(ZAPI_CALLBACK_ARGS) { struct interface *ifp; ifp = zebra_interface_state_read(zclient->ibuf, vrf_id); DEBUGD(&pbr_dbg_zebra, "%s: %s is up", __PRETTY_FUNCTION__, ifp->name); pbr_nht_nexthop_interface_update(ifp); return 0; } static int interface_state_down(ZAPI_CALLBACK_ARGS) { struct interface *ifp; ifp = zebra_interface_state_read(zclient->ibuf, vrf_id); DEBUGD(&pbr_dbg_zebra, "%s: %s is down", __PRETTY_FUNCTION__, ifp->name); pbr_nht_nexthop_interface_update(ifp); return 0; } static int route_notify_owner(ZAPI_CALLBACK_ARGS) { struct prefix p; enum zapi_route_notify_owner note; uint32_t table_id; char buf[PREFIX_STRLEN]; if (!zapi_route_notify_decode(zclient->ibuf, &p, &table_id, ¬e)) return -1; prefix2str(&p, buf, sizeof(buf)); switch (note) { case ZAPI_ROUTE_FAIL_INSTALL: DEBUGD(&pbr_dbg_zebra, "%s: [%s] Route install failure for table: %u", __PRETTY_FUNCTION__, buf, table_id); break; case ZAPI_ROUTE_BETTER_ADMIN_WON: DEBUGD(&pbr_dbg_zebra, "%s: [%s] Route better admin distance won for table: %u", __PRETTY_FUNCTION__, buf, table_id); break; case ZAPI_ROUTE_INSTALLED: DEBUGD(&pbr_dbg_zebra, "%s: [%s] Route installed succeeded for table: %u", __PRETTY_FUNCTION__, buf, table_id); pbr_nht_route_installed_for_table(table_id); break; case ZAPI_ROUTE_REMOVED: DEBUGD(&pbr_dbg_zebra, "%s: [%s] Route Removed succeeded for table: %u", __PRETTY_FUNCTION__, buf, table_id); pbr_nht_route_removed_for_table(table_id); break; case ZAPI_ROUTE_REMOVE_FAIL: DEBUGD(&pbr_dbg_zebra, "%s: [%s] Route remove fail for table: %u", __PRETTY_FUNCTION__, buf, table_id); break; } return 0; } static int rule_notify_owner(ZAPI_CALLBACK_ARGS) { uint32_t seqno, priority, unique; enum zapi_rule_notify_owner note; struct pbr_map_sequence *pbrms; struct pbr_map_interface *pmi; ifindex_t ifi; uint64_t installed; if (!zapi_rule_notify_decode(zclient->ibuf, &seqno, &priority, &unique, &ifi, ¬e)) return -1; pmi = NULL; pbrms = pbrms_lookup_unique(unique, ifi, &pmi); if (!pbrms) { DEBUGD(&pbr_dbg_zebra, "%s: Failure to lookup pbrms based upon %u", __PRETTY_FUNCTION__, unique); return 0; } installed = 1 << pmi->install_bit; switch (note) { case ZAPI_RULE_FAIL_INSTALL: pbrms->installed &= ~installed; DEBUGD(&pbr_dbg_zebra, "%s: Received RULE_FAIL_INSTALL: %" PRIu64, __PRETTY_FUNCTION__, pbrms->installed); break; case ZAPI_RULE_INSTALLED: pbrms->installed |= installed; DEBUGD(&pbr_dbg_zebra, "%s: Received RULE_INSTALLED: %" PRIu64, __PRETTY_FUNCTION__, pbrms->installed); break; case ZAPI_RULE_FAIL_REMOVE: case ZAPI_RULE_REMOVED: pbrms->installed &= ~installed; DEBUGD(&pbr_dbg_zebra, "%s: Received RULE REMOVED: %" PRIu64, __PRETTY_FUNCTION__, pbrms->installed); break; } pbr_map_final_interface_deletion(pbrms->parent, pmi); return 0; } static void zebra_connected(struct zclient *zclient) { DEBUGD(&pbr_dbg_zebra, "%s: Registering for fun and profit", __PRETTY_FUNCTION__); zclient_send_reg_requests(zclient, VRF_DEFAULT); } static void route_add_helper(struct zapi_route *api, struct nexthop_group nhg, uint8_t install_afi) { struct zapi_nexthop *api_nh; char buf[PREFIX_STRLEN]; struct nexthop *nhop; int i; api->prefix.family = install_afi; DEBUGD(&pbr_dbg_zebra, "\tEncoding %s", prefix2str(&api->prefix, buf, sizeof(buf))); i = 0; for (ALL_NEXTHOPS(nhg, nhop)) { api_nh = &api->nexthops[i]; api_nh->vrf_id = nhop->vrf_id; api_nh->type = nhop->type; switch (nhop->type) { case NEXTHOP_TYPE_IPV4: api_nh->gate.ipv4 = nhop->gate.ipv4; break; case NEXTHOP_TYPE_IPV4_IFINDEX: api_nh->gate.ipv4 = nhop->gate.ipv4; api_nh->ifindex = nhop->ifindex; break; case NEXTHOP_TYPE_IFINDEX: api_nh->ifindex = nhop->ifindex; break; case NEXTHOP_TYPE_IPV6: memcpy(&api_nh->gate.ipv6, &nhop->gate.ipv6, 16); break; case NEXTHOP_TYPE_IPV6_IFINDEX: api_nh->ifindex = nhop->ifindex; memcpy(&api_nh->gate.ipv6, &nhop->gate.ipv6, 16); break; case NEXTHOP_TYPE_BLACKHOLE: api_nh->bh_type = nhop->bh_type; break; } i++; } api->nexthop_num = i; zclient_route_send(ZEBRA_ROUTE_ADD, zclient, api); } /* * This function assumes a default route is being * installed into the appropriate tableid */ void route_add(struct pbr_nexthop_group_cache *pnhgc, struct nexthop_group nhg, afi_t install_afi) { struct zapi_route api; DEBUGD(&pbr_dbg_zebra, "%s for Table: %d", __PRETTY_FUNCTION__, pnhgc->table_id); memset(&api, 0, sizeof(api)); api.vrf_id = VRF_DEFAULT; api.type = ZEBRA_ROUTE_PBR; api.safi = SAFI_UNICAST; /* * Sending a default route */ api.tableid = pnhgc->table_id; SET_FLAG(api.flags, ZEBRA_FLAG_ALLOW_RECURSION); SET_FLAG(api.message, ZAPI_MESSAGE_TABLEID); SET_FLAG(api.message, ZAPI_MESSAGE_NEXTHOP); switch (install_afi) { case AFI_MAX: route_add_helper(&api, nhg, AF_INET); route_add_helper(&api, nhg, AF_INET6); break; case AFI_IP: route_add_helper(&api, nhg, AF_INET); break; case AFI_IP6: route_add_helper(&api, nhg, AF_INET6); break; case AFI_L2VPN: DEBUGD(&pbr_dbg_zebra, "%s: Asked to install unsupported route type: L2VPN", __PRETTY_FUNCTION__); break; case AFI_UNSPEC: DEBUGD(&pbr_dbg_zebra, "%s: Asked to install unspecified route type", __PRETTY_FUNCTION__); break; } } /* * This function assumes a default route is being * removed from the appropriate tableid */ void route_delete(struct pbr_nexthop_group_cache *pnhgc, afi_t afi) { struct zapi_route api; DEBUGD(&pbr_dbg_zebra, "%s for Table: %d", __PRETTY_FUNCTION__, pnhgc->table_id); memset(&api, 0, sizeof(api)); api.vrf_id = VRF_DEFAULT; api.type = ZEBRA_ROUTE_PBR; api.safi = SAFI_UNICAST; api.tableid = pnhgc->table_id; SET_FLAG(api.message, ZAPI_MESSAGE_TABLEID); switch (afi) { case AFI_IP: api.prefix.family = AF_INET; zclient_route_send(ZEBRA_ROUTE_DELETE, zclient, &api); break; case AFI_IP6: api.prefix.family = AF_INET6; zclient_route_send(ZEBRA_ROUTE_DELETE, zclient, &api); break; case AFI_MAX: api.prefix.family = AF_INET; zclient_route_send(ZEBRA_ROUTE_DELETE, zclient, &api); api.prefix.family = AF_INET6; zclient_route_send(ZEBRA_ROUTE_DELETE, zclient, &api); break; case AFI_L2VPN: DEBUGD(&pbr_dbg_zebra, "%s: Asked to delete unsupported route type: L2VPN", __PRETTY_FUNCTION__); break; case AFI_UNSPEC: DEBUGD(&pbr_dbg_zebra, "%s: Asked to delete unspecified route type", __PRETTY_FUNCTION__); break; } } static int pbr_zebra_nexthop_update(ZAPI_CALLBACK_ARGS) { struct zapi_route nhr; char buf[PREFIX2STR_BUFFER]; uint32_t i; if (!zapi_nexthop_update_decode(zclient->ibuf, &nhr)) { zlog_warn("Failure to decode Nexthop update message"); return 0; } if (DEBUG_MODE_CHECK(&pbr_dbg_zebra, DEBUG_MODE_ALL)) { DEBUGD(&pbr_dbg_zebra, "%s: Received Nexthop update: %s", __PRETTY_FUNCTION__, prefix2str(&nhr.prefix, buf, sizeof(buf))); DEBUGD(&pbr_dbg_zebra, "%s: (\tNexthops(%u)", __PRETTY_FUNCTION__, nhr.nexthop_num); for (i = 0; i < nhr.nexthop_num; i++) { DEBUGD(&pbr_dbg_zebra, "%s: \tType: %d: vrf: %d, ifindex: %d gate: %s", __PRETTY_FUNCTION__, nhr.nexthops[i].type, nhr.nexthops[i].vrf_id, nhr.nexthops[i].ifindex, inet_ntoa(nhr.nexthops[i].gate.ipv4)); } } pbr_nht_nexthop_update(&nhr); return 1; } extern struct zebra_privs_t pbr_privs; void pbr_zebra_init(void) { struct zclient_options opt = { .receive_notify = true }; zclient = zclient_new(master, &opt); zclient_init(zclient, ZEBRA_ROUTE_PBR, 0, &pbr_privs); zclient->zebra_connected = zebra_connected; zclient->interface_add = interface_add; zclient->interface_delete = interface_delete; zclient->interface_up = interface_state_up; zclient->interface_down = interface_state_down; zclient->interface_address_add = interface_address_add; zclient->interface_address_delete = interface_address_delete; zclient->route_notify_owner = route_notify_owner; zclient->rule_notify_owner = rule_notify_owner; zclient->nexthop_update = pbr_zebra_nexthop_update; } void pbr_send_rnh(struct nexthop *nhop, bool reg) { uint32_t command; struct prefix p; command = (reg) ? ZEBRA_NEXTHOP_REGISTER : ZEBRA_NEXTHOP_UNREGISTER; memset(&p, 0, sizeof(p)); switch (nhop->type) { case NEXTHOP_TYPE_IFINDEX: case NEXTHOP_TYPE_BLACKHOLE: return; case NEXTHOP_TYPE_IPV4: case NEXTHOP_TYPE_IPV4_IFINDEX: p.family = AF_INET; p.u.prefix4.s_addr = nhop->gate.ipv4.s_addr; p.prefixlen = 32; break; case NEXTHOP_TYPE_IPV6: case NEXTHOP_TYPE_IPV6_IFINDEX: p.family = AF_INET6; memcpy(&p.u.prefix6, &nhop->gate.ipv6, 16); p.prefixlen = 128; break; } if (zclient_send_rnh(zclient, command, &p, false, nhop->vrf_id) < 0) { zlog_warn("%s: Failure to send nexthop to zebra", __PRETTY_FUNCTION__); } } static void pbr_encode_pbr_map_sequence_prefix(struct stream *s, struct prefix *p, unsigned char family) { struct prefix any; if (!p) { memset(&any, 0, sizeof(any)); any.family = family; p = &any; } stream_putc(s, p->family); stream_putc(s, p->prefixlen); stream_put(s, &p->u.prefix, prefix_blen(p)); } static void pbr_encode_pbr_map_sequence(struct stream *s, struct pbr_map_sequence *pbrms, struct interface *ifp) { unsigned char family; family = AF_INET; if (pbrms->family) family = pbrms->family; stream_putl(s, pbrms->seqno); stream_putl(s, pbrms->ruleno); stream_putl(s, pbrms->unique); pbr_encode_pbr_map_sequence_prefix(s, pbrms->src, family); stream_putw(s, 0); /* src port */ pbr_encode_pbr_map_sequence_prefix(s, pbrms->dst, family); stream_putw(s, 0); /* dst port */ stream_putl(s, pbrms->mark); if (pbrms->nhgrp_name) stream_putl(s, pbr_nht_get_table(pbrms->nhgrp_name)); else if (pbrms->nhg) stream_putl(s, pbr_nht_get_table(pbrms->internal_nhg_name)); stream_putl(s, ifp->ifindex); } void pbr_send_pbr_map(struct pbr_map_sequence *pbrms, struct pbr_map_interface *pmi, bool install) { struct pbr_map *pbrm = pbrms->parent; struct stream *s; uint64_t is_installed = (uint64_t)1 << pmi->install_bit; is_installed &= pbrms->installed; DEBUGD(&pbr_dbg_zebra, "%s: for %s %d(%" PRIu64 ")", __PRETTY_FUNCTION__, pbrm->name, install, is_installed); /* * If we are installed and asked to do so again * just return. If we are not installed and asked * and asked to delete just return; */ if (install && is_installed) return; if (!install && !is_installed) return; s = zclient->obuf; stream_reset(s); zclient_create_header(s, install ? ZEBRA_RULE_ADD : ZEBRA_RULE_DELETE, VRF_DEFAULT); /* * We are sending one item at a time at the moment */ stream_putl(s, 1); DEBUGD(&pbr_dbg_zebra, "%s: \t%s %s %d %s %u", __PRETTY_FUNCTION__, install ? "Installing" : "Deleting", pbrm->name, install, pmi->ifp->name, pmi->delete); pbr_encode_pbr_map_sequence(s, pbrms, pmi->ifp); stream_putw_at(s, 0, stream_get_endp(s)); zclient_send_message(zclient); } frr-7.2.1/pbrd/pbr_zebra.h0000644000000000000000000000263613610377563012271 00000000000000/* * Zebra connect library for PBR * Copyright (C) 2018 Cumulus Networks, Inc. * Donald Sharp * * FRR is free software; you can redistribute it and/or modify it * under the terms of the GNU General Public License as published by the * Free Software Foundation; either version 2, or (at your option) any * later version. * * FRR 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; see the file COPYING; if not, write to the Free Software * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA */ #ifndef __PBR_ZEBRA_H__ #define __PBR_ZEBRA_H__ struct pbr_interface { char mapname[100]; }; extern struct thread_master *master; extern void pbr_zebra_init(void); extern void route_add(struct pbr_nexthop_group_cache *pnhgc, struct nexthop_group nhg, afi_t install_afi); extern void route_delete(struct pbr_nexthop_group_cache *pnhgc, afi_t install_afi); extern void pbr_send_rnh(struct nexthop *nhop, bool reg); extern void pbr_send_pbr_map(struct pbr_map_sequence *pbrms, struct pbr_map_interface *pmi, bool install); extern struct pbr_interface *pbr_if_new(struct interface *ifp); #endif frr-7.2.1/pbrd/pbrd.conf.sample0000644000000000000000000000050513610377563013221 00000000000000! Sample pbrd configuration file ! ! A quick example of what a pbr configuration might look like ! ! log stdout ! ! nexthop-group TEST ! nexthop 4.5.6.7 ! nexthop 5.6.7.8 ! ! ! pbr-map BLUE seq 100 ! match dst-ip 9.9.9.0/24 ! match src-ip 10.10.10.0/24 ! set nexthop-group TEST ! ! ! int swp1 ! pbr-policy BLUE ! frr-7.2.1/pbrd/subdir.am0000644000000000000000000000145513610377563011757 00000000000000# # pbrd # if PBRD noinst_LIBRARIES += pbrd/libpbr.a sbin_PROGRAMS += pbrd/pbrd dist_examples_DATA += pbrd/pbrd.conf.sample vtysh_scan += \ $(top_srcdir)/pbrd/pbr_vty.c \ $(top_srcdir)/pbrd/pbr_debug.c \ # end man8 += $(MANBUILD)/frr-pbrd.8 endif pbrd_libpbr_a_SOURCES = \ pbrd/pbr_zebra.c \ pbrd/pbr_vty.c \ pbrd/pbr_map.c \ pbrd/pbr_memory.c \ pbrd/pbr_nht.c \ pbrd/pbr_debug.c \ # end noinst_HEADERS += \ pbrd/pbr_map.h \ pbrd/pbr_memory.h \ pbrd/pbr_nht.h \ pbrd/pbr_vty.h \ pbrd/pbr_zebra.h \ pbrd/pbr_debug.h \ # end pbrd/pbr_vty_clippy.c: $(CLIPPY_DEPS) pbrd/pbr_vty.$(OBJEXT): pbrd/pbr_vty_clippy.c pbrd/pbr_debug_clippy.c: $(CLIPPY_DEPS) pbrd/pbr_debug.$(OBJEXT): pbrd/pbr_debug_clippy.c pbrd_pbrd_SOURCES = pbrd/pbr_main.c pbrd_pbrd_LDADD = pbrd/libpbr.a lib/libfrr.la $(LIBCAP) frr-7.2.1/pimd/0000755000000000000000000000000013610377563010225 500000000000000frr-7.2.1/pimd/Makefile0000644000000000000000000000021713610377563011605 00000000000000all: ALWAYS @$(MAKE) -s -C .. pimd/pimd %: ALWAYS @$(MAKE) -s -C .. pimd/$@ Makefile: #nothing ALWAYS: .PHONY: ALWAYS makefiles .SUFFIXES: frr-7.2.1/pimd/mtracebis.c0000644000000000000000000003142513610377563012267 00000000000000/* * Multicast Traceroute for FRRouting * Copyright (C) 2018 Mladen Sablic * * This program is free software; you can redistribute it and/or modify * it under the terms of the 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; see the file COPYING; if not, write to the Free Software * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA */ #include #ifdef __linux__ #include "pim_igmp_mtrace.h" #include "checksum.h" #include "prefix.h" #include "mtracebis_routeget.h" #include #include #include #include #include #include #include #include #include #include #include #include #include #include #define MTRACEBIS_VERSION "0.1" #define MTRACE_TIMEOUT (5) #define IP_HDR_LEN (sizeof(struct ip)) #define IP_RA_LEN (4) #define MTRACE_BUF_LEN (MTRACE_HDR_SIZE + (MTRACE_MAX_HOPS * MTRACE_RSP_SIZE)) #define IP_AND_MTRACE_BUF_LEN (IP_HDR_LEN + IP_RA_LEN + MTRACE_BUF_LEN) static const char *progname; static void usage(void) { fprintf(stderr, "Usage : %s []\n", progname); } static void version(void) { fprintf(stderr, "%s %s\n", progname, MTRACEBIS_VERSION); } static void print_host(struct in_addr addr) { struct hostent *h; h = gethostbyaddr(&addr, sizeof(addr), AF_INET); if (h == NULL) printf("?"); else printf("%s", h->h_name); printf(" (%s) ", inet_ntoa(addr)); } static void print_line_no(int i) { printf("%3d ", -i); } static const char *rtg_proto_str(enum mtrace_rtg_proto proto) { static char buf[80]; buf[0] = '\0'; switch (proto) { case MTRACE_RTG_PROTO_DVMRP: return "DVMRP"; case MTRACE_RTG_PROTO_MOSPF: return "MOSPF"; case MTRACE_RTG_PROTO_PIM: return "PIM"; case MTRACE_RTG_PROTO_CBT: return "CBT"; case MTRACE_RTG_PROTO_PIM_SPECIAL: return "PIM special"; case MTRACE_RTG_PROTO_PIM_STATIC: return "PIM static"; case MTRACE_RTG_PROTO_DVMRP_STATIC: return "DVMRP static"; case MTRACE_RTG_PROTO_PIM_MBGP: return "PIM MBGP"; case MTRACE_RTG_PROTO_CBT_SPECIAL: return "CBT special"; case MTRACE_RTG_PROTO_CBT_STATIC: return "CBT static"; case MTRACE_RTG_PROTO_PIM_ASSERT: return "PIM assert"; default: sprintf(buf, "unknown protocol (%d)", proto); return buf; } } static void print_rtg_proto(uint32_t rtg_proto) { printf("%s", rtg_proto_str(rtg_proto)); } static void print_fwd_ttl(uint32_t fwd_ttl) { printf("thresh^ %d", fwd_ttl); } static const char *fwd_code_str(enum mtrace_fwd_code code) { static char buf[80]; buf[0] = '\0'; switch (code) { case MTRACE_FWD_CODE_NO_ERROR: return "no error"; case MTRACE_FWD_CODE_WRONG_IF: return "wrong interface"; case MTRACE_FWD_CODE_PRUNE_SENT: return "prune sent"; case MTRACE_FWD_CODE_PRUNE_RCVD: return "prune received"; case MTRACE_FWD_CODE_SCOPED: return "scoped"; case MTRACE_FWD_CODE_NO_ROUTE: return "no route"; case MTRACE_FWD_CODE_WRONG_LAST_HOP: return "wrong last hop"; case MTRACE_FWD_CODE_NOT_FORWARDING: return "not forwarding"; case MTRACE_FWD_CODE_REACHED_RP: return "reached RP"; case MTRACE_FWD_CODE_RPF_IF: return "RPF interface"; case MTRACE_FWD_CODE_NO_MULTICAST: return "no multicast"; case MTRACE_FWD_CODE_INFO_HIDDEN: return "info hidden"; case MTRACE_FWD_CODE_NO_SPACE: return "no space"; case MTRACE_FWD_CODE_OLD_ROUTER: return "old router"; case MTRACE_FWD_CODE_ADMIN_PROHIB: return "admin. prohib."; default: sprintf(buf, "unknown fwd. code (%d)", code); return buf; } } static void print_fwd_code(uint32_t fwd_code) { printf("%s", fwd_code_str(fwd_code)); } static void print_rsp(struct igmp_mtrace_rsp *rsp) { print_host(rsp->outgoing); if (rsp->fwd_code == 0 || rsp->fwd_code == MTRACE_FWD_CODE_REACHED_RP) { print_rtg_proto(rsp->rtg_proto); printf(" "); if (rsp->fwd_code == MTRACE_FWD_CODE_REACHED_RP) printf("(RP) "); if (rsp->rtg_proto == MTRACE_RTG_PROTO_PIM) { switch (rsp->src_mask) { case MTRACE_SRC_MASK_GROUP: printf("(*,G) "); break; case MTRACE_SRC_MASK_SOURCE: printf("(S,G) "); break; } } print_fwd_ttl(rsp->fwd_ttl); } else { print_fwd_code(rsp->fwd_code); } printf("\n"); } static void print_dest(struct igmp_mtrace *mtrace) { print_line_no(0); print_host(mtrace->dst_addr); printf("\n"); } static void print_summary(struct igmp_mtrace *mtrace, int hops, long msec) { int i; int t = 0; for (i = 0; i < hops; i++) t += mtrace->rsp[i].fwd_ttl; printf("Round trip time %ld ms; total ttl of %d required.\n", msec, t); } static void print_responses(struct igmp_mtrace *mtrace, int hops, long msec) { int i; print_dest(mtrace); for (i = 0; i < hops; i++) { print_line_no(i + 1); print_rsp(&mtrace->rsp[i]); } print_summary(mtrace, hops, msec); } static int send_query(int fd, struct in_addr to_addr, struct igmp_mtrace *mtrace) { struct sockaddr_in to; socklen_t tolen; int sent; memset(&to, 0, sizeof(to)); to.sin_family = AF_INET; to.sin_addr = to_addr; tolen = sizeof(to); sent = sendto(fd, (char *)mtrace, sizeof(*mtrace), MSG_DONTWAIT, (struct sockaddr *)&to, tolen); if (sent < 1) return -1; return 0; } static void print_query(struct igmp_mtrace *mtrace) { char src_str[INET_ADDRSTRLEN]; char dst_str[INET_ADDRSTRLEN]; char grp_str[INET_ADDRSTRLEN]; printf("* Mtrace from %s to %s via group %s\n", inet_ntop(AF_INET, &mtrace->src_addr, src_str, sizeof(src_str)), inet_ntop(AF_INET, &mtrace->dst_addr, dst_str, sizeof(dst_str)), inet_ntop(AF_INET, &mtrace->grp_addr, grp_str, sizeof(grp_str))); } static int recv_response(int fd, int *hops, struct igmp_mtrace *mtracer) { int recvd; char mtrace_buf[IP_AND_MTRACE_BUF_LEN]; struct ip *ip; struct igmp_mtrace *mtrace; int mtrace_len; int responses; unsigned short sum; size_t mtrace_off; size_t ip_len; recvd = recvfrom(fd, mtrace_buf, IP_AND_MTRACE_BUF_LEN, 0, NULL, 0); if (recvd < 1) { fprintf(stderr, "recvfrom error: %s\n", strerror(errno)); return -1; } if (recvd < (int)sizeof(struct ip)) { fprintf(stderr, "no ip header\n"); return -1; } ip = (struct ip *)mtrace_buf; if (ip->ip_v != 4) { fprintf(stderr, "IP not version 4\n"); return -1; } sum = ip->ip_sum; ip->ip_sum = 0; if (sum != in_cksum(ip, ip->ip_hl * 4)) return -1; /* Header overflow check */ mtrace_off = 4 * ip->ip_hl; if (mtrace_off > MTRACE_BUF_LEN) return -1; /* Underflow/overflow check */ ip_len = ntohs(ip->ip_len); if (ip_len < mtrace_off || ip_len < MTRACE_HDR_SIZE || ip_len > MTRACE_BUF_LEN) return -1; mtrace_len = ip_len - mtrace_off; mtrace = (struct igmp_mtrace *)(mtrace_buf + mtrace_off); sum = mtrace->checksum; mtrace->checksum = 0; if (sum != in_cksum(mtrace, mtrace_len)) { fprintf(stderr, "mtrace checksum wrong\n"); return -1; } if (mtrace->type != PIM_IGMP_MTRACE_RESPONSE) return -1; responses = mtrace_len - sizeof(struct igmp_mtrace); responses /= sizeof(struct igmp_mtrace_rsp); if (responses > MTRACE_MAX_HOPS) { fprintf(stderr, "mtrace too large\n"); return -1; } if (hops) *hops = responses; if (mtracer) memcpy(mtracer, mtrace, mtrace_len); return 0; } static int wait_for_response(int fd, int *hops, struct igmp_mtrace *mtrace, long *ret_msec) { fd_set readfds; struct timeval timeout; int ret; long msec, rmsec, tmsec; FD_ZERO(&readfds); FD_SET(fd, &readfds); memset(&timeout, 0, sizeof(timeout)); timeout.tv_sec = MTRACE_TIMEOUT; tmsec = timeout.tv_sec * 1000 + timeout.tv_usec / 1000; do { ret = select(fd + 1, &readfds, NULL, NULL, &timeout); if (ret <= 0) return ret; rmsec = timeout.tv_sec * 1000 + timeout.tv_usec / 1000; msec = tmsec - rmsec; } while (recv_response(fd, hops, mtrace) != 0); if (ret_msec) *ret_msec = msec; return ret; } static bool check_end(struct igmp_mtrace *mtrace, int hops) { return mtrace->src_addr.s_addr == mtrace->rsp[hops - 1].prev_hop.s_addr; } int main(int argc, char *const argv[]) { struct in_addr mc_source; struct in_addr mc_group; struct in_addr iface_addr; struct in_addr gw_addr; struct in_addr mtrace_addr; struct igmp_mtrace mtrace; struct igmp_mtrace *mtracep; int hops = 255; int rhops; int maxhops = 255; int perhop = 3; int ifindex; int unicast = 1; int ttl = 64; int fd = -1; int ret = -1; int c; long msec; int i, j; char ifname[IF_NAMESIZE]; char mbuf[MTRACE_BUF_LEN]; bool not_group; mtrace_addr.s_addr = inet_addr("224.0.1.32"); uid_t uid = getuid(); if (uid != 0) { printf("must run as root\n"); exit(EXIT_FAILURE); } if (argc <= 0) progname = "mtracebis"; else progname = argv[0]; if (argc != 2 && argc != 3) { usage(); exit(EXIT_FAILURE); } while (1) { static struct option long_options[] = { {"help", no_argument, 0, 'h'}, {"version", no_argument, 0, 'v'}, {0, 0, 0, 0}}; int option_index = 0; c = getopt_long(argc, argv, "vh", long_options, &option_index); if (c == -1) break; switch (c) { case 'h': usage(); exit(0); case 'v': version(); exit(0); default: usage(); exit(EXIT_FAILURE); } } if (inet_pton(AF_INET, argv[1], &mc_source) != 1) { usage(); fprintf(stderr, "%s: %s is not a valid IPv4 address\n", argv[0], argv[1]); exit(EXIT_FAILURE); } mc_group.s_addr = 0; not_group = false; if (argc == 3) { if (inet_pton(AF_INET, argv[2], &mc_group) != 1) not_group = true; if (!not_group && !IPV4_CLASS_DE(ntohl(mc_group.s_addr))) not_group = true; } if (not_group) { usage(); fprintf(stderr, "%s: %s is not a valid IPv4 group address\n", argv[0], argv[2]); exit(EXIT_FAILURE); } ifindex = routeget(mc_source, &iface_addr, &gw_addr); if (ifindex < 0) { fprintf(stderr, "%s: failed to get route to source %s\n", argv[0], argv[1]); exit(EXIT_FAILURE); } if (if_indextoname(ifindex, ifname) == NULL) { fprintf(stderr, "%s: if_indextoname error: %s\n", argv[0], strerror(errno)); exit(EXIT_FAILURE); } /* zero mtrace struct */ memset((char *)&mtrace, 0, sizeof(mtrace)); /* set up query */ mtrace.type = PIM_IGMP_MTRACE_QUERY_REQUEST; mtrace.hops = hops; mtrace.checksum = 0; mtrace.grp_addr = mc_group; mtrace.src_addr = mc_source; mtrace.dst_addr = iface_addr; mtrace.rsp_addr = unicast ? iface_addr : mtrace_addr; mtrace.rsp_ttl = ttl; mtrace.qry_id = 0xffffff & time(NULL); mtrace.checksum = in_cksum(&mtrace, sizeof(mtrace)); fd = socket(AF_INET, SOCK_RAW, IPPROTO_IGMP); if (fd < 1) { fprintf(stderr, "%s: socket error: %s\n", argv[0], strerror(errno)); exit(EXIT_FAILURE); } ret = setsockopt(fd, SOL_SOCKET, SO_BINDTODEVICE, ifname, strlen(ifname)); if (ret < 0) { fprintf(stderr, "%s: setsockopt error: %s\n", argv[0], strerror(errno)); ret = EXIT_FAILURE; goto close_fd; } print_query(&mtrace); if (send_query(fd, gw_addr, &mtrace) < 0) { fprintf(stderr, "%s: sendto error: %s\n", argv[0], strerror(errno)); ret = EXIT_FAILURE; goto close_fd; } printf("Querying full reverse path...\n"); mtracep = (struct igmp_mtrace *)mbuf; ret = wait_for_response(fd, &rhops, mtracep, &msec); if (ret > 0) { print_responses(mtracep, rhops, msec); ret = 0; goto close_fd; } if (ret < 0) { fprintf(stderr, "%s: select error: %s\n", argv[0], strerror(errno)); ret = EXIT_FAILURE; goto close_fd; } printf(" * "); printf("switching to hop-by-hop:\n"); print_dest(&mtrace); for (i = 1; i < maxhops; i++) { print_line_no(i); mtrace.hops = i; for (j = 0; j < perhop; j++) { mtrace.qry_id++; mtrace.checksum = 0; mtrace.checksum = in_cksum(&mtrace, sizeof(mtrace)); if (send_query(fd, gw_addr, &mtrace) < 0) { fprintf(stderr, "%s: sendto error: %s\n", argv[0], strerror(errno)); ret = EXIT_FAILURE; goto close_fd; } ret = wait_for_response(fd, &rhops, mtracep, &msec); if (ret > 0) { if (check_end(mtracep, rhops)) { print_rsp(&mtracep->rsp[rhops - 1]); print_summary(mtracep, rhops, msec); ret = 0; goto close_fd; } if (i > rhops) { printf(" * ...giving up.\n"); ret = 0; goto close_fd; } print_rsp(&mtracep->rsp[rhops - 1]); break; } printf(" *"); } if (ret <= 0) printf("\n"); } ret = 0; close_fd: close(fd); exit(ret); } #else /* __linux__ */ #include #include int main(int argc, char *argv[]) { printf("%s implemented only for GNU/Linux\n", argv[0]); exit(0); } #endif /* __linux__ */ frr-7.2.1/pimd/mtracebis_netlink.c0000644000000000000000000004026413610377563014014 00000000000000/* * libnetlink.c RTnetlink service routines. * * This program is free software; you can redistribute it and/or * modify it under the terms of the GNU General Public License * as published by the Free Software Foundation; either version * 2 of the License, or (at your option) any later version. * * Authors: Alexey Kuznetsov, * */ #ifdef HAVE_CONFIG_H #include "config.h" #endif #ifdef __linux__ #include #include #include #include #include #include #include #include #include #include #include #include #include #include "mtracebis_netlink.h" int rcvbuf = 1024 * 1024; void rtnl_close(struct rtnl_handle *rth) { if (rth->fd >= 0) { close(rth->fd); rth->fd = -1; } } int rtnl_open_byproto(struct rtnl_handle *rth, unsigned subscriptions, int protocol) { socklen_t addr_len; int sndbuf = 32768; memset(rth, 0, sizeof(*rth)); rth->fd = socket(AF_NETLINK, SOCK_RAW, protocol); if (rth->fd < 0) { perror("Cannot open netlink socket"); return -1; } if (setsockopt(rth->fd, SOL_SOCKET, SO_SNDBUF, &sndbuf, sizeof(sndbuf)) < 0) { perror("SO_SNDBUF"); return -1; } if (setsockopt(rth->fd, SOL_SOCKET, SO_RCVBUF, &rcvbuf, sizeof(rcvbuf)) < 0) { perror("SO_RCVBUF"); return -1; } memset(&rth->local, 0, sizeof(rth->local)); rth->local.nl_family = AF_NETLINK; rth->local.nl_groups = subscriptions; if (bind(rth->fd, (struct sockaddr *)&rth->local, sizeof(rth->local)) < 0) { perror("Cannot bind netlink socket"); return -1; } addr_len = sizeof(rth->local); if (getsockname(rth->fd, (struct sockaddr *)&rth->local, &addr_len) < 0) { perror("Cannot getsockname"); return -1; } if (addr_len != sizeof(rth->local)) { fprintf(stderr, "Wrong address length %d\n", addr_len); return -1; } if (rth->local.nl_family != AF_NETLINK) { fprintf(stderr, "Wrong address family %d\n", rth->local.nl_family); return -1; } rth->seq = time(NULL); return 0; } int rtnl_open(struct rtnl_handle *rth, unsigned subscriptions) { return rtnl_open_byproto(rth, subscriptions, NETLINK_ROUTE); } int rtnl_wilddump_request(struct rtnl_handle *rth, int family, int type) { struct { struct nlmsghdr nlh; struct rtgenmsg g; } req; memset(&req, 0, sizeof(req)); req.nlh.nlmsg_len = sizeof(req); req.nlh.nlmsg_type = type; req.nlh.nlmsg_flags = NLM_F_ROOT | NLM_F_MATCH | NLM_F_REQUEST; req.nlh.nlmsg_pid = 0; req.nlh.nlmsg_seq = rth->dump = ++rth->seq; req.g.rtgen_family = family; return send(rth->fd, (void *)&req, sizeof(req), 0); } int rtnl_send(struct rtnl_handle *rth, const char *buf, int len) { return send(rth->fd, buf, len, 0); } int rtnl_send_check(struct rtnl_handle *rth, const char *buf, int len) { struct nlmsghdr *h; int status; char resp[1024]; status = send(rth->fd, buf, len, 0); if (status < 0) return status; /* Check for immediate errors */ status = recv(rth->fd, resp, sizeof(resp), MSG_DONTWAIT | MSG_PEEK); if (status < 0) { if (errno == EAGAIN) return 0; return -1; } for (h = (struct nlmsghdr *)resp; NLMSG_OK(h, (uint32_t)status); h = NLMSG_NEXT(h, status)) { if (h->nlmsg_type == NLMSG_ERROR) { struct nlmsgerr *err = (struct nlmsgerr *)NLMSG_DATA(h); if (h->nlmsg_len < NLMSG_LENGTH(sizeof(struct nlmsgerr))) fprintf(stderr, "ERROR truncated\n"); else errno = -err->error; return -1; } } return 0; } int rtnl_dump_request(struct rtnl_handle *rth, int type, void *req, int len) { struct nlmsghdr nlh; struct sockaddr_nl nladdr; struct iovec iov[2] = {{.iov_base = &nlh, .iov_len = sizeof(nlh)}, {.iov_base = req, .iov_len = len}}; struct msghdr msg = { .msg_name = &nladdr, .msg_namelen = sizeof(nladdr), .msg_iov = iov, .msg_iovlen = 2, }; memset(&nladdr, 0, sizeof(nladdr)); nladdr.nl_family = AF_NETLINK; nlh.nlmsg_len = NLMSG_LENGTH(len); nlh.nlmsg_type = type; nlh.nlmsg_flags = NLM_F_ROOT | NLM_F_MATCH | NLM_F_REQUEST; nlh.nlmsg_pid = 0; nlh.nlmsg_seq = rth->dump = ++rth->seq; return sendmsg(rth->fd, &msg, 0); } int rtnl_dump_filter_l(struct rtnl_handle *rth, const struct rtnl_dump_filter_arg *arg) { struct sockaddr_nl nladdr; struct iovec iov; struct msghdr msg = { .msg_name = &nladdr, .msg_namelen = sizeof(nladdr), .msg_iov = &iov, .msg_iovlen = 1, }; char buf[16384]; iov.iov_base = buf; while (1) { int status; const struct rtnl_dump_filter_arg *a; int found_done = 0; int msglen = 0; iov.iov_len = sizeof(buf); status = recvmsg(rth->fd, &msg, 0); if (status < 0) { if (errno == EINTR || errno == EAGAIN) continue; fprintf(stderr, "netlink receive error %s (%d)\n", strerror(errno), errno); return -1; } if (status == 0) { fprintf(stderr, "EOF on netlink\n"); return -1; } for (a = arg; a->filter; a++) { struct nlmsghdr *h = (struct nlmsghdr *)buf; msglen = status; while (NLMSG_OK(h, (uint32_t)msglen)) { int err; if (nladdr.nl_pid != 0 || h->nlmsg_pid != rth->local.nl_pid || h->nlmsg_seq != rth->dump) { if (a->junk) { err = a->junk(&nladdr, h, a->arg2); if (err < 0) return err; } goto skip_it; } if (h->nlmsg_type == NLMSG_DONE) { found_done = 1; break; /* process next filter */ } if (h->nlmsg_type == NLMSG_ERROR) { struct nlmsgerr *merr = (struct nlmsgerr *)NLMSG_DATA( h); if (h->nlmsg_len < NLMSG_LENGTH(sizeof( struct nlmsgerr))) { fprintf(stderr, "ERROR truncated\n"); } else { errno = -merr->error; perror("RTNETLINK answers"); } return -1; } err = a->filter(&nladdr, h, a->arg1); if (err < 0) return err; skip_it: h = NLMSG_NEXT(h, msglen); } } if (found_done) return 0; if (msg.msg_flags & MSG_TRUNC) { fprintf(stderr, "Message truncated\n"); continue; } if (msglen) { fprintf(stderr, "!!!Remnant of size %d\n", msglen); exit(1); } } } int rtnl_dump_filter(struct rtnl_handle *rth, rtnl_filter_t filter, void *arg1, rtnl_filter_t junk, void *arg2) { const struct rtnl_dump_filter_arg a[2] = { {.filter = filter, .arg1 = arg1, .junk = junk, .arg2 = arg2}, {.filter = NULL, .arg1 = NULL, .junk = NULL, .arg2 = NULL}}; return rtnl_dump_filter_l(rth, a); } int rtnl_talk(struct rtnl_handle *rtnl, struct nlmsghdr *n, pid_t peer, unsigned groups, struct nlmsghdr *answer, rtnl_filter_t junk, void *jarg) { int status; unsigned seq; struct nlmsghdr *h; struct sockaddr_nl nladdr; struct iovec iov = {.iov_base = (void *)n, .iov_len = n->nlmsg_len}; struct msghdr msg = { .msg_name = &nladdr, .msg_namelen = sizeof(nladdr), .msg_iov = &iov, .msg_iovlen = 1, }; char buf[16384]; memset(&nladdr, 0, sizeof(nladdr)); nladdr.nl_family = AF_NETLINK; nladdr.nl_pid = peer; nladdr.nl_groups = groups; n->nlmsg_seq = seq = ++rtnl->seq; if (answer == NULL) n->nlmsg_flags |= NLM_F_ACK; status = sendmsg(rtnl->fd, &msg, 0); if (status < 0) { perror("Cannot talk to rtnetlink"); return -1; } memset(buf, 0, sizeof(buf)); iov.iov_base = buf; while (1) { iov.iov_len = sizeof(buf); status = recvmsg(rtnl->fd, &msg, 0); if (status < 0) { if (errno == EINTR || errno == EAGAIN) continue; fprintf(stderr, "netlink receive error %s (%d)\n", strerror(errno), errno); return -1; } if (status == 0) { fprintf(stderr, "EOF on netlink\n"); return -1; } if (msg.msg_namelen != sizeof(nladdr)) { fprintf(stderr, "sender address length == %d\n", msg.msg_namelen); exit(1); } for (h = (struct nlmsghdr *)buf; status >= (int)sizeof(*h);) { int err; int len = h->nlmsg_len; int l = len - sizeof(*h); if (l < 0 || len > status) { if (msg.msg_flags & MSG_TRUNC) { fprintf(stderr, "Truncated message\n"); return -1; } fprintf(stderr, "!!!malformed message: len=%d\n", len); exit(1); } if ((int)nladdr.nl_pid != peer || h->nlmsg_pid != rtnl->local.nl_pid || h->nlmsg_seq != seq) { if (junk) { err = junk(&nladdr, h, jarg); if (err < 0) return err; } /* Don't forget to skip that message. */ status -= NLMSG_ALIGN(len); h = (struct nlmsghdr *)((char *)h + NLMSG_ALIGN(len)); continue; } if (h->nlmsg_type == NLMSG_ERROR) { struct nlmsgerr *merr = (struct nlmsgerr *)NLMSG_DATA(h); if (l < (int)sizeof(struct nlmsgerr)) { fprintf(stderr, "ERROR truncated\n"); } else { errno = -merr->error; if (errno == 0) { if (answer) memcpy(answer, h, h->nlmsg_len); return 0; } perror("RTNETLINK answers"); } return -1; } if (answer) { memcpy(answer, h, h->nlmsg_len); return 0; } fprintf(stderr, "Unexpected reply!!!\n"); status -= NLMSG_ALIGN(len); h = (struct nlmsghdr *)((char *)h + NLMSG_ALIGN(len)); } if (msg.msg_flags & MSG_TRUNC) { fprintf(stderr, "Message truncated\n"); continue; } if (status) { fprintf(stderr, "!!!Remnant of size %d\n", status); exit(1); } } } int rtnl_listen(struct rtnl_handle *rtnl, rtnl_filter_t handler, void *jarg) { int status; struct nlmsghdr *h; struct sockaddr_nl nladdr; struct iovec iov; struct msghdr msg = { .msg_name = &nladdr, .msg_namelen = sizeof(nladdr), .msg_iov = &iov, .msg_iovlen = 1, }; char buf[8192]; memset(&nladdr, 0, sizeof(nladdr)); nladdr.nl_family = AF_NETLINK; nladdr.nl_pid = 0; nladdr.nl_groups = 0; iov.iov_base = buf; while (1) { iov.iov_len = sizeof(buf); status = recvmsg(rtnl->fd, &msg, 0); if (status < 0) { if (errno == EINTR || errno == EAGAIN) continue; fprintf(stderr, "netlink receive error %s (%d)\n", strerror(errno), errno); if (errno == ENOBUFS) continue; return -1; } if (status == 0) { fprintf(stderr, "EOF on netlink\n"); return -1; } if (msg.msg_namelen != sizeof(nladdr)) { fprintf(stderr, "Sender address length == %d\n", msg.msg_namelen); exit(1); } for (h = (struct nlmsghdr *)buf; status >= (int)sizeof(*h);) { int err; int len = h->nlmsg_len; int l = len - sizeof(*h); if (l < 0 || len > status) { if (msg.msg_flags & MSG_TRUNC) { fprintf(stderr, "Truncated message\n"); return -1; } fprintf(stderr, "!!!malformed message: len=%d\n", len); exit(1); } err = handler(&nladdr, h, jarg); if (err < 0) return err; status -= NLMSG_ALIGN(len); h = (struct nlmsghdr *)((char *)h + NLMSG_ALIGN(len)); } if (msg.msg_flags & MSG_TRUNC) { fprintf(stderr, "Message truncated\n"); continue; } if (status) { fprintf(stderr, "!!!Remnant of size %d\n", status); exit(1); } } } int rtnl_from_file(FILE *rtnl, rtnl_filter_t handler, void *jarg) { struct sockaddr_nl nladdr; char buf[8192]; struct nlmsghdr *h = (void *)buf; memset(&nladdr, 0, sizeof(nladdr)); nladdr.nl_family = AF_NETLINK; nladdr.nl_pid = 0; nladdr.nl_groups = 0; while (1) { int err; size_t l, rl, arl; rl = sizeof(*h); arl = fread(&buf, 1, rl, rtnl); if (arl != rl) { if (arl == 0) return 0; if (ferror(rtnl)) fprintf(stderr, "%s: header read failed\n", __func__); else fprintf(stderr, "%s: truncated header\n", __func__); return -1; } l = h->nlmsg_len > rl ? h->nlmsg_len - rl : 0; if (l == 0 || (l + (size_t)NLMSG_HDRLEN) > sizeof(buf)) { fprintf(stderr, "%s: malformed message: len=%zu @%lu\n", __func__, (size_t)h->nlmsg_len, ftell(rtnl)); return -1; } rl = NLMSG_ALIGN(l); arl = fread(NLMSG_DATA(h), 1, rl, rtnl); if (arl != rl) { if (ferror(rtnl)) fprintf(stderr, "%s: msg read failed\n", __func__); else fprintf(stderr, "%s: truncated message\n", __func__); return -1; } err = handler(&nladdr, h, jarg); if (err < 0) return err; } } int addattr32(struct nlmsghdr *n, int maxlen, int type, __u32 data) { int len = RTA_LENGTH(4); struct rtattr *rta; if ((int)(NLMSG_ALIGN(n->nlmsg_len) + len) > maxlen) { fprintf(stderr, "addattr32: Error! max allowed bound %d exceeded\n", maxlen); return -1; } rta = NLMSG_TAIL(n); rta->rta_type = type; rta->rta_len = len; memcpy(RTA_DATA(rta), &data, 4); n->nlmsg_len = NLMSG_ALIGN(n->nlmsg_len) + len; return 0; } int addattr_l(struct nlmsghdr *n, int maxlen, int type, const void *data, int alen) { int len = RTA_LENGTH(alen); struct rtattr *rta; if ((int)(NLMSG_ALIGN(n->nlmsg_len) + RTA_ALIGN(len)) > maxlen) { fprintf(stderr, "addattr_l ERROR: message exceeded bound of %d\n", maxlen); return -1; } rta = NLMSG_TAIL(n); rta->rta_type = type; rta->rta_len = len; if (data) memcpy(RTA_DATA(rta), data, alen); else assert(alen == 0); n->nlmsg_len = NLMSG_ALIGN(n->nlmsg_len) + RTA_ALIGN(len); return 0; } int addraw_l(struct nlmsghdr *n, int maxlen, const void *data, int len) { if ((int)(NLMSG_ALIGN(n->nlmsg_len) + NLMSG_ALIGN(len)) > maxlen) { fprintf(stderr, "addraw_l ERROR: message exceeded bound of %d\n", maxlen); return -1; } memcpy(NLMSG_TAIL(n), data, len); memset((uint8_t *)NLMSG_TAIL(n) + len, 0, NLMSG_ALIGN(len) - len); n->nlmsg_len = NLMSG_ALIGN(n->nlmsg_len) + NLMSG_ALIGN(len); return 0; } struct rtattr *addattr_nest(struct nlmsghdr *n, int maxlen, int type) { struct rtattr *nest = NLMSG_TAIL(n); addattr_l(n, maxlen, type, NULL, 0); return nest; } int addattr_nest_end(struct nlmsghdr *n, struct rtattr *nest) { nest->rta_len = (uint8_t *)NLMSG_TAIL(n) - (uint8_t *)nest; return n->nlmsg_len; } struct rtattr *addattr_nest_compat(struct nlmsghdr *n, int maxlen, int type, const void *data, int len) { struct rtattr *start = NLMSG_TAIL(n); addattr_l(n, maxlen, type, data, len); addattr_nest(n, maxlen, type); return start; } int addattr_nest_compat_end(struct nlmsghdr *n, struct rtattr *start) { struct rtattr *nest = start + NLMSG_ALIGN(start->rta_len); start->rta_len = (uint8_t *)NLMSG_TAIL(n) - (uint8_t *)start; addattr_nest_end(n, nest); return n->nlmsg_len; } int rta_addattr32(struct rtattr *rta, int maxlen, int type, __u32 data) { int len = RTA_LENGTH(4); struct rtattr *subrta; if ((int)(RTA_ALIGN(rta->rta_len) + len) > maxlen) { fprintf(stderr, "rta_addattr32: Error! max allowed bound %d exceeded\n", maxlen); return -1; } subrta = (struct rtattr *)(((char *)rta) + RTA_ALIGN(rta->rta_len)); subrta->rta_type = type; subrta->rta_len = len; memcpy(RTA_DATA(subrta), &data, 4); rta->rta_len = NLMSG_ALIGN(rta->rta_len) + len; return 0; } int rta_addattr_l(struct rtattr *rta, int maxlen, int type, const void *data, int alen) { struct rtattr *subrta; int len = RTA_LENGTH(alen); if ((int)(RTA_ALIGN(rta->rta_len) + RTA_ALIGN(len)) > maxlen) { fprintf(stderr, "rta_addattr_l: Error! max allowed bound %d exceeded\n", maxlen); return -1; } subrta = (struct rtattr *)(((char *)rta) + RTA_ALIGN(rta->rta_len)); subrta->rta_type = type; subrta->rta_len = len; memcpy(RTA_DATA(subrta), data, alen); rta->rta_len = NLMSG_ALIGN(rta->rta_len) + RTA_ALIGN(len); return 0; } int parse_rtattr(struct rtattr *tb[], int max, struct rtattr *rta, int len) { memset(tb, 0, sizeof(struct rtattr *) * (max + 1)); while (RTA_OK(rta, len)) { if ((rta->rta_type <= max) && (!tb[rta->rta_type])) tb[rta->rta_type] = rta; rta = RTA_NEXT(rta, len); } if (len) fprintf(stderr, "!!!Deficit %d, rta_len=%d\n", len, rta->rta_len); return 0; } int parse_rtattr_byindex(struct rtattr *tb[], int max, struct rtattr *rta, int len) { int i = 0; memset(tb, 0, sizeof(struct rtattr *) * max); while (RTA_OK(rta, len)) { if (rta->rta_type <= max && i < max) tb[i++] = rta; rta = RTA_NEXT(rta, len); } if (len) fprintf(stderr, "!!!Deficit %d, rta_len=%d\n", len, rta->rta_len); return i; } int __parse_rtattr_nested_compat(struct rtattr *tb[], int max, struct rtattr *rta, int len) { if ((int)RTA_PAYLOAD(rta) < len) return -1; if (RTA_PAYLOAD(rta) >= RTA_ALIGN(len) + sizeof(struct rtattr)) { rta = (struct rtattr *)(uint8_t *)RTA_DATA(rta) + RTA_ALIGN(len); return parse_rtattr_nested(tb, max, rta); } memset(tb, 0, sizeof(struct rtattr *) * (max + 1)); return 0; } #endif /* __linux__ */ frr-7.2.1/pimd/mtracebis_netlink.h0000644000000000000000000001154013610377563014014 00000000000000/* * libnetlink.c RTnetlink service routines. * * This program is free software; you can redistribute it and/or * modify it under the terms of the GNU General Public License * as published by the Free Software Foundation; either version * 2 of the License, or (at your option) any later version. * * Authors: Alexey Kuznetsov, * */ #ifdef __linux__ #ifndef __LIBNETLINK_H__ #define __LIBNETLINK_H__ 1 #include #include #include #include #include #include struct rtnl_handle { int fd; struct sockaddr_nl local; struct sockaddr_nl peer; __u32 seq; __u32 dump; }; extern int rcvbuf; extern int rtnl_open(struct rtnl_handle *rth, unsigned subscriptions); extern int rtnl_open_byproto(struct rtnl_handle *rth, unsigned subscriptions, int protocol); extern void rtnl_close(struct rtnl_handle *rth); extern int rtnl_wilddump_request(struct rtnl_handle *rth, int fam, int type); extern int rtnl_dump_request(struct rtnl_handle *rth, int type, void *req, int len); typedef int (*rtnl_filter_t)(const struct sockaddr_nl *, struct nlmsghdr *n, void *); struct rtnl_dump_filter_arg { rtnl_filter_t filter; void *arg1; rtnl_filter_t junk; void *arg2; }; extern int rtnl_dump_filter_l(struct rtnl_handle *rth, const struct rtnl_dump_filter_arg *arg); extern int rtnl_dump_filter(struct rtnl_handle *rth, rtnl_filter_t filter, void *arg1, rtnl_filter_t junk, void *arg2); extern int rtnl_talk(struct rtnl_handle *rtnl, struct nlmsghdr *n, pid_t peer, unsigned groups, struct nlmsghdr *answer, rtnl_filter_t junk, void *jarg); extern int rtnl_send(struct rtnl_handle *rth, const char *buf, int); extern int rtnl_send_check(struct rtnl_handle *rth, const char *buf, int); extern int addattr32(struct nlmsghdr *n, int maxlen, int type, __u32 data); extern int addattr_l(struct nlmsghdr *n, int maxlen, int type, const void *data, int alen); extern int addraw_l(struct nlmsghdr *n, int maxlen, const void *data, int len); extern struct rtattr *addattr_nest(struct nlmsghdr *n, int maxlen, int type); extern int addattr_nest_end(struct nlmsghdr *n, struct rtattr *nest); extern struct rtattr *addattr_nest_compat(struct nlmsghdr *n, int maxlen, int type, const void *data, int len); extern int addattr_nest_compat_end(struct nlmsghdr *n, struct rtattr *nest); extern int rta_addattr32(struct rtattr *rta, int maxlen, int type, __u32 data); extern int rta_addattr_l(struct rtattr *rta, int maxlen, int type, const void *data, int alen); extern int parse_rtattr(struct rtattr *tb[], int max, struct rtattr *rta, int len); extern int parse_rtattr_byindex(struct rtattr *tb[], int max, struct rtattr *rta, int len); extern int __parse_rtattr_nested_compat(struct rtattr *tb[], int max, struct rtattr *rta, int len); #define parse_rtattr_nested(tb, max, rta) \ (parse_rtattr((tb), (max), RTA_DATA(rta), RTA_PAYLOAD(rta))) #define parse_rtattr_nested_compat(tb, max, rta, data, len) \ ({ \ data = RTA_PAYLOAD(rta) >= len ? RTA_DATA(rta) : NULL; \ __parse_rtattr_nested_compat(tb, max, rta, len); \ }) extern int rtnl_listen(struct rtnl_handle *, rtnl_filter_t handler, void *jarg); extern int rtnl_from_file(FILE *, rtnl_filter_t handler, void *jarg); #define NLMSG_TAIL(nmsg) \ ((struct rtattr *)(((uint8_t *)(nmsg)) \ + NLMSG_ALIGN((nmsg)->nlmsg_len))) #ifndef IFA_RTA #define IFA_RTA(r) \ ((struct rtattr *)(((char *)(r)) \ + NLMSG_ALIGN(sizeof(struct ifaddrmsg)))) #endif #ifndef IFA_PAYLOAD #define IFA_PAYLOAD(n) NLMSG_PAYLOAD(n,sizeof(struct ifaddrmsg)) #endif #ifndef IFLA_RTA #define IFLA_RTA(r) \ ((struct rtattr *)(((char *)(r)) \ + NLMSG_ALIGN(sizeof(struct ifinfomsg)))) #endif #ifndef IFLA_PAYLOAD #define IFLA_PAYLOAD(n) NLMSG_PAYLOAD(n,sizeof(struct ifinfomsg)) #endif #ifndef NDA_RTA #define NDA_RTA(r) \ ((struct rtattr *)(((char *)(r)) + NLMSG_ALIGN(sizeof(struct ndmsg)))) #endif #ifndef NDA_PAYLOAD #define NDA_PAYLOAD(n) NLMSG_PAYLOAD(n,sizeof(struct ndmsg)) #endif #ifndef NDTA_RTA #define NDTA_RTA(r) \ ((struct rtattr *)(((char *)(r)) + NLMSG_ALIGN(sizeof(struct ndtmsg)))) #endif #ifndef NDTA_PAYLOAD #define NDTA_PAYLOAD(n) NLMSG_PAYLOAD(n,sizeof(struct ndtmsg)) #endif #endif /* __LIBNETLINK_H__ */ #endif /* __linux__ */ frr-7.2.1/pimd/mtracebis_routeget.c0000644000000000000000000000503013610377563014176 00000000000000/* * Multicast Traceroute for FRRouting * Copyright (C) 2018 Mladen Sablic * * This program is free software; you can redistribute it and/or modify * it under the terms of the 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; see the file COPYING; if not, write to the Free Software * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA */ #ifdef HAVE_CONFIG_H #include "config.h" #endif #ifdef __linux__ #include #include #include #include #include #include #include #include "mtracebis_netlink.h" #include "mtracebis_routeget.h" static int find_dst(struct nlmsghdr *n, struct in_addr *src, struct in_addr *gw) { struct rtmsg *r = NLMSG_DATA(n); int len = n->nlmsg_len; struct rtattr *tb[RTA_MAX + 1]; len -= NLMSG_LENGTH(sizeof(*r)); if (len < 0) { fprintf(stderr, "BUG: wrong nlmsg len %d\n", len); return -1; } parse_rtattr(tb, RTA_MAX, RTM_RTA(r), len); if (tb[RTA_PREFSRC]) src->s_addr = *(uint32_t *)RTA_DATA(tb[RTA_PREFSRC]); if (tb[RTA_GATEWAY]) gw->s_addr = *(uint32_t *)RTA_DATA(tb[RTA_GATEWAY]); if (tb[RTA_OIF]) return *(int *)RTA_DATA(tb[RTA_OIF]); return 0; } int routeget(struct in_addr dst, struct in_addr *src, struct in_addr *gw) { struct { struct nlmsghdr n; struct rtmsg r; char buf[1024]; } req; int ret; struct rtnl_handle rth = {.fd = -1}; memset(&req, 0, sizeof(req)); req.n.nlmsg_len = NLMSG_LENGTH(sizeof(struct rtmsg)); req.n.nlmsg_flags = NLM_F_REQUEST; req.n.nlmsg_type = RTM_GETROUTE; req.r.rtm_family = AF_INET; req.r.rtm_table = 0; req.r.rtm_protocol = 0; req.r.rtm_scope = 0; req.r.rtm_type = 0; req.r.rtm_src_len = 0; req.r.rtm_dst_len = 0; req.r.rtm_tos = 0; addattr_l(&req.n, sizeof(req), RTA_DST, &dst.s_addr, 4); req.r.rtm_dst_len = 32; ret = rtnl_open(&rth, 0); if (ret < 0 || rth.fd <= 0) return ret; if (rtnl_talk(&rth, &req.n, 0, 0, &req.n, NULL, NULL) < 0) { ret = -1; goto close_rth; } ret = find_dst(&req.n, src, gw); close_rth: rtnl_close(&rth); return ret; } #endif /* __linux__ */ frr-7.2.1/pimd/mtracebis_routeget.h0000644000000000000000000000200013610377563014175 00000000000000/* * Multicast Traceroute for FRRouting * Copyright (C) 2018 Mladen Sablic * * This program is free software; you can redistribute it and/or modify * it under the terms of the 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; see the file COPYING; if not, write to the Free Software * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA */ #ifdef __linux__ #ifndef ROUTEGET_H #define ROUTEGET_H #include int routeget(struct in_addr dst, struct in_addr *src, struct in_addr *gw); #endif /* ROUTEGET */ #endif /* __linux__ */ frr-7.2.1/pimd/pim_assert.c0000644000000000000000000005033413610377563012464 00000000000000/* * PIM for Quagga * Copyright (C) 2008 Everton da Silva Marques * * This program is free software; you can redistribute it and/or modify * it under the terms of the 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; see the file COPYING; if not, write to the Free Software * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA */ #include #include "log.h" #include "prefix.h" #include "if.h" #include "pimd.h" #include "pim_str.h" #include "pim_tlv.h" #include "pim_msg.h" #include "pim_pim.h" #include "pim_int.h" #include "pim_time.h" #include "pim_iface.h" #include "pim_hello.h" #include "pim_macro.h" #include "pim_assert.h" #include "pim_ifchannel.h" static int assert_action_a3(struct pim_ifchannel *ch); static void assert_action_a2(struct pim_ifchannel *ch, struct pim_assert_metric winner_metric); static void assert_action_a6(struct pim_ifchannel *ch, struct pim_assert_metric winner_metric); void pim_ifassert_winner_set(struct pim_ifchannel *ch, enum pim_ifassert_state new_state, struct in_addr winner, struct pim_assert_metric winner_metric) { struct pim_interface *pim_ifp = ch->interface->info; int winner_changed = (ch->ifassert_winner.s_addr != winner.s_addr); int metric_changed = !pim_assert_metric_match( &ch->ifassert_winner_metric, &winner_metric); if (PIM_DEBUG_PIM_EVENTS) { if (ch->ifassert_state != new_state) { zlog_debug( "%s: (S,G)=%s assert state changed from %s to %s on interface %s", __PRETTY_FUNCTION__, ch->sg_str, pim_ifchannel_ifassert_name(ch->ifassert_state), pim_ifchannel_ifassert_name(new_state), ch->interface->name); } if (winner_changed) { char was_str[INET_ADDRSTRLEN]; char winner_str[INET_ADDRSTRLEN]; pim_inet4_dump("", ch->ifassert_winner, was_str, sizeof(was_str)); pim_inet4_dump("", winner, winner_str, sizeof(winner_str)); zlog_debug( "%s: (S,G)=%s assert winner changed from %s to %s on interface %s", __PRETTY_FUNCTION__, ch->sg_str, was_str, winner_str, ch->interface->name); } } /* PIM_DEBUG_PIM_EVENTS */ ch->ifassert_state = new_state; ch->ifassert_winner = winner; ch->ifassert_winner_metric = winner_metric; ch->ifassert_creation = pim_time_monotonic_sec(); if (winner_changed || metric_changed) { pim_upstream_update_join_desired(pim_ifp->pim, ch->upstream); pim_ifchannel_update_could_assert(ch); pim_ifchannel_update_assert_tracking_desired(ch); } } static void on_trace(const char *label, struct interface *ifp, struct in_addr src) { if (PIM_DEBUG_PIM_TRACE) { char src_str[INET_ADDRSTRLEN]; pim_inet4_dump("", src, src_str, sizeof(src_str)); zlog_debug("%s: from %s on %s", label, src_str, ifp->name); } } static int preferred_assert(const struct pim_ifchannel *ch, const struct pim_assert_metric *recv_metric) { return pim_assert_metric_better(recv_metric, &ch->ifassert_winner_metric); } static int acceptable_assert(const struct pim_assert_metric *my_metric, const struct pim_assert_metric *recv_metric) { return pim_assert_metric_better(recv_metric, my_metric); } static int inferior_assert(const struct pim_assert_metric *my_metric, const struct pim_assert_metric *recv_metric) { return pim_assert_metric_better(my_metric, recv_metric); } static int cancel_assert(const struct pim_assert_metric *recv_metric) { return (recv_metric->metric_preference == PIM_ASSERT_METRIC_PREFERENCE_MAX) && (recv_metric->route_metric == PIM_ASSERT_ROUTE_METRIC_MAX); } static void if_could_assert_do_a1(const char *caller, struct pim_ifchannel *ch) { if (PIM_IF_FLAG_TEST_COULD_ASSERT(ch->flags)) { if (assert_action_a1(ch)) { zlog_warn( "%s: %s: (S,G)=%s assert_action_a1 failure on interface %s", __PRETTY_FUNCTION__, caller, ch->sg_str, ch->interface->name); /* log warning only */ } } } static int dispatch_assert(struct interface *ifp, struct in_addr source_addr, struct in_addr group_addr, struct pim_assert_metric recv_metric) { struct pim_ifchannel *ch; struct prefix_sg sg; memset(&sg, 0, sizeof(struct prefix_sg)); sg.src = source_addr; sg.grp = group_addr; ch = pim_ifchannel_add(ifp, &sg, 0, 0); switch (ch->ifassert_state) { case PIM_IFASSERT_NOINFO: if (recv_metric.rpt_bit_flag) { /* RPT bit set */ if_could_assert_do_a1(__PRETTY_FUNCTION__, ch); } else { /* RPT bit clear */ if (inferior_assert(&ch->ifassert_my_metric, &recv_metric)) { if_could_assert_do_a1(__PRETTY_FUNCTION__, ch); } else if (acceptable_assert(&ch->ifassert_my_metric, &recv_metric)) { if (PIM_IF_FLAG_TEST_ASSERT_TRACKING_DESIRED( ch->flags)) { assert_action_a6(ch, recv_metric); } } } break; case PIM_IFASSERT_I_AM_WINNER: if (preferred_assert(ch, &recv_metric)) { assert_action_a2(ch, recv_metric); } else { if (inferior_assert(&ch->ifassert_my_metric, &recv_metric)) { assert_action_a3(ch); } } break; case PIM_IFASSERT_I_AM_LOSER: if (recv_metric.ip_address.s_addr == ch->ifassert_winner.s_addr) { /* Assert from current winner */ if (cancel_assert(&recv_metric)) { assert_action_a5(ch); } else { if (inferior_assert(&ch->ifassert_my_metric, &recv_metric)) { assert_action_a5(ch); } else if (acceptable_assert( &ch->ifassert_my_metric, &recv_metric)) { if (!recv_metric.rpt_bit_flag) { assert_action_a2(ch, recv_metric); } } } } else if (preferred_assert(ch, &recv_metric)) { assert_action_a2(ch, recv_metric); } break; default: { zlog_warn( "%s: (S,G)=%s invalid assert state %d on interface %s", __PRETTY_FUNCTION__, ch->sg_str, ch->ifassert_state, ifp->name); } return -2; } return 0; } int pim_assert_recv(struct interface *ifp, struct pim_neighbor *neigh, struct in_addr src_addr, uint8_t *buf, int buf_size) { struct prefix_sg sg; struct prefix msg_source_addr; struct pim_assert_metric msg_metric; int offset; uint8_t *curr; int curr_size; struct pim_interface *pim_ifp = NULL; on_trace(__PRETTY_FUNCTION__, ifp, src_addr); curr = buf; curr_size = buf_size; /* Parse assert group addr */ memset(&sg, 0, sizeof(struct prefix_sg)); offset = pim_parse_addr_group(&sg, curr, curr_size); if (offset < 1) { char src_str[INET_ADDRSTRLEN]; pim_inet4_dump("", src_addr, src_str, sizeof(src_str)); zlog_warn("%s: pim_parse_addr_group() failure: from %s on %s", __PRETTY_FUNCTION__, src_str, ifp->name); return -1; } curr += offset; curr_size -= offset; /* Parse assert source addr */ offset = pim_parse_addr_ucast(&msg_source_addr, curr, curr_size); if (offset < 1) { char src_str[INET_ADDRSTRLEN]; pim_inet4_dump("", src_addr, src_str, sizeof(src_str)); zlog_warn("%s: pim_parse_addr_ucast() failure: from %s on %s", __PRETTY_FUNCTION__, src_str, ifp->name); return -2; } curr += offset; curr_size -= offset; if (curr_size != 8) { char src_str[INET_ADDRSTRLEN]; pim_inet4_dump("", src_addr, src_str, sizeof(src_str)); zlog_warn( "%s: preference/metric size is not 8: size=%d from %s on interface %s", __PRETTY_FUNCTION__, curr_size, src_str, ifp->name); return -3; } /* Parse assert metric preference */ msg_metric.metric_preference = pim_read_uint32_host(curr); msg_metric.rpt_bit_flag = msg_metric.metric_preference & 0x80000000; /* save highest bit */ msg_metric.metric_preference &= ~0x80000000; /* clear highest bit */ curr += 4; /* Parse assert route metric */ msg_metric.route_metric = pim_read_uint32_host(curr); if (PIM_DEBUG_PIM_TRACE) { char neigh_str[INET_ADDRSTRLEN]; char source_str[INET_ADDRSTRLEN]; char group_str[INET_ADDRSTRLEN]; pim_inet4_dump("", src_addr, neigh_str, sizeof(neigh_str)); pim_inet4_dump("", msg_source_addr.u.prefix4, source_str, sizeof(source_str)); pim_inet4_dump("", sg.grp, group_str, sizeof(group_str)); zlog_debug( "%s: from %s on %s: (S,G)=(%s,%s) pref=%u metric=%u rpt_bit=%u", __PRETTY_FUNCTION__, neigh_str, ifp->name, source_str, group_str, msg_metric.metric_preference, msg_metric.route_metric, PIM_FORCE_BOOLEAN(msg_metric.rpt_bit_flag)); } msg_metric.ip_address = src_addr; pim_ifp = ifp->info; zassert(pim_ifp); ++pim_ifp->pim_ifstat_assert_recv; return dispatch_assert(ifp, msg_source_addr.u.prefix4, sg.grp, msg_metric); } /* RFC 4601: 4.6.3. Assert Metrics Assert metrics are defined as: When comparing assert_metrics, the rpt_bit_flag, metric_preference, and route_metric field are compared in order, where the first lower value wins. If all fields are equal, the primary IP address of the router that sourced the Assert message is used as a tie-breaker, with the highest IP address winning. */ int pim_assert_metric_better(const struct pim_assert_metric *m1, const struct pim_assert_metric *m2) { if (m1->rpt_bit_flag < m2->rpt_bit_flag) return 1; if (m1->rpt_bit_flag > m2->rpt_bit_flag) return 0; if (m1->metric_preference < m2->metric_preference) return 1; if (m1->metric_preference > m2->metric_preference) return 0; if (m1->route_metric < m2->route_metric) return 1; if (m1->route_metric > m2->route_metric) return 0; return ntohl(m1->ip_address.s_addr) > ntohl(m2->ip_address.s_addr); } int pim_assert_metric_match(const struct pim_assert_metric *m1, const struct pim_assert_metric *m2) { if (m1->rpt_bit_flag != m2->rpt_bit_flag) return 0; if (m1->metric_preference != m2->metric_preference) return 0; if (m1->route_metric != m2->route_metric) return 0; return m1->ip_address.s_addr == m2->ip_address.s_addr; } int pim_assert_build_msg(uint8_t *pim_msg, int buf_size, struct interface *ifp, struct in_addr group_addr, struct in_addr source_addr, uint32_t metric_preference, uint32_t route_metric, uint32_t rpt_bit_flag) { uint8_t *buf_pastend = pim_msg + buf_size; uint8_t *pim_msg_curr; int pim_msg_size; int remain; pim_msg_curr = pim_msg + PIM_MSG_HEADER_LEN; /* skip room for pim header */ /* Encode group */ remain = buf_pastend - pim_msg_curr; pim_msg_curr = pim_msg_addr_encode_ipv4_group(pim_msg_curr, group_addr); if (!pim_msg_curr) { char group_str[INET_ADDRSTRLEN]; pim_inet4_dump("", group_addr, group_str, sizeof(group_str)); zlog_warn( "%s: failure encoding group address %s: space left=%d", __PRETTY_FUNCTION__, group_str, remain); return -1; } /* Encode source */ remain = buf_pastend - pim_msg_curr; pim_msg_curr = pim_msg_addr_encode_ipv4_ucast(pim_msg_curr, source_addr); if (!pim_msg_curr) { char source_str[INET_ADDRSTRLEN]; pim_inet4_dump("", source_addr, source_str, sizeof(source_str)); zlog_warn( "%s: failure encoding source address %s: space left=%d", __PRETTY_FUNCTION__, source_str, remain); return -2; } /* Metric preference */ pim_write_uint32(pim_msg_curr, rpt_bit_flag ? metric_preference | 0x80000000 : metric_preference); pim_msg_curr += 4; /* Route metric */ pim_write_uint32(pim_msg_curr, route_metric); pim_msg_curr += 4; /* Add PIM header */ pim_msg_size = pim_msg_curr - pim_msg; pim_msg_build_header(pim_msg, pim_msg_size, PIM_MSG_TYPE_ASSERT, false); return pim_msg_size; } static int pim_assert_do(struct pim_ifchannel *ch, struct pim_assert_metric metric) { struct interface *ifp; struct pim_interface *pim_ifp; uint8_t pim_msg[1000]; int pim_msg_size; ifp = ch->interface; if (!ifp) { if (PIM_DEBUG_PIM_TRACE) zlog_debug("%s: channel%s has no associated interface!", __PRETTY_FUNCTION__, ch->sg_str); return -1; } pim_ifp = ifp->info; if (!pim_ifp) { if (PIM_DEBUG_PIM_TRACE) zlog_debug( "%s: channel %s pim not enabled on interface: %s", __PRETTY_FUNCTION__, ch->sg_str, ifp->name); return -1; } pim_msg_size = pim_assert_build_msg(pim_msg, sizeof(pim_msg), ifp, ch->sg.grp, ch->sg.src, metric.metric_preference, metric.route_metric, metric.rpt_bit_flag); if (pim_msg_size < 1) { zlog_warn( "%s: failure building PIM assert message: msg_size=%d", __PRETTY_FUNCTION__, pim_msg_size); return -2; } /* RFC 4601: 4.3.1. Sending Hello Messages Thus, if a router needs to send a Join/Prune or Assert message on an interface on which it has not yet sent a Hello message with the currently configured IP address, then it MUST immediately send the relevant Hello message without waiting for the Hello Timer to expire, followed by the Join/Prune or Assert message. */ pim_hello_require(ifp); if (PIM_DEBUG_PIM_TRACE) { zlog_debug("%s: to %s: (S,G)=%s pref=%u metric=%u rpt_bit=%u", __PRETTY_FUNCTION__, ifp->name, ch->sg_str, metric.metric_preference, metric.route_metric, PIM_FORCE_BOOLEAN(metric.rpt_bit_flag)); } ++pim_ifp->pim_ifstat_assert_send; if (pim_msg_send(pim_ifp->pim_sock_fd, pim_ifp->primary_address, qpim_all_pim_routers_addr, pim_msg, pim_msg_size, ifp->name)) { zlog_warn("%s: could not send PIM message on interface %s", __PRETTY_FUNCTION__, ifp->name); return -3; } return 0; } int pim_assert_send(struct pim_ifchannel *ch) { return pim_assert_do(ch, ch->ifassert_my_metric); } /* RFC 4601: 4.6.4. AssertCancel Messages An AssertCancel(S,G) is an infinite metric assert with the RPT bit set that names S as the source. */ static int pim_assert_cancel(struct pim_ifchannel *ch) { struct pim_assert_metric metric; metric.rpt_bit_flag = 0; metric.metric_preference = PIM_ASSERT_METRIC_PREFERENCE_MAX; metric.route_metric = PIM_ASSERT_ROUTE_METRIC_MAX; metric.ip_address = ch->sg.src; return pim_assert_do(ch, metric); } static int on_assert_timer(struct thread *t) { struct pim_ifchannel *ch; struct interface *ifp; ch = THREAD_ARG(t); ifp = ch->interface; if (PIM_DEBUG_PIM_TRACE) { zlog_debug("%s: (S,G)=%s timer expired on interface %s", __PRETTY_FUNCTION__, ch->sg_str, ifp->name); } ch->t_ifassert_timer = NULL; switch (ch->ifassert_state) { case PIM_IFASSERT_I_AM_WINNER: assert_action_a3(ch); break; case PIM_IFASSERT_I_AM_LOSER: assert_action_a5(ch); break; default: { if (PIM_DEBUG_PIM_EVENTS) zlog_warn( "%s: (S,G)=%s invalid assert state %d on interface %s", __PRETTY_FUNCTION__, ch->sg_str, ch->ifassert_state, ifp->name); } } return 0; } static void assert_timer_off(struct pim_ifchannel *ch) { if (PIM_DEBUG_PIM_TRACE) { if (ch->t_ifassert_timer) { zlog_debug( "%s: (S,G)=%s cancelling timer on interface %s", __PRETTY_FUNCTION__, ch->sg_str, ch->interface->name); } } THREAD_OFF(ch->t_ifassert_timer); } static void pim_assert_timer_set(struct pim_ifchannel *ch, int interval) { assert_timer_off(ch); if (PIM_DEBUG_PIM_TRACE) { zlog_debug("%s: (S,G)=%s starting %u sec timer on interface %s", __PRETTY_FUNCTION__, ch->sg_str, interval, ch->interface->name); } thread_add_timer(router->master, on_assert_timer, ch, interval, &ch->t_ifassert_timer); } static void pim_assert_timer_reset(struct pim_ifchannel *ch) { pim_assert_timer_set(ch, PIM_ASSERT_TIME - PIM_ASSERT_OVERRIDE_INTERVAL); } /* RFC 4601: 4.6.1. (S,G) Assert Message State Machine (S,G) Assert State machine Actions A1: Send Assert(S,G). Set Assert Timer to (Assert_Time - Assert_Override_Interval). Store self as AssertWinner(S,G,I). Store spt_assert_metric(S,I) as AssertWinnerMetric(S,G,I). */ int assert_action_a1(struct pim_ifchannel *ch) { struct interface *ifp = ch->interface; struct pim_interface *pim_ifp; pim_ifp = ifp->info; if (!pim_ifp) { zlog_warn("%s: (S,G)=%s multicast not enabled on interface %s", __PRETTY_FUNCTION__, ch->sg_str, ifp->name); return -1; /* must return since pim_ifp is used below */ } /* Switch to I_AM_WINNER before performing action_a3 below */ pim_ifassert_winner_set( ch, PIM_IFASSERT_I_AM_WINNER, pim_ifp->primary_address, pim_macro_spt_assert_metric(&ch->upstream->rpf, pim_ifp->primary_address)); if (assert_action_a3(ch)) { zlog_warn( "%s: (S,G)=%s assert_action_a3 failure on interface %s", __PRETTY_FUNCTION__, ch->sg_str, ifp->name); /* warning only */ } if (ch->ifassert_state != PIM_IFASSERT_I_AM_WINNER) { if (PIM_DEBUG_PIM_EVENTS) zlog_warn( "%s: channel%s not in expected PIM_IFASSERT_I_AM_WINNER state", __PRETTY_FUNCTION__, ch->sg_str); } return 0; } /* RFC 4601: 4.6.1. (S,G) Assert Message State Machine (S,G) Assert State machine Actions A2: Store new assert winner as AssertWinner(S,G,I) and assert winner metric as AssertWinnerMetric(S,G,I). Set Assert Timer to Assert_Time. */ static void assert_action_a2(struct pim_ifchannel *ch, struct pim_assert_metric winner_metric) { pim_ifassert_winner_set(ch, PIM_IFASSERT_I_AM_LOSER, winner_metric.ip_address, winner_metric); pim_assert_timer_set(ch, PIM_ASSERT_TIME); if (ch->ifassert_state != PIM_IFASSERT_I_AM_LOSER) { if (PIM_DEBUG_PIM_EVENTS) zlog_warn( "%s: channel%s not in expected PIM_IFASSERT_I_AM_LOSER state", __PRETTY_FUNCTION__, ch->sg_str); } } /* RFC 4601: 4.6.1. (S,G) Assert Message State Machine (S,G) Assert State machine Actions A3: Send Assert(S,G). Set Assert Timer to (Assert_Time - Assert_Override_Interval). */ static int assert_action_a3(struct pim_ifchannel *ch) { if (ch->ifassert_state != PIM_IFASSERT_I_AM_WINNER) { if (PIM_DEBUG_PIM_EVENTS) zlog_warn( "%s: channel%s expected to be in PIM_IFASSERT_I_AM_WINNER state", __PRETTY_FUNCTION__, ch->sg_str); return -1; } pim_assert_timer_reset(ch); if (pim_assert_send(ch)) { zlog_warn("%s: (S,G)=%s failure sending assert on interface %s", __PRETTY_FUNCTION__, ch->sg_str, ch->interface->name); return -1; } return 0; } /* RFC 4601: 4.6.1. (S,G) Assert Message State Machine (S,G) Assert State machine Actions A4: Send AssertCancel(S,G). Delete assert info (AssertWinner(S,G,I) and AssertWinnerMetric(S,G,I) will then return their default values). */ void assert_action_a4(struct pim_ifchannel *ch) { if (pim_assert_cancel(ch)) { zlog_warn("%s: failure sending AssertCancel%s on interface %s", __PRETTY_FUNCTION__, ch->sg_str, ch->interface->name); /* log warning only */ } assert_action_a5(ch); if (ch->ifassert_state != PIM_IFASSERT_NOINFO) { if (PIM_DEBUG_PIM_EVENTS) zlog_warn( "%s: channel%s not in PIM_IFASSERT_NOINFO state as expected", __PRETTY_FUNCTION__, ch->sg_str); } } /* RFC 4601: 4.6.1. (S,G) Assert Message State Machine (S,G) Assert State machine Actions A5: Delete assert info (AssertWinner(S,G,I) and AssertWinnerMetric(S,G,I) will then return their default values). */ void assert_action_a5(struct pim_ifchannel *ch) { reset_ifassert_state(ch); if (ch->ifassert_state != PIM_IFASSERT_NOINFO) { if (PIM_DEBUG_PIM_EVENTS) zlog_warn( "%s: channel%s not in PIM_IFSSERT_NOINFO state as expected", __PRETTY_FUNCTION__, ch->sg_str); } } /* RFC 4601: 4.6.1. (S,G) Assert Message State Machine (S,G) Assert State machine Actions A6: Store new assert winner as AssertWinner(S,G,I) and assert winner metric as AssertWinnerMetric(S,G,I). Set Assert Timer to Assert_Time. If (I is RPF_interface(S)) AND (UpstreamJPState(S,G) == true) set SPTbit(S,G) to true. */ static void assert_action_a6(struct pim_ifchannel *ch, struct pim_assert_metric winner_metric) { assert_action_a2(ch, winner_metric); /* If (I is RPF_interface(S)) AND (UpstreamJPState(S,G) == true) set SPTbit(S,G) to true. */ if (ch->upstream->rpf.source_nexthop.interface == ch->interface) if (ch->upstream->join_state == PIM_UPSTREAM_JOINED) ch->upstream->sptbit = PIM_UPSTREAM_SPTBIT_TRUE; if (ch->ifassert_state != PIM_IFASSERT_I_AM_LOSER) { if (PIM_DEBUG_PIM_EVENTS) zlog_warn( "%s: channel%s not in PIM_IFASSERT_I_AM_LOSER state as expected", __PRETTY_FUNCTION__, ch->sg_str); } } frr-7.2.1/pimd/pim_assert.h0000644000000000000000000000444513610377563012473 00000000000000/* * PIM for Quagga * Copyright (C) 2008 Everton da Silva Marques * * This program is free software; you can redistribute it and/or modify * it under the terms of the 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; see the file COPYING; if not, write to the Free Software * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA */ #ifndef PIM_ASSERT_H #define PIM_ASSERT_H #include #include "if.h" #include "pim_neighbor.h" #include "pim_ifchannel.h" /* RFC 4601: 4.11. Timer Values Note that for historical reasons, the Assert message lacks a Holdtime field. Thus, changing the Assert Time from the default value is not recommended. */ #define PIM_ASSERT_OVERRIDE_INTERVAL (3) /* seconds */ #define PIM_ASSERT_TIME (180) /* seconds */ #define PIM_ASSERT_METRIC_PREFERENCE_MAX (0xFFFFFFFF) #define PIM_ASSERT_ROUTE_METRIC_MAX (0xFFFFFFFF) void pim_ifassert_winner_set(struct pim_ifchannel *ch, enum pim_ifassert_state new_state, struct in_addr winner, struct pim_assert_metric winner_metric); int pim_assert_recv(struct interface *ifp, struct pim_neighbor *neigh, struct in_addr src_addr, uint8_t *buf, int buf_size); int pim_assert_metric_better(const struct pim_assert_metric *m1, const struct pim_assert_metric *m2); int pim_assert_metric_match(const struct pim_assert_metric *m1, const struct pim_assert_metric *m2); int pim_assert_build_msg(uint8_t *pim_msg, int buf_size, struct interface *ifp, struct in_addr group_addr, struct in_addr source_addr, uint32_t metric_preference, uint32_t route_metric, uint32_t rpt_bit_flag); int pim_assert_send(struct pim_ifchannel *ch); int assert_action_a1(struct pim_ifchannel *ch); void assert_action_a4(struct pim_ifchannel *ch); void assert_action_a5(struct pim_ifchannel *ch); #endif /* PIM_ASSERT_H */ frr-7.2.1/pimd/pim_bfd.c0000644000000000000000000002222413610377563011713 00000000000000/* * pim_bfd.c: PIM BFD handling routines * * Copyright (C) 2017 Cumulus Networks, Inc. * Chirag Shah * * This program is free software; you can redistribute it and/or modify * it under the terms of the 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; see the file COPYING; if not, write to the * Free Software Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, * MA 02110-1301 USA */ #include #include "lib/json.h" #include "command.h" #include "vty.h" #include "zclient.h" #include "pim_instance.h" #include "pim_cmd.h" #include "pim_vty.h" #include "pim_iface.h" #include "pim_bfd.h" #include "bfd.h" #include "pimd.h" #include "pim_zebra.h" /* * pim_bfd_write_config - Write the interface BFD configuration. */ void pim_bfd_write_config(struct vty *vty, struct interface *ifp) { struct pim_interface *pim_ifp = ifp->info; struct bfd_info *bfd_info = NULL; if (!pim_ifp) return; bfd_info = (struct bfd_info *)pim_ifp->bfd_info; if (!bfd_info) return; #if HAVE_BFDD == 0 if (CHECK_FLAG(bfd_info->flags, BFD_FLAG_PARAM_CFG)) vty_out(vty, " ip pim bfd %d %d %d\n", bfd_info->detect_mult, bfd_info->required_min_rx, bfd_info->desired_min_tx); else #endif /* ! HAVE_BFDD */ vty_out(vty, " ip pim bfd\n"); } /* * pim_bfd_show_info - Show BFD info structure */ void pim_bfd_show_info(struct vty *vty, void *bfd_info, json_object *json_obj, bool use_json, int param_only) { if (param_only) bfd_show_param(vty, (struct bfd_info *)bfd_info, 1, 0, use_json, json_obj); else bfd_show_info(vty, (struct bfd_info *)bfd_info, 0, 1, use_json, json_obj); } /* * pim_bfd_info_nbr_create - Create/update BFD information for a neighbor. */ void pim_bfd_info_nbr_create(struct pim_interface *pim_ifp, struct pim_neighbor *neigh) { struct bfd_info *nbr_bfd_info = NULL; /* Check if Pim Interface BFD is enabled */ if (!pim_ifp || !pim_ifp->bfd_info) return; if (!neigh->bfd_info) neigh->bfd_info = bfd_info_create(); if (!neigh->bfd_info) return; nbr_bfd_info = (struct bfd_info *)neigh->bfd_info; nbr_bfd_info->detect_mult = pim_ifp->bfd_info->detect_mult; nbr_bfd_info->desired_min_tx = pim_ifp->bfd_info->desired_min_tx; nbr_bfd_info->required_min_rx = pim_ifp->bfd_info->required_min_rx; } /* * pim_bfd_info_free - Free BFD info structure */ void pim_bfd_info_free(struct bfd_info **bfd_info) { bfd_info_free(bfd_info); } static void pim_bfd_reg_dereg_nbr(struct pim_neighbor *nbr, int command) { struct pim_interface *pim_ifp = NULL; struct bfd_info *bfd_info = NULL; struct zclient *zclient = NULL; int cbit; zclient = pim_zebra_zclient_get(); if (!nbr) return; pim_ifp = nbr->interface->info; bfd_info = (struct bfd_info *)pim_ifp->bfd_info; if (!bfd_info) return; if (PIM_DEBUG_PIM_TRACE) { char str[INET_ADDRSTRLEN]; pim_inet4_dump("", nbr->source_addr, str, sizeof(str)); zlog_debug("%s Nbr %s %s with BFD", __PRETTY_FUNCTION__, str, bfd_get_command_dbg_str(command)); } cbit = CHECK_FLAG(bfd_info->flags, BFD_FLAG_BFD_CBIT_ON); bfd_peer_sendmsg(zclient, bfd_info, AF_INET, &nbr->source_addr, NULL, nbr->interface->name, 0, 0, cbit, command, 0, VRF_DEFAULT); } /* * pim_bfd_reg_dereg_all_nbr - Register/Deregister all neighbors associated * with a interface with BFD through * zebra for starting/stopping the monitoring of * the neighbor rechahability. */ int pim_bfd_reg_dereg_all_nbr(struct interface *ifp, int command) { struct pim_interface *pim_ifp = NULL; struct listnode *node = NULL; struct pim_neighbor *neigh = NULL; pim_ifp = ifp->info; if (!pim_ifp) return -1; if (!pim_ifp->bfd_info) return -1; for (ALL_LIST_ELEMENTS_RO(pim_ifp->pim_neighbor_list, node, neigh)) { if (command != ZEBRA_BFD_DEST_DEREGISTER) pim_bfd_info_nbr_create(pim_ifp, neigh); else pim_bfd_info_free((struct bfd_info **)&neigh->bfd_info); pim_bfd_reg_dereg_nbr(neigh, command); } return 0; } /* * pim_bfd_trigger_event - Neighbor is registered/deregistered with BFD when * neighbor state is changed to/from 2way. */ void pim_bfd_trigger_event(struct pim_interface *pim_ifp, struct pim_neighbor *nbr, uint8_t nbr_up) { if (nbr_up) { pim_bfd_info_nbr_create(pim_ifp, nbr); pim_bfd_reg_dereg_nbr(nbr, ZEBRA_BFD_DEST_REGISTER); } else { pim_bfd_info_free(&nbr->bfd_info); pim_bfd_reg_dereg_nbr(nbr, ZEBRA_BFD_DEST_DEREGISTER); } } /* * pim_bfd_if_param_set - Set the configured BFD paramter values for * interface. */ void pim_bfd_if_param_set(struct interface *ifp, uint32_t min_rx, uint32_t min_tx, uint8_t detect_mult, int defaults) { struct pim_interface *pim_ifp = ifp->info; int command = 0; if (!pim_ifp) return; bfd_set_param((struct bfd_info **)&(pim_ifp->bfd_info), min_rx, min_tx, detect_mult, defaults, &command); if (pim_ifp->bfd_info) { if (PIM_DEBUG_PIM_TRACE) zlog_debug("%s: interface %s has bfd_info", __PRETTY_FUNCTION__, ifp->name); } if (command) pim_bfd_reg_dereg_all_nbr(ifp, command); } /* * pim_bfd_interface_dest_update - Find the neighbor for which the BFD status * has changed and bring down the neighbor * connectivity if the BFD status changed to * down. */ static int pim_bfd_interface_dest_update(ZAPI_CALLBACK_ARGS) { struct interface *ifp = NULL; struct pim_interface *pim_ifp = NULL; struct prefix p; int status; char msg[100]; int old_status; struct bfd_info *bfd_info = NULL; struct timeval tv; struct listnode *neigh_node = NULL; struct listnode *neigh_nextnode = NULL; struct pim_neighbor *neigh = NULL; ifp = bfd_get_peer_info(zclient->ibuf, &p, NULL, &status, NULL, vrf_id); if ((ifp == NULL) || (p.family != AF_INET)) return 0; pim_ifp = ifp->info; if (!pim_ifp) return 0; if (!pim_ifp->bfd_info) { if (PIM_DEBUG_PIM_TRACE) zlog_debug("%s: pim interface %s BFD is disabled ", __PRETTY_FUNCTION__, ifp->name); return 0; } if (PIM_DEBUG_PIM_TRACE) { char buf[PREFIX2STR_BUFFER]; prefix2str(&p, buf, sizeof(buf)); zlog_debug("%s: interface %s bfd destination %s %s", __PRETTY_FUNCTION__, ifp->name, buf, bfd_get_status_str(status)); } for (ALL_LIST_ELEMENTS(pim_ifp->pim_neighbor_list, neigh_node, neigh_nextnode, neigh)) { /* Check neigh address matches with BFD address */ if (neigh->source_addr.s_addr != p.u.prefix4.s_addr) continue; bfd_info = (struct bfd_info *)neigh->bfd_info; if (bfd_info->status == status) { if (PIM_DEBUG_PIM_TRACE) { char str[INET_ADDRSTRLEN]; pim_inet4_dump("", neigh->source_addr, str, sizeof(str)); zlog_debug("%s: bfd status is same for nbr %s", __PRETTY_FUNCTION__, str); } continue; } old_status = bfd_info->status; bfd_info->status = status; monotime(&tv); bfd_info->last_update = tv.tv_sec; if (PIM_DEBUG_PIM_TRACE) { zlog_debug("%s: status %s old_status %s", __PRETTY_FUNCTION__, bfd_get_status_str(status), bfd_get_status_str(old_status)); } if ((status == BFD_STATUS_DOWN) && (old_status == BFD_STATUS_UP)) { snprintf(msg, sizeof(msg), "BFD Session Expired"); pim_neighbor_delete(ifp, neigh, msg); } } return 0; } /* * pim_bfd_nbr_replay - Replay all the neighbors that have BFD enabled * to zebra */ static int pim_bfd_nbr_replay(ZAPI_CALLBACK_ARGS) { struct interface *ifp = NULL; struct pim_interface *pim_ifp = NULL; struct pim_neighbor *neigh = NULL; struct listnode *neigh_node; struct listnode *neigh_nextnode; struct vrf *vrf = NULL; /* Send the client registration */ bfd_client_sendmsg(zclient, ZEBRA_BFD_CLIENT_REGISTER, vrf_id); RB_FOREACH (vrf, vrf_name_head, &vrfs_by_name) { FOR_ALL_INTERFACES (vrf, ifp) { pim_ifp = ifp->info; if (!pim_ifp) continue; if (pim_ifp->pim_sock_fd < 0) continue; for (ALL_LIST_ELEMENTS(pim_ifp->pim_neighbor_list, neigh_node, neigh_nextnode, neigh)) { if (!neigh->bfd_info) continue; if (PIM_DEBUG_PIM_TRACE) { char str[INET_ADDRSTRLEN]; pim_inet4_dump("", neigh->source_addr, str, sizeof(str)); zlog_debug( "%s: Replaying Pim Neigh %s to BFD vrf_id %u", __PRETTY_FUNCTION__, str, vrf->vrf_id); } pim_bfd_reg_dereg_nbr(neigh, ZEBRA_BFD_DEST_UPDATE); } } } return 0; } void pim_bfd_init(void) { struct zclient *zclient = NULL; zclient = pim_zebra_zclient_get(); bfd_gbl_init(); zclient->interface_bfd_dest_update = pim_bfd_interface_dest_update; zclient->bfd_dest_replay = pim_bfd_nbr_replay; } frr-7.2.1/pimd/pim_bfd.h0000644000000000000000000000307513610377563011723 00000000000000/* * pim_bfd.h: PIM BFD definitions and structures * * Copyright (C) 2017 Cumulus Networks, Inc. * Chirag Shah * * This program is free software; you can redistribute it and/or modify * it under the terms of the 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; see the file COPYING; if not, write to the * Free Software Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, * MA 02110-1301 USA */ #ifndef PIM_BFD_H #define PIM_BFD_H #include "if.h" void pim_bfd_init(void); void pim_bfd_write_config(struct vty *vty, struct interface *ifp); void pim_bfd_show_info(struct vty *vty, void *bfd_info, json_object *json_obj, bool use_json, int param_only); void pim_bfd_if_param_set(struct interface *ifp, uint32_t min_rx, uint32_t min_tx, uint8_t detect_mult, int defaults); int pim_bfd_reg_dereg_all_nbr(struct interface *ifp, int command); void pim_bfd_trigger_event(struct pim_interface *pim_ifp, struct pim_neighbor *nbr, uint8_t nbr_up); void pim_bfd_info_nbr_create(struct pim_interface *pim_ifp, struct pim_neighbor *neigh); void pim_bfd_info_free(struct bfd_info **bfd_info); #endif /* _PIM_BFD_H */ frr-7.2.1/pimd/pim_br.c0000644000000000000000000000443213610377563011564 00000000000000/* * PIM for Quagga * Copyright (C) 2015 Cumulus Networks, Inc. * Donald Sharp * * This program is free software; you can redistribute it and/or modify * it under the terms of the 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; see the file COPYING; if not, write to the Free Software * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA */ #include #include "memory.h" #include "log.h" #include "if.h" #include "pimd.h" #include "pim_str.h" #include "pim_br.h" #include "linklist.h" struct pim_br { struct prefix_sg sg; struct in_addr pmbr; }; struct in_addr pim_br_unknown = {.s_addr = 0}; static struct list *pim_br_list = NULL; struct in_addr pim_br_get_pmbr(struct prefix_sg *sg) { struct listnode *node; struct pim_br *pim_br; for (ALL_LIST_ELEMENTS_RO(pim_br_list, node, pim_br)) { if (sg->src.s_addr == pim_br->sg.src.s_addr && sg->grp.s_addr == pim_br->sg.grp.s_addr) return pim_br->pmbr; } return pim_br_unknown; } void pim_br_set_pmbr(struct prefix_sg *sg, struct in_addr br) { struct listnode *node, *next; struct pim_br *pim_br; for (ALL_LIST_ELEMENTS(pim_br_list, node, next, pim_br)) { if (sg->src.s_addr == pim_br->sg.src.s_addr && sg->grp.s_addr == pim_br->sg.grp.s_addr) break; } if (!pim_br) { pim_br = XCALLOC(MTYPE_PIM_BR, sizeof(*pim_br)); pim_br->sg = *sg; listnode_add(pim_br_list, pim_br); } pim_br->pmbr = br; } /* * Remove the (S,G) from the stored values */ void pim_br_clear_pmbr(struct prefix_sg *sg) { struct listnode *node, *next; struct pim_br *pim_br; for (ALL_LIST_ELEMENTS(pim_br_list, node, next, pim_br)) { if (sg->src.s_addr == pim_br->sg.src.s_addr && sg->grp.s_addr == pim_br->sg.grp.s_addr) break; } if (!pim_br) return; listnode_delete(pim_br_list, pim_br); } void pim_br_init(void) { pim_br_list = list_new(); } frr-7.2.1/pimd/pim_br.h0000644000000000000000000000211113610377563011561 00000000000000/* * PIM for Quagga * Copyright (C) 2015 Cumulus Networks, Inc. * Donald Sharp * * This program is free software; you can redistribute it and/or modify * it under the terms of the 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; see the file COPYING; if not, write to the Free Software * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA */ #ifndef PIM_BR_H #define PIM_BR_H struct in_addr pim_br_get_pmbr(struct prefix_sg *sg); void pim_br_set_pmbr(struct prefix_sg *sg, struct in_addr value); void pim_br_clear_pmbr(struct prefix_sg *sg); void pim_br_init(void); extern struct in_addr pim_br_unknown; #endif frr-7.2.1/pimd/pim_bsm.c0000644000000000000000000011340413610377563011742 00000000000000/* * pim_bsm.c: PIM BSM handling routines * * Copyright (C) 2018-19 Vmware, Inc. * Saravanan K * * This program is free software; you can redistribute it and/or modify * it under the terms of the 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; see the file COPYING; if not, write to the * Free Software Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, * MA 02110-1301 USA */ #ifdef HAVE_CONFIG_H #include "config.h" #endif #include "if.h" #include "pimd.h" #include "pim_iface.h" #include "pim_instance.h" #include "pim_rpf.h" #include "pim_hello.h" #include "pim_pim.h" #include "pim_nht.h" #include "pim_bsm.h" #include "pim_time.h" /* Functions forward declaration */ static void pim_bs_timer_start(struct bsm_scope *scope, int bs_timeout); static void pim_g2rp_timer_start(struct bsm_rpinfo *bsrp, int hold_time); static inline void pim_g2rp_timer_restart(struct bsm_rpinfo *bsrp, int hold_time); /* Memory Types */ DEFINE_MTYPE_STATIC(PIMD, PIM_BSGRP_NODE, "PIM BSR advertised grp info") DEFINE_MTYPE_STATIC(PIMD, PIM_BSRP_NODE, "PIM BSR advertised RP info") DEFINE_MTYPE_STATIC(PIMD, PIM_BSM_INFO, "PIM BSM Info") DEFINE_MTYPE_STATIC(PIMD, PIM_BSM_PKT_VAR_MEM, "PIM BSM Packet") /* All bsm packets forwarded shall be fit within ip mtu less iphdr(max) */ #define MAX_IP_HDR_LEN 24 /* pim_bsm_write_config - Write the interface pim bsm configuration.*/ void pim_bsm_write_config(struct vty *vty, struct interface *ifp) { struct pim_interface *pim_ifp = ifp->info; if (pim_ifp) { if (!pim_ifp->bsm_enable) vty_out(vty, " no ip pim bsm\n"); if (!pim_ifp->ucast_bsm_accept) vty_out(vty, " no ip pim unicast-bsm\n"); } } static void pim_free_bsgrp_data(struct bsgrp_node *bsgrp_node) { if (bsgrp_node->bsrp_list) list_delete(&bsgrp_node->bsrp_list); if (bsgrp_node->partial_bsrp_list) list_delete(&bsgrp_node->partial_bsrp_list); XFREE(MTYPE_PIM_BSGRP_NODE, bsgrp_node); } static void pim_free_bsgrp_node(struct route_table *rt, struct prefix *grp) { struct route_node *rn; rn = route_node_lookup(rt, grp); if (rn) { rn->info = NULL; route_unlock_node(rn); route_unlock_node(rn); } } static void pim_bsm_node_free(struct bsm_info *bsm) { if (bsm->bsm) XFREE(MTYPE_PIM_BSM_PKT_VAR_MEM, bsm->bsm); XFREE(MTYPE_PIM_BSM_INFO, bsm); } static int pim_g2rp_list_compare(struct bsm_rpinfo *node1, struct bsm_rpinfo *node2) { /* RP election Algo : * Step-1 : Loweset Rp priority will have higher precedance. * Step-2 : If priority same then higher hash val will have * higher precedance. * Step-3 : If Hash val is same then highest rp address will * become elected RP. */ if (node1->rp_prio < node2->rp_prio) return -1; if (node1->rp_prio > node2->rp_prio) return 1; if (node1->hash < node2->hash) return 1; if (node1->hash > node2->hash) return -1; if (node1->rp_address.s_addr < node2->rp_address.s_addr) return 1; if (node1->rp_address.s_addr > node2->rp_address.s_addr) return -1; return 0; } static void pim_free_bsrp_node(struct bsm_rpinfo *bsrp_info) { if (bsrp_info->g2rp_timer) THREAD_OFF(bsrp_info->g2rp_timer); XFREE(MTYPE_PIM_BSRP_NODE, bsrp_info); } static struct list *pim_alloc_bsrp_list(void) { struct list *new_list = NULL; new_list = list_new(); if (!new_list) return NULL; new_list->cmp = (int (*)(void *, void *))pim_g2rp_list_compare; new_list->del = (void (*)(void *))pim_free_bsrp_node; return new_list; } static struct bsgrp_node *pim_bsm_new_bsgrp_node(struct route_table *rt, struct prefix *grp) { struct route_node *rn; struct bsgrp_node *bsgrp; rn = route_node_get(rt, grp); if (!rn) { zlog_warn("%s: route node creation failed", __PRETTY_FUNCTION__); return NULL; } bsgrp = XCALLOC(MTYPE_PIM_BSGRP_NODE, sizeof(struct bsgrp_node)); rn->info = bsgrp; bsgrp->bsrp_list = pim_alloc_bsrp_list(); bsgrp->partial_bsrp_list = pim_alloc_bsrp_list(); if ((!bsgrp->bsrp_list) || (!bsgrp->partial_bsrp_list)) { route_unlock_node(rn); pim_free_bsgrp_data(bsgrp); return NULL; } prefix_copy(&bsgrp->group, grp); return bsgrp; } static int pim_on_bs_timer(struct thread *t) { struct route_node *rn; struct bsm_scope *scope; struct bsgrp_node *bsgrp_node; struct bsm_rpinfo *bsrp; struct prefix nht_p; char buf[PREFIX2STR_BUFFER]; bool is_bsr_tracking = true; scope = THREAD_ARG(t); THREAD_OFF(scope->bs_timer); if (PIM_DEBUG_BSM) zlog_debug("%s: Bootstrap Timer expired for scope: %d", __PRETTY_FUNCTION__, scope->sz_id); /* Remove next hop tracking for the bsr */ nht_p.family = AF_INET; nht_p.prefixlen = IPV4_MAX_BITLEN; nht_p.u.prefix4 = scope->current_bsr; if (PIM_DEBUG_BSM) { prefix2str(&nht_p, buf, sizeof(buf)); zlog_debug("%s: Deregister BSR addr %s with Zebra NHT", __PRETTY_FUNCTION__, buf); } pim_delete_tracked_nexthop(scope->pim, &nht_p, NULL, NULL, is_bsr_tracking); /* Reset scope zone data */ scope->accept_nofwd_bsm = false; scope->state = ACCEPT_ANY; scope->current_bsr.s_addr = INADDR_ANY; scope->current_bsr_prio = 0; scope->current_bsr_first_ts = 0; scope->current_bsr_last_ts = 0; scope->bsm_frag_tag = 0; list_delete_all_node(scope->bsm_list); for (rn = route_top(scope->bsrp_table); rn; rn = route_next(rn)) { bsgrp_node = (struct bsgrp_node *)rn->info; if (!bsgrp_node) { if (PIM_DEBUG_BSM) zlog_debug("%s: bsgrp_node is null", __PRETTY_FUNCTION__); continue; } /* Give grace time for rp to continue for another hold time */ if ((bsgrp_node->bsrp_list) && (bsgrp_node->bsrp_list->count)) { bsrp = listnode_head(bsgrp_node->bsrp_list); pim_g2rp_timer_restart(bsrp, bsrp->rp_holdtime); } /* clear pending list */ if ((bsgrp_node->partial_bsrp_list) && (bsgrp_node->partial_bsrp_list->count)) { list_delete_all_node(bsgrp_node->partial_bsrp_list); bsgrp_node->pend_rp_cnt = 0; } } return 0; } static void pim_bs_timer_stop(struct bsm_scope *scope) { if (PIM_DEBUG_BSM) zlog_debug("%s : BS timer being stopped of sz: %d", __PRETTY_FUNCTION__, scope->sz_id); THREAD_OFF(scope->bs_timer); } static void pim_bs_timer_start(struct bsm_scope *scope, int bs_timeout) { if (!scope) { if (PIM_DEBUG_BSM) zlog_debug("%s : Invalid scope(NULL).", __PRETTY_FUNCTION__); return; } THREAD_OFF(scope->bs_timer); if (PIM_DEBUG_BSM) zlog_debug("%s : starting bs timer for scope %d with timeout %d secs", __PRETTY_FUNCTION__, scope->sz_id, bs_timeout); thread_add_timer(router->master, pim_on_bs_timer, scope, bs_timeout, &scope->bs_timer); } static inline void pim_bs_timer_restart(struct bsm_scope *scope, int bs_timeout) { pim_bs_timer_start(scope, bs_timeout); } void pim_bsm_proc_init(struct pim_instance *pim) { memset(&pim->global_scope, 0, sizeof(struct bsm_scope)); pim->global_scope.sz_id = PIM_GBL_SZ_ID; pim->global_scope.bsrp_table = route_table_init(); pim->global_scope.accept_nofwd_bsm = true; pim->global_scope.state = NO_INFO; pim->global_scope.pim = pim; pim->global_scope.bsm_list = list_new(); pim->global_scope.bsm_list->del = (void (*)(void *))pim_bsm_node_free; pim_bs_timer_start(&pim->global_scope, PIM_BS_TIME); } void pim_bsm_proc_free(struct pim_instance *pim) { struct route_node *rn; struct bsgrp_node *bsgrp; pim_bs_timer_stop(&pim->global_scope); if (pim->global_scope.bsm_list) list_delete(&pim->global_scope.bsm_list); for (rn = route_top(pim->global_scope.bsrp_table); rn; rn = route_next(rn)) { bsgrp = rn->info; if (!bsgrp) continue; pim_free_bsgrp_data(bsgrp); } if (pim->global_scope.bsrp_table) route_table_finish(pim->global_scope.bsrp_table); } static bool is_hold_time_elapsed(void *data) { struct bsm_rpinfo *bsrp; bsrp = data; if (bsrp->elapse_time < bsrp->rp_holdtime) return false; else return true; } static int pim_on_g2rp_timer(struct thread *t) { struct bsm_rpinfo *bsrp; struct bsm_rpinfo *bsrp_node; struct bsgrp_node *bsgrp_node; struct listnode *bsrp_ln; struct pim_instance *pim; struct rp_info *rp_info; struct route_node *rn; uint16_t elapse; struct in_addr bsrp_addr; bsrp = THREAD_ARG(t); THREAD_OFF(bsrp->g2rp_timer); bsgrp_node = bsrp->bsgrp_node; /* elapse time is the hold time of expired node */ elapse = bsrp->rp_holdtime; bsrp_addr = bsrp->rp_address; /* update elapse for all bsrp nodes */ for (ALL_LIST_ELEMENTS_RO(bsgrp_node->bsrp_list, bsrp_ln, bsrp_node)) bsrp_node->elapse_time += elapse; /* remove the expired nodes from the list */ list_filter_out_nodes(bsgrp_node->bsrp_list, is_hold_time_elapsed); /* Get the next elected rp node */ bsrp = listnode_head(bsgrp_node->bsrp_list); pim = bsgrp_node->scope->pim; rn = route_node_lookup(pim->rp_table, &bsgrp_node->group); if (!rn) { zlog_warn("%s: Route node doesn't exist", __PRETTY_FUNCTION__); return 0; } rp_info = (struct rp_info *)rn->info; if (!rp_info) { route_unlock_node(rn); return 0; } if (rp_info->rp_src != RP_SRC_STATIC) { /* If new rp available, change it else delete the existing */ if (bsrp) { bsrp_addr = bsrp->rp_address; pim_g2rp_timer_start( bsrp, (bsrp->rp_holdtime - bsrp->elapse_time)); pim_rp_change(pim, bsrp_addr, bsgrp_node->group, RP_SRC_BSR); } else { pim_rp_del(pim, bsrp_addr, bsgrp_node->group, NULL, RP_SRC_BSR); } } if ((!bsgrp_node->bsrp_list->count) && (!bsgrp_node->partial_bsrp_list->count)) { pim_free_bsgrp_node(pim->global_scope.bsrp_table, &bsgrp_node->group); pim_free_bsgrp_data(bsgrp_node); } return 0; } static void pim_g2rp_timer_start(struct bsm_rpinfo *bsrp, int hold_time) { if (!bsrp) { if (PIM_DEBUG_BSM) zlog_debug("%s : Invalid brsp(NULL).", __PRETTY_FUNCTION__); return; } THREAD_OFF(bsrp->g2rp_timer); if (PIM_DEBUG_BSM) { char buf[48]; zlog_debug( "%s : starting g2rp timer for grp: %s - rp: %s with timeout %d secs(Actual Hold time : %d secs)", __PRETTY_FUNCTION__, prefix2str(&bsrp->bsgrp_node->group, buf, 48), inet_ntoa(bsrp->rp_address), hold_time, bsrp->rp_holdtime); } thread_add_timer(router->master, pim_on_g2rp_timer, bsrp, hold_time, &bsrp->g2rp_timer); } static inline void pim_g2rp_timer_restart(struct bsm_rpinfo *bsrp, int hold_time) { pim_g2rp_timer_start(bsrp, hold_time); } static void pim_g2rp_timer_stop(struct bsm_rpinfo *bsrp) { if (!bsrp) return; if (PIM_DEBUG_BSM) { char buf[48]; zlog_debug("%s : stopping g2rp timer for grp: %s - rp: %s", __PRETTY_FUNCTION__, prefix2str(&bsrp->bsgrp_node->group, buf, 48), inet_ntoa(bsrp->rp_address)); } THREAD_OFF(bsrp->g2rp_timer); } static bool is_hold_time_zero(void *data) { struct bsm_rpinfo *bsrp; bsrp = data; if (bsrp->rp_holdtime) return false; else return true; } static void pim_instate_pend_list(struct bsgrp_node *bsgrp_node) { struct bsm_rpinfo *active; struct bsm_rpinfo *pend; struct list *temp; struct rp_info *rp_info; struct route_node *rn; struct pim_instance *pim; struct rp_info *rp_all; struct prefix group_all; bool had_rp_node = true; pim = bsgrp_node->scope->pim; active = listnode_head(bsgrp_node->bsrp_list); /* Remove nodes with hold time 0 & check if list still has a head */ list_filter_out_nodes(bsgrp_node->partial_bsrp_list, is_hold_time_zero); pend = listnode_head(bsgrp_node->partial_bsrp_list); if (!str2prefix("224.0.0.0/4", &group_all)) return; rp_all = pim_rp_find_match_group(pim, &group_all); rn = route_node_lookup(pim->rp_table, &bsgrp_node->group); if (pend) pim_g2rp_timer_start(pend, pend->rp_holdtime); /* if rp node doesn't exist or exist but not configured(rp_all), * install the rp from head(if exists) of partial list. List is * is sorted such that head is the elected RP for the group. */ if (!rn || (prefix_same(&rp_all->group, &bsgrp_node->group) && pim_rpf_addr_is_inaddr_none(&rp_all->rp))) { if (PIM_DEBUG_BSM) zlog_debug("%s: Route node doesn't exist", __PRETTY_FUNCTION__); if (pend) pim_rp_new(pim, pend->rp_address, bsgrp_node->group, NULL, RP_SRC_BSR); had_rp_node = false; } else { rp_info = (struct rp_info *)rn->info; if (!rp_info) { route_unlock_node(rn); if (pend) pim_rp_new(pim, pend->rp_address, bsgrp_node->group, NULL, RP_SRC_BSR); had_rp_node = false; } } /* We didn't have rp node and pending list is empty(unlikely), cleanup*/ if ((!had_rp_node) && (!pend)) { pim_free_bsgrp_node(bsgrp_node->scope->bsrp_table, &bsgrp_node->group); pim_free_bsgrp_data(bsgrp_node); return; } if ((had_rp_node) && (rp_info->rp_src != RP_SRC_STATIC)) { /* This means we searched and got rp node, needs unlock */ route_unlock_node(rn); if (active && pend) { if ((active->rp_address.s_addr != pend->rp_address.s_addr)) pim_rp_change(pim, pend->rp_address, bsgrp_node->group, RP_SRC_BSR); } /* Possible when the first BSM has group with 0 rp count */ if ((!active) && (!pend)) { if (PIM_DEBUG_BSM) { zlog_debug( "%s: Both bsrp and partial list are empty", __PRETTY_FUNCTION__); } pim_free_bsgrp_node(bsgrp_node->scope->bsrp_table, &bsgrp_node->group); pim_free_bsgrp_data(bsgrp_node); return; } /* Possible when a group with 0 rp count received in BSM */ if ((active) && (!pend)) { pim_rp_del(pim, active->rp_address, bsgrp_node->group, NULL, RP_SRC_BSR); pim_free_bsgrp_node(bsgrp_node->scope->bsrp_table, &bsgrp_node->group); if (PIM_DEBUG_BSM) { zlog_debug("%s:Pend List is null,del grp node", __PRETTY_FUNCTION__); } pim_free_bsgrp_data(bsgrp_node); return; } } if ((had_rp_node) && (rp_info->rp_src == RP_SRC_STATIC)) { /* We need to unlock rn this case */ route_unlock_node(rn); /* there is a chance that static rp exist and bsrp cleaned * so clean bsgrp node if pending list empty */ if (!pend) { if (PIM_DEBUG_BSM) zlog_debug( "%s: Partial list is empty, static rp exists", __PRETTY_FUNCTION__); pim_free_bsgrp_node(bsgrp_node->scope->bsrp_table, &bsgrp_node->group); pim_free_bsgrp_data(bsgrp_node); return; } } /* swap the list & delete all nodes in partial list (old bsrp_list) * before swap * active is head of bsrp list * pend is head of partial list * After swap * active is head of partial list * pend is head of bsrp list * So check appriate head after swap and clean the new partial list */ temp = bsgrp_node->bsrp_list; bsgrp_node->bsrp_list = bsgrp_node->partial_bsrp_list; bsgrp_node->partial_bsrp_list = temp; if (active) { pim_g2rp_timer_stop(active); list_delete_all_node(bsgrp_node->partial_bsrp_list); } } static bool pim_bsr_rpf_check(struct pim_instance *pim, struct in_addr bsr, struct in_addr ip_src_addr) { struct pim_nexthop nexthop; int result; memset(&nexthop, 0, sizeof(nexthop)); /* New BSR recived */ if (bsr.s_addr != pim->global_scope.current_bsr.s_addr) { result = pim_nexthop_match(pim, bsr, ip_src_addr); /* Nexthop lookup pass for the new BSR address */ if (result) return true; if (PIM_DEBUG_BSM) { char bsr_str[INET_ADDRSTRLEN]; pim_inet4_dump("", bsr, bsr_str, sizeof(bsr_str)); zlog_debug("%s : No route to BSR address %s", __PRETTY_FUNCTION__, bsr_str); } return false; } return pim_nexthop_match_nht_cache(pim, bsr, ip_src_addr); } static bool is_preferred_bsr(struct pim_instance *pim, struct in_addr bsr, uint32_t bsr_prio) { if (bsr.s_addr == pim->global_scope.current_bsr.s_addr) return true; if (bsr_prio > pim->global_scope.current_bsr_prio) return true; else if (bsr_prio == pim->global_scope.current_bsr_prio) { if (bsr.s_addr >= pim->global_scope.current_bsr.s_addr) return true; else return false; } else return false; } static void pim_bsm_update(struct pim_instance *pim, struct in_addr bsr, uint32_t bsr_prio) { struct pim_nexthop_cache pnc; if (bsr.s_addr != pim->global_scope.current_bsr.s_addr) { struct prefix nht_p; char buf[PREFIX2STR_BUFFER]; bool is_bsr_tracking = true; /* De-register old BSR and register new BSR with Zebra NHT */ nht_p.family = AF_INET; nht_p.prefixlen = IPV4_MAX_BITLEN; if (pim->global_scope.current_bsr.s_addr != INADDR_ANY) { nht_p.u.prefix4 = pim->global_scope.current_bsr; if (PIM_DEBUG_BSM) { prefix2str(&nht_p, buf, sizeof(buf)); zlog_debug( "%s: Deregister BSR addr %s with Zebra NHT", __PRETTY_FUNCTION__, buf); } pim_delete_tracked_nexthop(pim, &nht_p, NULL, NULL, is_bsr_tracking); } nht_p.u.prefix4 = bsr; if (PIM_DEBUG_BSM) { prefix2str(&nht_p, buf, sizeof(buf)); zlog_debug( "%s: NHT Register BSR addr %s with Zebra NHT", __PRETTY_FUNCTION__, buf); } memset(&pnc, 0, sizeof(struct pim_nexthop_cache)); pim_find_or_track_nexthop(pim, &nht_p, NULL, NULL, is_bsr_tracking, &pnc); pim->global_scope.current_bsr = bsr; pim->global_scope.current_bsr_first_ts = pim_time_monotonic_sec(); pim->global_scope.state = ACCEPT_PREFERRED; } pim->global_scope.current_bsr_prio = bsr_prio; pim->global_scope.current_bsr_last_ts = pim_time_monotonic_sec(); } static bool pim_bsm_send_intf(uint8_t *buf, int len, struct interface *ifp, struct in_addr dst_addr) { struct pim_interface *pim_ifp; pim_ifp = ifp->info; if (!pim_ifp) { if (PIM_DEBUG_BSM) zlog_debug("%s: Pim interface not available for %s", __PRETTY_FUNCTION__, ifp->name); return false; } if (pim_ifp->pim_sock_fd == -1) { if (PIM_DEBUG_BSM) zlog_debug("%s: Pim sock not available for %s", __PRETTY_FUNCTION__, ifp->name); return false; } pim_msg_send(pim_ifp->pim_sock_fd, pim_ifp->primary_address, dst_addr, buf, len, ifp->name); pim_ifp->pim_ifstat_bsm_tx++; pim_ifp->pim->bsm_sent++; return true; } static bool pim_bsm_frag_send(uint8_t *buf, uint32_t len, struct interface *ifp, uint32_t pim_mtu, struct in_addr dst_addr, bool no_fwd) { struct bsmmsg_grpinfo *grpinfo, *curgrp; uint8_t *firstgrp_ptr; uint8_t *pkt; uint8_t *pak_start; uint32_t parsed_len = 0; uint32_t this_pkt_rem; uint32_t copy_byte_count; uint32_t this_pkt_len; uint8_t total_rp_cnt; uint8_t this_rp_cnt; uint8_t frag_rp_cnt; uint8_t rp_fit_cnt; bool pak_pending = false; /* MTU passed here is PIM MTU (IP MTU less IP Hdr) */ if (pim_mtu < (PIM_MIN_BSM_LEN)) { zlog_warn( "%s: mtu(pim mtu: %d) size less than minimum bootstrap len", __PRETTY_FUNCTION__, pim_mtu); if (PIM_DEBUG_BSM) zlog_debug( "%s: mtu (pim mtu:%d) less than minimum bootstrap len", __PRETTY_FUNCTION__, pim_mtu); return false; } pak_start = XCALLOC(MTYPE_PIM_BSM_PKT_VAR_MEM, pim_mtu); pkt = pak_start; /* Fill PIM header later before sending packet to calc checksum */ pkt += PIM_MSG_HEADER_LEN; buf += PIM_MSG_HEADER_LEN; /* copy bsm header to new packet at offset of pim hdr */ memcpy(pkt, buf, PIM_BSM_HDR_LEN); pkt += PIM_BSM_HDR_LEN; buf += PIM_BSM_HDR_LEN; parsed_len += (PIM_MSG_HEADER_LEN + PIM_BSM_HDR_LEN); /* Store the position of first grp ptr, which can be reused for * next packet to start filling group. old bsm header and pim hdr * remains. So need not be filled again for next packet onwards. */ firstgrp_ptr = pkt; /* we received mtu excluding IP hdr len as param * now this_pkt_rem is mtu excluding * PIM_BSM_HDR_LEN + PIM_MSG_HEADER_LEN */ this_pkt_rem = pim_mtu - (PIM_BSM_HDR_LEN + PIM_MSG_HEADER_LEN); /* For each group till the packet length parsed */ while (parsed_len < len) { /* pkt ---> fragment's current pointer * buf ---> input buffer's current pointer * mtu ---> size of the pim packet - PIM header * curgrp ---> current group on the fragment * grpinfo ---> current group on the input buffer * this_pkt_rem ---> bytes remaing on the current fragment * rp_fit_cnt ---> num of rp for current grp that * fits this frag * total_rp_cnt ---> total rp present for the group in the buf * frag_rp_cnt ---> no of rp for the group to be fit in * the frag * this_rp_cnt ---> how many rp have we parsed */ grpinfo = (struct bsmmsg_grpinfo *)buf; memcpy(pkt, buf, PIM_BSM_GRP_LEN); curgrp = (struct bsmmsg_grpinfo *)pkt; parsed_len += PIM_BSM_GRP_LEN; pkt += PIM_BSM_GRP_LEN; buf += PIM_BSM_GRP_LEN; this_pkt_rem -= PIM_BSM_GRP_LEN; /* initialize rp count and total_rp_cnt before the rp loop */ this_rp_cnt = 0; total_rp_cnt = grpinfo->frag_rp_count; /* Loop till all RPs for the group parsed */ while (this_rp_cnt < total_rp_cnt) { /* All RP from a group processed here. * group is pointed by grpinfo. * At this point make sure buf pointing to a RP * within a group */ rp_fit_cnt = this_pkt_rem / PIM_BSM_RP_LEN; /* calculate how many rp am i going to copy in * this frag */ if (rp_fit_cnt > (total_rp_cnt - this_rp_cnt)) frag_rp_cnt = total_rp_cnt - this_rp_cnt; else frag_rp_cnt = rp_fit_cnt; /* populate the frag rp count for the current grp */ curgrp->frag_rp_count = frag_rp_cnt; copy_byte_count = frag_rp_cnt * PIM_BSM_RP_LEN; /* copy all the rp that we are fitting in this * frag for the grp */ memcpy(pkt, buf, copy_byte_count); this_rp_cnt += frag_rp_cnt; buf += copy_byte_count; pkt += copy_byte_count; parsed_len += copy_byte_count; this_pkt_rem -= copy_byte_count; /* Either we couldn't fit all rp for the group or the * mtu reached */ if ((this_rp_cnt < total_rp_cnt) || (this_pkt_rem < (PIM_BSM_GRP_LEN + PIM_BSM_RP_LEN))) { /* No space to fit in more rp, send this pkt */ this_pkt_len = pim_mtu - this_pkt_rem; pim_msg_build_header(pak_start, this_pkt_len, PIM_MSG_TYPE_BOOTSTRAP, no_fwd); pim_bsm_send_intf(pak_start, this_pkt_len, ifp, dst_addr); /* Construct next fragment. Reuse old packet */ pkt = firstgrp_ptr; this_pkt_rem = pim_mtu - (PIM_BSM_HDR_LEN + PIM_MSG_HEADER_LEN); /* If pkt can't accomodate next group + atleast * one rp, we must break out of this inner loop * and process next RP */ if (total_rp_cnt == this_rp_cnt) break; /* If some more RPs for the same group pending, * fill grp hdr */ memcpy(pkt, (uint8_t *)grpinfo, PIM_BSM_GRP_LEN); curgrp = (struct bsmmsg_grpinfo *)pkt; pkt += PIM_BSM_GRP_LEN; this_pkt_rem -= PIM_BSM_GRP_LEN; pak_pending = false; } else { /* We filled something but not yet sent out */ pak_pending = true; } } /* while RP count */ } /*while parsed len */ /* Send if we have any unsent packet */ if (pak_pending) { this_pkt_len = pim_mtu - this_pkt_rem; pim_msg_build_header(pak_start, this_pkt_len, PIM_MSG_TYPE_BOOTSTRAP, no_fwd); pim_bsm_send_intf(pak_start, (pim_mtu - this_pkt_rem), ifp, dst_addr); } XFREE(MTYPE_PIM_BSM_PKT_VAR_MEM, pak_start); return true; } static void pim_bsm_fwd_whole_sz(struct pim_instance *pim, uint8_t *buf, uint32_t len, int sz) { struct interface *ifp; struct pim_interface *pim_ifp; struct in_addr dst_addr; uint32_t pim_mtu; bool no_fwd = false; bool ret = false; /* For now only global scope zone is supported, so send on all * pim interfaces in the vrf */ dst_addr = qpim_all_pim_routers_addr; FOR_ALL_INTERFACES (pim->vrf, ifp) { pim_ifp = ifp->info; if ((!pim_ifp) || (!pim_ifp->bsm_enable)) continue; pim_hello_require(ifp); pim_mtu = ifp->mtu - MAX_IP_HDR_LEN; if (pim_mtu < len) { ret = pim_bsm_frag_send(buf, len, ifp, pim_mtu, dst_addr, no_fwd); if (PIM_DEBUG_BSM) zlog_debug("%s: pim_bsm_frag_send returned %s", __PRETTY_FUNCTION__, ret ? "TRUE" : "FALSE"); } else { pim_msg_build_header(buf, len, PIM_MSG_TYPE_BOOTSTRAP, no_fwd); if (!pim_bsm_send_intf(buf, len, ifp, dst_addr)) { if (PIM_DEBUG_BSM) zlog_debug( "%s: pim_bsm_send_intf returned false", __PRETTY_FUNCTION__); } } } } bool pim_bsm_new_nbr_fwd(struct pim_neighbor *neigh, struct interface *ifp) { struct in_addr dst_addr; struct pim_interface *pim_ifp; struct bsm_scope *scope; struct listnode *bsm_ln; struct bsm_info *bsminfo; char neigh_src_str[INET_ADDRSTRLEN]; uint32_t pim_mtu; bool no_fwd = true; bool ret = false; if (PIM_DEBUG_BSM) { pim_inet4_dump("", neigh->source_addr, neigh_src_str, sizeof(neigh_src_str)); zlog_debug("%s: New neighbor %s seen on %s", __PRETTY_FUNCTION__, neigh_src_str, ifp->name); } pim_ifp = ifp->info; /* DR only forwards BSM packet */ if (pim_ifp->pim_dr_addr.s_addr == pim_ifp->primary_address.s_addr) { if (PIM_DEBUG_BSM) zlog_debug( "%s: It is not DR, so don't forward BSM packet", __PRETTY_FUNCTION__); } if (!pim_ifp->bsm_enable) { if (PIM_DEBUG_BSM) zlog_debug("%s: BSM proc not enabled on %s", __PRETTY_FUNCTION__, ifp->name); return ret; } scope = &pim_ifp->pim->global_scope; if (!scope->bsm_list->count) { if (PIM_DEBUG_BSM) zlog_debug("%s: BSM list for the scope is empty", __PRETTY_FUNCTION__); return ret; } if (!pim_ifp->ucast_bsm_accept) { dst_addr = qpim_all_pim_routers_addr; if (PIM_DEBUG_BSM) zlog_debug("%s: Sending BSM mcast to %s", __PRETTY_FUNCTION__, neigh_src_str); } else { dst_addr = neigh->source_addr; if (PIM_DEBUG_BSM) zlog_debug("%s: Sending BSM ucast to %s", __PRETTY_FUNCTION__, neigh_src_str); } pim_mtu = ifp->mtu - MAX_IP_HDR_LEN; pim_hello_require(ifp); for (ALL_LIST_ELEMENTS_RO(scope->bsm_list, bsm_ln, bsminfo)) { if (pim_mtu < bsminfo->size) { ret = pim_bsm_frag_send(bsminfo->bsm, bsminfo->size, ifp, pim_mtu, dst_addr, no_fwd); if (!ret) { if (PIM_DEBUG_BSM) zlog_debug( "%s: pim_bsm_frag_send failed", __PRETTY_FUNCTION__); } } else { /* Pim header needs to be constructed */ pim_msg_build_header(bsminfo->bsm, bsminfo->size, PIM_MSG_TYPE_BOOTSTRAP, no_fwd); ret = pim_bsm_send_intf(bsminfo->bsm, bsminfo->size, ifp, dst_addr); if (!ret) { if (PIM_DEBUG_BSM) zlog_debug( "%s: pim_bsm_frag_send failed", __PRETTY_FUNCTION__); } } } return ret; } struct bsgrp_node *pim_bsm_get_bsgrp_node(struct bsm_scope *scope, struct prefix *grp) { struct route_node *rn; struct bsgrp_node *bsgrp; rn = route_node_lookup(scope->bsrp_table, grp); if (!rn) { if (PIM_DEBUG_BSM) zlog_debug("%s: Route node doesn't exist for the group", __PRETTY_FUNCTION__); return NULL; } bsgrp = rn->info; route_unlock_node(rn); return bsgrp; } static uint32_t hash_calc_on_grp_rp(struct prefix group, struct in_addr rp, uint8_t hashmasklen) { uint64_t temp; uint32_t hash; uint32_t grpaddr; uint32_t rp_add; uint32_t mask = 0xffffffff; /* mask to be made zero if hashmasklen is 0 because mask << 32 * may not give 0. hashmasklen can be 0 to 32. */ if (hashmasklen == 0) mask = 0; /* in_addr stores ip in big endian, hence network byte order * convert to uint32 before processing hash */ grpaddr = ntohl(group.u.prefix4.s_addr); /* Avoid shifting by 32 bit on a 32 bit register */ if (hashmasklen) grpaddr = grpaddr & ((mask << (32 - hashmasklen))); else grpaddr = grpaddr & mask; rp_add = ntohl(rp.s_addr); temp = 1103515245 * ((1103515245 * grpaddr + 12345) ^ rp_add) + 12345; hash = temp & (0x7fffffff); return hash; } static bool pim_install_bsm_grp_rp(struct pim_instance *pim, struct bsgrp_node *grpnode, struct bsmmsg_rpinfo *rp) { struct bsm_rpinfo *bsm_rpinfo; uint8_t hashMask_len = pim->global_scope.hashMasklen; /*memory allocation for bsm_rpinfo */ bsm_rpinfo = XCALLOC(MTYPE_PIM_BSRP_NODE, sizeof(*bsm_rpinfo)); bsm_rpinfo->rp_prio = rp->rp_pri; bsm_rpinfo->rp_holdtime = rp->rp_holdtime; memcpy(&bsm_rpinfo->rp_address, &rp->rpaddr.addr, sizeof(struct in_addr)); bsm_rpinfo->elapse_time = 0; /* Back pointer to the group node. */ bsm_rpinfo->bsgrp_node = grpnode; /* update hash for this rp node */ bsm_rpinfo->hash = hash_calc_on_grp_rp(grpnode->group, rp->rpaddr.addr, hashMask_len); if (listnode_add_sort_nodup(grpnode->partial_bsrp_list, bsm_rpinfo)) { if (PIM_DEBUG_BSM) zlog_debug( "%s, bs_rpinfo node added to the partial bs_rplist.\r\n", __PRETTY_FUNCTION__); return true; } if (PIM_DEBUG_BSM) zlog_debug("%s: list node not added\n", __PRETTY_FUNCTION__); XFREE(MTYPE_PIM_BSRP_NODE, bsm_rpinfo); return false; } static void pim_update_pending_rp_cnt(struct bsm_scope *sz, struct bsgrp_node *bsgrp, uint16_t bsm_frag_tag, uint32_t total_rp_count) { if (bsgrp->pend_rp_cnt) { /* received bsm is different packet , * it is not same fragment. */ if (bsm_frag_tag != bsgrp->frag_tag) { if (PIM_DEBUG_BSM) zlog_debug( "%s,Received a new BSM ,so clear the pending bs_rpinfo list.\r\n", __PRETTY_FUNCTION__); list_delete_all_node(bsgrp->partial_bsrp_list); bsgrp->pend_rp_cnt = total_rp_count; } } else bsgrp->pend_rp_cnt = total_rp_count; bsgrp->frag_tag = bsm_frag_tag; } /* Parsing BSR packet and adding to partial list of corresponding bsgrp node */ static bool pim_bsm_parse_install_g2rp(struct bsm_scope *scope, uint8_t *buf, int buflen, uint16_t bsm_frag_tag) { struct bsmmsg_grpinfo grpinfo; struct bsmmsg_rpinfo rpinfo; struct prefix group; struct bsgrp_node *bsgrp = NULL; int frag_rp_cnt = 0; int offset = 0; int ins_count = 0; while (buflen > offset) { if (offset + (int)sizeof(struct bsmmsg_grpinfo) > buflen) { if (PIM_DEBUG_BSM) zlog_debug( "%s: buflen received %d is less than the internal data structure of the packet would suggest", __PRETTY_FUNCTION__, buflen); return false; } /* Extract Group tlv from BSM */ memcpy(&grpinfo, buf, sizeof(struct bsmmsg_grpinfo)); if (PIM_DEBUG_BSM) { char grp_str[INET_ADDRSTRLEN]; pim_inet4_dump("", grpinfo.group.addr, grp_str, sizeof(grp_str)); zlog_debug( "%s, Group %s Rpcount:%d Fragment-Rp-count:%d\r\n", __PRETTY_FUNCTION__, grp_str, grpinfo.rp_count, grpinfo.frag_rp_count); } buf += sizeof(struct bsmmsg_grpinfo); offset += sizeof(struct bsmmsg_grpinfo); if (grpinfo.rp_count == 0) { if (PIM_DEBUG_BSM) { char grp_str[INET_ADDRSTRLEN]; pim_inet4_dump("", grpinfo.group.addr, grp_str, sizeof(grp_str)); zlog_debug( "%s, Rp count is zero for group: %s\r\n", __PRETTY_FUNCTION__, grp_str); } return false; } group.family = AF_INET; if (grpinfo.group.mask > IPV4_MAX_BITLEN) { if (PIM_DEBUG_BSM) zlog_debug("%s, v4 prefix length specified: %d is too long", __PRETTY_FUNCTION__, grpinfo.group.mask); return false; } group.prefixlen = grpinfo.group.mask; group.u.prefix4.s_addr = grpinfo.group.addr.s_addr; /* Get the Group node for the BSM rp table */ bsgrp = pim_bsm_get_bsgrp_node(scope, &group); if (!bsgrp) { if (PIM_DEBUG_BSM) zlog_debug( "%s, Create new BSM Group node.\r\n", __PRETTY_FUNCTION__); /* create a new node to be added to the tree. */ bsgrp = pim_bsm_new_bsgrp_node(scope->bsrp_table, &group); if (!bsgrp) { zlog_debug( "%s, Failed to get the BSM group node.\r\n", __PRETTY_FUNCTION__); continue; } bsgrp->scope = scope; } pim_update_pending_rp_cnt(scope, bsgrp, bsm_frag_tag, grpinfo.rp_count); frag_rp_cnt = grpinfo.frag_rp_count; ins_count = 0; while (frag_rp_cnt--) { if (offset + (int)sizeof(struct bsmmsg_rpinfo) > buflen) { if (PIM_DEBUG_BSM) zlog_debug( "%s, buflen received: %u is less than the internal data structure of the packet would suggest", __PRETTY_FUNCTION__, buflen); return false; } /* Extract RP address tlv from BSM */ memcpy(&rpinfo, buf, sizeof(struct bsmmsg_rpinfo)); rpinfo.rp_holdtime = ntohs(rpinfo.rp_holdtime); buf += sizeof(struct bsmmsg_rpinfo); offset += sizeof(struct bsmmsg_rpinfo); if (PIM_DEBUG_BSM) { char rp_str[INET_ADDRSTRLEN]; pim_inet4_dump("", rpinfo.rpaddr.addr, rp_str, sizeof(rp_str)); zlog_debug( "%s, Rp address - %s; pri:%d hold:%d\r\n", __PRETTY_FUNCTION__, rp_str, rpinfo.rp_pri, rpinfo.rp_holdtime); } /* Call Install api to update grp-rp mappings */ if (pim_install_bsm_grp_rp(scope->pim, bsgrp, &rpinfo)) ins_count++; } bsgrp->pend_rp_cnt -= ins_count; if (!bsgrp->pend_rp_cnt) { if (PIM_DEBUG_BSM) zlog_debug( "%s, Recvd all the rps for this group, so bsrp list with penidng rp list.", __PRETTY_FUNCTION__); /* replace the bsrp_list with pending list */ pim_instate_pend_list(bsgrp); } } return true; } int pim_bsm_process(struct interface *ifp, struct ip *ip_hdr, uint8_t *buf, uint32_t buf_size, bool no_fwd) { struct bsm_hdr *bshdr; int sz = PIM_GBL_SZ_ID; struct bsmmsg_grpinfo *msg_grp; struct pim_interface *pim_ifp = NULL; struct bsm_info *bsminfo; struct pim_instance *pim; char bsr_str[INET_ADDRSTRLEN]; uint16_t frag_tag; bool empty_bsm = false; /* BSM Packet acceptance validation */ pim_ifp = ifp->info; if (!pim_ifp) { if (PIM_DEBUG_BSM) zlog_debug("%s: multicast not enabled on interface %s", __PRETTY_FUNCTION__, ifp->name); return -1; } pim_ifp->pim_ifstat_bsm_rx++; pim = pim_ifp->pim; pim->bsm_rcvd++; /* Drop if bsm processing is disabled on interface */ if (!pim_ifp->bsm_enable) { zlog_warn("%s: BSM not enabled on interface %s", __PRETTY_FUNCTION__, ifp->name); pim_ifp->pim_ifstat_bsm_cfg_miss++; pim->bsm_dropped++; return -1; } if (buf_size < (PIM_MSG_HEADER_LEN + sizeof(struct bsm_hdr))) { if (PIM_DEBUG_BSM) zlog_debug("%s: received buffer length of %d which is too small to properly decode", __PRETTY_FUNCTION__, buf_size); return -1; } bshdr = (struct bsm_hdr *)(buf + PIM_MSG_HEADER_LEN); pim_inet4_dump("", bshdr->bsr_addr.addr, bsr_str, sizeof(bsr_str)); pim->global_scope.hashMasklen = bshdr->hm_len; frag_tag = ntohs(bshdr->frag_tag); /* Identify empty BSM */ if ((buf_size - PIM_BSM_HDR_LEN - PIM_MSG_HEADER_LEN) < PIM_BSM_GRP_LEN) empty_bsm = true; if (!empty_bsm) { msg_grp = (struct bsmmsg_grpinfo *)(buf + PIM_MSG_HEADER_LEN + PIM_BSM_HDR_LEN); /* Currently we don't support scope zoned BSM */ if (msg_grp->group.sz) { if (PIM_DEBUG_BSM) zlog_debug( "%s : Administratively scoped range BSM received", __PRETTY_FUNCTION__); pim_ifp->pim_ifstat_bsm_invalid_sz++; pim->bsm_dropped++; return -1; } } /* Drop if bsr is not preferred bsr */ if (!is_preferred_bsr(pim, bshdr->bsr_addr.addr, bshdr->bsr_prio)) { if (PIM_DEBUG_BSM) zlog_debug("%s : Received a non-preferred BSM", __PRETTY_FUNCTION__); pim->bsm_dropped++; return -1; } if (no_fwd) { /* only accept no-forward BSM if quick refresh on startup */ if ((pim->global_scope.accept_nofwd_bsm) || (frag_tag == pim->global_scope.bsm_frag_tag)) { pim->global_scope.accept_nofwd_bsm = false; } else { if (PIM_DEBUG_BSM) zlog_debug( "%s : nofwd_bsm received on %s when accpt_nofwd_bsm false", __PRETTY_FUNCTION__, bsr_str); pim->bsm_dropped++; pim_ifp->pim_ifstat_ucast_bsm_cfg_miss++; return -1; } } /* Mulicast BSM received */ if (ip_hdr->ip_dst.s_addr == qpim_all_pim_routers_addr.s_addr) { if (!no_fwd) { if (!pim_bsr_rpf_check(pim, bshdr->bsr_addr.addr, ip_hdr->ip_src)) { if (PIM_DEBUG_BSM) zlog_debug( "%s : RPF check fail for BSR address %s", __PRETTY_FUNCTION__, bsr_str); pim->bsm_dropped++; return -1; } } } else if (if_lookup_exact_address(&ip_hdr->ip_dst, AF_INET, pim->vrf_id)) { /* Unicast BSM received - if ucast bsm not enabled on * the interface, drop it */ if (!pim_ifp->ucast_bsm_accept) { if (PIM_DEBUG_BSM) zlog_debug( "%s : Unicast BSM not enabled on interface %s", __PRETTY_FUNCTION__, ifp->name); pim_ifp->pim_ifstat_ucast_bsm_cfg_miss++; pim->bsm_dropped++; return -1; } } else { if (PIM_DEBUG_BSM) zlog_debug("%s : Invalid destination address", __PRETTY_FUNCTION__); pim->bsm_dropped++; return -1; } if (empty_bsm) { if (PIM_DEBUG_BSM) zlog_debug("%s : Empty Pref BSM received", __PRETTY_FUNCTION__); } /* Parse Update bsm rp table and install/uninstall rp if required */ if (!pim_bsm_parse_install_g2rp( &pim_ifp->pim->global_scope, (buf + PIM_BSM_HDR_LEN + PIM_MSG_HEADER_LEN), (buf_size - PIM_BSM_HDR_LEN - PIM_MSG_HEADER_LEN), frag_tag)) { if (PIM_DEBUG_BSM) { zlog_debug("%s, Parsing BSM failed.\r\n", __PRETTY_FUNCTION__); } pim->bsm_dropped++; return -1; } /* Restart the bootstrap timer */ pim_bs_timer_restart(&pim_ifp->pim->global_scope, PIM_BSR_DEFAULT_TIMEOUT); /* If new BSM received, clear the old bsm database */ if (pim_ifp->pim->global_scope.bsm_frag_tag != frag_tag) { if (PIM_DEBUG_BSM) { zlog_debug("%s: Current frag tag: %d Frag teg rcvd: %d", __PRETTY_FUNCTION__, pim_ifp->pim->global_scope.bsm_frag_tag, frag_tag); } list_delete_all_node(pim_ifp->pim->global_scope.bsm_list); pim_ifp->pim->global_scope.bsm_frag_tag = frag_tag; } /* update the scope information from bsm */ pim_bsm_update(pim, bshdr->bsr_addr.addr, bshdr->bsr_prio); if (!no_fwd) { pim_bsm_fwd_whole_sz(pim_ifp->pim, buf, buf_size, sz); bsminfo = XCALLOC(MTYPE_PIM_BSM_INFO, sizeof(struct bsm_info)); bsminfo->bsm = XCALLOC(MTYPE_PIM_BSM_PKT_VAR_MEM, buf_size); bsminfo->size = buf_size; memcpy(bsminfo->bsm, buf, buf_size); listnode_add(pim_ifp->pim->global_scope.bsm_list, bsminfo); } return 0; } frr-7.2.1/pimd/pim_bsm.h0000644000000000000000000002064213610377563011750 00000000000000/* * pim_bsm.h: PIM BSM handling related * * Copyright (C) 2018-19 Vmware, Inc. * Saravanan K * * This program is free software; you can redistribute it and/or modify * it under the terms of the 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; see the file COPYING; if not, write to the * Free Software Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, * MA 02110-1301 USA */ #ifndef __PIM_BSM_H__ #define __PIM_BSM_H__ #include "if.h" #include "vty.h" #include "linklist.h" #include "table.h" #include "pim_rp.h" #include "pim_msg.h" /* Defines */ #define PIM_GBL_SZ_ID 0 /* global scope zone id set to 0 */ #define PIM_BS_TIME 60 /* RFC 5059 - Sec 5 */ #define PIM_BSR_DEFAULT_TIMEOUT 130 /* RFC 5059 - Sec 5 */ /* These structures are only encoded IPv4 specific */ #define PIM_BSM_HDR_LEN sizeof(struct bsm_hdr) #define PIM_BSM_GRP_LEN sizeof(struct bsmmsg_grpinfo) #define PIM_BSM_RP_LEN sizeof(struct bsmmsg_rpinfo) #define PIM_MIN_BSM_LEN \ (PIM_HDR_LEN + PIM_BSM_HDR_LEN + PIM_BSM_GRP_LEN + PIM_BSM_RP_LEN) /* Datastructures * ============== */ /* Non candidate BSR states */ enum ncbsr_state { NO_INFO = 0, ACCEPT_ANY, ACCEPT_PREFERRED }; /* BSM scope - bsm processing is per scope */ struct bsm_scope { int sz_id; /* scope zone id */ enum ncbsr_state state; /* non candidate BSR state */ bool accept_nofwd_bsm; /* no fwd bsm accepted for scope */ struct in_addr current_bsr; /* current elected BSR for the sz */ uint32_t current_bsr_prio; /* current BSR priority */ int64_t current_bsr_first_ts; /* current BSR elected time */ int64_t current_bsr_last_ts; /* Last BSM received from E-BSR */ uint16_t bsm_frag_tag; /* Last received frag tag from E-BSR */ uint8_t hashMasklen; /* Mask in hash calc RFC 7761 4.7.2 */ struct pim_instance *pim; /* Back pointer to pim instance */ struct list *bsm_list; /* list of bsm frag for frowarding */ struct route_table *bsrp_table; /* group2rp mapping rcvd from BSR */ struct thread *bs_timer; /* Boot strap timer */ struct thread *sz_timer; }; /* BSM packet - this is stored as list in bsm_list inside scope * This is used for forwarding to new neighbors or restarting mcast routers */ struct bsm_info { uint32_t size; /* size of the packet */ unsigned char *bsm; /* Actual packet */ }; /* This is the group node of the bsrp table in scope. * this node maintains the list of rp for the group. */ struct bsgrp_node { struct prefix group; /* Group range */ struct bsm_scope *scope; /* Back ptr to scope */ struct list *bsrp_list; /* list of RPs adv by BSR */ struct list *partial_bsrp_list; /* maintained until all RPs received */ int pend_rp_cnt; /* Total RP - Received RP */ uint16_t frag_tag; /* frag tag to identify the fragment */ }; /* This is the list node of bsrp_list and partial bsrp list in * bsgrp_node. Hold info of each RP received for the group */ struct bsm_rpinfo { uint32_t hash; /* Hash Value as per RFC 7761 4.7.2 */ uint32_t elapse_time; /* upd at expiry of elected RP node */ uint16_t rp_prio; /* RP priority */ uint16_t rp_holdtime; /* RP holdtime - g2rp timer value */ struct in_addr rp_address; /* RP Address */ struct bsgrp_node *bsgrp_node; /* Back ptr to bsgrp_node */ struct thread *g2rp_timer; /* Run only for elected RP node */ }; /* Structures to extract Bootstrap Message header and Grp to RP Mappings * ===================================================================== * BSM Format: * * 0 1 2 3 * 0 1 2 3 4 5 6 7 0 1 2 3 4 5 6 7 0 1 2 3 4 5 6 7 0 1 2 3 4 5 6 7 * +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ * |PIM Ver| Type |N| Reserved | Checksum | PIM HDR * +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ * | Fragment Tag | Hash Mask Len | BSR Priority | BS HDR(1) * +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ * | BSR Address (Encoded-Unicast format) | BS HDR(2) * +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ * | Group Address 1 (Encoded-Group format) | * +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ * | RP Count 1 | Frag RP Cnt 1 | Reserved | * +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ * | RP Address 1 (Encoded-Unicast format) | * +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ * | RP1 Holdtime | RP1 Priority | Reserved | * +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ * | RP Address 2 (Encoded-Unicast format) | * +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ * | RP2 Holdtime | RP2 Priority | Reserved | * +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ * | . | * | . | * +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ * | RP Address m (Encoded-Unicast format) | * +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ * | RPm Holdtime | RPm Priority | Reserved | * +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ * | Group Address 2 (Encoded-Group format) | * +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ * | . | * | . | * +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ * | Group Address n (Encoded-Group format) | * +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ * | RP Count n | Frag RP Cnt n | Reserved | * +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ * | RP Address 1 (Encoded-Unicast format) | * +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ * | RP1 Holdtime | RP1 Priority | Reserved | * +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ * | RP Address 2 (Encoded-Unicast format) | * +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ * | RP2 Holdtime | RP2 Priority | Reserved | * +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ * | . | * | . | * +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ * | RP Address m (Encoded-Unicast format) | * +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ * | RPm Holdtime | RPm Priority | Reserved | * +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ */ struct bsm_hdr { uint16_t frag_tag; uint8_t hm_len; uint8_t bsr_prio; struct pim_encoded_ipv4_unicast bsr_addr; } __attribute__((packed)); struct bsmmsg_grpinfo { struct pim_encoded_group_ipv4 group; uint8_t rp_count; uint8_t frag_rp_count; uint16_t reserved; } __attribute__((packed)); struct bsmmsg_rpinfo { struct pim_encoded_ipv4_unicast rpaddr; uint16_t rp_holdtime; uint8_t rp_pri; uint8_t reserved; } __attribute__((packed)); /* API */ void pim_bsm_proc_init(struct pim_instance *pim); void pim_bsm_proc_free(struct pim_instance *pim); void pim_bsm_write_config(struct vty *vty, struct interface *ifp); int pim_bsm_process(struct interface *ifp, struct ip *ip_hdr, uint8_t *buf, uint32_t buf_size, bool no_fwd); bool pim_bsm_new_nbr_fwd(struct pim_neighbor *neigh, struct interface *ifp); struct bsgrp_node *pim_bsm_get_bsgrp_node(struct bsm_scope *scope, struct prefix *grp); #endif frr-7.2.1/pimd/pim_cmd.c0000644000000000000000000103521413610377563011727 00000000000000/* * PIM for Quagga * Copyright (C) 2008 Everton da Silva Marques * * This program is free software; you can redistribute it and/or modify * it under the terms of the 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; see the file COPYING; if not, write to the Free Software * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA */ #include #include "lib/json.h" #include "command.h" #include "if.h" #include "prefix.h" #include "zclient.h" #include "plist.h" #include "hash.h" #include "nexthop.h" #include "vrf.h" #include "ferr.h" #include "pimd.h" #include "pim_mroute.h" #include "pim_cmd.h" #include "pim_iface.h" #include "pim_vty.h" #include "pim_mroute.h" #include "pim_str.h" #include "pim_igmp.h" #include "pim_igmpv3.h" #include "pim_sock.h" #include "pim_time.h" #include "pim_util.h" #include "pim_oil.h" #include "pim_neighbor.h" #include "pim_pim.h" #include "pim_ifchannel.h" #include "pim_hello.h" #include "pim_msg.h" #include "pim_upstream.h" #include "pim_rpf.h" #include "pim_macro.h" #include "pim_ssmpingd.h" #include "pim_zebra.h" #include "pim_static.h" #include "pim_rp.h" #include "pim_zlookup.h" #include "pim_msdp.h" #include "pim_ssm.h" #include "pim_nht.h" #include "pim_bfd.h" #include "pim_vxlan.h" #include "bfd.h" #include "pim_bsm.h" #ifndef VTYSH_EXTRACT_PL #include "pimd/pim_cmd_clippy.c" #endif static struct cmd_node interface_node = { INTERFACE_NODE, "%s(config-if)# ", 1 /* vtysh ? yes */ }; static struct cmd_node debug_node = {DEBUG_NODE, "", 1}; static struct vrf *pim_cmd_lookup_vrf(struct vty *vty, struct cmd_token *argv[], const int argc, int *idx) { struct vrf *vrf; if (argv_find(argv, argc, "NAME", idx)) vrf = vrf_lookup_by_name(argv[*idx]->arg); else vrf = vrf_lookup_by_id(VRF_DEFAULT); if (!vrf) vty_out(vty, "Specified VRF: %s does not exist\n", argv[*idx]->arg); return vrf; } static void pim_if_membership_clear(struct interface *ifp) { struct pim_interface *pim_ifp; pim_ifp = ifp->info; zassert(pim_ifp); if (PIM_IF_TEST_PIM(pim_ifp->options) && PIM_IF_TEST_IGMP(pim_ifp->options)) { return; } pim_ifchannel_membership_clear(ifp); } /* When PIM is disabled on interface, IGMPv3 local membership information is not injected into PIM interface state. The function pim_if_membership_refresh() fetches all IGMPv3 local membership information into PIM. It is intented to be called whenever PIM is enabled on the interface in order to collect missed local membership information. */ static void pim_if_membership_refresh(struct interface *ifp) { struct pim_interface *pim_ifp; struct listnode *sock_node; struct igmp_sock *igmp; pim_ifp = ifp->info; zassert(pim_ifp); if (!PIM_IF_TEST_PIM(pim_ifp->options)) return; if (!PIM_IF_TEST_IGMP(pim_ifp->options)) return; /* First clear off membership from all PIM (S,G) entries on the interface */ pim_ifchannel_membership_clear(ifp); /* Then restore PIM (S,G) membership from all IGMPv3 (S,G) entries on the interface */ /* scan igmp sockets */ for (ALL_LIST_ELEMENTS_RO(pim_ifp->igmp_socket_list, sock_node, igmp)) { struct listnode *grpnode; struct igmp_group *grp; /* scan igmp groups */ for (ALL_LIST_ELEMENTS_RO(igmp->igmp_group_list, grpnode, grp)) { struct listnode *srcnode; struct igmp_source *src; /* scan group sources */ for (ALL_LIST_ELEMENTS_RO(grp->group_source_list, srcnode, src)) { if (IGMP_SOURCE_TEST_FORWARDING( src->source_flags)) { struct prefix_sg sg; memset(&sg, 0, sizeof(struct prefix_sg)); sg.src = src->source_addr; sg.grp = grp->group_addr; pim_ifchannel_local_membership_add(ifp, &sg); } } /* scan group sources */ } /* scan igmp groups */ } /* scan igmp sockets */ /* Finally delete every PIM (S,G) entry lacking all state info */ pim_ifchannel_delete_on_noinfo(ifp); } static void pim_show_assert_helper(struct vty *vty, struct pim_interface *pim_ifp, struct pim_ifchannel *ch, time_t now) { char ch_src_str[INET_ADDRSTRLEN]; char ch_grp_str[INET_ADDRSTRLEN]; char winner_str[INET_ADDRSTRLEN]; struct in_addr ifaddr; char uptime[10]; char timer[10]; ifaddr = pim_ifp->primary_address; pim_inet4_dump("", ch->sg.src, ch_src_str, sizeof(ch_src_str)); pim_inet4_dump("", ch->sg.grp, ch_grp_str, sizeof(ch_grp_str)); pim_inet4_dump("", ch->ifassert_winner, winner_str, sizeof(winner_str)); pim_time_uptime(uptime, sizeof(uptime), now - ch->ifassert_creation); pim_time_timer_to_mmss(timer, sizeof(timer), ch->t_ifassert_timer); vty_out(vty, "%-16s %-15s %-15s %-15s %-6s %-15s %-8s %-5s\n", ch->interface->name, inet_ntoa(ifaddr), ch_src_str, ch_grp_str, pim_ifchannel_ifassert_name(ch->ifassert_state), winner_str, uptime, timer); } static void pim_show_assert(struct pim_instance *pim, struct vty *vty) { struct pim_interface *pim_ifp; struct pim_ifchannel *ch; struct interface *ifp; time_t now; now = pim_time_monotonic_sec(); vty_out(vty, "Interface Address Source Group State Winner Uptime Timer\n"); FOR_ALL_INTERFACES (pim->vrf, ifp) { pim_ifp = ifp->info; if (!pim_ifp) continue; RB_FOREACH (ch, pim_ifchannel_rb, &pim_ifp->ifchannel_rb) { pim_show_assert_helper(vty, pim_ifp, ch, now); } /* scan interface channels */ } } static void pim_show_assert_internal_helper(struct vty *vty, struct pim_interface *pim_ifp, struct pim_ifchannel *ch) { char ch_src_str[INET_ADDRSTRLEN]; char ch_grp_str[INET_ADDRSTRLEN]; struct in_addr ifaddr; ifaddr = pim_ifp->primary_address; pim_inet4_dump("", ch->sg.src, ch_src_str, sizeof(ch_src_str)); pim_inet4_dump("", ch->sg.grp, ch_grp_str, sizeof(ch_grp_str)); vty_out(vty, "%-16s %-15s %-15s %-15s %-3s %-3s %-3s %-4s\n", ch->interface->name, inet_ntoa(ifaddr), ch_src_str, ch_grp_str, PIM_IF_FLAG_TEST_COULD_ASSERT(ch->flags) ? "yes" : "no", pim_macro_ch_could_assert_eval(ch) ? "yes" : "no", PIM_IF_FLAG_TEST_ASSERT_TRACKING_DESIRED(ch->flags) ? "yes" : "no", pim_macro_assert_tracking_desired_eval(ch) ? "yes" : "no"); } static void pim_show_assert_internal(struct pim_instance *pim, struct vty *vty) { struct pim_interface *pim_ifp; struct pim_ifchannel *ch; struct interface *ifp; vty_out(vty, "CA: CouldAssert\n" "ECA: Evaluate CouldAssert\n" "ATD: AssertTrackingDesired\n" "eATD: Evaluate AssertTrackingDesired\n\n"); vty_out(vty, "Interface Address Source Group CA eCA ATD eATD\n"); FOR_ALL_INTERFACES (pim->vrf, ifp) { pim_ifp = ifp->info; if (!pim_ifp) continue; RB_FOREACH (ch, pim_ifchannel_rb, &pim_ifp->ifchannel_rb) { pim_show_assert_internal_helper(vty, pim_ifp, ch); } /* scan interface channels */ } } static void pim_show_assert_metric_helper(struct vty *vty, struct pim_interface *pim_ifp, struct pim_ifchannel *ch) { char ch_src_str[INET_ADDRSTRLEN]; char ch_grp_str[INET_ADDRSTRLEN]; char addr_str[INET_ADDRSTRLEN]; struct pim_assert_metric am; struct in_addr ifaddr; ifaddr = pim_ifp->primary_address; am = pim_macro_spt_assert_metric(&ch->upstream->rpf, pim_ifp->primary_address); pim_inet4_dump("", ch->sg.src, ch_src_str, sizeof(ch_src_str)); pim_inet4_dump("", ch->sg.grp, ch_grp_str, sizeof(ch_grp_str)); pim_inet4_dump("", am.ip_address, addr_str, sizeof(addr_str)); vty_out(vty, "%-16s %-15s %-15s %-15s %-3s %4u %6u %-15s\n", ch->interface->name, inet_ntoa(ifaddr), ch_src_str, ch_grp_str, am.rpt_bit_flag ? "yes" : "no", am.metric_preference, am.route_metric, addr_str); } static void pim_show_assert_metric(struct pim_instance *pim, struct vty *vty) { struct pim_interface *pim_ifp; struct pim_ifchannel *ch; struct interface *ifp; vty_out(vty, "Interface Address Source Group RPT Pref Metric Address \n"); FOR_ALL_INTERFACES (pim->vrf, ifp) { pim_ifp = ifp->info; if (!pim_ifp) continue; RB_FOREACH (ch, pim_ifchannel_rb, &pim_ifp->ifchannel_rb) { pim_show_assert_metric_helper(vty, pim_ifp, ch); } /* scan interface channels */ } } static void pim_show_assert_winner_metric_helper(struct vty *vty, struct pim_interface *pim_ifp, struct pim_ifchannel *ch) { char ch_src_str[INET_ADDRSTRLEN]; char ch_grp_str[INET_ADDRSTRLEN]; char addr_str[INET_ADDRSTRLEN]; struct pim_assert_metric *am; struct in_addr ifaddr; char pref_str[16]; char metr_str[16]; ifaddr = pim_ifp->primary_address; am = &ch->ifassert_winner_metric; pim_inet4_dump("", ch->sg.src, ch_src_str, sizeof(ch_src_str)); pim_inet4_dump("", ch->sg.grp, ch_grp_str, sizeof(ch_grp_str)); pim_inet4_dump("", am->ip_address, addr_str, sizeof(addr_str)); if (am->metric_preference == PIM_ASSERT_METRIC_PREFERENCE_MAX) snprintf(pref_str, sizeof(pref_str), "INFI"); else snprintf(pref_str, sizeof(pref_str), "%4u", am->metric_preference); if (am->route_metric == PIM_ASSERT_ROUTE_METRIC_MAX) snprintf(metr_str, sizeof(metr_str), "INFI"); else snprintf(metr_str, sizeof(metr_str), "%6u", am->route_metric); vty_out(vty, "%-16s %-15s %-15s %-15s %-3s %-4s %-6s %-15s\n", ch->interface->name, inet_ntoa(ifaddr), ch_src_str, ch_grp_str, am->rpt_bit_flag ? "yes" : "no", pref_str, metr_str, addr_str); } static void pim_show_assert_winner_metric(struct pim_instance *pim, struct vty *vty) { struct pim_interface *pim_ifp; struct pim_ifchannel *ch; struct interface *ifp; vty_out(vty, "Interface Address Source Group RPT Pref Metric Address \n"); FOR_ALL_INTERFACES (pim->vrf, ifp) { pim_ifp = ifp->info; if (!pim_ifp) continue; RB_FOREACH (ch, pim_ifchannel_rb, &pim_ifp->ifchannel_rb) { pim_show_assert_winner_metric_helper(vty, pim_ifp, ch); } /* scan interface channels */ } } static void json_object_pim_ifp_add(struct json_object *json, struct interface *ifp) { struct pim_interface *pim_ifp; pim_ifp = ifp->info; json_object_string_add(json, "name", ifp->name); json_object_string_add(json, "state", if_is_up(ifp) ? "up" : "down"); json_object_string_add(json, "address", inet_ntoa(pim_ifp->primary_address)); json_object_int_add(json, "index", ifp->ifindex); if (if_is_multicast(ifp)) json_object_boolean_true_add(json, "flagMulticast"); if (if_is_broadcast(ifp)) json_object_boolean_true_add(json, "flagBroadcast"); if (ifp->flags & IFF_ALLMULTI) json_object_boolean_true_add(json, "flagAllMulticast"); if (ifp->flags & IFF_PROMISC) json_object_boolean_true_add(json, "flagPromiscuous"); if (PIM_IF_IS_DELETED(ifp)) json_object_boolean_true_add(json, "flagDeleted"); if (pim_if_lan_delay_enabled(ifp)) json_object_boolean_true_add(json, "lanDelayEnabled"); } static void pim_show_membership_helper(struct vty *vty, struct pim_interface *pim_ifp, struct pim_ifchannel *ch, struct json_object *json) { char ch_src_str[INET_ADDRSTRLEN]; char ch_grp_str[INET_ADDRSTRLEN]; json_object *json_iface = NULL; json_object *json_row = NULL; pim_inet4_dump("", ch->sg.src, ch_src_str, sizeof(ch_src_str)); pim_inet4_dump("", ch->sg.grp, ch_grp_str, sizeof(ch_grp_str)); json_object_object_get_ex(json, ch->interface->name, &json_iface); if (!json_iface) { json_iface = json_object_new_object(); json_object_pim_ifp_add(json_iface, ch->interface); json_object_object_add(json, ch->interface->name, json_iface); } json_row = json_object_new_object(); json_object_string_add(json_row, "source", ch_src_str); json_object_string_add(json_row, "group", ch_grp_str); json_object_string_add(json_row, "localMembership", ch->local_ifmembership == PIM_IFMEMBERSHIP_NOINFO ? "NOINFO" : "INCLUDE"); json_object_object_add(json_iface, ch_grp_str, json_row); } static void pim_show_membership(struct pim_instance *pim, struct vty *vty, bool uj) { struct pim_interface *pim_ifp; struct pim_ifchannel *ch; struct interface *ifp; enum json_type type; json_object *json = NULL; json_object *json_tmp = NULL; json = json_object_new_object(); FOR_ALL_INTERFACES (pim->vrf, ifp) { pim_ifp = ifp->info; if (!pim_ifp) continue; RB_FOREACH (ch, pim_ifchannel_rb, &pim_ifp->ifchannel_rb) { pim_show_membership_helper(vty, pim_ifp, ch, json); } /* scan interface channels */ } if (uj) { vty_out(vty, "%s\n", json_object_to_json_string_ext( json, JSON_C_TO_STRING_PRETTY)); } else { vty_out(vty, "Interface Address Source Group Membership\n"); /* * Example of the json data we are traversing * * { * "swp3":{ * "name":"swp3", * "state":"up", * "address":"10.1.20.1", * "index":5, * "flagMulticast":true, * "flagBroadcast":true, * "lanDelayEnabled":true, * "226.10.10.10":{ * "source":"*", * "group":"226.10.10.10", * "localMembership":"INCLUDE" * } * } * } */ /* foreach interface */ json_object_object_foreach(json, key, val) { /* Find all of the keys where the val is an object. In * the example * above the only one is 226.10.10.10 */ json_object_object_foreach(val, if_field_key, if_field_val) { type = json_object_get_type(if_field_val); if (type == json_type_object) { vty_out(vty, "%-16s ", key); json_object_object_get_ex( val, "address", &json_tmp); vty_out(vty, "%-15s ", json_object_get_string( json_tmp)); json_object_object_get_ex(if_field_val, "source", &json_tmp); vty_out(vty, "%-15s ", json_object_get_string( json_tmp)); /* Group */ vty_out(vty, "%-15s ", if_field_key); json_object_object_get_ex( if_field_val, "localMembership", &json_tmp); vty_out(vty, "%-10s\n", json_object_get_string( json_tmp)); } } } } json_object_free(json); } static void pim_print_ifp_flags(struct vty *vty, struct interface *ifp, int mloop) { vty_out(vty, "Flags\n"); vty_out(vty, "-----\n"); vty_out(vty, "All Multicast : %s\n", (ifp->flags & IFF_ALLMULTI) ? "yes" : "no"); vty_out(vty, "Broadcast : %s\n", if_is_broadcast(ifp) ? "yes" : "no"); vty_out(vty, "Deleted : %s\n", PIM_IF_IS_DELETED(ifp) ? "yes" : "no"); vty_out(vty, "Interface Index : %d\n", ifp->ifindex); vty_out(vty, "Multicast : %s\n", if_is_multicast(ifp) ? "yes" : "no"); vty_out(vty, "Multicast Loop : %d\n", mloop); vty_out(vty, "Promiscuous : %s\n", (ifp->flags & IFF_PROMISC) ? "yes" : "no"); vty_out(vty, "\n"); vty_out(vty, "\n"); } static void igmp_show_interfaces(struct pim_instance *pim, struct vty *vty, bool uj) { struct interface *ifp; time_t now; json_object *json = NULL; json_object *json_row = NULL; now = pim_time_monotonic_sec(); if (uj) json = json_object_new_object(); else vty_out(vty, "Interface State Address V Querier Query Timer Uptime\n"); FOR_ALL_INTERFACES (pim->vrf, ifp) { struct pim_interface *pim_ifp; struct listnode *sock_node; struct igmp_sock *igmp; pim_ifp = ifp->info; if (!pim_ifp) continue; for (ALL_LIST_ELEMENTS_RO(pim_ifp->igmp_socket_list, sock_node, igmp)) { char uptime[10]; char query_hhmmss[10]; pim_time_uptime(uptime, sizeof(uptime), now - igmp->sock_creation); pim_time_timer_to_hhmmss(query_hhmmss, sizeof(query_hhmmss), igmp->t_igmp_query_timer); if (uj) { json_row = json_object_new_object(); json_object_pim_ifp_add(json_row, ifp); json_object_string_add(json_row, "upTime", uptime); json_object_int_add(json_row, "version", pim_ifp->igmp_version); if (igmp->t_igmp_query_timer) { json_object_boolean_true_add(json_row, "querier"); json_object_string_add(json_row, "queryTimer", query_hhmmss); } json_object_object_add(json, ifp->name, json_row); if (igmp->mtrace_only) { json_object_boolean_true_add( json_row, "mtraceOnly"); } } else { vty_out(vty, "%-16s %5s %15s %d %7s %11s %8s\n", ifp->name, if_is_up(ifp) ? (igmp->mtrace_only ? "mtrc" : "up") : "down", inet_ntoa(igmp->ifaddr), pim_ifp->igmp_version, igmp->t_igmp_query_timer ? "local" : "other", query_hhmmss, uptime); } } } if (uj) { vty_out(vty, "%s\n", json_object_to_json_string_ext( json, JSON_C_TO_STRING_PRETTY)); json_object_free(json); } } static void igmp_show_interfaces_single(struct pim_instance *pim, struct vty *vty, const char *ifname, bool uj) { struct igmp_sock *igmp; struct interface *ifp; struct listnode *sock_node; struct pim_interface *pim_ifp; char uptime[10]; char query_hhmmss[10]; char other_hhmmss[10]; int found_ifname = 0; int sqi; int mloop = 0; long gmi_msec; /* Group Membership Interval */ long lmqt_msec; long ohpi_msec; long oqpi_msec; /* Other Querier Present Interval */ long qri_msec; time_t now; int lmqc; json_object *json = NULL; json_object *json_row = NULL; if (uj) json = json_object_new_object(); now = pim_time_monotonic_sec(); FOR_ALL_INTERFACES (pim->vrf, ifp) { pim_ifp = ifp->info; if (!pim_ifp) continue; if (strcmp(ifname, "detail") && strcmp(ifname, ifp->name)) continue; for (ALL_LIST_ELEMENTS_RO(pim_ifp->igmp_socket_list, sock_node, igmp)) { found_ifname = 1; pim_time_uptime(uptime, sizeof(uptime), now - igmp->sock_creation); pim_time_timer_to_hhmmss(query_hhmmss, sizeof(query_hhmmss), igmp->t_igmp_query_timer); pim_time_timer_to_hhmmss(other_hhmmss, sizeof(other_hhmmss), igmp->t_other_querier_timer); gmi_msec = PIM_IGMP_GMI_MSEC( igmp->querier_robustness_variable, igmp->querier_query_interval, pim_ifp->igmp_query_max_response_time_dsec); sqi = PIM_IGMP_SQI( pim_ifp->igmp_default_query_interval); oqpi_msec = PIM_IGMP_OQPI_MSEC( igmp->querier_robustness_variable, igmp->querier_query_interval, pim_ifp->igmp_query_max_response_time_dsec); lmqt_msec = PIM_IGMP_LMQT_MSEC( pim_ifp->igmp_specific_query_max_response_time_dsec, pim_ifp->igmp_last_member_query_count); ohpi_msec = PIM_IGMP_OHPI_DSEC( igmp->querier_robustness_variable, igmp->querier_query_interval, pim_ifp->igmp_query_max_response_time_dsec) * 100; qri_msec = pim_ifp->igmp_query_max_response_time_dsec * 100; if (pim_ifp->pim_sock_fd >= 0) mloop = pim_socket_mcastloop_get( pim_ifp->pim_sock_fd); else mloop = 0; lmqc = pim_ifp->igmp_last_member_query_count; if (uj) { json_row = json_object_new_object(); json_object_pim_ifp_add(json_row, ifp); json_object_string_add(json_row, "upTime", uptime); json_object_string_add(json_row, "querier", igmp->t_igmp_query_timer ? "local" : "other"); json_object_int_add(json_row, "queryStartCount", igmp->startup_query_count); json_object_string_add(json_row, "queryQueryTimer", query_hhmmss); json_object_string_add(json_row, "queryOtherTimer", other_hhmmss); json_object_int_add(json_row, "version", pim_ifp->igmp_version); json_object_int_add( json_row, "timerGroupMembershipIntervalMsec", gmi_msec); json_object_int_add(json_row, "lastMemberQueryCount", lmqc); json_object_int_add(json_row, "timerLastMemberQueryMsec", lmqt_msec); json_object_int_add( json_row, "timerOlderHostPresentIntervalMsec", ohpi_msec); json_object_int_add( json_row, "timerOtherQuerierPresentIntervalMsec", oqpi_msec); json_object_int_add( json_row, "timerQueryInterval", igmp->querier_query_interval); json_object_int_add( json_row, "timerQueryResponseIntervalMsec", qri_msec); json_object_int_add( json_row, "timerRobustnessVariable", igmp->querier_robustness_variable); json_object_int_add(json_row, "timerStartupQueryInterval", sqi); json_object_object_add(json, ifp->name, json_row); if (igmp->mtrace_only) { json_object_boolean_true_add( json_row, "mtraceOnly"); } } else { vty_out(vty, "Interface : %s\n", ifp->name); vty_out(vty, "State : %s\n", if_is_up(ifp) ? (igmp->mtrace_only ? "mtrace" : "up") : "down"); vty_out(vty, "Address : %s\n", inet_ntoa(pim_ifp->primary_address)); vty_out(vty, "Uptime : %s\n", uptime); vty_out(vty, "Version : %d\n", pim_ifp->igmp_version); vty_out(vty, "\n"); vty_out(vty, "\n"); vty_out(vty, "Querier\n"); vty_out(vty, "-------\n"); vty_out(vty, "Querier : %s\n", igmp->t_igmp_query_timer ? "local" : "other"); vty_out(vty, "Start Count : %d\n", igmp->startup_query_count); vty_out(vty, "Query Timer : %s\n", query_hhmmss); vty_out(vty, "Other Timer : %s\n", other_hhmmss); vty_out(vty, "\n"); vty_out(vty, "\n"); vty_out(vty, "Timers\n"); vty_out(vty, "------\n"); vty_out(vty, "Group Membership Interval : %lis\n", gmi_msec / 1000); vty_out(vty, "Last Member Query Count : %d\n", lmqc); vty_out(vty, "Last Member Query Time : %lis\n", lmqt_msec / 1000); vty_out(vty, "Older Host Present Interval : %lis\n", ohpi_msec / 1000); vty_out(vty, "Other Querier Present Interval : %lis\n", oqpi_msec / 1000); vty_out(vty, "Query Interval : %ds\n", igmp->querier_query_interval); vty_out(vty, "Query Response Interval : %lis\n", qri_msec / 1000); vty_out(vty, "Robustness Variable : %d\n", igmp->querier_robustness_variable); vty_out(vty, "Startup Query Interval : %ds\n", sqi); vty_out(vty, "\n"); vty_out(vty, "\n"); pim_print_ifp_flags(vty, ifp, mloop); } } } if (uj) { vty_out(vty, "%s\n", json_object_to_json_string_ext( json, JSON_C_TO_STRING_PRETTY)); json_object_free(json); } else { if (!found_ifname) vty_out(vty, "%% No such interface\n"); } } static void igmp_show_interface_join(struct pim_instance *pim, struct vty *vty) { struct interface *ifp; time_t now; now = pim_time_monotonic_sec(); vty_out(vty, "Interface Address Source Group Socket Uptime \n"); FOR_ALL_INTERFACES (pim->vrf, ifp) { struct pim_interface *pim_ifp; struct listnode *join_node; struct igmp_join *ij; struct in_addr pri_addr; char pri_addr_str[INET_ADDRSTRLEN]; pim_ifp = ifp->info; if (!pim_ifp) continue; if (!pim_ifp->igmp_join_list) continue; pri_addr = pim_find_primary_addr(ifp); pim_inet4_dump("", pri_addr, pri_addr_str, sizeof(pri_addr_str)); for (ALL_LIST_ELEMENTS_RO(pim_ifp->igmp_join_list, join_node, ij)) { char group_str[INET_ADDRSTRLEN]; char source_str[INET_ADDRSTRLEN]; char uptime[10]; pim_time_uptime(uptime, sizeof(uptime), now - ij->sock_creation); pim_inet4_dump("", ij->group_addr, group_str, sizeof(group_str)); pim_inet4_dump("", ij->source_addr, source_str, sizeof(source_str)); vty_out(vty, "%-16s %-15s %-15s %-15s %6d %8s\n", ifp->name, pri_addr_str, source_str, group_str, ij->sock_fd, uptime); } /* for (pim_ifp->igmp_join_list) */ } /* for (iflist) */ } static void pim_show_interfaces_single(struct pim_instance *pim, struct vty *vty, const char *ifname, bool uj) { struct in_addr ifaddr; struct interface *ifp; struct listnode *neighnode; struct listnode *upnode; struct pim_interface *pim_ifp; struct pim_neighbor *neigh; struct pim_upstream *up; time_t now; char dr_str[INET_ADDRSTRLEN]; char dr_uptime[10]; char expire[10]; char grp_str[INET_ADDRSTRLEN]; char hello_period[10]; char hello_timer[10]; char neigh_src_str[INET_ADDRSTRLEN]; char src_str[INET_ADDRSTRLEN]; char stat_uptime[10]; char uptime[10]; int mloop = 0; int found_ifname = 0; int print_header; json_object *json = NULL; json_object *json_row = NULL; json_object *json_pim_neighbor = NULL; json_object *json_pim_neighbors = NULL; json_object *json_group = NULL; json_object *json_group_source = NULL; json_object *json_fhr_sources = NULL; struct pim_secondary_addr *sec_addr; struct listnode *sec_node; now = pim_time_monotonic_sec(); if (uj) json = json_object_new_object(); FOR_ALL_INTERFACES (pim->vrf, ifp) { pim_ifp = ifp->info; if (!pim_ifp) continue; if (strcmp(ifname, "detail") && strcmp(ifname, ifp->name)) continue; found_ifname = 1; ifaddr = pim_ifp->primary_address; pim_inet4_dump("", pim_ifp->pim_dr_addr, dr_str, sizeof(dr_str)); pim_time_uptime_begin(dr_uptime, sizeof(dr_uptime), now, pim_ifp->pim_dr_election_last); pim_time_timer_to_hhmmss(hello_timer, sizeof(hello_timer), pim_ifp->t_pim_hello_timer); pim_time_mmss(hello_period, sizeof(hello_period), pim_ifp->pim_hello_period); pim_time_uptime(stat_uptime, sizeof(stat_uptime), now - pim_ifp->pim_ifstat_start); if (pim_ifp->pim_sock_fd >= 0) mloop = pim_socket_mcastloop_get(pim_ifp->pim_sock_fd); else mloop = 0; if (uj) { char pbuf[PREFIX2STR_BUFFER]; json_row = json_object_new_object(); json_object_pim_ifp_add(json_row, ifp); if (pim_ifp->update_source.s_addr != INADDR_ANY) { json_object_string_add( json_row, "useSource", inet_ntoa(pim_ifp->update_source)); } if (pim_ifp->sec_addr_list) { json_object *sec_list = NULL; sec_list = json_object_new_array(); for (ALL_LIST_ELEMENTS_RO( pim_ifp->sec_addr_list, sec_node, sec_addr)) { json_object_array_add( sec_list, json_object_new_string( prefix2str( &sec_addr->addr, pbuf, sizeof(pbuf)))); } json_object_object_add(json_row, "secondaryAddressList", sec_list); } // PIM neighbors if (pim_ifp->pim_neighbor_list->count) { json_pim_neighbors = json_object_new_object(); for (ALL_LIST_ELEMENTS_RO( pim_ifp->pim_neighbor_list, neighnode, neigh)) { json_pim_neighbor = json_object_new_object(); pim_inet4_dump("", neigh->source_addr, neigh_src_str, sizeof(neigh_src_str)); pim_time_uptime(uptime, sizeof(uptime), now - neigh->creation); pim_time_timer_to_hhmmss( expire, sizeof(expire), neigh->t_expire_timer); json_object_string_add( json_pim_neighbor, "address", neigh_src_str); json_object_string_add( json_pim_neighbor, "upTime", uptime); json_object_string_add( json_pim_neighbor, "holdtime", expire); json_object_object_add( json_pim_neighbors, neigh_src_str, json_pim_neighbor); } json_object_object_add(json_row, "neighbors", json_pim_neighbors); } json_object_string_add(json_row, "drAddress", dr_str); json_object_int_add(json_row, "drPriority", pim_ifp->pim_dr_priority); json_object_string_add(json_row, "drUptime", dr_uptime); json_object_int_add(json_row, "drElections", pim_ifp->pim_dr_election_count); json_object_int_add(json_row, "drChanges", pim_ifp->pim_dr_election_changes); // FHR for (ALL_LIST_ELEMENTS_RO(pim->upstream_list, upnode, up)) { if (ifp != up->rpf.source_nexthop.interface) continue; if (!(up->flags & PIM_UPSTREAM_FLAG_MASK_FHR)) continue; if (!json_fhr_sources) json_fhr_sources = json_object_new_object(); pim_inet4_dump("", up->sg.src, src_str, sizeof(src_str)); pim_inet4_dump("", up->sg.grp, grp_str, sizeof(grp_str)); pim_time_uptime(uptime, sizeof(uptime), now - up->state_transition); /* * Does this group live in json_fhr_sources? * If not create it. */ json_object_object_get_ex(json_fhr_sources, grp_str, &json_group); if (!json_group) { json_group = json_object_new_object(); json_object_object_add(json_fhr_sources, grp_str, json_group); } json_group_source = json_object_new_object(); json_object_string_add(json_group_source, "source", src_str); json_object_string_add(json_group_source, "group", grp_str); json_object_string_add(json_group_source, "upTime", uptime); json_object_object_add(json_group, src_str, json_group_source); } if (json_fhr_sources) { json_object_object_add(json_row, "firstHopRouter", json_fhr_sources); } json_object_int_add(json_row, "helloPeriod", pim_ifp->pim_hello_period); json_object_string_add(json_row, "helloTimer", hello_timer); json_object_string_add(json_row, "helloStatStart", stat_uptime); json_object_int_add(json_row, "helloReceived", pim_ifp->pim_ifstat_hello_recv); json_object_int_add(json_row, "helloReceivedFailed", pim_ifp->pim_ifstat_hello_recvfail); json_object_int_add(json_row, "helloSend", pim_ifp->pim_ifstat_hello_sent); json_object_int_add(json_row, "hellosendFailed", pim_ifp->pim_ifstat_hello_sendfail); json_object_int_add(json_row, "helloGenerationId", pim_ifp->pim_generation_id); json_object_int_add(json_row, "flagMulticastLoop", mloop); json_object_int_add( json_row, "effectivePropagationDelay", pim_if_effective_propagation_delay_msec(ifp)); json_object_int_add( json_row, "effectiveOverrideInterval", pim_if_effective_override_interval_msec(ifp)); json_object_int_add( json_row, "joinPruneOverrideInterval", pim_if_jp_override_interval_msec(ifp)); json_object_int_add( json_row, "propagationDelay", pim_ifp->pim_propagation_delay_msec); json_object_int_add( json_row, "propagationDelayHighest", pim_ifp->pim_neighbors_highest_propagation_delay_msec); json_object_int_add( json_row, "overrideInterval", pim_ifp->pim_override_interval_msec); json_object_int_add( json_row, "overrideIntervalHighest", pim_ifp->pim_neighbors_highest_override_interval_msec); json_object_object_add(json, ifp->name, json_row); } else { vty_out(vty, "Interface : %s\n", ifp->name); vty_out(vty, "State : %s\n", if_is_up(ifp) ? "up" : "down"); if (pim_ifp->update_source.s_addr != INADDR_ANY) { vty_out(vty, "Use Source : %s\n", inet_ntoa(pim_ifp->update_source)); } if (pim_ifp->sec_addr_list) { char pbuf[PREFIX2STR_BUFFER]; vty_out(vty, "Address : %s (primary)\n", inet_ntoa(ifaddr)); for (ALL_LIST_ELEMENTS_RO( pim_ifp->sec_addr_list, sec_node, sec_addr)) { vty_out(vty, " %s\n", prefix2str(&sec_addr->addr, pbuf, sizeof(pbuf))); } } else { vty_out(vty, "Address : %s\n", inet_ntoa(ifaddr)); } vty_out(vty, "\n"); // PIM neighbors print_header = 1; for (ALL_LIST_ELEMENTS_RO(pim_ifp->pim_neighbor_list, neighnode, neigh)) { if (print_header) { vty_out(vty, "PIM Neighbors\n"); vty_out(vty, "-------------\n"); print_header = 0; } pim_inet4_dump("", neigh->source_addr, neigh_src_str, sizeof(neigh_src_str)); pim_time_uptime(uptime, sizeof(uptime), now - neigh->creation); pim_time_timer_to_hhmmss(expire, sizeof(expire), neigh->t_expire_timer); vty_out(vty, "%-15s : up for %s, holdtime expires in %s\n", neigh_src_str, uptime, expire); } if (!print_header) { vty_out(vty, "\n"); vty_out(vty, "\n"); } vty_out(vty, "Designated Router\n"); vty_out(vty, "-----------------\n"); vty_out(vty, "Address : %s\n", dr_str); vty_out(vty, "Priority : %u(%d)\n", pim_ifp->pim_dr_priority, pim_ifp->pim_dr_num_nondrpri_neighbors); vty_out(vty, "Uptime : %s\n", dr_uptime); vty_out(vty, "Elections : %d\n", pim_ifp->pim_dr_election_count); vty_out(vty, "Changes : %d\n", pim_ifp->pim_dr_election_changes); vty_out(vty, "\n"); vty_out(vty, "\n"); // FHR print_header = 1; for (ALL_LIST_ELEMENTS_RO(pim->upstream_list, upnode, up)) { if (!up->rpf.source_nexthop.interface) continue; if (strcmp(ifp->name, up->rpf.source_nexthop .interface->name) != 0) continue; if (!(up->flags & PIM_UPSTREAM_FLAG_MASK_FHR)) continue; if (print_header) { vty_out(vty, "FHR - First Hop Router\n"); vty_out(vty, "----------------------\n"); print_header = 0; } pim_inet4_dump("", up->sg.src, src_str, sizeof(src_str)); pim_inet4_dump("", up->sg.grp, grp_str, sizeof(grp_str)); pim_time_uptime(uptime, sizeof(uptime), now - up->state_transition); vty_out(vty, "%s : %s is a source, uptime is %s\n", grp_str, src_str, uptime); } if (!print_header) { vty_out(vty, "\n"); vty_out(vty, "\n"); } vty_out(vty, "Hellos\n"); vty_out(vty, "------\n"); vty_out(vty, "Period : %d\n", pim_ifp->pim_hello_period); vty_out(vty, "Timer : %s\n", hello_timer); vty_out(vty, "StatStart : %s\n", stat_uptime); vty_out(vty, "Receive : %d\n", pim_ifp->pim_ifstat_hello_recv); vty_out(vty, "Receive Failed : %d\n", pim_ifp->pim_ifstat_hello_recvfail); vty_out(vty, "Send : %d\n", pim_ifp->pim_ifstat_hello_sent); vty_out(vty, "Send Failed : %d\n", pim_ifp->pim_ifstat_hello_sendfail); vty_out(vty, "Generation ID : %08x\n", pim_ifp->pim_generation_id); vty_out(vty, "\n"); vty_out(vty, "\n"); pim_print_ifp_flags(vty, ifp, mloop); vty_out(vty, "Join Prune Interval\n"); vty_out(vty, "-------------------\n"); vty_out(vty, "LAN Delay : %s\n", pim_if_lan_delay_enabled(ifp) ? "yes" : "no"); vty_out(vty, "Effective Propagation Delay : %d msec\n", pim_if_effective_propagation_delay_msec(ifp)); vty_out(vty, "Effective Override Interval : %d msec\n", pim_if_effective_override_interval_msec(ifp)); vty_out(vty, "Join Prune Override Interval : %d msec\n", pim_if_jp_override_interval_msec(ifp)); vty_out(vty, "\n"); vty_out(vty, "\n"); vty_out(vty, "LAN Prune Delay\n"); vty_out(vty, "---------------\n"); vty_out(vty, "Propagation Delay : %d msec\n", pim_ifp->pim_propagation_delay_msec); vty_out(vty, "Propagation Delay (Highest) : %d msec\n", pim_ifp->pim_neighbors_highest_propagation_delay_msec); vty_out(vty, "Override Interval : %d msec\n", pim_ifp->pim_override_interval_msec); vty_out(vty, "Override Interval (Highest) : %d msec\n", pim_ifp->pim_neighbors_highest_override_interval_msec); vty_out(vty, "\n"); vty_out(vty, "\n"); } } if (uj) { vty_out(vty, "%s\n", json_object_to_json_string_ext( json, JSON_C_TO_STRING_PRETTY)); json_object_free(json); } else { if (!found_ifname) vty_out(vty, "%% No such interface\n"); } } static void igmp_show_statistics(struct pim_instance *pim, struct vty *vty, const char *ifname, bool uj) { struct interface *ifp; struct igmp_stats rx_stats; igmp_stats_init(&rx_stats); FOR_ALL_INTERFACES (pim->vrf, ifp) { struct pim_interface *pim_ifp; struct listnode *sock_node; struct igmp_sock *igmp; pim_ifp = ifp->info; if (!pim_ifp) continue; if (ifname && strcmp(ifname, ifp->name)) continue; for (ALL_LIST_ELEMENTS_RO(pim_ifp->igmp_socket_list, sock_node, igmp)) { igmp_stats_add(&rx_stats, &igmp->rx_stats); } } if (uj) { json_object *json = NULL; json_object *json_row = NULL; json = json_object_new_object(); json_row = json_object_new_object(); json_object_string_add(json_row, "name", ifname ? ifname : "global"); json_object_int_add(json_row, "queryV1", rx_stats.query_v1); json_object_int_add(json_row, "queryV2", rx_stats.query_v2); json_object_int_add(json_row, "queryV3", rx_stats.query_v3); json_object_int_add(json_row, "leaveV3", rx_stats.leave_v2); json_object_int_add(json_row, "reportV1", rx_stats.report_v1); json_object_int_add(json_row, "reportV2", rx_stats.report_v2); json_object_int_add(json_row, "reportV3", rx_stats.report_v3); json_object_int_add(json_row, "mtraceResponse", rx_stats.mtrace_rsp); json_object_int_add(json_row, "mtraceRequest", rx_stats.mtrace_req); json_object_int_add(json_row, "unsupported", rx_stats.unsupported); json_object_object_add(json, ifname ? ifname : "global", json_row); vty_out(vty, "%s\n", json_object_to_json_string_ext( json, JSON_C_TO_STRING_PRETTY)); json_object_free(json); } else { vty_out(vty, "IGMP RX statistics\n"); vty_out(vty, "Interface : %s\n", ifname ? ifname : "global"); vty_out(vty, "V1 query : %u\n", rx_stats.query_v1); vty_out(vty, "V2 query : %u\n", rx_stats.query_v2); vty_out(vty, "V3 query : %u\n", rx_stats.query_v3); vty_out(vty, "V2 leave : %u\n", rx_stats.leave_v2); vty_out(vty, "V1 report : %u\n", rx_stats.report_v1); vty_out(vty, "V2 report : %u\n", rx_stats.report_v2); vty_out(vty, "V3 report : %u\n", rx_stats.report_v3); vty_out(vty, "mtrace response : %u\n", rx_stats.mtrace_rsp); vty_out(vty, "mtrace request : %u\n", rx_stats.mtrace_req); vty_out(vty, "unsupported : %u\n", rx_stats.unsupported); } } static void pim_show_interfaces(struct pim_instance *pim, struct vty *vty, bool uj) { struct interface *ifp; struct listnode *upnode; struct pim_interface *pim_ifp; struct pim_upstream *up; int fhr = 0; int pim_nbrs = 0; int pim_ifchannels = 0; json_object *json = NULL; json_object *json_row = NULL; json_object *json_tmp; json = json_object_new_object(); FOR_ALL_INTERFACES (pim->vrf, ifp) { pim_ifp = ifp->info; if (!pim_ifp) continue; pim_nbrs = pim_ifp->pim_neighbor_list->count; pim_ifchannels = pim_if_ifchannel_count(pim_ifp); fhr = 0; for (ALL_LIST_ELEMENTS_RO(pim->upstream_list, upnode, up)) if (ifp == up->rpf.source_nexthop.interface) if (up->flags & PIM_UPSTREAM_FLAG_MASK_FHR) fhr++; json_row = json_object_new_object(); json_object_pim_ifp_add(json_row, ifp); json_object_int_add(json_row, "pimNeighbors", pim_nbrs); json_object_int_add(json_row, "pimIfChannels", pim_ifchannels); json_object_int_add(json_row, "firstHopRouterCount", fhr); json_object_string_add(json_row, "pimDesignatedRouter", inet_ntoa(pim_ifp->pim_dr_addr)); if (pim_ifp->pim_dr_addr.s_addr == pim_ifp->primary_address.s_addr) json_object_boolean_true_add( json_row, "pimDesignatedRouterLocal"); json_object_object_add(json, ifp->name, json_row); } if (uj) { vty_out(vty, "%s\n", json_object_to_json_string_ext( json, JSON_C_TO_STRING_PRETTY)); } else { vty_out(vty, "Interface State Address PIM Nbrs PIM DR FHR IfChannels\n"); json_object_object_foreach(json, key, val) { vty_out(vty, "%-16s ", key); json_object_object_get_ex(val, "state", &json_tmp); vty_out(vty, "%5s ", json_object_get_string(json_tmp)); json_object_object_get_ex(val, "address", &json_tmp); vty_out(vty, "%15s ", json_object_get_string(json_tmp)); json_object_object_get_ex(val, "pimNeighbors", &json_tmp); vty_out(vty, "%8d ", json_object_get_int(json_tmp)); if (json_object_object_get_ex( val, "pimDesignatedRouterLocal", &json_tmp)) { vty_out(vty, "%15s ", "local"); } else { json_object_object_get_ex( val, "pimDesignatedRouter", &json_tmp); vty_out(vty, "%15s ", json_object_get_string(json_tmp)); } json_object_object_get_ex(val, "firstHopRouter", &json_tmp); vty_out(vty, "%3d ", json_object_get_int(json_tmp)); json_object_object_get_ex(val, "pimIfChannels", &json_tmp); vty_out(vty, "%9d\n", json_object_get_int(json_tmp)); } } json_object_free(json); } static void pim_show_interface_traffic(struct pim_instance *pim, struct vty *vty, bool uj) { struct interface *ifp = NULL; struct pim_interface *pim_ifp = NULL; json_object *json = NULL; json_object *json_row = NULL; if (uj) json = json_object_new_object(); else { vty_out(vty, "\n"); vty_out(vty, "%-16s%-17s%-17s%-17s%-17s%-17s%-17s%-17s\n", "Interface", " HELLO", " JOIN", " PRUNE", " REGISTER", "REGISTER-STOP", " ASSERT", " BSM"); vty_out(vty, "%-16s%-17s%-17s%-17s%-17s%-17s%-17s%-17s\n", "", " Rx/Tx", " Rx/Tx", " Rx/Tx", " Rx/Tx", " Rx/Tx", " Rx/Tx", " Rx/Tx"); vty_out(vty, "---------------------------------------------------------------------------------------------------------------\n"); } FOR_ALL_INTERFACES (pim->vrf, ifp) { pim_ifp = ifp->info; if (!pim_ifp) continue; if (pim_ifp->pim_sock_fd < 0) continue; if (uj) { json_row = json_object_new_object(); json_object_pim_ifp_add(json_row, ifp); json_object_int_add(json_row, "helloRx", pim_ifp->pim_ifstat_hello_recv); json_object_int_add(json_row, "helloTx", pim_ifp->pim_ifstat_hello_sent); json_object_int_add(json_row, "joinRx", pim_ifp->pim_ifstat_join_recv); json_object_int_add(json_row, "joinTx", pim_ifp->pim_ifstat_join_send); json_object_int_add(json_row, "registerRx", pim_ifp->pim_ifstat_reg_recv); json_object_int_add(json_row, "registerTx", pim_ifp->pim_ifstat_reg_recv); json_object_int_add(json_row, "registerStopRx", pim_ifp->pim_ifstat_reg_stop_recv); json_object_int_add(json_row, "registerStopTx", pim_ifp->pim_ifstat_reg_stop_send); json_object_int_add(json_row, "assertRx", pim_ifp->pim_ifstat_assert_recv); json_object_int_add(json_row, "assertTx", pim_ifp->pim_ifstat_assert_send); json_object_int_add(json_row, "bsmRx", pim_ifp->pim_ifstat_bsm_rx); json_object_int_add(json_row, "bsmTx", pim_ifp->pim_ifstat_bsm_tx); json_object_object_add(json, ifp->name, json_row); } else { vty_out(vty, "%-16s %8u/%-8u %7u/%-7u %7u/%-7u %7u/%-7u %7u/%-7u %7u/%-7u %7" PRIu64 "/%-7" PRIu64 "\n", ifp->name, pim_ifp->pim_ifstat_hello_recv, pim_ifp->pim_ifstat_hello_sent, pim_ifp->pim_ifstat_join_recv, pim_ifp->pim_ifstat_join_send, pim_ifp->pim_ifstat_prune_recv, pim_ifp->pim_ifstat_prune_send, pim_ifp->pim_ifstat_reg_recv, pim_ifp->pim_ifstat_reg_send, pim_ifp->pim_ifstat_reg_stop_recv, pim_ifp->pim_ifstat_reg_stop_send, pim_ifp->pim_ifstat_assert_recv, pim_ifp->pim_ifstat_assert_send, pim_ifp->pim_ifstat_bsm_rx, pim_ifp->pim_ifstat_bsm_tx); } } if (uj) { vty_out(vty, "%s\n", json_object_to_json_string_ext( json, JSON_C_TO_STRING_PRETTY)); json_object_free(json); } } static void pim_show_interface_traffic_single(struct pim_instance *pim, struct vty *vty, const char *ifname, bool uj) { struct interface *ifp = NULL; struct pim_interface *pim_ifp = NULL; json_object *json = NULL; json_object *json_row = NULL; uint8_t found_ifname = 0; if (uj) json = json_object_new_object(); else { vty_out(vty, "\n"); vty_out(vty, "%-16s%-17s%-17s%-17s%-17s%-17s%-17s%-17s\n", "Interface", " HELLO", " JOIN", " PRUNE", " REGISTER", " REGISTER-STOP", " ASSERT", " BSM"); vty_out(vty, "%-14s%-18s%-17s%-17s%-17s%-17s%-17s%-17s\n", "", " Rx/Tx", " Rx/Tx", " Rx/Tx", " Rx/Tx", " Rx/Tx", " Rx/Tx", " Rx/Tx"); vty_out(vty, "-------------------------------------------------------------------------------------------------------------------------------\n"); } FOR_ALL_INTERFACES (pim->vrf, ifp) { if (strcmp(ifname, ifp->name)) continue; pim_ifp = ifp->info; if (!pim_ifp) continue; if (pim_ifp->pim_sock_fd < 0) continue; found_ifname = 1; if (uj) { json_row = json_object_new_object(); json_object_pim_ifp_add(json_row, ifp); json_object_int_add(json_row, "helloRx", pim_ifp->pim_ifstat_hello_recv); json_object_int_add(json_row, "helloTx", pim_ifp->pim_ifstat_hello_sent); json_object_int_add(json_row, "joinRx", pim_ifp->pim_ifstat_join_recv); json_object_int_add(json_row, "joinTx", pim_ifp->pim_ifstat_join_send); json_object_int_add(json_row, "registerRx", pim_ifp->pim_ifstat_reg_recv); json_object_int_add(json_row, "registerTx", pim_ifp->pim_ifstat_reg_recv); json_object_int_add(json_row, "registerStopRx", pim_ifp->pim_ifstat_reg_stop_recv); json_object_int_add(json_row, "registerStopTx", pim_ifp->pim_ifstat_reg_stop_send); json_object_int_add(json_row, "assertRx", pim_ifp->pim_ifstat_assert_recv); json_object_int_add(json_row, "assertTx", pim_ifp->pim_ifstat_assert_send); json_object_int_add(json_row, "bsmRx", pim_ifp->pim_ifstat_bsm_rx); json_object_int_add(json_row, "bsmTx", pim_ifp->pim_ifstat_bsm_tx); json_object_object_add(json, ifp->name, json_row); } else { vty_out(vty, "%-16s %8u/%-8u %7u/%-7u %7u/%-7u %7u/%-7u %7u/%-7u %7u/%-7u %7" PRIu64 "/%-7" PRIu64 "\n", ifp->name, pim_ifp->pim_ifstat_hello_recv, pim_ifp->pim_ifstat_hello_sent, pim_ifp->pim_ifstat_join_recv, pim_ifp->pim_ifstat_join_send, pim_ifp->pim_ifstat_prune_recv, pim_ifp->pim_ifstat_prune_send, pim_ifp->pim_ifstat_reg_recv, pim_ifp->pim_ifstat_reg_send, pim_ifp->pim_ifstat_reg_stop_recv, pim_ifp->pim_ifstat_reg_stop_send, pim_ifp->pim_ifstat_assert_recv, pim_ifp->pim_ifstat_assert_send, pim_ifp->pim_ifstat_bsm_rx, pim_ifp->pim_ifstat_bsm_tx); } } if (uj) { vty_out(vty, "%s\n", json_object_to_json_string_ext( json, JSON_C_TO_STRING_PRETTY)); json_object_free(json); } else { if (!found_ifname) vty_out(vty, "%% No such interface\n"); } } static void pim_show_join_helper(struct vty *vty, struct pim_interface *pim_ifp, struct pim_ifchannel *ch, json_object *json, time_t now, bool uj) { char ch_src_str[INET_ADDRSTRLEN]; char ch_grp_str[INET_ADDRSTRLEN]; json_object *json_iface = NULL; json_object *json_row = NULL; json_object *json_grp = NULL; struct in_addr ifaddr; char uptime[10]; char expire[10]; char prune[10]; ifaddr = pim_ifp->primary_address; pim_inet4_dump("", ch->sg.src, ch_src_str, sizeof(ch_src_str)); pim_inet4_dump("", ch->sg.grp, ch_grp_str, sizeof(ch_grp_str)); pim_time_uptime_begin(uptime, sizeof(uptime), now, ch->ifjoin_creation); pim_time_timer_to_mmss(expire, sizeof(expire), ch->t_ifjoin_expiry_timer); pim_time_timer_to_mmss(prune, sizeof(prune), ch->t_ifjoin_prune_pending_timer); if (uj) { json_object_object_get_ex(json, ch->interface->name, &json_iface); if (!json_iface) { json_iface = json_object_new_object(); json_object_pim_ifp_add(json_iface, ch->interface); json_object_object_add(json, ch->interface->name, json_iface); } json_row = json_object_new_object(); json_object_string_add(json_row, "source", ch_src_str); json_object_string_add(json_row, "group", ch_grp_str); json_object_string_add(json_row, "upTime", uptime); json_object_string_add(json_row, "expire", expire); json_object_string_add(json_row, "prune", prune); json_object_string_add( json_row, "channelJoinName", pim_ifchannel_ifjoin_name(ch->ifjoin_state, ch->flags)); if (PIM_IF_FLAG_TEST_S_G_RPT(ch->flags)) json_object_int_add(json_row, "SGRpt", 1); json_object_object_get_ex(json_iface, ch_grp_str, &json_grp); if (!json_grp) { json_grp = json_object_new_object(); json_object_object_add(json_grp, ch_src_str, json_row); json_object_object_add(json_iface, ch_grp_str, json_grp); } else json_object_object_add(json_grp, ch_src_str, json_row); } else { vty_out(vty, "%-16s %-15s %-15s %-15s %-10s %8s %-6s %5s\n", ch->interface->name, inet_ntoa(ifaddr), ch_src_str, ch_grp_str, pim_ifchannel_ifjoin_name(ch->ifjoin_state, ch->flags), uptime, expire, prune); } } static void pim_show_join(struct pim_instance *pim, struct vty *vty, struct prefix_sg *sg, bool uj) { struct pim_interface *pim_ifp; struct pim_ifchannel *ch; struct interface *ifp; time_t now; json_object *json = NULL; now = pim_time_monotonic_sec(); if (uj) json = json_object_new_object(); else vty_out(vty, "Interface Address Source Group State Uptime Expire Prune\n"); FOR_ALL_INTERFACES (pim->vrf, ifp) { pim_ifp = ifp->info; if (!pim_ifp) continue; RB_FOREACH (ch, pim_ifchannel_rb, &pim_ifp->ifchannel_rb) { if (sg->grp.s_addr != 0 && sg->grp.s_addr != ch->sg.grp.s_addr) continue; if (sg->src.s_addr != 0 && sg->src.s_addr != ch->sg.src.s_addr) continue; pim_show_join_helper(vty, pim_ifp, ch, json, now, uj); } /* scan interface channels */ } if (uj) { vty_out(vty, "%s\n", json_object_to_json_string_ext( json, JSON_C_TO_STRING_PRETTY)); json_object_free(json); } } static void pim_show_neighbors_single(struct pim_instance *pim, struct vty *vty, const char *neighbor, bool uj) { struct listnode *neighnode; struct interface *ifp; struct pim_interface *pim_ifp; struct pim_neighbor *neigh; time_t now; int found_neighbor = 0; int option_address_list; int option_dr_priority; int option_generation_id; int option_holdtime; int option_lan_prune_delay; int option_t_bit; char uptime[10]; char expire[10]; char neigh_src_str[INET_ADDRSTRLEN]; json_object *json = NULL; json_object *json_ifp = NULL; json_object *json_row = NULL; now = pim_time_monotonic_sec(); if (uj) json = json_object_new_object(); FOR_ALL_INTERFACES (pim->vrf, ifp) { pim_ifp = ifp->info; if (!pim_ifp) continue; if (pim_ifp->pim_sock_fd < 0) continue; for (ALL_LIST_ELEMENTS_RO(pim_ifp->pim_neighbor_list, neighnode, neigh)) { pim_inet4_dump("", neigh->source_addr, neigh_src_str, sizeof(neigh_src_str)); /* * The user can specify either the interface name or the * PIM neighbor IP. * If this pim_ifp matches neither then skip. */ if (strcmp(neighbor, "detail") && strcmp(neighbor, ifp->name) && strcmp(neighbor, neigh_src_str)) continue; found_neighbor = 1; pim_time_uptime(uptime, sizeof(uptime), now - neigh->creation); pim_time_timer_to_hhmmss(expire, sizeof(expire), neigh->t_expire_timer); option_address_list = 0; option_dr_priority = 0; option_generation_id = 0; option_holdtime = 0; option_lan_prune_delay = 0; option_t_bit = 0; if (PIM_OPTION_IS_SET(neigh->hello_options, PIM_OPTION_MASK_ADDRESS_LIST)) option_address_list = 1; if (PIM_OPTION_IS_SET(neigh->hello_options, PIM_OPTION_MASK_DR_PRIORITY)) option_dr_priority = 1; if (PIM_OPTION_IS_SET(neigh->hello_options, PIM_OPTION_MASK_GENERATION_ID)) option_generation_id = 1; if (PIM_OPTION_IS_SET(neigh->hello_options, PIM_OPTION_MASK_HOLDTIME)) option_holdtime = 1; if (PIM_OPTION_IS_SET(neigh->hello_options, PIM_OPTION_MASK_LAN_PRUNE_DELAY)) option_lan_prune_delay = 1; if (PIM_OPTION_IS_SET( neigh->hello_options, PIM_OPTION_MASK_CAN_DISABLE_JOIN_SUPPRESSION)) option_t_bit = 1; if (uj) { /* Does this ifp live in json? If not create * it. */ json_object_object_get_ex(json, ifp->name, &json_ifp); if (!json_ifp) { json_ifp = json_object_new_object(); json_object_pim_ifp_add(json_ifp, ifp); json_object_object_add(json, ifp->name, json_ifp); } json_row = json_object_new_object(); json_object_string_add(json_row, "interface", ifp->name); json_object_string_add(json_row, "address", neigh_src_str); json_object_string_add(json_row, "upTime", uptime); json_object_string_add(json_row, "holdtime", expire); json_object_int_add(json_row, "drPriority", neigh->dr_priority); json_object_int_add(json_row, "generationId", neigh->generation_id); if (option_address_list) json_object_boolean_true_add( json_row, "helloOptionAddressList"); if (option_dr_priority) json_object_boolean_true_add( json_row, "helloOptionDrPriority"); if (option_generation_id) json_object_boolean_true_add( json_row, "helloOptionGenerationId"); if (option_holdtime) json_object_boolean_true_add( json_row, "helloOptionHoldtime"); if (option_lan_prune_delay) json_object_boolean_true_add( json_row, "helloOptionLanPruneDelay"); if (option_t_bit) json_object_boolean_true_add( json_row, "helloOptionTBit"); json_object_object_add(json_ifp, neigh_src_str, json_row); } else { vty_out(vty, "Interface : %s\n", ifp->name); vty_out(vty, "Neighbor : %s\n", neigh_src_str); vty_out(vty, " Uptime : %s\n", uptime); vty_out(vty, " Holdtime : %s\n", expire); vty_out(vty, " DR Priority : %d\n", neigh->dr_priority); vty_out(vty, " Generation ID : %08x\n", neigh->generation_id); vty_out(vty, " Override Interval (msec) : %d\n", neigh->override_interval_msec); vty_out(vty, " Propagation Delay (msec) : %d\n", neigh->propagation_delay_msec); vty_out(vty, " Hello Option - Address List : %s\n", option_address_list ? "yes" : "no"); vty_out(vty, " Hello Option - DR Priority : %s\n", option_dr_priority ? "yes" : "no"); vty_out(vty, " Hello Option - Generation ID : %s\n", option_generation_id ? "yes" : "no"); vty_out(vty, " Hello Option - Holdtime : %s\n", option_holdtime ? "yes" : "no"); vty_out(vty, " Hello Option - LAN Prune Delay : %s\n", option_lan_prune_delay ? "yes" : "no"); vty_out(vty, " Hello Option - T-bit : %s\n", option_t_bit ? "yes" : "no"); pim_bfd_show_info(vty, neigh->bfd_info, json_ifp, uj, 0); vty_out(vty, "\n"); } } } if (uj) { vty_out(vty, "%s\n", json_object_to_json_string_ext( json, JSON_C_TO_STRING_PRETTY)); json_object_free(json); } else { { if (!found_neighbor) vty_out(vty, "%% No such interface or neighbor\n"); } } } static void pim_show_state(struct pim_instance *pim, struct vty *vty, const char *src_or_group, const char *group, bool uj) { struct channel_oil *c_oil; struct listnode *node; json_object *json = NULL; json_object *json_group = NULL; json_object *json_ifp_in = NULL; json_object *json_ifp_out = NULL; json_object *json_source = NULL; time_t now; int first_oif; now = pim_time_monotonic_sec(); if (uj) { json = json_object_new_object(); } else { vty_out(vty, "Codes: J -> Pim Join, I -> IGMP Report, S -> Source, * -> Inherited from (*,G), V -> VxLAN"); vty_out(vty, "\nInstalled Source Group IIF OIL\n"); } for (ALL_LIST_ELEMENTS_RO(pim->channel_oil_list, node, c_oil)) { char grp_str[INET_ADDRSTRLEN]; char src_str[INET_ADDRSTRLEN]; char in_ifname[INTERFACE_NAMSIZ + 1]; char out_ifname[INTERFACE_NAMSIZ + 1]; int oif_vif_index; struct interface *ifp_in; first_oif = 1; pim_inet4_dump("", c_oil->oil.mfcc_mcastgrp, grp_str, sizeof(grp_str)); pim_inet4_dump("", c_oil->oil.mfcc_origin, src_str, sizeof(src_str)); ifp_in = pim_if_find_by_vif_index(pim, c_oil->oil.mfcc_parent); if (ifp_in) strlcpy(in_ifname, ifp_in->name, sizeof(in_ifname)); else strlcpy(in_ifname, "", sizeof(in_ifname)); if (src_or_group) { if (strcmp(src_or_group, src_str) && strcmp(src_or_group, grp_str)) continue; if (group && strcmp(group, grp_str)) continue; } if (uj) { /* Find the group, create it if it doesn't exist */ json_object_object_get_ex(json, grp_str, &json_group); if (!json_group) { json_group = json_object_new_object(); json_object_object_add(json, grp_str, json_group); } /* Find the source nested under the group, create it if * it doesn't exist */ json_object_object_get_ex(json_group, src_str, &json_source); if (!json_source) { json_source = json_object_new_object(); json_object_object_add(json_group, src_str, json_source); } /* Find the inbound interface nested under the source, * create it if it doesn't exist */ json_object_object_get_ex(json_source, in_ifname, &json_ifp_in); if (!json_ifp_in) { json_ifp_in = json_object_new_object(); json_object_object_add(json_source, in_ifname, json_ifp_in); json_object_int_add(json_source, "Installed", c_oil->installed); json_object_int_add(json_source, "RefCount", c_oil->oil_ref_count); json_object_int_add(json_source, "OilListSize", c_oil->oil_size); json_object_int_add( json_source, "OilRescan", c_oil->oil_inherited_rescan); json_object_int_add(json_source, "LastUsed", c_oil->cc.lastused); json_object_int_add(json_source, "PacketCount", c_oil->cc.pktcnt); json_object_int_add(json_source, "ByteCount", c_oil->cc.bytecnt); json_object_int_add(json_source, "WrongInterface", c_oil->cc.wrong_if); } } else { vty_out(vty, "%-9d %-15s %-15s %-16s ", c_oil->installed, src_str, grp_str, in_ifname); } for (oif_vif_index = 0; oif_vif_index < MAXVIFS; ++oif_vif_index) { struct interface *ifp_out; char oif_uptime[10]; int ttl; ttl = c_oil->oil.mfcc_ttls[oif_vif_index]; if (ttl < 1) continue; ifp_out = pim_if_find_by_vif_index(pim, oif_vif_index); pim_time_uptime( oif_uptime, sizeof(oif_uptime), now - c_oil->oif_creation[oif_vif_index]); if (ifp_out) strlcpy(out_ifname, ifp_out->name, sizeof(out_ifname)); else strlcpy(out_ifname, "", sizeof(out_ifname)); if (uj) { json_ifp_out = json_object_new_object(); json_object_string_add(json_ifp_out, "source", src_str); json_object_string_add(json_ifp_out, "group", grp_str); json_object_string_add(json_ifp_out, "inboundInterface", in_ifname); json_object_string_add(json_ifp_out, "outboundInterface", out_ifname); json_object_int_add(json_ifp_out, "installed", c_oil->installed); json_object_object_add(json_ifp_in, out_ifname, json_ifp_out); } else { if (first_oif) { first_oif = 0; vty_out(vty, "%s(%c%c%c%c)", out_ifname, (c_oil->oif_flags[oif_vif_index] & PIM_OIF_FLAG_PROTO_IGMP) ? 'I' : ' ', (c_oil->oif_flags[oif_vif_index] & PIM_OIF_FLAG_PROTO_PIM) ? 'J' : ' ', (c_oil->oif_flags[oif_vif_index] & PIM_OIF_FLAG_PROTO_VXLAN) ? 'V' : ' ', (c_oil->oif_flags[oif_vif_index] & PIM_OIF_FLAG_PROTO_STAR) ? '*' : ' '); } else vty_out(vty, ", %s(%c%c%c%c)", out_ifname, (c_oil->oif_flags[oif_vif_index] & PIM_OIF_FLAG_PROTO_IGMP) ? 'I' : ' ', (c_oil->oif_flags[oif_vif_index] & PIM_OIF_FLAG_PROTO_PIM) ? 'J' : ' ', (c_oil->oif_flags[oif_vif_index] & PIM_OIF_FLAG_PROTO_VXLAN) ? 'V' : ' ', (c_oil->oif_flags[oif_vif_index] & PIM_OIF_FLAG_PROTO_STAR) ? '*' : ' '); } } if (!uj) vty_out(vty, "\n"); } if (uj) { vty_out(vty, "%s\n", json_object_to_json_string_ext( json, JSON_C_TO_STRING_PRETTY)); json_object_free(json); } else { vty_out(vty, "\n"); } } static void pim_show_neighbors(struct pim_instance *pim, struct vty *vty, bool uj) { struct listnode *neighnode; struct interface *ifp; struct pim_interface *pim_ifp; struct pim_neighbor *neigh; time_t now; char uptime[10]; char expire[10]; char neigh_src_str[INET_ADDRSTRLEN]; json_object *json = NULL; json_object *json_ifp_rows = NULL; json_object *json_row = NULL; now = pim_time_monotonic_sec(); if (uj) { json = json_object_new_object(); } else { vty_out(vty, "Interface Neighbor Uptime Holdtime DR Pri\n"); } FOR_ALL_INTERFACES (pim->vrf, ifp) { pim_ifp = ifp->info; if (!pim_ifp) continue; if (pim_ifp->pim_sock_fd < 0) continue; if (uj) json_ifp_rows = json_object_new_object(); for (ALL_LIST_ELEMENTS_RO(pim_ifp->pim_neighbor_list, neighnode, neigh)) { pim_inet4_dump("", neigh->source_addr, neigh_src_str, sizeof(neigh_src_str)); pim_time_uptime(uptime, sizeof(uptime), now - neigh->creation); pim_time_timer_to_hhmmss(expire, sizeof(expire), neigh->t_expire_timer); if (uj) { json_row = json_object_new_object(); json_object_string_add(json_row, "interface", ifp->name); json_object_string_add(json_row, "neighbor", neigh_src_str); json_object_string_add(json_row, "upTime", uptime); json_object_string_add(json_row, "holdTime", expire); json_object_int_add(json_row, "holdTimeMax", neigh->holdtime); json_object_int_add(json_row, "drPriority", neigh->dr_priority); json_object_object_add(json_ifp_rows, neigh_src_str, json_row); } else { vty_out(vty, "%-16s %15s %8s %8s %6d\n", ifp->name, neigh_src_str, uptime, expire, neigh->dr_priority); } } if (uj) { json_object_object_add(json, ifp->name, json_ifp_rows); json_ifp_rows = NULL; } } if (uj) { vty_out(vty, "%s\n", json_object_to_json_string_ext( json, JSON_C_TO_STRING_PRETTY)); json_object_free(json); } } static void pim_show_neighbors_secondary(struct pim_instance *pim, struct vty *vty) { struct interface *ifp; vty_out(vty, "Interface Address Neighbor Secondary \n"); FOR_ALL_INTERFACES (pim->vrf, ifp) { struct pim_interface *pim_ifp; struct in_addr ifaddr; struct listnode *neighnode; struct pim_neighbor *neigh; pim_ifp = ifp->info; if (!pim_ifp) continue; if (pim_ifp->pim_sock_fd < 0) continue; ifaddr = pim_ifp->primary_address; for (ALL_LIST_ELEMENTS_RO(pim_ifp->pim_neighbor_list, neighnode, neigh)) { char neigh_src_str[INET_ADDRSTRLEN]; struct listnode *prefix_node; struct prefix *p; if (!neigh->prefix_list) continue; pim_inet4_dump("", neigh->source_addr, neigh_src_str, sizeof(neigh_src_str)); for (ALL_LIST_ELEMENTS_RO(neigh->prefix_list, prefix_node, p)) { char neigh_sec_str[PREFIX2STR_BUFFER]; prefix2str(p, neigh_sec_str, sizeof(neigh_sec_str)); vty_out(vty, "%-16s %-15s %-15s %-15s\n", ifp->name, inet_ntoa(ifaddr), neigh_src_str, neigh_sec_str); } } } } static void json_object_pim_upstream_add(json_object *json, struct pim_upstream *up) { if (up->flags & PIM_UPSTREAM_FLAG_MASK_DR_JOIN_DESIRED) json_object_boolean_true_add(json, "drJoinDesired"); if (up->flags & PIM_UPSTREAM_FLAG_MASK_DR_JOIN_DESIRED_UPDATED) json_object_boolean_true_add(json, "drJoinDesiredUpdated"); if (up->flags & PIM_UPSTREAM_FLAG_MASK_FHR) json_object_boolean_true_add(json, "firstHopRouter"); if (up->flags & PIM_UPSTREAM_FLAG_MASK_SRC_IGMP) json_object_boolean_true_add(json, "sourceIgmp"); if (up->flags & PIM_UPSTREAM_FLAG_MASK_SRC_PIM) json_object_boolean_true_add(json, "sourcePim"); if (up->flags & PIM_UPSTREAM_FLAG_MASK_SRC_STREAM) json_object_boolean_true_add(json, "sourceStream"); /* XXX: need to print ths flag in the plain text display as well */ if (up->flags & PIM_UPSTREAM_FLAG_MASK_SRC_MSDP) json_object_boolean_true_add(json, "sourceMsdp"); if (up->flags & PIM_UPSTREAM_FLAG_MASK_SEND_SG_RPT_PRUNE) json_object_boolean_true_add(json, "sendSGRptPrune"); if (up->flags & PIM_UPSTREAM_FLAG_MASK_SRC_LHR) json_object_boolean_true_add(json, "lastHopRouter"); if (up->flags & PIM_UPSTREAM_FLAG_MASK_DISABLE_KAT_EXPIRY) json_object_boolean_true_add(json, "disableKATExpiry"); if (up->flags & PIM_UPSTREAM_FLAG_MASK_STATIC_IIF) json_object_boolean_true_add(json, "staticIncomingInterface"); if (up->flags & PIM_UPSTREAM_FLAG_MASK_ALLOW_IIF_IN_OIL) json_object_boolean_true_add(json, "allowIncomingInterfaceinOil"); if (up->flags & PIM_UPSTREAM_FLAG_MASK_NO_PIMREG_DATA) json_object_boolean_true_add(json, "noPimRegistrationData"); if (up->flags & PIM_UPSTREAM_FLAG_MASK_FORCE_PIMREG) json_object_boolean_true_add(json, "forcePimRegistration"); if (up->flags & PIM_UPSTREAM_FLAG_MASK_SRC_VXLAN_ORIG) json_object_boolean_true_add(json, "sourceVxlanOrigination"); if (up->flags & PIM_UPSTREAM_FLAG_MASK_SRC_VXLAN_TERM) json_object_boolean_true_add(json, "sourceVxlanTermination"); if (up->flags & PIM_UPSTREAM_FLAG_MASK_MLAG_VXLAN) json_object_boolean_true_add(json, "mlagVxlan"); if (up->flags & PIM_UPSTREAM_FLAG_MASK_MLAG_NON_DF) json_object_boolean_true_add(json, "mlagNonDesignatedForwarder"); } static const char * pim_upstream_state2brief_str(enum pim_upstream_state join_state, char *state_str, size_t state_str_len) { switch (join_state) { case PIM_UPSTREAM_NOTJOINED: strlcpy(state_str, "NotJ", state_str_len); break; case PIM_UPSTREAM_JOINED: strlcpy(state_str, "J", state_str_len); break; default: strlcpy(state_str, "Unk", state_str_len); } return state_str; } static const char *pim_reg_state2brief_str(enum pim_reg_state reg_state, char *state_str, size_t state_str_len) { switch (reg_state) { case PIM_REG_NOINFO: strlcpy(state_str, "RegNI", state_str_len); break; case PIM_REG_JOIN: strlcpy(state_str, "RegJ", state_str_len); break; case PIM_REG_JOIN_PENDING: case PIM_REG_PRUNE: strlcpy(state_str, "RegP", state_str_len); break; default: strlcpy(state_str, "Unk", state_str_len); } return state_str; } static void pim_show_upstream(struct pim_instance *pim, struct vty *vty, struct prefix_sg *sg, bool uj) { struct listnode *upnode; struct pim_upstream *up; time_t now; json_object *json = NULL; json_object *json_group = NULL; json_object *json_row = NULL; now = pim_time_monotonic_sec(); if (uj) json = json_object_new_object(); else vty_out(vty, "Iif Source Group State Uptime JoinTimer RSTimer KATimer RefCnt\n"); for (ALL_LIST_ELEMENTS_RO(pim->upstream_list, upnode, up)) { char src_str[INET_ADDRSTRLEN]; char grp_str[INET_ADDRSTRLEN]; char uptime[10]; char join_timer[10]; char rs_timer[10]; char ka_timer[10]; char msdp_reg_timer[10]; char state_str[PIM_REG_STATE_STR_LEN]; if (sg->grp.s_addr != 0 && sg->grp.s_addr != up->sg.grp.s_addr) continue; if (sg->src.s_addr != 0 && sg->src.s_addr != up->sg.src.s_addr) continue; pim_inet4_dump("", up->sg.src, src_str, sizeof(src_str)); pim_inet4_dump("", up->sg.grp, grp_str, sizeof(grp_str)); pim_time_uptime(uptime, sizeof(uptime), now - up->state_transition); pim_time_timer_to_hhmmss(join_timer, sizeof(join_timer), up->t_join_timer); /* * If the upstream is not dummy and it has a J/P timer for the * neighbor display that */ if (!up->t_join_timer && up->rpf.source_nexthop.interface) { struct pim_neighbor *nbr; nbr = pim_neighbor_find( up->rpf.source_nexthop.interface, up->rpf.rpf_addr.u.prefix4); if (nbr) pim_time_timer_to_hhmmss(join_timer, sizeof(join_timer), nbr->jp_timer); } pim_time_timer_to_hhmmss(rs_timer, sizeof(rs_timer), up->t_rs_timer); pim_time_timer_to_hhmmss(ka_timer, sizeof(ka_timer), up->t_ka_timer); pim_time_timer_to_hhmmss(msdp_reg_timer, sizeof(msdp_reg_timer), up->t_msdp_reg_timer); pim_upstream_state2brief_str(up->join_state, state_str, sizeof(state_str)); if (up->reg_state != PIM_REG_NOINFO) { char tmp_str[PIM_REG_STATE_STR_LEN]; sprintf(state_str + strlen(state_str), ",%s", pim_reg_state2brief_str(up->reg_state, tmp_str, sizeof(tmp_str))); } if (uj) { json_object_object_get_ex(json, grp_str, &json_group); if (!json_group) { json_group = json_object_new_object(); json_object_object_add(json, grp_str, json_group); } json_row = json_object_new_object(); json_object_pim_upstream_add(json_row, up); json_object_string_add( json_row, "inboundInterface", up->rpf.source_nexthop.interface ? up->rpf.source_nexthop.interface->name : "Unknown"); /* * The RPF address we use is slightly different * based upon what we are looking up. * If we have a S, list that unless * we are the FHR, else we just put * the RP as the rpfAddress */ if (up->flags & PIM_UPSTREAM_FLAG_MASK_FHR || up->sg.src.s_addr == INADDR_ANY) { char rpf[PREFIX_STRLEN]; struct pim_rpf *rpg; rpg = RP(pim, up->sg.grp); pim_inet4_dump("", rpg->rpf_addr.u.prefix4, rpf, sizeof(rpf)); json_object_string_add(json_row, "rpfAddress", rpf); } else { json_object_string_add(json_row, "rpfAddress", src_str); } json_object_string_add(json_row, "source", src_str); json_object_string_add(json_row, "group", grp_str); json_object_string_add(json_row, "state", state_str); json_object_string_add( json_row, "joinState", pim_upstream_state2str(up->join_state)); json_object_string_add( json_row, "regState", pim_reg_state2str(up->reg_state, state_str, sizeof(state_str))); json_object_string_add(json_row, "upTime", uptime); json_object_string_add(json_row, "joinTimer", join_timer); json_object_string_add(json_row, "resetTimer", rs_timer); json_object_string_add(json_row, "keepaliveTimer", ka_timer); json_object_string_add(json_row, "msdpRegTimer", msdp_reg_timer); json_object_int_add(json_row, "refCount", up->ref_count); json_object_int_add(json_row, "sptBit", up->sptbit); json_object_object_add(json_group, src_str, json_row); } else { vty_out(vty, "%-16s%-15s %-15s %-11s %-8s %-9s %-9s %-9s %6d\n", up->rpf.source_nexthop.interface ? up->rpf.source_nexthop.interface->name : "Unknown", src_str, grp_str, state_str, uptime, join_timer, rs_timer, ka_timer, up->ref_count); } } if (uj) { vty_out(vty, "%s\n", json_object_to_json_string_ext( json, JSON_C_TO_STRING_PRETTY)); json_object_free(json); } } static void pim_show_join_desired_helper(struct pim_instance *pim, struct vty *vty, struct pim_interface *pim_ifp, struct pim_ifchannel *ch, json_object *json, bool uj) { struct pim_upstream *up = ch->upstream; json_object *json_group = NULL; char src_str[INET_ADDRSTRLEN]; char grp_str[INET_ADDRSTRLEN]; json_object *json_row = NULL; pim_inet4_dump("", up->sg.src, src_str, sizeof(src_str)); pim_inet4_dump("", up->sg.grp, grp_str, sizeof(grp_str)); if (uj) { json_object_object_get_ex(json, grp_str, &json_group); if (!json_group) { json_group = json_object_new_object(); json_object_object_add(json, grp_str, json_group); } json_row = json_object_new_object(); json_object_pim_upstream_add(json_row, up); json_object_string_add(json_row, "interface", ch->interface->name); json_object_string_add(json_row, "source", src_str); json_object_string_add(json_row, "group", grp_str); if (pim_macro_ch_lost_assert(ch)) json_object_boolean_true_add(json_row, "lostAssert"); if (pim_macro_chisin_joins(ch)) json_object_boolean_true_add(json_row, "joins"); if (pim_macro_chisin_pim_include(ch)) json_object_boolean_true_add(json_row, "pimInclude"); if (pim_upstream_evaluate_join_desired(pim, up)) json_object_boolean_true_add(json_row, "evaluateJoinDesired"); json_object_object_add(json_group, src_str, json_row); } else { vty_out(vty, "%-16s %-15s %-15s %-10s %-5s %-10s %-11s %-6s\n", ch->interface->name, src_str, grp_str, pim_macro_ch_lost_assert(ch) ? "yes" : "no", pim_macro_chisin_joins(ch) ? "yes" : "no", pim_macro_chisin_pim_include(ch) ? "yes" : "no", PIM_UPSTREAM_FLAG_TEST_DR_JOIN_DESIRED(up->flags) ? "yes" : "no", pim_upstream_evaluate_join_desired(pim, up) ? "yes" : "no"); } } static void pim_show_join_desired(struct pim_instance *pim, struct vty *vty, bool uj) { struct pim_interface *pim_ifp; struct pim_ifchannel *ch; struct interface *ifp; json_object *json = NULL; if (uj) json = json_object_new_object(); else vty_out(vty, "Interface Source Group LostAssert Joins PimInclude JoinDesired EvalJD\n"); /* scan per-interface (S,G) state */ FOR_ALL_INTERFACES (pim->vrf, ifp) { pim_ifp = ifp->info; if (!pim_ifp) continue; RB_FOREACH (ch, pim_ifchannel_rb, &pim_ifp->ifchannel_rb) { /* scan all interfaces */ pim_show_join_desired_helper(pim, vty, pim_ifp, ch, json, uj); } } if (uj) { vty_out(vty, "%s\n", json_object_to_json_string_ext( json, JSON_C_TO_STRING_PRETTY)); json_object_free(json); } } static void pim_show_upstream_rpf(struct pim_instance *pim, struct vty *vty, bool uj) { struct listnode *upnode; struct pim_upstream *up; json_object *json = NULL; json_object *json_group = NULL; json_object *json_row = NULL; if (uj) json = json_object_new_object(); else vty_out(vty, "Source Group RpfIface RibNextHop RpfAddress \n"); for (ALL_LIST_ELEMENTS_RO(pim->upstream_list, upnode, up)) { char src_str[INET_ADDRSTRLEN]; char grp_str[INET_ADDRSTRLEN]; char rpf_nexthop_str[PREFIX_STRLEN]; char rpf_addr_str[PREFIX_STRLEN]; struct pim_rpf *rpf; const char *rpf_ifname; rpf = &up->rpf; pim_inet4_dump("", up->sg.src, src_str, sizeof(src_str)); pim_inet4_dump("", up->sg.grp, grp_str, sizeof(grp_str)); pim_addr_dump("", &rpf->source_nexthop.mrib_nexthop_addr, rpf_nexthop_str, sizeof(rpf_nexthop_str)); pim_addr_dump("", &rpf->rpf_addr, rpf_addr_str, sizeof(rpf_addr_str)); rpf_ifname = rpf->source_nexthop.interface ? rpf->source_nexthop.interface->name : ""; if (uj) { json_object_object_get_ex(json, grp_str, &json_group); if (!json_group) { json_group = json_object_new_object(); json_object_object_add(json, grp_str, json_group); } json_row = json_object_new_object(); json_object_pim_upstream_add(json_row, up); json_object_string_add(json_row, "source", src_str); json_object_string_add(json_row, "group", grp_str); json_object_string_add(json_row, "rpfInterface", rpf_ifname); json_object_string_add(json_row, "ribNexthop", rpf_nexthop_str); json_object_string_add(json_row, "rpfAddress", rpf_addr_str); json_object_object_add(json_group, src_str, json_row); } else { vty_out(vty, "%-15s %-15s %-16s %-15s %-15s\n", src_str, grp_str, rpf_ifname, rpf_nexthop_str, rpf_addr_str); } } if (uj) { vty_out(vty, "%s\n", json_object_to_json_string_ext( json, JSON_C_TO_STRING_PRETTY)); json_object_free(json); } } static void show_rpf_refresh_stats(struct vty *vty, struct pim_instance *pim, time_t now, json_object *json) { char refresh_uptime[10]; pim_time_uptime_begin(refresh_uptime, sizeof(refresh_uptime), now, pim->rpf_cache_refresh_last); if (json) { json_object_int_add(json, "rpfCacheRefreshDelayMsecs", router->rpf_cache_refresh_delay_msec); json_object_int_add( json, "rpfCacheRefreshTimer", pim_time_timer_remain_msec(pim->rpf_cache_refresher)); json_object_int_add(json, "rpfCacheRefreshRequests", pim->rpf_cache_refresh_requests); json_object_int_add(json, "rpfCacheRefreshEvents", pim->rpf_cache_refresh_events); json_object_string_add(json, "rpfCacheRefreshLast", refresh_uptime); json_object_int_add(json, "nexthopLookups", pim->nexthop_lookups); json_object_int_add(json, "nexthopLookupsAvoided", pim->nexthop_lookups_avoided); } else { vty_out(vty, "RPF Cache Refresh Delay: %ld msecs\n" "RPF Cache Refresh Timer: %ld msecs\n" "RPF Cache Refresh Requests: %lld\n" "RPF Cache Refresh Events: %lld\n" "RPF Cache Refresh Last: %s\n" "Nexthop Lookups: %lld\n" "Nexthop Lookups Avoided: %lld\n", router->rpf_cache_refresh_delay_msec, pim_time_timer_remain_msec(pim->rpf_cache_refresher), (long long)pim->rpf_cache_refresh_requests, (long long)pim->rpf_cache_refresh_events, refresh_uptime, (long long)pim->nexthop_lookups, (long long)pim->nexthop_lookups_avoided); } } static void show_scan_oil_stats(struct pim_instance *pim, struct vty *vty, time_t now) { char uptime_scan_oil[10]; char uptime_mroute_add[10]; char uptime_mroute_del[10]; pim_time_uptime_begin(uptime_scan_oil, sizeof(uptime_scan_oil), now, pim->scan_oil_last); pim_time_uptime_begin(uptime_mroute_add, sizeof(uptime_mroute_add), now, pim->mroute_add_last); pim_time_uptime_begin(uptime_mroute_del, sizeof(uptime_mroute_del), now, pim->mroute_del_last); vty_out(vty, "Scan OIL - Last: %s Events: %lld\n" "MFC Add - Last: %s Events: %lld\n" "MFC Del - Last: %s Events: %lld\n", uptime_scan_oil, (long long)pim->scan_oil_events, uptime_mroute_add, (long long)pim->mroute_add_events, uptime_mroute_del, (long long)pim->mroute_del_events); } static void pim_show_rpf(struct pim_instance *pim, struct vty *vty, bool uj) { struct listnode *up_node; struct pim_upstream *up; time_t now = pim_time_monotonic_sec(); json_object *json = NULL; json_object *json_group = NULL; json_object *json_row = NULL; if (uj) { json = json_object_new_object(); show_rpf_refresh_stats(vty, pim, now, json); } else { show_rpf_refresh_stats(vty, pim, now, json); vty_out(vty, "\n"); vty_out(vty, "Source Group RpfIface RpfAddress RibNextHop Metric Pref\n"); } for (ALL_LIST_ELEMENTS_RO(pim->upstream_list, up_node, up)) { char src_str[INET_ADDRSTRLEN]; char grp_str[INET_ADDRSTRLEN]; char rpf_addr_str[PREFIX_STRLEN]; char rib_nexthop_str[PREFIX_STRLEN]; const char *rpf_ifname; struct pim_rpf *rpf = &up->rpf; pim_inet4_dump("", up->sg.src, src_str, sizeof(src_str)); pim_inet4_dump("", up->sg.grp, grp_str, sizeof(grp_str)); pim_addr_dump("", &rpf->rpf_addr, rpf_addr_str, sizeof(rpf_addr_str)); pim_addr_dump("", &rpf->source_nexthop.mrib_nexthop_addr, rib_nexthop_str, sizeof(rib_nexthop_str)); rpf_ifname = rpf->source_nexthop.interface ? rpf->source_nexthop.interface->name : ""; if (uj) { json_object_object_get_ex(json, grp_str, &json_group); if (!json_group) { json_group = json_object_new_object(); json_object_object_add(json, grp_str, json_group); } json_row = json_object_new_object(); json_object_string_add(json_row, "source", src_str); json_object_string_add(json_row, "group", grp_str); json_object_string_add(json_row, "rpfInterface", rpf_ifname); json_object_string_add(json_row, "rpfAddress", rpf_addr_str); json_object_string_add(json_row, "ribNexthop", rib_nexthop_str); json_object_int_add( json_row, "routeMetric", rpf->source_nexthop.mrib_route_metric); json_object_int_add( json_row, "routePreference", rpf->source_nexthop.mrib_metric_preference); json_object_object_add(json_group, src_str, json_row); } else { vty_out(vty, "%-15s %-15s %-16s %-15s %-15s %6d %4d\n", src_str, grp_str, rpf_ifname, rpf_addr_str, rib_nexthop_str, rpf->source_nexthop.mrib_route_metric, rpf->source_nexthop.mrib_metric_preference); } } if (uj) { vty_out(vty, "%s\n", json_object_to_json_string_ext( json, JSON_C_TO_STRING_PRETTY)); json_object_free(json); } } struct pnc_cache_walk_data { struct vty *vty; struct pim_instance *pim; }; static int pim_print_pnc_cache_walkcb(struct hash_bucket *bucket, void *arg) { struct pim_nexthop_cache *pnc = bucket->data; struct pnc_cache_walk_data *cwd = arg; struct vty *vty = cwd->vty; struct pim_instance *pim = cwd->pim; struct nexthop *nh_node = NULL; ifindex_t first_ifindex; struct interface *ifp = NULL; for (nh_node = pnc->nexthop; nh_node; nh_node = nh_node->next) { first_ifindex = nh_node->ifindex; ifp = if_lookup_by_index(first_ifindex, pim->vrf_id); vty_out(vty, "%-15s ", inet_ntoa(pnc->rpf.rpf_addr.u.prefix4)); vty_out(vty, "%-16s ", ifp ? ifp->name : "NULL"); vty_out(vty, "%s ", inet_ntoa(nh_node->gate.ipv4)); vty_out(vty, "\n"); } return CMD_SUCCESS; } static void pim_show_nexthop(struct pim_instance *pim, struct vty *vty) { struct pnc_cache_walk_data cwd; cwd.vty = vty; cwd.pim = pim; vty_out(vty, "Number of registered addresses: %lu\n", pim->rpf_hash->count); vty_out(vty, "Address Interface Nexthop\n"); vty_out(vty, "---------------------------------------------\n"); hash_walk(pim->rpf_hash, pim_print_pnc_cache_walkcb, &cwd); } /* Display the bsm database details */ static void pim_show_bsm_db(struct pim_instance *pim, struct vty *vty, bool uj) { struct listnode *bsmnode; int count = 0; int fragment = 1; struct bsm_info *bsm; json_object *json = NULL; json_object *json_group = NULL; json_object *json_row = NULL; count = pim->global_scope.bsm_list->count; if (uj) { json = json_object_new_object(); json_object_int_add(json, "Number of the fragments", count); } else { vty_out(vty, "Scope Zone: Global\n"); vty_out(vty, "Number of the fragments: %d\n", count); vty_out(vty, "\n"); } for (ALL_LIST_ELEMENTS_RO(pim->global_scope.bsm_list, bsmnode, bsm)) { char grp_str[INET_ADDRSTRLEN]; char rp_str[INET_ADDRSTRLEN]; char bsr_str[INET_ADDRSTRLEN]; struct bsmmsg_grpinfo *group; struct bsmmsg_rpinfo *rpaddr; struct prefix grp; struct bsm_hdr *hdr; uint32_t offset = 0; uint8_t *buf; uint32_t len = 0; uint32_t frag_rp_cnt = 0; buf = bsm->bsm; len = bsm->size; /* skip pim header */ buf += PIM_MSG_HEADER_LEN; len -= PIM_MSG_HEADER_LEN; hdr = (struct bsm_hdr *)buf; /* BSM starts with bsr header */ buf += sizeof(struct bsm_hdr); len -= sizeof(struct bsm_hdr); pim_inet4_dump("", hdr->bsr_addr.addr, bsr_str, sizeof(bsr_str)); if (uj) { json_object_string_add(json, "BSR address", bsr_str); json_object_int_add(json, "BSR priority", hdr->bsr_prio); json_object_int_add(json, "Hashmask Length", hdr->hm_len); json_object_int_add(json, "Fragment Tag", ntohs(hdr->frag_tag)); } else { vty_out(vty, "BSM Fragment : %d\n", fragment); vty_out(vty, "------------------\n"); vty_out(vty, "%-15s %-15s %-15s %-15s\n", "BSR-Address", "BSR-Priority", "Hashmask-len", "Fragment-Tag"); vty_out(vty, "%-15s %-15d %-15d %-15d\n", bsr_str, hdr->bsr_prio, hdr->hm_len, ntohs(hdr->frag_tag)); } vty_out(vty, "\n"); while (offset < len) { group = (struct bsmmsg_grpinfo *)buf; if (group->group.family == PIM_MSG_ADDRESS_FAMILY_IPV4) grp.family = AF_INET; grp.prefixlen = group->group.mask; grp.u.prefix4.s_addr = group->group.addr.s_addr; prefix2str(&grp, grp_str, sizeof(grp_str)); buf += sizeof(struct bsmmsg_grpinfo); offset += sizeof(struct bsmmsg_grpinfo); if (uj) { json_object_object_get_ex(json, grp_str, &json_group); if (!json_group) { json_group = json_object_new_object(); json_object_int_add(json_group, "Rp Count", group->rp_count); json_object_int_add( json_group, "Fragment Rp count", group->frag_rp_count); json_object_object_add(json, grp_str, json_group); } } else { vty_out(vty, "Group : %s\n", grp_str); vty_out(vty, "-------------------\n"); vty_out(vty, "Rp Count:%d\n", group->rp_count); vty_out(vty, "Fragment Rp Count : %d\n", group->frag_rp_count); } frag_rp_cnt = group->frag_rp_count; if (!frag_rp_cnt) continue; if (!uj) vty_out(vty, "RpAddress HoldTime Priority\n"); while (frag_rp_cnt--) { rpaddr = (struct bsmmsg_rpinfo *)buf; buf += sizeof(struct bsmmsg_rpinfo); offset += sizeof(struct bsmmsg_rpinfo); pim_inet4_dump("", rpaddr->rpaddr.addr, rp_str, sizeof(rp_str)); if (uj) { json_row = json_object_new_object(); json_object_string_add( json_row, "Rp Address", rp_str); json_object_int_add( json_row, "Rp HoldTime", ntohs(rpaddr->rp_holdtime)); json_object_int_add(json_row, "Rp Priority", rpaddr->rp_pri); json_object_object_add( json_group, rp_str, json_row); } else { vty_out(vty, "%-15s %-12d %d\n", rp_str, ntohs(rpaddr->rp_holdtime), rpaddr->rp_pri); } } vty_out(vty, "\n"); } fragment++; } if (uj) { vty_out(vty, "%s\n", json_object_to_json_string_ext( json, JSON_C_TO_STRING_PRETTY)); json_object_free(json); } } /*Display the group-rp mappings */ static void pim_show_group_rp_mappings_info(struct pim_instance *pim, struct vty *vty, bool uj) { struct bsgrp_node *bsgrp; struct listnode *rpnode; struct bsm_rpinfo *bsm_rp; struct route_node *rn; char bsr_str[INET_ADDRSTRLEN]; json_object *json = NULL; json_object *json_group = NULL; json_object *json_row = NULL; if (pim->global_scope.current_bsr.s_addr == INADDR_ANY) strlcpy(bsr_str, "0.0.0.0", sizeof(bsr_str)); else pim_inet4_dump("", pim->global_scope.current_bsr, bsr_str, sizeof(bsr_str)); if (uj) { json = json_object_new_object(); json_object_string_add(json, "BSR Address", bsr_str); } else { vty_out(vty, "BSR Address %s\n", bsr_str); } for (rn = route_top(pim->global_scope.bsrp_table); rn; rn = route_next(rn)) { bsgrp = (struct bsgrp_node *)rn->info; if (!bsgrp) continue; char grp_str[INET_ADDRSTRLEN]; prefix2str(&bsgrp->group, grp_str, sizeof(grp_str)); if (uj) { json_object_object_get_ex(json, grp_str, &json_group); if (!json_group) { json_group = json_object_new_object(); json_object_object_add(json, grp_str, json_group); } } else { vty_out(vty, "Group Address %s\n", grp_str); vty_out(vty, "--------------------------\n"); vty_out(vty, "%-15s %-15s %-15s %-15s\n", "Rp Address", "priority", "Holdtime", "Hash"); vty_out(vty, "(ACTIVE)\n"); } if (bsgrp->bsrp_list) { for (ALL_LIST_ELEMENTS_RO(bsgrp->bsrp_list, rpnode, bsm_rp)) { char rp_str[INET_ADDRSTRLEN]; pim_inet4_dump("", bsm_rp->rp_address, rp_str, sizeof(rp_str)); if (uj) { json_row = json_object_new_object(); json_object_string_add( json_row, "Rp Address", rp_str); json_object_int_add( json_row, "Rp HoldTime", bsm_rp->rp_holdtime); json_object_int_add(json_row, "Rp Priority", bsm_rp->rp_prio); json_object_int_add(json_row, "Hash Val", bsm_rp->hash); json_object_object_add( json_group, rp_str, json_row); } else { vty_out(vty, "%-15s %-15u %-15u %-15u\n", rp_str, bsm_rp->rp_prio, bsm_rp->rp_holdtime, bsm_rp->hash); } } if (!bsgrp->bsrp_list->count && !uj) vty_out(vty, "Active List is empty.\n"); } if (uj) { json_object_int_add(json_group, "Pending RP count", bsgrp->pend_rp_cnt); } else { vty_out(vty, "(PENDING)\n"); vty_out(vty, "Pending RP count :%d\n", bsgrp->pend_rp_cnt); if (bsgrp->pend_rp_cnt) vty_out(vty, "%-15s %-15s %-15s %-15s\n", "Rp Address", "priority", "Holdtime", "Hash"); } if (bsgrp->partial_bsrp_list) { for (ALL_LIST_ELEMENTS_RO(bsgrp->partial_bsrp_list, rpnode, bsm_rp)) { char rp_str[INET_ADDRSTRLEN]; pim_inet4_dump("", bsm_rp->rp_address, rp_str, sizeof(rp_str)); if (uj) { json_row = json_object_new_object(); json_object_string_add( json_row, "Rp Address", rp_str); json_object_int_add( json_row, "Rp HoldTime", bsm_rp->rp_holdtime); json_object_int_add(json_row, "Rp Priority", bsm_rp->rp_prio); json_object_int_add(json_row, "Hash Val", bsm_rp->hash); json_object_object_add( json_group, rp_str, json_row); } else { vty_out(vty, "%-15s %-15u %-15u %-15u\n", rp_str, bsm_rp->rp_prio, bsm_rp->rp_holdtime, bsm_rp->hash); } } if (!bsgrp->partial_bsrp_list->count && !uj) vty_out(vty, "Partial List is empty\n"); } if (!uj) vty_out(vty, "\n"); } if (uj) { vty_out(vty, "%s\n", json_object_to_json_string_ext( json, JSON_C_TO_STRING_PRETTY)); json_object_free(json); } } /* pim statistics - just adding only bsm related now. * We can continue to add all pim related stats here. */ static void pim_show_statistics(struct pim_instance *pim, struct vty *vty, const char *ifname, bool uj) { json_object *json = NULL; struct interface *ifp; if (uj) { json = json_object_new_object(); json_object_int_add(json, "Number of Received BSMs", pim->bsm_rcvd); json_object_int_add(json, "Number of Forwared BSMs", pim->bsm_sent); json_object_int_add(json, "Number of Dropped BSMs", pim->bsm_dropped); } else { vty_out(vty, "BSM Statistics :\n"); vty_out(vty, "----------------\n"); vty_out(vty, "Number of Received BSMs : %" PRIu64 "\n", pim->bsm_rcvd); vty_out(vty, "Number of Forwared BSMs : %" PRIu64 "\n", pim->bsm_sent); vty_out(vty, "Number of Dropped BSMs : %" PRIu64 "\n", pim->bsm_dropped); } vty_out(vty, "\n"); /* scan interfaces */ FOR_ALL_INTERFACES (pim->vrf, ifp) { struct pim_interface *pim_ifp = ifp->info; if (ifname && strcmp(ifname, ifp->name)) continue; if (!pim_ifp) continue; if (!uj) { vty_out(vty, "Interface : %s\n", ifp->name); vty_out(vty, "-------------------\n"); vty_out(vty, "Number of BSMs dropped due to config miss : %u\n", pim_ifp->pim_ifstat_bsm_cfg_miss); vty_out(vty, "Number of unicast BSMs dropped : %u\n", pim_ifp->pim_ifstat_ucast_bsm_cfg_miss); vty_out(vty, "Number of BSMs dropped due to invalid scope zone : %u\n", pim_ifp->pim_ifstat_bsm_invalid_sz); } else { json_object *json_row = NULL; json_row = json_object_new_object(); json_object_string_add(json_row, "If Name", ifp->name); json_object_int_add( json_row, "Number of BSMs dropped due to config miss", pim_ifp->pim_ifstat_bsm_cfg_miss); json_object_int_add( json_row, "Number of unicast BSMs dropped", pim_ifp->pim_ifstat_ucast_bsm_cfg_miss); json_object_int_add(json_row, "Number of BSMs dropped due to invalid scope zone", pim_ifp->pim_ifstat_bsm_invalid_sz); json_object_object_add(json, ifp->name, json_row); } vty_out(vty, "\n"); } if (uj) { vty_out(vty, "%s\n", json_object_to_json_string_ext( json, JSON_C_TO_STRING_PRETTY)); json_object_free(json); } } static void clear_pim_statistics(struct pim_instance *pim) { struct interface *ifp; pim->bsm_rcvd = 0; pim->bsm_sent = 0; pim->bsm_dropped = 0; /* scan interfaces */ FOR_ALL_INTERFACES (pim->vrf, ifp) { struct pim_interface *pim_ifp = ifp->info; if (!pim_ifp) continue; pim_ifp->pim_ifstat_bsm_cfg_miss = 0; pim_ifp->pim_ifstat_ucast_bsm_cfg_miss = 0; pim_ifp->pim_ifstat_bsm_invalid_sz = 0; } } static void igmp_show_groups(struct pim_instance *pim, struct vty *vty, bool uj) { struct interface *ifp; time_t now; json_object *json = NULL; json_object *json_iface = NULL; json_object *json_row = NULL; now = pim_time_monotonic_sec(); if (uj) json = json_object_new_object(); else vty_out(vty, "Interface Address Group Mode Timer Srcs V Uptime \n"); /* scan interfaces */ FOR_ALL_INTERFACES (pim->vrf, ifp) { struct pim_interface *pim_ifp = ifp->info; struct listnode *sock_node; struct igmp_sock *igmp; if (!pim_ifp) continue; /* scan igmp sockets */ for (ALL_LIST_ELEMENTS_RO(pim_ifp->igmp_socket_list, sock_node, igmp)) { char ifaddr_str[INET_ADDRSTRLEN]; struct listnode *grpnode; struct igmp_group *grp; pim_inet4_dump("", igmp->ifaddr, ifaddr_str, sizeof(ifaddr_str)); /* scan igmp groups */ for (ALL_LIST_ELEMENTS_RO(igmp->igmp_group_list, grpnode, grp)) { char group_str[INET_ADDRSTRLEN]; char hhmmss[10]; char uptime[10]; pim_inet4_dump("", grp->group_addr, group_str, sizeof(group_str)); pim_time_timer_to_hhmmss(hhmmss, sizeof(hhmmss), grp->t_group_timer); pim_time_uptime(uptime, sizeof(uptime), now - grp->group_creation); if (uj) { json_object_object_get_ex( json, ifp->name, &json_iface); if (!json_iface) { json_iface = json_object_new_object(); json_object_pim_ifp_add( json_iface, ifp); json_object_object_add( json, ifp->name, json_iface); } json_row = json_object_new_object(); json_object_string_add( json_row, "source", ifaddr_str); json_object_string_add( json_row, "group", group_str); if (grp->igmp_version == 3) json_object_string_add( json_row, "mode", grp->group_filtermode_isexcl ? "EXCLUDE" : "INCLUDE"); json_object_string_add(json_row, "timer", hhmmss); json_object_int_add( json_row, "sourcesCount", grp->group_source_list ? listcount( grp->group_source_list) : 0); json_object_int_add(json_row, "version", grp->igmp_version); json_object_string_add( json_row, "uptime", uptime); json_object_object_add(json_iface, group_str, json_row); } else { vty_out(vty, "%-16s %-15s %-15s %4s %8s %4d %d %8s\n", ifp->name, ifaddr_str, group_str, grp->igmp_version == 3 ? (grp->group_filtermode_isexcl ? "EXCL" : "INCL") : "----", hhmmss, grp->group_source_list ? listcount( grp->group_source_list) : 0, grp->igmp_version, uptime); } } /* scan igmp groups */ } /* scan igmp sockets */ } /* scan interfaces */ if (uj) { vty_out(vty, "%s\n", json_object_to_json_string_ext( json, JSON_C_TO_STRING_PRETTY)); json_object_free(json); } } static void igmp_show_group_retransmission(struct pim_instance *pim, struct vty *vty) { struct interface *ifp; vty_out(vty, "Interface Address Group RetTimer Counter RetSrcs\n"); /* scan interfaces */ FOR_ALL_INTERFACES (pim->vrf, ifp) { struct pim_interface *pim_ifp = ifp->info; struct listnode *sock_node; struct igmp_sock *igmp; if (!pim_ifp) continue; /* scan igmp sockets */ for (ALL_LIST_ELEMENTS_RO(pim_ifp->igmp_socket_list, sock_node, igmp)) { char ifaddr_str[INET_ADDRSTRLEN]; struct listnode *grpnode; struct igmp_group *grp; pim_inet4_dump("", igmp->ifaddr, ifaddr_str, sizeof(ifaddr_str)); /* scan igmp groups */ for (ALL_LIST_ELEMENTS_RO(igmp->igmp_group_list, grpnode, grp)) { char group_str[INET_ADDRSTRLEN]; char grp_retr_mmss[10]; struct listnode *src_node; struct igmp_source *src; int grp_retr_sources = 0; pim_inet4_dump("", grp->group_addr, group_str, sizeof(group_str)); pim_time_timer_to_mmss( grp_retr_mmss, sizeof(grp_retr_mmss), grp->t_group_query_retransmit_timer); /* count group sources with retransmission state */ for (ALL_LIST_ELEMENTS_RO( grp->group_source_list, src_node, src)) { if (src->source_query_retransmit_count > 0) { ++grp_retr_sources; } } vty_out(vty, "%-16s %-15s %-15s %-8s %7d %7d\n", ifp->name, ifaddr_str, group_str, grp_retr_mmss, grp->group_specific_query_retransmit_count, grp_retr_sources); } /* scan igmp groups */ } /* scan igmp sockets */ } /* scan interfaces */ } static void igmp_show_sources(struct pim_instance *pim, struct vty *vty) { struct interface *ifp; time_t now; now = pim_time_monotonic_sec(); vty_out(vty, "Interface Address Group Source Timer Fwd Uptime \n"); /* scan interfaces */ FOR_ALL_INTERFACES (pim->vrf, ifp) { struct pim_interface *pim_ifp = ifp->info; struct listnode *sock_node; struct igmp_sock *igmp; if (!pim_ifp) continue; /* scan igmp sockets */ for (ALL_LIST_ELEMENTS_RO(pim_ifp->igmp_socket_list, sock_node, igmp)) { char ifaddr_str[INET_ADDRSTRLEN]; struct listnode *grpnode; struct igmp_group *grp; pim_inet4_dump("", igmp->ifaddr, ifaddr_str, sizeof(ifaddr_str)); /* scan igmp groups */ for (ALL_LIST_ELEMENTS_RO(igmp->igmp_group_list, grpnode, grp)) { char group_str[INET_ADDRSTRLEN]; struct listnode *srcnode; struct igmp_source *src; pim_inet4_dump("", grp->group_addr, group_str, sizeof(group_str)); /* scan group sources */ for (ALL_LIST_ELEMENTS_RO( grp->group_source_list, srcnode, src)) { char source_str[INET_ADDRSTRLEN]; char mmss[10]; char uptime[10]; pim_inet4_dump( "", src->source_addr, source_str, sizeof(source_str)); pim_time_timer_to_mmss( mmss, sizeof(mmss), src->t_source_timer); pim_time_uptime( uptime, sizeof(uptime), now - src->source_creation); vty_out(vty, "%-16s %-15s %-15s %-15s %5s %3s %8s\n", ifp->name, ifaddr_str, group_str, source_str, mmss, IGMP_SOURCE_TEST_FORWARDING( src->source_flags) ? "Y" : "N", uptime); } /* scan group sources */ } /* scan igmp groups */ } /* scan igmp sockets */ } /* scan interfaces */ } static void igmp_show_source_retransmission(struct pim_instance *pim, struct vty *vty) { struct interface *ifp; vty_out(vty, "Interface Address Group Source Counter\n"); /* scan interfaces */ FOR_ALL_INTERFACES (pim->vrf, ifp) { struct pim_interface *pim_ifp = ifp->info; struct listnode *sock_node; struct igmp_sock *igmp; if (!pim_ifp) continue; /* scan igmp sockets */ for (ALL_LIST_ELEMENTS_RO(pim_ifp->igmp_socket_list, sock_node, igmp)) { char ifaddr_str[INET_ADDRSTRLEN]; struct listnode *grpnode; struct igmp_group *grp; pim_inet4_dump("", igmp->ifaddr, ifaddr_str, sizeof(ifaddr_str)); /* scan igmp groups */ for (ALL_LIST_ELEMENTS_RO(igmp->igmp_group_list, grpnode, grp)) { char group_str[INET_ADDRSTRLEN]; struct listnode *srcnode; struct igmp_source *src; pim_inet4_dump("", grp->group_addr, group_str, sizeof(group_str)); /* scan group sources */ for (ALL_LIST_ELEMENTS_RO( grp->group_source_list, srcnode, src)) { char source_str[INET_ADDRSTRLEN]; pim_inet4_dump( "", src->source_addr, source_str, sizeof(source_str)); vty_out(vty, "%-16s %-15s %-15s %-15s %7d\n", ifp->name, ifaddr_str, group_str, source_str, src->source_query_retransmit_count); } /* scan group sources */ } /* scan igmp groups */ } /* scan igmp sockets */ } /* scan interfaces */ } static void pim_show_bsr(struct pim_instance *pim, struct vty *vty, bool uj) { char uptime[10]; char last_bsm_seen[10]; time_t now; char bsr_state[20]; char bsr_str[PREFIX_STRLEN]; json_object *json = NULL; vty_out(vty, "PIMv2 Bootstrap information\n"); if (pim->global_scope.current_bsr.s_addr == INADDR_ANY) { strlcpy(bsr_str, "0.0.0.0", sizeof(bsr_str)); pim_time_uptime(uptime, sizeof(uptime), pim->global_scope.current_bsr_first_ts); pim_time_uptime(last_bsm_seen, sizeof(last_bsm_seen), pim->global_scope.current_bsr_last_ts); } else { pim_inet4_dump("", pim->global_scope.current_bsr, bsr_str, sizeof(bsr_str)); now = pim_time_monotonic_sec(); pim_time_uptime(uptime, sizeof(uptime), (now - pim->global_scope.current_bsr_first_ts)); pim_time_uptime(last_bsm_seen, sizeof(last_bsm_seen), now - pim->global_scope.current_bsr_last_ts); } switch (pim->global_scope.state) { case NO_INFO: strlcpy(bsr_state, "NO_INFO", sizeof(bsr_state)); break; case ACCEPT_ANY: strlcpy(bsr_state, "ACCEPT_ANY", sizeof(bsr_state)); break; case ACCEPT_PREFERRED: strlcpy(bsr_state, "ACCEPT_PREFERRED", sizeof(bsr_state)); break; default: strlcpy(bsr_state, "", sizeof(bsr_state)); } if (uj) { json = json_object_new_object(); json_object_string_add(json, "bsr", bsr_str); json_object_int_add(json, "priority", pim->global_scope.current_bsr_prio); json_object_int_add(json, "fragment_tag", pim->global_scope.bsm_frag_tag); json_object_string_add(json, "state", bsr_state); json_object_string_add(json, "upTime", uptime); json_object_string_add(json, "last_bsm_seen", last_bsm_seen); } else { vty_out(vty, "Current preferred BSR address: %s\n", bsr_str); vty_out(vty, "Priority Fragment-Tag State UpTime\n"); vty_out(vty, " %-12d %-12d %-13s %7s\n", pim->global_scope.current_bsr_prio, pim->global_scope.bsm_frag_tag, bsr_state, uptime); vty_out(vty, "Last BSM seen: %s\n", last_bsm_seen); } if (uj) { vty_out(vty, "%s\n", json_object_to_json_string_ext( json, JSON_C_TO_STRING_PRETTY)); json_object_free(json); } } static void clear_igmp_interfaces(struct pim_instance *pim) { struct interface *ifp; FOR_ALL_INTERFACES (pim->vrf, ifp) pim_if_addr_del_all_igmp(ifp); FOR_ALL_INTERFACES (pim->vrf, ifp) pim_if_addr_add_all(ifp); } static void clear_pim_interfaces(struct pim_instance *pim) { struct interface *ifp; FOR_ALL_INTERFACES (pim->vrf, ifp) { if (ifp->info) { pim_neighbor_delete_all(ifp, "interface cleared"); } } } static void clear_interfaces(struct pim_instance *pim) { clear_igmp_interfaces(pim); clear_pim_interfaces(pim); } #define PIM_GET_PIM_INTERFACE(pim_ifp, ifp) \ pim_ifp = ifp->info; \ if (!pim_ifp) { \ vty_out(vty, \ "%% Enable PIM and/or IGMP on this interface first\n"); \ return CMD_WARNING_CONFIG_FAILED; \ } DEFUN (clear_ip_interfaces, clear_ip_interfaces_cmd, "clear ip interfaces [vrf NAME]", CLEAR_STR IP_STR "Reset interfaces\n" VRF_CMD_HELP_STR) { int idx = 2; struct vrf *vrf = pim_cmd_lookup_vrf(vty, argv, argc, &idx); if (!vrf) return CMD_WARNING; clear_interfaces(vrf->info); return CMD_SUCCESS; } DEFUN (clear_ip_igmp_interfaces, clear_ip_igmp_interfaces_cmd, "clear ip igmp [vrf NAME] interfaces", CLEAR_STR IP_STR CLEAR_IP_IGMP_STR VRF_CMD_HELP_STR "Reset IGMP interfaces\n") { int idx = 2; struct vrf *vrf = pim_cmd_lookup_vrf(vty, argv, argc, &idx); if (!vrf) return CMD_WARNING; clear_igmp_interfaces(vrf->info); return CMD_SUCCESS; } DEFUN (clear_ip_pim_statistics, clear_ip_pim_statistics_cmd, "clear ip pim statistics [vrf NAME]", CLEAR_STR IP_STR CLEAR_IP_PIM_STR VRF_CMD_HELP_STR "Reset PIM statistics\n") { int idx = 2; struct vrf *vrf = pim_cmd_lookup_vrf(vty, argv, argc, &idx); if (!vrf) return CMD_WARNING; clear_pim_statistics(vrf->info); return CMD_SUCCESS; } static void clear_mroute(struct pim_instance *pim) { struct pim_upstream *up; struct interface *ifp; /* scan interfaces */ FOR_ALL_INTERFACES (pim->vrf, ifp) { struct pim_interface *pim_ifp = ifp->info; struct listnode *sock_node; struct igmp_sock *igmp; struct pim_ifchannel *ch; if (!pim_ifp) continue; /* deleting all ifchannels */ while (!RB_EMPTY(pim_ifchannel_rb, &pim_ifp->ifchannel_rb)) { ch = RB_ROOT(pim_ifchannel_rb, &pim_ifp->ifchannel_rb); pim_ifchannel_delete(ch); } /* clean up all igmp groups */ /* scan igmp sockets */ for (ALL_LIST_ELEMENTS_RO(pim_ifp->igmp_socket_list, sock_node, igmp)) { struct igmp_group *grp; if (igmp->igmp_group_list) { while (igmp->igmp_group_list->count) { grp = listnode_head( igmp->igmp_group_list); igmp_group_delete(grp); } } } } /* clean up all upstreams*/ if (pim->upstream_list) { while (pim->upstream_list->count) { up = listnode_head(pim->upstream_list); pim_upstream_del(pim, up, __PRETTY_FUNCTION__); } } } DEFUN (clear_ip_mroute, clear_ip_mroute_cmd, "clear ip mroute [vrf NAME]", CLEAR_STR IP_STR "Reset multicast routes\n" VRF_CMD_HELP_STR) { int idx = 2; struct vrf *vrf = pim_cmd_lookup_vrf(vty, argv, argc, &idx); if (!vrf) return CMD_WARNING; clear_mroute(vrf->info); return CMD_SUCCESS; } DEFUN (clear_ip_pim_interfaces, clear_ip_pim_interfaces_cmd, "clear ip pim [vrf NAME] interfaces", CLEAR_STR IP_STR CLEAR_IP_PIM_STR VRF_CMD_HELP_STR "Reset PIM interfaces\n") { int idx = 2; struct vrf *vrf = pim_cmd_lookup_vrf(vty, argv, argc, &idx); if (!vrf) return CMD_WARNING; clear_pim_interfaces(vrf->info); return CMD_SUCCESS; } DEFUN (clear_ip_pim_interface_traffic, clear_ip_pim_interface_traffic_cmd, "clear ip pim [vrf NAME] interface traffic", "Reset functions\n" "IP information\n" "PIM clear commands\n" VRF_CMD_HELP_STR "Reset PIM interfaces\n" "Reset Protocol Packet counters\n") { int idx = 2; struct vrf *vrf = pim_cmd_lookup_vrf(vty, argv, argc, &idx); struct interface *ifp = NULL; struct pim_interface *pim_ifp = NULL; if (!vrf) return CMD_WARNING; FOR_ALL_INTERFACES (vrf, ifp) { pim_ifp = ifp->info; if (!pim_ifp) continue; pim_ifp->pim_ifstat_hello_recv = 0; pim_ifp->pim_ifstat_hello_sent = 0; pim_ifp->pim_ifstat_join_recv = 0; pim_ifp->pim_ifstat_join_send = 0; pim_ifp->pim_ifstat_prune_recv = 0; pim_ifp->pim_ifstat_prune_send = 0; pim_ifp->pim_ifstat_reg_recv = 0; pim_ifp->pim_ifstat_reg_send = 0; pim_ifp->pim_ifstat_reg_stop_recv = 0; pim_ifp->pim_ifstat_reg_stop_send = 0; pim_ifp->pim_ifstat_assert_recv = 0; pim_ifp->pim_ifstat_assert_send = 0; pim_ifp->pim_ifstat_bsm_rx = 0; pim_ifp->pim_ifstat_bsm_tx = 0; } return CMD_SUCCESS; } DEFUN (clear_ip_pim_oil, clear_ip_pim_oil_cmd, "clear ip pim [vrf NAME] oil", CLEAR_STR IP_STR CLEAR_IP_PIM_STR VRF_CMD_HELP_STR "Rescan PIM OIL (output interface list)\n") { int idx = 2; struct vrf *vrf = pim_cmd_lookup_vrf(vty, argv, argc, &idx); if (!vrf) return CMD_WARNING; pim_scan_oil(vrf->info); return CMD_SUCCESS; } DEFUN (show_ip_igmp_interface, show_ip_igmp_interface_cmd, "show ip igmp [vrf NAME] interface [detail|WORD] [json]", SHOW_STR IP_STR IGMP_STR VRF_CMD_HELP_STR "IGMP interface information\n" "Detailed output\n" "interface name\n" JSON_STR) { int idx = 2; struct vrf *vrf = pim_cmd_lookup_vrf(vty, argv, argc, &idx); bool uj = use_json(argc, argv); if (!vrf) return CMD_WARNING; if (argv_find(argv, argc, "detail", &idx) || argv_find(argv, argc, "WORD", &idx)) igmp_show_interfaces_single(vrf->info, vty, argv[idx]->arg, uj); else igmp_show_interfaces(vrf->info, vty, uj); return CMD_SUCCESS; } DEFUN (show_ip_igmp_interface_vrf_all, show_ip_igmp_interface_vrf_all_cmd, "show ip igmp vrf all interface [detail|WORD] [json]", SHOW_STR IP_STR IGMP_STR VRF_CMD_HELP_STR "IGMP interface information\n" "Detailed output\n" "interface name\n" JSON_STR) { int idx = 2; bool uj = use_json(argc, argv); struct vrf *vrf; bool first = true; if (uj) vty_out(vty, "{ "); RB_FOREACH (vrf, vrf_name_head, &vrfs_by_name) { if (uj) { if (!first) vty_out(vty, ", "); vty_out(vty, " \"%s\": ", vrf->name); first = false; } else vty_out(vty, "VRF: %s\n", vrf->name); if (argv_find(argv, argc, "detail", &idx) || argv_find(argv, argc, "WORD", &idx)) igmp_show_interfaces_single(vrf->info, vty, argv[idx]->arg, uj); else igmp_show_interfaces(vrf->info, vty, uj); } if (uj) vty_out(vty, "}\n"); return CMD_SUCCESS; } DEFUN (show_ip_igmp_join, show_ip_igmp_join_cmd, "show ip igmp [vrf NAME] join", SHOW_STR IP_STR IGMP_STR VRF_CMD_HELP_STR "IGMP static join information\n") { int idx = 2; struct vrf *vrf = pim_cmd_lookup_vrf(vty, argv, argc, &idx); if (!vrf) return CMD_WARNING; igmp_show_interface_join(vrf->info, vty); return CMD_SUCCESS; } DEFUN (show_ip_igmp_join_vrf_all, show_ip_igmp_join_vrf_all_cmd, "show ip igmp vrf all join", SHOW_STR IP_STR IGMP_STR VRF_CMD_HELP_STR "IGMP static join information\n") { bool uj = use_json(argc, argv); struct vrf *vrf; bool first = true; if (uj) vty_out(vty, "{ "); RB_FOREACH (vrf, vrf_name_head, &vrfs_by_name) { if (uj) { if (!first) vty_out(vty, ", "); vty_out(vty, " \"%s\": ", vrf->name); first = false; } else vty_out(vty, "VRF: %s\n", vrf->name); igmp_show_interface_join(vrf->info, vty); } if (uj) vty_out(vty, "}\n"); return CMD_SUCCESS; } DEFUN (show_ip_igmp_groups, show_ip_igmp_groups_cmd, "show ip igmp [vrf NAME] groups [json]", SHOW_STR IP_STR IGMP_STR VRF_CMD_HELP_STR IGMP_GROUP_STR JSON_STR) { int idx = 2; struct vrf *vrf = pim_cmd_lookup_vrf(vty, argv, argc, &idx); bool uj = use_json(argc, argv); if (!vrf) return CMD_WARNING; igmp_show_groups(vrf->info, vty, uj); return CMD_SUCCESS; } DEFUN (show_ip_igmp_groups_vrf_all, show_ip_igmp_groups_vrf_all_cmd, "show ip igmp vrf all groups [json]", SHOW_STR IP_STR IGMP_STR VRF_CMD_HELP_STR IGMP_GROUP_STR JSON_STR) { bool uj = use_json(argc, argv); struct vrf *vrf; bool first = true; if (uj) vty_out(vty, "{ "); RB_FOREACH (vrf, vrf_name_head, &vrfs_by_name) { if (uj) { if (!first) vty_out(vty, ", "); vty_out(vty, " \"%s\": ", vrf->name); first = false; } else vty_out(vty, "VRF: %s\n", vrf->name); igmp_show_groups(vrf->info, vty, uj); } if (uj) vty_out(vty, "}\n"); return CMD_SUCCESS; } DEFUN (show_ip_igmp_groups_retransmissions, show_ip_igmp_groups_retransmissions_cmd, "show ip igmp [vrf NAME] groups retransmissions", SHOW_STR IP_STR IGMP_STR VRF_CMD_HELP_STR IGMP_GROUP_STR "IGMP group retransmissions\n") { int idx = 2; struct vrf *vrf = pim_cmd_lookup_vrf(vty, argv, argc, &idx); if (!vrf) return CMD_WARNING; igmp_show_group_retransmission(vrf->info, vty); return CMD_SUCCESS; } DEFUN (show_ip_igmp_sources, show_ip_igmp_sources_cmd, "show ip igmp [vrf NAME] sources", SHOW_STR IP_STR IGMP_STR VRF_CMD_HELP_STR IGMP_SOURCE_STR) { int idx = 2; struct vrf *vrf = pim_cmd_lookup_vrf(vty, argv, argc, &idx); if (!vrf) return CMD_WARNING; igmp_show_sources(vrf->info, vty); return CMD_SUCCESS; } DEFUN (show_ip_igmp_sources_retransmissions, show_ip_igmp_sources_retransmissions_cmd, "show ip igmp [vrf NAME] sources retransmissions", SHOW_STR IP_STR IGMP_STR VRF_CMD_HELP_STR IGMP_SOURCE_STR "IGMP source retransmissions\n") { int idx = 2; struct vrf *vrf = pim_cmd_lookup_vrf(vty, argv, argc, &idx); if (!vrf) return CMD_WARNING; igmp_show_source_retransmission(vrf->info, vty); return CMD_SUCCESS; } DEFUN (show_ip_igmp_statistics, show_ip_igmp_statistics_cmd, "show ip igmp [vrf NAME] statistics [interface WORD] [json]", SHOW_STR IP_STR IGMP_STR VRF_CMD_HELP_STR "IGMP statistics\n" "interface\n" "IGMP interface\n" JSON_STR) { int idx = 2; struct vrf *vrf = pim_cmd_lookup_vrf(vty, argv, argc, &idx); bool uj = use_json(argc, argv); if (!vrf) return CMD_WARNING; if (argv_find(argv, argc, "WORD", &idx)) igmp_show_statistics(vrf->info, vty, argv[idx]->arg, uj); else igmp_show_statistics(vrf->info, vty, NULL, uj); return CMD_SUCCESS; } DEFUN (show_ip_pim_assert, show_ip_pim_assert_cmd, "show ip pim [vrf NAME] assert", SHOW_STR IP_STR PIM_STR VRF_CMD_HELP_STR "PIM interface assert\n") { int idx = 2; struct vrf *vrf = pim_cmd_lookup_vrf(vty, argv, argc, &idx); if (!vrf) return CMD_WARNING; pim_show_assert(vrf->info, vty); return CMD_SUCCESS; } DEFUN (show_ip_pim_assert_internal, show_ip_pim_assert_internal_cmd, "show ip pim [vrf NAME] assert-internal", SHOW_STR IP_STR PIM_STR VRF_CMD_HELP_STR "PIM interface internal assert state\n") { int idx = 2; struct vrf *vrf = pim_cmd_lookup_vrf(vty, argv, argc, &idx); if (!vrf) return CMD_WARNING; pim_show_assert_internal(vrf->info, vty); return CMD_SUCCESS; } DEFUN (show_ip_pim_assert_metric, show_ip_pim_assert_metric_cmd, "show ip pim [vrf NAME] assert-metric", SHOW_STR IP_STR PIM_STR VRF_CMD_HELP_STR "PIM interface assert metric\n") { int idx = 2; struct vrf *vrf = pim_cmd_lookup_vrf(vty, argv, argc, &idx); if (!vrf) return CMD_WARNING; pim_show_assert_metric(vrf->info, vty); return CMD_SUCCESS; } DEFUN (show_ip_pim_assert_winner_metric, show_ip_pim_assert_winner_metric_cmd, "show ip pim [vrf NAME] assert-winner-metric", SHOW_STR IP_STR PIM_STR VRF_CMD_HELP_STR "PIM interface assert winner metric\n") { int idx = 2; struct vrf *vrf = pim_cmd_lookup_vrf(vty, argv, argc, &idx); if (!vrf) return CMD_WARNING; pim_show_assert_winner_metric(vrf->info, vty); return CMD_SUCCESS; } DEFUN (show_ip_pim_interface, show_ip_pim_interface_cmd, "show ip pim [vrf NAME] interface [detail|WORD] [json]", SHOW_STR IP_STR PIM_STR VRF_CMD_HELP_STR "PIM interface information\n" "Detailed output\n" "interface name\n" JSON_STR) { int idx = 2; struct vrf *vrf = pim_cmd_lookup_vrf(vty, argv, argc, &idx); bool uj = use_json(argc, argv); if (!vrf) return CMD_WARNING; if (argv_find(argv, argc, "WORD", &idx) || argv_find(argv, argc, "detail", &idx)) pim_show_interfaces_single(vrf->info, vty, argv[idx]->arg, uj); else pim_show_interfaces(vrf->info, vty, uj); return CMD_SUCCESS; } DEFUN (show_ip_pim_interface_vrf_all, show_ip_pim_interface_vrf_all_cmd, "show ip pim vrf all interface [detail|WORD] [json]", SHOW_STR IP_STR PIM_STR VRF_CMD_HELP_STR "PIM interface information\n" "Detailed output\n" "interface name\n" JSON_STR) { int idx = 6; bool uj = use_json(argc, argv); struct vrf *vrf; bool first = true; if (uj) vty_out(vty, "{ "); RB_FOREACH (vrf, vrf_name_head, &vrfs_by_name) { if (uj) { if (!first) vty_out(vty, ", "); vty_out(vty, " \"%s\": ", vrf->name); first = false; } else vty_out(vty, "VRF: %s\n", vrf->name); if (argv_find(argv, argc, "WORD", &idx) || argv_find(argv, argc, "detail", &idx)) pim_show_interfaces_single(vrf->info, vty, argv[idx]->arg, uj); else pim_show_interfaces(vrf->info, vty, uj); } if (uj) vty_out(vty, "}\n"); return CMD_SUCCESS; } DEFPY (show_ip_pim_join, show_ip_pim_join_cmd, "show ip pim [vrf NAME] join [A.B.C.D$s_or_g [A.B.C.D$g]] [json$json]", SHOW_STR IP_STR PIM_STR VRF_CMD_HELP_STR "PIM interface join information\n" "The Source or Group\n" "The Group\n" JSON_STR) { struct prefix_sg sg = {0}; struct vrf *v; bool uj = !!json; struct pim_instance *pim; v = vrf_lookup_by_name(vrf ? vrf : VRF_DEFAULT_NAME); if (!v) { vty_out(vty, "%% Vrf specified: %s does not exist\n", vrf); return CMD_WARNING; } pim = pim_get_pim_instance(v->vrf_id); if (!pim) { vty_out(vty, "%% Unable to find pim instance\n"); return CMD_WARNING; } if (s_or_g.s_addr != 0) { if (g.s_addr != 0) { sg.src = s_or_g; sg.grp = g; } else sg.grp = s_or_g; } pim_show_join(pim, vty, &sg, uj); return CMD_SUCCESS; } DEFUN (show_ip_pim_join_vrf_all, show_ip_pim_join_vrf_all_cmd, "show ip pim vrf all join [json]", SHOW_STR IP_STR PIM_STR VRF_CMD_HELP_STR "PIM interface join information\n" JSON_STR) { struct prefix_sg sg = {0}; bool uj = use_json(argc, argv); struct vrf *vrf; bool first = true; if (uj) vty_out(vty, "{ "); RB_FOREACH (vrf, vrf_name_head, &vrfs_by_name) { if (uj) { if (!first) vty_out(vty, ", "); vty_out(vty, " \"%s\": ", vrf->name); first = false; } else vty_out(vty, "VRF: %s\n", vrf->name); pim_show_join(vrf->info, vty, &sg, uj); } if (uj) vty_out(vty, "}\n"); return CMD_WARNING; } DEFUN (show_ip_pim_local_membership, show_ip_pim_local_membership_cmd, "show ip pim [vrf NAME] local-membership [json]", SHOW_STR IP_STR PIM_STR VRF_CMD_HELP_STR "PIM interface local-membership\n" JSON_STR) { int idx = 2; struct vrf *vrf = pim_cmd_lookup_vrf(vty, argv, argc, &idx); bool uj = use_json(argc, argv); if (!vrf) return CMD_WARNING; pim_show_membership(vrf->info, vty, uj); return CMD_SUCCESS; } DEFUN (show_ip_pim_neighbor, show_ip_pim_neighbor_cmd, "show ip pim [vrf NAME] neighbor [detail|WORD] [json]", SHOW_STR IP_STR PIM_STR VRF_CMD_HELP_STR "PIM neighbor information\n" "Detailed output\n" "Name of interface or neighbor\n" JSON_STR) { int idx = 2; struct vrf *vrf = pim_cmd_lookup_vrf(vty, argv, argc, &idx); bool uj = use_json(argc, argv); if (!vrf) return CMD_WARNING; if (argv_find(argv, argc, "detail", &idx) || argv_find(argv, argc, "WORD", &idx)) pim_show_neighbors_single(vrf->info, vty, argv[idx]->arg, uj); else pim_show_neighbors(vrf->info, vty, uj); return CMD_SUCCESS; } DEFUN (show_ip_pim_neighbor_vrf_all, show_ip_pim_neighbor_vrf_all_cmd, "show ip pim vrf all neighbor [detail|WORD] [json]", SHOW_STR IP_STR PIM_STR VRF_CMD_HELP_STR "PIM neighbor information\n" "Detailed output\n" "Name of interface or neighbor\n" JSON_STR) { int idx = 2; bool uj = use_json(argc, argv); struct vrf *vrf; bool first = true; if (uj) vty_out(vty, "{ "); RB_FOREACH (vrf, vrf_name_head, &vrfs_by_name) { if (uj) { if (!first) vty_out(vty, ", "); vty_out(vty, " \"%s\": ", vrf->name); first = false; } else vty_out(vty, "VRF: %s\n", vrf->name); if (argv_find(argv, argc, "detail", &idx) || argv_find(argv, argc, "WORD", &idx)) pim_show_neighbors_single(vrf->info, vty, argv[idx]->arg, uj); else pim_show_neighbors(vrf->info, vty, uj); } if (uj) vty_out(vty, "}\n"); return CMD_SUCCESS; } DEFUN (show_ip_pim_secondary, show_ip_pim_secondary_cmd, "show ip pim [vrf NAME] secondary", SHOW_STR IP_STR PIM_STR VRF_CMD_HELP_STR "PIM neighbor addresses\n") { int idx = 2; struct vrf *vrf = pim_cmd_lookup_vrf(vty, argv, argc, &idx); if (!vrf) return CMD_WARNING; pim_show_neighbors_secondary(vrf->info, vty); return CMD_SUCCESS; } DEFUN (show_ip_pim_state, show_ip_pim_state_cmd, "show ip pim [vrf NAME] state [A.B.C.D [A.B.C.D]] [json]", SHOW_STR IP_STR PIM_STR VRF_CMD_HELP_STR "PIM state information\n" "Unicast or Multicast address\n" "Multicast address\n" JSON_STR) { const char *src_or_group = NULL; const char *group = NULL; int idx = 2; struct vrf *vrf = pim_cmd_lookup_vrf(vty, argv, argc, &idx); bool uj = use_json(argc, argv); if (!vrf) return CMD_WARNING; if (uj) argc--; if (argv_find(argv, argc, "A.B.C.D", &idx)) { src_or_group = argv[idx]->arg; if (idx + 1 < argc) group = argv[idx + 1]->arg; } pim_show_state(vrf->info, vty, src_or_group, group, uj); return CMD_SUCCESS; } DEFUN (show_ip_pim_state_vrf_all, show_ip_pim_state_vrf_all_cmd, "show ip pim vrf all state [A.B.C.D [A.B.C.D]] [json]", SHOW_STR IP_STR PIM_STR VRF_CMD_HELP_STR "PIM state information\n" "Unicast or Multicast address\n" "Multicast address\n" JSON_STR) { const char *src_or_group = NULL; const char *group = NULL; int idx = 2; bool uj = use_json(argc, argv); struct vrf *vrf; bool first = true; if (uj) { vty_out(vty, "{ "); argc--; } if (argv_find(argv, argc, "A.B.C.D", &idx)) { src_or_group = argv[idx]->arg; if (idx + 1 < argc) group = argv[idx + 1]->arg; } RB_FOREACH (vrf, vrf_name_head, &vrfs_by_name) { if (uj) { if (!first) vty_out(vty, ", "); vty_out(vty, " \"%s\": ", vrf->name); first = false; } else vty_out(vty, "VRF: %s\n", vrf->name); pim_show_state(vrf->info, vty, src_or_group, group, uj); } if (uj) vty_out(vty, "}\n"); return CMD_SUCCESS; } DEFPY (show_ip_pim_upstream, show_ip_pim_upstream_cmd, "show ip pim [vrf NAME] upstream [A.B.C.D$s_or_g [A.B.C.D$g]] [json$json]", SHOW_STR IP_STR PIM_STR VRF_CMD_HELP_STR "PIM upstream information\n" "The Source or Group\n" "The Group\n" JSON_STR) { struct prefix_sg sg = {0}; struct vrf *v; bool uj = !!json; struct pim_instance *pim; v = vrf_lookup_by_name(vrf ? vrf : VRF_DEFAULT_NAME); if (!v) { vty_out(vty, "%% Vrf specified: %s does not exist\n", vrf); return CMD_WARNING; } pim = pim_get_pim_instance(v->vrf_id); if (!pim) { vty_out(vty, "%% Unable to find pim instance\n"); return CMD_WARNING; } if (s_or_g.s_addr != 0) { if (g.s_addr != 0) { sg.src = s_or_g; sg.grp = g; } else sg.grp = s_or_g; } pim_show_upstream(pim, vty, &sg, uj); return CMD_SUCCESS; } DEFUN (show_ip_pim_upstream_vrf_all, show_ip_pim_upstream_vrf_all_cmd, "show ip pim vrf all upstream [json]", SHOW_STR IP_STR PIM_STR VRF_CMD_HELP_STR "PIM upstream information\n" JSON_STR) { struct prefix_sg sg = {0}; bool uj = use_json(argc, argv); struct vrf *vrf; bool first = true; if (uj) vty_out(vty, "{ "); RB_FOREACH (vrf, vrf_name_head, &vrfs_by_name) { if (uj) { if (!first) vty_out(vty, ", "); vty_out(vty, " \"%s\": ", vrf->name); first = false; } else vty_out(vty, "VRF: %s\n", vrf->name); pim_show_upstream(vrf->info, vty, &sg, uj); } return CMD_SUCCESS; } DEFUN (show_ip_pim_upstream_join_desired, show_ip_pim_upstream_join_desired_cmd, "show ip pim [vrf NAME] upstream-join-desired [json]", SHOW_STR IP_STR PIM_STR VRF_CMD_HELP_STR "PIM upstream join-desired\n" JSON_STR) { int idx = 2; struct vrf *vrf = pim_cmd_lookup_vrf(vty, argv, argc, &idx); bool uj = use_json(argc, argv); if (!vrf) return CMD_WARNING; pim_show_join_desired(vrf->info, vty, uj); return CMD_SUCCESS; } DEFUN (show_ip_pim_upstream_rpf, show_ip_pim_upstream_rpf_cmd, "show ip pim [vrf NAME] upstream-rpf [json]", SHOW_STR IP_STR PIM_STR VRF_CMD_HELP_STR "PIM upstream source rpf\n" JSON_STR) { int idx = 2; struct vrf *vrf = pim_cmd_lookup_vrf(vty, argv, argc, &idx); bool uj = use_json(argc, argv); if (!vrf) return CMD_WARNING; pim_show_upstream_rpf(vrf->info, vty, uj); return CMD_SUCCESS; } DEFUN (show_ip_pim_rp, show_ip_pim_rp_cmd, "show ip pim [vrf NAME] rp-info [json]", SHOW_STR IP_STR PIM_STR VRF_CMD_HELP_STR "PIM RP information\n" JSON_STR) { int idx = 2; struct vrf *vrf = pim_cmd_lookup_vrf(vty, argv, argc, &idx); bool uj = use_json(argc, argv); if (!vrf) return CMD_WARNING; pim_rp_show_information(vrf->info, vty, uj); return CMD_SUCCESS; } DEFUN (show_ip_pim_rp_vrf_all, show_ip_pim_rp_vrf_all_cmd, "show ip pim vrf all rp-info [json]", SHOW_STR IP_STR PIM_STR VRF_CMD_HELP_STR "PIM RP information\n" JSON_STR) { bool uj = use_json(argc, argv); struct vrf *vrf; bool first = true; if (uj) vty_out(vty, "{ "); RB_FOREACH (vrf, vrf_name_head, &vrfs_by_name) { if (uj) { if (!first) vty_out(vty, ", "); vty_out(vty, " \"%s\": ", vrf->name); first = false; } else vty_out(vty, "VRF: %s\n", vrf->name); pim_rp_show_information(vrf->info, vty, uj); } if (uj) vty_out(vty, "}\n"); return CMD_SUCCESS; } DEFUN (show_ip_pim_rpf, show_ip_pim_rpf_cmd, "show ip pim [vrf NAME] rpf [json]", SHOW_STR IP_STR PIM_STR VRF_CMD_HELP_STR "PIM cached source rpf information\n" JSON_STR) { int idx = 2; struct vrf *vrf = pim_cmd_lookup_vrf(vty, argv, argc, &idx); bool uj = use_json(argc, argv); if (!vrf) return CMD_WARNING; pim_show_rpf(vrf->info, vty, uj); return CMD_SUCCESS; } DEFUN (show_ip_pim_rpf_vrf_all, show_ip_pim_rpf_vrf_all_cmd, "show ip pim vrf all rpf [json]", SHOW_STR IP_STR PIM_STR VRF_CMD_HELP_STR "PIM cached source rpf information\n" JSON_STR) { bool uj = use_json(argc, argv); struct vrf *vrf; bool first = true; if (uj) vty_out(vty, "{ "); RB_FOREACH (vrf, vrf_name_head, &vrfs_by_name) { if (uj) { if (!first) vty_out(vty, ", "); vty_out(vty, " \"%s\": ", vrf->name); first = false; } else vty_out(vty, "VRF: %s\n", vrf->name); pim_show_rpf(vrf->info, vty, uj); } if (uj) vty_out(vty, "}\n"); return CMD_SUCCESS; } DEFUN (show_ip_pim_nexthop, show_ip_pim_nexthop_cmd, "show ip pim [vrf NAME] nexthop", SHOW_STR IP_STR PIM_STR VRF_CMD_HELP_STR "PIM cached nexthop rpf information\n") { int idx = 2; struct vrf *vrf = pim_cmd_lookup_vrf(vty, argv, argc, &idx); if (!vrf) return CMD_WARNING; pim_show_nexthop(vrf->info, vty); return CMD_SUCCESS; } DEFUN (show_ip_pim_nexthop_lookup, show_ip_pim_nexthop_lookup_cmd, "show ip pim [vrf NAME] nexthop-lookup A.B.C.D A.B.C.D", SHOW_STR IP_STR PIM_STR VRF_CMD_HELP_STR "PIM cached nexthop rpf lookup\n" "Source/RP address\n" "Multicast Group address\n") { struct prefix nht_p; int result = 0; struct in_addr src_addr, grp_addr; struct in_addr vif_source; const char *addr_str, *addr_str1; struct prefix grp; struct pim_nexthop nexthop; char nexthop_addr_str[PREFIX_STRLEN]; char grp_str[PREFIX_STRLEN]; int idx = 2; struct vrf *vrf = pim_cmd_lookup_vrf(vty, argv, argc, &idx); if (!vrf) return CMD_WARNING; argv_find(argv, argc, "A.B.C.D", &idx); addr_str = argv[idx]->arg; result = inet_pton(AF_INET, addr_str, &src_addr); if (result <= 0) { vty_out(vty, "Bad unicast address %s: errno=%d: %s\n", addr_str, errno, safe_strerror(errno)); return CMD_WARNING; } if (pim_is_group_224_4(src_addr)) { vty_out(vty, "Invalid argument. Expected Valid Source Address.\n"); return CMD_WARNING; } addr_str1 = argv[idx + 1]->arg; result = inet_pton(AF_INET, addr_str1, &grp_addr); if (result <= 0) { vty_out(vty, "Bad unicast address %s: errno=%d: %s\n", addr_str, errno, safe_strerror(errno)); return CMD_WARNING; } if (!pim_is_group_224_4(grp_addr)) { vty_out(vty, "Invalid argument. Expected Valid Multicast Group Address.\n"); return CMD_WARNING; } if (!pim_rp_set_upstream_addr(vrf->info, &vif_source, src_addr, grp_addr)) return CMD_SUCCESS; nht_p.family = AF_INET; nht_p.prefixlen = IPV4_MAX_BITLEN; nht_p.u.prefix4 = vif_source; grp.family = AF_INET; grp.prefixlen = IPV4_MAX_BITLEN; grp.u.prefix4 = grp_addr; memset(&nexthop, 0, sizeof(nexthop)); result = pim_ecmp_nexthop_lookup(vrf->info, &nexthop, &nht_p, &grp, 0); if (!result) { vty_out(vty, "Nexthop Lookup failed, no usable routes returned.\n"); return CMD_SUCCESS; } pim_addr_dump("", &grp, grp_str, sizeof(grp_str)); pim_addr_dump("", &nexthop.mrib_nexthop_addr, nexthop_addr_str, sizeof(nexthop_addr_str)); vty_out(vty, "Group %s --- Nexthop %s Interface %s \n", grp_str, nexthop_addr_str, nexthop.interface->name); return CMD_SUCCESS; } DEFUN (show_ip_pim_interface_traffic, show_ip_pim_interface_traffic_cmd, "show ip pim [vrf NAME] interface traffic [WORD] [json]", SHOW_STR IP_STR PIM_STR VRF_CMD_HELP_STR "PIM interface information\n" "Protocol Packet counters\n" "Interface name\n" JSON_STR) { int idx = 2; struct vrf *vrf = pim_cmd_lookup_vrf(vty, argv, argc, &idx); bool uj = use_json(argc, argv); if (!vrf) return CMD_WARNING; if (argv_find(argv, argc, "WORD", &idx)) pim_show_interface_traffic_single(vrf->info, vty, argv[idx]->arg, uj); else pim_show_interface_traffic(vrf->info, vty, uj); return CMD_SUCCESS; } DEFUN (show_ip_pim_bsm_db, show_ip_pim_bsm_db_cmd, "show ip pim bsm-database [vrf NAME] [json]", SHOW_STR IP_STR PIM_STR "PIM cached bsm packets information\n" VRF_CMD_HELP_STR JSON_STR) { int idx = 2; struct vrf *vrf = pim_cmd_lookup_vrf(vty, argv, argc, &idx); bool uj = use_json(argc, argv); if (!vrf) return CMD_WARNING; pim_show_bsm_db(vrf->info, vty, uj); return CMD_SUCCESS; } DEFUN (show_ip_pim_bsrp, show_ip_pim_bsrp_cmd, "show ip pim bsrp-info [vrf NAME] [json]", SHOW_STR IP_STR PIM_STR "PIM cached group-rp mappings information\n" VRF_CMD_HELP_STR JSON_STR) { int idx = 2; struct vrf *vrf = pim_cmd_lookup_vrf(vty, argv, argc, &idx); bool uj = use_json(argc, argv); if (!vrf) return CMD_WARNING; pim_show_group_rp_mappings_info(vrf->info, vty, uj); return CMD_SUCCESS; } DEFUN (show_ip_pim_statistics, show_ip_pim_statistics_cmd, "show ip pim [vrf NAME] statistics [interface WORD] [json]", SHOW_STR IP_STR PIM_STR VRF_CMD_HELP_STR "PIM statistics\n" INTERFACE_STR "PIM interface\n" JSON_STR) { int idx = 2; struct vrf *vrf = pim_cmd_lookup_vrf(vty, argv, argc, &idx); bool uj = use_json(argc, argv); if (!vrf) return CMD_WARNING; if (argv_find(argv, argc, "WORD", &idx)) pim_show_statistics(vrf->info, vty, argv[idx]->arg, uj); else pim_show_statistics(vrf->info, vty, NULL, uj); return CMD_SUCCESS; } static void show_multicast_interfaces(struct pim_instance *pim, struct vty *vty) { struct interface *ifp; vty_out(vty, "\n"); vty_out(vty, "Interface Address ifi Vif PktsIn PktsOut BytesIn BytesOut\n"); FOR_ALL_INTERFACES (pim->vrf, ifp) { struct pim_interface *pim_ifp; struct in_addr ifaddr; struct sioc_vif_req vreq; pim_ifp = ifp->info; if (!pim_ifp) continue; memset(&vreq, 0, sizeof(vreq)); vreq.vifi = pim_ifp->mroute_vif_index; if (ioctl(pim->mroute_socket, SIOCGETVIFCNT, &vreq)) { zlog_warn( "ioctl(SIOCGETVIFCNT=%lu) failure for interface %s vif_index=%d: errno=%d: %s", (unsigned long)SIOCGETVIFCNT, ifp->name, pim_ifp->mroute_vif_index, errno, safe_strerror(errno)); } ifaddr = pim_ifp->primary_address; vty_out(vty, "%-16s %-15s %3d %3d %7lu %7lu %10lu %10lu\n", ifp->name, inet_ntoa(ifaddr), ifp->ifindex, pim_ifp->mroute_vif_index, (unsigned long)vreq.icount, (unsigned long)vreq.ocount, (unsigned long)vreq.ibytes, (unsigned long)vreq.obytes); } } static void pim_cmd_show_ip_multicast_helper(struct pim_instance *pim, struct vty *vty) { struct vrf *vrf = pim->vrf; time_t now = pim_time_monotonic_sec(); char uptime[10]; char mlag_role[80]; pim = vrf->info; vty_out(vty, "Router MLAG Role: %s\n", mlag_role2str(router->role, mlag_role, sizeof(mlag_role))); vty_out(vty, "Mroute socket descriptor:"); vty_out(vty, " %d(%s)\n", pim->mroute_socket, vrf->name); pim_time_uptime(uptime, sizeof(uptime), now - pim->mroute_socket_creation); vty_out(vty, "Mroute socket uptime: %s\n", uptime); vty_out(vty, "\n"); pim_zebra_zclient_update(vty); pim_zlookup_show_ip_multicast(vty); vty_out(vty, "\n"); vty_out(vty, "Maximum highest VifIndex: %d\n", PIM_MAX_USABLE_VIFS); vty_out(vty, "\n"); vty_out(vty, "Upstream Join Timer: %d secs\n", router->t_periodic); vty_out(vty, "Join/Prune Holdtime: %d secs\n", PIM_JP_HOLDTIME); vty_out(vty, "PIM ECMP: %s\n", pim->ecmp_enable ? "Enable" : "Disable"); vty_out(vty, "PIM ECMP Rebalance: %s\n", pim->ecmp_rebalance_enable ? "Enable" : "Disable"); vty_out(vty, "\n"); show_rpf_refresh_stats(vty, pim, now, NULL); vty_out(vty, "\n"); show_scan_oil_stats(pim, vty, now); show_multicast_interfaces(pim, vty); } DEFUN (show_ip_multicast, show_ip_multicast_cmd, "show ip multicast [vrf NAME]", SHOW_STR IP_STR VRF_CMD_HELP_STR "Multicast global information\n") { int idx = 2; struct vrf *vrf = pim_cmd_lookup_vrf(vty, argv, argc, &idx); if (!vrf) return CMD_WARNING; pim_cmd_show_ip_multicast_helper(vrf->info, vty); return CMD_SUCCESS; } DEFUN (show_ip_multicast_vrf_all, show_ip_multicast_vrf_all_cmd, "show ip multicast vrf all", SHOW_STR IP_STR VRF_CMD_HELP_STR "Multicast global information\n") { bool uj = use_json(argc, argv); struct vrf *vrf; bool first = true; if (uj) vty_out(vty, "{ "); RB_FOREACH (vrf, vrf_name_head, &vrfs_by_name) { if (uj) { if (!first) vty_out(vty, ", "); vty_out(vty, " \"%s\": ", vrf->name); first = false; } else vty_out(vty, "VRF: %s\n", vrf->name); pim_cmd_show_ip_multicast_helper(vrf->info, vty); } if (uj) vty_out(vty, "}\n"); return CMD_SUCCESS; } static void show_mroute(struct pim_instance *pim, struct vty *vty, struct prefix_sg *sg, bool fill, bool uj) { struct listnode *node; struct channel_oil *c_oil; struct static_route *s_route; time_t now; json_object *json = NULL; json_object *json_group = NULL; json_object *json_source = NULL; json_object *json_oil = NULL; json_object *json_ifp_out = NULL; int found_oif; int first; char grp_str[INET_ADDRSTRLEN]; char src_str[INET_ADDRSTRLEN]; char in_ifname[INTERFACE_NAMSIZ + 1]; char out_ifname[INTERFACE_NAMSIZ + 1]; int oif_vif_index; struct interface *ifp_in; char proto[100]; if (uj) { json = json_object_new_object(); } else { vty_out(vty, "Source Group Proto Input Output TTL Uptime\n"); } now = pim_time_monotonic_sec(); /* print list of PIM and IGMP routes */ for (ALL_LIST_ELEMENTS_RO(pim->channel_oil_list, node, c_oil)) { found_oif = 0; first = 1; if (!c_oil->installed && !uj) continue; if (sg->grp.s_addr != 0 && sg->grp.s_addr != c_oil->oil.mfcc_mcastgrp.s_addr) continue; if (sg->src.s_addr != 0 && sg->src.s_addr != c_oil->oil.mfcc_origin.s_addr) continue; pim_inet4_dump("", c_oil->oil.mfcc_mcastgrp, grp_str, sizeof(grp_str)); pim_inet4_dump("", c_oil->oil.mfcc_origin, src_str, sizeof(src_str)); ifp_in = pim_if_find_by_vif_index(pim, c_oil->oil.mfcc_parent); if (ifp_in) strlcpy(in_ifname, ifp_in->name, sizeof(in_ifname)); else strlcpy(in_ifname, "", sizeof(in_ifname)); if (uj) { /* Find the group, create it if it doesn't exist */ json_object_object_get_ex(json, grp_str, &json_group); if (!json_group) { json_group = json_object_new_object(); json_object_object_add(json, grp_str, json_group); } /* Find the source nested under the group, create it if * it doesn't exist */ json_object_object_get_ex(json_group, src_str, &json_source); if (!json_source) { json_source = json_object_new_object(); json_object_object_add(json_group, src_str, json_source); } /* Find the inbound interface nested under the source, * create it if it doesn't exist */ json_object_int_add(json_source, "installed", c_oil->installed); json_object_int_add(json_source, "refCount", c_oil->oil_ref_count); json_object_int_add(json_source, "oilSize", c_oil->oil_size); json_object_int_add(json_source, "OilInheritedRescan", c_oil->oil_inherited_rescan); json_object_string_add(json_source, "iif", in_ifname); json_oil = NULL; } for (oif_vif_index = 0; oif_vif_index < MAXVIFS; ++oif_vif_index) { struct interface *ifp_out; char mroute_uptime[10]; int ttl; ttl = c_oil->oil.mfcc_ttls[oif_vif_index]; if (ttl < 1) continue; ifp_out = pim_if_find_by_vif_index(pim, oif_vif_index); pim_time_uptime( mroute_uptime, sizeof(mroute_uptime), now - c_oil->mroute_creation); found_oif = 1; if (ifp_out) strlcpy(out_ifname, ifp_out->name, sizeof(out_ifname)); else strlcpy(out_ifname, "", sizeof(out_ifname)); if (uj) { json_ifp_out = json_object_new_object(); json_object_string_add(json_ifp_out, "source", src_str); json_object_string_add(json_ifp_out, "group", grp_str); if (c_oil->oif_flags[oif_vif_index] & PIM_OIF_FLAG_PROTO_PIM) json_object_boolean_true_add( json_ifp_out, "protocolPim"); if (c_oil->oif_flags[oif_vif_index] & PIM_OIF_FLAG_PROTO_IGMP) json_object_boolean_true_add( json_ifp_out, "protocolIgmp"); if (c_oil->oif_flags[oif_vif_index] & PIM_OIF_FLAG_PROTO_VXLAN) json_object_boolean_true_add( json_ifp_out, "protocolVxlan"); if (c_oil->oif_flags[oif_vif_index] & PIM_OIF_FLAG_PROTO_STAR) json_object_boolean_true_add( json_ifp_out, "protocolInherited"); json_object_string_add(json_ifp_out, "inboundInterface", in_ifname); json_object_int_add(json_ifp_out, "iVifI", c_oil->oil.mfcc_parent); json_object_string_add(json_ifp_out, "outboundInterface", out_ifname); json_object_int_add(json_ifp_out, "oVifI", oif_vif_index); json_object_int_add(json_ifp_out, "ttl", ttl); json_object_string_add(json_ifp_out, "upTime", mroute_uptime); if (!json_oil) { json_oil = json_object_new_object(); json_object_object_add(json_source, "oil", json_oil); } json_object_object_add(json_oil, out_ifname, json_ifp_out); } else { if (c_oil->oif_flags[oif_vif_index] & PIM_OIF_FLAG_PROTO_PIM) { strlcpy(proto, "PIM", sizeof(proto)); } if (c_oil->oif_flags[oif_vif_index] & PIM_OIF_FLAG_PROTO_IGMP) { strlcpy(proto, "IGMP", sizeof(proto)); } if (c_oil->oif_flags[oif_vif_index] & PIM_OIF_FLAG_PROTO_VXLAN) { strlcpy(proto, "VxLAN", sizeof(proto)); } if (c_oil->oif_flags[oif_vif_index] & PIM_OIF_FLAG_PROTO_STAR) { strlcpy(proto, "STAR", sizeof(proto)); } vty_out(vty, "%-15s %-15s %-6s %-16s %-16s %-3d %8s\n", src_str, grp_str, proto, in_ifname, out_ifname, ttl, mroute_uptime); if (first) { src_str[0] = '\0'; grp_str[0] = '\0'; in_ifname[0] = '\0'; first = 0; } } } if (!uj && !found_oif) { vty_out(vty, "%-15s %-15s %-6s %-16s %-16s %-3d %8s\n", src_str, grp_str, "none", in_ifname, "none", 0, "--:--:--"); } } /* Print list of static routes */ for (ALL_LIST_ELEMENTS_RO(pim->static_routes, node, s_route)) { first = 1; if (!s_route->c_oil.installed) continue; pim_inet4_dump("", s_route->group, grp_str, sizeof(grp_str)); pim_inet4_dump("", s_route->source, src_str, sizeof(src_str)); ifp_in = pim_if_find_by_vif_index(pim, s_route->iif); found_oif = 0; if (ifp_in) strlcpy(in_ifname, ifp_in->name, sizeof(in_ifname)); else strlcpy(in_ifname, "", sizeof(in_ifname)); if (uj) { /* Find the group, create it if it doesn't exist */ json_object_object_get_ex(json, grp_str, &json_group); if (!json_group) { json_group = json_object_new_object(); json_object_object_add(json, grp_str, json_group); } /* Find the source nested under the group, create it if * it doesn't exist */ json_object_object_get_ex(json_group, src_str, &json_source); if (!json_source) { json_source = json_object_new_object(); json_object_object_add(json_group, src_str, json_source); } json_object_string_add(json_source, "iif", in_ifname); json_oil = NULL; } else { strlcpy(proto, "STATIC", sizeof(proto)); } for (oif_vif_index = 0; oif_vif_index < MAXVIFS; ++oif_vif_index) { struct interface *ifp_out; char oif_uptime[10]; int ttl; ttl = s_route->oif_ttls[oif_vif_index]; if (ttl < 1) continue; ifp_out = pim_if_find_by_vif_index(pim, oif_vif_index); pim_time_uptime( oif_uptime, sizeof(oif_uptime), now - s_route->c_oil .oif_creation[oif_vif_index]); found_oif = 1; if (ifp_out) strlcpy(out_ifname, ifp_out->name, sizeof(out_ifname)); else strlcpy(out_ifname, "", sizeof(out_ifname)); if (uj) { json_ifp_out = json_object_new_object(); json_object_string_add(json_ifp_out, "source", src_str); json_object_string_add(json_ifp_out, "group", grp_str); json_object_boolean_true_add(json_ifp_out, "protocolStatic"); json_object_string_add(json_ifp_out, "inboundInterface", in_ifname); json_object_int_add( json_ifp_out, "iVifI", s_route->c_oil.oil.mfcc_parent); json_object_string_add(json_ifp_out, "outboundInterface", out_ifname); json_object_int_add(json_ifp_out, "oVifI", oif_vif_index); json_object_int_add(json_ifp_out, "ttl", ttl); json_object_string_add(json_ifp_out, "upTime", oif_uptime); if (!json_oil) { json_oil = json_object_new_object(); json_object_object_add(json_source, "oil", json_oil); } json_object_object_add(json_oil, out_ifname, json_ifp_out); } else { vty_out(vty, "%-15s %-15s %-6s %-16s %-16s %-3d %8s %s\n", src_str, grp_str, proto, in_ifname, out_ifname, ttl, oif_uptime, pim->vrf->name); if (first && !fill) { src_str[0] = '\0'; grp_str[0] = '\0'; in_ifname[0] = '\0'; first = 0; } } } if (!uj && !found_oif) { vty_out(vty, "%-15s %-15s %-6s %-16s %-16s %-3d %8s %s\n", src_str, grp_str, proto, in_ifname, "none", 0, "--:--:--", pim->vrf->name); } } if (uj) { vty_out(vty, "%s\n", json_object_to_json_string_ext( json, JSON_C_TO_STRING_PRETTY)); json_object_free(json); } } DEFPY (show_ip_mroute, show_ip_mroute_cmd, "show ip mroute [vrf NAME] [A.B.C.D$s_or_g [A.B.C.D$g]] [fill$fill] [json$json]", SHOW_STR IP_STR MROUTE_STR VRF_CMD_HELP_STR "The Source or Group\n" "The Group\n" "Fill in Assumed data\n" JSON_STR) { struct prefix_sg sg = {0}; struct pim_instance *pim; struct vrf *v; v = vrf_lookup_by_name(vrf ? vrf : VRF_DEFAULT_NAME); if (!v) { vty_out(vty, "%% Vrf specified: %s does not exist\n", vrf); return CMD_WARNING; } pim = pim_get_pim_instance(v->vrf_id); if (!pim) { vty_out(vty, "%% Unable to find pim instance\n"); return CMD_WARNING; } if (s_or_g.s_addr != 0) { if (g.s_addr != 0) { sg.src = s_or_g; sg.grp = g; } else sg.grp = s_or_g; } show_mroute(pim, vty, &sg, !!fill, !!json); return CMD_SUCCESS; } DEFUN (show_ip_mroute_vrf_all, show_ip_mroute_vrf_all_cmd, "show ip mroute vrf all [fill] [json]", SHOW_STR IP_STR MROUTE_STR VRF_CMD_HELP_STR "Fill in Assumed data\n" JSON_STR) { struct prefix_sg sg = {0}; bool uj = use_json(argc, argv); int idx = 4; struct vrf *vrf; bool first = true; bool fill = false; if (argv_find(argv, argc, "fill", &idx)) fill = true; if (uj) vty_out(vty, "{ "); RB_FOREACH (vrf, vrf_name_head, &vrfs_by_name) { if (uj) { if (!first) vty_out(vty, ", "); vty_out(vty, " \"%s\": ", vrf->name); first = false; } else vty_out(vty, "VRF: %s\n", vrf->name); show_mroute(vrf->info, vty, &sg, fill, uj); } if (uj) vty_out(vty, "}\n"); return CMD_SUCCESS; } DEFUN (clear_ip_mroute_count, clear_ip_mroute_count_cmd, "clear ip mroute [vrf NAME] count", CLEAR_STR IP_STR MROUTE_STR VRF_CMD_HELP_STR "Route and packet count data\n") { int idx = 2; struct listnode *node; struct channel_oil *c_oil; struct static_route *sr; struct vrf *vrf = pim_cmd_lookup_vrf(vty, argv, argc, &idx); struct pim_instance *pim; if (!vrf) return CMD_WARNING; pim = vrf->info; for (ALL_LIST_ELEMENTS_RO(pim->channel_oil_list, node, c_oil)) { if (!c_oil->installed) continue; pim_mroute_update_counters(c_oil); c_oil->cc.origpktcnt = c_oil->cc.pktcnt; c_oil->cc.origbytecnt = c_oil->cc.bytecnt; c_oil->cc.origwrong_if = c_oil->cc.wrong_if; } for (ALL_LIST_ELEMENTS_RO(pim->static_routes, node, sr)) { if (!sr->c_oil.installed) continue; pim_mroute_update_counters(&sr->c_oil); sr->c_oil.cc.origpktcnt = sr->c_oil.cc.pktcnt; sr->c_oil.cc.origbytecnt = sr->c_oil.cc.bytecnt; sr->c_oil.cc.origwrong_if = sr->c_oil.cc.wrong_if; } return CMD_SUCCESS; } static void show_mroute_count(struct pim_instance *pim, struct vty *vty) { struct listnode *node; struct channel_oil *c_oil; struct static_route *sr; vty_out(vty, "\n"); vty_out(vty, "Source Group LastUsed Packets Bytes WrongIf \n"); /* Print PIM and IGMP route counts */ for (ALL_LIST_ELEMENTS_RO(pim->channel_oil_list, node, c_oil)) { char group_str[INET_ADDRSTRLEN]; char source_str[INET_ADDRSTRLEN]; if (!c_oil->installed) continue; pim_mroute_update_counters(c_oil); pim_inet4_dump("", c_oil->oil.mfcc_mcastgrp, group_str, sizeof(group_str)); pim_inet4_dump("", c_oil->oil.mfcc_origin, source_str, sizeof(source_str)); vty_out(vty, "%-15s %-15s %-8llu %-7ld %-10ld %-7ld\n", source_str, group_str, c_oil->cc.lastused / 100, c_oil->cc.pktcnt - c_oil->cc.origpktcnt, c_oil->cc.bytecnt - c_oil->cc.origbytecnt, c_oil->cc.wrong_if - c_oil->cc.origwrong_if); } for (ALL_LIST_ELEMENTS_RO(pim->static_routes, node, sr)) { char group_str[INET_ADDRSTRLEN]; char source_str[INET_ADDRSTRLEN]; if (!sr->c_oil.installed) continue; pim_mroute_update_counters(&sr->c_oil); pim_inet4_dump("", sr->c_oil.oil.mfcc_mcastgrp, group_str, sizeof(group_str)); pim_inet4_dump("", sr->c_oil.oil.mfcc_origin, source_str, sizeof(source_str)); vty_out(vty, "%-15s %-15s %-8llu %-7ld %-10ld %-7ld\n", source_str, group_str, sr->c_oil.cc.lastused, sr->c_oil.cc.pktcnt - sr->c_oil.cc.origpktcnt, sr->c_oil.cc.bytecnt - sr->c_oil.cc.origbytecnt, sr->c_oil.cc.wrong_if - sr->c_oil.cc.origwrong_if); } } DEFUN (show_ip_mroute_count, show_ip_mroute_count_cmd, "show ip mroute [vrf NAME] count", SHOW_STR IP_STR MROUTE_STR VRF_CMD_HELP_STR "Route and packet count data\n") { int idx = 2; struct vrf *vrf = pim_cmd_lookup_vrf(vty, argv, argc, &idx); if (!vrf) return CMD_WARNING; show_mroute_count(vrf->info, vty); return CMD_SUCCESS; } DEFUN (show_ip_mroute_count_vrf_all, show_ip_mroute_count_vrf_all_cmd, "show ip mroute vrf all count", SHOW_STR IP_STR MROUTE_STR VRF_CMD_HELP_STR "Route and packet count data\n") { bool uj = use_json(argc, argv); struct vrf *vrf; bool first = true; if (uj) vty_out(vty, "{ "); RB_FOREACH (vrf, vrf_name_head, &vrfs_by_name) { if (uj) { if (!first) vty_out(vty, ", "); vty_out(vty, " \"%s\": ", vrf->name); first = false; } else vty_out(vty, "VRF: %s\n", vrf->name); show_mroute_count(vrf->info, vty); } if (uj) vty_out(vty, "}\n"); return CMD_SUCCESS; } static void show_mroute_summary(struct pim_instance *pim, struct vty *vty) { struct listnode *node; struct channel_oil *c_oil; struct static_route *s_route; uint32_t starg_sw_mroute_cnt = 0; uint32_t sg_sw_mroute_cnt = 0; uint32_t starg_hw_mroute_cnt = 0; uint32_t sg_hw_mroute_cnt = 0; vty_out(vty, "Mroute Type Installed/Total\n"); for (ALL_LIST_ELEMENTS_RO(pim->channel_oil_list, node, c_oil)) { if (!c_oil->installed) { if (c_oil->oil.mfcc_origin.s_addr == INADDR_ANY) starg_sw_mroute_cnt++; else sg_sw_mroute_cnt++; } else { if (c_oil->oil.mfcc_origin.s_addr == INADDR_ANY) starg_hw_mroute_cnt++; else sg_hw_mroute_cnt++; } } for (ALL_LIST_ELEMENTS_RO(pim->static_routes, node, s_route)) { if (!s_route->c_oil.installed) { if (s_route->c_oil.oil.mfcc_origin.s_addr == INADDR_ANY) starg_sw_mroute_cnt++; else sg_sw_mroute_cnt++; } else { if (s_route->c_oil.oil.mfcc_origin.s_addr == INADDR_ANY) starg_hw_mroute_cnt++; else sg_hw_mroute_cnt++; } } vty_out(vty, "%-20s %d/%d\n", "(*, G)", starg_hw_mroute_cnt, starg_sw_mroute_cnt + starg_hw_mroute_cnt); vty_out(vty, "%-20s %d/%d\n", "(S, G)", sg_hw_mroute_cnt, sg_sw_mroute_cnt + sg_hw_mroute_cnt); vty_out(vty, "------\n"); vty_out(vty, "%-20s %d/%d\n", "Total", (starg_hw_mroute_cnt + sg_hw_mroute_cnt), (starg_sw_mroute_cnt + starg_hw_mroute_cnt + sg_sw_mroute_cnt + sg_hw_mroute_cnt)); } DEFUN (show_ip_mroute_summary, show_ip_mroute_summary_cmd, "show ip mroute [vrf NAME] summary", SHOW_STR IP_STR MROUTE_STR VRF_CMD_HELP_STR "Summary of all mroutes\n") { int idx = 2; struct vrf *vrf = pim_cmd_lookup_vrf(vty, argv, argc, &idx); if (!vrf) return CMD_WARNING; show_mroute_summary(vrf->info, vty); return CMD_SUCCESS; } DEFUN (show_ip_mroute_summary_vrf_all, show_ip_mroute_summary_vrf_all_cmd, "show ip mroute vrf all summary", SHOW_STR IP_STR MROUTE_STR VRF_CMD_HELP_STR "Summary of all mroutes\n") { struct vrf *vrf; RB_FOREACH (vrf, vrf_name_head, &vrfs_by_name) { vty_out(vty, "VRF: %s\n", vrf->name); show_mroute_summary(vrf->info, vty); } return CMD_SUCCESS; } DEFUN (show_ip_rib, show_ip_rib_cmd, "show ip rib [vrf NAME] A.B.C.D", SHOW_STR IP_STR RIB_STR VRF_CMD_HELP_STR "Unicast address\n") { int idx = 2; struct vrf *vrf = pim_cmd_lookup_vrf(vty, argv, argc, &idx); struct in_addr addr; const char *addr_str; struct pim_nexthop nexthop; char nexthop_addr_str[PREFIX_STRLEN]; int result; if (!vrf) return CMD_WARNING; memset(&nexthop, 0, sizeof(nexthop)); argv_find(argv, argc, "A.B.C.D", &idx); addr_str = argv[idx]->arg; result = inet_pton(AF_INET, addr_str, &addr); if (result <= 0) { vty_out(vty, "Bad unicast address %s: errno=%d: %s\n", addr_str, errno, safe_strerror(errno)); return CMD_WARNING; } if (!pim_nexthop_lookup(vrf->info, &nexthop, addr, 0)) { vty_out(vty, "Failure querying RIB nexthop for unicast address %s\n", addr_str); return CMD_WARNING; } vty_out(vty, "Address NextHop Interface Metric Preference\n"); pim_addr_dump("", &nexthop.mrib_nexthop_addr, nexthop_addr_str, sizeof(nexthop_addr_str)); vty_out(vty, "%-15s %-15s %-9s %6d %10d\n", addr_str, nexthop_addr_str, nexthop.interface ? nexthop.interface->name : "", nexthop.mrib_route_metric, nexthop.mrib_metric_preference); return CMD_SUCCESS; } static void show_ssmpingd(struct pim_instance *pim, struct vty *vty) { struct listnode *node; struct ssmpingd_sock *ss; time_t now; vty_out(vty, "Source Socket Address Port Uptime Requests\n"); if (!pim->ssmpingd_list) return; now = pim_time_monotonic_sec(); for (ALL_LIST_ELEMENTS_RO(pim->ssmpingd_list, node, ss)) { char source_str[INET_ADDRSTRLEN]; char ss_uptime[10]; struct sockaddr_in bind_addr; socklen_t len = sizeof(bind_addr); char bind_addr_str[INET_ADDRSTRLEN]; pim_inet4_dump("", ss->source_addr, source_str, sizeof(source_str)); if (pim_socket_getsockname( ss->sock_fd, (struct sockaddr *)&bind_addr, &len)) { vty_out(vty, "%% Failure reading socket name for ssmpingd source %s on fd=%d\n", source_str, ss->sock_fd); } pim_inet4_dump("", bind_addr.sin_addr, bind_addr_str, sizeof(bind_addr_str)); pim_time_uptime(ss_uptime, sizeof(ss_uptime), now - ss->creation); vty_out(vty, "%-15s %6d %-15s %5d %8s %8lld\n", source_str, ss->sock_fd, bind_addr_str, ntohs(bind_addr.sin_port), ss_uptime, (long long)ss->requests); } } DEFUN (show_ip_ssmpingd, show_ip_ssmpingd_cmd, "show ip ssmpingd [vrf NAME]", SHOW_STR IP_STR SHOW_SSMPINGD_STR VRF_CMD_HELP_STR) { int idx = 2; struct vrf *vrf = pim_cmd_lookup_vrf(vty, argv, argc, &idx); if (!vrf) return CMD_WARNING; show_ssmpingd(vrf->info, vty); return CMD_SUCCESS; } static int pim_rp_cmd_worker(struct pim_instance *pim, struct vty *vty, const char *rp, const char *group, const char *plist) { int result; result = pim_rp_new_config(pim, rp, group, plist); if (result == PIM_GROUP_BAD_ADDR_MASK_COMBO) { vty_out(vty, "%% Inconsistent address and mask: %s\n", group); return CMD_WARNING_CONFIG_FAILED; } if (result == PIM_GROUP_BAD_ADDRESS) { vty_out(vty, "%% Bad group address specified: %s\n", group); return CMD_WARNING_CONFIG_FAILED; } if (result == PIM_RP_BAD_ADDRESS) { vty_out(vty, "%% Bad RP address specified: %s\n", rp); return CMD_WARNING_CONFIG_FAILED; } if (result == PIM_RP_NO_PATH) { vty_out(vty, "%% No Path to RP address specified: %s\n", rp); return CMD_WARNING; } if (result == PIM_GROUP_OVERLAP) { vty_out(vty, "%% Group range specified cannot exact match another\n"); return CMD_WARNING_CONFIG_FAILED; } if (result == PIM_GROUP_PFXLIST_OVERLAP) { vty_out(vty, "%% This group is already covered by a RP prefix-list\n"); return CMD_WARNING_CONFIG_FAILED; } if (result == PIM_RP_PFXLIST_IN_USE) { vty_out(vty, "%% The same prefix-list cannot be applied to multiple RPs\n"); return CMD_WARNING_CONFIG_FAILED; } if (result == PIM_GROUP_BAD_ADDR_MASK_COMBO) { vty_out(vty, "%% Inconsistent address and mask: %s\n", group); return CMD_WARNING_CONFIG_FAILED; } return CMD_SUCCESS; } static int pim_cmd_spt_switchover(struct pim_instance *pim, enum pim_spt_switchover spt, const char *plist) { pim->spt.switchover = spt; switch (pim->spt.switchover) { case PIM_SPT_IMMEDIATE: XFREE(MTYPE_PIM_SPT_PLIST_NAME, pim->spt.plist); pim_upstream_add_lhr_star_pimreg(pim); break; case PIM_SPT_INFINITY: pim_upstream_remove_lhr_star_pimreg(pim, plist); XFREE(MTYPE_PIM_SPT_PLIST_NAME, pim->spt.plist); if (plist) pim->spt.plist = XSTRDUP(MTYPE_PIM_SPT_PLIST_NAME, plist); break; } return CMD_SUCCESS; } DEFUN (ip_pim_spt_switchover_infinity, ip_pim_spt_switchover_infinity_cmd, "ip pim spt-switchover infinity-and-beyond", IP_STR PIM_STR "SPT-Switchover\n" "Never switch to SPT Tree\n") { PIM_DECLVAR_CONTEXT(vrf, pim); return pim_cmd_spt_switchover(pim, PIM_SPT_INFINITY, NULL); } DEFUN (ip_pim_spt_switchover_infinity_plist, ip_pim_spt_switchover_infinity_plist_cmd, "ip pim spt-switchover infinity-and-beyond prefix-list WORD", IP_STR PIM_STR "SPT-Switchover\n" "Never switch to SPT Tree\n" "Prefix-List to control which groups to switch\n" "Prefix-List name\n") { PIM_DECLVAR_CONTEXT(vrf, pim); return pim_cmd_spt_switchover(pim, PIM_SPT_INFINITY, argv[5]->arg); } DEFUN (no_ip_pim_spt_switchover_infinity, no_ip_pim_spt_switchover_infinity_cmd, "no ip pim spt-switchover infinity-and-beyond", NO_STR IP_STR PIM_STR "SPT_Switchover\n" "Never switch to SPT Tree\n") { PIM_DECLVAR_CONTEXT(vrf, pim); return pim_cmd_spt_switchover(pim, PIM_SPT_IMMEDIATE, NULL); } DEFUN (no_ip_pim_spt_switchover_infinity_plist, no_ip_pim_spt_switchover_infinity_plist_cmd, "no ip pim spt-switchover infinity-and-beyond prefix-list WORD", NO_STR IP_STR PIM_STR "SPT_Switchover\n" "Never switch to SPT Tree\n" "Prefix-List to control which groups to switch\n" "Prefix-List name\n") { PIM_DECLVAR_CONTEXT(vrf, pim); return pim_cmd_spt_switchover(pim, PIM_SPT_IMMEDIATE, NULL); } DEFUN (ip_pim_joinprune_time, ip_pim_joinprune_time_cmd, "ip pim join-prune-interval (60-600)", IP_STR "pim multicast routing\n" "Join Prune Send Interval\n" "Seconds\n") { PIM_DECLVAR_CONTEXT(vrf, pim); router->t_periodic = atoi(argv[3]->arg); return CMD_SUCCESS; } DEFUN (no_ip_pim_joinprune_time, no_ip_pim_joinprune_time_cmd, "no ip pim join-prune-interval (60-600)", NO_STR IP_STR "pim multicast routing\n" "Join Prune Send Interval\n" "Seconds\n") { PIM_DECLVAR_CONTEXT(vrf, pim); router->t_periodic = PIM_DEFAULT_T_PERIODIC; return CMD_SUCCESS; } DEFUN (ip_pim_register_suppress, ip_pim_register_suppress_cmd, "ip pim register-suppress-time (5-60000)", IP_STR "pim multicast routing\n" "Register Suppress Timer\n" "Seconds\n") { PIM_DECLVAR_CONTEXT(vrf, pim); router->register_suppress_time = atoi(argv[3]->arg); return CMD_SUCCESS; } DEFUN (no_ip_pim_register_suppress, no_ip_pim_register_suppress_cmd, "no ip pim register-suppress-time (5-60000)", NO_STR IP_STR "pim multicast routing\n" "Register Suppress Timer\n" "Seconds\n") { PIM_DECLVAR_CONTEXT(vrf, pim); router->register_suppress_time = PIM_REGISTER_SUPPRESSION_TIME_DEFAULT; return CMD_SUCCESS; } DEFUN (ip_pim_rp_keep_alive, ip_pim_rp_keep_alive_cmd, "ip pim rp keep-alive-timer (31-60000)", IP_STR "pim multicast routing\n" "Rendevous Point\n" "Keep alive Timer\n" "Seconds\n") { PIM_DECLVAR_CONTEXT(vrf, pim); pim->rp_keep_alive_time = atoi(argv[4]->arg); return CMD_SUCCESS; } DEFUN (no_ip_pim_rp_keep_alive, no_ip_pim_rp_keep_alive_cmd, "no ip pim rp keep-alive-timer (31-60000)", NO_STR IP_STR "pim multicast routing\n" "Rendevous Point\n" "Keep alive Timer\n" "Seconds\n") { PIM_DECLVAR_CONTEXT(vrf, pim); pim->rp_keep_alive_time = PIM_KEEPALIVE_PERIOD; return CMD_SUCCESS; } DEFUN (ip_pim_keep_alive, ip_pim_keep_alive_cmd, "ip pim keep-alive-timer (31-60000)", IP_STR "pim multicast routing\n" "Keep alive Timer\n" "Seconds\n") { PIM_DECLVAR_CONTEXT(vrf, pim); pim->keep_alive_time = atoi(argv[3]->arg); return CMD_SUCCESS; } DEFUN (no_ip_pim_keep_alive, no_ip_pim_keep_alive_cmd, "no ip pim keep-alive-timer (31-60000)", NO_STR IP_STR "pim multicast routing\n" "Keep alive Timer\n" "Seconds\n") { PIM_DECLVAR_CONTEXT(vrf, pim); pim->keep_alive_time = PIM_KEEPALIVE_PERIOD; return CMD_SUCCESS; } DEFUN (ip_pim_packets, ip_pim_packets_cmd, "ip pim packets (1-100)", IP_STR "pim multicast routing\n" "packets to process at one time per fd\n" "Number of packets\n") { PIM_DECLVAR_CONTEXT(vrf, pim); router->packet_process = atoi(argv[3]->arg); return CMD_SUCCESS; } DEFUN (no_ip_pim_packets, no_ip_pim_packets_cmd, "no ip pim packets (1-100)", NO_STR IP_STR "pim multicast routing\n" "packets to process at one time per fd\n" "Number of packets\n") { PIM_DECLVAR_CONTEXT(vrf, pim); router->packet_process = PIM_DEFAULT_PACKET_PROCESS; return CMD_SUCCESS; } DEFUN (ip_pim_v6_secondary, ip_pim_v6_secondary_cmd, "ip pim send-v6-secondary", IP_STR "pim multicast routing\n" "Send v6 secondary addresses\n") { PIM_DECLVAR_CONTEXT(vrf, pim); pim->send_v6_secondary = 1; return CMD_SUCCESS; } DEFUN (no_ip_pim_v6_secondary, no_ip_pim_v6_secondary_cmd, "no ip pim send-v6-secondary", NO_STR IP_STR "pim multicast routing\n" "Send v6 secondary addresses\n") { PIM_DECLVAR_CONTEXT(vrf, pim); pim->send_v6_secondary = 0; return CMD_SUCCESS; } DEFUN (ip_pim_rp, ip_pim_rp_cmd, "ip pim rp A.B.C.D [A.B.C.D/M]", IP_STR "pim multicast routing\n" "Rendevous Point\n" "ip address of RP\n" "Group Address range to cover\n") { PIM_DECLVAR_CONTEXT(vrf, pim); int idx_ipv4 = 3; if (argc == (idx_ipv4 + 1)) return pim_rp_cmd_worker(pim, vty, argv[idx_ipv4]->arg, NULL, NULL); else return pim_rp_cmd_worker(pim, vty, argv[idx_ipv4]->arg, argv[idx_ipv4 + 1]->arg, NULL); } DEFUN (ip_pim_rp_prefix_list, ip_pim_rp_prefix_list_cmd, "ip pim rp A.B.C.D prefix-list WORD", IP_STR "pim multicast routing\n" "Rendevous Point\n" "ip address of RP\n" "group prefix-list filter\n" "Name of a prefix-list\n") { PIM_DECLVAR_CONTEXT(vrf, pim); return pim_rp_cmd_worker(pim, vty, argv[3]->arg, NULL, argv[5]->arg); } static int pim_no_rp_cmd_worker(struct pim_instance *pim, struct vty *vty, const char *rp, const char *group, const char *plist) { int result = pim_rp_del_config(pim, rp, group, plist); if (result == PIM_GROUP_BAD_ADDRESS) { vty_out(vty, "%% Bad group address specified: %s\n", group); return CMD_WARNING_CONFIG_FAILED; } if (result == PIM_RP_BAD_ADDRESS) { vty_out(vty, "%% Bad RP address specified: %s\n", rp); return CMD_WARNING_CONFIG_FAILED; } if (result == PIM_RP_NOT_FOUND) { vty_out(vty, "%% Unable to find specified RP\n"); return CMD_WARNING_CONFIG_FAILED; } return CMD_SUCCESS; } DEFUN (no_ip_pim_rp, no_ip_pim_rp_cmd, "no ip pim rp A.B.C.D [A.B.C.D/M]", NO_STR IP_STR "pim multicast routing\n" "Rendevous Point\n" "ip address of RP\n" "Group Address range to cover\n") { PIM_DECLVAR_CONTEXT(vrf, pim); int idx_ipv4 = 4, idx_group = 0; if (argv_find(argv, argc, "A.B.C.D/M", &idx_group)) return pim_no_rp_cmd_worker(pim, vty, argv[idx_ipv4]->arg, argv[idx_group]->arg, NULL); else return pim_no_rp_cmd_worker(pim, vty, argv[idx_ipv4]->arg, NULL, NULL); } DEFUN (no_ip_pim_rp_prefix_list, no_ip_pim_rp_prefix_list_cmd, "no ip pim rp A.B.C.D prefix-list WORD", NO_STR IP_STR "pim multicast routing\n" "Rendevous Point\n" "ip address of RP\n" "group prefix-list filter\n" "Name of a prefix-list\n") { PIM_DECLVAR_CONTEXT(vrf, pim); return pim_no_rp_cmd_worker(pim, vty, argv[4]->arg, NULL, argv[6]->arg); } static int pim_ssm_cmd_worker(struct pim_instance *pim, struct vty *vty, const char *plist) { int result = pim_ssm_range_set(pim, pim->vrf_id, plist); if (result == PIM_SSM_ERR_NONE) return CMD_SUCCESS; switch (result) { case PIM_SSM_ERR_NO_VRF: vty_out(vty, "%% VRF doesn't exist\n"); break; case PIM_SSM_ERR_DUP: vty_out(vty, "%% duplicate config\n"); break; default: vty_out(vty, "%% ssm range config failed\n"); } return CMD_WARNING_CONFIG_FAILED; } DEFUN (ip_pim_ssm_prefix_list, ip_pim_ssm_prefix_list_cmd, "ip pim ssm prefix-list WORD", IP_STR "pim multicast routing\n" "Source Specific Multicast\n" "group range prefix-list filter\n" "Name of a prefix-list\n") { PIM_DECLVAR_CONTEXT(vrf, pim); return pim_ssm_cmd_worker(pim, vty, argv[4]->arg); } DEFUN (no_ip_pim_ssm_prefix_list, no_ip_pim_ssm_prefix_list_cmd, "no ip pim ssm prefix-list", NO_STR IP_STR "pim multicast routing\n" "Source Specific Multicast\n" "group range prefix-list filter\n") { PIM_DECLVAR_CONTEXT(vrf, pim); return pim_ssm_cmd_worker(pim, vty, NULL); } DEFUN (no_ip_pim_ssm_prefix_list_name, no_ip_pim_ssm_prefix_list_name_cmd, "no ip pim ssm prefix-list WORD", NO_STR IP_STR "pim multicast routing\n" "Source Specific Multicast\n" "group range prefix-list filter\n" "Name of a prefix-list\n") { PIM_DECLVAR_CONTEXT(vrf, pim); struct pim_ssm *ssm = pim->ssm_info; if (ssm->plist_name && !strcmp(ssm->plist_name, argv[5]->arg)) return pim_ssm_cmd_worker(pim, vty, NULL); vty_out(vty, "%% pim ssm prefix-list %s doesn't exist\n", argv[5]->arg); return CMD_WARNING_CONFIG_FAILED; } static void ip_pim_ssm_show_group_range(struct pim_instance *pim, struct vty *vty, bool uj) { struct pim_ssm *ssm = pim->ssm_info; const char *range_str = ssm->plist_name ? ssm->plist_name : PIM_SSM_STANDARD_RANGE; if (uj) { json_object *json; json = json_object_new_object(); json_object_string_add(json, "ssmGroups", range_str); vty_out(vty, "%s\n", json_object_to_json_string_ext( json, JSON_C_TO_STRING_PRETTY)); json_object_free(json); } else vty_out(vty, "SSM group range : %s\n", range_str); } DEFUN (show_ip_pim_ssm_range, show_ip_pim_ssm_range_cmd, "show ip pim [vrf NAME] group-type [json]", SHOW_STR IP_STR PIM_STR VRF_CMD_HELP_STR "PIM group type\n" JSON_STR) { int idx = 2; struct vrf *vrf = pim_cmd_lookup_vrf(vty, argv, argc, &idx); bool uj = use_json(argc, argv); if (!vrf) return CMD_WARNING; ip_pim_ssm_show_group_range(vrf->info, vty, uj); return CMD_SUCCESS; } static void ip_pim_ssm_show_group_type(struct pim_instance *pim, struct vty *vty, bool uj, const char *group) { struct in_addr group_addr; const char *type_str; int result; result = inet_pton(AF_INET, group, &group_addr); if (result <= 0) type_str = "invalid"; else { if (pim_is_group_224_4(group_addr)) type_str = pim_is_grp_ssm(pim, group_addr) ? "SSM" : "ASM"; else type_str = "not-multicast"; } if (uj) { json_object *json; json = json_object_new_object(); json_object_string_add(json, "groupType", type_str); vty_out(vty, "%s\n", json_object_to_json_string_ext( json, JSON_C_TO_STRING_PRETTY)); json_object_free(json); } else vty_out(vty, "Group type : %s\n", type_str); } DEFUN (show_ip_pim_group_type, show_ip_pim_group_type_cmd, "show ip pim [vrf NAME] group-type A.B.C.D [json]", SHOW_STR IP_STR PIM_STR VRF_CMD_HELP_STR "multicast group type\n" "group address\n" JSON_STR) { int idx = 2; struct vrf *vrf = pim_cmd_lookup_vrf(vty, argv, argc, &idx); bool uj = use_json(argc, argv); if (!vrf) return CMD_WARNING; argv_find(argv, argc, "A.B.C.D", &idx); ip_pim_ssm_show_group_type(vrf->info, vty, uj, argv[idx]->arg); return CMD_SUCCESS; } DEFUN (show_ip_pim_bsr, show_ip_pim_bsr_cmd, "show ip pim bsr [json]", SHOW_STR IP_STR PIM_STR "boot-strap router information\n" JSON_STR) { int idx = 2; struct vrf *vrf = pim_cmd_lookup_vrf(vty, argv, argc, &idx); bool uj = use_json(argc, argv); if (!vrf) return CMD_WARNING; pim_show_bsr(vrf->info, vty, uj); return CMD_SUCCESS; } DEFUN (ip_ssmpingd, ip_ssmpingd_cmd, "ip ssmpingd [A.B.C.D]", IP_STR CONF_SSMPINGD_STR "Source address\n") { PIM_DECLVAR_CONTEXT(vrf, pim); int idx_ipv4 = 2; int result; struct in_addr source_addr; const char *source_str = (argc == 3) ? argv[idx_ipv4]->arg : "0.0.0.0"; result = inet_pton(AF_INET, source_str, &source_addr); if (result <= 0) { vty_out(vty, "%% Bad source address %s: errno=%d: %s\n", source_str, errno, safe_strerror(errno)); return CMD_WARNING_CONFIG_FAILED; } result = pim_ssmpingd_start(pim, source_addr); if (result) { vty_out(vty, "%% Failure starting ssmpingd for source %s: %d\n", source_str, result); return CMD_WARNING_CONFIG_FAILED; } return CMD_SUCCESS; } DEFUN (no_ip_ssmpingd, no_ip_ssmpingd_cmd, "no ip ssmpingd [A.B.C.D]", NO_STR IP_STR CONF_SSMPINGD_STR "Source address\n") { PIM_DECLVAR_CONTEXT(vrf, pim); int idx_ipv4 = 3; int result; struct in_addr source_addr; const char *source_str = (argc == 4) ? argv[idx_ipv4]->arg : "0.0.0.0"; result = inet_pton(AF_INET, source_str, &source_addr); if (result <= 0) { vty_out(vty, "%% Bad source address %s: errno=%d: %s\n", source_str, errno, safe_strerror(errno)); return CMD_WARNING_CONFIG_FAILED; } result = pim_ssmpingd_stop(pim, source_addr); if (result) { vty_out(vty, "%% Failure stopping ssmpingd for source %s: %d\n", source_str, result); return CMD_WARNING_CONFIG_FAILED; } return CMD_SUCCESS; } DEFUN (ip_pim_ecmp, ip_pim_ecmp_cmd, "ip pim ecmp", IP_STR "pim multicast routing\n" "Enable PIM ECMP \n") { PIM_DECLVAR_CONTEXT(vrf, pim); pim->ecmp_enable = true; return CMD_SUCCESS; } DEFUN (no_ip_pim_ecmp, no_ip_pim_ecmp_cmd, "no ip pim ecmp", NO_STR IP_STR "pim multicast routing\n" "Disable PIM ECMP \n") { PIM_DECLVAR_CONTEXT(vrf, pim); pim->ecmp_enable = false; return CMD_SUCCESS; } DEFUN (ip_pim_ecmp_rebalance, ip_pim_ecmp_rebalance_cmd, "ip pim ecmp rebalance", IP_STR "pim multicast routing\n" "Enable PIM ECMP \n" "Enable PIM ECMP Rebalance\n") { PIM_DECLVAR_CONTEXT(vrf, pim); pim->ecmp_enable = true; pim->ecmp_rebalance_enable = true; return CMD_SUCCESS; } DEFUN (no_ip_pim_ecmp_rebalance, no_ip_pim_ecmp_rebalance_cmd, "no ip pim ecmp rebalance", NO_STR IP_STR "pim multicast routing\n" "Disable PIM ECMP \n" "Disable PIM ECMP Rebalance\n") { PIM_DECLVAR_CONTEXT(vrf, pim); pim->ecmp_rebalance_enable = false; return CMD_SUCCESS; } static int pim_cmd_igmp_start(struct vty *vty, struct interface *ifp) { struct pim_interface *pim_ifp; uint8_t need_startup = 0; pim_ifp = ifp->info; if (!pim_ifp) { (void)pim_if_new(ifp, true, false, false, false); need_startup = 1; } else { if (!PIM_IF_TEST_IGMP(pim_ifp->options)) { PIM_IF_DO_IGMP(pim_ifp->options); need_startup = 1; } } /* 'ip igmp' executed multiple times, with need_startup avoid multiple if add all and membership refresh */ if (need_startup) { pim_if_addr_add_all(ifp); pim_if_membership_refresh(ifp); } return CMD_SUCCESS; } DEFUN (interface_ip_igmp, interface_ip_igmp_cmd, "ip igmp", IP_STR IFACE_IGMP_STR) { VTY_DECLVAR_CONTEXT(interface, ifp); return pim_cmd_igmp_start(vty, ifp); } DEFUN (interface_no_ip_igmp, interface_no_ip_igmp_cmd, "no ip igmp", NO_STR IP_STR IFACE_IGMP_STR) { VTY_DECLVAR_CONTEXT(interface, ifp); struct pim_interface *pim_ifp = ifp->info; if (!pim_ifp) return CMD_SUCCESS; PIM_IF_DONT_IGMP(pim_ifp->options); pim_if_membership_clear(ifp); pim_if_addr_del_all_igmp(ifp); if (!PIM_IF_TEST_PIM(pim_ifp->options)) { pim_if_delete(ifp); } return CMD_SUCCESS; } DEFUN (interface_ip_igmp_join, interface_ip_igmp_join_cmd, "ip igmp join A.B.C.D A.B.C.D", IP_STR IFACE_IGMP_STR "IGMP join multicast group\n" "Multicast group address\n" "Source address\n") { VTY_DECLVAR_CONTEXT(interface, ifp); int idx_ipv4 = 3; int idx_ipv4_2 = 4; const char *group_str; const char *source_str; struct in_addr group_addr; struct in_addr source_addr; int result; /* Group address */ group_str = argv[idx_ipv4]->arg; result = inet_pton(AF_INET, group_str, &group_addr); if (result <= 0) { vty_out(vty, "Bad group address %s: errno=%d: %s\n", group_str, errno, safe_strerror(errno)); return CMD_WARNING_CONFIG_FAILED; } /* Source address */ source_str = argv[idx_ipv4_2]->arg; result = inet_pton(AF_INET, source_str, &source_addr); if (result <= 0) { vty_out(vty, "Bad source address %s: errno=%d: %s\n", source_str, errno, safe_strerror(errno)); return CMD_WARNING_CONFIG_FAILED; } CMD_FERR_RETURN(pim_if_igmp_join_add(ifp, group_addr, source_addr), "Failure joining IGMP group: $ERR"); return CMD_SUCCESS; } DEFUN (interface_no_ip_igmp_join, interface_no_ip_igmp_join_cmd, "no ip igmp join A.B.C.D A.B.C.D", NO_STR IP_STR IFACE_IGMP_STR "IGMP join multicast group\n" "Multicast group address\n" "Source address\n") { VTY_DECLVAR_CONTEXT(interface, ifp); int idx_ipv4 = 4; int idx_ipv4_2 = 5; const char *group_str; const char *source_str; struct in_addr group_addr; struct in_addr source_addr; int result; /* Group address */ group_str = argv[idx_ipv4]->arg; result = inet_pton(AF_INET, group_str, &group_addr); if (result <= 0) { vty_out(vty, "Bad group address %s: errno=%d: %s\n", group_str, errno, safe_strerror(errno)); return CMD_WARNING_CONFIG_FAILED; } /* Source address */ source_str = argv[idx_ipv4_2]->arg; result = inet_pton(AF_INET, source_str, &source_addr); if (result <= 0) { vty_out(vty, "Bad source address %s: errno=%d: %s\n", source_str, errno, safe_strerror(errno)); return CMD_WARNING_CONFIG_FAILED; } result = pim_if_igmp_join_del(ifp, group_addr, source_addr); if (result) { vty_out(vty, "%% Failure leaving IGMP group %s source %s on interface %s: %d\n", group_str, source_str, ifp->name, result); return CMD_WARNING_CONFIG_FAILED; } return CMD_SUCCESS; } /* CLI reconfiguration affects the interface level (struct pim_interface). This function propagates the reconfiguration to every active socket for that interface. */ static void igmp_sock_query_interval_reconfig(struct igmp_sock *igmp) { struct interface *ifp; struct pim_interface *pim_ifp; zassert(igmp); /* other querier present? */ if (igmp->t_other_querier_timer) return; /* this is the querier */ zassert(igmp->interface); zassert(igmp->interface->info); ifp = igmp->interface; pim_ifp = ifp->info; if (PIM_DEBUG_IGMP_TRACE) { char ifaddr_str[INET_ADDRSTRLEN]; pim_inet4_dump("", igmp->ifaddr, ifaddr_str, sizeof(ifaddr_str)); zlog_debug("%s: Querier %s on %s reconfig query_interval=%d", __PRETTY_FUNCTION__, ifaddr_str, ifp->name, pim_ifp->igmp_default_query_interval); } /* igmp_startup_mode_on() will reset QQI: igmp->querier_query_interval = pim_ifp->igmp_default_query_interval; */ igmp_startup_mode_on(igmp); } static void igmp_sock_query_reschedule(struct igmp_sock *igmp) { if (igmp->t_igmp_query_timer) { /* other querier present */ zassert(igmp->t_igmp_query_timer); zassert(!igmp->t_other_querier_timer); pim_igmp_general_query_off(igmp); pim_igmp_general_query_on(igmp); zassert(igmp->t_igmp_query_timer); zassert(!igmp->t_other_querier_timer); } else { /* this is the querier */ zassert(!igmp->t_igmp_query_timer); zassert(igmp->t_other_querier_timer); pim_igmp_other_querier_timer_off(igmp); pim_igmp_other_querier_timer_on(igmp); zassert(!igmp->t_igmp_query_timer); zassert(igmp->t_other_querier_timer); } } static void change_query_interval(struct pim_interface *pim_ifp, int query_interval) { struct listnode *sock_node; struct igmp_sock *igmp; pim_ifp->igmp_default_query_interval = query_interval; for (ALL_LIST_ELEMENTS_RO(pim_ifp->igmp_socket_list, sock_node, igmp)) { igmp_sock_query_interval_reconfig(igmp); igmp_sock_query_reschedule(igmp); } } static void change_query_max_response_time(struct pim_interface *pim_ifp, int query_max_response_time_dsec) { struct listnode *sock_node; struct igmp_sock *igmp; pim_ifp->igmp_query_max_response_time_dsec = query_max_response_time_dsec; /* Below we modify socket/group/source timers in order to quickly reflect the change. Otherwise, those timers would eventually catch up. */ /* scan all sockets */ for (ALL_LIST_ELEMENTS_RO(pim_ifp->igmp_socket_list, sock_node, igmp)) { struct listnode *grp_node; struct igmp_group *grp; /* reschedule socket general query */ igmp_sock_query_reschedule(igmp); /* scan socket groups */ for (ALL_LIST_ELEMENTS_RO(igmp->igmp_group_list, grp_node, grp)) { struct listnode *src_node; struct igmp_source *src; /* reset group timers for groups in EXCLUDE mode */ if (grp->group_filtermode_isexcl) { igmp_group_reset_gmi(grp); } /* scan group sources */ for (ALL_LIST_ELEMENTS_RO(grp->group_source_list, src_node, src)) { /* reset source timers for sources with running * timers */ if (src->t_source_timer) { igmp_source_reset_gmi(igmp, grp, src); } } } } } #define IGMP_QUERY_INTERVAL_MIN (1) #define IGMP_QUERY_INTERVAL_MAX (1800) DEFUN (interface_ip_igmp_query_interval, interface_ip_igmp_query_interval_cmd, "ip igmp query-interval (1-1800)", IP_STR IFACE_IGMP_STR IFACE_IGMP_QUERY_INTERVAL_STR "Query interval in seconds\n") { VTY_DECLVAR_CONTEXT(interface, ifp); struct pim_interface *pim_ifp = ifp->info; int query_interval; int query_interval_dsec; int ret; if (!pim_ifp) { ret = pim_cmd_igmp_start(vty, ifp); if (ret != CMD_SUCCESS) return ret; pim_ifp = ifp->info; } query_interval = atoi(argv[3]->arg); query_interval_dsec = 10 * query_interval; /* It seems we don't need to check bounds since command.c does it already, but we verify them anyway for extra safety. */ if (query_interval < IGMP_QUERY_INTERVAL_MIN) { vty_out(vty, "General query interval %d lower than minimum %d\n", query_interval, IGMP_QUERY_INTERVAL_MIN); return CMD_WARNING_CONFIG_FAILED; } if (query_interval > IGMP_QUERY_INTERVAL_MAX) { vty_out(vty, "General query interval %d higher than maximum %d\n", query_interval, IGMP_QUERY_INTERVAL_MAX); return CMD_WARNING_CONFIG_FAILED; } if (query_interval_dsec <= pim_ifp->igmp_query_max_response_time_dsec) { vty_out(vty, "Can't set general query interval %d dsec <= query max response time %d dsec.\n", query_interval_dsec, pim_ifp->igmp_query_max_response_time_dsec); return CMD_WARNING_CONFIG_FAILED; } change_query_interval(pim_ifp, query_interval); return CMD_SUCCESS; } DEFUN (interface_no_ip_igmp_query_interval, interface_no_ip_igmp_query_interval_cmd, "no ip igmp query-interval", NO_STR IP_STR IFACE_IGMP_STR IFACE_IGMP_QUERY_INTERVAL_STR) { VTY_DECLVAR_CONTEXT(interface, ifp); struct pim_interface *pim_ifp = ifp->info; int default_query_interval_dsec; if (!pim_ifp) return CMD_SUCCESS; default_query_interval_dsec = IGMP_GENERAL_QUERY_INTERVAL * 10; if (default_query_interval_dsec <= pim_ifp->igmp_query_max_response_time_dsec) { vty_out(vty, "Can't set default general query interval %d dsec <= query max response time %d dsec.\n", default_query_interval_dsec, pim_ifp->igmp_query_max_response_time_dsec); return CMD_WARNING_CONFIG_FAILED; } change_query_interval(pim_ifp, IGMP_GENERAL_QUERY_INTERVAL); return CMD_SUCCESS; } DEFUN (interface_ip_igmp_version, interface_ip_igmp_version_cmd, "ip igmp version (2-3)", IP_STR IFACE_IGMP_STR "IGMP version\n" "IGMP version number\n") { VTY_DECLVAR_CONTEXT(interface, ifp); struct pim_interface *pim_ifp = ifp->info; int igmp_version, old_version = 0; int ret; if (!pim_ifp) { ret = pim_cmd_igmp_start(vty, ifp); if (ret != CMD_SUCCESS) return ret; pim_ifp = ifp->info; } igmp_version = atoi(argv[3]->arg); old_version = pim_ifp->igmp_version; pim_ifp->igmp_version = igmp_version; // Check if IGMP is Enabled otherwise, enable on interface if (!PIM_IF_TEST_IGMP(pim_ifp->options)) { PIM_IF_DO_IGMP(pim_ifp->options); pim_if_addr_add_all(ifp); pim_if_membership_refresh(ifp); old_version = igmp_version; // avoid refreshing membership again. } /* Current and new version is different refresh existing membership. Going from 3 -> 2 or 2 -> 3. */ if (old_version != igmp_version) pim_if_membership_refresh(ifp); return CMD_SUCCESS; } DEFUN (interface_no_ip_igmp_version, interface_no_ip_igmp_version_cmd, "no ip igmp version (2-3)", NO_STR IP_STR IFACE_IGMP_STR "IGMP version\n" "IGMP version number\n") { VTY_DECLVAR_CONTEXT(interface, ifp); struct pim_interface *pim_ifp = ifp->info; if (!pim_ifp) return CMD_SUCCESS; pim_ifp->igmp_version = IGMP_DEFAULT_VERSION; return CMD_SUCCESS; } #define IGMP_QUERY_MAX_RESPONSE_TIME_MIN_DSEC (10) #define IGMP_QUERY_MAX_RESPONSE_TIME_MAX_DSEC (250) DEFUN (interface_ip_igmp_query_max_response_time, interface_ip_igmp_query_max_response_time_cmd, "ip igmp query-max-response-time (10-250)", IP_STR IFACE_IGMP_STR IFACE_IGMP_QUERY_MAX_RESPONSE_TIME_STR "Query response value in deci-seconds\n") { VTY_DECLVAR_CONTEXT(interface, ifp); struct pim_interface *pim_ifp = ifp->info; int query_max_response_time; int ret; if (!pim_ifp) { ret = pim_cmd_igmp_start(vty, ifp); if (ret != CMD_SUCCESS) return ret; pim_ifp = ifp->info; } query_max_response_time = atoi(argv[3]->arg); if (query_max_response_time >= pim_ifp->igmp_default_query_interval * 10) { vty_out(vty, "Can't set query max response time %d sec >= general query interval %d sec\n", query_max_response_time, pim_ifp->igmp_default_query_interval); return CMD_WARNING_CONFIG_FAILED; } change_query_max_response_time(pim_ifp, query_max_response_time); return CMD_SUCCESS; } DEFUN (interface_no_ip_igmp_query_max_response_time, interface_no_ip_igmp_query_max_response_time_cmd, "no ip igmp query-max-response-time (10-250)", NO_STR IP_STR IFACE_IGMP_STR IFACE_IGMP_QUERY_MAX_RESPONSE_TIME_STR "Time for response in deci-seconds\n") { VTY_DECLVAR_CONTEXT(interface, ifp); struct pim_interface *pim_ifp = ifp->info; if (!pim_ifp) return CMD_SUCCESS; change_query_max_response_time(pim_ifp, IGMP_QUERY_MAX_RESPONSE_TIME_DSEC); return CMD_SUCCESS; } #define IGMP_QUERY_MAX_RESPONSE_TIME_MIN_DSEC (10) #define IGMP_QUERY_MAX_RESPONSE_TIME_MAX_DSEC (250) DEFUN_HIDDEN (interface_ip_igmp_query_max_response_time_dsec, interface_ip_igmp_query_max_response_time_dsec_cmd, "ip igmp query-max-response-time-dsec (10-250)", IP_STR IFACE_IGMP_STR IFACE_IGMP_QUERY_MAX_RESPONSE_TIME_DSEC_STR "Query response value in deciseconds\n") { VTY_DECLVAR_CONTEXT(interface, ifp); struct pim_interface *pim_ifp = ifp->info; int query_max_response_time_dsec; int default_query_interval_dsec; int ret; if (!pim_ifp) { ret = pim_cmd_igmp_start(vty, ifp); if (ret != CMD_SUCCESS) return ret; pim_ifp = ifp->info; } query_max_response_time_dsec = atoi(argv[4]->arg); default_query_interval_dsec = 10 * pim_ifp->igmp_default_query_interval; if (query_max_response_time_dsec >= default_query_interval_dsec) { vty_out(vty, "Can't set query max response time %d dsec >= general query interval %d dsec\n", query_max_response_time_dsec, default_query_interval_dsec); return CMD_WARNING_CONFIG_FAILED; } change_query_max_response_time(pim_ifp, query_max_response_time_dsec); return CMD_SUCCESS; } DEFUN_HIDDEN (interface_no_ip_igmp_query_max_response_time_dsec, interface_no_ip_igmp_query_max_response_time_dsec_cmd, "no ip igmp query-max-response-time-dsec", NO_STR IP_STR IFACE_IGMP_STR IFACE_IGMP_QUERY_MAX_RESPONSE_TIME_DSEC_STR) { VTY_DECLVAR_CONTEXT(interface, ifp); struct pim_interface *pim_ifp = ifp->info; if (!pim_ifp) return CMD_SUCCESS; change_query_max_response_time(pim_ifp, IGMP_QUERY_MAX_RESPONSE_TIME_DSEC); return CMD_SUCCESS; } #define IGMP_LAST_MEMBER_QUERY_COUNT_MIN (1) #define IGMP_LAST_MEMBER_QUERY_COUNT_MAX (7) DEFUN (interface_ip_igmp_last_member_query_count, interface_ip_igmp_last_member_query_count_cmd, "ip igmp last-member-query-count (1-7)", IP_STR IFACE_IGMP_STR IFACE_IGMP_LAST_MEMBER_QUERY_COUNT_STR "Last member query count\n") { VTY_DECLVAR_CONTEXT(interface, ifp); struct pim_interface *pim_ifp = ifp->info; int last_member_query_count; int ret; if (!pim_ifp) { ret = pim_cmd_igmp_start(vty, ifp); if (ret != CMD_SUCCESS) return ret; pim_ifp = ifp->info; } last_member_query_count = atoi(argv[3]->arg); pim_ifp->igmp_last_member_query_count = last_member_query_count; return CMD_SUCCESS; } DEFUN (interface_no_ip_igmp_last_member_query_count, interface_no_ip_igmp_last_member_query_count_cmd, "no ip igmp last-member-query-count", NO_STR IP_STR IFACE_IGMP_STR IFACE_IGMP_LAST_MEMBER_QUERY_COUNT_STR) { VTY_DECLVAR_CONTEXT(interface, ifp); struct pim_interface *pim_ifp = ifp->info; if (!pim_ifp) return CMD_SUCCESS; pim_ifp->igmp_last_member_query_count = IGMP_DEFAULT_ROBUSTNESS_VARIABLE; return CMD_SUCCESS; } #define IGMP_LAST_MEMBER_QUERY_INTERVAL_MIN (1) #define IGMP_LAST_MEMBER_QUERY_INTERVAL_MAX (255) DEFUN (interface_ip_igmp_last_member_query_interval, interface_ip_igmp_last_member_query_interval_cmd, "ip igmp last-member-query-interval (1-255)", IP_STR IFACE_IGMP_STR IFACE_IGMP_LAST_MEMBER_QUERY_INTERVAL_STR "Last member query interval in deciseconds\n") { VTY_DECLVAR_CONTEXT(interface, ifp); struct pim_interface *pim_ifp = ifp->info; int last_member_query_interval; int ret; if (!pim_ifp) { ret = pim_cmd_igmp_start(vty, ifp); if (ret != CMD_SUCCESS) return ret; pim_ifp = ifp->info; } last_member_query_interval = atoi(argv[3]->arg); pim_ifp->igmp_specific_query_max_response_time_dsec = last_member_query_interval; return CMD_SUCCESS; } DEFUN (interface_no_ip_igmp_last_member_query_interval, interface_no_ip_igmp_last_member_query_interval_cmd, "no ip igmp last-member-query-interval", NO_STR IP_STR IFACE_IGMP_STR IFACE_IGMP_LAST_MEMBER_QUERY_INTERVAL_STR) { VTY_DECLVAR_CONTEXT(interface, ifp); struct pim_interface *pim_ifp = ifp->info; if (!pim_ifp) return CMD_SUCCESS; pim_ifp->igmp_specific_query_max_response_time_dsec = IGMP_SPECIFIC_QUERY_MAX_RESPONSE_TIME_DSEC; return CMD_SUCCESS; } DEFUN (interface_ip_pim_drprio, interface_ip_pim_drprio_cmd, "ip pim drpriority (1-4294967295)", IP_STR PIM_STR "Set the Designated Router Election Priority\n" "Value of the new DR Priority\n") { VTY_DECLVAR_CONTEXT(interface, ifp); int idx_number = 3; struct pim_interface *pim_ifp = ifp->info; uint32_t old_dr_prio; if (!pim_ifp) { vty_out(vty, "Please enable PIM on interface, first\n"); return CMD_WARNING_CONFIG_FAILED; } old_dr_prio = pim_ifp->pim_dr_priority; pim_ifp->pim_dr_priority = strtol(argv[idx_number]->arg, NULL, 10); if (old_dr_prio != pim_ifp->pim_dr_priority) { pim_if_dr_election(ifp); pim_hello_restart_now(ifp); } return CMD_SUCCESS; } DEFUN (interface_no_ip_pim_drprio, interface_no_ip_pim_drprio_cmd, "no ip pim drpriority [(1-4294967295)]", NO_STR IP_STR PIM_STR "Revert the Designated Router Priority to default\n" "Old Value of the Priority\n") { VTY_DECLVAR_CONTEXT(interface, ifp); struct pim_interface *pim_ifp = ifp->info; if (!pim_ifp) { vty_out(vty, "Pim not enabled on this interface\n"); return CMD_WARNING_CONFIG_FAILED; } if (pim_ifp->pim_dr_priority != PIM_DEFAULT_DR_PRIORITY) { pim_ifp->pim_dr_priority = PIM_DEFAULT_DR_PRIORITY; pim_if_dr_election(ifp); pim_hello_restart_now(ifp); } return CMD_SUCCESS; } DEFPY_HIDDEN (interface_ip_igmp_query_generate, interface_ip_igmp_query_generate_cmd, "ip igmp generate-query-once [version (2-3)]", IP_STR IFACE_IGMP_STR "Generate igmp general query once\n" "IGMP version\n" "IGMP version number\n") { VTY_DECLVAR_CONTEXT(interface, ifp); int igmp_version = 2; if (!ifp->info) { vty_out(vty, "IGMP/PIM is not enabled on the interface %s\n", ifp->name); return CMD_WARNING_CONFIG_FAILED; } if (argc > 3) igmp_version = atoi(argv[4]->arg); igmp_send_query_on_intf(ifp, igmp_version); return CMD_SUCCESS; } static int pim_cmd_interface_add(struct interface *ifp) { struct pim_interface *pim_ifp = ifp->info; if (!pim_ifp) pim_ifp = pim_if_new(ifp, false, true, false, false); else PIM_IF_DO_PIM(pim_ifp->options); pim_if_addr_add_all(ifp); pim_if_membership_refresh(ifp); pim_if_create_pimreg(pim_ifp->pim); return 1; } DEFPY_HIDDEN (pim_test_sg_keepalive, pim_test_sg_keepalive_cmd, "test pim [vrf NAME$name] keepalive-reset A.B.C.D$source A.B.C.D$group", "Test code\n" PIM_STR VRF_CMD_HELP_STR "Reset the Keepalive Timer\n" "The Source we are resetting\n" "The Group we are resetting\n") { struct pim_upstream *up; struct pim_instance *pim; struct prefix_sg sg; sg.src = source; sg.grp = group; if (!name) pim = pim_get_pim_instance(VRF_DEFAULT); else { struct vrf *vrf = vrf_lookup_by_name(name); if (!vrf) { vty_out(vty, "%% Vrf specified: %s does not exist\n", name); return CMD_WARNING; } pim = pim_get_pim_instance(vrf->vrf_id); } if (!pim) { vty_out(vty, "%% Unable to find pim instance\n"); return CMD_WARNING; } up = pim_upstream_find(pim, &sg); if (!up) { vty_out(vty, "%% Unable to find %s specified\n", pim_str_sg_dump(&sg)); return CMD_WARNING; } vty_out(vty, "Setting %s to current keep alive time: %d\n", pim_str_sg_dump(&sg), pim->keep_alive_time); pim_upstream_keep_alive_timer_start(up, pim->keep_alive_time); return CMD_SUCCESS; } DEFPY_HIDDEN (interface_ip_pim_activeactive, interface_ip_pim_activeactive_cmd, "[no$no] ip pim active-active", NO_STR IP_STR PIM_STR "Mark interface as Active-Active for MLAG operations, Hidden because not finished yet\n") { VTY_DECLVAR_CONTEXT(interface, ifp); struct pim_interface *pim_ifp; if (!no && !pim_cmd_interface_add(ifp)) { vty_out(vty, "Could not enable PIM SM active-active on interface\n"); return CMD_WARNING_CONFIG_FAILED; } pim_ifp = ifp->info; if (no) pim_ifp->activeactive = false; else pim_ifp->activeactive = true; return CMD_SUCCESS; } DEFUN_HIDDEN (interface_ip_pim_ssm, interface_ip_pim_ssm_cmd, "ip pim ssm", IP_STR PIM_STR IFACE_PIM_STR) { VTY_DECLVAR_CONTEXT(interface, ifp); if (!pim_cmd_interface_add(ifp)) { vty_out(vty, "Could not enable PIM SM on interface\n"); return CMD_WARNING_CONFIG_FAILED; } vty_out(vty, "WARN: Enabled PIM SM on interface; configure PIM SSM " "range if needed\n"); return CMD_SUCCESS; } static int interface_ip_pim_helper(struct vty *vty) { struct pim_interface *pim_ifp; VTY_DECLVAR_CONTEXT(interface, ifp); if (!pim_cmd_interface_add(ifp)) { vty_out(vty, "Could not enable PIM SM on interface\n"); return CMD_WARNING_CONFIG_FAILED; } pim_ifp = ifp->info; pim_if_create_pimreg(pim_ifp->pim); return CMD_SUCCESS; } DEFUN_HIDDEN (interface_ip_pim_sm, interface_ip_pim_sm_cmd, "ip pim sm", IP_STR PIM_STR IFACE_PIM_SM_STR) { return interface_ip_pim_helper(vty); } DEFUN (interface_ip_pim, interface_ip_pim_cmd, "ip pim", IP_STR PIM_STR) { return interface_ip_pim_helper(vty); } static int pim_cmd_interface_delete(struct interface *ifp) { struct pim_interface *pim_ifp = ifp->info; if (!pim_ifp) return 1; PIM_IF_DONT_PIM(pim_ifp->options); pim_if_membership_clear(ifp); /* pim_sock_delete() removes all neighbors from pim_ifp->pim_neighbor_list. */ pim_sock_delete(ifp, "pim unconfigured on interface"); if (!PIM_IF_TEST_IGMP(pim_ifp->options)) { pim_if_addr_del_all(ifp); pim_if_delete(ifp); } return 1; } static int interface_no_ip_pim_helper(struct vty *vty) { VTY_DECLVAR_CONTEXT(interface, ifp); if (!pim_cmd_interface_delete(ifp)) { vty_out(vty, "Unable to delete interface information\n"); return CMD_WARNING_CONFIG_FAILED; } return CMD_SUCCESS; } DEFUN_HIDDEN (interface_no_ip_pim_ssm, interface_no_ip_pim_ssm_cmd, "no ip pim ssm", NO_STR IP_STR PIM_STR IFACE_PIM_STR) { return interface_no_ip_pim_helper(vty); } DEFUN_HIDDEN (interface_no_ip_pim_sm, interface_no_ip_pim_sm_cmd, "no ip pim sm", NO_STR IP_STR PIM_STR IFACE_PIM_SM_STR) { return interface_no_ip_pim_helper(vty); } DEFUN (interface_no_ip_pim, interface_no_ip_pim_cmd, "no ip pim", NO_STR IP_STR PIM_STR) { return interface_no_ip_pim_helper(vty); } /* boundaries */ DEFUN(interface_ip_pim_boundary_oil, interface_ip_pim_boundary_oil_cmd, "ip multicast boundary oil WORD", IP_STR "Generic multicast configuration options\n" "Define multicast boundary\n" "Filter OIL by group using prefix list\n" "Prefix list to filter OIL with\n") { VTY_DECLVAR_CONTEXT(interface, iif); struct pim_interface *pim_ifp; int idx = 0; argv_find(argv, argc, "WORD", &idx); PIM_GET_PIM_INTERFACE(pim_ifp, iif); if (pim_ifp->boundary_oil_plist) XFREE(MTYPE_PIM_INTERFACE, pim_ifp->boundary_oil_plist); pim_ifp->boundary_oil_plist = XSTRDUP(MTYPE_PIM_INTERFACE, argv[idx]->arg); /* Interface will be pruned from OIL on next Join */ return CMD_SUCCESS; } DEFUN(interface_no_ip_pim_boundary_oil, interface_no_ip_pim_boundary_oil_cmd, "no ip multicast boundary oil [WORD]", NO_STR IP_STR "Generic multicast configuration options\n" "Define multicast boundary\n" "Filter OIL by group using prefix list\n" "Prefix list to filter OIL with\n") { VTY_DECLVAR_CONTEXT(interface, iif); struct pim_interface *pim_ifp; int idx = 0; argv_find(argv, argc, "WORD", &idx); PIM_GET_PIM_INTERFACE(pim_ifp, iif); if (pim_ifp->boundary_oil_plist) XFREE(MTYPE_PIM_INTERFACE, pim_ifp->boundary_oil_plist); return CMD_SUCCESS; } DEFUN (interface_ip_mroute, interface_ip_mroute_cmd, "ip mroute INTERFACE A.B.C.D", IP_STR "Add multicast route\n" "Outgoing interface name\n" "Group address\n") { VTY_DECLVAR_CONTEXT(interface, iif); struct pim_interface *pim_ifp; struct pim_instance *pim; int idx_interface = 2; int idx_ipv4 = 3; struct interface *oif; const char *oifname; const char *grp_str; struct in_addr grp_addr; struct in_addr src_addr; int result; PIM_GET_PIM_INTERFACE(pim_ifp, iif); pim = pim_ifp->pim; oifname = argv[idx_interface]->arg; oif = if_lookup_by_name(oifname, pim->vrf_id); if (!oif) { vty_out(vty, "No such interface name %s\n", oifname); return CMD_WARNING; } grp_str = argv[idx_ipv4]->arg; result = inet_pton(AF_INET, grp_str, &grp_addr); if (result <= 0) { vty_out(vty, "Bad group address %s: errno=%d: %s\n", grp_str, errno, safe_strerror(errno)); return CMD_WARNING; } src_addr.s_addr = INADDR_ANY; if (pim_static_add(pim, iif, oif, grp_addr, src_addr)) { vty_out(vty, "Failed to add route\n"); return CMD_WARNING; } return CMD_SUCCESS; } DEFUN (interface_ip_mroute_source, interface_ip_mroute_source_cmd, "ip mroute INTERFACE A.B.C.D A.B.C.D", IP_STR "Add multicast route\n" "Outgoing interface name\n" "Group address\n" "Source address\n") { VTY_DECLVAR_CONTEXT(interface, iif); struct pim_interface *pim_ifp; struct pim_instance *pim; int idx_interface = 2; int idx_ipv4 = 3; int idx_ipv4_2 = 4; struct interface *oif; const char *oifname; const char *grp_str; struct in_addr grp_addr; const char *src_str; struct in_addr src_addr; int result; PIM_GET_PIM_INTERFACE(pim_ifp, iif); pim = pim_ifp->pim; oifname = argv[idx_interface]->arg; oif = if_lookup_by_name(oifname, pim->vrf_id); if (!oif) { vty_out(vty, "No such interface name %s\n", oifname); return CMD_WARNING; } grp_str = argv[idx_ipv4]->arg; result = inet_pton(AF_INET, grp_str, &grp_addr); if (result <= 0) { vty_out(vty, "Bad group address %s: errno=%d: %s\n", grp_str, errno, safe_strerror(errno)); return CMD_WARNING; } src_str = argv[idx_ipv4_2]->arg; result = inet_pton(AF_INET, src_str, &src_addr); if (result <= 0) { vty_out(vty, "Bad source address %s: errno=%d: %s\n", src_str, errno, safe_strerror(errno)); return CMD_WARNING; } if (pim_static_add(pim, iif, oif, grp_addr, src_addr)) { vty_out(vty, "Failed to add route\n"); return CMD_WARNING; } return CMD_SUCCESS; } DEFUN (interface_no_ip_mroute, interface_no_ip_mroute_cmd, "no ip mroute INTERFACE A.B.C.D", NO_STR IP_STR "Add multicast route\n" "Outgoing interface name\n" "Group Address\n") { VTY_DECLVAR_CONTEXT(interface, iif); struct pim_interface *pim_ifp; struct pim_instance *pim; int idx_interface = 3; int idx_ipv4 = 4; struct interface *oif; const char *oifname; const char *grp_str; struct in_addr grp_addr; struct in_addr src_addr; int result; PIM_GET_PIM_INTERFACE(pim_ifp, iif); pim = pim_ifp->pim; oifname = argv[idx_interface]->arg; oif = if_lookup_by_name(oifname, pim->vrf_id); if (!oif) { vty_out(vty, "No such interface name %s\n", oifname); return CMD_WARNING; } grp_str = argv[idx_ipv4]->arg; result = inet_pton(AF_INET, grp_str, &grp_addr); if (result <= 0) { vty_out(vty, "Bad group address %s: errno=%d: %s\n", grp_str, errno, safe_strerror(errno)); return CMD_WARNING; } src_addr.s_addr = INADDR_ANY; if (pim_static_del(pim, iif, oif, grp_addr, src_addr)) { vty_out(vty, "Failed to remove route\n"); return CMD_WARNING; } return CMD_SUCCESS; } DEFUN (interface_no_ip_mroute_source, interface_no_ip_mroute_source_cmd, "no ip mroute INTERFACE A.B.C.D A.B.C.D", NO_STR IP_STR "Add multicast route\n" "Outgoing interface name\n" "Group Address\n" "Source Address\n") { VTY_DECLVAR_CONTEXT(interface, iif); struct pim_interface *pim_ifp; struct pim_instance *pim; int idx_interface = 3; int idx_ipv4 = 4; int idx_ipv4_2 = 5; struct interface *oif; const char *oifname; const char *grp_str; struct in_addr grp_addr; const char *src_str; struct in_addr src_addr; int result; PIM_GET_PIM_INTERFACE(pim_ifp, iif); pim = pim_ifp->pim; oifname = argv[idx_interface]->arg; oif = if_lookup_by_name(oifname, pim->vrf_id); if (!oif) { vty_out(vty, "No such interface name %s\n", oifname); return CMD_WARNING; } grp_str = argv[idx_ipv4]->arg; result = inet_pton(AF_INET, grp_str, &grp_addr); if (result <= 0) { vty_out(vty, "Bad group address %s: errno=%d: %s\n", grp_str, errno, safe_strerror(errno)); return CMD_WARNING; } src_str = argv[idx_ipv4_2]->arg; result = inet_pton(AF_INET, src_str, &src_addr); if (result <= 0) { vty_out(vty, "Bad source address %s: errno=%d: %s\n", src_str, errno, safe_strerror(errno)); return CMD_WARNING; } if (pim_static_del(pim, iif, oif, grp_addr, src_addr)) { vty_out(vty, "Failed to remove route\n"); return CMD_WARNING; } return CMD_SUCCESS; } DEFUN (interface_ip_pim_hello, interface_ip_pim_hello_cmd, "ip pim hello (1-180) [(1-180)]", IP_STR PIM_STR IFACE_PIM_HELLO_STR IFACE_PIM_HELLO_TIME_STR IFACE_PIM_HELLO_HOLD_STR) { VTY_DECLVAR_CONTEXT(interface, ifp); int idx_time = 3; int idx_hold = 4; struct pim_interface *pim_ifp = ifp->info; if (!pim_ifp) { if (!pim_cmd_interface_add(ifp)) { vty_out(vty, "Could not enable PIM SM on interface\n"); return CMD_WARNING_CONFIG_FAILED; } } pim_ifp = ifp->info; pim_ifp->pim_hello_period = strtol(argv[idx_time]->arg, NULL, 10); if (argc == idx_hold + 1) pim_ifp->pim_default_holdtime = strtol(argv[idx_hold]->arg, NULL, 10); return CMD_SUCCESS; } DEFUN (interface_no_ip_pim_hello, interface_no_ip_pim_hello_cmd, "no ip pim hello [(1-180) (1-180)]", NO_STR IP_STR PIM_STR IFACE_PIM_HELLO_STR IFACE_PIM_HELLO_TIME_STR IFACE_PIM_HELLO_HOLD_STR) { VTY_DECLVAR_CONTEXT(interface, ifp); struct pim_interface *pim_ifp = ifp->info; if (!pim_ifp) { vty_out(vty, "Pim not enabled on this interface\n"); return CMD_WARNING_CONFIG_FAILED; } pim_ifp->pim_hello_period = PIM_DEFAULT_HELLO_PERIOD; pim_ifp->pim_default_holdtime = -1; return CMD_SUCCESS; } DEFUN (debug_igmp, debug_igmp_cmd, "debug igmp", DEBUG_STR DEBUG_IGMP_STR) { PIM_DO_DEBUG_IGMP_EVENTS; PIM_DO_DEBUG_IGMP_PACKETS; PIM_DO_DEBUG_IGMP_TRACE; return CMD_SUCCESS; } DEFUN (no_debug_igmp, no_debug_igmp_cmd, "no debug igmp", NO_STR DEBUG_STR DEBUG_IGMP_STR) { PIM_DONT_DEBUG_IGMP_EVENTS; PIM_DONT_DEBUG_IGMP_PACKETS; PIM_DONT_DEBUG_IGMP_TRACE; return CMD_SUCCESS; } DEFUN (debug_igmp_events, debug_igmp_events_cmd, "debug igmp events", DEBUG_STR DEBUG_IGMP_STR DEBUG_IGMP_EVENTS_STR) { PIM_DO_DEBUG_IGMP_EVENTS; return CMD_SUCCESS; } DEFUN (no_debug_igmp_events, no_debug_igmp_events_cmd, "no debug igmp events", NO_STR DEBUG_STR DEBUG_IGMP_STR DEBUG_IGMP_EVENTS_STR) { PIM_DONT_DEBUG_IGMP_EVENTS; return CMD_SUCCESS; } DEFUN (debug_igmp_packets, debug_igmp_packets_cmd, "debug igmp packets", DEBUG_STR DEBUG_IGMP_STR DEBUG_IGMP_PACKETS_STR) { PIM_DO_DEBUG_IGMP_PACKETS; return CMD_SUCCESS; } DEFUN (no_debug_igmp_packets, no_debug_igmp_packets_cmd, "no debug igmp packets", NO_STR DEBUG_STR DEBUG_IGMP_STR DEBUG_IGMP_PACKETS_STR) { PIM_DONT_DEBUG_IGMP_PACKETS; return CMD_SUCCESS; } DEFUN (debug_igmp_trace, debug_igmp_trace_cmd, "debug igmp trace", DEBUG_STR DEBUG_IGMP_STR DEBUG_IGMP_TRACE_STR) { PIM_DO_DEBUG_IGMP_TRACE; return CMD_SUCCESS; } DEFUN (no_debug_igmp_trace, no_debug_igmp_trace_cmd, "no debug igmp trace", NO_STR DEBUG_STR DEBUG_IGMP_STR DEBUG_IGMP_TRACE_STR) { PIM_DONT_DEBUG_IGMP_TRACE; return CMD_SUCCESS; } DEFUN (debug_mroute, debug_mroute_cmd, "debug mroute", DEBUG_STR DEBUG_MROUTE_STR) { PIM_DO_DEBUG_MROUTE; return CMD_SUCCESS; } DEFUN (debug_mroute_detail, debug_mroute_detail_cmd, "debug mroute detail", DEBUG_STR DEBUG_MROUTE_STR "detailed\n") { PIM_DO_DEBUG_MROUTE_DETAIL; return CMD_SUCCESS; } DEFUN (no_debug_mroute, no_debug_mroute_cmd, "no debug mroute", NO_STR DEBUG_STR DEBUG_MROUTE_STR) { PIM_DONT_DEBUG_MROUTE; return CMD_SUCCESS; } DEFUN (no_debug_mroute_detail, no_debug_mroute_detail_cmd, "no debug mroute detail", NO_STR DEBUG_STR DEBUG_MROUTE_STR "detailed\n") { PIM_DONT_DEBUG_MROUTE_DETAIL; return CMD_SUCCESS; } DEFUN (debug_pim_static, debug_pim_static_cmd, "debug pim static", DEBUG_STR DEBUG_PIM_STR DEBUG_STATIC_STR) { PIM_DO_DEBUG_STATIC; return CMD_SUCCESS; } DEFUN (no_debug_pim_static, no_debug_pim_static_cmd, "no debug pim static", NO_STR DEBUG_STR DEBUG_PIM_STR DEBUG_STATIC_STR) { PIM_DONT_DEBUG_STATIC; return CMD_SUCCESS; } DEFUN (debug_pim, debug_pim_cmd, "debug pim", DEBUG_STR DEBUG_PIM_STR) { PIM_DO_DEBUG_PIM_EVENTS; PIM_DO_DEBUG_PIM_PACKETS; PIM_DO_DEBUG_PIM_TRACE; PIM_DO_DEBUG_MSDP_EVENTS; PIM_DO_DEBUG_MSDP_PACKETS; PIM_DO_DEBUG_BSM; return CMD_SUCCESS; } DEFUN (no_debug_pim, no_debug_pim_cmd, "no debug pim", NO_STR DEBUG_STR DEBUG_PIM_STR) { PIM_DONT_DEBUG_PIM_EVENTS; PIM_DONT_DEBUG_PIM_PACKETS; PIM_DONT_DEBUG_PIM_TRACE; PIM_DONT_DEBUG_MSDP_EVENTS; PIM_DONT_DEBUG_MSDP_PACKETS; PIM_DONT_DEBUG_PIM_PACKETDUMP_SEND; PIM_DONT_DEBUG_PIM_PACKETDUMP_RECV; PIM_DONT_DEBUG_BSM; return CMD_SUCCESS; } DEFUN (debug_pim_nht, debug_pim_nht_cmd, "debug pim nht", DEBUG_STR DEBUG_PIM_STR "Nexthop Tracking\n") { PIM_DO_DEBUG_PIM_NHT; return CMD_SUCCESS; } DEFUN (no_debug_pim_nht, no_debug_pim_nht_cmd, "no debug pim nht", NO_STR DEBUG_STR DEBUG_PIM_STR "Nexthop Tracking\n") { PIM_DONT_DEBUG_PIM_NHT; return CMD_SUCCESS; } DEFUN (debug_pim_nht_rp, debug_pim_nht_rp_cmd, "debug pim nht rp", DEBUG_STR DEBUG_PIM_STR "Nexthop Tracking\n" "RP Nexthop Tracking\n") { PIM_DO_DEBUG_PIM_NHT_RP; return CMD_SUCCESS; } DEFUN (no_debug_pim_nht_rp, no_debug_pim_nht_rp_cmd, "no debug pim nht rp", NO_STR DEBUG_STR DEBUG_PIM_STR "Nexthop Tracking\n" "RP Nexthop Tracking\n") { PIM_DONT_DEBUG_PIM_NHT_RP; return CMD_SUCCESS; } DEFUN (debug_pim_events, debug_pim_events_cmd, "debug pim events", DEBUG_STR DEBUG_PIM_STR DEBUG_PIM_EVENTS_STR) { PIM_DO_DEBUG_PIM_EVENTS; return CMD_SUCCESS; } DEFUN (no_debug_pim_events, no_debug_pim_events_cmd, "no debug pim events", NO_STR DEBUG_STR DEBUG_PIM_STR DEBUG_PIM_EVENTS_STR) { PIM_DONT_DEBUG_PIM_EVENTS; return CMD_SUCCESS; } DEFUN (debug_pim_packets, debug_pim_packets_cmd, "debug pim packets []", DEBUG_STR DEBUG_PIM_STR DEBUG_PIM_PACKETS_STR DEBUG_PIM_HELLO_PACKETS_STR DEBUG_PIM_J_P_PACKETS_STR DEBUG_PIM_PIM_REG_PACKETS_STR) { int idx = 0; if (argv_find(argv, argc, "hello", &idx)) { PIM_DO_DEBUG_PIM_HELLO; vty_out(vty, "PIM Hello debugging is on\n"); } else if (argv_find(argv, argc, "joins", &idx)) { PIM_DO_DEBUG_PIM_J_P; vty_out(vty, "PIM Join/Prune debugging is on\n"); } else if (argv_find(argv, argc, "register", &idx)) { PIM_DO_DEBUG_PIM_REG; vty_out(vty, "PIM Register debugging is on\n"); } else { PIM_DO_DEBUG_PIM_PACKETS; vty_out(vty, "PIM Packet debugging is on \n"); } return CMD_SUCCESS; } DEFUN (no_debug_pim_packets, no_debug_pim_packets_cmd, "no debug pim packets []", NO_STR DEBUG_STR DEBUG_PIM_STR DEBUG_PIM_PACKETS_STR DEBUG_PIM_HELLO_PACKETS_STR DEBUG_PIM_J_P_PACKETS_STR DEBUG_PIM_PIM_REG_PACKETS_STR) { int idx = 0; if (argv_find(argv, argc, "hello", &idx)) { PIM_DONT_DEBUG_PIM_HELLO; vty_out(vty, "PIM Hello debugging is off \n"); } else if (argv_find(argv, argc, "joins", &idx)) { PIM_DONT_DEBUG_PIM_J_P; vty_out(vty, "PIM Join/Prune debugging is off \n"); } else if (argv_find(argv, argc, "register", &idx)) { PIM_DONT_DEBUG_PIM_REG; vty_out(vty, "PIM Register debugging is off\n"); } else PIM_DONT_DEBUG_PIM_PACKETS; return CMD_SUCCESS; } DEFUN (debug_pim_packetdump_send, debug_pim_packetdump_send_cmd, "debug pim packet-dump send", DEBUG_STR DEBUG_PIM_STR DEBUG_PIM_PACKETDUMP_STR DEBUG_PIM_PACKETDUMP_SEND_STR) { PIM_DO_DEBUG_PIM_PACKETDUMP_SEND; return CMD_SUCCESS; } DEFUN (no_debug_pim_packetdump_send, no_debug_pim_packetdump_send_cmd, "no debug pim packet-dump send", NO_STR DEBUG_STR DEBUG_PIM_STR DEBUG_PIM_PACKETDUMP_STR DEBUG_PIM_PACKETDUMP_SEND_STR) { PIM_DONT_DEBUG_PIM_PACKETDUMP_SEND; return CMD_SUCCESS; } DEFUN (debug_pim_packetdump_recv, debug_pim_packetdump_recv_cmd, "debug pim packet-dump receive", DEBUG_STR DEBUG_PIM_STR DEBUG_PIM_PACKETDUMP_STR DEBUG_PIM_PACKETDUMP_RECV_STR) { PIM_DO_DEBUG_PIM_PACKETDUMP_RECV; return CMD_SUCCESS; } DEFUN (no_debug_pim_packetdump_recv, no_debug_pim_packetdump_recv_cmd, "no debug pim packet-dump receive", NO_STR DEBUG_STR DEBUG_PIM_STR DEBUG_PIM_PACKETDUMP_STR DEBUG_PIM_PACKETDUMP_RECV_STR) { PIM_DONT_DEBUG_PIM_PACKETDUMP_RECV; return CMD_SUCCESS; } DEFUN (debug_pim_trace, debug_pim_trace_cmd, "debug pim trace", DEBUG_STR DEBUG_PIM_STR DEBUG_PIM_TRACE_STR) { PIM_DO_DEBUG_PIM_TRACE; return CMD_SUCCESS; } DEFUN (debug_pim_trace_detail, debug_pim_trace_detail_cmd, "debug pim trace detail", DEBUG_STR DEBUG_PIM_STR DEBUG_PIM_TRACE_STR "Detailed Information\n") { PIM_DO_DEBUG_PIM_TRACE_DETAIL; return CMD_SUCCESS; } DEFUN (no_debug_pim_trace, no_debug_pim_trace_cmd, "no debug pim trace", NO_STR DEBUG_STR DEBUG_PIM_STR DEBUG_PIM_TRACE_STR) { PIM_DONT_DEBUG_PIM_TRACE; return CMD_SUCCESS; } DEFUN (no_debug_pim_trace_detail, no_debug_pim_trace_detail_cmd, "no debug pim trace detail", NO_STR DEBUG_STR DEBUG_PIM_STR DEBUG_PIM_TRACE_STR "Detailed Information\n") { PIM_DONT_DEBUG_PIM_TRACE_DETAIL; return CMD_SUCCESS; } DEFUN (debug_ssmpingd, debug_ssmpingd_cmd, "debug ssmpingd", DEBUG_STR DEBUG_SSMPINGD_STR) { PIM_DO_DEBUG_SSMPINGD; return CMD_SUCCESS; } DEFUN (no_debug_ssmpingd, no_debug_ssmpingd_cmd, "no debug ssmpingd", NO_STR DEBUG_STR DEBUG_SSMPINGD_STR) { PIM_DONT_DEBUG_SSMPINGD; return CMD_SUCCESS; } DEFUN (debug_pim_zebra, debug_pim_zebra_cmd, "debug pim zebra", DEBUG_STR DEBUG_PIM_STR DEBUG_PIM_ZEBRA_STR) { PIM_DO_DEBUG_ZEBRA; return CMD_SUCCESS; } DEFUN (no_debug_pim_zebra, no_debug_pim_zebra_cmd, "no debug pim zebra", NO_STR DEBUG_STR DEBUG_PIM_STR DEBUG_PIM_ZEBRA_STR) { PIM_DONT_DEBUG_ZEBRA; return CMD_SUCCESS; } DEFUN (debug_pim_vxlan, debug_pim_vxlan_cmd, "debug pim vxlan", DEBUG_STR DEBUG_PIM_STR DEBUG_PIM_VXLAN_STR) { PIM_DO_DEBUG_VXLAN; return CMD_SUCCESS; } DEFUN (no_debug_pim_vxlan, no_debug_pim_vxlan_cmd, "no debug pim vxlan", NO_STR DEBUG_STR DEBUG_PIM_STR DEBUG_PIM_VXLAN_STR) { PIM_DONT_DEBUG_VXLAN; return CMD_SUCCESS; } DEFUN (debug_msdp, debug_msdp_cmd, "debug msdp", DEBUG_STR DEBUG_MSDP_STR) { PIM_DO_DEBUG_MSDP_EVENTS; PIM_DO_DEBUG_MSDP_PACKETS; return CMD_SUCCESS; } DEFUN (no_debug_msdp, no_debug_msdp_cmd, "no debug msdp", NO_STR DEBUG_STR DEBUG_MSDP_STR) { PIM_DONT_DEBUG_MSDP_EVENTS; PIM_DONT_DEBUG_MSDP_PACKETS; return CMD_SUCCESS; } DEFUN (debug_msdp_events, debug_msdp_events_cmd, "debug msdp events", DEBUG_STR DEBUG_MSDP_STR DEBUG_MSDP_EVENTS_STR) { PIM_DO_DEBUG_MSDP_EVENTS; return CMD_SUCCESS; } DEFUN (no_debug_msdp_events, no_debug_msdp_events_cmd, "no debug msdp events", NO_STR DEBUG_STR DEBUG_MSDP_STR DEBUG_MSDP_EVENTS_STR) { PIM_DONT_DEBUG_MSDP_EVENTS; return CMD_SUCCESS; } DEFUN (debug_msdp_packets, debug_msdp_packets_cmd, "debug msdp packets", DEBUG_STR DEBUG_MSDP_STR DEBUG_MSDP_PACKETS_STR) { PIM_DO_DEBUG_MSDP_PACKETS; return CMD_SUCCESS; } DEFUN (no_debug_msdp_packets, no_debug_msdp_packets_cmd, "no debug msdp packets", NO_STR DEBUG_STR DEBUG_MSDP_STR DEBUG_MSDP_PACKETS_STR) { PIM_DONT_DEBUG_MSDP_PACKETS; return CMD_SUCCESS; } DEFUN (debug_mtrace, debug_mtrace_cmd, "debug mtrace", DEBUG_STR DEBUG_MTRACE_STR) { PIM_DO_DEBUG_MTRACE; return CMD_SUCCESS; } DEFUN (no_debug_mtrace, no_debug_mtrace_cmd, "no debug mtrace", NO_STR DEBUG_STR DEBUG_MTRACE_STR) { PIM_DONT_DEBUG_MTRACE; return CMD_SUCCESS; } DEFUN (debug_bsm, debug_bsm_cmd, "debug pim bsm", DEBUG_STR DEBUG_PIM_STR DEBUG_PIM_BSM_STR) { PIM_DO_DEBUG_BSM; return CMD_SUCCESS; } DEFUN (no_debug_bsm, no_debug_bsm_cmd, "no debug pim bsm", NO_STR DEBUG_STR DEBUG_PIM_STR DEBUG_PIM_BSM_STR) { PIM_DONT_DEBUG_BSM; return CMD_SUCCESS; } DEFUN_NOSH (show_debugging_pim, show_debugging_pim_cmd, "show debugging [pim]", SHOW_STR DEBUG_STR PIM_STR) { vty_out(vty, "PIM debugging status\n"); pim_debug_config_write(vty); return CMD_SUCCESS; } static int interface_pim_use_src_cmd_worker(struct vty *vty, const char *source) { int result; struct in_addr source_addr; int ret = CMD_SUCCESS; VTY_DECLVAR_CONTEXT(interface, ifp); result = inet_pton(AF_INET, source, &source_addr); if (result <= 0) { vty_out(vty, "%% Bad source address %s: errno=%d: %s\n", source, errno, safe_strerror(errno)); return CMD_WARNING_CONFIG_FAILED; } result = pim_update_source_set(ifp, source_addr); switch (result) { case PIM_SUCCESS: break; case PIM_IFACE_NOT_FOUND: ret = CMD_WARNING_CONFIG_FAILED; vty_out(vty, "Pim not enabled on this interface\n"); break; case PIM_UPDATE_SOURCE_DUP: ret = CMD_WARNING; vty_out(vty, "%% Source already set to %s\n", source); break; default: ret = CMD_WARNING_CONFIG_FAILED; vty_out(vty, "%% Source set failed\n"); } return ret; } DEFUN (interface_pim_use_source, interface_pim_use_source_cmd, "ip pim use-source A.B.C.D", IP_STR PIM_STR "Configure primary IP address\n" "source ip address\n") { return interface_pim_use_src_cmd_worker(vty, argv[3]->arg); } DEFUN (interface_no_pim_use_source, interface_no_pim_use_source_cmd, "no ip pim use-source [A.B.C.D]", NO_STR IP_STR PIM_STR "Delete source IP address\n" "source ip address\n") { return interface_pim_use_src_cmd_worker(vty, "0.0.0.0"); } DEFUN (ip_pim_bfd, ip_pim_bfd_cmd, "ip pim bfd", IP_STR PIM_STR "Enables BFD support\n") { VTY_DECLVAR_CONTEXT(interface, ifp); struct pim_interface *pim_ifp = ifp->info; struct bfd_info *bfd_info = NULL; if (!pim_ifp) { if (!pim_cmd_interface_add(ifp)) { vty_out(vty, "Could not enable PIM SM on interface\n"); return CMD_WARNING; } } pim_ifp = ifp->info; bfd_info = pim_ifp->bfd_info; if (!bfd_info || !CHECK_FLAG(bfd_info->flags, BFD_FLAG_PARAM_CFG)) pim_bfd_if_param_set(ifp, BFD_DEF_MIN_RX, BFD_DEF_MIN_TX, BFD_DEF_DETECT_MULT, 1); return CMD_SUCCESS; } DEFUN (no_ip_pim_bfd, no_ip_pim_bfd_cmd, "no ip pim bfd", NO_STR IP_STR PIM_STR "Disables BFD support\n") { VTY_DECLVAR_CONTEXT(interface, ifp); struct pim_interface *pim_ifp = ifp->info; if (!pim_ifp) { vty_out(vty, "Pim not enabled on this interface\n"); return CMD_WARNING; } if (pim_ifp->bfd_info) { pim_bfd_reg_dereg_all_nbr(ifp, ZEBRA_BFD_DEST_DEREGISTER); bfd_info_free(&(pim_ifp->bfd_info)); } return CMD_SUCCESS; } DEFUN (ip_pim_bsm, ip_pim_bsm_cmd, "ip pim bsm", IP_STR PIM_STR "Enables BSM support on the interface\n") { VTY_DECLVAR_CONTEXT(interface, ifp); struct pim_interface *pim_ifp = ifp->info; if (!pim_ifp) { if (!pim_cmd_interface_add(ifp)) { vty_out(vty, "Could not enable PIM SM on interface\n"); return CMD_WARNING; } } pim_ifp = ifp->info; pim_ifp->bsm_enable = true; return CMD_SUCCESS; } DEFUN (no_ip_pim_bsm, no_ip_pim_bsm_cmd, "no ip pim bsm", NO_STR IP_STR PIM_STR "Disables BSM support\n") { VTY_DECLVAR_CONTEXT(interface, ifp); struct pim_interface *pim_ifp = ifp->info; if (!pim_ifp) { vty_out(vty, "Pim not enabled on this interface\n"); return CMD_WARNING; } pim_ifp->bsm_enable = false; return CMD_SUCCESS; } DEFUN (ip_pim_ucast_bsm, ip_pim_ucast_bsm_cmd, "ip pim unicast-bsm", IP_STR PIM_STR "Accept/Send unicast BSM on the interface\n") { VTY_DECLVAR_CONTEXT(interface, ifp); struct pim_interface *pim_ifp = ifp->info; if (!pim_ifp) { if (!pim_cmd_interface_add(ifp)) { vty_out(vty, "Could not enable PIM SM on interface\n"); return CMD_WARNING; } } pim_ifp = ifp->info; pim_ifp->ucast_bsm_accept = true; return CMD_SUCCESS; } DEFUN (no_ip_pim_ucast_bsm, no_ip_pim_ucast_bsm_cmd, "no ip pim unicast-bsm", NO_STR IP_STR PIM_STR "Block send/receive unicast BSM on this interface\n") { VTY_DECLVAR_CONTEXT(interface, ifp); struct pim_interface *pim_ifp = ifp->info; if (!pim_ifp) { vty_out(vty, "Pim not enabled on this interface\n"); return CMD_WARNING; } pim_ifp->ucast_bsm_accept = false; return CMD_SUCCESS; } #if HAVE_BFDD > 0 DEFUN_HIDDEN( ip_pim_bfd_param, ip_pim_bfd_param_cmd, "ip pim bfd (2-255) (50-60000) (50-60000)", IP_STR PIM_STR "Enables BFD support\n" "Detect Multiplier\n" "Required min receive interval\n" "Desired min transmit interval\n") #else DEFUN( ip_pim_bfd_param, ip_pim_bfd_param_cmd, "ip pim bfd (2-255) (50-60000) (50-60000)", IP_STR PIM_STR "Enables BFD support\n" "Detect Multiplier\n" "Required min receive interval\n" "Desired min transmit interval\n") #endif /* HAVE_BFDD */ { VTY_DECLVAR_CONTEXT(interface, ifp); int idx_number = 3; int idx_number_2 = 4; int idx_number_3 = 5; uint32_t rx_val; uint32_t tx_val; uint8_t dm_val; int ret; struct pim_interface *pim_ifp = ifp->info; if (!pim_ifp) { if (!pim_cmd_interface_add(ifp)) { vty_out(vty, "Could not enable PIM SM on interface\n"); return CMD_WARNING; } } if ((ret = bfd_validate_param( vty, argv[idx_number]->arg, argv[idx_number_2]->arg, argv[idx_number_3]->arg, &dm_val, &rx_val, &tx_val)) != CMD_SUCCESS) return ret; pim_bfd_if_param_set(ifp, rx_val, tx_val, dm_val, 0); return CMD_SUCCESS; } #if HAVE_BFDD == 0 ALIAS(no_ip_pim_bfd, no_ip_pim_bfd_param_cmd, "no ip pim bfd (2-255) (50-60000) (50-60000)", NO_STR IP_STR PIM_STR "Enables BFD support\n" "Detect Multiplier\n" "Required min receive interval\n" "Desired min transmit interval\n") #endif /* !HAVE_BFDD */ static int ip_msdp_peer_cmd_worker(struct pim_instance *pim, struct vty *vty, const char *peer, const char *local) { enum pim_msdp_err result; struct in_addr peer_addr; struct in_addr local_addr; int ret = CMD_SUCCESS; result = inet_pton(AF_INET, peer, &peer_addr); if (result <= 0) { vty_out(vty, "%% Bad peer address %s: errno=%d: %s\n", peer, errno, safe_strerror(errno)); return CMD_WARNING_CONFIG_FAILED; } result = inet_pton(AF_INET, local, &local_addr); if (result <= 0) { vty_out(vty, "%% Bad source address %s: errno=%d: %s\n", local, errno, safe_strerror(errno)); return CMD_WARNING_CONFIG_FAILED; } result = pim_msdp_peer_add(pim, peer_addr, local_addr, "default", NULL /* mp_p */); switch (result) { case PIM_MSDP_ERR_NONE: break; case PIM_MSDP_ERR_OOM: ret = CMD_WARNING_CONFIG_FAILED; vty_out(vty, "%% Out of memory\n"); break; case PIM_MSDP_ERR_PEER_EXISTS: ret = CMD_WARNING; vty_out(vty, "%% Peer exists\n"); break; case PIM_MSDP_ERR_MAX_MESH_GROUPS: ret = CMD_WARNING_CONFIG_FAILED; vty_out(vty, "%% Only one mesh-group allowed currently\n"); break; default: ret = CMD_WARNING_CONFIG_FAILED; vty_out(vty, "%% peer add failed\n"); } return ret; } DEFUN_HIDDEN (ip_msdp_peer, ip_msdp_peer_cmd, "ip msdp peer A.B.C.D source A.B.C.D", IP_STR CFG_MSDP_STR "Configure MSDP peer\n" "peer ip address\n" "Source address for TCP connection\n" "local ip address\n") { PIM_DECLVAR_CONTEXT(vrf, pim); return ip_msdp_peer_cmd_worker(pim, vty, argv[3]->arg, argv[5]->arg); } static int ip_no_msdp_peer_cmd_worker(struct pim_instance *pim, struct vty *vty, const char *peer) { enum pim_msdp_err result; struct in_addr peer_addr; result = inet_pton(AF_INET, peer, &peer_addr); if (result <= 0) { vty_out(vty, "%% Bad peer address %s: errno=%d: %s\n", peer, errno, safe_strerror(errno)); return CMD_WARNING_CONFIG_FAILED; } result = pim_msdp_peer_del(pim, peer_addr); switch (result) { case PIM_MSDP_ERR_NONE: break; case PIM_MSDP_ERR_NO_PEER: vty_out(vty, "%% Peer does not exist\n"); break; default: vty_out(vty, "%% peer del failed\n"); } return result ? CMD_WARNING_CONFIG_FAILED : CMD_SUCCESS; } DEFUN_HIDDEN (no_ip_msdp_peer, no_ip_msdp_peer_cmd, "no ip msdp peer A.B.C.D", NO_STR IP_STR CFG_MSDP_STR "Delete MSDP peer\n" "peer ip address\n") { PIM_DECLVAR_CONTEXT(vrf, pim); return ip_no_msdp_peer_cmd_worker(pim, vty, argv[4]->arg); } static int ip_msdp_mesh_group_member_cmd_worker(struct pim_instance *pim, struct vty *vty, const char *mg, const char *mbr) { enum pim_msdp_err result; struct in_addr mbr_ip; int ret = CMD_SUCCESS; result = inet_pton(AF_INET, mbr, &mbr_ip); if (result <= 0) { vty_out(vty, "%% Bad member address %s: errno=%d: %s\n", mbr, errno, safe_strerror(errno)); return CMD_WARNING_CONFIG_FAILED; } result = pim_msdp_mg_mbr_add(pim, mg, mbr_ip); switch (result) { case PIM_MSDP_ERR_NONE: break; case PIM_MSDP_ERR_OOM: ret = CMD_WARNING_CONFIG_FAILED; vty_out(vty, "%% Out of memory\n"); break; case PIM_MSDP_ERR_MG_MBR_EXISTS: ret = CMD_WARNING; vty_out(vty, "%% mesh-group member exists\n"); break; case PIM_MSDP_ERR_MAX_MESH_GROUPS: ret = CMD_WARNING_CONFIG_FAILED; vty_out(vty, "%% Only one mesh-group allowed currently\n"); break; default: ret = CMD_WARNING_CONFIG_FAILED; vty_out(vty, "%% member add failed\n"); } return ret; } DEFUN (ip_msdp_mesh_group_member, ip_msdp_mesh_group_member_cmd, "ip msdp mesh-group WORD member A.B.C.D", IP_STR CFG_MSDP_STR "Configure MSDP mesh-group\n" "mesh group name\n" "mesh group member\n" "peer ip address\n") { PIM_DECLVAR_CONTEXT(vrf, pim); return ip_msdp_mesh_group_member_cmd_worker(pim, vty, argv[3]->arg, argv[5]->arg); } static int ip_no_msdp_mesh_group_member_cmd_worker(struct pim_instance *pim, struct vty *vty, const char *mg, const char *mbr) { enum pim_msdp_err result; struct in_addr mbr_ip; result = inet_pton(AF_INET, mbr, &mbr_ip); if (result <= 0) { vty_out(vty, "%% Bad member address %s: errno=%d: %s\n", mbr, errno, safe_strerror(errno)); return CMD_WARNING_CONFIG_FAILED; } result = pim_msdp_mg_mbr_del(pim, mg, mbr_ip); switch (result) { case PIM_MSDP_ERR_NONE: break; case PIM_MSDP_ERR_NO_MG: vty_out(vty, "%% mesh-group does not exist\n"); break; case PIM_MSDP_ERR_NO_MG_MBR: vty_out(vty, "%% mesh-group member does not exist\n"); break; default: vty_out(vty, "%% mesh-group member del failed\n"); } return result ? CMD_WARNING_CONFIG_FAILED : CMD_SUCCESS; } DEFUN (no_ip_msdp_mesh_group_member, no_ip_msdp_mesh_group_member_cmd, "no ip msdp mesh-group WORD member A.B.C.D", NO_STR IP_STR CFG_MSDP_STR "Delete MSDP mesh-group member\n" "mesh group name\n" "mesh group member\n" "peer ip address\n") { PIM_DECLVAR_CONTEXT(vrf, pim); return ip_no_msdp_mesh_group_member_cmd_worker(pim, vty, argv[4]->arg, argv[6]->arg); } static int ip_msdp_mesh_group_source_cmd_worker(struct pim_instance *pim, struct vty *vty, const char *mg, const char *src) { enum pim_msdp_err result; struct in_addr src_ip; result = inet_pton(AF_INET, src, &src_ip); if (result <= 0) { vty_out(vty, "%% Bad source address %s: errno=%d: %s\n", src, errno, safe_strerror(errno)); return CMD_WARNING_CONFIG_FAILED; } result = pim_msdp_mg_src_add(pim, mg, src_ip); switch (result) { case PIM_MSDP_ERR_NONE: break; case PIM_MSDP_ERR_OOM: vty_out(vty, "%% Out of memory\n"); break; case PIM_MSDP_ERR_MAX_MESH_GROUPS: vty_out(vty, "%% Only one mesh-group allowed currently\n"); break; default: vty_out(vty, "%% source add failed\n"); } return result ? CMD_WARNING_CONFIG_FAILED : CMD_SUCCESS; } DEFUN (ip_msdp_mesh_group_source, ip_msdp_mesh_group_source_cmd, "ip msdp mesh-group WORD source A.B.C.D", IP_STR CFG_MSDP_STR "Configure MSDP mesh-group\n" "mesh group name\n" "mesh group local address\n" "source ip address for the TCP connection\n") { PIM_DECLVAR_CONTEXT(vrf, pim); return ip_msdp_mesh_group_source_cmd_worker(pim, vty, argv[3]->arg, argv[5]->arg); } static int ip_no_msdp_mesh_group_source_cmd_worker(struct pim_instance *pim, struct vty *vty, const char *mg) { enum pim_msdp_err result; result = pim_msdp_mg_src_del(pim, mg); switch (result) { case PIM_MSDP_ERR_NONE: break; case PIM_MSDP_ERR_NO_MG: vty_out(vty, "%% mesh-group does not exist\n"); break; default: vty_out(vty, "%% mesh-group source del failed\n"); } return result ? CMD_WARNING_CONFIG_FAILED : CMD_SUCCESS; } static int ip_no_msdp_mesh_group_cmd_worker(struct pim_instance *pim, struct vty *vty, const char *mg) { enum pim_msdp_err result; result = pim_msdp_mg_del(pim, mg); switch (result) { case PIM_MSDP_ERR_NONE: break; case PIM_MSDP_ERR_NO_MG: vty_out(vty, "%% mesh-group does not exist\n"); break; default: vty_out(vty, "%% mesh-group source del failed\n"); } return result ? CMD_WARNING_CONFIG_FAILED : CMD_SUCCESS; } DEFUN (no_ip_msdp_mesh_group_source, no_ip_msdp_mesh_group_source_cmd, "no ip msdp mesh-group WORD source [A.B.C.D]", NO_STR IP_STR CFG_MSDP_STR "Delete MSDP mesh-group source\n" "mesh group name\n" "mesh group source\n" "mesh group local address\n") { PIM_DECLVAR_CONTEXT(vrf, pim); if (argc == 7) return ip_no_msdp_mesh_group_cmd_worker(pim, vty, argv[6]->arg); else return ip_no_msdp_mesh_group_source_cmd_worker(pim, vty, argv[4]->arg); } static void print_empty_json_obj(struct vty *vty) { json_object *json; json = json_object_new_object(); vty_out(vty, "%s\n", json_object_to_json_string_ext(json, JSON_C_TO_STRING_PRETTY)); json_object_free(json); } static void ip_msdp_show_mesh_group(struct pim_instance *pim, struct vty *vty, bool uj) { struct listnode *mbrnode; struct pim_msdp_mg_mbr *mbr; struct pim_msdp_mg *mg = pim->msdp.mg; char mbr_str[INET_ADDRSTRLEN]; char src_str[INET_ADDRSTRLEN]; char state_str[PIM_MSDP_STATE_STRLEN]; enum pim_msdp_peer_state state; json_object *json = NULL; json_object *json_mg_row = NULL; json_object *json_members = NULL; json_object *json_row = NULL; if (!mg) { if (uj) print_empty_json_obj(vty); return; } pim_inet4_dump("", mg->src_ip, src_str, sizeof(src_str)); if (uj) { json = json_object_new_object(); /* currently there is only one mesh group but we should still * make * it a dict with mg-name as key */ json_mg_row = json_object_new_object(); json_object_string_add(json_mg_row, "name", mg->mesh_group_name); json_object_string_add(json_mg_row, "source", src_str); } else { vty_out(vty, "Mesh group : %s\n", mg->mesh_group_name); vty_out(vty, " Source : %s\n", src_str); vty_out(vty, " Member State\n"); } for (ALL_LIST_ELEMENTS_RO(mg->mbr_list, mbrnode, mbr)) { pim_inet4_dump("", mbr->mbr_ip, mbr_str, sizeof(mbr_str)); if (mbr->mp) { state = mbr->mp->state; } else { state = PIM_MSDP_DISABLED; } pim_msdp_state_dump(state, state_str, sizeof(state_str)); if (uj) { json_row = json_object_new_object(); json_object_string_add(json_row, "member", mbr_str); json_object_string_add(json_row, "state", state_str); if (!json_members) { json_members = json_object_new_object(); json_object_object_add(json_mg_row, "members", json_members); } json_object_object_add(json_members, mbr_str, json_row); } else { vty_out(vty, " %-15s %11s\n", mbr_str, state_str); } } if (uj) { json_object_object_add(json, mg->mesh_group_name, json_mg_row); vty_out(vty, "%s\n", json_object_to_json_string_ext( json, JSON_C_TO_STRING_PRETTY)); json_object_free(json); } } DEFUN (show_ip_msdp_mesh_group, show_ip_msdp_mesh_group_cmd, "show ip msdp [vrf NAME] mesh-group [json]", SHOW_STR IP_STR MSDP_STR VRF_CMD_HELP_STR "MSDP mesh-group information\n" JSON_STR) { bool uj = use_json(argc, argv); int idx = 2; struct vrf *vrf = pim_cmd_lookup_vrf(vty, argv, argc, &idx); if (!vrf) return CMD_WARNING; ip_msdp_show_mesh_group(vrf->info, vty, uj); return CMD_SUCCESS; } DEFUN (show_ip_msdp_mesh_group_vrf_all, show_ip_msdp_mesh_group_vrf_all_cmd, "show ip msdp vrf all mesh-group [json]", SHOW_STR IP_STR MSDP_STR VRF_CMD_HELP_STR "MSDP mesh-group information\n" JSON_STR) { bool uj = use_json(argc, argv); struct vrf *vrf; bool first = true; if (uj) vty_out(vty, "{ "); RB_FOREACH (vrf, vrf_name_head, &vrfs_by_name) { if (uj) { if (!first) vty_out(vty, ", "); vty_out(vty, " \"%s\": ", vrf->name); first = false; } else vty_out(vty, "VRF: %s\n", vrf->name); ip_msdp_show_mesh_group(vrf->info, vty, uj); } if (uj) vty_out(vty, "}\n"); return CMD_SUCCESS; } static void ip_msdp_show_peers(struct pim_instance *pim, struct vty *vty, bool uj) { struct listnode *mpnode; struct pim_msdp_peer *mp; char peer_str[INET_ADDRSTRLEN]; char local_str[INET_ADDRSTRLEN]; char state_str[PIM_MSDP_STATE_STRLEN]; char timebuf[PIM_MSDP_UPTIME_STRLEN]; int64_t now; json_object *json = NULL; json_object *json_row = NULL; if (uj) { json = json_object_new_object(); } else { vty_out(vty, "Peer Local State Uptime SaCnt\n"); } for (ALL_LIST_ELEMENTS_RO(pim->msdp.peer_list, mpnode, mp)) { if (mp->state == PIM_MSDP_ESTABLISHED) { now = pim_time_monotonic_sec(); pim_time_uptime(timebuf, sizeof(timebuf), now - mp->uptime); } else { strlcpy(timebuf, "-", sizeof(timebuf)); } pim_inet4_dump("", mp->peer, peer_str, sizeof(peer_str)); pim_inet4_dump("", mp->local, local_str, sizeof(local_str)); pim_msdp_state_dump(mp->state, state_str, sizeof(state_str)); if (uj) { json_row = json_object_new_object(); json_object_string_add(json_row, "peer", peer_str); json_object_string_add(json_row, "local", local_str); json_object_string_add(json_row, "state", state_str); json_object_string_add(json_row, "upTime", timebuf); json_object_int_add(json_row, "saCount", mp->sa_cnt); json_object_object_add(json, peer_str, json_row); } else { vty_out(vty, "%-15s %15s %11s %8s %6d\n", peer_str, local_str, state_str, timebuf, mp->sa_cnt); } } if (uj) { vty_out(vty, "%s\n", json_object_to_json_string_ext( json, JSON_C_TO_STRING_PRETTY)); json_object_free(json); } } static void ip_msdp_show_peers_detail(struct pim_instance *pim, struct vty *vty, const char *peer, bool uj) { struct listnode *mpnode; struct pim_msdp_peer *mp; char peer_str[INET_ADDRSTRLEN]; char local_str[INET_ADDRSTRLEN]; char state_str[PIM_MSDP_STATE_STRLEN]; char timebuf[PIM_MSDP_UPTIME_STRLEN]; char katimer[PIM_MSDP_TIMER_STRLEN]; char crtimer[PIM_MSDP_TIMER_STRLEN]; char holdtimer[PIM_MSDP_TIMER_STRLEN]; int64_t now; json_object *json = NULL; json_object *json_row = NULL; if (uj) { json = json_object_new_object(); } for (ALL_LIST_ELEMENTS_RO(pim->msdp.peer_list, mpnode, mp)) { pim_inet4_dump("", mp->peer, peer_str, sizeof(peer_str)); if (strcmp(peer, "detail") && strcmp(peer, peer_str)) continue; if (mp->state == PIM_MSDP_ESTABLISHED) { now = pim_time_monotonic_sec(); pim_time_uptime(timebuf, sizeof(timebuf), now - mp->uptime); } else { strlcpy(timebuf, "-", sizeof(timebuf)); } pim_inet4_dump("", mp->local, local_str, sizeof(local_str)); pim_msdp_state_dump(mp->state, state_str, sizeof(state_str)); pim_time_timer_to_hhmmss(katimer, sizeof(katimer), mp->ka_timer); pim_time_timer_to_hhmmss(crtimer, sizeof(crtimer), mp->cr_timer); pim_time_timer_to_hhmmss(holdtimer, sizeof(holdtimer), mp->hold_timer); if (uj) { json_row = json_object_new_object(); json_object_string_add(json_row, "peer", peer_str); json_object_string_add(json_row, "local", local_str); json_object_string_add(json_row, "meshGroupName", mp->mesh_group_name); json_object_string_add(json_row, "state", state_str); json_object_string_add(json_row, "upTime", timebuf); json_object_string_add(json_row, "keepAliveTimer", katimer); json_object_string_add(json_row, "connRetryTimer", crtimer); json_object_string_add(json_row, "holdTimer", holdtimer); json_object_string_add(json_row, "lastReset", mp->last_reset); json_object_int_add(json_row, "connAttempts", mp->conn_attempts); json_object_int_add(json_row, "establishedChanges", mp->est_flaps); json_object_int_add(json_row, "saCount", mp->sa_cnt); json_object_int_add(json_row, "kaSent", mp->ka_tx_cnt); json_object_int_add(json_row, "kaRcvd", mp->ka_rx_cnt); json_object_int_add(json_row, "saSent", mp->sa_tx_cnt); json_object_int_add(json_row, "saRcvd", mp->sa_rx_cnt); json_object_object_add(json, peer_str, json_row); } else { vty_out(vty, "Peer : %s\n", peer_str); vty_out(vty, " Local : %s\n", local_str); vty_out(vty, " Mesh Group : %s\n", mp->mesh_group_name); vty_out(vty, " State : %s\n", state_str); vty_out(vty, " Uptime : %s\n", timebuf); vty_out(vty, " Keepalive Timer : %s\n", katimer); vty_out(vty, " Conn Retry Timer : %s\n", crtimer); vty_out(vty, " Hold Timer : %s\n", holdtimer); vty_out(vty, " Last Reset : %s\n", mp->last_reset); vty_out(vty, " Conn Attempts : %d\n", mp->conn_attempts); vty_out(vty, " Established Changes : %d\n", mp->est_flaps); vty_out(vty, " SA Count : %d\n", mp->sa_cnt); vty_out(vty, " Statistics :\n"); vty_out(vty, " Sent Rcvd\n"); vty_out(vty, " Keepalives : %10d %10d\n", mp->ka_tx_cnt, mp->ka_rx_cnt); vty_out(vty, " SAs : %10d %10d\n", mp->sa_tx_cnt, mp->sa_rx_cnt); vty_out(vty, "\n"); } } if (uj) { vty_out(vty, "%s\n", json_object_to_json_string_ext( json, JSON_C_TO_STRING_PRETTY)); json_object_free(json); } } DEFUN (show_ip_msdp_peer_detail, show_ip_msdp_peer_detail_cmd, "show ip msdp [vrf NAME] peer [detail|A.B.C.D] [json]", SHOW_STR IP_STR MSDP_STR VRF_CMD_HELP_STR "MSDP peer information\n" "Detailed output\n" "peer ip address\n" JSON_STR) { bool uj = use_json(argc, argv); int idx = 2; struct vrf *vrf = pim_cmd_lookup_vrf(vty, argv, argc, &idx); if (!vrf) return CMD_WARNING; char *arg = NULL; if (argv_find(argv, argc, "detail", &idx)) arg = argv[idx]->text; else if (argv_find(argv, argc, "A.B.C.D", &idx)) arg = argv[idx]->arg; if (arg) ip_msdp_show_peers_detail(vrf->info, vty, argv[idx]->arg, uj); else ip_msdp_show_peers(vrf->info, vty, uj); return CMD_SUCCESS; } DEFUN (show_ip_msdp_peer_detail_vrf_all, show_ip_msdp_peer_detail_vrf_all_cmd, "show ip msdp vrf all peer [detail|A.B.C.D] [json]", SHOW_STR IP_STR MSDP_STR VRF_CMD_HELP_STR "MSDP peer information\n" "Detailed output\n" "peer ip address\n" JSON_STR) { int idx = 2; bool uj = use_json(argc, argv); struct vrf *vrf; bool first = true; if (uj) vty_out(vty, "{ "); RB_FOREACH (vrf, vrf_name_head, &vrfs_by_name) { if (uj) { if (!first) vty_out(vty, ", "); vty_out(vty, " \"%s\": ", vrf->name); first = false; } else vty_out(vty, "VRF: %s\n", vrf->name); if (argv_find(argv, argc, "detail", &idx) || argv_find(argv, argc, "A.B.C.D", &idx)) ip_msdp_show_peers_detail(vrf->info, vty, argv[idx]->arg, uj); else ip_msdp_show_peers(vrf->info, vty, uj); } if (uj) vty_out(vty, "}\n"); return CMD_SUCCESS; } static void ip_msdp_show_sa(struct pim_instance *pim, struct vty *vty, bool uj) { struct listnode *sanode; struct pim_msdp_sa *sa; char src_str[INET_ADDRSTRLEN]; char grp_str[INET_ADDRSTRLEN]; char rp_str[INET_ADDRSTRLEN]; char timebuf[PIM_MSDP_UPTIME_STRLEN]; char spt_str[8]; char local_str[8]; int64_t now; json_object *json = NULL; json_object *json_group = NULL; json_object *json_row = NULL; if (uj) { json = json_object_new_object(); } else { vty_out(vty, "Source Group RP Local SPT Uptime\n"); } for (ALL_LIST_ELEMENTS_RO(pim->msdp.sa_list, sanode, sa)) { now = pim_time_monotonic_sec(); pim_time_uptime(timebuf, sizeof(timebuf), now - sa->uptime); pim_inet4_dump("", sa->sg.src, src_str, sizeof(src_str)); pim_inet4_dump("", sa->sg.grp, grp_str, sizeof(grp_str)); if (sa->flags & PIM_MSDP_SAF_PEER) { pim_inet4_dump("", sa->rp, rp_str, sizeof(rp_str)); if (sa->up) { strlcpy(spt_str, "yes", sizeof(spt_str)); } else { strlcpy(spt_str, "no", sizeof(spt_str)); } } else { strlcpy(rp_str, "-", sizeof(rp_str)); strlcpy(spt_str, "-", sizeof(spt_str)); } if (sa->flags & PIM_MSDP_SAF_LOCAL) { strlcpy(local_str, "yes", sizeof(local_str)); } else { strlcpy(local_str, "no", sizeof(local_str)); } if (uj) { json_object_object_get_ex(json, grp_str, &json_group); if (!json_group) { json_group = json_object_new_object(); json_object_object_add(json, grp_str, json_group); } json_row = json_object_new_object(); json_object_string_add(json_row, "source", src_str); json_object_string_add(json_row, "group", grp_str); json_object_string_add(json_row, "rp", rp_str); json_object_string_add(json_row, "local", local_str); json_object_string_add(json_row, "sptSetup", spt_str); json_object_string_add(json_row, "upTime", timebuf); json_object_object_add(json_group, src_str, json_row); } else { vty_out(vty, "%-15s %15s %15s %5c %3c %8s\n", src_str, grp_str, rp_str, local_str[0], spt_str[0], timebuf); } } if (uj) { vty_out(vty, "%s\n", json_object_to_json_string_ext( json, JSON_C_TO_STRING_PRETTY)); json_object_free(json); } } static void ip_msdp_show_sa_entry_detail(struct pim_msdp_sa *sa, const char *src_str, const char *grp_str, struct vty *vty, bool uj, json_object *json) { char rp_str[INET_ADDRSTRLEN]; char peer_str[INET_ADDRSTRLEN]; char timebuf[PIM_MSDP_UPTIME_STRLEN]; char spt_str[8]; char local_str[8]; char statetimer[PIM_MSDP_TIMER_STRLEN]; int64_t now; json_object *json_group = NULL; json_object *json_row = NULL; now = pim_time_monotonic_sec(); pim_time_uptime(timebuf, sizeof(timebuf), now - sa->uptime); if (sa->flags & PIM_MSDP_SAF_PEER) { pim_inet4_dump("", sa->rp, rp_str, sizeof(rp_str)); pim_inet4_dump("", sa->peer, peer_str, sizeof(peer_str)); if (sa->up) { strlcpy(spt_str, "yes", sizeof(spt_str)); } else { strlcpy(spt_str, "no", sizeof(spt_str)); } } else { strlcpy(rp_str, "-", sizeof(rp_str)); strlcpy(peer_str, "-", sizeof(peer_str)); strlcpy(spt_str, "-", sizeof(spt_str)); } if (sa->flags & PIM_MSDP_SAF_LOCAL) { strlcpy(local_str, "yes", sizeof(local_str)); } else { strlcpy(local_str, "no", sizeof(local_str)); } pim_time_timer_to_hhmmss(statetimer, sizeof(statetimer), sa->sa_state_timer); if (uj) { json_object_object_get_ex(json, grp_str, &json_group); if (!json_group) { json_group = json_object_new_object(); json_object_object_add(json, grp_str, json_group); } json_row = json_object_new_object(); json_object_string_add(json_row, "source", src_str); json_object_string_add(json_row, "group", grp_str); json_object_string_add(json_row, "rp", rp_str); json_object_string_add(json_row, "local", local_str); json_object_string_add(json_row, "sptSetup", spt_str); json_object_string_add(json_row, "upTime", timebuf); json_object_string_add(json_row, "stateTimer", statetimer); json_object_object_add(json_group, src_str, json_row); } else { vty_out(vty, "SA : %s\n", sa->sg_str); vty_out(vty, " RP : %s\n", rp_str); vty_out(vty, " Peer : %s\n", peer_str); vty_out(vty, " Local : %s\n", local_str); vty_out(vty, " SPT Setup : %s\n", spt_str); vty_out(vty, " Uptime : %s\n", timebuf); vty_out(vty, " State Timer : %s\n", statetimer); vty_out(vty, "\n"); } } static void ip_msdp_show_sa_detail(struct pim_instance *pim, struct vty *vty, bool uj) { struct listnode *sanode; struct pim_msdp_sa *sa; char src_str[INET_ADDRSTRLEN]; char grp_str[INET_ADDRSTRLEN]; json_object *json = NULL; if (uj) { json = json_object_new_object(); } for (ALL_LIST_ELEMENTS_RO(pim->msdp.sa_list, sanode, sa)) { pim_inet4_dump("", sa->sg.src, src_str, sizeof(src_str)); pim_inet4_dump("", sa->sg.grp, grp_str, sizeof(grp_str)); ip_msdp_show_sa_entry_detail(sa, src_str, grp_str, vty, uj, json); } if (uj) { vty_out(vty, "%s\n", json_object_to_json_string_ext( json, JSON_C_TO_STRING_PRETTY)); json_object_free(json); } } DEFUN (show_ip_msdp_sa_detail, show_ip_msdp_sa_detail_cmd, "show ip msdp [vrf NAME] sa detail [json]", SHOW_STR IP_STR MSDP_STR VRF_CMD_HELP_STR "MSDP active-source information\n" "Detailed output\n" JSON_STR) { bool uj = use_json(argc, argv); int idx = 2; struct vrf *vrf = pim_cmd_lookup_vrf(vty, argv, argc, &idx); if (!vrf) return CMD_WARNING; ip_msdp_show_sa_detail(vrf->info, vty, uj); return CMD_SUCCESS; } DEFUN (show_ip_msdp_sa_detail_vrf_all, show_ip_msdp_sa_detail_vrf_all_cmd, "show ip msdp vrf all sa detail [json]", SHOW_STR IP_STR MSDP_STR VRF_CMD_HELP_STR "MSDP active-source information\n" "Detailed output\n" JSON_STR) { bool uj = use_json(argc, argv); struct vrf *vrf; bool first = true; if (uj) vty_out(vty, "{ "); RB_FOREACH (vrf, vrf_name_head, &vrfs_by_name) { if (uj) { if (!first) vty_out(vty, ", "); vty_out(vty, " \"%s\": ", vrf->name); first = false; } else vty_out(vty, "VRF: %s\n", vrf->name); ip_msdp_show_sa_detail(vrf->info, vty, uj); } if (uj) vty_out(vty, "}\n"); return CMD_SUCCESS; } static void ip_msdp_show_sa_addr(struct pim_instance *pim, struct vty *vty, const char *addr, bool uj) { struct listnode *sanode; struct pim_msdp_sa *sa; char src_str[INET_ADDRSTRLEN]; char grp_str[INET_ADDRSTRLEN]; json_object *json = NULL; if (uj) { json = json_object_new_object(); } for (ALL_LIST_ELEMENTS_RO(pim->msdp.sa_list, sanode, sa)) { pim_inet4_dump("", sa->sg.src, src_str, sizeof(src_str)); pim_inet4_dump("", sa->sg.grp, grp_str, sizeof(grp_str)); if (!strcmp(addr, src_str) || !strcmp(addr, grp_str)) { ip_msdp_show_sa_entry_detail(sa, src_str, grp_str, vty, uj, json); } } if (uj) { vty_out(vty, "%s\n", json_object_to_json_string_ext( json, JSON_C_TO_STRING_PRETTY)); json_object_free(json); } } static void ip_msdp_show_sa_sg(struct pim_instance *pim, struct vty *vty, const char *src, const char *grp, bool uj) { struct listnode *sanode; struct pim_msdp_sa *sa; char src_str[INET_ADDRSTRLEN]; char grp_str[INET_ADDRSTRLEN]; json_object *json = NULL; if (uj) { json = json_object_new_object(); } for (ALL_LIST_ELEMENTS_RO(pim->msdp.sa_list, sanode, sa)) { pim_inet4_dump("", sa->sg.src, src_str, sizeof(src_str)); pim_inet4_dump("", sa->sg.grp, grp_str, sizeof(grp_str)); if (!strcmp(src, src_str) && !strcmp(grp, grp_str)) { ip_msdp_show_sa_entry_detail(sa, src_str, grp_str, vty, uj, json); } } if (uj) { vty_out(vty, "%s\n", json_object_to_json_string_ext( json, JSON_C_TO_STRING_PRETTY)); json_object_free(json); } } DEFUN (show_ip_msdp_sa_sg, show_ip_msdp_sa_sg_cmd, "show ip msdp [vrf NAME] sa [A.B.C.D [A.B.C.D]] [json]", SHOW_STR IP_STR MSDP_STR VRF_CMD_HELP_STR "MSDP active-source information\n" "source or group ip\n" "group ip\n" JSON_STR) { bool uj = use_json(argc, argv); struct vrf *vrf; int idx = 2; vrf = pim_cmd_lookup_vrf(vty, argv, argc, &idx); if (!vrf) return CMD_WARNING; char *src_ip = argv_find(argv, argc, "A.B.C.D", &idx) ? argv[idx++]->arg : NULL; char *grp_ip = idx < argc && argv_find(argv, argc, "A.B.C.D", &idx) ? argv[idx]->arg : NULL; if (src_ip && grp_ip) ip_msdp_show_sa_sg(vrf->info, vty, src_ip, grp_ip, uj); else if (src_ip) ip_msdp_show_sa_addr(vrf->info, vty, src_ip, uj); else ip_msdp_show_sa(vrf->info, vty, uj); return CMD_SUCCESS; } DEFUN (show_ip_msdp_sa_sg_vrf_all, show_ip_msdp_sa_sg_vrf_all_cmd, "show ip msdp vrf all sa [A.B.C.D [A.B.C.D]] [json]", SHOW_STR IP_STR MSDP_STR VRF_CMD_HELP_STR "MSDP active-source information\n" "source or group ip\n" "group ip\n" JSON_STR) { bool uj = use_json(argc, argv); struct vrf *vrf; bool first = true; int idx = 2; char *src_ip = argv_find(argv, argc, "A.B.C.D", &idx) ? argv[idx++]->arg : NULL; char *grp_ip = idx < argc && argv_find(argv, argc, "A.B.C.D", &idx) ? argv[idx]->arg : NULL; if (uj) vty_out(vty, "{ "); RB_FOREACH (vrf, vrf_name_head, &vrfs_by_name) { if (uj) { if (!first) vty_out(vty, ", "); vty_out(vty, " \"%s\": ", vrf->name); first = false; } else vty_out(vty, "VRF: %s\n", vrf->name); if (src_ip && grp_ip) ip_msdp_show_sa_sg(vrf->info, vty, src_ip, grp_ip, uj); else if (src_ip) ip_msdp_show_sa_addr(vrf->info, vty, src_ip, uj); else ip_msdp_show_sa(vrf->info, vty, uj); } if (uj) vty_out(vty, "}\n"); return CMD_SUCCESS; } struct pim_sg_cache_walk_data { struct vty *vty; json_object *json; json_object *json_group; struct in_addr addr; bool addr_match; }; static void pim_show_vxlan_sg_entry(struct pim_vxlan_sg *vxlan_sg, struct pim_sg_cache_walk_data *cwd) { struct vty *vty = cwd->vty; json_object *json = cwd->json; char src_str[INET_ADDRSTRLEN]; char grp_str[INET_ADDRSTRLEN]; json_object *json_row; bool installed = (vxlan_sg->up) ? true : false; const char *iif_name = vxlan_sg->iif?vxlan_sg->iif->name:"-"; const char *oif_name; if (pim_vxlan_is_orig_mroute(vxlan_sg)) oif_name = vxlan_sg->orig_oif?vxlan_sg->orig_oif->name:""; else oif_name = vxlan_sg->term_oif?vxlan_sg->term_oif->name:""; if (cwd->addr_match && (vxlan_sg->sg.src.s_addr != cwd->addr.s_addr) && (vxlan_sg->sg.grp.s_addr != cwd->addr.s_addr)) { return; } pim_inet4_dump("", vxlan_sg->sg.src, src_str, sizeof(src_str)); pim_inet4_dump("", vxlan_sg->sg.grp, grp_str, sizeof(grp_str)); if (json) { json_object_object_get_ex(json, grp_str, &cwd->json_group); if (!cwd->json_group) { cwd->json_group = json_object_new_object(); json_object_object_add(json, grp_str, cwd->json_group); } json_row = json_object_new_object(); json_object_string_add(json_row, "source", src_str); json_object_string_add(json_row, "group", grp_str); json_object_string_add(json_row, "input", iif_name); json_object_string_add(json_row, "output", oif_name); if (installed) json_object_boolean_true_add(json_row, "installed"); else json_object_boolean_false_add(json_row, "installed"); json_object_object_add(cwd->json_group, src_str, json_row); } else { vty_out(vty, "%-15s %-15s %-15s %-15s %-5s\n", src_str, grp_str, iif_name, oif_name, installed?"I":""); } } static void pim_show_vxlan_sg_hash_entry(struct hash_backet *backet, void *arg) { pim_show_vxlan_sg_entry((struct pim_vxlan_sg *)backet->data, (struct pim_sg_cache_walk_data *)arg); } static void pim_show_vxlan_sg(struct pim_instance *pim, struct vty *vty, bool uj) { json_object *json = NULL; struct pim_sg_cache_walk_data cwd; if (uj) { json = json_object_new_object(); } else { vty_out(vty, "Codes: I -> installed\n"); vty_out(vty, "Source Group Input Output Flags\n"); } memset(&cwd, 0, sizeof(cwd)); cwd.vty = vty; cwd.json = json; hash_iterate(pim->vxlan.sg_hash, pim_show_vxlan_sg_hash_entry, &cwd); if (uj) { vty_out(vty, "%s\n", json_object_to_json_string_ext( json, JSON_C_TO_STRING_PRETTY)); json_object_free(json); } } static void pim_show_vxlan_sg_match_addr(struct pim_instance *pim, struct vty *vty, char *addr_str, bool uj) { json_object *json = NULL; struct pim_sg_cache_walk_data cwd; int result = 0; memset(&cwd, 0, sizeof(cwd)); result = inet_pton(AF_INET, addr_str, &cwd.addr); if (result <= 0) { vty_out(vty, "Bad address %s: errno=%d: %s\n", addr_str, errno, safe_strerror(errno)); return; } if (uj) { json = json_object_new_object(); } else { vty_out(vty, "Codes: I -> installed\n"); vty_out(vty, "Source Group Input Output Flags\n"); } cwd.vty = vty; cwd.json = json; cwd.addr_match = true; hash_iterate(pim->vxlan.sg_hash, pim_show_vxlan_sg_hash_entry, &cwd); if (uj) { vty_out(vty, "%s\n", json_object_to_json_string_ext( json, JSON_C_TO_STRING_PRETTY)); json_object_free(json); } } static void pim_show_vxlan_sg_one(struct pim_instance *pim, struct vty *vty, char *src_str, char *grp_str, bool uj) { json_object *json = NULL; struct prefix_sg sg; int result = 0; struct pim_vxlan_sg *vxlan_sg; const char *iif_name; bool installed; const char *oif_name; result = inet_pton(AF_INET, src_str, &sg.src); if (result <= 0) { vty_out(vty, "Bad src address %s: errno=%d: %s\n", src_str, errno, safe_strerror(errno)); return; } result = inet_pton(AF_INET, grp_str, &sg.grp); if (result <= 0) { vty_out(vty, "Bad grp address %s: errno=%d: %s\n", grp_str, errno, safe_strerror(errno)); return; } sg.family = AF_INET; sg.prefixlen = IPV4_MAX_BITLEN; if (uj) json = json_object_new_object(); vxlan_sg = pim_vxlan_sg_find(pim, &sg); if (vxlan_sg) { installed = (vxlan_sg->up) ? true : false; iif_name = vxlan_sg->iif?vxlan_sg->iif->name:"-"; if (pim_vxlan_is_orig_mroute(vxlan_sg)) oif_name = vxlan_sg->orig_oif?vxlan_sg->orig_oif->name:""; else oif_name = vxlan_sg->term_oif?vxlan_sg->term_oif->name:""; if (uj) { json_object_string_add(json, "source", src_str); json_object_string_add(json, "group", grp_str); json_object_string_add(json, "input", iif_name); json_object_string_add(json, "output", oif_name); if (installed) json_object_boolean_true_add(json, "installed"); else json_object_boolean_false_add(json, "installed"); } else { vty_out(vty, "SG : %s\n", vxlan_sg->sg_str); vty_out(vty, " Input : %s\n", iif_name); vty_out(vty, " Output : %s\n", oif_name); vty_out(vty, " installed : %s\n", installed?"yes":"no"); } } if (uj) { vty_out(vty, "%s\n", json_object_to_json_string_ext( json, JSON_C_TO_STRING_PRETTY)); json_object_free(json); } } DEFUN (show_ip_pim_vxlan_sg, show_ip_pim_vxlan_sg_cmd, "show ip pim [vrf NAME] vxlan-groups [A.B.C.D [A.B.C.D]] [json]", SHOW_STR IP_STR PIM_STR VRF_CMD_HELP_STR "VxLAN BUM groups\n" "source or group ip\n" "group ip\n" JSON_STR) { bool uj = use_json(argc, argv); struct vrf *vrf; int idx = 2; vrf = pim_cmd_lookup_vrf(vty, argv, argc, &idx); if (!vrf) return CMD_WARNING; char *src_ip = argv_find(argv, argc, "A.B.C.D", &idx) ? argv[idx++]->arg:NULL; char *grp_ip = idx < argc && argv_find(argv, argc, "A.B.C.D", &idx) ? argv[idx]->arg:NULL; if (src_ip && grp_ip) pim_show_vxlan_sg_one(vrf->info, vty, src_ip, grp_ip, uj); else if (src_ip) pim_show_vxlan_sg_match_addr(vrf->info, vty, src_ip, uj); else pim_show_vxlan_sg(vrf->info, vty, uj); return CMD_SUCCESS; } static void pim_show_vxlan_sg_work(struct pim_instance *pim, struct vty *vty, bool uj) { json_object *json = NULL; struct pim_sg_cache_walk_data cwd; struct listnode *node; struct pim_vxlan_sg *vxlan_sg; if (uj) { json = json_object_new_object(); } else { vty_out(vty, "Codes: I -> installed\n"); vty_out(vty, "Source Group Input Flags\n"); } memset(&cwd, 0, sizeof(cwd)); cwd.vty = vty; cwd.json = json; for (ALL_LIST_ELEMENTS_RO(pim_vxlan_p->work_list, node, vxlan_sg)) pim_show_vxlan_sg_entry(vxlan_sg, &cwd); if (uj) { vty_out(vty, "%s\n", json_object_to_json_string_ext( json, JSON_C_TO_STRING_PRETTY)); json_object_free(json); } } DEFUN_HIDDEN (show_ip_pim_vxlan_sg_work, show_ip_pim_vxlan_sg_work_cmd, "show ip pim [vrf NAME] vxlan-work [json]", SHOW_STR IP_STR PIM_STR VRF_CMD_HELP_STR "VxLAN work list\n" JSON_STR) { bool uj = use_json(argc, argv); struct vrf *vrf; int idx = 2; vrf = pim_cmd_lookup_vrf(vty, argv, argc, &idx); if (!vrf) return CMD_WARNING; pim_show_vxlan_sg_work(vrf->info, vty, uj); return CMD_SUCCESS; } DEFUN_HIDDEN (no_ip_pim_mlag, no_ip_pim_mlag_cmd, "no ip pim mlag", NO_STR IP_STR PIM_STR "MLAG\n") { struct in_addr addr; addr.s_addr = 0; pim_vxlan_mlag_update(true/*mlag_enable*/, false/*peer_state*/, PIM_VXLAN_MLAG_ROLE_SECONDARY, NULL/*peerlink*/, &addr); return CMD_SUCCESS; } DEFUN_HIDDEN (ip_pim_mlag, ip_pim_mlag_cmd, "ip pim mlag INTERFACE role [primary|secondary] state [up|down] addr A.B.C.D", IP_STR PIM_STR "MLAG\n" "peerlink sub interface\n" "MLAG role\n" "MLAG role primary\n" "MLAG role secondary\n" "peer session state\n" "peer session state up\n" "peer session state down\n" "configure PIP\n" "unique ip address\n") { struct interface *ifp; const char *peerlink; uint32_t role; int idx; bool peer_state; int result; struct in_addr reg_addr; idx = 3; peerlink = argv[idx]->arg; ifp = if_lookup_by_name(peerlink, VRF_DEFAULT); if (!ifp) { vty_out(vty, "No such interface name %s\n", peerlink); return CMD_WARNING; } idx += 2; if (!strcmp(argv[idx]->arg, "primary")) { role = PIM_VXLAN_MLAG_ROLE_PRIMARY; } else if (!strcmp(argv[idx]->arg, "secondary")) { role = PIM_VXLAN_MLAG_ROLE_SECONDARY; } else { vty_out(vty, "unknown MLAG role %s\n", argv[idx]->arg); return CMD_WARNING; } idx += 2; if (!strcmp(argv[idx]->arg, "up")) { peer_state = true; } else if (strcmp(argv[idx]->arg, "down")) { peer_state = false; } else { vty_out(vty, "unknown MLAG state %s\n", argv[idx]->arg); return CMD_WARNING; } idx += 2; result = inet_pton(AF_INET, argv[idx]->arg, ®_addr); if (result <= 0) { vty_out(vty, "%% Bad reg address %s: errno=%d: %s\n", argv[idx]->arg, errno, safe_strerror(errno)); return CMD_WARNING_CONFIG_FAILED; } pim_vxlan_mlag_update(true, peer_state, role, ifp, ®_addr); return CMD_SUCCESS; } void pim_cmd_init(void) { install_node(&interface_node, pim_interface_config_write); /* INTERFACE_NODE */ if_cmd_init(); install_node(&debug_node, pim_debug_config_write); install_element(ENABLE_NODE, &pim_test_sg_keepalive_cmd); install_element(CONFIG_NODE, &ip_pim_rp_cmd); install_element(VRF_NODE, &ip_pim_rp_cmd); install_element(CONFIG_NODE, &no_ip_pim_rp_cmd); install_element(VRF_NODE, &no_ip_pim_rp_cmd); install_element(CONFIG_NODE, &ip_pim_rp_prefix_list_cmd); install_element(VRF_NODE, &ip_pim_rp_prefix_list_cmd); install_element(CONFIG_NODE, &no_ip_pim_rp_prefix_list_cmd); install_element(VRF_NODE, &no_ip_pim_rp_prefix_list_cmd); install_element(CONFIG_NODE, &no_ip_pim_ssm_prefix_list_cmd); install_element(VRF_NODE, &no_ip_pim_ssm_prefix_list_cmd); install_element(CONFIG_NODE, &no_ip_pim_ssm_prefix_list_name_cmd); install_element(VRF_NODE, &no_ip_pim_ssm_prefix_list_name_cmd); install_element(CONFIG_NODE, &ip_pim_ssm_prefix_list_cmd); install_element(VRF_NODE, &ip_pim_ssm_prefix_list_cmd); install_element(CONFIG_NODE, &ip_pim_register_suppress_cmd); install_element(VRF_NODE, &ip_pim_register_suppress_cmd); install_element(CONFIG_NODE, &no_ip_pim_register_suppress_cmd); install_element(VRF_NODE, &no_ip_pim_register_suppress_cmd); install_element(CONFIG_NODE, &ip_pim_spt_switchover_infinity_cmd); install_element(VRF_NODE, &ip_pim_spt_switchover_infinity_cmd); install_element(CONFIG_NODE, &ip_pim_spt_switchover_infinity_plist_cmd); install_element(VRF_NODE, &ip_pim_spt_switchover_infinity_plist_cmd); install_element(CONFIG_NODE, &no_ip_pim_spt_switchover_infinity_cmd); install_element(VRF_NODE, &no_ip_pim_spt_switchover_infinity_cmd); install_element(CONFIG_NODE, &no_ip_pim_spt_switchover_infinity_plist_cmd); install_element(VRF_NODE, &no_ip_pim_spt_switchover_infinity_plist_cmd); install_element(CONFIG_NODE, &ip_pim_joinprune_time_cmd); install_element(VRF_NODE, &ip_pim_joinprune_time_cmd); install_element(CONFIG_NODE, &no_ip_pim_joinprune_time_cmd); install_element(VRF_NODE, &no_ip_pim_joinprune_time_cmd); install_element(CONFIG_NODE, &ip_pim_keep_alive_cmd); install_element(VRF_NODE, &ip_pim_keep_alive_cmd); install_element(CONFIG_NODE, &ip_pim_rp_keep_alive_cmd); install_element(VRF_NODE, &ip_pim_rp_keep_alive_cmd); install_element(CONFIG_NODE, &no_ip_pim_keep_alive_cmd); install_element(VRF_NODE, &no_ip_pim_keep_alive_cmd); install_element(CONFIG_NODE, &no_ip_pim_rp_keep_alive_cmd); install_element(VRF_NODE, &no_ip_pim_rp_keep_alive_cmd); install_element(CONFIG_NODE, &ip_pim_packets_cmd); install_element(VRF_NODE, &ip_pim_packets_cmd); install_element(CONFIG_NODE, &no_ip_pim_packets_cmd); install_element(VRF_NODE, &no_ip_pim_packets_cmd); install_element(CONFIG_NODE, &ip_pim_v6_secondary_cmd); install_element(VRF_NODE, &ip_pim_v6_secondary_cmd); install_element(CONFIG_NODE, &no_ip_pim_v6_secondary_cmd); install_element(VRF_NODE, &no_ip_pim_v6_secondary_cmd); install_element(CONFIG_NODE, &ip_ssmpingd_cmd); install_element(VRF_NODE, &ip_ssmpingd_cmd); install_element(CONFIG_NODE, &no_ip_ssmpingd_cmd); install_element(VRF_NODE, &no_ip_ssmpingd_cmd); install_element(CONFIG_NODE, &ip_msdp_peer_cmd); install_element(VRF_NODE, &ip_msdp_peer_cmd); install_element(CONFIG_NODE, &no_ip_msdp_peer_cmd); install_element(VRF_NODE, &no_ip_msdp_peer_cmd); install_element(CONFIG_NODE, &ip_pim_ecmp_cmd); install_element(VRF_NODE, &ip_pim_ecmp_cmd); install_element(CONFIG_NODE, &no_ip_pim_ecmp_cmd); install_element(VRF_NODE, &no_ip_pim_ecmp_cmd); install_element(CONFIG_NODE, &ip_pim_ecmp_rebalance_cmd); install_element(VRF_NODE, &ip_pim_ecmp_rebalance_cmd); install_element(CONFIG_NODE, &no_ip_pim_ecmp_rebalance_cmd); install_element(VRF_NODE, &no_ip_pim_ecmp_rebalance_cmd); install_element(CONFIG_NODE, &ip_pim_mlag_cmd); install_element(CONFIG_NODE, &no_ip_pim_mlag_cmd); install_element(INTERFACE_NODE, &interface_ip_igmp_cmd); install_element(INTERFACE_NODE, &interface_no_ip_igmp_cmd); install_element(INTERFACE_NODE, &interface_ip_igmp_join_cmd); install_element(INTERFACE_NODE, &interface_no_ip_igmp_join_cmd); install_element(INTERFACE_NODE, &interface_ip_igmp_version_cmd); install_element(INTERFACE_NODE, &interface_no_ip_igmp_version_cmd); install_element(INTERFACE_NODE, &interface_ip_igmp_query_interval_cmd); install_element(INTERFACE_NODE, &interface_no_ip_igmp_query_interval_cmd); install_element(INTERFACE_NODE, &interface_ip_igmp_query_max_response_time_cmd); install_element(INTERFACE_NODE, &interface_no_ip_igmp_query_max_response_time_cmd); install_element(INTERFACE_NODE, &interface_ip_igmp_query_max_response_time_dsec_cmd); install_element(INTERFACE_NODE, &interface_no_ip_igmp_query_max_response_time_dsec_cmd); install_element(INTERFACE_NODE, &interface_ip_igmp_last_member_query_count_cmd); install_element(INTERFACE_NODE, &interface_no_ip_igmp_last_member_query_count_cmd); install_element(INTERFACE_NODE, &interface_ip_igmp_last_member_query_interval_cmd); install_element(INTERFACE_NODE, &interface_no_ip_igmp_last_member_query_interval_cmd); install_element(INTERFACE_NODE, &interface_ip_pim_activeactive_cmd); install_element(INTERFACE_NODE, &interface_ip_pim_ssm_cmd); install_element(INTERFACE_NODE, &interface_no_ip_pim_ssm_cmd); install_element(INTERFACE_NODE, &interface_ip_pim_sm_cmd); install_element(INTERFACE_NODE, &interface_no_ip_pim_sm_cmd); install_element(INTERFACE_NODE, &interface_ip_pim_cmd); install_element(INTERFACE_NODE, &interface_no_ip_pim_cmd); install_element(INTERFACE_NODE, &interface_ip_pim_drprio_cmd); install_element(INTERFACE_NODE, &interface_no_ip_pim_drprio_cmd); install_element(INTERFACE_NODE, &interface_ip_pim_hello_cmd); install_element(INTERFACE_NODE, &interface_no_ip_pim_hello_cmd); install_element(INTERFACE_NODE, &interface_ip_pim_boundary_oil_cmd); install_element(INTERFACE_NODE, &interface_no_ip_pim_boundary_oil_cmd); install_element(INTERFACE_NODE, &interface_ip_igmp_query_generate_cmd); // Static mroutes NEB install_element(INTERFACE_NODE, &interface_ip_mroute_cmd); install_element(INTERFACE_NODE, &interface_ip_mroute_source_cmd); install_element(INTERFACE_NODE, &interface_no_ip_mroute_cmd); install_element(INTERFACE_NODE, &interface_no_ip_mroute_source_cmd); install_element(VIEW_NODE, &show_ip_igmp_interface_cmd); install_element(VIEW_NODE, &show_ip_igmp_interface_vrf_all_cmd); install_element(VIEW_NODE, &show_ip_igmp_join_cmd); install_element(VIEW_NODE, &show_ip_igmp_join_vrf_all_cmd); install_element(VIEW_NODE, &show_ip_igmp_groups_cmd); install_element(VIEW_NODE, &show_ip_igmp_groups_vrf_all_cmd); install_element(VIEW_NODE, &show_ip_igmp_groups_retransmissions_cmd); install_element(VIEW_NODE, &show_ip_igmp_sources_cmd); install_element(VIEW_NODE, &show_ip_igmp_sources_retransmissions_cmd); install_element(VIEW_NODE, &show_ip_igmp_statistics_cmd); install_element(VIEW_NODE, &show_ip_pim_assert_cmd); install_element(VIEW_NODE, &show_ip_pim_assert_internal_cmd); install_element(VIEW_NODE, &show_ip_pim_assert_metric_cmd); install_element(VIEW_NODE, &show_ip_pim_assert_winner_metric_cmd); install_element(VIEW_NODE, &show_ip_pim_interface_traffic_cmd); install_element(VIEW_NODE, &show_ip_pim_interface_cmd); install_element(VIEW_NODE, &show_ip_pim_interface_vrf_all_cmd); install_element(VIEW_NODE, &show_ip_pim_join_cmd); install_element(VIEW_NODE, &show_ip_pim_join_vrf_all_cmd); install_element(VIEW_NODE, &show_ip_pim_local_membership_cmd); install_element(VIEW_NODE, &show_ip_pim_neighbor_cmd); install_element(VIEW_NODE, &show_ip_pim_neighbor_vrf_all_cmd); install_element(VIEW_NODE, &show_ip_pim_rpf_cmd); install_element(VIEW_NODE, &show_ip_pim_rpf_vrf_all_cmd); install_element(VIEW_NODE, &show_ip_pim_secondary_cmd); install_element(VIEW_NODE, &show_ip_pim_state_cmd); install_element(VIEW_NODE, &show_ip_pim_state_vrf_all_cmd); install_element(VIEW_NODE, &show_ip_pim_upstream_cmd); install_element(VIEW_NODE, &show_ip_pim_upstream_vrf_all_cmd); install_element(VIEW_NODE, &show_ip_pim_upstream_join_desired_cmd); install_element(VIEW_NODE, &show_ip_pim_upstream_rpf_cmd); install_element(VIEW_NODE, &show_ip_pim_rp_cmd); install_element(VIEW_NODE, &show_ip_pim_rp_vrf_all_cmd); install_element(VIEW_NODE, &show_ip_pim_bsr_cmd); install_element(VIEW_NODE, &show_ip_multicast_cmd); install_element(VIEW_NODE, &show_ip_multicast_vrf_all_cmd); install_element(VIEW_NODE, &show_ip_mroute_cmd); install_element(VIEW_NODE, &show_ip_mroute_vrf_all_cmd); install_element(VIEW_NODE, &show_ip_mroute_count_cmd); install_element(VIEW_NODE, &show_ip_mroute_count_vrf_all_cmd); install_element(VIEW_NODE, &show_ip_mroute_summary_cmd); install_element(VIEW_NODE, &show_ip_mroute_summary_vrf_all_cmd); install_element(VIEW_NODE, &show_ip_rib_cmd); install_element(VIEW_NODE, &show_ip_ssmpingd_cmd); install_element(VIEW_NODE, &show_debugging_pim_cmd); install_element(VIEW_NODE, &show_ip_pim_nexthop_cmd); install_element(VIEW_NODE, &show_ip_pim_nexthop_lookup_cmd); install_element(VIEW_NODE, &show_ip_pim_bsrp_cmd); install_element(VIEW_NODE, &show_ip_pim_bsm_db_cmd); install_element(VIEW_NODE, &show_ip_pim_statistics_cmd); install_element(ENABLE_NODE, &clear_ip_mroute_count_cmd); install_element(ENABLE_NODE, &clear_ip_interfaces_cmd); install_element(ENABLE_NODE, &clear_ip_igmp_interfaces_cmd); install_element(ENABLE_NODE, &clear_ip_mroute_cmd); install_element(ENABLE_NODE, &clear_ip_pim_interfaces_cmd); install_element(ENABLE_NODE, &clear_ip_pim_interface_traffic_cmd); install_element(ENABLE_NODE, &clear_ip_pim_oil_cmd); install_element(ENABLE_NODE, &clear_ip_pim_statistics_cmd); install_element(ENABLE_NODE, &debug_igmp_cmd); install_element(ENABLE_NODE, &no_debug_igmp_cmd); install_element(ENABLE_NODE, &debug_igmp_events_cmd); install_element(ENABLE_NODE, &no_debug_igmp_events_cmd); install_element(ENABLE_NODE, &debug_igmp_packets_cmd); install_element(ENABLE_NODE, &no_debug_igmp_packets_cmd); install_element(ENABLE_NODE, &debug_igmp_trace_cmd); install_element(ENABLE_NODE, &no_debug_igmp_trace_cmd); install_element(ENABLE_NODE, &debug_mroute_cmd); install_element(ENABLE_NODE, &debug_mroute_detail_cmd); install_element(ENABLE_NODE, &no_debug_mroute_cmd); install_element(ENABLE_NODE, &no_debug_mroute_detail_cmd); install_element(ENABLE_NODE, &debug_pim_static_cmd); install_element(ENABLE_NODE, &no_debug_pim_static_cmd); install_element(ENABLE_NODE, &debug_pim_cmd); install_element(ENABLE_NODE, &no_debug_pim_cmd); install_element(ENABLE_NODE, &debug_pim_nht_cmd); install_element(ENABLE_NODE, &no_debug_pim_nht_cmd); install_element(ENABLE_NODE, &debug_pim_nht_rp_cmd); install_element(ENABLE_NODE, &no_debug_pim_nht_rp_cmd); install_element(ENABLE_NODE, &debug_pim_events_cmd); install_element(ENABLE_NODE, &no_debug_pim_events_cmd); install_element(ENABLE_NODE, &debug_pim_packets_cmd); install_element(ENABLE_NODE, &no_debug_pim_packets_cmd); install_element(ENABLE_NODE, &debug_pim_packetdump_send_cmd); install_element(ENABLE_NODE, &no_debug_pim_packetdump_send_cmd); install_element(ENABLE_NODE, &debug_pim_packetdump_recv_cmd); install_element(ENABLE_NODE, &no_debug_pim_packetdump_recv_cmd); install_element(ENABLE_NODE, &debug_pim_trace_cmd); install_element(ENABLE_NODE, &no_debug_pim_trace_cmd); install_element(ENABLE_NODE, &debug_pim_trace_detail_cmd); install_element(ENABLE_NODE, &no_debug_pim_trace_detail_cmd); install_element(ENABLE_NODE, &debug_ssmpingd_cmd); install_element(ENABLE_NODE, &no_debug_ssmpingd_cmd); install_element(ENABLE_NODE, &debug_pim_zebra_cmd); install_element(ENABLE_NODE, &no_debug_pim_zebra_cmd); install_element(ENABLE_NODE, &debug_pim_vxlan_cmd); install_element(ENABLE_NODE, &no_debug_pim_vxlan_cmd); install_element(ENABLE_NODE, &debug_msdp_cmd); install_element(ENABLE_NODE, &no_debug_msdp_cmd); install_element(ENABLE_NODE, &debug_msdp_events_cmd); install_element(ENABLE_NODE, &no_debug_msdp_events_cmd); install_element(ENABLE_NODE, &debug_msdp_packets_cmd); install_element(ENABLE_NODE, &no_debug_msdp_packets_cmd); install_element(ENABLE_NODE, &debug_mtrace_cmd); install_element(ENABLE_NODE, &no_debug_mtrace_cmd); install_element(ENABLE_NODE, &debug_bsm_cmd); install_element(ENABLE_NODE, &no_debug_bsm_cmd); install_element(CONFIG_NODE, &debug_igmp_cmd); install_element(CONFIG_NODE, &no_debug_igmp_cmd); install_element(CONFIG_NODE, &debug_igmp_events_cmd); install_element(CONFIG_NODE, &no_debug_igmp_events_cmd); install_element(CONFIG_NODE, &debug_igmp_packets_cmd); install_element(CONFIG_NODE, &no_debug_igmp_packets_cmd); install_element(CONFIG_NODE, &debug_igmp_trace_cmd); install_element(CONFIG_NODE, &no_debug_igmp_trace_cmd); install_element(CONFIG_NODE, &debug_mroute_cmd); install_element(CONFIG_NODE, &debug_mroute_detail_cmd); install_element(CONFIG_NODE, &no_debug_mroute_cmd); install_element(CONFIG_NODE, &no_debug_mroute_detail_cmd); install_element(CONFIG_NODE, &debug_pim_static_cmd); install_element(CONFIG_NODE, &no_debug_pim_static_cmd); install_element(CONFIG_NODE, &debug_pim_cmd); install_element(CONFIG_NODE, &no_debug_pim_cmd); install_element(CONFIG_NODE, &debug_pim_nht_cmd); install_element(CONFIG_NODE, &no_debug_pim_nht_cmd); install_element(CONFIG_NODE, &debug_pim_nht_rp_cmd); install_element(CONFIG_NODE, &no_debug_pim_nht_rp_cmd); install_element(CONFIG_NODE, &debug_pim_events_cmd); install_element(CONFIG_NODE, &no_debug_pim_events_cmd); install_element(CONFIG_NODE, &debug_pim_packets_cmd); install_element(CONFIG_NODE, &no_debug_pim_packets_cmd); install_element(CONFIG_NODE, &debug_pim_trace_cmd); install_element(CONFIG_NODE, &no_debug_pim_trace_cmd); install_element(CONFIG_NODE, &debug_pim_trace_detail_cmd); install_element(CONFIG_NODE, &no_debug_pim_trace_detail_cmd); install_element(CONFIG_NODE, &debug_ssmpingd_cmd); install_element(CONFIG_NODE, &no_debug_ssmpingd_cmd); install_element(CONFIG_NODE, &debug_pim_zebra_cmd); install_element(CONFIG_NODE, &no_debug_pim_zebra_cmd); install_element(CONFIG_NODE, &debug_pim_vxlan_cmd); install_element(CONFIG_NODE, &no_debug_pim_vxlan_cmd); install_element(CONFIG_NODE, &debug_msdp_cmd); install_element(CONFIG_NODE, &no_debug_msdp_cmd); install_element(CONFIG_NODE, &debug_msdp_events_cmd); install_element(CONFIG_NODE, &no_debug_msdp_events_cmd); install_element(CONFIG_NODE, &debug_msdp_packets_cmd); install_element(CONFIG_NODE, &no_debug_msdp_packets_cmd); install_element(CONFIG_NODE, &debug_mtrace_cmd); install_element(CONFIG_NODE, &no_debug_mtrace_cmd); install_element(CONFIG_NODE, &debug_bsm_cmd); install_element(CONFIG_NODE, &no_debug_bsm_cmd); install_element(CONFIG_NODE, &ip_msdp_mesh_group_member_cmd); install_element(VRF_NODE, &ip_msdp_mesh_group_member_cmd); install_element(CONFIG_NODE, &no_ip_msdp_mesh_group_member_cmd); install_element(VRF_NODE, &no_ip_msdp_mesh_group_member_cmd); install_element(CONFIG_NODE, &ip_msdp_mesh_group_source_cmd); install_element(VRF_NODE, &ip_msdp_mesh_group_source_cmd); install_element(CONFIG_NODE, &no_ip_msdp_mesh_group_source_cmd); install_element(VRF_NODE, &no_ip_msdp_mesh_group_source_cmd); install_element(VIEW_NODE, &show_ip_msdp_peer_detail_cmd); install_element(VIEW_NODE, &show_ip_msdp_peer_detail_vrf_all_cmd); install_element(VIEW_NODE, &show_ip_msdp_sa_detail_cmd); install_element(VIEW_NODE, &show_ip_msdp_sa_detail_vrf_all_cmd); install_element(VIEW_NODE, &show_ip_msdp_sa_sg_cmd); install_element(VIEW_NODE, &show_ip_msdp_sa_sg_vrf_all_cmd); install_element(VIEW_NODE, &show_ip_msdp_mesh_group_cmd); install_element(VIEW_NODE, &show_ip_msdp_mesh_group_vrf_all_cmd); install_element(VIEW_NODE, &show_ip_pim_ssm_range_cmd); install_element(VIEW_NODE, &show_ip_pim_group_type_cmd); install_element(VIEW_NODE, &show_ip_pim_vxlan_sg_cmd); install_element(VIEW_NODE, &show_ip_pim_vxlan_sg_work_cmd); install_element(INTERFACE_NODE, &interface_pim_use_source_cmd); install_element(INTERFACE_NODE, &interface_no_pim_use_source_cmd); /* Install BSM command */ install_element(INTERFACE_NODE, &ip_pim_bsm_cmd); install_element(INTERFACE_NODE, &no_ip_pim_bsm_cmd); install_element(INTERFACE_NODE, &ip_pim_ucast_bsm_cmd); install_element(INTERFACE_NODE, &no_ip_pim_ucast_bsm_cmd); /* Install BFD command */ install_element(INTERFACE_NODE, &ip_pim_bfd_cmd); install_element(INTERFACE_NODE, &ip_pim_bfd_param_cmd); install_element(INTERFACE_NODE, &no_ip_pim_bfd_cmd); #if HAVE_BFDD == 0 install_element(INTERFACE_NODE, &no_ip_pim_bfd_param_cmd); #endif /* !HAVE_BFDD */ } frr-7.2.1/pimd/pim_cmd.h0000644000000000000000000001203213610377563011724 00000000000000/* * PIM for Quagga * Copyright (C) 2008 Everton da Silva Marques * * This program is free software; you can redistribute it and/or modify * it under the terms of the 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; see the file COPYING; if not, write to the Free Software * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA */ #ifndef PIM_CMD_H #define PIM_CMD_H #define PIM_STR "PIM information\n" #define IGMP_STR "IGMP information\n" #define IGMP_GROUP_STR "IGMP groups information\n" #define IGMP_SOURCE_STR "IGMP sources information\n" #define CONF_SSMPINGD_STR "Enable ssmpingd operation\n" #define SHOW_SSMPINGD_STR "ssmpingd operation\n" #define IFACE_PIM_STR "Enable PIM SSM operation\n" #define IFACE_PIM_SM_STR "Enable PIM SM operation\n" #define IFACE_PIM_HELLO_STR "Hello Interval\n" #define IFACE_PIM_HELLO_TIME_STR "Time in seconds for Hello Interval\n" #define IFACE_PIM_HELLO_HOLD_STR "Time in seconds for Hold Interval\n" #define IFACE_IGMP_STR "Enable IGMP operation\n" #define IFACE_IGMP_QUERY_INTERVAL_STR "IGMP host query interval\n" #define IFACE_IGMP_QUERY_MAX_RESPONSE_TIME_STR "IGMP max query response value (seconds)\n" #define IFACE_IGMP_QUERY_MAX_RESPONSE_TIME_DSEC_STR "IGMP max query response value (deciseconds)\n" #define IFACE_IGMP_LAST_MEMBER_QUERY_INTERVAL_STR "IGMP last member query interval\n" #define IFACE_IGMP_LAST_MEMBER_QUERY_COUNT_STR "IGMP last member query count\n" #define DEBUG_IGMP_STR "IGMP protocol activity\n" #define DEBUG_IGMP_EVENTS_STR "IGMP protocol events\n" #define DEBUG_IGMP_PACKETS_STR "IGMP protocol packets\n" #define DEBUG_IGMP_TRACE_STR "IGMP internal daemon activity\n" #define DEBUG_MROUTE_STR "PIM interaction with kernel MFC cache\n" #define DEBUG_STATIC_STR "PIM Static Multicast Route activity\n" #define DEBUG_PIM_STR "PIM protocol activity\n" #define DEBUG_PIM_EVENTS_STR "PIM protocol events\n" #define DEBUG_PIM_PACKETS_STR "PIM protocol packets\n" #define DEBUG_PIM_HELLO_PACKETS_STR "PIM Hello protocol packets\n" #define DEBUG_PIM_J_P_PACKETS_STR "PIM Join/Prune protocol packets\n" #define DEBUG_PIM_PIM_REG_PACKETS_STR "PIM Register/Reg-Stop protocol packets\n" #define DEBUG_PIM_PACKETDUMP_STR "PIM packet dump\n" #define DEBUG_PIM_PACKETDUMP_SEND_STR "Dump sent packets\n" #define DEBUG_PIM_PACKETDUMP_RECV_STR "Dump received packets\n" #define DEBUG_PIM_TRACE_STR "PIM internal daemon activity\n" #define DEBUG_PIM_ZEBRA_STR "ZEBRA protocol activity\n" #define DEBUG_PIM_VXLAN_STR "PIM VxLAN events\n" #define DEBUG_SSMPINGD_STR "ssmpingd activity\n" #define CLEAR_IP_IGMP_STR "IGMP clear commands\n" #define CLEAR_IP_PIM_STR "PIM clear commands\n" #define MROUTE_STR "IP multicast routing table\n" #define RIB_STR "IP unicast routing table\n" #define CFG_MSDP_STR "Configure multicast source discovery protocol\n" #define MSDP_STR "MSDP information\n" #define DEBUG_MSDP_STR "MSDP protocol activity\n" #define DEBUG_MSDP_EVENTS_STR "MSDP protocol events\n" #define DEBUG_MSDP_INTERNAL_STR "MSDP protocol internal\n" #define DEBUG_MSDP_PACKETS_STR "MSDP protocol packets\n" #define DEBUG_MTRACE_STR "Mtrace protocol activity\n" #define DEBUG_PIM_BSM_STR "BSR message processing activity\n" void pim_cmd_init(void); /* * Special Macro to allow us to get the correct pim_instance; */ #define PIM_DECLVAR_CONTEXT(A, B) \ struct vrf *A = VTY_GET_CONTEXT(vrf); \ struct pim_instance *B = \ (vrf) ? vrf->info : pim_get_pim_instance(VRF_DEFAULT); \ vrf = (vrf) ? vrf : pim->vrf; #endif /* PIM_CMD_H */ frr-7.2.1/pimd/pim_errors.c0000644000000000000000000000300313610377563012466 00000000000000/* * PIM-specific error messages. * Copyright (C) 2018 Cumulus Networks, Inc. * Donald Sharp * * This program is free software; you can redistribute it and/or modify it * under the terms of the 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; see the file COPYING; if not, write to the Free Software * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA */ #include #include "lib/ferr.h" #include "pim_errors.h" /* clang-format off */ static struct log_ref ferr_pim_err[] = { { .code = EC_PIM_MSDP_PACKET, .title = "PIM MSDP Packet Error", .description = "PIM has received a packet from a peer that does not correctly decode", .suggestion = "Check MSDP peer and ensure it is correctly working" }, { .code = EC_PIM_CONFIG, .title = "PIM Configuration Error", .description = "PIM has detected a configuration error", .suggestion = "Ensure the configuration is correct and apply correct configuration" }, { .code = END_FERR, } }; /* clang-format on */ void pim_error_init(void) { log_ref_add(ferr_pim_err); } frr-7.2.1/pimd/pim_errors.h0000644000000000000000000000202613610377563012477 00000000000000/* * PIM-specific error messages. * Copyright (C) 2018 Cumulus Networks, Inc. * Donald Sharp * * This program is free software; you can redistribute it and/or modify it * under the terms of the 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; see the file COPYING; if not, write to the Free Software * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA */ #ifndef __PIM_ERRORS_H__ #define __PIM_ERRORS_H__ #include "lib/ferr.h" enum pim_log_refs { EC_PIM_MSDP_PACKET = PIM_FERR_START, EC_PIM_CONFIG, }; extern void pim_error_init(void); #endif frr-7.2.1/pimd/pim_hello.c0000644000000000000000000004042613610377563012267 00000000000000/* * PIM for Quagga * Copyright (C) 2008 Everton da Silva Marques * * This program is free software; you can redistribute it and/or modify * it under the terms of the 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; see the file COPYING; if not, write to the Free Software * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA */ #include #include "log.h" #include "if.h" #include "pimd.h" #include "pim_pim.h" #include "pim_str.h" #include "pim_tlv.h" #include "pim_util.h" #include "pim_hello.h" #include "pim_iface.h" #include "pim_neighbor.h" #include "pim_upstream.h" #include "pim_bsm.h" static void on_trace(const char *label, struct interface *ifp, struct in_addr src) { if (PIM_DEBUG_PIM_TRACE) { char src_str[INET_ADDRSTRLEN]; pim_inet4_dump("", src, src_str, sizeof(src_str)); zlog_debug("%s: from %s on %s", label, src_str, ifp->name); } } static void tlv_trace_bool(const char *label, const char *tlv_name, const char *ifname, struct in_addr src_addr, int isset, int value) { if (isset) { char src_str[INET_ADDRSTRLEN]; pim_inet4_dump("", src_addr, src_str, sizeof(src_str)); zlog_debug( "%s: PIM hello option from %s on interface %s: %s=%d", label, src_str, ifname, tlv_name, value); } } static void tlv_trace_uint16(const char *label, const char *tlv_name, const char *ifname, struct in_addr src_addr, int isset, uint16_t value) { if (isset) { char src_str[INET_ADDRSTRLEN]; pim_inet4_dump("", src_addr, src_str, sizeof(src_str)); zlog_debug( "%s: PIM hello option from %s on interface %s: %s=%u", label, src_str, ifname, tlv_name, value); } } static void tlv_trace_uint32(const char *label, const char *tlv_name, const char *ifname, struct in_addr src_addr, int isset, uint32_t value) { if (isset) { char src_str[INET_ADDRSTRLEN]; pim_inet4_dump("", src_addr, src_str, sizeof(src_str)); zlog_debug( "%s: PIM hello option from %s on interface %s: %s=%u", label, src_str, ifname, tlv_name, value); } } static void tlv_trace_uint32_hex(const char *label, const char *tlv_name, const char *ifname, struct in_addr src_addr, int isset, uint32_t value) { if (isset) { char src_str[INET_ADDRSTRLEN]; pim_inet4_dump("", src_addr, src_str, sizeof(src_str)); zlog_debug( "%s: PIM hello option from %s on interface %s: %s=%08x", label, src_str, ifname, tlv_name, value); } } #if 0 static void tlv_trace(const char *label, const char *tlv_name, const char *ifname, struct in_addr src_addr, int isset) { if (isset) { char src_str[INET_ADDRSTRLEN]; pim_inet4_dump("", src_addr, src_str, sizeof(src_str)); zlog_debug("%s: PIM hello option from %s on interface %s: %s", label, src_str, ifname, tlv_name); } } #endif static void tlv_trace_list(const char *label, const char *tlv_name, const char *ifname, struct in_addr src_addr, int isset, struct list *addr_list) { if (isset) { char src_str[INET_ADDRSTRLEN]; pim_inet4_dump("", src_addr, src_str, sizeof(src_str)); zlog_debug( "%s: PIM hello option from %s on interface %s: %s size=%d list=%p", label, src_str, ifname, tlv_name, addr_list ? ((int)listcount(addr_list)) : -1, (void *)addr_list); } } #define FREE_ADDR_LIST \ if (hello_option_addr_list) { \ list_delete(&hello_option_addr_list); \ } #define FREE_ADDR_LIST_THEN_RETURN(code) \ { \ FREE_ADDR_LIST \ return (code); \ } int pim_hello_recv(struct interface *ifp, struct in_addr src_addr, uint8_t *tlv_buf, int tlv_buf_size) { struct pim_interface *pim_ifp; struct pim_neighbor *neigh; uint8_t *tlv_curr; uint8_t *tlv_pastend; pim_hello_options hello_options = 0; /* bit array recording options found */ uint16_t hello_option_holdtime = 0; uint16_t hello_option_propagation_delay = 0; uint16_t hello_option_override_interval = 0; uint32_t hello_option_dr_priority = 0; uint32_t hello_option_generation_id = 0; struct list *hello_option_addr_list = 0; if (PIM_DEBUG_PIM_HELLO) on_trace(__PRETTY_FUNCTION__, ifp, src_addr); pim_ifp = ifp->info; zassert(pim_ifp); ++pim_ifp->pim_ifstat_hello_recv; /* Parse PIM hello TLVs */ zassert(tlv_buf_size >= 0); tlv_curr = tlv_buf; tlv_pastend = tlv_buf + tlv_buf_size; while (tlv_curr < tlv_pastend) { uint16_t option_type; uint16_t option_len; int remain = tlv_pastend - tlv_curr; if (remain < PIM_TLV_MIN_SIZE) { if (PIM_DEBUG_PIM_HELLO) { char src_str[INET_ADDRSTRLEN]; pim_inet4_dump("", src_addr, src_str, sizeof(src_str)); zlog_debug( "%s: short PIM hello TLV size=%d < min=%d from %s on interface %s", __PRETTY_FUNCTION__, remain, PIM_TLV_MIN_SIZE, src_str, ifp->name); } FREE_ADDR_LIST_THEN_RETURN(-1); } option_type = PIM_TLV_GET_TYPE(tlv_curr); tlv_curr += PIM_TLV_TYPE_SIZE; option_len = PIM_TLV_GET_LENGTH(tlv_curr); tlv_curr += PIM_TLV_LENGTH_SIZE; if ((tlv_curr + option_len) > tlv_pastend) { if (PIM_DEBUG_PIM_HELLO) { char src_str[INET_ADDRSTRLEN]; pim_inet4_dump("", src_addr, src_str, sizeof(src_str)); zlog_debug( "%s: long PIM hello TLV type=%d length=%d > left=%td from %s on interface %s", __PRETTY_FUNCTION__, option_type, option_len, tlv_pastend - tlv_curr, src_str, ifp->name); } FREE_ADDR_LIST_THEN_RETURN(-2); } if (PIM_DEBUG_PIM_HELLO) { char src_str[INET_ADDRSTRLEN]; pim_inet4_dump("", src_addr, src_str, sizeof(src_str)); zlog_debug( "%s: parse left_size=%d: PIM hello TLV type=%d length=%d from %s on %s", __PRETTY_FUNCTION__, remain, option_type, option_len, src_str, ifp->name); } switch (option_type) { case PIM_MSG_OPTION_TYPE_HOLDTIME: if (pim_tlv_parse_holdtime(ifp->name, src_addr, &hello_options, &hello_option_holdtime, option_len, tlv_curr)) { FREE_ADDR_LIST_THEN_RETURN(-3); } break; case PIM_MSG_OPTION_TYPE_LAN_PRUNE_DELAY: if (pim_tlv_parse_lan_prune_delay( ifp->name, src_addr, &hello_options, &hello_option_propagation_delay, &hello_option_override_interval, option_len, tlv_curr)) { FREE_ADDR_LIST_THEN_RETURN(-4); } break; case PIM_MSG_OPTION_TYPE_DR_PRIORITY: if (pim_tlv_parse_dr_priority(ifp->name, src_addr, &hello_options, &hello_option_dr_priority, option_len, tlv_curr)) { FREE_ADDR_LIST_THEN_RETURN(-5); } break; case PIM_MSG_OPTION_TYPE_GENERATION_ID: if (pim_tlv_parse_generation_id( ifp->name, src_addr, &hello_options, &hello_option_generation_id, option_len, tlv_curr)) { FREE_ADDR_LIST_THEN_RETURN(-6); } break; case PIM_MSG_OPTION_TYPE_ADDRESS_LIST: if (pim_tlv_parse_addr_list(ifp->name, src_addr, &hello_options, &hello_option_addr_list, option_len, tlv_curr)) { return -7; } break; case PIM_MSG_OPTION_TYPE_DM_STATE_REFRESH: if (PIM_DEBUG_PIM_HELLO) { char src_str[INET_ADDRSTRLEN]; pim_inet4_dump("", src_addr, src_str, sizeof(src_str)); zlog_debug( "%s: ignoring PIM hello dense-mode state refresh TLV option type=%d length=%d from %s on interface %s", __PRETTY_FUNCTION__, option_type, option_len, src_str, ifp->name); } break; default: if (PIM_DEBUG_PIM_HELLO) { char src_str[INET_ADDRSTRLEN]; pim_inet4_dump("", src_addr, src_str, sizeof(src_str)); zlog_debug( "%s: ignoring unknown PIM hello TLV type=%d length=%d from %s on interface %s", __PRETTY_FUNCTION__, option_type, option_len, src_str, ifp->name); } } tlv_curr += option_len; } /* Check received PIM hello options */ if (PIM_DEBUG_PIM_HELLO) { tlv_trace_uint16(__PRETTY_FUNCTION__, "holdtime", ifp->name, src_addr, PIM_OPTION_IS_SET(hello_options, PIM_OPTION_MASK_HOLDTIME), hello_option_holdtime); tlv_trace_uint16( __PRETTY_FUNCTION__, "propagation_delay", ifp->name, src_addr, PIM_OPTION_IS_SET(hello_options, PIM_OPTION_MASK_LAN_PRUNE_DELAY), hello_option_propagation_delay); tlv_trace_uint16( __PRETTY_FUNCTION__, "override_interval", ifp->name, src_addr, PIM_OPTION_IS_SET(hello_options, PIM_OPTION_MASK_LAN_PRUNE_DELAY), hello_option_override_interval); tlv_trace_bool( __PRETTY_FUNCTION__, "can_disable_join_suppression", ifp->name, src_addr, PIM_OPTION_IS_SET(hello_options, PIM_OPTION_MASK_LAN_PRUNE_DELAY), PIM_OPTION_IS_SET( hello_options, PIM_OPTION_MASK_CAN_DISABLE_JOIN_SUPPRESSION)); tlv_trace_uint32(__PRETTY_FUNCTION__, "dr_priority", ifp->name, src_addr, PIM_OPTION_IS_SET(hello_options, PIM_OPTION_MASK_DR_PRIORITY), hello_option_dr_priority); tlv_trace_uint32_hex( __PRETTY_FUNCTION__, "generation_id", ifp->name, src_addr, PIM_OPTION_IS_SET(hello_options, PIM_OPTION_MASK_GENERATION_ID), hello_option_generation_id); tlv_trace_list(__PRETTY_FUNCTION__, "address_list", ifp->name, src_addr, PIM_OPTION_IS_SET(hello_options, PIM_OPTION_MASK_ADDRESS_LIST), hello_option_addr_list); } if (!PIM_OPTION_IS_SET(hello_options, PIM_OPTION_MASK_HOLDTIME)) { if (PIM_DEBUG_PIM_HELLO) { char src_str[INET_ADDRSTRLEN]; pim_inet4_dump("", src_addr, src_str, sizeof(src_str)); zlog_debug( "%s: PIM hello missing holdtime from %s on interface %s", __PRETTY_FUNCTION__, src_str, ifp->name); } } /* New neighbor? */ neigh = pim_neighbor_find(ifp, src_addr); if (!neigh) { /* Add as new neighbor */ neigh = pim_neighbor_add( ifp, src_addr, hello_options, hello_option_holdtime, hello_option_propagation_delay, hello_option_override_interval, hello_option_dr_priority, hello_option_generation_id, hello_option_addr_list, PIM_NEIGHBOR_SEND_DELAY); if (!neigh) { if (PIM_DEBUG_PIM_HELLO) { char src_str[INET_ADDRSTRLEN]; pim_inet4_dump("", src_addr, src_str, sizeof(src_str)); zlog_warn( "%s: failure creating PIM neighbor %s on interface %s", __PRETTY_FUNCTION__, src_str, ifp->name); } FREE_ADDR_LIST_THEN_RETURN(-8); } /* Forward BSM if required */ if (!pim_bsm_new_nbr_fwd(neigh, ifp)) { if (PIM_DEBUG_PIM_HELLO) zlog_debug("%s: forwarding bsm to new nbr failed", __PRETTY_FUNCTION__); } /* actual addr list has been saved under neighbor */ return 0; } /* Received generation ID ? */ if (PIM_OPTION_IS_SET(hello_options, PIM_OPTION_MASK_GENERATION_ID)) { /* GenID mismatch ? */ if (!PIM_OPTION_IS_SET(neigh->hello_options, PIM_OPTION_MASK_GENERATION_ID) || (hello_option_generation_id != neigh->generation_id)) { /* GenID mismatch, then replace neighbor */ if (PIM_DEBUG_PIM_HELLO) { char src_str[INET_ADDRSTRLEN]; pim_inet4_dump("", src_addr, src_str, sizeof(src_str)); zlog_debug( "%s: GenId mismatch new=%08x old=%08x: replacing neighbor %s on %s", __PRETTY_FUNCTION__, hello_option_generation_id, neigh->generation_id, src_str, ifp->name); } pim_upstream_rpf_genid_changed(pim_ifp->pim, neigh->source_addr); pim_neighbor_delete(ifp, neigh, "GenID mismatch"); neigh = pim_neighbor_add(ifp, src_addr, hello_options, hello_option_holdtime, hello_option_propagation_delay, hello_option_override_interval, hello_option_dr_priority, hello_option_generation_id, hello_option_addr_list, PIM_NEIGHBOR_SEND_NOW); if (!neigh) { if (PIM_DEBUG_PIM_HELLO) { char src_str[INET_ADDRSTRLEN]; pim_inet4_dump("", src_addr, src_str, sizeof(src_str)); zlog_debug( "%s: failure re-creating PIM neighbor %s on interface %s", __PRETTY_FUNCTION__, src_str, ifp->name); } FREE_ADDR_LIST_THEN_RETURN(-9); } /* Forward BSM if required */ if (!pim_bsm_new_nbr_fwd(neigh, ifp)) { if (PIM_DEBUG_PIM_HELLO) zlog_debug("%s: forwarding bsm to new nbr failed", __PRETTY_FUNCTION__); } /* actual addr list is saved under neighbor */ return 0; } /* GenId mismatch: replace neighbor */ } /* GenId received */ /* Update existing neighbor */ pim_neighbor_update(neigh, hello_options, hello_option_holdtime, hello_option_dr_priority, hello_option_addr_list); /* actual addr list is saved under neighbor */ return 0; } int pim_hello_build_tlv(struct interface *ifp, uint8_t *tlv_buf, int tlv_buf_size, uint16_t holdtime, uint32_t dr_priority, uint32_t generation_id, uint16_t propagation_delay, uint16_t override_interval, int can_disable_join_suppression) { uint8_t *curr = tlv_buf; uint8_t *pastend = tlv_buf + tlv_buf_size; uint8_t *tmp; struct pim_interface *pim_ifp = ifp->info; struct pim_instance *pim = pim_ifp->pim; /* * Append options */ /* Holdtime */ curr = pim_tlv_append_uint16(curr, pastend, PIM_MSG_OPTION_TYPE_HOLDTIME, holdtime); if (!curr) { if (PIM_DEBUG_PIM_HELLO) { zlog_debug( "%s: could not set PIM hello Holdtime option for interface %s", __PRETTY_FUNCTION__, ifp->name); } return -1; } /* LAN Prune Delay */ tmp = pim_tlv_append_2uint16(curr, pastend, PIM_MSG_OPTION_TYPE_LAN_PRUNE_DELAY, propagation_delay, override_interval); if (!tmp) { if (PIM_DEBUG_PIM_HELLO) { zlog_debug( "%s: could not set PIM LAN Prune Delay option for interface %s", __PRETTY_FUNCTION__, ifp->name); } return -1; } if (can_disable_join_suppression) { *((uint8_t *)(curr) + 4) |= 0x80; /* enable T bit */ } curr = tmp; /* DR Priority */ curr = pim_tlv_append_uint32( curr, pastend, PIM_MSG_OPTION_TYPE_DR_PRIORITY, dr_priority); if (!curr) { if (PIM_DEBUG_PIM_HELLO) { zlog_debug( "%s: could not set PIM hello DR Priority option for interface %s", __PRETTY_FUNCTION__, ifp->name); } return -2; } /* Generation ID */ curr = pim_tlv_append_uint32(curr, pastend, PIM_MSG_OPTION_TYPE_GENERATION_ID, generation_id); if (!curr) { if (PIM_DEBUG_PIM_HELLO) { zlog_debug( "%s: could not set PIM hello Generation ID option for interface %s", __PRETTY_FUNCTION__, ifp->name); } return -3; } /* Secondary Address List */ if (ifp->connected->count) { curr = pim_tlv_append_addrlist_ucast(curr, pastend, ifp->connected, AF_INET); if (!curr) { if (PIM_DEBUG_PIM_HELLO) { zlog_debug( "%s: could not set PIM hello v4 Secondary Address List option for interface %s", __PRETTY_FUNCTION__, ifp->name); } return -4; } if (pim->send_v6_secondary) { curr = pim_tlv_append_addrlist_ucast( curr, pastend, ifp->connected, AF_INET6); if (!curr) { if (PIM_DEBUG_PIM_HELLO) { zlog_debug( "%s: could not sent PIM hello v6 secondary Address List option for interface %s", __PRETTY_FUNCTION__, ifp->name); } return -4; } } } return curr - tlv_buf; } /* RFC 4601: 4.3.1. Sending Hello Messages Thus, if a router needs to send a Join/Prune or Assert message on an interface on which it has not yet sent a Hello message with the currently configured IP address, then it MUST immediately send the relevant Hello message without waiting for the Hello Timer to expire, followed by the Join/Prune or Assert message. */ void pim_hello_require(struct interface *ifp) { struct pim_interface *pim_ifp; zassert(ifp); pim_ifp = ifp->info; zassert(pim_ifp); if (pim_ifp->pim_ifstat_hello_sent) return; pim_hello_restart_now(ifp); /* Send hello and restart timer */ } frr-7.2.1/pimd/pim_hello.h0000644000000000000000000000245413610377563012273 00000000000000/* * PIM for Quagga * Copyright (C) 2008 Everton da Silva Marques * * This program is free software; you can redistribute it and/or modify * it under the terms of the 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; see the file COPYING; if not, write to the Free Software * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA */ #ifndef PIM_HELLO_H #define PIM_HELLO_H #include #include "if.h" int pim_hello_recv(struct interface *ifp, struct in_addr src_addr, uint8_t *tlv_buf, int tlv_buf_size); int pim_hello_build_tlv(struct interface *ifp, uint8_t *tlv_buf, int tlv_buf_size, uint16_t holdtime, uint32_t dr_priority, uint32_t generation_id, uint16_t propagation_delay, uint16_t override_interval, int can_disable_join_suppression); void pim_hello_require(struct interface *ifp); #endif /* PIM_HELLO_H */ frr-7.2.1/pimd/pim_iface.c0000644000000000000000000011063613610377563012234 00000000000000/* * PIM for Quagga * Copyright (C) 2008 Everton da Silva Marques * This program is free software; you can redistribute it and/or modify * it under the terms of the 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; see the file COPYING; if not, write to the Free Software * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA */ #include #include "if.h" #include "log.h" #include "vty.h" #include "memory.h" #include "prefix.h" #include "vrf.h" #include "linklist.h" #include "plist.h" #include "hash.h" #include "ferr.h" #include "pimd.h" #include "pim_instance.h" #include "pim_zebra.h" #include "pim_iface.h" #include "pim_igmp.h" #include "pim_mroute.h" #include "pim_oil.h" #include "pim_str.h" #include "pim_pim.h" #include "pim_neighbor.h" #include "pim_ifchannel.h" #include "pim_sock.h" #include "pim_time.h" #include "pim_ssmpingd.h" #include "pim_rp.h" #include "pim_nht.h" #include "pim_jp_agg.h" #include "pim_igmp_join.h" #include "pim_vxlan.h" static void pim_if_igmp_join_del_all(struct interface *ifp); static int igmp_join_sock(const char *ifname, ifindex_t ifindex, struct in_addr group_addr, struct in_addr source_addr); void pim_if_init(struct pim_instance *pim) { int i; for (i = 0; i < MAXVIFS; i++) pim->iface_vif_index[i] = 0; } void pim_if_terminate(struct pim_instance *pim) { struct interface *ifp; FOR_ALL_INTERFACES (pim->vrf, ifp) { struct pim_interface *pim_ifp = ifp->info; if (!pim_ifp) continue; pim_if_delete(ifp); } return; } static void pim_sec_addr_free(struct pim_secondary_addr *sec_addr) { XFREE(MTYPE_PIM_SEC_ADDR, sec_addr); } static int pim_sec_addr_comp(const void *p1, const void *p2) { const struct pim_secondary_addr *sec1 = p1; const struct pim_secondary_addr *sec2 = p2; if (sec1->addr.family == AF_INET && sec2->addr.family == AF_INET6) return -1; if (sec1->addr.family == AF_INET6 && sec2->addr.family == AF_INET) return 1; if (sec1->addr.family == AF_INET) { if (ntohl(sec1->addr.u.prefix4.s_addr) < ntohl(sec2->addr.u.prefix4.s_addr)) return -1; if (ntohl(sec1->addr.u.prefix4.s_addr) > ntohl(sec2->addr.u.prefix4.s_addr)) return 1; } else { return memcmp(&sec1->addr.u.prefix6, &sec2->addr.u.prefix6, sizeof(struct in6_addr)); } return 0; } struct pim_interface *pim_if_new(struct interface *ifp, bool igmp, bool pim, bool ispimreg, bool is_vxlan_term) { struct pim_interface *pim_ifp; zassert(ifp); zassert(!ifp->info); pim_ifp = XCALLOC(MTYPE_PIM_INTERFACE, sizeof(*pim_ifp)); pim_ifp->options = 0; pim_ifp->pim = pim_get_pim_instance(ifp->vrf_id); pim_ifp->mroute_vif_index = -1; pim_ifp->igmp_version = IGMP_DEFAULT_VERSION; pim_ifp->igmp_default_robustness_variable = IGMP_DEFAULT_ROBUSTNESS_VARIABLE; pim_ifp->igmp_default_query_interval = IGMP_GENERAL_QUERY_INTERVAL; pim_ifp->igmp_query_max_response_time_dsec = IGMP_QUERY_MAX_RESPONSE_TIME_DSEC; pim_ifp->igmp_specific_query_max_response_time_dsec = IGMP_SPECIFIC_QUERY_MAX_RESPONSE_TIME_DSEC; pim_ifp->igmp_last_member_query_count = IGMP_DEFAULT_ROBUSTNESS_VARIABLE; /* BSM config on interface: true by default */ pim_ifp->bsm_enable = true; pim_ifp->ucast_bsm_accept = true; /* RFC 3376: 8.3. Query Response Interval The number of seconds represented by the [Query Response Interval] must be less than the [Query Interval]. */ zassert(pim_ifp->igmp_query_max_response_time_dsec < pim_ifp->igmp_default_query_interval); if (pim) PIM_IF_DO_PIM(pim_ifp->options); if (igmp) PIM_IF_DO_IGMP(pim_ifp->options); PIM_IF_DO_IGMP_LISTEN_ALLROUTERS(pim_ifp->options); pim_ifp->igmp_join_list = NULL; pim_ifp->igmp_socket_list = NULL; pim_ifp->pim_neighbor_list = NULL; pim_ifp->upstream_switch_list = NULL; pim_ifp->pim_generation_id = 0; /* list of struct igmp_sock */ pim_ifp->igmp_socket_list = list_new(); pim_ifp->igmp_socket_list->del = (void (*)(void *))igmp_sock_free; /* list of struct pim_neighbor */ pim_ifp->pim_neighbor_list = list_new(); pim_ifp->pim_neighbor_list->del = (void (*)(void *))pim_neighbor_free; pim_ifp->upstream_switch_list = list_new(); pim_ifp->upstream_switch_list->del = (void (*)(void *))pim_jp_agg_group_list_free; pim_ifp->upstream_switch_list->cmp = pim_jp_agg_group_list_cmp; pim_ifp->sec_addr_list = list_new(); pim_ifp->sec_addr_list->del = (void (*)(void *))pim_sec_addr_free; pim_ifp->sec_addr_list->cmp = (int (*)(void *, void *))pim_sec_addr_comp; pim_ifp->activeactive = false; RB_INIT(pim_ifchannel_rb, &pim_ifp->ifchannel_rb); ifp->info = pim_ifp; pim_sock_reset(ifp); pim_if_add_vif(ifp, ispimreg, is_vxlan_term); return pim_ifp; } void pim_if_delete(struct interface *ifp) { struct pim_interface *pim_ifp; struct pim_ifchannel *ch; zassert(ifp); pim_ifp = ifp->info; zassert(pim_ifp); if (pim_ifp->igmp_join_list) { pim_if_igmp_join_del_all(ifp); } pim_ifchannel_delete_all(ifp); igmp_sock_delete_all(ifp); pim_neighbor_delete_all(ifp, "Interface removed from configuration"); pim_if_del_vif(ifp); list_delete(&pim_ifp->igmp_socket_list); list_delete(&pim_ifp->pim_neighbor_list); list_delete(&pim_ifp->upstream_switch_list); list_delete(&pim_ifp->sec_addr_list); XFREE(MTYPE_PIM_INTERFACE, pim_ifp->boundary_oil_plist); while (!RB_EMPTY(pim_ifchannel_rb, &pim_ifp->ifchannel_rb)) { ch = RB_ROOT(pim_ifchannel_rb, &pim_ifp->ifchannel_rb); pim_ifchannel_delete(ch); } XFREE(MTYPE_PIM_INTERFACE, pim_ifp); ifp->info = NULL; } void pim_if_update_could_assert(struct interface *ifp) { struct pim_interface *pim_ifp; struct pim_ifchannel *ch; pim_ifp = ifp->info; zassert(pim_ifp); RB_FOREACH (ch, pim_ifchannel_rb, &pim_ifp->ifchannel_rb) { pim_ifchannel_update_could_assert(ch); } } static void pim_if_update_my_assert_metric(struct interface *ifp) { struct pim_interface *pim_ifp; struct pim_ifchannel *ch; pim_ifp = ifp->info; zassert(pim_ifp); RB_FOREACH (ch, pim_ifchannel_rb, &pim_ifp->ifchannel_rb) { pim_ifchannel_update_my_assert_metric(ch); } } static void pim_addr_change(struct interface *ifp) { struct pim_interface *pim_ifp; pim_ifp = ifp->info; zassert(pim_ifp); pim_if_dr_election(ifp); /* router's own DR Priority (addr) changes -- Done TODO T30 */ pim_if_update_join_desired(pim_ifp); /* depends on DR */ pim_if_update_could_assert(ifp); /* depends on DR */ pim_if_update_my_assert_metric(ifp); /* depends on could_assert */ pim_if_update_assert_tracking_desired( ifp); /* depends on DR, join_desired */ /* RFC 4601: 4.3.1. Sending Hello Messages 1) Before an interface goes down or changes primary IP address, a Hello message with a zero HoldTime should be sent immediately (with the old IP address if the IP address changed). -- FIXME See CAVEAT C13 2) After an interface has changed its IP address, it MUST send a Hello message with its new IP address. -- DONE below 3) If an interface changes one of its secondary IP addresses, a Hello message with an updated Address_List option and a non-zero HoldTime should be sent immediately. -- FIXME See TODO T31 */ pim_ifp->pim_ifstat_hello_sent = 0; /* reset hello counter */ if (pim_ifp->pim_sock_fd < 0) return; pim_hello_restart_now(ifp); /* send hello and restart timer */ } static int detect_primary_address_change(struct interface *ifp, int force_prim_as_any, const char *caller) { struct pim_interface *pim_ifp = ifp->info; struct in_addr new_prim_addr; int changed; if (force_prim_as_any) new_prim_addr.s_addr = INADDR_ANY; else new_prim_addr = pim_find_primary_addr(ifp); changed = new_prim_addr.s_addr != pim_ifp->primary_address.s_addr; if (PIM_DEBUG_ZEBRA) { char new_prim_str[INET_ADDRSTRLEN]; char old_prim_str[INET_ADDRSTRLEN]; pim_inet4_dump("", new_prim_addr, new_prim_str, sizeof(new_prim_str)); pim_inet4_dump("", pim_ifp->primary_address, old_prim_str, sizeof(old_prim_str)); zlog_debug("%s: old=%s new=%s on interface %s: %s", __PRETTY_FUNCTION__, old_prim_str, new_prim_str, ifp->name, changed ? "changed" : "unchanged"); } if (changed) { pim_ifp->primary_address = new_prim_addr; } return changed; } static struct pim_secondary_addr * pim_sec_addr_find(struct pim_interface *pim_ifp, struct prefix *addr) { struct pim_secondary_addr *sec_addr; struct listnode *node; for (ALL_LIST_ELEMENTS_RO(pim_ifp->sec_addr_list, node, sec_addr)) { if (prefix_cmp(&sec_addr->addr, addr)) { return sec_addr; } } return NULL; } static void pim_sec_addr_del(struct pim_interface *pim_ifp, struct pim_secondary_addr *sec_addr) { listnode_delete(pim_ifp->sec_addr_list, sec_addr); pim_sec_addr_free(sec_addr); } static int pim_sec_addr_add(struct pim_interface *pim_ifp, struct prefix *addr) { int changed = 0; struct pim_secondary_addr *sec_addr; sec_addr = pim_sec_addr_find(pim_ifp, addr); if (sec_addr) { sec_addr->flags &= ~PIM_SEC_ADDRF_STALE; return changed; } sec_addr = XCALLOC(MTYPE_PIM_SEC_ADDR, sizeof(*sec_addr)); changed = 1; sec_addr->addr = *addr; listnode_add_sort(pim_ifp->sec_addr_list, sec_addr); return changed; } static int pim_sec_addr_del_all(struct pim_interface *pim_ifp) { int changed = 0; if (!list_isempty(pim_ifp->sec_addr_list)) { changed = 1; /* remove all nodes and free up the list itself */ list_delete_all_node(pim_ifp->sec_addr_list); } return changed; } static int pim_sec_addr_update(struct interface *ifp) { struct pim_interface *pim_ifp = ifp->info; struct connected *ifc; struct listnode *node; struct listnode *nextnode; struct pim_secondary_addr *sec_addr; int changed = 0; for (ALL_LIST_ELEMENTS_RO(pim_ifp->sec_addr_list, node, sec_addr)) { sec_addr->flags |= PIM_SEC_ADDRF_STALE; } for (ALL_LIST_ELEMENTS_RO(ifp->connected, node, ifc)) { struct prefix *p = ifc->address; if (PIM_INADDR_IS_ANY(p->u.prefix4)) { continue; } if (pim_ifp->primary_address.s_addr == p->u.prefix4.s_addr) { /* don't add the primary address into the secondary * address list */ continue; } if (pim_sec_addr_add(pim_ifp, p)) { changed = 1; } } /* Drop stale entries */ for (ALL_LIST_ELEMENTS(pim_ifp->sec_addr_list, node, nextnode, sec_addr)) { if (sec_addr->flags & PIM_SEC_ADDRF_STALE) { pim_sec_addr_del(pim_ifp, sec_addr); changed = 1; } } return changed; } static int detect_secondary_address_change(struct interface *ifp, int force_prim_as_any, const char *caller) { struct pim_interface *pim_ifp = ifp->info; int changed = 0; if (force_prim_as_any) { /* if primary address is being forced to zero just flush the * secondary address list */ changed = pim_sec_addr_del_all(pim_ifp); } else { /* re-evaluate the secondary address list */ changed = pim_sec_addr_update(ifp); } return changed; } static void detect_address_change(struct interface *ifp, int force_prim_as_any, const char *caller) { int changed = 0; struct pim_interface *pim_ifp; pim_ifp = ifp->info; if (!pim_ifp) return; if (detect_primary_address_change(ifp, force_prim_as_any, caller)) { changed = 1; } if (detect_secondary_address_change(ifp, force_prim_as_any, caller)) { changed = 1; } if (changed) { if (!PIM_IF_TEST_PIM(pim_ifp->options)) { return; } pim_addr_change(ifp); } /* XXX: if we have unnumbered interfaces we need to run detect address * address change on all of them when the lo address changes */ } int pim_update_source_set(struct interface *ifp, struct in_addr source) { struct pim_interface *pim_ifp = ifp->info; if (!pim_ifp) { return PIM_IFACE_NOT_FOUND; } if (pim_ifp->update_source.s_addr == source.s_addr) { return PIM_UPDATE_SOURCE_DUP; } pim_ifp->update_source = source; detect_address_change(ifp, 0 /* force_prim_as_any */, __PRETTY_FUNCTION__); return PIM_SUCCESS; } void pim_if_addr_add(struct connected *ifc) { struct pim_interface *pim_ifp; struct interface *ifp; struct in_addr ifaddr; zassert(ifc); ifp = ifc->ifp; zassert(ifp); pim_ifp = ifp->info; if (!pim_ifp) return; if (!if_is_operative(ifp)) return; if (PIM_DEBUG_ZEBRA) { char buf[BUFSIZ]; prefix2str(ifc->address, buf, BUFSIZ); zlog_debug("%s: %s ifindex=%d connected IP address %s %s", __PRETTY_FUNCTION__, ifp->name, ifp->ifindex, buf, CHECK_FLAG(ifc->flags, ZEBRA_IFA_SECONDARY) ? "secondary" : "primary"); } ifaddr = ifc->address->u.prefix4; detect_address_change(ifp, 0, __PRETTY_FUNCTION__); // if (ifc->address->family != AF_INET) // return; if (PIM_IF_TEST_IGMP(pim_ifp->options)) { struct igmp_sock *igmp; /* lookup IGMP socket */ igmp = pim_igmp_sock_lookup_ifaddr(pim_ifp->igmp_socket_list, ifaddr); if (!igmp) { /* if addr new, add IGMP socket */ if (ifc->address->family == AF_INET) pim_igmp_sock_add(pim_ifp->igmp_socket_list, ifaddr, ifp, false); } else if (igmp->mtrace_only) { igmp_sock_delete(igmp); pim_igmp_sock_add(pim_ifp->igmp_socket_list, ifaddr, ifp, false); } /* Replay Static IGMP groups */ if (pim_ifp->igmp_join_list) { struct listnode *node; struct listnode *nextnode; struct igmp_join *ij; int join_fd; for (ALL_LIST_ELEMENTS(pim_ifp->igmp_join_list, node, nextnode, ij)) { /* Close socket and reopen with Source and Group */ close(ij->sock_fd); join_fd = igmp_join_sock( ifp->name, ifp->ifindex, ij->group_addr, ij->source_addr); if (join_fd < 0) { char group_str[INET_ADDRSTRLEN]; char source_str[INET_ADDRSTRLEN]; pim_inet4_dump("", ij->group_addr, group_str, sizeof(group_str)); pim_inet4_dump( "", ij->source_addr, source_str, sizeof(source_str)); zlog_warn( "%s: igmp_join_sock() failure for IGMP group %s source %s on interface %s", __PRETTY_FUNCTION__, group_str, source_str, ifp->name); /* warning only */ } else ij->sock_fd = join_fd; } } } /* igmp */ else { struct igmp_sock *igmp; /* lookup IGMP socket */ igmp = pim_igmp_sock_lookup_ifaddr(pim_ifp->igmp_socket_list, ifaddr); if (ifc->address->family == AF_INET) { if (igmp) igmp_sock_delete(igmp); /* if addr new, add IGMP socket */ pim_igmp_sock_add(pim_ifp->igmp_socket_list, ifaddr, ifp, true); } } /* igmp mtrace only */ if (PIM_IF_TEST_PIM(pim_ifp->options)) { if (PIM_INADDR_ISNOT_ANY(pim_ifp->primary_address)) { /* Interface has a valid socket ? */ if (pim_ifp->pim_sock_fd < 0) { if (pim_sock_add(ifp)) { zlog_warn( "Failure creating PIM socket for interface %s", ifp->name); } } struct pim_nexthop_cache *pnc = NULL; struct pim_rpf rpf; struct zclient *zclient = NULL; zclient = pim_zebra_zclient_get(); /* RP config might come prior to (local RP's interface) IF UP event. In this case, pnc would not have pim enabled nexthops. Once Interface is UP and pim info is available, reregister with RNH address to receive update and add the interface as nexthop. */ memset(&rpf, 0, sizeof(struct pim_rpf)); rpf.rpf_addr.family = AF_INET; rpf.rpf_addr.prefixlen = IPV4_MAX_BITLEN; rpf.rpf_addr.u.prefix4 = ifc->address->u.prefix4; pnc = pim_nexthop_cache_find(pim_ifp->pim, &rpf); if (pnc) pim_sendmsg_zebra_rnh(pim_ifp->pim, zclient, pnc, ZEBRA_NEXTHOP_REGISTER); } } /* pim */ /* PIM or IGMP is enabled on interface, and there is at least one address assigned, then try to create a vif_index. */ if (pim_ifp->mroute_vif_index < 0) { pim_if_add_vif(ifp, false, false /*vxlan_term*/); } pim_ifchannel_scan_forward_start(ifp); } static void pim_if_addr_del_igmp(struct connected *ifc) { struct pim_interface *pim_ifp = ifc->ifp->info; struct igmp_sock *igmp; struct in_addr ifaddr; if (ifc->address->family != AF_INET) { /* non-IPv4 address */ return; } if (!pim_ifp) { /* IGMP not enabled on interface */ return; } ifaddr = ifc->address->u.prefix4; /* lookup IGMP socket */ igmp = pim_igmp_sock_lookup_ifaddr(pim_ifp->igmp_socket_list, ifaddr); if (igmp) { /* if addr found, del IGMP socket */ igmp_sock_delete(igmp); } } static void pim_if_addr_del_pim(struct connected *ifc) { struct pim_interface *pim_ifp = ifc->ifp->info; if (ifc->address->family != AF_INET) { /* non-IPv4 address */ return; } if (!pim_ifp) { /* PIM not enabled on interface */ return; } if (PIM_INADDR_ISNOT_ANY(pim_ifp->primary_address)) { /* Interface keeps a valid primary address */ return; } if (pim_ifp->pim_sock_fd < 0) { /* Interface does not hold a valid socket any longer */ return; } /* pim_sock_delete() closes the socket, stops read and timer threads, and kills all neighbors. */ pim_sock_delete(ifc->ifp, "last address has been removed from interface"); } void pim_if_addr_del(struct connected *ifc, int force_prim_as_any) { struct interface *ifp; zassert(ifc); ifp = ifc->ifp; zassert(ifp); if (PIM_DEBUG_ZEBRA) { char buf[BUFSIZ]; prefix2str(ifc->address, buf, BUFSIZ); zlog_debug("%s: %s ifindex=%d disconnected IP address %s %s", __PRETTY_FUNCTION__, ifp->name, ifp->ifindex, buf, CHECK_FLAG(ifc->flags, ZEBRA_IFA_SECONDARY) ? "secondary" : "primary"); } detect_address_change(ifp, force_prim_as_any, __PRETTY_FUNCTION__); pim_if_addr_del_igmp(ifc); pim_if_addr_del_pim(ifc); } void pim_if_addr_add_all(struct interface *ifp) { struct connected *ifc; struct listnode *node; struct listnode *nextnode; int v4_addrs = 0; int v6_addrs = 0; struct pim_interface *pim_ifp = ifp->info; /* PIM/IGMP enabled ? */ if (!pim_ifp) return; for (ALL_LIST_ELEMENTS(ifp->connected, node, nextnode, ifc)) { struct prefix *p = ifc->address; if (p->family != AF_INET) v6_addrs++; else v4_addrs++; pim_if_addr_add(ifc); } if (!v4_addrs && v6_addrs && !if_is_loopback(ifp)) { if (PIM_IF_TEST_PIM(pim_ifp->options)) { /* Interface has a valid primary address ? */ if (PIM_INADDR_ISNOT_ANY(pim_ifp->primary_address)) { /* Interface has a valid socket ? */ if (pim_ifp->pim_sock_fd < 0) { if (pim_sock_add(ifp)) { zlog_warn( "Failure creating PIM socket for interface %s", ifp->name); } } } } /* pim */ } /* * PIM or IGMP is enabled on interface, and there is at least one * address assigned, then try to create a vif_index. */ if (pim_ifp->mroute_vif_index < 0) { pim_if_add_vif(ifp, false, false /*vxlan_term*/); } pim_ifchannel_scan_forward_start(ifp); pim_rp_setup(pim_ifp->pim); pim_rp_check_on_if_add(pim_ifp); } void pim_if_addr_del_all(struct interface *ifp) { struct connected *ifc; struct listnode *node; struct listnode *nextnode; struct vrf *vrf = vrf_lookup_by_id(ifp->vrf_id); struct pim_instance *pim; if (!vrf) return; pim = vrf->info; /* PIM/IGMP enabled ? */ if (!ifp->info) return; for (ALL_LIST_ELEMENTS(ifp->connected, node, nextnode, ifc)) { struct prefix *p = ifc->address; if (p->family != AF_INET) continue; pim_if_addr_del(ifc, 1 /* force_prim_as_any=true */); } pim_rp_setup(pim); pim_i_am_rp_re_evaluate(pim); } void pim_if_addr_del_all_igmp(struct interface *ifp) { struct connected *ifc; struct listnode *node; struct listnode *nextnode; /* PIM/IGMP enabled ? */ if (!ifp->info) return; for (ALL_LIST_ELEMENTS(ifp->connected, node, nextnode, ifc)) { struct prefix *p = ifc->address; if (p->family != AF_INET) continue; pim_if_addr_del_igmp(ifc); } } void pim_if_addr_del_all_pim(struct interface *ifp) { struct connected *ifc; struct listnode *node; struct listnode *nextnode; /* PIM/IGMP enabled ? */ if (!ifp->info) return; for (ALL_LIST_ELEMENTS(ifp->connected, node, nextnode, ifc)) { struct prefix *p = ifc->address; if (p->family != AF_INET) continue; pim_if_addr_del_pim(ifc); } } struct in_addr pim_find_primary_addr(struct interface *ifp) { struct connected *ifc; struct listnode *node; struct in_addr addr = {0}; int v4_addrs = 0; int v6_addrs = 0; struct pim_interface *pim_ifp = ifp->info; struct vrf *vrf = vrf_lookup_by_id(ifp->vrf_id); if (!vrf) return addr; if (pim_ifp && PIM_INADDR_ISNOT_ANY(pim_ifp->update_source)) { return pim_ifp->update_source; } for (ALL_LIST_ELEMENTS_RO(ifp->connected, node, ifc)) { struct prefix *p = ifc->address; if (p->family != AF_INET) { v6_addrs++; continue; } if (PIM_INADDR_IS_ANY(p->u.prefix4)) { zlog_warn( "%s: null IPv4 address connected to interface %s", __PRETTY_FUNCTION__, ifp->name); continue; } v4_addrs++; if (CHECK_FLAG(ifc->flags, ZEBRA_IFA_SECONDARY)) continue; return p->u.prefix4; } /* * If we have no v4_addrs and v6 is configured * We probably are using unnumbered * So let's grab the loopbacks v4 address * and use that as the primary address */ if (!v4_addrs && v6_addrs && !if_is_loopback(ifp)) { struct interface *lo_ifp; // DBS - Come back and check here if (ifp->vrf_id == VRF_DEFAULT) lo_ifp = if_lookup_by_name("lo", vrf->vrf_id); else lo_ifp = if_lookup_by_name(vrf->name, vrf->vrf_id); if (lo_ifp) return pim_find_primary_addr(lo_ifp); } addr.s_addr = PIM_NET_INADDR_ANY; return addr; } static int pim_iface_next_vif_index(struct interface *ifp) { struct pim_interface *pim_ifp = ifp->info; struct pim_instance *pim = pim_ifp->pim; int i; /* * The pimreg vif is always going to be in index 0 * of the table. */ if (ifp->ifindex == PIM_OIF_PIM_REGISTER_VIF) return 0; for (i = 1; i < MAXVIFS; i++) { if (pim->iface_vif_index[i] == 0) return i; } return MAXVIFS; } /* pim_if_add_vif() uses ifindex as vif_index see also pim_if_find_vifindex_by_ifindex() */ int pim_if_add_vif(struct interface *ifp, bool ispimreg, bool is_vxlan_term) { struct pim_interface *pim_ifp = ifp->info; struct in_addr ifaddr; unsigned char flags = 0; zassert(pim_ifp); if (pim_ifp->mroute_vif_index > 0) { zlog_warn("%s: vif_index=%d > 0 on interface %s ifindex=%d", __PRETTY_FUNCTION__, pim_ifp->mroute_vif_index, ifp->name, ifp->ifindex); return -1; } if (ifp->ifindex < 0) { zlog_warn("%s: ifindex=%d < 1 on interface %s", __PRETTY_FUNCTION__, ifp->ifindex, ifp->name); return -2; } ifaddr = pim_ifp->primary_address; if (!ispimreg && !is_vxlan_term && PIM_INADDR_IS_ANY(ifaddr)) { zlog_warn( "%s: could not get address for interface %s ifindex=%d", __PRETTY_FUNCTION__, ifp->name, ifp->ifindex); return -4; } pim_ifp->mroute_vif_index = pim_iface_next_vif_index(ifp); if (pim_ifp->mroute_vif_index >= MAXVIFS) { zlog_warn( "%s: Attempting to configure more than MAXVIFS=%d on pim enabled interface %s", __PRETTY_FUNCTION__, MAXVIFS, ifp->name); return -3; } if (ifp->ifindex == PIM_OIF_PIM_REGISTER_VIF) flags = VIFF_REGISTER; #ifdef VIFF_USE_IFINDEX else flags = VIFF_USE_IFINDEX; #endif if (pim_mroute_add_vif(ifp, ifaddr, flags)) { /* pim_mroute_add_vif reported error */ return -5; } pim_ifp->pim->iface_vif_index[pim_ifp->mroute_vif_index] = 1; /* if the device qualifies as pim_vxlan iif/oif update vxlan entries */ pim_vxlan_add_vif(ifp); return 0; } int pim_if_del_vif(struct interface *ifp) { struct pim_interface *pim_ifp = ifp->info; if (pim_ifp->mroute_vif_index < 1) { zlog_warn("%s: vif_index=%d < 1 on interface %s ifindex=%d", __PRETTY_FUNCTION__, pim_ifp->mroute_vif_index, ifp->name, ifp->ifindex); return -1; } /* if the device was a pim_vxlan iif/oif update vxlan mroute entries */ pim_vxlan_del_vif(ifp); pim_mroute_del_vif(ifp); /* Update vif_index */ pim_ifp->pim->iface_vif_index[pim_ifp->mroute_vif_index] = 0; pim_ifp->mroute_vif_index = -1; return 0; } // DBS - VRF Revist struct interface *pim_if_find_by_vif_index(struct pim_instance *pim, ifindex_t vif_index) { struct interface *ifp; FOR_ALL_INTERFACES (pim->vrf, ifp) { if (ifp->info) { struct pim_interface *pim_ifp; pim_ifp = ifp->info; if (vif_index == pim_ifp->mroute_vif_index) return ifp; } } return 0; } /* pim_if_add_vif() uses ifindex as vif_index */ int pim_if_find_vifindex_by_ifindex(struct pim_instance *pim, ifindex_t ifindex) { struct pim_interface *pim_ifp; struct interface *ifp; ifp = if_lookup_by_index(ifindex, pim->vrf_id); if (!ifp || !ifp->info) return -1; pim_ifp = ifp->info; return pim_ifp->mroute_vif_index; } int pim_if_lan_delay_enabled(struct interface *ifp) { struct pim_interface *pim_ifp; pim_ifp = ifp->info; zassert(pim_ifp); zassert(pim_ifp->pim_number_of_nonlandelay_neighbors >= 0); return pim_ifp->pim_number_of_nonlandelay_neighbors == 0; } uint16_t pim_if_effective_propagation_delay_msec(struct interface *ifp) { if (pim_if_lan_delay_enabled(ifp)) { struct pim_interface *pim_ifp; pim_ifp = ifp->info; return pim_ifp->pim_neighbors_highest_propagation_delay_msec; } else { return PIM_DEFAULT_PROPAGATION_DELAY_MSEC; } } uint16_t pim_if_effective_override_interval_msec(struct interface *ifp) { if (pim_if_lan_delay_enabled(ifp)) { struct pim_interface *pim_ifp; pim_ifp = ifp->info; return pim_ifp->pim_neighbors_highest_override_interval_msec; } else { return PIM_DEFAULT_OVERRIDE_INTERVAL_MSEC; } } int pim_if_t_override_msec(struct interface *ifp) { int effective_override_interval_msec; int t_override_msec; effective_override_interval_msec = pim_if_effective_override_interval_msec(ifp); t_override_msec = random() % (effective_override_interval_msec + 1); return t_override_msec; } uint16_t pim_if_jp_override_interval_msec(struct interface *ifp) { return pim_if_effective_propagation_delay_msec(ifp) + pim_if_effective_override_interval_msec(ifp); } /* RFC 4601: 4.1.6. State Summarization Macros The function NBR( I, A ) uses information gathered through PIM Hello messages to map the IP address A of a directly connected PIM neighbor router on interface I to the primary IP address of the same router (Section 4.3.4). The primary IP address of a neighbor is the address that it uses as the source of its PIM Hello messages. */ struct pim_neighbor *pim_if_find_neighbor(struct interface *ifp, struct in_addr addr) { struct listnode *neighnode; struct pim_neighbor *neigh; struct pim_interface *pim_ifp; struct prefix p; zassert(ifp); pim_ifp = ifp->info; if (!pim_ifp) { zlog_warn("%s: multicast not enabled on interface %s", __PRETTY_FUNCTION__, ifp->name); return 0; } p.family = AF_INET; p.u.prefix4 = addr; p.prefixlen = IPV4_MAX_PREFIXLEN; for (ALL_LIST_ELEMENTS_RO(pim_ifp->pim_neighbor_list, neighnode, neigh)) { /* primary address ? */ if (neigh->source_addr.s_addr == addr.s_addr) return neigh; /* secondary address ? */ if (pim_neighbor_find_secondary(neigh, &p)) return neigh; } if (PIM_DEBUG_PIM_TRACE) { char addr_str[INET_ADDRSTRLEN]; pim_inet4_dump("", addr, addr_str, sizeof(addr_str)); zlog_debug( "%s: neighbor not found for address %s on interface %s", __PRETTY_FUNCTION__, addr_str, ifp->name); } return NULL; } long pim_if_t_suppressed_msec(struct interface *ifp) { struct pim_interface *pim_ifp; long t_suppressed_msec; uint32_t ramount = 0; pim_ifp = ifp->info; zassert(pim_ifp); /* join suppression disabled ? */ if (PIM_IF_TEST_PIM_CAN_DISABLE_JOIN_SUPRESSION(pim_ifp->options)) return 0; /* t_suppressed = t_periodic * rand(1.1, 1.4) */ ramount = 1100 + (random() % (1400 - 1100 + 1)); t_suppressed_msec = router->t_periodic * ramount; return t_suppressed_msec; } static void igmp_join_free(struct igmp_join *ij) { XFREE(MTYPE_PIM_IGMP_JOIN, ij); } static struct igmp_join *igmp_join_find(struct list *join_list, struct in_addr group_addr, struct in_addr source_addr) { struct listnode *node; struct igmp_join *ij; zassert(join_list); for (ALL_LIST_ELEMENTS_RO(join_list, node, ij)) { if ((group_addr.s_addr == ij->group_addr.s_addr) && (source_addr.s_addr == ij->source_addr.s_addr)) return ij; } return 0; } static int igmp_join_sock(const char *ifname, ifindex_t ifindex, struct in_addr group_addr, struct in_addr source_addr) { int join_fd; join_fd = pim_socket_raw(IPPROTO_IGMP); if (join_fd < 0) { return -1; } if (pim_igmp_join_source(join_fd, ifindex, group_addr, source_addr)) { char group_str[INET_ADDRSTRLEN]; char source_str[INET_ADDRSTRLEN]; pim_inet4_dump("", group_addr, group_str, sizeof(group_str)); pim_inet4_dump("", source_addr, source_str, sizeof(source_str)); zlog_warn( "%s: setsockopt(fd=%d) failure for IGMP group %s source %s ifindex %d on interface %s: errno=%d: %s", __PRETTY_FUNCTION__, join_fd, group_str, source_str, ifindex, ifname, errno, safe_strerror(errno)); close(join_fd); return -2; } return join_fd; } static struct igmp_join *igmp_join_new(struct interface *ifp, struct in_addr group_addr, struct in_addr source_addr) { struct pim_interface *pim_ifp; struct igmp_join *ij; int join_fd; pim_ifp = ifp->info; zassert(pim_ifp); join_fd = igmp_join_sock(ifp->name, ifp->ifindex, group_addr, source_addr); if (join_fd < 0) { char group_str[INET_ADDRSTRLEN]; char source_str[INET_ADDRSTRLEN]; pim_inet4_dump("", group_addr, group_str, sizeof(group_str)); pim_inet4_dump("", source_addr, source_str, sizeof(source_str)); zlog_warn( "%s: igmp_join_sock() failure for IGMP group %s source %s on interface %s", __PRETTY_FUNCTION__, group_str, source_str, ifp->name); return 0; } ij = XCALLOC(MTYPE_PIM_IGMP_JOIN, sizeof(*ij)); ij->sock_fd = join_fd; ij->group_addr = group_addr; ij->source_addr = source_addr; ij->sock_creation = pim_time_monotonic_sec(); listnode_add(pim_ifp->igmp_join_list, ij); return ij; } ferr_r pim_if_igmp_join_add(struct interface *ifp, struct in_addr group_addr, struct in_addr source_addr) { struct pim_interface *pim_ifp; struct igmp_join *ij; pim_ifp = ifp->info; if (!pim_ifp) { return ferr_cfg_invalid("multicast not enabled on interface %s", ifp->name); } if (!pim_ifp->igmp_join_list) { pim_ifp->igmp_join_list = list_new(); pim_ifp->igmp_join_list->del = (void (*)(void *))igmp_join_free; } ij = igmp_join_find(pim_ifp->igmp_join_list, group_addr, source_addr); /* This interface has already been configured to join this IGMP group */ if (ij) { return ferr_ok(); } (void)igmp_join_new(ifp, group_addr, source_addr); if (PIM_DEBUG_IGMP_EVENTS) { char group_str[INET_ADDRSTRLEN]; char source_str[INET_ADDRSTRLEN]; pim_inet4_dump("", group_addr, group_str, sizeof(group_str)); pim_inet4_dump("", source_addr, source_str, sizeof(source_str)); zlog_debug( "%s: issued static igmp join for channel (S,G)=(%s,%s) on interface %s", __PRETTY_FUNCTION__, source_str, group_str, ifp->name); } return ferr_ok(); } int pim_if_igmp_join_del(struct interface *ifp, struct in_addr group_addr, struct in_addr source_addr) { struct pim_interface *pim_ifp; struct igmp_join *ij; pim_ifp = ifp->info; if (!pim_ifp) { zlog_warn("%s: multicast not enabled on interface %s", __PRETTY_FUNCTION__, ifp->name); return -1; } if (!pim_ifp->igmp_join_list) { zlog_warn("%s: no IGMP join on interface %s", __PRETTY_FUNCTION__, ifp->name); return -2; } ij = igmp_join_find(pim_ifp->igmp_join_list, group_addr, source_addr); if (!ij) { char group_str[INET_ADDRSTRLEN]; char source_str[INET_ADDRSTRLEN]; pim_inet4_dump("", group_addr, group_str, sizeof(group_str)); pim_inet4_dump("", source_addr, source_str, sizeof(source_str)); zlog_warn( "%s: could not find IGMP group %s source %s on interface %s", __PRETTY_FUNCTION__, group_str, source_str, ifp->name); return -3; } if (close(ij->sock_fd)) { char group_str[INET_ADDRSTRLEN]; char source_str[INET_ADDRSTRLEN]; pim_inet4_dump("", group_addr, group_str, sizeof(group_str)); pim_inet4_dump("", source_addr, source_str, sizeof(source_str)); zlog_warn( "%s: failure closing sock_fd=%d for IGMP group %s source %s on interface %s: errno=%d: %s", __PRETTY_FUNCTION__, ij->sock_fd, group_str, source_str, ifp->name, errno, safe_strerror(errno)); /* warning only */ } listnode_delete(pim_ifp->igmp_join_list, ij); igmp_join_free(ij); if (listcount(pim_ifp->igmp_join_list) < 1) { list_delete(&pim_ifp->igmp_join_list); pim_ifp->igmp_join_list = 0; } return 0; } static void pim_if_igmp_join_del_all(struct interface *ifp) { struct pim_interface *pim_ifp; struct listnode *node; struct listnode *nextnode; struct igmp_join *ij; pim_ifp = ifp->info; if (!pim_ifp) { zlog_warn("%s: multicast not enabled on interface %s", __PRETTY_FUNCTION__, ifp->name); return; } if (!pim_ifp->igmp_join_list) return; for (ALL_LIST_ELEMENTS(pim_ifp->igmp_join_list, node, nextnode, ij)) pim_if_igmp_join_del(ifp, ij->group_addr, ij->source_addr); } /* RFC 4601 Transitions from "I am Assert Loser" State Current Winner's GenID Changes or NLT Expires The Neighbor Liveness Timer associated with the current winner expires or we receive a Hello message from the current winner reporting a different GenID from the one it previously reported. This indicates that the current winner's interface or router has gone down (and may have come back up), and so we must assume it no longer knows it was the winner. */ void pim_if_assert_on_neighbor_down(struct interface *ifp, struct in_addr neigh_addr) { struct pim_interface *pim_ifp; struct pim_ifchannel *ch; pim_ifp = ifp->info; zassert(pim_ifp); RB_FOREACH (ch, pim_ifchannel_rb, &pim_ifp->ifchannel_rb) { /* Is (S,G,I) assert loser ? */ if (ch->ifassert_state != PIM_IFASSERT_I_AM_LOSER) continue; /* Dead neighbor was winner ? */ if (ch->ifassert_winner.s_addr != neigh_addr.s_addr) continue; assert_action_a5(ch); } } void pim_if_update_join_desired(struct pim_interface *pim_ifp) { struct pim_ifchannel *ch; /* clear off flag from interface's upstreams */ RB_FOREACH (ch, pim_ifchannel_rb, &pim_ifp->ifchannel_rb) { PIM_UPSTREAM_FLAG_UNSET_DR_JOIN_DESIRED_UPDATED( ch->upstream->flags); } /* scan per-interface (S,G,I) state on this I interface */ RB_FOREACH (ch, pim_ifchannel_rb, &pim_ifp->ifchannel_rb) { struct pim_upstream *up = ch->upstream; if (PIM_UPSTREAM_FLAG_TEST_DR_JOIN_DESIRED_UPDATED(up->flags)) continue; /* update join_desired for the global (S,G) state */ pim_upstream_update_join_desired(pim_ifp->pim, up); PIM_UPSTREAM_FLAG_SET_DR_JOIN_DESIRED_UPDATED(up->flags); } } void pim_if_update_assert_tracking_desired(struct interface *ifp) { struct pim_interface *pim_ifp; struct pim_ifchannel *ch; pim_ifp = ifp->info; if (!pim_ifp) return; RB_FOREACH (ch, pim_ifchannel_rb, &pim_ifp->ifchannel_rb) { pim_ifchannel_update_assert_tracking_desired(ch); } } /* * PIM wants to have an interface pointer for everything it does. * The pimreg is a special interface that we have that is not * quite an inteface but a VIF is created for it. */ void pim_if_create_pimreg(struct pim_instance *pim) { char pimreg_name[INTERFACE_NAMSIZ]; if (!pim->regiface) { if (pim->vrf_id == VRF_DEFAULT) strlcpy(pimreg_name, "pimreg", sizeof(pimreg_name)); else snprintf(pimreg_name, sizeof(pimreg_name), "pimreg%u", pim->vrf->data.l.table_id); pim->regiface = if_create(pimreg_name, pim->vrf_id); pim->regiface->ifindex = PIM_OIF_PIM_REGISTER_VIF; pim_if_new(pim->regiface, false, false, true, false /*vxlan_term*/); } } int pim_if_connected_to_source(struct interface *ifp, struct in_addr src) { struct listnode *cnode; struct connected *c; struct prefix p; if (!ifp) return 0; p.family = AF_INET; p.u.prefix4 = src; p.prefixlen = IPV4_MAX_BITLEN; for (ALL_LIST_ELEMENTS_RO(ifp->connected, cnode, c)) { if ((c->address->family == AF_INET) && prefix_match(CONNECTED_PREFIX(c), &p)) { return 1; } } return 0; } bool pim_if_is_vrf_device(struct interface *ifp) { if (if_is_vrf(ifp)) return true; return false; } int pim_if_ifchannel_count(struct pim_interface *pim_ifp) { struct pim_ifchannel *ch; int count = 0; RB_FOREACH (ch, pim_ifchannel_rb, &pim_ifp->ifchannel_rb) { count++; } return count; } frr-7.2.1/pimd/pim_iface.h0000644000000000000000000002102113610377563012226 00000000000000/* * PIM for Quagga * Copyright (C) 2008 Everton da Silva Marques * * This program is free software; you can redistribute it and/or modify * it under the terms of the 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; see the file COPYING; if not, write to the Free Software * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA */ #ifndef PIM_IFACE_H #define PIM_IFACE_H #include #include "if.h" #include "vty.h" #include "vrf.h" #include "zclient.h" #include "ferr.h" #include "pim_igmp.h" #include "pim_upstream.h" #include "bfd.h" #define PIM_IF_MASK_PIM (1 << 0) #define PIM_IF_MASK_IGMP (1 << 1) #define PIM_IF_MASK_IGMP_LISTEN_ALLROUTERS (1 << 2) #define PIM_IF_MASK_PIM_CAN_DISABLE_JOIN_SUPRESSION (1 << 3) #define PIM_IF_IS_DELETED(ifp) ((ifp)->ifindex == IFINDEX_INTERNAL) #define PIM_IF_TEST_PIM(options) (PIM_IF_MASK_PIM & (options)) #define PIM_IF_TEST_IGMP(options) (PIM_IF_MASK_IGMP & (options)) #define PIM_IF_TEST_IGMP_LISTEN_ALLROUTERS(options) (PIM_IF_MASK_IGMP_LISTEN_ALLROUTERS & (options)) #define PIM_IF_TEST_PIM_CAN_DISABLE_JOIN_SUPRESSION(options) (PIM_IF_MASK_PIM_CAN_DISABLE_JOIN_SUPRESSION & (options)) #define PIM_IF_DO_PIM(options) ((options) |= PIM_IF_MASK_PIM) #define PIM_IF_DO_IGMP(options) ((options) |= PIM_IF_MASK_IGMP) #define PIM_IF_DO_IGMP_LISTEN_ALLROUTERS(options) ((options) |= PIM_IF_MASK_IGMP_LISTEN_ALLROUTERS) #define PIM_IF_DO_PIM_CAN_DISABLE_JOIN_SUPRESSION(options) ((options) |= PIM_IF_MASK_PIM_CAN_DISABLE_JOIN_SUPRESSION) #define PIM_IF_DONT_PIM(options) ((options) &= ~PIM_IF_MASK_PIM) #define PIM_IF_DONT_IGMP(options) ((options) &= ~PIM_IF_MASK_IGMP) #define PIM_IF_DONT_IGMP_LISTEN_ALLROUTERS(options) ((options) &= ~PIM_IF_MASK_IGMP_LISTEN_ALLROUTERS) #define PIM_IF_DONT_PIM_CAN_DISABLE_JOIN_SUPRESSION(options) ((options) &= ~PIM_IF_MASK_PIM_CAN_DISABLE_JOIN_SUPRESSION) #define PIM_I_am_DR(pim_ifp) (pim_ifp)->pim_dr_addr.s_addr == (pim_ifp)->primary_address.s_addr struct pim_iface_upstream_switch { struct in_addr address; struct list *us; }; enum pim_secondary_addr_flags { PIM_SEC_ADDRF_NONE = 0, PIM_SEC_ADDRF_STALE = (1 << 0) }; struct pim_secondary_addr { struct prefix addr; enum pim_secondary_addr_flags flags; }; struct pim_interface { uint32_t options; /* bit vector */ ifindex_t mroute_vif_index; struct pim_instance *pim; struct in_addr primary_address; /* remember addr to detect change */ struct list *sec_addr_list; /* list of struct pim_secondary_addr */ struct in_addr update_source; /* user can statically set the primary * address of the interface */ int igmp_version; /* IGMP version */ int igmp_default_robustness_variable; /* IGMPv3 QRV */ int igmp_default_query_interval; /* IGMPv3 secs between general queries */ int igmp_query_max_response_time_dsec; /* IGMPv3 Max Response Time in dsecs for general queries */ int igmp_specific_query_max_response_time_dsec; /* IGMPv3 Max Response Time in dsecs called as last member query interval, defines the maximum response time advertised in IGMP group-specific queries */ int igmp_last_member_query_count; /* IGMP last member query count */ struct list *igmp_socket_list; /* list of struct igmp_sock */ struct list *igmp_join_list; /* list of struct igmp_join */ int pim_sock_fd; /* PIM socket file descriptor */ struct thread *t_pim_sock_read; /* thread for reading PIM socket */ int64_t pim_sock_creation; /* timestamp of PIM socket creation */ struct thread *t_pim_hello_timer; int pim_hello_period; int pim_default_holdtime; int pim_triggered_hello_delay; uint32_t pim_generation_id; uint16_t pim_propagation_delay_msec; /* config */ uint16_t pim_override_interval_msec; /* config */ struct list *pim_neighbor_list; /* list of struct pim_neighbor */ struct list *upstream_switch_list; struct pim_ifchannel_rb ifchannel_rb; /* neighbors without lan_delay */ int pim_number_of_nonlandelay_neighbors; uint16_t pim_neighbors_highest_propagation_delay_msec; uint16_t pim_neighbors_highest_override_interval_msec; /* DR Election */ int64_t pim_dr_election_last; /* timestamp */ int pim_dr_election_count; int pim_dr_election_changes; struct in_addr pim_dr_addr; uint32_t pim_dr_priority; /* config */ int pim_dr_num_nondrpri_neighbors; /* neighbors without dr_pri */ /* boundary prefix-list */ char *boundary_oil_plist; /* Turn on Active-Active for this interface */ bool activeactive; int64_t pim_ifstat_start; /* start timestamp for stats */ uint64_t pim_ifstat_bsm_rx; uint64_t pim_ifstat_bsm_tx; uint32_t pim_ifstat_hello_sent; uint32_t pim_ifstat_hello_sendfail; uint32_t pim_ifstat_hello_recv; uint32_t pim_ifstat_hello_recvfail; uint32_t pim_ifstat_join_recv; uint32_t pim_ifstat_join_send; uint32_t pim_ifstat_prune_recv; uint32_t pim_ifstat_prune_send; uint32_t pim_ifstat_reg_recv; uint32_t pim_ifstat_reg_send; uint32_t pim_ifstat_reg_stop_recv; uint32_t pim_ifstat_reg_stop_send; uint32_t pim_ifstat_assert_recv; uint32_t pim_ifstat_assert_send; uint32_t pim_ifstat_bsm_cfg_miss; uint32_t pim_ifstat_ucast_bsm_cfg_miss; uint32_t pim_ifstat_bsm_invalid_sz; struct bfd_info *bfd_info; bool bsm_enable; /* bsm processing enable */ bool ucast_bsm_accept; /* ucast bsm processing */ }; /* if default_holdtime is set (>= 0), use it; otherwise default_holdtime is 3.5 * hello_period */ #define PIM_IF_DEFAULT_HOLDTIME(pim_ifp) \ (((pim_ifp)->pim_default_holdtime < 0) \ ? ((pim_ifp)->pim_hello_period * 7 / 2) \ : ((pim_ifp)->pim_default_holdtime)) void pim_if_init(struct pim_instance *pim); void pim_if_terminate(struct pim_instance *pim); struct pim_interface *pim_if_new(struct interface *ifp, bool igmp, bool pim, bool ispimreg, bool is_vxlan_term); void pim_if_delete(struct interface *ifp); void pim_if_addr_add(struct connected *ifc); void pim_if_addr_del(struct connected *ifc, int force_prim_as_any); void pim_if_addr_add_all(struct interface *ifp); void pim_if_addr_del_all(struct interface *ifp); void pim_if_addr_del_all_igmp(struct interface *ifp); void pim_if_addr_del_all_pim(struct interface *ifp); int pim_if_add_vif(struct interface *ifp, bool ispimreg, bool is_vxlan_term); int pim_if_del_vif(struct interface *ifp); void pim_if_add_vif_all(struct pim_instance *pim); void pim_if_del_vif_all(struct pim_instance *pim); struct interface *pim_if_find_by_vif_index(struct pim_instance *pim, ifindex_t vif_index); int pim_if_find_vifindex_by_ifindex(struct pim_instance *pim, ifindex_t ifindex); int pim_if_lan_delay_enabled(struct interface *ifp); uint16_t pim_if_effective_propagation_delay_msec(struct interface *ifp); uint16_t pim_if_effective_override_interval_msec(struct interface *ifp); uint16_t pim_if_jp_override_interval_msec(struct interface *ifp); struct pim_neighbor *pim_if_find_neighbor(struct interface *ifp, struct in_addr addr); long pim_if_t_suppressed_msec(struct interface *ifp); int pim_if_t_override_msec(struct interface *ifp); struct in_addr pim_find_primary_addr(struct interface *ifp); ferr_r pim_if_igmp_join_add(struct interface *ifp, struct in_addr group_addr, struct in_addr source_addr); int pim_if_igmp_join_del(struct interface *ifp, struct in_addr group_addr, struct in_addr source_addr); void pim_if_update_could_assert(struct interface *ifp); void pim_if_assert_on_neighbor_down(struct interface *ifp, struct in_addr neigh_addr); void pim_if_rpf_interface_changed(struct interface *old_rpf_ifp, struct pim_upstream *up); void pim_if_update_join_desired(struct pim_interface *pim_ifp); void pim_if_update_assert_tracking_desired(struct interface *ifp); void pim_if_create_pimreg(struct pim_instance *pim); int pim_if_connected_to_source(struct interface *ifp, struct in_addr src); int pim_update_source_set(struct interface *ifp, struct in_addr source); bool pim_if_is_vrf_device(struct interface *ifp); int pim_if_ifchannel_count(struct pim_interface *pim_ifp); #endif /* PIM_IFACE_H */ frr-7.2.1/pimd/pim_ifchannel.c0000644000000000000000000011256013610377563013112 00000000000000/* * PIM for Quagga * Copyright (C) 2008 Everton da Silva Marques * * This program is free software; you can redistribute it and/or modify * it under the terms of the 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; see the file COPYING; if not, write to the Free Software * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA */ #include #include "linklist.h" #include "thread.h" #include "memory.h" #include "if.h" #include "vrf.h" #include "hash.h" #include "jhash.h" #include "prefix.h" #include "pimd.h" #include "pim_str.h" #include "pim_iface.h" #include "pim_ifchannel.h" #include "pim_zebra.h" #include "pim_time.h" #include "pim_msg.h" #include "pim_pim.h" #include "pim_join.h" #include "pim_rpf.h" #include "pim_macro.h" #include "pim_oil.h" #include "pim_upstream.h" #include "pim_ssm.h" #include "pim_rp.h" RB_GENERATE(pim_ifchannel_rb, pim_ifchannel, pim_ifp_rb, pim_ifchannel_compare); int pim_ifchannel_compare(const struct pim_ifchannel *ch1, const struct pim_ifchannel *ch2) { struct pim_interface *pim_ifp1; struct pim_interface *pim_ifp2; pim_ifp1 = ch1->interface->info; pim_ifp2 = ch2->interface->info; if (pim_ifp1->mroute_vif_index < pim_ifp2->mroute_vif_index) return -1; if (pim_ifp1->mroute_vif_index > pim_ifp2->mroute_vif_index) return 1; if (ntohl(ch1->sg.grp.s_addr) < ntohl(ch2->sg.grp.s_addr)) return -1; if (ntohl(ch1->sg.grp.s_addr) > ntohl(ch2->sg.grp.s_addr)) return 1; if (ntohl(ch1->sg.src.s_addr) < ntohl(ch2->sg.src.s_addr)) return -1; if (ntohl(ch1->sg.src.s_addr) > ntohl(ch2->sg.src.s_addr)) return 1; return 0; } /* * A (*,G) or a (*,*) is going away * remove the parent pointer from * those pointing at us */ static void pim_ifchannel_remove_children(struct pim_ifchannel *ch) { struct pim_ifchannel *child; if (!ch->sources) return; while (!list_isempty(ch->sources)) { child = listnode_head(ch->sources); child->parent = NULL; listnode_delete(ch->sources, child); } } /* * A (*,G) or a (*,*) is being created * find all the children that would point * at us. */ static void pim_ifchannel_find_new_children(struct pim_ifchannel *ch) { struct pim_interface *pim_ifp = ch->interface->info; struct pim_ifchannel *child; // Basic Sanity that we are not being silly if ((ch->sg.src.s_addr != INADDR_ANY) && (ch->sg.grp.s_addr != INADDR_ANY)) return; if ((ch->sg.src.s_addr == INADDR_ANY) && (ch->sg.grp.s_addr == INADDR_ANY)) return; RB_FOREACH (child, pim_ifchannel_rb, &pim_ifp->ifchannel_rb) { if ((ch->sg.grp.s_addr != INADDR_ANY) && (child->sg.grp.s_addr == ch->sg.grp.s_addr) && (child != ch)) { child->parent = ch; listnode_add_sort(ch->sources, child); } } } void pim_ifchannel_delete(struct pim_ifchannel *ch) { struct pim_interface *pim_ifp; pim_ifp = ch->interface->info; if (ch->upstream->channel_oil) { uint32_t mask = PIM_OIF_FLAG_PROTO_PIM; if (ch->upstream->flags & PIM_UPSTREAM_FLAG_MASK_SRC_IGMP) mask |= PIM_OIF_FLAG_PROTO_IGMP; /* * A S,G RPT channel can have an empty oil, we also * need to take into account the fact that a ifchannel * might have been suppressing a *,G ifchannel from * being inherited. So let's figure out what * needs to be done here */ if ((ch->sg.src.s_addr != INADDR_ANY) && pim_upstream_evaluate_join_desired_interface( ch->upstream, ch, ch->parent)) pim_channel_add_oif(ch->upstream->channel_oil, ch->interface, PIM_OIF_FLAG_PROTO_STAR); pim_channel_del_oif(ch->upstream->channel_oil, ch->interface, mask); /* * Do we have any S,G's that are inheriting? * Nuke from on high too. */ if (ch->upstream->sources) { struct pim_upstream *child; struct listnode *up_node; for (ALL_LIST_ELEMENTS_RO(ch->upstream->sources, up_node, child)) pim_channel_del_oif(child->channel_oil, ch->interface, PIM_OIF_FLAG_PROTO_STAR); } } /* * When this channel is removed * we need to find all our children * and make sure our pointers are fixed */ pim_ifchannel_remove_children(ch); if (ch->sources) list_delete(&ch->sources); listnode_delete(ch->upstream->ifchannels, ch); if (ch->ifjoin_state != PIM_IFJOIN_NOINFO) { pim_upstream_update_join_desired(pim_ifp->pim, ch->upstream); } /* upstream is common across ifchannels, check if upstream's ifchannel list is empty before deleting upstream_del ref count will take care of it. */ if (ch->upstream->ref_count > 0) pim_upstream_del(pim_ifp->pim, ch->upstream, __PRETTY_FUNCTION__); else zlog_warn("%s: Avoiding deletion of upstream with ref_count %d " "from ifchannel(%s): %s", __PRETTY_FUNCTION__, ch->upstream->ref_count, ch->interface->name, ch->sg_str); ch->upstream = NULL; THREAD_OFF(ch->t_ifjoin_expiry_timer); THREAD_OFF(ch->t_ifjoin_prune_pending_timer); THREAD_OFF(ch->t_ifassert_timer); if (ch->parent) { listnode_delete(ch->parent->sources, ch); ch->parent = NULL; } RB_REMOVE(pim_ifchannel_rb, &pim_ifp->ifchannel_rb, ch); if (PIM_DEBUG_PIM_TRACE) zlog_debug("%s: ifchannel entry %s is deleted ", __PRETTY_FUNCTION__, ch->sg_str); XFREE(MTYPE_PIM_IFCHANNEL, ch); } void pim_ifchannel_delete_all(struct interface *ifp) { struct pim_interface *pim_ifp; struct pim_ifchannel *ch; pim_ifp = ifp->info; if (!pim_ifp) return; while (!RB_EMPTY(pim_ifchannel_rb, &pim_ifp->ifchannel_rb)) { ch = RB_ROOT(pim_ifchannel_rb, &pim_ifp->ifchannel_rb); pim_ifchannel_ifjoin_switch(__PRETTY_FUNCTION__, ch, PIM_IFJOIN_NOINFO); pim_ifchannel_delete(ch); } } static void delete_on_noinfo(struct pim_ifchannel *ch) { if (ch->local_ifmembership == PIM_IFMEMBERSHIP_NOINFO && ch->ifjoin_state == PIM_IFJOIN_NOINFO && ch->t_ifjoin_expiry_timer == NULL) pim_ifchannel_delete(ch); } void pim_ifchannel_ifjoin_switch(const char *caller, struct pim_ifchannel *ch, enum pim_ifjoin_state new_state) { enum pim_ifjoin_state old_state = ch->ifjoin_state; struct pim_interface *pim_ifp = ch->interface->info; if (PIM_DEBUG_PIM_EVENTS) zlog_debug( "PIM_IFCHANNEL(%s): %s is switching from %s to %s", ch->interface->name, ch->sg_str, pim_ifchannel_ifjoin_name(ch->ifjoin_state, ch->flags), pim_ifchannel_ifjoin_name(new_state, 0)); if (old_state == new_state) { if (PIM_DEBUG_PIM_EVENTS) { zlog_debug( "%s calledby %s: non-transition on state %d (%s)", __PRETTY_FUNCTION__, caller, new_state, pim_ifchannel_ifjoin_name(new_state, 0)); } return; } ch->ifjoin_state = new_state; if (ch->sg.src.s_addr == INADDR_ANY) { struct pim_upstream *up = ch->upstream; struct pim_upstream *child; struct listnode *up_node; if (up) { if (ch->ifjoin_state == PIM_IFJOIN_NOINFO) { for (ALL_LIST_ELEMENTS_RO(up->sources, up_node, child)) { struct channel_oil *c_oil = child->channel_oil; if (PIM_DEBUG_PIM_TRACE) zlog_debug( "%s %s: Prune(S,G)=%s from %s", __FILE__, __PRETTY_FUNCTION__, child->sg_str, up->sg_str); if (!c_oil) continue; if (!pim_upstream_evaluate_join_desired( pim_ifp->pim, child)) { pim_channel_del_oif( c_oil, ch->interface, PIM_OIF_FLAG_PROTO_STAR); pim_upstream_update_join_desired( pim_ifp->pim, child); } /* * If the S,G has no if channel and the * c_oil still * has output here then the *,G was * supplying the implied * if channel. So remove it. * I think this is dead code now. is it? */ if (c_oil->oil.mfcc_ttls [pim_ifp->mroute_vif_index]) pim_channel_del_oif( c_oil, ch->interface, PIM_OIF_FLAG_PROTO_STAR); } } if (ch->ifjoin_state == PIM_IFJOIN_JOIN) { for (ALL_LIST_ELEMENTS_RO(up->sources, up_node, child)) { if (PIM_DEBUG_PIM_TRACE) zlog_debug( "%s %s: Join(S,G)=%s from %s", __FILE__, __PRETTY_FUNCTION__, child->sg_str, up->sg_str); if (pim_upstream_evaluate_join_desired( pim_ifp->pim, child)) { pim_channel_add_oif( child->channel_oil, ch->interface, PIM_OIF_FLAG_PROTO_STAR); pim_upstream_update_join_desired( pim_ifp->pim, child); } } } } } /* Transition to/from NOINFO ? */ if ((old_state == PIM_IFJOIN_NOINFO) || (new_state == PIM_IFJOIN_NOINFO)) { if (PIM_DEBUG_PIM_EVENTS) { zlog_debug("PIM_IFCHANNEL_%s: (S,G)=%s on interface %s", ((new_state == PIM_IFJOIN_NOINFO) ? "DOWN" : "UP"), ch->sg_str, ch->interface->name); } /* Record uptime of state transition to/from NOINFO */ ch->ifjoin_creation = pim_time_monotonic_sec(); pim_upstream_update_join_desired(pim_ifp->pim, ch->upstream); pim_ifchannel_update_could_assert(ch); pim_ifchannel_update_assert_tracking_desired(ch); } } const char *pim_ifchannel_ifjoin_name(enum pim_ifjoin_state ifjoin_state, int flags) { switch (ifjoin_state) { case PIM_IFJOIN_NOINFO: if (PIM_IF_FLAG_TEST_S_G_RPT(flags)) return "SGRpt(NI)"; else return "NOINFO"; break; case PIM_IFJOIN_JOIN: return "JOIN"; break; case PIM_IFJOIN_PRUNE: if (PIM_IF_FLAG_TEST_S_G_RPT(flags)) return "SGRpt(P)"; else return "PRUNE"; break; case PIM_IFJOIN_PRUNE_PENDING: if (PIM_IF_FLAG_TEST_S_G_RPT(flags)) return "SGRpt(PP)"; else return "PRUNEP"; break; case PIM_IFJOIN_PRUNE_TMP: if (PIM_IF_FLAG_TEST_S_G_RPT(flags)) return "SGRpt(P')"; else return "PRUNET"; break; case PIM_IFJOIN_PRUNE_PENDING_TMP: if (PIM_IF_FLAG_TEST_S_G_RPT(flags)) return "SGRpt(PP')"; else return "PRUNEPT"; break; } return "ifjoin_bad_state"; } const char *pim_ifchannel_ifassert_name(enum pim_ifassert_state ifassert_state) { switch (ifassert_state) { case PIM_IFASSERT_NOINFO: return "NOINFO"; case PIM_IFASSERT_I_AM_WINNER: return "WINNER"; case PIM_IFASSERT_I_AM_LOSER: return "LOSER"; } return "ifassert_bad_state"; } /* RFC 4601: 4.6.5. Assert State Macros AssertWinner(S,G,I) defaults to NULL and AssertWinnerMetric(S,G,I) defaults to Infinity when in the NoInfo state. */ void reset_ifassert_state(struct pim_ifchannel *ch) { struct in_addr any = {.s_addr = INADDR_ANY}; THREAD_OFF(ch->t_ifassert_timer); pim_ifassert_winner_set(ch, PIM_IFASSERT_NOINFO, any, router->infinite_assert_metric); } struct pim_ifchannel *pim_ifchannel_find(struct interface *ifp, struct prefix_sg *sg) { struct pim_interface *pim_ifp; struct pim_ifchannel *ch; struct pim_ifchannel lookup; pim_ifp = ifp->info; if (!pim_ifp) { zlog_warn("%s: (S,G)=%s: multicast not enabled on interface %s", __PRETTY_FUNCTION__, pim_str_sg_dump(sg), ifp->name); return NULL; } lookup.sg = *sg; lookup.interface = ifp; ch = RB_FIND(pim_ifchannel_rb, &pim_ifp->ifchannel_rb, &lookup); return ch; } static void ifmembership_set(struct pim_ifchannel *ch, enum pim_ifmembership membership) { struct pim_interface *pim_ifp = ch->interface->info; if (ch->local_ifmembership == membership) return; if (PIM_DEBUG_PIM_EVENTS) { zlog_debug("%s: (S,G)=%s membership now is %s on interface %s", __PRETTY_FUNCTION__, ch->sg_str, membership == PIM_IFMEMBERSHIP_INCLUDE ? "INCLUDE" : "NOINFO", ch->interface->name); } ch->local_ifmembership = membership; pim_upstream_update_join_desired(pim_ifp->pim, ch->upstream); pim_ifchannel_update_could_assert(ch); pim_ifchannel_update_assert_tracking_desired(ch); } void pim_ifchannel_membership_clear(struct interface *ifp) { struct pim_interface *pim_ifp; struct pim_ifchannel *ch; pim_ifp = ifp->info; zassert(pim_ifp); RB_FOREACH (ch, pim_ifchannel_rb, &pim_ifp->ifchannel_rb) ifmembership_set(ch, PIM_IFMEMBERSHIP_NOINFO); } void pim_ifchannel_delete_on_noinfo(struct interface *ifp) { struct pim_interface *pim_ifp; struct pim_ifchannel *ch, *ch_tmp; pim_ifp = ifp->info; zassert(pim_ifp); RB_FOREACH_SAFE (ch, pim_ifchannel_rb, &pim_ifp->ifchannel_rb, ch_tmp) delete_on_noinfo(ch); } /* * For a given Interface, if we are given a S,G * Find the *,G (If we have it). * If we are passed a *,G, find the *,* ifchannel * if we have it. */ static struct pim_ifchannel *pim_ifchannel_find_parent(struct pim_ifchannel *ch) { struct prefix_sg parent_sg = ch->sg; struct pim_ifchannel *parent = NULL; // (S,G) if ((parent_sg.src.s_addr != INADDR_ANY) && (parent_sg.grp.s_addr != INADDR_ANY)) { parent_sg.src.s_addr = INADDR_ANY; parent = pim_ifchannel_find(ch->interface, &parent_sg); if (parent) listnode_add(parent->sources, ch); return parent; } return NULL; } struct pim_ifchannel *pim_ifchannel_add(struct interface *ifp, struct prefix_sg *sg, uint8_t source_flags, int up_flags) { struct pim_interface *pim_ifp; struct pim_ifchannel *ch; struct pim_upstream *up; ch = pim_ifchannel_find(ifp, sg); if (ch) return ch; pim_ifp = ifp->info; ch = XCALLOC(MTYPE_PIM_IFCHANNEL, sizeof(*ch)); ch->flags = 0; if ((source_flags & PIM_ENCODE_RPT_BIT) && !(source_flags & PIM_ENCODE_WC_BIT)) PIM_IF_FLAG_SET_S_G_RPT(ch->flags); ch->interface = ifp; ch->sg = *sg; pim_str_sg_set(sg, ch->sg_str); ch->parent = pim_ifchannel_find_parent(ch); if (ch->sg.src.s_addr == INADDR_ANY) { ch->sources = list_new(); ch->sources->cmp = (int (*)(void *, void *))pim_ifchannel_compare; } else ch->sources = NULL; pim_ifchannel_find_new_children(ch); ch->local_ifmembership = PIM_IFMEMBERSHIP_NOINFO; ch->ifjoin_state = PIM_IFJOIN_NOINFO; ch->t_ifjoin_expiry_timer = NULL; ch->t_ifjoin_prune_pending_timer = NULL; ch->ifjoin_creation = 0; RB_INSERT(pim_ifchannel_rb, &pim_ifp->ifchannel_rb, ch); up = pim_upstream_add(pim_ifp->pim, sg, NULL, up_flags, __PRETTY_FUNCTION__, ch); ch->upstream = up; listnode_add_sort(up->ifchannels, ch); ch->ifassert_my_metric = pim_macro_ch_my_assert_metric_eval(ch); ch->ifassert_winner_metric = pim_macro_ch_my_assert_metric_eval(ch); ch->ifassert_winner.s_addr = 0; /* Assert state */ ch->t_ifassert_timer = NULL; ch->ifassert_state = PIM_IFASSERT_NOINFO; reset_ifassert_state(ch); if (pim_macro_ch_could_assert_eval(ch)) PIM_IF_FLAG_SET_COULD_ASSERT(ch->flags); else PIM_IF_FLAG_UNSET_COULD_ASSERT(ch->flags); if (pim_macro_assert_tracking_desired_eval(ch)) PIM_IF_FLAG_SET_ASSERT_TRACKING_DESIRED(ch->flags); else PIM_IF_FLAG_UNSET_ASSERT_TRACKING_DESIRED(ch->flags); if (PIM_DEBUG_PIM_TRACE) zlog_debug("%s: ifchannel %s is created ", __PRETTY_FUNCTION__, ch->sg_str); return ch; } static void ifjoin_to_noinfo(struct pim_ifchannel *ch, bool ch_del) { pim_forward_stop(ch, !ch_del); pim_ifchannel_ifjoin_switch(__PRETTY_FUNCTION__, ch, PIM_IFJOIN_NOINFO); if (ch_del) delete_on_noinfo(ch); } static int on_ifjoin_expiry_timer(struct thread *t) { struct pim_ifchannel *ch; ch = THREAD_ARG(t); if (PIM_DEBUG_TRACE) zlog_debug("%s: ifchannel %s expiry timer", __PRETTY_FUNCTION__, ch->sg_str); ifjoin_to_noinfo(ch, true); /* ch may have been deleted */ return 0; } static int on_ifjoin_prune_pending_timer(struct thread *t) { struct pim_ifchannel *ch; int send_prune_echo; /* boolean */ struct interface *ifp; struct pim_interface *pim_ifp; ch = THREAD_ARG(t); if (PIM_DEBUG_TRACE) zlog_debug( "%s: IFCHANNEL%s %s Prune Pending Timer Popped", __PRETTY_FUNCTION__, pim_str_sg_dump(&ch->sg), pim_ifchannel_ifjoin_name(ch->ifjoin_state, ch->flags)); if (ch->ifjoin_state == PIM_IFJOIN_PRUNE_PENDING) { ifp = ch->interface; pim_ifp = ifp->info; if (!PIM_IF_FLAG_TEST_S_G_RPT(ch->flags)) { /* Send PruneEcho(S,G) ? */ send_prune_echo = (listcount(pim_ifp->pim_neighbor_list) > 1); if (send_prune_echo) { struct pim_rpf rpf; rpf.source_nexthop.interface = ifp; rpf.rpf_addr.u.prefix4 = pim_ifp->primary_address; pim_jp_agg_single_upstream_send( &rpf, ch->upstream, 0); } ifjoin_to_noinfo(ch, true); } else { /* If SGRpt flag is set on ifchannel, Trigger SGRpt * message on RP path upon prune timer expiry. */ ch->ifjoin_state = PIM_IFJOIN_PRUNE; if (ch->upstream) { struct pim_upstream *parent = ch->upstream->parent; pim_upstream_update_join_desired(pim_ifp->pim, ch->upstream); pim_jp_agg_single_upstream_send(&parent->rpf, parent, true); } } /* from here ch may have been deleted */ } return 0; } static void check_recv_upstream(int is_join, struct interface *recv_ifp, struct in_addr upstream, struct prefix_sg *sg, uint8_t source_flags, int holdtime) { struct pim_upstream *up; struct pim_interface *pim_ifp = recv_ifp->info; /* Upstream (S,G) in Joined state ? */ up = pim_upstream_find(pim_ifp->pim, sg); if (!up) return; if (up->join_state != PIM_UPSTREAM_JOINED) return; /* Upstream (S,G) in Joined state */ if (pim_rpf_addr_is_inaddr_any(&up->rpf)) { /* RPF'(S,G) not found */ zlog_warn("%s %s: RPF'%s not found", __FILE__, __PRETTY_FUNCTION__, up->sg_str); return; } /* upstream directed to RPF'(S,G) ? */ if (upstream.s_addr != up->rpf.rpf_addr.u.prefix4.s_addr) { char up_str[INET_ADDRSTRLEN]; char rpf_str[PREFIX_STRLEN]; pim_inet4_dump("", upstream, up_str, sizeof(up_str)); pim_addr_dump("", &up->rpf.rpf_addr, rpf_str, sizeof(rpf_str)); zlog_warn( "%s %s: (S,G)=%s upstream=%s not directed to RPF'(S,G)=%s on interface %s", __FILE__, __PRETTY_FUNCTION__, up->sg_str, up_str, rpf_str, recv_ifp->name); return; } /* upstream directed to RPF'(S,G) */ if (is_join) { /* Join(S,G) to RPF'(S,G) */ pim_upstream_join_suppress(up, up->rpf.rpf_addr.u.prefix4, holdtime); return; } /* Prune to RPF'(S,G) */ if (source_flags & PIM_RPT_BIT_MASK) { if (source_flags & PIM_WILDCARD_BIT_MASK) { /* Prune(*,G) to RPF'(S,G) */ pim_upstream_join_timer_decrease_to_t_override( "Prune(*,G)", up); return; } /* Prune(S,G,rpt) to RPF'(S,G) */ pim_upstream_join_timer_decrease_to_t_override("Prune(S,G,rpt)", up); return; } /* Prune(S,G) to RPF'(S,G) */ pim_upstream_join_timer_decrease_to_t_override("Prune(S,G)", up); } static int nonlocal_upstream(int is_join, struct interface *recv_ifp, struct in_addr upstream, struct prefix_sg *sg, uint8_t source_flags, uint16_t holdtime) { struct pim_interface *recv_pim_ifp; int is_local; /* boolean */ recv_pim_ifp = recv_ifp->info; zassert(recv_pim_ifp); is_local = (upstream.s_addr == recv_pim_ifp->primary_address.s_addr); if (is_local) return 0; if (PIM_DEBUG_PIM_TRACE_DETAIL) { char up_str[INET_ADDRSTRLEN]; pim_inet4_dump("", upstream, up_str, sizeof(up_str)); zlog_warn("%s: recv %s (S,G)=%s to non-local upstream=%s on %s", __PRETTY_FUNCTION__, is_join ? "join" : "prune", pim_str_sg_dump(sg), up_str, recv_ifp->name); } /* * Since recv upstream addr was not directed to our primary * address, check if we should react to it in any way. */ check_recv_upstream(is_join, recv_ifp, upstream, sg, source_flags, holdtime); return 1; /* non-local */ } void pim_ifchannel_join_add(struct interface *ifp, struct in_addr neigh_addr, struct in_addr upstream, struct prefix_sg *sg, uint8_t source_flags, uint16_t holdtime) { struct pim_interface *pim_ifp; struct pim_ifchannel *ch; if (nonlocal_upstream(1 /* join */, ifp, upstream, sg, source_flags, holdtime)) { return; } ch = pim_ifchannel_add(ifp, sg, source_flags, PIM_UPSTREAM_FLAG_MASK_SRC_PIM); /* RFC 4601: 4.6.1. (S,G) Assert Message State Machine Transitions from "I am Assert Loser" State Receive Join(S,G) on Interface I We receive a Join(S,G) that has the Upstream Neighbor Address field set to my primary IP address on interface I. The action is to transition to NoInfo state, delete this (S,G) assert state (Actions A5 below), and allow the normal PIM Join/Prune mechanisms to operate. Notice: The nonlocal_upstream() test above ensures the upstream address of the join message is our primary address. */ if (ch->ifassert_state == PIM_IFASSERT_I_AM_LOSER) { char neigh_str[INET_ADDRSTRLEN]; pim_inet4_dump("", neigh_addr, neigh_str, sizeof(neigh_str)); zlog_warn("%s: Assert Loser recv Join%s from %s on %s", __PRETTY_FUNCTION__, ch->sg_str, neigh_str, ifp->name); assert_action_a5(ch); } pim_ifp = ifp->info; zassert(pim_ifp); switch (ch->ifjoin_state) { case PIM_IFJOIN_NOINFO: pim_ifchannel_ifjoin_switch(__PRETTY_FUNCTION__, ch, PIM_IFJOIN_JOIN); if (pim_macro_chisin_oiflist(ch)) { pim_upstream_inherited_olist(pim_ifp->pim, ch->upstream); pim_forward_start(ch); } /* * If we are going to be a LHR, we need to note it */ if (ch->upstream->parent && (ch->upstream->parent->flags & PIM_UPSTREAM_FLAG_MASK_SRC_IGMP) && !(ch->upstream->flags & PIM_UPSTREAM_FLAG_MASK_SRC_LHR)) { pim_upstream_ref(ch->upstream, PIM_UPSTREAM_FLAG_MASK_SRC_LHR, __PRETTY_FUNCTION__); pim_upstream_keep_alive_timer_start( ch->upstream, pim_ifp->pim->keep_alive_time); } break; case PIM_IFJOIN_JOIN: zassert(!ch->t_ifjoin_prune_pending_timer); /* In the JOIN state ch->t_ifjoin_expiry_timer may be NULL due to a previously received join message with holdtime=0xFFFF. */ if (ch->t_ifjoin_expiry_timer) { unsigned long remain = thread_timer_remain_second( ch->t_ifjoin_expiry_timer); if (remain > holdtime) { /* RFC 4601: 4.5.3. Receiving (S,G) Join/Prune Messages Transitions from Join State The (S,G) downstream state machine on interface I remains in Join state, and the Expiry Timer (ET) is restarted, set to maximum of its current value and the HoldTime from the triggering Join/Prune message. Conclusion: Do not change the ET if the current value is higher than the received join holdtime. */ return; } } THREAD_OFF(ch->t_ifjoin_expiry_timer); break; case PIM_IFJOIN_PRUNE: if (source_flags & PIM_ENCODE_RPT_BIT) pim_ifchannel_ifjoin_switch(__PRETTY_FUNCTION__, ch, PIM_IFJOIN_NOINFO); else { /* * We have received a S,G join and we are in * S,G RPT Prune state. Which means we need * to transition to Join state and setup * state as appropriate. */ pim_ifchannel_ifjoin_switch(__PRETTY_FUNCTION__, ch, PIM_IFJOIN_JOIN); PIM_IF_FLAG_UNSET_S_G_RPT(ch->flags); if (pim_upstream_evaluate_join_desired(pim_ifp->pim, ch->upstream)) { pim_channel_add_oif(ch->upstream->channel_oil, ch->interface, PIM_OIF_FLAG_PROTO_PIM); pim_upstream_update_join_desired(pim_ifp->pim, ch->upstream); } } break; case PIM_IFJOIN_PRUNE_PENDING: THREAD_OFF(ch->t_ifjoin_prune_pending_timer); if (source_flags & PIM_ENCODE_RPT_BIT) { THREAD_OFF(ch->t_ifjoin_expiry_timer); pim_ifchannel_ifjoin_switch(__PRETTY_FUNCTION__, ch, PIM_IFJOIN_NOINFO); } else pim_ifchannel_ifjoin_switch(__PRETTY_FUNCTION__, ch, PIM_IFJOIN_JOIN); break; case PIM_IFJOIN_PRUNE_TMP: break; case PIM_IFJOIN_PRUNE_PENDING_TMP: break; } if (holdtime != 0xFFFF) { thread_add_timer(router->master, on_ifjoin_expiry_timer, ch, holdtime, &ch->t_ifjoin_expiry_timer); } } void pim_ifchannel_prune(struct interface *ifp, struct in_addr upstream, struct prefix_sg *sg, uint8_t source_flags, uint16_t holdtime) { struct pim_ifchannel *ch; struct pim_interface *pim_ifp; int jp_override_interval_msec; if (nonlocal_upstream(0 /* prune */, ifp, upstream, sg, source_flags, holdtime)) { return; } ch = pim_ifchannel_find(ifp, sg); if (!ch && !(source_flags & PIM_ENCODE_RPT_BIT)) { if (PIM_DEBUG_TRACE) zlog_debug( "%s: Received prune with no relevant ifchannel %s%s state: %d", __PRETTY_FUNCTION__, ifp->name, pim_str_sg_dump(sg), source_flags); return; } ch = pim_ifchannel_add(ifp, sg, source_flags, PIM_UPSTREAM_FLAG_MASK_SRC_PIM); pim_ifp = ifp->info; switch (ch->ifjoin_state) { case PIM_IFJOIN_NOINFO: if (source_flags & PIM_ENCODE_RPT_BIT) { if (!(source_flags & PIM_ENCODE_WC_BIT)) PIM_IF_FLAG_SET_S_G_RPT(ch->flags); ch->ifjoin_state = PIM_IFJOIN_PRUNE_PENDING; if (listcount(pim_ifp->pim_neighbor_list) > 1) jp_override_interval_msec = pim_if_jp_override_interval_msec(ifp); else jp_override_interval_msec = 0; /* schedule to expire immediately */ /* If we called ifjoin_prune() directly instead, care should be taken not to use "ch" afterwards since it would be deleted. */ THREAD_OFF(ch->t_ifjoin_prune_pending_timer); THREAD_OFF(ch->t_ifjoin_expiry_timer); thread_add_timer_msec( router->master, on_ifjoin_prune_pending_timer, ch, jp_override_interval_msec, &ch->t_ifjoin_prune_pending_timer); thread_add_timer(router->master, on_ifjoin_expiry_timer, ch, holdtime, &ch->t_ifjoin_expiry_timer); pim_upstream_update_join_desired(pim_ifp->pim, ch->upstream); } break; case PIM_IFJOIN_PRUNE_PENDING: /* nothing to do */ break; case PIM_IFJOIN_JOIN: THREAD_OFF(ch->t_ifjoin_expiry_timer); pim_ifchannel_ifjoin_switch(__PRETTY_FUNCTION__, ch, PIM_IFJOIN_PRUNE_PENDING); if (listcount(pim_ifp->pim_neighbor_list) > 1) jp_override_interval_msec = pim_if_jp_override_interval_msec(ifp); else jp_override_interval_msec = 0; /* schedule to expire immediately */ /* If we called ifjoin_prune() directly instead, care should be taken not to use "ch" afterwards since it would be deleted. */ THREAD_OFF(ch->t_ifjoin_prune_pending_timer); thread_add_timer_msec(router->master, on_ifjoin_prune_pending_timer, ch, jp_override_interval_msec, &ch->t_ifjoin_prune_pending_timer); break; case PIM_IFJOIN_PRUNE: if (source_flags & PIM_ENCODE_RPT_BIT) { THREAD_OFF(ch->t_ifjoin_prune_pending_timer); thread_add_timer(router->master, on_ifjoin_expiry_timer, ch, holdtime, &ch->t_ifjoin_expiry_timer); } break; case PIM_IFJOIN_PRUNE_TMP: if (source_flags & PIM_ENCODE_RPT_BIT) { ch->ifjoin_state = PIM_IFJOIN_PRUNE; THREAD_OFF(ch->t_ifjoin_expiry_timer); thread_add_timer(router->master, on_ifjoin_expiry_timer, ch, holdtime, &ch->t_ifjoin_expiry_timer); } break; case PIM_IFJOIN_PRUNE_PENDING_TMP: if (source_flags & PIM_ENCODE_RPT_BIT) { ch->ifjoin_state = PIM_IFJOIN_PRUNE_PENDING; THREAD_OFF(ch->t_ifjoin_expiry_timer); thread_add_timer(router->master, on_ifjoin_expiry_timer, ch, holdtime, &ch->t_ifjoin_expiry_timer); } break; } } int pim_ifchannel_local_membership_add(struct interface *ifp, struct prefix_sg *sg) { struct pim_ifchannel *ch, *starch; struct pim_interface *pim_ifp; struct pim_instance *pim; /* PIM enabled on interface? */ pim_ifp = ifp->info; if (!pim_ifp) { if (PIM_DEBUG_EVENTS) zlog_debug("%s:%s Expected pim interface setup for %s", __PRETTY_FUNCTION__, pim_str_sg_dump(sg), ifp->name); return 0; } if (!PIM_IF_TEST_PIM(pim_ifp->options)) { if (PIM_DEBUG_EVENTS) zlog_debug("%s:%s PIM is not configured on this interface %s", __PRETTY_FUNCTION__, pim_str_sg_dump(sg), ifp->name); return 0; } pim = pim_ifp->pim; /* skip (*,G) ch creation if G is of type SSM */ if (sg->src.s_addr == INADDR_ANY) { if (pim_is_grp_ssm(pim, sg->grp)) { if (PIM_DEBUG_PIM_EVENTS) zlog_debug( "%s: local membership (S,G)=%s ignored as group is SSM", __PRETTY_FUNCTION__, pim_str_sg_dump(sg)); return 1; } } ch = pim_ifchannel_add(ifp, sg, 0, PIM_UPSTREAM_FLAG_MASK_SRC_IGMP); ifmembership_set(ch, PIM_IFMEMBERSHIP_INCLUDE); if (sg->src.s_addr == INADDR_ANY) { struct pim_upstream *up = pim_upstream_find(pim, sg); struct pim_upstream *child; struct listnode *up_node; starch = ch; for (ALL_LIST_ELEMENTS_RO(up->sources, up_node, child)) { if (PIM_DEBUG_EVENTS) zlog_debug("%s %s: IGMP (S,G)=%s(%s) from %s", __FILE__, __PRETTY_FUNCTION__, child->sg_str, ifp->name, up->sg_str); ch = pim_ifchannel_find(ifp, &child->sg); if (pim_upstream_evaluate_join_desired_interface( child, ch, starch)) { pim_channel_add_oif(child->channel_oil, ifp, PIM_OIF_FLAG_PROTO_STAR); pim_upstream_switch(pim, child, PIM_UPSTREAM_JOINED); } } if (pim->spt.switchover == PIM_SPT_INFINITY) { if (pim->spt.plist) { struct prefix_list *plist = prefix_list_lookup( AFI_IP, pim->spt.plist); struct prefix g; g.family = AF_INET; g.prefixlen = IPV4_MAX_PREFIXLEN; g.u.prefix4 = up->sg.grp; if (prefix_list_apply(plist, &g) == PREFIX_DENY) { pim_channel_add_oif( up->channel_oil, pim->regiface, PIM_OIF_FLAG_PROTO_IGMP); } } } else pim_channel_add_oif(up->channel_oil, pim->regiface, PIM_OIF_FLAG_PROTO_IGMP); } return 1; } void pim_ifchannel_local_membership_del(struct interface *ifp, struct prefix_sg *sg) { struct pim_ifchannel *starch, *ch, *orig; struct pim_interface *pim_ifp; /* PIM enabled on interface? */ pim_ifp = ifp->info; if (!pim_ifp) return; if (!PIM_IF_TEST_PIM(pim_ifp->options)) return; orig = ch = pim_ifchannel_find(ifp, sg); if (!ch) return; ifmembership_set(ch, PIM_IFMEMBERSHIP_NOINFO); if (sg->src.s_addr == INADDR_ANY) { struct pim_upstream *up = pim_upstream_find(pim_ifp->pim, sg); struct pim_upstream *child; struct listnode *up_node, *up_nnode; starch = ch; for (ALL_LIST_ELEMENTS(up->sources, up_node, up_nnode, child)) { struct channel_oil *c_oil = child->channel_oil; struct pim_ifchannel *chchannel = pim_ifchannel_find(ifp, &child->sg); pim_ifp = ifp->info; if (PIM_DEBUG_EVENTS) zlog_debug("%s %s: Prune(S,G)=%s(%s) from %s", __FILE__, __PRETTY_FUNCTION__, up->sg_str, ifp->name, child->sg_str); ch = pim_ifchannel_find(ifp, &child->sg); if (c_oil && !pim_upstream_evaluate_join_desired_interface( child, ch, starch)) pim_channel_del_oif(c_oil, ifp, PIM_OIF_FLAG_PROTO_STAR); /* * If the S,G has no if channel and the c_oil still * has output here then the *,G was supplying the * implied * if channel. So remove it. */ if (!chchannel && c_oil && c_oil->oil.mfcc_ttls[pim_ifp->mroute_vif_index]) pim_channel_del_oif(c_oil, ifp, PIM_OIF_FLAG_PROTO_STAR); /* Child node removal/ref count-- will happen as part of * parent' delete_no_info */ } } delete_on_noinfo(orig); } void pim_ifchannel_update_could_assert(struct pim_ifchannel *ch) { int old_couldassert = PIM_FORCE_BOOLEAN(PIM_IF_FLAG_TEST_COULD_ASSERT(ch->flags)); int new_couldassert = PIM_FORCE_BOOLEAN(pim_macro_ch_could_assert_eval(ch)); if (new_couldassert == old_couldassert) return; if (PIM_DEBUG_PIM_EVENTS) { char src_str[INET_ADDRSTRLEN]; char grp_str[INET_ADDRSTRLEN]; pim_inet4_dump("", ch->sg.src, src_str, sizeof(src_str)); pim_inet4_dump("", ch->sg.grp, grp_str, sizeof(grp_str)); zlog_debug("%s: CouldAssert(%s,%s,%s) changed from %d to %d", __PRETTY_FUNCTION__, src_str, grp_str, ch->interface->name, old_couldassert, new_couldassert); } if (new_couldassert) { /* CouldAssert(S,G,I) switched from false to true */ PIM_IF_FLAG_SET_COULD_ASSERT(ch->flags); } else { /* CouldAssert(S,G,I) switched from true to false */ PIM_IF_FLAG_UNSET_COULD_ASSERT(ch->flags); if (ch->ifassert_state == PIM_IFASSERT_I_AM_WINNER) { assert_action_a4(ch); } } pim_ifchannel_update_my_assert_metric(ch); } /* my_assert_metric may be affected by: CouldAssert(S,G) pim_ifp->primary_address rpf->source_nexthop.mrib_metric_preference; rpf->source_nexthop.mrib_route_metric; */ void pim_ifchannel_update_my_assert_metric(struct pim_ifchannel *ch) { struct pim_assert_metric my_metric_new = pim_macro_ch_my_assert_metric_eval(ch); if (pim_assert_metric_match(&my_metric_new, &ch->ifassert_my_metric)) return; if (PIM_DEBUG_PIM_EVENTS) { char src_str[INET_ADDRSTRLEN]; char grp_str[INET_ADDRSTRLEN]; char old_addr_str[INET_ADDRSTRLEN]; char new_addr_str[INET_ADDRSTRLEN]; pim_inet4_dump("", ch->sg.src, src_str, sizeof(src_str)); pim_inet4_dump("", ch->sg.grp, grp_str, sizeof(grp_str)); pim_inet4_dump("", ch->ifassert_my_metric.ip_address, old_addr_str, sizeof(old_addr_str)); pim_inet4_dump("", my_metric_new.ip_address, new_addr_str, sizeof(new_addr_str)); zlog_debug( "%s: my_assert_metric(%s,%s,%s) changed from %u,%u,%u,%s to %u,%u,%u,%s", __PRETTY_FUNCTION__, src_str, grp_str, ch->interface->name, ch->ifassert_my_metric.rpt_bit_flag, ch->ifassert_my_metric.metric_preference, ch->ifassert_my_metric.route_metric, old_addr_str, my_metric_new.rpt_bit_flag, my_metric_new.metric_preference, my_metric_new.route_metric, new_addr_str); } ch->ifassert_my_metric = my_metric_new; if (pim_assert_metric_better(&ch->ifassert_my_metric, &ch->ifassert_winner_metric)) { assert_action_a5(ch); } } void pim_ifchannel_update_assert_tracking_desired(struct pim_ifchannel *ch) { int old_atd = PIM_FORCE_BOOLEAN( PIM_IF_FLAG_TEST_ASSERT_TRACKING_DESIRED(ch->flags)); int new_atd = PIM_FORCE_BOOLEAN(pim_macro_assert_tracking_desired_eval(ch)); if (new_atd == old_atd) return; if (PIM_DEBUG_PIM_EVENTS) { char src_str[INET_ADDRSTRLEN]; char grp_str[INET_ADDRSTRLEN]; pim_inet4_dump("", ch->sg.src, src_str, sizeof(src_str)); pim_inet4_dump("", ch->sg.grp, grp_str, sizeof(grp_str)); zlog_debug( "%s: AssertTrackingDesired(%s,%s,%s) changed from %d to %d", __PRETTY_FUNCTION__, src_str, grp_str, ch->interface->name, old_atd, new_atd); } if (new_atd) { /* AssertTrackingDesired(S,G,I) switched from false to true */ PIM_IF_FLAG_SET_ASSERT_TRACKING_DESIRED(ch->flags); } else { /* AssertTrackingDesired(S,G,I) switched from true to false */ PIM_IF_FLAG_UNSET_ASSERT_TRACKING_DESIRED(ch->flags); if (ch->ifassert_state == PIM_IFASSERT_I_AM_LOSER) { assert_action_a5(ch); } } } /* * If we have a new pim interface, check to * see if any of the pre-existing channels have * their upstream out that way and turn on forwarding * for that ifchannel then. */ void pim_ifchannel_scan_forward_start(struct interface *new_ifp) { struct pim_interface *new_pim_ifp = new_ifp->info; struct pim_instance *pim = new_pim_ifp->pim; struct interface *ifp; FOR_ALL_INTERFACES (pim->vrf, ifp) { struct pim_interface *loop_pim_ifp = ifp->info; struct pim_ifchannel *ch; if (!loop_pim_ifp) continue; if (new_pim_ifp == loop_pim_ifp) continue; RB_FOREACH (ch, pim_ifchannel_rb, &loop_pim_ifp->ifchannel_rb) { if (ch->ifjoin_state == PIM_IFJOIN_JOIN) { struct pim_upstream *up = ch->upstream; if ((!up->channel_oil) && (up->rpf.source_nexthop .interface == new_ifp)) pim_forward_start(ch); } } } } /* * Downstream per-interface (S,G,rpt) state machine * states that we need to move (S,G,rpt) items * into different states at the start of the * reception of a *,G join as well, when * we get End of Message */ void pim_ifchannel_set_star_g_join_state(struct pim_ifchannel *ch, int eom, uint8_t join) { bool send_upstream_starg = false; struct pim_ifchannel *child; struct listnode *ch_node, *nch_node; struct pim_instance *pim = ((struct pim_interface *)ch->interface->info)->pim; struct pim_upstream *starup = ch->upstream; if (PIM_DEBUG_PIM_TRACE) zlog_debug( "%s: %s %s eom: %d join %u", __PRETTY_FUNCTION__, pim_ifchannel_ifjoin_name(ch->ifjoin_state, ch->flags), ch->sg_str, eom, join); if (!ch->sources) return; for (ALL_LIST_ELEMENTS(ch->sources, ch_node, nch_node, child)) { if (!PIM_IF_FLAG_TEST_S_G_RPT(child->flags)) continue; switch (child->ifjoin_state) { case PIM_IFJOIN_NOINFO: case PIM_IFJOIN_JOIN: break; case PIM_IFJOIN_PRUNE: if (!eom) child->ifjoin_state = PIM_IFJOIN_PRUNE_TMP; break; case PIM_IFJOIN_PRUNE_PENDING: if (!eom) child->ifjoin_state = PIM_IFJOIN_PRUNE_PENDING_TMP; break; case PIM_IFJOIN_PRUNE_TMP: case PIM_IFJOIN_PRUNE_PENDING_TMP: if (!eom) break; if (child->ifjoin_state == PIM_IFJOIN_PRUNE_PENDING_TMP) THREAD_OFF(child->t_ifjoin_prune_pending_timer); THREAD_OFF(child->t_ifjoin_expiry_timer); PIM_IF_FLAG_UNSET_S_G_RPT(child->flags); child->ifjoin_state = PIM_IFJOIN_NOINFO; if ((I_am_RP(pim, child->sg.grp)) && (!pim_upstream_empty_inherited_olist( child->upstream))) { pim_channel_add_oif( child->upstream->channel_oil, ch->interface, PIM_OIF_FLAG_PROTO_STAR); pim_upstream_switch(pim, child->upstream, PIM_UPSTREAM_JOINED); pim_jp_agg_single_upstream_send( &child->upstream->rpf, child->upstream, true); } send_upstream_starg = true; delete_on_noinfo(child); break; } } if (send_upstream_starg) pim_jp_agg_single_upstream_send(&starup->rpf, starup, true); } unsigned int pim_ifchannel_hash_key(const void *arg) { const struct pim_ifchannel *ch = arg; return jhash_2words(ch->sg.src.s_addr, ch->sg.grp.s_addr, 0); } frr-7.2.1/pimd/pim_ifchannel.h0000644000000000000000000001322013610377563013110 00000000000000/* * PIM for Quagga * Copyright (C) 2008 Everton da Silva Marques * * This program is free software; you can redistribute it and/or modify * it under the terms of the 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; see the file COPYING; if not, write to the Free Software * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA */ #ifndef PIM_IFCHANNEL_H #define PIM_IFCHANNEL_H #include #include "if.h" #include "prefix.h" struct pim_ifchannel; #include "pim_upstream.h" enum pim_ifmembership { PIM_IFMEMBERSHIP_NOINFO, PIM_IFMEMBERSHIP_INCLUDE }; enum pim_ifjoin_state { PIM_IFJOIN_NOINFO, PIM_IFJOIN_JOIN, PIM_IFJOIN_PRUNE, PIM_IFJOIN_PRUNE_PENDING, PIM_IFJOIN_PRUNE_TMP, PIM_IFJOIN_PRUNE_PENDING_TMP, }; enum pim_ifassert_state { PIM_IFASSERT_NOINFO, PIM_IFASSERT_I_AM_WINNER, PIM_IFASSERT_I_AM_LOSER }; struct pim_assert_metric { uint32_t rpt_bit_flag; uint32_t metric_preference; uint32_t route_metric; struct in_addr ip_address; /* neighbor router that sourced the Assert message */ }; /* Flag to detect change in CouldAssert(S,G,I) */ #define PIM_IF_FLAG_MASK_COULD_ASSERT (1 << 0) #define PIM_IF_FLAG_TEST_COULD_ASSERT(flags) ((flags) & PIM_IF_FLAG_MASK_COULD_ASSERT) #define PIM_IF_FLAG_SET_COULD_ASSERT(flags) ((flags) |= PIM_IF_FLAG_MASK_COULD_ASSERT) #define PIM_IF_FLAG_UNSET_COULD_ASSERT(flags) ((flags) &= ~PIM_IF_FLAG_MASK_COULD_ASSERT) /* Flag to detect change in AssertTrackingDesired(S,G,I) */ #define PIM_IF_FLAG_MASK_ASSERT_TRACKING_DESIRED (1 << 1) #define PIM_IF_FLAG_TEST_ASSERT_TRACKING_DESIRED(flags) ((flags) & PIM_IF_FLAG_MASK_ASSERT_TRACKING_DESIRED) #define PIM_IF_FLAG_SET_ASSERT_TRACKING_DESIRED(flags) ((flags) |= PIM_IF_FLAG_MASK_ASSERT_TRACKING_DESIRED) #define PIM_IF_FLAG_UNSET_ASSERT_TRACKING_DESIRED(flags) ((flags) &= ~PIM_IF_FLAG_MASK_ASSERT_TRACKING_DESIRED) /* * Flat to tell us if the ifchannel is (S,G,rpt) */ #define PIM_IF_FLAG_MASK_S_G_RPT (1 << 2) #define PIM_IF_FLAG_TEST_S_G_RPT(flags) ((flags) & PIM_IF_FLAG_MASK_S_G_RPT) #define PIM_IF_FLAG_SET_S_G_RPT(flags) ((flags) |= PIM_IF_FLAG_MASK_S_G_RPT) #define PIM_IF_FLAG_UNSET_S_G_RPT(flags) ((flags) &= ~PIM_IF_FLAG_MASK_S_G_RPT) /* Per-interface (S,G) state */ struct pim_ifchannel { RB_ENTRY(rb_ifchannel) pim_ifp_rb; struct pim_ifchannel *parent; struct list *sources; struct prefix_sg sg; char sg_str[PIM_SG_LEN]; struct interface *interface; /* backpointer to interface */ uint32_t flags; /* IGMPv3 determined interface has local members for (S,G) ? */ enum pim_ifmembership local_ifmembership; /* Per-interface (S,G) Join/Prune State (Section 4.1.4 of RFC4601) */ enum pim_ifjoin_state ifjoin_state; struct thread *t_ifjoin_expiry_timer; struct thread *t_ifjoin_prune_pending_timer; int64_t ifjoin_creation; /* Record uptime of ifjoin state */ /* Per-interface (S,G) Assert State (Section 4.6.1 of RFC4601) */ enum pim_ifassert_state ifassert_state; struct thread *t_ifassert_timer; struct in_addr ifassert_winner; struct pim_assert_metric ifassert_winner_metric; int64_t ifassert_creation; /* Record uptime of ifassert state */ struct pim_assert_metric ifassert_my_metric; /* Upstream (S,G) state */ struct pim_upstream *upstream; }; RB_HEAD(pim_ifchannel_rb, pim_ifchannel); RB_PROTOTYPE(pim_ifchannel_rb, pim_ifchannel, pim_ifp_rb, pim_ifchannel_compare); void pim_ifchannel_delete(struct pim_ifchannel *ch); void pim_ifchannel_delete_all(struct interface *ifp); void pim_ifchannel_membership_clear(struct interface *ifp); void pim_ifchannel_delete_on_noinfo(struct interface *ifp); struct pim_ifchannel *pim_ifchannel_find(struct interface *ifp, struct prefix_sg *sg); struct pim_ifchannel *pim_ifchannel_add(struct interface *ifp, struct prefix_sg *sg, uint8_t ch_flags, int up_flags); void pim_ifchannel_join_add(struct interface *ifp, struct in_addr neigh_addr, struct in_addr upstream, struct prefix_sg *sg, uint8_t source_flags, uint16_t holdtime); void pim_ifchannel_prune(struct interface *ifp, struct in_addr upstream, struct prefix_sg *sg, uint8_t source_flags, uint16_t holdtime); int pim_ifchannel_local_membership_add(struct interface *ifp, struct prefix_sg *sg); void pim_ifchannel_local_membership_del(struct interface *ifp, struct prefix_sg *sg); void pim_ifchannel_ifjoin_switch(const char *caller, struct pim_ifchannel *ch, enum pim_ifjoin_state new_state); const char *pim_ifchannel_ifjoin_name(enum pim_ifjoin_state ifjoin_state, int flags); const char *pim_ifchannel_ifassert_name(enum pim_ifassert_state ifassert_state); int pim_ifchannel_isin_oiflist(struct pim_ifchannel *ch); void reset_ifassert_state(struct pim_ifchannel *ch); void pim_ifchannel_update_could_assert(struct pim_ifchannel *ch); void pim_ifchannel_update_my_assert_metric(struct pim_ifchannel *ch); void pim_ifchannel_update_assert_tracking_desired(struct pim_ifchannel *ch); void pim_ifchannel_scan_forward_start(struct interface *new_ifp); void pim_ifchannel_set_star_g_join_state(struct pim_ifchannel *ch, int eom, uint8_t join); int pim_ifchannel_compare(const struct pim_ifchannel *ch1, const struct pim_ifchannel *ch2); unsigned int pim_ifchannel_hash_key(const void *arg); #endif /* PIM_IFCHANNEL_H */ frr-7.2.1/pimd/pim_igmp.c0000644000000000000000000010125213610377563012113 00000000000000/* * PIM for Quagga * Copyright (C) 2008 Everton da Silva Marques * * This program is free software; you can redistribute it and/or modify * it under the terms of the 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; see the file COPYING; if not, write to the Free Software * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA */ #include #include "memory.h" #include "prefix.h" #include "if.h" #include "hash.h" #include "jhash.h" #include "lib_errors.h" #include "pimd.h" #include "pim_igmp.h" #include "pim_igmpv2.h" #include "pim_igmpv3.h" #include "pim_igmp_mtrace.h" #include "pim_iface.h" #include "pim_sock.h" #include "pim_mroute.h" #include "pim_str.h" #include "pim_util.h" #include "pim_time.h" #include "pim_zebra.h" static void group_timer_off(struct igmp_group *group); static int pim_igmp_general_query(struct thread *t); /* This socket is used for TXing IGMP packets only, IGMP RX happens * in pim_mroute_msg() */ static int igmp_sock_open(struct in_addr ifaddr, struct interface *ifp, uint32_t pim_options) { int fd; int join = 0; struct in_addr group; fd = pim_socket_mcast(IPPROTO_IGMP, ifaddr, ifp, 1); if (fd < 0) return -1; if (PIM_IF_TEST_IGMP_LISTEN_ALLROUTERS(pim_options)) { if (inet_aton(PIM_ALL_ROUTERS, &group)) { if (!pim_socket_join(fd, group, ifaddr, ifp->ifindex)) ++join; } else { zlog_warn( "%s %s: IGMP socket fd=%d interface %s: could not solve %s to group address: errno=%d: %s", __FILE__, __PRETTY_FUNCTION__, fd, inet_ntoa(ifaddr), PIM_ALL_ROUTERS, errno, safe_strerror(errno)); } } /* IGMP routers periodically send IGMP general queries to AllSystems=224.0.0.1 IGMP routers must receive general queries for querier election. */ if (inet_aton(PIM_ALL_SYSTEMS, &group)) { if (!pim_socket_join(fd, group, ifaddr, ifp->ifindex)) ++join; } else { zlog_warn( "%s %s: IGMP socket fd=%d interface %s: could not solve %s to group address: errno=%d: %s", __FILE__, __PRETTY_FUNCTION__, fd, inet_ntoa(ifaddr), PIM_ALL_SYSTEMS, errno, safe_strerror(errno)); } if (inet_aton(PIM_ALL_IGMP_ROUTERS, &group)) { if (!pim_socket_join(fd, group, ifaddr, ifp->ifindex)) { ++join; } } else { zlog_warn( "%s %s: IGMP socket fd=%d interface %s: could not solve %s to group address: errno=%d: %s", __FILE__, __PRETTY_FUNCTION__, fd, inet_ntoa(ifaddr), PIM_ALL_IGMP_ROUTERS, errno, safe_strerror(errno)); } if (!join) { flog_err_sys( EC_LIB_SOCKET, "IGMP socket fd=%d could not join any group on interface address %s", fd, inet_ntoa(ifaddr)); close(fd); fd = -1; } return fd; } #undef IGMP_SOCK_DUMP #ifdef IGMP_SOCK_DUMP static void igmp_sock_dump(array_t *igmp_sock_array) { int size = array_size(igmp_sock_array); for (int i = 0; i < size; ++i) { struct igmp_sock *igmp = array_get(igmp_sock_array, i); zlog_debug("%s %s: [%d/%d] igmp_addr=%s fd=%d", __FILE__, __PRETTY_FUNCTION__, i, size, inet_ntoa(igmp->ifaddr), igmp->fd); } } #endif struct igmp_sock *pim_igmp_sock_lookup_ifaddr(struct list *igmp_sock_list, struct in_addr ifaddr) { struct listnode *sock_node; struct igmp_sock *igmp; #ifdef IGMP_SOCK_DUMP igmp_sock_dump(igmp_sock_list); #endif for (ALL_LIST_ELEMENTS_RO(igmp_sock_list, sock_node, igmp)) if (ifaddr.s_addr == igmp->ifaddr.s_addr) return igmp; return 0; } struct igmp_sock *igmp_sock_lookup_by_fd(struct list *igmp_sock_list, int fd) { struct listnode *sock_node; struct igmp_sock *igmp; for (ALL_LIST_ELEMENTS_RO(igmp_sock_list, sock_node, igmp)) if (fd == igmp->fd) return igmp; return 0; } static int pim_igmp_other_querier_expire(struct thread *t) { struct igmp_sock *igmp; igmp = THREAD_ARG(t); zassert(!igmp->t_igmp_query_timer); if (PIM_DEBUG_IGMP_TRACE) { char ifaddr_str[INET_ADDRSTRLEN]; pim_inet4_dump("", igmp->ifaddr, ifaddr_str, sizeof(ifaddr_str)); zlog_debug("%s: Querier %s resuming", __PRETTY_FUNCTION__, ifaddr_str); } /* We are the current querier, then re-start sending general queries. RFC 2236 - sec 7 Other Querier present timer expired (Send General Query, Set Gen. Query. timer) */ pim_igmp_general_query(t); return 0; } void pim_igmp_other_querier_timer_on(struct igmp_sock *igmp) { long other_querier_present_interval_msec; struct pim_interface *pim_ifp; zassert(igmp); zassert(igmp->interface); zassert(igmp->interface->info); pim_ifp = igmp->interface->info; if (igmp->t_other_querier_timer) { /* There is other querier present already, then reset the other-querier-present timer. */ if (PIM_DEBUG_IGMP_TRACE) { char ifaddr_str[INET_ADDRSTRLEN]; pim_inet4_dump("", igmp->ifaddr, ifaddr_str, sizeof(ifaddr_str)); zlog_debug( "Querier %s resetting TIMER event for Other-Querier-Present", ifaddr_str); } THREAD_OFF(igmp->t_other_querier_timer); } else { /* We are the current querier, then stop sending general queries: igmp->t_igmp_query_timer = NULL; */ pim_igmp_general_query_off(igmp); } /* Since this socket is starting the other-querier-present timer, there should not be periodic query timer for this socket. */ zassert(!igmp->t_igmp_query_timer); /* RFC 3376: 8.5. Other Querier Present Interval The Other Querier Present Interval is the length of time that must pass before a multicast router decides that there is no longer another multicast router which should be the querier. This value MUST be ((the Robustness Variable) times (the Query Interval)) plus (one half of one Query Response Interval). other_querier_present_interval_msec = \ igmp->querier_robustness_variable * \ 1000 * igmp->querier_query_interval + \ 100 * (pim_ifp->query_max_response_time_dsec >> 1); */ other_querier_present_interval_msec = PIM_IGMP_OQPI_MSEC( igmp->querier_robustness_variable, igmp->querier_query_interval, pim_ifp->igmp_query_max_response_time_dsec); if (PIM_DEBUG_IGMP_TRACE) { char ifaddr_str[INET_ADDRSTRLEN]; pim_inet4_dump("", igmp->ifaddr, ifaddr_str, sizeof(ifaddr_str)); zlog_debug( "Querier %s scheduling %ld.%03ld sec TIMER event for Other-Querier-Present", ifaddr_str, other_querier_present_interval_msec / 1000, other_querier_present_interval_msec % 1000); } thread_add_timer_msec(router->master, pim_igmp_other_querier_expire, igmp, other_querier_present_interval_msec, &igmp->t_other_querier_timer); } void pim_igmp_other_querier_timer_off(struct igmp_sock *igmp) { zassert(igmp); if (PIM_DEBUG_IGMP_TRACE) { if (igmp->t_other_querier_timer) { char ifaddr_str[INET_ADDRSTRLEN]; pim_inet4_dump("", igmp->ifaddr, ifaddr_str, sizeof(ifaddr_str)); zlog_debug( "IGMP querier %s fd=%d cancelling other-querier-present TIMER event on %s", ifaddr_str, igmp->fd, igmp->interface->name); } } THREAD_OFF(igmp->t_other_querier_timer); } static int igmp_recv_query(struct igmp_sock *igmp, int query_version, int max_resp_code, struct in_addr from, const char *from_str, char *igmp_msg, int igmp_msg_len) { struct interface *ifp; struct pim_interface *pim_ifp; struct in_addr group_addr; uint16_t recv_checksum; uint16_t checksum; if (igmp->mtrace_only) return 0; memcpy(&group_addr, igmp_msg + 4, sizeof(struct in_addr)); ifp = igmp->interface; pim_ifp = ifp->info; recv_checksum = *(uint16_t *)(igmp_msg + IGMP_CHECKSUM_OFFSET); /* for computing checksum */ *(uint16_t *)(igmp_msg + IGMP_CHECKSUM_OFFSET) = 0; checksum = in_cksum(igmp_msg, igmp_msg_len); if (checksum != recv_checksum) { zlog_warn( "Recv IGMP query v%d from %s on %s: checksum mismatch: received=%x computed=%x", query_version, from_str, ifp->name, recv_checksum, checksum); return -1; } if (!pim_if_connected_to_source(ifp, from)) { if (PIM_DEBUG_IGMP_PACKETS) zlog_debug("Recv IGMP query on interface: %s from a non-connected source: %s", ifp->name, from_str); return 0; } /* Collecting IGMP Rx stats */ switch (query_version) { case 1: igmp->rx_stats.query_v1++; break; case 2: igmp->rx_stats.query_v2++; break; case 3: igmp->rx_stats.query_v3++; break; default: igmp->rx_stats.unsupported++; } /* * RFC 3376 defines some guidelines on operating in backwards * compatibility with older versions of IGMP but there are some gaps in * the logic: * * - once we drop from say version 3 to version 2 we will never go back * to version 3 even if the node that TXed an IGMP v2 query upgrades * to v3 * * - The node with the lowest IP is the querier so we will only know to * drop from v3 to v2 if the node that is the querier is also the one * that is running igmp v2. If a non-querier only supports igmp v2 * we will have no way of knowing. * * For now we will simplify things and inform the user that they need to * configure all PIM routers to use the same version of IGMP. */ if (query_version != pim_ifp->igmp_version) { zlog_warn( "Recv IGMP query v%d from %s on %s but we are using v%d, please " "configure all PIM routers on this subnet to use the same " "IGMP version", query_version, from_str, ifp->name, pim_ifp->igmp_version); return 0; } if (PIM_DEBUG_IGMP_PACKETS) { char group_str[INET_ADDRSTRLEN]; pim_inet4_dump("", group_addr, group_str, sizeof(group_str)); zlog_debug("Recv IGMP query v%d from %s on %s for group %s", query_version, from_str, ifp->name, group_str); } /* RFC 3376: 6.6.2. Querier Election When a router receives a query with a lower IP address, it sets the Other-Querier-Present timer to Other Querier Present Interval and ceases to send queries on the network if it was the previously elected querier. */ if (ntohl(from.s_addr) < ntohl(igmp->ifaddr.s_addr)) { if (PIM_DEBUG_IGMP_TRACE) { char ifaddr_str[INET_ADDRSTRLEN]; pim_inet4_dump("", igmp->ifaddr, ifaddr_str, sizeof(ifaddr_str)); zlog_debug( "%s: local address %s (%u) lost querier election to %s (%u)", ifp->name, ifaddr_str, ntohl(igmp->ifaddr.s_addr), from_str, ntohl(from.s_addr)); } pim_igmp_other_querier_timer_on(igmp); } /* IGMP version 3 is the only one where we process the RXed query */ if (query_version == 3) { igmp_v3_recv_query(igmp, from_str, igmp_msg); } return 0; } static void on_trace(const char *label, struct interface *ifp, struct in_addr from) { if (PIM_DEBUG_IGMP_TRACE) { char from_str[INET_ADDRSTRLEN]; pim_inet4_dump("", from, from_str, sizeof(from_str)); zlog_debug("%s: from %s on %s", label, from_str, ifp->name); } } static int igmp_v1_recv_report(struct igmp_sock *igmp, struct in_addr from, const char *from_str, char *igmp_msg, int igmp_msg_len) { struct interface *ifp = igmp->interface; struct igmp_group *group; struct in_addr group_addr; on_trace(__PRETTY_FUNCTION__, igmp->interface, from); if (igmp->mtrace_only) return 0; if (igmp_msg_len != IGMP_V12_MSG_SIZE) { zlog_warn( "Recv IGMP report v1 from %s on %s: size=%d other than correct=%d", from_str, ifp->name, igmp_msg_len, IGMP_V12_MSG_SIZE); return -1; } /* Collecting IGMP Rx stats */ igmp->rx_stats.report_v1++; if (PIM_DEBUG_IGMP_TRACE) { zlog_warn("%s %s: FIXME WRITEME", __FILE__, __PRETTY_FUNCTION__); } memcpy(&group_addr, igmp_msg + 4, sizeof(struct in_addr)); if (pim_is_group_filtered(ifp->info, &group_addr)) return -1; /* non-existant group is created as INCLUDE {empty} */ group = igmp_add_group_by_addr(igmp, group_addr); if (!group) { return -1; } group->last_igmp_v1_report_dsec = pim_time_monotonic_dsec(); return 0; } int pim_igmp_packet(struct igmp_sock *igmp, char *buf, size_t len) { struct ip *ip_hdr; size_t ip_hlen; /* ip header length in bytes */ char *igmp_msg; int igmp_msg_len; int msg_type; char from_str[INET_ADDRSTRLEN]; char to_str[INET_ADDRSTRLEN]; if (len < sizeof(*ip_hdr)) { zlog_warn("IGMP packet size=%zu shorter than minimum=%zu", len, sizeof(*ip_hdr)); return -1; } ip_hdr = (struct ip *)buf; pim_inet4_dump("", ip_hdr->ip_src, from_str, sizeof(from_str)); pim_inet4_dump("", ip_hdr->ip_dst, to_str, sizeof(to_str)); ip_hlen = ip_hdr->ip_hl << 2; /* ip_hl gives length in 4-byte words */ if (PIM_DEBUG_IGMP_PACKETS) { zlog_debug( "Recv IP packet from %s to %s on %s: size=%zu ip_header_size=%zu ip_proto=%d", from_str, to_str, igmp->interface->name, len, ip_hlen, ip_hdr->ip_p); } if (ip_hlen > len) { zlog_warn( "IGMP packet header claims size %zu, but we only have %zu bytes", ip_hlen, len); return -1; } igmp_msg = buf + ip_hlen; igmp_msg_len = len - ip_hlen; if (igmp_msg_len < PIM_IGMP_MIN_LEN) { zlog_warn("IGMP message size=%d shorter than minimum=%d", igmp_msg_len, PIM_IGMP_MIN_LEN); return -1; } msg_type = *igmp_msg; if (PIM_DEBUG_IGMP_PACKETS) { zlog_debug( "Recv IGMP packet from %s to %s on %s: ttl=%d msg_type=%d msg_size=%d", from_str, to_str, igmp->interface->name, ip_hdr->ip_ttl, msg_type, igmp_msg_len); } switch (msg_type) { case PIM_IGMP_MEMBERSHIP_QUERY: { int max_resp_code = igmp_msg[1]; int query_version; /* RFC 3376: 7.1. Query Version Distinctions IGMPv1 Query: length = 8 octets AND Max Resp Code field is zero IGMPv2 Query: length = 8 octets AND Max Resp Code field is non-zero IGMPv3 Query: length >= 12 octets */ if (igmp_msg_len == 8) { query_version = max_resp_code ? 2 : 1; } else if (igmp_msg_len >= 12) { query_version = 3; } else { zlog_warn("Unknown IGMP query version"); return -1; } return igmp_recv_query(igmp, query_version, max_resp_code, ip_hdr->ip_src, from_str, igmp_msg, igmp_msg_len); } case PIM_IGMP_V3_MEMBERSHIP_REPORT: return igmp_v3_recv_report(igmp, ip_hdr->ip_src, from_str, igmp_msg, igmp_msg_len); case PIM_IGMP_V2_MEMBERSHIP_REPORT: return igmp_v2_recv_report(igmp, ip_hdr->ip_src, from_str, igmp_msg, igmp_msg_len); case PIM_IGMP_V1_MEMBERSHIP_REPORT: return igmp_v1_recv_report(igmp, ip_hdr->ip_src, from_str, igmp_msg, igmp_msg_len); case PIM_IGMP_V2_LEAVE_GROUP: return igmp_v2_recv_leave(igmp, ip_hdr->ip_src, from_str, igmp_msg, igmp_msg_len); case PIM_IGMP_MTRACE_RESPONSE: return igmp_mtrace_recv_response(igmp, ip_hdr, ip_hdr->ip_src, from_str, igmp_msg, igmp_msg_len); case PIM_IGMP_MTRACE_QUERY_REQUEST: return igmp_mtrace_recv_qry_req(igmp, ip_hdr, ip_hdr->ip_src, from_str, igmp_msg, igmp_msg_len); } zlog_warn("Ignoring unsupported IGMP message type: %d", msg_type); /* Collecting IGMP Rx stats */ igmp->rx_stats.unsupported++; return -1; } void pim_igmp_general_query_on(struct igmp_sock *igmp) { struct pim_interface *pim_ifp; int startup_mode; int query_interval; /* Since this socket is starting as querier, there should not exist a timer for other-querier-present. */ zassert(!igmp->t_other_querier_timer); pim_ifp = igmp->interface->info; zassert(pim_ifp); /* RFC 3376: 8.6. Startup Query Interval The Startup Query Interval is the interval between General Queries sent by a Querier on startup. Default: 1/4 the Query Interval. The first one should be sent out immediately instead of 125/4 seconds from now. */ startup_mode = igmp->startup_query_count > 0; if (startup_mode) { /* * If this is the first time we are sending a query on a * newly configured igmp interface send it out in 1 second * just to give the entire world a tiny bit of time to settle * else the query interval is: * query_interval = pim_ifp->igmp_default_query_interval >> 2; */ if (igmp->startup_query_count == igmp->querier_robustness_variable) query_interval = 1; else query_interval = PIM_IGMP_SQI( pim_ifp->igmp_default_query_interval); --igmp->startup_query_count; } else { query_interval = igmp->querier_query_interval; } if (PIM_DEBUG_IGMP_TRACE) { char ifaddr_str[INET_ADDRSTRLEN]; pim_inet4_dump("", igmp->ifaddr, ifaddr_str, sizeof(ifaddr_str)); zlog_debug( "Querier %s scheduling %d-second (%s) TIMER event for IGMP query on fd=%d", ifaddr_str, query_interval, startup_mode ? "startup" : "non-startup", igmp->fd); } igmp->t_igmp_query_timer = NULL; thread_add_timer(router->master, pim_igmp_general_query, igmp, query_interval, &igmp->t_igmp_query_timer); } void pim_igmp_general_query_off(struct igmp_sock *igmp) { zassert(igmp); if (PIM_DEBUG_IGMP_TRACE) { if (igmp->t_igmp_query_timer) { char ifaddr_str[INET_ADDRSTRLEN]; pim_inet4_dump("", igmp->ifaddr, ifaddr_str, sizeof(ifaddr_str)); zlog_debug( "IGMP querier %s fd=%d cancelling query TIMER event on %s", ifaddr_str, igmp->fd, igmp->interface->name); } } THREAD_OFF(igmp->t_igmp_query_timer); } /* Issue IGMP general query */ static int pim_igmp_general_query(struct thread *t) { struct igmp_sock *igmp; struct in_addr dst_addr; struct in_addr group_addr; struct pim_interface *pim_ifp; int query_buf_size; igmp = THREAD_ARG(t); zassert(igmp->interface); zassert(igmp->interface->info); pim_ifp = igmp->interface->info; if (pim_ifp->igmp_version == 3) { query_buf_size = PIM_IGMP_BUFSIZE_WRITE; } else { query_buf_size = IGMP_V12_MSG_SIZE; } char query_buf[query_buf_size]; /* RFC3376: 4.1.12. IP Destination Addresses for Queries In IGMPv3, General Queries are sent with an IP destination address of 224.0.0.1, the all-systems multicast address. Group-Specific and Group-and-Source-Specific Queries are sent with an IP destination address equal to the multicast address of interest. */ dst_addr.s_addr = htonl(INADDR_ALLHOSTS_GROUP); group_addr.s_addr = PIM_NET_INADDR_ANY; if (PIM_DEBUG_IGMP_TRACE) { char querier_str[INET_ADDRSTRLEN]; char dst_str[INET_ADDRSTRLEN]; pim_inet4_dump("", igmp->ifaddr, querier_str, sizeof(querier_str)); pim_inet4_dump("", dst_addr, dst_str, sizeof(dst_str)); zlog_debug("Querier %s issuing IGMP general query to %s on %s", querier_str, dst_str, igmp->interface->name); } igmp_send_query(pim_ifp->igmp_version, 0 /* igmp_group */, igmp->fd, igmp->interface->name, query_buf, sizeof(query_buf), 0 /* num_sources */, dst_addr, group_addr, pim_ifp->igmp_query_max_response_time_dsec, 1 /* s_flag: always set for general queries */, igmp->querier_robustness_variable, igmp->querier_query_interval); pim_igmp_general_query_on(igmp); return 0; } static void sock_close(struct igmp_sock *igmp) { pim_igmp_other_querier_timer_off(igmp); pim_igmp_general_query_off(igmp); if (PIM_DEBUG_IGMP_TRACE_DETAIL) { if (igmp->t_igmp_read) { zlog_debug( "Cancelling READ event on IGMP socket %s fd=%d on interface %s", inet_ntoa(igmp->ifaddr), igmp->fd, igmp->interface->name); } } THREAD_OFF(igmp->t_igmp_read); if (close(igmp->fd)) { flog_err( EC_LIB_SOCKET, "Failure closing IGMP socket %s fd=%d on interface %s: errno=%d: %s", inet_ntoa(igmp->ifaddr), igmp->fd, igmp->interface->name, errno, safe_strerror(errno)); } if (PIM_DEBUG_IGMP_TRACE_DETAIL) { zlog_debug("Deleted IGMP socket %s fd=%d on interface %s", inet_ntoa(igmp->ifaddr), igmp->fd, igmp->interface->name); } } void igmp_startup_mode_on(struct igmp_sock *igmp) { struct pim_interface *pim_ifp; pim_ifp = igmp->interface->info; /* RFC 3376: 8.7. Startup Query Count The Startup Query Count is the number of Queries sent out on startup, separated by the Startup Query Interval. Default: the Robustness Variable. */ igmp->startup_query_count = igmp->querier_robustness_variable; /* Since we're (re)starting, reset QQI to default Query Interval */ igmp->querier_query_interval = pim_ifp->igmp_default_query_interval; } static void igmp_group_free(struct igmp_group *group) { list_delete(&group->group_source_list); XFREE(MTYPE_PIM_IGMP_GROUP, group); } void igmp_group_delete(struct igmp_group *group) { struct listnode *src_node; struct listnode *src_nextnode; struct igmp_source *src; if (PIM_DEBUG_IGMP_TRACE) { char group_str[INET_ADDRSTRLEN]; pim_inet4_dump("", group->group_addr, group_str, sizeof(group_str)); zlog_debug("Deleting IGMP group %s from socket %d interface %s", group_str, group->group_igmp_sock->fd, group->group_igmp_sock->interface->name); } for (ALL_LIST_ELEMENTS(group->group_source_list, src_node, src_nextnode, src)) { igmp_source_delete(src); } if (group->t_group_query_retransmit_timer) { THREAD_OFF(group->t_group_query_retransmit_timer); } group_timer_off(group); listnode_delete(group->group_igmp_sock->igmp_group_list, group); hash_release(group->group_igmp_sock->igmp_group_hash, group); igmp_group_free(group); } void igmp_group_delete_empty_include(struct igmp_group *group) { zassert(!group->group_filtermode_isexcl); zassert(!listcount(group->group_source_list)); igmp_group_delete(group); } void igmp_sock_free(struct igmp_sock *igmp) { zassert(!igmp->t_igmp_read); zassert(!igmp->t_igmp_query_timer); zassert(!igmp->t_other_querier_timer); zassert(igmp->igmp_group_list); zassert(!listcount(igmp->igmp_group_list)); list_delete(&igmp->igmp_group_list); hash_free(igmp->igmp_group_hash); XFREE(MTYPE_PIM_IGMP_SOCKET, igmp); } void igmp_sock_delete(struct igmp_sock *igmp) { struct pim_interface *pim_ifp; struct listnode *grp_node; struct listnode *grp_nextnode; struct igmp_group *grp; for (ALL_LIST_ELEMENTS(igmp->igmp_group_list, grp_node, grp_nextnode, grp)) { igmp_group_delete(grp); } sock_close(igmp); pim_ifp = igmp->interface->info; listnode_delete(pim_ifp->igmp_socket_list, igmp); igmp_sock_free(igmp); } void igmp_sock_delete_all(struct interface *ifp) { struct pim_interface *pim_ifp; struct listnode *igmp_node, *igmp_nextnode; struct igmp_sock *igmp; pim_ifp = ifp->info; for (ALL_LIST_ELEMENTS(pim_ifp->igmp_socket_list, igmp_node, igmp_nextnode, igmp)) { igmp_sock_delete(igmp); } } static unsigned int igmp_group_hash_key(const void *arg) { const struct igmp_group *group = arg; return jhash_1word(group->group_addr.s_addr, 0); } static bool igmp_group_hash_equal(const void *arg1, const void *arg2) { const struct igmp_group *g1 = (const struct igmp_group *)arg1; const struct igmp_group *g2 = (const struct igmp_group *)arg2; if (g1->group_addr.s_addr == g2->group_addr.s_addr) return true; return false; } static struct igmp_sock *igmp_sock_new(int fd, struct in_addr ifaddr, struct interface *ifp, int mtrace_only) { struct pim_interface *pim_ifp; struct igmp_sock *igmp; char hash_name[64]; pim_ifp = ifp->info; if (PIM_DEBUG_IGMP_TRACE) { zlog_debug( "Creating IGMP socket fd=%d for address %s on interface %s", fd, inet_ntoa(ifaddr), ifp->name); } igmp = XCALLOC(MTYPE_PIM_IGMP_SOCKET, sizeof(*igmp)); igmp->igmp_group_list = list_new(); igmp->igmp_group_list->del = (void (*)(void *))igmp_group_free; snprintf(hash_name, 64, "IGMP %s hash", ifp->name); igmp->igmp_group_hash = hash_create(igmp_group_hash_key, igmp_group_hash_equal, hash_name); igmp->fd = fd; igmp->interface = ifp; igmp->ifaddr = ifaddr; igmp->t_igmp_read = NULL; igmp->t_igmp_query_timer = NULL; igmp->t_other_querier_timer = NULL; /* no other querier present */ igmp->querier_robustness_variable = pim_ifp->igmp_default_robustness_variable; igmp->sock_creation = pim_time_monotonic_sec(); igmp_stats_init(&igmp->rx_stats); if (mtrace_only) { igmp->mtrace_only = mtrace_only; return igmp; } igmp->mtrace_only = false; /* igmp_startup_mode_on() will reset QQI: igmp->querier_query_interval = pim_ifp->igmp_default_query_interval; */ igmp_startup_mode_on(igmp); pim_igmp_general_query_on(igmp); return igmp; } static void igmp_read_on(struct igmp_sock *igmp); static int pim_igmp_read(struct thread *t) { uint8_t buf[10000]; struct igmp_sock *igmp = (struct igmp_sock *)THREAD_ARG(t); struct sockaddr_in from; struct sockaddr_in to; socklen_t fromlen = sizeof(from); socklen_t tolen = sizeof(to); ifindex_t ifindex = -1; int len; while (1) { len = pim_socket_recvfromto(igmp->fd, buf, sizeof(buf), &from, &fromlen, &to, &tolen, &ifindex); if (len < 0) { if (errno == EINTR) continue; if (errno == EWOULDBLOCK || errno == EAGAIN) break; goto done; } } done: igmp_read_on(igmp); return 0; } static void igmp_read_on(struct igmp_sock *igmp) { if (PIM_DEBUG_IGMP_TRACE_DETAIL) { zlog_debug("Scheduling READ event on IGMP socket fd=%d", igmp->fd); } igmp->t_igmp_read = NULL; thread_add_read(router->master, pim_igmp_read, igmp, igmp->fd, &igmp->t_igmp_read); } struct igmp_sock *pim_igmp_sock_add(struct list *igmp_sock_list, struct in_addr ifaddr, struct interface *ifp, bool mtrace_only) { struct pim_interface *pim_ifp; struct igmp_sock *igmp; int fd; pim_ifp = ifp->info; fd = igmp_sock_open(ifaddr, ifp, pim_ifp->options); if (fd < 0) { zlog_warn("Could not open IGMP socket for %s on %s", inet_ntoa(ifaddr), ifp->name); return 0; } igmp = igmp_sock_new(fd, ifaddr, ifp, mtrace_only); igmp_read_on(igmp); listnode_add(igmp_sock_list, igmp); #ifdef IGMP_SOCK_DUMP igmp_sock_dump(igmp_sock_array); #endif return igmp; } /* RFC 3376: 6.5. Switching Router Filter-Modes When a router's filter-mode for a group is EXCLUDE and the group timer expires, the router filter-mode for the group transitions to INCLUDE. A router uses source records with running source timers as its state for the switch to a filter-mode of INCLUDE. If there are any source records with source timers greater than zero (i.e., requested to be forwarded), a router switches to filter-mode of INCLUDE using those source records. Source records whose timers are zero (from the previous EXCLUDE mode) are deleted. */ static int igmp_group_timer(struct thread *t) { struct igmp_group *group; group = THREAD_ARG(t); if (PIM_DEBUG_IGMP_TRACE) { char group_str[INET_ADDRSTRLEN]; pim_inet4_dump("", group->group_addr, group_str, sizeof(group_str)); zlog_debug("%s: Timer for group %s on interface %s", __PRETTY_FUNCTION__, group_str, group->group_igmp_sock->interface->name); } zassert(group->group_filtermode_isexcl); group->group_filtermode_isexcl = 0; /* Any source (*,G) is forwarded only if mode is EXCLUDE {empty} */ igmp_anysource_forward_stop(group); igmp_source_delete_expired(group->group_source_list); zassert(!group->group_filtermode_isexcl); /* RFC 3376: 6.2.2. Definition of Group Timers If there are no more source records for the group, delete group record. */ if (listcount(group->group_source_list) < 1) { igmp_group_delete_empty_include(group); } return 0; } static void group_timer_off(struct igmp_group *group) { if (!group->t_group_timer) return; if (PIM_DEBUG_IGMP_TRACE) { char group_str[INET_ADDRSTRLEN]; pim_inet4_dump("", group->group_addr, group_str, sizeof(group_str)); zlog_debug("Cancelling TIMER event for group %s on %s", group_str, group->group_igmp_sock->interface->name); } THREAD_OFF(group->t_group_timer); } void igmp_group_timer_on(struct igmp_group *group, long interval_msec, const char *ifname) { group_timer_off(group); if (PIM_DEBUG_IGMP_EVENTS) { char group_str[INET_ADDRSTRLEN]; pim_inet4_dump("", group->group_addr, group_str, sizeof(group_str)); zlog_debug( "Scheduling %ld.%03ld sec TIMER event for group %s on %s", interval_msec / 1000, interval_msec % 1000, group_str, ifname); } /* RFC 3376: 6.2.2. Definition of Group Timers The group timer is only used when a group is in EXCLUDE mode and it represents the time for the *filter-mode* of the group to expire and switch to INCLUDE mode. */ zassert(group->group_filtermode_isexcl); thread_add_timer_msec(router->master, igmp_group_timer, group, interval_msec, &group->t_group_timer); } struct igmp_group *find_group_by_addr(struct igmp_sock *igmp, struct in_addr group_addr) { struct igmp_group lookup; lookup.group_addr.s_addr = group_addr.s_addr; return hash_lookup(igmp->igmp_group_hash, &lookup); } struct igmp_group *igmp_add_group_by_addr(struct igmp_sock *igmp, struct in_addr group_addr) { struct igmp_group *group; group = find_group_by_addr(igmp, group_addr); if (group) { return group; } if (!pim_is_group_224_4(group_addr)) { zlog_warn("%s: Group Specified is not part of 224.0.0.0/4", __PRETTY_FUNCTION__); return NULL; } if (pim_is_group_224_0_0_0_24(group_addr)) { zlog_warn("%s: Group specified is part of 224.0.0.0/24", __PRETTY_FUNCTION__); return NULL; } /* Non-existant group is created as INCLUDE {empty}: RFC 3376 - 5.1. Action on Change of Interface State If no interface state existed for that multicast address before the change (i.e., the change consisted of creating a new per-interface record), or if no state exists after the change (i.e., the change consisted of deleting a per-interface record), then the "non-existent" state is considered to have a filter mode of INCLUDE and an empty source list. */ group = XCALLOC(MTYPE_PIM_IGMP_GROUP, sizeof(*group)); group->group_source_list = list_new(); group->group_source_list->del = (void (*)(void *))igmp_source_free; group->t_group_timer = NULL; group->t_group_query_retransmit_timer = NULL; group->group_specific_query_retransmit_count = 0; group->group_addr = group_addr; group->group_igmp_sock = igmp; group->last_igmp_v1_report_dsec = -1; group->last_igmp_v2_report_dsec = -1; group->group_creation = pim_time_monotonic_sec(); group->igmp_version = IGMP_DEFAULT_VERSION; /* initialize new group as INCLUDE {empty} */ group->group_filtermode_isexcl = 0; /* 0=INCLUDE, 1=EXCLUDE */ listnode_add(igmp->igmp_group_list, group); group = hash_get(igmp->igmp_group_hash, group, hash_alloc_intern); if (PIM_DEBUG_IGMP_TRACE) { char group_str[INET_ADDRSTRLEN]; pim_inet4_dump("", group->group_addr, group_str, sizeof(group_str)); zlog_debug( "Creating new IGMP group %s on socket %d interface %s", group_str, igmp->fd, igmp->interface->name); } /* RFC 3376: 6.2.2. Definition of Group Timers The group timer is only used when a group is in EXCLUDE mode and it represents the time for the *filter-mode* of the group to expire and switch to INCLUDE mode. */ zassert(!group->group_filtermode_isexcl); /* INCLUDE mode */ zassert(!group->t_group_timer); /* group timer == 0 */ /* Any source (*,G) is forwarded only if mode is EXCLUDE {empty} */ igmp_anysource_forward_stop(group); return group; } void igmp_send_query(int igmp_version, struct igmp_group *group, int fd, const char *ifname, char *query_buf, int query_buf_size, int num_sources, struct in_addr dst_addr, struct in_addr group_addr, int query_max_response_time_dsec, uint8_t s_flag, uint8_t querier_robustness_variable, uint16_t querier_query_interval) { if (igmp_version == 3) { igmp_v3_send_query(group, fd, ifname, query_buf, query_buf_size, num_sources, dst_addr, group_addr, query_max_response_time_dsec, s_flag, querier_robustness_variable, querier_query_interval); } else if (igmp_version == 2) { igmp_v2_send_query(group, fd, ifname, query_buf, dst_addr, group_addr, query_max_response_time_dsec); } } void igmp_send_query_on_intf(struct interface *ifp, int igmp_ver) { struct pim_interface *pim_ifp = ifp->info; struct listnode *sock_node = NULL; struct igmp_sock *igmp = NULL; struct in_addr dst_addr; struct in_addr group_addr; int query_buf_size; if (!igmp_ver) igmp_ver = 2; if (igmp_ver == 3) query_buf_size = PIM_IGMP_BUFSIZE_WRITE; else query_buf_size = IGMP_V12_MSG_SIZE; dst_addr.s_addr = htonl(INADDR_ALLHOSTS_GROUP); group_addr.s_addr = PIM_NET_INADDR_ANY; if (PIM_DEBUG_IGMP_TRACE) zlog_debug("Issuing general query on request on %s", ifp->name); for (ALL_LIST_ELEMENTS_RO(pim_ifp->igmp_socket_list, sock_node, igmp)) { char query_buf[query_buf_size]; igmp_send_query(igmp_ver, 0 /* igmp_group */, igmp->fd, igmp->interface->name, query_buf, sizeof(query_buf), 0 /* num_sources */, dst_addr, group_addr, pim_ifp->igmp_query_max_response_time_dsec, 1 /* s_flag: always set for general queries */, igmp->querier_robustness_variable, igmp->querier_query_interval); } } frr-7.2.1/pimd/pim_igmp.h0000644000000000000000000001616513610377563012130 00000000000000/* * PIM for Quagga * Copyright (C) 2008 Everton da Silva Marques * * This program is free software; you can redistribute it and/or modify * it under the terms of the 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; see the file COPYING; if not, write to the Free Software * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA */ #ifndef PIM_IGMP_H #define PIM_IGMP_H #include #include #include "vty.h" #include "linklist.h" #include "pim_igmp_stats.h" /* The following sizes are likely to support any message sent within local MTU. */ #define PIM_IGMP_BUFSIZE_READ (20000) #define PIM_IGMP_BUFSIZE_WRITE (20000) #define PIM_IGMP_MEMBERSHIP_QUERY (0x11) #define PIM_IGMP_V1_MEMBERSHIP_REPORT (0x12) #define PIM_IGMP_V2_MEMBERSHIP_REPORT (0x16) #define PIM_IGMP_V2_LEAVE_GROUP (0x17) #define PIM_IGMP_MTRACE_RESPONSE (0x1E) #define PIM_IGMP_MTRACE_QUERY_REQUEST (0x1F) #define PIM_IGMP_V3_MEMBERSHIP_REPORT (0x22) #define IGMP_V3_REPORT_HEADER_SIZE (8) #define IGMP_V3_GROUP_RECORD_MIN_SIZE (8) #define IGMP_V3_MSG_MIN_SIZE \ (IGMP_V3_REPORT_HEADER_SIZE + IGMP_V3_GROUP_RECORD_MIN_SIZE) #define IGMP_V12_MSG_SIZE (8) #define IGMP_V3_GROUP_RECORD_TYPE_OFFSET (0) #define IGMP_V3_GROUP_RECORD_AUXDATALEN_OFFSET (1) #define IGMP_V3_GROUP_RECORD_NUMSOURCES_OFFSET (2) #define IGMP_V3_GROUP_RECORD_GROUP_OFFSET (4) #define IGMP_V3_GROUP_RECORD_SOURCE_OFFSET (8) #define IGMP_CHECKSUM_OFFSET (2) /* RFC 3376: 8.1. Robustness Variable - Default: 2 */ #define IGMP_DEFAULT_ROBUSTNESS_VARIABLE (2) /* RFC 3376: 8.2. Query Interval - Default: 125 seconds */ #define IGMP_GENERAL_QUERY_INTERVAL (125) /* RFC 3376: 8.3. Query Response Interval - Default: 100 deciseconds */ #define IGMP_QUERY_MAX_RESPONSE_TIME_DSEC (100) /* RFC 3376: 8.8. Last Member Query Interval - Default: 10 deciseconds */ #define IGMP_SPECIFIC_QUERY_MAX_RESPONSE_TIME_DSEC (10) #define IGMP_DEFAULT_VERSION (3) struct igmp_join { struct in_addr group_addr; struct in_addr source_addr; int sock_fd; time_t sock_creation; }; struct igmp_sock { int fd; struct interface *interface; struct in_addr ifaddr; time_t sock_creation; struct thread *t_igmp_read; /* read: IGMP sockets */ struct thread *t_igmp_query_timer; /* timer: issue IGMP general queries */ struct thread *t_other_querier_timer; /* timer: other querier present */ int querier_query_interval; /* QQI */ int querier_robustness_variable; /* QRV */ int startup_query_count; bool mtrace_only; struct list *igmp_group_list; /* list of struct igmp_group */ struct hash *igmp_group_hash; struct igmp_stats rx_stats; }; struct igmp_sock *pim_igmp_sock_lookup_ifaddr(struct list *igmp_sock_list, struct in_addr ifaddr); struct igmp_sock *igmp_sock_lookup_by_fd(struct list *igmp_sock_list, int fd); struct igmp_sock *pim_igmp_sock_add(struct list *igmp_sock_list, struct in_addr ifaddr, struct interface *ifp, bool mtrace_only); void igmp_sock_delete(struct igmp_sock *igmp); void igmp_sock_free(struct igmp_sock *igmp); void igmp_sock_delete_all(struct interface *ifp); int pim_igmp_packet(struct igmp_sock *igmp, char *buf, size_t len); void pim_igmp_general_query_on(struct igmp_sock *igmp); void pim_igmp_general_query_off(struct igmp_sock *igmp); void pim_igmp_other_querier_timer_on(struct igmp_sock *igmp); void pim_igmp_other_querier_timer_off(struct igmp_sock *igmp); #define IGMP_SOURCE_MASK_FORWARDING (1 << 0) #define IGMP_SOURCE_MASK_DELETE (1 << 1) #define IGMP_SOURCE_MASK_SEND (1 << 2) #define IGMP_SOURCE_TEST_FORWARDING(flags) ((flags) & IGMP_SOURCE_MASK_FORWARDING) #define IGMP_SOURCE_TEST_DELETE(flags) ((flags) & IGMP_SOURCE_MASK_DELETE) #define IGMP_SOURCE_TEST_SEND(flags) ((flags) & IGMP_SOURCE_MASK_SEND) #define IGMP_SOURCE_DO_FORWARDING(flags) ((flags) |= IGMP_SOURCE_MASK_FORWARDING) #define IGMP_SOURCE_DO_DELETE(flags) ((flags) |= IGMP_SOURCE_MASK_DELETE) #define IGMP_SOURCE_DO_SEND(flags) ((flags) |= IGMP_SOURCE_MASK_SEND) #define IGMP_SOURCE_DONT_FORWARDING(flags) ((flags) &= ~IGMP_SOURCE_MASK_FORWARDING) #define IGMP_SOURCE_DONT_DELETE(flags) ((flags) &= ~IGMP_SOURCE_MASK_DELETE) #define IGMP_SOURCE_DONT_SEND(flags) ((flags) &= ~IGMP_SOURCE_MASK_SEND) struct igmp_source { struct in_addr source_addr; struct thread *t_source_timer; struct igmp_group *source_group; /* back pointer */ time_t source_creation; uint32_t source_flags; struct channel_oil *source_channel_oil; /* RFC 3376: 6.6.3.2. Building and Sending Group and Source Specific Queries */ int source_query_retransmit_count; }; struct igmp_group { /* RFC 3376: 6.2.2. Definition of Group Timers The group timer is only used when a group is in EXCLUDE mode and it represents the time for the *filter-mode* of the group to expire and switch to INCLUDE mode. */ struct thread *t_group_timer; /* Shared between group-specific and group-and-source-specific retransmissions */ struct thread *t_group_query_retransmit_timer; /* Counter exclusive for group-specific retransmissions (not used by group-and-source-specific retransmissions, since sources have their counters) */ int group_specific_query_retransmit_count; /* compatibility mode - igmp v1, v2 or v3 */ int igmp_version; struct in_addr group_addr; int group_filtermode_isexcl; /* 0=INCLUDE, 1=EXCLUDE */ struct list *group_source_list; /* list of struct igmp_source */ time_t group_creation; struct igmp_sock *group_igmp_sock; /* back pointer */ int64_t last_igmp_v1_report_dsec; int64_t last_igmp_v2_report_dsec; }; struct igmp_group *find_group_by_addr(struct igmp_sock *igmp, struct in_addr group_addr); struct igmp_group *igmp_add_group_by_addr(struct igmp_sock *igmp, struct in_addr group_addr); void igmp_group_delete_empty_include(struct igmp_group *group); void igmp_startup_mode_on(struct igmp_sock *igmp); void igmp_group_timer_on(struct igmp_group *group, long interval_msec, const char *ifname); struct igmp_source *source_new(struct igmp_group *group, struct in_addr src_addr); void igmp_send_query(int igmp_version, struct igmp_group *group, int fd, const char *ifname, char *query_buf, int query_buf_size, int num_sources, struct in_addr dst_addr, struct in_addr group_addr, int query_max_response_time_dsec, uint8_t s_flag, uint8_t querier_robustness_variable, uint16_t querier_query_interval); void igmp_group_delete(struct igmp_group *group); void igmp_send_query_on_intf(struct interface *ifp, int igmp_ver); #endif /* PIM_IGMP_H */ frr-7.2.1/pimd/pim_igmp_join.h0000644000000000000000000000357513610377563013150 00000000000000/* * PIM for Quagga * Copyright (C) 2008 Everton da Silva Marques * * This program is free software; you can redistribute it and/or modify * it under the terms of the 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; see the file COPYING; if not, write to the Free Software * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA */ #ifndef PIM_IGMP_JOIN_H #define PIM_IGMP_JOIN_H /* required headers #include'd by caller */ #ifndef SOL_IP #define SOL_IP IPPROTO_IP #endif #ifndef MCAST_JOIN_SOURCE_GROUP #define MCAST_JOIN_SOURCE_GROUP 46 struct group_source_req { uint32_t gsr_interface; struct sockaddr_storage gsr_group; struct sockaddr_storage gsr_source; }; #endif static int pim_igmp_join_source(int fd, ifindex_t ifindex, struct in_addr group_addr, struct in_addr source_addr) { struct group_source_req req; struct sockaddr_in group; struct sockaddr_in source; memset(&req, 0, sizeof(req)); memset(&group, 0, sizeof(group)); group.sin_family = AF_INET; group.sin_addr = group_addr; group.sin_port = htons(0); memcpy(&req.gsr_group, &group, sizeof(struct sockaddr_in)); memset(&source, 0, sizeof(source)); source.sin_family = AF_INET; source.sin_addr = source_addr; source.sin_port = htons(0); memcpy(&req.gsr_source, &source, sizeof(struct sockaddr_in)); req.gsr_interface = ifindex; return setsockopt(fd, SOL_IP, MCAST_JOIN_SOURCE_GROUP, &req, sizeof(req)); } #endif /* PIM_IGMP_JOIN_H */ frr-7.2.1/pimd/pim_igmp_mtrace.c0000644000000000000000000005567013610377563013462 00000000000000/* * Multicast traceroute for FRRouting * Copyright (C) 2017 Mladen Sablic * * This program is free software; you can redistribute it and/or modify * it under the terms of the 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; see the file COPYING; if not, write to the Free Software * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA */ /* based on draft-ietf-idmr-traceroute-ipm-07 */ #include #include "pimd.h" #include "pim_util.h" #include "pim_sock.h" #include "pim_rp.h" #include "pim_oil.h" #include "pim_ifchannel.h" #include "pim_macro.h" #include "pim_igmp_mtrace.h" static struct in_addr mtrace_primary_address(struct interface *ifp) { struct connected *ifc; struct listnode *node; struct in_addr any; struct pim_interface *pim_ifp; if (ifp->info) { pim_ifp = ifp->info; return pim_ifp->primary_address; } any.s_addr = INADDR_ANY; for (ALL_LIST_ELEMENTS_RO(ifp->connected, node, ifc)) { struct prefix *p = ifc->address; if (p->family != AF_INET) continue; if (!CHECK_FLAG(ifc->flags, ZEBRA_IFA_SECONDARY)) return p->u.prefix4; /* in case no primary found, return a secondary */ any = p->u.prefix4; } return any; } static bool mtrace_fwd_info_weak(struct pim_instance *pim, struct igmp_mtrace *mtracep, struct igmp_mtrace_rsp *rspp, struct interface **ifpp) { struct pim_nexthop nexthop; struct interface *ifp_in; struct in_addr nh_addr; char nexthop_str[INET_ADDRSTRLEN]; nh_addr.s_addr = 0; memset(&nexthop, 0, sizeof(nexthop)); if (!pim_nexthop_lookup(pim, &nexthop, mtracep->src_addr, 1)) { if (PIM_DEBUG_MTRACE) zlog_debug("mtrace not found neighbor"); return false; } if (PIM_DEBUG_MTRACE) zlog_debug("mtrace pim_nexthop_lookup OK"); if (PIM_DEBUG_MTRACE) zlog_warn("mtrace next_hop=%s", inet_ntop(nexthop.mrib_nexthop_addr.family, &nexthop.mrib_nexthop_addr.u.prefix, nexthop_str, sizeof(nexthop_str))); if (nexthop.mrib_nexthop_addr.family == AF_INET) nh_addr = nexthop.mrib_nexthop_addr.u.prefix4; ifp_in = nexthop.interface; /* return interface for forwarding mtrace packets */ *ifpp = ifp_in; /* 6.2.2. 4. Fill in the Incoming Interface Address... */ rspp->incoming = mtrace_primary_address(ifp_in); rspp->prev_hop = nh_addr; rspp->in_count = htonl(MTRACE_UNKNOWN_COUNT); rspp->total = htonl(MTRACE_UNKNOWN_COUNT); rspp->rtg_proto = MTRACE_RTG_PROTO_PIM; return true; } static bool mtrace_fwd_info(struct pim_instance *pim, struct igmp_mtrace *mtracep, struct igmp_mtrace_rsp *rspp, struct interface **ifpp) { struct prefix_sg sg; struct pim_upstream *up; struct interface *ifp_in; struct in_addr nh_addr; uint32_t total; char up_str[INET_ADDRSTRLEN]; memset(&sg, 0, sizeof(struct prefix_sg)); sg.src = mtracep->src_addr; sg.grp = mtracep->grp_addr; up = pim_upstream_find(pim, &sg); if (!up) { sg.src.s_addr = 0; up = pim_upstream_find(pim, &sg); } if (!up) return false; if (!up->rpf.source_nexthop.interface) { if (PIM_DEBUG_TRACE) zlog_debug("%s: up %s RPF is not present", __PRETTY_FUNCTION__, up->sg_str); return false; } ifp_in = up->rpf.source_nexthop.interface; nh_addr = up->rpf.source_nexthop.mrib_nexthop_addr.u.prefix4; total = htonl(MTRACE_UNKNOWN_COUNT); if (PIM_DEBUG_MTRACE) zlog_debug("fwd_info: upstream next hop=%s", inet_ntop(AF_INET, &(nh_addr), up_str, sizeof(up_str))); if (up->channel_oil) total = up->channel_oil->cc.pktcnt; /* return interface for forwarding mtrace packets */ *ifpp = ifp_in; /* 6.2.2. 4. Fill in the Incoming Interface Address... */ rspp->incoming = mtrace_primary_address(ifp_in); rspp->prev_hop = nh_addr; rspp->in_count = htonl(MTRACE_UNKNOWN_COUNT); rspp->total = total; rspp->rtg_proto = MTRACE_RTG_PROTO_PIM; /* 6.2.2. 4. Fill in ... S, and Src Mask */ if (sg.src.s_addr) { rspp->s = 1; rspp->src_mask = MTRACE_SRC_MASK_SOURCE; } else { rspp->s = 0; rspp->src_mask = MTRACE_SRC_MASK_GROUP; } return true; } static void mtrace_rsp_set_fwd_code(struct igmp_mtrace_rsp *mtrace_rspp, enum mtrace_fwd_code fwd_code) { if (mtrace_rspp->fwd_code == MTRACE_FWD_CODE_NO_ERROR) mtrace_rspp->fwd_code = fwd_code; } static void mtrace_rsp_init(struct igmp_mtrace_rsp *mtrace_rspp) { mtrace_rspp->arrival = 0; mtrace_rspp->incoming.s_addr = 0; mtrace_rspp->outgoing.s_addr = 0; mtrace_rspp->prev_hop.s_addr = 0; mtrace_rspp->in_count = htonl(MTRACE_UNKNOWN_COUNT); mtrace_rspp->out_count = htonl(MTRACE_UNKNOWN_COUNT); mtrace_rspp->total = htonl(MTRACE_UNKNOWN_COUNT); mtrace_rspp->rtg_proto = 0; mtrace_rspp->fwd_ttl = 0; mtrace_rspp->mbz = 0; mtrace_rspp->s = 0; mtrace_rspp->src_mask = 0; mtrace_rspp->fwd_code = MTRACE_FWD_CODE_NO_ERROR; } static void mtrace_rsp_debug(uint32_t qry_id, int rsp, struct igmp_mtrace_rsp *mrspp) { char inc_str[INET_ADDRSTRLEN]; char out_str[INET_ADDRSTRLEN]; char prv_str[INET_ADDRSTRLEN]; zlog_debug( "Rx mt(%d) qid=%ud arr=%x in=%s out=%s prev=%s proto=%d fwd=%d", rsp, ntohl(qry_id), mrspp->arrival, inet_ntop(AF_INET, &(mrspp->incoming), inc_str, sizeof(inc_str)), inet_ntop(AF_INET, &(mrspp->outgoing), out_str, sizeof(out_str)), inet_ntop(AF_INET, &(mrspp->prev_hop), prv_str, sizeof(prv_str)), mrspp->rtg_proto, mrspp->fwd_code); } static void mtrace_debug(struct pim_interface *pim_ifp, struct igmp_mtrace *mtracep, int mtrace_len) { char inc_str[INET_ADDRSTRLEN]; char grp_str[INET_ADDRSTRLEN]; char src_str[INET_ADDRSTRLEN]; char dst_str[INET_ADDRSTRLEN]; char rsp_str[INET_ADDRSTRLEN]; struct in_addr ga, sa, da, ra; ga = mtracep->grp_addr; sa = mtracep->src_addr; da = mtracep->dst_addr; ra = mtracep->rsp_addr; zlog_debug( "Rx mtrace packet incoming on %s: " "hops=%d type=%d size=%d, grp=%s, src=%s," " dst=%s rsp=%s ttl=%d qid=%ud", inet_ntop(AF_INET, &(pim_ifp->primary_address), inc_str, sizeof(inc_str)), mtracep->hops, mtracep->type, mtrace_len, inet_ntop(AF_INET, &ga, grp_str, sizeof(grp_str)), inet_ntop(AF_INET, &sa, src_str, sizeof(src_str)), inet_ntop(AF_INET, &da, dst_str, sizeof(dst_str)), inet_ntop(AF_INET, &ra, rsp_str, sizeof(rsp_str)), mtracep->rsp_ttl, ntohl(mtracep->qry_id)); if (mtrace_len > (int)sizeof(struct igmp_mtrace)) { int i; int responses = mtrace_len - sizeof(struct igmp_mtrace); if ((responses % sizeof(struct igmp_mtrace_rsp)) != 0) if (PIM_DEBUG_MTRACE) zlog_debug( "Mtrace response block of wrong" " length"); responses = responses / sizeof(struct igmp_mtrace_rsp); for (i = 0; i < responses; i++) mtrace_rsp_debug(mtracep->qry_id, i, &mtracep->rsp[i]); } } /* 5.1 Query Arrival Time */ static uint32_t query_arrival_time(void) { struct timeval tv; uint32_t qat; char m_qat[] = "Query arrival time lookup failed: errno=%d: %s"; if (gettimeofday(&tv, NULL) < 0) { if (PIM_DEBUG_MTRACE) zlog_warn(m_qat, errno, safe_strerror(errno)); return 0; } /* not sure second offset correct, as I get different value */ qat = ((tv.tv_sec + 32384) << 16) + ((tv.tv_usec << 10) / 15625); return qat; } static int mtrace_send_packet(struct interface *ifp, struct igmp_mtrace *mtracep, size_t mtrace_buf_len, struct in_addr dst_addr, struct in_addr group_addr) { struct sockaddr_in to; socklen_t tolen; ssize_t sent; int ret; int fd; char if_str[INET_ADDRSTRLEN]; char rsp_str[INET_ADDRSTRLEN]; uint8_t ttl; memset(&to, 0, sizeof(to)); to.sin_family = AF_INET; to.sin_addr = dst_addr; tolen = sizeof(to); if (PIM_DEBUG_MTRACE) { struct in_addr if_addr; if_addr = mtrace_primary_address(ifp); zlog_debug( "Sending mtrace packet to %s on %s", inet_ntop(AF_INET, &mtracep->rsp_addr, rsp_str, sizeof(rsp_str)), inet_ntop(AF_INET, &if_addr, if_str, sizeof(if_str))); } fd = pim_socket_raw(IPPROTO_IGMP); if (fd < 0) return -1; ret = pim_socket_bind(fd, ifp); if (ret < 0) { ret = -1; goto close_fd; } if (IPV4_CLASS_DE(ntohl(dst_addr.s_addr))) { if (IPV4_MC_LINKLOCAL(ntohl(dst_addr.s_addr))) { ttl = 1; } else { if (mtracep->type == PIM_IGMP_MTRACE_RESPONSE) ttl = mtracep->rsp_ttl; else ttl = 64; } ret = setsockopt(fd, IPPROTO_IP, IP_MULTICAST_TTL, &ttl, sizeof(ttl)); if (ret < 0) { if (PIM_DEBUG_MTRACE) zlog_warn("Failed to set socket multicast TTL"); ret = -1; goto close_fd; } } sent = sendto(fd, (char *)mtracep, mtrace_buf_len, MSG_DONTWAIT, (struct sockaddr *)&to, tolen); if (sent != (ssize_t)mtrace_buf_len) { char dst_str[INET_ADDRSTRLEN]; char group_str[INET_ADDRSTRLEN]; pim_inet4_dump("", dst_addr, dst_str, sizeof(dst_str)); pim_inet4_dump("", group_addr, group_str, sizeof(group_str)); if (sent < 0) { if (PIM_DEBUG_MTRACE) zlog_warn( "Send mtrace request failed for %s on" "%s: group=%s msg_size=%zd: errno=%d: " " %s", dst_str, ifp->name, group_str, mtrace_buf_len, errno, safe_strerror(errno)); } else { if (PIM_DEBUG_MTRACE) zlog_warn( "Send mtrace request failed for %s on" " %s: group=%s msg_size=%zd: sent=%zd", dst_str, ifp->name, group_str, mtrace_buf_len, sent); } ret = -1; goto close_fd; } ret = 0; close_fd: close(fd); return ret; } static int mtrace_un_forward_packet(struct pim_instance *pim, struct ip *ip_hdr, struct interface *interface) { struct pim_nexthop nexthop; struct sockaddr_in to; struct interface *if_out; socklen_t tolen; int ret; int fd; int sent; uint16_t checksum; checksum = ip_hdr->ip_sum; ip_hdr->ip_sum = 0; if (checksum != in_cksum(ip_hdr, ip_hdr->ip_hl * 4)) return -1; if (ip_hdr->ip_ttl-- <= 1) return -1; ip_hdr->ip_sum = in_cksum(ip_hdr, ip_hdr->ip_hl * 4); fd = pim_socket_raw(IPPROTO_RAW); if (fd < 0) return -1; pim_socket_ip_hdr(fd); if (interface == NULL) { memset(&nexthop, 0, sizeof(nexthop)); if (!pim_nexthop_lookup(pim, &nexthop, ip_hdr->ip_dst, 0)) { close(fd); if (PIM_DEBUG_MTRACE) zlog_warn( "Dropping mtrace packet, " "no route to destination"); return -1; } if_out = nexthop.interface; } else { if_out = interface; } ret = pim_socket_bind(fd, if_out); if (ret < 0) { close(fd); return -1; } memset(&to, 0, sizeof(to)); to.sin_family = AF_INET; to.sin_addr = ip_hdr->ip_dst; tolen = sizeof(to); sent = sendto(fd, ip_hdr, ntohs(ip_hdr->ip_len), 0, (struct sockaddr *)&to, tolen); close(fd); if (sent < 0) { if (PIM_DEBUG_MTRACE) zlog_warn( "Failed to forward mtrace packet:" " sendto errno=%d, %s", errno, safe_strerror(errno)); return -1; } if (PIM_DEBUG_MTRACE) { zlog_debug("Fwd mtrace packet len=%u to %s ttl=%u", ntohs(ip_hdr->ip_len), inet_ntoa(ip_hdr->ip_dst), ip_hdr->ip_ttl); } return 0; } static int mtrace_mc_forward_packet(struct pim_instance *pim, struct ip *ip_hdr) { struct prefix_sg sg; struct channel_oil *c_oil; struct listnode *chnode; struct listnode *chnextnode; struct pim_ifchannel *ch = NULL; int ret = -1; memset(&sg, 0, sizeof(struct prefix_sg)); sg.grp = ip_hdr->ip_dst; c_oil = pim_find_channel_oil(pim, &sg); if (c_oil == NULL) { if (PIM_DEBUG_MTRACE) { zlog_debug( "Dropping mtrace multicast packet " "len=%u to %s ttl=%u", ntohs(ip_hdr->ip_len), inet_ntoa(ip_hdr->ip_dst), ip_hdr->ip_ttl); } return -1; } if (c_oil->up == NULL) return -1; if (c_oil->up->ifchannels == NULL) return -1; for (ALL_LIST_ELEMENTS(c_oil->up->ifchannels, chnode, chnextnode, ch)) { if (pim_macro_chisin_oiflist(ch)) { int r; r = mtrace_un_forward_packet(pim, ip_hdr, ch->interface); if (r == 0) ret = 0; } } return ret; } static int mtrace_forward_packet(struct pim_instance *pim, struct ip *ip_hdr) { if (IPV4_CLASS_DE(ntohl(ip_hdr->ip_dst.s_addr))) return mtrace_mc_forward_packet(pim, ip_hdr); else return mtrace_un_forward_packet(pim, ip_hdr, NULL); } static int mtrace_send_mc_response(struct pim_instance *pim, struct igmp_mtrace *mtracep, size_t mtrace_len) { struct prefix_sg sg; struct channel_oil *c_oil; struct listnode *chnode; struct listnode *chnextnode; struct pim_ifchannel *ch = NULL; int ret = -1; memset(&sg, 0, sizeof(struct prefix_sg)); sg.grp = mtracep->rsp_addr; c_oil = pim_find_channel_oil(pim, &sg); if (c_oil == NULL) { if (PIM_DEBUG_MTRACE) { zlog_debug( "Dropping mtrace multicast response packet " "len=%u to %s", (unsigned int)mtrace_len, inet_ntoa(mtracep->rsp_addr)); } return -1; } if (c_oil->up == NULL) return -1; if (c_oil->up->ifchannels == NULL) return -1; for (ALL_LIST_ELEMENTS(c_oil->up->ifchannels, chnode, chnextnode, ch)) { if (pim_macro_chisin_oiflist(ch)) { int r; r = mtrace_send_packet(ch->interface, mtracep, mtrace_len, mtracep->rsp_addr, mtracep->grp_addr); if (r == 0) ret = 0; } } return ret; } /* 6.5 Sending Traceroute Responses */ static int mtrace_send_response(struct pim_instance *pim, struct igmp_mtrace *mtracep, size_t mtrace_len) { struct pim_nexthop nexthop; mtracep->type = PIM_IGMP_MTRACE_RESPONSE; mtracep->checksum = 0; mtracep->checksum = in_cksum((char *)mtracep, mtrace_len); if (IPV4_CLASS_DE(ntohl(mtracep->rsp_addr.s_addr))) { struct pim_rpf *p_rpf; char grp_str[INET_ADDRSTRLEN]; if (pim_rp_i_am_rp(pim, mtracep->rsp_addr)) return mtrace_send_mc_response(pim, mtracep, mtrace_len); p_rpf = pim_rp_g(pim, mtracep->rsp_addr); if (p_rpf == NULL) { if (PIM_DEBUG_MTRACE) zlog_warn("mtrace no RP for %s", inet_ntop(AF_INET, &(mtracep->rsp_addr), grp_str, sizeof(grp_str))); return -1; } nexthop = p_rpf->source_nexthop; if (PIM_DEBUG_MTRACE) zlog_debug("mtrace response to RP"); } else { memset(&nexthop, 0, sizeof(nexthop)); /* TODO: should use unicast rib lookup */ if (!pim_nexthop_lookup(pim, &nexthop, mtracep->rsp_addr, 1)) { if (PIM_DEBUG_MTRACE) zlog_warn( "Dropped response qid=%ud, no route to " "response address", mtracep->qry_id); return -1; } } return mtrace_send_packet(nexthop.interface, mtracep, mtrace_len, mtracep->rsp_addr, mtracep->grp_addr); } int igmp_mtrace_recv_qry_req(struct igmp_sock *igmp, struct ip *ip_hdr, struct in_addr from, const char *from_str, char *igmp_msg, int igmp_msg_len) { static uint32_t qry_id, qry_src; char mtrace_buf[MTRACE_HDR_SIZE + MTRACE_MAX_HOPS * MTRACE_RSP_SIZE]; struct interface *ifp; struct interface *out_ifp = NULL; struct pim_interface *pim_ifp; struct pim_instance *pim; struct igmp_mtrace *mtracep; struct igmp_mtrace_rsp *rspp; struct in_addr nh_addr; enum mtrace_fwd_code fwd_code = MTRACE_FWD_CODE_NO_ERROR; size_t r_len; int last_rsp_ind = 0; size_t mtrace_len; uint16_t recv_checksum; uint16_t checksum; bool reached_source; bool fwd_info; ifp = igmp->interface; pim_ifp = ifp->info; pim = pim_ifp->pim; /* * 6. Router Behaviour * Check if mtrace packet is addressed elsewhere and forward, * if applicable */ if (!IPV4_CLASS_DE(ntohl(ip_hdr->ip_dst.s_addr))) if (!if_lookup_exact_address(&ip_hdr->ip_dst, AF_INET, pim->vrf_id)) return mtrace_forward_packet(pim, ip_hdr); if (igmp_msg_len < (int)sizeof(struct igmp_mtrace)) { if (PIM_DEBUG_MTRACE) zlog_warn( "Recv mtrace packet from %s on %s: too short," " len=%d, min=%zu", from_str, ifp->name, igmp_msg_len, sizeof(struct igmp_mtrace)); return -1; } mtracep = (struct igmp_mtrace *)igmp_msg; recv_checksum = mtracep->checksum; mtracep->checksum = 0; checksum = in_cksum(igmp_msg, igmp_msg_len); if (recv_checksum != checksum) { if (PIM_DEBUG_MTRACE) zlog_warn( "Recv mtrace packet from %s on %s: checksum" " mismatch: received=%x computed=%x", from_str, ifp->name, recv_checksum, checksum); return -1; } /* Collecting IGMP Rx stats */ igmp->rx_stats.mtrace_req++; if (PIM_DEBUG_MTRACE) mtrace_debug(pim_ifp, mtracep, igmp_msg_len); /* subtract header from message length */ r_len = igmp_msg_len - sizeof(struct igmp_mtrace); /* Classify mtrace packet, check if it is a query */ if (!r_len) { if (PIM_DEBUG_MTRACE) zlog_debug("Received IGMP multicast traceroute query"); /* 6.1.1 Packet verification */ if (!pim_if_connected_to_source(ifp, mtracep->dst_addr)) { if (IPV4_CLASS_DE(ntohl(ip_hdr->ip_dst.s_addr))) { if (PIM_DEBUG_MTRACE) zlog_debug( "Dropping multicast query " "on wrong interface"); return -1; } /* Unicast query on wrong interface */ fwd_code = MTRACE_FWD_CODE_WRONG_IF; if (PIM_DEBUG_MTRACE) zlog_debug("Multicast query on wrong interface"); } if (qry_id == mtracep->qry_id && qry_src == from.s_addr) { if (PIM_DEBUG_MTRACE) zlog_debug( "Dropping multicast query with " "duplicate source and id"); return -1; } qry_id = mtracep->qry_id; qry_src = from.s_addr; } /* if response fields length is equal to a whole number of responses */ else if ((r_len % sizeof(struct igmp_mtrace_rsp)) == 0) { r_len = igmp_msg_len - sizeof(struct igmp_mtrace); if (r_len != 0) last_rsp_ind = r_len / sizeof(struct igmp_mtrace_rsp); if (last_rsp_ind > MTRACE_MAX_HOPS) { if (PIM_DEBUG_MTRACE) zlog_warn("Mtrace request of excessive size"); return -1; } } else { if (PIM_DEBUG_MTRACE) zlog_warn( "Recv mtrace packet from %s on %s: " "invalid length %d", from_str, ifp->name, igmp_msg_len); return -1; } /* 6.2.1 Packet Verification - drop not link-local multicast */ if (IPV4_CLASS_DE(ntohl(ip_hdr->ip_dst.s_addr)) && !IPV4_MC_LINKLOCAL(ntohl(ip_hdr->ip_dst.s_addr))) { if (PIM_DEBUG_MTRACE) zlog_warn( "Recv mtrace packet from %s on %s:" " not link-local multicast %s", from_str, ifp->name, inet_ntoa(ip_hdr->ip_dst)); return -1; } /* 6.2.2. Normal Processing */ /* 6.2.2. 1. If there is room in the current buffer? */ if (last_rsp_ind == MTRACE_MAX_HOPS) { /* ...there was no room... */ mtracep->rsp[MTRACE_MAX_HOPS - 1].fwd_code = MTRACE_FWD_CODE_NO_SPACE; return mtrace_send_response(pim_ifp->pim, mtracep, igmp_msg_len); } /* ...insert new response block... */ /* calculate new mtrace lenght with extra response */ mtrace_len = igmp_msg_len + sizeof(struct igmp_mtrace_rsp); /* copy received query/request */ memcpy(mtrace_buf, igmp_msg, igmp_msg_len); /* repoint mtracep pointer to copy */ mtracep = (struct igmp_mtrace *)mtrace_buf; /* pointer for extra response field to be filled in */ rspp = &mtracep->rsp[last_rsp_ind]; /* initialize extra response field */ mtrace_rsp_init(rspp); /* carry over any error noted when receiving the query */ rspp->fwd_code = fwd_code; /* ...and fill in Query Arrival Time... */ rspp->arrival = htonl(query_arrival_time()); rspp->outgoing = pim_ifp->primary_address; rspp->out_count = htonl(MTRACE_UNKNOWN_COUNT); rspp->fwd_ttl = 1; /* 6.2.2. 2. Attempt to determine the forwarding information... */ if (mtracep->grp_addr.s_addr) fwd_info = mtrace_fwd_info(pim, mtracep, rspp, &out_ifp); else fwd_info = mtrace_fwd_info_weak(pim, mtracep, rspp, &out_ifp); /* 6.2.2 3. If no forwarding information... */ if (!fwd_info) { if (PIM_DEBUG_MTRACE) zlog_debug("mtrace not found multicast state"); mtrace_rsp_set_fwd_code(rspp, MTRACE_FWD_CODE_NO_ROUTE); /* 6.2.2. 3. forward the packet to requester */ return mtrace_send_response(pim, mtracep, mtrace_len); } nh_addr = rspp->prev_hop; reached_source = false; if (nh_addr.s_addr == 0) { /* no pim? i.e. 7.5.3. No Previous Hop */ if (!out_ifp->info) { if (PIM_DEBUG_MTRACE) zlog_debug("mtrace not found incoming if w/ pim"); mtrace_rsp_set_fwd_code(rspp, MTRACE_FWD_CODE_NO_MULTICAST); return mtrace_send_response(pim, mtracep, mtrace_len); } /* reached source? i.e. 7.5.1 Arriving at source */ if (pim_if_connected_to_source(out_ifp, mtracep->src_addr)) { reached_source = true; rspp->prev_hop = mtracep->src_addr; } /* * 6.4 Forwarding Traceroute Requests: * Previous-hop router not known, * packet is sent to an appropriate multicast address */ (void)inet_aton(MCAST_ALL_ROUTERS, &nh_addr); } /* 6.2.2 8. If this router is the Rendez-vous Point */ if (pim_rp_i_am_rp(pim, mtracep->grp_addr)) { mtrace_rsp_set_fwd_code(rspp, MTRACE_FWD_CODE_REACHED_RP); /* 7.7.1. PIM-SM ...RP has not performed source-specific join */ if (rspp->src_mask == MTRACE_SRC_MASK_GROUP) return mtrace_send_response(pim, mtracep, mtrace_len); } /* * 6.4 Forwarding Traceroute Requests: the number of response * blocks exceeds number of responses, so forward to the requester. */ if (mtracep->hops <= (last_rsp_ind + 1)) return mtrace_send_response(pim, mtracep, mtrace_len); /* 7.5.1. Arriving at source: terminate trace */ if (reached_source) return mtrace_send_response(pim, mtracep, mtrace_len); mtracep->checksum = 0; mtracep->checksum = in_cksum(mtrace_buf, mtrace_len); /* 6.4 Forwarding Traceroute Requests: response blocks less than req. */ return mtrace_send_packet(out_ifp, mtracep, mtrace_len, nh_addr, mtracep->grp_addr); } /* 6.3. Traceroute responses */ int igmp_mtrace_recv_response(struct igmp_sock *igmp, struct ip *ip_hdr, struct in_addr from, const char *from_str, char *igmp_msg, int igmp_msg_len) { static uint32_t qry_id, rsp_dst; struct interface *ifp; struct pim_interface *pim_ifp; struct pim_instance *pim; struct igmp_mtrace *mtracep; uint16_t recv_checksum; uint16_t checksum; ifp = igmp->interface; pim_ifp = ifp->info; pim = pim_ifp->pim; if (igmp_msg_len < (int)sizeof(struct igmp_mtrace)) { if (PIM_DEBUG_MTRACE) zlog_warn( "Recv mtrace packet from %s on %s: too short," " len=%d, min=%zu", from_str, ifp->name, igmp_msg_len, sizeof(struct igmp_mtrace)); return -1; } mtracep = (struct igmp_mtrace *)igmp_msg; recv_checksum = mtracep->checksum; mtracep->checksum = 0; checksum = in_cksum(igmp_msg, igmp_msg_len); if (recv_checksum != checksum) { if (PIM_DEBUG_MTRACE) zlog_warn( "Recv mtrace response from %s on %s: checksum" " mismatch: received=%x computed=%x", from_str, ifp->name, recv_checksum, checksum); return -1; } mtracep->checksum = checksum; /* Collecting IGMP Rx stats */ igmp->rx_stats.mtrace_rsp++; if (PIM_DEBUG_MTRACE) mtrace_debug(pim_ifp, mtracep, igmp_msg_len); /* Drop duplicate packets */ if (qry_id == mtracep->qry_id && rsp_dst == ip_hdr->ip_dst.s_addr) { if (PIM_DEBUG_MTRACE) zlog_debug("duplicate mtrace response packet dropped"); return -1; } qry_id = mtracep->qry_id; rsp_dst = ip_hdr->ip_dst.s_addr; return mtrace_forward_packet(pim, ip_hdr); } frr-7.2.1/pimd/pim_igmp_mtrace.h0000644000000000000000000000623613610377563013461 00000000000000/* * Multicast traceroute for FRRouting * Copyright (C) 2017 Mladen Sablic * * This program is free software; you can redistribute it and/or modify * it under the terms of the 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; see the file COPYING; if not, write to the Free Software * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA */ #ifndef PIM_IGMP_MTRACE_H #define PIM_IGMP_MTRACE_H #include #include "pim_igmp.h" #define MTRACE_MAX_HOPS (255) #define MTRACE_UNKNOWN_COUNT (0xffffffff) #define MTRACE_SRC_MASK_GROUP (0x3f) /* forwarding on group state (*,G) */ #define MTRACE_SRC_MASK_SOURCE (0x20) /* i.e. 32 forwarding on (S,G) */ enum mtrace_fwd_code { MTRACE_FWD_CODE_NO_ERROR = 0x00, MTRACE_FWD_CODE_WRONG_IF = 0x01, MTRACE_FWD_CODE_PRUNE_SENT = 0x02, MTRACE_FWD_CODE_PRUNE_RCVD = 0x03, MTRACE_FWD_CODE_SCOPED = 0x04, MTRACE_FWD_CODE_NO_ROUTE = 0x05, MTRACE_FWD_CODE_WRONG_LAST_HOP = 0x06, MTRACE_FWD_CODE_NOT_FORWARDING = 0x07, MTRACE_FWD_CODE_REACHED_RP = 0x08, MTRACE_FWD_CODE_RPF_IF = 0x09, MTRACE_FWD_CODE_NO_MULTICAST = 0x0A, MTRACE_FWD_CODE_INFO_HIDDEN = 0x0B, MTRACE_FWD_CODE_NO_SPACE = 0x81, MTRACE_FWD_CODE_OLD_ROUTER = 0x82, MTRACE_FWD_CODE_ADMIN_PROHIB = 0x83 }; enum mtrace_rtg_proto { MTRACE_RTG_PROTO_DVMRP = 1, MTRACE_RTG_PROTO_MOSPF = 2, MTRACE_RTG_PROTO_PIM = 3, MTRACE_RTG_PROTO_CBT = 4, MTRACE_RTG_PROTO_PIM_SPECIAL = 5, MTRACE_RTG_PROTO_PIM_STATIC = 6, MTRACE_RTG_PROTO_DVMRP_STATIC = 7, MTRACE_RTG_PROTO_PIM_MBGP = 8, MTRACE_RTG_PROTO_CBT_SPECIAL = 9, MTRACE_RTG_PROTO_CBT_STATIC = 10, MTRACE_RTG_PROTO_PIM_ASSERT = 11, }; struct igmp_mtrace_rsp { uint32_t arrival; struct in_addr incoming; struct in_addr outgoing; struct in_addr prev_hop; uint32_t in_count; uint32_t out_count; uint32_t total; uint32_t rtg_proto : 8; uint32_t fwd_ttl : 8; /* little endian order for next three fields */ uint32_t src_mask : 6; uint32_t s : 1; uint32_t mbz : 1; uint32_t fwd_code : 8; } __attribute__((packed)); struct igmp_mtrace { uint8_t type; uint8_t hops; uint16_t checksum; struct in_addr grp_addr; struct in_addr src_addr; struct in_addr dst_addr; struct in_addr rsp_addr; uint32_t rsp_ttl : 8; uint32_t qry_id : 24; struct igmp_mtrace_rsp rsp[0]; } __attribute__((packed)); #define MTRACE_HDR_SIZE (sizeof(struct igmp_mtrace)) #define MTRACE_RSP_SIZE (sizeof(struct igmp_mtrace_rsp)) int igmp_mtrace_recv_qry_req(struct igmp_sock *igmp, struct ip *ip_hdr, struct in_addr from, const char *from_str, char *igmp_msg, int igmp_msg_len); int igmp_mtrace_recv_response(struct igmp_sock *igmp, struct ip *ip_hdr, struct in_addr from, const char *from_str, char *igmp_msg, int igmp_msg_len); #endif /* PIM_IGMP_MTRACE_H */ frr-7.2.1/pimd/pim_igmp_stats.c0000644000000000000000000000254713610377563013340 00000000000000/* * PIM for FRRouting * Copyright (C) 2018 Mladen Sablic * * This program is free software; you can redistribute it and/or modify * it under the terms of the 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; see the file COPYING; if not, write to the Free Software * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA */ #ifdef HAVE_CONFIG_H #include "config.h" #endif #include "pim_igmp_stats.h" void igmp_stats_init(struct igmp_stats *stats) { memset(stats, 0, sizeof(struct igmp_stats)); } void igmp_stats_add(struct igmp_stats *a, struct igmp_stats *b) { if (!a || !b) return; a->query_v1 += b->query_v1; a->query_v2 += b->query_v2; a->query_v3 += b->query_v3; a->report_v1 += b->report_v1; a->report_v2 += b->report_v2; a->report_v3 += b->report_v3; a->leave_v2 += b->leave_v2; a->mtrace_rsp += b->mtrace_rsp; a->mtrace_req += b->mtrace_req; a->unsupported += b->unsupported; } frr-7.2.1/pimd/pim_igmp_stats.h0000644000000000000000000000234413610377563013340 00000000000000/* * PIM for FRRouting * Copyright (C) 2018 Mladen Sablic * * This program is free software; you can redistribute it and/or modify * it under the terms of the 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; see the file COPYING; if not, write to the Free Software * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA */ #ifndef PIM_IGMP_STATS_H #define PIM_IGMP_STATS_H #include struct igmp_stats { uint32_t query_v1; uint32_t query_v2; uint32_t query_v3; uint32_t report_v1; uint32_t report_v2; uint32_t report_v3; uint32_t leave_v2; uint32_t mtrace_rsp; uint32_t mtrace_req; uint32_t unsupported; }; void igmp_stats_init(struct igmp_stats *stats); void igmp_stats_add(struct igmp_stats *a, struct igmp_stats *b); #endif /* PIM_IGMP_STATS_H */ frr-7.2.1/pimd/pim_igmpv2.c0000644000000000000000000001360113610377563012363 00000000000000/* * PIM for Quagga * Copyright (C) 2016 Cumulus Networks, Inc. * Daniel Walton * * This program is free software; you can redistribute it and/or modify * it under the terms of the 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; see the file COPYING; if not, write to the Free Software * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA */ #include "zebra.h" #include "pimd.h" #include "pim_igmp.h" #include "pim_igmpv2.h" #include "pim_igmpv3.h" #include "pim_str.h" #include "pim_time.h" #include "pim_util.h" static void on_trace(const char *label, struct interface *ifp, struct in_addr from) { if (PIM_DEBUG_IGMP_TRACE) { char from_str[INET_ADDRSTRLEN]; pim_inet4_dump("", from, from_str, sizeof(from_str)); zlog_debug("%s: from %s on %s", label, from_str, ifp->name); } } void igmp_v2_send_query(struct igmp_group *group, int fd, const char *ifname, char *query_buf, struct in_addr dst_addr, struct in_addr group_addr, int query_max_response_time_dsec) { ssize_t msg_size = 8; uint8_t max_resp_code; ssize_t sent; struct sockaddr_in to; socklen_t tolen; uint16_t checksum; /* max_resp_code must be non-zero else this will look like an IGMP v1 * query */ max_resp_code = igmp_msg_encode16to8(query_max_response_time_dsec); zassert(max_resp_code > 0); query_buf[0] = PIM_IGMP_MEMBERSHIP_QUERY; query_buf[1] = max_resp_code; *(uint16_t *)(query_buf + IGMP_CHECKSUM_OFFSET) = 0; /* for computing checksum */ memcpy(query_buf + 4, &group_addr, sizeof(struct in_addr)); checksum = in_cksum(query_buf, msg_size); *(uint16_t *)(query_buf + IGMP_CHECKSUM_OFFSET) = checksum; if (PIM_DEBUG_IGMP_PACKETS) { char dst_str[INET_ADDRSTRLEN]; char group_str[INET_ADDRSTRLEN]; pim_inet4_dump("", dst_addr, dst_str, sizeof(dst_str)); pim_inet4_dump("", group_addr, group_str, sizeof(group_str)); zlog_debug("Send IGMPv2 QUERY to %s on %s for group %s", dst_str, ifname, group_str); } memset(&to, 0, sizeof(to)); to.sin_family = AF_INET; to.sin_addr = dst_addr; tolen = sizeof(to); sent = sendto(fd, query_buf, msg_size, MSG_DONTWAIT, (struct sockaddr *)&to, tolen); if (sent != (ssize_t)msg_size) { char dst_str[INET_ADDRSTRLEN]; char group_str[INET_ADDRSTRLEN]; pim_inet4_dump("", dst_addr, dst_str, sizeof(dst_str)); pim_inet4_dump("", group_addr, group_str, sizeof(group_str)); if (sent < 0) { zlog_warn( "Send IGMPv2 QUERY failed due to %s on %s: group=%s msg_size=%zd: errno=%d: %s", dst_str, ifname, group_str, msg_size, errno, safe_strerror(errno)); } else { zlog_warn( "Send IGMPv2 QUERY failed due to %s on %s: group=%s msg_size=%zd: sent=%zd", dst_str, ifname, group_str, msg_size, sent); } return; } } int igmp_v2_recv_report(struct igmp_sock *igmp, struct in_addr from, const char *from_str, char *igmp_msg, int igmp_msg_len) { struct interface *ifp = igmp->interface; struct in_addr group_addr; char group_str[INET_ADDRSTRLEN]; on_trace(__PRETTY_FUNCTION__, igmp->interface, from); if (igmp->mtrace_only) return 0; if (igmp_msg_len != IGMP_V12_MSG_SIZE) { zlog_warn( "Recv IGMPv2 REPORT from %s on %s: size=%d other than correct=%d", from_str, ifp->name, igmp_msg_len, IGMP_V12_MSG_SIZE); return -1; } /* Collecting IGMP Rx stats */ igmp->rx_stats.report_v2++; memcpy(&group_addr, igmp_msg + 4, sizeof(struct in_addr)); if (PIM_DEBUG_IGMP_PACKETS) { pim_inet4_dump("", group_addr, group_str, sizeof(group_str)); zlog_debug("Recv IGMPv2 REPORT from %s on %s for %s", from_str, ifp->name, group_str); } /* * RFC 3376 * 7.3.2. In the Presence of Older Version Group Members * * When Group Compatibility Mode is IGMPv2, a router internally * translates the following IGMPv2 messages for that group to their * IGMPv3 equivalents: * * IGMPv2 Message IGMPv3 Equivalent * -------------- ----------------- * Report IS_EX( {} ) * Leave TO_IN( {} ) */ igmpv3_report_isex(igmp, from, group_addr, 0, NULL, 1); return 0; } int igmp_v2_recv_leave(struct igmp_sock *igmp, struct in_addr from, const char *from_str, char *igmp_msg, int igmp_msg_len) { struct interface *ifp = igmp->interface; struct in_addr group_addr; char group_str[INET_ADDRSTRLEN]; on_trace(__PRETTY_FUNCTION__, igmp->interface, from); if (igmp->mtrace_only) return 0; if (igmp_msg_len != IGMP_V12_MSG_SIZE) { zlog_warn( "Recv IGMPv2 LEAVE from %s on %s: size=%d other than correct=%d", from_str, ifp->name, igmp_msg_len, IGMP_V12_MSG_SIZE); return -1; } /* Collecting IGMP Rx stats */ igmp->rx_stats.leave_v2++; memcpy(&group_addr, igmp_msg + 4, sizeof(struct in_addr)); if (PIM_DEBUG_IGMP_PACKETS) { pim_inet4_dump("", group_addr, group_str, sizeof(group_str)); zlog_debug("Recv IGMPv2 LEAVE from %s on %s for %s", from_str, ifp->name, group_str); } /* * RFC 3376 * 7.3.2. In the Presence of Older Version Group Members * * When Group Compatibility Mode is IGMPv2, a router internally * translates the following IGMPv2 messages for that group to their * IGMPv3 equivalents: * * IGMPv2 Message IGMPv3 Equivalent * -------------- ----------------- * Report IS_EX( {} ) * Leave TO_IN( {} ) */ igmpv3_report_toin(igmp, from, group_addr, 0, NULL); return 0; } frr-7.2.1/pimd/pim_igmpv2.h0000644000000000000000000000250713610377563012373 00000000000000/* * PIM for Quagga * Copyright (C) 2016 Cumulus Networks, Inc. * Daniel Walton * * This program is free software; you can redistribute it and/or modify * it under the terms of the 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; see the file COPYING; if not, write to the Free Software * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA */ #ifndef PIM_IGMPV2_H #define PIM_IGMPV2_H void igmp_v2_send_query(struct igmp_group *group, int fd, const char *ifname, char *query_buf, struct in_addr dst_addr, struct in_addr group_addr, int query_max_response_time_dsec); int igmp_v2_recv_report(struct igmp_sock *igmp, struct in_addr from, const char *from_str, char *igmp_msg, int igmp_msg_len); int igmp_v2_recv_leave(struct igmp_sock *igmp, struct in_addr from, const char *from_str, char *igmp_msg, int igmp_msg_len); #endif /* PIM_IGMPV2_H */ frr-7.2.1/pimd/pim_igmpv3.c0000644000000000000000000016135013610377563012371 00000000000000/* * PIM for Quagga * Copyright (C) 2008 Everton da Silva Marques * * This program is free software; you can redistribute it and/or modify * it under the terms of the 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; see the file COPYING; if not, write to the Free Software * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA */ #include #include "log.h" #include "memory.h" #include "if.h" #include "lib_errors.h" #include "pimd.h" #include "pim_iface.h" #include "pim_igmp.h" #include "pim_igmpv3.h" #include "pim_str.h" #include "pim_util.h" #include "pim_time.h" #include "pim_zebra.h" #include "pim_oil.h" static void group_retransmit_timer_on(struct igmp_group *group); static long igmp_group_timer_remain_msec(struct igmp_group *group); static long igmp_source_timer_remain_msec(struct igmp_source *source); static void group_query_send(struct igmp_group *group); static void source_query_send_by_flag(struct igmp_group *group, int num_sources_tosend); static void on_trace(const char *label, struct interface *ifp, struct in_addr from, struct in_addr group_addr, int num_sources, struct in_addr *sources) { if (PIM_DEBUG_IGMP_TRACE) { char from_str[INET_ADDRSTRLEN]; char group_str[INET_ADDRSTRLEN]; pim_inet4_dump("", from, from_str, sizeof(from_str)); pim_inet4_dump("", group_addr, group_str, sizeof(group_str)); zlog_debug("%s: from %s on %s: group=%s sources=%d", label, from_str, ifp->name, group_str, num_sources); } } void igmp_group_reset_gmi(struct igmp_group *group) { long group_membership_interval_msec; struct pim_interface *pim_ifp; struct igmp_sock *igmp; struct interface *ifp; igmp = group->group_igmp_sock; ifp = igmp->interface; pim_ifp = ifp->info; /* RFC 3376: 8.4. Group Membership Interval The Group Membership Interval is the amount of time that must pass before a multicast router decides there are no more members of a group or a particular source on a network. This value MUST be ((the Robustness Variable) times (the Query Interval)) plus (one Query Response Interval). group_membership_interval_msec = querier_robustness_variable * (1000 * querier_query_interval) + 100 * query_response_interval_dsec; */ group_membership_interval_msec = PIM_IGMP_GMI_MSEC( igmp->querier_robustness_variable, igmp->querier_query_interval, pim_ifp->igmp_query_max_response_time_dsec); if (PIM_DEBUG_IGMP_TRACE) { char group_str[INET_ADDRSTRLEN]; pim_inet4_dump("", group->group_addr, group_str, sizeof(group_str)); zlog_debug( "Resetting group %s timer to GMI=%ld.%03ld sec on %s", group_str, group_membership_interval_msec / 1000, group_membership_interval_msec % 1000, ifp->name); } /* RFC 3376: 6.2.2. Definition of Group Timers The group timer is only used when a group is in EXCLUDE mode and it represents the time for the *filter-mode* of the group to expire and switch to INCLUDE mode. */ zassert(group->group_filtermode_isexcl); igmp_group_timer_on(group, group_membership_interval_msec, ifp->name); } static int igmp_source_timer(struct thread *t) { struct igmp_source *source; struct igmp_group *group; source = THREAD_ARG(t); group = source->source_group; if (PIM_DEBUG_IGMP_TRACE) { char group_str[INET_ADDRSTRLEN]; char source_str[INET_ADDRSTRLEN]; pim_inet4_dump("", group->group_addr, group_str, sizeof(group_str)); pim_inet4_dump("", source->source_addr, source_str, sizeof(source_str)); zlog_debug( "%s: Source timer expired for group %s source %s on %s", __PRETTY_FUNCTION__, group_str, source_str, group->group_igmp_sock->interface->name); } /* RFC 3376: 6.3. IGMPv3 Source-Specific Forwarding Rules Group Filter-Mode Source Timer Value Action ----------- ------------------ ------ INCLUDE TIMER == 0 Suggest to stop forwarding traffic from source and remove source record. If there are no more source records for the group, delete group record. EXCLUDE TIMER == 0 Suggest to not forward traffic from source (DO NOT remove record) Source timer switched from (T > 0) to (T == 0): disable forwarding. */ if (group->group_filtermode_isexcl) { /* EXCLUDE mode */ igmp_source_forward_stop(source); } else { /* INCLUDE mode */ /* igmp_source_delete() will stop forwarding source */ igmp_source_delete(source); /* If there are no more source records for the group, delete group record. */ if (!listcount(group->group_source_list)) { igmp_group_delete_empty_include(group); } } return 0; } static void source_timer_off(struct igmp_group *group, struct igmp_source *source) { if (!source->t_source_timer) return; if (PIM_DEBUG_IGMP_TRACE) { char group_str[INET_ADDRSTRLEN]; char source_str[INET_ADDRSTRLEN]; pim_inet4_dump("", group->group_addr, group_str, sizeof(group_str)); pim_inet4_dump("", source->source_addr, source_str, sizeof(source_str)); zlog_debug( "Cancelling TIMER event for group %s source %s on %s", group_str, source_str, group->group_igmp_sock->interface->name); } THREAD_OFF(source->t_source_timer); } static void igmp_source_timer_on(struct igmp_group *group, struct igmp_source *source, long interval_msec) { source_timer_off(group, source); struct pim_interface *pim_ifp = group->group_igmp_sock->interface->info; if (PIM_DEBUG_IGMP_EVENTS) { char group_str[INET_ADDRSTRLEN]; char source_str[INET_ADDRSTRLEN]; pim_inet4_dump("", group->group_addr, group_str, sizeof(group_str)); pim_inet4_dump("", source->source_addr, source_str, sizeof(source_str)); zlog_debug( "Scheduling %ld.%03ld sec TIMER event for group %s source %s on %s", interval_msec / 1000, interval_msec % 1000, group_str, source_str, group->group_igmp_sock->interface->name); } thread_add_timer_msec(router->master, igmp_source_timer, source, interval_msec, &source->t_source_timer); /* RFC 3376: 6.3. IGMPv3 Source-Specific Forwarding Rules Source timer switched from (T == 0) to (T > 0): enable forwarding. */ igmp_source_forward_start(pim_ifp->pim, source); } void igmp_source_reset_gmi(struct igmp_sock *igmp, struct igmp_group *group, struct igmp_source *source) { long group_membership_interval_msec; struct pim_interface *pim_ifp; struct interface *ifp; ifp = igmp->interface; pim_ifp = ifp->info; group_membership_interval_msec = PIM_IGMP_GMI_MSEC( igmp->querier_robustness_variable, igmp->querier_query_interval, pim_ifp->igmp_query_max_response_time_dsec); if (PIM_DEBUG_IGMP_TRACE) { char group_str[INET_ADDRSTRLEN]; char source_str[INET_ADDRSTRLEN]; pim_inet4_dump("", group->group_addr, group_str, sizeof(group_str)); pim_inet4_dump("", source->source_addr, source_str, sizeof(source_str)); zlog_debug( "Resetting source %s timer to GMI=%ld.%03ld sec for group %s on %s", source_str, group_membership_interval_msec / 1000, group_membership_interval_msec % 1000, group_str, ifp->name); } igmp_source_timer_on(group, source, group_membership_interval_msec); } static void source_mark_delete_flag(struct igmp_group *group) { struct listnode *src_node; struct igmp_source *src; for (ALL_LIST_ELEMENTS_RO(group->group_source_list, src_node, src)) { IGMP_SOURCE_DO_DELETE(src->source_flags); } } static void source_mark_send_flag(struct igmp_group *group) { struct listnode *src_node; struct igmp_source *src; for (ALL_LIST_ELEMENTS_RO(group->group_source_list, src_node, src)) { IGMP_SOURCE_DO_SEND(src->source_flags); } } static int source_mark_send_flag_by_timer(struct igmp_group *group) { struct listnode *src_node; struct igmp_source *src; int num_marked_sources = 0; for (ALL_LIST_ELEMENTS_RO(group->group_source_list, src_node, src)) { /* Is source timer running? */ if (src->t_source_timer) { IGMP_SOURCE_DO_SEND(src->source_flags); ++num_marked_sources; } else { IGMP_SOURCE_DONT_SEND(src->source_flags); } } return num_marked_sources; } static void source_clear_send_flag(struct list *source_list) { struct listnode *src_node; struct igmp_source *src; for (ALL_LIST_ELEMENTS_RO(source_list, src_node, src)) { IGMP_SOURCE_DONT_SEND(src->source_flags); } } /* Any source (*,G) is forwarded only if mode is EXCLUDE {empty} */ static void group_exclude_fwd_anysrc_ifempty(struct igmp_group *group) { struct pim_interface *pim_ifp = group->group_igmp_sock->interface->info; zassert(group->group_filtermode_isexcl); if (listcount(group->group_source_list) < 1) { igmp_anysource_forward_start(pim_ifp->pim, group); } } void igmp_source_free(struct igmp_source *source) { /* make sure there is no source timer running */ zassert(!source->t_source_timer); XFREE(MTYPE_PIM_IGMP_GROUP_SOURCE, source); } static void source_channel_oil_detach(struct igmp_source *source) { if (source->source_channel_oil) { pim_channel_oil_del(source->source_channel_oil, __PRETTY_FUNCTION__); source->source_channel_oil = NULL; } } /* igmp_source_delete: stop fowarding, and delete the source igmp_source_forward_stop: stop fowarding, but keep the source */ void igmp_source_delete(struct igmp_source *source) { struct igmp_group *group; struct in_addr src; group = source->source_group; if (PIM_DEBUG_IGMP_TRACE) { char group_str[INET_ADDRSTRLEN]; char source_str[INET_ADDRSTRLEN]; pim_inet4_dump("", group->group_addr, group_str, sizeof(group_str)); pim_inet4_dump("", source->source_addr, source_str, sizeof(source_str)); zlog_debug( "Deleting IGMP source %s for group %s from socket %d interface %s c_oil ref_count %d", source_str, group_str, group->group_igmp_sock->fd, group->group_igmp_sock->interface->name, source->source_channel_oil ? source->source_channel_oil->oil_ref_count : 0); } source_timer_off(group, source); igmp_source_forward_stop(source); /* sanity check that forwarding has been disabled */ if (IGMP_SOURCE_TEST_FORWARDING(source->source_flags)) { char group_str[INET_ADDRSTRLEN]; char source_str[INET_ADDRSTRLEN]; pim_inet4_dump("", group->group_addr, group_str, sizeof(group_str)); pim_inet4_dump("", source->source_addr, source_str, sizeof(source_str)); zlog_warn( "%s: forwarding=ON(!) IGMP source %s for group %s from socket %d interface %s", __PRETTY_FUNCTION__, source_str, group_str, group->group_igmp_sock->fd, group->group_igmp_sock->interface->name); /* warning only */ } source_channel_oil_detach(source); /* notice that listnode_delete() can't be moved into igmp_source_free() because the later is called by list_delete_all_node() */ listnode_delete(group->group_source_list, source); src.s_addr = source->source_addr.s_addr; igmp_source_free(source); /* Group source list is empty and current source is * then *,G group going away so do not trigger start */ if (group->group_filtermode_isexcl && (listcount(group->group_source_list) != 0) && src.s_addr != INADDR_ANY) { group_exclude_fwd_anysrc_ifempty(group); } } static void source_delete_by_flag(struct list *source_list) { struct listnode *src_node; struct listnode *src_nextnode; struct igmp_source *src; for (ALL_LIST_ELEMENTS(source_list, src_node, src_nextnode, src)) if (IGMP_SOURCE_TEST_DELETE(src->source_flags)) igmp_source_delete(src); } void igmp_source_delete_expired(struct list *source_list) { struct listnode *src_node; struct listnode *src_nextnode; struct igmp_source *src; for (ALL_LIST_ELEMENTS(source_list, src_node, src_nextnode, src)) if (!src->t_source_timer) igmp_source_delete(src); } struct igmp_source *igmp_find_source_by_addr(struct igmp_group *group, struct in_addr src_addr) { struct listnode *src_node; struct igmp_source *src; for (ALL_LIST_ELEMENTS_RO(group->group_source_list, src_node, src)) if (src_addr.s_addr == src->source_addr.s_addr) return src; return 0; } struct igmp_source *source_new(struct igmp_group *group, struct in_addr src_addr) { struct igmp_source *src; if (PIM_DEBUG_IGMP_TRACE) { char group_str[INET_ADDRSTRLEN]; char source_str[INET_ADDRSTRLEN]; pim_inet4_dump("", group->group_addr, group_str, sizeof(group_str)); pim_inet4_dump("", src_addr, source_str, sizeof(source_str)); zlog_debug( "Creating new IGMP source %s for group %s on socket %d interface %s", source_str, group_str, group->group_igmp_sock->fd, group->group_igmp_sock->interface->name); } src = XCALLOC(MTYPE_PIM_IGMP_GROUP_SOURCE, sizeof(*src)); src->t_source_timer = NULL; src->source_group = group; /* back pointer */ src->source_addr = src_addr; src->source_creation = pim_time_monotonic_sec(); src->source_flags = 0; src->source_query_retransmit_count = 0; src->source_channel_oil = NULL; listnode_add(group->group_source_list, src); /* Any source (*,G) is forwarded only if mode is EXCLUDE {empty} */ igmp_anysource_forward_stop(group); return src; } static struct igmp_source *add_source_by_addr(struct igmp_sock *igmp, struct igmp_group *group, struct in_addr src_addr) { struct igmp_source *src; src = igmp_find_source_by_addr(group, src_addr); if (src) { return src; } src = source_new(group, src_addr); return src; } static void allow(struct igmp_sock *igmp, struct in_addr from, struct in_addr group_addr, int num_sources, struct in_addr *sources) { struct igmp_source *source; struct igmp_group *group; int i; /* non-existant group is created as INCLUDE {empty} */ group = igmp_add_group_by_addr(igmp, group_addr); if (!group) { return; } /* scan received sources */ for (i = 0; i < num_sources; ++i) { struct in_addr *src_addr; src_addr = sources + i; source = add_source_by_addr(igmp, group, *src_addr); if (!source) { continue; } /* RFC 3376: 6.4.1. Reception of Current-State Records When receiving IS_IN reports for groups in EXCLUDE mode is sources should be moved from set with (timers = 0) to set with (timers > 0). igmp_source_reset_gmi() below, resetting the source timers to GMI, accomplishes this. */ igmp_source_reset_gmi(igmp, group, source); } /* scan received sources */ if ((num_sources == 0) && (group->group_filtermode_isexcl) && (listcount(group->group_source_list) == 1)) { struct in_addr star = {.s_addr = INADDR_ANY}; source = igmp_find_source_by_addr(group, star); if (source) igmp_source_reset_gmi(igmp, group, source); } } void igmpv3_report_isin(struct igmp_sock *igmp, struct in_addr from, struct in_addr group_addr, int num_sources, struct in_addr *sources) { on_trace(__PRETTY_FUNCTION__, igmp->interface, from, group_addr, num_sources, sources); allow(igmp, from, group_addr, num_sources, sources); } static void isex_excl(struct igmp_group *group, int num_sources, struct in_addr *sources) { struct igmp_source *source; int i; /* EXCLUDE mode */ zassert(group->group_filtermode_isexcl); /* E.1: set deletion flag for known sources (X,Y) */ source_mark_delete_flag(group); /* scan received sources (A) */ for (i = 0; i < num_sources; ++i) { struct in_addr *src_addr; src_addr = sources + i; /* E.2: lookup reported source from (A) in (X,Y) */ source = igmp_find_source_by_addr(group, *src_addr); if (source) { /* E.3: if found, clear deletion flag: (X*A) or (Y*A) */ IGMP_SOURCE_DONT_DELETE(source->source_flags); } else { /* E.4: if not found, create source with timer=GMI: * (A-X-Y) */ source = source_new(group, *src_addr); zassert(!source->t_source_timer); /* timer == 0 */ igmp_source_reset_gmi(group->group_igmp_sock, group, source); zassert(source->t_source_timer); /* (A-X-Y) timer > 0 */ } } /* scan received sources */ /* * If we are in isexcl mode and num_sources == 0 * than that means we have a *,g entry that * needs to be handled */ if (group->group_filtermode_isexcl && num_sources == 0) { struct in_addr star = {.s_addr = INADDR_ANY}; source = igmp_find_source_by_addr(group, star); if (source) { IGMP_SOURCE_DONT_DELETE(source->source_flags); igmp_source_reset_gmi(group->group_igmp_sock, group, source); } } /* E.5: delete all sources marked with deletion flag: (X-A) and (Y-A) */ source_delete_by_flag(group->group_source_list); } static void isex_incl(struct igmp_group *group, int num_sources, struct in_addr *sources) { int i; /* INCLUDE mode */ zassert(!group->group_filtermode_isexcl); /* I.1: set deletion flag for known sources (A) */ source_mark_delete_flag(group); /* scan received sources (B) */ for (i = 0; i < num_sources; ++i) { struct igmp_source *source; struct in_addr *src_addr; src_addr = sources + i; /* I.2: lookup reported source (B) */ source = igmp_find_source_by_addr(group, *src_addr); if (source) { /* I.3: if found, clear deletion flag (A*B) */ IGMP_SOURCE_DONT_DELETE(source->source_flags); } else { /* I.4: if not found, create source with timer=0 (B-A) */ source = source_new(group, *src_addr); zassert(!source->t_source_timer); /* (B-A) timer=0 */ } } /* scan received sources */ /* I.5: delete all sources marked with deletion flag (A-B) */ source_delete_by_flag(group->group_source_list); group->group_filtermode_isexcl = 1; /* boolean=true */ zassert(group->group_filtermode_isexcl); group_exclude_fwd_anysrc_ifempty(group); } void igmpv3_report_isex(struct igmp_sock *igmp, struct in_addr from, struct in_addr group_addr, int num_sources, struct in_addr *sources, int from_igmp_v2_report) { struct interface *ifp = igmp->interface; struct igmp_group *group; on_trace(__PRETTY_FUNCTION__, ifp, from, group_addr, num_sources, sources); if (pim_is_group_filtered(ifp->info, &group_addr)) return; /* non-existant group is created as INCLUDE {empty} */ group = igmp_add_group_by_addr(igmp, group_addr); if (!group) { return; } /* So we can display how we learned the group in our show command output */ if (from_igmp_v2_report) group->igmp_version = 2; if (group->group_filtermode_isexcl) { /* EXCLUDE mode */ isex_excl(group, num_sources, sources); } else { /* INCLUDE mode */ isex_incl(group, num_sources, sources); zassert(group->group_filtermode_isexcl); } zassert(group->group_filtermode_isexcl); igmp_group_reset_gmi(group); } static void toin_incl(struct igmp_group *group, int num_sources, struct in_addr *sources) { struct igmp_sock *igmp = group->group_igmp_sock; int num_sources_tosend = listcount(group->group_source_list); int i; /* Set SEND flag for all known sources (A) */ source_mark_send_flag(group); /* Scan received sources (B) */ for (i = 0; i < num_sources; ++i) { struct igmp_source *source; struct in_addr *src_addr; src_addr = sources + i; /* Lookup reported source (B) */ source = igmp_find_source_by_addr(group, *src_addr); if (source) { /* If found, clear SEND flag (A*B) */ IGMP_SOURCE_DONT_SEND(source->source_flags); --num_sources_tosend; } else { /* If not found, create new source */ source = source_new(group, *src_addr); } /* (B)=GMI */ igmp_source_reset_gmi(igmp, group, source); } /* Send sources marked with SEND flag: Q(G,A-B) */ if (num_sources_tosend > 0) { source_query_send_by_flag(group, num_sources_tosend); } } static void toin_excl(struct igmp_group *group, int num_sources, struct in_addr *sources) { struct igmp_sock *igmp = group->group_igmp_sock; int num_sources_tosend; int i; /* Set SEND flag for X (sources with timer > 0) */ num_sources_tosend = source_mark_send_flag_by_timer(group); /* Scan received sources (A) */ for (i = 0; i < num_sources; ++i) { struct igmp_source *source; struct in_addr *src_addr; src_addr = sources + i; /* Lookup reported source (A) */ source = igmp_find_source_by_addr(group, *src_addr); if (source) { if (source->t_source_timer) { /* If found and timer running, clear SEND flag * (X*A) */ IGMP_SOURCE_DONT_SEND(source->source_flags); --num_sources_tosend; } } else { /* If not found, create new source */ source = source_new(group, *src_addr); } /* (A)=GMI */ igmp_source_reset_gmi(igmp, group, source); } /* Send sources marked with SEND flag: Q(G,X-A) */ if (num_sources_tosend > 0) { source_query_send_by_flag(group, num_sources_tosend); } /* Send Q(G) */ group_query_send(group); } void igmpv3_report_toin(struct igmp_sock *igmp, struct in_addr from, struct in_addr group_addr, int num_sources, struct in_addr *sources) { struct interface *ifp = igmp->interface; struct igmp_group *group; on_trace(__PRETTY_FUNCTION__, ifp, from, group_addr, num_sources, sources); /* * If the requested filter mode is INCLUDE *and* the requested source * list is empty, then the entry corresponding to the requested * interface and multicast address is deleted if present. If no such * entry is present, the request is ignored. */ if (num_sources) { /* non-existant group is created as INCLUDE {empty} */ group = igmp_add_group_by_addr(igmp, group_addr); if (!group) { return; } } else { group = find_group_by_addr(igmp, group_addr); if (!group) return; } if (group->group_filtermode_isexcl) { /* EXCLUDE mode */ toin_excl(group, num_sources, sources); } else { /* INCLUDE mode */ toin_incl(group, num_sources, sources); } } static void toex_incl(struct igmp_group *group, int num_sources, struct in_addr *sources) { int num_sources_tosend = 0; int i; zassert(!group->group_filtermode_isexcl); /* Set DELETE flag for all known sources (A) */ source_mark_delete_flag(group); /* Clear off SEND flag from all known sources (A) */ source_clear_send_flag(group->group_source_list); /* Scan received sources (B) */ for (i = 0; i < num_sources; ++i) { struct igmp_source *source; struct in_addr *src_addr; src_addr = sources + i; /* Lookup reported source (B) */ source = igmp_find_source_by_addr(group, *src_addr); if (source) { /* If found, clear deletion flag: (A*B) */ IGMP_SOURCE_DONT_DELETE(source->source_flags); /* and set SEND flag (A*B) */ IGMP_SOURCE_DO_SEND(source->source_flags); ++num_sources_tosend; } else { /* If source not found, create source with timer=0: * (B-A)=0 */ source = source_new(group, *src_addr); zassert(!source->t_source_timer); /* (B-A) timer=0 */ } } /* Scan received sources (B) */ group->group_filtermode_isexcl = 1; /* boolean=true */ /* Delete all sources marked with DELETE flag (A-B) */ source_delete_by_flag(group->group_source_list); /* Send sources marked with SEND flag: Q(G,A*B) */ if (num_sources_tosend > 0) { source_query_send_by_flag(group, num_sources_tosend); } zassert(group->group_filtermode_isexcl); group_exclude_fwd_anysrc_ifempty(group); } static void toex_excl(struct igmp_group *group, int num_sources, struct in_addr *sources) { int num_sources_tosend = 0; int i; /* set DELETE flag for all known sources (X,Y) */ source_mark_delete_flag(group); /* clear off SEND flag from all known sources (X,Y) */ source_clear_send_flag(group->group_source_list); if (num_sources == 0) { struct igmp_source *source; struct in_addr any = {.s_addr = INADDR_ANY}; source = igmp_find_source_by_addr(group, any); if (source) IGMP_SOURCE_DONT_DELETE(source->source_flags); } /* scan received sources (A) */ for (i = 0; i < num_sources; ++i) { struct igmp_source *source; struct in_addr *src_addr; src_addr = sources + i; /* lookup reported source (A) in known sources (X,Y) */ source = igmp_find_source_by_addr(group, *src_addr); if (source) { /* if found, clear off DELETE flag from reported source * (A) */ IGMP_SOURCE_DONT_DELETE(source->source_flags); } else { /* if not found, create source with Group Timer: * (A-X-Y)=Group Timer */ long group_timer_msec; source = source_new(group, *src_addr); zassert(!source->t_source_timer); /* timer == 0 */ group_timer_msec = igmp_group_timer_remain_msec(group); igmp_source_timer_on(group, source, group_timer_msec); zassert(source->t_source_timer); /* (A-X-Y) timer > 0 */ /* make sure source is created with DELETE flag unset */ zassert(!IGMP_SOURCE_TEST_DELETE(source->source_flags)); } /* make sure reported source has DELETE flag unset */ zassert(!IGMP_SOURCE_TEST_DELETE(source->source_flags)); if (source->t_source_timer) { /* if source timer>0 mark SEND flag: Q(G,A-Y) */ IGMP_SOURCE_DO_SEND(source->source_flags); ++num_sources_tosend; } } /* scan received sources (A) */ /* delete all sources marked with DELETE flag: Delete (X-A) Delete (Y-A) */ source_delete_by_flag(group->group_source_list); /* send sources marked with SEND flag: Q(G,A-Y) */ if (num_sources_tosend > 0) { source_query_send_by_flag(group, num_sources_tosend); } } void igmpv3_report_toex(struct igmp_sock *igmp, struct in_addr from, struct in_addr group_addr, int num_sources, struct in_addr *sources) { struct interface *ifp = igmp->interface; struct igmp_group *group; on_trace(__PRETTY_FUNCTION__, ifp, from, group_addr, num_sources, sources); /* non-existant group is created as INCLUDE {empty} */ group = igmp_add_group_by_addr(igmp, group_addr); if (!group) { return; } if (group->group_filtermode_isexcl) { /* EXCLUDE mode */ toex_excl(group, num_sources, sources); } else { /* INCLUDE mode */ toex_incl(group, num_sources, sources); zassert(group->group_filtermode_isexcl); } zassert(group->group_filtermode_isexcl); /* Group Timer=GMI */ igmp_group_reset_gmi(group); } void igmpv3_report_allow(struct igmp_sock *igmp, struct in_addr from, struct in_addr group_addr, int num_sources, struct in_addr *sources) { on_trace(__PRETTY_FUNCTION__, igmp->interface, from, group_addr, num_sources, sources); allow(igmp, from, group_addr, num_sources, sources); } /* RFC3376: 6.6.3.1. Building and Sending Group Specific Queries When transmitting a group specific query, if the group timer is larger than LMQT, the "Suppress Router-Side Processing" bit is set in the query message. */ static void group_retransmit_group(struct igmp_group *group) { struct igmp_sock *igmp; struct pim_interface *pim_ifp; long lmqc; /* Last Member Query Count */ long lmqi_msec; /* Last Member Query Interval */ long lmqt_msec; /* Last Member Query Time */ int s_flag; int query_buf_size; igmp = group->group_igmp_sock; pim_ifp = igmp->interface->info; if (pim_ifp->igmp_version == 3) { query_buf_size = PIM_IGMP_BUFSIZE_WRITE; } else { query_buf_size = IGMP_V12_MSG_SIZE; } char query_buf[query_buf_size]; lmqc = pim_ifp->igmp_last_member_query_count; lmqi_msec = 100 * pim_ifp->igmp_specific_query_max_response_time_dsec; lmqt_msec = lmqc * lmqi_msec; /* RFC3376: 6.6.3.1. Building and Sending Group Specific Queries When transmitting a group specific query, if the group timer is larger than LMQT, the "Suppress Router-Side Processing" bit is set in the query message. */ s_flag = igmp_group_timer_remain_msec(group) > lmqt_msec; if (PIM_DEBUG_IGMP_TRACE) { char group_str[INET_ADDRSTRLEN]; pim_inet4_dump("", group->group_addr, group_str, sizeof(group_str)); zlog_debug( "retransmit_group_specific_query: group %s on %s: s_flag=%d count=%d", group_str, igmp->interface->name, s_flag, group->group_specific_query_retransmit_count); } /* RFC3376: 4.1.12. IP Destination Addresses for Queries Group-Specific and Group-and-Source-Specific Queries are sent with an IP destination address equal to the multicast address of interest. */ igmp_send_query(pim_ifp->igmp_version, group, igmp->fd, igmp->interface->name, query_buf, sizeof(query_buf), 0 /* num_sources_tosend */, group->group_addr /* dst_addr */, group->group_addr /* group_addr */, pim_ifp->igmp_specific_query_max_response_time_dsec, s_flag, igmp->querier_robustness_variable, igmp->querier_query_interval); } /* RFC3376: 6.6.3.2. Building and Sending Group and Source Specific Queries When building a group and source specific query for a group G, two separate query messages are sent for the group. The first one has the "Suppress Router-Side Processing" bit set and contains all the sources with retransmission state and timers greater than LMQT. The second has the "Suppress Router-Side Processing" bit clear and contains all the sources with retransmission state and timers lower or equal to LMQT. If either of the two calculated messages does not contain any sources, then its transmission is suppressed. */ static int group_retransmit_sources(struct igmp_group *group, int send_with_sflag_set) { struct igmp_sock *igmp; struct pim_interface *pim_ifp; long lmqc; /* Last Member Query Count */ long lmqi_msec; /* Last Member Query Interval */ long lmqt_msec; /* Last Member Query Time */ char query_buf1[PIM_IGMP_BUFSIZE_WRITE]; /* 1 = with s_flag set */ char query_buf2[PIM_IGMP_BUFSIZE_WRITE]; /* 2 = with s_flag clear */ int query_buf1_max_sources; int query_buf2_max_sources; struct in_addr *source_addr1; struct in_addr *source_addr2; int num_sources_tosend1; int num_sources_tosend2; struct listnode *src_node; struct igmp_source *src; int num_retransmit_sources_left = 0; source_addr1 = (struct in_addr *)(query_buf1 + IGMP_V3_SOURCES_OFFSET); source_addr2 = (struct in_addr *)(query_buf2 + IGMP_V3_SOURCES_OFFSET); igmp = group->group_igmp_sock; pim_ifp = igmp->interface->info; lmqc = pim_ifp->igmp_last_member_query_count; lmqi_msec = 100 * pim_ifp->igmp_specific_query_max_response_time_dsec; lmqt_msec = lmqc * lmqi_msec; /* Scan all group sources */ for (ALL_LIST_ELEMENTS_RO(group->group_source_list, src_node, src)) { /* Source has retransmission state? */ if (src->source_query_retransmit_count < 1) continue; if (--src->source_query_retransmit_count > 0) { ++num_retransmit_sources_left; } /* Copy source address into appropriate query buffer */ if (igmp_source_timer_remain_msec(src) > lmqt_msec) { *source_addr1 = src->source_addr; ++source_addr1; } else { *source_addr2 = src->source_addr; ++source_addr2; } } num_sources_tosend1 = source_addr1 - (struct in_addr *)(query_buf1 + IGMP_V3_SOURCES_OFFSET); num_sources_tosend2 = source_addr2 - (struct in_addr *)(query_buf2 + IGMP_V3_SOURCES_OFFSET); if (PIM_DEBUG_IGMP_TRACE) { char group_str[INET_ADDRSTRLEN]; pim_inet4_dump("", group->group_addr, group_str, sizeof(group_str)); zlog_debug( "retransmit_grp&src_specific_query: group %s on %s: srcs_with_sflag=%d srcs_wo_sflag=%d will_send_sflag=%d retransmit_src_left=%d", group_str, igmp->interface->name, num_sources_tosend1, num_sources_tosend2, send_with_sflag_set, num_retransmit_sources_left); } if (num_sources_tosend1 > 0) { /* Send group-and-source-specific query with s_flag set and all sources with timers greater than LMQT. */ if (send_with_sflag_set) { query_buf1_max_sources = (sizeof(query_buf1) - IGMP_V3_SOURCES_OFFSET) >> 2; if (num_sources_tosend1 > query_buf1_max_sources) { char group_str[INET_ADDRSTRLEN]; pim_inet4_dump("", group->group_addr, group_str, sizeof(group_str)); zlog_warn( "%s: group %s on %s: s_flag=1 unable to fit %d sources into buf_size=%zu (max_sources=%d)", __PRETTY_FUNCTION__, group_str, igmp->interface->name, num_sources_tosend1, sizeof(query_buf1), query_buf1_max_sources); } else { /* RFC3376: 4.1.12. IP Destination Addresses for Queries Group-Specific and Group-and-Source-Specific Queries are sent with an IP destination address equal to the multicast address of interest. */ igmp_send_query( pim_ifp->igmp_version, group, igmp->fd, igmp->interface->name, query_buf1, sizeof(query_buf1), num_sources_tosend1, group->group_addr, group->group_addr, pim_ifp->igmp_specific_query_max_response_time_dsec, 1 /* s_flag */, igmp->querier_robustness_variable, igmp->querier_query_interval); } } /* send_with_sflag_set */ } if (num_sources_tosend2 > 0) { /* Send group-and-source-specific query with s_flag clear and all sources with timers lower or equal to LMQT. */ query_buf2_max_sources = (sizeof(query_buf2) - IGMP_V3_SOURCES_OFFSET) >> 2; if (num_sources_tosend2 > query_buf2_max_sources) { char group_str[INET_ADDRSTRLEN]; pim_inet4_dump("", group->group_addr, group_str, sizeof(group_str)); zlog_warn( "%s: group %s on %s: s_flag=0 unable to fit %d sources into buf_size=%zu (max_sources=%d)", __PRETTY_FUNCTION__, group_str, igmp->interface->name, num_sources_tosend2, sizeof(query_buf2), query_buf2_max_sources); } else { /* RFC3376: 4.1.12. IP Destination Addresses for Queries Group-Specific and Group-and-Source-Specific Queries are sent with an IP destination address equal to the multicast address of interest. */ igmp_send_query( pim_ifp->igmp_version, group, igmp->fd, igmp->interface->name, query_buf2, sizeof(query_buf2), num_sources_tosend2, group->group_addr, group->group_addr, pim_ifp->igmp_specific_query_max_response_time_dsec, 0 /* s_flag */, igmp->querier_robustness_variable, igmp->querier_query_interval); } } return num_retransmit_sources_left; } static int igmp_group_retransmit(struct thread *t) { struct igmp_group *group; int num_retransmit_sources_left; int send_with_sflag_set; /* boolean */ group = THREAD_ARG(t); if (PIM_DEBUG_IGMP_TRACE) { char group_str[INET_ADDRSTRLEN]; pim_inet4_dump("", group->group_addr, group_str, sizeof(group_str)); zlog_debug("group_retransmit_timer: group %s on %s", group_str, group->group_igmp_sock->interface->name); } /* Retransmit group-specific queries? (RFC3376: 6.6.3.1) */ if (group->group_specific_query_retransmit_count > 0) { /* Retransmit group-specific queries (RFC3376: 6.6.3.1) */ group_retransmit_group(group); --group->group_specific_query_retransmit_count; /* RFC3376: 6.6.3.2 If a group specific query is scheduled to be transmitted at the same time as a group and source specific query for the same group, then transmission of the group and source specific message with the "Suppress Router-Side Processing" bit set may be suppressed. */ send_with_sflag_set = 0; /* boolean=false */ } else { send_with_sflag_set = 1; /* boolean=true */ } /* Retransmit group-and-source-specific queries (RFC3376: 6.6.3.2) */ num_retransmit_sources_left = group_retransmit_sources(group, send_with_sflag_set); /* Keep group retransmit timer running if there is any retransmit counter pending */ if ((num_retransmit_sources_left > 0) || (group->group_specific_query_retransmit_count > 0)) { group_retransmit_timer_on(group); } return 0; } /* group_retransmit_timer_on: if group retransmit timer isn't running, starts it; otherwise, do nothing */ static void group_retransmit_timer_on(struct igmp_group *group) { struct igmp_sock *igmp; struct pim_interface *pim_ifp; long lmqi_msec; /* Last Member Query Interval */ /* if group retransmit timer is running, do nothing */ if (group->t_group_query_retransmit_timer) { return; } igmp = group->group_igmp_sock; pim_ifp = igmp->interface->info; lmqi_msec = 100 * pim_ifp->igmp_specific_query_max_response_time_dsec; if (PIM_DEBUG_IGMP_TRACE) { char group_str[INET_ADDRSTRLEN]; pim_inet4_dump("", group->group_addr, group_str, sizeof(group_str)); zlog_debug( "Scheduling %ld.%03ld sec retransmit timer for group %s on %s", lmqi_msec / 1000, lmqi_msec % 1000, group_str, igmp->interface->name); } thread_add_timer_msec(router->master, igmp_group_retransmit, group, lmqi_msec, &group->t_group_query_retransmit_timer); } static long igmp_group_timer_remain_msec(struct igmp_group *group) { return pim_time_timer_remain_msec(group->t_group_timer); } static long igmp_source_timer_remain_msec(struct igmp_source *source) { return pim_time_timer_remain_msec(source->t_source_timer); } /* RFC3376: 6.6.3.1. Building and Sending Group Specific Queries */ static void group_query_send(struct igmp_group *group) { struct pim_interface *pim_ifp; struct igmp_sock *igmp; long lmqc; /* Last Member Query Count */ igmp = group->group_igmp_sock; pim_ifp = igmp->interface->info; lmqc = pim_ifp->igmp_last_member_query_count; /* lower group timer to lmqt */ igmp_group_timer_lower_to_lmqt(group); /* reset retransmission counter */ group->group_specific_query_retransmit_count = lmqc; /* immediately send group specific query (decrease retransmit counter by * 1)*/ group_retransmit_group(group); /* make sure group retransmit timer is running */ group_retransmit_timer_on(group); } /* RFC3376: 6.6.3.2. Building and Sending Group and Source Specific Queries */ static void source_query_send_by_flag(struct igmp_group *group, int num_sources_tosend) { struct igmp_sock *igmp; struct pim_interface *pim_ifp; struct listnode *src_node; struct igmp_source *src; long lmqc; /* Last Member Query Count */ long lmqi_msec; /* Last Member Query Interval */ long lmqt_msec; /* Last Member Query Time */ zassert(num_sources_tosend > 0); igmp = group->group_igmp_sock; pim_ifp = igmp->interface->info; lmqc = pim_ifp->igmp_last_member_query_count; lmqi_msec = 100 * pim_ifp->igmp_specific_query_max_response_time_dsec; lmqt_msec = lmqc * lmqi_msec; /* RFC3376: 6.6.3.2. Building and Sending Group and Source Specific Queries (...) for each of the sources in X of group G, with source timer larger than LMQT: o Set number of retransmissions for each source to [Last Member Query Count]. o Lower source timer to LMQT. */ for (ALL_LIST_ELEMENTS_RO(group->group_source_list, src_node, src)) { if (IGMP_SOURCE_TEST_SEND(src->source_flags)) { /* source "src" in X of group G */ if (igmp_source_timer_remain_msec(src) > lmqt_msec) { src->source_query_retransmit_count = lmqc; igmp_source_timer_lower_to_lmqt(src); } } } /* send group-and-source specific queries */ group_retransmit_sources(group, 1 /* send_with_sflag_set=true */); /* make sure group retransmit timer is running */ group_retransmit_timer_on(group); } static void block_excl(struct igmp_group *group, int num_sources, struct in_addr *sources) { int num_sources_tosend = 0; int i; /* 1. clear off SEND flag from all known sources (X,Y) */ source_clear_send_flag(group->group_source_list); /* 2. scan received sources (A) */ for (i = 0; i < num_sources; ++i) { struct igmp_source *source; struct in_addr *src_addr; src_addr = sources + i; /* lookup reported source (A) in known sources (X,Y) */ source = igmp_find_source_by_addr(group, *src_addr); if (!source) { /* 3: if not found, create source with Group Timer: * (A-X-Y)=Group Timer */ long group_timer_msec; source = source_new(group, *src_addr); zassert(!source->t_source_timer); /* timer == 0 */ group_timer_msec = igmp_group_timer_remain_msec(group); igmp_source_timer_on(group, source, group_timer_msec); zassert(source->t_source_timer); /* (A-X-Y) timer > 0 */ } if (source->t_source_timer) { /* 4. if source timer>0 mark SEND flag: Q(G,A-Y) */ IGMP_SOURCE_DO_SEND(source->source_flags); ++num_sources_tosend; } } /* 5. send sources marked with SEND flag: Q(G,A-Y) */ if (num_sources_tosend > 0) { source_query_send_by_flag(group, num_sources_tosend); } } static void block_incl(struct igmp_group *group, int num_sources, struct in_addr *sources) { int num_sources_tosend = 0; int i; /* 1. clear off SEND flag from all known sources (B) */ source_clear_send_flag(group->group_source_list); /* 2. scan received sources (A) */ for (i = 0; i < num_sources; ++i) { struct igmp_source *source; struct in_addr *src_addr; src_addr = sources + i; /* lookup reported source (A) in known sources (B) */ source = igmp_find_source_by_addr(group, *src_addr); if (source) { /* 3. if found (A*B), mark SEND flag: Q(G,A*B) */ IGMP_SOURCE_DO_SEND(source->source_flags); ++num_sources_tosend; } } /* 4. send sources marked with SEND flag: Q(G,A*B) */ if (num_sources_tosend > 0) { source_query_send_by_flag(group, num_sources_tosend); } } void igmpv3_report_block(struct igmp_sock *igmp, struct in_addr from, struct in_addr group_addr, int num_sources, struct in_addr *sources) { struct interface *ifp = igmp->interface; struct igmp_group *group; on_trace(__PRETTY_FUNCTION__, ifp, from, group_addr, num_sources, sources); /* non-existant group is created as INCLUDE {empty} */ group = igmp_add_group_by_addr(igmp, group_addr); if (!group) { return; } if (group->group_filtermode_isexcl) { /* EXCLUDE mode */ block_excl(group, num_sources, sources); } else { /* INCLUDE mode */ block_incl(group, num_sources, sources); } } void igmp_group_timer_lower_to_lmqt(struct igmp_group *group) { struct igmp_sock *igmp; struct interface *ifp; struct pim_interface *pim_ifp; char *ifname; int lmqi_dsec; /* Last Member Query Interval */ int lmqc; /* Last Member Query Count */ int lmqt_msec; /* Last Member Query Time */ /* RFC 3376: 6.2.2. Definition of Group Timers The group timer is only used when a group is in EXCLUDE mode and it represents the time for the *filter-mode* of the group to expire and switch to INCLUDE mode. */ if (!group->group_filtermode_isexcl) { return; } igmp = group->group_igmp_sock; ifp = igmp->interface; pim_ifp = ifp->info; ifname = ifp->name; lmqi_dsec = pim_ifp->igmp_specific_query_max_response_time_dsec; lmqc = pim_ifp->igmp_last_member_query_count; lmqt_msec = PIM_IGMP_LMQT_MSEC( lmqi_dsec, lmqc); /* lmqt_msec = (100 * lmqi_dsec) * lmqc */ if (PIM_DEBUG_IGMP_TRACE) { char group_str[INET_ADDRSTRLEN]; pim_inet4_dump("", group->group_addr, group_str, sizeof(group_str)); zlog_debug( "%s: group %s on %s: LMQC=%d LMQI=%d dsec LMQT=%d msec", __PRETTY_FUNCTION__, group_str, ifname, lmqc, lmqi_dsec, lmqt_msec); } zassert(group->group_filtermode_isexcl); igmp_group_timer_on(group, lmqt_msec, ifname); } void igmp_source_timer_lower_to_lmqt(struct igmp_source *source) { struct igmp_group *group; struct igmp_sock *igmp; struct interface *ifp; struct pim_interface *pim_ifp; char *ifname; int lmqi_dsec; /* Last Member Query Interval */ int lmqc; /* Last Member Query Count */ int lmqt_msec; /* Last Member Query Time */ group = source->source_group; igmp = group->group_igmp_sock; ifp = igmp->interface; pim_ifp = ifp->info; ifname = ifp->name; lmqi_dsec = pim_ifp->igmp_specific_query_max_response_time_dsec; lmqc = pim_ifp->igmp_last_member_query_count; lmqt_msec = PIM_IGMP_LMQT_MSEC( lmqi_dsec, lmqc); /* lmqt_msec = (100 * lmqi_dsec) * lmqc */ if (PIM_DEBUG_IGMP_TRACE) { char group_str[INET_ADDRSTRLEN]; char source_str[INET_ADDRSTRLEN]; pim_inet4_dump("", group->group_addr, group_str, sizeof(group_str)); pim_inet4_dump("", source->source_addr, source_str, sizeof(source_str)); zlog_debug( "%s: group %s source %s on %s: LMQC=%d LMQI=%d dsec LMQT=%d msec", __PRETTY_FUNCTION__, group_str, source_str, ifname, lmqc, lmqi_dsec, lmqt_msec); } igmp_source_timer_on(group, source, lmqt_msec); } void igmp_v3_send_query(struct igmp_group *group, int fd, const char *ifname, char *query_buf, int query_buf_size, int num_sources, struct in_addr dst_addr, struct in_addr group_addr, int query_max_response_time_dsec, uint8_t s_flag, uint8_t querier_robustness_variable, uint16_t querier_query_interval) { ssize_t msg_size; uint8_t max_resp_code; uint8_t qqic; ssize_t sent; struct sockaddr_in to; socklen_t tolen; uint16_t checksum; zassert(num_sources >= 0); msg_size = IGMP_V3_SOURCES_OFFSET + (num_sources << 2); if (msg_size > query_buf_size) { flog_err( EC_LIB_DEVELOPMENT, "%s %s: unable to send: msg_size=%zd larger than query_buf_size=%d", __FILE__, __PRETTY_FUNCTION__, msg_size, query_buf_size); return; } s_flag = PIM_FORCE_BOOLEAN(s_flag); zassert((s_flag == 0) || (s_flag == 1)); max_resp_code = igmp_msg_encode16to8(query_max_response_time_dsec); qqic = igmp_msg_encode16to8(querier_query_interval); /* RFC 3376: 4.1.6. QRV (Querier's Robustness Variable) If non-zero, the QRV field contains the [Robustness Variable] value used by the querier, i.e., the sender of the Query. If the querier's [Robustness Variable] exceeds 7, the maximum value of the QRV field, the QRV is set to zero. */ if (querier_robustness_variable > 7) { querier_robustness_variable = 0; } query_buf[0] = PIM_IGMP_MEMBERSHIP_QUERY; query_buf[1] = max_resp_code; *(uint16_t *)(query_buf + IGMP_CHECKSUM_OFFSET) = 0; /* for computing checksum */ memcpy(query_buf + 4, &group_addr, sizeof(struct in_addr)); query_buf[8] = (s_flag << 3) | querier_robustness_variable; query_buf[9] = qqic; *(uint16_t *)(query_buf + IGMP_V3_NUMSOURCES_OFFSET) = htons(num_sources); checksum = in_cksum(query_buf, msg_size); *(uint16_t *)(query_buf + IGMP_CHECKSUM_OFFSET) = checksum; if (PIM_DEBUG_IGMP_PACKETS) { char dst_str[INET_ADDRSTRLEN]; char group_str[INET_ADDRSTRLEN]; pim_inet4_dump("", dst_addr, dst_str, sizeof(dst_str)); pim_inet4_dump("", group_addr, group_str, sizeof(group_str)); zlog_debug( "Send IGMPv3 query to %s on %s for group %s, sources=%d msg_size=%zd s_flag=%x QRV=%u QQI=%u QQIC=%02x", dst_str, ifname, group_str, num_sources, msg_size, s_flag, querier_robustness_variable, querier_query_interval, qqic); } memset(&to, 0, sizeof(to)); to.sin_family = AF_INET; to.sin_addr = dst_addr; tolen = sizeof(to); sent = sendto(fd, query_buf, msg_size, MSG_DONTWAIT, (struct sockaddr *)&to, tolen); if (sent != (ssize_t)msg_size) { char dst_str[INET_ADDRSTRLEN]; char group_str[INET_ADDRSTRLEN]; pim_inet4_dump("", dst_addr, dst_str, sizeof(dst_str)); pim_inet4_dump("", group_addr, group_str, sizeof(group_str)); if (sent < 0) { zlog_warn( "Send IGMPv3 query failed due to %s on %s: group=%s msg_size=%zd: errno=%d: %s", dst_str, ifname, group_str, msg_size, errno, safe_strerror(errno)); } else { zlog_warn( "Send IGMPv3 query failed due to %s on %s: group=%s msg_size=%zd: sent=%zd", dst_str, ifname, group_str, msg_size, sent); } return; } /* s_flag sanity test: s_flag must be set for general queries RFC 3376: 6.6.1. Timer Updates When a router sends or receives a query with a clear Suppress Router-Side Processing flag, it must update its timers to reflect the correct timeout values for the group or sources being queried. General queries don't trigger timer update. */ if (!s_flag) { /* general query? */ if (PIM_INADDR_IS_ANY(group_addr)) { char dst_str[INET_ADDRSTRLEN]; char group_str[INET_ADDRSTRLEN]; pim_inet4_dump("", dst_addr, dst_str, sizeof(dst_str)); pim_inet4_dump("", group_addr, group_str, sizeof(group_str)); zlog_warn( "%s: to %s on %s: group=%s sources=%d: s_flag is clear for general query!", __PRETTY_FUNCTION__, dst_str, ifname, group_str, num_sources); } } } void igmp_v3_recv_query(struct igmp_sock *igmp, const char *from_str, char *igmp_msg) { struct interface *ifp; struct pim_interface *pim_ifp; struct in_addr group_addr; uint8_t resv_s_qrv = 0; uint8_t s_flag = 0; uint8_t qrv = 0; int i; memcpy(&group_addr, igmp_msg + 4, sizeof(struct in_addr)); ifp = igmp->interface; pim_ifp = ifp->info; /* * RFC 3376: 4.1.6. QRV (Querier's Robustness Variable) * * Routers adopt the QRV value from the most recently received Query * as their own [Robustness Variable] value, unless that most * recently received QRV was zero, in which case the receivers use * the default [Robustness Variable] value specified in section 8.1 * or a statically configured value. */ resv_s_qrv = igmp_msg[8]; qrv = 7 & resv_s_qrv; igmp->querier_robustness_variable = qrv ? qrv : pim_ifp->igmp_default_robustness_variable; /* * RFC 3376: 4.1.7. QQIC (Querier's Query Interval Code) * * Multicast routers that are not the current querier adopt the QQI * value from the most recently received Query as their own [Query * Interval] value, unless that most recently received QQI was zero, * in which case the receiving routers use the default. */ if (igmp->t_other_querier_timer) { /* other querier present */ uint8_t qqic; uint16_t qqi; qqic = igmp_msg[9]; qqi = igmp_msg_decode8to16(qqic); igmp->querier_query_interval = qqi ? qqi : pim_ifp->igmp_default_query_interval; if (PIM_DEBUG_IGMP_TRACE) { char ifaddr_str[INET_ADDRSTRLEN]; pim_inet4_dump("", igmp->ifaddr, ifaddr_str, sizeof(ifaddr_str)); zlog_debug( "Querier %s new query interval is %s QQI=%u sec (recv QQIC=%02x from %s)", ifaddr_str, qqi ? "recv-non-default" : "default", igmp->querier_query_interval, qqic, from_str); } } /* * RFC 3376: 6.6.1. Timer Updates * * When a router sends or receives a query with a clear Suppress * Router-Side Processing flag, it must update its timers to reflect * the correct timeout values for the group or sources being queried. * * General queries don't trigger timer update. */ s_flag = (1 << 3) & resv_s_qrv; if (!s_flag) { /* s_flag is clear */ if (PIM_INADDR_IS_ANY(group_addr)) { /* this is a general query */ /* log that general query should have the s_flag set */ zlog_warn( "General IGMP query v3 from %s on %s: Suppress Router-Side Processing flag is clear", from_str, ifp->name); } else { struct igmp_group *group; /* this is a non-general query: perform timer updates */ group = find_group_by_addr(igmp, group_addr); if (group) { int recv_num_sources = ntohs(*( uint16_t *)(igmp_msg + IGMP_V3_NUMSOURCES_OFFSET)); /* * RFC 3376: 6.6.1. Timer Updates * Query Q(G,A): Source Timer for sources in A * are lowered to LMQT * Query Q(G): Group Timer is lowered to LMQT */ if (recv_num_sources < 1) { /* Query Q(G): Group Timer is lowered to * LMQT */ igmp_group_timer_lower_to_lmqt(group); } else { /* Query Q(G,A): Source Timer for * sources in A are lowered to LMQT */ /* Scan sources in query and lower their * timers to LMQT */ struct in_addr *sources = (struct in_addr *)(igmp_msg + IGMP_V3_SOURCES_OFFSET); for (i = 0; i < recv_num_sources; ++i) { struct in_addr src_addr; struct igmp_source *src; memcpy(&src_addr, sources + i, sizeof(struct in_addr)); src = igmp_find_source_by_addr( group, src_addr); if (src) { igmp_source_timer_lower_to_lmqt( src); } } } } else { char group_str[INET_ADDRSTRLEN]; pim_inet4_dump("", group_addr, group_str, sizeof(group_str)); zlog_warn( "IGMP query v3 from %s on %s: could not find group %s for timer update", from_str, ifp->name, group_str); } } } /* s_flag is clear: timer updates */ } int igmp_v3_recv_report(struct igmp_sock *igmp, struct in_addr from, const char *from_str, char *igmp_msg, int igmp_msg_len) { uint16_t recv_checksum; uint16_t checksum; int num_groups; uint8_t *group_record; uint8_t *report_pastend = (uint8_t *)igmp_msg + igmp_msg_len; struct interface *ifp = igmp->interface; int i; int local_ncb = 0; struct pim_interface *pim_ifp; if (igmp->mtrace_only) return 0; pim_ifp = igmp->interface->info; if (igmp_msg_len < IGMP_V3_MSG_MIN_SIZE) { zlog_warn( "Recv IGMP report v3 from %s on %s: size=%d shorter than minimum=%d", from_str, ifp->name, igmp_msg_len, IGMP_V3_MSG_MIN_SIZE); return -1; } recv_checksum = *(uint16_t *)(igmp_msg + IGMP_CHECKSUM_OFFSET); /* for computing checksum */ *(uint16_t *)(igmp_msg + IGMP_CHECKSUM_OFFSET) = 0; checksum = in_cksum(igmp_msg, igmp_msg_len); if (checksum != recv_checksum) { zlog_warn( "Recv IGMP report v3 from %s on %s: checksum mismatch: received=%x computed=%x", from_str, ifp->name, recv_checksum, checksum); return -1; } /* Collecting IGMP Rx stats */ igmp->rx_stats.report_v3++; num_groups = ntohs( *(uint16_t *)(igmp_msg + IGMP_V3_REPORT_NUMGROUPS_OFFSET)); if (num_groups < 1) { zlog_warn( "Recv IGMP report v3 from %s on %s: missing group records", from_str, ifp->name); return -1; } if (PIM_DEBUG_IGMP_PACKETS) { zlog_debug( "Recv IGMP report v3 from %s on %s: size=%d checksum=%x groups=%d", from_str, ifp->name, igmp_msg_len, checksum, num_groups); } group_record = (uint8_t *)igmp_msg + IGMP_V3_REPORT_GROUPPRECORD_OFFSET; /* Scan groups */ for (i = 0; i < num_groups; ++i) { struct in_addr rec_group; uint8_t *sources; uint8_t *src; int rec_type; int rec_auxdatalen; int rec_num_sources; int j; struct prefix lncb; struct prefix g; bool filtered = false; if ((group_record + IGMP_V3_GROUP_RECORD_MIN_SIZE) > report_pastend) { zlog_warn( "Recv IGMP report v3 from %s on %s: group record beyond report end", from_str, ifp->name); return -1; } rec_type = group_record[IGMP_V3_GROUP_RECORD_TYPE_OFFSET]; rec_auxdatalen = group_record[IGMP_V3_GROUP_RECORD_AUXDATALEN_OFFSET]; rec_num_sources = ntohs(*( uint16_t *)(group_record + IGMP_V3_GROUP_RECORD_NUMSOURCES_OFFSET)); memcpy(&rec_group, group_record + IGMP_V3_GROUP_RECORD_GROUP_OFFSET, sizeof(struct in_addr)); if (PIM_DEBUG_IGMP_PACKETS) { zlog_debug( "Recv IGMP report v3 from %s on %s: record=%d type=%d auxdatalen=%d sources=%d group=%s", from_str, ifp->name, i, rec_type, rec_auxdatalen, rec_num_sources, inet_ntoa(rec_group)); } /* Scan sources */ sources = group_record + IGMP_V3_GROUP_RECORD_SOURCE_OFFSET; for (j = 0, src = sources; j < rec_num_sources; ++j, src += 4) { if ((src + 4) > report_pastend) { zlog_warn( "Recv IGMP report v3 from %s on %s: group source beyond report end", from_str, ifp->name); return -1; } if (PIM_DEBUG_IGMP_PACKETS) { char src_str[200]; if (!inet_ntop(AF_INET, src, src_str, sizeof(src_str))) sprintf(src_str, ""); zlog_debug( "Recv IGMP report v3 from %s on %s: record=%d group=%s source=%s", from_str, ifp->name, i, inet_ntoa(rec_group), src_str); } } /* for (sources) */ lncb.family = AF_INET; lncb.u.prefix4.s_addr = 0x000000E0; lncb.prefixlen = 24; g.family = AF_INET; g.u.prefix4 = rec_group; g.prefixlen = 32; /* determine filtering status for group */ filtered = pim_is_group_filtered(ifp->info, &rec_group); if (PIM_DEBUG_IGMP_PACKETS && filtered) zlog_debug( "Filtering IGMPv3 group record %s from %s on %s per prefix-list %s", inet_ntoa(rec_group), from_str, ifp->name, pim_ifp->boundary_oil_plist); /* * If we receive a igmp report with the group in 224.0.0.0/24 * then we should ignore it */ if (prefix_match(&lncb, &g)) local_ncb = 1; if (!local_ncb && !filtered) switch (rec_type) { case IGMP_GRP_REC_TYPE_MODE_IS_INCLUDE: igmpv3_report_isin(igmp, from, rec_group, rec_num_sources, (struct in_addr *)sources); break; case IGMP_GRP_REC_TYPE_MODE_IS_EXCLUDE: igmpv3_report_isex( igmp, from, rec_group, rec_num_sources, (struct in_addr *)sources, 0); break; case IGMP_GRP_REC_TYPE_CHANGE_TO_INCLUDE_MODE: igmpv3_report_toin(igmp, from, rec_group, rec_num_sources, (struct in_addr *)sources); break; case IGMP_GRP_REC_TYPE_CHANGE_TO_EXCLUDE_MODE: igmpv3_report_toex(igmp, from, rec_group, rec_num_sources, (struct in_addr *)sources); break; case IGMP_GRP_REC_TYPE_ALLOW_NEW_SOURCES: igmpv3_report_allow(igmp, from, rec_group, rec_num_sources, (struct in_addr *)sources); break; case IGMP_GRP_REC_TYPE_BLOCK_OLD_SOURCES: igmpv3_report_block(igmp, from, rec_group, rec_num_sources, (struct in_addr *)sources); break; default: zlog_warn( "Recv IGMP report v3 from %s on %s: unknown record type: type=%d", from_str, ifp->name, rec_type); } group_record += 8 + (rec_num_sources << 2) + (rec_auxdatalen << 2); local_ncb = 0; } /* for (group records) */ return 0; } frr-7.2.1/pimd/pim_igmpv3.h0000644000000000000000000000775313610377563012404 00000000000000/* * PIM for Quagga * Copyright (C) 2008 Everton da Silva Marques * * This program is free software; you can redistribute it and/or modify * it under the terms of the 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; see the file COPYING; if not, write to the Free Software * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA */ #ifndef PIM_IGMPV3_H #define PIM_IGMPV3_H #include #include "if.h" #define IGMP_V3_CHECKSUM_OFFSET (2) #define IGMP_V3_REPORT_NUMGROUPS_OFFSET (6) #define IGMP_V3_REPORT_GROUPPRECORD_OFFSET (8) #define IGMP_V3_NUMSOURCES_OFFSET (10) #define IGMP_V3_SOURCES_OFFSET (12) #define IGMP_GRP_REC_TYPE_MODE_IS_INCLUDE (1) #define IGMP_GRP_REC_TYPE_MODE_IS_EXCLUDE (2) #define IGMP_GRP_REC_TYPE_CHANGE_TO_INCLUDE_MODE (3) #define IGMP_GRP_REC_TYPE_CHANGE_TO_EXCLUDE_MODE (4) #define IGMP_GRP_REC_TYPE_ALLOW_NEW_SOURCES (5) #define IGMP_GRP_REC_TYPE_BLOCK_OLD_SOURCES (6) /* GMI: Group Membership Interval */ #define PIM_IGMP_GMI_MSEC(qrv,qqi,qri_dsec) ((qrv) * (1000 * (qqi)) + 100 * (qri_dsec)) /* OQPI: Other Querier Present Interval */ #define PIM_IGMP_OQPI_MSEC(qrv,qqi,qri_dsec) ((qrv) * (1000 * (qqi)) + 100 * ((qri_dsec) >> 1)) /* SQI: Startup Query Interval */ #define PIM_IGMP_SQI(qi) (((qi) < 4) ? 1 : ((qi) >> 2)) /* LMQT: Last Member Query Time */ #define PIM_IGMP_LMQT_MSEC(lmqi_dsec, lmqc) ((lmqc) * (100 * (lmqi_dsec))) /* OHPI: Older Host Present Interval */ #define PIM_IGMP_OHPI_DSEC(qrv,qqi,qri_dsec) ((qrv) * (10 * (qqi)) + (qri_dsec)) void igmp_group_reset_gmi(struct igmp_group *group); void igmp_source_reset_gmi(struct igmp_sock *igmp, struct igmp_group *group, struct igmp_source *source); void igmp_source_free(struct igmp_source *source); void igmp_source_delete(struct igmp_source *source); void igmp_source_delete_expired(struct list *source_list); void igmpv3_report_isin(struct igmp_sock *igmp, struct in_addr from, struct in_addr group_addr, int num_sources, struct in_addr *sources); void igmpv3_report_isex(struct igmp_sock *igmp, struct in_addr from, struct in_addr group_addr, int num_sources, struct in_addr *sources, int from_igmp_v2_report); void igmpv3_report_toin(struct igmp_sock *igmp, struct in_addr from, struct in_addr group_addr, int num_sources, struct in_addr *sources); void igmpv3_report_toex(struct igmp_sock *igmp, struct in_addr from, struct in_addr group_addr, int num_sources, struct in_addr *sources); void igmpv3_report_allow(struct igmp_sock *igmp, struct in_addr from, struct in_addr group_addr, int num_sources, struct in_addr *sources); void igmpv3_report_block(struct igmp_sock *igmp, struct in_addr from, struct in_addr group_addr, int num_sources, struct in_addr *sources); void igmp_group_timer_lower_to_lmqt(struct igmp_group *group); void igmp_source_timer_lower_to_lmqt(struct igmp_source *source); struct igmp_source *igmp_find_source_by_addr(struct igmp_group *group, struct in_addr src_addr); void igmp_v3_send_query(struct igmp_group *group, int fd, const char *ifname, char *query_buf, int query_buf_size, int num_sources, struct in_addr dst_addr, struct in_addr group_addr, int query_max_response_time_dsec, uint8_t s_flag, uint8_t querier_robustness_variable, uint16_t querier_query_interval); void igmp_v3_recv_query(struct igmp_sock *igmp, const char *from_str, char *igmp_msg); int igmp_v3_recv_report(struct igmp_sock *igmp, struct in_addr from, const char *from_str, char *igmp_msg, int igmp_msg_len); #endif /* PIM_IGMPV3_H */ frr-7.2.1/pimd/pim_instance.c0000644000000000000000000001055213610377563012765 00000000000000/* * PIM for FRR - PIM Instance * Copyright (C) 2017 Cumulus Networks, Inc. * Donald Sharp * * This program is free software; you can redistribute it and/or modify * it under the terms of the 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; see the file COPYING; if not, write to the * Free Software Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, * MA 02110-1301 USA */ #include #include "hash.h" #include "vrf.h" #include "lib_errors.h" #include "pimd.h" #include "pim_ssm.h" #include "pim_rpf.h" #include "pim_rp.h" #include "pim_mroute.h" #include "pim_oil.h" #include "pim_static.h" #include "pim_ssmpingd.h" #include "pim_vty.h" #include "pim_bsm.h" static void pim_instance_terminate(struct pim_instance *pim) { pim_vxlan_exit(pim); if (pim->ssm_info) { pim_ssm_terminate(pim->ssm_info); pim->ssm_info = NULL; } if (pim->static_routes) list_delete(&pim->static_routes); pim_upstream_terminate(pim); pim_rp_free(pim); pim_bsm_proc_free(pim); /* Traverse and cleanup rpf_hash */ if (pim->rpf_hash) { hash_clean(pim->rpf_hash, (void *)pim_rp_list_hash_clean); hash_free(pim->rpf_hash); pim->rpf_hash = NULL; } pim_if_terminate(pim); pim_oil_terminate(pim); pim_msdp_exit(pim); XFREE(MTYPE_PIM_PIM_INSTANCE, pim); } static struct pim_instance *pim_instance_init(struct vrf *vrf) { struct pim_instance *pim; char hash_name[64]; pim = XCALLOC(MTYPE_PIM_PIM_INSTANCE, sizeof(struct pim_instance)); pim_if_init(pim); pim->keep_alive_time = PIM_KEEPALIVE_PERIOD; pim->rp_keep_alive_time = PIM_RP_KEEPALIVE_PERIOD; pim->ecmp_enable = false; pim->ecmp_rebalance_enable = false; pim->vrf_id = vrf->vrf_id; pim->vrf = vrf; pim->spt.switchover = PIM_SPT_IMMEDIATE; pim->spt.plist = NULL; pim_msdp_init(pim, router->master); pim_vxlan_init(pim); snprintf(hash_name, 64, "PIM %s RPF Hash", vrf->name); pim->rpf_hash = hash_create_size(256, pim_rpf_hash_key, pim_rpf_equal, hash_name); if (PIM_DEBUG_ZEBRA) zlog_debug("%s: NHT rpf hash init ", __PRETTY_FUNCTION__); pim->ssm_info = pim_ssm_init(); pim->static_routes = list_new(); pim->static_routes->del = (void (*)(void *))pim_static_route_free; pim->send_v6_secondary = 1; pim_rp_init(pim); pim_bsm_proc_init(pim); pim_oil_init(pim); pim_upstream_init(pim); pim->last_route_change_time = -1; return pim; } struct pim_instance *pim_get_pim_instance(vrf_id_t vrf_id) { struct vrf *vrf = vrf_lookup_by_id(vrf_id); if (vrf) return vrf->info; return NULL; } static int pim_vrf_new(struct vrf *vrf) { struct pim_instance *pim = pim_instance_init(vrf); zlog_debug("VRF Created: %s(%u)", vrf->name, vrf->vrf_id); vrf->info = (void *)pim; pim_ssmpingd_init(pim); return 0; } static int pim_vrf_delete(struct vrf *vrf) { struct pim_instance *pim = vrf->info; zlog_debug("VRF Deletion: %s(%u)", vrf->name, vrf->vrf_id); pim_ssmpingd_destroy(pim); pim_instance_terminate(pim); return 0; } /* * Code to turn on the pim instance that * we have created with new */ static int pim_vrf_enable(struct vrf *vrf) { struct pim_instance *pim = (struct pim_instance *)vrf->info; zlog_debug("%s: for %s", __PRETTY_FUNCTION__, vrf->name); pim_mroute_socket_enable(pim); return 0; } static int pim_vrf_disable(struct vrf *vrf) { /* Note: This is a callback, the VRF will be deleted by the caller. */ return 0; } static int pim_vrf_config_write(struct vty *vty) { struct vrf *vrf; struct pim_instance *pim; RB_FOREACH (vrf, vrf_name_head, &vrfs_by_name) { pim = vrf->info; if (!pim) continue; if (vrf->vrf_id != VRF_DEFAULT) vty_frame(vty, "vrf %s\n", vrf->name); pim_global_config_write_worker(pim, vty); if (vrf->vrf_id != VRF_DEFAULT) vty_endframe(vty, " exit-vrf\n!\n"); } return 0; } void pim_vrf_init(void) { vrf_init(pim_vrf_new, pim_vrf_enable, pim_vrf_disable, pim_vrf_delete, NULL); vrf_cmd_init(pim_vrf_config_write, &pimd_privs); } void pim_vrf_terminate(void) { vrf_terminate(); } frr-7.2.1/pimd/pim_instance.h0000644000000000000000000000630713610377563012775 00000000000000/* * PIM for FRR - PIM Instance * Copyright (C) 2017 Cumulus Networks, Inc. * Donald Sharp * * This program is free software; you can redistribute it and/or modify * it under the terms of the 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; see the file COPYING; if not, write to the * Free Software Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, * MA 02110-1301 USA */ #ifndef __PIM_INSTANCE_H__ #define __PIM_INSTANCE_H__ #include #include "pim_str.h" #include "pim_msdp.h" #include "pim_assert.h" #include "pim_bsm.h" #include "pim_vxlan_instance.h" #if defined(HAVE_LINUX_MROUTE_H) #include #else /* Below: from */ #ifndef MAXVIFS #define MAXVIFS (256) #endif #endif enum pim_spt_switchover { PIM_SPT_IMMEDIATE, PIM_SPT_INFINITY, }; struct pim_router { struct thread_master *master; uint32_t debugs; int t_periodic; struct pim_assert_metric infinite_assert_metric; long rpf_cache_refresh_delay_msec; int32_t register_suppress_time; int packet_process; int32_t register_probe_time; /* * What is the default vrf that we work in */ vrf_id_t vrf_id; enum mlag_role role; }; /* Per VRF PIM DB */ struct pim_instance { vrf_id_t vrf_id; struct vrf *vrf; struct { enum pim_spt_switchover switchover; char *plist; } spt; struct hash *rpf_hash; void *ssm_info; /* per-vrf SSM configuration */ int send_v6_secondary; struct thread *thread; int mroute_socket; int64_t mroute_socket_creation; int64_t mroute_add_events; int64_t mroute_add_last; int64_t mroute_del_events; int64_t mroute_del_last; struct interface *regiface; // List of static routes; struct list *static_routes; // Upstream vrf specific information struct list *upstream_list; struct hash *upstream_hash; struct timer_wheel *upstream_sg_wheel; /* * RP information */ struct list *rp_list; struct route_table *rp_table; int iface_vif_index[MAXVIFS]; struct list *channel_oil_list; struct hash *channel_oil_hash; struct pim_msdp msdp; struct pim_vxlan_instance vxlan; struct list *ssmpingd_list; struct in_addr ssmpingd_group_addr; unsigned int keep_alive_time; unsigned int rp_keep_alive_time; bool ecmp_enable; bool ecmp_rebalance_enable; /* Bsm related */ struct bsm_scope global_scope; uint64_t bsm_rcvd; uint64_t bsm_sent; uint64_t bsm_dropped; /* If we need to rescan all our upstreams */ struct thread *rpf_cache_refresher; int64_t rpf_cache_refresh_requests; int64_t rpf_cache_refresh_events; int64_t rpf_cache_refresh_last; int64_t scan_oil_events; int64_t scan_oil_last; int64_t nexthop_lookups; int64_t nexthop_lookups_avoided; int64_t last_route_change_time; }; void pim_vrf_init(void); void pim_vrf_terminate(void); struct pim_instance *pim_get_pim_instance(vrf_id_t vrf_id); #endif frr-7.2.1/pimd/pim_int.c0000644000000000000000000000243013610377563011747 00000000000000/* * PIM for Quagga * Copyright (C) 2008 Everton da Silva Marques * * This program is free software; you can redistribute it and/or modify * it under the terms of the 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; see the file COPYING; if not, write to the Free Software * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA */ #include #include #include #include #include "pim_int.h" uint32_t pim_read_uint32_host(const uint8_t *buf) { uint32_t val; memcpy(&val, buf, sizeof(val)); /* val is in netorder */ val = ntohl(val); /* val is in hostorder */ return val; } void pim_write_uint32(uint8_t *buf, uint32_t val_host) { /* val_host is in host order */ val_host = htonl(val_host); /* val_host is in netorder */ memcpy(buf, &val_host, sizeof(val_host)); } frr-7.2.1/pimd/pim_int.h0000644000000000000000000000175013610377563011760 00000000000000/* * PIM for Quagga * Copyright (C) 2008 Everton da Silva Marques * * This program is free software; you can redistribute it and/or modify * it under the terms of the 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; see the file COPYING; if not, write to the Free Software * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA */ #ifndef PIM_INT_H #define PIM_INT_H #include uint32_t pim_read_uint32_host(const uint8_t *buf); void pim_write_uint32(uint8_t *buf, uint32_t val_host); #endif /* PIM_INT_H */ frr-7.2.1/pimd/pim_join.c0000644000000000000000000004625213610377563012126 00000000000000/* * PIM for Quagga * Copyright (C) 2008 Everton da Silva Marques * * This program is free software; you can redistribute it and/or modify * it under the terms of the 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; see the file COPYING; if not, write to the Free Software * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA */ #include #include "log.h" #include "prefix.h" #include "if.h" #include "vty.h" #include "plist.h" #include "pimd.h" #include "pim_str.h" #include "pim_tlv.h" #include "pim_msg.h" #include "pim_pim.h" #include "pim_join.h" #include "pim_oil.h" #include "pim_iface.h" #include "pim_hello.h" #include "pim_ifchannel.h" #include "pim_rpf.h" #include "pim_rp.h" #include "pim_jp_agg.h" #include "pim_util.h" static void on_trace(const char *label, struct interface *ifp, struct in_addr src) { if (PIM_DEBUG_PIM_TRACE) { char src_str[INET_ADDRSTRLEN]; pim_inet4_dump("", src, src_str, sizeof(src_str)); zlog_debug("%s: from %s on %s", label, src_str, ifp->name); } } static void recv_join(struct interface *ifp, struct pim_neighbor *neigh, uint16_t holdtime, struct in_addr upstream, struct prefix_sg *sg, uint8_t source_flags) { struct pim_interface *pim_ifp = NULL; if (PIM_DEBUG_PIM_TRACE) { char up_str[INET_ADDRSTRLEN]; char neigh_str[INET_ADDRSTRLEN]; pim_inet4_dump("", upstream, up_str, sizeof(up_str)); pim_inet4_dump("", neigh->source_addr, neigh_str, sizeof(neigh_str)); zlog_warn( "%s: join (S,G)=%s rpt=%d wc=%d upstream=%s holdtime=%d from %s on %s", __PRETTY_FUNCTION__, pim_str_sg_dump(sg), !!(source_flags & PIM_RPT_BIT_MASK), !!(source_flags & PIM_WILDCARD_BIT_MASK), up_str, holdtime, neigh_str, ifp->name); } pim_ifp = ifp->info; zassert(pim_ifp); ++pim_ifp->pim_ifstat_join_recv; /* * If the RPT and WC are set it's a (*,G) * and the source is the RP */ if ((source_flags & PIM_RPT_BIT_MASK) && (source_flags & PIM_WILDCARD_BIT_MASK)) { struct pim_rpf *rp = RP(pim_ifp->pim, sg->grp); if (!rp) { zlog_warn("%s: Lookup of RP failed for %pSG4", __PRETTY_FUNCTION__, sg); return; } /* * If the RP sent in the message is not * our RP for the group, drop the message */ if (sg->src.s_addr != rp->rpf_addr.u.prefix4.s_addr) { char received_rp[INET_ADDRSTRLEN]; char local_rp[INET_ADDRSTRLEN]; pim_inet4_dump("", sg->src, received_rp, sizeof(received_rp)); pim_inet4_dump("", rp->rpf_addr.u.prefix4, local_rp, sizeof(local_rp)); if (PIM_DEBUG_PIM_TRACE) zlog_warn( "%s: Specified RP(%s) in join is different than our configured RP(%s)", __PRETTY_FUNCTION__, received_rp, local_rp); return; } sg->src.s_addr = INADDR_ANY; } /* Restart join expiry timer */ pim_ifchannel_join_add(ifp, neigh->source_addr, upstream, sg, source_flags, holdtime); } static void recv_prune(struct interface *ifp, struct pim_neighbor *neigh, uint16_t holdtime, struct in_addr upstream, struct prefix_sg *sg, uint8_t source_flags) { struct pim_interface *pim_ifp = NULL; if (PIM_DEBUG_PIM_TRACE) { char up_str[INET_ADDRSTRLEN]; char neigh_str[INET_ADDRSTRLEN]; pim_inet4_dump("", upstream, up_str, sizeof(up_str)); pim_inet4_dump("", neigh->source_addr, neigh_str, sizeof(neigh_str)); zlog_warn( "%s: prune (S,G)=%s rpt=%d wc=%d upstream=%s holdtime=%d from %s on %s", __PRETTY_FUNCTION__, pim_str_sg_dump(sg), source_flags & PIM_RPT_BIT_MASK, source_flags & PIM_WILDCARD_BIT_MASK, up_str, holdtime, neigh_str, ifp->name); } pim_ifp = ifp->info; zassert(pim_ifp); ++pim_ifp->pim_ifstat_prune_recv; if ((source_flags & PIM_RPT_BIT_MASK) && (source_flags & PIM_WILDCARD_BIT_MASK)) { struct pim_rpf *rp = RP(pim_ifp->pim, sg->grp); if (!rp) { if (PIM_DEBUG_PIM_TRACE) zlog_debug("%s: RP for %pSG4 completely failed lookup", __PRETTY_FUNCTION__, sg); return; } // Ignoring Prune *,G's at the moment. if (sg->src.s_addr != rp->rpf_addr.u.prefix4.s_addr) return; sg->src.s_addr = INADDR_ANY; } pim_ifchannel_prune(ifp, upstream, sg, source_flags, holdtime); } int pim_joinprune_recv(struct interface *ifp, struct pim_neighbor *neigh, struct in_addr src_addr, uint8_t *tlv_buf, int tlv_buf_size) { struct prefix msg_upstream_addr; struct pim_interface *pim_ifp; uint8_t msg_num_groups; uint16_t msg_holdtime; int addr_offset; uint8_t *buf; uint8_t *pastend; int remain; int group; buf = tlv_buf; pastend = tlv_buf + tlv_buf_size; pim_ifp = ifp->info; /* Parse ucast addr */ addr_offset = pim_parse_addr_ucast(&msg_upstream_addr, buf, pastend - buf); if (addr_offset < 1) { char src_str[INET_ADDRSTRLEN]; pim_inet4_dump("", src_addr, src_str, sizeof(src_str)); zlog_warn("%s: pim_parse_addr_ucast() failure: from %s on %s", __PRETTY_FUNCTION__, src_str, ifp->name); return -1; } buf += addr_offset; /* Check upstream address family */ if (msg_upstream_addr.family != AF_INET) { if (PIM_DEBUG_PIM_J_P) { char src_str[INET_ADDRSTRLEN]; pim_inet4_dump("", src_addr, src_str, sizeof(src_str)); zlog_warn( "%s: ignoring join/prune directed to unexpected addr family=%d from %s on %s", __PRETTY_FUNCTION__, msg_upstream_addr.family, src_str, ifp->name); } return -2; } remain = pastend - buf; if (remain < 4) { char src_str[INET_ADDRSTRLEN]; pim_inet4_dump("", src_addr, src_str, sizeof(src_str)); zlog_warn( "%s: short join/prune message buffer for group list: size=%d minimum=%d from %s on %s", __PRETTY_FUNCTION__, remain, 4, src_str, ifp->name); return -4; } ++buf; /* skip reserved byte */ msg_num_groups = *(const uint8_t *)buf; ++buf; msg_holdtime = ntohs(*(const uint16_t *)buf); ++buf; ++buf; if (PIM_DEBUG_PIM_J_P) { char src_str[INET_ADDRSTRLEN]; char upstream_str[INET_ADDRSTRLEN]; pim_inet4_dump("", src_addr, src_str, sizeof(src_str)); pim_inet4_dump("", msg_upstream_addr.u.prefix4, upstream_str, sizeof(upstream_str)); zlog_debug( "%s: join/prune upstream=%s groups=%d holdtime=%d from %s on %s", __PRETTY_FUNCTION__, upstream_str, msg_num_groups, msg_holdtime, src_str, ifp->name); } /* Scan groups */ for (group = 0; group < msg_num_groups; ++group) { struct prefix_sg sg; uint8_t msg_source_flags; uint16_t msg_num_joined_sources; uint16_t msg_num_pruned_sources; int source; struct pim_ifchannel *starg_ch = NULL, *sg_ch = NULL; bool filtered = false; memset(&sg, 0, sizeof(struct prefix_sg)); addr_offset = pim_parse_addr_group(&sg, buf, pastend - buf); if (addr_offset < 1) { return -5; } buf += addr_offset; remain = pastend - buf; if (remain < 4) { char src_str[INET_ADDRSTRLEN]; pim_inet4_dump("", src_addr, src_str, sizeof(src_str)); zlog_warn( "%s: short join/prune buffer for source list: size=%d minimum=%d from %s on %s", __PRETTY_FUNCTION__, remain, 4, src_str, ifp->name); return -6; } msg_num_joined_sources = ntohs(*(const uint16_t *)buf); buf += 2; msg_num_pruned_sources = ntohs(*(const uint16_t *)buf); buf += 2; if (PIM_DEBUG_PIM_J_P) { char src_str[INET_ADDRSTRLEN]; char upstream_str[INET_ADDRSTRLEN]; char group_str[INET_ADDRSTRLEN]; pim_inet4_dump("", src_addr, src_str, sizeof(src_str)); pim_inet4_dump("", msg_upstream_addr.u.prefix4, upstream_str, sizeof(upstream_str)); pim_inet4_dump("", sg.grp, group_str, sizeof(group_str)); zlog_warn( "%s: join/prune upstream=%s group=%s/32 join_src=%d prune_src=%d from %s on %s", __PRETTY_FUNCTION__, upstream_str, group_str, msg_num_joined_sources, msg_num_pruned_sources, src_str, ifp->name); } /* boundary check */ filtered = pim_is_group_filtered(pim_ifp, &sg.grp); /* Scan joined sources */ for (source = 0; source < msg_num_joined_sources; ++source) { addr_offset = pim_parse_addr_source( &sg, &msg_source_flags, buf, pastend - buf); if (addr_offset < 1) { return -7; } buf += addr_offset; /* if we are filtering this group, skip the join */ if (filtered) continue; recv_join(ifp, neigh, msg_holdtime, msg_upstream_addr.u.prefix4, &sg, msg_source_flags); if (sg.src.s_addr == INADDR_ANY) { starg_ch = pim_ifchannel_find(ifp, &sg); if (starg_ch) pim_ifchannel_set_star_g_join_state( starg_ch, 0, 1); } } /* Scan pruned sources */ for (source = 0; source < msg_num_pruned_sources; ++source) { addr_offset = pim_parse_addr_source( &sg, &msg_source_flags, buf, pastend - buf); if (addr_offset < 1) { return -8; } buf += addr_offset; /* if we are filtering this group, skip the prune */ if (filtered) continue; recv_prune(ifp, neigh, msg_holdtime, msg_upstream_addr.u.prefix4, &sg, msg_source_flags); /* * So if we are receiving a S,G,RPT prune * before we have any data for that S,G * We need to retrieve the sg_ch after * we parse the prune. */ sg_ch = pim_ifchannel_find(ifp, &sg); /* Received SG-RPT Prune delete oif from specific S,G */ if (starg_ch && sg_ch && (msg_source_flags & PIM_RPT_BIT_MASK) && !(msg_source_flags & PIM_WILDCARD_BIT_MASK)) { struct pim_upstream *up = sg_ch->upstream; PIM_IF_FLAG_SET_S_G_RPT(sg_ch->flags); if (up) { if (PIM_DEBUG_TRACE) zlog_debug( "%s: SGRpt flag is set, del inherit oif from up %s", __PRETTY_FUNCTION__, up->sg_str); pim_channel_del_oif( up->channel_oil, starg_ch->interface, PIM_OIF_FLAG_PROTO_STAR); } } } if (starg_ch && !filtered) pim_ifchannel_set_star_g_join_state(starg_ch, 1, 0); starg_ch = NULL; } /* scan groups */ return 0; } /* * J/P Message Format * * While the RFC clearly states that this is 32 bits wide, it * is cheating. These fields: * Encoded-Unicast format (6 bytes MIN) * Encoded-Group format (8 bytes MIN) * Encoded-Source format (8 bytes MIN) * are *not* 32 bits wide. * * Nor does the RFC explicitly call out the size for: * Reserved (1 byte) * Num Groups (1 byte) * Holdtime (2 bytes) * Number of Joined Sources (2 bytes) * Number of Pruned Sources (2 bytes) * * This leads to a missleading representation from casual * reading and making assumptions. Be careful! * * 0 1 2 3 * 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 * +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ * |PIM Ver| Type | Reserved | Checksum | * +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ * | Upstream Neighbor Address (Encoded-Unicast format) | * +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ * | Reserved | Num groups | Holdtime | * +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ * | Multicast Group Address 1 (Encoded-Group format) | * +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ * | Number of Joined Sources | Number of Pruned Sources | * +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ * | Joined Source Address 1 (Encoded-Source format) | * +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ * | . | * | . | * +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ * | Joined Source Address n (Encoded-Source format) | * +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ * | Pruned Source Address 1 (Encoded-Source format) | * +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ * | . | * | . | * +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ * | Pruned Source Address n (Encoded-Source format) | * +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ * | Multicast Group Address m (Encoded-Group format) | * +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ * | Number of Joined Sources | Number of Pruned Sources | * +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ * | Joined Source Address 1 (Encoded-Source format) | * +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ * | . | * | . | * +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ * | Joined Source Address n (Encoded-Source format) | * +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ * | Pruned Source Address 1 (Encoded-Source format) | * +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ * | . | * | . | * +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ * | Pruned Source Address n (Encoded-Source format) | * +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ */ int pim_joinprune_send(struct pim_rpf *rpf, struct list *groups) { struct pim_jp_agg_group *group; struct pim_interface *pim_ifp = NULL; struct pim_jp_groups *grp = NULL; struct pim_jp *msg = NULL; struct listnode *node, *nnode; uint8_t pim_msg[10000]; uint8_t *curr_ptr = pim_msg; bool new_packet = true; size_t packet_left = 0; size_t packet_size = 0; size_t group_size = 0; if (rpf->source_nexthop.interface) pim_ifp = rpf->source_nexthop.interface->info; else { zlog_warn("%s: RPF interface is not present", __PRETTY_FUNCTION__); return -1; } on_trace(__PRETTY_FUNCTION__, rpf->source_nexthop.interface, rpf->rpf_addr.u.prefix4); if (!pim_ifp) { zlog_warn("%s: multicast not enabled on interface %s", __PRETTY_FUNCTION__, rpf->source_nexthop.interface->name); return -1; } if (PIM_INADDR_IS_ANY(rpf->rpf_addr.u.prefix4)) { if (PIM_DEBUG_PIM_J_P) { char dst_str[INET_ADDRSTRLEN]; pim_inet4_dump("", rpf->rpf_addr.u.prefix4, dst_str, sizeof(dst_str)); zlog_debug("%s: upstream=%s is myself on interface %s", __PRETTY_FUNCTION__, dst_str, rpf->source_nexthop.interface->name); } return 0; } /* RFC 4601: 4.3.1. Sending Hello Messages Thus, if a router needs to send a Join/Prune or Assert message on an interface on which it has not yet sent a Hello message with the currently configured IP address, then it MUST immediately send the relevant Hello message without waiting for the Hello Timer to expire, followed by the Join/Prune or Assert message. */ pim_hello_require(rpf->source_nexthop.interface); for (ALL_LIST_ELEMENTS(groups, node, nnode, group)) { if (new_packet) { msg = (struct pim_jp *)pim_msg; memset(msg, 0, sizeof(*msg)); pim_msg_addr_encode_ipv4_ucast((uint8_t *)&msg->addr, rpf->rpf_addr.u.prefix4); msg->reserved = 0; msg->holdtime = htons(PIM_JP_HOLDTIME); new_packet = false; grp = &msg->groups[0]; curr_ptr = (uint8_t *)grp; packet_size = sizeof(struct pim_msg_header); packet_size += sizeof(struct pim_encoded_ipv4_unicast); packet_size += 4; // reserved (1) + groups (1) + holdtime (2) packet_left = rpf->source_nexthop.interface->mtu - 24; packet_left -= packet_size; } if (PIM_DEBUG_PIM_J_P) { char dst_str[INET_ADDRSTRLEN]; char grp_str[INET_ADDRSTRLEN]; pim_inet4_dump("", rpf->rpf_addr.u.prefix4, dst_str, sizeof(dst_str)); pim_inet4_dump("", group->group, grp_str, sizeof(grp_str)); zlog_debug( "%s: sending (G)=%s to upstream=%s on interface %s", __PRETTY_FUNCTION__, grp_str, dst_str, rpf->source_nexthop.interface->name); } group_size = pim_msg_get_jp_group_size(group->sources); if (group_size > packet_left) { pim_msg_build_header(pim_msg, packet_size, PIM_MSG_TYPE_JOIN_PRUNE, false); if (pim_msg_send(pim_ifp->pim_sock_fd, pim_ifp->primary_address, qpim_all_pim_routers_addr, pim_msg, packet_size, rpf->source_nexthop.interface->name)) { zlog_warn( "%s: could not send PIM message on interface %s", __PRETTY_FUNCTION__, rpf->source_nexthop.interface->name); } msg = (struct pim_jp *)pim_msg; memset(msg, 0, sizeof(*msg)); pim_msg_addr_encode_ipv4_ucast((uint8_t *)&msg->addr, rpf->rpf_addr.u.prefix4); msg->reserved = 0; msg->holdtime = htons(PIM_JP_HOLDTIME); new_packet = false; grp = &msg->groups[0]; curr_ptr = (uint8_t *)grp; packet_size = sizeof(struct pim_msg_header); packet_size += sizeof(struct pim_encoded_ipv4_unicast); packet_size += 4; // reserved (1) + groups (1) + holdtime (2) packet_left = rpf->source_nexthop.interface->mtu - 24; packet_left -= packet_size; } msg->num_groups++; /* Build PIM message */ curr_ptr += group_size; packet_left -= group_size; packet_size += group_size; pim_msg_build_jp_groups(grp, group, group_size); pim_ifp->pim_ifstat_join_send += ntohs(grp->joins); pim_ifp->pim_ifstat_prune_send += ntohs(grp->prunes); if (PIM_DEBUG_PIM_TRACE) zlog_debug( "%s: interface %s num_joins %u num_prunes %u", __PRETTY_FUNCTION__, rpf->source_nexthop.interface->name, ntohs(grp->joins), ntohs(grp->prunes)); grp = (struct pim_jp_groups *)curr_ptr; if (packet_left < sizeof(struct pim_jp_groups) || msg->num_groups == 255) { pim_msg_build_header(pim_msg, packet_size, PIM_MSG_TYPE_JOIN_PRUNE, false); if (pim_msg_send(pim_ifp->pim_sock_fd, pim_ifp->primary_address, qpim_all_pim_routers_addr, pim_msg, packet_size, rpf->source_nexthop.interface->name)) { zlog_warn( "%s: could not send PIM message on interface %s", __PRETTY_FUNCTION__, rpf->source_nexthop.interface->name); } new_packet = true; } } if (!new_packet) { // msg->num_groups = htons (msg->num_groups); pim_msg_build_header(pim_msg, packet_size, PIM_MSG_TYPE_JOIN_PRUNE, false); if (pim_msg_send(pim_ifp->pim_sock_fd, pim_ifp->primary_address, qpim_all_pim_routers_addr, pim_msg, packet_size, rpf->source_nexthop.interface->name)) { zlog_warn( "%s: could not send PIM message on interface %s", __PRETTY_FUNCTION__, rpf->source_nexthop.interface->name); } } return 0; } frr-7.2.1/pimd/pim_join.h0000644000000000000000000000221413610377563012121 00000000000000/* * PIM for Quagga * Copyright (C) 2008 Everton da Silva Marques * * This program is free software; you can redistribute it and/or modify * it under the terms of the 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; see the file COPYING; if not, write to the Free Software * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA */ #ifndef PIM_JOIN_H #define PIM_JOIN_H #include #include "if.h" #include "pim_neighbor.h" int pim_joinprune_recv(struct interface *ifp, struct pim_neighbor *neigh, struct in_addr src_addr, uint8_t *tlv_buf, int tlv_buf_size); int pim_joinprune_send(struct pim_rpf *nexthop, struct list *groups); #endif /* PIM_JOIN_H */ frr-7.2.1/pimd/pim_jp_agg.c0000644000000000000000000002113013610377563012402 00000000000000/* * PIM for FRR - J/P Aggregation * Copyright (C) 2017 Cumulus Networks, Inc. * Donald Sharp * * This program is free software; you can redistribute it and/or modify * it under the terms of the 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; see the file COPYING; if not, write to the Free Software * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA */ #include #include "linklist.h" #include "log.h" #include "vrf.h" #include "if.h" #include "pimd.h" #include "pim_msg.h" #include "pim_jp_agg.h" #include "pim_join.h" #include "pim_iface.h" void pim_jp_agg_group_list_free(struct pim_jp_agg_group *jag) { list_delete(&jag->sources); XFREE(MTYPE_PIM_JP_AGG_GROUP, jag); } static void pim_jp_agg_src_free(struct pim_jp_sources *js) { struct pim_upstream *up = js->up; /* * When we are being called here, we know * that the neighbor is going away start * the normal j/p timer so that it can * pick this shit back up when the * nbr comes back alive */ if (up) join_timer_start(js->up); XFREE(MTYPE_PIM_JP_AGG_SOURCE, js); } int pim_jp_agg_group_list_cmp(void *arg1, void *arg2) { const struct pim_jp_agg_group *jag1 = (const struct pim_jp_agg_group *)arg1; const struct pim_jp_agg_group *jag2 = (const struct pim_jp_agg_group *)arg2; if (jag1->group.s_addr < jag2->group.s_addr) return -1; if (jag1->group.s_addr > jag2->group.s_addr) return 1; return 0; } static int pim_jp_agg_src_cmp(void *arg1, void *arg2) { const struct pim_jp_sources *js1 = (const struct pim_jp_sources *)arg1; const struct pim_jp_sources *js2 = (const struct pim_jp_sources *)arg2; if (js1->is_join && !js2->is_join) return -1; if (!js1->is_join && js2->is_join) return 1; if ((uint32_t)js1->up->sg.src.s_addr < (uint32_t)js2->up->sg.src.s_addr) return -1; if ((uint32_t)js1->up->sg.src.s_addr > (uint32_t)js2->up->sg.src.s_addr) return 1; return 0; } /* * This function is used by scan_oil to clear * the created jp_agg_group created when * figuring out where to send prunes * and joins. */ void pim_jp_agg_clear_group(struct list *group) { struct listnode *gnode, *gnnode; struct listnode *snode, *snnode; struct pim_jp_agg_group *jag; struct pim_jp_sources *js; for (ALL_LIST_ELEMENTS(group, gnode, gnnode, jag)) { for (ALL_LIST_ELEMENTS(jag->sources, snode, snnode, js)) { listnode_delete(jag->sources, js); js->up = NULL; XFREE(MTYPE_PIM_JP_AGG_SOURCE, js); } list_delete(&jag->sources); listnode_delete(group, jag); XFREE(MTYPE_PIM_JP_AGG_GROUP, jag); } } static struct pim_iface_upstream_switch * pim_jp_agg_get_interface_upstream_switch_list(struct pim_rpf *rpf) { struct pim_interface *pim_ifp = rpf->source_nexthop.interface->info; struct pim_iface_upstream_switch *pius; struct listnode *node, *nnode; /* Old interface is pim disabled */ if (!pim_ifp) return NULL; for (ALL_LIST_ELEMENTS(pim_ifp->upstream_switch_list, node, nnode, pius)) { if (pius->address.s_addr == rpf->rpf_addr.u.prefix4.s_addr) break; } if (!pius) { pius = XCALLOC(MTYPE_PIM_JP_AGG_GROUP, sizeof(struct pim_iface_upstream_switch)); pius->address.s_addr = rpf->rpf_addr.u.prefix4.s_addr; pius->us = list_new(); listnode_add_sort(pim_ifp->upstream_switch_list, pius); } return pius; } void pim_jp_agg_remove_group(struct list *group, struct pim_upstream *up) { struct listnode *node, *nnode; struct pim_jp_agg_group *jag = NULL; struct pim_jp_sources *js = NULL; for (ALL_LIST_ELEMENTS(group, node, nnode, jag)) { if (jag->group.s_addr == up->sg.grp.s_addr) break; } if (!jag) return; for (ALL_LIST_ELEMENTS(jag->sources, node, nnode, js)) { if (js->up == up) break; } if (js) { js->up = NULL; listnode_delete(jag->sources, js); XFREE(MTYPE_PIM_JP_AGG_SOURCE, js); } if (jag->sources->count == 0) { list_delete(&jag->sources); listnode_delete(group, jag); XFREE(MTYPE_PIM_JP_AGG_GROUP, jag); } } int pim_jp_agg_is_in_list(struct list *group, struct pim_upstream *up) { struct listnode *node, *nnode; struct pim_jp_agg_group *jag = NULL; struct pim_jp_sources *js = NULL; for (ALL_LIST_ELEMENTS(group, node, nnode, jag)) { if (jag->group.s_addr == up->sg.grp.s_addr) break; } if (!jag) return 0; for (ALL_LIST_ELEMENTS(jag->sources, node, nnode, js)) { if (js->up == up) return 1; } return 0; } //#define PIM_JP_AGG_DEBUG 1 /* * For the given upstream, check all the neighbor * jp_agg lists and ensure that it is not * in another list * * *IF* ignore is true we can skip * up->rpf.source_nexthop.interface particular interface for checking * * This is a debugging function, Probably * can be safely compiled out in real * builds */ void pim_jp_agg_upstream_verification(struct pim_upstream *up, bool ignore) { #ifdef PIM_JP_AGG_DEBUG struct interface *ifp; struct pim_interface *pim_ifp; struct pim_instance *pim; if (!up->rpf.source_nexthop.interface) { if (PIM_DEBUG_TRACE) zlog_debug("%s: up %s RPF is not present", __PRETTY_FUNCTION__, up->sg_str); return; } pim_ifp = up->rpf.source_nexthop.interface->info; pim = pim_ifp->pim; FOR_ALL_INTERFACES (pim->vrf, ifp) { pim_ifp = ifp->info; struct listnode *nnode; if (ignore && ifp == up->rpf.source_nexthop.interface) continue; if (pim_ifp) { struct pim_neighbor *neigh; for (ALL_LIST_ELEMENTS_RO(pim_ifp->pim_neighbor_list, nnode, neigh)) { assert(!pim_jp_agg_is_in_list( neigh->upstream_jp_agg, up)); } } } #else return; #endif } void pim_jp_agg_add_group(struct list *group, struct pim_upstream *up, bool is_join) { struct listnode *node, *nnode; struct pim_jp_agg_group *jag = NULL; struct pim_jp_sources *js = NULL; for (ALL_LIST_ELEMENTS(group, node, nnode, jag)) { if (jag->group.s_addr == up->sg.grp.s_addr) break; } if (!jag) { jag = XCALLOC(MTYPE_PIM_JP_AGG_GROUP, sizeof(struct pim_jp_agg_group)); jag->group.s_addr = up->sg.grp.s_addr; jag->sources = list_new(); jag->sources->cmp = pim_jp_agg_src_cmp; jag->sources->del = (void (*)(void *))pim_jp_agg_src_free; listnode_add_sort(group, jag); } for (ALL_LIST_ELEMENTS(jag->sources, node, nnode, js)) { if (js->up == up) break; } if (!js) { js = XCALLOC(MTYPE_PIM_JP_AGG_SOURCE, sizeof(struct pim_jp_sources)); js->up = up; js->is_join = is_join; listnode_add_sort(jag->sources, js); } else { if (js->is_join != is_join) { listnode_delete(jag->sources, js); js->is_join = is_join; listnode_add_sort(jag->sources, js); } } } void pim_jp_agg_switch_interface(struct pim_rpf *orpf, struct pim_rpf *nrpf, struct pim_upstream *up) { struct pim_iface_upstream_switch *opius; struct pim_iface_upstream_switch *npius; opius = pim_jp_agg_get_interface_upstream_switch_list(orpf); npius = pim_jp_agg_get_interface_upstream_switch_list(nrpf); /* * RFC 4601: 4.5.7. Sending (S,G) Join/Prune Messages * * Transitions from Joined State * * RPF'(S,G) changes not due to an Assert * * The upstream (S,G) state machine remains in Joined * state. Send Join(S,G) to the new upstream neighbor, which is * the new value of RPF'(S,G). Send Prune(S,G) to the old * upstream neighbor, which is the old value of RPF'(S,G). Set * the Join Timer (JT) to expire after t_periodic seconds. */ /* send Prune(S,G) to the old upstream neighbor */ if (opius) pim_jp_agg_add_group(opius->us, up, false); /* send Join(S,G) to the current upstream neighbor */ pim_jp_agg_add_group(npius->us, up, true); } void pim_jp_agg_single_upstream_send(struct pim_rpf *rpf, struct pim_upstream *up, bool is_join) { static struct list *groups = NULL; static struct pim_jp_agg_group jag; static struct pim_jp_sources js; static bool first = true; /* skip JP upstream messages if source is directly connected */ if (!up || !rpf->source_nexthop.interface || pim_if_connected_to_source( rpf->source_nexthop .interface, up->sg.src)) return; if (first) { groups = list_new(); jag.sources = list_new(); listnode_add(groups, &jag); listnode_add(jag.sources, &js); first = false; } jag.group.s_addr = up->sg.grp.s_addr; js.up = up; js.is_join = is_join; pim_joinprune_send(rpf, groups); } frr-7.2.1/pimd/pim_jp_agg.h0000644000000000000000000000331613610377563012415 00000000000000/* * PIM for FRR - J/P Aggregation * Copyright (C) 2017 Cumulus Networks, Inc. * Donald Sharp * * This program is free software; you can redistribute it and/or modify * it under the terms of the 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; see the file COPYING; if not, write to the Free Software * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA */ #ifndef __PIM_JP_AGG_H__ #define __PIM_JP_AGG_H__ struct pim_jp_sources { struct pim_upstream *up; int is_join; }; struct pim_jp_agg_group { struct in_addr group; struct list *sources; }; void pim_jp_agg_upstream_verification(struct pim_upstream *up, bool ignore); int pim_jp_agg_is_in_list(struct list *group, struct pim_upstream *up); void pim_jp_agg_group_list_free(struct pim_jp_agg_group *jag); int pim_jp_agg_group_list_cmp(void *arg1, void *arg2); void pim_jp_agg_clear_group(struct list *group); void pim_jp_agg_remove_group(struct list *group, struct pim_upstream *up); void pim_jp_agg_add_group(struct list *group, struct pim_upstream *up, bool is_join); void pim_jp_agg_switch_interface(struct pim_rpf *orpf, struct pim_rpf *nrpf, struct pim_upstream *up); void pim_jp_agg_single_upstream_send(struct pim_rpf *rpf, struct pim_upstream *up, bool is_join); #endif frr-7.2.1/pimd/pim_macro.c0000644000000000000000000002610213610377563012260 00000000000000/* * PIM for Quagga * Copyright (C) 2008 Everton da Silva Marques * * This program is free software; you can redistribute it and/or modify * it under the terms of the 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; see the file COPYING; if not, write to the Free Software * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA */ #include #include "log.h" #include "prefix.h" #include "vty.h" #include "plist.h" #include "pimd.h" #include "pim_macro.h" #include "pim_iface.h" #include "pim_ifchannel.h" #include "pim_rp.h" /* DownstreamJPState(S,G,I) is the per-interface state machine for receiving (S,G) Join/Prune messages. DownstreamJPState(S,G,I) is either Join or Prune-Pending DownstreamJPState(*,G,I) is either Join or Prune-Pending */ static int downstream_jpstate_isjoined(const struct pim_ifchannel *ch) { switch (ch->ifjoin_state) { case PIM_IFJOIN_NOINFO: case PIM_IFJOIN_PRUNE: case PIM_IFJOIN_PRUNE_TMP: case PIM_IFJOIN_PRUNE_PENDING_TMP: return 0; break; case PIM_IFJOIN_JOIN: case PIM_IFJOIN_PRUNE_PENDING: return 1; break; } return 0; } /* The clause "local_receiver_include(S,G,I)" is true if the IGMP/MLD module or other local membership mechanism has determined that local members on interface I desire to receive traffic sent specifically by S to G. */ static int local_receiver_include(const struct pim_ifchannel *ch) { /* local_receiver_include(S,G,I) ? */ return ch->local_ifmembership == PIM_IFMEMBERSHIP_INCLUDE; } /* RFC 4601: 4.1.6. State Summarization Macros The set "joins(S,G)" is the set of all interfaces on which the router has received (S,G) Joins: joins(S,G) = { all interfaces I such that DownstreamJPState(S,G,I) is either Join or Prune-Pending } DownstreamJPState(S,G,I) is either Join or Prune-Pending ? */ int pim_macro_chisin_joins(const struct pim_ifchannel *ch) { return downstream_jpstate_isjoined(ch); } /* RFC 4601: 4.6.5. Assert State Macros The set "lost_assert(S,G)" is the set of all interfaces on which the router has received (S,G) joins but has lost an (S,G) assert. lost_assert(S,G) = { all interfaces I such that lost_assert(S,G,I) == true } bool lost_assert(S,G,I) { if ( RPF_interface(S) == I ) { return false } else { return ( AssertWinner(S,G,I) != NULL AND AssertWinner(S,G,I) != me AND (AssertWinnerMetric(S,G,I) is better than spt_assert_metric(S,I) ) } } AssertWinner(S,G,I) is the IP source address of the Assert(S,G) packet that won an Assert. */ int pim_macro_ch_lost_assert(const struct pim_ifchannel *ch) { struct interface *ifp; struct pim_interface *pim_ifp; struct pim_assert_metric spt_assert_metric; ifp = ch->interface; if (!ifp) { zlog_warn("%s: (S,G)=%s: null interface", __PRETTY_FUNCTION__, ch->sg_str); return 0; /* false */ } /* RPF_interface(S) == I ? */ if (ch->upstream->rpf.source_nexthop.interface == ifp) return 0; /* false */ pim_ifp = ifp->info; if (!pim_ifp) { zlog_warn("%s: (S,G)=%s: multicast not enabled on interface %s", __PRETTY_FUNCTION__, ch->sg_str, ifp->name); return 0; /* false */ } if (PIM_INADDR_IS_ANY(ch->ifassert_winner)) return 0; /* false */ /* AssertWinner(S,G,I) == me ? */ if (ch->ifassert_winner.s_addr == pim_ifp->primary_address.s_addr) return 0; /* false */ spt_assert_metric = pim_macro_spt_assert_metric( &ch->upstream->rpf, pim_ifp->primary_address); return pim_assert_metric_better(&ch->ifassert_winner_metric, &spt_assert_metric); } /* RFC 4601: 4.1.6. State Summarization Macros pim_include(S,G) = { all interfaces I such that: ( (I_am_DR( I ) AND lost_assert(S,G,I) == false ) OR AssertWinner(S,G,I) == me ) AND local_receiver_include(S,G,I) } AssertWinner(S,G,I) is the IP source address of the Assert(S,G) packet that won an Assert. */ int pim_macro_chisin_pim_include(const struct pim_ifchannel *ch) { struct pim_interface *pim_ifp = ch->interface->info; if (!pim_ifp) { zlog_warn("%s: (S,G)=%s: multicast not enabled on interface %s", __PRETTY_FUNCTION__, ch->sg_str, ch->interface->name); return 0; /* false */ } /* local_receiver_include(S,G,I) ? */ if (!local_receiver_include(ch)) return 0; /* false */ /* OR AssertWinner(S,G,I) == me ? */ if (ch->ifassert_winner.s_addr == pim_ifp->primary_address.s_addr) return 1; /* true */ return ( /* I_am_DR( I ) ? */ PIM_I_am_DR(pim_ifp) && /* lost_assert(S,G,I) == false ? */ (!pim_macro_ch_lost_assert(ch))); } int pim_macro_chisin_joins_or_include(const struct pim_ifchannel *ch) { if (pim_macro_chisin_joins(ch)) return 1; /* true */ return pim_macro_chisin_pim_include(ch); } /* RFC 4601: 4.6.1. (S,G) Assert Message State Machine CouldAssert(S,G,I) = SPTbit(S,G)==TRUE AND (RPF_interface(S) != I) AND (I in ( ( joins(*,*,RP(G)) (+) joins(*,G) (-) prunes(S,G,rpt) ) (+) ( pim_include(*,G) (-) pim_exclude(S,G) ) (-) lost_assert(*,G) (+) joins(S,G) (+) pim_include(S,G) ) ) CouldAssert(S,G,I) is true for downstream interfaces that would be in the inherited_olist(S,G) if (S,G) assert information was not taken into account. CouldAssert(S,G,I) may be affected by changes in the following: pim_ifp->primary_address pim_ifp->pim_dr_addr ch->ifassert_winner_metric ch->ifassert_winner ch->local_ifmembership ch->ifjoin_state ch->upstream->rpf.source_nexthop.mrib_metric_preference ch->upstream->rpf.source_nexthop.mrib_route_metric ch->upstream->rpf.source_nexthop.interface */ int pim_macro_ch_could_assert_eval(const struct pim_ifchannel *ch) { struct interface *ifp; ifp = ch->interface; if (!ifp) { zlog_warn("%s: (S,G)=%s: null interface", __PRETTY_FUNCTION__, ch->sg_str); return 0; /* false */ } /* SPTbit(S,G) == true */ if (ch->upstream->sptbit == PIM_UPSTREAM_SPTBIT_FALSE) return 0; /* false */ /* RPF_interface(S) != I ? */ if (ch->upstream->rpf.source_nexthop.interface == ifp) return 0; /* false */ /* I in joins(S,G) (+) pim_include(S,G) ? */ return pim_macro_chisin_joins_or_include(ch); } /* RFC 4601: 4.6.3. Assert Metrics spt_assert_metric(S,I) gives the assert metric we use if we're sending an assert based on active (S,G) forwarding state: assert_metric spt_assert_metric(S,I) { return {0,MRIB.pref(S),MRIB.metric(S),my_ip_address(I)} } */ struct pim_assert_metric pim_macro_spt_assert_metric(const struct pim_rpf *rpf, struct in_addr ifaddr) { struct pim_assert_metric metric; metric.rpt_bit_flag = 0; metric.metric_preference = rpf->source_nexthop.mrib_metric_preference; metric.route_metric = rpf->source_nexthop.mrib_route_metric; metric.ip_address = ifaddr; return metric; } /* RFC 4601: 4.6.3. Assert Metrics An assert metric for (S,G) to include in (or compare against) an Assert message sent on interface I should be computed using the following pseudocode: assert_metric my_assert_metric(S,G,I) { if( CouldAssert(S,G,I) == true ) { return spt_assert_metric(S,I) } else if( CouldAssert(*,G,I) == true ) { return rpt_assert_metric(G,I) } else { return infinite_assert_metric() } } */ struct pim_assert_metric pim_macro_ch_my_assert_metric_eval(const struct pim_ifchannel *ch) { struct pim_interface *pim_ifp; pim_ifp = ch->interface->info; if (pim_ifp) { if (PIM_IF_FLAG_TEST_COULD_ASSERT(ch->flags)) { return pim_macro_spt_assert_metric( &ch->upstream->rpf, pim_ifp->primary_address); } } return router->infinite_assert_metric; } /* RFC 4601 4.2. Data Packet Forwarding Rules Macro: inherited_olist(S,G) = inherited_olist(S,G,rpt) (+) joins(S,G) (+) pim_include(S,G) (-) lost_assert(S,G) */ static int pim_macro_chisin_inherited_olist(const struct pim_ifchannel *ch) { if (pim_macro_ch_lost_assert(ch)) return 0; /* false */ return pim_macro_chisin_joins_or_include(ch); } /* RFC 4601 4.2. Data Packet Forwarding Rules RFC 4601 4.8.2. PIM-SSM-Only Routers Additionally, the Packet forwarding rules of Section 4.2 can be simplified in a PIM-SSM-only router: iif is the incoming interface of the packet. oiflist = NULL if (iif == RPF_interface(S) AND UpstreamJPState(S,G) == Joined) { oiflist = inherited_olist(S,G) } else if (iif is in inherited_olist(S,G)) { send Assert(S,G) on iif } oiflist = oiflist (-) iif forward packet on all interfaces in oiflist Macro: inherited_olist(S,G) = joins(S,G) (+) pim_include(S,G) (-) lost_assert(S,G) Note: - The following test is performed as response to WRONGVIF kernel upcall: if (iif is in inherited_olist(S,G)) { send Assert(S,G) on iif } See pim_mroute.c mroute_msg(). */ int pim_macro_chisin_oiflist(const struct pim_ifchannel *ch) { if (ch->upstream->join_state == PIM_UPSTREAM_NOTJOINED) { /* oiflist is NULL */ return 0; /* false */ } /* oiflist = oiflist (-) iif */ if (ch->interface == ch->upstream->rpf.source_nexthop.interface) return 0; /* false */ return pim_macro_chisin_inherited_olist(ch); } /* RFC 4601: 4.6.1. (S,G) Assert Message State Machine AssertTrackingDesired(S,G,I) = (I in ( ( joins(*,*,RP(G)) (+) joins(*,G) (-) prunes(S,G,rpt) ) (+) ( pim_include(*,G) (-) pim_exclude(S,G) ) (-) lost_assert(*,G) (+) joins(S,G) ) ) OR (local_receiver_include(S,G,I) == true AND (I_am_DR(I) OR (AssertWinner(S,G,I) == me))) OR ((RPF_interface(S) == I) AND (JoinDesired(S,G) == true)) OR ((RPF_interface(RP(G)) == I) AND (JoinDesired(*,G) == true) AND (SPTbit(S,G) == false)) AssertTrackingDesired(S,G,I) is true on any interface in which an (S,G) assert might affect our behavior. */ int pim_macro_assert_tracking_desired_eval(const struct pim_ifchannel *ch) { struct pim_interface *pim_ifp; struct interface *ifp; ifp = ch->interface; if (!ifp) { zlog_warn("%s: (S,G)=%s: null interface", __PRETTY_FUNCTION__, ch->sg_str); return 0; /* false */ } pim_ifp = ifp->info; if (!pim_ifp) { zlog_warn("%s: (S,G)=%s: multicast not enabled on interface %s", __PRETTY_FUNCTION__, ch->sg_str, ch->interface->name); return 0; /* false */ } /* I in joins(S,G) ? */ if (pim_macro_chisin_joins(ch)) return 1; /* true */ /* local_receiver_include(S,G,I) ? */ if (local_receiver_include(ch)) { /* I_am_DR(I) ? */ if (PIM_I_am_DR(pim_ifp)) return 1; /* true */ /* AssertWinner(S,G,I) == me ? */ if (ch->ifassert_winner.s_addr == pim_ifp->primary_address.s_addr) return 1; /* true */ } /* RPF_interface(S) == I ? */ if (ch->upstream->rpf.source_nexthop.interface == ifp) { /* JoinDesired(S,G) ? */ if (PIM_UPSTREAM_FLAG_TEST_DR_JOIN_DESIRED(ch->upstream->flags)) return 1; /* true */ } return 0; /* false */ } frr-7.2.1/pimd/pim_macro.h0000644000000000000000000000315213610377563012265 00000000000000/* * PIM for Quagga * Copyright (C) 2008 Everton da Silva Marques * * This program is free software; you can redistribute it and/or modify * it under the terms of the 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; see the file COPYING; if not, write to the Free Software * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA */ #ifndef PIM_MACRO_H #define PIM_MACRO_H #include #include "if.h" #include "pim_upstream.h" #include "pim_ifchannel.h" int pim_macro_ch_lost_assert(const struct pim_ifchannel *ch); int pim_macro_chisin_joins(const struct pim_ifchannel *ch); int pim_macro_chisin_pim_include(const struct pim_ifchannel *ch); int pim_macro_chisin_joins_or_include(const struct pim_ifchannel *ch); int pim_macro_ch_could_assert_eval(const struct pim_ifchannel *ch); struct pim_assert_metric pim_macro_spt_assert_metric(const struct pim_rpf *rpf, struct in_addr ifaddr); struct pim_assert_metric pim_macro_ch_my_assert_metric_eval(const struct pim_ifchannel *ch); int pim_macro_chisin_oiflist(const struct pim_ifchannel *ch); int pim_macro_assert_tracking_desired_eval(const struct pim_ifchannel *ch); #endif /* PIM_MACRO_H */ frr-7.2.1/pimd/pim_main.c0000644000000000000000000000721013610377563012102 00000000000000/* * PIM for Quagga * Copyright (C) 2008 Everton da Silva Marques * * This program is free software; you can redistribute it and/or modify * it under the terms of the 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; see the file COPYING; if not, write to the Free Software * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA */ #include #include "log.h" #include "privs.h" #include "version.h" #include #include "command.h" #include "thread.h" #include #include "memory.h" #include "vrf.h" #include "memory_vty.h" #include "filter.h" #include "vty.h" #include "sigevent.h" #include "version.h" #include "prefix.h" #include "plist.h" #include "vrf.h" #include "libfrr.h" #include "pimd.h" #include "pim_instance.h" #include "pim_version.h" #include "pim_signals.h" #include "pim_zebra.h" #include "pim_msdp.h" #include "pim_iface.h" #include "pim_bfd.h" #include "pim_errors.h" extern struct host host; struct option longopts[] = {{0}}; /* pimd privileges */ zebra_capabilities_t _caps_p[] = { ZCAP_NET_ADMIN, ZCAP_SYS_ADMIN, ZCAP_NET_RAW, ZCAP_BIND, }; /* pimd privileges to run with */ struct zebra_privs_t pimd_privs = { #if defined(FRR_USER) && defined(FRR_GROUP) .user = FRR_USER, .group = FRR_GROUP, #endif #ifdef VTY_GROUP .vty_group = VTY_GROUP, #endif .caps_p = _caps_p, .cap_num_p = array_size(_caps_p), .cap_num_i = 0}; static const struct frr_yang_module_info *pimd_yang_modules[] = { &frr_interface_info, }; FRR_DAEMON_INFO(pimd, PIM, .vty_port = PIMD_VTY_PORT, .proghelp = "Implementation of the PIM routing protocol.", .signals = pimd_signals, .n_signals = 4 /* XXX array_size(pimd_signals) XXX*/, .privs = &pimd_privs, .yang_modules = pimd_yang_modules, .n_yang_modules = array_size(pimd_yang_modules), ) int main(int argc, char **argv, char **envp) { frr_preinit(&pimd_di, argc, argv); frr_opt_add("", longopts, ""); /* this while just reads the options */ while (1) { int opt; opt = frr_getopt(argc, argv, NULL); if (opt == EOF) break; switch (opt) { case 0: break; default: frr_help_exit(1); break; } } pim_router_init(); /* * Initializations */ pim_error_init(); pim_vrf_init(); access_list_init(); prefix_list_init(); prefix_list_add_hook(pim_prefix_list_update); prefix_list_delete_hook(pim_prefix_list_update); pim_route_map_init(); pim_init(); /* * Initialize zclient "update" and "lookup" sockets */ pim_zebra_init(); pim_bfd_init(); frr_config_fork(); #ifdef PIM_DEBUG_BYDEFAULT zlog_notice("PIM_DEBUG_BYDEFAULT: Enabling all debug commands"); PIM_DO_DEBUG_PIM_EVENTS; PIM_DO_DEBUG_PIM_PACKETS; PIM_DO_DEBUG_PIM_TRACE; PIM_DO_DEBUG_IGMP_EVENTS; PIM_DO_DEBUG_IGMP_PACKETS; PIM_DO_DEBUG_IGMP_TRACE; PIM_DO_DEBUG_ZEBRA; #endif #ifdef PIM_CHECK_RECV_IFINDEX_SANITY zlog_notice( "PIM_CHECK_RECV_IFINDEX_SANITY: will match sock/recv ifindex"); #ifdef PIM_REPORT_RECV_IFINDEX_MISMATCH zlog_notice( "PIM_REPORT_RECV_IFINDEX_MISMATCH: will report sock/recv ifindex mismatch"); #endif #endif #ifdef PIM_UNEXPECTED_KERNEL_UPCALL zlog_notice( "PIM_UNEXPECTED_KERNEL_UPCALL: report unexpected kernel upcall"); #endif frr_run(router->master); /* never reached */ return 0; } frr-7.2.1/pimd/pim_memory.c0000644000000000000000000000474713610377563012502 00000000000000/* pimd memory type definitions * * Copyright (C) 2015 David Lamparter * * This file is part of Quagga. * * Quagga is free software; you can redistribute it and/or modify it * under the terms of the GNU General Public License as published by the * Free Software Foundation; either version 2, or (at your option) any * later version. * * Quagga 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; see the file COPYING; if not, write to the Free Software * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA */ #ifdef HAVE_CONFIG_H #include "config.h" #endif #include "pim_memory.h" DEFINE_MGROUP(PIMD, "pimd") DEFINE_MTYPE(PIMD, PIM_CHANNEL_OIL, "PIM SSM (S,G) channel OIL") DEFINE_MTYPE(PIMD, PIM_INTERFACE, "PIM interface") DEFINE_MTYPE(PIMD, PIM_IGMP_JOIN, "PIM interface IGMP static join") DEFINE_MTYPE(PIMD, PIM_IGMP_SOCKET, "PIM interface IGMP socket") DEFINE_MTYPE(PIMD, PIM_IGMP_GROUP, "PIM interface IGMP group") DEFINE_MTYPE(PIMD, PIM_IGMP_GROUP_SOURCE, "PIM interface IGMP source") DEFINE_MTYPE(PIMD, PIM_NEIGHBOR, "PIM interface neighbor") DEFINE_MTYPE(PIMD, PIM_IFCHANNEL, "PIM interface (S,G) state") DEFINE_MTYPE(PIMD, PIM_UPSTREAM, "PIM upstream (S,G) state") DEFINE_MTYPE(PIMD, PIM_SSMPINGD, "PIM sspimgd socket") DEFINE_MTYPE(PIMD, PIM_STATIC_ROUTE, "PIM Static Route") DEFINE_MTYPE(PIMD, PIM_BR, "PIM Bridge Router info") DEFINE_MTYPE(PIMD, PIM_RP, "PIM RP info") DEFINE_MTYPE(PIMD, PIM_FILTER_NAME, "PIM RP filter info") DEFINE_MTYPE(PIMD, PIM_MSDP_PEER, "PIM MSDP peer") DEFINE_MTYPE(PIMD, PIM_MSDP_MG_NAME, "PIM MSDP mesh-group name") DEFINE_MTYPE(PIMD, PIM_MSDP_SA, "PIM MSDP source-active cache") DEFINE_MTYPE(PIMD, PIM_MSDP_MG, "PIM MSDP mesh group") DEFINE_MTYPE(PIMD, PIM_MSDP_MG_MBR, "PIM MSDP mesh group mbr") DEFINE_MTYPE(PIMD, PIM_SEC_ADDR, "PIM secondary address") DEFINE_MTYPE(PIMD, PIM_JP_AGG_GROUP, "PIM JP AGG Group") DEFINE_MTYPE(PIMD, PIM_JP_AGG_SOURCE, "PIM JP AGG Source") DEFINE_MTYPE(PIMD, PIM_PIM_INSTANCE, "PIM global state") DEFINE_MTYPE(PIMD, PIM_NEXTHOP_CACHE, "PIM nexthop cache state") DEFINE_MTYPE(PIMD, PIM_SSM_INFO, "PIM SSM configuration") DEFINE_MTYPE(PIMD, PIM_SPT_PLIST_NAME, "PIM SPT Prefix List Name") DEFINE_MTYPE(PIMD, PIM_VXLAN_SG, "PIM VxLAN mroute cache") frr-7.2.1/pimd/pim_memory.h0000644000000000000000000000334313610377563012476 00000000000000/* pimd memory type declarations * * Copyright (C) 2015 David Lamparter * * This file is part of Quagga. * * Quagga is free software; you can redistribute it and/or modify it * under the terms of the GNU General Public License as published by the * Free Software Foundation; either version 2, or (at your option) any * later version. * * Quagga 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; see the file COPYING; if not, write to the Free Software * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA */ #ifndef _QUAGGA_PIM_MEMORY_H #define _QUAGGA_PIM_MEMORY_H #include "memory.h" DECLARE_MGROUP(PIMD) DECLARE_MTYPE(PIM_CHANNEL_OIL) DECLARE_MTYPE(PIM_INTERFACE) DECLARE_MTYPE(PIM_IGMP_JOIN) DECLARE_MTYPE(PIM_IGMP_SOCKET) DECLARE_MTYPE(PIM_IGMP_GROUP) DECLARE_MTYPE(PIM_IGMP_GROUP_SOURCE) DECLARE_MTYPE(PIM_NEIGHBOR) DECLARE_MTYPE(PIM_IFCHANNEL) DECLARE_MTYPE(PIM_UPSTREAM) DECLARE_MTYPE(PIM_SSMPINGD) DECLARE_MTYPE(PIM_STATIC_ROUTE) DECLARE_MTYPE(PIM_BR) DECLARE_MTYPE(PIM_RP) DECLARE_MTYPE(PIM_FILTER_NAME) DECLARE_MTYPE(PIM_MSDP_PEER) DECLARE_MTYPE(PIM_MSDP_MG_NAME) DECLARE_MTYPE(PIM_MSDP_SA) DECLARE_MTYPE(PIM_MSDP_MG) DECLARE_MTYPE(PIM_MSDP_MG_MBR) DECLARE_MTYPE(PIM_SEC_ADDR) DECLARE_MTYPE(PIM_JP_AGG_GROUP) DECLARE_MTYPE(PIM_JP_AGG_SOURCE) DECLARE_MTYPE(PIM_PIM_INSTANCE) DECLARE_MTYPE(PIM_NEXTHOP_CACHE) DECLARE_MTYPE(PIM_SSM_INFO) DECLARE_MTYPE(PIM_SPT_PLIST_NAME); DECLARE_MTYPE(PIM_VXLAN_SG) #endif /* _QUAGGA_PIM_MEMORY_H */ frr-7.2.1/pimd/pim_mroute.c0000644000000000000000000007122313610377563012476 00000000000000/* * PIM for Quagga * Copyright (C) 2008 Everton da Silva Marques * * This program is free software; you can redistribute it and/or modify * it under the terms of the 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; see the file COPYING; if not, write to the Free Software * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA */ #include #include "log.h" #include "privs.h" #include "if.h" #include "prefix.h" #include "vty.h" #include "plist.h" #include "sockopt.h" #include "lib_errors.h" #include "pimd.h" #include "pim_rpf.h" #include "pim_mroute.h" #include "pim_oil.h" #include "pim_str.h" #include "pim_time.h" #include "pim_iface.h" #include "pim_macro.h" #include "pim_rp.h" #include "pim_oil.h" #include "pim_register.h" #include "pim_ifchannel.h" #include "pim_zlookup.h" #include "pim_ssm.h" #include "pim_sock.h" #include "pim_vxlan.h" static void mroute_read_on(struct pim_instance *pim); static int pim_mroute_set(struct pim_instance *pim, int enable) { int err; int opt, data; socklen_t data_len = sizeof(data); long flags; /* * We need to create the VRF table for the pim mroute_socket */ if (pim->vrf_id != VRF_DEFAULT) { frr_with_privs(&pimd_privs) { data = pim->vrf->data.l.table_id; err = setsockopt(pim->mroute_socket, IPPROTO_IP, MRT_TABLE, &data, data_len); if (err) { zlog_warn( "%s %s: failure: setsockopt(fd=%d,IPPROTO_IP, MRT_TABLE=%d): errno=%d: %s", __FILE__, __PRETTY_FUNCTION__, pim->mroute_socket, data, errno, safe_strerror(errno)); return -1; } } } frr_with_privs(&pimd_privs) { opt = enable ? MRT_INIT : MRT_DONE; /* * *BSD *cares* about what value we pass down * here */ data = 1; err = setsockopt(pim->mroute_socket, IPPROTO_IP, opt, &data, data_len); if (err) { zlog_warn( "%s %s: failure: setsockopt(fd=%d,IPPROTO_IP,%s=%d): errno=%d: %s", __FILE__, __PRETTY_FUNCTION__, pim->mroute_socket, enable ? "MRT_INIT" : "MRT_DONE", data, errno, safe_strerror(errno)); return -1; } } #if defined(HAVE_IP_PKTINFO) if (enable) { /* Linux and Solaris IP_PKTINFO */ data = 1; if (setsockopt(pim->mroute_socket, IPPROTO_IP, IP_PKTINFO, &data, data_len)) { zlog_warn( "Could not set IP_PKTINFO on socket fd=%d: errno=%d: %s", pim->mroute_socket, errno, safe_strerror(errno)); } } #endif setsockopt_so_recvbuf(pim->mroute_socket, 1024 * 1024 * 8); flags = fcntl(pim->mroute_socket, F_GETFL, 0); if (flags < 0) { zlog_warn("Could not get flags on socket fd:%d %d %s", pim->mroute_socket, errno, safe_strerror(errno)); close(pim->mroute_socket); return -1; } if (fcntl(pim->mroute_socket, F_SETFL, flags | O_NONBLOCK)) { zlog_warn("Could not set O_NONBLOCK on socket fd:%d %d %s", pim->mroute_socket, errno, safe_strerror(errno)); close(pim->mroute_socket); return -1; } if (enable) { #if defined linux int upcalls = IGMPMSG_WRVIFWHOLE; opt = MRT_PIM; err = setsockopt(pim->mroute_socket, IPPROTO_IP, opt, &upcalls, sizeof(upcalls)); if (err) { zlog_warn( "Failure to register for VIFWHOLE and WRONGVIF upcalls %d %s", errno, safe_strerror(errno)); return -1; } #else zlog_warn( "PIM-SM will not work properly on this platform, until the ability to receive the WRVIFWHOLE upcall"); #endif } return 0; } static const char *igmpmsgtype2str[IGMPMSG_WRVIFWHOLE + 1] = { "", "NOCACHE", "WRONGVIF", "WHOLEPKT", "WRVIFWHOLE"}; static int pim_mroute_msg_nocache(int fd, struct interface *ifp, const struct igmpmsg *msg) { struct pim_interface *pim_ifp = ifp->info; struct pim_upstream *up; struct pim_rpf *rpg; struct prefix_sg sg; rpg = pim_ifp ? RP(pim_ifp->pim, msg->im_dst) : NULL; /* * If the incoming interface is unknown OR * the Interface type is SSM we don't need to * do anything here */ if (!rpg || pim_rpf_addr_is_inaddr_none(rpg)) { if (PIM_DEBUG_MROUTE_DETAIL) zlog_debug( "%s: Interface is not configured correctly to handle incoming packet: Could be !pim_ifp, !SM, !RP", __PRETTY_FUNCTION__); return 0; } /* * If we've received a multicast packet that isn't connected to * us */ if (!pim_if_connected_to_source(ifp, msg->im_src)) { if (PIM_DEBUG_MROUTE_DETAIL) zlog_debug( "%s: Received incoming packet that doesn't originate on our seg", __PRETTY_FUNCTION__); return 0; } memset(&sg, 0, sizeof(struct prefix_sg)); sg.src = msg->im_src; sg.grp = msg->im_dst; if (!(PIM_I_am_DR(pim_ifp))) { if (PIM_DEBUG_MROUTE_DETAIL) zlog_debug("%s: Interface is not the DR blackholing incoming traffic for %s", __PRETTY_FUNCTION__, pim_str_sg_dump(&sg)); /* * We are not the DR, but we are still receiving packets * Let's blackhole those packets for the moment * As that they will be coming up to the cpu * and causing us to consider them. * * This *will* create a dangling channel_oil * that I see no way to get rid of. Just noting * this for future reference. */ up = pim_upstream_find_or_add( &sg, ifp, PIM_UPSTREAM_FLAG_MASK_SRC_NOCACHE, __PRETTY_FUNCTION__); pim_mroute_add(up->channel_oil, __PRETTY_FUNCTION__); return 0; } up = pim_upstream_find_or_add(&sg, ifp, PIM_UPSTREAM_FLAG_MASK_FHR, __PRETTY_FUNCTION__); /* * I moved this debug till after the actual add because * I want to take advantage of the up->sg_str being filled in. */ if (PIM_DEBUG_MROUTE) { zlog_debug("%s: Adding a Route %s for WHOLEPKT consumption", __PRETTY_FUNCTION__, up->sg_str); } PIM_UPSTREAM_FLAG_SET_SRC_STREAM(up->flags); pim_upstream_keep_alive_timer_start(up, pim_ifp->pim->keep_alive_time); up->channel_oil->cc.pktcnt++; PIM_UPSTREAM_FLAG_SET_FHR(up->flags); // resolve mfcc_parent prior to mroute_add in channel_add_oif if (up->rpf.source_nexthop.interface && up->channel_oil->oil.mfcc_parent >= MAXVIFS) { int vif_index = 0; vif_index = pim_if_find_vifindex_by_ifindex( pim_ifp->pim, up->rpf.source_nexthop.interface->ifindex); pim_channel_oil_change_iif(pim_ifp->pim, up->channel_oil, vif_index, __PRETTY_FUNCTION__); } pim_register_join(up); return 0; } static int pim_mroute_msg_wholepkt(int fd, struct interface *ifp, const char *buf) { struct pim_interface *pim_ifp; struct prefix_sg sg; struct pim_rpf *rpg; const struct ip *ip_hdr; struct pim_upstream *up; pim_ifp = ifp->info; ip_hdr = (const struct ip *)buf; memset(&sg, 0, sizeof(struct prefix_sg)); sg.src = ip_hdr->ip_src; sg.grp = ip_hdr->ip_dst; up = pim_upstream_find(pim_ifp->pim, &sg); if (!up) { struct prefix_sg star = sg; star.src.s_addr = INADDR_ANY; up = pim_upstream_find(pim_ifp->pim, &star); if (up && PIM_UPSTREAM_FLAG_TEST_SRC_IGMP(up->flags)) { up = pim_upstream_add(pim_ifp->pim, &sg, ifp, PIM_UPSTREAM_FLAG_MASK_SRC_LHR, __PRETTY_FUNCTION__, NULL); if (!up) { if (PIM_DEBUG_MROUTE) zlog_debug( "%s: Unable to create upstream information for %s", __PRETTY_FUNCTION__, pim_str_sg_dump(&sg)); return 0; } pim_upstream_keep_alive_timer_start( up, pim_ifp->pim->keep_alive_time); pim_upstream_inherited_olist(pim_ifp->pim, up); pim_upstream_switch(pim_ifp->pim, up, PIM_UPSTREAM_JOINED); if (PIM_DEBUG_MROUTE) zlog_debug("%s: Creating %s upstream on LHR", __PRETTY_FUNCTION__, up->sg_str); return 0; } if (PIM_DEBUG_MROUTE_DETAIL) { zlog_debug( "%s: Unable to find upstream channel WHOLEPKT%s", __PRETTY_FUNCTION__, pim_str_sg_dump(&sg)); } return 0; } if (!up->rpf.source_nexthop.interface) { if (PIM_DEBUG_TRACE) zlog_debug("%s: up %s RPF is not present", __PRETTY_FUNCTION__, up->sg_str); return 0; } pim_ifp = up->rpf.source_nexthop.interface->info; rpg = pim_ifp ? RP(pim_ifp->pim, sg.grp) : NULL; if ((pim_rpf_addr_is_inaddr_none(rpg)) || (!pim_ifp) || (!(PIM_I_am_DR(pim_ifp)))) { if (PIM_DEBUG_MROUTE) { zlog_debug("%s: Failed Check send packet", __PRETTY_FUNCTION__); } return 0; } /* * If we've received a register suppress */ if (!up->t_rs_timer) { if (pim_is_grp_ssm(pim_ifp->pim, sg.grp)) { if (PIM_DEBUG_PIM_REG) zlog_debug( "%s register forward skipped as group is SSM", pim_str_sg_dump(&sg)); return 0; } pim_register_send((uint8_t *)buf + sizeof(struct ip), ntohs(ip_hdr->ip_len) - sizeof(struct ip), pim_ifp->primary_address, rpg, 0, up); } return 0; } static int pim_mroute_msg_wrongvif(int fd, struct interface *ifp, const struct igmpmsg *msg) { struct pim_ifchannel *ch; struct pim_interface *pim_ifp; struct prefix_sg sg; memset(&sg, 0, sizeof(struct prefix_sg)); sg.src = msg->im_src; sg.grp = msg->im_dst; /* Send Assert(S,G) on iif as response to WRONGVIF kernel upcall. RFC 4601 4.8.2. PIM-SSM-Only Routers iif is the incoming interface of the packet. if (iif is in inherited_olist(S,G)) { send Assert(S,G) on iif } */ if (!ifp) { if (PIM_DEBUG_MROUTE) zlog_debug( "%s: WRONGVIF (S,G)=%s could not find input interface for input_vif_index=%d", __PRETTY_FUNCTION__, pim_str_sg_dump(&sg), msg->im_vif); return -1; } pim_ifp = ifp->info; if (!pim_ifp) { if (PIM_DEBUG_MROUTE) zlog_debug( "%s: WRONGVIF (S,G)=%s multicast not enabled on interface %s", __PRETTY_FUNCTION__, pim_str_sg_dump(&sg), ifp->name); return -2; } ch = pim_ifchannel_find(ifp, &sg); if (!ch) { struct prefix_sg star_g = sg; if (PIM_DEBUG_MROUTE) zlog_debug( "%s: WRONGVIF (S,G)=%s could not find channel on interface %s", __PRETTY_FUNCTION__, pim_str_sg_dump(&sg), ifp->name); star_g.src.s_addr = INADDR_ANY; ch = pim_ifchannel_find(ifp, &star_g); if (!ch) { if (PIM_DEBUG_MROUTE) zlog_debug( "%s: WRONGVIF (*,G)=%s could not find channel on interface %s", __PRETTY_FUNCTION__, pim_str_sg_dump(&star_g), ifp->name); return -3; } } /* RFC 4601: 4.6.1. (S,G) Assert Message State Machine Transitions from NoInfo State An (S,G) data packet arrives on interface I, AND CouldAssert(S,G,I)==TRUE An (S,G) data packet arrived on an downstream interface that is in our (S,G) outgoing interface list. We optimistically assume that we will be the assert winner for this (S,G), and so we transition to the "I am Assert Winner" state and perform Actions A1 (below), which will initiate the assert negotiation for (S,G). */ if (ch->ifassert_state != PIM_IFASSERT_NOINFO) { if (PIM_DEBUG_MROUTE) { zlog_debug( "%s: WRONGVIF (S,G)=%s channel is not on Assert NoInfo state for interface %s", __PRETTY_FUNCTION__, ch->sg_str, ifp->name); } return -4; } if (!PIM_IF_FLAG_TEST_COULD_ASSERT(ch->flags)) { if (PIM_DEBUG_MROUTE) { zlog_debug( "%s: WRONGVIF (S,G)=%s interface %s is not downstream for channel", __PRETTY_FUNCTION__, ch->sg_str, ifp->name); } return -5; } if (assert_action_a1(ch)) { if (PIM_DEBUG_MROUTE) { zlog_debug( "%s: WRONGVIF (S,G)=%s assert_action_a1 failure on interface %s", __PRETTY_FUNCTION__, ch->sg_str, ifp->name); } return -6; } return 0; } static int pim_mroute_msg_wrvifwhole(int fd, struct interface *ifp, const char *buf) { const struct ip *ip_hdr = (const struct ip *)buf; struct pim_interface *pim_ifp; struct pim_ifchannel *ch; struct pim_upstream *up; struct prefix_sg star_g; struct prefix_sg sg; pim_ifp = ifp->info; memset(&sg, 0, sizeof(struct prefix_sg)); sg.src = ip_hdr->ip_src; sg.grp = ip_hdr->ip_dst; ch = pim_ifchannel_find(ifp, &sg); if (ch) { if (PIM_DEBUG_MROUTE) zlog_debug( "WRVIFWHOLE (S,G)=%s found ifchannel on interface %s", ch->sg_str, ifp->name); return -1; } star_g = sg; star_g.src.s_addr = INADDR_ANY; #if 0 ch = pim_ifchannel_find(ifp, &star_g); if (ch) { if (PIM_DEBUG_MROUTE) zlog_debug ("WRVIFWHOLE (*,G)=%s found ifchannel on interface %s", pim_str_sg_dump (&star_g), ifp->name); return -1; } #endif up = pim_upstream_find(pim_ifp->pim, &sg); if (up) { struct pim_upstream *parent; struct pim_nexthop source; struct pim_rpf *rpf = RP(pim_ifp->pim, sg.grp); if (!rpf || !rpf->source_nexthop.interface) return 0; /* * If we have received a WRVIFWHOLE and are at this * point, we could be receiving the packet on the *,G * tree, let's check and if so we can safely drop * it. */ parent = pim_upstream_find(pim_ifp->pim, &star_g); if (parent && parent->rpf.source_nexthop.interface == ifp) return 0; pim_ifp = rpf->source_nexthop.interface->info; memset(&source, 0, sizeof(source)); /* * If we are the fhr that means we are getting a callback during * the pimreg period, so I believe we can ignore this packet */ if (!PIM_UPSTREAM_FLAG_TEST_FHR(up->flags)) { // No if channel, but upstream we are at the RP. if (pim_nexthop_lookup(pim_ifp->pim, &source, up->upstream_register, 0)) { pim_register_stop_send(source.interface, &sg, pim_ifp->primary_address, up->upstream_register); up->sptbit = PIM_UPSTREAM_SPTBIT_TRUE; } pim_upstream_inherited_olist(pim_ifp->pim, up); if (!up->channel_oil->installed) pim_mroute_add(up->channel_oil, __PRETTY_FUNCTION__); } else { if (I_am_RP(pim_ifp->pim, up->sg.grp)) { if (pim_nexthop_lookup(pim_ifp->pim, &source, up->upstream_register, 0)) pim_register_stop_send( source.interface, &sg, pim_ifp->primary_address, up->upstream_register); up->sptbit = PIM_UPSTREAM_SPTBIT_TRUE; } pim_upstream_keep_alive_timer_start( up, pim_ifp->pim->keep_alive_time); pim_upstream_inherited_olist(pim_ifp->pim, up); pim_mroute_msg_wholepkt(fd, ifp, buf); } return 0; } pim_ifp = ifp->info; if (pim_if_connected_to_source(ifp, sg.src)) { up = pim_upstream_add(pim_ifp->pim, &sg, ifp, PIM_UPSTREAM_FLAG_MASK_FHR, __PRETTY_FUNCTION__, NULL); if (!up) { if (PIM_DEBUG_MROUTE) zlog_debug( "%s: WRONGVIF%s unable to create upstream on interface", pim_str_sg_dump(&sg), ifp->name); return -2; } PIM_UPSTREAM_FLAG_SET_SRC_STREAM(up->flags); pim_upstream_keep_alive_timer_start( up, pim_ifp->pim->keep_alive_time); up->channel_oil->cc.pktcnt++; pim_register_join(up); pim_upstream_inherited_olist(pim_ifp->pim, up); // Send the packet to the RP pim_mroute_msg_wholepkt(fd, ifp, buf); } else { up = pim_upstream_add(pim_ifp->pim, &sg, ifp, PIM_UPSTREAM_FLAG_MASK_SRC_NOCACHE, __PRETTY_FUNCTION__, NULL); if (!up->channel_oil->installed) pim_mroute_add(up->channel_oil, __PRETTY_FUNCTION__); } return 0; } static int pim_mroute_msg(struct pim_instance *pim, const char *buf, int buf_size, ifindex_t ifindex) { struct interface *ifp; struct pim_interface *pim_ifp; const struct ip *ip_hdr; const struct igmpmsg *msg; char ip_src_str[INET_ADDRSTRLEN] = ""; char ip_dst_str[INET_ADDRSTRLEN] = ""; char src_str[INET_ADDRSTRLEN] = ""; char grp_str[INET_ADDRSTRLEN] = ""; struct in_addr ifaddr; struct igmp_sock *igmp; if (buf_size < (int)sizeof(struct ip)) return 0; ip_hdr = (const struct ip *)buf; if (ip_hdr->ip_p == IPPROTO_IGMP) { /* We have the IP packet but we do not know which interface this * packet was * received on. Find the interface that is on the same subnet as * the source * of the IP packet. */ ifp = if_lookup_by_index(ifindex, pim->vrf_id); if (!ifp || !ifp->info) return 0; pim_ifp = ifp->info; ifaddr = pim_find_primary_addr(ifp); igmp = pim_igmp_sock_lookup_ifaddr(pim_ifp->igmp_socket_list, ifaddr); if (PIM_DEBUG_MROUTE) { pim_inet4_dump("", ip_hdr->ip_src, ip_src_str, sizeof(ip_src_str)); pim_inet4_dump("", ip_hdr->ip_dst, ip_dst_str, sizeof(ip_dst_str)); zlog_warn( "%s(%s): igmp kernel upcall on %s(%p) for %s -> %s", __PRETTY_FUNCTION__, pim->vrf->name, ifp->name, igmp, ip_src_str, ip_dst_str); } if (igmp) pim_igmp_packet(igmp, (char *)buf, buf_size); } else if (ip_hdr->ip_p) { if (PIM_DEBUG_MROUTE_DETAIL) { pim_inet4_dump("", ip_hdr->ip_src, src_str, sizeof(src_str)); pim_inet4_dump("", ip_hdr->ip_dst, grp_str, sizeof(grp_str)); zlog_debug( "%s: no kernel upcall proto=%d src: %s dst: %s msg_size=%d", __PRETTY_FUNCTION__, ip_hdr->ip_p, src_str, grp_str, buf_size); } } else { msg = (const struct igmpmsg *)buf; ifp = pim_if_find_by_vif_index(pim, msg->im_vif); if (!ifp) return 0; if (PIM_DEBUG_MROUTE) { pim_inet4_dump("", msg->im_src, src_str, sizeof(src_str)); pim_inet4_dump("", msg->im_dst, grp_str, sizeof(grp_str)); zlog_warn( "%s: pim kernel upcall %s type=%d ip_p=%d from fd=%d for (S,G)=(%s,%s) on %s vifi=%d size=%d", __PRETTY_FUNCTION__, igmpmsgtype2str[msg->im_msgtype], msg->im_msgtype, ip_hdr->ip_p, pim->mroute_socket, src_str, grp_str, ifp->name, msg->im_vif, buf_size); } switch (msg->im_msgtype) { case IGMPMSG_WRONGVIF: return pim_mroute_msg_wrongvif(pim->mroute_socket, ifp, msg); break; case IGMPMSG_NOCACHE: return pim_mroute_msg_nocache(pim->mroute_socket, ifp, msg); break; case IGMPMSG_WHOLEPKT: return pim_mroute_msg_wholepkt(pim->mroute_socket, ifp, (const char *)msg); break; case IGMPMSG_WRVIFWHOLE: return pim_mroute_msg_wrvifwhole( pim->mroute_socket, ifp, (const char *)msg); break; default: break; } } return 0; } static int mroute_read(struct thread *t) { struct pim_instance *pim; static long long count; char buf[10000]; int result = 0; int cont = 1; int rd; ifindex_t ifindex; pim = THREAD_ARG(t); while (cont) { rd = pim_socket_recvfromto(pim->mroute_socket, (uint8_t *)buf, sizeof(buf), NULL, NULL, NULL, NULL, &ifindex); if (rd <= 0) { if (errno == EINTR) continue; if (errno == EWOULDBLOCK || errno == EAGAIN) break; if (PIM_DEBUG_MROUTE) zlog_warn( "%s: failure reading rd=%d: fd=%d: errno=%d: %s", __PRETTY_FUNCTION__, rd, pim->mroute_socket, errno, safe_strerror(errno)); goto done; } result = pim_mroute_msg(pim, buf, rd, ifindex); count++; if (count % router->packet_process == 0) cont = 0; } /* Keep reading */ done: mroute_read_on(pim); return result; } static void mroute_read_on(struct pim_instance *pim) { thread_add_read(router->master, mroute_read, pim, pim->mroute_socket, &pim->thread); } static void mroute_read_off(struct pim_instance *pim) { THREAD_OFF(pim->thread); } int pim_mroute_socket_enable(struct pim_instance *pim) { int fd; frr_with_privs(&pimd_privs) { fd = socket(AF_INET, SOCK_RAW, IPPROTO_IGMP); if (fd < 0) { zlog_warn("Could not create mroute socket: errno=%d: %s", errno, safe_strerror(errno)); return -2; } #ifdef SO_BINDTODEVICE if (pim->vrf->vrf_id != VRF_DEFAULT && setsockopt(fd, SOL_SOCKET, SO_BINDTODEVICE, pim->vrf->name, strlen(pim->vrf->name))) { zlog_warn("Could not setsockopt SO_BINDTODEVICE: %s", safe_strerror(errno)); close(fd); return -3; } #endif } pim->mroute_socket = fd; if (pim_mroute_set(pim, 1)) { zlog_warn( "Could not enable mroute on socket fd=%d: errno=%d: %s", fd, errno, safe_strerror(errno)); close(fd); pim->mroute_socket = -1; return -3; } pim->mroute_socket_creation = pim_time_monotonic_sec(); mroute_read_on(pim); return 0; } int pim_mroute_socket_disable(struct pim_instance *pim) { if (pim_mroute_set(pim, 0)) { zlog_warn( "Could not disable mroute on socket fd=%d: errno=%d: %s", pim->mroute_socket, errno, safe_strerror(errno)); return -2; } if (close(pim->mroute_socket)) { zlog_warn("Failure closing mroute socket: fd=%d errno=%d: %s", pim->mroute_socket, errno, safe_strerror(errno)); return -3; } mroute_read_off(pim); pim->mroute_socket = -1; return 0; } /* For each network interface (e.g., physical or a virtual tunnel) that would be used for multicast forwarding, a corresponding multicast interface must be added to the kernel. */ int pim_mroute_add_vif(struct interface *ifp, struct in_addr ifaddr, unsigned char flags) { struct pim_interface *pim_ifp = ifp->info; struct vifctl vc; int err; if (PIM_DEBUG_MROUTE) zlog_debug("%s: Add Vif %d (%s[%s])", __PRETTY_FUNCTION__, pim_ifp->mroute_vif_index, ifp->name, pim_ifp->pim->vrf->name); memset(&vc, 0, sizeof(vc)); vc.vifc_vifi = pim_ifp->mroute_vif_index; #ifdef VIFF_USE_IFINDEX vc.vifc_lcl_ifindex = ifp->ifindex; #else if (ifaddr.s_addr == INADDR_ANY) { zlog_warn( "%s: unnumbered interfaces are not supported on this platform", __PRETTY_FUNCTION__); return -1; } memcpy(&vc.vifc_lcl_addr, &ifaddr, sizeof(vc.vifc_lcl_addr)); #endif vc.vifc_flags = flags; vc.vifc_threshold = PIM_MROUTE_MIN_TTL; vc.vifc_rate_limit = 0; #ifdef PIM_DVMRP_TUNNEL if (vc.vifc_flags & VIFF_TUNNEL) { memcpy(&vc.vifc_rmt_addr, &vif_remote_addr, sizeof(vc.vifc_rmt_addr)); } #endif err = setsockopt(pim_ifp->pim->mroute_socket, IPPROTO_IP, MRT_ADD_VIF, (void *)&vc, sizeof(vc)); if (err) { char ifaddr_str[INET_ADDRSTRLEN]; pim_inet4_dump("", ifaddr, ifaddr_str, sizeof(ifaddr_str)); zlog_warn( "%s: failure: setsockopt(fd=%d,IPPROTO_IP,MRT_ADD_VIF,vif_index=%d,ifaddr=%s,flag=%d): errno=%d: %s", __PRETTY_FUNCTION__, pim_ifp->pim->mroute_socket, ifp->ifindex, ifaddr_str, flags, errno, safe_strerror(errno)); return -2; } return 0; } int pim_mroute_del_vif(struct interface *ifp) { struct pim_interface *pim_ifp = ifp->info; struct vifctl vc; int err; if (PIM_DEBUG_MROUTE) zlog_debug("%s: Del Vif %d (%s[%s])", __PRETTY_FUNCTION__, pim_ifp->mroute_vif_index, ifp->name, pim_ifp->pim->vrf->name); memset(&vc, 0, sizeof(vc)); vc.vifc_vifi = pim_ifp->mroute_vif_index; err = setsockopt(pim_ifp->pim->mroute_socket, IPPROTO_IP, MRT_DEL_VIF, (void *)&vc, sizeof(vc)); if (err) { zlog_warn( "%s %s: failure: setsockopt(fd=%d,IPPROTO_IP,MRT_DEL_VIF,vif_index=%d): errno=%d: %s", __FILE__, __PRETTY_FUNCTION__, pim_ifp->pim->mroute_socket, pim_ifp->mroute_vif_index, errno, safe_strerror(errno)); return -2; } return 0; } int pim_mroute_add(struct channel_oil *c_oil, const char *name) { struct pim_instance *pim = c_oil->pim; int err; int orig = 0; int orig_iif_vif = 0; struct pim_interface *pim_reg_ifp = NULL; int orig_pimreg_ttl = 0; bool pimreg_ttl_reset = false; struct pim_interface *vxlan_ifp = NULL; int orig_term_ttl = 0; bool orig_term_ttl_reset = false; pim->mroute_add_last = pim_time_monotonic_sec(); ++pim->mroute_add_events; /* Do not install route if incoming interface is undefined. */ if (c_oil->oil.mfcc_parent >= MAXVIFS) { if (PIM_DEBUG_MROUTE) { char buf[1000]; zlog_debug( "%s(%s) %s Attempting to add vifi that is invalid to mroute table", __PRETTY_FUNCTION__, name, pim_channel_oil_dump(c_oil, buf, sizeof(buf))); } return -2; } /* The linux kernel *expects* the incoming * vif to be part of the outgoing list * in the case of a (*,G). */ if (c_oil->oil.mfcc_origin.s_addr == INADDR_ANY) { orig = c_oil->oil.mfcc_ttls[c_oil->oil.mfcc_parent]; c_oil->oil.mfcc_ttls[c_oil->oil.mfcc_parent] = 1; } if (c_oil->up) { /* suppress pimreg in the OIL if the mroute is not supposed to * trigger register encapsulated data */ if (PIM_UPSTREAM_FLAG_TEST_NO_PIMREG_DATA(c_oil->up->flags)) { pim_reg_ifp = pim->regiface->info; orig_pimreg_ttl = c_oil->oil.mfcc_ttls[pim_reg_ifp->mroute_vif_index]; c_oil->oil.mfcc_ttls[pim_reg_ifp->mroute_vif_index] = 0; /* remember to flip it back after MFC programming */ pimreg_ttl_reset = true; } vxlan_ifp = pim_vxlan_get_term_ifp(pim); /* 1. vxlan termination device must never be added to the * origination mroute (and that can actually happen because * of XG inheritance from the termination mroute) otherwise * traffic will end up looping. * 2. vxlan termination device should be removed from the non-DF * to prevent duplicates to the overlay rxer */ if (vxlan_ifp && (PIM_UPSTREAM_FLAG_TEST_SRC_VXLAN_ORIG(c_oil->up->flags) || PIM_UPSTREAM_FLAG_TEST_MLAG_NON_DF(c_oil->up->flags))) { orig_term_ttl_reset = true; orig_term_ttl = c_oil->oil.mfcc_ttls[vxlan_ifp->mroute_vif_index]; c_oil->oil.mfcc_ttls[vxlan_ifp->mroute_vif_index] = 0; } } /* * If we have an unresolved cache entry for the S,G * it is owned by the pimreg for the incoming IIF * So set pimreg as the IIF temporarily to cause * the packets to be forwarded. Then set it * to the correct IIF afterwords. */ if (!c_oil->installed && c_oil->oil.mfcc_origin.s_addr != INADDR_ANY && c_oil->oil.mfcc_parent != 0) { orig_iif_vif = c_oil->oil.mfcc_parent; c_oil->oil.mfcc_parent = 0; } err = setsockopt(pim->mroute_socket, IPPROTO_IP, MRT_ADD_MFC, &c_oil->oil, sizeof(c_oil->oil)); if (!err && !c_oil->installed && c_oil->oil.mfcc_origin.s_addr != INADDR_ANY && orig_iif_vif != 0) { c_oil->oil.mfcc_parent = orig_iif_vif; err = setsockopt(pim->mroute_socket, IPPROTO_IP, MRT_ADD_MFC, &c_oil->oil, sizeof(c_oil->oil)); } if (c_oil->oil.mfcc_origin.s_addr == INADDR_ANY) c_oil->oil.mfcc_ttls[c_oil->oil.mfcc_parent] = orig; if (pimreg_ttl_reset) { assert(pim_reg_ifp); c_oil->oil.mfcc_ttls[pim_reg_ifp->mroute_vif_index] = orig_pimreg_ttl; } if (orig_term_ttl_reset) c_oil->oil.mfcc_ttls[vxlan_ifp->mroute_vif_index] = orig_term_ttl; if (err) { zlog_warn( "%s %s: failure: setsockopt(fd=%d,IPPROTO_IP,MRT_ADD_MFC): errno=%d: %s", __FILE__, __PRETTY_FUNCTION__, pim->mroute_socket, errno, safe_strerror(errno)); return -2; } if (PIM_DEBUG_MROUTE) { char buf[1000]; zlog_debug("%s(%s), vrf %s Added Route: %s", __PRETTY_FUNCTION__, name, pim->vrf->name, pim_channel_oil_dump(c_oil, buf, sizeof(buf))); } c_oil->installed = 1; c_oil->mroute_creation = pim_time_monotonic_sec(); return 0; } int pim_mroute_del(struct channel_oil *c_oil, const char *name) { struct pim_instance *pim = c_oil->pim; int err; pim->mroute_del_last = pim_time_monotonic_sec(); ++pim->mroute_del_events; if (!c_oil->installed) { if (PIM_DEBUG_MROUTE) { char buf[1000]; zlog_debug( "%s %s: vifi %d for route is %s not installed, do not need to send del req. ", __FILE__, __PRETTY_FUNCTION__, c_oil->oil.mfcc_parent, pim_channel_oil_dump(c_oil, buf, sizeof(buf))); } return -2; } err = setsockopt(pim->mroute_socket, IPPROTO_IP, MRT_DEL_MFC, &c_oil->oil, sizeof(c_oil->oil)); if (err) { if (PIM_DEBUG_MROUTE) zlog_warn( "%s %s: failure: setsockopt(fd=%d,IPPROTO_IP,MRT_DEL_MFC): errno=%d: %s", __FILE__, __PRETTY_FUNCTION__, pim->mroute_socket, errno, safe_strerror(errno)); return -2; } if (PIM_DEBUG_MROUTE) { char buf[1000]; zlog_debug("%s(%s), vrf %s Deleted Route: %s", __PRETTY_FUNCTION__, name, pim->vrf->name, pim_channel_oil_dump(c_oil, buf, sizeof(buf))); } // Reset kernel installed flag c_oil->installed = 0; return 0; } void pim_mroute_update_counters(struct channel_oil *c_oil) { struct pim_instance *pim = c_oil->pim; struct sioc_sg_req sgreq; c_oil->cc.oldpktcnt = c_oil->cc.pktcnt; c_oil->cc.oldbytecnt = c_oil->cc.bytecnt; c_oil->cc.oldwrong_if = c_oil->cc.wrong_if; if (!c_oil->installed) { c_oil->cc.lastused = 100 * pim->keep_alive_time; if (PIM_DEBUG_MROUTE) { struct prefix_sg sg; sg.src = c_oil->oil.mfcc_origin; sg.grp = c_oil->oil.mfcc_mcastgrp; if (PIM_DEBUG_MROUTE) zlog_debug( "Channel%s is not installed no need to collect data from kernel", pim_str_sg_dump(&sg)); } return; } memset(&sgreq, 0, sizeof(sgreq)); sgreq.src = c_oil->oil.mfcc_origin; sgreq.grp = c_oil->oil.mfcc_mcastgrp; pim_zlookup_sg_statistics(c_oil); if (ioctl(pim->mroute_socket, SIOCGETSGCNT, &sgreq)) { if (PIM_DEBUG_MROUTE) { struct prefix_sg sg; sg.src = c_oil->oil.mfcc_origin; sg.grp = c_oil->oil.mfcc_mcastgrp; zlog_warn( "ioctl(SIOCGETSGCNT=%lu) failure for (S,G)=%s: errno=%d: %s", (unsigned long)SIOCGETSGCNT, pim_str_sg_dump(&sg), errno, safe_strerror(errno)); } return; } c_oil->cc.pktcnt = sgreq.pktcnt; c_oil->cc.bytecnt = sgreq.bytecnt; c_oil->cc.wrong_if = sgreq.wrong_if; return; } frr-7.2.1/pimd/pim_mroute.h0000644000000000000000000001227613610377563012506 00000000000000/* * PIM for Quagga * Copyright (C) 2008 Everton da Silva Marques * * This program is free software; you can redistribute it and/or modify * it under the terms of the 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; see the file COPYING; if not, write to the Free Software * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA */ #ifndef PIM_MROUTE_H #define PIM_MROUTE_H /* For msghdr.msg_control in Solaris 10 */ #ifndef _XPG4_2 #define _XPG4_2 #endif #ifndef __EXTENSIONS__ #define __EXTENSIONS__ #endif #include #ifdef HAVE_NETINET_IP_MROUTE_H #include #endif #define PIM_MROUTE_MIN_TTL (1) #if defined(HAVE_LINUX_MROUTE_H) #include #else /* Below: from */ #ifndef MAXVIFS #define MAXVIFS (256) #endif #ifndef SIOCGETVIFCNT #define SIOCGETVIFCNT SIOCPROTOPRIVATE /* IP protocol privates */ #define SIOCGETSGCNT (SIOCPROTOPRIVATE+1) #define SIOCGETRPF (SIOCPROTOPRIVATE+2) #endif #ifndef MRT_INIT #define MRT_BASE 200 #define MRT_INIT (MRT_BASE) /* Activate the kernel mroute code */ #define MRT_DONE (MRT_BASE+1) /* Shutdown the kernel mroute */ #define MRT_ADD_VIF (MRT_BASE+2) /* Add a virtual interface */ #define MRT_DEL_VIF (MRT_BASE+3) /* Delete a virtual interface */ #define MRT_ADD_MFC (MRT_BASE+4) /* Add a multicast forwarding entry */ #define MRT_DEL_MFC (MRT_BASE+5) /* Delete a multicast forwarding entry */ #define MRT_VERSION (MRT_BASE+6) /* Get the kernel multicast version */ #define MRT_ASSERT (MRT_BASE+7) /* Activate PIM assert mode */ #define MRT_PIM (MRT_BASE+8) /* enable PIM code */ #endif #ifndef MRT_TABLE #define MRT_TABLE (209) /* Specify mroute table ID */ #endif #ifndef HAVE_VIFI_T typedef unsigned short vifi_t; #endif #ifndef HAVE_STRUCT_VIFCTL struct vifctl { vifi_t vifc_vifi; /* Index of VIF */ unsigned char vifc_flags; /* VIFF_ flags */ unsigned char vifc_threshold; /* ttl limit */ unsigned int vifc_rate_limit; /* Rate limiter values (NI) */ struct in_addr vifc_lcl_addr; /* Our address */ struct in_addr vifc_rmt_addr; /* IPIP tunnel addr */ }; #endif #ifndef HAVE_STRUCT_MFCCTL struct mfcctl { struct in_addr mfcc_origin; /* Origin of mcast */ struct in_addr mfcc_mcastgrp; /* Group in question */ vifi_t mfcc_parent; /* Where it arrived */ unsigned char mfcc_ttls[MAXVIFS]; /* Where it is going */ unsigned int mfcc_pkt_cnt; /* pkt count for src-grp */ unsigned int mfcc_byte_cnt; unsigned int mfcc_wrong_if; int mfcc_expire; }; #endif /* * Group count retrieval for mrouted */ /* struct sioc_sg_req sgreq; memset(&sgreq, 0, sizeof(sgreq)); memcpy(&sgreq.src, &source_addr, sizeof(sgreq.src)); memcpy(&sgreq.grp, &group_addr, sizeof(sgreq.grp)); ioctl(mrouter_s4, SIOCGETSGCNT, &sgreq); */ #ifndef HAVE_STRUCT_SIOC_SG_REQ struct sioc_sg_req { struct in_addr src; struct in_addr grp; unsigned long pktcnt; unsigned long bytecnt; unsigned long wrong_if; }; #endif /* * To get vif packet counts */ /* struct sioc_vif_req vreq; memset(&vreq, 0, sizeof(vreq)); vreq.vifi = vif_index; ioctl(mrouter_s4, SIOCGETVIFCNT, &vreq); */ #ifndef HAVE_STRUCT_SIOC_VIF_REQ struct sioc_vif_req { vifi_t vifi; /* Which iface */ unsigned long icount; /* In packets */ unsigned long ocount; /* Out packets */ unsigned long ibytes; /* In bytes */ unsigned long obytes; /* Out bytes */ }; #endif /* * Pseudo messages used by mrouted */ #ifndef IGMPMSG_NOCACHE #define IGMPMSG_NOCACHE 1 /* Kern cache fill request to mrouted */ #define IGMPMSG_WRONGVIF 2 /* For PIM assert processing (unused) */ #define IGMPMSG_WHOLEPKT 3 /* For PIM Register processing */ #endif #ifndef HAVE_STRUCT_IGMPMSG struct igmpmsg { uint32_t unused1, unused2; unsigned char im_msgtype; /* What is this */ unsigned char im_mbz; /* Must be zero */ unsigned char im_vif; /* Interface (this ought to be a vifi_t!) */ unsigned char unused3; struct in_addr im_src, im_dst; }; #endif #endif #ifndef IGMPMSG_WRVIFWHOLE #define IGMPMSG_WRVIFWHOLE 4 /* For PIM processing */ #endif /* Above: from */ int pim_mroute_socket_enable(struct pim_instance *pim); int pim_mroute_socket_disable(struct pim_instance *pim); int pim_mroute_add_vif(struct interface *ifp, struct in_addr ifaddr, unsigned char flags); int pim_mroute_del_vif(struct interface *ifp); int pim_mroute_add(struct channel_oil *c_oil, const char *name); int pim_mroute_del(struct channel_oil *c_oil, const char *name); void pim_mroute_update_counters(struct channel_oil *c_oil); #endif /* PIM_MROUTE_H */ frr-7.2.1/pimd/pim_msdp.c0000644000000000000000000012273613610377563012134 00000000000000/* * IP MSDP for Quagga * Copyright (C) 2016 Cumulus Networks, Inc. * * This program is free software; you can redistribute it and/or modify * it under the terms of the 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; see the file COPYING; if not, write to the Free Software * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA */ #include #include #include #include #include #include #include #include #include #include #include #include "pimd.h" #include "pim_cmd.h" #include "pim_memory.h" #include "pim_iface.h" #include "pim_rp.h" #include "pim_str.h" #include "pim_time.h" #include "pim_upstream.h" #include "pim_oil.h" #include "pim_msdp.h" #include "pim_msdp_packet.h" #include "pim_msdp_socket.h" // struct pim_msdp pim_msdp, *msdp = &pim_msdp; static void pim_msdp_peer_listen(struct pim_msdp_peer *mp); static void pim_msdp_peer_cr_timer_setup(struct pim_msdp_peer *mp, bool start); static void pim_msdp_peer_ka_timer_setup(struct pim_msdp_peer *mp, bool start); static void pim_msdp_peer_hold_timer_setup(struct pim_msdp_peer *mp, bool start); static void pim_msdp_peer_free(struct pim_msdp_peer *mp); static void pim_msdp_enable(struct pim_instance *pim); static void pim_msdp_sa_adv_timer_setup(struct pim_instance *pim, bool start); static void pim_msdp_sa_deref(struct pim_msdp_sa *sa, enum pim_msdp_sa_flags flags); static int pim_msdp_mg_mbr_comp(const void *p1, const void *p2); static void pim_msdp_mg_mbr_free(struct pim_msdp_mg_mbr *mbr); static void pim_msdp_mg_mbr_do_del(struct pim_msdp_mg *mg, struct pim_msdp_mg_mbr *mbr); /************************ SA cache management ******************************/ static void pim_msdp_sa_timer_expiry_log(struct pim_msdp_sa *sa, const char *timer_str) { zlog_debug("MSDP SA %s %s timer expired", sa->sg_str, timer_str); } /* RFC-3618:Sec-5.1 - global active source advertisement timer */ static int pim_msdp_sa_adv_timer_cb(struct thread *t) { struct pim_instance *pim = THREAD_ARG(t); if (PIM_DEBUG_MSDP_EVENTS) { zlog_debug("MSDP SA advertisement timer expired"); } pim_msdp_sa_adv_timer_setup(pim, true /* start */); pim_msdp_pkt_sa_tx(pim); return 0; } static void pim_msdp_sa_adv_timer_setup(struct pim_instance *pim, bool start) { THREAD_OFF(pim->msdp.sa_adv_timer); if (start) { thread_add_timer(pim->msdp.master, pim_msdp_sa_adv_timer_cb, pim, PIM_MSDP_SA_ADVERTISMENT_TIME, &pim->msdp.sa_adv_timer); } } /* RFC-3618:Sec-5.3 - SA cache state timer */ static int pim_msdp_sa_state_timer_cb(struct thread *t) { struct pim_msdp_sa *sa; sa = THREAD_ARG(t); if (PIM_DEBUG_MSDP_EVENTS) { pim_msdp_sa_timer_expiry_log(sa, "state"); } pim_msdp_sa_deref(sa, PIM_MSDP_SAF_PEER); return 0; } static void pim_msdp_sa_state_timer_setup(struct pim_msdp_sa *sa, bool start) { THREAD_OFF(sa->sa_state_timer); if (start) { thread_add_timer(sa->pim->msdp.master, pim_msdp_sa_state_timer_cb, sa, PIM_MSDP_SA_HOLD_TIME, &sa->sa_state_timer); } } static void pim_msdp_sa_upstream_del(struct pim_msdp_sa *sa) { struct pim_upstream *up = sa->up; if (!up) { return; } sa->up = NULL; if (PIM_UPSTREAM_FLAG_TEST_SRC_MSDP(up->flags)) { PIM_UPSTREAM_FLAG_UNSET_SRC_MSDP(up->flags); sa->flags |= PIM_MSDP_SAF_UP_DEL_IN_PROG; pim_upstream_del(sa->pim, up, __PRETTY_FUNCTION__); sa->flags &= ~PIM_MSDP_SAF_UP_DEL_IN_PROG; } if (PIM_DEBUG_MSDP_EVENTS) { zlog_debug("MSDP SA %s de-referenced SPT", sa->sg_str); } } static bool pim_msdp_sa_upstream_add_ok(struct pim_msdp_sa *sa, struct pim_upstream *xg_up) { if (!(sa->flags & PIM_MSDP_SAF_PEER)) { /* SA should have been rxed from a peer */ return false; } /* check if we are RP */ if (!I_am_RP(sa->pim, sa->sg.grp)) { return false; } /* check if we have a (*, G) with a non-empty immediate OIL */ if (!xg_up) { struct prefix_sg sg; memset(&sg, 0, sizeof(sg)); sg.grp = sa->sg.grp; xg_up = pim_upstream_find(sa->pim, &sg); } if (!xg_up || (xg_up->join_state != PIM_UPSTREAM_JOINED)) { /* join desired will be true for such (*, G) entries so we will * just look at join_state and let the PIM state machine do the * rest of * the magic */ return false; } return true; } /* Upstream add evaluation needs to happen everytime - * 1. Peer reference is added or removed. * 2. The RP for a group changes. * 3. joinDesired for the associated (*, G) changes * 4. associated (*, G) is removed - this seems like a bit redundant * (considering #4); but just in case an entry gets nuked without * upstream state transition * */ static void pim_msdp_sa_upstream_update(struct pim_msdp_sa *sa, struct pim_upstream *xg_up, const char *ctx) { struct pim_upstream *up; if (!pim_msdp_sa_upstream_add_ok(sa, xg_up)) { pim_msdp_sa_upstream_del(sa); return; } if (sa->up) { /* nothing to do */ return; } up = pim_upstream_find(sa->pim, &sa->sg); if (up && (PIM_UPSTREAM_FLAG_TEST_SRC_MSDP(up->flags))) { /* somehow we lost track of the upstream ptr? best log it */ sa->up = up; if (PIM_DEBUG_MSDP_EVENTS) { zlog_debug("MSDP SA %s SPT reference missing", sa->sg_str); } return; } /* RFC3618: "RP triggers a (S, G) join event towards the data source * as if a JP message was rxed addressed to the RP itself." */ up = pim_upstream_add(sa->pim, &sa->sg, NULL /* iif */, PIM_UPSTREAM_FLAG_MASK_SRC_MSDP, __PRETTY_FUNCTION__, NULL); sa->up = up; if (up) { /* update inherited oil */ pim_upstream_inherited_olist(sa->pim, up); /* should we also start the kat in parallel? we will need it * when the * SA ages out */ if (PIM_DEBUG_MSDP_EVENTS) { zlog_debug("MSDP SA %s referenced SPT", sa->sg_str); } } else { if (PIM_DEBUG_MSDP_EVENTS) { zlog_debug("MSDP SA %s SPT reference failed", sa->sg_str); } } } /* release all mem associated with a sa */ static void pim_msdp_sa_free(struct pim_msdp_sa *sa) { pim_msdp_sa_state_timer_setup(sa, false); XFREE(MTYPE_PIM_MSDP_SA, sa); } static struct pim_msdp_sa *pim_msdp_sa_new(struct pim_instance *pim, struct prefix_sg *sg, struct in_addr rp) { struct pim_msdp_sa *sa; sa = XCALLOC(MTYPE_PIM_MSDP_SA, sizeof(*sa)); sa->pim = pim; sa->sg = *sg; pim_str_sg_set(sg, sa->sg_str); sa->rp = rp; sa->uptime = pim_time_monotonic_sec(); /* insert into misc tables for easy access */ sa = hash_get(pim->msdp.sa_hash, sa, hash_alloc_intern); listnode_add_sort(pim->msdp.sa_list, sa); if (PIM_DEBUG_MSDP_EVENTS) { zlog_debug("MSDP SA %s created", sa->sg_str); } return sa; } static struct pim_msdp_sa *pim_msdp_sa_find(struct pim_instance *pim, struct prefix_sg *sg) { struct pim_msdp_sa lookup; lookup.sg = *sg; return hash_lookup(pim->msdp.sa_hash, &lookup); } static struct pim_msdp_sa *pim_msdp_sa_add(struct pim_instance *pim, struct prefix_sg *sg, struct in_addr rp) { struct pim_msdp_sa *sa; sa = pim_msdp_sa_find(pim, sg); if (sa) { return sa; } return pim_msdp_sa_new(pim, sg, rp); } static void pim_msdp_sa_del(struct pim_msdp_sa *sa) { /* this is somewhat redundant - still want to be careful not to leave * stale upstream references */ pim_msdp_sa_upstream_del(sa); /* stop timers */ pim_msdp_sa_state_timer_setup(sa, false /* start */); /* remove the entry from various tables */ listnode_delete(sa->pim->msdp.sa_list, sa); hash_release(sa->pim->msdp.sa_hash, sa); if (PIM_DEBUG_MSDP_EVENTS) { zlog_debug("MSDP SA %s deleted", sa->sg_str); } /* free up any associated memory */ pim_msdp_sa_free(sa); } static void pim_msdp_sa_peer_ip_set(struct pim_msdp_sa *sa, struct pim_msdp_peer *mp, struct in_addr rp) { struct pim_msdp_peer *old_mp; /* optimize the "no change" case as it will happen * frequently/periodically */ if (mp && (sa->peer.s_addr == mp->peer.s_addr)) { return; } /* any time the peer ip changes also update the rp address */ if (PIM_INADDR_ISNOT_ANY(sa->peer)) { old_mp = pim_msdp_peer_find(sa->pim, sa->peer); if (old_mp && old_mp->sa_cnt) { --old_mp->sa_cnt; } } if (mp) { ++mp->sa_cnt; sa->peer = mp->peer; } else { sa->peer.s_addr = PIM_NET_INADDR_ANY; } sa->rp = rp; } /* When a local active-source is removed there is no way to withdraw the * source from peers. We will simply remove it from the SA cache so it will * not be sent in supsequent SA updates. Peers will consequently timeout the * SA. * Similarly a "peer-added" SA is never explicitly deleted. It is simply * aged out overtime if not seen in the SA updates from the peers. * XXX: should we provide a knob to drop entries learnt from a peer when the * peer goes down? */ static void pim_msdp_sa_deref(struct pim_msdp_sa *sa, enum pim_msdp_sa_flags flags) { bool update_up = false; if ((sa->flags & PIM_MSDP_SAF_LOCAL)) { if (flags & PIM_MSDP_SAF_LOCAL) { if (PIM_DEBUG_MSDP_EVENTS) { zlog_debug("MSDP SA %s local reference removed", sa->sg_str); } if (sa->pim->msdp.local_cnt) --sa->pim->msdp.local_cnt; } } if ((sa->flags & PIM_MSDP_SAF_PEER)) { if (flags & PIM_MSDP_SAF_PEER) { struct in_addr rp; if (PIM_DEBUG_MSDP_EVENTS) { zlog_debug("MSDP SA %s peer reference removed", sa->sg_str); } pim_msdp_sa_state_timer_setup(sa, false /* start */); rp.s_addr = INADDR_ANY; pim_msdp_sa_peer_ip_set(sa, NULL /* mp */, rp); /* if peer ref was removed we need to remove the msdp * reference on the * msdp entry */ update_up = true; } } sa->flags &= ~flags; if (update_up) { pim_msdp_sa_upstream_update(sa, NULL /* xg_up */, "sa-deref"); } if (!(sa->flags & PIM_MSDP_SAF_REF)) { pim_msdp_sa_del(sa); } } void pim_msdp_sa_ref(struct pim_instance *pim, struct pim_msdp_peer *mp, struct prefix_sg *sg, struct in_addr rp) { struct pim_msdp_sa *sa; sa = pim_msdp_sa_add(pim, sg, rp); if (!sa) { return; } /* reference it */ if (mp) { if (!(sa->flags & PIM_MSDP_SAF_PEER)) { sa->flags |= PIM_MSDP_SAF_PEER; if (PIM_DEBUG_MSDP_EVENTS) { zlog_debug("MSDP SA %s added by peer", sa->sg_str); } } pim_msdp_sa_peer_ip_set(sa, mp, rp); /* start/re-start the state timer to prevent cache expiry */ pim_msdp_sa_state_timer_setup(sa, true /* start */); /* We re-evaluate SA "SPT-trigger" everytime we hear abt it from * a * peer. XXX: If this becomes too much of a periodic overhead we * can make it event based */ pim_msdp_sa_upstream_update(sa, NULL /* xg_up */, "peer-ref"); } else { if (!(sa->flags & PIM_MSDP_SAF_LOCAL)) { sa->flags |= PIM_MSDP_SAF_LOCAL; ++sa->pim->msdp.local_cnt; if (PIM_DEBUG_MSDP_EVENTS) { zlog_debug("MSDP SA %s added locally", sa->sg_str); } /* send an immediate SA update to peers */ pim_msdp_pkt_sa_tx_one(sa); } sa->flags &= ~PIM_MSDP_SAF_STALE; } } /* The following criteria must be met to originate an SA from the MSDP * speaker - * 1. KAT must be running i.e. source is active. * 2. We must be RP for the group. * 3. Source must be registrable to the RP (this is where the RFC is vague * and especially ambiguous in CLOS networks; with anycast RP all sources * are potentially registrable to all RPs in the domain). We assume #3 is * satisfied if - * a. We are also the FHR-DR for the source (OR) * b. We rxed a pim register (null or data encapsulated) within the last * (3 * (1.5 * register_suppression_timer))). */ static bool pim_msdp_sa_local_add_ok(struct pim_upstream *up) { struct pim_instance *pim = up->channel_oil->pim; if (!(pim->msdp.flags & PIM_MSDPF_ENABLE)) { return false; } if (!up->t_ka_timer) { /* stream is not active */ return false; } if (!I_am_RP(pim, up->sg.grp)) { /* we are not RP for the group */ return false; } /* we are the FHR-DR for this stream or we are RP and have seen * registers * from a FHR for this source */ if (PIM_UPSTREAM_FLAG_TEST_FHR(up->flags) || up->t_msdp_reg_timer) { return true; } return false; } static void pim_msdp_sa_local_add(struct pim_instance *pim, struct prefix_sg *sg) { struct in_addr rp; rp.s_addr = 0; pim_msdp_sa_ref(pim, NULL /* mp */, sg, rp); } void pim_msdp_sa_local_del(struct pim_instance *pim, struct prefix_sg *sg) { struct pim_msdp_sa *sa; sa = pim_msdp_sa_find(pim, sg); if (sa) { pim_msdp_sa_deref(sa, PIM_MSDP_SAF_LOCAL); } } /* we need to be very cautious with this API as SA del too can trigger an * upstream del and we will get stuck in a simple loop */ static void pim_msdp_sa_local_del_on_up_del(struct pim_instance *pim, struct prefix_sg *sg) { struct pim_msdp_sa *sa; sa = pim_msdp_sa_find(pim, sg); if (sa) { if (PIM_DEBUG_MSDP_INTERNAL) { zlog_debug("MSDP local sa %s del on up del", sa->sg_str); } /* if there is no local reference escape */ if (!(sa->flags & PIM_MSDP_SAF_LOCAL)) { if (PIM_DEBUG_MSDP_INTERNAL) { zlog_debug("MSDP local sa %s del; no local ref", sa->sg_str); } return; } if (sa->flags & PIM_MSDP_SAF_UP_DEL_IN_PROG) { /* MSDP is the one that triggered the upstream del. if * this happens * we most certainly have a bug in the PIM upstream * state machine. We * will not have a local reference unless the KAT is * running. And if the * KAT is running there MUST be an additional * source-stream reference to * the flow. Accounting for such cases requires lot of * changes; perhaps * address this in the next release? - XXX */ flog_err( EC_LIB_DEVELOPMENT, "MSDP sa %s SPT teardown is causing the local entry to be removed", sa->sg_str); return; } /* we are dropping the sa on upstream del we should not have an * upstream reference */ if (sa->up) { if (PIM_DEBUG_MSDP_INTERNAL) { zlog_debug("MSDP local sa %s del; up non-NULL", sa->sg_str); } sa->up = NULL; } pim_msdp_sa_deref(sa, PIM_MSDP_SAF_LOCAL); } } /* Local SA qualification needs to be re-evaluated when - * 1. KAT is started or stopped * 2. on RP changes * 3. Whenever FHR status changes for a (S,G) - XXX - currently there * is no clear path to transition an entry out of "MASK_FHR" need * to discuss this with Donald. May result in some strangeness if the * FHR is also the RP. * 4. When msdp_reg timer is started or stopped */ void pim_msdp_sa_local_update(struct pim_upstream *up) { struct pim_instance *pim = up->channel_oil->pim; if (pim_msdp_sa_local_add_ok(up)) { pim_msdp_sa_local_add(pim, &up->sg); } else { pim_msdp_sa_local_del(pim, &up->sg); } } static void pim_msdp_sa_local_setup(struct pim_instance *pim) { struct pim_upstream *up; struct listnode *up_node; for (ALL_LIST_ELEMENTS_RO(pim->upstream_list, up_node, up)) { pim_msdp_sa_local_update(up); } } /* whenever the RP changes we need to re-evaluate the "local" SA-cache */ /* XXX: needs to be tested */ void pim_msdp_i_am_rp_changed(struct pim_instance *pim) { struct listnode *sanode; struct listnode *nextnode; struct pim_msdp_sa *sa; if (!(pim->msdp.flags & PIM_MSDPF_ENABLE)) { /* if the feature is not enabled do nothing */ return; } if (PIM_DEBUG_MSDP_INTERNAL) { zlog_debug("MSDP i_am_rp changed"); } /* mark all local entries as stale */ for (ALL_LIST_ELEMENTS_RO(pim->msdp.sa_list, sanode, sa)) { if (sa->flags & PIM_MSDP_SAF_LOCAL) { sa->flags |= PIM_MSDP_SAF_STALE; } } /* re-setup local SA entries */ pim_msdp_sa_local_setup(pim); for (ALL_LIST_ELEMENTS(pim->msdp.sa_list, sanode, nextnode, sa)) { /* purge stale SA entries */ if (sa->flags & PIM_MSDP_SAF_STALE) { /* clear the stale flag; the entry may be kept even * after * "local-deref" */ sa->flags &= ~PIM_MSDP_SAF_STALE; /* sa_deref can end up freeing the sa; so don't access * contents after */ pim_msdp_sa_deref(sa, PIM_MSDP_SAF_LOCAL); } else { /* if the souce is still active check if we can * influence SPT */ pim_msdp_sa_upstream_update(sa, NULL /* xg_up */, "rp-change"); } } } /* We track the join state of (*, G) entries. If G has sources in the SA-cache * we need to setup or teardown SPT when the JoinDesired status changes for * (*, G) */ void pim_msdp_up_join_state_changed(struct pim_instance *pim, struct pim_upstream *xg_up) { struct listnode *sanode; struct pim_msdp_sa *sa; if (PIM_DEBUG_MSDP_INTERNAL) { zlog_debug("MSDP join state changed for %s", xg_up->sg_str); } /* If this is not really an XG entry just move on */ if ((xg_up->sg.src.s_addr != INADDR_ANY) || (xg_up->sg.grp.s_addr == INADDR_ANY)) { return; } /* XXX: Need to maintain SAs per-group to avoid all this unnecessary * walking */ for (ALL_LIST_ELEMENTS_RO(pim->msdp.sa_list, sanode, sa)) { if (sa->sg.grp.s_addr != xg_up->sg.grp.s_addr) { continue; } pim_msdp_sa_upstream_update(sa, xg_up, "up-jp-change"); } } static void pim_msdp_up_xg_del(struct pim_instance *pim, struct prefix_sg *sg) { struct listnode *sanode; struct pim_msdp_sa *sa; if (PIM_DEBUG_MSDP_INTERNAL) { zlog_debug("MSDP %s del", pim_str_sg_dump(sg)); } /* If this is not really an XG entry just move on */ if ((sg->src.s_addr != INADDR_ANY) || (sg->grp.s_addr == INADDR_ANY)) { return; } /* XXX: Need to maintain SAs per-group to avoid all this unnecessary * walking */ for (ALL_LIST_ELEMENTS_RO(pim->msdp.sa_list, sanode, sa)) { if (sa->sg.grp.s_addr != sg->grp.s_addr) { continue; } pim_msdp_sa_upstream_update(sa, NULL /* xg */, "up-jp-change"); } } void pim_msdp_up_del(struct pim_instance *pim, struct prefix_sg *sg) { if (PIM_DEBUG_MSDP_INTERNAL) { zlog_debug("MSDP up %s del", pim_str_sg_dump(sg)); } if (sg->src.s_addr == INADDR_ANY) { pim_msdp_up_xg_del(pim, sg); } else { pim_msdp_sa_local_del_on_up_del(pim, sg); } } /* sa hash and peer list helpers */ static unsigned int pim_msdp_sa_hash_key_make(const void *p) { const struct pim_msdp_sa *sa = p; return (jhash_2words(sa->sg.src.s_addr, sa->sg.grp.s_addr, 0)); } static bool pim_msdp_sa_hash_eq(const void *p1, const void *p2) { const struct pim_msdp_sa *sa1 = p1; const struct pim_msdp_sa *sa2 = p2; return ((sa1->sg.src.s_addr == sa2->sg.src.s_addr) && (sa1->sg.grp.s_addr == sa2->sg.grp.s_addr)); } static int pim_msdp_sa_comp(const void *p1, const void *p2) { const struct pim_msdp_sa *sa1 = p1; const struct pim_msdp_sa *sa2 = p2; if (ntohl(sa1->sg.grp.s_addr) < ntohl(sa2->sg.grp.s_addr)) return -1; if (ntohl(sa1->sg.grp.s_addr) > ntohl(sa2->sg.grp.s_addr)) return 1; if (ntohl(sa1->sg.src.s_addr) < ntohl(sa2->sg.src.s_addr)) return -1; if (ntohl(sa1->sg.src.s_addr) > ntohl(sa2->sg.src.s_addr)) return 1; return 0; } /* RFC-3618:Sec-10.1.3 - Peer-RPF forwarding */ /* XXX: this can use a bit of refining and extensions */ bool pim_msdp_peer_rpf_check(struct pim_msdp_peer *mp, struct in_addr rp) { if (mp->peer.s_addr == rp.s_addr) { return true; } return false; } /************************ Peer session management **************************/ char *pim_msdp_state_dump(enum pim_msdp_peer_state state, char *buf, int buf_size) { switch (state) { case PIM_MSDP_DISABLED: snprintf(buf, buf_size, "%s", "disabled"); break; case PIM_MSDP_INACTIVE: snprintf(buf, buf_size, "%s", "inactive"); break; case PIM_MSDP_LISTEN: snprintf(buf, buf_size, "%s", "listen"); break; case PIM_MSDP_CONNECTING: snprintf(buf, buf_size, "%s", "connecting"); break; case PIM_MSDP_ESTABLISHED: snprintf(buf, buf_size, "%s", "established"); break; default: snprintf(buf, buf_size, "unk-%d", state); } return buf; } char *pim_msdp_peer_key_dump(struct pim_msdp_peer *mp, char *buf, int buf_size, bool long_format) { char peer_str[INET_ADDRSTRLEN]; char local_str[INET_ADDRSTRLEN]; pim_inet4_dump("", mp->peer, peer_str, sizeof(peer_str)); if (long_format) { pim_inet4_dump("", mp->local, local_str, sizeof(local_str)); snprintf(buf, buf_size, "MSDP peer %s local %s mg %s", peer_str, local_str, mp->mesh_group_name); } else { snprintf(buf, buf_size, "MSDP peer %s", peer_str); } return buf; } static void pim_msdp_peer_state_chg_log(struct pim_msdp_peer *mp) { char state_str[PIM_MSDP_STATE_STRLEN]; pim_msdp_state_dump(mp->state, state_str, sizeof(state_str)); zlog_debug("MSDP peer %s state chg to %s", mp->key_str, state_str); } /* MSDP Connection State Machine actions (defined in RFC-3618:Sec-11.2) */ /* 11.2.A2: active peer - start connect retry timer; when the timer fires * a tcp connection will be made */ static void pim_msdp_peer_connect(struct pim_msdp_peer *mp) { mp->state = PIM_MSDP_CONNECTING; if (PIM_DEBUG_MSDP_EVENTS) { pim_msdp_peer_state_chg_log(mp); } pim_msdp_peer_cr_timer_setup(mp, true /* start */); } /* 11.2.A3: passive peer - just listen for connections */ static void pim_msdp_peer_listen(struct pim_msdp_peer *mp) { mp->state = PIM_MSDP_LISTEN; if (PIM_DEBUG_MSDP_EVENTS) { pim_msdp_peer_state_chg_log(mp); } /* this is interntionally asymmetric i.e. we set up listen-socket when * the * first listening peer is configured; but don't bother tearing it down * when * all the peers go down */ pim_msdp_sock_listen(mp->pim); } /* 11.2.A4 and 11.2.A5: transition active or passive peer to * established state */ void pim_msdp_peer_established(struct pim_msdp_peer *mp) { if (mp->state != PIM_MSDP_ESTABLISHED) { ++mp->est_flaps; } mp->state = PIM_MSDP_ESTABLISHED; mp->uptime = pim_time_monotonic_sec(); if (PIM_DEBUG_MSDP_EVENTS) { pim_msdp_peer_state_chg_log(mp); } /* stop retry timer on active peers */ pim_msdp_peer_cr_timer_setup(mp, false /* start */); /* send KA; start KA and hold timers */ pim_msdp_pkt_ka_tx(mp); pim_msdp_peer_ka_timer_setup(mp, true /* start */); pim_msdp_peer_hold_timer_setup(mp, true /* start */); pim_msdp_pkt_sa_tx_to_one_peer(mp); PIM_MSDP_PEER_WRITE_ON(mp); PIM_MSDP_PEER_READ_ON(mp); } /* 11.2.A6, 11.2.A7 and 11.2.A8: shutdown the peer tcp connection */ void pim_msdp_peer_stop_tcp_conn(struct pim_msdp_peer *mp, bool chg_state) { if (chg_state) { if (mp->state == PIM_MSDP_ESTABLISHED) { ++mp->est_flaps; } mp->state = PIM_MSDP_INACTIVE; if (PIM_DEBUG_MSDP_EVENTS) { pim_msdp_peer_state_chg_log(mp); } } if (PIM_DEBUG_MSDP_INTERNAL) { zlog_debug("MSDP peer %s pim_msdp_peer_stop_tcp_conn", mp->key_str); } /* stop read and write threads */ PIM_MSDP_PEER_READ_OFF(mp); PIM_MSDP_PEER_WRITE_OFF(mp); /* reset buffers */ mp->packet_size = 0; if (mp->ibuf) stream_reset(mp->ibuf); if (mp->obuf) stream_fifo_clean(mp->obuf); /* stop all peer timers */ pim_msdp_peer_ka_timer_setup(mp, false /* start */); pim_msdp_peer_cr_timer_setup(mp, false /* start */); pim_msdp_peer_hold_timer_setup(mp, false /* start */); /* close connection */ if (mp->fd >= 0) { close(mp->fd); mp->fd = -1; } } /* RFC-3618:Sec-5.6 - stop the peer tcp connection and startover */ void pim_msdp_peer_reset_tcp_conn(struct pim_msdp_peer *mp, const char *rc_str) { if (PIM_DEBUG_EVENTS) { zlog_debug("MSDP peer %s tcp reset %s", mp->key_str, rc_str); snprintf(mp->last_reset, sizeof(mp->last_reset), "%s", rc_str); } /* close the connection and transition to listening or connecting */ pim_msdp_peer_stop_tcp_conn(mp, true /* chg_state */); if (PIM_MSDP_PEER_IS_LISTENER(mp)) { pim_msdp_peer_listen(mp); } else { pim_msdp_peer_connect(mp); } } static void pim_msdp_peer_timer_expiry_log(struct pim_msdp_peer *mp, const char *timer_str) { zlog_debug("MSDP peer %s %s timer expired", mp->key_str, timer_str); } /* RFC-3618:Sec-5.4 - peer hold timer */ static int pim_msdp_peer_hold_timer_cb(struct thread *t) { struct pim_msdp_peer *mp; mp = THREAD_ARG(t); if (PIM_DEBUG_MSDP_EVENTS) { pim_msdp_peer_timer_expiry_log(mp, "hold"); } if (mp->state != PIM_MSDP_ESTABLISHED) { return 0; } if (PIM_DEBUG_MSDP_EVENTS) { pim_msdp_peer_state_chg_log(mp); } pim_msdp_peer_reset_tcp_conn(mp, "ht-expired"); return 0; } static void pim_msdp_peer_hold_timer_setup(struct pim_msdp_peer *mp, bool start) { struct pim_instance *pim = mp->pim; THREAD_OFF(mp->hold_timer); if (start) { thread_add_timer(pim->msdp.master, pim_msdp_peer_hold_timer_cb, mp, PIM_MSDP_PEER_HOLD_TIME, &mp->hold_timer); } } /* RFC-3618:Sec-5.5 - peer keepalive timer */ static int pim_msdp_peer_ka_timer_cb(struct thread *t) { struct pim_msdp_peer *mp; mp = THREAD_ARG(t); if (PIM_DEBUG_MSDP_EVENTS) { pim_msdp_peer_timer_expiry_log(mp, "ka"); } pim_msdp_pkt_ka_tx(mp); pim_msdp_peer_ka_timer_setup(mp, true /* start */); return 0; } static void pim_msdp_peer_ka_timer_setup(struct pim_msdp_peer *mp, bool start) { THREAD_OFF(mp->ka_timer); if (start) { thread_add_timer(mp->pim->msdp.master, pim_msdp_peer_ka_timer_cb, mp, PIM_MSDP_PEER_KA_TIME, &mp->ka_timer); } } static void pim_msdp_peer_active_connect(struct pim_msdp_peer *mp) { int rc; ++mp->conn_attempts; rc = pim_msdp_sock_connect(mp); if (PIM_DEBUG_MSDP_INTERNAL) { zlog_debug("MSDP peer %s pim_msdp_peer_active_connect: %d", mp->key_str, rc); } switch (rc) { case connect_error: case -1: /* connect failed restart the connect-retry timer */ pim_msdp_peer_cr_timer_setup(mp, true /* start */); break; case connect_success: /* connect was sucessful move to established */ pim_msdp_peer_established(mp); break; case connect_in_progress: /* for NB content we need to wait till sock is readable or * writeable */ PIM_MSDP_PEER_WRITE_ON(mp); PIM_MSDP_PEER_READ_ON(mp); /* also restart connect-retry timer to reset the socket if * connect is * not sucessful */ pim_msdp_peer_cr_timer_setup(mp, true /* start */); break; } } /* RFC-3618:Sec-5.6 - connection retry on active peer */ static int pim_msdp_peer_cr_timer_cb(struct thread *t) { struct pim_msdp_peer *mp; mp = THREAD_ARG(t); if (PIM_DEBUG_MSDP_EVENTS) { pim_msdp_peer_timer_expiry_log(mp, "connect-retry"); } if (mp->state != PIM_MSDP_CONNECTING || PIM_MSDP_PEER_IS_LISTENER(mp)) { return 0; } pim_msdp_peer_active_connect(mp); return 0; } static void pim_msdp_peer_cr_timer_setup(struct pim_msdp_peer *mp, bool start) { THREAD_OFF(mp->cr_timer); if (start) { thread_add_timer( mp->pim->msdp.master, pim_msdp_peer_cr_timer_cb, mp, PIM_MSDP_PEER_CONNECT_RETRY_TIME, &mp->cr_timer); } } /* if a valid packet is rxed from the peer we can restart hold timer */ void pim_msdp_peer_pkt_rxed(struct pim_msdp_peer *mp) { if (mp->state == PIM_MSDP_ESTABLISHED) { pim_msdp_peer_hold_timer_setup(mp, true /* start */); } } /* if a valid packet is txed to the peer we can restart ka timer and avoid * unnecessary ka noise in the network */ void pim_msdp_peer_pkt_txed(struct pim_msdp_peer *mp) { if (mp->state == PIM_MSDP_ESTABLISHED) { pim_msdp_peer_ka_timer_setup(mp, true /* start */); if (PIM_DEBUG_MSDP_INTERNAL) { zlog_debug("MSDP ka timer restart on pkt tx to %s", mp->key_str); } } } static void pim_msdp_addr2su(union sockunion *su, struct in_addr addr) { sockunion_init(su); su->sin.sin_addr = addr; su->sin.sin_family = AF_INET; #ifdef HAVE_STRUCT_SOCKADDR_IN_SIN_LEN su->sin.sin_len = sizeof(struct sockaddr_in); #endif /* HAVE_STRUCT_SOCKADDR_IN_SIN_LEN */ } /* 11.2.A1: create a new peer and transition state to listen or connecting */ static enum pim_msdp_err pim_msdp_peer_new(struct pim_instance *pim, struct in_addr peer_addr, struct in_addr local_addr, const char *mesh_group_name, struct pim_msdp_peer **mp_p) { struct pim_msdp_peer *mp; pim_msdp_enable(pim); mp = XCALLOC(MTYPE_PIM_MSDP_PEER, sizeof(*mp)); mp->pim = pim; mp->peer = peer_addr; pim_inet4_dump("", mp->peer, mp->key_str, sizeof(mp->key_str)); pim_msdp_addr2su(&mp->su_peer, mp->peer); mp->local = local_addr; /* XXX: originator_id setting needs to move to the mesh group */ pim->msdp.originator_id = local_addr; pim_msdp_addr2su(&mp->su_local, mp->local); mp->mesh_group_name = XSTRDUP(MTYPE_PIM_MSDP_MG_NAME, mesh_group_name); mp->state = PIM_MSDP_INACTIVE; mp->fd = -1; strlcpy(mp->last_reset, "-", sizeof(mp->last_reset)); /* higher IP address is listener */ if (ntohl(mp->local.s_addr) > ntohl(mp->peer.s_addr)) { mp->flags |= PIM_MSDP_PEERF_LISTENER; } /* setup packet buffers */ mp->ibuf = stream_new(PIM_MSDP_MAX_PACKET_SIZE); mp->obuf = stream_fifo_new(); /* insert into misc tables for easy access */ mp = hash_get(pim->msdp.peer_hash, mp, hash_alloc_intern); listnode_add_sort(pim->msdp.peer_list, mp); if (PIM_DEBUG_MSDP_EVENTS) { zlog_debug("MSDP peer %s created", mp->key_str); pim_msdp_peer_state_chg_log(mp); } /* fireup the connect state machine */ if (PIM_MSDP_PEER_IS_LISTENER(mp)) { pim_msdp_peer_listen(mp); } else { pim_msdp_peer_connect(mp); } if (mp_p) { *mp_p = mp; } return PIM_MSDP_ERR_NONE; } struct pim_msdp_peer *pim_msdp_peer_find(struct pim_instance *pim, struct in_addr peer_addr) { struct pim_msdp_peer lookup; lookup.peer = peer_addr; return hash_lookup(pim->msdp.peer_hash, &lookup); } /* add peer configuration if it doesn't already exist */ enum pim_msdp_err pim_msdp_peer_add(struct pim_instance *pim, struct in_addr peer_addr, struct in_addr local_addr, const char *mesh_group_name, struct pim_msdp_peer **mp_p) { struct pim_msdp_peer *mp; if (mp_p) { *mp_p = NULL; } if (peer_addr.s_addr == local_addr.s_addr) { /* skip session setup if config is invalid */ if (PIM_DEBUG_MSDP_EVENTS) { char peer_str[INET_ADDRSTRLEN]; pim_inet4_dump("", peer_addr, peer_str, sizeof(peer_str)); zlog_debug("%s add skipped as DIP=SIP", peer_str); } return PIM_MSDP_ERR_SIP_EQ_DIP; } mp = pim_msdp_peer_find(pim, peer_addr); if (mp) { if (mp_p) { *mp_p = mp; } return PIM_MSDP_ERR_PEER_EXISTS; } return pim_msdp_peer_new(pim, peer_addr, local_addr, mesh_group_name, mp_p); } /* release all mem associated with a peer */ static void pim_msdp_peer_free(struct pim_msdp_peer *mp) { /* * Let's make sure we are not running when we delete * the underlying data structure */ pim_msdp_peer_stop_tcp_conn(mp, false); if (mp->ibuf) { stream_free(mp->ibuf); } if (mp->obuf) { stream_fifo_free(mp->obuf); } if (mp->mesh_group_name) { XFREE(MTYPE_PIM_MSDP_MG_NAME, mp->mesh_group_name); } mp->pim = NULL; XFREE(MTYPE_PIM_MSDP_PEER, mp); } /* delete the peer config */ static enum pim_msdp_err pim_msdp_peer_do_del(struct pim_msdp_peer *mp) { /* stop the tcp connection and shutdown all timers */ pim_msdp_peer_stop_tcp_conn(mp, true /* chg_state */); /* remove the session from various tables */ listnode_delete(mp->pim->msdp.peer_list, mp); hash_release(mp->pim->msdp.peer_hash, mp); if (PIM_DEBUG_MSDP_EVENTS) { zlog_debug("MSDP peer %s deleted", mp->key_str); } /* free up any associated memory */ pim_msdp_peer_free(mp); return PIM_MSDP_ERR_NONE; } enum pim_msdp_err pim_msdp_peer_del(struct pim_instance *pim, struct in_addr peer_addr) { struct pim_msdp_peer *mp; mp = pim_msdp_peer_find(pim, peer_addr); if (!mp) { return PIM_MSDP_ERR_NO_PEER; } return pim_msdp_peer_do_del(mp); } /* peer hash and peer list helpers */ static unsigned int pim_msdp_peer_hash_key_make(const void *p) { const struct pim_msdp_peer *mp = p; return (jhash_1word(mp->peer.s_addr, 0)); } static bool pim_msdp_peer_hash_eq(const void *p1, const void *p2) { const struct pim_msdp_peer *mp1 = p1; const struct pim_msdp_peer *mp2 = p2; return (mp1->peer.s_addr == mp2->peer.s_addr); } static int pim_msdp_peer_comp(const void *p1, const void *p2) { const struct pim_msdp_peer *mp1 = p1; const struct pim_msdp_peer *mp2 = p2; if (ntohl(mp1->peer.s_addr) < ntohl(mp2->peer.s_addr)) return -1; if (ntohl(mp1->peer.s_addr) > ntohl(mp2->peer.s_addr)) return 1; return 0; } /************************** Mesh group management **************************/ static void pim_msdp_mg_free(struct pim_instance *pim) { struct pim_msdp_mg *mg = pim->msdp.mg; /* If the mesh-group has valid member or src_ip don't delete it */ if (!mg || mg->mbr_cnt || (mg->src_ip.s_addr != INADDR_ANY)) { return; } if (PIM_DEBUG_MSDP_EVENTS) { zlog_debug("MSDP mesh-group %s deleted", mg->mesh_group_name); } XFREE(MTYPE_PIM_MSDP_MG_NAME, mg->mesh_group_name); if (mg->mbr_list) list_delete(&mg->mbr_list); XFREE(MTYPE_PIM_MSDP_MG, pim->msdp.mg); } static struct pim_msdp_mg *pim_msdp_mg_new(const char *mesh_group_name) { struct pim_msdp_mg *mg; mg = XCALLOC(MTYPE_PIM_MSDP_MG, sizeof(*mg)); mg->mesh_group_name = XSTRDUP(MTYPE_PIM_MSDP_MG_NAME, mesh_group_name); mg->mbr_list = list_new(); mg->mbr_list->del = (void (*)(void *))pim_msdp_mg_mbr_free; mg->mbr_list->cmp = (int (*)(void *, void *))pim_msdp_mg_mbr_comp; if (PIM_DEBUG_MSDP_EVENTS) { zlog_debug("MSDP mesh-group %s created", mg->mesh_group_name); } return mg; } enum pim_msdp_err pim_msdp_mg_del(struct pim_instance *pim, const char *mesh_group_name) { struct pim_msdp_mg *mg = pim->msdp.mg; struct pim_msdp_mg_mbr *mbr; if (!mg || strcmp(mg->mesh_group_name, mesh_group_name)) { return PIM_MSDP_ERR_NO_MG; } /* delete all the mesh-group members */ while (!list_isempty(mg->mbr_list)) { mbr = listnode_head(mg->mbr_list); pim_msdp_mg_mbr_do_del(mg, mbr); } /* clear src ip */ mg->src_ip.s_addr = INADDR_ANY; /* free up the mesh-group */ pim_msdp_mg_free(pim); return PIM_MSDP_ERR_NONE; } static enum pim_msdp_err pim_msdp_mg_add(struct pim_instance *pim, const char *mesh_group_name) { if (pim->msdp.mg) { if (!strcmp(pim->msdp.mg->mesh_group_name, mesh_group_name)) { return PIM_MSDP_ERR_NONE; } /* currently only one mesh-group can exist at a time */ return PIM_MSDP_ERR_MAX_MESH_GROUPS; } pim->msdp.mg = pim_msdp_mg_new(mesh_group_name); if (!pim->msdp.mg) { return PIM_MSDP_ERR_OOM; } return PIM_MSDP_ERR_NONE; } static int pim_msdp_mg_mbr_comp(const void *p1, const void *p2) { const struct pim_msdp_mg_mbr *mbr1 = p1; const struct pim_msdp_mg_mbr *mbr2 = p2; if (ntohl(mbr1->mbr_ip.s_addr) < ntohl(mbr2->mbr_ip.s_addr)) return -1; if (ntohl(mbr1->mbr_ip.s_addr) > ntohl(mbr2->mbr_ip.s_addr)) return 1; return 0; } static void pim_msdp_mg_mbr_free(struct pim_msdp_mg_mbr *mbr) { XFREE(MTYPE_PIM_MSDP_MG_MBR, mbr); } static struct pim_msdp_mg_mbr *pim_msdp_mg_mbr_find(struct pim_instance *pim, struct in_addr mbr_ip) { struct pim_msdp_mg_mbr *mbr; struct listnode *mbr_node; if (!pim->msdp.mg) { return NULL; } /* we can move this to a hash but considering that number of peers in * a mesh-group that seems like bit of an overkill */ for (ALL_LIST_ELEMENTS_RO(pim->msdp.mg->mbr_list, mbr_node, mbr)) { if (mbr->mbr_ip.s_addr == mbr_ip.s_addr) { return mbr; } } return mbr; } enum pim_msdp_err pim_msdp_mg_mbr_add(struct pim_instance *pim, const char *mesh_group_name, struct in_addr mbr_ip) { int rc; struct pim_msdp_mg_mbr *mbr; struct pim_msdp_mg *mg; rc = pim_msdp_mg_add(pim, mesh_group_name); if (rc != PIM_MSDP_ERR_NONE) { return rc; } mg = pim->msdp.mg; mbr = pim_msdp_mg_mbr_find(pim, mbr_ip); if (mbr) { return PIM_MSDP_ERR_MG_MBR_EXISTS; } mbr = XCALLOC(MTYPE_PIM_MSDP_MG_MBR, sizeof(*mbr)); mbr->mbr_ip = mbr_ip; listnode_add_sort(mg->mbr_list, mbr); /* if valid SIP has been configured add peer session */ if (mg->src_ip.s_addr != INADDR_ANY) { pim_msdp_peer_add(pim, mbr_ip, mg->src_ip, mesh_group_name, &mbr->mp); } if (PIM_DEBUG_MSDP_EVENTS) { char ip_str[INET_ADDRSTRLEN]; pim_inet4_dump("", mbr->mbr_ip, ip_str, sizeof(ip_str)); zlog_debug("MSDP mesh-group %s mbr %s created", mg->mesh_group_name, ip_str); } ++mg->mbr_cnt; return PIM_MSDP_ERR_NONE; } static void pim_msdp_mg_mbr_do_del(struct pim_msdp_mg *mg, struct pim_msdp_mg_mbr *mbr) { /* Delete active peer session if any */ if (mbr->mp) { pim_msdp_peer_do_del(mbr->mp); } listnode_delete(mg->mbr_list, mbr); if (PIM_DEBUG_MSDP_EVENTS) { char ip_str[INET_ADDRSTRLEN]; pim_inet4_dump("", mbr->mbr_ip, ip_str, sizeof(ip_str)); zlog_debug("MSDP mesh-group %s mbr %s deleted", mg->mesh_group_name, ip_str); } pim_msdp_mg_mbr_free(mbr); if (mg->mbr_cnt) { --mg->mbr_cnt; } } enum pim_msdp_err pim_msdp_mg_mbr_del(struct pim_instance *pim, const char *mesh_group_name, struct in_addr mbr_ip) { struct pim_msdp_mg_mbr *mbr; struct pim_msdp_mg *mg = pim->msdp.mg; if (!mg || strcmp(mg->mesh_group_name, mesh_group_name)) { return PIM_MSDP_ERR_NO_MG; } mbr = pim_msdp_mg_mbr_find(pim, mbr_ip); if (!mbr) { return PIM_MSDP_ERR_NO_MG_MBR; } pim_msdp_mg_mbr_do_del(mg, mbr); /* if there are no references to the mg free it */ pim_msdp_mg_free(pim); return PIM_MSDP_ERR_NONE; } static void pim_msdp_mg_src_do_del(struct pim_instance *pim) { struct pim_msdp_mg_mbr *mbr; struct listnode *mbr_node; struct pim_msdp_mg *mg = pim->msdp.mg; /* SIP is being removed - tear down all active peer sessions */ for (ALL_LIST_ELEMENTS_RO(mg->mbr_list, mbr_node, mbr)) { if (mbr->mp) { pim_msdp_peer_do_del(mbr->mp); mbr->mp = NULL; } } if (PIM_DEBUG_MSDP_EVENTS) { zlog_debug("MSDP mesh-group %s src cleared", mg->mesh_group_name); } } enum pim_msdp_err pim_msdp_mg_src_del(struct pim_instance *pim, const char *mesh_group_name) { struct pim_msdp_mg *mg = pim->msdp.mg; if (!mg || strcmp(mg->mesh_group_name, mesh_group_name)) { return PIM_MSDP_ERR_NO_MG; } if (mg->src_ip.s_addr != INADDR_ANY) { mg->src_ip.s_addr = INADDR_ANY; pim_msdp_mg_src_do_del(pim); /* if there are no references to the mg free it */ pim_msdp_mg_free(pim); } return PIM_MSDP_ERR_NONE; } enum pim_msdp_err pim_msdp_mg_src_add(struct pim_instance *pim, const char *mesh_group_name, struct in_addr src_ip) { int rc; struct pim_msdp_mg_mbr *mbr; struct listnode *mbr_node; struct pim_msdp_mg *mg; if (src_ip.s_addr == INADDR_ANY) { pim_msdp_mg_src_del(pim, mesh_group_name); return PIM_MSDP_ERR_NONE; } rc = pim_msdp_mg_add(pim, mesh_group_name); if (rc != PIM_MSDP_ERR_NONE) { return rc; } mg = pim->msdp.mg; if (mg->src_ip.s_addr != INADDR_ANY) { pim_msdp_mg_src_do_del(pim); } mg->src_ip = src_ip; for (ALL_LIST_ELEMENTS_RO(mg->mbr_list, mbr_node, mbr)) { pim_msdp_peer_add(pim, mbr->mbr_ip, mg->src_ip, mesh_group_name, &mbr->mp); } if (PIM_DEBUG_MSDP_EVENTS) { char ip_str[INET_ADDRSTRLEN]; pim_inet4_dump("", mg->src_ip, ip_str, sizeof(ip_str)); zlog_debug("MSDP mesh-group %s src %s set", mg->mesh_group_name, ip_str); } return PIM_MSDP_ERR_NONE; } /*********************** MSDP feature APIs *********************************/ int pim_msdp_config_write(struct pim_instance *pim, struct vty *vty, const char *spaces) { struct listnode *mbrnode; struct pim_msdp_mg_mbr *mbr; struct pim_msdp_mg *mg = pim->msdp.mg; char mbr_str[INET_ADDRSTRLEN]; char src_str[INET_ADDRSTRLEN]; int count = 0; if (!mg) { return count; } if (mg->src_ip.s_addr != INADDR_ANY) { pim_inet4_dump("", mg->src_ip, src_str, sizeof(src_str)); vty_out(vty, "%sip msdp mesh-group %s source %s\n", spaces, mg->mesh_group_name, src_str); ++count; } for (ALL_LIST_ELEMENTS_RO(mg->mbr_list, mbrnode, mbr)) { pim_inet4_dump("", mbr->mbr_ip, mbr_str, sizeof(mbr_str)); vty_out(vty, "%sip msdp mesh-group %s member %s\n", spaces, mg->mesh_group_name, mbr_str); ++count; } return count; } /* Enable feature including active/periodic timers etc. on the first peer * config. Till then MSDP should just stay quiet. */ static void pim_msdp_enable(struct pim_instance *pim) { if (pim->msdp.flags & PIM_MSDPF_ENABLE) { /* feature is already enabled */ return; } pim->msdp.flags |= PIM_MSDPF_ENABLE; pim->msdp.work_obuf = stream_new(PIM_MSDP_MAX_PACKET_SIZE); pim_msdp_sa_adv_timer_setup(pim, true /* start */); /* setup sa cache based on local sources */ pim_msdp_sa_local_setup(pim); } /* MSDP init */ void pim_msdp_init(struct pim_instance *pim, struct thread_master *master) { pim->msdp.master = master; char hash_name[64]; snprintf(hash_name, 64, "PIM %s MSDP Peer Hash", pim->vrf->name); pim->msdp.peer_hash = hash_create(pim_msdp_peer_hash_key_make, pim_msdp_peer_hash_eq, hash_name); pim->msdp.peer_list = list_new(); pim->msdp.peer_list->del = (void (*)(void *))pim_msdp_peer_free; pim->msdp.peer_list->cmp = (int (*)(void *, void *))pim_msdp_peer_comp; snprintf(hash_name, 64, "PIM %s MSDP SA Hash", pim->vrf->name); pim->msdp.sa_hash = hash_create(pim_msdp_sa_hash_key_make, pim_msdp_sa_hash_eq, hash_name); pim->msdp.sa_list = list_new(); pim->msdp.sa_list->del = (void (*)(void *))pim_msdp_sa_free; pim->msdp.sa_list->cmp = (int (*)(void *, void *))pim_msdp_sa_comp; } /* counterpart to MSDP init; XXX: unused currently */ void pim_msdp_exit(struct pim_instance *pim) { pim_msdp_sa_adv_timer_setup(pim, false); /* XXX: stop listener and delete all peer sessions */ pim_msdp_mg_free(pim); if (pim->msdp.peer_hash) { hash_clean(pim->msdp.peer_hash, NULL); hash_free(pim->msdp.peer_hash); pim->msdp.peer_hash = NULL; } if (pim->msdp.peer_list) { list_delete(&pim->msdp.peer_list); } if (pim->msdp.sa_hash) { hash_clean(pim->msdp.sa_hash, NULL); hash_free(pim->msdp.sa_hash); pim->msdp.sa_hash = NULL; } if (pim->msdp.sa_list) { list_delete(&pim->msdp.sa_list); } if (pim->msdp.work_obuf) stream_free(pim->msdp.work_obuf); pim->msdp.work_obuf = NULL; } frr-7.2.1/pimd/pim_msdp.h0000644000000000000000000001762313610377563012137 00000000000000/* * IP MSDP for Quagga * Copyright (C) 2016 Cumulus Networks, Inc. * * This program is free software; you can redistribute it and/or modify * it under the terms of the 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; see the file COPYING; if not, write to the Free Software * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA */ #ifndef PIM_MSDP_H #define PIM_MSDP_H enum pim_msdp_peer_state { PIM_MSDP_DISABLED, PIM_MSDP_INACTIVE, PIM_MSDP_LISTEN, PIM_MSDP_CONNECTING, PIM_MSDP_ESTABLISHED }; /* SA and KA TLVs are processed; rest ignored */ enum pim_msdp_tlv { PIM_MSDP_V4_SOURCE_ACTIVE = 1, PIM_MSDP_V4_SOURCE_ACTIVE_REQUEST, PIM_MSDP_V4_SOURCE_ACTIVE_RESPONSE, PIM_MSDP_KEEPALIVE, PIM_MSDP_RESERVED, PIM_MSDP_TRACEROUTE_PROGRESS, PIM_MSDP_TRACEROUTE_REPLY, }; /* MSDP error codes */ enum pim_msdp_err { PIM_MSDP_ERR_NONE = 0, PIM_MSDP_ERR_OOM = -1, PIM_MSDP_ERR_PEER_EXISTS = -2, PIM_MSDP_ERR_MAX_MESH_GROUPS = -3, PIM_MSDP_ERR_NO_PEER = -4, PIM_MSDP_ERR_MG_MBR_EXISTS = -5, PIM_MSDP_ERR_NO_MG = -6, PIM_MSDP_ERR_NO_MG_MBR = -7, PIM_MSDP_ERR_SIP_EQ_DIP = -8, }; #define PIM_MSDP_STATE_STRLEN 16 #define PIM_MSDP_UPTIME_STRLEN 80 #define PIM_MSDP_TIMER_STRLEN 12 #define PIM_MSDP_TCP_PORT 639 #define PIM_MSDP_SOCKET_SNDBUF_SIZE 65536 enum pim_msdp_sa_flags { PIM_MSDP_SAF_NONE = 0, /* There are two cases where we can pickup an active source locally - * 1. We are RP and got a source-register from the FHR * 2. We are RP and FHR and learnt a new directly connected source on a * DR interface */ PIM_MSDP_SAF_LOCAL = (1 << 0), /* We got this in the MSDP SA TLV from a peer (and this passed peer-RPF * checks) */ PIM_MSDP_SAF_PEER = (1 << 1), PIM_MSDP_SAF_REF = (PIM_MSDP_SAF_LOCAL | PIM_MSDP_SAF_PEER), PIM_MSDP_SAF_STALE = (1 << 2), /* local entries can get kicked out on * misc pim events such as RP change */ PIM_MSDP_SAF_UP_DEL_IN_PROG = (1 << 3) }; struct pim_msdp_sa { struct pim_instance *pim; struct prefix_sg sg; char sg_str[PIM_SG_LEN]; struct in_addr rp; /* Last RP address associated with this SA */ struct in_addr peer; /* last peer from who we heard this SA */ enum pim_msdp_sa_flags flags; /* rfc-3618 is missing default value for SA-hold-down-Period. pulled * this number from industry-standards */ #define PIM_MSDP_SA_HOLD_TIME ((3*60)+30) struct thread *sa_state_timer; // 5.6 int64_t uptime; struct pim_upstream *up; }; enum pim_msdp_peer_flags { PIM_MSDP_PEERF_NONE = 0, PIM_MSDP_PEERF_LISTENER = (1 << 0), #define PIM_MSDP_PEER_IS_LISTENER(mp) (mp->flags & PIM_MSDP_PEERF_LISTENER) PIM_MSDP_PEERF_SA_JUST_SENT = (1 << 1) }; struct pim_msdp_peer { struct pim_instance *pim; /* configuration */ struct in_addr local; struct in_addr peer; char *mesh_group_name; char key_str[INET_ADDRSTRLEN]; /* state */ enum pim_msdp_peer_state state; enum pim_msdp_peer_flags flags; /* TCP socket info */ union sockunion su_local; union sockunion su_peer; int fd; /* protocol timers */ #define PIM_MSDP_PEER_HOLD_TIME 75 struct thread *hold_timer; // 5.4 #define PIM_MSDP_PEER_KA_TIME 60 struct thread *ka_timer; // 5.5 #define PIM_MSDP_PEER_CONNECT_RETRY_TIME 30 struct thread *cr_timer; // 5.6 /* packet thread and buffers */ uint32_t packet_size; struct stream *ibuf; struct stream_fifo *obuf; struct thread *t_read; struct thread *t_write; /* stats */ uint32_t conn_attempts; uint32_t est_flaps; uint32_t sa_cnt; /* number of SAs attributed to this peer */ #define PIM_MSDP_PEER_LAST_RESET_STR 20 char last_reset[PIM_MSDP_PEER_LAST_RESET_STR]; /* packet stats */ uint32_t ka_tx_cnt; uint32_t sa_tx_cnt; uint32_t ka_rx_cnt; uint32_t sa_rx_cnt; uint32_t unk_rx_cnt; /* timestamps */ int64_t uptime; }; struct pim_msdp_mg_mbr { struct in_addr mbr_ip; struct pim_msdp_peer *mp; }; /* PIM MSDP mesh-group */ struct pim_msdp_mg { char *mesh_group_name; struct in_addr src_ip; uint32_t mbr_cnt; struct list *mbr_list; }; enum pim_msdp_flags { PIM_MSDPF_NONE = 0, PIM_MSDPF_ENABLE = (1 << 0), PIM_MSDPF_LISTENER = (1 << 1) }; struct pim_msdp_listener { int fd; union sockunion su; struct thread *thread; }; struct pim_msdp { enum pim_msdp_flags flags; struct thread_master *master; struct pim_msdp_listener listener; uint32_t rejected_accepts; /* MSDP peer info */ struct hash *peer_hash; struct list *peer_list; /* MSDP active-source info */ #define PIM_MSDP_SA_ADVERTISMENT_TIME 60 struct thread *sa_adv_timer; // 5.6 struct hash *sa_hash; struct list *sa_list; uint32_t local_cnt; /* keep a scratch pad for building SA TLVs */ struct stream *work_obuf; struct in_addr originator_id; /* currently only one mesh-group is supported - so just stash it here */ struct pim_msdp_mg *mg; }; #define PIM_MSDP_PEER_READ_ON(mp) \ thread_add_read(mp->pim->msdp.master, pim_msdp_read, mp, mp->fd, \ &mp->t_read) #define PIM_MSDP_PEER_WRITE_ON(mp) \ thread_add_write(mp->pim->msdp.master, pim_msdp_write, mp, mp->fd, \ &mp->t_write) #define PIM_MSDP_PEER_READ_OFF(mp) THREAD_READ_OFF(mp->t_read) #define PIM_MSDP_PEER_WRITE_OFF(mp) THREAD_WRITE_OFF(mp->t_write) // struct pim_msdp *msdp; struct pim_instance; void pim_msdp_init(struct pim_instance *pim, struct thread_master *master); void pim_msdp_exit(struct pim_instance *pim); enum pim_msdp_err pim_msdp_peer_add(struct pim_instance *pim, struct in_addr peer, struct in_addr local, const char *mesh_group_name, struct pim_msdp_peer **mp_p); enum pim_msdp_err pim_msdp_peer_del(struct pim_instance *pim, struct in_addr peer_addr); char *pim_msdp_state_dump(enum pim_msdp_peer_state state, char *buf, int buf_size); struct pim_msdp_peer *pim_msdp_peer_find(struct pim_instance *pim, struct in_addr peer_addr); void pim_msdp_peer_established(struct pim_msdp_peer *mp); void pim_msdp_peer_pkt_rxed(struct pim_msdp_peer *mp); void pim_msdp_peer_stop_tcp_conn(struct pim_msdp_peer *mp, bool chg_state); void pim_msdp_peer_reset_tcp_conn(struct pim_msdp_peer *mp, const char *rc_str); int pim_msdp_write(struct thread *thread); char *pim_msdp_peer_key_dump(struct pim_msdp_peer *mp, char *buf, int buf_size, bool long_format); int pim_msdp_config_write(struct pim_instance *pim, struct vty *vty, const char *spaces); void pim_msdp_peer_pkt_txed(struct pim_msdp_peer *mp); void pim_msdp_sa_ref(struct pim_instance *pim, struct pim_msdp_peer *mp, struct prefix_sg *sg, struct in_addr rp); void pim_msdp_sa_local_update(struct pim_upstream *up); void pim_msdp_sa_local_del(struct pim_instance *pim, struct prefix_sg *sg); void pim_msdp_i_am_rp_changed(struct pim_instance *pim); bool pim_msdp_peer_rpf_check(struct pim_msdp_peer *mp, struct in_addr rp); void pim_msdp_up_join_state_changed(struct pim_instance *pim, struct pim_upstream *xg_up); void pim_msdp_up_del(struct pim_instance *pim, struct prefix_sg *sg); enum pim_msdp_err pim_msdp_mg_mbr_add(struct pim_instance *pim, const char *mesh_group_name, struct in_addr mbr_ip); enum pim_msdp_err pim_msdp_mg_mbr_del(struct pim_instance *pim, const char *mesh_group_name, struct in_addr mbr_ip); enum pim_msdp_err pim_msdp_mg_src_del(struct pim_instance *pim, const char *mesh_group_name); enum pim_msdp_err pim_msdp_mg_src_add(struct pim_instance *pim, const char *mesh_group_name, struct in_addr src_ip); enum pim_msdp_err pim_msdp_mg_del(struct pim_instance *pim, const char *mesh_group_name); #endif frr-7.2.1/pimd/pim_msdp_packet.c0000644000000000000000000004010513610377563013450 00000000000000/* * IP MSDP packet helper * Copyright (C) 2016 Cumulus Networks, Inc. * * This program is free software; you can redistribute it and/or modify * it under the terms of the 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; see the file COPYING; if not, write to the Free Software * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA */ #include #include #include #include #include #include #include #include "pimd.h" #include "pim_str.h" #include "pim_errors.h" #include "pim_msdp.h" #include "pim_msdp_packet.h" #include "pim_msdp_socket.h" static char *pim_msdp_pkt_type_dump(enum pim_msdp_tlv type, char *buf, int buf_size) { switch (type) { case PIM_MSDP_V4_SOURCE_ACTIVE: snprintf(buf, buf_size, "%s", "SA"); break; case PIM_MSDP_V4_SOURCE_ACTIVE_REQUEST: snprintf(buf, buf_size, "%s", "SA_REQ"); break; case PIM_MSDP_V4_SOURCE_ACTIVE_RESPONSE: snprintf(buf, buf_size, "%s", "SA_RESP"); break; case PIM_MSDP_KEEPALIVE: snprintf(buf, buf_size, "%s", "KA"); break; case PIM_MSDP_RESERVED: snprintf(buf, buf_size, "%s", "RSVD"); break; case PIM_MSDP_TRACEROUTE_PROGRESS: snprintf(buf, buf_size, "%s", "TRACE_PROG"); break; case PIM_MSDP_TRACEROUTE_REPLY: snprintf(buf, buf_size, "%s", "TRACE_REPLY"); break; default: snprintf(buf, buf_size, "UNK-%d", type); } return buf; } static void pim_msdp_pkt_sa_dump_one(struct stream *s) { struct prefix_sg sg; /* just throw away the three reserved bytes */ stream_get3(s); /* throw away the prefix length also */ stream_getc(s); memset(&sg, 0, sizeof(struct prefix_sg)); sg.grp.s_addr = stream_get_ipv4(s); sg.src.s_addr = stream_get_ipv4(s); zlog_debug(" sg %s", pim_str_sg_dump(&sg)); } static void pim_msdp_pkt_sa_dump(struct stream *s) { int entry_cnt; int i; struct in_addr rp; /* Last RP address associated with this SA */ entry_cnt = stream_getc(s); rp.s_addr = stream_get_ipv4(s); if (PIM_DEBUG_MSDP_PACKETS) { char rp_str[INET_ADDRSTRLEN]; pim_inet4_dump("", rp, rp_str, sizeof(rp_str)); zlog_debug(" entry_cnt %d rp %s", entry_cnt, rp_str); } /* dump SAs */ for (i = 0; i < entry_cnt; ++i) { pim_msdp_pkt_sa_dump_one(s); } } static void pim_msdp_pkt_dump(struct pim_msdp_peer *mp, int type, int len, bool rx, struct stream *s) { char type_str[PIM_MSDP_PKT_TYPE_STRLEN]; pim_msdp_pkt_type_dump(type, type_str, sizeof(type_str)); zlog_debug("MSDP peer %s pkt %s type %s len %d", mp->key_str, rx ? "rx" : "tx", type_str, len); if (!s) { return; } switch (type) { case PIM_MSDP_V4_SOURCE_ACTIVE: pim_msdp_pkt_sa_dump(s); break; default:; } } /* Check file descriptor whether connect is established. */ static void pim_msdp_connect_check(struct pim_msdp_peer *mp) { int status; socklen_t slen; int ret; if (mp->state != PIM_MSDP_CONNECTING) { /* if we are here it means we are not in a connecting or * established state * for now treat this as a fatal error */ pim_msdp_peer_reset_tcp_conn(mp, "invalid-state"); return; } PIM_MSDP_PEER_READ_OFF(mp); PIM_MSDP_PEER_WRITE_OFF(mp); /* Check file descriptor. */ slen = sizeof(status); ret = getsockopt(mp->fd, SOL_SOCKET, SO_ERROR, (void *)&status, &slen); /* If getsockopt is fail, this is fatal error. */ if (ret < 0) { flog_err_sys(EC_LIB_SOCKET, "can't get sockopt for nonblocking connect"); pim_msdp_peer_reset_tcp_conn(mp, "connect-failed"); return; } /* When status is 0 then TCP connection is established. */ if (PIM_DEBUG_MSDP_INTERNAL) { zlog_debug("MSDP peer %s pim_connect_check %s", mp->key_str, status ? "fail" : "success"); } if (status == 0) { pim_msdp_peer_established(mp); } else { pim_msdp_peer_reset_tcp_conn(mp, "connect-failed"); } } static void pim_msdp_pkt_delete(struct pim_msdp_peer *mp) { stream_free(stream_fifo_pop(mp->obuf)); } static void pim_msdp_pkt_add(struct pim_msdp_peer *mp, struct stream *s) { stream_fifo_push(mp->obuf, s); } static void pim_msdp_write_proceed_actions(struct pim_msdp_peer *mp) { if (stream_fifo_head(mp->obuf)) { PIM_MSDP_PEER_WRITE_ON(mp); } } int pim_msdp_write(struct thread *thread) { struct pim_msdp_peer *mp; struct stream *s; int num; enum pim_msdp_tlv type; int len; int work_cnt = 0; int work_max_cnt = 100; mp = THREAD_ARG(thread); mp->t_write = NULL; if (PIM_DEBUG_MSDP_INTERNAL) { zlog_debug("MSDP peer %s pim_msdp_write", mp->key_str); } if (mp->fd < 0) { return -1; } /* check if TCP connection is established */ if (mp->state != PIM_MSDP_ESTABLISHED) { pim_msdp_connect_check(mp); return 0; } s = stream_fifo_head(mp->obuf); if (!s) { pim_msdp_write_proceed_actions(mp); return 0; } sockopt_cork(mp->fd, 1); /* Nonblocking write until TCP output buffer is full */ do { int writenum; /* Number of bytes to be sent */ writenum = stream_get_endp(s) - stream_get_getp(s); /* Call write() system call */ num = write(mp->fd, stream_pnt(s), writenum); if (num < 0) { /* write failed either retry needed or error */ if (ERRNO_IO_RETRY(errno)) { if (PIM_DEBUG_MSDP_INTERNAL) { zlog_debug( "MSDP peer %s pim_msdp_write io retry", mp->key_str); } break; } pim_msdp_peer_reset_tcp_conn(mp, "pkt-tx-failed"); return 0; } if (num != writenum) { /* Partial write */ stream_forward_getp(s, num); if (PIM_DEBUG_MSDP_INTERNAL) { zlog_debug( "MSDP peer %s pim_msdp_partial_write", mp->key_str); } break; } /* Retrieve msdp packet type. */ stream_set_getp(s, 0); type = stream_getc(s); len = stream_getw(s); switch (type) { case PIM_MSDP_KEEPALIVE: mp->ka_tx_cnt++; break; case PIM_MSDP_V4_SOURCE_ACTIVE: mp->sa_tx_cnt++; break; default:; } if (PIM_DEBUG_MSDP_PACKETS) { pim_msdp_pkt_dump(mp, type, len, false /*rx*/, s); } /* packet sent delete it. */ pim_msdp_pkt_delete(mp); ++work_cnt; /* may need to pause if we have done too much work in this * loop */ if (work_cnt >= work_max_cnt) { break; } } while ((s = stream_fifo_head(mp->obuf)) != NULL); pim_msdp_write_proceed_actions(mp); sockopt_cork(mp->fd, 0); if (PIM_DEBUG_MSDP_INTERNAL) { zlog_debug("MSDP peer %s pim_msdp_write wrote %d packets", mp->key_str, work_cnt); } return 0; } static void pim_msdp_pkt_send(struct pim_msdp_peer *mp, struct stream *s) { /* Add packet to the end of list. */ pim_msdp_pkt_add(mp, s); PIM_MSDP_PEER_WRITE_ON(mp); } void pim_msdp_pkt_ka_tx(struct pim_msdp_peer *mp) { struct stream *s; if (mp->state != PIM_MSDP_ESTABLISHED) { /* don't tx anything unless a session is established */ return; } s = stream_new(PIM_MSDP_KA_TLV_MAX_SIZE); stream_putc(s, PIM_MSDP_KEEPALIVE); stream_putw(s, PIM_MSDP_KA_TLV_MAX_SIZE); pim_msdp_pkt_send(mp, s); } static void pim_msdp_pkt_sa_push_to_one_peer(struct pim_instance *pim, struct pim_msdp_peer *mp) { struct stream *s; if (mp->state != PIM_MSDP_ESTABLISHED) { /* don't tx anything unless a session is established */ return; } s = stream_dup(pim->msdp.work_obuf); if (s) { pim_msdp_pkt_send(mp, s); mp->flags |= PIM_MSDP_PEERF_SA_JUST_SENT; } } /* push the stream into the obuf fifo of all the peers */ static void pim_msdp_pkt_sa_push(struct pim_instance *pim, struct pim_msdp_peer *mp) { struct listnode *mpnode; if (mp) { pim_msdp_pkt_sa_push_to_one_peer(pim, mp); } else { for (ALL_LIST_ELEMENTS_RO(pim->msdp.peer_list, mpnode, mp)) { if (PIM_DEBUG_MSDP_INTERNAL) { zlog_debug("MSDP peer %s pim_msdp_pkt_sa_push", mp->key_str); } pim_msdp_pkt_sa_push_to_one_peer(pim, mp); } } } static int pim_msdp_pkt_sa_fill_hdr(struct pim_instance *pim, int local_cnt) { int curr_tlv_ecnt; stream_reset(pim->msdp.work_obuf); curr_tlv_ecnt = local_cnt > PIM_MSDP_SA_MAX_ENTRY_CNT ? PIM_MSDP_SA_MAX_ENTRY_CNT : local_cnt; local_cnt -= curr_tlv_ecnt; stream_putc(pim->msdp.work_obuf, PIM_MSDP_V4_SOURCE_ACTIVE); stream_putw(pim->msdp.work_obuf, PIM_MSDP_SA_ENTRY_CNT2SIZE(curr_tlv_ecnt)); stream_putc(pim->msdp.work_obuf, curr_tlv_ecnt); stream_put_ipv4(pim->msdp.work_obuf, pim->msdp.originator_id.s_addr); return local_cnt; } static void pim_msdp_pkt_sa_fill_one(struct pim_msdp_sa *sa) { stream_put3(sa->pim->msdp.work_obuf, 0 /* reserved */); stream_putc(sa->pim->msdp.work_obuf, 32 /* sprefix len */); stream_put_ipv4(sa->pim->msdp.work_obuf, sa->sg.grp.s_addr); stream_put_ipv4(sa->pim->msdp.work_obuf, sa->sg.src.s_addr); } static void pim_msdp_pkt_sa_gen(struct pim_instance *pim, struct pim_msdp_peer *mp) { struct listnode *sanode; struct pim_msdp_sa *sa; int sa_count; int local_cnt = pim->msdp.local_cnt; sa_count = 0; if (PIM_DEBUG_MSDP_INTERNAL) { zlog_debug(" sa gen %d", local_cnt); } local_cnt = pim_msdp_pkt_sa_fill_hdr(pim, local_cnt); for (ALL_LIST_ELEMENTS_RO(pim->msdp.sa_list, sanode, sa)) { if (!(sa->flags & PIM_MSDP_SAF_LOCAL)) { /* current implementation of MSDP is for anycast i.e. * full mesh. so * no re-forwarding of SAs that we learnt from other * peers */ continue; } /* add sa into scratch pad */ pim_msdp_pkt_sa_fill_one(sa); ++sa_count; if (sa_count >= PIM_MSDP_SA_MAX_ENTRY_CNT) { pim_msdp_pkt_sa_push(pim, mp); /* reset headers */ sa_count = 0; if (PIM_DEBUG_MSDP_INTERNAL) { zlog_debug(" sa gen for remainder %d", local_cnt); } local_cnt = pim_msdp_pkt_sa_fill_hdr(pim, local_cnt); } } if (sa_count) { pim_msdp_pkt_sa_push(pim, mp); } return; } static void pim_msdp_pkt_sa_tx_done(struct pim_instance *pim) { struct listnode *mpnode; struct pim_msdp_peer *mp; /* if SA were sent to the peers we restart ka timer and avoid * unnecessary ka noise */ for (ALL_LIST_ELEMENTS_RO(pim->msdp.peer_list, mpnode, mp)) { if (mp->flags & PIM_MSDP_PEERF_SA_JUST_SENT) { mp->flags &= ~PIM_MSDP_PEERF_SA_JUST_SENT; pim_msdp_peer_pkt_txed(mp); } } } void pim_msdp_pkt_sa_tx(struct pim_instance *pim) { pim_msdp_pkt_sa_gen(pim, NULL /* mp */); pim_msdp_pkt_sa_tx_done(pim); } void pim_msdp_pkt_sa_tx_one(struct pim_msdp_sa *sa) { pim_msdp_pkt_sa_fill_hdr(sa->pim, 1 /* cnt */); pim_msdp_pkt_sa_fill_one(sa); pim_msdp_pkt_sa_push(sa->pim, NULL); pim_msdp_pkt_sa_tx_done(sa->pim); } /* when a connection is first established we push all SAs immediately */ void pim_msdp_pkt_sa_tx_to_one_peer(struct pim_msdp_peer *mp) { pim_msdp_pkt_sa_gen(mp->pim, mp); pim_msdp_pkt_sa_tx_done(mp->pim); } static void pim_msdp_pkt_rxed_with_fatal_error(struct pim_msdp_peer *mp) { pim_msdp_peer_reset_tcp_conn(mp, "invalid-pkt-rx"); } static void pim_msdp_pkt_ka_rx(struct pim_msdp_peer *mp, int len) { mp->ka_rx_cnt++; if (len != PIM_MSDP_KA_TLV_MAX_SIZE) { pim_msdp_pkt_rxed_with_fatal_error(mp); return; } pim_msdp_peer_pkt_rxed(mp); } static void pim_msdp_pkt_sa_rx_one(struct pim_msdp_peer *mp, struct in_addr rp) { int prefix_len; struct prefix_sg sg; /* just throw away the three reserved bytes */ stream_get3(mp->ibuf); prefix_len = stream_getc(mp->ibuf); memset(&sg, 0, sizeof(struct prefix_sg)); sg.grp.s_addr = stream_get_ipv4(mp->ibuf); sg.src.s_addr = stream_get_ipv4(mp->ibuf); if (prefix_len != 32) { /* ignore SA update if the prefix length is not 32 */ flog_err(EC_PIM_MSDP_PACKET, "rxed sa update with invalid prefix length %d", prefix_len); return; } if (PIM_DEBUG_MSDP_PACKETS) { zlog_debug(" sg %s", pim_str_sg_dump(&sg)); } pim_msdp_sa_ref(mp->pim, mp, &sg, rp); } static void pim_msdp_pkt_sa_rx(struct pim_msdp_peer *mp, int len) { int entry_cnt; int i; struct in_addr rp; /* Last RP address associated with this SA */ mp->sa_rx_cnt++; if (len < PIM_MSDP_SA_TLV_MIN_SIZE) { pim_msdp_pkt_rxed_with_fatal_error(mp); return; } entry_cnt = stream_getc(mp->ibuf); /* some vendors include the actual multicast data in the tlv (at the * end). * we will ignore such data. in the future we may consider pushing it * down * the RPT */ if (len < PIM_MSDP_SA_ENTRY_CNT2SIZE(entry_cnt)) { pim_msdp_pkt_rxed_with_fatal_error(mp); return; } rp.s_addr = stream_get_ipv4(mp->ibuf); if (PIM_DEBUG_MSDP_PACKETS) { char rp_str[INET_ADDRSTRLEN]; pim_inet4_dump("", rp, rp_str, sizeof(rp_str)); zlog_debug(" entry_cnt %d rp %s", entry_cnt, rp_str); } if (!pim_msdp_peer_rpf_check(mp, rp)) { /* if peer-RPF check fails don't process the packet any further */ if (PIM_DEBUG_MSDP_PACKETS) { zlog_debug(" peer RPF check failed"); } return; } pim_msdp_peer_pkt_rxed(mp); /* update SA cache */ for (i = 0; i < entry_cnt; ++i) { pim_msdp_pkt_sa_rx_one(mp, rp); } } static void pim_msdp_pkt_rx(struct pim_msdp_peer *mp) { enum pim_msdp_tlv type; int len; /* re-read type and len */ type = stream_getc_from(mp->ibuf, 0); len = stream_getw_from(mp->ibuf, 1); if (len < PIM_MSDP_HEADER_SIZE) { pim_msdp_pkt_rxed_with_fatal_error(mp); return; } if (len > PIM_MSDP_SA_TLV_MAX_SIZE) { /* if tlv size if greater than max just ignore the tlv */ return; } if (PIM_DEBUG_MSDP_PACKETS) { pim_msdp_pkt_dump(mp, type, len, true /*rx*/, NULL /*s*/); } switch (type) { case PIM_MSDP_KEEPALIVE: pim_msdp_pkt_ka_rx(mp, len); break; case PIM_MSDP_V4_SOURCE_ACTIVE: mp->sa_rx_cnt++; pim_msdp_pkt_sa_rx(mp, len); break; default: mp->unk_rx_cnt++; } } /* pim msdp read utility function. */ static int pim_msdp_read_packet(struct pim_msdp_peer *mp) { int nbytes; int readsize; int old_endp; int new_endp; old_endp = stream_get_endp(mp->ibuf); readsize = mp->packet_size - old_endp; if (!readsize) { return 0; } /* Read packet from fd */ nbytes = stream_read_try(mp->ibuf, mp->fd, readsize); new_endp = stream_get_endp(mp->ibuf); if (nbytes < 0) { if (PIM_DEBUG_MSDP_INTERNAL) { zlog_debug("MSDP peer %s read failed %d", mp->key_str, nbytes); } if (nbytes == -2) { if (PIM_DEBUG_MSDP_INTERNAL) { zlog_debug( "MSDP peer %s pim_msdp_read io retry old_end: %d new_end: %d", mp->key_str, old_endp, new_endp); } /* transient error retry */ return -1; } pim_msdp_pkt_rxed_with_fatal_error(mp); return -1; } if (!nbytes) { if (PIM_DEBUG_MSDP_INTERNAL) { zlog_debug("MSDP peer %s read failed %d", mp->key_str, nbytes); } pim_msdp_peer_reset_tcp_conn(mp, "peer-down"); return -1; } /* We read partial packet. */ if (stream_get_endp(mp->ibuf) != mp->packet_size) { if (PIM_DEBUG_MSDP_INTERNAL) { zlog_debug( "MSDP peer %s read partial len %d old_endp %d new_endp %d", mp->key_str, mp->packet_size, old_endp, new_endp); } return -1; } return 0; } int pim_msdp_read(struct thread *thread) { struct pim_msdp_peer *mp; int rc; uint32_t len; mp = THREAD_ARG(thread); mp->t_read = NULL; if (PIM_DEBUG_MSDP_INTERNAL) { zlog_debug("MSDP peer %s pim_msdp_read", mp->key_str); } if (mp->fd < 0) { return -1; } /* check if TCP connection is established */ if (mp->state != PIM_MSDP_ESTABLISHED) { pim_msdp_connect_check(mp); return 0; } PIM_MSDP_PEER_READ_ON(mp); if (!mp->packet_size) { mp->packet_size = PIM_MSDP_HEADER_SIZE; } if (stream_get_endp(mp->ibuf) < PIM_MSDP_HEADER_SIZE) { /* start by reading the TLV header */ rc = pim_msdp_read_packet(mp); if (rc < 0) { goto pim_msdp_read_end; } /* Find TLV type and len */ stream_getc(mp->ibuf); len = stream_getw(mp->ibuf); if (len < PIM_MSDP_HEADER_SIZE) { pim_msdp_pkt_rxed_with_fatal_error(mp); goto pim_msdp_read_end; } /* read complete TLV */ mp->packet_size = len; } rc = pim_msdp_read_packet(mp); if (rc < 0) { goto pim_msdp_read_end; } pim_msdp_pkt_rx(mp); /* reset input buffers and get ready for the next packet */ mp->packet_size = 0; stream_reset(mp->ibuf); pim_msdp_read_end: return 0; } frr-7.2.1/pimd/pim_msdp_packet.h0000644000000000000000000000615413610377563013463 00000000000000/* * IP MSDP packet helpers * Copyright (C) 2016 Cumulus Networks, Inc. * * This program is free software; you can redistribute it and/or modify * it under the terms of the 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; see the file COPYING; if not, write to the Free Software * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA */ #ifndef PIM_MSDP_PACKET_H #define PIM_MSDP_PACKET_H /* type and length of a single tlv can be consider packet header */ #define PIM_MSDP_HEADER_SIZE 3 /* Keepalive TLV 0 1 2 3 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ | 4 | 3 | +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ */ #define PIM_MSDP_KA_TLV_MAX_SIZE PIM_MSDP_HEADER_SIZE /* Source-Active TLV (x=8, y=12xEntryCount) 0 1 2 3 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ | 1 | x + y | Entry Count | +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ | RP Address | +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ | Reserved | Sprefix Len | \ +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ \ | Group Address | ) z +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ / | Source Address | / +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ */ #define PIM_MSDP_SA_TLV_MAX_SIZE 9192 #define PIM_MSDP_SA_X_SIZE 8 #define PIM_MSDP_SA_ONE_ENTRY_SIZE 12 #define PIM_MSDP_SA_Y_SIZE(entry_cnt) (PIM_MSDP_SA_ONE_ENTRY_SIZE * entry_cnt) #define PIM_MSDP_SA_ENTRY_CNT2SIZE(entry_cnt) \ (PIM_MSDP_SA_X_SIZE + PIM_MSDP_SA_Y_SIZE(entry_cnt)) /* SA TLV has to have atleast only one entry in it so x=8 + y=12 */ #define PIM_MSDP_SA_TLV_MIN_SIZE PIM_MSDP_SA_ENTRY_CNT2SIZE(1) /* XXX: theoretically we can fix a max of 255 but that may result in packet * fragmentation */ #define PIM_MSDP_SA_MAX_ENTRY_CNT 120 #define PIM_MSDP_MAX_PACKET_SIZE max(PIM_MSDP_SA_TLV_MAX_SIZE, PIM_MSDP_KA_TLV_MAX_SIZE) #define PIM_MSDP_PKT_TYPE_STRLEN 16 void pim_msdp_pkt_ka_tx(struct pim_msdp_peer *mp); int pim_msdp_read(struct thread *thread); void pim_msdp_pkt_sa_tx(struct pim_instance *pim); void pim_msdp_pkt_sa_tx_one(struct pim_msdp_sa *sa); void pim_msdp_pkt_sa_tx_to_one_peer(struct pim_msdp_peer *mp); #endif frr-7.2.1/pimd/pim_msdp_socket.c0000644000000000000000000001620013610377563013470 00000000000000/* * IP MSDP socket management * Copyright (C) 2016 Cumulus Networks, Inc. * * This program is free software; you can redistribute it and/or modify * it under the terms of the 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; see the file COPYING; if not, write to the Free Software * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA */ #include #include #include #include #include #include #include #include #include #include "pimd.h" #include "pim_sock.h" #include "pim_errors.h" #include "pim_msdp.h" #include "pim_msdp_socket.h" /* increase socket send buffer size */ static void pim_msdp_update_sock_send_buffer_size(int fd) { int size = PIM_MSDP_SOCKET_SNDBUF_SIZE; int optval; socklen_t optlen = sizeof(optval); if (getsockopt(fd, SOL_SOCKET, SO_SNDBUF, &optval, &optlen) < 0) { flog_err_sys(EC_LIB_SOCKET, "getsockopt of SO_SNDBUF failed %s\n", safe_strerror(errno)); return; } if (optval < size) { if (setsockopt(fd, SOL_SOCKET, SO_SNDBUF, &size, sizeof(size)) < 0) { flog_err_sys(EC_LIB_SOCKET, "Couldn't increase send buffer: %s\n", safe_strerror(errno)); } } } /* passive peer socket accept */ static int pim_msdp_sock_accept(struct thread *thread) { union sockunion su; struct pim_instance *pim = THREAD_ARG(thread); int accept_sock; int msdp_sock; struct pim_msdp_peer *mp; char buf[SU_ADDRSTRLEN]; sockunion_init(&su); /* re-register accept thread */ accept_sock = THREAD_FD(thread); if (accept_sock < 0) { flog_err(EC_LIB_DEVELOPMENT, "accept_sock is negative value %d", accept_sock); return -1; } pim->msdp.listener.thread = NULL; thread_add_read(router->master, pim_msdp_sock_accept, pim, accept_sock, &pim->msdp.listener.thread); /* accept client connection. */ msdp_sock = sockunion_accept(accept_sock, &su); if (msdp_sock < 0) { flog_err_sys(EC_LIB_SOCKET, "pim_msdp_sock_accept failed (%s)", safe_strerror(errno)); return -1; } /* see if have peer config for this */ mp = pim_msdp_peer_find(pim, su.sin.sin_addr); if (!mp || !PIM_MSDP_PEER_IS_LISTENER(mp)) { ++pim->msdp.rejected_accepts; if (PIM_DEBUG_MSDP_EVENTS) { flog_err(EC_PIM_MSDP_PACKET, "msdp peer connection refused from %s", sockunion2str(&su, buf, SU_ADDRSTRLEN)); } close(msdp_sock); return -1; } if (PIM_DEBUG_MSDP_INTERNAL) { zlog_debug("MSDP peer %s accept success%s", mp->key_str, mp->fd >= 0 ? "(dup)" : ""); } /* if we have an existing connection we need to kill that one * with this one */ if (mp->fd >= 0) { if (PIM_DEBUG_MSDP_EVENTS) { zlog_notice( "msdp peer new connection from %s stop old connection", sockunion2str(&su, buf, SU_ADDRSTRLEN)); } pim_msdp_peer_stop_tcp_conn(mp, true /* chg_state */); } mp->fd = msdp_sock; set_nonblocking(mp->fd); pim_msdp_update_sock_send_buffer_size(mp->fd); pim_msdp_peer_established(mp); return 0; } /* global listener for the MSDP well know TCP port */ int pim_msdp_sock_listen(struct pim_instance *pim) { int sock; int socklen; struct sockaddr_in sin; int rc; struct pim_msdp_listener *listener = &pim->msdp.listener; if (pim->msdp.flags & PIM_MSDPF_LISTENER) { /* listener already setup */ return 0; } sock = socket(AF_INET, SOCK_STREAM, 0); if (sock < 0) { flog_err_sys(EC_LIB_SOCKET, "socket: %s", safe_strerror(errno)); return sock; } memset(&sin, 0, sizeof(struct sockaddr_in)); sin.sin_family = AF_INET; sin.sin_port = htons(PIM_MSDP_TCP_PORT); socklen = sizeof(struct sockaddr_in); #ifdef HAVE_STRUCT_SOCKADDR_IN_SIN_LEN sin.sin_len = socklen; #endif /* HAVE_STRUCT_SOCKADDR_IN_SIN_LEN */ sockopt_reuseaddr(sock); sockopt_reuseport(sock); if (pim->vrf_id != VRF_DEFAULT) { struct interface *ifp = if_lookup_by_name(pim->vrf->name, pim->vrf_id); if (!ifp) { flog_err(EC_LIB_INTERFACE, "%s: Unable to lookup vrf interface: %s", __PRETTY_FUNCTION__, pim->vrf->name); close(sock); return -1; } if (pim_socket_bind(sock, ifp)) { flog_err_sys(EC_LIB_SOCKET, "%s: Unable to bind to socket: %s", __PRETTY_FUNCTION__, safe_strerror(errno)); close(sock); return -1; } } frr_with_privs(&pimd_privs) { /* bind to well known TCP port */ rc = bind(sock, (struct sockaddr *)&sin, socklen); } if (rc < 0) { flog_err_sys(EC_LIB_SOCKET, "pim_msdp_socket bind to port %d: %s", ntohs(sin.sin_port), safe_strerror(errno)); close(sock); return rc; } rc = listen(sock, 3 /* backlog */); if (rc < 0) { flog_err_sys(EC_LIB_SOCKET, "pim_msdp_socket listen: %s", safe_strerror(errno)); close(sock); return rc; } /* add accept thread */ listener->fd = sock; memcpy(&listener->su, &sin, socklen); listener->thread = NULL; thread_add_read(pim->msdp.master, pim_msdp_sock_accept, pim, sock, &listener->thread); pim->msdp.flags |= PIM_MSDPF_LISTENER; return 0; } /* active peer socket setup */ int pim_msdp_sock_connect(struct pim_msdp_peer *mp) { int rc; if (PIM_DEBUG_MSDP_INTERNAL) { zlog_debug("MSDP peer %s attempt connect%s", mp->key_str, mp->fd < 0 ? "" : "(dup)"); } /* if we have an existing connection we need to kill that one * with this one */ if (mp->fd >= 0) { if (PIM_DEBUG_MSDP_EVENTS) { zlog_notice( "msdp duplicate connect to %s nuke old connection", mp->key_str); } pim_msdp_peer_stop_tcp_conn(mp, false /* chg_state */); } /* Make socket for the peer. */ mp->fd = sockunion_socket(&mp->su_peer); if (mp->fd < 0) { flog_err_sys(EC_LIB_SOCKET, "pim_msdp_socket socket failure: %s", safe_strerror(errno)); return -1; } if (mp->pim->vrf_id != VRF_DEFAULT) { struct interface *ifp = if_lookup_by_name(mp->pim->vrf->name, mp->pim->vrf_id); if (!ifp) { flog_err(EC_LIB_INTERFACE, "%s: Unable to lookup vrf interface: %s", __PRETTY_FUNCTION__, mp->pim->vrf->name); return -1; } if (pim_socket_bind(mp->fd, ifp)) { flog_err_sys(EC_LIB_SOCKET, "%s: Unable to bind to socket: %s", __PRETTY_FUNCTION__, safe_strerror(errno)); close(mp->fd); mp->fd = -1; return -1; } } set_nonblocking(mp->fd); /* Set socket send buffer size */ pim_msdp_update_sock_send_buffer_size(mp->fd); sockopt_reuseaddr(mp->fd); sockopt_reuseport(mp->fd); /* source bind */ rc = sockunion_bind(mp->fd, &mp->su_local, 0, &mp->su_local); if (rc < 0) { flog_err_sys(EC_LIB_SOCKET, "pim_msdp_socket connect bind failure: %s", safe_strerror(errno)); close(mp->fd); mp->fd = -1; return rc; } /* Connect to the remote mp. */ return (sockunion_connect(mp->fd, &mp->su_peer, htons(PIM_MSDP_TCP_PORT), 0)); } frr-7.2.1/pimd/pim_msdp_socket.h0000644000000000000000000000174213610377563013502 00000000000000/* * IP MSDP socket management for Quagga * Copyright (C) 2016 Cumulus Networks, Inc. * * This program is free software; you can redistribute it and/or modify * it under the terms of the 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; see the file COPYING; if not, write to the Free Software * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA */ #ifndef PIM_MSDP_SOCKET_H #define PIM_MSDP_SOCKET_H int pim_msdp_sock_listen(struct pim_instance *pim); int pim_msdp_sock_connect(struct pim_msdp_peer *mp); #endif frr-7.2.1/pimd/pim_msg.c0000644000000000000000000001554113610377563011752 00000000000000/* * PIM for Quagga * Copyright (C) 2008 Everton da Silva Marques * * This program is free software; you can redistribute it and/or modify * it under the terms of the 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; see the file COPYING; if not, write to the Free Software * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA */ #include #include "if.h" #include "log.h" #include "prefix.h" #include "vty.h" #include "plist.h" #include "pimd.h" #include "pim_vty.h" #include "pim_pim.h" #include "pim_msg.h" #include "pim_util.h" #include "pim_str.h" #include "pim_iface.h" #include "pim_rp.h" #include "pim_rpf.h" #include "pim_register.h" #include "pim_jp_agg.h" #include "pim_oil.h" void pim_msg_build_header(uint8_t *pim_msg, size_t pim_msg_size, uint8_t pim_msg_type, bool no_fwd) { struct pim_msg_header *header = (struct pim_msg_header *)pim_msg; /* * Write header */ header->ver = PIM_PROTO_VERSION; header->type = pim_msg_type; header->Nbit = no_fwd; header->reserved = 0; header->checksum = 0; /* * The checksum for Registers is done only on the first 8 bytes of the * packet, * including the PIM header and the next 4 bytes, excluding the data * packet portion */ if (pim_msg_type == PIM_MSG_TYPE_REGISTER) header->checksum = in_cksum(pim_msg, PIM_MSG_REGISTER_LEN); else header->checksum = in_cksum(pim_msg, pim_msg_size); } uint8_t *pim_msg_addr_encode_ipv4_ucast(uint8_t *buf, struct in_addr addr) { buf[0] = PIM_MSG_ADDRESS_FAMILY_IPV4; /* addr family */ buf[1] = '\0'; /* native encoding */ memcpy(buf + 2, &addr, sizeof(struct in_addr)); return buf + PIM_ENCODED_IPV4_UCAST_SIZE; } uint8_t *pim_msg_addr_encode_ipv4_group(uint8_t *buf, struct in_addr addr) { buf[0] = PIM_MSG_ADDRESS_FAMILY_IPV4; /* addr family */ buf[1] = '\0'; /* native encoding */ buf[2] = '\0'; /* reserved */ buf[3] = 32; /* mask len */ memcpy(buf + 4, &addr, sizeof(struct in_addr)); return buf + PIM_ENCODED_IPV4_GROUP_SIZE; } uint8_t *pim_msg_addr_encode_ipv4_source(uint8_t *buf, struct in_addr addr, uint8_t bits) { buf[0] = PIM_MSG_ADDRESS_FAMILY_IPV4; /* addr family */ buf[1] = '\0'; /* native encoding */ buf[2] = bits; buf[3] = 32; /* mask len */ memcpy(buf + 4, &addr, sizeof(struct in_addr)); return buf + PIM_ENCODED_IPV4_SOURCE_SIZE; } /* * For the given 'struct pim_jp_sources' list * determine the size_t it would take up. */ size_t pim_msg_get_jp_group_size(struct list *sources) { struct pim_jp_sources *js; size_t size = 0; if (!sources) return 0; size += sizeof(struct pim_encoded_group_ipv4); size += 4; // Joined sources (2) + Pruned Sources (2) size += sizeof(struct pim_encoded_source_ipv4) * sources->count; js = listgetdata(listhead(sources)); if (js && js->up->sg.src.s_addr == INADDR_ANY && js->is_join) { struct pim_upstream *child, *up; struct listnode *up_node; up = js->up; if (PIM_DEBUG_PIM_PACKETS) zlog_debug( "%s: Considering (%s) children for (S,G,rpt) prune", __PRETTY_FUNCTION__, up->sg_str); for (ALL_LIST_ELEMENTS_RO(up->sources, up_node, child)) { if (child->sptbit == PIM_UPSTREAM_SPTBIT_TRUE) { if (!pim_rpf_is_same(&up->rpf, &child->rpf)) { size += sizeof( struct pim_encoded_source_ipv4); PIM_UPSTREAM_FLAG_SET_SEND_SG_RPT_PRUNE( child->flags); if (PIM_DEBUG_PIM_PACKETS) zlog_debug( "%s: SPT Bit and RPF'(%s) != RPF'(S,G): Add Prune (%s,rpt) to compound message", __PRETTY_FUNCTION__, up->sg_str, child->sg_str); } else if (PIM_DEBUG_PIM_PACKETS) zlog_debug( "%s: SPT Bit and RPF'(%s) == RPF'(S,G): Not adding Prune for (%s,rpt)", __PRETTY_FUNCTION__, up->sg_str, child->sg_str); } else if (pim_upstream_is_sg_rpt(child)) { if (pim_upstream_empty_inherited_olist(child)) { size += sizeof( struct pim_encoded_source_ipv4); PIM_UPSTREAM_FLAG_SET_SEND_SG_RPT_PRUNE( child->flags); if (PIM_DEBUG_PIM_PACKETS) zlog_debug( "%s: inherited_olist(%s,rpt) is NULL, Add Prune to compound message", __PRETTY_FUNCTION__, child->sg_str); } else if (!pim_rpf_is_same(&up->rpf, &child->rpf)) { size += sizeof( struct pim_encoded_source_ipv4); PIM_UPSTREAM_FLAG_SET_SEND_SG_RPT_PRUNE( child->flags); if (PIM_DEBUG_PIM_PACKETS) zlog_debug( "%s: RPF'(%s) != RPF'(%s,rpt), Add Prune to compound message", __PRETTY_FUNCTION__, up->sg_str, child->sg_str); } else if (PIM_DEBUG_PIM_PACKETS) zlog_debug( "%s: RPF'(%s) == RPF'(%s,rpt), Do not add Prune to compound message", __PRETTY_FUNCTION__, up->sg_str, child->sg_str); } else if (PIM_DEBUG_PIM_PACKETS) zlog_debug("%s: SPT bit is not set for (%s)", __PRETTY_FUNCTION__, child->sg_str); } } return size; } size_t pim_msg_build_jp_groups(struct pim_jp_groups *grp, struct pim_jp_agg_group *sgs, size_t size) { struct listnode *node, *nnode; struct pim_jp_sources *source; struct pim_upstream *up = NULL; struct in_addr stosend; uint8_t bits; uint8_t tgroups = 0; memset(grp, 0, size); pim_msg_addr_encode_ipv4_group((uint8_t *)&grp->g, sgs->group); for (ALL_LIST_ELEMENTS(sgs->sources, node, nnode, source)) { /* number of joined/pruned sources */ if (source->is_join) grp->joins++; else grp->prunes++; if (source->up->sg.src.s_addr == INADDR_ANY) { struct pim_instance *pim = source->up->channel_oil->pim; struct pim_rpf *rpf = pim_rp_g(pim, source->up->sg.grp); bits = PIM_ENCODE_SPARSE_BIT | PIM_ENCODE_WC_BIT | PIM_ENCODE_RPT_BIT; stosend = rpf->rpf_addr.u.prefix4; /* Only Send SGRpt in case of *,G Join */ if (source->is_join) up = source->up; } else { bits = PIM_ENCODE_SPARSE_BIT; stosend = source->up->sg.src; } pim_msg_addr_encode_ipv4_source((uint8_t *)&grp->s[tgroups], stosend, bits); tgroups++; } if (up) { struct pim_upstream *child; for (ALL_LIST_ELEMENTS(up->sources, node, nnode, child)) { if (PIM_UPSTREAM_FLAG_TEST_SEND_SG_RPT_PRUNE( child->flags)) { pim_msg_addr_encode_ipv4_source( (uint8_t *)&grp->s[tgroups], child->sg.src, PIM_ENCODE_SPARSE_BIT | PIM_ENCODE_RPT_BIT); tgroups++; PIM_UPSTREAM_FLAG_UNSET_SEND_SG_RPT_PRUNE( child->flags); grp->prunes++; } } } grp->joins = htons(grp->joins); grp->prunes = htons(grp->prunes); return size; } frr-7.2.1/pimd/pim_msg.h0000644000000000000000000001150513610377563011753 00000000000000/* * PIM for Quagga * Copyright (C) 2008 Everton da Silva Marques * * This program is free software; you can redistribute it and/or modify * it under the terms of the 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; see the file COPYING; if not, write to the Free Software * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA */ #ifndef PIM_MSG_H #define PIM_MSG_H #include #include "pim_jp_agg.h" #define PIM_HDR_LEN sizeof(struct pim_msg_header) /* Number Description ---------- ------------------ 0 Reserved 1 IP (IP version 4) 2 IP6 (IP version 6) From: http://www.iana.org/assignments/address-family-numbers */ enum pim_msg_address_family { PIM_MSG_ADDRESS_FAMILY_RESERVED, PIM_MSG_ADDRESS_FAMILY_IPV4, PIM_MSG_ADDRESS_FAMILY_IPV6, }; /* * pim_msg_hdr * ========================= * PIM Header definition as per RFC 5059. N bit introduced to indicate * do-not-forward option in PIM Boot strap Message. * 0 1 2 3 * 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 * +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ * |PIM Ver| Type |N| Reserved | Checksum | * +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ */ struct pim_msg_header { #if (BYTE_ORDER == LITTLE_ENDIAN) uint8_t type : 4; uint8_t ver : 4; uint8_t reserved : 7; uint8_t Nbit : 1; /* No Fwd Bit */ #elif (BYTE_ORDER == BIG_ENDIAN) uint8_t ver : 4; uint8_t type : 4; uint8_t Nbit : 1; /* No Fwd Bit */ uint8_t reserved : 7; #else #error"Please set byte order" #endif uint16_t checksum; } __attribute__((packed)); struct pim_encoded_ipv4_unicast { uint8_t family; uint8_t reserved; struct in_addr addr; } __attribute__((packed)); /* * Encoded Group format. RFC 4601 Sec 4.9.1 * 0 1 2 3 * 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 * +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ * | Addr Family | Encoding Type |B| Reserved |Z| Mask Len | * +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ * | Group multicast Address * +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+... */ struct pim_encoded_group_ipv4 { uint8_t family; uint8_t ne; #if (BYTE_ORDER == LITTLE_ENDIAN) uint8_t sz : 1; /* scope zone bit */ uint8_t reserved : 6; /* Reserved */ uint8_t bidir : 1; /* Bidir bit */ #elif (BYTE_ORDER == BIG_ENDIAN) uint8_t bidir : 1; /* Bidir bit */ uint8_t reserved : 6; /* Reserved */ uint8_t sz : 1; /* scope zone bit */ #else #error"Please set byte order" #endif uint8_t mask; struct in_addr addr; } __attribute__((packed)); /* * Encoded Source format. RFC 4601 Sec 4.9.1 * 0 1 2 3 * 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 * +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ * | Addr Family | Encoding Type | Rsrvd |S|W|R| Mask Len | * +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ * | Source Address * +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-... */ struct pim_encoded_source_ipv4 { uint8_t family; uint8_t ne; uint8_t bits; uint8_t mask; struct in_addr addr; } __attribute__((packed)); struct pim_jp_groups { struct pim_encoded_group_ipv4 g; uint16_t joins; uint16_t prunes; struct pim_encoded_source_ipv4 s[1]; } __attribute__((packed)); struct pim_jp { struct pim_msg_header header; struct pim_encoded_ipv4_unicast addr; uint8_t reserved; uint8_t num_groups; uint16_t holdtime; struct pim_jp_groups groups[1]; } __attribute__((packed)); void pim_msg_build_header(uint8_t *pim_msg, size_t pim_msg_size, uint8_t pim_msg_type, bool no_fwd); uint8_t *pim_msg_addr_encode_ipv4_ucast(uint8_t *buf, struct in_addr addr); uint8_t *pim_msg_addr_encode_ipv4_group(uint8_t *buf, struct in_addr addr); #define PIM_ENCODE_SPARSE_BIT 0x04 #define PIM_ENCODE_WC_BIT 0x02 #define PIM_ENCODE_RPT_BIT 0x01 uint8_t *pim_msg_addr_encode_ipv4_source(uint8_t *buf, struct in_addr addr, uint8_t bits); size_t pim_msg_get_jp_group_size(struct list *sources); size_t pim_msg_build_jp_groups(struct pim_jp_groups *grp, struct pim_jp_agg_group *sgs, size_t size); #endif /* PIM_MSG_H */ frr-7.2.1/pimd/pim_neighbor.c0000644000000000000000000005350613610377563012764 00000000000000/* * PIM for Quagga * Copyright (C) 2008 Everton da Silva Marques * * This program is free software; you can redistribute it and/or modify * it under the terms of the 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; see the file COPYING; if not, write to the Free Software * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA */ #include #include "log.h" #include "prefix.h" #include "memory.h" #include "if.h" #include "vty.h" #include "plist.h" #include "lib_errors.h" #include "pimd.h" #include "pim_neighbor.h" #include "pim_time.h" #include "pim_str.h" #include "pim_iface.h" #include "pim_pim.h" #include "pim_upstream.h" #include "pim_ifchannel.h" #include "pim_rp.h" #include "pim_zebra.h" #include "pim_join.h" #include "pim_jp_agg.h" #include "pim_bfd.h" static void dr_election_by_addr(struct interface *ifp) { struct pim_interface *pim_ifp; struct listnode *node; struct pim_neighbor *neigh; pim_ifp = ifp->info; zassert(pim_ifp); pim_ifp->pim_dr_addr = pim_ifp->primary_address; if (PIM_DEBUG_PIM_TRACE) { zlog_debug("%s: on interface %s", __PRETTY_FUNCTION__, ifp->name); } for (ALL_LIST_ELEMENTS_RO(pim_ifp->pim_neighbor_list, node, neigh)) { if (ntohl(neigh->source_addr.s_addr) > ntohl(pim_ifp->pim_dr_addr.s_addr)) { pim_ifp->pim_dr_addr = neigh->source_addr; } } } static void dr_election_by_pri(struct interface *ifp) { struct pim_interface *pim_ifp; struct listnode *node; struct pim_neighbor *neigh; uint32_t dr_pri; pim_ifp = ifp->info; zassert(pim_ifp); pim_ifp->pim_dr_addr = pim_ifp->primary_address; dr_pri = pim_ifp->pim_dr_priority; if (PIM_DEBUG_PIM_TRACE) { zlog_debug("%s: dr pri %u on interface %s", __PRETTY_FUNCTION__, dr_pri, ifp->name); } for (ALL_LIST_ELEMENTS_RO(pim_ifp->pim_neighbor_list, node, neigh)) { if (PIM_DEBUG_PIM_TRACE) { zlog_info("%s: neigh pri %u addr %x if dr addr %x", __PRETTY_FUNCTION__, neigh->dr_priority, ntohl(neigh->source_addr.s_addr), ntohl(pim_ifp->pim_dr_addr.s_addr)); } if ((neigh->dr_priority > dr_pri) || ((neigh->dr_priority == dr_pri) && (ntohl(neigh->source_addr.s_addr) > ntohl(pim_ifp->pim_dr_addr.s_addr)))) { pim_ifp->pim_dr_addr = neigh->source_addr; dr_pri = neigh->dr_priority; } } } /* RFC 4601: 4.3.2. DR Election A router's idea of the current DR on an interface can change when a PIM Hello message is received, when a neighbor times out, or when a router's own DR Priority changes. */ int pim_if_dr_election(struct interface *ifp) { struct pim_interface *pim_ifp = ifp->info; struct in_addr old_dr_addr; ++pim_ifp->pim_dr_election_count; old_dr_addr = pim_ifp->pim_dr_addr; if (pim_ifp->pim_dr_num_nondrpri_neighbors) { dr_election_by_addr(ifp); } else { dr_election_by_pri(ifp); } /* DR changed ? */ if (old_dr_addr.s_addr != pim_ifp->pim_dr_addr.s_addr) { if (PIM_DEBUG_PIM_EVENTS) { char dr_old_str[INET_ADDRSTRLEN]; char dr_new_str[INET_ADDRSTRLEN]; pim_inet4_dump("", old_dr_addr, dr_old_str, sizeof(dr_old_str)); pim_inet4_dump("", pim_ifp->pim_dr_addr, dr_new_str, sizeof(dr_new_str)); zlog_debug("%s: DR was %s now is %s on interface %s", __PRETTY_FUNCTION__, dr_old_str, dr_new_str, ifp->name); } pim_ifp->pim_dr_election_last = pim_time_monotonic_sec(); /* timestamp */ ++pim_ifp->pim_dr_election_changes; pim_if_update_join_desired(pim_ifp); pim_if_update_could_assert(ifp); pim_if_update_assert_tracking_desired(ifp); return 1; } return 0; } static void update_dr_priority(struct pim_neighbor *neigh, pim_hello_options hello_options, uint32_t dr_priority) { pim_hello_options will_set_pri; /* boolean */ pim_hello_options bit_flip; /* boolean */ pim_hello_options pri_change; /* boolean */ will_set_pri = PIM_OPTION_IS_SET(hello_options, PIM_OPTION_MASK_DR_PRIORITY); bit_flip = (will_set_pri != PIM_OPTION_IS_SET(neigh->hello_options, PIM_OPTION_MASK_DR_PRIORITY)); if (bit_flip) { struct pim_interface *pim_ifp = neigh->interface->info; /* update num. of neighbors without dr_pri */ if (will_set_pri) { --pim_ifp->pim_dr_num_nondrpri_neighbors; } else { ++pim_ifp->pim_dr_num_nondrpri_neighbors; } } pri_change = (bit_flip || (neigh->dr_priority != dr_priority)); if (will_set_pri) { neigh->dr_priority = dr_priority; } else { neigh->dr_priority = 0; /* cosmetic unset */ } if (pri_change) { /* RFC 4601: 4.3.2. DR Election A router's idea of the current DR on an interface can change when a PIM Hello message is received, when a neighbor times out, or when a router's own DR Priority changes. */ pim_if_dr_election( neigh->interface); // router's own DR Priority changes } } static int on_neighbor_timer(struct thread *t) { struct pim_neighbor *neigh; struct interface *ifp; char msg[100]; neigh = THREAD_ARG(t); ifp = neigh->interface; if (PIM_DEBUG_PIM_TRACE) { char src_str[INET_ADDRSTRLEN]; pim_inet4_dump("", neigh->source_addr, src_str, sizeof(src_str)); zlog_debug( "Expired %d sec holdtime for neighbor %s on interface %s", neigh->holdtime, src_str, ifp->name); } snprintf(msg, sizeof(msg), "%d-sec holdtime expired", neigh->holdtime); pim_neighbor_delete(ifp, neigh, msg); /* RFC 4601: 4.3.2. DR Election A router's idea of the current DR on an interface can change when a PIM Hello message is received, when a neighbor times out, or when a router's own DR Priority changes. */ pim_if_dr_election(ifp); // neighbor times out return 0; } void pim_neighbor_timer_reset(struct pim_neighbor *neigh, uint16_t holdtime) { neigh->holdtime = holdtime; THREAD_OFF(neigh->t_expire_timer); /* 0xFFFF is request for no holdtime */ if (neigh->holdtime == 0xFFFF) { return; } if (PIM_DEBUG_PIM_TRACE_DETAIL) { char src_str[INET_ADDRSTRLEN]; pim_inet4_dump("", neigh->source_addr, src_str, sizeof(src_str)); zlog_debug("%s: starting %u sec timer for neighbor %s on %s", __PRETTY_FUNCTION__, neigh->holdtime, src_str, neigh->interface->name); } thread_add_timer(router->master, on_neighbor_timer, neigh, neigh->holdtime, &neigh->t_expire_timer); } static int on_neighbor_jp_timer(struct thread *t) { struct pim_neighbor *neigh = THREAD_ARG(t); struct pim_rpf rpf; if (PIM_DEBUG_PIM_TRACE) { char src_str[INET_ADDRSTRLEN]; pim_inet4_dump("", neigh->source_addr, src_str, sizeof(src_str)); zlog_debug("%s:Sending JP Agg to %s on %s with %d groups", __PRETTY_FUNCTION__, src_str, neigh->interface->name, neigh->upstream_jp_agg->count); } rpf.source_nexthop.interface = neigh->interface; rpf.rpf_addr.u.prefix4 = neigh->source_addr; pim_joinprune_send(&rpf, neigh->upstream_jp_agg); thread_add_timer(router->master, on_neighbor_jp_timer, neigh, router->t_periodic, &neigh->jp_timer); return 0; } static void pim_neighbor_start_jp_timer(struct pim_neighbor *neigh) { THREAD_TIMER_OFF(neigh->jp_timer); thread_add_timer(router->master, on_neighbor_jp_timer, neigh, router->t_periodic, &neigh->jp_timer); } static struct pim_neighbor * pim_neighbor_new(struct interface *ifp, struct in_addr source_addr, pim_hello_options hello_options, uint16_t holdtime, uint16_t propagation_delay, uint16_t override_interval, uint32_t dr_priority, uint32_t generation_id, struct list *addr_list) { struct pim_interface *pim_ifp; struct pim_neighbor *neigh; char src_str[INET_ADDRSTRLEN]; zassert(ifp); pim_ifp = ifp->info; zassert(pim_ifp); neigh = XCALLOC(MTYPE_PIM_NEIGHBOR, sizeof(*neigh)); neigh->creation = pim_time_monotonic_sec(); neigh->source_addr = source_addr; neigh->hello_options = hello_options; neigh->propagation_delay_msec = propagation_delay; neigh->override_interval_msec = override_interval; neigh->dr_priority = dr_priority; neigh->generation_id = generation_id; neigh->prefix_list = addr_list; neigh->t_expire_timer = NULL; neigh->interface = ifp; neigh->upstream_jp_agg = list_new(); neigh->upstream_jp_agg->cmp = pim_jp_agg_group_list_cmp; neigh->upstream_jp_agg->del = (void (*)(void *))pim_jp_agg_group_list_free; pim_neighbor_start_jp_timer(neigh); pim_neighbor_timer_reset(neigh, holdtime); /* * The pim_ifstat_hello_sent variable is used to decide if * we should expedite a hello out the interface. If we * establish a new neighbor, we unfortunately need to * reset the value so that we can know to hurry up and * hello */ pim_ifp->pim_ifstat_hello_sent = 0; pim_inet4_dump("", source_addr, src_str, sizeof(src_str)); if (PIM_DEBUG_PIM_EVENTS) { zlog_debug("%s: creating PIM neighbor %s on interface %s", __PRETTY_FUNCTION__, src_str, ifp->name); } zlog_info("PIM NEIGHBOR UP: neighbor %s on interface %s", src_str, ifp->name); if (neigh->propagation_delay_msec > pim_ifp->pim_neighbors_highest_propagation_delay_msec) { pim_ifp->pim_neighbors_highest_propagation_delay_msec = neigh->propagation_delay_msec; } if (neigh->override_interval_msec > pim_ifp->pim_neighbors_highest_override_interval_msec) { pim_ifp->pim_neighbors_highest_override_interval_msec = neigh->override_interval_msec; } if (!PIM_OPTION_IS_SET(neigh->hello_options, PIM_OPTION_MASK_LAN_PRUNE_DELAY)) { /* update num. of neighbors without hello option lan_delay */ ++pim_ifp->pim_number_of_nonlandelay_neighbors; } if (!PIM_OPTION_IS_SET(neigh->hello_options, PIM_OPTION_MASK_DR_PRIORITY)) { /* update num. of neighbors without hello option dr_pri */ ++pim_ifp->pim_dr_num_nondrpri_neighbors; } // Register PIM Neighbor with BFD pim_bfd_trigger_event(pim_ifp, neigh, 1); return neigh; } static void delete_prefix_list(struct pim_neighbor *neigh) { if (neigh->prefix_list) { #ifdef DUMP_PREFIX_LIST struct listnode *p_node; struct prefix *p; char addr_str[10]; int list_size = neigh->prefix_list ? (int)listcount(neigh->prefix_list) : -1; int i = 0; for (ALL_LIST_ELEMENTS_RO(neigh->prefix_list, p_node, p)) { pim_inet4_dump("", p->u.prefix4, addr_str, sizeof(addr_str)); zlog_debug( "%s: DUMP_PREFIX_LIST neigh=%x prefix_list=%x prefix=%x addr=%s [%d/%d]", __PRETTY_FUNCTION__, (unsigned)neigh, (unsigned)neigh->prefix_list, (unsigned)p, addr_str, i, list_size); ++i; } #endif list_delete(&neigh->prefix_list); } } void pim_neighbor_free(struct pim_neighbor *neigh) { zassert(!neigh->t_expire_timer); delete_prefix_list(neigh); list_delete(&neigh->upstream_jp_agg); THREAD_OFF(neigh->jp_timer); if (neigh->bfd_info) pim_bfd_info_free(&neigh->bfd_info); XFREE(MTYPE_PIM_NEIGHBOR, neigh); } struct pim_neighbor *pim_neighbor_find_by_secondary(struct interface *ifp, struct prefix *src) { struct pim_interface *pim_ifp; struct listnode *node, *pnode; struct pim_neighbor *neigh; struct prefix *p; if (!ifp || !ifp->info) return NULL; pim_ifp = ifp->info; for (ALL_LIST_ELEMENTS_RO(pim_ifp->pim_neighbor_list, node, neigh)) { for (ALL_LIST_ELEMENTS_RO(neigh->prefix_list, pnode, p)) { if (prefix_same(p, src)) return neigh; } } return NULL; } struct pim_neighbor *pim_neighbor_find(struct interface *ifp, struct in_addr source_addr) { struct pim_interface *pim_ifp; struct listnode *node; struct pim_neighbor *neigh; if (!ifp) return NULL; pim_ifp = ifp->info; if (!pim_ifp) return NULL; for (ALL_LIST_ELEMENTS_RO(pim_ifp->pim_neighbor_list, node, neigh)) { if (source_addr.s_addr == neigh->source_addr.s_addr) { return neigh; } } return NULL; } /* * Find the *one* interface out * this interface. If more than * one return NULL */ struct pim_neighbor *pim_neighbor_find_if(struct interface *ifp) { struct pim_interface *pim_ifp = ifp->info; if (!pim_ifp || pim_ifp->pim_neighbor_list->count != 1) return NULL; return listnode_head(pim_ifp->pim_neighbor_list); } struct pim_neighbor * pim_neighbor_add(struct interface *ifp, struct in_addr source_addr, pim_hello_options hello_options, uint16_t holdtime, uint16_t propagation_delay, uint16_t override_interval, uint32_t dr_priority, uint32_t generation_id, struct list *addr_list, int send_hello_now) { struct pim_interface *pim_ifp; struct pim_neighbor *neigh; neigh = pim_neighbor_new(ifp, source_addr, hello_options, holdtime, propagation_delay, override_interval, dr_priority, generation_id, addr_list); if (!neigh) { return 0; } pim_ifp = ifp->info; zassert(pim_ifp); listnode_add(pim_ifp->pim_neighbor_list, neigh); if (PIM_DEBUG_PIM_TRACE_DETAIL) { char str[INET_ADDRSTRLEN]; pim_inet4_dump("", source_addr, str, sizeof(str)); zlog_debug("%s: neighbor %s added ", __PRETTY_FUNCTION__, str); } /* RFC 4601: 4.3.2. DR Election A router's idea of the current DR on an interface can change when a PIM Hello message is received, when a neighbor times out, or when a router's own DR Priority changes. */ pim_if_dr_election(neigh->interface); // new neighbor -- should not // trigger dr election... /* RFC 4601: 4.3.1. Sending Hello Messages To allow new or rebooting routers to learn of PIM neighbors quickly, when a Hello message is received from a new neighbor, or a Hello message with a new GenID is received from an existing neighbor, a new Hello message should be sent on this interface after a randomized delay between 0 and Triggered_Hello_Delay. This is a bit silly to do it that way. If I get a new genid we need to send the hello *now* because we've lined up a bunch of join/prune messages to go out the interface. */ if (send_hello_now) pim_hello_restart_now(ifp); else pim_hello_restart_triggered(neigh->interface); pim_upstream_find_new_rpf(pim_ifp->pim); /* RNH can send nexthop update prior to PIM neibhor UP in that case nexthop cache would not consider this neighbor as RPF. Upon PIM neighbor UP, iterate all RPs and update nexthop cache with this neighbor. */ pim_resolve_rp_nh(pim_ifp->pim, neigh); pim_rp_setup(pim_ifp->pim); sched_rpf_cache_refresh(pim_ifp->pim); return neigh; } static uint16_t find_neighbors_next_highest_propagation_delay_msec( struct interface *ifp, struct pim_neighbor *highest_neigh) { struct pim_interface *pim_ifp; struct listnode *neigh_node; struct pim_neighbor *neigh; uint16_t next_highest_delay_msec; pim_ifp = ifp->info; zassert(pim_ifp); next_highest_delay_msec = pim_ifp->pim_propagation_delay_msec; for (ALL_LIST_ELEMENTS_RO(pim_ifp->pim_neighbor_list, neigh_node, neigh)) { if (neigh == highest_neigh) continue; if (neigh->propagation_delay_msec > next_highest_delay_msec) next_highest_delay_msec = neigh->propagation_delay_msec; } return next_highest_delay_msec; } static uint16_t find_neighbors_next_highest_override_interval_msec( struct interface *ifp, struct pim_neighbor *highest_neigh) { struct pim_interface *pim_ifp; struct listnode *neigh_node; struct pim_neighbor *neigh; uint16_t next_highest_interval_msec; pim_ifp = ifp->info; zassert(pim_ifp); next_highest_interval_msec = pim_ifp->pim_override_interval_msec; for (ALL_LIST_ELEMENTS_RO(pim_ifp->pim_neighbor_list, neigh_node, neigh)) { if (neigh == highest_neigh) continue; if (neigh->override_interval_msec > next_highest_interval_msec) next_highest_interval_msec = neigh->override_interval_msec; } return next_highest_interval_msec; } void pim_neighbor_delete(struct interface *ifp, struct pim_neighbor *neigh, const char *delete_message) { struct pim_interface *pim_ifp; char src_str[INET_ADDRSTRLEN]; pim_ifp = ifp->info; zassert(pim_ifp); pim_inet4_dump("", neigh->source_addr, src_str, sizeof(src_str)); zlog_info("PIM NEIGHBOR DOWN: neighbor %s on interface %s: %s", src_str, ifp->name, delete_message); THREAD_OFF(neigh->t_expire_timer); pim_if_assert_on_neighbor_down(ifp, neigh->source_addr); if (!PIM_OPTION_IS_SET(neigh->hello_options, PIM_OPTION_MASK_LAN_PRUNE_DELAY)) { /* update num. of neighbors without hello option lan_delay */ --pim_ifp->pim_number_of_nonlandelay_neighbors; } if (!PIM_OPTION_IS_SET(neigh->hello_options, PIM_OPTION_MASK_DR_PRIORITY)) { /* update num. of neighbors without dr_pri */ --pim_ifp->pim_dr_num_nondrpri_neighbors; } zassert(neigh->propagation_delay_msec <= pim_ifp->pim_neighbors_highest_propagation_delay_msec); zassert(neigh->override_interval_msec <= pim_ifp->pim_neighbors_highest_override_interval_msec); if (pim_if_lan_delay_enabled(ifp)) { /* will delete a neighbor with highest propagation delay? */ if (neigh->propagation_delay_msec == pim_ifp->pim_neighbors_highest_propagation_delay_msec) { /* then find the next highest propagation delay */ pim_ifp->pim_neighbors_highest_propagation_delay_msec = find_neighbors_next_highest_propagation_delay_msec( ifp, neigh); } /* will delete a neighbor with highest override interval? */ if (neigh->override_interval_msec == pim_ifp->pim_neighbors_highest_override_interval_msec) { /* then find the next highest propagation delay */ pim_ifp->pim_neighbors_highest_override_interval_msec = find_neighbors_next_highest_override_interval_msec( ifp, neigh); } } if (PIM_DEBUG_PIM_TRACE) { zlog_debug("%s: deleting PIM neighbor %s on interface %s", __PRETTY_FUNCTION__, src_str, ifp->name); } // De-Register PIM Neighbor with BFD pim_bfd_trigger_event(pim_ifp, neigh, 0); listnode_delete(pim_ifp->pim_neighbor_list, neigh); pim_neighbor_free(neigh); sched_rpf_cache_refresh(pim_ifp->pim); } void pim_neighbor_delete_all(struct interface *ifp, const char *delete_message) { struct pim_interface *pim_ifp; struct listnode *neigh_node; struct listnode *neigh_nextnode; struct pim_neighbor *neigh; pim_ifp = ifp->info; zassert(pim_ifp); for (ALL_LIST_ELEMENTS(pim_ifp->pim_neighbor_list, neigh_node, neigh_nextnode, neigh)) { pim_neighbor_delete(ifp, neigh, delete_message); } } struct prefix *pim_neighbor_find_secondary(struct pim_neighbor *neigh, struct prefix *addr) { struct listnode *node; struct prefix *p; if (!neigh->prefix_list) return 0; for (ALL_LIST_ELEMENTS_RO(neigh->prefix_list, node, p)) { if (prefix_same(p, addr)) return p; } return NULL; } /* RFC 4601: 4.3.4. Maintaining Secondary Address Lists All the advertised secondary addresses in received Hello messages must be checked against those previously advertised by all other PIM neighbors on that interface. If there is a conflict and the same secondary address was previously advertised by another neighbor, then only the most recently received mapping MUST be maintained, and an error message SHOULD be logged to the administrator in a rate-limited manner. */ static void delete_from_neigh_addr(struct interface *ifp, struct list *addr_list, struct in_addr neigh_addr) { struct listnode *addr_node; struct prefix *addr; struct pim_interface *pim_ifp; pim_ifp = ifp->info; zassert(pim_ifp); zassert(addr_list); /* Scan secondary address list */ for (ALL_LIST_ELEMENTS_RO(addr_list, addr_node, addr)) { struct listnode *neigh_node; struct pim_neighbor *neigh; if (addr->family != AF_INET) continue; /* Scan neighbors */ for (ALL_LIST_ELEMENTS_RO(pim_ifp->pim_neighbor_list, neigh_node, neigh)) { { struct prefix *p = pim_neighbor_find_secondary( neigh, addr); if (p) { char addr_str[INET_ADDRSTRLEN]; char this_neigh_str[INET_ADDRSTRLEN]; char other_neigh_str[INET_ADDRSTRLEN]; pim_inet4_dump( "", addr->u.prefix4, addr_str, sizeof(addr_str)); pim_inet4_dump("", neigh_addr, this_neigh_str, sizeof(this_neigh_str)); pim_inet4_dump("", neigh->source_addr, other_neigh_str, sizeof(other_neigh_str)); zlog_info( "secondary addr %s recvd from neigh %s deleted from neigh %s on %s", addr_str, this_neigh_str, other_neigh_str, ifp->name); listnode_delete(neigh->prefix_list, p); prefix_free(p); } } } /* scan neighbors */ } /* scan addr list */ } void pim_neighbor_update(struct pim_neighbor *neigh, pim_hello_options hello_options, uint16_t holdtime, uint32_t dr_priority, struct list *addr_list) { struct pim_interface *pim_ifp = neigh->interface->info; /* Received holdtime ? */ if (PIM_OPTION_IS_SET(hello_options, PIM_OPTION_MASK_HOLDTIME)) { pim_neighbor_timer_reset(neigh, holdtime); } else { pim_neighbor_timer_reset(neigh, PIM_IF_DEFAULT_HOLDTIME(pim_ifp)); } #ifdef DUMP_PREFIX_LIST zlog_debug( "%s: DUMP_PREFIX_LIST old_prefix_list=%x old_size=%d new_prefix_list=%x new_size=%d", __PRETTY_FUNCTION__, (unsigned)neigh->prefix_list, neigh->prefix_list ? (int)listcount(neigh->prefix_list) : -1, (unsigned)addr_list, addr_list ? (int)listcount(addr_list) : -1); #endif if (neigh->prefix_list == addr_list) { if (addr_list) { flog_err( EC_LIB_DEVELOPMENT, "%s: internal error: trying to replace same prefix list=%p", __PRETTY_FUNCTION__, (void *)addr_list); } } else { /* Delete existing secondary address list */ delete_prefix_list(neigh); } if (addr_list) { delete_from_neigh_addr(neigh->interface, addr_list, neigh->source_addr); } /* Replace secondary address list */ neigh->prefix_list = addr_list; update_dr_priority(neigh, hello_options, dr_priority); /* Copy flags */ neigh->hello_options = hello_options; } frr-7.2.1/pimd/pim_neighbor.h0000644000000000000000000000526013610377563012763 00000000000000/* * PIM for Quagga * Copyright (C) 2008 Everton da Silva Marques * * This program is free software; you can redistribute it and/or modify * it under the terms of the 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; see the file COPYING; if not, write to the Free Software * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA */ #ifndef PIM_NEIGHBOR_H #define PIM_NEIGHBOR_H #include #include "if.h" #include "linklist.h" #include "prefix.h" #include "pim_tlv.h" struct pim_neighbor { int64_t creation; /* timestamp of creation */ struct in_addr source_addr; pim_hello_options hello_options; uint16_t holdtime; uint16_t propagation_delay_msec; uint16_t override_interval_msec; uint32_t dr_priority; uint32_t generation_id; struct list *prefix_list; /* list of struct prefix */ struct thread *t_expire_timer; struct interface *interface; struct thread *jp_timer; struct list *upstream_jp_agg; struct bfd_info *bfd_info; }; void pim_neighbor_timer_reset(struct pim_neighbor *neigh, uint16_t holdtime); void pim_neighbor_free(struct pim_neighbor *neigh); struct pim_neighbor *pim_neighbor_find(struct interface *ifp, struct in_addr source_addr); struct pim_neighbor *pim_neighbor_find_by_secondary(struct interface *ifp, struct prefix *src); struct pim_neighbor *pim_neighbor_find_if(struct interface *ifp); #define PIM_NEIGHBOR_SEND_DELAY 0 #define PIM_NEIGHBOR_SEND_NOW 1 struct pim_neighbor * pim_neighbor_add(struct interface *ifp, struct in_addr source_addr, pim_hello_options hello_options, uint16_t holdtime, uint16_t propagation_delay, uint16_t override_interval, uint32_t dr_priority, uint32_t generation_id, struct list *addr_list, int send_hello_now); void pim_neighbor_delete(struct interface *ifp, struct pim_neighbor *neigh, const char *delete_message); void pim_neighbor_delete_all(struct interface *ifp, const char *delete_message); void pim_neighbor_update(struct pim_neighbor *neigh, pim_hello_options hello_options, uint16_t holdtime, uint32_t dr_priority, struct list *addr_list); struct prefix *pim_neighbor_find_secondary(struct pim_neighbor *neigh, struct prefix *addr); int pim_if_dr_election(struct interface *ifp); #endif /* PIM_NEIGHBOR_H */ frr-7.2.1/pimd/pim_nht.c0000644000000000000000000007324713610377563011764 00000000000000/* * PIM for Quagga * Copyright (C) 2017 Cumulus Networks, Inc. * Chirag Shah * * This program is free software; you can redistribute it and/or modify * it under the terms of the 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; see the file COPYING; if not, write to the Free Software * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA */ #include #include "network.h" #include "zclient.h" #include "stream.h" #include "nexthop.h" #include "if.h" #include "hash.h" #include "jhash.h" #include "pimd.h" #include "pimd/pim_nht.h" #include "log.h" #include "pim_time.h" #include "pim_oil.h" #include "pim_ifchannel.h" #include "pim_mroute.h" #include "pim_zebra.h" #include "pim_upstream.h" #include "pim_join.h" #include "pim_jp_agg.h" #include "pim_zebra.h" #include "pim_zlookup.h" #include "pim_rp.h" /** * pim_sendmsg_zebra_rnh -- Format and send a nexthop register/Unregister * command to Zebra. */ void pim_sendmsg_zebra_rnh(struct pim_instance *pim, struct zclient *zclient, struct pim_nexthop_cache *pnc, int command) { struct prefix *p; int ret; p = &(pnc->rpf.rpf_addr); ret = zclient_send_rnh(zclient, command, p, false, pim->vrf_id); if (ret < 0) zlog_warn("sendmsg_nexthop: zclient_send_message() failed"); if (PIM_DEBUG_PIM_NHT) { char buf[PREFIX2STR_BUFFER]; prefix2str(p, buf, sizeof(buf)); zlog_debug( "%s: NHT %sregistered addr %s(%s) with Zebra ret:%d ", __PRETTY_FUNCTION__, (command == ZEBRA_NEXTHOP_REGISTER) ? " " : "de", buf, pim->vrf->name, ret); } return; } struct pim_nexthop_cache *pim_nexthop_cache_find(struct pim_instance *pim, struct pim_rpf *rpf) { struct pim_nexthop_cache *pnc = NULL; struct pim_nexthop_cache lookup; lookup.rpf.rpf_addr.family = rpf->rpf_addr.family; lookup.rpf.rpf_addr.prefixlen = rpf->rpf_addr.prefixlen; lookup.rpf.rpf_addr.u.prefix4.s_addr = rpf->rpf_addr.u.prefix4.s_addr; pnc = hash_lookup(pim->rpf_hash, &lookup); return pnc; } static struct pim_nexthop_cache *pim_nexthop_cache_add(struct pim_instance *pim, struct pim_rpf *rpf_addr) { struct pim_nexthop_cache *pnc; char hash_name[64]; char buf1[64]; pnc = XCALLOC(MTYPE_PIM_NEXTHOP_CACHE, sizeof(struct pim_nexthop_cache)); pnc->rpf.rpf_addr.family = rpf_addr->rpf_addr.family; pnc->rpf.rpf_addr.prefixlen = rpf_addr->rpf_addr.prefixlen; pnc->rpf.rpf_addr.u.prefix4.s_addr = rpf_addr->rpf_addr.u.prefix4.s_addr; pnc = hash_get(pim->rpf_hash, pnc, hash_alloc_intern); pnc->rp_list = list_new(); pnc->rp_list->cmp = pim_rp_list_cmp; snprintf(hash_name, 64, "PNC %s(%s) Upstream Hash", prefix2str(&pnc->rpf.rpf_addr, buf1, 64), pim->vrf->name); pnc->upstream_hash = hash_create_size(8192, pim_upstream_hash_key, pim_upstream_equal, hash_name); return pnc; } /* * pim_find_or_track_nexthop * * This API is used to Register an address with Zebra * * 1 -> Success * 0 -> Failure */ int pim_find_or_track_nexthop(struct pim_instance *pim, struct prefix *addr, struct pim_upstream *up, struct rp_info *rp, bool bsr_track_needed, struct pim_nexthop_cache *out_pnc) { struct pim_nexthop_cache *pnc = NULL; struct pim_rpf rpf; struct listnode *ch_node = NULL; struct zclient *zclient = NULL; zclient = pim_zebra_zclient_get(); memset(&rpf, 0, sizeof(struct pim_rpf)); rpf.rpf_addr.family = addr->family; rpf.rpf_addr.prefixlen = addr->prefixlen; rpf.rpf_addr.u.prefix4 = addr->u.prefix4; pnc = pim_nexthop_cache_find(pim, &rpf); if (!pnc) { pnc = pim_nexthop_cache_add(pim, &rpf); pim_sendmsg_zebra_rnh(pim, zclient, pnc, ZEBRA_NEXTHOP_REGISTER); if (PIM_DEBUG_PIM_NHT) { char buf[PREFIX2STR_BUFFER]; prefix2str(addr, buf, sizeof(buf)); zlog_debug( "%s: NHT cache and zebra notification added for %s(%s)", __PRETTY_FUNCTION__, buf, pim->vrf->name); } } if (rp != NULL) { ch_node = listnode_lookup(pnc->rp_list, rp); if (ch_node == NULL) listnode_add_sort(pnc->rp_list, rp); } if (up != NULL) hash_get(pnc->upstream_hash, up, hash_alloc_intern); if (bsr_track_needed) pnc->bsr_tracking = true; if (CHECK_FLAG(pnc->flags, PIM_NEXTHOP_VALID)) { if (out_pnc) memcpy(out_pnc, pnc, sizeof(struct pim_nexthop_cache)); return 1; } return 0; } void pim_delete_tracked_nexthop(struct pim_instance *pim, struct prefix *addr, struct pim_upstream *up, struct rp_info *rp, bool del_bsr_tracking) { struct pim_nexthop_cache *pnc = NULL; struct pim_nexthop_cache lookup; struct zclient *zclient = NULL; struct listnode *upnode = NULL; struct pim_upstream *upstream = NULL; zclient = pim_zebra_zclient_get(); /* Remove from RPF hash if it is the last entry */ lookup.rpf.rpf_addr = *addr; pnc = hash_lookup(pim->rpf_hash, &lookup); if (pnc) { if (rp) { /* Release the (*, G)upstream from pnc->upstream_hash, * whose Group belongs to the RP getting deleted */ for (ALL_LIST_ELEMENTS_RO(pim->upstream_list, upnode, upstream)) { struct prefix grp; struct rp_info *trp_info; if (upstream->sg.src.s_addr != INADDR_ANY) continue; grp.family = AF_INET; grp.prefixlen = IPV4_MAX_BITLEN; grp.u.prefix4 = upstream->sg.grp; trp_info = pim_rp_find_match_group(pim, &grp); if (trp_info == rp) hash_release(pnc->upstream_hash, upstream); } listnode_delete(pnc->rp_list, rp); } if (up) hash_release(pnc->upstream_hash, up); if (del_bsr_tracking) pnc->bsr_tracking = false; if (PIM_DEBUG_PIM_NHT) { char buf[PREFIX_STRLEN]; prefix2str(addr, buf, sizeof buf); zlog_debug( "%s: NHT %s(%s) rp_list count:%d upstream count:%ld", __PRETTY_FUNCTION__, buf, pim->vrf->name, pnc->rp_list->count, pnc->upstream_hash->count); } if (pnc->rp_list->count == 0 && pnc->upstream_hash->count == 0 && pnc->bsr_tracking == false) { pim_sendmsg_zebra_rnh(pim, zclient, pnc, ZEBRA_NEXTHOP_UNREGISTER); list_delete(&pnc->rp_list); hash_free(pnc->upstream_hash); hash_release(pim->rpf_hash, pnc); if (pnc->nexthop) nexthops_free(pnc->nexthop); XFREE(MTYPE_PIM_NEXTHOP_CACHE, pnc); } } } /* Given a source address and a neighbor address, check if the neighbor is one * of the next hop to reach the source. search from zebra route database */ bool pim_nexthop_match(struct pim_instance *pim, struct in_addr addr, struct in_addr ip_src) { struct pim_zlookup_nexthop nexthop_tab[MULTIPATH_NUM]; int i = 0; ifindex_t first_ifindex = 0; struct interface *ifp = NULL; struct pim_neighbor *nbr = NULL; int num_ifindex; if (addr.s_addr == INADDR_NONE) return 0; memset(nexthop_tab, 0, sizeof(struct pim_zlookup_nexthop) * MULTIPATH_NUM); num_ifindex = zclient_lookup_nexthop(pim, nexthop_tab, MULTIPATH_NUM, addr, PIM_NEXTHOP_LOOKUP_MAX); if (num_ifindex < 1) { char addr_str[INET_ADDRSTRLEN]; pim_inet4_dump("", addr, addr_str, sizeof(addr_str)); zlog_warn( "%s %s: could not find nexthop ifindex for address %s", __FILE__, __PRETTY_FUNCTION__, addr_str); return 0; } while (i < num_ifindex) { first_ifindex = nexthop_tab[i].ifindex; ifp = if_lookup_by_index(first_ifindex, pim->vrf_id); if (!ifp) { if (PIM_DEBUG_ZEBRA) { char addr_str[INET_ADDRSTRLEN]; pim_inet4_dump("", addr, addr_str, sizeof(addr_str)); zlog_debug( "%s %s: could not find interface for ifindex %d (address %s)", __FILE__, __PRETTY_FUNCTION__, first_ifindex, addr_str); } i++; continue; } if (!ifp->info) { if (PIM_DEBUG_ZEBRA) { char addr_str[INET_ADDRSTRLEN]; pim_inet4_dump("", addr, addr_str, sizeof(addr_str)); zlog_debug( "%s: multicast not enabled on input interface %s (ifindex=%d, RPF for source %s)", __PRETTY_FUNCTION__, ifp->name, first_ifindex, addr_str); } i++; continue; } if (!pim_if_connected_to_source(ifp, addr)) { nbr = pim_neighbor_find( ifp, nexthop_tab[i].nexthop_addr.u.prefix4); if (PIM_DEBUG_PIM_TRACE_DETAIL) zlog_debug("ifp name: %s, pim nbr: %p", ifp->name, nbr); if (!nbr && !if_is_loopback(ifp)) { i++; continue; } } if (nexthop_tab[i].nexthop_addr.u.prefix4.s_addr == ip_src.s_addr) return 1; i++; } return 0; } /* Given a source address and a neighbor address, check if the neighbor is one * of the next hop to reach the source. search from pim next hop cache */ bool pim_nexthop_match_nht_cache(struct pim_instance *pim, struct in_addr addr, struct in_addr ip_src) { struct pim_rpf rpf; ifindex_t first_ifindex; struct interface *ifp = NULL; uint8_t nh_iter = 0; struct pim_neighbor *nbr = NULL; struct nexthop *nh_node = NULL; struct pim_nexthop_cache *pnc = NULL; memset(&rpf, 0, sizeof(struct pim_rpf)); rpf.rpf_addr.family = AF_INET; rpf.rpf_addr.prefixlen = IPV4_MAX_BITLEN; rpf.rpf_addr.u.prefix4 = addr; pnc = pim_nexthop_cache_find(pim, &rpf); if (!pnc || !pnc->nexthop_num) return 0; for (nh_node = pnc->nexthop; nh_node; nh_node = nh_node->next) { first_ifindex = nh_node->ifindex; ifp = if_lookup_by_index(first_ifindex, pim->vrf_id); if (!ifp) { if (PIM_DEBUG_PIM_NHT) { char addr_str[INET_ADDRSTRLEN]; pim_inet4_dump("", addr, addr_str, sizeof(addr_str)); zlog_debug( "%s %s: could not find interface for ifindex %d (address %s(%s))", __FILE__, __PRETTY_FUNCTION__, first_ifindex, addr_str, pim->vrf->name); } nh_iter++; continue; } if (!ifp->info) { if (PIM_DEBUG_PIM_NHT) { char addr_str[INET_ADDRSTRLEN]; pim_inet4_dump("", addr, addr_str, sizeof(addr_str)); zlog_debug( "%s: multicast not enabled on input interface %s(%s) (ifindex=%d, RPF for source %s)", __PRETTY_FUNCTION__, ifp->name, pim->vrf->name, first_ifindex, addr_str); } nh_iter++; continue; } if (!pim_if_connected_to_source(ifp, addr)) { nbr = pim_neighbor_find(ifp, nh_node->gate.ipv4); if (!nbr && !if_is_loopback(ifp)) { if (PIM_DEBUG_PIM_NHT) zlog_debug( "%s: pim nbr not found on input interface %s(%s)", __PRETTY_FUNCTION__, ifp->name, pim->vrf->name); nh_iter++; continue; } } if (nh_node->gate.ipv4.s_addr == ip_src.s_addr) return 1; } return 0; } void pim_rp_nexthop_del(struct rp_info *rp_info) { rp_info->rp.source_nexthop.interface = NULL; rp_info->rp.source_nexthop.mrib_nexthop_addr.u.prefix4.s_addr = PIM_NET_INADDR_ANY; rp_info->rp.source_nexthop.mrib_metric_preference = router->infinite_assert_metric.metric_preference; rp_info->rp.source_nexthop.mrib_route_metric = router->infinite_assert_metric.route_metric; } /* Update RP nexthop info based on Nexthop update received from Zebra.*/ static void pim_update_rp_nh(struct pim_instance *pim, struct pim_nexthop_cache *pnc) { struct listnode *node = NULL; struct rp_info *rp_info = NULL; /*Traverse RP list and update each RP Nexthop info */ for (ALL_LIST_ELEMENTS_RO(pnc->rp_list, node, rp_info)) { if (rp_info->rp.rpf_addr.u.prefix4.s_addr == INADDR_NONE) continue; // Compute PIM RPF using cached nexthop if (!pim_ecmp_nexthop_lookup(pim, &rp_info->rp.source_nexthop, &rp_info->rp.rpf_addr, &rp_info->group, 1)) pim_rp_nexthop_del(rp_info); } } /* Update Upstream nexthop info based on Nexthop update received from Zebra.*/ static int pim_update_upstream_nh_helper(struct hash_bucket *bucket, void *arg) { struct pim_instance *pim = (struct pim_instance *)arg; struct pim_upstream *up = (struct pim_upstream *)bucket->data; int vif_index = 0; enum pim_rpf_result rpf_result; struct pim_rpf old; old.source_nexthop.interface = up->rpf.source_nexthop.interface; rpf_result = pim_rpf_update(pim, up, &old); if (rpf_result == PIM_RPF_FAILURE) { pim_upstream_rpf_clear(pim, up); return HASHWALK_CONTINUE; } /* update kernel multicast forwarding cache (MFC) */ if (up->rpf.source_nexthop.interface) { ifindex_t ifindex = up->rpf.source_nexthop.interface->ifindex; vif_index = pim_if_find_vifindex_by_ifindex(pim, ifindex); /* Pass Current selected NH vif index to mroute download */ if (vif_index) pim_scan_individual_oil(up->channel_oil, vif_index); else { if (PIM_DEBUG_PIM_NHT) zlog_debug( "%s: NHT upstream %s channel_oil IIF %s vif_index is not valid", __PRETTY_FUNCTION__, up->sg_str, up->rpf.source_nexthop.interface->name); } } if (rpf_result == PIM_RPF_CHANGED) pim_zebra_upstream_rpf_changed(pim, up, &old); if (PIM_DEBUG_PIM_NHT) { zlog_debug("%s: NHT upstream %s(%s) old ifp %s new ifp %s", __PRETTY_FUNCTION__, up->sg_str, pim->vrf->name, old.source_nexthop.interface ? old.source_nexthop.interface->name : "Unknown", up->rpf.source_nexthop.interface->name); } return HASHWALK_CONTINUE; } static int pim_update_upstream_nh(struct pim_instance *pim, struct pim_nexthop_cache *pnc) { hash_walk(pnc->upstream_hash, pim_update_upstream_nh_helper, pim); pim_zebra_update_all_interfaces(pim); return 0; } uint32_t pim_compute_ecmp_hash(struct prefix *src, struct prefix *grp) { uint32_t hash_val; uint32_t s = 0, g = 0; if ((!src)) return 0; switch (src->family) { case AF_INET: { s = src->u.prefix4.s_addr; s = s == 0 ? 1 : s; if (grp) g = grp->u.prefix4.s_addr; } break; default: break; } hash_val = jhash_2words(g, s, 101); return hash_val; } static int pim_ecmp_nexthop_search(struct pim_instance *pim, struct pim_nexthop_cache *pnc, struct pim_nexthop *nexthop, struct prefix *src, struct prefix *grp, int neighbor_needed) { struct pim_neighbor *nbrs[MULTIPATH_NUM], *nbr = NULL; struct interface *ifps[MULTIPATH_NUM]; struct nexthop *nh_node = NULL; ifindex_t first_ifindex; struct interface *ifp = NULL; uint32_t hash_val = 0, mod_val = 0; uint8_t nh_iter = 0, found = 0; uint32_t i, num_nbrs = 0; if (!pnc || !pnc->nexthop_num || !nexthop) return 0; memset(&nbrs, 0, sizeof(nbrs)); memset(&ifps, 0, sizeof(ifps)); // Current Nexthop is VALID, check to stay on the current path. if (nexthop->interface && nexthop->interface->info && nexthop->mrib_nexthop_addr.u.prefix4.s_addr != PIM_NET_INADDR_ANY) { /* User configured knob to explicitly switch to new path is disabled or current path metric is less than nexthop update. */ if (pim->ecmp_rebalance_enable == 0) { uint8_t curr_route_valid = 0; // Check if current nexthop is present in new updated // Nexthop list. // If the current nexthop is not valid, candidate to // choose new Nexthop. for (nh_node = pnc->nexthop; nh_node; nh_node = nh_node->next) { curr_route_valid = (nexthop->interface->ifindex == nh_node->ifindex); if (curr_route_valid) break; } if (curr_route_valid && !pim_if_connected_to_source(nexthop->interface, src->u.prefix4)) { nbr = pim_neighbor_find( nexthop->interface, nexthop->mrib_nexthop_addr.u.prefix4); if (!nbr && !if_is_loopback(nexthop->interface)) { if (PIM_DEBUG_PIM_NHT) zlog_debug( "%s: current nexthop does not have nbr ", __PRETTY_FUNCTION__); } else { if (PIM_DEBUG_PIM_NHT) { char src_str[INET_ADDRSTRLEN]; pim_inet4_dump("", src->u.prefix4, src_str, sizeof(src_str)); char grp_str[INET_ADDRSTRLEN]; pim_inet4_dump("", grp->u.prefix4, grp_str, sizeof(grp_str)); zlog_debug( "%s: (%s,%s)(%s) current nexthop %s is valid, skipping new path selection", __PRETTY_FUNCTION__, src_str, grp_str, pim->vrf->name, nexthop->interface->name); } return 1; } } } } /* * Look up all interfaces and neighbors, * store for later usage */ for (nh_node = pnc->nexthop, i = 0; nh_node; nh_node = nh_node->next, i++) { ifps[i] = if_lookup_by_index(nh_node->ifindex, pim->vrf_id); if (ifps[i]) { nbrs[i] = pim_neighbor_find(ifps[i], nh_node->gate.ipv4); if (nbrs[i] || pim_if_connected_to_source(ifps[i], src->u.prefix4)) num_nbrs++; } } if (pim->ecmp_enable) { uint32_t consider = pnc->nexthop_num; if (neighbor_needed && num_nbrs < consider) consider = num_nbrs; if (consider == 0) return 0; // PIM ECMP flag is enable then choose ECMP path. hash_val = pim_compute_ecmp_hash(src, grp); mod_val = hash_val % consider; } for (nh_node = pnc->nexthop; nh_node && (found == 0); nh_node = nh_node->next) { first_ifindex = nh_node->ifindex; ifp = ifps[nh_iter]; if (!ifp) { if (PIM_DEBUG_PIM_NHT) { char addr_str[INET_ADDRSTRLEN]; pim_inet4_dump("", src->u.prefix4, addr_str, sizeof(addr_str)); zlog_debug( "%s %s: could not find interface for ifindex %d (address %s(%s))", __FILE__, __PRETTY_FUNCTION__, first_ifindex, addr_str, pim->vrf->name); } if (nh_iter == mod_val) mod_val++; // Select nexthpath nh_iter++; continue; } if (!ifp->info) { if (PIM_DEBUG_PIM_NHT) { char addr_str[INET_ADDRSTRLEN]; pim_inet4_dump("", src->u.prefix4, addr_str, sizeof(addr_str)); zlog_debug( "%s: multicast not enabled on input interface %s(%s) (ifindex=%d, RPF for source %s)", __PRETTY_FUNCTION__, ifp->name, pim->vrf->name, first_ifindex, addr_str); } if (nh_iter == mod_val) mod_val++; // Select nexthpath nh_iter++; continue; } if (neighbor_needed && !pim_if_connected_to_source(ifp, src->u.prefix4)) { nbr = nbrs[nh_iter]; if (!nbr && !if_is_loopback(ifp)) { if (PIM_DEBUG_PIM_NHT) zlog_debug( "%s: pim nbr not found on input interface %s(%s)", __PRETTY_FUNCTION__, ifp->name, pim->vrf->name); if (nh_iter == mod_val) mod_val++; // Select nexthpath nh_iter++; continue; } } if (nh_iter == mod_val) { nexthop->interface = ifp; nexthop->mrib_nexthop_addr.family = AF_INET; nexthop->mrib_nexthop_addr.prefixlen = IPV4_MAX_BITLEN; nexthop->mrib_nexthop_addr.u.prefix4 = nh_node->gate.ipv4; nexthop->mrib_metric_preference = pnc->distance; nexthop->mrib_route_metric = pnc->metric; nexthop->last_lookup = src->u.prefix4; nexthop->last_lookup_time = pim_time_monotonic_usec(); nexthop->nbr = nbr; found = 1; if (PIM_DEBUG_PIM_NHT) { char buf[INET_ADDRSTRLEN]; char buf2[INET_ADDRSTRLEN]; char buf3[INET_ADDRSTRLEN]; pim_inet4_dump("", src->u.prefix4, buf2, sizeof(buf2)); pim_inet4_dump("", grp->u.prefix4, buf3, sizeof(buf3)); pim_inet4_dump( "", nexthop->mrib_nexthop_addr.u.prefix4, buf, sizeof(buf)); zlog_debug( "%s: (%s,%s)(%s) selected nhop interface %s addr %s mod_val %u iter %d ecmp %d", __PRETTY_FUNCTION__, buf2, buf3, pim->vrf->name, ifp->name, buf, mod_val, nh_iter, pim->ecmp_enable); } } nh_iter++; } if (found) return 1; else return 0; } /* This API is used to parse Registered address nexthop update coming from Zebra */ int pim_parse_nexthop_update(ZAPI_CALLBACK_ARGS) { struct nexthop *nexthop; struct nexthop *nhlist_head = NULL; struct nexthop *nhlist_tail = NULL; int i; struct pim_rpf rpf; struct pim_nexthop_cache *pnc = NULL; struct pim_neighbor *nbr = NULL; struct interface *ifp = NULL; struct interface *ifp1 = NULL; struct vrf *vrf = vrf_lookup_by_id(vrf_id); struct pim_instance *pim; struct zapi_route nhr; if (!vrf) return 0; pim = vrf->info; if (!zapi_nexthop_update_decode(zclient->ibuf, &nhr)) { if (PIM_DEBUG_PIM_NHT) zlog_debug( "%s: Decode of nexthop update from zebra failed", __PRETTY_FUNCTION__); return 0; } if (cmd == ZEBRA_NEXTHOP_UPDATE) { prefix_copy(&rpf.rpf_addr, &nhr.prefix); pnc = pim_nexthop_cache_find(pim, &rpf); if (!pnc) { if (PIM_DEBUG_PIM_NHT) { char buf[PREFIX2STR_BUFFER]; prefix2str(&rpf.rpf_addr, buf, sizeof(buf)); zlog_debug( "%s: Skipping NHT update, addr %s is not in local cached DB.", __PRETTY_FUNCTION__, buf); } return 0; } } else { /* * We do not currently handle ZEBRA_IMPORT_CHECK_UPDATE */ return 0; } pnc->last_update = pim_time_monotonic_usec(); if (nhr.nexthop_num) { pnc->nexthop_num = 0; // Only increment for pim enabled rpf. for (i = 0; i < nhr.nexthop_num; i++) { nexthop = nexthop_from_zapi_nexthop(&nhr.nexthops[i]); switch (nexthop->type) { case NEXTHOP_TYPE_IPV4: case NEXTHOP_TYPE_IPV4_IFINDEX: case NEXTHOP_TYPE_IPV6: case NEXTHOP_TYPE_BLACKHOLE: break; case NEXTHOP_TYPE_IFINDEX: /* * Connected route (i.e. no nexthop), use * RPF address from nexthop cache (i.e. * destination) as PIM nexthop. */ nexthop->type = NEXTHOP_TYPE_IPV4_IFINDEX; nexthop->gate.ipv4 = pnc->rpf.rpf_addr.u.prefix4; break; case NEXTHOP_TYPE_IPV6_IFINDEX: ifp1 = if_lookup_by_index(nexthop->ifindex, pim->vrf_id); nbr = pim_neighbor_find_if(ifp1); /* Overwrite with Nbr address as NH addr */ if (nbr) nexthop->gate.ipv4 = nbr->source_addr; else { // Mark nexthop address to 0 until PIM // Nbr is resolved. nexthop->gate.ipv4.s_addr = PIM_NET_INADDR_ANY; } break; } ifp = if_lookup_by_index(nexthop->ifindex, pim->vrf_id); if (!ifp) { if (PIM_DEBUG_PIM_NHT) { char buf[NEXTHOP_STRLEN]; zlog_debug( "%s: could not find interface for ifindex %d(%s) (addr %s)", __PRETTY_FUNCTION__, nexthop->ifindex, pim->vrf->name, nexthop2str(nexthop, buf, sizeof(buf))); } nexthop_free(nexthop); continue; } if (PIM_DEBUG_PIM_NHT) { char p_str[PREFIX2STR_BUFFER]; prefix2str(&nhr.prefix, p_str, sizeof(p_str)); zlog_debug( "%s: NHT addr %s(%s) %d-nhop via %s(%s) type %d distance:%u metric:%u ", __PRETTY_FUNCTION__, p_str, pim->vrf->name, i + 1, inet_ntoa(nexthop->gate.ipv4), ifp->name, nexthop->type, nhr.distance, nhr.metric); } if (!ifp->info) { /* * Though Multicast is not enabled on this * Interface store it in database otheriwse we * may miss this update and this will not cause * any issue, because while choosing the path we * are ommitting the Interfaces which are not * multicast enabled */ if (PIM_DEBUG_PIM_NHT) { char buf[NEXTHOP_STRLEN]; zlog_debug( "%s: multicast not enabled on input interface %s(%s) (ifindex=%d, addr %s)", __PRETTY_FUNCTION__, ifp->name, pim->vrf->name, nexthop->ifindex, nexthop2str(nexthop, buf, sizeof(buf))); } } if (nhlist_tail) { nhlist_tail->next = nexthop; nhlist_tail = nexthop; } else { nhlist_tail = nexthop; nhlist_head = nexthop; } // Only keep track of nexthops which are PIM enabled. pnc->nexthop_num++; } /* Reset existing pnc->nexthop before assigning new list */ nexthops_free(pnc->nexthop); pnc->nexthop = nhlist_head; if (pnc->nexthop_num) { pnc->flags |= PIM_NEXTHOP_VALID; pnc->distance = nhr.distance; pnc->metric = nhr.metric; } } else { pnc->flags &= ~PIM_NEXTHOP_VALID; pnc->nexthop_num = nhr.nexthop_num; nexthops_free(pnc->nexthop); pnc->nexthop = NULL; } SET_FLAG(pnc->flags, PIM_NEXTHOP_ANSWER_RECEIVED); if (PIM_DEBUG_PIM_NHT) { char buf[PREFIX2STR_BUFFER]; prefix2str(&nhr.prefix, buf, sizeof(buf)); zlog_debug( "%s: NHT Update for %s(%s) num_nh %d num_pim_nh %d vrf:%u up %ld rp %d", __PRETTY_FUNCTION__, buf, pim->vrf->name, nhr.nexthop_num, pnc->nexthop_num, vrf_id, pnc->upstream_hash->count, listcount(pnc->rp_list)); } pim_rpf_set_refresh_time(pim); if (listcount(pnc->rp_list)) pim_update_rp_nh(pim, pnc); if (pnc->upstream_hash->count) pim_update_upstream_nh(pim, pnc); return 0; } int pim_ecmp_nexthop_lookup(struct pim_instance *pim, struct pim_nexthop *nexthop, struct prefix *src, struct prefix *grp, int neighbor_needed) { struct pim_nexthop_cache *pnc; struct pim_zlookup_nexthop nexthop_tab[MULTIPATH_NUM]; struct pim_neighbor *nbrs[MULTIPATH_NUM], *nbr = NULL; struct pim_rpf rpf; int num_ifindex; struct interface *ifps[MULTIPATH_NUM], *ifp; int first_ifindex; int found = 0; uint8_t i = 0; uint32_t hash_val = 0, mod_val = 0; uint32_t num_nbrs = 0; char addr_str[PREFIX_STRLEN]; if (PIM_DEBUG_PIM_NHT) { pim_inet4_dump("", src->u.prefix4, addr_str, sizeof(addr_str)); zlog_debug("%s: Looking up: %s(%s), last lookup time: %lld", __PRETTY_FUNCTION__, addr_str, pim->vrf->name, nexthop->last_lookup_time); } memset(&rpf, 0, sizeof(struct pim_rpf)); rpf.rpf_addr.family = AF_INET; rpf.rpf_addr.prefixlen = IPV4_MAX_BITLEN; rpf.rpf_addr.u.prefix4 = src->u.prefix4; pnc = pim_nexthop_cache_find(pim, &rpf); if (pnc) { if (CHECK_FLAG(pnc->flags, PIM_NEXTHOP_ANSWER_RECEIVED)) return pim_ecmp_nexthop_search(pim, pnc, nexthop, src, grp, neighbor_needed); } memset(nexthop_tab, 0, sizeof(struct pim_zlookup_nexthop) * MULTIPATH_NUM); num_ifindex = zclient_lookup_nexthop(pim, nexthop_tab, MULTIPATH_NUM, src->u.prefix4, PIM_NEXTHOP_LOOKUP_MAX); if (num_ifindex < 1) { if (PIM_DEBUG_PIM_NHT) zlog_warn( "%s: could not find nexthop ifindex for address %s(%s)", __PRETTY_FUNCTION__, addr_str, pim->vrf->name); return 0; } memset(&nbrs, 0, sizeof(nbrs)); memset(&ifps, 0, sizeof(ifps)); /* * Look up all interfaces and neighbors, * store for later usage */ for (i = 0; i < num_ifindex; i++) { ifps[i] = if_lookup_by_index(nexthop_tab[i].ifindex, pim->vrf_id); if (ifps[i]) { nbrs[i] = pim_neighbor_find( ifps[i], nexthop_tab[i].nexthop_addr.u.prefix4); if (nbrs[i] || pim_if_connected_to_source(ifps[i], src->u.prefix4)) num_nbrs++; } } // If PIM ECMP enable then choose ECMP path. if (pim->ecmp_enable) { uint32_t consider = num_ifindex; if (neighbor_needed && num_nbrs < consider) consider = num_nbrs; if (consider == 0) return 0; hash_val = pim_compute_ecmp_hash(src, grp); mod_val = hash_val % consider; if (PIM_DEBUG_PIM_NHT_DETAIL) zlog_debug("%s: hash_val %u mod_val %u", __PRETTY_FUNCTION__, hash_val, mod_val); } i = 0; while (!found && (i < num_ifindex)) { first_ifindex = nexthop_tab[i].ifindex; ifp = ifps[i]; if (!ifp) { if (PIM_DEBUG_PIM_NHT) zlog_debug( "%s %s: could not find interface for ifindex %d (address %s(%s))", __FILE__, __PRETTY_FUNCTION__, first_ifindex, addr_str, pim->vrf->name); if (i == mod_val) mod_val++; i++; continue; } if (!ifp->info) { if (PIM_DEBUG_PIM_NHT) zlog_debug( "%s: multicast not enabled on input interface %s(%s) (ifindex=%d, RPF for source %s)", __PRETTY_FUNCTION__, ifp->name, pim->vrf->name, first_ifindex, addr_str); if (i == mod_val) mod_val++; i++; continue; } if (neighbor_needed && !pim_if_connected_to_source(ifp, src->u.prefix4)) { nbr = nbrs[i]; if (PIM_DEBUG_PIM_NHT_DETAIL) zlog_debug("ifp name: %s(%s), pim nbr: %p", ifp->name, pim->vrf->name, nbr); if (!nbr && !if_is_loopback(ifp)) { if (i == mod_val) mod_val++; i++; if (PIM_DEBUG_PIM_NHT) zlog_debug( "%s: NBR not found on input interface %s(%s) (RPF for source %s)", __PRETTY_FUNCTION__, ifp->name, pim->vrf->name, addr_str); continue; } } if (i == mod_val) { if (PIM_DEBUG_PIM_NHT) { char nexthop_str[PREFIX_STRLEN]; pim_addr_dump("", &nexthop_tab[i].nexthop_addr, nexthop_str, sizeof(nexthop_str)); zlog_debug( "%s: found nhop %s for addr %s interface %s(%s) metric %d dist %d", __PRETTY_FUNCTION__, nexthop_str, addr_str, ifp->name, pim->vrf->name, nexthop_tab[i].route_metric, nexthop_tab[i].protocol_distance); } /* update nexthop data */ nexthop->interface = ifp; nexthop->mrib_nexthop_addr = nexthop_tab[i].nexthop_addr; nexthop->mrib_metric_preference = nexthop_tab[i].protocol_distance; nexthop->mrib_route_metric = nexthop_tab[i].route_metric; nexthop->last_lookup = src->u.prefix4; nexthop->last_lookup_time = pim_time_monotonic_usec(); nexthop->nbr = nbr; found = 1; } i++; } if (found) return 1; else return 0; } int pim_ecmp_fib_lookup_if_vif_index(struct pim_instance *pim, struct prefix *src, struct prefix *grp) { struct pim_nexthop nhop; int vif_index; ifindex_t ifindex; char addr_str[PREFIX_STRLEN]; if (PIM_DEBUG_PIM_NHT) pim_inet4_dump("", src->u.prefix4, addr_str, sizeof(addr_str)); memset(&nhop, 0, sizeof(nhop)); if (!pim_ecmp_nexthop_lookup(pim, &nhop, src, grp, 0)) { if (PIM_DEBUG_PIM_NHT) zlog_debug( "%s: could not find nexthop ifindex for address %s(%s)", __PRETTY_FUNCTION__, addr_str, pim->vrf->name); return -1; } ifindex = nhop.interface->ifindex; if (PIM_DEBUG_PIM_NHT) zlog_debug( "%s: found nexthop ifindex=%d (interface %s(%s)) for address %s", __PRETTY_FUNCTION__, ifindex, ifindex2ifname(ifindex, pim->vrf_id), pim->vrf->name, addr_str); vif_index = pim_if_find_vifindex_by_ifindex(pim, ifindex); if (vif_index < 0) { if (PIM_DEBUG_PIM_NHT) { zlog_debug( "%s: low vif_index=%d(%s) < 1 nexthop for address %s", __PRETTY_FUNCTION__, vif_index, pim->vrf->name, addr_str); } return -2; } return vif_index; } frr-7.2.1/pimd/pim_nht.h0000644000000000000000000000561613610377563011764 00000000000000/* * PIM for Quagga * Copyright (C) 2017 Cumulus Networks, Inc. * Chirag Shah * * This program is free software; you can redistribute it and/or modify * it under the terms of the 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; see the file COPYING; if not, write to the Free Software * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA */ #ifndef PIM_NHT_H #define PIM_NHT_H #include "prefix.h" #include #include "zclient.h" #include "vrf.h" #include "pimd.h" #include "pim_rp.h" #include "pim_rpf.h" /* PIM nexthop cache value structure. */ struct pim_nexthop_cache { struct pim_rpf rpf; /* IGP route's metric. */ uint32_t metric; uint32_t distance; /* Nexthop number and nexthop linked list. */ uint8_t nexthop_num; struct nexthop *nexthop; int64_t last_update; uint16_t flags; #define PIM_NEXTHOP_VALID (1 << 0) #define PIM_NEXTHOP_ANSWER_RECEIVED (1 << 1) struct list *rp_list; struct hash *upstream_hash; /* Ideally this has to be list of scope zone. But for now we can just * have as a bool variable to say bsr_tracking. * Later this variable can be changed as a list of scope zones for * tracking same bsr for multiple scope zones. */ bool bsr_tracking; }; int pim_parse_nexthop_update(ZAPI_CALLBACK_ARGS); int pim_find_or_track_nexthop(struct pim_instance *pim, struct prefix *addr, struct pim_upstream *up, struct rp_info *rp, bool bsr_track_needed, struct pim_nexthop_cache *out_pnc); void pim_delete_tracked_nexthop(struct pim_instance *pim, struct prefix *addr, struct pim_upstream *up, struct rp_info *rp, bool del_bsr_tracking); struct pim_nexthop_cache *pim_nexthop_cache_find(struct pim_instance *pim, struct pim_rpf *rpf); uint32_t pim_compute_ecmp_hash(struct prefix *src, struct prefix *grp); int pim_ecmp_nexthop_lookup(struct pim_instance *pim, struct pim_nexthop *nexthop, struct prefix *src, struct prefix *grp, int neighbor_needed); void pim_sendmsg_zebra_rnh(struct pim_instance *pim, struct zclient *zclient, struct pim_nexthop_cache *pnc, int command); int pim_ecmp_fib_lookup_if_vif_index(struct pim_instance *pim, struct prefix *src, struct prefix *grp); void pim_rp_nexthop_del(struct rp_info *rp_info); bool pim_nexthop_match(struct pim_instance *pim, struct in_addr addr, struct in_addr ip_src); bool pim_nexthop_match_nht_cache(struct pim_instance *pim, struct in_addr addr, struct in_addr ip_src); #endif frr-7.2.1/pimd/pim_oil.c0000644000000000000000000004120613610377563011744 00000000000000/* * PIM for Quagga * Copyright (C) 2008 Everton da Silva Marques * * This program is free software; you can redistribute it and/or modify * it under the terms of the 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; see the file COPYING; if not, write to the Free Software * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA */ #include #include "log.h" #include "memory.h" #include "linklist.h" #include "if.h" #include "hash.h" #include "jhash.h" #include "pimd.h" #include "pim_oil.h" #include "pim_str.h" #include "pim_iface.h" #include "pim_time.h" // struct list *pim_channel_oil_list = NULL; // struct hash *pim_channel_oil_hash = NULL; char *pim_channel_oil_dump(struct channel_oil *c_oil, char *buf, size_t size) { char *out; struct interface *ifp; struct prefix_sg sg; int i; sg.src = c_oil->oil.mfcc_origin; sg.grp = c_oil->oil.mfcc_mcastgrp; ifp = pim_if_find_by_vif_index(c_oil->pim, c_oil->oil.mfcc_parent); snprintf(buf, size, "%s IIF: %s, OIFS: ", pim_str_sg_dump(&sg), ifp ? ifp->name : "(?)"); out = buf + strlen(buf); for (i = 0; i < MAXVIFS; i++) { if (c_oil->oil.mfcc_ttls[i] != 0) { ifp = pim_if_find_by_vif_index(c_oil->pim, i); snprintf(out, buf + size - out, "%s ", ifp ? ifp->name : "(?)"); out += strlen(out); } } return buf; } static int pim_channel_oil_compare(struct channel_oil *c1, struct channel_oil *c2) { if (ntohl(c1->oil.mfcc_mcastgrp.s_addr) < ntohl(c2->oil.mfcc_mcastgrp.s_addr)) return -1; if (ntohl(c1->oil.mfcc_mcastgrp.s_addr) > ntohl(c2->oil.mfcc_mcastgrp.s_addr)) return 1; if (ntohl(c1->oil.mfcc_origin.s_addr) < ntohl(c2->oil.mfcc_origin.s_addr)) return -1; if (ntohl(c1->oil.mfcc_origin.s_addr) > ntohl(c2->oil.mfcc_origin.s_addr)) return 1; return 0; } static bool pim_oil_equal(const void *arg1, const void *arg2) { const struct channel_oil *c1 = (const struct channel_oil *)arg1; const struct channel_oil *c2 = (const struct channel_oil *)arg2; if ((c1->oil.mfcc_mcastgrp.s_addr == c2->oil.mfcc_mcastgrp.s_addr) && (c1->oil.mfcc_origin.s_addr == c2->oil.mfcc_origin.s_addr)) return true; return false; } static unsigned int pim_oil_hash_key(const void *arg) { const struct channel_oil *oil = arg; return jhash_2words(oil->oil.mfcc_mcastgrp.s_addr, oil->oil.mfcc_origin.s_addr, 0); } void pim_oil_init(struct pim_instance *pim) { char hash_name[64]; snprintf(hash_name, 64, "PIM %s Oil Hash", pim->vrf->name); pim->channel_oil_hash = hash_create_size(8192, pim_oil_hash_key, pim_oil_equal, hash_name); pim->channel_oil_list = list_new(); pim->channel_oil_list->del = (void (*)(void *))pim_channel_oil_free; pim->channel_oil_list->cmp = (int (*)(void *, void *))pim_channel_oil_compare; } void pim_oil_terminate(struct pim_instance *pim) { if (pim->channel_oil_list) list_delete(&pim->channel_oil_list); if (pim->channel_oil_hash) hash_free(pim->channel_oil_hash); pim->channel_oil_hash = NULL; } void pim_channel_oil_free(struct channel_oil *c_oil) { XFREE(MTYPE_PIM_CHANNEL_OIL, c_oil); } struct channel_oil *pim_find_channel_oil(struct pim_instance *pim, struct prefix_sg *sg) { struct channel_oil *c_oil = NULL; struct channel_oil lookup; lookup.oil.mfcc_mcastgrp = sg->grp; lookup.oil.mfcc_origin = sg->src; c_oil = hash_lookup(pim->channel_oil_hash, &lookup); return c_oil; } void pim_channel_oil_change_iif(struct pim_instance *pim, struct channel_oil *c_oil, int input_vif_index, const char *name) { int old_vif_index = c_oil->oil.mfcc_parent; struct prefix_sg sg = {.src = c_oil->oil.mfcc_mcastgrp, .grp = c_oil->oil.mfcc_origin}; if (c_oil->oil.mfcc_parent == input_vif_index) { if (PIM_DEBUG_MROUTE_DETAIL) zlog_debug("%s(%s): Existing channel oil %pSG4 already using %d as IIF", __PRETTY_FUNCTION__, name, &sg, input_vif_index); return; } if (PIM_DEBUG_MROUTE_DETAIL) zlog_debug("%s(%s): Changing channel oil %pSG4 IIF from %d to %d installed: %d", __PRETTY_FUNCTION__, name, &sg, c_oil->oil.mfcc_parent, input_vif_index, c_oil->installed); c_oil->oil.mfcc_parent = input_vif_index; if (c_oil->installed) { if (input_vif_index == MAXVIFS) pim_mroute_del(c_oil, name); else pim_mroute_add(c_oil, name); } else if (old_vif_index == MAXVIFS) pim_mroute_add(c_oil, name); return; } struct channel_oil *pim_channel_oil_add(struct pim_instance *pim, struct prefix_sg *sg, int input_vif_index, const char *name) { struct channel_oil *c_oil; struct interface *ifp; c_oil = pim_find_channel_oil(pim, sg); if (c_oil) { if (c_oil->oil.mfcc_parent != input_vif_index) { c_oil->oil_inherited_rescan = 1; if (PIM_DEBUG_MROUTE_DETAIL) zlog_debug( "%s: Existing channel oil %pSG4 points to %d, modifying to point at %d", __PRETTY_FUNCTION__, sg, c_oil->oil.mfcc_parent, input_vif_index); } pim_channel_oil_change_iif(pim, c_oil, input_vif_index, name); ++c_oil->oil_ref_count; /* channel might be present prior to upstream */ c_oil->up = pim_upstream_find(pim, sg); if (PIM_DEBUG_MROUTE) zlog_debug( "%s(%s): Existing oil for %pSG4 Ref Count: %d (Post Increment)", __PRETTY_FUNCTION__, name, sg, c_oil->oil_ref_count); return c_oil; } if (input_vif_index != MAXVIFS) { ifp = pim_if_find_by_vif_index(pim, input_vif_index); if (!ifp) { /* warning only */ zlog_warn( "%s:%s (S,G)=%pSG4 could not find input interface for input_vif_index=%d", __PRETTY_FUNCTION__, name, sg, input_vif_index); } } c_oil = XCALLOC(MTYPE_PIM_CHANNEL_OIL, sizeof(*c_oil)); c_oil->oil.mfcc_mcastgrp = sg->grp; c_oil->oil.mfcc_origin = sg->src; c_oil = hash_get(pim->channel_oil_hash, c_oil, hash_alloc_intern); c_oil->oil.mfcc_parent = input_vif_index; c_oil->oil_ref_count = 1; c_oil->installed = 0; c_oil->up = pim_upstream_find(pim, sg); c_oil->pim = pim; listnode_add_sort(pim->channel_oil_list, c_oil); if (PIM_DEBUG_MROUTE) zlog_debug( "%s(%s): New oil for %pSG4 vif_index: %d Ref Count: 1 (Post Increment)", __PRETTY_FUNCTION__, name, sg, input_vif_index); return c_oil; } void pim_channel_oil_del(struct channel_oil *c_oil, const char *name) { if (PIM_DEBUG_MROUTE) { struct prefix_sg sg = {.src = c_oil->oil.mfcc_mcastgrp, .grp = c_oil->oil.mfcc_origin}; zlog_debug( "%s(%s): Del oil for %pSG4, Ref Count: %d (Predecrement)", __PRETTY_FUNCTION__, name, &sg, c_oil->oil_ref_count); } --c_oil->oil_ref_count; if (c_oil->oil_ref_count < 1) { /* * notice that listnode_delete() can't be moved * into pim_channel_oil_free() because the later is * called by list_delete_all_node() */ c_oil->up = NULL; listnode_delete(c_oil->pim->channel_oil_list, c_oil); hash_release(c_oil->pim->channel_oil_hash, c_oil); pim_channel_oil_free(c_oil); } } int pim_channel_del_oif(struct channel_oil *channel_oil, struct interface *oif, uint32_t proto_mask) { struct pim_interface *pim_ifp; zassert(channel_oil); zassert(oif); pim_ifp = oif->info; /* * Don't do anything if we've been asked to remove a source * that is not actually on it. */ if (!(channel_oil->oif_flags[pim_ifp->mroute_vif_index] & proto_mask)) { if (PIM_DEBUG_MROUTE) { char group_str[INET_ADDRSTRLEN]; char source_str[INET_ADDRSTRLEN]; pim_inet4_dump("", channel_oil->oil.mfcc_mcastgrp, group_str, sizeof(group_str)); pim_inet4_dump("", channel_oil->oil.mfcc_origin, source_str, sizeof(source_str)); zlog_debug( "%s %s: no existing protocol mask %u(%u) for requested OIF %s (vif_index=%d, min_ttl=%d) for channel (S,G)=(%s,%s)", __FILE__, __PRETTY_FUNCTION__, proto_mask, channel_oil ->oif_flags[pim_ifp->mroute_vif_index], oif->name, pim_ifp->mroute_vif_index, channel_oil->oil .mfcc_ttls[pim_ifp->mroute_vif_index], source_str, group_str); } return 0; } channel_oil->oif_flags[pim_ifp->mroute_vif_index] &= ~proto_mask; if (channel_oil->oif_flags[pim_ifp->mroute_vif_index]) { if (PIM_DEBUG_MROUTE) { char group_str[INET_ADDRSTRLEN]; char source_str[INET_ADDRSTRLEN]; pim_inet4_dump("", channel_oil->oil.mfcc_mcastgrp, group_str, sizeof(group_str)); pim_inet4_dump("", channel_oil->oil.mfcc_origin, source_str, sizeof(source_str)); zlog_debug( "%s %s: other protocol masks remain for requested OIF %s (vif_index=%d, min_ttl=%d) for channel (S,G)=(%s,%s)", __FILE__, __PRETTY_FUNCTION__, oif->name, pim_ifp->mroute_vif_index, channel_oil->oil .mfcc_ttls[pim_ifp->mroute_vif_index], source_str, group_str); } return 0; } channel_oil->oil.mfcc_ttls[pim_ifp->mroute_vif_index] = 0; if (pim_mroute_add(channel_oil, __PRETTY_FUNCTION__)) { if (PIM_DEBUG_MROUTE) { char group_str[INET_ADDRSTRLEN]; char source_str[INET_ADDRSTRLEN]; pim_inet4_dump("", channel_oil->oil.mfcc_mcastgrp, group_str, sizeof(group_str)); pim_inet4_dump("", channel_oil->oil.mfcc_origin, source_str, sizeof(source_str)); zlog_debug( "%s %s: could not remove output interface %s (vif_index=%d) for channel (S,G)=(%s,%s)", __FILE__, __PRETTY_FUNCTION__, oif->name, pim_ifp->mroute_vif_index, source_str, group_str); } return -1; } --channel_oil->oil_size; if (PIM_DEBUG_MROUTE) { char group_str[INET_ADDRSTRLEN]; char source_str[INET_ADDRSTRLEN]; pim_inet4_dump("", channel_oil->oil.mfcc_mcastgrp, group_str, sizeof(group_str)); pim_inet4_dump("", channel_oil->oil.mfcc_origin, source_str, sizeof(source_str)); zlog_debug( "%s %s: (S,G)=(%s,%s): proto_mask=%u IIF:%d OIF=%s vif_index=%d", __FILE__, __PRETTY_FUNCTION__, source_str, group_str, proto_mask, channel_oil->oil.mfcc_parent, oif->name, pim_ifp->mroute_vif_index); } return 0; } int pim_channel_add_oif(struct channel_oil *channel_oil, struct interface *oif, uint32_t proto_mask) { struct pim_interface *pim_ifp; int old_ttl; bool allow_iif_in_oil = false; /* * If we've gotten here we've gone bad, but let's * not take down pim */ if (!channel_oil) { zlog_warn("Attempt to Add OIF for non-existent channel oil"); return -1; } pim_ifp = oif->info; #ifdef PIM_ENFORCE_LOOPFREE_MFC /* Prevent creating MFC entry with OIF=IIF. This is a protection against implementation mistakes. PIM protocol implicitely ensures loopfree multicast topology. IGMP must be protected against adding looped MFC entries created by both source and receiver attached to the same interface. See TODO T22. We shall allow igmp to create upstream when it is DR for the intf. Assume RP reachable via non DR. */ if ((channel_oil->up && PIM_UPSTREAM_FLAG_TEST_ALLOW_IIF_IN_OIL(channel_oil->up->flags)) || ((proto_mask == PIM_OIF_FLAG_PROTO_IGMP) && PIM_I_am_DR(pim_ifp))) { allow_iif_in_oil = true; } if (!allow_iif_in_oil && pim_ifp->mroute_vif_index == channel_oil->oil.mfcc_parent) { channel_oil->oil_inherited_rescan = 1; if (PIM_DEBUG_MROUTE) { char group_str[INET_ADDRSTRLEN]; char source_str[INET_ADDRSTRLEN]; pim_inet4_dump("", channel_oil->oil.mfcc_mcastgrp, group_str, sizeof(group_str)); pim_inet4_dump("", channel_oil->oil.mfcc_origin, source_str, sizeof(source_str)); zlog_debug( "%s %s: refusing protocol mask %u request for IIF=OIF=%s (vif_index=%d) for channel (S,G)=(%s,%s)", __FILE__, __PRETTY_FUNCTION__, proto_mask, oif->name, pim_ifp->mroute_vif_index, source_str, group_str); } return -2; } #endif /* Prevent single protocol from subscribing same interface to channel (S,G) multiple times */ if (channel_oil->oif_flags[pim_ifp->mroute_vif_index] & proto_mask) { if (PIM_DEBUG_MROUTE) { char group_str[INET_ADDRSTRLEN]; char source_str[INET_ADDRSTRLEN]; pim_inet4_dump("", channel_oil->oil.mfcc_mcastgrp, group_str, sizeof(group_str)); pim_inet4_dump("", channel_oil->oil.mfcc_origin, source_str, sizeof(source_str)); zlog_debug( "%s %s: existing protocol mask %u requested OIF %s (vif_index=%d, min_ttl=%d) for channel (S,G)=(%s,%s)", __FILE__, __PRETTY_FUNCTION__, proto_mask, oif->name, pim_ifp->mroute_vif_index, channel_oil->oil .mfcc_ttls[pim_ifp->mroute_vif_index], source_str, group_str); } return -3; } /* Allow other protocol to request subscription of same interface to * channel (S,G), we need to note this information */ if (channel_oil->oif_flags[pim_ifp->mroute_vif_index] & PIM_OIF_FLAG_PROTO_ANY) { /* Updating time here is not required as this time has to * indicate when the interface is added */ channel_oil->oif_flags[pim_ifp->mroute_vif_index] |= proto_mask; /* Check the OIF really exists before returning, and only log warning otherwise */ if (channel_oil->oil.mfcc_ttls[pim_ifp->mroute_vif_index] < 1) { { char group_str[INET_ADDRSTRLEN]; char source_str[INET_ADDRSTRLEN]; pim_inet4_dump("", channel_oil->oil.mfcc_mcastgrp, group_str, sizeof(group_str)); pim_inet4_dump("", channel_oil->oil.mfcc_origin, source_str, sizeof(source_str)); zlog_warn( "%s %s: new protocol mask %u requested nonexistent OIF %s (vif_index=%d, min_ttl=%d) for channel (S,G)=(%s,%s)", __FILE__, __PRETTY_FUNCTION__, proto_mask, oif->name, pim_ifp->mroute_vif_index, channel_oil->oil.mfcc_ttls [pim_ifp->mroute_vif_index], source_str, group_str); } } return 0; } old_ttl = channel_oil->oil.mfcc_ttls[pim_ifp->mroute_vif_index]; if (old_ttl > 0) { if (PIM_DEBUG_MROUTE) { char group_str[INET_ADDRSTRLEN]; char source_str[INET_ADDRSTRLEN]; pim_inet4_dump("", channel_oil->oil.mfcc_mcastgrp, group_str, sizeof(group_str)); pim_inet4_dump("", channel_oil->oil.mfcc_origin, source_str, sizeof(source_str)); zlog_debug( "%s %s: interface %s (vif_index=%d) is existing output for channel (S,G)=(%s,%s)", __FILE__, __PRETTY_FUNCTION__, oif->name, pim_ifp->mroute_vif_index, source_str, group_str); } return -4; } channel_oil->oil.mfcc_ttls[pim_ifp->mroute_vif_index] = PIM_MROUTE_MIN_TTL; /* channel_oil->oil.mfcc_parent != MAXVIFS indicate this entry is not * valid to get installed in kernel. */ if (channel_oil->oil.mfcc_parent != MAXVIFS) { if (pim_mroute_add(channel_oil, __PRETTY_FUNCTION__)) { if (PIM_DEBUG_MROUTE) { char group_str[INET_ADDRSTRLEN]; char source_str[INET_ADDRSTRLEN]; pim_inet4_dump("", channel_oil->oil.mfcc_mcastgrp, group_str, sizeof(group_str)); pim_inet4_dump("", channel_oil->oil.mfcc_origin, source_str, sizeof(source_str)); zlog_debug( "%s %s: could not add output interface %s (vif_index=%d) for channel (S,G)=(%s,%s)", __FILE__, __PRETTY_FUNCTION__, oif->name, pim_ifp->mroute_vif_index, source_str, group_str); } channel_oil->oil.mfcc_ttls[pim_ifp->mroute_vif_index] = old_ttl; return -5; } } channel_oil->oif_creation[pim_ifp->mroute_vif_index] = pim_time_monotonic_sec(); ++channel_oil->oil_size; channel_oil->oif_flags[pim_ifp->mroute_vif_index] |= proto_mask; if (PIM_DEBUG_MROUTE) { char group_str[INET_ADDRSTRLEN]; char source_str[INET_ADDRSTRLEN]; pim_inet4_dump("", channel_oil->oil.mfcc_mcastgrp, group_str, sizeof(group_str)); pim_inet4_dump("", channel_oil->oil.mfcc_origin, source_str, sizeof(source_str)); zlog_debug( "%s %s: (S,G)=(%s,%s): proto_mask=%u OIF=%s vif_index=%d: DONE", __FILE__, __PRETTY_FUNCTION__, source_str, group_str, proto_mask, oif->name, pim_ifp->mroute_vif_index); } return 0; } int pim_channel_oil_empty(struct channel_oil *c_oil) { static uint32_t zero[MAXVIFS]; static int inited = 0; if (!c_oil) return 1; /* * Not sure that this is necessary, but I would rather ensure * that this works. */ if (!inited) { memset(&zero, 0, sizeof(uint32_t) * MAXVIFS); inited = 1; } return !memcmp(c_oil->oif_flags, zero, MAXVIFS * sizeof(uint32_t)); } frr-7.2.1/pimd/pim_oil.h0000644000000000000000000001056413610377563011754 00000000000000/* * PIM for Quagga * Copyright (C) 2008 Everton da Silva Marques * * This program is free software; you can redistribute it and/or modify * it under the terms of the 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; see the file COPYING; if not, write to the Free Software * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA */ #ifndef PIM_OIL_H #define PIM_OIL_H #include "pim_mroute.h" /* * Where did we get this (S,G) from? * * IGMP - Learned from IGMP * PIM - Learned from PIM * SOURCE - Learned from Source multicast packet received * STAR - Inherited */ #define PIM_OIF_FLAG_PROTO_IGMP (1 << 0) #define PIM_OIF_FLAG_PROTO_PIM (1 << 1) #define PIM_OIF_FLAG_PROTO_STAR (1 << 2) #define PIM_OIF_FLAG_PROTO_VXLAN (1 << 3) #define PIM_OIF_FLAG_PROTO_ANY \ (PIM_OIF_FLAG_PROTO_IGMP | PIM_OIF_FLAG_PROTO_PIM \ | PIM_OIF_FLAG_PROTO_STAR | PIM_OIF_FLAG_PROTO_VXLAN) /* * We need a pimreg vif id from the kernel. * Since ifindex == vif id for most cases and the number * of expected interfaces is at most 100, using MAXVIFS -1 * is probably ok. * Don't come running to me if this assumption is bad, * fix it. */ #define PIM_OIF_PIM_REGISTER_VIF 0 #define PIM_MAX_USABLE_VIFS (MAXVIFS - 1) struct channel_counts { unsigned long long lastused; unsigned long origpktcnt; unsigned long pktcnt; unsigned long oldpktcnt; unsigned long origbytecnt; unsigned long bytecnt; unsigned long oldbytecnt; unsigned long origwrong_if; unsigned long wrong_if; unsigned long oldwrong_if; }; /* qpim_channel_oil_list holds a list of struct channel_oil. Each channel_oil.oil is used to control an (S,G) entry in the Kernel Multicast Forwarding Cache. There is a case when we create a channel_oil but don't install in the kernel Case where (S, G) entry not installed in the kernel: FRR receives IGMP/PIM (*, G) join and RP is not configured or not-reachable, then create a channel_oil for the group G with the incoming interface(channel_oil.oil.mfcc_parent) as invalid i.e "MAXVIF" and populate the outgoing interface where join is received. Keep this entry in the stack, but don't install in the kernel(channel_oil.installed = 0). Case where (S, G) entry installed in the kernel: When RP is configured and is reachable for the group G, and receiving a join if channel_oil is already present then populate the incoming interface and install the entry in the kernel, if channel_oil not present, then create a new_channel oil(channel_oil.installed = 1). is_valid: indicate if this entry is valid to get installed in kernel. installed: indicate if this entry is installed in the kernel. */ struct channel_oil { struct pim_instance *pim; struct mfcctl oil; int installed; int oil_inherited_rescan; int oil_size; int oil_ref_count; time_t oif_creation[MAXVIFS]; uint32_t oif_flags[MAXVIFS]; struct channel_counts cc; struct pim_upstream *up; time_t mroute_creation; }; extern struct list *pim_channel_oil_list; void pim_oil_init(struct pim_instance *pim); void pim_oil_terminate(struct pim_instance *pim); void pim_channel_oil_free(struct channel_oil *c_oil); struct channel_oil *pim_find_channel_oil(struct pim_instance *pim, struct prefix_sg *sg); struct channel_oil *pim_channel_oil_add(struct pim_instance *pim, struct prefix_sg *sg, int input_vif_index, const char *name); void pim_channel_oil_change_iif(struct pim_instance *pim, struct channel_oil *c_oil, int input_vif_index, const char *name); void pim_channel_oil_del(struct channel_oil *c_oil, const char *name); int pim_channel_add_oif(struct channel_oil *c_oil, struct interface *oif, uint32_t proto_mask); int pim_channel_del_oif(struct channel_oil *c_oil, struct interface *oif, uint32_t proto_mask); int pim_channel_oil_empty(struct channel_oil *c_oil); char *pim_channel_oil_dump(struct channel_oil *c_oil, char *buf, size_t size); #endif /* PIM_OIL_H */ frr-7.2.1/pimd/pim_pim.c0000644000000000000000000005400013610377563011742 00000000000000/* * PIM for Quagga * Copyright (C) 2008 Everton da Silva Marques * * This program is free software; you can redistribute it and/or modify * it under the terms of the 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; see the file COPYING; if not, write to the Free Software * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA */ #include #include "log.h" #include "thread.h" #include "memory.h" #include "if.h" #include "pimd.h" #include "pim_pim.h" #include "pim_time.h" #include "pim_iface.h" #include "pim_sock.h" #include "pim_str.h" #include "pim_util.h" #include "pim_tlv.h" #include "pim_neighbor.h" #include "pim_hello.h" #include "pim_join.h" #include "pim_assert.h" #include "pim_msg.h" #include "pim_register.h" #include "pim_errors.h" #include "pim_bsm.h" static int on_pim_hello_send(struct thread *t); static int pim_hello_send(struct interface *ifp, uint16_t holdtime); static const char *pim_pim_msgtype2str(enum pim_msg_type type) { switch (type) { case PIM_MSG_TYPE_HELLO: return "HELLO"; case PIM_MSG_TYPE_REGISTER: return "REGISTER"; case PIM_MSG_TYPE_REG_STOP: return "REGSTOP"; case PIM_MSG_TYPE_JOIN_PRUNE: return "JOINPRUNE"; case PIM_MSG_TYPE_BOOTSTRAP: return "BOOT"; case PIM_MSG_TYPE_ASSERT: return "ASSERT"; case PIM_MSG_TYPE_GRAFT: return "GRAFT"; case PIM_MSG_TYPE_GRAFT_ACK: return "GACK"; case PIM_MSG_TYPE_CANDIDATE: return "CANDIDATE"; } return "UNKNOWN"; } static void sock_close(struct interface *ifp) { struct pim_interface *pim_ifp = ifp->info; if (PIM_DEBUG_PIM_TRACE) { if (pim_ifp->t_pim_sock_read) { zlog_debug( "Cancelling READ event for PIM socket fd=%d on interface %s", pim_ifp->pim_sock_fd, ifp->name); } } THREAD_OFF(pim_ifp->t_pim_sock_read); if (PIM_DEBUG_PIM_TRACE) { if (pim_ifp->t_pim_hello_timer) { zlog_debug( "Cancelling PIM hello timer for interface %s", ifp->name); } } THREAD_OFF(pim_ifp->t_pim_hello_timer); if (PIM_DEBUG_PIM_TRACE) { zlog_debug("Deleting PIM socket fd=%d on interface %s", pim_ifp->pim_sock_fd, ifp->name); } /* * If the fd is already deleted no need to do anything here */ if (pim_ifp->pim_sock_fd > 0 && close(pim_ifp->pim_sock_fd)) { zlog_warn( "Failure closing PIM socket fd=%d on interface %s: errno=%d: %s", pim_ifp->pim_sock_fd, ifp->name, errno, safe_strerror(errno)); } pim_ifp->pim_sock_fd = -1; pim_ifp->pim_sock_creation = 0; } void pim_sock_delete(struct interface *ifp, const char *delete_message) { zlog_info("PIM INTERFACE DOWN: on interface %s: %s", ifp->name, delete_message); if (!ifp->info) { flog_err(EC_PIM_CONFIG, "%s: %s: but PIM not enabled on interface %s (!)", __PRETTY_FUNCTION__, delete_message, ifp->name); return; } /* RFC 4601: 4.3.1. Sending Hello Messages Before an interface goes down or changes primary IP address, a Hello message with a zero HoldTime should be sent immediately (with the old IP address if the IP address changed). */ pim_hello_send(ifp, 0 /* zero-sec holdtime */); pim_neighbor_delete_all(ifp, delete_message); sock_close(ifp); } int pim_pim_packet(struct interface *ifp, uint8_t *buf, size_t len) { struct ip *ip_hdr; size_t ip_hlen; /* ip header length in bytes */ char src_str[INET_ADDRSTRLEN]; char dst_str[INET_ADDRSTRLEN]; uint8_t *pim_msg; int pim_msg_len; uint16_t pim_checksum; /* received checksum */ uint16_t checksum; /* computed checksum */ struct pim_neighbor *neigh; struct pim_msg_header *header; bool no_fwd; if (len < sizeof(*ip_hdr)) { if (PIM_DEBUG_PIM_PACKETS) zlog_debug( "PIM packet size=%zu shorter than minimum=%zu", len, sizeof(*ip_hdr)); return -1; } ip_hdr = (struct ip *)buf; ip_hlen = ip_hdr->ip_hl << 2; /* ip_hl gives length in 4-byte words */ pim_msg = buf + ip_hlen; pim_msg_len = len - ip_hlen; header = (struct pim_msg_header *)pim_msg; if (pim_msg_len < PIM_PIM_MIN_LEN) { if (PIM_DEBUG_PIM_PACKETS) zlog_debug( "PIM message size=%d shorter than minimum=%d", pim_msg_len, PIM_PIM_MIN_LEN); return -1; } if (header->ver != PIM_PROTO_VERSION) { if (PIM_DEBUG_PIM_PACKETS) zlog_debug( "Ignoring PIM pkt from %s with unsupported version: %d", ifp->name, header->ver); return -1; } /* save received checksum */ pim_checksum = header->checksum; /* for computing checksum */ header->checksum = 0; no_fwd = header->Nbit; if (header->type == PIM_MSG_TYPE_REGISTER) { if (pim_msg_len < PIM_MSG_REGISTER_LEN) { if (PIM_DEBUG_PIM_PACKETS) zlog_debug("PIM Register Message size=%d shorther than min length %d", pim_msg_len, PIM_MSG_REGISTER_LEN); return -1; } /* First 8 byte header checksum */ checksum = in_cksum(pim_msg, PIM_MSG_REGISTER_LEN); if (checksum != pim_checksum) { checksum = in_cksum(pim_msg, pim_msg_len); if (checksum != pim_checksum) { if (PIM_DEBUG_PIM_PACKETS) zlog_debug( "Ignoring PIM pkt from %s with invalid checksum: received=%x calculated=%x", ifp->name, pim_checksum, checksum); return -1; } } } else { checksum = in_cksum(pim_msg, pim_msg_len); if (checksum != pim_checksum) { if (PIM_DEBUG_PIM_PACKETS) zlog_debug( "Ignoring PIM pkt from %s with invalid checksum: received=%x calculated=%x", ifp->name, pim_checksum, checksum); return -1; } } if (PIM_DEBUG_PIM_PACKETS) { pim_inet4_dump("", ip_hdr->ip_src, src_str, sizeof(src_str)); pim_inet4_dump("", ip_hdr->ip_dst, dst_str, sizeof(dst_str)); zlog_debug( "Recv PIM %s packet from %s to %s on %s: ttl=%d pim_version=%d pim_msg_size=%d checksum=%x", pim_pim_msgtype2str(header->type), src_str, dst_str, ifp->name, ip_hdr->ip_ttl, header->ver, pim_msg_len, checksum); if (PIM_DEBUG_PIM_PACKETDUMP_RECV) { pim_pkt_dump(__PRETTY_FUNCTION__, pim_msg, pim_msg_len); } } switch (header->type) { case PIM_MSG_TYPE_HELLO: return pim_hello_recv(ifp, ip_hdr->ip_src, pim_msg + PIM_MSG_HEADER_LEN, pim_msg_len - PIM_MSG_HEADER_LEN); break; case PIM_MSG_TYPE_REGISTER: return pim_register_recv(ifp, ip_hdr->ip_dst, ip_hdr->ip_src, pim_msg + PIM_MSG_HEADER_LEN, pim_msg_len - PIM_MSG_HEADER_LEN); break; case PIM_MSG_TYPE_REG_STOP: return pim_register_stop_recv(ifp, pim_msg + PIM_MSG_HEADER_LEN, pim_msg_len - PIM_MSG_HEADER_LEN); break; case PIM_MSG_TYPE_JOIN_PRUNE: neigh = pim_neighbor_find(ifp, ip_hdr->ip_src); if (!neigh) { if (PIM_DEBUG_PIM_PACKETS) zlog_debug( "%s %s: non-hello PIM message type=%d from non-neighbor %s on %s", __FILE__, __PRETTY_FUNCTION__, header->type, src_str, ifp->name); return -1; } pim_neighbor_timer_reset(neigh, neigh->holdtime); return pim_joinprune_recv(ifp, neigh, ip_hdr->ip_src, pim_msg + PIM_MSG_HEADER_LEN, pim_msg_len - PIM_MSG_HEADER_LEN); break; case PIM_MSG_TYPE_ASSERT: neigh = pim_neighbor_find(ifp, ip_hdr->ip_src); if (!neigh) { if (PIM_DEBUG_PIM_PACKETS) zlog_debug( "%s %s: non-hello PIM message type=%d from non-neighbor %s on %s", __FILE__, __PRETTY_FUNCTION__, header->type, src_str, ifp->name); return -1; } pim_neighbor_timer_reset(neigh, neigh->holdtime); return pim_assert_recv(ifp, neigh, ip_hdr->ip_src, pim_msg + PIM_MSG_HEADER_LEN, pim_msg_len - PIM_MSG_HEADER_LEN); break; case PIM_MSG_TYPE_BOOTSTRAP: return pim_bsm_process(ifp, ip_hdr, pim_msg, pim_msg_len, no_fwd); break; default: if (PIM_DEBUG_PIM_PACKETS) { zlog_debug( "Recv PIM packet type %d which is not currently understood", header->type); } return -1; } return -1; } static void pim_sock_read_on(struct interface *ifp); static int pim_sock_read(struct thread *t) { struct interface *ifp, *orig_ifp; struct pim_interface *pim_ifp; int fd; struct sockaddr_in from; struct sockaddr_in to; socklen_t fromlen = sizeof(from); socklen_t tolen = sizeof(to); uint8_t buf[PIM_PIM_BUFSIZE_READ]; int len; ifindex_t ifindex = -1; int result = -1; /* defaults to bad */ static long long count = 0; int cont = 1; orig_ifp = ifp = THREAD_ARG(t); fd = THREAD_FD(t); pim_ifp = ifp->info; while (cont) { len = pim_socket_recvfromto(fd, buf, sizeof(buf), &from, &fromlen, &to, &tolen, &ifindex); if (len < 0) { if (errno == EINTR) continue; if (errno == EWOULDBLOCK || errno == EAGAIN) break; if (PIM_DEBUG_PIM_PACKETS) zlog_debug("Received errno: %d %s", errno, safe_strerror(errno)); goto done; } /* * What? So with vrf's the incoming packet is received * on the vrf interface but recvfromto above returns * the right ifindex, so just use it. We know * it's the right interface because we bind to it */ ifp = if_lookup_by_index(ifindex, pim_ifp->pim->vrf_id); if (!ifp || !ifp->info) { if (PIM_DEBUG_PIM_PACKETS) zlog_debug( "%s: Received incoming pim packet on interface(%s:%d) not yet configured for pim", __PRETTY_FUNCTION__, ifp ? ifp->name : "Unknown", ifindex); goto done; } int fail = pim_pim_packet(ifp, buf, len); if (fail) { if (PIM_DEBUG_PIM_PACKETS) zlog_debug("%s: pim_pim_packet() return=%d", __PRETTY_FUNCTION__, fail); goto done; } count++; if (count % router->packet_process == 0) cont = 0; } result = 0; /* good */ done: pim_sock_read_on(orig_ifp); if (result) { ++pim_ifp->pim_ifstat_hello_recvfail; } return result; } static void pim_sock_read_on(struct interface *ifp) { struct pim_interface *pim_ifp; zassert(ifp); zassert(ifp->info); pim_ifp = ifp->info; if (PIM_DEBUG_PIM_TRACE_DETAIL) { zlog_debug("Scheduling READ event on PIM socket fd=%d", pim_ifp->pim_sock_fd); } pim_ifp->t_pim_sock_read = NULL; thread_add_read(router->master, pim_sock_read, ifp, pim_ifp->pim_sock_fd, &pim_ifp->t_pim_sock_read); } static int pim_sock_open(struct interface *ifp) { int fd; struct pim_interface *pim_ifp = ifp->info; fd = pim_socket_mcast(IPPROTO_PIM, pim_ifp->primary_address, ifp, 0 /* loop=false */); if (fd < 0) return -1; if (pim_socket_join(fd, qpim_all_pim_routers_addr, pim_ifp->primary_address, ifp->ifindex)) { close(fd); return -2; } return fd; } void pim_ifstat_reset(struct interface *ifp) { struct pim_interface *pim_ifp; zassert(ifp); pim_ifp = ifp->info; if (!pim_ifp) { return; } pim_ifp->pim_ifstat_start = pim_time_monotonic_sec(); pim_ifp->pim_ifstat_hello_sent = 0; pim_ifp->pim_ifstat_hello_sendfail = 0; pim_ifp->pim_ifstat_hello_recv = 0; pim_ifp->pim_ifstat_hello_recvfail = 0; } void pim_sock_reset(struct interface *ifp) { struct pim_interface *pim_ifp; zassert(ifp); zassert(ifp->info); pim_ifp = ifp->info; pim_ifp->primary_address = pim_find_primary_addr(ifp); pim_ifp->pim_sock_fd = -1; pim_ifp->pim_sock_creation = 0; pim_ifp->t_pim_sock_read = NULL; pim_ifp->t_pim_hello_timer = NULL; pim_ifp->pim_hello_period = PIM_DEFAULT_HELLO_PERIOD; pim_ifp->pim_default_holdtime = -1; /* unset: means 3.5 * pim_hello_period */ pim_ifp->pim_triggered_hello_delay = PIM_DEFAULT_TRIGGERED_HELLO_DELAY; pim_ifp->pim_dr_priority = PIM_DEFAULT_DR_PRIORITY; pim_ifp->pim_propagation_delay_msec = PIM_DEFAULT_PROPAGATION_DELAY_MSEC; pim_ifp->pim_override_interval_msec = PIM_DEFAULT_OVERRIDE_INTERVAL_MSEC; if (PIM_DEFAULT_CAN_DISABLE_JOIN_SUPPRESSION) { PIM_IF_DO_PIM_CAN_DISABLE_JOIN_SUPRESSION(pim_ifp->options); } else { PIM_IF_DONT_PIM_CAN_DISABLE_JOIN_SUPRESSION(pim_ifp->options); } /* neighbors without lan_delay */ pim_ifp->pim_number_of_nonlandelay_neighbors = 0; pim_ifp->pim_neighbors_highest_propagation_delay_msec = 0; pim_ifp->pim_neighbors_highest_override_interval_msec = 0; /* DR Election */ pim_ifp->pim_dr_election_last = 0; /* timestamp */ pim_ifp->pim_dr_election_count = 0; pim_ifp->pim_dr_election_changes = 0; pim_ifp->pim_dr_num_nondrpri_neighbors = 0; /* neighbors without dr_pri */ pim_ifp->pim_dr_addr = pim_ifp->primary_address; pim_ifstat_reset(ifp); } static uint16_t ip_id = 0; static int pim_msg_send_frame(int fd, char *buf, size_t len, struct sockaddr *dst, size_t salen) { struct ip *ip = (struct ip *)buf; while (sendto(fd, buf, len, MSG_DONTWAIT, dst, salen) < 0) { char dst_str[INET_ADDRSTRLEN]; switch (errno) { case EMSGSIZE: { size_t hdrsize = sizeof(struct ip); size_t newlen1 = ((len - hdrsize) / 2) & 0xFFF8; size_t sendlen = newlen1 + hdrsize; size_t offset = ntohs(ip->ip_off); ip->ip_len = htons(sendlen); ip->ip_off = htons(offset | IP_MF); if (pim_msg_send_frame(fd, buf, sendlen, dst, salen) == 0) { struct ip *ip2 = (struct ip *)(buf + newlen1); size_t newlen2 = len - sendlen; sendlen = newlen2 + hdrsize; memcpy(ip2, ip, hdrsize); ip2->ip_len = htons(sendlen); ip2->ip_off = htons(offset + (newlen1 >> 3)); return pim_msg_send_frame(fd, (char *)ip2, sendlen, dst, salen); } } return -1; break; default: if (PIM_DEBUG_PIM_PACKETS) { pim_inet4_dump("", ip->ip_dst, dst_str, sizeof(dst_str)); zlog_warn( "%s: sendto() failure to %s: fd=%d msg_size=%zd: errno=%d: %s", __PRETTY_FUNCTION__, dst_str, fd, len, errno, safe_strerror(errno)); } return -1; break; } } return 0; } int pim_msg_send(int fd, struct in_addr src, struct in_addr dst, uint8_t *pim_msg, int pim_msg_size, const char *ifname) { struct sockaddr_in to; socklen_t tolen; unsigned char buffer[10000]; unsigned char *msg_start; uint8_t ttl; struct pim_msg_header *header; struct ip *ip; memset(buffer, 0, 10000); int sendlen = sizeof(struct ip) + pim_msg_size; msg_start = buffer + sizeof(struct ip); memcpy(msg_start, pim_msg, pim_msg_size); header = (struct pim_msg_header *)pim_msg; /* * Omnios apparently doesn't have a #define for IP default * ttl that is the same as all other platforms. */ #ifndef IPDEFTTL #define IPDEFTTL 64 #endif /* TTL for packets destine to ALL-PIM-ROUTERS is 1 */ switch (header->type) { case PIM_MSG_TYPE_HELLO: case PIM_MSG_TYPE_JOIN_PRUNE: case PIM_MSG_TYPE_BOOTSTRAP: case PIM_MSG_TYPE_ASSERT: ttl = 1; break; case PIM_MSG_TYPE_REGISTER: case PIM_MSG_TYPE_REG_STOP: case PIM_MSG_TYPE_GRAFT: case PIM_MSG_TYPE_GRAFT_ACK: case PIM_MSG_TYPE_CANDIDATE: ttl = IPDEFTTL; break; default: ttl = MAXTTL; break; } ip = (struct ip *)buffer; ip->ip_id = htons(++ip_id); ip->ip_hl = 5; ip->ip_v = 4; ip->ip_p = PIM_IP_PROTO_PIM; ip->ip_src = src; ip->ip_dst = dst; ip->ip_ttl = ttl; ip->ip_len = htons(sendlen); if (PIM_DEBUG_PIM_PACKETS) { char dst_str[INET_ADDRSTRLEN]; pim_inet4_dump("", dst, dst_str, sizeof(dst_str)); zlog_debug("%s: to %s on %s: msg_size=%d checksum=%x", __PRETTY_FUNCTION__, dst_str, ifname, pim_msg_size, header->checksum); } memset(&to, 0, sizeof(to)); to.sin_family = AF_INET; to.sin_addr = dst; tolen = sizeof(to); if (PIM_DEBUG_PIM_PACKETDUMP_SEND) { pim_pkt_dump(__PRETTY_FUNCTION__, pim_msg, pim_msg_size); } pim_msg_send_frame(fd, (char *)buffer, sendlen, (struct sockaddr *)&to, tolen); return 0; } static int hello_send(struct interface *ifp, uint16_t holdtime) { uint8_t pim_msg[PIM_PIM_BUFSIZE_WRITE]; struct pim_interface *pim_ifp; int pim_tlv_size; int pim_msg_size; pim_ifp = ifp->info; if (PIM_DEBUG_PIM_HELLO) { char dst_str[INET_ADDRSTRLEN]; pim_inet4_dump("", qpim_all_pim_routers_addr, dst_str, sizeof(dst_str)); zlog_debug( "%s: to %s on %s: holdt=%u prop_d=%u overr_i=%u dis_join_supp=%d dr_prio=%u gen_id=%08x addrs=%d", __PRETTY_FUNCTION__, dst_str, ifp->name, holdtime, pim_ifp->pim_propagation_delay_msec, pim_ifp->pim_override_interval_msec, PIM_IF_TEST_PIM_CAN_DISABLE_JOIN_SUPRESSION( pim_ifp->options), pim_ifp->pim_dr_priority, pim_ifp->pim_generation_id, listcount(ifp->connected)); } pim_tlv_size = pim_hello_build_tlv( ifp, pim_msg + PIM_PIM_MIN_LEN, sizeof(pim_msg) - PIM_PIM_MIN_LEN, holdtime, pim_ifp->pim_dr_priority, pim_ifp->pim_generation_id, pim_ifp->pim_propagation_delay_msec, pim_ifp->pim_override_interval_msec, PIM_IF_TEST_PIM_CAN_DISABLE_JOIN_SUPRESSION(pim_ifp->options)); if (pim_tlv_size < 0) { return -1; } pim_msg_size = pim_tlv_size + PIM_PIM_MIN_LEN; zassert(pim_msg_size >= PIM_PIM_MIN_LEN); zassert(pim_msg_size <= PIM_PIM_BUFSIZE_WRITE); pim_msg_build_header(pim_msg, pim_msg_size, PIM_MSG_TYPE_HELLO, false); if (pim_msg_send(pim_ifp->pim_sock_fd, pim_ifp->primary_address, qpim_all_pim_routers_addr, pim_msg, pim_msg_size, ifp->name)) { if (PIM_DEBUG_PIM_HELLO) { zlog_debug( "%s: could not send PIM message on interface %s", __PRETTY_FUNCTION__, ifp->name); } return -2; } return 0; } static int pim_hello_send(struct interface *ifp, uint16_t holdtime) { struct pim_interface *pim_ifp = ifp->info; if (if_is_loopback_or_vrf(ifp)) return 0; if (hello_send(ifp, holdtime)) { ++pim_ifp->pim_ifstat_hello_sendfail; if (PIM_DEBUG_PIM_HELLO) { zlog_warn("Could not send PIM hello on interface %s", ifp->name); } return -1; } ++pim_ifp->pim_ifstat_hello_sent; return 0; } static void hello_resched(struct interface *ifp) { struct pim_interface *pim_ifp; pim_ifp = ifp->info; if (PIM_DEBUG_PIM_HELLO) { zlog_debug("Rescheduling %d sec hello on interface %s", pim_ifp->pim_hello_period, ifp->name); } THREAD_OFF(pim_ifp->t_pim_hello_timer); thread_add_timer(router->master, on_pim_hello_send, ifp, pim_ifp->pim_hello_period, &pim_ifp->t_pim_hello_timer); } /* Periodic hello timer */ static int on_pim_hello_send(struct thread *t) { struct pim_interface *pim_ifp; struct interface *ifp; ifp = THREAD_ARG(t); pim_ifp = ifp->info; /* * Schedule next hello */ hello_resched(ifp); /* * Send hello */ return pim_hello_send(ifp, PIM_IF_DEFAULT_HOLDTIME(pim_ifp)); } /* RFC 4601: 4.3.1. Sending Hello Messages Thus, if a router needs to send a Join/Prune or Assert message on an interface on which it has not yet sent a Hello message with the currently configured IP address, then it MUST immediately send the relevant Hello message without waiting for the Hello Timer to expire, followed by the Join/Prune or Assert message. */ void pim_hello_restart_now(struct interface *ifp) { struct pim_interface *pim_ifp; pim_ifp = ifp->info; /* * Reset next hello timer */ hello_resched(ifp); /* * Immediately send hello */ pim_hello_send(ifp, PIM_IF_DEFAULT_HOLDTIME(pim_ifp)); } /* RFC 4601: 4.3.1. Sending Hello Messages To allow new or rebooting routers to learn of PIM neighbors quickly, when a Hello message is received from a new neighbor, or a Hello message with a new GenID is received from an existing neighbor, a new Hello message should be sent on this interface after a randomized delay between 0 and Triggered_Hello_Delay. */ void pim_hello_restart_triggered(struct interface *ifp) { struct pim_interface *pim_ifp; int triggered_hello_delay_msec; int random_msec; pim_ifp = ifp->info; /* * No need to ever start loopback or vrf device hello's */ if (if_is_loopback_or_vrf(ifp)) return; /* * There exists situations where we have the a RPF out this * interface, but we haven't formed a neighbor yet. This * happens especially during interface flaps. While * we would like to handle this more gracefully in other * parts of the code. In order to get us up and running * let's just send the hello immediate'ish * This should be revisited when we get nexthop tracking * in and when we have a better handle on safely * handling the rpf information for upstreams that * we cannot legally reach yet. */ triggered_hello_delay_msec = 1; // triggered_hello_delay_msec = 1000 * // pim_ifp->pim_triggered_hello_delay; if (pim_ifp->t_pim_hello_timer) { long remain_msec = pim_time_timer_remain_msec(pim_ifp->t_pim_hello_timer); if (remain_msec <= triggered_hello_delay_msec) { /* Rescheduling hello would increase the delay, then it's faster to just wait for the scheduled periodic hello. */ return; } THREAD_OFF(pim_ifp->t_pim_hello_timer); } random_msec = triggered_hello_delay_msec; // random_msec = random() % (triggered_hello_delay_msec + 1); if (PIM_DEBUG_PIM_HELLO) { zlog_debug("Scheduling %d msec triggered hello on interface %s", random_msec, ifp->name); } thread_add_timer_msec(router->master, on_pim_hello_send, ifp, random_msec, &pim_ifp->t_pim_hello_timer); } int pim_sock_add(struct interface *ifp) { struct pim_interface *pim_ifp; uint32_t old_genid; pim_ifp = ifp->info; zassert(pim_ifp); if (pim_ifp->pim_sock_fd >= 0) { if (PIM_DEBUG_PIM_PACKETS) zlog_debug( "Can't recreate existing PIM socket fd=%d for interface %s", pim_ifp->pim_sock_fd, ifp->name); return -1; } pim_ifp->pim_sock_fd = pim_sock_open(ifp); if (pim_ifp->pim_sock_fd < 0) { if (PIM_DEBUG_PIM_PACKETS) zlog_debug("Could not open PIM socket on interface %s", ifp->name); return -2; } pim_socket_ip_hdr(pim_ifp->pim_sock_fd); pim_ifp->t_pim_sock_read = NULL; pim_ifp->pim_sock_creation = pim_time_monotonic_sec(); /* * Just ensure that the new generation id * actually chooses something different. * Actually ran across a case where this * happened, pre-switch to random(). * While this is unlikely to happen now * let's make sure it doesn't. */ old_genid = pim_ifp->pim_generation_id; while (old_genid == pim_ifp->pim_generation_id) pim_ifp->pim_generation_id = random(); zlog_info("PIM INTERFACE UP: on interface %s ifindex=%d", ifp->name, ifp->ifindex); /* * Start receiving PIM messages */ pim_sock_read_on(ifp); /* * Start sending PIM hello's */ pim_hello_restart_triggered(ifp); return 0; } frr-7.2.1/pimd/pim_pim.h0000644000000000000000000000443313610377563011754 00000000000000/* * PIM for Quagga * Copyright (C) 2008 Everton da Silva Marques * * This program is free software; you can redistribute it and/or modify * it under the terms of the 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; see the file COPYING; if not, write to the Free Software * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA */ #ifndef PIM_PIM_H #define PIM_PIM_H #include #include "if.h" #define PIM_PIM_BUFSIZE_READ (20000) #define PIM_PIM_BUFSIZE_WRITE (20000) #define PIM_DEFAULT_HELLO_PERIOD (30) /* seconds, RFC 4601: 4.11 */ #define PIM_DEFAULT_TRIGGERED_HELLO_DELAY (5) /* seconds, RFC 4601: 4.11 */ #define PIM_DEFAULT_DR_PRIORITY (1) /* RFC 4601: 4.3.1 */ #define PIM_DEFAULT_PROPAGATION_DELAY_MSEC (500) /* RFC 4601: 4.11. Timer Values */ #define PIM_DEFAULT_OVERRIDE_INTERVAL_MSEC (2500) /* RFC 4601: 4.11. Timer Values */ #define PIM_DEFAULT_CAN_DISABLE_JOIN_SUPPRESSION (0) /* boolean */ #define PIM_DEFAULT_T_PERIODIC (60) /* RFC 4601: 4.11. Timer Values */ enum pim_msg_type { PIM_MSG_TYPE_HELLO = 0, PIM_MSG_TYPE_REGISTER, PIM_MSG_TYPE_REG_STOP, PIM_MSG_TYPE_JOIN_PRUNE, PIM_MSG_TYPE_BOOTSTRAP, PIM_MSG_TYPE_ASSERT, PIM_MSG_TYPE_GRAFT, PIM_MSG_TYPE_GRAFT_ACK, PIM_MSG_TYPE_CANDIDATE }; void pim_ifstat_reset(struct interface *ifp); void pim_sock_reset(struct interface *ifp); int pim_sock_add(struct interface *ifp); void pim_sock_delete(struct interface *ifp, const char *delete_message); void pim_hello_restart_now(struct interface *ifp); void pim_hello_restart_triggered(struct interface *ifp); int pim_pim_packet(struct interface *ifp, uint8_t *buf, size_t len); int pim_msg_send(int fd, struct in_addr src, struct in_addr dst, uint8_t *pim_msg, int pim_msg_size, const char *ifname); #endif /* PIM_PIM_H */ frr-7.2.1/pimd/pim_register.c0000644000000000000000000003421713610377563013011 00000000000000/* * PIM for Quagga * Copyright (C) 2015 Cumulus Networks, Inc. * Donald Sharp * * This program is free software; you can redistribute it and/or modify * it under the terms of the 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; see the file COPYING; if not, write to the Free Software * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA */ #include #include "log.h" #include "if.h" #include "thread.h" #include "prefix.h" #include "vty.h" #include "plist.h" #include "pimd.h" #include "pim_mroute.h" #include "pim_iface.h" #include "pim_msg.h" #include "pim_pim.h" #include "pim_str.h" #include "pim_rp.h" #include "pim_register.h" #include "pim_upstream.h" #include "pim_br.h" #include "pim_rpf.h" #include "pim_oil.h" #include "pim_zebra.h" #include "pim_join.h" #include "pim_util.h" #include "pim_ssm.h" #include "pim_vxlan.h" struct thread *send_test_packet_timer = NULL; void pim_register_join(struct pim_upstream *up) { struct pim_instance *pim = up->channel_oil->pim; if (pim_is_grp_ssm(pim, up->sg.grp)) { if (PIM_DEBUG_PIM_EVENTS) zlog_debug("%s register setup skipped as group is SSM", up->sg_str); return; } pim_channel_add_oif(up->channel_oil, pim->regiface, PIM_OIF_FLAG_PROTO_PIM); up->reg_state = PIM_REG_JOIN; pim_vxlan_update_sg_reg_state(pim, up, true /*reg_join*/); } void pim_register_stop_send(struct interface *ifp, struct prefix_sg *sg, struct in_addr src, struct in_addr originator) { struct pim_interface *pinfo; unsigned char buffer[10000]; unsigned int b1length = 0; unsigned int length; uint8_t *b1; struct prefix p; if (PIM_DEBUG_PIM_REG) { zlog_debug("Sending Register stop for %s to %s on %s", pim_str_sg_dump(sg), inet_ntoa(originator), ifp->name); } memset(buffer, 0, 10000); b1 = (uint8_t *)buffer + PIM_MSG_REGISTER_STOP_LEN; length = pim_encode_addr_group(b1, AFI_IP, 0, 0, sg->grp); b1length += length; b1 += length; p.family = AF_INET; p.u.prefix4 = sg->src; p.prefixlen = 32; length = pim_encode_addr_ucast(b1, &p); b1length += length; pim_msg_build_header(buffer, b1length + PIM_MSG_REGISTER_STOP_LEN, PIM_MSG_TYPE_REG_STOP, false); pinfo = (struct pim_interface *)ifp->info; if (!pinfo) { if (PIM_DEBUG_PIM_TRACE) zlog_debug("%s: No pinfo!", __PRETTY_FUNCTION__); return; } if (pim_msg_send(pinfo->pim_sock_fd, src, originator, buffer, b1length + PIM_MSG_REGISTER_STOP_LEN, ifp->name)) { if (PIM_DEBUG_PIM_TRACE) { zlog_debug( "%s: could not send PIM register stop message on interface %s", __PRETTY_FUNCTION__, ifp->name); } } ++pinfo->pim_ifstat_reg_stop_send; } int pim_register_stop_recv(struct interface *ifp, uint8_t *buf, int buf_size) { struct pim_interface *pim_ifp = ifp->info; struct pim_instance *pim = pim_ifp->pim; struct pim_upstream *upstream = NULL; struct prefix source; struct prefix_sg sg; int l; memset(&sg, 0, sizeof(struct prefix_sg)); l = pim_parse_addr_group(&sg, buf, buf_size); buf += l; buf_size -= l; pim_parse_addr_ucast(&source, buf, buf_size); sg.src = source.u.prefix4; upstream = pim_upstream_find(pim, &sg); if (!upstream) { return 0; } if (PIM_DEBUG_PIM_REG) zlog_debug("Received Register stop for %s", upstream->sg_str); switch (upstream->reg_state) { case PIM_REG_NOINFO: case PIM_REG_PRUNE: return 0; break; case PIM_REG_JOIN: upstream->reg_state = PIM_REG_PRUNE; pim_channel_del_oif(upstream->channel_oil, pim->regiface, PIM_OIF_FLAG_PROTO_PIM); pim_upstream_start_register_stop_timer(upstream, 0); pim_vxlan_update_sg_reg_state(pim, upstream, false/*reg_join*/); break; case PIM_REG_JOIN_PENDING: upstream->reg_state = PIM_REG_PRUNE; pim_upstream_start_register_stop_timer(upstream, 0); return 0; break; } return 0; } void pim_register_send(const uint8_t *buf, int buf_size, struct in_addr src, struct pim_rpf *rpg, int null_register, struct pim_upstream *up) { unsigned char buffer[10000]; unsigned char *b1; struct pim_interface *pinfo; struct interface *ifp; if (PIM_DEBUG_PIM_REG) { zlog_debug("Sending %s %sRegister Packet to %s", up->sg_str, null_register ? "NULL " : "", inet_ntoa(rpg->rpf_addr.u.prefix4)); } ifp = rpg->source_nexthop.interface; if (!ifp) { if (PIM_DEBUG_PIM_REG) zlog_debug("%s: No interface to transmit register on", __PRETTY_FUNCTION__); return; } pinfo = (struct pim_interface *)ifp->info; if (!pinfo) { if (PIM_DEBUG_PIM_REG) zlog_debug( "%s: Interface: %s not configured for pim to trasmit on!\n", __PRETTY_FUNCTION__, ifp->name); return; } if (PIM_DEBUG_PIM_REG) { char rp_str[INET_ADDRSTRLEN]; strlcpy(rp_str, inet_ntoa(rpg->rpf_addr.u.prefix4), sizeof(rp_str)); zlog_debug("%s: Sending %s %sRegister Packet to %s on %s", __PRETTY_FUNCTION__, up->sg_str, null_register ? "NULL " : "", rp_str, ifp->name); } memset(buffer, 0, 10000); b1 = buffer + PIM_MSG_HEADER_LEN; *b1 |= null_register << 6; b1 = buffer + PIM_MSG_REGISTER_LEN; memcpy(b1, (const unsigned char *)buf, buf_size); pim_msg_build_header(buffer, buf_size + PIM_MSG_REGISTER_LEN, PIM_MSG_TYPE_REGISTER, false); ++pinfo->pim_ifstat_reg_send; if (pim_msg_send(pinfo->pim_sock_fd, src, rpg->rpf_addr.u.prefix4, buffer, buf_size + PIM_MSG_REGISTER_LEN, ifp->name)) { if (PIM_DEBUG_PIM_TRACE) { zlog_debug( "%s: could not send PIM register message on interface %s", __PRETTY_FUNCTION__, ifp->name); } return; } } void pim_null_register_send(struct pim_upstream *up) { struct ip ip_hdr; struct pim_interface *pim_ifp; struct pim_rpf *rpg; struct in_addr src; pim_ifp = up->rpf.source_nexthop.interface->info; if (!pim_ifp) { if (PIM_DEBUG_TRACE) zlog_debug( "%s: Cannot send null-register for %s no valid iif", __PRETTY_FUNCTION__, up->sg_str); return; } rpg = RP(pim_ifp->pim, up->sg.grp); if (!rpg) { if (PIM_DEBUG_TRACE) zlog_debug( "%s: Cannot send null-register for %s no RPF to the RP", __PRETTY_FUNCTION__, up->sg_str); return; } memset(&ip_hdr, 0, sizeof(struct ip)); ip_hdr.ip_p = PIM_IP_PROTO_PIM; ip_hdr.ip_hl = 5; ip_hdr.ip_v = 4; ip_hdr.ip_src = up->sg.src; ip_hdr.ip_dst = up->sg.grp; ip_hdr.ip_len = htons(20); /* checksum is broken */ src = pim_ifp->primary_address; if (PIM_UPSTREAM_FLAG_TEST_SRC_VXLAN_ORIG(up->flags)) { if (!pim_vxlan_get_register_src(pim_ifp->pim, up, &src)) { if (PIM_DEBUG_TRACE) zlog_debug( "%s: Cannot send null-register for %s vxlan-aa PIP unavailable", __PRETTY_FUNCTION__, up->sg_str); return; } } pim_register_send((uint8_t *)&ip_hdr, sizeof(struct ip), src, rpg, 1, up); } /* * 4.4.2 Receiving Register Messages at the RP * * When an RP receives a Register message, the course of action is * decided according to the following pseudocode: * * packet_arrives_on_rp_tunnel( pkt ) { * if( outer.dst is not one of my addresses ) { * drop the packet silently. * # Note: this may be a spoofing attempt * } * if( I_am_RP(G) AND outer.dst == RP(G) ) { * sentRegisterStop = false; * if ( register.borderbit == true ) { * if ( PMBR(S,G) == unknown ) { * PMBR(S,G) = outer.src * } else if ( outer.src != PMBR(S,G) ) { * send Register-Stop(S,G) to outer.src * drop the packet silently. * } * } * if ( SPTbit(S,G) OR * ( SwitchToSptDesired(S,G) AND * ( inherited_olist(S,G) == NULL ))) { * send Register-Stop(S,G) to outer.src * sentRegisterStop = true; * } * if ( SPTbit(S,G) OR SwitchToSptDesired(S,G) ) { * if ( sentRegisterStop == true ) { * set KeepaliveTimer(S,G) to RP_Keepalive_Period; * } else { * set KeepaliveTimer(S,G) to Keepalive_Period; * } * } * if( !SPTbit(S,G) AND ! pkt.NullRegisterBit ) { * decapsulate and forward the inner packet to * inherited_olist(S,G,rpt) # Note (+) * } * } else { * send Register-Stop(S,G) to outer.src * # Note (*) * } * } */ int pim_register_recv(struct interface *ifp, struct in_addr dest_addr, struct in_addr src_addr, uint8_t *tlv_buf, int tlv_buf_size) { int sentRegisterStop = 0; struct ip *ip_hdr; struct prefix_sg sg; uint32_t *bits; int i_am_rp = 0; struct pim_interface *pim_ifp = NULL; pim_ifp = ifp->info; #define PIM_MSG_REGISTER_BIT_RESERVED_LEN 4 ip_hdr = (struct ip *)(tlv_buf + PIM_MSG_REGISTER_BIT_RESERVED_LEN); if (!pim_rp_check_is_my_ip_address(pim_ifp->pim, dest_addr)) { if (PIM_DEBUG_PIM_REG) { char dest[INET_ADDRSTRLEN]; pim_inet4_dump("", dest_addr, dest, sizeof(dest)); zlog_debug( "%s: Received Register message for destination address: %s that I do not own", __func__, dest); } return 0; } ++pim_ifp->pim_ifstat_reg_recv; /* * Please note this is not drawn to get the correct bit/data size * * The entirety of the REGISTER packet looks like this: * ------------------------------------------------------------- * | Ver | Type | Reserved | Checksum | * |-----------------------------------------------------------| * |B|N| Reserved 2 | * |-----------------------------------------------------------| * | Encap | IP HDR | * | Mcast | | * | Packet |--------------------------------------------------| * | | Mcast Data | * | | | * ... * * tlv_buf when received from the caller points at the B bit * We need to know the inner source and dest */ bits = (uint32_t *)tlv_buf; /* * tlv_buf points to the start of the |B|N|... Reserved * Line above. So we need to add 4 bytes to get to the * start of the actual Encapsulated data. */ memset(&sg, 0, sizeof(struct prefix_sg)); sg.src = ip_hdr->ip_src; sg.grp = ip_hdr->ip_dst; i_am_rp = I_am_RP(pim_ifp->pim, sg.grp); if (PIM_DEBUG_PIM_REG) { char src_str[INET_ADDRSTRLEN]; pim_inet4_dump("", src_addr, src_str, sizeof(src_str)); zlog_debug("Received Register message%s from %s on %s, rp: %d", pim_str_sg_dump(&sg), src_str, ifp->name, i_am_rp); } if (i_am_rp && (dest_addr.s_addr == ((RP(pim_ifp->pim, sg.grp))->rpf_addr.u.prefix4.s_addr))) { sentRegisterStop = 0; if (*bits & PIM_REGISTER_BORDER_BIT) { struct in_addr pimbr = pim_br_get_pmbr(&sg); if (PIM_DEBUG_PIM_PACKETS) zlog_debug( "%s: Received Register message with Border bit set", __func__); if (pimbr.s_addr == pim_br_unknown.s_addr) pim_br_set_pmbr(&sg, src_addr); else if (src_addr.s_addr != pimbr.s_addr) { pim_register_stop_send(ifp, &sg, dest_addr, src_addr); if (PIM_DEBUG_PIM_PACKETS) zlog_debug( "%s: Sending register-Stop to %s and dropping mr. packet", __func__, "Sender"); /* Drop Packet Silently */ return 0; } } struct pim_upstream *upstream = pim_upstream_find(pim_ifp->pim, &sg); /* * If we don't have a place to send ignore the packet */ if (!upstream) { upstream = pim_upstream_add( pim_ifp->pim, &sg, ifp, PIM_UPSTREAM_FLAG_MASK_SRC_STREAM, __PRETTY_FUNCTION__, NULL); if (!upstream) { zlog_warn("Failure to create upstream state"); return 1; } upstream->upstream_register = src_addr; } else { /* * If the FHR has set a very very fast register timer * there exists a possibility that the incoming NULL * register * is happening before we set the spt bit. If so * Do a quick check to update the counters and * then set the spt bit as appropriate */ if (upstream->sptbit != PIM_UPSTREAM_SPTBIT_TRUE) { pim_mroute_update_counters( upstream->channel_oil); /* * Have we seen packets? */ if (upstream->channel_oil->cc.oldpktcnt < upstream->channel_oil->cc.pktcnt) pim_upstream_set_sptbit( upstream, upstream->rpf.source_nexthop .interface); } } if ((upstream->sptbit == PIM_UPSTREAM_SPTBIT_TRUE) || ((SwitchToSptDesired(pim_ifp->pim, &sg)) && pim_upstream_inherited_olist(pim_ifp->pim, upstream) == 0)) { // pim_scan_individual_oil (upstream->channel_oil); pim_register_stop_send(ifp, &sg, dest_addr, src_addr); sentRegisterStop = 1; } else { if (PIM_DEBUG_PIM_REG) zlog_debug("(%s) sptbit: %d", upstream->sg_str, upstream->sptbit); } if ((upstream->sptbit == PIM_UPSTREAM_SPTBIT_TRUE) || (SwitchToSptDesired(pim_ifp->pim, &sg))) { if (sentRegisterStop) { pim_upstream_keep_alive_timer_start( upstream, pim_ifp->pim->rp_keep_alive_time); } else { pim_upstream_keep_alive_timer_start( upstream, pim_ifp->pim->keep_alive_time); } } if (!(upstream->sptbit == PIM_UPSTREAM_SPTBIT_TRUE) && !(*bits & PIM_REGISTER_NR_BIT)) { // decapsulate and forward the iner packet to // inherited_olist(S,G,rpt) // This is taken care of by the kernel for us } pim_upstream_msdp_reg_timer_start(upstream); } else { if (PIM_DEBUG_PIM_REG) { if (!i_am_rp) zlog_debug( "Received Register packet for %s, Rejecting packet because I am not the RP configured for group", pim_str_sg_dump(&sg)); else zlog_debug( "Received Register packet for %s, Rejecting packet because the dst ip address is not the actual RP", pim_str_sg_dump(&sg)); } pim_register_stop_send(ifp, &sg, dest_addr, src_addr); } return 0; } frr-7.2.1/pimd/pim_register.h0000644000000000000000000000324213610377563013010 00000000000000/* * PIM for Quagga * Copyright (C) 2015 Cumulus Networks, Inc. * Donald Sharp * * This program is free software; you can redistribute it and/or modify * it under the terms of the 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; see the file COPYING; if not, write to the Free Software * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA */ #ifndef PIM_REGISTER_H #define PIM_REGISTER_H #include #include "if.h" #define PIM_REGISTER_BORDER_BIT 0x80000000 #define PIM_REGISTER_NR_BIT 0x40000000 #define PIM_MSG_REGISTER_LEN (8) #define PIM_MSG_REGISTER_STOP_LEN (4) int pim_register_stop_recv(struct interface *ifp, uint8_t *buf, int buf_size); int pim_register_recv(struct interface *ifp, struct in_addr dest_addr, struct in_addr src_addr, uint8_t *tlv_buf, int tlv_buf_size); void pim_register_send(const uint8_t *buf, int buf_size, struct in_addr src, struct pim_rpf *rpg, int null_register, struct pim_upstream *up); void pim_register_stop_send(struct interface *ifp, struct prefix_sg *sg, struct in_addr src, struct in_addr originator); void pim_register_join(struct pim_upstream *up); void pim_null_register_send(struct pim_upstream *up); #endif frr-7.2.1/pimd/pim_routemap.c0000644000000000000000000000312513610377563013013 00000000000000/* PIM Route-map Code * Copyright (C) 2016 Cumulus Networks * Copyright (C) 1999 Kunihiro Ishiguro * * This file is part of Quagga * * Quagga is free software; you can redistribute it and/or modify it * under the terms of the GNU General Public License as published by the * Free Software Foundation; either version 2, or (at your option) any * later version. * * Quagga 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; see the file COPYING; if not, write to the Free Software * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA */ #include #include "if.h" #include "vty.h" #include "routemap.h" #include "pimd.h" static void pim_route_map_add(const char *rmap_name) { route_map_notify_dependencies(rmap_name, RMAP_EVENT_MATCH_ADDED); } static void pim_route_map_delete(const char *rmap_name) { route_map_notify_dependencies(rmap_name, RMAP_EVENT_MATCH_DELETED); } static void pim_route_map_event(const char *rmap_name) { route_map_notify_dependencies(rmap_name, RMAP_EVENT_MATCH_ADDED); } void pim_route_map_init(void) { route_map_init(); route_map_add_hook(pim_route_map_add); route_map_delete_hook(pim_route_map_delete); route_map_event_hook(pim_route_map_event); } void pim_route_map_terminate(void) { route_map_finish(); } frr-7.2.1/pimd/pim_rp.c0000644000000000000000000010606713610377563011611 00000000000000/* * PIM for Quagga * Copyright (C) 2015 Cumulus Networks, Inc. * Donald Sharp * * This program is free software; you can redistribute it and/or modify * it under the terms of the 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; see the file COPYING; if not, write to the Free Software * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA */ #include #include "lib/json.h" #include "log.h" #include "network.h" #include "if.h" #include "linklist.h" #include "prefix.h" #include "memory.h" #include "vty.h" #include "vrf.h" #include "plist.h" #include "nexthop.h" #include "table.h" #include "lib_errors.h" #include "pimd.h" #include "pim_vty.h" #include "pim_str.h" #include "pim_iface.h" #include "pim_rp.h" #include "pim_str.h" #include "pim_rpf.h" #include "pim_sock.h" #include "pim_memory.h" #include "pim_iface.h" #include "pim_msdp.h" #include "pim_nht.h" #include "pim_mroute.h" #include "pim_oil.h" #include "pim_zebra.h" #include "pim_bsm.h" /* Cleanup pim->rpf_hash each node data */ void pim_rp_list_hash_clean(void *data) { struct pim_nexthop_cache *pnc = (struct pim_nexthop_cache *)data; list_delete(&pnc->rp_list); hash_clean(pnc->upstream_hash, NULL); hash_free(pnc->upstream_hash); pnc->upstream_hash = NULL; if (pnc->nexthop) nexthops_free(pnc->nexthop); XFREE(MTYPE_PIM_NEXTHOP_CACHE, pnc); } static void pim_rp_info_free(struct rp_info *rp_info) { XFREE(MTYPE_PIM_FILTER_NAME, rp_info->plist); XFREE(MTYPE_PIM_RP, rp_info); } int pim_rp_list_cmp(void *v1, void *v2) { struct rp_info *rp1 = (struct rp_info *)v1; struct rp_info *rp2 = (struct rp_info *)v2; /* * Sort by RP IP address */ if (rp1->rp.rpf_addr.u.prefix4.s_addr < rp2->rp.rpf_addr.u.prefix4.s_addr) return -1; if (rp1->rp.rpf_addr.u.prefix4.s_addr > rp2->rp.rpf_addr.u.prefix4.s_addr) return 1; /* * Sort by group IP address */ if (rp1->group.u.prefix4.s_addr < rp2->group.u.prefix4.s_addr) return -1; if (rp1->group.u.prefix4.s_addr > rp2->group.u.prefix4.s_addr) return 1; return 0; } void pim_rp_init(struct pim_instance *pim) { struct rp_info *rp_info; struct route_node *rn; pim->rp_list = list_new(); pim->rp_list->del = (void (*)(void *))pim_rp_info_free; pim->rp_list->cmp = pim_rp_list_cmp; pim->rp_table = route_table_init(); rp_info = XCALLOC(MTYPE_PIM_RP, sizeof(*rp_info)); if (!str2prefix("224.0.0.0/4", &rp_info->group)) { flog_err(EC_LIB_DEVELOPMENT, "Unable to convert 224.0.0.0/4 to prefix"); list_delete(&pim->rp_list); route_table_finish(pim->rp_table); XFREE(MTYPE_PIM_RP, rp_info); return; } rp_info->group.family = AF_INET; rp_info->rp.rpf_addr.family = AF_INET; rp_info->rp.rpf_addr.prefixlen = IPV4_MAX_PREFIXLEN; rp_info->rp.rpf_addr.u.prefix4.s_addr = INADDR_NONE; listnode_add(pim->rp_list, rp_info); rn = route_node_get(pim->rp_table, &rp_info->group); rn->info = rp_info; if (PIM_DEBUG_TRACE) zlog_debug( "Allocated: %p for rp_info: %p(224.0.0.0/4) Lock: %d", rn, rp_info, rn->lock); } void pim_rp_free(struct pim_instance *pim) { if (pim->rp_list) list_delete(&pim->rp_list); } /* * Given an RP's prefix-list, return the RP's rp_info for that prefix-list */ static struct rp_info *pim_rp_find_prefix_list(struct pim_instance *pim, struct in_addr rp, const char *plist) { struct listnode *node; struct rp_info *rp_info; for (ALL_LIST_ELEMENTS_RO(pim->rp_list, node, rp_info)) { if (rp.s_addr == rp_info->rp.rpf_addr.u.prefix4.s_addr && rp_info->plist && strcmp(rp_info->plist, plist) == 0) { return rp_info; } } return NULL; } /* * Return true if plist is used by any rp_info */ static int pim_rp_prefix_list_used(struct pim_instance *pim, const char *plist) { struct listnode *node; struct rp_info *rp_info; for (ALL_LIST_ELEMENTS_RO(pim->rp_list, node, rp_info)) { if (rp_info->plist && strcmp(rp_info->plist, plist) == 0) { return 1; } } return 0; } /* * Given an RP's address, return the RP's rp_info that is an exact match for * 'group' */ static struct rp_info *pim_rp_find_exact(struct pim_instance *pim, struct in_addr rp, const struct prefix *group) { struct listnode *node; struct rp_info *rp_info; for (ALL_LIST_ELEMENTS_RO(pim->rp_list, node, rp_info)) { if (rp.s_addr == rp_info->rp.rpf_addr.u.prefix4.s_addr && prefix_same(&rp_info->group, group)) return rp_info; } return NULL; } /* * Given a group, return the rp_info for that group */ struct rp_info *pim_rp_find_match_group(struct pim_instance *pim, const struct prefix *group) { struct listnode *node; struct rp_info *best = NULL; struct rp_info *rp_info; struct prefix_list *plist; const struct prefix *p, *bp; struct route_node *rn; bp = NULL; for (ALL_LIST_ELEMENTS_RO(pim->rp_list, node, rp_info)) { if (rp_info->plist) { plist = prefix_list_lookup(AFI_IP, rp_info->plist); if (prefix_list_apply_which_prefix(plist, &p, group) == PREFIX_DENY) continue; if (!best) { best = rp_info; bp = p; continue; } if (bp && bp->prefixlen < p->prefixlen) { best = rp_info; bp = p; } } } rn = route_node_match(pim->rp_table, group); if (!rn) { flog_err( EC_LIB_DEVELOPMENT, "%s: BUG We should have found default group information\n", __PRETTY_FUNCTION__); return best; } rp_info = rn->info; if (PIM_DEBUG_TRACE) { char buf[PREFIX_STRLEN]; route_unlock_node(rn); zlog_debug("Lookedup: %p for rp_info: %p(%s) Lock: %d", rn, rp_info, prefix2str(&rp_info->group, buf, sizeof(buf)), rn->lock); } if (!best) return rp_info; if (rp_info->group.prefixlen < best->group.prefixlen) best = rp_info; return best; } /* * When the user makes "ip pim rp" configuration changes or if they change the * prefix-list(s) used by these statements we must tickle the upstream state * for each group to make them re-lookup who their RP should be. * * This is a placeholder function for now. */ static void pim_rp_refresh_group_to_rp_mapping(struct pim_instance *pim) { pim_msdp_i_am_rp_changed(pim); } void pim_rp_prefix_list_update(struct pim_instance *pim, struct prefix_list *plist) { struct listnode *node; struct rp_info *rp_info; int refresh_needed = 0; for (ALL_LIST_ELEMENTS_RO(pim->rp_list, node, rp_info)) { if (rp_info->plist && strcmp(rp_info->plist, prefix_list_name(plist)) == 0) { refresh_needed = 1; break; } } if (refresh_needed) pim_rp_refresh_group_to_rp_mapping(pim); } static int pim_rp_check_interface_addrs(struct rp_info *rp_info, struct pim_interface *pim_ifp) { struct listnode *node; struct pim_secondary_addr *sec_addr; if (pim_ifp->primary_address.s_addr == rp_info->rp.rpf_addr.u.prefix4.s_addr) return 1; if (!pim_ifp->sec_addr_list) { return 0; } for (ALL_LIST_ELEMENTS_RO(pim_ifp->sec_addr_list, node, sec_addr)) { if (prefix_same(&sec_addr->addr, &rp_info->rp.rpf_addr)) { return 1; } } return 0; } static void pim_rp_check_interfaces(struct pim_instance *pim, struct rp_info *rp_info) { struct interface *ifp; rp_info->i_am_rp = 0; FOR_ALL_INTERFACES (pim->vrf, ifp) { struct pim_interface *pim_ifp = ifp->info; if (!pim_ifp) continue; if (pim_rp_check_interface_addrs(rp_info, pim_ifp)) { rp_info->i_am_rp = 1; } } } void pim_upstream_update(struct pim_instance *pim, struct pim_upstream *up) { struct pim_rpf old_rpf; enum pim_rpf_result rpf_result; struct in_addr old_upstream_addr; struct in_addr new_upstream_addr; struct prefix nht_p; old_upstream_addr = up->upstream_addr; pim_rp_set_upstream_addr(pim, &new_upstream_addr, up->sg.src, up->sg.grp); if (PIM_DEBUG_TRACE) zlog_debug("%s: pim upstream update for old upstream %s", __PRETTY_FUNCTION__, inet_ntoa(old_upstream_addr)); if (old_upstream_addr.s_addr == new_upstream_addr.s_addr) return; /* Lets consider a case, where a PIM upstream has a better RP as a * result of a new RP configuration with more precise group range. * This upstream has to be added to the upstream hash of new RP's * NHT(pnc) and has to be removed from old RP's NHT upstream hash */ if (old_upstream_addr.s_addr != INADDR_ANY) { /* Deregister addr with Zebra NHT */ nht_p.family = AF_INET; nht_p.prefixlen = IPV4_MAX_BITLEN; nht_p.u.prefix4 = old_upstream_addr; if (PIM_DEBUG_TRACE) { char buf[PREFIX2STR_BUFFER]; prefix2str(&nht_p, buf, sizeof(buf)); zlog_debug("%s: Deregister upstream %s addr %s with Zebra NHT", __PRETTY_FUNCTION__, up->sg_str, buf); } pim_delete_tracked_nexthop(pim, &nht_p, up, NULL, false); } /* Update the upstream address */ up->upstream_addr = new_upstream_addr; old_rpf.source_nexthop.interface = up->rpf.source_nexthop.interface; rpf_result = pim_rpf_update(pim, up, &old_rpf); if (rpf_result == PIM_RPF_FAILURE) pim_mroute_del(up->channel_oil, __PRETTY_FUNCTION__); /* update kernel multicast forwarding cache (MFC) */ if (up->rpf.source_nexthop.interface && up->channel_oil) { ifindex_t ifindex = up->rpf.source_nexthop.interface->ifindex; int vif_index = pim_if_find_vifindex_by_ifindex(pim, ifindex); /* Pass Current selected NH vif index to mroute download */ if (vif_index) pim_scan_individual_oil(up->channel_oil, vif_index); else { if (PIM_DEBUG_PIM_NHT) zlog_debug( "%s: NHT upstream %s channel_oil IIF %s vif_index is not valid", __PRETTY_FUNCTION__, up->sg_str, up->rpf.source_nexthop.interface->name); } } if (rpf_result == PIM_RPF_CHANGED) pim_zebra_upstream_rpf_changed(pim, up, &old_rpf); pim_zebra_update_all_interfaces(pim); } int pim_rp_new_config(struct pim_instance *pim, const char *rp, const char *group_range, const char *plist) { int result = 0; struct prefix group; struct in_addr rp_addr; if (group_range == NULL) result = str2prefix("224.0.0.0/4", &group); else { result = str2prefix(group_range, &group); if (result) { struct prefix temp; prefix_copy(&temp, &group); apply_mask(&temp); if (!prefix_same(&group, &temp)) return PIM_GROUP_BAD_ADDR_MASK_COMBO; } } if (!result) return PIM_GROUP_BAD_ADDRESS; result = inet_pton(AF_INET, rp, &rp_addr); if (result <= 0) return PIM_RP_BAD_ADDRESS; result = pim_rp_new(pim, rp_addr, group, plist, RP_SRC_STATIC); return result; } int pim_rp_new(struct pim_instance *pim, struct in_addr rp_addr, struct prefix group, const char *plist, enum rp_source rp_src_flag) { int result = 0; char rp[INET_ADDRSTRLEN]; struct rp_info *rp_info; struct rp_info *rp_all; struct prefix group_all; struct listnode *node, *nnode; struct rp_info *tmp_rp_info; char buffer[BUFSIZ]; struct prefix nht_p; struct route_node *rn; struct pim_upstream *up; struct listnode *upnode; if (rp_addr.s_addr == INADDR_ANY || rp_addr.s_addr == INADDR_NONE) return PIM_RP_BAD_ADDRESS; rp_info = XCALLOC(MTYPE_PIM_RP, sizeof(*rp_info)); rp_info->rp.rpf_addr.family = AF_INET; rp_info->rp.rpf_addr.prefixlen = IPV4_MAX_PREFIXLEN; rp_info->rp.rpf_addr.u.prefix4 = rp_addr; prefix_copy(&rp_info->group, &group); rp_info->rp_src = rp_src_flag; inet_ntop(AF_INET, &rp_info->rp.rpf_addr.u.prefix4, rp, sizeof(rp)); if (plist) { /* * Return if the prefix-list is already configured for this RP */ if (pim_rp_find_prefix_list(pim, rp_info->rp.rpf_addr.u.prefix4, plist)) { XFREE(MTYPE_PIM_RP, rp_info); return PIM_SUCCESS; } /* * Barf if the prefix-list is already configured for an RP */ if (pim_rp_prefix_list_used(pim, plist)) { XFREE(MTYPE_PIM_RP, rp_info); return PIM_RP_PFXLIST_IN_USE; } /* * Free any existing rp_info entries for this RP */ for (ALL_LIST_ELEMENTS(pim->rp_list, node, nnode, tmp_rp_info)) { if (rp_info->rp.rpf_addr.u.prefix4.s_addr == tmp_rp_info->rp.rpf_addr.u.prefix4.s_addr) { if (tmp_rp_info->plist) pim_rp_del_config(pim, rp, NULL, tmp_rp_info->plist); else pim_rp_del_config( pim, rp, prefix2str(&tmp_rp_info->group, buffer, BUFSIZ), NULL); } } rp_info->plist = XSTRDUP(MTYPE_PIM_FILTER_NAME, plist); } else { if (!str2prefix("224.0.0.0/4", &group_all)) { XFREE(MTYPE_PIM_RP, rp_info); return PIM_GROUP_BAD_ADDRESS; } rp_all = pim_rp_find_match_group(pim, &group_all); /* * Barf if group is a non-multicast subnet */ if (!prefix_match(&rp_all->group, &rp_info->group)) { XFREE(MTYPE_PIM_RP, rp_info); return PIM_GROUP_BAD_ADDRESS; } /* * Remove any prefix-list rp_info entries for this RP */ for (ALL_LIST_ELEMENTS(pim->rp_list, node, nnode, tmp_rp_info)) { if (tmp_rp_info->plist && rp_info->rp.rpf_addr.u.prefix4.s_addr == tmp_rp_info->rp.rpf_addr.u.prefix4 .s_addr) { pim_rp_del_config(pim, rp, NULL, tmp_rp_info->plist); } } /* * Take over the 224.0.0.0/4 group if the rp is INADDR_NONE */ if (prefix_same(&rp_all->group, &rp_info->group) && pim_rpf_addr_is_inaddr_none(&rp_all->rp)) { rp_all->rp.rpf_addr = rp_info->rp.rpf_addr; rp_all->rp_src = rp_src_flag; XFREE(MTYPE_PIM_RP, rp_info); /* Register addr with Zebra NHT */ nht_p.family = AF_INET; nht_p.prefixlen = IPV4_MAX_BITLEN; nht_p.u.prefix4 = rp_all->rp.rpf_addr.u.prefix4; // RP address if (PIM_DEBUG_PIM_NHT_RP) { char buf[PREFIX2STR_BUFFER]; char buf1[PREFIX2STR_BUFFER]; prefix2str(&nht_p, buf, sizeof(buf)); prefix2str(&rp_all->group, buf1, sizeof(buf1)); zlog_debug( "%s: NHT Register rp_all addr %s grp %s ", __PRETTY_FUNCTION__, buf, buf1); } for (ALL_LIST_ELEMENTS_RO(pim->upstream_list, upnode, up)) { /* Find (*, G) upstream whose RP is not * configured yet */ if ((up->upstream_addr.s_addr == INADDR_ANY) && (up->sg.src.s_addr == INADDR_ANY)) { struct prefix grp; struct rp_info *trp_info; grp.family = AF_INET; grp.prefixlen = IPV4_MAX_BITLEN; grp.u.prefix4 = up->sg.grp; trp_info = pim_rp_find_match_group( pim, &grp); if (trp_info == rp_all) pim_upstream_update(pim, up); } } pim_rp_check_interfaces(pim, rp_all); pim_rp_refresh_group_to_rp_mapping(pim); pim_find_or_track_nexthop(pim, &nht_p, NULL, rp_all, false, NULL); if (!pim_ecmp_nexthop_lookup(pim, &rp_all->rp.source_nexthop, &nht_p, &rp_all->group, 1)) return PIM_RP_NO_PATH; return PIM_SUCCESS; } /* * Return if the group is already configured for this RP */ tmp_rp_info = pim_rp_find_exact( pim, rp_info->rp.rpf_addr.u.prefix4, &rp_info->group); if (tmp_rp_info) { if ((tmp_rp_info->rp_src != rp_src_flag) && (rp_src_flag == RP_SRC_STATIC)) tmp_rp_info->rp_src = rp_src_flag; XFREE(MTYPE_PIM_RP, rp_info); return result; } /* * Barf if this group is already covered by some other RP */ tmp_rp_info = pim_rp_find_match_group(pim, &rp_info->group); if (tmp_rp_info) { if (tmp_rp_info->plist) { XFREE(MTYPE_PIM_RP, rp_info); return PIM_GROUP_PFXLIST_OVERLAP; } else { /* * If the only RP that covers this group is an * RP configured for * 224.0.0.0/4 that is fine, ignore that one. * For all others * though we must return PIM_GROUP_OVERLAP */ if (prefix_same(&rp_info->group, &tmp_rp_info->group)) { if ((rp_src_flag == RP_SRC_STATIC) && (tmp_rp_info->rp_src == RP_SRC_STATIC)) { XFREE(MTYPE_PIM_RP, rp_info); return PIM_GROUP_OVERLAP; } result = pim_rp_change( pim, rp_info->rp.rpf_addr.u.prefix4, tmp_rp_info->group, rp_src_flag); XFREE(MTYPE_PIM_RP, rp_info); return result; } } } } listnode_add_sort(pim->rp_list, rp_info); rn = route_node_get(pim->rp_table, &rp_info->group); rn->info = rp_info; if (PIM_DEBUG_TRACE) { char buf[PREFIX_STRLEN]; zlog_debug("Allocated: %p for rp_info: %p(%s) Lock: %d", rn, rp_info, prefix2str(&rp_info->group, buf, sizeof(buf)), rn->lock); } for (ALL_LIST_ELEMENTS_RO(pim->upstream_list, upnode, up)) { if (up->sg.src.s_addr == INADDR_ANY) { struct prefix grp; struct rp_info *trp_info; grp.family = AF_INET; grp.prefixlen = IPV4_MAX_BITLEN; grp.u.prefix4 = up->sg.grp; trp_info = pim_rp_find_match_group(pim, &grp); if (trp_info == rp_info) pim_upstream_update(pim, up); } } pim_rp_check_interfaces(pim, rp_info); pim_rp_refresh_group_to_rp_mapping(pim); /* Register addr with Zebra NHT */ nht_p.family = AF_INET; nht_p.prefixlen = IPV4_MAX_BITLEN; nht_p.u.prefix4 = rp_info->rp.rpf_addr.u.prefix4; if (PIM_DEBUG_PIM_NHT_RP) { char buf[PREFIX2STR_BUFFER]; char buf1[PREFIX2STR_BUFFER]; prefix2str(&nht_p, buf, sizeof(buf)); prefix2str(&rp_info->group, buf1, sizeof(buf1)); zlog_debug("%s: NHT Register RP addr %s grp %s with Zebra ", __PRETTY_FUNCTION__, buf, buf1); } pim_find_or_track_nexthop(pim, &nht_p, NULL, rp_info, false, NULL); if (!pim_ecmp_nexthop_lookup(pim, &rp_info->rp.source_nexthop, &nht_p, &rp_info->group, 1)) return PIM_RP_NO_PATH; return PIM_SUCCESS; } int pim_rp_del_config(struct pim_instance *pim, const char *rp, const char *group_range, const char *plist) { struct prefix group; struct in_addr rp_addr; int result; if (group_range == NULL) result = str2prefix("224.0.0.0/4", &group); else result = str2prefix(group_range, &group); if (!result) return PIM_GROUP_BAD_ADDRESS; result = inet_pton(AF_INET, rp, &rp_addr); if (result <= 0) return PIM_RP_BAD_ADDRESS; result = pim_rp_del(pim, rp_addr, group, plist, RP_SRC_STATIC); return result; } int pim_rp_del(struct pim_instance *pim, struct in_addr rp_addr, struct prefix group, const char *plist, enum rp_source rp_src_flag) { struct prefix g_all; struct rp_info *rp_info; struct rp_info *rp_all; struct prefix nht_p; struct route_node *rn; bool was_plist = false; struct rp_info *trp_info; struct pim_upstream *up; struct listnode *upnode; struct bsgrp_node *bsgrp = NULL; struct bsm_rpinfo *bsrp = NULL; char grp_str[PREFIX2STR_BUFFER]; char rp_str[INET_ADDRSTRLEN]; if (!inet_ntop(AF_INET, &rp_addr, rp_str, sizeof(rp_str))) sprintf(rp_str, ""); prefix2str(&group, grp_str, sizeof(grp_str)); if (plist) rp_info = pim_rp_find_prefix_list(pim, rp_addr, plist); else rp_info = pim_rp_find_exact(pim, rp_addr, &group); if (!rp_info) return PIM_RP_NOT_FOUND; if (rp_info->plist) { XFREE(MTYPE_PIM_FILTER_NAME, rp_info->plist); was_plist = true; } if (PIM_DEBUG_TRACE) zlog_debug("%s: Delete RP %s for the group %s", __PRETTY_FUNCTION__, rp_str, grp_str); /* While static RP is getting deleted, we need to check if dynamic RP * present for the same group in BSM RP table, then install the dynamic * RP for the group node into the main rp table */ if (rp_src_flag == RP_SRC_STATIC) { bsgrp = pim_bsm_get_bsgrp_node(&pim->global_scope, &group); if (bsgrp) { bsrp = listnode_head(bsgrp->bsrp_list); if (bsrp) { if (PIM_DEBUG_TRACE) { char bsrp_str[INET_ADDRSTRLEN]; if (!inet_ntop(AF_INET, bsrp, bsrp_str, sizeof(bsrp_str))) sprintf(bsrp_str, ""); zlog_debug("%s: BSM RP %s found for the group %s", __PRETTY_FUNCTION__, bsrp_str, grp_str); } return pim_rp_change(pim, bsrp->rp_address, group, RP_SRC_BSR); } } else { if (PIM_DEBUG_TRACE) zlog_debug( "%s: BSM RP not found for the group %s", __PRETTY_FUNCTION__, grp_str); } } /* Deregister addr with Zebra NHT */ nht_p.family = AF_INET; nht_p.prefixlen = IPV4_MAX_BITLEN; nht_p.u.prefix4 = rp_info->rp.rpf_addr.u.prefix4; if (PIM_DEBUG_PIM_NHT_RP) { char buf[PREFIX2STR_BUFFER]; prefix2str(&nht_p, buf, sizeof(buf)); zlog_debug("%s: Deregister RP addr %s with Zebra ", __PRETTY_FUNCTION__, buf); } pim_delete_tracked_nexthop(pim, &nht_p, NULL, rp_info, false); if (!str2prefix("224.0.0.0/4", &g_all)) return PIM_RP_BAD_ADDRESS; rp_all = pim_rp_find_match_group(pim, &g_all); if (rp_all == rp_info) { for (ALL_LIST_ELEMENTS_RO(pim->upstream_list, upnode, up)) { /* Find the upstream (*, G) whose upstream address is * same as the deleted RP */ if ((up->upstream_addr.s_addr == rp_info->rp.rpf_addr.u.prefix4.s_addr) && (up->sg.src.s_addr == INADDR_ANY)) { struct prefix grp; grp.family = AF_INET; grp.prefixlen = IPV4_MAX_BITLEN; grp.u.prefix4 = up->sg.grp; trp_info = pim_rp_find_match_group(pim, &grp); if (trp_info == rp_all) { pim_upstream_rpf_clear(pim, up); up->upstream_addr.s_addr = INADDR_ANY; } } } rp_all->rp.rpf_addr.family = AF_INET; rp_all->rp.rpf_addr.u.prefix4.s_addr = INADDR_NONE; rp_all->i_am_rp = 0; return PIM_SUCCESS; } listnode_delete(pim->rp_list, rp_info); if (!was_plist) { rn = route_node_get(pim->rp_table, &rp_info->group); if (rn) { if (rn->info != rp_info) flog_err( EC_LIB_DEVELOPMENT, "Expected rn->info to be equal to rp_info"); if (PIM_DEBUG_TRACE) { char buf[PREFIX_STRLEN]; zlog_debug( "%s:Found for Freeing: %p for rp_info: %p(%s) Lock: %d", __PRETTY_FUNCTION__, rn, rp_info, prefix2str(&rp_info->group, buf, sizeof(buf)), rn->lock); } rn->info = NULL; route_unlock_node(rn); route_unlock_node(rn); } } pim_rp_refresh_group_to_rp_mapping(pim); for (ALL_LIST_ELEMENTS_RO(pim->upstream_list, upnode, up)) { /* Find the upstream (*, G) whose upstream address is same as * the deleted RP */ if ((up->upstream_addr.s_addr == rp_info->rp.rpf_addr.u.prefix4.s_addr) && (up->sg.src.s_addr == INADDR_ANY)) { struct prefix grp; grp.family = AF_INET; grp.prefixlen = IPV4_MAX_BITLEN; grp.u.prefix4 = up->sg.grp; trp_info = pim_rp_find_match_group(pim, &grp); /* RP not found for the group grp */ if (pim_rpf_addr_is_inaddr_none(&trp_info->rp)) { pim_upstream_rpf_clear(pim, up); pim_rp_set_upstream_addr( pim, &up->upstream_addr, up->sg.src, up->sg.grp); } /* RP found for the group grp */ else pim_upstream_update(pim, up); } } XFREE(MTYPE_PIM_RP, rp_info); return PIM_SUCCESS; } int pim_rp_change(struct pim_instance *pim, struct in_addr new_rp_addr, struct prefix group, enum rp_source rp_src_flag) { struct prefix nht_p; struct route_node *rn; int result = 0; struct rp_info *rp_info = NULL; struct pim_upstream *up; struct listnode *upnode; rn = route_node_lookup(pim->rp_table, &group); if (!rn) { result = pim_rp_new(pim, new_rp_addr, group, NULL, rp_src_flag); return result; } rp_info = rn->info; if (!rp_info) { route_unlock_node(rn); result = pim_rp_new(pim, new_rp_addr, group, NULL, rp_src_flag); return result; } if (rp_info->rp.rpf_addr.u.prefix4.s_addr == new_rp_addr.s_addr) { if (rp_info->rp_src != rp_src_flag) { rp_info->rp_src = rp_src_flag; route_unlock_node(rn); return PIM_SUCCESS; } } /* Deregister old RP addr with Zebra NHT */ if (rp_info->rp.rpf_addr.u.prefix4.s_addr != INADDR_ANY) { nht_p.family = AF_INET; nht_p.prefixlen = IPV4_MAX_BITLEN; nht_p.u.prefix4 = rp_info->rp.rpf_addr.u.prefix4; if (PIM_DEBUG_PIM_NHT_RP) { char buf[PREFIX2STR_BUFFER]; prefix2str(&nht_p, buf, sizeof(buf)); zlog_debug("%s: Deregister RP addr %s with Zebra ", __PRETTY_FUNCTION__, buf); } pim_delete_tracked_nexthop(pim, &nht_p, NULL, rp_info, false); } pim_rp_nexthop_del(rp_info); listnode_delete(pim->rp_list, rp_info); /* Update the new RP address*/ rp_info->rp.rpf_addr.u.prefix4 = new_rp_addr; rp_info->rp_src = rp_src_flag; rp_info->i_am_rp = 0; listnode_add_sort(pim->rp_list, rp_info); for (ALL_LIST_ELEMENTS_RO(pim->upstream_list, upnode, up)) { if (up->sg.src.s_addr == INADDR_ANY) { struct prefix grp; struct rp_info *trp_info; grp.family = AF_INET; grp.prefixlen = IPV4_MAX_BITLEN; grp.u.prefix4 = up->sg.grp; trp_info = pim_rp_find_match_group(pim, &grp); if (trp_info == rp_info) pim_upstream_update(pim, up); } } /* Register new RP addr with Zebra NHT */ nht_p.u.prefix4 = rp_info->rp.rpf_addr.u.prefix4; if (PIM_DEBUG_PIM_NHT_RP) { char buf[PREFIX2STR_BUFFER]; char buf1[PREFIX2STR_BUFFER]; prefix2str(&nht_p, buf, sizeof(buf)); prefix2str(&rp_info->group, buf1, sizeof(buf1)); zlog_debug("%s: NHT Register RP addr %s grp %s with Zebra ", __PRETTY_FUNCTION__, buf, buf1); } pim_find_or_track_nexthop(pim, &nht_p, NULL, rp_info, false, NULL); if (!pim_ecmp_nexthop_lookup(pim, &rp_info->rp.source_nexthop, &nht_p, &rp_info->group, 1)) { route_unlock_node(rn); return PIM_RP_NO_PATH; } pim_rp_check_interfaces(pim, rp_info); route_unlock_node(rn); pim_rp_refresh_group_to_rp_mapping(pim); return result; } void pim_rp_setup(struct pim_instance *pim) { struct listnode *node; struct rp_info *rp_info; struct prefix nht_p; for (ALL_LIST_ELEMENTS_RO(pim->rp_list, node, rp_info)) { if (rp_info->rp.rpf_addr.u.prefix4.s_addr == INADDR_NONE) continue; nht_p.family = AF_INET; nht_p.prefixlen = IPV4_MAX_BITLEN; nht_p.u.prefix4 = rp_info->rp.rpf_addr.u.prefix4; pim_find_or_track_nexthop(pim, &nht_p, NULL, rp_info, false, NULL); if (!pim_ecmp_nexthop_lookup(pim, &rp_info->rp.source_nexthop, &nht_p, &rp_info->group, 1)) if (PIM_DEBUG_PIM_NHT_RP) zlog_debug( "Unable to lookup nexthop for rp specified"); } } /* * Checks to see if we should elect ourself the actual RP when new if * addresses are added against an interface. */ void pim_rp_check_on_if_add(struct pim_interface *pim_ifp) { struct listnode *node; struct rp_info *rp_info; bool i_am_rp_changed = false; struct pim_instance *pim = pim_ifp->pim; if (pim->rp_list == NULL) return; for (ALL_LIST_ELEMENTS_RO(pim->rp_list, node, rp_info)) { if (pim_rpf_addr_is_inaddr_none(&rp_info->rp)) continue; /* if i_am_rp is already set nothing to be done (adding new * addresses * is not going to make a difference). */ if (rp_info->i_am_rp) { continue; } if (pim_rp_check_interface_addrs(rp_info, pim_ifp)) { i_am_rp_changed = true; rp_info->i_am_rp = 1; if (PIM_DEBUG_PIM_NHT_RP) { char rp[PREFIX_STRLEN]; pim_addr_dump("", &rp_info->rp.rpf_addr, rp, sizeof(rp)); zlog_debug("%s: %s: i am rp", __func__, rp); } } } if (i_am_rp_changed) { pim_msdp_i_am_rp_changed(pim); } } /* up-optimized re-evaluation of "i_am_rp". this is used when ifaddresses * are removed. Removing numbers is an uncommon event in an active network * so I have made no attempt to optimize it. */ void pim_i_am_rp_re_evaluate(struct pim_instance *pim) { struct listnode *node; struct rp_info *rp_info; bool i_am_rp_changed = false; int old_i_am_rp; if (pim->rp_list == NULL) return; for (ALL_LIST_ELEMENTS_RO(pim->rp_list, node, rp_info)) { if (pim_rpf_addr_is_inaddr_none(&rp_info->rp)) continue; old_i_am_rp = rp_info->i_am_rp; pim_rp_check_interfaces(pim, rp_info); if (old_i_am_rp != rp_info->i_am_rp) { i_am_rp_changed = true; if (PIM_DEBUG_PIM_NHT_RP) { char rp[PREFIX_STRLEN]; pim_addr_dump("", &rp_info->rp.rpf_addr, rp, sizeof(rp)); if (rp_info->i_am_rp) { zlog_debug("%s: %s: i am rp", __func__, rp); } else { zlog_debug("%s: %s: i am no longer rp", __func__, rp); } } } } if (i_am_rp_changed) { pim_msdp_i_am_rp_changed(pim); } } /* * I_am_RP(G) is true if the group-to-RP mapping indicates that * this router is the RP for the group. * * Since we only have static RP, all groups are part of this RP */ int pim_rp_i_am_rp(struct pim_instance *pim, struct in_addr group) { struct prefix g; struct rp_info *rp_info; memset(&g, 0, sizeof(g)); g.family = AF_INET; g.prefixlen = 32; g.u.prefix4 = group; rp_info = pim_rp_find_match_group(pim, &g); if (rp_info) return rp_info->i_am_rp; return 0; } /* * RP(G) * * Return the RP that the Group belongs too. */ struct pim_rpf *pim_rp_g(struct pim_instance *pim, struct in_addr group) { struct prefix g; struct rp_info *rp_info; memset(&g, 0, sizeof(g)); g.family = AF_INET; g.prefixlen = 32; g.u.prefix4 = group; rp_info = pim_rp_find_match_group(pim, &g); if (rp_info) { struct prefix nht_p; /* Register addr with Zebra NHT */ nht_p.family = AF_INET; nht_p.prefixlen = IPV4_MAX_BITLEN; nht_p.u.prefix4 = rp_info->rp.rpf_addr.u.prefix4; if (PIM_DEBUG_PIM_NHT_RP) { char buf[PREFIX2STR_BUFFER]; char buf1[PREFIX2STR_BUFFER]; prefix2str(&nht_p, buf, sizeof(buf)); prefix2str(&rp_info->group, buf1, sizeof(buf1)); zlog_debug( "%s: NHT Register RP addr %s grp %s with Zebra", __PRETTY_FUNCTION__, buf, buf1); } pim_find_or_track_nexthop(pim, &nht_p, NULL, rp_info, false, NULL); pim_rpf_set_refresh_time(pim); (void)pim_ecmp_nexthop_lookup(pim, &rp_info->rp.source_nexthop, &nht_p, &rp_info->group, 1); return (&rp_info->rp); } // About to Go Down return NULL; } /* * Set the upstream IP address we want to talk to based upon * the rp configured and the source address * * If we have don't have a RP configured and the source address is * * then set the upstream addr as INADDR_ANY and return failure. * */ int pim_rp_set_upstream_addr(struct pim_instance *pim, struct in_addr *up, struct in_addr source, struct in_addr group) { struct rp_info *rp_info; struct prefix g; memset(&g, 0, sizeof(g)); g.family = AF_INET; g.prefixlen = 32; g.u.prefix4 = group; rp_info = pim_rp_find_match_group(pim, &g); if (!rp_info || ((pim_rpf_addr_is_inaddr_none(&rp_info->rp)) && (source.s_addr == INADDR_ANY))) { if (PIM_DEBUG_PIM_NHT_RP) zlog_debug("%s: Received a (*,G) with no RP configured", __PRETTY_FUNCTION__); up->s_addr = INADDR_ANY; return 0; } *up = (source.s_addr == INADDR_ANY) ? rp_info->rp.rpf_addr.u.prefix4 : source; return 1; } int pim_rp_config_write(struct pim_instance *pim, struct vty *vty, const char *spaces) { struct listnode *node; struct rp_info *rp_info; char rp_buffer[32]; char group_buffer[32]; int count = 0; for (ALL_LIST_ELEMENTS_RO(pim->rp_list, node, rp_info)) { if (pim_rpf_addr_is_inaddr_none(&rp_info->rp)) continue; if (rp_info->rp_src == RP_SRC_BSR) continue; if (rp_info->plist) vty_out(vty, "%sip pim rp %s prefix-list %s\n", spaces, inet_ntop(AF_INET, &rp_info->rp.rpf_addr.u.prefix4, rp_buffer, 32), rp_info->plist); else vty_out(vty, "%sip pim rp %s %s\n", spaces, inet_ntop(AF_INET, &rp_info->rp.rpf_addr.u.prefix4, rp_buffer, 32), prefix2str(&rp_info->group, group_buffer, 32)); count++; } return count; } bool pim_rp_check_is_my_ip_address(struct pim_instance *pim, struct in_addr dest_addr) { if (if_lookup_exact_address(&dest_addr, AF_INET, pim->vrf_id)) return true; return false; } void pim_rp_show_information(struct pim_instance *pim, struct vty *vty, bool uj) { struct rp_info *rp_info; struct rp_info *prev_rp_info = NULL; struct listnode *node; char source[7]; json_object *json = NULL; json_object *json_rp_rows = NULL; json_object *json_row = NULL; if (uj) json = json_object_new_object(); else vty_out(vty, "RP address group/prefix-list OIF I am RP Source\n"); for (ALL_LIST_ELEMENTS_RO(pim->rp_list, node, rp_info)) { if (!pim_rpf_addr_is_inaddr_none(&rp_info->rp)) { char buf[48]; if (rp_info->rp_src == RP_SRC_STATIC) strcpy(source, "Static"); else if (rp_info->rp_src == RP_SRC_BSR) strcpy(source, "BSR"); else strcpy(source, "None"); if (uj) { /* * If we have moved on to a new RP then add the * entry for the previous RP */ if (prev_rp_info && prev_rp_info->rp.rpf_addr.u.prefix4 .s_addr != rp_info->rp.rpf_addr.u.prefix4 .s_addr) { json_object_object_add( json, inet_ntoa(prev_rp_info->rp .rpf_addr.u .prefix4), json_rp_rows); json_rp_rows = NULL; } if (!json_rp_rows) json_rp_rows = json_object_new_array(); json_row = json_object_new_object(); if (rp_info->rp.source_nexthop.interface) json_object_string_add( json_row, "outboundInterface", rp_info->rp.source_nexthop .interface->name); if (rp_info->i_am_rp) json_object_boolean_true_add(json_row, "iAmRP"); if (rp_info->plist) json_object_string_add(json_row, "prefixList", rp_info->plist); else json_object_string_add( json_row, "group", prefix2str(&rp_info->group, buf, 48)); json_object_string_add(json_row, "source", source); json_object_array_add(json_rp_rows, json_row); } else { vty_out(vty, "%-15s ", inet_ntoa(rp_info->rp.rpf_addr.u .prefix4)); if (rp_info->plist) vty_out(vty, "%-18s ", rp_info->plist); else vty_out(vty, "%-18s ", prefix2str(&rp_info->group, buf, 48)); if (rp_info->rp.source_nexthop.interface) vty_out(vty, "%-16s ", rp_info->rp.source_nexthop .interface->name); else vty_out(vty, "%-16s ", "(Unknown)"); if (rp_info->i_am_rp) vty_out(vty, "yes\n"); else vty_out(vty, "no"); vty_out(vty, "%14s\n", source); } prev_rp_info = rp_info; } } if (uj) { if (prev_rp_info && json_rp_rows) json_object_object_add( json, inet_ntoa(prev_rp_info->rp.rpf_addr.u.prefix4), json_rp_rows); vty_out(vty, "%s\n", json_object_to_json_string_ext( json, JSON_C_TO_STRING_PRETTY)); json_object_free(json); } } void pim_resolve_rp_nh(struct pim_instance *pim, struct pim_neighbor *nbr) { struct listnode *node = NULL; struct rp_info *rp_info = NULL; struct nexthop *nh_node = NULL; struct prefix nht_p; struct pim_nexthop_cache pnc; for (ALL_LIST_ELEMENTS_RO(pim->rp_list, node, rp_info)) { if (rp_info->rp.rpf_addr.u.prefix4.s_addr == INADDR_NONE) continue; nht_p.family = AF_INET; nht_p.prefixlen = IPV4_MAX_BITLEN; nht_p.u.prefix4 = rp_info->rp.rpf_addr.u.prefix4; memset(&pnc, 0, sizeof(struct pim_nexthop_cache)); if (!pim_find_or_track_nexthop(pim, &nht_p, NULL, rp_info, false, &pnc)) continue; for (nh_node = pnc.nexthop; nh_node; nh_node = nh_node->next) { if (nh_node->gate.ipv4.s_addr != 0) continue; struct interface *ifp1 = if_lookup_by_index( nh_node->ifindex, pim->vrf_id); if (nbr->interface != ifp1) continue; nh_node->gate.ipv4 = nbr->source_addr; if (PIM_DEBUG_PIM_NHT_RP) { char str[PREFIX_STRLEN]; char str1[INET_ADDRSTRLEN]; pim_inet4_dump("", nbr->source_addr, str1, sizeof(str1)); pim_addr_dump("", &nht_p, str, sizeof(str)); zlog_debug( "%s: addr %s new nexthop addr %s interface %s", __PRETTY_FUNCTION__, str, str1, ifp1->name); } } } } frr-7.2.1/pimd/pim_rp.h0000644000000000000000000000610713610377563011610 00000000000000/* * PIM for Quagga * Copyright (C) 2015 Cumulus Networks, Inc. * Donald Sharp * * This program is free software; you can redistribute it and/or modify * it under the terms of the 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; see the file COPYING; if not, write to the Free Software * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA */ #ifndef PIM_RP_H #define PIM_RP_H #include #include "prefix.h" #include "vty.h" #include "plist.h" #include "pim_iface.h" #include "pim_rpf.h" enum rp_source { RP_SRC_NONE = 0, RP_SRC_STATIC, RP_SRC_BSR }; struct rp_info { struct prefix group; struct pim_rpf rp; enum rp_source rp_src; int i_am_rp; char *plist; }; void pim_rp_init(struct pim_instance *pim); void pim_rp_free(struct pim_instance *pim); void pim_rp_list_hash_clean(void *data); int pim_rp_new_config(struct pim_instance *pim, const char *rp, const char *group, const char *plist); int pim_rp_new(struct pim_instance *pim, struct in_addr rp_addr, struct prefix group, const char *plist, enum rp_source rp_src_flag); int pim_rp_del_config(struct pim_instance *pim, const char *rp, const char *group, const char *plist); int pim_rp_del(struct pim_instance *pim, struct in_addr rp_addr, struct prefix group, const char *plist, enum rp_source rp_src_flag); int pim_rp_change(struct pim_instance *pim, struct in_addr new_rp_addr, struct prefix group, enum rp_source rp_src_flag); void pim_rp_prefix_list_update(struct pim_instance *pim, struct prefix_list *plist); int pim_rp_config_write(struct pim_instance *pim, struct vty *vty, const char *spaces); void pim_rp_setup(struct pim_instance *pim); int pim_rp_i_am_rp(struct pim_instance *pim, struct in_addr group); void pim_rp_check_on_if_add(struct pim_interface *pim_ifp); void pim_i_am_rp_re_evaluate(struct pim_instance *pim); bool pim_rp_check_is_my_ip_address(struct pim_instance *pim, struct in_addr dest_addr); int pim_rp_set_upstream_addr(struct pim_instance *pim, struct in_addr *up, struct in_addr source, struct in_addr group); struct pim_rpf *pim_rp_g(struct pim_instance *pim, struct in_addr group); #define I_am_RP(P, G) pim_rp_i_am_rp ((P), (G)) #define RP(P, G) pim_rp_g ((P), (G)) void pim_rp_show_information(struct pim_instance *pim, struct vty *vty, bool uj); void pim_resolve_rp_nh(struct pim_instance *pim, struct pim_neighbor *nbr); int pim_rp_list_cmp(void *v1, void *v2); struct rp_info *pim_rp_find_match_group(struct pim_instance *pim, const struct prefix *group); void pim_upstream_update(struct pim_instance *pim, struct pim_upstream *up); #endif frr-7.2.1/pimd/pim_rpf.c0000644000000000000000000003034013610377563011745 00000000000000/* * PIM for Quagga * Copyright (C) 2008 Everton da Silva Marques * * This program is free software; you can redistribute it and/or modify * it under the terms of the 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; see the file COPYING; if not, write to the Free Software * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA */ #include #include "if.h" #include "log.h" #include "prefix.h" #include "memory.h" #include "jhash.h" #include "pimd.h" #include "pim_rpf.h" #include "pim_pim.h" #include "pim_str.h" #include "pim_iface.h" #include "pim_zlookup.h" #include "pim_ifchannel.h" #include "pim_time.h" #include "pim_nht.h" #include "pim_oil.h" static struct in_addr pim_rpf_find_rpf_addr(struct pim_upstream *up); void pim_rpf_set_refresh_time(struct pim_instance *pim) { pim->last_route_change_time = pim_time_monotonic_usec(); if (PIM_DEBUG_TRACE) zlog_debug("%s: vrf(%s) New last route change time: %" PRId64, __PRETTY_FUNCTION__, pim->vrf->name, pim->last_route_change_time); } bool pim_nexthop_lookup(struct pim_instance *pim, struct pim_nexthop *nexthop, struct in_addr addr, int neighbor_needed) { struct pim_zlookup_nexthop nexthop_tab[MULTIPATH_NUM]; struct pim_neighbor *nbr = NULL; int num_ifindex; struct interface *ifp = NULL; ifindex_t first_ifindex = 0; int found = 0; int i = 0; /* * We should not attempt to lookup a * 255.255.255.255 address, since * it will never work */ if (addr.s_addr == INADDR_NONE) return false; if ((nexthop->last_lookup.s_addr == addr.s_addr) && (nexthop->last_lookup_time > pim->last_route_change_time)) { if (PIM_DEBUG_TRACE) { char addr_str[INET_ADDRSTRLEN]; pim_inet4_dump("", addr, addr_str, sizeof(addr_str)); char nexthop_str[PREFIX_STRLEN]; pim_addr_dump("", &nexthop->mrib_nexthop_addr, nexthop_str, sizeof(nexthop_str)); zlog_debug( "%s: Using last lookup for %s at %lld, %" PRId64 " addr %s", __PRETTY_FUNCTION__, addr_str, nexthop->last_lookup_time, pim->last_route_change_time, nexthop_str); } pim->nexthop_lookups_avoided++; return true; } else { if (PIM_DEBUG_TRACE) { char addr_str[INET_ADDRSTRLEN]; pim_inet4_dump("", addr, addr_str, sizeof(addr_str)); zlog_debug( "%s: Looking up: %s, last lookup time: %lld, %" PRId64, __PRETTY_FUNCTION__, addr_str, nexthop->last_lookup_time, pim->last_route_change_time); } } memset(nexthop_tab, 0, sizeof(struct pim_zlookup_nexthop) * MULTIPATH_NUM); num_ifindex = zclient_lookup_nexthop(pim, nexthop_tab, MULTIPATH_NUM, addr, PIM_NEXTHOP_LOOKUP_MAX); if (num_ifindex < 1) { char addr_str[INET_ADDRSTRLEN]; pim_inet4_dump("", addr, addr_str, sizeof(addr_str)); zlog_warn( "%s %s: could not find nexthop ifindex for address %s", __FILE__, __PRETTY_FUNCTION__, addr_str); return false; } while (!found && (i < num_ifindex)) { first_ifindex = nexthop_tab[i].ifindex; ifp = if_lookup_by_index(first_ifindex, pim->vrf_id); if (!ifp) { if (PIM_DEBUG_ZEBRA) { char addr_str[INET_ADDRSTRLEN]; pim_inet4_dump("", addr, addr_str, sizeof(addr_str)); zlog_debug( "%s %s: could not find interface for ifindex %d (address %s)", __FILE__, __PRETTY_FUNCTION__, first_ifindex, addr_str); } i++; continue; } if (!ifp->info) { if (PIM_DEBUG_ZEBRA) { char addr_str[INET_ADDRSTRLEN]; pim_inet4_dump("", addr, addr_str, sizeof(addr_str)); zlog_debug( "%s: multicast not enabled on input interface %s (ifindex=%d, RPF for source %s)", __PRETTY_FUNCTION__, ifp->name, first_ifindex, addr_str); } i++; } else if (neighbor_needed && !pim_if_connected_to_source(ifp, addr)) { nbr = pim_neighbor_find( ifp, nexthop_tab[i].nexthop_addr.u.prefix4); if (PIM_DEBUG_PIM_TRACE_DETAIL) zlog_debug("ifp name: %s, pim nbr: %p", ifp->name, nbr); if (!nbr && !if_is_loopback(ifp)) i++; else found = 1; } else found = 1; } if (found) { if (PIM_DEBUG_ZEBRA) { char nexthop_str[PREFIX_STRLEN]; char addr_str[INET_ADDRSTRLEN]; pim_addr_dump("", &nexthop_tab[i].nexthop_addr, nexthop_str, sizeof(nexthop_str)); pim_inet4_dump("", addr, addr_str, sizeof(addr_str)); zlog_debug( "%s %s: found nexthop %s for address %s: interface %s ifindex=%d metric=%d pref=%d", __FILE__, __PRETTY_FUNCTION__, nexthop_str, addr_str, ifp->name, first_ifindex, nexthop_tab[i].route_metric, nexthop_tab[i].protocol_distance); } /* update nexthop data */ nexthop->interface = ifp; nexthop->mrib_nexthop_addr = nexthop_tab[i].nexthop_addr; nexthop->mrib_metric_preference = nexthop_tab[i].protocol_distance; nexthop->mrib_route_metric = nexthop_tab[i].route_metric; nexthop->last_lookup = addr; nexthop->last_lookup_time = pim_time_monotonic_usec(); nexthop->nbr = nbr; return true; } else return false; } static int nexthop_mismatch(const struct pim_nexthop *nh1, const struct pim_nexthop *nh2) { return (nh1->interface != nh2->interface) || (nh1->mrib_nexthop_addr.u.prefix4.s_addr != nh2->mrib_nexthop_addr.u.prefix4.s_addr) || (nh1->mrib_metric_preference != nh2->mrib_metric_preference) || (nh1->mrib_route_metric != nh2->mrib_route_metric); } enum pim_rpf_result pim_rpf_update(struct pim_instance *pim, struct pim_upstream *up, struct pim_rpf *old) { struct pim_rpf *rpf = &up->rpf; struct pim_rpf saved; struct prefix nht_p; struct prefix src, grp; bool neigh_needed = true; if (PIM_UPSTREAM_FLAG_TEST_STATIC_IIF(up->flags)) return PIM_RPF_OK; if (up->upstream_addr.s_addr == INADDR_ANY) { zlog_debug("%s: RP is not configured yet for %s", __PRETTY_FUNCTION__, up->sg_str); return PIM_RPF_OK; } saved.source_nexthop = rpf->source_nexthop; saved.rpf_addr = rpf->rpf_addr; nht_p.family = AF_INET; nht_p.prefixlen = IPV4_MAX_BITLEN; nht_p.u.prefix4.s_addr = up->upstream_addr.s_addr; src.family = AF_INET; src.prefixlen = IPV4_MAX_BITLEN; src.u.prefix4 = up->upstream_addr; // RP or Src address grp.family = AF_INET; grp.prefixlen = IPV4_MAX_BITLEN; grp.u.prefix4 = up->sg.grp; if ((up->sg.src.s_addr == INADDR_ANY && I_am_RP(pim, up->sg.grp)) || PIM_UPSTREAM_FLAG_TEST_FHR(up->flags)) neigh_needed = false; pim_find_or_track_nexthop(pim, &nht_p, up, NULL, false, NULL); if (!pim_ecmp_nexthop_lookup(pim, &rpf->source_nexthop, &src, &grp, neigh_needed)) return PIM_RPF_FAILURE; rpf->rpf_addr.family = AF_INET; rpf->rpf_addr.u.prefix4 = pim_rpf_find_rpf_addr(up); if (pim_rpf_addr_is_inaddr_any(rpf) && PIM_DEBUG_ZEBRA) { /* RPF'(S,G) not found */ zlog_debug("%s %s: RPF'%s not found: won't send join upstream", __FILE__, __PRETTY_FUNCTION__, up->sg_str); /* warning only */ } /* detect change in pim_nexthop */ if (nexthop_mismatch(&rpf->source_nexthop, &saved.source_nexthop)) { if (PIM_DEBUG_ZEBRA) { char nhaddr_str[PREFIX_STRLEN]; pim_addr_dump("", &rpf->source_nexthop.mrib_nexthop_addr, nhaddr_str, sizeof(nhaddr_str)); zlog_debug("%s %s: (S,G)=%s source nexthop now is: interface=%s address=%s pref=%d metric=%d", __FILE__, __PRETTY_FUNCTION__, up->sg_str, rpf->source_nexthop.interface ? rpf->source_nexthop.interface->name : "", nhaddr_str, rpf->source_nexthop.mrib_metric_preference, rpf->source_nexthop.mrib_route_metric); } pim_upstream_update_join_desired(pim, up); pim_upstream_update_could_assert(up); pim_upstream_update_my_assert_metric(up); } /* detect change in RPF_interface(S) */ if (saved.source_nexthop.interface != rpf->source_nexthop.interface) { if (PIM_DEBUG_ZEBRA) { zlog_debug("%s %s: (S,G)=%s RPF_interface(S) changed from %s to %s", __FILE__, __PRETTY_FUNCTION__, up->sg_str, saved.source_nexthop.interface ? saved.source_nexthop.interface->name : "", rpf->source_nexthop.interface ? rpf->source_nexthop.interface->name : ""); /* warning only */ } pim_upstream_rpf_interface_changed( up, saved.source_nexthop.interface); } /* detect change in RPF'(S,G) */ if (saved.rpf_addr.u.prefix4.s_addr != rpf->rpf_addr.u.prefix4.s_addr || saved.source_nexthop .interface != rpf->source_nexthop.interface) { /* return old rpf to caller ? */ if (old) { old->source_nexthop = saved.source_nexthop; old->rpf_addr = saved.rpf_addr; } return PIM_RPF_CHANGED; } return PIM_RPF_OK; } /* * In the case of RP deletion and RP unreachablity, * uninstall the mroute in the kernel and clear the * rpf information in the pim upstream and pim channel * oil data structure. */ void pim_upstream_rpf_clear(struct pim_instance *pim, struct pim_upstream *up) { if (up->rpf.source_nexthop.interface) { if (up->channel_oil) pim_channel_oil_change_iif(pim, up->channel_oil, MAXVIFS, __PRETTY_FUNCTION__); pim_upstream_switch(pim, up, PIM_UPSTREAM_NOTJOINED); up->rpf.source_nexthop.interface = NULL; up->rpf.source_nexthop.mrib_nexthop_addr.u.prefix4.s_addr = PIM_NET_INADDR_ANY; up->rpf.source_nexthop.mrib_metric_preference = router->infinite_assert_metric.metric_preference; up->rpf.source_nexthop.mrib_route_metric = router->infinite_assert_metric.route_metric; up->rpf.rpf_addr.u.prefix4.s_addr = PIM_NET_INADDR_ANY; } } /* RFC 4601: 4.1.6. State Summarization Macros neighbor RPF'(S,G) { if ( I_Am_Assert_Loser(S, G, RPF_interface(S) )) { return AssertWinner(S, G, RPF_interface(S) ) } else { return NBR( RPF_interface(S), MRIB.next_hop( S ) ) } } RPF'(*,G) and RPF'(S,G) indicate the neighbor from which data packets should be coming and to which joins should be sent on the RP tree and SPT, respectively. */ static struct in_addr pim_rpf_find_rpf_addr(struct pim_upstream *up) { struct pim_ifchannel *rpf_ch; struct pim_neighbor *neigh; struct in_addr rpf_addr; if (!up->rpf.source_nexthop.interface) { zlog_warn("%s: missing RPF interface for upstream (S,G)=%s", __PRETTY_FUNCTION__, up->sg_str); rpf_addr.s_addr = PIM_NET_INADDR_ANY; return rpf_addr; } rpf_ch = pim_ifchannel_find(up->rpf.source_nexthop.interface, &up->sg); if (rpf_ch) { if (rpf_ch->ifassert_state == PIM_IFASSERT_I_AM_LOSER) { return rpf_ch->ifassert_winner; } } /* return NBR( RPF_interface(S), MRIB.next_hop( S ) ) */ neigh = pim_if_find_neighbor( up->rpf.source_nexthop.interface, up->rpf.source_nexthop.mrib_nexthop_addr.u.prefix4); if (neigh) rpf_addr = neigh->source_addr; else rpf_addr.s_addr = PIM_NET_INADDR_ANY; return rpf_addr; } int pim_rpf_addr_is_inaddr_none(struct pim_rpf *rpf) { switch (rpf->rpf_addr.family) { case AF_INET: return rpf->rpf_addr.u.prefix4.s_addr == INADDR_NONE; break; case AF_INET6: zlog_warn("%s: v6 Unimplmeneted", __PRETTY_FUNCTION__); return 1; break; default: return 0; break; } return 0; } int pim_rpf_addr_is_inaddr_any(struct pim_rpf *rpf) { switch (rpf->rpf_addr.family) { case AF_INET: return rpf->rpf_addr.u.prefix4.s_addr == INADDR_ANY; break; case AF_INET6: zlog_warn("%s: v6 Unimplmented", __PRETTY_FUNCTION__); return 1; break; default: return 0; break; } return 0; } int pim_rpf_is_same(struct pim_rpf *rpf1, struct pim_rpf *rpf2) { if (rpf1->source_nexthop.interface == rpf2->source_nexthop.interface) return 1; return 0; } unsigned int pim_rpf_hash_key(const void *arg) { const struct pim_nexthop_cache *r = arg; return jhash_1word(r->rpf.rpf_addr.u.prefix4.s_addr, 0); } bool pim_rpf_equal(const void *arg1, const void *arg2) { const struct pim_nexthop_cache *r1 = (const struct pim_nexthop_cache *)arg1; const struct pim_nexthop_cache *r2 = (const struct pim_nexthop_cache *)arg2; return prefix_same(&r1->rpf.rpf_addr, &r2->rpf.rpf_addr); } frr-7.2.1/pimd/pim_rpf.h0000644000000000000000000000473413610377563011762 00000000000000/* * PIM for Quagga * Copyright (C) 2008 Everton da Silva Marques * * This program is free software; you can redistribute it and/or modify * it under the terms of the 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; see the file COPYING; if not, write to the Free Software * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA */ #ifndef PIM_RPF_H #define PIM_RPF_H #include #include "pim_upstream.h" #include "pim_neighbor.h" /* RFC 4601: Metric Preference Preference value assigned to the unicast routing protocol that provided the route to the multicast source or Rendezvous-Point. Metric The unicast routing table metric associated with the route used to reach the multicast source or Rendezvous-Point. The metric is in units applicable to the unicast routing protocol used. */ struct pim_nexthop { struct in_addr last_lookup; long long last_lookup_time; struct interface *interface; /* RPF_interface(S) */ struct prefix mrib_nexthop_addr; /* MRIB.next_hop(S) */ uint32_t mrib_metric_preference; /* MRIB.pref(S) */ uint32_t mrib_route_metric; /* MRIB.metric(S) */ struct pim_neighbor *nbr; }; struct pim_rpf { struct pim_nexthop source_nexthop; struct prefix rpf_addr; /* RPF'(S,G) */ }; enum pim_rpf_result { PIM_RPF_OK = 0, PIM_RPF_CHANGED, PIM_RPF_FAILURE }; struct pim_upstream; unsigned int pim_rpf_hash_key(const void *arg); bool pim_rpf_equal(const void *arg1, const void *arg2); bool pim_nexthop_lookup(struct pim_instance *pim, struct pim_nexthop *nexthop, struct in_addr addr, int neighbor_needed); enum pim_rpf_result pim_rpf_update(struct pim_instance *pim, struct pim_upstream *up, struct pim_rpf *old); void pim_upstream_rpf_clear(struct pim_instance *pim, struct pim_upstream *up); int pim_rpf_addr_is_inaddr_none(struct pim_rpf *rpf); int pim_rpf_addr_is_inaddr_any(struct pim_rpf *rpf); int pim_rpf_is_same(struct pim_rpf *rpf1, struct pim_rpf *rpf2); void pim_rpf_set_refresh_time(struct pim_instance *pim); #endif /* PIM_RPF_H */ frr-7.2.1/pimd/pim_signals.c0000644000000000000000000000310413610377563012614 00000000000000/* * PIM for Quagga * Copyright (C) 2008 Everton da Silva Marques * * This program is free software; you can redistribute it and/or modify * it under the terms of the 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; see the file COPYING; if not, write to the Free Software * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA */ #include #include #include "sigevent.h" #include "memory.h" #include "log.h" #include "if.h" #include "pim_signals.h" #include "pimd.h" /* * Signal handlers */ static void pim_sighup(void) { zlog_info("SIGHUP received, ignoring"); } static void pim_sigint(void) { zlog_notice("Terminating on signal SIGINT"); pim_terminate(); exit(1); } static void pim_sigterm(void) { zlog_notice("Terminating on signal SIGTERM"); pim_terminate(); exit(1); } static void pim_sigusr1(void) { zlog_rotate(); } struct quagga_signal_t pimd_signals[] = { { .signal = SIGHUP, .handler = &pim_sighup, }, { .signal = SIGUSR1, .handler = &pim_sigusr1, }, { .signal = SIGINT, .handler = &pim_sigint, }, { .signal = SIGTERM, .handler = &pim_sigterm, }, }; frr-7.2.1/pimd/pim_signals.h0000644000000000000000000000167013610377563012627 00000000000000/* * PIM for Quagga * Copyright (C) 2008 Everton da Silva Marques * * This program is free software; you can redistribute it and/or modify * it under the terms of the 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; see the file COPYING; if not, write to the Free Software * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA */ #ifndef PIM_SIGNALS_H #define PIM_SIGNALS_H #include "sigevent.h" extern struct quagga_signal_t pimd_signals[]; #endif /* PIM_SIGNALS_H */ frr-7.2.1/pimd/pim_sock.c0000644000000000000000000002375013610377563012124 00000000000000/* * PIM for Quagga * Copyright (C) 2008 Everton da Silva Marques * * This program is free software; you can redistribute it and/or modify * it under the terms of the 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; see the file COPYING; if not, write to the Free Software * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA */ #include #include #include #include #include #include #include #include #include #include "log.h" #include "privs.h" #include "if.h" #include "vrf.h" #include "sockopt.h" #include "lib_errors.h" #include "pimd.h" #include "pim_mroute.h" #include "pim_sock.h" #include "pim_str.h" /* GLOBAL VARS */ int pim_socket_raw(int protocol) { int fd; frr_with_privs(&pimd_privs) { fd = socket(AF_INET, SOCK_RAW, protocol); } if (fd < 0) { zlog_warn("Could not create raw socket: errno=%d: %s", errno, safe_strerror(errno)); return PIM_SOCK_ERR_SOCKET; } return fd; } void pim_socket_ip_hdr(int fd) { const int on = 1; frr_with_privs(&pimd_privs) { if (setsockopt(fd, IPPROTO_IP, IP_HDRINCL, &on, sizeof(on))) zlog_err("%s: Could not turn on IP_HDRINCL option: %s", __PRETTY_FUNCTION__, safe_strerror(errno)); } } /* * Given a socket and a interface, * Bind that socket to that interface */ int pim_socket_bind(int fd, struct interface *ifp) { int ret = 0; #ifdef SO_BINDTODEVICE frr_with_privs(&pimd_privs) { ret = setsockopt(fd, SOL_SOCKET, SO_BINDTODEVICE, ifp->name, strlen(ifp->name)); } #endif return ret; } int pim_socket_mcast(int protocol, struct in_addr ifaddr, struct interface *ifp, uint8_t loop) { int rcvbuf = 1024 * 1024 * 8; #ifdef HAVE_STRUCT_IP_MREQN_IMR_IFINDEX struct ip_mreqn mreq; #else struct ip_mreq mreq; #endif int fd; fd = pim_socket_raw(protocol); if (fd < 0) { zlog_warn("Could not create multicast socket: errno=%d: %s", errno, safe_strerror(errno)); return PIM_SOCK_ERR_SOCKET; } #ifdef SO_BINDTODEVICE if (protocol == IPPROTO_PIM) { int ret; ret = pim_socket_bind(fd, ifp); if (ret) { close(fd); zlog_warn( "Could not set fd: %d for interface: %s to device", fd, ifp->name); return PIM_SOCK_ERR_BIND; } } #else /* XXX: use IP_PKTINFO / IP_RECVIF to emulate behaviour? Or change to * only use 1 socket for all interfaces? */ #endif /* Needed to obtain destination address from recvmsg() */ { #if defined(HAVE_IP_PKTINFO) /* Linux and Solaris IP_PKTINFO */ int opt = 1; if (setsockopt(fd, IPPROTO_IP, IP_PKTINFO, &opt, sizeof(opt))) { zlog_warn( "Could not set IP_PKTINFO on socket fd=%d: errno=%d: %s", fd, errno, safe_strerror(errno)); } #elif defined(HAVE_IP_RECVDSTADDR) /* BSD IP_RECVDSTADDR */ int opt = 1; if (setsockopt(fd, IPPROTO_IP, IP_RECVDSTADDR, &opt, sizeof(opt))) { zlog_warn( "Could not set IP_RECVDSTADDR on socket fd=%d: errno=%d: %s", fd, errno, safe_strerror(errno)); } #else flog_err( EC_LIB_DEVELOPMENT, "%s %s: Missing IP_PKTINFO and IP_RECVDSTADDR: unable to get dst addr from recvmsg()", __FILE__, __PRETTY_FUNCTION__); close(fd); return PIM_SOCK_ERR_DSTADDR; #endif } /* Set router alert (RFC 2113) for all IGMP messages (RFC 3376 4. * Message Formats)*/ if (protocol == IPPROTO_IGMP) { uint8_t ra[4]; ra[0] = 148; ra[1] = 4; ra[2] = 0; ra[3] = 0; if (setsockopt(fd, IPPROTO_IP, IP_OPTIONS, ra, 4)) { zlog_warn( "Could not set Router Alert Option on socket fd=%d: errno=%d: %s", fd, errno, safe_strerror(errno)); close(fd); return PIM_SOCK_ERR_RA; } } { int reuse = 1; if (setsockopt(fd, SOL_SOCKET, SO_REUSEADDR, (void *)&reuse, sizeof(reuse))) { zlog_warn( "Could not set Reuse Address Option on socket fd=%d: errno=%d: %s", fd, errno, safe_strerror(errno)); close(fd); return PIM_SOCK_ERR_REUSE; } } { const int MTTL = 1; int ttl = MTTL; if (setsockopt(fd, IPPROTO_IP, IP_MULTICAST_TTL, (void *)&ttl, sizeof(ttl))) { zlog_warn( "Could not set multicast TTL=%d on socket fd=%d: errno=%d: %s", MTTL, fd, errno, safe_strerror(errno)); close(fd); return PIM_SOCK_ERR_TTL; } } if (setsockopt_ipv4_multicast_loop(fd, loop)) { zlog_warn( "Could not %s Multicast Loopback Option on socket fd=%d: errno=%d: %s", loop ? "enable" : "disable", fd, errno, safe_strerror(errno)); close(fd); return PIM_SOCK_ERR_LOOP; } memset(&mreq, 0, sizeof(mreq)); #ifdef HAVE_STRUCT_IP_MREQN_IMR_IFINDEX mreq.imr_ifindex = ifp->ifindex; #else /* * I am not sure what to do here yet for *BSD */ // mreq.imr_interface = ifindex; #endif if (setsockopt(fd, IPPROTO_IP, IP_MULTICAST_IF, (void *)&mreq, sizeof(mreq))) { zlog_warn( "Could not set Outgoing Interface Option on socket fd=%d: errno=%d: %s", fd, errno, safe_strerror(errno)); close(fd); return PIM_SOCK_ERR_IFACE; } if (setsockopt(fd, SOL_SOCKET, SO_RCVBUF, &rcvbuf, sizeof(rcvbuf))) zlog_warn("%s: Failure to set buffer size to %d", __PRETTY_FUNCTION__, rcvbuf); { long flags; flags = fcntl(fd, F_GETFL, 0); if (flags < 0) { zlog_warn( "Could not get fcntl(F_GETFL,O_NONBLOCK) on socket fd=%d: errno=%d: %s", fd, errno, safe_strerror(errno)); close(fd); return PIM_SOCK_ERR_NONBLOCK_GETFL; } if (fcntl(fd, F_SETFL, flags | O_NONBLOCK)) { zlog_warn( "Could not set fcntl(F_SETFL,O_NONBLOCK) on socket fd=%d: errno=%d: %s", fd, errno, safe_strerror(errno)); close(fd); return PIM_SOCK_ERR_NONBLOCK_SETFL; } } return fd; } int pim_socket_join(int fd, struct in_addr group, struct in_addr ifaddr, ifindex_t ifindex) { int ret; #ifdef HAVE_STRUCT_IP_MREQN_IMR_IFINDEX struct ip_mreqn opt; #else struct ip_mreq opt; #endif opt.imr_multiaddr = group; #ifdef HAVE_STRUCT_IP_MREQN_IMR_IFINDEX opt.imr_address = ifaddr; opt.imr_ifindex = ifindex; #else opt.imr_interface = ifaddr; #endif ret = setsockopt(fd, IPPROTO_IP, IP_ADD_MEMBERSHIP, &opt, sizeof(opt)); if (ret) { char group_str[INET_ADDRSTRLEN]; char ifaddr_str[INET_ADDRSTRLEN]; if (!inet_ntop(AF_INET, &group, group_str, sizeof(group_str))) sprintf(group_str, ""); if (!inet_ntop(AF_INET, &ifaddr, ifaddr_str, sizeof(ifaddr_str))) sprintf(ifaddr_str, ""); flog_err( EC_LIB_SOCKET, "Failure socket joining fd=%d group %s on interface address %s: errno=%d: %s", fd, group_str, ifaddr_str, errno, safe_strerror(errno)); return ret; } if (PIM_DEBUG_TRACE) { char group_str[INET_ADDRSTRLEN]; char ifaddr_str[INET_ADDRSTRLEN]; if (!inet_ntop(AF_INET, &group, group_str, sizeof(group_str))) sprintf(group_str, ""); if (!inet_ntop(AF_INET, &ifaddr, ifaddr_str, sizeof(ifaddr_str))) sprintf(ifaddr_str, ""); zlog_debug( "Socket fd=%d joined group %s on interface address %s", fd, group_str, ifaddr_str); } return ret; } int pim_socket_recvfromto(int fd, uint8_t *buf, size_t len, struct sockaddr_in *from, socklen_t *fromlen, struct sockaddr_in *to, socklen_t *tolen, ifindex_t *ifindex) { struct msghdr msgh; struct cmsghdr *cmsg; struct iovec iov; char cbuf[1000]; int err; /* * IP_PKTINFO / IP_RECVDSTADDR don't yield sin_port. * Use getsockname() to get sin_port. */ if (to) { struct sockaddr_in si; socklen_t si_len = sizeof(si); memset(&si, 0, sizeof(si)); to->sin_family = AF_INET; pim_socket_getsockname(fd, (struct sockaddr *)&si, &si_len); to->sin_port = si.sin_port; to->sin_addr = si.sin_addr; if (tolen) *tolen = sizeof(si); } memset(&msgh, 0, sizeof(struct msghdr)); iov.iov_base = buf; iov.iov_len = len; msgh.msg_control = cbuf; msgh.msg_controllen = sizeof(cbuf); msgh.msg_name = from; msgh.msg_namelen = fromlen ? *fromlen : 0; msgh.msg_iov = &iov; msgh.msg_iovlen = 1; msgh.msg_flags = 0; err = recvmsg(fd, &msgh, 0); if (err < 0) return err; if (fromlen) *fromlen = msgh.msg_namelen; for (cmsg = CMSG_FIRSTHDR(&msgh); cmsg != NULL; cmsg = CMSG_NXTHDR(&msgh, cmsg)) { #ifdef HAVE_IP_PKTINFO if ((cmsg->cmsg_level == IPPROTO_IP) && (cmsg->cmsg_type == IP_PKTINFO)) { struct in_pktinfo *i = (struct in_pktinfo *)CMSG_DATA(cmsg); if (to) ((struct sockaddr_in *)to)->sin_addr = i->ipi_addr; if (tolen) *tolen = sizeof(struct sockaddr_in); if (ifindex) *ifindex = i->ipi_ifindex; break; } #endif #ifdef HAVE_IP_RECVDSTADDR if ((cmsg->cmsg_level == IPPROTO_IP) && (cmsg->cmsg_type == IP_RECVDSTADDR)) { struct in_addr *i = (struct in_addr *)CMSG_DATA(cmsg); if (to) ((struct sockaddr_in *)to)->sin_addr = *i; if (tolen) *tolen = sizeof(struct sockaddr_in); break; } #endif #if defined(HAVE_IP_RECVIF) && defined(CMSG_IFINDEX) if (cmsg->cmsg_type == IP_RECVIF) if (ifindex) *ifindex = CMSG_IFINDEX(cmsg); #endif } /* for (cmsg) */ return err; /* len */ } int pim_socket_mcastloop_get(int fd) { int loop; socklen_t loop_len = sizeof(loop); if (getsockopt(fd, IPPROTO_IP, IP_MULTICAST_LOOP, &loop, &loop_len)) { int e = errno; zlog_warn( "Could not get Multicast Loopback Option on socket fd=%d: errno=%d: %s", fd, errno, safe_strerror(errno)); errno = e; return PIM_SOCK_ERR_LOOP; } return loop; } int pim_socket_getsockname(int fd, struct sockaddr *name, socklen_t *namelen) { if (getsockname(fd, name, namelen)) { int e = errno; zlog_warn( "Could not get Socket Name for socket fd=%d: errno=%d: %s", fd, errno, safe_strerror(errno)); errno = e; return PIM_SOCK_ERR_NAME; } return PIM_SOCK_ERR_NONE; } frr-7.2.1/pimd/pim_sock.h0000644000000000000000000000426613610377563012132 00000000000000/* * PIM for Quagga * Copyright (C) 2008 Everton da Silva Marques * * This program is free software; you can redistribute it and/or modify * it under the terms of the 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; see the file COPYING; if not, write to the Free Software * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA */ #ifndef PIM_SOCK_H #define PIM_SOCK_H #include #define PIM_SOCK_ERR_NONE (0) /* No error */ #define PIM_SOCK_ERR_SOCKET (-1) /* socket() */ #define PIM_SOCK_ERR_RA (-2) /* Router Alert option */ #define PIM_SOCK_ERR_REUSE (-3) /* Reuse option */ #define PIM_SOCK_ERR_TTL (-4) /* TTL option */ #define PIM_SOCK_ERR_LOOP (-5) /* Loopback option */ #define PIM_SOCK_ERR_IFACE (-6) /* Outgoing interface option */ #define PIM_SOCK_ERR_DSTADDR (-7) /* Outgoing interface option */ #define PIM_SOCK_ERR_NONBLOCK_GETFL (-8) /* Get O_NONBLOCK */ #define PIM_SOCK_ERR_NONBLOCK_SETFL (-9) /* Set O_NONBLOCK */ #define PIM_SOCK_ERR_NAME (-10) /* Socket name (getsockname) */ #define PIM_SOCK_ERR_BIND (-11) /* Can't bind to interface */ int pim_socket_bind(int fd, struct interface *ifp); void pim_socket_ip_hdr(int fd); int pim_socket_raw(int protocol); int pim_socket_mcast(int protocol, struct in_addr ifaddr, struct interface *ifp, uint8_t loop); int pim_socket_join(int fd, struct in_addr group, struct in_addr ifaddr, ifindex_t ifindex); int pim_socket_recvfromto(int fd, uint8_t *buf, size_t len, struct sockaddr_in *from, socklen_t *fromlen, struct sockaddr_in *to, socklen_t *tolen, ifindex_t *ifindex); int pim_socket_mcastloop_get(int fd); int pim_socket_getsockname(int fd, struct sockaddr *name, socklen_t *namelen); #endif /* PIM_SOCK_H */ frr-7.2.1/pimd/pim_ssm.c0000644000000000000000000000757613610377563011777 00000000000000/* * IP SSM ranges for FRR * Copyright (C) 2017 Cumulus Networks, Inc. * * This program is free software; you can redistribute it and/or modify * it under the terms of the 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; see the file COPYING; if not, write to the Free Software * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA */ #include #include #include #include #include #include #include #include "pimd.h" #include "pim_ssm.h" #include "pim_zebra.h" static void pim_ssm_range_reevaluate(struct pim_instance *pim) { /* 1. Setup register state for (S,G) entries if G has changed from SSM * to * ASM. * 2. check existing (*,G) IGMP registrations to see if they are * still ASM. if they are now SSM delete them. * 3. Allow channel setup for IGMP (*,G) members if G is now ASM * 4. I could tear down all (*,G), (S,G,rpt) states. But that is an * unnecessary sladge hammer and may not be particularly useful as it is * likely the SPT switchover has already happened for flows along such * RPTs. * As for the RPT states it seems that the best thing to do is let them * age * out gracefully. As long as the FHR and LHR do the right thing RPTs * will * disappear in time for SSM groups. */ pim_upstream_register_reevaluate(pim); igmp_source_forward_reevaluate_all(pim); } void pim_ssm_prefix_list_update(struct pim_instance *pim, struct prefix_list *plist) { struct pim_ssm *ssm = pim->ssm_info; if (!ssm->plist_name || strcmp(ssm->plist_name, prefix_list_name(plist))) { /* not ours */ return; } pim_ssm_range_reevaluate(pim); } static int pim_is_grp_standard_ssm(struct prefix *group) { static int first = 1; static struct prefix group_ssm; if (first) { if (!str2prefix(PIM_SSM_STANDARD_RANGE, &group_ssm)) flog_err(EC_LIB_DEVELOPMENT, "%s: Failure to Read Group Address: %s", __PRETTY_FUNCTION__, PIM_SSM_STANDARD_RANGE); first = 0; } return prefix_match(&group_ssm, group); } int pim_is_grp_ssm(struct pim_instance *pim, struct in_addr group_addr) { struct pim_ssm *ssm; struct prefix group; struct prefix_list *plist; memset(&group, 0, sizeof(group)); group.family = AF_INET; group.u.prefix4 = group_addr; group.prefixlen = 32; ssm = pim->ssm_info; if (!ssm->plist_name) { return pim_is_grp_standard_ssm(&group); } plist = prefix_list_lookup(AFI_IP, ssm->plist_name); if (!plist) return 0; return (prefix_list_apply(plist, &group) == PREFIX_PERMIT); } int pim_ssm_range_set(struct pim_instance *pim, vrf_id_t vrf_id, const char *plist_name) { struct pim_ssm *ssm; int change = 0; if (vrf_id != pim->vrf_id) return PIM_SSM_ERR_NO_VRF; ssm = pim->ssm_info; if (plist_name) { if (ssm->plist_name) { if (!strcmp(ssm->plist_name, plist_name)) return PIM_SSM_ERR_DUP; XFREE(MTYPE_PIM_FILTER_NAME, ssm->plist_name); } ssm->plist_name = XSTRDUP(MTYPE_PIM_FILTER_NAME, plist_name); change = 1; } else { if (ssm->plist_name) { change = 1; XFREE(MTYPE_PIM_FILTER_NAME, ssm->plist_name); } } if (change) pim_ssm_range_reevaluate(pim); return PIM_SSM_ERR_NONE; } void *pim_ssm_init(void) { struct pim_ssm *ssm; ssm = XCALLOC(MTYPE_PIM_SSM_INFO, sizeof(*ssm)); return ssm; } void pim_ssm_terminate(struct pim_ssm *ssm) { if (!ssm) return; XFREE(MTYPE_PIM_FILTER_NAME, ssm->plist_name); XFREE(MTYPE_PIM_SSM_INFO, ssm); } frr-7.2.1/pimd/pim_ssm.h0000644000000000000000000000262313610377563011770 00000000000000/* * IP SSM ranges for FRR * Copyright (C) 2017 Cumulus Networks, Inc. * * This program is free software; you can redistribute it and/or modify * it under the terms of the 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; see the file COPYING; if not, write to the Free Software * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA */ #ifndef PIM_SSM_H #define PIM_SSM_H #define PIM_SSM_STANDARD_RANGE "232.0.0.0/8" /* SSM error codes */ enum pim_ssm_err { PIM_SSM_ERR_NONE = 0, PIM_SSM_ERR_NO_VRF = -1, PIM_SSM_ERR_DUP = -2, }; struct pim_ssm { char *plist_name; /* prefix list of group ranges */ }; void pim_ssm_prefix_list_update(struct pim_instance *pim, struct prefix_list *plist); int pim_is_grp_ssm(struct pim_instance *pim, struct in_addr group_addr); int pim_ssm_range_set(struct pim_instance *pim, vrf_id_t vrf_id, const char *plist_name); void *pim_ssm_init(void); void pim_ssm_terminate(struct pim_ssm *ssm); #endif frr-7.2.1/pimd/pim_ssmpingd.c0000644000000000000000000002620313610377563013005 00000000000000/* * PIM for Quagga * Copyright (C) 2008 Everton da Silva Marques * * This program is free software; you can redistribute it and/or modify * it under the terms of the 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; see the file COPYING; if not, write to the Free Software * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA */ #include #include "if.h" #include "log.h" #include "memory.h" #include "sockopt.h" #include "vrf.h" #include "lib_errors.h" #include "pimd.h" #include "pim_ssmpingd.h" #include "pim_time.h" #include "pim_sock.h" static const char *const PIM_SSMPINGD_REPLY_GROUP = "232.43.211.234"; enum { PIM_SSMPINGD_REQUEST = 'Q', PIM_SSMPINGD_REPLY = 'A' }; static void ssmpingd_read_on(struct ssmpingd_sock *ss); void pim_ssmpingd_init(struct pim_instance *pim) { int result; zassert(!pim->ssmpingd_list); result = inet_pton(AF_INET, PIM_SSMPINGD_REPLY_GROUP, &pim->ssmpingd_group_addr); zassert(result > 0); } void pim_ssmpingd_destroy(struct pim_instance *pim) { if (pim->ssmpingd_list) list_delete(&pim->ssmpingd_list); } static struct ssmpingd_sock *ssmpingd_find(struct pim_instance *pim, struct in_addr source_addr) { struct listnode *node; struct ssmpingd_sock *ss; if (!pim->ssmpingd_list) return 0; for (ALL_LIST_ELEMENTS_RO(pim->ssmpingd_list, node, ss)) if (source_addr.s_addr == ss->source_addr.s_addr) return ss; return 0; } static void ssmpingd_free(struct ssmpingd_sock *ss) { XFREE(MTYPE_PIM_SSMPINGD, ss); } static int ssmpingd_socket(struct in_addr addr, int port, int mttl) { struct sockaddr_in sockaddr; int fd; fd = socket(AF_INET, SOCK_DGRAM, IPPROTO_UDP); if (fd < 0) { flog_err_sys(EC_LIB_SOCKET, "%s: could not create socket: errno=%d: %s", __PRETTY_FUNCTION__, errno, safe_strerror(errno)); return -1; } sockaddr.sin_family = AF_INET; sockaddr.sin_addr = addr; sockaddr.sin_port = htons(port); if (bind(fd, (struct sockaddr *)&sockaddr, sizeof(sockaddr))) { char addr_str[INET_ADDRSTRLEN]; pim_inet4_dump("", addr, addr_str, sizeof(addr_str)); zlog_warn( "%s: bind(fd=%d,addr=%s,port=%d,len=%zu) failure: errno=%d: %s", __PRETTY_FUNCTION__, fd, addr_str, port, sizeof(sockaddr), errno, safe_strerror(errno)); close(fd); return -1; } /* Needed to obtain destination address from recvmsg() */ { #if defined(HAVE_IP_PKTINFO) /* Linux and Solaris IP_PKTINFO */ int opt = 1; if (setsockopt(fd, IPPROTO_IP, IP_PKTINFO, &opt, sizeof(opt))) { zlog_warn( "%s: could not set IP_PKTINFO on socket fd=%d: errno=%d: %s", __PRETTY_FUNCTION__, fd, errno, safe_strerror(errno)); } #elif defined(HAVE_IP_RECVDSTADDR) /* BSD IP_RECVDSTADDR */ int opt = 1; if (setsockopt(fd, IPPROTO_IP, IP_RECVDSTADDR, &opt, sizeof(opt))) { zlog_warn( "%s: could not set IP_RECVDSTADDR on socket fd=%d: errno=%d: %s", __PRETTY_FUNCTION__, fd, errno, safe_strerror(errno)); } #else flog_err( EC_LIB_DEVELOPMENT, "%s %s: missing IP_PKTINFO and IP_RECVDSTADDR: unable to get dst addr from recvmsg()", __FILE__, __PRETTY_FUNCTION__); close(fd); return -1; #endif } { int reuse = 1; if (setsockopt(fd, SOL_SOCKET, SO_REUSEADDR, (void *)&reuse, sizeof(reuse))) { zlog_warn( "%s: could not set Reuse Address Option on socket fd=%d: errno=%d: %s", __PRETTY_FUNCTION__, fd, errno, safe_strerror(errno)); close(fd); return -1; } } if (setsockopt(fd, IPPROTO_IP, IP_MULTICAST_TTL, (void *)&mttl, sizeof(mttl))) { zlog_warn( "%s: could not set multicast TTL=%d on socket fd=%d: errno=%d: %s", __PRETTY_FUNCTION__, mttl, fd, errno, safe_strerror(errno)); close(fd); return -1; } if (setsockopt_ipv4_multicast_loop(fd, 0)) { zlog_warn( "%s: could not disable Multicast Loopback Option on socket fd=%d: errno=%d: %s", __PRETTY_FUNCTION__, fd, errno, safe_strerror(errno)); close(fd); return PIM_SOCK_ERR_LOOP; } if (setsockopt(fd, IPPROTO_IP, IP_MULTICAST_IF, (void *)&addr, sizeof(addr))) { zlog_warn( "%s: could not set Outgoing Interface Option on socket fd=%d: errno=%d: %s", __PRETTY_FUNCTION__, fd, errno, safe_strerror(errno)); close(fd); return -1; } { long flags; flags = fcntl(fd, F_GETFL, 0); if (flags < 0) { zlog_warn( "%s: could not get fcntl(F_GETFL,O_NONBLOCK) on socket fd=%d: errno=%d: %s", __PRETTY_FUNCTION__, fd, errno, safe_strerror(errno)); close(fd); return -1; } if (fcntl(fd, F_SETFL, flags | O_NONBLOCK)) { zlog_warn( "%s: could not set fcntl(F_SETFL,O_NONBLOCK) on socket fd=%d: errno=%d: %s", __PRETTY_FUNCTION__, fd, errno, safe_strerror(errno)); close(fd); return -1; } } return fd; } static void ssmpingd_delete(struct ssmpingd_sock *ss) { zassert(ss); THREAD_OFF(ss->t_sock_read); if (close(ss->sock_fd)) { char source_str[INET_ADDRSTRLEN]; pim_inet4_dump("", ss->source_addr, source_str, sizeof(source_str)); zlog_warn( "%s: failure closing ssmpingd sock_fd=%d for source %s: errno=%d: %s", __PRETTY_FUNCTION__, ss->sock_fd, source_str, errno, safe_strerror(errno)); /* warning only */ } listnode_delete(ss->pim->ssmpingd_list, ss); ssmpingd_free(ss); } static void ssmpingd_sendto(struct ssmpingd_sock *ss, const uint8_t *buf, int len, struct sockaddr_in to) { socklen_t tolen = sizeof(to); int sent; sent = sendto(ss->sock_fd, buf, len, MSG_DONTWAIT, (struct sockaddr *)&to, tolen); if (sent != len) { char to_str[INET_ADDRSTRLEN]; pim_inet4_dump("", to.sin_addr, to_str, sizeof(to_str)); if (sent < 0) { zlog_warn( "%s: sendto() failure to %s,%d: fd=%d len=%d: errno=%d: %s", __PRETTY_FUNCTION__, to_str, ntohs(to.sin_port), ss->sock_fd, len, errno, safe_strerror(errno)); } else { zlog_warn( "%s: sendto() partial to %s,%d: fd=%d len=%d: sent=%d", __PRETTY_FUNCTION__, to_str, ntohs(to.sin_port), ss->sock_fd, len, sent); } } } static int ssmpingd_read_msg(struct ssmpingd_sock *ss) { struct interface *ifp; struct sockaddr_in from; struct sockaddr_in to; socklen_t fromlen = sizeof(from); socklen_t tolen = sizeof(to); ifindex_t ifindex = -1; uint8_t buf[1000]; int len; ++ss->requests; len = pim_socket_recvfromto(ss->sock_fd, buf, sizeof(buf), &from, &fromlen, &to, &tolen, &ifindex); if (len < 0) { char source_str[INET_ADDRSTRLEN]; pim_inet4_dump("", ss->source_addr, source_str, sizeof(source_str)); zlog_warn( "%s: failure receiving ssmping for source %s on fd=%d: errno=%d: %s", __PRETTY_FUNCTION__, source_str, ss->sock_fd, errno, safe_strerror(errno)); return -1; } ifp = if_lookup_by_index(ifindex, ss->pim->vrf_id); if (buf[0] != PIM_SSMPINGD_REQUEST) { char source_str[INET_ADDRSTRLEN]; char from_str[INET_ADDRSTRLEN]; char to_str[INET_ADDRSTRLEN]; pim_inet4_dump("", ss->source_addr, source_str, sizeof(source_str)); pim_inet4_dump("", from.sin_addr, from_str, sizeof(from_str)); pim_inet4_dump("", to.sin_addr, to_str, sizeof(to_str)); zlog_warn( "%s: bad ssmping type=%d from %s,%d to %s,%d on interface %s ifindex=%d fd=%d src=%s", __PRETTY_FUNCTION__, buf[0], from_str, ntohs(from.sin_port), to_str, ntohs(to.sin_port), ifp ? ifp->name : "", ifindex, ss->sock_fd, source_str); return 0; } if (PIM_DEBUG_SSMPINGD) { char source_str[INET_ADDRSTRLEN]; char from_str[INET_ADDRSTRLEN]; char to_str[INET_ADDRSTRLEN]; pim_inet4_dump("", ss->source_addr, source_str, sizeof(source_str)); pim_inet4_dump("", from.sin_addr, from_str, sizeof(from_str)); pim_inet4_dump("", to.sin_addr, to_str, sizeof(to_str)); zlog_debug( "%s: recv ssmping from %s,%d to %s,%d on interface %s ifindex=%d fd=%d src=%s", __PRETTY_FUNCTION__, from_str, ntohs(from.sin_port), to_str, ntohs(to.sin_port), ifp ? ifp->name : "", ifindex, ss->sock_fd, source_str); } buf[0] = PIM_SSMPINGD_REPLY; /* unicast reply */ ssmpingd_sendto(ss, buf, len, from); /* multicast reply */ from.sin_addr = ss->pim->ssmpingd_group_addr; ssmpingd_sendto(ss, buf, len, from); return 0; } static int ssmpingd_sock_read(struct thread *t) { struct ssmpingd_sock *ss; int result; ss = THREAD_ARG(t); result = ssmpingd_read_msg(ss); /* Keep reading */ ssmpingd_read_on(ss); return result; } static void ssmpingd_read_on(struct ssmpingd_sock *ss) { thread_add_read(router->master, ssmpingd_sock_read, ss, ss->sock_fd, &ss->t_sock_read); } static struct ssmpingd_sock *ssmpingd_new(struct pim_instance *pim, struct in_addr source_addr) { struct ssmpingd_sock *ss; int sock_fd; if (!pim->ssmpingd_list) { pim->ssmpingd_list = list_new(); pim->ssmpingd_list->del = (void (*)(void *))ssmpingd_free; } sock_fd = ssmpingd_socket(source_addr, /* port: */ 4321, /* mTTL: */ 64); if (sock_fd < 0) { char source_str[INET_ADDRSTRLEN]; pim_inet4_dump("", source_addr, source_str, sizeof(source_str)); zlog_warn("%s: ssmpingd_socket() failure for source %s", __PRETTY_FUNCTION__, source_str); return 0; } ss = XCALLOC(MTYPE_PIM_SSMPINGD, sizeof(*ss)); ss->pim = pim; ss->sock_fd = sock_fd; ss->t_sock_read = NULL; ss->source_addr = source_addr; ss->creation = pim_time_monotonic_sec(); ss->requests = 0; listnode_add(pim->ssmpingd_list, ss); ssmpingd_read_on(ss); return ss; } int pim_ssmpingd_start(struct pim_instance *pim, struct in_addr source_addr) { struct ssmpingd_sock *ss; ss = ssmpingd_find(pim, source_addr); if (ss) { /* silently ignore request to recreate entry */ return 0; } { char source_str[INET_ADDRSTRLEN]; pim_inet4_dump("", source_addr, source_str, sizeof(source_str)); zlog_info("%s: starting ssmpingd for source %s", __PRETTY_FUNCTION__, source_str); } ss = ssmpingd_new(pim, source_addr); if (!ss) { char source_str[INET_ADDRSTRLEN]; pim_inet4_dump("", source_addr, source_str, sizeof(source_str)); zlog_warn("%s: ssmpingd_new() failure for source %s", __PRETTY_FUNCTION__, source_str); return -1; } return 0; } int pim_ssmpingd_stop(struct pim_instance *pim, struct in_addr source_addr) { struct ssmpingd_sock *ss; ss = ssmpingd_find(pim, source_addr); if (!ss) { char source_str[INET_ADDRSTRLEN]; pim_inet4_dump("", source_addr, source_str, sizeof(source_str)); zlog_warn("%s: could not find ssmpingd for source %s", __PRETTY_FUNCTION__, source_str); return -1; } { char source_str[INET_ADDRSTRLEN]; pim_inet4_dump("", source_addr, source_str, sizeof(source_str)); zlog_info("%s: stopping ssmpingd for source %s", __PRETTY_FUNCTION__, source_str); } ssmpingd_delete(ss); return 0; } frr-7.2.1/pimd/pim_ssmpingd.h0000644000000000000000000000273113610377563013012 00000000000000/* * PIM for Quagga * Copyright (C) 2008 Everton da Silva Marques * * This program is free software; you can redistribute it and/or modify * it under the terms of the 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; see the file COPYING; if not, write to the Free Software * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA */ #ifndef PIM_SSMPINGD_H #define PIM_SSMPINGD_H #include #include "if.h" #include "pim_iface.h" struct ssmpingd_sock { struct pim_instance *pim; int sock_fd; /* socket */ struct thread *t_sock_read; /* thread for reading socket */ struct in_addr source_addr; /* source address */ int64_t creation; /* timestamp of socket creation */ int64_t requests; /* counter */ }; void pim_ssmpingd_init(struct pim_instance *pim); void pim_ssmpingd_destroy(struct pim_instance *pim); int pim_ssmpingd_start(struct pim_instance *pim, struct in_addr source_addr); int pim_ssmpingd_stop(struct pim_instance *pim, struct in_addr source_addr); #endif /* PIM_SSMPINGD_H */ frr-7.2.1/pimd/pim_static.c0000644000000000000000000002547713610377563012464 00000000000000/* * PIM for Quagga: add the ability to configure multicast static routes * Copyright (C) 2014 Nathan Bahr, ATCorp * * This program is free software; you can redistribute it and/or modify * it under the terms of the 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; see the file COPYING; if not, write to the Free Software * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA */ #include #include "vty.h" #include "if.h" #include "log.h" #include "memory.h" #include "linklist.h" #include "pimd.h" #include "pim_oil.h" #include "pim_static.h" #include "pim_time.h" #include "pim_str.h" #include "pim_iface.h" void pim_static_route_free(struct static_route *s_route) { XFREE(MTYPE_PIM_STATIC_ROUTE, s_route); } static struct static_route *static_route_alloc(void) { return XCALLOC(MTYPE_PIM_STATIC_ROUTE, sizeof(struct static_route)); } static struct static_route *static_route_new(ifindex_t iif, ifindex_t oif, struct in_addr group, struct in_addr source) { struct static_route *s_route; s_route = static_route_alloc(); s_route->group = group; s_route->source = source; s_route->iif = iif; s_route->oif_ttls[oif] = 1; s_route->c_oil.oil_ref_count = 1; s_route->c_oil.oil.mfcc_origin = source; s_route->c_oil.oil.mfcc_mcastgrp = group; s_route->c_oil.oil.mfcc_parent = iif; s_route->c_oil.oil.mfcc_ttls[oif] = 1; s_route->c_oil.oif_creation[oif] = pim_time_monotonic_sec(); return s_route; } int pim_static_add(struct pim_instance *pim, struct interface *iif, struct interface *oif, struct in_addr group, struct in_addr source) { struct listnode *node = NULL; struct static_route *s_route = NULL; struct static_route *original_s_route = NULL; struct pim_interface *pim_iif = iif ? iif->info : NULL; struct pim_interface *pim_oif = oif ? oif->info : NULL; ifindex_t iif_index = pim_iif ? pim_iif->mroute_vif_index : 0; ifindex_t oif_index = pim_oif ? pim_oif->mroute_vif_index : 0; if (!iif_index || !oif_index || iif_index == -1 || oif_index == -1) { zlog_warn( "%s %s: Unable to add static route: Invalid interface index(iif=%d,oif=%d)", __FILE__, __PRETTY_FUNCTION__, iif_index, oif_index); return -2; } #ifdef PIM_ENFORCE_LOOPFREE_MFC if (iif_index == oif_index) { /* looped MFC entry */ zlog_warn( "%s %s: Unable to add static route: Looped MFC entry(iif=%d,oif=%d)", __FILE__, __PRETTY_FUNCTION__, iif_index, oif_index); return -4; } #endif if (iif->vrf_id != oif->vrf_id) { return -3; } for (ALL_LIST_ELEMENTS_RO(pim->static_routes, node, s_route)) { if (s_route->group.s_addr == group.s_addr && s_route->source.s_addr == source.s_addr) { if (s_route->iif == iif_index && s_route->oif_ttls[oif_index]) { char gifaddr_str[INET_ADDRSTRLEN]; char sifaddr_str[INET_ADDRSTRLEN]; pim_inet4_dump("", group, gifaddr_str, sizeof(gifaddr_str)); pim_inet4_dump("", source, sifaddr_str, sizeof(sifaddr_str)); zlog_warn( "%s %s: Unable to add static route: Route already exists (iif=%d,oif=%d,group=%s,source=%s)", __FILE__, __PRETTY_FUNCTION__, iif_index, oif_index, gifaddr_str, sifaddr_str); return -3; } /* Ok, from here on out we will be making changes to the * s_route structure, but if * for some reason we fail to commit these changes to * the kernel, we want to be able * restore the state of the list. So copy the node data * and if need be, we can copy * back if it fails. */ original_s_route = static_route_alloc(); memcpy(original_s_route, s_route, sizeof(struct static_route)); /* Route exists and has the same input interface, but * adding a new output interface */ if (s_route->iif == iif_index) { s_route->oif_ttls[oif_index] = 1; s_route->c_oil.oil.mfcc_ttls[oif_index] = 1; s_route->c_oil.oif_creation[oif_index] = pim_time_monotonic_sec(); ++s_route->c_oil.oil_ref_count; } else { /* input interface changed */ s_route->iif = iif_index; pim_channel_oil_change_iif(pim, &s_route->c_oil, iif_index, __PRETTY_FUNCTION__); #ifdef PIM_ENFORCE_LOOPFREE_MFC /* check to make sure the new input was not an * old output */ if (s_route->oif_ttls[iif_index]) { s_route->oif_ttls[iif_index] = 0; s_route->c_oil.oif_creation[iif_index] = 0; s_route->c_oil.oil .mfcc_ttls[iif_index] = 0; --s_route->c_oil.oil_ref_count; } #endif /* now add the new output, if it is new */ if (!s_route->oif_ttls[oif_index]) { s_route->oif_ttls[oif_index] = 1; s_route->c_oil.oif_creation[oif_index] = pim_time_monotonic_sec(); s_route->c_oil.oil .mfcc_ttls[oif_index] = 1; ++s_route->c_oil.oil_ref_count; } } break; } } /* If node is null then we reached the end of the list without finding a * match */ if (!node) { s_route = static_route_new(iif_index, oif_index, group, source); listnode_add(pim->static_routes, s_route); } s_route->c_oil.pim = pim; if (pim_mroute_add(&s_route->c_oil, __PRETTY_FUNCTION__)) { char gifaddr_str[INET_ADDRSTRLEN]; char sifaddr_str[INET_ADDRSTRLEN]; pim_inet4_dump("", group, gifaddr_str, sizeof(gifaddr_str)); pim_inet4_dump("", source, sifaddr_str, sizeof(sifaddr_str)); zlog_warn( "%s %s: Unable to add static route(iif=%d,oif=%d,group=%s,source=%s)", __FILE__, __PRETTY_FUNCTION__, iif_index, oif_index, gifaddr_str, sifaddr_str); /* Need to put s_route back to the way it was */ if (original_s_route) { memcpy(s_route, original_s_route, sizeof(struct static_route)); } else { /* we never stored off a copy, so it must have been a * fresh new route */ listnode_delete(pim->static_routes, s_route); pim_static_route_free(s_route); } if (original_s_route) { pim_static_route_free(original_s_route); } return -1; } /* Make sure we free the memory for the route copy if used */ if (original_s_route) { pim_static_route_free(original_s_route); } if (PIM_DEBUG_STATIC) { char gifaddr_str[INET_ADDRSTRLEN]; char sifaddr_str[INET_ADDRSTRLEN]; pim_inet4_dump("", group, gifaddr_str, sizeof(gifaddr_str)); pim_inet4_dump("", source, sifaddr_str, sizeof(sifaddr_str)); zlog_debug( "%s: Static route added(iif=%d,oif=%d,group=%s,source=%s)", __PRETTY_FUNCTION__, iif_index, oif_index, gifaddr_str, sifaddr_str); } return 0; } int pim_static_del(struct pim_instance *pim, struct interface *iif, struct interface *oif, struct in_addr group, struct in_addr source) { struct listnode *node = NULL; struct listnode *nextnode = NULL; struct static_route *s_route = NULL; struct pim_interface *pim_iif = iif ? iif->info : 0; struct pim_interface *pim_oif = oif ? oif->info : 0; ifindex_t iif_index = pim_iif ? pim_iif->mroute_vif_index : 0; ifindex_t oif_index = pim_oif ? pim_oif->mroute_vif_index : 0; if (!iif_index || !oif_index) { zlog_warn( "%s %s: Unable to remove static route: Invalid interface index(iif=%d,oif=%d)", __FILE__, __PRETTY_FUNCTION__, iif_index, oif_index); return -2; } for (ALL_LIST_ELEMENTS(pim->static_routes, node, nextnode, s_route)) { if (s_route->iif == iif_index && s_route->group.s_addr == group.s_addr && s_route->source.s_addr == source.s_addr && s_route->oif_ttls[oif_index]) { s_route->oif_ttls[oif_index] = 0; s_route->c_oil.oil.mfcc_ttls[oif_index] = 0; --s_route->c_oil.oil_ref_count; /* If there are no more outputs then delete the whole * route, otherwise set the route with the new outputs */ if (s_route->c_oil.oil_ref_count <= 0 ? pim_mroute_del(&s_route->c_oil, __PRETTY_FUNCTION__) : pim_mroute_add(&s_route->c_oil, __PRETTY_FUNCTION__)) { char gifaddr_str[INET_ADDRSTRLEN]; char sifaddr_str[INET_ADDRSTRLEN]; pim_inet4_dump("", group, gifaddr_str, sizeof(gifaddr_str)); pim_inet4_dump("", source, sifaddr_str, sizeof(sifaddr_str)); zlog_warn( "%s %s: Unable to remove static route(iif=%d,oif=%d,group=%s,source=%s)", __FILE__, __PRETTY_FUNCTION__, iif_index, oif_index, gifaddr_str, sifaddr_str); s_route->oif_ttls[oif_index] = 1; s_route->c_oil.oil.mfcc_ttls[oif_index] = 1; ++s_route->c_oil.oil_ref_count; return -1; } s_route->c_oil.oif_creation[oif_index] = 0; if (s_route->c_oil.oil_ref_count <= 0) { listnode_delete(pim->static_routes, s_route); pim_static_route_free(s_route); } if (PIM_DEBUG_STATIC) { char gifaddr_str[INET_ADDRSTRLEN]; char sifaddr_str[INET_ADDRSTRLEN]; pim_inet4_dump("", group, gifaddr_str, sizeof(gifaddr_str)); pim_inet4_dump("", source, sifaddr_str, sizeof(sifaddr_str)); zlog_debug( "%s: Static route removed(iif=%d,oif=%d,group=%s,source=%s)", __PRETTY_FUNCTION__, iif_index, oif_index, gifaddr_str, sifaddr_str); } break; } } if (!node) { char gifaddr_str[INET_ADDRSTRLEN]; char sifaddr_str[INET_ADDRSTRLEN]; pim_inet4_dump("", group, gifaddr_str, sizeof(gifaddr_str)); pim_inet4_dump("", source, sifaddr_str, sizeof(sifaddr_str)); zlog_warn( "%s %s: Unable to remove static route: Route does not exist(iif=%d,oif=%d,group=%s,source=%s)", __FILE__, __PRETTY_FUNCTION__, iif_index, oif_index, gifaddr_str, sifaddr_str); return -3; } return 0; } int pim_static_write_mroute(struct pim_instance *pim, struct vty *vty, struct interface *ifp) { struct pim_interface *pim_ifp = ifp->info; struct listnode *node; struct static_route *sroute; int count = 0; char sbuf[INET_ADDRSTRLEN]; char gbuf[INET_ADDRSTRLEN]; if (!pim_ifp) return 0; for (ALL_LIST_ELEMENTS_RO(pim->static_routes, node, sroute)) { pim_inet4_dump("", sroute->group, gbuf, sizeof(gbuf)); pim_inet4_dump("", sroute->source, sbuf, sizeof(sbuf)); if (sroute->iif == pim_ifp->mroute_vif_index) { int i; for (i = 0; i < MAXVIFS; i++) if (sroute->oif_ttls[i]) { struct interface *oifp = pim_if_find_by_vif_index(pim, i); if (sroute->source.s_addr == 0) vty_out(vty, " ip mroute %s %s\n", oifp->name, gbuf); else vty_out(vty, " ip mroute %s %s %s\n", oifp->name, gbuf, sbuf); count++; } } } return count; } frr-7.2.1/pimd/pim_static.h0000644000000000000000000000317513610377563012460 00000000000000/* * PIM for Quagga: add the ability to configure multicast static routes * Copyright (C) 2014 Nathan Bahr, ATCorp * * This program is free software; you can redistribute it and/or modify * it under the terms of the 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; see the file COPYING; if not, write to the Free Software * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA */ #ifndef PIM_STATIC_H_ #define PIM_STATIC_H_ #include #include "pim_mroute.h" #include "if.h" struct static_route { /* Each static route is unique by these pair of addresses */ struct in_addr group; struct in_addr source; struct channel_oil c_oil; ifindex_t iif; unsigned char oif_ttls[MAXVIFS]; }; void pim_static_route_free(struct static_route *s_route); int pim_static_add(struct pim_instance *pim, struct interface *iif, struct interface *oif, struct in_addr group, struct in_addr source); int pim_static_del(struct pim_instance *pim, struct interface *iif, struct interface *oif, struct in_addr group, struct in_addr source); int pim_static_write_mroute(struct pim_instance *pim, struct vty *vty, struct interface *ifp); #endif /* PIM_STATIC_H_ */ frr-7.2.1/pimd/pim_str.c0000644000000000000000000000263313610377563011772 00000000000000/* * PIM for Quagga * Copyright (C) 2008 Everton da Silva Marques * * This program is free software; you can redistribute it and/or modify * it under the terms of the 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; see the file COPYING; if not, write to the Free Software * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA */ #include #include #include #include #include "log.h" #include "pim_str.h" void pim_addr_dump(const char *onfail, struct prefix *p, char *buf, int buf_size) { int save_errno = errno; if (!inet_ntop(p->family, &p->u.prefix, buf, buf_size)) { zlog_warn("pim_addr_dump: inet_ntop(buf_size=%d): errno=%d: %s", buf_size, errno, safe_strerror(errno)); if (onfail) snprintf(buf, buf_size, "%s", onfail); } errno = save_errno; } char *pim_str_sg_dump(const struct prefix_sg *sg) { static char sg_str[PIM_SG_LEN]; pim_str_sg_set(sg, sg_str); return sg_str; } frr-7.2.1/pimd/pim_str.h0000644000000000000000000000267713610377563012007 00000000000000/* * PIM for Quagga * Copyright (C) 2008 Everton da Silva Marques * * This program is free software; you can redistribute it and/or modify * it under the terms of the 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; see the file COPYING; if not, write to the Free Software * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA */ #ifndef PIM_STR_H #define PIM_STR_H #include #include #include #include /* * Longest possible length of a (S,G) string is 36 bytes * 123.123.123.123 = 16 * 2 * (,) = 3 * NULL Character at end = 1 * (123.123.123.123,123,123,123,123) */ #define PIM_SG_LEN PREFIX_SG_STR_LEN #define pim_inet4_dump prefix_mcast_inet4_dump #define pim_str_sg_set prefix_sg2str void pim_addr_dump(const char *onfail, struct prefix *p, char *buf, int buf_size); void pim_inet4_dump(const char *onfail, struct in_addr addr, char *buf, int buf_size); char *pim_str_sg_dump(const struct prefix_sg *sg); #endif frr-7.2.1/pimd/pim_time.c0000644000000000000000000000767613610377563012134 00000000000000/* * PIM for Quagga * Copyright (C) 2008 Everton da Silva Marques * * This program is free software; you can redistribute it and/or modify * it under the terms of the 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; see the file COPYING; if not, write to the Free Software * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA */ #include #include #include #include #include "log.h" #include "thread.h" #include "lib_errors.h" #include "pim_time.h" static int gettime_monotonic(struct timeval *tv) { int result; result = gettimeofday(tv, 0); if (result) { flog_err_sys(EC_LIB_SYSTEM_CALL, "%s: gettimeofday() failure: errno=%d: %s", __PRETTY_FUNCTION__, errno, safe_strerror(errno)); } return result; } /* pim_time_monotonic_sec(): number of seconds since some unspecified starting point */ int64_t pim_time_monotonic_sec(void) { struct timeval now_tv; if (gettime_monotonic(&now_tv)) { flog_err_sys(EC_LIB_SYSTEM_CALL, "%s: gettime_monotonic() failure: errno=%d: %s", __PRETTY_FUNCTION__, errno, safe_strerror(errno)); return -1; } return now_tv.tv_sec; } /* pim_time_monotonic_dsec(): number of deciseconds since some unspecified starting point */ int64_t pim_time_monotonic_dsec(void) { struct timeval now_tv; int64_t now_dsec; if (gettime_monotonic(&now_tv)) { flog_err_sys(EC_LIB_SYSTEM_CALL, "%s: gettime_monotonic() failure: errno=%d: %s", __PRETTY_FUNCTION__, errno, safe_strerror(errno)); return -1; } now_dsec = ((int64_t)now_tv.tv_sec) * 10 + ((int64_t)now_tv.tv_usec) / 100000; return now_dsec; } int64_t pim_time_monotonic_usec(void) { struct timeval now_tv; int64_t now_dsec; if (gettime_monotonic(&now_tv)) { flog_err_sys(EC_LIB_SYSTEM_CALL, "%s: gettime_monotonic() failure: errno=%d: %s", __PRETTY_FUNCTION__, errno, safe_strerror(errno)); return -1; } now_dsec = ((int64_t)now_tv.tv_sec) * 1000000 + ((int64_t)now_tv.tv_usec); return now_dsec; } int pim_time_mmss(char *buf, int buf_size, long sec) { long mm; int wr; zassert(buf_size >= 5); mm = sec / 60; sec %= 60; wr = snprintf(buf, buf_size, "%02ld:%02ld", mm, sec); return wr != 8; } static int pim_time_hhmmss(char *buf, int buf_size, long sec) { long hh; long mm; int wr; zassert(buf_size >= 8); hh = sec / 3600; sec %= 3600; mm = sec / 60; sec %= 60; wr = snprintf(buf, buf_size, "%02ld:%02ld:%02ld", hh, mm, sec); return wr != 8; } void pim_time_timer_to_mmss(char *buf, int buf_size, struct thread *t_timer) { if (t_timer) { pim_time_mmss(buf, buf_size, thread_timer_remain_second(t_timer)); } else { snprintf(buf, buf_size, "--:--"); } } void pim_time_timer_to_hhmmss(char *buf, int buf_size, struct thread *t_timer) { if (t_timer) { pim_time_hhmmss(buf, buf_size, thread_timer_remain_second(t_timer)); } else { snprintf(buf, buf_size, "--:--:--"); } } void pim_time_uptime(char *buf, int buf_size, int64_t uptime_sec) { zassert(buf_size >= 8); pim_time_hhmmss(buf, buf_size, uptime_sec); } void pim_time_uptime_begin(char *buf, int buf_size, int64_t now, int64_t begin) { if (begin > 0) pim_time_uptime(buf, buf_size, now - begin); else snprintf(buf, buf_size, "--:--:--"); } long pim_time_timer_remain_msec(struct thread *t_timer) { /* FIXME: Actually fetch msec resolution from thread */ /* no timer thread running means timer has expired: return 0 */ return t_timer ? 1000 * thread_timer_remain_second(t_timer) : 0; } frr-7.2.1/pimd/pim_time.h0000644000000000000000000000266113610377563012126 00000000000000/* * PIM for Quagga * Copyright (C) 2008 Everton da Silva Marques * * This program is free software; you can redistribute it and/or modify * it under the terms of the 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; see the file COPYING; if not, write to the Free Software * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA */ #ifndef PIM_TIME_H #define PIM_TIME_H #include #include #include "thread.h" int64_t pim_time_monotonic_sec(void); int64_t pim_time_monotonic_dsec(void); int64_t pim_time_monotonic_usec(void); int pim_time_mmss(char *buf, int buf_size, long sec); void pim_time_timer_to_mmss(char *buf, int buf_size, struct thread *t); void pim_time_timer_to_hhmmss(char *buf, int buf_size, struct thread *t); void pim_time_uptime(char *buf, int buf_size, int64_t uptime_sec); void pim_time_uptime_begin(char *buf, int buf_size, int64_t now, int64_t begin); long pim_time_timer_remain_msec(struct thread *t_timer); #endif /* PIM_TIME_H */ frr-7.2.1/pimd/pim_tlv.c0000644000000000000000000005124513610377563011772 00000000000000/* * PIM for Quagga * Copyright (C) 2008 Everton da Silva Marques * * This program is free software; you can redistribute it and/or modify * it under the terms of the 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; see the file COPYING; if not, write to the Free Software * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA */ #include #include "log.h" #include "prefix.h" #include "if.h" #include "pimd.h" #include "pim_int.h" #include "pim_tlv.h" #include "pim_str.h" #include "pim_msg.h" uint8_t *pim_tlv_append_uint16(uint8_t *buf, const uint8_t *buf_pastend, uint16_t option_type, uint16_t option_value) { uint16_t option_len = 2; if ((buf + PIM_TLV_OPTION_SIZE(option_len)) > buf_pastend) return NULL; *(uint16_t *)buf = htons(option_type); buf += 2; *(uint16_t *)buf = htons(option_len); buf += 2; *(uint16_t *)buf = htons(option_value); buf += option_len; return buf; } uint8_t *pim_tlv_append_2uint16(uint8_t *buf, const uint8_t *buf_pastend, uint16_t option_type, uint16_t option_value1, uint16_t option_value2) { uint16_t option_len = 4; if ((buf + PIM_TLV_OPTION_SIZE(option_len)) > buf_pastend) return NULL; *(uint16_t *)buf = htons(option_type); buf += 2; *(uint16_t *)buf = htons(option_len); buf += 2; *(uint16_t *)buf = htons(option_value1); buf += 2; *(uint16_t *)buf = htons(option_value2); buf += 2; return buf; } uint8_t *pim_tlv_append_uint32(uint8_t *buf, const uint8_t *buf_pastend, uint16_t option_type, uint32_t option_value) { uint16_t option_len = 4; if ((buf + PIM_TLV_OPTION_SIZE(option_len)) > buf_pastend) return NULL; *(uint16_t *)buf = htons(option_type); buf += 2; *(uint16_t *)buf = htons(option_len); buf += 2; pim_write_uint32(buf, option_value); buf += option_len; return buf; } #define ucast_ipv4_encoding_len (2 + sizeof(struct in_addr)) #define ucast_ipv6_encoding_len (2 + sizeof(struct in6_addr)) /* * An Encoded-Unicast address takes the following format: * * 0 1 2 3 * 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 * +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ * | Addr Family | Encoding Type | Unicast Address * +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+... * * Addr Family * The PIM address family of the 'Unicast Address' field of this * address. * * Values 0-127 are as assigned by the IANA for Internet Address * * Families in [7]. Values 128-250 are reserved to be assigned by * the IANA for PIM-specific Address Families. Values 251 though * 255 are designated for private use. As there is no assignment * authority for this space, collisions should be expected. * * Encoding Type * The type of encoding used within a specific Address Family. The * value '0' is reserved for this field and represents the native * encoding of the Address Family. * * Unicast Address * The unicast address as represented by the given Address Family * and Encoding Type. */ int pim_encode_addr_ucast(uint8_t *buf, struct prefix *p) { switch (p->family) { case AF_INET: *(uint8_t *)buf = PIM_MSG_ADDRESS_FAMILY_IPV4; /* notice: AF_INET != PIM_MSG_ADDRESS_FAMILY_IPV4 */ ++buf; *(uint8_t *)buf = 0; /* ucast IPv4 native encoding type (RFC 4601: 4.9.1) */ ++buf; memcpy(buf, &p->u.prefix4, sizeof(struct in_addr)); return ucast_ipv4_encoding_len; break; case AF_INET6: *(uint8_t *)buf = PIM_MSG_ADDRESS_FAMILY_IPV6; ++buf; *(uint8_t *)buf = 0; ++buf; memcpy(buf, &p->u.prefix6, sizeof(struct in6_addr)); return ucast_ipv6_encoding_len; break; default: return 0; break; } } #define group_ipv4_encoding_len (4 + sizeof (struct in_addr)) /* * Encoded-Group addresses take the following format: * * 0 1 2 3 * 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 * +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ * | Addr Family | Encoding Type |B| Reserved |Z| Mask Len | * +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ * | Group multicast Address * +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+... * * Addr Family * Described above. * * Encoding Type * Described above. * * [B]idirectional PIM * Indicates the group range should use Bidirectional PIM [13]. * For PIM-SM defined in this specification, this bit MUST be zero. * * Reserved * Transmitted as zero. Ignored upon receipt. * * Admin Scope [Z]one * indicates the group range is an admin scope zone. This is used * in the Bootstrap Router Mechanism [11] only. For all other * purposes, this bit is set to zero and ignored on receipt. * * Mask Len * The Mask length field is 8 bits. The value is the number of * contiguous one bits that are left justified and used as a mask; * when combined with the group address, it describes a range of * groups. It is less than or equal to the address length in bits * for the given Address Family and Encoding Type. If the message * is sent for a single group, then the Mask length must equal the * address length in bits for the given Address Family and Encoding * Type (e.g., 32 for IPv4 native encoding, 128 for IPv6 native * encoding). * * Group multicast Address * Contains the group address. */ int pim_encode_addr_group(uint8_t *buf, afi_t afi, int bidir, int scope, struct in_addr group) { uint8_t flags = 0; flags |= bidir << 8; flags |= scope; switch (afi) { case AFI_IP: *(uint8_t *)buf = PIM_MSG_ADDRESS_FAMILY_IPV4; ++buf; *(uint8_t *)buf = 0; ++buf; *(uint8_t *)buf = flags; ++buf; *(uint8_t *)buf = 32; ++buf; memcpy(buf, &group, sizeof(struct in_addr)); return group_ipv4_encoding_len; break; default: return 0; break; } } uint8_t *pim_tlv_append_addrlist_ucast(uint8_t *buf, const uint8_t *buf_pastend, struct list *ifconnected, int family) { struct listnode *node; uint16_t option_len = 0; uint8_t *curr; size_t uel; node = listhead(ifconnected); /* Empty address list ? */ if (!node) { return buf; } if (family == AF_INET) uel = ucast_ipv4_encoding_len; else uel = ucast_ipv6_encoding_len; /* Scan secondary address list */ curr = buf + 4; /* skip T and L */ for (; node; node = listnextnode(node)) { struct connected *ifc = listgetdata(node); struct prefix *p = ifc->address; int l_encode; if (!CHECK_FLAG(ifc->flags, ZEBRA_IFA_SECONDARY)) continue; if ((curr + uel) > buf_pastend) return 0; if (p->family != family) continue; l_encode = pim_encode_addr_ucast(curr, p); curr += l_encode; option_len += l_encode; } if (PIM_DEBUG_PIM_TRACE_DETAIL) { zlog_debug( "%s: number of encoded secondary unicast IPv4 addresses: %zu", __PRETTY_FUNCTION__, option_len / uel); } if (option_len < 1) { /* Empty secondary unicast IPv4 address list */ return buf; } /* * Write T and L */ *(uint16_t *)buf = htons(PIM_MSG_OPTION_TYPE_ADDRESS_LIST); *(uint16_t *)(buf + 2) = htons(option_len); return curr; } static int check_tlv_length(const char *label, const char *tlv_name, const char *ifname, struct in_addr src_addr, int correct_len, int option_len) { if (option_len != correct_len) { char src_str[INET_ADDRSTRLEN]; pim_inet4_dump("", src_addr, src_str, sizeof(src_str)); zlog_warn( "%s: PIM hello %s TLV with incorrect value size=%d correct=%d from %s on interface %s", label, tlv_name, option_len, correct_len, src_str, ifname); return -1; } return 0; } static void check_tlv_redefinition_uint16( const char *label, const char *tlv_name, const char *ifname, struct in_addr src_addr, pim_hello_options options, pim_hello_options opt_mask, uint16_t new, uint16_t old) { if (PIM_OPTION_IS_SET(options, opt_mask)) { char src_str[INET_ADDRSTRLEN]; pim_inet4_dump("", src_addr, src_str, sizeof(src_str)); zlog_warn( "%s: PIM hello TLV redefined %s=%u old=%u from %s on interface %s", label, tlv_name, new, old, src_str, ifname); } } static void check_tlv_redefinition_uint32( const char *label, const char *tlv_name, const char *ifname, struct in_addr src_addr, pim_hello_options options, pim_hello_options opt_mask, uint32_t new, uint32_t old) { if (PIM_OPTION_IS_SET(options, opt_mask)) { char src_str[INET_ADDRSTRLEN]; pim_inet4_dump("", src_addr, src_str, sizeof(src_str)); zlog_warn( "%s: PIM hello TLV redefined %s=%u old=%u from %s on interface %s", label, tlv_name, new, old, src_str, ifname); } } static void check_tlv_redefinition_uint32_hex( const char *label, const char *tlv_name, const char *ifname, struct in_addr src_addr, pim_hello_options options, pim_hello_options opt_mask, uint32_t new, uint32_t old) { if (PIM_OPTION_IS_SET(options, opt_mask)) { char src_str[INET_ADDRSTRLEN]; pim_inet4_dump("", src_addr, src_str, sizeof(src_str)); zlog_warn( "%s: PIM hello TLV redefined %s=%08x old=%08x from %s on interface %s", label, tlv_name, new, old, src_str, ifname); } } int pim_tlv_parse_holdtime(const char *ifname, struct in_addr src_addr, pim_hello_options *hello_options, uint16_t *hello_option_holdtime, uint16_t option_len, const uint8_t *tlv_curr) { const char *label = "holdtime"; if (check_tlv_length(__PRETTY_FUNCTION__, label, ifname, src_addr, sizeof(uint16_t), option_len)) { return -1; } check_tlv_redefinition_uint16( __PRETTY_FUNCTION__, label, ifname, src_addr, *hello_options, PIM_OPTION_MASK_HOLDTIME, PIM_TLV_GET_HOLDTIME(tlv_curr), *hello_option_holdtime); PIM_OPTION_SET(*hello_options, PIM_OPTION_MASK_HOLDTIME); *hello_option_holdtime = PIM_TLV_GET_HOLDTIME(tlv_curr); return 0; } int pim_tlv_parse_lan_prune_delay(const char *ifname, struct in_addr src_addr, pim_hello_options *hello_options, uint16_t *hello_option_propagation_delay, uint16_t *hello_option_override_interval, uint16_t option_len, const uint8_t *tlv_curr) { if (check_tlv_length(__PRETTY_FUNCTION__, "lan_prune_delay", ifname, src_addr, sizeof(uint32_t), option_len)) { return -1; } check_tlv_redefinition_uint16(__PRETTY_FUNCTION__, "propagation_delay", ifname, src_addr, *hello_options, PIM_OPTION_MASK_LAN_PRUNE_DELAY, PIM_TLV_GET_PROPAGATION_DELAY(tlv_curr), *hello_option_propagation_delay); PIM_OPTION_SET(*hello_options, PIM_OPTION_MASK_LAN_PRUNE_DELAY); *hello_option_propagation_delay = PIM_TLV_GET_PROPAGATION_DELAY(tlv_curr); if (PIM_TLV_GET_CAN_DISABLE_JOIN_SUPPRESSION(tlv_curr)) { PIM_OPTION_SET(*hello_options, PIM_OPTION_MASK_CAN_DISABLE_JOIN_SUPPRESSION); } else { PIM_OPTION_UNSET(*hello_options, PIM_OPTION_MASK_CAN_DISABLE_JOIN_SUPPRESSION); } ++tlv_curr; ++tlv_curr; *hello_option_override_interval = PIM_TLV_GET_OVERRIDE_INTERVAL(tlv_curr); return 0; } int pim_tlv_parse_dr_priority(const char *ifname, struct in_addr src_addr, pim_hello_options *hello_options, uint32_t *hello_option_dr_priority, uint16_t option_len, const uint8_t *tlv_curr) { const char *label = "dr_priority"; if (check_tlv_length(__PRETTY_FUNCTION__, label, ifname, src_addr, sizeof(uint32_t), option_len)) { return -1; } check_tlv_redefinition_uint32( __PRETTY_FUNCTION__, label, ifname, src_addr, *hello_options, PIM_OPTION_MASK_DR_PRIORITY, PIM_TLV_GET_DR_PRIORITY(tlv_curr), *hello_option_dr_priority); PIM_OPTION_SET(*hello_options, PIM_OPTION_MASK_DR_PRIORITY); *hello_option_dr_priority = PIM_TLV_GET_DR_PRIORITY(tlv_curr); return 0; } int pim_tlv_parse_generation_id(const char *ifname, struct in_addr src_addr, pim_hello_options *hello_options, uint32_t *hello_option_generation_id, uint16_t option_len, const uint8_t *tlv_curr) { const char *label = "generation_id"; if (check_tlv_length(__PRETTY_FUNCTION__, label, ifname, src_addr, sizeof(uint32_t), option_len)) { return -1; } check_tlv_redefinition_uint32_hex(__PRETTY_FUNCTION__, label, ifname, src_addr, *hello_options, PIM_OPTION_MASK_GENERATION_ID, PIM_TLV_GET_GENERATION_ID(tlv_curr), *hello_option_generation_id); PIM_OPTION_SET(*hello_options, PIM_OPTION_MASK_GENERATION_ID); *hello_option_generation_id = PIM_TLV_GET_GENERATION_ID(tlv_curr); return 0; } int pim_parse_addr_ucast(struct prefix *p, const uint8_t *buf, int buf_size) { const int ucast_encoding_min_len = 3; /* 1 family + 1 type + 1 addr */ const uint8_t *addr; const uint8_t *pastend; int family; int type; if (buf_size < ucast_encoding_min_len) { zlog_warn( "%s: unicast address encoding overflow: left=%d needed=%d", __PRETTY_FUNCTION__, buf_size, ucast_encoding_min_len); return -1; } addr = buf; pastend = buf + buf_size; family = *addr++; type = *addr++; if (type) { zlog_warn("%s: unknown unicast address encoding type=%d", __PRETTY_FUNCTION__, type); return -2; } switch (family) { case PIM_MSG_ADDRESS_FAMILY_IPV4: if ((addr + sizeof(struct in_addr)) > pastend) { zlog_warn( "%s: IPv4 unicast address overflow: left=%zd needed=%zu", __PRETTY_FUNCTION__, pastend - addr, sizeof(struct in_addr)); return -3; } p->family = AF_INET; /* notice: AF_INET != PIM_MSG_ADDRESS_FAMILY_IPV4 */ memcpy(&p->u.prefix4, addr, sizeof(struct in_addr)); p->prefixlen = IPV4_MAX_PREFIXLEN; addr += sizeof(struct in_addr); break; case PIM_MSG_ADDRESS_FAMILY_IPV6: if ((addr + sizeof(struct in6_addr)) > pastend) { zlog_warn( "%s: IPv6 unicast address overflow: left=%zd needed %zu", __PRETTY_FUNCTION__, pastend - addr, sizeof(struct in6_addr)); return -3; } p->family = AF_INET6; p->prefixlen = IPV6_MAX_PREFIXLEN; memcpy(&p->u.prefix6, addr, sizeof(struct in6_addr)); addr += sizeof(struct in6_addr); break; default: { zlog_warn("%s: unknown unicast address encoding family=%d from", __PRETTY_FUNCTION__, family); return -4; } } return addr - buf; } int pim_parse_addr_group(struct prefix_sg *sg, const uint8_t *buf, int buf_size) { const int grp_encoding_min_len = 4; /* 1 family + 1 type + 1 reserved + 1 addr */ const uint8_t *addr; const uint8_t *pastend; int family; int type; int mask_len; if (buf_size < grp_encoding_min_len) { zlog_warn( "%s: group address encoding overflow: left=%d needed=%d", __PRETTY_FUNCTION__, buf_size, grp_encoding_min_len); return -1; } addr = buf; pastend = buf + buf_size; family = *addr++; type = *addr++; //++addr; ++addr; /* skip b_reserved_z fields */ mask_len = *addr++; switch (family) { case PIM_MSG_ADDRESS_FAMILY_IPV4: if (type) { zlog_warn( "%s: unknown group address encoding type=%d from", __PRETTY_FUNCTION__, type); return -2; } if ((addr + sizeof(struct in_addr)) > pastend) { zlog_warn( "%s: IPv4 group address overflow: left=%zd needed=%zu from", __PRETTY_FUNCTION__, pastend - addr, sizeof(struct in_addr)); return -3; } memcpy(&sg->grp.s_addr, addr, sizeof(struct in_addr)); addr += sizeof(struct in_addr); break; default: { zlog_warn( "%s: unknown group address encoding family=%d mask_len=%d from", __PRETTY_FUNCTION__, family, mask_len); return -4; } } return addr - buf; } int pim_parse_addr_source(struct prefix_sg *sg, uint8_t *flags, const uint8_t *buf, int buf_size) { const int src_encoding_min_len = 4; /* 1 family + 1 type + 1 reserved + 1 addr */ const uint8_t *addr; const uint8_t *pastend; int family; int type; int mask_len; if (buf_size < src_encoding_min_len) { zlog_warn( "%s: source address encoding overflow: left=%d needed=%d", __PRETTY_FUNCTION__, buf_size, src_encoding_min_len); return -1; } addr = buf; pastend = buf + buf_size; family = *addr++; type = *addr++; *flags = *addr++; mask_len = *addr++; if (type) { zlog_warn( "%s: unknown source address encoding type=%d: %02x%02x%02x%02x", __PRETTY_FUNCTION__, type, buf[0], buf[1], buf[2], buf[3]); return -2; } switch (family) { case PIM_MSG_ADDRESS_FAMILY_IPV4: if ((addr + sizeof(struct in_addr)) > pastend) { zlog_warn( "%s: IPv4 source address overflow: left=%zd needed=%zu", __PRETTY_FUNCTION__, pastend - addr, sizeof(struct in_addr)); return -3; } memcpy(&sg->src, addr, sizeof(struct in_addr)); /* RFC 4601: 4.9.1 Encoded Source and Group Address Formats Encoded-Source Address The mask length MUST be equal to the mask length in bits for the given Address Family and Encoding Type (32 for IPv4 native and 128 for IPv6 native). A router SHOULD ignore any messages received with any other mask length. */ if (mask_len != 32) { zlog_warn("%s: IPv4 bad source address mask: %d", __PRETTY_FUNCTION__, mask_len); return -4; } addr += sizeof(struct in_addr); break; default: { zlog_warn( "%s: unknown source address encoding family=%d: %02x%02x%02x%02x", __PRETTY_FUNCTION__, family, buf[0], buf[1], buf[2], buf[3]); return -5; } } return addr - buf; } #define FREE_ADDR_LIST(hello_option_addr_list) \ { \ if (hello_option_addr_list) { \ list_delete(&hello_option_addr_list); \ hello_option_addr_list = 0; \ } \ } int pim_tlv_parse_addr_list(const char *ifname, struct in_addr src_addr, pim_hello_options *hello_options, struct list **hello_option_addr_list, uint16_t option_len, const uint8_t *tlv_curr) { const uint8_t *addr; const uint8_t *pastend; zassert(hello_option_addr_list); /* Scan addr list */ addr = tlv_curr; pastend = tlv_curr + option_len; while (addr < pastend) { struct prefix tmp; int addr_offset; /* Parse ucast addr */ addr_offset = pim_parse_addr_ucast(&tmp, addr, pastend - addr); if (addr_offset < 1) { char src_str[INET_ADDRSTRLEN]; pim_inet4_dump("", src_addr, src_str, sizeof(src_str)); zlog_warn( "%s: pim_parse_addr_ucast() failure: from %s on %s", __PRETTY_FUNCTION__, src_str, ifname); FREE_ADDR_LIST(*hello_option_addr_list); return -1; } addr += addr_offset; /* Debug */ if (PIM_DEBUG_PIM_TRACE) { switch (tmp.family) { case AF_INET: { char addr_str[INET_ADDRSTRLEN]; char src_str[INET_ADDRSTRLEN]; pim_inet4_dump("", tmp.u.prefix4, addr_str, sizeof(addr_str)); pim_inet4_dump("", src_addr, src_str, sizeof(src_str)); zlog_debug( "%s: PIM hello TLV option: list_old_size=%d IPv4 address %s from %s on %s", __PRETTY_FUNCTION__, *hello_option_addr_list ? ((int)listcount( *hello_option_addr_list)) : -1, addr_str, src_str, ifname); } break; case AF_INET6: break; default: { char src_str[INET_ADDRSTRLEN]; pim_inet4_dump("", src_addr, src_str, sizeof(src_str)); zlog_debug( "%s: PIM hello TLV option: list_old_size=%d UNKNOWN address family from %s on %s", __PRETTY_FUNCTION__, *hello_option_addr_list ? ((int)listcount( *hello_option_addr_list)) : -1, src_str, ifname); } } } /* Exclude neighbor's primary address if incorrectly included in the secondary address list */ if (tmp.family == AF_INET) { if (tmp.u.prefix4.s_addr == src_addr.s_addr) { char src_str[INET_ADDRSTRLEN]; pim_inet4_dump("", src_addr, src_str, sizeof(src_str)); zlog_warn( "%s: ignoring primary address in secondary list from %s on %s", __PRETTY_FUNCTION__, src_str, ifname); continue; } } /* Allocate list if needed */ if (!*hello_option_addr_list) { *hello_option_addr_list = list_new(); (*hello_option_addr_list)->del = (void (*)(void *))prefix_free; } /* Attach addr to list */ { struct prefix *p; p = prefix_new(); prefix_copy(p, &tmp); listnode_add(*hello_option_addr_list, p); } } /* while (addr < pastend) */ /* Mark hello option */ PIM_OPTION_SET(*hello_options, PIM_OPTION_MASK_ADDRESS_LIST); return 0; } frr-7.2.1/pimd/pim_tlv.h0000644000000000000000000001201213610377563011764 00000000000000/* * PIM for Quagga * Copyright (C) 2008 Everton da Silva Marques * * This program is free software; you can redistribute it and/or modify * it under the terms of the 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; see the file COPYING; if not, write to the Free Software * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA */ #ifndef PIM_TLV_H #define PIM_TLV_H #include #include "config.h" #include "if.h" #include "linklist.h" #define PIM_MSG_OPTION_TYPE_HOLDTIME (1) #define PIM_MSG_OPTION_TYPE_LAN_PRUNE_DELAY (2) #define PIM_MSG_OPTION_TYPE_DR_PRIORITY (19) #define PIM_MSG_OPTION_TYPE_GENERATION_ID (20) #define PIM_MSG_OPTION_TYPE_DM_STATE_REFRESH (21) #define PIM_MSG_OPTION_TYPE_ADDRESS_LIST (24) typedef uint32_t pim_hello_options; #define PIM_OPTION_MASK_HOLDTIME (1 << 0) /* recv holdtime */ #define PIM_OPTION_MASK_LAN_PRUNE_DELAY (1 << 1) /* recv lan_prune_delay */ #define PIM_OPTION_MASK_DR_PRIORITY (1 << 2) /* recv dr_priority */ #define PIM_OPTION_MASK_GENERATION_ID (1 << 3) /* recv generation_id */ #define PIM_OPTION_MASK_ADDRESS_LIST (1 << 4) /* recv secondary address list */ #define PIM_OPTION_MASK_CAN_DISABLE_JOIN_SUPPRESSION (1 << 5) /* T bit value (valid if recv lan_prune_delay) */ #define PIM_RPT_BIT_MASK (1 << 0) #define PIM_WILDCARD_BIT_MASK (1 << 1) #define PIM_OPTION_SET(options, option_mask) ((options) |= (option_mask)) #define PIM_OPTION_UNSET(options, option_mask) ((options) &= ~(option_mask)) #define PIM_OPTION_IS_SET(options, option_mask) ((options) & (option_mask)) #define PIM_TLV_GET_UINT16(buf) ntohs(*(const uint16_t *)(buf)) #define PIM_TLV_GET_UINT32(buf) ntohl(*(const uint32_t *)(buf)) #define PIM_TLV_GET_TYPE(buf) PIM_TLV_GET_UINT16(buf) #define PIM_TLV_GET_LENGTH(buf) PIM_TLV_GET_UINT16(buf) #define PIM_TLV_GET_HOLDTIME(buf) PIM_TLV_GET_UINT16(buf) #define PIM_TLV_GET_PROPAGATION_DELAY(buf) (PIM_TLV_GET_UINT16(buf) & 0x7FFF) #define PIM_TLV_GET_OVERRIDE_INTERVAL(buf) PIM_TLV_GET_UINT16(buf) #define PIM_TLV_GET_CAN_DISABLE_JOIN_SUPPRESSION(buf) ((*(const uint8_t *)(buf)) & 0x80) #define PIM_TLV_GET_DR_PRIORITY(buf) PIM_TLV_GET_UINT32(buf) #define PIM_TLV_GET_GENERATION_ID(buf) PIM_TLV_GET_UINT32(buf) #define PIM_TLV_TYPE_SIZE (2) #define PIM_TLV_LENGTH_SIZE (2) #define PIM_TLV_MIN_SIZE (PIM_TLV_TYPE_SIZE + PIM_TLV_LENGTH_SIZE) #define PIM_TLV_OPTION_SIZE(option_len) (PIM_TLV_MIN_SIZE + (option_len)) uint8_t *pim_tlv_append_uint16(uint8_t *buf, const uint8_t *buf_pastend, uint16_t option_type, uint16_t option_value); uint8_t *pim_tlv_append_2uint16(uint8_t *buf, const uint8_t *buf_pastend, uint16_t option_type, uint16_t option_value1, uint16_t option_value2); uint8_t *pim_tlv_append_uint32(uint8_t *buf, const uint8_t *buf_pastend, uint16_t option_type, uint32_t option_value); uint8_t *pim_tlv_append_addrlist_ucast(uint8_t *buf, const uint8_t *buf_pastend, struct list *ifconnected, int family); int pim_tlv_parse_holdtime(const char *ifname, struct in_addr src_addr, pim_hello_options *hello_options, uint16_t *hello_option_holdtime, uint16_t option_len, const uint8_t *tlv_curr); int pim_tlv_parse_lan_prune_delay(const char *ifname, struct in_addr src_addr, pim_hello_options *hello_options, uint16_t *hello_option_propagation_delay, uint16_t *hello_option_override_interval, uint16_t option_len, const uint8_t *tlv_curr); int pim_tlv_parse_dr_priority(const char *ifname, struct in_addr src_addr, pim_hello_options *hello_options, uint32_t *hello_option_dr_priority, uint16_t option_len, const uint8_t *tlv_curr); int pim_tlv_parse_generation_id(const char *ifname, struct in_addr src_addr, pim_hello_options *hello_options, uint32_t *hello_option_generation_id, uint16_t option_len, const uint8_t *tlv_curr); int pim_tlv_parse_addr_list(const char *ifname, struct in_addr src_addr, pim_hello_options *hello_options, struct list **hello_option_addr_list, uint16_t option_len, const uint8_t *tlv_curr); int pim_encode_addr_ucast(uint8_t *buf, struct prefix *p); int pim_encode_addr_group(uint8_t *buf, afi_t afi, int bidir, int scope, struct in_addr group); int pim_parse_addr_ucast(struct prefix *p, const uint8_t *buf, int buf_size); int pim_parse_addr_group(struct prefix_sg *sg, const uint8_t *buf, int buf_size); int pim_parse_addr_source(struct prefix_sg *sg, uint8_t *flags, const uint8_t *buf, int buf_size); #endif /* PIM_TLV_H */ frr-7.2.1/pimd/pim_upstream.c0000644000000000000000000014435313610377563013030 00000000000000/* * PIM for Quagga * Copyright (C) 2008 Everton da Silva Marques * * This program is free software; you can redistribute it and/or modify * it under the terms of the 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; see the file COPYING; if not, write to the Free Software * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA */ #include #include "log.h" #include "zclient.h" #include "memory.h" #include "thread.h" #include "linklist.h" #include "vty.h" #include "plist.h" #include "hash.h" #include "jhash.h" #include "wheel.h" #include "pimd.h" #include "pim_pim.h" #include "pim_str.h" #include "pim_time.h" #include "pim_iface.h" #include "pim_join.h" #include "pim_zlookup.h" #include "pim_upstream.h" #include "pim_ifchannel.h" #include "pim_neighbor.h" #include "pim_rpf.h" #include "pim_zebra.h" #include "pim_oil.h" #include "pim_macro.h" #include "pim_rp.h" #include "pim_br.h" #include "pim_register.h" #include "pim_msdp.h" #include "pim_jp_agg.h" #include "pim_nht.h" #include "pim_ssm.h" #include "pim_vxlan.h" static void join_timer_stop(struct pim_upstream *up); static void pim_upstream_update_assert_tracking_desired(struct pim_upstream *up); /* * A (*,G) or a (*,*) is going away * remove the parent pointer from * those pointing at us */ static void pim_upstream_remove_children(struct pim_instance *pim, struct pim_upstream *up) { struct pim_upstream *child; if (!up->sources) return; while (!list_isempty(up->sources)) { child = listnode_head(up->sources); listnode_delete(up->sources, child); if (PIM_UPSTREAM_FLAG_TEST_SRC_LHR(child->flags)) { PIM_UPSTREAM_FLAG_UNSET_SRC_LHR(child->flags); child = pim_upstream_del(pim, child, __PRETTY_FUNCTION__); } if (child) child->parent = NULL; } list_delete(&up->sources); } /* * A (*,G) or a (*,*) is being created * Find the children that would point * at us. */ static void pim_upstream_find_new_children(struct pim_instance *pim, struct pim_upstream *up) { struct pim_upstream *child; struct listnode *ch_node; if ((up->sg.src.s_addr != INADDR_ANY) && (up->sg.grp.s_addr != INADDR_ANY)) return; if ((up->sg.src.s_addr == INADDR_ANY) && (up->sg.grp.s_addr == INADDR_ANY)) return; for (ALL_LIST_ELEMENTS_RO(pim->upstream_list, ch_node, child)) { if ((up->sg.grp.s_addr != INADDR_ANY) && (child->sg.grp.s_addr == up->sg.grp.s_addr) && (child != up)) { child->parent = up; listnode_add_sort(up->sources, child); } } } /* * If we have a (*,*) || (S,*) there is no parent * If we have a (S,G), find the (*,G) * If we have a (*,G), find the (*,*) */ static struct pim_upstream *pim_upstream_find_parent(struct pim_instance *pim, struct pim_upstream *child) { struct prefix_sg any = child->sg; struct pim_upstream *up = NULL; // (S,G) if ((child->sg.src.s_addr != INADDR_ANY) && (child->sg.grp.s_addr != INADDR_ANY)) { any.src.s_addr = INADDR_ANY; up = pim_upstream_find(pim, &any); if (up) listnode_add(up->sources, child); return up; } return NULL; } static void upstream_channel_oil_detach(struct pim_upstream *up) { if (up->channel_oil) { /* Detaching from channel_oil, channel_oil may exist post del, but upstream would not keep reference of it */ up->channel_oil->up = NULL; pim_channel_oil_del(up->channel_oil, __PRETTY_FUNCTION__); up->channel_oil = NULL; } } struct pim_upstream *pim_upstream_del(struct pim_instance *pim, struct pim_upstream *up, const char *name) { struct listnode *node, *nnode; struct pim_ifchannel *ch; bool notify_msdp = false; struct prefix nht_p; if (PIM_DEBUG_TRACE) zlog_debug( "%s(%s): Delete %s[%s] ref count: %d , flags: %d c_oil ref count %d (Pre decrement)", __PRETTY_FUNCTION__, name, up->sg_str, pim->vrf->name, up->ref_count, up->flags, up->channel_oil->oil_ref_count); assert(up->ref_count > 0); --up->ref_count; if (up->ref_count >= 1) return up; THREAD_OFF(up->t_ka_timer); THREAD_OFF(up->t_rs_timer); THREAD_OFF(up->t_msdp_reg_timer); if (up->join_state == PIM_UPSTREAM_JOINED) { pim_jp_agg_single_upstream_send(&up->rpf, up, 0); if (up->sg.src.s_addr == INADDR_ANY) { /* if a (*, G) entry in the joined state is being * deleted we * need to notify MSDP */ notify_msdp = true; } } join_timer_stop(up); pim_jp_agg_upstream_verification(up, false); up->rpf.source_nexthop.interface = NULL; if (up->sg.src.s_addr != INADDR_ANY) { if (pim->upstream_sg_wheel) wheel_remove_item(pim->upstream_sg_wheel, up); notify_msdp = true; } pim_mroute_del(up->channel_oil, __PRETTY_FUNCTION__); upstream_channel_oil_detach(up); for (ALL_LIST_ELEMENTS(up->ifchannels, node, nnode, ch)) pim_ifchannel_delete(ch); list_delete(&up->ifchannels); pim_upstream_remove_children(pim, up); if (up->sources) list_delete(&up->sources); if (up->parent && up->parent->sources) listnode_delete(up->parent->sources, up); up->parent = NULL; listnode_delete(pim->upstream_list, up); hash_release(pim->upstream_hash, up); if (notify_msdp) { pim_msdp_up_del(pim, &up->sg); } /* When RP gets deleted, pim_rp_del() deregister addr with Zebra NHT * and assign up->upstream_addr as INADDR_ANY. * So before de-registering the upstream address, check if is not equal * to INADDR_ANY. This is done in order to avoid de-registering for * 255.255.255.255 which is maintained for some reason.. */ if (up->upstream_addr.s_addr != INADDR_ANY) { /* Deregister addr with Zebra NHT */ nht_p.family = AF_INET; nht_p.prefixlen = IPV4_MAX_BITLEN; nht_p.u.prefix4 = up->upstream_addr; if (PIM_DEBUG_TRACE) { char buf[PREFIX2STR_BUFFER]; prefix2str(&nht_p, buf, sizeof(buf)); zlog_debug("%s: Deregister upstream %s addr %s with Zebra NHT", __PRETTY_FUNCTION__, up->sg_str, buf); } pim_delete_tracked_nexthop(pim, &nht_p, up, NULL, false); } XFREE(MTYPE_PIM_UPSTREAM, up); return NULL; } void pim_upstream_send_join(struct pim_upstream *up) { if (!up->rpf.source_nexthop.interface) { if (PIM_DEBUG_TRACE) zlog_debug("%s: up %s RPF is not present", __PRETTY_FUNCTION__, up->sg_str); return; } if (PIM_DEBUG_TRACE) { char rpf_str[PREFIX_STRLEN]; pim_addr_dump("", &up->rpf.rpf_addr, rpf_str, sizeof(rpf_str)); zlog_debug("%s: RPF'%s=%s(%s) for Interface %s", __PRETTY_FUNCTION__, up->sg_str, rpf_str, pim_upstream_state2str(up->join_state), up->rpf.source_nexthop.interface->name); if (pim_rpf_addr_is_inaddr_any(&up->rpf)) { zlog_debug("%s: can't send join upstream: RPF'%s=%s", __PRETTY_FUNCTION__, up->sg_str, rpf_str); /* warning only */ } } /* send Join(S,G) to the current upstream neighbor */ pim_jp_agg_single_upstream_send(&up->rpf, up, 1 /* join */); } static int on_join_timer(struct thread *t) { struct pim_upstream *up; up = THREAD_ARG(t); if (!up->rpf.source_nexthop.interface) { if (PIM_DEBUG_TRACE) zlog_debug("%s: up %s RPF is not present", __PRETTY_FUNCTION__, up->sg_str); return 0; } /* * In the case of a HFR we will not ahve anyone to send this to. */ if (PIM_UPSTREAM_FLAG_TEST_FHR(up->flags)) return 0; /* * Don't send the join if the outgoing interface is a loopback * But since this might change leave the join timer running */ if (up->rpf.source_nexthop .interface && !if_is_loopback(up->rpf.source_nexthop.interface)) pim_upstream_send_join(up); join_timer_start(up); return 0; } static void join_timer_stop(struct pim_upstream *up) { struct pim_neighbor *nbr = NULL; THREAD_OFF(up->t_join_timer); if (up->rpf.source_nexthop.interface) nbr = pim_neighbor_find(up->rpf.source_nexthop.interface, up->rpf.rpf_addr.u.prefix4); if (nbr) pim_jp_agg_remove_group(nbr->upstream_jp_agg, up); pim_jp_agg_upstream_verification(up, false); } void join_timer_start(struct pim_upstream *up) { struct pim_neighbor *nbr = NULL; if (up->rpf.source_nexthop.interface) { nbr = pim_neighbor_find(up->rpf.source_nexthop.interface, up->rpf.rpf_addr.u.prefix4); if (PIM_DEBUG_PIM_EVENTS) { zlog_debug( "%s: starting %d sec timer for upstream (S,G)=%s", __PRETTY_FUNCTION__, router->t_periodic, up->sg_str); } } if (nbr) pim_jp_agg_add_group(nbr->upstream_jp_agg, up, 1); else { THREAD_OFF(up->t_join_timer); thread_add_timer(router->master, on_join_timer, up, router->t_periodic, &up->t_join_timer); } pim_jp_agg_upstream_verification(up, true); } /* * This is only called when we are switching the upstream * J/P from one neighbor to another * * As such we need to remove from the old list and * add to the new list. */ void pim_upstream_join_timer_restart(struct pim_upstream *up, struct pim_rpf *old) { // THREAD_OFF(up->t_join_timer); join_timer_start(up); } static void pim_upstream_join_timer_restart_msec(struct pim_upstream *up, int interval_msec) { if (PIM_DEBUG_PIM_EVENTS) { zlog_debug("%s: restarting %d msec timer for upstream (S,G)=%s", __PRETTY_FUNCTION__, interval_msec, up->sg_str); } THREAD_OFF(up->t_join_timer); thread_add_timer_msec(router->master, on_join_timer, up, interval_msec, &up->t_join_timer); } void pim_upstream_join_suppress(struct pim_upstream *up, struct in_addr rpf_addr, int holdtime) { long t_joinsuppress_msec; long join_timer_remain_msec; if (!up->rpf.source_nexthop.interface) { if (PIM_DEBUG_TRACE) zlog_debug("%s: up %s RPF is not present", __PRETTY_FUNCTION__, up->sg_str); return; } t_joinsuppress_msec = MIN(pim_if_t_suppressed_msec(up->rpf.source_nexthop.interface), 1000 * holdtime); join_timer_remain_msec = pim_time_timer_remain_msec(up->t_join_timer); if (PIM_DEBUG_TRACE) { char rpf_str[INET_ADDRSTRLEN]; pim_inet4_dump("", rpf_addr, rpf_str, sizeof(rpf_str)); zlog_debug( "%s %s: detected Join%s to RPF'(S,G)=%s: join_timer=%ld msec t_joinsuppress=%ld msec", __FILE__, __PRETTY_FUNCTION__, up->sg_str, rpf_str, join_timer_remain_msec, t_joinsuppress_msec); } if (join_timer_remain_msec < t_joinsuppress_msec) { if (PIM_DEBUG_TRACE) { zlog_debug( "%s %s: suppressing Join(S,G)=%s for %ld msec", __FILE__, __PRETTY_FUNCTION__, up->sg_str, t_joinsuppress_msec); } pim_upstream_join_timer_restart_msec(up, t_joinsuppress_msec); } } void pim_upstream_join_timer_decrease_to_t_override(const char *debug_label, struct pim_upstream *up) { long join_timer_remain_msec; int t_override_msec; if (!up->rpf.source_nexthop.interface) { if (PIM_DEBUG_TRACE) zlog_debug("%s: up %s RPF is not present", __PRETTY_FUNCTION__, up->sg_str); return; } join_timer_remain_msec = pim_time_timer_remain_msec(up->t_join_timer); t_override_msec = pim_if_t_override_msec(up->rpf.source_nexthop.interface); if (PIM_DEBUG_TRACE) { char rpf_str[INET_ADDRSTRLEN]; pim_inet4_dump("", up->rpf.rpf_addr.u.prefix4, rpf_str, sizeof(rpf_str)); zlog_debug( "%s: to RPF'%s=%s: join_timer=%ld msec t_override=%d msec", debug_label, up->sg_str, rpf_str, join_timer_remain_msec, t_override_msec); } if (join_timer_remain_msec > t_override_msec) { if (PIM_DEBUG_TRACE) { zlog_debug( "%s: decreasing (S,G)=%s join timer to t_override=%d msec", debug_label, up->sg_str, t_override_msec); } pim_upstream_join_timer_restart_msec(up, t_override_msec); } } static void forward_on(struct pim_upstream *up) { struct listnode *chnode; struct listnode *chnextnode; struct pim_ifchannel *ch = NULL; /* scan (S,G) state */ for (ALL_LIST_ELEMENTS(up->ifchannels, chnode, chnextnode, ch)) { if (pim_macro_chisin_oiflist(ch)) pim_forward_start(ch); } /* scan iface channel list */ } static void forward_off(struct pim_upstream *up) { struct listnode *chnode; struct listnode *chnextnode; struct pim_ifchannel *ch; /* scan per-interface (S,G) state */ for (ALL_LIST_ELEMENTS(up->ifchannels, chnode, chnextnode, ch)) { pim_forward_stop(ch, false); } /* scan iface channel list */ } static int pim_upstream_could_register(struct pim_upstream *up) { struct pim_interface *pim_ifp = NULL; /* FORCE_PIMREG is a generic flag to let an app like VxLAN-AA register * a source on an upstream entry even if the source is not directly * connected on the IIF. */ if (PIM_UPSTREAM_FLAG_TEST_FORCE_PIMREG(up->flags)) return 1; if (up->rpf.source_nexthop.interface) pim_ifp = up->rpf.source_nexthop.interface->info; else { if (PIM_DEBUG_TRACE) zlog_debug("%s: up %s RPF is not present", __PRETTY_FUNCTION__, up->sg_str); } if (pim_ifp && PIM_I_am_DR(pim_ifp) && pim_if_connected_to_source(up->rpf.source_nexthop.interface, up->sg.src)) return 1; return 0; } /* Source registration is suppressed for SSM groups. When the SSM range changes * we re-revaluate register setup for existing upstream entries */ void pim_upstream_register_reevaluate(struct pim_instance *pim) { struct listnode *upnode; struct pim_upstream *up; for (ALL_LIST_ELEMENTS_RO(pim->upstream_list, upnode, up)) { /* If FHR is set CouldRegister is True. Also check if the flow * is actually active; if it is not kat setup will trigger * source * registration whenever the flow becomes active. */ if (!PIM_UPSTREAM_FLAG_TEST_FHR(up->flags) || !up->t_ka_timer) continue; if (pim_is_grp_ssm(pim, up->sg.grp)) { /* clear the register state for SSM groups */ if (up->reg_state != PIM_REG_NOINFO) { if (PIM_DEBUG_PIM_EVENTS) zlog_debug( "Clear register for %s as G is now SSM", up->sg_str); /* remove regiface from the OIL if it is there*/ pim_channel_del_oif(up->channel_oil, pim->regiface, PIM_OIF_FLAG_PROTO_PIM); up->reg_state = PIM_REG_NOINFO; } } else { /* register ASM sources with the RP */ if (up->reg_state == PIM_REG_NOINFO) { if (PIM_DEBUG_PIM_EVENTS) zlog_debug( "Register %s as G is now ASM", up->sg_str); pim_channel_add_oif(up->channel_oil, pim->regiface, PIM_OIF_FLAG_PROTO_PIM); up->reg_state = PIM_REG_JOIN; } } } } void pim_upstream_switch(struct pim_instance *pim, struct pim_upstream *up, enum pim_upstream_state new_state) { enum pim_upstream_state old_state = up->join_state; if (up->upstream_addr.s_addr == INADDR_ANY) { if (PIM_DEBUG_PIM_EVENTS) zlog_debug("%s: RPF not configured for %s", __PRETTY_FUNCTION__, up->sg_str); return; } if (!up->rpf.source_nexthop.interface) { if (PIM_DEBUG_PIM_EVENTS) zlog_debug("%s: RP not reachable for %s", __PRETTY_FUNCTION__, up->sg_str); return; } if (PIM_DEBUG_PIM_EVENTS) { zlog_debug("%s: PIM_UPSTREAM_%s: (S,G) old: %s new: %s", __PRETTY_FUNCTION__, up->sg_str, pim_upstream_state2str(up->join_state), pim_upstream_state2str(new_state)); } up->join_state = new_state; if (old_state != new_state) up->state_transition = pim_time_monotonic_sec(); pim_upstream_update_assert_tracking_desired(up); if (new_state == PIM_UPSTREAM_JOINED) { pim_upstream_inherited_olist_decide(pim, up); if (old_state != PIM_UPSTREAM_JOINED) { int old_fhr = PIM_UPSTREAM_FLAG_TEST_FHR(up->flags); pim_msdp_up_join_state_changed(pim, up); if (pim_upstream_could_register(up)) { PIM_UPSTREAM_FLAG_SET_FHR(up->flags); if (!old_fhr && PIM_UPSTREAM_FLAG_TEST_SRC_STREAM( up->flags)) { pim_upstream_keep_alive_timer_start( up, pim->keep_alive_time); pim_register_join(up); } } else { pim_upstream_send_join(up); join_timer_start(up); } } } else { forward_off(up); if (old_state == PIM_UPSTREAM_JOINED) pim_msdp_up_join_state_changed(pim, up); /* IHR, Trigger SGRpt on *,G IIF to prune S,G from RPT towards RP. If I am RP for G then send S,G prune to its IIF. */ if (pim_upstream_is_sg_rpt(up) && up->parent && !I_am_RP(pim, up->sg.grp)) { if (PIM_DEBUG_PIM_TRACE_DETAIL) zlog_debug( "%s: *,G IIF %s S,G IIF %s ", __PRETTY_FUNCTION__, up->parent->rpf.source_nexthop.interface ? up->parent->rpf.source_nexthop.interface->name : "Unknown", up->rpf.source_nexthop.interface ? up->rpf.source_nexthop.interface->name : "Unknown"); pim_jp_agg_single_upstream_send(&up->parent->rpf, up->parent, 1 /* (W,G) Join */); } else pim_jp_agg_single_upstream_send(&up->rpf, up, 0 /* prune */); join_timer_stop(up); } } int pim_upstream_compare(void *arg1, void *arg2) { const struct pim_upstream *up1 = (const struct pim_upstream *)arg1; const struct pim_upstream *up2 = (const struct pim_upstream *)arg2; if (ntohl(up1->sg.grp.s_addr) < ntohl(up2->sg.grp.s_addr)) return -1; if (ntohl(up1->sg.grp.s_addr) > ntohl(up2->sg.grp.s_addr)) return 1; if (ntohl(up1->sg.src.s_addr) < ntohl(up2->sg.src.s_addr)) return -1; if (ntohl(up1->sg.src.s_addr) > ntohl(up2->sg.src.s_addr)) return 1; return 0; } void pim_upstream_fill_static_iif(struct pim_upstream *up, struct interface *incoming) { up->rpf.source_nexthop.interface = incoming; /* reset other parameters to matched a connected incoming interface */ up->rpf.source_nexthop.mrib_nexthop_addr.family = AF_INET; up->rpf.source_nexthop.mrib_nexthop_addr.u.prefix4.s_addr = PIM_NET_INADDR_ANY; up->rpf.source_nexthop.mrib_metric_preference = ZEBRA_CONNECT_DISTANCE_DEFAULT; up->rpf.source_nexthop.mrib_route_metric = 0; up->rpf.rpf_addr.family = AF_INET; up->rpf.rpf_addr.u.prefix4.s_addr = PIM_NET_INADDR_ANY; } static struct pim_upstream *pim_upstream_new(struct pim_instance *pim, struct prefix_sg *sg, struct interface *incoming, int flags, struct pim_ifchannel *ch) { enum pim_rpf_result rpf_result; struct pim_interface *pim_ifp; struct pim_upstream *up; up = XCALLOC(MTYPE_PIM_UPSTREAM, sizeof(*up)); up->sg = *sg; pim_str_sg_set(sg, up->sg_str); if (ch) ch->upstream = up; up = hash_get(pim->upstream_hash, up, hash_alloc_intern); /* Set up->upstream_addr as INADDR_ANY, if RP is not * configured and retain the upstream data structure */ if (!pim_rp_set_upstream_addr(pim, &up->upstream_addr, sg->src, sg->grp)) { if (PIM_DEBUG_TRACE) zlog_debug("%s: Received a (*,G) with no RP configured", __PRETTY_FUNCTION__); } up->parent = pim_upstream_find_parent(pim, up); if (up->sg.src.s_addr == INADDR_ANY) { up->sources = list_new(); up->sources->cmp = pim_upstream_compare; } else up->sources = NULL; pim_upstream_find_new_children(pim, up); up->flags = flags; up->ref_count = 1; up->t_join_timer = NULL; up->t_ka_timer = NULL; up->t_rs_timer = NULL; up->t_msdp_reg_timer = NULL; up->join_state = PIM_UPSTREAM_NOTJOINED; up->reg_state = PIM_REG_NOINFO; up->state_transition = pim_time_monotonic_sec(); up->channel_oil = pim_channel_oil_add(pim, &up->sg, MAXVIFS, __PRETTY_FUNCTION__); up->sptbit = PIM_UPSTREAM_SPTBIT_FALSE; up->rpf.source_nexthop.interface = NULL; up->rpf.source_nexthop.mrib_nexthop_addr.family = AF_INET; up->rpf.source_nexthop.mrib_nexthop_addr.u.prefix4.s_addr = PIM_NET_INADDR_ANY; up->rpf.source_nexthop.mrib_metric_preference = router->infinite_assert_metric.metric_preference; up->rpf.source_nexthop.mrib_route_metric = router->infinite_assert_metric.route_metric; up->rpf.rpf_addr.family = AF_INET; up->rpf.rpf_addr.u.prefix4.s_addr = PIM_NET_INADDR_ANY; up->ifchannels = list_new(); up->ifchannels->cmp = (int (*)(void *, void *))pim_ifchannel_compare; if (up->sg.src.s_addr != INADDR_ANY) wheel_add_item(pim->upstream_sg_wheel, up); if (PIM_UPSTREAM_FLAG_TEST_STATIC_IIF(up->flags) || PIM_UPSTREAM_FLAG_TEST_SRC_NOCACHE(up->flags)) { pim_upstream_fill_static_iif(up, incoming); pim_ifp = up->rpf.source_nexthop.interface->info; assert(pim_ifp); pim_channel_oil_change_iif(pim, up->channel_oil, pim_ifp->mroute_vif_index, __PRETTY_FUNCTION__); if (PIM_UPSTREAM_FLAG_TEST_SRC_NOCACHE(up->flags)) pim_upstream_keep_alive_timer_start( up, pim->keep_alive_time); } else if (up->upstream_addr.s_addr != INADDR_ANY) { rpf_result = pim_rpf_update(pim, up, NULL); if (rpf_result == PIM_RPF_FAILURE) { if (PIM_DEBUG_TRACE) zlog_debug( "%s: Attempting to create upstream(%s), Unable to RPF for source", __PRETTY_FUNCTION__, up->sg_str); } if (up->rpf.source_nexthop.interface) { pim_ifp = up->rpf.source_nexthop.interface->info; if (pim_ifp) pim_channel_oil_change_iif( pim, up->channel_oil, pim_ifp->mroute_vif_index, __PRETTY_FUNCTION__); } } listnode_add_sort(pim->upstream_list, up); if (PIM_DEBUG_TRACE) { zlog_debug( "%s: Created Upstream %s upstream_addr %s ref count %d increment", __PRETTY_FUNCTION__, up->sg_str, inet_ntoa(up->upstream_addr), up->ref_count); } return up; } struct pim_upstream *pim_upstream_find(struct pim_instance *pim, struct prefix_sg *sg) { struct pim_upstream lookup; struct pim_upstream *up = NULL; lookup.sg = *sg; up = hash_lookup(pim->upstream_hash, &lookup); return up; } struct pim_upstream *pim_upstream_find_or_add(struct prefix_sg *sg, struct interface *incoming, int flags, const char *name) { struct pim_upstream *up; struct pim_interface *pim_ifp; pim_ifp = incoming->info; up = pim_upstream_find(pim_ifp->pim, sg); if (up) { if (!(up->flags & flags)) { up->flags |= flags; up->ref_count++; if (PIM_DEBUG_TRACE) zlog_debug( "%s(%s): upstream %s ref count %d increment", __PRETTY_FUNCTION__, name, up->sg_str, up->ref_count); } } else up = pim_upstream_add(pim_ifp->pim, sg, incoming, flags, name, NULL); return up; } void pim_upstream_ref(struct pim_upstream *up, int flags, const char *name) { up->flags |= flags; ++up->ref_count; if (PIM_DEBUG_TRACE) zlog_debug("%s(%s): upstream %s ref count %d increment", __PRETTY_FUNCTION__, name, up->sg_str, up->ref_count); } struct pim_upstream *pim_upstream_add(struct pim_instance *pim, struct prefix_sg *sg, struct interface *incoming, int flags, const char *name, struct pim_ifchannel *ch) { struct pim_upstream *up = NULL; int found = 0; up = pim_upstream_find(pim, sg); if (up) { pim_upstream_ref(up, flags, name); found = 1; } else { up = pim_upstream_new(pim, sg, incoming, flags, ch); } if (PIM_DEBUG_TRACE) { if (up) { char buf[PREFIX2STR_BUFFER]; prefix2str(&up->rpf.rpf_addr, buf, sizeof(buf)); zlog_debug("%s(%s): %s, iif %s (%s) found: %d: ref_count: %d", __PRETTY_FUNCTION__, name, up->sg_str, buf, up->rpf.source_nexthop.interface ? up->rpf.source_nexthop.interface->name : "Unknown" , found, up->ref_count); } else zlog_debug("%s(%s): (%s) failure to create", __PRETTY_FUNCTION__, name, pim_str_sg_dump(sg)); } return up; } /* * Passed in up must be the upstream for ch. starch is NULL if no * information */ int pim_upstream_evaluate_join_desired_interface(struct pim_upstream *up, struct pim_ifchannel *ch, struct pim_ifchannel *starch) { if (ch) { if (PIM_IF_FLAG_TEST_S_G_RPT(ch->flags)) return 0; if (!pim_macro_ch_lost_assert(ch) && pim_macro_chisin_joins_or_include(ch)) return 1; } /* * joins (*,G) */ if (starch) { if (PIM_IF_FLAG_TEST_S_G_RPT(starch->upstream->flags)) return 0; if (!pim_macro_ch_lost_assert(starch) && pim_macro_chisin_joins_or_include(starch)) return 1; } return 0; } /* Evaluate JoinDesired(S,G): JoinDesired(S,G) is true if there is a downstream (S,G) interface I in the set: inherited_olist(S,G) = joins(S,G) (+) pim_include(S,G) (-) lost_assert(S,G) JoinDesired(S,G) may be affected by changes in the following: pim_ifp->primary_address pim_ifp->pim_dr_addr ch->ifassert_winner_metric ch->ifassert_winner ch->local_ifmembership ch->ifjoin_state ch->upstream->rpf.source_nexthop.mrib_metric_preference ch->upstream->rpf.source_nexthop.mrib_route_metric ch->upstream->rpf.source_nexthop.interface See also pim_upstream_update_join_desired() below. */ int pim_upstream_evaluate_join_desired(struct pim_instance *pim, struct pim_upstream *up) { struct interface *ifp; struct pim_ifchannel *ch, *starch; struct pim_upstream *starup = up->parent; int ret = 0; FOR_ALL_INTERFACES (pim->vrf, ifp) { if (!ifp->info) continue; ch = pim_ifchannel_find(ifp, &up->sg); if (starup) starch = pim_ifchannel_find(ifp, &starup->sg); else starch = NULL; if (!ch && !starch) continue; ret += pim_upstream_evaluate_join_desired_interface(up, ch, starch); } /* scan iface channel list */ return ret; /* false */ } /* See also pim_upstream_evaluate_join_desired() above. */ void pim_upstream_update_join_desired(struct pim_instance *pim, struct pim_upstream *up) { int was_join_desired; /* boolean */ int is_join_desired; /* boolean */ was_join_desired = PIM_UPSTREAM_FLAG_TEST_DR_JOIN_DESIRED(up->flags); is_join_desired = pim_upstream_evaluate_join_desired(pim, up); if (is_join_desired) PIM_UPSTREAM_FLAG_SET_DR_JOIN_DESIRED(up->flags); else PIM_UPSTREAM_FLAG_UNSET_DR_JOIN_DESIRED(up->flags); /* switched from false to true */ if (is_join_desired && (up->join_state == PIM_UPSTREAM_NOTJOINED)) { pim_upstream_switch(pim, up, PIM_UPSTREAM_JOINED); return; } /* switched from true to false */ if (!is_join_desired && was_join_desired) { pim_upstream_switch(pim, up, PIM_UPSTREAM_NOTJOINED); return; } } /* RFC 4601 4.5.7. Sending (S,G) Join/Prune Messages Transitions from Joined State RPF'(S,G) GenID changes The upstream (S,G) state machine remains in Joined state. If the Join Timer is set to expire in more than t_override seconds, reset it so that it expires after t_override seconds. */ void pim_upstream_rpf_genid_changed(struct pim_instance *pim, struct in_addr neigh_addr) { struct listnode *up_node; struct listnode *up_nextnode; struct pim_upstream *up; /* * Scan all (S,G) upstreams searching for RPF'(S,G)=neigh_addr */ for (ALL_LIST_ELEMENTS(pim->upstream_list, up_node, up_nextnode, up)) { if (PIM_DEBUG_TRACE) { char neigh_str[INET_ADDRSTRLEN]; char rpf_addr_str[PREFIX_STRLEN]; pim_inet4_dump("", neigh_addr, neigh_str, sizeof(neigh_str)); pim_addr_dump("", &up->rpf.rpf_addr, rpf_addr_str, sizeof(rpf_addr_str)); zlog_debug( "%s: matching neigh=%s against upstream (S,G)=%s[%s] joined=%d rpf_addr=%s", __PRETTY_FUNCTION__, neigh_str, up->sg_str, pim->vrf->name, up->join_state == PIM_UPSTREAM_JOINED, rpf_addr_str); } /* consider only (S,G) upstream in Joined state */ if (up->join_state != PIM_UPSTREAM_JOINED) continue; /* match RPF'(S,G)=neigh_addr */ if (up->rpf.rpf_addr.u.prefix4.s_addr != neigh_addr.s_addr) continue; pim_upstream_join_timer_decrease_to_t_override( "RPF'(S,G) GenID change", up); } } void pim_upstream_rpf_interface_changed(struct pim_upstream *up, struct interface *old_rpf_ifp) { struct listnode *chnode; struct listnode *chnextnode; struct pim_ifchannel *ch; /* search all ifchannels */ for (ALL_LIST_ELEMENTS(up->ifchannels, chnode, chnextnode, ch)) { if (ch->ifassert_state == PIM_IFASSERT_I_AM_LOSER) { if ( /* RPF_interface(S) was NOT I */ (old_rpf_ifp == ch->interface) && /* RPF_interface(S) stopped being I */ (ch->upstream->rpf.source_nexthop .interface) && (ch->upstream->rpf.source_nexthop .interface != ch->interface)) { assert_action_a5(ch); } } /* PIM_IFASSERT_I_AM_LOSER */ pim_ifchannel_update_assert_tracking_desired(ch); } } void pim_upstream_update_could_assert(struct pim_upstream *up) { struct listnode *chnode; struct listnode *chnextnode; struct pim_ifchannel *ch; /* scan per-interface (S,G) state */ for (ALL_LIST_ELEMENTS(up->ifchannels, chnode, chnextnode, ch)) { pim_ifchannel_update_could_assert(ch); } /* scan iface channel list */ } void pim_upstream_update_my_assert_metric(struct pim_upstream *up) { struct listnode *chnode; struct listnode *chnextnode; struct pim_ifchannel *ch; /* scan per-interface (S,G) state */ for (ALL_LIST_ELEMENTS(up->ifchannels, chnode, chnextnode, ch)) { pim_ifchannel_update_my_assert_metric(ch); } /* scan iface channel list */ } static void pim_upstream_update_assert_tracking_desired(struct pim_upstream *up) { struct listnode *chnode; struct listnode *chnextnode; struct pim_interface *pim_ifp; struct pim_ifchannel *ch; /* scan per-interface (S,G) state */ for (ALL_LIST_ELEMENTS(up->ifchannels, chnode, chnextnode, ch)) { if (!ch->interface) continue; pim_ifp = ch->interface->info; if (!pim_ifp) continue; pim_ifchannel_update_assert_tracking_desired(ch); } /* scan iface channel list */ } /* When kat is stopped CouldRegister goes to false so we need to * transition the (S, G) on FHR to NI state and remove reg tunnel * from the OIL */ static void pim_upstream_fhr_kat_expiry(struct pim_instance *pim, struct pim_upstream *up) { if (!PIM_UPSTREAM_FLAG_TEST_FHR(up->flags)) return; if (PIM_DEBUG_TRACE) zlog_debug("kat expired on %s; clear fhr reg state", up->sg_str); /* stop reg-stop timer */ THREAD_OFF(up->t_rs_timer); /* remove regiface from the OIL if it is there*/ pim_channel_del_oif(up->channel_oil, pim->regiface, PIM_OIF_FLAG_PROTO_PIM); /* clear the register state */ up->reg_state = PIM_REG_NOINFO; PIM_UPSTREAM_FLAG_UNSET_FHR(up->flags); } /* When kat is started CouldRegister can go to true. And if it does we * need to transition the (S, G) on FHR to JOINED state and add reg tunnel * to the OIL */ static void pim_upstream_fhr_kat_start(struct pim_upstream *up) { if (pim_upstream_could_register(up)) { if (PIM_DEBUG_TRACE) zlog_debug( "kat started on %s; set fhr reg state to joined", up->sg_str); PIM_UPSTREAM_FLAG_SET_FHR(up->flags); if (up->reg_state == PIM_REG_NOINFO) pim_register_join(up); } } /* * On an RP, the PMBR value must be cleared when the * Keepalive Timer expires * KAT expiry indicates that flow is inactive. If the flow was created or * maintained by activity now is the time to deref it. */ struct pim_upstream *pim_upstream_keep_alive_timer_proc( struct pim_upstream *up) { struct pim_instance *pim; pim = up->channel_oil->pim; if (PIM_UPSTREAM_FLAG_TEST_DISABLE_KAT_EXPIRY(up->flags)) { /* if the router is a PIM vxlan encapsulator we prevent expiry * of KAT as the mroute is pre-setup without any traffic */ pim_upstream_keep_alive_timer_start(up, pim->keep_alive_time); return up; } if (I_am_RP(pim, up->sg.grp)) { pim_br_clear_pmbr(&up->sg); /* * We need to do more here :) * But this is the start. */ } /* source is no longer active - pull the SA from MSDP's cache */ pim_msdp_sa_local_del(pim, &up->sg); /* if entry was created because of activity we need to deref it */ if (PIM_UPSTREAM_FLAG_TEST_SRC_STREAM(up->flags)) { pim_upstream_fhr_kat_expiry(pim, up); if (PIM_DEBUG_TRACE) zlog_debug( "kat expired on %s[%s]; remove stream reference", up->sg_str, pim->vrf->name); PIM_UPSTREAM_FLAG_UNSET_SRC_STREAM(up->flags); /* Return if upstream entry got deleted.*/ if (!pim_upstream_del(pim, up, __PRETTY_FUNCTION__)) return NULL; } if (PIM_UPSTREAM_FLAG_TEST_SRC_NOCACHE(up->flags)) { PIM_UPSTREAM_FLAG_UNSET_SRC_NOCACHE(up->flags); if (!pim_upstream_del(pim, up, __PRETTY_FUNCTION__)) return NULL; } /* upstream reference would have been added to track the local * membership if it is LHR. We have to clear it when KAT expires. * Otherwise would result in stale entry with uncleared ref count. */ if (PIM_UPSTREAM_FLAG_TEST_SRC_LHR(up->flags)) { struct pim_upstream *parent = up->parent; PIM_UPSTREAM_FLAG_UNSET_SRC_LHR(up->flags); up = pim_upstream_del(pim, up, __PRETTY_FUNCTION__); if (parent) { pim_jp_agg_single_upstream_send(&parent->rpf, parent, true); } } return up; } static int pim_upstream_keep_alive_timer(struct thread *t) { struct pim_upstream *up; up = THREAD_ARG(t); pim_upstream_keep_alive_timer_proc(up); return 0; } void pim_upstream_keep_alive_timer_start(struct pim_upstream *up, uint32_t time) { if (!PIM_UPSTREAM_FLAG_TEST_SRC_STREAM(up->flags)) { if (PIM_DEBUG_TRACE) zlog_debug("kat start on %s with no stream reference", up->sg_str); } THREAD_OFF(up->t_ka_timer); thread_add_timer(router->master, pim_upstream_keep_alive_timer, up, time, &up->t_ka_timer); /* any time keepalive is started against a SG we will have to * re-evaluate our active source database */ pim_msdp_sa_local_update(up); } /* MSDP on RP needs to know if a source is registerable to this RP */ static int pim_upstream_msdp_reg_timer(struct thread *t) { struct pim_upstream *up = THREAD_ARG(t); struct pim_instance *pim = up->channel_oil->pim; /* source is no longer active - pull the SA from MSDP's cache */ pim_msdp_sa_local_del(pim, &up->sg); return 1; } void pim_upstream_msdp_reg_timer_start(struct pim_upstream *up) { THREAD_OFF(up->t_msdp_reg_timer); thread_add_timer(router->master, pim_upstream_msdp_reg_timer, up, PIM_MSDP_REG_RXED_PERIOD, &up->t_msdp_reg_timer); pim_msdp_sa_local_update(up); } /* * 4.2.1 Last-Hop Switchover to the SPT * * In Sparse-Mode PIM, last-hop routers join the shared tree towards the * RP. Once traffic from sources to joined groups arrives at a last-hop * router, it has the option of switching to receive the traffic on a * shortest path tree (SPT). * * The decision for a router to switch to the SPT is controlled as * follows: * * void * CheckSwitchToSpt(S,G) { * if ( ( pim_include(*,G) (-) pim_exclude(S,G) * (+) pim_include(S,G) != NULL ) * AND SwitchToSptDesired(S,G) ) { * # Note: Restarting the KAT will result in the SPT switch * set KeepaliveTimer(S,G) to Keepalive_Period * } * } * * SwitchToSptDesired(S,G) is a policy function that is implementation * defined. An "infinite threshold" policy can be implemented by making * SwitchToSptDesired(S,G) return false all the time. A "switch on * first packet" policy can be implemented by making * SwitchToSptDesired(S,G) return true once a single packet has been * received for the source and group. */ int pim_upstream_switch_to_spt_desired(struct pim_instance *pim, struct prefix_sg *sg) { if (I_am_RP(pim, sg->grp)) return 1; return 0; } int pim_upstream_is_sg_rpt(struct pim_upstream *up) { struct listnode *chnode; struct pim_ifchannel *ch; for (ALL_LIST_ELEMENTS_RO(up->ifchannels, chnode, ch)) { if (PIM_IF_FLAG_TEST_S_G_RPT(ch->flags)) return 1; } return 0; } /* * After receiving a packet set SPTbit: * void * Update_SPTbit(S,G,iif) { * if ( iif == RPF_interface(S) * AND JoinDesired(S,G) == true * AND ( DirectlyConnected(S) == true * OR RPF_interface(S) != RPF_interface(RP(G)) * OR inherited_olist(S,G,rpt) == NULL * OR ( ( RPF'(S,G) == RPF'(*,G) ) AND * ( RPF'(S,G) != NULL ) ) * OR ( I_Am_Assert_Loser(S,G,iif) ) { * Set SPTbit(S,G) to true * } * } */ void pim_upstream_set_sptbit(struct pim_upstream *up, struct interface *incoming) { struct pim_upstream *starup = up->parent; // iif == RPF_interfvace(S) if (up->rpf.source_nexthop.interface != incoming) { if (PIM_DEBUG_TRACE) zlog_debug( "%s: Incoming Interface: %s is different than RPF_interface(S) %s", __PRETTY_FUNCTION__, incoming->name, up->rpf.source_nexthop.interface->name); return; } // AND JoinDesired(S,G) == true if (!pim_upstream_evaluate_join_desired(up->channel_oil->pim, up)) { if (PIM_DEBUG_TRACE) zlog_debug("%s: %s Join is not Desired", __PRETTY_FUNCTION__, up->sg_str); return; } // DirectlyConnected(S) == true if (pim_if_connected_to_source(up->rpf.source_nexthop.interface, up->sg.src)) { if (PIM_DEBUG_TRACE) zlog_debug("%s: %s is directly connected to the source", __PRETTY_FUNCTION__, up->sg_str); up->sptbit = PIM_UPSTREAM_SPTBIT_TRUE; return; } // OR RPF_interface(S) != RPF_interface(RP(G)) if (!starup || up->rpf.source_nexthop .interface != starup->rpf.source_nexthop.interface) { struct pim_upstream *starup = up->parent; if (PIM_DEBUG_TRACE) zlog_debug( "%s: %s RPF_interface(S) != RPF_interface(RP(G))", __PRETTY_FUNCTION__, up->sg_str); up->sptbit = PIM_UPSTREAM_SPTBIT_TRUE; pim_jp_agg_single_upstream_send(&starup->rpf, starup, true); return; } // OR inherited_olist(S,G,rpt) == NULL if (pim_upstream_is_sg_rpt(up) && pim_upstream_empty_inherited_olist(up)) { if (PIM_DEBUG_TRACE) zlog_debug("%s: %s OR inherited_olist(S,G,rpt) == NULL", __PRETTY_FUNCTION__, up->sg_str); up->sptbit = PIM_UPSTREAM_SPTBIT_TRUE; return; } // OR ( ( RPF'(S,G) == RPF'(*,G) ) AND // ( RPF'(S,G) != NULL ) ) if (up->parent && pim_rpf_is_same(&up->rpf, &up->parent->rpf)) { if (PIM_DEBUG_TRACE) zlog_debug("%s: %s RPF'(S,G) is the same as RPF'(*,G)", __PRETTY_FUNCTION__, up->sg_str); up->sptbit = PIM_UPSTREAM_SPTBIT_TRUE; return; } return; } const char *pim_upstream_state2str(enum pim_upstream_state join_state) { switch (join_state) { case PIM_UPSTREAM_NOTJOINED: return "NotJoined"; break; case PIM_UPSTREAM_JOINED: return "Joined"; break; } return "Unknown"; } const char *pim_reg_state2str(enum pim_reg_state reg_state, char *state_str, size_t state_str_len) { switch (reg_state) { case PIM_REG_NOINFO: strlcpy(state_str, "RegNoInfo", state_str_len); break; case PIM_REG_JOIN: strlcpy(state_str, "RegJoined", state_str_len); break; case PIM_REG_JOIN_PENDING: strlcpy(state_str, "RegJoinPend", state_str_len); break; case PIM_REG_PRUNE: strlcpy(state_str, "RegPrune", state_str_len); break; default: strlcpy(state_str, "RegUnknown", state_str_len); } return state_str; } static int pim_upstream_register_stop_timer(struct thread *t) { struct pim_interface *pim_ifp; struct pim_instance *pim; struct pim_upstream *up; up = THREAD_ARG(t); pim = up->channel_oil->pim; if (PIM_DEBUG_TRACE) { char state_str[PIM_REG_STATE_STR_LEN]; zlog_debug("%s: (S,G)=%s[%s] upstream register stop timer %s", __PRETTY_FUNCTION__, up->sg_str, pim->vrf->name, pim_reg_state2str(up->reg_state, state_str, sizeof(state_str))); } switch (up->reg_state) { case PIM_REG_JOIN_PENDING: up->reg_state = PIM_REG_JOIN; pim_channel_add_oif(up->channel_oil, pim->regiface, PIM_OIF_FLAG_PROTO_PIM); pim_vxlan_update_sg_reg_state(pim, up, true /*reg_join*/); break; case PIM_REG_JOIN: break; case PIM_REG_PRUNE: if (!up->rpf.source_nexthop.interface) { if (PIM_DEBUG_TRACE) zlog_debug("%s: up %s RPF is not present", __PRETTY_FUNCTION__, up->sg_str); return 0; } pim_ifp = up->rpf.source_nexthop.interface->info; if (!pim_ifp) { if (PIM_DEBUG_TRACE) zlog_debug( "%s: Interface: %s is not configured for pim", __PRETTY_FUNCTION__, up->rpf.source_nexthop.interface->name); return 0; } up->reg_state = PIM_REG_JOIN_PENDING; pim_upstream_start_register_stop_timer(up, 1); if (((up->channel_oil->cc.lastused / 100) > pim->keep_alive_time) && (I_am_RP(pim_ifp->pim, up->sg.grp))) { if (PIM_DEBUG_TRACE) zlog_debug( "%s: Stop sending the register, because I am the RP and we haven't seen a packet in a while", __PRETTY_FUNCTION__); return 0; } pim_null_register_send(up); break; default: break; } return 0; } void pim_upstream_start_register_stop_timer(struct pim_upstream *up, int null_register) { uint32_t time; THREAD_TIMER_OFF(up->t_rs_timer); if (!null_register) { uint32_t lower = (0.5 * PIM_REGISTER_SUPPRESSION_PERIOD); uint32_t upper = (1.5 * PIM_REGISTER_SUPPRESSION_PERIOD); time = lower + (random() % (upper - lower + 1)) - PIM_REGISTER_PROBE_PERIOD; } else time = PIM_REGISTER_PROBE_PERIOD; if (PIM_DEBUG_TRACE) { zlog_debug( "%s: (S,G)=%s Starting upstream register stop timer %d", __PRETTY_FUNCTION__, up->sg_str, time); } thread_add_timer(router->master, pim_upstream_register_stop_timer, up, time, &up->t_rs_timer); } int pim_upstream_inherited_olist_decide(struct pim_instance *pim, struct pim_upstream *up) { struct interface *ifp; struct pim_ifchannel *ch, *starch; struct pim_upstream *starup = up->parent; int output_intf = 0; if (!up->rpf.source_nexthop.interface) if (PIM_DEBUG_TRACE) zlog_debug("%s: up %s RPF is not present", __PRETTY_FUNCTION__, up->sg_str); FOR_ALL_INTERFACES (pim->vrf, ifp) { if (!ifp->info) continue; ch = pim_ifchannel_find(ifp, &up->sg); if (starup) starch = pim_ifchannel_find(ifp, &starup->sg); else starch = NULL; if (!ch && !starch) continue; if (pim_upstream_evaluate_join_desired_interface(up, ch, starch)) { int flag = PIM_OIF_FLAG_PROTO_PIM; if (!ch) flag = PIM_OIF_FLAG_PROTO_STAR; pim_channel_add_oif(up->channel_oil, ifp, flag); output_intf++; } } return output_intf; } /* * For a given upstream, determine the inherited_olist * and apply it. * * inherited_olist(S,G,rpt) = * ( joins(*,*,RP(G)) (+) joins(*,G) (-) prunes(S,G,rpt) ) * (+) ( pim_include(*,G) (-) pim_exclude(S,G)) * (-) ( lost_assert(*,G) (+) lost_assert(S,G,rpt) ) * * inherited_olist(S,G) = * inherited_olist(S,G,rpt) (+) * joins(S,G) (+) pim_include(S,G) (-) lost_assert(S,G) * * return 1 if there are any output interfaces * return 0 if there are not any output interfaces */ int pim_upstream_inherited_olist(struct pim_instance *pim, struct pim_upstream *up) { int output_intf = pim_upstream_inherited_olist_decide(pim, up); /* * If we have output_intf switch state to Join and work like normal * If we don't have an output_intf that means we are probably a * switch on a stick so turn on forwarding to just accept the * incoming packets so we don't bother the other stuff! */ if (output_intf) pim_upstream_switch(pim, up, PIM_UPSTREAM_JOINED); else forward_on(up); return output_intf; } int pim_upstream_empty_inherited_olist(struct pim_upstream *up) { return pim_channel_oil_empty(up->channel_oil); } /* * When we have a new neighbor, * find upstreams that don't have their rpf_addr * set and see if the new neighbor allows * the join to be sent */ void pim_upstream_find_new_rpf(struct pim_instance *pim) { struct listnode *up_node; struct listnode *up_nextnode; struct pim_upstream *up; /* * Scan all (S,G) upstreams searching for RPF'(S,G)=neigh_addr */ for (ALL_LIST_ELEMENTS(pim->upstream_list, up_node, up_nextnode, up)) { if (up->upstream_addr.s_addr == INADDR_ANY) { if (PIM_DEBUG_TRACE) zlog_debug( "%s: RP not configured for Upstream %s", __PRETTY_FUNCTION__, up->sg_str); continue; } if (pim_rpf_addr_is_inaddr_any(&up->rpf)) { if (PIM_DEBUG_TRACE) zlog_debug( "%s: Upstream %s without a path to send join, checking", __PRETTY_FUNCTION__, up->sg_str); pim_rpf_update(pim, up, NULL); } } } unsigned int pim_upstream_hash_key(const void *arg) { const struct pim_upstream *up = arg; return jhash_2words(up->sg.src.s_addr, up->sg.grp.s_addr, 0); } void pim_upstream_terminate(struct pim_instance *pim) { struct pim_upstream *up; if (pim->upstream_list) { while (pim->upstream_list->count) { up = listnode_head(pim->upstream_list); pim_upstream_del(pim, up, __PRETTY_FUNCTION__); } list_delete(&pim->upstream_list); } if (pim->upstream_hash) hash_free(pim->upstream_hash); pim->upstream_hash = NULL; if (pim->upstream_sg_wheel) wheel_delete(pim->upstream_sg_wheel); pim->upstream_sg_wheel = NULL; } bool pim_upstream_equal(const void *arg1, const void *arg2) { const struct pim_upstream *up1 = (const struct pim_upstream *)arg1; const struct pim_upstream *up2 = (const struct pim_upstream *)arg2; if ((up1->sg.grp.s_addr == up2->sg.grp.s_addr) && (up1->sg.src.s_addr == up2->sg.src.s_addr)) return true; return false; } /* rfc4601:section-4.2:"Data Packet Forwarding Rules" defines * the cases where kat has to be restarted on rxing traffic - * * if( DirectlyConnected(S) == true AND iif == RPF_interface(S) ) { * set KeepaliveTimer(S,G) to Keepalive_Period * # Note: a register state transition or UpstreamJPState(S,G) * # transition may happen as a result of restarting * # KeepaliveTimer, and must be dealt with here. * } * if( iif == RPF_interface(S) AND UpstreamJPState(S,G) == Joined AND * inherited_olist(S,G) != NULL ) { * set KeepaliveTimer(S,G) to Keepalive_Period * } */ static bool pim_upstream_kat_start_ok(struct pim_upstream *up) { struct pim_instance *pim = up->channel_oil->pim; /* "iif == RPF_interface(S)" check has to be done by the kernel or hw * so we will skip that here */ if (up->rpf.source_nexthop.interface && pim_if_connected_to_source(up->rpf.source_nexthop.interface, up->sg.src)) { return true; } if ((up->join_state == PIM_UPSTREAM_JOINED) && !pim_upstream_empty_inherited_olist(up)) { /* XXX: I have added this RP check just for 3.2 and it's a * digression from * what rfc-4601 says. Till now we were only running KAT on FHR * and RP and * there is some angst around making the change to run it all * routers that * maintain the (S, G) state. This is tracked via CM-13601 and * MUST be * removed to handle spt turn-arounds correctly in a 3-tier clos */ if (I_am_RP(pim, up->sg.grp)) return true; } return false; } /* * Code to check and see if we've received packets on a S,G mroute * and if so to set the SPT bit appropriately */ static void pim_upstream_sg_running(void *arg) { struct pim_upstream *up = (struct pim_upstream *)arg; struct pim_instance *pim = up->channel_oil->pim; // No packet can have arrived here if this is the case if (!up->channel_oil->installed) { if (PIM_DEBUG_TRACE) zlog_debug("%s: %s%s is not installed in mroute", __PRETTY_FUNCTION__, up->sg_str, pim->vrf->name); return; } /* * This is a bit of a hack * We've noted that we should rescan but * we've missed the window for doing so in * pim_zebra.c for some reason. I am * only doing this at this point in time * to get us up and working for the moment */ if (up->channel_oil->oil_inherited_rescan) { if (PIM_DEBUG_TRACE) zlog_debug( "%s: Handling unscanned inherited_olist for %s[%s]", __PRETTY_FUNCTION__, up->sg_str, pim->vrf->name); pim_upstream_inherited_olist_decide(pim, up); up->channel_oil->oil_inherited_rescan = 0; } pim_mroute_update_counters(up->channel_oil); // Have we seen packets? if ((up->channel_oil->cc.oldpktcnt >= up->channel_oil->cc.pktcnt) && (up->channel_oil->cc.lastused / 100 > 30)) { if (PIM_DEBUG_TRACE) { zlog_debug( "%s[%s]: %s old packet count is equal or lastused is greater than 30, (%ld,%ld,%lld)", __PRETTY_FUNCTION__, up->sg_str, pim->vrf->name, up->channel_oil->cc.oldpktcnt, up->channel_oil->cc.pktcnt, up->channel_oil->cc.lastused / 100); } return; } if (pim_upstream_kat_start_ok(up)) { /* Add a source reference to the stream if * one doesn't already exist */ if (!PIM_UPSTREAM_FLAG_TEST_SRC_STREAM(up->flags)) { if (PIM_DEBUG_TRACE) zlog_debug( "source reference created on kat restart %s[%s]", up->sg_str, pim->vrf->name); pim_upstream_ref(up, PIM_UPSTREAM_FLAG_MASK_SRC_STREAM, __PRETTY_FUNCTION__); PIM_UPSTREAM_FLAG_SET_SRC_STREAM(up->flags); pim_upstream_fhr_kat_start(up); } pim_upstream_keep_alive_timer_start(up, pim->keep_alive_time); } else if (PIM_UPSTREAM_FLAG_TEST_SRC_LHR(up->flags)) pim_upstream_keep_alive_timer_start(up, pim->keep_alive_time); if ((up->sptbit != PIM_UPSTREAM_SPTBIT_TRUE) && (up->rpf.source_nexthop.interface)) { pim_upstream_set_sptbit(up, up->rpf.source_nexthop.interface); } return; } void pim_upstream_add_lhr_star_pimreg(struct pim_instance *pim) { struct pim_upstream *up; struct listnode *node; for (ALL_LIST_ELEMENTS_RO(pim->upstream_list, node, up)) { if (up->sg.src.s_addr != INADDR_ANY) continue; if (!PIM_UPSTREAM_FLAG_TEST_SRC_IGMP(up->flags)) continue; pim_channel_add_oif(up->channel_oil, pim->regiface, PIM_OIF_FLAG_PROTO_IGMP); } } void pim_upstream_spt_prefix_list_update(struct pim_instance *pim, struct prefix_list *pl) { const char *pname = prefix_list_name(pl); if (pim->spt.plist && strcmp(pim->spt.plist, pname) == 0) { pim_upstream_remove_lhr_star_pimreg(pim, pname); } } /* * nlist -> The new prefix list * * Per Group Application of pimreg to the OIL * If the prefix list tells us DENY then * we need to Switchover to SPT immediate * so add the pimreg. * If the prefix list tells us to ACCEPT than * we need to Never do the SPT so remove * the interface * */ void pim_upstream_remove_lhr_star_pimreg(struct pim_instance *pim, const char *nlist) { struct pim_upstream *up; struct listnode *node; struct prefix_list *np; struct prefix g; enum prefix_list_type apply_new; np = prefix_list_lookup(AFI_IP, nlist); g.family = AF_INET; g.prefixlen = IPV4_MAX_PREFIXLEN; for (ALL_LIST_ELEMENTS_RO(pim->upstream_list, node, up)) { if (up->sg.src.s_addr != INADDR_ANY) continue; if (!PIM_UPSTREAM_FLAG_TEST_SRC_IGMP(up->flags)) continue; if (!nlist) { pim_channel_del_oif(up->channel_oil, pim->regiface, PIM_OIF_FLAG_PROTO_IGMP); continue; } g.u.prefix4 = up->sg.grp; apply_new = prefix_list_apply(np, &g); if (apply_new == PREFIX_DENY) pim_channel_add_oif(up->channel_oil, pim->regiface, PIM_OIF_FLAG_PROTO_IGMP); else pim_channel_del_oif(up->channel_oil, pim->regiface, PIM_OIF_FLAG_PROTO_IGMP); } } void pim_upstream_init(struct pim_instance *pim) { char name[64]; snprintf(name, 64, "PIM %s Timer Wheel", pim->vrf->name); pim->upstream_sg_wheel = wheel_init(router->master, 31000, 100, pim_upstream_hash_key, pim_upstream_sg_running, name); snprintf(name, 64, "PIM %s Upstream Hash", pim->vrf->name); pim->upstream_hash = hash_create_size(8192, pim_upstream_hash_key, pim_upstream_equal, name); pim->upstream_list = list_new(); pim->upstream_list->cmp = pim_upstream_compare; } frr-7.2.1/pimd/pim_upstream.h0000644000000000000000000003637213610377563013036 00000000000000/* * PIM for Quagga * Copyright (C) 2008 Everton da Silva Marques * * This program is free software; you can redistribute it and/or modify * it under the terms of the 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; see the file COPYING; if not, write to the Free Software * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA */ #ifndef PIM_UPSTREAM_H #define PIM_UPSTREAM_H #include #include #include "plist.h" #include #include "pim_str.h" #include "pim_ifchannel.h" #define PIM_UPSTREAM_FLAG_MASK_DR_JOIN_DESIRED (1 << 0) #define PIM_UPSTREAM_FLAG_MASK_DR_JOIN_DESIRED_UPDATED (1 << 1) #define PIM_UPSTREAM_FLAG_MASK_FHR (1 << 2) #define PIM_UPSTREAM_FLAG_MASK_SRC_IGMP (1 << 3) #define PIM_UPSTREAM_FLAG_MASK_SRC_PIM (1 << 4) #define PIM_UPSTREAM_FLAG_MASK_SRC_STREAM (1 << 5) #define PIM_UPSTREAM_FLAG_MASK_SRC_MSDP (1 << 6) #define PIM_UPSTREAM_FLAG_MASK_SEND_SG_RPT_PRUNE (1 << 7) #define PIM_UPSTREAM_FLAG_MASK_SRC_LHR (1 << 8) /* In the case of pim vxlan we prime the pump by registering the * vxlan source and keeping the SPT (FHR-RP) alive by sending periodic * NULL registers. So we need to prevent KAT expiry because of the * lack of BUM traffic. */ #define PIM_UPSTREAM_FLAG_MASK_DISABLE_KAT_EXPIRY (1 << 9) /* for pim vxlan we need to pin the IIF to lo or MLAG-ISL on the * originating VTEP. This flag allows that by setting IIF to the * value specified and preventing next-hop-tracking on the entry */ #define PIM_UPSTREAM_FLAG_MASK_STATIC_IIF (1 << 10) #define PIM_UPSTREAM_FLAG_MASK_ALLOW_IIF_IN_OIL (1 << 11) /* Disable pimreg encasulation for a flow */ #define PIM_UPSTREAM_FLAG_MASK_NO_PIMREG_DATA (1 << 12) /* For some MDTs we need to register the router as a source even * if the not DR or directly connected on the IIF. This is typically * needed on a VxLAN-AA (MLAG) setup. */ #define PIM_UPSTREAM_FLAG_MASK_FORCE_PIMREG (1 << 13) /* VxLAN origination mroute - SG was registered by EVPN where S is the * local VTEP IP and G is the BUM multicast group address */ #define PIM_UPSTREAM_FLAG_MASK_SRC_VXLAN_ORIG (1 << 14) /* VxLAN termination mroute - *G entry where G is the BUM multicast group * address */ #define PIM_UPSTREAM_FLAG_MASK_SRC_VXLAN_TERM (1 << 15) /* MLAG mroute - synced to the MLAG peer and subject to DF (designated * forwarder) election */ #define PIM_UPSTREAM_FLAG_MASK_MLAG_VXLAN (1 << 16) /* MLAG mroute that lost the DF election with peer and is installed in * a dormant state i.e. MLAG OIFs are removed from the MFC. * In most cases the OIL is empty (but not not always) simply * blackholing the traffic pulled down to the LHR. */ #define PIM_UPSTREAM_FLAG_MASK_MLAG_NON_DF (1 << 17) /* * We are creating a non-joined upstream data structure * for this S,G as that we want to have a channel oil * associated with an upstream */ #define PIM_UPSTREAM_FLAG_MASK_SRC_NOCACHE (1 << 19) #define PIM_UPSTREAM_FLAG_ALL 0xFFFFFFFF #define PIM_UPSTREAM_FLAG_TEST_DR_JOIN_DESIRED(flags) ((flags) & PIM_UPSTREAM_FLAG_MASK_DR_JOIN_DESIRED) #define PIM_UPSTREAM_FLAG_TEST_DR_JOIN_DESIRED_UPDATED(flags) ((flags) & PIM_UPSTREAM_FLAG_MASK_DR_JOIN_DESIRED_UPDATED) #define PIM_UPSTREAM_FLAG_TEST_FHR(flags) ((flags) & PIM_UPSTREAM_FLAG_MASK_FHR) #define PIM_UPSTREAM_FLAG_TEST_SRC_IGMP(flags) ((flags) & PIM_UPSTREAM_FLAG_MASK_SRC_IGMP) #define PIM_UPSTREAM_FLAG_TEST_SRC_PIM(flags) ((flags) & PIM_UPSTREAM_FLAG_MASK_SRC_PIM) #define PIM_UPSTREAM_FLAG_TEST_SRC_STREAM(flags) ((flags) & PIM_UPSTREAM_FLAG_MASK_SRC_STREAM) #define PIM_UPSTREAM_FLAG_TEST_SRC_MSDP(flags) ((flags) & PIM_UPSTREAM_FLAG_MASK_SRC_MSDP) #define PIM_UPSTREAM_FLAG_TEST_SEND_SG_RPT_PRUNE(flags) ((flags) & PIM_UPSTREAM_FLAG_MASK_SEND_SG_RPT_PRUNE) #define PIM_UPSTREAM_FLAG_TEST_SRC_LHR(flags) ((flags) & PIM_UPSTREAM_FLAG_MASK_SRC_LHR) #define PIM_UPSTREAM_FLAG_TEST_DISABLE_KAT_EXPIRY(flags) ((flags) & PIM_UPSTREAM_FLAG_MASK_DISABLE_KAT_EXPIRY) #define PIM_UPSTREAM_FLAG_TEST_STATIC_IIF(flags) ((flags) & PIM_UPSTREAM_FLAG_MASK_STATIC_IIF) #define PIM_UPSTREAM_FLAG_TEST_ALLOW_IIF_IN_OIL(flags) ((flags) & PIM_UPSTREAM_FLAG_MASK_ALLOW_IIF_IN_OIL) #define PIM_UPSTREAM_FLAG_TEST_NO_PIMREG_DATA(flags) ((flags) & PIM_UPSTREAM_FLAG_MASK_NO_PIMREG_DATA) #define PIM_UPSTREAM_FLAG_TEST_FORCE_PIMREG(flags) ((flags) & PIM_UPSTREAM_FLAG_MASK_FORCE_PIMREG) #define PIM_UPSTREAM_FLAG_TEST_SRC_VXLAN_ORIG(flags) ((flags) & PIM_UPSTREAM_FLAG_MASK_SRC_VXLAN_ORIG) #define PIM_UPSTREAM_FLAG_TEST_SRC_VXLAN_TERM(flags) ((flags) & PIM_UPSTREAM_FLAG_MASK_SRC_VXLAN_TERM) #define PIM_UPSTREAM_FLAG_TEST_SRC_VXLAN(flags) ((flags) & (PIM_UPSTREAM_FLAG_MASK_SRC_VXLAN_ORIG | PIM_UPSTREAM_FLAG_MASK_SRC_VXLAN_TERM)) #define PIM_UPSTREAM_FLAG_TEST_MLAG_VXLAN(flags) ((flags) & PIM_UPSTREAM_FLAG_MASK_MLAG_VXLAN) #define PIM_UPSTREAM_FLAG_TEST_MLAG_NON_DF(flags) ((flags) & PIM_UPSTREAM_FLAG_MASK_MLAG_NON_DF) #define PIM_UPSTREAM_FLAG_TEST_SRC_NOCACHE(flags) ((flags) &PIM_UPSTREAM_FLAG_MASK_SRC_NOCACHE) #define PIM_UPSTREAM_FLAG_SET_DR_JOIN_DESIRED(flags) ((flags) |= PIM_UPSTREAM_FLAG_MASK_DR_JOIN_DESIRED) #define PIM_UPSTREAM_FLAG_SET_DR_JOIN_DESIRED_UPDATED(flags) ((flags) |= PIM_UPSTREAM_FLAG_MASK_DR_JOIN_DESIRED_UPDATED) #define PIM_UPSTREAM_FLAG_SET_FHR(flags) ((flags) |= PIM_UPSTREAM_FLAG_MASK_FHR) #define PIM_UPSTREAM_FLAG_SET_SRC_IGMP(flags) ((flags) |= PIM_UPSTREAM_FLAG_MASK_SRC_IGMP) #define PIM_UPSTREAM_FLAG_SET_SRC_PIM(flags) ((flags) |= PIM_UPSTREAM_FLAG_MASK_SRC_PIM) #define PIM_UPSTREAM_FLAG_SET_SRC_STREAM(flags) ((flags) |= PIM_UPSTREAM_FLAG_MASK_SRC_STREAM) #define PIM_UPSTREAM_FLAG_SET_SRC_MSDP(flags) ((flags) |= PIM_UPSTREAM_FLAG_MASK_SRC_MSDP) #define PIM_UPSTREAM_FLAG_SET_SEND_SG_RPT_PRUNE(flags) ((flags) |= PIM_UPSTREAM_FLAG_MASK_SEND_SG_RPT_PRUNE) #define PIM_UPSTREAM_FLAG_SET_SRC_LHR(flags) ((flags) |= PIM_UPSTREAM_FLAG_MASK_SRC_LHR) #define PIM_UPSTREAM_FLAG_SET_DISABLE_KAT_EXPIRY(flags) ((flags) |= PIM_UPSTREAM_FLAG_MASK_DISABLE_KAT_EXPIRY) #define PIM_UPSTREAM_FLAG_SET_STATIC_IIF(flags) ((flags) |= PIM_UPSTREAM_FLAG_MASK_STATIC_IIF) #define PIM_UPSTREAM_FLAG_SET_ALLOW_IIF_IN_OIL(flags) ((flags) |= PIM_UPSTREAM_FLAG_MASK_ALLOW_IIF_IN_OIL) #define PIM_UPSTREAM_FLAG_SET_NO_PIMREG_DATA(flags) ((flags) |= PIM_UPSTREAM_FLAG_MASK_NO_PIMREG_DATA) #define PIM_UPSTREAM_FLAG_SET_FORCE_PIMREG(flags) ((flags) |= PIM_UPSTREAM_FLAG_MASK_FORCE_PIMREG) #define PIM_UPSTREAM_FLAG_SET_SRC_VXLAN_ORIG(flags) ((flags) |= PIM_UPSTREAM_FLAG_MASK_SRC_VXLAN_ORIG) #define PIM_UPSTREAM_FLAG_SET_SRC_VXLAN_TERM(flags) ((flags) |= PIM_UPSTREAM_FLAG_MASK_SRC_VXLAN_TERM) #define PIM_UPSTREAM_FLAG_SET_MLAG_VXLAN(flags) ((flags) |= PIM_UPSTREAM_FLAG_MASK_MLAG_VXLAN) #define PIM_UPSTREAM_FLAG_SET_MLAG_NON_DF(flags) ((flags) |= PIM_UPSTREAM_FLAG_MASK_MLAG_NON_DF) #define PIM_UPSTREAM_FLAG_UNSET_DR_JOIN_DESIRED(flags) ((flags) &= ~PIM_UPSTREAM_FLAG_MASK_DR_JOIN_DESIRED) #define PIM_UPSTREAM_FLAG_UNSET_DR_JOIN_DESIRED_UPDATED(flags) ((flags) &= ~PIM_UPSTREAM_FLAG_MASK_DR_JOIN_DESIRED_UPDATED) #define PIM_UPSTREAM_FLAG_UNSET_FHR(flags) ((flags) &= ~PIM_UPSTREAM_FLAG_MASK_FHR) #define PIM_UPSTREAM_FLAG_UNSET_SRC_IGMP(flags) ((flags) &= ~PIM_UPSTREAM_FLAG_MASK_SRC_IGMP) #define PIM_UPSTREAM_FLAG_UNSET_SRC_PIM(flags) ((flags) &= ~PIM_UPSTREAM_FLAG_MASK_SRC_PIM) #define PIM_UPSTREAM_FLAG_UNSET_SRC_STREAM(flags) ((flags) &= ~PIM_UPSTREAM_FLAG_MASK_SRC_STREAM) #define PIM_UPSTREAM_FLAG_UNSET_SRC_MSDP(flags) ((flags) &= ~PIM_UPSTREAM_FLAG_MASK_SRC_MSDP) #define PIM_UPSTREAM_FLAG_UNSET_SEND_SG_RPT_PRUNE(flags) ((flags) &= ~PIM_UPSTREAM_FLAG_MASK_SEND_SG_RPT_PRUNE) #define PIM_UPSTREAM_FLAG_UNSET_SRC_LHR(flags) ((flags) &= ~PIM_UPSTREAM_FLAG_MASK_SRC_LHR) #define PIM_UPSTREAM_FLAG_UNSET_DISABLE_KAT_EXPIRY(flags) ((flags) &= ~PIM_UPSTREAM_FLAG_MASK_DISABLE_KAT_EXPIRY) #define PIM_UPSTREAM_FLAG_UNSET_STATIC_IIF(flags) ((flags) &= ~PIM_UPSTREAM_FLAG_MASK_STATIC_IIF) #define PIM_UPSTREAM_FLAG_UNSET_ALLOW_IIF_IN_OIL(flags) ((flags) &= ~PIM_UPSTREAM_FLAG_MASK_ALLOW_IIF_IN_OIL) #define PIM_UPSTREAM_FLAG_UNSET_NO_PIMREG_DATA(flags) ((flags) &= ~PIM_UPSTREAM_FLAG_MASK_NO_PIMREG_DATA) #define PIM_UPSTREAM_FLAG_UNSET_FORCE_PIMREG(flags) ((flags) &= ~PIM_UPSTREAM_FLAG_MASK_FORCE_PIMREG) #define PIM_UPSTREAM_FLAG_UNSET_SRC_VXLAN_ORIG(flags) ((flags) &= ~PIM_UPSTREAM_FLAG_MASK_SRC_VXLAN_ORIG) #define PIM_UPSTREAM_FLAG_UNSET_SRC_VXLAN_TERM(flags) ((flags) &= ~PIM_UPSTREAM_FLAG_MASK_SRC_VXLAN_TERM) #define PIM_UPSTREAM_FLAG_UNSET_MLAG_VXLAN(flags) ((flags) &= ~PIM_UPSTREAM_FLAG_MASK_MLAG_VXLAN) #define PIM_UPSTREAM_FLAG_UNSET_MLAG_NON_DF(flags) ((flags) &= ~PIM_UPSTREAM_FLAG_MASK_MLAG_NON_DF) #define PIM_UPSTREAM_FLAG_UNSET_SRC_NOCACHE(flags) ((flags) &= ~PIM_UPSTREAM_FLAG_MASK_SRC_NOCACHE) enum pim_upstream_state { PIM_UPSTREAM_NOTJOINED, PIM_UPSTREAM_JOINED, }; enum pim_reg_state { PIM_REG_NOINFO, PIM_REG_JOIN, PIM_REG_JOIN_PENDING, PIM_REG_PRUNE, }; enum pim_upstream_sptbit { PIM_UPSTREAM_SPTBIT_FALSE, PIM_UPSTREAM_SPTBIT_TRUE }; /* Upstream (S,G) channel in Joined state (S,G) in the "Not Joined" state is not represented See RFC 4601: 4.5.7. Sending (S,G) Join/Prune Message upstream_addr : Who we are talking to. For (*, G), upstream_addr is RP address or INADDR_ANY(if RP not configured) For (S, G), upstream_addr is source address rpf: contains the nexthop information to whom we are talking to. join_state: JOINED/NOTJOINED In the case when FRR receives IGMP/PIM (*, G) join for group G and RP is not configured, then create a pim_upstream with the below information. pim_upstream->upstream address: INADDR_ANY pim_upstream->rpf: Unknown pim_upstream->state: NOTJOINED When a new RP gets configured for G, find the corresponding pim upstream (*,G) entries and update the upstream address as new RP address if it the better one for the group G. When RP becomes reachable, populate the nexthop information in pim_upstream->rpf and update the state to JOINED. */ struct pim_upstream { struct pim_upstream *parent; struct in_addr upstream_addr; /* Who we are talking to */ struct in_addr upstream_register; /*Who we received a register from*/ struct prefix_sg sg; /* (S,G) group key */ char sg_str[PIM_SG_LEN]; uint32_t flags; struct channel_oil *channel_oil; struct list *sources; struct list *ifchannels; enum pim_upstream_state join_state; enum pim_reg_state reg_state; enum pim_upstream_sptbit sptbit; int ref_count; struct pim_rpf rpf; struct thread *t_join_timer; /* * RST(S,G) */ struct thread *t_rs_timer; #define PIM_REGISTER_SUPPRESSION_PERIOD (60) #define PIM_REGISTER_PROBE_PERIOD (5) /* * KAT(S,G) */ struct thread *t_ka_timer; #define PIM_KEEPALIVE_PERIOD (210) #define PIM_RP_KEEPALIVE_PERIOD \ (3 * router->register_suppress_time + router->register_probe_time) /* on the RP we restart a timer to indicate if registers are being rxed * for * SG. This is needed by MSDP to determine its local SA cache */ struct thread *t_msdp_reg_timer; #define PIM_MSDP_REG_RXED_PERIOD (3 * (1.5 * router->register_suppress_time)) int64_t state_transition; /* Record current state uptime */ }; struct pim_upstream *pim_upstream_find(struct pim_instance *pim, struct prefix_sg *sg); struct pim_upstream *pim_upstream_find_or_add(struct prefix_sg *sg, struct interface *ifp, int flags, const char *name); struct pim_upstream *pim_upstream_add(struct pim_instance *pim, struct prefix_sg *sg, struct interface *ifp, int flags, const char *name, struct pim_ifchannel *ch); void pim_upstream_ref(struct pim_upstream *up, int flags, const char *name); struct pim_upstream *pim_upstream_del(struct pim_instance *pim, struct pim_upstream *up, const char *name); int pim_upstream_evaluate_join_desired(struct pim_instance *pim, struct pim_upstream *up); int pim_upstream_evaluate_join_desired_interface(struct pim_upstream *up, struct pim_ifchannel *ch, struct pim_ifchannel *starch); void pim_upstream_update_join_desired(struct pim_instance *pim, struct pim_upstream *up); void pim_upstream_join_suppress(struct pim_upstream *up, struct in_addr rpf_addr, int holdtime); void pim_upstream_join_timer_decrease_to_t_override(const char *debug_label, struct pim_upstream *up); void pim_upstream_join_timer_restart(struct pim_upstream *up, struct pim_rpf *old); void pim_upstream_rpf_genid_changed(struct pim_instance *pim, struct in_addr neigh_addr); void pim_upstream_rpf_interface_changed(struct pim_upstream *up, struct interface *old_rpf_ifp); void pim_upstream_update_could_assert(struct pim_upstream *up); void pim_upstream_update_my_assert_metric(struct pim_upstream *up); void pim_upstream_keep_alive_timer_start(struct pim_upstream *up, uint32_t time); int pim_upstream_switch_to_spt_desired(struct pim_instance *pim, struct prefix_sg *sg); #define SwitchToSptDesired(pim, sg) pim_upstream_switch_to_spt_desired (pim, sg) int pim_upstream_is_sg_rpt(struct pim_upstream *up); void pim_upstream_set_sptbit(struct pim_upstream *up, struct interface *incoming); void pim_upstream_start_register_stop_timer(struct pim_upstream *up, int null_register); void pim_upstream_send_join(struct pim_upstream *up); void pim_upstream_switch(struct pim_instance *pim, struct pim_upstream *up, enum pim_upstream_state new_state); const char *pim_upstream_state2str(enum pim_upstream_state join_state); #define PIM_REG_STATE_STR_LEN 12 const char *pim_reg_state2str(enum pim_reg_state state, char *state_str, size_t state_str_len); int pim_upstream_inherited_olist_decide(struct pim_instance *pim, struct pim_upstream *up); int pim_upstream_inherited_olist(struct pim_instance *pim, struct pim_upstream *up); int pim_upstream_empty_inherited_olist(struct pim_upstream *up); void pim_upstream_find_new_rpf(struct pim_instance *pim); void pim_upstream_msdp_reg_timer_start(struct pim_upstream *up); void pim_upstream_init(struct pim_instance *pim); void pim_upstream_terminate(struct pim_instance *pim); void join_timer_start(struct pim_upstream *up); int pim_upstream_compare(void *arg1, void *arg2); void pim_upstream_register_reevaluate(struct pim_instance *pim); void pim_upstream_add_lhr_star_pimreg(struct pim_instance *pim); void pim_upstream_remove_lhr_star_pimreg(struct pim_instance *pim, const char *nlist); void pim_upstream_spt_prefix_list_update(struct pim_instance *pim, struct prefix_list *pl); unsigned int pim_upstream_hash_key(const void *arg); bool pim_upstream_equal(const void *arg1, const void *arg2); struct pim_upstream *pim_upstream_keep_alive_timer_proc( struct pim_upstream *up); void pim_upstream_fill_static_iif(struct pim_upstream *up, struct interface *incoming); #endif /* PIM_UPSTREAM_H */ frr-7.2.1/pimd/pim_util.c0000644000000000000000000000666713610377563012152 00000000000000/* * PIM for Quagga * Copyright (C) 2008 Everton da Silva Marques * * This program is free software; you can redistribute it and/or modify * it under the terms of the 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; see the file COPYING; if not, write to the Free Software * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA */ #include #include "log.h" #include "prefix.h" #include "plist.h" #include "pim_util.h" /* RFC 3376: 4.1.7. QQIC (Querier's Query Interval Code) If QQIC < 128, QQI = QQIC If QQIC >= 128, QQI = (mant | 0x10) << (exp + 3) 0 1 2 3 4 5 6 7 +-+-+-+-+-+-+-+-+ |1| exp | mant | +-+-+-+-+-+-+-+-+ Since exp=0..7 then (exp+3)=3..10, then QQI has one of the following bit patterns: exp=0: QQI = 0000.0000.1MMM.M000 exp=1: QQI = 0000.0001.MMMM.0000 ... exp=6: QQI = 001M.MMM0.0000.0000 exp=7: QQI = 01MM.MM00.0000.0000 --------- --------- 0x4 0x0 0x0 0x0 */ uint8_t igmp_msg_encode16to8(uint16_t value) { uint8_t code; if (value < 128) { code = value; } else { uint16_t mask = 0x4000; uint8_t exp; uint16_t mant; for (exp = 7; exp > 0; --exp) { if (mask & value) break; mask >>= 1; } mant = 0x000F & (value >> (exp + 3)); code = ((uint8_t)1 << 7) | ((uint8_t)exp << 4) | (uint8_t)mant; } return code; } /* RFC 3376: 4.1.7. QQIC (Querier's Query Interval Code) If QQIC < 128, QQI = QQIC If QQIC >= 128, QQI = (mant | 0x10) << (exp + 3) 0 1 2 3 4 5 6 7 +-+-+-+-+-+-+-+-+ |1| exp | mant | +-+-+-+-+-+-+-+-+ */ uint16_t igmp_msg_decode8to16(uint8_t code) { uint16_t value; if (code < 128) { value = code; } else { uint16_t mant = (code & 0x0F); uint8_t exp = (code & 0x70) >> 4; value = (mant | 0x10) << (exp + 3); } return value; } void pim_pkt_dump(const char *label, const uint8_t *buf, int size) { zlog_debug("%s: pkt dump size=%d", label, size); zlog_hexdump(buf, size); } int pim_is_group_224_0_0_0_24(struct in_addr group_addr) { static int first = 1; static struct prefix group_224; struct prefix group; if (first) { if (!str2prefix("224.0.0.0/24", &group_224)) return 0; first = 0; } group.family = AF_INET; group.u.prefix4 = group_addr; group.prefixlen = IPV4_MAX_PREFIXLEN; return prefix_match(&group_224, &group); } int pim_is_group_224_4(struct in_addr group_addr) { static int first = 1; static struct prefix group_all; struct prefix group; if (first) { if (!str2prefix("224.0.0.0/4", &group_all)) return 0; first = 0; } group.family = AF_INET; group.u.prefix4 = group_addr; group.prefixlen = 32; return prefix_match(&group_all, &group); } bool pim_is_group_filtered(struct pim_interface *pim_ifp, struct in_addr *grp) { struct prefix grp_pfx; struct prefix_list *pl; if (!pim_ifp->boundary_oil_plist) return false; grp_pfx.family = AF_INET; grp_pfx.prefixlen = 32; grp_pfx.u.prefix4 = *grp; pl = prefix_list_lookup(AFI_IP, pim_ifp->boundary_oil_plist); return pl ? prefix_list_apply(pl, &grp_pfx) == PREFIX_DENY : false; } frr-7.2.1/pimd/pim_util.h0000644000000000000000000000246113610377563012143 00000000000000/* * PIM for Quagga * Copyright (C) 2008 Everton da Silva Marques * * This program is free software; you can redistribute it and/or modify * it under the terms of the 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; see the file COPYING; if not, write to the Free Software * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA */ #ifndef PIM_UTIL_H #define PIM_UTIL_H #include #include #include "checksum.h" #include "pimd.h" #include "pim_iface.h" uint8_t igmp_msg_encode16to8(uint16_t value); uint16_t igmp_msg_decode8to16(uint8_t code); void pim_pkt_dump(const char *label, const uint8_t *buf, int size); int pim_is_group_224_0_0_0_24(struct in_addr group_addr); int pim_is_group_224_4(struct in_addr group_addr); bool pim_is_group_filtered(struct pim_interface *pim_ifp, struct in_addr *grp); #endif /* PIM_UTIL_H */ frr-7.2.1/pimd/pim_version.c0000644000000000000000000000161413610377563012645 00000000000000/* * PIM for Quagga * Copyright (C) 2008 Everton da Silva Marques * * This program is free software; you can redistribute it and/or modify * it under the terms of the 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; see the file COPYING; if not, write to the Free Software * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA */ #include #include "pim_version.h" const char *const PIMD_VERSION = PIMD_VERSION_STR; frr-7.2.1/pimd/pim_version.h0000644000000000000000000000166613610377563012661 00000000000000/* * PIM for Quagga * Copyright (C) 2008 Everton da Silva Marques * * This program is free software; you can redistribute it and/or modify * it under the terms of the 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; see the file COPYING; if not, write to the Free Software * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA */ #ifndef PIM_VERSION_H #define PIM_VERSION_H #define PIMD_VERSION_STR "0.166" const char *const PIMD_VERSION; #endif /* PIM_VERSION_H */ frr-7.2.1/pimd/pim_vty.c0000644000000000000000000002312313610377563012001 00000000000000/* * PIM for Quagga * Copyright (C) 2008 Everton da Silva Marques * * This program is free software; you can redistribute it and/or modify * it under the terms of the 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; see the file COPYING; if not, write to the Free Software * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA */ #include #include "if.h" #include "linklist.h" #include "prefix.h" #include "vty.h" #include "vrf.h" #include "plist.h" #include "pimd.h" #include "pim_vty.h" #include "pim_iface.h" #include "pim_cmd.h" #include "pim_str.h" #include "pim_ssmpingd.h" #include "pim_pim.h" #include "pim_oil.h" #include "pim_static.h" #include "pim_rp.h" #include "pim_msdp.h" #include "pim_ssm.h" #include "pim_bfd.h" #include "pim_bsm.h" #include "pim_vxlan.h" int pim_debug_config_write(struct vty *vty) { int writes = 0; if (PIM_DEBUG_MSDP_EVENTS) { vty_out(vty, "debug msdp events\n"); ++writes; } if (PIM_DEBUG_MSDP_PACKETS) { vty_out(vty, "debug msdp packets\n"); ++writes; } if (PIM_DEBUG_MSDP_INTERNAL) { vty_out(vty, "debug msdp internal\n"); ++writes; } if (PIM_DEBUG_IGMP_EVENTS) { vty_out(vty, "debug igmp events\n"); ++writes; } if (PIM_DEBUG_IGMP_PACKETS) { vty_out(vty, "debug igmp packets\n"); ++writes; } if (PIM_DEBUG_IGMP_TRACE) { vty_out(vty, "debug igmp trace\n"); ++writes; } if (PIM_DEBUG_MROUTE) { vty_out(vty, "debug mroute\n"); ++writes; } if (PIM_DEBUG_MTRACE) { vty_out(vty, "debug mtrace\n"); ++writes; } if (PIM_DEBUG_MROUTE_DETAIL_ONLY) { vty_out(vty, "debug mroute detail\n"); ++writes; } if (PIM_DEBUG_PIM_EVENTS) { vty_out(vty, "debug pim events\n"); ++writes; } if (PIM_DEBUG_PIM_PACKETS) { vty_out(vty, "debug pim packets\n"); ++writes; } if (PIM_DEBUG_PIM_PACKETDUMP_SEND) { vty_out(vty, "debug pim packet-dump send\n"); ++writes; } if (PIM_DEBUG_PIM_PACKETDUMP_RECV) { vty_out(vty, "debug pim packet-dump receive\n"); ++writes; } if (PIM_DEBUG_PIM_TRACE) { vty_out(vty, "debug pim trace\n"); ++writes; } if (PIM_DEBUG_PIM_TRACE_DETAIL_ONLY) { vty_out(vty, "debug pim trace detail\n"); ++writes; } if (PIM_DEBUG_ZEBRA) { vty_out(vty, "debug pim zebra\n"); ++writes; } if (PIM_DEBUG_BSM) { vty_out(vty, "debug pim bsm\n"); ++writes; } if (PIM_DEBUG_VXLAN) { vty_out(vty, "debug pim vxlan\n"); ++writes; } if (PIM_DEBUG_SSMPINGD) { vty_out(vty, "debug ssmpingd\n"); ++writes; } if (PIM_DEBUG_PIM_HELLO) { vty_out(vty, "debug pim packets hello\n"); ++writes; } if (PIM_DEBUG_PIM_J_P) { vty_out(vty, "debug pim packets joins\n"); ++writes; } if (PIM_DEBUG_PIM_REG) { vty_out(vty, "debug pim packets register\n"); ++writes; } if (PIM_DEBUG_STATIC) { vty_out(vty, "debug pim static\n"); ++writes; } if (PIM_DEBUG_PIM_NHT) { vty_out(vty, "debug pim nht\n"); ++writes; } return writes; } int pim_global_config_write_worker(struct pim_instance *pim, struct vty *vty) { int writes = 0; struct pim_ssm *ssm = pim->ssm_info; char spaces[10]; if (pim->vrf_id == VRF_DEFAULT) sprintf(spaces, "%s", ""); else sprintf(spaces, "%s", " "); writes += pim_msdp_config_write(pim, vty, spaces); if (!pim->send_v6_secondary) { vty_out(vty, "%sno ip pim send-v6-secondary\n", spaces); ++writes; } writes += pim_rp_config_write(pim, vty, spaces); if (router->register_suppress_time != PIM_REGISTER_SUPPRESSION_TIME_DEFAULT) { vty_out(vty, "%sip pim register-suppress-time %d\n", spaces, router->register_suppress_time); ++writes; } if (router->t_periodic != PIM_DEFAULT_T_PERIODIC) { vty_out(vty, "%sip pim join-prune-interval %d\n", spaces, router->t_periodic); ++writes; } if (pim->keep_alive_time != PIM_KEEPALIVE_PERIOD) { vty_out(vty, "%sip pim keep-alive-timer %d\n", spaces, pim->keep_alive_time); ++writes; } if (pim->rp_keep_alive_time != (unsigned int)PIM_RP_KEEPALIVE_PERIOD) { vty_out(vty, "%sip pim rp keep-alive-timer %d\n", spaces, pim->rp_keep_alive_time); ++writes; } if (router->packet_process != PIM_DEFAULT_PACKET_PROCESS) { vty_out(vty, "%sip pim packets %d\n", spaces, router->packet_process); ++writes; } if (ssm->plist_name) { vty_out(vty, "%sip pim ssm prefix-list %s\n", spaces, ssm->plist_name); ++writes; } if (pim->spt.switchover == PIM_SPT_INFINITY) { if (pim->spt.plist) vty_out(vty, "%sip pim spt-switchover infinity-and-beyond prefix-list %s\n", spaces, pim->spt.plist); else vty_out(vty, "%sip pim spt-switchover infinity-and-beyond\n", spaces); ++writes; } if (pim->ecmp_rebalance_enable) { vty_out(vty, "%sip pim ecmp rebalance\n", spaces); ++writes; } else if (pim->ecmp_enable) { vty_out(vty, "%sip pim ecmp\n", spaces); ++writes; } if (pim->ssmpingd_list) { struct listnode *node; struct ssmpingd_sock *ss; ++writes; for (ALL_LIST_ELEMENTS_RO(pim->ssmpingd_list, node, ss)) { char source_str[INET_ADDRSTRLEN]; pim_inet4_dump("", ss->source_addr, source_str, sizeof(source_str)); vty_out(vty, "%sip ssmpingd %s\n", spaces, source_str); ++writes; } } pim_vxlan_config_write(vty, spaces, &writes); return writes; } int pim_interface_config_write(struct vty *vty) { struct pim_instance *pim; struct interface *ifp; struct vrf *vrf; int writes = 0; RB_FOREACH (vrf, vrf_name_head, &vrfs_by_name) { pim = vrf->info; if (!pim) continue; FOR_ALL_INTERFACES (pim->vrf, ifp) { /* IF name */ if (vrf->vrf_id == VRF_DEFAULT) vty_frame(vty, "interface %s\n", ifp->name); else vty_frame(vty, "interface %s vrf %s\n", ifp->name, vrf->name); ++writes; if (ifp->desc) { vty_out(vty, " description %s\n", ifp->desc); ++writes; } if (ifp->info) { struct pim_interface *pim_ifp = ifp->info; if (PIM_IF_TEST_PIM(pim_ifp->options)) { vty_out(vty, " ip pim\n"); ++writes; } /* IF ip pim drpriority */ if (pim_ifp->pim_dr_priority != PIM_DEFAULT_DR_PRIORITY) { vty_out(vty, " ip pim drpriority %u\n", pim_ifp->pim_dr_priority); ++writes; } /* IF ip pim hello */ if (pim_ifp->pim_hello_period != PIM_DEFAULT_HELLO_PERIOD) { vty_out(vty, " ip pim hello %d", pim_ifp->pim_hello_period); if (pim_ifp->pim_default_holdtime != -1) vty_out(vty, " %d", pim_ifp->pim_default_holdtime); vty_out(vty, "\n"); ++writes; } /* update source */ if (PIM_INADDR_ISNOT_ANY( pim_ifp->update_source)) { char src_str[INET_ADDRSTRLEN]; pim_inet4_dump("", pim_ifp->update_source, src_str, sizeof(src_str)); vty_out(vty, " ip pim use-source %s\n", src_str); ++writes; } /* IF ip igmp */ if (PIM_IF_TEST_IGMP(pim_ifp->options)) { vty_out(vty, " ip igmp\n"); ++writes; } /* ip igmp version */ if (pim_ifp->igmp_version != IGMP_DEFAULT_VERSION) { vty_out(vty, " ip igmp version %d\n", pim_ifp->igmp_version); ++writes; } /* IF ip igmp query-max-response-time */ if (pim_ifp->igmp_query_max_response_time_dsec != IGMP_QUERY_MAX_RESPONSE_TIME_DSEC) { vty_out(vty, " ip igmp query-max-response-time %d\n", pim_ifp->igmp_query_max_response_time_dsec); ++writes; } /* IF ip igmp query-interval */ if (pim_ifp->igmp_default_query_interval != IGMP_GENERAL_QUERY_INTERVAL) { vty_out(vty, " ip igmp query-interval %d\n", pim_ifp->igmp_default_query_interval); ++writes; } /* IF ip igmp last-member_query-count */ if (pim_ifp->igmp_last_member_query_count != IGMP_DEFAULT_ROBUSTNESS_VARIABLE) { vty_out(vty, " ip igmp last-member-query-count %d\n", pim_ifp->igmp_last_member_query_count); ++writes; } /* IF ip igmp last-member_query-interval */ if (pim_ifp->igmp_specific_query_max_response_time_dsec != IGMP_SPECIFIC_QUERY_MAX_RESPONSE_TIME_DSEC) { vty_out(vty, " ip igmp last-member-query-interval %d\n", pim_ifp->igmp_specific_query_max_response_time_dsec); ++writes; } /* IF ip igmp join */ if (pim_ifp->igmp_join_list) { struct listnode *node; struct igmp_join *ij; for (ALL_LIST_ELEMENTS_RO( pim_ifp->igmp_join_list, node, ij)) { char group_str[INET_ADDRSTRLEN]; char source_str [INET_ADDRSTRLEN]; pim_inet4_dump( "", ij->group_addr, group_str, sizeof(group_str)); inet_ntop(AF_INET, &ij->source_addr, source_str, sizeof(source_str)); vty_out(vty, " ip igmp join %s %s\n", group_str, source_str); ++writes; } } if (pim_ifp->activeactive) vty_out(vty, " ip pim active-active\n"); /* boundary */ if (pim_ifp->boundary_oil_plist) { vty_out(vty, " ip multicast boundary oil %s\n", pim_ifp->boundary_oil_plist); ++writes; } writes += pim_static_write_mroute(pim, vty, ifp); pim_bsm_write_config(vty, ifp); ++writes; pim_bfd_write_config(vty, ifp); ++writes; } vty_endframe(vty, "!\n"); ++writes; } } return writes; } frr-7.2.1/pimd/pim_vty.h0000644000000000000000000000204713610377563012010 00000000000000/* * PIM for Quagga * Copyright (C) 2008 Everton da Silva Marques * * This program is free software; you can redistribute it and/or modify * it under the terms of the 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; see the file COPYING; if not, write to the Free Software * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA */ #ifndef PIM_VTY_H #define PIM_VTY_H #include "vty.h" int pim_debug_config_write(struct vty *vty); int pim_global_config_write_worker(struct pim_instance *pim, struct vty *vty); int pim_interface_config_write(struct vty *vty); #endif /* PIM_VTY_H */ frr-7.2.1/pimd/pim_vxlan.c0000644000000000000000000007040413610377563012313 00000000000000/* PIM support for VxLAN BUM flooding * * Copyright (C) 2019 Cumulus Networks, Inc. * * This file is part of FRR. * * FRR is free software; you can redistribute it and/or modify it * under the terms of the GNU General Public License as published by the * Free Software Foundation; either version 2, or (at your option) any * later version. * * FRR 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. * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation; either version 2 of the License, or * (at your option) any later version. */ #include #include #include #include #include #include #include "pimd.h" #include "pim_iface.h" #include "pim_memory.h" #include "pim_oil.h" #include "pim_register.h" #include "pim_str.h" #include "pim_upstream.h" #include "pim_ifchannel.h" #include "pim_nht.h" #include "pim_zebra.h" #include "pim_vxlan.h" /* pim-vxlan global info */ struct pim_vxlan vxlan_info, *pim_vxlan_p = &vxlan_info; static void pim_vxlan_work_timer_setup(bool start); static void pim_vxlan_set_peerlink_rif(struct pim_instance *pim, struct interface *ifp); /*************************** vxlan work list ********************************** * A work list is maintained for staggered generation of pim null register * messages for vxlan SG entries that are in a reg_join state. * * A max of 500 NULL registers are generated at one shot. If paused reg * generation continues on the next second and so on till all register * messages have been sent out. And the process is restarted every 60s. * * purpose of this null register generation is to setup the SPT and maintain * independent of the presence of overlay BUM traffic. ****************************************************************************/ static void pim_vxlan_do_reg_work(void) { struct listnode *listnode; int work_cnt = 0; struct pim_vxlan_sg *vxlan_sg; static int sec_count; ++sec_count; if (sec_count > PIM_VXLAN_NULL_REG_INTERVAL) { sec_count = 0; listnode = vxlan_info.next_work ? vxlan_info.next_work : vxlan_info.work_list->head; if (PIM_DEBUG_VXLAN && listnode) zlog_debug("vxlan SG work %s", vxlan_info.next_work ? "continues" : "starts"); } else { listnode = vxlan_info.next_work; } for (; listnode; listnode = listnode->next) { vxlan_sg = (struct pim_vxlan_sg *)listnode->data; if (vxlan_sg->up && (vxlan_sg->up->reg_state == PIM_REG_JOIN)) { if (PIM_DEBUG_VXLAN) zlog_debug("vxlan SG %s periodic NULL register", vxlan_sg->sg_str); pim_null_register_send(vxlan_sg->up); ++work_cnt; } if (work_cnt > vxlan_info.max_work_cnt) { vxlan_info.next_work = listnode->next; if (PIM_DEBUG_VXLAN) zlog_debug("vxlan SG %d work items proc and pause", work_cnt); return; } } if (work_cnt) { if (PIM_DEBUG_VXLAN) zlog_debug("vxlan SG %d work items proc", work_cnt); } vxlan_info.next_work = NULL; } /* Staggered work related info is initialized when the first work comes * along */ static void pim_vxlan_init_work(void) { if (vxlan_info.flags & PIM_VXLANF_WORK_INITED) return; vxlan_info.max_work_cnt = PIM_VXLAN_WORK_MAX; vxlan_info.flags |= PIM_VXLANF_WORK_INITED; vxlan_info.work_list = list_new(); pim_vxlan_work_timer_setup(true/* start */); } static void pim_vxlan_add_work(struct pim_vxlan_sg *vxlan_sg) { if (vxlan_sg->flags & PIM_VXLAN_SGF_DEL_IN_PROG) { if (PIM_DEBUG_VXLAN) zlog_debug("vxlan SG %s skip work list; del-in-prog", vxlan_sg->sg_str); return; } pim_vxlan_init_work(); /* already a part of the work list */ if (vxlan_sg->work_node) return; if (PIM_DEBUG_VXLAN) zlog_debug("vxlan SG %s work list add", vxlan_sg->sg_str); vxlan_sg->work_node = listnode_add(vxlan_info.work_list, vxlan_sg); /* XXX: adjust max_work_cnt if needed */ } static void pim_vxlan_del_work(struct pim_vxlan_sg *vxlan_sg) { if (!vxlan_sg->work_node) return; if (PIM_DEBUG_VXLAN) zlog_debug("vxlan SG %s work list del", vxlan_sg->sg_str); if (vxlan_sg->work_node == vxlan_info.next_work) vxlan_info.next_work = vxlan_sg->work_node->next; list_delete_node(vxlan_info.work_list, vxlan_sg->work_node); vxlan_sg->work_node = NULL; } void pim_vxlan_update_sg_reg_state(struct pim_instance *pim, struct pim_upstream *up, bool reg_join) { struct pim_vxlan_sg *vxlan_sg; vxlan_sg = pim_vxlan_sg_find(pim, &up->sg); if (!vxlan_sg) return; /* add the vxlan sg entry to a work list for periodic reg joins. * the entry will stay in the list as long as the register state is * PIM_REG_JOIN */ if (reg_join) pim_vxlan_add_work(vxlan_sg); else pim_vxlan_del_work(vxlan_sg); } static int pim_vxlan_work_timer_cb(struct thread *t) { pim_vxlan_do_reg_work(); pim_vxlan_work_timer_setup(true /* start */); return 0; } /* global 1second timer used for periodic processing */ static void pim_vxlan_work_timer_setup(bool start) { THREAD_OFF(vxlan_info.work_timer); if (start) thread_add_timer(router->master, pim_vxlan_work_timer_cb, NULL, PIM_VXLAN_WORK_TIME, &vxlan_info.work_timer); } /**************************** vxlan origination mroutes *********************** * For every (local-vtep-ip, bum-mcast-grp) registered by evpn an origination * mroute is setup by pimd. The purpose of this mroute is to forward vxlan * encapsulated BUM (broadcast, unknown-unicast and unknown-multicast packets * over the underlay.) * * Sample mroute (single VTEP): * (27.0.0.7, 239.1.1.100) Iif: lo Oifs: uplink-1 * * Sample mroute (anycast VTEP): * (36.0.0.9, 239.1.1.100) Iif: peerlink-3.4094\ * Oifs: peerlink-3.4094 uplink-1 ***************************************************************************/ static void pim_vxlan_orig_mr_up_del(struct pim_vxlan_sg *vxlan_sg) { struct pim_upstream *up = vxlan_sg->up; if (!up) return; if (PIM_DEBUG_VXLAN) zlog_debug("vxlan SG %s orig mroute-up del", vxlan_sg->sg_str); vxlan_sg->up = NULL; if (up->flags & PIM_UPSTREAM_FLAG_MASK_SRC_VXLAN_ORIG) { /* clear out all the vxlan properties */ up->flags &= ~(PIM_UPSTREAM_FLAG_MASK_SRC_VXLAN_ORIG | PIM_UPSTREAM_FLAG_MASK_STATIC_IIF | PIM_UPSTREAM_FLAG_MASK_DISABLE_KAT_EXPIRY | PIM_UPSTREAM_FLAG_MASK_FORCE_PIMREG | PIM_UPSTREAM_FLAG_MASK_NO_PIMREG_DATA | PIM_UPSTREAM_FLAG_MASK_ALLOW_IIF_IN_OIL); /* We bring things to a grinding halt by force expirying * the kat. Doing this will also remove the reference we * created as a "vxlan" source and delete the upstream entry * if there are no other references. */ if (PIM_UPSTREAM_FLAG_TEST_SRC_STREAM(up->flags)) { THREAD_OFF(up->t_ka_timer); up = pim_upstream_keep_alive_timer_proc(up); } else { /* this is really unexpected as we force vxlan * origination mroutes active sources but just in * case */ up = pim_upstream_del(vxlan_sg->pim, up, __PRETTY_FUNCTION__); } /* if there are other references register the source * for nht */ if (up) pim_rpf_update(vxlan_sg->pim, up, NULL); } } static void pim_vxlan_orig_mr_up_iif_update(struct pim_vxlan_sg *vxlan_sg) { int vif_index; /* update MFC with the new IIF */ pim_upstream_fill_static_iif(vxlan_sg->up, vxlan_sg->iif); vif_index = pim_if_find_vifindex_by_ifindex(vxlan_sg->pim, vxlan_sg->iif->ifindex); if (vif_index > 0) pim_scan_individual_oil(vxlan_sg->up->channel_oil, vif_index); if (PIM_DEBUG_VXLAN) zlog_debug("vxlan SG %s orig mroute-up updated with iif %s vifi %d", vxlan_sg->sg_str, vxlan_sg->iif?vxlan_sg->iif->name:"-", vif_index); } /* For every VxLAN BUM multicast group we setup a SG-up that has the following * "forced properties" - * 1. Directly connected on a DR interface i.e. we must act as an FHR * 2. We prime the pump i.e. no multicast data is needed to register this * source with the FHR. To do that we send periodic null registers if * the SG entry is in a register-join state. We also prevent expiry of * KAT. * 3. As this SG is setup without data there is no need to register encapsulate * data traffic. This encapsulation is explicitly skipped for the following * reasons - * a) Many levels of encapsulation are needed creating MTU disc challenges. * Overlay BUM is encapsulated in a vxlan/UDP/IP header and then * encapsulated again in a pim-register header. * b) On a vxlan-aa setup both switches rx a copy of each BUM packet. if * they both reg encapsulated traffic the RP will accept the duplicates * as there are no RPF checks for this encapsulated data. * a), b) can be workarounded if needed, but there is really no need because * of (2) i.e. the pump is primed without data. */ static void pim_vxlan_orig_mr_up_add(struct pim_vxlan_sg *vxlan_sg) { struct pim_upstream *up; int flags = 0; struct prefix nht_p; if (vxlan_sg->up) { /* nothing to do */ return; } if (PIM_DEBUG_VXLAN) zlog_debug("vxlan SG %s orig mroute-up add with iif %s", vxlan_sg->sg_str, vxlan_sg->iif?vxlan_sg->iif->name:"-"); PIM_UPSTREAM_FLAG_SET_SRC_VXLAN_ORIG(flags); /* pin the IIF to lo or peerlink-subinterface and disable NHT */ PIM_UPSTREAM_FLAG_SET_STATIC_IIF(flags); /* Fake traffic by setting SRC_STREAM and starting KAT */ /* We intentionally skip updating ref count for SRC_STREAM/FHR. * Setting SRC_VXLAN should have already created a reference * preventing the entry from being deleted */ PIM_UPSTREAM_FLAG_SET_FHR(flags); PIM_UPSTREAM_FLAG_SET_SRC_STREAM(flags); /* Force pimreg even if non-DR. This is needed on a MLAG setup for * VxLAN AA */ PIM_UPSTREAM_FLAG_SET_FORCE_PIMREG(flags); /* prevent KAT expiry. we want the MDT setup even if there is no BUM * traffic */ PIM_UPSTREAM_FLAG_SET_DISABLE_KAT_EXPIRY(flags); /* SPT for vxlan BUM groups is primed and maintained via NULL * registers so there is no need to reg-encapsulate * vxlan-encapsulated overlay data traffic */ PIM_UPSTREAM_FLAG_SET_NO_PIMREG_DATA(flags); /* On a MLAG setup we force a copy to the MLAG peer while also * accepting traffic from the peer. To do this we set peerlink-rif as * the IIF and also add it to the OIL */ PIM_UPSTREAM_FLAG_SET_ALLOW_IIF_IN_OIL(flags); /* XXX: todo: defer pim_upstream add if pim is not enabled on the iif */ up = pim_upstream_find(vxlan_sg->pim, &vxlan_sg->sg); if (up) { /* if the iif is set to something other than the vxlan_sg->iif * we must dereg the old nexthop and force to new "static" * iif */ if (!PIM_UPSTREAM_FLAG_TEST_STATIC_IIF(up->flags)) { nht_p.family = AF_INET; nht_p.prefixlen = IPV4_MAX_BITLEN; nht_p.u.prefix4 = up->upstream_addr; pim_delete_tracked_nexthop(vxlan_sg->pim, &nht_p, up, NULL, false); } pim_upstream_ref(up, flags, __PRETTY_FUNCTION__); vxlan_sg->up = up; pim_vxlan_orig_mr_up_iif_update(vxlan_sg); } else { up = pim_upstream_add(vxlan_sg->pim, &vxlan_sg->sg, vxlan_sg->iif, flags, __PRETTY_FUNCTION__, NULL); vxlan_sg->up = up; } if (!up) { if (PIM_DEBUG_VXLAN) zlog_debug("vxlan SG %s orig mroute-up add failed", vxlan_sg->sg_str); return; } pim_upstream_keep_alive_timer_start(up, vxlan_sg->pim->keep_alive_time); /* register the source with the RP */ if (up->reg_state == PIM_REG_NOINFO) { pim_register_join(up); pim_null_register_send(up); } /* update the inherited OIL */ pim_upstream_inherited_olist(vxlan_sg->pim, up); } static void pim_vxlan_orig_mr_oif_add(struct pim_vxlan_sg *vxlan_sg) { if (!vxlan_sg->up || !vxlan_sg->orig_oif) return; if (PIM_DEBUG_VXLAN) zlog_debug("vxlan SG %s oif %s add", vxlan_sg->sg_str, vxlan_sg->orig_oif->name); vxlan_sg->flags |= PIM_VXLAN_SGF_OIF_INSTALLED; pim_channel_add_oif(vxlan_sg->up->channel_oil, vxlan_sg->orig_oif, PIM_OIF_FLAG_PROTO_VXLAN); } static void pim_vxlan_orig_mr_oif_del(struct pim_vxlan_sg *vxlan_sg) { struct interface *orig_oif; orig_oif = vxlan_sg->orig_oif; vxlan_sg->orig_oif = NULL; if (!(vxlan_sg->flags & PIM_VXLAN_SGF_OIF_INSTALLED)) return; if (PIM_DEBUG_VXLAN) zlog_debug("vxlan SG %s oif %s del", vxlan_sg->sg_str, orig_oif->name); vxlan_sg->flags &= ~PIM_VXLAN_SGF_OIF_INSTALLED; pim_channel_del_oif(vxlan_sg->up->channel_oil, orig_oif, PIM_OIF_FLAG_PROTO_VXLAN); } static inline struct interface *pim_vxlan_orig_mr_oif_get( struct pim_instance *pim) { return (vxlan_mlag.flags & PIM_VXLAN_MLAGF_ENABLED) ? pim->vxlan.peerlink_rif : NULL; } /* Single VTEPs: IIF for the vxlan-origination-mroutes is lo or vrf-dev (if * the mroute is in a non-default vrf). * Anycast VTEPs: IIF is the MLAG ISL/peerlink. */ static inline struct interface *pim_vxlan_orig_mr_iif_get( struct pim_instance *pim) { return ((vxlan_mlag.flags & PIM_VXLAN_MLAGF_ENABLED) && pim->vxlan.peerlink_rif) ? pim->vxlan.peerlink_rif : pim->vxlan.default_iif; } static bool pim_vxlan_orig_mr_add_is_ok(struct pim_vxlan_sg *vxlan_sg) { struct pim_interface *pim_ifp; vxlan_sg->iif = pim_vxlan_orig_mr_iif_get(vxlan_sg->pim); if (!vxlan_sg->iif) return false; pim_ifp = (struct pim_interface *)vxlan_sg->iif->info; if (!pim_ifp || (pim_ifp->mroute_vif_index < 0)) return false; return true; } static void pim_vxlan_orig_mr_install(struct pim_vxlan_sg *vxlan_sg) { pim_vxlan_orig_mr_up_add(vxlan_sg); vxlan_sg->orig_oif = pim_vxlan_orig_mr_oif_get(vxlan_sg->pim); pim_vxlan_orig_mr_oif_add(vxlan_sg); } static void pim_vxlan_orig_mr_add(struct pim_vxlan_sg *vxlan_sg) { if (!pim_vxlan_orig_mr_add_is_ok(vxlan_sg)) return; if (PIM_DEBUG_VXLAN) zlog_debug("vxlan SG %s orig-mr add", vxlan_sg->sg_str); pim_vxlan_orig_mr_install(vxlan_sg); } static void pim_vxlan_orig_mr_del(struct pim_vxlan_sg *vxlan_sg) { if (PIM_DEBUG_VXLAN) zlog_debug("vxlan SG %s orig-mr del", vxlan_sg->sg_str); pim_vxlan_orig_mr_oif_del(vxlan_sg); pim_vxlan_orig_mr_up_del(vxlan_sg); } static void pim_vxlan_orig_mr_iif_update(struct hash_backet *backet, void *arg) { struct interface *ifp = (struct interface *)arg; struct pim_vxlan_sg *vxlan_sg = (struct pim_vxlan_sg *)backet->data; struct interface *old_iif = vxlan_sg->iif; if (!pim_vxlan_is_orig_mroute(vxlan_sg)) return; if (PIM_DEBUG_VXLAN) zlog_debug("vxlan SG %s iif changed from %s to %s", vxlan_sg->sg_str, old_iif ? old_iif->name : "-", ifp ? ifp->name : "-"); if (pim_vxlan_orig_mr_add_is_ok(vxlan_sg)) { if (vxlan_sg->up) { /* upstream exists but iif changed */ pim_vxlan_orig_mr_up_iif_update(vxlan_sg); } else { /* install mroute */ pim_vxlan_orig_mr_install(vxlan_sg); } } else { pim_vxlan_orig_mr_del(vxlan_sg); } } /**************************** vxlan termination mroutes *********************** * For every bum-mcast-grp registered by evpn a *G termination * mroute is setup by pimd. The purpose of this mroute is to pull down vxlan * packets with the bum-mcast-grp dip from the underlay and terminate the * tunnel. This is done by including the vxlan termination device (ipmr-lo) in * its OIL. The vxlan de-capsulated packets are subject to subsequent overlay * bridging. * * Sample mroute: * (0.0.0.0, 239.1.1.100) Iif: uplink-1 Oifs: ipmr-lo, uplink-1 *****************************************************************************/ struct pim_interface *pim_vxlan_get_term_ifp(struct pim_instance *pim) { return pim->vxlan.term_if ? (struct pim_interface *)pim->vxlan.term_if->info : NULL; } static void pim_vxlan_term_mr_oif_add(struct pim_vxlan_sg *vxlan_sg) { if (vxlan_sg->flags & PIM_VXLAN_SGF_OIF_INSTALLED) return; if (PIM_DEBUG_VXLAN) zlog_debug("vxlan SG %s term-oif %s add", vxlan_sg->sg_str, vxlan_sg->term_oif->name); if (pim_ifchannel_local_membership_add(vxlan_sg->term_oif, &vxlan_sg->sg)) { vxlan_sg->flags |= PIM_VXLAN_SGF_OIF_INSTALLED; } else { zlog_warn("vxlan SG %s term-oif %s add failed", vxlan_sg->sg_str, vxlan_sg->term_oif->name); } } static void pim_vxlan_term_mr_oif_del(struct pim_vxlan_sg *vxlan_sg) { if (!(vxlan_sg->flags & PIM_VXLAN_SGF_OIF_INSTALLED)) return; if (PIM_DEBUG_VXLAN) zlog_debug("vxlan SG %s oif %s del", vxlan_sg->sg_str, vxlan_sg->term_oif->name); vxlan_sg->flags &= ~PIM_VXLAN_SGF_OIF_INSTALLED; pim_ifchannel_local_membership_del(vxlan_sg->term_oif, &vxlan_sg->sg); } static void pim_vxlan_term_mr_up_add(struct pim_vxlan_sg *vxlan_sg) { struct pim_upstream *up; int flags = 0; if (vxlan_sg->up) { /* nothing to do */ return; } if (PIM_DEBUG_VXLAN) zlog_debug("vxlan SG %s term mroute-up add", vxlan_sg->sg_str); PIM_UPSTREAM_FLAG_SET_SRC_VXLAN_TERM(flags); /* enable MLAG designated-forwarder election on termination mroutes */ PIM_UPSTREAM_FLAG_SET_MLAG_VXLAN(flags); up = pim_upstream_add(vxlan_sg->pim, &vxlan_sg->sg, NULL /* iif */, flags, __PRETTY_FUNCTION__, NULL); vxlan_sg->up = up; if (!up) { zlog_warn("vxlan SG %s term mroute-up add failed", vxlan_sg->sg_str); } } static void pim_vxlan_term_mr_up_del(struct pim_vxlan_sg *vxlan_sg) { struct pim_upstream *up = vxlan_sg->up; if (!up) return; if (PIM_DEBUG_VXLAN) zlog_debug("vxlan SG %s term mroute-up del", vxlan_sg->sg_str); vxlan_sg->up = NULL; if (up->flags & PIM_UPSTREAM_FLAG_MASK_SRC_VXLAN_TERM) { /* clear out all the vxlan related flags */ up->flags &= ~(PIM_UPSTREAM_FLAG_MASK_SRC_VXLAN_TERM | PIM_UPSTREAM_FLAG_MASK_MLAG_VXLAN); pim_upstream_del(vxlan_sg->pim, up, __PRETTY_FUNCTION__); } } static void pim_vxlan_term_mr_add(struct pim_vxlan_sg *vxlan_sg) { if (PIM_DEBUG_VXLAN) zlog_debug("vxlan SG %s term mroute add", vxlan_sg->sg_str); vxlan_sg->term_oif = vxlan_sg->pim->vxlan.term_if; if (!vxlan_sg->term_oif) /* defer termination mroute till we have a termination device */ return; pim_vxlan_term_mr_up_add(vxlan_sg); /* set up local membership for the term-oif */ pim_vxlan_term_mr_oif_add(vxlan_sg); } static void pim_vxlan_term_mr_del(struct pim_vxlan_sg *vxlan_sg) { if (PIM_DEBUG_VXLAN) zlog_debug("vxlan SG %s term mroute del", vxlan_sg->sg_str); /* remove local membership associated with the term oif */ pim_vxlan_term_mr_oif_del(vxlan_sg); /* remove references to the upstream entry */ pim_vxlan_term_mr_up_del(vxlan_sg); } /************************** vxlan SG cache management ************************/ static unsigned int pim_vxlan_sg_hash_key_make(const void *p) { const struct pim_vxlan_sg *vxlan_sg = p; return (jhash_2words(vxlan_sg->sg.src.s_addr, vxlan_sg->sg.grp.s_addr, 0)); } static bool pim_vxlan_sg_hash_eq(const void *p1, const void *p2) { const struct pim_vxlan_sg *sg1 = p1; const struct pim_vxlan_sg *sg2 = p2; return ((sg1->sg.src.s_addr == sg2->sg.src.s_addr) && (sg1->sg.grp.s_addr == sg2->sg.grp.s_addr)); } static struct pim_vxlan_sg *pim_vxlan_sg_new(struct pim_instance *pim, struct prefix_sg *sg) { struct pim_vxlan_sg *vxlan_sg; vxlan_sg = XCALLOC(MTYPE_PIM_VXLAN_SG, sizeof(*vxlan_sg)); vxlan_sg->pim = pim; vxlan_sg->sg = *sg; pim_str_sg_set(sg, vxlan_sg->sg_str); if (PIM_DEBUG_VXLAN) zlog_debug("vxlan SG %s alloc", vxlan_sg->sg_str); vxlan_sg = hash_get(pim->vxlan.sg_hash, vxlan_sg, hash_alloc_intern); return vxlan_sg; } struct pim_vxlan_sg *pim_vxlan_sg_find(struct pim_instance *pim, struct prefix_sg *sg) { struct pim_vxlan_sg lookup; lookup.sg = *sg; return hash_lookup(pim->vxlan.sg_hash, &lookup); } struct pim_vxlan_sg *pim_vxlan_sg_add(struct pim_instance *pim, struct prefix_sg *sg) { struct pim_vxlan_sg *vxlan_sg; vxlan_sg = pim_vxlan_sg_find(pim, sg); if (vxlan_sg) return vxlan_sg; vxlan_sg = pim_vxlan_sg_new(pim, sg); if (pim_vxlan_is_orig_mroute(vxlan_sg)) pim_vxlan_orig_mr_add(vxlan_sg); else pim_vxlan_term_mr_add(vxlan_sg); return vxlan_sg; } void pim_vxlan_sg_del(struct pim_instance *pim, struct prefix_sg *sg) { struct pim_vxlan_sg *vxlan_sg; vxlan_sg = pim_vxlan_sg_find(pim, sg); if (!vxlan_sg) return; vxlan_sg->flags |= PIM_VXLAN_SGF_DEL_IN_PROG; pim_vxlan_del_work(vxlan_sg); if (pim_vxlan_is_orig_mroute(vxlan_sg)) pim_vxlan_orig_mr_del(vxlan_sg); else pim_vxlan_term_mr_del(vxlan_sg); hash_release(vxlan_sg->pim->vxlan.sg_hash, vxlan_sg); if (PIM_DEBUG_VXLAN) zlog_debug("vxlan SG %s free", vxlan_sg->sg_str); XFREE(MTYPE_PIM_VXLAN_SG, vxlan_sg); } /******************************* MLAG handling *******************************/ /* The peerlink sub-interface is added as an OIF to the origination-mroute. * This is done to send a copy of the multicast-vxlan encapsulated traffic * to the MLAG peer which may mroute it over the underlay if there are any * interested receivers. */ static void pim_vxlan_sg_peerlink_update(struct hash_backet *backet, void *arg) { struct interface *new_oif = (struct interface *)arg; struct pim_vxlan_sg *vxlan_sg = (struct pim_vxlan_sg *)backet->data; if (!pim_vxlan_is_orig_mroute(vxlan_sg)) return; if (vxlan_sg->orig_oif == new_oif) return; pim_vxlan_orig_mr_oif_del(vxlan_sg); vxlan_sg->orig_oif = new_oif; pim_vxlan_orig_mr_oif_add(vxlan_sg); } /* In the case of anycast VTEPs the VTEP-PIP must be used as the * register source. */ bool pim_vxlan_get_register_src(struct pim_instance *pim, struct pim_upstream *up, struct in_addr *src_p) { if (!(vxlan_mlag.flags & PIM_VXLAN_MLAGF_ENABLED)) return true; /* if address is not available suppress the pim-register */ if (vxlan_mlag.reg_addr.s_addr == INADDR_ANY) return false; *src_p = vxlan_mlag.reg_addr; return true; } void pim_vxlan_mlag_update(bool enable, bool peer_state, uint32_t role, struct interface *peerlink_rif, struct in_addr *reg_addr) { struct pim_instance *pim; struct interface *old_oif; struct interface *new_oif; char addr_buf[INET_ADDRSTRLEN]; struct pim_interface *pim_ifp = NULL; if (PIM_DEBUG_VXLAN) { inet_ntop(AF_INET, reg_addr, addr_buf, INET_ADDRSTRLEN); zlog_debug("vxlan MLAG update %s state %s role %d rif %s addr %s", enable ? "enable" : "disable", peer_state ? "up" : "down", role, peerlink_rif ? peerlink_rif->name : "-", addr_buf); } /* XXX: for now vxlan termination is only possible in the default VRF * when that changes this will need to change to iterate all VRFs */ pim = pim_get_pim_instance(VRF_DEFAULT); old_oif = pim_vxlan_orig_mr_oif_get(pim); if (enable) vxlan_mlag.flags |= PIM_VXLAN_MLAGF_ENABLED; else vxlan_mlag.flags &= ~PIM_VXLAN_MLAGF_ENABLED; if (vxlan_mlag.peerlink_rif != peerlink_rif) vxlan_mlag.peerlink_rif = peerlink_rif; vxlan_mlag.reg_addr = *reg_addr; vxlan_mlag.peer_state = peer_state; vxlan_mlag.role = role; /* process changes */ if (vxlan_mlag.peerlink_rif) pim_ifp = (struct pim_interface *)vxlan_mlag.peerlink_rif->info; if ((vxlan_mlag.flags & PIM_VXLAN_MLAGF_ENABLED) && pim_ifp && (pim_ifp->mroute_vif_index > 0)) pim_vxlan_set_peerlink_rif(pim, peerlink_rif); else pim_vxlan_set_peerlink_rif(pim, NULL); new_oif = pim_vxlan_orig_mr_oif_get(pim); if (old_oif != new_oif) hash_iterate(pim->vxlan.sg_hash, pim_vxlan_sg_peerlink_update, new_oif); } /****************************** misc callbacks *******************************/ void pim_vxlan_config_write(struct vty *vty, char *spaces, int *writes) { char addr_buf[INET_ADDRSTRLEN]; if ((vxlan_mlag.flags & PIM_VXLAN_MLAGF_ENABLED) && vxlan_mlag.peerlink_rif) { inet_ntop(AF_INET, &vxlan_mlag.reg_addr, addr_buf, sizeof(addr_buf)); vty_out(vty, "%sip pim mlag %s role %s state %s addr %s\n", spaces, vxlan_mlag.peerlink_rif->name, (vxlan_mlag.role == PIM_VXLAN_MLAG_ROLE_PRIMARY) ? "primary":"secondary", vxlan_mlag.peer_state ? "up" : "down", addr_buf); *writes += 1; } } static void pim_vxlan_set_default_iif(struct pim_instance *pim, struct interface *ifp) { struct interface *old_iif; if (pim->vxlan.default_iif == ifp) return; old_iif = pim->vxlan.default_iif; if (PIM_DEBUG_VXLAN) zlog_debug("%s: vxlan default iif changed from %s to %s", __PRETTY_FUNCTION__, old_iif ? old_iif->name : "-", ifp ? ifp->name : "-"); old_iif = pim_vxlan_orig_mr_iif_get(pim); pim->vxlan.default_iif = ifp; ifp = pim_vxlan_orig_mr_iif_get(pim); if (old_iif == ifp) return; if (PIM_DEBUG_VXLAN) zlog_debug("%s: vxlan orig iif changed from %s to %s", __PRETTY_FUNCTION__, old_iif ? old_iif->name : "-", ifp ? ifp->name : "-"); /* add/del upstream entries for the existing vxlan SG when the * interface becomes available */ if (pim->vxlan.sg_hash) hash_iterate(pim->vxlan.sg_hash, pim_vxlan_orig_mr_iif_update, ifp); } static void pim_vxlan_set_peerlink_rif(struct pim_instance *pim, struct interface *ifp) { struct interface *old_iif; if (pim->vxlan.peerlink_rif == ifp) return; old_iif = pim->vxlan.peerlink_rif; if (PIM_DEBUG_VXLAN) zlog_debug("%s: vxlan peerlink_rif changed from %s to %s", __PRETTY_FUNCTION__, old_iif ? old_iif->name : "-", ifp ? ifp->name : "-"); old_iif = pim_vxlan_orig_mr_iif_get(pim); pim->vxlan.peerlink_rif = ifp; ifp = pim_vxlan_orig_mr_iif_get(pim); if (old_iif == ifp) return; if (PIM_DEBUG_VXLAN) zlog_debug("%s: vxlan orig iif changed from %s to %s", __PRETTY_FUNCTION__, old_iif ? old_iif->name : "-", ifp ? ifp->name : "-"); /* add/del upstream entries for the existing vxlan SG when the * interface becomes available */ if (pim->vxlan.sg_hash) hash_iterate(pim->vxlan.sg_hash, pim_vxlan_orig_mr_iif_update, ifp); } void pim_vxlan_add_vif(struct interface *ifp) { struct pim_interface *pim_ifp = ifp->info; struct pim_instance *pim = pim_ifp->pim; if (pim->vrf_id != VRF_DEFAULT) return; if (if_is_loopback_or_vrf(ifp)) pim_vxlan_set_default_iif(pim, ifp); if (vxlan_mlag.flags & PIM_VXLAN_MLAGF_ENABLED && (ifp == vxlan_mlag.peerlink_rif)) pim_vxlan_set_peerlink_rif(pim, ifp); } void pim_vxlan_del_vif(struct interface *ifp) { struct pim_interface *pim_ifp = ifp->info; struct pim_instance *pim = pim_ifp->pim; if (pim->vrf_id != VRF_DEFAULT) return; if (pim->vxlan.default_iif == ifp) pim_vxlan_set_default_iif(pim, NULL); if (pim->vxlan.peerlink_rif == ifp) pim_vxlan_set_peerlink_rif(pim, NULL); } static void pim_vxlan_term_mr_oif_update(struct hash_backet *backet, void *arg) { struct interface *ifp = (struct interface *)arg; struct pim_vxlan_sg *vxlan_sg = (struct pim_vxlan_sg *)backet->data; if (pim_vxlan_is_orig_mroute(vxlan_sg)) return; if (vxlan_sg->term_oif == ifp) return; if (PIM_DEBUG_VXLAN) zlog_debug("vxlan SG %s term oif changed from %s to %s", vxlan_sg->sg_str, vxlan_sg->term_oif ? vxlan_sg->term_oif->name : "-", ifp ? ifp->name : "-"); pim_vxlan_term_mr_del(vxlan_sg); vxlan_sg->term_oif = ifp; pim_vxlan_term_mr_add(vxlan_sg); } void pim_vxlan_add_term_dev(struct pim_instance *pim, struct interface *ifp) { struct pim_interface *pim_ifp; if (pim->vxlan.term_if == ifp) return; if (PIM_DEBUG_VXLAN) zlog_debug("vxlan term oif changed from %s to %s", pim->vxlan.term_if ? pim->vxlan.term_if->name : "-", ifp->name); /* enable pim on the term ifp */ pim_ifp = (struct pim_interface *)ifp->info; if (pim_ifp) { PIM_IF_DO_PIM(pim_ifp->options); } else { pim_ifp = pim_if_new(ifp, false /*igmp*/, true /*pim*/, false /*pimreg*/, true /*vxlan_term*/); /* ensure that pimreg existss before using the newly created * vxlan termination device */ pim_if_create_pimreg(pim); } pim->vxlan.term_if = ifp; if (pim->vxlan.sg_hash) hash_iterate(pim_ifp->pim->vxlan.sg_hash, pim_vxlan_term_mr_oif_update, ifp); } void pim_vxlan_del_term_dev(struct pim_instance *pim) { struct interface *ifp = pim->vxlan.term_if; struct pim_interface *pim_ifp; if (PIM_DEBUG_VXLAN) zlog_debug("vxlan term oif changed from %s to -", ifp->name); pim->vxlan.term_if = NULL; if (pim->vxlan.sg_hash) hash_iterate(pim->vxlan.sg_hash, pim_vxlan_term_mr_oif_update, NULL); pim_ifp = (struct pim_interface *)ifp->info; if (pim_ifp) { PIM_IF_DONT_PIM(pim_ifp->options); if (!PIM_IF_TEST_IGMP(pim_ifp->options)) pim_if_delete(ifp); } } void pim_vxlan_init(struct pim_instance *pim) { char hash_name[64]; snprintf(hash_name, sizeof(hash_name), "PIM %s vxlan SG hash", pim->vrf->name); pim->vxlan.sg_hash = hash_create(pim_vxlan_sg_hash_key_make, pim_vxlan_sg_hash_eq, hash_name); } void pim_vxlan_exit(struct pim_instance *pim) { if (pim->vxlan.sg_hash) { hash_clean(pim->vxlan.sg_hash, NULL); hash_free(pim->vxlan.sg_hash); pim->vxlan.sg_hash = NULL; } } frr-7.2.1/pimd/pim_vxlan.h0000644000000000000000000001065513610377563012322 00000000000000/* PIM support for VxLAN BUM flooding * * Copyright (C) 2019 Cumulus Networks, Inc. * * This file is part of FRR. * * FRR is free software; you can redistribute it and/or modify it * under the terms of the GNU General Public License as published by the * Free Software Foundation; either version 2, or (at your option) any * later version. * * FRR 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. * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation; either version 2 of the License, or * (at your option) any later version. */ #ifndef PIM_VXLAN_H #define PIM_VXLAN_H /* global timer used for miscellaneous staggered processing */ #define PIM_VXLAN_WORK_TIME 1 /* number of SG entries processed at one shot */ #define PIM_VXLAN_WORK_MAX 500 /* frequency of periodic NULL registers */ #define PIM_VXLAN_NULL_REG_INTERVAL 60 /* seconds */ #define vxlan_mlag (vxlan_info.mlag) enum pim_vxlan_sg_flags { PIM_VXLAN_SGF_NONE = 0, PIM_VXLAN_SGF_DEL_IN_PROG = (1 << 0), PIM_VXLAN_SGF_OIF_INSTALLED = (1 << 1) }; struct pim_vxlan_sg { struct pim_instance *pim; /* key */ struct prefix_sg sg; char sg_str[PIM_SG_LEN]; enum pim_vxlan_sg_flags flags; struct pim_upstream *up; struct listnode *work_node; /* to pim_vxlan.work_list */ /* termination info (only applicable to termination XG mroutes) * term_if - termination device ipmr-lo is added to the OIL * as local/IGMP membership to allow termination of vxlan traffic */ struct interface *term_oif; /* origination info * iif - lo/vrf or peerlink (on MLAG setups) * peerlink_oif - added to the OIL to send encapsulated BUM traffic to * the MLAG peer switch */ struct interface *iif; /* on a MLAG setup the peerlink is added as a static OIF */ struct interface *orig_oif; }; enum pim_vxlan_mlag_flags { PIM_VXLAN_MLAGF_NONE = 0, PIM_VXLAN_MLAGF_ENABLED = (1 << 0) }; enum pim_vxlan_mlag_role { PIM_VXLAN_MLAG_ROLE_SECONDARY = 0, PIM_VXLAN_MLAG_ROLE_PRIMARY }; struct pim_vxlan_mlag { enum pim_vxlan_mlag_flags flags; enum pim_vxlan_mlag_role role; bool peer_state; /* routed interface setup on top of MLAG peerlink */ struct interface *peerlink_rif; struct in_addr reg_addr; }; enum pim_vxlan_flags { PIM_VXLANF_NONE = 0, PIM_VXLANF_WORK_INITED = (1 << 0) }; struct pim_vxlan { enum pim_vxlan_flags flags; struct thread *work_timer; struct list *work_list; struct listnode *next_work; int max_work_cnt; struct pim_vxlan_mlag mlag; }; /* zebra adds- * 1. one (S, G) entry where S=local-VTEP-IP and G==BUM-mcast-grp for * each BUM MDT. This is the origination entry. * 2. and one (*, G) entry each MDT. This is the termination place holder. * * Note: This doesn't mean that only (*, G) mroutes are used for tunnel * termination. (S, G) mroutes with ipmr-lo in the OIL can also be * used for tunnel termiation if SPT switchover happens; however such * SG entries are created by traffic and will NOT be a part of the vxlan SG * database. */ static inline bool pim_vxlan_is_orig_mroute(struct pim_vxlan_sg *vxlan_sg) { return (vxlan_sg->sg.src.s_addr != 0); } extern struct pim_vxlan *pim_vxlan_p; extern struct pim_vxlan_sg *pim_vxlan_sg_find(struct pim_instance *pim, struct prefix_sg *sg); extern struct pim_vxlan_sg *pim_vxlan_sg_add(struct pim_instance *pim, struct prefix_sg *sg); extern void pim_vxlan_sg_del(struct pim_instance *pim, struct prefix_sg *sg); extern void pim_vxlan_update_sg_reg_state(struct pim_instance *pim, struct pim_upstream *up, bool reg_join); extern struct pim_interface *pim_vxlan_get_term_ifp(struct pim_instance *pim); extern void pim_vxlan_add_vif(struct interface *ifp); extern void pim_vxlan_del_vif(struct interface *ifp); extern void pim_vxlan_add_term_dev(struct pim_instance *pim, struct interface *ifp); extern void pim_vxlan_del_term_dev(struct pim_instance *pim); extern bool pim_vxlan_get_register_src(struct pim_instance *pim, struct pim_upstream *up, struct in_addr *src_p); extern void pim_vxlan_mlag_update(bool enable, bool peer_state, uint32_t role, struct interface *peerlink_rif, struct in_addr *reg_addr); extern void pim_vxlan_config_write(struct vty *vty, char *spaces, int *writes); #endif /* PIM_VXLAN_H */ frr-7.2.1/pimd/pim_vxlan_instance.h0000644000000000000000000000247413610377563014206 00000000000000/* PIM support for VxLAN BUM flooding * * Copyright (C) 2019 Cumulus Networks, Inc. * * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation; either version 2 of the License, or * (at your option) any later version. * * This program is distributed in the hope that it will be useful, but * WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU * General Public License for more details. * */ #ifndef PIM_VXLAN_INSTANCE_H #define PIM_VXLAN_INSTANCE_H /* pim termination device is expected to include the substring ipmr-lo */ #define PIM_VXLAN_TERM_DEV_NAME "ipmr-lo" struct pim_vxlan_instance { struct hash *sg_hash; /* this is lo for default instance and vrf-dev for non-default * instances */ struct interface *default_iif; /* In a MLAG/VxLAN-AA setup the peerlink sub-interface (ISL-rif) is * used as the IIF in */ struct interface *peerlink_rif; /* device used by the dataplane to terminate multicast encapsulated * vxlan traffic */ struct interface *term_if; }; extern void pim_vxlan_init(struct pim_instance *pim); extern void pim_vxlan_exit(struct pim_instance *pim); #endif /* PIM_VXLAN_INSTANCE_H */ frr-7.2.1/pimd/pim_zebra.c0000644000000000000000000010327613610377563012272 00000000000000/* * PIM for Quagga * Copyright (C) 2008 Everton da Silva Marques * * This program is free software; you can redistribute it and/or modify * it under the terms of the 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; see the file COPYING; if not, write to the Free Software * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA */ #include #include "if.h" #include "log.h" #include "prefix.h" #include "zclient.h" #include "stream.h" #include "network.h" #include "vty.h" #include "plist.h" #include "lib/bfd.h" #include "pimd.h" #include "pim_pim.h" #include "pim_zebra.h" #include "pim_iface.h" #include "pim_str.h" #include "pim_oil.h" #include "pim_rpf.h" #include "pim_time.h" #include "pim_join.h" #include "pim_zlookup.h" #include "pim_ifchannel.h" #include "pim_rp.h" #include "pim_igmpv3.h" #include "pim_jp_agg.h" #include "pim_nht.h" #include "pim_ssm.h" #include "pim_vxlan.h" #undef PIM_DEBUG_IFADDR_DUMP #define PIM_DEBUG_IFADDR_DUMP static struct zclient *zclient = NULL; /* Router-id update message from zebra. */ static int pim_router_id_update_zebra(ZAPI_CALLBACK_ARGS) { struct prefix router_id; zebra_router_id_update_read(zclient->ibuf, &router_id); return 0; } static int pim_zebra_if_add(ZAPI_CALLBACK_ARGS) { struct interface *ifp; struct pim_instance *pim; /* zebra api adds/dels interfaces using the same call interface_add_read below, see comments in lib/zclient.c */ ifp = zebra_interface_add_read(zclient->ibuf, vrf_id); if (!ifp) return 0; pim = pim_get_pim_instance(vrf_id); if (PIM_DEBUG_ZEBRA) { zlog_debug( "%s: %s index %d(%u) flags %ld metric %d mtu %d operative %d", __PRETTY_FUNCTION__, ifp->name, ifp->ifindex, vrf_id, (long)ifp->flags, ifp->metric, ifp->mtu, if_is_operative(ifp)); } if (if_is_operative(ifp)) { struct pim_interface *pim_ifp; pim_ifp = ifp->info; /* * If we have a pim_ifp already and this is an if_add * that means that we probably have a vrf move event * If that is the case, set the proper vrfness. */ if (pim_ifp) pim_ifp->pim = pim; pim_if_addr_add_all(ifp); } /* * If we are a vrf device that is up, open up the pim_socket for * listening * to incoming pim messages irrelevant if the user has configured us * for pim or not. */ if (pim_if_is_vrf_device(ifp)) { struct pim_interface *pim_ifp; if (!ifp->info) { pim_ifp = pim_if_new(ifp, false, false, false, false /*vxlan_term*/); ifp->info = pim_ifp; } pim_sock_add(ifp); } if (!strncmp(ifp->name, PIM_VXLAN_TERM_DEV_NAME, sizeof(PIM_VXLAN_TERM_DEV_NAME))) pim_vxlan_add_term_dev(pim, ifp); return 0; } static int pim_zebra_if_del(ZAPI_CALLBACK_ARGS) { struct interface *ifp; struct pim_instance *pim; /* zebra api adds/dels interfaces using the same call interface_add_read below, see comments in lib/zclient.c comments in lib/zclient.c seem to indicate that calling zebra_interface_add_read is the correct call, but that results in an attemted out of bounds read which causes pimd to assert. Other clients use zebra_interface_state_read and it appears to work just fine. */ ifp = zebra_interface_state_read(zclient->ibuf, vrf_id); if (!ifp) return 0; if (PIM_DEBUG_ZEBRA) { zlog_debug( "%s: %s index %d(%u) flags %ld metric %d mtu %d operative %d", __PRETTY_FUNCTION__, ifp->name, ifp->ifindex, vrf_id, (long)ifp->flags, ifp->metric, ifp->mtu, if_is_operative(ifp)); } if (!if_is_operative(ifp)) pim_if_addr_del_all(ifp); if_set_index(ifp, IFINDEX_INTERNAL); pim = pim_get_pim_instance(vrf_id); if (pim && pim->vxlan.term_if == ifp) pim_vxlan_del_term_dev(pim); return 0; } static int pim_zebra_if_state_up(ZAPI_CALLBACK_ARGS) { struct pim_instance *pim; struct interface *ifp; uint32_t table_id; /* zebra api notifies interface up/down events by using the same call zebra_interface_state_read below, see comments in lib/zclient.c */ ifp = zebra_interface_state_read(zclient->ibuf, vrf_id); if (!ifp) return 0; if (PIM_DEBUG_ZEBRA) { zlog_debug( "%s: %s index %d(%u) flags %ld metric %d mtu %d operative %d", __PRETTY_FUNCTION__, ifp->name, ifp->ifindex, vrf_id, (long)ifp->flags, ifp->metric, ifp->mtu, if_is_operative(ifp)); } pim = pim_get_pim_instance(vrf_id); if (if_is_operative(ifp)) { struct pim_interface *pim_ifp; pim_ifp = ifp->info; /* * If we have a pim_ifp already and this is an if_add * that means that we probably have a vrf move event * If that is the case, set the proper vrfness. */ if (pim_ifp) pim_ifp->pim = pim; /* pim_if_addr_add_all() suffices for bringing up both IGMP and PIM */ pim_if_addr_add_all(ifp); } /* * If we have a pimreg device callback and it's for a specific * table set the master appropriately */ if (sscanf(ifp->name, "pimreg%" SCNu32, &table_id) == 1) { struct vrf *vrf; RB_FOREACH (vrf, vrf_name_head, &vrfs_by_name) { if ((table_id == vrf->data.l.table_id) && (ifp->vrf_id != vrf->vrf_id)) { struct interface *master = if_lookup_by_name( vrf->name, vrf->vrf_id); if (!master) { zlog_debug( "%s: Unable to find Master interface for %s", __PRETTY_FUNCTION__, vrf->name); return 0; } zclient_interface_set_master(zclient, master, ifp); } } } return 0; } static int pim_zebra_if_state_down(ZAPI_CALLBACK_ARGS) { struct interface *ifp; /* zebra api notifies interface up/down events by using the same call zebra_interface_state_read below, see comments in lib/zclient.c */ ifp = zebra_interface_state_read(zclient->ibuf, vrf_id); if (!ifp) return 0; if (PIM_DEBUG_ZEBRA) { zlog_debug( "%s: %s index %d(%u) flags %ld metric %d mtu %d operative %d", __PRETTY_FUNCTION__, ifp->name, ifp->ifindex, vrf_id, (long)ifp->flags, ifp->metric, ifp->mtu, if_is_operative(ifp)); } if (!if_is_operative(ifp)) { pim_ifchannel_delete_all(ifp); /* pim_if_addr_del_all() suffices for shutting down IGMP, but not for shutting down PIM */ pim_if_addr_del_all(ifp); /* pim_sock_delete() closes the socket, stops read and timer threads, and kills all neighbors. */ if (ifp->info) { pim_sock_delete(ifp, "link down"); } } if (ifp->info) pim_if_del_vif(ifp); return 0; } static int pim_zebra_interface_vrf_update(ZAPI_CALLBACK_ARGS) { struct interface *ifp; vrf_id_t new_vrf_id; ifp = zebra_interface_vrf_update_read(zclient->ibuf, vrf_id, &new_vrf_id); if (!ifp) return 0; if (PIM_DEBUG_ZEBRA) zlog_debug("%s: %s updating from %u to %u", __PRETTY_FUNCTION__, ifp->name, vrf_id, new_vrf_id); if_update_to_new_vrf(ifp, new_vrf_id); return 0; } #ifdef PIM_DEBUG_IFADDR_DUMP static void dump_if_address(struct interface *ifp) { struct connected *ifc; struct listnode *node; zlog_debug("%s %s: interface %s addresses:", __FILE__, __PRETTY_FUNCTION__, ifp->name); for (ALL_LIST_ELEMENTS_RO(ifp->connected, node, ifc)) { struct prefix *p = ifc->address; if (p->family != AF_INET) continue; zlog_debug("%s %s: interface %s address %s %s", __FILE__, __PRETTY_FUNCTION__, ifp->name, inet_ntoa(p->u.prefix4), CHECK_FLAG(ifc->flags, ZEBRA_IFA_SECONDARY) ? "secondary" : "primary"); } } #endif static int pim_zebra_if_address_add(ZAPI_CALLBACK_ARGS) { struct connected *c; struct prefix *p; struct pim_interface *pim_ifp; struct pim_instance *pim; /* zebra api notifies address adds/dels events by using the same call interface_add_read below, see comments in lib/zclient.c zebra_interface_address_read(ZEBRA_INTERFACE_ADDRESS_ADD, ...) will add address to interface list by calling connected_add_by_prefix() */ c = zebra_interface_address_read(cmd, zclient->ibuf, vrf_id); if (!c) return 0; pim_ifp = c->ifp->info; p = c->address; if (PIM_DEBUG_ZEBRA) { char buf[BUFSIZ]; prefix2str(p, buf, BUFSIZ); zlog_debug("%s: %s(%u) connected IP address %s flags %u %s", __PRETTY_FUNCTION__, c->ifp->name, vrf_id, buf, c->flags, CHECK_FLAG(c->flags, ZEBRA_IFA_SECONDARY) ? "secondary" : "primary"); #ifdef PIM_DEBUG_IFADDR_DUMP dump_if_address(c->ifp); #endif } if (!CHECK_FLAG(c->flags, ZEBRA_IFA_SECONDARY)) { /* trying to add primary address */ struct in_addr primary_addr = pim_find_primary_addr(c->ifp); if (p->family != AF_INET || primary_addr.s_addr != p->u.prefix4.s_addr) { if (PIM_DEBUG_ZEBRA) { /* but we had a primary address already */ char buf[BUFSIZ]; prefix2str(p, buf, BUFSIZ); zlog_warn( "%s: %s : forcing secondary flag on %s", __PRETTY_FUNCTION__, c->ifp->name, buf); } SET_FLAG(c->flags, ZEBRA_IFA_SECONDARY); } } pim_if_addr_add(c); if (pim_ifp) { pim = pim_get_pim_instance(vrf_id); pim_ifp->pim = pim; pim_rp_check_on_if_add(pim_ifp); } if (if_is_loopback(c->ifp)) { struct vrf *vrf = vrf_lookup_by_id(VRF_DEFAULT); struct interface *ifp; FOR_ALL_INTERFACES (vrf, ifp) { if (!if_is_loopback(ifp) && if_is_operative(ifp)) pim_if_addr_add_all(ifp); } } return 0; } static int pim_zebra_if_address_del(ZAPI_CALLBACK_ARGS) { struct connected *c; struct prefix *p; struct vrf *vrf = vrf_lookup_by_id(vrf_id); struct pim_instance *pim; if (!vrf) return 0; pim = vrf->info; /* zebra api notifies address adds/dels events by using the same call interface_add_read below, see comments in lib/zclient.c zebra_interface_address_read(ZEBRA_INTERFACE_ADDRESS_DELETE, ...) will remove address from interface list by calling connected_delete_by_prefix() */ c = zebra_interface_address_read(cmd, zclient->ibuf, vrf_id); if (!c) return 0; p = c->address; if (p->family == AF_INET) { if (PIM_DEBUG_ZEBRA) { char buf[BUFSIZ]; prefix2str(p, buf, BUFSIZ); zlog_debug( "%s: %s(%u) disconnected IP address %s flags %u %s", __PRETTY_FUNCTION__, c->ifp->name, vrf_id, buf, c->flags, CHECK_FLAG(c->flags, ZEBRA_IFA_SECONDARY) ? "secondary" : "primary"); #ifdef PIM_DEBUG_IFADDR_DUMP dump_if_address(c->ifp); #endif } pim_if_addr_del(c, 0); pim_rp_setup(pim); pim_i_am_rp_re_evaluate(pim); } connected_free(c); return 0; } void pim_zebra_update_all_interfaces(struct pim_instance *pim) { struct interface *ifp; FOR_ALL_INTERFACES (pim->vrf, ifp) { struct pim_interface *pim_ifp = ifp->info; struct pim_iface_upstream_switch *us; struct listnode *node; if (!pim_ifp) continue; for (ALL_LIST_ELEMENTS_RO(pim_ifp->upstream_switch_list, node, us)) { struct pim_rpf rpf; rpf.source_nexthop.interface = ifp; rpf.rpf_addr.u.prefix4 = us->address; pim_joinprune_send(&rpf, us->us); pim_jp_agg_clear_group(us->us); } } } void pim_zebra_upstream_rpf_changed(struct pim_instance *pim, struct pim_upstream *up, struct pim_rpf *old) { if (old->source_nexthop.interface) { struct pim_neighbor *nbr; nbr = pim_neighbor_find(old->source_nexthop.interface, old->rpf_addr.u.prefix4); if (nbr) pim_jp_agg_remove_group(nbr->upstream_jp_agg, up); /* * We have detected a case where we might need * to rescan the inherited o_list so do it. */ if (up->channel_oil->oil_inherited_rescan) { pim_upstream_inherited_olist_decide(pim, up); up->channel_oil->oil_inherited_rescan = 0; } if (up->join_state == PIM_UPSTREAM_JOINED) { /* * If we come up real fast we can be here * where the mroute has not been installed * so install it. */ if (!up->channel_oil->installed) pim_mroute_add(up->channel_oil, __PRETTY_FUNCTION__); /* * RFC 4601: 4.5.7. Sending (S,G) * Join/Prune Messages * * Transitions from Joined State * * RPF'(S,G) changes not due to an Assert * * The upstream (S,G) state machine remains * in Joined state. Send Join(S,G) to the new * upstream neighbor, which is the new value * of RPF'(S,G). Send Prune(S,G) to the old * upstream neighbor, which is the old value * of RPF'(S,G). Set the Join Timer (JT) to * expire after t_periodic seconds. */ pim_jp_agg_switch_interface(old, &up->rpf, up); pim_upstream_join_timer_restart(up, old); } /* up->join_state == PIM_UPSTREAM_JOINED */ } else { /* * We have detected a case where we might need * to rescan the inherited o_list so do it. */ if (up->channel_oil->oil_inherited_rescan) { pim_upstream_inherited_olist_decide(pim, up); up->channel_oil->oil_inherited_rescan = 0; } if (!up->channel_oil->installed) pim_mroute_add(up->channel_oil, __PRETTY_FUNCTION__); } /* FIXME can join_desired actually be changed by pim_rpf_update() * returning PIM_RPF_CHANGED ? */ pim_upstream_update_join_desired(pim, up); } static int pim_zebra_vxlan_sg_proc(ZAPI_CALLBACK_ARGS) { struct stream *s; struct pim_instance *pim; struct prefix_sg sg; pim = pim_get_pim_instance(vrf_id); if (!pim) return 0; s = zclient->ibuf; sg.family = AF_INET; sg.prefixlen = stream_getl(s); stream_get(&sg.src.s_addr, s, sg.prefixlen); stream_get(&sg.grp.s_addr, s, sg.prefixlen); if (PIM_DEBUG_ZEBRA) { char sg_str[PIM_SG_LEN]; pim_str_sg_set(&sg, sg_str); zlog_debug("%u:recv SG %s %s", vrf_id, (cmd == ZEBRA_VXLAN_SG_ADD)?"add":"del", sg_str); } if (cmd == ZEBRA_VXLAN_SG_ADD) pim_vxlan_sg_add(pim, &sg); else pim_vxlan_sg_del(pim, &sg); return 0; } static void pim_zebra_vxlan_replay(void) { struct stream *s = NULL; /* Check socket. */ if (!zclient || zclient->sock < 0) return; s = zclient->obuf; stream_reset(s); zclient_create_header(s, ZEBRA_VXLAN_SG_REPLAY, VRF_DEFAULT); stream_putw_at(s, 0, stream_get_endp(s)); zclient_send_message(zclient); } void pim_scan_individual_oil(struct channel_oil *c_oil, int in_vif_index) { struct in_addr vif_source; int input_iface_vif_index; pim_rp_set_upstream_addr(c_oil->pim, &vif_source, c_oil->oil.mfcc_origin, c_oil->oil.mfcc_mcastgrp); if (in_vif_index) input_iface_vif_index = in_vif_index; else { struct prefix src, grp; src.family = AF_INET; src.prefixlen = IPV4_MAX_BITLEN; src.u.prefix4 = vif_source; grp.family = AF_INET; grp.prefixlen = IPV4_MAX_BITLEN; grp.u.prefix4 = c_oil->oil.mfcc_mcastgrp; if (PIM_DEBUG_ZEBRA) { char source_str[INET_ADDRSTRLEN]; char group_str[INET_ADDRSTRLEN]; pim_inet4_dump("", c_oil->oil.mfcc_origin, source_str, sizeof(source_str)); pim_inet4_dump("", c_oil->oil.mfcc_mcastgrp, group_str, sizeof(group_str)); zlog_debug( "%s: channel_oil (%s,%s) upstream info is not present.", __PRETTY_FUNCTION__, source_str, group_str); } input_iface_vif_index = pim_ecmp_fib_lookup_if_vif_index( c_oil->pim, &src, &grp); } if (input_iface_vif_index < 1) { if (PIM_DEBUG_ZEBRA) { char source_str[INET_ADDRSTRLEN]; char group_str[INET_ADDRSTRLEN]; pim_inet4_dump("", c_oil->oil.mfcc_origin, source_str, sizeof(source_str)); pim_inet4_dump("", c_oil->oil.mfcc_mcastgrp, group_str, sizeof(group_str)); zlog_debug( "%s %s: could not find input interface(%d) for (S,G)=(%s,%s)", __FILE__, __PRETTY_FUNCTION__, c_oil->oil.mfcc_parent, source_str, group_str); } pim_mroute_del(c_oil, __PRETTY_FUNCTION__); return; } if (input_iface_vif_index == c_oil->oil.mfcc_parent) { if (!c_oil->installed) pim_mroute_add(c_oil, __PRETTY_FUNCTION__); /* RPF unchanged */ return; } if (PIM_DEBUG_ZEBRA) { struct interface *old_iif = pim_if_find_by_vif_index( c_oil->pim, c_oil->oil.mfcc_parent); struct interface *new_iif = pim_if_find_by_vif_index( c_oil->pim, input_iface_vif_index); char source_str[INET_ADDRSTRLEN]; char group_str[INET_ADDRSTRLEN]; pim_inet4_dump("", c_oil->oil.mfcc_origin, source_str, sizeof(source_str)); pim_inet4_dump("", c_oil->oil.mfcc_mcastgrp, group_str, sizeof(group_str)); zlog_debug( "%s %s: (S,G)=(%s,%s) input interface changed from %s vif_index=%d to %s vif_index=%d", __FILE__, __PRETTY_FUNCTION__, source_str, group_str, (old_iif) ? old_iif->name : "", c_oil->oil.mfcc_parent, (new_iif) ? new_iif->name : "", input_iface_vif_index); } /* new iif loops to existing oif ? */ if (c_oil->oil.mfcc_ttls[input_iface_vif_index]) { struct interface *new_iif = pim_if_find_by_vif_index( c_oil->pim, input_iface_vif_index); if (PIM_DEBUG_ZEBRA) { char source_str[INET_ADDRSTRLEN]; char group_str[INET_ADDRSTRLEN]; pim_inet4_dump("", c_oil->oil.mfcc_origin, source_str, sizeof(source_str)); pim_inet4_dump("", c_oil->oil.mfcc_mcastgrp, group_str, sizeof(group_str)); zlog_debug( "%s %s: (S,G)=(%s,%s) new iif loops to existing oif: %s vif_index=%d", __FILE__, __PRETTY_FUNCTION__, source_str, group_str, (new_iif) ? new_iif->name : "", input_iface_vif_index); } } /* update iif vif_index */ pim_channel_oil_change_iif(c_oil->pim, c_oil, input_iface_vif_index, __PRETTY_FUNCTION__); pim_mroute_add(c_oil, __PRETTY_FUNCTION__); } void pim_scan_oil(struct pim_instance *pim) { struct listnode *node; struct listnode *nextnode; struct channel_oil *c_oil; ifindex_t ifindex; int vif_index = 0; pim->scan_oil_last = pim_time_monotonic_sec(); ++pim->scan_oil_events; for (ALL_LIST_ELEMENTS(pim->channel_oil_list, node, nextnode, c_oil)) { if (c_oil->up && c_oil->up->rpf.source_nexthop.interface) { ifindex = c_oil->up->rpf.source_nexthop .interface->ifindex; vif_index = pim_if_find_vifindex_by_ifindex(pim, ifindex); /* Pass Current selected NH vif index to mroute * download */ if (vif_index) pim_scan_individual_oil(c_oil, vif_index); } else pim_scan_individual_oil(c_oil, 0); } } static int on_rpf_cache_refresh(struct thread *t) { struct pim_instance *pim = THREAD_ARG(t); /* update kernel multicast forwarding cache (MFC) */ pim_scan_oil(pim); pim->rpf_cache_refresh_last = pim_time_monotonic_sec(); ++pim->rpf_cache_refresh_events; // It is called as part of pim_neighbor_add // pim_rp_setup (); return 0; } void sched_rpf_cache_refresh(struct pim_instance *pim) { ++pim->rpf_cache_refresh_requests; pim_rpf_set_refresh_time(pim); if (pim->rpf_cache_refresher) { /* Refresh timer is already running */ return; } /* Start refresh timer */ if (PIM_DEBUG_ZEBRA) { zlog_debug("%s: triggering %ld msec timer", __PRETTY_FUNCTION__, router->rpf_cache_refresh_delay_msec); } thread_add_timer_msec(router->master, on_rpf_cache_refresh, pim, router->rpf_cache_refresh_delay_msec, &pim->rpf_cache_refresher); } static void pim_zebra_connected(struct zclient *zclient) { /* Send the client registration */ bfd_client_sendmsg(zclient, ZEBRA_BFD_CLIENT_REGISTER, router->vrf_id); zclient_send_reg_requests(zclient, router->vrf_id); /* request for VxLAN BUM group addresses */ pim_zebra_vxlan_replay(); } static void pim_zebra_capabilities(struct zclient_capabilities *cap) { router->role = cap->role; } void pim_zebra_init(void) { /* Socket for receiving updates from Zebra daemon */ zclient = zclient_new(router->master, &zclient_options_default); zclient->zebra_capabilities = pim_zebra_capabilities; zclient->zebra_connected = pim_zebra_connected; zclient->router_id_update = pim_router_id_update_zebra; zclient->interface_add = pim_zebra_if_add; zclient->interface_delete = pim_zebra_if_del; zclient->interface_up = pim_zebra_if_state_up; zclient->interface_down = pim_zebra_if_state_down; zclient->interface_address_add = pim_zebra_if_address_add; zclient->interface_address_delete = pim_zebra_if_address_del; zclient->interface_vrf_update = pim_zebra_interface_vrf_update; zclient->nexthop_update = pim_parse_nexthop_update; zclient->vxlan_sg_add = pim_zebra_vxlan_sg_proc; zclient->vxlan_sg_del = pim_zebra_vxlan_sg_proc; zclient_init(zclient, ZEBRA_ROUTE_PIM, 0, &pimd_privs); if (PIM_DEBUG_PIM_TRACE) { zlog_notice("%s: zclient socket initialized", __PRETTY_FUNCTION__); } zclient_lookup_new(); } void igmp_anysource_forward_start(struct pim_instance *pim, struct igmp_group *group) { struct igmp_source *source; struct in_addr src_addr = {.s_addr = 0}; /* Any source (*,G) is forwarded only if mode is EXCLUDE {empty} */ zassert(group->group_filtermode_isexcl); zassert(listcount(group->group_source_list) < 1); source = source_new(group, src_addr); if (!source) { zlog_warn("%s: Failure to create * source", __PRETTY_FUNCTION__); return; } igmp_source_forward_start(pim, source); } void igmp_anysource_forward_stop(struct igmp_group *group) { struct igmp_source *source; struct in_addr star = {.s_addr = 0}; source = igmp_find_source_by_addr(group, star); if (source) igmp_source_forward_stop(source); } static void igmp_source_forward_reevaluate_one(struct pim_instance *pim, struct igmp_source *source) { struct prefix_sg sg; struct igmp_group *group = source->source_group; struct pim_ifchannel *ch; if ((source->source_addr.s_addr != INADDR_ANY) || !IGMP_SOURCE_TEST_FORWARDING(source->source_flags)) return; memset(&sg, 0, sizeof(struct prefix_sg)); sg.src = source->source_addr; sg.grp = group->group_addr; ch = pim_ifchannel_find(group->group_igmp_sock->interface, &sg); if (pim_is_grp_ssm(pim, group->group_addr)) { /* If SSM group withdraw local membership */ if (ch && (ch->local_ifmembership == PIM_IFMEMBERSHIP_INCLUDE)) { if (PIM_DEBUG_PIM_EVENTS) zlog_debug( "local membership del for %s as G is now SSM", pim_str_sg_dump(&sg)); pim_ifchannel_local_membership_del( group->group_igmp_sock->interface, &sg); } } else { /* If ASM group add local membership */ if (!ch || (ch->local_ifmembership == PIM_IFMEMBERSHIP_NOINFO)) { if (PIM_DEBUG_PIM_EVENTS) zlog_debug( "local membership add for %s as G is now ASM", pim_str_sg_dump(&sg)); pim_ifchannel_local_membership_add( group->group_igmp_sock->interface, &sg); } } } void igmp_source_forward_reevaluate_all(struct pim_instance *pim) { struct interface *ifp; FOR_ALL_INTERFACES (pim->vrf, ifp) { struct pim_interface *pim_ifp = ifp->info; struct listnode *sock_node; struct igmp_sock *igmp; if (!pim_ifp) continue; /* scan igmp sockets */ for (ALL_LIST_ELEMENTS_RO(pim_ifp->igmp_socket_list, sock_node, igmp)) { struct listnode *grpnode; struct igmp_group *grp; /* scan igmp groups */ for (ALL_LIST_ELEMENTS_RO(igmp->igmp_group_list, grpnode, grp)) { struct listnode *srcnode; struct igmp_source *src; /* scan group sources */ for (ALL_LIST_ELEMENTS_RO( grp->group_source_list, srcnode, src)) { igmp_source_forward_reevaluate_one(pim, src); } /* scan group sources */ } /* scan igmp groups */ } /* scan igmp sockets */ } /* scan interfaces */ } void igmp_source_forward_start(struct pim_instance *pim, struct igmp_source *source) { struct pim_interface *pim_oif; struct igmp_group *group; struct prefix_sg sg; int result; int input_iface_vif_index = 0; memset(&sg, 0, sizeof(struct prefix_sg)); sg.src = source->source_addr; sg.grp = source->source_group->group_addr; if (PIM_DEBUG_IGMP_TRACE) { zlog_debug( "%s: (S,G)=%s igmp_sock=%d oif=%s fwd=%d", __PRETTY_FUNCTION__, pim_str_sg_dump(&sg), source->source_group->group_igmp_sock->fd, source->source_group->group_igmp_sock->interface->name, IGMP_SOURCE_TEST_FORWARDING(source->source_flags)); } /* Prevent IGMP interface from installing multicast route multiple times */ if (IGMP_SOURCE_TEST_FORWARDING(source->source_flags)) { return; } group = source->source_group; pim_oif = group->group_igmp_sock->interface->info; if (!pim_oif) { if (PIM_DEBUG_IGMP_TRACE) { zlog_debug( "%s: multicast not enabled on oif=%s ?", __PRETTY_FUNCTION__, source->source_group->group_igmp_sock ->interface->name); } return; } if (!source->source_channel_oil) { struct in_addr vif_source; struct prefix src, grp; struct pim_nexthop nexthop; struct pim_upstream *up = NULL; if (!pim_rp_set_upstream_addr(pim, &vif_source, source->source_addr, sg.grp)) { /*Create a dummy channel oil */ source->source_channel_oil = pim_channel_oil_add( pim, &sg, MAXVIFS, __PRETTY_FUNCTION__); } else { src.family = AF_INET; src.prefixlen = IPV4_MAX_BITLEN; src.u.prefix4 = vif_source; // RP or Src address grp.family = AF_INET; grp.prefixlen = IPV4_MAX_BITLEN; grp.u.prefix4 = sg.grp; up = pim_upstream_find(pim, &sg); if (up) { memcpy(&nexthop, &up->rpf.source_nexthop, sizeof(struct pim_nexthop)); pim_ecmp_nexthop_lookup(pim, &nexthop, &src, &grp, 0); if (nexthop.interface) input_iface_vif_index = pim_if_find_vifindex_by_ifindex( pim, nexthop.interface->ifindex); } else input_iface_vif_index = pim_ecmp_fib_lookup_if_vif_index( pim, &src, &grp); if (PIM_DEBUG_ZEBRA) { char buf2[INET_ADDRSTRLEN]; pim_inet4_dump("", vif_source, buf2, sizeof(buf2)); zlog_debug("%s: NHT %s vif_source %s vif_index:%d ", __PRETTY_FUNCTION__, pim_str_sg_dump(&sg), buf2, input_iface_vif_index); } if (input_iface_vif_index < 1) { if (PIM_DEBUG_IGMP_TRACE) { char source_str[INET_ADDRSTRLEN]; pim_inet4_dump("", source->source_addr, source_str, sizeof(source_str)); zlog_debug( "%s %s: could not find input interface for source %s", __FILE__, __PRETTY_FUNCTION__, source_str); } source->source_channel_oil = pim_channel_oil_add( pim, &sg, MAXVIFS, __PRETTY_FUNCTION__); } else { /* * Protect IGMP against adding looped MFC * entries created by both source and receiver * attached to the same interface. See TODO * T22. Block only when the intf is non DR * DR must create upstream. */ if ((input_iface_vif_index == pim_oif->mroute_vif_index) && !(PIM_I_am_DR(pim_oif))) { /* ignore request for looped MFC entry */ if (PIM_DEBUG_IGMP_TRACE) { zlog_debug( "%s: ignoring request for looped MFC entry (S,G)=%s: igmp_sock=%d oif=%s vif_index=%d", __PRETTY_FUNCTION__, pim_str_sg_dump(&sg), source->source_group ->group_igmp_sock->fd, source->source_group ->group_igmp_sock ->interface->name, input_iface_vif_index); } return; } source->source_channel_oil = pim_channel_oil_add( pim, &sg, input_iface_vif_index, __PRETTY_FUNCTION__); if (!source->source_channel_oil) { if (PIM_DEBUG_IGMP_TRACE) { zlog_debug( "%s %s: could not create OIL for channel (S,G)=%s", __FILE__, __PRETTY_FUNCTION__, pim_str_sg_dump(&sg)); } return; } } } } result = pim_channel_add_oif(source->source_channel_oil, group->group_igmp_sock->interface, PIM_OIF_FLAG_PROTO_IGMP); if (result) { if (PIM_DEBUG_MROUTE) { zlog_warn("%s: add_oif() failed with return=%d", __func__, result); } return; } if (!(PIM_I_am_DR(pim_oif))) { if (PIM_DEBUG_IGMP_TRACE) zlog_debug("%s: %s was received on %s interface but we are not DR for that interface", __PRETTY_FUNCTION__, pim_str_sg_dump(&sg), group->group_igmp_sock->interface->name); pim_channel_del_oif(source->source_channel_oil, group->group_igmp_sock->interface, PIM_OIF_FLAG_PROTO_IGMP); return; } /* Feed IGMPv3-gathered local membership information into PIM per-interface (S,G) state. */ if (!pim_ifchannel_local_membership_add( group->group_igmp_sock->interface, &sg)) { if (PIM_DEBUG_MROUTE) zlog_warn("%s: Failure to add local membership for %s", __PRETTY_FUNCTION__, pim_str_sg_dump(&sg)); pim_channel_del_oif(source->source_channel_oil, group->group_igmp_sock->interface, PIM_OIF_FLAG_PROTO_IGMP); return; } IGMP_SOURCE_DO_FORWARDING(source->source_flags); } /* igmp_source_forward_stop: stop fowarding, but keep the source igmp_source_delete: stop fowarding, and delete the source */ void igmp_source_forward_stop(struct igmp_source *source) { struct igmp_group *group; struct prefix_sg sg; int result; memset(&sg, 0, sizeof(struct prefix_sg)); sg.src = source->source_addr; sg.grp = source->source_group->group_addr; if (PIM_DEBUG_IGMP_TRACE) { zlog_debug( "%s: (S,G)=%s igmp_sock=%d oif=%s fwd=%d", __PRETTY_FUNCTION__, pim_str_sg_dump(&sg), source->source_group->group_igmp_sock->fd, source->source_group->group_igmp_sock->interface->name, IGMP_SOURCE_TEST_FORWARDING(source->source_flags)); } /* Prevent IGMP interface from removing multicast route multiple times */ if (!IGMP_SOURCE_TEST_FORWARDING(source->source_flags)) { return; } group = source->source_group; /* It appears that in certain circumstances that igmp_source_forward_stop is called when IGMP forwarding was not enabled in oif_flags for this outgoing interface. Possibly because of multiple calls. When that happens, we enter the below if statement and this function returns early which in turn triggers the calling function to assert. Making the call to pim_channel_del_oif and ignoring the return code fixes the issue without ill effect, similar to pim_forward_stop below. */ result = pim_channel_del_oif(source->source_channel_oil, group->group_igmp_sock->interface, PIM_OIF_FLAG_PROTO_IGMP); if (result) { if (PIM_DEBUG_IGMP_TRACE) zlog_debug( "%s: pim_channel_del_oif() failed with return=%d", __func__, result); return; } /* Feed IGMPv3-gathered local membership information into PIM per-interface (S,G) state. */ pim_ifchannel_local_membership_del(group->group_igmp_sock->interface, &sg); IGMP_SOURCE_DONT_FORWARDING(source->source_flags); } void pim_forward_start(struct pim_ifchannel *ch) { struct pim_upstream *up = ch->upstream; uint32_t mask = PIM_OIF_FLAG_PROTO_PIM; int input_iface_vif_index = 0; struct pim_instance *pim; struct pim_interface *pim_ifp; pim_ifp = ch->interface->info; pim = pim_ifp->pim; if (PIM_DEBUG_PIM_TRACE) { char source_str[INET_ADDRSTRLEN]; char group_str[INET_ADDRSTRLEN]; char upstream_str[INET_ADDRSTRLEN]; pim_inet4_dump("", ch->sg.src, source_str, sizeof(source_str)); pim_inet4_dump("", ch->sg.grp, group_str, sizeof(group_str)); pim_inet4_dump("", up->upstream_addr, upstream_str, sizeof(upstream_str)); zlog_debug("%s: (S,G)=(%s,%s) oif=%s (%s)", __PRETTY_FUNCTION__, source_str, group_str, ch->interface->name, inet_ntoa(up->upstream_addr)); } /* Resolve IIF for upstream as mroute_del sets mfcc_parent to MAXVIFS, as part of mroute_del called by pim_forward_stop. */ if ((up->upstream_addr.s_addr != INADDR_ANY) && (!up->channel_oil)) { struct prefix src, grp; grp.family = AF_INET; grp.prefixlen = IPV4_MAX_BITLEN; grp.u.prefix4 = up->sg.grp; src.family = AF_INET; src.prefixlen = IPV4_MAX_BITLEN; src.u.prefix4 = up->sg.src; if (pim_ecmp_nexthop_lookup(pim, &up->rpf.source_nexthop, &src, &grp, 0)) input_iface_vif_index = pim_if_find_vifindex_by_ifindex( pim, up->rpf.source_nexthop.interface->ifindex); if (input_iface_vif_index < 1) { if (PIM_DEBUG_PIM_TRACE) { char source_str[INET_ADDRSTRLEN]; pim_inet4_dump("", up->sg.src, source_str, sizeof(source_str)); zlog_debug( "%s %s: could not find input interface for source %s", __FILE__, __PRETTY_FUNCTION__, source_str); } pim_channel_oil_change_iif(pim, up->channel_oil, MAXVIFS, __PRETTY_FUNCTION__); } else pim_channel_oil_change_iif(pim, up->channel_oil, input_iface_vif_index, __PRETTY_FUNCTION__); if (PIM_DEBUG_TRACE) { struct interface *in_intf = pim_if_find_by_vif_index( pim, input_iface_vif_index); zlog_debug( "%s: Update channel_oil IIF %s VIFI %d entry %s ", __PRETTY_FUNCTION__, in_intf ? in_intf->name : "Unknown", input_iface_vif_index, up->sg_str); } } if (up->flags & PIM_UPSTREAM_FLAG_MASK_SRC_IGMP) mask = PIM_OIF_FLAG_PROTO_IGMP; pim_channel_add_oif(up->channel_oil, ch->interface, mask); } void pim_forward_stop(struct pim_ifchannel *ch, bool install_it) { struct pim_upstream *up = ch->upstream; if (PIM_DEBUG_PIM_TRACE) { zlog_debug("%s: (S,G)=%s oif=%s install_it: %d installed: %d", __PRETTY_FUNCTION__, ch->sg_str, ch->interface->name, install_it, up->channel_oil->installed); } /* * If a channel is being removed, check to see if we still need * to inherit the interface. If so make sure it is added in */ if (pim_upstream_evaluate_join_desired_interface(up, ch, ch->parent)) pim_channel_add_oif(up->channel_oil, ch->interface, PIM_OIF_FLAG_PROTO_PIM); else pim_channel_del_oif(up->channel_oil, ch->interface, PIM_OIF_FLAG_PROTO_PIM); if (install_it && !up->channel_oil->installed) pim_mroute_add(up->channel_oil, __PRETTY_FUNCTION__); } void pim_zebra_zclient_update(struct vty *vty) { vty_out(vty, "Zclient update socket: "); if (zclient) { vty_out(vty, "%d failures=%d\n", zclient->sock, zclient->fail); } else { vty_out(vty, "\n"); } } struct zclient *pim_zebra_zclient_get(void) { if (zclient) return zclient; else return NULL; } frr-7.2.1/pimd/pim_zebra.h0000644000000000000000000000364613610377563012277 00000000000000/* * PIM for Quagga * Copyright (C) 2008 Everton da Silva Marques * * This program is free software; you can redistribute it and/or modify * it under the terms of the 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; see the file COPYING; if not, write to the Free Software * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA */ #ifndef PIM_ZEBRA_H #define PIM_ZEBRA_H #include #include "zclient.h" #include "pim_igmp.h" #include "pim_ifchannel.h" void pim_zebra_init(void); void pim_zebra_zclient_update(struct vty *vty); void pim_scan_individual_oil(struct channel_oil *c_oil, int in_vif_index); void pim_scan_oil(struct pim_instance *pim_matcher); void igmp_anysource_forward_start(struct pim_instance *pim, struct igmp_group *group); void igmp_anysource_forward_stop(struct igmp_group *group); void igmp_source_forward_start(struct pim_instance *pim, struct igmp_source *source); void igmp_source_forward_stop(struct igmp_source *source); void igmp_source_forward_reevaluate_all(struct pim_instance *pim); void pim_forward_start(struct pim_ifchannel *ch); void pim_forward_stop(struct pim_ifchannel *ch, bool install_it); void sched_rpf_cache_refresh(struct pim_instance *pim); struct zclient *pim_zebra_zclient_get(void); void pim_zebra_update_all_interfaces(struct pim_instance *pim); void pim_zebra_upstream_rpf_changed(struct pim_instance *pim, struct pim_upstream *up, struct pim_rpf *old); #endif /* PIM_ZEBRA_H */ frr-7.2.1/pimd/pim_zlookup.c0000644000000000000000000003720013610377563012663 00000000000000/* * PIM for Quagga * Copyright (C) 2008 Everton da Silva Marques * * This program is free software; you can redistribute it and/or modify * it under the terms of the 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; see the file COPYING; if not, write to the Free Software * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA */ #include #include "log.h" #include "prefix.h" #include "zclient.h" #include "stream.h" #include "network.h" #include "thread.h" #include "prefix.h" #include "vty.h" #include "lib_errors.h" #include "pimd.h" #include "pim_iface.h" #include "pim_pim.h" #include "pim_str.h" #include "pim_oil.h" #include "pim_zlookup.h" static struct zclient *zlookup = NULL; struct thread *zlookup_read; static void zclient_lookup_sched(struct zclient *zlookup, int delay); static int zclient_lookup_read_pipe(struct thread *thread); /* Connect to zebra for nexthop lookup. */ static int zclient_lookup_connect(struct thread *t) { struct zclient *zlookup; zlookup = THREAD_ARG(t); if (zlookup->sock >= 0) { return 0; } if (zclient_socket_connect(zlookup) < 0) { ++zlookup->fail; zlog_warn("%s: failure connecting zclient socket: failures=%d", __PRETTY_FUNCTION__, zlookup->fail); } else { zlookup->fail = 0; /* reset counter on connection */ } if (zlookup->sock < 0) { /* Since last connect failed, retry within 10 secs */ zclient_lookup_sched(zlookup, 10); return -1; } thread_add_timer(router->master, zclient_lookup_read_pipe, zlookup, 60, &zlookup_read); return 0; } /* Schedule connection with delay. */ static void zclient_lookup_sched(struct zclient *zlookup, int delay) { thread_add_timer(router->master, zclient_lookup_connect, zlookup, delay, &zlookup->t_connect); zlog_notice("%s: zclient lookup connection scheduled for %d seconds", __PRETTY_FUNCTION__, delay); } /* Schedule connection for now. */ static void zclient_lookup_sched_now(struct zclient *zlookup) { thread_add_event(router->master, zclient_lookup_connect, zlookup, 0, &zlookup->t_connect); zlog_notice("%s: zclient lookup immediate connection scheduled", __PRETTY_FUNCTION__); } /* Schedule reconnection, if needed. */ static void zclient_lookup_reconnect(struct zclient *zlookup) { if (zlookup->t_connect) { return; } zclient_lookup_sched_now(zlookup); } static void zclient_lookup_failed(struct zclient *zlookup) { if (zlookup->sock >= 0) { if (close(zlookup->sock)) { zlog_warn("%s: closing fd=%d: errno=%d %s", __func__, zlookup->sock, errno, safe_strerror(errno)); } zlookup->sock = -1; } zclient_lookup_reconnect(zlookup); } void zclient_lookup_free(void) { thread_cancel(zlookup_read); zclient_stop(zlookup); zclient_free(zlookup); zlookup = NULL; } void zclient_lookup_new(void) { zlookup = zclient_new(router->master, &zclient_options_default); if (!zlookup) { flog_err(EC_LIB_ZAPI_SOCKET, "%s: zclient_new() failure", __PRETTY_FUNCTION__); return; } zlookup->sock = -1; zlookup->t_connect = NULL; zlookup->privs = &pimd_privs; zclient_lookup_sched_now(zlookup); zlog_notice("%s: zclient lookup socket initialized", __PRETTY_FUNCTION__); } static int zclient_read_nexthop(struct pim_instance *pim, struct zclient *zlookup, struct pim_zlookup_nexthop nexthop_tab[], const int tab_size, struct in_addr addr) { int num_ifindex = 0; struct stream *s; uint16_t length; uint8_t marker; uint8_t version; vrf_id_t vrf_id; uint16_t command = 0; struct in_addr raddr; uint8_t distance; uint32_t metric; int nexthop_num; int i, err; if (PIM_DEBUG_PIM_NHT_DETAIL) { char addr_str[INET_ADDRSTRLEN]; pim_inet4_dump("", addr, addr_str, sizeof(addr_str)); zlog_debug("%s: addr=%s(%s)", __PRETTY_FUNCTION__, addr_str, pim->vrf->name); } s = zlookup->ibuf; while (command != ZEBRA_IPV4_NEXTHOP_LOOKUP_MRIB) { stream_reset(s); err = zclient_read_header(s, zlookup->sock, &length, &marker, &version, &vrf_id, &command); if (err < 0) { flog_err(EC_LIB_ZAPI_MISSMATCH, "%s: zclient_read_header() failed", __PRETTY_FUNCTION__); zclient_lookup_failed(zlookup); return -1; } } raddr.s_addr = stream_get_ipv4(s); if (raddr.s_addr != addr.s_addr) { char addr_str[INET_ADDRSTRLEN]; char raddr_str[INET_ADDRSTRLEN]; pim_inet4_dump("", addr, addr_str, sizeof(addr_str)); pim_inet4_dump("", raddr, raddr_str, sizeof(raddr_str)); zlog_warn("%s: address mismatch: addr=%s(%s) raddr=%s", __PRETTY_FUNCTION__, addr_str, pim->vrf->name, raddr_str); /* warning only */ } distance = stream_getc(s); metric = stream_getl(s); nexthop_num = stream_getc(s); if (nexthop_num < 1) { if (PIM_DEBUG_PIM_NHT_DETAIL) zlog_debug("%s: socket %d bad nexthop_num=%d", __func__, zlookup->sock, nexthop_num); return -6; } for (i = 0; i < nexthop_num; ++i) { vrf_id_t nexthop_vrf_id; enum nexthop_types_t nexthop_type; struct pim_neighbor *nbr; struct prefix p; nexthop_vrf_id = stream_getl(s); nexthop_type = stream_getc(s); if (num_ifindex >= tab_size) { char addr_str[INET_ADDRSTRLEN]; pim_inet4_dump("", addr, addr_str, sizeof(addr_str)); zlog_warn( "%s: found too many nexthop ifindexes (%d > %d) for address %s(%s)", __PRETTY_FUNCTION__, (num_ifindex + 1), tab_size, addr_str, pim->vrf->name); return num_ifindex; } nexthop_tab[num_ifindex].protocol_distance = distance; nexthop_tab[num_ifindex].route_metric = metric; nexthop_tab[num_ifindex].vrf_id = nexthop_vrf_id; switch (nexthop_type) { case NEXTHOP_TYPE_IFINDEX: nexthop_tab[num_ifindex].ifindex = stream_getl(s); /* * Connected route (i.e. no nexthop), use * address passed in as PIM nexthop. This will * allow us to work in cases where we are * trying to find a route for this box. */ nexthop_tab[num_ifindex].nexthop_addr.family = AF_INET; nexthop_tab[num_ifindex].nexthop_addr.prefixlen = IPV4_MAX_BITLEN; nexthop_tab[num_ifindex].nexthop_addr.u.prefix4 = addr; ++num_ifindex; break; case NEXTHOP_TYPE_IPV4_IFINDEX: case NEXTHOP_TYPE_IPV4: nexthop_tab[num_ifindex].nexthop_addr.family = AF_INET; nexthop_tab[num_ifindex].nexthop_addr.u.prefix4.s_addr = stream_get_ipv4(s); nexthop_tab[num_ifindex].ifindex = stream_getl(s); ++num_ifindex; break; case NEXTHOP_TYPE_IPV6_IFINDEX: nexthop_tab[num_ifindex].nexthop_addr.family = AF_INET6; stream_get(&nexthop_tab[num_ifindex] .nexthop_addr.u.prefix6, s, sizeof(struct in6_addr)); nexthop_tab[num_ifindex].ifindex = stream_getl(s); p.family = AF_INET6; p.prefixlen = IPV6_MAX_PREFIXLEN; memcpy(&p.u.prefix6, &nexthop_tab[num_ifindex].nexthop_addr.u.prefix6, sizeof(struct in6_addr)); /* * If we are sending v6 secondary assume we receive v6 * secondary */ if (pim->send_v6_secondary) nbr = pim_neighbor_find_by_secondary( if_lookup_by_index( nexthop_tab[num_ifindex] .ifindex, nexthop_vrf_id), &p); else nbr = pim_neighbor_find_if(if_lookup_by_index( nexthop_tab[num_ifindex].ifindex, nexthop_vrf_id)); if (nbr) { nexthop_tab[num_ifindex].nexthop_addr.family = AF_INET; nexthop_tab[num_ifindex] .nexthop_addr.u.prefix4 = nbr->source_addr; } ++num_ifindex; break; default: /* do nothing */ { char addr_str[INET_ADDRSTRLEN]; pim_inet4_dump("", addr, addr_str, sizeof(addr_str)); zlog_warn( "%s: found non-ifindex nexthop type=%d for address %s(%s)", __PRETTY_FUNCTION__, nexthop_type, addr_str, pim->vrf->name); } break; } } return num_ifindex; } static int zclient_lookup_nexthop_once(struct pim_instance *pim, struct pim_zlookup_nexthop nexthop_tab[], const int tab_size, struct in_addr addr) { struct stream *s; int ret; if (PIM_DEBUG_PIM_NHT_DETAIL) { char addr_str[INET_ADDRSTRLEN]; pim_inet4_dump("", addr, addr_str, sizeof(addr_str)); zlog_debug("%s: addr=%s(%s)", __PRETTY_FUNCTION__, addr_str, pim->vrf->name); } /* Check socket. */ if (zlookup->sock < 0) { flog_err(EC_LIB_ZAPI_SOCKET, "%s: zclient lookup socket is not connected", __PRETTY_FUNCTION__); zclient_lookup_failed(zlookup); return -1; } if (pim->vrf->vrf_id == VRF_UNKNOWN) { zlog_notice( "%s: VRF: %s does not fully exist yet, delaying lookup", __PRETTY_FUNCTION__, pim->vrf->name); return -1; } s = zlookup->obuf; stream_reset(s); zclient_create_header(s, ZEBRA_IPV4_NEXTHOP_LOOKUP_MRIB, pim->vrf_id); stream_put_in_addr(s, &addr); stream_putw_at(s, 0, stream_get_endp(s)); ret = writen(zlookup->sock, s->data, stream_get_endp(s)); if (ret < 0) { flog_err( EC_LIB_SOCKET, "%s: writen() failure: %d writing to zclient lookup socket", __PRETTY_FUNCTION__, errno); zclient_lookup_failed(zlookup); return -2; } if (ret == 0) { flog_err_sys(EC_LIB_SOCKET, "%s: connection closed on zclient lookup socket", __PRETTY_FUNCTION__); zclient_lookup_failed(zlookup); return -3; } return zclient_read_nexthop(pim, zlookup, nexthop_tab, tab_size, addr); } int zclient_lookup_read_pipe(struct thread *thread) { struct zclient *zlookup = THREAD_ARG(thread); struct pim_instance *pim = pim_get_pim_instance(VRF_DEFAULT); struct pim_zlookup_nexthop nexthop_tab[10]; struct in_addr l = {.s_addr = INADDR_ANY}; zclient_lookup_nexthop_once(pim, nexthop_tab, 10, l); thread_add_timer(router->master, zclient_lookup_read_pipe, zlookup, 60, &zlookup_read); return 1; } int zclient_lookup_nexthop(struct pim_instance *pim, struct pim_zlookup_nexthop nexthop_tab[], const int tab_size, struct in_addr addr, int max_lookup) { int lookup; uint32_t route_metric = 0xFFFFFFFF; uint8_t protocol_distance = 0xFF; pim->nexthop_lookups++; for (lookup = 0; lookup < max_lookup; ++lookup) { int num_ifindex; int first_ifindex; struct prefix nexthop_addr; num_ifindex = zclient_lookup_nexthop_once(pim, nexthop_tab, tab_size, addr); if (num_ifindex < 1) { if (PIM_DEBUG_PIM_NHT) { char addr_str[INET_ADDRSTRLEN]; pim_inet4_dump("", addr, addr_str, sizeof(addr_str)); zlog_debug( "%s: lookup=%d/%d: could not find nexthop ifindex for address %s(%s)", __PRETTY_FUNCTION__, lookup, max_lookup, addr_str, pim->vrf->name); } return -1; } if (lookup < 1) { /* this is the non-recursive lookup - save original * metric/distance */ route_metric = nexthop_tab[0].route_metric; protocol_distance = nexthop_tab[0].protocol_distance; } /* * FIXME: Non-recursive nexthop ensured only for first ifindex. * However, recursive route lookup should really be fixed in * zebra daemon. * See also TODO T24. * * So Zebra for NEXTHOP_TYPE_IPV4 returns the ifindex now since * it was being stored. This Doesn't solve all cases of * recursive lookup but for the most common types it does. */ first_ifindex = nexthop_tab[0].ifindex; nexthop_addr = nexthop_tab[0].nexthop_addr; if (first_ifindex > 0) { /* found: first ifindex is non-recursive nexthop */ if (lookup > 0) { /* Report non-recursive success after first * lookup */ if (PIM_DEBUG_PIM_NHT) { char addr_str[INET_ADDRSTRLEN]; pim_inet4_dump("", addr, addr_str, sizeof(addr_str)); zlog_debug( "%s: lookup=%d/%d: found non-recursive ifindex=%d for address %s(%s) dist=%d met=%d", __PRETTY_FUNCTION__, lookup, max_lookup, first_ifindex, addr_str, pim->vrf->name, nexthop_tab[0] .protocol_distance, nexthop_tab[0].route_metric); } /* use last address as nexthop address */ nexthop_tab[0].nexthop_addr.u.prefix4 = addr; /* report original route metric/distance */ nexthop_tab[0].route_metric = route_metric; nexthop_tab[0].protocol_distance = protocol_distance; } return num_ifindex; } if (PIM_DEBUG_PIM_NHT) { char addr_str[INET_ADDRSTRLEN]; char nexthop_str[PREFIX_STRLEN]; pim_inet4_dump("", addr, addr_str, sizeof(addr_str)); pim_addr_dump("", &nexthop_addr, nexthop_str, sizeof(nexthop_str)); zlog_debug( "%s: lookup=%d/%d: zebra returned recursive nexthop %s for address %s(%s) dist=%d met=%d", __PRETTY_FUNCTION__, lookup, max_lookup, nexthop_str, addr_str, pim->vrf->name, nexthop_tab[0].protocol_distance, nexthop_tab[0].route_metric); } addr = nexthop_addr.u.prefix4; /* use nexthop addr for recursive lookup */ } /* for (max_lookup) */ if (PIM_DEBUG_PIM_NHT) { char addr_str[INET_ADDRSTRLEN]; pim_inet4_dump("", addr, addr_str, sizeof(addr_str)); zlog_warn( "%s: lookup=%d/%d: failure searching recursive nexthop ifindex for address %s(%s)", __PRETTY_FUNCTION__, lookup, max_lookup, addr_str, pim->vrf->name); } return -2; } void pim_zlookup_show_ip_multicast(struct vty *vty) { vty_out(vty, "Zclient lookup socket: "); if (zlookup) { vty_out(vty, "%d failures=%d\n", zlookup->sock, zlookup->fail); } else { vty_out(vty, "\n"); } } int pim_zlookup_sg_statistics(struct channel_oil *c_oil) { struct stream *s = zlookup->obuf; uint16_t command = 0; unsigned long long lastused; struct prefix_sg sg; int count = 0; int ret; struct interface *ifp = pim_if_find_by_vif_index(c_oil->pim, c_oil->oil.mfcc_parent); if (PIM_DEBUG_ZEBRA) { struct prefix_sg more; more.src = c_oil->oil.mfcc_origin; more.grp = c_oil->oil.mfcc_mcastgrp; zlog_debug( "Sending Request for New Channel Oil Information%s VIIF %d(%s)", pim_str_sg_dump(&more), c_oil->oil.mfcc_parent, c_oil->pim->vrf->name); } if (!ifp) return -1; stream_reset(s); zclient_create_header(s, ZEBRA_IPMR_ROUTE_STATS, c_oil->pim->vrf_id); stream_put_in_addr(s, &c_oil->oil.mfcc_origin); stream_put_in_addr(s, &c_oil->oil.mfcc_mcastgrp); stream_putl(s, ifp->ifindex); stream_putw_at(s, 0, stream_get_endp(s)); count = stream_get_endp(s); ret = writen(zlookup->sock, s->data, count); if (ret <= 0) { flog_err( EC_LIB_SOCKET, "%s: writen() failure: %d writing to zclient lookup socket", __PRETTY_FUNCTION__, errno); return -1; } s = zlookup->ibuf; while (command != ZEBRA_IPMR_ROUTE_STATS) { int err; uint16_t length = 0; vrf_id_t vrf_id; uint8_t marker; uint8_t version; stream_reset(s); err = zclient_read_header(s, zlookup->sock, &length, &marker, &version, &vrf_id, &command); if (err < 0) { flog_err(EC_LIB_ZAPI_MISSMATCH, "%s: zclient_read_header() failed", __PRETTY_FUNCTION__); zclient_lookup_failed(zlookup); return -1; } } sg.src.s_addr = stream_get_ipv4(s); sg.grp.s_addr = stream_get_ipv4(s); if (sg.src.s_addr != c_oil->oil.mfcc_origin.s_addr || sg.grp.s_addr != c_oil->oil.mfcc_mcastgrp.s_addr) { if (PIM_DEBUG_ZEBRA) { struct prefix_sg more; more.src = c_oil->oil.mfcc_origin; more.grp = c_oil->oil.mfcc_mcastgrp; flog_err( EC_LIB_ZAPI_MISSMATCH, "%s: Received wrong %s(%s) information requested", __PRETTY_FUNCTION__, pim_str_sg_dump(&more), c_oil->pim->vrf->name); } zclient_lookup_failed(zlookup); return -3; } stream_get(&lastused, s, sizeof(lastused)); stream_getl(s); c_oil->cc.lastused = lastused; return 0; } frr-7.2.1/pimd/pim_zlookup.h0000644000000000000000000000272713610377563012676 00000000000000/* * PIM for Quagga * Copyright (C) 2008 Everton da Silva Marques * * This program is free software; you can redistribute it and/or modify * it under the terms of the 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; see the file COPYING; if not, write to the Free Software * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA */ #ifndef PIM_ZLOOKUP_H #define PIM_ZLOOKUP_H #include #include "zclient.h" #define PIM_NEXTHOP_LOOKUP_MAX (3) /* max. recursive route lookup */ struct pim_zlookup_nexthop { vrf_id_t vrf_id; struct prefix nexthop_addr; ifindex_t ifindex; uint32_t route_metric; uint8_t protocol_distance; }; void zclient_lookup_new(void); void zclient_lookup_free(void); int zclient_lookup_nexthop(struct pim_instance *pim, struct pim_zlookup_nexthop nexthop_tab[], const int tab_size, struct in_addr addr, int max_lookup); void pim_zlookup_show_ip_multicast(struct vty *vty); int pim_zlookup_sg_statistics(struct channel_oil *c_oil); #endif /* PIM_ZLOOKUP_H */ frr-7.2.1/pimd/pimd.c0000644000000000000000000000704113610377563011244 00000000000000/* * PIM for Quagga * Copyright (C) 2008 Everton da Silva Marques * * This program is free software; you can redistribute it and/or modify * it under the terms of the 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; see the file COPYING; if not, write to the Free Software * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA */ #include #include "log.h" #include "memory.h" #include "if.h" #include "prefix.h" #include "vty.h" #include "plist.h" #include "hash.h" #include "jhash.h" #include "vrf.h" #include "lib_errors.h" #include "pimd.h" #include "pim_cmd.h" #include "pim_str.h" #include "pim_oil.h" #include "pim_pim.h" #include "pim_ssmpingd.h" #include "pim_static.h" #include "pim_rp.h" #include "pim_ssm.h" #include "pim_zlookup.h" #include "pim_zebra.h" const char *const PIM_ALL_SYSTEMS = MCAST_ALL_SYSTEMS; const char *const PIM_ALL_ROUTERS = MCAST_ALL_ROUTERS; const char *const PIM_ALL_PIM_ROUTERS = MCAST_ALL_PIM_ROUTERS; const char *const PIM_ALL_IGMP_ROUTERS = MCAST_ALL_IGMP_ROUTERS; DEFINE_MTYPE_STATIC(PIMD, ROUTER, "PIM Router information"); struct pim_router *router = NULL; void pim_prefix_list_update(struct prefix_list *plist) { struct pim_instance *pim; struct vrf *vrf; RB_FOREACH (vrf, vrf_name_head, &vrfs_by_name) { pim = vrf->info; if (!pim) continue; pim_rp_prefix_list_update(pim, plist); pim_ssm_prefix_list_update(pim, plist); pim_upstream_spt_prefix_list_update(pim, plist); } } static void pim_free(void) { pim_route_map_terminate(); zclient_lookup_free(); } void pim_router_init(void) { router = XCALLOC(MTYPE_ROUTER, sizeof(*router)); router->debugs = 0; router->master = frr_init(); router->t_periodic = PIM_DEFAULT_T_PERIODIC; /* RFC 4601: 4.6.3. Assert Metrics assert_metric infinite_assert_metric() { return {1,infinity,infinity,0} } */ router->infinite_assert_metric.rpt_bit_flag = 1; router->infinite_assert_metric.metric_preference = PIM_ASSERT_METRIC_PREFERENCE_MAX; router->infinite_assert_metric.route_metric = PIM_ASSERT_ROUTE_METRIC_MAX; router->infinite_assert_metric.ip_address.s_addr = INADDR_ANY; router->rpf_cache_refresh_delay_msec = 50; router->register_suppress_time = PIM_REGISTER_SUPPRESSION_TIME_DEFAULT; router->packet_process = PIM_DEFAULT_PACKET_PROCESS; router->register_probe_time = PIM_REGISTER_PROBE_TIME_DEFAULT; router->vrf_id = VRF_DEFAULT; } void pim_router_terminate(void) { XFREE(MTYPE_ROUTER, router); } void pim_init(void) { if (!inet_aton(PIM_ALL_PIM_ROUTERS, &qpim_all_pim_routers_addr)) { flog_err( EC_LIB_SOCKET, "%s %s: could not solve %s to group address: errno=%d: %s", __FILE__, __PRETTY_FUNCTION__, PIM_ALL_PIM_ROUTERS, errno, safe_strerror(errno)); zassert(0); return; } pim_cmd_init(); } void pim_terminate(void) { struct zclient *zclient; /* reverse prefix_list_init */ prefix_list_add_hook(NULL); prefix_list_delete_hook(NULL); prefix_list_reset(); pim_vrf_terminate(); zclient = pim_zebra_zclient_get(); if (zclient) { zclient_stop(zclient); zclient_free(zclient); } pim_free(); pim_router_terminate(); frr_fini(); } frr-7.2.1/pimd/pimd.conf.sample0000644000000000000000000000165313610377563013232 00000000000000! ! pimd sample configuration file ! hostname quagga-pimd-router password zebra !enable password zebra ! !log file pimd.log log stdout ! line vty exec-timeout 60 ! !debug igmp !debug pim !debug pim zebra ! ip multicast-routing ! ! ! You may want to enable ssmpingd for troubleshooting ! ! See http://www.venaas.no/multicast/ssmping/ ! ! ! ip ssmpingd 1.1.1.1 ! ip ssmpingd 2.2.2.2 ! ! ! HINTS: ! ! - Enable "ip pim ssm" on the interface directly attached to the ! ! multicast source host (if this is the first-hop router) ! ! - Enable "ip pim ssm" on pim-routers-facing interfaces ! ! - Enable "ip igmp" on IGMPv3-hosts-facing interfaces ! ! - In order to inject IGMPv3 local membership information in the ! ! PIM protocol state, enable both "ip pim ssm" and "ip igmp" on ! ! the same interface; otherwise PIM won't advertise ! ! IGMPv3-learned membership to other PIM routers ! interface eth0 ip pim ssm ip igmp ! -x- frr-7.2.1/pimd/pimd.h0000644000000000000000000003205513610377563011254 00000000000000/* * PIM for Quagga * Copyright (C) 2008 Everton da Silva Marques * * This program is free software; you can redistribute it and/or modify * it under the terms of the 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; see the file COPYING; if not, write to the Free Software * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA */ #ifndef PIMD_H #define PIMD_H #include #include "zebra.h" #include "libfrr.h" #include "prefix.h" #include "vty.h" #include "plist.h" #include "pim_instance.h" #include "pim_str.h" #include "pim_memory.h" #include "pim_assert.h" #define PIMD_PROGNAME "pimd" #define PIMD_DEFAULT_CONFIG "pimd.conf" #define PIMD_VTY_PORT 2611 #define PIM_IP_PROTO_IGMP (2) #define PIM_IP_PROTO_PIM (103) #define PIM_IGMP_MIN_LEN (8) #define PIM_ENFORCE_LOOPFREE_MFC /* * PIM MSG Header Format * 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 * +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ * |PIM Ver| Type | Reserved | Checksum | * +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ */ #define PIM_MSG_HEADER_LEN (4) #define PIM_PIM_MIN_LEN PIM_MSG_HEADER_LEN #define PIM_ENCODED_IPV4_UCAST_SIZE (6) #define PIM_ENCODED_IPV4_GROUP_SIZE (8) #define PIM_ENCODED_IPV4_SOURCE_SIZE (8) /* * J/P Message Format, Group Header * +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ * | Upstream Neighbor Address (Encoded-Unicast format) | * +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ * | Reserved | Num groups | Holdtime | * +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ * | Multicast Group Address 1 (Encoded-Group format) | * +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ * | Number of Joined Sources | Number of Pruned Sources | * +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ */ #define PIM_JP_GROUP_HEADER_SIZE \ (PIM_ENCODED_IPV4_UCAST_SIZE + 1 + 1 + 2 + PIM_ENCODED_IPV4_GROUP_SIZE \ + 2 + 2) #define PIM_PROTO_VERSION (2) #define MCAST_ALL_SYSTEMS "224.0.0.1" #define MCAST_ALL_ROUTERS "224.0.0.2" #define MCAST_ALL_PIM_ROUTERS "224.0.0.13" #define MCAST_ALL_IGMP_ROUTERS "224.0.0.22" #define PIM_FORCE_BOOLEAN(expr) ((expr) != 0) #define PIM_NET_INADDR_ANY (htonl(INADDR_ANY)) #define PIM_INADDR_IS_ANY(addr) (addr).s_addr == PIM_NET_INADDR_ANY #define PIM_INADDR_ISNOT_ANY(addr) ((addr).s_addr != PIM_NET_INADDR_ANY) /* struct in_addr addr */ #define max(x,y) ((x) > (y) ? (x) : (y)) #define PIM_MASK_PIM_EVENTS (1 << 0) #define PIM_MASK_PIM_EVENTS_DETAIL (1 << 1) #define PIM_MASK_PIM_PACKETS (1 << 2) #define PIM_MASK_PIM_PACKETDUMP_SEND (1 << 3) #define PIM_MASK_PIM_PACKETDUMP_RECV (1 << 4) #define PIM_MASK_PIM_TRACE (1 << 5) #define PIM_MASK_PIM_TRACE_DETAIL (1 << 6) #define PIM_MASK_IGMP_EVENTS (1 << 7) #define PIM_MASK_IGMP_PACKETS (1 << 8) #define PIM_MASK_IGMP_TRACE (1 << 9) #define PIM_MASK_IGMP_TRACE_DETAIL (1 << 10) #define PIM_MASK_ZEBRA (1 << 11) #define PIM_MASK_SSMPINGD (1 << 12) #define PIM_MASK_MROUTE (1 << 13) #define PIM_MASK_MROUTE_DETAIL (1 << 14) #define PIM_MASK_PIM_HELLO (1 << 15) #define PIM_MASK_PIM_J_P (1 << 16) #define PIM_MASK_STATIC (1 << 17) #define PIM_MASK_PIM_REG (1 << 18) #define PIM_MASK_MSDP_EVENTS (1 << 19) #define PIM_MASK_MSDP_PACKETS (1 << 20) #define PIM_MASK_MSDP_INTERNAL (1 << 21) #define PIM_MASK_PIM_NHT (1 << 22) #define PIM_MASK_PIM_NHT_DETAIL (1 << 23) #define PIM_MASK_PIM_NHT_RP (1 << 24) #define PIM_MASK_MTRACE (1 << 25) #define PIM_MASK_VXLAN (1 << 26) #define PIM_MASK_BSM_PROC (1 << 27) /* Remember 32 bits!!! */ /* PIM error codes */ #define PIM_SUCCESS 0 #define PIM_GROUP_BAD_ADDRESS -2 #define PIM_GROUP_OVERLAP -3 #define PIM_GROUP_PFXLIST_OVERLAP -4 #define PIM_RP_BAD_ADDRESS -5 #define PIM_RP_NO_PATH -6 #define PIM_RP_NOT_FOUND -7 #define PIM_RP_PFXLIST_IN_USE -8 #define PIM_IFACE_NOT_FOUND -9 #define PIM_UPDATE_SOURCE_DUP -10 #define PIM_GROUP_BAD_ADDR_MASK_COMBO -11 const char *const PIM_ALL_SYSTEMS; const char *const PIM_ALL_ROUTERS; const char *const PIM_ALL_PIM_ROUTERS; const char *const PIM_ALL_IGMP_ROUTERS; extern struct pim_router *router; extern struct zebra_privs_t pimd_privs; struct in_addr qpim_all_pim_routers_addr; extern uint8_t qpim_ecmp_enable; extern uint8_t qpim_ecmp_rebalance_enable; #define PIM_DEFAULT_PACKET_PROCESS 3 #define PIM_JP_HOLDTIME (router->t_periodic * 7 / 2) /* * Register-Stop Timer (RST(S,G)) * Default values */ #define PIM_REGISTER_SUPPRESSION_TIME_DEFAULT (60) #define PIM_REGISTER_PROBE_TIME_DEFAULT (5) #define PIM_DEBUG_PIM_EVENTS (router->debugs & PIM_MASK_PIM_EVENTS) #define PIM_DEBUG_PIM_EVENTS_DETAIL \ (router->debugs & (PIM_MASK_PIM_EVENTS_DETAIL | PIM_MASK_PIM_EVENTS)) #define PIM_DEBUG_PIM_PACKETS (router->debugs & PIM_MASK_PIM_PACKETS) #define PIM_DEBUG_PIM_PACKETDUMP_SEND \ (router->debugs & PIM_MASK_PIM_PACKETDUMP_SEND) #define PIM_DEBUG_PIM_PACKETDUMP_RECV \ (router->debugs & PIM_MASK_PIM_PACKETDUMP_RECV) #define PIM_DEBUG_PIM_TRACE (router->debugs & PIM_MASK_PIM_TRACE) #define PIM_DEBUG_PIM_TRACE_DETAIL \ (router->debugs & (PIM_MASK_PIM_TRACE_DETAIL | PIM_MASK_PIM_TRACE)) #define PIM_DEBUG_PIM_TRACE_DETAIL_ONLY \ (router->debugs & PIM_MASK_PIM_TRACE_DETAIL) #define PIM_DEBUG_IGMP_EVENTS (router->debugs & PIM_MASK_IGMP_EVENTS) #define PIM_DEBUG_IGMP_PACKETS (router->debugs & PIM_MASK_IGMP_PACKETS) #define PIM_DEBUG_IGMP_TRACE (router->debugs & PIM_MASK_IGMP_TRACE) #define PIM_DEBUG_IGMP_TRACE_DETAIL \ (router->debugs & (PIM_MASK_IGMP_TRACE_DETAIL | PIM_MASK_IGMP_TRACE)) #define PIM_DEBUG_ZEBRA (router->debugs & PIM_MASK_ZEBRA) #define PIM_DEBUG_SSMPINGD (router->debugs & PIM_MASK_SSMPINGD) #define PIM_DEBUG_MROUTE (router->debugs & PIM_MASK_MROUTE) #define PIM_DEBUG_MROUTE_DETAIL \ (router->debugs & (PIM_MASK_MROUTE_DETAIL | PIM_MASK_MROUTE)) #define PIM_DEBUG_MROUTE_DETAIL_ONLY (router->debugs & PIM_MASK_MROUTE_DETAIL) #define PIM_DEBUG_PIM_HELLO (router->debugs & PIM_MASK_PIM_HELLO) #define PIM_DEBUG_PIM_J_P (router->debugs & PIM_MASK_PIM_J_P) #define PIM_DEBUG_PIM_REG (router->debugs & PIM_MASK_PIM_REG) #define PIM_DEBUG_STATIC (router->debugs & PIM_MASK_STATIC) #define PIM_DEBUG_MSDP_EVENTS (router->debugs & PIM_MASK_MSDP_EVENTS) #define PIM_DEBUG_MSDP_PACKETS (router->debugs & PIM_MASK_MSDP_PACKETS) #define PIM_DEBUG_MSDP_INTERNAL (router->debugs & PIM_MASK_MSDP_INTERNAL) #define PIM_DEBUG_PIM_NHT (router->debugs & PIM_MASK_PIM_NHT) #define PIM_DEBUG_PIM_NHT_DETAIL \ (router->debugs & (PIM_MASK_PIM_NHT_DETAIL | PIM_MASK_PIM_NHT)) #define PIM_DEBUG_PIM_NHT_RP (router->debugs & PIM_MASK_PIM_NHT_RP) #define PIM_DEBUG_MTRACE (router->debugs & PIM_MASK_MTRACE) #define PIM_DEBUG_VXLAN (router->debugs & PIM_MASK_VXLAN) #define PIM_DEBUG_BSM (router->debugs & PIM_MASK_BSM_PROC) #define PIM_DEBUG_EVENTS \ (router->debugs \ & (PIM_MASK_PIM_EVENTS | PIM_MASK_IGMP_EVENTS \ | PIM_MASK_MSDP_EVENTS | PIM_MASK_BSM_PROC)) #define PIM_DEBUG_PACKETS \ (router->debugs \ & (PIM_MASK_PIM_PACKETS | PIM_MASK_IGMP_PACKETS \ | PIM_MASK_MSDP_PACKETS)) #define PIM_DEBUG_TRACE \ (router->debugs & (PIM_MASK_PIM_TRACE | PIM_MASK_IGMP_TRACE)) #define PIM_DO_DEBUG_PIM_EVENTS (router->debugs |= PIM_MASK_PIM_EVENTS) #define PIM_DO_DEBUG_PIM_PACKETS (router->debugs |= PIM_MASK_PIM_PACKETS) #define PIM_DO_DEBUG_PIM_PACKETDUMP_SEND \ (router->debugs |= PIM_MASK_PIM_PACKETDUMP_SEND) #define PIM_DO_DEBUG_PIM_PACKETDUMP_RECV \ (router->debugs |= PIM_MASK_PIM_PACKETDUMP_RECV) #define PIM_DO_DEBUG_PIM_TRACE (router->debugs |= PIM_MASK_PIM_TRACE) #define PIM_DO_DEBUG_PIM_TRACE_DETAIL \ (router->debugs |= PIM_MASK_PIM_TRACE_DETAIL) #define PIM_DO_DEBUG_IGMP_EVENTS (router->debugs |= PIM_MASK_IGMP_EVENTS) #define PIM_DO_DEBUG_IGMP_PACKETS (router->debugs |= PIM_MASK_IGMP_PACKETS) #define PIM_DO_DEBUG_IGMP_TRACE (router->debugs |= PIM_MASK_IGMP_TRACE) #define PIM_DO_DEBUG_IGMP_TRACE_DETAIL \ (router->debugs |= PIM_MASK_IGMP_TRACE_DETAIL) #define PIM_DO_DEBUG_ZEBRA (router->debugs |= PIM_MASK_ZEBRA) #define PIM_DO_DEBUG_SSMPINGD (router->debugs |= PIM_MASK_SSMPINGD) #define PIM_DO_DEBUG_MROUTE (router->debugs |= PIM_MASK_MROUTE) #define PIM_DO_DEBUG_MROUTE_DETAIL (router->debugs |= PIM_MASK_MROUTE_DETAIL) #define PIM_DO_DEBUG_BSM (router->debugs |= PIM_MASK_BSM_PROC) #define PIM_DO_DEBUG_PIM_HELLO (router->debugs |= PIM_MASK_PIM_HELLO) #define PIM_DO_DEBUG_PIM_J_P (router->debugs |= PIM_MASK_PIM_J_P) #define PIM_DO_DEBUG_PIM_REG (router->debugs |= PIM_MASK_PIM_REG) #define PIM_DO_DEBUG_STATIC (router->debugs |= PIM_MASK_STATIC) #define PIM_DO_DEBUG_MSDP_EVENTS (router->debugs |= PIM_MASK_MSDP_EVENTS) #define PIM_DO_DEBUG_MSDP_PACKETS (router->debugs |= PIM_MASK_MSDP_PACKETS) #define PIM_DO_DEBUG_MSDP_INTERNAL (router->debugs |= PIM_MASK_MSDP_INTERNAL) #define PIM_DO_DEBUG_PIM_NHT (router->debugs |= PIM_MASK_PIM_NHT) #define PIM_DO_DEBUG_PIM_NHT_RP (router->debugs |= PIM_MASK_PIM_NHT_RP) #define PIM_DO_DEBUG_MTRACE (router->debugs |= PIM_MASK_MTRACE) #define PIM_DO_DEBUG_VXLAN (router->debugs |= PIM_MASK_VXLAN) #define PIM_DONT_DEBUG_PIM_EVENTS (router->debugs &= ~PIM_MASK_PIM_EVENTS) #define PIM_DONT_DEBUG_PIM_PACKETS (router->debugs &= ~PIM_MASK_PIM_PACKETS) #define PIM_DONT_DEBUG_PIM_PACKETDUMP_SEND \ (router->debugs &= ~PIM_MASK_PIM_PACKETDUMP_SEND) #define PIM_DONT_DEBUG_PIM_PACKETDUMP_RECV \ (router->debugs &= ~PIM_MASK_PIM_PACKETDUMP_RECV) #define PIM_DONT_DEBUG_PIM_TRACE (router->debugs &= ~PIM_MASK_PIM_TRACE) #define PIM_DONT_DEBUG_PIM_TRACE_DETAIL \ (router->debugs &= ~PIM_MASK_PIM_TRACE_DETAIL) #define PIM_DONT_DEBUG_IGMP_EVENTS (router->debugs &= ~PIM_MASK_IGMP_EVENTS) #define PIM_DONT_DEBUG_IGMP_PACKETS (router->debugs &= ~PIM_MASK_IGMP_PACKETS) #define PIM_DONT_DEBUG_IGMP_TRACE (router->debugs &= ~PIM_MASK_IGMP_TRACE) #define PIM_DONT_DEBUG_IGMP_TRACE_DETAIL \ (router->debugs &= ~PIM_MASK_IGMP_TRACE_DETAIL) #define PIM_DONT_DEBUG_ZEBRA (router->debugs &= ~PIM_MASK_ZEBRA) #define PIM_DONT_DEBUG_SSMPINGD (router->debugs &= ~PIM_MASK_SSMPINGD) #define PIM_DONT_DEBUG_MROUTE (router->debugs &= ~PIM_MASK_MROUTE) #define PIM_DONT_DEBUG_MROUTE_DETAIL (router->debugs &= ~PIM_MASK_MROUTE_DETAIL) #define PIM_DONT_DEBUG_PIM_HELLO (router->debugs &= ~PIM_MASK_PIM_HELLO) #define PIM_DONT_DEBUG_PIM_J_P (router->debugs &= ~PIM_MASK_PIM_J_P) #define PIM_DONT_DEBUG_PIM_REG (router->debugs &= ~PIM_MASK_PIM_REG) #define PIM_DONT_DEBUG_STATIC (router->debugs &= ~PIM_MASK_STATIC) #define PIM_DONT_DEBUG_MSDP_EVENTS (router->debugs &= ~PIM_MASK_MSDP_EVENTS) #define PIM_DONT_DEBUG_MSDP_PACKETS (router->debugs &= ~PIM_MASK_MSDP_PACKETS) #define PIM_DONT_DEBUG_MSDP_INTERNAL (router->debugs &= ~PIM_MASK_MSDP_INTERNAL) #define PIM_DONT_DEBUG_PIM_NHT (router->debugs &= ~PIM_MASK_PIM_NHT) #define PIM_DONT_DEBUG_PIM_NHT_RP (router->debugs &= ~PIM_MASK_PIM_NHT_RP) #define PIM_DONT_DEBUG_MTRACE (router->debugs &= ~PIM_MASK_MTRACE) #define PIM_DONT_DEBUG_VXLAN (router->debugs &= ~PIM_MASK_VXLAN) #define PIM_DONT_DEBUG_BSM (router->debugs &= ~PIM_MASK_BSM_PROC) void pim_router_init(void); void pim_router_terminate(void); void pim_init(void); void pim_terminate(void); extern void pim_route_map_init(void); extern void pim_route_map_terminate(void); void pim_prefix_list_update(struct prefix_list *plist); #endif /* PIMD_H */ frr-7.2.1/pimd/subdir.am0000644000000000000000000000550513610377563011761 00000000000000# # pimd # if PIMD noinst_LIBRARIES += pimd/libpim.a sbin_PROGRAMS += pimd/pimd bin_PROGRAMS += pimd/mtracebis noinst_PROGRAMS += pimd/test_igmpv3_join dist_examples_DATA += pimd/pimd.conf.sample vtysh_scan += $(top_srcdir)/pimd/pim_cmd.c man8 += $(MANBUILD)/frr-pimd.8 man8 += $(MANBUILD)/mtracebis.8 endif pimd_libpim_a_SOURCES = \ pimd/pim_assert.c \ pimd/pim_bfd.c \ pimd/pim_br.c \ pimd/pim_bsm.c \ pimd/pim_cmd.c \ pimd/pim_errors.c \ pimd/pim_hello.c \ pimd/pim_iface.c \ pimd/pim_ifchannel.c \ pimd/pim_igmp.c \ pimd/pim_igmp_mtrace.c \ pimd/pim_igmp_stats.c \ pimd/pim_igmpv2.c \ pimd/pim_igmpv3.c \ pimd/pim_instance.c \ pimd/pim_int.c \ pimd/pim_join.c \ pimd/pim_jp_agg.c \ pimd/pim_macro.c \ pimd/pim_memory.c \ pimd/pim_mroute.c \ pimd/pim_msdp.c \ pimd/pim_msdp_packet.c \ pimd/pim_msdp_socket.c \ pimd/pim_msg.c \ pimd/pim_neighbor.c \ pimd/pim_nht.c \ pimd/pim_oil.c \ pimd/pim_pim.c \ pimd/pim_register.c \ pimd/pim_routemap.c \ pimd/pim_rp.c \ pimd/pim_rpf.c \ pimd/pim_signals.c \ pimd/pim_sock.c \ pimd/pim_ssm.c \ pimd/pim_ssmpingd.c \ pimd/pim_static.c \ pimd/pim_str.c \ pimd/pim_time.c \ pimd/pim_tlv.c \ pimd/pim_upstream.c \ pimd/pim_util.c \ pimd/pim_version.c \ pimd/pim_vty.c \ pimd/pim_zebra.c \ pimd/pim_zlookup.c \ pimd/pim_vxlan.c \ pimd/pimd.c \ # end noinst_HEADERS += \ pimd/pim_assert.h \ pimd/pim_bfd.h \ pimd/pim_br.h \ pimd/pim_bsm.h \ pimd/pim_cmd.h \ pimd/pim_errors.h \ pimd/pim_hello.h \ pimd/pim_iface.h \ pimd/pim_ifchannel.h \ pimd/pim_igmp.h \ pimd/pim_igmp_join.h \ pimd/pim_igmp_mtrace.h \ pimd/pim_igmp_stats.h \ pimd/pim_igmpv2.h \ pimd/pim_igmpv3.h \ pimd/pim_instance.h \ pimd/pim_int.h \ pimd/pim_join.h \ pimd/pim_jp_agg.h \ pimd/pim_macro.h \ pimd/pim_memory.h \ pimd/pim_mroute.h \ pimd/pim_msdp.h \ pimd/pim_msdp_packet.h \ pimd/pim_msdp_socket.h \ pimd/pim_msg.h \ pimd/pim_neighbor.h \ pimd/pim_nht.h \ pimd/pim_oil.h \ pimd/pim_pim.h \ pimd/pim_register.h \ pimd/pim_rp.h \ pimd/pim_rpf.h \ pimd/pim_signals.h \ pimd/pim_sock.h \ pimd/pim_ssm.h \ pimd/pim_ssmpingd.h \ pimd/pim_static.h \ pimd/pim_str.h \ pimd/pim_time.h \ pimd/pim_tlv.h \ pimd/pim_upstream.h \ pimd/pim_util.h \ pimd/pim_version.h \ pimd/pim_vty.h \ pimd/pim_zebra.h \ pimd/pim_zlookup.h \ pimd/pim_vxlan.h \ pimd/pim_vxlan_instance.h \ pimd/pimd.h \ pimd/mtracebis_netlink.h \ pimd/mtracebis_routeget.h \ # end pimd/pim_cmd_clippy.c: $(CLIPPY_DEPS) pimd/pim_cmd.$(OBJEXT): pimd/pim_cmd_clippy.c pimd_pimd_LDADD = pimd/libpim.a lib/libfrr.la $(LIBCAP) pimd_pimd_SOURCES = pimd/pim_main.c pimd_test_igmpv3_join_LDADD = lib/libfrr.la pimd_test_igmpv3_join_SOURCES = pimd/test_igmpv3_join.c pimd_mtracebis_LDADD = lib/libfrr.la pimd_mtracebis_SOURCES = pimd/mtracebis.c \ pimd/mtracebis_netlink.c \ pimd/mtracebis_routeget.c \ # end frr-7.2.1/pimd/test_igmpv3_join.c0000644000000000000000000000671713610377563013607 00000000000000/* * PIM for Quagga * Copyright (C) 2008 Everton da Silva Marques * * This program is free software; you can redistribute it and/or modify * it under the terms of the 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; see the file COPYING; if not, write to the Free Software * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA */ #include #include #include #include #include #include #include #include #include #include #include "if.h" #include "pim_igmp_join.h" const char *prog_name = 0; static int iface_solve_index(const char *ifname) { struct if_nameindex *ini; ifindex_t ifindex = -1; int i; if (!ifname) return -1; ini = if_nameindex(); if (!ini) { int err = errno; fprintf(stderr, "%s: interface=%s: failure solving index: errno=%d: %s\n", prog_name, ifname, err, strerror(err)); errno = err; return -1; } for (i = 0; ini[i].if_index; ++i) { #if 0 fprintf(stderr, "%s: interface=%s matching against local ifname=%s ifindex=%d\n", prog_name, ifname, ini[i].if_name, ini[i].if_index); #endif if (!strcmp(ini[i].if_name, ifname)) { ifindex = ini[i].if_index; break; } } if_freenameindex(ini); return ifindex; } int main(int argc, const char *argv[]) { struct in_addr group_addr; struct in_addr source_addr; const char *ifname; const char *group; const char *source; ifindex_t ifindex; int result; int fd; prog_name = argv[0]; fd = socket(AF_INET, SOCK_DGRAM, IPPROTO_UDP); if (fd < 0) { fprintf(stderr, "%s: could not create socket: socket(): errno=%d: %s\n", prog_name, errno, strerror(errno)); exit(1); } if (argc != 4) { fprintf(stderr, "usage: %s interface group source\n" "example: %s eth0 232.1.1.1 1.1.1.1\n", prog_name, prog_name); exit(1); } ifname = argv[1]; group = argv[2]; source = argv[3]; ifindex = iface_solve_index(ifname); if (ifindex < 0) { fprintf(stderr, "%s: could not find interface: %s\n", prog_name, ifname); exit(1); } result = inet_pton(AF_INET, group, &group_addr); if (result <= 0) { fprintf(stderr, "%s: bad group address: %s\n", prog_name, group); exit(1); } result = inet_pton(AF_INET, source, &source_addr); if (result <= 0) { fprintf(stderr, "%s: bad source address: %s\n", prog_name, source); exit(1); } result = pim_igmp_join_source(fd, ifindex, group_addr, source_addr); if (result) { fprintf(stderr, "%s: setsockopt(fd=%d) failure for IGMP group %s source %s ifindex %d on interface %s: errno=%d: %s\n", prog_name, fd, group, source, ifindex, ifname, errno, strerror(errno)); exit(1); } printf("%s: joined channel (S,G)=(%s,%s) on interface %s\n", prog_name, source, group, ifname); printf("%s: waiting...\n", prog_name); if (getchar() == EOF) fprintf(stderr, "getchar failure\n"); close(fd); printf("%s: left channel (S,G)=(%s,%s) on interface %s\n", prog_name, source, group, ifname); exit(0); } frr-7.2.1/pkgsrc/0000755000000000000000000000000013610377563010565 500000000000000frr-7.2.1/pkgsrc/bgpd.sh.in0000644000000000000000000000125313610377563012363 00000000000000#!/bin/sh # # bgpd is part of the quagga routing beast # # PROVIDE: bgpd # REQUIRE: zebra ## PATH=/sbin:/bin:/usr/sbin:/usr/bin:@prefix@/sbin:@prefix@/bin export PATH if [ -f /etc/rc.subr ] then . /etc/rc.subr fi name="bgpd" rcvar=$name required_files="@sysconfdir@/${name}.conf" command="@prefix@/sbin/${name}" command_args="-d" start_precmd="zebra_precmd" socket_dir=@localstatedir@ pidfile="${socket_dir}/${name}.pid" zebra_precmd() { rc_flags="$( set -- $rc_flags while [ $# -ne 0 ]; do if [ X"$1" = X-P -o X"$1" = X-A ]; then break fi shift done if [ $# -eq 0 ]; then echo "-P 0" fi ) $rc_flags" } load_rc_config $name run_rc_command "$1" frr-7.2.1/pkgsrc/eigrpd.sh.in0000644000000000000000000000126113610377563012720 00000000000000#!/bin/sh # # eigrpd is part of the quagga routing beast # # PROVIDE: eigrpd # REQUIRE: zebra ## PATH=/sbin:/bin:/usr/sbin:/usr/bin:@prefix@/sbin:@prefix@/bin export PATH if [ -f /etc/rc.subr ] then . /etc/rc.subr fi name="eigrpd" rcvar=$name required_files="@sysconfdir@/${name}.conf" command="@prefix@/sbin/${name}" command_args="-d" start_precmd="zebra_precmd" socket_dir=@localstatedir@ pidfile="${socket_dir}/${name}.pid" zebra_precmd() { rc_flags="$( set -- $rc_flags while [ $# -ne 0 ]; do if [ X"$1" = X-P -o X"$1" = X-A ]; then break fi shift done if [ $# -eq 0 ]; then echo "-P 0" fi ) $rc_flags" } load_rc_config $name run_rc_command "$1" frr-7.2.1/pkgsrc/ospf6d.sh.in0000644000000000000000000000126113610377563012647 00000000000000#!/bin/sh # # ospf6d is part of the quagga routing beast # # PROVIDE: ospf6d # REQUIRE: zebra ## PATH=/sbin:/bin:/usr/sbin:/usr/bin:@prefix@/sbin:@prefix@/bin export PATH if [ -f /etc/rc.subr ] then . /etc/rc.subr fi name="ospf6d" rcvar=$name required_files="@sysconfdir@/${name}.conf" command="@prefix@/sbin/${name}" command_args="-d" start_precmd="zebra_precmd" socket_dir=@localstatedir@ pidfile="${socket_dir}/${name}.pid" zebra_precmd() { rc_flags="$( set -- $rc_flags while [ $# -ne 0 ]; do if [ X"$1" = X-P -o X"$1" = X-A ]; then break fi shift done if [ $# -eq 0 ]; then echo "-P 0" fi ) $rc_flags" } load_rc_config $name run_rc_command "$1" frr-7.2.1/pkgsrc/ospfd.sh.in0000644000000000000000000000125613610377563012565 00000000000000#!/bin/sh # # ospfd is part of the quagga routing beast # # PROVIDE: ospfd # REQUIRE: zebra ## PATH=/sbin:/bin:/usr/sbin:/usr/bin:@prefix@/sbin:@prefix@/bin export PATH if [ -f /etc/rc.subr ] then . /etc/rc.subr fi name="ospfd" rcvar=$name required_files="@sysconfdir@/${name}.conf" command="@prefix@/sbin/${name}" command_args="-d" start_precmd="zebra_precmd" socket_dir=@localstatedir@ pidfile="${socket_dir}/${name}.pid" zebra_precmd() { rc_flags="$( set -- $rc_flags while [ $# -ne 0 ]; do if [ X"$1" = X-P -o X"$1" = X-A ]; then break fi shift done if [ $# -eq 0 ]; then echo "-P 0" fi ) $rc_flags" } load_rc_config $name run_rc_command "$1" frr-7.2.1/pkgsrc/ripd.sh.in0000644000000000000000000000125313610377563012405 00000000000000#!/bin/sh # # ripd is part of the quagga routing beast # # PROVIDE: ripd # REQUIRE: zebra ## PATH=/sbin:/bin:/usr/sbin:/usr/bin:@prefix@/sbin:@prefix@/bin export PATH if [ -f /etc/rc.subr ] then . /etc/rc.subr fi name="ripd" rcvar=$name required_files="@sysconfdir@/${name}.conf" command="@prefix@/sbin/${name}" command_args="-d" start_precmd="zebra_precmd" socket_dir=@localstatedir@ pidfile="${socket_dir}/${name}.pid" zebra_precmd() { rc_flags="$( set -- $rc_flags while [ $# -ne 0 ]; do if [ X"$1" = X-P -o X"$1" = X-A ]; then break fi shift done if [ $# -eq 0 ]; then echo "-P 0" fi ) $rc_flags" } load_rc_config $name run_rc_command "$1" frr-7.2.1/pkgsrc/ripngd.sh.in0000644000000000000000000000126113610377563012731 00000000000000#!/bin/sh # # ripngd is part of the quagga routing beast # # PROVIDE: ripngd # REQUIRE: zebra ## PATH=/sbin:/bin:/usr/sbin:/usr/bin:@prefix@/sbin:@prefix@/bin export PATH if [ -f /etc/rc.subr ] then . /etc/rc.subr fi name="ripngd" rcvar=$name required_files="@sysconfdir@/${name}.conf" command="@prefix@/sbin/${name}" command_args="-d" start_precmd="zebra_precmd" socket_dir=@localstatedir@ pidfile="${socket_dir}/${name}.pid" zebra_precmd() { rc_flags="$( set -- $rc_flags while [ $# -ne 0 ]; do if [ X"$1" = X-P -o X"$1" = X-A ]; then break fi shift done if [ $# -eq 0 ]; then echo "-P 0" fi ) $rc_flags" } load_rc_config $name run_rc_command "$1" frr-7.2.1/pkgsrc/zebra.sh.in0000644000000000000000000000162113610377563012551 00000000000000#!/bin/sh # # zebra is the head of the quagga routing beast # # PROVIDE: zebra # REQUIRE: NETWORKING ## PATH=/sbin:/bin:/usr/sbin:/usr/bin:@prefix@/sbin:@prefix@/bin export PATH if [ -f /etc/rc.subr ] then . /etc/rc.subr fi name="zebra" rcvar=$name required_files="@sysconfdir@/${name}.conf" command="@prefix@/sbin/${name}" command_args="-d" start_precmd="zebra_precmd" stop_postcmd="zebra_postcmd" socket_dir=@localstatedir@ pidfile="${socket_dir}/${name}.pid" zebra_precmd() { mkdir -p "${socket_dir}" chown quagga.quagga "${socket_dir}" chmod 750 "${socket_dir}" rc_flags="$( set -- $rc_flags while [ $# -ne 0 ]; do if [ X"$1" = X-P -o X"$1" = X-A ]; then break fi shift done if [ $# -eq 0 ]; then echo "-P 0" fi ) $rc_flags" } zebra_postcmd() { if [ -d "${socketdir}" ]; then rmdir ${socketdir} fi } load_rc_config $name run_rc_command "$1" frr-7.2.1/python/0000755000000000000000000000000013610377563010615 500000000000000frr-7.2.1/python/clidef.py0000644000000000000000000003131113610377563012334 00000000000000# FRR CLI preprocessor (DEFPY) # # Copyright (C) 2017 David Lamparter for NetDEF, Inc. # # This program is free software; you can redistribute it and/or modify it # under the terms of the 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; see the file COPYING; if not, write to the Free Software # Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA import clippy, traceback, sys, os from collections import OrderedDict from functools import reduce from pprint import pprint from string import Template from io import StringIO # the various handlers generate output C code for a particular type of # CLI token, choosing the most useful output C type. class RenderHandler(object): def __init__(self, token): pass def combine(self, other): if type(self) == type(other): return other return StringHandler(None) deref = '' drop_str = False canfail = True canassert = False class StringHandler(RenderHandler): argtype = 'const char *' decl = Template('const char *$varname = NULL;') code = Template('$varname = (argv[_i]->type == WORD_TKN) ? argv[_i]->text : argv[_i]->arg;') drop_str = True canfail = False canassert = True class LongHandler(RenderHandler): argtype = 'long' decl = Template('long $varname = 0;') code = Template('''\ char *_end; $varname = strtol(argv[_i]->arg, &_end, 10); _fail = (_end == argv[_i]->arg) || (*_end != '\\0');''') # A.B.C.D/M (prefix_ipv4) and # X:X::X:X/M (prefix_ipv6) are "compatible" and can merge into a # struct prefix: class PrefixBase(RenderHandler): def combine(self, other): if type(self) == type(other): return other if isinstance(other, PrefixBase): return PrefixGenHandler(None) return StringHandler(None) deref = '&' class Prefix4Handler(PrefixBase): argtype = 'const struct prefix_ipv4 *' decl = Template('struct prefix_ipv4 $varname = { };') code = Template('_fail = !str2prefix_ipv4(argv[_i]->arg, &$varname);') class Prefix6Handler(PrefixBase): argtype = 'const struct prefix_ipv6 *' decl = Template('struct prefix_ipv6 $varname = { };') code = Template('_fail = !str2prefix_ipv6(argv[_i]->arg, &$varname);') class PrefixEthHandler(PrefixBase): argtype = 'struct prefix_eth *' decl = Template('struct prefix_eth $varname = { };') code = Template('_fail = !str2prefix_eth(argv[_i]->arg, &$varname);') class PrefixGenHandler(PrefixBase): argtype = 'const struct prefix *' decl = Template('struct prefix $varname = { };') code = Template('_fail = !str2prefix(argv[_i]->arg, &$varname);') # same for IP addresses. result is union sockunion. class IPBase(RenderHandler): def combine(self, other): if type(self) == type(other): return other if type(other) in [IP4Handler, IP6Handler, IPGenHandler]: return IPGenHandler(None) return StringHandler(None) class IP4Handler(IPBase): argtype = 'struct in_addr' decl = Template('struct in_addr $varname = { INADDR_ANY };') code = Template('_fail = !inet_aton(argv[_i]->arg, &$varname);') class IP6Handler(IPBase): argtype = 'struct in6_addr' decl = Template('struct in6_addr $varname = {};') code = Template('_fail = !inet_pton(AF_INET6, argv[_i]->arg, &$varname);') class IPGenHandler(IPBase): argtype = 'const union sockunion *' decl = Template('''union sockunion s__$varname = { .sa.sa_family = AF_UNSPEC }, *$varname = NULL;''') code = Template('''\ if (argv[_i]->text[0] == 'X') { s__$varname.sa.sa_family = AF_INET6; _fail = !inet_pton(AF_INET6, argv[_i]->arg, &s__$varname.sin6.sin6_addr); $varname = &s__$varname; } else { s__$varname.sa.sa_family = AF_INET; _fail = !inet_aton(argv[_i]->arg, &s__$varname.sin.sin_addr); $varname = &s__$varname; }''') canassert = True def mix_handlers(handlers): def combine(a, b): if a is None: return b return a.combine(b) return reduce(combine, handlers, None) handlers = { 'WORD_TKN': StringHandler, 'VARIABLE_TKN': StringHandler, 'RANGE_TKN': LongHandler, 'IPV4_TKN': IP4Handler, 'IPV4_PREFIX_TKN': Prefix4Handler, 'IPV6_TKN': IP6Handler, 'IPV6_PREFIX_TKN': Prefix6Handler, 'MAC_TKN': PrefixEthHandler, 'MAC_PREFIX_TKN': PrefixEthHandler, } # core template invoked for each occurence of DEFPY. # # the "#if $..." bits are there to keep this template unified into one # common form, without requiring a more advanced template engine (e.g. # jinja2) templ = Template('''/* $fnname => "$cmddef" */ DEFUN_CMD_FUNC_DECL($fnname) #define funcdecl_$fnname static int ${fnname}_magic(\\ const struct cmd_element *self __attribute__ ((unused)),\\ struct vty *vty __attribute__ ((unused)),\\ int argc __attribute__ ((unused)),\\ struct cmd_token *argv[] __attribute__ ((unused))$argdefs) funcdecl_$fnname; DEFUN_CMD_FUNC_TEXT($fnname) { #if $nonempty /* anything to parse? */ int _i; #if $canfail /* anything that can fail? */ unsigned _fail = 0, _failcnt = 0; #endif $argdecls for (_i = 0; _i < argc; _i++) { if (!argv[_i]->varname) continue; #if $canfail /* anything that can fail? */ _fail = 0; #endif $argblocks #if $canfail /* anything that can fail? */ if (_fail) vty_out (vty, "%% invalid input for %s: %s\\n", argv[_i]->varname, argv[_i]->arg); _failcnt += _fail; #endif } #if $canfail /* anything that can fail? */ if (_failcnt) return CMD_WARNING; #endif #endif $argassert return ${fnname}_magic(self, vty, argc, argv$arglist); } ''') # invoked for each named parameter argblock = Template(''' if (!strcmp(argv[_i]->varname, \"$varname\")) {$strblock $code }''') def get_always_args(token, always_args, args = [], stack = []): if token in stack: return if token.type == 'END_TKN': for arg in list(always_args): if arg not in args: always_args.remove(arg) return stack = stack + [token] if token.type in handlers and token.varname is not None: args = args + [token.varname] for nexttkn in token.next(): get_always_args(nexttkn, always_args, args, stack) class Macros(dict): def load(self, filename): filedata = clippy.parse(filename) for entry in filedata['data']: if entry['type'] != 'PREPROC': continue ppdir = entry['line'].lstrip().split(None, 1) if ppdir[0] != 'define' or len(ppdir) != 2: continue ppdef = ppdir[1].split(None, 1) name = ppdef[0] if '(' in name: continue val = ppdef[1] if len(ppdef) == 2 else '' val = val.strip(' \t\n\\') if name in self: sys.stderr.write('warning: macro %s redefined!\n' % (name)) self[name] = val def process_file(fn, ofd, dumpfd, all_defun, macros): errors = 0 filedata = clippy.parse(fn) for entry in filedata['data']: if entry['type'].startswith('DEFPY') or (all_defun and entry['type'].startswith('DEFUN')): if len(entry['args'][0]) != 1: sys.stderr.write('%s:%d: DEFPY function name not parseable (%r)\n' % (fn, entry['lineno'], entry['args'][0])) errors += 1 continue cmddef = entry['args'][2] cmddefx = [] for i in cmddef: while i in macros: i = macros[i] if i.startswith('"') and i.endswith('"'): cmddefx.append(i[1:-1]) continue sys.stderr.write('%s:%d: DEFPY command string not parseable (%r)\n' % (fn, entry['lineno'], cmddef)) errors += 1 cmddefx = None break if cmddefx is None: continue cmddef = ''.join([i for i in cmddefx]) graph = clippy.Graph(cmddef) args = OrderedDict() always_args = set() for token, depth in clippy.graph_iterate(graph): if token.type not in handlers: continue if token.varname is None: continue arg = args.setdefault(token.varname, []) arg.append(handlers[token.type](token)) always_args.add(token.varname) get_always_args(graph.first(), always_args) #print('-' * 76) #pprint(entry) #clippy.dump(graph) #pprint(args) params = { 'cmddef': cmddef, 'fnname': entry['args'][0][0] } argdefs = [] argdecls = [] arglist = [] argblocks = [] argassert = [] doc = [] canfail = 0 def do_add(handler, basename, varname, attr = ''): argdefs.append(',\\\n\t%s %s%s' % (handler.argtype, varname, attr)) argdecls.append('\t%s\n' % (handler.decl.substitute({'varname': varname}).replace('\n', '\n\t'))) arglist.append(', %s%s' % (handler.deref, varname)) if basename in always_args and handler.canassert: argassert.append('''\tif (!%s) { \t\tvty_out(vty, "Internal CLI error [%%s]\\n", "%s"); \t\treturn CMD_WARNING; \t}\n''' % (varname, varname)) if attr == '': at = handler.argtype if not at.startswith('const '): at = '. . . ' + at doc.append('\t%-26s %s %s' % (at, 'alw' if basename in always_args else 'opt', varname)) for varname in args.keys(): handler = mix_handlers(args[varname]) #print(varname, handler) if handler is None: continue do_add(handler, varname, varname) code = handler.code.substitute({'varname': varname}).replace('\n', '\n\t\t\t') if handler.canfail: canfail = 1 strblock = '' if not handler.drop_str: do_add(StringHandler(None), varname, '%s_str' % (varname), ' __attribute__ ((unused))') strblock = '\n\t\t\t%s_str = argv[_i]->arg;' % (varname) argblocks.append(argblock.substitute({'varname': varname, 'strblock': strblock, 'code': code})) if dumpfd is not None: if len(arglist) > 0: dumpfd.write('"%s":\n%s\n\n' % (cmddef, '\n'.join(doc))) else: dumpfd.write('"%s":\n\t---- no magic arguments ----\n\n' % (cmddef)) params['argdefs'] = ''.join(argdefs) params['argdecls'] = ''.join(argdecls) params['arglist'] = ''.join(arglist) params['argblocks'] = ''.join(argblocks) params['canfail'] = canfail params['nonempty'] = len(argblocks) params['argassert'] = ''.join(argassert) ofd.write(templ.substitute(params)) return errors if __name__ == '__main__': import argparse argp = argparse.ArgumentParser(description = 'FRR CLI preprocessor in Python') argp.add_argument('--all-defun', action = 'store_const', const = True, help = 'process DEFUN() statements in addition to DEFPY()') argp.add_argument('--show', action = 'store_const', const = True, help = 'print out list of arguments and types for each definition') argp.add_argument('-o', type = str, metavar = 'OUTFILE', help = 'output C file name') argp.add_argument('cfile', type = str) args = argp.parse_args() dumpfd = None if args.o is not None: ofd = StringIO() if args.show: dumpfd = sys.stdout else: ofd = sys.stdout if args.show: dumpfd = sys.stderr basepath = os.path.dirname(os.path.dirname(os.path.abspath(__file__))) macros = Macros() macros.load('lib/route_types.h') macros.load(os.path.join(basepath, 'lib/command.h')) macros.load(os.path.join(basepath, 'bgpd/bgp_vty.h')) # sigh :( macros['PROTO_REDIST_STR'] = 'FRR_REDIST_STR_ISISD' errors = process_file(args.cfile, ofd, dumpfd, args.all_defun, macros) if errors != 0: sys.exit(1) if args.o is not None: clippy.wrdiff(args.o, ofd, [args.cfile, os.path.realpath(__file__), sys.executable]) frr-7.2.1/python/clippy/0000755000000000000000000000000013610377563012115 500000000000000frr-7.2.1/python/clippy/__init__.py0000644000000000000000000000470413610377563014153 00000000000000# FRR CLI preprocessor # # Copyright (C) 2017 David Lamparter for NetDEF, Inc. # # This program is free software; you can redistribute it and/or modify it # under the terms of the 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; see the file COPYING; if not, write to the Free Software # Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA import os, stat import _clippy from _clippy import parse, Graph, GraphNode def graph_iterate(graph): '''iterator yielding all nodes of a graph nodes arrive in input/definition order, graph circles are avoided. ''' queue = [(graph.first(), frozenset(), 0)] while len(queue) > 0: node, stop, depth = queue.pop(0) yield node, depth join = node.join() if join is not None: queue.insert(0, (join, stop.union(frozenset([node])), depth)) join = frozenset([join]) stop = join or stop nnext = node.next() for n in reversed(nnext): if n not in stop and n is not node: queue.insert(0, (n, stop, depth + 1)) def dump(graph): '''print out clippy.Graph''' for i, depth in graph_iterate(graph): print('\t%s%s %r' % (' ' * (depth * 2), i.type, i.text)) def wrdiff(filename, buf, reffiles = []): '''write buffer to file if contents changed''' expl = '' if hasattr(buf, 'getvalue'): buf = buf.getvalue() old = None try: old = open(filename, 'r').read() except: pass if old == buf: for reffile in reffiles: # ensure output timestamp is newer than inputs, for make reftime = os.stat(reffile)[stat.ST_MTIME] outtime = os.stat(filename)[stat.ST_MTIME] if outtime <= reftime: os.utime(filename, (reftime + 1, reftime + 1)) # sys.stderr.write('%s unchanged, not written\n' % (filename)) return newname = '%s.new-%d' % (filename, os.getpid()) with open(newname, 'w') as out: out.write(buf) os.rename(newname, filename) frr-7.2.1/qpb/0000755000000000000000000000000013610377563010056 500000000000000frr-7.2.1/qpb/Makefile0000644000000000000000000000022513610377563011435 00000000000000all: ALWAYS @$(MAKE) -s -C .. fpm/libfrr_pb.la %: ALWAYS @$(MAKE) -s -C .. fpm/$@ Makefile: #nothing ALWAYS: .PHONY: ALWAYS makefiles .SUFFIXES: frr-7.2.1/qpb/linear_allocator.h0000644000000000000000000001116213610377563013462 00000000000000/* * linear_allocator.h * * @copyright Copyright (C) 2016 Sproute Networks, Inc. * * @author Avneesh Sachdev * * This file is part of Quagga. * * Quagga is free software; you can redistribute it and/or modify it * under the terms of the GNU General Public License as published by the * Free Software Foundation; either version 2, or (at your option) any * later version. * * Quagga 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; see the file COPYING; if not, write to the Free Software * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA */ /* * Header file for the linear allocator. * * An allocator that allocates memory by walking down towards the end * of a buffer. No attempt is made to reuse blocks that are freed * subsequently. The assumption is that the buffer is big enough to * cover allocations for a given purpose. */ #include #include #include #include /* * Alignment for block allocated by the allocator. Must be a power of 2. */ #define LINEAR_ALLOCATOR_ALIGNMENT 8 #define LINEAR_ALLOCATOR_ALIGN(value) \ (((value) + LINEAR_ALLOCATOR_ALIGNMENT - 1) \ & ~(LINEAR_ALLOCATOR_ALIGNMENT - 1)); /* * linear_allocator_align_ptr */ static inline char *linear_allocator_align_ptr(char *ptr) { return (char *)LINEAR_ALLOCATOR_ALIGN((intptr_t)ptr); } typedef struct linear_allocator_t_ { char *buf; /* * Current location in the buffer. */ char *cur; /* * End of buffer. */ char *end; /* * Version number of the allocator, this is bumped up when the allocator * is reset and helps identifies bad frees. */ uint32_t version; /* * The number of blocks that are currently allocated. */ int num_allocated; } linear_allocator_t; /* * linear_allocator_block_t * * Header structure at the begining of each block. */ typedef struct linear_allocator_block_t_ { uint32_t flags; /* * The version of the allocator when this block was allocated. */ uint32_t version; char data[0]; } linear_allocator_block_t; #define LINEAR_ALLOCATOR_BLOCK_IN_USE 0x01 #define LINEAR_ALLOCATOR_HDR_SIZE (sizeof(linear_allocator_block_t)) /* * linear_allocator_block_size * * The total amount of space a block will take in the buffer, * including the size of the header. */ static inline size_t linear_allocator_block_size(size_t user_size) { return LINEAR_ALLOCATOR_ALIGN(LINEAR_ALLOCATOR_HDR_SIZE + user_size); } /* * linear_allocator_ptr_to_block */ static inline linear_allocator_block_t *linear_allocator_ptr_to_block(void *ptr) { void *block_ptr; block_ptr = ((char *)ptr) - offsetof(linear_allocator_block_t, data); return block_ptr; } /* * linear_allocator_init */ static inline void linear_allocator_init(linear_allocator_t *allocator, char *buf, size_t buf_len) { memset(allocator, 0, sizeof(*allocator)); assert(linear_allocator_align_ptr(buf) == buf); allocator->buf = buf; allocator->cur = buf; allocator->end = buf + buf_len; } /* * linear_allocator_reset * * Prepare an allocator for reuse. * * *** NOTE ** This implicitly frees all the blocks in the allocator. */ static inline void linear_allocator_reset(linear_allocator_t *allocator) { allocator->num_allocated = 0; allocator->version++; allocator->cur = allocator->buf; } /* * linear_allocator_alloc */ static inline void *linear_allocator_alloc(linear_allocator_t *allocator, size_t user_size) { size_t block_size; linear_allocator_block_t *block; block_size = linear_allocator_block_size(user_size); if (allocator->cur + block_size > allocator->end) { return NULL; } block = (linear_allocator_block_t *)allocator->cur; allocator->cur += block_size; block->flags = LINEAR_ALLOCATOR_BLOCK_IN_USE; block->version = allocator->version; allocator->num_allocated++; return block->data; } /* * linear_allocator_free */ static inline void linear_allocator_free(linear_allocator_t *allocator, void *ptr) { linear_allocator_block_t *block; if (((char *)ptr) < allocator->buf || ((char *)ptr) >= allocator->end) { assert(0); return; } block = linear_allocator_ptr_to_block(ptr); if (block->version != allocator->version) { assert(0); return; } block->flags = block->flags & ~LINEAR_ALLOCATOR_BLOCK_IN_USE; if (--allocator->num_allocated < 0) { assert(0); } } frr-7.2.1/qpb/qpb.c0000644000000000000000000000162213610377563010725 00000000000000/* * qpb.c * * @copyright Copyright (C) 2016 Sproute Networks, Inc. * * @author Avneesh Sachdev * * This file is part of Quagga. * * Quagga is free software; you can redistribute it and/or modify it * under the terms of the GNU General Public License as published by the * Free Software Foundation; either version 2, or (at your option) any * later version. * * Quagga 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; see the file COPYING; if not, write to the Free Software * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA */ /* * Main file for the qpb library. */ frr-7.2.1/qpb/qpb.h0000644000000000000000000001637313610377563010743 00000000000000/* * qpb.h * * @copyright Copyright (C) 2016 Sproute Networks, Inc. * * @author Avneesh Sachdev * * This file is part of Quagga. * * Quagga is free software; you can redistribute it and/or modify it * under the terms of the GNU General Public License as published by the * Free Software Foundation; either version 2, or (at your option) any * later version. * * Quagga 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; see the file COPYING; if not, write to the Free Software * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA */ /* * Main public header file for the quagga protobuf library. */ #ifndef _QPB_H #define _QPB_H #include "prefix.h" #include "qpb/qpb.pb-c.h" #include "qpb/qpb_allocator.h" /* * qpb__address_family__set */ #define qpb_address_family_set qpb__address_family__set static inline int qpb__address_family__set(Qpb__AddressFamily *pb_family, uint8_t family) { switch (family) { case AF_INET: *pb_family = QPB__ADDRESS_FAMILY__IPV4; return 1; case AF_INET6: *pb_family = QPB__ADDRESS_FAMILY__IPV6; return 1; default: *pb_family = QPB__ADDRESS_FAMILY__UNKNOWN_AF; } return 0; } /* * qpb__address_family__get */ #define qpb_address_family_get qpb__address_family__get static inline int qpb__address_family__get(Qpb__AddressFamily pb_family, uint8_t *family) { switch (pb_family) { case QPB__ADDRESS_FAMILY__IPV4: *family = AF_INET; return 1; case QPB__ADDRESS_FAMILY__IPV6: *family = AF_INET6; return 1; case QPB__ADDRESS_FAMILY__UNKNOWN_AF: return 0; default: /* protobuf "magic value" _QPB__ADDRESS_FAMILY_IS_INT_SIZE */ return 0; } return 0; } /* * qpb__l3_prefix__create */ #define qpb_l3_prefix_create qpb__l3_prefix__create static inline Qpb__L3Prefix *qpb__l3_prefix__create(qpb_allocator_t *allocator, struct prefix *p) { Qpb__L3Prefix *prefix; prefix = QPB_ALLOC(allocator, typeof(*prefix)); if (!prefix) { return NULL; } qpb__l3_prefix__init(prefix); prefix->length = p->prefixlen; prefix->bytes.len = (p->prefixlen + 7) / 8; prefix->bytes.data = qpb_alloc(allocator, prefix->bytes.len); if (!prefix->bytes.data) { return NULL; } memcpy(prefix->bytes.data, &p->u.prefix, prefix->bytes.len); return prefix; } /* * qpb__l3_prefix__get */ #define qpb_l3_prefix_get qpb__l3_prefix__get static inline int qpb__l3_prefix__get(const Qpb__L3Prefix *pb_prefix, uint8_t family, struct prefix *prefix) { switch (family) { case AF_INET: memset(prefix, 0, sizeof(struct prefix_ipv4)); break; case AF_INET6: memset(prefix, 0, sizeof(struct prefix_ipv6)); break; default: memset(prefix, 0, sizeof(*prefix)); } prefix->prefixlen = pb_prefix->length; prefix->family = family; memcpy(&prefix->u.prefix, pb_prefix->bytes.data, pb_prefix->bytes.len); return 1; } /* * qpb__protocol__set * * Translate a quagga route type to a protobuf protocol. */ #define qpb_protocol_set qpb__protocol__set static inline int qpb__protocol__set(Qpb__Protocol *pb_proto, int route_type) { switch (route_type) { case ZEBRA_ROUTE_KERNEL: *pb_proto = QPB__PROTOCOL__KERNEL; break; case ZEBRA_ROUTE_CONNECT: *pb_proto = QPB__PROTOCOL__CONNECTED; break; case ZEBRA_ROUTE_STATIC: *pb_proto = QPB__PROTOCOL__STATIC; break; case ZEBRA_ROUTE_RIP: *pb_proto = QPB__PROTOCOL__RIP; break; case ZEBRA_ROUTE_RIPNG: *pb_proto = QPB__PROTOCOL__RIPNG; break; case ZEBRA_ROUTE_OSPF: case ZEBRA_ROUTE_OSPF6: *pb_proto = QPB__PROTOCOL__OSPF; break; case ZEBRA_ROUTE_ISIS: *pb_proto = QPB__PROTOCOL__ISIS; break; case ZEBRA_ROUTE_BGP: *pb_proto = QPB__PROTOCOL__BGP; break; case ZEBRA_ROUTE_HSLS: case ZEBRA_ROUTE_OLSR: case ZEBRA_ROUTE_MAX: case ZEBRA_ROUTE_SYSTEM: default: *pb_proto = QPB__PROTOCOL__OTHER; } return 1; } /* * qpb__ipv4_address__create */ static inline Qpb__Ipv4Address * qpb__ipv4_address__create(qpb_allocator_t *allocator, struct in_addr *addr) { Qpb__Ipv4Address *v4; v4 = QPB_ALLOC(allocator, typeof(*v4)); if (!v4) { return NULL; } qpb__ipv4_address__init(v4); v4->value = ntohl(addr->s_addr); return v4; } /* * qpb__ipv4_address__get */ static inline int qpb__ipv4_address__get(const Qpb__Ipv4Address *v4, struct in_addr *addr) { addr->s_addr = htonl(v4->value); return 1; } /* * qpb__ipv6_address__create */ static inline Qpb__Ipv6Address * qpb__ipv6_address__create(qpb_allocator_t *allocator, struct in6_addr *addr) { Qpb__Ipv6Address *v6; v6 = QPB_ALLOC(allocator, typeof(*v6)); if (!v6) return NULL; qpb__ipv6_address__init(v6); v6->bytes.len = 16; v6->bytes.data = qpb_alloc(allocator, 16); if (!v6->bytes.data) return NULL; memcpy(v6->bytes.data, addr->s6_addr, v6->bytes.len); return v6; } /* * qpb__ipv6_address__get * * Read out information from a protobuf ipv6 address structure. */ static inline int qpb__ipv6_address__get(const Qpb__Ipv6Address *v6, struct in6_addr *addr) { if (v6->bytes.len != 16) return 0; memcpy(addr->s6_addr, v6->bytes.data, v6->bytes.len); return 1; } /* * qpb__l3_address__create */ #define qpb_l3_address_create qpb__l3_address__create static inline Qpb__L3Address * qpb__l3_address__create(qpb_allocator_t *allocator, union g_addr *addr, uint8_t family) { Qpb__L3Address *l3_addr; l3_addr = QPB_ALLOC(allocator, typeof(*l3_addr)); if (!l3_addr) return NULL; qpb__l3_address__init(l3_addr); switch (family) { case AF_INET: l3_addr->v4 = qpb__ipv4_address__create(allocator, &addr->ipv4); if (!l3_addr->v4) return NULL; break; case AF_INET6: l3_addr->v6 = qpb__ipv6_address__create(allocator, &addr->ipv6); if (!l3_addr->v6) return NULL; break; } return l3_addr; } /* * qpb__l3_address__get * * Read out a gateway address from a protobuf l3 address. */ #define qpb_l3_address_get qpb__l3_address__get static inline int qpb__l3_address__get(const Qpb__L3Address *l3_addr, uint8_t *family, union g_addr *addr) { if (l3_addr->v4) { qpb__ipv4_address__get(l3_addr->v4, &addr->ipv4); *family = AF_INET; return 1; } if (l3_addr->v6) { qpb__ipv6_address__get(l3_addr->v6, &addr->ipv6); *family = AF_INET6; return 1; } return 0; } /* * qpb__if_identifier__create */ #define qpb_if_identifier_create qpb__if_identifier__create static inline Qpb__IfIdentifier * qpb__if_identifier__create(qpb_allocator_t *allocator, uint if_index) { Qpb__IfIdentifier *if_id; if_id = QPB_ALLOC(allocator, typeof(*if_id)); if (!if_id) { return NULL; } qpb__if_identifier__init(if_id); if_id->has_index = 1; if_id->index = if_index; return if_id; } /* * qpb__if_identifier__get * * Get interface name and/or if_index from an if identifier. */ #define qpb_if_identifier_get qpb__if_identifier__get static inline int qpb__if_identifier__get(Qpb__IfIdentifier *if_id, uint *if_index, char **name) { char *str; uint ix; if (!if_index) if_index = &ix; if (!name) name = &str; if (if_id->has_index) *if_index = if_id->index; else *if_index = 0; *name = if_id->name; return 1; } #endif frr-7.2.1/qpb/qpb.proto0000644000000000000000000000401013610377563011640 00000000000000/* * qpb.proto * * @copyright Copyright (C) 2016 Sproute Networks, Inc. * * @author Avneesh Sachdev * * Permission to use, copy, modify, and/or distribute this software * for any purpose with or without fee is hereby granted, provided * that the above copyright notice and this permission notice appear * in all copies. * * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL * WARRANTIES WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED * WARRANTIES OF MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE * AUTHOR BE LIABLE FOR ANY SPECIAL, DIRECT, INDIRECT, OR * CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM LOSS * OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, * NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN * CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. */ syntax = "proto2"; /* * Protobuf definitions pertaining to the Quagga/FRR Protobuf component. */ package qpb; enum AddressFamily { UNKNOWN_AF = 0; IPV4 = 1; // IP version 4 IPV6 = 2; // IP version 6 }; enum SubAddressFamily { UNKNOWN_SAF = 0; UNICAST = 1; MULTICAST = 2; }; // // An IP version 4 address, such as 10.1.1.1. // message Ipv4Address { required fixed32 value = 1 ; }; message Ipv6Address { // 16 bytes. required bytes bytes = 1; }; // // An IP version 4 or IP version 6 address. // message L3Address { optional Ipv4Address v4 = 1; optional Ipv6Address v6 = 2; }; // // An IP prefix, such as 10.1/16. // We use the message below to represent both IPv4 and IPv6 prefixes. message L3Prefix { required uint32 length = 1; required bytes bytes = 2; }; // // Something that identifies an interface on a machine. It can either // be a name (for instance, 'eth0') or a number currently. // message IfIdentifier { optional uint32 index = 1; optional string name = 2; }; enum Protocol { UNKNOWN_PROTO = 0; LOCAL = 1; CONNECTED = 2; KERNEL = 3; STATIC = 4; RIP = 5; RIPNG = 6; OSPF = 7; ISIS = 8; BGP = 9; OTHER = 10; } frr-7.2.1/qpb/qpb_allocator.c0000644000000000000000000000313413610377563012765 00000000000000/* * qpb_allocator.c * * @copyright Copyright (C) 2016 Sproute Networks, Inc. * * @author Avneesh Sachdev * * This file is part of Quagga. * * Quagga is free software; you can redistribute it and/or modify it * under the terms of the GNU General Public License as published by the * Free Software Foundation; either version 2, or (at your option) any * later version. * * Quagga 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; see the file COPYING; if not, write to the Free Software * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA */ #ifdef HAVE_CONFIG_H #include "config.h" #endif #include "linear_allocator.h" #include "qpb_allocator.h" /* * _qpb_alloc */ static void *_qpb_alloc(void *allocator_data, size_t size) { return linear_allocator_alloc(allocator_data, size); } /* * _qpb_free */ static void _qpb_free(void *allocator_data, void *ptr) { linear_allocator_free(allocator_data, ptr); } static ProtobufCAllocator allocator_template = {_qpb_alloc, _qpb_free, NULL}; /* * qpb_allocator_init_linear * * Initialize qpb_allocator_t with the given linear allocator. */ void qpb_allocator_init_linear(qpb_allocator_t *allocator, linear_allocator_t *linear_allocator) { *allocator = allocator_template; allocator->allocator_data = linear_allocator; } frr-7.2.1/qpb/qpb_allocator.h0000644000000000000000000000626113610377563012776 00000000000000/* * qpb_allocator.h * * @copyright Copyright (C) 2016 Sproute Networks, Inc. * * @author Avneesh Sachdev * * This file is part of Quagga. * * Quagga is free software; you can redistribute it and/or modify it * under the terms of the GNU General Public License as published by the * Free Software Foundation; either version 2, or (at your option) any * later version. * * Quagga 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; see the file COPYING; if not, write to the Free Software * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA */ /* * Header file for Quagga/FRR protobuf memory management code. */ #ifndef _QPB_ALLOCATOR_H_ #define _QPB_ALLOCATOR_H_ #include struct linear_allocator_t_; /* * Alias for ProtobufCAllocator that is easier on the fingers. */ typedef ProtobufCAllocator qpb_allocator_t; /* * qpb_alloc */ static inline void *qpb_alloc(qpb_allocator_t *allocator, size_t size) { return allocator->alloc(allocator->allocator_data, size); } /* * qpb_alloc_ptr_array * * Allocate space for the specified number of pointers. */ static inline void *qpb_alloc_ptr_array(qpb_allocator_t *allocator, size_t num_ptrs) { return qpb_alloc(allocator, num_ptrs * sizeof(void *)); } /* * qpb_free */ static inline void qpb_free(qpb_allocator_t *allocator, void *ptr) { allocator->free(allocator->allocator_data, ptr); } /* * QPB_ALLOC * * Convenience macro to reduce the probability of allocating memory of * incorrect size. It returns enough memory to store the given type, * and evaluates to an appropriately typed pointer. */ #define QPB_ALLOC(allocator, type) (type *)qpb_alloc(allocator, sizeof(type)) /* * Externs. */ extern void qpb_allocator_init_linear(qpb_allocator_t *, struct linear_allocator_t_ *); /* * The following macros are for the common case where a qpb allocator * is being used alongside a linear allocator that allocates memory * off of the stack. */ #define QPB_DECLARE_STACK_ALLOCATOR(allocator, size) \ qpb_allocator_t allocator; \ linear_allocator_t lin_##allocator; \ char lin_##allocator##_buf[size] #define QPB_INIT_STACK_ALLOCATOR(allocator) \ do { \ linear_allocator_init(&(lin_##allocator), \ lin_##allocator##_buf, \ sizeof(lin_##allocator##_buf)); \ qpb_allocator_init_linear(&allocator, &(lin_##allocator)); \ } while (0) #define QPB_RESET_STACK_ALLOCATOR(allocator) \ do { \ linear_allocator_reset(&(lin_##allocator)); \ } while (0) #endif /* _QPB_ALLOCATOR_H_ */ frr-7.2.1/qpb/subdir.am0000644000000000000000000000166613610377563011616 00000000000000if HAVE_PROTOBUF lib_LTLIBRARIES += qpb/libfrr_pb.la endif qpb_libfrr_pb_la_CPPFLAGS = $(AM_CPPFLAGS) $(PROTOBUF_C_CFLAGS) qpb_libfrr_pb_la_LIBADD = $(PROTOBUF_C_LIBS) qpb_libfrr_pb_la_LDFLAGS = -version-info 0:0:0 qpb_libfrr_pb_la_SOURCES = \ qpb/qpb.c \ qpb/qpb_allocator.c \ # end nodist_qpb_libfrr_pb_la_SOURCES = \ qpb/qpb.pb-c.c \ # end noinst_HEADERS += \ qpb/linear_allocator.h \ qpb/qpb.h \ qpb/qpb_allocator.h \ # end CLEANFILES += \ qpb/qpb.pb-c.c \ qpb/qpb.pb-c.h \ # end EXTRA_DIST += qpb/qpb.proto if HAVE_PROTOBUF # Rules .proto.pb.h: $(PROTOC) -I$(top_srcdir) --cpp_out=$(top_srcdir) $(top_srcdir)/$^ AM_V_PROTOC_C = $(am__v_PROTOC_C_$(V)) am__v_PROTOC_C_ = $(am__v_PROTOC_C_$(AM_DEFAULT_VERBOSITY)) am__v_PROTOC_C_0 = @echo " PROTOC_C" $@; am__v_PROTOC_C_1 = .proto.pb-c.c: $(AM_V_PROTOC_C)$(PROTOC_C) -I$(top_srcdir) --c_out=$(top_srcdir) $(top_srcdir)/$^ .pb-c.c.pb-c.h: @/bin/true endif # HAVE_PROTOBUF frr-7.2.1/redhat/0000755000000000000000000000000013610377563010543 500000000000000frr-7.2.1/redhat/frr.logrotate0000644000000000000000000000420313610377563013175 00000000000000/var/log/frr/frr.log { notifempty missingok postrotate /bin/kill -HUP `cat /var/run/*syslog*.pid 2> /dev/null` 2> /dev/null || true endscript } /var/log/frr/zebra.log { notifempty missingok postrotate /bin/kill -USR1 `cat /var/run/frr/zebra.pid 2> /dev/null` 2> /dev/null || true endscript } /var/log/frr/bgpd.log { notifempty missingok postrotate /bin/kill -USR1 `cat /var/run/frr/bgpd.pid 2> /dev/null` 2> /dev/null || true endscript } /var/log/frr/isisd.log { notifempty missingok postrotate /bin/kill -USR1 `cat /var/run/frr/isisd.pid 2> /dev/null` 2> /dev/null || true endscript } /var/log/frr/ospfd.log { notifempty missingok postrotate /bin/kill -USR1 `cat /var/run/frr/ospfd.pid 2> /dev/null` 2> /dev/null || true endscript } /var/log/frr/ospf6d.log { notifempty missingok postrotate /bin/kill -USR1 `cat /var/run/frr/ospf6d.pid 2> /dev/null` 2> /dev/null || true endscript } /var/log/frr/ripd.log { notifempty missingok postrotate /bin/kill -USR1 `cat /var/run/frr/ripd.pid 2> /dev/null` 2> /dev/null || true endscript } /var/log/frr/ripngd.log { notifempty missingok postrotate /bin/kill -USR1 `cat /var/run/frr/ripngd.pid 2> /dev/null` 2> /dev/null || true endscript } /var/log/frr/ldpd.log { notifempty missingok postrotate /bin/kill -USR1 `cat /var/run/frr/ldpd.pid 2> /dev/null` 2> /dev/null || true endscript } /var/log/frr/nhrpd.log { notifempty missingok postrotate /bin/kill -USR1 `cat /var/run/frr/nhrpd.pid 2> /dev/null` 2> /dev/null || true endscript } /var/log/frr/eigrpd.log { notifempty missingok postrotate /bin/kill -USR1 `cat /var/run/frr/eigrpd.pid 2> /dev/null` 2> /dev/null || true endscript } /var/log/frr/bfdd.log { notifempty missingok postrotate /bin/kill -USR1 `cat /var/run/frr/bfdd.pid 2> /dev/null` 2> /dev/null || true endscript } /var/log/frr/fabricd.log { notifempty missingok postrotate /bin/kill -USR1 `cat /var/run/frr/fabricd.pid 2> /dev/null` 2> /dev/null || true endscript } frr-7.2.1/redhat/frr.pam0000644000000000000000000000171113610377563011753 00000000000000#%PAM-1.0 # ##### if running frr as root: # Only allow root (and possibly wheel) to use this because enable access # is unrestricted. auth sufficient pam_rootok.so # Uncomment the following line to implicitly trust users in the "wheel" group. #auth sufficient pam_wheel.so trust use_uid # Uncomment the following line to require a user to be in the "wheel" group. #auth required pam_wheel.so use_uid ########################################################### # If using frr privileges and with a seperate group for vty access, then # access can be controlled via the vty access group, and pam can simply # check for valid user/password, eg: # # only allow local users. #auth required pam_securetty.so #auth include system-auth #auth required pam_nologin.so #account include system-auth #password include system-auth #session include system-auth #session optional pam_console.so frr-7.2.1/redhat/frr.spec0000644000000000000000000007644213610377563012145 00000000000000# configure options # # Some can be overridden on rpmbuild commandline with: # rpmbuild --define 'variable value' # (use any value, ie 1 for flag "with_XXXX" definitions) # # E.g. rpmbuild --define 'release_rev 02' may be useful if building # rpms again and again on the same day, so the newer rpms can be installed. # bumping the number each time. #################### FRRouting (FRR) configure options ##################### # with-feature options %{!?with_babeld: %global with_babeld 1 } %{!?with_bfdd: %global with_bfdd 1 } %{!?with_bgp_vnc: %global with_bgp_vnc 0 } %{!?with_cumulus: %global with_cumulus 0 } %{!?with_eigrpd: %global with_eigrpd 1 } %{!?with_fpm: %global with_fpm 1 } %{!?with_ldpd: %global with_ldpd 1 } %{!?with_multipath: %global with_multipath 256 } %{!?with_nhrpd: %global with_nhrpd 1 } %{!?with_ospfapi: %global with_ospfapi 1 } %{!?with_ospfclient: %global with_ospfclient 1 } %{!?with_pam: %global with_pam 0 } %{!?with_pbrd: %global with_pbrd 1 } %{!?with_pimd: %global with_pimd 1 } %{!?with_vrrpd: %global with_vrrpd 1 } %{!?with_rpki: %global with_rpki 0 } %{!?with_rtadv: %global with_rtadv 1 } %{!?with_watchfrr: %global with_watchfrr 1 } # user and group %{!?frr_user: %global frr_user frr } %{!?vty_group: %global vty_group frrvty } # path defines %define configdir %{_sysconfdir}/%{name} %define _sbindir /usr/lib/frr %define zeb_src %{_builddir}/%{name}-%{frrversion} %define zeb_rh_src %{zeb_src}/redhat %define zeb_docs %{zeb_src}/doc %define frr_tools %{zeb_src}/tools # defines for configure %define rundir %{_localstatedir}/run/%{name} ############################################################################ #### Version String tweak # Remove invalid characters form version string and replace with _ %{expand: %%global rpmversion %(echo '7.2.1' | tr [:blank:]- _ )} %define frrversion 7.2.1 #### Check for systemd or init.d (upstart) # Check for init.d (upstart) as used in CentOS 6 or systemd (ie CentOS 7) %if 0%{?fedora} || 0%{?rhel} >= 7 || 0%{?suse_version} >= 1210 %global initsystem systemd %else %if 0%{?rhel} && 0%{?rhel} < 7 %global initsystem upstart %else %{expand: %%global initsystem %(if [[ `/sbin/init --version 2> /dev/null` =~ upstart ]]; then echo upstart; elif [[ `readlink -f /sbin/init` = /usr/lib/systemd/systemd ]]; then echo systemd; elif [[ `systemctl` =~ -\.mount ]]; then echo systemd; fi)} %endif %endif # If init system is systemd, then always enable watchfrr %if "%{initsystem}" == "systemd" %global with_watchfrr 1 %endif #### Check for RedHat 6.x or CentOS 6.x - they are too old to support PIM. #### Always disable it on these old systems unconditionally # # if CentOS / RedHat and version < 7, then disable PIMd (too old, won't work) %if 0%{?rhel} && 0%{?rhel} < 7 %global with_pimd 0 %endif # misc internal defines %{!?frr_uid: %global frr_uid 92 } %{!?frr_gid: %global frr_gid 92 } %{!?vty_gid: %global vty_gid 85 } %define daemon_list zebra ripd ospfd bgpd isisd ripngd ospf6d pbrd staticd bfdd fabricd %if %{with_ldpd} %define daemon_ldpd ldpd %else %define daemon_ldpd "" %endif %if %{with_pimd} %define daemon_pimd pimd %else %define daemon_pimd "" %endif %if %{with_pbrd} %define daemon_pbrd pbrd %else %define daemon_pbrd "" %endif %if %{with_nhrpd} %define daemon_nhrpd nhrpd %else %define daemon_nhrpd "" %endif %if %{with_eigrpd} %define daemon_eigrpd eigrpd %else %define daemon_eigrpd "" %endif %if %{with_babeld} %define daemon_babeld babeld %else %define daemon_babeld "" %endif %if %{with_vrrpd} %define daemon_vrrpd vrrpd %else %define daemon_vrrpd "" %endif %if %{with_watchfrr} %define daemon_watchfrr watchfrr %else %define daemon_watchfrr "" %endif %if %{with_bfdd} %define daemon_bfdd bfdd %else %define daemon_bfdd "" %endif %define all_daemons %{daemon_list} %{daemon_ldpd} %{daemon_pimd} %{daemon_nhrpd} %{daemon_eigrpd} %{daemon_babeld} %{daemon_watchfrr} %{daemon_pbrd} %{daemon_bfdd} %{daemon_vrrpd} #release sub-revision (the two digits after the CONFDATE) %{!?release_rev: %global release_rev 01 } Summary: Routing daemon Name: frr Version: %{rpmversion} Release: %{release_rev}%{?dist} License: GPLv2+ Group: System Environment/Daemons Source0: https://github.com/FRRouting/frr/archive/%{name}-%{frrversion}.tar.gz URL: https://www.frrouting.org Requires(pre): shadow-utils Requires(preun): info Requires(post): info BuildRequires: bison >= 2.7 BuildRequires: c-ares-devel BuildRequires: flex BuildRequires: gcc BuildRequires: json-c-devel BuildRequires: libcap-devel BuildRequires: make BuildRequires: ncurses-devel BuildRequires: readline-devel BuildRequires: texinfo BuildRequires: libyang-devel >= 0.16.74 %if 0%{?rhel} && 0%{?rhel} < 7 #python27-devel is available from ius community repo for RedHat/CentOS 6 BuildRequires: python27-devel BuildRequires: python27-sphinx %else BuildRequires: python-devel >= 2.7 BuildRequires: python-sphinx %endif Requires: initscripts %if %{with_pam} BuildRequires: pam-devel %endif %if %{with_rpki} BuildRequires: librtr-devel >= 0.5 %endif %if "%{initsystem}" == "systemd" BuildRequires: systemd BuildRequires: systemd-devel Requires(post): systemd Requires(preun): systemd Requires(postun): systemd %else Requires(post): chkconfig Requires(preun): chkconfig # Initscripts > 5.60 is required for IPv6 support Requires(pre): initscripts >= 5.60 %endif Provides: routingdaemon = %{version}-%{release} Obsoletes: gated mrt zebra frr-sysvinit Conflicts: bird %description FRRouting is a free software that manages TCP/IP based routing protocol. It takes multi-server and multi-thread approach to resolve the current complexity of the Internet. FRRouting supports BGP4, OSPFv2, OSPFv3, ISIS, RIP, RIPng, PIM, LDP NHRP, Babel, PBR, EIGRP and BFD. FRRouting is a fork of Quagga. %package contrib Summary: contrib tools for frr Group: System Environment/Daemons %description contrib Contributed/3rd party tools which may be of use with frr. %package pythontools Summary: python tools for frr BuildRequires: python Requires: python-ipaddress Group: System Environment/Daemons %description pythontools Contributed python 2.7 tools which may be of use with frr. %package devel Summary: Header and object files for frr development Group: System Environment/Daemons Requires: %{name} = %{version}-%{release} %description devel The frr-devel package contains the header and object files neccessary for developing OSPF-API and frr applications. %prep %setup -q -n frr-%{frrversion} %build # For standard gcc verbosity, uncomment these lines: #CFLAGS="%{optflags} -Wall -Wsign-compare -Wpointer-arith" #CFLAGS="${CFLAGS} -Wbad-function-cast -Wwrite-strings" # For ultra gcc verbosity, uncomment these lines also: #CFLAGS="${CFLAGS} -W -Wcast-qual -Wstrict-prototypes" #CFLAGS="${CFLAGS} -Wmissing-declarations -Wmissing-noreturn" #CFLAGS="${CFLAGS} -Wmissing-format-attribute -Wunreachable-code" #CFLAGS="${CFLAGS} -Wpacked -Wpadded" %configure \ --sbindir=%{_sbindir} \ --sysconfdir=%{configdir} \ --localstatedir=%{rundir} \ --disable-static \ --disable-werror \ --enable-irdp \ %if %{with_multipath} --enable-multipath=%{with_multipath} \ %endif --enable-vtysh \ %if %{with_ospfclient} --enable-ospfclient \ %else --disable-ospfclient\ %endif %if %{with_ospfapi} --enable-ospfapi \ %else --disable-ospfapi \ %endif %if %{with_rtadv} --enable-rtadv \ %else --disable-rtadv \ %endif %if %{with_ldpd} --enable-ldpd \ %else --disable-ldpd \ %endif %if %{with_pimd} --enable-pimd \ %else --disable-pimd \ %endif %if %{with_pbrd} --enable-pbrd \ %else --disable-pbrd \ %endif %if %{with_nhrpd} --enable-nhrpd \ %else --disable-nhrpd \ %endif %if %{with_eigrpd} --enable-eigrpd \ %else --disable-eigrpd \ %endif %if %{with_babeld} --enable-babeld \ %else --disable-babeld \ %endif %if %{with_vrrpd} --enable-vrrpd \ %else --disable-vrrpd \ %endif %if %{with_pam} --with-libpam \ %endif %if 0%{?frr_user:1} --enable-user=%{frr_user} \ --enable-group=%{frr_user} \ %endif %if 0%{?vty_group:1} --enable-vty-group=%{vty_group} \ %endif %if %{with_fpm} --enable-fpm \ %else --disable-fpm \ %endif %if %{with_watchfrr} --enable-watchfrr \ %else --disable-watchfrr \ %endif %if %{with_cumulus} --enable-cumulus \ %endif %if %{with_bgp_vnc} --enable-bgp-vnc \ %else --disable-bgp-vnc \ %endif --enable-isisd \ %if "%{initsystem}" == "systemd" --enable-systemd \ %endif %if %{with_rpki} --enable-rpki \ %else --disable-rpki \ %endif %if %{with_bfdd} --enable-bfdd \ %else --disable-bfdd \ %endif # end make %{?_smp_mflags} MAKEINFO="makeinfo --no-split" pushd doc make info popd %install mkdir -p %{buildroot}%{_sysconfdir}/{frr,sysconfig,logrotate.d,pam.d,default} \ %{buildroot}%{_localstatedir}/log/frr %{buildroot}%{_infodir} make DESTDIR=%{buildroot} INSTALL="install -p" CP="cp -p" install # Remove this file, as it is uninstalled and causes errors when building on RH9 rm -rf %{buildroot}/usr/share/info/dir # Remove debian init script if it was installed rm -f %{buildroot}%{_sbindir}/frr # kill bogus libtool files rm -vf %{buildroot}%{_libdir}/frr/modules/*.la rm -vf %{buildroot}%{_libdir}/*.la rm -vf %{buildroot}%{_libdir}/frr/libyang_plugins/*.la # install /etc sources %if "%{initsystem}" == "systemd" mkdir -p %{buildroot}%{_unitdir} install -m644 %{zeb_src}/tools/frr.service %{buildroot}%{_unitdir}/frr.service %else mkdir -p %{buildroot}%{_initddir} ln -s %{_sbindir}/frrinit.sh %{buildroot}%{_initddir}/frr %endif install %{zeb_src}/tools/etc/frr/daemons %{buildroot}%{_sysconfdir}/frr # add rpki module to daemon %if %{with_rpki} sed -i -e 's/^\(bgpd_options=\)\(.*\)\(".*\)/\1\2 -M rpki\3/' %{buildroot}%{_sysconfdir}/frr/daemons %endif install -m644 %{zeb_rh_src}/frr.pam %{buildroot}%{_sysconfdir}/pam.d/frr install -m644 %{zeb_rh_src}/frr.logrotate %{buildroot}%{_sysconfdir}/logrotate.d/frr install -d -m750 %{buildroot}%{rundir} %pre # add vty_group %if 0%{?vty_group:1} getent group %{vty_group} >/dev/null || groupadd -r -g %{vty_gid} %{vty_group} %endif # add frr user and group %if 0%{?frr_user:1} # Ensure that frr_gid gets correctly allocated getent group %{frr_user} >/dev/null || groupadd -g %{frr_gid} %{frr_user} getent passwd %{frr_user} >/dev/null || \ useradd -r -u %{frr_uid} -g %{frr_user} \ -s /sbin/nologin -c "FRRouting suite" \ -d %{rundir} %{frr_user} %if 0%{?vty_group:1} usermod -a -G %{vty_group} %{frr_user} %endif %endif exit 0 %post # zebra_spec_add_service # e.g. zebra_spec_add_service zebrasrv 2600/tcp "zebra service" zebra_spec_add_service () { # Add port /etc/services entry if it isn't already there if [ -f %{_sysconfdir}/services ] && \ ! %__sed -e 's/#.*$//' %{_sysconfdir}/services | %__grep -wq $1 ; then echo "$1 $2 # $3" >> %{_sysconfdir}/services fi } zebra_spec_add_service zebrasrv 2600/tcp "zebra service" zebra_spec_add_service zebra 2601/tcp "zebra vty" zebra_spec_add_service staticd 2616/tcp "staticd vty" zebra_spec_add_service ripd 2602/tcp "RIPd vty" zebra_spec_add_service ripngd 2603/tcp "RIPngd vty" zebra_spec_add_service ospfd 2604/tcp "OSPFd vty" zebra_spec_add_service bgpd 2605/tcp "BGPd vty" zebra_spec_add_service ospf6d 2606/tcp "OSPF6d vty" zebra_spec_add_service isisd 2608/tcp "ISISd vty" %if %{with_ospfapi} zebra_spec_add_service ospfapi 2607/tcp "OSPF-API" %endif %if %{with_babeld} zebra_spec_add_service babeld 2609/tcp "BABELd vty" %endif %if %{with_nhrpd} zebra_spec_add_service nhrpd 2610/tcp "NHRPd vty" %endif %if %{with_pimd} zebra_spec_add_service pimd 2611/tcp "PIMd vty" %endif %if %{with_pbrd} zebra_spec_add_service pbrd 2615/tcp "PBRd vty" %endif %if %{with_ldpd} zebra_spec_add_service ldpd 2612/tcp "LDPd vty" %endif %if %{with_eigrpd} zebra_spec_add_service eigrpd 2613/tcp "EIGRPd vty" %endif %if %{with_bfdd} zebra_spec_add_service bfdd 2617/tcp "BFDd vty" %endif zebra_spec_add_service fabricd 2618/tcp "Fabricd vty" %if %{with_vrrpd} zebra_spec_add_service vrrpd 2619/tcp "VRRPd vty" %endif %if "%{initsystem}" == "systemd" for daemon in %all_daemons ; do %systemd_post frr.service done %else /sbin/chkconfig --add frr %endif # Fix bad path in previous config files # Config files won't get replaced by default, so we do this ugly hack to fix it %__sed -i 's|watchfrr_options=|#watchfrr_options=|g' %{configdir}/daemons 2> /dev/null || true # With systemd, watchfrr is mandatory. Fix config to make sure it's enabled if # we install or upgrade to a frr built with systemd %if "%{initsystem}" == "systemd" %__sed -i 's|watchfrr_enable=no|watchfrr_enable=yes|g' %{configdir}/daemons 2> /dev/null || true %endif /sbin/install-info %{_infodir}/frr.info.gz %{_infodir}/dir # Create dummy files if they don't exist so basic functions can be used. if [ ! -e %{configdir}/zebra.conf ]; then echo "hostname `hostname`" > %{configdir}/zebra.conf %if 0%{?frr_user:1} chown %{frr_user}:%{frr_user} %{configdir}/zebra.conf* %endif chmod 640 %{configdir}/zebra.conf* fi for daemon in %{all_daemons} ; do if [ x"${daemon}" != x"" ] ; then if [ ! -e %{configdir}/${daemon}.conf ]; then touch %{configdir}/${daemon}.conf %if 0%{?frr_user:1} chown %{frr_user}:%{frr_user} %{configdir}/${daemon}.conf* %endif fi fi done %if 0%{?frr_user:1} chown %{frr_user}:%{frr_user} %{configdir}/daemons %endif %if %{with_watchfrr} # No config for watchfrr - this is part of /etc/sysconfig/frr rm -f %{configdir}/watchfrr.* %endif if [ ! -e %{configdir}/vtysh.conf ]; then touch %{configdir}/vtysh.conf chmod 640 %{configdir}/vtysh.conf %if 0%{?frr_user:1} %if 0%{?vty_group:1} chown %{frr_user}:%{vty_group} %{configdir}/vtysh.conf* %endif %endif fi %postun if [ "$1" -ge 1 ]; then # # Upgrade from older version # %if "%{initsystem}" == "systemd" ## ## Systemd Version ## %systemd_postun_with_restart frr.service %else ## ## init.d Version ## service frr restart >/dev/null 2>&1 %endif : fi %preun %if "%{initsystem}" == "systemd" ## ## Systemd Version ## if [ $1 -eq 0 ] ; then %systemd_preun frr.service fi %else ## ## init.d Version ## if [ $1 -eq 0 ] ; then service frr stop >/dev/null 2>&1 /sbin/chkconfig --del frr fi %endif /sbin/install-info --delete %{_infodir}/frr.info.gz %{_infodir}/dir %files %doc */*.sample* COPYING %doc doc/mpls %doc README.md /usr/share/yang/*.yang %if 0%{?frr_user:1} %dir %attr(751,%{frr_user},%{frr_user}) %{configdir} %dir %attr(750,%{frr_user},%{frr_user}) %{_localstatedir}/log/frr %dir %attr(751,%{frr_user},%{frr_user}) %{rundir} %else %dir %attr(750,root,root) %{configdir} %dir %attr(750,root,root) %{_localstatedir}/log/frr %dir %attr(750,root,root) %{rundir} %endif %if 0%{?vty_group:1} %attr(750,%{frr_user},%{vty_group}) %{configdir}/vtysh.conf.sample %endif %{_infodir}/frr.info.gz %{_mandir}/man*/* %{_sbindir}/zebra %{_sbindir}/staticd %{_sbindir}/ospfd %{_sbindir}/ripd %{_sbindir}/bgpd %exclude %{_sbindir}/ssd %if %{with_watchfrr} %{_sbindir}/watchfrr %endif %{_sbindir}/ripngd %{_sbindir}/ospf6d %if %{with_pimd} %{_sbindir}/pimd %endif %if %{with_pbrd} %{_sbindir}/pbrd %endif %if %{with_vrrpd} %{_sbindir}/vrrpd %endif %{_sbindir}/isisd %{_sbindir}/fabricd %if %{with_ldpd} %{_sbindir}/ldpd %endif %if %{with_eigrpd} %{_sbindir}/eigrpd %endif %if %{with_nhrpd} %{_sbindir}/nhrpd %endif %if %{with_babeld} %{_sbindir}/babeld %endif %if %{with_bfdd} %{_sbindir}/bfdd %endif %{_libdir}/lib*.so.0 %{_libdir}/lib*.so.0.* %if %{with_fpm} %{_libdir}/frr/modules/zebra_fpm.so %endif %if %{with_rpki} %{_libdir}/frr/modules/bgpd_rpki.so %endif %{_libdir}/frr/modules/zebra_irdp.so %{_libdir}/frr/modules/bgpd_bmp.so %{_bindir}/* %config(noreplace) %{configdir}/[!v]*.conf* %config(noreplace) %attr(750,%{frr_user},%{frr_user}) %{configdir}/daemons %if "%{initsystem}" == "systemd" %{_unitdir}/frr.service %else %{_initddir}/frr %endif %config(noreplace) %{_sysconfdir}/pam.d/frr %config(noreplace) %{_sysconfdir}/logrotate.d/frr %{_sbindir}/frr-reload %{_sbindir}/frrcommon.sh %{_sbindir}/frrinit.sh %{_sbindir}/watchfrr.sh %files contrib %doc tools %files pythontools %{_sbindir}/frr-reload.py %{_sbindir}/frr-reload.pyc %{_sbindir}/frr-reload.pyo %files devel %{_libdir}/lib*.so %dir %{_includedir}/%{name} %{_includedir}/%{name}/*.h %dir %{_includedir}/%{name}/ospfd %{_includedir}/%{name}/ospfd/*.h %if %{with_ospfapi} %dir %{_includedir}/%{name}/ospfapi %{_includedir}/%{name}/ospfapi/*.h %endif %if %{with_eigrpd} %dir %{_includedir}/%{name}/eigrpd %{_includedir}/%{name}/eigrpd/*.h %endif %changelog * Fri Jan 17 2020 Martin Winter - %{version} - BGPd - Fix Addpath issue - Do not apply eBGP policy for iBGP peers - Show `ip` and `fqdn` in json output for `show [ip] bgp json` - Fix large route-distinguisher's format - Fix `no bgp listen range ...` configuration command - Autocomplete neighbor for clear bgp - Reflect the distance in RIB when it is changed for an arbitrary afi/safi - Notify "Peer De-configured" after entering 'no neighbor cmd - Fix per afi/safi addpath peer counting - Rework BGP dampening to be per AFI/SAFI - Do not send next-hop as :: in MP_REACH_NLRI if no link-local exists - Override peer's TTL only if peer-group is configured with TTL - Remove error message for unkown afi/safi combination - Keep the session down if maximum-prefix is reached - OSPFd - Fix BFD down not tearing down OSPF adjacency for point-to-point net - BFDd - Fix multiple VRF handling - VRF security improvement - PIMd - Fix rp crash - NHRPd - Make sure `no ip nhrp map ` works as expected - LDPd - Add missing sanity check in the parsing of label messages - Zebra - Use correct state when installing evpn macs - Capture dplane plugin flags - lib - Fix interface config when vrf changes - Fix Interface Infinite Loop Walk (for special interfaces such as bond) - snapcraft - fix missing vrrpd daemon - Others - Rename man pages (to avoid conflicts with other packages) - Various other fixes for code cleanup and memory leaks * Tue Oct 15 2019 Martin Winter - 7.2 - ALL Daemons - -N to allow for config file locating when running FRR inside of a namespace - Impoved Testing across all daemons - BFD - VRF Support - Conversion to Northbound interface - BGP - Aggregate-address add route-map support - BMP Support - Improved JSON output for many commands - `show bgp afi safi summary failed` command - `clear bop *` clears all peers - Show FQDN for `show bgp ipv4 uni` commands - Display BestPath selection reason as part of show commands - EIGRP - Infrastructure changes to allow VRF's - SIGHUP signals the config reload - Conversion to Northbound interface - ISIS - BFD Support - Support for circuits with MTU > 8192 - PBRD - fwmark support as part of match criteria - autocompletion of PBRMAPS - Improved Nexthop Support - PIMD - PIM-BSM receive support - Improved debugging support - Store ECMP paths that are not currently legal for use - Disallow igmp query from a non-connected source - Many new cli improvements and changes - VRRPD - Add Support for RFC 3768 and RFC 5798 - Route-Maps - Add sequence numbers to access-lists - Add `match ip next-hop type blackhole` - Improved ability to notice dependency changes - SHARPD - `sharp watch [import|nexthop]` you can now specify a prefix instead of assuming a /32 - STATICD - Significantly Improved NHT - ZEBRA - Many dataplane improvements for routes, neighbor table and EVPN - NHT cli can now be specified per VRF and improved ability to control NHT data being shown - Removed duplicate processing of routes - Improved debugablility - RMAC and VxLan support for the FPM - LIB - RCU support - Nexthop Group Improvements - `log-filter WORD` added - Building - openssl support - libcap should be used as part of build or significant slowdowns will be experienced - Lua builds have been fixed - Improved Cross building * Mon Jun 17 2019 David Lamparter - 7.1 - gRPC northbound plugin - "table NNN" removed from zebra - more dataplane MT work - EVPN in non-default VRFs - RFC 8212 (default deny policy for eBGP) - RFC 8106 (IPv6 RA DNS options) * Wed May 8 2019 Martin Winter - 7.0.1 - bgp: - Don't send Updates with BGP Max-Prefix Overflow - Make sure `next-hop-self all` backward compatible with force - Fix as-path validation in "show bgp regexp" - Fix interface-based peers to override peergroups - Fix removing private AS numbers if local-as is used - Fix show bgp labeled_unicast - Add command to lookup prefixes in rpki table - Fix peer count in "show bgp ipv6 summary" - Add missing ipv6 only peer flag action - Fix address family output in "show bgp [ipv4|ipv6] neighbors" - Add missing checks for vpnv6 nexthops - Fix nexthop for ipv6 vpn case - rip: Fix removal of passive interfaces - ospf: - Fix json timer output - Fix milliseconds in json output - bfd: - Fix source port according RFC 5881, Sec 4 - Fix IPv6 link-local peer removal - Fix interface clean up when deleting interface - pim: Fix interface clean up when deleting interface - nhrp: Fix interface clean up when deleting interface - lib: - Workaround to get FRR building with libyang 0.x and 1.x - Fix in priv handling - Make priv elevation thread-safe - zebra: - Pseudowire event recovery - Fix race condition in label manager - Fix system routes selection and next-hop tracking - Set connected route metric based on devaddr metric - Display metric for connected routes - Add selected fib details to json output - Always use replace if installing new route - watchfrr: Silently ignore declare failures (for backward compatibility) - RPM packages: Switch to new init script * Thu Feb 28 2019 Martin Winter - 7.0 - Added libyang dependency: New work for northbound interface based on libyang - Fabricd: New Daemon based on https://datatracker.ietf.org/doc/draft-white-openfabric/ - various bug fixes and other enhancements * Sun Oct 7 2018 Martin Winter - 6.0 - Staticd: New daemon responsible for management of static routes - ISISd: Implement dst-src routing as per draft-ietf-isis-ipv6-dst-src-routing - BFDd: new daemon for BFD (Bidrectional Forwarding Detection). Responsible for notifying link changes to make routing protocols converge faster. - various bug fixes * Thu Jul 5 2018 Martin Winter - 5.0.1 - Support Automake 1.16.1 - BGPd: Support for flowspec ICMP, DSCP, packet length, fragment and tcp flags - BGPd: fix rpki validation for ipv6 - VRF: Workaround for kernel bug on Linux 4.14 and newer - Zebra: Fix interface based routes from zebra not marked up - Zebra: Fix large zebra memory usage when redistribute between protocols - Zebra: Allow route-maps to match on source instance - BGPd: Backport peer-attr overrides, peer-level enforce-first-as and filtered-routes fix - BGPd: fix for crash during display of filtered-routes - BGPd: Actually display labeled unicast routes received - Label Manager: Fix to work correctly behind a label manager proxy * Thu Jun 7 2018 Martin Winter - 5.0 - PIM: Add a Multicast Trace Command draft-ietf-idmr-traceroute-ipm-05 - IS-IS: Implement Three-Way Handshake as per RFC5303 - BGPD: Implement VPN-VRF route leaking per RFC4364. - BGPD: Implement VRF with NETNS backend - BGPD: Flowspec - PBRD: Add a new Policy Based Routing Daemon * Mon May 28 2018 Rafael Zalamena - Add BFDd support * Sun May 20 2018 Martin Winter - Fixed RPKI RPM build * Sun Mar 4 2018 Martin Winter - Add option to build with RPKI (default: disabled) * Tue Feb 20 2018 Martin Winter - Adapt to new documentation structure based on Sphinx * Fri Oct 20 2017 Martin Winter - Fix script location for watchfrr restart functions in daemon config - Fix postun script to restart frr during upgrade * Mon Jun 5 2017 Martin Winter - added NHRP and EIGRP daemon * Mon Apr 17 2017 Martin Winter - new subpackage frr-pythontools with python 2.7 restart script - remove PIMd from CentOS/RedHat 6 RPM packages (won't work - too old) - converted to single frr init script (not per daemon) based on debian init script - created systemd service file for systemd based systems (which uses init script) - Various other RPM package fixes for FRR 2.0 * Fri Jan 6 2017 Martin Winter - Renamed to frr for FRRouting fork of Quagga * Thu Feb 11 2016 Paul Jakma - remove with_ipv6 conditionals, always build v6 - Fix UTF-8 char in spec changelog - remove quagga.pam.stack, long deprecated. * Thu Oct 22 2015 Martin Winter - Cleanup configure: remove --enable-ipv6 (default now), --enable-nssa, --enable-netlink - Remove support for old fedora 4/5 - Fix for package nameing - Fix Weekdays of previous changelogs (bogus dates) - Add conditional logic to only build tex footnotes with supported texi2html - Added pimd to files section and fix double listing of /var/lib*/quagga - Numerous fixes to unify upstart/systemd startup into same spec file - Only allow use of watchfrr for non-systemd systems. no need with systemd * Fri Sep 4 2015 Paul Jakma - buildreq updates - add a default define for with_pimd * Mon Sep 12 2005 Paul Jakma - Steal some changes from Fedora spec file: - Add with_rtadv variable - Test for groups/users with getent before group/user adding - Readline need not be an explicit prerequisite - install-info delete should be postun, not preun * Wed Jan 12 2005 Andrew J. Schorr - on package upgrade, implement careful, phased restart logic - use gcc -rdynamic flag when linking for better backtraces * Wed Dec 22 2004 Andrew J. Schorr - daemonv6_list should contain only IPv6 daemons * Wed Dec 22 2004 Andrew J. Schorr - watchfrr added - on upgrade, all daemons should be condrestart'ed - on removal, all daemons should be stopped * Mon Nov 08 2004 Paul Jakma - Use makeinfo --html to generate quagga.html * Sun Nov 07 2004 Paul Jakma - Fix with_ipv6 set to 0 build * Sat Oct 23 2004 Paul Jakma - Update to 0.97.2 * Sat Oct 23 2004 Andrew J. Schorr - Make directories be owned by the packages concerned - Update logrotate scripts to use correct path to killall and use pid files * Fri Oct 08 2004 Paul Jakma - Update to 0.97.0 * Wed Sep 15 2004 Paul Jakma - build snmp support by default - build irdp support - build with shared libs - devel subpackage for archives and headers * Thu Jan 08 2004 Paul Jakma - updated sysconfig files to specify local dir - added ospf_dump.c crash quick fix patch - added ospfd persistent interface configuration patch * Tue Dec 30 2003 Paul Jakma - sync to CVS - integrate RH sysconfig patch to specify daemon options (RH) - default to have vty listen only to 127.1 (RH) - add user with fixed UID/GID (RH) - create user with shell /sbin/nologin rather than /bin/false (RH) - stop daemons on uninstall (RH) - delete info file on preun, not postun to avoid deletion on upgrade. (RH) - isisd added - cleanup tasks carried out for every daemon * Sun Nov 2 2003 Paul Jakma - Fix -devel package to include all files - Sync to 0.96.4 * Tue Aug 12 2003 Paul Jakma - Renamed to Quagga - Sync to Quagga release 0.96 * Thu Mar 20 2003 Paul Jakma - zebra privileges support * Tue Mar 18 2003 Paul Jakma - Fix mem leak in 'show thread cpu' - Ralph Keller's OSPF-API - Amir: Fix configure.ac for net-snmp * Sat Mar 1 2003 Paul Jakma - ospfd IOS prefix to interface matching for 'network' statement - temporary fix for PtP and IPv6 - sync to zebra.org CVS * Mon Jan 20 2003 Paul Jakma - update to latest cvs - Yon's "show thread cpu" patch - 17217 - walk up tree - 17218 - ospfd NSSA fixes - 16681 - ospfd nsm fixes - 16824 - ospfd OLSA fixes and new feature - 16823 - KAME and ifindex fixes - 16525 - spec file changes to allow redhat files to be in tree * Sat Dec 28 2002 Alexander Hoogerhuis - Added conditionals for building with(out) IPv6, vtysh, RIP, BGP - Fixed up some build requirements (patch) - Added conditional build requirements for vtysh / snmp - Added conditional to files for _bindir depending on vtysh * Mon Nov 11 2002 Paul Jakma - update to latest CVS - add Greg Troxel's md5 buffer copy/dup fix - add RIPv1 fix - add Frank's multicast flag fix * Wed Oct 09 2002 Paul Jakma - update to latest CVS - timestamped crypt_seqnum patch - oi->on_write_q fix * Mon Sep 30 2002 Paul Jakma - update to latest CVS - add vtysh 'write-config (integrated|daemon)' patch - always 'make rebuild' in vtysh/ to catch new commands * Fri Sep 13 2002 Paul Jakma - update to 0.93b * Wed Sep 11 2002 Paul Jakma - update to latest CVS - add "/sbin/ip route flush proto zebra" to zebra RH init on startup * Sat Aug 24 2002 Paul Jakma - update to current CVS - add OSPF point to multipoint patch - add OSPF bugfixes - add BGP hash optimisation patch * Fri Jun 14 2002 Paul Jakma - update to 0.93-pre1 / CVS - add link state detection support - add generic PtP and RFC3021 support - various bug fixes * Thu Aug 09 2001 Elliot Lee 0.91a-6 - Fix bug #51336 * Wed Aug 1 2001 Trond Eivind Glomsrød 0.91a-5 - Use generic initscript strings instead of initscript specific ( "Starting foo: " -> "Starting $prog:" ) * Fri Jul 27 2001 Elliot Lee 0.91a-4 - Bump the release when rebuilding into the dist. * Tue Feb 6 2001 Tim Powers - built for Powertools * Sun Feb 4 2001 Pekka Savola - Hacked up from PLD Linux 0.90-1, Mandrake 0.90-1mdk and one from zebra.org. - Update to 0.91a - Very heavy modifications to init.d/*, .spec, pam, i18n, logrotate, etc. - Should be quite Red Hat'isque now. frr-7.2.1/redhat/frr.spec.in0000644000000000000000000007645213610377563012553 00000000000000# configure options # # Some can be overridden on rpmbuild commandline with: # rpmbuild --define 'variable value' # (use any value, ie 1 for flag "with_XXXX" definitions) # # E.g. rpmbuild --define 'release_rev 02' may be useful if building # rpms again and again on the same day, so the newer rpms can be installed. # bumping the number each time. #################### FRRouting (FRR) configure options ##################### # with-feature options %{!?with_babeld: %global with_babeld 1 } %{!?with_bfdd: %global with_bfdd 1 } %{!?with_bgp_vnc: %global with_bgp_vnc 0 } %{!?with_cumulus: %global with_cumulus 0 } %{!?with_eigrpd: %global with_eigrpd 1 } %{!?with_fpm: %global with_fpm 1 } %{!?with_ldpd: %global with_ldpd 1 } %{!?with_multipath: %global with_multipath 256 } %{!?with_nhrpd: %global with_nhrpd 1 } %{!?with_ospfapi: %global with_ospfapi 1 } %{!?with_ospfclient: %global with_ospfclient 1 } %{!?with_pam: %global with_pam 0 } %{!?with_pbrd: %global with_pbrd 1 } %{!?with_pimd: %global with_pimd 1 } %{!?with_vrrpd: %global with_vrrpd 1 } %{!?with_rpki: %global with_rpki 0 } %{!?with_rtadv: %global with_rtadv 1 } %{!?with_watchfrr: %global with_watchfrr 1 } # user and group %{!?frr_user: %global frr_user frr } %{!?vty_group: %global vty_group frrvty } # path defines %define configdir %{_sysconfdir}/%{name} %define _sbindir /usr/lib/frr %define zeb_src %{_builddir}/%{name}-%{frrversion} %define zeb_rh_src %{zeb_src}/redhat %define zeb_docs %{zeb_src}/doc %define frr_tools %{zeb_src}/tools # defines for configure %define rundir %{_localstatedir}/run/%{name} ############################################################################ #### Version String tweak # Remove invalid characters form version string and replace with _ %{expand: %%global rpmversion %(echo '@VERSION@' | tr [:blank:]- _ )} %define frrversion @VERSION@ #### Check for systemd or init.d (upstart) # Check for init.d (upstart) as used in CentOS 6 or systemd (ie CentOS 7) %if 0%{?fedora} || 0%{?rhel} >= 7 || 0%{?suse_version} >= 1210 %global initsystem systemd %else %if 0%{?rhel} && 0%{?rhel} < 7 %global initsystem upstart %else %{expand: %%global initsystem %(if [[ `/sbin/init --version 2> /dev/null` =~ upstart ]]; then echo upstart; elif [[ `readlink -f /sbin/init` = /usr/lib/systemd/systemd ]]; then echo systemd; elif [[ `systemctl` =~ -\.mount ]]; then echo systemd; fi)} %endif %endif # If init system is systemd, then always enable watchfrr %if "%{initsystem}" == "systemd" %global with_watchfrr 1 %endif #### Check for RedHat 6.x or CentOS 6.x - they are too old to support PIM. #### Always disable it on these old systems unconditionally # # if CentOS / RedHat and version < 7, then disable PIMd (too old, won't work) %if 0%{?rhel} && 0%{?rhel} < 7 %global with_pimd 0 %endif # misc internal defines %{!?frr_uid: %global frr_uid 92 } %{!?frr_gid: %global frr_gid 92 } %{!?vty_gid: %global vty_gid 85 } %define daemon_list zebra ripd ospfd bgpd isisd ripngd ospf6d pbrd staticd bfdd fabricd %if %{with_ldpd} %define daemon_ldpd ldpd %else %define daemon_ldpd "" %endif %if %{with_pimd} %define daemon_pimd pimd %else %define daemon_pimd "" %endif %if %{with_pbrd} %define daemon_pbrd pbrd %else %define daemon_pbrd "" %endif %if %{with_nhrpd} %define daemon_nhrpd nhrpd %else %define daemon_nhrpd "" %endif %if %{with_eigrpd} %define daemon_eigrpd eigrpd %else %define daemon_eigrpd "" %endif %if %{with_babeld} %define daemon_babeld babeld %else %define daemon_babeld "" %endif %if %{with_vrrpd} %define daemon_vrrpd vrrpd %else %define daemon_vrrpd "" %endif %if %{with_watchfrr} %define daemon_watchfrr watchfrr %else %define daemon_watchfrr "" %endif %if %{with_bfdd} %define daemon_bfdd bfdd %else %define daemon_bfdd "" %endif %define all_daemons %{daemon_list} %{daemon_ldpd} %{daemon_pimd} %{daemon_nhrpd} %{daemon_eigrpd} %{daemon_babeld} %{daemon_watchfrr} %{daemon_pbrd} %{daemon_bfdd} %{daemon_vrrpd} #release sub-revision (the two digits after the CONFDATE) %{!?release_rev: %global release_rev 01 } Summary: Routing daemon Name: frr Version: %{rpmversion} Release: %{release_rev}%{?dist} License: GPLv2+ Group: System Environment/Daemons Source0: https://github.com/FRRouting/frr/archive/%{name}-%{frrversion}.tar.gz URL: https://www.frrouting.org Requires(pre): shadow-utils Requires(preun): info Requires(post): info BuildRequires: bison >= 2.7 BuildRequires: c-ares-devel BuildRequires: flex BuildRequires: gcc BuildRequires: json-c-devel BuildRequires: libcap-devel BuildRequires: make BuildRequires: ncurses-devel BuildRequires: readline-devel BuildRequires: texinfo BuildRequires: libyang-devel >= 0.16.74 %if 0%{?rhel} && 0%{?rhel} < 7 #python27-devel is available from ius community repo for RedHat/CentOS 6 BuildRequires: python27-devel BuildRequires: python27-sphinx %else BuildRequires: python-devel >= 2.7 BuildRequires: python-sphinx %endif Requires: initscripts %if %{with_pam} BuildRequires: pam-devel %endif %if %{with_rpki} BuildRequires: librtr-devel >= 0.5 %endif %if "%{initsystem}" == "systemd" BuildRequires: systemd BuildRequires: systemd-devel Requires(post): systemd Requires(preun): systemd Requires(postun): systemd %else Requires(post): chkconfig Requires(preun): chkconfig # Initscripts > 5.60 is required for IPv6 support Requires(pre): initscripts >= 5.60 %endif Provides: routingdaemon = %{version}-%{release} Obsoletes: gated mrt zebra frr-sysvinit Conflicts: bird %description FRRouting is a free software that manages TCP/IP based routing protocol. It takes multi-server and multi-thread approach to resolve the current complexity of the Internet. FRRouting supports BGP4, OSPFv2, OSPFv3, ISIS, RIP, RIPng, PIM, LDP NHRP, Babel, PBR, EIGRP and BFD. FRRouting is a fork of Quagga. %package contrib Summary: contrib tools for frr Group: System Environment/Daemons %description contrib Contributed/3rd party tools which may be of use with frr. %package pythontools Summary: python tools for frr BuildRequires: python Requires: python-ipaddress Group: System Environment/Daemons %description pythontools Contributed python 2.7 tools which may be of use with frr. %package devel Summary: Header and object files for frr development Group: System Environment/Daemons Requires: %{name} = %{version}-%{release} %description devel The frr-devel package contains the header and object files neccessary for developing OSPF-API and frr applications. %prep %setup -q -n frr-%{frrversion} %build # For standard gcc verbosity, uncomment these lines: #CFLAGS="%{optflags} -Wall -Wsign-compare -Wpointer-arith" #CFLAGS="${CFLAGS} -Wbad-function-cast -Wwrite-strings" # For ultra gcc verbosity, uncomment these lines also: #CFLAGS="${CFLAGS} -W -Wcast-qual -Wstrict-prototypes" #CFLAGS="${CFLAGS} -Wmissing-declarations -Wmissing-noreturn" #CFLAGS="${CFLAGS} -Wmissing-format-attribute -Wunreachable-code" #CFLAGS="${CFLAGS} -Wpacked -Wpadded" %configure \ --sbindir=%{_sbindir} \ --sysconfdir=%{configdir} \ --localstatedir=%{rundir} \ --disable-static \ --disable-werror \ --enable-irdp \ %if %{with_multipath} --enable-multipath=%{with_multipath} \ %endif --enable-vtysh \ %if %{with_ospfclient} --enable-ospfclient \ %else --disable-ospfclient\ %endif %if %{with_ospfapi} --enable-ospfapi \ %else --disable-ospfapi \ %endif %if %{with_rtadv} --enable-rtadv \ %else --disable-rtadv \ %endif %if %{with_ldpd} --enable-ldpd \ %else --disable-ldpd \ %endif %if %{with_pimd} --enable-pimd \ %else --disable-pimd \ %endif %if %{with_pbrd} --enable-pbrd \ %else --disable-pbrd \ %endif %if %{with_nhrpd} --enable-nhrpd \ %else --disable-nhrpd \ %endif %if %{with_eigrpd} --enable-eigrpd \ %else --disable-eigrpd \ %endif %if %{with_babeld} --enable-babeld \ %else --disable-babeld \ %endif %if %{with_vrrpd} --enable-vrrpd \ %else --disable-vrrpd \ %endif %if %{with_pam} --with-libpam \ %endif %if 0%{?frr_user:1} --enable-user=%{frr_user} \ --enable-group=%{frr_user} \ %endif %if 0%{?vty_group:1} --enable-vty-group=%{vty_group} \ %endif %if %{with_fpm} --enable-fpm \ %else --disable-fpm \ %endif %if %{with_watchfrr} --enable-watchfrr \ %else --disable-watchfrr \ %endif %if %{with_cumulus} --enable-cumulus \ %endif %if %{with_bgp_vnc} --enable-bgp-vnc \ %else --disable-bgp-vnc \ %endif --enable-isisd \ %if "%{initsystem}" == "systemd" --enable-systemd \ %endif %if %{with_rpki} --enable-rpki \ %else --disable-rpki \ %endif %if %{with_bfdd} --enable-bfdd \ %else --disable-bfdd \ %endif # end make %{?_smp_mflags} MAKEINFO="makeinfo --no-split" pushd doc make info popd %install mkdir -p %{buildroot}%{_sysconfdir}/{frr,sysconfig,logrotate.d,pam.d,default} \ %{buildroot}%{_localstatedir}/log/frr %{buildroot}%{_infodir} make DESTDIR=%{buildroot} INSTALL="install -p" CP="cp -p" install # Remove this file, as it is uninstalled and causes errors when building on RH9 rm -rf %{buildroot}/usr/share/info/dir # Remove debian init script if it was installed rm -f %{buildroot}%{_sbindir}/frr # kill bogus libtool files rm -vf %{buildroot}%{_libdir}/frr/modules/*.la rm -vf %{buildroot}%{_libdir}/*.la rm -vf %{buildroot}%{_libdir}/frr/libyang_plugins/*.la # install /etc sources %if "%{initsystem}" == "systemd" mkdir -p %{buildroot}%{_unitdir} install -m644 %{zeb_src}/tools/frr.service %{buildroot}%{_unitdir}/frr.service %else mkdir -p %{buildroot}%{_initddir} ln -s %{_sbindir}/frrinit.sh %{buildroot}%{_initddir}/frr %endif install %{zeb_src}/tools/etc/frr/daemons %{buildroot}%{_sysconfdir}/frr # add rpki module to daemon %if %{with_rpki} sed -i -e 's/^\(bgpd_options=\)\(.*\)\(".*\)/\1\2 -M rpki\3/' %{buildroot}%{_sysconfdir}/frr/daemons %endif install -m644 %{zeb_rh_src}/frr.pam %{buildroot}%{_sysconfdir}/pam.d/frr install -m644 %{zeb_rh_src}/frr.logrotate %{buildroot}%{_sysconfdir}/logrotate.d/frr install -d -m750 %{buildroot}%{rundir} %pre # add vty_group %if 0%{?vty_group:1} getent group %{vty_group} >/dev/null || groupadd -r -g %{vty_gid} %{vty_group} %endif # add frr user and group %if 0%{?frr_user:1} # Ensure that frr_gid gets correctly allocated getent group %{frr_user} >/dev/null || groupadd -g %{frr_gid} %{frr_user} getent passwd %{frr_user} >/dev/null || \ useradd -r -u %{frr_uid} -g %{frr_user} \ -s /sbin/nologin -c "FRRouting suite" \ -d %{rundir} %{frr_user} %if 0%{?vty_group:1} usermod -a -G %{vty_group} %{frr_user} %endif %endif exit 0 %post # zebra_spec_add_service # e.g. zebra_spec_add_service zebrasrv 2600/tcp "zebra service" zebra_spec_add_service () { # Add port /etc/services entry if it isn't already there if [ -f %{_sysconfdir}/services ] && \ ! %__sed -e 's/#.*$//' %{_sysconfdir}/services | %__grep -wq $1 ; then echo "$1 $2 # $3" >> %{_sysconfdir}/services fi } zebra_spec_add_service zebrasrv 2600/tcp "zebra service" zebra_spec_add_service zebra 2601/tcp "zebra vty" zebra_spec_add_service staticd 2616/tcp "staticd vty" zebra_spec_add_service ripd 2602/tcp "RIPd vty" zebra_spec_add_service ripngd 2603/tcp "RIPngd vty" zebra_spec_add_service ospfd 2604/tcp "OSPFd vty" zebra_spec_add_service bgpd 2605/tcp "BGPd vty" zebra_spec_add_service ospf6d 2606/tcp "OSPF6d vty" zebra_spec_add_service isisd 2608/tcp "ISISd vty" %if %{with_ospfapi} zebra_spec_add_service ospfapi 2607/tcp "OSPF-API" %endif %if %{with_babeld} zebra_spec_add_service babeld 2609/tcp "BABELd vty" %endif %if %{with_nhrpd} zebra_spec_add_service nhrpd 2610/tcp "NHRPd vty" %endif %if %{with_pimd} zebra_spec_add_service pimd 2611/tcp "PIMd vty" %endif %if %{with_pbrd} zebra_spec_add_service pbrd 2615/tcp "PBRd vty" %endif %if %{with_ldpd} zebra_spec_add_service ldpd 2612/tcp "LDPd vty" %endif %if %{with_eigrpd} zebra_spec_add_service eigrpd 2613/tcp "EIGRPd vty" %endif %if %{with_bfdd} zebra_spec_add_service bfdd 2617/tcp "BFDd vty" %endif zebra_spec_add_service fabricd 2618/tcp "Fabricd vty" %if %{with_vrrpd} zebra_spec_add_service vrrpd 2619/tcp "VRRPd vty" %endif %if "%{initsystem}" == "systemd" for daemon in %all_daemons ; do %systemd_post frr.service done %else /sbin/chkconfig --add frr %endif # Fix bad path in previous config files # Config files won't get replaced by default, so we do this ugly hack to fix it %__sed -i 's|watchfrr_options=|#watchfrr_options=|g' %{configdir}/daemons 2> /dev/null || true # With systemd, watchfrr is mandatory. Fix config to make sure it's enabled if # we install or upgrade to a frr built with systemd %if "%{initsystem}" == "systemd" %__sed -i 's|watchfrr_enable=no|watchfrr_enable=yes|g' %{configdir}/daemons 2> /dev/null || true %endif /sbin/install-info %{_infodir}/frr.info.gz %{_infodir}/dir # Create dummy files if they don't exist so basic functions can be used. if [ ! -e %{configdir}/zebra.conf ]; then echo "hostname `hostname`" > %{configdir}/zebra.conf %if 0%{?frr_user:1} chown %{frr_user}:%{frr_user} %{configdir}/zebra.conf* %endif chmod 640 %{configdir}/zebra.conf* fi for daemon in %{all_daemons} ; do if [ x"${daemon}" != x"" ] ; then if [ ! -e %{configdir}/${daemon}.conf ]; then touch %{configdir}/${daemon}.conf %if 0%{?frr_user:1} chown %{frr_user}:%{frr_user} %{configdir}/${daemon}.conf* %endif fi fi done %if 0%{?frr_user:1} chown %{frr_user}:%{frr_user} %{configdir}/daemons %endif %if %{with_watchfrr} # No config for watchfrr - this is part of /etc/sysconfig/frr rm -f %{configdir}/watchfrr.* %endif if [ ! -e %{configdir}/vtysh.conf ]; then touch %{configdir}/vtysh.conf chmod 640 %{configdir}/vtysh.conf %if 0%{?frr_user:1} %if 0%{?vty_group:1} chown %{frr_user}:%{vty_group} %{configdir}/vtysh.conf* %endif %endif fi %postun if [ "$1" -ge 1 ]; then # # Upgrade from older version # %if "%{initsystem}" == "systemd" ## ## Systemd Version ## %systemd_postun_with_restart frr.service %else ## ## init.d Version ## service frr restart >/dev/null 2>&1 %endif : fi %preun %if "%{initsystem}" == "systemd" ## ## Systemd Version ## if [ $1 -eq 0 ] ; then %systemd_preun frr.service fi %else ## ## init.d Version ## if [ $1 -eq 0 ] ; then service frr stop >/dev/null 2>&1 /sbin/chkconfig --del frr fi %endif /sbin/install-info --delete %{_infodir}/frr.info.gz %{_infodir}/dir %files %doc */*.sample* COPYING %doc doc/mpls %doc README.md /usr/share/yang/*.yang %if 0%{?frr_user:1} %dir %attr(751,%{frr_user},%{frr_user}) %{configdir} %dir %attr(750,%{frr_user},%{frr_user}) %{_localstatedir}/log/frr %dir %attr(751,%{frr_user},%{frr_user}) %{rundir} %else %dir %attr(750,root,root) %{configdir} %dir %attr(750,root,root) %{_localstatedir}/log/frr %dir %attr(750,root,root) %{rundir} %endif %if 0%{?vty_group:1} %attr(750,%{frr_user},%{vty_group}) %{configdir}/vtysh.conf.sample %endif %{_infodir}/frr.info.gz %{_mandir}/man*/* %{_sbindir}/zebra %{_sbindir}/staticd %{_sbindir}/ospfd %{_sbindir}/ripd %{_sbindir}/bgpd %exclude %{_sbindir}/ssd %if %{with_watchfrr} %{_sbindir}/watchfrr %endif %{_sbindir}/ripngd %{_sbindir}/ospf6d %if %{with_pimd} %{_sbindir}/pimd %endif %if %{with_pbrd} %{_sbindir}/pbrd %endif %if %{with_vrrpd} %{_sbindir}/vrrpd %endif %{_sbindir}/isisd %{_sbindir}/fabricd %if %{with_ldpd} %{_sbindir}/ldpd %endif %if %{with_eigrpd} %{_sbindir}/eigrpd %endif %if %{with_nhrpd} %{_sbindir}/nhrpd %endif %if %{with_babeld} %{_sbindir}/babeld %endif %if %{with_bfdd} %{_sbindir}/bfdd %endif %{_libdir}/lib*.so.0 %{_libdir}/lib*.so.0.* %if %{with_fpm} %{_libdir}/frr/modules/zebra_fpm.so %endif %if %{with_rpki} %{_libdir}/frr/modules/bgpd_rpki.so %endif %{_libdir}/frr/modules/zebra_irdp.so %{_libdir}/frr/modules/bgpd_bmp.so %{_bindir}/* %config(noreplace) %{configdir}/[!v]*.conf* %config(noreplace) %attr(750,%{frr_user},%{frr_user}) %{configdir}/daemons %if "%{initsystem}" == "systemd" %{_unitdir}/frr.service %else %{_initddir}/frr %endif %config(noreplace) %{_sysconfdir}/pam.d/frr %config(noreplace) %{_sysconfdir}/logrotate.d/frr %{_sbindir}/frr-reload %{_sbindir}/frrcommon.sh %{_sbindir}/frrinit.sh %{_sbindir}/watchfrr.sh %files contrib %doc tools %files pythontools %{_sbindir}/frr-reload.py %{_sbindir}/frr-reload.pyc %{_sbindir}/frr-reload.pyo %files devel %{_libdir}/lib*.so %dir %{_includedir}/%{name} %{_includedir}/%{name}/*.h %dir %{_includedir}/%{name}/ospfd %{_includedir}/%{name}/ospfd/*.h %if %{with_ospfapi} %dir %{_includedir}/%{name}/ospfapi %{_includedir}/%{name}/ospfapi/*.h %endif %if %{with_eigrpd} %dir %{_includedir}/%{name}/eigrpd %{_includedir}/%{name}/eigrpd/*.h %endif %changelog * Fri Jan 17 2020 Martin Winter - %{version} - BGPd - Fix Addpath issue - Do not apply eBGP policy for iBGP peers - Show `ip` and `fqdn` in json output for `show [ip] bgp json` - Fix large route-distinguisher's format - Fix `no bgp listen range ...` configuration command - Autocomplete neighbor for clear bgp - Reflect the distance in RIB when it is changed for an arbitrary afi/safi - Notify "Peer De-configured" after entering 'no neighbor cmd - Fix per afi/safi addpath peer counting - Rework BGP dampening to be per AFI/SAFI - Do not send next-hop as :: in MP_REACH_NLRI if no link-local exists - Override peer's TTL only if peer-group is configured with TTL - Remove error message for unkown afi/safi combination - Keep the session down if maximum-prefix is reached - OSPFd - Fix BFD down not tearing down OSPF adjacency for point-to-point net - BFDd - Fix multiple VRF handling - VRF security improvement - PIMd - Fix rp crash - NHRPd - Make sure `no ip nhrp map ` works as expected - LDPd - Add missing sanity check in the parsing of label messages - Zebra - Use correct state when installing evpn macs - Capture dplane plugin flags - lib - Fix interface config when vrf changes - Fix Interface Infinite Loop Walk (for special interfaces such as bond) - snapcraft - fix missing vrrpd daemon - Others - Rename man pages (to avoid conflicts with other packages) - Various other fixes for code cleanup and memory leaks * Tue Oct 15 2019 Martin Winter - 7.2 - ALL Daemons - -N to allow for config file locating when running FRR inside of a namespace - Impoved Testing across all daemons - BFD - VRF Support - Conversion to Northbound interface - BGP - Aggregate-address add route-map support - BMP Support - Improved JSON output for many commands - `show bgp afi safi summary failed` command - `clear bop *` clears all peers - Show FQDN for `show bgp ipv4 uni` commands - Display BestPath selection reason as part of show commands - EIGRP - Infrastructure changes to allow VRF's - SIGHUP signals the config reload - Conversion to Northbound interface - ISIS - BFD Support - Support for circuits with MTU > 8192 - PBRD - fwmark support as part of match criteria - autocompletion of PBRMAPS - Improved Nexthop Support - PIMD - PIM-BSM receive support - Improved debugging support - Store ECMP paths that are not currently legal for use - Disallow igmp query from a non-connected source - Many new cli improvements and changes - VRRPD - Add Support for RFC 3768 and RFC 5798 - Route-Maps - Add sequence numbers to access-lists - Add `match ip next-hop type blackhole` - Improved ability to notice dependency changes - SHARPD - `sharp watch [import|nexthop]` you can now specify a prefix instead of assuming a /32 - STATICD - Significantly Improved NHT - ZEBRA - Many dataplane improvements for routes, neighbor table and EVPN - NHT cli can now be specified per VRF and improved ability to control NHT data being shown - Removed duplicate processing of routes - Improved debugablility - RMAC and VxLan support for the FPM - LIB - RCU support - Nexthop Group Improvements - `log-filter WORD` added - Building - openssl support - libcap should be used as part of build or significant slowdowns will be experienced - Lua builds have been fixed - Improved Cross building * Mon Jun 17 2019 David Lamparter - 7.1 - gRPC northbound plugin - "table NNN" removed from zebra - more dataplane MT work - EVPN in non-default VRFs - RFC 8212 (default deny policy for eBGP) - RFC 8106 (IPv6 RA DNS options) * Wed May 8 2019 Martin Winter - 7.0.1 - bgp: - Don't send Updates with BGP Max-Prefix Overflow - Make sure `next-hop-self all` backward compatible with force - Fix as-path validation in "show bgp regexp" - Fix interface-based peers to override peergroups - Fix removing private AS numbers if local-as is used - Fix show bgp labeled_unicast - Add command to lookup prefixes in rpki table - Fix peer count in "show bgp ipv6 summary" - Add missing ipv6 only peer flag action - Fix address family output in "show bgp [ipv4|ipv6] neighbors" - Add missing checks for vpnv6 nexthops - Fix nexthop for ipv6 vpn case - rip: Fix removal of passive interfaces - ospf: - Fix json timer output - Fix milliseconds in json output - bfd: - Fix source port according RFC 5881, Sec 4 - Fix IPv6 link-local peer removal - Fix interface clean up when deleting interface - pim: Fix interface clean up when deleting interface - nhrp: Fix interface clean up when deleting interface - lib: - Workaround to get FRR building with libyang 0.x and 1.x - Fix in priv handling - Make priv elevation thread-safe - zebra: - Pseudowire event recovery - Fix race condition in label manager - Fix system routes selection and next-hop tracking - Set connected route metric based on devaddr metric - Display metric for connected routes - Add selected fib details to json output - Always use replace if installing new route - watchfrr: Silently ignore declare failures (for backward compatibility) - RPM packages: Switch to new init script * Thu Feb 28 2019 Martin Winter - 7.0 - Added libyang dependency: New work for northbound interface based on libyang - Fabricd: New Daemon based on https://datatracker.ietf.org/doc/draft-white-openfabric/ - various bug fixes and other enhancements * Sun Oct 7 2018 Martin Winter - 6.0 - Staticd: New daemon responsible for management of static routes - ISISd: Implement dst-src routing as per draft-ietf-isis-ipv6-dst-src-routing - BFDd: new daemon for BFD (Bidrectional Forwarding Detection). Responsible for notifying link changes to make routing protocols converge faster. - various bug fixes * Thu Jul 5 2018 Martin Winter - 5.0.1 - Support Automake 1.16.1 - BGPd: Support for flowspec ICMP, DSCP, packet length, fragment and tcp flags - BGPd: fix rpki validation for ipv6 - VRF: Workaround for kernel bug on Linux 4.14 and newer - Zebra: Fix interface based routes from zebra not marked up - Zebra: Fix large zebra memory usage when redistribute between protocols - Zebra: Allow route-maps to match on source instance - BGPd: Backport peer-attr overrides, peer-level enforce-first-as and filtered-routes fix - BGPd: fix for crash during display of filtered-routes - BGPd: Actually display labeled unicast routes received - Label Manager: Fix to work correctly behind a label manager proxy * Thu Jun 7 2018 Martin Winter - 5.0 - PIM: Add a Multicast Trace Command draft-ietf-idmr-traceroute-ipm-05 - IS-IS: Implement Three-Way Handshake as per RFC5303 - BGPD: Implement VPN-VRF route leaking per RFC4364. - BGPD: Implement VRF with NETNS backend - BGPD: Flowspec - PBRD: Add a new Policy Based Routing Daemon * Mon May 28 2018 Rafael Zalamena - Add BFDd support * Sun May 20 2018 Martin Winter - Fixed RPKI RPM build * Sun Mar 4 2018 Martin Winter - Add option to build with RPKI (default: disabled) * Tue Feb 20 2018 Martin Winter - Adapt to new documentation structure based on Sphinx * Fri Oct 20 2017 Martin Winter - Fix script location for watchfrr restart functions in daemon config - Fix postun script to restart frr during upgrade * Mon Jun 5 2017 Martin Winter - added NHRP and EIGRP daemon * Mon Apr 17 2017 Martin Winter - new subpackage frr-pythontools with python 2.7 restart script - remove PIMd from CentOS/RedHat 6 RPM packages (won't work - too old) - converted to single frr init script (not per daemon) based on debian init script - created systemd service file for systemd based systems (which uses init script) - Various other RPM package fixes for FRR 2.0 * Fri Jan 6 2017 Martin Winter - Renamed to frr for FRRouting fork of Quagga * Thu Feb 11 2016 Paul Jakma - remove with_ipv6 conditionals, always build v6 - Fix UTF-8 char in spec changelog - remove quagga.pam.stack, long deprecated. * Thu Oct 22 2015 Martin Winter - Cleanup configure: remove --enable-ipv6 (default now), --enable-nssa, --enable-netlink - Remove support for old fedora 4/5 - Fix for package nameing - Fix Weekdays of previous changelogs (bogus dates) - Add conditional logic to only build tex footnotes with supported texi2html - Added pimd to files section and fix double listing of /var/lib*/quagga - Numerous fixes to unify upstart/systemd startup into same spec file - Only allow use of watchfrr for non-systemd systems. no need with systemd * Fri Sep 4 2015 Paul Jakma - buildreq updates - add a default define for with_pimd * Mon Sep 12 2005 Paul Jakma - Steal some changes from Fedora spec file: - Add with_rtadv variable - Test for groups/users with getent before group/user adding - Readline need not be an explicit prerequisite - install-info delete should be postun, not preun * Wed Jan 12 2005 Andrew J. Schorr - on package upgrade, implement careful, phased restart logic - use gcc -rdynamic flag when linking for better backtraces * Wed Dec 22 2004 Andrew J. Schorr - daemonv6_list should contain only IPv6 daemons * Wed Dec 22 2004 Andrew J. Schorr - watchfrr added - on upgrade, all daemons should be condrestart'ed - on removal, all daemons should be stopped * Mon Nov 08 2004 Paul Jakma - Use makeinfo --html to generate quagga.html * Sun Nov 07 2004 Paul Jakma - Fix with_ipv6 set to 0 build * Sat Oct 23 2004 Paul Jakma - Update to 0.97.2 * Sat Oct 23 2004 Andrew J. Schorr - Make directories be owned by the packages concerned - Update logrotate scripts to use correct path to killall and use pid files * Fri Oct 08 2004 Paul Jakma - Update to 0.97.0 * Wed Sep 15 2004 Paul Jakma - build snmp support by default - build irdp support - build with shared libs - devel subpackage for archives and headers * Thu Jan 08 2004 Paul Jakma - updated sysconfig files to specify local dir - added ospf_dump.c crash quick fix patch - added ospfd persistent interface configuration patch * Tue Dec 30 2003 Paul Jakma - sync to CVS - integrate RH sysconfig patch to specify daemon options (RH) - default to have vty listen only to 127.1 (RH) - add user with fixed UID/GID (RH) - create user with shell /sbin/nologin rather than /bin/false (RH) - stop daemons on uninstall (RH) - delete info file on preun, not postun to avoid deletion on upgrade. (RH) - isisd added - cleanup tasks carried out for every daemon * Sun Nov 2 2003 Paul Jakma - Fix -devel package to include all files - Sync to 0.96.4 * Tue Aug 12 2003 Paul Jakma - Renamed to Quagga - Sync to Quagga release 0.96 * Thu Mar 20 2003 Paul Jakma - zebra privileges support * Tue Mar 18 2003 Paul Jakma - Fix mem leak in 'show thread cpu' - Ralph Keller's OSPF-API - Amir: Fix configure.ac for net-snmp * Sat Mar 1 2003 Paul Jakma - ospfd IOS prefix to interface matching for 'network' statement - temporary fix for PtP and IPv6 - sync to zebra.org CVS * Mon Jan 20 2003 Paul Jakma - update to latest cvs - Yon's "show thread cpu" patch - 17217 - walk up tree - 17218 - ospfd NSSA fixes - 16681 - ospfd nsm fixes - 16824 - ospfd OLSA fixes and new feature - 16823 - KAME and ifindex fixes - 16525 - spec file changes to allow redhat files to be in tree * Sat Dec 28 2002 Alexander Hoogerhuis - Added conditionals for building with(out) IPv6, vtysh, RIP, BGP - Fixed up some build requirements (patch) - Added conditional build requirements for vtysh / snmp - Added conditional to files for _bindir depending on vtysh * Mon Nov 11 2002 Paul Jakma - update to latest CVS - add Greg Troxel's md5 buffer copy/dup fix - add RIPv1 fix - add Frank's multicast flag fix * Wed Oct 09 2002 Paul Jakma - update to latest CVS - timestamped crypt_seqnum patch - oi->on_write_q fix * Mon Sep 30 2002 Paul Jakma - update to latest CVS - add vtysh 'write-config (integrated|daemon)' patch - always 'make rebuild' in vtysh/ to catch new commands * Fri Sep 13 2002 Paul Jakma - update to 0.93b * Wed Sep 11 2002 Paul Jakma - update to latest CVS - add "/sbin/ip route flush proto zebra" to zebra RH init on startup * Sat Aug 24 2002 Paul Jakma - update to current CVS - add OSPF point to multipoint patch - add OSPF bugfixes - add BGP hash optimisation patch * Fri Jun 14 2002 Paul Jakma - update to 0.93-pre1 / CVS - add link state detection support - add generic PtP and RFC3021 support - various bug fixes * Thu Aug 09 2001 Elliot Lee 0.91a-6 - Fix bug #51336 * Wed Aug 1 2001 Trond Eivind Glomsrød 0.91a-5 - Use generic initscript strings instead of initscript specific ( "Starting foo: " -> "Starting $prog:" ) * Fri Jul 27 2001 Elliot Lee 0.91a-4 - Bump the release when rebuilding into the dist. * Tue Feb 6 2001 Tim Powers - built for Powertools * Sun Feb 4 2001 Pekka Savola - Hacked up from PLD Linux 0.90-1, Mandrake 0.90-1mdk and one from zebra.org. - Update to 0.91a - Very heavy modifications to init.d/*, .spec, pam, i18n, logrotate, etc. - Should be quite Red Hat'isque now. frr-7.2.1/ripd/0000755000000000000000000000000013610377563010232 500000000000000frr-7.2.1/ripd/Makefile0000644000000000000000000000021713610377563011612 00000000000000all: ALWAYS @$(MAKE) -s -C .. ripd/ripd %: ALWAYS @$(MAKE) -s -C .. ripd/$@ Makefile: #nothing ALWAYS: .PHONY: ALWAYS makefiles .SUFFIXES: frr-7.2.1/ripd/rip_cli.c0000644000000000000000000007024413610377563011746 00000000000000/* * Copyright (C) 1997, 1998, 1999 Kunihiro Ishiguro * Copyright (C) 2018 NetDEF, Inc. * Renato Westphal * * This program is free software; you can redistribute it and/or modify it * under the terms of the 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; see the file COPYING; if not, write to the Free Software * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA */ #include #include "if.h" #include "vrf.h" #include "log.h" #include "prefix.h" #include "command.h" #include "northbound_cli.h" #include "libfrr.h" #include "ripd/ripd.h" #include "ripd/rip_cli.h" #ifndef VTYSH_EXTRACT_PL #include "ripd/rip_cli_clippy.c" #endif /* * XPath: /frr-ripd:ripd/instance */ DEFPY_NOSH (router_rip, router_rip_cmd, "router rip [vrf NAME]", "Enable a routing process\n" "Routing Information Protocol (RIP)\n" VRF_CMD_HELP_STR) { char xpath[XPATH_MAXLEN]; int ret; /* Build RIP instance XPath. */ if (!vrf) vrf = VRF_DEFAULT_NAME; snprintf(xpath, sizeof(xpath), "/frr-ripd:ripd/instance[vrf='%s']", vrf); nb_cli_enqueue_change(vty, xpath, NB_OP_CREATE, NULL); ret = nb_cli_apply_changes(vty, NULL); if (ret == CMD_SUCCESS) VTY_PUSH_XPATH(RIP_NODE, xpath); return ret; } DEFPY (no_router_rip, no_router_rip_cmd, "no router rip [vrf NAME]", NO_STR "Enable a routing process\n" "Routing Information Protocol (RIP)\n" VRF_CMD_HELP_STR) { char xpath[XPATH_MAXLEN]; /* Build RIP instance XPath. */ if (!vrf) vrf = VRF_DEFAULT_NAME; snprintf(xpath, sizeof(xpath), "/frr-ripd:ripd/instance[vrf='%s']", vrf); nb_cli_enqueue_change(vty, xpath, NB_OP_DESTROY, NULL); return nb_cli_apply_changes(vty, NULL); } void cli_show_router_rip(struct vty *vty, struct lyd_node *dnode, bool show_defaults) { const char *vrf_name; vrf_name = yang_dnode_get_string(dnode, "./vrf"); vty_out(vty, "!\n"); vty_out(vty, "router rip"); if (!strmatch(vrf_name, VRF_DEFAULT_NAME)) vty_out(vty, " vrf %s", vrf_name); vty_out(vty, "\n"); } /* * XPath: /frr-ripd:ripd/instance/allow-ecmp */ DEFPY (rip_allow_ecmp, rip_allow_ecmp_cmd, "[no] allow-ecmp", NO_STR "Allow Equal Cost MultiPath\n") { nb_cli_enqueue_change(vty, "./allow-ecmp", NB_OP_MODIFY, no ? "false" : "true"); return nb_cli_apply_changes(vty, NULL); } void cli_show_rip_allow_ecmp(struct vty *vty, struct lyd_node *dnode, bool show_defaults) { if (!yang_dnode_get_bool(dnode, NULL)) vty_out(vty, " no"); vty_out(vty, " allow-ecmp\n"); } /* * XPath: /frr-ripd:ripd/instance/default-information-originate */ DEFPY (rip_default_information_originate, rip_default_information_originate_cmd, "[no] default-information originate", NO_STR "Control distribution of default route\n" "Distribute a default route\n") { nb_cli_enqueue_change(vty, "./default-information-originate", NB_OP_MODIFY, no ? "false" : "true"); return nb_cli_apply_changes(vty, NULL); } void cli_show_rip_default_information_originate(struct vty *vty, struct lyd_node *dnode, bool show_defaults) { if (!yang_dnode_get_bool(dnode, NULL)) vty_out(vty, " no"); vty_out(vty, " default-information originate\n"); } /* * XPath: /frr-ripd:ripd/instance/default-metric */ DEFPY (rip_default_metric, rip_default_metric_cmd, "default-metric (1-16)", "Set a metric of redistribute routes\n" "Default metric\n") { nb_cli_enqueue_change(vty, "./default-metric", NB_OP_MODIFY, default_metric_str); return nb_cli_apply_changes(vty, NULL); } DEFPY (no_rip_default_metric, no_rip_default_metric_cmd, "no default-metric [(1-16)]", NO_STR "Set a metric of redistribute routes\n" "Default metric\n") { nb_cli_enqueue_change(vty, "./default-metric", NB_OP_MODIFY, NULL); return nb_cli_apply_changes(vty, NULL); } void cli_show_rip_default_metric(struct vty *vty, struct lyd_node *dnode, bool show_defaults) { vty_out(vty, " default-metric %s\n", yang_dnode_get_string(dnode, NULL)); } /* * XPath: /frr-ripd:ripd/instance/distance/default */ DEFPY (rip_distance, rip_distance_cmd, "distance (1-255)", "Administrative distance\n" "Distance value\n") { nb_cli_enqueue_change(vty, "./distance/default", NB_OP_MODIFY, distance_str); return nb_cli_apply_changes(vty, NULL); } DEFPY (no_rip_distance, no_rip_distance_cmd, "no distance [(1-255)]", NO_STR "Administrative distance\n" "Distance value\n") { nb_cli_enqueue_change(vty, "./distance/default", NB_OP_MODIFY, NULL); return nb_cli_apply_changes(vty, NULL); } void cli_show_rip_distance(struct vty *vty, struct lyd_node *dnode, bool show_defaults) { if (yang_dnode_is_default(dnode, NULL)) vty_out(vty, " no distance\n"); else vty_out(vty, " distance %s\n", yang_dnode_get_string(dnode, NULL)); } /* * XPath: /frr-ripd:ripd/instance/distance/source */ DEFPY (rip_distance_source, rip_distance_source_cmd, "[no] distance (1-255) A.B.C.D/M$prefix [WORD$acl]", NO_STR "Administrative distance\n" "Distance value\n" "IP source prefix\n" "Access list name\n") { if (!no) { nb_cli_enqueue_change(vty, ".", NB_OP_CREATE, NULL); nb_cli_enqueue_change(vty, "./distance", NB_OP_MODIFY, distance_str); nb_cli_enqueue_change(vty, "./access-list", acl ? NB_OP_MODIFY : NB_OP_DESTROY, acl); } else nb_cli_enqueue_change(vty, ".", NB_OP_DESTROY, NULL); return nb_cli_apply_changes(vty, "./distance/source[prefix='%s']", prefix_str); } void cli_show_rip_distance_source(struct vty *vty, struct lyd_node *dnode, bool show_defaults) { vty_out(vty, " distance %s %s", yang_dnode_get_string(dnode, "./distance"), yang_dnode_get_string(dnode, "./prefix")); if (yang_dnode_exists(dnode, "./access-list")) vty_out(vty, " %s", yang_dnode_get_string(dnode, "./access-list")); vty_out(vty, "\n"); } /* * XPath: /frr-ripd:ripd/instance/explicit-neighbor */ DEFPY (rip_neighbor, rip_neighbor_cmd, "[no] neighbor A.B.C.D", NO_STR "Specify a neighbor router\n" "Neighbor address\n") { nb_cli_enqueue_change(vty, "./explicit-neighbor", no ? NB_OP_DESTROY : NB_OP_CREATE, neighbor_str); return nb_cli_apply_changes(vty, NULL); } void cli_show_rip_neighbor(struct vty *vty, struct lyd_node *dnode, bool show_defaults) { vty_out(vty, " neighbor %s\n", yang_dnode_get_string(dnode, NULL)); } /* * XPath: /frr-ripd:ripd/instance/network */ DEFPY (rip_network_prefix, rip_network_prefix_cmd, "[no] network A.B.C.D/M", NO_STR "Enable routing on an IP network\n" "IP prefix /, e.g., 35.0.0.0/8\n") { nb_cli_enqueue_change(vty, "./network", no ? NB_OP_DESTROY : NB_OP_CREATE, network_str); return nb_cli_apply_changes(vty, NULL); } void cli_show_rip_network_prefix(struct vty *vty, struct lyd_node *dnode, bool show_defaults) { vty_out(vty, " network %s\n", yang_dnode_get_string(dnode, NULL)); } /* * XPath: /frr-ripd:ripd/instance/interface */ DEFPY (rip_network_if, rip_network_if_cmd, "[no] network WORD", NO_STR "Enable routing on an IP network\n" "Interface name\n") { nb_cli_enqueue_change(vty, "./interface", no ? NB_OP_DESTROY : NB_OP_CREATE, network); return nb_cli_apply_changes(vty, NULL); } void cli_show_rip_network_interface(struct vty *vty, struct lyd_node *dnode, bool show_defaults) { vty_out(vty, " network %s\n", yang_dnode_get_string(dnode, NULL)); } /* * XPath: /frr-ripd:ripd/instance/offset-list */ DEFPY (rip_offset_list, rip_offset_list_cmd, "[no] offset-list WORD$acl $direction (0-16)$metric [IFNAME]", NO_STR "Modify RIP metric\n" "Access-list name\n" "For incoming updates\n" "For outgoing updates\n" "Metric value\n" "Interface to match\n") { if (!no) { nb_cli_enqueue_change(vty, ".", NB_OP_CREATE, NULL); nb_cli_enqueue_change(vty, "./access-list", NB_OP_MODIFY, acl); nb_cli_enqueue_change(vty, "./metric", NB_OP_MODIFY, metric_str); } else nb_cli_enqueue_change(vty, ".", NB_OP_DESTROY, NULL); return nb_cli_apply_changes( vty, "./offset-list[interface='%s'][direction='%s']", ifname ? ifname : "*", direction); } void cli_show_rip_offset_list(struct vty *vty, struct lyd_node *dnode, bool show_defaults) { const char *interface; interface = yang_dnode_get_string(dnode, "./interface"); vty_out(vty, " offset-list %s %s %s", yang_dnode_get_string(dnode, "./access-list"), yang_dnode_get_string(dnode, "./direction"), yang_dnode_get_string(dnode, "./metric")); if (!strmatch(interface, "*")) vty_out(vty, " %s", interface); vty_out(vty, "\n"); } /* * XPath: /frr-ripd:ripd/instance/passive-default */ DEFPY (rip_passive_default, rip_passive_default_cmd, "[no] passive-interface default", NO_STR "Suppress routing updates on an interface\n" "default for all interfaces\n") { nb_cli_enqueue_change(vty, "./passive-default", NB_OP_MODIFY, no ? "false" : "true"); return nb_cli_apply_changes(vty, NULL); } void cli_show_rip_passive_default(struct vty *vty, struct lyd_node *dnode, bool show_defaults) { if (!yang_dnode_get_bool(dnode, NULL)) vty_out(vty, " no"); vty_out(vty, " passive-interface default\n"); } /* * XPath: /frr-ripd:ripd/instance/passive-interface * /frr-ripd:ripd/instance/non-passive-interface */ DEFPY (rip_passive_interface, rip_passive_interface_cmd, "[no] passive-interface IFNAME", NO_STR "Suppress routing updates on an interface\n" "Interface name\n") { bool passive_default = yang_dnode_get_bool(vty->candidate_config->dnode, "%s%s", VTY_CURR_XPATH, "/passive-default"); if (passive_default) { nb_cli_enqueue_change(vty, "./non-passive-interface", no ? NB_OP_CREATE : NB_OP_DESTROY, ifname); } else { nb_cli_enqueue_change(vty, "./passive-interface", no ? NB_OP_DESTROY : NB_OP_CREATE, ifname); } return nb_cli_apply_changes(vty, NULL); } void cli_show_rip_passive_interface(struct vty *vty, struct lyd_node *dnode, bool show_defaults) { vty_out(vty, " passive-interface %s\n", yang_dnode_get_string(dnode, NULL)); } void cli_show_rip_non_passive_interface(struct vty *vty, struct lyd_node *dnode, bool show_defaults) { vty_out(vty, " no passive-interface %s\n", yang_dnode_get_string(dnode, NULL)); } /* * XPath: /frr-ripd:ripd/instance/redistribute */ DEFPY (rip_redistribute, rip_redistribute_cmd, "[no] redistribute " FRR_REDIST_STR_RIPD "$protocol [{metric (0-16)|route-map WORD}]", NO_STR REDIST_STR FRR_REDIST_HELP_STR_RIPD "Metric\n" "Metric value\n" "Route map reference\n" "Pointer to route-map entries\n") { if (!no) { nb_cli_enqueue_change(vty, ".", NB_OP_CREATE, NULL); nb_cli_enqueue_change(vty, "./route-map", route_map ? NB_OP_MODIFY : NB_OP_DESTROY, route_map); nb_cli_enqueue_change(vty, "./metric", metric_str ? NB_OP_MODIFY : NB_OP_DESTROY, metric_str); } else nb_cli_enqueue_change(vty, ".", NB_OP_DESTROY, NULL); return nb_cli_apply_changes(vty, "./redistribute[protocol='%s']", protocol); } void cli_show_rip_redistribute(struct vty *vty, struct lyd_node *dnode, bool show_defaults) { vty_out(vty, " redistribute %s", yang_dnode_get_string(dnode, "./protocol")); if (yang_dnode_exists(dnode, "./metric")) vty_out(vty, " metric %s", yang_dnode_get_string(dnode, "./metric")); if (yang_dnode_exists(dnode, "./route-map")) vty_out(vty, " route-map %s", yang_dnode_get_string(dnode, "./route-map")); vty_out(vty, "\n"); } /* * XPath: /frr-ripd:ripd/instance/static-route */ DEFPY (rip_route, rip_route_cmd, "[no] route A.B.C.D/M", NO_STR "RIP static route configuration\n" "IP prefix /\n") { nb_cli_enqueue_change(vty, "./static-route", no ? NB_OP_DESTROY : NB_OP_CREATE, route_str); return nb_cli_apply_changes(vty, NULL); } void cli_show_rip_route(struct vty *vty, struct lyd_node *dnode, bool show_defaults) { vty_out(vty, " route %s\n", yang_dnode_get_string(dnode, NULL)); } /* * XPath: /frr-ripd:ripd/instance/timers */ DEFPY (rip_timers, rip_timers_cmd, "timers basic (5-2147483647)$update (5-2147483647)$timeout (5-2147483647)$garbage", "Adjust routing timers\n" "Basic routing protocol update timers\n" "Routing table update timer value in second. Default is 30.\n" "Routing information timeout timer. Default is 180.\n" "Garbage collection timer. Default is 120.\n") { nb_cli_enqueue_change(vty, "./update-interval", NB_OP_MODIFY, update_str); nb_cli_enqueue_change(vty, "./holddown-interval", NB_OP_MODIFY, timeout_str); nb_cli_enqueue_change(vty, "./flush-interval", NB_OP_MODIFY, garbage_str); return nb_cli_apply_changes(vty, "./timers"); } DEFPY (no_rip_timers, no_rip_timers_cmd, "no timers basic [(5-2147483647) (5-2147483647) (5-2147483647)]", NO_STR "Adjust routing timers\n" "Basic routing protocol update timers\n" "Routing table update timer value in second. Default is 30.\n" "Routing information timeout timer. Default is 180.\n" "Garbage collection timer. Default is 120.\n") { nb_cli_enqueue_change(vty, "./update-interval", NB_OP_MODIFY, NULL); nb_cli_enqueue_change(vty, "./holddown-interval", NB_OP_MODIFY, NULL); nb_cli_enqueue_change(vty, "./flush-interval", NB_OP_MODIFY, NULL); return nb_cli_apply_changes(vty, "./timers"); } void cli_show_rip_timers(struct vty *vty, struct lyd_node *dnode, bool show_defaults) { vty_out(vty, " timers basic %s %s %s\n", yang_dnode_get_string(dnode, "./update-interval"), yang_dnode_get_string(dnode, "./holddown-interval"), yang_dnode_get_string(dnode, "./flush-interval")); } /* * XPath: /frr-ripd:ripd/instance/version */ DEFPY (rip_version, rip_version_cmd, "version (1-2)", "Set routing protocol version\n" "version\n") { nb_cli_enqueue_change(vty, "./version/receive", NB_OP_MODIFY, version_str); nb_cli_enqueue_change(vty, "./version/send", NB_OP_MODIFY, version_str); return nb_cli_apply_changes(vty, NULL); } DEFPY (no_rip_version, no_rip_version_cmd, "no version [(1-2)]", NO_STR "Set routing protocol version\n" "version\n") { nb_cli_enqueue_change(vty, "./version/receive", NB_OP_MODIFY, NULL); nb_cli_enqueue_change(vty, "./version/send", NB_OP_MODIFY, NULL); return nb_cli_apply_changes(vty, NULL); } void cli_show_rip_version(struct vty *vty, struct lyd_node *dnode, bool show_defaults) { /* * We have only one "version" command and three possible combinations of * send/receive values. */ switch (yang_dnode_get_enum(dnode, "./receive")) { case RI_RIP_VERSION_1: vty_out(vty, " version 1\n"); break; case RI_RIP_VERSION_2: vty_out(vty, " version 2\n"); break; case RI_RIP_VERSION_1_AND_2: vty_out(vty, " no version\n"); break; } } /* * XPath: /frr-interface:lib/interface/frr-ripd:rip/split-horizon */ DEFPY (ip_rip_split_horizon, ip_rip_split_horizon_cmd, "[no] ip rip split-horizon [poisoned-reverse$poisoned_reverse]", NO_STR IP_STR "Routing Information Protocol\n" "Perform split horizon\n" "With poisoned-reverse\n") { const char *value; if (no) value = "disabled"; else if (poisoned_reverse) value = "poison-reverse"; else value = "simple"; nb_cli_enqueue_change(vty, "./split-horizon", NB_OP_MODIFY, value); return nb_cli_apply_changes(vty, "./frr-ripd:rip"); } void cli_show_ip_rip_split_horizon(struct vty *vty, struct lyd_node *dnode, bool show_defaults) { int value; value = yang_dnode_get_enum(dnode, NULL); switch (value) { case RIP_NO_SPLIT_HORIZON: vty_out(vty, " no ip rip split-horizon\n"); break; case RIP_SPLIT_HORIZON: vty_out(vty, " ip rip split-horizon\n"); break; case RIP_SPLIT_HORIZON_POISONED_REVERSE: vty_out(vty, " ip rip split-horizon poisoned-reverse\n"); break; } } /* * XPath: /frr-interface:lib/interface/frr-ripd:rip/v2-broadcast */ DEFPY (ip_rip_v2_broadcast, ip_rip_v2_broadcast_cmd, "[no] ip rip v2-broadcast", NO_STR IP_STR "Routing Information Protocol\n" "Send ip broadcast v2 update\n") { nb_cli_enqueue_change(vty, "./v2-broadcast", NB_OP_MODIFY, no ? "false" : "true"); return nb_cli_apply_changes(vty, "./frr-ripd:rip"); } void cli_show_ip_rip_v2_broadcast(struct vty *vty, struct lyd_node *dnode, bool show_defaults) { if (!yang_dnode_get_bool(dnode, NULL)) vty_out(vty, " no"); vty_out(vty, " ip rip v2-broadcast\n"); } /* * XPath: /frr-interface:lib/interface/frr-ripd:rip/version-receive */ DEFPY (ip_rip_receive_version, ip_rip_receive_version_cmd, "ip rip receive version <{1$v1|2$v2}|none>", IP_STR "Routing Information Protocol\n" "Advertisement reception\n" "Version control\n" "RIP version 1\n" "RIP version 2\n" "None\n") { const char *value; if (v1 && v2) value = "both"; else if (v1) value = "1"; else if (v2) value = "2"; else value = "none"; nb_cli_enqueue_change(vty, "./version-receive", NB_OP_MODIFY, value); return nb_cli_apply_changes(vty, "./frr-ripd:rip"); } DEFPY (no_ip_rip_receive_version, no_ip_rip_receive_version_cmd, "no ip rip receive version [<{1|2}|none>]", NO_STR IP_STR "Routing Information Protocol\n" "Advertisement reception\n" "Version control\n" "RIP version 1\n" "RIP version 2\n" "None\n") { nb_cli_enqueue_change(vty, "./version-receive", NB_OP_MODIFY, NULL); return nb_cli_apply_changes(vty, "./frr-ripd:rip"); } void cli_show_ip_rip_receive_version(struct vty *vty, struct lyd_node *dnode, bool show_defaults) { switch (yang_dnode_get_enum(dnode, NULL)) { case RI_RIP_UNSPEC: vty_out(vty, " no ip rip receive version\n"); break; case RI_RIP_VERSION_1: vty_out(vty, " ip rip receive version 1\n"); break; case RI_RIP_VERSION_2: vty_out(vty, " ip rip receive version 2\n"); break; case RI_RIP_VERSION_1_AND_2: vty_out(vty, " ip rip receive version 1 2\n"); break; case RI_RIP_VERSION_NONE: vty_out(vty, " ip rip receive version none\n"); break; } } /* * XPath: /frr-interface:lib/interface/frr-ripd:rip/version-send */ DEFPY (ip_rip_send_version, ip_rip_send_version_cmd, "ip rip send version <{1$v1|2$v2}|none>", IP_STR "Routing Information Protocol\n" "Advertisement transmission\n" "Version control\n" "RIP version 1\n" "RIP version 2\n" "None\n") { const char *value; if (v1 && v2) value = "both"; else if (v1) value = "1"; else if (v2) value = "2"; else value = "none"; nb_cli_enqueue_change(vty, "./version-send", NB_OP_MODIFY, value); return nb_cli_apply_changes(vty, "./frr-ripd:rip"); } DEFPY (no_ip_rip_send_version, no_ip_rip_send_version_cmd, "no ip rip send version [<{1|2}|none>]", NO_STR IP_STR "Routing Information Protocol\n" "Advertisement transmission\n" "Version control\n" "RIP version 1\n" "RIP version 2\n" "None\n") { nb_cli_enqueue_change(vty, "./version-send", NB_OP_MODIFY, NULL); return nb_cli_apply_changes(vty, "./frr-ripd:rip"); } void cli_show_ip_rip_send_version(struct vty *vty, struct lyd_node *dnode, bool show_defaults) { switch (yang_dnode_get_enum(dnode, NULL)) { case RI_RIP_UNSPEC: vty_out(vty, " no ip rip send version\n"); break; case RI_RIP_VERSION_1: vty_out(vty, " ip rip send version 1\n"); break; case RI_RIP_VERSION_2: vty_out(vty, " ip rip send version 2\n"); break; case RI_RIP_VERSION_1_AND_2: vty_out(vty, " ip rip send version 1 2\n"); break; case RI_RIP_VERSION_NONE: vty_out(vty, " ip rip send version none\n"); break; } } /* * XPath: /frr-interface:lib/interface/frr-ripd:rip/authentication-scheme */ DEFPY (ip_rip_authentication_mode, ip_rip_authentication_mode_cmd, "ip rip authentication mode $auth_length]|text$mode>", IP_STR "Routing Information Protocol\n" "Authentication control\n" "Authentication mode\n" "Keyed message digest\n" "MD5 authentication data length\n" "RFC compatible\n" "Old ripd compatible\n" "Clear text authentication\n") { const char *value = NULL; if (auth_length) { if (strmatch(auth_length, "rfc")) value = "16"; else value = "20"; } nb_cli_enqueue_change(vty, "./authentication-scheme/mode", NB_OP_MODIFY, strmatch(mode, "md5") ? "md5" : "plain-text"); if (strmatch(mode, "md5")) nb_cli_enqueue_change(vty, "./authentication-scheme/md5-auth-length", NB_OP_MODIFY, value); return nb_cli_apply_changes(vty, "./frr-ripd:rip"); } DEFPY (no_ip_rip_authentication_mode, no_ip_rip_authentication_mode_cmd, "no ip rip authentication mode []|text>]", NO_STR IP_STR "Routing Information Protocol\n" "Authentication control\n" "Authentication mode\n" "Keyed message digest\n" "MD5 authentication data length\n" "RFC compatible\n" "Old ripd compatible\n" "Clear text authentication\n") { nb_cli_enqueue_change(vty, "./authentication-scheme/mode", NB_OP_MODIFY, NULL); nb_cli_enqueue_change(vty, "./authentication-scheme/md5-auth-length", NB_OP_DESTROY, NULL); return nb_cli_apply_changes(vty, "./frr-ripd:rip"); } void cli_show_ip_rip_authentication_scheme(struct vty *vty, struct lyd_node *dnode, bool show_defaults) { switch (yang_dnode_get_enum(dnode, "./mode")) { case RIP_NO_AUTH: vty_out(vty, " no ip rip authentication mode\n"); break; case RIP_AUTH_SIMPLE_PASSWORD: vty_out(vty, " ip rip authentication mode text\n"); break; case RIP_AUTH_MD5: vty_out(vty, " ip rip authentication mode md5"); if (show_defaults || !yang_dnode_is_default(dnode, "./md5-auth-length")) { if (yang_dnode_get_enum(dnode, "./md5-auth-length") == RIP_AUTH_MD5_SIZE) vty_out(vty, " auth-length rfc"); else vty_out(vty, " auth-length old-ripd"); } vty_out(vty, "\n"); break; } } /* * XPath: /frr-interface:lib/interface/frr-ripd:rip/authentication-password */ DEFPY (ip_rip_authentication_string, ip_rip_authentication_string_cmd, "ip rip authentication string LINE$password", IP_STR "Routing Information Protocol\n" "Authentication control\n" "Authentication string\n" "Authentication string\n") { if (strlen(password) > 16) { vty_out(vty, "%% RIPv2 authentication string must be shorter than 16\n"); return CMD_WARNING_CONFIG_FAILED; } if (yang_dnode_exists(vty->candidate_config->dnode, "%s%s", VTY_CURR_XPATH, "/frr-ripd:rip/authentication-key-chain")) { vty_out(vty, "%% key-chain configuration exists\n"); return CMD_WARNING_CONFIG_FAILED; } nb_cli_enqueue_change(vty, "./authentication-password", NB_OP_MODIFY, password); return nb_cli_apply_changes(vty, "./frr-ripd:rip"); } DEFPY (no_ip_rip_authentication_string, no_ip_rip_authentication_string_cmd, "no ip rip authentication string [LINE]", NO_STR IP_STR "Routing Information Protocol\n" "Authentication control\n" "Authentication string\n" "Authentication string\n") { nb_cli_enqueue_change(vty, "./authentication-password", NB_OP_DESTROY, NULL); return nb_cli_apply_changes(vty, "./frr-ripd:rip"); } void cli_show_ip_rip_authentication_string(struct vty *vty, struct lyd_node *dnode, bool show_defaults) { vty_out(vty, " ip rip authentication string %s\n", yang_dnode_get_string(dnode, NULL)); } /* * XPath: /frr-interface:lib/interface/frr-ripd:rip/authentication-key-chain */ DEFPY (ip_rip_authentication_key_chain, ip_rip_authentication_key_chain_cmd, "ip rip authentication key-chain LINE$keychain", IP_STR "Routing Information Protocol\n" "Authentication control\n" "Authentication key-chain\n" "name of key-chain\n") { if (yang_dnode_exists(vty->candidate_config->dnode, "%s%s", VTY_CURR_XPATH, "/frr-ripd:rip/authentication-password")) { vty_out(vty, "%% authentication string configuration exists\n"); return CMD_WARNING_CONFIG_FAILED; } nb_cli_enqueue_change(vty, "./authentication-key-chain", NB_OP_MODIFY, keychain); return nb_cli_apply_changes(vty, "./frr-ripd:rip"); } DEFPY (no_ip_rip_authentication_key_chain, no_ip_rip_authentication_key_chain_cmd, "no ip rip authentication key-chain [LINE]", NO_STR IP_STR "Routing Information Protocol\n" "Authentication control\n" "Authentication key-chain\n" "name of key-chain\n") { nb_cli_enqueue_change(vty, "./authentication-key-chain", NB_OP_DESTROY, NULL); return nb_cli_apply_changes(vty, "./frr-ripd:rip"); } void cli_show_ip_rip_authentication_key_chain(struct vty *vty, struct lyd_node *dnode, bool show_defaults) { vty_out(vty, " ip rip authentication key-chain %s\n", yang_dnode_get_string(dnode, NULL)); } /* * XPath: /frr-ripd:clear-rip-route */ DEFPY (clear_ip_rip, clear_ip_rip_cmd, "clear ip rip [vrf WORD]", CLEAR_STR IP_STR "Clear IP RIP database\n" VRF_CMD_HELP_STR) { struct list *input; input = list_new(); if (vrf) { struct yang_data *yang_vrf; yang_vrf = yang_data_new("/frr-ripd:clear-rip-route/input/vrf", vrf); listnode_add(input, yang_vrf); } return nb_cli_rpc("/frr-ripd:clear-rip-route", input, NULL); } void rip_cli_init(void) { install_element(CONFIG_NODE, &router_rip_cmd); install_element(CONFIG_NODE, &no_router_rip_cmd); install_element(RIP_NODE, &rip_allow_ecmp_cmd); install_element(RIP_NODE, &rip_default_information_originate_cmd); install_element(RIP_NODE, &rip_default_metric_cmd); install_element(RIP_NODE, &no_rip_default_metric_cmd); install_element(RIP_NODE, &rip_distance_cmd); install_element(RIP_NODE, &no_rip_distance_cmd); install_element(RIP_NODE, &rip_distance_source_cmd); install_element(RIP_NODE, &rip_neighbor_cmd); install_element(RIP_NODE, &rip_network_prefix_cmd); install_element(RIP_NODE, &rip_network_if_cmd); install_element(RIP_NODE, &rip_offset_list_cmd); install_element(RIP_NODE, &rip_passive_default_cmd); install_element(RIP_NODE, &rip_passive_interface_cmd); install_element(RIP_NODE, &rip_redistribute_cmd); install_element(RIP_NODE, &rip_route_cmd); install_element(RIP_NODE, &rip_timers_cmd); install_element(RIP_NODE, &no_rip_timers_cmd); install_element(RIP_NODE, &rip_version_cmd); install_element(RIP_NODE, &no_rip_version_cmd); install_element(INTERFACE_NODE, &ip_rip_split_horizon_cmd); install_element(INTERFACE_NODE, &ip_rip_v2_broadcast_cmd); install_element(INTERFACE_NODE, &ip_rip_receive_version_cmd); install_element(INTERFACE_NODE, &no_ip_rip_receive_version_cmd); install_element(INTERFACE_NODE, &ip_rip_send_version_cmd); install_element(INTERFACE_NODE, &no_ip_rip_send_version_cmd); install_element(INTERFACE_NODE, &ip_rip_authentication_mode_cmd); install_element(INTERFACE_NODE, &no_ip_rip_authentication_mode_cmd); install_element(INTERFACE_NODE, &ip_rip_authentication_string_cmd); install_element(INTERFACE_NODE, &no_ip_rip_authentication_string_cmd); install_element(INTERFACE_NODE, &ip_rip_authentication_key_chain_cmd); install_element(INTERFACE_NODE, &no_ip_rip_authentication_key_chain_cmd); install_element(ENABLE_NODE, &clear_ip_rip_cmd); } frr-7.2.1/ripd/rip_cli.h0000644000000000000000000000721613610377563011752 00000000000000/* * Copyright (C) 1997, 1998, 1999 Kunihiro Ishiguro * Copyright (C) 2018 NetDEF, Inc. * Renato Westphal * * This program is free software; you can redistribute it and/or modify it * under the terms of the 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; see the file COPYING; if not, write to the Free Software * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA */ #ifndef _FRR_RIP_CLI_H_ #define _FRR_RIP_CLI_H_ extern void cli_show_router_rip(struct vty *vty, struct lyd_node *dnode, bool show_defaults); extern void cli_show_rip_allow_ecmp(struct vty *vty, struct lyd_node *dnode, bool show_defaults); extern void cli_show_rip_default_information_originate(struct vty *vty, struct lyd_node *dnode, bool show_defaults); extern void cli_show_rip_default_metric(struct vty *vty, struct lyd_node *dnode, bool show_defaults); extern void cli_show_rip_distance(struct vty *vty, struct lyd_node *dnode, bool show_defaults); extern void cli_show_rip_distance_source(struct vty *vty, struct lyd_node *dnode, bool show_defaults); extern void cli_show_rip_neighbor(struct vty *vty, struct lyd_node *dnode, bool show_defaults); extern void cli_show_rip_network_prefix(struct vty *vty, struct lyd_node *dnode, bool show_defaults); extern void cli_show_rip_network_interface(struct vty *vty, struct lyd_node *dnode, bool show_defaults); extern void cli_show_rip_offset_list(struct vty *vty, struct lyd_node *dnode, bool show_defaults); extern void cli_show_rip_passive_default(struct vty *vty, struct lyd_node *dnode, bool show_defaults); extern void cli_show_rip_passive_interface(struct vty *vty, struct lyd_node *dnode, bool show_defaults); extern void cli_show_rip_non_passive_interface(struct vty *vty, struct lyd_node *dnode, bool show_defaults); extern void cli_show_rip_redistribute(struct vty *vty, struct lyd_node *dnode, bool show_defaults); extern void cli_show_rip_route(struct vty *vty, struct lyd_node *dnode, bool show_defaults); extern void cli_show_rip_timers(struct vty *vty, struct lyd_node *dnode, bool show_defaults); extern void cli_show_rip_version(struct vty *vty, struct lyd_node *dnode, bool show_defaults); extern void cli_show_ip_rip_split_horizon(struct vty *vty, struct lyd_node *dnode, bool show_defaults); extern void cli_show_ip_rip_v2_broadcast(struct vty *vty, struct lyd_node *dnode, bool show_defaults); extern void cli_show_ip_rip_receive_version(struct vty *vty, struct lyd_node *dnode, bool show_defaults); extern void cli_show_ip_rip_send_version(struct vty *vty, struct lyd_node *dnode, bool show_defaults); extern void cli_show_ip_rip_authentication_scheme(struct vty *vty, struct lyd_node *dnode, bool show_defaults); extern void cli_show_ip_rip_authentication_string(struct vty *vty, struct lyd_node *dnode, bool show_defaults); extern void cli_show_ip_rip_authentication_key_chain(struct vty *vty, struct lyd_node *dnode, bool show_defaults); #endif /* _FRR_RIP_CLI_H_ */ frr-7.2.1/ripd/rip_debug.c0000644000000000000000000001343513610377563012264 00000000000000/* RIP debug routines * Copyright (C) 1999 Kunihiro Ishiguro * * This file is part of GNU Zebra. * * GNU Zebra is free software; you can redistribute it and/or modify it * under the terms of the GNU General Public License as published by the * Free Software Foundation; either version 2, or (at your option) any * later version. * * GNU Zebra 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; see the file COPYING; if not, write to the Free Software * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA */ #include #include "command.h" #include "ripd/rip_debug.h" /* For debug statement. */ unsigned long rip_debug_event = 0; unsigned long rip_debug_packet = 0; unsigned long rip_debug_zebra = 0; DEFUN_NOSH (show_debugging_rip, show_debugging_rip_cmd, "show debugging [rip]", SHOW_STR DEBUG_STR RIP_STR) { vty_out(vty, "RIP debugging status:\n"); if (IS_RIP_DEBUG_EVENT) vty_out(vty, " RIP event debugging is on\n"); if (IS_RIP_DEBUG_PACKET) { if (IS_RIP_DEBUG_SEND && IS_RIP_DEBUG_RECV) { vty_out(vty, " RIP packet debugging is on\n"); } else { if (IS_RIP_DEBUG_SEND) vty_out(vty, " RIP packet send debugging is on\n"); else vty_out(vty, " RIP packet receive debugging is on\n"); } } if (IS_RIP_DEBUG_ZEBRA) vty_out(vty, " RIP zebra debugging is on\n"); return CMD_SUCCESS; } DEFUN (debug_rip_events, debug_rip_events_cmd, "debug rip events", DEBUG_STR RIP_STR "RIP events\n") { rip_debug_event = RIP_DEBUG_EVENT; return CMD_SUCCESS; } DEFUN (debug_rip_packet, debug_rip_packet_cmd, "debug rip packet", DEBUG_STR RIP_STR "RIP packet\n") { rip_debug_packet = RIP_DEBUG_PACKET; rip_debug_packet |= RIP_DEBUG_SEND; rip_debug_packet |= RIP_DEBUG_RECV; return CMD_SUCCESS; } DEFUN (debug_rip_packet_direct, debug_rip_packet_direct_cmd, "debug rip packet ", DEBUG_STR RIP_STR "RIP packet\n" "RIP receive packet\n" "RIP send packet\n") { int idx_recv_send = 3; rip_debug_packet |= RIP_DEBUG_PACKET; if (strcmp("send", argv[idx_recv_send]->text) == 0) rip_debug_packet |= RIP_DEBUG_SEND; if (strcmp("recv", argv[idx_recv_send]->text) == 0) rip_debug_packet |= RIP_DEBUG_RECV; return CMD_SUCCESS; } DEFUN (debug_rip_zebra, debug_rip_zebra_cmd, "debug rip zebra", DEBUG_STR RIP_STR "RIP and ZEBRA communication\n") { rip_debug_zebra = RIP_DEBUG_ZEBRA; return CMD_SUCCESS; } DEFUN (no_debug_rip_events, no_debug_rip_events_cmd, "no debug rip events", NO_STR DEBUG_STR RIP_STR "RIP events\n") { rip_debug_event = 0; return CMD_SUCCESS; } DEFUN (no_debug_rip_packet, no_debug_rip_packet_cmd, "no debug rip packet", NO_STR DEBUG_STR RIP_STR "RIP packet\n") { rip_debug_packet = 0; return CMD_SUCCESS; } DEFUN (no_debug_rip_packet_direct, no_debug_rip_packet_direct_cmd, "no debug rip packet ", NO_STR DEBUG_STR RIP_STR "RIP packet\n" "RIP option set for receive packet\n" "RIP option set for send packet\n") { int idx_recv_send = 4; if (strcmp("send", argv[idx_recv_send]->text) == 0) { if (IS_RIP_DEBUG_RECV) rip_debug_packet &= ~RIP_DEBUG_SEND; else rip_debug_packet = 0; } else if (strcmp("recv", argv[idx_recv_send]->text) == 0) { if (IS_RIP_DEBUG_SEND) rip_debug_packet &= ~RIP_DEBUG_RECV; else rip_debug_packet = 0; } return CMD_SUCCESS; } DEFUN (no_debug_rip_zebra, no_debug_rip_zebra_cmd, "no debug rip zebra", NO_STR DEBUG_STR RIP_STR "RIP and ZEBRA communication\n") { rip_debug_zebra = 0; return CMD_SUCCESS; } /* Debug node. */ static struct cmd_node debug_node = {DEBUG_NODE, "", /* Debug node has no interface. */ 1}; static int config_write_debug(struct vty *vty) { int write = 0; if (IS_RIP_DEBUG_EVENT) { vty_out(vty, "debug rip events\n"); write++; } if (IS_RIP_DEBUG_PACKET) { if (IS_RIP_DEBUG_SEND && IS_RIP_DEBUG_RECV) { vty_out(vty, "debug rip packet\n"); write++; } else { if (IS_RIP_DEBUG_SEND) vty_out(vty, "debug rip packet send\n"); else vty_out(vty, "debug rip packet recv\n"); write++; } } if (IS_RIP_DEBUG_ZEBRA) { vty_out(vty, "debug rip zebra\n"); write++; } return write; } void rip_debug_init(void) { rip_debug_event = 0; rip_debug_packet = 0; rip_debug_zebra = 0; install_node(&debug_node, config_write_debug); install_element(ENABLE_NODE, &show_debugging_rip_cmd); install_element(ENABLE_NODE, &debug_rip_events_cmd); install_element(ENABLE_NODE, &debug_rip_packet_cmd); install_element(ENABLE_NODE, &debug_rip_packet_direct_cmd); install_element(ENABLE_NODE, &debug_rip_zebra_cmd); install_element(ENABLE_NODE, &no_debug_rip_events_cmd); install_element(ENABLE_NODE, &no_debug_rip_packet_cmd); install_element(ENABLE_NODE, &no_debug_rip_packet_direct_cmd); install_element(ENABLE_NODE, &no_debug_rip_zebra_cmd); install_element(CONFIG_NODE, &debug_rip_events_cmd); install_element(CONFIG_NODE, &debug_rip_packet_cmd); install_element(CONFIG_NODE, &debug_rip_packet_direct_cmd); install_element(CONFIG_NODE, &debug_rip_zebra_cmd); install_element(CONFIG_NODE, &no_debug_rip_events_cmd); install_element(CONFIG_NODE, &no_debug_rip_packet_cmd); install_element(CONFIG_NODE, &no_debug_rip_packet_direct_cmd); install_element(CONFIG_NODE, &no_debug_rip_zebra_cmd); } frr-7.2.1/ripd/rip_debug.h0000644000000000000000000000325513610377563012270 00000000000000/* RIP debug routines * Copyright (C) 1999 Kunihiro Ishiguro * * This file is part of GNU Zebra. * * GNU Zebra is free software; you can redistribute it and/or modify it * under the terms of the GNU General Public License as published by the * Free Software Foundation; either version 2, or (at your option) any * later version. * * GNU Zebra 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; see the file COPYING; if not, write to the Free Software * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA */ #ifndef _ZEBRA_RIP_DEBUG_H #define _ZEBRA_RIP_DEBUG_H /* RIP debug event flags. */ #define RIP_DEBUG_EVENT 0x01 /* RIP debug packet flags. */ #define RIP_DEBUG_PACKET 0x01 #define RIP_DEBUG_SEND 0x20 #define RIP_DEBUG_RECV 0x40 #define RIP_DEBUG_DETAIL 0x80 /* RIP debug zebra flags. */ #define RIP_DEBUG_ZEBRA 0x01 /* Debug related macro. */ #define IS_RIP_DEBUG_EVENT (rip_debug_event & RIP_DEBUG_EVENT) #define IS_RIP_DEBUG_PACKET (rip_debug_packet & RIP_DEBUG_PACKET) #define IS_RIP_DEBUG_SEND (rip_debug_packet & RIP_DEBUG_SEND) #define IS_RIP_DEBUG_RECV (rip_debug_packet & RIP_DEBUG_RECV) #define IS_RIP_DEBUG_ZEBRA (rip_debug_zebra & RIP_DEBUG_ZEBRA) extern unsigned long rip_debug_event; extern unsigned long rip_debug_packet; extern unsigned long rip_debug_zebra; extern void rip_debug_init(void); #endif /* _ZEBRA_RIP_DEBUG_H */ frr-7.2.1/ripd/rip_errors.c0000644000000000000000000000232713610377563012510 00000000000000/* * RIP-specific error messages. * Copyright (C) 2018 Cumulus Networks, Inc. * Donald Sharp * * This program is free software; you can redistribute it and/or modify it * under the terms of the 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; see the file COPYING; if not, write to the Free Software * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA */ #include #include "lib/ferr.h" #include "rip_errors.h" static struct log_ref ferr_rip_err[] = { {.code = EC_RIP_PACKET, .title = "RIP Packet Error", .description = "RIP has detected a packet encode/decode issue", .suggestion = "Gather log files from both sides and open a Issue"}, { .code = END_FERR, }}; void rip_error_init(void) { log_ref_add(ferr_rip_err); } frr-7.2.1/ripd/rip_errors.h0000644000000000000000000000202213610377563012505 00000000000000/* * RIP-specific error messages. * Copyright (C) 2018 Cumulus Networks, Inc. * Donald Sharp * * This program is free software; you can redistribute it and/or modify it * under the terms of the 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; see the file COPYING; if not, write to the Free Software * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA */ #ifndef __RIP_ERRORS_H__ #define __RIP_ERRORS_H__ #include "lib/ferr.h" enum rip_log_refs { EC_RIP_PACKET = RIP_FERR_START, RIP_ERR_CONFIG, }; extern void rip_error_init(void); #endif frr-7.2.1/ripd/rip_interface.c0000644000000000000000000007214113610377563013135 00000000000000/* Interface related function for RIP. * Copyright (C) 1997, 98 Kunihiro Ishiguro * * This file is part of GNU Zebra. * * GNU Zebra is free software; you can redistribute it and/or modify it * under the terms of the GNU General Public License as published by the * Free Software Foundation; either version 2, or (at your option) any * later version. * * GNU Zebra 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; see the file COPYING; if not, write to the Free Software * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA */ #include #include "command.h" #include "if.h" #include "sockunion.h" #include "prefix.h" #include "memory.h" #include "network.h" #include "table.h" #include "log.h" #include "stream.h" #include "thread.h" #include "zclient.h" #include "filter.h" #include "sockopt.h" #include "privs.h" #include "lib_errors.h" #include "northbound_cli.h" #include "zebra/connected.h" #include "ripd/ripd.h" #include "ripd/rip_debug.h" #include "ripd/rip_interface.h" DEFINE_MTYPE_STATIC(RIPD, RIP_INTERFACE, "RIP interface") DEFINE_MTYPE(RIPD, RIP_INTERFACE_STRING, "RIP Interface String") DEFINE_HOOK(rip_ifaddr_add, (struct connected * ifc), (ifc)) DEFINE_HOOK(rip_ifaddr_del, (struct connected * ifc), (ifc)) /* static prototypes */ static void rip_enable_apply(struct interface *); static void rip_passive_interface_apply(struct interface *); static int rip_if_down(struct interface *ifp); static int rip_enable_if_lookup(struct rip *rip, const char *ifname); static int rip_enable_network_lookup2(struct connected *connected); static void rip_enable_apply_all(struct rip *rip); const struct message ri_version_msg[] = {{RI_RIP_VERSION_1, "1"}, {RI_RIP_VERSION_2, "2"}, {RI_RIP_VERSION_1_AND_2, "1 2"}, {RI_RIP_VERSION_NONE, "none"}, {0}}; /* Join to the RIP version 2 multicast group. */ static int ipv4_multicast_join(int sock, struct in_addr group, struct in_addr ifa, ifindex_t ifindex) { int ret; ret = setsockopt_ipv4_multicast(sock, IP_ADD_MEMBERSHIP, ifa, group.s_addr, ifindex); if (ret < 0) zlog_info("can't setsockopt IP_ADD_MEMBERSHIP %s", safe_strerror(errno)); return ret; } /* Leave from the RIP version 2 multicast group. */ static int ipv4_multicast_leave(int sock, struct in_addr group, struct in_addr ifa, ifindex_t ifindex) { int ret; ret = setsockopt_ipv4_multicast(sock, IP_DROP_MEMBERSHIP, ifa, group.s_addr, ifindex); if (ret < 0) zlog_info("can't setsockopt IP_DROP_MEMBERSHIP"); return ret; } static void rip_interface_reset(struct rip_interface *); /* Allocate new RIP's interface configuration. */ static struct rip_interface *rip_interface_new(void) { struct rip_interface *ri; ri = XCALLOC(MTYPE_RIP_INTERFACE, sizeof(struct rip_interface)); rip_interface_reset(ri); return ri; } void rip_interface_multicast_set(int sock, struct connected *connected) { struct in_addr addr; assert(connected != NULL); addr = CONNECTED_ID(connected)->u.prefix4; if (setsockopt_ipv4_multicast_if(sock, addr, connected->ifp->ifindex) < 0) { zlog_warn( "Can't setsockopt IP_MULTICAST_IF on fd %d to " "ifindex %d for interface %s", sock, connected->ifp->ifindex, connected->ifp->name); } return; } /* Send RIP request packet to specified interface. */ static void rip_request_interface_send(struct interface *ifp, uint8_t version) { struct sockaddr_in to; /* RIPv2 support multicast. */ if (version == RIPv2 && if_is_multicast(ifp)) { if (IS_RIP_DEBUG_EVENT) zlog_debug("multicast request on %s", ifp->name); rip_request_send(NULL, ifp, version, NULL); return; } /* RIPv1 and non multicast interface. */ if (if_is_pointopoint(ifp) || if_is_broadcast(ifp)) { struct listnode *cnode, *cnnode; struct connected *connected; if (IS_RIP_DEBUG_EVENT) zlog_debug("broadcast request to %s", ifp->name); for (ALL_LIST_ELEMENTS(ifp->connected, cnode, cnnode, connected)) { if (connected->address->family != AF_INET) continue; memset(&to, 0, sizeof(struct sockaddr_in)); to.sin_port = htons(RIP_PORT_DEFAULT); if (connected->destination) /* use specified broadcast or peer * destination addr */ to.sin_addr = connected->destination->u.prefix4; else if (connected->address->prefixlen < IPV4_MAX_PREFIXLEN) /* calculate the appropriate broadcast * address */ to.sin_addr.s_addr = ipv4_broadcast_addr( connected->address->u.prefix4.s_addr, connected->address->prefixlen); else /* do not know where to send the packet */ continue; if (IS_RIP_DEBUG_EVENT) zlog_debug("SEND request to %s", inet_ntoa(to.sin_addr)); rip_request_send(&to, ifp, version, connected); } } } /* This will be executed when interface goes up. */ static void rip_request_interface(struct interface *ifp) { struct rip_interface *ri; int vsend; /* In default ripd doesn't send RIP_REQUEST to the loopback interface. */ if (if_is_loopback(ifp)) return; /* If interface is down, don't send RIP packet. */ if (!if_is_operative(ifp)) return; /* Fetch RIP interface information. */ ri = ifp->info; /* If there is no version configuration in the interface, use rip's version setting. */ vsend = ((ri->ri_send == RI_RIP_UNSPEC) ? ri->rip->version_send : ri->ri_send); if (vsend & RIPv1) rip_request_interface_send(ifp, RIPv1); if (vsend & RIPv2) rip_request_interface_send(ifp, RIPv2); } #if 0 /* Send RIP request to the neighbor. */ static void rip_request_neighbor (struct in_addr addr) { struct sockaddr_in to; memset (&to, 0, sizeof (struct sockaddr_in)); to.sin_port = htons (RIP_PORT_DEFAULT); to.sin_addr = addr; rip_request_send (&to, NULL, rip->version_send, NULL); } /* Request routes at all interfaces. */ static void rip_request_neighbor_all (void) { struct route_node *rp; if (! rip) return; if (IS_RIP_DEBUG_EVENT) zlog_debug ("request to the all neighbor"); /* Send request to all neighbor. */ for (rp = route_top (rip->neighbor); rp; rp = route_next (rp)) if (rp->info) rip_request_neighbor (rp->p.u.prefix4); } #endif /* Multicast packet receive socket. */ static int rip_multicast_join(struct interface *ifp, int sock) { struct listnode *cnode; struct connected *ifc; if (if_is_operative(ifp) && if_is_multicast(ifp)) { if (IS_RIP_DEBUG_EVENT) zlog_debug("multicast join at %s", ifp->name); for (ALL_LIST_ELEMENTS_RO(ifp->connected, cnode, ifc)) { struct prefix_ipv4 *p; struct in_addr group; p = (struct prefix_ipv4 *)ifc->address; if (p->family != AF_INET) continue; group.s_addr = htonl(INADDR_RIP_GROUP); if (ipv4_multicast_join(sock, group, p->prefix, ifp->ifindex) < 0) return -1; else return 0; } } return 0; } /* Leave from multicast group. */ static void rip_multicast_leave(struct interface *ifp, int sock) { struct listnode *cnode; struct connected *connected; if (if_is_up(ifp) && if_is_multicast(ifp)) { if (IS_RIP_DEBUG_EVENT) zlog_debug("multicast leave from %s", ifp->name); for (ALL_LIST_ELEMENTS_RO(ifp->connected, cnode, connected)) { struct prefix_ipv4 *p; struct in_addr group; p = (struct prefix_ipv4 *)connected->address; if (p->family != AF_INET) continue; group.s_addr = htonl(INADDR_RIP_GROUP); if (ipv4_multicast_leave(sock, group, p->prefix, ifp->ifindex) == 0) return; } } } /* Is there and address on interface that I could use ? */ static int rip_if_ipv4_address_check(struct interface *ifp) { struct listnode *nn; struct connected *connected; int count = 0; for (ALL_LIST_ELEMENTS_RO(ifp->connected, nn, connected)) { struct prefix *p; p = connected->address; if (p->family == AF_INET) count++; } return count; } /* Does this address belongs to me ? */ int if_check_address(struct rip *rip, struct in_addr addr) { struct interface *ifp; FOR_ALL_INTERFACES (rip->vrf, ifp) { struct listnode *cnode; struct connected *connected; for (ALL_LIST_ELEMENTS_RO(ifp->connected, cnode, connected)) { struct prefix_ipv4 *p; p = (struct prefix_ipv4 *)connected->address; if (p->family != AF_INET) continue; if (IPV4_ADDR_CMP(&p->prefix, &addr) == 0) return 1; } } return 0; } /* Inteface link down message processing. */ int rip_interface_down(ZAPI_CALLBACK_ARGS) { struct interface *ifp; struct stream *s; s = zclient->ibuf; /* zebra_interface_state_read() updates interface structure in iflist. */ ifp = zebra_interface_state_read(s, vrf_id); if (ifp == NULL) return 0; rip_interface_sync(ifp); rip_if_down(ifp); if (IS_RIP_DEBUG_ZEBRA) zlog_debug( "interface %s vrf %u index %d flags %llx metric %d mtu %d is down", ifp->name, ifp->vrf_id, ifp->ifindex, (unsigned long long)ifp->flags, ifp->metric, ifp->mtu); return 0; } /* Inteface link up message processing */ int rip_interface_up(ZAPI_CALLBACK_ARGS) { struct interface *ifp; /* zebra_interface_state_read () updates interface structure in iflist. */ ifp = zebra_interface_state_read(zclient->ibuf, vrf_id); if (ifp == NULL) return 0; if (IS_RIP_DEBUG_ZEBRA) zlog_debug( "interface %s vrf %u index %d flags %#llx metric %d mtu %d is up", ifp->name, ifp->vrf_id, ifp->ifindex, (unsigned long long)ifp->flags, ifp->metric, ifp->mtu); rip_interface_sync(ifp); /* Check if this interface is RIP enabled or not.*/ rip_enable_apply(ifp); /* Check for a passive interface */ rip_passive_interface_apply(ifp); /* Apply distribute list to the all interface. */ rip_distribute_update_interface(ifp); return 0; } /* Inteface addition message from zebra. */ int rip_interface_add(ZAPI_CALLBACK_ARGS) { struct interface *ifp; ifp = zebra_interface_add_read(zclient->ibuf, vrf_id); rip_interface_sync(ifp); if (IS_RIP_DEBUG_ZEBRA) zlog_debug( "interface add %s vrf %u index %d flags %#llx metric %d mtu %d", ifp->name, ifp->vrf_id, ifp->ifindex, (unsigned long long)ifp->flags, ifp->metric, ifp->mtu); /* Check if this interface is RIP enabled or not.*/ rip_enable_apply(ifp); /* Check for a passive interface */ rip_passive_interface_apply(ifp); /* Apply distribute list to the all interface. */ rip_distribute_update_interface(ifp); /* rip_request_neighbor_all (); */ /* Check interface routemap. */ rip_if_rmap_update_interface(ifp); return 0; } int rip_interface_delete(ZAPI_CALLBACK_ARGS) { struct interface *ifp; struct stream *s; s = zclient->ibuf; /* zebra_interface_state_read() updates interface structure in iflist */ ifp = zebra_interface_state_read(s, vrf_id); if (ifp == NULL) return 0; rip_interface_sync(ifp); if (if_is_up(ifp)) { rip_if_down(ifp); } zlog_info( "interface delete %s vrf %u index %d flags %#llx metric %d mtu %d", ifp->name, ifp->vrf_id, ifp->ifindex, (unsigned long long)ifp->flags, ifp->metric, ifp->mtu); /* To support pseudo interface do not free interface structure. */ /* if_delete(ifp); */ if_set_index(ifp, IFINDEX_INTERNAL); return 0; } /* VRF update for an interface. */ int rip_interface_vrf_update(ZAPI_CALLBACK_ARGS) { struct interface *ifp; vrf_id_t new_vrf_id; ifp = zebra_interface_vrf_update_read(zclient->ibuf, vrf_id, &new_vrf_id); if (!ifp) return 0; if (IS_RIP_DEBUG_ZEBRA) zlog_debug("interface %s VRF change vrf_id %u new vrf id %u", ifp->name, vrf_id, new_vrf_id); if_update_to_new_vrf(ifp, new_vrf_id); rip_interface_sync(ifp); return 0; } static void rip_interface_clean(struct rip_interface *ri) { ri->enable_network = 0; ri->enable_interface = 0; ri->running = 0; if (ri->t_wakeup) { thread_cancel(ri->t_wakeup); ri->t_wakeup = NULL; } } void rip_interfaces_clean(struct rip *rip) { struct interface *ifp; FOR_ALL_INTERFACES (rip->vrf, ifp) rip_interface_clean(ifp->info); } static void rip_interface_reset(struct rip_interface *ri) { ri->auth_type = yang_get_default_enum("%s/authentication-scheme/mode", RIP_IFACE); ri->md5_auth_len = yang_get_default_enum( "%s/authentication-scheme/md5-auth-length", RIP_IFACE); /* Set default split-horizon behavior. If the interface is Frame Relay or SMDS is enabled, the default value for split-horizon is off. But currently Zebra does detect Frame Relay or SMDS interface. So all interface is set to split horizon. */ ri->split_horizon = yang_get_default_enum("%s/split-horizon", RIP_IFACE); ri->ri_send = yang_get_default_enum("%s/version-send", RIP_IFACE); ri->ri_receive = yang_get_default_enum("%s/version-receive", RIP_IFACE); ri->v2_broadcast = yang_get_default_bool("%s/v2-broadcast", RIP_IFACE); XFREE(MTYPE_RIP_INTERFACE_STRING, ri->auth_str); XFREE(MTYPE_RIP_INTERFACE_STRING, ri->key_chain); ri->list[RIP_FILTER_IN] = NULL; ri->list[RIP_FILTER_OUT] = NULL; ri->prefix[RIP_FILTER_IN] = NULL; ri->prefix[RIP_FILTER_OUT] = NULL; ri->recv_badpackets = 0; ri->recv_badroutes = 0; ri->sent_updates = 0; ri->passive = 0; rip_interface_clean(ri); } int rip_if_down(struct interface *ifp) { struct rip *rip; struct route_node *rp; struct rip_info *rinfo; struct rip_interface *ri = NULL; struct list *list = NULL; struct listnode *listnode = NULL, *nextnode = NULL; ri = ifp->info; rip = ri->rip; if (rip) { for (rp = route_top(rip->table); rp; rp = route_next(rp)) if ((list = rp->info) != NULL) for (ALL_LIST_ELEMENTS(list, listnode, nextnode, rinfo)) if (rinfo->nh.ifindex == ifp->ifindex) rip_ecmp_delete(rip, rinfo); if (ri->running) { if (IS_RIP_DEBUG_EVENT) zlog_debug("turn off %s", ifp->name); /* Leave from multicast group. */ rip_multicast_leave(ifp, rip->sock); ri->running = 0; } } return 0; } static void rip_apply_address_add(struct connected *ifc) { struct rip_interface *ri = ifc->ifp->info; struct rip *rip = ri->rip; struct prefix_ipv4 address; struct nexthop nh; struct prefix *p; if (!rip) return; if (!if_is_up(ifc->ifp)) return; p = ifc->address; memset(&address, 0, sizeof(address)); memset(&nh, 0, sizeof(nh)); address.family = p->family; address.prefix = p->u.prefix4; address.prefixlen = p->prefixlen; apply_mask_ipv4(&address); nh.ifindex = ifc->ifp->ifindex; nh.type = NEXTHOP_TYPE_IFINDEX; /* Check if this interface is RIP enabled or not or Check if this address's prefix is RIP enabled */ if ((rip_enable_if_lookup(rip, ifc->ifp->name) >= 0) || (rip_enable_network_lookup2(ifc) >= 0)) rip_redistribute_add(rip, ZEBRA_ROUTE_CONNECT, RIP_ROUTE_INTERFACE, &address, &nh, 0, 0, 0); } int rip_interface_address_add(ZAPI_CALLBACK_ARGS) { struct connected *ifc; struct prefix *p; ifc = zebra_interface_address_read(ZEBRA_INTERFACE_ADDRESS_ADD, zclient->ibuf, vrf_id); if (ifc == NULL) return 0; p = ifc->address; if (p->family == AF_INET) { if (IS_RIP_DEBUG_ZEBRA) zlog_debug("connected address %s/%d is added", inet_ntoa(p->u.prefix4), p->prefixlen); rip_enable_apply(ifc->ifp); /* Check if this prefix needs to be redistributed */ rip_apply_address_add(ifc); hook_call(rip_ifaddr_add, ifc); } return 0; } static void rip_apply_address_del(struct connected *ifc) { struct rip_interface *ri = ifc->ifp->info; struct rip *rip = ri->rip; struct prefix_ipv4 address; struct prefix *p; if (!rip) return; if (!if_is_up(ifc->ifp)) return; p = ifc->address; memset(&address, 0, sizeof(address)); address.family = p->family; address.prefix = p->u.prefix4; address.prefixlen = p->prefixlen; apply_mask_ipv4(&address); rip_redistribute_delete(rip, ZEBRA_ROUTE_CONNECT, RIP_ROUTE_INTERFACE, &address, ifc->ifp->ifindex); } int rip_interface_address_delete(ZAPI_CALLBACK_ARGS) { struct connected *ifc; struct prefix *p; ifc = zebra_interface_address_read(ZEBRA_INTERFACE_ADDRESS_DELETE, zclient->ibuf, vrf_id); if (ifc) { p = ifc->address; if (p->family == AF_INET) { if (IS_RIP_DEBUG_ZEBRA) zlog_debug("connected address %s/%d is deleted", inet_ntoa(p->u.prefix4), p->prefixlen); hook_call(rip_ifaddr_del, ifc); /* Chech wether this prefix needs to be removed */ rip_apply_address_del(ifc); } connected_free(ifc); } return 0; } /* Check interface is enabled by network statement. */ /* Check wether the interface has at least a connected prefix that * is within the ripng_enable_network table. */ static int rip_enable_network_lookup_if(struct interface *ifp) { struct rip_interface *ri = ifp->info; struct rip *rip = ri->rip; struct listnode *node, *nnode; struct connected *connected; struct prefix_ipv4 address; if (!rip) return -1; for (ALL_LIST_ELEMENTS(ifp->connected, node, nnode, connected)) { struct prefix *p; struct route_node *n; p = connected->address; if (p->family == AF_INET) { address.family = AF_INET; address.prefix = p->u.prefix4; address.prefixlen = IPV4_MAX_BITLEN; n = route_node_match(rip->enable_network, (struct prefix *)&address); if (n) { route_unlock_node(n); return 1; } } } return -1; } /* Check wether connected is within the ripng_enable_network table. */ static int rip_enable_network_lookup2(struct connected *connected) { struct rip_interface *ri = connected->ifp->info; struct rip *rip = ri->rip; struct prefix_ipv4 address; struct prefix *p; p = connected->address; if (p->family == AF_INET) { struct route_node *node; address.family = p->family; address.prefix = p->u.prefix4; address.prefixlen = IPV4_MAX_BITLEN; /* LPM on p->family, p->u.prefix4/IPV4_MAX_BITLEN within * rip->enable_network */ node = route_node_match(rip->enable_network, (struct prefix *)&address); if (node) { route_unlock_node(node); return 1; } } return -1; } /* Add RIP enable network. */ int rip_enable_network_add(struct rip *rip, struct prefix *p) { struct route_node *node; node = route_node_get(rip->enable_network, p); if (node->info) { route_unlock_node(node); return NB_ERR_INCONSISTENCY; } else node->info = (void *)1; /* XXX: One should find a better solution than a generic one */ rip_enable_apply_all(rip); return NB_OK; } /* Delete RIP enable network. */ int rip_enable_network_delete(struct rip *rip, struct prefix *p) { struct route_node *node; node = route_node_lookup(rip->enable_network, p); if (node) { node->info = NULL; /* Unlock info lock. */ route_unlock_node(node); /* Unlock lookup lock. */ route_unlock_node(node); /* XXX: One should find a better solution than a generic one */ rip_enable_apply_all(rip); return NB_OK; } return NB_ERR_INCONSISTENCY; } /* Check interface is enabled by ifname statement. */ static int rip_enable_if_lookup(struct rip *rip, const char *ifname) { unsigned int i; char *str; if (!rip) return -1; for (i = 0; i < vector_active(rip->enable_interface); i++) if ((str = vector_slot(rip->enable_interface, i)) != NULL) if (strcmp(str, ifname) == 0) return i; return -1; } /* Add interface to rip_enable_if. */ int rip_enable_if_add(struct rip *rip, const char *ifname) { int ret; ret = rip_enable_if_lookup(rip, ifname); if (ret >= 0) return NB_ERR_INCONSISTENCY; vector_set(rip->enable_interface, XSTRDUP(MTYPE_RIP_INTERFACE_STRING, ifname)); rip_enable_apply_all(rip); /* TODOVJ */ return NB_OK; } /* Delete interface from rip_enable_if. */ int rip_enable_if_delete(struct rip *rip, const char *ifname) { int index; char *str; index = rip_enable_if_lookup(rip, ifname); if (index < 0) return NB_ERR_INCONSISTENCY; str = vector_slot(rip->enable_interface, index); XFREE(MTYPE_RIP_INTERFACE_STRING, str); vector_unset(rip->enable_interface, index); rip_enable_apply_all(rip); /* TODOVJ */ return NB_OK; } /* Join to multicast group and send request to the interface. */ static int rip_interface_wakeup(struct thread *t) { struct interface *ifp; struct rip_interface *ri; /* Get interface. */ ifp = THREAD_ARG(t); ri = ifp->info; ri->t_wakeup = NULL; /* Join to multicast group. */ if (rip_multicast_join(ifp, ri->rip->sock) < 0) { flog_err_sys(EC_LIB_SOCKET, "multicast join failed, interface %s not running", ifp->name); return 0; } /* Set running flag. */ ri->running = 1; /* Send RIP request to the interface. */ rip_request_interface(ifp); return 0; } static void rip_connect_set(struct interface *ifp, int set) { struct rip_interface *ri = ifp->info; struct rip *rip = ri->rip; struct listnode *node, *nnode; struct connected *connected; struct prefix_ipv4 address; struct nexthop nh; memset(&nh, 0, sizeof(nh)); for (ALL_LIST_ELEMENTS(ifp->connected, node, nnode, connected)) { struct prefix *p; p = connected->address; if (p->family != AF_INET) continue; address.family = AF_INET; address.prefix = p->u.prefix4; address.prefixlen = p->prefixlen; apply_mask_ipv4(&address); nh.ifindex = connected->ifp->ifindex; nh.type = NEXTHOP_TYPE_IFINDEX; if (set) { /* Check once more wether this prefix is within a * "network IF_OR_PREF" one */ if ((rip_enable_if_lookup(rip, connected->ifp->name) >= 0) || (rip_enable_network_lookup2(connected) >= 0)) rip_redistribute_add(rip, ZEBRA_ROUTE_CONNECT, RIP_ROUTE_INTERFACE, &address, &nh, 0, 0, 0); } else { rip_redistribute_delete(rip, ZEBRA_ROUTE_CONNECT, RIP_ROUTE_INTERFACE, &address, connected->ifp->ifindex); if (rip_redistribute_check(rip, ZEBRA_ROUTE_CONNECT)) rip_redistribute_add(rip, ZEBRA_ROUTE_CONNECT, RIP_ROUTE_REDISTRIBUTE, &address, &nh, 0, 0, 0); } } } /* Update interface status. */ void rip_enable_apply(struct interface *ifp) { int ret; struct rip_interface *ri = NULL; /* Check interface. */ if (!if_is_operative(ifp)) return; ri = ifp->info; /* Check network configuration. */ ret = rip_enable_network_lookup_if(ifp); /* If the interface is matched. */ if (ret > 0) ri->enable_network = 1; else ri->enable_network = 0; /* Check interface name configuration. */ ret = rip_enable_if_lookup(ri->rip, ifp->name); if (ret >= 0) ri->enable_interface = 1; else ri->enable_interface = 0; /* any interface MUST have an IPv4 address */ if (!rip_if_ipv4_address_check(ifp)) { ri->enable_network = 0; ri->enable_interface = 0; } /* Update running status of the interface. */ if (ri->enable_network || ri->enable_interface) { if (IS_RIP_DEBUG_EVENT) zlog_debug("turn on %s", ifp->name); /* Add interface wake up thread. */ thread_add_timer(master, rip_interface_wakeup, ifp, 1, &ri->t_wakeup); rip_connect_set(ifp, 1); } else if (ri->running) { /* Might as well clean up the route table as well * rip_if_down sets to 0 ri->running, and displays "turn *off %s" **/ rip_if_down(ifp); rip_connect_set(ifp, 0); } } /* Apply network configuration to all interface. */ static void rip_enable_apply_all(struct rip *rip) { struct interface *ifp; /* Check each interface. */ FOR_ALL_INTERFACES (rip->vrf, ifp) rip_enable_apply(ifp); } int rip_neighbor_lookup(struct rip *rip, struct sockaddr_in *from) { struct prefix_ipv4 p; struct route_node *node; memset(&p, 0, sizeof(struct prefix_ipv4)); p.family = AF_INET; p.prefix = from->sin_addr; p.prefixlen = IPV4_MAX_BITLEN; node = route_node_lookup(rip->neighbor, (struct prefix *)&p); if (node) { route_unlock_node(node); return 1; } return 0; } /* Add new RIP neighbor to the neighbor tree. */ int rip_neighbor_add(struct rip *rip, struct prefix_ipv4 *p) { struct route_node *node; node = route_node_get(rip->neighbor, (struct prefix *)p); if (node->info) return NB_ERR_INCONSISTENCY; node->info = rip->neighbor; return NB_OK; } /* Delete RIP neighbor from the neighbor tree. */ int rip_neighbor_delete(struct rip *rip, struct prefix_ipv4 *p) { struct route_node *node; /* Lock for look up. */ node = route_node_lookup(rip->neighbor, (struct prefix *)p); if (!node) return NB_ERR_INCONSISTENCY; node->info = NULL; /* Unlock lookup lock. */ route_unlock_node(node); /* Unlock real neighbor information lock. */ route_unlock_node(node); return NB_OK; } /* Clear all network and neighbor configuration. */ void rip_clean_network(struct rip *rip) { unsigned int i; char *str; struct route_node *rn; /* rip->enable_network. */ for (rn = route_top(rip->enable_network); rn; rn = route_next(rn)) if (rn->info) { rn->info = NULL; route_unlock_node(rn); } /* rip->enable_interface. */ for (i = 0; i < vector_active(rip->enable_interface); i++) if ((str = vector_slot(rip->enable_interface, i)) != NULL) { XFREE(MTYPE_RIP_INTERFACE_STRING, str); vector_slot(rip->enable_interface, i) = NULL; } } /* Utility function for looking up passive interface settings. */ static int rip_passive_nondefault_lookup(struct rip *rip, const char *ifname) { unsigned int i; char *str; for (i = 0; i < vector_active(rip->passive_nondefault); i++) if ((str = vector_slot(rip->passive_nondefault, i)) != NULL) if (strcmp(str, ifname) == 0) return i; return -1; } static void rip_passive_interface_apply(struct interface *ifp) { struct rip *rip; struct rip_interface *ri; ri = ifp->info; rip = ri->rip; if (rip == NULL) return; ri->passive = ((rip_passive_nondefault_lookup(rip, ifp->name) < 0) ? rip->passive_default : !rip->passive_default); if (IS_RIP_DEBUG_ZEBRA) zlog_debug("interface %s: passive = %d", ifp->name, ri->passive); } static void rip_passive_interface_apply_all(struct rip *rip) { struct interface *ifp; FOR_ALL_INTERFACES (rip->vrf, ifp) rip_passive_interface_apply(ifp); } /* Passive interface. */ int rip_passive_nondefault_set(struct rip *rip, const char *ifname) { if (rip_passive_nondefault_lookup(rip, ifname) >= 0) /* * Don't return an error, this can happen after changing * 'passive-default'. */ return NB_OK; vector_set(rip->passive_nondefault, XSTRDUP(MTYPE_RIP_INTERFACE_STRING, ifname)); rip_passive_interface_apply_all(rip); return NB_OK; } int rip_passive_nondefault_unset(struct rip *rip, const char *ifname) { int i; char *str; i = rip_passive_nondefault_lookup(rip, ifname); if (i < 0) /* * Don't return an error, this can happen after changing * 'passive-default'. */ return NB_OK; str = vector_slot(rip->passive_nondefault, i); XFREE(MTYPE_RIP_INTERFACE_STRING, str); vector_unset(rip->passive_nondefault, i); rip_passive_interface_apply_all(rip); return NB_OK; } /* Free all configured RIP passive-interface settings. */ void rip_passive_nondefault_clean(struct rip *rip) { unsigned int i; char *str; for (i = 0; i < vector_active(rip->passive_nondefault); i++) if ((str = vector_slot(rip->passive_nondefault, i)) != NULL) { XFREE(MTYPE_RIP_INTERFACE_STRING, str); vector_slot(rip->passive_nondefault, i) = NULL; } rip_passive_interface_apply_all(rip); } /* Write rip configuration of each interface. */ static int rip_interface_config_write(struct vty *vty) { struct vrf *vrf; int write = 0; RB_FOREACH (vrf, vrf_name_head, &vrfs_by_name) { struct interface *ifp; FOR_ALL_INTERFACES (vrf, ifp) { struct lyd_node *dnode; dnode = yang_dnode_get( running_config->dnode, "/frr-interface:lib/interface[name='%s'][vrf='%s']", ifp->name, vrf->name); if (dnode == NULL) continue; write = 1; nb_cli_show_dnode_cmds(vty, dnode, false); } } return write; } int rip_show_network_config(struct vty *vty, struct rip *rip) { unsigned int i; char *ifname; struct route_node *node; /* Network type RIP enable interface statement. */ for (node = route_top(rip->enable_network); node; node = route_next(node)) if (node->info) vty_out(vty, " %s/%u\n", inet_ntoa(node->p.u.prefix4), node->p.prefixlen); /* Interface name RIP enable statement. */ for (i = 0; i < vector_active(rip->enable_interface); i++) if ((ifname = vector_slot(rip->enable_interface, i)) != NULL) vty_out(vty, " %s\n", ifname); /* RIP neighbors listing. */ for (node = route_top(rip->neighbor); node; node = route_next(node)) if (node->info) vty_out(vty, " %s\n", inet_ntoa(node->p.u.prefix4)); return 0; } static struct cmd_node interface_node = { INTERFACE_NODE, "%s(config-if)# ", 1, }; void rip_interface_sync(struct interface *ifp) { struct vrf *vrf; vrf = vrf_lookup_by_id(ifp->vrf_id); if (vrf) { struct rip_interface *ri; ri = ifp->info; if (ri) ri->rip = vrf->info; } } /* Called when interface structure allocated. */ static int rip_interface_new_hook(struct interface *ifp) { ifp->info = rip_interface_new(); rip_interface_sync(ifp); return 0; } /* Called when interface structure deleted. */ static int rip_interface_delete_hook(struct interface *ifp) { rip_interface_reset(ifp->info); XFREE(MTYPE_RIP_INTERFACE, ifp->info); ifp->info = NULL; return 0; } /* Allocate and initialize interface vector. */ void rip_if_init(void) { /* Default initial size of interface vector. */ hook_register_prio(if_add, 0, rip_interface_new_hook); hook_register_prio(if_del, 0, rip_interface_delete_hook); /* Install interface node. */ install_node(&interface_node, rip_interface_config_write); if_cmd_init(); } frr-7.2.1/ripd/rip_interface.h0000644000000000000000000000303713610377563013140 00000000000000/* RIP interface routines * * This file is part of Quagga * * Quagga is free software; you can redistribute it and/or modify it * under the terms of the GNU General Public License as published by the * Free Software Foundation; either version 2, or (at your option) any * later version. * * Quagga 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; see the file COPYING; if not, write to the Free Software * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA */ #ifndef _QUAGGA_RIP_INTERFACE_H #define _QUAGGA_RIP_INTERFACE_H #include "memory.h" #include "zclient.h" DECLARE_MTYPE(RIP_INTERFACE_STRING) extern int rip_interface_down(int, struct zclient *, zebra_size_t, vrf_id_t); extern int rip_interface_up(int, struct zclient *, zebra_size_t, vrf_id_t); extern int rip_interface_add(int, struct zclient *, zebra_size_t, vrf_id_t); extern int rip_interface_delete(int, struct zclient *, zebra_size_t, vrf_id_t); extern int rip_interface_address_add(int, struct zclient *, zebra_size_t, vrf_id_t); extern int rip_interface_address_delete(int, struct zclient *, zebra_size_t, vrf_id_t); extern int rip_interface_vrf_update(ZAPI_CALLBACK_ARGS); extern void rip_interface_sync(struct interface *ifp); #endif /* _QUAGGA_RIP_INTERFACE_H */ frr-7.2.1/ripd/rip_main.c0000644000000000000000000000723413610377563012122 00000000000000/* RIPd main routine. * Copyright (C) 1997, 98 Kunihiro Ishiguro * * This file is part of GNU Zebra. * * GNU Zebra is free software; you can redistribute it and/or modify it * under the terms of the GNU General Public License as published by the * Free Software Foundation; either version 2, or (at your option) any * later version. * * GNU Zebra 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; see the file COPYING; if not, write to the Free Software * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA */ #include #include #include "getopt.h" #include "thread.h" #include "command.h" #include "memory.h" #include "memory_vty.h" #include "prefix.h" #include "filter.h" #include "keychain.h" #include "log.h" #include "privs.h" #include "sigevent.h" #include "zclient.h" #include "vrf.h" #include "if_rmap.h" #include "libfrr.h" #include "ripd/ripd.h" #include "ripd/rip_errors.h" /* ripd options. */ static struct option longopts[] = {{0}}; /* ripd privileges */ zebra_capabilities_t _caps_p[] = {ZCAP_NET_RAW, ZCAP_BIND, ZCAP_SYS_ADMIN}; struct zebra_privs_t ripd_privs = { #if defined(FRR_USER) .user = FRR_USER, #endif #if defined FRR_GROUP .group = FRR_GROUP, #endif #ifdef VTY_GROUP .vty_group = VTY_GROUP, #endif .caps_p = _caps_p, .cap_num_p = array_size(_caps_p), .cap_num_i = 0}; /* Master of threads. */ struct thread_master *master; static struct frr_daemon_info ripd_di; /* SIGHUP handler. */ static void sighup(void) { zlog_info("SIGHUP received"); /* Reload config file. */ vty_read_config(NULL, ripd_di.config_file, config_default); } /* SIGINT handler. */ static void sigint(void) { zlog_notice("Terminating on signal"); rip_vrf_terminate(); if_rmap_terminate(); rip_zclient_stop(); frr_fini(); exit(0); } /* SIGUSR1 handler. */ static void sigusr1(void) { zlog_rotate(); } static struct quagga_signal_t ripd_signals[] = { { .signal = SIGHUP, .handler = &sighup, }, { .signal = SIGUSR1, .handler = &sigusr1, }, { .signal = SIGINT, .handler = &sigint, }, { .signal = SIGTERM, .handler = &sigint, }, }; static const struct frr_yang_module_info *ripd_yang_modules[] = { &frr_interface_info, &frr_ripd_info, }; FRR_DAEMON_INFO(ripd, RIP, .vty_port = RIP_VTY_PORT, .proghelp = "Implementation of the RIP routing protocol.", .signals = ripd_signals, .n_signals = array_size(ripd_signals), .privs = &ripd_privs, .yang_modules = ripd_yang_modules, .n_yang_modules = array_size(ripd_yang_modules), ) #define DEPRECATED_OPTIONS "" /* Main routine of ripd. */ int main(int argc, char **argv) { frr_preinit(&ripd_di, argc, argv); frr_opt_add("" DEPRECATED_OPTIONS, longopts, ""); /* Command line option parse. */ while (1) { int opt; opt = frr_getopt(argc, argv, NULL); if (opt && opt < 128 && strchr(DEPRECATED_OPTIONS, opt)) { fprintf(stderr, "The -%c option no longer exists.\nPlease refer to the manual.\n", opt); continue; } if (opt == EOF) break; switch (opt) { case 0: break; default: frr_help_exit(1); break; } } /* Prepare master thread. */ master = frr_init(); /* Library initialization. */ rip_error_init(); keychain_init(); rip_vrf_init(); /* RIP related initialization. */ rip_init(); rip_if_init(); rip_cli_init(); rip_zclient_init(master); frr_config_fork(); frr_run(master); /* Not reached. */ return (0); } frr-7.2.1/ripd/rip_northbound.c0000644000000000000000000012500213610377563013352 00000000000000/* * Copyright (C) 1997, 1998, 1999 Kunihiro Ishiguro * Copyright (C) 2018 NetDEF, Inc. * Renato Westphal * * This program is free software; you can redistribute it and/or modify it * under the terms of the 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; see the file COPYING; if not, write to the Free Software * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA */ #include #include "if.h" #include "vrf.h" #include "log.h" #include "prefix.h" #include "table.h" #include "command.h" #include "routemap.h" #include "northbound.h" #include "libfrr.h" #include "ripd/ripd.h" #include "ripd/rip_debug.h" #include "ripd/rip_cli.h" #include "ripd/rip_interface.h" /* * XPath: /frr-ripd:ripd/instance */ static int ripd_instance_create(enum nb_event event, const struct lyd_node *dnode, union nb_resource *resource) { struct rip *rip; struct vrf *vrf; const char *vrf_name; int socket; vrf_name = yang_dnode_get_string(dnode, "./vrf"); vrf = vrf_lookup_by_name(vrf_name); /* * Try to create a RIP socket only if the VRF is enabled, otherwise * create a disabled RIP instance and wait for the VRF to be enabled. */ switch (event) { case NB_EV_VALIDATE: break; case NB_EV_PREPARE: if (!vrf || !vrf_is_enabled(vrf)) break; socket = rip_create_socket(vrf); if (socket < 0) return NB_ERR_RESOURCE; resource->fd = socket; break; case NB_EV_ABORT: if (!vrf || !vrf_is_enabled(vrf)) break; socket = resource->fd; close(socket); break; case NB_EV_APPLY: if (vrf && vrf_is_enabled(vrf)) socket = resource->fd; else socket = -1; rip = rip_create(vrf_name, vrf, socket); nb_running_set_entry(dnode, rip); break; } return NB_OK; } static int ripd_instance_destroy(enum nb_event event, const struct lyd_node *dnode) { struct rip *rip; if (event != NB_EV_APPLY) return NB_OK; rip = nb_running_unset_entry(dnode); rip_clean(rip); return NB_OK; } static const void *ripd_instance_get_next(const void *parent_list_entry, const void *list_entry) { struct rip *rip = (struct rip *)list_entry; if (list_entry == NULL) rip = RB_MIN(rip_instance_head, &rip_instances); else rip = RB_NEXT(rip_instance_head, rip); return rip; } static int ripd_instance_get_keys(const void *list_entry, struct yang_list_keys *keys) { const struct rip *rip = list_entry; keys->num = 1; strlcpy(keys->key[0], rip->vrf_name, sizeof(keys->key[0])); return NB_OK; } static const void *ripd_instance_lookup_entry(const void *parent_list_entry, const struct yang_list_keys *keys) { const char *vrf_name = keys->key[0]; return rip_lookup_by_vrf_name(vrf_name); } /* * XPath: /frr-ripd:ripd/instance/allow-ecmp */ static int ripd_instance_allow_ecmp_modify(enum nb_event event, const struct lyd_node *dnode, union nb_resource *resource) { struct rip *rip; if (event != NB_EV_APPLY) return NB_OK; rip = nb_running_get_entry(dnode, NULL, true); rip->ecmp = yang_dnode_get_bool(dnode, NULL); if (!rip->ecmp) rip_ecmp_disable(rip); return NB_OK; } /* * XPath: /frr-ripd:ripd/instance/default-information-originate */ static int ripd_instance_default_information_originate_modify(enum nb_event event, const struct lyd_node *dnode, union nb_resource *resource) { struct rip *rip; bool default_information; struct prefix_ipv4 p; if (event != NB_EV_APPLY) return NB_OK; rip = nb_running_get_entry(dnode, NULL, true); default_information = yang_dnode_get_bool(dnode, NULL); memset(&p, 0, sizeof(struct prefix_ipv4)); p.family = AF_INET; if (default_information) { struct nexthop nh; memset(&nh, 0, sizeof(nh)); nh.type = NEXTHOP_TYPE_IPV4; rip_redistribute_add(rip, ZEBRA_ROUTE_RIP, RIP_ROUTE_DEFAULT, &p, &nh, 0, 0, 0); } else { rip_redistribute_delete(rip, ZEBRA_ROUTE_RIP, RIP_ROUTE_DEFAULT, &p, 0); } return NB_OK; } /* * XPath: /frr-ripd:ripd/instance/default-metric */ static int ripd_instance_default_metric_modify(enum nb_event event, const struct lyd_node *dnode, union nb_resource *resource) { struct rip *rip; if (event != NB_EV_APPLY) return NB_OK; rip = nb_running_get_entry(dnode, NULL, true); rip->default_metric = yang_dnode_get_uint8(dnode, NULL); /* rip_update_default_metric (); */ return NB_OK; } /* * XPath: /frr-ripd:ripd/instance/distance/default */ static int ripd_instance_distance_default_modify(enum nb_event event, const struct lyd_node *dnode, union nb_resource *resource) { struct rip *rip; if (event != NB_EV_APPLY) return NB_OK; rip = nb_running_get_entry(dnode, NULL, true); rip->distance = yang_dnode_get_uint8(dnode, NULL); return NB_OK; } /* * XPath: /frr-ripd:ripd/instance/distance/source */ static int ripd_instance_distance_source_create(enum nb_event event, const struct lyd_node *dnode, union nb_resource *resource) { struct rip *rip; struct prefix_ipv4 prefix; struct route_node *rn; if (event != NB_EV_APPLY) return NB_OK; yang_dnode_get_ipv4p(&prefix, dnode, "./prefix"); apply_mask_ipv4(&prefix); /* Get RIP distance node. */ rip = nb_running_get_entry(dnode, NULL, true); rn = route_node_get(rip->distance_table, (struct prefix *)&prefix); rn->info = rip_distance_new(); nb_running_set_entry(dnode, rn); return NB_OK; } static int ripd_instance_distance_source_destroy(enum nb_event event, const struct lyd_node *dnode) { struct route_node *rn; struct rip_distance *rdistance; if (event != NB_EV_APPLY) return NB_OK; rn = nb_running_unset_entry(dnode); rdistance = rn->info; rip_distance_free(rdistance); rn->info = NULL; route_unlock_node(rn); return NB_OK; } /* * XPath: /frr-ripd:ripd/instance/distance/source/distance */ static int ripd_instance_distance_source_distance_modify(enum nb_event event, const struct lyd_node *dnode, union nb_resource *resource) { struct route_node *rn; uint8_t distance; struct rip_distance *rdistance; if (event != NB_EV_APPLY) return NB_OK; /* Set distance value. */ rn = nb_running_get_entry(dnode, NULL, true); distance = yang_dnode_get_uint8(dnode, NULL); rdistance = rn->info; rdistance->distance = distance; return NB_OK; } /* * XPath: /frr-ripd:ripd/instance/distance/source/access-list */ static int ripd_instance_distance_source_access_list_modify(enum nb_event event, const struct lyd_node *dnode, union nb_resource *resource) { const char *acl_name; struct route_node *rn; struct rip_distance *rdistance; if (event != NB_EV_APPLY) return NB_OK; acl_name = yang_dnode_get_string(dnode, NULL); /* Set access-list */ rn = nb_running_get_entry(dnode, NULL, true); rdistance = rn->info; if (rdistance->access_list) free(rdistance->access_list); rdistance->access_list = strdup(acl_name); return NB_OK; } static int ripd_instance_distance_source_access_list_destroy(enum nb_event event, const struct lyd_node *dnode) { struct route_node *rn; struct rip_distance *rdistance; if (event != NB_EV_APPLY) return NB_OK; /* Reset access-list configuration. */ rn = nb_running_get_entry(dnode, NULL, true); rdistance = rn->info; free(rdistance->access_list); rdistance->access_list = NULL; return NB_OK; } /* * XPath: /frr-ripd:ripd/instance/explicit-neighbor */ static int ripd_instance_explicit_neighbor_create(enum nb_event event, const struct lyd_node *dnode, union nb_resource *resource) { struct rip *rip; struct prefix_ipv4 p; if (event != NB_EV_APPLY) return NB_OK; rip = nb_running_get_entry(dnode, NULL, true); p.family = AF_INET; p.prefixlen = IPV4_MAX_BITLEN; yang_dnode_get_ipv4(&p.prefix, dnode, NULL); return rip_neighbor_add(rip, &p); } static int ripd_instance_explicit_neighbor_destroy(enum nb_event event, const struct lyd_node *dnode) { struct rip *rip; struct prefix_ipv4 p; if (event != NB_EV_APPLY) return NB_OK; rip = nb_running_get_entry(dnode, NULL, true); p.family = AF_INET; p.prefixlen = IPV4_MAX_BITLEN; yang_dnode_get_ipv4(&p.prefix, dnode, NULL); return rip_neighbor_delete(rip, &p); } /* * XPath: /frr-ripd:ripd/instance/network */ static int ripd_instance_network_create(enum nb_event event, const struct lyd_node *dnode, union nb_resource *resource) { struct rip *rip; struct prefix p; if (event != NB_EV_APPLY) return NB_OK; rip = nb_running_get_entry(dnode, NULL, true); yang_dnode_get_ipv4p(&p, dnode, NULL); apply_mask_ipv4((struct prefix_ipv4 *)&p); return rip_enable_network_add(rip, &p); } static int ripd_instance_network_destroy(enum nb_event event, const struct lyd_node *dnode) { struct rip *rip; struct prefix p; if (event != NB_EV_APPLY) return NB_OK; rip = nb_running_get_entry(dnode, NULL, true); yang_dnode_get_ipv4p(&p, dnode, NULL); apply_mask_ipv4((struct prefix_ipv4 *)&p); return rip_enable_network_delete(rip, &p); } /* * XPath: /frr-ripd:ripd/instance/interface */ static int ripd_instance_interface_create(enum nb_event event, const struct lyd_node *dnode, union nb_resource *resource) { struct rip *rip; const char *ifname; if (event != NB_EV_APPLY) return NB_OK; rip = nb_running_get_entry(dnode, NULL, true); ifname = yang_dnode_get_string(dnode, NULL); return rip_enable_if_add(rip, ifname); } static int ripd_instance_interface_destroy(enum nb_event event, const struct lyd_node *dnode) { struct rip *rip; const char *ifname; if (event != NB_EV_APPLY) return NB_OK; rip = nb_running_get_entry(dnode, NULL, true); ifname = yang_dnode_get_string(dnode, NULL); return rip_enable_if_delete(rip, ifname); } /* * XPath: /frr-ripd:ripd/instance/offset-list */ static int ripd_instance_offset_list_create(enum nb_event event, const struct lyd_node *dnode, union nb_resource *resource) { struct rip *rip; const char *ifname; struct rip_offset_list *offset; if (event != NB_EV_APPLY) return NB_OK; rip = nb_running_get_entry(dnode, NULL, true); ifname = yang_dnode_get_string(dnode, "./interface"); offset = rip_offset_list_new(rip, ifname); nb_running_set_entry(dnode, offset); return NB_OK; } static int ripd_instance_offset_list_destroy(enum nb_event event, const struct lyd_node *dnode) { int direct; struct rip_offset_list *offset; if (event != NB_EV_APPLY) return NB_OK; direct = yang_dnode_get_enum(dnode, "./direction"); offset = nb_running_unset_entry(dnode); if (offset->direct[direct].alist_name) { free(offset->direct[direct].alist_name); offset->direct[direct].alist_name = NULL; } if (offset->direct[RIP_OFFSET_LIST_IN].alist_name == NULL && offset->direct[RIP_OFFSET_LIST_OUT].alist_name == NULL) offset_list_del(offset); return NB_OK; } /* * XPath: /frr-ripd:ripd/instance/offset-list/access-list */ static int ripd_instance_offset_list_access_list_modify(enum nb_event event, const struct lyd_node *dnode, union nb_resource *resource) { int direct; struct rip_offset_list *offset; const char *alist_name; if (event != NB_EV_APPLY) return NB_OK; direct = yang_dnode_get_enum(dnode, "../direction"); alist_name = yang_dnode_get_string(dnode, NULL); offset = nb_running_get_entry(dnode, NULL, true); if (offset->direct[direct].alist_name) free(offset->direct[direct].alist_name); offset->direct[direct].alist_name = strdup(alist_name); return NB_OK; } /* * XPath: /frr-ripd:ripd/instance/offset-list/metric */ static int ripd_instance_offset_list_metric_modify(enum nb_event event, const struct lyd_node *dnode, union nb_resource *resource) { int direct; uint8_t metric; struct rip_offset_list *offset; if (event != NB_EV_APPLY) return NB_OK; direct = yang_dnode_get_enum(dnode, "../direction"); metric = yang_dnode_get_uint8(dnode, NULL); offset = nb_running_get_entry(dnode, NULL, true); offset->direct[direct].metric = metric; return NB_OK; } /* * XPath: /frr-ripd:ripd/instance/passive-default */ static int ripd_instance_passive_default_modify(enum nb_event event, const struct lyd_node *dnode, union nb_resource *resource) { struct rip *rip; if (event != NB_EV_APPLY) return NB_OK; rip = nb_running_get_entry(dnode, NULL, true); rip->passive_default = yang_dnode_get_bool(dnode, NULL); rip_passive_nondefault_clean(rip); return NB_OK; } /* * XPath: /frr-ripd:ripd/instance/passive-interface */ static int ripd_instance_passive_interface_create(enum nb_event event, const struct lyd_node *dnode, union nb_resource *resource) { struct rip *rip; const char *ifname; if (event != NB_EV_APPLY) return NB_OK; rip = nb_running_get_entry(dnode, NULL, true); ifname = yang_dnode_get_string(dnode, NULL); return rip_passive_nondefault_set(rip, ifname); } static int ripd_instance_passive_interface_destroy(enum nb_event event, const struct lyd_node *dnode) { struct rip *rip; const char *ifname; if (event != NB_EV_APPLY) return NB_OK; rip = nb_running_get_entry(dnode, NULL, true); ifname = yang_dnode_get_string(dnode, NULL); return rip_passive_nondefault_unset(rip, ifname); } /* * XPath: /frr-ripd:ripd/instance/non-passive-interface */ static int ripd_instance_non_passive_interface_create(enum nb_event event, const struct lyd_node *dnode, union nb_resource *resource) { struct rip *rip; const char *ifname; if (event != NB_EV_APPLY) return NB_OK; rip = nb_running_get_entry(dnode, NULL, true); ifname = yang_dnode_get_string(dnode, NULL); return rip_passive_nondefault_unset(rip, ifname); } static int ripd_instance_non_passive_interface_destroy(enum nb_event event, const struct lyd_node *dnode) { struct rip *rip; const char *ifname; if (event != NB_EV_APPLY) return NB_OK; rip = nb_running_get_entry(dnode, NULL, true); ifname = yang_dnode_get_string(dnode, NULL); return rip_passive_nondefault_set(rip, ifname); } /* * XPath: /frr-ripd:ripd/instance/redistribute */ static int ripd_instance_redistribute_create(enum nb_event event, const struct lyd_node *dnode, union nb_resource *resource) { struct rip *rip; int type; if (event != NB_EV_APPLY) return NB_OK; rip = nb_running_get_entry(dnode, NULL, true); type = yang_dnode_get_enum(dnode, "./protocol"); rip->redist[type].enabled = true; return NB_OK; } static int ripd_instance_redistribute_destroy(enum nb_event event, const struct lyd_node *dnode) { struct rip *rip; int type; if (event != NB_EV_APPLY) return NB_OK; rip = nb_running_get_entry(dnode, NULL, true); type = yang_dnode_get_enum(dnode, "./protocol"); rip->redist[type].enabled = false; if (rip->redist[type].route_map.name) { free(rip->redist[type].route_map.name); rip->redist[type].route_map.name = NULL; rip->redist[type].route_map.map = NULL; } rip->redist[type].metric_config = false; rip->redist[type].metric = 0; if (rip->enabled) rip_redistribute_conf_delete(rip, type); return NB_OK; } static void ripd_instance_redistribute_apply_finish(const struct lyd_node *dnode) { struct rip *rip; int type; rip = nb_running_get_entry(dnode, NULL, true); type = yang_dnode_get_enum(dnode, "./protocol"); if (rip->enabled) rip_redistribute_conf_update(rip, type); } /* * XPath: /frr-ripd:ripd/instance/redistribute/route-map */ static int ripd_instance_redistribute_route_map_modify(enum nb_event event, const struct lyd_node *dnode, union nb_resource *resource) { struct rip *rip; int type; const char *rmap_name; if (event != NB_EV_APPLY) return NB_OK; rip = nb_running_get_entry(dnode, NULL, true); type = yang_dnode_get_enum(dnode, "../protocol"); rmap_name = yang_dnode_get_string(dnode, NULL); if (rip->redist[type].route_map.name) free(rip->redist[type].route_map.name); rip->redist[type].route_map.name = strdup(rmap_name); rip->redist[type].route_map.map = route_map_lookup_by_name(rmap_name); return NB_OK; } static int ripd_instance_redistribute_route_map_destroy(enum nb_event event, const struct lyd_node *dnode) { struct rip *rip; int type; if (event != NB_EV_APPLY) return NB_OK; rip = nb_running_get_entry(dnode, NULL, true); type = yang_dnode_get_enum(dnode, "../protocol"); free(rip->redist[type].route_map.name); rip->redist[type].route_map.name = NULL; rip->redist[type].route_map.map = NULL; return NB_OK; } /* * XPath: /frr-ripd:ripd/instance/redistribute/metric */ static int ripd_instance_redistribute_metric_modify(enum nb_event event, const struct lyd_node *dnode, union nb_resource *resource) { struct rip *rip; int type; uint8_t metric; if (event != NB_EV_APPLY) return NB_OK; rip = nb_running_get_entry(dnode, NULL, true); type = yang_dnode_get_enum(dnode, "../protocol"); metric = yang_dnode_get_uint8(dnode, NULL); rip->redist[type].metric_config = true; rip->redist[type].metric = metric; return NB_OK; } static int ripd_instance_redistribute_metric_destroy(enum nb_event event, const struct lyd_node *dnode) { struct rip *rip; int type; if (event != NB_EV_APPLY) return NB_OK; rip = nb_running_get_entry(dnode, NULL, true); type = yang_dnode_get_enum(dnode, "../protocol"); rip->redist[type].metric_config = false; rip->redist[type].metric = 0; return NB_OK; } /* * XPath: /frr-ripd:ripd/instance/static-route */ static int ripd_instance_static_route_create(enum nb_event event, const struct lyd_node *dnode, union nb_resource *resource) { struct rip *rip; struct nexthop nh; struct prefix_ipv4 p; if (event != NB_EV_APPLY) return NB_OK; rip = nb_running_get_entry(dnode, NULL, true); yang_dnode_get_ipv4p(&p, dnode, NULL); apply_mask_ipv4(&p); memset(&nh, 0, sizeof(nh)); nh.type = NEXTHOP_TYPE_IPV4; rip_redistribute_add(rip, ZEBRA_ROUTE_RIP, RIP_ROUTE_STATIC, &p, &nh, 0, 0, 0); return NB_OK; } static int ripd_instance_static_route_destroy(enum nb_event event, const struct lyd_node *dnode) { struct rip *rip; struct prefix_ipv4 p; if (event != NB_EV_APPLY) return NB_OK; rip = nb_running_get_entry(dnode, NULL, true); yang_dnode_get_ipv4p(&p, dnode, NULL); apply_mask_ipv4(&p); rip_redistribute_delete(rip, ZEBRA_ROUTE_RIP, RIP_ROUTE_STATIC, &p, 0); return NB_OK; } /* * XPath: /frr-ripd:ripd/instance/timers/ */ static void ripd_instance_timers_apply_finish(const struct lyd_node *dnode) { struct rip *rip; rip = nb_running_get_entry(dnode, NULL, true); /* Reset update timer thread. */ rip_event(rip, RIP_UPDATE_EVENT, 0); } /* * XPath: /frr-ripd:ripd/instance/timers/flush-interval */ static int ripd_instance_timers_flush_interval_modify(enum nb_event event, const struct lyd_node *dnode, union nb_resource *resource) { struct rip *rip; if (event != NB_EV_APPLY) return NB_OK; rip = nb_running_get_entry(dnode, NULL, true); rip->garbage_time = yang_dnode_get_uint32(dnode, NULL); return NB_OK; } /* * XPath: /frr-ripd:ripd/instance/timers/holddown-interval */ static int ripd_instance_timers_holddown_interval_modify(enum nb_event event, const struct lyd_node *dnode, union nb_resource *resource) { struct rip *rip; if (event != NB_EV_APPLY) return NB_OK; rip = nb_running_get_entry(dnode, NULL, true); rip->timeout_time = yang_dnode_get_uint32(dnode, NULL); return NB_OK; } /* * XPath: /frr-ripd:ripd/instance/timers/update-interval */ static int ripd_instance_timers_update_interval_modify(enum nb_event event, const struct lyd_node *dnode, union nb_resource *resource) { struct rip *rip; if (event != NB_EV_APPLY) return NB_OK; rip = nb_running_get_entry(dnode, NULL, true); rip->update_time = yang_dnode_get_uint32(dnode, NULL); return NB_OK; } /* * XPath: /frr-ripd:ripd/instance/version/receive */ static int ripd_instance_version_receive_modify(enum nb_event event, const struct lyd_node *dnode, union nb_resource *resource) { struct rip *rip; if (event != NB_EV_APPLY) return NB_OK; rip = nb_running_get_entry(dnode, NULL, true); rip->version_recv = yang_dnode_get_enum(dnode, NULL); return NB_OK; } /* * XPath: /frr-ripd:ripd/instance/version/send */ static int ripd_instance_version_send_modify(enum nb_event event, const struct lyd_node *dnode, union nb_resource *resource) { struct rip *rip; if (event != NB_EV_APPLY) return NB_OK; rip = nb_running_get_entry(dnode, NULL, true); rip->version_send = yang_dnode_get_enum(dnode, NULL); return NB_OK; } /* * XPath: /frr-interface:lib/interface/frr-ripd:rip/split-horizon */ static int lib_interface_rip_split_horizon_modify(enum nb_event event, const struct lyd_node *dnode, union nb_resource *resource) { struct interface *ifp; struct rip_interface *ri; if (event != NB_EV_APPLY) return NB_OK; ifp = nb_running_get_entry(dnode, NULL, true); ri = ifp->info; ri->split_horizon = yang_dnode_get_enum(dnode, NULL); return NB_OK; } /* * XPath: /frr-interface:lib/interface/frr-ripd:rip/v2-broadcast */ static int lib_interface_rip_v2_broadcast_modify(enum nb_event event, const struct lyd_node *dnode, union nb_resource *resource) { struct interface *ifp; struct rip_interface *ri; if (event != NB_EV_APPLY) return NB_OK; ifp = nb_running_get_entry(dnode, NULL, true); ri = ifp->info; ri->v2_broadcast = yang_dnode_get_bool(dnode, NULL); return NB_OK; } /* * XPath: /frr-interface:lib/interface/frr-ripd:rip/version-receive */ static int lib_interface_rip_version_receive_modify(enum nb_event event, const struct lyd_node *dnode, union nb_resource *resource) { struct interface *ifp; struct rip_interface *ri; if (event != NB_EV_APPLY) return NB_OK; ifp = nb_running_get_entry(dnode, NULL, true); ri = ifp->info; ri->ri_receive = yang_dnode_get_enum(dnode, NULL); return NB_OK; } /* * XPath: /frr-interface:lib/interface/frr-ripd:rip/version-send */ static int lib_interface_rip_version_send_modify(enum nb_event event, const struct lyd_node *dnode, union nb_resource *resource) { struct interface *ifp; struct rip_interface *ri; if (event != NB_EV_APPLY) return NB_OK; ifp = nb_running_get_entry(dnode, NULL, true); ri = ifp->info; ri->ri_send = yang_dnode_get_enum(dnode, NULL); return NB_OK; } /* * XPath: /frr-interface:lib/interface/frr-ripd:rip/authentication-scheme/mode */ static int lib_interface_rip_authentication_scheme_mode_modify( enum nb_event event, const struct lyd_node *dnode, union nb_resource *resource) { struct interface *ifp; struct rip_interface *ri; if (event != NB_EV_APPLY) return NB_OK; ifp = nb_running_get_entry(dnode, NULL, true); ri = ifp->info; ri->auth_type = yang_dnode_get_enum(dnode, NULL); return NB_OK; } /* * XPath: * /frr-interface:lib/interface/frr-ripd:rip/authentication-scheme/md5-auth-length */ static int lib_interface_rip_authentication_scheme_md5_auth_length_modify( enum nb_event event, const struct lyd_node *dnode, union nb_resource *resource) { struct interface *ifp; struct rip_interface *ri; if (event != NB_EV_APPLY) return NB_OK; ifp = nb_running_get_entry(dnode, NULL, true); ri = ifp->info; ri->md5_auth_len = yang_dnode_get_enum(dnode, NULL); return NB_OK; } static int lib_interface_rip_authentication_scheme_md5_auth_length_destroy( enum nb_event event, const struct lyd_node *dnode) { struct interface *ifp; struct rip_interface *ri; if (event != NB_EV_APPLY) return NB_OK; ifp = nb_running_get_entry(dnode, NULL, true); ri = ifp->info; ri->md5_auth_len = yang_get_default_enum( "%s/authentication-scheme/md5-auth-length", RIP_IFACE); return NB_OK; } /* * XPath: /frr-interface:lib/interface/frr-ripd:rip/authentication-password */ static int lib_interface_rip_authentication_password_modify(enum nb_event event, const struct lyd_node *dnode, union nb_resource *resource) { struct interface *ifp; struct rip_interface *ri; if (event != NB_EV_APPLY) return NB_OK; ifp = nb_running_get_entry(dnode, NULL, true); ri = ifp->info; XFREE(MTYPE_RIP_INTERFACE_STRING, ri->auth_str); ri->auth_str = XSTRDUP(MTYPE_RIP_INTERFACE_STRING, yang_dnode_get_string(dnode, NULL)); return NB_OK; } static int lib_interface_rip_authentication_password_destroy(enum nb_event event, const struct lyd_node *dnode) { struct interface *ifp; struct rip_interface *ri; if (event != NB_EV_APPLY) return NB_OK; ifp = nb_running_get_entry(dnode, NULL, true); ri = ifp->info; XFREE(MTYPE_RIP_INTERFACE_STRING, ri->auth_str); return NB_OK; } /* * XPath: /frr-interface:lib/interface/frr-ripd:rip/authentication-key-chain */ static int lib_interface_rip_authentication_key_chain_modify(enum nb_event event, const struct lyd_node *dnode, union nb_resource *resource) { struct interface *ifp; struct rip_interface *ri; if (event != NB_EV_APPLY) return NB_OK; ifp = nb_running_get_entry(dnode, NULL, true); ri = ifp->info; XFREE(MTYPE_RIP_INTERFACE_STRING, ri->key_chain); ri->key_chain = XSTRDUP(MTYPE_RIP_INTERFACE_STRING, yang_dnode_get_string(dnode, NULL)); return NB_OK; } static int lib_interface_rip_authentication_key_chain_destroy(enum nb_event event, const struct lyd_node *dnode) { struct interface *ifp; struct rip_interface *ri; if (event != NB_EV_APPLY) return NB_OK; ifp = nb_running_get_entry(dnode, NULL, true); ri = ifp->info; XFREE(MTYPE_RIP_INTERFACE_STRING, ri->key_chain); return NB_OK; } /* * XPath: /frr-ripd:ripd/instance/state/neighbors/neighbor */ static const void * ripd_instance_state_neighbors_neighbor_get_next(const void *parent_list_entry, const void *list_entry) { const struct rip *rip = parent_list_entry; struct listnode *node; if (list_entry == NULL) node = listhead(rip->peer_list); else node = listnextnode((struct listnode *)list_entry); return node; } static int ripd_instance_state_neighbors_neighbor_get_keys(const void *list_entry, struct yang_list_keys *keys) { const struct listnode *node = list_entry; const struct rip_peer *peer = listgetdata(node); keys->num = 1; (void)inet_ntop(AF_INET, &peer->addr, keys->key[0], sizeof(keys->key[0])); return NB_OK; } static const void *ripd_instance_state_neighbors_neighbor_lookup_entry( const void *parent_list_entry, const struct yang_list_keys *keys) { const struct rip *rip = parent_list_entry; struct in_addr address; struct rip_peer *peer; struct listnode *node; yang_str2ipv4(keys->key[0], &address); for (ALL_LIST_ELEMENTS_RO(rip->peer_list, node, peer)) { if (IPV4_ADDR_SAME(&peer->addr, &address)) return node; } return NULL; } /* * XPath: /frr-ripd:ripd/instance/state/neighbors/neighbor/address */ static struct yang_data * ripd_instance_state_neighbors_neighbor_address_get_elem(const char *xpath, const void *list_entry) { const struct listnode *node = list_entry; const struct rip_peer *peer = listgetdata(node); return yang_data_new_ipv4(xpath, &peer->addr); } /* * XPath: /frr-ripd:ripd/instance/state/neighbors/neighbor/last-update */ static struct yang_data * ripd_instance_state_neighbors_neighbor_last_update_get_elem( const char *xpath, const void *list_entry) { /* TODO: yang:date-and-time is tricky */ return NULL; } /* * XPath: /frr-ripd:ripd/instance/state/neighbors/neighbor/bad-packets-rcvd */ static struct yang_data * ripd_instance_state_neighbors_neighbor_bad_packets_rcvd_get_elem( const char *xpath, const void *list_entry) { const struct listnode *node = list_entry; const struct rip_peer *peer = listgetdata(node); return yang_data_new_uint32(xpath, peer->recv_badpackets); } /* * XPath: /frr-ripd:ripd/instance/state/neighbors/neighbor/bad-routes-rcvd */ static struct yang_data * ripd_instance_state_neighbors_neighbor_bad_routes_rcvd_get_elem( const char *xpath, const void *list_entry) { const struct listnode *node = list_entry; const struct rip_peer *peer = listgetdata(node); return yang_data_new_uint32(xpath, peer->recv_badroutes); } /* * XPath: /frr-ripd:ripd/instance/state/routes/route */ static const void * ripd_instance_state_routes_route_get_next(const void *parent_list_entry, const void *list_entry) { const struct rip *rip = parent_list_entry; struct route_node *rn; if (list_entry == NULL) rn = route_top(rip->table); else rn = route_next((struct route_node *)list_entry); while (rn && rn->info == NULL) rn = route_next(rn); return rn; } static int ripd_instance_state_routes_route_get_keys(const void *list_entry, struct yang_list_keys *keys) { const struct route_node *rn = list_entry; keys->num = 1; (void)prefix2str(&rn->p, keys->key[0], sizeof(keys->key[0])); return NB_OK; } static const void * ripd_instance_state_routes_route_lookup_entry(const void *parent_list_entry, const struct yang_list_keys *keys) { const struct rip *rip = parent_list_entry; struct prefix prefix; struct route_node *rn; yang_str2ipv4p(keys->key[0], &prefix); rn = route_node_lookup(rip->table, &prefix); if (!rn || !rn->info) return NULL; route_unlock_node(rn); return rn; } /* * XPath: /frr-ripd:ripd/instance/state/routes/route/prefix */ static struct yang_data * ripd_instance_state_routes_route_prefix_get_elem(const char *xpath, const void *list_entry) { const struct route_node *rn = list_entry; const struct rip_info *rinfo = listnode_head(rn->info); return yang_data_new_ipv4p(xpath, &rinfo->rp->p); } /* * XPath: /frr-ripd:ripd/instance/state/routes/route/next-hop */ static struct yang_data * ripd_instance_state_routes_route_next_hop_get_elem(const char *xpath, const void *list_entry) { const struct route_node *rn = list_entry; const struct rip_info *rinfo = listnode_head(rn->info); switch (rinfo->nh.type) { case NEXTHOP_TYPE_IPV4: case NEXTHOP_TYPE_IPV4_IFINDEX: return yang_data_new_ipv4(xpath, &rinfo->nh.gate.ipv4); default: return NULL; } } /* * XPath: /frr-ripd:ripd/instance/state/routes/route/interface */ static struct yang_data * ripd_instance_state_routes_route_interface_get_elem(const char *xpath, const void *list_entry) { const struct route_node *rn = list_entry; const struct rip_info *rinfo = listnode_head(rn->info); const struct rip *rip = rip_info_get_instance(rinfo); switch (rinfo->nh.type) { case NEXTHOP_TYPE_IFINDEX: case NEXTHOP_TYPE_IPV4_IFINDEX: return yang_data_new_string( xpath, ifindex2ifname(rinfo->nh.ifindex, rip->vrf->vrf_id)); default: return NULL; } } /* * XPath: /frr-ripd:ripd/instance/state/routes/route/metric */ static struct yang_data * ripd_instance_state_routes_route_metric_get_elem(const char *xpath, const void *list_entry) { const struct route_node *rn = list_entry; const struct rip_info *rinfo = listnode_head(rn->info); return yang_data_new_uint8(xpath, rinfo->metric); } /* * XPath: /frr-ripd:clear-rip-route */ static void clear_rip_route(struct rip *rip) { struct route_node *rp; if (IS_RIP_DEBUG_EVENT) zlog_debug("Clearing all RIP routes (VRF %s)", rip->vrf_name); /* Clear received RIP routes */ for (rp = route_top(rip->table); rp; rp = route_next(rp)) { struct list *list; struct listnode *listnode; struct rip_info *rinfo; list = rp->info; if (!list) continue; for (ALL_LIST_ELEMENTS_RO(list, listnode, rinfo)) { if (!rip_route_rte(rinfo)) continue; if (CHECK_FLAG(rinfo->flags, RIP_RTF_FIB)) rip_zebra_ipv4_delete(rip, rp); break; } if (rinfo) { RIP_TIMER_OFF(rinfo->t_timeout); RIP_TIMER_OFF(rinfo->t_garbage_collect); listnode_delete(list, rinfo); rip_info_free(rinfo); } if (list_isempty(list)) { list_delete(&list); rp->info = NULL; route_unlock_node(rp); } } } static int clear_rip_route_rpc(const char *xpath, const struct list *input, struct list *output) { struct rip *rip; struct yang_data *yang_vrf; yang_vrf = yang_data_list_find(input, "%s/%s", xpath, "input/vrf"); if (yang_vrf) { rip = rip_lookup_by_vrf_name(yang_vrf->value); if (rip) clear_rip_route(rip); } else { struct vrf *vrf; RB_FOREACH (vrf, vrf_name_head, &vrfs_by_name) { rip = vrf->info; if (!rip) continue; clear_rip_route(rip); } } return NB_OK; } /* * XPath: /frr-ripd:authentication-type-failure */ void ripd_notif_send_auth_type_failure(const char *ifname) { const char *xpath = "/frr-ripd:authentication-type-failure"; struct list *arguments; char xpath_arg[XPATH_MAXLEN]; struct yang_data *data; arguments = yang_data_list_new(); snprintf(xpath_arg, sizeof(xpath_arg), "%s/interface-name", xpath); data = yang_data_new_string(xpath_arg, ifname); listnode_add(arguments, data); nb_notification_send(xpath, arguments); } /* * XPath: /frr-ripd:authentication-failure */ void ripd_notif_send_auth_failure(const char *ifname) { const char *xpath = "/frr-ripd:authentication-failure"; struct list *arguments; char xpath_arg[XPATH_MAXLEN]; struct yang_data *data; arguments = yang_data_list_new(); snprintf(xpath_arg, sizeof(xpath_arg), "%s/interface-name", xpath); data = yang_data_new_string(xpath_arg, ifname); listnode_add(arguments, data); nb_notification_send(xpath, arguments); } /* clang-format off */ const struct frr_yang_module_info frr_ripd_info = { .name = "frr-ripd", .nodes = { { .xpath = "/frr-ripd:ripd/instance", .cbs = { .cli_show = cli_show_router_rip, .create = ripd_instance_create, .destroy = ripd_instance_destroy, .get_keys = ripd_instance_get_keys, .get_next = ripd_instance_get_next, .lookup_entry = ripd_instance_lookup_entry, }, }, { .xpath = "/frr-ripd:ripd/instance/allow-ecmp", .cbs = { .cli_show = cli_show_rip_allow_ecmp, .modify = ripd_instance_allow_ecmp_modify, }, }, { .xpath = "/frr-ripd:ripd/instance/default-information-originate", .cbs = { .cli_show = cli_show_rip_default_information_originate, .modify = ripd_instance_default_information_originate_modify, }, }, { .xpath = "/frr-ripd:ripd/instance/default-metric", .cbs = { .cli_show = cli_show_rip_default_metric, .modify = ripd_instance_default_metric_modify, }, }, { .xpath = "/frr-ripd:ripd/instance/distance/default", .cbs = { .cli_show = cli_show_rip_distance, .modify = ripd_instance_distance_default_modify, }, }, { .xpath = "/frr-ripd:ripd/instance/distance/source", .cbs = { .cli_show = cli_show_rip_distance_source, .create = ripd_instance_distance_source_create, .destroy = ripd_instance_distance_source_destroy, }, }, { .xpath = "/frr-ripd:ripd/instance/distance/source/distance", .cbs = { .modify = ripd_instance_distance_source_distance_modify, }, }, { .xpath = "/frr-ripd:ripd/instance/distance/source/access-list", .cbs = { .destroy = ripd_instance_distance_source_access_list_destroy, .modify = ripd_instance_distance_source_access_list_modify, }, }, { .xpath = "/frr-ripd:ripd/instance/explicit-neighbor", .cbs = { .cli_show = cli_show_rip_neighbor, .create = ripd_instance_explicit_neighbor_create, .destroy = ripd_instance_explicit_neighbor_destroy, }, }, { .xpath = "/frr-ripd:ripd/instance/network", .cbs = { .cli_show = cli_show_rip_network_prefix, .create = ripd_instance_network_create, .destroy = ripd_instance_network_destroy, }, }, { .xpath = "/frr-ripd:ripd/instance/interface", .cbs = { .cli_show = cli_show_rip_network_interface, .create = ripd_instance_interface_create, .destroy = ripd_instance_interface_destroy, }, }, { .xpath = "/frr-ripd:ripd/instance/offset-list", .cbs = { .cli_show = cli_show_rip_offset_list, .create = ripd_instance_offset_list_create, .destroy = ripd_instance_offset_list_destroy, }, }, { .xpath = "/frr-ripd:ripd/instance/offset-list/access-list", .cbs = { .modify = ripd_instance_offset_list_access_list_modify, }, }, { .xpath = "/frr-ripd:ripd/instance/offset-list/metric", .cbs = { .modify = ripd_instance_offset_list_metric_modify, }, }, { .xpath = "/frr-ripd:ripd/instance/passive-default", .cbs = { .cli_show = cli_show_rip_passive_default, .modify = ripd_instance_passive_default_modify, }, }, { .xpath = "/frr-ripd:ripd/instance/passive-interface", .cbs = { .cli_show = cli_show_rip_passive_interface, .create = ripd_instance_passive_interface_create, .destroy = ripd_instance_passive_interface_destroy, }, }, { .xpath = "/frr-ripd:ripd/instance/non-passive-interface", .cbs = { .cli_show = cli_show_rip_non_passive_interface, .create = ripd_instance_non_passive_interface_create, .destroy = ripd_instance_non_passive_interface_destroy, }, }, { .xpath = "/frr-ripd:ripd/instance/redistribute", .cbs = { .apply_finish = ripd_instance_redistribute_apply_finish, .cli_show = cli_show_rip_redistribute, .create = ripd_instance_redistribute_create, .destroy = ripd_instance_redistribute_destroy, }, }, { .xpath = "/frr-ripd:ripd/instance/redistribute/route-map", .cbs = { .destroy = ripd_instance_redistribute_route_map_destroy, .modify = ripd_instance_redistribute_route_map_modify, }, }, { .xpath = "/frr-ripd:ripd/instance/redistribute/metric", .cbs = { .destroy = ripd_instance_redistribute_metric_destroy, .modify = ripd_instance_redistribute_metric_modify, }, }, { .xpath = "/frr-ripd:ripd/instance/static-route", .cbs = { .cli_show = cli_show_rip_route, .create = ripd_instance_static_route_create, .destroy = ripd_instance_static_route_destroy, }, }, { .xpath = "/frr-ripd:ripd/instance/timers", .cbs = { .apply_finish = ripd_instance_timers_apply_finish, .cli_show = cli_show_rip_timers, }, }, { .xpath = "/frr-ripd:ripd/instance/timers/flush-interval", .cbs = { .modify = ripd_instance_timers_flush_interval_modify, }, }, { .xpath = "/frr-ripd:ripd/instance/timers/holddown-interval", .cbs = { .modify = ripd_instance_timers_holddown_interval_modify, }, }, { .xpath = "/frr-ripd:ripd/instance/timers/update-interval", .cbs = { .modify = ripd_instance_timers_update_interval_modify, }, }, { .xpath = "/frr-ripd:ripd/instance/version", .cbs = { .cli_show = cli_show_rip_version, }, }, { .xpath = "/frr-ripd:ripd/instance/version/receive", .cbs = { .modify = ripd_instance_version_receive_modify, }, }, { .xpath = "/frr-ripd:ripd/instance/version/send", .cbs = { .modify = ripd_instance_version_send_modify, }, }, { .xpath = "/frr-interface:lib/interface/frr-ripd:rip/split-horizon", .cbs = { .cli_show = cli_show_ip_rip_split_horizon, .modify = lib_interface_rip_split_horizon_modify, }, }, { .xpath = "/frr-interface:lib/interface/frr-ripd:rip/v2-broadcast", .cbs = { .cli_show = cli_show_ip_rip_v2_broadcast, .modify = lib_interface_rip_v2_broadcast_modify, }, }, { .xpath = "/frr-interface:lib/interface/frr-ripd:rip/version-receive", .cbs = { .cli_show = cli_show_ip_rip_receive_version, .modify = lib_interface_rip_version_receive_modify, }, }, { .xpath = "/frr-interface:lib/interface/frr-ripd:rip/version-send", .cbs = { .cli_show = cli_show_ip_rip_send_version, .modify = lib_interface_rip_version_send_modify, }, }, { .xpath = "/frr-interface:lib/interface/frr-ripd:rip/authentication-scheme", .cbs = { .cli_show = cli_show_ip_rip_authentication_scheme, }, }, { .xpath = "/frr-interface:lib/interface/frr-ripd:rip/authentication-scheme/mode", .cbs = { .modify = lib_interface_rip_authentication_scheme_mode_modify, }, }, { .xpath = "/frr-interface:lib/interface/frr-ripd:rip/authentication-scheme/md5-auth-length", .cbs = { .destroy = lib_interface_rip_authentication_scheme_md5_auth_length_destroy, .modify = lib_interface_rip_authentication_scheme_md5_auth_length_modify, }, }, { .xpath = "/frr-interface:lib/interface/frr-ripd:rip/authentication-password", .cbs = { .cli_show = cli_show_ip_rip_authentication_string, .destroy = lib_interface_rip_authentication_password_destroy, .modify = lib_interface_rip_authentication_password_modify, }, }, { .xpath = "/frr-interface:lib/interface/frr-ripd:rip/authentication-key-chain", .cbs = { .cli_show = cli_show_ip_rip_authentication_key_chain, .destroy = lib_interface_rip_authentication_key_chain_destroy, .modify = lib_interface_rip_authentication_key_chain_modify, }, }, { .xpath = "/frr-ripd:ripd/instance/state/neighbors/neighbor", .cbs = { .get_keys = ripd_instance_state_neighbors_neighbor_get_keys, .get_next = ripd_instance_state_neighbors_neighbor_get_next, .lookup_entry = ripd_instance_state_neighbors_neighbor_lookup_entry, }, }, { .xpath = "/frr-ripd:ripd/instance/state/neighbors/neighbor/address", .cbs = { .get_elem = ripd_instance_state_neighbors_neighbor_address_get_elem, }, }, { .xpath = "/frr-ripd:ripd/instance/state/neighbors/neighbor/last-update", .cbs = { .get_elem = ripd_instance_state_neighbors_neighbor_last_update_get_elem, }, }, { .xpath = "/frr-ripd:ripd/instance/state/neighbors/neighbor/bad-packets-rcvd", .cbs = { .get_elem = ripd_instance_state_neighbors_neighbor_bad_packets_rcvd_get_elem, }, }, { .xpath = "/frr-ripd:ripd/instance/state/neighbors/neighbor/bad-routes-rcvd", .cbs = { .get_elem = ripd_instance_state_neighbors_neighbor_bad_routes_rcvd_get_elem, }, }, { .xpath = "/frr-ripd:ripd/instance/state/routes/route", .cbs = { .get_keys = ripd_instance_state_routes_route_get_keys, .get_next = ripd_instance_state_routes_route_get_next, .lookup_entry = ripd_instance_state_routes_route_lookup_entry, }, }, { .xpath = "/frr-ripd:ripd/instance/state/routes/route/prefix", .cbs = { .get_elem = ripd_instance_state_routes_route_prefix_get_elem, }, }, { .xpath = "/frr-ripd:ripd/instance/state/routes/route/next-hop", .cbs = { .get_elem = ripd_instance_state_routes_route_next_hop_get_elem, }, }, { .xpath = "/frr-ripd:ripd/instance/state/routes/route/interface", .cbs = { .get_elem = ripd_instance_state_routes_route_interface_get_elem, }, }, { .xpath = "/frr-ripd:ripd/instance/state/routes/route/metric", .cbs = { .get_elem = ripd_instance_state_routes_route_metric_get_elem, }, }, { .xpath = "/frr-ripd:clear-rip-route", .cbs = { .rpc = clear_rip_route_rpc, }, }, { .xpath = NULL, }, } }; frr-7.2.1/ripd/rip_offset.c0000644000000000000000000001102213610377563012452 00000000000000/* RIP offset-list * Copyright (C) 2000 Kunihiro Ishiguro * * This file is part of GNU Zebra. * * GNU Zebra is free software; you can redistribute it and/or modify it * under the terms of the GNU General Public License as published by the * Free Software Foundation; either version 2, or (at your option) any * later version. * * GNU Zebra 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; see the file COPYING; if not, write to the Free Software * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA */ #include #include "if.h" #include "prefix.h" #include "filter.h" #include "command.h" #include "linklist.h" #include "memory.h" #include "ripd/ripd.h" DEFINE_MTYPE_STATIC(RIPD, RIP_OFFSET_LIST, "RIP offset list") #define OFFSET_LIST_IN_NAME(O) ((O)->direct[RIP_OFFSET_LIST_IN].alist_name) #define OFFSET_LIST_IN_METRIC(O) ((O)->direct[RIP_OFFSET_LIST_IN].metric) #define OFFSET_LIST_OUT_NAME(O) ((O)->direct[RIP_OFFSET_LIST_OUT].alist_name) #define OFFSET_LIST_OUT_METRIC(O) ((O)->direct[RIP_OFFSET_LIST_OUT].metric) struct rip_offset_list *rip_offset_list_new(struct rip *rip, const char *ifname) { struct rip_offset_list *offset; offset = XCALLOC(MTYPE_RIP_OFFSET_LIST, sizeof(struct rip_offset_list)); offset->rip = rip; offset->ifname = strdup(ifname); listnode_add_sort(rip->offset_list_master, offset); return offset; } void offset_list_del(struct rip_offset_list *offset) { listnode_delete(offset->rip->offset_list_master, offset); offset_list_free(offset); } void offset_list_free(struct rip_offset_list *offset) { if (OFFSET_LIST_IN_NAME(offset)) free(OFFSET_LIST_IN_NAME(offset)); if (OFFSET_LIST_OUT_NAME(offset)) free(OFFSET_LIST_OUT_NAME(offset)); free(offset->ifname); XFREE(MTYPE_RIP_OFFSET_LIST, offset); } struct rip_offset_list *rip_offset_list_lookup(struct rip *rip, const char *ifname) { struct rip_offset_list *offset; struct listnode *node, *nnode; for (ALL_LIST_ELEMENTS(rip->offset_list_master, node, nnode, offset)) { if (strcmp(offset->ifname, ifname) == 0) return offset; } return NULL; } /* If metric is modifed return 1. */ int rip_offset_list_apply_in(struct prefix_ipv4 *p, struct interface *ifp, uint32_t *metric) { struct rip_interface *ri = ifp->info; struct rip_offset_list *offset; struct access_list *alist; /* Look up offset-list with interface name. */ offset = rip_offset_list_lookup(ri->rip, ifp->name); if (offset && OFFSET_LIST_IN_NAME(offset)) { alist = access_list_lookup(AFI_IP, OFFSET_LIST_IN_NAME(offset)); if (alist && access_list_apply(alist, (struct prefix *)p) == FILTER_PERMIT) { *metric += OFFSET_LIST_IN_METRIC(offset); return 1; } return 0; } /* Look up offset-list without interface name. */ offset = rip_offset_list_lookup(ri->rip, "*"); if (offset && OFFSET_LIST_IN_NAME(offset)) { alist = access_list_lookup(AFI_IP, OFFSET_LIST_IN_NAME(offset)); if (alist && access_list_apply(alist, (struct prefix *)p) == FILTER_PERMIT) { *metric += OFFSET_LIST_IN_METRIC(offset); return 1; } return 0; } return 0; } /* If metric is modifed return 1. */ int rip_offset_list_apply_out(struct prefix_ipv4 *p, struct interface *ifp, uint32_t *metric) { struct rip_interface *ri = ifp->info; struct rip_offset_list *offset; struct access_list *alist; /* Look up offset-list with interface name. */ offset = rip_offset_list_lookup(ri->rip, ifp->name); if (offset && OFFSET_LIST_OUT_NAME(offset)) { alist = access_list_lookup(AFI_IP, OFFSET_LIST_OUT_NAME(offset)); if (alist && access_list_apply(alist, (struct prefix *)p) == FILTER_PERMIT) { *metric += OFFSET_LIST_OUT_METRIC(offset); return 1; } return 0; } /* Look up offset-list without interface name. */ offset = rip_offset_list_lookup(ri->rip, "*"); if (offset && OFFSET_LIST_OUT_NAME(offset)) { alist = access_list_lookup(AFI_IP, OFFSET_LIST_OUT_NAME(offset)); if (alist && access_list_apply(alist, (struct prefix *)p) == FILTER_PERMIT) { *metric += OFFSET_LIST_OUT_METRIC(offset); return 1; } return 0; } return 0; } int offset_list_cmp(struct rip_offset_list *o1, struct rip_offset_list *o2) { return strcmp(o1->ifname, o2->ifname); } frr-7.2.1/ripd/rip_peer.c0000644000000000000000000001077213610377563012132 00000000000000/* RIP peer support * Copyright (C) 2000 Kunihiro Ishiguro * * This file is part of GNU Zebra. * * GNU Zebra is free software; you can redistribute it and/or modify it * under the terms of the GNU General Public License as published by the * Free Software Foundation; either version 2, or (at your option) any * later version. * * GNU Zebra 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; see the file COPYING; if not, write to the Free Software * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA */ #include #include "if.h" #include "prefix.h" #include "command.h" #include "linklist.h" #include "thread.h" #include "memory.h" #include "ripd/ripd.h" DEFINE_MTYPE_STATIC(RIPD, RIP_PEER, "RIP peer") static struct rip_peer *rip_peer_new(void) { return XCALLOC(MTYPE_RIP_PEER, sizeof(struct rip_peer)); } static void rip_peer_free(struct rip_peer *peer) { RIP_TIMER_OFF(peer->t_timeout); XFREE(MTYPE_RIP_PEER, peer); } struct rip_peer *rip_peer_lookup(struct rip *rip, struct in_addr *addr) { struct rip_peer *peer; struct listnode *node, *nnode; for (ALL_LIST_ELEMENTS(rip->peer_list, node, nnode, peer)) { if (IPV4_ADDR_SAME(&peer->addr, addr)) return peer; } return NULL; } struct rip_peer *rip_peer_lookup_next(struct rip *rip, struct in_addr *addr) { struct rip_peer *peer; struct listnode *node, *nnode; for (ALL_LIST_ELEMENTS(rip->peer_list, node, nnode, peer)) { if (htonl(peer->addr.s_addr) > htonl(addr->s_addr)) return peer; } return NULL; } /* RIP peer is timeout. */ static int rip_peer_timeout(struct thread *t) { struct rip_peer *peer; peer = THREAD_ARG(t); listnode_delete(peer->rip->peer_list, peer); rip_peer_free(peer); return 0; } /* Get RIP peer. At the same time update timeout thread. */ static struct rip_peer *rip_peer_get(struct rip *rip, struct in_addr *addr) { struct rip_peer *peer; peer = rip_peer_lookup(rip, addr); if (peer) { if (peer->t_timeout) thread_cancel(peer->t_timeout); } else { peer = rip_peer_new(); peer->rip = rip; peer->addr = *addr; listnode_add_sort(rip->peer_list, peer); } /* Update timeout thread. */ peer->t_timeout = NULL; thread_add_timer(master, rip_peer_timeout, peer, RIP_PEER_TIMER_DEFAULT, &peer->t_timeout); /* Last update time set. */ time(&peer->uptime); return peer; } void rip_peer_update(struct rip *rip, struct sockaddr_in *from, uint8_t version) { struct rip_peer *peer; peer = rip_peer_get(rip, &from->sin_addr); peer->version = version; } void rip_peer_bad_route(struct rip *rip, struct sockaddr_in *from) { struct rip_peer *peer; peer = rip_peer_get(rip, &from->sin_addr); peer->recv_badroutes++; } void rip_peer_bad_packet(struct rip *rip, struct sockaddr_in *from) { struct rip_peer *peer; peer = rip_peer_get(rip, &from->sin_addr); peer->recv_badpackets++; } /* Display peer uptime. */ static char *rip_peer_uptime(struct rip_peer *peer, char *buf, size_t len) { time_t uptime; struct tm *tm; /* If there is no connection has been done before print `never'. */ if (peer->uptime == 0) { snprintf(buf, len, "never "); return buf; } /* Get current time. */ uptime = time(NULL); uptime -= peer->uptime; tm = gmtime(&uptime); if (uptime < ONE_DAY_SECOND) snprintf(buf, len, "%02d:%02d:%02d", tm->tm_hour, tm->tm_min, tm->tm_sec); else if (uptime < ONE_WEEK_SECOND) snprintf(buf, len, "%dd%02dh%02dm", tm->tm_yday, tm->tm_hour, tm->tm_min); else snprintf(buf, len, "%02dw%dd%02dh", tm->tm_yday / 7, tm->tm_yday - ((tm->tm_yday / 7) * 7), tm->tm_hour); return buf; } void rip_peer_display(struct vty *vty, struct rip *rip) { struct rip_peer *peer; struct listnode *node, *nnode; #define RIP_UPTIME_LEN 25 char timebuf[RIP_UPTIME_LEN]; for (ALL_LIST_ELEMENTS(rip->peer_list, node, nnode, peer)) { vty_out(vty, " %-16s %9d %9d %9d %s\n", inet_ntoa(peer->addr), peer->recv_badpackets, peer->recv_badroutes, ZEBRA_RIP_DISTANCE_DEFAULT, rip_peer_uptime(peer, timebuf, RIP_UPTIME_LEN)); } } int rip_peer_list_cmp(struct rip_peer *p1, struct rip_peer *p2) { if (p2->addr.s_addr == p1->addr.s_addr) return 0; return (htonl(p1->addr.s_addr) < htonl(p2->addr.s_addr)) ? -1 : 1; } void rip_peer_list_del(void *arg) { rip_peer_free(arg); } frr-7.2.1/ripd/rip_routemap.c0000644000000000000000000003725013610377563013033 00000000000000/* RIPv2 routemap. * Copyright (C) 2005 6WIND * Copyright (C) 1999 Kunihiro Ishiguro * * This file is part of GNU Zebra. * * GNU Zebra is free software; you can redistribute it and/or modify it * under the terms of the GNU General Public License as published by the * Free Software Foundation; either version 2, or (at your option) any * later version. * * GNU Zebra 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; see the file COPYING; if not, write to the Free Software * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA */ #include #include "memory.h" #include "prefix.h" #include "vty.h" #include "routemap.h" #include "command.h" #include "filter.h" #include "log.h" #include "sockunion.h" /* for inet_aton () */ #include "plist.h" #include "vrf.h" #include "ripd/ripd.h" struct rip_metric_modifier { enum { metric_increment, metric_decrement, metric_absolute } type; bool used; uint8_t metric; }; /* `match metric METRIC' */ /* Match function return 1 if match is success else return zero. */ static enum route_map_cmd_result_t route_match_metric(void *rule, const struct prefix *prefix, route_map_object_t type, void *object) { uint32_t *metric; uint32_t check; struct rip_info *rinfo; if (type == RMAP_RIP) { metric = rule; rinfo = object; /* If external metric is available, the route-map should work on this one (for redistribute purpose) */ check = (rinfo->external_metric) ? rinfo->external_metric : rinfo->metric; if (check == *metric) return RMAP_MATCH; else return RMAP_NOMATCH; } return RMAP_NOMATCH; } /* Route map `match metric' match statement. `arg' is METRIC value */ static void *route_match_metric_compile(const char *arg) { uint32_t *metric; metric = XMALLOC(MTYPE_ROUTE_MAP_COMPILED, sizeof(uint32_t)); *metric = atoi(arg); if (*metric > 0) return metric; XFREE(MTYPE_ROUTE_MAP_COMPILED, metric); return NULL; } /* Free route map's compiled `match metric' value. */ static void route_match_metric_free(void *rule) { XFREE(MTYPE_ROUTE_MAP_COMPILED, rule); } /* Route map commands for metric matching. */ struct route_map_rule_cmd route_match_metric_cmd = { "metric", route_match_metric, route_match_metric_compile, route_match_metric_free}; /* `match interface IFNAME' */ /* Match function return 1 if match is success else return zero. */ static enum route_map_cmd_result_t route_match_interface(void *rule, const struct prefix *prefix, route_map_object_t type, void *object) { struct rip_info *rinfo; struct interface *ifp; char *ifname; if (type == RMAP_RIP) { ifname = rule; ifp = if_lookup_by_name(ifname, VRF_DEFAULT); if (!ifp) return RMAP_NOMATCH; rinfo = object; if (rinfo->ifindex_out == ifp->ifindex || rinfo->nh.ifindex == ifp->ifindex) return RMAP_MATCH; else return RMAP_NOMATCH; } return RMAP_NOMATCH; } /* Route map `match interface' match statement. `arg' is IFNAME value */ /* XXX I don`t know if I need to check does interface exist? */ static void *route_match_interface_compile(const char *arg) { return XSTRDUP(MTYPE_ROUTE_MAP_COMPILED, arg); } /* Free route map's compiled `match interface' value. */ static void route_match_interface_free(void *rule) { XFREE(MTYPE_ROUTE_MAP_COMPILED, rule); } /* Route map commands for interface matching. */ struct route_map_rule_cmd route_match_interface_cmd = { "interface", route_match_interface, route_match_interface_compile, route_match_interface_free}; /* `match ip next-hop IP_ACCESS_LIST' */ /* Match function return 1 if match is success else return zero. */ static enum route_map_cmd_result_t route_match_ip_next_hop(void *rule, const struct prefix *prefix, route_map_object_t type, void *object) { struct access_list *alist; struct rip_info *rinfo; struct prefix_ipv4 p; if (type == RMAP_RIP) { rinfo = object; p.family = AF_INET; p.prefix = (rinfo->nh.gate.ipv4.s_addr) ? rinfo->nh.gate.ipv4 : rinfo->from; p.prefixlen = IPV4_MAX_BITLEN; alist = access_list_lookup(AFI_IP, (char *)rule); if (alist == NULL) return RMAP_NOMATCH; return (access_list_apply(alist, &p) == FILTER_DENY ? RMAP_NOMATCH : RMAP_MATCH); } return RMAP_NOMATCH; } /* Route map `ip next-hop' match statement. `arg' should be access-list name. */ static void *route_match_ip_next_hop_compile(const char *arg) { return XSTRDUP(MTYPE_ROUTE_MAP_COMPILED, arg); } /* Free route map's compiled `. */ static void route_match_ip_next_hop_free(void *rule) { XFREE(MTYPE_ROUTE_MAP_COMPILED, rule); } /* Route map commands for ip next-hop matching. */ static struct route_map_rule_cmd route_match_ip_next_hop_cmd = { "ip next-hop", route_match_ip_next_hop, route_match_ip_next_hop_compile, route_match_ip_next_hop_free}; /* `match ip next-hop prefix-list PREFIX_LIST' */ static enum route_map_cmd_result_t route_match_ip_next_hop_prefix_list(void *rule, const struct prefix *prefix, route_map_object_t type, void *object) { struct prefix_list *plist; struct rip_info *rinfo; struct prefix_ipv4 p; if (type == RMAP_RIP) { rinfo = object; p.family = AF_INET; p.prefix = (rinfo->nh.gate.ipv4.s_addr) ? rinfo->nh.gate.ipv4 : rinfo->from; p.prefixlen = IPV4_MAX_BITLEN; plist = prefix_list_lookup(AFI_IP, (char *)rule); if (plist == NULL) return RMAP_NOMATCH; return (prefix_list_apply(plist, &p) == PREFIX_DENY ? RMAP_NOMATCH : RMAP_MATCH); } return RMAP_NOMATCH; } static void *route_match_ip_next_hop_prefix_list_compile(const char *arg) { return XSTRDUP(MTYPE_ROUTE_MAP_COMPILED, arg); } static void route_match_ip_next_hop_prefix_list_free(void *rule) { XFREE(MTYPE_ROUTE_MAP_COMPILED, rule); } static struct route_map_rule_cmd route_match_ip_next_hop_prefix_list_cmd = { "ip next-hop prefix-list", route_match_ip_next_hop_prefix_list, route_match_ip_next_hop_prefix_list_compile, route_match_ip_next_hop_prefix_list_free}; /* `match ip next-hop type ' */ static enum route_map_cmd_result_t route_match_ip_next_hop_type(void *rule, const struct prefix *prefix, route_map_object_t type, void *object) { struct rip_info *rinfo; if (type == RMAP_RIP && prefix->family == AF_INET) { rinfo = (struct rip_info *)object; if (!rinfo) return RMAP_NOMATCH; if (rinfo->nh.type == NEXTHOP_TYPE_BLACKHOLE) return RMAP_MATCH; } return RMAP_NOMATCH; } static void *route_match_ip_next_hop_type_compile(const char *arg) { return XSTRDUP(MTYPE_ROUTE_MAP_COMPILED, arg); } static void route_match_ip_next_hop_type_free(void *rule) { XFREE(MTYPE_ROUTE_MAP_COMPILED, rule); } static struct route_map_rule_cmd route_match_ip_next_hop_type_cmd = { "ip next-hop type", route_match_ip_next_hop_type, route_match_ip_next_hop_type_compile, route_match_ip_next_hop_type_free}; /* `match ip address IP_ACCESS_LIST' */ /* Match function should return 1 if match is success else return zero. */ static enum route_map_cmd_result_t route_match_ip_address(void *rule, const struct prefix *prefix, route_map_object_t type, void *object) { struct access_list *alist; if (type == RMAP_RIP) { alist = access_list_lookup(AFI_IP, (char *)rule); if (alist == NULL) return RMAP_NOMATCH; return (access_list_apply(alist, prefix) == FILTER_DENY ? RMAP_NOMATCH : RMAP_MATCH); } return RMAP_NOMATCH; } /* Route map `ip address' match statement. `arg' should be access-list name. */ static void *route_match_ip_address_compile(const char *arg) { return XSTRDUP(MTYPE_ROUTE_MAP_COMPILED, arg); } /* Free route map's compiled `ip address' value. */ static void route_match_ip_address_free(void *rule) { XFREE(MTYPE_ROUTE_MAP_COMPILED, rule); } /* Route map commands for ip address matching. */ static struct route_map_rule_cmd route_match_ip_address_cmd = { "ip address", route_match_ip_address, route_match_ip_address_compile, route_match_ip_address_free}; /* `match ip address prefix-list PREFIX_LIST' */ static enum route_map_cmd_result_t route_match_ip_address_prefix_list(void *rule, const struct prefix *prefix, route_map_object_t type, void *object) { struct prefix_list *plist; if (type == RMAP_RIP) { plist = prefix_list_lookup(AFI_IP, (char *)rule); if (plist == NULL) return RMAP_NOMATCH; return (prefix_list_apply(plist, prefix) == PREFIX_DENY ? RMAP_NOMATCH : RMAP_MATCH); } return RMAP_NOMATCH; } static void *route_match_ip_address_prefix_list_compile(const char *arg) { return XSTRDUP(MTYPE_ROUTE_MAP_COMPILED, arg); } static void route_match_ip_address_prefix_list_free(void *rule) { XFREE(MTYPE_ROUTE_MAP_COMPILED, rule); } static struct route_map_rule_cmd route_match_ip_address_prefix_list_cmd = { "ip address prefix-list", route_match_ip_address_prefix_list, route_match_ip_address_prefix_list_compile, route_match_ip_address_prefix_list_free}; /* `match tag TAG' */ /* Match function return 1 if match is success else return zero. */ static enum route_map_cmd_result_t route_match_tag(void *rule, const struct prefix *p, route_map_object_t type, void *object) { route_tag_t *tag; struct rip_info *rinfo; route_tag_t rinfo_tag; if (type == RMAP_RIP) { tag = rule; rinfo = object; /* The information stored by rinfo is host ordered. */ rinfo_tag = rinfo->tag; if (rinfo_tag == *tag) return RMAP_MATCH; else return RMAP_NOMATCH; } return RMAP_NOMATCH; } /* Route map commands for tag matching. */ static struct route_map_rule_cmd route_match_tag_cmd = { "tag", route_match_tag, route_map_rule_tag_compile, route_map_rule_tag_free, }; /* `set metric METRIC' */ /* Set metric to attribute. */ static enum route_map_cmd_result_t route_set_metric(void *rule, const struct prefix *prefix, route_map_object_t type, void *object) { if (type == RMAP_RIP) { struct rip_metric_modifier *mod; struct rip_info *rinfo; mod = rule; rinfo = object; if (!mod->used) return RMAP_OKAY; if (mod->type == metric_increment) rinfo->metric_out += mod->metric; else if (mod->type == metric_decrement) rinfo->metric_out -= mod->metric; else if (mod->type == metric_absolute) rinfo->metric_out = mod->metric; if ((signed int)rinfo->metric_out < 1) rinfo->metric_out = 1; if (rinfo->metric_out > RIP_METRIC_INFINITY) rinfo->metric_out = RIP_METRIC_INFINITY; rinfo->metric_set = 1; } return RMAP_OKAY; } /* set metric compilation. */ static void *route_set_metric_compile(const char *arg) { int len; const char *pnt; long metric; char *endptr = NULL; struct rip_metric_modifier *mod; mod = XMALLOC(MTYPE_ROUTE_MAP_COMPILED, sizeof(struct rip_metric_modifier)); mod->used = false; len = strlen(arg); pnt = arg; if (len == 0) return mod; /* Examine first character. */ if (arg[0] == '+') { mod->type = metric_increment; pnt++; } else if (arg[0] == '-') { mod->type = metric_decrement; pnt++; } else mod->type = metric_absolute; /* Check beginning with digit string. */ if (*pnt < '0' || *pnt > '9') return mod; /* Convert string to integer. */ metric = strtol(pnt, &endptr, 10); if (*endptr != '\0' || metric < 0) { return mod; } if (metric > RIP_METRIC_INFINITY) { zlog_info( "%s: Metric specified: %ld is greater than RIP_METRIC_INFINITY, using INFINITY instead", __PRETTY_FUNCTION__, metric); mod->metric = RIP_METRIC_INFINITY; } else mod->metric = metric; mod->used = true; return mod; } /* Free route map's compiled `set metric' value. */ static void route_set_metric_free(void *rule) { XFREE(MTYPE_ROUTE_MAP_COMPILED, rule); } /* Set metric rule structure. */ static struct route_map_rule_cmd route_set_metric_cmd = { "metric", route_set_metric, route_set_metric_compile, route_set_metric_free, }; /* `set ip next-hop IP_ADDRESS' */ /* Set nexthop to object. ojbect must be pointer to struct attr. */ static enum route_map_cmd_result_t route_set_ip_nexthop(void *rule, const struct prefix *prefix, route_map_object_t type, void *object) { struct in_addr *address; struct rip_info *rinfo; if (type == RMAP_RIP) { /* Fetch routemap's rule information. */ address = rule; rinfo = object; /* Set next hop value. */ rinfo->nexthop_out = *address; } return RMAP_OKAY; } /* Route map `ip nexthop' compile function. Given string is converted to struct in_addr structure. */ static void *route_set_ip_nexthop_compile(const char *arg) { int ret; struct in_addr *address; address = XMALLOC(MTYPE_ROUTE_MAP_COMPILED, sizeof(struct in_addr)); ret = inet_aton(arg, address); if (ret == 0) { XFREE(MTYPE_ROUTE_MAP_COMPILED, address); return NULL; } return address; } /* Free route map's compiled `ip nexthop' value. */ static void route_set_ip_nexthop_free(void *rule) { XFREE(MTYPE_ROUTE_MAP_COMPILED, rule); } /* Route map commands for ip nexthop set. */ static struct route_map_rule_cmd route_set_ip_nexthop_cmd = { "ip next-hop", route_set_ip_nexthop, route_set_ip_nexthop_compile, route_set_ip_nexthop_free}; /* `set tag TAG' */ /* Set tag to object. ojbect must be pointer to struct attr. */ static enum route_map_cmd_result_t route_set_tag(void *rule, const struct prefix *prefix, route_map_object_t type, void *object) { route_tag_t *tag; struct rip_info *rinfo; if (type == RMAP_RIP) { /* Fetch routemap's rule information. */ tag = rule; rinfo = object; /* Set next hop value. */ rinfo->tag_out = *tag; } return RMAP_OKAY; } /* Route map commands for tag set. */ static struct route_map_rule_cmd route_set_tag_cmd = { "tag", route_set_tag, route_map_rule_tag_compile, route_map_rule_tag_free}; #define MATCH_STR "Match values from routing table\n" #define SET_STR "Set values in destination routing protocol\n" /* Route-map init */ void rip_route_map_init(void) { route_map_init(); route_map_match_interface_hook(generic_match_add); route_map_no_match_interface_hook(generic_match_delete); route_map_match_ip_address_hook(generic_match_add); route_map_no_match_ip_address_hook(generic_match_delete); route_map_match_ip_address_prefix_list_hook(generic_match_add); route_map_no_match_ip_address_prefix_list_hook(generic_match_delete); route_map_match_ip_next_hop_hook(generic_match_add); route_map_no_match_ip_next_hop_hook(generic_match_delete); route_map_match_ip_next_hop_prefix_list_hook(generic_match_add); route_map_no_match_ip_next_hop_prefix_list_hook(generic_match_delete); route_map_match_ip_next_hop_type_hook(generic_match_add); route_map_no_match_ip_next_hop_type_hook(generic_match_delete); route_map_match_metric_hook(generic_match_add); route_map_no_match_metric_hook(generic_match_delete); route_map_match_tag_hook(generic_match_add); route_map_no_match_tag_hook(generic_match_delete); route_map_set_ip_nexthop_hook(generic_set_add); route_map_no_set_ip_nexthop_hook(generic_set_delete); route_map_set_metric_hook(generic_set_add); route_map_no_set_metric_hook(generic_set_delete); route_map_set_tag_hook(generic_set_add); route_map_no_set_tag_hook(generic_set_delete); route_map_install_match(&route_match_metric_cmd); route_map_install_match(&route_match_interface_cmd); route_map_install_match(&route_match_ip_next_hop_cmd); route_map_install_match(&route_match_ip_next_hop_prefix_list_cmd); route_map_install_match(&route_match_ip_next_hop_type_cmd); route_map_install_match(&route_match_ip_address_cmd); route_map_install_match(&route_match_ip_address_prefix_list_cmd); route_map_install_match(&route_match_tag_cmd); route_map_install_set(&route_set_metric_cmd); route_map_install_set(&route_set_ip_nexthop_cmd); route_map_install_set(&route_set_tag_cmd); } frr-7.2.1/ripd/rip_snmp.c0000644000000000000000000003463413610377563012157 00000000000000/* RIP SNMP support * Copyright (C) 1999 Kunihiro Ishiguro * * This file is part of GNU Zebra. * * GNU Zebra is free software; you can redistribute it and/or modify it * under the terms of the GNU General Public License as published by the * Free Software Foundation; either version 2, or (at your option) any * later version. * * GNU Zebra 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; see the file COPYING; if not, write to the Free Software * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA */ #include #include #include #include "if.h" #include "vrf.h" #include "log.h" #include "prefix.h" #include "command.h" #include "table.h" #include "smux.h" #include "libfrr.h" #include "version.h" #include "ripd/ripd.h" /* RIPv2-MIB. */ #define RIPV2MIB 1,3,6,1,2,1,23 /* RIPv2-MIB rip2Globals values. */ #define RIP2GLOBALROUTECHANGES 1 #define RIP2GLOBALQUERIES 2 /* RIPv2-MIB rip2IfStatEntry. */ #define RIP2IFSTATENTRY 1 /* RIPv2-MIB rip2IfStatTable. */ #define RIP2IFSTATADDRESS 1 #define RIP2IFSTATRCVBADPACKETS 2 #define RIP2IFSTATRCVBADROUTES 3 #define RIP2IFSTATSENTUPDATES 4 #define RIP2IFSTATSTATUS 5 /* RIPv2-MIB rip2IfConfTable. */ #define RIP2IFCONFADDRESS 1 #define RIP2IFCONFDOMAIN 2 #define RIP2IFCONFAUTHTYPE 3 #define RIP2IFCONFAUTHKEY 4 #define RIP2IFCONFSEND 5 #define RIP2IFCONFRECEIVE 6 #define RIP2IFCONFDEFAULTMETRIC 7 #define RIP2IFCONFSTATUS 8 #define RIP2IFCONFSRCADDRESS 9 /* RIPv2-MIB rip2PeerTable. */ #define RIP2PEERADDRESS 1 #define RIP2PEERDOMAIN 2 #define RIP2PEERLASTUPDATE 3 #define RIP2PEERVERSION 4 #define RIP2PEERRCVBADPACKETS 5 #define RIP2PEERRCVBADROUTES 6 /* SNMP value hack. */ #define COUNTER ASN_COUNTER #define INTEGER ASN_INTEGER #define TIMETICKS ASN_TIMETICKS #define IPADDRESS ASN_IPADDRESS #define STRING ASN_OCTET_STR /* Define SNMP local variables. */ SNMP_LOCAL_VARIABLES /* RIP-MIB instances. */ static oid rip_oid[] = {RIPV2MIB}; /* Interface cache table sorted by interface's address. */ static struct route_table *rip_ifaddr_table; /* Hook functions. */ static uint8_t *rip2Globals(struct variable *, oid[], size_t *, int, size_t *, WriteMethod **); static uint8_t *rip2IfStatEntry(struct variable *, oid[], size_t *, int, size_t *, WriteMethod **); static uint8_t *rip2IfConfAddress(struct variable *, oid[], size_t *, int, size_t *, WriteMethod **); static uint8_t *rip2PeerTable(struct variable *, oid[], size_t *, int, size_t *, WriteMethod **); static struct variable rip_variables[] = { /* RIP Global Counters. */ {RIP2GLOBALROUTECHANGES, COUNTER, RONLY, rip2Globals, 2, {1, 1}}, {RIP2GLOBALQUERIES, COUNTER, RONLY, rip2Globals, 2, {1, 2}}, /* RIP Interface Tables. */ {RIP2IFSTATADDRESS, IPADDRESS, RONLY, rip2IfStatEntry, 3, {2, 1, 1}}, {RIP2IFSTATRCVBADPACKETS, COUNTER, RONLY, rip2IfStatEntry, 3, {2, 1, 2}}, {RIP2IFSTATRCVBADROUTES, COUNTER, RONLY, rip2IfStatEntry, 3, {2, 1, 3}}, {RIP2IFSTATSENTUPDATES, COUNTER, RONLY, rip2IfStatEntry, 3, {2, 1, 4}}, {RIP2IFSTATSTATUS, COUNTER, RWRITE, rip2IfStatEntry, 3, {2, 1, 5}}, {RIP2IFCONFADDRESS, IPADDRESS, RONLY, rip2IfConfAddress, /* RIP Interface Configuration Table. */ 3, {3, 1, 1}}, {RIP2IFCONFDOMAIN, STRING, RONLY, rip2IfConfAddress, 3, {3, 1, 2}}, {RIP2IFCONFAUTHTYPE, COUNTER, RONLY, rip2IfConfAddress, 3, {3, 1, 3}}, {RIP2IFCONFAUTHKEY, STRING, RONLY, rip2IfConfAddress, 3, {3, 1, 4}}, {RIP2IFCONFSEND, COUNTER, RONLY, rip2IfConfAddress, 3, {3, 1, 5}}, {RIP2IFCONFRECEIVE, COUNTER, RONLY, rip2IfConfAddress, 3, {3, 1, 6}}, {RIP2IFCONFDEFAULTMETRIC, COUNTER, RONLY, rip2IfConfAddress, 3, {3, 1, 7}}, {RIP2IFCONFSTATUS, COUNTER, RONLY, rip2IfConfAddress, 3, {3, 1, 8}}, {RIP2IFCONFSRCADDRESS, IPADDRESS, RONLY, rip2IfConfAddress, 3, {3, 1, 9}}, {RIP2PEERADDRESS, IPADDRESS, RONLY, rip2PeerTable, /* RIP Peer Table. */ 3, {4, 1, 1}}, {RIP2PEERDOMAIN, STRING, RONLY, rip2PeerTable, 3, {4, 1, 2}}, {RIP2PEERLASTUPDATE, TIMETICKS, RONLY, rip2PeerTable, 3, {4, 1, 3}}, {RIP2PEERVERSION, INTEGER, RONLY, rip2PeerTable, 3, {4, 1, 4}}, {RIP2PEERRCVBADPACKETS, COUNTER, RONLY, rip2PeerTable, 3, {4, 1, 5}}, {RIP2PEERRCVBADROUTES, COUNTER, RONLY, rip2PeerTable, 3, {4, 1, 6}}}; extern struct thread_master *master; static uint8_t *rip2Globals(struct variable *v, oid name[], size_t *length, int exact, size_t *var_len, WriteMethod **write_method) { struct rip *rip; if (smux_header_generic(v, name, length, exact, var_len, write_method) == MATCH_FAILED) return NULL; rip = rip_lookup_by_vrf_id(VRF_DEFAULT); if (!rip) return NULL; /* Retrun global counter. */ switch (v->magic) { case RIP2GLOBALROUTECHANGES: return SNMP_INTEGER(rip->counters.route_changes); break; case RIP2GLOBALQUERIES: return SNMP_INTEGER(rip->counters.queries); break; default: return NULL; break; } return NULL; } static int rip_snmp_ifaddr_add(struct connected *ifc) { struct interface *ifp = ifc->ifp; struct prefix *p; struct route_node *rn; p = ifc->address; if (p->family != AF_INET) return 0; rn = route_node_get(rip_ifaddr_table, p); rn->info = ifp; return 0; } static int rip_snmp_ifaddr_del(struct connected *ifc) { struct interface *ifp = ifc->ifp; struct prefix *p; struct route_node *rn; struct interface *i; p = ifc->address; if (p->family != AF_INET) return 0; rn = route_node_lookup(rip_ifaddr_table, p); if (!rn) return 0; i = rn->info; if (!strncmp(i->name, ifp->name, INTERFACE_NAMSIZ)) { rn->info = NULL; route_unlock_node(rn); route_unlock_node(rn); } return 0; } static struct interface *rip_ifaddr_lookup_next(struct in_addr *addr) { struct prefix_ipv4 p; struct route_node *rn; struct interface *ifp; p.family = AF_INET; p.prefixlen = IPV4_MAX_BITLEN; p.prefix = *addr; rn = route_node_get(rip_ifaddr_table, (struct prefix *)&p); for (rn = route_next(rn); rn; rn = route_next(rn)) if (rn->info) break; if (rn && rn->info) { ifp = rn->info; *addr = rn->p.u.prefix4; route_unlock_node(rn); return ifp; } return NULL; } static struct interface *rip2IfLookup(struct variable *v, oid name[], size_t *length, struct in_addr *addr, int exact) { int len; struct interface *ifp; if (exact) { /* Check the length. */ if (*length - v->namelen != sizeof(struct in_addr)) return NULL; oid2in_addr(name + v->namelen, sizeof(struct in_addr), addr); return if_lookup_exact_address((void *)addr, AF_INET, VRF_DEFAULT); } else { len = *length - v->namelen; if (len > 4) len = 4; oid2in_addr(name + v->namelen, len, addr); ifp = rip_ifaddr_lookup_next(addr); if (ifp == NULL) return NULL; oid_copy_addr(name + v->namelen, addr, sizeof(struct in_addr)); *length = v->namelen + sizeof(struct in_addr); return ifp; } return NULL; } static struct rip_peer *rip2PeerLookup(struct variable *v, oid name[], size_t *length, struct in_addr *addr, int exact) { struct rip *rip; int len; struct rip_peer *peer; rip = rip_lookup_by_vrf_id(VRF_DEFAULT); if (!rip) return NULL; if (exact) { /* Check the length. */ if (*length - v->namelen != sizeof(struct in_addr) + 1) return NULL; oid2in_addr(name + v->namelen, sizeof(struct in_addr), addr); peer = rip_peer_lookup(rip, addr); if (peer->domain == (int)name[v->namelen + sizeof(struct in_addr)]) return peer; return NULL; } else { len = *length - v->namelen; if (len > 4) len = 4; oid2in_addr(name + v->namelen, len, addr); len = *length - v->namelen; peer = rip_peer_lookup(rip, addr); if (peer) { if ((len < (int)sizeof(struct in_addr) + 1) || (peer->domain > (int)name[v->namelen + sizeof(struct in_addr)])) { oid_copy_addr(name + v->namelen, &peer->addr, sizeof(struct in_addr)); name[v->namelen + sizeof(struct in_addr)] = peer->domain; *length = sizeof(struct in_addr) + v->namelen + 1; return peer; } } peer = rip_peer_lookup_next(rip, addr); if (!peer) return NULL; oid_copy_addr(name + v->namelen, &peer->addr, sizeof(struct in_addr)); name[v->namelen + sizeof(struct in_addr)] = peer->domain; *length = sizeof(struct in_addr) + v->namelen + 1; return peer; } return NULL; } static uint8_t *rip2IfStatEntry(struct variable *v, oid name[], size_t *length, int exact, size_t *var_len, WriteMethod **write_method) { struct interface *ifp; struct rip_interface *ri; static struct in_addr addr; static long valid = SNMP_VALID; if (smux_header_table(v, name, length, exact, var_len, write_method) == MATCH_FAILED) return NULL; memset(&addr, 0, sizeof(struct in_addr)); /* Lookup interface. */ ifp = rip2IfLookup(v, name, length, &addr, exact); if (!ifp) return NULL; /* Fetch rip_interface information. */ ri = ifp->info; switch (v->magic) { case RIP2IFSTATADDRESS: return SNMP_IPADDRESS(addr); break; case RIP2IFSTATRCVBADPACKETS: *var_len = sizeof(long); return (uint8_t *)&ri->recv_badpackets; case RIP2IFSTATRCVBADROUTES: *var_len = sizeof(long); return (uint8_t *)&ri->recv_badroutes; case RIP2IFSTATSENTUPDATES: *var_len = sizeof(long); return (uint8_t *)&ri->sent_updates; case RIP2IFSTATSTATUS: *var_len = sizeof(long); v->type = ASN_INTEGER; return (uint8_t *)&valid; default: return NULL; } return NULL; } static long rip2IfConfSend(struct rip_interface *ri) { #define doNotSend 1 #define ripVersion1 2 #define rip1Compatible 3 #define ripVersion2 4 #define ripV1Demand 5 #define ripV2Demand 6 if (!ri->running) return doNotSend; if (ri->ri_send & RIPv2) return ripVersion2; else if (ri->ri_send & RIPv1) return ripVersion1; else if (ri->rip) { if (ri->rip->version_send == RIPv2) return ripVersion2; else if (ri->rip->version_send == RIPv1) return ripVersion1; } return doNotSend; } static long rip2IfConfReceive(struct rip_interface *ri) { #define rip1 1 #define rip2 2 #define rip1OrRip2 3 #define doNotReceive 4 int recvv; if (!ri->running) return doNotReceive; recvv = (ri->ri_receive == RI_RIP_UNSPEC) ? ri->rip->version_recv : ri->ri_receive; if (recvv == RI_RIP_VERSION_1_AND_2) return rip1OrRip2; else if (recvv & RIPv2) return rip2; else if (recvv & RIPv1) return rip1; else return doNotReceive; } static uint8_t *rip2IfConfAddress(struct variable *v, oid name[], size_t *length, int exact, size_t *val_len, WriteMethod **write_method) { static struct in_addr addr; static long valid = SNMP_INVALID; static long domain = 0; static long config = 0; static unsigned int auth = 0; struct interface *ifp; struct rip_interface *ri; if (smux_header_table(v, name, length, exact, val_len, write_method) == MATCH_FAILED) return NULL; memset(&addr, 0, sizeof(struct in_addr)); /* Lookup interface. */ ifp = rip2IfLookup(v, name, length, &addr, exact); if (!ifp) return NULL; /* Fetch rip_interface information. */ ri = ifp->info; switch (v->magic) { case RIP2IFCONFADDRESS: *val_len = sizeof(struct in_addr); return (uint8_t *)&addr; case RIP2IFCONFDOMAIN: *val_len = 2; return (uint8_t *)&domain; case RIP2IFCONFAUTHTYPE: auth = ri->auth_type; *val_len = sizeof(long); v->type = ASN_INTEGER; return (uint8_t *)&auth; case RIP2IFCONFAUTHKEY: *val_len = 0; return (uint8_t *)&domain; case RIP2IFCONFSEND: config = rip2IfConfSend(ri); *val_len = sizeof(long); v->type = ASN_INTEGER; return (uint8_t *)&config; case RIP2IFCONFRECEIVE: config = rip2IfConfReceive(ri); *val_len = sizeof(long); v->type = ASN_INTEGER; return (uint8_t *)&config; case RIP2IFCONFDEFAULTMETRIC: *val_len = sizeof(long); v->type = ASN_INTEGER; return (uint8_t *)&ifp->metric; case RIP2IFCONFSTATUS: *val_len = sizeof(long); v->type = ASN_INTEGER; return (uint8_t *)&valid; case RIP2IFCONFSRCADDRESS: *val_len = sizeof(struct in_addr); return (uint8_t *)&addr; default: return NULL; } return NULL; } static uint8_t *rip2PeerTable(struct variable *v, oid name[], size_t *length, int exact, size_t *val_len, WriteMethod **write_method) { static struct in_addr addr; static int domain = 0; static int version; /* static time_t uptime; */ struct rip_peer *peer; if (smux_header_table(v, name, length, exact, val_len, write_method) == MATCH_FAILED) return NULL; memset(&addr, 0, sizeof(struct in_addr)); /* Lookup interface. */ peer = rip2PeerLookup(v, name, length, &addr, exact); if (!peer) return NULL; switch (v->magic) { case RIP2PEERADDRESS: *val_len = sizeof(struct in_addr); return (uint8_t *)&peer->addr; case RIP2PEERDOMAIN: *val_len = 2; return (uint8_t *)&domain; case RIP2PEERLASTUPDATE: #if 0 /* We don't know the SNMP agent startup time. We have two choices here: * - assume ripd startup time equals SNMP agent startup time * - don't support this variable, at all * Currently, we do the latter... */ *val_len = sizeof (time_t); uptime = peer->uptime; /* now - snmp_agent_startup - peer->uptime */ return (uint8_t *) &uptime; #else return (uint8_t *)NULL; #endif case RIP2PEERVERSION: *val_len = sizeof(int); version = peer->version; return (uint8_t *)&version; case RIP2PEERRCVBADPACKETS: *val_len = sizeof(int); return (uint8_t *)&peer->recv_badpackets; case RIP2PEERRCVBADROUTES: *val_len = sizeof(int); return (uint8_t *)&peer->recv_badroutes; default: return NULL; } return NULL; } /* Register RIPv2-MIB. */ static int rip_snmp_init(struct thread_master *master) { rip_ifaddr_table = route_table_init(); smux_init(master); REGISTER_MIB("mibII/rip", rip_variables, variable, rip_oid); return 0; } static int rip_snmp_module_init(void) { hook_register(rip_ifaddr_add, rip_snmp_ifaddr_add); hook_register(rip_ifaddr_del, rip_snmp_ifaddr_del); hook_register(frr_late_init, rip_snmp_init); return 0; } FRR_MODULE_SETUP(.name = "ripd_snmp", .version = FRR_VERSION, .description = "ripd AgentX SNMP module", .init = rip_snmp_module_init, ) frr-7.2.1/ripd/rip_zebra.c0000644000000000000000000001533613610377563012303 00000000000000/* RIPd and zebra interface. * Copyright (C) 1997, 1999 Kunihiro Ishiguro * * This file is part of GNU Zebra. * * GNU Zebra is free software; you can redistribute it and/or modify it * under the terms of the GNU General Public License as published by the * Free Software Foundation; either version 2, or (at your option) any * later version. * * GNU Zebra 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; see the file COPYING; if not, write to the Free Software * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA */ #include #include "command.h" #include "prefix.h" #include "table.h" #include "stream.h" #include "memory.h" #include "zclient.h" #include "log.h" #include "vrf.h" #include "ripd/ripd.h" #include "ripd/rip_debug.h" #include "ripd/rip_interface.h" /* All information about zebra. */ struct zclient *zclient = NULL; /* Send ECMP routes to zebra. */ static void rip_zebra_ipv4_send(struct rip *rip, struct route_node *rp, uint8_t cmd) { struct list *list = (struct list *)rp->info; struct zapi_route api; struct zapi_nexthop *api_nh; struct listnode *listnode = NULL; struct rip_info *rinfo = NULL; int count = 0; memset(&api, 0, sizeof(api)); api.vrf_id = rip->vrf->vrf_id; api.type = ZEBRA_ROUTE_RIP; api.safi = SAFI_UNICAST; SET_FLAG(api.message, ZAPI_MESSAGE_NEXTHOP); for (ALL_LIST_ELEMENTS_RO(list, listnode, rinfo)) { if (count >= MULTIPATH_NUM) break; api_nh = &api.nexthops[count]; api_nh->vrf_id = rip->vrf->vrf_id; api_nh->gate = rinfo->nh.gate; api_nh->type = NEXTHOP_TYPE_IPV4; if (cmd == ZEBRA_ROUTE_ADD) SET_FLAG(rinfo->flags, RIP_RTF_FIB); else UNSET_FLAG(rinfo->flags, RIP_RTF_FIB); count++; } api.prefix = rp->p; api.nexthop_num = count; rinfo = listgetdata(listhead(list)); SET_FLAG(api.message, ZAPI_MESSAGE_METRIC); api.metric = rinfo->metric; if (rinfo->distance && rinfo->distance != ZEBRA_RIP_DISTANCE_DEFAULT) { SET_FLAG(api.message, ZAPI_MESSAGE_DISTANCE); api.distance = rinfo->distance; } if (rinfo->tag) { SET_FLAG(api.message, ZAPI_MESSAGE_TAG); api.tag = rinfo->tag; } zclient_route_send(cmd, zclient, &api); if (IS_RIP_DEBUG_ZEBRA) { if (rip->ecmp) zlog_debug("%s: %s/%d nexthops %d", (cmd == ZEBRA_ROUTE_ADD) ? "Install into zebra" : "Delete from zebra", inet_ntoa(rp->p.u.prefix4), rp->p.prefixlen, count); else zlog_debug("%s: %s/%d", (cmd == ZEBRA_ROUTE_ADD) ? "Install into zebra" : "Delete from zebra", inet_ntoa(rp->p.u.prefix4), rp->p.prefixlen); } rip->counters.route_changes++; } /* Add/update ECMP routes to zebra. */ void rip_zebra_ipv4_add(struct rip *rip, struct route_node *rp) { rip_zebra_ipv4_send(rip, rp, ZEBRA_ROUTE_ADD); } /* Delete ECMP routes from zebra. */ void rip_zebra_ipv4_delete(struct rip *rip, struct route_node *rp) { rip_zebra_ipv4_send(rip, rp, ZEBRA_ROUTE_DELETE); } /* Zebra route add and delete treatment. */ static int rip_zebra_read_route(ZAPI_CALLBACK_ARGS) { struct rip *rip; struct zapi_route api; struct nexthop nh; rip = rip_lookup_by_vrf_id(vrf_id); if (!rip) return 0; if (zapi_route_decode(zclient->ibuf, &api) < 0) return -1; memset(&nh, 0, sizeof(nh)); nh.type = api.nexthops[0].type; nh.gate.ipv4 = api.nexthops[0].gate.ipv4; nh.ifindex = api.nexthops[0].ifindex; /* Then fetch IPv4 prefixes. */ if (cmd == ZEBRA_REDISTRIBUTE_ROUTE_ADD) rip_redistribute_add(rip, api.type, RIP_ROUTE_REDISTRIBUTE, (struct prefix_ipv4 *)&api.prefix, &nh, api.metric, api.distance, api.tag); else if (cmd == ZEBRA_REDISTRIBUTE_ROUTE_DEL) rip_redistribute_delete(rip, api.type, RIP_ROUTE_REDISTRIBUTE, (struct prefix_ipv4 *)&api.prefix, nh.ifindex); return 0; } void rip_redistribute_conf_update(struct rip *rip, int type) { zclient_redistribute(ZEBRA_REDISTRIBUTE_ADD, zclient, AFI_IP, type, 0, rip->vrf->vrf_id); } void rip_redistribute_conf_delete(struct rip *rip, int type) { if (zclient->sock > 0) zebra_redistribute_send(ZEBRA_REDISTRIBUTE_DELETE, zclient, AFI_IP, type, 0, rip->vrf->vrf_id); /* Remove the routes from RIP table. */ rip_redistribute_withdraw(rip, type); } int rip_redistribute_check(struct rip *rip, int type) { return rip->redist[type].enabled; } void rip_redistribute_enable(struct rip *rip) { for (int i = 0; i < ZEBRA_ROUTE_MAX; i++) { if (!rip_redistribute_check(rip, i)) continue; zebra_redistribute_send(ZEBRA_REDISTRIBUTE_ADD, zclient, AFI_IP, i, 0, rip->vrf->vrf_id); } } void rip_redistribute_disable(struct rip *rip) { for (int i = 0; i < ZEBRA_ROUTE_MAX; i++) { if (!rip_redistribute_check(rip, i)) continue; zebra_redistribute_send(ZEBRA_REDISTRIBUTE_DELETE, zclient, AFI_IP, i, 0, rip->vrf->vrf_id); } } void rip_show_redistribute_config(struct vty *vty, struct rip *rip) { for (int i = 0; i < ZEBRA_ROUTE_MAX; i++) { if (i == zclient->redist_default || !rip_redistribute_check(rip, i)) continue; vty_out(vty, " %s", zebra_route_string(i)); } } void rip_zebra_vrf_register(struct vrf *vrf) { if (vrf->vrf_id == VRF_DEFAULT) return; if (IS_RIP_DEBUG_EVENT) zlog_debug("%s: register VRF %s(%u) to zebra", __func__, vrf->name, vrf->vrf_id); zclient_send_reg_requests(zclient, vrf->vrf_id); } void rip_zebra_vrf_deregister(struct vrf *vrf) { if (vrf->vrf_id == VRF_DEFAULT) return; if (IS_RIP_DEBUG_EVENT) zlog_debug("%s: deregister VRF %s(%u) from zebra.", __func__, vrf->name, vrf->vrf_id); zclient_send_dereg_requests(zclient, vrf->vrf_id); } static void rip_zebra_connected(struct zclient *zclient) { zclient_send_reg_requests(zclient, VRF_DEFAULT); } void rip_zclient_init(struct thread_master *master) { /* Set default value to the zebra client structure. */ zclient = zclient_new(master, &zclient_options_default); zclient_init(zclient, ZEBRA_ROUTE_RIP, 0, &ripd_privs); zclient->zebra_connected = rip_zebra_connected; zclient->interface_add = rip_interface_add; zclient->interface_delete = rip_interface_delete; zclient->interface_address_add = rip_interface_address_add; zclient->interface_address_delete = rip_interface_address_delete; zclient->interface_up = rip_interface_up; zclient->interface_down = rip_interface_down; zclient->interface_vrf_update = rip_interface_vrf_update; zclient->redistribute_route_add = rip_zebra_read_route; zclient->redistribute_route_del = rip_zebra_read_route; } void rip_zclient_stop(void) { zclient_stop(zclient); zclient_free(zclient); } frr-7.2.1/ripd/ripd.c0000644000000000000000000030406013610377563011257 00000000000000/* RIP version 1 and 2. * Copyright (C) 2005 6WIND * Copyright (C) 1997, 98, 99 Kunihiro Ishiguro * * This file is part of GNU Zebra. * * GNU Zebra is free software; you can redistribute it and/or modify it * under the terms of the GNU General Public License as published by the * Free Software Foundation; either version 2, or (at your option) any * later version. * * GNU Zebra 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; see the file COPYING; if not, write to the Free Software * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA */ #include #include "vrf.h" #include "if.h" #include "command.h" #include "prefix.h" #include "table.h" #include "thread.h" #include "memory.h" #include "log.h" #include "stream.h" #include "filter.h" #include "sockunion.h" #include "sockopt.h" #include "routemap.h" #include "if_rmap.h" #include "plist.h" #include "distribute.h" #ifdef CRYPTO_INTERNAL #include "md5.h" #endif #include "keychain.h" #include "privs.h" #include "lib_errors.h" #include "northbound_cli.h" #include "ripd/ripd.h" #include "ripd/rip_debug.h" #include "ripd/rip_errors.h" #include "ripd/rip_interface.h" /* UDP receive buffer size */ #define RIP_UDP_RCV_BUF 41600 DEFINE_MGROUP(RIPD, "ripd") DEFINE_MTYPE_STATIC(RIPD, RIP, "RIP structure") DEFINE_MTYPE_STATIC(RIPD, RIP_VRF_NAME, "RIP VRF name") DEFINE_MTYPE_STATIC(RIPD, RIP_INFO, "RIP route info") DEFINE_MTYPE_STATIC(RIPD, RIP_DISTANCE, "RIP distance") /* Prototypes. */ static void rip_output_process(struct connected *, struct sockaddr_in *, int, uint8_t); static int rip_triggered_update(struct thread *); static int rip_update_jitter(unsigned long); static void rip_distance_table_node_cleanup(struct route_table *table, struct route_node *node); static void rip_instance_enable(struct rip *rip, struct vrf *vrf, int sock); static void rip_instance_disable(struct rip *rip); static void rip_distribute_update(struct distribute_ctx *ctx, struct distribute *dist); static void rip_if_rmap_update(struct if_rmap_ctx *ctx, struct if_rmap *if_rmap); /* RIP output routes type. */ enum { rip_all_route, rip_changed_route }; /* RIP command strings. */ static const struct message rip_msg[] = {{RIP_REQUEST, "REQUEST"}, {RIP_RESPONSE, "RESPONSE"}, {RIP_TRACEON, "TRACEON"}, {RIP_TRACEOFF, "TRACEOFF"}, {RIP_POLL, "POLL"}, {RIP_POLL_ENTRY, "POLL ENTRY"}, {0}}; /* Generate rb-tree of RIP instances. */ static inline int rip_instance_compare(const struct rip *a, const struct rip *b) { return strcmp(a->vrf_name, b->vrf_name); } RB_GENERATE(rip_instance_head, rip, entry, rip_instance_compare) struct rip_instance_head rip_instances = RB_INITIALIZER(&rip_instances); /* Utility function to set boradcast option to the socket. */ static int sockopt_broadcast(int sock) { int ret; int on = 1; ret = setsockopt(sock, SOL_SOCKET, SO_BROADCAST, (char *)&on, sizeof on); if (ret < 0) { zlog_warn("can't set sockopt SO_BROADCAST to socket %d", sock); return -1; } return 0; } int rip_route_rte(struct rip_info *rinfo) { return (rinfo->type == ZEBRA_ROUTE_RIP && rinfo->sub_type == RIP_ROUTE_RTE); } static struct rip_info *rip_info_new(void) { return XCALLOC(MTYPE_RIP_INFO, sizeof(struct rip_info)); } void rip_info_free(struct rip_info *rinfo) { XFREE(MTYPE_RIP_INFO, rinfo); } struct rip *rip_info_get_instance(const struct rip_info *rinfo) { return route_table_get_info(rinfo->rp->table); } /* RIP route garbage collect timer. */ static int rip_garbage_collect(struct thread *t) { struct rip_info *rinfo; struct route_node *rp; rinfo = THREAD_ARG(t); rinfo->t_garbage_collect = NULL; /* Off timeout timer. */ RIP_TIMER_OFF(rinfo->t_timeout); /* Get route_node pointer. */ rp = rinfo->rp; /* Unlock route_node. */ listnode_delete(rp->info, rinfo); if (list_isempty((struct list *)rp->info)) { list_delete((struct list **)&rp->info); route_unlock_node(rp); } /* Free RIP routing information. */ rip_info_free(rinfo); return 0; } static void rip_timeout_update(struct rip *rip, struct rip_info *rinfo); /* Add new route to the ECMP list. * RETURN: the new entry added in the list, or NULL if it is not the first * entry and ECMP is not allowed. */ struct rip_info *rip_ecmp_add(struct rip *rip, struct rip_info *rinfo_new) { struct route_node *rp = rinfo_new->rp; struct rip_info *rinfo = NULL; struct list *list = NULL; if (rp->info == NULL) rp->info = list_new(); list = (struct list *)rp->info; /* If ECMP is not allowed and some entry already exists in the list, * do nothing. */ if (listcount(list) && !rip->ecmp) return NULL; rinfo = rip_info_new(); memcpy(rinfo, rinfo_new, sizeof(struct rip_info)); listnode_add(list, rinfo); if (rip_route_rte(rinfo)) { rip_timeout_update(rip, rinfo); rip_zebra_ipv4_add(rip, rp); } /* Set the route change flag on the first entry. */ rinfo = listgetdata(listhead(list)); SET_FLAG(rinfo->flags, RIP_RTF_CHANGED); /* Signal the output process to trigger an update (see section 2.5). */ rip_event(rip, RIP_TRIGGERED_UPDATE, 0); return rinfo; } /* Replace the ECMP list with the new route. * RETURN: the new entry added in the list */ struct rip_info *rip_ecmp_replace(struct rip *rip, struct rip_info *rinfo_new) { struct route_node *rp = rinfo_new->rp; struct list *list = (struct list *)rp->info; struct rip_info *rinfo = NULL, *tmp_rinfo = NULL; struct listnode *node = NULL, *nextnode = NULL; if (list == NULL || listcount(list) == 0) return rip_ecmp_add(rip, rinfo_new); /* Get the first entry */ rinfo = listgetdata(listhead(list)); /* Learnt route replaced by a local one. Delete it from zebra. */ if (rip_route_rte(rinfo) && !rip_route_rte(rinfo_new)) if (CHECK_FLAG(rinfo->flags, RIP_RTF_FIB)) rip_zebra_ipv4_delete(rip, rp); /* Re-use the first entry, and delete the others. */ for (ALL_LIST_ELEMENTS(list, node, nextnode, tmp_rinfo)) if (tmp_rinfo != rinfo) { RIP_TIMER_OFF(tmp_rinfo->t_timeout); RIP_TIMER_OFF(tmp_rinfo->t_garbage_collect); list_delete_node(list, node); rip_info_free(tmp_rinfo); } RIP_TIMER_OFF(rinfo->t_timeout); RIP_TIMER_OFF(rinfo->t_garbage_collect); memcpy(rinfo, rinfo_new, sizeof(struct rip_info)); if (rip_route_rte(rinfo)) { rip_timeout_update(rip, rinfo); /* The ADD message implies an update. */ rip_zebra_ipv4_add(rip, rp); } /* Set the route change flag. */ SET_FLAG(rinfo->flags, RIP_RTF_CHANGED); /* Signal the output process to trigger an update (see section 2.5). */ rip_event(rip, RIP_TRIGGERED_UPDATE, 0); return rinfo; } /* Delete one route from the ECMP list. * RETURN: * null - the entry is freed, and other entries exist in the list * the entry - the entry is the last one in the list; its metric is set * to INFINITY, and the garbage collector is started for it */ struct rip_info *rip_ecmp_delete(struct rip *rip, struct rip_info *rinfo) { struct route_node *rp = rinfo->rp; struct list *list = (struct list *)rp->info; RIP_TIMER_OFF(rinfo->t_timeout); if (listcount(list) > 1) { /* Some other ECMP entries still exist. Just delete this entry. */ RIP_TIMER_OFF(rinfo->t_garbage_collect); listnode_delete(list, rinfo); if (rip_route_rte(rinfo) && CHECK_FLAG(rinfo->flags, RIP_RTF_FIB)) /* The ADD message implies the update. */ rip_zebra_ipv4_add(rip, rp); rip_info_free(rinfo); rinfo = NULL; } else { assert(rinfo == listgetdata(listhead(list))); /* This is the only entry left in the list. We must keep it in * the list for garbage collection time, with INFINITY metric. */ rinfo->metric = RIP_METRIC_INFINITY; RIP_TIMER_ON(rinfo->t_garbage_collect, rip_garbage_collect, rip->garbage_time); if (rip_route_rte(rinfo) && CHECK_FLAG(rinfo->flags, RIP_RTF_FIB)) rip_zebra_ipv4_delete(rip, rp); } /* Set the route change flag on the first entry. */ rinfo = listgetdata(listhead(list)); SET_FLAG(rinfo->flags, RIP_RTF_CHANGED); /* Signal the output process to trigger an update (see section 2.5). */ rip_event(rip, RIP_TRIGGERED_UPDATE, 0); return rinfo; } /* Timeout RIP routes. */ static int rip_timeout(struct thread *t) { struct rip_info *rinfo = THREAD_ARG(t); struct rip *rip = rip_info_get_instance(rinfo); rip_ecmp_delete(rip, rinfo); return 0; } static void rip_timeout_update(struct rip *rip, struct rip_info *rinfo) { if (rinfo->metric != RIP_METRIC_INFINITY) { RIP_TIMER_OFF(rinfo->t_timeout); thread_add_timer(master, rip_timeout, rinfo, rip->timeout_time, &rinfo->t_timeout); } } static int rip_filter(int rip_distribute, struct prefix_ipv4 *p, struct rip_interface *ri) { struct distribute *dist; struct access_list *alist; struct prefix_list *plist; int distribute = rip_distribute == RIP_FILTER_OUT ? DISTRIBUTE_V4_OUT : DISTRIBUTE_V4_IN; const char *inout = rip_distribute == RIP_FILTER_OUT ? "out" : "in"; /* Input distribute-list filtering. */ if (ri->list[rip_distribute]) { if (access_list_apply(ri->list[rip_distribute], (struct prefix *)p) == FILTER_DENY) { if (IS_RIP_DEBUG_PACKET) zlog_debug("%s/%d filtered by distribute %s", inet_ntoa(p->prefix), p->prefixlen, inout); return -1; } } if (ri->prefix[rip_distribute]) { if (prefix_list_apply(ri->prefix[rip_distribute], (struct prefix *)p) == PREFIX_DENY) { if (IS_RIP_DEBUG_PACKET) zlog_debug("%s/%d filtered by prefix-list %s", inet_ntoa(p->prefix), p->prefixlen, inout); return -1; } } /* All interface filter check. */ dist = distribute_lookup(ri->rip->distribute_ctx, NULL); if (dist) { if (dist->list[distribute]) { alist = access_list_lookup(AFI_IP, dist->list[distribute]); if (alist) { if (access_list_apply(alist, (struct prefix *)p) == FILTER_DENY) { if (IS_RIP_DEBUG_PACKET) zlog_debug( "%s/%d filtered by distribute %s", inet_ntoa(p->prefix), p->prefixlen, inout); return -1; } } } if (dist->prefix[distribute]) { plist = prefix_list_lookup(AFI_IP, dist->prefix[distribute]); if (plist) { if (prefix_list_apply(plist, (struct prefix *)p) == PREFIX_DENY) { if (IS_RIP_DEBUG_PACKET) zlog_debug( "%s/%d filtered by prefix-list %s", inet_ntoa(p->prefix), p->prefixlen, inout); return -1; } } } } return 0; } /* Check nexthop address validity. */ static int rip_nexthop_check(struct rip *rip, struct in_addr *addr) { struct interface *ifp; struct listnode *cnode; struct connected *ifc; struct prefix *p; /* If nexthop address matches local configured address then it is invalid nexthop. */ FOR_ALL_INTERFACES (rip->vrf, ifp) { for (ALL_LIST_ELEMENTS_RO(ifp->connected, cnode, ifc)) { p = ifc->address; if (p->family == AF_INET && IPV4_ADDR_SAME(&p->u.prefix4, addr)) return -1; } } return 0; } /* RIP add route to routing table. */ static void rip_rte_process(struct rte *rte, struct sockaddr_in *from, struct interface *ifp) { struct rip *rip; int ret; struct prefix_ipv4 p; struct route_node *rp; struct rip_info *rinfo = NULL, newinfo; struct rip_interface *ri; struct in_addr *nexthop; int same = 0; unsigned char old_dist, new_dist; struct list *list = NULL; struct listnode *node = NULL; /* Make prefix structure. */ memset(&p, 0, sizeof(struct prefix_ipv4)); p.family = AF_INET; p.prefix = rte->prefix; p.prefixlen = ip_masklen(rte->mask); /* Make sure mask is applied. */ apply_mask_ipv4(&p); ri = ifp->info; rip = ri->rip; /* Apply input filters. */ ret = rip_filter(RIP_FILTER_IN, &p, ri); if (ret < 0) return; memset(&newinfo, 0, sizeof(newinfo)); newinfo.type = ZEBRA_ROUTE_RIP; newinfo.sub_type = RIP_ROUTE_RTE; newinfo.nh.gate.ipv4 = rte->nexthop; newinfo.from = from->sin_addr; newinfo.nh.ifindex = ifp->ifindex; newinfo.nh.type = NEXTHOP_TYPE_IPV4_IFINDEX; newinfo.metric = rte->metric; newinfo.metric_out = rte->metric; /* XXX */ newinfo.tag = ntohs(rte->tag); /* XXX */ /* Modify entry according to the interface routemap. */ if (ri->routemap[RIP_FILTER_IN]) { /* The object should be of the type of rip_info */ ret = route_map_apply(ri->routemap[RIP_FILTER_IN], (struct prefix *)&p, RMAP_RIP, &newinfo); if (ret == RMAP_DENYMATCH) { if (IS_RIP_DEBUG_PACKET) zlog_debug( "RIP %s/%d is filtered by route-map in", inet_ntoa(p.prefix), p.prefixlen); return; } /* Get back the object */ rte->nexthop = newinfo.nexthop_out; rte->tag = htons(newinfo.tag_out); /* XXX */ rte->metric = newinfo.metric_out; /* XXX: the routemap uses the metric_out field */ } /* Once the entry has been validated, update the metric by adding the cost of the network on wich the message arrived. If the result is greater than infinity, use infinity (RFC2453 Sec. 3.9.2) */ /* Zebra ripd can handle offset-list in. */ ret = rip_offset_list_apply_in(&p, ifp, &rte->metric); /* If offset-list does not modify the metric use interface's metric. */ if (!ret) rte->metric += ifp->metric ? ifp->metric : 1; if (rte->metric > RIP_METRIC_INFINITY) rte->metric = RIP_METRIC_INFINITY; /* Set nexthop pointer. */ if (rte->nexthop.s_addr == 0) nexthop = &from->sin_addr; else nexthop = &rte->nexthop; /* Check if nexthop address is myself, then do nothing. */ if (rip_nexthop_check(rip, nexthop) < 0) { if (IS_RIP_DEBUG_PACKET) zlog_debug("Nexthop address %s is myself", inet_ntoa(*nexthop)); return; } /* Get index for the prefix. */ rp = route_node_get(rip->table, (struct prefix *)&p); newinfo.rp = rp; newinfo.nh.gate.ipv4 = *nexthop; newinfo.nh.type = NEXTHOP_TYPE_IPV4; newinfo.metric = rte->metric; newinfo.tag = ntohs(rte->tag); newinfo.distance = rip_distance_apply(rip, &newinfo); new_dist = newinfo.distance ? newinfo.distance : ZEBRA_RIP_DISTANCE_DEFAULT; /* Check to see whether there is already RIP route on the table. */ if ((list = rp->info) != NULL) for (ALL_LIST_ELEMENTS_RO(list, node, rinfo)) { /* Need to compare with redistributed entry or local * entry */ if (!rip_route_rte(rinfo)) break; if (IPV4_ADDR_SAME(&rinfo->from, &from->sin_addr) && IPV4_ADDR_SAME(&rinfo->nh.gate.ipv4, nexthop)) break; if (!listnextnode(node)) { /* Not found in the list */ if (rte->metric > rinfo->metric) { /* New route has a greater metric. * Discard it. */ route_unlock_node(rp); return; } if (rte->metric < rinfo->metric) /* New route has a smaller metric. * Replace the ECMP list * with the new one in below. */ break; /* Metrics are same. We compare the distances. */ old_dist = rinfo->distance ? rinfo->distance : ZEBRA_RIP_DISTANCE_DEFAULT; if (new_dist > old_dist) { /* New route has a greater distance. * Discard it. */ route_unlock_node(rp); return; } if (new_dist < old_dist) /* New route has a smaller distance. * Replace the ECMP list * with the new one in below. */ break; /* Metrics and distances are both same. Keep * "rinfo" null and * the new route is added in the ECMP list in * below. */ } } if (rinfo) { /* Local static route. */ if (rinfo->type == ZEBRA_ROUTE_RIP && ((rinfo->sub_type == RIP_ROUTE_STATIC) || (rinfo->sub_type == RIP_ROUTE_DEFAULT)) && rinfo->metric != RIP_METRIC_INFINITY) { route_unlock_node(rp); return; } /* Redistributed route check. */ if (rinfo->type != ZEBRA_ROUTE_RIP && rinfo->metric != RIP_METRIC_INFINITY) { old_dist = rinfo->distance; /* Only routes directly connected to an interface * (nexthop == 0) * may have a valid NULL distance */ if (rinfo->nh.gate.ipv4.s_addr != 0) old_dist = old_dist ? old_dist : ZEBRA_RIP_DISTANCE_DEFAULT; /* If imported route does not have STRICT precedence, mark it as a ghost */ if (new_dist <= old_dist && rte->metric != RIP_METRIC_INFINITY) rip_ecmp_replace(rip, &newinfo); route_unlock_node(rp); return; } } if (!rinfo) { if (rp->info) route_unlock_node(rp); /* Now, check to see whether there is already an explicit route for the destination prefix. If there is no such route, add this route to the routing table, unless the metric is infinity (there is no point in adding a route which unusable). */ if (rte->metric != RIP_METRIC_INFINITY) rip_ecmp_add(rip, &newinfo); } else { /* Route is there but we are not sure the route is RIP or not. */ /* If there is an existing route, compare the next hop address to the address of the router from which the datagram came. If this datagram is from the same router as the existing route, reinitialize the timeout. */ same = (IPV4_ADDR_SAME(&rinfo->from, &from->sin_addr) && (rinfo->nh.ifindex == ifp->ifindex)); old_dist = rinfo->distance ? rinfo->distance : ZEBRA_RIP_DISTANCE_DEFAULT; /* Next, compare the metrics. If the datagram is from the same router as the existing route, and the new metric is different than the old one; or, if the new metric is lower than the old one, or if the tag has been changed; or if there is a route with a lower administrave distance; or an update of the distance on the actual route; do the following actions: */ if ((same && rinfo->metric != rte->metric) || (rte->metric < rinfo->metric) || ((same) && (rinfo->metric == rte->metric) && (newinfo.tag != rinfo->tag)) || (old_dist > new_dist) || ((old_dist != new_dist) && same)) { if (listcount(list) == 1) { if (newinfo.metric != RIP_METRIC_INFINITY) rip_ecmp_replace(rip, &newinfo); else rip_ecmp_delete(rip, rinfo); } else { if (newinfo.metric < rinfo->metric) rip_ecmp_replace(rip, &newinfo); else if (newinfo.metric > rinfo->metric) rip_ecmp_delete(rip, rinfo); else if (new_dist < old_dist) rip_ecmp_replace(rip, &newinfo); else if (new_dist > old_dist) rip_ecmp_delete(rip, rinfo); else { int update = CHECK_FLAG(rinfo->flags, RIP_RTF_FIB) ? 1 : 0; assert(newinfo.metric != RIP_METRIC_INFINITY); RIP_TIMER_OFF(rinfo->t_timeout); RIP_TIMER_OFF(rinfo->t_garbage_collect); memcpy(rinfo, &newinfo, sizeof(struct rip_info)); rip_timeout_update(rip, rinfo); if (update) rip_zebra_ipv4_add(rip, rp); /* - Set the route change flag on the * first entry. */ rinfo = listgetdata(listhead(list)); SET_FLAG(rinfo->flags, RIP_RTF_CHANGED); rip_event(rip, RIP_TRIGGERED_UPDATE, 0); } } } else /* same & no change */ rip_timeout_update(rip, rinfo); /* Unlock tempolary lock of the route. */ route_unlock_node(rp); } } /* Dump RIP packet */ static void rip_packet_dump(struct rip_packet *packet, int size, const char *sndrcv) { caddr_t lim; struct rte *rte; const char *command_str; char pbuf[BUFSIZ], nbuf[BUFSIZ]; uint8_t netmask = 0; uint8_t *p; /* Set command string. */ if (packet->command > 0 && packet->command < RIP_COMMAND_MAX) command_str = lookup_msg(rip_msg, packet->command, NULL); else command_str = "unknown"; /* Dump packet header. */ zlog_debug("%s %s version %d packet size %d", sndrcv, command_str, packet->version, size); /* Dump each routing table entry. */ rte = packet->rte; for (lim = (caddr_t)packet + size; (caddr_t)rte < lim; rte++) { if (packet->version == RIPv2) { netmask = ip_masklen(rte->mask); if (rte->family == htons(RIP_FAMILY_AUTH)) { if (rte->tag == htons(RIP_AUTH_SIMPLE_PASSWORD)) { p = (uint8_t *)&rte->prefix; zlog_debug( " family 0x%X type %d auth string: %s", ntohs(rte->family), ntohs(rte->tag), p); } else if (rte->tag == htons(RIP_AUTH_MD5)) { struct rip_md5_info *md5; md5 = (struct rip_md5_info *)&packet ->rte; zlog_debug( " family 0x%X type %d (MD5 authentication)", ntohs(md5->family), ntohs(md5->type)); zlog_debug( " RIP-2 packet len %d Key ID %d" " Auth Data len %d", ntohs(md5->packet_len), md5->keyid, md5->auth_len); zlog_debug(" Sequence Number %ld", (unsigned long)ntohl( md5->sequence)); } else if (rte->tag == htons(RIP_AUTH_DATA)) { p = (uint8_t *)&rte->prefix; zlog_debug( " family 0x%X type %d (MD5 data)", ntohs(rte->family), ntohs(rte->tag)); zlog_debug( " MD5: %02X%02X%02X%02X%02X%02X%02X%02X" "%02X%02X%02X%02X%02X%02X%02X%02X", p[0], p[1], p[2], p[3], p[4], p[5], p[6], p[7], p[8], p[9], p[10], p[11], p[12], p[13], p[14], p[15]); } else { zlog_debug( " family 0x%X type %d (Unknown auth type)", ntohs(rte->family), ntohs(rte->tag)); } } else zlog_debug( " %s/%d -> %s family %d tag %" ROUTE_TAG_PRI " metric %ld", inet_ntop(AF_INET, &rte->prefix, pbuf, BUFSIZ), netmask, inet_ntop(AF_INET, &rte->nexthop, nbuf, BUFSIZ), ntohs(rte->family), (route_tag_t)ntohs(rte->tag), (unsigned long)ntohl(rte->metric)); } else { zlog_debug( " %s family %d tag %" ROUTE_TAG_PRI " metric %ld", inet_ntop(AF_INET, &rte->prefix, pbuf, BUFSIZ), ntohs(rte->family), (route_tag_t)ntohs(rte->tag), (unsigned long)ntohl(rte->metric)); } } } /* Check if the destination address is valid (unicast; not net 0 or 127) (RFC2453 Section 3.9.2 - Page 26). But we don't check net 0 because we accept default route. */ static int rip_destination_check(struct in_addr addr) { uint32_t destination; /* Convert to host byte order. */ destination = ntohl(addr.s_addr); if (IPV4_NET127(destination)) return 0; /* Net 0 may match to the default route. */ if (IPV4_NET0(destination) && destination != 0) return 0; /* Unicast address must belong to class A, B, C. */ if (IN_CLASSA(destination)) return 1; if (IN_CLASSB(destination)) return 1; if (IN_CLASSC(destination)) return 1; return 0; } /* RIP version 2 authentication. */ static int rip_auth_simple_password(struct rte *rte, struct sockaddr_in *from, struct interface *ifp) { struct rip_interface *ri; char *auth_str = (char *)rte + offsetof(struct rte, prefix); int i; /* reject passwords with zeros in the middle of the string */ for (i = strnlen(auth_str, 16); i < 16; i++) { if (auth_str[i] != '\0') return 0; } if (IS_RIP_DEBUG_EVENT) zlog_debug("RIPv2 simple password authentication from %s", inet_ntoa(from->sin_addr)); ri = ifp->info; if (ri->auth_type != RIP_AUTH_SIMPLE_PASSWORD || rte->tag != htons(RIP_AUTH_SIMPLE_PASSWORD)) return 0; /* Simple password authentication. */ if (ri->auth_str) { if (strncmp(auth_str, ri->auth_str, 16) == 0) return 1; } if (ri->key_chain) { struct keychain *keychain; struct key *key; keychain = keychain_lookup(ri->key_chain); if (keychain == NULL || keychain->key == NULL) return 0; key = key_match_for_accept(keychain, auth_str); if (key) return 1; } return 0; } /* RIP version 2 authentication with MD5. */ static int rip_auth_md5(struct rip_packet *packet, struct sockaddr_in *from, int length, struct interface *ifp) { struct rip_interface *ri; struct rip_md5_info *md5; struct rip_md5_data *md5data; struct keychain *keychain; struct key *key; #ifdef CRYPTO_OPENSSL EVP_MD_CTX *ctx; #elif CRYPTO_INTERNAL MD5_CTX ctx; #endif uint8_t digest[RIP_AUTH_MD5_SIZE]; uint16_t packet_len; char auth_str[RIP_AUTH_MD5_SIZE] = {}; if (IS_RIP_DEBUG_EVENT) zlog_debug("RIPv2 MD5 authentication from %s", inet_ntoa(from->sin_addr)); ri = ifp->info; md5 = (struct rip_md5_info *)&packet->rte; /* Check auth type. */ if (ri->auth_type != RIP_AUTH_MD5 || md5->type != htons(RIP_AUTH_MD5)) return 0; /* If the authentication length is less than 16, then it must be wrong * for * any interpretation of rfc2082. Some implementations also interpret * this as RIP_HEADER_SIZE+ RIP_AUTH_MD5_SIZE, aka * RIP_AUTH_MD5_COMPAT_SIZE. */ if (!((md5->auth_len == RIP_AUTH_MD5_SIZE) || (md5->auth_len == RIP_AUTH_MD5_COMPAT_SIZE))) { if (IS_RIP_DEBUG_EVENT) zlog_debug( "RIPv2 MD5 authentication, strange authentication " "length field %d", md5->auth_len); return 0; } /* grab and verify check packet length */ packet_len = ntohs(md5->packet_len); if (packet_len > (length - RIP_HEADER_SIZE - RIP_AUTH_MD5_SIZE)) { if (IS_RIP_DEBUG_EVENT) zlog_debug( "RIPv2 MD5 authentication, packet length field %d " "greater than received length %d!", md5->packet_len, length); return 0; } /* retrieve authentication data */ md5data = (struct rip_md5_data *)(((uint8_t *)packet) + packet_len); if (ri->key_chain) { keychain = keychain_lookup(ri->key_chain); if (keychain == NULL) return 0; key = key_lookup_for_accept(keychain, md5->keyid); if (key == NULL || key->string == NULL) return 0; strlcpy(auth_str, key->string, sizeof(auth_str)); } else if (ri->auth_str) strlcpy(auth_str, ri->auth_str, sizeof(auth_str)); if (auth_str[0] == 0) return 0; /* MD5 digest authentication. */ #ifdef CRYPTO_OPENSSL unsigned int md5_size = RIP_AUTH_MD5_SIZE; ctx = EVP_MD_CTX_new(); EVP_DigestInit(ctx, EVP_md5()); EVP_DigestUpdate(ctx, packet, packet_len + RIP_HEADER_SIZE); EVP_DigestUpdate(ctx, auth_str, RIP_AUTH_MD5_SIZE); EVP_DigestFinal(ctx, digest, &md5_size); EVP_MD_CTX_free(ctx); #elif CRYPTO_INTERNAL memset(&ctx, 0, sizeof(ctx)); MD5Init(&ctx); MD5Update(&ctx, packet, packet_len + RIP_HEADER_SIZE); MD5Update(&ctx, auth_str, RIP_AUTH_MD5_SIZE); MD5Final(digest, &ctx); #endif if (memcmp(md5data->digest, digest, RIP_AUTH_MD5_SIZE) == 0) return packet_len; else return 0; } /* Pick correct auth string for sends, prepare auth_str buffer for use. * (left justified and padded). * * presumes one of ri or key is valid, and that the auth strings they point * to are nul terminated. If neither are present, auth_str will be fully * zero padded. * */ static void rip_auth_prepare_str_send(struct rip_interface *ri, struct key *key, char *auth_str, int len) { assert(ri || key); memset(auth_str, 0, len); if (key && key->string) strlcpy(auth_str, key->string, len); else if (ri->auth_str) strlcpy(auth_str, ri->auth_str, len); return; } /* Write RIPv2 simple password authentication information * * auth_str is presumed to be 2 bytes and correctly prepared * (left justified and zero padded). */ static void rip_auth_simple_write(struct stream *s, char *auth_str, int len) { assert(s && len == RIP_AUTH_SIMPLE_SIZE); stream_putw(s, RIP_FAMILY_AUTH); stream_putw(s, RIP_AUTH_SIMPLE_PASSWORD); stream_put(s, auth_str, RIP_AUTH_SIMPLE_SIZE); return; } /* write RIPv2 MD5 "authentication header" * (uses the auth key data field) * * Digest offset field is set to 0. * * returns: offset of the digest offset field, which must be set when * length to the auth-data MD5 digest is known. */ static size_t rip_auth_md5_ah_write(struct stream *s, struct rip_interface *ri, struct key *key) { size_t doff = 0; assert(s && ri && ri->auth_type == RIP_AUTH_MD5); /* MD5 authentication. */ stream_putw(s, RIP_FAMILY_AUTH); stream_putw(s, RIP_AUTH_MD5); /* MD5 AH digest offset field. * * Set to placeholder value here, to true value when RIP-2 Packet length * is known. Actual value is set in .....(). */ doff = stream_get_endp(s); stream_putw(s, 0); /* Key ID. */ if (key) stream_putc(s, key->index % 256); else stream_putc(s, 1); /* Auth Data Len. Set 16 for MD5 authentication data. Older ripds * however expect RIP_HEADER_SIZE + RIP_AUTH_MD5_SIZE so we allow for * this * to be configurable. */ stream_putc(s, ri->md5_auth_len); /* Sequence Number (non-decreasing). */ /* RFC2080: The value used in the sequence number is arbitrary, but two suggestions are the time of the message's creation or a simple message counter. */ stream_putl(s, time(NULL)); /* Reserved field must be zero. */ stream_putl(s, 0); stream_putl(s, 0); return doff; } /* If authentication is in used, write the appropriate header * returns stream offset to which length must later be written * or 0 if this is not required */ static size_t rip_auth_header_write(struct stream *s, struct rip_interface *ri, struct key *key, char *auth_str, int len) { assert(ri->auth_type != RIP_NO_AUTH); switch (ri->auth_type) { case RIP_AUTH_SIMPLE_PASSWORD: rip_auth_prepare_str_send(ri, key, auth_str, len); rip_auth_simple_write(s, auth_str, len); return 0; case RIP_AUTH_MD5: return rip_auth_md5_ah_write(s, ri, key); } assert(1); return 0; } /* Write RIPv2 MD5 authentication data trailer */ static void rip_auth_md5_set(struct stream *s, struct rip_interface *ri, size_t doff, char *auth_str, int authlen) { unsigned long len; #ifdef CRYPTO_OPENSSL EVP_MD_CTX *ctx; #elif CRYPTO_INTERNAL MD5_CTX ctx; #endif unsigned char digest[RIP_AUTH_MD5_SIZE]; /* Make it sure this interface is configured as MD5 authentication. */ assert((ri->auth_type == RIP_AUTH_MD5) && (authlen == RIP_AUTH_MD5_SIZE)); assert(doff > 0); /* Get packet length. */ len = stream_get_endp(s); /* Check packet length. */ if (len < (RIP_HEADER_SIZE + RIP_RTE_SIZE)) { flog_err( EC_RIP_PACKET, "rip_auth_md5_set(): packet length %ld is less than minimum length.", len); return; } /* Set the digest offset length in the header */ stream_putw_at(s, doff, len); /* Set authentication data. */ stream_putw(s, RIP_FAMILY_AUTH); stream_putw(s, RIP_AUTH_DATA); /* Generate a digest for the RIP packet. */ #ifdef CRYPTO_OPENSSL unsigned int md5_size = RIP_AUTH_MD5_SIZE; ctx = EVP_MD_CTX_new(); EVP_DigestInit(ctx, EVP_md5()); EVP_DigestUpdate(ctx, STREAM_DATA(s), stream_get_endp(s)); EVP_DigestUpdate(ctx, auth_str, RIP_AUTH_MD5_SIZE); EVP_DigestFinal(ctx, digest, &md5_size); EVP_MD_CTX_free(ctx); #elif CRYPTO_INTERNAL memset(&ctx, 0, sizeof(ctx)); MD5Init(&ctx); MD5Update(&ctx, STREAM_DATA(s), stream_get_endp(s)); MD5Update(&ctx, auth_str, RIP_AUTH_MD5_SIZE); MD5Final(digest, &ctx); #endif /* Copy the digest to the packet. */ stream_write(s, digest, RIP_AUTH_MD5_SIZE); } /* RIP routing information. */ static void rip_response_process(struct rip_packet *packet, int size, struct sockaddr_in *from, struct connected *ifc) { struct rip_interface *ri = ifc->ifp->info; struct rip *rip = ri->rip; caddr_t lim; struct rte *rte; struct prefix_ipv4 ifaddr; struct prefix_ipv4 ifaddrclass; int subnetted; memset(&ifaddr, 0, sizeof(ifaddr)); /* We don't know yet. */ subnetted = -1; /* The Response must be ignored if it is not from the RIP port. (RFC2453 - Sec. 3.9.2)*/ if (from->sin_port != htons(RIP_PORT_DEFAULT)) { zlog_info("response doesn't come from RIP port: %d", from->sin_port); rip_peer_bad_packet(rip, from); return; } /* The datagram's IPv4 source address should be checked to see whether the datagram is from a valid neighbor; the source of the datagram must be on a directly connected network (RFC2453 - Sec. 3.9.2) */ if (if_lookup_address((void *)&from->sin_addr, AF_INET, rip->vrf->vrf_id) == NULL) { zlog_info( "This datagram doesn't came from a valid neighbor: %s", inet_ntoa(from->sin_addr)); rip_peer_bad_packet(rip, from); return; } /* It is also worth checking to see whether the response is from one of the router's own addresses. */ ; /* Alredy done in rip_read () */ /* Update RIP peer. */ rip_peer_update(rip, from, packet->version); /* Set RTE pointer. */ rte = packet->rte; for (lim = (caddr_t)packet + size; (caddr_t)rte < lim; rte++) { /* RIPv2 authentication check. */ /* If the Address Family Identifier of the first (and only the first) entry in the message is 0xFFFF, then the remainder of the entry contains the authentication. */ /* If the packet gets here it means authentication enabled */ /* Check is done in rip_read(). So, just skipping it */ if (packet->version == RIPv2 && rte == packet->rte && rte->family == htons(RIP_FAMILY_AUTH)) continue; if (rte->family != htons(AF_INET)) { /* Address family check. RIP only supports AF_INET. */ zlog_info("Unsupported family %d from %s.", ntohs(rte->family), inet_ntoa(from->sin_addr)); continue; } /* - is the destination address valid (e.g., unicast; not net 0 or 127) */ if (!rip_destination_check(rte->prefix)) { zlog_info( "Network is net 0 or net 127 or it is not unicast network"); rip_peer_bad_route(rip, from); continue; } /* Convert metric value to host byte order. */ rte->metric = ntohl(rte->metric); /* - is the metric valid (i.e., between 1 and 16, inclusive) */ if (!(rte->metric >= 1 && rte->metric <= 16)) { zlog_info("Route's metric is not in the 1-16 range."); rip_peer_bad_route(rip, from); continue; } /* RIPv1 does not have nexthop value. */ if (packet->version == RIPv1 && rte->nexthop.s_addr != 0) { zlog_info("RIPv1 packet with nexthop value %s", inet_ntoa(rte->nexthop)); rip_peer_bad_route(rip, from); continue; } /* That is, if the provided information is ignored, a possibly sub-optimal, but absolutely valid, route may be taken. If the received Next Hop is not directly reachable, it should be treated as 0.0.0.0. */ if (packet->version == RIPv2 && rte->nexthop.s_addr != 0) { uint32_t addrval; /* Multicast address check. */ addrval = ntohl(rte->nexthop.s_addr); if (IN_CLASSD(addrval)) { zlog_info( "Nexthop %s is multicast address, skip this rte", inet_ntoa(rte->nexthop)); continue; } if (!if_lookup_address((void *)&rte->nexthop, AF_INET, rip->vrf->vrf_id)) { struct route_node *rn; struct rip_info *rinfo; rn = route_node_match_ipv4(rip->table, &rte->nexthop); if (rn) { rinfo = rn->info; if (rinfo->type == ZEBRA_ROUTE_RIP && rinfo->sub_type == RIP_ROUTE_RTE) { if (IS_RIP_DEBUG_EVENT) zlog_debug( "Next hop %s is on RIP network. Set nexthop to the packet's originator", inet_ntoa( rte->nexthop)); rte->nexthop = rinfo->from; } else { if (IS_RIP_DEBUG_EVENT) zlog_debug( "Next hop %s is not directly reachable. Treat it as 0.0.0.0", inet_ntoa( rte->nexthop)); rte->nexthop.s_addr = 0; } route_unlock_node(rn); } else { if (IS_RIP_DEBUG_EVENT) zlog_debug( "Next hop %s is not directly reachable. Treat it as 0.0.0.0", inet_ntoa( rte->nexthop)); rte->nexthop.s_addr = 0; } } } /* For RIPv1, there won't be a valid netmask. This is a best guess at the masks. If everyone was using old Ciscos before the 'ip subnet zero' option, it would be almost right too :-) Cisco summarize ripv1 advertisements to the classful boundary (/16 for class B's) except when the RIP packet does to inside the classful network in question. */ if ((packet->version == RIPv1 && rte->prefix.s_addr != 0) || (packet->version == RIPv2 && (rte->prefix.s_addr != 0 && rte->mask.s_addr == 0))) { uint32_t destination; if (subnetted == -1) { memcpy(&ifaddr, ifc->address, sizeof(struct prefix_ipv4)); memcpy(&ifaddrclass, &ifaddr, sizeof(struct prefix_ipv4)); apply_classful_mask_ipv4(&ifaddrclass); subnetted = 0; if (ifaddr.prefixlen > ifaddrclass.prefixlen) subnetted = 1; } destination = ntohl(rte->prefix.s_addr); if (IN_CLASSA(destination)) masklen2ip(8, &rte->mask); else if (IN_CLASSB(destination)) masklen2ip(16, &rte->mask); else if (IN_CLASSC(destination)) masklen2ip(24, &rte->mask); if (subnetted == 1) masklen2ip(ifaddrclass.prefixlen, (struct in_addr *)&destination); if ((subnetted == 1) && ((rte->prefix.s_addr & destination) == ifaddrclass.prefix.s_addr)) { masklen2ip(ifaddr.prefixlen, &rte->mask); if ((rte->prefix.s_addr & rte->mask.s_addr) != rte->prefix.s_addr) masklen2ip(32, &rte->mask); if (IS_RIP_DEBUG_EVENT) zlog_debug("Subnetted route %s", inet_ntoa(rte->prefix)); } else { if ((rte->prefix.s_addr & rte->mask.s_addr) != rte->prefix.s_addr) continue; } if (IS_RIP_DEBUG_EVENT) { zlog_debug("Resultant route %s", inet_ntoa(rte->prefix)); zlog_debug("Resultant mask %s", inet_ntoa(rte->mask)); } } /* In case of RIPv2, if prefix in RTE is not netmask applied one ignore the entry. */ if ((packet->version == RIPv2) && (rte->mask.s_addr != 0) && ((rte->prefix.s_addr & rte->mask.s_addr) != rte->prefix.s_addr)) { zlog_warn( "RIPv2 address %s is not mask /%d applied one", inet_ntoa(rte->prefix), ip_masklen(rte->mask)); rip_peer_bad_route(rip, from); continue; } /* Default route's netmask is ignored. */ if (packet->version == RIPv2 && (rte->prefix.s_addr == 0) && (rte->mask.s_addr != 0)) { if (IS_RIP_DEBUG_EVENT) zlog_debug( "Default route with non-zero netmask. Set zero to netmask"); rte->mask.s_addr = 0; } /* Routing table updates. */ rip_rte_process(rte, from, ifc->ifp); } } /* Make socket for RIP protocol. */ int rip_create_socket(struct vrf *vrf) { int ret; int sock; struct sockaddr_in addr; const char *vrf_dev = NULL; memset(&addr, 0, sizeof(struct sockaddr_in)); addr.sin_family = AF_INET; addr.sin_addr.s_addr = INADDR_ANY; #ifdef HAVE_STRUCT_SOCKADDR_IN_SIN_LEN addr.sin_len = sizeof(struct sockaddr_in); #endif /* HAVE_STRUCT_SOCKADDR_IN_SIN_LEN */ /* sending port must always be the RIP port */ addr.sin_port = htons(RIP_PORT_DEFAULT); /* Make datagram socket. */ if (vrf->vrf_id != VRF_DEFAULT) vrf_dev = vrf->name; frr_with_privs(&ripd_privs) { sock = vrf_socket(AF_INET, SOCK_DGRAM, IPPROTO_UDP, vrf->vrf_id, vrf_dev); if (sock < 0) { flog_err_sys(EC_LIB_SOCKET, "Cannot create UDP socket: %s", safe_strerror(errno)); return -1; } } sockopt_broadcast(sock); sockopt_reuseaddr(sock); sockopt_reuseport(sock); setsockopt_ipv4_multicast_loop(sock, 0); #ifdef IPTOS_PREC_INTERNETCONTROL setsockopt_ipv4_tos(sock, IPTOS_PREC_INTERNETCONTROL); #endif setsockopt_so_recvbuf(sock, RIP_UDP_RCV_BUF); frr_with_privs(&ripd_privs) { if ((ret = bind(sock, (struct sockaddr *)&addr, sizeof(addr))) < 0) { zlog_err("%s: Can't bind socket %d to %s port %d: %s", __func__, sock, inet_ntoa(addr.sin_addr), (int)ntohs(addr.sin_port), safe_strerror(errno)); close(sock); return ret; } } return sock; } /* RIP packet send to destination address, on interface denoted by * by connected argument. NULL to argument denotes destination should be * should be RIP multicast group */ static int rip_send_packet(uint8_t *buf, int size, struct sockaddr_in *to, struct connected *ifc) { struct rip_interface *ri; struct rip *rip; int ret; struct sockaddr_in sin; struct msghdr msg; struct iovec iov; #ifdef GNU_LINUX struct cmsghdr *cmsgptr; char adata[256] = {}; struct in_pktinfo *pkt; #endif /* GNU_LINUX */ assert(ifc != NULL); ri = ifc->ifp->info; rip = ri->rip; if (IS_RIP_DEBUG_PACKET) { #define ADDRESS_SIZE 20 char dst[ADDRESS_SIZE]; if (to) { strlcpy(dst, inet_ntoa(to->sin_addr), sizeof(dst)); } else { sin.sin_addr.s_addr = htonl(INADDR_RIP_GROUP); strlcpy(dst, inet_ntoa(sin.sin_addr), sizeof(dst)); } #undef ADDRESS_SIZE zlog_debug("rip_send_packet %s > %s (%s)", inet_ntoa(ifc->address->u.prefix4), dst, ifc->ifp->name); } if (CHECK_FLAG(ifc->flags, ZEBRA_IFA_SECONDARY)) { /* * ZEBRA_IFA_SECONDARY is set on linux when an interface is * configured * with multiple addresses on the same subnet: the first address * on the subnet is configured "primary", and all subsequent * addresses * on that subnet are treated as "secondary" addresses. * In order to avoid routing-table bloat on other rip listeners, * we do not send out RIP packets with ZEBRA_IFA_SECONDARY * source addrs. * XXX Since Linux is the only system for which the * ZEBRA_IFA_SECONDARY * flag is set, we would end up sending a packet for a * "secondary" * source address on non-linux systems. */ if (IS_RIP_DEBUG_PACKET) zlog_debug("duplicate dropped"); return 0; } /* Make destination address. */ memset(&sin, 0, sizeof(struct sockaddr_in)); sin.sin_family = AF_INET; #ifdef HAVE_STRUCT_SOCKADDR_IN_SIN_LEN sin.sin_len = sizeof(struct sockaddr_in); #endif /* HAVE_STRUCT_SOCKADDR_IN_SIN_LEN */ /* When destination is specified, use it's port and address. */ if (to) { sin.sin_port = to->sin_port; sin.sin_addr = to->sin_addr; } else { sin.sin_port = htons(RIP_PORT_DEFAULT); sin.sin_addr.s_addr = htonl(INADDR_RIP_GROUP); rip_interface_multicast_set(rip->sock, ifc); } memset(&msg, 0, sizeof(msg)); msg.msg_name = (void *)&sin; msg.msg_namelen = sizeof(struct sockaddr_in); msg.msg_iov = &iov; msg.msg_iovlen = 1; iov.iov_base = buf; iov.iov_len = size; #ifdef GNU_LINUX msg.msg_control = (void *)adata; msg.msg_controllen = CMSG_SPACE(sizeof(struct in_pktinfo)); cmsgptr = (struct cmsghdr *)adata; cmsgptr->cmsg_len = CMSG_LEN(sizeof(struct in_pktinfo)); cmsgptr->cmsg_level = IPPROTO_IP; cmsgptr->cmsg_type = IP_PKTINFO; pkt = (struct in_pktinfo *)CMSG_DATA(cmsgptr); pkt->ipi_ifindex = ifc->ifp->ifindex; #endif /* GNU_LINUX */ ret = sendmsg(rip->sock, &msg, 0); if (IS_RIP_DEBUG_EVENT) zlog_debug("SEND to %s.%d", inet_ntoa(sin.sin_addr), ntohs(sin.sin_port)); if (ret < 0) zlog_warn("can't send packet : %s", safe_strerror(errno)); return ret; } /* Add redistributed route to RIP table. */ void rip_redistribute_add(struct rip *rip, int type, int sub_type, struct prefix_ipv4 *p, struct nexthop *nh, unsigned int metric, unsigned char distance, route_tag_t tag) { int ret; struct route_node *rp = NULL; struct rip_info *rinfo = NULL, newinfo; struct list *list = NULL; /* Redistribute route */ ret = rip_destination_check(p->prefix); if (!ret) return; rp = route_node_get(rip->table, (struct prefix *)p); memset(&newinfo, 0, sizeof(struct rip_info)); newinfo.type = type; newinfo.sub_type = sub_type; newinfo.metric = 1; newinfo.external_metric = metric; newinfo.distance = distance; if (tag <= UINT16_MAX) /* RIP only supports 16 bit tags */ newinfo.tag = tag; newinfo.rp = rp; newinfo.nh = *nh; if ((list = rp->info) != NULL && listcount(list) != 0) { rinfo = listgetdata(listhead(list)); if (rinfo->type == ZEBRA_ROUTE_CONNECT && rinfo->sub_type == RIP_ROUTE_INTERFACE && rinfo->metric != RIP_METRIC_INFINITY) { route_unlock_node(rp); return; } /* Manually configured RIP route check. */ if (rinfo->type == ZEBRA_ROUTE_RIP && ((rinfo->sub_type == RIP_ROUTE_STATIC) || (rinfo->sub_type == RIP_ROUTE_DEFAULT))) { if (type != ZEBRA_ROUTE_RIP || ((sub_type != RIP_ROUTE_STATIC) && (sub_type != RIP_ROUTE_DEFAULT))) { route_unlock_node(rp); return; } } (void)rip_ecmp_replace(rip, &newinfo); route_unlock_node(rp); } else (void)rip_ecmp_add(rip, &newinfo); if (IS_RIP_DEBUG_EVENT) { zlog_debug("Redistribute new prefix %s/%d", inet_ntoa(p->prefix), p->prefixlen); } rip_event(rip, RIP_TRIGGERED_UPDATE, 0); } /* Delete redistributed route from RIP table. */ void rip_redistribute_delete(struct rip *rip, int type, int sub_type, struct prefix_ipv4 *p, ifindex_t ifindex) { int ret; struct route_node *rp; struct rip_info *rinfo; ret = rip_destination_check(p->prefix); if (!ret) return; rp = route_node_lookup(rip->table, (struct prefix *)p); if (rp) { struct list *list = rp->info; if (list != NULL && listcount(list) != 0) { rinfo = listgetdata(listhead(list)); if (rinfo != NULL && rinfo->type == type && rinfo->sub_type == sub_type && rinfo->nh.ifindex == ifindex) { /* Perform poisoned reverse. */ rinfo->metric = RIP_METRIC_INFINITY; RIP_TIMER_ON(rinfo->t_garbage_collect, rip_garbage_collect, rip->garbage_time); RIP_TIMER_OFF(rinfo->t_timeout); rinfo->flags |= RIP_RTF_CHANGED; if (IS_RIP_DEBUG_EVENT) zlog_debug( "Poison %s/%d on the interface %s with an " "infinity metric [delete]", inet_ntoa(p->prefix), p->prefixlen, ifindex2ifname( ifindex, rip->vrf->vrf_id)); rip_event(rip, RIP_TRIGGERED_UPDATE, 0); } } route_unlock_node(rp); } } /* Response to request called from rip_read ().*/ static void rip_request_process(struct rip_packet *packet, int size, struct sockaddr_in *from, struct connected *ifc) { struct rip *rip; caddr_t lim; struct rte *rte; struct prefix_ipv4 p; struct route_node *rp; struct rip_info *rinfo; struct rip_interface *ri; /* Does not reponse to the requests on the loopback interfaces */ if (if_is_loopback(ifc->ifp)) return; /* Check RIP process is enabled on this interface. */ ri = ifc->ifp->info; if (!ri->running) return; rip = ri->rip; /* When passive interface is specified, suppress responses */ if (ri->passive) return; /* RIP peer update. */ rip_peer_update(rip, from, packet->version); lim = ((caddr_t)packet) + size; rte = packet->rte; /* The Request is processed entry by entry. If there are no entries, no response is given. */ if (lim == (caddr_t)rte) return; /* There is one special case. If there is exactly one entry in the request, and it has an address family identifier of zero and a metric of infinity (i.e., 16), then this is a request to send the entire routing table. */ if (lim == ((caddr_t)(rte + 1)) && ntohs(rte->family) == 0 && ntohl(rte->metric) == RIP_METRIC_INFINITY) { /* All route with split horizon */ rip_output_process(ifc, from, rip_all_route, packet->version); } else { if (ntohs(rte->family) != AF_INET) return; /* Examine the list of RTEs in the Request one by one. For each entry, look up the destination in the router's routing database and, if there is a route, put that route's metric in the metric field of the RTE. If there is no explicit route to the specified destination, put infinity in the metric field. Once all the entries have been filled in, change the command from Request to Response and send the datagram back to the requestor. */ p.family = AF_INET; for (; ((caddr_t)rte) < lim; rte++) { p.prefix = rte->prefix; p.prefixlen = ip_masklen(rte->mask); apply_mask_ipv4(&p); rp = route_node_lookup(rip->table, (struct prefix *)&p); if (rp) { rinfo = listgetdata( listhead((struct list *)rp->info)); rte->metric = htonl(rinfo->metric); route_unlock_node(rp); } else rte->metric = htonl(RIP_METRIC_INFINITY); } packet->command = RIP_RESPONSE; (void)rip_send_packet((uint8_t *)packet, size, from, ifc); } rip->counters.queries++; } /* First entry point of RIP packet. */ static int rip_read(struct thread *t) { struct rip *rip = THREAD_ARG(t); int sock; int ret; int rtenum; union rip_buf rip_buf; struct rip_packet *packet; struct sockaddr_in from; int len; int vrecv; socklen_t fromlen; struct interface *ifp = NULL; struct connected *ifc; struct rip_interface *ri; struct prefix p; /* Fetch socket then register myself. */ sock = THREAD_FD(t); rip->t_read = NULL; /* Add myself to tne next event */ rip_event(rip, RIP_READ, sock); /* RIPd manages only IPv4. */ memset(&from, 0, sizeof(struct sockaddr_in)); fromlen = sizeof(struct sockaddr_in); len = recvfrom(sock, (char *)&rip_buf.buf, sizeof(rip_buf.buf), 0, (struct sockaddr *)&from, &fromlen); if (len < 0) { zlog_info("recvfrom failed (VRF %s): %s", rip->vrf_name, safe_strerror(errno)); return len; } /* Check is this packet comming from myself? */ if (if_check_address(rip, from.sin_addr)) { if (IS_RIP_DEBUG_PACKET) zlog_debug("ignore packet comes from myself (VRF %s)", rip->vrf_name); return -1; } /* Which interface is this packet comes from. */ ifc = if_lookup_address((void *)&from.sin_addr, AF_INET, rip->vrf->vrf_id); if (ifc) ifp = ifc->ifp; /* RIP packet received */ if (IS_RIP_DEBUG_EVENT) zlog_debug("RECV packet from %s port %d on %s (VRF %s)", inet_ntoa(from.sin_addr), ntohs(from.sin_port), ifp ? ifp->name : "unknown", rip->vrf_name); /* If this packet come from unknown interface, ignore it. */ if (ifp == NULL) { zlog_info( "rip_read: cannot find interface for packet from %s port %d (VRF %s)", inet_ntoa(from.sin_addr), ntohs(from.sin_port), rip->vrf_name); return -1; } p.family = AF_INET; p.u.prefix4 = from.sin_addr; p.prefixlen = IPV4_MAX_BITLEN; ifc = connected_lookup_prefix(ifp, &p); if (ifc == NULL) { zlog_info( "rip_read: cannot find connected address for packet from %s " "port %d on interface %s (VRF %s)", inet_ntoa(from.sin_addr), ntohs(from.sin_port), ifp->name, rip->vrf_name); return -1; } /* Packet length check. */ if (len < RIP_PACKET_MINSIZ) { zlog_warn("packet size %d is smaller than minimum size %d", len, RIP_PACKET_MINSIZ); rip_peer_bad_packet(rip, &from); return len; } if (len > RIP_PACKET_MAXSIZ) { zlog_warn("packet size %d is larger than max size %d", len, RIP_PACKET_MAXSIZ); rip_peer_bad_packet(rip, &from); return len; } /* Packet alignment check. */ if ((len - RIP_PACKET_MINSIZ) % 20) { zlog_warn("packet size %d is wrong for RIP packet alignment", len); rip_peer_bad_packet(rip, &from); return len; } /* Set RTE number. */ rtenum = ((len - RIP_PACKET_MINSIZ) / 20); /* For easy to handle. */ packet = &rip_buf.rip_packet; /* RIP version check. */ if (packet->version == 0) { zlog_info("version 0 with command %d received.", packet->command); rip_peer_bad_packet(rip, &from); return -1; } /* Dump RIP packet. */ if (IS_RIP_DEBUG_RECV) rip_packet_dump(packet, len, "RECV"); /* RIP version adjust. This code should rethink now. RFC1058 says that "Version 1 implementations are to ignore this extra data and process only the fields specified in this document.". So RIPv3 packet should be treated as RIPv1 ignoring must be zero field. */ if (packet->version > RIPv2) packet->version = RIPv2; /* Is RIP running or is this RIP neighbor ?*/ ri = ifp->info; if (!ri->running && !rip_neighbor_lookup(rip, &from)) { if (IS_RIP_DEBUG_EVENT) zlog_debug("RIP is not enabled on interface %s.", ifp->name); rip_peer_bad_packet(rip, &from); return -1; } /* RIP Version check. RFC2453, 4.6 and 5.1 */ vrecv = ((ri->ri_receive == RI_RIP_UNSPEC) ? rip->version_recv : ri->ri_receive); if (vrecv == RI_RIP_VERSION_NONE || ((packet->version == RIPv1) && !(vrecv & RIPv1)) || ((packet->version == RIPv2) && !(vrecv & RIPv2))) { if (IS_RIP_DEBUG_PACKET) zlog_debug( " packet's v%d doesn't fit to if version spec", packet->version); rip_peer_bad_packet(rip, &from); return -1; } /* RFC2453 5.2 If the router is not configured to authenticate RIP-2 messages, then RIP-1 and unauthenticated RIP-2 messages will be accepted; authenticated RIP-2 messages shall be discarded. */ if ((ri->auth_type == RIP_NO_AUTH) && rtenum && (packet->version == RIPv2) && (packet->rte->family == htons(RIP_FAMILY_AUTH))) { if (IS_RIP_DEBUG_EVENT) zlog_debug( "packet RIPv%d is dropped because authentication disabled", packet->version); ripd_notif_send_auth_type_failure(ifp->name); rip_peer_bad_packet(rip, &from); return -1; } /* RFC: If the router is configured to authenticate RIP-2 messages, then RIP-1 messages and RIP-2 messages which pass authentication testing shall be accepted; unauthenticated and failed authentication RIP-2 messages shall be discarded. For maximum security, RIP-1 messages should be ignored when authentication is in use (see section 4.1); otherwise, the routing information from authenticated messages will be propagated by RIP-1 routers in an unauthenticated manner. */ /* We make an exception for RIPv1 REQUEST packets, to which we'll * always reply regardless of authentication settings, because: * * - if there other authorised routers on-link, the REQUESTor can * passively obtain the routing updates anyway * - if there are no other authorised routers on-link, RIP can * easily be disabled for the link to prevent giving out information * on state of this routers RIP routing table.. * * I.e. if RIPv1 has any place anymore these days, it's as a very * simple way to distribute routing information (e.g. to embedded * hosts / appliances) and the ability to give out RIPv1 * routing-information freely, while still requiring RIPv2 * authentication for any RESPONSEs might be vaguely useful. */ if (ri->auth_type != RIP_NO_AUTH && packet->version == RIPv1) { /* Discard RIPv1 messages other than REQUESTs */ if (packet->command != RIP_REQUEST) { if (IS_RIP_DEBUG_PACKET) zlog_debug( "RIPv1" " dropped because authentication enabled"); ripd_notif_send_auth_type_failure(ifp->name); rip_peer_bad_packet(rip, &from); return -1; } } else if (ri->auth_type != RIP_NO_AUTH) { const char *auth_desc; if (rtenum == 0) { /* There definitely is no authentication in the packet. */ if (IS_RIP_DEBUG_PACKET) zlog_debug( "RIPv2 authentication failed: no auth RTE in packet"); ripd_notif_send_auth_type_failure(ifp->name); rip_peer_bad_packet(rip, &from); return -1; } /* First RTE must be an Authentication Family RTE */ if (packet->rte->family != htons(RIP_FAMILY_AUTH)) { if (IS_RIP_DEBUG_PACKET) zlog_debug( "RIPv2" " dropped because authentication enabled"); ripd_notif_send_auth_type_failure(ifp->name); rip_peer_bad_packet(rip, &from); return -1; } /* Check RIPv2 authentication. */ switch (ntohs(packet->rte->tag)) { case RIP_AUTH_SIMPLE_PASSWORD: auth_desc = "simple"; ret = rip_auth_simple_password(packet->rte, &from, ifp); break; case RIP_AUTH_MD5: auth_desc = "MD5"; ret = rip_auth_md5(packet, &from, len, ifp); /* Reset RIP packet length to trim MD5 data. */ len = ret; break; default: ret = 0; auth_desc = "unknown type"; if (IS_RIP_DEBUG_PACKET) zlog_debug( "RIPv2 Unknown authentication type %d", ntohs(packet->rte->tag)); } if (ret) { if (IS_RIP_DEBUG_PACKET) zlog_debug("RIPv2 %s authentication success", auth_desc); } else { if (IS_RIP_DEBUG_PACKET) zlog_debug("RIPv2 %s authentication failure", auth_desc); ripd_notif_send_auth_failure(ifp->name); rip_peer_bad_packet(rip, &from); return -1; } } /* Process each command. */ switch (packet->command) { case RIP_RESPONSE: rip_response_process(packet, len, &from, ifc); break; case RIP_REQUEST: case RIP_POLL: rip_request_process(packet, len, &from, ifc); break; case RIP_TRACEON: case RIP_TRACEOFF: zlog_info( "Obsolete command %s received, please sent it to routed", lookup_msg(rip_msg, packet->command, NULL)); rip_peer_bad_packet(rip, &from); break; case RIP_POLL_ENTRY: zlog_info("Obsolete command %s received", lookup_msg(rip_msg, packet->command, NULL)); rip_peer_bad_packet(rip, &from); break; default: zlog_info("Unknown RIP command %d received", packet->command); rip_peer_bad_packet(rip, &from); break; } return len; } /* Write routing table entry to the stream and return next index of the routing table entry in the stream. */ static int rip_write_rte(int num, struct stream *s, struct prefix_ipv4 *p, uint8_t version, struct rip_info *rinfo) { struct in_addr mask; /* Write routing table entry. */ if (version == RIPv1) { stream_putw(s, AF_INET); stream_putw(s, 0); stream_put_ipv4(s, p->prefix.s_addr); stream_put_ipv4(s, 0); stream_put_ipv4(s, 0); stream_putl(s, rinfo->metric_out); } else { masklen2ip(p->prefixlen, &mask); stream_putw(s, AF_INET); stream_putw(s, rinfo->tag_out); stream_put_ipv4(s, p->prefix.s_addr); stream_put_ipv4(s, mask.s_addr); stream_put_ipv4(s, rinfo->nexthop_out.s_addr); stream_putl(s, rinfo->metric_out); } return ++num; } /* Send update to the ifp or spcified neighbor. */ void rip_output_process(struct connected *ifc, struct sockaddr_in *to, int route_type, uint8_t version) { struct rip *rip; int ret; struct stream *s; struct route_node *rp; struct rip_info *rinfo; struct rip_interface *ri; struct prefix_ipv4 *p; struct prefix_ipv4 classfull; struct prefix_ipv4 ifaddrclass; struct key *key = NULL; /* this might need to made dynamic if RIP ever supported auth methods with larger key string sizes */ char auth_str[RIP_AUTH_SIMPLE_SIZE]; size_t doff = 0; /* offset of digest offset field */ int num = 0; int rtemax; int subnetted = 0; struct list *list = NULL; struct listnode *listnode = NULL; /* Logging output event. */ if (IS_RIP_DEBUG_EVENT) { if (to) zlog_debug("update routes to neighbor %s", inet_ntoa(to->sin_addr)); else zlog_debug("update routes on interface %s ifindex %d", ifc->ifp->name, ifc->ifp->ifindex); } /* Get RIP interface. */ ri = ifc->ifp->info; rip = ri->rip; /* Set output stream. */ s = rip->obuf; /* Reset stream and RTE counter. */ stream_reset(s); rtemax = RIP_MAX_RTE; /* If output interface is in simple password authentication mode, we need space for authentication data. */ if (ri->auth_type == RIP_AUTH_SIMPLE_PASSWORD) rtemax -= 1; /* If output interface is in MD5 authentication mode, we need space for authentication header and data. */ if (ri->auth_type == RIP_AUTH_MD5) rtemax -= 2; /* If output interface is in simple password authentication mode and string or keychain is specified we need space for auth. data */ if (ri->auth_type != RIP_NO_AUTH) { if (ri->key_chain) { struct keychain *keychain; keychain = keychain_lookup(ri->key_chain); if (keychain) key = key_lookup_for_send(keychain); } /* to be passed to auth functions later */ rip_auth_prepare_str_send(ri, key, auth_str, sizeof(auth_str)); if (strlen(auth_str) == 0) return; } if (version == RIPv1) { memcpy(&ifaddrclass, ifc->address, sizeof(struct prefix_ipv4)); apply_classful_mask_ipv4(&ifaddrclass); subnetted = 0; if (ifc->address->prefixlen > ifaddrclass.prefixlen) subnetted = 1; } for (rp = route_top(rip->table); rp; rp = route_next(rp)) if ((list = rp->info) != NULL && listcount(list) != 0) { rinfo = listgetdata(listhead(list)); /* For RIPv1, if we are subnetted, output subnets in our * network */ /* that have the same mask as the output "interface". * For other */ /* networks, only the classfull version is output. */ if (version == RIPv1) { p = (struct prefix_ipv4 *)&rp->p; if (IS_RIP_DEBUG_PACKET) zlog_debug( "RIPv1 mask check, %s/%d considered for output", inet_ntoa(rp->p.u.prefix4), rp->p.prefixlen); if (subnetted && prefix_match( (struct prefix *)&ifaddrclass, &rp->p)) { if ((ifc->address->prefixlen != rp->p.prefixlen) && (rp->p.prefixlen != 32)) continue; } else { memcpy(&classfull, &rp->p, sizeof(struct prefix_ipv4)); apply_classful_mask_ipv4(&classfull); if (rp->p.u.prefix4.s_addr != 0 && classfull.prefixlen != rp->p.prefixlen) continue; } if (IS_RIP_DEBUG_PACKET) zlog_debug( "RIPv1 mask check, %s/%d made it through", inet_ntoa(rp->p.u.prefix4), rp->p.prefixlen); } else p = (struct prefix_ipv4 *)&rp->p; /* Apply output filters. */ ret = rip_filter(RIP_FILTER_OUT, p, ri); if (ret < 0) continue; /* Changed route only output. */ if (route_type == rip_changed_route && (!(rinfo->flags & RIP_RTF_CHANGED))) continue; /* Split horizon. */ /* if (split_horizon == rip_split_horizon) */ if (ri->split_horizon == RIP_SPLIT_HORIZON) { /* * We perform split horizon for RIP and * connected route. * For rip routes, we want to suppress the route * if we would * end up sending the route back on the * interface that we * learned it from, with a higher metric. For * connected routes, * we suppress the route if the prefix is a * subset of the * source address that we are going to use for * the packet * (in order to handle the case when multiple * subnets are * configured on the same interface). */ int suppress = 0; struct rip_info *tmp_rinfo = NULL; struct connected *tmp_ifc = NULL; for (ALL_LIST_ELEMENTS_RO(list, listnode, tmp_rinfo)) if (tmp_rinfo->type == ZEBRA_ROUTE_RIP && tmp_rinfo->nh.ifindex == ifc->ifp->ifindex) { suppress = 1; break; } if (!suppress && rinfo->type == ZEBRA_ROUTE_CONNECT) { for (ALL_LIST_ELEMENTS_RO( ifc->ifp->connected, listnode, tmp_ifc)) if (prefix_match( (struct prefix *)p, tmp_ifc->address)) { suppress = 1; break; } } if (suppress) continue; } /* Preparation for route-map. */ rinfo->metric_set = 0; rinfo->nexthop_out.s_addr = 0; rinfo->metric_out = rinfo->metric; rinfo->tag_out = rinfo->tag; rinfo->ifindex_out = ifc->ifp->ifindex; /* In order to avoid some local loops, * if the RIP route has a nexthop via this interface, * keep the nexthop, * otherwise set it to 0. The nexthop should not be * propagated * beyond the local broadcast/multicast area in order * to avoid an IGP multi-level recursive look-up. * see (4.4) */ if (rinfo->nh.ifindex == ifc->ifp->ifindex) rinfo->nexthop_out = rinfo->nh.gate.ipv4; /* Interface route-map */ if (ri->routemap[RIP_FILTER_OUT]) { ret = route_map_apply( ri->routemap[RIP_FILTER_OUT], (struct prefix *)p, RMAP_RIP, rinfo); if (ret == RMAP_DENYMATCH) { if (IS_RIP_DEBUG_PACKET) zlog_debug( "RIP %s/%d is filtered by route-map out", inet_ntoa(p->prefix), p->prefixlen); continue; } } /* Apply redistribute route map - continue, if deny */ if (rip->redist[rinfo->type].route_map.name && rinfo->sub_type != RIP_ROUTE_INTERFACE) { ret = route_map_apply( rip->redist[rinfo->type].route_map.map, (struct prefix *)p, RMAP_RIP, rinfo); if (ret == RMAP_DENYMATCH) { if (IS_RIP_DEBUG_PACKET) zlog_debug( "%s/%d is filtered by route-map", inet_ntoa(p->prefix), p->prefixlen); continue; } } /* When route-map does not set metric. */ if (!rinfo->metric_set) { /* If redistribute metric is set. */ if (rip->redist[rinfo->type].metric_config && rinfo->metric != RIP_METRIC_INFINITY) { rinfo->metric_out = rip->redist[rinfo->type].metric; } else { /* If the route is not connected or localy generated one, use default-metric value*/ if (rinfo->type != ZEBRA_ROUTE_RIP && rinfo->type != ZEBRA_ROUTE_CONNECT && rinfo->metric != RIP_METRIC_INFINITY) rinfo->metric_out = rip->default_metric; } } /* Apply offset-list */ if (rinfo->metric != RIP_METRIC_INFINITY) rip_offset_list_apply_out(p, ifc->ifp, &rinfo->metric_out); if (rinfo->metric_out > RIP_METRIC_INFINITY) rinfo->metric_out = RIP_METRIC_INFINITY; /* Perform split-horizon with poisoned reverse * for RIP and connected routes. **/ if (ri->split_horizon == RIP_SPLIT_HORIZON_POISONED_REVERSE) { /* * We perform split horizon for RIP and * connected route. * For rip routes, we want to suppress the route * if we would * end up sending the route back on the * interface that we * learned it from, with a higher metric. For * connected routes, * we suppress the route if the prefix is a * subset of the * source address that we are going to use for * the packet * (in order to handle the case when multiple * subnets are * configured on the same interface). */ struct rip_info *tmp_rinfo = NULL; struct connected *tmp_ifc = NULL; for (ALL_LIST_ELEMENTS_RO(list, listnode, tmp_rinfo)) if (tmp_rinfo->type == ZEBRA_ROUTE_RIP && tmp_rinfo->nh.ifindex == ifc->ifp->ifindex) rinfo->metric_out = RIP_METRIC_INFINITY; if (rinfo->metric_out != RIP_METRIC_INFINITY && rinfo->type == ZEBRA_ROUTE_CONNECT) { for (ALL_LIST_ELEMENTS_RO( ifc->ifp->connected, listnode, tmp_ifc)) if (prefix_match( (struct prefix *)p, tmp_ifc->address)) { rinfo->metric_out = RIP_METRIC_INFINITY; break; } } } /* Prepare preamble, auth headers, if needs be */ if (num == 0) { stream_putc(s, RIP_RESPONSE); stream_putc(s, version); stream_putw(s, 0); /* auth header for !v1 && !no_auth */ if ((ri->auth_type != RIP_NO_AUTH) && (version != RIPv1)) doff = rip_auth_header_write( s, ri, key, auth_str, RIP_AUTH_SIMPLE_SIZE); } /* Write RTE to the stream. */ num = rip_write_rte(num, s, p, version, rinfo); if (num == rtemax) { if (version == RIPv2 && ri->auth_type == RIP_AUTH_MD5) rip_auth_md5_set(s, ri, doff, auth_str, RIP_AUTH_SIMPLE_SIZE); ret = rip_send_packet(STREAM_DATA(s), stream_get_endp(s), to, ifc); if (ret >= 0 && IS_RIP_DEBUG_SEND) rip_packet_dump((struct rip_packet *) STREAM_DATA(s), stream_get_endp(s), "SEND"); num = 0; stream_reset(s); } } /* Flush unwritten RTE. */ if (num != 0) { if (version == RIPv2 && ri->auth_type == RIP_AUTH_MD5) rip_auth_md5_set(s, ri, doff, auth_str, RIP_AUTH_SIMPLE_SIZE); ret = rip_send_packet(STREAM_DATA(s), stream_get_endp(s), to, ifc); if (ret >= 0 && IS_RIP_DEBUG_SEND) rip_packet_dump((struct rip_packet *)STREAM_DATA(s), stream_get_endp(s), "SEND"); stream_reset(s); } /* Statistics updates. */ ri->sent_updates++; } /* Send RIP packet to the interface. */ static void rip_update_interface(struct connected *ifc, uint8_t version, int route_type) { struct interface *ifp = ifc->ifp; struct rip_interface *ri = ifp->info; struct sockaddr_in to; /* When RIP version is 2 and multicast enable interface. */ if (version == RIPv2 && !ri->v2_broadcast && if_is_multicast(ifp)) { if (IS_RIP_DEBUG_EVENT) zlog_debug("multicast announce on %s ", ifp->name); rip_output_process(ifc, NULL, route_type, version); return; } /* If we can't send multicast packet, send it with unicast. */ if (if_is_broadcast(ifp) || if_is_pointopoint(ifp)) { if (ifc->address->family == AF_INET) { /* Destination address and port setting. */ memset(&to, 0, sizeof(struct sockaddr_in)); if (ifc->destination) /* use specified broadcast or peer destination * addr */ to.sin_addr = ifc->destination->u.prefix4; else if (ifc->address->prefixlen < IPV4_MAX_PREFIXLEN) /* calculate the appropriate broadcast address */ to.sin_addr.s_addr = ipv4_broadcast_addr( ifc->address->u.prefix4.s_addr, ifc->address->prefixlen); else /* do not know where to send the packet */ return; to.sin_port = htons(RIP_PORT_DEFAULT); if (IS_RIP_DEBUG_EVENT) zlog_debug("%s announce to %s on %s", CONNECTED_PEER(ifc) ? "unicast" : "broadcast", inet_ntoa(to.sin_addr), ifp->name); rip_output_process(ifc, &to, route_type, version); } } } /* Update send to all interface and neighbor. */ static void rip_update_process(struct rip *rip, int route_type) { struct listnode *ifnode, *ifnnode; struct connected *connected; struct interface *ifp; struct rip_interface *ri; struct route_node *rp; struct sockaddr_in to; struct prefix *p; /* Send RIP update to each interface. */ FOR_ALL_INTERFACES (rip->vrf, ifp) { if (if_is_loopback(ifp)) continue; if (!if_is_operative(ifp)) continue; /* Fetch RIP interface information. */ ri = ifp->info; /* When passive interface is specified, suppress announce to the interface. */ if (ri->passive) continue; if (ri->running) { /* * If there is no version configuration in the * interface, * use rip's version setting. */ int vsend = ((ri->ri_send == RI_RIP_UNSPEC) ? rip->version_send : ri->ri_send); if (IS_RIP_DEBUG_EVENT) zlog_debug("SEND UPDATE to %s ifindex %d", ifp->name, ifp->ifindex); /* send update on each connected network */ for (ALL_LIST_ELEMENTS(ifp->connected, ifnode, ifnnode, connected)) { if (connected->address->family == AF_INET) { if (vsend & RIPv1) rip_update_interface( connected, RIPv1, route_type); if ((vsend & RIPv2) && if_is_multicast(ifp)) rip_update_interface( connected, RIPv2, route_type); } } } } /* RIP send updates to each neighbor. */ for (rp = route_top(rip->neighbor); rp; rp = route_next(rp)) if (rp->info != NULL) { p = &rp->p; connected = if_lookup_address(&p->u.prefix4, AF_INET, rip->vrf->vrf_id); if (!connected) { zlog_warn( "Neighbor %s doesn't have connected interface!", inet_ntoa(p->u.prefix4)); continue; } /* Set destination address and port */ memset(&to, 0, sizeof(struct sockaddr_in)); to.sin_addr = p->u.prefix4; to.sin_port = htons(RIP_PORT_DEFAULT); /* RIP version is rip's configuration. */ rip_output_process(connected, &to, route_type, rip->version_send); } } /* RIP's periodical timer. */ static int rip_update(struct thread *t) { struct rip *rip = THREAD_ARG(t); /* Clear timer pointer. */ rip->t_update = NULL; if (IS_RIP_DEBUG_EVENT) zlog_debug("update timer fire!"); /* Process update output. */ rip_update_process(rip, rip_all_route); /* Triggered updates may be suppressed if a regular update is due by the time the triggered update would be sent. */ RIP_TIMER_OFF(rip->t_triggered_interval); rip->trigger = 0; /* Register myself. */ rip_event(rip, RIP_UPDATE_EVENT, 0); return 0; } /* Walk down the RIP routing table then clear changed flag. */ static void rip_clear_changed_flag(struct rip *rip) { struct route_node *rp; struct rip_info *rinfo = NULL; struct list *list = NULL; struct listnode *listnode = NULL; for (rp = route_top(rip->table); rp; rp = route_next(rp)) if ((list = rp->info) != NULL) for (ALL_LIST_ELEMENTS_RO(list, listnode, rinfo)) { UNSET_FLAG(rinfo->flags, RIP_RTF_CHANGED); /* This flag can be set only on the first entry. */ break; } } /* Triggered update interval timer. */ static int rip_triggered_interval(struct thread *t) { struct rip *rip = THREAD_ARG(t); rip->t_triggered_interval = NULL; if (rip->trigger) { rip->trigger = 0; rip_triggered_update(t); } return 0; } /* Execute triggered update. */ static int rip_triggered_update(struct thread *t) { struct rip *rip = THREAD_ARG(t); int interval; /* Clear thred pointer. */ rip->t_triggered_update = NULL; /* Cancel interval timer. */ RIP_TIMER_OFF(rip->t_triggered_interval); rip->trigger = 0; /* Logging triggered update. */ if (IS_RIP_DEBUG_EVENT) zlog_debug("triggered update!"); /* Split Horizon processing is done when generating triggered updates as well as normal updates (see section 2.6). */ rip_update_process(rip, rip_changed_route); /* Once all of the triggered updates have been generated, the route change flags should be cleared. */ rip_clear_changed_flag(rip); /* After a triggered update is sent, a timer should be set for a random interval between 1 and 5 seconds. If other changes that would trigger updates occur before the timer expires, a single update is triggered when the timer expires. */ interval = (random() % 5) + 1; rip->t_triggered_interval = NULL; thread_add_timer(master, rip_triggered_interval, rip, interval, &rip->t_triggered_interval); return 0; } /* Withdraw redistributed route. */ void rip_redistribute_withdraw(struct rip *rip, int type) { struct route_node *rp; struct rip_info *rinfo = NULL; struct list *list = NULL; for (rp = route_top(rip->table); rp; rp = route_next(rp)) if ((list = rp->info) != NULL) { rinfo = listgetdata(listhead(list)); if (rinfo->type == type && rinfo->sub_type != RIP_ROUTE_INTERFACE) { /* Perform poisoned reverse. */ rinfo->metric = RIP_METRIC_INFINITY; RIP_TIMER_ON(rinfo->t_garbage_collect, rip_garbage_collect, rip->garbage_time); RIP_TIMER_OFF(rinfo->t_timeout); rinfo->flags |= RIP_RTF_CHANGED; if (IS_RIP_DEBUG_EVENT) { struct prefix_ipv4 *p = (struct prefix_ipv4 *)&rp->p; zlog_debug( "Poisone %s/%d on the interface %s with an infinity metric [withdraw]", inet_ntoa(p->prefix), p->prefixlen, ifindex2ifname( rinfo->nh.ifindex, rip->vrf->vrf_id)); } rip_event(rip, RIP_TRIGGERED_UPDATE, 0); } } } struct rip *rip_lookup_by_vrf_id(vrf_id_t vrf_id) { struct vrf *vrf; vrf = vrf_lookup_by_id(vrf_id); if (!vrf) return NULL; return vrf->info; } struct rip *rip_lookup_by_vrf_name(const char *vrf_name) { struct rip rip; rip.vrf_name = (char *)vrf_name; return RB_FIND(rip_instance_head, &rip_instances, &rip); } /* Create new RIP instance and set it to global variable. */ struct rip *rip_create(const char *vrf_name, struct vrf *vrf, int socket) { struct rip *rip; rip = XCALLOC(MTYPE_RIP, sizeof(struct rip)); rip->vrf_name = XSTRDUP(MTYPE_RIP_VRF_NAME, vrf_name); /* Set initial value. */ rip->ecmp = yang_get_default_bool("%s/allow-ecmp", RIP_INSTANCE); rip->default_metric = yang_get_default_uint8("%s/default-metric", RIP_INSTANCE); rip->distance = yang_get_default_uint8("%s/distance/default", RIP_INSTANCE); rip->passive_default = yang_get_default_bool("%s/passive-default", RIP_INSTANCE); rip->garbage_time = yang_get_default_uint32("%s/timers/flush-interval", RIP_INSTANCE); rip->timeout_time = yang_get_default_uint32( "%s/timers/holddown-interval", RIP_INSTANCE); rip->update_time = yang_get_default_uint32("%s/timers/update-interval", RIP_INSTANCE); rip->version_send = yang_get_default_enum("%s/version/send", RIP_INSTANCE); rip->version_recv = yang_get_default_enum("%s/version/receive", RIP_INSTANCE); /* Initialize RIP data structures. */ rip->table = route_table_init(); route_table_set_info(rip->table, rip); rip->neighbor = route_table_init(); rip->peer_list = list_new(); rip->peer_list->cmp = (int (*)(void *, void *))rip_peer_list_cmp; rip->peer_list->del = rip_peer_list_del; rip->distance_table = route_table_init(); rip->distance_table->cleanup = rip_distance_table_node_cleanup; rip->enable_interface = vector_init(1); rip->enable_network = route_table_init(); rip->passive_nondefault = vector_init(1); rip->offset_list_master = list_new(); rip->offset_list_master->cmp = (int (*)(void *, void *))offset_list_cmp; rip->offset_list_master->del = (void (*)(void *))offset_list_free; /* Distribute list install. */ rip->distribute_ctx = distribute_list_ctx_create(vrf); distribute_list_add_hook(rip->distribute_ctx, rip_distribute_update); distribute_list_delete_hook(rip->distribute_ctx, rip_distribute_update); /* if rmap install. */ rip->if_rmap_ctx = if_rmap_ctx_create(vrf_name); if_rmap_hook_add(rip->if_rmap_ctx, rip_if_rmap_update); if_rmap_hook_delete(rip->if_rmap_ctx, rip_if_rmap_update); /* Make output stream. */ rip->obuf = stream_new(1500); /* Enable the routing instance if possible. */ if (vrf && vrf_is_enabled(vrf)) rip_instance_enable(rip, vrf, socket); else { rip->vrf = NULL; rip->sock = -1; } RB_INSERT(rip_instance_head, &rip_instances, rip); return rip; } /* Sned RIP request to the destination. */ int rip_request_send(struct sockaddr_in *to, struct interface *ifp, uint8_t version, struct connected *connected) { struct rte *rte; struct rip_packet rip_packet; struct listnode *node, *nnode; memset(&rip_packet, 0, sizeof(rip_packet)); rip_packet.command = RIP_REQUEST; rip_packet.version = version; rte = rip_packet.rte; rte->metric = htonl(RIP_METRIC_INFINITY); if (connected) { /* * connected is only sent for ripv1 case, or when * interface does not support multicast. Caller loops * over each connected address for this case. */ if (rip_send_packet((uint8_t *)&rip_packet, sizeof(rip_packet), to, connected) != sizeof(rip_packet)) return -1; else return sizeof(rip_packet); } /* send request on each connected network */ for (ALL_LIST_ELEMENTS(ifp->connected, node, nnode, connected)) { struct prefix_ipv4 *p; p = (struct prefix_ipv4 *)connected->address; if (p->family != AF_INET) continue; if (rip_send_packet((uint8_t *)&rip_packet, sizeof(rip_packet), to, connected) != sizeof(rip_packet)) return -1; } return sizeof(rip_packet); } static int rip_update_jitter(unsigned long time) { #define JITTER_BOUND 4 /* We want to get the jitter to +/- 1/JITTER_BOUND the interval. Given that, we cannot let time be less than JITTER_BOUND seconds. The RIPv2 RFC says jitter should be small compared to update_time. We consider 1/JITTER_BOUND to be small. */ int jitter_input = time; int jitter; if (jitter_input < JITTER_BOUND) jitter_input = JITTER_BOUND; jitter = (((random() % ((jitter_input * 2) + 1)) - jitter_input)); return jitter / JITTER_BOUND; } void rip_event(struct rip *rip, enum rip_event event, int sock) { int jitter = 0; switch (event) { case RIP_READ: rip->t_read = NULL; thread_add_read(master, rip_read, rip, sock, &rip->t_read); break; case RIP_UPDATE_EVENT: RIP_TIMER_OFF(rip->t_update); jitter = rip_update_jitter(rip->update_time); thread_add_timer(master, rip_update, rip, sock ? 2 : rip->update_time + jitter, &rip->t_update); break; case RIP_TRIGGERED_UPDATE: if (rip->t_triggered_interval) rip->trigger = 1; else thread_add_event(master, rip_triggered_update, rip, 0, &rip->t_triggered_update); break; default: break; } } #if 0 static void rip_update_default_metric (void) { struct route_node *np; struct rip_info *rinfo = NULL; struct list *list = NULL; struct listnode *listnode = NULL; for (np = route_top (rip->table); np; np = route_next (np)) if ((list = np->info) != NULL) for (ALL_LIST_ELEMENTS_RO (list, listnode, rinfo)) if (rinfo->type != ZEBRA_ROUTE_RIP && rinfo->type != ZEBRA_ROUTE_CONNECT) rinfo->metric = rip->default_metric; } #endif struct rip_distance *rip_distance_new(void) { return XCALLOC(MTYPE_RIP_DISTANCE, sizeof(struct rip_distance)); } void rip_distance_free(struct rip_distance *rdistance) { if (rdistance->access_list) free(rdistance->access_list); XFREE(MTYPE_RIP_DISTANCE, rdistance); } static void rip_distance_table_node_cleanup(struct route_table *table, struct route_node *node) { struct rip_distance *rdistance; rdistance = node->info; if (rdistance) rip_distance_free(rdistance); } /* Apply RIP information to distance method. */ uint8_t rip_distance_apply(struct rip *rip, struct rip_info *rinfo) { struct route_node *rn; struct prefix_ipv4 p; struct rip_distance *rdistance; struct access_list *alist; memset(&p, 0, sizeof(struct prefix_ipv4)); p.family = AF_INET; p.prefix = rinfo->from; p.prefixlen = IPV4_MAX_BITLEN; /* Check source address. */ rn = route_node_match(rip->distance_table, (struct prefix *)&p); if (rn) { rdistance = rn->info; route_unlock_node(rn); if (rdistance->access_list) { alist = access_list_lookup(AFI_IP, rdistance->access_list); if (alist == NULL) return 0; if (access_list_apply(alist, &rinfo->rp->p) == FILTER_DENY) return 0; return rdistance->distance; } else return rdistance->distance; } if (rip->distance) return rip->distance; return 0; } static void rip_distance_show(struct vty *vty, struct rip *rip) { struct route_node *rn; struct rip_distance *rdistance; int header = 1; char buf[BUFSIZ]; vty_out(vty, " Distance: (default is %u)\n", rip->distance ? rip->distance : ZEBRA_RIP_DISTANCE_DEFAULT); for (rn = route_top(rip->distance_table); rn; rn = route_next(rn)) if ((rdistance = rn->info) != NULL) { if (header) { vty_out(vty, " Address Distance List\n"); header = 0; } sprintf(buf, "%s/%d", inet_ntoa(rn->p.u.prefix4), rn->p.prefixlen); vty_out(vty, " %-20s %4d %s\n", buf, rdistance->distance, rdistance->access_list ? rdistance->access_list : ""); } } /* Update ECMP routes to zebra when ECMP is disabled. */ void rip_ecmp_disable(struct rip *rip) { struct route_node *rp; struct rip_info *rinfo, *tmp_rinfo; struct list *list; struct listnode *node, *nextnode; for (rp = route_top(rip->table); rp; rp = route_next(rp)) if ((list = rp->info) != NULL && listcount(list) > 1) { rinfo = listgetdata(listhead(list)); if (!rip_route_rte(rinfo)) continue; /* Drop all other entries, except the first one. */ for (ALL_LIST_ELEMENTS(list, node, nextnode, tmp_rinfo)) if (tmp_rinfo != rinfo) { RIP_TIMER_OFF(tmp_rinfo->t_timeout); RIP_TIMER_OFF( tmp_rinfo->t_garbage_collect); list_delete_node(list, node); rip_info_free(tmp_rinfo); } /* Update zebra. */ rip_zebra_ipv4_add(rip, rp); /* Set the route change flag. */ SET_FLAG(rinfo->flags, RIP_RTF_CHANGED); /* Signal the output process to trigger an update. */ rip_event(rip, RIP_TRIGGERED_UPDATE, 0); } } /* Print out routes update time. */ static void rip_vty_out_uptime(struct vty *vty, struct rip_info *rinfo) { time_t clock; struct tm *tm; #define TIME_BUF 25 char timebuf[TIME_BUF]; struct thread *thread; if ((thread = rinfo->t_timeout) != NULL) { clock = thread_timer_remain_second(thread); tm = gmtime(&clock); strftime(timebuf, TIME_BUF, "%M:%S", tm); vty_out(vty, "%5s", timebuf); } else if ((thread = rinfo->t_garbage_collect) != NULL) { clock = thread_timer_remain_second(thread); tm = gmtime(&clock); strftime(timebuf, TIME_BUF, "%M:%S", tm); vty_out(vty, "%5s", timebuf); } } static const char *rip_route_type_print(int sub_type) { switch (sub_type) { case RIP_ROUTE_RTE: return "n"; case RIP_ROUTE_STATIC: return "s"; case RIP_ROUTE_DEFAULT: return "d"; case RIP_ROUTE_REDISTRIBUTE: return "r"; case RIP_ROUTE_INTERFACE: return "i"; default: return "?"; } } DEFUN (show_ip_rip, show_ip_rip_cmd, "show ip rip [vrf NAME]", SHOW_STR IP_STR "Show RIP routes\n" VRF_CMD_HELP_STR) { struct rip *rip; struct route_node *np; struct rip_info *rinfo = NULL; struct list *list = NULL; struct listnode *listnode = NULL; const char *vrf_name; int idx = 0; if (argv_find(argv, argc, "vrf", &idx)) vrf_name = argv[idx + 1]->arg; else vrf_name = VRF_DEFAULT_NAME; rip = rip_lookup_by_vrf_name(vrf_name); if (!rip) { vty_out(vty, "%% RIP instance not found\n"); return CMD_SUCCESS; } if (!rip->enabled) { vty_out(vty, "%% RIP instance is disabled\n"); return CMD_SUCCESS; } vty_out(vty, "Codes: R - RIP, C - connected, S - Static, O - OSPF, B - BGP\n" "Sub-codes:\n" " (n) - normal, (s) - static, (d) - default, (r) - redistribute,\n" " (i) - interface\n\n" " Network Next Hop Metric From Tag Time\n"); for (np = route_top(rip->table); np; np = route_next(np)) if ((list = np->info) != NULL) for (ALL_LIST_ELEMENTS_RO(list, listnode, rinfo)) { int len; len = vty_out( vty, "%c(%s) %s/%d", /* np->lock, For debugging. */ zebra_route_char(rinfo->type), rip_route_type_print(rinfo->sub_type), inet_ntoa(np->p.u.prefix4), np->p.prefixlen); len = 24 - len; if (len > 0) vty_out(vty, "%*s", len, " "); switch (rinfo->nh.type) { case NEXTHOP_TYPE_IPV4: case NEXTHOP_TYPE_IPV4_IFINDEX: vty_out(vty, "%-20s %2d ", inet_ntoa(rinfo->nh.gate.ipv4), rinfo->metric); break; case NEXTHOP_TYPE_IFINDEX: vty_out(vty, "0.0.0.0 %2d ", rinfo->metric); break; case NEXTHOP_TYPE_BLACKHOLE: vty_out(vty, "blackhole %2d ", rinfo->metric); break; case NEXTHOP_TYPE_IPV6: case NEXTHOP_TYPE_IPV6_IFINDEX: vty_out(vty, "V6 Address Hidden %2d ", rinfo->metric); break; } /* Route which exist in kernel routing table. */ if ((rinfo->type == ZEBRA_ROUTE_RIP) && (rinfo->sub_type == RIP_ROUTE_RTE)) { vty_out(vty, "%-15s ", inet_ntoa(rinfo->from)); vty_out(vty, "%3" ROUTE_TAG_PRI " ", (route_tag_t)rinfo->tag); rip_vty_out_uptime(vty, rinfo); } else if (rinfo->metric == RIP_METRIC_INFINITY) { vty_out(vty, "self "); vty_out(vty, "%3" ROUTE_TAG_PRI " ", (route_tag_t)rinfo->tag); rip_vty_out_uptime(vty, rinfo); } else { if (rinfo->external_metric) { len = vty_out( vty, "self (%s:%d)", zebra_route_string( rinfo->type), rinfo->external_metric); len = 16 - len; if (len > 0) vty_out(vty, "%*s", len, " "); } else vty_out(vty, "self "); vty_out(vty, "%3" ROUTE_TAG_PRI, (route_tag_t)rinfo->tag); } vty_out(vty, "\n"); } return CMD_SUCCESS; } /* Vincent: formerly, it was show_ip_protocols_rip: "show ip protocols" */ DEFUN (show_ip_rip_status, show_ip_rip_status_cmd, "show ip rip [vrf NAME] status", SHOW_STR IP_STR "Show RIP routes\n" VRF_CMD_HELP_STR "IP routing protocol process parameters and statistics\n") { struct rip *rip; struct interface *ifp; struct rip_interface *ri; extern const struct message ri_version_msg[]; const char *send_version; const char *receive_version; const char *vrf_name; int idx = 0; if (argv_find(argv, argc, "vrf", &idx)) vrf_name = argv[idx + 1]->arg; else vrf_name = VRF_DEFAULT_NAME; rip = rip_lookup_by_vrf_name(vrf_name); if (!rip) { vty_out(vty, "%% RIP instance not found\n"); return CMD_SUCCESS; } if (!rip->enabled) { vty_out(vty, "%% RIP instance is disabled\n"); return CMD_SUCCESS; } vty_out(vty, "Routing Protocol is \"rip\"\n"); vty_out(vty, " Sending updates every %u seconds with +/-50%%,", rip->update_time); vty_out(vty, " next due in %lu seconds\n", thread_timer_remain_second(rip->t_update)); vty_out(vty, " Timeout after %u seconds,", rip->timeout_time); vty_out(vty, " garbage collect after %u seconds\n", rip->garbage_time); /* Filtering status show. */ config_show_distribute(vty, rip->distribute_ctx); /* Default metric information. */ vty_out(vty, " Default redistribution metric is %u\n", rip->default_metric); /* Redistribute information. */ vty_out(vty, " Redistributing:"); rip_show_redistribute_config(vty, rip); vty_out(vty, "\n"); vty_out(vty, " Default version control: send version %s,", lookup_msg(ri_version_msg, rip->version_send, NULL)); if (rip->version_recv == RI_RIP_VERSION_1_AND_2) vty_out(vty, " receive any version \n"); else vty_out(vty, " receive version %s \n", lookup_msg(ri_version_msg, rip->version_recv, NULL)); vty_out(vty, " Interface Send Recv Key-chain\n"); FOR_ALL_INTERFACES (rip->vrf, ifp) { ri = ifp->info; if (!ri->running) continue; if (ri->enable_network || ri->enable_interface) { if (ri->ri_send == RI_RIP_UNSPEC) send_version = lookup_msg(ri_version_msg, rip->version_send, NULL); else send_version = lookup_msg(ri_version_msg, ri->ri_send, NULL); if (ri->ri_receive == RI_RIP_UNSPEC) receive_version = lookup_msg(ri_version_msg, rip->version_recv, NULL); else receive_version = lookup_msg( ri_version_msg, ri->ri_receive, NULL); vty_out(vty, " %-17s%-3s %-3s %s\n", ifp->name, send_version, receive_version, ri->key_chain ? ri->key_chain : ""); } } vty_out(vty, " Routing for Networks:\n"); rip_show_network_config(vty, rip); { int found_passive = 0; FOR_ALL_INTERFACES (rip->vrf, ifp) { ri = ifp->info; if ((ri->enable_network || ri->enable_interface) && ri->passive) { if (!found_passive) { vty_out(vty, " Passive Interface(s):\n"); found_passive = 1; } vty_out(vty, " %s\n", ifp->name); } } } vty_out(vty, " Routing Information Sources:\n"); vty_out(vty, " Gateway BadPackets BadRoutes Distance Last Update\n"); rip_peer_display(vty, rip); rip_distance_show(vty, rip); return CMD_SUCCESS; } /* RIP configuration write function. */ static int config_write_rip(struct vty *vty) { struct rip *rip; int write = 0; RB_FOREACH(rip, rip_instance_head, &rip_instances) { char xpath[XPATH_MAXLEN]; struct lyd_node *dnode; snprintf(xpath, sizeof(xpath), "/frr-ripd:ripd/instance[vrf='%s']", rip->vrf_name); dnode = yang_dnode_get(running_config->dnode, xpath); assert(dnode); nb_cli_show_dnode_cmds(vty, dnode, false); /* Distribute configuration. */ config_write_distribute(vty, rip->distribute_ctx); /* Interface routemap configuration */ config_write_if_rmap(vty, rip->if_rmap_ctx); write = 1; } return write; } /* RIP node structure. */ static struct cmd_node rip_node = {RIP_NODE, "%s(config-router)# ", 1}; /* Distribute-list update functions. */ static void rip_distribute_update(struct distribute_ctx *ctx, struct distribute *dist) { struct interface *ifp; struct rip_interface *ri; struct access_list *alist; struct prefix_list *plist; if (!ctx->vrf || !dist->ifname) return; ifp = if_lookup_by_name(dist->ifname, ctx->vrf->vrf_id); if (ifp == NULL) return; ri = ifp->info; if (dist->list[DISTRIBUTE_V4_IN]) { alist = access_list_lookup(AFI_IP, dist->list[DISTRIBUTE_V4_IN]); if (alist) ri->list[RIP_FILTER_IN] = alist; else ri->list[RIP_FILTER_IN] = NULL; } else ri->list[RIP_FILTER_IN] = NULL; if (dist->list[DISTRIBUTE_V4_OUT]) { alist = access_list_lookup(AFI_IP, dist->list[DISTRIBUTE_V4_OUT]); if (alist) ri->list[RIP_FILTER_OUT] = alist; else ri->list[RIP_FILTER_OUT] = NULL; } else ri->list[RIP_FILTER_OUT] = NULL; if (dist->prefix[DISTRIBUTE_V4_IN]) { plist = prefix_list_lookup(AFI_IP, dist->prefix[DISTRIBUTE_V4_IN]); if (plist) ri->prefix[RIP_FILTER_IN] = plist; else ri->prefix[RIP_FILTER_IN] = NULL; } else ri->prefix[RIP_FILTER_IN] = NULL; if (dist->prefix[DISTRIBUTE_V4_OUT]) { plist = prefix_list_lookup(AFI_IP, dist->prefix[DISTRIBUTE_V4_OUT]); if (plist) ri->prefix[RIP_FILTER_OUT] = plist; else ri->prefix[RIP_FILTER_OUT] = NULL; } else ri->prefix[RIP_FILTER_OUT] = NULL; } void rip_distribute_update_interface(struct interface *ifp) { struct rip_interface *ri = ifp->info; struct rip *rip = ri->rip; struct distribute *dist; if (!rip) return; dist = distribute_lookup(rip->distribute_ctx, ifp->name); if (dist) rip_distribute_update(rip->distribute_ctx, dist); } /* Update all interface's distribute list. */ /* ARGSUSED */ static void rip_distribute_update_all(struct prefix_list *notused) { struct vrf *vrf = vrf_lookup_by_id(VRF_DEFAULT); struct interface *ifp; FOR_ALL_INTERFACES (vrf, ifp) rip_distribute_update_interface(ifp); } /* ARGSUSED */ static void rip_distribute_update_all_wrapper(struct access_list *notused) { rip_distribute_update_all(NULL); } /* Delete all added rip route. */ void rip_clean(struct rip *rip) { if (rip->enabled) rip_instance_disable(rip); stream_free(rip->obuf); for (int i = 0; i < ZEBRA_ROUTE_MAX; i++) if (rip->redist[i].route_map.name) free(rip->redist[i].route_map.name); route_table_finish(rip->table); route_table_finish(rip->neighbor); list_delete(&rip->peer_list); distribute_list_delete(&rip->distribute_ctx); if_rmap_ctx_delete(rip->if_rmap_ctx); rip_clean_network(rip); rip_passive_nondefault_clean(rip); vector_free(rip->enable_interface); route_table_finish(rip->enable_network); vector_free(rip->passive_nondefault); list_delete(&rip->offset_list_master); rip_interfaces_clean(rip); route_table_finish(rip->distance_table); RB_REMOVE(rip_instance_head, &rip_instances, rip); XFREE(MTYPE_RIP_VRF_NAME, rip->vrf_name); XFREE(MTYPE_RIP, rip); } static void rip_if_rmap_update(struct if_rmap_ctx *ctx, struct if_rmap *if_rmap) { struct interface *ifp = NULL; struct rip_interface *ri; struct route_map *rmap; struct vrf *vrf = NULL; if (ctx->name) vrf = vrf_lookup_by_name(ctx->name); if (vrf) ifp = if_lookup_by_name(if_rmap->ifname, vrf->vrf_id); if (ifp == NULL) return; ri = ifp->info; if (if_rmap->routemap[IF_RMAP_IN]) { rmap = route_map_lookup_by_name(if_rmap->routemap[IF_RMAP_IN]); if (rmap) ri->routemap[IF_RMAP_IN] = rmap; else ri->routemap[IF_RMAP_IN] = NULL; } else ri->routemap[RIP_FILTER_IN] = NULL; if (if_rmap->routemap[IF_RMAP_OUT]) { rmap = route_map_lookup_by_name(if_rmap->routemap[IF_RMAP_OUT]); if (rmap) ri->routemap[IF_RMAP_OUT] = rmap; else ri->routemap[IF_RMAP_OUT] = NULL; } else ri->routemap[RIP_FILTER_OUT] = NULL; } void rip_if_rmap_update_interface(struct interface *ifp) { struct rip_interface *ri = ifp->info; struct rip *rip = ri->rip; struct if_rmap *if_rmap; struct if_rmap_ctx *ctx; if (!rip) return; ctx = rip->if_rmap_ctx; if (!ctx) return; if_rmap = if_rmap_lookup(ctx, ifp->name); if (if_rmap) rip_if_rmap_update(ctx, if_rmap); } static void rip_routemap_update_redistribute(struct rip *rip) { for (int i = 0; i < ZEBRA_ROUTE_MAX; i++) { if (rip->redist[i].route_map.name) { rip->redist[i].route_map.map = route_map_lookup_by_name( rip->redist[i].route_map.name); route_map_counter_increment( rip->redist[i].route_map.map); } } } /* ARGSUSED */ static void rip_routemap_update(const char *notused) { struct vrf *vrf = vrf_lookup_by_id(VRF_DEFAULT); struct rip *rip; struct interface *ifp; FOR_ALL_INTERFACES (vrf, ifp) rip_if_rmap_update_interface(ifp); rip = vrf->info; if (rip) rip_routemap_update_redistribute(rip); } /* Link RIP instance to VRF. */ static void rip_vrf_link(struct rip *rip, struct vrf *vrf) { struct interface *ifp; rip->vrf = vrf; rip->distribute_ctx->vrf = vrf; vrf->info = rip; FOR_ALL_INTERFACES (vrf, ifp) rip_interface_sync(ifp); } /* Unlink RIP instance from VRF. */ static void rip_vrf_unlink(struct rip *rip, struct vrf *vrf) { struct interface *ifp; rip->vrf = NULL; rip->distribute_ctx->vrf = NULL; vrf->info = NULL; FOR_ALL_INTERFACES (vrf, ifp) rip_interface_sync(ifp); } static void rip_instance_enable(struct rip *rip, struct vrf *vrf, int sock) { rip->sock = sock; rip_vrf_link(rip, vrf); rip->enabled = true; /* Resend all redistribute requests. */ rip_redistribute_enable(rip); /* Create read and timer thread. */ rip_event(rip, RIP_READ, rip->sock); rip_event(rip, RIP_UPDATE_EVENT, 1); rip_zebra_vrf_register(vrf); } static void rip_instance_disable(struct rip *rip) { struct vrf *vrf = rip->vrf; struct route_node *rp; /* Clear RIP routes */ for (rp = route_top(rip->table); rp; rp = route_next(rp)) { struct rip_info *rinfo; struct list *list; struct listnode *listnode; if ((list = rp->info) == NULL) continue; rinfo = listgetdata(listhead(list)); if (rip_route_rte(rinfo)) rip_zebra_ipv4_delete(rip, rp); for (ALL_LIST_ELEMENTS_RO(list, listnode, rinfo)) { RIP_TIMER_OFF(rinfo->t_timeout); RIP_TIMER_OFF(rinfo->t_garbage_collect); rip_info_free(rinfo); } list_delete(&list); rp->info = NULL; route_unlock_node(rp); } /* Flush all redistribute requests. */ rip_redistribute_disable(rip); /* Cancel RIP related timers. */ RIP_TIMER_OFF(rip->t_update); RIP_TIMER_OFF(rip->t_triggered_update); RIP_TIMER_OFF(rip->t_triggered_interval); /* Cancel read thread. */ THREAD_READ_OFF(rip->t_read); /* Close RIP socket. */ close(rip->sock); rip->sock = -1; /* Clear existing peers. */ list_delete_all_node(rip->peer_list); rip_zebra_vrf_deregister(vrf); rip_vrf_unlink(rip, vrf); rip->enabled = false; } static int rip_vrf_new(struct vrf *vrf) { if (IS_RIP_DEBUG_EVENT) zlog_debug("%s: VRF created: %s(%u)", __func__, vrf->name, vrf->vrf_id); return 0; } static int rip_vrf_delete(struct vrf *vrf) { if (IS_RIP_DEBUG_EVENT) zlog_debug("%s: VRF deleted: %s(%u)", __func__, vrf->name, vrf->vrf_id); return 0; } static int rip_vrf_enable(struct vrf *vrf) { struct rip *rip; int socket; rip = rip_lookup_by_vrf_name(vrf->name); if (!rip || rip->enabled) return 0; if (IS_RIP_DEBUG_EVENT) zlog_debug("%s: VRF %s(%u) enabled", __func__, vrf->name, vrf->vrf_id); /* Activate the VRF RIP instance. */ if (!rip->enabled) { socket = rip_create_socket(vrf); if (socket < 0) return -1; rip_instance_enable(rip, vrf, socket); } return 0; } static int rip_vrf_disable(struct vrf *vrf) { struct rip *rip; rip = rip_lookup_by_vrf_name(vrf->name); if (!rip || !rip->enabled) return 0; if (IS_RIP_DEBUG_EVENT) zlog_debug("%s: VRF %s(%u) disabled", __func__, vrf->name, vrf->vrf_id); /* Deactivate the VRF RIP instance. */ if (rip->enabled) rip_instance_disable(rip); return 0; } void rip_vrf_init(void) { vrf_init(rip_vrf_new, rip_vrf_enable, rip_vrf_disable, rip_vrf_delete, NULL); } void rip_vrf_terminate(void) { vrf_terminate(); } /* Allocate new rip structure and set default value. */ void rip_init(void) { /* Install top nodes. */ install_node(&rip_node, config_write_rip); /* Install rip commands. */ install_element(VIEW_NODE, &show_ip_rip_cmd); install_element(VIEW_NODE, &show_ip_rip_status_cmd); install_default(RIP_NODE); /* Debug related init. */ rip_debug_init(); /* Access list install. */ access_list_init(); access_list_add_hook(rip_distribute_update_all_wrapper); access_list_delete_hook(rip_distribute_update_all_wrapper); /* Prefix list initialize.*/ prefix_list_init(); prefix_list_add_hook(rip_distribute_update_all); prefix_list_delete_hook(rip_distribute_update_all); /* Distribute list install. */ distribute_list_init(RIP_NODE); /* Route-map */ rip_route_map_init(); route_map_add_hook(rip_routemap_update); route_map_delete_hook(rip_routemap_update); if_rmap_init(RIP_NODE); } frr-7.2.1/ripd/ripd.conf.sample0000644000000000000000000000052713610377563013243 00000000000000! -*- rip -*- ! ! RIPd sample configuration file ! hostname ripd password zebra ! ! debug rip events ! debug rip packet ! router rip ! network 11.0.0.0/8 ! network eth0 ! route 10.0.0.0/8 ! distribute-list private-only in eth0 ! !access-list private-only permit 10.0.0.0/8 !access-list private-only deny any ! !log file ripd.log ! log stdout frr-7.2.1/ripd/ripd.h0000644000000000000000000003605113610377563011266 00000000000000/* RIP related values and structures. * Copyright (C) 1997, 1998, 1999 Kunihiro Ishiguro * * This file is part of GNU Zebra. * * GNU Zebra is free software; you can redistribute it and/or modify it * under the terms of the GNU General Public License as published by the * Free Software Foundation; either version 2, or (at your option) any * later version. * * GNU Zebra 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; see the file COPYING; if not, write to the Free Software * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA */ #ifndef _ZEBRA_RIP_H #define _ZEBRA_RIP_H #include "hook.h" #include "nexthop.h" #include "distribute.h" #include "memory.h" /* RIP version number. */ #define RIPv1 1 #define RIPv2 2 /* N.B. stuff will break if (RIPv1 != RI_RIP_VERSION_1) || (RIPv2 != RI_RIP_VERSION_2) */ /* RIP command list. */ #define RIP_REQUEST 1 #define RIP_RESPONSE 2 #define RIP_TRACEON 3 /* Obsolete */ #define RIP_TRACEOFF 4 /* Obsolete */ #define RIP_POLL 5 #define RIP_POLL_ENTRY 6 #define RIP_COMMAND_MAX 7 /* RIP metric infinity value.*/ #define RIP_METRIC_INFINITY 16 /* Normal RIP packet min and max size. */ #define RIP_PACKET_MINSIZ 4 #define RIP_PACKET_MAXSIZ 512 #define RIP_HEADER_SIZE 4 #define RIP_RTE_SIZE 20 /* Max count of routing table entry in one rip packet. */ #define RIP_MAX_RTE ((RIP_PACKET_MAXSIZ - RIP_HEADER_SIZE) / RIP_RTE_SIZE) /* RIP version 2 multicast address. */ #ifndef INADDR_RIP_GROUP #define INADDR_RIP_GROUP 0xe0000009 /* 224.0.0.9 */ #endif /* RIP peer timeout value. */ #define RIP_PEER_TIMER_DEFAULT 180 /* RIP port number. */ #define RIP_PORT_DEFAULT 520 #define RIP_VTY_PORT 2602 /* Default configuration file name. */ #define RIPD_DEFAULT_CONFIG "ripd.conf" /* RIP route types. */ #define RIP_ROUTE_RTE 0 #define RIP_ROUTE_STATIC 1 #define RIP_ROUTE_DEFAULT 2 #define RIP_ROUTE_REDISTRIBUTE 3 #define RIP_ROUTE_INTERFACE 4 /* RIPv2 special RTE family types */ #define RIP_FAMILY_AUTH 0xffff /* RIPv2 authentication types, for RIP_FAMILY_AUTH RTE's */ #define RIP_NO_AUTH 0 #define RIP_AUTH_DATA 1 #define RIP_AUTH_SIMPLE_PASSWORD 2 #define RIP_AUTH_MD5 3 /* RIPv2 Simple authentication */ #define RIP_AUTH_SIMPLE_SIZE 16 /* RIPv2 MD5 authentication. */ #define RIP_AUTH_MD5_SIZE 16 #define RIP_AUTH_MD5_COMPAT_SIZE RIP_RTE_SIZE /* YANG paths */ #define RIP_INSTANCE "/frr-ripd:ripd/instance" #define RIP_IFACE "/frr-interface:lib/interface/frr-ripd:rip" DECLARE_MGROUP(RIPD) /* RIP structure. */ struct rip { RB_ENTRY(rip) entry; /* VRF this routing instance is associated with. */ char *vrf_name; /* VRF backpointer (might be NULL if the VRF doesn't exist). */ struct vrf *vrf; /* Status of the routing instance. */ bool enabled; /* RIP socket. */ int sock; /* Default version of rip instance. */ int version_send; /* version 1 or 2 (but not both) */ int version_recv; /* version 1 or 2 or both */ /* Output buffer of RIP. */ struct stream *obuf; /* RIP routing information base. */ struct route_table *table; /* RIP static neighbors. */ struct route_table *neighbor; /* Linked list of RIP peers. */ struct list *peer_list; /* RIP threads. */ struct thread *t_read; /* Update and garbage timer. */ struct thread *t_update; /* Triggered update hack. */ int trigger; struct thread *t_triggered_update; struct thread *t_triggered_interval; /* RIP timer values. */ uint32_t update_time; uint32_t timeout_time; uint32_t garbage_time; /* RIP default metric. */ uint8_t default_metric; /* RIP default distance. */ uint8_t distance; struct route_table *distance_table; /* RIP ECMP flag */ bool ecmp; /* Are we in passive-interface default mode? */ bool passive_default; /* RIP enabled interfaces. */ vector enable_interface; /* RIP enabled networks. */ struct route_table *enable_network; /* Vector to store passive-interface name. */ vector passive_nondefault; /* RIP offset-lists. */ struct list *offset_list_master; /* RIP redistribute configuration. */ struct { bool enabled; struct { char *name; struct route_map *map; } route_map; bool metric_config; uint8_t metric; } redist[ZEBRA_ROUTE_MAX]; /* For distribute-list container */ struct distribute_ctx *distribute_ctx; /* For if_rmap container */ struct if_rmap_ctx *if_rmap_ctx; /* Counters for SNMP. */ struct { /* RIP route changes. */ long route_changes; /* RIP queries. */ long queries; } counters; }; RB_HEAD(rip_instance_head, rip); RB_PROTOTYPE(rip_instance_head, rip, entry, rip_instance_compare) /* RIP routing table entry which belong to rip_packet. */ struct rte { uint16_t family; /* Address family of this route. */ uint16_t tag; /* Route Tag which included in RIP2 packet. */ struct in_addr prefix; /* Prefix of rip route. */ struct in_addr mask; /* Netmask of rip route. */ struct in_addr nexthop; /* Next hop of rip route. */ uint32_t metric; /* Metric value of rip route. */ }; /* RIP packet structure. */ struct rip_packet { unsigned char command; /* Command type of RIP packet. */ unsigned char version; /* RIP version which coming from peer. */ unsigned char pad1; /* Padding of RIP packet header. */ unsigned char pad2; /* Same as above. */ struct rte rte[1]; /* Address structure. */ }; /* Buffer to read RIP packet. */ union rip_buf { struct rip_packet rip_packet; char buf[RIP_PACKET_MAXSIZ]; }; /* RIP route information. */ struct rip_info { /* This route's type. */ int type; /* Sub type. */ int sub_type; /* RIP nexthop. */ struct nexthop nh; struct in_addr from; /* Metric of this route. */ uint32_t metric; /* External metric of this route. if learnt from an externalm proto */ uint32_t external_metric; /* Tag information of this route. */ uint16_t tag; /* Flags of RIP route. */ #define RIP_RTF_FIB 1 #define RIP_RTF_CHANGED 2 uint8_t flags; /* Garbage collect timer. */ struct thread *t_timeout; struct thread *t_garbage_collect; /* Route-map futures - this variables can be changed. */ struct in_addr nexthop_out; uint8_t metric_set; uint32_t metric_out; uint16_t tag_out; ifindex_t ifindex_out; struct route_node *rp; uint8_t distance; }; typedef enum { RIP_NO_SPLIT_HORIZON = 0, RIP_SPLIT_HORIZON, RIP_SPLIT_HORIZON_POISONED_REVERSE } split_horizon_policy_t; /* RIP specific interface configuration. */ struct rip_interface { /* Parent routing instance. */ struct rip *rip; /* RIP is enabled on this interface. */ int enable_network; int enable_interface; /* RIP is running on this interface. */ int running; /* RIP version control. */ int ri_send; int ri_receive; /* RIPv2 broadcast mode */ bool v2_broadcast; /* RIPv2 authentication type. */ int auth_type; /* RIPv2 authentication string. */ char *auth_str; /* RIPv2 authentication key chain. */ char *key_chain; /* value to use for md5->auth_len */ int md5_auth_len; /* Split horizon flag. */ split_horizon_policy_t split_horizon; /* For filter type slot. */ #define RIP_FILTER_IN 0 #define RIP_FILTER_OUT 1 #define RIP_FILTER_MAX 2 /* Access-list. */ struct access_list *list[RIP_FILTER_MAX]; /* Prefix-list. */ struct prefix_list *prefix[RIP_FILTER_MAX]; /* Route-map. */ struct route_map *routemap[RIP_FILTER_MAX]; /* Wake up thread. */ struct thread *t_wakeup; /* Interface statistics. */ int recv_badpackets; int recv_badroutes; int sent_updates; /* Passive interface. */ int passive; }; /* RIP peer information. */ struct rip_peer { /* Parent routing instance. */ struct rip *rip; /* Peer address. */ struct in_addr addr; /* Peer RIP tag value. */ int domain; /* Last update time. */ time_t uptime; /* Peer RIP version. */ uint8_t version; /* Statistics. */ int recv_badpackets; int recv_badroutes; /* Timeout thread. */ struct thread *t_timeout; }; struct rip_distance { /* Distance value for the IP source prefix. */ uint8_t distance; /* Name of the access-list to be matched. */ char *access_list; }; struct rip_md5_info { uint16_t family; uint16_t type; uint16_t packet_len; uint8_t keyid; uint8_t auth_len; uint32_t sequence; uint32_t reserv1; uint32_t reserv2; }; struct rip_md5_data { uint16_t family; uint16_t type; uint8_t digest[16]; }; /* RIP accepet/announce methods. */ #define RI_RIP_UNSPEC 0 #define RI_RIP_VERSION_1 1 #define RI_RIP_VERSION_2 2 #define RI_RIP_VERSION_1_AND_2 3 #define RI_RIP_VERSION_NONE 4 /* N.B. stuff will break if (RIPv1 != RI_RIP_VERSION_1) || (RIPv2 != RI_RIP_VERSION_2) */ /* RIP event. */ enum rip_event { RIP_READ, RIP_UPDATE_EVENT, RIP_TRIGGERED_UPDATE, }; /* Macro for timer turn on. */ #define RIP_TIMER_ON(T,F,V) thread_add_timer (master, (F), rinfo, (V), &(T)) /* Macro for timer turn off. */ #define RIP_TIMER_OFF(X) THREAD_TIMER_OFF(X) #define RIP_OFFSET_LIST_IN 0 #define RIP_OFFSET_LIST_OUT 1 #define RIP_OFFSET_LIST_MAX 2 struct rip_offset_list { /* Parent routing instance. */ struct rip *rip; char *ifname; struct { char *alist_name; /* struct access_list *alist; */ uint8_t metric; } direct[RIP_OFFSET_LIST_MAX]; }; /* Prototypes. */ extern void rip_init(void); extern void rip_clean(struct rip *rip); extern void rip_clean_network(struct rip *rip); extern void rip_interfaces_clean(struct rip *rip); extern int rip_passive_nondefault_set(struct rip *rip, const char *ifname); extern int rip_passive_nondefault_unset(struct rip *rip, const char *ifname); extern void rip_passive_nondefault_clean(struct rip *rip); extern void rip_if_init(void); extern void rip_route_map_init(void); extern void rip_zebra_vrf_register(struct vrf *vrf); extern void rip_zebra_vrf_deregister(struct vrf *vrf); extern void rip_zclient_init(struct thread_master *); extern void rip_zclient_stop(void); extern int if_check_address(struct rip *rip, struct in_addr addr); extern struct rip *rip_lookup_by_vrf_id(vrf_id_t vrf_id); extern struct rip *rip_lookup_by_vrf_name(const char *vrf_name); extern struct rip *rip_create(const char *vrf_name, struct vrf *vrf, int socket); extern int rip_request_send(struct sockaddr_in *, struct interface *, uint8_t, struct connected *); extern int rip_neighbor_lookup(struct rip *rip, struct sockaddr_in *from); extern int rip_neighbor_add(struct rip *rip, struct prefix_ipv4 *p); extern int rip_neighbor_delete(struct rip *rip, struct prefix_ipv4 *p); extern int rip_enable_network_add(struct rip *rip, struct prefix *p); extern int rip_enable_network_delete(struct rip *rip, struct prefix *p); extern int rip_enable_if_add(struct rip *rip, const char *ifname); extern int rip_enable_if_delete(struct rip *rip, const char *ifname); extern void rip_event(struct rip *rip, enum rip_event event, int sock); extern void rip_ecmp_disable(struct rip *rip); extern int rip_create_socket(struct vrf *vrf); extern int rip_redistribute_check(struct rip *rip, int type); extern void rip_redistribute_conf_update(struct rip *rip, int type); extern void rip_redistribute_conf_delete(struct rip *rip, int type); extern void rip_redistribute_add(struct rip *rip, int type, int sub_type, struct prefix_ipv4 *p, struct nexthop *nh, unsigned int metric, unsigned char distance, route_tag_t tag); extern void rip_redistribute_delete(struct rip *rip, int type, int sub_type, struct prefix_ipv4 *p, ifindex_t ifindex); extern void rip_redistribute_withdraw(struct rip *rip, int type); extern void rip_zebra_ipv4_add(struct rip *rip, struct route_node *rp); extern void rip_zebra_ipv4_delete(struct rip *rip, struct route_node *rp); extern void rip_interface_multicast_set(int, struct connected *); extern void rip_distribute_update_interface(struct interface *); extern void rip_if_rmap_update_interface(struct interface *ifp); extern int rip_show_network_config(struct vty *vty, struct rip *rip); extern void rip_show_redistribute_config(struct vty *vty, struct rip *rip); extern void rip_peer_update(struct rip *rip, struct sockaddr_in *from, uint8_t version); extern void rip_peer_bad_route(struct rip *rip, struct sockaddr_in *from); extern void rip_peer_bad_packet(struct rip *rip, struct sockaddr_in *from); extern void rip_peer_display(struct vty *vty, struct rip *rip); extern struct rip_peer *rip_peer_lookup(struct rip *rip, struct in_addr *addr); extern struct rip_peer *rip_peer_lookup_next(struct rip *rip, struct in_addr *addr); extern int rip_peer_list_cmp(struct rip_peer *p1, struct rip_peer *p2); extern void rip_peer_list_del(void *arg); extern void rip_info_free(struct rip_info *); extern struct rip *rip_info_get_instance(const struct rip_info *rinfo); extern struct rip_distance *rip_distance_new(void); extern void rip_distance_free(struct rip_distance *rdistance); extern uint8_t rip_distance_apply(struct rip *rip, struct rip_info *rinfo); extern void rip_redistribute_enable(struct rip *rip); extern void rip_redistribute_disable(struct rip *rip); extern int rip_route_rte(struct rip_info *rinfo); extern struct rip_info *rip_ecmp_add(struct rip *rip, struct rip_info *rinfo_new); extern struct rip_info *rip_ecmp_replace(struct rip *rip, struct rip_info *rinfo_new); extern struct rip_info *rip_ecmp_delete(struct rip *rip, struct rip_info *rinfo); extern struct rip_offset_list *rip_offset_list_new(struct rip *rip, const char *ifname); extern void offset_list_del(struct rip_offset_list *offset); extern void offset_list_free(struct rip_offset_list *offset); extern struct rip_offset_list *rip_offset_list_lookup(struct rip *rip, const char *ifname); extern int rip_offset_list_apply_in(struct prefix_ipv4 *, struct interface *, uint32_t *); extern int rip_offset_list_apply_out(struct prefix_ipv4 *, struct interface *, uint32_t *); extern int offset_list_cmp(struct rip_offset_list *o1, struct rip_offset_list *o2); extern void rip_vrf_init(void); extern void rip_vrf_terminate(void); /* YANG notifications */ extern void ripd_notif_send_auth_type_failure(const char *ifname); extern void ripd_notif_send_auth_failure(const char *ifname); extern struct zebra_privs_t ripd_privs; extern struct rip_instance_head rip_instances; /* Master thread strucutre. */ extern struct thread_master *master; DECLARE_HOOK(rip_ifaddr_add, (struct connected * ifc), (ifc)) DECLARE_HOOK(rip_ifaddr_del, (struct connected * ifc), (ifc)) /* Northbound. */ extern void rip_cli_init(void); extern const struct frr_yang_module_info frr_ripd_info; #endif /* _ZEBRA_RIP_H */ frr-7.2.1/ripd/subdir.am0000644000000000000000000000223313610377563011761 00000000000000# # ripd # if RIPD noinst_LIBRARIES += ripd/librip.a sbin_PROGRAMS += ripd/ripd dist_examples_DATA += ripd/ripd.conf.sample vtysh_scan += \ $(top_srcdir)/ripd/rip_cli.c \ $(top_srcdir)/ripd/rip_debug.c \ $(top_srcdir)/ripd/ripd.c \ # end if SNMP module_LTLIBRARIES += ripd/ripd_snmp.la endif man8 += $(MANBUILD)/frr-ripd.8 endif ripd_librip_a_SOURCES = \ ripd/rip_cli.c \ ripd/rip_debug.c \ ripd/rip_errors.c \ ripd/rip_interface.c \ ripd/rip_offset.c \ ripd/rip_northbound.c \ ripd/rip_peer.c \ ripd/rip_routemap.c \ ripd/rip_zebra.c \ ripd/ripd.c \ # end ripd/rip_cli_clippy.c: $(CLIPPY_DEPS) ripd/rip_cli.$(OBJEXT): ripd/rip_cli_clippy.c noinst_HEADERS += \ ripd/rip_cli.h \ ripd/rip_debug.h \ ripd/rip_errors.h \ ripd/rip_interface.h \ ripd/ripd.h \ # end ripd_ripd_LDADD = ripd/librip.a lib/libfrr.la $(LIBCAP) ripd_ripd_SOURCES = \ ripd/rip_main.c \ # end nodist_ripd_ripd_SOURCES = \ yang/frr-ripd.yang.c \ # end ripd_ripd_snmp_la_SOURCES = ripd/rip_snmp.c ripd_ripd_snmp_la_CFLAGS = $(WERROR) $(SNMP_CFLAGS) -std=gnu99 ripd_ripd_snmp_la_LDFLAGS = -avoid-version -module -shared -export-dynamic ripd_ripd_snmp_la_LIBADD = lib/libfrrsnmp.la frr-7.2.1/ripngd/0000755000000000000000000000000013610377563010557 500000000000000frr-7.2.1/ripngd/Makefile0000644000000000000000000000022513610377563012136 00000000000000all: ALWAYS @$(MAKE) -s -C .. ripngd/ripngd %: ALWAYS @$(MAKE) -s -C .. ripngd/$@ Makefile: #nothing ALWAYS: .PHONY: ALWAYS makefiles .SUFFIXES: frr-7.2.1/ripngd/ripng_cli.c0000644000000000000000000003403313610377563012614 00000000000000/* * Copyright (C) 1998 Kunihiro Ishiguro * Copyright (C) 2018 NetDEF, Inc. * Renato Westphal * * This program is free software; you can redistribute it and/or modify it * under the terms of the 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; see the file COPYING; if not, write to the Free Software * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA */ #include #include "if.h" #include "vrf.h" #include "log.h" #include "prefix.h" #include "command.h" #include "northbound_cli.h" #include "libfrr.h" #include "ripngd/ripngd.h" #include "ripngd/ripng_cli.h" #ifndef VTYSH_EXTRACT_PL #include "ripngd/ripng_cli_clippy.c" #endif /* * XPath: /frr-ripngd:ripngd/instance */ DEFPY_NOSH (router_ripng, router_ripng_cmd, "router ripng [vrf NAME]", "Enable a routing process\n" "Make RIPng instance command\n" VRF_CMD_HELP_STR) { char xpath[XPATH_MAXLEN]; int ret; /* Build RIPng instance XPath. */ if (!vrf) vrf = VRF_DEFAULT_NAME; snprintf(xpath, sizeof(xpath), "/frr-ripngd:ripngd/instance[vrf='%s']", vrf); nb_cli_enqueue_change(vty, xpath, NB_OP_CREATE, NULL); ret = nb_cli_apply_changes(vty, NULL); if (ret == CMD_SUCCESS) VTY_PUSH_XPATH(RIPNG_NODE, xpath); return ret; } DEFPY (no_router_ripng, no_router_ripng_cmd, "no router ripng [vrf NAME]", NO_STR "Enable a routing process\n" "Make RIPng instance command\n" VRF_CMD_HELP_STR) { char xpath[XPATH_MAXLEN]; /* Build RIPng instance XPath. */ if (!vrf) vrf = VRF_DEFAULT_NAME; snprintf(xpath, sizeof(xpath), "/frr-ripngd:ripngd/instance[vrf='%s']", vrf); nb_cli_enqueue_change(vty, xpath, NB_OP_DESTROY, NULL); return nb_cli_apply_changes(vty, NULL); } void cli_show_router_ripng(struct vty *vty, struct lyd_node *dnode, bool show_defaults) { const char *vrf_name; vrf_name = yang_dnode_get_string(dnode, "./vrf"); vty_out(vty, "!\n"); vty_out(vty, "router ripng"); if (!strmatch(vrf_name, VRF_DEFAULT_NAME)) vty_out(vty, " vrf %s", vrf_name); vty_out(vty, "\n"); } /* * XPath: /frr-ripngd:ripngd/instance/allow-ecmp */ DEFPY (ripng_allow_ecmp, ripng_allow_ecmp_cmd, "[no] allow-ecmp", NO_STR "Allow Equal Cost MultiPath\n") { nb_cli_enqueue_change(vty, "./allow-ecmp", NB_OP_MODIFY, no ? "false" : "true"); return nb_cli_apply_changes(vty, NULL); } void cli_show_ripng_allow_ecmp(struct vty *vty, struct lyd_node *dnode, bool show_defaults) { if (!yang_dnode_get_bool(dnode, NULL)) vty_out(vty, " no"); vty_out(vty, " allow-ecmp\n"); } /* * XPath: /frr-ripngd:ripngd/instance/default-information-originate */ DEFPY (ripng_default_information_originate, ripng_default_information_originate_cmd, "[no] default-information originate", NO_STR "Default route information\n" "Distribute default route\n") { nb_cli_enqueue_change(vty, "./default-information-originate", NB_OP_MODIFY, no ? "false" : "true"); return nb_cli_apply_changes(vty, NULL); } void cli_show_ripng_default_information_originate(struct vty *vty, struct lyd_node *dnode, bool show_defaults) { if (!yang_dnode_get_bool(dnode, NULL)) vty_out(vty, " no"); vty_out(vty, " default-information originate\n"); } /* * XPath: /frr-ripngd:ripngd/instance/default-metric */ DEFPY (ripng_default_metric, ripng_default_metric_cmd, "default-metric (1-16)", "Set a metric of redistribute routes\n" "Default metric\n") { nb_cli_enqueue_change(vty, "./default-metric", NB_OP_MODIFY, default_metric_str); return nb_cli_apply_changes(vty, NULL); } DEFPY (no_ripng_default_metric, no_ripng_default_metric_cmd, "no default-metric [(1-16)]", NO_STR "Set a metric of redistribute routes\n" "Default metric\n") { nb_cli_enqueue_change(vty, "./default-metric", NB_OP_MODIFY, NULL); return nb_cli_apply_changes(vty, NULL); } void cli_show_ripng_default_metric(struct vty *vty, struct lyd_node *dnode, bool show_defaults) { vty_out(vty, " default-metric %s\n", yang_dnode_get_string(dnode, NULL)); } /* * XPath: /frr-ripngd:ripngd/instance/network */ DEFPY (ripng_network_prefix, ripng_network_prefix_cmd, "[no] network X:X::X:X/M", NO_STR "RIPng enable on specified interface or network.\n" "IPv6 network\n") { nb_cli_enqueue_change(vty, "./network", no ? NB_OP_DESTROY : NB_OP_CREATE, network_str); return nb_cli_apply_changes(vty, NULL); } void cli_show_ripng_network_prefix(struct vty *vty, struct lyd_node *dnode, bool show_defaults) { vty_out(vty, " network %s\n", yang_dnode_get_string(dnode, NULL)); } /* * XPath: /frr-ripngd:ripngd/instance/interface */ DEFPY (ripng_network_if, ripng_network_if_cmd, "[no] network WORD", NO_STR "RIPng enable on specified interface or network.\n" "Interface name\n") { nb_cli_enqueue_change(vty, "./interface", no ? NB_OP_DESTROY : NB_OP_CREATE, network); return nb_cli_apply_changes(vty, NULL); } void cli_show_ripng_network_interface(struct vty *vty, struct lyd_node *dnode, bool show_defaults) { vty_out(vty, " network %s\n", yang_dnode_get_string(dnode, NULL)); } /* * XPath: /frr-ripngd:ripngd/instance/offset-list */ DEFPY (ripng_offset_list, ripng_offset_list_cmd, "[no] offset-list WORD$acl $direction (0-16)$metric [IFNAME]", NO_STR "Modify RIPng metric\n" "Access-list name\n" "For incoming updates\n" "For outgoing updates\n" "Metric value\n" "Interface to match\n") { if (!no) { nb_cli_enqueue_change(vty, ".", NB_OP_CREATE, NULL); nb_cli_enqueue_change(vty, "./access-list", NB_OP_MODIFY, acl); nb_cli_enqueue_change(vty, "./metric", NB_OP_MODIFY, metric_str); } else nb_cli_enqueue_change(vty, ".", NB_OP_DESTROY, NULL); return nb_cli_apply_changes( vty, "./offset-list[interface='%s'][direction='%s']", ifname ? ifname : "*", direction); } void cli_show_ripng_offset_list(struct vty *vty, struct lyd_node *dnode, bool show_defaults) { const char *interface; interface = yang_dnode_get_string(dnode, "./interface"); vty_out(vty, " offset-list %s %s %s", yang_dnode_get_string(dnode, "./access-list"), yang_dnode_get_string(dnode, "./direction"), yang_dnode_get_string(dnode, "./metric")); if (!strmatch(interface, "*")) vty_out(vty, " %s", interface); vty_out(vty, "\n"); } /* * XPath: /frr-ripngd:ripngd/instance/passive-interface */ DEFPY (ripng_passive_interface, ripng_passive_interface_cmd, "[no] passive-interface IFNAME", NO_STR "Suppress routing updates on an interface\n" "Interface name\n") { nb_cli_enqueue_change(vty, "./passive-interface", no ? NB_OP_DESTROY : NB_OP_CREATE, ifname); return nb_cli_apply_changes(vty, NULL); } void cli_show_ripng_passive_interface(struct vty *vty, struct lyd_node *dnode, bool show_defaults) { vty_out(vty, " passive-interface %s\n", yang_dnode_get_string(dnode, NULL)); } /* * XPath: /frr-ripngd:ripngd/instance/redistribute */ DEFPY (ripng_redistribute, ripng_redistribute_cmd, "[no] redistribute " FRR_REDIST_STR_RIPNGD "$protocol [{metric (0-16)|route-map WORD}]", NO_STR REDIST_STR FRR_REDIST_HELP_STR_RIPNGD "Metric\n" "Metric value\n" "Route map reference\n" "Pointer to route-map entries\n") { if (!no) { nb_cli_enqueue_change(vty, ".", NB_OP_CREATE, NULL); nb_cli_enqueue_change(vty, "./route-map", route_map ? NB_OP_MODIFY : NB_OP_DESTROY, route_map); nb_cli_enqueue_change(vty, "./metric", metric_str ? NB_OP_MODIFY : NB_OP_DESTROY, metric_str); } else nb_cli_enqueue_change(vty, ".", NB_OP_DESTROY, NULL); return nb_cli_apply_changes(vty, "./redistribute[protocol='%s']", protocol); } void cli_show_ripng_redistribute(struct vty *vty, struct lyd_node *dnode, bool show_defaults) { vty_out(vty, " redistribute %s", yang_dnode_get_string(dnode, "./protocol")); if (yang_dnode_exists(dnode, "./metric")) vty_out(vty, " metric %s", yang_dnode_get_string(dnode, "./metric")); if (yang_dnode_exists(dnode, "./route-map")) vty_out(vty, " route-map %s", yang_dnode_get_string(dnode, "./route-map")); vty_out(vty, "\n"); } /* * XPath: /frr-ripngd:ripngd/instance/static-route */ DEFPY (ripng_route, ripng_route_cmd, "[no] route X:X::X:X/M", NO_STR "Static route setup\n" "Set static RIPng route announcement\n") { nb_cli_enqueue_change(vty, "./static-route", no ? NB_OP_DESTROY : NB_OP_CREATE, route_str); return nb_cli_apply_changes(vty, NULL); } void cli_show_ripng_route(struct vty *vty, struct lyd_node *dnode, bool show_defaults) { vty_out(vty, " route %s\n", yang_dnode_get_string(dnode, NULL)); } /* * XPath: /frr-ripngd:ripngd/instance/aggregate-addres */ DEFPY (ripng_aggregate_address, ripng_aggregate_address_cmd, "[no] aggregate-address X:X::X:X/M", NO_STR "Set aggregate RIPng route announcement\n" "Aggregate network\n") { nb_cli_enqueue_change(vty, "./aggregate-address", no ? NB_OP_DESTROY : NB_OP_CREATE, aggregate_address_str); return nb_cli_apply_changes(vty, NULL); } void cli_show_ripng_aggregate_address(struct vty *vty, struct lyd_node *dnode, bool show_defaults) { vty_out(vty, " aggregate-address %s\n", yang_dnode_get_string(dnode, NULL)); } /* * XPath: /frr-ripngd:ripngd/instance/timers */ DEFPY (ripng_timers, ripng_timers_cmd, "timers basic (1-65535)$update (1-65535)$timeout (1-65535)$garbage", "RIPng timers setup\n" "Basic timer\n" "Routing table update timer value in second. Default is 30.\n" "Routing information timeout timer. Default is 180.\n" "Garbage collection timer. Default is 120.\n") { nb_cli_enqueue_change(vty, "./update-interval", NB_OP_MODIFY, update_str); nb_cli_enqueue_change(vty, "./holddown-interval", NB_OP_MODIFY, timeout_str); nb_cli_enqueue_change(vty, "./flush-interval", NB_OP_MODIFY, garbage_str); return nb_cli_apply_changes(vty, "./timers"); } DEFPY (no_ripng_timers, no_ripng_timers_cmd, "no timers basic [(1-65535) (1-65535) (1-65535)]", NO_STR "RIPng timers setup\n" "Basic timer\n" "Routing table update timer value in second. Default is 30.\n" "Routing information timeout timer. Default is 180.\n" "Garbage collection timer. Default is 120.\n") { nb_cli_enqueue_change(vty, "./update-interval", NB_OP_MODIFY, NULL); nb_cli_enqueue_change(vty, "./holddown-interval", NB_OP_MODIFY, NULL); nb_cli_enqueue_change(vty, "./flush-interval", NB_OP_MODIFY, NULL); return nb_cli_apply_changes(vty, "./timers"); } void cli_show_ripng_timers(struct vty *vty, struct lyd_node *dnode, bool show_defaults) { vty_out(vty, " timers basic %s %s %s\n", yang_dnode_get_string(dnode, "./update-interval"), yang_dnode_get_string(dnode, "./holddown-interval"), yang_dnode_get_string(dnode, "./flush-interval")); } /* * XPath: /frr-interface:lib/interface/frr-ripngd:ripng/split-horizon */ DEFPY (ipv6_ripng_split_horizon, ipv6_ripng_split_horizon_cmd, "[no] ipv6 ripng split-horizon [poisoned-reverse$poisoned_reverse]", NO_STR IPV6_STR "Routing Information Protocol\n" "Perform split horizon\n" "With poisoned-reverse\n") { const char *value; if (no) value = "disabled"; else if (poisoned_reverse) value = "poison-reverse"; else value = "simple"; nb_cli_enqueue_change(vty, "./split-horizon", NB_OP_MODIFY, value); return nb_cli_apply_changes(vty, "./frr-ripngd:ripng"); } void cli_show_ipv6_ripng_split_horizon(struct vty *vty, struct lyd_node *dnode, bool show_defaults) { int value; value = yang_dnode_get_enum(dnode, NULL); switch (value) { case RIPNG_NO_SPLIT_HORIZON: vty_out(vty, " no ipv6 ripng split-horizon\n"); break; case RIPNG_SPLIT_HORIZON: vty_out(vty, " ipv6 ripng split-horizon\n"); break; case RIPNG_SPLIT_HORIZON_POISONED_REVERSE: vty_out(vty, " ipv6 ripng split-horizon poisoned-reverse\n"); break; } } /* * XPath: /frr-ripngd:clear-ripng-route */ DEFPY (clear_ipv6_rip, clear_ipv6_rip_cmd, "clear ipv6 ripng [vrf WORD]", CLEAR_STR IPV6_STR "Clear IPv6 RIP database\n" VRF_CMD_HELP_STR) { struct list *input; input = list_new(); if (vrf) { struct yang_data *yang_vrf; yang_vrf = yang_data_new( "/frr-ripngd:clear-ripng-route/input/vrf", vrf); listnode_add(input, yang_vrf); } return nb_cli_rpc("/frr-ripngd:clear-ripng-route", input, NULL); } void ripng_cli_init(void) { install_element(CONFIG_NODE, &router_ripng_cmd); install_element(CONFIG_NODE, &no_router_ripng_cmd); install_element(RIPNG_NODE, &ripng_allow_ecmp_cmd); install_element(RIPNG_NODE, &ripng_default_information_originate_cmd); install_element(RIPNG_NODE, &ripng_default_metric_cmd); install_element(RIPNG_NODE, &no_ripng_default_metric_cmd); install_element(RIPNG_NODE, &ripng_network_prefix_cmd); install_element(RIPNG_NODE, &ripng_network_if_cmd); install_element(RIPNG_NODE, &ripng_offset_list_cmd); install_element(RIPNG_NODE, &ripng_passive_interface_cmd); install_element(RIPNG_NODE, &ripng_redistribute_cmd); install_element(RIPNG_NODE, &ripng_route_cmd); install_element(RIPNG_NODE, &ripng_aggregate_address_cmd); install_element(RIPNG_NODE, &ripng_timers_cmd); install_element(RIPNG_NODE, &no_ripng_timers_cmd); install_element(INTERFACE_NODE, &ipv6_ripng_split_horizon_cmd); install_element(ENABLE_NODE, &clear_ipv6_rip_cmd); } frr-7.2.1/ripngd/ripng_cli.h0000644000000000000000000000463213610377563012623 00000000000000/* * Copyright (C) 1998 Kunihiro Ishiguro * Copyright (C) 2018 NetDEF, Inc. * Renato Westphal * * This program is free software; you can redistribute it and/or modify it * under the terms of the 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; see the file COPYING; if not, write to the Free Software * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA */ #ifndef _FRR_RIPNG_CLI_H_ #define _FRR_RIPNG_CLI_H_ extern void cli_show_router_ripng(struct vty *vty, struct lyd_node *dnode, bool show_defaults); extern void cli_show_ripng_allow_ecmp(struct vty *vty, struct lyd_node *dnode, bool show_defaults); extern void cli_show_ripng_default_information_originate(struct vty *vty, struct lyd_node *dnode, bool show_defaults); extern void cli_show_ripng_default_metric(struct vty *vty, struct lyd_node *dnode, bool show_defaults); extern void cli_show_ripng_network_prefix(struct vty *vty, struct lyd_node *dnode, bool show_defaults); extern void cli_show_ripng_network_interface(struct vty *vty, struct lyd_node *dnode, bool show_defaults); extern void cli_show_ripng_offset_list(struct vty *vty, struct lyd_node *dnode, bool show_defaults); extern void cli_show_ripng_passive_interface(struct vty *vty, struct lyd_node *dnode, bool show_defaults); extern void cli_show_ripng_redistribute(struct vty *vty, struct lyd_node *dnode, bool show_defaults); extern void cli_show_ripng_route(struct vty *vty, struct lyd_node *dnode, bool show_defaults); extern void cli_show_ripng_aggregate_address(struct vty *vty, struct lyd_node *dnode, bool show_defaults); extern void cli_show_ripng_timers(struct vty *vty, struct lyd_node *dnode, bool show_defaults); extern void cli_show_ipv6_ripng_split_horizon(struct vty *vty, struct lyd_node *dnode, bool show_defaults); #endif /* _FRR_RIPNG_CLI_H_ */ frr-7.2.1/ripngd/ripng_debug.c0000644000000000000000000001451713610377563013140 00000000000000/* * RIPng debug output routines * Copyright (C) 1998 Kunihiro Ishiguro * * This file is part of GNU Zebra. * * GNU Zebra is free software; you can redistribute it and/or modify it * under the terms of the GNU General Public License as published by the * Free Software Foundation; either version 2, or (at your option) any * later version. * * GNU Zebra 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; see the file COPYING; if not, write to the Free Software * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA */ #include #include "command.h" #include "ripngd/ripng_debug.h" /* For debug statement. */ unsigned long ripng_debug_event = 0; unsigned long ripng_debug_packet = 0; unsigned long ripng_debug_zebra = 0; DEFUN_NOSH (show_debugging_ripng, show_debugging_ripng_cmd, "show debugging [ripng]", SHOW_STR DEBUG_STR "RIPng configuration\n") { vty_out(vty, "RIPng debugging status:\n"); if (IS_RIPNG_DEBUG_EVENT) vty_out(vty, " RIPng event debugging is on\n"); if (IS_RIPNG_DEBUG_PACKET) { if (IS_RIPNG_DEBUG_SEND && IS_RIPNG_DEBUG_RECV) { vty_out(vty, " RIPng packet debugging is on\n"); } else { if (IS_RIPNG_DEBUG_SEND) vty_out(vty, " RIPng packet send debugging is on\n"); else vty_out(vty, " RIPng packet receive debugging is on\n"); } } if (IS_RIPNG_DEBUG_ZEBRA) vty_out(vty, " RIPng zebra debugging is on\n"); return CMD_SUCCESS; } DEFUN (debug_ripng_events, debug_ripng_events_cmd, "debug ripng events", DEBUG_STR "RIPng configuration\n" "Debug option set for ripng events\n") { ripng_debug_event = RIPNG_DEBUG_EVENT; return CMD_SUCCESS; } DEFUN (debug_ripng_packet, debug_ripng_packet_cmd, "debug ripng packet", DEBUG_STR "RIPng configuration\n" "Debug option set for ripng packet\n") { ripng_debug_packet = RIPNG_DEBUG_PACKET; ripng_debug_packet |= RIPNG_DEBUG_SEND; ripng_debug_packet |= RIPNG_DEBUG_RECV; return CMD_SUCCESS; } DEFUN (debug_ripng_packet_direct, debug_ripng_packet_direct_cmd, "debug ripng packet ", DEBUG_STR "RIPng configuration\n" "Debug option set for ripng packet\n" "Debug option set for receive packet\n" "Debug option set for send packet\n") { int idx_recv_send = 3; ripng_debug_packet |= RIPNG_DEBUG_PACKET; if (strcmp("send", argv[idx_recv_send]->text) == 0) ripng_debug_packet |= RIPNG_DEBUG_SEND; if (strcmp("recv", argv[idx_recv_send]->text) == 0) ripng_debug_packet |= RIPNG_DEBUG_RECV; return CMD_SUCCESS; } DEFUN (debug_ripng_zebra, debug_ripng_zebra_cmd, "debug ripng zebra", DEBUG_STR "RIPng configuration\n" "Debug option set for ripng and zebra communication\n") { ripng_debug_zebra = RIPNG_DEBUG_ZEBRA; return CMD_SUCCESS; } DEFUN (no_debug_ripng_events, no_debug_ripng_events_cmd, "no debug ripng events", NO_STR DEBUG_STR "RIPng configuration\n" "Debug option set for ripng events\n") { ripng_debug_event = 0; return CMD_SUCCESS; } DEFUN (no_debug_ripng_packet, no_debug_ripng_packet_cmd, "no debug ripng packet", NO_STR DEBUG_STR "RIPng configuration\n" "Debug option set for ripng packet\n") { ripng_debug_packet = 0; return CMD_SUCCESS; } DEFUN (no_debug_ripng_packet_direct, no_debug_ripng_packet_direct_cmd, "no debug ripng packet ", NO_STR DEBUG_STR "RIPng configuration\n" "Debug option set for ripng packet\n" "Debug option set for receive packet\n" "Debug option set for send packet\n") { int idx_recv_send = 4; if (strcmp("send", argv[idx_recv_send]->text) == 0) { if (IS_RIPNG_DEBUG_RECV) ripng_debug_packet &= ~RIPNG_DEBUG_SEND; else ripng_debug_packet = 0; } else if (strcmp("recv", argv[idx_recv_send]->text) == 0) { if (IS_RIPNG_DEBUG_SEND) ripng_debug_packet &= ~RIPNG_DEBUG_RECV; else ripng_debug_packet = 0; } return CMD_SUCCESS; } DEFUN (no_debug_ripng_zebra, no_debug_ripng_zebra_cmd, "no debug ripng zebra", NO_STR DEBUG_STR "RIPng configuration\n" "Debug option set for ripng and zebra communication\n") { ripng_debug_zebra = 0; return CMD_SUCCESS; } /* Debug node. */ static struct cmd_node debug_node = { DEBUG_NODE, "", /* Debug node has no interface. */ 1 /* VTYSH */ }; static int config_write_debug(struct vty *vty) { int write = 0; if (IS_RIPNG_DEBUG_EVENT) { vty_out(vty, "debug ripng events\n"); write++; } if (IS_RIPNG_DEBUG_PACKET) { if (IS_RIPNG_DEBUG_SEND && IS_RIPNG_DEBUG_RECV) { vty_out(vty, "debug ripng packet\n"); write++; } else { if (IS_RIPNG_DEBUG_SEND) vty_out(vty, "debug ripng packet send\n"); else vty_out(vty, "debug ripng packet recv\n"); write++; } } if (IS_RIPNG_DEBUG_ZEBRA) { vty_out(vty, "debug ripng zebra\n"); write++; } return write; } void ripng_debug_init(void) { ripng_debug_event = 0; ripng_debug_packet = 0; ripng_debug_zebra = 0; install_node(&debug_node, config_write_debug); install_element(VIEW_NODE, &show_debugging_ripng_cmd); install_element(ENABLE_NODE, &debug_ripng_events_cmd); install_element(ENABLE_NODE, &debug_ripng_packet_cmd); install_element(ENABLE_NODE, &debug_ripng_packet_direct_cmd); install_element(ENABLE_NODE, &debug_ripng_zebra_cmd); install_element(ENABLE_NODE, &no_debug_ripng_events_cmd); install_element(ENABLE_NODE, &no_debug_ripng_packet_cmd); install_element(ENABLE_NODE, &no_debug_ripng_packet_direct_cmd); install_element(ENABLE_NODE, &no_debug_ripng_zebra_cmd); install_element(CONFIG_NODE, &debug_ripng_events_cmd); install_element(CONFIG_NODE, &debug_ripng_packet_cmd); install_element(CONFIG_NODE, &debug_ripng_packet_direct_cmd); install_element(CONFIG_NODE, &debug_ripng_zebra_cmd); install_element(CONFIG_NODE, &no_debug_ripng_events_cmd); install_element(CONFIG_NODE, &no_debug_ripng_packet_cmd); install_element(CONFIG_NODE, &no_debug_ripng_packet_direct_cmd); install_element(CONFIG_NODE, &no_debug_ripng_zebra_cmd); } frr-7.2.1/ripngd/ripng_debug.h0000644000000000000000000000317413610377563013142 00000000000000/* * RIPng debug output routines * Copyright (C) 1998, 1999 Kunihiro Ishiguro * * This file is part of GNU Zebra. * * GNU Zebra is free software; you can redistribute it and/or modify it * under the terms of the GNU General Public License as published by the * Free Software Foundation; either version 2, or (at your option) any * later version. * * GNU Zebra 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; see the file COPYING; if not, write to the Free Software * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA */ #ifndef _ZEBRA_RIPNG_DEBUG_H #define _ZEBRA_RIPNG_DEBUG_H /* Debug flags. */ #define RIPNG_DEBUG_EVENT 0x01 #define RIPNG_DEBUG_PACKET 0x01 #define RIPNG_DEBUG_SEND 0x20 #define RIPNG_DEBUG_RECV 0x40 #define RIPNG_DEBUG_ZEBRA 0x01 /* Debug related macro. */ #define IS_RIPNG_DEBUG_EVENT (ripng_debug_event & RIPNG_DEBUG_EVENT) #define IS_RIPNG_DEBUG_PACKET (ripng_debug_packet & RIPNG_DEBUG_PACKET) #define IS_RIPNG_DEBUG_SEND (ripng_debug_packet & RIPNG_DEBUG_SEND) #define IS_RIPNG_DEBUG_RECV (ripng_debug_packet & RIPNG_DEBUG_RECV) #define IS_RIPNG_DEBUG_ZEBRA (ripng_debug_zebra & RIPNG_DEBUG_ZEBRA) extern unsigned long ripng_debug_event; extern unsigned long ripng_debug_packet; extern unsigned long ripng_debug_zebra; extern void ripng_debug_init(void); #endif /* _ZEBRA_RIPNG_DEBUG_H */ frr-7.2.1/ripngd/ripng_interface.c0000644000000000000000000005524113610377563014011 00000000000000/* * Interface related function for RIPng. * Copyright (C) 1998 Kunihiro Ishiguro * * This file is part of GNU Zebra. * * GNU Zebra is free software; you can redistribute it and/or modify it * under the terms of the GNU General Public License as published by the * Free Software Foundation; either version 2, or (at your option) any * later version. * * GNU Zebra 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; see the file COPYING; if not, write to the Free Software * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA */ #include #include "linklist.h" #include "if.h" #include "prefix.h" #include "memory.h" #include "network.h" #include "filter.h" #include "log.h" #include "stream.h" #include "zclient.h" #include "command.h" #include "agg_table.h" #include "thread.h" #include "privs.h" #include "vrf.h" #include "lib_errors.h" #include "northbound_cli.h" #include "ripngd/ripngd.h" #include "ripngd/ripng_debug.h" /* If RFC2133 definition is used. */ #ifndef IPV6_JOIN_GROUP #define IPV6_JOIN_GROUP IPV6_ADD_MEMBERSHIP #endif #ifndef IPV6_LEAVE_GROUP #define IPV6_LEAVE_GROUP IPV6_DROP_MEMBERSHIP #endif DEFINE_MTYPE_STATIC(RIPNGD, RIPNG_IF, "ripng interface") /* Static utility function. */ static void ripng_enable_apply(struct interface *); static void ripng_passive_interface_apply(struct interface *); static int ripng_enable_if_lookup(struct ripng *ripng, const char *ifname); static int ripng_enable_network_lookup2(struct connected *); static void ripng_enable_apply_all(struct ripng *ripng); /* Join to the all rip routers multicast group. */ static int ripng_multicast_join(struct interface *ifp, int sock) { int ret; struct ipv6_mreq mreq; int save_errno; if (if_is_multicast(ifp)) { memset(&mreq, 0, sizeof(mreq)); inet_pton(AF_INET6, RIPNG_GROUP, &mreq.ipv6mr_multiaddr); mreq.ipv6mr_interface = ifp->ifindex; /* * NetBSD 1.6.2 requires root to join groups on gif(4). * While this is bogus, privs are available and easy to use * for this call as a workaround. */ frr_with_privs(&ripngd_privs) { ret = setsockopt(sock, IPPROTO_IPV6, IPV6_JOIN_GROUP, (char *)&mreq, sizeof(mreq)); save_errno = errno; } if (ret < 0 && save_errno == EADDRINUSE) { /* * Group is already joined. This occurs due to sloppy * group * management, in particular declining to leave the * group on * an interface that has just gone down. */ zlog_warn("ripng join on %s EADDRINUSE (ignoring)", ifp->name); return 0; /* not an error */ } if (ret < 0) zlog_warn("can't setsockopt IPV6_JOIN_GROUP: %s", safe_strerror(save_errno)); if (IS_RIPNG_DEBUG_EVENT) zlog_debug( "RIPng %s join to all-rip-routers multicast group", ifp->name); if (ret < 0) return -1; } return 0; } /* Leave from the all rip routers multicast group. */ static int ripng_multicast_leave(struct interface *ifp, int sock) { int ret; struct ipv6_mreq mreq; if (if_is_multicast(ifp)) { memset(&mreq, 0, sizeof(mreq)); inet_pton(AF_INET6, RIPNG_GROUP, &mreq.ipv6mr_multiaddr); mreq.ipv6mr_interface = ifp->ifindex; ret = setsockopt(sock, IPPROTO_IPV6, IPV6_LEAVE_GROUP, (char *)&mreq, sizeof(mreq)); if (ret < 0) zlog_warn("can't setsockopt IPV6_LEAVE_GROUP: %s", safe_strerror(errno)); if (IS_RIPNG_DEBUG_EVENT) zlog_debug( "RIPng %s leave from all-rip-routers multicast group", ifp->name); if (ret < 0) return -1; } return 0; } /* How many link local IPv6 address could be used on the interface ? */ static int ripng_if_ipv6_lladdress_check(struct interface *ifp) { struct listnode *nn; struct connected *connected; int count = 0; for (ALL_LIST_ELEMENTS_RO(ifp->connected, nn, connected)) { struct prefix *p; p = connected->address; if ((p->family == AF_INET6) && IN6_IS_ADDR_LINKLOCAL(&p->u.prefix6)) count++; } return count; } static int ripng_if_down(struct interface *ifp) { struct agg_node *rp; struct ripng_info *rinfo; struct ripng_interface *ri; struct ripng *ripng; struct list *list = NULL; struct listnode *listnode = NULL, *nextnode = NULL; ri = ifp->info; ripng = ri->ripng; if (ripng) for (rp = agg_route_top(ripng->table); rp; rp = agg_route_next(rp)) if ((list = rp->info) != NULL) for (ALL_LIST_ELEMENTS(list, listnode, nextnode, rinfo)) if (rinfo->ifindex == ifp->ifindex) ripng_ecmp_delete(ripng, rinfo); if (ri->running) { if (IS_RIPNG_DEBUG_EVENT) zlog_debug("turn off %s", ifp->name); /* Leave from multicast group. */ if (ripng) ripng_multicast_leave(ifp, ripng->sock); ri->running = 0; } return 0; } /* Inteface link up message processing. */ int ripng_interface_up(ZAPI_CALLBACK_ARGS) { struct stream *s; struct interface *ifp; /* zebra_interface_state_read() updates interface structure in iflist. */ s = zclient->ibuf; ifp = zebra_interface_state_read(s, vrf_id); if (ifp == NULL) return 0; if (IS_RIPNG_DEBUG_ZEBRA) zlog_debug( "interface up %s vrf %u index %d flags %llx metric %d mtu %d", ifp->name, ifp->vrf_id, ifp->ifindex, (unsigned long long)ifp->flags, ifp->metric, ifp->mtu6); ripng_interface_sync(ifp); /* Check if this interface is RIPng enabled or not. */ ripng_enable_apply(ifp); /* Check for a passive interface. */ ripng_passive_interface_apply(ifp); /* Apply distribute list to the all interface. */ ripng_distribute_update_interface(ifp); return 0; } /* Inteface link down message processing. */ int ripng_interface_down(ZAPI_CALLBACK_ARGS) { struct stream *s; struct interface *ifp; /* zebra_interface_state_read() updates interface structure in iflist. */ s = zclient->ibuf; ifp = zebra_interface_state_read(s, vrf_id); if (ifp == NULL) return 0; ripng_interface_sync(ifp); ripng_if_down(ifp); if (IS_RIPNG_DEBUG_ZEBRA) zlog_debug( "interface down %s vrf %u index %d flags %#llx metric %d mtu %d", ifp->name, ifp->vrf_id, ifp->ifindex, (unsigned long long)ifp->flags, ifp->metric, ifp->mtu6); return 0; } /* Inteface addition message from zebra. */ int ripng_interface_add(ZAPI_CALLBACK_ARGS) { struct interface *ifp; ifp = zebra_interface_add_read(zclient->ibuf, vrf_id); ripng_interface_sync(ifp); if (IS_RIPNG_DEBUG_ZEBRA) zlog_debug( "RIPng interface add %s vrf %u index %d flags %#llx metric %d mtu %d", ifp->name, ifp->vrf_id, ifp->ifindex, (unsigned long long)ifp->flags, ifp->metric, ifp->mtu6); /* Check is this interface is RIP enabled or not.*/ ripng_enable_apply(ifp); /* Apply distribute list to the interface. */ ripng_distribute_update_interface(ifp); /* Check interface routemap. */ ripng_if_rmap_update_interface(ifp); return 0; } int ripng_interface_delete(ZAPI_CALLBACK_ARGS) { struct interface *ifp; struct stream *s; s = zclient->ibuf; /* zebra_interface_state_read() updates interface structure in iflist */ ifp = zebra_interface_state_read(s, vrf_id); if (ifp == NULL) return 0; ripng_interface_sync(ifp); if (if_is_up(ifp)) { ripng_if_down(ifp); } zlog_info( "interface delete %s vrf %u index %d flags %#llx metric %d mtu %d", ifp->name, ifp->vrf_id, ifp->ifindex, (unsigned long long)ifp->flags, ifp->metric, ifp->mtu6); /* To support pseudo interface do not free interface structure. */ /* if_delete(ifp); */ if_set_index(ifp, IFINDEX_INTERNAL); return 0; } /* VRF update for an interface. */ int ripng_interface_vrf_update(ZAPI_CALLBACK_ARGS) { struct interface *ifp; vrf_id_t new_vrf_id; ifp = zebra_interface_vrf_update_read(zclient->ibuf, vrf_id, &new_vrf_id); if (!ifp) return 0; if (IS_RIPNG_DEBUG_ZEBRA) zlog_debug("interface %s VRF change vrf_id %u new vrf id %u", ifp->name, vrf_id, new_vrf_id); if_update_to_new_vrf(ifp, new_vrf_id); ripng_interface_sync(ifp); return 0; } void ripng_interface_clean(struct ripng *ripng) { struct interface *ifp; struct ripng_interface *ri; FOR_ALL_INTERFACES (ripng->vrf, ifp) { ri = ifp->info; ri->enable_network = 0; ri->enable_interface = 0; ri->running = 0; if (ri->t_wakeup) { thread_cancel(ri->t_wakeup); ri->t_wakeup = NULL; } } } static void ripng_apply_address_add(struct connected *ifc) { struct ripng_interface *ri = ifc->ifp->info; struct ripng *ripng = ri->ripng; struct prefix_ipv6 address; struct prefix *p; if (!ripng) return; if (!if_is_up(ifc->ifp)) return; p = ifc->address; memset(&address, 0, sizeof(address)); address.family = p->family; address.prefix = p->u.prefix6; address.prefixlen = p->prefixlen; apply_mask_ipv6(&address); /* Check if this interface is RIP enabled or not or Check if this address's prefix is RIP enabled */ if ((ripng_enable_if_lookup(ripng, ifc->ifp->name) >= 0) || (ripng_enable_network_lookup2(ifc) >= 0)) ripng_redistribute_add(ripng, ZEBRA_ROUTE_CONNECT, RIPNG_ROUTE_INTERFACE, &address, ifc->ifp->ifindex, NULL, 0); } int ripng_interface_address_add(ZAPI_CALLBACK_ARGS) { struct connected *c; struct prefix *p; c = zebra_interface_address_read(ZEBRA_INTERFACE_ADDRESS_ADD, zclient->ibuf, vrf_id); if (c == NULL) return 0; p = c->address; if (p->family == AF_INET6) { struct ripng_interface *ri = c->ifp->info; if (IS_RIPNG_DEBUG_ZEBRA) zlog_debug("RIPng connected address %s/%d add", inet6_ntoa(p->u.prefix6), p->prefixlen); /* Check is this prefix needs to be redistributed. */ ripng_apply_address_add(c); /* Let's try once again whether the interface could be activated */ if (!ri->running) { /* Check if this interface is RIP enabled or not.*/ ripng_enable_apply(c->ifp); /* Apply distribute list to the interface. */ ripng_distribute_update_interface(c->ifp); /* Check interface routemap. */ ripng_if_rmap_update_interface(c->ifp); } } return 0; } static void ripng_apply_address_del(struct connected *ifc) { struct ripng_interface *ri = ifc->ifp->info; struct ripng *ripng = ri->ripng; struct prefix_ipv6 address; struct prefix *p; if (!ripng) return; if (!if_is_up(ifc->ifp)) return; p = ifc->address; memset(&address, 0, sizeof(address)); address.family = p->family; address.prefix = p->u.prefix6; address.prefixlen = p->prefixlen; apply_mask_ipv6(&address); ripng_redistribute_delete(ripng, ZEBRA_ROUTE_CONNECT, RIPNG_ROUTE_INTERFACE, &address, ifc->ifp->ifindex); } int ripng_interface_address_delete(ZAPI_CALLBACK_ARGS) { struct connected *ifc; struct prefix *p; char buf[INET6_ADDRSTRLEN]; ifc = zebra_interface_address_read(ZEBRA_INTERFACE_ADDRESS_DELETE, zclient->ibuf, vrf_id); if (ifc) { p = ifc->address; if (p->family == AF_INET6) { if (IS_RIPNG_DEBUG_ZEBRA) zlog_debug( "RIPng connected address %s/%d delete", inet_ntop(AF_INET6, &p->u.prefix6, buf, INET6_ADDRSTRLEN), p->prefixlen); /* Check wether this prefix needs to be removed. */ ripng_apply_address_del(ifc); } connected_free(ifc); } return 0; } /* Lookup RIPng enable network. */ /* Check wether the interface has at least a connected prefix that * is within the ripng->enable_network table. */ static int ripng_enable_network_lookup_if(struct interface *ifp) { struct ripng_interface *ri = ifp->info; struct ripng *ripng = ri->ripng; struct listnode *node; struct connected *connected; struct prefix_ipv6 address; if (!ripng) return -1; for (ALL_LIST_ELEMENTS_RO(ifp->connected, node, connected)) { struct prefix *p; struct agg_node *n; p = connected->address; if (p->family == AF_INET6) { address.family = AF_INET6; address.prefix = p->u.prefix6; address.prefixlen = IPV6_MAX_BITLEN; n = agg_node_match(ripng->enable_network, (struct prefix *)&address); if (n) { agg_unlock_node(n); return 1; } } } return -1; } /* Check wether connected is within the ripng->enable_network table. */ static int ripng_enable_network_lookup2(struct connected *connected) { struct ripng_interface *ri = connected->ifp->info; struct ripng *ripng = ri->ripng; struct prefix_ipv6 address; struct prefix *p; if (!ripng) return -1; p = connected->address; if (p->family == AF_INET6) { struct agg_node *node; address.family = p->family; address.prefix = p->u.prefix6; address.prefixlen = IPV6_MAX_BITLEN; /* LPM on p->family, p->u.prefix6/IPV6_MAX_BITLEN within * ripng->enable_network */ node = agg_node_match(ripng->enable_network, (struct prefix *)&address); if (node) { agg_unlock_node(node); return 1; } } return -1; } /* Add RIPng enable network. */ int ripng_enable_network_add(struct ripng *ripng, struct prefix *p) { struct agg_node *node; node = agg_node_get(ripng->enable_network, p); if (node->info) { agg_unlock_node(node); return NB_ERR_INCONSISTENCY; } else node->info = (void *)1; /* XXX: One should find a better solution than a generic one */ ripng_enable_apply_all(ripng); return NB_OK; } /* Delete RIPng enable network. */ int ripng_enable_network_delete(struct ripng *ripng, struct prefix *p) { struct agg_node *node; node = agg_node_lookup(ripng->enable_network, p); if (node) { node->info = NULL; /* Unlock info lock. */ agg_unlock_node(node); /* Unlock lookup lock. */ agg_unlock_node(node); return NB_OK; } return NB_ERR_INCONSISTENCY; } /* Lookup function. */ static int ripng_enable_if_lookup(struct ripng *ripng, const char *ifname) { unsigned int i; char *str; if (!ripng) return -1; for (i = 0; i < vector_active(ripng->enable_if); i++) if ((str = vector_slot(ripng->enable_if, i)) != NULL) if (strcmp(str, ifname) == 0) return i; return -1; } int ripng_enable_if_add(struct ripng *ripng, const char *ifname) { int ret; ret = ripng_enable_if_lookup(ripng, ifname); if (ret >= 0) return NB_ERR_INCONSISTENCY; vector_set(ripng->enable_if, strdup(ifname)); ripng_enable_apply_all(ripng); return NB_OK; } int ripng_enable_if_delete(struct ripng *ripng, const char *ifname) { int index; char *str; index = ripng_enable_if_lookup(ripng, ifname); if (index < 0) return NB_ERR_INCONSISTENCY; str = vector_slot(ripng->enable_if, index); free(str); vector_unset(ripng->enable_if, index); ripng_enable_apply_all(ripng); return NB_OK; } /* Wake up interface. */ static int ripng_interface_wakeup(struct thread *t) { struct interface *ifp; struct ripng_interface *ri; /* Get interface. */ ifp = THREAD_ARG(t); ri = ifp->info; ri->t_wakeup = NULL; /* Join to multicast group. */ if (ripng_multicast_join(ifp, ri->ripng->sock) < 0) { flog_err_sys(EC_LIB_SOCKET, "multicast join failed, interface %s not running", ifp->name); return 0; } /* Set running flag. */ ri->running = 1; /* Send RIP request to the interface. */ ripng_request(ifp); return 0; } static void ripng_connect_set(struct interface *ifp, int set) { struct ripng_interface *ri = ifp->info; struct ripng *ripng = ri->ripng; struct listnode *node, *nnode; struct connected *connected; struct prefix_ipv6 address; for (ALL_LIST_ELEMENTS(ifp->connected, node, nnode, connected)) { struct prefix *p; p = connected->address; if (p->family != AF_INET6) continue; address.family = AF_INET6; address.prefix = p->u.prefix6; address.prefixlen = p->prefixlen; apply_mask_ipv6(&address); if (set) { /* Check once more wether this prefix is within a * "network IF_OR_PREF" one */ if ((ripng_enable_if_lookup(ripng, connected->ifp->name) >= 0) || (ripng_enable_network_lookup2(connected) >= 0)) ripng_redistribute_add( ripng, ZEBRA_ROUTE_CONNECT, RIPNG_ROUTE_INTERFACE, &address, connected->ifp->ifindex, NULL, 0); } else { ripng_redistribute_delete(ripng, ZEBRA_ROUTE_CONNECT, RIPNG_ROUTE_INTERFACE, &address, connected->ifp->ifindex); if (ripng_redistribute_check(ripng, ZEBRA_ROUTE_CONNECT)) ripng_redistribute_add( ripng, ZEBRA_ROUTE_CONNECT, RIPNG_ROUTE_REDISTRIBUTE, &address, connected->ifp->ifindex, NULL, 0); } } } /* Check RIPng is enabed on this interface. */ void ripng_enable_apply(struct interface *ifp) { int ret; struct ripng_interface *ri = NULL; /* Check interface. */ if (!if_is_up(ifp)) return; ri = ifp->info; /* Is this interface a candidate for RIPng ? */ ret = ripng_enable_network_lookup_if(ifp); /* If the interface is matched. */ if (ret > 0) ri->enable_network = 1; else ri->enable_network = 0; /* Check interface name configuration. */ ret = ripng_enable_if_lookup(ri->ripng, ifp->name); if (ret >= 0) ri->enable_interface = 1; else ri->enable_interface = 0; /* any candidate interface MUST have a link-local IPv6 address */ if ((!ripng_if_ipv6_lladdress_check(ifp)) && (ri->enable_network || ri->enable_interface)) { ri->enable_network = 0; ri->enable_interface = 0; zlog_warn("Interface %s does not have any link-local address", ifp->name); } /* Update running status of the interface. */ if (ri->enable_network || ri->enable_interface) { zlog_info("RIPng INTERFACE ON %s", ifp->name); /* Add interface wake up thread. */ thread_add_timer(master, ripng_interface_wakeup, ifp, 1, &ri->t_wakeup); ripng_connect_set(ifp, 1); } else { if (ri->running) { /* Might as well clean up the route table as well * ripng_if_down sets to 0 ri->running, and displays *"turn off %s" **/ ripng_if_down(ifp); ripng_connect_set(ifp, 0); } } } /* Set distribute list to all interfaces. */ static void ripng_enable_apply_all(struct ripng *ripng) { struct interface *ifp; FOR_ALL_INTERFACES (ripng->vrf, ifp) ripng_enable_apply(ifp); } /* Clear all network and neighbor configuration */ void ripng_clean_network(struct ripng *ripng) { unsigned int i; char *str; struct agg_node *rn; /* ripng->enable_network */ for (rn = agg_route_top(ripng->enable_network); rn; rn = agg_route_next(rn)) if (rn->info) { rn->info = NULL; agg_unlock_node(rn); } /* ripng->enable_if */ for (i = 0; i < vector_active(ripng->enable_if); i++) if ((str = vector_slot(ripng->enable_if, i)) != NULL) { free(str); vector_slot(ripng->enable_if, i) = NULL; } } /* Utility function for looking up passive interface settings. */ static int ripng_passive_interface_lookup(struct ripng *ripng, const char *ifname) { unsigned int i; char *str; for (i = 0; i < vector_active(ripng->passive_interface); i++) if ((str = vector_slot(ripng->passive_interface, i)) != NULL) if (strcmp(str, ifname) == 0) return i; return -1; } void ripng_passive_interface_apply(struct interface *ifp) { int ret; struct ripng_interface *ri; struct ripng *ripng; ri = ifp->info; ripng = ri->ripng; if (!ripng) return; ret = ripng_passive_interface_lookup(ripng, ifp->name); if (ret < 0) ri->passive = 0; else ri->passive = 1; } static void ripng_passive_interface_apply_all(struct ripng *ripng) { struct interface *ifp; FOR_ALL_INTERFACES (ripng->vrf, ifp) ripng_passive_interface_apply(ifp); } /* Passive interface. */ int ripng_passive_interface_set(struct ripng *ripng, const char *ifname) { if (ripng_passive_interface_lookup(ripng, ifname) >= 0) return NB_ERR_INCONSISTENCY; vector_set(ripng->passive_interface, strdup(ifname)); ripng_passive_interface_apply_all(ripng); return NB_OK; } int ripng_passive_interface_unset(struct ripng *ripng, const char *ifname) { int i; char *str; i = ripng_passive_interface_lookup(ripng, ifname); if (i < 0) return NB_ERR_INCONSISTENCY; str = vector_slot(ripng->passive_interface, i); free(str); vector_unset(ripng->passive_interface, i); ripng_passive_interface_apply_all(ripng); return NB_OK; } /* Free all configured RIP passive-interface settings. */ void ripng_passive_interface_clean(struct ripng *ripng) { unsigned int i; char *str; for (i = 0; i < vector_active(ripng->passive_interface); i++) if ((str = vector_slot(ripng->passive_interface, i)) != NULL) { free(str); vector_slot(ripng->passive_interface, i) = NULL; } ripng_passive_interface_apply_all(ripng); } /* Write RIPng enable network and interface to the vty. */ int ripng_network_write(struct vty *vty, struct ripng *ripng) { unsigned int i; const char *ifname; struct agg_node *node; char buf[BUFSIZ]; /* Write enable network. */ for (node = agg_route_top(ripng->enable_network); node; node = agg_route_next(node)) if (node->info) { struct prefix *p = &node->p; vty_out(vty, " %s/%d\n", inet_ntop(p->family, &p->u.prefix, buf, BUFSIZ), p->prefixlen); } /* Write enable interface. */ for (i = 0; i < vector_active(ripng->enable_if); i++) if ((ifname = vector_slot(ripng->enable_if, i)) != NULL) vty_out(vty, " %s\n", ifname); return 0; } static struct ripng_interface *ri_new(void) { struct ripng_interface *ri; ri = XCALLOC(MTYPE_RIPNG_IF, sizeof(struct ripng_interface)); /* Set default split-horizon behavior. If the interface is Frame Relay or SMDS is enabled, the default value for split-horizon is off. But currently Zebra does detect Frame Relay or SMDS interface. So all interface is set to split horizon. */ ri->split_horizon = yang_get_default_enum("%s/split-horizon", RIPNG_IFACE); return ri; } void ripng_interface_sync(struct interface *ifp) { struct vrf *vrf; vrf = vrf_lookup_by_id(ifp->vrf_id); if (vrf) { struct ripng_interface *ri; ri = ifp->info; if (ri) ri->ripng = vrf->info; } } static int ripng_if_new_hook(struct interface *ifp) { ifp->info = ri_new(); ripng_interface_sync(ifp); return 0; } /* Called when interface structure deleted. */ static int ripng_if_delete_hook(struct interface *ifp) { XFREE(MTYPE_RIPNG_IF, ifp->info); ifp->info = NULL; return 0; } /* Configuration write function for ripngd. */ static int interface_config_write(struct vty *vty) { struct vrf *vrf; int write = 0; RB_FOREACH (vrf, vrf_name_head, &vrfs_by_name) { struct interface *ifp; FOR_ALL_INTERFACES (vrf, ifp) { struct lyd_node *dnode; dnode = yang_dnode_get( running_config->dnode, "/frr-interface:lib/interface[name='%s'][vrf='%s']", ifp->name, vrf->name); if (dnode == NULL) continue; write = 1; nb_cli_show_dnode_cmds(vty, dnode, false); } } return write; } /* ripngd's interface node. */ static struct cmd_node interface_node = { INTERFACE_NODE, "%s(config-if)# ", 1 /* VTYSH */ }; /* Initialization of interface. */ void ripng_if_init(void) { /* Interface initialize. */ hook_register_prio(if_add, 0, ripng_if_new_hook); hook_register_prio(if_del, 0, ripng_if_delete_hook); /* Install interface node. */ install_node(&interface_node, interface_config_write); if_cmd_init(); } frr-7.2.1/ripngd/ripng_main.c0000644000000000000000000000676613610377563013005 00000000000000/* * RIPngd main routine. * Copyright (C) 1998, 1999 Kunihiro Ishiguro * * This file is part of GNU Zebra. * * GNU Zebra is free software; you can redistribute it and/or modify it * under the terms of the GNU General Public License as published by the * Free Software Foundation; either version 2, or (at your option) any * later version. * * GNU Zebra 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; see the file COPYING; if not, write to the Free Software * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA */ #include #include #include "getopt.h" #include "vector.h" #include "vty.h" #include "command.h" #include "memory.h" #include "memory_vty.h" #include "thread.h" #include "log.h" #include "prefix.h" #include "if.h" #include "privs.h" #include "sigevent.h" #include "vrf.h" #include "if_rmap.h" #include "libfrr.h" #include "ripngd/ripngd.h" /* RIPngd options. */ struct option longopts[] = {{0}}; /* ripngd privileges */ zebra_capabilities_t _caps_p[] = {ZCAP_NET_RAW, ZCAP_BIND, ZCAP_SYS_ADMIN}; struct zebra_privs_t ripngd_privs = { #if defined(FRR_USER) .user = FRR_USER, #endif #if defined FRR_GROUP .group = FRR_GROUP, #endif #ifdef VTY_GROUP .vty_group = VTY_GROUP, #endif .caps_p = _caps_p, .cap_num_p = array_size(_caps_p), .cap_num_i = 0}; /* Master of threads. */ struct thread_master *master; static struct frr_daemon_info ripngd_di; /* SIGHUP handler. */ static void sighup(void) { zlog_info("SIGHUP received"); /* Reload config file. */ vty_read_config(NULL, ripngd_di.config_file, config_default); } /* SIGINT handler. */ static void sigint(void) { zlog_notice("Terminating on signal"); ripng_vrf_terminate(); if_rmap_terminate(); ripng_zebra_stop(); frr_fini(); exit(0); } /* SIGUSR1 handler. */ static void sigusr1(void) { zlog_rotate(); } struct quagga_signal_t ripng_signals[] = { { .signal = SIGHUP, .handler = &sighup, }, { .signal = SIGUSR1, .handler = &sigusr1, }, { .signal = SIGINT, .handler = &sigint, }, { .signal = SIGTERM, .handler = &sigint, }, }; static const struct frr_yang_module_info *ripngd_yang_modules[] = { &frr_interface_info, &frr_ripngd_info, }; FRR_DAEMON_INFO(ripngd, RIPNG, .vty_port = RIPNG_VTY_PORT, .proghelp = "Implementation of the RIPng routing protocol.", .signals = ripng_signals, .n_signals = array_size(ripng_signals), .privs = &ripngd_privs, .yang_modules = ripngd_yang_modules, .n_yang_modules = array_size(ripngd_yang_modules), ) #define DEPRECATED_OPTIONS "" /* RIPngd main routine. */ int main(int argc, char **argv) { frr_preinit(&ripngd_di, argc, argv); frr_opt_add("" DEPRECATED_OPTIONS, longopts, ""); while (1) { int opt; opt = frr_getopt(argc, argv, NULL); if (opt && opt < 128 && strchr(DEPRECATED_OPTIONS, opt)) { fprintf(stderr, "The -%c option no longer exists.\nPlease refer to the manual.\n", opt); continue; } if (opt == EOF) break; switch (opt) { case 0: break; default: frr_help_exit(1); break; } } master = frr_init(); /* Library inits. */ ripng_vrf_init(); /* RIPngd inits. */ ripng_init(); ripng_cli_init(); zebra_init(master); frr_config_fork(); frr_run(master); /* Not reached. */ return 0; } frr-7.2.1/ripngd/ripng_nexthop.c0000644000000000000000000001357313610377563013540 00000000000000/* RIPngd Zebra * Copyright (C) 2002 6WIND * * This file is part of GNU Zebra. * * GNU Zebra is free software; you can redistribute it and/or modify it * under the terms of the GNU General Public License as published by the * Free Software Foundation; either version 2, or (at your option) any * later version. * * GNU Zebra 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; see the file COPYING; if not, write to the Free Software * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA */ /* This file is required in order to support properly the RIPng nexthop * feature. */ #include /* For struct udphdr. */ #include #include "linklist.h" #include "stream.h" #include "log.h" #include "memory.h" #include "vty.h" #include "if.h" #include "prefix.h" #include "ripngd/ripngd.h" #include "ripngd/ripng_debug.h" #include "ripngd/ripng_nexthop.h" DEFINE_MTYPE_STATIC(RIPNGD, RIPNG_RTE_DATA, "RIPng rte data") #define DEBUG 1 #define min(a, b) ((a) < (b) ? (a) : (b)) struct ripng_rte_data { struct prefix_ipv6 *p; struct ripng_info *rinfo; struct ripng_aggregate *aggregate; }; void _ripng_rte_del(struct ripng_rte_data *A); int _ripng_rte_cmp(struct ripng_rte_data *A, struct ripng_rte_data *B); #define METRIC_OUT(a) \ ((a)->rinfo ? (a)->rinfo->metric_out : (a)->aggregate->metric_out) #define NEXTHOP_OUT_PTR(a) \ ((a)->rinfo ? &((a)->rinfo->nexthop_out) \ : &((a)->aggregate->nexthop_out)) #define TAG_OUT(a) ((a)->rinfo ? (a)->rinfo->tag_out : (a)->aggregate->tag_out) struct list *ripng_rte_new(void) { struct list *rte; rte = list_new(); rte->cmp = (int (*)(void *, void *))_ripng_rte_cmp; rte->del = (void (*)(void *))_ripng_rte_del; return rte; } void ripng_rte_free(struct list *ripng_rte_list) { list_delete(&ripng_rte_list); } /* Delete RTE */ void _ripng_rte_del(struct ripng_rte_data *A) { XFREE(MTYPE_RIPNG_RTE_DATA, A); } /* Compare RTE: * return + if A > B * 0 if A = B * - if A < B */ int _ripng_rte_cmp(struct ripng_rte_data *A, struct ripng_rte_data *B) { return addr6_cmp(NEXTHOP_OUT_PTR(A), NEXTHOP_OUT_PTR(B)); } /* Add routing table entry */ void ripng_rte_add(struct list *ripng_rte_list, struct prefix_ipv6 *p, struct ripng_info *rinfo, struct ripng_aggregate *aggregate) { struct ripng_rte_data *data; /* At least one should not be null */ assert(!rinfo || !aggregate); data = XMALLOC(MTYPE_RIPNG_RTE_DATA, sizeof(*data)); data->p = p; data->rinfo = rinfo; data->aggregate = aggregate; listnode_add_sort(ripng_rte_list, data); } /* Send the RTE with the nexthop support */ void ripng_rte_send(struct list *ripng_rte_list, struct interface *ifp, struct sockaddr_in6 *to) { struct ripng_interface *ri = ifp->info; struct ripng *ripng = ri->ripng; struct ripng_rte_data *data; struct listnode *node, *nnode; struct in6_addr last_nexthop; struct in6_addr myself_nexthop; struct stream *s; int num; int mtu; int rtemax; int ret; /* Most of the time, there is no nexthop */ memset(&last_nexthop, 0, sizeof(last_nexthop)); /* Use myself_nexthop if the nexthop is not a link-local address, * because * we remain a right path without beeing the optimal one. */ memset(&myself_nexthop, 0, sizeof(myself_nexthop)); /* Output stream get from ripng structre. XXX this should be interface structure. */ s = ripng->obuf; /* Reset stream and RTE counter. */ stream_reset(s); num = 0; mtu = ifp->mtu6; if (mtu < 0) mtu = IFMINMTU; rtemax = (min(mtu, RIPNG_MAX_PACKET_SIZE) - IPV6_HDRLEN - sizeof(struct udphdr) - sizeof(struct ripng_packet) + sizeof(struct rte)) / sizeof(struct rte); for (ALL_LIST_ELEMENTS(ripng_rte_list, node, nnode, data)) { /* (2.1) Next hop support */ if (!IPV6_ADDR_SAME(&last_nexthop, NEXTHOP_OUT_PTR(data))) { /* A nexthop entry should be at least followed by 1 RTE */ if (num == (rtemax - 1)) { ret = ripng_send_packet((caddr_t)STREAM_DATA(s), stream_get_endp(s), to, ifp); if (ret >= 0 && IS_RIPNG_DEBUG_SEND) ripng_packet_dump( (struct ripng_packet *) STREAM_DATA(s), stream_get_endp(s), "SEND"); num = 0; stream_reset(s); } /* Add the nexthop (2.1) */ /* If the received next hop address is not a link-local * address, * it should be treated as 0:0:0:0:0:0:0:0. */ if (!IN6_IS_ADDR_LINKLOCAL(NEXTHOP_OUT_PTR(data))) last_nexthop = myself_nexthop; else last_nexthop = *NEXTHOP_OUT_PTR(data); num = ripng_write_rte(num, s, NULL, &last_nexthop, 0, RIPNG_METRIC_NEXTHOP); } else { /* Rewrite the nexthop for each new packet */ if ((num == 0) && !IPV6_ADDR_SAME(&last_nexthop, &myself_nexthop)) num = ripng_write_rte(num, s, NULL, &last_nexthop, 0, RIPNG_METRIC_NEXTHOP); } num = ripng_write_rte(num, s, data->p, NULL, TAG_OUT(data), METRIC_OUT(data)); if (num == rtemax) { ret = ripng_send_packet((caddr_t)STREAM_DATA(s), stream_get_endp(s), to, ifp); if (ret >= 0 && IS_RIPNG_DEBUG_SEND) ripng_packet_dump( (struct ripng_packet *)STREAM_DATA(s), stream_get_endp(s), "SEND"); num = 0; stream_reset(s); } } /* If unwritten RTE exist, flush it. */ if (num != 0) { ret = ripng_send_packet((caddr_t)STREAM_DATA(s), stream_get_endp(s), to, ifp); if (ret >= 0 && IS_RIPNG_DEBUG_SEND) ripng_packet_dump((struct ripng_packet *)STREAM_DATA(s), stream_get_endp(s), "SEND"); stream_reset(s); } } frr-7.2.1/ripngd/ripng_nexthop.h0000644000000000000000000000366213610377563013543 00000000000000/* RIPng nexthop support * Copyright (C) 6WIND Vincent Jardin * * This file is part of GNU Zebra. * * GNU Zebra is free software; you can redistribute it and/or modify it * under the terms of the GNU General Public License as published by the * Free Software Foundation; either version 2, or (at your option) any * later version. * * GNU Zebra 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; see the file COPYING; if not, write to the Free Software * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA */ #ifndef _ZEBRA_RIPNG_RIPNG_NEXTHOP_H #define _ZEBRA_RIPNG_RIPNG_NEXTHOP_H #include #include "linklist.h" #include "ripngd/ripng_route.h" #include "ripngd/ripngd.h" extern struct list *ripng_rte_new(void); extern void ripng_rte_free(struct list *ripng_rte_list); extern void ripng_rte_add(struct list *ripng_rte_list, struct prefix_ipv6 *p, struct ripng_info *rinfo, struct ripng_aggregate *aggregate); extern void ripng_rte_send(struct list *ripng_rte_list, struct interface *ifp, struct sockaddr_in6 *to); /*** * 1 if A > B * 0 if A = B * -1 if A < B **/ static inline int addr6_cmp(struct in6_addr *A, struct in6_addr *B) { #define a(i) A->s6_addr32[i] #define b(i) B->s6_addr32[i] if (a(3) > b(3)) return 1; else if ((a(3) == b(3)) && (a(2) > b(2))) return 1; else if ((a(3) == b(3)) && (a(2) == b(2)) && (a(1) > b(1))) return 1; else if ((a(3) == b(3)) && (a(2) == b(2)) && (a(1) == b(1)) && (a(0) > b(0))) return 1; if ((a(3) == b(3)) && (a(2) == b(2)) && (a(1) == b(1)) && (a(0) == b(0))) return 0; return -1; } #endif /* _ZEBRA_RIPNG_RIPNG_NEXTHOP_H */ frr-7.2.1/ripngd/ripng_northbound.c0000644000000000000000000007264113610377563014236 00000000000000/* * Copyright (C) 1998 Kunihiro Ishiguro * Copyright (C) 2018 NetDEF, Inc. * Renato Westphal * * This program is free software; you can redistribute it and/or modify it * under the terms of the 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; see the file COPYING; if not, write to the Free Software * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA */ #include #include "if.h" #include "vrf.h" #include "log.h" #include "prefix.h" #include "table.h" #include "command.h" #include "routemap.h" #include "agg_table.h" #include "northbound.h" #include "libfrr.h" #include "ripngd/ripngd.h" #include "ripngd/ripng_debug.h" #include "ripngd/ripng_route.h" #include "ripngd/ripng_cli.h" /* * XPath: /frr-ripngd:ripngd/instance */ static int ripngd_instance_create(enum nb_event event, const struct lyd_node *dnode, union nb_resource *resource) { struct ripng *ripng; struct vrf *vrf; const char *vrf_name; int socket; vrf_name = yang_dnode_get_string(dnode, "./vrf"); vrf = vrf_lookup_by_name(vrf_name); /* * Try to create a RIPng socket only if the VRF is enabled, otherwise * create a disabled RIPng instance and wait for the VRF to be enabled. */ switch (event) { case NB_EV_VALIDATE: break; case NB_EV_PREPARE: if (!vrf || !vrf_is_enabled(vrf)) break; socket = ripng_make_socket(vrf); if (socket < 0) return NB_ERR_RESOURCE; resource->fd = socket; break; case NB_EV_ABORT: if (!vrf || !vrf_is_enabled(vrf)) break; socket = resource->fd; close(socket); break; case NB_EV_APPLY: if (vrf && vrf_is_enabled(vrf)) socket = resource->fd; else socket = -1; ripng = ripng_create(vrf_name, vrf, socket); nb_running_set_entry(dnode, ripng); break; } return NB_OK; } static int ripngd_instance_destroy(enum nb_event event, const struct lyd_node *dnode) { struct ripng *ripng; if (event != NB_EV_APPLY) return NB_OK; ripng = nb_running_unset_entry(dnode); ripng_clean(ripng); return NB_OK; } static const void *ripngd_instance_get_next(const void *parent_list_entry, const void *list_entry) { struct ripng *ripng = (struct ripng *)list_entry; if (list_entry == NULL) ripng = RB_MIN(ripng_instance_head, &ripng_instances); else ripng = RB_NEXT(ripng_instance_head, ripng); return ripng; } static int ripngd_instance_get_keys(const void *list_entry, struct yang_list_keys *keys) { const struct ripng *ripng = list_entry; keys->num = 1; strlcpy(keys->key[0], ripng->vrf_name, sizeof(keys->key[0])); return NB_OK; } static const void * ripngd_instance_lookup_entry(const void *parent_list_entry, const struct yang_list_keys *keys) { const char *vrf_name = keys->key[0]; return ripng_lookup_by_vrf_name(vrf_name); } /* * XPath: /frr-ripngd:ripngd/instance/allow-ecmp */ static int ripngd_instance_allow_ecmp_modify(enum nb_event event, const struct lyd_node *dnode, union nb_resource *resource) { struct ripng *ripng; if (event != NB_EV_APPLY) return NB_OK; ripng = nb_running_get_entry(dnode, NULL, true); ripng->ecmp = yang_dnode_get_bool(dnode, NULL); if (!ripng->ecmp) ripng_ecmp_disable(ripng); return NB_OK; } /* * XPath: /frr-ripngd:ripngd/instance/default-information-originate */ static int ripngd_instance_default_information_originate_modify( enum nb_event event, const struct lyd_node *dnode, union nb_resource *resource) { struct ripng *ripng; bool default_information; struct prefix_ipv6 p; if (event != NB_EV_APPLY) return NB_OK; ripng = nb_running_get_entry(dnode, NULL, true); default_information = yang_dnode_get_bool(dnode, NULL); str2prefix_ipv6("::/0", &p); if (default_information) { ripng_redistribute_add(ripng, ZEBRA_ROUTE_RIPNG, RIPNG_ROUTE_DEFAULT, &p, 0, NULL, 0); } else { ripng_redistribute_delete(ripng, ZEBRA_ROUTE_RIPNG, RIPNG_ROUTE_DEFAULT, &p, 0); } return NB_OK; } /* * XPath: /frr-ripngd:ripngd/instance/default-metric */ static int ripngd_instance_default_metric_modify(enum nb_event event, const struct lyd_node *dnode, union nb_resource *resource) { struct ripng *ripng; if (event != NB_EV_APPLY) return NB_OK; ripng = nb_running_get_entry(dnode, NULL, true); ripng->default_metric = yang_dnode_get_uint8(dnode, NULL); return NB_OK; } /* * XPath: /frr-ripngd:ripngd/instance/network */ static int ripngd_instance_network_create(enum nb_event event, const struct lyd_node *dnode, union nb_resource *resource) { struct ripng *ripng; struct prefix p; if (event != NB_EV_APPLY) return NB_OK; ripng = nb_running_get_entry(dnode, NULL, true); yang_dnode_get_ipv6p(&p, dnode, NULL); apply_mask_ipv6((struct prefix_ipv6 *)&p); return ripng_enable_network_add(ripng, &p); } static int ripngd_instance_network_destroy(enum nb_event event, const struct lyd_node *dnode) { struct ripng *ripng; struct prefix p; if (event != NB_EV_APPLY) return NB_OK; ripng = nb_running_get_entry(dnode, NULL, true); yang_dnode_get_ipv6p(&p, dnode, NULL); apply_mask_ipv6((struct prefix_ipv6 *)&p); return ripng_enable_network_delete(ripng, &p); } /* * XPath: /frr-ripngd:ripngd/instance/interface */ static int ripngd_instance_interface_create(enum nb_event event, const struct lyd_node *dnode, union nb_resource *resource) { struct ripng *ripng; const char *ifname; if (event != NB_EV_APPLY) return NB_OK; ripng = nb_running_get_entry(dnode, NULL, true); ifname = yang_dnode_get_string(dnode, NULL); return ripng_enable_if_add(ripng, ifname); } static int ripngd_instance_interface_destroy(enum nb_event event, const struct lyd_node *dnode) { struct ripng *ripng; const char *ifname; if (event != NB_EV_APPLY) return NB_OK; ripng = nb_running_get_entry(dnode, NULL, true); ifname = yang_dnode_get_string(dnode, NULL); return ripng_enable_if_delete(ripng, ifname); } /* * XPath: /frr-ripngd:ripngd/instance/offset-list */ static int ripngd_instance_offset_list_create(enum nb_event event, const struct lyd_node *dnode, union nb_resource *resource) { struct ripng *ripng; const char *ifname; struct ripng_offset_list *offset; if (event != NB_EV_APPLY) return NB_OK; ripng = nb_running_get_entry(dnode, NULL, true); ifname = yang_dnode_get_string(dnode, "./interface"); offset = ripng_offset_list_new(ripng, ifname); nb_running_set_entry(dnode, offset); return NB_OK; } static int ripngd_instance_offset_list_destroy(enum nb_event event, const struct lyd_node *dnode) { int direct; struct ripng_offset_list *offset; if (event != NB_EV_APPLY) return NB_OK; direct = yang_dnode_get_enum(dnode, "./direction"); offset = nb_running_unset_entry(dnode); if (offset->direct[direct].alist_name) { free(offset->direct[direct].alist_name); offset->direct[direct].alist_name = NULL; } if (offset->direct[RIPNG_OFFSET_LIST_IN].alist_name == NULL && offset->direct[RIPNG_OFFSET_LIST_OUT].alist_name == NULL) ripng_offset_list_del(offset); return NB_OK; } /* * XPath: /frr-ripngd:ripngd/instance/offset-list/access-list */ static int ripngd_instance_offset_list_access_list_modify(enum nb_event event, const struct lyd_node *dnode, union nb_resource *resource) { int direct; struct ripng_offset_list *offset; const char *alist_name; if (event != NB_EV_APPLY) return NB_OK; direct = yang_dnode_get_enum(dnode, "../direction"); alist_name = yang_dnode_get_string(dnode, NULL); offset = nb_running_get_entry(dnode, NULL, true); if (offset->direct[direct].alist_name) free(offset->direct[direct].alist_name); offset->direct[direct].alist_name = strdup(alist_name); return NB_OK; } /* * XPath: /frr-ripngd:ripngd/instance/offset-list/metric */ static int ripngd_instance_offset_list_metric_modify(enum nb_event event, const struct lyd_node *dnode, union nb_resource *resource) { int direct; uint8_t metric; struct ripng_offset_list *offset; if (event != NB_EV_APPLY) return NB_OK; direct = yang_dnode_get_enum(dnode, "../direction"); metric = yang_dnode_get_uint8(dnode, NULL); offset = nb_running_get_entry(dnode, NULL, true); offset->direct[direct].metric = metric; return NB_OK; } /* * XPath: /frr-ripngd:ripngd/instance/passive-interface */ static int ripngd_instance_passive_interface_create(enum nb_event event, const struct lyd_node *dnode, union nb_resource *resource) { struct ripng *ripng; const char *ifname; if (event != NB_EV_APPLY) return NB_OK; ripng = nb_running_get_entry(dnode, NULL, true); ifname = yang_dnode_get_string(dnode, NULL); return ripng_passive_interface_set(ripng, ifname); } static int ripngd_instance_passive_interface_destroy(enum nb_event event, const struct lyd_node *dnode) { struct ripng *ripng; const char *ifname; if (event != NB_EV_APPLY) return NB_OK; ripng = nb_running_get_entry(dnode, NULL, true); ifname = yang_dnode_get_string(dnode, NULL); return ripng_passive_interface_unset(ripng, ifname); } /* * XPath: /frr-ripngd:ripngd/instance/redistribute */ static int ripngd_instance_redistribute_create(enum nb_event event, const struct lyd_node *dnode, union nb_resource *resource) { struct ripng *ripng; int type; if (event != NB_EV_APPLY) return NB_OK; ripng = nb_running_get_entry(dnode, NULL, true); type = yang_dnode_get_enum(dnode, "./protocol"); ripng->redist[type].enabled = true; return NB_OK; } static int ripngd_instance_redistribute_destroy(enum nb_event event, const struct lyd_node *dnode) { struct ripng *ripng; int type; if (event != NB_EV_APPLY) return NB_OK; ripng = nb_running_get_entry(dnode, NULL, true); type = yang_dnode_get_enum(dnode, "./protocol"); ripng->redist[type].enabled = false; if (ripng->redist[type].route_map.name) { free(ripng->redist[type].route_map.name); ripng->redist[type].route_map.name = NULL; ripng->redist[type].route_map.map = NULL; } ripng->redist[type].metric_config = false; ripng->redist[type].metric = 0; if (ripng->enabled) ripng_redistribute_conf_delete(ripng, type); return NB_OK; } static void ripngd_instance_redistribute_apply_finish(const struct lyd_node *dnode) { struct ripng *ripng; int type; ripng = nb_running_get_entry(dnode, NULL, true); type = yang_dnode_get_enum(dnode, "./protocol"); if (ripng->enabled) ripng_redistribute_conf_update(ripng, type); } /* * XPath: /frr-ripngd:ripngd/instance/redistribute/route-map */ static int ripngd_instance_redistribute_route_map_modify(enum nb_event event, const struct lyd_node *dnode, union nb_resource *resource) { struct ripng *ripng; int type; const char *rmap_name; if (event != NB_EV_APPLY) return NB_OK; ripng = nb_running_get_entry(dnode, NULL, true); type = yang_dnode_get_enum(dnode, "../protocol"); rmap_name = yang_dnode_get_string(dnode, NULL); if (ripng->redist[type].route_map.name) free(ripng->redist[type].route_map.name); ripng->redist[type].route_map.name = strdup(rmap_name); ripng->redist[type].route_map.map = route_map_lookup_by_name(rmap_name); return NB_OK; } static int ripngd_instance_redistribute_route_map_destroy(enum nb_event event, const struct lyd_node *dnode) { struct ripng *ripng; int type; if (event != NB_EV_APPLY) return NB_OK; ripng = nb_running_get_entry(dnode, NULL, true); type = yang_dnode_get_enum(dnode, "../protocol"); free(ripng->redist[type].route_map.name); ripng->redist[type].route_map.name = NULL; ripng->redist[type].route_map.map = NULL; return NB_OK; } /* * XPath: /frr-ripngd:ripngd/instance/redistribute/metric */ static int ripngd_instance_redistribute_metric_modify(enum nb_event event, const struct lyd_node *dnode, union nb_resource *resource) { struct ripng *ripng; int type; uint8_t metric; if (event != NB_EV_APPLY) return NB_OK; ripng = nb_running_get_entry(dnode, NULL, true); type = yang_dnode_get_enum(dnode, "../protocol"); metric = yang_dnode_get_uint8(dnode, NULL); ripng->redist[type].metric_config = true; ripng->redist[type].metric = metric; return NB_OK; } static int ripngd_instance_redistribute_metric_destroy(enum nb_event event, const struct lyd_node *dnode) { struct ripng *ripng; int type; if (event != NB_EV_APPLY) return NB_OK; ripng = nb_running_get_entry(dnode, NULL, true); type = yang_dnode_get_enum(dnode, "../protocol"); ripng->redist[type].metric_config = false; ripng->redist[type].metric = 0; return NB_OK; } /* * XPath: /frr-ripngd:ripngd/instance/static-route */ static int ripngd_instance_static_route_create(enum nb_event event, const struct lyd_node *dnode, union nb_resource *resource) { struct ripng *ripng; struct prefix_ipv6 p; if (event != NB_EV_APPLY) return NB_OK; ripng = nb_running_get_entry(dnode, NULL, true); yang_dnode_get_ipv6p(&p, dnode, NULL); apply_mask_ipv6(&p); ripng_redistribute_add(ripng, ZEBRA_ROUTE_RIPNG, RIPNG_ROUTE_STATIC, &p, 0, NULL, 0); return NB_OK; } static int ripngd_instance_static_route_destroy(enum nb_event event, const struct lyd_node *dnode) { struct ripng *ripng; struct prefix_ipv6 p; if (event != NB_EV_APPLY) return NB_OK; ripng = nb_running_get_entry(dnode, NULL, true); yang_dnode_get_ipv6p(&p, dnode, NULL); apply_mask_ipv6(&p); ripng_redistribute_delete(ripng, ZEBRA_ROUTE_RIPNG, RIPNG_ROUTE_STATIC, &p, 0); return NB_OK; } /* * XPath: /frr-ripngd:ripngd/instance/aggregate-address */ static int ripngd_instance_aggregate_address_create(enum nb_event event, const struct lyd_node *dnode, union nb_resource *resource) { struct ripng *ripng; struct prefix_ipv6 p; if (event != NB_EV_APPLY) return NB_OK; ripng = nb_running_get_entry(dnode, NULL, true); yang_dnode_get_ipv6p(&p, dnode, NULL); apply_mask_ipv6(&p); ripng_aggregate_add(ripng, (struct prefix *)&p); return NB_OK; } static int ripngd_instance_aggregate_address_destroy(enum nb_event event, const struct lyd_node *dnode) { struct ripng *ripng; struct prefix_ipv6 p; if (event != NB_EV_APPLY) return NB_OK; ripng = nb_running_get_entry(dnode, NULL, true); yang_dnode_get_ipv6p(&p, dnode, NULL); apply_mask_ipv6(&p); ripng_aggregate_delete(ripng, (struct prefix *)&p); return NB_OK; } /* * XPath: /frr-ripngd:ripngd/instance/timers */ static void ripngd_instance_timers_apply_finish(const struct lyd_node *dnode) { struct ripng *ripng; ripng = nb_running_get_entry(dnode, NULL, true); /* Reset update timer thread. */ ripng_event(ripng, RIPNG_UPDATE_EVENT, 0); } /* * XPath: /frr-ripngd:ripngd/instance/timers/flush-interval */ static int ripngd_instance_timers_flush_interval_modify(enum nb_event event, const struct lyd_node *dnode, union nb_resource *resource) { struct ripng *ripng; if (event != NB_EV_APPLY) return NB_OK; ripng = nb_running_get_entry(dnode, NULL, true); ripng->garbage_time = yang_dnode_get_uint16(dnode, NULL); return NB_OK; } /* * XPath: /frr-ripngd:ripngd/instance/timers/holddown-interval */ static int ripngd_instance_timers_holddown_interval_modify(enum nb_event event, const struct lyd_node *dnode, union nb_resource *resource) { struct ripng *ripng; if (event != NB_EV_APPLY) return NB_OK; ripng = nb_running_get_entry(dnode, NULL, true); ripng->timeout_time = yang_dnode_get_uint16(dnode, NULL); return NB_OK; } /* * XPath: /frr-ripngd:ripngd/instance/timers/update-interval */ static int ripngd_instance_timers_update_interval_modify(enum nb_event event, const struct lyd_node *dnode, union nb_resource *resource) { struct ripng *ripng; if (event != NB_EV_APPLY) return NB_OK; ripng = nb_running_get_entry(dnode, NULL, true); ripng->update_time = yang_dnode_get_uint16(dnode, NULL); return NB_OK; } /* * XPath: /frr-ripngd:ripngd/instance/state/neighbors/neighbor */ static const void * ripngd_instance_state_neighbors_neighbor_get_next(const void *parent_list_entry, const void *list_entry) { const struct ripng *ripng = parent_list_entry; struct listnode *node; if (list_entry == NULL) node = listhead(ripng->peer_list); else node = listnextnode((struct listnode *)list_entry); return node; } static int ripngd_instance_state_neighbors_neighbor_get_keys(const void *list_entry, struct yang_list_keys *keys) { const struct listnode *node = list_entry; const struct ripng_peer *peer = listgetdata(node); keys->num = 1; (void)inet_ntop(AF_INET6, &peer->addr, keys->key[0], sizeof(keys->key[0])); return NB_OK; } static const void *ripngd_instance_state_neighbors_neighbor_lookup_entry( const void *parent_list_entry, const struct yang_list_keys *keys) { const struct ripng *ripng = parent_list_entry; struct in6_addr address; struct ripng_peer *peer; struct listnode *node; yang_str2ipv6(keys->key[0], &address); for (ALL_LIST_ELEMENTS_RO(ripng->peer_list, node, peer)) { if (IPV6_ADDR_SAME(&peer->addr, &address)) return node; } return NULL; } /* * XPath: /frr-ripngd:ripngd/instance/state/neighbors/neighbor/address */ static struct yang_data * ripngd_instance_state_neighbors_neighbor_address_get_elem( const char *xpath, const void *list_entry) { const struct listnode *node = list_entry; const struct ripng_peer *peer = listgetdata(node); return yang_data_new_ipv6(xpath, &peer->addr); } /* * XPath: /frr-ripngd:ripngd/instance/state/neighbors/neighbor/last-update */ static struct yang_data * ripngd_instance_state_neighbors_neighbor_last_update_get_elem( const char *xpath, const void *list_entry) { /* TODO: yang:date-and-time is tricky */ return NULL; } /* * XPath: /frr-ripngd:ripngd/instance/state/neighbors/neighbor/bad-packets-rcvd */ static struct yang_data * ripngd_instance_state_neighbors_neighbor_bad_packets_rcvd_get_elem( const char *xpath, const void *list_entry) { const struct listnode *node = list_entry; const struct ripng_peer *peer = listgetdata(node); return yang_data_new_uint32(xpath, peer->recv_badpackets); } /* * XPath: /frr-ripngd:ripngd/instance/state/neighbors/neighbor/bad-routes-rcvd */ static struct yang_data * ripngd_instance_state_neighbors_neighbor_bad_routes_rcvd_get_elem( const char *xpath, const void *list_entry) { const struct listnode *node = list_entry; const struct ripng_peer *peer = listgetdata(node); return yang_data_new_uint32(xpath, peer->recv_badroutes); } /* * XPath: /frr-ripngd:ripngd/instance/state/routes/route */ static const void * ripngd_instance_state_routes_route_get_next(const void *parent_list_entry, const void *list_entry) { const struct ripng *ripng = parent_list_entry; struct agg_node *rn; if (list_entry == NULL) rn = agg_route_top(ripng->table); else rn = agg_route_next((struct agg_node *)list_entry); while (rn && rn->info == NULL) rn = agg_route_next(rn); return rn; } static int ripngd_instance_state_routes_route_get_keys(const void *list_entry, struct yang_list_keys *keys) { const struct agg_node *rn = list_entry; keys->num = 1; (void)prefix2str(&rn->p, keys->key[0], sizeof(keys->key[0])); return NB_OK; } static const void *ripngd_instance_state_routes_route_lookup_entry( const void *parent_list_entry, const struct yang_list_keys *keys) { const struct ripng *ripng = parent_list_entry; struct prefix prefix; struct agg_node *rn; yang_str2ipv6p(keys->key[0], &prefix); rn = agg_node_lookup(ripng->table, &prefix); if (!rn || !rn->info) return NULL; agg_unlock_node(rn); return rn; } /* * XPath: /frr-ripngd:ripngd/instance/state/routes/route/prefix */ static struct yang_data * ripngd_instance_state_routes_route_prefix_get_elem(const char *xpath, const void *list_entry) { const struct agg_node *rn = list_entry; const struct ripng_info *rinfo = listnode_head(rn->info); return yang_data_new_ipv6p(xpath, &rinfo->rp->p); } /* * XPath: /frr-ripngd:ripngd/instance/state/routes/route/next-hop */ static struct yang_data * ripngd_instance_state_routes_route_next_hop_get_elem(const char *xpath, const void *list_entry) { const struct agg_node *rn = list_entry; const struct ripng_info *rinfo = listnode_head(rn->info); return yang_data_new_ipv6(xpath, &rinfo->nexthop); } /* * XPath: /frr-ripngd:ripngd/instance/state/routes/route/interface */ static struct yang_data * ripngd_instance_state_routes_route_interface_get_elem(const char *xpath, const void *list_entry) { const struct agg_node *rn = list_entry; const struct ripng_info *rinfo = listnode_head(rn->info); const struct ripng *ripng = ripng_info_get_instance(rinfo); return yang_data_new_string( xpath, ifindex2ifname(rinfo->ifindex, ripng->vrf->vrf_id)); } /* * XPath: /frr-ripngd:ripngd/instance/state/routes/route/metric */ static struct yang_data * ripngd_instance_state_routes_route_metric_get_elem(const char *xpath, const void *list_entry) { const struct agg_node *rn = list_entry; const struct ripng_info *rinfo = listnode_head(rn->info); return yang_data_new_uint8(xpath, rinfo->metric); } /* * XPath: /frr-ripngd:clear-ripng-route */ static void clear_ripng_route(struct ripng *ripng) { struct agg_node *rp; if (IS_RIPNG_DEBUG_EVENT) zlog_debug("Clearing all RIPng routes (VRF %s)", ripng->vrf_name); /* Clear received RIPng routes */ for (rp = agg_route_top(ripng->table); rp; rp = agg_route_next(rp)) { struct list *list; struct listnode *listnode; struct ripng_info *rinfo; list = rp->info; if (list == NULL) continue; for (ALL_LIST_ELEMENTS_RO(list, listnode, rinfo)) { if (!ripng_route_rte(rinfo)) continue; if (CHECK_FLAG(rinfo->flags, RIPNG_RTF_FIB)) ripng_zebra_ipv6_delete(ripng, rp); break; } if (rinfo) { RIPNG_TIMER_OFF(rinfo->t_timeout); RIPNG_TIMER_OFF(rinfo->t_garbage_collect); listnode_delete(list, rinfo); ripng_info_free(rinfo); } if (list_isempty(list)) { list_delete(&list); rp->info = NULL; agg_unlock_node(rp); } } } static int clear_ripng_route_rpc(const char *xpath, const struct list *input, struct list *output) { struct ripng *ripng; struct yang_data *yang_vrf; yang_vrf = yang_data_list_find(input, "%s/%s", xpath, "input/vrf"); if (yang_vrf) { ripng = ripng_lookup_by_vrf_name(yang_vrf->value); if (ripng) clear_ripng_route(ripng); } else { struct vrf *vrf; RB_FOREACH (vrf, vrf_name_head, &vrfs_by_name) { ripng = vrf->info; if (!ripng) continue; clear_ripng_route(ripng); } } return NB_OK; } /* * XPath: /frr-interface:lib/interface/frr-ripngd:ripng/split-horizon */ static int lib_interface_ripng_split_horizon_modify(enum nb_event event, const struct lyd_node *dnode, union nb_resource *resource) { struct interface *ifp; struct ripng_interface *ri; if (event != NB_EV_APPLY) return NB_OK; ifp = nb_running_get_entry(dnode, NULL, true); ri = ifp->info; ri->split_horizon = yang_dnode_get_enum(dnode, NULL); return NB_OK; } /* clang-format off */ const struct frr_yang_module_info frr_ripngd_info = { .name = "frr-ripngd", .nodes = { { .xpath = "/frr-ripngd:ripngd/instance", .cbs = { .cli_show = cli_show_router_ripng, .create = ripngd_instance_create, .destroy = ripngd_instance_destroy, .get_keys = ripngd_instance_get_keys, .get_next = ripngd_instance_get_next, .lookup_entry = ripngd_instance_lookup_entry, }, }, { .xpath = "/frr-ripngd:ripngd/instance/allow-ecmp", .cbs = { .cli_show = cli_show_ripng_allow_ecmp, .modify = ripngd_instance_allow_ecmp_modify, }, }, { .xpath = "/frr-ripngd:ripngd/instance/default-information-originate", .cbs = { .cli_show = cli_show_ripng_default_information_originate, .modify = ripngd_instance_default_information_originate_modify, }, }, { .xpath = "/frr-ripngd:ripngd/instance/default-metric", .cbs = { .cli_show = cli_show_ripng_default_metric, .modify = ripngd_instance_default_metric_modify, }, }, { .xpath = "/frr-ripngd:ripngd/instance/network", .cbs = { .cli_show = cli_show_ripng_network_prefix, .create = ripngd_instance_network_create, .destroy = ripngd_instance_network_destroy, }, }, { .xpath = "/frr-ripngd:ripngd/instance/interface", .cbs = { .cli_show = cli_show_ripng_network_interface, .create = ripngd_instance_interface_create, .destroy = ripngd_instance_interface_destroy, }, }, { .xpath = "/frr-ripngd:ripngd/instance/offset-list", .cbs = { .cli_show = cli_show_ripng_offset_list, .create = ripngd_instance_offset_list_create, .destroy = ripngd_instance_offset_list_destroy, }, }, { .xpath = "/frr-ripngd:ripngd/instance/offset-list/access-list", .cbs = { .modify = ripngd_instance_offset_list_access_list_modify, }, }, { .xpath = "/frr-ripngd:ripngd/instance/offset-list/metric", .cbs = { .modify = ripngd_instance_offset_list_metric_modify, }, }, { .xpath = "/frr-ripngd:ripngd/instance/passive-interface", .cbs = { .cli_show = cli_show_ripng_passive_interface, .create = ripngd_instance_passive_interface_create, .destroy = ripngd_instance_passive_interface_destroy, }, }, { .xpath = "/frr-ripngd:ripngd/instance/redistribute", .cbs = { .apply_finish = ripngd_instance_redistribute_apply_finish, .cli_show = cli_show_ripng_redistribute, .create = ripngd_instance_redistribute_create, .destroy = ripngd_instance_redistribute_destroy, }, }, { .xpath = "/frr-ripngd:ripngd/instance/redistribute/route-map", .cbs = { .destroy = ripngd_instance_redistribute_route_map_destroy, .modify = ripngd_instance_redistribute_route_map_modify, }, }, { .xpath = "/frr-ripngd:ripngd/instance/redistribute/metric", .cbs = { .destroy = ripngd_instance_redistribute_metric_destroy, .modify = ripngd_instance_redistribute_metric_modify, }, }, { .xpath = "/frr-ripngd:ripngd/instance/static-route", .cbs = { .cli_show = cli_show_ripng_route, .create = ripngd_instance_static_route_create, .destroy = ripngd_instance_static_route_destroy, }, }, { .xpath = "/frr-ripngd:ripngd/instance/aggregate-address", .cbs = { .cli_show = cli_show_ripng_aggregate_address, .create = ripngd_instance_aggregate_address_create, .destroy = ripngd_instance_aggregate_address_destroy, }, }, { .xpath = "/frr-ripngd:ripngd/instance/timers", .cbs = { .apply_finish = ripngd_instance_timers_apply_finish, .cli_show = cli_show_ripng_timers, }, }, { .xpath = "/frr-ripngd:ripngd/instance/timers/flush-interval", .cbs = { .modify = ripngd_instance_timers_flush_interval_modify, }, }, { .xpath = "/frr-ripngd:ripngd/instance/timers/holddown-interval", .cbs = { .modify = ripngd_instance_timers_holddown_interval_modify, }, }, { .xpath = "/frr-ripngd:ripngd/instance/timers/update-interval", .cbs = { .modify = ripngd_instance_timers_update_interval_modify, }, }, { .xpath = "/frr-ripngd:ripngd/instance/state/neighbors/neighbor", .cbs = { .get_keys = ripngd_instance_state_neighbors_neighbor_get_keys, .get_next = ripngd_instance_state_neighbors_neighbor_get_next, .lookup_entry = ripngd_instance_state_neighbors_neighbor_lookup_entry, }, }, { .xpath = "/frr-ripngd:ripngd/instance/state/neighbors/neighbor/address", .cbs = { .get_elem = ripngd_instance_state_neighbors_neighbor_address_get_elem, }, }, { .xpath = "/frr-ripngd:ripngd/instance/state/neighbors/neighbor/last-update", .cbs = { .get_elem = ripngd_instance_state_neighbors_neighbor_last_update_get_elem, }, }, { .xpath = "/frr-ripngd:ripngd/instance/state/neighbors/neighbor/bad-packets-rcvd", .cbs = { .get_elem = ripngd_instance_state_neighbors_neighbor_bad_packets_rcvd_get_elem, }, }, { .xpath = "/frr-ripngd:ripngd/instance/state/neighbors/neighbor/bad-routes-rcvd", .cbs = { .get_elem = ripngd_instance_state_neighbors_neighbor_bad_routes_rcvd_get_elem, }, }, { .xpath = "/frr-ripngd:ripngd/instance/state/routes/route", .cbs = { .get_keys = ripngd_instance_state_routes_route_get_keys, .get_next = ripngd_instance_state_routes_route_get_next, .lookup_entry = ripngd_instance_state_routes_route_lookup_entry, }, }, { .xpath = "/frr-ripngd:ripngd/instance/state/routes/route/prefix", .cbs = { .get_elem = ripngd_instance_state_routes_route_prefix_get_elem, }, }, { .xpath = "/frr-ripngd:ripngd/instance/state/routes/route/next-hop", .cbs = { .get_elem = ripngd_instance_state_routes_route_next_hop_get_elem, }, }, { .xpath = "/frr-ripngd:ripngd/instance/state/routes/route/interface", .cbs = { .get_elem = ripngd_instance_state_routes_route_interface_get_elem, }, }, { .xpath = "/frr-ripngd:ripngd/instance/state/routes/route/metric", .cbs = { .get_elem = ripngd_instance_state_routes_route_metric_get_elem, }, }, { .xpath = "/frr-ripngd:clear-ripng-route", .cbs = { .rpc = clear_ripng_route_rpc, }, }, { .xpath = "/frr-interface:lib/interface/frr-ripngd:ripng/split-horizon", .cbs = { .cli_show = cli_show_ipv6_ripng_split_horizon, .modify = lib_interface_ripng_split_horizon_modify, }, }, { .xpath = NULL, }, } }; frr-7.2.1/ripngd/ripng_offset.c0000644000000000000000000001127013610377563013331 00000000000000/* RIPng offset-list * Copyright (C) 2000 Kunihiro Ishiguro * * This file is part of GNU Zebra. * * GNU Zebra is free software; you can redistribute it and/or modify it * under the terms of the GNU General Public License as published by the * Free Software Foundation; either version 2, or (at your option) any * later version. * * GNU Zebra 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; see the file COPYING; if not, write to the Free Software * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA */ /* RIPng support by Vincent Jardin * Copyright (C) 2002 6WIND */ #include #include "if.h" #include "prefix.h" #include "filter.h" #include "command.h" #include "linklist.h" #include "memory.h" #include "ripngd/ripngd.h" DEFINE_MTYPE_STATIC(RIPNGD, RIPNG_OFFSET_LIST, "RIPng offset lst") #define OFFSET_LIST_IN_NAME(O) ((O)->direct[RIPNG_OFFSET_LIST_IN].alist_name) #define OFFSET_LIST_IN_METRIC(O) ((O)->direct[RIPNG_OFFSET_LIST_IN].metric) #define OFFSET_LIST_OUT_NAME(O) ((O)->direct[RIPNG_OFFSET_LIST_OUT].alist_name) #define OFFSET_LIST_OUT_METRIC(O) ((O)->direct[RIPNG_OFFSET_LIST_OUT].metric) struct ripng_offset_list *ripng_offset_list_new(struct ripng *ripng, const char *ifname) { struct ripng_offset_list *new; new = XCALLOC(MTYPE_RIPNG_OFFSET_LIST, sizeof(struct ripng_offset_list)); new->ripng = ripng; new->ifname = strdup(ifname); listnode_add_sort(ripng->offset_list_master, new); return new; } void ripng_offset_list_del(struct ripng_offset_list *offset) { listnode_delete(offset->ripng->offset_list_master, offset); ripng_offset_list_free(offset); } void ripng_offset_list_free(struct ripng_offset_list *offset) { if (OFFSET_LIST_IN_NAME(offset)) free(OFFSET_LIST_IN_NAME(offset)); if (OFFSET_LIST_OUT_NAME(offset)) free(OFFSET_LIST_OUT_NAME(offset)); free(offset->ifname); XFREE(MTYPE_RIPNG_OFFSET_LIST, offset); } struct ripng_offset_list *ripng_offset_list_lookup(struct ripng *ripng, const char *ifname) { struct ripng_offset_list *offset; struct listnode *node, *nnode; for (ALL_LIST_ELEMENTS(ripng->offset_list_master, node, nnode, offset)) { if (strcmp(offset->ifname, ifname) == 0) return offset; } return NULL; } /* If metric is modifed return 1. */ int ripng_offset_list_apply_in(struct ripng *ripng, struct prefix_ipv6 *p, struct interface *ifp, uint8_t *metric) { struct ripng_offset_list *offset; struct access_list *alist; /* Look up offset-list with interface name. */ offset = ripng_offset_list_lookup(ripng, ifp->name); if (offset && OFFSET_LIST_IN_NAME(offset)) { alist = access_list_lookup(AFI_IP6, OFFSET_LIST_IN_NAME(offset)); if (alist && access_list_apply(alist, (struct prefix *)p) == FILTER_PERMIT) { *metric += OFFSET_LIST_IN_METRIC(offset); return 1; } return 0; } /* Look up offset-list without interface name. */ offset = ripng_offset_list_lookup(ripng, "*"); if (offset && OFFSET_LIST_IN_NAME(offset)) { alist = access_list_lookup(AFI_IP6, OFFSET_LIST_IN_NAME(offset)); if (alist && access_list_apply(alist, (struct prefix *)p) == FILTER_PERMIT) { *metric += OFFSET_LIST_IN_METRIC(offset); return 1; } return 0; } return 0; } /* If metric is modifed return 1. */ int ripng_offset_list_apply_out(struct ripng *ripng, struct prefix_ipv6 *p, struct interface *ifp, uint8_t *metric) { struct ripng_offset_list *offset; struct access_list *alist; /* Look up offset-list with interface name. */ offset = ripng_offset_list_lookup(ripng, ifp->name); if (offset && OFFSET_LIST_OUT_NAME(offset)) { alist = access_list_lookup(AFI_IP6, OFFSET_LIST_OUT_NAME(offset)); if (alist && access_list_apply(alist, (struct prefix *)p) == FILTER_PERMIT) { *metric += OFFSET_LIST_OUT_METRIC(offset); return 1; } return 0; } /* Look up offset-list without interface name. */ offset = ripng_offset_list_lookup(ripng, "*"); if (offset && OFFSET_LIST_OUT_NAME(offset)) { alist = access_list_lookup(AFI_IP6, OFFSET_LIST_OUT_NAME(offset)); if (alist && access_list_apply(alist, (struct prefix *)p) == FILTER_PERMIT) { *metric += OFFSET_LIST_OUT_METRIC(offset); return 1; } return 0; } return 0; } int offset_list_cmp(struct ripng_offset_list *o1, struct ripng_offset_list *o2) { return strcmp(o1->ifname, o2->ifname); } frr-7.2.1/ripngd/ripng_peer.c0000644000000000000000000001143713610377563013003 00000000000000/* RIPng peer support * Copyright (C) 2000 Kunihiro Ishiguro * * This file is part of GNU Zebra. * * GNU Zebra is free software; you can redistribute it and/or modify it * under the terms of the GNU General Public License as published by the * Free Software Foundation; either version 2, or (at your option) any * later version. * * GNU Zebra 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; see the file COPYING; if not, write to the Free Software * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA */ /* RIPng support added by Vincent Jardin * Copyright (C) 2002 6WIND */ #include #include "if.h" #include "prefix.h" #include "command.h" #include "linklist.h" #include "thread.h" #include "memory.h" #include "ripngd/ripngd.h" #include "ripngd/ripng_nexthop.h" DEFINE_MTYPE_STATIC(RIPNGD, RIPNG_PEER, "RIPng peer") static struct ripng_peer *ripng_peer_new(void) { return XCALLOC(MTYPE_RIPNG_PEER, sizeof(struct ripng_peer)); } static void ripng_peer_free(struct ripng_peer *peer) { RIPNG_TIMER_OFF(peer->t_timeout); XFREE(MTYPE_RIPNG_PEER, peer); } struct ripng_peer *ripng_peer_lookup(struct ripng *ripng, struct in6_addr *addr) { struct ripng_peer *peer; struct listnode *node, *nnode; for (ALL_LIST_ELEMENTS(ripng->peer_list, node, nnode, peer)) { if (IPV6_ADDR_SAME(&peer->addr, addr)) return peer; } return NULL; } struct ripng_peer *ripng_peer_lookup_next(struct ripng *ripng, struct in6_addr *addr) { struct ripng_peer *peer; struct listnode *node, *nnode; for (ALL_LIST_ELEMENTS(ripng->peer_list, node, nnode, peer)) { if (addr6_cmp(&peer->addr, addr) > 0) return peer; } return NULL; } /* RIPng peer is timeout. * Garbage collector. **/ static int ripng_peer_timeout(struct thread *t) { struct ripng_peer *peer; peer = THREAD_ARG(t); listnode_delete(peer->ripng->peer_list, peer); ripng_peer_free(peer); return 0; } /* Get RIPng peer. At the same time update timeout thread. */ static struct ripng_peer *ripng_peer_get(struct ripng *ripng, struct in6_addr *addr) { struct ripng_peer *peer; peer = ripng_peer_lookup(ripng, addr); if (peer) { if (peer->t_timeout) thread_cancel(peer->t_timeout); } else { peer = ripng_peer_new(); peer->ripng = ripng; peer->addr = *addr; listnode_add_sort(ripng->peer_list, peer); } /* Update timeout thread. */ peer->t_timeout = NULL; thread_add_timer(master, ripng_peer_timeout, peer, RIPNG_PEER_TIMER_DEFAULT, &peer->t_timeout); /* Last update time set. */ time(&peer->uptime); return peer; } void ripng_peer_update(struct ripng *ripng, struct sockaddr_in6 *from, uint8_t version) { struct ripng_peer *peer; peer = ripng_peer_get(ripng, &from->sin6_addr); peer->version = version; } void ripng_peer_bad_route(struct ripng *ripng, struct sockaddr_in6 *from) { struct ripng_peer *peer; peer = ripng_peer_get(ripng, &from->sin6_addr); peer->recv_badroutes++; } void ripng_peer_bad_packet(struct ripng *ripng, struct sockaddr_in6 *from) { struct ripng_peer *peer; peer = ripng_peer_get(ripng, &from->sin6_addr); peer->recv_badpackets++; } /* Display peer uptime. */ static char *ripng_peer_uptime(struct ripng_peer *peer, char *buf, size_t len) { time_t uptime; struct tm *tm; /* If there is no connection has been done before print `never'. */ if (peer->uptime == 0) { snprintf(buf, len, "never "); return buf; } /* Get current time. */ uptime = time(NULL); uptime -= peer->uptime; tm = gmtime(&uptime); if (uptime < ONE_DAY_SECOND) snprintf(buf, len, "%02d:%02d:%02d", tm->tm_hour, tm->tm_min, tm->tm_sec); else if (uptime < ONE_WEEK_SECOND) snprintf(buf, len, "%dd%02dh%02dm", tm->tm_yday, tm->tm_hour, tm->tm_min); else snprintf(buf, len, "%02dw%dd%02dh", tm->tm_yday / 7, tm->tm_yday - ((tm->tm_yday / 7) * 7), tm->tm_hour); return buf; } void ripng_peer_display(struct vty *vty, struct ripng *ripng) { struct ripng_peer *peer; struct listnode *node, *nnode; #define RIPNG_UPTIME_LEN 25 char timebuf[RIPNG_UPTIME_LEN]; for (ALL_LIST_ELEMENTS(ripng->peer_list, node, nnode, peer)) { vty_out(vty, " %s \n%14s %10d %10d %10d %s\n", inet6_ntoa(peer->addr), " ", peer->recv_badpackets, peer->recv_badroutes, ZEBRA_RIPNG_DISTANCE_DEFAULT, ripng_peer_uptime(peer, timebuf, RIPNG_UPTIME_LEN)); } } int ripng_peer_list_cmp(struct ripng_peer *p1, struct ripng_peer *p2) { return memcmp(&p1->addr, &p2->addr, sizeof(struct in6_addr)); } void ripng_peer_list_del(void *arg) { ripng_peer_free(arg); } frr-7.2.1/ripngd/ripng_route.c0000644000000000000000000001040613610377563013201 00000000000000/* * RIPng routes function. * Copyright (C) 1998 Kunihiro Ishiguro * * This file is part of GNU Zebra. * * GNU Zebra is free software; you can redistribute it and/or modify it * under the terms of the GNU General Public License as published by the * Free Software Foundation; either version 2, or (at your option) any * later version. * * GNU Zebra 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; see the file COPYING; if not, write to the Free Software * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA */ #include #include "prefix.h" #include "agg_table.h" #include "memory.h" #include "if.h" #include "vty.h" #include "ripngd/ripngd.h" #include "ripngd/ripng_route.h" DEFINE_MTYPE_STATIC(RIPNGD, RIPNG_AGGREGATE, "RIPng aggregate") static struct ripng_aggregate *ripng_aggregate_new(void) { struct ripng_aggregate *new; new = XCALLOC(MTYPE_RIPNG_AGGREGATE, sizeof(struct ripng_aggregate)); return new; } void ripng_aggregate_free(struct ripng_aggregate *aggregate) { XFREE(MTYPE_RIPNG_AGGREGATE, aggregate); } /* Aggregate count increment check. */ void ripng_aggregate_increment(struct agg_node *child, struct ripng_info *rinfo) { struct agg_node *np; struct ripng_aggregate *aggregate; for (np = child; np; np = agg_node_parent(np)) if ((aggregate = np->aggregate) != NULL) { aggregate->count++; rinfo->suppress++; } } /* Aggregate count decrement check. */ void ripng_aggregate_decrement(struct agg_node *child, struct ripng_info *rinfo) { struct agg_node *np; struct ripng_aggregate *aggregate; for (np = child; np; np = agg_node_parent(np)) if ((aggregate = np->aggregate) != NULL) { aggregate->count--; rinfo->suppress--; } } /* Aggregate count decrement check for a list. */ void ripng_aggregate_decrement_list(struct agg_node *child, struct list *list) { struct agg_node *np; struct ripng_aggregate *aggregate; struct ripng_info *rinfo = NULL; struct listnode *node = NULL; for (np = child; np; np = agg_node_parent(np)) if ((aggregate = np->aggregate) != NULL) aggregate->count -= listcount(list); for (ALL_LIST_ELEMENTS_RO(list, node, rinfo)) rinfo->suppress--; } /* RIPng routes treatment. */ int ripng_aggregate_add(struct ripng *ripng, struct prefix *p) { struct agg_node *top; struct agg_node *rp; struct ripng_info *rinfo; struct ripng_aggregate *aggregate; struct ripng_aggregate *sub; struct list *list = NULL; struct listnode *node = NULL; /* Get top node for aggregation. */ top = agg_node_get(ripng->table, p); /* Allocate new aggregate. */ aggregate = ripng_aggregate_new(); aggregate->metric = 1; top->aggregate = aggregate; /* Suppress routes match to the aggregate. */ for (rp = agg_lock_node(top); rp; rp = agg_route_next_until(rp, top)) { /* Suppress normal route. */ if ((list = rp->info) != NULL) for (ALL_LIST_ELEMENTS_RO(list, node, rinfo)) { aggregate->count++; rinfo->suppress++; } /* Suppress aggregate route. This may not need. */ if (rp != top && (sub = rp->aggregate) != NULL) { aggregate->count++; sub->suppress++; } } return 0; } /* Delete RIPng static route. */ int ripng_aggregate_delete(struct ripng *ripng, struct prefix *p) { struct agg_node *top; struct agg_node *rp; struct ripng_info *rinfo; struct ripng_aggregate *aggregate; struct ripng_aggregate *sub; struct list *list = NULL; struct listnode *node = NULL; /* Get top node for aggregation. */ top = agg_node_get(ripng->table, p); /* Allocate new aggregate. */ aggregate = top->aggregate; /* Suppress routes match to the aggregate. */ for (rp = agg_lock_node(top); rp; rp = agg_route_next_until(rp, top)) { /* Suppress normal route. */ if ((list = rp->info) != NULL) for (ALL_LIST_ELEMENTS_RO(list, node, rinfo)) { aggregate->count--; rinfo->suppress--; } if (rp != top && (sub = rp->aggregate) != NULL) { aggregate->count--; sub->suppress--; } } top->aggregate = NULL; ripng_aggregate_free(aggregate); agg_unlock_node(top); agg_unlock_node(top); return 0; } frr-7.2.1/ripngd/ripng_route.h0000644000000000000000000000340113610377563013203 00000000000000/* * RIPng daemon * Copyright (C) 1998 Kunihiro Ishiguro * * This file is part of GNU Zebra. * * GNU Zebra is free software; you can redistribute it and/or modify it * under the terms of the GNU General Public License as published by the * Free Software Foundation; either version 2, or (at your option) any * later version. * * GNU Zebra 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; see the file COPYING; if not, write to the Free Software * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA */ #ifndef _ZEBRA_RIPNG_ROUTE_H #define _ZEBRA_RIPNG_ROUTE_H struct ripng_aggregate { /* Aggregate route count. */ unsigned int count; /* Suppressed route count. */ unsigned int suppress; /* Metric of this route. */ uint8_t metric; /* Tag field of RIPng packet.*/ uint16_t tag; /* Route-map futures - this variables can be changed. */ struct in6_addr nexthop_out; uint8_t metric_set; uint8_t metric_out; uint16_t tag_out; }; extern void ripng_aggregate_increment(struct agg_node *rp, struct ripng_info *rinfo); extern void ripng_aggregate_decrement(struct agg_node *rp, struct ripng_info *rinfo); extern void ripng_aggregate_decrement_list(struct agg_node *rp, struct list *list); extern int ripng_aggregate_add(struct ripng *ripng, struct prefix *p); extern int ripng_aggregate_delete(struct ripng *ripng, struct prefix *p); extern void ripng_aggregate_free(struct ripng_aggregate *aggregate); #endif /* _ZEBRA_RIPNG_ROUTE_H */ frr-7.2.1/ripngd/ripng_routemap.c0000644000000000000000000002176513610377563013711 00000000000000/* RIPng routemap. * Copyright (C) 1999 Kunihiro Ishiguro * * This file is part of GNU Zebra. * * GNU Zebra is free software; you can redistribute it and/or modify it * under the terms of the GNU General Public License as published by the * Free Software Foundation; either version 2, or (at your option) any * later version. * * GNU Zebra 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; see the file COPYING; if not, write to the Free Software * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA */ #include #include "if.h" #include "memory.h" #include "prefix.h" #include "vty.h" #include "routemap.h" #include "command.h" #include "sockunion.h" #include "ripngd/ripngd.h" struct rip_metric_modifier { enum { metric_increment, metric_decrement, metric_absolute } type; bool used; uint8_t metric; }; /* `match metric METRIC' */ /* Match function return 1 if match is success else return zero. */ static enum route_map_cmd_result_t route_match_metric(void *rule, const struct prefix *prefix, route_map_object_t type, void *object) { uint32_t *metric; struct ripng_info *rinfo; if (type == RMAP_RIPNG) { metric = rule; rinfo = object; if (rinfo->metric == *metric) return RMAP_MATCH; else return RMAP_NOMATCH; } return RMAP_NOMATCH; } /* Route map `match metric' match statement. `arg' is METRIC value */ static void *route_match_metric_compile(const char *arg) { uint32_t *metric; metric = XMALLOC(MTYPE_ROUTE_MAP_COMPILED, sizeof(uint32_t)); *metric = atoi(arg); if (*metric > 0) return metric; XFREE(MTYPE_ROUTE_MAP_COMPILED, metric); return NULL; } /* Free route map's compiled `match metric' value. */ static void route_match_metric_free(void *rule) { XFREE(MTYPE_ROUTE_MAP_COMPILED, rule); } /* Route map commands for metric matching. */ static struct route_map_rule_cmd route_match_metric_cmd = { "metric", route_match_metric, route_match_metric_compile, route_match_metric_free}; /* `match interface IFNAME' */ /* Match function return 1 if match is success else return zero. */ static enum route_map_cmd_result_t route_match_interface(void *rule, const struct prefix *prefix, route_map_object_t type, void *object) { struct ripng_info *rinfo; struct interface *ifp; char *ifname; if (type == RMAP_RIPNG) { ifname = rule; ifp = if_lookup_by_name(ifname, VRF_DEFAULT); if (!ifp) return RMAP_NOMATCH; rinfo = object; if (rinfo->ifindex == ifp->ifindex) return RMAP_MATCH; else return RMAP_NOMATCH; } return RMAP_NOMATCH; } /* Route map `match interface' match statement. `arg' is IFNAME value */ static void *route_match_interface_compile(const char *arg) { return XSTRDUP(MTYPE_ROUTE_MAP_COMPILED, arg); } static void route_match_interface_free(void *rule) { XFREE(MTYPE_ROUTE_MAP_COMPILED, rule); } static struct route_map_rule_cmd route_match_interface_cmd = { "interface", route_match_interface, route_match_interface_compile, route_match_interface_free}; /* `match tag TAG' */ /* Match function return 1 if match is success else return zero. */ static enum route_map_cmd_result_t route_match_tag(void *rule, const struct prefix *prefix, route_map_object_t type, void *object) { route_tag_t *tag; struct ripng_info *rinfo; route_tag_t rinfo_tag; if (type == RMAP_RIPNG) { tag = rule; rinfo = object; /* The information stored by rinfo is host ordered. */ rinfo_tag = rinfo->tag; if (rinfo_tag == *tag) return RMAP_MATCH; else return RMAP_NOMATCH; } return RMAP_NOMATCH; } static struct route_map_rule_cmd route_match_tag_cmd = { "tag", route_match_tag, route_map_rule_tag_compile, route_map_rule_tag_free, }; /* `set metric METRIC' */ /* Set metric to attribute. */ static enum route_map_cmd_result_t route_set_metric(void *rule, const struct prefix *prefix, route_map_object_t type, void *object) { if (type == RMAP_RIPNG) { struct rip_metric_modifier *mod; struct ripng_info *rinfo; mod = rule; rinfo = object; if (!mod->used) return RMAP_OKAY; if (mod->type == metric_increment) rinfo->metric_out += mod->metric; else if (mod->type == metric_decrement) rinfo->metric_out -= mod->metric; else if (mod->type == metric_absolute) rinfo->metric_out = mod->metric; if (rinfo->metric_out < 1) rinfo->metric_out = 1; if (rinfo->metric_out > RIPNG_METRIC_INFINITY) rinfo->metric_out = RIPNG_METRIC_INFINITY; rinfo->metric_set = 1; } return RMAP_OKAY; } /* set metric compilation. */ static void *route_set_metric_compile(const char *arg) { int len; const char *pnt; long metric; char *endptr = NULL; struct rip_metric_modifier *mod; len = strlen(arg); pnt = arg; mod = XMALLOC(MTYPE_ROUTE_MAP_COMPILED, sizeof(struct rip_metric_modifier)); mod->used = false; if (len == 0) return mod; /* Examine first character. */ if (arg[0] == '+') { mod->type = metric_increment; pnt++; } else if (arg[0] == '-') { mod->type = metric_decrement; pnt++; } else mod->type = metric_absolute; /* Check beginning with digit string. */ if (*pnt < '0' || *pnt > '9') return mod; /* Convert string to integer. */ metric = strtol(pnt, &endptr, 10); if (*endptr != '\0' || metric < 0) return mod; if (metric > RIPNG_METRIC_INFINITY) { zlog_info("%s: Metric specified: %ld is being converted into METRIC_INFINITY", __PRETTY_FUNCTION__, metric); mod->metric = RIPNG_METRIC_INFINITY; } else mod->metric = metric; mod->used = true; return mod; } /* Free route map's compiled `set metric' value. */ static void route_set_metric_free(void *rule) { XFREE(MTYPE_ROUTE_MAP_COMPILED, rule); } static struct route_map_rule_cmd route_set_metric_cmd = { "metric", route_set_metric, route_set_metric_compile, route_set_metric_free, }; /* `set ipv6 next-hop local IP_ADDRESS' */ /* Set nexthop to object. ojbect must be pointer to struct attr. */ static enum route_map_cmd_result_t route_set_ipv6_nexthop_local(void *rule, const struct prefix *p, route_map_object_t type, void *object) { struct in6_addr *address; struct ripng_info *rinfo; if (type == RMAP_RIPNG) { /* Fetch routemap's rule information. */ address = rule; rinfo = object; /* Set next hop value. */ rinfo->nexthop_out = *address; } return RMAP_OKAY; } /* Route map `ipv6 nexthop local' compile function. Given string is converted to struct in6_addr structure. */ static void *route_set_ipv6_nexthop_local_compile(const char *arg) { int ret; struct in6_addr *address; address = XMALLOC(MTYPE_ROUTE_MAP_COMPILED, sizeof(struct in6_addr)); ret = inet_pton(AF_INET6, arg, address); if (ret == 0) { XFREE(MTYPE_ROUTE_MAP_COMPILED, address); return NULL; } return address; } /* Free route map's compiled `ipv6 nexthop local' value. */ static void route_set_ipv6_nexthop_local_free(void *rule) { XFREE(MTYPE_ROUTE_MAP_COMPILED, rule); } /* Route map commands for ipv6 nexthop local set. */ static struct route_map_rule_cmd route_set_ipv6_nexthop_local_cmd = { "ipv6 next-hop local", route_set_ipv6_nexthop_local, route_set_ipv6_nexthop_local_compile, route_set_ipv6_nexthop_local_free}; /* `set tag TAG' */ /* Set tag to object. ojbect must be pointer to struct attr. */ static enum route_map_cmd_result_t route_set_tag(void *rule, const struct prefix *prefix, route_map_object_t type, void *object) { route_tag_t *tag; struct ripng_info *rinfo; if (type == RMAP_RIPNG) { /* Fetch routemap's rule information. */ tag = rule; rinfo = object; /* Set next hop value. */ rinfo->tag_out = *tag; } return RMAP_OKAY; } /* Route map commands for tag set. */ static struct route_map_rule_cmd route_set_tag_cmd = { "tag", route_set_tag, route_map_rule_tag_compile, route_map_rule_tag_free}; #define MATCH_STR "Match values from routing table\n" #define SET_STR "Set values in destination routing protocol\n" void ripng_route_map_init(void) { route_map_init(); route_map_match_interface_hook(generic_match_add); route_map_no_match_interface_hook(generic_match_delete); route_map_match_metric_hook(generic_match_add); route_map_no_match_metric_hook(generic_match_delete); route_map_match_tag_hook(generic_match_add); route_map_no_match_tag_hook(generic_match_delete); route_map_set_ipv6_nexthop_local_hook(generic_set_add); route_map_no_set_ipv6_nexthop_local_hook(generic_set_delete); route_map_set_metric_hook(generic_set_add); route_map_no_set_metric_hook(generic_set_delete); route_map_set_tag_hook(generic_set_add); route_map_no_set_tag_hook(generic_set_delete); route_map_install_match(&route_match_metric_cmd); route_map_install_match(&route_match_interface_cmd); route_map_install_match(&route_match_tag_cmd); route_map_install_set(&route_set_metric_cmd); route_map_install_set(&route_set_ipv6_nexthop_local_cmd); route_map_install_set(&route_set_tag_cmd); } frr-7.2.1/ripngd/ripng_zebra.c0000644000000000000000000001537413610377563013157 00000000000000/* * RIPngd and zebra interface. * Copyright (C) 1998, 1999 Kunihiro Ishiguro * * This file is part of GNU Zebra. * * GNU Zebra is free software; you can redistribute it and/or modify it * under the terms of the GNU General Public License as published by the * Free Software Foundation; either version 2, or (at your option) any * later version. * * GNU Zebra 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; see the file COPYING; if not, write to the Free Software * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA */ #include #include "command.h" #include "prefix.h" #include "agg_table.h" #include "stream.h" #include "memory.h" #include "routemap.h" #include "zclient.h" #include "log.h" #include "ripngd/ripngd.h" #include "ripngd/ripng_debug.h" /* All information about zebra. */ struct zclient *zclient = NULL; /* Send ECMP routes to zebra. */ static void ripng_zebra_ipv6_send(struct ripng *ripng, struct agg_node *rp, uint8_t cmd) { struct list *list = (struct list *)rp->info; struct zapi_route api; struct zapi_nexthop *api_nh; struct listnode *listnode = NULL; struct ripng_info *rinfo = NULL; int count = 0; memset(&api, 0, sizeof(api)); api.vrf_id = ripng->vrf->vrf_id; api.type = ZEBRA_ROUTE_RIPNG; api.safi = SAFI_UNICAST; api.prefix = rp->p; SET_FLAG(api.message, ZAPI_MESSAGE_NEXTHOP); for (ALL_LIST_ELEMENTS_RO(list, listnode, rinfo)) { if (count >= MULTIPATH_NUM) break; api_nh = &api.nexthops[count]; api_nh->vrf_id = ripng->vrf->vrf_id; api_nh->gate.ipv6 = rinfo->nexthop; api_nh->ifindex = rinfo->ifindex; api_nh->type = NEXTHOP_TYPE_IPV6_IFINDEX; count++; if (cmd == ZEBRA_ROUTE_ADD) SET_FLAG(rinfo->flags, RIPNG_RTF_FIB); else UNSET_FLAG(rinfo->flags, RIPNG_RTF_FIB); } api.nexthop_num = count; rinfo = listgetdata(listhead(list)); SET_FLAG(api.message, ZAPI_MESSAGE_METRIC); api.metric = rinfo->metric; if (rinfo->tag) { SET_FLAG(api.message, ZAPI_MESSAGE_TAG); api.tag = rinfo->tag; } zclient_route_send(cmd, zclient, &api); if (IS_RIPNG_DEBUG_ZEBRA) { if (ripng->ecmp) zlog_debug("%s: %s/%d nexthops %d", (cmd == ZEBRA_ROUTE_ADD) ? "Install into zebra" : "Delete from zebra", inet6_ntoa(rp->p.u.prefix6), rp->p.prefixlen, count); else zlog_debug( "%s: %s/%d", (cmd == ZEBRA_ROUTE_ADD) ? "Install into zebra" : "Delete from zebra", inet6_ntoa(rp->p.u.prefix6), rp->p.prefixlen); } } /* Add/update ECMP routes to zebra. */ void ripng_zebra_ipv6_add(struct ripng *ripng, struct agg_node *rp) { ripng_zebra_ipv6_send(ripng, rp, ZEBRA_ROUTE_ADD); } /* Delete ECMP routes from zebra. */ void ripng_zebra_ipv6_delete(struct ripng *ripng, struct agg_node *rp) { ripng_zebra_ipv6_send(ripng, rp, ZEBRA_ROUTE_DELETE); } /* Zebra route add and delete treatment. */ static int ripng_zebra_read_route(ZAPI_CALLBACK_ARGS) { struct ripng *ripng; struct zapi_route api; struct in6_addr nexthop; unsigned long ifindex; ripng = ripng_lookup_by_vrf_id(vrf_id); if (!ripng) return 0; if (zapi_route_decode(zclient->ibuf, &api) < 0) return -1; /* we completely ignore srcdest routes for now. */ if (CHECK_FLAG(api.message, ZAPI_MESSAGE_SRCPFX)) return 0; if (IN6_IS_ADDR_LINKLOCAL(&api.prefix.u.prefix6)) return 0; nexthop = api.nexthops[0].gate.ipv6; ifindex = api.nexthops[0].ifindex; if (cmd == ZEBRA_REDISTRIBUTE_ROUTE_ADD) ripng_redistribute_add(ripng, api.type, RIPNG_ROUTE_REDISTRIBUTE, (struct prefix_ipv6 *)&api.prefix, ifindex, &nexthop, api.tag); else ripng_redistribute_delete( ripng, api.type, RIPNG_ROUTE_REDISTRIBUTE, (struct prefix_ipv6 *)&api.prefix, ifindex); return 0; } void ripng_redistribute_conf_update(struct ripng *ripng, int type) { zclient_redistribute(ZEBRA_REDISTRIBUTE_ADD, zclient, AFI_IP6, type, 0, ripng->vrf->vrf_id); } void ripng_redistribute_conf_delete(struct ripng *ripng, int type) { if (zclient->sock > 0) zebra_redistribute_send(ZEBRA_REDISTRIBUTE_DELETE, zclient, AFI_IP6, type, 0, ripng->vrf->vrf_id); ripng_redistribute_withdraw(ripng, type); } int ripng_redistribute_check(struct ripng *ripng, int type) { return ripng->redist[type].enabled; } void ripng_redistribute_enable(struct ripng *ripng) { for (int i = 0; i < ZEBRA_ROUTE_MAX; i++) { if (!ripng_redistribute_check(ripng, i)) continue; zebra_redistribute_send(ZEBRA_REDISTRIBUTE_ADD, zclient, AFI_IP6, i, 0, ripng->vrf->vrf_id); } } void ripng_redistribute_disable(struct ripng *ripng) { for (int i = 0; i < ZEBRA_ROUTE_MAX; i++) { if (!ripng_redistribute_check(ripng, i)) continue; zebra_redistribute_send(ZEBRA_REDISTRIBUTE_DELETE, zclient, AFI_IP6, i, 0, ripng->vrf->vrf_id); } } void ripng_redistribute_write(struct vty *vty, struct ripng *ripng) { int i; for (i = 0; i < ZEBRA_ROUTE_MAX; i++) { if (i == zclient->redist_default || !ripng_redistribute_check(ripng, i)) continue; vty_out(vty, " %s", zebra_route_string(i)); } } void ripng_zebra_vrf_register(struct vrf *vrf) { if (vrf->vrf_id == VRF_DEFAULT) return; if (IS_RIPNG_DEBUG_EVENT) zlog_debug("%s: register VRF %s(%u) to zebra", __func__, vrf->name, vrf->vrf_id); zclient_send_reg_requests(zclient, vrf->vrf_id); } void ripng_zebra_vrf_deregister(struct vrf *vrf) { if (vrf->vrf_id == VRF_DEFAULT) return; if (IS_RIPNG_DEBUG_EVENT) zlog_debug("%s: deregister VRF %s(%u) from zebra.", __func__, vrf->name, vrf->vrf_id); zclient_send_dereg_requests(zclient, vrf->vrf_id); } static void ripng_zebra_connected(struct zclient *zclient) { zclient_send_reg_requests(zclient, VRF_DEFAULT); } /* Initialize zebra structure and it's commands. */ void zebra_init(struct thread_master *master) { /* Allocate zebra structure. */ zclient = zclient_new(master, &zclient_options_default); zclient_init(zclient, ZEBRA_ROUTE_RIPNG, 0, &ripngd_privs); zclient->zebra_connected = ripng_zebra_connected; zclient->interface_up = ripng_interface_up; zclient->interface_down = ripng_interface_down; zclient->interface_add = ripng_interface_add; zclient->interface_delete = ripng_interface_delete; zclient->interface_address_add = ripng_interface_address_add; zclient->interface_address_delete = ripng_interface_address_delete; zclient->interface_vrf_update = ripng_interface_vrf_update; zclient->redistribute_route_add = ripng_zebra_read_route; zclient->redistribute_route_del = ripng_zebra_read_route; } void ripng_zebra_stop(void) { zclient_stop(zclient); zclient_free(zclient); } frr-7.2.1/ripngd/ripngd.c0000644000000000000000000022674413610377563012145 00000000000000/* RIPng daemon * Copyright (C) 1998, 1999 Kunihiro Ishiguro * * This file is part of GNU Zebra. * * GNU Zebra is free software; you can redistribute it and/or modify it * under the terms of the GNU General Public License as published by the * Free Software Foundation; either version 2, or (at your option) any * later version. * * GNU Zebra 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; see the file COPYING; if not, write to the Free Software * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA */ #include #include "prefix.h" #include "filter.h" #include "log.h" #include "thread.h" #include "memory.h" #include "if.h" #include "stream.h" #include "agg_table.h" #include "command.h" #include "sockopt.h" #include "distribute.h" #include "plist.h" #include "routemap.h" #include "if_rmap.h" #include "privs.h" #include "lib_errors.h" #include "northbound_cli.h" #include "ripngd/ripngd.h" #include "ripngd/ripng_route.h" #include "ripngd/ripng_debug.h" #include "ripngd/ripng_nexthop.h" DEFINE_MGROUP(RIPNGD, "ripngd") DEFINE_MTYPE_STATIC(RIPNGD, RIPNG, "RIPng structure") DEFINE_MTYPE_STATIC(RIPNGD, RIPNG_VRF_NAME, "RIPng VRF name") DEFINE_MTYPE_STATIC(RIPNGD, RIPNG_ROUTE, "RIPng route info") enum { ripng_all_route, ripng_changed_route, }; static void ripng_distribute_update(struct distribute_ctx *ctx, struct distribute *dist); /* Prototypes. */ void ripng_output_process(struct interface *, struct sockaddr_in6 *, int); static void ripng_instance_enable(struct ripng *ripng, struct vrf *vrf, int sock); static void ripng_instance_disable(struct ripng *ripng); int ripng_triggered_update(struct thread *); static void ripng_if_rmap_update(struct if_rmap_ctx *ctx, struct if_rmap *if_rmap); /* Generate rb-tree of RIPng instances. */ static inline int ripng_instance_compare(const struct ripng *a, const struct ripng *b) { return strcmp(a->vrf_name, b->vrf_name); } RB_GENERATE(ripng_instance_head, ripng, entry, ripng_instance_compare) struct ripng_instance_head ripng_instances = RB_INITIALIZER(&ripng_instances); /* RIPng next hop specification. */ struct ripng_nexthop { enum ripng_nexthop_type { RIPNG_NEXTHOP_UNSPEC, RIPNG_NEXTHOP_ADDRESS } flag; struct in6_addr address; }; int ripng_route_rte(struct ripng_info *rinfo) { return (rinfo->type == ZEBRA_ROUTE_RIPNG && rinfo->sub_type == RIPNG_ROUTE_RTE); } /* Allocate new ripng information. */ struct ripng_info *ripng_info_new(void) { struct ripng_info *new; new = XCALLOC(MTYPE_RIPNG_ROUTE, sizeof(struct ripng_info)); return new; } /* Free ripng information. */ void ripng_info_free(struct ripng_info *rinfo) { XFREE(MTYPE_RIPNG_ROUTE, rinfo); } struct ripng *ripng_info_get_instance(const struct ripng_info *rinfo) { return agg_get_table_info(agg_get_table(rinfo->rp)); } /* Create ripng socket. */ int ripng_make_socket(struct vrf *vrf) { int ret; int sock; struct sockaddr_in6 ripaddr; const char *vrf_dev = NULL; /* Make datagram socket. */ if (vrf->vrf_id != VRF_DEFAULT) vrf_dev = vrf->name; frr_with_privs(&ripngd_privs) { sock = vrf_socket(AF_INET6, SOCK_DGRAM, IPPROTO_UDP, vrf->vrf_id, vrf_dev); if (sock < 0) { flog_err_sys(EC_LIB_SOCKET, "Cannot create UDP socket: %s", safe_strerror(errno)); return -1; } } sockopt_reuseaddr(sock); sockopt_reuseport(sock); setsockopt_so_recvbuf(sock, 8096); ret = setsockopt_ipv6_pktinfo(sock, 1); if (ret < 0) goto error; #ifdef IPTOS_PREC_INTERNETCONTROL ret = setsockopt_ipv6_tclass(sock, IPTOS_PREC_INTERNETCONTROL); if (ret < 0) goto error; #endif ret = setsockopt_ipv6_multicast_hops(sock, 255); if (ret < 0) goto error; ret = setsockopt_ipv6_multicast_loop(sock, 0); if (ret < 0) goto error; ret = setsockopt_ipv6_hoplimit(sock, 1); if (ret < 0) goto error; memset(&ripaddr, 0, sizeof(ripaddr)); ripaddr.sin6_family = AF_INET6; #ifdef SIN6_LEN ripaddr.sin6_len = sizeof(struct sockaddr_in6); #endif /* SIN6_LEN */ ripaddr.sin6_port = htons(RIPNG_PORT_DEFAULT); frr_with_privs(&ripngd_privs) { ret = bind(sock, (struct sockaddr *)&ripaddr, sizeof(ripaddr)); if (ret < 0) { zlog_err("Can't bind ripng socket: %s.", safe_strerror(errno)); goto error; } } return sock; error: close(sock); return ret; } /* Send RIPng packet. */ int ripng_send_packet(caddr_t buf, int bufsize, struct sockaddr_in6 *to, struct interface *ifp) { struct ripng_interface *ri = ifp->info; struct ripng *ripng = ri->ripng; int ret; struct msghdr msg; struct iovec iov; struct cmsghdr *cmsgptr; char adata[256] = {}; struct in6_pktinfo *pkt; struct sockaddr_in6 addr; if (IS_RIPNG_DEBUG_SEND) { if (to) zlog_debug("send to %s", inet6_ntoa(to->sin6_addr)); zlog_debug(" send interface %s", ifp->name); zlog_debug(" send packet size %d", bufsize); } memset(&addr, 0, sizeof(struct sockaddr_in6)); addr.sin6_family = AF_INET6; #ifdef SIN6_LEN addr.sin6_len = sizeof(struct sockaddr_in6); #endif /* SIN6_LEN */ addr.sin6_flowinfo = htonl(RIPNG_PRIORITY_DEFAULT); /* When destination is specified. */ if (to != NULL) { addr.sin6_addr = to->sin6_addr; addr.sin6_port = to->sin6_port; } else { inet_pton(AF_INET6, RIPNG_GROUP, &addr.sin6_addr); addr.sin6_port = htons(RIPNG_PORT_DEFAULT); } memset(&msg, 0, sizeof(msg)); msg.msg_name = (void *)&addr; msg.msg_namelen = sizeof(struct sockaddr_in6); msg.msg_iov = &iov; msg.msg_iovlen = 1; msg.msg_control = (void *)adata; msg.msg_controllen = CMSG_SPACE(sizeof(struct in6_pktinfo)); iov.iov_base = buf; iov.iov_len = bufsize; cmsgptr = (struct cmsghdr *)adata; cmsgptr->cmsg_len = CMSG_LEN(sizeof(struct in6_pktinfo)); cmsgptr->cmsg_level = IPPROTO_IPV6; cmsgptr->cmsg_type = IPV6_PKTINFO; pkt = (struct in6_pktinfo *)CMSG_DATA(cmsgptr); memset(&pkt->ipi6_addr, 0, sizeof(struct in6_addr)); pkt->ipi6_ifindex = ifp->ifindex; ret = sendmsg(ripng->sock, &msg, 0); if (ret < 0) { if (to) flog_err_sys(EC_LIB_SOCKET, "RIPng send fail on %s to %s: %s", ifp->name, inet6_ntoa(to->sin6_addr), safe_strerror(errno)); else flog_err_sys(EC_LIB_SOCKET, "RIPng send fail on %s: %s", ifp->name, safe_strerror(errno)); } return ret; } /* Receive UDP RIPng packet from socket. */ static int ripng_recv_packet(int sock, uint8_t *buf, int bufsize, struct sockaddr_in6 *from, ifindex_t *ifindex, int *hoplimit) { int ret; struct msghdr msg; struct iovec iov; struct cmsghdr *cmsgptr; struct in6_addr dst = {.s6_addr = {0}}; memset(&dst, 0, sizeof(struct in6_addr)); /* Ancillary data. This store cmsghdr and in6_pktinfo. But at this point I can't determine size of cmsghdr */ char adata[1024]; /* Fill in message and iovec. */ memset(&msg, 0, sizeof(msg)); msg.msg_name = (void *)from; msg.msg_namelen = sizeof(struct sockaddr_in6); msg.msg_iov = &iov; msg.msg_iovlen = 1; msg.msg_control = (void *)adata; msg.msg_controllen = sizeof adata; iov.iov_base = buf; iov.iov_len = bufsize; /* If recvmsg fail return minus value. */ ret = recvmsg(sock, &msg, 0); if (ret < 0) return ret; for (cmsgptr = CMSG_FIRSTHDR(&msg); cmsgptr != NULL; cmsgptr = CMSG_NXTHDR(&msg, cmsgptr)) { /* I want interface index which this packet comes from. */ if (cmsgptr->cmsg_level == IPPROTO_IPV6 && cmsgptr->cmsg_type == IPV6_PKTINFO) { struct in6_pktinfo *ptr; ptr = (struct in6_pktinfo *)CMSG_DATA(cmsgptr); *ifindex = ptr->ipi6_ifindex; dst = ptr->ipi6_addr; if (*ifindex == 0) zlog_warn( "Interface index returned by IPV6_PKTINFO is zero"); } /* Incoming packet's multicast hop limit. */ if (cmsgptr->cmsg_level == IPPROTO_IPV6 && cmsgptr->cmsg_type == IPV6_HOPLIMIT) { int *phoplimit = (int *)CMSG_DATA(cmsgptr); *hoplimit = *phoplimit; } } /* Hoplimit check shold be done when destination address is multicast address. */ if (!IN6_IS_ADDR_MULTICAST(&dst)) *hoplimit = -1; return ret; } /* Dump rip packet */ void ripng_packet_dump(struct ripng_packet *packet, int size, const char *sndrcv) { caddr_t lim; struct rte *rte; const char *command_str; /* Set command string. */ if (packet->command == RIPNG_REQUEST) command_str = "request"; else if (packet->command == RIPNG_RESPONSE) command_str = "response"; else command_str = "unknown"; /* Dump packet header. */ zlog_debug("%s %s version %d packet size %d", sndrcv, command_str, packet->version, size); /* Dump each routing table entry. */ rte = packet->rte; for (lim = (caddr_t)packet + size; (caddr_t)rte < lim; rte++) { if (rte->metric == RIPNG_METRIC_NEXTHOP) zlog_debug(" nexthop %s/%d", inet6_ntoa(rte->addr), rte->prefixlen); else zlog_debug(" %s/%d metric %d tag %" ROUTE_TAG_PRI, inet6_ntoa(rte->addr), rte->prefixlen, rte->metric, (route_tag_t)ntohs(rte->tag)); } } /* RIPng next hop address RTE (Route Table Entry). */ static void ripng_nexthop_rte(struct rte *rte, struct sockaddr_in6 *from, struct ripng_nexthop *nexthop) { char buf[INET6_BUFSIZ]; /* Logging before checking RTE. */ if (IS_RIPNG_DEBUG_RECV) zlog_debug("RIPng nexthop RTE address %s tag %" ROUTE_TAG_PRI " prefixlen %d", inet6_ntoa(rte->addr), (route_tag_t)ntohs(rte->tag), rte->prefixlen); /* RFC2080 2.1.1 Next Hop: The route tag and prefix length in the next hop RTE must be set to zero on sending and ignored on receiption. */ if (ntohs(rte->tag) != 0) zlog_warn( "RIPng nexthop RTE with non zero tag value %" ROUTE_TAG_PRI " from %s", (route_tag_t)ntohs(rte->tag), inet6_ntoa(from->sin6_addr)); if (rte->prefixlen != 0) zlog_warn( "RIPng nexthop RTE with non zero prefixlen value %d from %s", rte->prefixlen, inet6_ntoa(from->sin6_addr)); /* Specifying a value of 0:0:0:0:0:0:0:0 in the prefix field of a next hop RTE indicates that the next hop address should be the originator of the RIPng advertisement. An address specified as a next hop must be a link-local address. */ if (IN6_IS_ADDR_UNSPECIFIED(&rte->addr)) { nexthop->flag = RIPNG_NEXTHOP_UNSPEC; memset(&nexthop->address, 0, sizeof(struct in6_addr)); return; } if (IN6_IS_ADDR_LINKLOCAL(&rte->addr)) { nexthop->flag = RIPNG_NEXTHOP_ADDRESS; IPV6_ADDR_COPY(&nexthop->address, &rte->addr); return; } /* The purpose of the next hop RTE is to eliminate packets being routed through extra hops in the system. It is particularly useful when RIPng is not being run on all of the routers on a network. Note that next hop RTE is "advisory". That is, if the provided information is ignored, a possibly sub-optimal, but absolutely valid, route may be taken. If the received next hop address is not a link-local address, it should be treated as 0:0:0:0:0:0:0:0. */ zlog_warn("RIPng nexthop RTE with non link-local address %s from %s", inet6_ntoa(rte->addr), inet_ntop(AF_INET6, &from->sin6_addr, buf, INET6_BUFSIZ)); nexthop->flag = RIPNG_NEXTHOP_UNSPEC; memset(&nexthop->address, 0, sizeof(struct in6_addr)); return; } /* If ifp has same link-local address then return 1. */ static int ripng_lladdr_check(struct interface *ifp, struct in6_addr *addr) { struct listnode *node; struct connected *connected; struct prefix *p; for (ALL_LIST_ELEMENTS_RO(ifp->connected, node, connected)) { p = connected->address; if (p->family == AF_INET6 && IN6_IS_ADDR_LINKLOCAL(&p->u.prefix6) && IN6_ARE_ADDR_EQUAL(&p->u.prefix6, addr)) return 1; } return 0; } /* RIPng route garbage collect timer. */ static int ripng_garbage_collect(struct thread *t) { struct ripng_info *rinfo; struct agg_node *rp; rinfo = THREAD_ARG(t); rinfo->t_garbage_collect = NULL; /* Off timeout timer. */ RIPNG_TIMER_OFF(rinfo->t_timeout); /* Get route_node pointer. */ rp = rinfo->rp; /* Unlock route_node. */ listnode_delete(rp->info, rinfo); if (list_isempty((struct list *)rp->info)) { list_delete((struct list **)&rp->info); agg_unlock_node(rp); } /* Free RIPng routing information. */ ripng_info_free(rinfo); return 0; } static void ripng_timeout_update(struct ripng *ripng, struct ripng_info *rinfo); /* Add new route to the ECMP list. * RETURN: the new entry added in the list, or NULL if it is not the first * entry and ECMP is not allowed. */ struct ripng_info *ripng_ecmp_add(struct ripng *ripng, struct ripng_info *rinfo_new) { struct agg_node *rp = rinfo_new->rp; struct ripng_info *rinfo = NULL; struct list *list = NULL; if (rp->info == NULL) rp->info = list_new(); list = (struct list *)rp->info; /* If ECMP is not allowed and some entry already exists in the list, * do nothing. */ if (listcount(list) && !ripng->ecmp) return NULL; rinfo = ripng_info_new(); memcpy(rinfo, rinfo_new, sizeof(struct ripng_info)); listnode_add(list, rinfo); if (ripng_route_rte(rinfo)) { ripng_timeout_update(ripng, rinfo); ripng_zebra_ipv6_add(ripng, rp); } ripng_aggregate_increment(rp, rinfo); /* Set the route change flag on the first entry. */ rinfo = listgetdata(listhead(list)); SET_FLAG(rinfo->flags, RIPNG_RTF_CHANGED); /* Signal the output process to trigger an update. */ ripng_event(ripng, RIPNG_TRIGGERED_UPDATE, 0); return rinfo; } /* Replace the ECMP list with the new route. * RETURN: the new entry added in the list */ struct ripng_info *ripng_ecmp_replace(struct ripng *ripng, struct ripng_info *rinfo_new) { struct agg_node *rp = rinfo_new->rp; struct list *list = (struct list *)rp->info; struct ripng_info *rinfo = NULL, *tmp_rinfo = NULL; struct listnode *node = NULL, *nextnode = NULL; if (list == NULL || listcount(list) == 0) return ripng_ecmp_add(ripng, rinfo_new); /* Get the first entry */ rinfo = listgetdata(listhead(list)); /* Learnt route replaced by a local one. Delete it from zebra. */ if (ripng_route_rte(rinfo) && !ripng_route_rte(rinfo_new)) if (CHECK_FLAG(rinfo->flags, RIPNG_RTF_FIB)) ripng_zebra_ipv6_delete(ripng, rp); if (rinfo->metric != RIPNG_METRIC_INFINITY) ripng_aggregate_decrement_list(rp, list); /* Re-use the first entry, and delete the others. */ for (ALL_LIST_ELEMENTS(list, node, nextnode, tmp_rinfo)) if (tmp_rinfo != rinfo) { RIPNG_TIMER_OFF(tmp_rinfo->t_timeout); RIPNG_TIMER_OFF(tmp_rinfo->t_garbage_collect); list_delete_node(list, node); ripng_info_free(tmp_rinfo); } RIPNG_TIMER_OFF(rinfo->t_timeout); RIPNG_TIMER_OFF(rinfo->t_garbage_collect); memcpy(rinfo, rinfo_new, sizeof(struct ripng_info)); if (ripng_route_rte(rinfo)) { ripng_timeout_update(ripng, rinfo); /* The ADD message implies an update. */ ripng_zebra_ipv6_add(ripng, rp); } ripng_aggregate_increment(rp, rinfo); /* Set the route change flag. */ SET_FLAG(rinfo->flags, RIPNG_RTF_CHANGED); /* Signal the output process to trigger an update. */ ripng_event(ripng, RIPNG_TRIGGERED_UPDATE, 0); return rinfo; } /* Delete one route from the ECMP list. * RETURN: * null - the entry is freed, and other entries exist in the list * the entry - the entry is the last one in the list; its metric is set * to INFINITY, and the garbage collector is started for it */ struct ripng_info *ripng_ecmp_delete(struct ripng *ripng, struct ripng_info *rinfo) { struct agg_node *rp = rinfo->rp; struct list *list = (struct list *)rp->info; RIPNG_TIMER_OFF(rinfo->t_timeout); if (rinfo->metric != RIPNG_METRIC_INFINITY) ripng_aggregate_decrement(rp, rinfo); if (listcount(list) > 1) { /* Some other ECMP entries still exist. Just delete this entry. */ RIPNG_TIMER_OFF(rinfo->t_garbage_collect); listnode_delete(list, rinfo); if (ripng_route_rte(rinfo) && CHECK_FLAG(rinfo->flags, RIPNG_RTF_FIB)) /* The ADD message implies the update. */ ripng_zebra_ipv6_add(ripng, rp); ripng_info_free(rinfo); rinfo = NULL; } else { assert(rinfo == listgetdata(listhead(list))); /* This is the only entry left in the list. We must keep it in * the list for garbage collection time, with INFINITY metric. */ rinfo->metric = RIPNG_METRIC_INFINITY; RIPNG_TIMER_ON(rinfo->t_garbage_collect, ripng_garbage_collect, ripng->garbage_time); if (ripng_route_rte(rinfo) && CHECK_FLAG(rinfo->flags, RIPNG_RTF_FIB)) ripng_zebra_ipv6_delete(ripng, rp); } /* Set the route change flag on the first entry. */ rinfo = listgetdata(listhead(list)); SET_FLAG(rinfo->flags, RIPNG_RTF_CHANGED); /* Signal the output process to trigger an update. */ ripng_event(ripng, RIPNG_TRIGGERED_UPDATE, 0); return rinfo; } /* Timeout RIPng routes. */ static int ripng_timeout(struct thread *t) { struct ripng_info *rinfo = THREAD_ARG(t); struct ripng *ripng = ripng_info_get_instance(rinfo); ripng_ecmp_delete(ripng, rinfo); return 0; } static void ripng_timeout_update(struct ripng *ripng, struct ripng_info *rinfo) { if (rinfo->metric != RIPNG_METRIC_INFINITY) { RIPNG_TIMER_OFF(rinfo->t_timeout); thread_add_timer(master, ripng_timeout, rinfo, ripng->timeout_time, &rinfo->t_timeout); } } static int ripng_filter(int ripng_distribute, struct prefix_ipv6 *p, struct ripng_interface *ri) { struct distribute *dist; struct access_list *alist; struct prefix_list *plist; int distribute = ripng_distribute == RIPNG_FILTER_OUT ? DISTRIBUTE_V6_OUT : DISTRIBUTE_V6_IN; const char *inout = ripng_distribute == RIPNG_FILTER_OUT ? "out" : "in"; /* Input distribute-list filtering. */ if (ri->list[ripng_distribute]) { if (access_list_apply(ri->list[ripng_distribute], (struct prefix *)p) == FILTER_DENY) { if (IS_RIPNG_DEBUG_PACKET) zlog_debug("%s/%d filtered by distribute %s", inet6_ntoa(p->prefix), p->prefixlen, inout); return -1; } } if (ri->prefix[ripng_distribute]) { if (prefix_list_apply(ri->prefix[ripng_distribute], (struct prefix *)p) == PREFIX_DENY) { if (IS_RIPNG_DEBUG_PACKET) zlog_debug("%s/%d filtered by prefix-list %s", inet6_ntoa(p->prefix), p->prefixlen, inout); return -1; } } /* All interface filter check. */ dist = distribute_lookup(ri->ripng->distribute_ctx, NULL); if (dist) { if (dist->list[distribute]) { alist = access_list_lookup(AFI_IP6, dist->list[distribute]); if (alist) { if (access_list_apply(alist, (struct prefix *)p) == FILTER_DENY) { if (IS_RIPNG_DEBUG_PACKET) zlog_debug( "%s/%d filtered by distribute %s", inet6_ntoa(p->prefix), p->prefixlen, inout); return -1; } } } if (dist->prefix[distribute]) { plist = prefix_list_lookup(AFI_IP6, dist->prefix[distribute]); if (plist) { if (prefix_list_apply(plist, (struct prefix *)p) == PREFIX_DENY) { if (IS_RIPNG_DEBUG_PACKET) zlog_debug( "%s/%d filtered by prefix-list %s", inet6_ntoa(p->prefix), p->prefixlen, inout); return -1; } } } } return 0; } /* Process RIPng route according to RFC2080. */ static void ripng_route_process(struct rte *rte, struct sockaddr_in6 *from, struct ripng_nexthop *ripng_nexthop, struct interface *ifp) { int ret; struct prefix_ipv6 p; struct agg_node *rp; struct ripng_info *rinfo = NULL, newinfo; struct ripng_interface *ri; struct ripng *ripng; struct in6_addr *nexthop; int same = 0; struct list *list = NULL; struct listnode *node = NULL; /* Make prefix structure. */ memset(&p, 0, sizeof(struct prefix_ipv6)); p.family = AF_INET6; /* p.prefix = rte->addr; */ IPV6_ADDR_COPY(&p.prefix, &rte->addr); p.prefixlen = rte->prefixlen; /* Make sure mask is applied. */ /* XXX We have to check the prefix is valid or not before call apply_mask_ipv6. */ apply_mask_ipv6(&p); ri = ifp->info; ripng = ri->ripng; /* Apply input filters. */ ret = ripng_filter(RIPNG_FILTER_IN, &p, ri); if (ret < 0) return; memset(&newinfo, 0, sizeof(newinfo)); newinfo.type = ZEBRA_ROUTE_RIPNG; newinfo.sub_type = RIPNG_ROUTE_RTE; if (ripng_nexthop->flag == RIPNG_NEXTHOP_ADDRESS) newinfo.nexthop = ripng_nexthop->address; else newinfo.nexthop = from->sin6_addr; newinfo.from = from->sin6_addr; newinfo.ifindex = ifp->ifindex; newinfo.metric = rte->metric; newinfo.metric_out = rte->metric; /* XXX */ newinfo.tag = ntohs(rte->tag); /* XXX */ /* Modify entry. */ if (ri->routemap[RIPNG_FILTER_IN]) { ret = route_map_apply(ri->routemap[RIPNG_FILTER_IN], (struct prefix *)&p, RMAP_RIPNG, &newinfo); if (ret == RMAP_DENYMATCH) { if (IS_RIPNG_DEBUG_PACKET) zlog_debug( "RIPng %s/%d is filtered by route-map in", inet6_ntoa(p.prefix), p.prefixlen); return; } /* Get back the object */ if (ripng_nexthop->flag == RIPNG_NEXTHOP_ADDRESS) { if (!IPV6_ADDR_SAME(&newinfo.nexthop, &ripng_nexthop->address)) { /* the nexthop get changed by the routemap */ if (IN6_IS_ADDR_LINKLOCAL(&newinfo.nexthop)) ripng_nexthop->address = newinfo.nexthop; else ripng_nexthop->address = in6addr_any; } } else { if (!IPV6_ADDR_SAME(&newinfo.nexthop, &from->sin6_addr)) { /* the nexthop get changed by the routemap */ if (IN6_IS_ADDR_LINKLOCAL(&newinfo.nexthop)) { ripng_nexthop->flag = RIPNG_NEXTHOP_ADDRESS; ripng_nexthop->address = newinfo.nexthop; } } } rte->tag = htons(newinfo.tag_out); /* XXX */ rte->metric = newinfo.metric_out; /* XXX: the routemap uses the metric_out field */ } /* Once the entry has been validated, update the metric by * adding the cost of the network on wich the message * arrived. If the result is greater than infinity, use infinity * (RFC2453 Sec. 3.9.2) **/ /* Zebra ripngd can handle offset-list in. */ ret = ripng_offset_list_apply_in(ripng, &p, ifp, &rte->metric); /* If offset-list does not modify the metric use interface's * one. */ if (!ret) rte->metric += ifp->metric ? ifp->metric : 1; if (rte->metric > RIPNG_METRIC_INFINITY) rte->metric = RIPNG_METRIC_INFINITY; /* Set nexthop pointer. */ if (ripng_nexthop->flag == RIPNG_NEXTHOP_ADDRESS) nexthop = &ripng_nexthop->address; else nexthop = &from->sin6_addr; /* Lookup RIPng routing table. */ rp = agg_node_get(ripng->table, (struct prefix *)&p); newinfo.rp = rp; newinfo.nexthop = *nexthop; newinfo.metric = rte->metric; newinfo.tag = ntohs(rte->tag); /* Check to see whether there is already RIPng route on the table. */ if ((list = rp->info) != NULL) for (ALL_LIST_ELEMENTS_RO(list, node, rinfo)) { /* Need to compare with redistributed entry or local * entry */ if (!ripng_route_rte(rinfo)) break; if (IPV6_ADDR_SAME(&rinfo->from, &from->sin6_addr) && IPV6_ADDR_SAME(&rinfo->nexthop, nexthop)) break; if (!listnextnode(node)) { /* Not found in the list */ if (rte->metric > rinfo->metric) { /* New route has a greater metric. * Discard it. */ agg_unlock_node(rp); return; } if (rte->metric < rinfo->metric) /* New route has a smaller metric. * Replace the ECMP list * with the new one in below. */ break; /* Metrics are same. Unless ECMP is disabled, * keep "rinfo" null and * the new route is added in the ECMP list in * below. */ if (!ripng->ecmp) break; } } if (rinfo) { /* Redistributed route check. */ if (rinfo->type != ZEBRA_ROUTE_RIPNG && rinfo->metric != RIPNG_METRIC_INFINITY) { agg_unlock_node(rp); return; } /* Local static route. */ if (rinfo->type == ZEBRA_ROUTE_RIPNG && ((rinfo->sub_type == RIPNG_ROUTE_STATIC) || (rinfo->sub_type == RIPNG_ROUTE_DEFAULT)) && rinfo->metric != RIPNG_METRIC_INFINITY) { agg_unlock_node(rp); return; } } if (!rinfo) { /* Now, check to see whether there is already an explicit route for the destination prefix. If there is no such route, add this route to the routing table, unless the metric is infinity (there is no point in adding a route which unusable). */ if (rte->metric != RIPNG_METRIC_INFINITY) ripng_ecmp_add(ripng, &newinfo); else agg_unlock_node(rp); } else { /* If there is an existing route, compare the next hop address to the address of the router from which the datagram came. If this datagram is from the same router as the existing route, reinitialize the timeout. */ same = (IN6_ARE_ADDR_EQUAL(&rinfo->from, &from->sin6_addr) && (rinfo->ifindex == ifp->ifindex)); /* * RFC 2080 - Section 2.4.2: * "If the new metric is the same as the old one, examine the * timeout * for the existing route. If it is at least halfway to the * expiration * point, switch to the new route. This heuristic is optional, * but * highly recommended". */ if (!ripng->ecmp && !same && rinfo->metric == rte->metric && rinfo->t_timeout && (thread_timer_remain_second(rinfo->t_timeout) < (ripng->timeout_time / 2))) { ripng_ecmp_replace(ripng, &newinfo); } /* Next, compare the metrics. If the datagram is from the same router as the existing route, and the new metric is different than the old one; or, if the new metric is lower than the old one; do the following actions: */ else if ((same && rinfo->metric != rte->metric) || rte->metric < rinfo->metric) { if (listcount(list) == 1) { if (newinfo.metric != RIPNG_METRIC_INFINITY) ripng_ecmp_replace(ripng, &newinfo); else ripng_ecmp_delete(ripng, rinfo); } else { if (newinfo.metric < rinfo->metric) ripng_ecmp_replace(ripng, &newinfo); else /* newinfo.metric > rinfo->metric */ ripng_ecmp_delete(ripng, rinfo); } } else /* same & no change */ ripng_timeout_update(ripng, rinfo); /* Unlock tempolary lock of the route. */ agg_unlock_node(rp); } } /* Add redistributed route to RIPng table. */ void ripng_redistribute_add(struct ripng *ripng, int type, int sub_type, struct prefix_ipv6 *p, ifindex_t ifindex, struct in6_addr *nexthop, route_tag_t tag) { struct agg_node *rp; struct ripng_info *rinfo = NULL, newinfo; struct list *list = NULL; /* Redistribute route */ if (IN6_IS_ADDR_LINKLOCAL(&p->prefix)) return; if (IN6_IS_ADDR_LOOPBACK(&p->prefix)) return; rp = agg_node_get(ripng->table, (struct prefix *)p); memset(&newinfo, 0, sizeof(struct ripng_info)); newinfo.type = type; newinfo.sub_type = sub_type; newinfo.ifindex = ifindex; newinfo.metric = 1; if (tag <= UINT16_MAX) /* RIPng only supports 16 bit tags */ newinfo.tag = tag; newinfo.rp = rp; if (nexthop && IN6_IS_ADDR_LINKLOCAL(nexthop)) newinfo.nexthop = *nexthop; if ((list = rp->info) != NULL && listcount(list) != 0) { rinfo = listgetdata(listhead(list)); if (rinfo->type == ZEBRA_ROUTE_CONNECT && rinfo->sub_type == RIPNG_ROUTE_INTERFACE && rinfo->metric != RIPNG_METRIC_INFINITY) { agg_unlock_node(rp); return; } /* Manually configured RIPng route check. * They have the precedence on all the other entries. **/ if (rinfo->type == ZEBRA_ROUTE_RIPNG && ((rinfo->sub_type == RIPNG_ROUTE_STATIC) || (rinfo->sub_type == RIPNG_ROUTE_DEFAULT))) { if (type != ZEBRA_ROUTE_RIPNG || ((sub_type != RIPNG_ROUTE_STATIC) && (sub_type != RIPNG_ROUTE_DEFAULT))) { agg_unlock_node(rp); return; } } ripng_ecmp_replace(ripng, &newinfo); agg_unlock_node(rp); } else ripng_ecmp_add(ripng, &newinfo); if (IS_RIPNG_DEBUG_EVENT) { if (!nexthop) zlog_debug( "Redistribute new prefix %s/%d on the interface %s", inet6_ntoa(p->prefix), p->prefixlen, ifindex2ifname(ifindex, ripng->vrf->vrf_id)); else zlog_debug( "Redistribute new prefix %s/%d with nexthop %s on the interface %s", inet6_ntoa(p->prefix), p->prefixlen, inet6_ntoa(*nexthop), ifindex2ifname(ifindex, ripng->vrf->vrf_id)); } ripng_event(ripng, RIPNG_TRIGGERED_UPDATE, 0); } /* Delete redistributed route to RIPng table. */ void ripng_redistribute_delete(struct ripng *ripng, int type, int sub_type, struct prefix_ipv6 *p, ifindex_t ifindex) { struct agg_node *rp; struct ripng_info *rinfo; if (IN6_IS_ADDR_LINKLOCAL(&p->prefix)) return; if (IN6_IS_ADDR_LOOPBACK(&p->prefix)) return; rp = agg_node_lookup(ripng->table, (struct prefix *)p); if (rp) { struct list *list = rp->info; if (list != NULL && listcount(list) != 0) { rinfo = listgetdata(listhead(list)); if (rinfo != NULL && rinfo->type == type && rinfo->sub_type == sub_type && rinfo->ifindex == ifindex) { /* Perform poisoned reverse. */ rinfo->metric = RIPNG_METRIC_INFINITY; RIPNG_TIMER_ON(rinfo->t_garbage_collect, ripng_garbage_collect, ripng->garbage_time); RIPNG_TIMER_OFF(rinfo->t_timeout); /* Aggregate count decrement. */ ripng_aggregate_decrement(rp, rinfo); rinfo->flags |= RIPNG_RTF_CHANGED; if (IS_RIPNG_DEBUG_EVENT) zlog_debug( "Poisone %s/%d on the interface %s with an " "infinity metric [delete]", inet6_ntoa(p->prefix), p->prefixlen, ifindex2ifname( ifindex, ripng->vrf->vrf_id)); ripng_event(ripng, RIPNG_TRIGGERED_UPDATE, 0); } } agg_unlock_node(rp); } } /* Withdraw redistributed route. */ void ripng_redistribute_withdraw(struct ripng *ripng, int type) { struct agg_node *rp; struct ripng_info *rinfo = NULL; struct list *list = NULL; for (rp = agg_route_top(ripng->table); rp; rp = agg_route_next(rp)) if ((list = rp->info) != NULL) { rinfo = listgetdata(listhead(list)); if ((rinfo->type == type) && (rinfo->sub_type != RIPNG_ROUTE_INTERFACE)) { /* Perform poisoned reverse. */ rinfo->metric = RIPNG_METRIC_INFINITY; RIPNG_TIMER_ON(rinfo->t_garbage_collect, ripng_garbage_collect, ripng->garbage_time); RIPNG_TIMER_OFF(rinfo->t_timeout); /* Aggregate count decrement. */ ripng_aggregate_decrement(rp, rinfo); rinfo->flags |= RIPNG_RTF_CHANGED; if (IS_RIPNG_DEBUG_EVENT) { struct prefix_ipv6 *p = (struct prefix_ipv6 *)&rp->p; zlog_debug( "Poisone %s/%d on the interface %s [withdraw]", inet6_ntoa(p->prefix), p->prefixlen, ifindex2ifname( rinfo->ifindex, ripng->vrf->vrf_id)); } ripng_event(ripng, RIPNG_TRIGGERED_UPDATE, 0); } } } /* RIP routing information. */ static void ripng_response_process(struct ripng_packet *packet, int size, struct sockaddr_in6 *from, struct interface *ifp, int hoplimit) { struct ripng_interface *ri = ifp->info; struct ripng *ripng = ri->ripng; caddr_t lim; struct rte *rte; struct ripng_nexthop nexthop; /* RFC2080 2.4.2 Response Messages: The Response must be ignored if it is not from the RIPng port. */ if (ntohs(from->sin6_port) != RIPNG_PORT_DEFAULT) { zlog_warn("RIPng packet comes from non RIPng port %d from %s", ntohs(from->sin6_port), inet6_ntoa(from->sin6_addr)); ripng_peer_bad_packet(ripng, from); return; } /* The datagram's IPv6 source address should be checked to see whether the datagram is from a valid neighbor; the source of the datagram must be a link-local address. */ if (!IN6_IS_ADDR_LINKLOCAL(&from->sin6_addr)) { zlog_warn("RIPng packet comes from non link local address %s", inet6_ntoa(from->sin6_addr)); ripng_peer_bad_packet(ripng, from); return; } /* It is also worth checking to see whether the response is from one of the router's own addresses. Interfaces on broadcast networks may receive copies of their own multicasts immediately. If a router processes its own output as new input, confusion is likely, and such datagrams must be ignored. */ if (ripng_lladdr_check(ifp, &from->sin6_addr)) { zlog_warn( "RIPng packet comes from my own link local address %s", inet6_ntoa(from->sin6_addr)); ripng_peer_bad_packet(ripng, from); return; } /* As an additional check, periodic advertisements must have their hop counts set to 255, and inbound, multicast packets sent from the RIPng port (i.e. periodic advertisement or triggered update packets) must be examined to ensure that the hop count is 255. */ if (hoplimit >= 0 && hoplimit != 255) { zlog_warn( "RIPng packet comes with non 255 hop count %d from %s", hoplimit, inet6_ntoa(from->sin6_addr)); ripng_peer_bad_packet(ripng, from); return; } /* Update RIPng peer. */ ripng_peer_update(ripng, from, packet->version); /* Reset nexthop. */ memset(&nexthop, 0, sizeof(struct ripng_nexthop)); nexthop.flag = RIPNG_NEXTHOP_UNSPEC; /* Set RTE pointer. */ rte = packet->rte; for (lim = ((caddr_t)packet) + size; (caddr_t)rte < lim; rte++) { /* First of all, we have to check this RTE is next hop RTE or not. Next hop RTE is completely different with normal RTE so we need special treatment. */ if (rte->metric == RIPNG_METRIC_NEXTHOP) { ripng_nexthop_rte(rte, from, &nexthop); continue; } /* RTE information validation. */ /* - is the destination prefix valid (e.g., not a multicast prefix and not a link-local address) A link-local address should never be present in an RTE. */ if (IN6_IS_ADDR_MULTICAST(&rte->addr)) { zlog_warn( "Destination prefix is a multicast address %s/%d [%d]", inet6_ntoa(rte->addr), rte->prefixlen, rte->metric); ripng_peer_bad_route(ripng, from); continue; } if (IN6_IS_ADDR_LINKLOCAL(&rte->addr)) { zlog_warn( "Destination prefix is a link-local address %s/%d [%d]", inet6_ntoa(rte->addr), rte->prefixlen, rte->metric); ripng_peer_bad_route(ripng, from); continue; } if (IN6_IS_ADDR_LOOPBACK(&rte->addr)) { zlog_warn( "Destination prefix is a loopback address %s/%d [%d]", inet6_ntoa(rte->addr), rte->prefixlen, rte->metric); ripng_peer_bad_route(ripng, from); continue; } /* - is the prefix length valid (i.e., between 0 and 128, inclusive) */ if (rte->prefixlen > 128) { zlog_warn("Invalid prefix length %s/%d from %s%%%s", inet6_ntoa(rte->addr), rte->prefixlen, inet6_ntoa(from->sin6_addr), ifp->name); ripng_peer_bad_route(ripng, from); continue; } /* - is the metric valid (i.e., between 1 and 16, inclusive) */ if (!(rte->metric >= 1 && rte->metric <= 16)) { zlog_warn("Invalid metric %d from %s%%%s", rte->metric, inet6_ntoa(from->sin6_addr), ifp->name); ripng_peer_bad_route(ripng, from); continue; } /* Vincent: XXX Should we compute the direclty reachable nexthop * for our RIPng network ? **/ /* Routing table updates. */ ripng_route_process(rte, from, &nexthop, ifp); } } /* Response to request message. */ static void ripng_request_process(struct ripng_packet *packet, int size, struct sockaddr_in6 *from, struct interface *ifp) { struct ripng *ripng; caddr_t lim; struct rte *rte; struct prefix_ipv6 p; struct agg_node *rp; struct ripng_info *rinfo; struct ripng_interface *ri; /* Does not reponse to the requests on the loopback interfaces */ if (if_is_loopback(ifp)) return; /* Check RIPng process is enabled on this interface. */ ri = ifp->info; if (!ri->running) return; ripng = ri->ripng; /* When passive interface is specified, suppress responses */ if (ri->passive) return; /* RIPng peer update. */ ripng_peer_update(ripng, from, packet->version); lim = ((caddr_t)packet) + size; rte = packet->rte; /* The Request is processed entry by entry. If there are no entries, no response is given. */ if (lim == (caddr_t)rte) return; /* There is one special case. If there is exactly one entry in the request, and it has a destination prefix of zero, a prefix length of zero, and a metric of infinity (i.e., 16), then this is a request to send the entire routing table. In that case, a call is made to the output process to send the routing table to the requesting address/port. */ if (lim == ((caddr_t)(rte + 1)) && IN6_IS_ADDR_UNSPECIFIED(&rte->addr) && rte->prefixlen == 0 && rte->metric == RIPNG_METRIC_INFINITY) { /* All route with split horizon */ ripng_output_process(ifp, from, ripng_all_route); } else { /* Except for this special case, processing is quite simple. Examine the list of RTEs in the Request one by one. For each entry, look up the destination in the router's routing database and, if there is a route, put that route's metric in the metric field of the RTE. If there is no explicit route to the specified destination, put infinity in the metric field. Once all the entries have been filled in, change the command from Request to Response and send the datagram back to the requestor. */ memset(&p, 0, sizeof(struct prefix_ipv6)); p.family = AF_INET6; for (; ((caddr_t)rte) < lim; rte++) { p.prefix = rte->addr; p.prefixlen = rte->prefixlen; apply_mask_ipv6(&p); rp = agg_node_lookup(ripng->table, (struct prefix *)&p); if (rp) { rinfo = listgetdata( listhead((struct list *)rp->info)); rte->metric = rinfo->metric; agg_unlock_node(rp); } else rte->metric = RIPNG_METRIC_INFINITY; } packet->command = RIPNG_RESPONSE; ripng_send_packet((caddr_t)packet, size, from, ifp); } } /* First entry point of reading RIPng packet. */ static int ripng_read(struct thread *thread) { struct ripng *ripng = THREAD_ARG(thread); int len; int sock; struct sockaddr_in6 from; struct ripng_packet *packet; ifindex_t ifindex = 0; struct interface *ifp; int hoplimit = -1; /* Check ripng is active and alive. */ assert(ripng != NULL); assert(ripng->sock >= 0); /* Fetch thread data and set read pointer to empty for event managing. `sock' sould be same as ripng->sock. */ sock = THREAD_FD(thread); ripng->t_read = NULL; /* Add myself to the next event. */ ripng_event(ripng, RIPNG_READ, sock); /* Read RIPng packet. */ len = ripng_recv_packet(sock, STREAM_DATA(ripng->ibuf), STREAM_SIZE(ripng->ibuf), &from, &ifindex, &hoplimit); if (len < 0) { zlog_warn("RIPng recvfrom failed (VRF %s): %s.", ripng->vrf_name, safe_strerror(errno)); return len; } /* Check RTE boundary. RTE size (Packet length - RIPng header size (4)) must be multiple size of one RTE size (20). */ if (((len - 4) % 20) != 0) { zlog_warn("RIPng invalid packet size %d from %s (VRF %s)", len, inet6_ntoa(from.sin6_addr), ripng->vrf_name); ripng_peer_bad_packet(ripng, &from); return 0; } packet = (struct ripng_packet *)STREAM_DATA(ripng->ibuf); ifp = if_lookup_by_index(ifindex, ripng->vrf->vrf_id); /* RIPng packet received. */ if (IS_RIPNG_DEBUG_EVENT) zlog_debug( "RIPng packet received from %s port %d on %s (VRF %s)", inet6_ntoa(from.sin6_addr), ntohs(from.sin6_port), ifp ? ifp->name : "unknown", ripng->vrf_name); /* Logging before packet checking. */ if (IS_RIPNG_DEBUG_RECV) ripng_packet_dump(packet, len, "RECV"); /* Packet comes from unknown interface. */ if (ifp == NULL) { zlog_warn( "RIPng packet comes from unknown interface %d (VRF %s)", ifindex, ripng->vrf_name); return 0; } /* Packet version mismatch checking. */ if (packet->version != ripng->version) { zlog_warn( "RIPng packet version %d doesn't fit to my version %d (VRF %s)", packet->version, ripng->version, ripng->vrf_name); ripng_peer_bad_packet(ripng, &from); return 0; } /* Process RIPng packet. */ switch (packet->command) { case RIPNG_REQUEST: ripng_request_process(packet, len, &from, ifp); break; case RIPNG_RESPONSE: ripng_response_process(packet, len, &from, ifp, hoplimit); break; default: zlog_warn("Invalid RIPng command %d (VRF %s)", packet->command, ripng->vrf_name); ripng_peer_bad_packet(ripng, &from); break; } return 0; } /* Walk down the RIPng routing table then clear changed flag. */ static void ripng_clear_changed_flag(struct ripng *ripng) { struct agg_node *rp; struct ripng_info *rinfo = NULL; struct list *list = NULL; struct listnode *listnode = NULL; for (rp = agg_route_top(ripng->table); rp; rp = agg_route_next(rp)) if ((list = rp->info) != NULL) for (ALL_LIST_ELEMENTS_RO(list, listnode, rinfo)) { UNSET_FLAG(rinfo->flags, RIPNG_RTF_CHANGED); /* This flag can be set only on the first entry. */ break; } } /* Regular update of RIPng route. Send all routing formation to RIPng enabled interface. */ static int ripng_update(struct thread *t) { struct ripng *ripng = THREAD_ARG(t); struct interface *ifp; struct ripng_interface *ri; /* Clear update timer thread. */ ripng->t_update = NULL; /* Logging update event. */ if (IS_RIPNG_DEBUG_EVENT) zlog_debug("RIPng update timer expired!"); /* Supply routes to each interface. */ FOR_ALL_INTERFACES (ripng->vrf, ifp) { ri = ifp->info; if (if_is_loopback(ifp) || !if_is_up(ifp)) continue; if (!ri->running) continue; /* When passive interface is specified, suppress announce to the interface. */ if (ri->passive) continue; #if RIPNG_ADVANCED if (ri->ri_send == RIPNG_SEND_OFF) { if (IS_RIPNG_DEBUG_EVENT) zlog_debug( "[Event] RIPng send to if %d is suppressed by config", ifp->ifindex); continue; } #endif /* RIPNG_ADVANCED */ ripng_output_process(ifp, NULL, ripng_all_route); } /* Triggered updates may be suppressed if a regular update is due by the time the triggered update would be sent. */ if (ripng->t_triggered_interval) { thread_cancel(ripng->t_triggered_interval); ripng->t_triggered_interval = NULL; } ripng->trigger = 0; /* Reset flush event. */ ripng_event(ripng, RIPNG_UPDATE_EVENT, 0); return 0; } /* Triggered update interval timer. */ static int ripng_triggered_interval(struct thread *t) { struct ripng *ripng = THREAD_ARG(t); ripng->t_triggered_interval = NULL; if (ripng->trigger) { ripng->trigger = 0; ripng_triggered_update(t); } return 0; } /* Execute triggered update. */ int ripng_triggered_update(struct thread *t) { struct ripng *ripng = THREAD_ARG(t); struct interface *ifp; struct ripng_interface *ri; int interval; ripng->t_triggered_update = NULL; /* Cancel interval timer. */ if (ripng->t_triggered_interval) { thread_cancel(ripng->t_triggered_interval); ripng->t_triggered_interval = NULL; } ripng->trigger = 0; /* Logging triggered update. */ if (IS_RIPNG_DEBUG_EVENT) zlog_debug("RIPng triggered update!"); /* Split Horizon processing is done when generating triggered updates as well as normal updates (see section 2.6). */ FOR_ALL_INTERFACES (ripng->vrf, ifp) { ri = ifp->info; if (if_is_loopback(ifp) || !if_is_up(ifp)) continue; if (!ri->running) continue; /* When passive interface is specified, suppress announce to the interface. */ if (ri->passive) continue; ripng_output_process(ifp, NULL, ripng_changed_route); } /* Once all of the triggered updates have been generated, the route change flags should be cleared. */ ripng_clear_changed_flag(ripng); /* After a triggered update is sent, a timer should be set for a random interval between 1 and 5 seconds. If other changes that would trigger updates occur before the timer expires, a single update is triggered when the timer expires. */ interval = (random() % 5) + 1; ripng->t_triggered_interval = NULL; thread_add_timer(master, ripng_triggered_interval, ripng, interval, &ripng->t_triggered_interval); return 0; } /* Write routing table entry to the stream and return next index of the routing table entry in the stream. */ int ripng_write_rte(int num, struct stream *s, struct prefix_ipv6 *p, struct in6_addr *nexthop, uint16_t tag, uint8_t metric) { /* RIPng packet header. */ if (num == 0) { stream_putc(s, RIPNG_RESPONSE); stream_putc(s, RIPNG_V1); stream_putw(s, 0); } /* Write routing table entry. */ if (!nexthop) { assert(p); stream_write(s, (uint8_t *)&p->prefix, sizeof(struct in6_addr)); } else stream_write(s, (uint8_t *)nexthop, sizeof(struct in6_addr)); stream_putw(s, tag); if (p) stream_putc(s, p->prefixlen); else stream_putc(s, 0); stream_putc(s, metric); return ++num; } /* Send RESPONSE message to specified destination. */ void ripng_output_process(struct interface *ifp, struct sockaddr_in6 *to, int route_type) { struct ripng *ripng; int ret; struct agg_node *rp; struct ripng_info *rinfo; struct ripng_interface *ri; struct ripng_aggregate *aggregate; struct prefix_ipv6 *p; struct list *ripng_rte_list; struct list *list = NULL; struct listnode *listnode = NULL; if (IS_RIPNG_DEBUG_EVENT) { if (to) zlog_debug("RIPng update routes to neighbor %s", inet6_ntoa(to->sin6_addr)); else zlog_debug("RIPng update routes on interface %s", ifp->name); } /* Get RIPng interface and instance. */ ri = ifp->info; ripng = ri->ripng; ripng_rte_list = ripng_rte_new(); for (rp = agg_route_top(ripng->table); rp; rp = agg_route_next(rp)) { if ((list = rp->info) != NULL && (rinfo = listgetdata(listhead(list))) != NULL && rinfo->suppress == 0) { /* If no route-map are applied, the RTE will be these * following * information. */ p = (struct prefix_ipv6 *)&rp->p; rinfo->metric_out = rinfo->metric; rinfo->tag_out = rinfo->tag; memset(&rinfo->nexthop_out, 0, sizeof(rinfo->nexthop_out)); /* In order to avoid some local loops, * if the RIPng route has a nexthop via this interface, * keep the nexthop, * otherwise set it to 0. The nexthop should not be * propagated * beyond the local broadcast/multicast area in order * to avoid an IGP multi-level recursive look-up. */ if (rinfo->ifindex == ifp->ifindex) rinfo->nexthop_out = rinfo->nexthop; /* Apply output filters. */ ret = ripng_filter(RIPNG_FILTER_OUT, p, ri); if (ret < 0) continue; /* Changed route only output. */ if (route_type == ripng_changed_route && (!(rinfo->flags & RIPNG_RTF_CHANGED))) continue; /* Split horizon. */ if (ri->split_horizon == RIPNG_SPLIT_HORIZON) { /* We perform split horizon for RIPng routes. */ int suppress = 0; struct ripng_info *tmp_rinfo = NULL; for (ALL_LIST_ELEMENTS_RO(list, listnode, tmp_rinfo)) if (tmp_rinfo->type == ZEBRA_ROUTE_RIPNG && tmp_rinfo->ifindex == ifp->ifindex) { suppress = 1; break; } if (suppress) continue; } /* Preparation for route-map. */ rinfo->metric_set = 0; /* nexthop_out, * metric_out * and tag_out are already initialized. */ /* Interface route-map */ if (ri->routemap[RIPNG_FILTER_OUT]) { ret = route_map_apply( ri->routemap[RIPNG_FILTER_OUT], (struct prefix *)p, RMAP_RIPNG, rinfo); if (ret == RMAP_DENYMATCH) { if (IS_RIPNG_DEBUG_PACKET) zlog_debug( "RIPng %s/%d is filtered by route-map out", inet6_ntoa(p->prefix), p->prefixlen); continue; } } /* Redistribute route-map. */ if (ripng->redist[rinfo->type].route_map.name) { ret = route_map_apply(ripng->redist[rinfo->type] .route_map.map, (struct prefix *)p, RMAP_RIPNG, rinfo); if (ret == RMAP_DENYMATCH) { if (IS_RIPNG_DEBUG_PACKET) zlog_debug( "RIPng %s/%d is filtered by route-map", inet6_ntoa(p->prefix), p->prefixlen); continue; } } /* When the route-map does not set metric. */ if (!rinfo->metric_set) { /* If the redistribute metric is set. */ if (ripng->redist[rinfo->type].metric_config && rinfo->metric != RIPNG_METRIC_INFINITY) { rinfo->metric_out = ripng->redist[rinfo->type] .metric; } else { /* If the route is not connected or localy generated one, use default-metric value */ if (rinfo->type != ZEBRA_ROUTE_RIPNG && rinfo->type != ZEBRA_ROUTE_CONNECT && rinfo->metric != RIPNG_METRIC_INFINITY) rinfo->metric_out = ripng->default_metric; } } /* Apply offset-list */ if (rinfo->metric_out != RIPNG_METRIC_INFINITY) ripng_offset_list_apply_out(ripng, p, ifp, &rinfo->metric_out); if (rinfo->metric_out > RIPNG_METRIC_INFINITY) rinfo->metric_out = RIPNG_METRIC_INFINITY; /* Perform split-horizon with poisoned reverse * for RIPng routes. **/ if (ri->split_horizon == RIPNG_SPLIT_HORIZON_POISONED_REVERSE) { struct ripng_info *tmp_rinfo = NULL; for (ALL_LIST_ELEMENTS_RO(list, listnode, tmp_rinfo)) if ((tmp_rinfo->type == ZEBRA_ROUTE_RIPNG) && tmp_rinfo->ifindex == ifp->ifindex) rinfo->metric_out = RIPNG_METRIC_INFINITY; } /* Add RTE to the list */ ripng_rte_add(ripng_rte_list, p, rinfo, NULL); } /* Process the aggregated RTE entry */ if ((aggregate = rp->aggregate) != NULL && aggregate->count > 0 && aggregate->suppress == 0) { /* If no route-map are applied, the RTE will be these * following * information. */ p = (struct prefix_ipv6 *)&rp->p; aggregate->metric_set = 0; aggregate->metric_out = aggregate->metric; aggregate->tag_out = aggregate->tag; memset(&aggregate->nexthop_out, 0, sizeof(aggregate->nexthop_out)); /* Apply output filters.*/ ret = ripng_filter(RIPNG_FILTER_OUT, p, ri); if (ret < 0) continue; /* Interface route-map */ if (ri->routemap[RIPNG_FILTER_OUT]) { struct ripng_info newinfo; /* let's cast the aggregate structure to * ripng_info */ memset(&newinfo, 0, sizeof(struct ripng_info)); /* the nexthop is :: */ newinfo.metric = aggregate->metric; newinfo.metric_out = aggregate->metric_out; newinfo.tag = aggregate->tag; newinfo.tag_out = aggregate->tag_out; ret = route_map_apply( ri->routemap[RIPNG_FILTER_OUT], (struct prefix *)p, RMAP_RIPNG, &newinfo); if (ret == RMAP_DENYMATCH) { if (IS_RIPNG_DEBUG_PACKET) zlog_debug( "RIPng %s/%d is filtered by route-map out", inet6_ntoa(p->prefix), p->prefixlen); continue; } aggregate->metric_out = newinfo.metric_out; aggregate->tag_out = newinfo.tag_out; if (IN6_IS_ADDR_LINKLOCAL(&newinfo.nexthop_out)) aggregate->nexthop_out = newinfo.nexthop_out; } /* There is no redistribute routemap for the aggregated * RTE */ /* Changed route only output. */ /* XXX, vincent, in order to increase time convergence, * it should be announced if a child has changed. */ if (route_type == ripng_changed_route) continue; /* Apply offset-list */ if (aggregate->metric_out != RIPNG_METRIC_INFINITY) ripng_offset_list_apply_out( ripng, p, ifp, &aggregate->metric_out); if (aggregate->metric_out > RIPNG_METRIC_INFINITY) aggregate->metric_out = RIPNG_METRIC_INFINITY; /* Add RTE to the list */ ripng_rte_add(ripng_rte_list, p, NULL, aggregate); } } /* Flush the list */ ripng_rte_send(ripng_rte_list, ifp, to); ripng_rte_free(ripng_rte_list); } struct ripng *ripng_lookup_by_vrf_id(vrf_id_t vrf_id) { struct vrf *vrf; vrf = vrf_lookup_by_id(vrf_id); if (!vrf) return NULL; return vrf->info; } struct ripng *ripng_lookup_by_vrf_name(const char *vrf_name) { struct ripng ripng; ripng.vrf_name = (char *)vrf_name; return RB_FIND(ripng_instance_head, &ripng_instances, &ripng); } /* Create new RIPng instance and set it to global variable. */ struct ripng *ripng_create(const char *vrf_name, struct vrf *vrf, int socket) { struct ripng *ripng; /* Allocaste RIPng instance. */ ripng = XCALLOC(MTYPE_RIPNG, sizeof(struct ripng)); ripng->vrf_name = XSTRDUP(MTYPE_RIPNG_VRF_NAME, vrf_name); /* Default version and timer values. */ ripng->version = RIPNG_V1; ripng->update_time = yang_get_default_uint32( "%s/timers/update-interval", RIPNG_INSTANCE); ripng->timeout_time = yang_get_default_uint32( "%s/timers/holddown-interval", RIPNG_INSTANCE); ripng->garbage_time = yang_get_default_uint32( "%s/timers/flush-interval", RIPNG_INSTANCE); ripng->default_metric = yang_get_default_uint8("%s/default-metric", RIPNG_INSTANCE); ripng->ecmp = yang_get_default_bool("%s/allow-ecmp", RIPNG_INSTANCE); /* Make buffer. */ ripng->ibuf = stream_new(RIPNG_MAX_PACKET_SIZE * 5); ripng->obuf = stream_new(RIPNG_MAX_PACKET_SIZE); /* Initialize RIPng data structures. */ ripng->table = agg_table_init(); agg_set_table_info(ripng->table, ripng); ripng->peer_list = list_new(); ripng->peer_list->cmp = (int (*)(void *, void *))ripng_peer_list_cmp; ripng->peer_list->del = ripng_peer_list_del; ripng->enable_if = vector_init(1); ripng->enable_network = agg_table_init(); ripng->passive_interface = vector_init(1); ripng->offset_list_master = list_new(); ripng->offset_list_master->cmp = (int (*)(void *, void *))offset_list_cmp; ripng->offset_list_master->del = (void (*)(void *))ripng_offset_list_free; ripng->distribute_ctx = distribute_list_ctx_create(vrf); distribute_list_add_hook(ripng->distribute_ctx, ripng_distribute_update); distribute_list_delete_hook(ripng->distribute_ctx, ripng_distribute_update); /* if rmap install. */ ripng->if_rmap_ctx = if_rmap_ctx_create(vrf_name); if_rmap_hook_add(ripng->if_rmap_ctx, ripng_if_rmap_update); if_rmap_hook_delete(ripng->if_rmap_ctx, ripng_if_rmap_update); /* Enable the routing instance if possible. */ if (vrf && vrf_is_enabled(vrf)) ripng_instance_enable(ripng, vrf, socket); else { ripng->vrf = NULL; ripng->sock = -1; } RB_INSERT(ripng_instance_head, &ripng_instances, ripng); return ripng; } /* Send RIPng request to the interface. */ int ripng_request(struct interface *ifp) { struct rte *rte; struct ripng_packet ripng_packet; /* In default ripd doesn't send RIP_REQUEST to the loopback interface. */ if (if_is_loopback(ifp)) return 0; /* If interface is down, don't send RIP packet. */ if (!if_is_up(ifp)) return 0; if (IS_RIPNG_DEBUG_EVENT) zlog_debug("RIPng send request to %s", ifp->name); memset(&ripng_packet, 0, sizeof(ripng_packet)); ripng_packet.command = RIPNG_REQUEST; ripng_packet.version = RIPNG_V1; rte = ripng_packet.rte; rte->metric = RIPNG_METRIC_INFINITY; return ripng_send_packet((caddr_t)&ripng_packet, sizeof(ripng_packet), NULL, ifp); } static int ripng_update_jitter(int time) { return ((random() % (time + 1)) - (time / 2)); } void ripng_event(struct ripng *ripng, enum ripng_event event, int sock) { int jitter = 0; switch (event) { case RIPNG_READ: thread_add_read(master, ripng_read, ripng, sock, &ripng->t_read); break; case RIPNG_UPDATE_EVENT: if (ripng->t_update) { thread_cancel(ripng->t_update); ripng->t_update = NULL; } /* Update timer jitter. */ jitter = ripng_update_jitter(ripng->update_time); ripng->t_update = NULL; thread_add_timer(master, ripng_update, ripng, sock ? 2 : ripng->update_time + jitter, &ripng->t_update); break; case RIPNG_TRIGGERED_UPDATE: if (ripng->t_triggered_interval) ripng->trigger = 1; else thread_add_event(master, ripng_triggered_update, ripng, 0, &ripng->t_triggered_update); break; default: break; } } /* Print out routes update time. */ static void ripng_vty_out_uptime(struct vty *vty, struct ripng_info *rinfo) { time_t clock; struct tm *tm; #define TIME_BUF 25 char timebuf[TIME_BUF]; struct thread *thread; if ((thread = rinfo->t_timeout) != NULL) { clock = thread_timer_remain_second(thread); tm = gmtime(&clock); strftime(timebuf, TIME_BUF, "%M:%S", tm); vty_out(vty, "%5s", timebuf); } else if ((thread = rinfo->t_garbage_collect) != NULL) { clock = thread_timer_remain_second(thread); tm = gmtime(&clock); strftime(timebuf, TIME_BUF, "%M:%S", tm); vty_out(vty, "%5s", timebuf); } } static char *ripng_route_subtype_print(struct ripng_info *rinfo) { static char str[3]; memset(str, 0, 3); if (rinfo->suppress) strlcat(str, "S", sizeof(str)); switch (rinfo->sub_type) { case RIPNG_ROUTE_RTE: strlcat(str, "n", sizeof(str)); break; case RIPNG_ROUTE_STATIC: strlcat(str, "s", sizeof(str)); break; case RIPNG_ROUTE_DEFAULT: strlcat(str, "d", sizeof(str)); break; case RIPNG_ROUTE_REDISTRIBUTE: strlcat(str, "r", sizeof(str)); break; case RIPNG_ROUTE_INTERFACE: strlcat(str, "i", sizeof(str)); break; default: strlcat(str, "?", sizeof(str)); break; } return str; } DEFUN (show_ipv6_ripng, show_ipv6_ripng_cmd, "show ipv6 ripng [vrf NAME]", SHOW_STR IPV6_STR "Show RIPng routes\n" VRF_CMD_HELP_STR) { struct ripng *ripng; struct agg_node *rp; struct ripng_info *rinfo; struct ripng_aggregate *aggregate; struct prefix_ipv6 *p; struct list *list = NULL; struct listnode *listnode = NULL; int len; const char *vrf_name; int idx = 0; if (argv_find(argv, argc, "vrf", &idx)) vrf_name = argv[idx + 1]->arg; else vrf_name = VRF_DEFAULT_NAME; ripng = ripng_lookup_by_vrf_name(vrf_name); if (!ripng) { vty_out(vty, "%% RIPng instance not found\n"); return CMD_SUCCESS; } if (!ripng->enabled) { vty_out(vty, "%% RIPng instance is disabled\n"); return CMD_SUCCESS; } /* Header of display. */ vty_out(vty, "Codes: R - RIPng, C - connected, S - Static, O - OSPF, B - BGP\n" "Sub-codes:\n" " (n) - normal, (s) - static, (d) - default, (r) - redistribute,\n" " (i) - interface, (a/S) - aggregated/Suppressed\n\n" " Network Next Hop Via Metric Tag Time\n"); for (rp = agg_route_top(ripng->table); rp; rp = agg_route_next(rp)) { if ((aggregate = rp->aggregate) != NULL) { p = (struct prefix_ipv6 *)&rp->p; #ifdef DEBUG vty_out(vty, "R(a) %d/%d %s/%d ", aggregate->count, aggregate->suppress, inet6_ntoa(p->prefix), p->prefixlen); #else vty_out(vty, "R(a) %s/%d ", inet6_ntoa(p->prefix), p->prefixlen); #endif /* DEBUG */ vty_out(vty, "\n"); vty_out(vty, "%*s", 18, " "); vty_out(vty, "%*s", 28, " "); vty_out(vty, "self %2d %3" ROUTE_TAG_PRI "\n", aggregate->metric, (route_tag_t)aggregate->tag); } if ((list = rp->info) != NULL) for (ALL_LIST_ELEMENTS_RO(list, listnode, rinfo)) { p = (struct prefix_ipv6 *)&rp->p; #ifdef DEBUG vty_out(vty, "%c(%s) 0/%d %s/%d ", zebra_route_char(rinfo->type), ripng_route_subtype_print(rinfo), rinfo->suppress, inet6_ntoa(p->prefix), p->prefixlen); #else vty_out(vty, "%c(%s) %s/%d ", zebra_route_char(rinfo->type), ripng_route_subtype_print(rinfo), inet6_ntoa(p->prefix), p->prefixlen); #endif /* DEBUG */ vty_out(vty, "\n"); vty_out(vty, "%*s", 18, " "); len = vty_out(vty, "%s", inet6_ntoa(rinfo->nexthop)); len = 28 - len; if (len > 0) vty_out(vty, "%*s", len, " "); /* from */ if ((rinfo->type == ZEBRA_ROUTE_RIPNG) && (rinfo->sub_type == RIPNG_ROUTE_RTE)) { len = vty_out( vty, "%s", ifindex2ifname( rinfo->ifindex, ripng->vrf->vrf_id)); } else if (rinfo->metric == RIPNG_METRIC_INFINITY) { len = vty_out(vty, "kill"); } else len = vty_out(vty, "self"); len = 9 - len; if (len > 0) vty_out(vty, "%*s", len, " "); vty_out(vty, " %2d %3" ROUTE_TAG_PRI " ", rinfo->metric, (route_tag_t)rinfo->tag); /* time */ if ((rinfo->type == ZEBRA_ROUTE_RIPNG) && (rinfo->sub_type == RIPNG_ROUTE_RTE)) { /* RTE from remote RIP routers */ ripng_vty_out_uptime(vty, rinfo); } else if (rinfo->metric == RIPNG_METRIC_INFINITY) { /* poisonous reversed routes (gc) */ ripng_vty_out_uptime(vty, rinfo); } vty_out(vty, "\n"); } } return CMD_SUCCESS; } DEFUN (show_ipv6_ripng_status, show_ipv6_ripng_status_cmd, "show ipv6 ripng [vrf NAME] status", SHOW_STR IPV6_STR "Show RIPng routes\n" VRF_CMD_HELP_STR "IPv6 routing protocol process parameters and statistics\n") { struct ripng *ripng; struct interface *ifp; const char *vrf_name; int idx = 0; if (argv_find(argv, argc, "vrf", &idx)) vrf_name = argv[idx + 1]->arg; else vrf_name = VRF_DEFAULT_NAME; ripng = ripng_lookup_by_vrf_name(vrf_name); if (!ripng) { vty_out(vty, "%% RIPng instance not found\n"); return CMD_SUCCESS; } if (!ripng->enabled) { vty_out(vty, "%% RIPng instance is disabled\n"); return CMD_SUCCESS; } vty_out(vty, "Routing Protocol is \"RIPng\"\n"); vty_out(vty, " Sending updates every %u seconds with +/-50%%,", ripng->update_time); vty_out(vty, " next due in %lu seconds\n", thread_timer_remain_second(ripng->t_update)); vty_out(vty, " Timeout after %u seconds,", ripng->timeout_time); vty_out(vty, " garbage collect after %u seconds\n", ripng->garbage_time); /* Filtering status show. */ config_show_distribute(vty, ripng->distribute_ctx); /* Default metric information. */ vty_out(vty, " Default redistribution metric is %d\n", ripng->default_metric); /* Redistribute information. */ vty_out(vty, " Redistributing:"); ripng_redistribute_write(vty, ripng); vty_out(vty, "\n"); vty_out(vty, " Default version control: send version %d,", ripng->version); vty_out(vty, " receive version %d \n", ripng->version); vty_out(vty, " Interface Send Recv\n"); FOR_ALL_INTERFACES (ripng->vrf, ifp) { struct ripng_interface *ri; ri = ifp->info; if (ri->enable_network || ri->enable_interface) { vty_out(vty, " %-17s%-3d %-3d\n", ifp->name, ripng->version, ripng->version); } } vty_out(vty, " Routing for Networks:\n"); ripng_network_write(vty, ripng); vty_out(vty, " Routing Information Sources:\n"); vty_out(vty, " Gateway BadPackets BadRoutes Distance Last Update\n"); ripng_peer_display(vty, ripng); return CMD_SUCCESS; } #if 0 /* RIPng update timer setup. */ DEFUN (ripng_update_timer, ripng_update_timer_cmd, "update-timer SECOND", "Set RIPng update timer in seconds\n" "Seconds\n") { unsigned long update; char *endptr = NULL; update = strtoul (argv[0], &endptr, 10); if (update == ULONG_MAX || *endptr != '\0') { vty_out (vty, "update timer value error\n"); return CMD_WARNING_CONFIG_FAILED; } ripng->update_time = update; ripng_event (RIPNG_UPDATE_EVENT, 0); return CMD_SUCCESS; } DEFUN (no_ripng_update_timer, no_ripng_update_timer_cmd, "no update-timer SECOND", NO_STR "Unset RIPng update timer in seconds\n" "Seconds\n") { ripng->update_time = RIPNG_UPDATE_TIMER_DEFAULT; ripng_event (RIPNG_UPDATE_EVENT, 0); return CMD_SUCCESS; } /* RIPng timeout timer setup. */ DEFUN (ripng_timeout_timer, ripng_timeout_timer_cmd, "timeout-timer SECOND", "Set RIPng timeout timer in seconds\n" "Seconds\n") { unsigned long timeout; char *endptr = NULL; timeout = strtoul (argv[0], &endptr, 10); if (timeout == ULONG_MAX || *endptr != '\0') { vty_out (vty, "timeout timer value error\n"); return CMD_WARNING_CONFIG_FAILED; } ripng->timeout_time = timeout; return CMD_SUCCESS; } DEFUN (no_ripng_timeout_timer, no_ripng_timeout_timer_cmd, "no timeout-timer SECOND", NO_STR "Unset RIPng timeout timer in seconds\n" "Seconds\n") { ripng->timeout_time = RIPNG_TIMEOUT_TIMER_DEFAULT; return CMD_SUCCESS; } /* RIPng garbage timer setup. */ DEFUN (ripng_garbage_timer, ripng_garbage_timer_cmd, "garbage-timer SECOND", "Set RIPng garbage timer in seconds\n" "Seconds\n") { unsigned long garbage; char *endptr = NULL; garbage = strtoul (argv[0], &endptr, 10); if (garbage == ULONG_MAX || *endptr != '\0') { vty_out (vty, "garbage timer value error\n"); return CMD_WARNING_CONFIG_FAILED; } ripng->garbage_time = garbage; return CMD_SUCCESS; } DEFUN (no_ripng_garbage_timer, no_ripng_garbage_timer_cmd, "no garbage-timer SECOND", NO_STR "Unset RIPng garbage timer in seconds\n" "Seconds\n") { ripng->garbage_time = RIPNG_GARBAGE_TIMER_DEFAULT; return CMD_SUCCESS; } #endif /* 0 */ #if 0 DEFUN (show_ipv6_protocols, show_ipv6_protocols_cmd, "show ipv6 protocols", SHOW_STR IPV6_STR "Routing protocol information\n") { if (! ripng) return CMD_SUCCESS; vty_out (vty, "Routing Protocol is \"ripng\"\n"); vty_out (vty, "Sending updates every %ld seconds, next due in %d seconds\n", ripng->update_time, 0); vty_out (vty, "Timerout after %ld seconds, garbage correct %ld\n", ripng->timeout_time, ripng->garbage_time); vty_out (vty, "Outgoing update filter list for all interfaces is not set"); vty_out (vty, "Incoming update filter list for all interfaces is not set"); return CMD_SUCCESS; } #endif /* Update ECMP routes to zebra when ECMP is disabled. */ void ripng_ecmp_disable(struct ripng *ripng) { struct agg_node *rp; struct ripng_info *rinfo, *tmp_rinfo; struct list *list; struct listnode *node, *nextnode; if (!ripng) return; for (rp = agg_route_top(ripng->table); rp; rp = agg_route_next(rp)) if ((list = rp->info) != NULL && listcount(list) > 1) { rinfo = listgetdata(listhead(list)); if (!ripng_route_rte(rinfo)) continue; /* Drop all other entries, except the first one. */ for (ALL_LIST_ELEMENTS(list, node, nextnode, tmp_rinfo)) if (tmp_rinfo != rinfo) { RIPNG_TIMER_OFF(tmp_rinfo->t_timeout); RIPNG_TIMER_OFF( tmp_rinfo->t_garbage_collect); list_delete_node(list, node); ripng_info_free(tmp_rinfo); } /* Update zebra. */ ripng_zebra_ipv6_add(ripng, rp); /* Set the route change flag. */ SET_FLAG(rinfo->flags, RIPNG_RTF_CHANGED); /* Signal the output process to trigger an update. */ ripng_event(ripng, RIPNG_TRIGGERED_UPDATE, 0); } } /* RIPng configuration write function. */ static int ripng_config_write(struct vty *vty) { struct ripng *ripng; int write = 0; RB_FOREACH(ripng, ripng_instance_head, &ripng_instances) { char xpath[XPATH_MAXLEN]; struct lyd_node *dnode; snprintf(xpath, sizeof(xpath), "/frr-ripngd:ripngd/instance[vrf='%s']", ripng->vrf_name); dnode = yang_dnode_get(running_config->dnode, xpath); assert(dnode); nb_cli_show_dnode_cmds(vty, dnode, false); config_write_distribute(vty, ripng->distribute_ctx); config_write_if_rmap(vty, ripng->if_rmap_ctx); write = 1; } return write; } /* RIPng node structure. */ static struct cmd_node cmd_ripng_node = { RIPNG_NODE, "%s(config-router)# ", 1, }; static void ripng_distribute_update(struct distribute_ctx *ctx, struct distribute *dist) { struct interface *ifp; struct ripng_interface *ri; struct access_list *alist; struct prefix_list *plist; if (!ctx->vrf || !dist->ifname) return; ifp = if_lookup_by_name(dist->ifname, ctx->vrf->vrf_id); if (ifp == NULL) return; ri = ifp->info; if (dist->list[DISTRIBUTE_V6_IN]) { alist = access_list_lookup(AFI_IP6, dist->list[DISTRIBUTE_V6_IN]); if (alist) ri->list[RIPNG_FILTER_IN] = alist; else ri->list[RIPNG_FILTER_IN] = NULL; } else ri->list[RIPNG_FILTER_IN] = NULL; if (dist->list[DISTRIBUTE_V6_OUT]) { alist = access_list_lookup(AFI_IP6, dist->list[DISTRIBUTE_V6_OUT]); if (alist) ri->list[RIPNG_FILTER_OUT] = alist; else ri->list[RIPNG_FILTER_OUT] = NULL; } else ri->list[RIPNG_FILTER_OUT] = NULL; if (dist->prefix[DISTRIBUTE_V6_IN]) { plist = prefix_list_lookup(AFI_IP6, dist->prefix[DISTRIBUTE_V6_IN]); if (plist) ri->prefix[RIPNG_FILTER_IN] = plist; else ri->prefix[RIPNG_FILTER_IN] = NULL; } else ri->prefix[RIPNG_FILTER_IN] = NULL; if (dist->prefix[DISTRIBUTE_V6_OUT]) { plist = prefix_list_lookup(AFI_IP6, dist->prefix[DISTRIBUTE_V6_OUT]); if (plist) ri->prefix[RIPNG_FILTER_OUT] = plist; else ri->prefix[RIPNG_FILTER_OUT] = NULL; } else ri->prefix[RIPNG_FILTER_OUT] = NULL; } void ripng_distribute_update_interface(struct interface *ifp) { struct ripng_interface *ri = ifp->info; struct ripng *ripng = ri->ripng; struct distribute *dist; if (!ripng) return; dist = distribute_lookup(ripng->distribute_ctx, ifp->name); if (dist) ripng_distribute_update(ripng->distribute_ctx, dist); } /* Update all interface's distribute list. */ static void ripng_distribute_update_all(struct prefix_list *notused) { struct vrf *vrf = vrf_lookup_by_id(VRF_DEFAULT); struct interface *ifp; FOR_ALL_INTERFACES (vrf, ifp) ripng_distribute_update_interface(ifp); } static void ripng_distribute_update_all_wrapper(struct access_list *notused) { ripng_distribute_update_all(NULL); } /* delete all the added ripng routes. */ void ripng_clean(struct ripng *ripng) { if (ripng->enabled) ripng_instance_disable(ripng); for (int i = 0; i < ZEBRA_ROUTE_MAX; i++) if (ripng->redist[i].route_map.name) free(ripng->redist[i].route_map.name); agg_table_finish(ripng->table); list_delete(&ripng->peer_list); distribute_list_delete(&ripng->distribute_ctx); if_rmap_ctx_delete(ripng->if_rmap_ctx); stream_free(ripng->ibuf); stream_free(ripng->obuf); ripng_clean_network(ripng); ripng_passive_interface_clean(ripng); vector_free(ripng->enable_if); agg_table_finish(ripng->enable_network); vector_free(ripng->passive_interface); list_delete(&ripng->offset_list_master); ripng_interface_clean(ripng); RB_REMOVE(ripng_instance_head, &ripng_instances, ripng); XFREE(MTYPE_RIPNG_VRF_NAME, ripng->vrf_name); XFREE(MTYPE_RIPNG, ripng); } static void ripng_if_rmap_update(struct if_rmap_ctx *ctx, struct if_rmap *if_rmap) { struct interface *ifp = NULL; struct ripng_interface *ri; struct route_map *rmap; struct vrf *vrf = NULL; if (ctx->name) vrf = vrf_lookup_by_name(ctx->name); if (vrf) ifp = if_lookup_by_name(if_rmap->ifname, vrf->vrf_id); if (ifp == NULL) return; ri = ifp->info; if (if_rmap->routemap[IF_RMAP_IN]) { rmap = route_map_lookup_by_name(if_rmap->routemap[IF_RMAP_IN]); if (rmap) ri->routemap[IF_RMAP_IN] = rmap; else ri->routemap[IF_RMAP_IN] = NULL; } else ri->routemap[RIPNG_FILTER_IN] = NULL; if (if_rmap->routemap[IF_RMAP_OUT]) { rmap = route_map_lookup_by_name(if_rmap->routemap[IF_RMAP_OUT]); if (rmap) ri->routemap[IF_RMAP_OUT] = rmap; else ri->routemap[IF_RMAP_OUT] = NULL; } else ri->routemap[RIPNG_FILTER_OUT] = NULL; } void ripng_if_rmap_update_interface(struct interface *ifp) { struct ripng_interface *ri = ifp->info; struct ripng *ripng = ri->ripng; struct if_rmap *if_rmap; struct if_rmap_ctx *ctx; if (!ripng) return; ctx = ripng->if_rmap_ctx; if (!ctx) return; if_rmap = if_rmap_lookup(ctx, ifp->name); if (if_rmap) ripng_if_rmap_update(ctx, if_rmap); } static void ripng_routemap_update_redistribute(struct ripng *ripng) { for (int i = 0; i < ZEBRA_ROUTE_MAX; i++) { if (ripng->redist[i].route_map.name) { ripng->redist[i].route_map.map = route_map_lookup_by_name( ripng->redist[i].route_map.name); route_map_counter_increment( ripng->redist[i].route_map.map); } } } static void ripng_routemap_update(const char *unused) { struct vrf *vrf = vrf_lookup_by_id(VRF_DEFAULT); struct ripng *ripng; struct interface *ifp; FOR_ALL_INTERFACES (vrf, ifp) ripng_if_rmap_update_interface(ifp); ripng = vrf->info; if (ripng) ripng_routemap_update_redistribute(ripng); } /* Link RIPng instance to VRF. */ static void ripng_vrf_link(struct ripng *ripng, struct vrf *vrf) { struct interface *ifp; ripng->vrf = vrf; ripng->distribute_ctx->vrf = vrf; vrf->info = ripng; FOR_ALL_INTERFACES (vrf, ifp) ripng_interface_sync(ifp); } /* Unlink RIPng instance from VRF. */ static void ripng_vrf_unlink(struct ripng *ripng, struct vrf *vrf) { struct interface *ifp; ripng->vrf = NULL; ripng->distribute_ctx->vrf = NULL; vrf->info = NULL; FOR_ALL_INTERFACES (vrf, ifp) ripng_interface_sync(ifp); } static void ripng_instance_enable(struct ripng *ripng, struct vrf *vrf, int sock) { ripng->sock = sock; ripng_vrf_link(ripng, vrf); ripng->enabled = true; /* Resend all redistribute requests. */ ripng_redistribute_enable(ripng); /* Create read and timer thread. */ ripng_event(ripng, RIPNG_READ, ripng->sock); ripng_event(ripng, RIPNG_UPDATE_EVENT, 1); ripng_zebra_vrf_register(vrf); } static void ripng_instance_disable(struct ripng *ripng) { struct vrf *vrf = ripng->vrf; struct agg_node *rp; /* Clear RIPng routes */ for (rp = agg_route_top(ripng->table); rp; rp = agg_route_next(rp)) { struct ripng_aggregate *aggregate; struct list *list; if ((list = rp->info) != NULL) { struct ripng_info *rinfo; struct listnode *listnode; rinfo = listgetdata(listhead(list)); if (ripng_route_rte(rinfo)) ripng_zebra_ipv6_delete(ripng, rp); for (ALL_LIST_ELEMENTS_RO(list, listnode, rinfo)) { RIPNG_TIMER_OFF(rinfo->t_timeout); RIPNG_TIMER_OFF(rinfo->t_garbage_collect); ripng_info_free(rinfo); } list_delete(&list); rp->info = NULL; agg_unlock_node(rp); } if ((aggregate = rp->aggregate) != NULL) { ripng_aggregate_free(aggregate); rp->aggregate = NULL; agg_unlock_node(rp); } } /* Flush all redistribute requests. */ ripng_redistribute_disable(ripng); /* Cancel the RIPng timers */ RIPNG_TIMER_OFF(ripng->t_update); RIPNG_TIMER_OFF(ripng->t_triggered_update); RIPNG_TIMER_OFF(ripng->t_triggered_interval); /* Cancel the read thread */ if (ripng->t_read) { thread_cancel(ripng->t_read); ripng->t_read = NULL; } /* Close the RIPng socket */ if (ripng->sock >= 0) { close(ripng->sock); ripng->sock = -1; } /* Clear existing peers. */ list_delete_all_node(ripng->peer_list); ripng_zebra_vrf_deregister(vrf); ripng_vrf_unlink(ripng, vrf); ripng->enabled = false; } static int ripng_vrf_new(struct vrf *vrf) { if (IS_RIPNG_DEBUG_EVENT) zlog_debug("%s: VRF created: %s(%u)", __func__, vrf->name, vrf->vrf_id); return 0; } static int ripng_vrf_delete(struct vrf *vrf) { if (IS_RIPNG_DEBUG_EVENT) zlog_debug("%s: VRF deleted: %s(%u)", __func__, vrf->name, vrf->vrf_id); return 0; } static int ripng_vrf_enable(struct vrf *vrf) { struct ripng *ripng; int socket; ripng = ripng_lookup_by_vrf_name(vrf->name); if (!ripng || ripng->enabled) return 0; if (IS_RIPNG_DEBUG_EVENT) zlog_debug("%s: VRF %s(%u) enabled", __func__, vrf->name, vrf->vrf_id); /* Activate the VRF RIPng instance. */ if (!ripng->enabled) { socket = ripng_make_socket(vrf); if (socket < 0) return -1; ripng_instance_enable(ripng, vrf, socket); } return 0; } static int ripng_vrf_disable(struct vrf *vrf) { struct ripng *ripng; ripng = ripng_lookup_by_vrf_name(vrf->name); if (!ripng || !ripng->enabled) return 0; if (IS_RIPNG_DEBUG_EVENT) zlog_debug("%s: VRF %s(%u) disabled", __func__, vrf->name, vrf->vrf_id); /* Deactivate the VRF RIPng instance. */ if (ripng->enabled) ripng_instance_disable(ripng); return 0; } void ripng_vrf_init(void) { vrf_init(ripng_vrf_new, ripng_vrf_enable, ripng_vrf_disable, ripng_vrf_delete, NULL); } void ripng_vrf_terminate(void) { vrf_terminate(); } /* Initialize ripng structure and set commands. */ void ripng_init(void) { /* Install RIPNG_NODE. */ install_node(&cmd_ripng_node, ripng_config_write); /* Install ripng commands. */ install_element(VIEW_NODE, &show_ipv6_ripng_cmd); install_element(VIEW_NODE, &show_ipv6_ripng_status_cmd); install_default(RIPNG_NODE); #if 0 install_element (VIEW_NODE, &show_ipv6_protocols_cmd); install_element (RIPNG_NODE, &ripng_update_timer_cmd); install_element (RIPNG_NODE, &no_ripng_update_timer_cmd); install_element (RIPNG_NODE, &ripng_timeout_timer_cmd); install_element (RIPNG_NODE, &no_ripng_timeout_timer_cmd); install_element (RIPNG_NODE, &ripng_garbage_timer_cmd); install_element (RIPNG_NODE, &no_ripng_garbage_timer_cmd); #endif /* 0 */ ripng_if_init(); ripng_debug_init(); /* Access list install. */ access_list_init(); access_list_add_hook(ripng_distribute_update_all_wrapper); access_list_delete_hook(ripng_distribute_update_all_wrapper); /* Prefix list initialize.*/ prefix_list_init(); prefix_list_add_hook(ripng_distribute_update_all); prefix_list_delete_hook(ripng_distribute_update_all); /* Distribute list install. */ distribute_list_init(RIPNG_NODE); /* Route-map for interface. */ ripng_route_map_init(); route_map_add_hook(ripng_routemap_update); route_map_delete_hook(ripng_routemap_update); if_rmap_init(RIPNG_NODE); } frr-7.2.1/ripngd/ripngd.conf.sample0000644000000000000000000000050513610377563014111 00000000000000! -*- rip -*- ! ! RIPngd sample configuration file ! hostname ripngd password zebra ! ! debug ripng events ! debug ripng packet ! ! router ripng ! network sit1 ! route 3ffe:506::0/32 ! distribute-list local-only out sit1 ! !ipv6 access-list local-only permit 3ffe:506::0/32 !ipv6 access-list local-only deny any ! log stdout frr-7.2.1/ripngd/ripngd.h0000644000000000000000000003443313610377563012142 00000000000000/* * RIPng related value and structure. * Copyright (C) 1998 Kunihiro Ishiguro * * This file is part of GNU Zebra. * * GNU Zebra is free software; you can redistribute it and/or modify it * under the terms of the GNU General Public License as published by the * Free Software Foundation; either version 2, or (at your option) any * later version. * * GNU Zebra 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; see the file COPYING; if not, write to the Free Software * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA */ #ifndef _ZEBRA_RIPNG_RIPNGD_H #define _ZEBRA_RIPNG_RIPNGD_H #include #include #include #include #include /* RIPng version and port number. */ #define RIPNG_V1 1 #define RIPNG_PORT_DEFAULT 521 #define RIPNG_VTY_PORT 2603 #define RIPNG_MAX_PACKET_SIZE 1500 #define RIPNG_PRIORITY_DEFAULT 0 /* RIPng commands. */ #define RIPNG_REQUEST 1 #define RIPNG_RESPONSE 2 /* RIPng metric and multicast group address. */ #define RIPNG_METRIC_INFINITY 16 #define RIPNG_METRIC_NEXTHOP 0xff #define RIPNG_GROUP "ff02::9" /* RIPng peer timeout value. */ #define RIPNG_PEER_TIMER_DEFAULT 180 /* Default config file name. */ #define RIPNG_DEFAULT_CONFIG "ripngd.conf" /* RIPng route types. */ #define RIPNG_ROUTE_RTE 0 #define RIPNG_ROUTE_STATIC 1 #define RIPNG_ROUTE_DEFAULT 2 #define RIPNG_ROUTE_REDISTRIBUTE 3 #define RIPNG_ROUTE_INTERFACE 4 #define RIPNG_ROUTE_AGGREGATE 5 /* Interface send/receive configuration. */ #define RIPNG_SEND_UNSPEC 0 #define RIPNG_SEND_OFF 1 #define RIPNG_RECEIVE_UNSPEC 0 #define RIPNG_RECEIVE_OFF 1 /* RIP default route's accept/announce methods. */ #define RIPNG_DEFAULT_ADVERTISE_UNSPEC 0 #define RIPNG_DEFAULT_ADVERTISE_NONE 1 #define RIPNG_DEFAULT_ADVERTISE 2 #define RIPNG_DEFAULT_ACCEPT_UNSPEC 0 #define RIPNG_DEFAULT_ACCEPT_NONE 1 #define RIPNG_DEFAULT_ACCEPT 2 /* For max RTE calculation. */ #ifndef IPV6_HDRLEN #define IPV6_HDRLEN 40 #endif /* IPV6_HDRLEN */ #ifndef IFMINMTU #define IFMINMTU 576 #endif /* IFMINMTU */ /* YANG paths */ #define RIPNG_INSTANCE "/frr-ripngd:ripngd/instance" #define RIPNG_IFACE "/frr-interface:lib/interface/frr-ripngd:ripng" DECLARE_MGROUP(RIPNGD) /* RIPng structure. */ struct ripng { RB_ENTRY(ripng) entry; /* VRF this routing instance is associated with. */ char *vrf_name; /* VRF backpointer (might be NULL if the VRF doesn't exist). */ struct vrf *vrf; /* Status of the routing instance. */ bool enabled; /* RIPng socket. */ int sock; /* RIPng Parameters.*/ uint8_t command; uint8_t version; uint16_t update_time; uint16_t timeout_time; uint16_t garbage_time; int max_mtu; uint8_t default_metric; /* Input/output buffer of RIPng. */ struct stream *ibuf; struct stream *obuf; /* RIPng routing information base. */ struct agg_table *table; /* Linked list of RIPng peers. */ struct list *peer_list; /* RIPng enabled interfaces. */ vector enable_if; /* RIPng enabled networks. */ struct agg_table *enable_network; /* Vector to store passive-interface name. */ vector passive_interface; /* RIPng offset-lists. */ struct list *offset_list_master; /* RIPng threads. */ struct thread *t_read; struct thread *t_write; struct thread *t_update; struct thread *t_garbage; struct thread *t_zebra; /* Triggered update hack. */ int trigger; struct thread *t_triggered_update; struct thread *t_triggered_interval; /* RIPng ECMP flag */ bool ecmp; /* RIPng redistribute configuration. */ struct { bool enabled; struct { char *name; struct route_map *map; } route_map; bool metric_config; uint8_t metric; } redist[ZEBRA_ROUTE_MAX]; /* For distribute-list container */ struct distribute_ctx *distribute_ctx; /* For if_rmap container */ struct if_rmap_ctx *if_rmap_ctx; }; RB_HEAD(ripng_instance_head, ripng); RB_PROTOTYPE(ripng_instance_head, ripng, entry, ripng_instance_compare) /* Routing table entry. */ struct rte { struct in6_addr addr; /* RIPng destination prefix */ uint16_t tag; /* RIPng tag */ uint8_t prefixlen; /* Length of the RIPng prefix */ uint8_t metric; /* Metric of the RIPng route */ /* The nexthop is stored by the structure * ripng_nexthop within ripngd.c */ }; /* RIPNG send packet. */ struct ripng_packet { uint8_t command; uint8_t version; uint16_t zero; struct rte rte[1]; }; /* Each route's information. */ struct ripng_info { /* This route's type. Static, ripng or aggregate. */ uint8_t type; /* Sub type for static route. */ uint8_t sub_type; /* RIPng specific information */ struct in6_addr nexthop; struct in6_addr from; /* Which interface does this route come from. */ ifindex_t ifindex; /* Metric of this route. */ uint8_t metric; /* Tag field of RIPng packet.*/ uint16_t tag; /* For aggregation. */ unsigned int suppress; /* Flags of RIPng route. */ #define RIPNG_RTF_FIB 1 #define RIPNG_RTF_CHANGED 2 uint8_t flags; /* Garbage collect timer. */ struct thread *t_timeout; struct thread *t_garbage_collect; /* Route-map features - this variables can be changed. */ struct in6_addr nexthop_out; uint8_t metric_set; uint8_t metric_out; uint16_t tag_out; struct agg_node *rp; }; #ifdef notyet #if 0 /* RIPng tag structure. */ struct ripng_tag { /* Tag value. */ uint16_t tag; /* Port. */ uint16_t port; /* Multicast group. */ struct in6_addr maddr; /* Table number. */ int table; /* Distance. */ int distance; /* Split horizon. */ uint8_t split_horizon; /* Poison reverse. */ uint8_t poison_reverse; }; #endif /* 0 */ #endif /* not yet */ typedef enum { RIPNG_NO_SPLIT_HORIZON = 0, RIPNG_SPLIT_HORIZON, RIPNG_SPLIT_HORIZON_POISONED_REVERSE } split_horizon_policy_t; /* RIPng specific interface configuration. */ struct ripng_interface { /* Parent routing instance. */ struct ripng *ripng; /* RIPng is enabled on this interface. */ int enable_network; int enable_interface; /* RIPng is running on this interface. */ int running; /* Split horizon flag. */ split_horizon_policy_t split_horizon; /* For filter type slot. */ #define RIPNG_FILTER_IN 0 #define RIPNG_FILTER_OUT 1 #define RIPNG_FILTER_MAX 2 /* Access-list. */ struct access_list *list[RIPNG_FILTER_MAX]; /* Prefix-list. */ struct prefix_list *prefix[RIPNG_FILTER_MAX]; /* Route-map. */ struct route_map *routemap[RIPNG_FILTER_MAX]; #ifdef notyet #if 0 /* RIPng tag configuration. */ struct ripng_tag *rtag; #endif /* 0 */ #endif /* notyet */ /* Default information originate. */ uint8_t default_originate; /* Default information only. */ uint8_t default_only; /* Wake up thread. */ struct thread *t_wakeup; /* Passive interface. */ int passive; }; /* RIPng peer information. */ struct ripng_peer { /* Parent routing instance. */ struct ripng *ripng; /* Peer address. */ struct in6_addr addr; /* Peer RIPng tag value. */ int domain; /* Last update time. */ time_t uptime; /* Peer RIP version. */ uint8_t version; /* Statistics. */ int recv_badpackets; int recv_badroutes; /* Timeout thread. */ struct thread *t_timeout; }; /* All RIPng events. */ enum ripng_event { RIPNG_READ, RIPNG_ZEBRA, RIPNG_REQUEST_EVENT, RIPNG_UPDATE_EVENT, RIPNG_TRIGGERED_UPDATE, }; /* RIPng timer on/off macro. */ #define RIPNG_TIMER_ON(T,F,V) thread_add_timer (master, (F), rinfo, (V), &(T)) #define RIPNG_TIMER_OFF(T) \ do { \ if (T) { \ thread_cancel(T); \ (T) = NULL; \ } \ } while (0) #define RIPNG_OFFSET_LIST_IN 0 #define RIPNG_OFFSET_LIST_OUT 1 #define RIPNG_OFFSET_LIST_MAX 2 struct ripng_offset_list { /* Parent routing instance. */ struct ripng *ripng; char *ifname; struct { char *alist_name; /* struct access_list *alist; */ uint8_t metric; } direct[RIPNG_OFFSET_LIST_MAX]; }; /* Extern variables. */ extern struct zebra_privs_t ripngd_privs; extern struct thread_master *master; extern struct ripng_instance_head ripng_instances; /* Prototypes. */ extern void ripng_init(void); extern void ripng_clean(struct ripng *ripng); extern void ripng_clean_network(struct ripng *ripng); extern void ripng_interface_clean(struct ripng *ripng); extern int ripng_enable_network_add(struct ripng *ripng, struct prefix *p); extern int ripng_enable_network_delete(struct ripng *ripng, struct prefix *p); extern int ripng_enable_if_add(struct ripng *ripng, const char *ifname); extern int ripng_enable_if_delete(struct ripng *ripng, const char *ifname); extern int ripng_passive_interface_set(struct ripng *ripng, const char *ifname); extern int ripng_passive_interface_unset(struct ripng *ripng, const char *ifname); extern void ripng_passive_interface_clean(struct ripng *ripng); extern void ripng_if_init(void); extern void ripng_route_map_init(void); extern void ripng_zebra_vrf_register(struct vrf *vrf); extern void ripng_zebra_vrf_deregister(struct vrf *vrf); extern void ripng_terminate(void); /* zclient_init() is done by ripng_zebra.c:zebra_init() */ extern void zebra_init(struct thread_master *); extern void ripng_zebra_stop(void); extern void ripng_redistribute_conf_update(struct ripng *ripng, int type); extern void ripng_redistribute_conf_delete(struct ripng *ripng, int type); extern void ripng_peer_update(struct ripng *ripng, struct sockaddr_in6 *from, uint8_t version); extern void ripng_peer_bad_route(struct ripng *ripng, struct sockaddr_in6 *from); extern void ripng_peer_bad_packet(struct ripng *ripng, struct sockaddr_in6 *from); extern void ripng_peer_display(struct vty *vty, struct ripng *ripng); extern struct ripng_peer *ripng_peer_lookup(struct ripng *ripng, struct in6_addr *addr); extern struct ripng_peer *ripng_peer_lookup_next(struct ripng *ripng, struct in6_addr *addr); extern int ripng_peer_list_cmp(struct ripng_peer *p1, struct ripng_peer *p2); extern void ripng_peer_list_del(void *arg); extern struct ripng_offset_list *ripng_offset_list_new(struct ripng *ripng, const char *ifname); extern void ripng_offset_list_del(struct ripng_offset_list *offset); extern void ripng_offset_list_free(struct ripng_offset_list *offset); extern struct ripng_offset_list *ripng_offset_list_lookup(struct ripng *ripng, const char *ifname); extern int ripng_offset_list_apply_in(struct ripng *ripng, struct prefix_ipv6 *p, struct interface *ifp, uint8_t *metric); extern int ripng_offset_list_apply_out(struct ripng *ripng, struct prefix_ipv6 *p, struct interface *ifp, uint8_t *metric); extern int offset_list_cmp(struct ripng_offset_list *o1, struct ripng_offset_list *o2); extern int ripng_route_rte(struct ripng_info *rinfo); extern struct ripng_info *ripng_info_new(void); extern void ripng_info_free(struct ripng_info *rinfo); extern struct ripng *ripng_info_get_instance(const struct ripng_info *rinfo); extern void ripng_event(struct ripng *ripng, enum ripng_event event, int sock); extern int ripng_request(struct interface *ifp); extern void ripng_redistribute_add(struct ripng *ripng, int type, int sub_type, struct prefix_ipv6 *p, ifindex_t ifindex, struct in6_addr *nexthop, route_tag_t tag); extern void ripng_redistribute_delete(struct ripng *ripng, int type, int sub_type, struct prefix_ipv6 *p, ifindex_t ifindex); extern void ripng_redistribute_withdraw(struct ripng *ripng, int type); extern void ripng_ecmp_disable(struct ripng *ripng); extern void ripng_distribute_update_interface(struct interface *); extern void ripng_if_rmap_update_interface(struct interface *); extern void ripng_zebra_ipv6_add(struct ripng *ripng, struct agg_node *node); extern void ripng_zebra_ipv6_delete(struct ripng *ripng, struct agg_node *node); extern void ripng_redistribute_enable(struct ripng *ripng); extern void ripng_redistribute_disable(struct ripng *ripng); extern int ripng_redistribute_check(struct ripng *ripng, int type); extern void ripng_redistribute_write(struct vty *vty, struct ripng *ripng); extern int ripng_write_rte(int num, struct stream *s, struct prefix_ipv6 *p, struct in6_addr *nexthop, uint16_t tag, uint8_t metric); extern int ripng_send_packet(caddr_t buf, int bufsize, struct sockaddr_in6 *to, struct interface *ifp); extern void ripng_packet_dump(struct ripng_packet *packet, int size, const char *sndrcv); extern int ripng_interface_up(ZAPI_CALLBACK_ARGS); extern int ripng_interface_down(ZAPI_CALLBACK_ARGS); extern int ripng_interface_add(ZAPI_CALLBACK_ARGS); extern int ripng_interface_delete(ZAPI_CALLBACK_ARGS); extern int ripng_interface_address_add(ZAPI_CALLBACK_ARGS); extern int ripng_interface_address_delete(ZAPI_CALLBACK_ARGS); extern int ripng_interface_vrf_update(ZAPI_CALLBACK_ARGS); extern void ripng_interface_sync(struct interface *ifp); extern struct ripng *ripng_lookup_by_vrf_id(vrf_id_t vrf_id); extern struct ripng *ripng_lookup_by_vrf_name(const char *vrf_name); extern struct ripng *ripng_create(const char *vrf_name, struct vrf *vrf, int socket); extern int ripng_make_socket(struct vrf *vrf); extern int ripng_network_write(struct vty *vty, struct ripng *ripng); extern struct ripng_info *ripng_ecmp_add(struct ripng *ripng, struct ripng_info *rinfo); extern struct ripng_info *ripng_ecmp_replace(struct ripng *ripng, struct ripng_info *rinfo); extern struct ripng_info *ripng_ecmp_delete(struct ripng *ripng, struct ripng_info *rinfo); extern void ripng_vrf_init(void); extern void ripng_vrf_terminate(void); /* Northbound. */ extern void ripng_cli_init(void); extern const struct frr_yang_module_info frr_ripngd_info; #endif /* _ZEBRA_RIPNG_RIPNGD_H */ frr-7.2.1/ripngd/subdir.am0000644000000000000000000000202713610377563012307 00000000000000# # ripngd # if RIPNGD noinst_LIBRARIES += ripngd/libripng.a sbin_PROGRAMS += ripngd/ripngd vtysh_scan += \ $(top_srcdir)/ripngd/ripng_cli.c \ $(top_srcdir)/ripngd/ripng_debug.c \ $(top_srcdir)/ripngd/ripngd.c \ # end man8 += $(MANBUILD)/frr-ripngd.8 endif ripngd_libripng_a_SOURCES = \ ripngd/ripng_cli.c \ ripngd/ripng_debug.c \ ripngd/ripng_interface.c \ ripngd/ripng_nexthop.c \ ripngd/ripng_offset.c \ ripngd/ripng_northbound.c \ ripngd/ripng_peer.c \ ripngd/ripng_route.c \ ripngd/ripng_routemap.c \ ripngd/ripng_zebra.c \ ripngd/ripngd.c \ # end ripngd/ripng_cli_clippy.c: $(CLIPPY_DEPS) ripngd/ripng_cli.$(OBJEXT): ripngd/ripng_cli_clippy.c noinst_HEADERS += \ ripngd/ripng_cli.h \ ripngd/ripng_debug.h \ ripngd/ripng_nexthop.h \ ripngd/ripng_route.h \ ripngd/ripngd.h \ # end ripngd_ripngd_LDADD = ripngd/libripng.a lib/libfrr.la $(LIBCAP) ripngd_ripngd_SOURCES = \ ripngd/ripng_main.c \ # end nodist_ripngd_ripngd_SOURCES = \ yang/frr-ripngd.yang.c \ # end dist_examples_DATA += ripngd/ripngd.conf.sample frr-7.2.1/sharpd/0000755000000000000000000000000013610377563010555 500000000000000frr-7.2.1/sharpd/sharp_globals.h0000644000000000000000000000301713610377563013467 00000000000000/* * SHARP - code to track globals * Copyright (C) 2019 Cumulus Networks, Inc. * Donald Sharp * * This file is part of FRR. * * FRR is free software; you can redistribute it and/or modify it * under the terms of the GNU General Public License as published by the * Free Software Foundation; either version 2, or (at your option) any * later version. * * FRR 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; see the file COPYING; if not, write to the Free Software * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA */ #ifndef __SHARP_GLOBAL_H__ #define __SHARP_GLOBAL_H__ DECLARE_MGROUP(SHARPD) struct sharp_routes { /* The original prefix for route installation */ struct prefix orig_prefix; /* The nexthop group we are using for installation */ struct nexthop nhop; struct nexthop_group nhop_group; uint32_t total_routes; uint32_t installed_routes; uint32_t removed_routes; int32_t repeat; uint8_t inst; vrf_id_t vrf_id; struct timeval t_start; struct timeval t_end; }; struct sharp_global { /* Global data about route install/deletions */ struct sharp_routes r; /* The list of nexthops that we are watching and data about them */ struct list *nhs; }; extern struct sharp_global sg; #endif frr-7.2.1/sharpd/sharp_main.c0000644000000000000000000000660613610377563012772 00000000000000/* * SHARP - main code * Copyright (C) Cumulus Networks, Inc. * Donald Sharp * * This file is part of FRR. * * FRR is free software; you can redistribute it and/or modify it * under the terms of the GNU General Public License as published by the * Free Software Foundation; either version 2, or (at your option) any * later version. * * FRR 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; see the file COPYING; if not, write to the Free Software * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA */ #include #include #include "getopt.h" #include "thread.h" #include "prefix.h" #include "linklist.h" #include "if.h" #include "vector.h" #include "vty.h" #include "command.h" #include "filter.h" #include "plist.h" #include "stream.h" #include "log.h" #include "memory.h" #include "privs.h" #include "sigevent.h" #include "zclient.h" #include "keychain.h" #include "distribute.h" #include "libfrr.h" #include "routemap.h" #include "nexthop_group.h" #include "sharp_zebra.h" #include "sharp_vty.h" #include "sharp_globals.h" DEFINE_MGROUP(SHARPD, "sharpd") zebra_capabilities_t _caps_p[] = { }; struct zebra_privs_t sharp_privs = { #if defined(FRR_USER) && defined(FRR_GROUP) .user = FRR_USER, .group = FRR_GROUP, #endif #if defined(VTY_GROUP) .vty_group = VTY_GROUP, #endif .caps_p = _caps_p, .cap_num_p = array_size(_caps_p), .cap_num_i = 0}; struct option longopts[] = {{0}}; /* Master of threads. */ struct thread_master *master; /* SIGHUP handler. */ static void sighup(void) { zlog_info("SIGHUP received"); } /* SIGINT / SIGTERM handler. */ static void sigint(void) { zlog_notice("Terminating on signal"); exit(0); } /* SIGUSR1 handler. */ static void sigusr1(void) { zlog_rotate(); } struct quagga_signal_t sharp_signals[] = { { .signal = SIGHUP, .handler = &sighup, }, { .signal = SIGUSR1, .handler = &sigusr1, }, { .signal = SIGINT, .handler = &sigint, }, { .signal = SIGTERM, .handler = &sigint, }, }; #define SHARP_VTY_PORT 2614 static const struct frr_yang_module_info *sharpd_yang_modules[] = { }; FRR_DAEMON_INFO(sharpd, SHARP, .vty_port = SHARP_VTY_PORT, .proghelp = "Implementation of a Sharp of routes daemon.", .signals = sharp_signals, .n_signals = array_size(sharp_signals), .privs = &sharp_privs, .yang_modules = sharpd_yang_modules, .n_yang_modules = array_size(sharpd_yang_modules), ) struct sharp_global sg; static void sharp_global_init(void) { memset(&sg, 0, sizeof(sg)); sg.nhs = list_new(); } int main(int argc, char **argv, char **envp) { frr_preinit(&sharpd_di, argc, argv); frr_opt_add("", longopts, ""); while (1) { int opt; opt = frr_getopt(argc, argv, NULL); if (opt == EOF) break; switch (opt) { case 0: break; default: frr_help_exit(1); break; } } master = frr_init(); sharp_global_init(); nexthop_group_init(NULL, NULL, NULL, NULL); vrf_init(NULL, NULL, NULL, NULL, NULL); access_list_init(); route_map_init(); sharp_zebra_init(); /* Get configuration file. */ sharp_vty_init(); frr_config_fork(); frr_run(master); /* Not reached. */ return 0; } frr-7.2.1/sharpd/sharp_nht.c0000644000000000000000000000330713610377563012632 00000000000000/* * SHARP - code to track nexthops * Copyright (C) Cumulus Networks, Inc. * Donald Sharp * * This file is part of FRR. * * FRR is free software; you can redistribute it and/or modify it * under the terms of the GNU General Public License as published by the * Free Software Foundation; either version 2, or (at your option) any * later version. * * FRR 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; see the file COPYING; if not, write to the Free Software * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA */ #include #include "memory.h" #include "nexthop.h" #include "nexthop_group.h" #include "vty.h" #include "sharp_nht.h" #include "sharp_globals.h" DEFINE_MTYPE_STATIC(SHARPD, NH_TRACKER, "Nexthop Tracker") struct sharp_nh_tracker *sharp_nh_tracker_get(struct prefix *p) { struct listnode *node; struct sharp_nh_tracker *nht; for (ALL_LIST_ELEMENTS_RO(sg.nhs, node, nht)) { if (prefix_same(&nht->p, p)) break; } if (nht) return nht; nht = XCALLOC(MTYPE_NH_TRACKER, sizeof(*nht)); prefix_copy(&nht->p, p); listnode_add(sg.nhs, nht); return nht; } void sharp_nh_tracker_dump(struct vty *vty) { struct listnode *node; struct sharp_nh_tracker *nht; for (ALL_LIST_ELEMENTS_RO(sg.nhs, node, nht)) { char buf[PREFIX_STRLEN]; vty_out(vty, "%s: Nexthops: %u Updates: %u\n", prefix2str(&nht->p, buf, sizeof(buf)), nht->nhop_num, nht->updates); } } frr-7.2.1/sharpd/sharp_nht.h0000644000000000000000000000223113610377563012632 00000000000000/* * SHARP - code to track nexthops * Copyright (C) Cumulus Networks, Inc. * Donald Sharp * * This file is part of FRR. * * FRR is free software; you can redistribute it and/or modify it * under the terms of the GNU General Public License as published by the * Free Software Foundation; either version 2, or (at your option) any * later version. * * FRR 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; see the file COPYING; if not, write to the Free Software * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA */ #ifndef __SHARP_NHT_H__ #define __SHARP_NHT_H__ struct sharp_nh_tracker { /* What are we watching */ struct prefix p; /* Number of valid nexthops */ uint32_t nhop_num; uint32_t updates; }; extern struct sharp_nh_tracker *sharp_nh_tracker_get(struct prefix *p); extern void sharp_nh_tracker_dump(struct vty *vty); #endif frr-7.2.1/sharpd/sharp_vty.c0000644000000000000000000002236213610377563012665 00000000000000/* * SHARP - vty code * Copyright (C) Cumulus Networks, Inc. * Donald Sharp * * This file is part of FRR. * * FRR is free software; you can redistribute it and/or modify it * under the terms of the GNU General Public License as published by the * Free Software Foundation; either version 2, or (at your option) any * later version. * * FRR 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; see the file COPYING; if not, write to the Free Software * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA */ #include #include "vty.h" #include "command.h" #include "prefix.h" #include "nexthop.h" #include "log.h" #include "vrf.h" #include "zclient.h" #include "nexthop_group.h" #include "sharpd/sharp_globals.h" #include "sharpd/sharp_zebra.h" #include "sharpd/sharp_nht.h" #include "sharpd/sharp_vty.h" #ifndef VTYSH_EXTRACT_PL #include "sharpd/sharp_vty_clippy.c" #endif DEFPY(watch_nexthop_v6, watch_nexthop_v6_cmd, "sharp watch [vrf NAME$vrf_name] [connected$connected]", "Sharp routing Protocol\n" "Watch for changes\n" "The vrf we would like to watch if non-default\n" "The NAME of the vrf\n" "Watch for nexthop changes\n" "The v6 nexthop to signal for watching\n" "Watch for import check changes\n" "The v6 prefix to signal for watching\n" "Should the route be connected\n") { struct vrf *vrf; struct prefix p; bool type_import; if (!vrf_name) vrf_name = VRF_DEFAULT_NAME; vrf = vrf_lookup_by_name(vrf_name); if (!vrf) { vty_out(vty, "The vrf NAME specified: %s does not exist\n", vrf_name); return CMD_WARNING; } memset(&p, 0, sizeof(p)); if (n) { type_import = false; p.prefixlen = 128; memcpy(&p.u.prefix6, &nhop, 16); p.family = AF_INET6; } else { type_import = true; p = *(const struct prefix *)inhop; } sharp_nh_tracker_get(&p); sharp_zebra_nexthop_watch(&p, vrf->vrf_id, type_import, true, !!connected); return CMD_SUCCESS; } DEFPY(watch_nexthop_v4, watch_nexthop_v4_cmd, "sharp watch [vrf NAME$vrf_name] [connected$connected]", "Sharp routing Protocol\n" "Watch for changes\n" "The vrf we would like to watch if non-default\n" "The NAME of the vrf\n" "Watch for nexthop changes\n" "The v4 address to signal for watching\n" "Watch for import check changes\n" "The v4 prefix for import check to watch\n" "Should the route be connected\n") { struct vrf *vrf; struct prefix p; bool type_import; if (!vrf_name) vrf_name = VRF_DEFAULT_NAME; vrf = vrf_lookup_by_name(vrf_name); if (!vrf) { vty_out(vty, "The vrf NAME specified: %s does not exist\n", vrf_name); return CMD_WARNING; } memset(&p, 0, sizeof(p)); if (n) { type_import = false; p.prefixlen = 32; p.u.prefix4 = nhop; p.family = AF_INET; } else { type_import = true; p = *(const struct prefix *)inhop; } sharp_nh_tracker_get(&p); sharp_zebra_nexthop_watch(&p, vrf->vrf_id, type_import, true, !!connected); return CMD_SUCCESS; } DEFPY(sharp_nht_data_dump, sharp_nht_data_dump_cmd, "sharp data nexthop", "Sharp routing Protocol\n" "Nexthop information\n" "Data Dump\n") { sharp_nh_tracker_dump(vty); return CMD_SUCCESS; } DEFPY (install_routes_data_dump, install_routes_data_dump_cmd, "sharp data route", "Sharp routing Protocol\n" "Data about what is going on\n" "Route Install/Removal Information\n") { char buf[PREFIX_STRLEN]; struct timeval r; timersub(&sg.r.t_end, &sg.r.t_start, &r); vty_out(vty, "Prefix: %s Total: %u %u %u Time: %jd.%ld\n", prefix2str(&sg.r.orig_prefix, buf, sizeof(buf)), sg.r.total_routes, sg.r.installed_routes, sg.r.removed_routes, (intmax_t)r.tv_sec, (long)r.tv_usec); return CMD_SUCCESS; } DEFPY (install_routes, install_routes_cmd, "sharp install routes [vrf NAME$vrf_name] |nexthop-group NHGNAME$nexthop_group> (1-1000000)$routes [instance (0-255)$instance] [repeat (2-1000)$rpt]", "Sharp routing Protocol\n" "install some routes\n" "Routes to install\n" "The vrf we would like to install into if non-default\n" "The NAME of the vrf\n" "v4 Address to start /32 generation at\n" "v6 Address to start /32 generation at\n" "Nexthop to use(Can be an IPv4 or IPv6 address)\n" "V4 Nexthop address to use\n" "V6 Nexthop address to use\n" "Nexthop-Group to use\n" "The Name of the nexthop-group\n" "How many to create\n" "Instance to use\n" "Instance\n" "Should we repeat this command\n" "How many times to repeat this command\n") { struct vrf *vrf; struct prefix prefix; uint32_t rts; sg.r.total_routes = routes; sg.r.installed_routes = 0; if (rpt >= 2) sg.r.repeat = rpt * 2; else sg.r.repeat = 0; memset(&prefix, 0, sizeof(prefix)); memset(&sg.r.orig_prefix, 0, sizeof(sg.r.orig_prefix)); memset(&sg.r.nhop, 0, sizeof(sg.r.nhop)); memset(&sg.r.nhop_group, 0, sizeof(sg.r.nhop_group)); if (start4.s_addr != 0) { prefix.family = AF_INET; prefix.prefixlen = 32; prefix.u.prefix4 = start4; } else { prefix.family = AF_INET6; prefix.prefixlen = 128; prefix.u.prefix6 = start6; } sg.r.orig_prefix = prefix; if (!vrf_name) vrf_name = VRF_DEFAULT_NAME; vrf = vrf_lookup_by_name(vrf_name); if (!vrf) { vty_out(vty, "The vrf NAME specified: %s does not exist\n", vrf_name); return CMD_WARNING; } if (nexthop_group) { struct nexthop_group_cmd *nhgc = nhgc_find(nexthop_group); if (!nhgc) { vty_out(vty, "Specified Nexthop Group: %s does not exist\n", nexthop_group); return CMD_WARNING; } sg.r.nhop_group.nexthop = nhgc->nhg.nexthop; } else { if (nexthop4.s_addr != INADDR_ANY) { sg.r.nhop.gate.ipv4 = nexthop4; sg.r.nhop.type = NEXTHOP_TYPE_IPV4; } else { sg.r.nhop.gate.ipv6 = nexthop6; sg.r.nhop.type = NEXTHOP_TYPE_IPV6; } sg.r.nhop.vrf_id = vrf->vrf_id; sg.r.nhop_group.nexthop = &sg.r.nhop; } sg.r.inst = instance; sg.r.vrf_id = vrf->vrf_id; rts = routes; sharp_install_routes_helper(&prefix, sg.r.vrf_id, sg.r.inst, &sg.r.nhop_group, rts); return CMD_SUCCESS; } DEFPY(vrf_label, vrf_label_cmd, "sharp label vrf NAME$vrf_name label (0-100000)$label", "Sharp Routing Protocol\n" "Give a vrf a label\n" "Pop and forward for IPv4\n" "Pop and forward for IPv6\n" VRF_CMD_HELP_STR "The label to use, 0 specifies remove the label installed from previous\n" "Specified range to use\n") { struct vrf *vrf; afi_t afi = (ipv4) ? AFI_IP : AFI_IP6; if (strcmp(vrf_name, "default") == 0) vrf = vrf_lookup_by_id(VRF_DEFAULT); else vrf = vrf_lookup_by_name(vrf_name); if (!vrf) { vty_out(vty, "Unable to find vrf you silly head"); return CMD_WARNING_CONFIG_FAILED; } if (label == 0) label = MPLS_LABEL_NONE; vrf_label_add(vrf->vrf_id, afi, label); return CMD_SUCCESS; } DEFPY (remove_routes, remove_routes_cmd, "sharp remove routes [vrf NAME$vrf_name] (1-1000000)$routes [instance (0-255)$instance]", "Sharp Routing Protocol\n" "Remove some routes\n" "Routes to remove\n" "The vrf we would like to remove from if non-default\n" "The NAME of the vrf\n" "v4 Starting spot\n" "v6 Starting spot\n" "Routes to uninstall\n" "instance to use\n" "Value of instance\n") { struct vrf *vrf; struct prefix prefix; sg.r.total_routes = routes; sg.r.removed_routes = 0; uint32_t rts; memset(&prefix, 0, sizeof(prefix)); if (start4.s_addr != 0) { prefix.family = AF_INET; prefix.prefixlen = 32; prefix.u.prefix4 = start4; } else { prefix.family = AF_INET6; prefix.prefixlen = 128; prefix.u.prefix6 = start6; } vrf = vrf_lookup_by_name(vrf_name ? vrf_name : VRF_DEFAULT_NAME); if (!vrf) { vty_out(vty, "The vrf NAME specified: %s does not exist\n", vrf_name ? vrf_name : VRF_DEFAULT_NAME); return CMD_WARNING; } sg.r.inst = instance; sg.r.vrf_id = vrf->vrf_id; rts = routes; sharp_remove_routes_helper(&prefix, sg.r.vrf_id, sg.r.inst, rts); return CMD_SUCCESS; } DEFUN_NOSH (show_debugging_sharpd, show_debugging_sharpd_cmd, "show debugging [sharp]", SHOW_STR DEBUG_STR "Sharp Information\n") { vty_out(vty, "Sharp debugging status\n"); return CMD_SUCCESS; } void sharp_vty_init(void) { install_element(ENABLE_NODE, &install_routes_data_dump_cmd); install_element(ENABLE_NODE, &install_routes_cmd); install_element(ENABLE_NODE, &remove_routes_cmd); install_element(ENABLE_NODE, &vrf_label_cmd); install_element(ENABLE_NODE, &sharp_nht_data_dump_cmd); install_element(ENABLE_NODE, &watch_nexthop_v6_cmd); install_element(ENABLE_NODE, &watch_nexthop_v4_cmd); install_element(VIEW_NODE, &show_debugging_sharpd_cmd); return; } frr-7.2.1/sharpd/sharp_vty.h0000644000000000000000000000164013610377563012666 00000000000000/* * VTY library for SHARP * Copyright (C) Cumulus Networks, Inc. * Donald Sharp * * This file is part of FRR. * * FRR is free software; you can redistribute it and/or modify it * under the terms of the GNU General Public License as published by the * Free Software Foundation; either version 2, or (at your option) any * later version. * * FRR 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; see the file COPYING; if not, write to the Free Software * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA */ #ifndef __SHARP_VTY_H__ #define __SHARP_VTY_H__ extern void sharp_vty_init(void); #endif frr-7.2.1/sharpd/sharp_zebra.c0000644000000000000000000002305713610377563013150 00000000000000/* * Zebra connect code. * Copyright (C) Cumulus Networks, Inc. * Donald Sharp * * This file is part of FRR. * * FRR is free software; you can redistribute it and/or modify it * under the terms of the GNU General Public License as published by the * Free Software Foundation; either version 2, or (at your option) any * later version. * * FRR 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; see the file COPYING; if not, write to the Free Software * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA */ #include #include "thread.h" #include "command.h" #include "network.h" #include "prefix.h" #include "routemap.h" #include "table.h" #include "stream.h" #include "memory.h" #include "zclient.h" #include "filter.h" #include "plist.h" #include "log.h" #include "nexthop.h" #include "nexthop_group.h" #include "sharp_globals.h" #include "sharp_nht.h" #include "sharp_zebra.h" /* Zebra structure to hold current status. */ struct zclient *zclient = NULL; /* For registering threads. */ extern struct thread_master *master; static struct interface *zebra_interface_if_lookup(struct stream *s) { char ifname_tmp[INTERFACE_NAMSIZ]; /* Read interface name. */ stream_get(ifname_tmp, s, INTERFACE_NAMSIZ); /* And look it up. */ return if_lookup_by_name(ifname_tmp, VRF_DEFAULT); } /* Inteface addition message from zebra. */ static int interface_add(ZAPI_CALLBACK_ARGS) { struct interface *ifp; ifp = zebra_interface_add_read(zclient->ibuf, vrf_id); if (!ifp->info) return 0; return 0; } static int interface_delete(ZAPI_CALLBACK_ARGS) { struct interface *ifp; struct stream *s; s = zclient->ibuf; /* zebra_interface_state_read () updates interface structure in iflist */ ifp = zebra_interface_state_read(s, vrf_id); if (ifp == NULL) return 0; if_set_index(ifp, IFINDEX_INTERNAL); return 0; } static int interface_address_add(ZAPI_CALLBACK_ARGS) { zebra_interface_address_read(cmd, zclient->ibuf, vrf_id); return 0; } static int interface_address_delete(ZAPI_CALLBACK_ARGS) { struct connected *c; c = zebra_interface_address_read(cmd, zclient->ibuf, vrf_id); if (!c) return 0; connected_free(c); return 0; } static int interface_state_up(ZAPI_CALLBACK_ARGS) { zebra_interface_if_lookup(zclient->ibuf); return 0; } static int interface_state_down(ZAPI_CALLBACK_ARGS) { zebra_interface_state_read(zclient->ibuf, vrf_id); return 0; } void sharp_install_routes_helper(struct prefix *p, vrf_id_t vrf_id, uint8_t instance, struct nexthop_group *nhg, uint32_t routes) { uint32_t temp, i; bool v4 = false; zlog_debug("Inserting %u routes", routes); if (p->family == AF_INET) { v4 = true; temp = ntohl(p->u.prefix4.s_addr); } else temp = ntohl(p->u.val32[3]); monotime(&sg.r.t_start); for (i = 0; i < routes; i++) { route_add(p, vrf_id, (uint8_t)instance, nhg); if (v4) p->u.prefix4.s_addr = htonl(++temp); else p->u.val32[3] = htonl(++temp); } } void sharp_remove_routes_helper(struct prefix *p, vrf_id_t vrf_id, uint8_t instance, uint32_t routes) { uint32_t temp, i; bool v4 = false; zlog_debug("Removing %u routes", routes); if (p->family == AF_INET) { v4 = true; temp = ntohl(p->u.prefix4.s_addr); } else temp = ntohl(p->u.val32[3]); monotime(&sg.r.t_start); for (i = 0; i < routes; i++) { route_delete(p, vrf_id, (uint8_t)instance); if (v4) p->u.prefix4.s_addr = htonl(++temp); else p->u.val32[3] = htonl(++temp); } } static void handle_repeated(bool installed) { struct prefix p = sg.r.orig_prefix; sg.r.repeat--; if (sg.r.repeat <= 0) return; if (installed) { sg.r.removed_routes = 0; sharp_remove_routes_helper(&p, sg.r.vrf_id, sg.r.inst, sg.r.total_routes); } if (!installed) { sg.r.installed_routes = 0; sharp_install_routes_helper(&p, sg.r.vrf_id, sg.r.inst, &sg.r.nhop_group, sg.r.total_routes); } } static int route_notify_owner(ZAPI_CALLBACK_ARGS) { struct timeval r; struct prefix p; enum zapi_route_notify_owner note; uint32_t table; if (!zapi_route_notify_decode(zclient->ibuf, &p, &table, ¬e)) return -1; switch (note) { case ZAPI_ROUTE_INSTALLED: sg.r.installed_routes++; if (sg.r.total_routes == sg.r.installed_routes) { monotime(&sg.r.t_end); timersub(&sg.r.t_end, &sg.r.t_start, &r); zlog_debug("Installed All Items %jd.%ld", (intmax_t)r.tv_sec, (long)r.tv_usec); handle_repeated(true); } break; case ZAPI_ROUTE_FAIL_INSTALL: zlog_debug("Failed install of route"); break; case ZAPI_ROUTE_BETTER_ADMIN_WON: zlog_debug("Better Admin Distance won over us"); break; case ZAPI_ROUTE_REMOVED: sg.r.removed_routes++; if (sg.r.total_routes == sg.r.removed_routes) { monotime(&sg.r.t_end); timersub(&sg.r.t_end, &sg.r.t_start, &r); zlog_debug("Removed all Items %jd.%ld", (intmax_t)r.tv_sec, (long)r.tv_usec); handle_repeated(false); } break; case ZAPI_ROUTE_REMOVE_FAIL: zlog_debug("Route removal Failure"); break; } return 0; } static void zebra_connected(struct zclient *zclient) { zclient_send_reg_requests(zclient, VRF_DEFAULT); } void vrf_label_add(vrf_id_t vrf_id, afi_t afi, mpls_label_t label) { zclient_send_vrf_label(zclient, vrf_id, afi, label, ZEBRA_LSP_SHARP); } void route_add(struct prefix *p, vrf_id_t vrf_id, uint8_t instance, struct nexthop_group *nhg) { struct zapi_route api; struct zapi_nexthop *api_nh; struct nexthop *nh; int i = 0; memset(&api, 0, sizeof(api)); api.vrf_id = vrf_id; api.type = ZEBRA_ROUTE_SHARP; api.instance = instance; api.safi = SAFI_UNICAST; memcpy(&api.prefix, p, sizeof(*p)); SET_FLAG(api.flags, ZEBRA_FLAG_ALLOW_RECURSION); SET_FLAG(api.message, ZAPI_MESSAGE_NEXTHOP); for (ALL_NEXTHOPS_PTR(nhg, nh)) { api_nh = &api.nexthops[i]; api_nh->vrf_id = nh->vrf_id; api_nh->type = nh->type; switch (nh->type) { case NEXTHOP_TYPE_IPV4: api_nh->gate = nh->gate; break; case NEXTHOP_TYPE_IPV4_IFINDEX: api_nh->gate = nh->gate; api_nh->ifindex = nh->ifindex; break; case NEXTHOP_TYPE_IFINDEX: api_nh->ifindex = nh->ifindex; break; case NEXTHOP_TYPE_IPV6: memcpy(&api_nh->gate.ipv6, &nh->gate.ipv6, 16); break; case NEXTHOP_TYPE_IPV6_IFINDEX: api_nh->ifindex = nh->ifindex; memcpy(&api_nh->gate.ipv6, &nh->gate.ipv6, 16); break; case NEXTHOP_TYPE_BLACKHOLE: api_nh->bh_type = nh->bh_type; break; } i++; } api.nexthop_num = i; zclient_route_send(ZEBRA_ROUTE_ADD, zclient, &api); } void route_delete(struct prefix *p, vrf_id_t vrf_id, uint8_t instance) { struct zapi_route api; memset(&api, 0, sizeof(api)); api.vrf_id = vrf_id; api.type = ZEBRA_ROUTE_SHARP; api.safi = SAFI_UNICAST; api.instance = instance; memcpy(&api.prefix, p, sizeof(*p)); zclient_route_send(ZEBRA_ROUTE_DELETE, zclient, &api); return; } void sharp_zebra_nexthop_watch(struct prefix *p, vrf_id_t vrf_id, bool import, bool watch, bool connected) { int command; if (!import) { command = ZEBRA_NEXTHOP_REGISTER; if (!watch) command = ZEBRA_NEXTHOP_UNREGISTER; } else { command = ZEBRA_IMPORT_ROUTE_REGISTER; if (!watch) command = ZEBRA_IMPORT_ROUTE_UNREGISTER; } if (zclient_send_rnh(zclient, command, p, connected, vrf_id) < 0) zlog_warn("%s: Failure to send nexthop to zebra", __PRETTY_FUNCTION__); } static int sharp_nexthop_update(ZAPI_CALLBACK_ARGS) { struct sharp_nh_tracker *nht; struct zapi_route nhr; char buf[PREFIX_STRLEN]; int i; if (!zapi_nexthop_update_decode(zclient->ibuf, &nhr)) { zlog_warn("%s: Decode of update failed", __PRETTY_FUNCTION__); return 0; } zlog_debug("Received update for %s", prefix2str(&nhr.prefix, buf, sizeof(buf))); nht = sharp_nh_tracker_get(&nhr.prefix); nht->nhop_num = nhr.nexthop_num; nht->updates++; for (i = 0; i < nhr.nexthop_num; i++) { struct zapi_nexthop *znh = &nhr.nexthops[i]; switch (znh->type) { case NEXTHOP_TYPE_IPV4_IFINDEX: case NEXTHOP_TYPE_IPV4: zlog_debug( "\tNexthop %s, type: %d, ifindex: %d, vrf: %d, label_num: %d", inet_ntop(AF_INET, &znh->gate.ipv4.s_addr, buf, sizeof(buf)), znh->type, znh->ifindex, znh->vrf_id, znh->label_num); break; case NEXTHOP_TYPE_IPV6_IFINDEX: case NEXTHOP_TYPE_IPV6: zlog_debug( "\tNexthop %s, type: %d, ifindex: %d, vrf: %d, label_num: %d", inet_ntop(AF_INET6, &znh->gate.ipv6, buf, sizeof(buf)), znh->type, znh->ifindex, znh->vrf_id, znh->label_num); break; case NEXTHOP_TYPE_IFINDEX: zlog_debug("\tNexthop IFINDEX: %d, ifindex: %d", znh->type, znh->ifindex); break; case NEXTHOP_TYPE_BLACKHOLE: zlog_debug("\tNexthop blackhole"); break; } } return 0; } extern struct zebra_privs_t sharp_privs; void sharp_zebra_init(void) { struct zclient_options opt = {.receive_notify = true}; zclient = zclient_new(master, &opt); zclient_init(zclient, ZEBRA_ROUTE_SHARP, 0, &sharp_privs); zclient->zebra_connected = zebra_connected; zclient->interface_add = interface_add; zclient->interface_delete = interface_delete; zclient->interface_up = interface_state_up; zclient->interface_down = interface_state_down; zclient->interface_address_add = interface_address_add; zclient->interface_address_delete = interface_address_delete; zclient->route_notify_owner = route_notify_owner; zclient->nexthop_update = sharp_nexthop_update; zclient->import_check_update = sharp_nexthop_update; } frr-7.2.1/sharpd/sharp_zebra.h0000644000000000000000000000310313610377563013143 00000000000000/* * Zebra connect library for SHARP * Copyright (C) Cumulus Networks, Inc. * Donald Sharp * * This file is part of FRR. * * FRR is free software; you can redistribute it and/or modify it * under the terms of the GNU General Public License as published by the * Free Software Foundation; either version 2, or (at your option) any * later version. * * FRR 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; see the file COPYING; if not, write to the Free Software * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA */ #ifndef __SHARP_ZEBRA_H__ #define __SHARP_ZEBRA_H__ extern void sharp_zebra_init(void); extern void vrf_label_add(vrf_id_t vrf_id, afi_t afi, mpls_label_t label); extern void route_add(struct prefix *p, vrf_id_t, uint8_t instance, struct nexthop_group *nhg); extern void route_delete(struct prefix *p, vrf_id_t vrf_id, uint8_t instance); extern void sharp_zebra_nexthop_watch(struct prefix *p, vrf_id_t vrf_id, bool import, bool watch, bool connected); extern void sharp_install_routes_helper(struct prefix *p, vrf_id_t vrf_id, uint8_t instance, struct nexthop_group *nhg, uint32_t routes); extern void sharp_remove_routes_helper(struct prefix *p, vrf_id_t vrf_id, uint8_t instance, uint32_t routes); #endif frr-7.2.1/sharpd/sharpd.conf.sample0000644000000000000000000000024013610377563014101 00000000000000! Default sharpd configuration sample ! ! There are no `default` configuration commands for sharpd ! all commands are at the view or enable level. ! log stdout frr-7.2.1/sharpd/subdir.am0000644000000000000000000000122613610377563012305 00000000000000# # sharpd # if SHARPD noinst_LIBRARIES += sharpd/libsharp.a sbin_PROGRAMS += sharpd/sharpd dist_examples_DATA += sharpd/sharpd.conf.sample vtysh_scan += $(top_srcdir)/sharpd/sharp_vty.c man8 += $(MANBUILD)/frr-sharpd.8 endif sharpd_libsharp_a_SOURCES = \ sharpd/sharp_nht.c \ sharpd/sharp_zebra.c \ sharpd/sharp_vty.c \ # end noinst_HEADERS += \ sharpd/sharp_nht.h \ sharpd/sharp_vty.h \ sharpd/sharp_globals.h \ sharpd/sharp_zebra.h \ # end sharpd/sharp_vty_clippy.c: $(CLIPPY_DEPS) sharpd/sharp_vty.$(OBJEXT): sharpd/sharp_vty_clippy.c sharpd_sharpd_SOURCES = sharpd/sharp_main.c sharpd_sharpd_LDADD = sharpd/libsharp.a lib/libfrr.la $(LIBCAP) frr-7.2.1/snapcraft/0000755000000000000000000000000013610377563011255 500000000000000frr-7.2.1/snapcraft/README.snap_build.md0000644000000000000000000000613313610377563014576 00000000000000Building your own FRRouting Snap ======================================== (Tested on Ubuntu 16.04 with Snap Version 2, does not work on Ubuntu 15.x which uses earlier versions of snaps) 1. Install snapcraft: sudo apt-get install snapcraft 2. Checkout FRRouting under a **unpriviledged** user account git clone https://github.com/frrouting/frr.git cd frr 3. (Optional) Add extra version information to `snapcraft/extra_version_info.txt`. Information in this file will be displayed with the frr.version command (simple `cat` after the display of the `zebra --version` output) 4. Run Bootstrap and make distribution tar.gz ./bootstrap.sh ./configure --with-pkg-extra-version=-MySnapVersion make dist Note: configure parameters are not important for the Snap building, except the `with-pkg-extra-version` if you want to give the Snap a specific name to mark your own unoffical build This will build `frr-something.tar.gz` - the distribution tar and the snapcraft/snapcraft.yaml with the matching version number 5. Create snap cd snapcraft snapcraft You should now end up with `frr_something.snap` Installing the snap =================== (This can be done on a different system) 1. Install snapd sudo apt-get install snapd 2. Install self-built frr snap. (`--force-dangerous` is required to install a unsigned self-built snap) snap install --force-dangerous ./frr*.snap Connect the priviledged `network-control` plug to the snap: snap connect frr:network-control core:network-control See README.usage.md for more details on setting up and using the snap DONE. The Snap will be auto-started and running. Operations ========== ### FRRouting Daemons At this time, all FRRouting daemons are auto-started. A daemon can be stopped/started with (ie ospf6d) systemctl stop snap.frr.ospf6d.service systemctl start snap.frr.ospf6d.service or disabled/enabled with systemctl disable snap.frr.ospf6d.service systemctl enable snap.frr.ospf6d.service ### FRRouting Commands All the commands are prefixed with frr. frr.vtysh -> vtysh frr.version -> Just gives version output (zebra --version) frr.readme -> Returns simple README with hints on using FRR frr.bgpd-debug -> Directly start each daemon (without service) frr.isisd-debug frr.ospf6d-debug frr.ospfd-debug frr.pimd-debug frr.ripd-debug frr.ripngd-debug frr.ldp-debug frr.zebra-debug frr.pimd-debug frr.nhrpd-debug frr.babeld-debug frr.eigrpd-debug frr.pbrd-debug frr.staticd-debug frr.bfdd-debug frr.fabricd-debug vtysh can be accessed as frr.vtysh (Make sure you have /snap/bin in your path). If access as `vtysh` instead of `frr.vtysh` is needed, you can enable it via a snap alias as follows: sudo snap alias frr vtysh This will add the vtysh command to your /snap/bin for direct access. The output of sudo snap aliases should list vtysh command alias as enabled: App Alias Notes frr.vtysh vtysh enabled frr-7.2.1/snapcraft/README.usage.md0000644000000000000000000001333513610377563013564 00000000000000Using the FRRouting Snap =============================== After installing the Snap, the priviledged plug need to be connected: snap connect frr:network-control core:network-control Enabling/Disabling FRRouting Daemons ------------------------------------------- By default (at this time), all FRRouting daemons will be enabled on installation. If you want to disable a specific daemon, then use the systemctl commands ie for `ospf6d` (OSPFv3): systemctl disable snap.frr.ospf6d.service systemctl enable snap.frr.ospf6d.service The daemons are: `ripd`, `ripngd`, `ospfd`, `ospf6d`, `isisd`, `bgpd`, `pimd`, `ldpd`, `eigrpd`, `babeld`, `nhrpd`, `bfdd`, `zebra` Commands defined by this snap ----------------------------- - `frr.vtysh`: FRRouting VTY Shell (configuration tool) - `frr.version`: Returns output of `zebra --version` to display version and configured options - `frr.readme`: Returns this document `cat README_usage.md` - `frr.set`: Allows to enable `FPM` and/or disable RPKIi module. See Module section below and for debugging defined at this time (May get removed later - do not depend on them). These are mainly intended to debug the Snap - `frr.zebra-debug`: Starts zebra daemon in foreground - `frr.ripd-debug`: Starts ripd daemon in foreground - `frr.ripngd-debug`: Starts ripng daemon in foreground - `frr.ospfd-debug`: Starts ospfd daemon in foreground - `frr.ospf6d-debug`: Starts ospf6d daemon in foreground - `frr.isisd-debug`: Starts isisd daemon in foreground - `frr.bgpd-debug`: Starts bgpd daemon in foreground - `frr.pimd-debug`: Starts pimd daemon in foreground - `frr.ldpd-debug`: Starts ldpd daemon in foreground - `frr.nhrpd-debug`: Starts nhrpd daemon in foreground - `frr.babeld-debug`: Starts babeld daemon in foreground - `frr.eigrpd-debug`: Starts eigrpd daemon in foreground - `frr.pbrd-debug`: Starts pbrd daemon in foreground - `frr.staticd-debug`: Starts staticd daemon in foreground - `frr.bfdd-debug`: Starts bfdd daemon in foreground - `frr.fabricd-debug`: Starts fabricd daemon in foreground MPLS (LDP) ---------- The MPLS forwarding requires a Linux Kernel version 4.5 or newer and specific MPLS kernel modules loaded. It will be auto-detected by FRR. You can check the detected setup with the `show mpls status` command from within `frr.vtysh` The following kernel modules `mpls-router` and `mpls-iptunnel` need to be loaded. On Ubuntu 16.04, this can be done by editing '/etc/modules-load.d/modules.conf' and add the following lines: # Load MPLS Kernel Modules mpls-router mpls-iptunnel For other distributions, please check the documentation on loading modules. You need to either reboot or use `modprobe` to manually load the modules as well before MPLS will be available. In addition to this, the MPLS Label-Processing needs to be enabled with `sysctl` on the required interfaces. Assuming the interfaces are named `eth0`, `eth1` and `eth2`, then the additional lines in `/etc/sysctl.conf` will enable it on a Ubuntu 16.04 system: # Enable MPLS Label processing on all interfaces net.mpls.conf.eth0.input=1 net.mpls.conf.eth1.input=1 net.mpls.conf.eth2.input=1 net.mpls.platform_labels=100000 These settings require either a reboot or a manual configuration with `sysctl` as well. Modules ---------- The `frr.set` allows to turn FPM module ond the RPKI module on or off. frr.set fpm {disable|protobuf|netlink} Disables FPM or enables FPM with selected mode (default: disabled) By default, the FPM module is disabled, but installed with netlink and protobuf support. To enable the FPM module, use the `frr.set fpm protobuf` or `frr.set fpm netlink` command. The command will only enable the mode for the next restart of zebra. Please reboot or restart zebra after changing the mode to become effective. frr.set rpki {enable|disable} Disables or enables BGP RPKI (default: enabled) By default, the RPKI module is enabled. To disable the RPKI module use the `frr.set rpki disable` command. The command will only enable the module after the next restart of the bgp daemon. Please reboot or restart bgpd after changing the mode to become effective. (Normally, there is no need to disable the module as it has no effect if there are no RPKI configurations in BGP) FAQ --- - frr.vtysh displays `--MORE--` on long output. How to suppress this? - Define `VTYSH_PAGER` to `cat` (default is `more`). (Ie add `export VTYSH_PAGER=cat` to the end of your `.profile`) - bfdd / ospfd / ospf6d / nhrpd are not running after installation - Installing a new snap starts the daemons, but at this time they may not have the required privileged access. Make sure you issue the `snap connect` command as given above (can be verified with `snap interfaces`) and **THEN** restart the daemons (or reboot the system). This is a limitation of any snap package at this time which requires privileged interfaces (ie to manipulate routing tables) - Can I run vtysh directly without the "frr." prefix? - Yes, enable the vtysh alias in the frr snap package by: sudo snap alias frr vtysh Sourcecode available ==================== The source for this SNAP is available as part of the FRRouting Source Code Distribution under `GPLv2 or later` Instructions for rebuilding the snap are in `snapcraft/README.snap_build.md` *Please checkout the desired branch before following the instructions as they may have changed between versions of FRR* Official Webpage for FRR ======================== Official webpage for FRR is at Feedback welcome ================ Please send Feedback about this snap to Martin Winter at `mwinter@opensourcerouting.org` frr-7.2.1/snapcraft/defaults/0000755000000000000000000000000013610377563013064 500000000000000frr-7.2.1/snapcraft/defaults/babeld.conf.default0000644000000000000000000000000013610377563016475 00000000000000frr-7.2.1/snapcraft/defaults/bfdd.conf.default0000644000000000000000000000000013610377563016163 00000000000000frr-7.2.1/snapcraft/defaults/bgpd.conf.default0000644000000000000000000000000013610377563016200 00000000000000frr-7.2.1/snapcraft/defaults/eigrpd.conf.default0000644000000000000000000000000013610377563016536 00000000000000frr-7.2.1/snapcraft/defaults/fabricd.conf.default0000644000000000000000000000000013610377563016656 00000000000000frr-7.2.1/snapcraft/defaults/isisd.conf.default0000644000000000000000000000000013610377563016377 00000000000000frr-7.2.1/snapcraft/defaults/ldpd.conf.default0000644000000000000000000000000013610377563016207 00000000000000frr-7.2.1/snapcraft/defaults/nhrpd.conf.default0000644000000000000000000000000013610377563016377 00000000000000frr-7.2.1/snapcraft/defaults/ospf6d.conf.default0000644000000000000000000000000013610377563016465 00000000000000frr-7.2.1/snapcraft/defaults/ospfd.conf.default0000644000000000000000000000000013610377563016377 00000000000000frr-7.2.1/snapcraft/defaults/pbrd.conf.default0000644000000000000000000000000013610377563016213 00000000000000frr-7.2.1/snapcraft/defaults/pimd.conf.default0000644000000000000000000000000013610377563016215 00000000000000frr-7.2.1/snapcraft/defaults/ripd.conf.default0000644000000000000000000000000013610377563016222 00000000000000frr-7.2.1/snapcraft/defaults/ripngd.conf.default0000644000000000000000000000000013610377563016547 00000000000000frr-7.2.1/snapcraft/defaults/staticd.conf.default0000644000000000000000000000000013610377563016717 00000000000000frr-7.2.1/snapcraft/defaults/vrrpd.conf.default0000644000000000000000000000000013610377563016421 00000000000000frr-7.2.1/snapcraft/defaults/vtysh.conf.default0000644000000000000000000000004313610377563016450 00000000000000no service integrated-vtysh-config frr-7.2.1/snapcraft/defaults/zebra.conf.default0000644000000000000000000000000013610377563016367 00000000000000frr-7.2.1/snapcraft/extra_version_info.txt0000644000000000000000000000000013610377563015627 00000000000000frr-7.2.1/snapcraft/helpers/0000755000000000000000000000000013610377563012717 500000000000000frr-7.2.1/snapcraft/helpers/Makefile0000644000000000000000000000037313610377563014302 00000000000000all: install: install -D -m 0755 $(DESTDIR)/usr/bin/telnet.netkit $(DESTDIR)/bin/telnet install -D -m 0755 $(DESTDIR)/usr/bin/traceroute.db $(DESTDIR)/bin/traceroute install -D -m 0755 $(DESTDIR)/usr/bin/traceroute6.db $(DESTDIR)/bin/traceroute6 frr-7.2.1/snapcraft/scripts/0000755000000000000000000000000013610377563012744 500000000000000frr-7.2.1/snapcraft/scripts/Makefile0000644000000000000000000000173613610377563014333 00000000000000all: install: mkdir -p $(DESTDIR)/bin install -D -m 0755 zebra-service $(DESTDIR)/bin/ install -D -m 0755 bgpd-service $(DESTDIR)/bin/ install -D -m 0755 ospfd-service $(DESTDIR)/bin/ install -D -m 0755 ospf6d-service $(DESTDIR)/bin/ install -D -m 0755 ripd-service $(DESTDIR)/bin/ install -D -m 0755 ripngd-service $(DESTDIR)/bin/ install -D -m 0755 isisd-service $(DESTDIR)/bin/ install -D -m 0755 pimd-service $(DESTDIR)/bin/ install -D -m 0755 ldpd-service $(DESTDIR)/bin/ install -D -m 0755 nhrpd-service $(DESTDIR)/bin/ install -D -m 0755 babeld-service $(DESTDIR)/bin/ install -D -m 0755 eigrpd-service $(DESTDIR)/bin/ install -D -m 0755 pbrd-service $(DESTDIR)/bin/ install -D -m 0755 staticd-service $(DESTDIR)/bin/ install -D -m 0755 bfdd-service $(DESTDIR)/bin/ install -D -m 0755 fabricd-service $(DESTDIR)/bin/ install -D -m 0755 vrrpd-service $(DESTDIR)/bin/ install -D -m 0755 set-options $(DESTDIR)/bin/ install -D -m 0755 show_version $(DESTDIR)/bin/ frr-7.2.1/snapcraft/scripts/babeld-service0000644000000000000000000000043713610377563015462 00000000000000#!/bin/sh set -e -x if ! [ -e $SNAP_DATA/babeld.conf ]; then cp $SNAP/etc/frr/babeld.conf.default $SNAP_DATA/babeld.conf fi exec $SNAP/sbin/babeld \ -f $SNAP_DATA/babeld.conf \ --pid_file $SNAP_DATA/babeld.pid \ --socket $SNAP_DATA/zsock \ --vty_socket $SNAP_DATA frr-7.2.1/snapcraft/scripts/bfdd-service0000644000000000000000000000046713610377563015153 00000000000000#!/bin/sh set -e -x if ! [ -e $SNAP_DATA/bfdd.conf ]; then cp $SNAP/etc/frr/bfdd.conf.default $SNAP_DATA/bfdd.conf fi exec $SNAP/sbin/bfdd \ -f $SNAP_DATA/bfdd.conf \ --pid_file $SNAP_DATA/bfdd.pid \ --socket $SNAP_DATA/zsock \ --vty_socket $SNAP_DATA \ --bfdctl $SNAP_DATA/bfdd.sock frr-7.2.1/snapcraft/scripts/bgpd-service0000644000000000000000000000104213610377563015156 00000000000000#!/bin/sh set -e -x if ! [ -e $SNAP_DATA/bgpd.conf ]; then cp $SNAP/etc/frr/bgpd.conf.default $SNAP_DATA/bgpd.conf fi # If no RPKI option is specified, then we create a default # with RPKI enabled if ! [ -e $SNAP_DATA/rpki.conf ]; then echo "-M rpki" > $SNAP_DATA/rpki.conf fi EXTRA_OPTIONS="`$SNAP/bin/cat $SNAP_DATA/rpki.conf`" exec $SNAP/sbin/bgpd \ -f $SNAP_DATA/bgpd.conf \ --pid_file $SNAP_DATA/bgpd.pid \ --socket $SNAP_DATA/zsock \ --vty_socket $SNAP_DATA \ --moduledir $SNAP/lib/frr/modules $EXTRA_OPTIONS frr-7.2.1/snapcraft/scripts/eigrpd-service0000644000000000000000000000043713610377563015523 00000000000000#!/bin/sh set -e -x if ! [ -e $SNAP_DATA/eigrpd.conf ]; then cp $SNAP/etc/frr/eigrpd.conf.default $SNAP_DATA/eigrpd.conf fi exec $SNAP/sbin/eigrpd \ -f $SNAP_DATA/eigrpd.conf \ --pid_file $SNAP_DATA/eigrpd.pid \ --socket $SNAP_DATA/zsock \ --vty_socket $SNAP_DATA frr-7.2.1/snapcraft/scripts/fabricd-service0000644000000000000000000000044513610377563015642 00000000000000#!/bin/sh set -e -x if ! [ -e $SNAP_DATA/fabricd.conf ]; then cp $SNAP/etc/frr/fabricd.conf.default $SNAP_DATA/fabricd.conf fi exec $SNAP/sbin/fabricd \ -f $SNAP_DATA/fabricd.conf \ --pid_file $SNAP_DATA/fabricd.pid \ --socket $SNAP_DATA/zsock \ --vty_socket $SNAP_DATA frr-7.2.1/snapcraft/scripts/isisd-service0000644000000000000000000000043113610377563015356 00000000000000#!/bin/sh set -e -x if ! [ -e $SNAP_DATA/isisd.conf ]; then cp $SNAP/etc/frr/isisd.conf.default $SNAP_DATA/isisd.conf fi exec $SNAP/sbin/isisd \ -f $SNAP_DATA/isisd.conf \ --pid_file $SNAP_DATA/isisd.pid \ --socket $SNAP_DATA/zsock \ --vty_socket $SNAP_DATA frr-7.2.1/snapcraft/scripts/ldpd-service0000644000000000000000000000046113610377563015171 00000000000000#!/bin/sh set -e -x if ! [ -e $SNAP_DATA/ldpd.conf ]; then cp $SNAP/etc/frr/ldpd.conf.default $SNAP_DATA/ldpd.conf fi exec $SNAP/sbin/ldpd \ -f $SNAP_DATA/ldpd.conf \ --pid_file $SNAP_DATA/ldpd.pid \ --socket $SNAP_DATA/zsock \ --ctl_socket $SNAP_DATA \ --vty_socket $SNAP_DATA frr-7.2.1/snapcraft/scripts/nhrpd-service0000644000000000000000000000043013610377563015355 00000000000000#!/bin/sh set -e -x if ! [ -e $SNAP_DATA/nhrpd.conf ]; then cp $SNAP/etc/frr/nhrpd.conf.default $SNAP_DATA/nhrpd.conf fi exec $SNAP/sbin/nhrpd \ -f $SNAP_DATA/nhrpd.conf \ --pid_file $SNAP_DATA/nhrpd.pid \ --socket $SNAP_DATA/zsock \ --vty_socket $SNAP_DATA frr-7.2.1/snapcraft/scripts/ospf6d-service0000644000000000000000000000043713610377563015452 00000000000000#!/bin/sh set -e -x if ! [ -e $SNAP_DATA/ospf6d.conf ]; then cp $SNAP/etc/frr/ospf6d.conf.default $SNAP_DATA/ospf6d.conf fi exec $SNAP/sbin/ospf6d \ -f $SNAP_DATA/ospf6d.conf \ --pid_file $SNAP_DATA/ospf6d.pid \ --socket $SNAP_DATA/zsock \ --vty_socket $SNAP_DATA frr-7.2.1/snapcraft/scripts/ospfd-service0000644000000000000000000000043113610377563015356 00000000000000#!/bin/sh set -e -x if ! [ -e $SNAP_DATA/ospfd.conf ]; then cp $SNAP/etc/frr/ospfd.conf.default $SNAP_DATA/ospfd.conf fi exec $SNAP/sbin/ospfd \ -f $SNAP_DATA/ospfd.conf \ --pid_file $SNAP_DATA/ospfd.pid \ --socket $SNAP_DATA/zsock \ --vty_socket $SNAP_DATA frr-7.2.1/snapcraft/scripts/pbrd-service0000644000000000000000000000042313610377563015173 00000000000000#!/bin/sh set -e -x if ! [ -e $SNAP_DATA/pbrd.conf ]; then cp $SNAP/etc/frr/pbrd.conf.default $SNAP_DATA/pbrd.conf fi exec $SNAP/sbin/pbrd \ -f $SNAP_DATA/pbrd.conf \ --pid_file $SNAP_DATA/pbrd.pid \ --socket $SNAP_DATA/zsock \ --vty_socket $SNAP_DATA frr-7.2.1/snapcraft/scripts/pimd-service0000644000000000000000000000042313610377563015175 00000000000000#!/bin/sh set -e -x if ! [ -e $SNAP_DATA/pimd.conf ]; then cp $SNAP/etc/frr/pimd.conf.default $SNAP_DATA/pimd.conf fi exec $SNAP/sbin/pimd \ -f $SNAP_DATA/pimd.conf \ --pid_file $SNAP_DATA/pimd.pid \ --socket $SNAP_DATA/zsock \ --vty_socket $SNAP_DATA frr-7.2.1/snapcraft/scripts/ripd-service0000644000000000000000000000042313610377563015202 00000000000000#!/bin/sh set -e -x if ! [ -e $SNAP_DATA/ripd.conf ]; then cp $SNAP/etc/frr/ripd.conf.default $SNAP_DATA/ripd.conf fi exec $SNAP/sbin/ripd \ -f $SNAP_DATA/ripd.conf \ --pid_file $SNAP_DATA/ripd.pid \ --socket $SNAP_DATA/zsock \ --vty_socket $SNAP_DATA frr-7.2.1/snapcraft/scripts/ripngd-service0000644000000000000000000000043713610377563015534 00000000000000#!/bin/sh set -e -x if ! [ -e $SNAP_DATA/ripngd.conf ]; then cp $SNAP/etc/frr/ripngd.conf.default $SNAP_DATA/ripngd.conf fi exec $SNAP/sbin/ripngd \ -f $SNAP_DATA/ripngd.conf \ --pid_file $SNAP_DATA/ripngd.pid \ --socket $SNAP_DATA/zsock \ --vty_socket $SNAP_DATA frr-7.2.1/snapcraft/scripts/set-options0000755000000000000000000000412113610377563015074 00000000000000#!/bin/sh set -e case $1 in fpm) case $2 in disable) rm -f $SNAP_DATA/fpm.conf echo "FPM module disabled. Please restart FRR" ;; protobuf) echo "-M fpm:protobuf" > $SNAP_DATA/fpm.conf echo "FPM enabled and set to protobuf mode. Please restart FRR" ;; netlink) echo "-M fpm:netlink" > $SNAP_DATA/fpm.conf echo "FPM enabled and set to netlink mode. Please restart FRR" ;; *) echo "Usage:" echo " ${SNAP_NAME}.set fpm {disable|protobuf|netlink}" echo "" echo " Disables FPM module or enables it with specified mode" echo " Mode will be saved for next restart of zebra, but zebra" echo " is not automatically restarted" exit 1 ;; esac ;; rpki) case $2 in disable) echo "" > $SNAP_DATA/rpki.conf echo "RPKI module disabled. Please restart FRR" ;; enable) echo "-M rpki" > $SNAP_DATA/rpki.conf echo "RPKI module enabled. Please restart FRR" ;; *) echo "Usage:" echo " ${SNAP_NAME}.set rpki {disable|enable}" echo "" echo " Disables BGP RPKI module or enables it (default: enabled)" echo " Mode will be saved for next restart of bgpd, but bgpd" echo " is not automatically restarted" exit 1 ;; esac ;; *) echo "Usage:" echo " ${SNAP_NAME}.set fpm {disable|protobuf|netlink}" echo " ${SNAP_NAME}.set rpki {disable|enable}" echo "" echo " fpm: Disables FPM or enables FPM with selected mode" echo " rpki: Disables BGP RPKI or enables it (default: enabled)" exit 1 ;; esac exit 0 frr-7.2.1/snapcraft/scripts/show_version0000644000000000000000000000013513610377563015333 00000000000000#!/bin/sh $SNAP/sbin/zebra --version $SNAP/bin/cat $SNAP/doc/extra_version_info.txt exit 0 frr-7.2.1/snapcraft/scripts/staticd-service0000644000000000000000000000107013610377563015676 00000000000000#!/bin/sh set -e -x if ! [ -e $SNAP_DATA/staticd.conf ]; then if [ -e $SNAP_DATA/zebra.conf ]; then # if we have a zebra.conf, but no staticd conf, then we use # this file as the default config for staticd cp $SNAP_DATA/zebra.conf $SNAP_DATA/staticd.conf else # new config, start with template cp $SNAP/etc/frr/staticd.conf $SNAP_DATA/staticd.conf fi fi exec $SNAP/sbin/staticd \ -f $SNAP_DATA/staticd.conf \ --pid_file $SNAP_DATA/staticd.pid \ --socket $SNAP_DATA/zsock \ --vty_socket $SNAP_DATA frr-7.2.1/snapcraft/scripts/vrrpd-service0000644000000000000000000000043113610377563015400 00000000000000#!/bin/sh set -e -x if ! [ -e $SNAP_DATA/vrrpd.conf ]; then cp $SNAP/etc/frr/vrrpd.conf.default $SNAP_DATA/vrrpd.conf fi exec $SNAP/sbin/vrrpd \ -f $SNAP_DATA/vrrpd.conf \ --pid_file $SNAP_DATA/vrrpd.pid \ --socket $SNAP_DATA/zsock \ --vty_socket $SNAP_DATA frr-7.2.1/snapcraft/scripts/zebra-service0000644000000000000000000000103313610377563015345 00000000000000#!/bin/sh set -e -x if ! [ -e $SNAP_DATA/zebra.conf ]; then cp $SNAP/etc/frr/zebra.conf.default $SNAP_DATA/zebra.conf fi if ! [ -e $SNAP_DATA/vtysh.conf ]; then cp $SNAP/etc/frr/vtysh.conf.default $SNAP_DATA/vtysh.conf fi EXTRA_OPTIONS="" if [ -e $SNAP_DATA/fpm.conf ]; then EXTRA_OPTIONS="`cat $SNAP_DATA/fpm.conf`" fi exec $SNAP/sbin/zebra \ -f $SNAP_DATA/zebra.conf \ --pid_file $SNAP_DATA/zebra.pid \ --socket $SNAP_DATA/zsock \ --vty_socket $SNAP_DATA \ --moduledir $SNAP/lib/frr/modules $EXTRA_OPTIONS frr-7.2.1/snapcraft/snap/0000755000000000000000000000000013610377563012216 500000000000000frr-7.2.1/snapcraft/snap/gui/0000755000000000000000000000000013610377563013002 500000000000000frr-7.2.1/snapcraft/snap/gui/icon.png0000644000000000000000000010476413610377563014374 00000000000000‰PNG  IHDR\r¨f$iCCPICC Profile8…UßoÛT>‰oR¤? XG‡ŠÅ¯US[¹­ÆI“¥íJ¥éØ*$ä:7‰©Û鶪O{7ü@ÙH§kk?ì<Ê»øÎí¾kktüqóÝ‹mÇ6°nÆ¶ÂøØ¯±-ümR;`zŠ–¡Êðv x#=\Ó% ëoàYÐÚRÚ±£¥êùÐ#&Á?È>ÌÒ¹áЪþ¢þ©n¨_¨Ôß;j„;¦$}*}+ý(}'}/ýLŠtYº"ý$]•¾‘.9»ï½Ÿ%Ø{¯_aÝŠ]hÕkŸ5'SNÊ{äå”ü¼ü²<°¹_“§ä½ðì öÍ ý½t ³jMµ{-ñ4%ׯTÅ„«tYÛŸ“¦R6ÈÆØô#§v\œå–Šx:žŠ'H‰ï‹OÄÇâ3·ž¼ø^ø&°¦õþ“0::àm,L%È3â:qVEô t›ÐÍ]~ߢI«vÖ6ÊWÙ¯ª¯) |ʸ2]ÕG‡Í4Ïå(6w¸½Â‹£$¾ƒ"ŽèAÞû¾EvÝ mî[D‡ÿÂ;ëVh[¨}íõ¿Ú†ðN|æ3¢‹õº½âç£Hä‘S:°ßûéKâÝt·Ñx€÷UÏ'D;7ÿ®7;_"ÿÑeó?Yqxl+ pHYs.#.#x¥?v@IDATxì]`[ÕÕ>’÷ŽL’@6 »ìU JÙm¡PB¡eþZJtRJKËhë@Ù”½w l#{ÈŽíÄ{[¶¤ÿûÎ}O–÷$Ù–ÝD²ôôÞçÞ³Ï=×D‘D‰"o|ô%Ñ‹‰Õ6ÀÓ ø¥­j‹éŸß%þ‚@‚ô¤;·c ^o’T<÷©|åa½ƒßƒþè ®Ä÷¾@‚ô \]Õhn–ò¿Ÿ)[ý·šÄ“"€:ÐÎ\Á1q³{$€{˜Åü oF–x‡ŠÔ<þ3)™ýsñ•¬…)Sãñˆ$J}è#Àºª–HŽ™H*ÞSš>¹K6ÿý ©_ø¾©„ apÍÄÍ. .€Õg·’ÓSíok‘¤Ââßú¡”ýã`©|éAH~H • Ï`¿ƒWœ q±@ð?( ¶Ö‹7gšxRGIågIé]’¶šr£°¯t&J1‚@‚ÄQUä7…<lkIÎoÑT©{é)¹þiúj©± ¨«0Alˆ%þFˆ~1yÚC@‹“…i ´àå“ä»IËŠ§¤ôï'IÍû/ë]4&ìÈ¢‚@‚D¾="áõQ'ðC%¨•¤a3$мI¶þëÛ²õ±Ù  MjPAÂU´Äg—H—ëßÛ-•ÀW+ÞÌñâÍ'Õ_&%7ÿB|eà9Àô%\…ý;%ÛYk .ØÚ@x¿¨÷·Â.àM•äâiÒôá)¹î‡Ò°äcsWB%‡Vâ³ $€ `õÙ­á »F¸I6` Y]…m%ïÀ.°¿T½ö¨zŒ«¾DÛŽÐ]E‰ë ´C AÚa1`ŸÚ€½uv ¤:¨»Š'y„TÌù”Ýsøk«Œ]€øŸ° ôÈÄï .–BWrOƒ«ñ’š'Þ‚ÉRûÜeóIóÚ–«¿'Bˆ{`â7  KÁVÜHïT ÚšÀíýpΖ%IÉßO•Ú^Ó%\…ñ0±ñ߇¨9 gú6p«¿“ÛTH*€«°~™l½i¦”?1G‚¾–Dñ@Íí j7Aâa²B ’ÎJ¢v,„gÁUøàÅRrÛ¯¤uëfBÌú*A$ÀÝîŸI€8˜bçFÀž:ËâFl)L•¤¢iÒ8o¶l¾n–4,ûÔ<”pö¼ö·ˆ‡©§(*áºAè¢Ãäô–«°x7iÝø¦”]w„T½ù„>ßî*tX]â¶íá+o»lÜ0* ó¨@ÔUX#ICfÀª&·~OÊî»Vüõ5–«›‰®Â΀Û!¿'@ê¹} „ I’ ¦K v¡l¹þ©xæn\ó%\…}öÁRm‚ôÛL!„é½Tì†Ež™YMõp×mI±;c)@«¥xÁbdRWáX©¼÷<)½ý*i­(5®BÞ—È6¤ÐÚ‘Þ<‰“úxºiÃK³üZM[}Ò¼n•4.ùH¾)­ë?²~!=î >FˆÞ4ô'YüeË$uÊ·¥àœk$sòžz‰½‰²c@ AúbžÕÂNDç 3ðµ–—èÞ†ÏÞ–e/H br~oöhõß÷Ÿe}ƒjàAÚ1Õ"ä(–¡³æÈož¬Ð0D€Äˆ’C¢lÏH€XÎnWÜÞߊ|~ˤ~þÒøésÒ¶ñeòžÌ!âM nLCcúÛГþD8#ixRr$иA‚ Õ’{ÒuRpêE 9è%‰„°X‚*QW|@ Ab1Šø¢3}îŒÂ«ïYXßï•@]½x2€â»@ÿ‡Ñé¾üÈû7à‚¢ßI™èF«øËWKƾçHáÙ¿“´Q;+d*A,HüÖ‘ ÑÌ }۠ǪhT«ÿâ]©Ÿ÷¨´,J™ºŠø)Ù¦¥8½r{~íOŽošo·8r¹€?¥¤öÝçÀñï…aï3lÃâdOWlÒè;­× §M¸»ÏFx<âæ@vüéÑàA`°µ!Á¸ÈNC%“œI’4tÒï$ÉCGâópIR I9C$);ÆÉ q0‰"½×óAB ¸;®! ½MO'ŽßVµEjÞy^êÞ¸SÚ6}"žL ~æ4E.E|å’t¡õEbÛ\Ø›qHɘ~`·"ûFƒìäêî½9#€äÓ%eäI±‹¤ +)…# ²gÙq*±',?QvX$@wSD ‚ëÛA1Æ:¤Ûz]j^ºUZ¿š+žl -úD@µæCǧ¨óŽô౎¶Fõ›×IŽ2|z’ ”Q»IêNÓðš$©Ãw’”¡EÊÙ=Iɽ÷ŒÄ…•ñùD¡”¤/ÆÅŠe ! ]Ì@Œcu Þ—ên—–ÏQQß“5OK°÷ÞöúBÔ·°gx’À¥IZÊ%ÐPªº; ö8Nü@I»·¤í²›¤*©#Àá𞉮Š"8°ê6èÝ~§£/ÆÒÞDâS|A AÂçC¹~{Ø.³ìV½|¿4¼õ7Åo>Œc@ž ij9ëŒ@áuEó™Vzrzò‚-ˆ!€±>iH¾¤ìŠmñïð{âKÝ@ Al±Ø°J â©zê_Ò²ø)ñääÀ­7‰=õ4´ÅTǧïHŸrìÁ¦¯ñÂe¸ÝÓ¦œ-™{)™SöVÑÞ›kcXÑ]…è»Ç ¶Ó~…5’ø¸ÝC`‡&á\Ÿ|•Ïß#õ¯^¥j½7â>ø=‘_‰ƒ%FG»$`ÀSNp`Ú‚- ´ eü·%so¼ö·‡xß%ÒCIpùh'!ñ¼ “tÒõë>yS*ý‹´®™'Þü±0¢eƒ#›C7¨‡Ç¤(âÃs@U¢­Y‚õ_¿p’¤ï~’dãhɘ²§$çíØŸ³•yKJéxCâ[‘C`‡#áþÖÊ2©|î?R÷ÂUð‡ÕÁ¤eŸâ¾òÿÈÛþ$°—F½ä, ~ƒª¿R„N™pŒdí{¢dï}¸¤lIÖSDz»ýÒ·ƒ2ñ)æØ¡@8ò×/|_*¾FZV¼‚Óv'GS àú±,D|ŠúP#U_#1ŸHú?’ìO”¬Ýà³ZjMÕÛJŸ@ú\ú;3ôš¥òÅû¥æÉ‹ æÃµ–¿›"(Þéhõ|Û¢k~RŠ”@Õ•.2ö»Dr=E²¦í#Þ kg ZT¢dÅê÷íT‡×Ž~²«¡b}±ºŸ0*†³ÝØî @¸¡¯eãWRþÐߥéû¡ëÆÆ—|¸ö°1F?Zä§q[j‰øàøµk!ö‹dðsÉ;â4M¹ ÐQ¢ û ñ•ZHïTÊà3 I$A3ÂE~ú*þûKi+ýB¼ÃÉ„…1.jnG}]u|÷š€ø_Äß÷É;êLÉœº7~¶Âpñ麋‘a±»É±ÚáÏzìXˆô5‹¿©‡†ÔŠ¿®ZÚª·JƤ=4¢0´ï »v×=ˆÂ1† é¶Z[¤âù{¥æ±‹4ÿ^RátET¼Åù=ÉôÏ{Ä_½TiJú^çÊ™gKÖî†!>ˆ 8~lÎ4Õµ¿#:1@ù½ÝEع`2þ KQ[U¹´–o–Ö-¥µl½´mÅ«b=×F-•`ÈÈ|f€q?´7“ø´ÝA`û#]QÈeéÛßúà?¤áÍ›°QfÜ{H}…M4¦DÃ…)CÏg>½¸óêš$u×%ïØó$çG é.væ°„vFÓ–©jÛ÷0¤G^A  »ÉßP‹ñ—aÛòzñm\×Jœ¸Lü[ÙË¥GJ=ÌŠâT¨EC*@¸”(;¶/@„³Ä릯–ÈÖ»#¾å/"ÅÝ{<A=QúH\·®ÏȽ¶’E’äÓ¯AÊ,Кœïü)³fIÚè]ÚŸbªã[b¥†üU-{¥¿¶JšðM_.”–UŸŠoÝÇàð 5'ã<éeΛ‚…­ƒ$|P{4wòj¼C‚¤LTÊ{®'>nߨ.@¸±¯òÕ‡¥ê¾3áßÃÿ¡@~µòs-¬q=ŸÊn!ƒëûªG3Äý$ÿÔ_HΞ‡ ZSox\7ÑÕv`HŠ0í雾^&K?’æeïJ놗U® -C³ýäqã’µo€R_­PìUl`6ì¿]5ž¸¶£@`Ðñhä*âV©ùßÏÁõFBŸ…‹§ã‚G>—DBåž^ þRÄæ§Ë3o‘ü™gHRn¾Ök‹ûvâÈ3Oj}üHÄ·p”± ܚܰà=iZø:r¾€d øYEzèîÃòŒHþ’³o‹ð¨È¢í`âùí ƒšØÈh¬—-\'u/]c¢ú˜‰—ƾˆ‘ßÁžT$Æl©DßfIÛë2ô{—KÖÔ}ÌP•‚MDA`–’ŽH^3 7àø°†_BŠñû±iíÁ¾èIÇ~…ì Ðì"TIö ¦²KámH$þö AKläo«©²»ÿ ónƒ¾ÿ>õXnä‰ù­Ø}õ"ûeÈo5é±³ršáö†žÁÛû¯%óšV/ÁIB¯Kã'ÿCÞÁ…Êé½Ùã°5j a$§·ˆ‹†,q¡÷&w$ ‚À $“àÏ.‘²Û%ÍŸ>¨Öøö#¶"åÊàüzjOP-üiS•agü^ãö 1Ã¥i}´þÜC.B».&!©_ü±Ôðœ4/˜Ÿ<´€Ü,In§“°u( ¤ïŽÄ×dÀø¾‰4ôo—Þú3ñ-yÚXúÕ¿î'¤È;öÍð‘וKÎñ”‚S.FÚì"ª-q¸†p‡ÐŽZõ¸i«.—ºOçâ\Ç!æ?¡ÞoÞ¸.aà —ÇBSÈÞ”‰/1À "Œv#ò·l^+e7ÿŸøV½„Ę´ôƒ]ªó*$Ñ`POR܆Kâ{¸ û鿤\¨Eôˆ¡Þ)(ˆˆ_ûÑkR÷YýŠz-¼¹;[¹/@ ˜O$cЏ—‰w0 `ëÝ<‰§ôß#57·ñF‰üÔ¡™q:¿ËIrœœóWÉœ´».ƒ˜p}´aî°R†ãÖ~üºÔ¾zÿ5ãºËçvä4ˆøàø6âGlÃØÁVpb¸QA`Pùi/›ýù ¢G~O ’t´bç^åZÉ:ò—RpÆV <` Ž­kÏ& ôÀqÓMÝgïHõKw@mA¾AXó½Ã&Ã;G)ƒV|KÔßÞÔOÏWฮȨµ/Ž{`#‘›WJoþ©ø¾|ÙâüÜÆË‰ˆ 䆋/иI8+õÌa'þš¶òñ¹P£1ô©už.=SÏc«_¾WšÞ¿Mcï½…ðVPeÑ@XôµD2ëѸýƒ1Òµ K‹E 8g½íRŒÛ!mg‹k`#?Sw•Ýú ñ­xA’Šv×F€ODˆÏÙƒ!%W5‹á^*—½ y§Óô#&>ª04R¶K¾² RýÚ#R÷Ú¯%¯Ÿ1 º'A›ÄÛöˆøíc£[“LÉ œ‚"¼E w0Z0°ÿÚ'þö9â–ØÈÏ=êew\)-‹þgü"E~ËÒ|þŠÅp¯í+…Þ"Y»~C¬íEƒüa\?Øæ“šw_”êgo@¢Ñ÷’–Êÿœ‚c¸ €»ØüÂ9-Âà|´íÈï/[.¹'üYŠÎú\ÈÑ ò“H¡Ÿtï1åVÅÓwJí³¿D½Ðk5";¹Y'B©ÂùøÛ„ 9w„Ñ*ªBQ °’`ž½¹SÅ;ž’ä=øp޾Rv™)YûŸ"9û~K“¯ØîÕË6!Øuý7.€Ñûâ»il½ó2 ½{ú‡Ãp_¿íCv<4,6p}¾ùOú ÿ×ø 4ÈC¡‰ ðhžòÿÂ8‰MHIzr0K(÷@ØwÜçäF‹t;ZÝðäÇ8BM#=™zmý«Rýå«R÷Æî’uÀ™’{È ªØêb{îÅì»…m7? 8°‘Ÿuë}Âö×1ñ´øGüýùO¼Úp~ ð‰ÜÇ.ò×}ú¶”ßs¼ Ȉz&ãN4¹º™™ö2‘/zN-¶¼yð¤@}Ô.”Ú§JÃ;wHÖ¡çKÞ7O5éØh t"Ò;,ð\|` @˜Ñ¯üñ›¥yþý¥1Ùº³ÏØœˆœŸ®>ü ó÷; ùqäWœ?„üèkåËIÕ³°(!òHÑÂÉÀµŠâ|T;î ª2ÀˆÈÍ^˜[oæT "ÛY e Á¯¥á½{$g&^9ü$sÌîO¨îVŒ[,sW{Ow«>mD¶ª7Ÿºç~£ßDL 3Ûô&+vU1¢Ìèç‡Á/ûÈ_Ààw%¬ÍÜ`=ç47HÙÿ.•wÌÂæ1âÍçjbWýO\ëJ`A˜*'µx" Ë¥ê¿Êæ¿þ@ªßy9RšA‹±¤•Ø!ÖÝÖšø0@÷'ªqå°ø_ŒWù˜<$Ø\NÃ{³w Žâ:OŠÎù½ææùÑ7£š$á -RzÛ¯¥ö©«`åŸ ? “Ì2ìº.‡”¸½ p]PÔ‡‹ÄÀ“: jØt$oyKÊo:QJnþ2$/Òçh(¤ô3é¢éíôÒ€#V{q U¹”?p5ר ñn4 Iï\Ó$Ä ¶ß_¾XÒ¦Ÿ*EòaTY4ÈÏECNâ+Y'%ÿú©4¾s+tLÇe,(p ÷}ÜNWãa!kqDR]w X BwQÖãX÷i VÜlUò·c¤â™»±Ñ«sˆø “êmè®®ûzÿÕûØ”Чn—–ÅÏX黩ç¹ìëâÆäîK}]ðÝΫ&’¸~UKÐ5ôƒYxK®?WZ–l·ÒÉŽ½ÀëïI*ÍäA$ÀFEFbe¬ ô~Úb $a‹8 ‡U÷'›o¼¹c.1_˜OJt‰²-\bܶ¸»Â‰7TóÞKR÷Âï1i“,Îï®&.$Šý†¯30ZŠ.¼YÒFí¬r¹©2„üPKV-”ÒëÏBÎý¹ÖÖcfâJ ¿ò^ã@ÓE|ÆuxRp4zbà¶Æ.î'ã ÚW¬7­P²š> %×#U¯?†©ƒ{ž»¨fG¼Ô¯@°EfèlåC¿E8h 4¤ˆrî „É;Õ[àk‚ŸÜ-™SöÔE`v—¹DT{QÐ&±üs)»ñ i+ÿ’‰åŽÔ•á²Îq5u1fÆ_x3†BJ[Í"lÃ^ 5ÊBcˆ9´J]<ìö9= …°#% …7 íTÜvº”Þ÷2ÔÍc°çÛmýÛáýýç$Ç&FX/ãæýe‹Áý‰`nE ?}P•ë%ÿÜÿHî3­©áo.iš½ù?“²›NGF^)6Äê[‚ë[°uùÇš‡ô1»Èˆ?¼+-ëVHó— ¤åˤmÃëØ’ A.=O6²"¥f[ˆë–tÕ'JH C­' ѤÃô¼ßúÅRøã¿JÆÎ̲Œ’ˆP0x`ÖôCa3@²ª×ÿ'sNÓäªSG@ý™½—9ü²g^%ÃÏÿ3V-¾†À¸‰=t"ÿÊ@þ3À¡–#ðÄæü.‰‰«ÆãáfN½%Ù¨$†#ÓÛJeÄï?’Œ‰È‹È¹qKP; +¬~½D*÷RØWVJÓ²¥iÑkˆýŸ«¿xóp2qÚåÚ‚³ Ñx‡š"ûÂ5‡Ø®—ŠE’„Ü‹çÝ¡'8³¾ˆÖLd‰Û§ú…«’†Ð–\{ôöõȉ‡ÝaØüZ€Ž@ë|r6¶Û.‘´É§Èˆ+îĉ¼H´¡:žÙ1æ¨ë&{4㬽’Δ@Åġà ëuSÛ`¸×F* =[‘›MÚ.MJÚü%8öHŒºG `"«pFÓ¬ñ\Þ†ù¯JóçwáÐÕFß!Ø4øð se‘ŠÄtÃX\£ ÂŒ‡þèaïö­-ÒµqWâìÁ¾W”3s§W«T>s;8÷2Ö Ÿ…vÌ9…8<è 4m¤¼ñÈÞ{uLŸöˆ²[.‚~ºÀx#¶Kä· ˜0¾yT}â‘bಠÀÁÖ-ØxBL—9>VÈÓIéý>Û#Cb`!4ÕAædÈÃ+w¿#!üXê>~ Ñ}÷‹¿'2eÃ`˜±“©_]¯¥‰Þ›n¿ãEBoòJàoÅí°ñÔVJá©‚šx{ƒQû3;Ƨ¾—,Ñ¿æÝçeë¿O‡Èb‘º;^håH«<Ðû¿”Ÿ='y‡¯ÜE§‰ÜÅE±©~kE©”Üt²ÕÀ©Y†™hÔ]].šíç[3žHÏÔç(Œ³¶¬Á _€ð yP.®{FÚïë<”ž¿ƒ€8Rªô—.øƒ2•Qè;`¹YW|!%×ì…7F÷x»‹õÇb$µöUa«¥ø×ï ‹ï¾X‘XüÛ¥¦«ºç'™‚]xÐFø>ä„´tc$9X n¥#ž bûB1‹>ÿÖ©zá.¾¯Ùpñh‚T›®)”@u™ 9{Àü,RIë•^ÞÈY,‘±ö£×¥úa ÿ°±¸„ë0NºU#zi­~¶Æcõ˜Ð_¹Lx©SN”ìOÑœzi#Ç0´O16¨Äm;×^?tØe0j¿E`DÌÞó²éRõÚ·¤æ©‹` o`žz’""à œ”$°y¬|-òOz3²¥ð´KÑ6.Z ÌeÇÝí}#áÀMj/oë ÇŠw(vÑ©K‡ ×i¡ËþÛª¥’>ã‡2ò·Á”£\έÅÖ¦èÍk–c³È Ø(²a$µö™;íQ\ܧ:l*)2Á–¨ƒKt2}Ïó$ç°ïHîc²êÚµ8ý Îª‹1ë’"b¢4,ùD*ú‹ã-˜d8#]o$Ã# OIÑ@ üKzÁý2ôس،^ã:ÞžKì €E9yÞý¦¿ƒ1žÇn-$rÐ rLŠgÜv믑áW¾ƒPß½¢B~m•l¾áBiY†F†!ÐÇ­42ીܛ:>¬Øp¡j×@­ÉØçÉ=â4E|ê°vQ¢Ç/ÒØ×óßp#][õV)ÿßÍRÿÊ_`XFÂÐ4ă¸6.[Ð @ 2\ƒõ¤ðòW%wÿ£ à-Û1h—cµ2,`Õ¼÷"(4`ªÑÆ¥Õ–Î_µY†üðVE~3.Ô>€‰µã¿ËŸš#Í þ‡m½Vˆo˜Å9VCï³z¬J‘G˜Óhžñ ¤Ä:ê,ÉÚmÿv1ŸlRY%Eh—°ê³ÎÇ®âZ€1&)”áçýY*ÇNA–¦3ü¥`4Öܺ\kùài ¦eKù]?Aâç5R évLbºJH¡I-éö«}íN=WEOšo -vù±i$uÚñ’´±*íz"ŒÄQƒl1uÏãŒBdÂDžB• È /H°e+öO&O’¢Ÿ½,#öoèÅ+ò«no!¾r|×p<á q³×ÚЙgHñ/ÞAáîê)"GT@hTôfCÄàÙz÷o…q"¡¶"ª4þŠÀ´uóš÷^¶µï"ˆcŠ{î¹–F-ªäŸr¹$ñPI­Û]WíÒ¼þK©|éÁ2ÓÐ?<Œb"ü àá‰F8ÎŒ¢þ9w«4÷ÀcB~ël¶g¤ïüºÖÔ¨)j ~ÅÃ’2î(M £[ŽõP7ÅäHÊŸ!¾•/Kù£7a-¶êºV"릪Ar¯;¬êaPÆ¥]É:©ë 6ªW„ëáÁðŸ(²ƒûÊWKö·®”œ½µ~u7‘*¶ÁŠð5açáMó] ÊîïÖÞ·~ûŒ±’ËQ×o)—ÀÖå’yðOeäï^anö>@ÂâW$ØÁ¿Ã4PÍ¡äƒu“±ó4qù$m×ï‚ɼª ¸[;¨L¥DoáixýºymªJŶ¶³Æýk?|yÜ?6‰3UÜvÚ„…üë4IÈc`‰UQ¢¿Ó:0;aÒBõ›OIÓ{s`‡€$2DêúPñà´åR褹2ìâÇdÄ%×kמ1„Û¡Žn)çAL>F†_:‘v"nj¦{\ŸD8¯Pª½L7,)ì k@Ú;Š}¥ëÃý_ˆþ .¥Ì]?ÑÕUúü1‘µ5šê9}‚txÅ%‡³ûB—_õ“¿OîjјTn1uá…ÐÖûñÒ2qñqÃTúî³däU/JþQßÇuƪƒ8„Ú~ìYü7EdEô"‰#+þ?œü¼ûéü :©$à’s«= q#`¨ŠÇ®×“ T °ÔŽˆ€‚ùc½0ßvˆtD9{Èkí¦Â0Ž[÷ÉÒºö#ce‚OÇœ›>ÿtø´—IÊ„oIÞa'jcÅaÇEÅb€TùÌlZƒØÀýÝôÅqk1¼ÈC_ q½º÷òN»QF^~‹áúä:Ö¸\Á"’Þ…(ß`+$fg_ÊÐb~ñ  (‰ ƒn‰êBØzÒ°éÒüéCRÍÔb,¶Úa¾9ç<*¡BÔ&úÙû‹¨Éµï²ßÎ{¤wFï´F‹iýÛ÷CôgŸÝXý „_ÂØD`VîÌóÌÑݨWu\Ç2æí @jœw³æ†ÓÞŽ ‘ãÆbz£'%.ÏE œc¥à‚G%ïàã´~@µ£ô¥¸O¤§êA9 íÄot 3‡ˆ@áH)¾è$v-‡÷ä}³ Íõt,eM/6BjžùµdNß_ê)eº’L-aZ²Öò2víçnÆD‚Ÿš&&’÷ZÏwswT—£#옵8ë¿xG|_Í3Çe¹4^ 8ßrIz0fê€\9`úBdõSÿP5Dé§µ¸£‚RŸÊ¥!+À“6’ÄR©|övyéM(°‘ˆëÊ!c±×r[M…”Þp:öj”`+v1úÂ}Ùá.™#Æ"¨^.$«PRj\þ™4.|GZVÌ•¶­Ÿë/$ëÌØÉV{Q`TªT¾M³¥æ‘Kqö$òQ¨AØÅ¡Ò2© §­æÅÛ%kÆ£/:˜×¼ÃO–ÆnÇgÏ1çanr-&HhË[ Ò8ÿyi;òûhk(:Kà1 1r@΀ÄÙîõï?§}cº©`€yþœ ˜õP÷?j–$a³ró°…ÚkMD~Ðê—ÿ‹|ƒ4Üð”¡xŒøÃXÉÒ[†}èÇý^ŠŒÂ›ŽãƨïÇXä·_­×d°Õ'M_-‘úO^—ÆÏžEÆͼ¡}oÎxè¾ä #âö øAp×§e@v¹â[³HšæßmNŸvµ„Œ3 ‘ÁØ·òE©E˜ûÐãfX“ :CÌ0Û´g|OߟmÍS!>sÄfM,T <é R¿¤Gå(cŒ=ò³­v–Ào.ŠøÓüÕRi^ôDnp]—Æ?µü×®Ô]O‘ì=¶Z·Áà¬3¶„Z¿à=d÷ùŒ=°úÇ,«¬³>8» ãäË_¶BrOø38?D%òóÓX"?€b ¦Ü'ÐT/uóߒͳ/—’¿ìs1 P!±%åO5¹÷ô\F褜è}ÙNбøÁp³dØ¿D ´É`k0 ˆT£:ä´€S3’Ôƒà̺7ï߈êgwT…J$äî)Ø®m$,ŵ¿¹Ã_øÆ„%è^Ão™ê­çµå⦠FG­ût. x•H;“ €Ò×î´X k.÷ˆ3õ¸ xœŠÁžôt?=÷Ô“h’ö*øÏA7݈«Ë†ScËôN“$ÿ´¿J°¡Ì ØE¢§ƒvÒ]HÜ+; ©ÌçIÝGÆ x`s¡^«!€E±Iðmì<\…yEé¢$žÌ,i^üš!6|Ðq;¼ÙY‰ˆñXèúkúüEM.IýÅ1E¥ØÎßZøý'âþZ/)ÃbßÊl1Í_܃ !»  ´¬Æ[tpty[é2É:ú×R<ë׌‘Ÿ  Ä&Ì ‹?’’Ù?—-7”Û÷ÛORÑ—‹^32“H†8 sþ1k4Þ]°Pì5›wÈw$óK‘~¥‘ÜÔ¬ðÂ\ÂØ]ÿÎCpé•èÓªÆ:ªÇÀ™áܙ߀gG"ÔÅÃ$iÑó#¨ M;]Üí¥ˆ€Å&¤qÙ§ÒºþMd<Öž  Ññ9ÙŸ&ÉùE:[Or2(C,¼z líÜGT\òàèn;e÷ÏI=}(<È$ó óÁù±1‰ ()ö÷ævØ9äüŒÆ,ûÏÕRzÝP‰îÇÚE3£AXš‘QÕ4çDÖaâþ6UsDUæŸp>’ÔŒ×À+&uHõhSP)`Wi]ý&Ò”½£ãV‹îÌuoݬÝÄéSPÿZî®+Ï®5-ùÀ];.fÃ=°Œ*Á¶V£Ÿï¹qÅùù~àþà††%’§j°ë³šŸ{-uܺ·ÿ‡öY% åÖ½ÓkCQÜ ÈŸ ƈ8s è¼¿kœº1ÎÅÀÇO5ŒD‹×·e£”Ü~•”Ï>…ó±™jWd¸Ar f`¦d¦†¦(Ʋ½< x)£Áxò¾y ‚¯¾ {À Ãiê>”`¹÷æNBv©'„IoµX ©7PÙëœÆßÌÝEäž Qé‚© mŒøKæI󺕽UÑïîÏÖ1éC¦+Ë“1¡ÞMzÕ[¦Âèl©<~Ùß8R0ÜßMWL[~ÓgwûOPѬ+ öÖ£¾ùãU×#Æð±àÜ›ˆ.….&Úm^V¬@ý‚÷eóßÎú×®ÓìKÞL¤_ãÞ‡PÚl'óâ¶±»?Bæ‡hÞùå\¯v×ÛhÇMC¹3Ï7ç%(CqA”9–•¾þã—k‹oÃÅ»è›!Ü’1iOÌÙÿ”jÞåd¡G0odh8 /mb\Xìþ‡n‹òƒ¬ƒøÕŒhnÄÁŽóÐiv~m])†œí7KãªMÿ],R,ÜI¨{ïY¥ F„r#…˜Vûì¨Þ(CN»KŒ—­¡ß9Ew} IåKÀº0rÛ¿‹ìÝDTª_Ùj«»Jø:¥H">ד„, êPç9%ïá+æÅZÇ9û%©Ž…îªz¸Ó–(I´@ (Bš¹'õ¸{ó¤Á§µ¤Ž+©»| íƒ!*u~ž k Î ßWŸcG"NÒF±ñÐi;=ÝçŠØñ<½–Ï!xÄŠgvº¨(D#æ4sŸ™ê›Åh0xݰ&¯iÍ iúô^Xþß­ÜαÒ'1Ú8*VHæa—ÊÐoÏÒVÜz8ºêšü¦)»ïZ©¼s<0£¤Çn­Q¢¨D¦«‡ãäš#">‘¼¹¹Y6nÜ(Ë–-“ ÈŠ+¤´´T%ÞÃ|,½FZ¢NZãsŽ8KÏQ@ÜAˆL/•{¾’†¥[Ï:[ƒ¶ÀÀ·ôÉû5€Ü¾+éíxÒ³¤uÃ'ªê™†: w]¿›ŽPg% Q›V~—!Á&3÷:ÿ¡û7o†ŽºÂ)÷×ví0^‡¥6U?ÿ pØ­0t1ãp¼¸þ,ä§sä¾RpÚå0Æ™;lÕÉÙ8·½‹„upGÙ–{þ$ oß þdE"“áØ…»mõýr…ÈO„fY¿~½¼ýöÛòÁ‡ÈòåË¥¬¬LZZZ$ gŽ5J¦ï:]9ô9äàCdذaúLøóz!ª7"‘G²÷þ¦ÔŒƒUþüîØxØ'ëÙbD §~÷T9ûì³åŽÛïÏ|.µµµÒÖÖ&ååòö;oË7Þ('Ÿt²œý£³åµ×_ÓÑ‘x°žØƒd)Ã%û Ó“‚åÜ•;®Ûư¡†y3ŠÅ·úuÅ¿Ö;ÝZéÓF„Ƹ-“@¡Öy²œ¶Ü–u0X²ïld®Düî\°šà¡-«^Aè/ÅRK‡4„â 7è ïY{~Ïá'ÓâNF@ôÇSÒ¸ôÄà QþÄMØ*]~m8Ú:çÛ¿7yåuPv¯ŒpÛ{lNÁ “Ò›/“–EcÁàŒEn¹¦ڕظm5zÅFþŠŠ ¹öÚk¹‡.3v›cÁøÏZЇ ×k4 ¾øÚ‹òâ /Êì›gË…\())Ø:&ID<0"‘µþ²ö:\j_,–#¢•\ØI, žÇœ3–'VÓHǼ„Nš}_ÊÐ"I»ÎÏxÓ WxX±áB;Àú¥â¥ê@xÙªDØÝ®?:Ã^vB¹=|’ØL¨X b™·õÿ– D¾u¬X:Õ\ìæ±à¦Ÿ†ÏÞð¹öáúsa€ì¦æè/+ògÁå‡}ýcö•¡ÇÿXáEäAmäçýÒÛ®PäO*æ«0é¸Ý0úaFRƒ¬555rÕï®RäßcÏ=¤¨¨HÚZÛ¤¹¥Yíÿù¢„ÐÔܤŸùì»î!S§M•K/¹Tn¹å–òó·X•ô& |òµØ§ß¥O¾›–¨ŸëþàÅŠOtm*žXHÛÍSæ²ÍÅ¡Ö¥ŽŸ¡×tkg‚®„ R´‹¶Í‹‘¯D*†ÅÐ ÅkZ>ß0}%änÎ ­ÿÁ†ˆÿG›Œ?ú˜ó¬F4<Ó²a5ü¯ˆ?È¥Ž ñ0àØ€ì<1ïÄ+ÚEÿΓé Tz—ü¤øewÿá¼Cç·_ïp;ÍÆôVùYéwÞ©âþÞûì->ŸO=@ŠBNÖùÅë|¾¡¡A’““eúŒérùå—˳ÏÁóƒbõK¤oº†Ñ>¢3÷8\kÑíìÖ>—Þ«5ëŸÁ:¾5óÃBƒ'[]H3Qœ‡“˜˜Žxeìãawû,ÔF,¸?Çç˜Q÷nYEc‰ÑuÑÛÞa¤wÐèA+'¬ÿÓ4Ï„IN*±=ËæÃ`ƒMé#Pg|ìùW«ÕJIßçG’wб:\k9_ø=¶ÁêÍÖ‡®—Æ÷n‡›o8? ®ƒ¯|ðÁò«_ýJ&O™¬œ½µÕdÝu2":¥a…ÃäÚ¿^«D>k« NêéòÌJiø‘RiÒˆ=a£Z¡ ìV™K—Ou¸¨Á:©£‘€ôƒ Ã =~1ø“Z<[ƒqêu[p¤3Jâµ lô¡µl½©‘kˉ¤ÑcûN €Eýç/Hk‰ ½Ô®?ãaW‚Íë þCÒwžj=dUê  åþ°Æ,~W{Íã°Õ2âàù¾»…cƒ•Ù]hrì¹0 !pˆ»­AÇa/,U‡wW<ŸÔ¿p58¿ÖÜgqØRŸÞfsrû'Ÿ|RÛÊÉÉQd¶=N;Àû) Œ;N>ýôSyõÕWõÑXHtI²¤‘´I‡©KPK9™‡‰œ¶‘NçÞrÚü!9¿Þ,‹é“ùŒwe–`ºXn¾’µ¡Ëø!ìsd;“›®k± uÔÿƒô¸ùRHvRÏ®¯o–´)ß ÿ»nªë«f -›ÖHË—¯CAâ µ?tT×÷áUŠ®lªÝ û_.Ù»é&:ñÌŒµîÓ·¤úc>t´é?÷:w^}8²XU½fíÛÇŽ+MM¡µÖ’Ûú SJ,¯¼òŠÐ¦ÀµÀþèÂ]›>iS'ÝÊÔíÚ,;€oý ð$Q{>½i’2r¢‰Ї»Z×ì#n)[£!ð¦ }ê­w` „ÙdZV#îÐî9 ûFƒsáú”}Ã8cWƒìb,œ‹"s[d b­ÿ‡wQg¬.Q÷gGl‡È;ò*U£È ö³-›×JÅýWI‡Yz\°«ÁE^‘ÒFô/W})_­þJ† ¢n¾H‰#Ÿ£›pÂÄ òþ‡ïˆ "ï`§'m]<>vo.¼¾JÀ ݇%ÁÆOl-Y!m5xV¯8àÎ,‡ø]Äêtï c0ÿqÄ•w؈Ðé6ê~é<Èn*³Û "SmórD\&lràÅaåþØÍ•¹ÿ¯$kêÞ:Û½ÓÍpº¿ ƒ 9HÅ7Ã͉ópàe° ÿmôÂ·_åǒı9^7]‚ϧ¥¥IYI™”””èM‘”®ZH¾\¬ûA–/ÎõŠV°#Ð#ÃÌÁåK¥ v2WÅ‚IJá(åðÝíªÕ6R q:ôjì@¬6M¢-Fi(uÅ|t²mùP8 Ž»ô e’2öp¤š¥ýuð¤ÞþæCË–Õ8p4dPã b0úðÜ~fÐâ H•s;Õ¢Ò€‹CÂÖ¡9.K4®yçyiœ{#¢%¡ÒÝ7Åþð±ÕÕs ±)$6Â76bÃSŒŠìL¾™2IC©iè"u¸ÆÈ“óáÞ IrØ-»N§]d<€' øÁ½Û¬!ôƒ.Gîð¬Ö ‡€‡ý롽{ -ëWYú?ÃI4®·€BB-J›°w(í—=‰=ôËú©}Â[&Ù_Š„•L>2àâ¿EØjÖ ¥÷E’9yÓßm&®÷êh)Y+UOü;%‘–? _¿Ž¡›7rþ¾(Î׃Ö9oœpýÔÑ“ð™_éµê=P9Ÿ%r/°Î™mY‹]goÍëDCBB:|o.Övýü]´Kœ3¥ ®)øìb­û«Û¿]´V…=„xJǪ»“”„Ý×ÝGGªJãEÚ¸iæ.»Îîž ¿N*l!UóêEJ™uß?ÏЂ€$êú˜“œƒOÂø è¸"蟳¸ÕKÿÿæ…8ÉxÖ8AW !‚&ò‘üü|mžÑ~Ñ"}ÀOæ›K^žþV­ÐJðfד2|¬Ju¢†@¢‡“~£O”qw[…QML½Nž5À›™ A#QÄkÍ›:ìw2C®; 5V0P—÷Ù÷;ûÛ#°#mµUÒºþQFµQ'C‡é"k)AšîÉbÇþÛu:ëžiÇTß-«?5ÁÎ1.Pû`²Oú¶dZº¿'RQ§Û ¯aÉ'Ò0÷O°úU¢ @wºsp}µÇ5nÜ8í¸ßw#%hhlÉS'놡îî‹ìºYkÜàÉL£GôhoÆB þªRH¼t•žÐ~SÏŸè H‚Ø ÝIÂû±*=Г¶m]gi.Fe(p¯PÎ  ¹þÿ=áþÞë#ÝÝÐZ^ $ÎwËDŒ¸£8íîjŠÑuP{æ3ÌÜïDÝRªµZ“ãªÀHÛAõ«_¹OU%=!X‰œ«šâêf"ªM&Nœ(t ””" xöu·æs©©©²ní:9ìÃdôhã*`nrîP=£‘=Ýpâîzdƒô×”µ»éº»µ‹ëž¤d¨ÃŒy­;â&”ÈÔU  9»¨ËÍ¥ž €µ¨[·nÇûœ'™8¿ù,‰(Zêè©ÐÿáÎBq>a ™æTÁšL—˜úÿµªxCŸ˜Ñ¨Û‡é 1섨ä¶C62Ô/ü@š?›£ ÏD7ÆfrÝö'–÷ÛóLD宾ЭŠÀö˜Ý¶Åúì:;î8IOg´^습ԡ~åHREq»n³ío]þå-`Œ”u[""¬× âÓ3åúCˆ™T6¥{`q'6â+]§ÜIè‚1{ìu"&êp԰›Iºo:WF°fÅ·~¥6«ô!+@IDATïëÏï„ ½u¥’6õdáá“.èþL®Z7ï cßàYöqàñ¨BYí ;'t’Æò¯Y³Fr²‘!Ùåâåý¹¹¹òÅç_ÈÎøvøaÚë·‰B¨áˆ?D÷¦¥ Í2ïŽoÓž%æ"@.ÐT À]²îKRì\÷ÝrwüH©:ˆ˜ˆX”î €Ý ¬­l¶Å =½(«Oè(É!b×$“2b¬^w§ÿ›ª¨OùÖ/3†™žÉ£ÕvþQ¢# æ:cÆ!zª[‹dÚœPó.¸Û«©ÞpRuôá ú¯j.&L«ÿ|µT”W¨ŸÐYSÝõȾ‡?Fr/ÀW\!y¹yú¬lÔÝó‘\çÉ=*­ºœ•Œ½C`óÂéÊ-ÆEiHŠƒ^1¢xÓ3”?v Þ§J°©.‡”n €Ý c½´n>‚è 2j±l…0ÿQ…ÿÒåѺy)PäTýˆ`¶­‚ÜFÍæêv mi6Ñ{ާÚT«Ü߸Çê?yM‚µ<‚ñq•Úl[¸½.œ|òÉ2çö9²rÅJa^Úx×¹¹ýâw"7Åüì¬lùlÑgR¾¥\zà!ÙkϽÜvÃÙýaSèÕPs>ƵöC5ÑžoPëFHsr:y4ì jwTFöC¨]´ËÁVl‹7Å>†ªèô¡[`ß篫AÐZ¥dQ*û·ÿrbÁ,“ &´Ê5[ãâDå'`D`@©&/uüa’:rœ5ü®&ªGÈ„8C~›¾xN3©XaºçÇÕ¯Dd[¸àü 䡇ÒýþŸö¹ÆóÓ°GbÀBö‹iÁ˜$´´¬T>ÿüsÙo¯ý4}ØÌ™3uì$¶t;`´Ï£"¢]qûeûJוV0_—©ý¼½o*€~Eeí—­ö€Oü„ëäþööø®;ãü*ùz¥­¶B5«!ÇC/¢bÛžuñ<{‰îÉEãpt[>Iòå´X·¶"ç}°ÛGóí$§Äú>†²b@5!+ ‹«1ñ.^+8¦qù§Ò¶q¾x™×pÅûs(N‹Mø÷Œ3ÎiÓ¦Éý÷ß/?ñ¸,Z¸¨ÛjvÛ}7¹ìÒËäÌ3Δ±cÇê}}ƒü»Ù¡-DZ,Z:ɬÔukuðñ$\÷Ý2Z¶ÁúIeð9¥[`û"ÛªAJa¡ÞÙ4ì¤QúTßGSŠÇâ ôšýuEŒpÒŠd Tèß' óÜ‚4\Lþ¶ót…‚Ù·ß«Õbœ8JG°m4-œ§ áÞ†¸;Õ¨c¯£þNöØc™>}ºÌš5K3¯ZµJ¹}+¶Ãfdd¨êÔ)²'Ä}ºYˆø,® ®>åòÍÍ:Õª‰•,f·Ûºìëæ×ÞÞçô3Ömî·/öÇmîqy¡k`-RÖÕV…Í Ti¸XCpyµ§æù•þzüI)É” ØDE/ôôÖ~ë–õªqß¶½zz´¯~cdÀ· ›šöDPˆ¡–žàÐsOZ˜[qÅ«8s®:s·Xz®=>%àòÅ$|q—· SU èŸ™™ÚMÈ‘ð:Ÿí·âÚØŒu ÜŸ"/–k*¸ ÇÙ'+òe· »&a·ùktØóü?EÀ^©#E\r´fîäIòÐb«67½æ½&ˆ¿|£’ØÑ¼°Á9þh3èócßö ñeÆäzrIج…Üüõ2ú%6ýL`ØïöOnÂŒ/"5מĀúxáo6â÷+ò£Á6 ²«‚¹ãÜ2æò¸zQ½îpny/—¾>úÒ~‰ŸCs%ê÷® €Õi&àlƒ€ÙNxŠiзCàï]í3~K™x8îÈ›…í•Øaå¶Ø0c>v²Á•°º·y.ìBÀDã2 º«¯a?êjÁì¢IbQº$ör¤žh¬¸7‘ê Q€É pÆOÑsÛP¼ÒàtÝåÄ›ÝÑ,u} ‚n@t’@DD=·û_¼¤‰>Ô4rS?ÅD§AM¡Èro…ïëOSü0 ‘¡Ž Ø["°lÓ0$†j³ÌÍZÜæ¶m.!áÆõdŒÃ9/Hé•R|Éíæø;®Ýn‰@{ýAÄ‘èÈ3îfõzµ#:?i5É©!Cò6Ýpy¡K`Saîr+8ó7üþå©q{g{m"žºPRŽÙ f‹¾½>vƒ¿¾‘U«!PävÑvX1ùHj =ÂO rDZt;ŸlÝZ‚ÏïcsÓh¸sà+ÝÁ¸¿ Ø-ŒÌ €ñ(ŸsÝ ®“f„‰OGðÜRzÙR|é½aƺbŠÖÚRæ®ûJæçIãw!Gàt£v DYEZ$ uù|äÅ®¥c *^@Ço7xu¼¥o¿™Aùë°ë©r7|ÅJ€ ÜæÍ™")Hày1ãò!³k ®E6%‘טx2V ƒ¡Ý©Iüµe*…G¶æ°V›4dŽ®[ "ð])ºäAä‹<ÕC’ìJr$¾£¤À^6üâë¤ ñ oÏ }Ætu¿³{”DA›Ò‘;R@, j쮘ÅÊH·h^ÝÕÞÓuÓ2ˆ)7=hèèYî€ô‰¼ycÛU»“Nû&2´–¬Õ‰4ñ9.§ßþï³—Oà ÔPí´yc$óC"P‹õ‚TîÍkeËMÇKÝ'o *‹;º•)1¿ð¯’}ÌopÊvà2üž†ê™À¢£ ԿιûÞ0q‘Fór×ën•@0V£ö³›œ·XQëÛ>ÄÉ!â~m/¯%¾`¥in÷~´om; Ä•m!`½ ^¯€î|ÅÖ\›*l{³ƒ+pyú@²)Ê·Ê–)5ï½hž£;ÈÞ¹èæ00\¦–þã?Jî‰W‹¿ ‰xXxb±ÿ¹W!²`%­©Ã[¯ ÃÝýò"ÐÅt^ˆlf‚ú¥ Q&4‘¤¼bsl§ßÝ|åáªþŠê/nqSCâÞ¾€!è<ƒ1Јؗ$ìb#¶ESÀ탾:¸ÃÇb¾‡Iù-ß‘ê¹OjÙ·­ŸÌ–·' ýÉûþ?%°uºB„D4øãeÞ€•ø#axN}L BØõÝE5hŸ”ûµmâäâaëV3€@StÌRãÚŒŠÃ¸ïAâ‰î!@„dQõŒvÙp±[‰ðD6oz±¦³/Ÿó]©|ùA­ÌæøkìL,:ãròÃ[qé×@~“„6)×äÖèü\$ßmE'’gûü{W9ðÀYÒð×kÅÿëÀ Ãpâ?éHs#TrzG”ª9ïûö~§e˜cÜ‹oÃJL4¦Šœ8F[ni¼ãiÎ$´ñ"H®ò®³tœ|¾i‡_½lí€\/øîŰügJÕçÀ0a"Ÿ^12·ëÐ<z? Àî&%èöÇ(hWÍ_›ø0²Q‹rnw³Q=€ØŠ` !‡I€h¦%fÏÚHmÕåØÍ»Äì|5³6HUt¿G2œów–êÿ^ [› Ž`_·•wjÍ&¼<ìøɰŸ<{µ;jÒâ*žsó5%3(n© ¶"³Šâ™;ds€^ï kºÃ6Ñ^ìúÚ¤&¶1±ëûWû¾Íëà¿Ü–ÉY€i±.ôp×'tyoÁD©yä2•ŠÎ¼*!‚z‘ò>]ÐÉù<›ƒ%lqš ®ßãXc —í‹Ip "<`‰Zž¤è&³—»V\$XM”‡æÖ¿›¿^Šƒ7€V…бû*ù,‰‚…˜ãß[8YjŸú-$ÂF)šõ 1: c^É;äx¬GËxhË£bô+:šÖ{|‚7]%ѹ[qü#úáx7cO•’˜PÈI”¸€€-þ3Wóªù–þwÀë£.*`êpøý‹§JÝ‹‘²»þ¨q/áb‡Öù × ?ǨĮ¦u¨C51 pê‹æ‹Â>zÌmßóMgÏÆfN)þû¾|Ù™ ™õGx6‘˜íàň¿†7¯—Ò9þAB÷D\H‰@ôëІ_ü ¿"KìÆj9‚¿F Fšé%¬Eî¯ðxáNìKÖ^âc2ÙâãÊ/t{¶—û34ïEˆžDè6m/©xº4½7GJo¹\Ï4D€¢~'P¦»¾Å1@×bîØÃpöáyàF‘h‹‡yßR˜þq£ßD;ªÁù|HüÇþýÆ/æjp–nz³uì~‰ ÞØk’4|·¸_Jg_">$Œ1q\xˆ@ û‡ÀP7†:zR¥cï;ô KmÚô‰ùص(v×'STÌ4HiHk­€{m=qCB@§õ7¯Y!-KþƒóÆ‚.ÃJCÛY÷Iàöæ&¢¢ÝЗ'¤ô¦ ¤eãWJ úˆÄ!°AáíBöïý÷×nY÷&ØÍºÃÿ³÷¦gÁˆ@?\ À†fÿÿ%BYˆ^?ÿ lÆW=ú.z)/²Áp•ÁðMDI…»!§ÀËÈ)ðc'l² R8&à–vnµv*l£zŠ˜ŽÉÁÿ2E^ áf"î) ¯BäÚ±Ÿ´ÉW²V>|;÷p¨G¿éþÝžkÄì$L*˜!­›æa;ñ,i\¹€‹Å¼bLâ6OrÍÄÐF€¬Ö¢r^(ªxOkèÀ†3M›¿º áÉ∪x$™©Òû Ð,ªníH[ë«uëf©{ë^ñäàÀ׸KÍfˆs êWœ$uŸÍ3³¤.DâDô% ñŸb7ˆt&aCÏ\×ûóýA &Í&kXj€ÕGÇÝPÊmúŸ:bœJnšƒ0f7Z0¯yïiÛð!lŒÇ(Ý¿§QuÀ›‹œ­•²å_‡K퇯š(Át‘S §Úºú-. €ÝQM|À`eL؆ûÇ~ý ¹™b¾žUÈ)²Àð1Ð9G`ïi-jJdQ€öÓ›Mt[6­‘º7æ ð‡™YQBLÆ|Ÿwæ@b‘¬IÀ|Ù:û©ž÷Œé^7‰EÜô=N €A”¤d>ÑC3‘mUUIp4z°a“´–—ºo—÷¦Œä‘ûc#H è)\dä¤ËÊ»‡¤6;ð§úõGÅ¿ytpæÝs½¾8gÕpôñüAÿÔc#‘³PÊo=Yª^{TÇÙ]b‘îÐñ—8%FßOÊÎÃñâÌœ–§½cÿûé&Z;Ó.]¯mÚ™“Ýu }\i»ì£ç-hηpÆï¶305,þHêß¼R¥0sâ´{}Ú†àIÉÅ [s±±ÇH¨ !‚c¢ D€‰E ! Œ–Š; ÏßkÖ¢J‘•âr7 AÀqÙI¹#‘]nt^µ‰pDú'•û÷ѼoÓ—¨“këônuxÚðLú¤=¥äןâ”G ¯8|ÎÞeÇD³UÏÜŠd˜Òa*^;W/‰Ô˜+ÌŸ¿z¹™çaª>H©žÔ¡P™F ‹„á½çOWŸ±K”ëjp°‰E@t¼¹ã¤êžs±“°I N½H%{ŒvkNþÆ%°ÝcI™ð™C4¬NÆÓG÷@tDÍ<¡¨u#Žôª­aÂA))ÝܫВŒŸŠÐÏÝ`áýF¨ÑCé]ˆrôÑè}µá¢ÿ#ÜöAìÂÛU‘É9òsz`¯¡·G¹ex)ÔÓ,=àÅ_±)»V™ØÜÂdyæƒQUÅCûA¹½=$!DU6$ÊIÎÛEªü?œGÒ$…§_†îáøú.r ôÔR|«Çž”4I.€Ïp3iÁˆ áµ§¡Åö7º%5©šˆoë&ÉPÛ€ËflO-%éÓŽ’†¹7@¤C2 AµuYiâö!`êÆåŸKÍ“ç㤫@rgŠþNaŽƒn’²Ä_·DR'ž,EçüÇÞ ºy‚¶oËH‡_#xg^<÷ñ}H®8Úžk—Â#$OêXE\³ÉDíãeJ‡Öw7}£A\‹˜œ5ÿ»Âäøá/чº ñIÈU üM)ÞIa¢®Aû:ÀÔï †b^ ú+iYÿ¥dì2Ý÷·;ì5cã¡+™»& oÝ`"ИN€}câo, `#Ó}•?x5¸%öàîw®cÏ×"¼å!®@èÜ£ÎVägÿR Gê+câ ín°µž¢ji«,Ó =>Äóû6®QX"þ­ïJgÝhAÊ‚Â<)8h†)¿!]„r_rrðÕA 4ÂÈEÃÇœµÏþA³ Ÿs•¦·ÇmµÖíŸø$Ú]RC€¢1f—–úi»¡ˆÝ/V?i‘»:æ¤yõB‘ož uJ¼!TÎ[‚Àº0Sö””±‡" Õ;HõL‘”é {€sXör'üäjõ‡-üfñ-}ÈŸz›{8{€hþªe’¾ç,ÉÝïHmX£B­ùRU ŠR+Oæ+cânæ>% UºÍ×W‚¼WKë…H •èy€z#ÕJ Épã4n.iAÕH-J¸æðÛ¦Ï%O“ú×þ®’@ñOþ¤Œ8!ñK þ+uõæ€PïIA`¯H¡¸†ý®eõ'ÒZ¹G9™±ÈH]{@jÑhÉØûx©}âæyL ¿ (ör+Åjh”Ê—’ú—¯Ö\ÅNîõÇirÜy°Äg!©„£¦rnU±&аM”( Q.™“öÐ>ñà]Æ”P…ð•¬U¢àƒúÐVºDá¤&+1 ‰¥…TÚà}ÐÊAB†FØ‚^?ã&ófK)’éŸ-²š~ZpІ;½… ÓOüÕböÉC %¹pº´®@˜‚NQwˆB^+īш{YÕ̆ûÎ`uØÔ9{ߣà’ñ g¤ALÝóóªJ™y©›ÿ&ÒiÏ‚Ëo”¹‹ÔÕœÁÐ 7ŸË—’ýí?Hök=ÛÑ›¤`Z2Áþâ\¼÷`£ãAøÊœº·ÞEc3µnQ(…¤° ’¼Nm8È_ù¡k-¢¬UCcÚX0EZ§¹» D†å¦î–ä8~ñ?Á@GYD@;êý!~ €ÆdĤŒÞéš^†˜Œ€»çýþD¢Hæv÷Wn”¦Ÿb1".ãZ à0Ë$ºcÆ>gÁø=<Âøú}pÛOƒ˜ 5úa^W|.[ïø1äã4Ìr0 7¿ºñ–ÈŸ] _IÒˆÝeèwÎÑ'•û÷ÀUCÕ[sl¾›ùÆb1ë…ÙWüÑÛ -xÓ2„R!_YÓ÷ÕÇx Œ­å6Q0†Fžè¯øÞkù©¡1„a¤$ÚWZ–?"¥7û¤ø¢$uÄXÜdI°úd´km(nß`KÝiªÔãŒrLl6M‹çJÛ1gJòø’qÝFèðÛ{üŒ ·¥€ÜÃN‘Æÿa|Ò´3ôUFÚ;´ü†üÍkWÈ–Û.Fôæ:øÌKïÆègÁ‚s„Áº&ɇÕ_÷pD2×@ B`Ó £ýQ™ZSc7î76lOåkÄN’5c­Émé†(” 0’ÂWpOæ°yˆÂ" n±ÖèGOÊæº-2üçwKúN“L/¸†Ãˆ@üJì¤ÕÙ4tž"î×¶¯wj?}á‚€%Ø›³ $’ç¤éëe’³×¡î‘ßê®m ÈÚõ’¹ÿ/áø§$àHh‘$Š;„!ˆÕR6ûBˆÎë–Z†ÑÚú¸ãJiÛ¡´WºP2ø¹äúó(פîÞr_ ÕÃ/üƒ” !Š€_­1< ÞBÚÄ <„¯´‘ãDv;@« ' ¾Ík`S =ákiYö€”\}¤^ú˜dóÞ Ľ@úëQ&©p/ Ô®4Á½®#ïï7uBœD> àTN2'ÈMÁdØâä™?”¦ùwB ¨Á¬P ˆ‘ÇÍ`ð^"% Õ±æuØ:{óEàˆóD†ã¹ÉùuK©ÞâìÈÝ:P»P’Gí-ÃNý)ÔlÜën‘Ÿ-†¯ ÔazËelï„:ˆß¶‘,Ñ73\Kê´êî’(`窿î…Ik=©ž¨«ÍXÙ–ËÛ¡{ýö%ÖÌÔñûI°apÇ€4ö[ÛÛ4D¤…ÿÕ›—/MŸ>&-›Öš["ì’=ÁteÏüì ›0Fè9ÆmÇ€PŠD˜—¦Õ‹‘Kï\Œç†!8Ku2L$Œ~´úáazÆ5’6jg寯pDPxøWaÕè¨9•.Á¬ÉÒºúHïÊÃOŠbâ8I°hs÷?Jý“Ô=÷'ä Ø IC%“Á5á À×]ñôˆïÇášYE È|ñE`ígå¨×“šƒPïÅXgIÑÙÐŒT®‘ŸUYk¶~Á{RÿêU’Á˜>õJ1u;$Åã.ÜQæÉšWË“R7®ö†=´ØÇAQ6ëÏXÔ) x›Á=à؉€">€j,ðAa.¿Í×~ö’ÿ‘¦‚…"@Œ¡â ž® ‘ð úª±¿¿V .x(™gh×I¦`‰þ•/Ü'¾eÏÙàÜ´c3-¸x¹Þž`¤ö‚AÞwo“ÌÉ&L˜k½¿J믮µ·cgrÉ? bñ𬶼rÛöª£û;€‡‰B HÕ¾~ŸÆvs('°f{Ñ16 ÿ쇄Rú``ÖÛ a݃î1 §ÍQ)]Ñ Vzçe뿎—@Ý2IU',1p*bäGª7„_ðuèO•ÜŽ6`"ÁµÅO‡€³ƒx˜½·îù_ˆ7 ÔºsÃ×)‘h‡º=Øð¨],)㗼ñ¹ŒE¥œ0À°Þm¤H>RÀJ1ˆýH);t(ì  7g²ø–?+µï½¤¿Ø;¸Ânsþ‘‹Îâöùßúžä}ïF¥@ð(ñ(‰FÆpÞ½“â¾ß¨C$¨æF©~ëiÙü×ïJýKÎ<^˜ÁAÚ«°Šaø’jÐE¨X#ù?ºGò:Í ÛV5\ýe_}e¥ò¡?é“LF&nÛš¨ò¡A¨´CNú…q˺‹{·}:fW…@ê®b þfÌ8H9®N¾ª‰˜,•°—›´j_¹;»Ö)u·ÅÖˆfÊ/.øîÿIα¿•¶’¥FØž‰_í àŽ8’ÈÍü}%ÿþ™”Ï>6‘ùz€&9*¹¶A¬HÆ ?·Ò*¾’!³n—a'œ£S¥óf‰ñNçN¥ô7Øê“òÇn’Ö5oÃ. ¤îö`\ô6ÊWHæa—ªá×4εÉxœötÛû0ÐÑwêI)£å\)*œ NÐ?½98ÊiýÇRõêÃÚO{ú¶ wvÅV%xœxѬßJÖ7/AÜÁâíë‘óE±:1K㪅RrÇï¥ôº!y¶òNU~ªñŠðÞH…È—› 2ê*¿–!gß!§Øî>üæùÙoû™Êï—Æ¹7âl?èýŠü:”Žo¼6‡@ãZ¨0;ËÐ/„]‹“`LnÛîXsDßâƒuÜŽ›çöÉôÝgJÝÚ·%˜Ån©¢“£Zúî&r¤¤¡ãàö¹R²önÝ‹u´‘ªúp ߯òÍ8qt™ äì7‹~ÂeK± ‘gým ß4sа©ºHÊŸ¾K»£âžÕ÷hú§b+k.cÂtyÅ’wÚõÈ$»–óe+!ö2{¬Âƒ sàŠ.tLMÒðIPö@¬ýH¨& …mO} ä7ãÔ°i ׿u ó`)úõÛpó]¦oSôpßž­«ùË€ü o\úa¤äYÝ[H6j®[„u:] »MN”ŠPõè‹Y|€P°&0}§‰Îî­JñC¢]_@ÊUX`táå…¯·æ‰K¥úígL ;mÔUnVÃ,ëXL\TEgþ‹}ž¤Œÿÿrp)$áF"³@†¨ñ/y¼H°Áì<ôZ°„»ÿÌ•Èß k=†Ö"D8P½Z²Žú­Œüí£ÈØt˜Ök.2,„üûËþó'äß¿N¥#iÑÝ×A!çG‚>fíD@×On†Þ?IçÞ¶‰D>àØ=9h '”%÷àã‘÷ ‰9›·—`ÜHXØ”ҞÌB©øÏÉR¿ðýö™‹ `àŒ%‰š™Ÿpäoî“!gÞ ±³†¯Å@8¸Ô ‚šÜfíèûOt•ØhÃB¤Ê¢oªiçxäî#ñ—ë$…—=/#.¼ÙuGâ‡>Dbì#¡¶‘¿­¦RJçüV^ÿ§ÂA›…ù®ŸkY…` To–¡?zDrö>¼½ÛH íÇöÓà$€ CÑ•yðOhäÕ· 3Xì%˜^¬.®­·ýDš¾\¤WŸx,ˆaaI$Š<¯Žzæˆ+Ÿ—ìc~‡ö7©\‰‘Mb»†Ôf™šb,„ÃѨ_[6HÎI×D´Ð(“P¢Ó‡î­×ÖÜPmc|éÍ?“Æ÷¨óS쇽I7ùtU/I^ÜÑ÷ý›$ÿèÓMkdZöÂí­ý~ú}Ð3Á¿ÐÜÃNFŽob>=[÷í'öÖ "D_† ûkVHÙ­KóúUŠ´±$„C¸4±ó4Ä üY†ÿæÉ:ò—E×c?Û^UO&±ä¾]ÄL¼ø§«5ÝÛûåwv…ýEßiUÔB܇º“¾÷9Rü›¤øGWJjñŒˆF<+¿¶a#*Újúj©”ÜðcYsRLO uþnÄ~0¥’yûsŽû½|ïÿ´JJê«÷ð6x ee"¡fÖ!ãä$]ÐÍ2q¶Š•Ô"Hh\Aï#eõEšÐB–œ&F’ç¹] qô ·¿ðZñ»Ï$çäHÍÆâ\ŠPÔf!ÓxÊhJþ%r)žYÈÖÃÂéߟˆXƲy¹þqRNÙ2IÙéX)üù³2òg7›”×è”"šrýÈ–v8¢2ÇCé?N•Öu¯ÁˆÇìLpõugðSäGÀ¼0m¥Kàv¼TŠÎú¾#¶ȯàþš£Ö¡0l\X°6póŽøâÇ€À¬.Ðýè ‹+vf$¦ªn]‡…õ¯ døes$mÌD³¨ˆsDÀXÔ£‰&±XI[79e/}µ}†Ô>Oæ¿€tfÏ`Qãw i€²áó¬ ppJp,ê(vê—«;1eü7%稟`ïþÌÐ)½jäø"F4Ê„öbWö\T¾ü T?tˆ#þç3ȇ©Ù8?]ÍLäÏò/–̃Η⟀Øf !Ž‘ŸpÜ€ÓAjB kö·~*5\ÀIâubU<,¨ Úi]ÿ¦”Þø)úém8j<,”5ž˜ôš‹’ P‘ñ=mÌ}11ì‹Þ—¦¥o#OÝ+ˆÁß$2@ÍGâ…Ç^õ2íLê„oKöag ÍõQ’¬ç2â²%AE,î³ Iioýÿö®LªêJŸ®ê}ß7vEA£FtŒ —¨c\À *£ã Æ`ÔI¢Fqÿˆ»qTLÌ šQGD—ð1!Æ 2®5*› [€FzÇÞª×ùÿsßkЦ·W½P¯êÝê*ªÞ»ïÞsïýïÙî9Hë]¶à (ûBÜ¡`8Ò­ÅßG¡«; Ò 3¤àÚ`™Él¯7 (Ùeb€zá¶JºllW?(úCÞk*ß —Àó aµSpF 츻ÐJÃD×R¹`p”ä_7¹àNÐíÉh_Ù¯ï:Ôf· ®·¹ª z‰Òðõ©ûìMIšpªäMû7s ïQ ¾£ŸÉ*pê6®Ff„r‹ÍÃV ýŒ*Ïzq¿.~°Î±‰?ú4I9þ’zÌ$ìøÙÖÍhW«Õ¶PÚÇZÐ7Íîlq<µk>†SÕÝÒ´gsqØŠH+Á°‡Å~‘í׋ŸŠØG¶½ŸJD€Òš¨´·—ÍRÈÜéaÇØ#%"Æ0$TL|¶ä\3O2N:Çt¥܆í§tún’¬ƒ¢Œ‘v[ í¬u§÷÷æË¾Ï4VâÜG®Íz]GrA¢°Ñ|iäœÎØqsY­öñºÖúÄqX U¯^£ø¤¡Åé¯Ð¥²w± qÀ8ä€Â/å´”í7Ž>á+ó³åÁ¥+h ¾Æ%Ÿ1 (LätœƒÊ×cÁLÄË޹úÒàG½lιRþ‡gUþä™î :Ñ¢íÜ1)™õyx–/1¥ï‹ÿ€6›±9àëî¾`ÀUcKÕ:©^¶H颗ëâç´íËâG{Жú+e×7Jås×`¾ 1'úºUöñ.Ì)*NцæhûϽC ¯½ßòòsÏâgO"8 XH̶’uNÉá„Y[^X:P!L@Rg  Ú¬&¤bD!•Ï_#»ÿóNi®Üƒùi™è(¿dÚ•g|^?>µcPÕÄEÇŽôv,ØoŒ'è²wñÝRm{R¼¡Þ‹:¶»ìµÿ’‡ÎC ‚çઌó zxÞŠÝ‚‹ñð2j-Eö K“‚Ì!æ¶?˜t‘ì'Jò˜£%ã‚§qJŽ~0ou; zËÁû£ €*_ÞáP>=,»¹Jdh£°8Õ¡¥/“ÞIïÈðÕOR6j µfŒz®×1ù ¸8_FšT.¼' ×éÛQ¢{®ãÀ+l•õÕoÌ„ea¼ë–ŸÊØÎN‹Ûj¿DHº¿Köµó$ÿ2$%AØ6·Èü»ÖUo;^çŽÿt± µ™gM“Ä£§"dlƒÀáÛN:*›  ãÑÒÆ-ÿ#»œ$Œ1G¼²êÚ7: ¸¬pÑ3¢íi Ðf!õ®äjÁŒ˜o”ò…©¼NnÅçÞÕ³ï*ÛZˆ”óÙÿº4Ç?žQжuNª/€ƒŠ”¯Æn˜äýâ/’}Ît­˜miç ö=ÊŸü÷¢¸¢¥½l$M9?‚†Äæ—Ú~Å…HõŒÔÅ ÷²þ»Œí"ûÝ„ ›<úP÷á‹ÒPRŽ,5‡"Hæ(¹Șl„NØkMßkÆÊB}IÉÒ°£>ÿ }ÇP(ftNÚÎkÁ øR‡J`ýŸE $;Uà{‹wÜV]ÐH4Óqà›Z„tÿ£i›jü•ºûªÄµæ„¡OÆO‘‚æJªeµá8Ù ²ï&÷|Š8 éí¤‹­-yˆ*»»H~ ÷Ôš|±ýdÕŽ”“¶„~­½Äi÷e “Ú÷çë‚?Ü »5±u|&j¢U€AWJ¥qû.I9î,pJô´3ãÜñŽžþ¯äÂEšî˜Œ¡Rû.Ú–D¶…S¡¥b*–³¯~Aò…iŸw® Cš÷ÔG&p18Ü%iˆ:Vê>…§ÜŸ…É×Ä+:Ò"Ìþoµ,³Ç]}È~KâÚž—Àž<á … S•æDTft8MJm‹ieî¦Ú8„Ðz  V¤ ÚÁYEä¡Ò¼ýCiiI“Tœõ] ͬ¶Á;²qo›4¬|fÀ˜ÐW´Ôâ”á&IŽ:~G,Ôfr@üçBÉ…è<ÏÃM š^*]0ˆ:ñ X4‰ƒã~ãú¥Rûñbi¬jR—Xî°ºðy-&µú´ðïÁ-h“Š+ y"r;Ö}µŽ3Xz§Üû‡?9Kk_ñD!°haß»mXÐñ8“Q¿ö3,ú`¼J_qû4iÂïÔLÁZµÊû‡* f[(?òpi$Að Pž¢•KŠ=¹i¦¢¹ú äD¬Z,5ãPOófK\&€þÿX öäfíûRwòŠYD4Ž--µ>‹–À<«®ÁÔË8Y¼úv´•I`ã:IúÎêlçæsÚE=K‚…íOÍ@2OˆË^”øqS$æs’ùý)ÊAª˜ÁŠ].ïwF›tY‚‘Gt‰4}ýÌp°y5ÒQpP¿ãˆyXpìT§Ôê?_ŒØúóU£-q‰âO‡È€":¹­þëÎÉ…vÀ@—8žÍ(=mþ¡Ði¼„1(4}qDCÒ–’¤¡è¤ùÛVIýîdËš>*]¨XŒ?l’dýã ¶í2&>˜iÝ‘&„‹#ç,@W6¬^`çf)yx:¢µ|d8¶gé¡‚pý©¦”±çZ+wAÄI8ò I™xŽ$1QŠGbç b[±Ûaº[óyÏæ.Ú¨CÂÏŸKýйæœ}c5äÄ%… }B ƒÖ=ëgÿDý½ÂŒP¨–÷µ+#táÛÓ9j€¶Ñœgw?z"¾ÉÆNR¨Î&Î& M¾pyÇ‚àÁž›‡{kõfUdû‹‘Äñ?ä£NFôà#é¬ãBãÄWH`_tŸ¶Ña@:g£"•Ü>’dn‡>`”ó1Pû<ú‹C P8k9âÓ>Æ¡5úœ0T*D »ßM¢ l­/ QõÎb){êBd¤ v1ôè°“]¨3räïh 0ï=tmuëñB›à[ø°ŸŸ"‰ãŽ—¤CÆkè,šÑ½(àà©ØYu æ` ²@¤G…-êMà iÌ}x{&Œ*Å·ô’é'ž\÷|·@†Å4H}ЦO¦oIùkOãÉàä,ée3"þ2ºbFqÄüîù7ÁS-yô¾(Ãô¬óô]P¯_S†·véœ Œ³ùç!­:MƒHôàˆ `è@Óàh©ùól©~w‰Õ0p^Q xÐÍDPÅ‘e§N›8Y o}CâÅ„DHµ¨^Àc)»!aH?Ùà›™+9ÓïÄâÇÒç‘a¦0w #bpIW¼|—æ@TÓ ' èØxÐÓå©2*ðJ:t‚Ýò¬¤œ~²è"Ig˜=Öãz"¢óßmÓ “¦fL ÎkGˆ¢¹˜SÔ"å¯<õ@½Š¶’óÖEÎôf,-N˜¸ìø < Y3Ç/°gïá4ŸAgÔäézCÎ^]CšÛ^‚ç\&I'^…x}ÔÁ¤Ìι.êü¹GHýû¿“Ê·h<Ó ˜ª^ †w¸É¡§Lhªºà*)øå{8@t*œ†V‘ =¡ý2e,/A&,ͽäfñg E,”‚Ž3=LèîÌÀª™ÅRõÊŒöÔkƒæöÜ/éÿJ<pHS[>åm©GŸ,Å·Í—Ô³n…ã ‚o4”»µ½õ@À!i;½Ü˜<ëÒ‡qbG™Q-NhŒ©Žü1‰Ù¡ì¥û4A¨ñ?ˆ^/A:vÝÙÎ: ‡ —…×Þ'¹7¼Š¬7`S×n¡]kíd’vÿܨü5HȘt¤œq›´–m€ƒ&´zNO Ò½?k‚VÿA*3:1¥7,ƒ(uò@§@81ÕdÅ0Õ±’9yªݹD’OºÇZ7"ëì—F^¥æ:™5„Eî-Zà%:çLý©ÄŽ<©¼ {¡ƱÞŘý¹‡ÈÞ%·ÊÞ1tó rçÏ@öÌì`!1˜¤¢èÆÇ$çºEØeŽ3æB¸¤ª’Páq¡ŽóøQ ËÐÝ9—Þ£AO5¥·cQ- iÞ… $TñÒ]ÒX² "Å>ÿƒPÛèÆû< ?Fê™`[¦/{ÖéKÑí¯Hêy÷Bfݪ)¥éÈbØÖþx`tÖaëÒŽ?]Ò~ˆ“~ê%H7aßQ±„R' Ù )[4˜€£ÄðŒ6Ó Ž&NwÓJ`E 7€@›…WÝ-·¾‡8ý—ÁR°GS× àѦ-œ´GÐEø b—½@sο t"-•@ıW¦@Ùÿïߥêí×ôqѦðà€YÖÇ/¨PÒ‰ [5ÞS¥à¦ßAIˆ”d¹'Áe-´Ð¥Ê‚2Í—Ù½¢W í”Ú\ ?²úæL¿ƒ©AÏ KР€ •Ù“bR³¤rá-R¿ ž(ÑdôÀé ìåõÊ pW‹Ñn²Îø‘ ¹s‘dþóoq¦ Ö‚u0"“@ ÆÏX(Žw1s[´ýµEä±ÇJæ´çÀ€ŽÈÖÊ)¨Äe‘äapèÚ!å/?‚¤À{Q—g•¹ój‡sg'Z«U7[m µ{ñuR|×IŸò(²!EÙ— `1 Ž€.,:tõ7HÈ:sšÆrl)¥— éç›" ¿¢?D>™'üo}j´ˆQ¼«¹5`ßÓÌÄE«4lÛ {ß_"µÌ“æ]_`'ÂÏiãñ«ñ_7@àTÁe×îðá·`±hkÚ.Ew}!Éc¾c€ˆ ¦E•®Ð»hš±§ªiÑMn'íæ¸À$ÐÂlJ[¡·y¢Û)¸ƒÆ+LÉЧf9¡RŸõ7c×ÒŰY‰#Æh&›âY¯JÖåO#}×i0®UÅXüÃŽ¦¬­ žÒ0xÙ»4ͯYÓ~‘ ˜Õ‚l'1NO Zú€8"0ˆÜ+LX ¦A‚gÔ`|Ø×>÷ž0ìPÉ: -^’œëJÂÓÔrƒ´ìWaŽÁ2"_Ü¥$ªìòÀÀ(P 2'8ŽwÀ4¸ :€ š½Tp]pö¥!€Èæ·@ä·Êµërz[Ë®óD€ƒ=`ä óëD³ÚBETÝW_HÍŠ7¥á³…PnVmw ”UÐ(ªæZ3úh.CžŒãdw:á;tÜ…"€Ý[Ð4cNG0×åXÈ˜Õ ]&zŽÜå&ɽþuÉüþE~ϧ9©Ën]x¿{.ãÓ PرYjW}(µŸ¾)š sšnL"r÷Åg à¼Âàyd}ér¬s4ÆÎÅ CHE+äõo‘flÏ#“`¤á˜'|*pž^IDATï µv=bŒ•¢Y¯KâˆÃ1pò¢ŸG„Âm@¹Û莃•¬ì¾i`k}Ôo^'u«?”úÕË¥iÛbÍåÇÔß1É9àò1A!" T&(Øõôfçr;´÷U¤tÁ“R5ÿg[|¤sµPÑÁHâ2`¡Y%I'üîÝã W²ênT|sPS¸_ê@8'µÊûûƒAËÞJ©ßº^ê×ÿMÖÿUš¶¾–u§ö$†YÍ’FY&1܇P3ë$ÖÇ¢l‚ùhÿu; T®r¶ÔTË®__+µ aÞ0œûç¨@WƒÃFôàÌžñ â?\mîGÕ…éÅ„éÀÐ,L¼Žº^C}A`çiØò¥6~.›?† @@¦_Îy„Äá"`k).”å;>˜Ç褆Øà23 iüþm e÷ƒg‚f1âKÈ5úG&M€¥ŠWÍœ¼C g­”ñÇ·ƒÌþOuïÿ<pÝØÑŒh8ƒŽ2iìØÍ¥(Ù&m_I`ëˆ _`[&m5–D@ý_qŲ ”!àK‡ÊËM¥RtÇrI:,üý:º ºbé‹R1÷ ñåUNHu$q?V„/©œE²–ª5ûC)þå •Ú ÓÕmnúÞ7VǶZ\¿îüN¡ºRmÚ»·IãŽMÒ´s’ŸÂÄX±\Â.,x^‰BOZ`Kñ}ŸKòáGë 8¸­Ø ´­±AJ~s‹Ô½ÿñçMtÚqä€?F Ʀ]x¿\y»!Gи>Áíõ ˜nþÌ I@@ÔW>HÜ­¶¦F•‘›«Ë¥©üi./@ì ì÷À÷¯¥àú¹ÂDÜÝ쯭µìøZv=/Á½+ái9 XO “¤çÏ@ÛÖo¿–üŸýIÒ¿w¶«iÜa‚©IŸ „ƒ¦g]€‚þˆÅÞhÀ¹„d×M‡øË“Š./¤ú\ýÞ)}ò|= "Mè;ᤀ ð'ƒcúI'Â4g-÷6QŠœ* $@ pkginfo.%.tmpl: $(srcdir)/pkginfo.%.tmpl.in Makefile rm -f $@ $(edit) $< > $@ pkginfo.%.full: pkginfo.%.tmpl pkginfo.tmpl Makefile cat pkginfo.tmpl pkginfo.$*.tmpl > $@ # use 'edit' above to transform prototype.in to pkgmk acceptable prototype prototype.%: $(srcdir)/prototype.%.in Makefile rm -f $@ $(edit) $< > $@ # use edit to construct the SMF manifest files %.xml: $(srcdir)/%.xml.in Makefile rm -f $@ $(edit) $< > $@ # use edit to construct the depend files depend.%: $(srcdir)/depend.%.in Makefile rm -f $@ $(edit) $< > $@ # method file (bit like init script) frr.init: $(srcdir)/frr.init.in Makefile rm -f $@ $(edit) $< > $@ # construct the pkg $(PACKAGE_TARNAME)-%-$(pkg_name_rev).pkg: prototype.% \ depend.% frr.init pkginfo.%.full ($(pkg_make) && \ $(pkg_trans) "FRR$*") %.pkg.gz : %.pkg (gzip -c $< > $@) pkg-root-install: (cd $(top_builddir) && \ $(MAKE) DESTDIR=$(abs_builddir)/frr-root install) packages: $(pkg_packages) all-files: $(pkg_pkginfos) pkginfo.tmpl $(pkg_prototypes) \ $(pkg_manifests) $(pkg_depends) frr.init frr-7.2.1/solaris/README.txt0000644000000000000000000001507713610377563012400 00000000000000To build packages for Solaris 10: Requirements: ------------- - Development environment including gcc (eg as shipped with Solaris 10) - The Package tools from Solaris 10 or Solaris Nevada/Express. - i.manifest and r.manifest scripts as supplied with Solaris Express in /usr/sadm/install/scripts/ or from OpenSolaris.org: http://cvs.opensolaris.org/source/xref/usr/src/pkgdefs/common_files/i.manifest http://cvs.opensolaris.org/source/xref/usr/src/pkgdefs/common_files/r.manifest i.manifest must be at least version 1.5. Place these scripts in this directory if you are using Solaris 10 GA (which does not ship with these scripts), or in the solaris/ directory in the FRRouting source. Package creation instructions: ------------------------------ 1. Configure and build FRRouting (frr) in the top level build directory as per normal, eg: ./configure --prefix=/usr/local/frr \ --localstatedir=/var/run/frr \ --enable-gcc-rdynamic --enable-opaque-lsa --enable-ospf-te \ --enable-multipath=64 --enable-user=frr \ --enable-ospfclient=yes --enable-ospfapi=yes \ --enable-group=frr --enable-nssa --enable-opaque-lsa You will need /usr/sfw/bin and /usr/ccs/bin in your path. 2. make install in the top-level build directory, it's a good idea to make use of DESTDIR to install to an alternate root, eg: gmake DESTDIR=/var/tmp/qroot install 3. In this directory (solaris/), run make packages, specifying DESTDIR if appropriate, eg: gmake DESTDIR=/var/tmp/qroot packages This should result in 4 packages being created: frr-libs-...-$ARCH.pkg - FRRlibs frr-daemons-...-$ARCH.pkg - FRRdaemons frr-doc-...-$ARCH.pkg - FRRdoc frr-dev-...-$ARCH.pkg - FRRdev frr-smf-...-$ARCH.pkg - FRRsmf FRRlibs and FRRdaemons are needed for daemon runtime. FRRsmf provides the required bits for Solaris 10+ SMF support. Install and post-install configuration notes: --------------------------------------------- - If you specified a user/group which does not exist per default on Solaris (eg frr/frr) you *must* create these before installing these on a system. The packages do *not* create the users. - The configuration files are not created. You must create the configuration file yourself, either with your complete desired configuration, or else if you wish to use the telnet interface for further configuration you must create them containing at least: password whatever The user which frr runs as must have write permissions on this file, no other user should have read permissions, and you would also have to enable the telnet interface (see below). - SMF notes: - FRRsmf installs a svc:/network/routing/frr service, with an instance for each daemon - The state of all instances of frr service can be inspected with: svcs -l svc:/network/routing/frr or typically just with a shortcut of 'frr': svcs -l frr - A specific instance of the frr service can be inspected by specifying the daemon name as the instance, ie frr:: svcs -l svc:/network/routing/frr:zebra svcs -l svc:/network/routing/frr:ospfd or typically just with the shortcut of 'frr:' or even : svcs -l frr:zebra svcs -l ospfd Eg: # # svcs -l ripd fmri svc:/network/routing/frr:ripd name FRRouting: ripd, RIPv1/2 IPv4 routing protocol daemon. enabled true state online next_state none state_time Wed Jun 15 16:21:02 2005 logfile /var/svc/log/network-routing-frr:ripd.log restarter svc:/system/svc/restarter:default contract_id 93 dependency require_all/restart svc:/network/routing/frr:zebra (online) dependency require_all/restart file://localhost//usr/local/frr/etc/ripd.conf (online) dependency require_all/none svc:/system/filesystem/usr:default (online) dependency require_all/none svc:/network/loopback (online) - Configuration of startup options is by way of SMF properties in a property group named 'frr'. The defaults should automatically be inline with how you configured FRRouting in Step 1 above. - By default the VTY interface is disabled. To change this, see below for how to set the 'frr/vty_port' property as appropriate for /each/ service. Also, the VTY is set to listen only to localhost by default, you may change the 'frr/vty_addr' property as appropriate for both of the 'frr' service and specific individual instances of the 'frr' service (ie frr:zebra, frr:ospfd, etc..). - Properties belonging to the 'frr' service are inherited by all instances. Eg: # svcprop -p frr svc:/network/routing/frr frr/group astring root frr/retain boolean false frr/user astring root frr/vty_addr astring 127.1 frr/vty_port integer 0 # svcprop -p frr svc:/network/routing/frr:ospfd frr/retain_routes boolean false frr/group astring root frr/retain boolean false frr/user astring root frr/vty_addr astring 127.1 frr/vty_port integer 0 All instances will inherit these properties, unless the instance itself overrides these defaults. This also implies one can modify properties of the 'frr' service and have them apply to all daemons. # svccfg -s svc:/network/routing/frr \ setprop frr/vty_addr = astring: ::1 # svcprop -p frr svc:/network/routing/frr frr/group astring root frr/retain boolean false frr/user astring root frr/vty_port integer 0 frr/vty_addr astring ::1 # # You *must* refresh instances to have the property change # # take affect for the 'running snapshot' of service state. # svcadm refresh frr:ospfd # svcprop -p frr svc:/network/routing/frr:ospfd frr/retain_routes boolean false frr/group astring root frr/retain boolean false frr/user astring root frr/vty_port integer 0 frr/vty_addr astring ::1 Other daemon-specific options/properties may be available, however they are not yet honoured/used (eg ospfd/apiserver on svc:/network/ospf). - As SMF is dependency aware, restarting network/zebra will restart all the other daemons. - To upgrade from one set of FRRouting packages to a newer release, one must first pkgrm the installed packages. When one pkgrm's FRRsmf all property configuration will be lost, and any customisations will have to redone after installing the updated FRRsmf package. - These packages are not supported by Sun Microsystems, report bugs via the usual FRRouting channels, ie Issue Tracker. Improvements/contributions of course would be greatly appreciated. frr-7.2.1/solaris/depend.daemons.in0000644000000000000000000000036113610377563014104 00000000000000P FRRlibs FRRouting common runtime libraries @PACKAGE_VERSION@,REV=@CONFDATE@ P SUNWcsu Core Solaris, (Usr) P SUNWcsr Core Solaris Libraries (Root) P SUNWcnetr Core Solaris Network Infrastructure (Root) I SUNWzebrar I SUNWzebrau I CSWzebra frr-7.2.1/solaris/depend.dev.in0000644000000000000000000000011713610377563013233 00000000000000P FRRlibs FRRouting common runtime libraries @PACKAGE_VERSION@,REV=@CONFDATE@ frr-7.2.1/solaris/depend.doc.in0000644000000000000000000000003613610377563013222 00000000000000P SUNWdoc Documentation Tools frr-7.2.1/solaris/depend.libs.in0000644000000000000000000000025213610377563013406 00000000000000P SUNWcslr Core Solaris Libraries (Root) P SUNWcsl Core Solaris, (Shared Libs) P SUNWlibmsr Math & Microtasking Libraries (Root) R FRRdaemons FRRouting daemons R FRRdev frr-7.2.1/solaris/depend.smf.in0000644000000000000000000000033613610377563013245 00000000000000P FRRaemons FRRouting daemons @PACKAGE_VERSION@,REV=@CONFDATE@ P SUNWcsu Core Solaris, (Usr) P SUNWcsr Core Solaris Libraries (Root) P SUNWroute Network Routing daemons/commands (Usr) I SUNWzebrar I SUNWzebrau I CSWzebra frr-7.2.1/solaris/frr.init.in0000755000000000000000000001635113610377563012764 00000000000000#!/sbin/sh # # Copyright 2007 Sun Microsystems, Inc. All rights reserved. # Use is subject to license terms. # # This file is part of FRRouting. # # FRRouting is free software; you can redistribute it and/or modify # it under the terms of the GNU General Public License as published by the # Free Software Foundation; either version 2, or (at your option) any # later version. # # FRRouting 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 FRRouting; see the file COPYING. If not, write to # the Free Software Foundation, Inc., 59 Temple Place - Suite 330, # Boston, MA 02111-1307, USA. # # Starts/stops the given daemon SMFINCLUDE=/lib/svc/share/smf_include.sh ROUTEADMINCLUDE=/lib/svc/share/routing_include.sh GLOBAL_OPTIONS="PAfiug" DAEMON_PATH=@sbindir@ USER=@enable_user@ GROUP=@enable_group@ # handle upgrade of daemon-args SMF property to new routeadm properties # used during upgrade too by routeadm. # relevant to S10U4+ only. handle_routeadm_upgrade () { GLOBAL_OPTIONS="PAfiug" daemon_args=`get_daemon_args $SMF_FMRI` if [ -n "$daemon_args" ]; then set_daemon_value_property "$SMF_FMRI" "$daemon_args" \ "$GLOBAL_OPTIONS" "P" vty_port 0 set_daemon_value_property "$SMF_FMRI" "$daemon_args" \ "$GLOBAL_OPTIONS" "A" vty_address set_daemon_value_property "$SMF_FMRI" "$daemon_args" \ "$GLOBAL_OPTIONS" "f" config_file set_daemon_value_property "$SMF_FMRI" "$daemon_args" \ "$GLOBAL_OPTIONS" "i" pid_file set_daemon_value_property "$SMF_FMRI" "$daemon_args" \ "$GLOBAL_OPTIONS" "u" user set_daemon_value_property "$SMF_FMRI" "$daemon_args" \ "$GLOBAL_OPTIONS" "g" group case "$1" in zebra) set_daemon_boolean_property "$SMF_FMRI" "$daemon_args" \ "${GLOBAL_OPTIONS}b" "b" batch true false ;; ripd|ripngd) set_daemon_boolean_property "$SMF_FMRI" "$daemon_args" \ "${GLOBAL_OPTIONS}r" "r" retain true false ;; bgpd) set_daemon_boolean_property "$SMF_FMRI" "$daemon_args" \ "${GLOBAL_OPTIONS}rnp" "r" retain true false set_daemon_boolean_property "$SMF_FMRI" "$daemon_args" \ "${GLOBAL_OPTIONS}rnp" "n" no_kernel true false set_daemon_value_property "$SMF_FMRI" "$daemon_args" \ "${GLOBAL_OPTIONS}rnp" "p" bgp_port esac clear_daemon_args $SMF_FMRI fi } upgrade_config () { DAEMON=$1 # handle upgrade of SUNWzebra to FRRouting if [ -d "/etc/frr" -a ! -f "/etc/frr/${DAEMON}.conf" ] ; then if [ -f "/etc/sfw/zebra/${DAEMON}.conf" ] ; then cp "/etc/sfw/zebra/${DAEMON}.conf" \ "/etc/frr/${DAEMON}.conf.upgrade" \ || exit $SMF_EXIT_ERR_FATAL chown "${USER}:${GROUP}" "/etc/frr/${DAEMON}.conf.upgrade" \ || exit $SMF_EXIT_ERR_FATAL chmod 0600 "/etc/frr/${DAEMON}.conf.upgrade" \ || exit $SMF_EXIT_ERR_FATAL mv "/etc/frr/${DAEMON}.conf.upgrade" "/etc/frr/${DAEMON}.conf" \ || exit $SMF_EXIT_ERR_FATAL fi fi if [ ! -f "/etc/frr/${DAEMON}.conf" ] ; then touch "/etc/frr/${DAEMON}.conf.new" \ || exit $SMF_EXIT_ERR_FATAL chown "${USER}:${GROUP}" "/etc/frr/${DAEMON}.conf.new" \ || exit $SMF_EXIT_ERR_FATAL chmod 0600 "/etc/frr/${DAEMON}.conf.new" \ || exit $SMF_EXIT_ERR_FATAL mv "/etc/frr/${DAEMON}.conf.new" "/etc/frr/${DAEMON}.conf" \ || exit $SMF_EXIT_ERR_FATAL fi } # Relevant to S10+ frr_is_globalzone () { if [ "${FRR_INIT_ZONENAME:=`/sbin/zonename`}" = "global" \ -o `/sbin/zonename -t` = "exclusive" ]; then return 0 else return 1 fi } routeadm_daemon_args () { # globals args="`get_daemon_option_from_property $SMF_FMRI config_file f`" args="${args} `get_daemon_option_from_property $SMF_FMRI vty_port P`" args="${args} `get_daemon_option_from_property $SMF_FMRI vty_address A`" args="${args} `get_daemon_option_from_property $SMF_FMRI pid_file i`" # user and group we need for config file upgrade.. SMF_USER=`get_routeadm_property $SMF_FMRI user` SMF_GROUP=`get_routeadm_property()$SMF_FMRI group` if [ "${SMF_USER}" ] ; then USER="${SMF_USER}" args="${args} -u ${SMF_USER}" fi if [ "${SMF_GROUP}" ] ; then GROUP="${SMF_GROUP}" args="${args} -g ${SMF_GROUP}" fi case $1 in zebra) args="${args} `get_daemon_option_from_boolean_property $SMF_FMRI batch -b true`" ;; ripd|ripngd) args="${args} `get_daemon_option_from_boolean_property $SMF_FMRI retain -r true`" ;; bgpd) args="${args} `get_daemon_option_from_boolean_property $SMF_FMRI retain -r true`" args="${args} `get_daemon_option_from_boolean_property $SMF_FMRI no_kernel -n true`" args="${args} `get_daemon_option_from_property $SMF_FMRI bgp_port p 179`" ;; esac echo ${args} } # Include smf functions, if available. If not, define smf_present to indicate # there is no SMF. Should allow this script to work pre-S10. if [ -f "$SMFINCLUDE" ] ; then . "$SMFINCLUDE"; # source the SMF-routeadm include if present.. if [ -f "$ROUTEADMINCLUDE" ] ; then . "$ROUTEADMINCLUDE" fi else # pre-SMF system, fake up any functions and exit codes # which SMFINCLUDE usually provides. smf_present () { return 1 } SMF_EXIT_OK=0; SMF_EXIT_ERR_CONFIG=96; SMF_EXIT_ERR_FATAL=95; fi # if there's no SMF, set some default DAEMON_ARGS smf_present || DAEMON_ARGS="" usage () { if smf_present ; then echo "Usage: $0 "; else echo "Usage: $0 "; fi echo "The --pid_file argument is implied"; echo "This help message: $0 "; } # parse arguments, different according to SMF or not. case $1 in 'help' | 'usage') usage exit $SMF_EXIT_OK ;; esac if smf_present ; then FRR_METHOD="start" else FRR_METHOD="$1" shift; fi DAEMON="$1" # daemon path must be given if [ -z "$DAEMON_PATH/$DAEMON" ]; then usage exit $SMF_EXIT_ERR_FATAL fi # only bgpd is suitable for running in a non-global zone, at this # time. case "${DAEMON}" in bgpd) ;; zebra | ospfd | ospf6d | ripd | ripngd ) frr_is_globalzone || exit $SMF_EXIT_OK ;; *) usage exit $SMF_EXIT_ERR_CONFIG; ;; esac # Older FRRouting SMF packages pass daemon args on the commandline # Newer SMF routeadm model uses properties for each argument # so we must handle that. if [ smf_present -a -f "$ROUTEADMINCLUDE" ]; then handle_routeadm_upgrade $DAEMON; DAEMON_ARGS=`routeadm_daemon_args`; else if [ $# -gt 0 ] ; then shift DAEMON_ARGS="$@" fi fi upgrade_config "$DAEMON" if [ ! -f "@sysconfdir@/${DAEMON}.conf" ] ; then echo "Could not find config file, @sysconfdir@/${DAEMON}.conf" exit $SMF_EXIT_ERR_CONFIG fi # we need @frr_statedir@ to exist, it probably is on tmpfs. if [ ! -d @frr_statedir@ ] ; then mkdir -p @frr_statedir@ chown @enable_user@:@enable_group@ @frr_statedir@ chmod 751 @frr_statedir@ fi PIDFILE="@frr_statedir@/${DAEMON}.pid" start () { if [ ! -x "$DAEMON_PATH/$DAEMON" ] ; then echo "Error, could not find daemon, $DAEMON_PATH/$DAEMON" exit $SMF_EXIT_ERR_FATAL fi eval exec $DAEMON_PATH/$DAEMON $DAEMON_ARGS --pid_file ${PIDFILE} & } stop_by_pidfile () { if [ -f "${PIDFILE}" ]; then /usr/bin/kill -TERM `/usr/bin/cat "${PIDFILE}"` fi } case "$FRR_METHOD" in 'start') start ;; 'stop') stop_by_pidfile ;; *) usage exit $SMF_EXIT_ERR_FATAL ;; esac exit $SMF_EXIT_OK; frr-7.2.1/solaris/frr.xml.in0000644000000000000000000005753413610377563012626 00000000000000 frr-7.2.1/solaris/pkginfo.daemons.tmpl.in0000644000000000000000000000010013610377563015244 00000000000000PKG="FRRdaemons" NAME="@PACKAGE_NAME@ - @PACKAGE_NAME@ daemons" frr-7.2.1/solaris/pkginfo.dev.tmpl.in0000644000000000000000000000010513610377563014401 00000000000000PKG=FRRdev NAME="@PACKAGE_NAME@ - @PACKAGE_NAME@ development files" frr-7.2.1/solaris/pkginfo.doc.tmpl.in0000644000000000000000000000010013610377563014363 00000000000000PKG=FRRdoc NAME="@PACKAGE_NAME@ - @PACKAGE_NAME@ documentation" frr-7.2.1/solaris/pkginfo.libs.tmpl.in0000644000000000000000000000011413610377563014554 00000000000000PKG=FRRlibs NAME="@PACKAGE_NAME@ - @PACKAGE_NAME@ common runtime libraries" frr-7.2.1/solaris/pkginfo.smf.tmpl.in0000644000000000000000000000010013610377563014403 00000000000000PKG="FRRsmf" NAME="@PACKAGE_NAME@ - @PACKAGE_NAME@ SMF support" frr-7.2.1/solaris/pkginfo.tmpl.in0000644000000000000000000000042713610377563013633 00000000000000ARCH="@host_cpu@" CATEGORY="system" VERSION="@PACKAGE_VERSION@,REV=@CONFDATE@" VENDOR="http://www.frrouting.org/" HOTLINE="@PACKAGE_BUGREPORT@" EMAIL=maintainers@frrouting.org DESC="@PACKAGE_NAME@ Routing Protocols" MAXINST=1 CLASSES="none preserve renamenew manifest" BASEDIR=/ frr-7.2.1/solaris/prototype.daemons.in0000644000000000000000000000257713610377563014725 00000000000000i pkginfo=$abs_builddir/pkginfo.daemons.full i depend=$abs_builddir/depend.daemons i copying=$abs_top_srcdir/COPYING d none @sbindir@=$DESTDIR/@sbindir@ 0755 root bin f none @sbindir@/zebra=$DESTDIR/@sbindir@/zebra 0755 root bin f none @sbindir@/bgpd=$DESTDIR/@sbindir@/bgpd 0755 root bin f none @sbindir@/ripd=$DESTDIR/@sbindir@/ripd 0755 root bin f none @sbindir@/ripngd=$DESTDIR/@sbindir@/ripngd 0755 root bin f none @sbindir@/ospfd=$DESTDIR/@sbindir@/ospfd 0755 root bin f none @sbindir@/ospf6d=$DESTDIR/@sbindir@/ospf6d 0755 root bin f none @sbindir@/watchfrr=$DESTDIR/@sbindir@/watchfrr 0755 root bin d none @sysconfdir@=$DESTDIR/@sysconfdir@ 0711 @enable_user@ @enable_group@ f none @sysconfdir@/zebra.conf.sample=$DESTDIR/@sysconfdir@/zebra.conf.sample 0644 root bin f none @sysconfdir@/bgpd.conf.sample=$DESTDIR/@sysconfdir@/bgpd.conf.sample 0644 root bin f none @sysconfdir@/bgpd.conf.sample2=$DESTDIR/@sysconfdir@/bgpd.conf.sample2 0644 root bin f none @sysconfdir@/ripd.conf.sample=$DESTDIR/@sysconfdir@/ripd.conf.sample 0644 root bin f none @sysconfdir@/ripngd.conf.sample=$DESTDIR/@sysconfdir@/ripngd.conf.sample 0644 root bin f none @sysconfdir@/ospfd.conf.sample=$DESTDIR/@sysconfdir@/ospfd.conf.sample 0644 root bin f none @sysconfdir@/ospf6d.conf.sample=$DESTDIR/@sysconfdir@/ospf6d.conf.sample 0644 root bin d none @frr_statedir@=$DESTDIR/@frr_statedir@ 0711 @enable_user@ @enable_group@ frr-7.2.1/solaris/prototype.dev.in0000644000000000000000000001061113610377563014041 00000000000000i pkginfo=$abs_builddir/pkginfo.dev.full i depend=$abs_builddir/depend.dev i copying=$abs_top_srcdir/COPYING f none @libdir@/libfrr.la=$DESTDIR/@libdir@/libfrr.la 0755 root bin f none @libdir@/libfrr.a=$DESTDIR/@libdir@/libfrr.a 0644 root bin f none @libdir@/libfrrospfapiclient.la=$DESTDIR/@libdir@/libfrrospfapiclient.la 0755 root bin f none @libdir@/libfrrospfapiclient.a=$DESTDIR/@libdir@/libfrrospfapiclient.a 0644 root bin d none @includedir@=$DESTDIR/@includedir@ 0755 root bin d none @includedir@/frr=$DESTDIR/@includedir@/frr 0755 root bin d none @includedir@/frr/ospfd=$DESTDIR/@includedir@/frr/ospfd 0755 root bin f none @includedir@/frr/ospfd/ospf_api.h=$DESTDIR/@includedir@/frr/ospfd/ospf_api.h 0644 root bin f none @includedir@/frr/ospfd/ospf_asbr.h=$DESTDIR/@includedir@/frr/ospfd/ospf_asbr.h 0644 root bin f none @includedir@/frr/ospfd/ospf_dump.h=$DESTDIR/@includedir@/frr/ospfd/ospf_dump.h 0644 root bin f none @includedir@/frr/ospfd/ospf_lsa.h=$DESTDIR/@includedir@/frr/ospfd/ospf_lsa.h 0644 root bin f none @includedir@/frr/ospfd/ospf_lsdb.h=$DESTDIR/@includedir@/frr/ospfd/ospf_lsdb.h 0644 root bin f none @includedir@/frr/ospfd/ospf_nsm.h=$DESTDIR/@includedir@/frr/ospfd/ospf_nsm.h 0644 root bin f none @includedir@/frr/ospfd/ospf_ism.h=$DESTDIR/@includedir@/frr/ospfd/ospf_ism.h 0644 root bin f none @includedir@/frr/ospfd/ospf_opaque.h=$DESTDIR/@includedir@/frr/ospfd/ospf_opaque.h 0644 root bin f none @includedir@/frr/ospfd/ospfd.h=$DESTDIR/@includedir@/frr/ospfd/ospfd.h 0644 root bin f none @includedir@/frr/buffer.h=$DESTDIR/@includedir@/frr/buffer.h 0644 root bin f none @includedir@/frr/command.h=$DESTDIR/@includedir@/frr/command.h 0644 root bin f none @includedir@/frr/filter.h=$DESTDIR/@includedir@/frr/filter.h 0644 root bin f none @includedir@/frr/getopt.h=$DESTDIR/@includedir@/frr/getopt.h 0644 root bin f none @includedir@/frr/hash.h=$DESTDIR/@includedir@/frr/hash.h 0644 root bin f none @includedir@/frr/if.h=$DESTDIR/@includedir@/frr/if.h 0644 root bin f none @includedir@/frr/linklist.h=$DESTDIR/@includedir@/frr/linklist.h 0644 root bin f none @includedir@/frr/log.h=$DESTDIR/@includedir@/frr/log.h 0644 root bin f none @includedir@/frr/memory.h=$DESTDIR/@includedir@/frr/memory.h 0644 root bin f none @includedir@/frr/network.h=$DESTDIR/@includedir@/frr/network.h 0644 root bin f none @includedir@/frr/prefix.h=$DESTDIR/@includedir@/frr/prefix.h 0644 root bin f none @includedir@/frr/routemap.h=$DESTDIR/@includedir@/frr/routemap.h 0644 root bin f none @includedir@/frr/distribute.h=$DESTDIR/@includedir@/frr/distribute.h 0644 root bin f none @includedir@/frr/sockunion.h=$DESTDIR/@includedir@/frr/sockunion.h 0644 root bin f none @includedir@/frr/str.h=$DESTDIR/@includedir@/frr/str.h 0644 root bin f none @includedir@/frr/stream.h=$DESTDIR/@includedir@/frr/stream.h 0644 root bin f none @includedir@/frr/table.h=$DESTDIR/@includedir@/frr/table.h 0644 root bin f none @includedir@/frr/thread.h=$DESTDIR/@includedir@/frr/thread.h 0644 root bin f none @includedir@/frr/vector.h=$DESTDIR/@includedir@/frr/vector.h 0644 root bin f none @includedir@/frr/version.h=$DESTDIR/@includedir@/frr/version.h 0644 root bin f none @includedir@/frr/vty.h=$DESTDIR/@includedir@/frr/vty.h 0644 root bin f none @includedir@/frr/zebra.h=$DESTDIR/@includedir@/frr/zebra.h 0644 root bin f none @includedir@/frr/plist.h=$DESTDIR/@includedir@/frr/plist.h 0644 root bin f none @includedir@/frr/zclient.h=$DESTDIR/@includedir@/frr/zclient.h 0644 root bin f none @includedir@/frr/sockopt.h=$DESTDIR/@includedir@/frr/sockopt.h 0644 root bin f none @includedir@/frr/smux.h=$DESTDIR/@includedir@/frr/smux.h 0644 root bin f none @includedir@/frr/md5.h=$DESTDIR/@includedir@/frr/md5.h 0644 root bin f none @includedir@/frr/if_rmap.h=$DESTDIR/@includedir@/frr/if_rmap.h 0644 root bin f none @includedir@/frr/keychain.h=$DESTDIR/@includedir@/frr/keychain.h 0644 root bin f none @includedir@/frr/privs.h=$DESTDIR/@includedir@/frr/privs.h 0644 root bin f none @includedir@/frr/sigevent.h=$DESTDIR/@includedir@/frr/sigevent.h 0644 root bin f none @includedir@/frr/pqueue.h=$DESTDIR/@includedir@/frr/pqueue.h 0644 root bin f none @includedir@/frr/jhash.h=$DESTDIR/@includedir@/frr/jhash.h 0644 root bin f none @includedir@/frr/zassert.h=$DESTDIR/@includedir@/frr/zassert.h 0644 root bin d none @includedir@/frr/ospfapi=$DESTDIR/@includedir@/frr/ospfapi 0755 root bin f none @includedir@/frr/ospfapi/ospf_apiclient.h=$DESTDIR/@includedir@/frr/ospfapi/ospf_apiclient.h 0644 root bin frr-7.2.1/solaris/prototype.doc.in0000644000000000000000000000210613610377563014030 00000000000000i pkginfo=$abs_builddir/pkginfo.doc.full i depend=$abs_builddir/depend.doc i copying=$abs_top_srcdir/COPYING d none @infodir@=$DESTDIR/@infodir@ 0755 root bin #f none @infodir@/dir=$DESTDIR/@infodir@/dir 0644 root bin f none @infodir@/frr.info=$DESTDIR/@infodir@/frr.info 0644 root bin d none @mandir@=$DESTDIR/@mandir@ 0755 root bin d none @mandir@/man1=$DESTDIR/@mandir@/man1 0755 root bin f none @mandir@/man1/vtysh.1=$DESTDIR/@mandir@/man1/vtysh.1 0644 root bin d none @mandir@/man8=$DESTDIR/@mandir@/man8 0755 root bin f none @mandir@/man8/frr-bgpd.8=$DESTDIR/@mandir@/man8/bgpd.8 0644 root bin f none @mandir@/man8/frr-ospf6d.8=$DESTDIR/@mandir@/man8/frr-ospf6d.8 0644 root bin f none @mandir@/man8/frr-ospfd.8=$DESTDIR/@mandir@/man8/frr-ospfd.8 0644 root bin f none @mandir@/man8/frr-ripd.8=$DESTDIR/@mandir@/man8/frr-ripd.8 0644 root bin f none @mandir@/man8/frr-ripngd.8=$DESTDIR/@mandir@/man8/frr-ripngd.8 0644 root bin f none @mandir@/man8/frr-zebra.8=$DESTDIR/@mandir@/man8/frr-zebra.8 0644 root bin f none @mandir@/man8/frr-isisd.8=$DESTDIR/@mandir@/man8/frr-isisd.8 0644 root bin frr-7.2.1/solaris/prototype.libs.in0000644000000000000000000000107113610377563014214 00000000000000i pkginfo=$abs_builddir/pkginfo.libs.full i depend=$abs_builddir/depend.libs i copying=$abs_top_srcdir/COPYING d none @libdir@=$DESTDIR/@libdir@ 0755 root bin s none @libdir@/libfrr.so.0=libfrr.so.0.0.0 f none @libdir@/libfrr.so.0.0.0=$DESTDIR/@libdir@/libfrr.so.0.0.0 0755 root bin s none @libdir@/libfrr.so=libfrr.so.0.0.0 f none @libdir@/libfrrospfapiclient.so.0.0.0=$DESTDIR/@libdir@/libfrrospfapiclient.so.0.0.0 0755 root bin s none @libdir@/libfrrospfapiclient.so.0=libfrrospfapiclient.so.0.0.0 s none @libdir@/libfrrospfapiclient.so=libfrrospfapiclient.so.0.0.0 frr-7.2.1/solaris/prototype.smf.in0000644000000000000000000000053613610377563014055 00000000000000i pkginfo=$abs_builddir/pkginfo.smf.full i depend=$abs_builddir/depend.smf i copying=$abs_top_srcdir/COPYING i i.manifest i r.manifest f manifest var/svc/manifest/network/frr.xml 0444 root bin #f none var/svc/profile/@PACKAGE_TARNAME@_options.xml=$abs_builddir/options.xml 0755 root sys f none lib/svc/method/frr=$abs_builddir/frr.init 0755 root bin frr-7.2.1/solaris/subdir.am0000644000000000000000000000216113610377563012477 00000000000000# # solaris # .PHONY: solaris/all if SOLARIS all: solaris/all solaris/all: @make -s -C solaris all endif CLEANFILES += \ solaris/frr.xml \ solaris/frr.init \ solaris/pkginfo.tmpl \ solaris/prototype.daemons \ solaris/prototype.dev \ solaris/prototype.doc \ solaris/prototype.libs \ solaris/prototype.smf \ solaris/pkginfo.daemons.tmpl \ solaris/pkginfo.dev.tmpl \ solaris/pkginfo.doc.tmpl \ solaris/pkginfo.libs.tmpl \ solaris/pkginfo.smf.tmpl \ solaris/depend.daemons \ solaris/depend.dev \ solaris/depend.doc \ solaris/depend.libs \ solaris/depend.smf \ # end EXTRA_DIST += \ solaris/frr.xml.in \ solaris/frr.init.in \ solaris/pkginfo.tmpl.in \ solaris/prototype.daemons.in \ solaris/prototype.dev.in \ solaris/prototype.doc.in \ solaris/prototype.libs.in \ solaris/prototype.smf.in \ solaris/pkginfo.daemons.tmpl.in \ solaris/pkginfo.dev.tmpl.in \ solaris/pkginfo.doc.tmpl.in \ solaris/pkginfo.libs.tmpl.in \ solaris/pkginfo.smf.tmpl.in \ solaris/depend.daemons.in \ solaris/depend.dev.in \ solaris/depend.doc.in \ solaris/depend.libs.in \ solaris/depend.smf.in \ solaris/README.txt \ # end frr-7.2.1/staticd/0000755000000000000000000000000013610377563010727 500000000000000frr-7.2.1/staticd/Makefile0000644000000000000000000000023013610377563012302 00000000000000all: ALWAYS @$(MAKE) -s -C .. staticd/staticd %: ALWAYS @$(MAKE) -s -C .. staticd/$@ Makefile: #nothing ALWAYS: .PHONY: ALWAYS makefiles .SUFFIXES: frr-7.2.1/staticd/static_main.c0000644000000000000000000000617213610377563013314 00000000000000/* * STATICd - main code * Copyright (C) 2018 Cumulus Networks, Inc. * Donald Sharp * * This program is free software; you can redistribute it and/or modify it * under the terms of the 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; see the file COPYING; if not, write to the Free Software * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA */ #include #include #include "getopt.h" #include "thread.h" #include "command.h" #include "log.h" #include "memory.h" #include "privs.h" #include "sigevent.h" #include "libfrr.h" #include "vrf.h" #include "nexthop.h" #include "filter.h" #include "static_vrf.h" #include "static_vty.h" #include "static_routes.h" #include "static_zebra.h" char backup_config_file[256]; bool mpls_enabled; zebra_capabilities_t _caps_p[] = { }; struct zebra_privs_t static_privs = { #if defined(FRR_USER) && defined(FRR_GROUP) .user = FRR_USER, .group = FRR_GROUP, #endif #if defined(VTY_GROUP) .vty_group = VTY_GROUP, #endif .caps_p = _caps_p, .cap_num_p = array_size(_caps_p), .cap_num_i = 0}; struct option longopts[] = { { 0 } }; /* Master of threads. */ struct thread_master *master; /* SIGHUP handler. */ static void sighup(void) { zlog_info("SIGHUP received"); } /* SIGINT / SIGTERM handler. */ static void sigint(void) { zlog_notice("Terminating on signal"); exit(0); } /* SIGUSR1 handler. */ static void sigusr1(void) { zlog_rotate(); } struct quagga_signal_t static_signals[] = { { .signal = SIGHUP, .handler = &sighup, }, { .signal = SIGUSR1, .handler = &sigusr1, }, { .signal = SIGINT, .handler = &sigint, }, { .signal = SIGTERM, .handler = &sigint, }, }; static const struct frr_yang_module_info *staticd_yang_modules[] = { }; #define STATIC_VTY_PORT 2616 FRR_DAEMON_INFO(staticd, STATIC, .vty_port = STATIC_VTY_PORT, .proghelp = "Implementation of STATIC.", .signals = static_signals, .n_signals = array_size(static_signals), .privs = &static_privs, .yang_modules = staticd_yang_modules, .n_yang_modules = array_size(staticd_yang_modules), ) int main(int argc, char **argv, char **envp) { frr_preinit(&staticd_di, argc, argv); frr_opt_add("", longopts, ""); while (1) { int opt; opt = frr_getopt(argc, argv, NULL); if (opt == EOF) break; switch (opt) { case 0: break; default: frr_help_exit(1); break; } } master = frr_init(); access_list_init(); static_vrf_init(); static_zebra_init(); static_vty_init(); snprintf(backup_config_file, sizeof(backup_config_file), "%s/zebra.conf", frr_sysconfdir); staticd_di.backup_config_file = backup_config_file; frr_config_fork(); frr_run(master); /* Not reached. */ return 0; } frr-7.2.1/staticd/static_memory.c0000644000000000000000000000175513610377563013702 00000000000000/* * static memory code. * Copyright (C) 2018 Cumulus Networks, Inc. * Donald Sharp * * This program is free software; you can redistribute it and/or modify it * under the terms of the 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; see the file COPYING; if not, write to the Free Software * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA */ #include #include #include "staticd/static_memory.h" DEFINE_MGROUP(STATIC, "staticd") DEFINE_MTYPE(STATIC, STATIC_ROUTE, "Static Route"); frr-7.2.1/staticd/static_memory.h0000644000000000000000000000167113610377563013704 00000000000000/* * static memory code. * Copyright (C) 2018 Cumulus Networks, Inc. * Donald Sharp * * This program is free software; you can redistribute it and/or modify it * under the terms of the 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; see the file COPYING; if not, write to the Free Software * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA */ #ifndef __STATIC_MEMORY_H__ #include "memory.h" DECLARE_MGROUP(STATIC) DECLARE_MTYPE(STATIC_ROUTE); #endif frr-7.2.1/staticd/static_nht.c0000644000000000000000000001173613610377563013163 00000000000000/* * Static NHT code. * Copyright (C) 2018 Cumulus Networks, Inc. * Donald Sharp * * This program is free software; you can redistribute it and/or modify it * under the terms of the 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; see the file COPYING; if not, write to the Free Software * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA */ #include #include "prefix.h" #include "table.h" #include "vrf.h" #include "nexthop.h" #include "srcdest_table.h" #include "static_vrf.h" #include "static_routes.h" #include "static_zebra.h" #include "static_nht.h" static void static_nht_update_rn(struct route_node *rn, struct prefix *nhp, uint32_t nh_num, vrf_id_t nh_vrf_id, struct vrf *vrf, safi_t safi) { struct static_route *si; for (si = rn->info; si; si = si->next) { if (si->nh_vrf_id != nh_vrf_id) continue; if (si->type != STATIC_IPV4_GATEWAY && si->type != STATIC_IPV4_GATEWAY_IFNAME && si->type != STATIC_IPV6_GATEWAY && si->type != STATIC_IPV6_GATEWAY_IFNAME) continue; if (nhp->family == AF_INET && nhp->u.prefix4.s_addr == si->addr.ipv4.s_addr) si->nh_valid = !!nh_num; if (nhp->family == AF_INET6 && memcmp(&nhp->u.prefix6, &si->addr.ipv6, 16) == 0) si->nh_valid = !!nh_num; if (si->state == STATIC_START) static_zebra_route_add(rn, si, vrf->vrf_id, safi, true); } } static void static_nht_update_safi(struct prefix *sp, struct prefix *nhp, uint32_t nh_num, afi_t afi, safi_t safi, struct vrf *vrf, vrf_id_t nh_vrf_id) { struct route_table *stable; struct static_vrf *svrf; struct route_node *rn; svrf = vrf->info; if (!svrf) return; stable = static_vrf_static_table(afi, safi, svrf); if (!stable) return; if (sp) { rn = srcdest_rnode_lookup(stable, sp, NULL); if (rn) { static_nht_update_rn(rn, nhp, nh_num, nh_vrf_id, vrf, safi); route_unlock_node(rn); } return; } for (rn = route_top(stable); rn; rn = route_next(rn)) static_nht_update_rn(rn, nhp, nh_num, nh_vrf_id, vrf, safi); } void static_nht_update(struct prefix *sp, struct prefix *nhp, uint32_t nh_num, afi_t afi, vrf_id_t nh_vrf_id) { struct vrf *vrf; RB_FOREACH (vrf, vrf_name_head, &vrfs_by_name) { static_nht_update_safi(sp, nhp, nh_num, afi, SAFI_UNICAST, vrf, nh_vrf_id); static_nht_update_safi(sp, nhp, nh_num, afi, SAFI_MULTICAST, vrf, nh_vrf_id); } } static void static_nht_reset_start_safi(struct prefix *nhp, afi_t afi, safi_t safi, struct vrf *vrf, vrf_id_t nh_vrf_id) { struct static_vrf *svrf; struct route_table *stable; struct static_route *si; struct route_node *rn; svrf = vrf->info; if (!svrf) return; stable = static_vrf_static_table(afi, safi, svrf); if (!stable) return; for (rn = route_top(stable); rn; rn = route_next(rn)) { for (si = rn->info; si; si = si->next) { if (si->nh_vrf_id != nh_vrf_id) continue; if (nhp->family == AF_INET && nhp->u.prefix4.s_addr != si->addr.ipv4.s_addr) continue; if (nhp->family == AF_INET6 && memcmp(&nhp->u.prefix6, &si->addr.ipv6, 16) != 0) continue; /* * We've been told that a nexthop we depend * on has changed in some manner, so reset * the state machine to allow us to start * over. */ si->state = STATIC_START; } } } void static_nht_reset_start(struct prefix *nhp, afi_t afi, vrf_id_t nh_vrf_id) { struct vrf *vrf; RB_FOREACH (vrf, vrf_name_head, &vrfs_by_name) { static_nht_reset_start_safi(nhp, afi, SAFI_UNICAST, vrf, nh_vrf_id); static_nht_reset_start_safi(nhp, afi, SAFI_MULTICAST, vrf, nh_vrf_id); } } static void static_nht_mark_state_safi(struct prefix *sp, afi_t afi, safi_t safi, struct vrf *vrf, enum static_install_states state) { struct static_vrf *svrf; struct route_table *stable; struct static_route *si; struct route_node *rn; svrf = vrf->info; if (!svrf) return; stable = static_vrf_static_table(afi, safi, svrf); if (!stable) return; rn = srcdest_rnode_lookup(stable, sp, NULL); if (!rn) return; for (si = rn->info; si; si = si->next) si->state = state; route_unlock_node(rn); } void static_nht_mark_state(struct prefix *sp, vrf_id_t vrf_id, enum static_install_states state) { struct vrf *vrf; afi_t afi = AFI_IP; if (sp->family == AF_INET6) afi = AFI_IP6; vrf = vrf_lookup_by_id(vrf_id); if (!vrf || !vrf->info) return; static_nht_mark_state_safi(sp, afi, SAFI_UNICAST, vrf, state); static_nht_mark_state_safi(sp, afi, SAFI_MULTICAST, vrf, state); } frr-7.2.1/staticd/static_nht.h0000644000000000000000000000344713610377563013170 00000000000000/* * Static NHT header. * Copyright (C) 2018 Cumulus Networks, Inc. * Donald Sharp * * This program is free software; you can redistribute it and/or modify it * under the terms of the 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; see the file COPYING; if not, write to the Free Software * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA */ #ifndef __STATIC_NHT_H__ #define __STATIC_NHT_H__ /* * When we get notification that nexthop tracking has an answer for * us call this function to find the nexthop we are tracking so it * can be installed or removed. * * sp -> The route we are looking at. If NULL then look at all * routes. * nhp -> The nexthop that is being tracked. * nh_num -> number of valid nexthops. * afi -> The afi we are working in. * vrf_id -> The vrf the nexthop is in. */ extern void static_nht_update(struct prefix *sp, struct prefix *nhp, uint32_t nh_num, afi_t afi, vrf_id_t vrf_id); /* * For the given tracked nexthop, nhp, mark all routes that use * this route as in starting state again. */ extern void static_nht_reset_start(struct prefix *nhp, afi_t afi, vrf_id_t nh_vrf_id); /* * For the given prefix, sp, mark it as in a particular state */ extern void static_nht_mark_state(struct prefix *sp, vrf_id_t vrf_id, enum static_install_states state); #endif frr-7.2.1/staticd/static_routes.c0000644000000000000000000003561713610377563013717 00000000000000/* * STATICd - route code * Copyright (C) 2018 Cumulus Networks, Inc. * Donald Sharp * * This program is free software; you can redistribute it and/or modify it * under the terms of the 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; see the file COPYING; if not, write to the Free Software * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA */ #include #include #include #include #include #include #include #include #include "static_vrf.h" #include "static_routes.h" #include "static_memory.h" #include "static_zebra.h" /* Install static route into rib. */ static void static_install_route(struct route_node *rn, struct static_route *si_changed, safi_t safi) { struct static_route *si; for (si = rn->info; si; si = si->next) static_zebra_nht_register(rn, si, true); si = rn->info; if (si) static_zebra_route_add(rn, si_changed, si->vrf_id, safi, true); } /* Uninstall static route from RIB. */ static void static_uninstall_route(vrf_id_t vrf_id, safi_t safi, struct route_node *rn, struct static_route *si_changed) { if (rn->info) static_zebra_route_add(rn, si_changed, vrf_id, safi, true); else static_zebra_route_add(rn, si_changed, vrf_id, safi, false); } int static_add_route(afi_t afi, safi_t safi, uint8_t type, struct prefix *p, struct prefix_ipv6 *src_p, union g_addr *gate, const char *ifname, enum static_blackhole_type bh_type, route_tag_t tag, uint8_t distance, struct static_vrf *svrf, struct static_vrf *nh_svrf, struct static_nh_label *snh_label, uint32_t table_id, bool onlink) { struct route_node *rn; struct static_route *si; struct static_route *pp; struct static_route *cp; struct static_route *update = NULL; struct route_table *stable = svrf->stable[afi][safi]; struct interface *ifp; if (!stable) return -1; if (!gate && (type == STATIC_IPV4_GATEWAY || type == STATIC_IPV4_GATEWAY_IFNAME || type == STATIC_IPV6_GATEWAY || type == STATIC_IPV6_GATEWAY_IFNAME)) return -1; if (!ifname && (type == STATIC_IFNAME || type == STATIC_IPV4_GATEWAY_IFNAME || type == STATIC_IPV6_GATEWAY_IFNAME)) return -1; /* Lookup static route prefix. */ rn = srcdest_rnode_get(stable, p, src_p); /* Do nothing if there is a same static route. */ for (si = rn->info; si; si = si->next) { if (type == si->type && (!gate || ((afi == AFI_IP && IPV4_ADDR_SAME(&gate->ipv4, &si->addr.ipv4)) || (afi == AFI_IP6 && IPV6_ADDR_SAME(gate, &si->addr.ipv6)))) && (!strcmp(ifname ? ifname : "", si->ifname))) { if ((distance == si->distance) && (tag == si->tag) && (table_id == si->table_id) && !memcmp(&si->snh_label, snh_label, sizeof(struct static_nh_label)) && si->bh_type == bh_type && si->onlink == onlink) { route_unlock_node(rn); return 0; } update = si; } } /* Distance or tag or label changed, delete existing first. */ if (update) static_delete_route(afi, safi, type, p, src_p, gate, ifname, update->tag, update->distance, svrf, &update->snh_label, table_id); /* Make new static route structure. */ si = XCALLOC(MTYPE_STATIC_ROUTE, sizeof(struct static_route)); si->type = type; si->distance = distance; si->bh_type = bh_type; si->tag = tag; si->vrf_id = svrf->vrf->vrf_id; si->nh_vrf_id = nh_svrf->vrf->vrf_id; strlcpy(si->nh_vrfname, nh_svrf->vrf->name, sizeof(si->nh_vrfname)); si->table_id = table_id; si->onlink = onlink; if (ifname) strlcpy(si->ifname, ifname, sizeof(si->ifname)); si->ifindex = IFINDEX_INTERNAL; switch (type) { case STATIC_IPV4_GATEWAY: case STATIC_IPV4_GATEWAY_IFNAME: si->addr.ipv4 = gate->ipv4; break; case STATIC_IPV6_GATEWAY: case STATIC_IPV6_GATEWAY_IFNAME: si->addr.ipv6 = gate->ipv6; break; case STATIC_IFNAME: break; } /* Save labels, if any. */ memcpy(&si->snh_label, snh_label, sizeof(struct static_nh_label)); /* * Add new static route information to the tree with sort by * distance value and gateway address. */ for (pp = NULL, cp = rn->info; cp; pp = cp, cp = cp->next) { if (si->distance < cp->distance) break; if (si->distance > cp->distance) continue; if (si->type == STATIC_IPV4_GATEWAY && cp->type == STATIC_IPV4_GATEWAY) { if (ntohl(si->addr.ipv4.s_addr) < ntohl(cp->addr.ipv4.s_addr)) break; if (ntohl(si->addr.ipv4.s_addr) > ntohl(cp->addr.ipv4.s_addr)) continue; } } /* Make linked list. */ if (pp) pp->next = si; else rn->info = si; if (cp) cp->prev = si; si->prev = pp; si->next = cp; /* check whether interface exists in system & install if it does */ switch (si->type) { case STATIC_IPV4_GATEWAY: case STATIC_IPV6_GATEWAY: static_zebra_nht_register(rn, si, true); break; case STATIC_IPV4_GATEWAY_IFNAME: case STATIC_IPV6_GATEWAY_IFNAME: ifp = if_lookup_by_name(ifname, nh_svrf->vrf->vrf_id); if (ifp && ifp->ifindex != IFINDEX_INTERNAL) si->ifindex = ifp->ifindex; else zlog_warn("Static Route using %s interface not installed because the interface does not exist in specified vrf", ifname); static_zebra_nht_register(rn, si, true); break; case STATIC_BLACKHOLE: static_install_route(rn, si, safi); break; case STATIC_IFNAME: ifp = if_lookup_by_name(ifname, nh_svrf->vrf->vrf_id); if (ifp && ifp->ifindex != IFINDEX_INTERNAL) { si->ifindex = ifp->ifindex; static_install_route(rn, si, safi); } else zlog_warn("Static Route using %s interface not installed because the interface does not exist in specified vrf", ifname); break; } return 1; } int static_delete_route(afi_t afi, safi_t safi, uint8_t type, struct prefix *p, struct prefix_ipv6 *src_p, union g_addr *gate, const char *ifname, route_tag_t tag, uint8_t distance, struct static_vrf *svrf, struct static_nh_label *snh_label, uint32_t table_id) { struct route_node *rn; struct static_route *si; struct route_table *stable; /* Lookup table. */ stable = static_vrf_static_table(afi, safi, svrf); if (!stable) return -1; /* Lookup static route prefix. */ rn = srcdest_rnode_lookup(stable, p, src_p); if (!rn) return 0; /* Find same static route is the tree */ for (si = rn->info; si; si = si->next) if (type == si->type && (!gate || ((afi == AFI_IP && IPV4_ADDR_SAME(&gate->ipv4, &si->addr.ipv4)) || (afi == AFI_IP6 && IPV6_ADDR_SAME(gate, &si->addr.ipv6)))) && (!strcmp(ifname ? ifname : "", si->ifname)) && (!tag || (tag == si->tag)) && (table_id == si->table_id) && (!snh_label->num_labels || !memcmp(&si->snh_label, snh_label, sizeof(struct static_nh_label)))) break; /* Can't find static route. */ if (!si) { route_unlock_node(rn); return 0; } static_zebra_nht_register(rn, si, false); /* Unlink static route from linked list. */ if (si->prev) si->prev->next = si->next; else rn->info = si->next; if (si->next) si->next->prev = si->prev; /* * If we have other si nodes then route replace * else delete the route */ static_uninstall_route(si->vrf_id, safi, rn, si); route_unlock_node(rn); /* Free static route configuration. */ XFREE(MTYPE_STATIC_ROUTE, si); route_unlock_node(rn); return 1; } static void static_ifindex_update_af(struct interface *ifp, bool up, afi_t afi, safi_t safi) { struct route_table *stable; struct route_node *rn; struct static_route *si; struct vrf *vrf; RB_FOREACH (vrf, vrf_name_head, &vrfs_by_name) { struct static_vrf *svrf; svrf = vrf->info; stable = static_vrf_static_table(afi, safi, svrf); if (!stable) continue; for (rn = route_top(stable); rn; rn = srcdest_route_next(rn)) { for (si = rn->info; si; si = si->next) { if (!si->ifname[0]) continue; if (up) { if (strcmp(si->ifname, ifp->name)) continue; si->ifindex = ifp->ifindex; } else { if (si->ifindex != ifp->ifindex) continue; si->ifindex = IFINDEX_INTERNAL; } static_install_route(rn, si, safi); } } } } /* * This function looks at a svrf's stable and notices if any of the * nexthops we are using are part of the vrf coming up. * If we are using them then cleanup the nexthop vrf id * to be the new value and then re-installs them * * * stable -> The table we are looking at. * svrf -> The newly changed vrf. * afi -> The afi to look at * safi -> the safi to look at */ static void static_fixup_vrf(struct static_vrf *svrf, struct route_table *stable, afi_t afi, safi_t safi) { struct route_node *rn; struct static_route *si; struct interface *ifp; for (rn = route_top(stable); rn; rn = route_next(rn)) { for (si = rn->info; si; si = si->next) { if (strcmp(svrf->vrf->name, si->nh_vrfname) != 0) continue; si->nh_vrf_id = svrf->vrf->vrf_id; si->nh_registered = false; if (si->ifindex) { ifp = if_lookup_by_name(si->ifname, si->nh_vrf_id); if (ifp) si->ifindex = ifp->ifindex; else continue; } static_install_route(rn, si, safi); } } } /* * This function enables static routes in a svrf as it * is coming up. It sets the new vrf_id as appropriate. * * svrf -> The svrf that is being brought up and enabled by the kernel * stable -> The stable we are looking at. * afi -> the afi in question * safi -> the safi in question */ static void static_enable_vrf(struct static_vrf *svrf, struct route_table *stable, afi_t afi, safi_t safi) { struct route_node *rn; struct static_route *si; struct interface *ifp; struct vrf *vrf = svrf->vrf; for (rn = route_top(stable); rn; rn = route_next(rn)) { for (si = rn->info; si; si = si->next) { si->vrf_id = vrf->vrf_id; if (si->ifindex) { ifp = if_lookup_by_name(si->ifname, si->nh_vrf_id); if (ifp) si->ifindex = ifp->ifindex; else continue; } static_install_route(rn, si, safi); } } } /* * When a vrf is being enabled by the kernel, go through all the * static routes in the system that use this vrf (both nexthops vrfs * and the routes vrf ) * * enable_svrf -> the vrf being enabled */ void static_fixup_vrf_ids(struct static_vrf *enable_svrf) { struct route_table *stable; struct vrf *vrf; afi_t afi; safi_t safi; RB_FOREACH (vrf, vrf_name_head, &vrfs_by_name) { struct static_vrf *svrf; svrf = vrf->info; /* Install any static routes configured for this VRF. */ for (afi = AFI_IP; afi < AFI_MAX; afi++) { for (safi = SAFI_UNICAST; safi < SAFI_MAX; safi++) { stable = svrf->stable[afi][safi]; if (!stable) continue; static_fixup_vrf(enable_svrf, stable, afi, safi); if (enable_svrf == svrf) static_enable_vrf(svrf, stable, afi, safi); } } } } /* * Look at the specified stable and if any of the routes in * this table are using the svrf as the nexthop, uninstall * those routes. * * svrf -> the vrf being disabled * stable -> the table we need to look at. * afi -> the afi in question * safi -> the safi in question */ static void static_cleanup_vrf(struct static_vrf *svrf, struct route_table *stable, afi_t afi, safi_t safi) { struct route_node *rn; struct static_route *si; for (rn = route_top(stable); rn; rn = route_next(rn)) { for (si = rn->info; si; si = si->next) { if (strcmp(svrf->vrf->name, si->nh_vrfname) != 0) continue; static_uninstall_route(si->vrf_id, safi, rn, si); } } } /* * Look at all static routes in this table and uninstall * them. * * stable -> The table to uninstall from * afi -> The afi in question * safi -> the safi in question */ static void static_disable_vrf(struct route_table *stable, afi_t afi, safi_t safi) { struct route_node *rn; struct static_route *si; for (rn = route_top(stable); rn; rn = route_next(rn)) for (si = rn->info; si; si = si->next) static_uninstall_route(si->vrf_id, safi, rn, si); } /* * When the disable_svrf is shutdown by the kernel, we call * this function and it cleans up all static routes using * this vrf as a nexthop as well as all static routes * in it's stables. * * disable_svrf - The vrf being disabled */ void static_cleanup_vrf_ids(struct static_vrf *disable_svrf) { struct vrf *vrf; afi_t afi; safi_t safi; RB_FOREACH (vrf, vrf_name_head, &vrfs_by_name) { struct static_vrf *svrf; svrf = vrf->info; /* Uninstall any static routes configured for this VRF. */ for (afi = AFI_IP; afi < AFI_MAX; afi++) { for (safi = SAFI_UNICAST; safi < SAFI_MAX; safi++) { struct route_table *stable; stable = svrf->stable[afi][safi]; if (!stable) continue; static_cleanup_vrf(disable_svrf, stable, afi, safi); if (disable_svrf == svrf) static_disable_vrf(stable, afi, safi); } } } } /* * This function enables static routes when an interface it relies * on in a different vrf is coming up. * * stable -> The stable we are looking at. * ifp -> interface coming up * afi -> the afi in question * safi -> the safi in question */ static void static_fixup_intf_nh(struct route_table *stable, struct interface *ifp, afi_t afi, safi_t safi) { struct route_node *rn; struct static_route *si; for (rn = route_top(stable); rn; rn = route_next(rn)) { for (si = rn->info; si; si = si->next) { if (si->nh_vrf_id != ifp->vrf_id) continue; if (si->ifindex != ifp->ifindex) continue; static_install_route(rn, si, safi); } } } /* * This function enables static routes that rely on an interface in * a different vrf when that interface comes up. */ void static_install_intf_nh(struct interface *ifp) { struct route_table *stable; struct vrf *vrf; afi_t afi; safi_t safi; RB_FOREACH(vrf, vrf_name_head, &vrfs_by_name) { struct static_vrf *svrf = vrf->info; /* Not needed if same vrf since happens naturally */ if (vrf->vrf_id == ifp->vrf_id) continue; /* Install any static routes configured for this interface. */ for (afi = AFI_IP; afi < AFI_MAX; afi++) { for (safi = SAFI_UNICAST; safi < SAFI_MAX; safi++) { stable = svrf->stable[afi][safi]; if (!stable) continue; static_fixup_intf_nh(stable, ifp, afi, safi); } } } } /* called from if_{add,delete}_update, i.e. when ifindex becomes [in]valid */ void static_ifindex_update(struct interface *ifp, bool up) { static_ifindex_update_af(ifp, up, AFI_IP, SAFI_UNICAST); static_ifindex_update_af(ifp, up, AFI_IP, SAFI_MULTICAST); static_ifindex_update_af(ifp, up, AFI_IP6, SAFI_UNICAST); static_ifindex_update_af(ifp, up, AFI_IP6, SAFI_MULTICAST); } frr-7.2.1/staticd/static_routes.h0000644000000000000000000000774713610377563013727 00000000000000/* * STATICd - static routes header * Copyright (C) 2018 Cumulus Networks, Inc. * Donald Sharp * * This program is free software; you can redistribute it and/or modify it * under the terms of the 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; see the file COPYING; if not, write to the Free Software * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA */ #ifndef __STATIC_ROUTES_H__ #define __STATIC_ROUTES_H__ #include "lib/mpls.h" /* Static route label information */ struct static_nh_label { uint8_t num_labels; uint8_t reserved[3]; mpls_label_t label[MPLS_MAX_LABELS]; }; enum static_blackhole_type { STATIC_BLACKHOLE_DROP = 0, STATIC_BLACKHOLE_NULL, STATIC_BLACKHOLE_REJECT }; typedef enum { STATIC_IFNAME, STATIC_IPV4_GATEWAY, STATIC_IPV4_GATEWAY_IFNAME, STATIC_BLACKHOLE, STATIC_IPV6_GATEWAY, STATIC_IPV6_GATEWAY_IFNAME, } static_types; /* * Route Creation gives us: * START -> Initial State, only exit is when we send the route to * zebra for installation * When we send the route to Zebra move to SENT_TO_ZEBRA * SENT_TO_ZEBRA -> A way to notice that we've sent the route to zebra * But have not received a response on it's status yet * After The response from zebra we move to INSTALLED or FAILED * INSTALLED -> Route was accepted * FAILED -> Route was rejected * When we receive notification about a nexthop that a route uses * We move the route back to START and initiate the process again. */ enum static_install_states { STATIC_START, STATIC_SENT_TO_ZEBRA, STATIC_INSTALLED, STATIC_NOT_INSTALLED, }; /* Static route information. */ struct static_route { /* For linked list. */ struct static_route *prev; struct static_route *next; /* VRF identifier. */ vrf_id_t vrf_id; vrf_id_t nh_vrf_id; char nh_vrfname[VRF_NAMSIZ + 1]; /* * States that we walk the route through * To know where we are. */ enum static_install_states state; /* Administrative distance. */ uint8_t distance; /* Tag */ route_tag_t tag; /* Flag for this static route's type. */ static_types type; /* * Nexthop value. */ enum static_blackhole_type bh_type; union g_addr addr; ifindex_t ifindex; bool nh_registered; bool nh_valid; char ifname[INTERFACE_NAMSIZ + 1]; /* Label information */ struct static_nh_label snh_label; uint32_t table_id; /* * Whether to pretend the nexthop is directly attached to the specified * link. Only meaningful when both a gateway address and interface name * are specified. */ bool onlink; }; extern bool mpls_enabled; extern struct zebra_privs_t static_privs; void static_fixup_vrf_ids(struct static_vrf *svrf); extern int static_add_route(afi_t afi, safi_t safi, uint8_t type, struct prefix *p, struct prefix_ipv6 *src_p, union g_addr *gate, const char *ifname, enum static_blackhole_type bh_type, route_tag_t tag, uint8_t distance, struct static_vrf *svrf, struct static_vrf *nh_svrf, struct static_nh_label *snh_label, uint32_t table_id, bool onlink); extern int static_delete_route(afi_t afi, safi_t safi, uint8_t type, struct prefix *p, struct prefix_ipv6 *src_p, union g_addr *gate, const char *ifname, route_tag_t tag, uint8_t distance, struct static_vrf *svrf, struct static_nh_label *snh_label, uint32_t table_id); extern void static_cleanup_vrf_ids(struct static_vrf *disable_svrf); extern void static_install_intf_nh(struct interface *ifp); extern void static_ifindex_update(struct interface *ifp, bool up); #endif frr-7.2.1/staticd/static_vrf.c0000644000000000000000000001062713610377563013165 00000000000000/* * STATICd - vrf code * Copyright (C) 2018 Cumulus Networks, Inc. * Donald Sharp * * This program is free software; you can redistribute it and/or modify it * under the terms of the 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; see the file COPYING; if not, write to the Free Software * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA */ #include #include "vrf.h" #include "nexthop.h" #include "table.h" #include "srcdest_table.h" #include "static_memory.h" #include "static_vrf.h" #include "static_routes.h" #include "static_vty.h" static void zebra_stable_node_cleanup(struct route_table *table, struct route_node *node) { struct static_route *si, *next; if (node->info) for (si = node->info; si; si = next) { next = si->next; XFREE(MTYPE_STATIC_ROUTE, si); } } static struct static_vrf *static_vrf_alloc(void) { struct route_table *table; struct static_vrf *svrf; safi_t safi; afi_t afi; svrf = XCALLOC(MTYPE_TMP, sizeof(struct static_vrf)); for (afi = AFI_IP; afi <= AFI_IP6; afi++) { for (safi = SAFI_UNICAST; safi <= SAFI_MULTICAST; safi++) { if (afi == AFI_IP6) table = srcdest_table_init(); else table = route_table_init(); table->cleanup = zebra_stable_node_cleanup; svrf->stable[afi][safi] = table; } } return svrf; } static int static_vrf_new(struct vrf *vrf) { struct static_vrf *svrf; svrf = static_vrf_alloc(); vrf->info = svrf; svrf->vrf = vrf; return 0; } static int static_vrf_enable(struct vrf *vrf) { static_fixup_vrf_ids(vrf->info); /* * We may have static routes that are now possible to * insert into the appropriate tables */ static_config_install_delayed_routes(vrf->info); return 0; } static int static_vrf_disable(struct vrf *vrf) { return 0; } static int static_vrf_delete(struct vrf *vrf) { struct route_table *table; struct static_vrf *svrf; safi_t safi; afi_t afi; svrf = vrf->info; for (afi = AFI_IP; afi <= AFI_IP6; afi++) { for (safi = SAFI_UNICAST; safi <= SAFI_MULTICAST; safi++) { table = svrf->stable[afi][safi]; route_table_finish(table); svrf->stable[afi][safi] = NULL; } } return 0; } /* Lookup the static routing table in a VRF. */ struct route_table *static_vrf_static_table(afi_t afi, safi_t safi, struct static_vrf *svrf) { if (!svrf) return NULL; if (afi >= AFI_MAX || safi >= SAFI_MAX) return NULL; return svrf->stable[afi][safi]; } struct static_vrf *static_vrf_lookup_by_id(vrf_id_t vrf_id) { struct vrf *vrf; vrf = vrf_lookup_by_id(vrf_id); if (vrf) return ((struct static_vrf *)vrf->info); return NULL; } struct static_vrf *static_vrf_lookup_by_name(const char *name) { struct vrf *vrf; if (!name) name = VRF_DEFAULT_NAME; vrf = vrf_lookup_by_name(name); if (vrf) return ((struct static_vrf *)vrf->info); return NULL; } static int static_vrf_config_write(struct vty *vty) { struct vrf *vrf; RB_FOREACH (vrf, vrf_name_head, &vrfs_by_name) { if (vrf->vrf_id != VRF_DEFAULT) vty_frame(vty, "vrf %s\n", vrf->name); static_config(vty, vrf->info, AFI_IP, SAFI_UNICAST, "ip route"); static_config(vty, vrf->info, AFI_IP, SAFI_MULTICAST, "ip mroute"); static_config(vty, vrf->info, AFI_IP6, SAFI_UNICAST, "ipv6 route"); if (vrf->vrf_id != VRF_DEFAULT) vty_endframe(vty, " exit-vrf\n!\n"); } return 0; } int static_vrf_has_config(struct static_vrf *svrf) { struct route_table *table; safi_t safi; afi_t afi; /* * NOTE: This is a don't care for the default VRF, but we go through * the motions to keep things consistent. */ for (afi = AFI_IP; afi < AFI_MAX; afi++) { for (safi = SAFI_UNICAST; safi < SAFI_MAX; safi++) { table = svrf->stable[afi][safi]; if (!table) continue; if (route_table_count(table)) return 1; } } return 0; } void static_vrf_init(void) { vrf_init(static_vrf_new, static_vrf_enable, static_vrf_disable, static_vrf_delete, NULL); vrf_cmd_init(static_vrf_config_write, &static_privs); } frr-7.2.1/staticd/static_vrf.h0000644000000000000000000000243313610377563013166 00000000000000/* * STATICd - vrf header * Copyright (C) 2018 Cumulus Networks, Inc. * Donald Sharp * * This program is free software; you can redistribute it and/or modify it * under the terms of the 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; see the file COPYING; if not, write to the Free Software * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA */ #ifndef __STATIC_VRF_H__ #define __STATIC_VRF_H__ struct static_vrf { struct vrf *vrf; struct route_table *stable[AFI_MAX][SAFI_MAX]; }; struct static_vrf *static_vrf_lookup_by_name(const char *vrf_name); struct static_vrf *static_vrf_lookup_by_id(vrf_id_t vrf_id); int static_vrf_has_config(struct static_vrf *svrf); void static_vrf_init(void); struct route_table *static_vrf_static_table(afi_t afi, safi_t safi, struct static_vrf *svrf); #endif frr-7.2.1/staticd/static_vty.c0000644000000000000000000012235413610377563013213 00000000000000/* * STATICd - vty code * Copyright (C) 2018 Cumulus Networks, Inc. * Donald Sharp * * This program is free software; you can redistribute it and/or modify it * under the terms of the 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; see the file COPYING; if not, write to the Free Software * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA */ #include #include "command.h" #include "vty.h" #include "vrf.h" #include "prefix.h" #include "nexthop.h" #include "table.h" #include "srcdest_table.h" #include "mpls.h" #include "static_vrf.h" #include "static_memory.h" #include "static_vty.h" #include "static_routes.h" #ifndef VTYSH_EXTRACT_PL #include "staticd/static_vty_clippy.c" #endif static struct static_vrf *static_vty_get_unknown_vrf(struct vty *vty, const char *vrf_name) { struct static_vrf *svrf; struct vrf *vrf; svrf = static_vrf_lookup_by_name(vrf_name); if (svrf) return svrf; vrf = vrf_get(VRF_UNKNOWN, vrf_name); if (!vrf) { vty_out(vty, "%% Could not create vrf %s\n", vrf_name); return NULL; } svrf = vrf->info; if (!svrf) { vty_out(vty, "%% Could not create vrf-info %s\n", vrf_name); return NULL; } /* Mark as having FRR configuration */ vrf_set_user_cfged(vrf); return svrf; } struct static_hold_route { char *vrf_name; char *nhvrf_name; afi_t afi; safi_t safi; char *dest_str; char *mask_str; char *src_str; char *gate_str; char *ifname; char *flag_str; char *tag_str; char *distance_str; char *label_str; char *table_str; bool onlink; /* processed & masked destination, used for config display */ struct prefix dest; }; static struct list *static_list; static int static_list_compare_helper(const char *s1, const char *s2) { /* extra (!s1 && !s2) to keep SA happy */ if (s1 == s2 || (!s1 && !s2)) return 0; if (!s1 && s2) return -1; if (s1 && !s2) return 1; return strcmp(s1, s2); } static void static_list_delete(struct static_hold_route *shr) { XFREE(MTYPE_STATIC_ROUTE, shr->vrf_name); XFREE(MTYPE_STATIC_ROUTE, shr->nhvrf_name); XFREE(MTYPE_STATIC_ROUTE, shr->dest_str); XFREE(MTYPE_STATIC_ROUTE, shr->mask_str); XFREE(MTYPE_STATIC_ROUTE, shr->src_str); XFREE(MTYPE_STATIC_ROUTE, shr->gate_str); XFREE(MTYPE_STATIC_ROUTE, shr->ifname); XFREE(MTYPE_STATIC_ROUTE, shr->flag_str); XFREE(MTYPE_STATIC_ROUTE, shr->tag_str); XFREE(MTYPE_STATIC_ROUTE, shr->distance_str); XFREE(MTYPE_STATIC_ROUTE, shr->label_str); XFREE(MTYPE_STATIC_ROUTE, shr->table_str); XFREE(MTYPE_STATIC_ROUTE, shr); } static int static_list_compare(void *arg1, void *arg2) { struct static_hold_route *shr1 = arg1; struct static_hold_route *shr2 = arg2; int ret; ret = strcmp(shr1->vrf_name, shr2->vrf_name); if (ret) return ret; ret = strcmp(shr1->nhvrf_name, shr2->nhvrf_name); if (ret) return ret; ret = shr1->afi - shr2->afi; if (ret) return ret; ret = shr1->safi - shr2->safi; if (ret) return ret; ret = prefix_cmp(&shr1->dest, &shr2->dest); if (ret) return ret; ret = static_list_compare_helper(shr1->src_str, shr2->src_str); if (ret) return ret; ret = static_list_compare_helper(shr1->gate_str, shr2->gate_str); if (ret) return ret; ret = static_list_compare_helper(shr1->ifname, shr2->ifname); if (ret) return ret; ret = static_list_compare_helper(shr1->flag_str, shr2->flag_str); if (ret) return ret; ret = static_list_compare_helper(shr1->tag_str, shr2->tag_str); if (ret) return ret; ret = static_list_compare_helper(shr1->distance_str, shr2->distance_str); if (ret) return ret; ret = static_list_compare_helper(shr1->table_str, shr2->table_str); if (ret) return ret; return static_list_compare_helper(shr1->label_str, shr2->label_str); } /* General function for static route. */ static int zebra_static_route_holdem( struct static_vrf *svrf, struct static_vrf *nh_svrf, afi_t afi, safi_t safi, const char *negate, struct prefix *dest, const char *dest_str, const char *mask_str, const char *src_str, const char *gate_str, const char *ifname, const char *flag_str, const char *tag_str, const char *distance_str, const char *label_str, const char *table_str, bool onlink) { struct static_hold_route *shr, *lookup; struct listnode *node; zlog_warn("Static Route to %s not installed currently because dependent config not fully available", dest_str); shr = XCALLOC(MTYPE_STATIC_ROUTE, sizeof(*shr)); shr->vrf_name = XSTRDUP(MTYPE_STATIC_ROUTE, svrf->vrf->name); shr->nhvrf_name = XSTRDUP(MTYPE_STATIC_ROUTE, nh_svrf->vrf->name); shr->afi = afi; shr->safi = safi; shr->onlink = onlink; if (dest) prefix_copy(&shr->dest, dest); if (dest_str) shr->dest_str = XSTRDUP(MTYPE_STATIC_ROUTE, dest_str); if (mask_str) shr->mask_str = XSTRDUP(MTYPE_STATIC_ROUTE, mask_str); if (src_str) shr->src_str = XSTRDUP(MTYPE_STATIC_ROUTE, src_str); if (gate_str) shr->gate_str = XSTRDUP(MTYPE_STATIC_ROUTE, gate_str); if (ifname) shr->ifname = XSTRDUP(MTYPE_STATIC_ROUTE, ifname); if (flag_str) shr->flag_str = XSTRDUP(MTYPE_STATIC_ROUTE, flag_str); if (tag_str) shr->tag_str = XSTRDUP(MTYPE_STATIC_ROUTE, tag_str); if (distance_str) shr->distance_str = XSTRDUP(MTYPE_STATIC_ROUTE, distance_str); if (label_str) shr->label_str = XSTRDUP(MTYPE_STATIC_ROUTE, label_str); if (table_str) shr->table_str = XSTRDUP(MTYPE_STATIC_ROUTE, table_str); for (ALL_LIST_ELEMENTS_RO(static_list, node, lookup)) { if (static_list_compare(shr, lookup) == 0) break; } if (lookup) { if (negate) { listnode_delete(static_list, lookup); static_list_delete(shr); static_list_delete(lookup); return CMD_SUCCESS; } /* * If a person enters the same line again * we need to silently accept it */ goto shr_cleanup; } if (!negate) { listnode_add_sort(static_list, shr); return CMD_SUCCESS; } shr_cleanup: XFREE(MTYPE_STATIC_ROUTE, shr->nhvrf_name); XFREE(MTYPE_STATIC_ROUTE, shr->vrf_name); XFREE(MTYPE_STATIC_ROUTE, shr); return CMD_SUCCESS; } static int static_route_leak( struct vty *vty, struct static_vrf *svrf, struct static_vrf *nh_svrf, afi_t afi, safi_t safi, const char *negate, const char *dest_str, const char *mask_str, const char *src_str, const char *gate_str, const char *ifname, const char *flag_str, const char *tag_str, const char *distance_str, const char *label_str, const char *table_str, bool onlink) { int ret; uint8_t distance; struct prefix p, src; struct prefix_ipv6 *src_p = NULL; union g_addr gate; union g_addr *gatep = NULL; struct in_addr mask; enum static_blackhole_type bh_type = 0; route_tag_t tag = 0; uint8_t type; struct static_nh_label snh_label; uint32_t table_id = 0; ret = str2prefix(dest_str, &p); if (ret <= 0) { if (vty) vty_out(vty, "%% Malformed address\n"); else zlog_warn("%s: Malformed address: %s", __PRETTY_FUNCTION__, dest_str); return CMD_WARNING_CONFIG_FAILED; } switch (afi) { case AFI_IP: /* Cisco like mask notation. */ if (mask_str) { ret = inet_aton(mask_str, &mask); if (ret == 0) { if (vty) vty_out(vty, "%% Malformed address\n"); else zlog_warn("%s: Malformed address: %s", __PRETTY_FUNCTION__, mask_str); return CMD_WARNING_CONFIG_FAILED; } p.prefixlen = ip_masklen(mask); } break; case AFI_IP6: /* srcdest routing */ if (src_str) { ret = str2prefix(src_str, &src); if (ret <= 0 || src.family != AF_INET6) { if (vty) vty_out(vty, "%% Malformed source address\n"); else zlog_warn( "%s: Malformed source address: %s", __PRETTY_FUNCTION__, src_str); return CMD_WARNING_CONFIG_FAILED; } src_p = (struct prefix_ipv6 *)&src; } break; default: break; } /* Apply mask for given prefix. */ apply_mask(&p); if (svrf->vrf->vrf_id == VRF_UNKNOWN || nh_svrf->vrf->vrf_id == VRF_UNKNOWN) { vrf_set_user_cfged(svrf->vrf); return zebra_static_route_holdem( svrf, nh_svrf, afi, safi, negate, &p, dest_str, mask_str, src_str, gate_str, ifname, flag_str, tag_str, distance_str, label_str, table_str, onlink); } if (table_str) { /* table configured. check consistent with vrf config */ if (svrf->vrf->data.l.table_id != RT_TABLE_MAIN) { if (vty) vty_out(vty, "%% Table %s overlaps vrf table %u\n", table_str, svrf->vrf->data.l.table_id); else zlog_warn( "%s: Table %s overlaps vrf table %u", __PRETTY_FUNCTION__, table_str, svrf->vrf->data.l.table_id); return CMD_WARNING_CONFIG_FAILED; } } /* Administrative distance. */ if (distance_str) distance = atoi(distance_str); else distance = ZEBRA_STATIC_DISTANCE_DEFAULT; /* tag */ if (tag_str) tag = strtoul(tag_str, NULL, 10); /* Labels */ memset(&snh_label, 0, sizeof(struct static_nh_label)); if (label_str) { if (!mpls_enabled) { if (vty) vty_out(vty, "%% MPLS not turned on in kernel, ignoring command\n"); else zlog_warn( "%s: MPLS not turned on in kernel ignoring static route to %s", __PRETTY_FUNCTION__, dest_str); return CMD_WARNING_CONFIG_FAILED; } int rc = mpls_str2label(label_str, &snh_label.num_labels, snh_label.label); if (rc < 0) { switch (rc) { case -1: if (vty) vty_out(vty, "%% Malformed label(s)\n"); else zlog_warn( "%s: Malformed labels specified for route %s", __PRETTY_FUNCTION__, dest_str); break; case -2: if (vty) vty_out(vty, "%% Cannot use reserved label(s) (%d-%d)\n", MPLS_LABEL_RESERVED_MIN, MPLS_LABEL_RESERVED_MAX); else zlog_warn( "%s: Cannot use reserved labels (%d-%d) for %s", __PRETTY_FUNCTION__, MPLS_LABEL_RESERVED_MIN, MPLS_LABEL_RESERVED_MAX, dest_str); break; case -3: if (vty) vty_out(vty, "%% Too many labels. Enter %d or fewer\n", MPLS_MAX_LABELS); else zlog_warn( "%s: Too many labels, Enter %d or fewer for %s", __PRETTY_FUNCTION__, MPLS_MAX_LABELS, dest_str); break; } return CMD_WARNING_CONFIG_FAILED; } } /* TableID */ if (table_str) table_id = atol(table_str); /* Null0 static route. */ if (ifname != NULL) { if (strncasecmp(ifname, "Null0", strlen(ifname)) == 0 || strncasecmp(ifname, "reject", strlen(ifname)) == 0 || strncasecmp(ifname, "blackhole", strlen(ifname)) == 0) { if (vty) vty_out(vty, "%% Nexthop interface cannot be Null0, reject or blackhole\n"); else zlog_warn( "%s: Nexthop interface cannot be Null0, reject or blackhole for %s", __PRETTY_FUNCTION__, dest_str); return CMD_WARNING_CONFIG_FAILED; } } /* Route flags */ if (flag_str) { switch (flag_str[0]) { case 'r': bh_type = STATIC_BLACKHOLE_REJECT; break; case 'b': bh_type = STATIC_BLACKHOLE_DROP; break; case 'N': bh_type = STATIC_BLACKHOLE_NULL; break; default: if (vty) vty_out(vty, "%% Malformed flag %s \n", flag_str); else zlog_warn("%s: Malformed flag %s for %s", __PRETTY_FUNCTION__, flag_str, dest_str); return CMD_WARNING_CONFIG_FAILED; } } if (gate_str) { if (inet_pton(afi2family(afi), gate_str, &gate) != 1) { if (vty) vty_out(vty, "%% Malformed nexthop address %s\n", gate_str); else zlog_warn( "%s: Malformed nexthop address %s for %s", __PRETTY_FUNCTION__, gate_str, dest_str); return CMD_WARNING_CONFIG_FAILED; } gatep = &gate; } if (gate_str == NULL && ifname == NULL) type = STATIC_BLACKHOLE; else if (gate_str && ifname) { if (afi == AFI_IP) type = STATIC_IPV4_GATEWAY_IFNAME; else type = STATIC_IPV6_GATEWAY_IFNAME; } else if (ifname) type = STATIC_IFNAME; else { if (afi == AFI_IP) type = STATIC_IPV4_GATEWAY; else type = STATIC_IPV6_GATEWAY; } if (!negate) { static_add_route(afi, safi, type, &p, src_p, gatep, ifname, bh_type, tag, distance, svrf, nh_svrf, &snh_label, table_id, onlink); /* Mark as having FRR configuration */ vrf_set_user_cfged(svrf->vrf); } else { static_delete_route(afi, safi, type, &p, src_p, gatep, ifname, tag, distance, svrf, &snh_label, table_id); /* If no other FRR config for this VRF, mark accordingly. */ if (!static_vrf_has_config(svrf)) vrf_reset_user_cfged(svrf->vrf); } return CMD_SUCCESS; } static int static_route(struct vty *vty, afi_t afi, safi_t safi, const char *negate, const char *dest_str, const char *mask_str, const char *src_str, const char *gate_str, const char *ifname, const char *flag_str, const char *tag_str, const char *distance_str, const char *vrf_name, const char *label_str, const char *table_str) { struct static_vrf *svrf; /* VRF id */ svrf = static_vrf_lookup_by_name(vrf_name); /* When trying to delete, the VRF must exist. */ if (negate && !svrf) { vty_out(vty, "%% vrf %s is not defined\n", vrf_name); return CMD_WARNING_CONFIG_FAILED; } /* When trying to create, create the VRF if it doesn't exist. * Note: The VRF isn't active until we hear about it from the kernel. */ if (!svrf) { svrf = static_vty_get_unknown_vrf(vty, vrf_name); if (!svrf) return CMD_WARNING_CONFIG_FAILED; } return static_route_leak(vty, svrf, svrf, afi, safi, negate, dest_str, mask_str, src_str, gate_str, ifname, flag_str, tag_str, distance_str, label_str, table_str, false); } void static_config_install_delayed_routes(struct static_vrf *svrf) { struct listnode *node, *nnode; struct static_hold_route *shr; struct static_vrf *osvrf, *nh_svrf; int installed; for (ALL_LIST_ELEMENTS(static_list, node, nnode, shr)) { osvrf = static_vrf_lookup_by_name(shr->vrf_name); nh_svrf = static_vrf_lookup_by_name(shr->nhvrf_name); if (osvrf != svrf && nh_svrf != svrf) continue; if (osvrf->vrf->vrf_id == VRF_UNKNOWN || nh_svrf->vrf->vrf_id == VRF_UNKNOWN) continue; installed = static_route_leak( NULL, osvrf, nh_svrf, shr->afi, shr->safi, NULL, shr->dest_str, shr->mask_str, shr->src_str, shr->gate_str, shr->ifname, shr->flag_str, shr->tag_str, shr->distance_str, shr->label_str, shr->table_str, shr->onlink); if (installed != CMD_SUCCESS) zlog_debug( "%s: Attempt to install %s as a route and it was rejected", __PRETTY_FUNCTION__, shr->dest_str); listnode_delete(static_list, shr); static_list_delete(shr); } } /* Write static route configuration. */ int static_config(struct vty *vty, struct static_vrf *svrf, afi_t afi, safi_t safi, const char *cmd) { struct static_hold_route *shr; struct listnode *node; char spacing[100]; struct route_node *rn; struct static_route *si; struct route_table *stable; char buf[SRCDEST2STR_BUFFER]; int write = 0; stable = svrf->stable[afi][safi]; if (stable == NULL) return write; sprintf(spacing, "%s%s", (svrf->vrf->vrf_id == VRF_DEFAULT) ? "" : " ", cmd); /* * Static routes for vrfs not fully inited */ for (ALL_LIST_ELEMENTS_RO(static_list, node, shr)) { if (shr->afi != afi || shr->safi != safi) continue; if (strcmp(svrf->vrf->name, shr->vrf_name) != 0) continue; char dest_str[PREFIX_STRLEN]; prefix2str(&shr->dest, dest_str, sizeof(dest_str)); vty_out(vty, "%s ", spacing); if (shr->dest_str) vty_out(vty, "%s ", dest_str); if (shr->src_str) vty_out(vty, "from %s ", shr->src_str); if (shr->gate_str) vty_out(vty, "%s ", shr->gate_str); if (shr->ifname) vty_out(vty, "%s ", shr->ifname); if (shr->flag_str) vty_out(vty, "%s ", shr->flag_str); if (shr->tag_str) vty_out(vty, "tag %s ", shr->tag_str); if (shr->distance_str) vty_out(vty, "%s ", shr->distance_str); if (shr->label_str) vty_out(vty, "label %s ", shr->label_str); if (shr->table_str) vty_out(vty, "table %s", shr->table_str); if (strcmp(shr->vrf_name, shr->nhvrf_name) != 0) vty_out(vty, "nexthop-vrf %s ", shr->nhvrf_name); if (shr->onlink) vty_out(vty, "onlink"); vty_out(vty, "\n"); } for (rn = route_top(stable); rn; rn = srcdest_route_next(rn)) for (si = rn->info; si; si = si->next) { vty_out(vty, "%s %s", spacing, srcdest_rnode2str(rn, buf, sizeof(buf))); switch (si->type) { case STATIC_IPV4_GATEWAY: vty_out(vty, " %s", inet_ntoa(si->addr.ipv4)); break; case STATIC_IPV6_GATEWAY: vty_out(vty, " %s", inet_ntop(AF_INET6, &si->addr.ipv6, buf, sizeof(buf))); break; case STATIC_IFNAME: vty_out(vty, " %s", si->ifname); break; case STATIC_BLACKHOLE: switch (si->bh_type) { case STATIC_BLACKHOLE_DROP: vty_out(vty, " blackhole"); break; case STATIC_BLACKHOLE_NULL: vty_out(vty, " Null0"); break; case STATIC_BLACKHOLE_REJECT: vty_out(vty, " reject"); break; } break; case STATIC_IPV4_GATEWAY_IFNAME: vty_out(vty, " %s %s", inet_ntop(AF_INET, &si->addr.ipv4, buf, sizeof(buf)), si->ifname); break; case STATIC_IPV6_GATEWAY_IFNAME: vty_out(vty, " %s %s", inet_ntop(AF_INET6, &si->addr.ipv6, buf, sizeof(buf)), si->ifname); break; } if (si->tag) vty_out(vty, " tag %" ROUTE_TAG_PRI, si->tag); if (si->distance != ZEBRA_STATIC_DISTANCE_DEFAULT) vty_out(vty, " %d", si->distance); /* Label information */ if (si->snh_label.num_labels) vty_out(vty, " label %s", mpls_label2str(si->snh_label.num_labels, si->snh_label.label, buf, sizeof(buf), 0)); if (si->nh_vrf_id != si->vrf_id) vty_out(vty, " nexthop-vrf %s", si->nh_vrfname); /* * table ID from VRF overrides configured */ if (si->table_id && svrf->vrf->data.l.table_id == RT_TABLE_MAIN) vty_out(vty, " table %u", si->table_id); if (si->onlink) vty_out(vty, " onlink"); vty_out(vty, "\n"); write = 1; } return write; } /* Static unicast routes for multicast RPF lookup. */ DEFPY (ip_mroute_dist, ip_mroute_dist_cmd, "[no] ip mroute A.B.C.D/M$prefix [(1-255)$distance]", NO_STR IP_STR "Configure static unicast route into MRIB for multicast RPF lookup\n" "IP destination prefix (e.g. 10.0.0.0/8)\n" "Nexthop address\n" "Nexthop interface name\n" "Distance\n") { return static_route(vty, AFI_IP, SAFI_MULTICAST, no, prefix_str, NULL, NULL, gate_str, ifname, NULL, NULL, distance_str, NULL, NULL, NULL); } /* Static route configuration. */ DEFPY(ip_route_blackhole, ip_route_blackhole_cmd, "[no] ip route\ \ $flag \ [{ \ tag (1-4294967295) \ |(1-255)$distance \ |vrf NAME \ |label WORD \ |table (1-4294967295) \ }]", NO_STR IP_STR "Establish static routes\n" "IP destination prefix (e.g. 10.0.0.0/8)\n" "IP destination prefix\n" "IP destination prefix mask\n" "Emit an ICMP unreachable when matched\n" "Silently discard pkts when matched\n" "Set tag for this route\n" "Tag value\n" "Distance value for this route\n" VRF_CMD_HELP_STR MPLS_LABEL_HELPSTR "Table to configure\n" "The table number to configure\n") { if (table_str && vrf && !vrf_is_backend_netns()) { vty_out(vty, "%% table param only available when running on netns-based vrfs\n"); return CMD_WARNING_CONFIG_FAILED; } return static_route(vty, AFI_IP, SAFI_UNICAST, no, prefix, mask_str, NULL, NULL, NULL, flag, tag_str, distance_str, vrf, label, table_str); } DEFPY(ip_route_blackhole_vrf, ip_route_blackhole_vrf_cmd, "[no] ip route\ \ $flag \ [{ \ tag (1-4294967295) \ |(1-255)$distance \ |label WORD \ |table (1-4294967295) \ }]", NO_STR IP_STR "Establish static routes\n" "IP destination prefix (e.g. 10.0.0.0/8)\n" "IP destination prefix\n" "IP destination prefix mask\n" "Emit an ICMP unreachable when matched\n" "Silently discard pkts when matched\n" "Set tag for this route\n" "Tag value\n" "Distance value for this route\n" MPLS_LABEL_HELPSTR "Table to configure\n" "The table number to configure\n") { VTY_DECLVAR_CONTEXT(vrf, vrf); struct static_vrf *svrf = vrf->info; if (table_str && !vrf_is_backend_netns()) { vty_out(vty, "%% table param only available when running on netns-based vrfs\n"); return CMD_WARNING_CONFIG_FAILED; } /* * Coverity is complaining that prefix could * be dereferenced, but we know that prefix will * valid. Add an assert to make it happy */ assert(prefix); return static_route_leak(vty, svrf, svrf, AFI_IP, SAFI_UNICAST, no, prefix, mask_str, NULL, NULL, NULL, flag, tag_str, distance_str, label, table_str, false); } DEFPY(ip_route_address_interface, ip_route_address_interface_cmd, "[no] ip route\ \ A.B.C.D$gate \ $ifname \ [{ \ tag (1-4294967295) \ |(1-255)$distance \ |vrf NAME \ |label WORD \ |table (1-4294967295) \ |nexthop-vrf NAME \ |onlink$onlink \ }]", NO_STR IP_STR "Establish static routes\n" "IP destination prefix (e.g. 10.0.0.0/8)\n" "IP destination prefix\n" "IP destination prefix mask\n" "IP gateway address\n" "IP gateway interface name\n" "Null interface\n" "Set tag for this route\n" "Tag value\n" "Distance value for this route\n" VRF_CMD_HELP_STR MPLS_LABEL_HELPSTR "Table to configure\n" "The table number to configure\n" VRF_CMD_HELP_STR "Treat the nexthop as directly attached to the interface") { struct static_vrf *svrf; struct static_vrf *nh_svrf; const char *flag = NULL; if (ifname && !strncasecmp(ifname, "Null0", 5)) { flag = "Null0"; ifname = NULL; } svrf = static_vty_get_unknown_vrf(vty, vrf); if (!svrf) { vty_out(vty, "%% vrf %s is not defined\n", vrf); return CMD_WARNING_CONFIG_FAILED; } if (table_str && vrf && !vrf_is_backend_netns()) { vty_out(vty, "%% table param only available when running on netns-based vrfs\n"); return CMD_WARNING_CONFIG_FAILED; } if (nexthop_vrf) nh_svrf = static_vty_get_unknown_vrf(vty, nexthop_vrf); else nh_svrf = svrf; if (!nh_svrf) { vty_out(vty, "%% nexthop vrf %s is not defined\n", nexthop_vrf); return CMD_WARNING_CONFIG_FAILED; } return static_route_leak(vty, svrf, nh_svrf, AFI_IP, SAFI_UNICAST, no, prefix, mask_str, NULL, gate_str, ifname, flag, tag_str, distance_str, label, table_str, !!onlink); } DEFPY(ip_route_address_interface_vrf, ip_route_address_interface_vrf_cmd, "[no] ip route\ \ A.B.C.D$gate \ $ifname \ [{ \ tag (1-4294967295) \ |(1-255)$distance \ |label WORD \ |table (1-4294967295) \ |nexthop-vrf NAME \ |onlink$onlink \ }]", NO_STR IP_STR "Establish static routes\n" "IP destination prefix (e.g. 10.0.0.0/8)\n" "IP destination prefix\n" "IP destination prefix mask\n" "IP gateway address\n" "IP gateway interface name\n" "Null interface\n" "Set tag for this route\n" "Tag value\n" "Distance value for this route\n" MPLS_LABEL_HELPSTR "Table to configure\n" "The table number to configure\n" VRF_CMD_HELP_STR "Treat the nexthop as directly attached to the interface") { VTY_DECLVAR_CONTEXT(vrf, vrf); const char *flag = NULL; struct static_vrf *svrf = vrf->info; struct static_vrf *nh_svrf; if (table_str && !vrf_is_backend_netns()) { vty_out(vty, "%% table param only available when running on netns-based vrfs\n"); return CMD_WARNING_CONFIG_FAILED; } if (ifname && !strncasecmp(ifname, "Null0", 5)) { flag = "Null0"; ifname = NULL; } if (nexthop_vrf) nh_svrf = static_vty_get_unknown_vrf(vty, nexthop_vrf); else nh_svrf = svrf; if (!nh_svrf) { vty_out(vty, "%% nexthop vrf %s is not defined\n", nexthop_vrf); return CMD_WARNING_CONFIG_FAILED; } return static_route_leak(vty, svrf, nh_svrf, AFI_IP, SAFI_UNICAST, no, prefix, mask_str, NULL, gate_str, ifname, flag, tag_str, distance_str, label, table_str, !!onlink); } DEFPY(ip_route, ip_route_cmd, "[no] ip route\ \ $ifname> \ [{ \ tag (1-4294967295) \ |(1-255)$distance \ |vrf NAME \ |label WORD \ |table (1-4294967295) \ |nexthop-vrf NAME \ }]", NO_STR IP_STR "Establish static routes\n" "IP destination prefix (e.g. 10.0.0.0/8)\n" "IP destination prefix\n" "IP destination prefix mask\n" "IP gateway address\n" "IP gateway interface name\n" "Null interface\n" "Set tag for this route\n" "Tag value\n" "Distance value for this route\n" VRF_CMD_HELP_STR MPLS_LABEL_HELPSTR "Table to configure\n" "The table number to configure\n" VRF_CMD_HELP_STR) { struct static_vrf *svrf; struct static_vrf *nh_svrf; const char *flag = NULL; if (table_str && vrf && !vrf_is_backend_netns()) { vty_out(vty, "%% table param only available when running on netns-based vrfs\n"); return CMD_WARNING_CONFIG_FAILED; } if (ifname && !strncasecmp(ifname, "Null0", 5)) { flag = "Null0"; ifname = NULL; } svrf = static_vty_get_unknown_vrf(vty, vrf); if (!svrf) { vty_out(vty, "%% vrf %s is not defined\n", vrf); return CMD_WARNING_CONFIG_FAILED; } if (nexthop_vrf) nh_svrf = static_vty_get_unknown_vrf(vty, nexthop_vrf); else nh_svrf = svrf; if (!nh_svrf) { vty_out(vty, "%% nexthop vrf %s is not defined\n", nexthop_vrf); return CMD_WARNING_CONFIG_FAILED; } return static_route_leak( vty, svrf, nh_svrf, AFI_IP, SAFI_UNICAST, no, prefix, mask_str, NULL, gate_str, ifname, flag, tag_str, distance_str, label, table_str, false); } DEFPY(ip_route_vrf, ip_route_vrf_cmd, "[no] ip route\ \ $ifname> \ [{ \ tag (1-4294967295) \ |(1-255)$distance \ |label WORD \ |table (1-4294967295) \ |nexthop-vrf NAME \ }]", NO_STR IP_STR "Establish static routes\n" "IP destination prefix (e.g. 10.0.0.0/8)\n" "IP destination prefix\n" "IP destination prefix mask\n" "IP gateway address\n" "IP gateway interface name\n" "Null interface\n" "Set tag for this route\n" "Tag value\n" "Distance value for this route\n" MPLS_LABEL_HELPSTR "Table to configure\n" "The table number to configure\n" VRF_CMD_HELP_STR) { VTY_DECLVAR_CONTEXT(vrf, vrf); struct static_vrf *svrf = vrf->info; struct static_vrf *nh_svrf; const char *flag = NULL; if (table_str && !vrf_is_backend_netns()) { vty_out(vty, "%% table param only available when running on netns-based vrfs\n"); return CMD_WARNING_CONFIG_FAILED; } if (ifname && !strncasecmp(ifname, "Null0", 5)) { flag = "Null0"; ifname = NULL; } if (nexthop_vrf) nh_svrf = static_vty_get_unknown_vrf(vty, nexthop_vrf); else nh_svrf = svrf; if (!nh_svrf) { vty_out(vty, "%% nexthop vrf %s is not defined\n", nexthop_vrf); return CMD_WARNING_CONFIG_FAILED; } return static_route_leak( vty, svrf, nh_svrf, AFI_IP, SAFI_UNICAST, no, prefix, mask_str, NULL, gate_str, ifname, flag, tag_str, distance_str, label, table_str, false); } DEFPY(ipv6_route_blackhole, ipv6_route_blackhole_cmd, "[no] ipv6 route X:X::X:X/M$prefix [from X:X::X:X/M] \ $flag \ [{ \ tag (1-4294967295) \ |(1-255)$distance \ |vrf NAME \ |label WORD \ |table (1-4294967295) \ }]", NO_STR IPV6_STR "Establish static routes\n" "IPv6 destination prefix (e.g. 3ffe:506::/32)\n" "IPv6 source-dest route\n" "IPv6 source prefix\n" "Emit an ICMP unreachable when matched\n" "Silently discard pkts when matched\n" "Set tag for this route\n" "Tag value\n" "Distance value for this prefix\n" VRF_CMD_HELP_STR MPLS_LABEL_HELPSTR "Table to configure\n" "The table number to configure\n") { if (table_str && vrf && !vrf_is_backend_netns()) { vty_out(vty, "%% table param only available when running on netns-based vrfs\n"); return CMD_WARNING_CONFIG_FAILED; } return static_route(vty, AFI_IP6, SAFI_UNICAST, no, prefix_str, NULL, from_str, NULL, NULL, flag, tag_str, distance_str, vrf, label, table_str); } DEFPY(ipv6_route_blackhole_vrf, ipv6_route_blackhole_vrf_cmd, "[no] ipv6 route X:X::X:X/M$prefix [from X:X::X:X/M] \ $flag \ [{ \ tag (1-4294967295) \ |(1-255)$distance \ |label WORD \ |table (1-4294967295) \ }]", NO_STR IPV6_STR "Establish static routes\n" "IPv6 destination prefix (e.g. 3ffe:506::/32)\n" "IPv6 source-dest route\n" "IPv6 source prefix\n" "Emit an ICMP unreachable when matched\n" "Silently discard pkts when matched\n" "Set tag for this route\n" "Tag value\n" "Distance value for this prefix\n" MPLS_LABEL_HELPSTR "Table to configure\n" "The table number to configure\n") { VTY_DECLVAR_CONTEXT(vrf, vrf); struct static_vrf *svrf = vrf->info; if (table_str && !vrf_is_backend_netns()) { vty_out(vty, "%% table param only available when running on netns-based vrfs\n"); return CMD_WARNING_CONFIG_FAILED; } /* * Coverity is complaining that prefix could * be dereferenced, but we know that prefix will * valid. Add an assert to make it happy */ assert(prefix); return static_route_leak( vty, svrf, svrf, AFI_IP6, SAFI_UNICAST, no, prefix_str, NULL, from_str, NULL, NULL, flag, tag_str, distance_str, label, table_str, false); } DEFPY(ipv6_route_address_interface, ipv6_route_address_interface_cmd, "[no] ipv6 route X:X::X:X/M$prefix [from X:X::X:X/M] \ X:X::X:X$gate \ $ifname \ [{ \ tag (1-4294967295) \ |(1-255)$distance \ |vrf NAME \ |label WORD \ |table (1-4294967295) \ |nexthop-vrf NAME \ |onlink$onlink \ }]", NO_STR IPV6_STR "Establish static routes\n" "IPv6 destination prefix (e.g. 3ffe:506::/32)\n" "IPv6 source-dest route\n" "IPv6 source prefix\n" "IPv6 gateway address\n" "IPv6 gateway interface name\n" "Null interface\n" "Set tag for this route\n" "Tag value\n" "Distance value for this prefix\n" VRF_CMD_HELP_STR MPLS_LABEL_HELPSTR "Table to configure\n" "The table number to configure\n" VRF_CMD_HELP_STR "Treat the nexthop as directly attached to the interface") { struct static_vrf *svrf; struct static_vrf *nh_svrf; const char *flag = NULL; if (table_str && vrf && !vrf_is_backend_netns()) { vty_out(vty, "%% table param only available when running on netns-based vrfs\n"); return CMD_WARNING_CONFIG_FAILED; } svrf = static_vty_get_unknown_vrf(vty, vrf); if (!svrf) { vty_out(vty, "%% vrf %s is not defined\n", vrf); return CMD_WARNING_CONFIG_FAILED; } if (nexthop_vrf) nh_svrf = static_vty_get_unknown_vrf(vty, nexthop_vrf); else nh_svrf = svrf; if (!nh_svrf) { vty_out(vty, "%% nexthop vrf %s is not defined\n", nexthop_vrf); return CMD_WARNING_CONFIG_FAILED; } if (ifname && !strncasecmp(ifname, "Null0", 5)) { flag = "Null0"; ifname = NULL; } return static_route_leak( vty, svrf, nh_svrf, AFI_IP6, SAFI_UNICAST, no, prefix_str, NULL, from_str, gate_str, ifname, flag, tag_str, distance_str, label, table_str, !!onlink); } DEFPY(ipv6_route_address_interface_vrf, ipv6_route_address_interface_vrf_cmd, "[no] ipv6 route X:X::X:X/M$prefix [from X:X::X:X/M] \ X:X::X:X$gate \ $ifname \ [{ \ tag (1-4294967295) \ |(1-255)$distance \ |label WORD \ |table (1-4294967295) \ |nexthop-vrf NAME \ |onlink$onlink \ }]", NO_STR IPV6_STR "Establish static routes\n" "IPv6 destination prefix (e.g. 3ffe:506::/32)\n" "IPv6 source-dest route\n" "IPv6 source prefix\n" "IPv6 gateway address\n" "IPv6 gateway interface name\n" "Null interface\n" "Set tag for this route\n" "Tag value\n" "Distance value for this prefix\n" MPLS_LABEL_HELPSTR "Table to configure\n" "The table number to configure\n" VRF_CMD_HELP_STR "Treat the nexthop as directly attached to the interface") { VTY_DECLVAR_CONTEXT(vrf, vrf); struct static_vrf *svrf = vrf->info; struct static_vrf *nh_svrf; const char *flag = NULL; if (table_str && !vrf_is_backend_netns()) { vty_out(vty, "%% table param only available when running on netns-based vrfs\n"); return CMD_WARNING_CONFIG_FAILED; } if (nexthop_vrf) nh_svrf = static_vty_get_unknown_vrf(vty, nexthop_vrf); else nh_svrf = svrf; if (!nh_svrf) { vty_out(vty, "%% nexthop vrf %s is not defined\n", nexthop_vrf); return CMD_WARNING_CONFIG_FAILED; } if (ifname && !strncasecmp(ifname, "Null0", 5)) { flag = "Null0"; ifname = NULL; } return static_route_leak( vty, svrf, nh_svrf, AFI_IP6, SAFI_UNICAST, no, prefix_str, NULL, from_str, gate_str, ifname, flag, tag_str, distance_str, label, table_str, !!onlink); } DEFPY(ipv6_route, ipv6_route_cmd, "[no] ipv6 route X:X::X:X/M$prefix [from X:X::X:X/M] \ $ifname> \ [{ \ tag (1-4294967295) \ |(1-255)$distance \ |vrf NAME \ |label WORD \ |table (1-4294967295) \ |nexthop-vrf NAME \ }]", NO_STR IPV6_STR "Establish static routes\n" "IPv6 destination prefix (e.g. 3ffe:506::/32)\n" "IPv6 source-dest route\n" "IPv6 source prefix\n" "IPv6 gateway address\n" "IPv6 gateway interface name\n" "Null interface\n" "Set tag for this route\n" "Tag value\n" "Distance value for this prefix\n" VRF_CMD_HELP_STR MPLS_LABEL_HELPSTR "Table to configure\n" "The table number to configure\n" VRF_CMD_HELP_STR) { struct static_vrf *svrf; struct static_vrf *nh_svrf; const char *flag = NULL; if (table_str && vrf && !vrf_is_backend_netns()) { vty_out(vty, "%% table param only available when running on netns-based vrfs\n"); return CMD_WARNING_CONFIG_FAILED; } svrf = static_vty_get_unknown_vrf(vty, vrf); if (!svrf) { vty_out(vty, "%% vrf %s is not defined\n", vrf); return CMD_WARNING_CONFIG_FAILED; } if (nexthop_vrf) nh_svrf = static_vty_get_unknown_vrf(vty, nexthop_vrf); else nh_svrf = svrf; if (!nh_svrf) { vty_out(vty, "%% nexthop vrf %s is not defined\n", nexthop_vrf); return CMD_WARNING_CONFIG_FAILED; } if (ifname && !strncasecmp(ifname, "Null0", 5)) { flag = "Null0"; ifname = NULL; } return static_route_leak( vty, svrf, nh_svrf, AFI_IP6, SAFI_UNICAST, no, prefix_str, NULL, from_str, gate_str, ifname, flag, tag_str, distance_str, label, table_str, false); } DEFPY(ipv6_route_vrf, ipv6_route_vrf_cmd, "[no] ipv6 route X:X::X:X/M$prefix [from X:X::X:X/M] \ $ifname> \ [{ \ tag (1-4294967295) \ |(1-255)$distance \ |label WORD \ |table (1-4294967295) \ |nexthop-vrf NAME \ }]", NO_STR IPV6_STR "Establish static routes\n" "IPv6 destination prefix (e.g. 3ffe:506::/32)\n" "IPv6 source-dest route\n" "IPv6 source prefix\n" "IPv6 gateway address\n" "IPv6 gateway interface name\n" "Null interface\n" "Set tag for this route\n" "Tag value\n" "Distance value for this prefix\n" MPLS_LABEL_HELPSTR "Table to configure\n" "The table number to configure\n" VRF_CMD_HELP_STR) { VTY_DECLVAR_CONTEXT(vrf, vrf); struct static_vrf *svrf = vrf->info; struct static_vrf *nh_svrf; const char *flag = NULL; if (table_str && !vrf_is_backend_netns()) { vty_out(vty, "%% table param only available when running on netns-based vrfs\n"); return CMD_WARNING_CONFIG_FAILED; } if (nexthop_vrf) nh_svrf = static_vty_get_unknown_vrf(vty, nexthop_vrf); else nh_svrf = svrf; if (!nh_svrf) { vty_out(vty, "%% nexthop vrf %s is not defined\n", nexthop_vrf); return CMD_WARNING_CONFIG_FAILED; } if (ifname && !strncasecmp(ifname, "Null0", 5)) { flag = "Null0"; ifname = NULL; } return static_route_leak( vty, svrf, nh_svrf, AFI_IP6, SAFI_UNICAST, no, prefix_str, NULL, from_str, gate_str, ifname, flag, tag_str, distance_str, label, table_str, false); } DEFUN_NOSH (show_debugging_staticd, show_debugging_staticd_cmd, "show debugging [static]", SHOW_STR DEBUG_STR "Static Information\n") { vty_out(vty, "Static debugging status\n"); return CMD_SUCCESS; } void static_vty_init(void) { install_element(CONFIG_NODE, &ip_mroute_dist_cmd); install_element(CONFIG_NODE, &ip_route_blackhole_cmd); install_element(VRF_NODE, &ip_route_blackhole_vrf_cmd); install_element(CONFIG_NODE, &ip_route_address_interface_cmd); install_element(VRF_NODE, &ip_route_address_interface_vrf_cmd); install_element(CONFIG_NODE, &ip_route_cmd); install_element(VRF_NODE, &ip_route_vrf_cmd); install_element(CONFIG_NODE, &ipv6_route_blackhole_cmd); install_element(VRF_NODE, &ipv6_route_blackhole_vrf_cmd); install_element(CONFIG_NODE, &ipv6_route_address_interface_cmd); install_element(VRF_NODE, &ipv6_route_address_interface_vrf_cmd); install_element(CONFIG_NODE, &ipv6_route_cmd); install_element(VRF_NODE, &ipv6_route_vrf_cmd); install_element(VIEW_NODE, &show_debugging_staticd_cmd); static_list = list_new(); static_list->cmp = (int (*)(void *, void *))static_list_compare; static_list->del = (void (*)(void *))static_list_delete; } frr-7.2.1/staticd/static_vty.h0000644000000000000000000000211613610377563013211 00000000000000/* * STATICd - vty header * Copyright (C) 2018 Cumulus Networks, Inc. * Donald Sharp * This program is free software; you can redistribute it and/or modify it * under the terms of the 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; see the file COPYING; if not, write to the Free Software * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA */ #ifndef __STATIC_VTY_H__ #define __STATIC_VTY_H__ void static_config_install_delayed_routes(struct static_vrf *svrf); int static_config(struct vty *vty, struct static_vrf *svrf, afi_t afi, safi_t safi, const char *cmd); void static_vty_init(void); #endif frr-7.2.1/staticd/static_zebra.c0000644000000000000000000002727513610377563013502 00000000000000/* * Zebra connect code. * Copyright (C) 2018 Cumulus Networks, Inc. * Donald Sharp * * This program is free software; you can redistribute it and/or modify it * under the terms of the 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; see the file COPYING; if not, write to the Free Software * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA */ #include #include "thread.h" #include "command.h" #include "network.h" #include "prefix.h" #include "routemap.h" #include "table.h" #include "srcdest_table.h" #include "stream.h" #include "memory.h" #include "zclient.h" #include "filter.h" #include "plist.h" #include "log.h" #include "nexthop.h" #include "nexthop_group.h" #include "hash.h" #include "jhash.h" #include "static_vrf.h" #include "static_routes.h" #include "static_zebra.h" #include "static_nht.h" #include "static_vty.h" bool debug; /* Zebra structure to hold current status. */ struct zclient *zclient; static struct hash *static_nht_hash; static struct interface *zebra_interface_if_lookup(struct stream *s) { char ifname_tmp[INTERFACE_NAMSIZ]; /* Read interface name. */ stream_get(ifname_tmp, s, INTERFACE_NAMSIZ); /* And look it up. */ return if_lookup_by_name(ifname_tmp, VRF_DEFAULT); } /* Inteface addition message from zebra. */ static int interface_add(ZAPI_CALLBACK_ARGS) { struct interface *ifp; ifp = zebra_interface_add_read(zclient->ibuf, vrf_id); if (!ifp) return 0; static_ifindex_update(ifp, true); return 0; } static int interface_delete(ZAPI_CALLBACK_ARGS) { struct interface *ifp; struct stream *s; s = zclient->ibuf; /* zebra_interface_state_read () updates interface structure in iflist */ ifp = zebra_interface_state_read(s, vrf_id); if (ifp == NULL) return 0; if_set_index(ifp, IFINDEX_INTERNAL); static_ifindex_update(ifp, false); return 0; } static int interface_address_add(ZAPI_CALLBACK_ARGS) { zebra_interface_address_read(cmd, zclient->ibuf, vrf_id); return 0; } static int interface_address_delete(ZAPI_CALLBACK_ARGS) { struct connected *c; c = zebra_interface_address_read(cmd, zclient->ibuf, vrf_id); if (!c) return 0; connected_free(c); return 0; } static int interface_state_up(ZAPI_CALLBACK_ARGS) { struct interface *ifp; ifp = zebra_interface_if_lookup(zclient->ibuf); if (ifp) { if (if_is_vrf(ifp)) { struct static_vrf *svrf = static_vrf_lookup_by_id(vrf_id); static_fixup_vrf_ids(svrf); static_config_install_delayed_routes(svrf); } /* Install any static reliant on this interface coming up */ static_install_intf_nh(ifp); static_ifindex_update(ifp, true); } return 0; } static int interface_state_down(ZAPI_CALLBACK_ARGS) { struct interface *ifp; ifp = zebra_interface_state_read(zclient->ibuf, vrf_id); if (ifp) static_ifindex_update(ifp, false); return 0; } static int route_notify_owner(ZAPI_CALLBACK_ARGS) { struct prefix p; enum zapi_route_notify_owner note; uint32_t table_id; char buf[PREFIX_STRLEN]; if (!zapi_route_notify_decode(zclient->ibuf, &p, &table_id, ¬e)) return -1; prefix2str(&p, buf, sizeof(buf)); switch (note) { case ZAPI_ROUTE_FAIL_INSTALL: static_nht_mark_state(&p, vrf_id, STATIC_NOT_INSTALLED); zlog_warn("%s: Route %s failed to install for table: %u", __PRETTY_FUNCTION__, buf, table_id); break; case ZAPI_ROUTE_BETTER_ADMIN_WON: static_nht_mark_state(&p, vrf_id, STATIC_NOT_INSTALLED); zlog_warn("%s: Route %s over-ridden by better route for table: %u", __PRETTY_FUNCTION__, buf, table_id); break; case ZAPI_ROUTE_INSTALLED: static_nht_mark_state(&p, vrf_id, STATIC_INSTALLED); break; case ZAPI_ROUTE_REMOVED: static_nht_mark_state(&p, vrf_id, STATIC_NOT_INSTALLED); break; case ZAPI_ROUTE_REMOVE_FAIL: static_nht_mark_state(&p, vrf_id, STATIC_INSTALLED); zlog_warn("%s: Route %s failure to remove for table: %u", __PRETTY_FUNCTION__, buf, table_id); break; } return 0; } static void zebra_connected(struct zclient *zclient) { zclient_send_reg_requests(zclient, VRF_DEFAULT); } struct static_nht_data { struct prefix *nh; vrf_id_t nh_vrf_id; uint32_t refcount; uint8_t nh_num; }; static int static_zebra_nexthop_update(ZAPI_CALLBACK_ARGS) { struct static_nht_data *nhtd, lookup; struct zapi_route nhr; afi_t afi = AFI_IP; if (!zapi_nexthop_update_decode(zclient->ibuf, &nhr)) { zlog_warn("Failure to decode nexthop update message"); return 1; } if (nhr.prefix.family == AF_INET6) afi = AFI_IP6; memset(&lookup, 0, sizeof(lookup)); lookup.nh = &nhr.prefix; lookup.nh_vrf_id = vrf_id; nhtd = hash_lookup(static_nht_hash, &lookup); if (nhtd) { nhtd->nh_num = nhr.nexthop_num; static_nht_reset_start(&nhr.prefix, afi, nhtd->nh_vrf_id); static_nht_update(NULL, &nhr.prefix, nhr.nexthop_num, afi, nhtd->nh_vrf_id); } else zlog_err("No nhtd?"); return 1; } static void static_zebra_capabilities(struct zclient_capabilities *cap) { mpls_enabled = cap->mpls_enabled; } static unsigned int static_nht_hash_key(const void *data) { const struct static_nht_data *nhtd = data; unsigned int key = 0; key = prefix_hash_key(nhtd->nh); return jhash_1word(nhtd->nh_vrf_id, key); } static bool static_nht_hash_cmp(const void *d1, const void *d2) { const struct static_nht_data *nhtd1 = d1; const struct static_nht_data *nhtd2 = d2; if (nhtd1->nh_vrf_id != nhtd2->nh_vrf_id) return false; return prefix_same(nhtd1->nh, nhtd2->nh); } static void *static_nht_hash_alloc(void *data) { struct static_nht_data *copy = data; struct static_nht_data *new; new = XMALLOC(MTYPE_TMP, sizeof(*new)); new->nh = prefix_new(); prefix_copy(new->nh, copy->nh); new->refcount = 0; new->nh_num = 0; new->nh_vrf_id = copy->nh_vrf_id; return new; } static void static_nht_hash_free(void *data) { struct static_nht_data *nhtd = data; prefix_free(nhtd->nh); XFREE(MTYPE_TMP, nhtd); } void static_zebra_nht_register(struct route_node *rn, struct static_route *si, bool reg) { struct static_nht_data *nhtd, lookup; uint32_t cmd; struct prefix p; afi_t afi = AFI_IP; cmd = (reg) ? ZEBRA_NEXTHOP_REGISTER : ZEBRA_NEXTHOP_UNREGISTER; if (si->nh_registered && reg) return; if (!si->nh_registered && !reg) return; memset(&p, 0, sizeof(p)); switch (si->type) { case STATIC_IFNAME: case STATIC_BLACKHOLE: return; case STATIC_IPV4_GATEWAY: case STATIC_IPV4_GATEWAY_IFNAME: p.family = AF_INET; p.prefixlen = IPV4_MAX_BITLEN; p.u.prefix4 = si->addr.ipv4; afi = AFI_IP; break; case STATIC_IPV6_GATEWAY: case STATIC_IPV6_GATEWAY_IFNAME: p.family = AF_INET6; p.prefixlen = IPV6_MAX_BITLEN; p.u.prefix6 = si->addr.ipv6; afi = AFI_IP6; break; } memset(&lookup, 0, sizeof(lookup)); lookup.nh = &p; lookup.nh_vrf_id = si->nh_vrf_id; si->nh_registered = reg; if (reg) { nhtd = hash_get(static_nht_hash, &lookup, static_nht_hash_alloc); nhtd->refcount++; if (debug) zlog_debug("Registered nexthop(%pFX) for %pRN %d", &p, rn, nhtd->nh_num); if (nhtd->refcount > 1 && nhtd->nh_num) { static_nht_update(&rn->p, nhtd->nh, nhtd->nh_num, afi, si->nh_vrf_id); return; } } else { nhtd = hash_lookup(static_nht_hash, &lookup); if (!nhtd) return; nhtd->refcount--; if (nhtd->refcount >= 1) return; hash_release(static_nht_hash, nhtd); static_nht_hash_free(nhtd); } if (zclient_send_rnh(zclient, cmd, &p, false, si->nh_vrf_id) < 0) zlog_warn("%s: Failure to send nexthop to zebra", __PRETTY_FUNCTION__); } extern void static_zebra_route_add(struct route_node *rn, struct static_route *si_changed, vrf_id_t vrf_id, safi_t safi, bool install) { struct static_route *si = rn->info; const struct prefix *p, *src_pp; struct zapi_nexthop *api_nh; struct zapi_route api; uint32_t nh_num = 0; p = src_pp = NULL; srcdest_rnode_prefixes(rn, &p, &src_pp); memset(&api, 0, sizeof(api)); api.vrf_id = vrf_id; api.type = ZEBRA_ROUTE_STATIC; api.safi = safi; memcpy(&api.prefix, p, sizeof(api.prefix)); if (src_pp) { SET_FLAG(api.message, ZAPI_MESSAGE_SRCPFX); memcpy(&api.src_prefix, src_pp, sizeof(api.src_prefix)); } SET_FLAG(api.flags, ZEBRA_FLAG_RR_USE_DISTANCE); SET_FLAG(api.message, ZAPI_MESSAGE_NEXTHOP); if (si_changed->distance) { SET_FLAG(api.message, ZAPI_MESSAGE_DISTANCE); api.distance = si_changed->distance; } if (si_changed->tag) { SET_FLAG(api.message, ZAPI_MESSAGE_TAG); api.tag = si_changed->tag; } if (si_changed->table_id != 0) { SET_FLAG(api.message, ZAPI_MESSAGE_TABLEID); api.tableid = si_changed->table_id; } for (/*loaded above*/; si; si = si->next) { api_nh = &api.nexthops[nh_num]; if (si->nh_vrf_id == VRF_UNKNOWN) continue; if (si->distance != si_changed->distance) continue; if (si->table_id != si_changed->table_id) continue; api_nh->vrf_id = si->nh_vrf_id; api_nh->onlink = si->onlink; si->state = STATIC_SENT_TO_ZEBRA; switch (si->type) { case STATIC_IFNAME: if (si->ifindex == IFINDEX_INTERNAL) continue; api_nh->ifindex = si->ifindex; api_nh->type = NEXTHOP_TYPE_IFINDEX; break; case STATIC_IPV4_GATEWAY: if (!si->nh_valid) continue; api_nh->type = NEXTHOP_TYPE_IPV4; api_nh->gate = si->addr; break; case STATIC_IPV4_GATEWAY_IFNAME: if (si->ifindex == IFINDEX_INTERNAL) continue; api_nh->ifindex = si->ifindex; api_nh->type = NEXTHOP_TYPE_IPV4_IFINDEX; api_nh->gate = si->addr; break; case STATIC_IPV6_GATEWAY: if (!si->nh_valid) continue; api_nh->type = NEXTHOP_TYPE_IPV6; api_nh->gate = si->addr; break; case STATIC_IPV6_GATEWAY_IFNAME: if (si->ifindex == IFINDEX_INTERNAL) continue; api_nh->type = NEXTHOP_TYPE_IPV6_IFINDEX; api_nh->ifindex = si->ifindex; api_nh->gate = si->addr; break; case STATIC_BLACKHOLE: api_nh->type = NEXTHOP_TYPE_BLACKHOLE; switch (si->bh_type) { case STATIC_BLACKHOLE_DROP: case STATIC_BLACKHOLE_NULL: api_nh->bh_type = BLACKHOLE_NULL; break; case STATIC_BLACKHOLE_REJECT: api_nh->bh_type = BLACKHOLE_REJECT; } break; } if (si->snh_label.num_labels) { int i; SET_FLAG(api.message, ZAPI_MESSAGE_LABEL); api_nh->label_num = si->snh_label.num_labels; for (i = 0; i < api_nh->label_num; i++) api_nh->labels[i] = si->snh_label.label[i]; } nh_num++; } api.nexthop_num = nh_num; /* * If we have been given an install but nothing is valid * go ahead and delete the route for double plus fun */ if (!nh_num && install) install = false; zclient_route_send(install ? ZEBRA_ROUTE_ADD : ZEBRA_ROUTE_DELETE, zclient, &api); } void static_zebra_init(void) { struct zclient_options opt = { .receive_notify = true }; zclient = zclient_new(master, &opt); zclient_init(zclient, ZEBRA_ROUTE_STATIC, 0, &static_privs); zclient->zebra_capabilities = static_zebra_capabilities; zclient->zebra_connected = zebra_connected; zclient->interface_add = interface_add; zclient->interface_delete = interface_delete; zclient->interface_up = interface_state_up; zclient->interface_down = interface_state_down; zclient->interface_address_add = interface_address_add; zclient->interface_address_delete = interface_address_delete; zclient->route_notify_owner = route_notify_owner; zclient->nexthop_update = static_zebra_nexthop_update; static_nht_hash = hash_create(static_nht_hash_key, static_nht_hash_cmp, "Static Nexthop Tracking hash"); } frr-7.2.1/staticd/static_zebra.h0000644000000000000000000000222713610377563013475 00000000000000/* * Zebra connect library for staticd * Copyright (C) 2018 Cumulus Networks, Inc. * Donald Sharp * * This program is free software; you can redistribute it and/or modify it * under the terms of the 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; see the file COPYING; if not, write to the Free Software */ #ifndef __STATIC_ZEBRA_H__ #define __STATIC_ZEBRA_H__ extern struct thread_master *master; extern void static_zebra_nht_register(struct route_node *rn, struct static_route *si, bool reg); extern void static_zebra_route_add(struct route_node *rn, struct static_route *si_changed, vrf_id_t vrf_id, safi_t safi, bool install); extern void static_zebra_init(void); #endif frr-7.2.1/staticd/staticd.conf.sample0000644000000000000000000000013013610377563014423 00000000000000! Default staticd configuration sample ! log stdout ! ! ip route 4.5.6.7/32 10.10.10.10 frr-7.2.1/staticd/subdir.am0000644000000000000000000000147613610377563012466 00000000000000# # staticd # if STATICD noinst_LIBRARIES += staticd/libstatic.a sbin_PROGRAMS += staticd/staticd dist_examples_DATA += staticd/staticd.conf.sample vtysh_scan += $(top_srcdir)/staticd/static_vty.c man8 += $(MANBUILD)/frr-staticd.8 endif staticd_libstatic_a_SOURCES = \ staticd/static_memory.c \ staticd/static_nht.c \ staticd/static_routes.c \ staticd/static_zebra.c \ staticd/static_vrf.c \ staticd/static_vty.c \ # end noinst_HEADERS += \ staticd/static_memory.h \ staticd/static_nht.h \ staticd/static_zebra.h \ staticd/static_routes.h \ staticd/static_vty.h \ staticd/static_vrf.h \ # end staticd/static_vty_clippy.c: $(CLIPPY_DEPS) staticd/static_vty.$(OBJEXT): staticd/static_vty_clippy.c staticd_staticd_SOURCES = staticd/static_main.c staticd_staticd_LDADD = staticd/libstatic.a lib/libfrr.la $(LIBCAP) frr-7.2.1/tests/0000755000000000000000000000000013610377563010436 500000000000000frr-7.2.1/tests/Makefile0000644000000000000000000000021413610377563012013 00000000000000all: ALWAYS @$(MAKE) -s -C .. check %: ALWAYS @$(MAKE) -s -C .. tests/$@ Makefile: #nothing ALWAYS: .PHONY: ALWAYS makefiles .SUFFIXES: frr-7.2.1/tests/bgpd/0000755000000000000000000000000013610377563011352 500000000000000frr-7.2.1/tests/bgpd/test_aspath.c0000644000000000000000000011574013610377563013765 00000000000000/* * Copyright (C) 2005 Sun Microsystems, Inc. * * This file is part of Quagga. * * Quagga is free software; you can redistribute it and/or modify it * under the terms of the GNU General Public License as published by the * Free Software Foundation; either version 2, or (at your option) any * later version. * * Quagga 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; see the file COPYING; if not, write to the Free Software * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA */ #include #include "vty.h" #include "stream.h" #include "privs.h" #include "queue.h" #include "filter.h" #include "frr_pthread.h" #include "bgpd/bgpd.c" #include "bgpd/bgp_aspath.h" #include "bgpd/bgp_attr.h" #include "bgpd/bgp_packet.h" #define VT100_RESET "\x1b[0m" #define VT100_RED "\x1b[31m" #define VT100_GREEN "\x1b[32m" #define VT100_YELLOW "\x1b[33m" #define OK VT100_GREEN "OK" VT100_RESET #define FAILED VT100_RED "failed" VT100_RESET /* need these to link in libbgp */ struct zebra_privs_t *bgpd_privs = NULL; struct thread_master *master = NULL; static int failed = 0; /* specification for a test - what the results should be */ struct test_spec { const char *shouldbe; /* the string the path should parse to */ const char *shouldbe_delete_confed; /* ditto, but once confeds are deleted */ const unsigned int hops; /* aspath_count_hops result */ const unsigned int confeds; /* aspath_count_confeds */ const int private_as; /* whether the private_as check should pass or fail */ #define NOT_ALL_PRIVATE 0 #define ALL_PRIVATE 1 const as_t does_loop; /* an ASN which should trigger loop-check */ const as_t doesnt_loop; /* one which should not */ const as_t first; /* the first ASN, if there is one */ #define NULL_ASN 0 }; /* test segments to parse and validate, and use for other tests */ static struct test_segment { const char *name; const char *desc; const uint8_t asdata[1024]; int len; struct test_spec sp; } test_segments[] = { { /* 0 */ "seq1", "seq(8466,3,52737,4096)", {0x2, 0x4, 0x21, 0x12, 0x00, 0x03, 0xce, 0x01, 0x10, 0x00}, 10, {"8466 3 52737 4096", "8466 3 52737 4096", 4, 0, NOT_ALL_PRIVATE, 4096, 4, 8466}, }, { /* 1 */ "seq2", "seq(8722) seq(4)", {0x2, 0x1, 0x22, 0x12, 0x2, 0x1, 0x00, 0x04}, 8, { "8722 4", "8722 4", 2, 0, NOT_ALL_PRIVATE, 4, 5, 8722, }, }, { /* 2 */ "seq3", "seq(8466,3,52737,4096,8722,4)", {0x2, 0x6, 0x21, 0x12, 0x00, 0x03, 0xce, 0x01, 0x10, 0x00, 0x22, 0x12, 0x00, 0x04}, 14, {"8466 3 52737 4096 8722 4", "8466 3 52737 4096 8722 4", 6, 0, NOT_ALL_PRIVATE, 3, 5, 8466}, }, { /* 3 */ "seqset", "seq(8482,51457) set(5204)", {0x2, 0x2, 0x21, 0x22, 0xc9, 0x01, 0x1, 0x1, 0x14, 0x54}, 10, {"8482 51457 {5204}", "8482 51457 {5204}", 3, 0, NOT_ALL_PRIVATE, 5204, 51456, 8482}, }, { /* 4 */ "seqset2", "seq(8467, 59649) set(4196,48658) set(17322,30745)", {0x2, 0x2, 0x21, 0x13, 0xe9, 0x01, 0x1, 0x2, 0x10, 0x64, 0xbe, 0x12, 0x1, 0x2, 0x43, 0xaa, 0x78, 0x19}, 18, {"8467 59649 {4196,48658} {17322,30745}", "8467 59649 {4196,48658} {17322,30745}", 4, 0, NOT_ALL_PRIVATE, 48658, 1, 8467}, }, { /* 5 */ "multi", "seq(6435,59408,21665) set(2457,61697,4369), seq(1842,41590,51793)", {0x2, 0x3, 0x19, 0x23, 0xe8, 0x10, 0x54, 0xa1, 0x1, 0x3, 0x09, 0x99, 0xf1, 0x01, 0x11, 0x11, 0x2, 0x3, 0x07, 0x32, 0xa2, 0x76, 0xca, 0x51}, 24, {"6435 59408 21665 {2457,4369,61697} 1842 41590 51793", "6435 59408 21665 {2457,4369,61697} 1842 41590 51793", 7, 0, NOT_ALL_PRIVATE, 51793, 1, 6435}, }, { /* 6 */ "confed", "confseq(123,456,789)", {0x3, 0x3, 0x00, 0x7b, 0x01, 0xc8, 0x03, 0x15}, 8, {"(123 456 789)", "", 0, 3, NOT_ALL_PRIVATE, 789, 1, NULL_ASN}, }, { /* 7 */ "confed2", "confseq(123,456,789) confseq(111,222)", {0x3, 0x3, 0x00, 0x7b, 0x01, 0xc8, 0x03, 0x15, 0x3, 0x2, 0x00, 0x6f, 0x00, 0xde}, 14, {"(123 456 789) (111 222)", "", 0, 5, NOT_ALL_PRIVATE, 111, 1, NULL_ASN}, }, { /* 8 */ "confset", "confset(456,123,789)", {0x4, 0x3, 0x01, 0xc8, 0x00, 0x7b, 0x03, 0x15}, 8, {"[123,456,789]", "", 0, 1, NOT_ALL_PRIVATE, 123, 1, NULL_ASN}, }, { /* 9 */ "confmulti", "confseq(123,456,789) confset(222,111) seq(8722) set(4196,48658)", {0x3, 0x3, 0x00, 0x7b, 0x01, 0xc8, 0x03, 0x15, 0x4, 0x2, 0x00, 0xde, 0x00, 0x6f, 0x2, 0x1, 0x22, 0x12, 0x1, 0x2, 0x10, 0x64, 0xbe, 0x12}, 24, {"(123 456 789) [111,222] 8722 {4196,48658}", "8722 {4196,48658}", 2, 4, NOT_ALL_PRIVATE, 123, 1, NULL_ASN}, }, { /* 10 */ "seq4", "seq(8466,2,52737,4096,8722,4)", {0x2, 0x6, 0x21, 0x12, 0x00, 0x02, 0xce, 0x01, 0x10, 0x00, 0x22, 0x12, 0x00, 0x04}, 14, {"8466 2 52737 4096 8722 4", "8466 2 52737 4096 8722 4", 6, 0, NOT_ALL_PRIVATE, 4096, 1, 8466}, }, { /* 11 */ "tripleseq1", "seq(8466,2,52737) seq(4096,8722,4) seq(8722)", {0x2, 0x3, 0x21, 0x12, 0x00, 0x02, 0xce, 0x01, 0x2, 0x3, 0x10, 0x00, 0x22, 0x12, 0x00, 0x04, 0x2, 0x1, 0x22, 0x12}, 20, {"8466 2 52737 4096 8722 4 8722", "8466 2 52737 4096 8722 4 8722", 7, 0, NOT_ALL_PRIVATE, 4096, 1, 8466}, }, { /* 12 */ "someprivate", "seq(8466,64512,52737,65535)", {0x2, 0x4, 0x21, 0x12, 0xfc, 0x00, 0xce, 0x01, 0xff, 0xff}, 10, {"8466 64512 52737 65535", "8466 64512 52737 65535", 4, 0, NOT_ALL_PRIVATE, 65535, 4, 8466}, }, { /* 13 */ "allprivate", "seq(65534,64512,64513,65535)", {0x2, 0x4, 0xff, 0xfe, 0xfc, 0x00, 0xfc, 0x01, 0xff, 0xff}, 10, {"65534 64512 64513 65535", "65534 64512 64513 65535", 4, 0, ALL_PRIVATE, 65534, 4, 65534}, }, { /* 14 */ "long", "seq(8466,3,52737,4096,34285,)", { 0x2, 0xfa, 0x21, 0x12, 0x00, 0x03, 0xce, 0x01, 0x10, 0x00, 0x85, 0xed, 0x21, 0x12, 0x00, 0x03, 0xce, 0x01, 0x10, 0x00, 0x85, 0xed, 0x21, 0x12, 0x00, 0x03, 0xce, 0x01, 0x10, 0x00, 0x85, 0xed, 0x21, 0x12, 0x00, 0x03, 0xce, 0x01, 0x10, 0x00, 0x85, 0xed, 0x21, 0x12, 0x00, 0x03, 0xce, 0x01, 0x10, 0x00, 0x85, 0xed, 0x21, 0x12, 0x00, 0x03, 0xce, 0x01, 0x10, 0x00, 0x85, 0xed, 0x21, 0x12, 0x00, 0x03, 0xce, 0x01, 0x10, 0x00, 0x85, 0xed, 0x21, 0x12, 0x00, 0x03, 0xce, 0x01, 0x10, 0x00, 0x85, 0xed, 0x21, 0x12, 0x00, 0x03, 0xce, 0x01, 0x10, 0x00, 0x85, 0xed, 0x21, 0x12, 0x00, 0x03, 0xce, 0x01, 0x10, 0x00, 0x85, 0xed, 0x21, 0x12, 0x00, 0x03, 0xce, 0x01, 0x10, 0x00, 0x85, 0xed, 0x21, 0x12, 0x00, 0x03, 0xce, 0x01, 0x10, 0x00, 0x85, 0xed, 0x21, 0x12, 0x00, 0x03, 0xce, 0x01, 0x10, 0x00, 0x85, 0xed, 0x21, 0x12, 0x00, 0x03, 0xce, 0x01, 0x10, 0x00, 0x85, 0xed, 0x21, 0x12, 0x00, 0x03, 0xce, 0x01, 0x10, 0x00, 0x85, 0xed, 0x21, 0x12, 0x00, 0x03, 0xce, 0x01, 0x10, 0x00, 0x85, 0xed, 0x21, 0x12, 0x00, 0x03, 0xce, 0x01, 0x10, 0x00, 0x85, 0xed, 0x21, 0x12, 0x00, 0x03, 0xce, 0x01, 0x10, 0x00, 0x85, 0xed, 0x21, 0x12, 0x00, 0x03, 0xce, 0x01, 0x10, 0x00, 0x85, 0xed, 0x21, 0x12, 0x00, 0x03, 0xce, 0x01, 0x10, 0x00, 0x85, 0xed, 0x21, 0x12, 0x00, 0x03, 0xce, 0x01, 0x10, 0x00, 0x85, 0xed, 0x21, 0x12, 0x00, 0x03, 0xce, 0x01, 0x10, 0x00, 0x85, 0xed, 0x21, 0x12, 0x00, 0x03, 0xce, 0x01, 0x10, 0x00, 0x85, 0xed, 0x21, 0x12, 0x00, 0x03, 0xce, 0x01, 0x10, 0x00, 0x85, 0xed, 0x21, 0x12, 0x00, 0x03, 0xce, 0x01, 0x10, 0x00, 0x85, 0xed, 0x21, 0x12, 0x00, 0x03, 0xce, 0x01, 0x10, 0x00, 0x85, 0xed, 0x21, 0x12, 0x00, 0x03, 0xce, 0x01, 0x10, 0x00, 0x85, 0xed, 0x21, 0x12, 0x00, 0x03, 0xce, 0x01, 0x10, 0x00, 0x85, 0xed, 0x21, 0x12, 0x00, 0x03, 0xce, 0x01, 0x10, 0x00, 0x85, 0xed, 0x21, 0x12, 0x00, 0x03, 0xce, 0x01, 0x10, 0x00, 0x85, 0xed, 0x21, 0x12, 0x00, 0x03, 0xce, 0x01, 0x10, 0x00, 0x85, 0xed, 0x21, 0x12, 0x00, 0x03, 0xce, 0x01, 0x10, 0x00, 0x85, 0xed, 0x21, 0x12, 0x00, 0x03, 0xce, 0x01, 0x10, 0x00, 0x85, 0xed, 0x21, 0x12, 0x00, 0x03, 0xce, 0x01, 0x10, 0x00, 0x85, 0xed, 0x21, 0x12, 0x00, 0x03, 0xce, 0x01, 0x10, 0x00, 0x85, 0xed, 0x21, 0x12, 0x00, 0x03, 0xce, 0x01, 0x10, 0x00, 0x85, 0xed, 0x21, 0x12, 0x00, 0x03, 0xce, 0x01, 0x10, 0x00, 0x85, 0xed, 0x21, 0x12, 0x00, 0x03, 0xce, 0x01, 0x10, 0x00, 0x85, 0xed, 0x21, 0x12, 0x00, 0x03, 0xce, 0x01, 0x10, 0x00, 0x85, 0xed, 0x21, 0x12, 0x00, 0x03, 0xce, 0x01, 0x10, 0x00, 0x85, 0xed, 0x21, 0x12, 0x00, 0x03, 0xce, 0x01, 0x10, 0x00, 0x85, 0xed, 0x21, 0x12, 0x00, 0x03, 0xce, 0x01, 0x10, 0x00, 0x85, 0xed, 0x21, 0x12, 0x00, 0x03, 0xce, 0x01, 0x10, 0x00, 0x85, 0xed, 0x21, 0x12, 0x00, 0x03, 0xce, 0x01, 0x10, 0x00, 0x85, 0xed, 0x21, 0x12, 0x00, 0x03, 0xce, 0x01, 0x10, 0x00, 0x85, 0xed, 0x21, 0x12, 0x00, 0x03, 0xce, 0x01, 0x10, 0x00, 0x85, 0xed, 0x21, 0x12, 0x00, 0x03, 0xce, 0x01, 0x10, 0x00, 0x85, 0xed, 0x21, 0x12, 0x00, 0x03, 0xce, 0x01, 0x10, 0x00, 0x85, 0xed, 0x21, 0x12, 0x00, 0x03, 0xce, 0x01, 0x10, 0x00, 0x85, 0xed, 0x21, 0x12, 0x00, 0x03, 0xce, 0x01, 0x10, 0x00, 0x85, 0xed, }, 502, {"8466 3 52737 4096 34285 8466 3 52737 4096 34285 " "8466 3 52737 4096 34285 8466 3 52737 4096 34285 " "8466 3 52737 4096 34285 8466 3 52737 4096 34285 " "8466 3 52737 4096 34285 8466 3 52737 4096 34285 " "8466 3 52737 4096 34285 8466 3 52737 4096 34285 " "8466 3 52737 4096 34285 8466 3 52737 4096 34285 " "8466 3 52737 4096 34285 8466 3 52737 4096 34285 " "8466 3 52737 4096 34285 8466 3 52737 4096 34285 " "8466 3 52737 4096 34285 8466 3 52737 4096 34285 " "8466 3 52737 4096 34285 8466 3 52737 4096 34285 " "8466 3 52737 4096 34285 8466 3 52737 4096 34285 " "8466 3 52737 4096 34285 8466 3 52737 4096 34285 " "8466 3 52737 4096 34285 8466 3 52737 4096 34285 " "8466 3 52737 4096 34285 8466 3 52737 4096 34285 " "8466 3 52737 4096 34285 8466 3 52737 4096 34285 " "8466 3 52737 4096 34285 8466 3 52737 4096 34285 " "8466 3 52737 4096 34285 8466 3 52737 4096 34285 " "8466 3 52737 4096 34285 8466 3 52737 4096 34285 " "8466 3 52737 4096 34285 8466 3 52737 4096 34285 " "8466 3 52737 4096 34285 8466 3 52737 4096 34285 " "8466 3 52737 4096 34285 8466 3 52737 4096 34285 " "8466 3 52737 4096 34285 8466 3 52737 4096 34285 " "8466 3 52737 4096 34285 8466 3 52737 4096 34285 " "8466 3 52737 4096 34285 8466 3 52737 4096 34285 " "8466 3 52737 4096 34285 8466 3 52737 4096 34285", "8466 3 52737 4096 34285 8466 3 52737 4096 34285 " "8466 3 52737 4096 34285 8466 3 52737 4096 34285 " "8466 3 52737 4096 34285 8466 3 52737 4096 34285 " "8466 3 52737 4096 34285 8466 3 52737 4096 34285 " "8466 3 52737 4096 34285 8466 3 52737 4096 34285 " "8466 3 52737 4096 34285 8466 3 52737 4096 34285 " "8466 3 52737 4096 34285 8466 3 52737 4096 34285 " "8466 3 52737 4096 34285 8466 3 52737 4096 34285 " "8466 3 52737 4096 34285 8466 3 52737 4096 34285 " "8466 3 52737 4096 34285 8466 3 52737 4096 34285 " "8466 3 52737 4096 34285 8466 3 52737 4096 34285 " "8466 3 52737 4096 34285 8466 3 52737 4096 34285 " "8466 3 52737 4096 34285 8466 3 52737 4096 34285 " "8466 3 52737 4096 34285 8466 3 52737 4096 34285 " "8466 3 52737 4096 34285 8466 3 52737 4096 34285 " "8466 3 52737 4096 34285 8466 3 52737 4096 34285 " "8466 3 52737 4096 34285 8466 3 52737 4096 34285 " "8466 3 52737 4096 34285 8466 3 52737 4096 34285 " "8466 3 52737 4096 34285 8466 3 52737 4096 34285 " "8466 3 52737 4096 34285 8466 3 52737 4096 34285 " "8466 3 52737 4096 34285 8466 3 52737 4096 34285 " "8466 3 52737 4096 34285 8466 3 52737 4096 34285 " "8466 3 52737 4096 34285 8466 3 52737 4096 34285 " "8466 3 52737 4096 34285 8466 3 52737 4096 34285 " "8466 3 52737 4096 34285 8466 3 52737 4096 34285", 250, 0, NOT_ALL_PRIVATE, 4096, 4, 8466}, }, { /* 15 */ "seq1extra", "seq(8466,3,52737,4096,3456)", {0x2, 0x5, 0x21, 0x12, 0x00, 0x03, 0xce, 0x01, 0x10, 0x00, 0x0d, 0x80}, 12, {"8466 3 52737 4096 3456", "8466 3 52737 4096 3456", 5, 0, NOT_ALL_PRIVATE, 4096, 4, 8466}, }, { /* 16 */ "empty", "", {}, 0, {"", "", 0, 0, 0, 0, 0, 0}, }, { /* 17 */ "redundantset", "seq(8466,3,52737,4096,3456) set(7099,8153,8153,8153)", {0x2, 0x5, 0x21, 0x12, 0x00, 0x03, 0xce, 0x01, 0x10, 0x00, 0x0d, 0x80, 0x1, 0x4, 0x1b, 0xbb, 0x1f, 0xd9, 0x1f, 0xd9, 0x1f, 0xd9}, 22, {/* We shouldn't ever /generate/ such paths. However, we should * cope with them fine. */ "8466 3 52737 4096 3456 {7099,8153}", "8466 3 52737 4096 3456 {7099,8153}", 6, 0, NOT_ALL_PRIVATE, 4096, 4, 8466}, }, { /* 18 */ "reconcile_lead_asp", "seq(6435,59408,21665) set(23456,23456,23456), seq(23456,23456,23456)", {0x2, 0x3, 0x19, 0x23, 0xe8, 0x10, 0x54, 0xa1, 0x1, 0x3, 0x5b, 0xa0, 0x5b, 0xa0, 0x5b, 0xa0, 0x2, 0x3, 0x5b, 0xa0, 0x5b, 0xa0, 0x5b, 0xa0}, 24, {"6435 59408 21665 {23456} 23456 23456 23456", "6435 59408 21665 {23456} 23456 23456 23456", 7, 0, NOT_ALL_PRIVATE, 23456, 1, 6435}, }, { /* 19 */ "reconcile_new_asp", "set(2457,61697,4369), seq(1842,41591,51793)", {0x1, 0x3, 0x09, 0x99, 0xf1, 0x01, 0x11, 0x11, 0x2, 0x3, 0x07, 0x32, 0xa2, 0x77, 0xca, 0x51}, 16, {"{2457,4369,61697} 1842 41591 51793", "{2457,4369,61697} 1842 41591 51793", 4, 0, NOT_ALL_PRIVATE, 51793, 1, 2457}, }, { /* 20 */ "reconcile_confed", "confseq(123,456,789) confset(456,124,788) seq(6435,59408,21665)" " set(23456,23456,23456), seq(23456,23456,23456)", {0x3, 0x3, 0x00, 0x7b, 0x01, 0xc8, 0x03, 0x15, 0x4, 0x3, 0x01, 0xc8, 0x00, 0x7c, 0x03, 0x14, 0x2, 0x3, 0x19, 0x23, 0xe8, 0x10, 0x54, 0xa1, 0x1, 0x3, 0x5b, 0xa0, 0x5b, 0xa0, 0x5b, 0xa0, 0x2, 0x3, 0x5b, 0xa0, 0x5b, 0xa0, 0x5b, 0xa0}, 40, {"(123 456 789) [124,456,788] 6435 59408 21665" " {23456} 23456 23456 23456", "6435 59408 21665 {23456} 23456 23456 23456", 7, 4, NOT_ALL_PRIVATE, 23456, 1, 6435}, }, { /* 21 */ "reconcile_start_trans", "seq(23456,23456,23456) seq(6435,59408,21665)", { 0x2, 0x3, 0x5b, 0xa0, 0x5b, 0xa0, 0x5b, 0xa0, 0x2, 0x3, 0x19, 0x23, 0xe8, 0x10, 0x54, 0xa1, }, 16, {"23456 23456 23456 6435 59408 21665", "23456 23456 23456 6435 59408 21665", 6, 0, NOT_ALL_PRIVATE, 21665, 1, 23456}, }, { /* 22 */ "reconcile_start_trans4", "seq(1842,41591,51793) seq(6435,59408,21665)", { 0x2, 0x3, 0x07, 0x32, 0xa2, 0x77, 0xca, 0x51, 0x2, 0x3, 0x19, 0x23, 0xe8, 0x10, 0x54, 0xa1, }, 16, {"1842 41591 51793 6435 59408 21665", "1842 41591 51793 6435 59408 21665", 6, 0, NOT_ALL_PRIVATE, 41591, 1, 1842}, }, { /* 23 */ "reconcile_start_trans_error", "seq(23456,23456,23456) seq(6435,59408)", { 0x2, 0x3, 0x5b, 0xa0, 0x5b, 0xa0, 0x5b, 0xa0, 0x2, 0x2, 0x19, 0x23, 0xe8, 0x10, }, 14, {"23456 23456 23456 6435 59408", "23456 23456 23456 6435 59408", 5, 0, NOT_ALL_PRIVATE, 59408, 1, 23456}, }, { /* 24 */ "redundantset2", "seq(8466,3,52737,4096,3456) set(7099,8153,8153,8153,7099)", { 0x2, 0x5, 0x21, 0x12, 0x00, 0x03, 0xce, 0x01, 0x10, 0x00, 0x0d, 0x80, 0x1, 0x5, 0x1b, 0xbb, 0x1f, 0xd9, 0x1f, 0xd9, 0x1f, 0xd9, 0x1b, 0xbb, }, 24, {/* We should weed out duplicate set members. */ "8466 3 52737 4096 3456 {7099,8153}", "8466 3 52737 4096 3456 {7099,8153}", 6, 0, NOT_ALL_PRIVATE, 4096, 4, 8466}, }, { /* 25 */ "zero-size overflow", "#ASNs = 0, data = seq(8466 3 52737 4096 3456)", {0x2, 0x0, 0x21, 0x12, 0x00, 0x03, 0xce, 0x01, 0x10, 0x00, 0x0d, 0x80}, 12, {NULL, NULL, 0, 0, 0, 0, 0, 0}, }, { /* 26 */ "zero-size overflow + valid segment", "seq(#AS=0:8466 3 52737),seq(4096 3456)", {0x2, 0x0, 0x21, 0x12, 0x00, 0x03, 0xce, 0x01, 0x2, 0x2, 0x10, 0x00, 0x0d, 0x80}, 14, {NULL, NULL, 0, 0, 0, 0, 0, 0}, }, { /* 27 */ "invalid segment type", "type=8(4096 3456)", {0x8, 0x2, 0x10, 0x00, 0x0d, 0x80}, 14, {NULL, NULL, 0, 0, 0, 0, 0, 0}, }, {NULL, NULL, {0}, 0, {NULL, 0, 0}}}; #define COMMON_ATTRS \ BGP_ATTR_FLAG_TRANS, BGP_ATTR_ORIGIN, 1, BGP_ORIGIN_EGP, \ BGP_ATTR_FLAG_TRANS, BGP_ATTR_NEXT_HOP, 4, 192, 0, 2, 0 #define COMMON_ATTR_SIZE 11 /* */ static struct aspath_tests { const char *desc; const struct test_segment *segment; const char *shouldbe; /* String it should evaluate to */ const enum as4 { AS4_DATA, AS2_DATA } as4; /* whether data should be as4 or not (ie as2) */ const int result; /* expected result for bgp_attr_parse */ const int cap; /* capabilities to set for peer */ const char attrheader[1024]; size_t len; const struct test_segment *old_segment; } aspath_tests[] = { /* 0 */ { "basic test", &test_segments[0], "8466 3 52737 4096", AS2_DATA, 0, 0, { COMMON_ATTRS, BGP_ATTR_FLAG_TRANS, BGP_ATTR_AS_PATH, 10, }, COMMON_ATTR_SIZE + 3, }, /* 1 */ { "length too short", &test_segments[0], "8466 3 52737 4096", AS2_DATA, -1, 0, { COMMON_ATTRS, BGP_ATTR_FLAG_TRANS, BGP_ATTR_AS_PATH, 8, }, COMMON_ATTR_SIZE + 3, }, /* 2 */ { "length too long", &test_segments[0], "8466 3 52737 4096", AS2_DATA, -1, 0, { COMMON_ATTRS, BGP_ATTR_FLAG_TRANS, BGP_ATTR_AS_PATH, 12, }, COMMON_ATTR_SIZE + 3, }, /* 3 */ { "incorrect flag", &test_segments[0], "8466 3 52737 4096", AS2_DATA, -1, 0, { COMMON_ATTRS, BGP_ATTR_FLAG_TRANS | BGP_ATTR_FLAG_OPTIONAL, BGP_ATTR_AS_PATH, 10, }, COMMON_ATTR_SIZE + 3, }, /* 4 */ { "as4_path, with as2 format data", &test_segments[0], "8466 3 52737 4096", AS2_DATA, -1, 0, { COMMON_ATTRS, BGP_ATTR_FLAG_TRANS | BGP_ATTR_FLAG_OPTIONAL, BGP_ATTR_AS4_PATH, 10, }, COMMON_ATTR_SIZE + 3, }, /* 5 */ { "as4, with incorrect attr length", &test_segments[0], "8466 3 52737 4096", AS4_DATA, -1, PEER_CAP_AS4_RCV, { COMMON_ATTRS, BGP_ATTR_FLAG_TRANS | BGP_ATTR_FLAG_OPTIONAL, BGP_ATTR_AS4_PATH, 10, }, COMMON_ATTR_SIZE + 3, }, /* 6 */ { "basic 4-byte as-path", &test_segments[0], "8466 3 52737 4096", AS4_DATA, 0, PEER_CAP_AS4_RCV | PEER_CAP_AS4_ADV, { COMMON_ATTRS, BGP_ATTR_FLAG_TRANS, BGP_ATTR_AS_PATH, 18, }, COMMON_ATTR_SIZE + 3, }, /* 7 */ { "4b AS_PATH: too short", &test_segments[0], "8466 3 52737 4096", AS4_DATA, -1, PEER_CAP_AS4_RCV | PEER_CAP_AS4_ADV, { COMMON_ATTRS, BGP_ATTR_FLAG_TRANS, BGP_ATTR_AS_PATH, 16, }, COMMON_ATTR_SIZE + 3, }, /* 8 */ { "4b AS_PATH: too long", &test_segments[0], "8466 3 52737 4096", AS4_DATA, -1, PEER_CAP_AS4_RCV | PEER_CAP_AS4_ADV, { COMMON_ATTRS, BGP_ATTR_FLAG_TRANS, BGP_ATTR_AS_PATH, 20, }, COMMON_ATTR_SIZE + 3, }, /* 9 */ { "4b AS_PATH: too long2", &test_segments[0], "8466 3 52737 4096", AS4_DATA, -1, PEER_CAP_AS4_RCV | PEER_CAP_AS4_ADV, { COMMON_ATTRS, BGP_ATTR_FLAG_TRANS, BGP_ATTR_AS_PATH, 22, }, COMMON_ATTR_SIZE + 3, }, /* 10 */ { "4b AS_PATH: bad flags", &test_segments[0], "8466 3 52737 4096", AS4_DATA, -1, PEER_CAP_AS4_RCV | PEER_CAP_AS4_ADV, { COMMON_ATTRS, BGP_ATTR_FLAG_TRANS | BGP_ATTR_FLAG_OPTIONAL, BGP_ATTR_AS_PATH, 18, }, COMMON_ATTR_SIZE + 3, }, /* 11 */ { "4b AS4_PATH w/o AS_PATH", &test_segments[6], NULL, AS4_DATA, -1, PEER_CAP_AS4_ADV, { COMMON_ATTRS, BGP_ATTR_FLAG_TRANS | BGP_ATTR_FLAG_OPTIONAL, BGP_ATTR_AS4_PATH, 14, }, COMMON_ATTR_SIZE + 3, }, /* 12 */ { "4b AS4_PATH: confed", &test_segments[6], "8466 3 52737 4096 (123 456 789)", AS4_DATA, 0, PEER_CAP_AS4_ADV, { COMMON_ATTRS, BGP_ATTR_FLAG_TRANS | BGP_ATTR_FLAG_OPTIONAL, BGP_ATTR_AS4_PATH, 14, }, COMMON_ATTR_SIZE + 3, &test_segments[0], }, {NULL, NULL, NULL, 0, 0, 0, {0}, 0}, }; /* prepending tests */ static struct tests { const struct test_segment *test1; const struct test_segment *test2; struct test_spec sp; } prepend_tests[] = { /* 0 */ { &test_segments[0], &test_segments[1], {"8466 3 52737 4096 8722 4", "8466 3 52737 4096 8722 4", 6, 0, NOT_ALL_PRIVATE, 4096, 1, 8466}, }, /* 1 */ {&test_segments[1], &test_segments[3], {"8722 4 8482 51457 {5204}", "8722 4 8482 51457 {5204}", 5, 0, NOT_ALL_PRIVATE, 5204, 1, 8722}}, /* 2 */ { &test_segments[3], &test_segments[4], {"8482 51457 {5204} 8467 59649 {4196,48658} {17322,30745}", "8482 51457 {5204} 8467 59649 {4196,48658} {17322,30745}", 7, 0, NOT_ALL_PRIVATE, 5204, 1, 8482}, }, /* 3 */ {&test_segments[4], &test_segments[5], {"8467 59649 {4196,48658} {17322,30745} 6435 59408 21665" " {2457,4369,61697} 1842 41590 51793", "8467 59649 {4196,48658} {17322,30745} 6435 59408 21665" " {2457,4369,61697} 1842 41590 51793", 11, 0, NOT_ALL_PRIVATE, 61697, 1, 8467}}, /* 4 */ { &test_segments[5], &test_segments[6], {"6435 59408 21665 {2457,4369,61697} 1842 41590 51793", "6435 59408 21665 {2457,4369,61697} 1842 41590 51793", 7, 0, NOT_ALL_PRIVATE, 1842, 1, 6435}, }, /* 5 */ {&test_segments[6], &test_segments[7], {"(123 456 789) (123 456 789) (111 222)", "", 0, 8, NOT_ALL_PRIVATE, 111, 1, 0}}, {&test_segments[7], &test_segments[8], {"(123 456 789) (111 222) [123,456,789]", "", 0, 6, NOT_ALL_PRIVATE, 111, 1, 0}}, { &test_segments[8], &test_segments[9], {"[123,456,789] (123 456 789) [111,222] 8722 {4196,48658}", "8722 {4196,48658}", 2, 5, NOT_ALL_PRIVATE, 456, 1, NULL_ASN}, }, { &test_segments[9], &test_segments[8], {"(123 456 789) [111,222] 8722 {4196,48658} [123,456,789]", "8722 {4196,48658}", 2, 5, NOT_ALL_PRIVATE, 48658, 1, NULL_ASN}, }, { &test_segments[14], &test_segments[11], {"8466 3 52737 4096 34285 8466 3 52737 4096 34285 " "8466 3 52737 4096 34285 8466 3 52737 4096 34285 " "8466 3 52737 4096 34285 8466 3 52737 4096 34285 " "8466 3 52737 4096 34285 8466 3 52737 4096 34285 " "8466 3 52737 4096 34285 8466 3 52737 4096 34285 " "8466 3 52737 4096 34285 8466 3 52737 4096 34285 " "8466 3 52737 4096 34285 8466 3 52737 4096 34285 " "8466 3 52737 4096 34285 8466 3 52737 4096 34285 " "8466 3 52737 4096 34285 8466 3 52737 4096 34285 " "8466 3 52737 4096 34285 8466 3 52737 4096 34285 " "8466 3 52737 4096 34285 8466 3 52737 4096 34285 " "8466 3 52737 4096 34285 8466 3 52737 4096 34285 " "8466 3 52737 4096 34285 8466 3 52737 4096 34285 " "8466 3 52737 4096 34285 8466 3 52737 4096 34285 " "8466 3 52737 4096 34285 8466 3 52737 4096 34285 " "8466 3 52737 4096 34285 8466 3 52737 4096 34285 " "8466 3 52737 4096 34285 8466 3 52737 4096 34285 " "8466 3 52737 4096 34285 8466 3 52737 4096 34285 " "8466 3 52737 4096 34285 8466 3 52737 4096 34285 " "8466 3 52737 4096 34285 8466 3 52737 4096 34285 " "8466 3 52737 4096 34285 8466 3 52737 4096 34285 " "8466 3 52737 4096 34285 8466 3 52737 4096 34285 " "8466 3 52737 4096 34285 8466 3 52737 4096 34285 " "8466 3 52737 4096 34285 8466 3 52737 4096 34285 " "8466 3 52737 4096 34285 8466 3 52737 4096 34285 " "8466 2 52737 4096 8722 4 8722", "8466 3 52737 4096 34285 8466 3 52737 4096 34285 " "8466 3 52737 4096 34285 8466 3 52737 4096 34285 " "8466 3 52737 4096 34285 8466 3 52737 4096 34285 " "8466 3 52737 4096 34285 8466 3 52737 4096 34285 " "8466 3 52737 4096 34285 8466 3 52737 4096 34285 " "8466 3 52737 4096 34285 8466 3 52737 4096 34285 " "8466 3 52737 4096 34285 8466 3 52737 4096 34285 " "8466 3 52737 4096 34285 8466 3 52737 4096 34285 " "8466 3 52737 4096 34285 8466 3 52737 4096 34285 " "8466 3 52737 4096 34285 8466 3 52737 4096 34285 " "8466 3 52737 4096 34285 8466 3 52737 4096 34285 " "8466 3 52737 4096 34285 8466 3 52737 4096 34285 " "8466 3 52737 4096 34285 8466 3 52737 4096 34285 " "8466 3 52737 4096 34285 8466 3 52737 4096 34285 " "8466 3 52737 4096 34285 8466 3 52737 4096 34285 " "8466 3 52737 4096 34285 8466 3 52737 4096 34285 " "8466 3 52737 4096 34285 8466 3 52737 4096 34285 " "8466 3 52737 4096 34285 8466 3 52737 4096 34285 " "8466 3 52737 4096 34285 8466 3 52737 4096 34285 " "8466 3 52737 4096 34285 8466 3 52737 4096 34285 " "8466 3 52737 4096 34285 8466 3 52737 4096 34285 " "8466 3 52737 4096 34285 8466 3 52737 4096 34285 " "8466 3 52737 4096 34285 8466 3 52737 4096 34285 " "8466 3 52737 4096 34285 8466 3 52737 4096 34285 " "8466 3 52737 4096 34285 8466 3 52737 4096 34285 " "8466 2 52737 4096 8722 4 8722", 257, 0, NOT_ALL_PRIVATE, 4096, 1000, 8466}, }, {NULL, NULL, { NULL, 0, 0, 0, 0, 0, 0, }}, }; struct tests reconcile_tests[] = { { &test_segments[18], &test_segments[19], {"6435 59408 21665 {2457,4369,61697} 1842 41591 51793", "6435 59408 21665 {2457,4369,61697} 1842 41591 51793", 7, 0, NOT_ALL_PRIVATE, 51793, 1, 6435}, }, { &test_segments[19], &test_segments[18], /* AS_PATH (19) has more hops than NEW_AS_PATH, * so just AS_PATH should be used (though, this practice * is bad imho). */ {"{2457,4369,61697} 1842 41591 51793 6435 59408 21665 {23456} 23456 23456 23456", "{2457,4369,61697} 1842 41591 51793 6435 59408 21665 {23456} 23456 23456 23456", 11, 0, NOT_ALL_PRIVATE, 51793, 1, 6435}, }, { &test_segments[20], &test_segments[19], {"(123 456 789) [124,456,788] 6435 59408 21665" " {2457,4369,61697} 1842 41591 51793", "6435 59408 21665 {2457,4369,61697} 1842 41591 51793", 7, 4, NOT_ALL_PRIVATE, 51793, 1, 6435}, }, { &test_segments[21], &test_segments[22], {"1842 41591 51793 6435 59408 21665", "1842 41591 51793 6435 59408 21665", 6, 0, NOT_ALL_PRIVATE, 51793, 1, 1842}, }, { &test_segments[23], &test_segments[22], {"23456 23456 23456 6435 59408 1842 41591 51793 6435 59408 21665", "23456 23456 23456 6435 59408 1842 41591 51793 6435 59408 21665", 11, 0, NOT_ALL_PRIVATE, 51793, 1, 1842}, }, {NULL, NULL, { NULL, 0, 0, 0, 0, 0, 0, }}, }; struct tests aggregate_tests[] = { { &test_segments[0], &test_segments[2], {"8466 3 52737 4096 {4,8722}", "8466 3 52737 4096 {4,8722}", 5, 0, NOT_ALL_PRIVATE, 4, 1, 8466}, }, { &test_segments[2], &test_segments[0], {"8466 3 52737 4096 {4,8722}", "8466 3 52737 4096 {4,8722}", 5, 0, NOT_ALL_PRIVATE, 8722, 1, 8466}, }, { &test_segments[2], &test_segments[10], {"8466 {2,3,4,4096,8722,52737}", "8466 {2,3,4,4096,8722,52737}", 2, 0, NOT_ALL_PRIVATE, 8722, 5, 8466}, }, { &test_segments[10], &test_segments[2], {"8466 {2,3,4,4096,8722,52737}", "8466 {2,3,4,4096,8722,52737}", 2, 0, NOT_ALL_PRIVATE, 2, 20000, 8466}, }, { &test_segments[5], &test_segments[18], {"6435 59408 21665 {1842,2457,4369,23456,41590,51793,61697}", "6435 59408 21665 {1842,2457,4369,23456,41590,51793,61697}", 4, 0, NOT_ALL_PRIVATE, 41590, 1, 6435}, }, {NULL, NULL, {NULL, 0, 0}}, }; struct compare_tests { int test_index1; int test_index2; #define CMP_RES_YES 1 #define CMP_RES_NO 0 char shouldbe_cmp; char shouldbe_confed; } left_compare[] = { {0, 1, CMP_RES_NO, CMP_RES_NO}, {0, 2, CMP_RES_YES, CMP_RES_NO}, {0, 11, CMP_RES_YES, CMP_RES_NO}, {0, 15, CMP_RES_YES, CMP_RES_NO}, {0, 16, CMP_RES_NO, CMP_RES_NO}, {1, 11, CMP_RES_NO, CMP_RES_NO}, {6, 7, CMP_RES_NO, CMP_RES_YES}, {6, 8, CMP_RES_NO, CMP_RES_NO}, {7, 8, CMP_RES_NO, CMP_RES_NO}, {1, 9, CMP_RES_YES, CMP_RES_NO}, {0, 9, CMP_RES_NO, CMP_RES_NO}, {3, 9, CMP_RES_NO, CMP_RES_NO}, {0, 6, CMP_RES_NO, CMP_RES_NO}, {1, 6, CMP_RES_NO, CMP_RES_NO}, {0, 8, CMP_RES_NO, CMP_RES_NO}, {1, 8, CMP_RES_NO, CMP_RES_NO}, {11, 6, CMP_RES_NO, CMP_RES_NO}, {11, 7, CMP_RES_NO, CMP_RES_NO}, {11, 8, CMP_RES_NO, CMP_RES_NO}, {9, 6, CMP_RES_NO, CMP_RES_YES}, {9, 7, CMP_RES_NO, CMP_RES_YES}, {9, 8, CMP_RES_NO, CMP_RES_NO}, }; /* make an aspath from a data stream */ static struct aspath *make_aspath(const uint8_t *data, size_t len, int use32bit) { struct stream *s = NULL; struct aspath *as; if (len) { s = stream_new(len); stream_put(s, data, len); } as = aspath_parse(s, len, use32bit); if (s) stream_free(s); return as; } static void printbytes(const uint8_t *bytes, int len) { int i = 0; while (i < len) { if (i % 2) printf("%02hhx%s", bytes[i], " "); else printf("0x%02hhx", bytes[i]); i++; } printf("\n"); } /* validate the given aspath */ static int validate(struct aspath *as, const struct test_spec *sp) { size_t bytes, bytes4; int fails = 0; const uint8_t *out; static struct stream *s; struct aspath *asinout, *asconfeddel, *asstr, *as4; if (as == NULL && sp->shouldbe == NULL) { printf("Correctly failed to parse\n"); return fails; } out = aspath_snmp_pathseg(as, &bytes); asinout = make_aspath(out, bytes, 0); /* Excercise AS4 parsing a bit, with a dogfood test */ if (!s) s = stream_new(4096); bytes4 = aspath_put(s, as, 1); as4 = make_aspath(STREAM_DATA(s), bytes4, 1); asstr = aspath_str2aspath(sp->shouldbe); asconfeddel = aspath_delete_confed_seq(aspath_dup(asinout)); printf("got: %s\n", aspath_print(as)); /* the parsed path should match the specified 'shouldbe' string. * We should pass the "eat our own dog food" test, be able to output * this path and then input it again. Ie the path resulting from: * * aspath_parse(aspath_put(as)) * * should: * * - also match the specified 'shouldbe' value * - hash to same value as original path * - have same hops and confed counts as original, and as the * the specified counts * * aspath_str2aspath() and shouldbe should match * * We do the same for: * * aspath_parse(aspath_put(as,USE32BIT)) * * Confederation related tests: * - aspath_delete_confed_seq(aspath) should match shouldbe_confed * - aspath_delete_confed_seq should be idempotent. */ if (strcmp(aspath_print(as), sp->shouldbe) /* hash validation */ || (aspath_key_make(as) != aspath_key_make(asinout)) /* by string */ || strcmp(aspath_print(asinout), sp->shouldbe) /* By 4-byte parsing */ || strcmp(aspath_print(as4), sp->shouldbe) /* by various path counts */ || (aspath_count_hops(as) != sp->hops) || (aspath_count_confeds(as) != sp->confeds) || (aspath_count_hops(asinout) != sp->hops) || (aspath_count_confeds(asinout) != sp->confeds)) { failed++; fails++; printf("shouldbe:\n%s\n", sp->shouldbe); printf("as4:\n%s\n", aspath_print(as4)); printf("hash keys: in: %d out->in: %d\n", aspath_key_make(as), aspath_key_make(asinout)); printf("hops: %d, counted %d %d\n", sp->hops, aspath_count_hops(as), aspath_count_hops(asinout)); printf("confeds: %d, counted %d %d\n", sp->confeds, aspath_count_confeds(as), aspath_count_confeds(asinout)); printf("out->in:\n%s\nbytes: ", aspath_print(asinout)); printbytes(out, bytes); } /* basic confed related tests */ if ((aspath_print(asconfeddel) == NULL && sp->shouldbe_delete_confed != NULL) || (aspath_print(asconfeddel) != NULL && sp->shouldbe_delete_confed == NULL) || strcmp(aspath_print(asconfeddel), sp->shouldbe_delete_confed) /* delete_confed_seq should be idempotent */ || (aspath_key_make(asconfeddel) != aspath_key_make(aspath_delete_confed_seq(asconfeddel)))) { failed++; fails++; printf("as-path minus confeds is: %s\n", aspath_print(asconfeddel)); printf("as-path minus confeds should be: %s\n", sp->shouldbe_delete_confed); } /* aspath_str2aspath test */ if ((aspath_print(asstr) == NULL && sp->shouldbe != NULL) || (aspath_print(asstr) != NULL && sp->shouldbe == NULL) || strcmp(aspath_print(asstr), sp->shouldbe)) { failed++; fails++; printf("asstr: %s\n", aspath_print(asstr)); } /* loop, private and first as checks */ if ((sp->does_loop && aspath_loop_check(as, sp->does_loop) == 0) || (sp->doesnt_loop && aspath_loop_check(as, sp->doesnt_loop) != 0) || (aspath_private_as_check(as) != sp->private_as) || (aspath_firstas_check(as, sp->first) && sp->first == 0)) { failed++; fails++; printf("firstas: %d, got %d\n", sp->first, aspath_firstas_check(as, sp->first)); printf("loop does: %d %d, doesnt: %d %d\n", sp->does_loop, aspath_loop_check(as, sp->does_loop), sp->doesnt_loop, aspath_loop_check(as, sp->doesnt_loop)); printf("private check: %d %d\n", sp->private_as, aspath_private_as_check(as)); } aspath_unintern(&asinout); aspath_unintern(&as4); aspath_free(asconfeddel); aspath_free(asstr); stream_reset(s); return fails; } static void empty_get_test(void) { struct aspath *as = aspath_empty_get(); struct test_spec sp = {"", "", 0, 0, 0, 0, 0, 0}; printf("empty_get_test, as: %s\n", aspath_print(as)); if (!validate(as, &sp)) printf("%s\n", OK); else printf("%s!\n", FAILED); printf("\n"); aspath_free(as); } /* basic parsing test */ static void parse_test(struct test_segment *t) { struct aspath *asp; printf("%s: %s\n", t->name, t->desc); asp = make_aspath(t->asdata, t->len, 0); printf("aspath: %s\nvalidating...:\n", aspath_print(asp)); if (!validate(asp, &t->sp)) printf(OK "\n"); else printf(FAILED "\n"); printf("\n"); if (asp) aspath_unintern(&asp); } /* prepend testing */ static void prepend_test(struct tests *t) { struct aspath *asp1, *asp2, *ascratch; printf("prepend %s: %s\n", t->test1->name, t->test1->desc); printf("to %s: %s\n", t->test2->name, t->test2->desc); asp1 = make_aspath(t->test1->asdata, t->test1->len, 0); asp2 = make_aspath(t->test2->asdata, t->test2->len, 0); ascratch = aspath_dup(asp2); aspath_unintern(&asp2); asp2 = aspath_prepend(asp1, ascratch); printf("aspath: %s\n", aspath_print(asp2)); if (!validate(asp2, &t->sp)) printf("%s\n", OK); else printf("%s!\n", FAILED); printf("\n"); aspath_unintern(&asp1); aspath_free(asp2); } /* empty-prepend testing */ static void empty_prepend_test(struct test_segment *t) { struct aspath *asp1, *asp2, *ascratch; printf("empty prepend %s: %s\n", t->name, t->desc); asp1 = make_aspath(t->asdata, t->len, 0); asp2 = aspath_empty(); ascratch = aspath_dup(asp2); aspath_unintern(&asp2); asp2 = aspath_prepend(asp1, ascratch); printf("aspath: %s\n", aspath_print(asp2)); if (!validate(asp2, &t->sp)) printf(OK "\n"); else printf(FAILED "!\n"); printf("\n"); if (asp1) aspath_unintern(&asp1); aspath_free(asp2); } /* as2+as4 reconciliation testing */ static void as4_reconcile_test(struct tests *t) { struct aspath *asp1, *asp2, *ascratch; printf("reconciling %s:\n %s\n", t->test1->name, t->test1->desc); printf("with %s:\n %s\n", t->test2->name, t->test2->desc); asp1 = make_aspath(t->test1->asdata, t->test1->len, 0); asp2 = make_aspath(t->test2->asdata, t->test2->len, 0); ascratch = aspath_reconcile_as4(asp1, asp2); if (!validate(ascratch, &t->sp)) printf(OK "\n"); else printf(FAILED "!\n"); printf("\n"); aspath_unintern(&asp1); aspath_unintern(&asp2); aspath_free(ascratch); } /* aggregation testing */ static void aggregate_test(struct tests *t) { struct aspath *asp1, *asp2, *ascratch; printf("aggregate %s: %s\n", t->test1->name, t->test1->desc); printf("with %s: %s\n", t->test2->name, t->test2->desc); asp1 = make_aspath(t->test1->asdata, t->test1->len, 0); asp2 = make_aspath(t->test2->asdata, t->test2->len, 0); ascratch = aspath_aggregate(asp1, asp2); if (!validate(ascratch, &t->sp)) printf(OK "\n"); else printf(FAILED "!\n"); printf("\n"); aspath_unintern(&asp1); aspath_unintern(&asp2); aspath_free(ascratch); /* aspath_unintern (ascratch);*/ } /* cmp_left tests */ static void cmp_test(void) { unsigned int i; #define CMP_TESTS_MAX (sizeof(left_compare) / sizeof(struct compare_tests)) for (i = 0; i < CMP_TESTS_MAX; i++) { struct test_segment *t1 = &test_segments[left_compare[i].test_index1]; struct test_segment *t2 = &test_segments[left_compare[i].test_index2]; struct aspath *asp1, *asp2; printf("left cmp %s: %s\n", t1->name, t1->desc); printf("and %s: %s\n", t2->name, t2->desc); asp1 = make_aspath(t1->asdata, t1->len, 0); asp2 = make_aspath(t2->asdata, t2->len, 0); if (aspath_cmp_left(asp1, asp2) != left_compare[i].shouldbe_cmp || aspath_cmp_left(asp2, asp1) != left_compare[i].shouldbe_cmp || aspath_cmp_left_confed(asp1, asp2) != left_compare[i].shouldbe_confed || aspath_cmp_left_confed(asp2, asp1) != left_compare[i].shouldbe_confed) { failed++; printf(FAILED "\n"); printf("result should be: cmp: %d, confed: %d\n", left_compare[i].shouldbe_cmp, left_compare[i].shouldbe_confed); printf("got: cmp %d, cmp_confed: %d\n", aspath_cmp_left(asp1, asp2), aspath_cmp_left_confed(asp1, asp2)); printf("path1: %s\npath2: %s\n", aspath_print(asp1), aspath_print(asp2)); } else printf(OK "\n"); printf("\n"); aspath_unintern(&asp1); aspath_unintern(&asp2); } } static int handle_attr_test(struct aspath_tests *t) { struct bgp bgp = {0}; struct peer peer = {0}; struct attr attr = {0}; int ret; int initfail = failed; struct aspath *asp; size_t datalen; asp = make_aspath(t->segment->asdata, t->segment->len, 0); peer.curr = stream_new(BGP_MAX_PACKET_SIZE); peer.obuf = stream_fifo_new(); peer.bgp = &bgp; peer.host = (char *)"none"; peer.fd = -1; peer.cap = t->cap; stream_write(peer.curr, t->attrheader, t->len); datalen = aspath_put(peer.curr, asp, t->as4 == AS4_DATA); if (t->old_segment) { char dummyaspath[] = {BGP_ATTR_FLAG_TRANS, BGP_ATTR_AS_PATH, t->old_segment->len}; stream_write(peer.curr, dummyaspath, sizeof(dummyaspath)); stream_write(peer.curr, t->old_segment->asdata, t->old_segment->len); datalen += sizeof(dummyaspath) + t->old_segment->len; } ret = bgp_attr_parse(&peer, &attr, t->len + datalen, NULL, NULL); if (ret != t->result) { printf("bgp_attr_parse returned %d, expected %d\n", ret, t->result); printf("datalen %zd\n", datalen); failed++; } if (ret != 0) goto out; if (t->shouldbe && attr.aspath == NULL) { printf("aspath is NULL, but should be: %s\n", t->shouldbe); failed++; } if (t->shouldbe && attr.aspath && strcmp(attr.aspath->str, t->shouldbe)) { printf("attr str and 'shouldbe' mismatched!\n" "attr str: %s\n" "shouldbe: %s\n", attr.aspath->str, t->shouldbe); failed++; } if (!t->shouldbe && attr.aspath) { printf("aspath should be NULL, but is: %s\n", attr.aspath->str); failed++; } out: if (attr.aspath) aspath_unintern(&attr.aspath); if (asp) aspath_unintern(&asp); return failed - initfail; } static void attr_test(struct aspath_tests *t) { printf("%s\n", t->desc); printf("%s\n\n", handle_attr_test(t) ? FAILED : OK); } int main(void) { int i = 0; qobj_init(); bgp_master_init(thread_master_create(NULL)); master = bm->master; bgp_option_set(BGP_OPT_NO_LISTEN); bgp_attr_init(); while (test_segments[i].name) { printf("test %u\n", i); parse_test(&test_segments[i]); empty_prepend_test(&test_segments[i++]); } i = 0; while (prepend_tests[i].test1) { printf("prepend test %u\n", i); prepend_test(&prepend_tests[i++]); } i = 0; while (aggregate_tests[i].test1) { printf("aggregate test %u\n", i); aggregate_test(&aggregate_tests[i++]); } i = 0; while (reconcile_tests[i].test1) { printf("reconcile test %u\n", i); as4_reconcile_test(&reconcile_tests[i++]); } i = 0; cmp_test(); i = 0; empty_get_test(); i = 0; bgp_pthreads_init(); bgp_pth_ka->running = true; while (aspath_tests[i].desc) { printf("aspath_attr test %d\n", i); attr_test(&aspath_tests[i++]); } printf("failures: %d\n", failed); printf("aspath count: %ld\n", aspath_count()); return (failed + aspath_count()); } frr-7.2.1/tests/bgpd/test_aspath.py0000644000000000000000000000516313610377563014170 00000000000000import frrtest import re re_okfail = re.compile(r'^(?:\x1b\[3[12]m)?(?POK|failed)'.encode('utf8'), re.MULTILINE) class TestAspath(frrtest.TestMultiOut): program = './test_aspath' def _parsertest(self, line): if not hasattr(self, 'parserno'): self.parserno = -1 self.parserno += 1 self._onesimple("test %d" % self.parserno) self._okfail("%s:" % line, okfail=re_okfail) self._okfail("empty prepend %s:" % line, okfail=re_okfail) def _attrtest(self, line): if not hasattr(self, 'attrno'): self.attrno = -1 self.attrno += 1 self._onesimple("aspath_attr test %d" % self.attrno) self._okfail(line, okfail=re_okfail) TestAspath.parsertest("seq1") TestAspath.parsertest("seq2") TestAspath.parsertest("seq3") TestAspath.parsertest("seqset") TestAspath.parsertest("seqset2") TestAspath.parsertest("multi") TestAspath.parsertest("confed") TestAspath.parsertest("confed2") TestAspath.parsertest("confset") TestAspath.parsertest("confmulti") TestAspath.parsertest("seq4") TestAspath.parsertest("tripleseq1") TestAspath.parsertest("someprivate") TestAspath.parsertest("allprivate") TestAspath.parsertest("long") TestAspath.parsertest("seq1extra") TestAspath.parsertest("empty") TestAspath.parsertest("redundantset") TestAspath.parsertest("reconcile_lead_asp") TestAspath.parsertest("reconcile_new_asp") TestAspath.parsertest("reconcile_confed") TestAspath.parsertest("reconcile_start_trans") TestAspath.parsertest("reconcile_start_trans4") TestAspath.parsertest("reconcile_start_trans_error") TestAspath.parsertest("redundantset2") TestAspath.parsertest("zero-size overflow") TestAspath.parsertest("zero-size overflow + valid segment") TestAspath.parsertest("invalid segment type") for i in range(10): TestAspath.okfail("prepend test %d" % i) for i in range(5): TestAspath.okfail("aggregate test %d" % i) for i in range(5): TestAspath.okfail("reconcile test %d" % i) for _ in range(22): TestAspath.okfail("left cmp ") TestAspath.okfail("empty_get_test") TestAspath.attrtest("basic test") TestAspath.attrtest("length too short") TestAspath.attrtest("length too long") TestAspath.attrtest("incorrect flag") TestAspath.attrtest("as4_path, with as2 format data") TestAspath.attrtest("as4, with incorrect attr length") TestAspath.attrtest("basic 4-byte as-path") TestAspath.attrtest("4b AS_PATH: too short") TestAspath.attrtest("4b AS_PATH: too long") TestAspath.attrtest("4b AS_PATH: too long2") TestAspath.attrtest("4b AS_PATH: bad flags") TestAspath.attrtest("4b AS4_PATH w/o AS_PATH") TestAspath.attrtest("4b AS4_PATH: confed") frr-7.2.1/tests/bgpd/test_bgp_table.c0000644000000000000000000001130413610377563014413 00000000000000/* * BGP Routing table range lookup test * Copyright (C) 2012 OSR. * Copyright (C) 2018 Marcel Röthke (marcel.roethke@haw-hamburg.de), for HAW * Hamburg * * This file is part of FRRouting * * Quagga is free software; you can redistribute it and/or modify it * under the terms of the GNU General Public License as published by the * Free Software Foundation; either version 2, or (at your option) any * later version. * * Quagga 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; see the file COPYING; if not, write to the Free Software * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA */ #include #include "prefix.h" #include "table.h" #include "bgpd/bgp_table.h" #include "linklist.h" /* Satisfy link requirements from including bgpd.h */ struct zebra_privs_t bgpd_privs = {0}; /* * test_node_t * * Information that is kept for each node in the radix tree. */ struct test_node_t { /* * Human readable representation of the string. Allocated using * malloc()/dup(). */ char *prefix_str; }; /* * add_node * * Add the given prefix (passed in as a string) to the given table. */ static void add_node(struct bgp_table *table, const char *prefix_str) { struct prefix_ipv4 p; struct test_node_t *node; struct bgp_node *rn; assert(prefix_str); if (str2prefix_ipv4(prefix_str, &p) <= 0) assert(0); rn = bgp_node_get(table, (struct prefix *)&p); if (rn->info) { assert(0); return; } node = malloc(sizeof(struct test_node_t)); assert(node); node->prefix_str = strdup(prefix_str); assert(node->prefix_str); rn->info = node; } static void print_range_result(struct list *list) { struct listnode *listnode; struct bgp_node *bnode; for (ALL_LIST_ELEMENTS_RO(list, listnode, bnode)) { char buf[PREFIX2STR_BUFFER]; prefix2str(&bnode->p, buf, PREFIX2STR_BUFFER); printf("%s\n", buf); } } static void check_lookup_result(struct list *list, va_list arglist) { char *prefix_str; unsigned int prefix_count = 0; printf("Searching results\n"); while ((prefix_str = va_arg(arglist, char *))) { struct listnode *listnode; struct bgp_node *bnode; struct prefix p; bool found = false; prefix_count++; printf("Searching for %s\n", prefix_str); if (str2prefix(prefix_str, &p) <= 0) assert(0); for (ALL_LIST_ELEMENTS_RO(list, listnode, bnode)) { if (prefix_same(&bnode->p, &p)) found = true; } assert(found); } printf("Checking for unexpected result items\n"); printf("Expecting %d found %d\n", prefix_count, listcount(list)); assert(prefix_count == listcount(list)); } static void do_test(struct bgp_table *table, const char *prefix, uint32_t maxlen, ...) { va_list arglist; struct list *list = list_new(); struct prefix p; list->del = (void (*)(void *))bgp_unlock_node; va_start(arglist, maxlen); printf("\nDoing lookup for %s-%d\n", prefix, maxlen); if (str2prefix(prefix, &p) <= 0) assert(0); bgp_table_range_lookup(table, &p, maxlen, list); print_range_result(list); check_lookup_result(list, arglist); list_delete(&list); va_end(arglist); printf("Checks successfull\n"); } /* * test_range_lookup */ static void test_range_lookup(void) { struct bgp_table *table = bgp_table_init(NULL, AFI_IP, SAFI_UNICAST); printf("Testing bgp_table_range_lookup\n"); printf("Setup bgp_table"); const char *prefixes[] = {"1.16.0.0/16", "1.16.128.0/18", "1.16.192.0/18", "1.16.64.0/19", "1.16.160.0/19", "1.16.32.0/20", "1.16.32.0/21", "16.0.0.0/16"}; int num_prefixes = array_size(prefixes); for (int i = 0; i < num_prefixes; i++) add_node(table, prefixes[i]); do_test(table, "1.16.0.0/17", 20, "1.16.64.0/19", "1.16.32.0/20", NULL); do_test(table, "1.16.128.0/17", 20, "1.16.128.0/18", "1.16.192.0/18", "1.16.160.0/19", NULL); do_test(table, "1.16.128.0/17", 20, "1.16.128.0/18", "1.16.192.0/18", "1.16.160.0/19", NULL); do_test(table, "1.16.0.0/16", 18, "1.16.0.0/16", "1.16.128.0/18", "1.16.192.0/18", NULL); do_test(table, "1.16.0.0/16", 21, "1.16.0.0/16", "1.16.128.0/18", "1.16.192.0/18", "1.16.64.0/19", "1.16.160.0/19", "1.16.32.0/20", "1.16.32.0/21", NULL); do_test(table, "1.17.0.0/16", 20, NULL); do_test(table, "128.0.0.0/8", 16, NULL); do_test(table, "16.0.0.0/8", 16, "16.0.0.0/16", NULL); do_test(table, "0.0.0.0/2", 21, "1.16.0.0/16", "1.16.128.0/18", "1.16.192.0/18", "1.16.64.0/19", "1.16.160.0/19", "1.16.32.0/20", "1.16.32.0/21", "16.0.0.0/16", NULL); } int main(void) { test_range_lookup(); } frr-7.2.1/tests/bgpd/test_capability.c0000644000000000000000000004277113610377563014631 00000000000000/* * Copyright (C) 2007 Sun Microsystems, Inc. * * This file is part of Quagga. * * Quagga is free software; you can redistribute it and/or modify it * under the terms of the GNU General Public License as published by the * Free Software Foundation; either version 2, or (at your option) any * later version. * * Quagga 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; see the file COPYING; if not, write to the Free Software * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA */ #include #include "qobj.h" #include "vty.h" #include "stream.h" #include "privs.h" #include "memory.h" #include "queue.h" #include "filter.h" #include "frr_pthread.h" #include "bgpd/bgpd.c" #include "bgpd/bgp_open.h" #include "bgpd/bgp_debug.h" #include "bgpd/bgp_packet.h" #define VT100_RESET "\x1b[0m" #define VT100_RED "\x1b[31m" #define VT100_GREEN "\x1b[32m" #define VT100_YELLOW "\x1b[33m" #define CAPABILITY 0 #define DYNCAP 1 #define OPT_PARAM 2 /* need these to link in libbgp */ struct zebra_privs_t *bgpd_privs = NULL; struct thread_master *master = NULL; static int failed = 0; static int tty = 0; /* test segments to parse and validate, and use for other tests */ static struct test_segment { const char *name; const char *desc; const uint8_t data[1024]; int len; #define SHOULD_PARSE 0 #define SHOULD_ERR -1 int parses; /* whether it should parse or not */ as_t peek_for; /* what peek_for_as4_capability should say */ /* AFI/SAFI validation */ int validate_afi; iana_afi_t afi; iana_safi_t safi; #define VALID_AFI 1 #define INVALID_AFI 0 int afi_valid; } test_segments[] = { /* 0 */ { "caphdr", "capability header, and no more", {CAPABILITY_CODE_REFRESH, 0x0}, 2, SHOULD_PARSE, }, /* 1 */ { "nodata", "header, no data but length says there is", {0x1, 0xa}, 2, SHOULD_ERR, }, /* 2 */ { "padded", "valid, with padding", {CAPABILITY_CODE_REFRESH, 0x2, 0x0, 0x0}, 4, SHOULD_PARSE, }, /* 3 */ { "minsize", "violates minsize requirement", {CAPABILITY_CODE_ORF, 0x2, 0x0, 0x0}, 4, SHOULD_ERR, }, {NULL, NULL, {0}, 0, 0}, }; static struct test_segment mp_segments[] = { { "MP4", "MP IP/Uni", {0x1, 0x4, 0x0, 0x1, 0x0, 0x1}, 6, SHOULD_PARSE, 0, 1, IANA_AFI_IPV4, IANA_SAFI_UNICAST, VALID_AFI, }, { "MPv6", "MP IPv6/Uni", {0x1, 0x4, 0x0, 0x2, 0x0, 0x1}, 6, SHOULD_PARSE, 0, 1, IANA_AFI_IPV6, IANA_SAFI_UNICAST, VALID_AFI, }, /* 5 */ { "MP2", "MP IP/Multicast", {CAPABILITY_CODE_MP, 0x4, 0x0, 0x1, 0x0, 0x2}, 6, SHOULD_PARSE, 0, 1, IANA_AFI_IPV4, IANA_SAFI_MULTICAST, VALID_AFI, }, /* 6 */ { "MP3", "MP IP6/MPLS-labeled VPN", {CAPABILITY_CODE_MP, 0x4, 0x0, 0x2, 0x0, 0x80}, 6, SHOULD_PARSE, 0, 1, IANA_AFI_IPV6, IANA_SAFI_MPLS_VPN, VALID_AFI, }, /* 7 */ { "MP5", "MP IP6/MPLS-VPN", {CAPABILITY_CODE_MP, 0x4, 0x0, 0x2, 0x0, 0x4}, 6, SHOULD_PARSE, 0, 1, IANA_AFI_IPV6, IANA_SAFI_MPLS_VPN, VALID_AFI, }, /* 8 */ { "MP6", "MP IP4/MPLS-labeled VPN", {CAPABILITY_CODE_MP, 0x4, 0x0, 0x1, 0x0, 0x80}, 6, SHOULD_PARSE, 0, 1, IANA_AFI_IPV4, IANA_SAFI_MPLS_VPN, VALID_AFI, }, /* 10 */ { "MP8", "MP unknown AFI/SAFI", {CAPABILITY_CODE_MP, 0x4, 0x0, 0xa, 0x0, 0x81}, 6, SHOULD_PARSE, 0, 1, 0xa, 0x81, INVALID_AFI, /* parses, but unknown */ }, /* 11 */ { "MP-short", "MP IP4/Unicast, length too short (< minimum)", {CAPABILITY_CODE_MP, 0x2, 0x0, 0x1, 0x0, 0x1}, 6, SHOULD_ERR, }, /* 12 */ { "MP-overflow", "MP IP4/Unicast, length too long", {CAPABILITY_CODE_MP, 0x6, 0x0, 0x1, 0x0, 0x1}, 6, SHOULD_ERR, 0, 1, IANA_AFI_IPV4, IANA_SAFI_UNICAST, VALID_AFI, }, {NULL, NULL, {0}, 0, 0}}; static struct test_segment misc_segments[] = { /* 13 */ { "ORF", "ORF, simple, single entry, single tuple", {/* hdr */ CAPABILITY_CODE_ORF, 0x7, /* mpc */ 0x0, 0x1, 0x0, 0x1, /* num */ 0x1, /* tuples */ 0x40, 0x3}, 9, SHOULD_PARSE, }, /* 14 */ { "ORF-many", "ORF, multi entry/tuple", { /* hdr */ CAPABILITY_CODE_ORF, 0x21, /* mpc */ 0x0, 0x1, 0x0, 0x1, /* num */ 0x3, /* tuples */ 0x40, ORF_MODE_BOTH, 0x80, ORF_MODE_RECEIVE, 0x80, ORF_MODE_SEND, /* mpc */ 0x0, 0x2, 0x0, 0x1, /* num */ 0x3, /* tuples */ 0x40, ORF_MODE_BOTH, 0x80, ORF_MODE_RECEIVE, 0x80, ORF_MODE_SEND, /* mpc */ 0x0, 0x2, 0x0, 0x2, /* num */ 0x3, /* tuples */ 0x40, ORF_MODE_RECEIVE, 0x80, ORF_MODE_SEND, 0x80, ORF_MODE_BOTH, }, 35, SHOULD_PARSE, }, /* 15 */ { "ORFlo", "ORF, multi entry/tuple, hdr length too short", { /* hdr */ CAPABILITY_CODE_ORF, 0x15, /* mpc */ 0x0, 0x1, 0x0, 0x1, /* num */ 0x3, /* tuples */ 0x40, 0x3, 0x80, 0x1, 0x80, 0x2, /* mpc */ 0x0, 0x1, 0x0, 0x1, /* num */ 0x3, /* tuples */ 0x40, 0x3, 0x80, 0x1, 0x80, 0x2, /* mpc */ 0x0, 0x2, 0x0, 0x2, /* num */ 0x3, /* tuples */ 0x40, 0x3, 0x80, 0x1, 0x80, 0x2, }, 35, SHOULD_ERR, /* It should error on invalid Route-Refresh.. */ }, /* 16 */ {"ORFlu", "ORF, multi entry/tuple, length too long", { /* hdr */ 0x3, 0x22, /* mpc */ 0x0, 0x1, 0x0, 0x1, /* num */ 0x3, /* tuples */ 0x40, 0x3, 0x80, 0x1, 0x80, 0x2, /* mpc */ 0x0, 0x2, 0x0, 0x1, /* num */ 0x3, /* tuples */ 0x40, 0x3, 0x80, 0x1, 0x80, 0x2, /* mpc */ 0x0, 0x2, 0x0, 0x2, /* num */ 0x3, /* tuples */ 0x40, 0x3, 0x80, 0x1, 0x80, 0x2, }, 35, SHOULD_ERR}, /* 17 */ { "ORFnu", "ORF, multi entry/tuple, entry number too long", { /* hdr */ 0x3, 0x21, /* mpc */ 0x0, 0x1, 0x0, 0x1, /* num */ 0x3, /* tuples */ 0x40, 0x3, 0x80, 0x1, 0x80, 0x2, /* mpc */ 0x0, 0x2, 0x0, 0x1, /* num */ 0x4, /* tuples */ 0x40, 0x3, 0x80, 0x1, 0x80, 0x2, /* mpc */ 0x0, 0x2, 0x0, 0x2, /* num */ 0x3, /* tuples */ 0x40, 0x3, 0x80, 0x1, 0x80, 0x2, }, 35, SHOULD_PARSE, /* parses, but last few tuples should be gibberish */ }, /* 18 */ { "ORFno", "ORF, multi entry/tuple, entry number too short", { /* hdr */ 0x3, 0x21, /* mpc */ 0x0, 0x1, 0x0, 0x1, /* num */ 0x3, /* tuples */ 0x40, 0x3, 0x80, 0x1, 0x80, 0x2, /* mpc */ 0x0, 0x2, 0x0, 0x1, /* num */ 0x1, /* tuples */ 0x40, 0x3, 0x80, 0x1, 0x80, 0x2, /* mpc */ 0x0, 0x2, 0x0, 0x2, /* num */ 0x3, /* tuples */ 0x40, 0x3, 0x80, 0x1, 0x80, 0x2, }, 35, SHOULD_PARSE, /* Parses, but should get gibberish afi/safis */ }, /* 17 */ { "ORFpad", "ORF, multi entry/tuple, padded to align", { /* hdr */ 0x3, 0x22, /* mpc */ 0x0, 0x1, 0x0, 0x1, /* num */ 0x3, /* tuples */ 0x40, 0x3, 0x80, 0x1, 0x80, 0x2, /* mpc */ 0x0, 0x2, 0x0, 0x1, /* num */ 0x3, /* tuples */ 0x40, 0x3, 0x80, 0x1, 0x80, 0x2, /* mpc */ 0x0, 0x2, 0x0, 0x2, /* num */ 0x3, /* tuples */ 0x40, 0x3, 0x80, 0x1, 0x80, 0x2, 0x00, }, 36, SHOULD_PARSE, }, /* 19 */ { "AS4", "AS4 capability", {0x41, 0x4, 0xab, 0xcd, 0xef, 0x12}, /* AS: 2882400018 */ 6, SHOULD_PARSE, 2882400018, }, { "AS4", "AS4 capability: short", {0x41, 0x4, 0xab, 0xcd, 0xef}, /* AS: 2882400018 */ 5, SHOULD_ERR, }, { "AS4", "AS4 capability: long", {0x41, 0x4, 0xab, 0xcd, 0xef, 0x12, 0x12}, 7, SHOULD_ERR, 2882400018, }, { "GR", "GR capability", { /* hdr */ CAPABILITY_CODE_RESTART, 0xe, /* R-bit, time */ 0xf1, 0x12, /* afi */ 0x0, 0x1, /* safi */ 0x1, /* flags */ 0xf, /* afi */ 0x0, 0x2, /* safi */ 0x1, /* flags */ 0x0, /* afi */ 0x0, 0x2, /* safi */ 0x2, /* flags */ 0x1, }, 16, SHOULD_PARSE, }, { "GR-short", "GR capability, but header length too short", { /* hdr */ 0x40, 0xa, /* R-bit, time */ 0xf1, 0x12, /* afi */ 0x0, 0x1, /* safi */ 0x1, /* flags */ 0xf, /* afi */ 0x0, 0x2, /* safi */ 0x1, /* flags */ 0x0, /* afi */ 0x0, 0x2, /* safi */ 0x2, /* flags */ 0x1, }, 15 /* array is 16 though */, SHOULD_ERR, }, { "GR-long", "GR capability, but header length too long", { /* hdr */ 0x40, 0xf, /* R-bit, time */ 0xf1, 0x12, /* afi */ 0x0, 0x1, /* safi */ 0x1, /* flags */ 0xf, /* afi */ 0x0, 0x2, /* safi */ 0x1, /* flags */ 0x0, /* afi */ 0x0, 0x2, /* safi */ 0x2, /* flags */ 0x01, }, 16, SHOULD_ERR, }, { "GR-trunc", "GR capability, but truncated", { /* hdr */ 0x40, 0xf, /* R-bit, time */ 0xf1, 0x12, /* afi */ 0x0, 0x1, /* safi */ 0x1, /* flags */ 0xf, /* afi */ 0x0, 0x2, /* safi */ 0x1, /* flags */ 0x0, /* afi */ 0x0, 0x2, /* safi */ 0x2, /* flags */ 0x1, }, 15, SHOULD_ERR, }, { "GR-empty", "GR capability, but empty.", { /* hdr */ 0x40, 0x0, }, 2, SHOULD_ERR, }, { "MP-empty", "MP capability, but empty.", { /* hdr */ 0x1, 0x0, }, 2, SHOULD_ERR, }, { "ORF-empty", "ORF capability, but empty.", { /* hdr */ 0x3, 0x0, }, 2, SHOULD_ERR, }, { "AS4-empty", "AS4 capability, but empty.", { /* hdr */ 0x41, 0x0, }, 2, SHOULD_ERR, }, { "dyn-empty", "Dynamic capability, but empty.", { /* hdr */ 0x42, 0x0, }, 2, SHOULD_PARSE, }, { "dyn-old", "Dynamic capability (deprecated version)", {CAPABILITY_CODE_DYNAMIC, 0x0}, 2, SHOULD_PARSE, }, {NULL, NULL, {0}, 0, 0}}; /* DYNAMIC message */ struct test_segment dynamic_cap_msgs[] = { { "DynCap", "Dynamic Capability Message, IP/Multicast", {0x0, 0x1, 0x4, 0x0, 0x1, 0x0, 0x2}, 7, SHOULD_PARSE, /* horrible alignment, just as with ORF */ }, { "DynCapLong", "Dynamic Capability Message, IP/Multicast, truncated", {0x0, 0x1, 0x4, 0x0, 0x1, 0x0, 0x2}, 5, SHOULD_ERR, }, { "DynCapPadded", "Dynamic Capability Message, IP/Multicast, padded", {0x0, 0x1, 0x4, 0x0, 0x1, 0x0, 0x2, 0x0}, 8, SHOULD_ERR, /* No way to tell padding from data.. */ }, { "DynCapMPCpadded", "Dynamic Capability Message, IP/Multicast, cap data padded", {0x0, 0x1, 0x5, 0x0, 0x1, 0x0, 0x2, 0x0}, 8, SHOULD_PARSE, /* You can though add padding to the capability data */ }, { "DynCapMPCoverflow", "Dynamic Capability Message, IP/Multicast, cap data != length", {0x0, 0x1, 0x3, 0x0, 0x1, 0x0, 0x2, 0x0}, 8, SHOULD_ERR, }, {NULL, NULL, {0}, 0, 0}}; /* Entire Optional-Parameters block */ struct test_segment opt_params[] = { { "Cap-singlets", "One capability per Optional-Param", { 0x02, 0x06, 0x01, 0x04, 0x00, 0x01, 0x00, 0x01, /* MP IPv4/Uni */ 0x02, 0x06, 0x01, 0x04, 0x00, 0x02, 0x00, 0x01, /* MP IPv6/Uni */ 0x02, 0x02, 0x80, 0x00, /* RR (old) */ 0x02, 0x02, 0x02, 0x00, /* RR */ }, 24, SHOULD_PARSE, }, { "Cap-series", "Series of capability, one Optional-Param", { 0x02, 0x10, 0x01, 0x04, 0x00, 0x01, 0x00, 0x01, /* MP IPv4/Uni */ 0x01, 0x04, 0x00, 0x02, 0x00, 0x01, /* MP IPv6/Uni */ 0x80, 0x00, /* RR (old) */ 0x02, 0x00, /* RR */ }, 18, SHOULD_PARSE, }, { "AS4more", "AS4 capability after other caps (singlets)", { 0x02, 0x06, 0x01, 0x04, 0x00, 0x01, 0x00, 0x01, /* MP IPv4/Uni */ 0x02, 0x06, 0x01, 0x04, 0x00, 0x02, 0x00, 0x01, /* MP IPv6/Uni */ 0x02, 0x02, 0x80, 0x00, /* RR (old) */ 0x02, 0x02, 0x02, 0x00, /* RR */ 0x02, 0x06, 0x41, 0x04, 0x00, 0x03, 0x00, 0x06 /* AS4: 1996614 */ }, 32, SHOULD_PARSE, 196614, }, { "AS4series", "AS4 capability, in series of capabilities", { 0x02, 0x16, 0x01, 0x04, 0x00, 0x01, 0x00, 0x01, /* MP IPv4/Uni */ 0x01, 0x04, 0x00, 0x02, 0x00, 0x01, /* MP IPv6/Uni */ 0x80, 0x00, /* RR (old) */ 0x02, 0x00, /* RR */ 0x41, 0x04, 0x00, 0x03, 0x00, 0x06 /* AS4: 1996614 */ }, 24, SHOULD_PARSE, 196614, }, { "AS4real", "AS4 capability, in series of capabilities", { 0x02, 0x06, 0x01, 0x04, 0x00, 0x01, 0x00, 0x01, /* MP IPv4/uni */ 0x02, 0x06, 0x01, 0x04, 0x00, 0x02, 0x00, 0x01, /* MP IPv6/uni */ 0x02, 0x02, 0x80, 0x00, /* RR old */ 0x02, 0x02, 0x02, 0x00, /* RR */ 0x02, 0x06, 0x41, 0x04, 0x00, 0x03, 0x00, 0x06, /* AS4 */ }, 32, SHOULD_PARSE, 196614, }, { "AS4real2", "AS4 capability, in series of capabilities", { 0x02, 0x06, 0x01, 0x04, 0x00, 0x01, 0x00, 0x01, 0x02, 0x06, 0x01, 0x04, 0x00, 0x02, 0x00, 0x01, 0x02, 0x02, 0x80, 0x00, 0x02, 0x02, 0x02, 0x00, 0x02, 0x06, 0x41, 0x04, 0x00, 0x00, 0xfc, 0x03, 0x02, 0x09, 0x82, 0x07, 0x00, 0x01, 0x00, 0x01, 0x01, 0x80, 0x03, 0x02, 0x09, 0x03, 0x07, 0x00, 0x01, 0x00, 0x01, 0x01, 0x40, 0x03, 0x02, 0x02, 0x42, 0x00, }, 58, SHOULD_PARSE, 64515, }, {NULL, NULL, {0}, 0, 0}}; /* basic parsing test */ static void parse_test(struct peer *peer, struct test_segment *t, int type) { int ret; int capability = 0; as_t as4 = 0; int oldfailed = failed; int len = t->len; #define RANDOM_FUZZ 35 stream_reset(peer->curr); stream_put(peer->curr, NULL, RANDOM_FUZZ); stream_set_getp(peer->curr, RANDOM_FUZZ); switch (type) { case CAPABILITY: stream_putc(peer->curr, BGP_OPEN_OPT_CAP); stream_putc(peer->curr, t->len); break; case DYNCAP: /* for (i = 0; i < BGP_MARKER_SIZE; i++) stream_putc (peer->, 0xff); stream_putw (s, 0); stream_putc (s, BGP_MSG_CAPABILITY);*/ break; } stream_write(peer->curr, t->data, t->len); printf("%s: %s\n", t->name, t->desc); switch (type) { case CAPABILITY: len += 2; /* to cover the OPT-Param header */ _FALLTHROUGH case OPT_PARAM: printf("len: %u\n", len); /* peek_for_as4 wants getp at capibility*/ as4 = peek_for_as4_capability(peer, len); printf("peek_for_as4: as4 is %u\n", as4); /* and it should leave getp as it found it */ assert(stream_get_getp(peer->curr) == RANDOM_FUZZ); ret = bgp_open_option_parse(peer, len, &capability); break; case DYNCAP: ret = bgp_capability_receive(peer, t->len); break; default: printf("unknown type %u\n", type); exit(1); } if (ret != BGP_Stop && t->validate_afi) { afi_t afi; safi_t safi; /* Convert AFI, SAFI to internal values, check. */ if (bgp_map_afi_safi_iana2int(t->afi, t->safi, &afi, &safi)) { if (t->afi_valid == VALID_AFI) failed++; } printf("MP: %u(%u)/%u(%u): recv %u, nego %u\n", t->afi, afi, t->safi, safi, peer->afc_recv[afi][safi], peer->afc_nego[afi][safi]); if (t->afi_valid == VALID_AFI) { if (!peer->afc_recv[afi][safi]) failed++; if (!peer->afc_nego[afi][safi]) failed++; } } if (as4 != t->peek_for) { printf("as4 %u != %u\n", as4, t->peek_for); failed++; } /* * Some of the functions used return BGP_Stop on error and some return * -1. If we have -1, keep it; if we have BGP_Stop, transform it to the * correct pass/fail code */ if (ret != -1) ret = (ret == BGP_Stop) ? -1 : 0; printf("parsed?: %s\n", ret ? "no" : "yes"); if (ret != t->parses) { printf("t->parses: %d\nret: %d\n", t->parses, ret); failed++; } if (tty) printf("%s", (failed > oldfailed) ? VT100_RED "failed!" VT100_RESET : VT100_GREEN "OK" VT100_RESET); else printf("%s", (failed > oldfailed) ? "failed!" : "OK"); if (failed) printf(" (%u)", failed); printf("\n\n"); } static struct bgp *bgp; static as_t asn = 100; int main(void) { struct peer *peer; int i, j; conf_bgp_debug_neighbor_events = -1UL; conf_bgp_debug_packet = -1UL; conf_bgp_debug_as4 = -1UL; term_bgp_debug_neighbor_events = -1UL; term_bgp_debug_packet = -1UL; term_bgp_debug_as4 = -1UL; qobj_init(); master = thread_master_create(NULL); bgp_master_init(master); vrf_init(NULL, NULL, NULL, NULL, NULL); bgp_option_set(BGP_OPT_NO_LISTEN); bgp_pthreads_init(); bgp_pth_ka->running = true; if (fileno(stdout) >= 0) tty = isatty(fileno(stdout)); if (bgp_get(&bgp, &asn, NULL, BGP_INSTANCE_TYPE_DEFAULT)) return -1; peer = peer_create_accept(bgp); peer->host = (char *)"foo"; for (i = AFI_IP; i < AFI_MAX; i++) for (j = SAFI_UNICAST; j < SAFI_MAX; j++) { peer->afc[i][j] = 1; peer->afc_adv[i][j] = 1; } peer->curr = stream_new(BGP_MAX_PACKET_SIZE); i = 0; while (mp_segments[i].name) parse_test(peer, &mp_segments[i++], CAPABILITY); /* These tests assume mp_segments tests set at least * one of the afc_nego's */ i = 0; while (test_segments[i].name) parse_test(peer, &test_segments[i++], CAPABILITY); i = 0; while (misc_segments[i].name) parse_test(peer, &misc_segments[i++], CAPABILITY); i = 0; while (opt_params[i].name) parse_test(peer, &opt_params[i++], OPT_PARAM); SET_FLAG(peer->cap, PEER_CAP_DYNAMIC_ADV); peer->status = Established; i = 0; while (dynamic_cap_msgs[i].name) parse_test(peer, &dynamic_cap_msgs[i++], DYNCAP); printf("failures: %d\n", failed); return failed; } frr-7.2.1/tests/bgpd/test_capability.py0000644000000000000000000000560513610377563015032 00000000000000import frrtest class TestCapability(frrtest.TestMultiOut): program = './test_capability' TestCapability.okfail("MP4: MP IP/Uni") TestCapability.okfail("MPv6: MP IPv6/Uni") TestCapability.okfail("MP2: MP IP/Multicast") TestCapability.okfail("MP3: MP IP6/MPLS-labeled VPN") TestCapability.okfail("MP5: MP IP6/MPLS-VPN") TestCapability.okfail("MP6: MP IP4/MPLS-labeled VPN") TestCapability.okfail("MP8: MP unknown AFI/SAFI") TestCapability.okfail("MP-short: MP IP4/Unicast, length too short (< minimum)") TestCapability.okfail("MP-overflow: MP IP4/Unicast, length too long") TestCapability.okfail("caphdr: capability header, and no more") TestCapability.okfail("nodata: header, no data but length says there is") TestCapability.okfail("padded: valid, with padding") TestCapability.okfail("minsize: violates minsize requirement") TestCapability.okfail("ORF: ORF, simple, single entry, single tuple") TestCapability.okfail("ORF-many: ORF, multi entry/tuple") TestCapability.okfail("ORFlo: ORF, multi entry/tuple, hdr length too short") TestCapability.okfail("ORFlu: ORF, multi entry/tuple, length too long") TestCapability.okfail("ORFnu: ORF, multi entry/tuple, entry number too long") TestCapability.okfail("ORFno: ORF, multi entry/tuple, entry number too short") TestCapability.okfail("ORFpad: ORF, multi entry/tuple, padded to align") TestCapability.okfail("AS4: AS4 capability") TestCapability.okfail("GR: GR capability") TestCapability.okfail("GR-short: GR capability, but header length too short") TestCapability.okfail("GR-long: GR capability, but header length too long") TestCapability.okfail("GR-trunc: GR capability, but truncated") TestCapability.okfail("GR-empty: GR capability, but empty.") TestCapability.okfail("MP-empty: MP capability, but empty.") TestCapability.okfail("ORF-empty: ORF capability, but empty.") TestCapability.okfail("AS4-empty: AS4 capability, but empty.") TestCapability.okfail("dyn-empty: Dynamic capability, but empty.") TestCapability.okfail("dyn-old: Dynamic capability (deprecated version)") TestCapability.okfail("Cap-singlets: One capability per Optional-Param") TestCapability.okfail("Cap-series: Series of capability, one Optional-Param") TestCapability.okfail("AS4more: AS4 capability after other caps (singlets)") TestCapability.okfail("AS4series: AS4 capability, in series of capabilities") TestCapability.okfail("AS4real: AS4 capability, in series of capabilities") TestCapability.okfail("AS4real2: AS4 capability, in series of capabilities") TestCapability.okfail("DynCap: Dynamic Capability Message, IP/Multicast") TestCapability.okfail("DynCapLong: Dynamic Capability Message, IP/Multicast, truncated") TestCapability.okfail("DynCapPadded: Dynamic Capability Message, IP/Multicast, padded") TestCapability.okfail("DynCapMPCpadded: Dynamic Capability Message, IP/Multicast, cap data padded") TestCapability.okfail("DynCapMPCoverflow: Dynamic Capability Message, IP/Multicast, cap data != length") frr-7.2.1/tests/bgpd/test_ecommunity.c0000644000000000000000000000734513610377563014677 00000000000000/* * Copyright (C) 2007 Sun Microsystems, Inc. * * This file is part of Quagga. * * Quagga is free software; you can redistribute it and/or modify it * under the terms of the GNU General Public License as published by the * Free Software Foundation; either version 2, or (at your option) any * later version. * * Quagga 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; see the file COPYING; if not, write to the Free Software * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA */ #include #include "vty.h" #include "stream.h" #include "privs.h" #include "memory.h" #include "queue.h" #include "filter.h" #include "bgpd/bgpd.h" #include "bgpd/bgp_ecommunity.h" /* need these to link in libbgp */ struct zebra_privs_t *bgpd_privs = NULL; struct thread_master *master = NULL; static int failed = 0; /* specification for a test - what the results should be */ struct test_spec { const char *shouldbe; /* the string the path should parse to */ }; /* test segments to parse and validate, and use for other tests */ static struct test_segment { const char *name; const char *desc; const uint8_t data[1024]; int len; struct test_spec sp; } test_segments[] = {{/* 0 */ "ipaddr", "rt 1.2.3.4:257", {ECOMMUNITY_ENCODE_IP, ECOMMUNITY_ROUTE_TARGET, 0x1, 0x2, 0x3, 0x4, 0x1, 0x1}, 8, {"rt 1.2.3.4:257"}}, {/* 1 */ "ipaddr-so", "soo 1.2.3.4:257", {ECOMMUNITY_ENCODE_IP, ECOMMUNITY_SITE_ORIGIN, 0x1, 0x2, 0x3, 0x4, 0x1, 0x1}, 8, {"soo 1.2.3.4:257"}}, {/* 2 */ "asn", "rt 23456:987654321", {ECOMMUNITY_ENCODE_AS, ECOMMUNITY_SITE_ORIGIN, 0x5b, 0xa0, 0x3a, 0xde, 0x68, 0xb1}, 8, {"soo 23456:987654321"}}, {/* 3 */ "asn4", "rt 168450976:4321", {ECOMMUNITY_ENCODE_AS4, ECOMMUNITY_SITE_ORIGIN, 0xa, 0xa, 0x5b, 0xa0, 0x10, 0xe1}, 8, {"soo 168450976:4321"}}, {NULL, NULL, {0}, 0, {NULL}}}; /* validate the given aspath */ static int validate(struct ecommunity *ecom, const struct test_spec *sp) { int fails = 0; struct ecommunity *etmp; char *str1, *str2; printf("got:\n %s\n", ecommunity_str(ecom)); str1 = ecommunity_ecom2str(ecom, ECOMMUNITY_FORMAT_COMMUNITY_LIST, 0); etmp = ecommunity_str2com(str1, 0, 1); if (etmp) str2 = ecommunity_ecom2str(etmp, ECOMMUNITY_FORMAT_COMMUNITY_LIST, 0); else str2 = NULL; if (strcmp(sp->shouldbe, str1)) { failed++; fails++; printf("shouldbe: %s\n%s\n", str1, sp->shouldbe); } if (!etmp || strcmp(str1, str2)) { failed++; fails++; printf("dogfood: in %s\n" " in->out %s\n", str1, (etmp && str2) ? str2 : "NULL"); } ecommunity_free(&etmp); XFREE(MTYPE_ECOMMUNITY_STR, str1); XFREE(MTYPE_ECOMMUNITY_STR, str2); return fails; } /* basic parsing test */ static void parse_test(struct test_segment *t) { struct ecommunity *ecom; printf("%s: %s\n", t->name, t->desc); ecom = ecommunity_parse((uint8_t *)t->data, t->len); printf("ecom: %s\nvalidating...:\n", ecommunity_str(ecom)); if (!validate(ecom, &t->sp)) printf("OK\n"); else printf("failed\n"); printf("\n"); ecommunity_unintern(&ecom); } int main(void) { int i = 0; ecommunity_init(); while (test_segments[i].name) parse_test(&test_segments[i++]); printf("failures: %d\n", failed); // printf ("aspath count: %ld\n", aspath_count()); return failed; // return (failed + aspath_count()); } frr-7.2.1/tests/bgpd/test_ecommunity.py0000644000000000000000000000033513610377563015075 00000000000000import frrtest class TestEcommunity(frrtest.TestMultiOut): program = './test_ecommunity' TestEcommunity.okfail('ipaddr') TestEcommunity.okfail('ipaddr-so') TestEcommunity.okfail('asn') TestEcommunity.okfail('asn4') frr-7.2.1/tests/bgpd/test_mp_attr.c0000644000000000000000000005370613610377563014156 00000000000000/* * Copyright (C) 2008 Sun Microsystems, Inc. * * This file is part of Quagga. * * Quagga is free software; you can redistribute it and/or modify it * under the terms of the GNU General Public License as published by the * Free Software Foundation; either version 2, or (at your option) any * later version. * * Quagga 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; see the file COPYING; if not, write to the Free Software * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA */ #include #include "qobj.h" #include "vty.h" #include "stream.h" #include "privs.h" #include "memory.h" #include "queue.h" #include "filter.h" #include "bgpd/bgpd.h" #include "bgpd/bgp_attr.h" #include "bgpd/bgp_open.h" #include "bgpd/bgp_debug.h" #include "bgpd/bgp_route.h" #include "bgpd/bgp_packet.h" #include "bgpd/bgp_mplsvpn.h" #include "bgpd/bgp_nexthop.h" #include "bgpd/bgp_vty.h" #define VT100_RESET "\x1b[0m" #define VT100_RED "\x1b[31m" #define VT100_GREEN "\x1b[32m" #define VT100_YELLOW "\x1b[33m" #define CAPABILITY 0 #define DYNCAP 1 #define OPT_PARAM 2 /* need these to link in libbgp */ struct zebra_privs_t *bgpd_privs = NULL; struct thread_master *master = NULL; static int failed = 0; static int tty = 0; /* test segments to parse and validate, and use for other tests */ static struct test_segment { const char *name; const char *desc; const uint8_t data[1024]; int len; #define SHOULD_PARSE 0 #define SHOULD_ERR -1 int parses; /* whether it should parse or not */ } mp_reach_segments[] = { { "IPv6", "IPV6 MP Reach, global nexthop, 1 NLRI", { /* AFI / SAFI */ 0x0, AFI_IP6, SAFI_UNICAST, /* nexthop bytes */ 16, /* Nexthop (global) */ 0xff, 0xfe, 0x1, 0x2, 0xaa, 0xbb, 0xcc, 0xdd, 0x3, 0x4, 0x5, 0x6, 0xa1, 0xa2, 0xa3, 0xa4, /* SNPA (defunct, MBZ) */ 0x0, /* NLRI tuples */ 32, 0xff, 0xfe, 0x1, 0x2, /* fffe:102::/32 */ }, (4 + 16 + 1 + 5), SHOULD_PARSE, }, { "IPv6-2", "IPV6 MP Reach, global nexthop, 2 NLRIs", { /* AFI / SAFI */ 0x0, AFI_IP6, SAFI_UNICAST, /* nexthop bytes */ 16, /* Nexthop (global) */ 0xff, 0xfe, 0x1, 0x2, /* ffee:102:... */ 0xaa, 0xbb, 0xcc, 0xdd, 0x3, 0x4, 0x5, 0x6, 0xa1, 0xa2, 0xa3, 0xa4, /* SNPA (defunct, MBZ) */ 0x0, /* NLRI tuples */ 32, 0xff, 0xfe, 0x1, 0x2, /* fffe:102::/32 */ 64, 0xff, 0xfe, 0x0, 0x1, /* fffe:1:2:3::/64 */ 0x0, 0x2, 0x0, 0x3, }, (4 + 16 + 1 + 5 + 9), SHOULD_PARSE, }, { "IPv6-default", "IPV6 MP Reach, global nexthop, 2 NLRIs + default", { /* AFI / SAFI */ 0x0, AFI_IP6, SAFI_UNICAST, /* nexthop bytes */ 16, /* Nexthop (global) */ 0xff, 0xfe, 0x1, 0x2, 0xaa, 0xbb, 0xcc, 0xdd, 0x3, 0x4, 0x5, 0x6, 0xa1, 0xa2, 0xa3, 0xa4, /* SNPA (defunct, MBZ) */ 0x0, /* NLRI tuples */ 32, 0xff, 0xfe, 0x1, 0x2, /* fffe:102::/32 */ 64, 0xff, 0xfe, 0x0, 0x1, /* fffe:1:2:3::/64 */ 0x0, 0x2, 0x0, 0x3, 0x0, /* ::/0 */ }, (4 + 16 + 1 + 5 + 9 + 1), SHOULD_PARSE, }, { "IPv6-lnh", "IPV6 MP Reach, global+local nexthops, 2 NLRIs + default", { /* AFI / SAFI */ 0x0, AFI_IP6, SAFI_UNICAST, /* nexthop bytes */ 32, /* Nexthop (global) */ 0xff, 0xfe, 0x1, 0x2, /* fffe:102:... */ 0xaa, 0xbb, 0xcc, 0xdd, 0x3, 0x4, 0x5, 0x6, 0xa1, 0xa2, 0xa3, 0xa4, /* Nexthop (local) */ 0xfe, 0x80, 0x0, 0x0, /* fe80::210:2ff:.. */ 0x0, 0x0, 0x0, 0x0, 0x2, 0x10, 0x2, 0xff, 0x1, 0x2, 0x3, 0x4, /* SNPA (defunct, MBZ) */ 0x0, /* NLRI tuples */ 32, 0xff, 0xfe, 0x1, 0x2, /* fffe:102::/32 */ 64, 0xff, 0xfe, 0x0, 0x1, /* fffe:1:2:3::/64 */ 0x0, 0x2, 0x0, 0x3, 0x0, /* ::/0 */ }, (4 + 32 + 1 + 5 + 9 + 1), SHOULD_PARSE, }, { "IPv6-nhlen", "IPV6 MP Reach, inappropriate nexthop length", { /* AFI / SAFI */ 0x0, AFI_IP6, SAFI_UNICAST, /* nexthop bytes */ 4, /* Nexthop (global) */ 0xff, 0xfe, 0x1, 0x2, /* fffe:102:... */ 0xaa, 0xbb, 0xcc, 0xdd, 0x3, 0x4, 0x5, 0x6, 0xa1, 0xa2, 0xa3, 0xa4, /* Nexthop (local) */ 0xfe, 0x80, 0x0, 0x0, /* fe80::210:2ff:.. */ 0x0, 0x0, 0x0, 0x0, 0x2, 0x10, 0x2, 0xff, 0x1, 0x2, 0x3, 0x4, /* SNPA (defunct, MBZ) */ 0x0, /* NLRI tuples */ 32, 0xff, 0xfe, 0x1, 0x2, /* fffe:102::/32 */ 64, 0xff, 0xfe, 0x0, 0x1, /* fffe:1:2:3::/64 */ 0x0, 0x2, 0x0, 0x3, 0x0, /* ::/0 */ }, (4 + 32 + 1 + 5 + 9 + 1), SHOULD_ERR, }, { "IPv6-nhlen2", "IPV6 MP Reach, invalid nexthop length", { /* AFI / SAFI */ 0x0, AFI_IP6, SAFI_UNICAST, /* nexthop bytes */ 5, /* Nexthop (global) */ 0xff, 0xfe, 0x1, 0x2, /* fffe:102:... */ 0xaa, 0xbb, 0xcc, 0xdd, 0x3, 0x4, 0x5, 0x6, 0xa1, 0xa2, 0xa3, 0xa4, /* Nexthop (local) */ 0xfe, 0x80, 0x0, 0x0, /* fe80::210:2ff:.. */ 0x0, 0x0, 0x0, 0x0, 0x2, 0x10, 0x2, 0xff, 0x1, 0x2, 0x3, 0x4, /* SNPA (defunct, MBZ) */ 0x0, /* NLRI tuples */ 32, 0xff, 0xfe, 0x1, 0x2, /* fffe:102::/32 */ 64, 0xff, 0xfe, 0x0, 0x1, /* fffe:1:2:3::/64 */ 0x0, 0x2, 0x0, 0x3, 0x0, /* ::/0 */ }, (4 + 32 + 1 + 5 + 9 + 1), SHOULD_ERR, }, { "IPv6-nhlen3", "IPV6 MP Reach, nexthop length overflow", { /* AFI / SAFI */ 0x0, AFI_IP6, SAFI_UNICAST, /* nexthop bytes */ 32, /* Nexthop (global) */ 0xff, 0xfe, 0x1, 0x2, /* fffe:102:... */ 0xaa, 0xbb, 0xcc, 0xdd, 0x3, 0x4, 0x5, 0x6, 0xa1, 0xa2, 0xa3, 0xa4, }, (4 + 16), SHOULD_ERR, }, { "IPv6-nhlen4", "IPV6 MP Reach, nexthop length short", { /* AFI / SAFI */ 0x0, AFI_IP6, SAFI_UNICAST, /* nexthop bytes */ 16, /* Nexthop (global) */ 0xff, 0xfe, 0x1, 0x2, /* fffe:102:... */ 0xaa, 0xbb, 0xcc, 0xdd, 0x3, 0x4, 0x5, 0x6, 0xa1, 0xa2, 0xa3, 0xa4, /* Nexthop (local) */ 0xfe, 0x80, 0x0, 0x0, /* fe80::210:2ff:.. */ 0x0, 0x0, 0x0, 0x0, 0x2, 0x10, 0x2, 0xff, 0x1, 0x2, 0x3, 0x4, /* SNPA (defunct, MBZ) */ 0x0, /* NLRI tuples */ 32, 0xff, 0xfe, 0x1, 0x2, /* fffe:102::/32 */ 64, 0xff, 0xfe, 0x0, 0x1, /* fffe:1:2:3::/64 */ 0x0, 0x2, 0x0, 0x3, 0x0, /* ::/0 */ }, (4 + 32 + 1 + 5 + 9 + 1), SHOULD_ERR, }, { "IPv6-nlri", "IPV6 MP Reach, NLRI bitlen overflow", { /* AFI / SAFI */ 0x0, AFI_IP6, SAFI_UNICAST, /* nexthop bytes */ 32, /* Nexthop (global) */ 0xff, 0xfe, 0x1, 0x2, /* fffe:102:... */ 0xaa, 0xbb, 0xcc, 0xdd, 0x3, 0x4, 0x5, 0x6, 0xa1, 0xa2, 0xa3, 0xa4, /* Nexthop (local) */ 0xfe, 0x80, 0x0, 0x0, /* fe80::210:2ff:.. */ 0x0, 0x0, 0x0, 0x0, 0x2, 0x10, 0x2, 0xff, 0x1, 0x2, 0x3, 0x4, /* SNPA (defunct, MBZ) */ 0x0, /* NLRI tuples */ 120, 0xff, 0xfe, 0x1, 0x2, /* fffe:102::/32 */ 64, 0xff, 0xfe, 0x0, 0x1, /* fffe:1:2:3::/64 */ 0x0, 0x2, 0x0, 0x3, 0, /* ::/0 */ }, (4 + 32 + 1 + 5 + 9 + 1), SHOULD_ERR, }, { "IPv4", "IPv4 MP Reach, 2 NLRIs + default", { /* AFI / SAFI */ 0x0, AFI_IP, SAFI_UNICAST, /* nexthop bytes */ 4, /* Nexthop */ 192, 168, 0, 1, /* SNPA (defunct, MBZ) */ 0x0, /* NLRI tuples */ 16, 10, 1, /* 10.1/16 */ 17, 10, 2, 3, /* 10.2.3/17 */ 0, /* 0/0 */ }, (4 + 4 + 1 + 3 + 4 + 1), SHOULD_PARSE, }, { "IPv4-nhlen", "IPv4 MP Reach, nexthop lenth overflow", { /* AFI / SAFI */ 0x0, AFI_IP, SAFI_UNICAST, /* nexthop bytes */ 32, /* Nexthop */ 192, 168, 0, 1, /* SNPA (defunct, MBZ) */ 0x0, /* NLRI tuples */ 16, 10, 1, /* 10.1/16 */ 17, 10, 2, 3, /* 10.2.3/17 */ 0, /* 0/0 */ }, (4 + 4 + 1 + 3 + 4 + 1), SHOULD_ERR, }, { "IPv4-nlrilen", "IPv4 MP Reach, nlri lenth overflow", { /* AFI / SAFI */ 0x0, AFI_IP, SAFI_UNICAST, /* nexthop bytes */ 4, /* Nexthop */ 192, 168, 0, 1, /* SNPA (defunct, MBZ) */ 0x0, /* NLRI tuples */ 16, 10, 1, /* 10.1/16 */ 30, 10, 0, /* 0/0 */ }, (4 + 4 + 1 + 3 + 2 + 1), SHOULD_ERR, }, { "IPv4-VPNv4", "IPv4/VPNv4 MP Reach, RD, Nexthop, 2 NLRIs", { /* AFI / SAFI */ 0x0, AFI_IP, IANA_SAFI_MPLS_VPN, /* nexthop bytes */ 12, /* RD */ 0, 0, 0, 0, /* RD defined to be 0 */ 0, 0, 0, 0, /* Nexthop */ 192, 168, 0, 1, /* SNPA (defunct, MBZ) */ 0x0, /* NLRI tuples */ 88 + 16, 0, 1, 2, /* tag */ /* rd, 8 octets */ 0, 0, /* RD_TYPE_AS */ 0, 2, 0, 0xff, 3, 4, /* AS(2):val(4) */ 10, 1, /* 10.1/16 */ 88 + 17, 0xff, 0, 0, /* tag */ /* rd, 8 octets */ 0, 0, /* RD_TYPE_IP */ 192, 168, 0, 1, /* IPv4 */ 10, 2, 3, /* 10.2.3/17 */ }, (4 + 12 + 1 + (1 + 3 + 8 + 2) + (1 + 3 + 8 + 3)), SHOULD_PARSE, }, { "IPv4-VPNv4-bogus-plen", "IPv4/MPLS-labeled VPN MP Reach, RD, Nexthop, NLRI / bogus p'len", { /* AFI / SAFI */ 0x0, AFI_IP, IANA_SAFI_MPLS_VPN, /* nexthop bytes */ 12, /* RD */ 0, 0, 1, 2, 0, 0xff, 3, 4, /* Nexthop */ 192, 168, 0, 1, /* SNPA (defunct, MBZ) */ 0x0, /* NLRI tuples */ 16, 10, 1, /* 10.1/16 */ 17, 10, 2, 3, /* 10.2.3/17 */ 0, /* 0/0 */ }, (3 + 1 + 3 * 4 + 1 + 3 + 4 + 1), SHOULD_ERR, }, { "IPv4-VPNv4-plen1-short", "IPv4/VPNv4 MP Reach, RD, Nexthop, 2 NLRIs, 1st plen short", { /* AFI / SAFI */ 0x0, AFI_IP, IANA_SAFI_MPLS_VPN, /* nexthop bytes */ 12, /* RD */ 0, 0, 0, 0, /* RD defined to be 0 */ 0, 0, 0, 0, /* Nexthop */ 192, 168, 0, 1, /* SNPA (defunct, MBZ) */ 0x0, /* NLRI tuples */ 88 + 1, 0, 1, 2, /* tag */ /* rd, 8 octets */ 0, 0, /* RD_TYPE_AS */ 0, 2, 0, 0xff, 3, 4, /* AS(2):val(4) */ 10, 1, /* 10.1/16 */ 88 + 17, 0xff, 0, 0, /* tag */ /* rd, 8 octets */ 0, 0, /* RD_TYPE_IP */ 192, 168, 0, 1, /* IPv4 */ 10, 2, 3, /* 10.2.3/17 */ }, (4 + 12 + 1 + (1 + 3 + 8 + 2) + (1 + 3 + 8 + 3)), SHOULD_ERR, }, { "IPv4-VPNv4-plen1-long", "IPv4/VPNv4 MP Reach, RD, Nexthop, 2 NLRIs, 1st plen long", { /* AFI / SAFI */ 0x0, AFI_IP, IANA_SAFI_MPLS_VPN, /* nexthop bytes */ 12, /* RD */ 0, 0, 0, 0, /* RD defined to be 0 */ 0, 0, 0, 0, /* Nexthop */ 192, 168, 0, 1, /* SNPA (defunct, MBZ) */ 0x0, /* NLRI tuples */ 88 + 32, 0, 1, 2, /* tag */ /* rd, 8 octets */ 0, 0, /* RD_TYPE_AS */ 0, 2, 0, 0xff, 3, 4, /* AS(2):val(4) */ 10, 1, /* 10.1/16 */ 88 + 17, 0xff, 0, 0, /* tag */ /* rd, 8 octets */ 0, 0, /* RD_TYPE_IP */ 192, 168, 0, 1, /* IPv4 */ 10, 2, 3, /* 10.2.3/17 */ }, (4 + 12 + 1 + (1 + 3 + 8 + 2) + (1 + 3 + 8 + 3)), SHOULD_ERR, }, { "IPv4-VPNv4-plenn-long", "IPv4/VPNv4 MP Reach, RD, Nexthop, 3 NLRIs, last plen long", { /* AFI / SAFI */ 0x0, AFI_IP, IANA_SAFI_MPLS_VPN, /* nexthop bytes */ 12, /* RD */ 0, 0, 0, 0, /* RD defined to be 0 */ 0, 0, 0, 0, /* Nexthop */ 192, 168, 0, 1, /* SNPA (defunct, MBZ) */ 0x0, /* NLRI tuples */ 88 + 16, 0, 1, 2, /* tag */ /* rd, 8 octets */ 0, 0, /* RD_TYPE_AS */ 0, 2, 0, 0xff, 3, 4, /* AS(2):val(4) */ 10, 1, /* 10.1/16 */ 88 + 17, 0xff, 0, 0, /* tag */ /* rd, 8 octets */ 0, 0, /* RD_TYPE_IP */ 192, 168, 0, 1, /* IPv4 */ 10, 2, 3, /* 10.2.3/17 */ 88 + 1, /* bogus */ }, (4 + 12 + 1 + (1 + 3 + 8 + 2) + (1 + 3 + 8 + 3) + 1), SHOULD_ERR, }, { "IPv4-VPNv4-plenn-short", "IPv4/VPNv4 MP Reach, RD, Nexthop, 2 NLRIs, last plen short", { /* AFI / SAFI */ 0x0, AFI_IP, IANA_SAFI_MPLS_VPN, /* nexthop bytes */ 12, /* RD */ 0, 0, 0, 0, /* RD defined to be 0 */ 0, 0, 0, 0, /* Nexthop */ 192, 168, 0, 1, /* SNPA (defunct, MBZ) */ 0x0, /* NLRI tuples */ 88 + 16, 0, 1, 2, /* tag */ /* rd, 8 octets */ 0, 0, /* RD_TYPE_AS */ 0, 2, 0, 0xff, 3, 4, /* AS(2):val(4) */ 10, 1, /* 10.1/16 */ 88 + 2, 0xff, 0, 0, /* tag */ /* rd, 8 octets */ 0, 0, /* RD_TYPE_IP */ 192, 168, 0, 1, /* IPv4 */ 10, 2, 3, /* 10.2.3/17 */ }, (4 + 12 + 1 + (1 + 3 + 8 + 2) + (1 + 3 + 8 + 3)), SHOULD_ERR, }, { "IPv4-VPNv4-bogus-rd-type", "IPv4/VPNv4 MP Reach, RD, NH, 2 NLRI, unknown RD in 1st (log, but parse)", { /* AFI / SAFI */ 0x0, AFI_IP, IANA_SAFI_MPLS_VPN, /* nexthop bytes */ 12, /* RD */ 0, 0, 0, 0, /* RD defined to be 0 */ 0, 0, 0, 0, /* Nexthop */ 192, 168, 0, 1, /* SNPA (defunct, MBZ) */ 0x0, /* NLRI tuples */ 88 + 16, 0, 1, 2, /* tag */ /* rd, 8 octets */ 0xff, 0, /* Bogus RD */ 0, 2, 0, 0xff, 3, 4, /* AS(2):val(4) */ 10, 1, /* 10.1/16 */ 88 + 17, 0xff, 0, 0, /* tag */ /* rd, 8 octets */ 0, 0, /* RD_TYPE_IP */ 192, 168, 0, 1, /* IPv4 */ 10, 2, 3, /* 10.2.3/17 */ }, (4 + 12 + 1 + (1 + 3 + 8 + 2) + (1 + 3 + 8 + 3)), SHOULD_PARSE, }, { "IPv4-VPNv4-0-nlri", "IPv4/VPNv4 MP Reach, RD, Nexthop, 3 NLRI, 3rd 0 bogus", { /* AFI / SAFI */ 0x0, AFI_IP, IANA_SAFI_MPLS_VPN, /* nexthop bytes */ 12, /* RD */ 0, 0, 0, 0, /* RD defined to be 0 */ 0, 0, 0, 0, /* Nexthop */ 192, 168, 0, 1, /* SNPA (defunct, MBZ) */ 0x0, /* NLRI tuples */ 88 + 16, 0, 1, 2, /* tag */ /* rd, 8 octets */ 0, 0, /* RD_TYPE_AS */ 0, 2, 0, 0xff, 3, 4, /* AS(2):val(4) */ 10, 1, /* 10.1/16 */ 88 + 17, 0xff, 0, 0, /* tag */ /* rd, 8 octets */ 0, 0, /* RD_TYPE_IP */ 192, 168, 0, 1, /* IPv4 */ 10, 2, 3, /* 10.2.3/17 */ 0 /* 0/0, bogus for vpnv4 ?? */ }, (4 + 12 + 1 + (1 + 3 + 8 + 2) + (1 + 3 + 8 + 3) + 1), SHOULD_ERR, }, /* From bug #385 */ { "IPv6-bug", "IPv6, global nexthop, 1 default NLRI", { /* AFI / SAFI */ 0x0, 0x2, 0x1, /* nexthop bytes */ 0x20, /* Nexthop (global) */ 0x20, 0x01, 0x04, 0x70, 0x00, 0x01, 0x00, 0x06, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x01, /* Nexthop (local) */ 0xfe, 0x80, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x02, 0x0c, 0xdb, 0xff, 0xfe, 0xfe, 0xeb, 0x00, /* SNPA (defunct, MBZ) */ 0, /* NLRI tuples */ /* Should have 0 here for ::/0, but dont */ }, 37, SHOULD_ERR, }, { .name = "IPv4", .desc = "IPV4 MP Reach, flowspec, 1 NLRI", .data = { /* AFI / SAFI */ 0x0, AFI_IP, IANA_SAFI_FLOWSPEC, 0x00, /* no NH */ 0x00, 0x06, /* FS Length */ 0x01, /* FS dest prefix ID */ 0x1e, /* IP */ 0x1e, 0x28, 0x28, 0x0 }, .len = 12, .parses = SHOULD_PARSE, }, {NULL, NULL, {0}, 0, 0}}; /* MP_UNREACH_NLRI tests */ static struct test_segment mp_unreach_segments[] = { { "IPv6-unreach", "IPV6 MP Unreach, 1 NLRI", { /* AFI / SAFI */ 0x0, AFI_IP6, SAFI_UNICAST, /* NLRI tuples */ 32, 0xff, 0xfe, 0x1, 0x2, /* fffe:102::/32 */ }, (3 + 5), SHOULD_PARSE, }, { "IPv6-unreach2", "IPV6 MP Unreach, 2 NLRIs", { /* AFI / SAFI */ 0x0, AFI_IP6, SAFI_UNICAST, /* NLRI tuples */ 32, 0xff, 0xfe, 0x1, 0x2, /* fffe:102::/32 */ 64, 0xff, 0xfe, 0x0, 0x1, /* fffe:1:2:3::/64 */ 0x0, 0x2, 0x0, 0x3, }, (3 + 5 + 9), SHOULD_PARSE, }, { "IPv6-unreach-default", "IPV6 MP Unreach, 2 NLRIs + default", { /* AFI / SAFI */ 0x0, AFI_IP6, SAFI_UNICAST, /* NLRI tuples */ 32, 0xff, 0xfe, 0x1, 0x2, /* fffe:102::/32 */ 64, 0xff, 0xfe, 0x0, 0x1, /* fffe:1:2:3::/64 */ 0x0, 0x2, 0x0, 0x3, 0x0, /* ::/0 */ }, (3 + 5 + 9 + 1), SHOULD_PARSE, }, { "IPv6-unreach-nlri", "IPV6 MP Unreach, NLRI bitlen overflow", { /* AFI / SAFI */ 0x0, AFI_IP6, SAFI_UNICAST, /* NLRI tuples */ 120, 0xff, 0xfe, 0x1, 0x2, /* fffe:102::/32 */ 64, 0xff, 0xfe, 0x0, 0x1, /* fffe:1:2:3::/64 */ 0x0, 0x2, 0x0, 0x3, 0, /* ::/0 */ }, (3 + 5 + 9 + 1), SHOULD_ERR, }, { "IPv4-unreach", "IPv4 MP Unreach, 2 NLRIs + default", { /* AFI / SAFI */ 0x0, AFI_IP, SAFI_UNICAST, /* NLRI tuples */ 16, 10, 1, /* 10.1/16 */ 17, 10, 2, 3, /* 10.2.3/17 */ 0, /* 0/0 */ }, (3 + 3 + 4 + 1), SHOULD_PARSE, }, { "IPv4-unreach-nlrilen", "IPv4 MP Unreach, nlri length overflow", { /* AFI / SAFI */ 0x0, AFI_IP, SAFI_UNICAST, /* NLRI tuples */ 16, 10, 1, /* 10.1/16 */ 30, 10, 0, /* 0/0 */ }, (3 + 3 + 2 + 1), SHOULD_ERR, }, { "IPv4-unreach-VPNv4", "IPv4/MPLS-labeled VPN MP Unreach, RD, 3 NLRIs", { /* AFI / SAFI */ 0x0, AFI_IP, IANA_SAFI_MPLS_VPN, /* NLRI tuples */ 88 + 16, 0, 1, 2, /* tag */ /* rd, 8 octets */ 0, 0, /* RD_TYPE_AS */ 0, 2, 0, 0xff, 3, 4, /* AS(2):val(4) */ 10, 1, /* 10.1/16 */ 88 + 17, 0xff, 0, 0, /* tag */ /* rd, 8 octets */ 0, 0, /* RD_TYPE_IP */ 192, 168, 0, 1, /* IPv4 */ 10, 2, 3, /* 10.2.3/17 */ }, (3 + (1 + 3 + 8 + 2) + (1 + 3 + 8 + 3)), SHOULD_PARSE, }, { .name = "IPv4", .desc = "IPV4 MP Unreach, flowspec, 1 NLRI", .data = { /* AFI / SAFI */ 0x0, AFI_IP, IANA_SAFI_FLOWSPEC, 0x06, /* FS Length */ 0x01, /* FS dest prefix ID */ 0x1e, /* IP */ 0x1e, 0x28, 0x28, 0x0 }, .len = 10, .parses = SHOULD_PARSE, }, {NULL, NULL, {0}, 0, 0}}; static struct test_segment mp_prefix_sid[] = { { "PREFIX-SID", "PREFIX-SID Test 1", { 0x01, 0x00, 0x07, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x02, 0x03, 0x00, 0x08, 0x00, 0x00, 0x0a, 0x1b, 0xfe, 0x00, 0x00, 0x0a }, .len = 21, .parses = SHOULD_PARSE, }, {NULL, NULL, { 0 }, 0, 0}, }; /* nlri_parse indicates 0 on successful parse, and -1 otherwise. * attr_parse indicates BGP_ATTR_PARSE_PROCEED/0 on success, * and BGP_ATTR_PARSE_ERROR/-1 or lower negative ret on err. */ static void handle_result(struct peer *peer, struct test_segment *t, int parse_ret, int nlri_ret) { int oldfailed = failed; printf("mp attr parsed?: %s\n", parse_ret ? "no" : "yes"); if (!parse_ret) printf("nrli parsed?: %s\n", nlri_ret ? "no" : "yes"); printf("should parse?: %s\n", t->parses ? "no" : "yes"); if ((parse_ret != 0 || nlri_ret != 0) != (t->parses != 0)) failed++; if (tty) printf("%s", (failed > oldfailed) ? VT100_RED "failed!" VT100_RESET : VT100_GREEN "OK" VT100_RESET); else printf("%s", (failed > oldfailed) ? "failed!" : "OK"); if (failed) printf(" (%u)", failed); printf("\n\n"); } /* basic parsing test */ static void parse_test(struct peer *peer, struct test_segment *t, int type) { int parse_ret = 0, nlri_ret = 0; struct attr attr = {}; struct bgp_nlri nlri = {}; struct bgp_attr_parser_args attr_args = { .peer = peer, .length = t->len, .total = 1, .attr = &attr, .type = type, .flags = BGP_ATTR_FLAG_OPTIONAL, .startp = BGP_INPUT_PNT(peer), }; #define RANDOM_FUZZ 35 stream_reset(peer->curr); stream_put(peer->curr, NULL, RANDOM_FUZZ); stream_set_getp(peer->curr, RANDOM_FUZZ); stream_write(peer->curr, t->data, t->len); printf("%s: %s\n", t->name, t->desc); switch (type) { case BGP_ATTR_MP_REACH_NLRI: parse_ret = bgp_mp_reach_parse(&attr_args, &nlri); break; case BGP_ATTR_MP_UNREACH_NLRI: parse_ret = bgp_mp_unreach_parse(&attr_args, &nlri); break; case BGP_ATTR_PREFIX_SID: parse_ret = bgp_attr_prefix_sid(t->len, &attr_args, &nlri); break; default: printf("unknown type"); return; } if (!parse_ret) { iana_afi_t pkt_afi; iana_safi_t pkt_safi; /* Convert AFI, SAFI to internal values, check. */ if (bgp_map_afi_safi_int2iana(nlri.afi, nlri.safi, &pkt_afi, &pkt_safi)) assert(0); printf("MP: %u(%u)/%u(%u): recv %u, nego %u\n", nlri.afi, pkt_afi, nlri.safi, pkt_safi, peer->afc_recv[nlri.afi][nlri.safi], peer->afc_nego[nlri.afi][nlri.safi]); } if (!parse_ret) { if (type == BGP_ATTR_MP_REACH_NLRI) nlri_ret = bgp_nlri_parse(peer, &attr, &nlri, 0); else if (type == BGP_ATTR_MP_UNREACH_NLRI) nlri_ret = bgp_nlri_parse(peer, &attr, &nlri, 1); } handle_result(peer, t, parse_ret, nlri_ret); } static struct bgp *bgp; static as_t asn = 100; int main(void) { struct interface ifp; struct peer *peer; int i, j; conf_bgp_debug_neighbor_events = -1UL; conf_bgp_debug_packet = -1UL; conf_bgp_debug_as4 = -1UL; conf_bgp_debug_flowspec = -1UL; term_bgp_debug_neighbor_events = -1UL; term_bgp_debug_packet = -1UL; term_bgp_debug_as4 = -1UL; term_bgp_debug_flowspec = -1UL; qobj_init(); cmd_init(0); bgp_vty_init(); master = thread_master_create("test mp attr"); bgp_master_init(master); vrf_init(NULL, NULL, NULL, NULL, NULL); bgp_option_set(BGP_OPT_NO_LISTEN); bgp_attr_init(); if (fileno(stdout) >= 0) tty = isatty(fileno(stdout)); if (bgp_get(&bgp, &asn, NULL, BGP_INSTANCE_TYPE_DEFAULT)) return -1; peer = peer_create_accept(bgp); peer->host = (char *)"foo"; peer->status = Established; peer->curr = stream_new(BGP_MAX_PACKET_SIZE); ifp.ifindex = 0; peer->nexthop.ifp = &ifp; for (i = AFI_IP; i < AFI_MAX; i++) for (j = SAFI_UNICAST; j < SAFI_MAX; j++) { peer->afc[i][j] = 1; peer->afc_adv[i][j] = 1; } i = 0; while (mp_reach_segments[i].name) parse_test(peer, &mp_reach_segments[i++], BGP_ATTR_MP_REACH_NLRI); i = 0; while (mp_unreach_segments[i].name) parse_test(peer, &mp_unreach_segments[i++], BGP_ATTR_MP_UNREACH_NLRI); i = 0; while (mp_prefix_sid[i].name) parse_test(peer, &mp_prefix_sid[i++], BGP_ATTR_PREFIX_SID); printf("failures: %d\n", failed); return failed; } frr-7.2.1/tests/bgpd/test_mp_attr.py0000644000000000000000000000444513610377563014360 00000000000000import frrtest class TestMpAttr(frrtest.TestMultiOut): program = './test_mp_attr' TestMpAttr.okfail("IPv6: IPV6 MP Reach, global nexthop, 1 NLRI") TestMpAttr.okfail("IPv6-2: IPV6 MP Reach, global nexthop, 2 NLRIs") TestMpAttr.okfail("IPv6-default: IPV6 MP Reach, global nexthop, 2 NLRIs + default") TestMpAttr.okfail("IPv6-lnh: IPV6 MP Reach, global+local nexthops, 2 NLRIs + default") TestMpAttr.okfail("IPv6-nhlen: IPV6 MP Reach, inappropriate nexthop length") TestMpAttr.okfail("IPv6-nhlen2: IPV6 MP Reach, invalid nexthop length") TestMpAttr.okfail("IPv6-nhlen3: IPV6 MP Reach, nexthop length overflow") TestMpAttr.okfail("IPv6-nhlen4: IPV6 MP Reach, nexthop length short") TestMpAttr.okfail("IPv6-nlri: IPV6 MP Reach, NLRI bitlen overflow") TestMpAttr.okfail("IPv4: IPv4 MP Reach, 2 NLRIs + default") TestMpAttr.okfail("IPv4-nhlen: IPv4 MP Reach, nexthop lenth overflow") TestMpAttr.okfail("IPv4-nlrilen: IPv4 MP Reach, nlri lenth overflow") TestMpAttr.okfail("IPv4-VPNv4: IPv4/VPNv4 MP Reach, RD, Nexthop, 2 NLRIs") TestMpAttr.okfail("IPv4-VPNv4-bogus-plen: IPv4/MPLS-labeled VPN MP Reach, RD, Nexthop, NLRI / bogus p'len") TestMpAttr.okfail("IPv4-VPNv4-plen1-short: IPv4/VPNv4 MP Reach, RD, Nexthop, 2 NLRIs, 1st plen short") TestMpAttr.okfail("IPv4-VPNv4-plen1-long: IPv4/VPNv4 MP Reach, RD, Nexthop, 2 NLRIs, 1st plen long") TestMpAttr.okfail("IPv4-VPNv4-plenn-long: IPv4/VPNv4 MP Reach, RD, Nexthop, 3 NLRIs, last plen long") TestMpAttr.okfail("IPv4-VPNv4-plenn-short: IPv4/VPNv4 MP Reach, RD, Nexthop, 2 NLRIs, last plen short") TestMpAttr.okfail("IPv4-VPNv4-bogus-rd-type: IPv4/VPNv4 MP Reach, RD, NH, 2 NLRI, unknown RD in 1st (log, but parse)") TestMpAttr.okfail("IPv4-VPNv4-0-nlri: IPv4/VPNv4 MP Reach, RD, Nexthop, 3 NLRI, 3rd 0 bogus") TestMpAttr.okfail("IPv6-bug: IPv6, global nexthop, 1 default NLRI") TestMpAttr.okfail("IPv6-unreach: IPV6 MP Unreach, 1 NLRI") TestMpAttr.okfail("IPv6-unreach2: IPV6 MP Unreach, 2 NLRIs") TestMpAttr.okfail("IPv6-unreach-default: IPV6 MP Unreach, 2 NLRIs + default") TestMpAttr.okfail("IPv6-unreach-nlri: IPV6 MP Unreach, NLRI bitlen overflow") TestMpAttr.okfail("IPv4-unreach: IPv4 MP Unreach, 2 NLRIs + default") TestMpAttr.okfail("IPv4-unreach-nlrilen: IPv4 MP Unreach, nlri length overflow") TestMpAttr.okfail("IPv4-unreach-VPNv4: IPv4/MPLS-labeled VPN MP Unreach, RD, 3 NLRIs") frr-7.2.1/tests/bgpd/test_mpath.c0000644000000000000000000003172113610377563013612 00000000000000/* * BGP Multipath Unit Test * Copyright (C) 2010 Google Inc. * * This file is part of Quagga * * Quagga is free software; you can redistribute it and/or modify it * under the terms of the GNU General Public License as published by the * Free Software Foundation; either version 2, or (at your option) any * later version. * * Quagga 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; see the file COPYING; if not, write to the Free Software * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA */ #include #include "qobj.h" #include "vty.h" #include "stream.h" #include "privs.h" #include "linklist.h" #include "memory.h" #include "zclient.h" #include "queue.h" #include "filter.h" #include "bgpd/bgpd.h" #include "bgpd/bgp_table.h" #include "bgpd/bgp_route.h" #include "bgpd/bgp_attr.h" #include "bgpd/bgp_nexthop.h" #include "bgpd/bgp_mpath.h" #include "bgpd/bgp_evpn.h" #define VT100_RESET "\x1b[0m" #define VT100_RED "\x1b[31m" #define VT100_GREEN "\x1b[32m" #define VT100_YELLOW "\x1b[33m" #define OK VT100_GREEN "OK" VT100_RESET #define FAILED VT100_RED "failed" VT100_RESET #define TEST_PASSED 0 #define TEST_FAILED -1 #define EXPECT_TRUE(expr, res) \ if (!(expr)) { \ printf("Test failure in %s line %u: %s\n", __FUNCTION__, \ __LINE__, #expr); \ (res) = TEST_FAILED; \ } typedef struct testcase_t__ testcase_t; typedef int (*test_setup_func)(testcase_t *); typedef int (*test_run_func)(testcase_t *); typedef int (*test_cleanup_func)(testcase_t *); struct testcase_t__ { const char *desc; void *test_data; void *verify_data; void *tmp_data; test_setup_func setup; test_run_func run; test_cleanup_func cleanup; }; /* need these to link in libbgp */ struct thread_master *master = NULL; struct zclient *zclient; struct zebra_privs_t bgpd_privs = { .user = NULL, .group = NULL, .vty_group = NULL, }; static int tty = 0; /* Create fake bgp instance */ static struct bgp *bgp_create_fake(as_t *as, const char *name) { struct bgp *bgp; afi_t afi; safi_t safi; if ((bgp = XCALLOC(MTYPE_BGP, sizeof(struct bgp))) == NULL) return NULL; bgp_lock(bgp); // bgp->peer_self = peer_new (bgp); // bgp->peer_self->host = XSTRDUP (MTYPE_BGP_PEER_HOST, "Static // announcement"); bgp->peer = list_new(); // bgp->peer->cmp = (int (*)(void *, void *)) peer_cmp; bgp->group = list_new(); // bgp->group->cmp = (int (*)(void *, void *)) peer_group_cmp; bgp_evpn_init(bgp); for (afi = AFI_IP; afi < AFI_MAX; afi++) for (safi = SAFI_UNICAST; safi < SAFI_MAX; safi++) { bgp->route[afi][safi] = bgp_table_init(bgp, afi, safi); bgp->aggregate[afi][safi] = bgp_table_init( bgp, afi, safi); bgp->rib[afi][safi] = bgp_table_init(bgp, afi, safi); bgp->maxpaths[afi][safi].maxpaths_ebgp = MULTIPATH_NUM; bgp->maxpaths[afi][safi].maxpaths_ibgp = MULTIPATH_NUM; } bgp_scan_init(bgp); bgp->default_local_pref = BGP_DEFAULT_LOCAL_PREF; bgp->default_holdtime = BGP_DEFAULT_HOLDTIME; bgp->default_keepalive = BGP_DEFAULT_KEEPALIVE; bgp->restart_time = BGP_DEFAULT_RESTART_TIME; bgp->stalepath_time = BGP_DEFAULT_STALEPATH_TIME; bgp->as = *as; if (name) bgp->name = strdup(name); return bgp; } /*========================================================= * Testcase for maximum-paths configuration */ static int setup_bgp_cfg_maximum_paths(testcase_t *t) { as_t asn = 1; t->tmp_data = bgp_create_fake(&asn, NULL); if (!t->tmp_data) return -1; return 0; } static int run_bgp_cfg_maximum_paths(testcase_t *t) { afi_t afi; safi_t safi; struct bgp *bgp; int api_result; int test_result = TEST_PASSED; bgp = t->tmp_data; for (afi = AFI_IP; afi < AFI_MAX; afi++) for (safi = SAFI_UNICAST; safi < SAFI_MAX; safi++) { /* test bgp_maximum_paths_set */ api_result = bgp_maximum_paths_set( bgp, afi, safi, BGP_PEER_EBGP, 10, 0); EXPECT_TRUE(api_result == 0, test_result); api_result = bgp_maximum_paths_set( bgp, afi, safi, BGP_PEER_IBGP, 10, 0); EXPECT_TRUE(api_result == 0, test_result); EXPECT_TRUE(bgp->maxpaths[afi][safi].maxpaths_ebgp == 10, test_result); EXPECT_TRUE(bgp->maxpaths[afi][safi].maxpaths_ibgp == 10, test_result); /* test bgp_maximum_paths_unset */ api_result = bgp_maximum_paths_unset(bgp, afi, safi, BGP_PEER_EBGP); EXPECT_TRUE(api_result == 0, test_result); api_result = bgp_maximum_paths_unset(bgp, afi, safi, BGP_PEER_IBGP); EXPECT_TRUE(api_result == 0, test_result); EXPECT_TRUE((bgp->maxpaths[afi][safi].maxpaths_ebgp == MULTIPATH_NUM), test_result); EXPECT_TRUE((bgp->maxpaths[afi][safi].maxpaths_ibgp == MULTIPATH_NUM), test_result); } return test_result; } static int cleanup_bgp_cfg_maximum_paths(testcase_t *t) { return bgp_delete((struct bgp *)t->tmp_data); } testcase_t test_bgp_cfg_maximum_paths = { .desc = "Test bgp maximum-paths config", .setup = setup_bgp_cfg_maximum_paths, .run = run_bgp_cfg_maximum_paths, .cleanup = cleanup_bgp_cfg_maximum_paths, }; /*========================================================= * Testcase for bgp_mp_list */ struct peer test_mp_list_peer[] = { {.local_as = 1, .as = 2}, {.local_as = 1, .as = 2}, {.local_as = 1, .as = 2}, {.local_as = 1, .as = 2}, {.local_as = 1, .as = 2}, }; int test_mp_list_peer_count = array_size(test_mp_list_peer); struct attr test_mp_list_attr[4]; struct bgp_path_info test_mp_list_info[] = { {.peer = &test_mp_list_peer[0], .attr = &test_mp_list_attr[0]}, {.peer = &test_mp_list_peer[1], .attr = &test_mp_list_attr[1]}, {.peer = &test_mp_list_peer[2], .attr = &test_mp_list_attr[1]}, {.peer = &test_mp_list_peer[3], .attr = &test_mp_list_attr[2]}, {.peer = &test_mp_list_peer[4], .attr = &test_mp_list_attr[3]}, }; int test_mp_list_info_count = array_size(test_mp_list_info); static int setup_bgp_mp_list(testcase_t *t) { test_mp_list_attr[0].nexthop.s_addr = 0x01010101; test_mp_list_attr[1].nexthop.s_addr = 0x02020202; test_mp_list_attr[2].nexthop.s_addr = 0x03030303; test_mp_list_attr[3].nexthop.s_addr = 0x04040404; if ((test_mp_list_peer[0].su_remote = sockunion_str2su("1.1.1.1")) == NULL) return -1; if ((test_mp_list_peer[1].su_remote = sockunion_str2su("2.2.2.2")) == NULL) return -1; if ((test_mp_list_peer[2].su_remote = sockunion_str2su("3.3.3.3")) == NULL) return -1; if ((test_mp_list_peer[3].su_remote = sockunion_str2su("4.4.4.4")) == NULL) return -1; if ((test_mp_list_peer[4].su_remote = sockunion_str2su("5.5.5.5")) == NULL) return -1; return 0; } static int run_bgp_mp_list(testcase_t *t) { struct list mp_list; struct listnode *mp_node; struct bgp_path_info *info; int i; int test_result = TEST_PASSED; bgp_mp_list_init(&mp_list); EXPECT_TRUE(listcount(&mp_list) == 0, test_result); bgp_mp_list_add(&mp_list, &test_mp_list_info[1]); bgp_mp_list_add(&mp_list, &test_mp_list_info[4]); bgp_mp_list_add(&mp_list, &test_mp_list_info[2]); bgp_mp_list_add(&mp_list, &test_mp_list_info[3]); bgp_mp_list_add(&mp_list, &test_mp_list_info[0]); for (i = 0, mp_node = mp_list.head; i < test_mp_list_info_count; i++, mp_node = listnextnode(mp_node)) { info = listgetdata(mp_node); EXPECT_TRUE(info == &test_mp_list_info[i], test_result); } bgp_mp_list_clear(&mp_list); EXPECT_TRUE(listcount(&mp_list) == 0, test_result); return test_result; } static int cleanup_bgp_mp_list(testcase_t *t) { int i; for (i = 0; i < test_mp_list_peer_count; i++) sockunion_free(test_mp_list_peer[i].su_remote); return 0; } testcase_t test_bgp_mp_list = { .desc = "Test bgp_mp_list", .setup = setup_bgp_mp_list, .run = run_bgp_mp_list, .cleanup = cleanup_bgp_mp_list, }; /*========================================================= * Testcase for bgp_path_info_mpath_update */ struct bgp_node test_rn; static int setup_bgp_path_info_mpath_update(testcase_t *t) { int i; str2prefix("42.1.1.0/24", &test_rn.p); setup_bgp_mp_list(t); for (i = 0; i < test_mp_list_info_count; i++) bgp_path_info_add(&test_rn, &test_mp_list_info[i]); return 0; } static int run_bgp_path_info_mpath_update(testcase_t *t) { struct bgp_path_info *new_best, *old_best, *mpath; struct list mp_list; struct bgp_maxpaths_cfg mp_cfg = {3, 3}; int test_result = TEST_PASSED; bgp_mp_list_init(&mp_list); bgp_mp_list_add(&mp_list, &test_mp_list_info[4]); bgp_mp_list_add(&mp_list, &test_mp_list_info[3]); bgp_mp_list_add(&mp_list, &test_mp_list_info[0]); bgp_mp_list_add(&mp_list, &test_mp_list_info[1]); new_best = &test_mp_list_info[3]; old_best = NULL; bgp_path_info_mpath_update(&test_rn, new_best, old_best, &mp_list, &mp_cfg); bgp_mp_list_clear(&mp_list); EXPECT_TRUE(bgp_path_info_mpath_count(new_best) == 2, test_result); mpath = bgp_path_info_mpath_first(new_best); EXPECT_TRUE(mpath == &test_mp_list_info[0], test_result); EXPECT_TRUE(CHECK_FLAG(mpath->flags, BGP_PATH_MULTIPATH), test_result); mpath = bgp_path_info_mpath_next(mpath); EXPECT_TRUE(mpath == &test_mp_list_info[1], test_result); EXPECT_TRUE(CHECK_FLAG(mpath->flags, BGP_PATH_MULTIPATH), test_result); bgp_mp_list_add(&mp_list, &test_mp_list_info[0]); bgp_mp_list_add(&mp_list, &test_mp_list_info[1]); new_best = &test_mp_list_info[0]; old_best = &test_mp_list_info[3]; bgp_path_info_mpath_update(&test_rn, new_best, old_best, &mp_list, &mp_cfg); bgp_mp_list_clear(&mp_list); EXPECT_TRUE(bgp_path_info_mpath_count(new_best) == 1, test_result); mpath = bgp_path_info_mpath_first(new_best); EXPECT_TRUE(mpath == &test_mp_list_info[1], test_result); EXPECT_TRUE(CHECK_FLAG(mpath->flags, BGP_PATH_MULTIPATH), test_result); EXPECT_TRUE(!CHECK_FLAG(test_mp_list_info[0].flags, BGP_PATH_MULTIPATH), test_result); return test_result; } static int cleanup_bgp_path_info_mpath_update(testcase_t *t) { int i; for (i = 0; i < test_mp_list_peer_count; i++) sockunion_free(test_mp_list_peer[i].su_remote); return 0; } testcase_t test_bgp_path_info_mpath_update = { .desc = "Test bgp_path_info_mpath_update", .setup = setup_bgp_path_info_mpath_update, .run = run_bgp_path_info_mpath_update, .cleanup = cleanup_bgp_path_info_mpath_update, }; /*========================================================= * Set up testcase vector */ testcase_t *all_tests[] = { &test_bgp_cfg_maximum_paths, &test_bgp_mp_list, &test_bgp_path_info_mpath_update, }; int all_tests_count = array_size(all_tests); /*========================================================= * Test Driver Functions */ static int global_test_init(void) { qobj_init(); master = thread_master_create(NULL); zclient = zclient_new(master, &zclient_options_default); bgp_master_init(master); vrf_init(NULL, NULL, NULL, NULL, NULL); bgp_option_set(BGP_OPT_NO_LISTEN); if (fileno(stdout) >= 0) tty = isatty(fileno(stdout)); return 0; } static int global_test_cleanup(void) { if (zclient != NULL) zclient_free(zclient); thread_master_free(master); return 0; } static void display_result(testcase_t *test, int result) { if (tty) printf("%s: %s\n", test->desc, result == TEST_PASSED ? OK : FAILED); else printf("%s: %s\n", test->desc, result == TEST_PASSED ? "OK" : "FAILED"); } static int setup_test(testcase_t *t) { int res = 0; if (t->setup) res = t->setup(t); return res; } static int cleanup_test(testcase_t *t) { int res = 0; if (t->cleanup) res = t->cleanup(t); return res; } static void run_tests(testcase_t *tests[], int num_tests, int *pass_count, int *fail_count) { int test_index, result; testcase_t *cur_test; *pass_count = *fail_count = 0; for (test_index = 0; test_index < num_tests; test_index++) { cur_test = tests[test_index]; if (!cur_test->desc) { printf("error: test %d has no description!\n", test_index); continue; } if (!cur_test->run) { printf("error: test %s has no run function!\n", cur_test->desc); continue; } if (setup_test(cur_test) != 0) { printf("error: setup failed for test %s\n", cur_test->desc); continue; } result = cur_test->run(cur_test); if (result == TEST_PASSED) *pass_count += 1; else *fail_count += 1; display_result(cur_test, result); if (cleanup_test(cur_test) != 0) { printf("error: cleanup failed for test %s\n", cur_test->desc); continue; } } } int main(void) { int pass_count, fail_count; time_t cur_time; time(&cur_time); printf("BGP Multipath Tests Run at %s", ctime(&cur_time)); if (global_test_init() != 0) { printf("Global init failed. Terminating.\n"); exit(1); } run_tests(all_tests, all_tests_count, &pass_count, &fail_count); global_test_cleanup(); printf("Total pass/fail: %d/%d\n", pass_count, fail_count); return fail_count; } frr-7.2.1/tests/bgpd/test_mpath.py0000644000000000000000000000032213610377563014011 00000000000000import frrtest class TestMpath(frrtest.TestMultiOut): program = './test_mpath' TestMpath.okfail("bgp maximum-paths config") TestMpath.okfail("bgp_mp_list") TestMpath.okfail("bgp_path_info_mpath_update") frr-7.2.1/tests/bgpd/test_packet.c0000644000000000000000000000425613610377563013753 00000000000000/* * Copyright (C) 2017 Cumulus Networks Inc. * Donald Sharp * * This file is part of FRR * * FRR is free software; you can redistribute it and/or modify it * under the terms of the GNU General Public License as published by the * Free Software Foundation; either version 2, or (at your option) any * later version. * * FRR 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; see the file COPYING; if not, write to the Free Software * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA */ #include #include "qobj.h" #include "vty.h" #include "stream.h" #include "privs.h" #include "memory.h" #include "queue.h" #include "filter.h" #include "bgpd/bgpd.h" #include "bgpd/bgp_open.h" #include "bgpd/bgp_debug.h" #include "bgpd/bgp_packet.h" #include "bgpd/bgp_aspath.h" /* need these to link in libbgp */ struct zebra_privs_t *bgpd_privs = NULL; struct thread_master *master = NULL; static struct bgp *bgp; static as_t asn = 100; extern int bgp_read_packet(struct peer *peer); /* * This file is intended to be used as input for some sort of * fuzzer. Specifically I had afl in mind when I wrote * this code. */ int main(int argc, char *argv[]) { struct peer *peer; int i, j; struct thread t; qobj_init(); bgp_attr_init(); master = thread_master_create(NULL); bgp_master_init(master); vrf_init(NULL, NULL, NULL, NULL, NULL); bgp_option_set(BGP_OPT_NO_LISTEN); if (bgp_get(&bgp, &asn, NULL, BGP_INSTANCE_TYPE_DEFAULT)) return -1; peer = peer_create_accept(bgp); peer->host = (char *)"foo"; for (i = AFI_IP; i < AFI_MAX; i++) for (j = SAFI_UNICAST; j < SAFI_MAX; j++) { peer->afc[i][j] = 1; peer->afc_adv[i][j] = 1; } SET_FLAG(peer->cap, PEER_CAP_DYNAMIC_ADV); peer->status = Established; peer->fd = open(argv[1], O_RDONLY|O_NONBLOCK); t.arg = peer; peer->t_read = &t; // printf("bgp_read_packet returns: %d\n", bgp_read(&t)); } frr-7.2.1/tests/bgpd/test_peer_attr.c0000644000000000000000000013014513610377563014466 00000000000000/* * BGP Peer Attribute Unit Tests * Copyright (C) 2018 Pascal Mathis * * This program is free software; you can redistribute it and/or modify it * under the terms of the 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; see the file COPYING; if not, write to the Free Software * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA */ #include #include "memory.h" #include "plist.h" #include "bgpd/bgpd.h" #include "bgpd/bgp_attr.h" #include "bgpd/bgp_regex.h" #include "bgpd/bgp_clist.h" #include "bgpd/bgp_dump.h" #include "bgpd/bgp_filter.h" #include "bgpd/bgp_route.h" #include "bgpd/bgp_vty.h" #include "bgpd/bgp_zebra.h" #ifdef ENABLE_BGP_VNC #include "bgpd/rfapi/rfapi_backend.h" #endif #define OUT_SYMBOL_INFO "\u25ba" #define OUT_SYMBOL_OK "\u2714" #define OUT_SYMBOL_NOK "\u2716" #define TEST_ASSERT(T, C) \ do { \ if ((T)->state != TEST_SUCCESS || (C)) \ break; \ (T)->state = TEST_ASSERT_ERROR; \ (T)->error = str_printf("assertion failed: %s (%s:%d)", (#C), \ __FILE__, __LINE__); \ } while (0) #define TEST_ASSERT_EQ(T, A, B) \ do { \ if ((T)->state != TEST_SUCCESS || ((A) == (B))) \ break; \ (T)->state = TEST_ASSERT_ERROR; \ (T)->error = str_printf( \ "assertion failed: %s[%d] == [%d]%s (%s:%d)", (#A), \ (A), (B), (#B), __FILE__, __LINE__); \ } while (0) #define TEST_HANDLER_MAX 5 #define TEST_HANDLER(name) _test_handler_##name #define TEST_HANDLER_DECL(name) \ static void _test_handler_##name( \ struct test *test, struct test_peer_attr *pa, \ struct peer *peer, struct peer *group, bool peer_set, \ bool group_set) #define TEST_ATTR_HANDLER_DECL(name, attr, pval, gval) \ TEST_HANDLER_DECL(name) \ { \ if (peer_set) \ TEST_ASSERT_EQ(test, peer->attr, (pval)); \ else if (peer_group_active(peer) && group_set) \ TEST_ASSERT_EQ(test, peer->attr, (gval)); \ if (group_set) \ TEST_ASSERT_EQ(test, group->attr, (gval)); \ } \ TEST_HANDLER_DECL(name) #define TEST_STR_ATTR_HANDLER_DECL(name, attr, pval, gval) \ TEST_HANDLER_DECL(name) \ { \ if (peer_set) { \ TEST_ASSERT(test, peer->attr != NULL); \ TEST_ASSERT(test, !strcmp(peer->attr, (pval))); \ } else if (peer_group_active(peer) && group_set) { \ TEST_ASSERT(test, peer->attr != NULL); \ TEST_ASSERT(test, !strcmp(peer->attr, (gval))); \ } \ if (group_set) { \ TEST_ASSERT(test, group->attr != NULL); \ TEST_ASSERT(test, !strcmp(group->attr, (gval))); \ } \ } \ TEST_HANDLER_DECL(name) #define TEST_SU_ATTR_HANDLER_DECL(name, attr, pval, gval) \ TEST_HANDLER_DECL(name) \ { \ union sockunion su; \ if (peer_set) { \ str2sockunion(pval, &su); \ TEST_ASSERT(test, !sockunion_cmp(peer->attr, &su)); \ } else if (peer_group_active(peer) && group_set) { \ str2sockunion(gval, &su); \ TEST_ASSERT(test, !sockunion_cmp(group->attr, &su)); \ } \ if (group_set) { \ str2sockunion(gval, &su); \ TEST_ASSERT(test, !sockunion_cmp(group->attr, &su)); \ } \ } \ TEST_HANDLER_DECL(name) /* Required variables to link in libbgp */ struct zebra_privs_t bgpd_privs = {0}; struct thread_master *master; enum test_state { TEST_SUCCESS, TEST_SKIPPING, TEST_COMMAND_ERROR, TEST_CONFIG_ERROR, TEST_ASSERT_ERROR, TEST_CUSTOM_ERROR, TEST_INTERNAL_ERROR, }; enum test_peer_attr_type { PEER_AT_AF_FLAG = 0, PEER_AT_AF_FILTER = 1, PEER_AT_AF_CUSTOM = 2, PEER_AT_GLOBAL_FLAG = 3, PEER_AT_GLOBAL_CUSTOM = 4 }; struct test { enum test_state state; char *desc; char *error; struct list *log; struct vty *vty; struct bgp *bgp; struct peer *peer; struct peer_group *group; struct { bool use_ibgp; bool use_iface_peer; } o; }; struct test_config { int local_asn; int peer_asn; const char *peer_address; const char *peer_interface; const char *peer_group; }; struct test_peer_family { afi_t afi; safi_t safi; }; struct test_peer_attr { const char *cmd; const char *peer_cmd; const char *group_cmd; enum test_peer_attr_type type; union { uint32_t flag; struct { uint32_t flag; size_t direct; } filter; } u; struct { bool invert_peer; bool invert_group; bool use_ibgp; bool use_iface_peer; bool skip_xfer_cases; } o; afi_t afi; safi_t safi; struct test_peer_family families[AFI_MAX * SAFI_MAX]; void (*handlers[TEST_HANDLER_MAX])(struct test *test, struct test_peer_attr *pa, struct peer *peer, struct peer *group, bool peer_set, bool group_set); }; static struct test_config cfg = { .local_asn = 100, .peer_asn = 200, .peer_address = "1.1.1.1", .peer_interface = "IP-TEST", .peer_group = "PG-TEST", }; static struct test_peer_family test_default_families[] = { {.afi = AFI_IP, .safi = SAFI_UNICAST}, {.afi = AFI_IP, .safi = SAFI_MULTICAST}, {.afi = AFI_IP6, .safi = SAFI_UNICAST}, {.afi = AFI_IP6, .safi = SAFI_MULTICAST}, }; static char *str_vprintf(const char *fmt, va_list ap) { int ret; int buf_size = 0; char *buf = NULL; va_list apc; while (1) { va_copy(apc, ap); ret = vsnprintf(buf, buf_size, fmt, apc); va_end(apc); if (ret >= 0 && ret < buf_size) break; if (ret >= 0) buf_size = ret + 1; else buf_size *= 2; buf = XREALLOC(MTYPE_TMP, buf, buf_size); } return buf; } static char *str_printf(const char *fmt, ...) { char *buf; va_list ap; va_start(ap, fmt); buf = str_vprintf(fmt, ap); va_end(ap); return buf; } TEST_ATTR_HANDLER_DECL(advertisement_interval, v_routeadv, 10, 20); TEST_STR_ATTR_HANDLER_DECL(password, password, "FRR-Peer", "FRR-Group"); TEST_ATTR_HANDLER_DECL(local_as, change_local_as, 1, 2); TEST_ATTR_HANDLER_DECL(timers_1, keepalive, 10, 20); TEST_ATTR_HANDLER_DECL(timers_2, holdtime, 30, 60); TEST_ATTR_HANDLER_DECL(addpath_types, addpath_type[pa->afi][pa->safi], BGP_ADDPATH_ALL, BGP_ADDPATH_BEST_PER_AS); TEST_SU_ATTR_HANDLER_DECL(update_source_su, update_source, "255.255.255.1", "255.255.255.2"); TEST_STR_ATTR_HANDLER_DECL(update_source_if, update_if, "IF-PEER", "IF-GROUP"); TEST_ATTR_HANDLER_DECL(allowas_in, allowas_in[pa->afi][pa->safi], 1, 2); TEST_STR_ATTR_HANDLER_DECL(default_originate_route_map, default_rmap[pa->afi][pa->safi].name, "RM-PEER", "RM-GROUP"); TEST_STR_ATTR_HANDLER_DECL( distribute_list, filter[pa->afi][pa->safi].dlist[pa->u.filter.direct].name, "DL-PEER", "DL-GROUP"); TEST_STR_ATTR_HANDLER_DECL( filter_list, filter[pa->afi][pa->safi].aslist[pa->u.filter.direct].name, "FL-PEER", "FL-GROUP"); TEST_ATTR_HANDLER_DECL(maximum_prefix, pmax[pa->afi][pa->safi], 10, 20); TEST_ATTR_HANDLER_DECL(maximum_prefix_threshold, pmax_threshold[pa->afi][pa->safi], 1, 2); TEST_ATTR_HANDLER_DECL(maximum_prefix_restart, pmax_restart[pa->afi][pa->safi], 100, 200); TEST_STR_ATTR_HANDLER_DECL( prefix_list, filter[pa->afi][pa->safi].plist[pa->u.filter.direct].name, "PL-PEER", "PL-GROUP"); TEST_STR_ATTR_HANDLER_DECL( route_map, filter[pa->afi][pa->safi].map[pa->u.filter.direct].name, "RM-PEER", "RM-GROUP"); TEST_STR_ATTR_HANDLER_DECL(unsuppress_map, filter[pa->afi][pa->safi].usmap.name, "UM-PEER", "UM-GROUP"); TEST_ATTR_HANDLER_DECL(weight, weight[pa->afi][pa->safi], 100, 200); /* clang-format off */ static struct test_peer_attr test_peer_attrs[] = { /* Peer Attributes */ { .cmd = "advertisement-interval", .peer_cmd = "advertisement-interval 10", .group_cmd = "advertisement-interval 20", .u.flag = PEER_FLAG_ROUTEADV, .type = PEER_AT_GLOBAL_FLAG, .handlers[0] = TEST_HANDLER(advertisement_interval), }, { .cmd = "capability dynamic", .u.flag = PEER_FLAG_DYNAMIC_CAPABILITY, .type = PEER_AT_GLOBAL_FLAG, }, { .cmd = "capability extended-nexthop", .u.flag = PEER_FLAG_CAPABILITY_ENHE, .type = PEER_AT_GLOBAL_FLAG, }, { .cmd = "capability extended-nexthop", .u.flag = PEER_FLAG_CAPABILITY_ENHE, .type = PEER_AT_GLOBAL_FLAG, .o.invert_peer = true, .o.use_iface_peer = true, }, { .cmd = "description", .peer_cmd = "description FRR Peer", .group_cmd = "description FRR Group", .type = PEER_AT_GLOBAL_CUSTOM, }, { .cmd = "disable-connected-check", .u.flag = PEER_FLAG_DISABLE_CONNECTED_CHECK, .type = PEER_AT_GLOBAL_FLAG, }, { .cmd = "dont-capability-negotiate", .u.flag = PEER_FLAG_DONT_CAPABILITY, .type = PEER_AT_GLOBAL_FLAG, }, { .cmd = "enforce-first-as", .u.flag = PEER_FLAG_ENFORCE_FIRST_AS, .type = PEER_AT_GLOBAL_FLAG, }, { .cmd = "local-as", .peer_cmd = "local-as 1", .group_cmd = "local-as 2", .u.flag = PEER_FLAG_LOCAL_AS, .type = PEER_AT_GLOBAL_FLAG, .handlers[0] = TEST_HANDLER(local_as), }, { .cmd = "local-as 1 no-prepend", .u.flag = PEER_FLAG_LOCAL_AS | PEER_FLAG_LOCAL_AS_NO_PREPEND, .type = PEER_AT_GLOBAL_FLAG, }, { .cmd = "local-as 1 no-prepend replace-as", .u.flag = PEER_FLAG_LOCAL_AS | PEER_FLAG_LOCAL_AS_REPLACE_AS, .type = PEER_AT_GLOBAL_FLAG, }, { .cmd = "override-capability", .u.flag = PEER_FLAG_OVERRIDE_CAPABILITY, .type = PEER_AT_GLOBAL_FLAG, }, { .cmd = "passive", .u.flag = PEER_FLAG_PASSIVE, .type = PEER_AT_GLOBAL_FLAG, }, { .cmd = "password", .peer_cmd = "password FRR-Peer", .group_cmd = "password FRR-Group", .u.flag = PEER_FLAG_PASSWORD, .type = PEER_AT_GLOBAL_FLAG, .handlers[0] = TEST_HANDLER(password), }, { .cmd = "shutdown", .u.flag = PEER_FLAG_SHUTDOWN, .type = PEER_AT_GLOBAL_FLAG, }, { .cmd = "strict-capability-match", .u.flag = PEER_FLAG_STRICT_CAP_MATCH, .type = PEER_AT_GLOBAL_FLAG, }, { .cmd = "timers", .peer_cmd = "timers 10 30", .group_cmd = "timers 20 60", .u.flag = PEER_FLAG_TIMER, .type = PEER_AT_GLOBAL_FLAG, .handlers[0] = TEST_HANDLER(timers_1), .handlers[1] = TEST_HANDLER(timers_2), }, { .cmd = "timers connect", .peer_cmd = "timers connect 10", .group_cmd = "timers connect 20", .u.flag = PEER_FLAG_TIMER_CONNECT, .type = PEER_AT_GLOBAL_FLAG, }, { .cmd = "update-source", .peer_cmd = "update-source 255.255.255.1", .group_cmd = "update-source 255.255.255.2", .u.flag = PEER_FLAG_UPDATE_SOURCE, .type = PEER_AT_GLOBAL_FLAG, .handlers[0] = TEST_HANDLER(update_source_su), }, { .cmd = "update-source", .peer_cmd = "update-source IF-PEER", .group_cmd = "update-source IF-GROUP", .u.flag = PEER_FLAG_UPDATE_SOURCE, .type = PEER_AT_GLOBAL_FLAG, .handlers[0] = TEST_HANDLER(update_source_if), }, /* Address Family Attributes */ { .cmd = "addpath", .peer_cmd = "addpath-tx-all-paths", .group_cmd = "addpath-tx-bestpath-per-AS", .type = PEER_AT_AF_CUSTOM, .handlers[0] = TEST_HANDLER(addpath_types), }, { .cmd = "allowas-in", .peer_cmd = "allowas-in 1", .group_cmd = "allowas-in 2", .u.flag = PEER_FLAG_ALLOWAS_IN, .handlers[0] = TEST_HANDLER(allowas_in), }, { .cmd = "allowas-in origin", .u.flag = PEER_FLAG_ALLOWAS_IN_ORIGIN, }, { .cmd = "as-override", .u.flag = PEER_FLAG_AS_OVERRIDE, }, { .cmd = "attribute-unchanged as-path", .u.flag = PEER_FLAG_AS_PATH_UNCHANGED, }, { .cmd = "attribute-unchanged next-hop", .u.flag = PEER_FLAG_NEXTHOP_UNCHANGED, }, { .cmd = "attribute-unchanged med", .u.flag = PEER_FLAG_MED_UNCHANGED, }, { .cmd = "attribute-unchanged as-path next-hop", .u.flag = PEER_FLAG_AS_PATH_UNCHANGED | PEER_FLAG_NEXTHOP_UNCHANGED, }, { .cmd = "attribute-unchanged as-path med", .u.flag = PEER_FLAG_AS_PATH_UNCHANGED | PEER_FLAG_MED_UNCHANGED, }, { .cmd = "attribute-unchanged as-path next-hop med", .u.flag = PEER_FLAG_AS_PATH_UNCHANGED | PEER_FLAG_NEXTHOP_UNCHANGED | PEER_FLAG_MED_UNCHANGED, }, { .cmd = "capability orf prefix-list send", .u.flag = PEER_FLAG_ORF_PREFIX_SM, }, { .cmd = "capability orf prefix-list receive", .u.flag = PEER_FLAG_ORF_PREFIX_RM, }, { .cmd = "capability orf prefix-list both", .u.flag = PEER_FLAG_ORF_PREFIX_SM | PEER_FLAG_ORF_PREFIX_RM, }, { .cmd = "default-originate", .u.flag = PEER_FLAG_DEFAULT_ORIGINATE, }, { .cmd = "default-originate route-map", .peer_cmd = "default-originate route-map RM-PEER", .group_cmd = "default-originate route-map RM-GROUP", .u.flag = PEER_FLAG_DEFAULT_ORIGINATE, .handlers[0] = TEST_HANDLER(default_originate_route_map), }, { .cmd = "distribute-list", .peer_cmd = "distribute-list DL-PEER in", .group_cmd = "distribute-list DL-GROUP in", .type = PEER_AT_AF_FILTER, .u.filter.flag = PEER_FT_DISTRIBUTE_LIST, .u.filter.direct = FILTER_IN, .handlers[0] = TEST_HANDLER(distribute_list), }, { .cmd = "distribute-list", .peer_cmd = "distribute-list DL-PEER out", .group_cmd = "distribute-list DL-GROUP out", .type = PEER_AT_AF_FILTER, .u.filter.flag = PEER_FT_DISTRIBUTE_LIST, .u.filter.direct = FILTER_OUT, .handlers[0] = TEST_HANDLER(distribute_list), }, { .cmd = "filter-list", .peer_cmd = "filter-list FL-PEER in", .group_cmd = "filter-list FL-GROUP in", .type = PEER_AT_AF_FILTER, .u.filter.flag = PEER_FT_FILTER_LIST, .u.filter.direct = FILTER_IN, .handlers[0] = TEST_HANDLER(filter_list), }, { .cmd = "filter-list", .peer_cmd = "filter-list FL-PEER out", .group_cmd = "filter-list FL-GROUP out", .type = PEER_AT_AF_FILTER, .u.filter.flag = PEER_FT_FILTER_LIST, .u.filter.direct = FILTER_OUT, .handlers[0] = TEST_HANDLER(filter_list), }, { .cmd = "maximum-prefix", .peer_cmd = "maximum-prefix 10", .group_cmd = "maximum-prefix 20", .u.flag = PEER_FLAG_MAX_PREFIX, .handlers[0] = TEST_HANDLER(maximum_prefix), }, { .cmd = "maximum-prefix", .peer_cmd = "maximum-prefix 10 restart 100", .group_cmd = "maximum-prefix 20 restart 200", .u.flag = PEER_FLAG_MAX_PREFIX, .handlers[0] = TEST_HANDLER(maximum_prefix), .handlers[1] = TEST_HANDLER(maximum_prefix_restart), }, { .cmd = "maximum-prefix", .peer_cmd = "maximum-prefix 10 1 restart 100", .group_cmd = "maximum-prefix 20 2 restart 200", .u.flag = PEER_FLAG_MAX_PREFIX, .handlers[0] = TEST_HANDLER(maximum_prefix), .handlers[1] = TEST_HANDLER(maximum_prefix_threshold), .handlers[2] = TEST_HANDLER(maximum_prefix_restart), }, { .cmd = "maximum-prefix", .peer_cmd = "maximum-prefix 10 warning-only", .group_cmd = "maximum-prefix 20 warning-only", .u.flag = PEER_FLAG_MAX_PREFIX | PEER_FLAG_MAX_PREFIX_WARNING, .handlers[0] = TEST_HANDLER(maximum_prefix), }, { .cmd = "maximum-prefix", .peer_cmd = "maximum-prefix 10 1 warning-only", .group_cmd = "maximum-prefix 20 2 warning-only", .u.flag = PEER_FLAG_MAX_PREFIX | PEER_FLAG_MAX_PREFIX_WARNING, .handlers[0] = TEST_HANDLER(maximum_prefix), .handlers[1] = TEST_HANDLER(maximum_prefix_threshold), }, { .cmd = "next-hop-self", .u.flag = PEER_FLAG_NEXTHOP_SELF, }, { .cmd = "next-hop-self force", .u.flag = PEER_FLAG_FORCE_NEXTHOP_SELF, }, { .cmd = "prefix-list", .peer_cmd = "prefix-list PL-PEER in", .group_cmd = "prefix-list PL-GROUP in", .type = PEER_AT_AF_FILTER, .u.filter.flag = PEER_FT_PREFIX_LIST, .u.filter.direct = FILTER_IN, .handlers[0] = TEST_HANDLER(prefix_list), }, { .cmd = "prefix-list", .peer_cmd = "prefix-list PL-PEER out", .group_cmd = "prefix-list PL-GROUP out", .type = PEER_AT_AF_FILTER, .u.filter.flag = PEER_FT_PREFIX_LIST, .u.filter.direct = FILTER_OUT, .handlers[0] = TEST_HANDLER(prefix_list), }, { .cmd = "remove-private-AS", .u.flag = PEER_FLAG_REMOVE_PRIVATE_AS, }, { .cmd = "remove-private-AS all", .u.flag = PEER_FLAG_REMOVE_PRIVATE_AS | PEER_FLAG_REMOVE_PRIVATE_AS_ALL, }, { .cmd = "remove-private-AS replace-AS", .u.flag = PEER_FLAG_REMOVE_PRIVATE_AS | PEER_FLAG_REMOVE_PRIVATE_AS_REPLACE, }, { .cmd = "remove-private-AS all replace-AS", .u.flag = PEER_FLAG_REMOVE_PRIVATE_AS_ALL_REPLACE, }, { .cmd = "route-map", .peer_cmd = "route-map RM-PEER in", .group_cmd = "route-map RM-GROUP in", .type = PEER_AT_AF_FILTER, .u.filter.flag = PEER_FT_ROUTE_MAP, .u.filter.direct = FILTER_IN, .handlers[0] = TEST_HANDLER(route_map), }, { .cmd = "route-map", .peer_cmd = "route-map RM-PEER out", .group_cmd = "route-map RM-GROUP out", .type = PEER_AT_AF_FILTER, .u.filter.flag = PEER_FT_ROUTE_MAP, .u.filter.direct = FILTER_OUT, .handlers[0] = TEST_HANDLER(route_map), }, { .cmd = "route-reflector-client", .u.flag = PEER_FLAG_REFLECTOR_CLIENT, .o.use_ibgp = true, .o.skip_xfer_cases = true, }, { .cmd = "route-server-client", .u.flag = PEER_FLAG_RSERVER_CLIENT, }, { .cmd = "send-community", .u.flag = PEER_FLAG_SEND_COMMUNITY, .o.invert_peer = true, .o.invert_group = true, }, { .cmd = "send-community extended", .u.flag = PEER_FLAG_SEND_EXT_COMMUNITY, .o.invert_peer = true, .o.invert_group = true, }, { .cmd = "send-community large", .u.flag = PEER_FLAG_SEND_LARGE_COMMUNITY, .o.invert_peer = true, .o.invert_group = true, }, { .cmd = "soft-reconfiguration inbound", .u.flag = PEER_FLAG_SOFT_RECONFIG, }, { .cmd = "unsuppress-map", .peer_cmd = "unsuppress-map UM-PEER", .group_cmd = "unsuppress-map UM-GROUP", .type = PEER_AT_AF_FILTER, .u.filter.flag = PEER_FT_UNSUPPRESS_MAP, .u.filter.direct = 0, .handlers[0] = TEST_HANDLER(unsuppress_map), }, { .cmd = "weight", .peer_cmd = "weight 100", .group_cmd = "weight 200", .u.flag = PEER_FLAG_WEIGHT, .handlers[0] = TEST_HANDLER(weight), }, {NULL} }; /* clang-format on */ static const char *str_from_afi(afi_t afi) { switch (afi) { case AFI_IP: return "ipv4"; case AFI_IP6: return "ipv6"; default: return ""; } } static const char *str_from_safi(safi_t safi) { switch (safi) { case SAFI_UNICAST: return "unicast"; case SAFI_MULTICAST: return "multicast"; default: return ""; } } static const char *str_from_attr_type(enum test_peer_attr_type at) { switch (at) { case PEER_AT_GLOBAL_FLAG: return "peer-flag"; case PEER_AT_AF_FLAG: return "af-flag"; case PEER_AT_AF_FILTER: return "af-filter"; case PEER_AT_GLOBAL_CUSTOM: case PEER_AT_AF_CUSTOM: return "custom"; default: return NULL; } } static bool is_attr_type_global(enum test_peer_attr_type at) { return at == PEER_AT_GLOBAL_FLAG || at == PEER_AT_GLOBAL_CUSTOM; } static void test_log(struct test *test, const char *fmt, ...) { va_list ap; /* Skip logging if test instance has previously failed. */ if (test->state != TEST_SUCCESS) return; /* Store formatted log message. */ va_start(ap, fmt); listnode_add(test->log, str_vprintf(fmt, ap)); va_end(ap); } static void test_execute(struct test *test, const char *fmt, ...) { int ret; char *cmd; va_list ap; vector vline; /* Skip execution if test instance has previously failed. */ if (test->state != TEST_SUCCESS) return; /* Format command string with variadic arguments. */ va_start(ap, fmt); cmd = str_vprintf(fmt, ap); va_end(ap); if (!cmd) { test->state = TEST_INTERNAL_ERROR; test->error = str_printf("could not format command string [%s]", fmt); return; } /* Tokenize formatted command string. */ vline = cmd_make_strvec(cmd); if (vline == NULL) { test->state = TEST_INTERNAL_ERROR; test->error = str_printf( "tokenizing command string [%s] returned empty result", cmd); XFREE(MTYPE_TMP, cmd); return; } /* Execute command (non-strict). */ ret = cmd_execute_command(vline, test->vty, NULL, 0); if (ret != CMD_SUCCESS) { test->state = TEST_COMMAND_ERROR; test->error = str_printf( "execution of command [%s] has failed with code [%d]", cmd, ret); } /* Free memory. */ cmd_free_strvec(vline); XFREE(MTYPE_TMP, cmd); } static void test_config(struct test *test, const char *fmt, bool invert, va_list ap) { char *matcher; char *config; bool matched; va_list apc; /* Skip execution if test instance has previously failed. */ if (test->state != TEST_SUCCESS) return; /* Format matcher string with variadic arguments. */ va_copy(apc, ap); matcher = str_vprintf(fmt, apc); va_end(apc); if (!matcher) { test->state = TEST_INTERNAL_ERROR; test->error = str_printf("could not format matcher string [%s]", fmt); return; } /* Fetch BGP configuration into buffer. */ bgp_config_write(test->vty); config = buffer_getstr(test->vty->obuf); buffer_reset(test->vty->obuf); /* Match config against matcher. */ matched = !!strstr(config, matcher); if (!matched && !invert) { test->state = TEST_CONFIG_ERROR; test->error = str_printf("expected config [%s] to be present", matcher); } else if (matched && invert) { test->state = TEST_CONFIG_ERROR; test->error = str_printf("expected config [%s] to be absent", matcher); } /* Free memory and return. */ XFREE(MTYPE_TMP, matcher); XFREE(MTYPE_TMP, config); } static void test_config_present(struct test *test, const char *fmt, ...) { va_list ap; va_start(ap, fmt); test_config(test, fmt, false, ap); va_end(ap); } static void test_config_absent(struct test *test, const char *fmt, ...) { va_list ap; va_start(ap, fmt); test_config(test, fmt, true, ap); va_end(ap); } static void test_initialize(struct test *test) { union sockunion su; /* Skip execution if test instance has previously failed. */ if (test->state != TEST_SUCCESS) return; /* Log message about (re)-initialization */ test_log(test, "prepare: %sinitialize bgp test environment", test->bgp ? "re-" : ""); /* Attempt gracefully to purge previous BGP configuration. */ test_execute(test, "no router bgp"); test->state = TEST_SUCCESS; /* Initialize BGP test environment. */ test_execute(test, "router bgp %d", cfg.local_asn); test_execute(test, "no bgp default ipv4-unicast"); test_execute(test, "neighbor %s peer-group", cfg.peer_group); if (test->o.use_iface_peer) { test_execute(test, "neighbor %s interface", cfg.peer_interface); test_execute(test, "neighbor %s remote-as %d", cfg.peer_interface, test->o.use_ibgp ? cfg.local_asn : cfg.peer_asn); } else { test_execute(test, "neighbor %s remote-as %d", cfg.peer_address, test->o.use_ibgp ? cfg.local_asn : cfg.peer_asn); } if (test->state != TEST_SUCCESS) return; /* Fetch default BGP instance. */ test->bgp = bgp_get_default(); if (!test->bgp) { test->state = TEST_INTERNAL_ERROR; test->error = str_printf("could not retrieve default bgp instance"); return; } /* Fetch peer instance. */ if (test->o.use_iface_peer) { test->peer = peer_lookup_by_conf_if(test->bgp, cfg.peer_interface); } else { str2sockunion(cfg.peer_address, &su); test->peer = peer_lookup(test->bgp, &su); } if (!test->peer) { test->state = TEST_INTERNAL_ERROR; test->error = str_printf( "could not retrieve instance of bgp peer [%s]", cfg.peer_address); return; } /* Fetch peer-group instance. */ test->group = peer_group_lookup(test->bgp, cfg.peer_group); if (!test->group) { test->state = TEST_INTERNAL_ERROR; test->error = str_printf( "could not retrieve instance of bgp peer-group [%s]", cfg.peer_group); return; } } static struct test *test_new(const char *desc, bool use_ibgp, bool use_iface_peer) { struct test *test; test = XCALLOC(MTYPE_TMP, sizeof(struct test)); test->state = TEST_SUCCESS; test->desc = XSTRDUP(MTYPE_TMP, desc); test->log = list_new(); test->o.use_ibgp = use_ibgp; test->o.use_iface_peer = use_iface_peer; test->vty = vty_new(); test->vty->type = VTY_TERM; test->vty->node = CONFIG_NODE; test_initialize(test); return test; }; static void test_finish(struct test *test) { char *msg; struct listnode *node, *nnode; /* Print test output header. */ printf("%s [test] %s\n", (test->state == TEST_SUCCESS) ? OUT_SYMBOL_OK : OUT_SYMBOL_NOK, test->desc); /* Print test log messages. */ for (ALL_LIST_ELEMENTS(test->log, node, nnode, msg)) { printf("%s %s\n", OUT_SYMBOL_INFO, msg); XFREE(MTYPE_TMP, msg); } /* Print test error message if available. */ if (test->state != TEST_SUCCESS && test->error) printf("%s error: %s\n", OUT_SYMBOL_INFO, test->error); /* Print machine-readable result of test. */ printf("%s\n", test->state == TEST_SUCCESS ? "OK" : "failed"); /* Cleanup allocated memory. */ if (test->vty) { vty_close(test->vty); test->vty = NULL; } if (test->log) list_delete(&test->log); if (test->desc) XFREE(MTYPE_TMP, test->desc); if (test->error) XFREE(MTYPE_TMP, test->error); XFREE(MTYPE_TMP, test); } static void test_peer_flags(struct test *test, struct test_peer_attr *pa, struct peer *peer, bool exp_val, bool exp_ovrd) { bool exp_inv, cur_val, cur_ovrd, cur_inv; /* Skip execution if test instance has previously failed. */ if (test->state != TEST_SUCCESS) return; /* Detect if flag is meant to be inverted. */ if (CHECK_FLAG(peer->sflags, PEER_STATUS_GROUP)) exp_inv = pa->o.invert_group; else exp_inv = pa->o.invert_peer; /* Flip expected value if flag is inverted. */ exp_val ^= exp_inv; /* Fetch current state of value, override and invert flags. */ if (pa->type == PEER_AT_GLOBAL_FLAG) { cur_val = !!CHECK_FLAG(peer->flags, pa->u.flag); cur_ovrd = !!CHECK_FLAG(peer->flags_override, pa->u.flag); cur_inv = !!CHECK_FLAG(peer->flags_invert, pa->u.flag); } else /* if (pa->type == PEER_AT_AF_FLAG) */ { cur_val = !!CHECK_FLAG(peer->af_flags[pa->afi][pa->safi], pa->u.flag); cur_ovrd = !!CHECK_FLAG( peer->af_flags_override[pa->afi][pa->safi], pa->u.flag); cur_inv = !!CHECK_FLAG(peer->af_flags_invert[pa->afi][pa->safi], pa->u.flag); } /* Assert expected flag states. */ TEST_ASSERT_EQ(test, cur_val, exp_val); TEST_ASSERT_EQ(test, cur_ovrd, exp_ovrd); TEST_ASSERT_EQ(test, cur_inv, exp_inv); } static void test_af_filter(struct test *test, struct test_peer_attr *pa, struct peer *peer, bool exp_state, bool exp_ovrd) { bool cur_ovrd; struct bgp_filter *filter; /* Skip execution if test instance has previously failed. */ if (test->state != TEST_SUCCESS) return; /* Fetch and assert current state of override flag. */ cur_ovrd = !!CHECK_FLAG( peer->filter_override[pa->afi][pa->safi][pa->u.filter.direct], pa->u.filter.flag); TEST_ASSERT_EQ(test, cur_ovrd, exp_ovrd); /* Assert that map/list matches expected state (set/unset). */ filter = &peer->filter[pa->afi][pa->safi]; switch (pa->u.filter.flag) { case PEER_FT_DISTRIBUTE_LIST: TEST_ASSERT_EQ(test, !!(filter->dlist[pa->u.filter.direct].name), exp_state); break; case PEER_FT_FILTER_LIST: TEST_ASSERT_EQ(test, !!(filter->aslist[pa->u.filter.direct].name), exp_state); break; case PEER_FT_PREFIX_LIST: TEST_ASSERT_EQ(test, !!(filter->plist[pa->u.filter.direct].name), exp_state); break; case PEER_FT_ROUTE_MAP: TEST_ASSERT_EQ(test, !!(filter->map[pa->u.filter.direct].name), exp_state); break; case PEER_FT_UNSUPPRESS_MAP: TEST_ASSERT_EQ(test, !!(filter->usmap.name), exp_state); break; } } static void test_custom(struct test *test, struct test_peer_attr *pa, struct peer *peer, struct peer *group, bool peer_set, bool group_set) { int i; char *handler_error; for (i = 0; i < TEST_HANDLER_MAX; i++) { /* Skip execution if test instance has previously failed. */ if (test->state != TEST_SUCCESS) return; /* Skip further execution if handler is undefined. */ if (!pa->handlers[i]) return; /* Execute custom handler. */ pa->handlers[i](test, pa, peer, group, peer_set, group_set); if (test->state != TEST_SUCCESS) { test->state = TEST_CUSTOM_ERROR; handler_error = test->error; test->error = str_printf("custom handler failed: %s", handler_error); XFREE(MTYPE_TMP, handler_error); } } } static void test_process(struct test *test, struct test_peer_attr *pa, struct peer *peer, struct peer *group, bool peer_set, bool group_set) { switch (pa->type) { case PEER_AT_GLOBAL_FLAG: case PEER_AT_AF_FLAG: test_peer_flags( test, pa, peer, peer_set || (peer_group_active(peer) && group_set), peer_set); test_peer_flags(test, pa, group, group_set, false); break; case PEER_AT_AF_FILTER: test_af_filter( test, pa, peer, peer_set || (peer_group_active(peer) && group_set), peer_set); test_af_filter(test, pa, group, group_set, false); break; case PEER_AT_GLOBAL_CUSTOM: case PEER_AT_AF_CUSTOM: /* * Do nothing here - a custom handler can be executed, but this * is not required. This will allow defining peer attributes * which shall not be checked for flag/filter/other internal * states. */ break; default: test->state = TEST_INTERNAL_ERROR; test->error = str_printf("invalid attribute type: %d", pa->type); break; } /* Attempt to call a custom handler if set for further processing. */ test_custom(test, pa, peer, group, peer_set, group_set); } static void test_peer_attr(struct test *test, struct test_peer_attr *pa) { int tc = 1; const char *type; const char *ecp = pa->o.invert_peer ? "no " : ""; const char *dcp = pa->o.invert_peer ? "" : "no "; const char *ecg = pa->o.invert_group ? "no " : ""; const char *dcg = pa->o.invert_group ? "" : "no "; const char *peer_cmd = pa->peer_cmd ?: pa->cmd; const char *group_cmd = pa->group_cmd ?: pa->cmd; struct peer *p = test->peer; struct peer_group *g = test->group; /* Determine type and if test is address-family relevant */ type = str_from_attr_type(pa->type); if (!type) { test->state = TEST_INTERNAL_ERROR; test->error = str_printf("invalid attribute type: %d", pa->type); return; } /* * ===================================================================== * Test Case Suite 1: Config persistence after adding peer to group * * Example: If a peer attribute has value [1] and a group attribute has * value [2], the peer attribute value should be persisted when the peer * gets added to the peer-group. * * This test suite is meant to test the group2peer functions which can * be found inside bgpd/bgpd.c, which are related to initial peer-group * inheritance. * ===================================================================== */ /* Test Preparation: Switch and activate address-family. */ if (!is_attr_type_global(pa->type)) { test_log(test, "prepare: switch address-family to [%s]", get_afi_safi_str(pa->afi, pa->safi, false)); test_execute(test, "address-family %s %s", str_from_afi(pa->afi), str_from_safi(pa->safi)); test_execute(test, "neighbor %s activate", g->name); test_execute(test, "neighbor %s activate", p->host); } /* Skip peer-group to peer transfer test cases if requested. */ if (pa->o.skip_xfer_cases && test->state == TEST_SUCCESS) test->state = TEST_SKIPPING; /* Test Case: Set flag on BGP peer. */ test_log(test, "case %02d: set %s [%s] on [%s]", tc++, type, peer_cmd, p->host); test_execute(test, "%sneighbor %s %s", ecp, p->host, peer_cmd); test_config_present(test, "%sneighbor %s %s", ecp, p->host, peer_cmd); test_config_absent(test, "neighbor %s %s", g->name, pa->cmd); test_process(test, pa, p, g->conf, true, false); /* Test Case: Set flag on BGP peer-group. */ test_log(test, "case %02d: set %s [%s] on [%s]", tc++, type, group_cmd, g->name); test_execute(test, "%sneighbor %s %s", ecg, g->name, group_cmd); test_config_present(test, "%sneighbor %s %s", ecp, p->host, peer_cmd); test_config_present(test, "%sneighbor %s %s", ecg, g->name, group_cmd); test_process(test, pa, p, g->conf, true, true); /* Test Case: Add BGP peer to peer-group. */ test_log(test, "case %02d: add peer [%s] to group [%s]", tc++, p->host, g->name); test_execute(test, "neighbor %s peer-group %s", p->host, g->name); test_config_present(test, "neighbor %s %speer-group %s", p->host, p->conf_if ? "interface " : "", g->name); test_config_present(test, "%sneighbor %s %s", ecp, p->host, peer_cmd); test_config_present(test, "%sneighbor %s %s", ecg, g->name, group_cmd); test_process(test, pa, p, g->conf, true, true); /* Test Case: Unset flag on BGP peer-group. */ test_log(test, "case %02d: unset %s [%s] on [%s]", tc++, type, group_cmd, g->name); test_execute(test, "%sneighbor %s %s", dcg, g->name, group_cmd); test_config_present(test, "%sneighbor %s %s", ecp, p->host, peer_cmd); test_config_absent(test, "neighbor %s %s", g->name, pa->cmd); test_process(test, pa, p, g->conf, true, false); /* * ===================================================================== * Test Case Suite 2: Config inheritance after adding peer to group * * Example: If a peer attribute has not been set and a group attribute * has a value of [2], the group attribute should be inherited to the * peer without flagging the newly set value as overridden. * * This test suite is meant to test the group2peer functions which can * be found inside bgpd/bgpd.c, which are related to initial peer-group * inheritance. * ===================================================================== */ /* Test Preparation: Re-initialize test environment. */ test_initialize(test); p = test->peer; g = test->group; /* Test Preparation: Switch and activate address-family. */ if (!is_attr_type_global(pa->type)) { test_log(test, "prepare: switch address-family to [%s]", get_afi_safi_str(pa->afi, pa->safi, false)); test_execute(test, "address-family %s %s", str_from_afi(pa->afi), str_from_safi(pa->safi)); test_execute(test, "neighbor %s activate", g->name); test_execute(test, "neighbor %s activate", p->host); } /* Test Case: Set flag on BGP peer-group. */ test_log(test, "case %02d: set %s [%s] on [%s]", tc++, type, group_cmd, g->name); test_execute(test, "%sneighbor %s %s", ecg, g->name, group_cmd); test_config_absent(test, "neighbor %s %s", p->host, pa->cmd); test_config_present(test, "%sneighbor %s %s", ecg, g->name, group_cmd); test_process(test, pa, p, g->conf, false, true); /* Test Case: Add BGP peer to peer-group. */ test_log(test, "case %02d: add peer [%s] to group [%s]", tc++, p->host, g->name); test_execute(test, "neighbor %s peer-group %s", p->host, g->name); test_config_present(test, "neighbor %s %speer-group %s", p->host, p->conf_if ? "interface " : "", g->name); test_config_absent(test, "neighbor %s %s", p->host, pa->cmd); test_config_present(test, "%sneighbor %s %s", ecg, g->name, group_cmd); test_process(test, pa, p, g->conf, false, true); /* Stop skipping test cases if previously enabled. */ if (pa->o.skip_xfer_cases && test->state == TEST_SKIPPING) test->state = TEST_SUCCESS; /* * ===================================================================== * Test Case Suite 3: Miscellaneous flag checks * * This test suite does not focus on initial peer-group inheritance and * instead executes various different commands to set/unset attributes * on both peer- and group-level. These checks should always be executed * and must pass. * ===================================================================== */ /* Test Preparation: Re-initialize test environment. */ test_initialize(test); p = test->peer; g = test->group; /* Test Preparation: Switch and activate address-family. */ if (!is_attr_type_global(pa->type)) { test_log(test, "prepare: switch address-family to [%s]", get_afi_safi_str(pa->afi, pa->safi, false)); test_execute(test, "address-family %s %s", str_from_afi(pa->afi), str_from_safi(pa->safi)); test_execute(test, "neighbor %s activate", g->name); test_execute(test, "neighbor %s activate", p->host); } /* Test Case: Set flag on BGP peer. */ test_log(test, "case %02d: set %s [%s] on [%s]", tc++, type, peer_cmd, p->host); test_execute(test, "%sneighbor %s %s", ecp, p->host, peer_cmd); test_config_present(test, "%sneighbor %s %s", ecp, p->host, peer_cmd); test_config_absent(test, "neighbor %s %s", g->name, pa->cmd); test_process(test, pa, p, g->conf, true, false); /* Test Case: Add BGP peer to peer-group. */ test_log(test, "case %02d: add peer [%s] to group [%s]", tc++, p->host, g->name); test_execute(test, "neighbor %s peer-group %s", p->host, g->name); test_config_present(test, "neighbor %s %speer-group %s", p->host, p->conf_if ? "interface " : "", g->name); test_config_present(test, "%sneighbor %s %s", ecp, p->host, peer_cmd); test_config_absent(test, "neighbor %s %s", g->name, pa->cmd); test_process(test, pa, p, g->conf, true, false); /* Test Case: Re-add BGP peer to peer-group. */ test_log(test, "case %02d: re-add peer [%s] to group [%s]", tc++, p->host, g->name); test_execute(test, "neighbor %s peer-group %s", p->host, g->name); test_config_present(test, "neighbor %s %speer-group %s", p->host, p->conf_if ? "interface " : "", g->name); test_config_present(test, "%sneighbor %s %s", ecp, p->host, peer_cmd); test_config_absent(test, "neighbor %s %s", g->name, pa->cmd); test_process(test, pa, p, g->conf, true, false); /* Test Case: Set flag on BGP peer-group. */ test_log(test, "case %02d: set %s [%s] on [%s]", tc++, type, group_cmd, g->name); test_execute(test, "%sneighbor %s %s", ecg, g->name, group_cmd); test_config_present(test, "%sneighbor %s %s", ecp, p->host, peer_cmd); test_config_present(test, "%sneighbor %s %s", ecg, g->name, group_cmd); test_process(test, pa, p, g->conf, true, true); /* Test Case: Unset flag on BGP peer-group. */ test_log(test, "case %02d: unset %s [%s] on [%s]", tc++, type, group_cmd, g->name); test_execute(test, "%sneighbor %s %s", dcg, g->name, group_cmd); test_config_present(test, "%sneighbor %s %s", ecp, p->host, peer_cmd); test_config_absent(test, "neighbor %s %s", g->name, pa->cmd); test_process(test, pa, p, g->conf, true, false); /* Test Case: Set flag on BGP peer-group. */ test_log(test, "case %02d: set %s [%s] on [%s]", tc++, type, group_cmd, g->name); test_execute(test, "%sneighbor %s %s", ecg, g->name, group_cmd); test_config_present(test, "%sneighbor %s %s", ecp, p->host, peer_cmd); test_config_present(test, "%sneighbor %s %s", ecg, g->name, group_cmd); test_process(test, pa, p, g->conf, true, true); /* Test Case: Re-set flag on BGP peer. */ test_log(test, "case %02d: re-set %s [%s] on [%s]", tc++, type, peer_cmd, p->host); test_execute(test, "%sneighbor %s %s", ecp, p->host, peer_cmd); test_config_present(test, "%sneighbor %s %s", ecp, p->host, peer_cmd); test_config_present(test, "%sneighbor %s %s", ecg, g->name, group_cmd); test_process(test, pa, p, g->conf, true, true); /* Test Case: Unset flag on BGP peer. */ test_log(test, "case %02d: unset %s [%s] on [%s]", tc++, type, peer_cmd, p->host); test_execute(test, "%sneighbor %s %s", dcp, p->host, peer_cmd); test_config_absent(test, "neighbor %s %s", p->host, pa->cmd); test_config_present(test, "%sneighbor %s %s", ecg, g->name, group_cmd); test_process(test, pa, p, g->conf, false, true); /* Test Case: Unset flag on BGP peer-group. */ test_log(test, "case %02d: unset %s [%s] on [%s]", tc++, type, group_cmd, g->name); test_execute(test, "%sneighbor %s %s", dcg, g->name, group_cmd); test_config_absent(test, "neighbor %s %s", p->host, pa->cmd); test_config_absent(test, "neighbor %s %s", g->name, pa->cmd); test_process(test, pa, p, g->conf, false, false); /* Test Case: Set flag on BGP peer. */ test_log(test, "case %02d: set %s [%s] on [%s]", tc++, type, peer_cmd, p->host); test_execute(test, "%sneighbor %s %s", ecp, p->host, peer_cmd); test_config_present(test, "%sneighbor %s %s", ecp, p->host, peer_cmd); test_config_absent(test, "neighbor %s %s", g->name, pa->cmd); test_process(test, pa, p, g->conf, true, false); } static void bgp_startup(void) { cmd_init(1); openzlog("testbgpd", "NONE", 0, LOG_CONS | LOG_NDELAY | LOG_PID, LOG_DAEMON); zprivs_preinit(&bgpd_privs); zprivs_init(&bgpd_privs); master = thread_master_create(NULL); yang_init(); nb_init(master, NULL, 0); bgp_master_init(master); bgp_option_set(BGP_OPT_NO_LISTEN); vrf_init(NULL, NULL, NULL, NULL, NULL); bgp_init(0); bgp_pthreads_run(); } static void bgp_shutdown(void) { struct bgp *bgp; struct listnode *node, *nnode; bgp_terminate(); bgp_close(); for (ALL_LIST_ELEMENTS(bm->bgp, node, nnode, bgp)) bgp_delete(bgp); bgp_dump_finish(); bgp_route_finish(); bgp_route_map_terminate(); bgp_attr_finish(); bgp_pthreads_finish(); access_list_add_hook(NULL); access_list_delete_hook(NULL); access_list_reset(); as_list_add_hook(NULL); as_list_delete_hook(NULL); bgp_filter_reset(); prefix_list_add_hook(NULL); prefix_list_delete_hook(NULL); prefix_list_reset(); community_list_terminate(bgp_clist); vrf_terminate(); #ifdef ENABLE_BGP_VNC vnc_zebra_destroy(); #endif bgp_zebra_destroy(); bf_free(bm->rd_idspace); list_delete(&bm->bgp); memset(bm, 0, sizeof(*bm)); vty_terminate(); cmd_terminate(); nb_terminate(); yang_terminate(); zprivs_terminate(&bgpd_privs); thread_master_free(master); master = NULL; closezlog(); } int main(void) { int i, ii; struct list *pa_list; struct test_peer_attr *pa, *pac; struct listnode *node, *nnode; bgp_startup(); pa_list = list_new(); i = 0; while (test_peer_attrs[i].cmd) { pa = &test_peer_attrs[i++]; /* Just copy the peer attribute structure for global flags. */ if (is_attr_type_global(pa->type)) { pac = XMALLOC(MTYPE_TMP, sizeof(struct test_peer_attr)); memcpy(pac, pa, sizeof(struct test_peer_attr)); listnode_add(pa_list, pac); continue; } /* Fallback to default families if not specified. */ if (!pa->families[0].afi && !pa->families[0].safi) memcpy(&pa->families, test_default_families, sizeof(test_default_families)); /* Add peer attribute definition for each address family. */ ii = 0; while (pa->families[ii].afi && pa->families[ii].safi) { pac = XMALLOC(MTYPE_TMP, sizeof(struct test_peer_attr)); memcpy(pac, pa, sizeof(struct test_peer_attr)); pac->afi = pa->families[ii].afi; pac->safi = pa->families[ii].safi; listnode_add(pa_list, pac); ii++; } } for (ALL_LIST_ELEMENTS(pa_list, node, nnode, pa)) { char *desc; struct test *test; /* Build test description string. */ if (pa->afi && pa->safi) desc = str_printf("peer\\%s-%s\\%s", str_from_afi(pa->afi), str_from_safi(pa->safi), pa->cmd); else desc = str_printf("peer\\%s", pa->cmd); /* Initialize new test instance. */ test = test_new(desc, pa->o.use_ibgp, pa->o.use_iface_peer); XFREE(MTYPE_TMP, desc); /* Execute tests and finish test instance. */ test_peer_attr(test, pa); test_finish(test); /* Print empty line as spacer. */ printf("\n"); /* Free memory used for peer-attr declaration. */ XFREE(MTYPE_TMP, pa); } list_delete(&pa_list); bgp_shutdown(); return 0; } frr-7.2.1/tests/bgpd/test_peer_attr.py0000644000000000000000000002573113610377563014700 00000000000000import frrtest class TestFlag(frrtest.TestMultiOut): program = './test_peer_attr' # List of tests can be generated by executing: # $> ./test_peer_attr 2>&1 | sed -n 's/\\/\\\\/g; s/\S\+ \[test\] \(.\+\)/TestFlag.okfail(\x27\1\x27)/pg' # TestFlag.okfail('peer\\advertisement-interval') TestFlag.okfail('peer\\capability dynamic') TestFlag.okfail('peer\\capability extended-nexthop') #TestFlag.okfail('peer\\capability extended-nexthop') TestFlag.okfail('peer\\description') TestFlag.okfail('peer\\disable-connected-check') TestFlag.okfail('peer\\dont-capability-negotiate') TestFlag.okfail('peer\\enforce-first-as') TestFlag.okfail('peer\\local-as') TestFlag.okfail('peer\\local-as 1 no-prepend') TestFlag.okfail('peer\\local-as 1 no-prepend replace-as') TestFlag.okfail('peer\\override-capability') TestFlag.okfail('peer\\passive') TestFlag.okfail('peer\\password') TestFlag.okfail('peer\\shutdown') TestFlag.okfail('peer\\strict-capability-match') TestFlag.okfail('peer\\timers') TestFlag.okfail('peer\\timers connect') TestFlag.okfail('peer\\update-source') TestFlag.okfail('peer\\update-source') TestFlag.okfail('peer\\ipv4-unicast\\addpath') TestFlag.okfail('peer\\ipv4-multicast\\addpath') TestFlag.okfail('peer\\ipv6-unicast\\addpath') TestFlag.okfail('peer\\ipv6-multicast\\addpath') TestFlag.okfail('peer\\ipv4-unicast\\allowas-in') TestFlag.okfail('peer\\ipv4-multicast\\allowas-in') TestFlag.okfail('peer\\ipv6-unicast\\allowas-in') TestFlag.okfail('peer\\ipv6-multicast\\allowas-in') TestFlag.okfail('peer\\ipv4-unicast\\allowas-in origin') TestFlag.okfail('peer\\ipv4-multicast\\allowas-in origin') TestFlag.okfail('peer\\ipv6-unicast\\allowas-in origin') TestFlag.okfail('peer\\ipv6-multicast\\allowas-in origin') TestFlag.okfail('peer\\ipv4-unicast\\as-override') TestFlag.okfail('peer\\ipv4-multicast\\as-override') TestFlag.okfail('peer\\ipv6-unicast\\as-override') TestFlag.okfail('peer\\ipv6-multicast\\as-override') TestFlag.okfail('peer\\ipv4-unicast\\attribute-unchanged as-path') TestFlag.okfail('peer\\ipv4-multicast\\attribute-unchanged as-path') TestFlag.okfail('peer\\ipv6-unicast\\attribute-unchanged as-path') TestFlag.okfail('peer\\ipv6-multicast\\attribute-unchanged as-path') TestFlag.okfail('peer\\ipv4-unicast\\attribute-unchanged next-hop') TestFlag.okfail('peer\\ipv4-multicast\\attribute-unchanged next-hop') TestFlag.okfail('peer\\ipv6-unicast\\attribute-unchanged next-hop') TestFlag.okfail('peer\\ipv6-multicast\\attribute-unchanged next-hop') TestFlag.okfail('peer\\ipv4-unicast\\attribute-unchanged med') TestFlag.okfail('peer\\ipv4-multicast\\attribute-unchanged med') TestFlag.okfail('peer\\ipv6-unicast\\attribute-unchanged med') TestFlag.okfail('peer\\ipv6-multicast\\attribute-unchanged med') TestFlag.okfail('peer\\ipv4-unicast\\attribute-unchanged as-path next-hop') TestFlag.okfail('peer\\ipv4-multicast\\attribute-unchanged as-path next-hop') TestFlag.okfail('peer\\ipv6-unicast\\attribute-unchanged as-path next-hop') TestFlag.okfail('peer\\ipv6-multicast\\attribute-unchanged as-path next-hop') TestFlag.okfail('peer\\ipv4-unicast\\attribute-unchanged as-path med') TestFlag.okfail('peer\\ipv4-multicast\\attribute-unchanged as-path med') TestFlag.okfail('peer\\ipv6-unicast\\attribute-unchanged as-path med') TestFlag.okfail('peer\\ipv6-multicast\\attribute-unchanged as-path med') TestFlag.okfail('peer\\ipv4-unicast\\attribute-unchanged as-path next-hop med') TestFlag.okfail('peer\\ipv4-multicast\\attribute-unchanged as-path next-hop med') TestFlag.okfail('peer\\ipv6-unicast\\attribute-unchanged as-path next-hop med') TestFlag.okfail('peer\\ipv6-multicast\\attribute-unchanged as-path next-hop med') TestFlag.okfail('peer\\ipv4-unicast\\capability orf prefix-list send') TestFlag.okfail('peer\\ipv4-multicast\\capability orf prefix-list send') TestFlag.okfail('peer\\ipv6-unicast\\capability orf prefix-list send') TestFlag.okfail('peer\\ipv6-multicast\\capability orf prefix-list send') TestFlag.okfail('peer\\ipv4-unicast\\capability orf prefix-list receive') TestFlag.okfail('peer\\ipv4-multicast\\capability orf prefix-list receive') TestFlag.okfail('peer\\ipv6-unicast\\capability orf prefix-list receive') TestFlag.okfail('peer\\ipv6-multicast\\capability orf prefix-list receive') TestFlag.okfail('peer\\ipv4-unicast\\capability orf prefix-list both') TestFlag.okfail('peer\\ipv4-multicast\\capability orf prefix-list both') TestFlag.okfail('peer\\ipv6-unicast\\capability orf prefix-list both') TestFlag.okfail('peer\\ipv6-multicast\\capability orf prefix-list both') TestFlag.okfail('peer\\ipv4-unicast\\default-originate') TestFlag.okfail('peer\\ipv4-multicast\\default-originate') TestFlag.okfail('peer\\ipv6-unicast\\default-originate') TestFlag.okfail('peer\\ipv6-multicast\\default-originate') TestFlag.okfail('peer\\ipv4-unicast\\default-originate route-map') TestFlag.okfail('peer\\ipv4-multicast\\default-originate route-map') TestFlag.okfail('peer\\ipv6-unicast\\default-originate route-map') TestFlag.okfail('peer\\ipv6-multicast\\default-originate route-map') TestFlag.okfail('peer\\ipv4-unicast\\distribute-list') TestFlag.okfail('peer\\ipv4-multicast\\distribute-list') TestFlag.okfail('peer\\ipv6-unicast\\distribute-list') TestFlag.okfail('peer\\ipv6-multicast\\distribute-list') TestFlag.okfail('peer\\ipv4-unicast\\distribute-list') TestFlag.okfail('peer\\ipv4-multicast\\distribute-list') TestFlag.okfail('peer\\ipv6-unicast\\distribute-list') TestFlag.okfail('peer\\ipv6-multicast\\distribute-list') TestFlag.okfail('peer\\ipv4-unicast\\filter-list') TestFlag.okfail('peer\\ipv4-multicast\\filter-list') TestFlag.okfail('peer\\ipv6-unicast\\filter-list') TestFlag.okfail('peer\\ipv6-multicast\\filter-list') TestFlag.okfail('peer\\ipv4-unicast\\filter-list') TestFlag.okfail('peer\\ipv4-multicast\\filter-list') TestFlag.okfail('peer\\ipv6-unicast\\filter-list') TestFlag.okfail('peer\\ipv6-multicast\\filter-list') TestFlag.okfail('peer\\ipv4-unicast\\maximum-prefix') TestFlag.okfail('peer\\ipv4-multicast\\maximum-prefix') TestFlag.okfail('peer\\ipv6-unicast\\maximum-prefix') TestFlag.okfail('peer\\ipv6-multicast\\maximum-prefix') TestFlag.okfail('peer\\ipv4-unicast\\maximum-prefix') TestFlag.okfail('peer\\ipv4-multicast\\maximum-prefix') TestFlag.okfail('peer\\ipv6-unicast\\maximum-prefix') TestFlag.okfail('peer\\ipv6-multicast\\maximum-prefix') TestFlag.okfail('peer\\ipv4-unicast\\maximum-prefix') TestFlag.okfail('peer\\ipv4-multicast\\maximum-prefix') TestFlag.okfail('peer\\ipv6-unicast\\maximum-prefix') TestFlag.okfail('peer\\ipv6-multicast\\maximum-prefix') TestFlag.okfail('peer\\ipv4-unicast\\maximum-prefix') TestFlag.okfail('peer\\ipv4-multicast\\maximum-prefix') TestFlag.okfail('peer\\ipv6-unicast\\maximum-prefix') TestFlag.okfail('peer\\ipv6-multicast\\maximum-prefix') TestFlag.okfail('peer\\ipv4-unicast\\maximum-prefix') TestFlag.okfail('peer\\ipv4-multicast\\maximum-prefix') TestFlag.okfail('peer\\ipv6-unicast\\maximum-prefix') TestFlag.okfail('peer\\ipv6-multicast\\maximum-prefix') TestFlag.okfail('peer\\ipv4-unicast\\next-hop-self') TestFlag.okfail('peer\\ipv4-multicast\\next-hop-self') TestFlag.okfail('peer\\ipv6-unicast\\next-hop-self') TestFlag.okfail('peer\\ipv6-multicast\\next-hop-self') TestFlag.okfail('peer\\ipv4-unicast\\next-hop-self force') TestFlag.okfail('peer\\ipv4-multicast\\next-hop-self force') TestFlag.okfail('peer\\ipv6-unicast\\next-hop-self force') TestFlag.okfail('peer\\ipv6-multicast\\next-hop-self force') TestFlag.okfail('peer\\ipv4-unicast\\prefix-list') TestFlag.okfail('peer\\ipv4-multicast\\prefix-list') TestFlag.okfail('peer\\ipv6-unicast\\prefix-list') TestFlag.okfail('peer\\ipv6-multicast\\prefix-list') TestFlag.okfail('peer\\ipv4-unicast\\prefix-list') TestFlag.okfail('peer\\ipv4-multicast\\prefix-list') TestFlag.okfail('peer\\ipv6-unicast\\prefix-list') TestFlag.okfail('peer\\ipv6-multicast\\prefix-list') TestFlag.okfail('peer\\ipv4-unicast\\remove-private-AS') TestFlag.okfail('peer\\ipv4-multicast\\remove-private-AS') TestFlag.okfail('peer\\ipv6-unicast\\remove-private-AS') TestFlag.okfail('peer\\ipv6-multicast\\remove-private-AS') TestFlag.okfail('peer\\ipv4-unicast\\remove-private-AS all') TestFlag.okfail('peer\\ipv4-multicast\\remove-private-AS all') TestFlag.okfail('peer\\ipv6-unicast\\remove-private-AS all') TestFlag.okfail('peer\\ipv6-multicast\\remove-private-AS all') TestFlag.okfail('peer\\ipv4-unicast\\remove-private-AS replace-AS') TestFlag.okfail('peer\\ipv4-multicast\\remove-private-AS replace-AS') TestFlag.okfail('peer\\ipv6-unicast\\remove-private-AS replace-AS') TestFlag.okfail('peer\\ipv6-multicast\\remove-private-AS replace-AS') TestFlag.okfail('peer\\ipv4-unicast\\remove-private-AS all replace-AS') TestFlag.okfail('peer\\ipv4-multicast\\remove-private-AS all replace-AS') TestFlag.okfail('peer\\ipv6-unicast\\remove-private-AS all replace-AS') TestFlag.okfail('peer\\ipv6-multicast\\remove-private-AS all replace-AS') TestFlag.okfail('peer\\ipv4-unicast\\route-map') TestFlag.okfail('peer\\ipv4-multicast\\route-map') TestFlag.okfail('peer\\ipv6-unicast\\route-map') TestFlag.okfail('peer\\ipv6-multicast\\route-map') TestFlag.okfail('peer\\ipv4-unicast\\route-map') TestFlag.okfail('peer\\ipv4-multicast\\route-map') TestFlag.okfail('peer\\ipv6-unicast\\route-map') TestFlag.okfail('peer\\ipv6-multicast\\route-map') TestFlag.okfail('peer\\ipv4-unicast\\route-reflector-client') TestFlag.okfail('peer\\ipv4-multicast\\route-reflector-client') TestFlag.okfail('peer\\ipv6-unicast\\route-reflector-client') TestFlag.okfail('peer\\ipv6-multicast\\route-reflector-client') TestFlag.okfail('peer\\ipv4-unicast\\route-server-client') TestFlag.okfail('peer\\ipv4-multicast\\route-server-client') TestFlag.okfail('peer\\ipv6-unicast\\route-server-client') TestFlag.okfail('peer\\ipv6-multicast\\route-server-client') TestFlag.okfail('peer\\ipv4-unicast\\send-community') TestFlag.okfail('peer\\ipv4-multicast\\send-community') TestFlag.okfail('peer\\ipv6-unicast\\send-community') TestFlag.okfail('peer\\ipv6-multicast\\send-community') TestFlag.okfail('peer\\ipv4-unicast\\send-community extended') TestFlag.okfail('peer\\ipv4-multicast\\send-community extended') TestFlag.okfail('peer\\ipv6-unicast\\send-community extended') TestFlag.okfail('peer\\ipv6-multicast\\send-community extended') TestFlag.okfail('peer\\ipv4-unicast\\send-community large') TestFlag.okfail('peer\\ipv4-multicast\\send-community large') TestFlag.okfail('peer\\ipv6-unicast\\send-community large') TestFlag.okfail('peer\\ipv6-multicast\\send-community large') TestFlag.okfail('peer\\ipv4-unicast\\soft-reconfiguration inbound') TestFlag.okfail('peer\\ipv4-multicast\\soft-reconfiguration inbound') TestFlag.okfail('peer\\ipv6-unicast\\soft-reconfiguration inbound') TestFlag.okfail('peer\\ipv6-multicast\\soft-reconfiguration inbound') TestFlag.okfail('peer\\ipv4-unicast\\unsuppress-map') TestFlag.okfail('peer\\ipv4-multicast\\unsuppress-map') TestFlag.okfail('peer\\ipv6-unicast\\unsuppress-map') TestFlag.okfail('peer\\ipv6-multicast\\unsuppress-map') TestFlag.okfail('peer\\ipv4-unicast\\weight') TestFlag.okfail('peer\\ipv4-multicast\\weight') TestFlag.okfail('peer\\ipv6-unicast\\weight') TestFlag.okfail('peer\\ipv6-multicast\\weight') frr-7.2.1/tests/helpers/0000755000000000000000000000000013610377563012100 500000000000000frr-7.2.1/tests/helpers/c/0000755000000000000000000000000013610377563012322 500000000000000frr-7.2.1/tests/helpers/c/main.c0000644000000000000000000001010113610377563013323 00000000000000/* * This file is part of Quagga. * * Quagga is free software; you can redistribute it and/or modify it * under the terms of the GNU General Public License as published by the * Free Software Foundation; either version 2, or (at your option) any * later version. * * Quagga 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; see the file COPYING; if not, write to the Free Software * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA */ #include #include #include "getopt.h" #include "thread.h" #include "vty.h" #include "command.h" #include "memory.h" #include "memory_vty.h" extern void test_init(void); struct thread_master *master; struct option longopts[] = {{"daemon", no_argument, NULL, 'd'}, {"config_file", required_argument, NULL, 'f'}, {"help", no_argument, NULL, 'h'}, {"vty_addr", required_argument, NULL, 'A'}, {"vty_port", required_argument, NULL, 'P'}, {"version", no_argument, NULL, 'v'}, {0}}; DEFUN (daemon_exit, daemon_exit_cmd, "daemon-exit", "Make the daemon exit\n") { exit(0); } static int timer_count; static int test_timer(struct thread *thread) { int *count = THREAD_ARG(thread); printf("run %d of timer\n", (*count)++); thread_add_timer(master, test_timer, count, 5, NULL); return 0; } static void test_timer_init(void) { thread_add_timer(master, test_timer, &timer_count, 10, NULL); } static void test_vty_init(void) { install_element(VIEW_NODE, &daemon_exit_cmd); } /* Help information display. */ static void usage(char *progname, int status) { if (status != 0) fprintf(stderr, "Try `%s --help' for more information.\n", progname); else { printf("Usage : %s [OPTION...]\n\ Daemon which does 'slow' things.\n\n\ -d, --daemon Runs in daemon mode\n\ -f, --config_file Set configuration file name\n\ -A, --vty_addr Set vty's bind address\n\ -P, --vty_port Set vty's port number\n\ -v, --version Print program version\n\ -h, --help Display this help and exit\n\ \n\ Report bugs to %s\n", progname, FRR_BUG_ADDRESS); } exit(status); } /* main routine. */ int main(int argc, char **argv) { char *p; char *vty_addr = NULL; int vty_port = 4000; int daemon_mode = 0; char *progname; struct thread thread; char *config_file = NULL; /* Set umask before anything for security */ umask(0027); /* get program name */ progname = ((p = strrchr(argv[0], '/')) ? ++p : argv[0]); /* master init. */ master = thread_master_create(NULL); while (1) { int opt; opt = getopt_long(argc, argv, "dhf:A:P:v", longopts, 0); if (opt == EOF) break; switch (opt) { case 0: break; case 'f': config_file = optarg; break; case 'd': daemon_mode = 1; break; case 'A': vty_addr = optarg; break; case 'P': /* Deal with atoi() returning 0 on failure */ if (strcmp(optarg, "0") == 0) { vty_port = 0; break; } vty_port = atoi(optarg); vty_port = (vty_port ? vty_port : 4000); break; case 'v': print_version(progname); exit(0); break; case 'h': usage(progname, 0); break; default: usage(progname, 1); break; } } /* Library inits. */ cmd_init(1); vty_init(master, false); memory_init(); yang_init(); nb_init(master, NULL, 0); /* OSPF vty inits. */ test_vty_init(); /* Change to the daemon program. */ if (daemon_mode && daemon(0, 0) < 0) { fprintf(stderr, "daemon failed: %s", strerror(errno)); exit(1); } /* Create VTY socket */ vty_serv_sock(vty_addr, vty_port, "/tmp/.heavy.sock"); /* Configuration file read*/ if (!config_file) usage(progname, 1); vty_read_config(NULL, config_file, NULL); test_timer_init(); test_init(); /* Fetch next active thread. */ while (thread_fetch(master, &thread)) thread_call(&thread); /* Not reached. */ exit(0); } frr-7.2.1/tests/helpers/c/prng.c0000644000000000000000000000513613610377563013361 00000000000000/* * Very simple prng to allow for randomized tests with reproducable * results. * * Copyright (C) 2012 by Open Source Routing. * Copyright (C) 2012 by Internet Systems Consortium, Inc. ("ISC") * Copyright (C) 2017 Christian Franke * * This file is part of Quagga * * Quagga is free software; you can redistribute it and/or modify it * under the terms of the GNU General Public License as published by the * Free Software Foundation; either version 2, or (at your option) any * later version. * * Quagga 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; see the file COPYING; if not, write to the Free Software * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA */ #include #include #include #include #include "prng.h" struct prng { uint64_t state; }; struct prng *prng_new(unsigned long long seed) { struct prng *rv = calloc(sizeof(*rv), 1); assert(rv); rv->state = seed; return rv; } /* * This implementation has originally been provided to musl libc by * Szabolcs Nagy in 2013 under the terms of * the MIT license. * It is a simple LCG which D.E. Knuth attributes to C.E. Haynes in * TAOCP Vol2 3.3.4 */ int prng_rand(struct prng *prng) { prng->state = 6364136223846793005ULL * prng->state + 1; return prng->state >> 33; } const char *prng_fuzz(struct prng *prng, const char *string, const char *charset, unsigned int operations) { static char buf[256]; unsigned int charset_len; unsigned int i; unsigned int offset; unsigned int op; unsigned int character; assert(strlen(string) < sizeof(buf)); strncpy(buf, string, sizeof(buf)); charset_len = strlen(charset); for (i = 0; i < operations; i++) { offset = prng_rand(prng) % strlen(buf); op = prng_rand(prng) % 3; switch (op) { case 0: /* replace */ character = prng_rand(prng) % charset_len; buf[offset] = charset[character]; break; case 1: /* remove */ memmove(buf + offset, buf + offset + 1, strlen(buf) - offset); break; case 2: /* insert */ assert(strlen(buf) + 1 < sizeof(buf)); memmove(buf + offset + 1, buf + offset, strlen(buf) + 1 - offset); character = prng_rand(prng) % charset_len; buf[offset] = charset[character]; break; } } return buf; } void prng_free(struct prng *prng) { free(prng); } frr-7.2.1/tests/helpers/c/prng.h0000644000000000000000000000230713610377563013363 00000000000000/* * Very simple prng to allow for randomized tests with reproducable * results. * * Copyright (C) 2012 by Open Source Routing. * Copyright (C) 2012 by Internet Systems Consortium, Inc. ("ISC") * * This file is part of Quagga * * Quagga is free software; you can redistribute it and/or modify it * under the terms of the GNU General Public License as published by the * Free Software Foundation; either version 2, or (at your option) any * later version. * * Quagga 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; see the file COPYING; if not, write to the Free Software * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA */ #ifndef _PRNG_H #define _PRNG_H struct prng; struct prng *prng_new(unsigned long long seed); int prng_rand(struct prng *); const char *prng_fuzz(struct prng *, const char *string, const char *charset, unsigned int operations); void prng_free(struct prng *); #endif frr-7.2.1/tests/helpers/c/tests.h0000644000000000000000000000201313610377563013551 00000000000000/* * Test wrappers common header file * * Copyright (C) 2015 by David Lamparter, * for Open Source Routing./ NetDEF, Inc. * * This file is part of Quagga * * Quagga is free software; you can redistribute it and/or modify it * under the terms of the GNU General Public License as published by the * Free Software Foundation; either version 2, or (at your option) any * later version. * * Quagga 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; see the file COPYING; if not, write to the Free Software * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA */ #ifndef _QUAGGA_TESTS_H #define _QUAGGA_TESTS_H extern void test_init(void); extern void test_init_cmd(void); #endif /* _QUAGGA_TESTS_H */ frr-7.2.1/tests/helpers/python/0000755000000000000000000000000013610377563013421 500000000000000frr-7.2.1/tests/helpers/python/frrsix.py0000644000000000000000000000517713610377563015242 00000000000000# # Copyright (c) 2010-2017 Benjamin Peterson # # Permission is hereby granted, free of charge, to any person obtaining a copy # of this software and associated documentation files (the "Software"), to deal # in the Software without restriction, including without limitation the rights # to use, copy, modify, merge, publish, distribute, sublicense, and/or sell # copies of the Software, and to permit persons to whom the Software is # furnished to do so, subject to the following conditions: # # The above copyright notice and this permission notice shall be included in all # copies or substantial portions of the Software. # # THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR # IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, # FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE # AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER # LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, # OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE # SOFTWARE. # # # This code is taken from the six python2 to python3 compatibility module # import sys PY2 = sys.version_info[0] == 2 PY3 = sys.version_info[0] == 3 def add_metaclass(metaclass): """Class decorator for creating a class with a metaclass.""" def wrapper(cls): orig_vars = cls.__dict__.copy() slots = orig_vars.get('__slots__') if slots is not None: if isinstance(slots, str): slots = [slots] for slots_var in slots: orig_vars.pop(slots_var) orig_vars.pop('__dict__', None) orig_vars.pop('__weakref__', None) return metaclass(cls.__name__, cls.__bases__, orig_vars) return wrapper if PY3: import builtins exec_ = getattr(builtins,'exec') def reraise(tp, value, tb=None): try: if value is None: value = tp() if value.__traceback__ is not tb: raise value.with_traceback(tb) raise value finally: value = None tb = None else: def exec_(_code_, _globs_=None, _locs_=None): """Execute code in a namespace.""" if _globs_ is None: frame = sys._getframe(1) _globs_ = frame.f_globals if _locs_ is None: _locs_ = frame.f_locals del frame elif _locs_ is None: _locs_ = _globs_ exec("""exec _code_ in _globs_, _locs_""") exec_("""def reraise(tp, value, tb=None): try: raise tp, value, tb finally: tb = None """) frr-7.2.1/tests/helpers/python/frrtest.py0000644000000000000000000001553413610377563015414 00000000000000# # Test helpers for FRR # # Copyright (C) 2017 by David Lamparter & Christian Franke, # Open Source Routing / NetDEF Inc. # # This file is part of FRRouting (FRR) # # FRR is free software; you can redistribute it and/or modify it # under the terms of the GNU General Public License as published by the # Free Software Foundation; either version 2, or (at your option) any # later version. # # FRR 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 FRR; see the file COPYING. If not, write to the Free # Software Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA # 02111-1307, USA. # import subprocess import sys import re import inspect import os import difflib import frrsix # # These are the gritty internals of the TestMultiOut implementation. # See below for the definition of actual TestMultiOut tests. # srcbase = os.path.abspath(inspect.getsourcefile(frrsix)) for i in range(0, 3): srcbase = os.path.dirname(srcbase) def binpath(srcpath): return os.path.relpath(os.path.abspath(srcpath), srcbase) class MultiTestFailure(Exception): pass class MetaTestMultiOut(type): def __getattr__(cls, name): if name.startswith('_'): raise AttributeError internal_name = '_{}'.format(name) if internal_name not in dir(cls): raise AttributeError def registrar(*args, **kwargs): cls._add_test(getattr(cls,internal_name), *args, **kwargs) return registrar @frrsix.add_metaclass(MetaTestMultiOut) class _TestMultiOut(object): def _run_tests(self): if 'tests_run' in dir(self.__class__) and self.tests_run: return self.__class__.tests_run = True basedir = os.path.dirname(inspect.getsourcefile(type(self))) program = os.path.join(basedir, self.program) proc = subprocess.Popen([binpath(program)], stdout=subprocess.PIPE) self.output,_ = proc.communicate('') self.exitcode = proc.wait() self.__class__.testresults = {} for test in self.tests: try: test(self) except MultiTestFailure: self.testresults[test] = sys.exc_info() else: self.testresults[test] = None def _exit_cleanly(self): if self.exitcode != 0: raise MultiTestFailure("Program did not terminate with exit code 0") @classmethod def _add_test(cls, method, *args, **kwargs): if 'tests' not in dir(cls): setattr(cls,'tests',[]) if method is not cls._exit_cleanly: cls._add_test(cls._exit_cleanly) def matchfunction(self): method(self, *args, **kwargs) cls.tests.append(matchfunction) def testfunction(self): self._run_tests() result = self.testresults[matchfunction] if result is not None: frrsix.reraise(*result) testname = re.sub(r'[^A-Za-z0-9]', '_', '%r%r' % (args, kwargs)) testname = re.sub(r'__*', '_', testname) testname = testname.strip('_') if not testname: testname = method.__name__.strip('_') if "test_%s" % testname in dir(cls): index = 2 while "test_%s_%d" % (testname,index) in dir(cls): index += 1 testname = "%s_%d" % (testname, index) setattr(cls,"test_%s" % testname, testfunction) # # This class houses the actual TestMultiOut tests types. # If you want to add a new test type, you probably do it here. # # Say you want to add a test type called foobarlicious. Then define # a function _foobarlicious here that takes self and the test arguments # when called. That function should check the output in self.output # to see whether it matches the expectation of foobarlicious with the # given arguments and should then adjust self.output according to how # much output it consumed. # If the output doesn't meet the expectations, MultiTestFailure can be # raised, however that should only be done after self.output has been # modified according to consumed content. # re_okfail = re.compile(r'(?:[3[12]m|^)?(?POK|failed)'.encode('utf8'), re.MULTILINE) class TestMultiOut(_TestMultiOut): def _onesimple(self, line): if type(line) is str: line = line.encode('utf8') idx = self.output.find(line) if idx != -1: self.output = self.output[idx+len(line):] else: raise MultiTestFailure("%r could not be found" % line) def _okfail(self, line, okfail=re_okfail): self._onesimple(line) m = okfail.search(self.output) if m is None: raise MultiTestFailure('OK/fail not found') self.output = self.output[m.end():] if m.group('ret') != 'OK'.encode('utf8'): raise MultiTestFailure('Test output indicates failure') # # This class implements a test comparing the output of a program against # an existing reference output # class TestRefMismatch(Exception): def __init__(self, _test, outtext, reftext): self.outtext = outtext.decode('utf8') if type(outtext) is bytes else outtext self.reftext = reftext.decode('utf8') if type(reftext) is bytes else reftext def __str__(self): rv = 'Expected output and actual output differ:\n' rv += '\n'.join(difflib.unified_diff(self.reftext.splitlines(), self.outtext.splitlines(), 'outtext', 'reftext', lineterm='')) return rv class TestExitNonzero(Exception): pass class TestRefOut(object): def test_refout(self): basedir = os.path.dirname(inspect.getsourcefile(type(self))) program = os.path.join(basedir, self.program) if getattr(self, 'built_refin', False): refin = binpath(program) + '.in' else: refin = program + '.in' if getattr(self, 'built_refout', False): refout = binpath(program) + '.refout' else: refout = program + '.refout' intext = '' if os.path.exists(refin): with open(refin, 'rb') as f: intext = f.read() with open(refout, 'rb') as f: reftext = f.read() proc = subprocess.Popen([binpath(program)], stdin=subprocess.PIPE, stdout=subprocess.PIPE) outtext,_ = proc.communicate(intext) if outtext != reftext: raise TestRefMismatch(self, outtext, reftext) if proc.wait() != 0: raise TestExitNonzero(self) frr-7.2.1/tests/isisd/0000755000000000000000000000000013610377563011551 500000000000000frr-7.2.1/tests/isisd/test_fuzz_isis_tlv.c0000644000000000000000000001057013610377563015611 00000000000000#ifdef HAVE_CONFIG_H #include "config.h" #endif #include "test_fuzz_isis_tlv_tests.h" #include #include "memory.h" #include "sbuf.h" #include "stream.h" #include "thread.h" #include "isisd/isis_circuit.h" #include "isisd/isis_tlvs.h" #define TEST_STREAM_SIZE 1500 struct thread_master *master; int isis_sock_init(struct isis_circuit *circuit); int isis_sock_init(struct isis_circuit *circuit) { return 0; } struct zebra_privs_t isisd_privs; static bool atexit_registered; static void show_meminfo_at_exit(void) { log_memstats(stderr, "isis fuzztest"); } static int comp_line(const void *p1, const void *p2) { return strcmp(*(char * const *)p1, *(char * const *)p2); } static char *sortlines(char *in) { size_t line_count = 1; size_t rv_len = strlen(in) + 1; size_t rv_pos = 0; char *rv = XMALLOC(MTYPE_TMP, rv_len); for (char *c = in; *c; c++) { if (*c == '\n') line_count++; } if (line_count == 1) { strncpy(rv, in, rv_len); return rv; } char **lines = XCALLOC(MTYPE_TMP, sizeof(char *)*line_count); char *saveptr = NULL; size_t i = 0; for (char *line = strtok_r(in, "\n", &saveptr); line; line = strtok_r(NULL, "\n", &saveptr)) { lines[i++] = line; assert(i <= line_count); } line_count = i; qsort(lines, line_count, sizeof(char *), comp_line); for (i = 0; i < line_count; i++) { int printf_rv = snprintf(rv + rv_pos, rv_len - rv_pos, "%s\n", lines[i]); assert(printf_rv >= 0); rv_pos += printf_rv; } XFREE(MTYPE_TMP, lines); return rv; } static int test(FILE *input, FILE *output) { struct stream *s = stream_new(TEST_STREAM_SIZE); char buf[TEST_STREAM_SIZE]; size_t bytes_read = 0; if (!atexit_registered) { atexit(show_meminfo_at_exit); atexit_registered = true; } while (STREAM_WRITEABLE(s) && !feof(input)) { bytes_read = fread(buf, 1, STREAM_WRITEABLE(s), input); if (bytes_read == 0) break; stream_put(s, buf, bytes_read); } if (bytes_read && !feof(input)) { fprintf(output, "Too much input data.\n"); stream_free(s); return 1; } stream_set_getp(s, 0); struct isis_tlvs *tlvs; const char *log; int rv = isis_unpack_tlvs(STREAM_READABLE(s), s, &tlvs, &log); if (rv) { fprintf(output, "Could not unpack TLVs:\n%s\n", log); isis_free_tlvs(tlvs); stream_free(s); return 2; } fprintf(output, "Unpack log:\n%s", log); const char *s_tlvs = isis_format_tlvs(tlvs); fprintf(output, "Unpacked TLVs:\n%s", s_tlvs); struct isis_item *orig_auth = tlvs->isis_auth.head; tlvs->isis_auth.head = NULL; s_tlvs = isis_format_tlvs(tlvs); struct isis_tlvs *tlv_copy = isis_copy_tlvs(tlvs); tlvs->isis_auth.head = orig_auth; isis_free_tlvs(tlvs); struct stream *s2 = stream_new(TEST_STREAM_SIZE); if (isis_pack_tlvs(tlv_copy, s2, (size_t)-1, false, false)) { fprintf(output, "Could not pack TLVs.\n"); assert(0); } stream_set_getp(s2, 0); rv = isis_unpack_tlvs(STREAM_READABLE(s2), s2, &tlvs, &log); if (rv) { fprintf(output, "Could not unpack own TLVs:\n%s\n", log); assert(0); } char *orig_tlvs = XSTRDUP(MTYPE_TMP, s_tlvs); s_tlvs = isis_format_tlvs(tlvs); if (strcmp(orig_tlvs, s_tlvs)) { fprintf(output, "Deserialized and Serialized LSP seem to differ.\n"); fprintf(output, "Re-Unpacked TLVs:\n%s", s_tlvs); assert(0); } isis_free_tlvs(tlv_copy); stream_free(s); stream_free(s2); struct list *fragments = isis_fragment_tlvs(tlvs, 550); isis_free_tlvs(tlvs); if (!fragments) { XFREE(MTYPE_TMP, orig_tlvs); return 0; } s = stream_new(550); struct sbuf fragment_format; sbuf_init(&fragment_format, NULL, 0); struct listnode *node; for (ALL_LIST_ELEMENTS_RO(fragments, node, tlvs)) { stream_reset(s); int rv = isis_pack_tlvs(tlvs, s, (size_t)-1, false, false); if (rv) { fprintf(output, "Could not pack fragment, too large.\n"); assert(0); } sbuf_push(&fragment_format, 0, "%s", isis_format_tlvs(tlvs)); isis_free_tlvs(tlvs); } list_delete(&fragments); stream_free(s); char *fragment_content = sortlines((char *)sbuf_buf(&fragment_format)); sbuf_free(&fragment_format); char *orig_tlv_content = sortlines(orig_tlvs); XFREE(MTYPE_TMP, orig_tlvs); if (strcmp(fragment_content, orig_tlv_content)) { fprintf(output, "Fragmented and unfragmented LSP seem to differ.\n"); fprintf(output, "Original:\n%s\nFragmented:\n%s\n", orig_tlv_content, fragment_content); assert(0); } XFREE(MTYPE_TMP, fragment_content); XFREE(MTYPE_TMP, orig_tlv_content); return 0; } frr-7.2.1/tests/isisd/test_fuzz_isis_tlv.py0000644000000000000000000000120313610377563016010 00000000000000import frrtest import pytest import platform import socket ## # on musl, ntop compresses a single :0: -> :: which is against RFC ## def inet_ntop_broken(): addr = '1:2:3:4:0:6:7:8' return socket.inet_ntop(socket.AF_INET6, socket.inet_pton(socket.AF_INET6, addr)) != addr if platform.uname()[0] == 'SunOS' or inet_ntop_broken(): class TestFuzzIsisTLV: @pytest.mark.skipif(True, reason='Test unsupported') def test_exit_cleanly(self): pass else: class TestFuzzIsisTLV(frrtest.TestMultiOut): program = './test_fuzz_isis_tlv' TestFuzzIsisTLV.exit_cleanly() frr-7.2.1/tests/isisd/test_fuzz_isis_tlv_tests.h.gz0000644000000000000000000066137213610377563017473 00000000000000‹+_Õ\test_fuzz_isis_tlv_tests.hìý[¯ì¸Ö°‡]¯õ+ڌƶ(Q'8;7±“ ßç\84欃¿F¶Woô»:ngcÿ÷ˆ)©$UGž 5K¥ùŒÁArpüoþë¯ßþëoÿù¿üößοýãô­úüøóÇïÿóéûé§ã·ÏÿíÛÿúçþË?þöí¿ûýûÿîÇ·ÿåãÿsúöñýûvø/ßÿçÓ|ûñû·ß~ülóQ}ù/§ÿíÛÿòÛÿü_~|û<}ûýÿ{úãýã·?NßÿV]ðß|ý¯~;?žÎßþóÿŸþóúõøOÿÃúï~5ÿÿúúÿ§ÿ©þþëþ¿þ?›Sÿ—¯ÿUuéoßOW·—ÿúþ¿ý¿þ§ÿûÿøÿø?þ÷ßTuú·ï‡üy<}ûßÿÇão¿ÿí¿ü:§þøíûÿÜ?wüÇoŸç>ÿýÝ“§?þøÞ{ÞŸß«.5ç¾VþóðãÛÓü8|üÇéÛ¿¾~9üþý?~p|û¯ûþÏ?ü·_¿üÇoÿ¿Ó¯?¾Õ_ýÇéûÛ½ì÷?t®k¾Û ûþãÛ§ê×ÿ·—ïsÿüÇÿëÿýíïÕÛ¿~©Rðåoõ‹ª?ýò×éó—¿¢cõyúå¯üüË_gqdé/QõlÏšk¢¢ú¿úítô÷–Iõ¿úå¯sä×îýéç6&UR’¨>Õd­Iš®™U·äÕ£²êöLÿòW\=2;Ùóö{ý»I¾ù­z}fîûl~K«ktuošU¿Wç“* Õ‘ŽÜ“•öùysbî‰ì{«¬äeó^“=ó¼ú¼A• ¿/>ùü7¶Ï }¿|ÞØ³Ü¡3ÁëäyužÑK¯ÉK§¨ÉSçØæÛ~¯Ó'žW˾£¾^5×¥+4¯æ7ÇT›t·2Èí{”½VÈ!;Îg5;mG¯oºú-5ìª÷$Nc›÷ŒÂþÛg|š ïÐôµ²<ÜûëôÏ|obßUëôiäóìu>.šw×ú®m>Ëæ]í³´Õ™Òþo>3!3måVÚôk«§¹Ð¡žŒ‘Ù€ÌbË2½"»b¦ì"Ë>kî­?ãæ=µ,…ì¦ä´ ›–/̦% ´iÙsËG*ŸÙg2ˆ½miù$þÿ6ËÙÖÉõo±^}½Ó£x8¿QÝ.øI´Al»$Õª>Y5±ªoñ×/ÿþy eZ:QÔ|&‡)ŠÉÈoæ¾s•lU‰å4-$åΛÖÓgÓ‚ª[Oî|•Ü“ò-.sNÞc²3tÞ¼Ã<'ýè>³óîóÓÜ{ÙZK³‹ÖZ:Ñ «%qn4ov ,õ¹›˜²Ú_Z«è±–êVK´)¦Ú^»®¡ïn9ØÞ’y¯cѱ†–͘•ŸÝÒyDëF‹–Œ`?øÿ--Ô¢iŬëX~êw]êô~gÖ„Fëëõ 嬬=—-WYÓŸDëSÝТµ-Óº•Œ¼ß/o'ãkrŸ[¦=×#ç‡r-{SÑ3tlò;z†Ñ%WÚL;l3eà)òdžŸG—ùkÓú)<¼ÂCÔO»L›ë^öÊ+}Žt˜‘ô¯­³Öºzmuκlù€‡CEQ,\ј‹C5ƒ=&¿*š(: ÙA¦<ò®…èÐ\{ÔW™ÜUõ{™O 2Eͽõóì½Ú¼Gû­þ3CóR§·w¯¼nv^NóórJ›¼DŸË8Œ|סÓÛï=žŸ>ç¦ >6.¯{ŽEÊKq:ý»±Ó÷<ÿYÇùÜËG´ÌtÖ‡zÌsŽ'oç£hãG<ïzux²\òÇêk´a}­uÔèkfÿwzû1ÿY™Þ®oà¶éœÖ=ŽÉC¯?¦ÿ0tþ:§Žo»¨ò9åCa#†mŃëÅÓq›m—B½·>ßRÛåT ´Wâð¶vLÞ„ RV浯êó¶# 1RI¤—®|X¸ %Ý]`RùÚ0Ê6dÐʸNgâoo3ÐT†»A&;ðÝÌé;‚n˜†Ö1oC@ëFp9ˆH70÷œ²:ªü9“6]åS+;hqµì?38÷Þã(ܹbàÙ)S nîn3ýa9Ó ?ê¦:¸ÂÑéBžÈg¡SFd„Ýša·lx}’`·g·b1%íÚÌdŒÚ/!Wäôû•Zû•‹évVv·ÈmÑ6MèAHÐþKƒÐDÚê 4}=zä†zÄ¥'~Ô%ۚܓ12{S»XLK­?ËnfJNØ´™}zÇž~ý2mš«ã]nÒë?ê€~¾12[p_DNØ´—5H±i›êó÷d‹¬VÔïŸÝâíÛœñF½¾ ûRÓØsk}qwÉ7¡÷]Ÿzî½§£Û…·!{ÎïâÇèÕŒö€zÃÄM%|f2½k÷›Áö{d_ÌðÑoðÓ=?ý–|õè=z¿G½Ÿ³œã«§i ]œbÌ£…òŽ` kX¯‚uºàE…Òù‹ ¡ßè7¬a kXÃÖ°†5¬a kXÃÖ°†5¬a kXÃÖ°†u'¾91ãÕÌ{éCÏÑó›cuâÇêÄè7ú kXÃÖ°†5¬a kXÃÖ°†5¬a kXÃÖ°†5¬aýdÖê¬z^ÃÖ°†5¬a kXÃÖ#ëó†ÆöÉxÒg®A^øu“ß½ù+×°¾gqy¸µªçœ ^Ï:ëî÷d5É>ñû®¶Ï•ûòFá{ñævÖÌíËzhÖÏäü‘?òGþÈù#ÿÉ?Fþ»/ÿö>äýOzzüw,ÿùÓþ{üí^síþJèýäü‘?òGþûm¤—{™¢Øä¿ù×{¤ÚñróYŸÓ¶Ìžüïõ‘vÏË_‰}…ìtÛ Ðôù#äü‘?òGþÈù#ÿåÊŸ¹Ì•€5¬a kXÃÖ°†5¬a kXÃÖ°†5¬a kXÃÖ°†õÎ×°r1¯…­e «e?ëæhÇ {òºk Ú=ûÉq·êÅ=é‹]Í“ùºrkºo)+WõöQ6H2½fܵ÷¦]5zR¿Ï•+WÎ>m&µ²ŸmbŸŸ:g/ïºÌZýv6$Ëü¹ú{"ÞY6ö¦ÎçÃ+¢Ÿ~þúåËß~ÿóÇ?ÿüñë?Nß¿ýý›Š³"ªOÿqúQ}¿~ù÷Ï_¿~ù—9õÛ÷êÂêäO¿üUT‰ˆªDUâ¢êáEvœÏÍKëG¹w¦"ÍãÇt´Í×¹ÑÓä£ÉKpYTbïð€²Zö]_+[—áëè'ÉýuuêêÁ³ÐùÄ~<ïÔÚ»¶Í%ÊÁ¨~‡ÖÝLC[—™òxí;û¿N›ç›­Kû?¿uÚ“euDÖy"Ê9ò\˜<ê$KD Ô §CSsh#®êÿ³y\Ú¼rêÐ׸#R×ä×d;ôóÔ?*Ù3®7&ç–Ãè0ÇÚŽËVh\ÍPˆ&ØA4™N¾ ’n¡¡æêhUÚduËþ;k¶Æbyƒ{]{7¸—nu-ši6£Û\Ú¦Ñ+Ý¥"}µË´nƵ»MCõ8[†‡¾»fthÜ#ú$ìcjÓ•[÷F,Üsݽçë+Ïï¸83ÿÙº.¯œC§Ð)t B§Ð)t B§Ð)t B§Ð©¥ éÎÊ XªnÕï¡Mm~us¸ßêp™ÂbôÀóõÄóûºªSòÜ.X¤^ßÜÿCç–ÆBÛš<òa6¹}N]úa>sÓÑ+Ÿ¦\ÕeÈúõÝ’,ã¡9i–Dæœ>›Èœó¹9ܘGaí‡ù<Çöw3dn®)šñWwoþé¯‹ÌØluî¨Ãî•ï4ÇØ½ÅñòÞƒ±y3v÷aŽÜF¹{£f¬îhòaï5ãÇf<3wùí=31ÃÖÕsN'Ï$”Ã1·×垃î%ÝkÞÑÊ`‚¡Ñ• ƾ~t9”öÞ£ãPÎã`¸&ŸÍx·KWªÉ%‡‹¼8¹÷î=%Me^:ü]^>Ãó¢ã&Í<{ì™&/užÙqÛŽž¨æœ²5õpûÌcóÌ¡ÇálÃÓú÷ªhX.ÅÇpY;Y¶§ŠO™ °­~+ó¶z«ÛãõgN±’W\}–†‘`kÚC®\©árdìc§üJ¶ÕyUñ;Ã÷;©·#:oÊà˜-Šn,ƒ¦Ž1ãZ¡ePü½¥º¯ :™šróñnËrÞ{}jß[Z¹$Bî3õsˆaV\×Ï!]’>‡î½¢KÙǺt¨×nà ÒÛìù½ºT‡Êßasó’Èt =F·ÊâÁé±åän˜ý±õü  Œ§ÓS÷ïnmܧeÙõÁ6LqÙ†9ïoÃŒÉÅä±åP6Ï4Ï“zrkÆÕ5OmÃÌѱbf&ó÷~ÄaõÚXÛÙOS¯š÷ši ¡mì¡¶Slíû˜n×혱¶“ê¶NyxÛÉć„´ŽÉ°M~xÛ©|NÛIÝÑvŠl»¶ßvʯÔwåᎶӹé;Í©ïêXž¶Ö†IÞØ†¹Ò–û˜Ù†)ÊÛejÒ2§ £’µaŠnzêgÞІ1ÏèØs=Ìð3¹]¦¹ºM¦æÙKmývˆ¶è|½|+K'»G¦}ªŽšìß§ýûòʽz Ž:të(sïEÿþÔ¼Ó”u“§Ïê9‡" îøèÖ&}*Îóá4ç~Üô¹Çë¤#ëgè©ùTÖéx¾]¸ý˜Ý˜äÙ2ËÝàP.)÷\ž¾vNäÕõ J‘¦7®_PÏùŠšŽAò†5 Üûï]ßÀ L]|žg®_ˆ‰™KkçÓj}ØÞ—'òOì'åTÌ\g"ò6­•—›³XÚÁóòºœ°_3ì—_ÞY—ûµû¥{ e·îwƒÏ“6­'cd¶P›6!'lÚ ›ÖÛ'›¶0›f9Ôº^t÷8´_sö=Â~½Æ~¹ú( ÛŸ û5Ó~)_G`¿f¿b[&\;ÌùcÊ€¶XO¶Èê ¶,µvÊí¿îu»ì6³x€~óÂzCk­.±NQbuÊòúù®Ÿ‹ |è»ìÉ™-´Ÿ?!'lÚ ›¦üB3ôójÓ”°aEwq(×þjËEOžÈçEö+uLÒ­g¦d‚­ši«Ò^š°U˲U®__Úú\Ú/· ÙTû«'cd¶öWÖÛÃ`BNØ´™cÇÚûƒ±i+ðSj¿®üÕ1c![dõ"[–ÛTˆñ`7A_5²¼Uvø)ñS.¾\x]§NYX"¦©uG _eà£ìÉy-ÔG9!'ìÙ {æêpÚÇëòOæ—ýÆA{&䋼ÞlË!?Ñ¿“vl†>1ìØJ⑲n|Þ ýrENoŽEÊíoÉír£Oÿ~ÑõHæÛ]Ô# œ+Yˆ1_¹g¸ò¿MÚ%!_äõÆú$÷mÞv3®äF}B}²ø1ÈÿÊbû%bŽj;¶åÆäCƱR|+oõ­ä"^5¹Œƒ“6læ˜ök¹1a¥ˆËz±IÀxököËýVôƹ°]÷Çà'ÄàovýžŒ‘Ù cÁ o³Úú(õ’ˆÕOˆÕ¿{®d)øbÓ¶3ÞÕ“-²ZȘW8¿uDvø)ñS.¾NÑÞ/O²±:EÈY­hÜkBvÔ)Ô)«X»5Ã÷²Øx 9æ¥Å‘ÄRô䋼^X¯Ä½¸âRø`zm‚)9aÏfú’1?{¶®5ªò›Ö“12{ó¸˜ð_¬‘0"'lÚ 69`ÛÖ“12[ðœÖ„¹`éw&Œmr}êžl‘Õ mY)ú©ði–·Ë &>ÌUÔ%ì?µÍõ^zòE^#õIñæ}§özä¾Sر öó…|‘×B×z‘vlæZ/Š8¥ÅöíSÑÇ—ûè]{rEN õQŽÈûuÃºÔ ãÈ›m‹õdŒÌFì™ Ý3Ûcr¦ݰÎEÂ8ò"mšó‡%"nB´Ï‚Ö¹H?^Ýœ¤ Ù1öÂØË*ÚÉ‘/7Ô)™“Ô“-²ZÀœ$íë‹‹öñ€œ°eø,7cÇ Ñï/EyÀg¹þXq|–‹/èßo>N¼ Ÿ¿Š8ñ‚8ñ‡¬£dû1Ø´ ʼn÷d‹¬V¸ŽÒ€ìðYâ³\E¼x,ÒD²½xñøroväµ 6ò„œ°gÄŒÓç'fœ˜ñís\Øw`Ƕ3Ž,䊜VÔÇ‘ý{ú÷«éß3¶½˜$![dµ ˜$=Ò·gü뾘¤˜µy7gÇ„\‘ÓŠÚÄ#r£ML›x>bÆ»¶·oHÁ8×*÷ )V6îE=B=¢ÄUÖEØÞ~!=ù"¯×)™àïú'JŒ‹•×å„=»ÁžeŒ;®r¿, ÏÞ“12[ðœ‰9aÓn˜3‘1'Ó1aBÆÈìMí4%ŽÒ¸°9aÓnÓh§-Ö¦ÅÂ&í\*ÖUÛh§-ª–ŒíG´Ó²vqJë—Ñboѵ“ˆSZçz|z…±K‰X»hiã3"mŒÏ¼ Œ?sÛs[ñe®cn+¾ÌǬYMÜÒv×àO‰_ZôÜÖaÇfÆñGÌÑßdÄÜüUÆñG+›¯Oü%õˆì3²â2ã/ 1ìd‹8>Ð&2FfõJ¶°¸þ ÙaçfعLìiÛ^¿_Èy-À‡éâ™ä¾"#2ÂŽÍŒÅTž9¶lã1…Ø":TÄ` Ù"«7÷ÿ3q¨Ûe‡À¢ë”œØþÍο̉ék}¢E_õÖ_*§e„ ›éÇÌ™C¾èXþ²W¢á9.£~°œ9䋨sOõöè-§e„»!†_cË=\Ú¯¤çÓñà{öR{æÆÒ"±÷kܵƒû klÚC滘VlÚ¶Ö‹²EV+Üso@vø,ñY®¢L,ÿ6×ä×Äñ¯rÒ ÙQ§P§,¾N)X‹t³ca=ù"¯Ö+qo^X)|0ـߥ`-Ò‡¬E‹qìÙòbÅ2Û]ríØkk Ù"«Ù29†_vç²:;&ÛÈSr–Ý÷Z§´é5H…Œ‘ÙB×"™6m†MÓ~Ž>Ìù0Ë[Ö“-²ZѸ؄ìmß´èç-͇)Ò†óëõiÖ†Ùôz}š5bV±^Ÿ^Ùš0KçgLfÛköiÆe®Ú4 »g¯Û§—yØq‘/kشٴTì…˜ôú¡{ÃE—{º#«…÷û'dGì±K«h'GŒm66b\l1ñ°Ú¿ó¢}1.v÷ÚÖJì=ƒÛÎÚÖV®ÈéÍsøK±çž´a#2Â~ݸýûmû,cúù‹‡‘6m†M+X~s1JëÁÊÈõ?–äŸ,V¶<~Iê 1ŽHŒë†ã‘bâ\ÑVb.R>ox^þ5[öŠzD‰}’©Kž³ç!>•mîyˆåµk!FÝy\u½ ˜ºMnôI蓬Â?¯ðim6VBáÛZE¬„b_ûc‰SæFlÎŽ ¹"§•ŧ+›A›˜z$ñ}@ê‘­Ï^à›_Ý:º#r£¡YÅÜÆ ?ýjÖÐ-D£~ ›#—á¯_Ì^l‰_‹²³î䈜oÏ´°×ìYy¿-Kí{[¹ëFÖ­Þ™´É­ÝsåC5éÊ>Äó]þÝŽææ¹F÷ô©ÑÇú3÷ºàòUëKa?-Ãú½ÎÖŽÈÀØíZo?šë¨ëîGH}Ü1uÝc›".ãþCâš„l‘ÕÊö ‘ý'úO«è?Ŭ߷É=HzòE^ ï?Ŭß÷=Hlý=[à\ØX´KQ>”·kA{‘Ø{‘Ù mšêõmrq®?·BNØ´™6MùyÇØ´Ú4áûl׺pu¯®µiBÆÈì…qé…_C©­Ÿr[“ölÚˆœ6gÓô âÙ'n¹e#m²¸×M.cl'ãÓØ3n\néƒ×YV¢ï©ÅÚ²²Ú·i)ûÅÝω)lÚòühJø÷]Yˆ½þ_òE^oð£‰Ķoéô¤¼.'ìÙ{ùWµÌÝ4p^‡#ry£ÿ?î­ìÛKüñSÄO=wo§„:m“ó°­l‘ÕÊâ§FdGüñS«è'“»Ýø©˜¸Ü·÷Ÿbñ]MĤì%ðXƒt¿ñSA}:™ŽÌ¾ÏíƒåÒ£Å÷sûæ‹ô%¢gËÃ[× ¶>ˆÉr¬g¶éãÞz ¹(Çv-¸ Øˆtýñ^Úú¦úÿí˜øP[­oSÆôð ž‘ú¼Ö÷žšô;Gë»{ŸjØÔùwyw,¬}rùŽ>~úùë—/ûýÏÿüóǯÿ8}ÿö÷oq®´®Oÿqúa¾ýò~ýò/sê·ïÕ…ÕÉŸ~ùëT=ì£PQ}FÕ¡£FxÊ8Ä¢æ{zlþïÉðq>û#wÿ—ÃÏußݻ͡Œ²©÷Eÿâákë#»ñ8Ýq¯<ôƒžó‚ãmr›•ÎFÑkõµz®òX(£Ó: …ƒ¨\'Nw;x}ÃÕ¯lsk\Ma 5vm fnXúï3p«h\e3Wn!ë6°^Ý€êThÙ‹:ï¼¹!òaÿ¿Å˜ôË•üÏ|p9œïà,ŒJÛˆsŽZ1صfΫéT7}æ¨óX}žŽÝÃÌó7Ÿ¦,–6eÖÍsQ=ó5×Öë<˜g}PͼþÙy=æ÷Gz¹óû£+‹¼mu~,^ ™ß¥°ÄùýýÅ~êºêìvq¾»X¶S,ã{›zî½çb1°Ü·s´µéή·õ‹µ›gRøÅ6ŸWåeïú±úlrý¬ƒ-Ûj'òqŸn¼Ý3AOÆõ¤ôu9z‚žŒÖ±¶ß„z¸·Àx„.µLÜ&•)L:ý »@å.llÙÝ`{ÀMwçùPv`2ĤGp1>Gáû°¾`çOÈ)[71£\ak`˜À&d’vcvñÑ øèöÞ²m´]øß_¦ŸLÿç©>·AJ ü·0¹ër#b³ðv-Üàvw?*g|rv;ZŽÁÐjx>Ž—ºxˆQ=Ɉ¯ê´ Sê­YÌr¯C»hÛ$”•¶½bý:ø¸ñûÃ&0 L`²d&‹ž›§üþÁsóÜõci Ùˆ|d±óçv¬ZÞnþxáç·ó”³ÞœáD¼O¬Ë0Ê' Ãs‡ó¸˜;\˜¶ê±Ñ-u÷Ì6ï-̹*íg3¯öÐì£m柫ßNŸÍ½F^î:³o¶¹ö¨Å½çñ{OfnnnçܪfßöÎ{£æÞúyöÞz^°ö{z÷Ÿš—zÞrï^yÝ켜æçÅÌÝîçå˜Í¾Ü|Êvµå=¿cðÞöùV7ÌNîŸÜ®eqvÅί×vIºëÅ´k‰[3jCBç ?0 ?ˆXÿ Ýïè0à[š{N¬1éΙ´Õ넨&?ûÝ~ýg&çâÞ{܈?÷âÙé ÷³O_«çr®»l󴌋÷ìs_¯£¤Ö4Ñ/ZOT°pï¿k-».ÁàçÜ=Ì\Û2³Ÿ¥XgÆ©èéõA¥|‘×€¼„ ™”[qƒÜòîØWkãìANnc2ÂŽÍ´cŽuŽ-[œ-“1N¹×‡6.ãš ²EVo°c©çßÎ3Müš·ÈnÑöMèÂUÛ6±nÝSÖ!ì­3>¹fTIrsû5Úõ©S–Õ>Ÿ‰úEwÛYƒö©'cdö¦6r.æe¸ýycÑFž6mf;Yû}°±iíó #³÷ûGä„M›aÓQ¤Ø´Mõý{²EV+êûOÈnSkkÖ"~z|MÉÚ‹µí2ï<ªg#cÓ5Ê~Èøó»týú´·qÑ=Œ+î±ß»—~ãžÆþöÒËí·jfG±%gÙ»WN9_Þç\+õ{ËæhÓaÍKý\ÕÝ~®îÚ ·õ=]ìk\?Y´“s÷hŠö˜ IÊ r*Këv9SfÆCf£TÍÌ%e"?§ÞÕ­úÿdg¬˜çÔ½O»Kœ¼§p3rœWÌü‹ÝêìÿŸö{õ̳¹ÎÎ.1ï8©°ç»]êBž¶»Ú"/.wo¿¢ùÍÝW~Œ¼'i®5;ó ¦¿ú=UMš§òhî?œü5£×Šçõ¤ÉHÚÎÍóÌQËõØ“a.Ã9Œï‘¡›-$Ce™šbÚg¢>š÷õ™,1¿pz~‡ËÊe+*Iâeî™øÞÄîvLfì¶^¬tçÈÝ~µl¹™Æ™¸WŽÎk»K­ØÑ|Jæsv]M‡žH‡\=-K|„k½³]Aµ]Èì¹Ô÷¾\Í\ŸebtàCü.wwŽ…§Ñ¾£N¯±í®ËRÿ¬»F•v"»,žK'»HÈ0êÉ«¿ç› ½œZyÈçÄ—òD6Wdc»I-Þ¾§¾ %‘Ð}YfÊ™\±uC:/w¢¿¢óµl&äêlÚ…\‘MP=Ôz³ûrè×…('ý²V6ßGmÜ5y'n‹‰]¦i-´„Ö‰h—©n¬-=oŸéîÓ—m¯,»}Æß®ê÷DŒšÔ ·Èq°-]ˆ’9]-O©Ø}f ^I’^’‰wõåÚïûènßÖG¢-'Ÿ¬ÛÑ@{7°<¸ëêçŒÉ¼ß/¥}ÞŸ¹¥N‘åá2FVåHÊBÚ)÷4\BÛísdîê3du½½—¹ˆ…xÂO#Û ÊöqÔx¦•EÑë—N”?dè+}BýRæO@[ïõϘŒ]9CNWÚÒ—óB¹ Õ}ÈêJ™zR» ½oF݇¬ÚOhÜSþY€¿ïÎvÁ­}/d ›'×?!òFNvïõOhßk­òÒ½ø)7vÔŽUåMøîØjñíxSfŸq²Qÿ&-…»’!¿Yo5ÉÐXª!V“ ì Ò:"õXX9±²|¬Ó4`eù(> /³ªºû<ÇA3â¶ÇYYàPj¶Â!ÔCcNõIèRjÓe‹v‹ æšùÞóõ•çËISí$ÔÌOº8—‰fëÚ6õˆf,n)'Ë?cSõæM=ò7¼©‡ÊCöô(›Éuäñ¡ÙÇ¢V·Þá"ŸMd´;ÌDš±ëÝ=æ37-ˆêøÌÄoiÍl¢š]dµŽº÷§‡îûBßY_ù(ke÷Ìp÷™g˜ÉAFÅÕ@Ôt–¾nî™Øæ™¹gãµÅ#–ãbþÙöæŸÉùfCæ ?'Í:¿>¿9³%¯|Þ€Ùºåýgöó&ÍVm²Ü¥™Wwð×$׈}èЩ·Îc׸ÃÌmý-zÝѯU¶p˜]£äUKM§)Î÷¯NoÍ3Ý–®p„Éýx‚ÖmT-ãóúmTÿˆÑ5lÈãìDvŸÉÖ{…}8ÅûÒË{ëŒ%ÆýaÀK­×ž~(úhK?°]ï±]F_‹…Û¹)ÛõÈø·C¯íú¹Ʀã¤Xîè´›[ìt–ÐÑê…,À²™íÝÒ¢ïÜ‚°¼²øÐš¶!Œ ÇÏ`.–kmÓkXÃÖ°†5¬·Åzî6{r«œgmµ7°E [í-h™þHl1Гìa{ØÃö°‡=ì—Ë>$Ö=•éÈìûÜØCá' ¶ßeüw,Ò—ˆ7©Ÿ,ù)üvºÿ÷õ%ž©3½-çèÍ­zÔ¾3p<(·[¬škãcøøÐ£Ó}Kÿíª®<ª_\Ì)¦·gNû=3Ü ?6ò¶Yn=ƒsT‘G!SÜLp‹=Š“ÿ_%—¶ù2GkóÎ ]šyšÔÒo_^B?ay W#*±±õIœÅ\äÎ÷u×]›¹çä8X> Ñ‘›bOYÃî̯xbG5·ƒQšùy½ÅÉïr¤TóûççåŽGõ‘tŸÓÞ×ÿÞ{ÎÙîÖ‰•îIù}è9sÒ3õœNz>»Ï1¿ ¥çã‰é©«ó)¢@> ”òÞ—¼ûò:–¯•—LOÿ9ïП¡ô„>o ‘«Ô/Øêä='=¹˜}Aý²Ýô0[†ƒƒcs+S$ráÐj—KÝÒY ømÙÖyØ_­VºæÞ¶*IÅΪ»tu–lû¬ºË]×þå8p9ø}Gß¶y__gè{g{u¹ml‰}tÝß´­÷£ûèþ.ì>ºî¯ÙîǺoï›e÷ºîc÷Ñ}tÿíºŸX{L{ÝÇî?Æîbï!tÝÇσî£û´÷Ñ}té~žë»Ü*ܽou{.3f­f/^A«—Æz%­âÊ6¸%v;DŒî£û«kÑvõ ÆZÐ}ì>vÝ_·Ý¿¦ûŒ±£ûØ}ì>º¿}»ÿàòÀ õ¬PkXÃÖ°†5¬a kXÃÖ°†5¬a kXÃÖ°~kõ†QÉ[Êxs“+F÷‰“@÷Ñ}Ö A÷Ñ}ì>ºî³ ºî³ ºîãçA÷Ñ}Úûè>ºÏ4¬AÃ4ءءÜÛ,£F÷©ƒYƒݧï…ÝG÷Y‹€1vt»ÝG÷±ûØ}t»n÷Wå[f~¬a kXÃÖ°†5¬a kXÃÖ°†5¬a kXÃÖ°†5¬a kXÃÖ°†5¬a kXÃÖ°†5¬a kXÃÖ°†5¬a kXÃÖ°†5¬a kXÃÖ°†5¬a kXÃÖ°†5¬a kXÃÖ°†5¬a kXÃÖ°†5¬a kXÃÖ°†5¬a kXÃÖ°†5¬a kXÃÖ°†5¬a kXÃÖ°†5¬a kXÃÖ°†5¬a kXÃÖ°†5¬a kXÃÖ°†5¬a kXÃÖ°†5¬a kXÃÖ°†5¬a kXÃÖ°†5¬a kXÃÖ°†5¬a kXÃÖ°†5¬a kXÃÖ°†5¬a kXÃÖ°†5¬a kXÃÖ°†5¬a kXÃÖ°†õcXWéOªóIÈÜ\Ÿ6ÇhÚªgä™}ViŸ¡Vîyõ³us>·Ÿîù.­uÚÏö»aVØ<ÇÍïõ32®þžˆ÷™wmúÇøä/úøéç¯_¾üí÷?üóÏ¿þãôýÛß¿ÅÕ_QŸþãôÃ|ÿúåß?ýúå_æÔoß« «“?ýòשzÁG%à¢úŒªCGðUõEÍ÷ôØüß9’áã|öGîþ/‡Ÿë¾›wǺy¿ª2©÷Eÿâákë#»ñ8Ýq¯<ôƒž3qÌaîdlßùŒg†¢×êkõ\å±PþF§u"ŒÏA gDu×Àö _¿ šÂ|4…-ÔX&ÕoIî Æ !îÀþ;ÇŒìâ ¶á•lÕ\kXV–ù­µã*X#;—ÇÜwëê{5‹Ë»1úõûÝ}…¨h\ÅSzŽ¡ije6ðÎtæ;ûóYÿî6$´­ êëÝ3qŸ“·©¤K+KeÏ9Ù4:à<“³j®¯mŠy^áË™+?µ­Y1ç4og†­>5i¨?sÏ!•éÈìû [æ]z´ø~nlBm b‘¾D4Ü,÷w胹'‰Ä!õ#™¡î9©-“¥ý[ÛJùk8eÍ5myú°¶Ëý?ô{aY»Îˆ”QÖœ –“òq6ÓÙÏ´+¯:ïú²ã§ÓFçÛ¶Dle¤š|‡0+÷šÇŒ¶L]æÒæÚøÞ¶ytºo©ó¯êã#ÛJqx;éÞtßSÆSѦ®õ-ñù˜²Éé„ q&óZÓ #cl9¶ü–>@>‘þüÖô÷îuý‚š±“ãÑû”j½w: œÆî¼s캶¶oß¶ÿÆ•~Q"|Îoäú´…?‡c]þ¶o]ö›gé†ë/çV/ ta—ºàü2Jø» ñ¿F/v¯N'䨾qô{± ½º Ñl6½@/Ð ôbSãí‘ç~óx{_V…õ%ÄÞßÄX;ã3ŒÏ0Á8,2FÆØrÆÚkÇö1Ö¾ø¾ÕT?IÝ0Æ:4þîÆPĘ+ý"êRêRê ÚÌÈcËéÑ/ÂöÑ/b,òM±±ý¶µé†î­×áÊjkòæ]c‹õ´å;ó2v\3×uÏMÄzÚ]:ûÒ.àsôßÛ…‚†ÊâÁþ–wó džîÑI™¬ÛS¨f}÷)ÿ?Ÿ/×I)–¹JJ±³ÕQ”ŸýT#éûWEi­æY,[ee_·"’nˤ­µÄRV£KT…®¢òÀ48ËgôפÃX ó|Wâjfÿ÷¯_Îè…,}­dé³$ÊB hÔý”‹—Õǧÿ=ÿ¸¼ÇýóÙ‡ø]]^×þnÐÆöšgñôa_»ÆuúC¯-šO—¦±ëÊüqéëWòÞ—“K§Y mJFçÔž¯Tñ3I³šŸÞöñmÇ]²In{çͲ e¢ºúÿ.ÙŒ±›¾ö²gz™M¨¸GSê†ÕXWД é »SÇ‘t%­mmõœÛô¹t¢èÃ.ô! ù¼÷.W‡ÑáÆsj¼KÖ^+ÿWb`cÆ}AϾãÞà´;GÒµ÷«Ç¤ëê;å{’^wYÍKOHÚL ˆÑ13ØQ;“mýÖòŽ÷“ç»XŰšÅ* ¿~°\®)¿ýúûÀo÷þ¶ázé!í±4 ÎWbaÚw·Áb¿XîÕkûå°X®}©psÊìÓøFë®iÂêÑmC¹Q\iò¿]³´iòÛóëé%äcUc=ÉŒ@oyý3B¥Ô›C¥ò7¼KdTÆ!±RÕsΑ·)\¾u³ `Z4ñu°«8t¥QÚf\ÒlhÎ×Ï+üóâOb&–þžöQó=·×›Ãð2 LÞköü³7]\ú¦o?L|ÍŒë>‡unÿÝø3à8EüXÄщÇê•wW®¦Ê}èu‹²¯àI™£Ìm¸ÌÅuò;Žwµ…°Ççz팧?•ØcŽç‡?.ç+¨(RÒ‰‰µÁ6nÚí>ò+íîr"B1#ÐX?&ý•;Þ·,ìD7³>/{1Ëglfxðìï‘™ïr¶;“_¦Ù:Îr·‚F9à´·Ú¹fxsV¸kæ¿(/†Cý]èD=QˆßÜàÊ-»Æß¹ø«åþpαàè}N½r{岸q=»3Ù.9ç~E¡–ÃI|F7èn´S–Zè¨ü?Ÿ·ØußΫfš 0¼•eC†0„! a¸0†JôoÜOXÞÆ²ðkpµÿÃ2xuú–].úÙ`Zì¸/‰>uÿsÃ}ìMû)Ó½ûüvZ>`CÎc¸èqŬZ¾¤ÕÈa kXÃÖ°&æ†>ö[bnâ^\M?g,&‡>ø2ã›\|±MÄ6ÛDl>üL0d,†«b¸é~åâ±±×þ'–à=õÞž9Æ~'ζ¿%6“¸øN»‚¶ aCÂð=õµôG_«¯¯ŠøÊuöÁÆäÙÿ_úÍoóYå8j>cáL1Îð²1ì^:㢗–-Œig2ÈÖ7†]/¾¬š…¢Ú RS›®Ü–ãX,‚:·Nè=__y¾´Ïõæ©ÒOpB§îÖ)mÇóF†kÚ¾µ¹×ôJ¿¡®é‹‹^z‰1#Æ Ö°Þ3ké«~ñÆÈ²`úS©Ñ¢Oþ‚6ÄEÚŽ~A~×ç4¸6T>³ï9»½!x ½ÿæ¾ï‡ýÿFßÅ|à oxÃÞð†7¼á½Þ±ß ÏÅ¡Á]‡7¼á oxÃÞð†÷ xÛ6¸?®ØžO½,´¾ã0ýæ…¿ˆ-kÕ¤s® /dš7ãæ†‰37¼êÏܧ¿ŽeKíXŠnÞ›g~|űj7͵,ÜfÅõ}©¸F[æ©×…v¼A~?u7ãJCž„ëÅ\=žCê݈ôÅÇð½r(cØ4xÃÞð†7¼á oxÃÞð†7¼á oxÃÞð†7¼á oxÃÞð†7¼á ox/‹÷+â¸ÓsËë/°þ¬a kXÃÖ°†5¬a kXÃÖ°†5¬a kXÃÖ°†5¬a kXÃÖ°†5¬a kXÃÖ°†5¬a kXÃÖ°†5¬a kXïõ(ãt˜Mnçó™{Ÿ¾ï{ïÜKÞ9°×¼›;yלIeßcõ¡þ®Å÷¢9Úßb»ïð-û¸{˧?é‹ü~õí;]úc›·Â¦_ù9§AyÈýÞ KÞ¤£N_î÷hn?£Ûç³>,Z0•ÿgâó}°ºöдfi»5i#m¤m÷iS¢>I¬ÍZZíšÿßF™¦\ÔÃHkñ„º#unÿóÝuð#Óö¬¶Ø^ø‘¶e¤­ß¾êËŒõm–ÔyEÿ˵Ûé{Ñ÷zVß ;F;´]¶}¯ý¿µ¶ï3ÒÛ¾W.ê“TØêþwli#mËI[Ük×^+¿®­³„t¥«ÿ¿lïÞØ·hÓ½²õ1I/é%½¤w5éµvºõAÀšô’^ÒKz_Ÿ^kƒ]lS}Äö|úœõó‘é%½¤—ô’^ÒKzI/é%½¤—ô’^ÒKz÷œÞýúY~úùë—/ûýÏÿüóǯÿ8}ÿö÷o*‹UZŸþãô£ú}ýò~ýò/sê·ïÕ…ÕÉŸ~ùëôY= zùÉLX¬u‡IPQ½àÛs‡æš¨zù9µ÷V‰9UßËê3RMBêÇÛtÄ‘H[óJˆù¢1ÏÓÍÅÔÝyšýy”Só'Cç·Ö~º©¹œ½ùªý÷͉]ôüZ‡”γU>F`p®mèžÊ3•åÊÅD¹˜Î8òe¦NÇq>«‡í'ªß·Ÿèbâh´Õ™Òþ_ ·‹õVÓö·/cd6_”^‘]1Sv.N<³õE&b‚Ê®ì¦ä´)›öL{æÆ~J‘žØ3Ý«»óVÆvú7mÚÏŽœ2ûŒ“×õ̵áÜsÁÔ±O½¾˜<:î»Ó—ÁºÞ¶ë4Š<¸ò}Œ´±Ò´-¬x¤…u¬²¢RÛ¢2bªþÿ4­¨êÿ²:¢¸iYEÕëTõ©L‹Ë¼2j÷ÝÜßþÞÞ?ÐâÒËmq鶸ìdb?«çi-®RD¾ÙB½²¦¾7ж¾N®r’[–¹8g¯ÉqNÍŒ((„œlÍæf$ ÉpR&ÎRžÅûd™œÑ2çrË¡¾îÐÔ,y²½x{-%{:¬©Š+5URD5ÕéÐÔ8ڈͼê8|§Š«Æ®q‡©ÕF‹^w…ñU¼òOÏÓg×_³Ôt|ÏñêôÖ<Ómé G˜ÜMkÛµQµŒÏë·Qý#F×8°!³Ù}6&_X{ìöá¯WÇŒ‡ŽÃÇã}÷¯FöŠ>ÚÒl×{úäF_‹´Ÿy××+Þc܃¡×vý\—#4*NŠeŽÑ(1¢Zì4:&{À*ô7ŒÓÜ:³h¦bäù*ÏÒ¯È÷²•ýEúêÕýK1VÇn ì\kXÃÖ°†5¬_¡Ù[µî)qP.:v‡‘š«ˆ<—³]z²‚=ìa{ØÃö°‡ýrÙ§y³³¨amb¯MêÏÜ3He:2û>7öàÒ£Å÷³E×>uú§î˜¿MG¦vMèëK|ûsõæV=jßyÃ\ø>>ôètßÒ»ª+ê3ÆF„Ý•vÕ”ÿú}N>Ä܃¹ubŸŸ:gQæ ?‡¡_‘õæ9$â¥÷N0¼Âaxåƒ"™ìP˜ÿC³ðAíO©ÒôamUçë“p]EjƒFŽ—Ï8'ã÷¹€°$öA'³Ÿ•ˆgÕ÷].§— .¤CÆ5b;3qöpqºÃEÜFšÑŒEâe ‡¾»å`Ã\u›—=3aÙŒ5%‚§eTy² ÐVï2ÍÖ”äVßêûú¦L˜ ÚÜX³Ô,xTS ØŒ5ßÝ5·n†­Ì%ìÊP^†^ê†ï¥/.Ä´½µ»ã±_د[íWä7™¼kq’Û-B訨¨ÄÿÉcê€Q=qÈç5¶Õ×T?¯G†\BQ¡CÖ¬ÒÏ¡qŘu0Œþ©H¬‹1´nE2|Èy?íÚšåðsÝw÷îẸ̀ʞ??éióiVÀr©s¦O92½«|Ø5i7='ú^YÄ”¯ÝÊ~Ƕö”;ê5ê5Êåz @Ð%ti…kÂ.Ïé²ðk ¬5’ÄåëÂÜÀ’Ý5„õC&Õ²ÇïFñê9ŒC;c==ž »q9YË]qœî+!ÛÎwqV½µö _ÆÚˆâus^͉êCfýÚCêG2C?ÜsR±S•+Ÿ9å¯å”5×´åéCÌùù½°¬#ϼ•Qoݤ›vKü®WR^·îäø”y/)s^ž±ν_f6XÖÄz|µ®%>Sö8Ðtà1k¯óZÐ-´ýn› CÂp{ YŒõÁ` kXÃÖ°†5¬a kX3.M?›qiÆ¥—f\šqiÆ¥—f\šqiÆ¥—^DÛ/m½Â¶õÜú»jýí>Æ\aCÆ­ñ«ÁÖ°†5¬a kX/||O-”y/}è9zkXÃÖ°†5¬a kXÃÖ°†5¬a kXÃÖ°†5¬aÍœ7bô˜óÆœ7æ¼1ç9oÌycÎsÞ˜óÆœ7æ¼½°ÚºF´àÊ-2Ï _~KXÃÖ°†5¬a kXÃÖ°†5¬a kXÃÖ°†5¬‰%”XPbA‰ÝI,¨['9·±d”?bA‰%”XPbA‰eÿÓCâBñ«ÁÖ°†5¬a kXÚýÐsôÖ°†5¬a kXÃÖ°†5¬a kXÃÖ°†5¬a kXÃÖ°†5¬a kX3œùàÌg>8óÁÙˆùàÌg>8óÁ™Î|pÚ~Ìg.3 aÈ|püj°†5¬a kXÃÖ°†5¬a kXÃÖ°†5¬‰Ÿ#~Žø9â爟#~Žø9â爟#~Žø9â爟£íGü±_0„!ñsøÕ` kXÃÖ°†5¬ÙO…ýTÐsXÃÖ°†5¬a kXÃÖ°†5¬a kXÃÖ°†5¬a kæ¼1ç9oÌycÎsÞ˜óÆœ7æ¼1ç9oÌycÎÛÚÛØ©­kD[®ÌÑ‚!óÜð¥á·„5¬a kXÃÖ°†5¬a kXÃÖ°†5¬a kXÚXPbA‰%”XÐÄ‚ºu’sKFù#”XPbA‰%”XPö?€110$.¿¬a kXÃÖ°†5¬Ùÿ=GÏa kXÃÖ°†5¬a kXÃÖ°†5¬a kXÃÖ°†5¬a kXÃÖ°†5óÁ™Î|pæƒ3œ½˜Î|pæƒ3œùà̧íÇ|pæ2ÆÌǯkXÃÖ°†5¬a kXÃÖ°†5¬a kXÃÖ°†5¬a kXÃÖ°†5¬a kXÃÖ°†5¬a kXÚ¹ö̵g®=sí™kÏ\{æÚ3מ¹ö̵g®=sí™kOÛ¹ö̇! ™k_ Ö°†5¬a kXÃÖ°†5¬a kXÃÖ°†5¬a kXÃÖ°†5¬a kXÃÖ°†5¬a kXÃÖ°†5¬a kXÃÖ°†5¬a kXÚuÃX7ŒuÃX7ŒuÃX7ŒuÃX7ŒuÃX7ŒuÃX7ŒuÃX7 ƬyCÖ Ã¯†Ö°†5¬a kXÃÖ°†5¬a kXÃÖ°†5¬a kXÃÖ°†5¬a kXÃÖ°†5¬ßÉZû9‹ã­»ó?Ðoôûi¬Ý\£wñs¦˜²N‡å6þÕÜ»Æ9­kœºæ9ŒÌ¼}> œ“U_h8Ê2ܰ1_LŠV¦IϺÒC½A½A½±¿zcÆüLì&é¡^¡^¡^¡^ *÷©•¡Ðgl%é¡.Yy]âÚ‰¹]߇º„º„> vœz…z…> õ õ vœôP¯P¯P¯P¯P¯êêêêÆTHu u c*Ô%ôQ°ãÔ+Ô+ôQ¨W¨W°›¤‡z…z…z…z…z…ôP¯P¯P¯P¯P¯`ÇoJÏO?ýòåo¿ÿùãŸþøõ§ïßþþMÇ*-ëÓœ~Tߣ¯_þýóׯ_þeNýö½º°:ùÓ/L2…ÀdàÐ|šïEõÿ¹JйJ@”6O:àwD*üÚ©C‰ÿ0.Þó9óˆüaò>ûþ…8òÀë8888888888888888ž|8QíølÆ‘V»Ç¬MÅq$¼kÓllåäv5ès³’³ó8‡®jí¼°õ1µjvoµêþ*Íc+b/zuíØ{§ï^Ñ<0Ÿæ·–§`œÃ9,ši6cEþÒŽ¼rÕòÞhR›†-¬oJ{wí+Ù‹ôÕìeza{ØÃöóØ'Í({0ÿäJûÂvÌÍïîEg¸<Ë]D:£åö\>s´<4Ýmú«ôiÕŒÊëS“†ú3÷#èc‘u¾\z´ø~n8×yEú,û<ñ#ö¡éÔ3øÝe üì;“6×~î|žgF÷ÄâH|4›ÙWG™ãÑ=èú‡þ¡èú‡þ¡èú‡þ¡èú‡þ¡èú‡þ¡èú‡þ¡èú‡þ¡èú‡þ¡èú‡þ¡èú‡þ¡èú‡þ¡èú‡þ¡èú‡þ¡!ú·Š¹…ñ¶çŽ•UmÓÔY:÷Ê9—º9\¹ªËK2¿üÞ“=‘ŽNy2šó¶°…-la [ØÂ¶°…-la [ØÂ¶°…-la [ØÂ¶°…-la [ØÂ¶°…-la [ØÂ¶°…íml‰'n›[ØÂ¶°…-la [ØÂ¶°…-la [ØÂ¶°…-la [ØÂ¶°…-la [ØÂ¶°…-la [Ø7NÜ8qãØØÂ¶°…-la [ØÂ¶°…-la [ØÂ¶°…-la [ØÂ¶°…-la [ØÂ¶°…-la [ØÂ–¸qâÆ‰Ç&À¶°…-la [ØÂ¶°…-la [ØÂ¶°…-la [ØÂ¶°…-la [ØÂ¶°…-la [ØÂ¶Ä7ŽÞb` [ØÂ¶°…-la [ØÂ¶°…-la [ØÂ¶°…-la [ØÂ¶°…-la [ØÂ¶°…-la [âÆ‰Go± °…-la [ØÂ¶°…-la [ØÂ¶°…-la [ØÂ¶°…-la [ØÂ¶°…-la [ØÂ¶°…-qãÄ£·°…-la [ØÂ¶°…-la [ØÂ¶°…-la [ØÂ¶°…-la [ØÂ¶°…-la [ØÂ¶°]ÛêÞ$íþÓ+L §R0›øß¤w4N:Žçvï2÷.½ì‘>ÒGúHé#}¤ô‘>ÒGúHé#}¤ô‘>ÒGúHé#}¤ô‘>ÒGúHé#}¤ô‘>ÒGúHé#}¤ô‘>ÒGúHé#}¤ô‘>ÒGúHé#}¤ô‘>ÒGúHé#}¤ô‘>ÒGúHé#}¤ô‘>ÒGúHé#}¤ô‘>ÒGúHé#}¤ô‘>ÒGúHé#}¤ô‘>ÒGúHé#}¤ô‘>ÒGúHé#}¤ô‘>ÒGúHé#}¤ô‘>ÒGúHé#}¤ô‘>ÒGúHé#}¤ô‘>ÒGúHéÛIúª{“´ûÿKÒeÞUŠ÷Nü_§Ç<£ðï6éÈÜÿ"}&ÍæÿÔ¾·M[aÿ7çM>ͧy^^öxU×$…MC4ðyöLâê:}´ytù-›w¶ÏŒÅaÙ'¹=Tó®ú}¥Í‡ÉWõŽÏçæ™Ê¨ÑÙßgž‘æJ«è²u‘-´u‘í°uZʳ钾X?„yVâý‰.OÎÇ——7Ô½gêgÊ¡ö!fŸ°®ìÿWϹ¾ÉÑZeóÿAXA£—¢cüªõ=éÀ§¶Ï=ëøik0Á_>§“îƒÐ‹ÒZ[×7Öµ­AlÍ—'+–Ý”œ"áKμ8Sþ\ýÿ±±%æûUÙõÞçdg®­}€3dWû¢®ÎÉNøKœ_{³2L›´¶eM”y®.îÿk2Ô^v²ü\”£k2»/D†V~K‘›îÕáí¸«¿ò¦E7Ölë¢Ì>ã$ÆlÜx’{n"ê-ìQ^¯_ä½ï×s²È»ùrÞ‰ÁÖbšé€ÖbQÚ–âØçÇ•ß{×¹£ÆZ^¶Äb½Ì–XìGæöäç©­vØ"‹îoi›Žúœ²VÄŽzÖ¿»~–+Uòºt>+Ó)0}BãÝ6VÁx²ëÏÜ[lg-ê<¼¿Í“íïåiwDù#äü‘?ò_…ü‹È_ä˼#.óئ÷S´írÿü~úûé‹ ?r:ØF+){”=äü‘?òGþÈŸ¶m¯öÑB™GmçÎa®º#"[Œ¼ìçoÐ{¯tž{ÔAftĆXžJ;9Cù õÿA׸ìÕ…Ë~2Tê`Ñæ7ºá£ª“1{@¶hØÕƒÚ¥hUþ·Î9Rç~w²eÀ€¬0yup|ì«ÒúY².½mzæª'>@?ßr8 º¿LÔ ¶Å~¸±N£–¬›™‰¯öë ®ÊÕÅh5Z†˜9ÙhÒþޤ¹3„úÄtQŽo)ǃ6¢€)Aƒøvzga{†CƒøJ/w²ƒø8p$!äü‘?òÇ‘Þ[z•#Yít¿ô=—ÍM^ëåm°—’ù4 3ÑËNö2GüÙEäçCš‰miÑL3*z<ôçð$´úýE÷ýmhó¡ñŸº{ÚgDÍ÷\¤Ã Mmç®ÆfÏGÙ‚½ðô=â0þî×Õ=L5äþ—áðsÓÑëß=ÏÙâ}À>`æÙ£¯‹*ǟر½‡‹8°£´³hgÑ΢µ}û@;‹ƒƒvé˨‘¤Ô¯‹$UbyÚ=Æ„Ä3Æ„b¿|Òêf<ûx#7Þ——½ñË'Žº‹,ºåýƒ—•Y®¿syïÄ9Ùq•Üê\}_LGŒÇÔc/v|'(öÛ¨Þ’SCßµØÖ š±­ò[Äå:dÿpÖc,Ob[‡SÕ\ÎÚ.¡·WÆqOw“SÇØÙ¿Â2Ë7q¶K{í’s.¢ls±”YÚÛödŽÞF;e©…ŽÊÿ3ñy‹­uq±{dš 0¼•eC†0„! a¸0†JôoÛæƒåm, ¿QIû?,ÃXJv¹èg?‚i±ã¾LtekÑö±7íw}¤L÷îWÝiù€á¾²¸‹kÁÖ°†5¬a kXÃÖ°†5¬aM|0~j⃉&>˜ø`⃉&>˜8$†0„! aH|0ñÁÄLÜñÁÄS>ðAÁø`ÆyS‡5¬a kXÃÖ°†5¬a kXÃÖ°†5¬a kXÃÖ°†5¬a kXÃÖ°†5¬a kXÃÖ°†5¬a kXÃÖ°†5¬a kXÃÖ°†5¬a kXÃÖ°†5¬a kXÃÖ°†5¬a kXÃÖ°†5¬a kXï`Ò²9ɼ—>ô=‡5¬a kXÃÖ°†5¬a kXÃÖ°†5¬a kXÃÖ°†5¬a kXÃÖ°†5¬a kXÃÖ°†5¬a kXÃÖ°†5¬a kXÃÖ°†5¬a kXÃÖ°†5¬Á: dM°ÌŸù­æceá˜eÇùù¿öîšQõ,]Ó6uZR›.#碑_Í1g+ƒÞóõ•ç'^ ÏDp 9‡N¡Sè:…N¡S«Ö)³[N¡S/¶SÕ³í?W£SE“Þ ½r×¾²_$ÒW÷dzé‹Ò…5¬a kXÃÖ·¯±7m« æ±mëFëëgÔ,MÞÌ{—Ç6öY¦/P§Ç=§O‡>Z}Ðö9eóŒúE7ï¹LóÁþfu©¾/î¥1ó}ê¾^ö…ªç¥ŽÕ@þêÿ]z^.†SÛ/’:s²ßEyüîtæÜ-Gcœ.Þ•_–«¥Êþá¬ÇXžlŸHÊÁ±šËÙ<3Ù1㸧»I©cìì_a+ï› âœ7~‚]rÎ^5GÇá$>o±ÑNYj¡£òÿL|Þbk­}Ù%Ól€á­,3†0„! aÃ…1T¢“Ø6,oc)ÆFÛÿaÆR²ËE?ûL‹÷e"ѧîn¸½i¿ë#eºw¿êNË ÷Åq^Æya kXÃÖ°†5¬a kXÃÖ°†5¬a kXÃÖ°†5¬a kXÃÖ°†5¬a kXÃÖ°†5¬a kXÃÖ°†5¬a kXÃÖ°†5¬a kXÃÖ°†5¬a kXÃÖ°†5¬a kXÃÖ°†5¬a kXÃÖ°†5¬a kXÃÖ°†5¬a kXÃÖ°†5¬a kXÃÖ°†5¬a kXÃÖ°†5¬a kXÃÖ°†5¬a kXÃÖ°†5¬a kX¯u•ž¤lŽE2ï¥=GÏa kXÃÖ°†5¬a kXÃÖ°†5¬a kXÃÖ°†5¬a kXÃÖ°†5¬a kXÃÖ°†5¬a kXÃÖ°†5¬a kXÃÖ°†5¬aý:Ö£ŒÓa6æÞ䣹לÓÕ=Ú¦Å|Ö÷•Mú³¢yw渹ϥ1Éü§árŽw¾ÓÌá.˜ÏêY‰öŸõ;«g§®<Äö}N ûlÓp°eÅ–Çöý&¿q“çú=ʾ7šø®í÷s·Ìè£-#ºÉcýÞdà]¹/CËÃXOVž2. sÓož™7ÏTºI“»Ï<#Í›R5ðj$–]– ž ¦±øÓ6–ÖfÙÅœ†Æ×±‡éDük/Æ·ã:G¼ø½…â{ e¾2Ÿš×È–£³e|¶×ž›w:Þ&ÞÙä±u¶q¤N.ƒù¾Â®MëÓбÁVœ‹{ß]ßùYçúïÏÄ÷4à×W~e\=9'_cÏJkÖ;Wܘ'É)¶íÔ¸{®ó»óƒ å!³m«G0,{ïNW óþ¹¼Ç'IkÔcœö~ïs‡Ëä…¾Å铌ŠÍ/Ëß½ó(°]Ø.l¶ë=¶k “'y@çhšdIóèCõèâlŽVÿE£_'¢Á{ Ô“7‚ròÛPç`Ô˜Îè ìªs #Ò9x@ÇÀüæxšNwªG6Aü³ã€Ðt…ŽÍt8„¦E÷tÙ±mei'ÔÕ¿iËÊ~vä‘Ùgœ¼£ÖÓÂËפ»ß‰rƒC±h(¸¼»ïÙTÙŠ:"õ3­ãfÐxÄ:Æ#žpDÇÆ• ª×ª× û9y\õB·‚­pÕr(Ü “Mó¡¦ê6ÇœK4i2ÈfÃØµk=¿Èî2»"³!W¹.š‘§Ï¦&8gòYF …q8ÇöÜÁ:œ+ìçTÜ[©Jiتi–ι7?4"?[ø¹:wÔa÷š)µïý0‡iÇâ^ã4¯PMZì½ÆqiŸ×þ3õGSÉGù|ÇÜ^wC^ ÿ²——Î{oÉKyý™&=uŸ6&fð¡”°E·°ù±iQ˜wöï5ïVQOÇr‘žc“žzeàÞ›õ¤|‚ž”·ë‰1w7ëIò=©[„’OöD=²®ëË:Ó“Á{M¾?šëúlOSlOy-í@ß [%8ì½ée‹8±ø—4‰{ä;ìP'¢;ð¢fñh§:±êLÈâ닦Ã;Û­lÅEhߪÿ²j=ô¶s]§__®öÔ—12YìWš”]1SvÎÓšùÕ•ÚnAÙ•Ý”œ°i3mZÞK6m96-¥wÕå£ °eB¶ÈjÁ¶lDNز™¶L‰™@زåµÏ\;Lû‘øVÿ3-=iÓ„Œ‘Ù‚mÚˆœ°i7Ø´tƒ¹Ø´^¹ˆEÝ÷Új¡¶ÌÊY½Á–¹¶´²ß…áVÙ-Ú¾ ]¸jÛô‹WÖi‹ ‘ε¯d¾´:%+GGÔ)‹œù–[¤„­Êýꣶ©'_äµÐ6ò„œ°g7;ÆØ³Íöû{2Ff µir¦Ý0Ö\Š clÚòûý™_}é곕-²ZY¿Dvôûé÷/¾ß¯E˜5uÊvꔞl‘Õë”\Ä&%~Ū[dGB²ø:¥³á©S–?–xÛÒú`´új›„|‘× ë•Xø[ñvÿ7%'ìÙL_rìëoìÙ cü“_²12{“/9q™gÇ—<"'lÚL›–`Ó6?>–`ÓV1>–`ÓÒïLÛd\lO¶ÈjEãc²Ã—‰/suŠŒ©S¶+䋼;"'ìÙ ¾LæÂn·Ï3vñýý˜9°wÙ±ÒÇcÇ6³$䊜VÔÇ‘ý{ú÷«ˆµ11Ô%+ò1”V¶Èj…ñ¯²£N¡N!þ•:…øWbÅ.ÖÂŒ‰}ŠÏ¸°ìño7þUÈ™-Ô¦MÈ ›6Ó¦i±6my6-k!•½q°"p,LÈ™½È¦%]ÕÖOÒ“ölÚˆœ6gÓô“ׇ‰}º°i ôeJ]Oü:orÜåêZ#BÆ[™Ù£K«FFfW“†ú3÷ìR™ŽÌ¾ÏÕÛ.=Z|?7e¤ÖýX¤Ï– óédµÛ‹ùNCº¦¼?é&?l)Öpã{ñ|}»g›È½Åßì!~e/±{ËßãÜß½ÌÝãȽÌ!Ücÿ~/ýã=ú£÷âÏÝãºÍ{Y÷xos©ö2iOó®÷_=ÆRžÅ}›–í³ó¹à}Ôö»ÙO?ýòåo¿ÿùãŸþøõ§ïßþþ­ŒuQŸýãô£ú}ýò~ýò/sê·ïÕuÕÉŸª{ÍûªçG¦?mžuhâÝUï?UçÎÕ5ººF™|›kM¾LìžI[úÙiÞaÎi›æþõ5÷ܿɦߤÅ<ߥ_ÅM,›¹×Ä%šÿUÔp¬ñXŒY!Ð6Èt"Š"ÑMÍÕÝi»ý𹩰¹ÐPNgF§õöšûï Ý\zvPøuöø)%ϯnóoõÉ…´æ¥ÏßXˆîìÐé²™v~—itU°˜Þ^?'±a»–IkîDH¼¹ÏÉ9Ô=—;áš6×$²ªUÝ.Œ«zœ{¸Ã:‡ë¤¾ÚÏšW)B2ë!½Îæë«ëZíŠmÑmÕºl›8í¹Äës}®˜ÙdL—Íu,ýrúB;5A¦ó`³õw}_ÜMWæêѸ©ƒêëõ›ËP.†er!çh¸n–s´p9÷Úz®ýѶsrû¼¨Iƒ“U¦zm–ÌOM©¹¤Ít×îÉÑîSZUxÛo0Ÿr™™½ª?ÛvÕÁÞ“wó]ËÜÊi°’+%z!ñH/ätô½ºaZøÛªïõHLsK~w-ýºå_%#«ŽOqì9DöÙZôDê&Ü¡¹.>ŽÑçÂŽh½‡Êî»ßèKÝ[L¬l®Ñ3ž'ëæ¹åCÙ^~±¼˜.ã«ÓTzåû^q}>{OQˆ8çöúÊñÑö‡ûój»±û0àÕÍôrݺٕ7êÚu£@ïrñ^¬´´Ý23Vi¸¶Jƒät·›#µÿ\º>®žû°®Y«Gõç‡uíf÷ˆ¨ÖúœŒúˆü*/A®“RŒ("‚#£ŠÎÍàìJluP5\d‘Å\oWÒ q_$Ö¾ÄM?<ÔÞ=:Í·”ù«:ühû:D–ÜŸö§¬\ÜËG\ˆŽV/Ææ®ÔæÆMÚÛÏëçjÛöaŸq‘s†ir1óBFJ÷unÑ·uè›bíèp­ëPaÛ 2š)ò:èúƒþ ?KÔŸEûä0gÈŠ›êÅ»lôduuXvM;mDÜ£3¡"ma kXÃÖ°†5¬a kXÃÖ°†5¬a kXÃÖ°†5¬a kXÃÖ°†õvXÇ3⟣e,„°†5¬a kXÃÖ«š‡û]0ž:[,)¶Ç9ÙO›w'çˆuæ,Ñ6$#˜2Ÿ³ôa!V÷†ýkÙÃÞð†7¼á oxÃÞð†7¼á oxÃÞð†7¼á½Þʯ£8k½© öè:¼á oxÃ{ï¼w¿vuêמNÜ:ÕÏ8¬Žۉ­ºÝÇ‹ö@‡Ñaw.ñ1€/Óa»tûYˆþŒØ»½F¯We›Ñkô½F¯Ñë‡épÍÈîã•eëÕéEÏI:G'e>ó¡` kX/~ž_ì÷\kÓÅ<¿åø®{ò7¼á ïY¼•ð—b¿2{¾í+éËz9¨ÿ4C†·ô½å^Äu: O×&p¬.úÕÑnHÅ5Ú2O½.´u¤ünÛ)SiÈoØÓ7x¿æèõû5SÆn,cYoßZìõ¼á oxÃÞð†7¼á oxÃÞð†7¼á oxÃÞð†7ë„­{tÞ̱`îÐrç+£ÃÛÓá9sŽ·:ÿ ½Æ6£×è5z^¿X¯ÛüÚ9EY"æéÈ}»ÔŒ¸á1ý‰®ï=ÙøçÒǾN¾O5|j.ÿއóäò}üôó×/_þöûŸ?þùç_ÿqúþíïß”ŠTRŸþãô£úýò~ýò/sê·ïÕ…ÕÉŸ~ùëX=$©U/-ªG‡FhýÃü®ÌDßÔÆ( ])ù¬3—[ŰçëgTŸçs£ Êö쟗c$ï™|zÿûUÔ€¬ñXŽY!Ø6Èt"æ¶„­ssôtwþ^¿lôç†å¶<YǹgÞ¢³¡±íkØ7;xïì¬i?u¿?½Œ½þ^éû¹¹nlÝ•Ùy“'ÿÿÕsÑ ów\VÚz1³v¾¼”ߤ<\qï’e1½mÎINÌSæÃ¤¯Ÿsošo)3Wõ÷Q6(m…köǶÃîN{H[ jÒQÛ*ûéüíü­swnVÛ6E[%óçb[^Û÷•Í‘m§ ~W ¶Ut¦Z*§¢i¸*×µ:ßm3öóóò÷“²ÇÈsŒÊ×j/þ;¢Ï°#·-«ö˜¸VEaÏŒóð÷ïöÈV|=ÐBgÞœS^ÎѺªlå:À±ÙÃô8·˜¯5Ú Ž{Ž‹6>L–W禤vÇ>•^÷a qèµ—#e*N㋱²tb ¬vÀ¿ïìñ/%ÖzŒw8–Ìs±à{öX×Xœ€¶iêÄ/dâ^¹¾³n76VoÜ0ŽqO:ôD:ä:Xí¸˜‹Q"6dìwÕý½ˆÃî7é0׸OtÝ_¤îÇÍýí§ŒŸ’熮›{ÎÖ!«) jæX¤zñzÔ"}qgUÂö°‡=ìa{ØÃö;fŸ…Ç9¯®{hú‡ú$bçS›®ÜÆ&Æ"qn¿µ÷|}åù2F9Éüg{|å:…N¡Sè:…N¡Sè:…N¡S ЩÈÏdQö¥üR' Sè:…N¡Sè:õæø¼X¬¡BŒÞbcôZ}üxíwdƒlø® åÙ dƒl ²A6ÈÙ dƒl ²A6”MýäCÙa\q=ÆŠÑ)t B§ˆ? þ€þ²A6ÈÙ dƒl ²A6ÈÙ dƒl ²A6ÈÙ dCìczŒé¡Sè:…N¡Sè:…N¡Sè:…N¡Sè:…N¡Sè:…NݦS¹˜« ¬¡Sèv B§Ð)t B§Ð)t B§Ð)t B§Ð)t B§Ð)t B§Ð)t B§ö¦Sõ8rŠN¡SØ)t B§Ð)t B§Ð©wë”k›£Sèv B§Ð)t B§Ð)t B§Ð)t bï+ö¾bÍQÖƒE6ÈÙ dƒl ²A6ÈÙ dƒl ²A6ÈÙ¬T6ÊÆ~4þlóüöóŽsÈꊬnàšÄ—Ó˜ìFÇ-Òáñ—7sï;Æ—x'ïœýNwhØòNÞÉ;y'ïä‹xgàZô°å¼“wòÎõ¾óÚZ1°å÷Îe†-ïäï|Á8…¹§~¯óA+›†‰ßU÷÷:ßqØý&æ÷¹ˆ|ùâ-{´o?jX½*ßï#$ ÌŸB~äü‘?òGþÈù#Ê_–‰±dHþÈù#äü‘?òGþÈù#äü‘?òGþÈù#äü‘?òGþ°VÀ¾çÛÿôó×/_þöûŸ?þùç_ÿqúþíïßT®£¸>ýÇéGõ=úúåß?ýúå_æÔoß« «“?ýòשÊldÍ„¨¨yPý³}ŽnîЉX.ù –7>Ùóö{>±LÃØÔÿÐåŽ÷ÎØ.ì½ØúK5÷ß5¶”Ãâ—–6G¸´´Zá’ÒÇæ™õu±ç&‹¯KKfC÷Í5¡ïÖ=ýt¼ZùØpèú7móo?;Œ3û óÎêzmÒVxy™<µ¬´—_¨¾Jy=Øóy7/µ¼Ë&ÿƒæ Na â)c ƒP(k”?>7G}îti,T|a-¦$^=7¹™mÔ<ª»´+0j ߣ{ïi¦Ö*šÍæóÛ†[NY¡¬Ü ?v‰‘EâÝ禱Þ6Ì]] ¼Œ´­]-\ç½ð5ë.¸•~¡»Õz°½{´MY²Ž=VŽ­³´P]µÂvÓŠ¶»Öýî>óñ{u.žêþvßçe 0É—Ù_LMÛUkQͬ° Q킉½\ë4Zù›÷9ÖibKPdŸi§ŸöûMs¸¥ySšM 5}=SëÏÜ—D׌í{CÒÚæ1±çmú†j!ô}X>DБŸVv½¼µéü6?÷Ïï§Û¥ËøVÚ4Ùì’²GÙã҇x¡6¹—¾MÙæèc*Ï®ÕvêÁWÙ»Gxã`¹–ØôKX–°„%,CX*;¨ìsöÿØs‹'süZÿ†ó}¸Háw‘×ÕÏs¾“¹,=s÷Ž ™º‘Måý$õèåi"vLåy/ÃÒx£§Eœ§;Ž#’ù~ÁÜmÓQŸSM;¿¾Æ2Ö®}ìæZÈëÒ'O¼sª].ÇNï·fvãeï^Ù8_ÞçúEõ{ËæhÓQØßcÏZF,:}\†Ž•Ç>tu݇f!ºs”r;5Ñ0™…Ü{¦x¦Óq7¿É}Ƨùçâ£øÿ ú´Fï”÷ äÅås.ž{úüiËžà?øœTÜw°2uQÀÚ~e¶šê¦mÚ©Ÿ»‘¡îm‘—72lÿ¿"CwÝØg¨ GïÛ¡ /XœæŸ[ •½·lä‡üÞ&?eg»ÿu»Lvqi/‘ߺË_]_aûJÈï¹òS[úRþ¨ÿòC~¾^ËD/뵬 ì'åù!?ä‡üßêåWøñ:ä·­ò·è±¹Ð±áüÅógr1&ßÈüX3æ¼õ1gÝØCÆœsfÌ™6×Ód˜Ø¸¾²‘%ò£ÏŠüò#惘b>ˆù æùQÿ!?äGÌò£ü!?ä‡üs^Ñ8hü†uåXh¼¡µ72ö|×N:̧½y\Œ1Å×°£.ƒÛK¹á;‰¾mÙg¯û·mrÃ'‰¾Á npÛî<#¸=_߈•ÇŸ„?é½±áp£^…þK¸á¿Ä‰}ƒþKü—èÜà¶nWö©h™Gö{Ëtž‰ýÞ¢±ýÞT³/K¬ýÿæxÄÿ!Ï4òšsDŸ¯?ÞÅfÎÿs9š}æÞ³DÙ¼ë0q…k<‡ç¿£ÞŒƒcÁÇÖíâ:ê¬½à´¾Ø N'"îýp¹ÿV»·îļ”±8ðYûÆéæØÝ¾qñŒ5Ô°o\êöQ;‹}•ÝžlÏ:µíñvþØkyl@ðZkLCÛVOš±~úùë—/ûýÏÿüóǯÿ8}ÿö÷o*Q™ªOÿqúQ}¿~ù÷Ï_¿~ù—9õÛ÷êÂêäO¿üuªW ‰¢æ…æ³>̹cóR%þl2pn¾ëÈ ›ÌÔß‹FAÌQ_7ÛkÄïî0»þ´:MFêäÙ|$™È[“d:8ù2(ãúúÜglä–|4,CË@bîÉ&bz:ÝßX¹YtŒm¾ãÀ2˜4ǽö¿.Ž©àlTk.‹kï®y•¶ÎɼŒuémn-ãØ–kw¯Œ™ÒÍ!mö”ÌÇŽ{Ò¡'Òáê-cCÌsÍwcÓêOÕüoî«¿'ÍóÍÿõ{Ìÿ¹?WÛ"wonLÜŸY=°×!“+21ü Ë- sË9Ücû©üu5wÇÜ]7ö¼¡s©x‡8gò²VÙé^½àlTkó¦Þ«ï[»–Ùgœšø76Õž³‘&Ýý¶\-›õÄ«I{MÞÍcÝî.'Ú:¨uPTPÕk"mU½þüÑÔðÊye†ÑÀq°­Sì«ä«ï§J%"ÓŠˆ/kú8YfMok½]ÕôÊZ|F4jyM¯mZês®ñX6×׿»Öµ+iòºô9½»w¶½½“·mÞÒK½­¹EÏ®ìÝ+{!çËû\‹¨~oÙm: û{ìYK+»Êž_hDz¼Ñ_|¥·—ÄY 5¯-yõøÂ6"ui‰/»\SÕ{Øs£$³k†a½æÆÀ¨®Ë¨ŽÊe¤ÀËFÉ5.m7álæsÐ(ÄI"ŒB4b"Û²7Ÿel¿»C7ŸE{îÒX¨øuÖB çÃŽ,Fp,ZŸ³¥-U©èp=²Ã>ðü ŽxÙèÚæòeue“ùÒÌW±ay©Ô˜(ßÈÃ#ôö:r^†œEeÐ’¨;ǦÁ§œÓÇ™WÕ¹slÏìpR•ôsêïÍm5xΛnÊÙ8—tؽ'Ûø4-•säÃï-Ž—÷Ý{Mú‹XÜkòS©ÞÑ<ÏÞk†½L )·yì?31¾¹ê™§Ó|dz½î¦±êÞçî=/ï5êpÁÁTù]†¥îq(¯s0Ï“ú|võ!ž¡W9ØaÉ }Hš4ŽêC`^ä3Ͱ¦i ›g=Óä¥N£¹7ó÷~Äaº=¦Ÿ™Í‹)®æ½sô³nÉGÝgƉuäj?ì› =9ÅMz†Øf¶Œ8þ';¤Û¦çØÜ«î5嬸h®ës8Y'#—d@¦Õoe~ŸLÍpwG¦åõgvd 'ñ§u±=7Nsí†ÙûlmÓ?|;l+{¢>š¡û‹{·ÛÏCuîØãp¡c=¶qy¥¼”ÍXÉ-e¿,ï+û5ŸDð¿·ì—¾ NéÉXz&ulJ¦é}2MgÊ4„ƒJo”©ºÓžëÛsSß©Ûdjt«|týòé½-÷Ø¢ãQè­çÇì¼³Õ2·²|JÚ£yi—åFÖQ;Z^¹W_¯£Ì½e¿Ž:5ï4eÝä鳺çPÔݺc*χÓtžÛ.{¿¬;Ô©ÉËнµîݘgÓ]”y¾½²?Ø{ÓKob^¨×¹sßuÚeW:õøà8.}lÒÝ+k×Ñz²ëñ"mG?z׉Áp¢K-²XŒ&–ž÷l7ŠàáÞ_§æ{ewÞ¼{ðóÜ]»L‹6F;p"]JÄpÅö»‹Ê­n”~°©¿ŽT_¾Èk@^q7®wTnÅ ¹¹Ø<ëåâÀb+»ÒÇ ]“öl†=S½¸/lÙrl™‹—Ô"62öƒò‰ã4eÏ„|‘×BmÙˆŒ°c3ìX!U°a˱a±owÕŸÊÏGIÒ+¶ËÊ-Àneö7!³!ù`³fجÄGŒ¶s±]Ë´]Iï{qÅvõd‹¬Þ`ÃRï3K2q¨Ûe·hû&tá®`Ýg¬Ç-ÒæÖBßÄZÜK­SäzçÔ)ËêÓ»¦RØ)×ÇÏl[ Ä>©ÛÃí¨[^ЯŸ6m¦M+=slÚý”=#³Û´9aÓnGŽÄŒmlÚ²lšëï—>ô½íª€vZOÆÈl!6­WMÉ ›6Ó¦iï/Ʀ­`,F‹©ª×l™-²z³?3ò(o—þLü™«h'g´“WoÍh#g´‘Wo™ÑF¾;Þ2ƹéxËæ"lYÒ[¸œ–vl†>1ìØ†b.Ëû–§Á~½©?"7ú÷ôï]d¾ÝE=²°z$¶ÈÙ—Xôó‹€XJ!_ä5QŸdO®Or1nïö¹Anض™¶¾þvûú}ýÅÏQÊèë?,¦dQßcË6ºî¨12[ðº£#r¦ͰiÚÏ‘À‡¹"f`Ëz²EV+›Ý¢í›œ»½4¦H>̬ÑG;yÛkôÑF^Ç}´‘3ÎϘÌvûý=#³…®Õ7!'lÚÌq™ÈØ´…Ù´Ôï?Ñ™×­Úg=Ù"«õû'dGì±K«h'GÌãßl”·kWmZÌüó·Ø4%d§„ÜÊ‘ýbæŸ?Ħ)? ›¶@›æú,‘X_FÎÛYSCÈ™½È¦¹93‘Ø“1v-ííÏ8!§ÍÙ4ýd›–Š=;°gË+±h“Ž>hro;j׬œ‘ÛˆÜÒ€úhŽÜ”è{j1.'û¡}›6 #Úh7쟭èwnzÿlEŸóm~41–Öö-ž”×å„=»ÁV0ïc±cÍn]Wîò@¿YÁœ·Ø°L´©¥ï,îÙ° 9aÃn˜o2¶¹Y¿YOÆÈl¡~³ 9á7›¹ÎVÞK6mYí²¾ÏÌ–½Ö®]‹ìÉx 2 Š3–éÈìûÜ~>.=Z|?7e¤ÖýX¤/qÇVV‹±½¢½>¨kÊñÝ¿(æqµmÌx¾¾êÕHœwnÓdîÝlŒnáבXýZ˜™ßç½­£Ÿ™¿%îa |D›]ÿÿ™y\¢Lc¿ÿÔ&×x\cŸ0ޏ‡q¸UÆñ¹~|¸Fvîã(V×+÷ ,_”Ç…¯¼‡uv7/ÓlCõêXÛ÷Ùy|ôXvÑ‹»E=[Œgg^7ž*Ólasõžï%Å,Ý_e¹•í Õ[‡°|Qº~õ^×Þ¼|õNå«w _±·ìöÏÝ˺©{]wtÑòM…¿%éåµü%lÎôëÛWä}áóã÷2¿|oëÆíeݵ=ΕÜË\Ã=Ä$ï%¦wë4ìeƒÅïmëdéŽÈûP¯Ê4ñyÞl_öù\büD²Òò:7.&YQy}T›IèúêÛLq·Бí³óùŒz6ïÖ-2$x¯ñȯ?¹‰r› ìçõì|¾jù< ®•{“G¶Ç¯Èç«äÍ(¯bÏùÍË5Ý‘\õF⟮ÉU¯Ä—ø(¹f«_Çäš­¤~}ÄzëÑŠãcæìí Ff`¿×½ìk»:™j1×-D¦ñŠ}·ŽÅ¾"ïKóAÅÝy!›!E>¼·Éž÷Ù¼œÓ÷kï•sº’¾îÖÃÛËzr»ëë߸Wï–bÌ X¦ØQýÛÛßwÏ{ïqM¬½¬)µ Ù:¹æ=ô k¤6ì‹|E>ŸeŸ“Þ÷"P¦éŠç‡¤ÝvR{¨äýÑc …˜ë’÷ÖŽÉk¾_Ö-rÎwäËJºíïMÛj½¢ñÀ'­ÑÇÚq+îG•å¹ôÏØÕXÄ+ò¾Dû]®| …Pûýì|.Å’ˆõ©ÔNËp¹Â>Õ\[­ý?«÷yÍ•óEÞúùë—/ûýÏÿüóǯÿ8}ÿö÷o±R±®OÿqúQ}¾~ù÷Ï_¿~ù—9õÛ÷êÂêäO¿üU˜N™qVŸ*òßÏæ¥çæ{Q6/¨o³ÏW‘xgó(ˆ5¹b-_·¼îîß_{vjÍÙÐuÝ|Éѽä{k‚÷ß7¶îñ¢×0W¾ ­aß¿~¹¶é¨Ï©fýi§ëõïýõ›åué|V!ëK‡¼3³:ZçQÛß]>ÒK­™my–÷Æžo|¾¼Ï­ó^¿·lŽ6ÖÕϵ¬[]½>¢‡ÛÓÃ\¬…îÖ1wzÕ®çníhý Çú žaÏ»çn^_äõciSͺ÷m¹> ”Ó¨yG]öìgÛ¶q\ÏÍÑ®3ïd eþ\[¦âÝÇñõÔCó7X·ë<5{o¤flíBæw[©¹¤Í.ζÈl¦»«œ„Ú×Á|F¦ˆZ7;Š´–¢å}°÷äÝ|»ÕJÆkV•Ô¬¦Ll­g>ÏU2ŠJ”‘=ÿQ4ÿ»Z²®u«¬hû½84¿›ïF”=o¾§G_{$ãGÝgÿlÞQéðsÝw÷Ne¶¶¶×){Φ¡ëþwÏvÏ;Ù|7ϼ¬é³b™5}æ}5{ªéÛ8ª?{üž¯Þ¿¬³ÿUö¢=ËÞys‹@ùZÉÕ2uôá[湉ûMwk°ú܇õM:yÞ²6ŽÜË"½½†Ûš|:|çú»“u¯o‘ÀùnÎr_–RÄgBŸãus^Í~{ÏÒ‡96-ã%qoO…œòÖrÊškÚò#ë–‘ß ËZÖU²îÑ7ŒæÂ6FÂ^^ûm{#‡Þú^a<çʼݣ5¤g“Øö\ÜŒ „¶/æ[Ú^Wõð‘íÕ8Ü|oºï)Û©èÓÔº–ˆ1… Û›Nè: ÆoÆ¡«3è1ôj”EèÕh½‡®·ÂyŒ×À¨ìÔ¡®q‡ñ>\½&ÞØQ$÷?ã"]Ÿû9Œ®ñ8Æ“63îuë;.¼u3ŽÓÑŽ§÷=‡ƒƒãÒ­“äÂ=9s°Õx~ƒ{Y,‹y¢Ù¶åE3]ç«<Ë+¡T"_¹ =êç±Mï§’]ë~úû]û6 cz_®,,IÍIR/f/Ò¬°‡=ì—;˜aï ?Eðe싞½—é];ûø†0T»¼Ì#Û1rúJ¸¤EÈ‹‡ÑëdýÆ¡ésîdŸgÒÚ<÷Ò°ñëö.®ì±ºå½…ã{#Fë³ÞCí$] b-A–‰{Ó'Xù;ÒTd¶mf÷6çÍuµÜ>šÚÂí%íÚpíµÊ¶Ý>Äï‘8geoZ5Iÿ^-®³û7"«+²Jº²©ÿ| Úaþ!¸ Ù¶Ü£KþIäåäÞS· ¬mlkjû‰¼Ë–½Îɤ~ž=Zþ,GõÿBWÓ´œ†ÊQû¿œD]YIþõs”µ™ªw´ŸeY])S7Ø/WÎúö«SÞDmËg: Û\<¯'ï»ÇÙwV×ï±¾ÜS³7»½oÛ7à )‹,Àm{²^Ž%f/Õ—hJ§Ù2£‹•CHýøÄîâÖ²Ìç~aÌsãY‡Ö°†5¬a kXÃÖ°†5¬a kXÃÖ°†5¬a kXÃÖ°†5¬a kXÃÖ°†5¬a kXÃÖ°†5¬a kXÃÖ°†5¬a kXÃÖ°†5¬a kXÃÖ°†5¬a kXÃÖ°†5¬a kXÃÖ°†5¬a kXÃÖ°†5¬a kXÃÖ°†5¬a kXÃÖ°†5¬a kXÃÖ°†5¬a kXÃÖ°†5¬a kXÃÖ°†5¬a kXÃÖ°†5¬a kXÃÖ°†5¬a kXÃÖ°†5¬a kXÃÖ°†5¬a kXÃÖ°†5¬a kXÃÖ°†5¬a kXÃÖ°†5¬a kXÃÖ°†5¬a kXÃÖ°†5¬a kXÃÖ°†5¬a kXÃÖ°†5¬a kXÃÖ°†5¬a kXÃÖ°†5¬a kXÃÖ°†5¬a kXÃÖ°†5¬a kXÃÖ°†5¬a kXÃÖ°†5¬a kXÃÖ°†5¬a kXÃÖ°†5¬a kXÃÖ°†5¬a kXÃÖ°†5¬a kXÃÖ°†5¬a kXÃÖ°†5¬a kXÃÖ°†5¬a kXÃÖ°†5¬a kXÃÖ°†5¬a kXÃÖ°†5¬a kXÃÖ°†5¬a kXÃÖ°†5¬a kXÃÖ°†5¬a kXÃÖ°†5¬a kXÃÖ°†5¬a kXÃÖ°†5¬a kXÃÖ°†5¬a kXÃÖ°†5¬a kXÃÖ°†5¬a kXÃÖ°†5¬a kXÃÖ°†5¬a kXÃÖ°†5¬a kXÃÖ°†5¬a kXÃÖ°†5¬a kXÃÖ°†5¬a kXÃÖ°†5¬a kXÃÖ°†5¬a kXÃÖ°†5¬a kXÃÖ°†5¬a kXÃÖ°†5¬a kXÃÖ°†5¬a kXÃÖ°†5¬a kXÃÖ°†5¬a kXÃÖ°†5¬a kXÃÖ°†5¬a kXÃÖ°†5¬a kXÃÖ°†5¬a kXÃÖ°†5¬a kXÃÖ°†5¬a kXÃÖ°†5¬a kXÃÖ°†5¬a kXÃÖ°†5¬a kXÃÖ°†5¬a kXÃÖ°†5¬a kXÃÖ°†5¬a kXÃÖ°†5¬a kXÃÖ°†5¬a kXÃÖ°†5¬a kXÃÖ°†5¬a kXÃÖ°†5¬a kXÃÖ°†5¬a kXÃÖ°†5¬a kXÃÖ°†5¬a kXÃÖ°†5¬a kXÃÖ°†5¬a kXÃÖ°†5¬a kXÃÖ°†5¬a kXÃÖ°†5¬a kXÃÖ°†5¬a kXÃÖ°†5¬a kXÃÖ°†5¬a kXÃÖ°†5¬a kXÃÖ°†5¬a kXÃÖ°†5¬a kXÃÖ°†5¬a kXÃÖ°†5¬a kXÃÖ°†5¬a kXÃÖ°†5¬a kXÃÖ°†5¬a kXÃÖ°†5¬a kXÃÖ°†5¬a kXÃÖ°†5¬a kXÃÖ°†5¬a kXÃÖ°†5¬a kXÃÖ°†5¬a kXÃÖ°†5¬a kXÃÖ°†5¬a kXÃÖ°†5¬a kXÃÖ°†5¬a kXÃÖ°†5¬a kXÃÖ°†5¬a kXÃÖ°†5¬a kXÃÖ°†5¬a kXÃÖ°†5¬a kXÃÖ°†5¬a kXÃÖ°†5¬a kXÃÖ°†5¬a kXÃÖ°†5¬a kXÃÖ°†5¬a kXÃÖ°†5¬a kXÃÖ°†5¬a kXÃÖ°†5¬a kXÃÖ°†5¬a kXÃÖ°†5¬a ë]°6yѼݵciªîÏ3ûœÒÞ¯FîYõsus>·Ÿ’m}îÜõwê°y›ßëgdþ\ý=ï3ï>Ú´q¹’·èã§Ÿ¿~ùò·ßÿüñÏ?üúÓ÷oÿ–e‰*êÓœ~Tßã¯_þýóׯ_þeNýö½º°:ùSus•¨SõÀS•ˆc• ]½ ŠšÿÝgÿ8›Ìœ»çL¢û×™ûãcs˜ÿÍ»Ìÿæ^w~ð8\ž‹Üçgø¡*¸‰jŽXYpª”ùTâù:m +5ž>}ºLßáž¾Ä ðâ·¨ážVÏÒÍ÷âÐp>WéŠÒô|ôÒfÓYùEoàßÏߢx«æ]Cï9LðÈz,œ>™cðªw 0p²8ºgæ¼]Vßc/Só©2›ÖÞuÎs_—nýùh<Æ¡^þ:IÏó>4ŠÐóÞKvýûÈÁ¢GâÀ‘†Ø2Š'Fxó™)ÁS06#s9\{wËàÐäͼ×q# fÔ¢Nƒ»··¶Æ¡~nlóëòWtó›ËtÚÑ‘ÜêW}_dFŒªÔ#(v”f*=mºÊfÔ­æ3¿v$ȱ(=›äÃ%v„¨6®'ÿÿÕs™×=3B7Æ©}—²†«DÖo‘uÚØ¿PY'bÄרg;‚d5ödɲ~(Û¼‘oËI0 =ÌV7²Ü Û!fÑȹØÊs°·ÿcÌ:çŒ=(mÁÚ‰öÿ9ö_íˆmâó¹ÓþgÈúÝmæ ›ùˆúÈÖì½>J\ÝìÚ£©åä"ÚÄu³ÊQL9Z”Í;§î´™;,GíÑ+ƒ¼e_Oøh3ß©·Ú×ùôõf°}Eݺ׾žõ…Ë>œñ_$IÓÞ*ü?AvBûü ·—lëkü?ÏòQ¸>ª¬ÿǶí ǰ Ol'¸s™o磷¾µ¤Û¾¥ð ¶Emî}ŒÒ&<¤ïPÒ_d<ºì· ù­ƒeËzÑñ"Wc(ܵ¯œ!Û“A'½kŸ) ûåÏß8#<ÞëtázžnXϳ1rÉô=ë¶§Ú4ì;Go¶í[Y}"~Ùi,èÃý±#±’?1c7°UÞvJŸJ’ø^¢boô¾ýZõýÙbW3ü4¶YoÔ{פPÄ 7{â=‰× ÞsYí¶–£ÓºhÿŽ´a{…m>£MŒÞ>&®`èñž´`{Ø3/yiôSè§0/yiÌK{u?…yi´×˜—FûyiÌKc^óÒ˜—Ƽ4æ¥ag™—Ƽ4æ¥1/yiÌK#îžyiÈšyiÈšyiÌKc^óÒ˜—Ƽ4æ¥1/yiÌK#nöÄlïI¼'ñžÄ{ïI¼'mÚ´öë9·~K‰“¡~ÛNý¶«½¹å¹¢wÎî-}S Ò}>nä%Î/ic~)mæ‡0?„zù![ŸBÝEÝ…¯_9¾| øÊñ•ã+§íð®~ïCæf¤ÌÃa²FÖÈú­¾Š{æW;ž•õhœ·rû,sï#ãû±[¦Îe¾rKâ!éOmü|`ú‡Ö~NfÇ´™þ|^푾‡§•hä܃ãì_Ë®š1´»Ë¾zpúÿÜI]Ñ¢¬$"Osý+É ôÇÍkïëzr§þ?£ü¾rgoîÙ3Óÿ”y’ÙÒÿƹkŸ_÷Žu9‡íáý÷Ï{hù ŒÁª'ÞZÝÓþѾñ.þõµÉŠÛ?‰¯k¥]qimm’§]‚þÜbÄ8”ëþ“ÿEŸrMöÇÚNúsßV”úóú«|¡þðûܹ6/ú³ýyã:®ø=ñ{â÷¤íCÛ‡ºkmk-á{[úß·ÎËO?ýòåo¿ÿùãŸþøõ§ïßþþM¥eRÔ§ÿ8ý¨¾G_¿üûç¯_¿üËœúí{uauò§_þ:}þò×ù\½°zh¡w”ùcŸÇÁÁñø#>Ê£1$µy°v$Î"a\›¡±ÐäA,éÎÔ uõ*tÑÈÚQš]Ùl¢·Øeÿc‹“-~#Ä\]¤3.Ôi*¨Tw"Ú Jž¼ˆçEºŽ^çêŠÜ°Ël}daº© }îBŸi•­š†ƒ>5i¨?s_a§2™}_Ñ]கaauÅ.´çÒ™k±^âÁ ’ ™9Nww¸báÔ‰>Ï36±p ÅöSÛE\ÏvÜë|èËÅz¥>¢_èú…~¡_è×fôË.´X?¿°ÿgvIå!D¿Ð/ìú…~¡_èú…~¡_èú…~¡_èú…~¡_èú…~¡_èú…~¡_èú…~¡_èú…~¡_èú…~¡_èú…~¡_èú…~¡_èú…~¡_èú…~¡_èú…~¡_èú…~¡_èú…~¡_èú…~¡_èú…~¡_èú…~¡_èú…~¡_èú…~¡_èú…~¡_¯Ô/ÝÛ¯Æí›ÒîÓ’7荒ÍçnÃy¹×JæõÄÉ¡–Aá÷nɱ'Šö›À¹=_òسpßÝž/ƒ{Ûìoy7rSËÁÁ²,Iž`ñȾ`QlÈ—ûéeî ¤÷·Pè.@ÜýçÙ;ü´y·zä,u^ú¼Y†·–~cCJ¾òV;DWó eÂjEZKûœÓ#Öq€Á(Jk0Lå\†ŒIálÂsŒÀU#àö’Ì_g ´+Œ'{ÝÑ75êßûM,y]:ŸUH0äYâ „+ÌmÞÒËJ«fvlx´Í)yo, Òùò>g4ë÷–ÍѦÃ6cê窮¡0çœ>Nê~:¬7²ò¼Æ¥mž®ÃF Q!»‰›Ç˜VCqhv5Gnº5‘6Ÿn×Q¥ío‡F½ŒHS{döPU–”»×æ:óéÞ¥þzsDÖÖÕ×Fþ÷ÔØÁ´÷¬Ì¿蘓n#5ÐbÊòe6™2a9ö¶‰b¨ÅÌì¦:ÏÜ@Q‹Í,—²böšþPÇY¾ÿæô‡ýÿì;[Á»‹Ûnýéw|¯?K»Ùey)¿-ÈcÍ—‡è¶Ž‘Ô~~ˆÿ3qÎýï'~gûÄíòîî•ßÝóŹša*îýè:x»kùl.„½+¬îfbÇñêðKlJtƒ"5|¾Hç=g¨LÐ÷ƒ~\^ÏåÈù7¢ÝüO}‚ž…è¡ñO=#*¼¬n;!£b±q u3£ØáÐe,òÒ¼Ò>jkñ ™åswlÈ3J:g[Ç«L³mæVçêûúÍÒ‘Xƒ ®Æ£¢â2?Œíóõÿª;XÓ¹vn¤œò»¸Dî‹“ûœïseït ¹/Cî…ˆz}¦Üí»;广Iîò¦œ#wäŽÜ‘;rGîȹ#wäŽÜ‘;rGîȹ#wäŽÜ7/wB‹Ü)ïȹ#÷…Ë=±±7È}_r/íëÉ}S1‹=^Ä,R¦¨C‘;rGîÈ}crýbƒ+¶Êß‘3å¹#wäŽÜ‘;rGîȹ#wäŽÜ‘ûräž÷¾»9áÈ}ýs ÇvÔ‰6,×wï{/dß‹ìʾZAk˜U^*±–™9LÚë5Îb»j¨]÷Ƭms®~;}6÷º5òÍuf=ysíQ‹{Ïã÷žª<–¹]ßI5k¤uÞ5÷Öϳ÷ÖëáÛõëÏÏ Í‹[§GÞ+¯3—v­*Õ¬«u-/õŠ­½¼ÓÕY£øu[¸egèú¦Öó)gŒ?`aýv1ò³X1ñvÕñNí‚ÅõvQw¹ÄÑeCÇј†¶nÉ-ŸC·½Ò~wã é ç”X²óàÓV/ô®l{è­»<38÷ÞãÚLî\1ðìt…H¤¯ÕóÑ $±°{¾°M$^´KäÃw_|dÑ­ï–ÙÏÒo@RŸ+|rt×ÅžŒ‘ÙÈø«µ%“²+n”U&l]ÙÛHaBNØ´™6-ï¥ ›¶› ùä—›Œ\µeB¶Èê ¶,µ²Ê{›Âd·ËnÑö-›±ŒûÄ&D©ÈSÛ†ïå¯Më§ð­†ý´Ë´ÅÅ•-,Kꔛ뗞x›­Q§ o¼&7ÙÒ»“÷dŒÌÞÔNÎý¦Vm›8íä 9aÓnÛ<2)°i‹ïûëî\9gã&mZOÆÈl¡}ÿ 9aÓnèûk¿a#6mÁ}åû޵…ô­l‘Õ ûþ²ÛÔvší Ÿ¶] c éüí7»…blË¡ºk02NÝÙwˆ±è‡ŒE¿Kן¡O{ì#í¥±GŸÞ^|b{ÜóÚžú{{î/í1Niq>1äq‘d"†<‰!—qàu ³ÝôˆzñϱÓù§ˆ»žC.ßYï;roq¼¼÷PñNónÌvwc¶£ÌÄd‡ÇŸ'¦=ž7ñàƒñçy9žíu¹ç {{ÃŽÝkÞÑÊ`‚aÝNïs0zúÑåPê‡rSVó.öHÕ‡ä’ÃE^ÎÃ1ü§¤IãT~—Ïy‰›¹æÙ¡±ýò™æ³–ÔÕœSB¶Ù9pžÂa`ŽC~y¯¸×è{ñ1\ÖN–í©âS&lϽùcl'ô$V=¶ÇëÏœ;$®>KÃV°56ß•+5\ŽŒì”_ɶ:¯*~§bø^c£'õvDçM³EÑeÐÔ¡ePü½¥º¯ :™šrcö”µe9ï½Ç¸ûÞZ.‰{z¿ËŠëú9¤K’ÃçнWt)û¸Q—ÎõÚ Tz›=¿W— }ŸÍÌK ÓôÝ*‹§Ç–“»m`"ôÇÖóƒ60žNOí£}`FÊ:²s3Û0Åeæt~Q¦lžiž÷ˆ6Œ«kžÚ†™£cÅÌ6LæïýˆÃêµÁ6öÁÛOS¯š÷šñÍÐ6öPÛ)¶ö}L·ëvÌXÛIuÛN§üñm§c2l“Þv*ŸÓvRw´"Û®Ûv*w´ªsÇ™õ]‹üÀ¶Ó&Ú0WÚr3Û0Ey»LMZÞÒ†)ºé©ŸyCÆ<ãÙm˜\Ý&Só쥶aŽÇ‰6ÌùzùV–NvMûDum‚Ñþ}yå^=PGºu”¹÷¢jÞiʺÉÓgõœCPw|të“>• çÙøÛ¦òÜú¹,³~½|R‘õ3ôÔü¿Yë×ÉzJ¿ï‘µ:5y´]Ÿ×ó|És|êæùBöJÈþàÖèXCgékäÐbÀjO‹r,-¸w (†àÞ…-h‹°kù=y"Ÿ…XNÈ û5s© fÒØ¯M1ôdŒÌjÓ&ä„M›9áªðü±i ³i™ž-ì½×‚°„<‘Ïì—«Ò^Ùˆœ°_3í—*Ç~­$€¸ (²EVo €v ¤g~rÑ­²c±(‹Z|¢üA²Ð~¾ëã;;¥…¯?d’12[ðd‹9aÓn\(šÅ¢–iÓ”°a…СLLº/‡å‰|^d¿2QÇ$ÝzfJ&ت™ Û¥,½ø‰ýnRlß~õ&·.Ú²ôâÚ_Y×/3%'lÚ̱cíýÁØ´ø)µØ¸ìÚ˜±-²z³Ÿ2ï.>s«ìðSâ§\Å¢ö¶/B²°:EùMBkY)a«¢@Û$䋼‹4"'ìÙ {Vˆÿ°eëñOæ—ýÆA{&䋼Þl˒­-‘vl†>1ìØ†4rEN éã'·Ëþ=ýûE×#™X$™zdys%-rö%ýü" FRÈy­hqß¹QŸPŸ,Þ_œâ_Yl¿DÌQuzÓŽÉç¾âßÊâ7yHñ­ÜÇêüTı®sÓ—$ 6_È™-ÀžÉ†Ëër¦͌w-=OlÚ×éÉ™½È¦%~Ý„¶mw7½ØdmDNØ´æ•ø/7¹±—-²ZQ¼ë„ìð_â¿\|¢½¿ž:ecuŠ-²ZÙf—#²£N¡NYÅš®¾—ÅÆYȱ0-Ž$pq!_äõÂz%îÅ—ÂÓkLÉ {6Ó—œˆy«Ø³u­]•Ø´žŒ‘Ù›ÇÇ„¿øbí„9aÓn°iÌ ÛîܰžŒ‘ÙBçºNÈ ›vÃÜ}ÆÇ¶·nuO¶Èê…¶¬ýÎTø4ËÛe†æ*êö¥Úö:0ìI5]Ÿ X†ý¨¶vlƒý|!_äµÐ5`Fd„›¹Œ"Ni±}ûTôñåþzׯ^„\‘ÓB}”#2Â~ݰ^uÂ8òfÛb=#³{¦d÷ÌöØ„œ°i3Ç‘#`ÓfÓœ?,q¢}vuüXÈY­hNÒ„ì{aìeíäˆõH67'©'[dµ€9IÚ÷û/ÚÇëà³Ü°+D¿¿åŸåúcÅñY>.N¼ N|óqâq⫈/ˆÈ:J¶ƒMÛPœxO¶Èj…ë( ÈŸ%>ËUÄ‹Ç"MÔ)Û‹/÷lG^ Ü7t@NØ3bÆéó3NÌø÷ %6i›û†—´®>~ILýû•÷ïÛ^L’-²ZPL’éÛ3þu_›8¦M¼É6qL›x•mâ˜61mâ•úˆïÚÞ¾!ã\«Ü3¤XÙ¸õõˆsTYa{û…ô䋼^\§d‚¿ëŸ(ûÒ‹/ŽXáaöÌÖÛØ³•í’͘_oeŒÌLÏ$÷‘vlf,¦ò̱e )Äþ¹Ð¡" SÈY½¹ÿŸ‰CÝ.;|ø]§äÄöovþeNLÿ[ë-úûª·þR9-#lØL?fÎòEÇò—½ò ÏqõƒåÌ!_Äž{ª·Go9-#ìØÌùãbOClÙBçKû•ô|`!{î #³Ù37–‰½_ã® èø+'ä„M»aþ¸‹iŦmk½8![dµÂ=÷d‡ÏŸå*æºË¿Í5ù5qü«\‡tBvÔ)Ô)‹¯S Ö"ÝìXXO¾Èë…õJÜ›V L¯M0%'ìÙL_r,bıgË‹Ë|lw}ȵc¯ù…l‘Õ›ÇÄ„Ÿ¸³¶Õ„œ°e7ĽÄ)mz R!cd¶ÐµH&ä„M›aÓ´Ÿ#sE>Ì2À–õd‹¬V4.6!»EÛ7-úyKóaŠ´áÃ|Áz}šµa6½^Ÿf˜U¬×§W¶&ÌRÇù“Ùöš}šq™«6MÈîÙëöiÆe¶G\DŒÿ"mZ*öBLzýÇнá"bûW×KÄ.­¢1>¶Ùx؈q±ÅÄÃjÿ΋öqĸØÝk[»ýݰcÛZÛÚÊ9-4^iDFد÷᣿mŸeL?ñã0#r¦ͰiëÃo.F©`=øÕù(‹•­o’ºCŒ%çºá˜¤˜X×E´…••íï_Ä$Åž›Í–½¢Qb¯dê’çì{ˆ_e›ûâKY羇ÄKÐ'Y£^á×Úl¼„¿µŠx ÅÞ†wÇ§ÌØœrEN+‹#NW6G‚61õˆ[ýöÖh/ðͯn-ݹQP¬b~#s¶×³Žn!êõKØ<9æk/g?¶Äï-ÙY{2Zé|m-Æø®Ù³ò~[–Ú÷¶r׬[½3i3:’[»çʇjÒ•}ˆç»ü'þº9Í;ÌsîéS£õgîuÁå«Ö—Â~Z†õ{­‘±ÛµÞ~4×Q×Ý1ŽúØcêºÆ6E"6\Æþ‡Ä5 Ù"«•íC2";úOôŸVÑŠYÃo“ûô䋼ÞŠYÃï!ûØú{¶Àù°±h—¢|(oׂö#±÷"³Ú4ÕëÛäâÜØ~$r¦ʹiÊÏ=Ʀ-Ц¹>K$Ö»pu¯®µiBÆÈìE6Í{Fb816Ñî—Q^—Óælš~A!{Å-·lÄ¢M÷ú ÉeŒíd|ûÆË-}ðZËJô=µ—“ýоMKÙ3îîx¶HÄHaÓ–çGS¿ïÊBìõÿj|”/òzƒMÄ ¶}K§'åu9aÏnÜÏ—ùÏËŒÑuk‘¹r—ÏØÇ—¹Ïï ˆ{kF#ûøFÄRKE,ÕS÷zJ¨ß69'ÛÊY­,–jDvÄRKµŠ>ñ¹Û¥Š‰Ñ]E,U¼Âx\b©°gÄRKE,±TÄR ÇR1ÄRKE,öŒX*b©ˆ¥ZߨLáëlÚÆüh=ù"¯…ÆRMÈ {F,±TÄR½Í†e¢M-}gñ†b©–Ö&+}} ۠߬'cdöF¿™›« bg:í²9á7›9g'ï¥ ›¶¬vYßgfË^k×BæíoAfAqÆ2™}ŸµImz´ø~nÊH­û±H_"⎭¬c{E{}P×”ã»)~±ú¦|Üþ\}Õ«‘8ïܦÉÜ»‡ýÀ÷¸Ÿö^Ç,öàóG¶Èvíû?íuÿ¤Å÷ñUoÞ`Üëç‡ôñSß6YõÞãIwŸñNÿø™y|†ï¹¶7ÞS}RžÙŠåy-žûÙy|EL®óÉ•kç+¶Å}™jïÛêÈ4ß‘ÝM|߬Ý}v‘)2}Ѿu{Ý÷ ù"ßUÛäÌ÷¯Þ&Ç¢í(}ÏÎã£c‹Þ¼¦XøŸŠ€xÁÌëÆ®Êî³ó½¤˜P¡û«”±ìë)߇íÄ"=;K³ÅEw¾â&ÛÇÏÎãRÚO¥Þ•øÙùF¾È÷•òÕ’o²€|#ße”ßµÈ7rvmÆ>PéÛ•›èצ>Om[êÙy|´,‹Þ:b-ÍË2cuÉÀz%k“å=vYnG–ñNÛÀñJÚÀ©ˆÙJzy ‰õ‰vÚÏyEÞ‘3r^ÀþÆ{ÙxrMV•‰ôbžrÑ›Ë÷Š|>[®Ew¯MËunyMVã‰8żWn³uOæ×nߤŸâù\Áþ´{Y“pëe"Ûm®W¿—õÞ÷°ÞÐ^ö>C–ÛY{iѲTBGÝ]®¿rU¦jÃc¯Èç öHØËÈÙ.ÒêkJ|Þ7m“ŸÏ¥Å+ÑÿË6îkzv>—*Ûr'²][}›wë˜VÞ¡~Ä´»>Ö&d› Øägçs‰~DQG­Þ‹6S¿Ü>;ŸÈöù²MF|Äk’í#æWÆ¢ÿðìõé²î7ý켿ªN½\Ísâǧ7[¿"Ÿö?&BÆJèv4C®ñÆÚVýú÷ùD®Ï“«^ßlur½'Ö\æ5ÛȺúrÝЗäs‰eU?9µX€ýÕ+‰E]‹L—R¯îM®[W|E>‘+r}æ~±¸vmñ¥ˆ™HE»©\@ž—ÔëÄnª=œöÊì³ó‰L_ßÇÙ›LÕöo ‘©Z‘_ø{è•Ïm7=;ŸÏ.¯Zìo*Ólgs¡_‘w䌜ßi«Ÿ=V=xÿ¥[öryv>‘-²}U»9Y±Oêž‹gç}ir^ó[÷Ê9]Ѹ|ÔÛëU®1$÷L,w(çgç}Ió¨Å|·Õ¶­…ý’s5;ó5_‘Ï%ÊukÑĽöU)â[³ŸsñËúÖ¢¹'^YÎ Üú˜}¹¢1ûGÊ6]™l‡bÑUw‰ÎÚ`¯ÈçÒì±Ø/sÓöøÙù|´\•h+Êu—¶ž_"ÖxQnC½"Ÿö[b]ö\øW‹@™æ'ÊÄ¡÷%îQ¬6`ŸCËp±ƒœD¬Rîp?Ûgç})1¯I·=¶;[ýì¼?ZΪóÙŽ…ä3Êuþâµ1ĺ…ƒ \ìÉ­²/ÑVy;‡GÊ>ëíåîÉ­7g7´Oõ+ò¹$[]úgì*þêyU»:óuUGËîO‰¾~Ò•ãKò´Ä¾ÐåØ3¾ŠvßÁ5ËöÞ¾P¹Òya²»{˜†œ×/çv”1YéÎäüм?z\PŽ{†æ¯ÜøúDùüéç¯_¾üí÷?üóÏ¿þãôýÛß¿%¥RI}úÓê{ôõË¿þúõ˿̩߾WV'úå¯S•¸¤JpT=PEÍgQe>:Xys.Šýÿõu©?tu>Íšßåa®5Ÿy•Ѭ:>Å5澓i<$öùF@‘ÿ]WïNÝ÷¸ë"åŸÝ¦Ï¤Ûýn¯=ž›g*¾ú?ÓâæþO“—^IJËRÁ³Á”¦žå‘ЙÏF5¦s“#S£wc×ײ?5ׯb,·~NÜèšÑ“Z_{Þ<;³z«-šÌþ~òGdõ©¾.ðýòycÏr‡Îl™³ÏÎôÀ3zéuå§NGÚ›wnÓP§O<¯æ™ŠríâVFÒšWó›c*íBÛ‹|™¯ßœÏhvšŽÍû¤}Ê3Ÿž|¦ M_+Óû(ß³ü°ÿϵùṟþCéó2÷¢œÄ–©jÒ#eÂ`®Ìœ¿2¤¼šsuy1÷ÃËï£Ó|‹_ÕGÙ±—a=ˆîO{*8™´äÑ%³6_Ÿö³×Þè糟¸ðº?¨+å6lHjªkÕØ }jÒPæ¾\§2™}Ÿm#µéÑâûÙ–íÓY§/ñåÊÙ‘·Ûº¬çóqåÜGó™YU~4ïiǯä=2ÖFÚÕ§}Ö^RE×§%×Az´Ý}=8¤ÚÊ%õòiמJ{ßû2w~È9õi)ú c±…ÈòymšL¬GÚó“?­-`“eÛ¤~ÿ¡áäê+ÇàÂÞD–Šk´e™z·ínùÝÖ¥SiÈ“ùu|pû-z}ûm÷eGù¸Ãv´n‹üo™˜C~— Œ»ïoÇwzk§Mù–æ–ÓQy§Ãíä<ñ¾ƒ=ôÉh‹=¯-F[æ,ÐÀV?ÂVøÎUR–!®óƒw[Wµîós•¦³u[›vÃÔ¡®q‡q}·÷é‘kòðçErÿ3.Òõ9ÿ0o¹ï݇)—k<‡ç¿CÁÌ=Ž'[¶*?g÷=kΡŽÓ¿×q0Ñ2-'-O>♺qxrzÔ}÷ŸÏ+b¯óœÖ¦ÑgŽèÓçmǫө‘•8.ÃPò¢¸ˆCщs=ˆ137>©»c—Cñ*c~§`ÿiîçAî2n%»œú–˜•[cSÍTø®òtû?1}Ƙ~ÏѦ!pLÑìmŒ{wí+Ù‹ôÅE/½°‡=ì×Ǿ˜aï ?oøe싞½—é];{±ç|±ëÓboc3×¶'߃ûÊñõ{æXÈÃÍ;˜s.x<¦¿~«Õ${»wÏ}@ F?Fù#ä?rÎÆÔ"ÿÊ?éîÓLù§ü#äüw,…ü)ÿÈù#äü‘?òGþÈù#äü‘?òGþÈù#ÿ]É?Fþ” ì?òGþë1¤½ý*Ðìò_¤üëµ5³}ËÑ1øÑæ>cÞCtÛšµ°†5¬a kXÃÖ°†5¬a kXÃÖ°†5¬a kXÃzþ:U¬3ˆÞb#` kXÃÖ°†5¬aÍÚ¢k‹&bï°ˆµESÚ“1¥Èù#äü‘?òGþÈù#äü‘?òGþÈù#äü‘?òGþ•¿·ËìšaîÿÐsÁòWVÞnªÂî·›5{Lߢº·¶slÇ8ó†Eìò¨¬ÜUoœ2Ïȼ®MéŒû­æúÑUÍÞÁñÁ¡Ö2I½^´c™òûÙ¦ÿ`uÂíƒ\^Ññ~>¬E?‰=ÅÝFãešÔgÿ8™mÆã¯_þýóׯ_þÕnHÞì=^TÏ=Wi>Ÿ›#þlóͲ: ›Îüܰ–×–¹»?ô¸çÞk‡ú¸<¥>ÍCG7{¶g6õ¹ê(ÍÿúÊý5³¿÷»ùßWÙõ÷â0²­ÿW {ó¿:ÙçÞÉ&Ζ}DÙòÓ¸Þ£±uñ·f"Nca;› a["Þ£g‡ó‘ú²‘ÛúÁتÐ:¶%‰Ãëýþ;WÓ®lÞ‹17Úïøîù¡ïnYXýÊÏÃå±MG4ÞÖ MÇX»¥~GÑÍ{G¯ö·Ô·ê6’L£hKV®ýÔ¾«ž—Fw¶ïzqWn¿0¹>èÅw?užÙ¾wú™_Ʋ-UöOe=çûÙ¶{ç´§usÏZXoºœ9Yœzvz¨ŒÝ+÷–³]ÙØg—{ä¿-ùSÇÎbí|­mµGÇÖ&7°]Y}ú´úëÚÿs¹fÖ…­ZV;åÑrVbN²~¿¬‡Ú¡±-‹ý¾^¿Ýz¾ml™ÓEþo”"Êûµÿi{.‡­»®p}6× ¦è*\á W¸Â®ûâúð˜Á“åd>f#KDœ„îö¡‚ãU‡ò)פéoÖ¿ÛXBƒXçKä[Êl0N0ŽË4$NPÙ8µÞ§;úçM¬äàµÊò{•oåÎßúlûýœúóÙÇ𻣑wGOJÓ­÷Þú®ÉüG3øØÿ•Ù9yBÚFò>7Ýè”¿×ØtêAùF§ª#èÔ}ùÆÎ-Àn=0m‘ºþ tû„}Â>½Ó> }êr^IéeÎ+)mì˜Þé¼’òuûO¤Î·qös{³ÄûëÔνl×âµ¾Ž¾_e·´JƒV/Íø‚Œ­þ̽OËùˆbûÞ´¶yLìy›>—Nô}@ЇÕëë¬Sö({èú°}݇¡þ2ÛÜKgm£ûºÌþ#Ô‹ØAì ú€>e‹²…> ø(èƒÐÁbÑôøêAêAìv²GÙ£ì¡è¾|3”MÊ&ú€>Ðn¦ìQöÐôø‘ÝÇÀ–«eÐ'6¶4Ïì³Jû Õ´“ÜóêgkËÕ~ÊöUkWÍ5¬Š®½¯Ÿ‘ùsõ÷D¼¯lä3Z/æ/úøIìi7ÍÌ“¬Ù5ó“Ù23þúåß?ýúå_íæšÍ>šn?òX‹½ÉÕcþÆ3Ûÿ+.åÍ×]î1šÅ[Œ¦[‡Öb97Ebö¶¡™pñ°e訚¯¹ÉÖ¦õipM½$itÙ¯·¹ÚDÏoIŸ|¾µ)Žo[-ýÖÉõ³œÌDõçλ*ÊÙô½CïÐ;ô½CïÐ;ô½CïÐ;ô½CïÐ;ô½CïÐ;ô½CïÐ;ô½CïÐ;ô½Cïл5ê]¯…î¡{Ø<ô®§w£rI“¹·}j|à@Xpœ'¹ ŽF‚OÕ»#£÷æÝ‡æÓ|/ªÿÏÕ»ÏÕ;#Ãæ8}è€kÜQ‡ »ûôÈ5yøóÆŽ"¹ÿéúœŽ·Ü÷îÃèòÃáùÛãÉ–-sd÷=kΡŽÓ¿'ÅëÒr5­ÑrÒÂñä#ž©‡'§GÝwmï×Â^=æ9Ò¦Íy÷ ýçÒ§ÏÛŽW§S#+q\N·Ë‹âb¾NDû úÄ'ß?’Kb ÍËíg…ÎÍË›öŽ9v5?/öýF¦,?ˆ©™ç©y–W|ŒÏX*F¤¯^.¦¼2/umKÆÌ™2®Þ°„šê-Ÿ¦`{د–}1ÃÞ»k_ɾèÙ{™Þµ³g.i®M›ã‘í}lÒ^¿CÛ%Û Ñ®º±ssÚŽ¢ÝlÇ@Úë ±ì‰[¥ô|CÓÒÊRä=ù®vì¢w˜wÏ=çô[»±mó^6i‘ïŠËaYM²OüXMû\Ù‡ˆÂûnL+sc9‡fisù#ä?rNÙ1Oä¿Mù'ÖCù§ü#äü‘¿Bþ”äü‘?òGþÈù#äü‘?òGþÈù#äü‘ÿ®ä#Ê¿ H?öù#ÿù»Ð =À ÿ¥Ë¿ÞJ1Û·üÙ-Ña kXÃÖ°†5¬a kXÃÖ°†5¬a kXÃÖ°^ò:U¬3ˆÞb#` kXÃÖ°†5¬aÍÚ¢k‹&~ýÖ–3k‹.'¦T‰˜Âž¬îŠ)tº¨Ÿ[z«^Ü›Æø¾§Â£Ó~Ky¹ª» ²ùÁ¼T7 cdŒŒ‘12FÆÈ#cdŒŒ‘12FÆÈ#cdŒŒ‘12FÆÈ#cdŒŒ‘12FÆÈ#cdŒŒ‘12FÆÈ#cdŒŒ‘12FÆÈ#cdŒŒ‘12FÆÈ#cdŒŒ‘12FÆÈ#cdŒŒ‘12FÆÈ#cdŒŒ‘12FÆÈ#cdŒŒ‘12FÆÈ#cdŒŒ‘12FÆÈ#cdŒŒ‘12FÆÈ#cdŒŒ‘12FÆÈ#cdŒŒ‘12FÆÈ#cdŒŒíòÕ5Iù€´«fÏûú¥}—jdãÞ[§A7çsûY§£ôi®ÏŸ›Ãí[Ÿ–KÜü^?'óçêï‰x§yÿÑæuŒa‹èã§Ÿ¿~ùò·ßÿüñÏ?üúÓ÷oÿ¦´VI}úÓê{üõË¿þúõ˿̩߾WV'ú寢zè¹JøùÜñgsBu&AÕ5ù¹Q¬Ðëä¡«k£´9tõ]ü³ö|\csú„Ñ–WnÌU…9r†2·ç*Ù—‡ævïI>ì5…?wþhžaŽó‡?¯2qßÑýߊºø[;§±0MЉ0ða˜]ŧ»•b¿ê2c“ÆX…V\µ¡K›ÏÑʳWÉôß9Vá=£ò|X¥¨lÞ‹•£¶×¤-4¿u…ç¸ Ö¦’ºµ1‘NèHÍÂê—y¯ãÑ©ˆ-ŸšWlÓãžS„§CÛŠ¶~GlóîòZtóÞÑk[YçVçêûúQÉ׺m8„4Ttõ¼Ô±È_Û8q\JÏ)ùЙ“ý.ǃߵý~î6VÇ8]¼+¿Þx]ŠìÎzŒå©‘AGŽÕ\Îæ™ÉNé²µçm½|?7×¹:#ˆsnï-±co·cS²}†ìWhË?òGþÈù#äß“¿‘gF_fv_fªm<°¼ÑÎÆÎ"äü‘?òGþÈù#äü‘?òGþÈù#ÿçË_w}~õ§Œg°±!wpME ƒ<_l?D÷â]¼Z—ÛçZ·1n™}ÆÉr2Œ /—%"FMwu04fp0ŸrÀHY­?Û8¼ƒ½'ïæ»–u9¤g: ˆÑŽªwª¨‰>š˜OÝ|öcZÇÝ£¨ž/ã?U¹ÌðOã¾³ÐÏ O7̵¶pOÕ-nm^íô÷[mž’LøÀóõÄóy­·Õ ‘O¢{çìÿîsiùйf˜Üx~yÌ÷˜9-¯™Ó²™ñR6ÖôTeSUÉ8Tâù¨>?ò&9'3&ö3/Êæ:c…Kq]qDÔGË ¨vLŸØé -«v«“½îØí@h7SÌ•&y]:ŸWš7–Î4\50Ùú3÷V.äYâ5ÎB´yK/õ¶æv´³&cm{¯ìøœ/ïs5Pæ:J¥HG!,¦êZžz†]²2]ŒfNèˆ'ÒtÏ,Æ%Ì`ìåoК'qÒ6®£òfÖE•eÎ%•?«ËÃ"kæ++žÛ6óØyõÙ|¯ÛàрſlCO¶~Ê×MV|oíâxFÉI¶9 j³®¬ä1!lu»]‰6¼º~®¶Bå 7Lö7 ²‘µ !l忦[eÍg–xWfb§öd™8—Γu_ÎK·‘Á­Ü‰53ž²†HÛÜõ0ß»Èʼ Â|”õHëNöB×nË×kŸZÎY–‹–s4Òr>Ù¤š–mÝâ=øÕ:ú«8ïs»JEâ¯×QÓÑ,dÝ7y•ܬ:>ÄŠ çdÝ÷öȽ7jÒQÏÖ¬ð¤Gñþ¼é׫ ´ÎSýºÖ¹Eº·Öy§WrͦÓ}Û¥¶ÒM‰Lµ°–ôI Íöb¾G÷ÞÓ±¢vBúæóšØüÚIܛϯõáÖòÍ…/H Ÿ›«±l­½y&V×ëÕ¹¬MÒgë³÷·6]y»´{n‰m¤¾¥·@û{v»ÈoâmuÊÆò›ûȃݗí¢ÇãŠ=ÜEÙÏmÝ£#22ɵ³aâ=|ídq˜4úQôÚ¥0ÙgÛ)¥?NÇîíš í¨ñº Ú‘¿?Õü¶¥ói+êÐÍÖ¹CBç-;ÑòîÑù)=CÇß56²äñ‡%ûøŸÑ|†¯z‘º:Óçú¿æ»Ú¼ïòϽËö®þÖ¢ËoºðºæÌßõÞw•å^qÉý°gôu­ÿ3ÛìoÍMuhøc”4zu0á‹fòÐù2|0[ælÎŒ(ÃÉ(Ä瀖ËÍë[— ™±üˆäp‘×±yðêê<ø8d¼Ó^Cª. Ÿ^¦áÛ[xµšiøÔý†/u“ÐÎbšpâ'¯9ÖfÂD]hݤ1uxtJp¨¡|`Ú)ð©äqô>ž®÷LÕVó¦j»ë®Måwz4¨…˜ü"ñÔTí8 Yx#2*œÆf÷—4ÒÖ–FÛ›j·º}Ö±WÄÌÒ¦ ŒRFéÏRF)£´ûÑl;¶ÛN]bI£7èÍÆõæc#w-¤Í[HÑ-{Ô®B›ˆ÷ ߨ(‰| /—¤³2dÉV³§…Y3ȤÝäÁ.¥«C¥—ëÉË\M¦Øá®bWˆ«¥ [—õ¼x·[ L‹…ÏzžëzùÕ¨)Ñ®…%­c»z¼+á¥_Î1žkC<ô©LGfßWˆ¥QÅŽ­åp °Å"}Io==£¦˜Á¯]?{·Î:Þ™¯Y.>ÏÝÖ¯>ú¼äîÙý^H"v(ü‹ñ•µÂCµ²eÐ-6èFLž]”µƒx^*¬ÿI,£{ô5ÁT 39ò2eù“4\(OUUQwq1ujÎGÇîùzÁ²êÚsæ×ùî\w |fõÛé#ô™Kè^®g6Ùsûnä7.‹Ëúd£í¤¤Mþt?;§ìr¬ªÛvÏtÀ½…hã[kœÙ%j/üb,®îXÊašKKS·½7°ñx¾Ü­ÇÛaݽmAžˆ|_s?DËp=,ši6#ð×m@õÊà߬|m¬5GÐãg0— ŠF°†5¬a kXÃÖ°†5¬a kXÃÖ°†5¬a kXÃÖ°†5¬a kXÃÖ°¾;Þ)tÙšØOÝxTÌ“Yr$Õ"þI.sy´K‘¸{sÍÒ7oYR&º²œL4c9%b Ý‹i}%2[œÌ"ä‚\ rÙž\hÓ.†5¬a kXÃÖw°.®,ÿlÞ…˜w†~£ß°†5¬a kXÃÖ°†5¬a kXÃÖ°†5¬a kXÃÖ°†5¬a kXÃÖ°†5¬a kXÃÖ°†5¬a kXÃÖ°†5¬a kXÃÖ°†5¬a kXÃÖ°†5¬a kXÃÖ°†5¬a kXÃÖ°†5¬a kXÃÖ°†5¬a kXÃÖ°†5¬a kXÃúyû›§öú'íqnö¤2|“R¤ëÕ{˜ýþéoÙ/VðXê¾°C²‚=ìa{ØÃö°‡=ìa{ØÃö°‡=ìa{Øïœ½CQ–wŒ(°‡=ìa{ØÃö°‡=ìa{ØÃö°‡=ìa{Ø¿–}Z¥C«†µ>5i¨?sÏ •éÈìû ›~—-¾Ÿ›¹õœ†X¤Ï·0ŸŽùÛt¤°ŒOÿ÷õ%ž©3±ÿIíóŠÛtèVjó36‡§?w&±smÌXÕ±;÷¤¾~¦NÝšî[æ¼\Õ›GÍ%’L¯Í#útWÚUc ê÷9]vº}ði3é¨çÙÏ6 ±ÏO¿³(®œÆÞþd™?WOÄ;ËÆVÕùcx…CôñÓÏ_¿|ùÛïþøçŸ?~ýÇéû·¿SIQDõé?N?ªïñ×/ÿþùë×/ÿ2§~û^]Xüé—¿ŠêA‘2Góú'ûŒD<¶¹Z'Bì!*7Nw§ÉõU³Ÿ½Üo“àb˜„ÁÐ"·èévÊN·‹‹‰zì4»gO£«¢µMS+ߨ%w¯œ~¨›ÃU§µéabuOÏV~yS´ÇÌB+ƒÌW¿u•–6Uº“g––bêb¨>qšÌÿÁ^“wóWëH9a>bã©1Çê1çÃ¥ÑË4 zg$öÇݤÿîR´?ëCw][žEAWž™lŸÖíg=Uß)lajºê‘çê•Ê”áêóth¾›#ª^YœšãTøãœ^*·J/´{ÊÄÕ”Ï&ÌÖX%f5ï©Ú‹›}¨Ö&ÉýU_Û+; vZzð¼SÛºoW Z>Úš ­*˜†NïÉÍÂ?fyï{Ü;çfûg½sýg ‹üÿª+»~¾}o|ã9í{‚£²‘S§VA”Å€åNâRXîhÌr—MSãd¬tõÿ9jþ¿ù8Mÿ~¬žå󎣞Ï#U45—©ÁŠè1Ï<ÝïEx¯jÒò¬¼Öµô“žëŽ"zߢ;érÓ¶ä²¾%æK-ëÏ´!Áe=òGQÖ—`>ìÁQ½ØœÞÿre2$Ý·–É‚2ùÔz–rµ6=}l!¶ð6[è~OOÏ9öf‹6ßçȺ¾Š-útt]¯,ÓmY5:ò90b—ÉkÇlìøÅnCò×…,h›Žúœ²Q56‚¬þ½A'¯Kç³ ‰ð yçÔ¨Ÿp#ä5³£gˆýµí½2Šé|yŸ í¨ß[6G›åW?WuÃ걌dez:vûÂՅΚ±'¹éòå¢(oŠî=S<Óéx½j?ãÓüsñQüèE³*?~eBiúϹxîAèó§ˆÂCe8zßexÁÂÚ»62:ïÊkèÜêdhÇÌë6V¹=ùÍ=‡üÞ(?åg98ÛXç=»œý€üÖ_þjÛZ<@~…í+!¿çÊO‰såúoXnÎf#?Ú/{”_«ÿ¢ý"ÏÉ6ò£ü!?ä‡üò[©ü ?^‡ü¶Uþ=6:6œ¿xÇÞ\ìØ›odÇ^ÆAe”qÐŽƒÒ_¡ü”ß1ùQþã ÈqPÊöù!?ÆAž#Êߎʟ]s ¹a7‘òC~ŒRþÿdüÖ‹kve7c¬ù!㔺aÊX3cÍŒ5Óæzª¯!µm.…ü2çÈò¤ÏúDù©îÜ\|”?ä·Nù9{‰üž°fÈÀX×Ô:o/{뚃ܬýŒ°™ÔyÈ?;¾_XÃÖ°f®"ãGŒ1~Äø} Æ?¢/OùC~È5“‘åù!¿mÊo®­Ü„üdPo·ü ­«0*?»våïò XWûIý‡ü–!¿Ög‚üÞÞ~ ]Wá)åoḧ́еŠâÏb{¯nÒ@,ã °†5¬a kXÃÖ°†5¬‰3ÜJœá`_ÞÅðüÿCçêþ}/Þo´ß{ÎÅsCû÷c÷í1FmB6O•á;cœ\™·kÐ?~nü¤ÈùçDùcOöD üQÿ!¿É/C~»Ú>³}¿Øæ+§ bC‘ò{ƒüœÿùx«„ü(ì°|ùݺæ\»ÔŒ¹=¢üŽW¥Ããiæ>ÃÑÜ˞طÿ°¾ÃkØ Æ{k_î©­D÷nø÷_¢o[ö«ãÏÞ}k}fpÛ…¾íÊoоÁ npÛ™O nÏ×7üIø“–âO¢¼Îàfã¨á†¾áOŸD9…ÜvæOŠ»_ú°Îz /ô nø‘Ð7ö¿Á„i9û½°ÏÊÂêUÕ_¢=B½ºnKÜž}Ø_ÀmçûŸcÓ¨ ècÑÇ¢E‹>õõ*s˜k„¾Ámí_öæ{¸¾ímO<ö¢Ã¾m‘ÛÖö^cϳÛöeç·åä©ÿœÓ‡Kg¶Æe¹æ±`Ý Ô‰ØËó öàt{’êî~¥CëJ­Sº'g­CñÄž¦½½Dûï[³zÑ{ŸÆ~~hШîÚµíZ6ö3Ïl›Ýžsv´–ql팻7LtsÔ×X}˜’ùè:åw¤CO¤£µe±°¥ÖÆ™sæYõoJœ‹EÿàCÜ+Îõûò\«;ö³°ÏÍzϳiAv×e÷9ÙúwLNY*|W…½N7u©ûm5öì‘¶ìûg‹ôÅE/½kßG;™¹—vÒèÙhºTSÖêg•öªáäžW?[ÛúÄ~ºç»tÖé>‹v¬kƾ]‘eþ\Û^=ˆwÇ÷DÍß`3Í#ÑÂŒGZ˜Q•Œ¨zT”„ý_¨+×ê±g\¶Öâ|™­µøÊô[l­©ÀÒ¥××B«ßgkÆL=¡öx~P­Üùp;G$è z³}½©ílÞ¢ºWêvìë‘:¶¾1ïsº•ZORÛ25úhMªoLƒÍw^­ºœ<3ìvdÒŽhêŸU÷®õò8¤P·êÓAõü°æuÆŸ{®’|®®93eüÅUöÎÕo§Ïæ^#wñÛškZÜ{¿·°ï4=†säj´÷Fͽõóì½:jz¹ó_÷ž©ã°¼¿s”vï•×ÍÍËédŸù¼dWòbžqL/{He±ÌRi]ð;ë!Íj±(Z-“­–¼;w¦æ%ÆõjÖ6Nxö9%ZÉŸ6}j¬}õî·±ÏÏLÎŽ÷ÄÝñ̺\ôŸ®pÜ&}­žËV™>6éî0.^Ób»H×ÑÛÔŽŸÞÙYÑòÊ\­ïZ¥gv¬g·î ÷þ:ý3ß+[}®Åwñy¶ŸÖ‡¬~lçR—‹\̱½VŸ¤Ûj§¹ísǶÿ]ÚÏÜsKe:2û>§ß…÷–·ßeìm,Ò—ôbŠõê½kú•ÞÑnwmåcdjÿD9­KØÛ™þí×%ÀænÔ!dŒÌ쇑6m†MKD¿IcÓgÓÜøE*Ú’Ê·)'mYO¶ÈjE>‰ Ù-Ú¾3b³¯Ø¶»¢1¢Äf÷ò7•Gq–q:Ø9„v.¡ C(>íT½êûGÑü‰és§Ä†F˜pŒCó»ùntEÙóæ{zô÷µG2~˜{Í»ÛpŠtø¹î»{· ­PYÚQfÍuÊž3‡éºÿݳÝóN6ßÍ3/Ã#²…†Gd; ˆ…‰ 1Ùã›8¯n MÛzzU8ðÎ[«ÁL,ÕNãêOûJÅT<}9=¬~ÖYÈsNu©Dw-óUgß=¼WùtøÎÎJz.H%\ENVp¾³M¾RLuÌ„>Çëæ¼÷í³ôaŽMKE·-Û…¹1Ê[[÷g±(?²nùù½°¬e]%ë}Ãp.lc$ìe:ÝM3ˤýîLÍvK€ÂxΕyë: hO¶K¡ÄÍ’+¡íËG§ù–¶×U=|d{5wÇÝ›î{Êv*ú4µ®‰éÿS¶7ЃGÉcìýƒ.ÍÔׯYÙm··ll÷;·º]ÿÖïú÷ºí}f£Ýî¢ éu®÷ÜûŒz ùb×›Ùel~¨*ZFǦ0Ý-Ó{ü¼jžŸ×]×úxÏbmåóáx ʶ]Ñð›ôó&Eèæmݲ;#Ì.‡/(¿<7÷(’ûŸq‘®ÏdzŽÓ±·ê L¶}(Ù€^ïaªŽÇ!6"*ýæôõ—2éevËt/„Ÿ±Aº¯˜ R¾a9ÂþtrC˪ ¨iÿáSØ‹ôÅE/½kg=À†<ƒ¹7lÓkXÃÖ°†5¬a kXÃÖ°†5¬a kXÃÖ°†5¬a kXÃÖ°†5¬a kXÃÖ°†5¬a kXÃÖ°†5¬a kXÃÖ°†õ+æïæ3–ùNýòêOÙÂ@‹í>–¶¥â«ÖdXÿ­ë¡Fb«Žž|à oxÃÞð†7¼á oxÃÞð†7¼á oxÃÞð†7¼á oxÃûåûf¦bL··Ý.Y ûð†7¼á oxÃÞ[彚}¿Ÿ¥©•«ûüxÒ¹HR‹ö´V¢ÏRˆ=¬¯ìW£Ã‹ÐaeŸë> ±ejnß•¡×è5z^£×è5z^¯_¯™gÉ?æ?+Ó;Ðõ•ÆKÛtÎÂî«ÛÜã‡^L_dèü#ôîmU>§Œ(ìÄjŽ-ÕÕÆ6ŸŠú9o[ÆèÄ&eenü5ÇÏÛŽW§Yo\&ñ`?§ñ3ÖîCëfTq çcãTÔ‰p®ÄÀBÏ›8ŽûNÿÜ:¨³3ÔÙÜÆ—Í稃µ7`ÒyÑ<"ïA<ªºw§ P±2®ÓiuÁ¼Óñ6u&í u„÷ç7 ü<0 í Hnù¯L|—ƒXsÏ)ô¡ü9“¶:øG5ùéܯ­>÷Ÿ™ œ‹{ï±Á)í¹bàÙé 2Ó×êùè`fbíMfÙ.i@S¿o@ó®L;à?ø9wpÓXe"ÐÊ„;™ÉIÜZ_ôeŒÌd&lɤìŠd'e• [WúAÑkr¦ʹiy/MØ´åØ´XÈ'÷ú৸jË„l· «5RßmsÝ"$Sº¥o´¹nÒ`.lpÙ ržÒ§Å÷—’@{›?°¯äÁsŸ}²ÈÅ}Ê÷g\‘Ñ›LûôÕ¿—^§ëw+qþ<èxs/øÈ¥É”kÀž•ž™<_ËÆÕY/êd?ZýNÅoRf‰•YrYïÔå[{A§6 ¥¢,$@Ê8*ëA_ó¬òÒ¢/¼é„W£&un$;Û£¡wæÅˆgz1âǶ‚žÝÊé×iì¹Éi"²Ä¹tO fê=º÷žNí’xK9©·#éÅ{|zJxÇ¢€G\;#\…•PÍèX沚7‚©%L› cd”nFM¤‡¹ÆË4¿ iŸoŸ1ùÞé÷šÍÜ÷žM”b>pof ¼QÉìÒÀeÅë,œô›`冭œ ˜ÆÒ=ÇÒ)ÛΛÆiÛ†rAám»ÜZº]XÿÈOtlÛ©™˜ÌèʽÚ“Ô÷…v‘ßLøõ(­(}ù€‰m•EÞOËÄÕ):Ì~ÂÍ–/åÜÝ…ž(ïoÛM[+÷íß½ë|;yÙùÌ¢!}zÖ¶Gµ¿0÷c ôIvX7ˆ¸®Ý÷EܘŸö“i_P®èë?¸¯_Ð×§¯O_Ÿ¾þ&¥¯[¨‹éëÓ×§¯O_Ÿ¾>}’;Û™Ó£N ä–ùþLÄXŒˆ5¡í%ô„v¼ï~éP&oŠ»±îx—Ïè1ïò}¼Ë¿ð®>ü’ÇÄŸÑ}Fï]}ªwô[^Õ7X­ 9žµä1#ê±ûǨÇVT=ÉßK=F=¶:úD_Í»ü!ïò9¼«_ÿž¾óÀ ®$I£)\¿(—™&¥í‚®ÅÁ.W6S§LYëÝïfAr}¸\ÄÎýîŽí¢÷Ï“Çáò\t¼¾À|bçù þžÍ_0ü³0þ+Ù´ë&°1Çaâþ¬wo[Ç©žªwغr[)‘ö£{f>¾C—Eç·ø¶ûâØ³3Ÿ*³yì?ߤS óx«8Õ)zþqŒ.ó¼æò¢Ï¶$ÏÇ-ö)N_oŸ®éa<¢‡}†¯*{‡õÚ©wÙ›wÉ ;‰¼f'ï±ifýlÚšÊÉÖlÚé›öh›6”çD].mQFÙëÖ¶(½_dwë[$"ß÷lðùµ-ÍT¬§²yçäÊHÏØ45ë®3wu î5mžªg¬ÍùêÍjEÚ¶¶amo±æìšÖÆqk“gÚ5ä¥Ïߨº™³c¢ÊFïî]g2Óþ³^[2ñkñf¶ž¼.ö² ^2½ÔÕ­²­¯Í†™…žÛÛ±<ÈuZÛ5Xe:ö7këêûânºäZ”õø›º_þÐr$V¯«û'qÎÚl·nkŸ›%k½ŸrÔa[ôØŠõ[ÛsJ”£hæú˪»ó¦êS·6¢N}I]©?µß×ãf»Ÿî«N ®?-{êÔש±?úö¼sN‰öª;—uûN!åh7eHùýƒæ¶K]›–6ÿ û”Û¢‹~—‚íÔ>cmçÁv÷À¹YlË}Ù×f‘{L\Ø_§·voˆ›Û,Ù¾m‚yNùÏkçh³¬£Í2W®wÉÚÖ)Èz%²Ö¶ε™+õ£ëzëâ;»ÛŸ¶óvTýœ¤Wÿ»ö@*ü6Þû^Ú¦õ6Ù·R7Ú„hg\£ö©êñm­ õô>«ž1v•ÌÛßpWvVû˜ê°7øå¹äF;»·:LŽ_Ðöz«wn¬ý‹ÞÞÏön½]ã8±ò>Р}¨Õ‹ãÝT×GÛI/q†Äg¸nÛ<6&øgxãxîÈøwHœÄ¬q±dg±gcàá´ñl»îÃÍŒ£¼×&Ð^£½FL%1•ÄTúvD‡mFÛìY~Ê6–îí¸Ž´z Û@ûËxÐtv1^h{ÑÙÇŒ Ù^Ø^a›…ͽ;îvïãAS}àþ¹‚˜æ ˜‘øJÜ}þ€vm¹?›ð’8Wbš‰iFÖË’µ˜‡·Á˜æMϯ|e¼ÄÞÖ,°|2;‰ý(\ÿÏÅ]kü 5 (G ì{ªŒÆ;më‰9àñøüEúžWôV‡ù¡ïŠ¡Ý![·w1´Û‹¡%n€¸ØïI¼ñžÄ{ïI¼'ñž´h;Ðv Ö“XObˆõÜV¬'uuý^ú½ô{é÷Òï¥ßK¿—¶Ã»ú½‰³L‰©%¦Y#kdýV_Å=s¥TwýÑ:8nä‰ß÷ví{ ®}¿¾µïA·ö}ÞÖ>n±ö½µÖ¼_ÔÚ÷dZû¾Gkß[híû÷¬}œµ·ñ×¾—ÉÚ÷ YûžkÞ÷bí{K¬yÿ†µï‘°ö}Ö¾ÖÿÚç­}ÍúµÇ;¬=¦`íãökÇçóÞxÐ5Ç\®=®qÍëD®}-Ƶ¯w¸ö5×¾nßÚׯ[ûúsø ß»NØÚ×¾ZûúRk_3iíë­}ퟵ¯¯ƒß ¿~ü>ø}ðûìÒïƒíÇöcûß2OßÃúÓÿ¾9?ýüõË—¿ýþçþùãל¾ûû7ee\Ÿþãô£ú}ýò~ýò/sê·ïÕ…ÕÉŸ~ù«¨ÅÍçù£ù?RÍëËìó2ñŠæNˆ©Š1ÅÐM½ÔS=zSRƦ-„N;L²‰éš½i’ýwMkYü”ÚØº]·±¼wjgê¦Y™ÖiLüt"ÇÚŸz ›žk§ë´SAõSA˜W„ŒþštèSóüø \ÖœlnºïÄkm¦Teö¥Ÿ–u1KûéW.<Ñ™ÞV6gûÝ™O!‡þt®vŠÎA¼û8¡3Wò5hã2Æ01†§*©‘õ>*qgs` —«ôm=¢üÉúæ¹×í’¨)˜N¾Ú¦©•ol ”»7<´ï{]˜’÷…¬zúëx¶òËíÑÃÐÊ ³Ï8ÙöIKáå™%—EÛÆ Ôé!V“ ¬Á¯ó ò˜ˆµ†‰ÎCŒI¥ªz…2-«*Ûªz´ª^‘Vç"ƒ=jþ?VGé~WM²´ù¿h’UI>WÉûPÍ=æp×'ö»ŠÜo—Æ*‰.¬Õä,ქšß`_ìÎ å3v/k‰Lo!Õ¢g©„žl¥.Òuô%i¨Dæ3{W³Wì,†ÞsïÔz MÓ»îm~ˆ™n–߇Qkgt¸ë ë²å*±V±öî|ØÑP13¡¾Æ¼ûܼsÖÈblßUÚ÷ÉÿµïùöõùOôtàˆü§ñ³ië§3KÇÜ©j²¤|kªxm‹¶Ø¡omÎp§ˆz[•—_ô5ÓØ³“­ÀX®xlùÞã™zî½§q_÷9Sò»éüFäwsùµ~;7›¨¾öl‡ýÝH¢«£”·³ÚŽêµ½]×kµµü.ôDYŸa²“übuNtÀÛ qÂäQ>üGÕ_‹f´àg>Ñ~>ÃF½Ë¼§¬ ô‹, ç0QG÷Úç¤é *ûÝ„„™Ð°ú5ÑÐ1Ð3Ì_Û3Ìw:Ö‘Îè®,þëâÝ.fJ‹8¬^ÌTg¤R¿fÄ=ÄW•Êtdö}Ewo€Vv}¿P,Ò—ˆÀÒÔÇ”µfð»w’‹éo×¥Q?udι[nçÄü·5×TŸ¿cÏ—µèÙËä'æó_|žoXcÁE;ä~Í•ö|oÝÓUØÞ(ÐæF/ÞÏKÈe2 ñûjìÄ–l¿Ì )(8¦馅X¸–âaä¼òç ;ÎàwÞ}ïß{:½ç]ƒ×L¼K¾g‹ùªÿO›ñ!óîÎÍùÓgÓ‹0ÅÁ¨i”û{ºÖiŽí¹CsÿÞÜõ( ½áq>ŠûUsÿÑpp÷§CÑVz™ÓT\1Ô f]Ûš‘¬':Â÷2¨ã‚` Ê e…²rµ¬¤M“‘²BY¹YÒ°ÁÍóp“?Êp&s)›”Í¥èâfƒ„mÛìZ)еRÆÖJi'9Ó‡ÀöÒ‡ ¿MY¡¬Ðߦ¬Ðߦ¿MÙ¤lÒߦ¿½³þvHØc˜¾ü–ô1›ÍÇæ¯Éækl>6›ÍÇæÓW§ S†i·ÑW§ÝF» ›Ï8#cò”Ê cò”Æäé[ÑϧlÒϧŸO?Ÿ~>ý|l>6›Ï˜<6›ÍÇæ?yAx»4ÚjÊž\Oéžr÷ŒeùDÚê¥ùôõ¥ùVµñï— ílüëÒ£ã¥lþûª%B6í\Ô²„‰ßxÔ-Ùè|ínÛq·ÝT¿\õ勼ä[>é¹3äæêÒÜ!ÖŸÎÏ]Z¿Iy]Nسp{ÖÖAN"VFîÞü]önaK%-³}!»Ìê¬c–tÛ_mßDÙö“Û¶4îöY\ûI¶«d^£± ˆã\î@¬±jv6kVš51•}§*»hŽzMÌ̯³iÖÔt¿nÛUq­ùí”ÛçEÍ.Ææ'³Î¦YG4j®«×æŒü=gDÍæ÷Øî`ôÁ¬År|¨+רmcå9e×þTŸÖt`ÍÏ<^æ’Ÿ¹ßÇaOË}™Q»RñÚºÂÎu’io‚ó²çÒ0s³›eÓ-¹«YàV.|S ³{¹»½çݹÌÊ뫹ræžóêöÕø_Áv,r7†Ö%–Þ^%dÎë÷ÊÚÉ0K}Ys«€;7o{.›!ëô¾VWŽ ßnˇã8ÀÛÉ¢>—Ï\¡>¦ ½­ Ù®€´íŠú…Ø} èò\°œ#äü69È/Sb߯Gʹ@Îo“³uû_•©­ ³Ìïšž(ÏáV9¯bØ*›áöH'Ò¥ì0‹2Q:¡½dJ„ßDbˆò,v»*ºÃ‘CÝïšëA¼û8=ly-oƒ]÷RÅ=w¹“†û?NüžyÊítQWŸþÚ»»3G­~î}ª¿³Çe¯9)_·W_"H÷´W ±>f!V…Ü D!eûÓ­¦Œg å­ºÞœ-1â¯Ô³¬º=¹6¯Úöþ„}›jö¨ž¯'ž/=ýú—|‘/òE¾ÈùZE¾¢ æËõ1ObD/{&oUžÅöËß={t?Óÿòˆ¼ñ ž1e£_aa{îç50Ê Ò( Ù„ýиýͦÜéÁnÎm÷6(ÐLÜK›àÑ©C\ãŽzhÁݧG®Éß7vÉýϸH×çü£Þäüs}‡©Öxχ†»å8ž|À­ ¾½çYsuœþ½^Ü+ZÆ¡¢å¤…ãÉG|9©¹§^öý½/Û²u²ï(Åóô•µæ‡òbuep_»,.±¯]<²¯]QÚ}…ì§û~>4{Þt¿Ü·BÅÛVLnz°ò¶¢Pˆ¶¶ …²Å%7µ÷šXmÓQŸSYsŹþ½°ysá¾òºt>«4o¶q4fÌw]ÚÏÜ›w¶[®ž¼ hó–^nŸR3;Úª^„+·÷Æžo|¾¼ÏUE™5Y)ÒQˆ¢oY·º{}D·§‡yÔÝŠÔqi«Ÿ£ßÒ§~†c}ϰçÝsÑôåV}¹gkëkéo›wçælФ¶‘0½ÃnavØ;6U$vþ<7¯*Ì9Ó296­“ܼ®hvÞ=}6÷šsîºèÐ\{ÔâÞóø½§*keÞ´z>Ì‘÷Þ5×Ç&ºyÆY«^yFE•Ï) q—­=NÇmÖy…zQ»å°ý6Ú©¸Ìß9o£Å”áMÊÊÜøfØÑ~;Ò«X/tKz%†L‹ŽÆÍÖäÁ['÷ûÖSç£>û%~Úñ¶ƒçÚ¡ô6|Ƴ·aúŽ0Œ¦¡õ[ç–ÏAðÊÄ÷¡þàsÊoÓäΙ´ÕcªÉOç~Ðf2p.î½'¶c-¿µñųӕ…%–ç õ|2¼¨lt£µ9o /2ƒ$uyÈü–Õ¯ 9rï¿7üÈ…]|žg†¹eÊÜrh¥t2«9•~¬°º×—12™°%“²+n”U&l]imhy]NØ´™6-ï¥ ›¶› ùä"¦"³òºfË„l· «x‹T¦#³ïsõµKßelC,Ò—ˆðÏ´nùrR"öqJ·ô67µ676¸§cSú´x››Î°¹Å‹§ôÒWO(62M ð:Â=Ñ/f_tËL'½°‡=S‘` kXÃÖ°†5¬a kXÃÖ°†5¬a k–ÁYÒ28½íÚXgaËàènÊC·×ÓW|èÚbñV½¸'}ñ1<~óÑé¾¥¬\ÕÛGÙ =cœGOó§]5zÒ®¯rX/ed9&¹ÌQÃycíE7^sh¹¦ZNñþãDli‡Á5 â"–OŠL™­ž£ ûf'6fM^ÌîÓ-^à¿]gË ¹ÎvjÍÓÒËX•;ôÝ5§CS”õIèSjÓeWUËbQôæVͽçë+Ïï„»>œ³­f3²Ò9wG¸æ’9ÔfO‹ã“?—e>¨ÝÒ8<|¿“mj¥Mh—+³r‰-WVçØ¬ì虸Ðù:m"í5ór¢ÚHÒ2 Ö8šÕrŠC3ÍÏL%2bPbjQ:4}2>äR9nYšs9ü\÷ݽ;ªp¨ìqK*Ü=môФÉl–:íð´Ð%Ð÷ É"^@ùú |mµ2¥›z–z–z–zv½åËäýa[ö^¿ì½­áêØ­ÉvhÙ0½S=į”9ÊÜü2—ê´$K^»%‰›’ìl|,öy'Ë¿œÁ«C[:ÓÞ³…¸ ¼óæP—ûÿ-K$vèÜ-½át_ Ù–p¾‹³ÓòK;šˆqμ;µFΫYÆáúPo•!ÃǤ~$3ôÃ='õËÀ%b©ÊŸ¯kÌ5myú¡v#¿–uä™·2ÊnX^ÃÉG‹å¨´]>½„K;¾-¶j·_9<)D/%<ï!¡qD÷¦ûžòŠ8³Z×±-Ò„=N'ôxLH~0¯…îÓö»m‡T†ÛcÈ´1¦ÁÖ›ž9Çü¤éø„ñ ãÆ'ŒOŸ0>a|Âø„ñ ãÆ'¼Ê¶_*Úzn‰þÈo‰²övþN†0„áæêí² Sô†0„! a¸OŸÿ ?Yl]›Ùõ¿Zt4à«NG|ÑÉ|Ýy‰Ÿ9@ÎcK‘:¿’cy!Ãð=¥âme’zýiljå÷SwùɱåPçúà‚}ëÑë}ëØ„…Ù„[Çýð¯v×J´GRÞhW‡®+ýÇéGõ=úúåß?ýúå_æÔoß« «“?ýòWQ%0ª^tª*UýŸW‰8‹ãpè§¢9ÎióÒúQö*éh?¶GB»ïùٳÁÅ,…î_Q×éÄ ½ý(úû0Œíy±èý3”1 ÚGÃÆ:Žî£šWv¶ŒÏöÚ³;Kº1iížçæ>'—Á|î‘úÈ4\ćVê³¼÷=î‹m[%®c7ôãÏ·yŠÎÙØÄ{÷2A6Ï”ÍmOòË~ªÄ§*ËEÝ£<ô=p.àÐâyJœÏ?šÏ(±µ…Q£è²¶H²×Õ‰¯¥wS[8ÍJk W³ÜY[˜ßSÓ‚IÝûÜnM®TEmËÆÝ›Ë{œu8ÞQ{„¦½×Sv¼¤¼(Ý/Ø<­Ò§UÓÂÔ§& õgî-æX´v/—-¾Ÿ=k—Î:}Vvyâ[ŸÁ;˜ÏàwW‹Ù¶þ«õwmk‹ÓÄïÚÏ"JŠ™Ñý‘ðFÄãôpçzxíû=z[ï˜mõ´^ÁRè¨F7ÑM¡[¡zé<¬ÐKk‹Ý;Ü컽\|<mÉkmªäÅ»XöÒ~æi;YR翟ÃÂëlÞ—ž}™f¡>ÛÈvŽþojÀO_ô¼µ˜ZÓuíäý:Ò" õÐGýCs|º1>Ý›÷¡^?7¤•¦­ÎÅö½!imó˜Øói× èÃ*õýé—ÇZ½‘µÚkìÜ6ìœj¼<õó\ËܵÔ^Þuš”Í‹å(ù; ¯JÑå.¯‹#ÁâäŸéòáôiP/ ѳñp.ƒ­ò<’ãmñÄx›‰¤p‘®E^Xµ7ŸçØþ~h"-¢:’Âß›úë¢êšsuî¨Ãî•ï4ÇØ½Åñò^ÙaTÔô>ÌQý_Äâ^óÌ áÑäÃÞ[ÿi1Òfb†3ó&²Ä1 åp<ÛërÏA÷z:c÷šw´2˜`X>‡ò’C©{ÊyLQOÌÿ¹OWªÉ%‡‹¼8¹÷î=%Me^:ü]^>gä%nz–æÙcÏ4y©Ó8ðLóYËNê‰í©*!Ûì,žylž9”GcbÌ}2bɘ±þ½jà^#çâc¸¬,ÛSŧLØV¿•yÛ =‰Uíñú3§ØÉ+®>KÃV°5æÕ•+5\ŽŒÉì”_ÉÖDŒ}4cüC÷³<©·#:?T-Šî(ƒ¦ùZM<ƒ»·T÷•A'SSn>>Âm`YÎ{ï1î¾·–K"äžÞoDzâº~é’äð9tï]Ê>nÔ¥s@½v•ÞfÏïÕ¥Bßgsó’Èt =F·ÊâÁé±åän˜ý±õü  Œ§ÓS7õ؆‘²Žl³x° S\¶aNçð6Œ‰Áº¹ S6Ï4Ï{DÆÕ5OmÃÌѱbf&ó÷~ÄaõÚXÛÙOS¯š÷š®Sh{¨í[û>¦Ûu;f¬í¤ºm§Sþø¶Ó1¶Éo;•Ïi;©;ÚNn$fnÛ©Ž¡Ô·×wÇ™õ]í6z`Ûim˜+m¹™m˜¢¼]¦Qô¦6LÑMOýÌÚ0æÏnÃäê6™šg/µ s©ÇÈúzZÏ;X‚¬õëd=¥ß÷ÈZš¼ Ú®Ïëy>ŽäÙ¸»ež/d¯„ìöÞ¡:{ñœ?m£kRfs¼bîߨlŽ6ò«izõl£zGıäñè5;Æ9ø9wnpâ#Ú\4ÚÔ}y"Ÿ‘èB;/oRN·D™æbÝ‹LÌOì­ï1%'ì× û%÷êÐØ¯ÅÙ/±îN«ïI¯œè+6­'cd¶P›6!'lÚ ›&ÖmcÙ°i˱i™Øª¸¾n[_žÈgö+óë°µökBNد™öKù:ûµ0ûåf:¹v˜ÛK± h‹õd‹¬Þ`ËRk§”ØO2»¾~ä”ìmß„.\µmúÅ1ý½ý%Ût®=®©uŠs'¨S–×Ïw}|g§´ðõ—öÉÈl¡ýü 9aÓfÚ´Ò3Ǧ-Ц‰Uêr‹µ­Uwe€¾<‘Ï‹ìW&꘤[ÏLÉ[5ÃV)±®;ãÄË´U±ØK o¿®¬|3$cd¶öWÖõËLÉ ›6sì8í[lÚòý”º»‡È䘱-²z³Ÿ2uTy»ìðSâ§\E"öþ¢NYPâÆºœ-R—«!Ù&+_äµàX¤9aÏfسB¬€‰-[2¿ì7Ú3!_äõf[–ôö‘)§e„›aÇ„O ;¶’x¤¬Ÿ7h¿„\‘ÓBúøÉír£Oÿ~ÑõHæÛ]Ô# œ+él‘³/±èç1’B¾ÈëõI.Æí“é¦äF}B}²xqŠe±ý1GÕéM;&ŸøŠS|+oõ­ä"^5¹Œƒ“6lf«óSÇºÜØ°RÄ‚e½˜Š$ 6_È™-Àži/“N¼ëˆœ°i3ã]KÏ›¶ÁuEz2Ff/²i‰_7¡m[ÇâèåOÈ ›vÃ|#â]·7Ö“-²ZQ¼ë„ìð_â¿\|¢½¿ž:ecuŠ-²ZÑxØ„ì¨S¨SV±¦k†ïe±qr,L‹# ˆ±èÉy½°^‰{ñÆ¥ðÁôÚSržÍô%'bÞ*öl]kWå6­'cdöæñ1á/¾X;aDNØ´lsö;7¬'cd¶Ð¹®r¦± ëV³Ì[mY)ú©ði² >Ì­×%ìKµíu`Ø“jº>)° ûQ=l?*ìØûùB¾Èk¡kÀŒÈ;6s EœÒbûö©èãËýõ®½¹"§…ú(Gd„ýšÙ§Ô¢nÀ†m¯-Ö“12±g*@vÏlMÈ ›6s9ò6ma6ÍùÃ7!ÚgWÇ…l‘ÕŠæ$MÈŽ±Æ^VÑNŽXdss’z²EV ˜“¤}¿ÿ¢}± >Ë Û±BôûKQðY®?VŸåãâÄ âÄ7'^'¾Š8ñ‚8ñ‡¬£dû1Ø´ ʼn÷d‹¬V¸ŽÒ€ìðYâ³\E¼x,ÒD²½xñørÏväµÀ}Cä„=#fœ>?1ãÄŒïpßPb“¶¹o(qIëêã—Ä$Ñ¿_yÿžq°íÅ$ Ù"«Å$鑾=ã_÷µ‰cÚÄ›lÇ´‰WÙ&ŽiÓ&^©˜ñ®ííR0εÊ=CŠ•{QP(1G•u¶·_HO¾ÈëÅuJ&ø»þ‰²ÿ'½øâˆ5fÏl½=[Ù~!ÙŒùõVÆÈlÁs&Fä„M»aÎDÆœüMÇ„ #³7µÓ”8JtâÂFä„M»aL?¢¶X› ?˜´s©XW!dl?¢¶¨vZ62¶ÑN{ÈÚIÄ)­g\F‹½ECÖN"Ni}cü²c|†ñ™U´“ñgn{n+¾ÌuÌmÅ—y¿=K‰[Úôü)ñK‹žÛ:"#ìØ s[™£¿Í¹­ÌÍ_çÜVMÿžþýÊÚÃN×Yq™ñ—…v2‹EŸ h #³‰z%[X\ÿ„ì°s3ì\&ö´ÆÆm¯ß/䋼àÃtñLr_‘aÇfÆb*Ï[¶Àñ˜Bì?‘ *b0…l‘Õ›ûÿ™8Ôí²Ã€`ÑuJNlÿfç_æÄô¿µ>Ñ¢¿¯zë/•Ó2†ÍôcæÌ!_t,Ù+Ñð—Q?XÎòEì¹§z{ô–Ó2ÂŽÍœ?.ö4Ä–-tþ¸´_Iϲçž12{‘=sci‘Øû5îÚ€Ž¿rBNØ´滘VlÚ¶Ö‹²EV+Üso@vø,ñY®b®+±üÛ\“_Ç¿ÊuH'dGB²ø:¥`-ÒÍŽ…õ䋼^X¯Ä½ya¥ðÁôÚSržÍô%Ç"F{¶¼X±ÌÇvׇ\;öšYÈY½yLLø‰;k[MÈ [vCÜkAœÒ¦× 2Ff ]‹dBNØ´6Mû9ø0WäÃ,lYO¶ÈjEãb²[´}Ó¢Ÿ·4¦H>̬קYfÓëõiÖˆYÅz}zekÂ,uœŸ1™m¯Ù§—¹jÓT€ìž½nŸf\æa{ÄEÄø/Ò¦¥b/Ĥ× Ý."¶uýþ Ù»DìÒ*ÚÉãc›‡[L<¬öï¼hGŒ‹Ý½¶µÛß ;¶­µ­­\‘ÓBã•Fd„ýºq>ú÷ÛöYÆôó?3"'lÚ ›V°>üæb” Öƒ_²XÙ:ðø&©;ÄX"q®ŽIЉu]D[XYÙÙþþELR¼á¹ù×lÙ+ê%öJ¦.yξ‡øU¶¹ï!¾”uî{H¼}’5úè~­ÍÆK(ü[«ˆ—Pìmxwþ¹ëõ¥ŠñÑ]„/U¼@\|©°gøRáK…/¾TøRõûR#}ð¥Â— _*ì¾TøRáKµ¼¹™"Ô Ø´•£u䋼fêKuFNØ3|©ð¥Â—êe6,mj9v¯È—jnm²2Ô÷ذŽ›udŒÌ^8næ×*ß™V»l@NŒ›M\³“wÒ„M›W»¬;fæÊ^cׯ¬Û2^ƒÌFùËtd.>g“šôhñûX—«û±H_"üެfc{E{½W×T˜ã»Ê±ú¦‚ßþT}Ô«?ïÜ¥É<»…óÀ·xžöVç,¶0æl‘íÒÏÚêùI³ïã«ÎºÁ¸ÓÏÓÇOCÛdÑg'ísÆ[ýãGæñcÏ…°½iÿ™êgå™-Xž—ü¹Çgøäú1¹rä^àù‚mqW¦:ŒmµdšoÈî&¡Œ¯Öî>:È™>éܺ­žû†|‘ï¢mræûo“cÑv”cÎã½}‹Îº¦XŒ?#ü³ ›*»Î÷œ|B…î/RƲ¯§B¶å‹ôè<ÎÍíõŠ«l?:si?•Á?xSãÃÎ7òE¾Ï”¯^‘|“äùΣü.E¾©³o3Žé•¡]¹Š~mòÔ´¥Ç{˲èìã!öòX½,s1W—ôìW²4YÞbw‘åzdo´ /¤ œ Ÿ­¤“×1¾>ÑFû9ÏÈ;rFÎ38ßx+çoB®ÉBý 2‘þB¬S.:kùž‘ÏG˵hïóµj¹N-¯É‚|Ü"᧘wÊm6¡îÉÂÞí«§xF>p>íVö$Üâ~™ÈvûÕoe¿÷-ì7´•³Ïåzö^šµ,•ÐQ¢ÓýW.ÊT­x.àù\À [9cÙ"ÛYŽGŒkJBÞWm“Ϲù+ÑÿËV>Öôè|ÎU¶åFd»´ú6o×1¼ÇŽ#¦íý±V!Û¬Ç&?:ŸsGuÔâÇcÑfê–ÛGçÙ>^¶ÉÀñ’d{õ•±è?#ŸÈ¹^Ó^Ò✆12-W°ÔÔ5=ÏÈû\Î…X²œKá“ yoE¾cû=b?àUõ{ÒŽm~t>‘éóû²[“©ZÁ9=cdª4þ3 ôÂ÷:Û>~t>çÔ>Ö+O¼¦}¬4ÆxÏ2üèu´êÎç/]s–Ë£ó‰l‘í³ÚSɂǤnñ±xtÞç&ç%Ÿ±u«œÓÍËG³^ÕÈ=†ä™‰ååüè¼ÏiµXï¶Ø¶µ°_r­fk½æ3ò9G¹.q/š¸Ó¾*…kÖ3Yü¶¼½hnñW–ë×>g_.hÎþž²M&Û>_tÕ>C¢µ7Ø3ò97{,ÎË\µ=~t>ï-W%ÚŠr5òìµ(pXmûéùœÛúùDسbÅçÕ>#ŸsÛÏ/{¼¨·¡ž‘Ϲø×$Âÿ¾\ÁUˆý÷s1Ž^ŒÌk¾’ùÀkäœoh>P¶ÑŠ Øêb>8‰Øë¦Üà¹ÅÎû½å¬Ú¾€Í\H>AÞù“÷Æûö2ð¾'×ʾDör÷”}Ö9Ë+y&·^‘ÿUß9ÕÏÈçœÚÕexǦü¯ž‘÷gµ·²ÐÞléq¹Àó©3Ñ×OÚr|JžæØF^¢»s&b¬¢9wpɲ½µ\nt]Ø3òŽœ‘ó3ÛQZød¥“ó3ò~ïyA9ï96åÊ÷':Éç×oïoo¿üõóÇÿ~þøýÏÃ÷/¿~IJ¥{ùïÃêwôþöß·÷÷·Í¥?¾W7V¿þöÏ¡Êhdà(Õ÷ø³ú}KÐÂ:ÃñX—•9¦-Ùõ\7i­ Çá^ñ¸÷=4/Ÿ3 ÊåýsáORnf€?¹ý=qŽ ´ ×ðÒئ-DNŒ{=¢¾¡?p!Ô½vøÖóª¤(Äèo=¨›¦õ¨x‰îÏzôØŽlWW–×#Ðf”|è~;R}¨ï=9-Ìp›óÜ”ÛQîÄ]7qdn´½úOWñ¦™ûÿBäFÁí}#Ó!ß7ô.tæf Ü»3ÝóŽNzý¨¿MGzºjìÓ'Þg¹¦b6"NÓØ|šÿž‚q¶ÏaÖL½WÅžþô¯!¦"_&Ž<:Íc“ÞO÷Ù™Aê¦_¦/.D†ô¾\û±Üõ“™KÞÖ°†5¬a kXÃÖ°†5¬a kXÃÖ°†5¬a kXÃz¬£™²ŽÐkôÖ°†5¬a kX?uôBÖÑJX«‘¬ã°ûÀüøÆÆÝÄ'v8hòëjùÿLšÎùcÝó~}æýrw¹`³“Açù&ß«ÊwvÖBÞä›|“ï5ä;‹Ã§¼¶Æ|wóø¨k°ƒÝ³Ø5'”F°Cï`;ØÁv>¯cÚøIg×Ó³÷Ñ"ßä›|“oòM¾gŸïÌí¾Ý\K7ù&ߋ̷;5y“oò~h°†5¬a kXÃÖ°†5¬a kXÃzÖûk±õܘwÒ‡ž£ç°†5¬a kXÃÖ°†5¬a½ÖþÜ´{ôÙgºçˆ®âHMþÅ™YÚ¥©‘[\sjžMà7sC:Fù×x?šžOÊ>eÖ°†5¬a kXhOÙóúÙûvÒ·ª}ÑsXÃÖ°†5¬Ù™ý˜Ù™5’ä›|“oòM¾É7û1³×!ì`Ç~Ìèì`;ØÁŽý˜é ‘oòM¾É7ù&ßä›|“oòÍ~ÌÈ›|³3~h°†5¬a kXÃÖ°†5¬çÈ:š)ë½F¯a kXÃÖ°†5k{YÛËÚ^æÛÈ7ù>›ï(¬¥AÞä›|“oÖö²n†5G°cm/z;ØÁv³®+XŸJ{ž²‚íyôvb7z¯Ñ®Â'ŸXÃÖ°†5¬a kXÃÖ°†5¬a=+Ö±Ó‰gʼ“>ô=‡5¬a kXÃÖ°†5¬a ë{°dœö³ñ¾æÙgøXÇøu¹°Zgç|œ—â_G:ïë' OÒI:ç—ÎG­GHÄQß—”âZ«§Å1á¬!äAÄAô£iWÓFtÒ&â úÑô£‰ƒùhäA?šv5ýhx’ÎÍ–#M=€^PÀóþé|æpO±[åoÿ¤Æ': ¾ÍÚùK7¾Íqí·Ýø2§ÒâãìùŒ>¾~{{û察?þ÷óÇï¾ùõ‹*£<³—ÿ>ü¨~Gïoÿ}{û×\úã{ucuñëoÿvÕ L"Œ3uõýXE™„íÛ!ÊO¯M Erû;NÒõ¹²Ö•~!ôݣᴞPòbù0‹j÷ £mD>l#ׇ˜u¨ï¶IîÚîy’‹}ÝN?»aõÎ- ÍÃȱ TÍdŸqTNò3 2;‹N»‹.‡¶Î~³%“ïì‡E?ñàâY3Í&,>.ÝFRÏ\€œu!—b³­Ä=ú0n&EŸÆ^¤Ï²—é]:ûh¦ÎG+§Õa¿Q~Y¿[nØot»OF0É<‚7¼á oxÃÞð†7¼á oxÃÞð†7¼á oxÃÞ𾑷rsvÜÑsxÃÞð†7¼ÇóN«thUsÕ‡: ö3 R™ŽÌÅçÚMz´ø}¬}¬OO,Òçü̧gþr½H\ýçǃ®E"H],&úéĮݗºg‹ Ÿ:­åÒø«ÅN×Ttž…+÷^ÿYˆ¾LîâÊÐkô½F¯Ñkô½F¯—¯×¬Ëa]¬a kX¯rm_ìê>-ÒÅÚ¾ùŒ_wäoxÃÞð†7¼á oxÃÞð†7¼á oxÃ{å¼c1ÿ™‹yÐ$ì!&} ‘ eÞð†7¼á ïÙ´aR׎ÉÄÚˆØùƒ! tÞð†7¼á oxÃ{¥¼Yƒ²±õÜè0:¼ÆuTè5z^£×è5z^£×3ÒëAýMû×óå.æÙ¥õ‡I/é%½¤—ô’^ÒKzIïòüs‘é%½·!WúÇÁžô’^ÒKzñ;¸4. ÛçŽCÃÞð†÷ìy˼gó~áü¥DzI/é%½¤—ô’^ÒKzI/éÿ¹Øp&½¤wéå¼WØ>ùü/xÃÞð¾ï¯ßÞßÞ~ùëçÿýüñûŸ‡ï_~ý¢ÒTGöò߇Õïèýí¿oïïoÿšK|¯n¬.~ýíŸCõÒƒYÌd„R}?šš¡öí™ èB0‡@UŽ&¨ ÷f#Þw!˜xL|æû˜ôï’;¦ÿaj~ï*¯žàÓrMúû¡¸ðŸŸÿCc‚)|þûIPî3:sY U¥Ûï5!9?cò¥;×ò3qQíÿê‚l‹§+Çq\ˆÂ]—Ù¡ÅfÍ!oÇzÁ¤_`6vq¥5J¢“Ü{ ^ç ºî¢¶¡•‹88/›pp^ìî¿ãÁyÏ>ï%›ÿܳ1×2j>?\¥ÚWq»J~RŹ÷•ÂÁ>qß³úžî‘÷X@l§®üé:ÿyʤgq²8x'Êm*îÑŽiäÛèžüíÎ<—_Æ'åy¬ý‰ÆÛ™¾x?Þmµ¬Ø…è.XÝveÃêñ‡(/â³5pšOÈûíƒa0×9³ÏúE ¾Ã&뀾zÁ÷6ýÃñNê‹ôÞæ_ŸB|©sG=B=²Z{&ÛPQýIY¡¬PV¨ûaC†¯ë Ñï¡-G[Ž~e…²B=¿|†Øl å†wÙ$*Ŧ`S(ôs)›* eƒ²±ì²±˜;æ´HB ,ˆû¾hÀ,ºÁ3›+7®M[ü†/\‡å\Ç• }ÈŽ®>b¡ÓOwú ‡+´¥RÞQçÚœœÓmæM‡­Ôß ¾Ùs§Ãgøº~ÓZç9W­Ÿ¤‡ôà„-£lÀðstk™OYËØ7ãÏÿcŒjüã(÷GéÙ¤G'‰¹GÏØ=_º×¦†"¹ý'éú\Yˆ–Ôû.öõ~7f/ž¡ýzt´lF§+n¨by9·çÓ½ƒ©‡žß3‚©oŽn¯0ó}Œ8æî~ÊÒ?Çû±˜]y\˜P3°÷ úsŽé:ÝÇ.+Ò“}ìt"öoÛ‰yaçCÕø^ÙïnhÞq´¿Tú?ìywfÏ»h{ÝÍš©8Èì"ϲ–Á S‘/Gæ±Iï§(3bÜ©›þîAkM†ô¾\{åæçǰ÷÷>“½H_\tÒ»töÑlÈ#˜‹±è³~±°†5¬a kXÃÖ°†5¬a kXÃÖ°†5¬a kXÃÖ°†uß|zÌ\:º‹€5¬a kXÃÖ°žì“šO8‡Ù¯}¾c{Z»½[kP¥.<ùœf‡÷…~Éþ¢‚ÇÃÖ[g×gŠ5×]Áü±kõá oxÃÞð†7¼á oxÃÞð†7¼á oxÃÞð†7¼Oö/&îÙÁ]‡7¼á oxo÷bΡ{”^ܺ¿¿~ò^ê±8S`â¾éè0:ÜwÆ‚÷û›Íyi»Ÿ‚^£×‹°Íè5z^£×èõÝtXžßd?ªÓ³^+’Ît]NÊ(Ö@ÁÖ°žýÚ¾ØÕ{Z¤‹µ}ó»îÈÞð†7¼á oxÃÞð†7¼á=?ÞCsƒý|¹·ŽØ×²ÉãÎõãÝØ‡ý/ï³÷û±Š¸?¿‘=ô«8‡ÔNšëTÛ«ÌѤñûÛßÞßßþm1­Ï+-”;·óÙœËÛ¹žŒ¿7ûhŸ§Û½¯÷î®ÿˆ3»oLÛ1½w4wô 4½šuó©NÏG?&H:†ŽÝ1_…ºÀׯszvs¢ô<ÏnNÂ\è¦Înös¾åóö›nês'[›F§&>ÏÚ̇Ø:ÚÏ…¸ú¸S×Ó¹ñQÐNïb6yLÜu—>ŸNô}@ЇÅëóÜ”=Êú€>ÌGÆž#õç™¶¹“θèÑel5e[>Ðn¦ÝLÙ£ì¡èÃ#õ! óÖ³³Éô±N‚z»‡Ý£ìQö({´AÑÆn»¥lR6Ñôv3ífʶ}ØŠ>÷¬6k‰tý¾cÏ;M=yË;w‡Ówöò9œò±ëœ:Ïš¸ý³‡}½žlʳQ^ßíÚl÷û lÃöY?‡CÚa[…}zºfK%ÅÉš­ôÌZ,«ÇÇÚ†L^‡¥ÜZ¬‚uXm_²ì¶îÉúî;¤¡Y Ÿ;>»ÎÙO»ž=á¦^óóó*\3i³ítUç§õ¼vã§Ýw&=×âNýº=,nڻµo{?§îg¡ƒŒŸƒhö*Öa‹fb}:f&勼zä%lÈY¹WÈ-w²+„ ÓºŽ³¶³,êØ¶Lœ:r>ìáûÂcÓÂ;e$Ïyõ¾i{Üæœœ°içÅòNš°i뙲EVO´e™¨‹” rl³¸^v³·oSüôô<ü½ÇÆÝÕÁ4ÜäÙß­5%Ží-º~.݉ç¤=TÛ®·÷ãnÊJÏ:—U3IÃ<Á ž øm¶ÎOÀ7ó.¾™¯ÒõGèÓÖ|·às·•¹î­Îoq\c+ã[‡ßÊ8öç·2ﺥq™-klÉ/}»~Ý}ë²óB‰uÙÑÀºìƒ[lÖ%§;·>Ù¬³Þ¹5¿FLþ÷çƒqfðÅ{òñï Erû;NÒõ¹`ÊÒÃn÷ø8乤SÃþàÖ¼GpŸ 7šï`—O÷I3ý¼ýCÒΚƒ­ì‹v+ûP=íŠïï¿rýJ)ö“^Ã> SöS/Ø[uöÄV°‡=ì˾˜`W,:ö~mk§œ‡ }?ä;ÚÈÎ׳{‘O7¦èŸÍõ‹|K¬ 硽¯ïS|G¤á–±nü˜ï”k£Ç…£Ó2ÓW®½TóÙÿˆÆ÷;rç3•y?©]­ky‚n èºn èºn èºn èºn èºn èºn èºn èºn èºn èºn èºn ú1ëõ!ÑÖµ>bMŽûY™”°†5¬a kXÃÖ°†5¬a ëY°Î^°ŸƒäÛËá–³<Ö8~BžÈy"Oä‰<‘'òDžÈy"Oä‰<‘'òDžÈÓižzÎêÉ"]Œ9«ÇœË²wçòˆóC 7æh>±ûßøÙ˜{Šú ÿ¬¼/2güT×özܳuþŽùùg‹ýé³öL–¼>ûçÄê{‹gM^²ê}&}îY{ž®ÓrìIObλ­Þs8&c9ìî¾|:G#ƒ3ÏÚsp»Œ~´9”ºÃ¡œÆÁœu•|Ög&ùtÅ#9˜1Ú‹¼®už=$ue^Zü¯É‹Ñ U¿{è&/6=ïŒÜÙ<òqR_SâÜžì(Þ¹¯ßÙ—ÇÌ<Gw6D÷YÕóljâøè/kÇöPñ)“úŒ­Ûê¿2¿m¬:l÷—ßyŽmŸ¼âÏ:ï½lÍÄÞ½Cõ—£Ø=Ó˶*תâw(úŸc‹útÞ”Á‹¶hªÞ~ÖõÀ5e°T·•Á³2í‰×–o9-Þ}ÜŽ×Ê%rOo·cYqY?/qøì{öQºt|€.U÷«ô5ºTè;Ûs“—ì:™Ý*ï]¿¨º~¹Õî÷B\=?T¿œKmþ¢ Sœ¶aÇç¶aÌûîцñuÍbÛ0™hcÇ·µ±½ý4õêÔ6ö5m'ÛŽj;©vÛéOh;åãÚNû¤G¦h;•+j;ío«ïöë;Ûï½PG\ÝvZiæcƒmûΙ¶aruLÍ»×Іéë#_Ëò!i¿¶Ÿ¶ë(S¾LùµÏ–žÕ=uÔ®]G™gËnu¨ã4eÝäéÓœY\Œ¨;>Úuǹ<ïçóÜœŸéžï«—×.ëVy¬õ‚e­ëgÕ¡ÎËé³§g©*d'‡©êD8Ëî„“«wBÕmÕ¾CW‡œFG¼ªjÖÖ!¤[;|5pp_ò¸ƒË¬ü¡¤é‹&Û½3F(•©=c±¸àáã¿u¢ÊO:|'ND%Ng¼ãwY7ÐmZõ©ãxWžÈ§G>±ã”^S1ANž{îääåå,Ýá—åe9a¿&Ø/%XدÙÙ//›Lè{Ò)'ú‚MëÈ™ÍÔ¦‘6m‚M‹ç\´±ió²i^×µ+qÇ®]²i#³Ø4_G¥Â¦‘6m‚MK\®ÞÀ¦Í̦ÅNǽ sú`ëøK¶¬#[dõ[–:[¦œ½òA]/»YÛ7¹þ’m{öb}‘¶¸·XŸ:å†:Åê”™öý}¿ßÛ)ßn3žÙ‘12›ißÿŒœ°imZ˜cÓVØ÷ïÈ™Íئ È ›6qŽ&í¤ ›6/›æûÿ¥+#ÒΕõÿçh„Œ‘ÙLlZ§>:''lÚÄ9Ƈ±i Ïô6-17#d‹¬^<ž™ y”×ËŽñLÆ3g_§8ŸØ$¡N™]âçļ}QÂVE#m“/òši¿ÿŒœ°gìYÆO°e+ÇòE^/¶e‰Ÿèß É;6Áމ11ìØBü–²¶_¯ýrEN êãÈþ=ýûY×#YhwQÌp¥·E޾ĢŸ_Œð¥òE^/¬Or1oïט_!7êê“Y×'9~b³®Oœ®´|^uð•¶’ ¿ÖäÔfHFذ‰¾aŒÏ×'¬>`YÇ—"áÆØð<ì—Ü#£–¶ë _ýœ6Øj÷éÈx 23›‡šM)Œô¡NƒýÌ»T¦#sñy÷éÑâ÷±.#V÷c‘>Wņ—ÕËmî%K¯7Š‚>5ãFñ™59íÆ»ØÞ’ñÔÕÍËud‹¬47wFvŒ§2ž:û:E‡ùꔕÕ)B¶ÈjAósgdGB2û5…ðˤNY×<]G¾Èë‰õJÜñ.ÅLֳ〜°gç뱎{6¿5j~îLJH¬)¼4_'d‹¬^; a­ÚÝö`^l}ûlwd‹¬¶/M¼c˜ ÃLi'¯v_!_ä5ã}iä„=›8Ï_°ŸÃª÷×.Ø×aÖûÒ È;6q_…¯Ò*÷¥Qø)ͺ-6 £YÛ/}'»õˆ¾¼ìÇ+±ÿ)}ùûÏyE!Po̬Þð}öDÌñæa îŹ.![dµ ñá3²c|˜ñáEœ…±zu}‘Žl‘Õ ÖMègklx@NØ2ÆTVcÇ 1F\Šò SY¼_ëÇTðiņáÓÊ1>­ßëÅõc°i+òiíÈY-p¯—Ù1fɘå"ÖIÄœI¾ê³cÎ#_ÄY‹1ç‘ãÓJŸŸV|Z9kߤ՞µˆ_Ò2ÏZÄ'‰þýRû÷̃­Ï'IÈYÍØ'i@Nز‰mâ˜6ñ*ÛÄ1mâE¶‰cÚÄ´‰:FÌ|×úÎ6(˜çZä¹ÅÂæ½¨G¨G”X£Êàë;Ó #_äõä:%ü}ÿD…ó ZþÅgÜÍžebß ìÙüÎñÄÙ©Ò®eÖ×;#³¯™6íŠ5kòWí&dŒÌfjÓÎÈ ›vÅœ~D;m¶6-ã`ÒÎ¥b_…1sûí´YÙ´l`n?¢v—½“ðSZμŒçŽÙ; ?¥ež×¢è»Äü uJ,æŽi'¯wm+c™ËXÛÊXæíö,Åoiµã˜B¾Èk¦k[d„»bm+kô×¹¶•µùË\ÛªéßÓ¿_P=’‰=©Gæç{Yˆý©½þćï’ÿ¥/ò:SŸd3óç¶m¢m‹ñYZm__ÈyÍ`ÜÒûš‹>ÎŒ°cý/U`Ž-›áL!ΜÈÅW4ÂïRÈY½¸ÏŸ‰ ®—ý~úý³®SrüùW»æ2Çÿ¥õ‰}}ÕÙs©‹söTç\Þò¼Œ°c׌‹s ±e3]3.íWÒsΞ12{’=ósh‘8ï5nÛ€Öxå9aÓ®X3^2·¼Ê=â„l‘ÕÏÙ+6·Ì˜%uŠ(Søï¯p~ïþ"÷=#;êê”Ù×)û®v.¬#_äõÄz%î¬+ÅL§MpNNس‰cɉØS{6?_1?¯âƒÜ/öÒ²-²zñœ˜'níguFNز‰cÈ¢¾Ç–­tßQ!cd6ã}Gä„M›`ÓtX#Áæ‚Æ0˶¬#[dµ y±3²›µ}“k·ç6†)ÒÆæö裼î=úh#/c>ÚÈ÷™çgNfÝûôiæe.Ú45BvÞ«O3/s·sá"|ügiÓRqþaÒé?Ž=.·qýþ3²Ãw ߥE´“#Öñ¯Ö6bÿlüauˆó¤}±–ÿæý¬ý™nرuígí䊜fê¯4 #ì×~—%{ô­v̲#cd6ãsßä„M»b ¿«ÿ±i+òUêÈY-p ì³dÌr¾J1þ¯«öUŠñ]„¯R¼â5û—lÙ3ê%ÎM¦.ÿøS¬ó D|)–y"~ôI–èG¡çZ­…bœk~jacZsœ‡LY7±:;&䊜æ_œ.límbê¿o>côëÛ»½`l~q{ìÈz„zdëY˽Îýu;òE^38§Møµ|"£…®ãÖbŽï’=+o·e©‹·‘»®eÝèI›Ñ‘ÜÙ=_>T®ìC¼ßç? ÷Máhâ0ï5º§µ>ÚÏ<è‚Ï—Õ—Â}:†6^okd`ì¶ÕÛú>êº;ì[’P×­rÁÉY-ð|’ÙÑ¢ÿ´ˆþSÌÞ~«î?Åìí·ˆþSÌÞ~7Û³˜u²³^' ?Rœìæ1íû.Ù´˜u²/±iJÈN‰~N9pNIÌ:Ù»Ø4Ö[`ÓfhÓ|Ÿ%û`øº¿S×Ú4!cdö$›æç=#qv\,ìZÚ9GVgÓôüöl¶e#{šÄ>hrêc{Ö?M!·A¹¥wÞƒY‰¾§ór²Úµi=2¢vÅ9¿Š~çªÏùUô9_6Ž&æÒš¾eÔ/8''ìÙãhëŸg;×ì÷_ðå.9nV°öyösr— _*|©ð¥Â— _*|©ð¥Â— _*ê|©è?áK…/¾TMùPÁ®áK…/6 _ªUøR#}ð¥Â— _*ì¾TøRáK…/m4|©èsâK…/ÕKÊÇ(ß™ŽÌÅçÏ÷éÑâ÷±æm9Æ"}‰ð•påáååø’ýÕø~Öù=9)×iÇ·C|oò·så6 ¾56}»`O2o—âþ¼š´F_¿½¿½ýò×Ïÿûùã÷?ß¿üú%.âHÛË~˜ßïoÿ}{û×\úã{ucuñëoÿªÈ"ób“¹êûÑ8ö8Ç¥sA¸Ç‡H¸'qO¶² W˜§ƒ1€×£Ã׆C¥[Çcny@Xr¨+R[=ºzT§±¨\ë:s¨‘Ò4ðuC%ù¨+è±ßéj&‡úœ¤:ÙpÏ“á†Ì":MÙÈNStßÓµ¢Õ8z'–W:–ô¾\ûQܳyèñظ-£]Ý™Ö.~AMWî:Z±è´LÌê¼__x¿dl&n³ÐÁ½t B§Ð)t B§Ð)t B§Ð)t B§Ð)t B§¦S©ó×ý×£SÅ„ñæâ Ñ‹Îxóš£Gw˜7ysáØÙ¤Ö°†5¬a kXÃÖ°†5¬a kXÃÖ°†5¬a kXÃÖ°†5¬a kXÃÖ°†5¬a kXÃÖ°†5¬a kXÃÖ°†5¬a kXÃÖ°†5¬a kXÃÖ°†5¬a kXÃÖ°†5¬a kXÃÖ°†5¬a kXÃÖ°†5¬a kXÃÖ°†5¬a kXÃÖ°†5¬a kXÃÖ°†5¬a kXÃÖ°†5¬a kXÃÖ°†5¬a kXÃÖ°~0ëAÆi?ólòQ?k®éêíÒb>íseþ¬¨ã¶iŽëç|“,|>c®'qç‹â¬žKtøì»}|ýöþööË_?üïçßÿ<|ÿòë•ZÛË~T¿£÷·ÿ¾½¿¿ýk.ýñ½º±ºøõ·Ub#“HcpªïÇꥑ1¶ûvˆòÓkSC‘ÜþŽ“t}®,DË *»íùC%ÏcU(¢JOEÿ=:Z6#‚ÓóYÉby1 µgcôŸß3‚©oL¹·e?g#޹»ÿrÝ-DÿŽ÷c1»ò¸0¡f`#îôçÓU7vmÖµu³" àº]«1à°¾3¯ÛýnGü\|ì‰m̵Íè ztãX™õ éÔ$®së`ÍÈ|šÿž‚q¶ÏaÖL3×ó¬eðÔ0‘>;æÓ°†AGU‡Qìý½Ïd/ÒgÙËô2àËà:¬a kXÃÖ°†5¬a kXÃÖ°†5¬a kXÃÖ°†5¬ç7Ÿ3—Žîb'` kXÃÖ°†5¬'û¤æ#™Çn]zßö´Þ×ioüö¢nio_¶}ð…ÖÕ©á—ß]³@Ѧſ£pßc÷®]H³a>6}|¾øÓ‰ñ7‹)?Ü÷ã©oª‰Óê°vù+ëwË…˜F·ûdó‘Ì#xÃÞð†7¼á oxÃÞð†7¼á oxÃÞð†7¼á ïy+7gWLà®\`®ÃÞð†7¼7Í;ÍëÍ» W³q·Iƒý̃T¦#sñ¹vG“-~kÿë׋ô9Ÿ ó陿\/:›‹[=yÄ5¡7-],&úêÄNS÷lôS§µ\ŸµØéšªó£Ã'×’à÷÷4Vî½þ³ý™ÜÅ•¡×èõÂl3z^£×è5z}7¶Œ²úw–-W§g½V$麜”5P¬‚5¬a=ûµ}±«÷´Hkûæ3vÝ‘¼á ïyòê#öž½"Ï)Ûû5yÜ9»íê:û_ÞgïÏ­}ù>^Í ‰sWã¡sW+–‰;³­0g°îÜá°îL±ôôlS‹›{÷Z<{~öPe¡ÌëSN?LÈ;ñFõýöÔkž›^[«wâ”÷uÓ{í‰êöôÙž¶þÒÓUo8Éy·{©ÌSO¿]°¼fÉSž*¼?ÿ¿©bfy2õqA'¹«û¼g§™G+ñD=Þ=X.9ú:%¯G©¯þócú»2½]_A¶ÉŽüŒxÆ6í ÃAÏ7m¦‹zîÿ{èÔ#Ú&ª|Œþ+lÀSÂa¿Î¶I¡^[_¯©mr(zÚ#ñø¶tL9ÛlÐ+Ï_Ük»NgÊTëyΕ)1Ö­76gæÝfË‘sfê>sfͼ“±M§Ó;þž·2;Fï]ÊÜ4wwŽàªy´;¦¡™GÉŸ]gÛ†];×Ôk~D…k&mvNMÕùi=ïç.ºïLz®ÅxܼOs­èywºÀ¹áô¹z>87œ8÷,l2›ùaý:—¤›æ[{jïçT÷$7wÙ̱–Á·ÀËÌròs¢úÔµ+cdÖ#3aKÎÊ®¸BvRV™°ue{>ûœœ°imZÞI6m>6-òÉE»7sòºdË„l× «%û'Ýls}_çœné+m®ßÞ"6¸ìl_vFŸfß_JFÚÛüŽ}%ïg•‡´è£E.žS¡?#]‚3Ògÿ/ƒNÛ¸•¸~X–s‰sÞî·7®ÌYðËÊÀL^·²pi¶ïpuH¿³^¿¯R”AQïØò­ÃAw[½Aÿ,eêžnÒQÇM:­¹ ÝïŸ1ŸÞMù3ÿ¥n€2i:ÉçÓ];¾±qzwk3¨iÞ©´sÛ7ië"=äÎ×=:Å=÷hÜ£_æ}Î5: «ìîÉH—蕺I_£;C.ÐÞæy™œèÅ€{´}G!ò"º­ßÎŽ_tÃ>LÌóXÛ]ç&ïÇÛylÎrw9É|÷Äï &mƒ¹# váÄvé;Ú%ºIY§‹U†®ÖSlÎØò—>¿üÝšækÚuø^í6_¯Œâ‹ïö1KZœ/eÓmT¡kßÔÇv]ÐØ‹Xt»]½DÄWº._z†ß½]º¼ÌGôè¬kˆé eu°SÝY óß1>íñ”'žôLGÆ&þX+âäNL¹½¹ê±ã.÷ì´<ºcÒmDe2Ÿºþ?k0¯0ŒºçýúÌûåøsâòØŒÊk™X¿¨êk™›k9«ËZvæ_“æc'™0ÑÐú<‡Ú •ªììÊÓpˆû¯û`ž5C<Ÿ¢r/†PGí²XÕœ&—‰Ù Å$¡Y·9¯›b¤EKÎWë³µjÇ W¾‹—&„c3Ôý›Ž¡¦´ì¶4]’ôú 6lsÄ5yFYÞ{ÍØk5êwìÊÕ1¬`ÝåðÞ?%²¹ìU§•­ ³kÍïTüö^cSg‡ýlÕRdÞ©£¼½lìsîÞ7ÐMill†&,§±;ĺ¹}ù”ä èXk&ÎËufØ|÷°«“FjìîU(=-‡çuiÜo/õïq½íw½íÛõê–îÇsÒØ×ÝPjDwã° 3½fv×/303ÏvŠ›å5Ãj¶Nè,K1³¸vÈÍ ½i·¥†r»Û¥È*÷¿*\=³fìÄ?t‚16‡êÿCV‡}ø.Cœô_ï†ÝaÜ}cƒ:„MÆ&o-á—F¸¼I?S‡]ÚŠážy®^2&ï»ñyomAòÑÝT‰û¯ˆî·ÆP§¼#Ùxï@\ç–Œ)u彉ºy¶lûít>]ÛãÂ6(çdcdè¿ÙnÜEÏV.Cï7y”ÑþºmO ¿©`¾Ÿ ™ß[K 'n¿Ñ؛ߞÆZ‘=µ¹æ½«7éJ“Ýaãì'ŽÝÌš©ðü½È³¼0öˆÍȳ¶GlkóV6€gxXÃzˬՄÚgóiÛ¤~ë H¡—øãz_?<ÁÛŽý»t´®ûïS}ïÄnR›ã*wR‡U–‡[vñJ7ÊUê¥ßéG¡³³Û•) ËÆ7ÇQ…¥³½eþÜïc»Ý;öÀQüÙgâÏ^\(CÉ:e;[;$Þ´joam­¥µa–Ø6˜{»äºl 6y¾¶®Ïý>KÔÈå¾Gq¬eüYéëéW*Ÿ|{Èôñt—5ýÙ¿#›}Ñ~ãºs¾éî™æn¾\¤ÇäߨhÃÃöáÝu¿§Á,ƒžyúîÌb« ÷Oò•?Üç(ÇÃ~CÇFÓý¿Ï•ÿ¾r?ö¾,§|R>ï_>s;zõ;²µ°Ëב¥#Ú7s;:f-íœå·sèÑ΢ôºvVqçòÍ$Ð.Ýzè;þ3Ÿ·0]…£e¦lÔ´ÉÍcqÎdLÊÄdVtfò*ºròe Þ•u.& »Ÿ~¯ïk˜F ÛÀoÍå©Sn¼#C#kÊÌtû¤:Ž YÇ©Î×l€¹UG<±5öÉçÎ0„! aC†0„áæö9V³k¶‹ 6Å0Gz~ý„! aø†l4ÄFC°†5¬a kXÃÖøáC¶:²îoÙM›Mô±ôãÈÝ&ò+7I˜ó`Ά0„! aC†0Ä'†9_|b`Š^†0„! ñÍbN”ùgXÃÖ°†5¬a kXÃÖ°†5¬a kXÃÖ°†5¬a kXÃÖ°†5¬a kXÃÖ°†5¬a kXÃÖ°†5¬a kXÃÖ°†5¬a kXÃÖ°†5¬aý ýÐKw†û™wÒ‡ž£ç°†5¬a kXÃÖ°†5¬a kXÃÖ°†5¬a kXÃÖ°†5¬a kXÃÖ‹òÍ™3óNúÐsôÖ°†5¬a kXÃÖ°†5¬a kXÃÖ°†5¬a kXÃÖ°†5¬a½RÖUú“b¦¼EÚVÅ<É;;Ã{dþÌ–“‡g–í§çÿRÜ–Qõ.]]Ó.6-©K—‘uQËÏrŒÇÇÙÈ ó~}áýÉGÐ]Ã3\Ç\C§Ð)t B§Ð©E딪ÛRè:õT;U½+ÑásQ¾ÝSúFE¿¥é—å°«óeâõ,|þšt¸w°éñï)®Ð¹ê}©O[Ïûì÷رÙ&]þ Þƒßs'¿c»ï®÷®ß¨]~Ê:î“w—ä…²|ò9•cC†0„áã2—Á\¬a kXÃÖ°¾ÃxVìÆí"Æ´Î¥c¨½jã(ÚyÏešwî?§Kö¹¸“Æ,Œ»Vö~ýÄv»Ô™Cg?‰¡ßúÊv½+ßhéK?®)åàYMålÞ™l˜qÜÑݤÃÔ3ööÏ«0Ï4Šs^Ïyl’s^ójÆàÓ0ÄØÉD–Zè¨üž‰Ïkl­ÚðGÖÃðZ– aC†0„áÌ*Ñ¿I\›–×±ìó3‚åt­\ô³ïÁ­Íù'¬zÜõž2Ýú¸*þ;0ÄŠy^æÔa ëíø/ÌÄoýEa kì2~eŒoàWF߆0„! aC†0„! aC†͸UYX¢0„! ñ£b^Ö°†5¬a kXÃÖ°†5¬a kXÃÖ°f=È8íg“»ñBóì+Ît#ÎÇmöг¯žæ??ôÿüçï¶Gdw_ÈT좮LkÔÞßô®srÿÐR¤y.L徎‡N™P¡¬´þ›šv/›{¦ûAû>‘6ÒFÚHi»ñÅ;íË5çùÿ»¤-vm½Tì!žvö½žKZçÌ‘´­/m/^ó»ä3¨Ii#m¤66mlÚØ¤´‘¶'Ûç\œ†}&m¤´1æ1ï1ž÷¸„s—t>!ýWÒFÚHÛìÒvçóµ–pnÕÒ΃¢ <ƒv$}ú7sìß {ÒFÚHi#m¤´½²-õÀýþ-i{^Ú¾~{{û察?þ÷óÇï¾ùõK¬£XÛË~T¿£÷·ÿ¾½¿¿ýk.ýñ½º±ºøõ·Šê…Q•ðÂ…Ô¼°zqQ…ñ¾v,êOsß±º~¬>URš{M8~¸çTx_úñ¸àãñiÝÛqŸ¤5ꇸýžnžö±ºïx a(ÏMº’z!ã~®¥E7]µà¬8œÜÊLȲ‘NÄšâX ì×ëêžs‰:û­±»ÚÚÊìÌzßΚæn|‹<¯Ï1ª‘k±Õ“×bwÒg×d«•sVŽd_„ºü–3ÎR¿÷¦+W6IØ¿Ó뺑§=óËËÒ­YnÎDÓ7œ‰vÇ4´êŠ]xά½6k±SqÝòNGwÍØ»nûÐs¯Ÿ¿½t¯êy®ï]ªýŒLGï=ÑÀwï£8ðÿ˜tÞSvÞËï‡þ¥ScuÛé÷]ôàž:½àZìêó[x>ÂVÄb^Ï_Kœ_I÷}qO™O¯¸ö$Û1J—’޾¨]‹¯(#E›ÿþ.¦} Ë×ȶٹöÑïæõgŽîºïUɈŠvð½Ö{tx‡éaÝã=Ýô¨²ó×3’á÷’)ï9í­åñIoíì.ð;§!ù=°ÜY¼õÀF—îl'«öNÿM^µ;) ò='ÃÁzޯϼß÷ìÿΓÓÈÇè^ëšû~r-;}V†µqJ"1ïkÙ¾k®gN¹£ÜM*wz Ü¥N~”;Êåîyånd}ç[ä›)wb« åŽrwc¹Kk=¢ÜM/w7í¼;Ã~Ã3Û sl¿­*ÿW”çg–9Ú³çå¿gôPÅ‘áLrØÕCeÚØ3º¼ï:mÿŽòá{ÇãÈrë;º!ú\Yˆk ‡ý©ƒaÅAÕkKχiƒîÆØˆÌÝ÷QPFnÐýÓéÅ´Œçé š:O­ÍGzï”ì§óLÈ;ñFõ³ö}îY]]‹tý¾cÏ;MCcL^¢èôYyß5y±ïŒÆçÅ>«k‡<Ÿ—}ÚÓŠâùv‚|G‘êœêüÚêfªÇ'"?q‰8ç×jñë,^åæoÏÅáæä¾Þ6>aš|ê:øÿì^’ÉUfÏûõ™÷K7ˆÌyv“n]$\w ½AoÆè‘mö,mOͱÓÉ“§/äÔEryêbñ€Û•t»{”ÝxŒ^õ4tž‰Ö@4Ð88ïKãÉ™îœG§ñ"ÝÕ^ÆQù\0÷ý§Ä÷4ºü®ès\°Ÿ‘gîUѸwÆùøø7²çÉÜèÔ‹ÓcZÚÇhåÌ „'ëѿƼ¥Ø °]»žá0KÙ¤¤m.þðØ ÒËc\½!{oÏÌ[œEÏ{‹ƒ‹Ù¦æàbáºpëY1OœSYÍR‰òüNOY.Q^X^¿¤%jÂü ¿÷™ìEúâbÄo°‡=ìaûÛö‹ÌØ/òéûEv®¡Sè:…N¡Sè:…N¡Sè:5ŠÂ’*Î6çlsÊ/u:…N¡Sè:…N¡Sò¹ˆGêV¶ŸXœŽõœ§]ššöpìN±ðϦX÷zC:F­czhÏ,|âodƒl ²A6ÈÙ dƒl ²A6ÈÙ dƒl ²y”lì{e‡y=æõ˜+F§Ð)t š¯N¥wô;x„m*|heð¥¥ôÈÙ dƒl ²A6ÈÙ dƒl ²A6ÈÙ ›…ÊF9_Àz<Û¼¿ù¼á²º «+¸&ñýå4$»Áy‹´~ÅçÍ<ûŠù%â$ÎÉqú aKœÄIœÄIœÄ9×8ûö¢‡-q'qçrã¼´W l‰óֵ̰%NâçÊçÈù#äü‘?òGþÈù[sþšqsdHþÈù#äü‘?òGþÈù#/_+±íõ_¿½¿½ýò×Ïÿûùã÷?ß¿üúEéD•öò߇Õïèýí¿oïïoÿšK|¯n¬.~ýíŸCñÛ?E•Ácõ¢ãñ·>Êi!ŠÂ÷üXÿöA»ÏÔLÂ¥uPE½ðÁÜÿ™„wÈ÷”âý‡¨~¯ Göy}m(]‡JÐû*ÞòØ~· Ÿ‡ðÞ&íÔ<i>~ˆßÕýÊ¥U™ÿîÕ~o±sïS§¬š¼~öça×ÇØÇU„w–#dÓÍ_î6ð2ïÑy×cõyØ× dÕÂëO ¥ªuåÜÂ#»¹×±žTõ YÆnÞe Câ6‹ê Ù\lâÕlì%6 ë.žZ ´ª Ó”{¦`ô¹.L³…Z ~ÔïIÊðùìkÈì‚Ì<³<ÈÌ6^>Â…ö{Z¿»¹žŠÏW> q]ÊÅ—uqÍ/ B>#å#Ù•tß;µI™æ¡\fÁýÖ5]ß+ŸíÆÌÆÉì¢J«e«.Ñ•vÐ¥«OîÈîBg'?åoÛ®L´l¡°>¾¡û2i'£Îû]çоC܇¬®/g¦.jÊ””©º\~Z²e0‹…}”e »x½¼’i¶ÍÊC–9_סÜÈç|]yîýÈíNõÙˆú§Ueû$g…hgæîZdnŸKï´áÄFûD[èWlµm¾õöíÖÚˆ[lgm¹­²íú¾oÂ'ò1ó=UýŸ¸¹3Oíê1áȽçá·ëHC°s»p C×ý»27‡r4öIŸg…g"Ÿ†¨žÇ0sJJ_þßο¸÷š8Ò¼n©èt®#KŸ7Õá×ênqšc™CI²¬)ŽnÜ}æÉ›šæ¹B˜úÒq(Ã{tÒ‰q¶æ÷sñ)«äwß±=}›iŽÓeoZËP5Ú÷–î=¹3yq¨Öãr¹ìy1ÕŠám¦‰MìgòžÊtd.¾ÂégºÃÍ–tH§MŸÓé< ¬g£#=ײäò=g¯É&’¬2E³Éê¢×Ïd‚>®Êzh?]@'—¥“£t­èéZôéšîüö6/wº1VǼ^¥ÎþeNß|œiý¹d]»«]I;ën¤,T‘]­¦ûå™êv÷Òë@Ÿé^ÞbG”ˆ;:æì‹—í½‡é–X¿S]Wa'Ûv›3Ææô ”zÔHy¼J¡ÇûÒ$ËzÆîš÷x¬¢>VÿLO»Jê¡2ueî¼UUè>»×ígÍAû§½ö${^¯]ö^·ÖkO&8'žëµ‹¼™xòè4ŸMš?Ýç±v¾õqtóÐM£)5M:†dU.Pé”÷91V(Õ¢ä4=ydÅÄ‘ëžV§~RKWððñßTóDa'©“ÏcpŠU#ÄB>±˜4ôNÃ¥K·>-[]Ù"«YÅa2ë¬ÌŠ 2Óa2ÔÖæEOË' ²CN/’“›øjZê¥hyù‰ù伜nêýmÈîl¥ÜnCï{z ¹ŽGÎUúudvýUæ-mÏÁÍ!ÆûóA¸ÇÓS{ï¹ Äw+Ön<ŸƒX;fò>ùù•‡BÛ ñkì [ ¾®–ƒñ‰:‰LÓ'®•NÆí8 ‹æá@4k¦¢‹‘gé&Ÿ9¼+Òg‡vK±V~éC»*LŽÖUOf¯Ú“w­ôÂö°‡=ì§±Ÿº×Kr¡Î]Â^/z&kBõÖ@ud4å7la [ØÂ¶°…-la [ØÂ¶°…-la [ØÂ¶°…-la [ØÂ¶°…-la [ØÂ¶°…-la ÛëØâ7Žß86¶°…-la [ØÂ¶°…-la{öwÛÑl³~v—~ö°…-la [ØÂöŽlé;Ð/ƒ-la [ØÂ¶°…-la [ØÂ¶°…-~ãøc` [ØÂ¶°…-la [ØÂ¶°…-la [ØÂ¶°…-la [ØÂ¶°…-la [ØÂ¶°…-laû|¶Õ³IÚþÓ L §R0;óݤwÐO:í÷çöq™gç^öHé#}¤ô‘>ÒGúHé#}¤ô‘>ÒGúHé#}¤ô‘>ÒGúHé›}ú²'§ï†ýíIé#}¤ïQgÒGúHé#}¤ô‘>ÒGúHé#}¤ô‘>ÒGúHé#}¤ô‘>ÒGú^½~}^kÀ¿~{{û察?þ÷óÇï¾ùõ‹J´Žìå¿?ªßÑûÛßÞßßþ5—þø^ÝX]üúÛ?‡Ïêû*QÕ§ª"Žòßþ9ë`VT׎±»¶«Y}FEõ= ÏæîP°cõlTÝs¬®íõ¸gwfQºy®‚òaBމפ©ÊäÞ\sÏjsM×ï;ö¼Ó".®Ï‹½ïм*–ÈË¥wšôØ4Ž|gìd­ª{¢¨怷æûúö¿Î³&nó¬çjÙæãŸõ÷uÙα=Ôù1̽”à°sϦuÁ°êîËE¡Ea©ËÀ¹M'ìÁu.~Ó„±ÓYcR‘Åîà< }÷p½îF C›SÌú0ÀØÑtÚa€÷<Pïët7é):>ø À“´íëø†6ñAóŽÂ}Ý»v!ÍyyŦ%‚‡ß¦b¼M¹J¨ïóè>£ÚkWös_á•u\'•°v²2¿óZì§‘iéÒ®ŽæB:òE^=òŠ;™!¹ä¦ë笌2'ç feWÖ²ô²;''ìÙ{¦‚ã¶lf¶L;Ùh ¡?¾œè öLÈyÍÔ– È;6ÁŽ%.Ç[63[‡ö—ýôí´ÌéÐ9Ö‘-²zKóíiß¾.¯—ݬí›Ð…‹¶MŸ±k"Ovl+:Í_“ÖO÷ÙaØM»L[|iL ¤N¹©¯Ÿ„öuÊ ûúB¾ÈkÆ}ý9aÏ&öõã +lÙ ûúN¾ÈkÆ}ýaÇ&Ø1Ç”~þÊúùB®ÈiA}ü¹Ñ¿§?ûþ½+OMš¨KÖQ—td‹¬^XŸä®ŽÈ‚ü®•u u þaÔ)W×)IдI»¯ŽØŒÇYr1·˜¸ú%Æ?ìacÆ%>b³7öòÑ¢•‹ñädä¸q‰ŸØ¬mÚ9aÓ&Ú4Ïž¹°yÚ4ßgñã‹IgŽeÌ\˜12{’MKœ|¢ §fG ;P^–Óêlš~‚ÎXælÇ2•˜çOœÎGíy—Q¾üùzÆ4Íz]³ˆÙÈHê4ØÏ<°Ke:2Ÿ¯·‹p wóûèAëN›>Wnò$Èj6¶7×zuM…ñ¤«ÆaK1¦êu(ž®o7¾±µ6[X«²5Ÿª-ø$mÅ¿d«þ[l'Q/Ä^?`,s+c[ÓÙʘÈÖÖáleË–ü¯¶ì¿´µ=s¶²çÌÖ|]¶â+²¥½¶»AÏf—©ŠÆìu©L»,ÁíõgýÁïh÷%LëgTâöWTõx¼ù?wûæ{76‰}ý¾šU^•ÛÇÐìU¨Ý^†&~¥ÚÏí ×óÜéþ…:=Ù¾P'bzl'†Õ½‹©n»Ÿöms84ü;z«C-¦°¶´ÍáX×áäÉ®ÃqÇu8Y‰ëpw9ôîöÞ;Ná>zжáàʲŸËË¿&Ñð”ß䩨{T#‚÷I‹e;Ƕ~ŽíŽÇå™*W&b긳/uv[Wb“\]3¬Å4ïü.ÄïüºfßRØêN{ÁÛÑÆnçî}‘Û¿\9;¨:¶7 Óô–QZOý{;ž%Â~v\–Ƕzó)ëM_ßĵnÒZº÷˜´íÜ3y;ßRf½ÍͬÕÚŒ‡vV¯^ŸëݨÍn×Z¶£úwºo·mHúƒÜqÛïì},ûßëû¸#³kwV·ZOâ{B0,Ž3gG¯as‘]>Ót¡ßë‘E|Y“ËËåe­å*޶YΨ7©7©7©7§Ô›”¯ùÊ~a[·^¿ÒÖZgˆwíß©Fçйçêœç¾E¦¼Õ¡ç¾DçÏ=†Ï»Jå=Š/9_šÝÿ³—´¶–DfOZÆÚçÕóRîû5.FIÇ _ š,¸ÂùÎJ¸5• -®kñ²9/f‰ïõÁ<Ó,[êêG2A?R±ôÉ/÷.€”¿P×d±(OÂeðcàÿ±ŽóFFÙDŸ’¾¥K‰pòrOlæ¶å™ß—ÿÝ8¦Så>ÅÏΖ·´¾7ÞoÓÜ;Í×Ô÷uñžm¤xü– ·¦û–ò _O«kâ\ùsö8=£è@¨ÿ/Ê?ºƒüáï)Ú/M^FøzÒö›Øö‹`C®•á¬ÇrîPGÍ©îYÕ¸™ômg쌱383vÆØcgŒ1vÆØcgŒ1v¶õ¶_*Úz~‹¹Õa cÆ…`C†³ª·Ëö–U0E/aC†ÛóïO[†Ûñç1õŒU§cÑÉtÝyÊ8ó9Ëqcç®æíÇ•<ËîÄØS*îÑN&iПfžXþvc]çÒ'ÓÇàF­GÏ[Ç&ÌÌ&\;ïÇøj˜KÍBHÊ+íjß}E`îí¶íÛ}?Gÿqz Š_;7yWQ^ÞÂ{½¦ C†0„! aC2NÊ8)ã¤ô»']Õ8iÌ8é-ã¤Ý1Kû·×¡dŒib[ïd[Y#ÌaXÃÖ°†5¬a kXÃÖ°†5¬a ëMfʼ“>ô=‡5¬a kXÃÖ°†5¬a kXÃÖ°†5¬a kXÃÖ°†5¬a kXÃÖO>cϯwæ|=Î׃3çëq¾çëq¾çëq¾çëq¾çëqNçë±ÿ 7Êq5ÆÕ` kXÃÖ°†5¬a kXÃÖ°†5¬a kXÃÖ°†5¬a kXÃÖ°†5¬a kXÃÖ°†5¬a kXÃÖÜ× vkwRö6`o8³·{°·{°·{°·{°·m?ö6`]> aCÂpaõv)ÆÃ2˜¢—0„! aíŽù÷Ž'û1h?þü!Æ £ž±êt`,:™®;Og!g9nlãÜÕ¼ý¸’gy"Ã{JÅ=ÚÉ$ úÓÌËßn¬ë\òdúÜè±õèùcëØ„™Ù„kçý_ s©YIy¥]í»¯̽ݶ}»áãàçè?\y>†±Ù$¿r×Ï æ¡‰½Æ^Ó¦/C†0„! aCÆI'eœ”~7㤛''eœ”qÒ-ÛëY¯…M&¬ñxöÚã¤m?YëÍZoXÃÖ°†5¬a kXÃÖ°†5¬a kXÃÖ°†5¬a ë«ö…U3eÞIzŽžÃÖ°†5¬a kXÃÖ°†5¬a kXÃÖ°†5¬a kXÃÖ°†5¬a ë'Ÿ#¬ÜÚÿ˜s„9GΜ#Ì9œ#Ì9œ#Ì9œ#Ì9œ#LÛs„Ùg†0„! aC†0„! aC2×Ĺ,ìóϹ,Øίæ\ÎeÁ^s~5ssô5`ÃÇ2Äï¿wXÃÖ°†5¬a kXÃÖ°†5¬a kXÃÖ°†5¬a kXÃÖ°†5¬a kXÃÖ°^$kö?›oÝÞ› ýF¿'³.f¬ßú~ÃÖ°†5¬a kXÃÖ°†5¬a kXÃÖ°†5¬g{žß?óï8ÿΜÇùwœÇùwœÇùwœÇùwœÇ›ì±Éþ0dMÆÕÄ5¬a kXÃÖ°†5¬a kXÃÖ°†5¬a kXÃÖ°†5¬a kXÃÖ°†5¬a köØdMô{I¬ýZ£WñkÎ1dö3Êÿ«yv‰kZ—¸>tÉkYxýz@¹&ËÞ¿«9Ê28Ê/þŠ5aC:>›2ͬL“že¥‡zƒzƒzc{õÆ„õ™ØMÒCzPþJQ×fð"=¤çbzúÚ#¾ ãÛ/¢ õ´uÒ¶L2=·´S&§)ùxPÛÞ¶…\HÊ+åÔw_ÒïõÀÚÒÑ_ñ}„'§chÓ$ù•mtßV̓ݞ­ü±¤‡ôÚ ´ÎÉ)žO;¡[gÛϸ=Æ–Q§3¦Ë˜.cºŒé2¦Kz¨W¨W¨W¨W¨W°ã¤‡¹Bx‘Òà s…Ìb7Hé!=¤‡vÂêæ i'0ÿÈ81ãÄŒ3NLû†ôÒCzHé¡?̸9ýáÙõ‡i“Æ[oa¼…ñêÒs1=_¿½¿½ýò×Ïÿûùã÷?ß¿üú%É’2±—ÿ>ü¨~Gïoÿ}{û×\úã{ucuñëoÿL2…Àd úï/‡H]¸'÷%¾§#Ÿ¢ü¾ï3!¹C£OÂf‚~A˜K:áÙ!ªÛ'‡%¥™º’ú{¦!Îïõ®º“b»®’—©è¸Ôý‘¡Õ›ÍâõFï¾3?vÓûfà(=³¹{g#ûîîC›åÏzãýX,ºõ°ƒ‘ù4ÿ5<ãl?žÃ¬™fë(ÝÀÊ34è Ò5iXÃAÊMôŒaïï}&{‘>Ë^¦ö°‡=ìa{ØÃö°‡=ìaû±ã Ï>QŽ#hXÃÖØoì7zkXÃÖ°†5¬a kXÃÖ°†5¬a kXÃÖ°†5¬a kXÃÖ°†5¬a kXÃÖ°†5¬a kXÃÖ°†5¬a kXÃÖ°†5¬a kXÃÖ°†5¬a kXÃÖ°†5¬'±Ž^È:ZÏ^[£XÇ.ó8WalÜM|âPÃ&¿ºþ?“¦sçc Ýó~}æýòàCo/ü÷¾kä›|“oòM¾É7ùžo¾íäîS^[c¾»y|Ô5ØÁv°ƒì`;ؽŽÝ˜6þÔkô…È7ù&ßä›|“oòM¾É7ù~B¾M›>GÞä›|û|ú¯¤ýþ5>óì3ÒGıõ8Îͱ.e¬t’NÒI:מÎGÍP'qÇKã˜0~‚<ÆÆñõÛûÛÛ/ýüñ¿Ÿ?~ÿóðý˯_Ê4ìÕ¿?ªŸÑûÛßÞßßþ5—þø^ÝW]üúÛ?‡ªO™ô˜>zõýX¥#2k\öçƒq‘qO>þ}C¡HnÇIº>—ŠòyÁŒåÌ)ìvÔº¡*ßÇc0Yy¨ê¡bùm³Ôãíbr| ½ÕÓó<-Ôí&Ûrͦ4ÏD[ªn#¿Ù¹µÂy˜Ó»ž7Ië¶p’YÝY‹ÜGššõÚiÓHD¾oY¯þĵӳfšMXÿ_Ö2xêYgŸ†•ì0z?ï3Ù‹ôYö2½°‡ý-ì7ž3–Æn±çÚ¥Ç^Sµ·÷8¯ —ÇÄñ•÷¥Ó™¥y=f“V2ÓÕ=ºtŸy¯§ÍÏÑåQ»ÿ}>„¿„×-Ënïô1÷6ÏÆs|<}Î×{6Þ²M: áâX7º‡öÅbô1šéþ7Ñ ÷¿5¬a kXÃÖ°†5¬a kXÃÖ°†5¬a kXÃÖ°†5¬a kXÃÖ°†5¬a kXÃÖóY“1Ö—7vëWÒû®ÍÐû:í±\ï\ºxžpîÝIÚöu|6mfí«á—…µ+r]·ô©Í¼¯î.¤Ù0Ÿ¼›àÑ:1þfmù‡û~lë³v¾Ê¹þÆ5éqÙ/xÃÞð†7¼á oxÃÞð†7¼á oxÃÞð†7¼á oxÃÞð~*o?o­Üžtæ{á®kdîÃÞð†7¼á ox¯—÷˜=MS™ŽÌÅW¸ôá¬Úæ·Üÿ4ésþv/RÇüåz‘:¹úÏ ×ÔÈç"¤íý°Gõ[”è¯A7ÍYÆá^l‡úñgÍViÇÏí_[8ÞyuŠë8»é‰\{¯%—üT.‡¢_.¦L\ÃvWÖþ!’CßÎÖ”ÿ(Ÿ®'Æáj=I.ËT]‘—BÝ· žÕ“ePГÁòûÑo‹çØ_£]ÍÖü×ËA ;÷lZÛ^kRéM’\ØãÚÎù¡4û7»ÝcÞËYõÈ*á¬ÌЉ2‹œl2ÑßSb¼»ãA=òÁvM°]‰+Y6lf6,ròñý¾D”¢=ßÓkÃ:2Ff¶L‡s3bË ÷éçî\½Ôز3r¦M´iy'MØ´ùØ49Ž›}ðóØm™-²z-“ígo¯’ÎXæDÙÍÚ¾ ]¸hÛô“ϼÊÚcÒM:×rîÕ\꟞Xœ-F2Ͼ¾v)oÏܵO#³µ“eŸß·‰cÑN>#'lÚÄqKÏž¾ÿ|ûþÝþcÒÇ/…Œ‘ÙLûþgä„M›hÓJÚi³¶i^×µðY—cšz„M+i§-¦•´Óî6GãùbÓÖ5ž)d‹¬^8žé}ÄXµ²c<“ñÌÙgêàC²¢:¥#[dµ 9²3²£N¡N9©SÒ Ü/Õ)`ßIc\ˆtP¯?¦¯SŸ/r~2Ñ߈‘Õ,æ%³ž½6zäƒíš8v/| ±a+»2Ff3»6í ¿1Æî—5ÎRްeÙ"«ÝŸ‘ã,Œ³,¢NÑ‚1uÊ|ê%äwÖç#úùù"¯™¶‘ÏÈ {6±ßーê>Œÿñìûû1¾Ç7Ù±RìO[?…+rza_îq_/7ú÷ôïá›—? ê’uùæ9Ù"«úæõÈŽ:…:eöuJØlë”$èK³¹öfÌÞ>b‹Ø»¢ÀWìncÆøŠ­×ßµ#cd6S›vFNØ´+öãQÌ…­v/ÞŽŒ‘Ù“lZâä95ã8êôL©srZMÓ_äÓ…M›áX¦ÔõDœ}%|+ǬKò2^ƒÌÌ™AZÕ2Ò‡: ö3ìR™ŽÌÅçëmŸ-~ë2bu?ésåÂ|zYÍÆöÆbSŸ®©0žtÕ8l)Ö;{?¹xº¾ êUÚoKó$œ·³¥3LÖ~È–Ö€®} åÖüâ¶àW¶¡­úØlqLr+cz[\›¼•µ½[Ü{+ûHoqLu+c’[Üëy+{%oñ¼•­œW²µõ«[Yÿ¹¥½.¶¼WÄ–üÓ·ìß½¥=‚·¼ÇîÖ|ƒ·â[»Å¹wæ„Wn»{ÎÑÝò«ßê9~•ý©gäsÆçþl÷Üœ¯ßÞßÞ~ùëçÿýüñûŸ‡ï_~ýR”Qj¯þ}øQýŒÞßþûöþþö¯¹ôÇ÷ê¾êâ×êYó¼é{'ã¾ê½þ÷Ä÷>*¾Cîý.ÝsýŠü]ŒoÌ÷èB|Wò¾§ï‘ÏIï8''ÿûUéD·­ ŸÚ&[SëLr¢a§kû;äwÙøškŸ^ïk9Öÿ·W=wNHǾëß9äó;kÿ}u‡uúWøì?Ú'¿‰O´eš|ê:øÿ¬tr…Ÿ}Ïûõ™÷Ë6¡åîÚDM'AoЛõëMÓf£?wXÿج›8:Ý9º{u|^·Ì~#&Í^#Çú¹Fßô úvÇ4ÈyÓx'ÊÙ.0ÃŽ`GÎÚ‘DØ>£7úöãû ɽ…Öræe”º½Aoh#>¤8fío>2}ù5é“ïOú`ËM‹õÑ©‹K‡:ÓËÂËÒrÝ‹zޱìmQÚ¢÷Þo$š)óNúè`7ßžKÆï ôÔ²š´Ïˆ§ŒRFïÒ†¡ŒRFi/b·j‹N›%¡OG¥ŒÒ¯@oV¡7ãuØvúè5mêÚ,Ìm3·½Â^1·M›‚2ÊX%e”2J¥ÝÞ`Û±í”QÊè}$Þ 7+×l;㮌»2îJÝ@½Ao¨ç¨ç¨ç°WÌ/Òbí,c‹ØGÚsøÏRFY;K¥ŒÒ^Äâ³@¥ŒRFéW 7¬Å¶³v–~zM›…º‡¹mæ¶™ÛÆ^1·M›‚6e”±JÊ(e”2ÊX%zƒmǶSFY9G½‰f:ÆMãd<0¶âåhŸ}ÁÙ°jOÌò½gÆà°ôÞë½Ú5³ä-Xn÷JïÒäöÈôž©ógù^ì$v;‰œüúíýíí—¿~þøßÏ¿ÿyøþå×/EÇöê߇ÕÏèýí¿oïïoÿšK|¯î«.~ýíŸÂ¤« »ª=}3ùWõûìmîu™ˆ¡~R'¢Ç´=ChùËžJ_Ot°1²‡—dgz2ÞZ7®¡áª<Ô²=š´> Gi¼ì{³•Ž0˜ò”žIªónßSºçU]–¤·ƒµLÊ1TíѱFÇà•‘mîöY¸f'">áI1ÈäBÞz-`\J@eĬŸ«‡²þô×Sc Ó:èν}ÿfá?U½ÿPa‰’ú}ETʸ ó¼Qå"Ä«¢ðNÒc}Ÿ¹ÇªT•ýã±þ9{oT_3iQFÜsEYWRÆǨ´ŠzŒ}:[kŸ¤³øqF•øì>>bÏ>‰;­‹@§Ë¯´²ý”y‹ã-—·deà9yüzDÒ‰iè¶ë­¬rמsßå5ónSË·îëŒãjïæÛe–&.ÿÞÜYÌ\”¡ò‚¼’`Ý›w˲8¡z_½Ì×Ä»ºæÉ“qò˜ªCצ-Þ·÷Nó5eê¢ßËeæU»¯rSúoi¡$/n¡ôpèm¥è,ÑJ1­å¢1QúÖAáDa«úÿÏÏðæºu6$í÷4ÏuwÞcZ…Ã|P.Üž¡÷LIϹ÷´ÒóÙ~ù¯/=ÏJÏ“ø¬I^ûòuòê¾çÕúóšôœöâ¢8é"œu9Ø93œ_ÑìݰZ±Á¦2¡ù¯Ü€ØÂÜQZ1Y¸æ—eXÇ®ŠöϦp[¹!£†p3×jýÎÒÐÜjþWî»{Þ6âžçýû#q-AßÑ÷ë{WŸ»ú:AßmZ²P^’¨ý¼-?*|¢ûèþªm=ºîoÍî£ûèþ’í~|F÷Ýs“ì¾B÷Ñ}ì>ºîÓÞG÷Ñ}ÆyÐ}t»î£û´÷Ñ}t¶ã<Ú¹y2΃îc÷±ûèþ²íþ%Ýg|ÝÇîc÷Ñ}ì>vÝÇî¶û·lI·fßî­Ìïle,+åy+¾XÈ“ò‰½Ež”Ïç̉ Ïuõy‘'ò\ž<û¶úˆ’l–Œþ3ŠëOeÖçkw­gM¾Šž·$_‰}èØ{‘½§î½˜Ôé0§?š÷¯îT˜SAîv*ˆÛñiu§ô<û½ì@úlÆí:ÕWM§ùˆZúàvÖ2ûkš½?uöñ´ûqºý:ͦrç‚q¦æ{ï¹ Äwƒò$žÏ‰! Áä}òó+…Ê‘÷@ ðŒ `°åàLjš3zf£âøÉóQÍÍKywëlä¸s4¹‰Y3râEya¾çcùYg<¿îögÆô3Ϫ&,xö<ŠHŸŸK3Ÿ{ØÃö°ïaïN'Í?¹…vé±×”;åÉ¢fÿ÷'EùÓÐä}étfæÐE3?mN3óØÆíÍ~æbž{DœÍ\ú!œzÕä-==ѲÛ;}§¹5ÏÊµŽ§Ïùöž·¬C“ŽBøŽ8ÖÒ/Oèç5uùb²LŸŸî¸³X¨àڑє߰…-la [ØÂ¶°…-la [ØÂ¶°…-la [ØÂ¶°…-la [ØÂ¶°…-la [ØÂ¶°…-l¯c‹ß8~ãØØÂ¶°…-la [ØÂ¶°…-la [ØÂ¶°…-la [ØÂ¶°…-la [ØÂ¶°…-la [ØÂ¿qüÆñÇ&À¶°…-la [ØÂ¶°…-la [ØÂ¶°…-la [ØÂ¶°…-la [ØÂ¶°…-la [ØÂ¶øã7Žß86¶°…-la [ØÂ¶°…-la [ØÂ¶°…-la [ØÂ¶°…-la [ØÂ¶°…-la [ØÂ¶°Åo¿qô›[ØÂ¶°…-la [ØÂ¶°…-la [ØÂ¶°…-la [ØÂ¶°…-la [ØÂ¶°…-la [Øâ7Žß8z‹M€-la [ØÂ¶°…-la [ØÂ¶°…-la [ØÂ¶°…-la [ØÂ¶°…-la [ØÂ¶°…-l7æ7^¥7)Ÿï7ne»|h—ÖÄ}î‚OyšÔù4ïË]žÌsŸ¹¾ÁÏüŽièÕ›׸Æ5®qk\ã×{Í´ñÒ ¿Ëé¿Mût°Í<оõýû,mX®qk\ã׸Æ5®½  « צL\›òàÚ²ûº}Ù´-BVwf>—CúHé#}¤ô‘>ÒGúHé#}¤ô‘>ÒGúHé#}¤ô‘>ÒGúHé#}¤ô‘>ÒGúHé#}¤ô‘>ÒGúHé#}¤ô‘>ÒGúHé#}¤ô‘>ÒGúHé#}¤ô‘>ÒGúHé#}¤ô‘>ÒGúHé#}¤ô‘>ÒGúHé#}¤ô‘>ÒGúHé#}¤ô‘>ÒGúHé#}¤ô‘>ÒGúHé#}¤ô‘>ÒGúHé#}¤ô‘>ÒGúHßøô}ýöþööË_?üïçßÿ<|ÿòë—¸ˆtn/ÿ}øQýŽÞßþûöþþö¯¹ôÇ÷êÆêâ×ßþ9ì~ûç#ÿퟢúŒª «ÄdŸ¿ý£ªÏ(ª§ûú{+$ýáx !÷ßËÓ÷&‡ðÛÄë:~UŒTO|Qÿ‡¸ÿ^²+Ãá†gÏ„s<äo/‹›x¸8—òÎq¡Vt«¾NÏU å¯uZ›Âgô¯*hÙ®.x¦™Âj¯»ßöS€#Qh?ëÿÒê]=›f¡ C<“9cå¢@ç®Àš{«Ï¼Æ"q×µÑè‹Ót"g(ì}#Ó ß7ô.t&˜³Ö;:é5y±yŠ‚±3ùµ†É߯Úï³òpq4œ²šÕPÚÆæ×ü×p¬³ýtSãnUž3öÍs…û»çw!†ãØ452ë‰3gS}¸ï•|Gõ¬Þ;}õ•WY¿·yÎË»ú?),}åäekdRÂù&Ϊ¾ßÚó¾"”3_~¬­Y0ç4¯K†­>Ôi°ŸyàÊtd.¾Â•yŸ-~]cK‡tÚô9;‘'û+ôÁEIØžnþd¦ÉKŸ}(‘12Æ–cËoïägÒŸ_›þγ¾_àx­÷aLÉê½×ˆß]Ïö¢\P¿mÞö1¾q¡_”ˆ± ?näû´E¸ÆøÆ²Æ7l¿¶ol#›¨¾¿œ;½(Ð…Mê‚—Qb¼«ß5z±y½ð:!ç&G/°èÅFôâás&Z|?7gîWÏ™teU¸ö`ú Ì—0ÆÆãIŒ¥#cdŒ-g¾„ùló%³ï[ë'©+ÆÉûæPü8˜7§_D]J]J½A›#cl9ý"úEØ>úEô‹èQ—R—RoÐfFÆÈ[N¿ˆ~¶~>š/Y÷1²ß¶4Ýн¨|YmlB^ÇeÿÓno#÷Ù*ßY±çšùþ«o"öBÒÁ§ÓÛ—<ö×ÿöö¥·,îÜy;Òg¶wS:]$ZìIìI§Ül»¤’[wO°}•–݇ë—WlÊ¢g°l¾[„eÜlB3.ÑËÞ,ë %ùüÊ¢øða¹i¸zû¢ÄmŸã>íÞ”±Øz(ê\ó¦ç ¾_³ °¿åÕÝMmäªha:åpœ}vWW#MUlØ}tÊûôæÔ>Wˆ!ÇTè¯ü}téß9}ˆÂVQƒv¤§º4ÓIœŒ°Ò…Áî‚i^ æ>}æÿCávmv‰<µäq:OKבMYrh£¬yy»%×.Í`ü>X û·a,ïK§³Ópg–„Rë-K“·ôTg-³½è0—gcÑ)>ž>çk¼ÌYž¬é(„EQíÆ¡¹ÖסžµÂúÝ¢ƒèÜíMü’r|wÖÌï0è0§Á„‡°Vu  )뻞²;ÐâH”hëд$ŠÐòhê6î9gk¼Ìze^ˆ °g[ :OG´ÌãÍ^ânñÈím÷ÒÚt zÄ=>˜¾àÅ{òñï Erû;NÒõ¹`Ôp‰a·{|ƒûÚ„m„ª+V¥Úùù…ÓQ‘´œéwÎà Œ+»IOäž5S±gȘn~?¹ëÙÙÓ¤Iúû*tIGMÚ¨'³é‹‹Nza{Ø/}1ÁÞ&Š5´+í½L/ûëÞ…5¬a kXÃÖ°†5¬a kXÃÖ°†5¬a kXÃÖ°†5¬a kXÃÖ°†5¬a kXÃÖ°†5¬a kXÃÖ°†5¬a kXÃÖ°†5¬a kXÃÖ°†5¬a kXÃÖ°†5¬a kXÃÖ°†5¬a kXÃÖ°†5¬a kXÃÖ°†5¬a kXÃÖ°†5¬a kXÃÖ°†5¬a kXÃÖ°†5¬a kXÃÖ°†5¬a kXÃÖ°†5¬a kXÃÖ°†5¬a kXÃÖ°†5¬a kXÃÖ°†5¬a kXÃÖ°†õšôE_¿½¿½ýò×Ïÿûùã÷?ß¿üúE©¼ˆìå¿?ªßñûÛßÞßßþ5—þø^ÝX]üZ=\%@™U ÞWÀŽºþì†ã±Q•˜¨h‡¢zG´¯a_íÓPŠtÕÑéDèíNè›/º]6ûä)õÃpJ>jcõÏêry¦üvÊM7¾!ýXM9Ï(ã'rèy¿>óþĽÓþ×ñù$ºsÍ}¿x-k‡µq2¶Ì×-þ{ïµ¢Öч—;“̽¯ öýĦkÇT‡üú²f¯ë`'µí¶å9¶N‡{2gm~´ˆÿdÕ[þ‹À<ó¶,ùë­#’T¨!Šêu‰±üU4Ee~ µ 6ÚnP×û‚©Öª$fUø4ÙÉëZæ`j—¤Ž×Õ÷F»Mº U¢ù®âóÏÉ{£4\Ò¼V)ÖrY1ÏZ.s¥gcµ\’Ô-ÕQµ]Vß¿´Úδ<Ó¨¶P‰­Ñƒqìj ÿlúkC:ÆÖ Öú«úý­ß¦7’»ßæ{Z˜^`*XuÙ5l}-Öù Û lϰëý]Ö: ×;sEga»$¶I§^‹a;šm&Ø®íwpíÀÒ}§]0®]´ùùž·ÿ„á†þ½gÊvóÝ´ÁôӲíËTŒXú{a:ÂvÁ66ý€Nù‡ãÈþ©ÿ(ÿpY—_(×Ôå7ðù¶°=aÚ·nf¤ý˜mf¥ûfDšq×̽ãP{Qh“–"Œášt7c§..Ÿß1cØ}œÎæçîÉÛùK„Gÿìzë1“ëÆfÅõ4„ûõS‘0¯¿'n%?†é ë…²¯yØßE˜Š°÷ÄNWý=EÏ”…ËWêäz(N§.’ìdêâìäáÎáί˜ŽHœ9ßÒtÄØiˆ$45é~ Ó[yÈ R9œ¹ ÐÑfgïôÃݲ~G3á)òÛšJ㌓…‰X[ŒœÉ]ŸxÓžü5ÅžEØÈ¦cæ¦4šæcÒžä·×rç8 ïÓmg´!N­®}|ꘆ¬Ÿ$ëÜ MebÊÊ}?‘kì>U¸ÏÛQ²öS9Èú5²ör•²NÃP¥¼–ùá^'ŸŸ²%ë¤-ëÁz"í¯Ã¤[ÁÒmÚÒËé²u¯§‰œ¥²… ´³ŽŠ‚¹1­Û“ߪþýùyúÿA¹P½§ìyO¼¯C*¾…ès\ð-õ&œ¹WEãÞiž>׎ndöêwd F´Ð™§Ç”—c´,†*[¸VìÒˆæk‰v‚@˜};`cùÆŽÌ3œôe`2¿¶EJÚ°cÂ6‚J/Õõ†xì½=KÕ=o.Ü/SS6’Xéœø¤%z ]ÞrEÉfâ•=Æå9K‚룹޸<}œù_µÿ·sñ¸çí\… Ÿ«Ö}½Lßôß}w94ŸbNN^ë»oê5W ³ ›rÄ•µ'×üösØAÖ}÷#‹ÛdÑ”¥[å%߇9}dô¤òråodƒl ²™ƒll}ï+d3·r“d¡Ïxíod3b‰óXß"›Y÷ó³ñÛO-®_¿«õDÄ–f©K—[fmå1l@7I;ï×ÞßÚ@0ël 1â:…N¡SèÔ,uÊÍæílÞNù¥N@§Ð)t B§Ð)têåþq{[jü3¯d,Ù ›ß ÙPn ²A6ÈÙ dƒl ²A6ÈÙ dsOÙø=7‘e‡y=æõ˜+F§Ð)t ÂÿÿúKÈÙ dƒl ²A6ÈÙ dƒl ²A6ÈÙ dsµl42¡¼0—Ç\óÃè:…N¡Sè:… ~,ô#èã!dƒl ²A6ÈÙo¸†¬.Èê ®I|9 ÉnpÞ"íŸ_ñy3Ͼb~iµqú aKœÄIœÄùÌ5°%Nâçú7æ¯o»ªö!½ÿ«öÿ6ßñ¸çM:Ì=þsùìkÃ{[vï>AT³zF¾­L¢ ›«~û=dÄÞ1}÷Ï!?Loͳ|§c°8¹]ù›ü‘¿GäÏÚÿ[­S~÷“]û˜3ù#OÏŸB~äü‘?òGþÈù#÷Ê_–‰}†‘!ù#äü‘?òGþÈù#äoëùÓÈü‘?òGþÈ߆ó÷À3Éù#äü]wVò#äü‘?òGþÈù#äü‘¿çïû m{Ïž¯ßÞßÞ~ùëçÿýüñûŸ‡ï_~ý¢Ò\—öò߇Õïèýí¿oïïoÿšK|¯n¬.~ýíŸC•Y]e0r¡ØUŸ;·P¼ÌÿÊÜ—† óáûý3æ37ΗUøÌÄÕó‡¢úLBü:j?ŸîÚñÓÞ[}õ;•î1M]â˼ºqß»¥0QŽGY×ã¾î´å[‹|a¯8˜l”Iî˜Ä09±¯Ym_Mc›,ûØTåêü¢.+›± ‘¨C° ØK˜ÀäZ&®mºú¼Æ¡_ÜŒç ÿšGúÍôID[3GOš²¹±V5NOàÆȹúý Mrvœ,ÇþnºŸ›„úf:ïÛ)vº&Œûލ ¢ØÆ²3æIßcœž(êÐÕ×¹»aœ¢f’L«/n:Ÿìc¯òExUgÑñ^Ñ~yÖÜð#ú¥¯šãD7¯˜szռΫæN^5?1ç9€EÖ¡WŒe/¶Ì^9&;Ûú¥3¶øˆ¾õÂ2ÚJ}ã)s³xĸÀ¬õbÿö5}Èžõ2‰*òËe"#³d¥JGQõ9£½»æ¯+yýtY‰ŠO–•èD,©Ø‰%wÝýÎÏtù‡º»£— ¨€o3KPÔÈ¥'Åò–ØøÜR¶Læs`™Û䡟ž÷ZFçŽõLjúc&­,á{"¸ûksc¡;åÓëL£›î¨ÕØå3SÁ$µô,sïpúWÏh“ž"èk–ѦQ9¾ÜžÍ÷ÎÝ“·óeøÇeÍ¢ßtF‘°œñ€å,òÚjšEyf!^ìV/Ö‹O-eñ¼õwÅö ŸoS]{·$x·/LZÒNaj­ö®0MB¹3"KÇ© Œ'Ñ éjÚGÒ#Ïuö†ôäóè>½áÙ;^Þ@¸þ¸4àVw’N›/ FâR¾ùåu¥aòeŒ©É«ýÌC|Ö8¥®lê:¹X+ëóÛèUžóùh [Ú–¾løv¡—u·Â ±séñ¶`RþÇÚ§h¼}’é‹÷—íÕMs)«Ôßž N«hD× Pu³ßdY¹•ç>˜$æZ•¤cuÏqçV£WÑÍ*x×e0×ü}f¥º¹w¯Å³Çág’2¯» &äx£ú~[áºÕðÇž÷ŒM¿]eßyVÞ×M¿QÇèsz°iü\^0ÅæUÁÍkŸÝ퟾(šV.¯[Â,å%‚ÚŸÿߘà[Þÿ¨`íN´ îóžý!ØüÅäýÚOÔã݃咣¯Sòz”úê??¦¿+ÓÐõi›ŽÙ¸gL×o¿s]@ÂiÐËσLì¹~{DÛE•) ñ”pد³íR¨×Öçkj»ŠžöJ<¾­SÎV”“¹/Û^žf½r™Ä½¶³ÇU#)g쬑´}¯65w9eßPÇêÖùËÔÏ:Ût:]°s@Žwêæ_¬«ËOŸNÞ_5ßyÇ44ó!¹ã³kûI7¿cç{–^qM çŽ]H›ãSn^H>ïö4>ygÒs-îÄ;_טÓï¾;]Øþ¸Sæèï¤çrž^úæ5ö&þÏžÇßûú’ùzÁcvóšZì·9Y%an³Y;æ}3ýÈs¡C#³™ [rVvŲ“²Ê„­+ -/Ë ›6Ѧå4aÓæcÓb!Ÿ\´{ݹ€m™íd5Æ_*•éÈ\|¾¾.ÚëìïcXg%ý©zý¨^¥SJ8¨ŸÓ-}¥ÍM…·Áe𠺤O³·¹é›{ÎÙ]ä«é[tòؤ÷Sô‰E¹ì¦¿›>#»³þ¸åÂúªSˆµMO•A'V2-k…Ø;aL8{ÖÈ#dP´íW+½°‡ýµì£‘Ü£'3íÀ³þͰ†5¬a kXÃÖ°¾kÙ÷~óîøzŽžÃÖ°†5¬a kXÃÖ°†5¬a kXÃÖ°†5¬a kXÃÖ°†5¬a kXÃÖ°†5¬a kXÃÖ°†5¬a kXÃÖ°†5¬a kXÃÖ°†5¬a kXÃÖ°†5¬ï»q,Ϋ¹ÇÞD·îÿî÷€vûí¿zÿ÷gîõ~Ëþî2øý·§\›´G·8³¢+¯³ü“pFNóny†’×ÇhüùI¹;W'ógéìêýßÍõ1ü§êÆ­iŒ÷ãÏ|ºwÚ¯)3õ÷^¶HOØXŸßx4·GÔº³o±ž¶o12GæÈ™¯JæwhÓ¿TÖW¶ý‘12^•Œ§œñ9W9‹< kÊ32FÆÈ/ZÆ©+]ªœE5å#cdŒŒ‘12FÆÈx2ŽÏûFÌFÎñ´³5å#cdŒŒ‘12FÆÈ#cdŒŒ‘12FÆÈ#cdŒŒ‘12FÆÈ#ãÕÈXÕkŠm¼~¶_—½ 242³ëÚݧ墂̭Ú`»¾Û­…nÖ›ëpÍãÑå[‹øAWzu­ëÈ=û8pˆ>¾~{{û察?þ÷óÇï¾ùõ‹ŠòHÙË~T¿ã÷·ÿ¾½¿¿ýk.ýñ½º±ºøõ·ôh_EX%DU‘!˜ˆ s-v×Ìbìê3ªtLó‡*!e%°¨Jà‡ yÿ³û*ñ…|V×ï;6ï¬3b“çò‘d"ou’u"ÊèN”-¯×º­ó]ë2ÎÝzxÃrôºñDøèŽX3>vøì÷—H„¯Á%Û¦êû¶·„O\¿ÿ½%LJ£Ú¶$/ØoÂÇëÞ~‰“Ïã{K¸õYö{Y—™$wº¡=uë·´>­—º2Ff=2‹Ëô‚슉²‹œ¬²°H³gLÙ–Ý99aÓ&Ø´Ä1—i¦ÍǦÅB>yÐ[6Ò ¶¬#[d5[–…ë×ÊnUöM?a/°r{¶Mwú&žy#㼎k¨ßÙÈ)sï8ý·º_¹g‰`ªÃz<¯/yX4{wóÞ¹ÿòvì>så™>h‘G#z …yEz‚>ì÷i]TEwP§½C¥Nz‡é™^Ÿ¥x¬s2¹Ç§Bï3=>,ß(ëQÞn=´K‡½¦œ–:‹nÿ÷£,^Ëå}étVi^™Ë”Rcìgj1qžÝ‘%Ñ—zËlïFìÄîyͳr4êxúœ·²6Þ²M:œÕ²ïUm‹`G§D vP.i¿ÞÈÑ’K\šøØLÑöß{-Hœ”‚DÄŒ;™±¤(÷½Pîõ¿ïñ®'¤ç…ûF¿K÷\Qþu?ß„ô,ZÎcåä£èã«ôÑþî¯OÕ|ìÓm6á^Ññ{tÇ®‰OtØš|ê:øÿlóäŠyuÏûõ™÷·6pVbCfßñMÐôfýz3icÁ; ¦§¾ uó¾;¶ º•º»' 1Y?ÔÝ­owLC£* ¾Ør¶ ̰#Ø‘³v$vD…ÏÅ较ýxÐFÑcT²’ƒC(£Ï/£E§®O(£”QÊ(íqôÛŽmß”mOÆ;Ÿ=•u"X«•°F¯Ÿï|Í”y'}´©÷7ß^¤.¢Œnil”2J¥½ˆ=d¬‚± Ê(e”~z³&½‰ðÀ¶Ó¯@¯i³P÷ÐfÁ—õ¶¥ùÈôåפO¾ßé”çÛ,œÞ‡u$ö]^fbã.Ý/CÌÖl`¯ð™¥MAe¬’2J¥ŒÒîGo°íØvl;eôæ>’FoЛ•ë ¶qWÆ]w¥n ¿ˆÞ 7ÔsÔsÔsØ+æé±v–±Eì#í9üg)£¬¥ŒRFi/bñY ŒRF)£ô+ÐÖÎbÛY;K¿½¦ÍBÝÃÜ6sÛÌmc¯˜Û¦MA›‚2ÊX%e”2Je¬½Á¶cÛ)£¬\²Þœ)£–_æÞQºgU]¦ä8„婜¾¨vüÍØÉ1Œ—dE{œÄ¾# ×ìïDÄ'Æ8yœÉWôñUœsÿûŸ‡ï_~ýR(UØ«~T?ã÷·ÿ¾½¿¿ýk.ýñ½º¯ºøõ·Õ{#ÃÂĹ«?Íï¢ú~¬Ò{¬âŒ ‡ýù GÜãC¤Æß{òìçƒB*¾?2ž ÁèÀ³Cr‡wìvO§ÑQåtwl(öÓî'® ™^G>Ÿu€Lo¡7\ÃòõâVB»½U·+msÑ5+3]ˆ¶f݆ԉhÓïD[üæe·¯¯$Û»¾¯bÚ¶cû IæÆ¨Š3ýâN¿¢çPßekó²‘ý¡hýèY3Í&Œ•µ ž:”uÆ‚|ֲߎï¿öt6‘>ïǶ_¶b‚ÞÆäÁ¾èè½LïÒÙGw°ß`.×TG°†5¬a kXÃÖ°†5¬a kXÃÖ°†5¬a kXÃÖ°†5¬a kXÃÖ°†5¬a kXÃÖ°†5¬a kXÃÖ°†5¬a kXÃÖ°†5¬a kXÃÖ°†5¬a kXÃÖ°†5¬a kXÃÖ°†5¬a kXÃÖ°†5¬a kXÃÖ°†5¬a kX?ø «93ï¤=GÏa kXÃÖ°†5¬a kXÃÖ°†5¬a kXÃÖ°†5¬a kXÃÖ°†5¬úJR¥'IG2ë{íýCìGæ×ügy™üìë´Ç‘Y•îÄ\Û‹|–î}îÙ\>ãdí§óœœv#ëBÄé˜Ùï.Mºz&•ù0ßcwwá¾Ç.Ž]È‹‘ÕØt7z1!]6¿Ó“|´Ë^ïç±]VŒl-/íòïdØzW‰\ rA.ȹ ä‚\ rA.ȹ ä‚\ rA.ȹ —%ËŦM·çÛÕÌd»uñn®/)ÝùT\‘'òDžÈy"Oä‰<‘'òDžÈy"Oä‰<‘'òDžÈy"Oäù,y";æ#‘ r±é¯Ò§UÍ[ê4ØÏ¯·^w!m¶,('c%Ò‡üØüEôe5ëd³pÍþNDœe½vV–¹†8D_¿½¿½ýò×Ïÿûùã÷?ß¿üúE%±.ìå¿?ªßñûÛßÞßßþ5—þø^ÝX]üúÛ?E•ˆ¨zYT}îvuØëÚÛÜû2Eýäú5I>Ö*˜|ÔÉ]̲ñEll‘šýQ qݼU ôíËÀ›êç(Ô8qŸ»À:u&¬YÎ/T{PeÇ.û¾cšêÀÓÝ@Õẕ[šV_ò'B×g·s'¥0Ñ€±8T¯Kª$ÕgTÕHTõEõït_o…¤?!äþ{Ùÿ^ÿÛÇm‚ÊjÃu_tþ¿û„#YœXI+±¥ú´CnŒÑgMµl‚EMŠYU¿ÉF6Òªfõý÷Ü`ãÙd´Jö¤NhOœWw>Ü÷kj’ÚâÙAšÌÕª‰ûô²-á|ghSî8¡ŒÙßw@ìœÓy¿£>˜güa\IW?’ ú‘ŠÒX ˜ÆµnPþB]“Å¢<}8Ûå¿÷ý_8ÖQ`ÞÈH n’“òñ6ÓÛÏ´-¯¾‰Š¾Á#ÏÁå÷ :ß Àܱl#©ÛÓ}KOE[Úê[òqÎ&§gô¸Ïæ£yÍh“@dŒŒ±åØòkúù™ôçצ¿ó¬ïXÆ^Žû0–dõÞ뀘¸ð×ýÄBžP¿aûßx¦s —ÅPwÆã‡oò:®¡‰áÆ–daÇ3kUü{“Ó´Dزf¢x~7“Ò}åÞÍòØ4Š<Ø £Ës“Äi®ÆÎWï)íp¨®í?Ýÿ6œN‰(õ¼‰cåš5LWq%“ÇC¬'æfwßëÍ4EÒ¹¦œ\üóIÍØ$ë{µ;6ßç8I½<†}“áeŽí‰‹³fšMXbRº´gq×ñÞiÒ°†£îä܈És³’a/ÒgÙ_BäHGŽt„5¬a kXÃÖ°†5¬a kXÃÖ°†5¬a kXÃÖ°†õkçÓcæÒÑ]ì¬a kXÃÖ°†õdŸÔ|$ó8™qÏö´v»e´v"”ºðäÝ6íNú…«ý‡­üŸº³œ8n¾+#˜?n·xÃÞð†7¼á oxÃÞð†7¼á oxÃÞð†7¼á ïo¿…l1ñ$° öè:¼á oxÃ{ë¼sræ£ô"'(º-Ñrmè¤Îb¢¯Žß6=uÏâÄÇ §;¢Ãèðɵ$øý=M‡•{¯ÿ,D&wqeè5z½0ÛŒ^£×è5z^ßM‡-£¬þm?ªÓ³^+’Ît]NÊ(Ö@ÁÖ°žýÚ¾ØÕ{Z¤‹µ}ó»îÈÞðž|lk§ÏÒ{ˆ<¥lï«Óäqç숳½ö¿8¼ÏÞŸµOUîæwðtä,WÅØÓ‘÷uÿÌœaåÏc3Á¤ÁœÁu¬â>šó×̱ªæ¼±¢>/ôðY?+ï‹võ½{-ž=?{¨â-óú¼¯òN¼Qý¬}ŸÖœ§ë÷{Þi½,/•LŽ7ç¥ç$ê(~îQÔÑ…s|8Žº¾?áHê¡ã”íøAÑ>²øä8dß›zÍë¨8٤͎3©Î±É±«âžw&=×âN±ÓèÄCd36œ“¹vˆaM ­öÒ@[¶¶mµM´Õ>Óq¶Ú6ƒrr¶û{|^žf½r™Ä½e¯goé“ÍUt"ºvÂëœUäFl}N=ƒN/S6bÑa¥MmÆ‹ h.9ÁeËs„;‰»³±ªÏ¯t¢z¸sèˆ4Ü2iæ'Ø3§“™˜Œ÷ÎÚI!&‡ü5ÝÞõâS"&Ö ±ð¤¼ «$8¡5ï•å1_sç°–y'µ]íè›'èËX}±÷¹ýd£ ͵´}­yfÊ„¤ß´ÍOBŠ Ø›tFOÛHëZ½¹VnI_¼_×Ü;Ýרâ‹úüìM`Ý„ÿÍéVµŽØøÊ°±àÉf‚ÎùÑ;AÊÉxŸ›·£ØT½h;ÛömJhe´ñïÏlH6‚Cÿ††Ñ˜ý m¿ÎŒa}º¢ëöâK][0‰CßÌîág÷?ìk>w»½­µîÔ„e\ñò–8ØøDÍ×äU×Áÿg­éVS÷¼_Ÿy˧5§ªŸ÷¥Ï»¡·jØäôZsAÞ©{¼w©¾eiÀ2òØc©âlŒ×QᬓÿŒÔ©JçÙ-M7¸/è”}ÆÕíK»t4gkìC³Õþß=EÞ—Ng5æÌ–1q6kwb­…ÏGzª¯–ÙÞ­ëÍçæYÙ„9ž>ç {æ÷O.E: aˆk¹–#OÖ[i¦å–¦â«›‰—šˆIœŒh"vµçÒÃG´Ž|ÜŒùïFµÇˆ–cêÒ•»’‹’8µ¶î¼__x«oY¸ùÜTÔÆîZ¦Emœ…¹_t B§Ð©§é”è7þ*J\/„oŠvº£SèÔctê&ÿ­ØÕEÅyƒ\žgOï&ÏÊ~n²÷A @¸=Ýî0!@ „þp×]Çúv’ÈŠ§ï%ÑŒåãWÃÜâ3æ×1×Ð)t B§Ð)t B§Ð)t B§Ð)t B§Ð)t B§Ð)t B§Ð)t B§Ð)t B§Ð)t B§Ð)t B§Ð)t B§Ð)t B§Ð)t B§Ð)t B§Ð)t B§Ð)t B§Ð)t B§Ð)t B§Ð)t B§Ð)t B§Ð)t B§Ð)t B§Ð)t B§Ð)t B§Ð)t B§Ð)t B§Ð)t B§Ð)t B§Ð)t B§Ð)t B§Ð)t B§Ð)t B§Ð)t B§Ð)t B§Ð)t B§Ð)t B§Ð)t B§Ð)t B§Ð)t B§Ð)t B§Ð)t B§Ð)t B§Ð)t š±NUéIL~‹‘ºÍC·fÍ4ºK‘óî<:Í[“ÎO÷iÒž‡÷wÓíÓ"±K¿“•K‰MÄ&RÏ¢Sè:…N¡Sè:…N¡Sè:…N¡Sè:…N¡Sè:…N¡Sè:…N¡Sè:…N¡Sè:…N¡Sè:…N¡Sè:…N¡Sè:…N¡Sè:…N¡Sè:…N¡Sè:…N¡Sè:…N¡Sè:…N¡Sè:…N¡Sè:…N¡Sè:…N¡Sè:…N¡Sè:…N¡Sè:…N¡Sè:…N¡Sè:…N¡Sè:…N¡Sè:…N¡Sè:…N¡Sè:…N¡Sè:…N¡Sè:…N¡Sè:…N¡Sè:…N¡Sè:…N¡Sè:…N¡Sè:…N¡Sè:…N¡Sè:…N¡Sè:…N¡Sè:…N¡Sè:…N¡Sè:…N¡Sè:…N¡Sè:…N¡Sè:…N¡Sè:…N¡Sè:…N¡Sè:…N¡Sè:…N¡Sè:…N¡Sè:…N¡Sè:…N¡Sè:…N¡Sè:…N¡Sè:…N¡Sè:…N¡Sè:…N¡Sè:…N¡Sè:…N¡Sè:…N¡Sè:…N¡S+Ð)ú®-F§¢‘:Ñ)‘óî<:Í[“ÎO÷iÒž‡÷wÓíÓ"±K¿ÓE«Ÿ%¬a kXÃÖ°†5¬·ÂzqÚÏÆçÏô¶ñ–uhÒQ¸ÿãÀºÑ×8èäÍçî]àÒÌëOƒNï¹®Ýý½f&ÖzÌŒ®®_Ì1–%v³`U6Ê]ýûPES¸# ûî5ï)µ˜õ:s¯oßÿf¶Ì̺)]ßg~›ûZqèËé™ǹ|?:ÖŠÃLaý_âçšpWÃoÒŒÇÌø¥ãBGæ×›¨Ø'Ṳ́ío˜Í;£'½@w­1«Îüe™xV¶ôœÝ°÷88'÷¡pK:ô™t4æÔ·Róàc?ÝŒ­ù†*Çʵp›ï©p=‚ã8]LB¡é1A'ýz × \{Ê´ç »Ëe»©ó¼Nj¡“–#XŽ(Ïðɳ§wÙ­\«¥÷uº[é‰ÃjôG¯å:IÛ>¬pïk_ø¶BóŽB,¡õ+îËÀ{ò8·àáã·éŸïI¦ïó8;ª…o– ÷õ3´Øi/wm²ÜéF–V[Í;»}&íqtäÕ‘—ÜâœÜŠ rsý +£¬~Ö~úötél_yYNس+ì™ úˆ=›‘=Ó¡/×l_8™®¼ä#mšºÞ?›öD›Ö#'lÚ›¦ÄøölžöL‡ñßF|9Ñ왺Íÿ[ö[6 #ìØ;–ˆñø[6;[‡>¥ýTaÞú¢ ëÈY½ÀŽ¥ÎŽù1-æP®”ݬí›Ð…‹¶íÜi'"OÍé ü5iý§Ú†Ý´Ë´96éìç,©Snîïkúû«¿ÔôõÑ××ôõoîëÇâ3lÙúúúN¾ÈkÆ}ýaÇ&öõo}ýõõ3§C—ú‹B¶Èja}ýÙÑ×§¯?û¾¾þÈÔ)+ªS:²EV/¬S\Ý`åæäw­ì¨S¨Sf_§3¨SæS§x{‹ñ—T´cÇŒ ù"¯¹d¢MàÇÎÊÎøñ€œ°gWŒ—ø‹Ív 9>M;K–där‰ÏØKmZ.|'¼ŸLÜ3Ž\â7v³MK°i«ŸK°i‹˜K°iøÂâ ‹/,¾°Œe2–‰/,u ¾°´‘ñ…Å–>?¾°øÂbÇZvÌ1¥Ÿ¿2Ÿ%!Wä´ >þ€ÜèßÓ¿Çÿ•ºÿWü_ñ¥NÁÿ•:ååuJôÅ-BÒî«ãÿº`_1ü_ñÅÿÿ×%Ø´ØÉ¬ñxûUàÿú›æÙ36O›‰=­ËνŒ^¦S*´ñÎê–¾ƒ“­tÄL;6w@ŸfosÓ 6·xòæ«ôÅÅ…>í’6`-&lz[¼`ãÛ¢³ñíš6¿…ýëØG#¹GOf.ÚMa kXÃÖ°†5¬a kXÃÖ°†5¬a kXÏ-Äâ0¯KÌåXÿ£ætµðЯŸÓ}æüí-s¶2ø9µ)×&Í» ?”®¼ÎòO‚ß[óné©/Œ¡wuÚùÉeÞ7nWÏçšëcØOÕ‹[ÒïÇûoÞ;Ý×”•‹z{/¤§,™è;¤]ÕzbãóåÊ—³]H›I‡µSîSnØìócówsíEÛ_Ó¾' ×ìïDÄYÖöfзt‡ÞM â"ĦñÀ¦f‘Krì,ÚPµîÈp,Ü£4ã·`î÷÷X}Ϙ÷™Ï¼JcV…ϬޠÀþ—ºÅIÛ!½ïÁ½?*]¼n‘…¹W¹M |LzŒEö9þ>Õã8ž¥'nãéwp+žcýêÉ®à™˜Úߊ ¸Ø{T±ÏëFÖ4½+Ð^äÑUþÙüEM’–»R†»RK/{Ø\Û”Ê2±LT_·MÞÈ}~ˆ}Ї,{„}ˆï©x&ëì%ö!Ü”"—w…òñ'Ýíä~ÛÊåÅ» Ê½pÒZN­‰¾©šÍ¤æÓ=jªy»çeq¢;aSqv2Mƒþ5e\þ>´«ç¡æâÔ&Åhû=¿¹ˆM™—M1å/ó.Š®î“e^‹ïCv¢sͦ£kwŠ;Ú%Ü%³Ž«e\.‡\x—ªŸw­Kºòѽ«l³¸-7«óYG/TxÞþ÷1ñýª£qhÞß5²Wb5†½n:ßzü!õ8¶ìv[†]¸Å.ô O”y6bKÅÃ.Œ ¤»°d½Ø¹-ÜH„i. zÄ=>˜Ñó™ìÇ?Ó”ønWPìo‘{Ùzê¦÷ôlxtfø[-¾*X“¸âüEÙˆPéw‘¼—°áp:2š¤ñ+ÆçÊ?jïƒþ£ÿ°‡=ìa¿DöSvï:»³ÆÇ ÆÆÝuÉd~uüÖmà O\Ýó~}æý-OíLx^gã¯Á°€,` XÀ°€,` XÀ°¸…îø­ù1Ñfì5¯]½‡Vˆ7ã¨YXrÔ¸S{wý(¸½wOJ²ñ>lgóë—ÎåíüX°òÜÊñTë‰+Çœ^"ç.W›ßÆ/¼Ø;ÿðètµz7tß{õœÃºŽ{#Œ=ÂôQÇ£æydi±‚|˜ùÀKA¼oëa´8b#NŽò½ñ2ñÈ`wZp¹', Ý†M¦Ý†M¦Ý†°A=1.Úm؈y‡ÓõÙ*Éóçí]©Ä±š9{X¾rK¿?ЬŽ@fO¹‹ÇêÞkO¦Vøxе¨³'ÓG8*]ÿ:j?&íÊ {à6ûu‡¾°Ç•Ôô}n骻ֻ·úü²#êï¢'~_³(È£wÏ9/×$l”1yß87û¹…aÍQØ uÛ ¸ÎOóyëµâŽöGêiáÚ§eØƶSo´?,(eA)ìa{ØsLGòÁÖ°†5¬a kXÃÖ°†5¬a kXÃÖ°†5¬a kXÃÖ°†5¬a kXÃÖ°†5¬a kXÃÖ°†5¬a kXÃÖ°†5¬a kXÃÖ°†5¬a kXÃÖ°†5¬a kXÃÖ°†5¬a kXÏâÜ8ì!ý°³?b·‡´éÚØ Û\îËMØ“;ûÂwäoxÃÞð†7¼á oxÃÞð†7¼á oxÃÞð†7¼á oxÃÞOåíç­åyæþ\a,Ð}xÃÞð†7¼á ïõòN«thUsÕ‡: ö3 R™ŽÌÅW¸ôûôhñûXûrZÍX¤ÏùwšOÏüåz‘:yúÏ ×´Ó‰ÏF"H],\ŸcJßE‰>KôS§µ\ÿãØéšªó£Ã7]»—+÷^ÿY¸ïq­“6® ½F¯Ñkô½F¯ÑkôzùzÍ:KÖYÂÖ°†õ*×jû±±ÒÅÃZíy_wäoxÃÞð†7¼á oxÃÞð†7¼gÊ[»±œÒ}/såæ,2dB€7¼ï¼^J…9Íf®4qöF·ý‘ eÞð†7¼á ïE­ùFè>¼á oxÃÞð†÷Êx³NeaëTn]ó£Ãk\k…^£×sÐkåt0sº¹÷”§scè5z½¦ï2©ï2sÙ±&‘5‰°†5¬a kXÃÖ°†5¬a kXÃÖ°†5¬a kXÃÖ°†5¬a kXÃÖ°†5¬a kXÃÖ°†5¬a kXÃÖ°†5¬a kXÃÖ°†5¬a kXÃÖ°†5¬a kÎg?)öÂ…7¼á oxÃÞð†7¼á oxÃÞð†7¼á oxÃÞð†7¼á͹ïÈ݇7¼á oxÃÞðæ VÎ`åÜwtæaô½F¯Ñkô½F¯Ñë!½f%ë,a kXÚµÚ¬ÕfüÞð†7¼á oxÃÞð†7¼oà­Úc¶0GÇá oxÃÞ| µ›÷ôs¡‰[¥Û~†È„2oxÃÞð†7kº‘ºoxÃÞð†7¼áÍ:Ö¡°¦f-kÑkô½F¯Ñkô½^ƒ^êoÚ¿¦/wi4Ï>TV2|<èÚ9Y¥WÈ*éÌÛ¤NVî÷$YAF³æ,ù¥ŽKæòýá¾o³÷GŠB~F’•ç’Ôå|—Ü=ç˸÷{*ƒíhÆŠ^¥[qmSšÏ[¯wÔ+ÉÏÏ·–.8›;;½ºÇ¸aìø9%½¤—ô’^ÒKzI/é%½¤—ô’^Ò»@aØ“^ÒKzIïãç‹t{|q¾Wsf»Æ¹8xÃÞð^"ïù«“^ÒKzI/é%½¤÷ éÕÂÏE»Oé“!Ò{ÿ=¶é%½·!Wîq{ÒKzI/éeï€Kk›`ûܵdð¼•c“…µ?ö=åi Þ×oòN;€ô’^ÒKzI/é%½¤—ô’^ÒKz™‚=é%½¤—ô2Ä9s@ð†7¼áMÛƒô’^ÒKzIï|Ó«Úõ ŒI/ée:CzI/óS°'½¤—ô2?ÅüãÉŒßÃÞð^ ï¯ßÞßÞ~ùëçÿýüñûŸ‡ï_~ý’$*.íå¿?ªßÑûÛßÞßßþ5—þø^ÝX]üúÛ?Eõ¢¨zñáP'"ª"úp4F®²1ŸÇ£ u„ö5.>¥EêWégÏmÜÕç6fy8Çoì–ž¥}O\ëGÃËùhßõŸ8tf娸åû.©3w>¥{wßY†Ýôz·éHEžcW×ûô‰÷Yž.yÿPºFŸªSÁÙœ#:•Å¥¸»:_þLϼ ùkÒ ŸW:6CeWž3Úœ!*Ó¼sÿ9}³ÏÅ4z¹Ç5+{¿yö¨³—7ÙÉ"Èß¶«Á®5×®=ãO…¶úÍgw.*¯}v6eeeMãSË™<Ïp&4š‰;4q¤Ñ¼Õ`jo°|¥½oÝ=°XÞ—^fuSa»wss< ì*‹G(û¡JN´¯ËµrM‡Ý¡þ4ј&DZŠâXÝs¬’™W×£*iûCxÖ$Ãßgš'æÞ½ÏÛÏÓð¬iÊ”yhÊ|äx£úYû>÷¬6Í ]¿ïØóNc§T§Ô—ÓTê>+ï“—ÇÈK|!/=L:ͳQ6œßCzšß}ÚÓ¬Sê¹í:6e¦bg¦Ò m»ä¾m;Ù_k|žÐæ}>½~Îùô}ýÖ›Ú&Q§]Òm“\»OªýG?˜wÎÖN?óŽîdȪWVqgeVL”YÔ9c;ã·nžfÜ»G>Ø®‰¶Kö“±_ó²_QØÏÙÚ±D”¢=§=h¿œ|‘×€ s㙳a…ûô¾ ®>jÙ°aÇ&رDÔ)¶lv¶,òÉÅPƒóÑ9kÃ:²EV/°c²Íì÷GL:s1e7kû&tá¢mÓgìšÈ“yæ¯Ië§ûì0ì¦]¦-..Ìí”Ô)W×)q÷¶Ž:e¦ý{ÝÙ·UÌ#ŸµO#³µ‘e??q²Œ;gÈÈ ›6±¿¯Ã¾ÆØ´•öù…Œ‘Ù€MKBÛíeýþ9aÓ&Ø´,ôG°g+ê÷ ¹"§™Ž]Èhön.Ùl×#ú÷"}q!Òp¡“«Ë†æq×>ºµ9­-Ì m¥ŽßB¹ÅþçVúo[/ÝÊxã–æW·;?ÙãÆçe6Â;ªÒ¨“Ú§Y¹¾†*ëøL°>ÈYð‡6¾Äþÿ¯y÷šÿ¹{_Tû'›8ŒsT={p>ßÖ—9 Ï´ÞaâtiŠç‡×~Íc‚ñ¡>{ϱf)¯)çk­v.­f!G¯t?ÏU:KY¶ä&=ª‹ëÌᗾݲܭµ¼,¹Ñü¹.ª5QÎäe®Y˜¹à¯eNþZÓÕ›¸ÝAwêNlg½”ð²ö2´«†òPå%‘X2wMïѲNCõ¾‰rT„*¿)‘8¦µÃ»:ˆïÊeèEeÈ5¿¥mlší~ —CøÞwmê6:Èùrî‘_æ—µÝ[Îr~™œ3W÷]’©« ³L §«ÀáZ9Ï~:=ž6%õÔé¨Núü”ÔC§£Þæ^z;rÉmµ%·?–\§.·žèÛ¹Ac¶n(TØBag†»Ì4@¥#‡²þgaËÌP\߽Ǟ!²8:"3#ŒMµ³Õ„7座‰ÐiÊ ™ÝÑÃiÒ#~k;J•ÏõzK}³ÒÉÖ¦3 ÍQÏÛ=ÛôÕ¾kî5Ãpú†a¸;¦ádϓ݀WÖît6¦¹–÷üŸ\x¦ŸÜY®õœ¯§^‹†ó²8ïαÃÇg†ŽµªóoßQ†®ÊI÷D‡.I¦Ú›/5úvt¿}u"t«Ûű¿_6íär&_ý;Y¥‘¨!â3“BQÔcÙgkØ™#éQ†h^ó#'ù,ÅØÀµæØÖ»)cƒ…Dåù˜B¢êݕ̧mR©ö÷¢§•Ï›<,·×Ò¹yÝë\' U[›|j7°Ùž“ßà`YÏûõ™÷·:mj…ùŠNóµ¶V>-üþ¾¹Wj=I£ÛŸ•Æ{”áÛËKOeª“1nH‡]íúóð ŸÏ@0c)Cÿ•þ{Üÿö€ôd÷â^¸÷íÃ5Ó‰óŒÙ.S»m*уuè ÑúÀNlC"´ `0©.ù<ã"L 1 îLWr Át-ç–&ô‹z„z„z„z„zä–ïeè9R%‰‹yN–+±F¸Øè,SöщƒßRæ OŽ«ê9›óáûFÝó<Тí€kÏêœÉ5»þ»sÅäóý> ‘Ø''m¯íï®G/6¦™ØG*C/¥ÍŠ[t ¸/Æ^lÒ^øs¥Ë‰ \ùcdž9δ¶«™ó©é“ßoéžò.:×¢ S»'‹Îf ôãèÇÑ^Ç£ôãèÇÑÃ^УGÛaQ¶ ê)»ô«†ûUÑ–>b3±§ðÙJXÃÖŒÍ06CœözÁØ c3Ø Æf9c3ØæØ™c§Gý‹-F/èÇ¡ôãÐ úqÈœ~zñð~sñÌYÂÖ°†5¬a kXÃÖ°†5¬a kXÃÖ°†5¬a kXÃÖ°†5¬a kXÃÖ°†5¬a kXÃÖ°†5¬·ÁZ½µB¯ÑkXÃÖ°†5¬a kXÃÖ°†5¬a kXÃÖ°†5¬a kXÃÖ°†5¬a kXÃÖ°†5¬a kXÃÖ°†5¬a kXÃzìý™“éHæq8æžgNš3hR§=3ÔŸS–¾æLJG®{ÎõÑO:KHððñßtž++½ŸÇ¶n_<( gýte{ØÃö°‡=ìa{ØÃö°‡=ìa{ØÃö°ß8{·¿›å#Êìa{ØÃö°‡=ìa{ØÃö°‡=ìa{ØÃöÏeŸVéЪf­uìg¤2™‹¯pé÷éÑâ÷±^û`×4Ä"}n=„ùôÌ_¦#…c|èùÞÕ—x¢ÎÄnþ'uï+®Ó¡kuªÉÏОîڙĭµ1sUûöÚ{ÿDº6Ý׬y¹¨7÷ZK$™^ZG$ô馴«ÚØø¼.{ÝÞ…´™tØõFî³ICòcówåÏ—Ó8ØŸ, ×ìïDÄYÖ¶ÊæsˆáÑÇ×oïoo¿üõóÇÿ~þøýÏÃ÷/¿~QI®•½ü÷áGõ;~ûïÛûûÛ¿æÒß««‹_«‡«DEUBIç{TÿVõï8v×vá?s¯ü~ÈμÏ߯.ß3æ=c¾wãÊÃËÒ=pÿq׎×Ëcè³÷QøoÌýÈüµ2‡e™ÏOæòþsi>wß2Ó]7*lSÁµ)RUІFÝ~ЉhîDãÍ/Ž×í…óÝÆj·Á“»¿iØŒn˜§Á1llã|lc|î ño>õŠ…÷×.¬‡åFXŽÝŒ#~áfñJ6ãH&t Ÿ½ù‰HÛª6@Qìų™‹´­mÓì3,a KX–°„%,a KX–°„%,aÉØ2cËŒ-c3` KX–°„%,a KX–°„%,ayÕÁw&ä3Ÿë¤ou‡Âö°‡=u'íÖW±¾ û‹ýÅfÀûËúV|ðA¢®Ã>Ö°„%,a KX–°„%,a KX–Œ-3¶ÌØ26–°„%,a KX–°„%,a KX–°„%,a KX–°„%,a‰~ øà‚ý…%,a KX–°„%,a KX–°dŸsö¼„=ìa{êNÚ!°dŸ]ì/ö›Kì/ö$|ðAÂ>Ö°„%,a KX–°„%,a KX–Œ-3¶ÌØ26–°„%,a KX–°„%,a KX–¬u€=ìa{ØSwÂ’õU¬oÅþb3°¿°Äþ¢·ØXbÐ[l,éÀö°‡=ãÔ°¤/Âx0ö›Kì/ö½…%60S½ÕF3÷ŽÒ=«j6þ=öÚé®û”LíµcìoèpyŒëÿí;²pÍþND|e]lº‡xœÉWôñõÛûÛÛ/ýüñ¿Ÿ?~ÿóðý˯_TšæÊ^þûð£ú¿¿ý÷íýýí_séïÕÕů¿ýs¨^œT +ªÏ¨ :ª…¬ªÏ(ª§ûú{+$ýáx !÷ßËþ÷úß>î¨Ê ª2©žø6– ›9]âÝé5 Ê epFe°®XmuéêUf¢²­ëPˆFÍN4F|ãL·nÝU·Â7ˆä£®ÜÇ6ÂÌF8Iuo’iàuVÝ8;›ˆ|_jfõýsèÌ\·®~§QÝxõù6 K¿®Y߸-ñijäÕg:1ÎÄÅc>í÷c»s¢]#ØÞïß‘ˆçâ°éSR:Y*wM Ù–p¾‰³ªï7È–sʘ ¹ë€-˜sš×@ÃVê4ØÏ˜g’H©ÉýðïI]™,Ýo?é@ùkêsOSž>œíòßûþ/ë(0od”µ7¼»('-äãmf"Ú B^6ïút0I§µÎ7íˆØÉHÕùÃtªÜ“t|Æ–·´¾7ÞoÓÜ;Í×Ô÷uñΖ·˜Ý›YoYmi«kIÈÇ9{œžÑt`Âàttù?z{Âd6m¿‰m¿†0„áZ®b5záêZ©` kXÃÖ°†5¬a kXÃÖ°†5¬a kXÃÖ°†5¬a?3þÌø3ãÏŒ?3þÌø3ãÏŒ?3þÌø3ãÏŒ?óœÛ~©hë®­¹ºC-¿Ý‡¯. aˆ¿3ãj°†5¬a kXÃÖ°†5¬a kXÃÖ°†5¬a kXÃÖ°†5¬a kXÃÖ°†5¬a½2Ö:¬±˜oÝ^ÿ~£ßcí׽зXë„~£ß°†5¬a kXÃÖ°†5¬a kXÃÖ°†5¬a kXÃÖìË^7ìËÞ±ìËÞ±ìËÞ±ìËÞ±ìËÞ±ìËÞ±ì{ C²w,ãj°†5¬a kXÃÖ°†5¬a kXÃÖ°†5¬a kXÃÖ°†5¬a kXÃÖ°†5¬a köŽE¿±%ãöŽ5kçŒïª}÷Mõ¾ª»Ÿy¿]+ë>í»“>›Þ£Xç×ÑÅn\äÖz¸k±[ÕÄWÖknmÚ‡˜\È[ôñõÛûÛÛ/ýüñ¿Ÿ?~ÿóðý˯_TœèÈ^þûð£ú¿¿ý÷íýýí_séïÕÕů¿ýs¨ík ªzùñ‚‰´0×bw­JH^}FU&ixÖÈßU÷«k{=îÙC£¬*ªâÿ0!ïÄÕÏÚ÷¹guu-ÒõûŽ=ï4y¹÷;ãÏšªî‰¢:¡6ïÜ×ï´ÿuž5qŸ°ÍÇ?;Äv¿¿ÀöPs0ÿåpH;ª°Ok³jãõ+)„ÒÕº4d8%vùN>jŽ Î/’.Æ/¬»n›¤ã¿Ý{sí£Åbl³ïɘ8¼Qm-@ÐOZô XøøoZøà½ŸSCô-–õ‹Ý•»¦Î/–•òE^=òŠÃâã³r+&ÊÍoVà­e.®4,\ór’vl‚s ›ú;6/;湑Xô/ˇ¾`Ç„|‘×LíØ€Œ°cì˜_Ô\bÇfgÇ|=ˆ6˜_˜ž^°_B®Èéö+›”ac.»Ðù ¹ÍÚ¦eŒŸ}SgàÕÆ4·zDè>õÈŠê!Wä´ zd@nÔ#Ô#³ö›m2F<¿º$ úbƒ!9>,䋼^4¶’‹MƒW·Ä1â9aÏ®'.ÅÝØ³eÍy%#NJݳÈl¦6팜°imšgÏü×zç¿„Œ‘Ù“lZææ›M浯 =mÙœ“6m‚MKDý‘bÓf9†)|’DâàÇÈ.õ=;2Ff Ï<#»ÙÛ¹X̯ß2®9Ùº:hõÎ^ ̈å¬c{‹®Ÿ‹Gwâ9ÑÃ2Øxíó½s~SVœ3ýf˜¸MïÏúT,0È“àý*=‚ÃÖ|ܶà#¶5ÿë-ø/oeî}«s×[ñÑÛªÛç¶2¾¾ÅqÒ­Œ3nÍOa+óü[Üî8ZÏÂÿ¤L±î?X÷_˜xªø;÷©ÛaŸ<7äŸa¯€üxºÞÜmfð”åæúÂ~k\j®‚zêôËñoîÔ.-Í™¼ûPtìÿÝs©å}ét^cÎÍgæÎyµyÔb/C{¨Í»§Znn«ŒÆ<Ègå¾#ÇÓçü°°·¬C“gÖì{k9t–‹©·›†/piLݱ6)æó•Á˜ù»×\ÆY9ÆZ*·³Hya—”ý™]RŽ7ì’r±£‰QÕ¿‹‰© »]øþ¨àwG>çG…YÊKµ?ÿ¿ímDËûƒ(ï‡qÏÄ»m…GËÀÚ²¥èŒº¿Þ-½ ] ñD»³{°\ò 0µ-Ͱ¥w zùy0íÕGÕ+h£¨ò1åCa#úmÅëÀÃ~u^¡žÔnÙ­¿v(NówŒÇ·ÑbÊð*‚r2·;¥~^žf½*ôì>ëÓígu"ÆÂw§c°ÞÕ;?3o04ö9i«Z?}TlpaŠ»ô–…¤~Lý(öäöãó»ÀÛl-aÝJý¶bŸîÁý·ÇºXß1 Í8º›ê³aÏ+¿c1Õ?õšwPášI› Qn u×™{Þ™ô\‹;ñ87«æZÑóî”åO7-*ƒÛ ËŸVèªÔ‘12›©{ó9aÓXÒɶtë\Ê9Ư$•éÈ\|¾¾öéÑâ·ôµˆEúqÞŒ“ÑËtJ…6ÞYÝÒwp•ó6¸£c‹^^šN°¹Å“·Íë¤ÏnW¬dë¼bÂv…Å ¶,,:[®iÛBØsVgÕÁÖ°†5¬a kXÃÖ°†5¬a kXÃÖ·o]d.Çú}ÌX)ÆS_8§ûÌùÛ[æleðsjS®Mšw~(]y埿7¹¾½µ§‚ï™;?¹ÌûÆíêùÜ<Ç~ª^Ü’¾x?ÞóÞ龦¬\ÔÛ{Ù =íH°Dß!íªÖ“f?†]Ïþ QŽL…ýäva>?6G1×^´ý5í{²p­Ùa'âߟñ-Á¡Ï‚"ĦñÀ¦*«™˜EE*ëÙRežÔzc^Óké%m0}wZ{¼4qº¼Êò¨ZYwt¸‰ßË0¯ã²,²àqãkÖ&_þ½É©W¹÷”‰£ŽÅØ‹Z3=£ûÎcÚ¦SäÃo6l-´a, ]ïbVvΡѬ’j®Çõu»BQõ­ÈxÞþLJ”˜­íÏÿ¶Þ­è{üö´Ü'Éíg”eâYé;¥ëàK’m£]Ñ»%úL:Z>sÞ_N®™9ˆm¢Ýè'úùtýÒM¿þHèæMû‰Í¬œÍ‡kO-®³1»:Œû«ÛW£pÝ–ÈmÅf7f½²ÙšÍ®cÎê`¾Û†Ï±ÿšù4û˜5Îþw™¸ß~ß2¹nÚÄQ¥ã¨¦íqf÷ºêìqVÄí=΢*{³ï™{Ö®síÙïÌ¿SÔ 4³ŸKï~mgÒ³ÏÝ}ùûµ/ï×69/ÕýeVËxp¸KãÈwÆŸu•Ø,óûÔ™jaïÖ?ž>kT]îÏpt͹î^xv}÷ÙÔ¥iÛÌëIyƒž†Ù^«'ytƒž$÷ÓS¶}^ì>j¹k¾Íþ^©íî=éäUÕ:¿ô|œì­¨û¯nß—²±ùFöÈ{r(°O±[ »ûÚ€GîF}¿v\¤±Ñ´ã°Ñ¯ fÈjÖí¸A;náƒvÜ]Bs.EÏl²Nõ|÷÷G®mÑSeôþ~Ç¡?ìèïÎÑȫΫßûEsì{k1}ê—»*Ÿô;16螺rTà‹>Ú=«eÜÌeû=ñܼxãwž…Ïø žë‹.Þk˾߇%þéB~7ù¦Çb¦9ú¦ß>|Óï´÷–v{ؾÒ?ÝíÑørÿôü‚z®ã§þ¬:åúSæÓ~wgìõýgüPŒ¯‚ñI°ÿguðe0É ×NûñLw7¸sx4û&ÏÁG¬i‹×´Ø”]Ú—Ø“ÛÕúàw¸ôyò-Ž«ZQwêžwž¬ÜS΂ÂwËTzÿéSoEûÌ>Úîýÿ›]?Uˆ'/Nãk>}™Ù‰àS´ö÷ÈÃâÅ.ð¾Uhkß[)Û§â¶ N[D³.SZñù2qk-ÿÒ¾“·~oT7ížù °Þ袤|BS"9ß‘âàøy¿}t„OiÚ>¬*ug½'½Õy’$#ªóÃ.Teé®v:ÒZÍd0NôÝkSƒ©Âo}ÇIº>W–|¨^v'Dzäô Æu‚·áq,»*$é ¤ÂÙ7}ÜÝJËÒñã~Nv8–=æ°ï%–Ký9Çt!dE:Ï!?ßnЧ,™Ðe‹æ11k¦Ù„}×Ê »J=bF-뜯S^ØÏ§\Ø„š0ü žÌ^¤/.:éeYö—…5¬a kXÃÖ°†5¬a kXÃÖ°†5¬a kXÃÖ°†õrOb.Ý…5¬a kXÃÖ°†uðI»¬õgg«ÎùÎ/Þ¯¬uVVöü=Ìúâ¿z_³÷ýxê›zqŸ2¹YGF0É<‚7¼á oxÃÞð†7¼á oxÃÞð†7¼á oxÃÞ𾑷jŸ²0Š»ßöè:¼á oxÃ{Ó¼Çl ŸÊtd.>º“O¿åöñ±H_Ò9ýEÏ@/ÒöY¸VOqMž¦#u±˜è«;=LÃy‚^?ÍI>F.rûûfûþ:Œ÷\K‚ßßÓtXž¥ÝóJœ“™¶û)è5z½ÛŒ^£×è5z^ßM‡3V÷‡û\¨NÏýÔÈY®ËIYÅ(XÃÖ³_Û‡Sš›t±¶o>c×ù,™÷Pº÷l y.GÙÞç¥É£;ž2w¶Àþ×=³s¬e7¿ƒÇRfYœ9—ò³~‡9²ü9€þ̰Þc…ãùž*oô8 ±ÍjuÿmŒN–]§¡˜•iÖ¯3Í7™d'¯ÞÏ©fZ‹®gæd厓²æ+뙆ò<—ÿ¦ÛêôSžŽœéžæÁÈrj‡$Rw¯®9å¾ÌîÄ{Rq2°?Ùyçd° cN.>{*°ÊGž œ¸ sBð®VÝnhN¼KC0£;Ýûí)xqøn>s3‚Q…ÏLÜ—º“_“ö‰¯ò]æ”bßPœ'ñ»šÃÔæÊ¨ß1dŽæÎ×(™;·{ã]÷Kð÷l´gn Ñ~ˆB9@Ÿv@ݽYÒy¶ï}ÒTœÕ>ªÖ)Ýs¹üŽc=Ç0*÷)­EY ÅûñeýÞi¾¦\ÔÅ{æ'tã–šÑÜuè_Gǧ è,É-f"éΓG™wjòŽ)r¢G‹ï}¶0 7ÞÎØXÝygtƒýô¶3 -ôf⨠Z/µ¥)¶ô®¶4Ÿx0j|‡´éEu:l{Í}ÆBŸíµcš²^„­¦‡Öä²2Ú‰¸÷ížã ¿ z{h¹;Íú|Meu/Æ÷ÐUrTRŸ•n²£ÓÐãiÎOïžßÓ#:VIŒJ×óªÄ{Ðõùòò¬yåþ‹«çUY‡8Ï|ŠÞcžÙêûåû[=Íž{}émŸ¯Þw&ø¬ß\o-žÐSÓËî©ešÍçWZЇ÷ÜF¤á–±>Šú9“¦,=(w-)D+À_Óm7†‹­ŠD¸ !Þ‹­Ù$Xùæ½²F÷ÛC_Æé‹½Ï¹.ùqáÖÖ§Ñ2ךg¦Ž—b¬8 ½ø›t¦|¼Î\ݲ-iÙ¾Ä}÷€_ÙªÍ_ܪí0èŸö’‘³¾j/ZvÕkwâS¹ë¾µY~º™b÷ŸŸ¶ãúî[ìÅó²õèß—õ´ uÒ‚LÏ´ -•c­Ý“[…‰èG1+<ì´S>nV¸›ó-нȣ«eü³¹žoËò%³ÉÒu—Yæ$ô£OfšU{½€°ìŒ[¥a<í^㦛—]Çéîä÷­rôc–~ÞÆÏáèà-°f9.fŒüYú&7„9癢®Xlœ„žhsÍé«}6ã¾öÞõäîfï g{”˜cV§ ¼Ð~hËÁ-Ô‰[«;¶d»¶QÆ{zªEkþ%:ã!ç½ÓŒGšœ‹±½P7ÿbºç‚q¦;öÞsA‰ïV|ûû…(¿ïûî–®Oa†!jv]ó6„{„¼;W~‡wÆùú8ÝT·TåVÝT7ŸŽ>§ÉL0¥¢«Ë9ç°ßÂT Y\äYžw{ˆ‹mçÜÈÖÚÉ¥¯™Ö¦^ô…µÒ`ßqi¥wéìÕD·Iõdöª=´ÐJ/ìa{ØÃö°‡=ìa{ØÃöSØG/æÎ>ƒì3x…~OÚ_0ZÖB¶&>áÊÑäW×ÁÿgݯX,¢{ޯϼ¿µˆH¸7eñý¯Áìúk°ƒì`;ØÁv°ƒì`»‰ìòö¹>ç6Ë€z7šÛDvèì`»žkqýNÿ9öÚÜØéÎz€fù¦ŸÉëå™C 5óYXÚß,½s€‚ž¶ÕêÙ<ûÃòvžšM˳‡h=ñ´óyÜÿöOñYŸ `~õ÷fÌêÚ!i¯ùŒÜÐúô±6tßÝ«Òn0̳&îæl›´ÿ½þ·Û¬55{‚šSì§ÙÝÈ]3Álá¿ûwû÷\¾O>`/MN>àäƒkW¸g*ì]ÙìÄ…#\mð˪º»rw;Ë&îÊ-÷CLÚ»‚/yGøY'çË¢{Mª°›[#+8߯Y‰}dKW–±·gÞn pÂÁBN8ú0ŦÉS üN5rˆƒòÖÔýYÜ9e§sjÄÉÿnOçV]%ë=Ñ>æâd %ŽÜ,;'ëèS—2NˆXÁ>ºSö~¿Ãé·”íTôi¬®%!çlozFî%.w,«=DOî \¶Û”vßß$ìûkóp íölDw¾7®ÎÂö™"üŸ9’V +d#‡zÏQì8;,P&å˜Q³ƒSNа[wg®§\Ô§SĦ7»ûÜw;²rì¿f>ÍI¦÷í—‰û»ž¹)8êS6Ì©fTb¯ë´ý½;·ÃGQؽ«ûv»ºIav¦ø0¡ú^ÄâÙ¨>åc_=Sa”!rg"{Þ©?jQ™žü‚ϘôìswŸ;§qJ^Õµ²“—V¼×䥺¿Ìj½Ó¤Ç¦qä;í‰'ª>EÅËϨeáFŒr³óVìvé?ÍÜoæ)˸o>¦=}„x¾=„­¡:‡eŒ8ä`q=ƒžó'} ­å¹—‰gÓ,»!£”uÏä“ç-vÿS¡f†ëˆ…ɉXØ®:‹•»g=úóü²å²}Ƚ ‹óÆÚà>NW-ÖS<òt¨Ë¢V ÔÁKÏ|æa¦ÕzdEጦf¤\~ÆW„žÑ¹$›gÍ+׋m©æM'Ô¼¯ØŽ6m÷ýÖ´Mê¤ñÐ;l“ÚøgEÀ•=ÛMÚ¾ÚE¿`°½Û˜µ;Þâ·|£/¤µÉ£ë›äiû^¶(f‹âÞ²§ÅØÝížHß*í^ñ<»·ú#ÕıñWès¼Òz\O8žñÙÇ(èÎÑŒ+:BaôÑ9ÛËßs» Èׂò¯7_Í|!y$kÝ&lÂlÐCFUœª‘rfQÇaˆÑNÒéz(Ð G6÷DÃ÷“}ýy:|ŸNÜU‹C”_1$37ª‰•1!7Û‰£,ÈçdNN&%·™ßÍð»[¶˜–°¼j²8®`zXOa9‚¥œl?£§‹rùÔwš"yÄðJ'}«ÖZ)ûAæÓZr*yŽuõÜ껹Õó²»=Ý«BÉÞU4Ô»ª^QîN·¿YJè.ÁœsZ»…1êm4îñÎÜ f+¦ú{ûl=¯®ÂŠ€MvÅ£ ]ñt™ÝqïÕ‘é°k^v¼ÚŸ¡/£GôVÈb›Ý_ešç{ž†ÝþN<ï…ǼõŽw£Sº7­²éúNù}lýŒÞuøÆe‘?òGþÈùÏ@þ*xA¯UþK/o”3ì,òGþÈfòOÄÐ$ò§ü#äü‘Ÿü㉧ÑÏzny‹(gkϸG›ýÈÇ#Æh—>ö‰>m$OSAäã}µ¥÷–ß–ïq½Œuñ½tb¿Àæ˜;¾ÇIÏQ.f#­ÏpïÕ¡²}¥Ûµ¶ñÍ0 ‘:õÌÓç¹ æÓ6º]‹à”M¤Ô²7‘jÒzÇ4œl±˜!ÿ%o"6vã°üšôumΓÀ·Y ¾æ: ¥Èa5Ýo ìíz‡Þ¡wl’7§aå9)¯‘ù(Þ1[Ý}#E¾Èù"_ä‹|-+_æÞfE¾ë—µVéË>[Ò¾¶:+Í—“}5újôÕè«ÑW#_ä‹|‘/òE¾è«ÑW›§îÞâÆþ ?‡{ä‘wðŽ{Û¹{Ø”{Œµ KÞAùÊŸ[m–g#Üj;mäƒHÍ–›U<‘[ÙÖUþÖ„[ƒ*·›÷Ì‘Œœq ƒQÁ¹áÌ÷ÝïR„§ô—€Ý™o;å9?5$NŸ›o¿ް^}ØÁ€@ ]H zÇÁò{½«çˆ%í†ÑŸvÆ’v›(dÛDAî3t«í9gÍT·w‘gŽË{šSrÖÞÓ§IÓÇr×Of.y듚Δ·HÛª˜£ß°†5¬a kXÃÖ°†5¬a kXÃÖ°†5¬a kXÃÖ°†5¬a kXÃÖ°†5¬a kXÃÖ°†5¬a kXÃzn(èž×›r€Â7'ÖbCIýœk›á¨V¾i7ù"_ä‹|‘/òE¾Èù"_ä‹|‘/òE¾Èù"_ä‹|‘/òE¾Èù"_ä‹|‘/òE¾Èù"_ä‹|‘¯Uú ’oòM¾É7ù&ßä›|“oòM¾É7ù&ßä›|“oòM¾É7ù&ßcóÍìÑkXÃÖ°†5¬a kXÚ=ðØ=ðØ1,òM¾É7ù&ßä›|Ì·ªÛÑÈ›|“oòM¾É7ù&ßä›|“oòM¾É7ù&ßäûÁùvi0Ï oòM¾É7ù&ßä›|“oòM¾É7ù&ß䛵§øÆÃÖ°†õYG3e¡×è5¬a ë«XÇõ^R¾¹LzŽžÃÖ°†5¬a kXÃÖ°†5¬a kXÃÖ°†5¬a kXÃÖ°†õœü Ô ý Ôzö6¾³Ôm‘6l ¶Ö°†5¬a kXÃÖ°†5¬a kXÃÖ°†5¬a kXÃÖ°†5¬a kXÃÖ°†5¬a kXÃÖ°†5¬a kXÃÖ°~$ëAÆi?6…yöÑgZÜûÚCÒ¬Ä5u‡8xï༃wðÞÁ;xïàë~Ç«ú.ÄAÄAÄAÄAŽccÍÄAÄaû\fN VÄAÄAÄA"ޤ¾fžqqq\Ç×oïoo¿üõóÇÿ~þøýÏÃ÷/¿~‰£$)ìå¿?ªßÑûÛßÞßßþ5—þø^ÝX]üZ' ª^ŵS¢ýî‚ID×ÎŽÚÜwüíŸC•ðcõY˜{ü³É@ˆÝÿŸõ;òcxwo'¤ûßþQæÿ}—» Ê€:ÖµÉwùÔZä½Î’N„éNø~zÿLÝöÝìúVžó©ëójeuο³ãÃÚoÈOvöçL9‹!zÁÙÝ3¢•ø;O8á,÷‘y4ÿ5z,¸™b;•Á¥¸›ü»²lí¤càóg¹kŸ-ÆÇÝÈ£¬uϦ§ç}™ÛÊ|ÜeHKc·«<'¹°ÎVùÙ6¿óI¶¿Í½EIÒñ×{§¾ž(ë´È:¢«§«åêúN²Õº&Ç]hÆ!LÖÂu(ý6î¢]>[õÜÎýçlœ}.n§+ó6)®Ë³½_?YÎ"tezrͯ…8?ZΙӥ9˺Ónñö¼©7r÷>׬òòòmͦÈÜ;ŽáR„z$K„×¢ÎTãÛ1½ù”u÷€nÙÿ®žÚ¹gòv¾¬¼œzÛ¼Y¢E‹7hñÕ+Ç:DÕ÷c~_çÿß›–m~!Fܳ \¤éaÜáû¢ý»HF>«\¯da ;ŠhCº“.;ýçìÑ^a°GØ#ìѰí°öÃèÈçé(Yš•'Ãdg›´;×˯úJ]³ºÜàðWº™‡bbwyûpŒö]Žƒ»oßîêÂåÑ7ñå}étfi^a›.žéž˜nŸýÌC×jLœYšý¾ËÒä-=ºµìönH0÷6ÏÊ®ìñô9ß-Î|×·éðÝæ8°nt6z¹}«‡ù“‡aÅ0L“Æ¥¿ÂÖ°†5¬a ë6ërB{x¡Ó“ö]Õï4jÑûi‡¼¼bʬóNÝóNßÖöÃûþ3vœ­+¾¼ÖwŸÿ¾ßݼm+SajÆLtßsòÞhWº>€àßûžT<禆üÍè;Ø>¦r:ãúR&žUÉðÜ5Ý™ Ëk6ß/ÈÐß7ô9V†ƒÏmP†',Ó¯-N†‰ó)kY"¿…ÉO¹gË: ?ä‡üßUòSÁÍ·MÝÜßâËŸmƒw_!Üøßãä§zÜé(ÔÈù!¿P¯eb¼%k+«ûIùC~Èù!¿óò[Ňsyp»µ 7¸Í–s5OÑ·5Ï‘07}ƒÛ:¹1޾Á n‹àva­iÃìH –z·|ÒJ«»œR·[ie åöIØ \Wázµ÷¯ð×ýïî³vïwYOþ¬¸zï9—Œgj\S>"¿Ï”ãœÙ¾"_ö{uMGÏIçl™$§²®ï=Ý%.fºMqìúq[Ûª8vaìÚT½À5©ûúÍVœŽ›ÜÓ¤okÆÉþ!#ãÑxš6yât0Ý@^c7?YPV(+”•‹e%­ûû”ÊÊÕúãd£ÍóYx¶ÙãÎűzþør<T‰ÕuÝÙÊ;=åFÙ¤lÎEWÉM…£ºm€Ùï9¥ì©ÛË^ê÷ˆt}i›Æ$l×ïËeêæ)?Z·=}w+ü«ÊêÓЪ×úØÞÚE~-Ç®Ò/§LÑ/§_NY¡_N¿œ²I¿œ~9ýòuöËÇœ[‘L_~MúäûÓàké}yÔ9N°ã¹¸³)°ùØ|lþ2l¾Ææcó±ùØ|l>}uÊ0e˜v}uÚm´Û°ùÌ32'OY¡¬0'OY¡¬PV(+ø¯à¿Â˜e“11ÆÄcLŒ11l>}ÆÄ(+”úù”úùô­èçS6éçÓϧŸO?›ÍÇæcóY§‚ÍÇæcó±ùôÕ)Ãè"e˜¾:í6ÚmØ|晓§¬PV˜“§¬0'Ïœ<ý|Ê&ý|úùôóéçÓÏÇæcó±ùÌÉcó±ùØ|lþÌm¾+.Tö2q>ø-m|‘§ÆvòפõSœƒž‡÷wÓ.Ó.þ¡sÌKìöŽñyÆç)+ŒÏ3>OÙ¤ÝN»±Æç«ÁæÓwÁ²BY¡ŸOY¡¬ÐϧŸOÙDéçÓϧŸO?›ÍÇæã‡‡ÍÇæOÒ»›Í¿7}e;Ž‚ž5z×ù°ÿ¹¼ù<¶t3sï8Ô>cÚ¤­znòÔµI|Ư¬Sn$‡“¼îÜõ¼Û&wþiÑÇ×oïoo¿üõóÇÿ~þøýÏÃ÷/¿~QQ%öò߇Õïøýí¿oïïoÿšK|¯n¬.~ýíŸCAda„P}?šJÍ(Çþ|Ð{¢üò3—B‘ÜþŽnˆ>W¢õ†C%¯ã1„5ç•P…ʈ+ȇ1Þ„û„16B»ïK²áUWü¶:wõ¾.3ѨëxˆFÏN4¾; ¤üLcM6šM#&ù¨cå¶ñYÖîcFÝ8‡~³ï%"ß—:@Ñ<:@«Y`RºÆø+™”:åÂ’Ôø‰ƒ§/ðéó |V³È'ºƒ yóHðŽ` kXÃÖ°†5¬a kXÃÖ°†5¬a kXÃÖ°†5¬a kXÃÖ°†5¬a kXÃÖ/õWÍG2—»ÜÑoU.Šê[¸öè…}'iÛ?i³ÀÎ.nÍ‚_¯_èÙZ §j}°ïÚ…4æ·,þë‹?³(ðÃ}?¶õYû§Úå­¬ßÛ<ÕzÝ'xÃÞð†7¼á oxÃÞð†7¼á oxÃÞð†7¼á oxÃÞð~*o?o­Ü~=©;Ì'v{÷ tÞð†7¼á oxÃ{¥¼Ç–‘Êtd.>¿y~Ñ>0Àþ>ºƒ3tH§MŸ8TÃ3¹^¤N®þóãA×"¤.íýBGõ]”è³A?›Ã Ä! V×Ttž…+÷^ÿYˆCŠrW†^£×è5z^£×è5z½|½f%ë,a kXÃúékµ³ûªÝZ§-ׂ³F{^ãÖB6°†5¬a kXÃÖ°f¼ñ¾ÆûV¢Óºs°ï;6}Õ¼ŽËþ§]?Ð}¶ú›YÐM/—ÌsôïMDßP‡þ©ï÷æq`áû~oo¿ÝɦQä!}úèã«8Ù˜œç:·Wÿ>˜ã’ã÷·ÿ¾½¿¿ýÛ¬\Ÿ¡ìÏqV»ö¹ÎÊåÙœc~¬Òu4gØW×rs6u•ßcõß¡’q´¯¯ùû¢]}ï^‹gÃÏ*–e^Ÿ™þaBÞ‰7ªï·gcëúÇž÷}RîìlúÒoó×yVÞ×M\}>§›ÆÏå…Wž‘nlÒµÏîv¯?Ãý$¬\^—Br\˜¼¤ýÛŸÿßÔ¯·¼ÿQÁÚh!AÝç=ûC°ù‹Éûµ!ž¨Ç»Ë%G_§äõ(õÕ~LW¦_Ïc¿†ò† ÂaƒkƒÓlÓ1[—Ý(ÔkËÒšìÆ¡è±ñøz.>ónM9\LPNæf¼lÿy]hÊç“ꈵëWÜk;ëq_;œë†}URÆb0¸ä o·õsòQ(з ‰sŽÏ bwœ¾¤³”ý` ‡@›‰¨b¦¼EÚpxE¿®ßÊM«ë¹LÇd D^ÆÈàÙüEúþ°‡ý­ì…£ÉEöÅGûG°ï8´ҋޣ÷°Çæ`sîß®÷ƒ¯jÛ§Âq޾})XÃÖ°†5¬a kXÃÖ°†5¬a½¸±×›Y_±©Ïµ÷ ·è-¬a kXÃÖ°†5¬7ä› ÿöKÌåf¡Ú4 > çnJúÌ¿®ÞЩLÜS¯]µ1XGVgÙû̓Žâ½r½E4~…Ýh+u÷êšKîÖ[ äü‘?òGþÈùoFþ1òß|ùwÏ!ìÒÑ ä¿aùçÈŸöß䟹1@·f} ?€ü‘?òGþÈÃíÔÉ?C°ÈŽò·¹ùró™øCaÒp­ k“ëÉNeŸ Ø… Èù#ÿMË?¡£Øäü‘?òŸ³üY3Áš XÃÖ°†5¬a kö™aŸôÖ°†5¬a kXÃÖ°fŸ憘DþÈù#äü‘?ògŸäÏ>3ÈŸ}f?ûÌ Úì3ƒ>`?òGþÈù³Ï ú€=`Ÿö™Á6°Ï z@ü‘?òGþÈù#äüÙ_†µ¬K5¬a kXÃÖ°†5¬a kXÃÖ°†5¬a kXÃÖ°†õ ÷°ò>¯…ó‡g«ùÎ?wd…ÿòGþ+—ê|Ñcô`óçèÀ¦uÀûž¥îS¹O^ÐF@þ³“¿Bþ”ÿq²–ë]‘?öù#äü‘ÿä¯ÿäoÞã÷!<§Wícâßm>ËzMÆ ç©~›î7~Ÿ›»FHÑ‹ÅÕš6mäü‘?òGþÈù#äü‘?òGþȽògýë'` kXÃÖ+d­œoû«xûø×À\¹}Jã;èùk ¯];ˆÍÀfÀÖ°†5¬a kXÃÖ°†5¬a ë±ÎÜžsä-Ò†~£ß°†5¬a kXÃÖ°†5¬a kXÃÖ°†5¬a kXÃÖ°†5¬a kXÃÖ°†5¬a½ ÖÉŒýúüúÐoX›4 2NûÙänßóì¢÷)JÄhù!?äg6ù‰W(÷ÜêõM­,?9ö`0?Y8ÿÁŸE‚½#?äçÆò”ºüdÈëÚütÏÃIÄY9òŒÒÎJÊNó’¬Q7ÕJóE~æV}\ç9‘òC~Èùa싱/ƾûbì ûM~ûB^Œ}1ö…M$?ä‡üŸ1çÒ‘Ÿç'uuk¼²|¹>áªòämAê>•ûÔ”±«ò£¶k3dÿ›N~ÈÏ„´«eæÇ¼Ç“žËãUãþÝæ³¬Ç­ŸÕ®ðã*Ý1?îr÷vEÔÉc:ƒö“ƆòC~ÈùY{~¾~{{û察?þ÷óÇï¾ùõKœ¤:µ—ÿ>ü¨~Gïoÿ}{û×\úã{ucuñëoÿÌKŽÕKªUF£]½Ð³ÌÿÊ º¥!è|ø~ÿŒùÌ«LdUøÌÄiw”Ô÷™ £öóé®ߨ8í½ÕçñX¿S¨ÇðœyGš×‹JUTóH»,i꯼n6c+Ê¥Kuê Ì½ãàÚt&-E¨ÏLº›zD·}VÆÖé}¬Î2ع{òvý8ßs£· ™f¥hAÆC-È*ŠC\·´L‹íèZjñ¾¢<´ôƒª[j¦åvT#î¿1˜xòch}.9ý×µ[ßþ»iÞ˜`š>þûIPî3:si&U¡è¼÷R°-÷εüÌý sÆÆóè𙤎§í8O{3q\̳;‹ié-vi²‘]Ï)[V·¦w_Õ•Oz¹Góëêី–QóùÑÓ 6÷ƒ[ÂdwÎÜM¥NG’0Ìc—r”ãòÝÈ&¯›‘&Ϧ™cxØÏ<Äm› ©+ºÎ.š$ž…¿×ç£iZÄõóÍ=Ú1Mƒ|Ý“¿­9—†</ë©úxv+²n9é‹÷ãíÐVËŠtçNT§l¸iÐ$Ÿ>ü ÛˆW1Ôí!$ûlÖéK;ßgûý÷Ht‰ï ã6¯út[Ŧ;)»Ä—ßõõŠm—lEí­7(+”Ê õ< a8w†ÒUÖò¡­Hý·¢úoŒ®¤2™‹Ïa:«ù}tìt[—<ã–>½¢l§eH¯©žkÑ„÷ÉñGiS¢Æ ÷é—U•Á嬱;7Øtxe:RE{ÎþÆfb3o¹ö,]MÛ[­UWg­È–q.–áh—O5aÜ`¨Þ;±ÏÜøGÜîÏÆ§j>–Ï¿ç± [² ºŠÆZc|E+ùD•ì‡SŸ=‡ñg;ÆîÚÎù6µOæa_ÿï]]Íj¥´¨ï1².а Hö¯²ï/Bœñ§ð½3v¯ Ï4ïˆêß¹»ß#ÔmUg}¥Üõ(›qÐ3OßÂ_MÓ^ãŸiôÐëÏ£ý{W>a°µ°Ëa0‹ð,û[µŠÚê[|ì±å—ƒm+Eí¶’ çÚ`cï“¶ÚFÛjØÚYíÒ†ÓuˆJéüd!â¹uávàÉ5&/.Tb0<ßèÃtäCuÿ=SäþJ3‰ø|òâC‡ m ë' ª >þ[÷žòûH|Nlw [¬“vŸ™™‹‡»2Ffý H›…¡çdWL”]$öáð‹Ec±÷¨êì¡? §EØ´|‚Mó‹Ëe×”ˆGÏÌ®e¯+#w™8Ì‚¢Ý?hê>5Ep˜È\¹ès²‹øâžÅ{~‘Ñ”}p1™Ÿuœ½ãË‹–ºú´ýXŒcУô8í8ïh±OV&’Æ]Ì… —{g¥Å«^gó‰Î¾Â9M:úŽuA_Wª¯zÀÙ,›¨[±hW&bgÞ9‡B‡Î-F š:=íœñ‚¡Cè:„¡CèÐvÆ åB‡Ð!tB‡Ð!tB‡Ð!tB‡î¤C³ö‹{ˆÀ°ŒvuбÙ[êÒåÎä±zt¬å?y\ªó~}áý­s³ðÙœçxá:…N]Ô)o—§„ô][ŒN‰ M.êUòÛ«["_¹Û ¡›Ç&½ŸâPákÕMwÕVzûüÆË±FržÌ\ø(ŸÝHÖ°†5¬a ëW±ök—^ÅûÒ¦Qè7ú kXÃÖ°†5¬a kXÃÖ°†5¬a kXÃÖ°†5¬a kXÃÖ°†5¬a kXÃÖ°†5¬a kXÃÖ°†5¬a kXÃÖ°†5¬a kXÃÖ°†5¬a kXÃÖ°†5¬a kXÃÖ°^e^º/¢OzŽžÃÖ°†5¬a kXÃÖ°†5¬a kXÃÖ°†5¬a kXeíφ.F2ÃÖK;#ײØÕyóç…çeÈc“Žhø,ô±é:Ã[žaÞœO.Ó¼sÿ9]²ÏÅ4fáœ]ÃÊÞ¯GžãëÎ2¿é s©3qytæ·×™c»:ëÜÄ•Ÿ–«¹ÊþXÜy¾RžÕTÎæÉ†ÇÝM:L=coÿ ÇX…sÅGqÎë3®7É9gM6âóûm”¥:*¿gâó[ëìË&™f= ¯e™Á†0„! aCÎŒ¡ý›Äµù`yËÂñ“ßa9Ž¥d—‹~ö=˜îËD¢OÝý\q{Õã®÷”éÖÇU7Z>`¸-†Ìó2Ï kXÃÖ°†5¬a kXÃÖ°†5¬a kXÃÖ°†5¬a kXÃÖ°†5¬a kXÃÖ°†5¬a kXÃÖ°†5¬a kXÃÖ°†5¬a kXÃÖ°†5¬a kXÃÖ°†5¬a kXÃÖ°†5¬a kXÃÖ°†5¬a kXÃÖ°†5¬a kXÃÖ°†5¬a kXÃÖ°†5¬a kXÃÖ°†5¬a kXÃÖ°†5¬a kXÃÖ°†5¬a kXÃz¬«ô$efɼ“>ô=‡5¬a kXÃÖ°†5¬a kXÃÖ°†5¬a kXÃÖ°†5¬a kXÃÖ°†5¬a kXÃÖ°†5¬a kXÃÖ°†5¬a kXÃÖ°†õóX2NûÙ˜g“úYsMWÏh—óiŸ+ëôgE·Ms\?çÓ˜dáÓðsí.qV÷&Ú½ß}?wÍÆY½;õ2‹]|žUá¾Ç. ;'O§3Müf]µIOáâQ.MÑ™ßÚý>¶åª÷NŽºÎ£7é‰+r¾[†Òx¨óÖÊŸOÃÔô›w&H{ÜatÒêÓ»4.íÊéäØôçµþÜ5ýy›>ÿþƒø¼FO¢;§Q ¦ò{&>¯Ñe§gwMkÖ“¶kÓ˜‘6ÒFÚ6Ÿ6%ê“ÄÙ¬¹¥±mb&i”iÊE=|´¨;"Qçv?_]ß3mjGn…ßRÒ¶¯ûx¶?5·tj§ƒ¥û^º~\.úªÎ‹M¿>í#[[œ‡¶äCò‡~ãÙ¼óîÛþ™³e™‹+u,DÞÇæ3ÏBæ–zÅÆ¥ë{Íu›¦È}~¸ôçõ5Ÿ¯æ97>a®e.oæÓ~ÿßcñþÑóý·±,cÇÇ÷ï³vÖÿV”°‰úÁ|S‘6É2Œ6’Q.ô#ÌS¡O’¥ïKNéÛº~¾—«ü™g 1Ö»q+Uçõéu‡£Ï÷Ôþ|,lQâÊ^!Ú+åBóæË€e·ß5y#o休7òFÞÈy{mÞ¾~{{û察?þ÷óÇï¾ùõKœ+ÙË~T¿£÷·ÿ¾½¿¿ýk.ýñ½º±ºøõ· Ó)¬^\T/VU"‹*DQìï8„ã±*®ÿOã:bû:¯ÊDZê(t"æ×wb^ÜÏ]ëö¼vwÞùÜ|óX9_×;÷Ý™ßïÆ7äC0û}Åã ûŠ—güÆæ3rúíäjÓèäoâ󬟃É_ããàææ½Lzó|[“Ö;¦Á—Os¯ñ+0~v\h'ÆJñ[Ž?íÄØWß÷γ¦Ÿlâ‰÷Î>%×ÿ·õãéH•÷¥MÕvݾËÛmoÇw!n‡¹ž»ÏXŒ5zst¿}} tľ# ×b7¦ÕÄgâÞŸÑç‘ùëµïI’ ëX÷î¶ÔÚDmÜd>êßÅÎYó*š(­U§>Ú¿Šé]¨"Uúÿíµ¸þž{Þ'ÃîôZä??‡CâõþŸ¶/ÀfZhš>õ§­ŽUÝZè¦ÝNuó³k³kýfæSeõgÖ¹Ï0VªŸáî ã¬ÃWæg‹yNÔi .ÎÒ“&\z¦ifMر®F&7Ëâ0½©¦™2Îîà2:2Ÿ™<cSEå0k¦™ë&lêz׿§¹áŠôYW\Ÿ†5¸>ë M¸g»?ëötÖjÜÍ㑼õùæò3íÇØ¸›ü»á¯g ›ðyÏÍØ¸yøîÔSýÙÿÙ;·]iq£ ßUŒæxa0»ƒÜI¤h­ÞH‘¢IÍH¿åÞÿ6ظ º›6ðX½Ý@ù}Ëå]¹JûOç2Ü\Kz®u~7Ë ½×ÕÝbkÛªût®“®%¾>`;[wÄb Þ“±M¬ÍBo_­ò.›ZžTû0å]iWM{¨Ä¸AÊ{¤ñÚ£ñÃc6ý`ŽˆÞ£÷Öû§Žln|ü»õ1æÖÇqÛ+õl­¨$I'ìœ_¾ëµySeÖÞs¿C^èzGýêvÍOõÚTÔû c÷Ù§Ük¾3kÆ×¼^_¾Þ®'¾÷dì@^¯)™’Û]~woT{œ,ö^³Oéúy׺üÕ%²¿[P—ËÛ¢S—Ö{ÔÅ<«|ðL#O%ãÄgÆß5×Jû½"Ó4Ï<[Ï u¯y·¹×ázµëíòÞ«ÛƒêÞ{©ÛB¶ÊÊñ6=ùöϬ®[=ÑßËô$ROÊ뉩Kß3+LË…zòe½lzôD¶¯6=û8e¦+N錱òÚu[é×È‹5ñ7¯ëÞÉvö:×rÉÓÃk9cã‰ÙëØä"„zâS9Ìå~„~$øuâBœ_¡/ «/ID8þT¬±h¿?õpXð _Z[é¦â(Ús“1ž°g Ö‰KG {¶­45ÉĵâÒ§;…³Ø´\øH$âüRù˜'lÚLÿ°\¤”ŦíÓçUp g+Ù´Xì}ÉTRnm8íIá+þ®ø»âïÊ:æA×1ç`þ¨?Yw%|.XCÆ7¹ ¾yøæaǺ¾yŠ9É.}óó‘Múæ)æ"ÌE6èS¡;)ÜèK6×—ty„—ûk÷ÿ—X¤‚µó˜‡~/ÜÑÐlÂ'OÑìÚ'Om¿_Iå³3ûŒ¢§ÍtÇBâü[S?›"6·6¤ú®›ž¶“Z¶[×ÁÔ°i« ¹aÏ&qìsžš8Âãn‰\œèÔ÷ÿÞÄAv÷˜Ïªç–G{=rñ‘¯u}”…ìž—Ú˜ÓòžÑ÷§Ï¿_E÷ñ“³"ÌðÉ™X~>RèäxFÊ;ë†úÖm€@Âò­Ù==•Þ!ñ®tUw”µÝë¯E ÂDÂåÒ-'w—§„ÜK|&ðæ]²-ÎH[›Ûló™Ë0ªMyžLãa®Ž,•->O·¯–yI›y¨¿¯²AùÌ´Mñ dßr&û½Ci²Ø“Å>À,ö²¾GÈb/ëK{²Ø“Åž,öd±'‹=YìÉbO{²Ø“Åž,öd±'›7cÆ£Kü}cé5ÃØa>¶Ê{Ä7˜Ùõ†Á±šý šÙ¿éaÛƒYu6Û¹zk½‚Œm+Іê\{fì0`;0v`ì°;=XcÐÃ}Þd;‘lìàpTÞ%¦¹–1.{×z™C¼rì®㆞kKuvО¸>H¾­¯Án}sëk‰[_¯Ûú:ÂÖçê[Ÿo}ιõ±ðÖÇ›[Óm{ÜÔãÚ'¥<…MõíŒÆýõ¥ß?L§|;>hS«£áï”ø;&¼w¢bu&å~qþšçìºd.FϴйŒòL…?õ\)” —{?ó4ÍÖó3w§°3üÌñ3ÇÏ|³{ìû‚=؃=؃=؃ý^±Ÿ:^϶ç_ežeÂ3è‹”Z¹r»f‹ sý(:Ïמ/÷OÝ^u³ÿ2á:…N¡Sè:…N¡Sè:…N¡Sè:µqŠ|8Ì»k:…Na§Ð)t B§Ð)t B§Ð)t B§Ð)t B§Ð)t B§¥Sœ¡yŒeh®€h‡y¦úðÆ>#òæbö©v ®¦¾ÚÆíŠ=wKÒ²éžçë‘çwmâT[(¯X€X€X€X€X€X€X€X€X€X€X€X€X€X€X€X<‹þ}ø÷á߇N…ªSOåümå½¼—÷ò^ÞË{y/ïå½¼—÷ò^ÞË{W}ïæë¼“w÷Î 1>Á–wòNÞÉ;×xç¯"'¬M›§iR]ýÏŤ‰~þøßo?þøo“P¶Î{9Õù¢MÞùôTšÿ‹Ûß×›|×›ÜQO®û¢Ü~ùRËï-‹ud4\¨hþ}Ù×ûe+WÖ³O5™ÛlÖOá±§…÷åßûh³”°K®6Ö|Á…r˜B?èËDÛgæ.Ol·tçK‹úžOtµßæ*óÿ÷kKß;Ó!9ãà¿ÏN§ù÷ä×N½£÷Èv'ëM§Ô“zåõ³žWÓ];-.ÓXÌ•ë9°N„/äIø0ºØwº¯ÏWpÈgnªocrû?IëXrƒñó:þ˜Ýwn2¦¡YÿHˆùjŸÜDOij´:·f¼H!_3²| ÷[Ëÿnô9Ÿ‘ÞýþI½ÖVžêšªõ¹úÕu]Ø:&_ù»t>fi^¯s¦q½Æ©Kû™û5Í)ï̬ý­ê¨í÷¥ð·îØã »³ÕÇØÿ¶¹7ö8Ç×ûû\û¯Þ[Ö¥‘£gT,Ö.ÆÞÎîVc»®¬Ž«^Yûãðqãó[7V¨žá0?‰gØëî¹»×ìzƒÞ 7è z³¦ÞèzÞñ™ç—õç÷íÞò˗爛÷›çºkÆ'­*yû·¦B¶>Y£è^ÖôÔ–õ¤Ú²u‹“µ’OÊpƒ·ìÈÔÈzí—UÅ^¾ê÷ö,Š“ñbdéó±Òw£ÍQ‹²Ú—/t›ÒAN]îK¸övÏô(nþ.·5rËñ’P{ò™ºç™MO/\ ÜÊôìY"\—ä±gå{G³¶T­œÅoOª¦õ 7¢0sÝ̺ýµ>sÿ3÷´U#÷9IԹϮoTÖ;½ßòiž‘l—³.†±×›¶Òº[¾JÛ ^Äßgñ÷#άÛb…i&t¡sÿ#ÎÜïÛ»Îâ,µú–ØRÜsütêÖ]þѧܶËw2ò¤ïX§ïëÄSû»ÞùTß‘ÔÏ[ÚwÜÝGßÑ\s¶:‹ÅžÀ‚¾£ynY?§é>ÑwDö3Þqß¡>äv¶W.¦¸<ž™1îd;ÅØrùØ’þð³zvŒ~¨Ï}Geé”6æøfÔ³ ìè!ü¥/8ð×}w)¼M&®i»ß=:ˆä¼]ªw–µÓð°£™Î&¬WWŠz®gÖU¥/™ ~f§úLnTÔ1™®Åôó´îo#rïoòçÏíÉÎ/8³]­Ço¯µúT1ÒÒ{O§÷ËE3ËÎùz¦É—(ç‹°ƒÙsÏšSÔyü{38YK–9Åí?n¢¨çî71>L}¥Žl¦îKK¦#ã.ƒÀésžˆjFþ‰s”ªã‰¨ÀìŸÄ¾˜qv¸ðž~«a_tÎN ¢÷è=Øcs°9ýØG/ð×|æb>=K¬Á¬Á¬Á¬Á¬Á¬Á¬Áz/XÇ3"¼D؃+Ñoô¬Á¬[X§/J€û¼…lè7ú Ö` Ö` Ö` Ö` Ö` Ö` Ö` Ö` Ö` Ö` Ö` Ö`½È‡!}Q|èwù1¤ó¬è9zÖ` Ö` Ö` Ö` Ö` Ö` Ö` Ö` Ö` Ö` Ö` Ö` Ö` Ö“ã(~ k…^£×` Ö` Ö` Ö` ÖË7•OÄ\ú6¾+çTbãb'"VU(9§²Ïå/’ï_œÇèËþ½4ÏTn¹Q6?‘m‰ke?‡pòNÀ¼Á¼Á¼Á¼Á¼Á{=¼cðF¿÷­ßU>\0GÇÁ¼Á{{xçà~ƒ7x¿oU¿»ÚRâöÐ}ðoðoðoðïXä†wô¼Áû•¾ˆ¥Ç¹š¦~*×½´¾÷íÕæù…ðÛ-檖w.—wÜÞÞ¥U¾Ô¸UŸ¹¯‡ñ}­üwc+Ç©ÆÕù;ÌÜo&ñIø§â7ÚbŸzhülåÿÖ×yL†Ê/÷2³ÎõyÔwºë³,ä‹ÏmŸ_'#~¿Ø6ðï§×3]‰Åu8¡ €7xƒ7xƒ7xƒ7xƒ7xƒ7x¯Œ71ˆÙÖ` Ö` Ö` Ö` Ö` Ö` Ö` Ö` Ö` Ö` Ö` Ö` Ö`½Û¸å¶·\‹XØÙçã–¯¹¾xϼSÌ»ç^›unH‹½ö_£ø'–Ç«x¶<Óâž½à\K÷ÜÍ[ÎÜÛ=‰UÛóãgŽaÛÇW|û, Fº[cH]»RýíÈÉVû•ØÞ®«~—¢ÿ^c´Gõv@çM²EÑÂ6h+Í ljT'o©žkƒŽSÓn¾¾¦ÛÀ²œ÷^cŸš÷––—Dð>S?û0ÌŠÇúÙ§K‡ï¾{èRöµP—®úµ8¨t™=V—ªù6§·.ÙN{ä1ºU/–Ƕ“§m`"ôÇöó½60—§H.Ã\ǹŽìÀ¸w SÜa.×çÇ0“x)ëgšçI=Y:†q}Í[Ç0st¬˜9†Éü½_ñ´~mhŒíì§éWÍ{Íäiê»oì[û>¤ÛÕ8fhì¤Úc§K>}ìdL;“~›üò±Sùž±“zbìÙqíܱSyzbìt­çNsú»j¡õ…c§—a’ŽaŒå¾fŽaŠr9§Q4o £’aж<Õ3ŒaÌ3Zö\÷cø,ç4WË85Ïu s>Œa®çÈK±tܽTö‘>jt~ŸŽÌïË÷êž>êÔî£Ì½wóûKýNÓÖM¾oÏ9úޝvßaäSIO—ñ:Gݵ®^õ®ß¡§æïA®Ó¹Öëq=¦ß“¹¶÷Ëq‚ºÔuéµ]ßë|¨³Yðnê\öp¯÷'{oZ/ŽWKÞnm\g©X1¯‡vSšxKlòU¯ºOÞ5RvÅ>³;§w¦îm"3v:q‡GÕ¿ûîr)d %+¶þÜÉígwŸÝNòÝçÜS܉ϘUµ—r<e—Oøéá'¶8¥x*fzØ¡MæŠLxˆ”u[¾óèá û5Ã~)‘QÛ–ír:ž ]O:mD?°g‚_ø Ô– p„›aÇßoJlY8¶ÌâPé~'+U¯Ýêð ?Ø.×¥=‘‘zxÂ~Í´_Ê÷دÀìWlÛD"²ì¹,z‚-ÜÂÕl™ËBá"ˆ»¢–s´}ËæyA¯zòEÈ<àKú”§û[èSßGl9Z¬ï—í“-pè„'lÚÌõÊÒÊÅœññ¶Ö'óûyc¯=üÂׇmY"øó›!ްc3ì˜XÃŽmÄ)kûçõÚ/Á+<}Ø)·ß%Ëyc~Ïü>è~$óã.ú‘ÏGbÏWFqVþ»Q»$ø…¯ö'¹óº±ÀÞèOèO‚߃LY_ v^"Φ6{[nO~Ê>VÊÚÊG×VrᯚÜûÁ q„ ›¹ç…ý ×'¬>`YÇ—"™°ß…ý Ã~‰l"­}.l×ó>ø >ø»Ž"8†³}Á ‘I·³jõîÙð„M›yV²øbÓö³ßÕá®ÙóŠ'žoàŽuJÖ)ƒïS´_—§OÙYŸ"¸…« í{pGŸBŸüÞWéçöô)úSÈ=/-J2Á—¢Ã/|­Ø¯Ä¿âR¬ÁtÆceþâñ}nvø hŒ<Âö ŸqæüøŒã3~°<Ç…}vl?ûÈ‚WxÚЀ7æ÷Ìï73¿gl>I‚[¸ È'IÌíÙÿzÎ')&6ïîì˜àž64&à11câM¬³ßµ¿¼!û\›ÌRllß‹~„~D‰3ªÄEØ_¾¿ðµrŸ’ üÝüD‰}±ò1Oسö,cßq“ùB² söÇp𙉞°i ÎLdœÉßµO˜àÎ>4NS¢”¾´üÂx¦-ØÓ§kÓb±&í\*â*LÙÛ§5NËöö#Æi/‰„ŸÒvöe´È-:%v~Jیǧ7è»Äþ }J,öŽ'ï÷l+k™Û8ÛÊZækbVã·´ßü)þKAŸmà;6Ó?âŒþ.ýø#ÎæoÒ?ÚØy}æ÷ô#rÎH<Ä0ý/ ±ì8‹…Ÿš0&ÃÙH¿’æ×?Âvn†ËDNklÜþæý‚_ø ` Óù3ɼ"aÇfúb*9¶,Àý˜BäŸÈ…|0·põáù&ŠZÎk¬ݧäøöïöüeŽOÿGû-æûª©ç6s3ç yоüe§=Dýg\×ÁrΑsOurô–ãaÇøðklYÐçÇ¥ýJ:k`z¢?¸Æž­jÏÜ^Z$r¿ÆmЛYcÓ^r~Üù´bÓö/Np W̹×Ãk–¬YnbœŒ/ÿ>còküø7‡t„;úú”àû”‚X¤»Ý ëð _+ö+qç\X)Ö`²žu—‚X¤/‰E qìYx¾b™÷í®ŠŒû(©à®>¼'VŠVºƒt€'lÙ¿×?¥]Ç ÃY ±HFx¦ͰiÚŸ‘` sCk˜å[Öá®6´/6Â]ÐöM‹y^hk˜B6Ö0Wˆ×§‰ ³ëx}š1›ˆ×§7&Ô}~ödö³O³/óЦ© ܽ;nŸf_æe9â"ßÖ°iÙ´TäBL:óÇ©¹á¢ûœîpø¼„;|—ð]ÚÄ89bl·þ°ûbÁøÃjÿλñqľØÓ±­]~7ìØ¾b[[^á)P¥ް_ óð1¿ß÷šeÌ—ÿÈ–­Ñ(‘'™¾ä=9YSÙgÎCÖQÖ…µÏqUýF,ÖÃÔ2Þ˜“0'ÙÄú¼bMk·¾е­MøJ(ò>íKœr6bwvLð Oó!N7v>‚11ýHâç€ô#;‹Ï^°6¿¹8º¼ÑÐlâl#çµ·C·}ŒúÛ´3rœÕ'[âóJ¶âNF=«­Åß#{V>oËRûÞ†w]sÝè‘ÍèHnížkª–+ûÏwõOüïæàhÞažktO_j}¬>s¯ ®^•¾öÓbX½×Ùڌݮôö«þ}Ýû©÷;¦¯ з)~áÒïŠ_“à®6–ƒd€;æOÌŸ61Љ߷Ë$~á+ðùSLü¾—ä ±ý7ö,À³°±—¢}(o×&å"±÷ÂÙŠ6Muæ6¹¸6”‹¤‡'lÚL›¦ü¹clZ€6ÍÍY"ëÂõý¾~Ц ŽálE¿ôÂÇPjú§\øÖ¤›6ÀÓîlš^ÁºéÄs‚Gxùàú܉ äíðŸÂ ÿ©·ævJèÓvyÛr WóŸàÿ)ü§61OÂ'w¿þS1~¹Ÿ?Åâ5âk’Kÿ)ü§ðŸÂ ›†ÿÔ.ü§Š‰~øOá?…ÿö ÿ)ü§ðŸÚÞÞLáûlÚÎÖÑ:üÂW þS#¶£æ3 ¾¯U3½q§¿²þ–úyæûÚDÔ?î¬]½³ŽïØ*„íu¶8›°Gûßîb~Ó«öÝu\c¾êÆÆåÄØüù†mñ”9ë»ëšÝM|ß­Ý}wáNWÊ#yÔ<Œð ¿›¶ÉÙÎÖ3Ÿã©±Éï®ã«ýx‹Î™ÃX¬?|y3¯‡j»ï®wHþÚB÷7ɱœë)?‡mù ¾»Ž¡ôµ…-ÉÁÖß]ïÐúÚ¢}V|—óŸw×1”6[ú³›áÒÅÞU¢í¥Â+žÐï¼»Þ¡ñ«wd““ê ¿Ÿís·Æo*xvcÆb¢R>ÖÔ.}dÞ]ÇWsYtbìˆ8;»ç2{uIO,¡­qùŒÝ…Ëýpol üª>4ÞÈ8>[I§®S|}¢ Îs^åçmh®ó†<ÔGÉã|^“úÇdBþBœ-/:ç/רç»y-Ú±ÙvÍëÜöšlÈ÷)þky§Ýf3úžlÃ{SÏx¼»žÈ#|”8’GŒq ·ûÌ1p”ý{ͱw”uGˆñu”YAs©„ŽºÝÇÉyÈ©ÚñºðõÜ@.‹£ä‚€[¸ r bêúRâë¾k›üîz†æ‡¨Äœ/ÛùúÒ»ë*·åA¸ÝZ›·û˜†ï©k‡i;ŽÙ.¸Ízlò»ëùê¹O"8V¢Ï&®÷Œ‰³¤Í¾»žkÙãLäÂ(ÛqÑ6瓉½©¤­¯«Ô)Äu|1^Üü:~,æ/ª‡ÛdC>.Ïž”ö93·Y€yÜß]÷µÆL©ˆCù¨Î‰÷!Øí˜iz†4fgÏv5îÚè5ê ¯ïãUõÇ&Û¯Ïø‰Ëºf;‰I ïc~®RÏÛª~³Ïp€ý}gÈi(ýêÑxÝ»Oéõ %Æ,~»µuÃRøB¤¢-¨sHã& tW㦴Ófß]O8],|4NÕ kŠ!pª6´~øŠøïzãçœ§Ž›ô†öyúÚ«ùŦršml'û ¥Ø·ùÐ>^wxþüùõ­ñüJ[ýî3±êÅ9v–äëxw=án×7';ˆ}ºd/þÝu1—¹öýÒ.üj’žó±kÔ3´6¼åY϶á­äÎz&T,æGK¿»î¯Þ+Š:ù²ÕLž·|Öý™öü×@œEÝl›N:ë±?ûÖêß]ÏyÝbl¨¸3G*Ř+ëgÛ^l¨gÇХ׋M¡U;_@+žÛõ ­ÍŠÜ—»n³ï®ç«yUbìã8¼’û¬“»)šž'~7>w}y‰×¨gHãêÒ?ãPûGkÔ=ÄñV¹ářȕ©:9ËÊN~w=Co•=oôª6|„sGð¼}ž§ôÉZø ¤ãyº¿zBî³L­_¹óø(wõüõ·Ÿ?~üå_þñï?ÿøû?/¿ÿò×_’2*£êò.Üþ~þøßo?þø¯¹ôßo?¼]üõoÿwù¾=à&àõZ—(ªKÕB›Ïkl¿¿’›ßܼ¦þÞüÛÿ.ºýæz»vÖÓî•ï4eèÞâ|ïév=5ƒË8_¦Üþ.bq¯yæ ȳ©‡½W›kº–åÚ#Or{f|{Îåâ1™ŠÃ9·¿Ë=:êÔoà^󎆃 3݃ƒQ®¯6¥½÷ìp(çá`Kbþν\ñT}Hîq¸«‹ã½sï%©e”uiáïêò=£.F'Týì¡gšºT2ö<Ó|VÜI=Qõ5%¸5©yæ¹~f_3{ŸÃ¡Ò±üþ^Õs¯Ñ÷â«¿­],¶—>eÒƒíí»2Ÿ€íˆžÄªƒíùñ3ǰíã+¾}–#݃­1ú®]©þvdŒd«ýJlo×Õ ¿Kѯ1Ò£z; ó§rØEO´AÓLmƒêäï-ÕsmÐqjÚÍ××tX–þ½Îö½×ا潥å%¼ÏÔÏ> ³â±~öé’Äá»ïÞº”}-Ô¥ë„~m*]fÏŸÕ%Ó;ÅÖ¾év5Ž;©öØé’O;™ Ó”±Ó9é·É/;•ï;©'ÆN‘×Î;•§'ÆN×zî4§¿3“áWކÆ0SÚ~k “|p ó`,÷5s S”Ë95²ÌèäEc˜¢-OõÌcóŒ–=×ý~'Ë9ÍÕ2Nͳß2†)Ÿ·EçóÈæ:>G6óŒ¥X:î^*ûH5:¿OGæ÷åƒ{uOuêôQ§žùý¥~§ië¦N߷眊 }ÇW»ï0ò©¤¿Î§Ëx£îZW¯‹jß»”ë¥cí1®Í߃\§+rÝ\gpý}™Çõ˜~?âzhÎ<Þ¿Ô ÕÕò³]§V:ÉÄêu½(­»ºŸÚ•x»‚oVú«ëöÿêûÛµ<+ùßv…ÿömˆ³zjVÛÍŽ@:pOµ*©ÉkV³e;{½s;I¹ó ‰ìÎÎŒ¥©ò5Üî|§~¶°ÓˆýZd¿dÖYýÚõ)e]Îú9À6m†MëxrcÓöw~¶q‚ûµÐ~‰“¿Ø¯}Ÿh‡«ížÈß„}ºðжé»&êdžŸG÷õkdýk¢îìR¶¸rö­g–ô)O÷)¶Ð§#œm#‚6mMSâ$/óüm\Îüø«i>ág%û•‰>&霨á[5ÓV¥™°UaÙªXDþ‰î#u?ŒÓáÎu"dŒñ„M›iÓö16mWÙÚ:ÜÂÕ†² ŽpÇ:%ë”Áû#‰Œñô)FùMÄxY‰µÊhFÆøÌgZƒ¯@}‘xžͰg"£¶l‡‘¿ðõa[6”Åu€#ìØ ;&Öİc;Š.)x…§@"&&Ëyc~Ïü>è~$óã.ú‘ÏJbÏ7”ÿnÔ. ~ákCLx£?¡? ~½8e}%Øy‰8£ÚÊÈšMØÇÜÂÕ‡ÖVrᯚÜûÁ q„ ›éGý ×'¬>`YÇ—"™°ý Ã~iŸñ¼å †ízÞ?Á·ñC:ÃÙŠ¾`…·YM‹~Iõøê'øê?}VRd¦í;›\m7딬Sn¦OÂéSvÖ§t²¿ÃÕFö½F¸£O¡OÙDìÖŒµ—`ý)äž—%™àKÑá¾VìWâŽ_q)Ö`:c‚1ž°g3×’q>{¶­Uù›ÖáÎ>¼/&Ö‹ïb$ ð„M[`Ó8¶ß3`Žá,Ð3­#ãøŒ,ÏqAÎöÝí# ^áiCsüÞ˜ß3¿ßÌüž}°ýù$ ná* Ÿ$=0·gÿë9Ÿ¤˜Ø¼»³c‚WxÚИx€7ÆÄŒ‰7±FÌ~×þò†ìsm2gH±±}/úú%ΨaùB:üÂ×Ê}J&ðwó%öÅÊÇa‚c8ûÐ8M‰RúÒò à ›¶`O?bœ¬M‹Å:˜´s©ˆ«0eo?bœÔ8-ØÛ§½$v~JÛÙ—Ñ"·è”ØIø)m3ŸÞ ïû3ô)±Ø;fœ¼ß³­¬enãl+k™¯‰YßÒ>×1¿ðèÙÖްc3ýø#ÎèïÒ?âlþ&ýø£×g~O?"çŒÄC Óÿ²ûÁ޳Xøñ© cbÁ1œô+Y`~ý#ÜaçfعLä´ÆÆíoÞ/ø…¯Ö0?“Ì+2Àvl¦/¦ò˜cËÜ)Dþ‰\èP1ÁSp W#v¬\aþŸ‰¢–sÇkA÷)9¾ý»=™ãÓÿÑq±ó}Õ‰¿TŽs„ ›¹Ž™s†MŒ˜MÄëÓ‹ ê>?{2ûŽÙ§Ù—yhÓÔîÞ·O³/ó²q‘okØ´€lZ*r!&ùãÔÜpÑ}Nw¸ |Þ?¾Kø.mbœ±?¶[؈}±`üaµçÝø8b_ìéØÖ.¿vl_±­-¯ð¨¿ÒGد…yø˜ßï{Í2fžü>ÌOØ´6­ >üî|” âÁr¤VŠ1:g}²ØX xÖ%é7Ä>">®;öGŠñs b¬,wv®çïø\þ#[¶F?¢Dždú’÷äÇUõ±XSËxcNœdëóŠ5­ÝúJ(Ö¶6á+¡Èkø´/qÊÙˆÝÙ1Á+FýmÚ9Îjûåo>«-s±%>¯d+îd´Ñ³ÚZìñ=²gåó¶,µïmx×5×ÞÙŒŽäÖî¹ö¡j¹²/ñ|WÿÄÿnŽææ¹F÷ô¥ÖÇê3÷ºàêUéKa?-†Õ{­àÀØíJo¿êßÑ×=±z¿cúº}›"á.ýþ§ø5 nájc9H¸cþÄüió§˜ø}»ÌAÒá¾Èe=6Љß÷’$¶ÿÆžx6ããR´åíÚ¤\$ö^8[Ѧ©ÎÜ&׆r‘ôð„M›iÓ”?wŒM Ц¹9K$b]¸¾¿Ó×Ú4Á1œ­è—^øJMÿ”´×rZ6m€§ÝÙ4½‚!yâÂm±“Å9hrïc;êŸFθaÞÒïÝ)1÷Ôb_NÎCÓŽMKÉ÷´?[$|¤°iá­£)±¾ïÚBìõÿ¡”à¾>°Ž&|›¹¥Ó“ò1Oس…¹|9ÿ¦n:ñ\‡à^>¸þwbFy{#ü§ðŸÂê­¹8w²ÏsØ gO6é?5ÀþSøOmbž„Oî~ý§bür?>ŠÅÿjÄ× %—þSøOá?…ÿ6 ÿ©]øOý ðŸ Ç*~)þSøOá?uç?Mô Å ÿ©#ì;oÀ¦íl­Ã/|ê?5ÂölÁøìÀq©&ùFH92û>—OÞÉ£Åÿ×ï ÇXÈ—_ Û>ÉåùˆµÔ¾¶,ò·L›È¶¬½Í¸;§’î#n–vûNR±šÖòeNg´m“ö³Õö3¯oç“3æÓØ”k]G‡…ûßÙ Þ½Õ“ý.o×Á­g˜zG_¿þöóÇ¿üëÏ?þýçÿçå÷_þúK\$yR]þÏåóÿÏÿûíçÏÿ5—þñû퇷‹¿þíÿ.·D$Cä©þ4ÿ·¿¯·^Ãuˆ+zÂo\‰”¸Oü&Ÿþ¼¡R$Ï?ãN®ïùÅà¸ä¾OcÔ¶XN§÷¿#Š–—óŶ-S²çž5§¨óø÷fà¶–,e‘…òæÏÔÓ›åQÏÝ_Ùûè…6ân]ûä]+Êúåü½¬¬-§Þ4ÎõÀ¾®Ûq}®µì×cxˆÉÌILx;Ÿ|`¢Ö]HÈí„ÑL.¦NîšÅî1GÑÎBL÷CºM,eŽ¢×.-]Úc´ ŽòIçèR,ÒmÝAZ‰Má)‹ jeì…|Mcö`ö›Å¾˜a):ö~obbÑ=Â_׿—al毹üÌf½,n“hεɛHr÷ÃÕ(önsä*ž+çÑô¹Cµ‰—Úßê—ÜÎ#àþáàšÝ‡ÿòoªiÿ´ø‡ø‡çÿ´ø‡ø‡ø‡ø‡ø‡ø‡ø‡ø‡ø‡ø?ÿ1üÓþ­O@ÿØø‡ÿɃ2ô;ÿ¡ó_ÆÉŽÍÐ>øS“_E+Ÿ{º0ÊI Ö` Ö`}¬C>3U†‘@š±ò„±R9!4ceø‡ÿ÷ñ¯àŸö¿ÿ.&þN}² tƒ¾a.ÿ/lÙµ"¨vH/úKßuO~ ¹`¹2à­êüµòŸ|2í&¹Uñ@—{‚ôÇÕETLˆ[˜Øx&&æíY…(—²ŽeV}_•û8]J…¦ëQØ=†èyЦ†yEŽùJµ¯ÞDg–ÿJ½aÂS1ݶMo4§ûÔiÛ eèvc®i·âxg˯7aÔ’Î5Ûü›û“cúª8ë'/×ÛõQÕrWÏs]‡ëJNÂŒõ˜Z׿®Â„mÌåïªç%Þ4ºgºz8}îm…èú´7¿£¦1.'E 7‘Ï5ÊFñ<Ù æ5…¹v{Õõö›«QµkmIM”Pwo™ùßE§ú·gm¯%6báï5‘Èݽ—Ûsʼ¶¾_¦ä÷Fõ{«çÙ{«Hº~Þµç™F5]]\é«‹“KÞ+wW—Ø>ï4P—øA]ì½eáï­¢FŸÇë{Iïë{Nûzªû®*é‚*U¿ÖMhi÷s¸.(Äô"¤I ;=¯KÕáR&öÿ|BÊ7R$ §HŠÛž/O5žû”VMª¤njR#=m»ZQb±aûKÇ+8†³¤{˼7óö«JroËúx¦ͰiI'5(6-,› ~r¯®mŒÚ²·põ[&ÇÏ¥·cÒƒy.w»‰ ¿v4ånôü=ER-%râÓ´Ò§<××Â.åb‡xJZdÁ1œ}hœ,çü‰ÈR>æ ›6sî/N`Óv:÷ÃÙ‡çþ±O-û£1ž°i3lš\WÁžígÞ/x…§lX&ö=ËqŽ‚·_zFÆÁGöësüŽŒMƪäñ\ûw(éw¤½Ý½ï¥_8‚]=âœå(cþ#îEe/÷ˆë§GY<Ò~ëq÷+û\¼‹HúxGC>Þ·º'·ç§Ú§ÙøO›q¸²ÙéÍÿé¹'k}Ò_¤_µóI¾–ýÏuÿ»wG·º¨Ìž¶‰(SË8ŒáiQ‰O÷×ôA±0¶½.÷ç’(^ï|C""«ñ|ÃÔ5©ìõkêk¯›ße[c ¶ç‹Ç0_öï%c³¤3æVbü)æàüÎJŒí èf ìÆüñ¶qNóúX¥ÁÖé42TŸ¹Ç!•rdö}…8⨄qG)]d„XÈ—ˆ#—iû”øšú`îiE ú1' ³{N*æÁQ;¢í¯¾?‹E{ús­¯ï ‹uä1o8êø/.Z§H†#€ôÍëtZë|.ŽñV©ºÞS0Ë{³?>3zH|ž>¦yµÌKúû‡ºøâ¨qS÷ìž•û™öŠP•®%¾cö8Ñtà5Q'ãP4GÆ~Ë"!‚áþ0$’0‘„Á¬Á¬Á¬Á¬Á¬Á¬Á¬Á¬Á¬Á¬Á¬Á¬Á¬Á¬Á¬Á¬9ëÃYÎúpÖ‡³>œõ¡ýqÖ‡³>œõá¬g}8ë³±±_*Æz.æO$âDÆ`Ì90ä,ëj¬a‚5Xƒ5Xƒ5Xƒ5Xƒ5Xƒ5Xƒ5Xƒ5Xƒ5Xƒ5Xƒ5Xƒ5Xƒ5Xƒ5Xƒ5Xƒ5Xƒ5Xƒ5X„µög,‚Ã[·Ï ßè÷Û°vg>…·8ë4†ù Öi?Fyâó(nñLëχnù #ç—Ÿ”g²ªßŸjeœä¿àLØŽÓ†¢ÀÚ4òlKú ú úãõ3Îgb7·$ϯ"/¼M_DYV]ýÏŤŠ~þøßo?þøo“T¾ÎÝäS·gFçúïHÛÏë…ø®ú;îüî|Ÿ·>V륭·¦ìP)ëÕôð I¹­ðUÍûDÓjêªë⾫Ây,Û¡{ž¯Gž/›Ÿ y‘D¢[•×”Z¹k™žpo!ºdwÍ Ñìw»Ã³§=ꌫ×SËOÖÙWëÇó\ôtŸ:Ž&ôž»º¥uô/•Öf2º]»^ëï }CßËOs½êQÓú¾(©¯¹ß»kæoStRŒ#[ ó|ӻ߿ž«/×ÛoNçúùNžÈtù}FëõØéƒò=öØn>5°’®[ÐÖzî¾A²¶2µ&s™¸7}ƒµ~BŽI–$µYý¯,U*&¾ì³3ûwj-¢ì¹Ï°ÃÍn~­*ípÙ6ù%î‘Ø»ß}ÙßE•Dü-Œàg?º£ûqo‘ç¡zWÚnOMÛÐöZ"F-iËÿžwlîÉÆ¡G×{F¼Y\LñžìèÓ4…SýY6OõHöjG—&ŽâXÑ~ãŠYWjîӿɧ?o¨Éóϸ“ë{~©fßÛ+Æ4n±œNµ-)ç‹m[¦dÏ=kNQçñï©ZKÊK}¦,ÒŸïeem9õ¦q¾_ γû¥`-|#ª¥J·léütuÛ‡·oych=yÙØMûŽºtœ½Àï}Åeã 1ÍfœÛ(í²ýš¾íŸ«F†=œ)P3ÔÊØ ùâ¢#/؃=Øoûb†½/>p–©èØûbÚy¦ÍŒ_æäŠíRvúÚqŒ¶y~béÈRˆqÕÊyÍ;šqó»óGˆº/öYí”Æ)hƵ¹~®}\bïòé]ÅsåbÆÜ!ñ+‡ø‡ÿkJlMÂÿþøO¼kíŸöÿðÿðÿðÿðÿðÿðÿðÿðÿðÿðÿðÿðÿ;à?†Ú¿ÝëOàûÿðß (ÿ´ÿ¥üK`ô€~`ü“3‡œ9` Ö` Ö` Ö` Ö` Ö` Ö` Ö`ML‚I1 Jûb,_‹T+­EZ®X‹>ÈžDìsæÍÕ݉•çìKcÏòºÞ±ÍîRÙ -’|(ðæ™×«1ýpßU~uì ³k'o/+üS¯ÝK^^ì;ÊN¬ºG Ùºu±:Ó¦`YÇ—"™à†ý Ã~¹ïŠŽ/¶ë9ÛUvrÊ`Ãö?¤Ã1œ­dË¡C‹Ôƒw{ö#7;|˜´‡'ì>ãÌùñÇgü€ùAñMÚg~Pü’¶5Ç/ñIb~¿ñù=û`ûóIÜÂU@>Iz`nÏþ×scâ˜1ñ.ÇÄ1câMމcÆÄŒ‰7ºFÌ~×þò†ìsm2gH±±}/úú%ΨaùB:üÂ×Ê}J&ðwóeÿN:þÅ1^fÏl¿=ÛX¾lÆùzË1œ|fb€'lÚ‚3gòwí&8†³Ó”(¥/-¿°ž°i öô#ÆiÁÚ´X¬ƒI;—Џ Söö#ÆiAÓ²½ýˆqÚKb'á§´}-r‹N‰„ŸÒööøG¸c†ý™MŒ“YÏÜ÷ÙVÖ2·q¶•µÌçíYŠßÒ®cð§ø/}¶u€#ìØ‚³­œÑßçÙVÎæoól«f~Ïü~cãa§ëÄC Óÿ²ûÁ޳Xøñ© cbÁ1œô+Y`~ý#ÜaçfعLä´ÆÆíoÞ/ø…¯Ö0?“Ì+2Àvl¦/¦ò˜cËÜ)Dþ‰\èP1ÁSp Wžÿg¢¨åܱÀ@Ð}JŽoÿnÏ_æøô´?Ñb¾¯:ñ—Êqްa3×1sÎíË_vÚCÔÆep,ç y9÷T'Go9Îvlæùq‘Ó[èùqi¿’ÎØ”œ{‚c8[Éž¹½´Hä~Û6 µ^9Â6mÁùqçÓŠMÛW¼8Á-\m0ç^w¬Y²f¹‰³®øòï3&¿Æ“qHG¸£O¡O ¾O)ˆEºÛ½°¿ðµb¿wÎ…•b ¦3&ã {6s-9>âØ³ð|Å2ïÛ];öѲà®>¼'&Ö‰[±­Fx–-ð{-ðSÚu RÁ1œ‹d„'lÚ ›¦ý Ö07´†YN°enájCûb#Ümß´˜ç…¶†)dc s…x}šØ0»Ž×§‰³‰x}zc1aBÝçgOfß1û4û2mššÀÝ»ãöiöe^–#.ÂÇ?H›–Š\ˆIgþ857\„oÿææý#ÜỄïÒ&ÆÉûc»õ‡Ø ÆVûwÞ#öÅžŽmíò»aÇöÛÚò Oú+ p„ýZ˜‡ùý¾×,cæùÁïà ð„M›aÓ âÃïÎG© üæÖ(‹Ågm’¾Cì%âçºcŸ¤_× ÆÂÊrgçûw>IñŽÏæ?²ekô#JäJ¦/yOÞCÖUö™÷µ”mæ=Ä_‚9É×èëZ»õ—P¬omÂ_B‘Ûðiâ”ó»³c‚WxÚ˜qº±3Œ‰éG\||Öè÷£½`m~s±tx£¡ÙÄùFÎlo'Žn!úõ·içä8¯N>¶Äç–lÅžŒ6z^[‹=¾Gö¬|Þ–¥ö½ ïºæºÑ;#›Ñ‘ÜÚ=×>T-Wö%žïêŸøßÍÁѼÃ<×èž¾ÔúX}æ^\½*})ì§Å°z¯³µ»]éíWý;úº'öRï{L_ oS$|Ã¥ïÿ¿&Á-\m,ÉwÌŸ˜?mbþÃo—yH:üÂWàó§˜~/ÉCbûoìY€çac1>.EûPÞ®MÊGbï…³mšêÌmrqm(IOØ´™6Mù³ÇØ´mš›³D"Þ…ëû;}ý MÃÙJ6Íí{F"GœØ›hòe”yÚMÓ+ø’+.ܶ‹1YÜ™ƒ&÷>¶£þiäæ-}q¬e%æžZìËÉyhצ¥äŒ{ÚŸ->RØ´ðÖÑ”Xßwm!öúÿÐ?Jð _XG>ˆÍÜÒéIù˜'ìÙÂ|¾œÓG×Å"sí.Ÿ‘Ç—³ÏŸÝ ˆ;1£<¾¾TøRáKõÖ\O ýÛ.Ïd[nájc¾TÜáK…/Õ&æLøçî×—*ÆGw¾Tñýqñ¥ÂžáK…/¾TøRáKÕïKULô9À— _*|©°gøRáK…/Õööf ß7`Óv¶ŽÖá¾õ¥á {†/¾TøR}̆ebL-×ÎâùR…6&+} ÛáºY‡c8ûນ;« |gZ㲞X7›yf'ïÈ„M k\Ö]3³m¯±kSÎíŽ÷ÀÙ$?c)GfßgmR#ÿ_ë6Ré~,äK„ß±å*Û+Æë½º¦üß"ÿÅRè›ò~ûsõmP¯ü¼s+“¹÷þٛ꺶/’ö}xkþ‘m€Ëåv?bnô£î?aÿnávë¹¼Žš +ø±“êœ;k6SÖkR?ÎÜtù¤3¾µÖñÎ:¾c¡¶×ÙâlžB컋ñpŸoþ»ë¸ÆüÆ­¯–ãºç¶ÅSæ8ï®chv7ñm|·v÷Ýu„S8])áQsøÁ/ünÚ&gÞwcó69cG¹&ñî:¾Úï³èœQ‹ÅúS1Á÷3óºq¨¶ûîz‡äß+t“˹žòsØ–_Ù»ëš-.ÚgOw9>~wC?•Þ×ûPëÃï®7üÂïšüêñ›Poø £ýn…ßTðìÆŒSæ@¥Wîb^›ú:5c©w×ñÕ\˜,".Ëî¹ÌÅ^]Ò{fk\>cwár?\ÆÇ§Âg+éÔuНOtÐyÎu‡gx WõQr=‚×d£~P™¿g΋ιÌ5êùn^‹v̶]ó:·½&òq‹„ŸbÞi·ÙŒ¾'óqøw¹N±F=7kø(ñ%ûn÷™{à(±û;ê(yìàr?q´‚æR u%º¥óSµã½€5ê¹|GÉ·päzÄÔµ¦Ä×}×6ùÝõ Í·X‰ù_¶óµ¦w×3Tn˃p»µþ6o÷1 ßS×Óv¬³]p›õØäw×3ÄuDÑGm~1c¦n»}w=áöýÜ&kÄ[â6óñ){t±°ÛùŽûÖ5êÚ9ÙXÌßÍm`øw×}­±Têõàaïg°Û±Ôõ|õ:r"8VB·£¼Æ;#÷Ùèw×^ßÇ«êS·9^Ÿ93 ëší$>…¾ÿºJ=Cl«úÍ~ÅEö÷u<"§¡ô«Gãuï~§kÔ^áõybñÛ­ùÉ”b­-ã¦2€:‡4ñ~w5N;möÝõ„Óõç8GãTí ÏNÕ†Ö…_‘ãAo<–ÁÔqÓ»ëùîöªEι©œf;Ó¾FÝáž?i«ß}Z½8Ö’œ<ï®'ÜÂíZãædÃkRÏøX¼»î¡ñ¼å\iÏòœnh_>êä_VcEÉÜ—åy~wÝC‹Q"ç’åÎçÆï®gH±Ä™ÔÍΛDß$ÏS·ÎT¯QÏyÝb¼¨¸3v.…zÖ³ŸPüm{ñ¢ž9S ÏîîÝ£Ü?Æ+¹M7ÆmßyÕÎóÒŠß·F=C³Ç"§í®íñ»ëùj^•˜ÈX;jb~ÄÈã°ÛñÓõ Å×&>Úåö2QTuõF!bíçb­µ˜X×|'{FKxδg$m}±ãqóõ )?±ökx‡ËQüšgÕö lÖËó|ç+ÇÁ1 {1pþ K¹/¢û8¯ä>ëäíŠ&æßÖ;òÅêËI½F=CW—þ‡òÑY£îk·2?Þléq¹Á\Ô™XÇHÚ<®R§ÇÈ[ä±»öªD)}Ù,·ÏŽ‘Ëžz•Ý=Â"xÞ>ÏSÆQZøí¤ãù®î¿þöóÇ¿üëÏ?þýçÿçå÷_þúKRª(©.ÿçòÇíÿèçÿýöóçÿšKÿøýöÃÛÅ_ÿö—ïÛnB_nÏo/ºŠb*RÜ^tíµSý›è&ôõöÝåv_TÖ÷–Æ©ïV‘/SroýÛZÀêµV¾82×¢¤i]Ç<\}×u®82"å5–†ï¡ßW˜_êßÊ8ûÕsâšcÃOÅSb¯›gÛßÒ·÷¥™ýþâKdy¬~7ñýòyCÏrEg–oûìL÷<£#¯ÓÛJŽ´snÔþ_É'žWá™ÞÇM’kj]ÍwSÙ[m\Ùß ²ó|¬fËv®ß'íCîÆƒ‘m¯3ìÄTùn·]M\Q¾ÿ³@ׯxÚ„MË'Ú´r{–hϲϵ ùþ¥m$ËDœ¨¾^}7·]<;üŽŽ7Ò~Î?V9¢í?Ší?¹¶ûŽbd2ïnÖÙ²i÷U4Î)_ îyAÑÑgÞë×=ë弫××+…By¦Œµïʼéûv=õw¦DÙ›ËÅÿ]Å›s¯^A¾7ÓL-ÆuÏýýÔˆõI)&®i\¬5&çñòÔZÆÂµ¡X±Ö¶ÕrÊ÷QLotOÈíï­e•õ5›iG‹h};úê½¥ë÷ð3Õ [€¤ í]”i¼^`¼<>àq§ØÂ{¸¬#’säiü@qß²lõŽ¢]÷Ö6›]jÊ­ÎU÷u—ºŽÊ¬vKêÌ¥X3øI èx[DZ^Šq.pì~æâxÛ’aɆޏí¹Eâè¢;ÖX ®i3óÕ§íQŸ}JfþÍÛGiKlKks¿Á¶ÿðÿðÿðÿðÿðÿðÿðÿðÿðÿðÿðÿðÿðÿðÿóø>ô@:#™R·^?Q?ó®<º¯k#÷·3!‚ówëÑ•3.zdïóÑÜSÒF+c/|ŽFÀ5Xƒ5Xƒ5Xƒ5Xƒ5Xƒ5Xƒ5Xƒ5Xƒ5Xƒ5Xƒ5Xƒ5Xƒ5Xƒ5Xƒ5Xƒ5Xƒ5Xƒ5Xƒ5Xƒ5Xƒ5Xƒõ4¬EJ§a.e@ÏÑs°k°k°k°k°k°k°k°k°k°k°k°k°k°k°k°k°k°k°k°k°k°k°k°k°k°k°k°k°k°k°k°k°k°k°k°k°k°k°k°k°k°ë=bÝÅæùÐsô¬Á¬Á¬Á¬Á¬Á¬Á¬Á¬Á¬Á¬Á¬Á¬Á¬Á¬Á¬Á¬Á¬Á¬Á¬Á¬Á¬Á¬Áz«XbœöccîM¾ê{õí©«KlïqÏ(ìß±­ÓÉÖÓb™| ýÛo’¢Æ)Iíg\ÿÝûÿµ]O}¶õ2ÏvïKzÞ‘ûz¿Dö\È×ýÌë÷.’Õ|&/–Õê¡Ã¢ú»2‡‚éïSô"±õ›ZÇÓ–ë°.¨õ ÔƒzPêA=¨õ8T=~ýíçùןüûÏ?þþÏËï¿üõ•ÆiY]þÏåÛÿÑÏÿûíçÏÿ5—þñû퇷‹¿Þn¾=Lߪn¥¸UP_ëE„èT¦+¹Õ¿R[NËJzº¯y~ß{Í{šëÿ»¡k×ÈËê®™÷™wE‘¨ƒYÔ1ß™ß|×V°Xü²L`ZC¥±tk7n}E·×^ºk#ck"S׬’¬.ƒë35¨îû†Ö¹B1iÍÌ`£GÖÍ&ÖÑ|×`)ðÍÎó1xôîæ}Âö4uÕuqß™Æ8*ºçùzäù)j9+›¥kŽ*Œs{-²vI^Këg%öÙÕ5û}"דäšU*®¥þ¹­wô\ÛÞë½èZê¯UíÉrjþßµÞ:Ý[¢·NîÍÒ‘w—²˜{BÃ[wú/gSÛ×c¡ê;måÐBg‡3ûŒ‹Ëy oÏM}{ª{Ö®'ök£õ>ÙßäízUïÉüدwÜ”FJŒšâQÓådG¦Ë£ 3’¹^ý#>ϣ߸bžéþ6]+QD™Z2ðšU.ßVŸ‡¾ŸŠû ¼¯ã²nIçT1áw*,}WèûËô}JÙ“¾O*ê5Ï ³GEö÷Ýr½úR<ø­,_Éôßn­ÐwS^Ö¯}3Ýbß|=}NÖÃõÍѺ}³J¶Ñ7/-Ÿè›»cyúæýèÓ[JT»‚>Í©^Ãzýì~·OÅ™s¿Ïíõº½­#íûÅvo<{ÏüŠ{Ac*õèžN÷ôÊg„ŒÕy)g@8oÖ` Ö` Ö` Ö` Ö` Ö` Ö` Ö` Ö` Ö` Ö` Ö` Ö` Ö` Ö` Ö` Ö` Ö` Ö` ÖÛÂÚù§1ïÄÞz•o°¶A?a#¯ó[=‹zÚØ]îÞ\¯WèNv‡C‹ø8.Z.üoó™1ÑfÇx™!×KâX÷}.‰ÁÕí¦’O·Ûâ”z7üåuLS/‡ÅÔµúÌý»*½ImÛÕusÌÕ·Ñ«ÄßçêÐÄD‰ëg5g´Å0õ\wcÍÈ3cò¸s³ê?õD4ý샔ÏK™r‚¶VۢюhG¢E¯mCð/ðÒŽ WÅìs%×áîµÜuc"ãrÆ´1ú-xxxx—ó‹ØôðDûxÙ/6'Ió,õk@&¯G…¼­Ã›¢=açà…ñí^à^à^àeʸÙÅ,†Ú ¼À ¼À ¼À ¼À ¼À ¼À ¼ø¬Aj÷:3[b¿÷ gœùålg 8óK;¢1®`¼/ð/œçeÌÍXÛ‡íƒxxéç…X¡Ä k°k°k°ë¬•8w"æùÐsô¬Á¬Á¬Á¬Á¬Á¬ÁúHsö§±^ëji®*ô½k°k°k°k°ëåéMgäé]óŽ|è9zÖ` Ö` Ö` Ö` Ö` Ö` Ö` Ö` Ö` Ö` Ö` Ö` Ö` Ö`$Ö.Wî§ð.|Üôýk°k°k°k°k°k°k°k°k°k°k°k°k°k°k°k°k°k°k°k°k°k°k°k°k°k°k°k°k°k°k°k°k°k°k°k°k°k°k°k°k°k°k°k°k°k°k°k°Äú&’Š· ýF¿Á¬Á¬Á¬Á¬Á¬Á¬ƒµªë,æùÐsô¬Á¬Á¬Á¬Á¬Á¬Á¬Á¬Á¬Á¬Á¬Á¬Á¬Á¬Á¬Á¬Á¬Á¬Á¬Á¬Á¬Á¬Á¬Á¬Á¬Áš.·r“'I'b׿­~?„ýÄúšï*¼L}εìq$8»É˜kgQÏÒ>ÏÞ›Ë{,Ùy>ž³e7\â³êo+“¾Ý“Êz˜¿cû÷ìÂþÛwœ|] WSånôb†\U}gÊ“|µÛÞݧÓÛg|µºûvcx®°Ó Ëgë¹¥•S·Ûä”ú7<ÞîѪ®Ÿ¾Ôu®>sÿ®JRÛ†u]×<óíÚջѯÄßçꟄHýï«çöw±oo•ÞÊÿ­Ý“§zïÜúOÔãQ;ÖµB¾øÜn_NFÚØ m,²m+šß®à^à^à^à%P^:s¬A~’:nckÆÖ´1l¼À ¼À ¼À ¼ÀËayqó§LÄ„'Ú¼À ¼À ¼À ¼À ¼ÀËxɼOURÀí^fñ¢m»q8Ám^à^à^à^àå¼Lñ—K¥™}_aëåäÑâÿ«?ÿ$ýézýèBÓŸÂbéù»«Kñ‚½?eçËE]é×bÿ¾ÂëК>~Ïʽä\ÜCÝyÕyD‰é£³ˆBwž’]Õö zŸÓ[§Ç'áOª­©ýldˆ…_êµísÚ´ÕXø»fþZõ"ÞYÖçe›»ÃðÑׯ¿ýüñã/ÿúóÿùÇßÿyùý—¿þç*ŽªËÿ¹üaþÿùã¿ýüùã¿æÒ?~¿ýðvñ׿ýßåöðÈ€b^|ªµ¯c!ÿòáßN-Eòü3º%úÞY‰(ï*—›¾\¯¾€ÉÎËÍà;¨‡é (¯)SlD”Ô‘꺳­ºPÛ×êL‰¸îWu"H'1¨q't;(Ew×ävÐk:üÉVmÁÕôAëÔAê&‚\d/,² ¸ÅÒàAcšÍ¨—v¼f°!_°¥ôõ]$óV±w¿];™·8˜Ø’—àD'k°k°k°k°k°k°k°k°k°k°k°k°k°k°k°k°k°k°k°k°k°k°k°k°k°k°k°k°ý\z>óNbÐWO×6U#ÀÙNŸ;¿¾X¶³‡Ð Ø–­HOàÑ÷þÅϾìß ƒäõñÞà Þà Þà Þà Þà Þà Þà Þà Þà Þà Þà Þà Þà Þà½*ÞnßZ‰dÆ6qŽKÈ è>xƒ7xƒ7xƒ7xƒ÷ñÞL2ØwéEjyuŸ_oº‰"u±hçZšP¶/anuÿÅâpB‡Ñá@tXÙçºO—“)®}Ï«weè5z^£×è5z^£×Û×kÎYrάÁ¬Ázõ³ÚÉÏhËsà:Ÿ­î9ÌS¡i-_æÆvÚâg?[e~üæô4sc ÷ÜD`ª}Nq§+yì±pÿ;]éÕu;^©du¨òbÛv}ýúÛÏ?þò¯?ÿø÷ŸüýŸ—ßùë/EÅÕÕÿ\þ¸ýÿüñ¿ß~þüñ_sé¿ß~w»øëßþïrã)ºÉÝž©n%º½çz­‹‘¡¸]»ÆöÚM–üöÝê{Mý½¹Õ›ëíÞèö›ëíÚYO»÷bÇãÑ­>_¦ä÷™nõ<›kö^m®éúyמgšzTïZX—êw®.—u¹ñU¾¸.¦ý^÷»–žÜ~wéŶÖëJ]­Z«¬º^ë°ND[> ÛÚi÷ù€êÚ½ÜÎ=LÛšjÛ¥b¤-wú‹î;‡ìÙ&ú·tbÿ¦ÞÜÇ)aoCéßôçödŸZ'°ã¿ÞϹû³–÷jÞíöÆÜÜ=·:áæïú~¬èx…§žb¿þ2ÊW1ƒ/·>’{»V}º1déÇ Cü`·fØ-±&†Ý ÈnÉuG-üÑ6ôÛe¹…«@mW?Ø®™¶+Çvg»b?¶ª>ÝÌž¥xh³r8úˆÍJÅW.ÆÌå|΂¶cBÚ0½òZtÖÞ£läÜËztHóõ˜þc·sö˜~$øy{¼±~#TV`Ã63wí$nŸµc|oÇ ìØb;f1MJìØ®æò‚WxÚÐ|~€7æôÌéƒîGD{ Î׌¾ä¹¾¤Ã-\}°?Ém‘yþ–rGŸBŸ|ŸRX™bú”àú”ÄëKsÌ•¤=W´M‚_øúÐ:Kîùª>KÛÞÊÇH]U­ß[¯‡?Êéþë”Çe²°6ÿ¨6â]¥ÀF¬b#(Ëmýý6Æq‘ÆF3ŽÃFcÃGÆq 6‚q6â0%u÷Ä Óyt,, V-ÒÛÌEÀ´ÝD‰öqg±Dîb#r_‹;üÂW ‡òFxžʹg9ޝAÛ²Ô;jTEyDZ‡¶,Çé5x;–ãðú´sÁY÷äP¤uAÔúœKDbO%lçpk9Á†u¸…« KáŽÃ­n ~~Ÿùdyô);\¯üÂWÀóüž°g3çú%û/»Ÿï—ìÁlbÎ_²ó’y)ðŦí'¨U‡[¸Úؼ€;æýÌû7(‘ä_aµ*ÄÞ~*Æ]™Ð©A÷H¶Ý€‰š¾…¾eƒÉuv0Q‘`gõ~%ë.‰ø;ós‘»€‰Š$;¯J†ßÅÎ&æø_01óïìK–0F{MÀDÆhûÝ'ÃYÀûdÙ®ÏóköÈ6qž_ol?Œ„áØ²µçü$ 'a8 ñc$ Ç~‘0œù}hýH,âI…â›t,}¨/YÓ=ÓW^2åíP¦ÄøÇÙ¦¢þmfÇBÕï²ó{ûÌÆÇEûg·æ‡‚ÇQ^ËïU¼SÆÓœCÓ\«ÚDlwªû—<ÙP›Œ&¶Åhe(ôd”“¬Á¬Á¬Á¬Á¬Á¬Á¬Á¬Á¬Á¬Á¬Á¬Á¬Á¬Á¬Á¬Á¬Á¬‡µ8[1 óØÇÌz›?¢óÅ-<Þ}ÎgZ¾±ßâ—ý{®?»ô3ìðÞà Þà Þà Þà Þà Þà Þà=ë¼tÚÉà þè;xƒ7xƒ7xƒ7xƒ7xƒ7xƒ÷«æ›2¶JÑ“k.Ð}ðoðoðoðïáÞäЪÆU_jªÏÜcJ92û>?ÝÉ£ÅÿWP{9+ù3Ðaþq½Ð~ϩґ´wú%×¢Î;¾|ŒZãtÒ¼E‰ùJ!bÒ¦5'/rlõLÕuGwª¿ïÐ×wê°êÄù/:9-ÓNnxô½F¯Ñkô½F¯Ñëê5g.9s Ö` Ö`½ËsÛ±íû´‹sÛá¬]wøoðoðoðoðoðoðoðoðoðRãf4ñù×¥!œÐÀ¼Á¼Á¼7u–.Ð}ðoðoðoðïáÍ”Ayö<7:Œïñz^£×è5z^ïmî2ÆùùåL"gÁ¬Á¬Á¬Á¬Á¬Á¬Á¬Á¬Á¬Á¬Á¬Á¬Á¬Á¬Á¬Á¬Á¬Áú…X+ïK$æùÐsô¬Á¬Á¬Á¬Á¬Á¬Á¬Á¬Á¬Á¬Á¬Á¬Á¬Á¬Á¬Á¬Á¬ÁúëØæ²Ê'bîrI¤#ØO_¦|¼:K¬‹ËÆàÊÆ&îÙ\¼ˆ­ö¸Ð~À¼Á¼Á¼Á¼Á¼Á¼Á¼Á¼Á¼Á¼Á¼Á¼Á¼Á¼WÅÛí[«Z†&ŸhlsGÁºÞà Þà Þà Þà½S¼ÉG¼±|ıÕC7g)fæ¦E‡Ñá=æÔF¯Ñkô½F¯Ñkô½P¯9gÉ9K°k°ëÝžÕ²qV;ÀõkÁxƒ7xƒ7xƒ7xƒ7xƒ7xƒ7xƒ7xƒ7xƒ7xà,”ÛÿÌÅ>hbÏFé¶!œÐÀ¼Á¼Á¼7wž.Ð}ðoðoðoðïáÍ”žçF‡Ñá=ž£B¯Ñkô½F¯Ñkôzzn_¯õ7í?Ï—[ͽ’«Å¼X¹2åÏóUç~ÿÃ\KìyG£—FþêwÙÌù²ÜSÉê3í ¶r.m÷Z*¿‘ä¾®N/Ÿ­wU¯¾Ï¹ëRO´Ø/JŽbïHëûs¦ÕýŸ·Ô7ömh´ÞÅÌzG¢Ž…ýt{he]§ÖÉ;ë"§q}ï®9}g_É©²ü¥VfÛ¯5<—øt¶?Þ1Ÿï®ã+ùt²º6©„¯aú€K[oWes\¦bLâ|-ã :üîz¿ÚÇbœPZnsÑf“‰68ß0×™Ðs%Úïƒü‡è_N»îcß]Ϲ-í}Ñι}w=ß1†Òb¿ÉõGù{ìô`ï¼®QÏyµm~÷¼¾³žkŒ‘Ë 68¿U'¯Q÷WòœØz¦BwóÎwJ묛æ;󅤳Vû©º‡f¯3¿V¾k{ýîz¾º'½ -ÖÃÕ^…½Û¯±à3»½‚²Ãë»ëJ?œ¿®£­W­Q÷PÖ$¯ç‡oM¬{Èþ¾È‹¼È‹¼È‹¼È‹¼È»½øCp€¼È{p²0þØ#/ò"/òr®úѹ°]÷œ xƒ7xsn/x¼_Õ—OðG^äE^äE^äE^äE^äE^äEÞÀ÷Ày‘y‘—}§Gë–`»î:1xƒ7xƒ÷Öð~E_nc]8ÿ[äE^äE^äE^äE^äÝÀ™(8@^äEÞ%{S`¼È‹¼ÈËÞÔ£µM°]w-¼Á¼ß7ø¼¦ïuX‰ˆ¼È‹¼È‹¼ÈûhMÄ#oäE^äE^äÝm÷hßì‘y‘yçËÛ]sÜóžÑš¸²¦Þà ÞàýF¼ýíçùןüûÏ?þþÏËï¿üõ—$S‰®.ÿçòÇíÿèçÿýöóçÿšKÿøýöÃÛÅ_o7ß LB¿›çï¿ýßUןM¹Úë׺D7¡¢¢] 5ï9ªì<'²%©+R‰×ÔCÔ­9MkLòHèÏwM¥7·WdyÍÑÅ¡ßW\]êß6¹³¬~=ÌKÁCb¯›g;¾}§oïK3ûýÅ—Èò_ýnâûåó†žåŠÎlî/ûìL÷<£#¯kK•©×ÏØÉ%žSá˜vòˆgÃòL­£ù®ÁRà›çcðèÝÍû„­iêªëâ¾32q8TtÏóõÈó]¨¾ë÷dÖ¼uÍþ}w-»¿W–½áäìœË©WÙ¼¾k¢ï¡ÝÑîÖjw?åÚÈoÙ”äA?›ö·÷¾j¼ ]Óf…¨ÏëéNÏø5‹Ë £×Ë©9j£Z·¿õWýq²£U;ÒŒÏòÕþß »£¸þ;¿¶¿S¹ÿ»2ŧûçE·RDõß§kÏûlÉ:÷VpÛ}ßù}U¬Êä6¯:Û‘óMö³{fÞ#ß©]×Öwñ²ûb{_dë­20Â$ë>ßÈ©úñ˜ƒÕW:\¾“ñï)û)ßÅûßÑ×§Ù^ã6‹v»fÛ;}-k{!Ø©jš@ѧpd¡»´ÚNT·÷©mú—stoÕÞ³ù6ñÑØõšN»êêY÷«§yTÜ-ŸêD,aœÄÒÃÅ^·ÿç#ÓÖ¡iÜä¥Ö\LŽ´Üw–™-ÿDa,ýiæ·dâYÖ b*êeÞ‘G÷uläým&÷ïèÊ/å3[F Cz_n{»uö´.¿w![\x7”Ca@ß»¸Ç;Òw5sé^­Œ½/.:ònûxžÎbê¶ÉÕɪeU‹ƒ«c…MëÄìm²Ö¿§\1ì²²±«™5×ìgu͹WÈk±ço’…Mu—ö³Ö5‹ýSØêð±ªCõî¢ÝN[ó–“ýÎڻ꾸-WæìS\·ëê÷z]®]‘¼&Qçšsÿ—ײv_;¥¦ )ïj5Ú†z®UïÉöÕ†ÞnŸ²a[ÔÂVÍÔYìÓGí“ã«áíRßcÖ1Z×ú~7—ëÄ®Ãu8\ϼ×p ×p ×p ×p ×p ×p ×p ×p ×p}X®ãÚç ®i×p ×[ãú¥{ˆÖ'É}6þ 3®Í áð5Ã× ¿ÖÍû÷iocñí{£mòåøùÓà;3â—4àÇ5ÅßOÚÜIc }l¿$çË5ÕlÖ˜"?¸Ï—=Ëê>[מ¯ ØÆkŒ×8ÀÙ€CŸ p¡zìgã{;ã¾·9°6׬5Â5\Ã5\Ã5\Ã5\Ã5\Ã5\Ã5\Ã5\Ã5\Ã5\Ã5\Ã5\Ã5\Ã5\Ã5\¯î£»4¾Ý«|tƒ÷Ç“1æŸõ+—Ož1.:gŽtŽâþU:²Ì>§ôöïÎæioç\Ú²$ñ26y""k7 o#]îi7cëÃÖ¼¯´¶4ÁåAÝRžéXä<‹‡2öÚTÁ.k®ûÛÙ-ó·RþÿK<­\Ëú³üz\¢«/Ýï !›)¥Ík¡„ŒeÊ}¾ŠT­—í7}àþºÇôåĦ˜<¨‹lõ¬¤Î6G¾N®[3xÌîª;ÏÔ=Ïl…ò:Ù]XT—B†÷nàîw.Í`õÛ“0SF/•ïúM–ð̹Î|ºûFÕFà?ôœæ>;ô¨Ì¡Hó×<#Ù.gwuw™4Ëöµêå?嵊3÷÷κϹ{îDΚûlw5‹³´•ÔÔ±ËaðC»tÆQ‹O ëÒÎQ‹ Cº§2èb§&Û)ìÃRûÐ7lUñ„L½&£Ž}†³Ëí•*©G‹æÑÚf;«¾w£Ç/›Å×ýo¿7×LÖ4uýªêŒhU&µ[“¼˜k_þó·²ßÅ·ûUY7j®î1Õþö²¹Ñªùî|©/ŸŸ\ÇëJŸ¼&»ptºùf&jËš¤mê2»hµ¥‘òÝ»ÓÚb4ït9ÂE¯“Ï\¤š=b› ÃS eÖ’¹Ãh•å.ýáÕÊZ;‹—‹kzA—Ø>Cæ}´(™øÅ‰æ¹²FÓÛ`Ó#Ø ƒKž-¥Q_ªßÉÅè¨gÄäzJq-[²hm{÷ªd"©Ë³:£$^{‘Þ,Õ£gä3#ª©}Í«å^b‹êó«ú®hb¿•‚ž,÷3‹²ÚËû±…Ùý#Ü(›°.[ÜÙüâפõ™uXóÿ—)¹_Ÿ½/÷#B•…9"T"}íQF„.ÄÅœ5‡dÿ£Âj3ª-@²RÏŸæµÕ1½»¾Ô2TŸ¹ïUS)GfßW´·Ôq½i,äK:suýÚŠÃïépB6¬¨•Ü}^„ r£ØÌ~º¿Õò-ö­èØjÜE8‹fpwÖllè§êoÇa¼_ît§ldrö?·®£‘Ɔg¾;웺ºç&¢ÞZ¤VŒ:£“³­ÿºµ¹¼]ÉWïÈ$U“vŒãzrI:Gõÿê«þ?¶ësÑÉg~+ÿ¾d#Ïs¿W3å9Sþî¾k¨“{à÷×Sû½Ž¡ÏÞgDþ»¹¿o×3úLòp‡Ÿ.RîÁ%§=Cv‚åA°œºà¯¼+†N£‹L[ò¦Kü0*¸¨ÄB¶]E%V3ìEô7ƒøÀ‘ ±Ï` –` –` –` –` –` –` –` –` –¬-³¶ÌÚ26,Á,Á,Á,Á,Á,Á,Á,Á,{‚€$y ësùö¶Fö`ö`OßÉ8„óUœ¯Âþb±`‰ýÝ·ý}VoŸ Höé`d‘iUã}(çÁĽñ[\mq1 L¤²o¥ÌÄ«½½÷ÚS²ïûØqfhˆx^œÌ£……ØZœÚÍ%d*úc„f6Îa·c„º¸ŸÕϤhOQàIÍÞ©‘¹Æ"1©y™]zë)»è$jÕ÷WÛÆ†"8霒$š`Ñ/6šQq{\zûûj£¥›0¨cEÛߨøño#5ü]èì<­¨÷™^gVq2«hðßuo5çN%†ŠVóÎ’RzK>á7¦Ml¥äçÇ¿Q§yÏL¾×+‹ë=±Nݸ¸króµ1]Êú®eïyWù†gV«ÏpM¡P(”@ÆEEO_ö .¡r³•R壅Õ¯xdüz^QÎû3¿æ==ÑžU‡îY‰èõGËC—ˆl+ϸ#°Û>uߤÜh&ìžÌ ºôYN«vÛµmw¯Ìnc¥[¯ö’…û* åÐ#r´²^hŸá·ùÔ÷ƒå,õ8~`ù,3±ç–Ó°T"»zIKŽèdXÂ÷,ÑË-a×c#°\ˆeOÛËè¥]ƒË ãtÕÆlèo°Ë °ŒÀñ!ŽÝ,µqm#±“3qL†´mì$Xîs\)×yGþK°\ ˬ3ÇËÕõ2èýq'û”½qQÏÕŽ= ùª£ÏRÞ­.ŵGØŽï¿{!_…}ñàœ]¹c¿ù{|CÂÏ Žæü¶`4¶qgÝlÑÛ­a×cpEgÁl«½Ö㚊õ…ƒ%X‚%X‚%X‚åF°dÌÉxlÁl_‡­=' ¶èín°ð]]컊^ÒæÁlÁlÁlÁvl³ÎY°EoÁlÁ6lñÓÆO›¶` ¶` ¶`ûfl#öpÐÛ°À½[°[°Ý9¶ZÄ[ôlÁlÁvëØ²Ž0 Ó®ÿ,zI›[°[°[°[°[°[°[°[°Å/¿pü± ` ¶` ¶` ¶` ¶` ¶` ¶` ¶` ¶` ¶` ¶` ¶` ¶` ¶` ¶` ¶` ¶` ¶` ¶` ¶` ¶` ¶øã7Žß86lÁlÁlÁlÁlÁlÁlÁlÁlÁlÁlÁlÁlÁlÁlÁlÁlÁlÁlÁlÁlÁ¿qüÆñÇ&€-Ø‚-Ø‚-Ø‚-Ø‚-Ø‚-Ø‚-Ø‚-Ø‚-Ø6ØÞ¾K"°]EoÍõl± ` ¶` ¶` ¶` ¶` ¶` ¶` ¶` ¶` ¶` ¶`‹ß8~ãØ°[°[°[°Ý$¶ýlÀ½[°[°[°[°[°[°Ûc›Úgƒ-z ¶` ¶` ¶` ¶` ¶` ¶` ¶` ¶` ¶øã7ŽM[°[°[°[°[°ý¶Ø¢·‰ÑZ‚-z ¶` ¶` ¶` ¶o÷m‹‹õŸÛ7èm¶Ø°[°[°=,¶·ß'Ø¢·` ¶` ¶` ¶ °µã°Eoñߖ߸Á6w˜—öæïØ?¯z¶¶¾ãö3޼Õµk]ªÿ f…­s\_=#óתÿñ¾²öA¯äÂgBý¢¯_ûùãÇ_þõçÿþó¿ÿóòû/ý%N¢²¬.ÿçò‡ùÿçÿýöóçÿšKÿøýöÃÛÅ_ÿö—ÑѹVLu{At#ûz­‹yqq»víµ›0ùí3ºUôšÖJbîÍ- ×Û½Ñí7×Ûµ³¿×½÷d*kî»UìË”¼ó^#ÓM¶³¹fïÕæš®Ÿwíy¦©K\Ì«ËEÔ¥úÝ‚º\nÄ”o¨Ë£gy*'>3þ®¹V·ßDQ]Œ’6Ï<×Ϭ¾ëÜkÞmîu¸VØæÓïu¿ëb{ÃöR×Çà`¾ëÅAÝãpIë†Q©»mªÐ¢±Ôm@'ÂX„q¹Øëöÿ|¤Ó‘ ×iÓ8§תa¾£0Æ(‡Uš,Â`vß9d”ƒ6ð±5hé<ÿÊCAú\ËÝ’Ç6^áÐÐlg¯s}ƒ7 hžQˆÌuh¥Ç{öÀEàáÞ_É?ó½Í@Åmrö}^=ÎÆfkk'r7ð)ëwÝ z´åHÕ›¬n³µÒÒÊn;é<úÓá¾zøŠ-¾éÞŠ¼iŸ(¾rš-¼ólÅ]Ysé¸ã)ø«­çK왨[5ΊîëÙÈü-ú,¡óÝ:te4ü5r õ=%}Êâ>%!%}Jp}JìûŽêÓõ1™Õ¡±þ¤Ã-\} ?ImâÆnlP.ç.hû&tá¡mÓ+÷-B¶øÑ|†>åùyŠÓ§ìožbù…¯Àç)=ä3c»õ¢Ä¶HvÏ<×Ïì«£Ñ'³ äp¸Úýç^£ïÅW[»Xl/7|ʤÛÛweþ¶±ê`[>~æ¶}|ÅßuÝ{±5vòlŸ¡úÛQlïéÅöÖ®Õ—Ý>ë¹w©-:]'Ø¢¹z[ÖýÍ’6Xª'Û ~q,ë¾û‘ž Éó¬ÞöÉcl³z{> ýÑOØ„tšMº÷‘M0÷ÞÙ„K½a°5uúÖ5sêlÚêXO—q¹›1‡öÛÒŸ¨s‹{%¸?Ù{Óû­î´È×Ûëvó¡ü ûÝéÄýnõæýîØÏ}‚ÚóÖŸ[Ûzjžywœ»Ï¹óÒÄÏ­ª9[9>Ç’\ÂM7±³¦8*fp¤ÅúH&¸ŠÅz‰ò¼ q„Íšé£ãp/°[ÁÙ-Ç[;,½WÓFô[Öáεg#\ÇÃY 6­Óñ„M›¹žéÚ {3ÛXÏt6-ž°&&¸…«¯gæ‚r9w¬g²ž¹‰=²ÌŸË£O ¨Oq{bZœ}ËÅ™Ërâ^‹å¾öÅà {6sÞ³Ž¹kߥ˜5Ì lY"ø‹;óý˜õ˧ì˜XÃŽmÄoiÊÞ²àž64Çàù=óû û‘Ì»èGØŸäbßÞŽ–ðFBt’ã'tbu¥å󪽯ä¨MÊñûøÚJ.üZ“{?˜!ްa3÷¼r‹+6,L¿°RøeŠdžWîSÁׇ혌QŽs„›¹×Uú¾Ø²ÆÃÙJöÌ­ÍD"¾p,Jßyýž°i Î"qf{_nájCû_#ܱfÉšå&ÎM¤œÛ]ŸÒá®6´6Â} }Jð}J!|éSöµÖá¾VìWâŽq)Ö`:c‚1ž°g3×’qV{Þ90·¯âJ$Îí=ZCÜÂÕ‡÷ÄÄ:ñ]œ„ž°e lYIœ„]ŸÃYÀq¬x¦-Ø‹Ež*lÚ>bYw¸…« î‹õpÇ&k˜›ˆý¢9g¼ëØ/šóÆ›ˆý¢7v¾8Ä1r-Ûu ë[tì—ްc b¿àÓ¿ÏØ/øño3öKÄüžùýý^ÉM½_¿WrR‡ã÷ªý;[s{rR¿fLlÏØaÇv6&¶¼ÂÓÆÆÄ=¼1&fL¼‰1qB¾ƒÝމrlö,X²±üô)ô)JÄneïqgÁ:üÂW ±Gxž-È¡Ãù‰ýÆHìp gÛ´˜ó/±iÚÇÕǦí4V¢àÎ>+1¹¿ÓŽMàiw6M¿Ùß5öraÓm±ÐùXÌAåZY6Á÷Up wÜY¬_Æã5×G%±[Úñà‰ñÚ‚øJŠ9è®ã+)柛Š}µfžéô¤|ÌölY^jüúÝwÎDß­mœ…‰¹¨ñéßÀZAl~ÖÐXCc 54ÖÐXCÛ̸lhí,Ÿ‘wdgkgéM­jŽô¥–¡úÌ=v©”#³ïsý¶“G‹ÿ¯u©t?òÙva>WÁØ^1^ïÕ5Ïv‘/c)ôM‰¼‘3õmP¯Ò~[š[™Ì½‡ˆå(Övñu ‘ÓØMvëâÝu|õz­Œ«>%vTºó6ùî:ž÷yeé1…‚·³…ÈÚÙ—vßršyÝØ$§KϽ»Þ!í Ýß…-î[wwëtÔ¸H»çW”_}~E,ƒ£Æp8Ú¾£œ;‚ßÅQüŽx.í(纎”î(ùÛŽ˜‡ë(y¬ã ídgŒÛw×s~^Gñ“‚[¸ýèø8o¯­4õ('ø–8ì\x769ëK½»ž!¶[1¶Ü|»Å§Ûß¾»žçá:J«Cðšy?¿]óúÎz†’‹5¿U;ØÈDQÔ=àü GÉ?°9NµðãžÂi¼áµ¨¥{ykÔ=à8ÏGŽ“¼{žÓ ÏuŸå9ÝÈü÷ ñ@ŽOã¼&Üû‹;¾É¥XßèØ¯UêùŽõ*·V•wøz®Rù²ÛùïVëù¦¸sG‰ÛvÄsÑœ×Ýùú–ðE2÷j޼FÝC<Vîà¬É”>úÝõ e^ì꺗5ê%m¸©û¯¿ýüñã/ÿúóÿùÇßÿyùý—¿þ¢²(ʪËÿ¹üqû?úùã¿ýüùã¿æÒ?~¿ýðvñ׿ý_a üg¤ëOõíÿŽTý‚ê6÷üH¼³~”ND|œ“ˆ«áò4év§nˆ±øSc’¸1Ï`ާN|žîû†bOHøÑN'ôl,¡&®Šåµ’ÑòoÞç°69½Lýš|^×ú¾&¶~"¶Ð epíÓè°‘ÃÄ‘I£méÀÓù×Ä‘zwœ¨æ}†7õÔuqßUq{’±Ÿzž¯Gž/çWY^½tÇÞ:þ=Ëë>°úN[9´áäã%¹8GMÿ–Öñ“œîeÉ}[ͱ×ÑåÑúžìoòv}ܘ'úèû´Ò¢ç‹Çz¾s=TP·Ç^¯¾1 sí&Òõö›«1'·ëÑ­ê×Ûw—ïú^sÍý.:Õ¿=kqïuøÞË Ö2¯{Ø/SòÎ{£ú÷:ª{âtF‰¢™åûý%ÕÛ,§Óûß1›/QÔyüûj*…WLÛQ®Þ¢^óœóÅÛ˜ÍÔ}i‰gêñéͼäè뜺^¥¾ºÏ¯ùÏÊôt}EÚ¦k6ížjˆF.zûu0c×¾ë¯Ð¹wŒ]Tùžö¡°«”ËyŸc—B}¶?ßÓØåRôŒWâécí˜v¶‹ñвœ›5–ó÷²R?¯g‡C¥Y¸{ÊŸd¯ãýù`öºßáö_ÝZk“kðÔ‰œ.¸æü”¿fd«bô+»?{º÷Ý¿{fÒs-î¼Çî%7׊žg§ä=z*Gˆð- GÈÏ^÷øÀÙ6ü|°i mZÞ‘ ›¶ÿ°¼;ƒ|GÍwTLÐ+ý‚³i¹Ï "ÏŒéRÐö¶¾lmñÀ7HÔ«™WtêØÈû-æÃ¢Mvå—òÞZòöÍgK°û ØGqVÆ\ô}£>ª` Ö`½ ¬åº×§0—2 çè9Xƒ5Xƒ5Xƒ5Xƒ5Xƒ5Xƒ5Xƒ5Xƒ5Xƒ5Xƒ5Xƒ5Xƒ5Xƒ5Xƒ5Xƒ5Xƒ5Xƒ5Xƒ5Xƒ5Xƒ5Xƒ5Xƒ5Xƒ5Xƒ5Xƒ5Xƒ5Xƒ5Xƒ5Xƒ5Xƒ5Xƒ5Xƒ5Xƒ5Xƒ5X¿>~jìsÿ$6ѳ1T],ÅBÄ¢ÿ` Õ5ã¥>#UÇrεY±.EÜç._£ø'>Î|ól™‡@ÏËõ™Û¸ô™‹Eªã§šëS°Ÿ«ÏÈŸ§çKxµÜKÚÊC½}• Ò3âZêñ¸–“eWµžTïsíʵ³“—­/—m“S 9®"¶mÑÎP='óתÿñβ¶7ƒ¹&àЛ³6Óª˜´Ö$¾Ñ2©Ð©N<[åÈí”&iUꋉ<ô{wùÌorf·ò‰ïR›x'ñï×QûþôÔ~ßÔwV¿µ‰|Ì3•¶Isí}æ&þ±i/ª'YKvŸªe4õòɪB¾ ýJvÀ´+.tûÔ¡G¶ÍTã­°Þ™¿VýÞq[áîMß’ü 9&¥.ê¿Í0 .D~ØPq—‹ÏG×äó”ø?µ×ÒÎÿÚß—%põ«¹|ô]ûª?3›Þ§úüªí—³g½¼ZβԇaOàlzû’iYÆxJ};l7©h;_ö÷#Ï…£'Ú•ä0mÿ_½/Ü:»g¹©¾ëãG‹”â}U´vst20ä”)äŽÖoÕöÅ~ÓôL •ŠÙa44;¼™„Ħ;5Ÿ×Û0¹ø®gQæÿ¯¢þ[¦M½$~Féf“Õ¬îÛ§<6ÿ§}iy“ábî5ïnRЧýÏuÿ»w›™ ²éÇ›O{Í3 t»g»ç]l½g‰E˜ =eR½£Í(³3Êd[3Êî»û¬ÙÛ¬{Þ¹táºê¹ŠN¯ß±ˆ™MØÙê=¤%ý² ›Ÿs²•Hl˜yKÚÝÄ9*?-|ç&ZK,Æ.± K¶¦WàüÎJ$+m[J:£ÀxÛ8o&IÝ»ôaŽM“#d›Ô:‰io¾ï—#äVßò5ð}ß Uö=z¦}” Ý{œ½|ŒR§µ~·f,[÷Ó›6?S6>ßá|ñ¬³Ë«1ëmgbNS隘ÙÞtD^ÆGg^åpk8Êk»0´¡Û`y{ïÚqæÚ™{nÒNzïú`Çw³Á{NcíÆ&”¯du¨ø.G6wK•OØÛ-T-þIÕ7{¼æÿÂ~ªzvmTÁýnì;[ó;ÕùÝØwöfv-×’Mû÷—eý·™åñý3ݾq‘Õ¿1Ï/¿úß/Ÿ×’Ë®ôÊ{m_+å³.=ï˜Yß!ùæÖ×|¦ %÷«*Öa.O(Ÿ÷üPKÊË&ç+ø:N­¯3ÓWá·bu¡ò›JÚ&»ñ›¾,ƒ>*S—5^(Ck d§ðΜ7²™®¡Ê}fµçïÁß‹üà~;øŒ¤ó<;]k]›*hò”Ëï}ÕõV}B“g"¾-¿ÚÔeq›ÐËt°º–N|Ÿµû²®f:cÚ¼"VÃÏ5ù’ŽHC&mlúê0ÈuÚ¹÷:ù$g/“CþMÓ··µK[Þ‚uW¿—ðR¬‡ÍQm_(í ›è·©±¼ž±„3²Õø”?zôa_ôäzGS–*Œó@á7àãﺑ÷¹0úiQ»qê§þNýÝïFÞ÷þÆ)Á¬¨–þžæQýÞqˆ¬:8ÙºÅ@ŸÚÍ ÖïñR=SYwøó²º5Í0ªœ6:nø®Î/ÃYÔ¡÷wK³rÇó±tå–ž±]å®þ¥]e·²›æÿ Yç•À‹á¶Ž9±]ʲæªrê9yž4G-”8jaW€Íñ¢Ç‰&NU˜ËTnh¢ºLUÌ8ªíï7v>Ã-=eÚïçe§Ûµø írN•chWPîR7;ÐRfÛ=çVçªûºCÑ­W]¸&ÌÙY}jGUêÌ¥í;ø¿Ýq]äõðäŽöÚÜ¿ë!,/Ö Dòà°º.8ê›㸣»IS‡±´Wïá4ç'½Î6snû׋Àá">—؇è Xj¡£þÓÏcêB\×n;—vÀÙÞ¹x–Ûê¯v?œ:þ“cÆ‚ËpNÄXD„è´S Û?ÛîÿÝñÊû_xÜ×7Ö“}ÁRoø„± ã¿ `ª¯3^‰:6ÈžP¨ÜJÚËKÛ‹òã¿Ecü£Žõºc»9ÿ_Ûñ–°O³Oks¿ÁyüÃ?üð¯,—¬«LÃÛžZoÆ€igmòâOoîylø–Óu…8Áúàd]ã^7åÔi_=eèï6\}µþ C'òŠ'òâXeS­¿ªÌ—ÆïHµáœO=ǹT¸§¹Žæ"³ Ø f6`Cœ¨ºÕRx6“]a_ÙžñÆVó¼±ÝïOì«E§|=g½œ¢Mˆ€£^ÙI©§öV.\Û©>xl¼uÍ¡„±¢'üÆÓó=üM>á7ÙΊ~Ís®Õɨî’r&pF¹œ½Wó3Ï¡P^Y°­ý%ÓØNl%…B¡ì½Ü¯òi¾^æ¡ÂGgùnárÈŠËwAcšÍHäVú]”Õ–˜:1:öÈs§ËÐF§zIN_ÄÖFjå²»OY,‚Ì]fî<_?x~+£Eæ?›dš®¡Sè:5ñD9º”n8€8€ƒÈ>f?w‡C§~}u‡áÐ)M»xp އCãÅyy¼lAhàààààààààààààŸÂÁE§iÕYF®¶ßg6JÐ.qHm1í£KezüÚîp«³nGÞÂ!hß\W‡)þ¹¢¾«ßòUç7¤¼œßÀú]~~™·÷œß@§8¿N¯SB§°Sè:ÅÙEt ÀÀa÷8pV áÀž8LÄÁãÛÝ3‰èíÀÀÀÀÀÀÀÀÀÀÀÀÀÀá(8Ö|ãiÍÁ¬Á¬Á¬Á¬Á¬Á¬Á¬Á¬Á¬Á¬Á¬Á¬Á¬Á¬Á¬Á¬Á¬Á¬Á¬Á¬Á¬Á¬Á¬Á¬Á¬Á¬Á¬Á¬Á¬Á¬Á¬Á¬Á¬Á¬Á¬Á¬Á¬Á¬Á¬Á¬Á¬Á¬Á¬Á¬Á¬Á¬Á¬Á¬Á¬Á¬Á¬Á¬Á¬Á¬Á¬Á¬Àzã´s¯ÉÑiî}*·gæ?›<ž®ñNÞÉ;®Ýž•hÿùÔ;;Ïê{þ.ÞùDŽá½¼³±ó—º¿“ly'ï伓wòNÞÉ;y'ïäk½³úmÒy¾ï´ß›¹Šùûéwš9eìçCîï±kO½sìùvŽûòwN]OÈ<Ö‡Y«Ñ¬IñNÞ¹Ê;²ÆØ÷|Æ Íºã3ïì®W‚-ï伓wòNÞÉ;y'ï伓wnå¿þöóÇ¿üëÏ?þýçÿçå÷_þú‹ÊJU—ÿsùãöôóÇÿ~ûùóÇÍ¥ü~ûáíâ¯û¿ËMå0ÍÄ2êù_ Q¦ÔTuï/„Lõ«†|&ÿÏkó“œêǚܾKŠŸÍŽ_j×/sÈ÷5h?Z£7Iý9ÉŸVùõ¨^ŸÚ‰u5ß5˜ œ³ó|,½»ÂëöŽÔ`Yùíµê÷ŽãØ®e¸{S‰®Kõ«cœ•gäÐ#r4m?«ïiö¾,g‘ý{è{õàû‘ûæ7îói¿æC`Ôck“(›bio0F·f¢¯µ½^}1/̵Ø^»Á›ß>£›Y»¦þ^ù»èö›ëíÚYO»÷r£¡¼5ãèV/SòÎ{£úÞêyò^CÉwýìú™÷Ö>UwÖ^'ÂÒ„erÞøºí©ß×ú‡4mrÏ`Y;dÏÎè’×ö ú\Ë]=?©{犋lžãN¶³×·–•r:h-NóŒÂþÛg¼ÌïÙ½‡Àý¿’æ{åHÑï>¯íÓ,ÚšÜYÖÒZ9÷,»Cž”öïÒZÀÜꆶm¨´òëûÓ4]Žá¬‡³¸ã0Ä]1“»Èr•Y2û®Ôr)¸ã ›6æ)}M Ò¦É]÷Òêl'úMëp gÚ´ž°i3ÇiyG&lZ86-üä^ëÅùp|&¸…«زÔÚ²\Œ±-K¹Û•}Óo¶m‘o7G²mº³Þâ0o8¶;ÕwÚbh?[œ¿Šwu–Õ§¶Ãj'µ¿Õ5V¹m“Sx˜«#Ke‹ÏÓmÆ«e^Òfêï«l2M™¢Æ/]Õ:R½Ïµ/×ÞNbÈÐ3\I_—ªnWÏuÕn­N7á¬34IÄûJ;TIGð{€AïðDgzÂèärò#30*ú¨˜Í½)¿ë–ÊÔœŸ+Ñ÷²r¸7þ^þÌÈn\R({,W;kÙŒÌQm›./~Þ[eþ°([÷ï—>]ØH;¸”vÖÿäsâøÊÙÚø 8$µ;ïÐ÷ÅFê‘…`^Ýß­QèïèïBµÏ/ëïîW¼Ë"^fl[š‹ꕚ½ þ÷Šg‚ÆTì>ij_š{KLu!_W½| ÷[Šc¯_°«ýÌ%Þ¬Á¬Á¬Á¬Á¬Á¬Á¬Á¬Á¬Á¬Á¬Á¬Á¬Á¬Á¬Á¬Á¬Á¬Á¬ÁzçXGb¡×è5Xƒ5Xƒ5Xƒ5X¯ŽuôA¬£`=5„ yVl/WMõ>*ìå9hzž?)oJÔÉ¡¬û¯Qo꽫z»¢à›zSoê½z›± û”×öXïnßu ìÀn-ìšP½Ø¡w`v`v`çê:eŒ—]ßË\ˆzSoêM½©7õ»Þ™ÍòÙ\Kà›zSïMÖ;‚oêM½ñCà ¬Á¬Á¬Á¬Á¬Á¬Á¬Á¬CÌ_¥Eþ¢Ð0ïȇž£ç` Ö` Ö` Ö` Ö` Ö` Ö‡ÁZٜӯ˜³sDßÞ‘šú‹\ËÚÊÔð×85÷¦oð›yBŽIþ5Φ瓶OÛk°k°k°ë ãâ91¯×ŽØ‘oWqÑs°k°k°kâ1™xÌœ‘¤ÞÔ›zSoêM½‰ÇL¬C°;â1£w`v`v`G‡õ×ß~þøñ—ýùÇ¿ÿüãïÿ¼üþË_QIªŠêò.Üþ~þøßo?þø¯¹ôßo?¼]üõoÿw9ýíÿŠˆ²—b‚z¸¿2N¹'ºé€6Êw­©RFb¡\µÎèDC9‰ &.¨‹n|és`rì˜Ôä.Èt_`˜N@–î;‡œb‚OˆeêM $£¶H¦ûî–uø£^ÝWØ¿c{ÿÉË™—O×v:4ó·AŒšÏ¯p®Å׎žØ KúlÛ’ë˜ÊºÎ­Á„¹/µ¥´ÏMÅõ¸ÐTá©ÛAŸÐ ô½@/Ð ô½@/Ž®Ú`Qøñ¾yv3ÆÖb<ŸMŸóäRÞÌÊe88‰g¤~ÎVÝ{©ë—í9Äè;UQ…ƒÃÀabç$®þ½ ¥JÄrE<°\eýÅP0ôÝÔßÉïÌß×s]š¿ãûéqÏìxôØÆÉ"œ/˜ñq¶;u–›m0…Ô©^úÓ±z’Z¹rkUb‹c¼ wé<_?x~kÙ3óŸÍRg÷Z&,}†N¡S/ЩεͥöÐ/Эw¦ö!µ³„Ô¦=¯ßž­í¯F”v+Êýí¾“×Ìg¿àØçú´Oؼõyé‘gE6a±ˆë ¤ËÅš›Ø_ªžÈFvg>¯FÔký}töOî¾kRߟ—¨ó»‹¹'õßæ=®˜÷™k…^PŠ…÷QY¢Ö¹ÜaÔ7º¼¥ò//¦ï4}¶+[h羚™-ÿ®‹:‰¿ÿ÷ z/J4¯­¼R÷žÖ]çÔÕi» ãaÆÃŒ‡×ëFË,i‡ËÛay¥ÒÅ<8ß®ìn·Ú¦jJÏa¨õü-”=ò“ˆ´­Gñ»p§+¢y§ 6—ªöêO°ä‰Ç¢ñXŠü>ìç×dï®o·êE»î­“3Ö“*·úVÝwdÌü^Áªú½žç…ø´÷¡8J]}¦í#Ò•àï?å÷­·–âYz:Òvh;£mG×mÅçýûêmò®Û«ïÄö³vZz'[åG£vâG3s¡›Ã]Ê»uì»§Y§ôÉ6ûgŽÊ¿4Äý»ìw8šó?Ø‚-Ø‚-Ø‚-Ø‚-Ø‚-Ø‚-Ø‚-Ø‚-Ø‚-Ø‚-Ø‚-Ø‚-Ø‚-Ø‚-Ø‚-Ø‚-Ø‚-Ø‚-Øn[ ¦è+Ø‚-؆€-þâø‹cÀlÁlÁlÁlÁlÁlŽ­ö±ôÁ½[°[°[°[°[°[°[°Û€°5qYspEgÁlÁlÁlÁlÁlñÇg›¶` ¶` ¶` ¶` ¶` ¶` ¶` ¶` ¶` ¶` ¶`<¶Y\5°EoÁlÁlÁlÁlÁlÁlÁlÁlÁlñÇo½Å&€-Ø‚-Ø®†­‰S‘Öl`Y̺Ÿè'1n±«` ¶` ¶` ¶` ¶` ¶`»%lbV‚-z ¶` ¶ÆVë$\•XÛ.ÚøåL,ËaüÀr¡^‚%X‚%X‚%X‚%X‚%X‚åæ± Þ·ZÍð«V#>Õ¢^æyt_ÇFÞoûiêûwtå—òÅEGÞØÖG{¾â¿vüÚY[°[°[°[°[°[°[°[°[°[°Û÷lÑ[â;€-g`° ` ¶` ¶` ¶` ¶Ç6ÓÙëĆ£íƒ-Ø‚-qÏñGo± ` ¶` ¶` ¶` ¶` ¶` ¶` ¶` ¶` ¶` ¶` ¶` ¶` ¶` ¶` ¶` ¶` ¶` ¶` ¶` ¶` ¶øã7ŽÞbžÃVƒ)ú ¶` ¶` ¶` ¶` ¶` ¶` ¶` ¶` ¶`û"lu¼q°EoÁlÁlÁlÁlÁlÁlÁlÁ6€Z9¸¢³` ¶øŠã+ŽM[°[°[°[°[°[°[°[°Û ØÆýþ `‹Þ‚-Ø‚íÊØj°EoÁlÁlÁlÁlÁlÁlÁlÁlÁlñÇo½[°[°[°Ûb«§ù(-z ¶` ¶` ¶` ¶` ¶` ¶` ¶` ¶+ckcÖëtöö»¤[ìØ‚-Ø‚-Ø‚-Ø‚-Ø‚-Ø‚-Ø‚->ãøŒ£·Ø°Ý¶6.Ø¢·` ¶` ¶Áb«Á½[°[°[°[°[°[°[°}1¶¹ÐÑ[°[°[°[°[°[°[°Û,u}vZþ ¦0Õâ¬ùƒ¿¼ƒ~Òi¿?·{—¹×Õ¥ú•§yFaÿŽ­ÏõÉûWÿï–¼é0ß­¿¯·O‡åíùúl1px”õ»[ú¥ì=å e.„ŽFö3GVÖâ5²†jÛùùùùùùùùïùõOpC>äC>äC>äC>ä{âÜ{~ȇ|ȇ|ȇ|È7 Ï@.°C>äC>äC>äC>äC>äC>äC>äC¾`äËú}GÀùùùùùù>*_lc'¤+Éuâ6Dø¨Ñùù6&߀Ÿø!_ò©eSb|Y´eøˆ<å° ÅyyyÂ’‡± ò!ò!ò!ò!ßñÖö ðÛå$k·È‡|ȇ|Óä‹>Ôï†è#‚ž!ò!ò!ò!ò!ò!ò!ò!ò!ò!ò!ò…%Ÿ7äC>äC>äC>äC¾·É7KüùùùOæ{;äC>äC>äC>äC>ä[E¾‰g}Áù>"Ÿ?äC>äC>äC>äC>äC>äC¾È71O ø!ò!ò!òT¾‰q&?†]Úɷȇ|ȇ|ȇ|ȇ|ï•Ïúž€òR> ~ȇ|ȇ|ȇ|ÁÉ—q6ùùùï­òéz?Nþ½Š\Zì>ø;úúõ·Ÿ?~üå_þñï?ÿøû?/¿ÿò×_t¢ó¢ºüŸË·ÿ£Ÿ?þ÷ÛÏŸ?þk.ýã÷Ûo½ÝlxßÝ^^(û¿ªXýÌ>/¯¨ïLS[ÝHTñ»?;ÝÊõVòºJ²¡ßg6óÛªê™}Æ š¼p&öúµ†ÕüÞ¾Ó·w¥™ýþâKd¡¬~7ñÝòyCÏrEgµ,¹}v¦{žÑ‘×Ô¥ªSäÕ¡J§îäÏ©0´Ïv*1$ËÔú™ï¶Ùy~ý½»yŸUñLÖs@ý§¾³á çù“šW!ÒØ;3ùtöƒx¦ý<»gW¦á-2õ4ñ8Ž&4ðËMÄ䦂Åí3ºÕUQ&ƒ\TÿŸžë¿[%é/׫/¹û»ì®ûß½;ºU[eÖȈwÅQÏû)O}Ðz›fY—ûNL•j½nLÕ½scÞÒÅv•MìÖ²ú÷[êÚºïn6¢ ¨î+ìß±½ÿäåÌË]_Ï;Ó™ïlºž/û÷Õfö4åv¯>[]uÝVY?·¹ÏqmG¢—n­·%8?…³ äç·1·XÍX6Œsš×Ã$ƒ­¾Ô2TŸ¹Ç!•rdö}…mó…Z6ÿ_í0K{9+ù¬Èû'ôÁÜSñêŠÔd†~¸ç¤¶M–öçéJûkúó›¦=}YÛåþîû¾°XGó†#1ûŸÄ“ü8›™ø•ÉWUwmu8÷ý¥Nko­2\lýOÓ0Ë{’NÃTí-­Ÿ§i^-ó’þþ¡.¾rŒO©çå~¦}§‰¯s¥kb%ķ#z€øþÿ!ÿÑ øøò諦>ßö³cwºõ“ã—¦.}ö¾ç¹±_*Æz…ëE~n¹õq_G`†abôZÏ ú°ú&°k°k°k°k°k°k°k°k°k°k°k°k°k°k°k°k°k°k°k°k°ÞÖÙƒ0ïÆ[œ'Ãü©ð<7¼Å3¸[>'Ê™Ëåg.å¹·ê÷§GÙ'=XpînHǃiC3Î2…vyÆäé‹@¦=!>QtÌ»½ãtºÛ’®µeÆIç½sÖqÒé‘Zëuµø^ío¯ÖZ&mKÚŒ´®õ}MdýDd—ÊàZƒNj9LÔ óüøäG;îïnÜÀgGsáÔ£7@Y9ÕÜ Íåö:uû[›`e—)¥'ÀSºn|'¬ÅÃØXŒá–Ö̃ ÑÊ”ºÿí|£·åeû½¢e„¯1¾zz€Dç{3}9)ÛØP~æï¢°Ÿv˜hèw¿ûΨKó;ÕùªÃVšÏ︾Þz·1}_õó˲¾~½©Bß?ïRØákVÿÆÈP~õ¿»y^t_²¨ÿú3Ũù+žcÂ|VÏ;‹kÙ´{«£ÏkOoÇëv×2ì]öp—?Xv>z·­ü2Rc‡Íþ^‰ð™~;øŒ¤ó¼¨ÓEÍ‘?4yÊå÷¾êz«>]yb»Ì•>9o#Fñ_¢kºóŒ¸=ëãµI>=å]±Žòˤ¦™p_Õð`MNb¸Hï’.=uÔÏ´Óµõn"g÷*t¦jßÿîën+çÓrp}%¾][)Àf\Åm»ö×òÉ÷©cèF³uunE?öA.k0že<Ëx–ñ,ãYƳŒgÏ2že<;y<Û³í£R5iß§©›nÏT_þZ÷;㙽“ïž}}· …÷\µÄí–»/~è,½uû¶‡às¶ô·Û,ŸõnŸ-ÚÊÑxdž½§Zõ}7Œü]:«)™j´Õ³ÞL4=ïoê”Øiœ¢¹©Úî<íÕ½ìÕNN5ÐÖŽÝÖà[‹­]ë™.Gw¯LªvŸ©žå¼ò—þIè©¶Y-µO$ì°m–~¯"“]Ñ^æ­ž‘ùkqäu¾y÷yÄÅbbýz§y‹@<”œø\''Žd’`US-˵°‰…S_Œ 0¿w¿©ÔGÜcži>'Wv+ß·*•î»Ô{nÉ„¹Ý÷^ìóÍÌ£zoT{Ž™ß*]{F9Œ<Æ\•ú;õdZOWMµ~8æxf¯ù†ü´ÚæC“³ÄÆš¯œ»Ö¼ÃY ò~Žäœ½ôlgæS¿7çå9Í/¿XŸXL²Dœ9ý§âw­û»¬sŽ4²ç¾Üû“™çx¥Û²J†Òöå´3”ݶ0+åÊÎe:Û縹ӓ°©ø¶§^7›v.ÿ¿´{顳¡ssüM¶ñÑú¹;±=Û°=¦VgËíÙá$騆Î9í^{Ò¹VÉѵOÅ mŽÛlN„-˅ͱõ;Ï=ÕÎlEßÖ7uyÔë¼s~_ð[µ“¬£?Êß_}÷5óùª£7±ÃMÒ›ÂÏ\ªç+@{}éêÆS±63¬>fÀV¾×VbkÞekú6R#ULØH­6HÍÒÉ·]U»½óËŽk"ÛæÊo_ÜòDì6WÏígTl[;õ—$´±{ÎýrF²ârFrÌ#ÙsÎu½rãÝKÝéT&ëªë⾫†ÜÉ‚¡SÏóõÈó[ݘª¹n7S~±½ -$6^œ?Ÿ¹/³\¸¿[×ì=δmFq¾2´M !ÛÞ6iÿhÿ™Ýô¼ø¿«Ï´çZ2óZÒ¶»ÛŒ?¸!¿9¼âFú“mè}Ï8KÕ„ñå»ÕšÊõËT¯vÖeB\ãz|=Ù º¢áàîÍ­š_íFãõví¬§Ý{:Õ«‹fTlFâ_yç½Q½©xNë°æ^íBO\ëgwŸiêbT8Êí»¯óêRýnA].I½qúʺ˜g=z¦‘§’qâ3ãïz“Øl¸:¾³«xæ¹~fõ]ç^óny\¯v 2åÞ*´‡ý]Û˶—º>ó]/Jàp²÷¦=.°…^7”Fq@ßÖ¸3Å\)úÕàα8šÕpÊî±þÜÊ÷S«|‘ï2ï>çFÇÌÄ §ª­´uø„Ÿ~ä·1žŠ™‚#±:ë"qÇb¥Tp7Æök¦ç‹ò+ÇØ®€l—«ÓÚ¶ á9ÕDá~´s |4?ø Ж p„›aÇql¹Ä–gËb± š·W:4fÃ:ÜÂÕìX*¼òvF‡¥Ümßd˜G¶míl5ìS²ÕЧ,œÛ'~üEŸXŸ"¯$±°M©ïkÎõ¿ðð\€'ìÙ̹~ì¹Â–íp®où…¯€çú=aÇfر҇JÃŽíhž/x…§ Íñxc~Ïü>è~Dè>ýÈŽúÁ+<}°ÉEfibvoô#ô#Á¯£ô%áô%‰×—æD®+ÉDŸ0Á/|}hm%û‰îdgÜY'à {¶`¸Ä/,صbÇc¬\¬!'׊K|¶i#]Sí³×]Kÿ¾F‡âùúöêˆk{>Ss„3)Gó:‚ïÑÑæGSeO÷¨{¢Gñý:ªïÔç6Œ¹7Ò¿aÏá(köG;Gu”sH{}s”Ø1Gó_9ŠÿÇ‘b"7¦@OˆÊ4*â)QÛm¼@×ðòåãº6'cVñóþëjåÐ û·ëËWŸ"Ýmiäe;eßÍÔw5K¹µÒl›H9mšÊÜêVVö¤Èé-«T–6]æ¤í–²v}~ÊLÙ¦ê"ë,Üß}ך#Ö„ezþ4è%QŠ7]÷“›¨)פóL®>½§¾úT¡E$òYÈt¤Q;…¨L/ÙH³ÕÎbOªÏæ™.uhâÓŒºï”ý?=µÓ›º{¢Kû÷ݧÝg¿ ݨN„å< ‹çÿuOkíX•!Í#5é‡ìs©I×vèøHº´W¦…Š|Ú&g±²nj¨ÜÆr·IŸ«ßÄ"µV7ET_Z)™æ+)c¯÷Á5f§†êÛ¸,üâ <[ì¿N µØ,ËÅ¢µå{hÂîw"eø7íø?릶+zž×“žmq»’©¿´ø?í,Ì?HmGúÛý§¿]³m6úù´ƒM?/<³x 9Í èLu¯M)——~ 3ú>UãSaPúYak&hëÞŸ/¤ÈÅÌ ˜T£ëÛã.Ûå©v½KY«CªÛ³„¾bTg¬˜g»¿ç<÷ÓŨУºõÖ1»µ‹É/R­}ßsÍ4ÝOÈz¾ŽËº*nigÍõ…Ï{žùîR„§G®}Í)êÁ÷_ɲç~ª`;¶g;T¾/N–Ø¢üº=Û‘®d;Nù6ìLH¿é€õÄú2~’»\…Õ‡0þÛ‡ ßâø/eüÇøÛíÐÛoçÇõø˜%ºÓO@µ÷0å/ÐMªp`5¼r×ÞjÏ:ÕžÿÆ‘6öž\Õÿîpë’ƒv¯vS=£‰m'Z9€ªÀ~t¬Áú¬…“ýGðÖüüÐoô¬Á¬ÁúôË€ûI!}%zÖ` Ö` Ö` Ö` Ö` Ö` Ö` Ö` Ö` Ö` Ö` Ö` Ö` Ö` Ö` Ö` Ö` Ö` Ö` Ö` ÖoHÂ-¼¼+wÙK.zõ´çòܽ¹þP¢îN|S‡™Œ7ù‘Þ3äzk²–dfâ·hYœàÅqSÝÙâÄO}Vö%zýPo²Ý“ñ ÈÆÃ1Ã1Ã1Ã1Ã1Ã1Ã1Ã1Ã1Ã1Ã1Ã1Ãñ‡8Žýž÷Ó²OÉÝg3»X¸æ3¶ =œÌ¹Ýï”y$«}XK·Ùãñu«ÿñÎÒÆÜMG0œ€EoŽÁ´ˆã)IUÀ®¸ØOuh;ÐlÜGËÄív¿§F‡SÏ{{¤Îûá*4>ñѯÖ©Ýeo¬§hƒÚ=Õ»ã…28Ï…n–Ó-éÀœ¨êI9"Ó3–ðÓV°§~½V0.õ#¨n¯Ñ6ó@qª³xª¤ÎÀjªdt¢Ô~ï²|ÝgÀ0ÉM¶ÆøÛfjp™ßìw盨ª¿5*yõï½ûmQÿ¾º'õY7Œ¬&£¡ÌñºçÝwY y²æWˆ½ÖC+à|Ø6”[a‰Ï^þyÏøä%6Bfu²Ê_ú<"M„Èæƒw×DìÕIþzÖ¿¯•s¾x<Ioé›ç¾ Ÿy7ß:ú2M_ªßeӋСŽgâbçB¯æè‹ÍãRéaás±÷åÓ@gÖÕ£ öÙÕwåÑ3tF·s¹93vfg:có9ý¹Ó£9v&³ç\Î,ý]‰×Ñ—Å¾ìø±¿o…$ßøÚq§¬!¿` 9_[eýØŽŸ>ºrÒÁ¡wÕDEJMX61ëÅUHåב[Ÿï—Ê»•„Ñ,{'‹R¾`u <ÞúðÔ¬!›Ë¶¨Ú£©¦žÚf`Œ=·KzCÝó|=òüVöBµÃzE÷õboä{#nÕiÔÖÈÝZÅ}³l¯h³Ï·žÞS'rã5Ûx=×[Qʮûb^iö®·×_o¿¹žìÚüMôkZgC7÷škîwÑ©þíY‹{¯Ã÷^n´–yÝQ™’wÞÕ¿m'Ÿ»¬æö9.3óé`¶ûï÷—­eš/QÔyü{Ó|·–u¾[Ì^\ÓÞ³i÷ħc•wsÐÝ» º¨×ëÝÖÛÐÃÏ´;§7ó’slé>ŠÞ~Ìxõ]ýÊ;Æ(Î/èÕEa#úmÅ‹ûÀËyŸ}^¡V·œö?F»÷õ»ÆÓÇh1mxEYÎÍÚÌù{YY[f½+î7¦T¬õrukÛú€Þ® ÖõÇN ~mßyž:;Ò§žH¡s¯)¿.î®Ùô¥^SO£ÎýÚz2wŸ™ô\‹;ïqÞ'¿¦÷ìtÇ]_¤çCÑ\+YÊŽ‡ýÚžßgo_?•Uà±JôÕh¦—eä½q+®Û¾-g΋®/:kÇpÖÙ°%£Ü ¸“\eÂÖYoJÇÝOØ´™6-ïÈ„M ǦłŸÜëƒó~hË·{à*Íí¸)¶c¨Ò~æ³TÊ‘Ù÷¹þºð¾DÍÿWÝÉYÉ—/DËÑÇtJù1Þ¨né…67µ676¸£ccú¼ÍMgØÜbål ù*Ïæb'Y ¯“SpõÃ|öE»Í´ä{°'ó Xƒ5Xƒ5Xƒ5Xƒ5Xƒ5Xƒ5Xƒ5Xƒ5Xƒõ±³ω>¸BÖáf_§ð‘5>¹§»æþíÓû¢ÅM¼6kßMø¡tùz*“~°†þéOOÈG„§¤gìóèñ}žU"þho/?ñ§ƒCœä"¦Fü9ÕàcDøËú(DÖæ—ß¾¸C±; qn?£’õdy<õ—$öÏ© DJ SŸ3Týœ0å„#zQØžø>c¦|¸çJÝêÒ_æ\nìß­k.ø¬ <»]Lf YÕÊÓ![\ˆv³õ©íÿ3í?óAÄÝßÕgÚs-™y-iێݺÄÉã%ïHñéá`OýåSRgtb@žÿ·õ™¿Í1×"z_É¿m諎 ÕñZ)ƒMy¾Ü uæR‹¶G:ø—ÿíù€–ï Ï+CóêýôÚ³[&¯é½÷HÑJ4vÄÈdø„:4Éu®·tG‰T€RŸT[ŸFuOµõtLg75âÓÏú:òíjæ·û·&ª e´ý(Q]–MÊÖYúà­Ju‚·ÚÀ›Mà׫OwM{^¤ë…bWM×™ÌÅ¾èø«¶²T×”ÕX»-W}ß=(—ÎÇkÊ1Å)ïlF[ß‚›º¥÷3¯ ·³µÊb{°¹WZ‡ëý}nͨzoY—FŽB¬ñX¬}½N>"û.ÍHòz_2ÝÝ•þm­'„¦¾œ|ÈŸüÔNYY›ÓÀ8Vô„߸b,™ûûU¡„)Ç,z3/tá÷x¨½KH! _ºÎm1äó;BÓ%Û Ýÿΰû åù¾’Âøl/}å[ŠV·¥~µ=WíÉi˜9¶Áñ(Ko±öôé]‚Æ4›áy-"¯º±’u¢ìH98íÄi'°k°k°k°k°k°k°k°k°k°k°k°k°k°k°k°ë-9 suA=GÏÁ¬Á¬Á¬Á¬Á¬Á¬Á¬Á¬Á¬Á¬Á¬Á¬Á¬Á¬Á¬Á¬Á¬Á¬Á¬Á¬Á¬Á¬Á¬Á¬Á¬µ‹‡=5ºL™þ¸ØÚæöjbdGubÇÄÆXoêiÓÕ¹{sý\\íŲ® ñN‹™LífÒÝ¥².]*ž]ˆ”t.e鹚*w£3äªê;Sž&íœm{½ŸW‘Ú¶mÓÖUω=­ç•>M¡l‹Sê='å`n5g.9ó©æÈµgWßF¯‘ÔÖÁ%­ž‘Š˜ýÚb˜z®]—ÿ_Ú GûäÉ“é¼ÏÕßQûÕµB>—ƒoJÚÖ›ÚV4¿]Á ¼À ¼À ¼ÀK ¼tæV£ckÅØš±5m Û/ð/ð/ð/ðr`^Üü)±á‰ö/ð/ð/ð/ð/ð²^2;Ÿ1óšžh?ð/ð/ð/ð/ð/ð/S|äR)GfßWØz9y´øÿêÏ:Iº^ß¹Ðô§°Ø_zþîêR¼`¿OÙ9rQ—GúµØ§¯ð:´¦_ß³r/9÷Pw^uöPbúèܡЧdWµ=¨ÞçôÖéñIøjëWj?bá‹zmû™6m5>®™¿VýŸˆw–õYEÙæî0|€Côõëo?üøË¿þüãßþñ÷^~ÿ寿”YUWÿsùãöoüóÇÿ~ûùóÇÍ¥ü~ûÝíâ¯û¿Â´•häóëÁ÷ß¹òªçV”uý*±mõt,ª\WE'B%OBܱ^Ý>òÛm6]èskf Ä“M„öê1ÅDL5 Á§•4Xå/8²=±žÚÊQ]S¶ Ù®¡ú¾ÛeÊߥó±šÒ¥káržMxS'kò´ÝýÂ?üÃ?üÃ?üo‚ÿ"à´ÒB¾Ý¥ô¦íÑöàþáþ៱c/B2ÎÅÎÑÏÁ?üÃ?üÃ?ã\ƹ´=ÚüÃ?üÃ?üÃÿþç¬w¹ß¤‡xÊ3ú°ûeOýz]0ã´Œ'¸`^Nµ«£6ï>ÕŸ• äíïëMîëí=QZ»=á7®DjúoÇŠWnâÝ÷|Ï,Â…ÔÔ}öý;/…¦P( …B¡P( …BÙNqkDÕzZSîúªØžd–‡}Ó‘C¼Õ‚ݵ^4}€WÙ˜ñ¼>»8Èëògë-ôËü¿Kó÷i6#¾Ai¯é¸ 䫜Êط渠flZ¨8ˆn-yÁìÁìÁ~¾£B<ÿäAŸ»â8f껇âši+SÃ_l7¿Ý½"Wfµ±®} ²Ê‘aALªgäÐ#r4ñ½:ÍùlÁlÁlÁlÁlÁlÁlÁlÁlÁlÁlÁlÁlÁlÁlÁlÁlÁlÁlÁlÁlÁl—a‹ß8~ãØ°[°[°[°[°[°[°[°[°[°[°[°[°[°[°[°[°[°[°[°[°[üÆñÇo›¶` ¶` ¶` ¶` ¶` ¶` ¶` ¶` ¶` ¶` ¶` ¶` ¶` ¶` ¶` ¶` ¶` ¶` ¶` ¶` ¶`‹ß8~ãøcÀlÁlÁlÁlÁlÁlÁlÁlÁlÁlÁlÁlÁlÁlÁlÁlÁlÁlÁlÁlÁlñÇo½Å&€-Ø‚-Ø‚-Ø‚-Ø‚-Ø‚-Ø‚-Ø‚-Ø‚-Ø‚-Ø‚-Ø‚-Ø‚-Ø‚-Ø‚-Ø‚-Ø‚-Ø‚-Ø‚-Ø‚-Ø‚-Øâ7Žß8z‹M[°[°[°[°[°[°[°[°[°[°[°[°[°[°[°[°[°[°[°[°[°Åo¿qôlÁlÁlÁlÁlÁlÁlÁlÁlÁlÁlÁlÁlÁlÁlÁlÁlÁlÁlÁlÁlÁlñÇo›¶` ¶` ¶` ¶` ¶` ¶` ¶` ¶` ¶` ¶` ¶` ¶` ¶` ¶` ¶` ¶` ¶` ¶` ¶` ¶` ¶`{»7IÛƒéL N¥Àläo#ï ŸtÚïÏíÞeî ½í!ò!ò!ò!ò!ò!ò!ò!ò!ò!ò!ò!ò!ò!ò!ò!ò!ò!ò!ò!ò!ò!ò!ò!ò!ò!ò!ò!ò!ò!ò!ò!ò!ò!ò!ò!ò!ò!ò!ò!ò!ò!ò!ò!ò!ò!ò!ò!ò!ò!ò!ò!ò!ò!ò!ò!ò!ò!ò!ò!ò!ò!ò!ò!ò!ò!ò!ò!ò!ò!ò!ò!ò!ò!ò!ò!ò!ò!ò!ò!ò!ò½U¾Û½IÚþ{¹Ì»JñÞ‘¿£¯_ûùãÇ_þõçÿþó¿ÿóòû/ý%ÑE¢ªËÿ¹üqû?úùã¿ýüùã¿æÒ?~¿ýðvñ׿ýߥøÛÿ©ïZ˜¨¬‹º eõß×ëí3ª‹ŠÄßßö7îZR?KP®õ}¦rÕß_þ9ç[¹ÆµÀ•NÞHÔ¡MÌÌcnød§ZDƒÁ¸ºnÿ¯¾7¸Gëïú»ôö}»7Í<¾†¿tàžÌr˜å–sOdßkªS ~{ýZóÜ÷>Ó+‘å¶úÝÄ÷Ëç =Ë ¼.¯Ö3:òšºTuм~ÝoÚ€“O<¯â¾£ù}>,×ÔºšïLÎÙy>ÞíÚkÓ†³ym8iÃSekx{BŽI¶ÄÚ(£ÇÆ^˜ëÕ§²åËþF|_½Ë]‹ìo”ø[\3í"qï±ßm•ݱ-NÏ›vuÓÕ¬°ßi«³ö³Õ62û óNc,…ogFîF¿µè‹²é¶¦«Q Nö7y»ŽNâ²Æ¦¿?Ieoõ&¦W8×¢™ÞâzõňRTÖÿVn¿¹žêÞ!ºÁr5Þ®EßÏc"ÞYN§Ç¿1Mréó QÌÿQ´ÿr¾´GƒE­+×år ügµYu÷ZÙù?nÿ_¼A†Ù­ˆÇm…‘ ¾)¡÷k¯(ùµ=¶ƒoÆ;ŒwïÐÿчP+oBc…­¤|F÷è§Ãî§ý:çýîŸÒª¸ÛÿÛ'¨ÖÙ¯õZöì==e÷CÌgqн½tâÞžÝåÞž>×rw÷›}Ö7ïýÝÉvö{!}ûún¥yFaÿŽí³N^æ¼\°ÿ'ðpï¯äŸùÞf¿/²þ}ŸWsµ/u¶úîö‹Êú]-?„Øî ZƒjŸ/o·#³wTɯ­žæýaÁ1œõpw|M†¸+frY®g‰å-µŸ¥çnŒ§ mšžiÏÔˆ=õ2ïÈ£û:6ò~‹½a¡ï]ù¥|†·F†¡~§Üö².!b/äÛö…·w±w¿]{!_…½”wëØGqVÆ\ôí£~k` Ö` Ö` Ö` Ö`=mí,žéŸú9ý;ÖÏÎÜÚÇYÔÓ®¸{sý¡õ5çû¬…?uÇ÷ùíë5dxf½M·^3ùšš±¦Ý·™¾öœ»u¸¸gý{Æy–JoRû[]cÛõotc݈РtÝ8ªnT÷¦èºÝ@7Ð tÝ@7Ð tÝ@76¦ Ý@7°èºn èÆ‹uC¿X7Rë³XÚO룾lL_â v£À–`KègÐ º£èvÝ@7Ð tÝ@7Ð tÝ@7>¡œÏá|Xƒ5Xƒ5Xsž’󔜧d¾ƒn0F7^‹Ñ£ž§lrܱŸ‹nЧ èºn èºn èºn èºn èºn èF/çÍ³í¾ ºn¸|OØ tƒ>Ý@7Ð tÝx»nä~êÆ¢òÿîøÝÀn`7Ð ÎNr‡sO` Ö` Ö` Ö` Ö` Ö` Ö` Ö` Ö` Ö` Ö` Ö` Ö` Ö` Ö` Ö` ÖÄ%~(ñCñùÃçÝ@7Ð}ÆE7Ð ìºn èºn èº±ÝPèºÝ@7Ð tÝ@7^¬úźaönÍþui?c»ÇоlK_â v£À–`KègÐ ºAþ>t»n èºn èºn Äå|Xƒ5Xƒ5Xƒ5ç)9OÉyJæ;ÌwÐ tÝ@7Ð tÝ@7Ð tÝ@7Ð tÝ@7Ð tÝ@7Ð tc˺¡ìþú~`;Ð tc·ºapŒóÛ}xt»Ý@7Ð tÝ@7^¡z†n” u#F76o7 ìºAŸ‚n œŸä<Xƒ5Xƒ5Xƒ5Xƒ5Xƒ5Xƒ5Xƒ5Xƒ5Xƒ5Xƒ5Xƒ5Xƒ5Xƒ5Xƒ5Xƒ5X¿kõ –å»ñvïG¿Ño°k°ë5c?ëa>µžö½iäe6~¢ÿjZÇ{Nsã¹´uVÖßôK<ßÕ?ñ¿›ƒ£y‡y®ñ[Õ·ßèÒ~æÞ_ÔÕËùàVŸÃ,þ°¨ôð«þÝftC­§2øÒ8ÞØ4lXƒ5Xƒ5Xƒ5Xƒ5Xƒ5Xƒ5Xƒ5Xƒ5Xƒ5Xƒ5Xƒ5Xƒ5Xƒõ¡±vù•?…·{ÿ̱ØwÏm¼'sïs$ï1uZV§Ægž¨u¢NÔ©ž¨uZV'ýâ:¥6ti?­ÿñjõŒ'ðT ÁÖ)ÆnP'êD¨Ó!êÄúX¸uŠ··>fÖÌ«|yŒ#¨u¢NÔ‰:Q'êDvY§æÙvÜ·é:)t:Q§—×)÷öÁÙù×nÀub- žðƒ'êD¨¾bðDðÃW }ÄW ž¨u¢NÔ‰õ1x¢NÔ‰:Q'êD¨uúP\Ž-¸¢Nö=Õ9€¡}€xš¿¿æ?+ÓÐõi›®Ù´{âe´èuÞ£ßø37é»þ {ÇØE•ïi ñ’ÂxœñøžÇU—â¾~×xú< ÆFì¢(˹Yk;/+kˬwÎIÜ;'íÙmÓQr·ß¦±/pëøn­]·×áûöb†ÖÅgí͹=Ìä`{tÊï[OÝOI’ç÷ëÌ>v{Ž+9­.˜w:¼Í©c³?c÷0r=¼72yï…24{*vï»Ð;¼2ñl÷ÄÓ×”8{sò²™ý³ÿ“Fû­OÂÝ3“žkqç=q]—æZÑóìtCzî|Òuõ\îKK_ŽJ–Òï­­±o}'ÛÙÛWç[’‹ùL“Ù{Ü¡ûóȽÐÒr–{ÎÜùøAžÇpö!$—ûÒÙº²í‹5Æ6m¦MË;2aÓ±i±à'÷úPé}:Á– n÷ÀUšÛqSlÇP¥ýÌ=f©”#³ïsýµ“G‹ÿ¯uÛ¨t>òÙöb>Gµ¹±ÏE<¨[z¡Íu±draƒ­}mÙÜ} ~¾”L´·ù çJ©Å=÷²è«à"÷)?Ÿ©|­Þd¼ú¾ô:]½[‰ë×·G8çíy»“ɴ몎V'fòzžúºeY[þê¶ɬÞ7ßIÎËYrßïTí[û5‚ÖüÌúáõú¶©(S¹-pnë:‹™Íñ^g±µ(‹˜é¼{s«³×|‚c\Ö¾·Úè8Ʊ¿÷lês«×ÙÈR´är'oG×¶c f£¼úÝ”ºtü’a'¿‹uò‹¬ìë’{N:ž¬Œî™é8>ñw­'JlŽd×¶Saµà©ú ïóé‰Czâ6䆰uzbäêÓ“ë#=ùîáúê1š«'¹Õ#·.—ër=é­Ëy‚Î/ѓό¿êëcz2ƵÓ³‰2WO†W/c¼\êµ"ƒƒù®Uß{ºÙ¡âdïMï“$_ïŒFâÇ%‡ZmÙ9Ä<2°y¤›7jv»÷»1ïÌsnðàºØOسöLù}ÖÅ>»›Û6áÖ/’gw;ÃY 6m„'lÚL›¦Ä_ìYXöÌÙ--âyËýK=ÁžY~YëßøZ¿ž°ÖŸ½ÙæöèövæÞªòøbs7²·ZNôÜÂÕÆŽŽ·Xä3°û—K¹ =6ËäzåY'~†~?ƒ>eAŸ’ ßOú“}­± ná*еˆްa3×!b|¨w½ã?üšjŒïôSv¬q2±cûñ›¼ÂÓJö+q£%o.6oæã Ïå9=súÞ~D½¨yö]LÒѳÎ[Óû2`½/YËZÅg°ðþJŒ¡C%^_ª¢EI&ÆÆüÂׇæ‚ÝœEOlÿž°g ÖµJ|l6›ï#™¸¶UîÇÏfóùJÜž}ܳ¾UâÃò´MK°i»_¯O°i«Û´X¬Õ§÷yßä:åGØ3|òv5çTbߪèÄuÊðË ~|æxsþ/bÜ_kø» ˜Ó¯ì2×g‡c8Û@lÀ›†ŸÞNÇÉ©ð-΄Ïq,òY⧦-‹½/RÅe$ú¦?=üôðÓÃOïö+õm+˜y>~zÌñ·ê¯¤EŽú’ýô%náêƒëƹ˜¿'ÖüG¸£O¡OÙ„¬¢OÙµ¬¢_ Ú_l„'ì>°»ÜKĸ©ìôCøÀâ‹M»Ï5Ê^Ø>óþu8†³œû.:q•°åcžvgÓô›ý•b/6-ÀµLá—ÔìGí}—‡þJ‚cbï &°³½cù¯~ì·x¶såun®¾ êÕ@p™ þhñºGúh±ÏŽ;ìh~rGð3;RœÕ#Ä)=ŠØQcÁï¾}äŽWã(kòGÜ/‚ÛýÆŽ8Bì…#î‡e?áˆy/’7òh9š’ãøH~éGöë>š¯èQ|-÷(qd”ÿñÈùSìÈ1¹Žè7ƒ?ÇNÖ¡Äx:FŒ¤_ûùãÇ_þõçÿþó¿ÿóòû/ý¥(“¼ºúŸË·£Ÿ?þ÷ÛÏŸ?þk.ýã÷ÛïnýÛÿ]nõ,¢Š^é=åzþ®tÇýßgo'{î…}ÞÙ_3>}SîNu;ʯµþTjaÕG%‘ЩZW†üÁØkíkè|À¦ú%J[?xNµã§Ûõ;òEÜ„_q6ïØÅôz¡oñÚ¾Ã-ßÓl%ážw.îäuT_åšoÌÞSQbž\Šq—y«ú_ôâ`z‘‰ùH†^¼K/*øìI(~c/i/¾,ïåLÿq8ç™Å™±Ãqõ"«Çý½ü¸uwê@ä9möGr;ù ^¿‡Kb×›{¿ÏÆïí+`3¯4ç®ÍžŸòûÞ¹ÝÏWè ú²@_d}+?¡n}NmìZßÇþæSeõgÖùÁX©~ O#g|eÕ9¨³¬ï–ê Wë|‰Þ_ÎÑ´:'ê~ÿÐû-Å7×3bÌE+ã.d‹‹¾:‹)?5†¿óùÛŠßMSÿS]/'-/}ý†öIí…$ÏûVfÚ:ßÚæZÒs­ó»Y>—é½®î[ÛVݧóUt-ñõÛéØº3ÜSðžu"Û9×fáU뿜ÌSýGÕÊã!_\täÝúØ!y)aïöùÖ+—û\Ì•+3Vf¬ÌX™ñceÆÊè-ceÆÊŒŽc'’±Cç\;c‡™Ø*v»Á,ó¾G½c57@¶ÿIý›>¶=˜U÷g°«·ÊÇR9ì|BÆRkÏŒlcÆŒ6b'¢k z¸Ï›l'ÒƒŽÊ¯7×2ÆeïZ/scˆWŽÀu`ÜÐïF;7Ðw1ïeÞ˼—y/ó^æ½Ì{;ìzÞ›Ù|4îS^ÛÓ¼w¨2Os“ƒYÊy²ßÙöXÝ·åÊ\û‰kÝ«~¯×ãzŒÃ©×ö´G ×p½®g¯Uô\›5ŽÎ<×OÇ1Ú°¿ØÖ}²¶î÷´uß"ôýGÿ·»ö¹õõÅ­¯ám}lëûÖ[ßÞòþëÖ÷8±ýØ~lÿv×n·¾F…üŸ\7ê‰G©s]NˆGY¨:>ÝÔmó3KÒðö)Þzº„$Udz$ÓãÈ=>Ó#z€ôgüŒ³3~Æ~埈¥dü$SY»Ð 2~¢düD/ÈøIÆOÆdü|1ßÅ62~2cÇx[Œ^0cÇ<{Á<ŽycæqÛšÇ=}⑱ï7>~f,Ї>®cŒ´1ŒoП1cÆ Œ3`k3,ĸç(E\äñ„³ÑMîèöŽË×½urçE­±ë|»Ä€æ.žVÇÛzð ÎTëä`žÖ3væ·7Ó<Ë&Ò¡G©•+·V!ö‡­&Ç~ëè£Ã©áÃ>»úNûøm™ê`›Ùg\lK6zVx~²äþðàh^Úß.v\u–Û’ªd²Ëý-=NEC‡ŽÑšGD=‡%V=*q¸Ó°‰ bðlrß©[.ˆâÅþîÜ|¨ [7§¸òwé|¬ÒÜžŒíÉÀÒ~æ¾{s± ¼øàýM{Ó6.×Èž[’ïi@ªÐOF¦:æÑ§úÓü_œêZñy¼˜ß}§ÄßiôøYÑ÷´bNÉE‘(#¿UÑ´gÆùô÷¶d.F´Ð™ËSÅrˆ6Ž)…H1}Õë… P(G¶ëù‚ä&E6Æ Ê1ŠJ¯aõ–xêo{b;Æi¼ntÇXlÍúç­ëگؘ }“/Ññ,­Î­™œPÈW%',è=‰!I ö`ö`¿U‡š ‡š;:Ïמ/w¸0™MÒ— ×Ð)t B§Ð)t B§Ð©:åŠÞNE/X[|ǼH$p ùÎ|ˆöKŸ€N¡Sè:…N¡SÇÑ)™ùlŠn¹¬hÑu¬çh·¶25ãáØ†`p÷¦]&¯:–¼& ÞT9ôˆòØy£_ëþ7p7p7p7p7p7p7p7p7p7pó.nšð¹ðCÛa_}=öŠÑ)t B§ð?Àÿ€ùÜÀ ÜÀ ÜÀ ÜÀ ÜÀ ÜÀ ÜÀ ÜÀ ÜÀ ÜÀ ÜÀ ¾ìé±§‡N¡Sè:…N¡Sè:…N¡Sè:…N¡Sè:…N¡Sè:õjÎoæõ':…Na§Ð)t B§Ð)t B§Ð)t B§Ð)t B§Ð)t B§Ð)t B§Ð)têX:Uí§è:…B§Ð)t B§Ð)t ú´N¹±9:…Na§Ð)t B§Ð)t B§Ð)t B§Ð)r_‘ûŠ˜£Äƒ…¸¸¸¸¸¸¸¸¸›r£¬/àW½žmžß|>q ®pµ×$~=OCÜ î[¤ýû+®næÞOì/ñοÓ ¶¼“wòNÞÉ;yg¨ïì‹ ¶¼“wòNÞ¹Ýw>ŠÛ¶¼óÙsÅ`Ë;yçÄwî|?‡úQ?êGý¨õ£~ÔúQ¿=ׯY7‡CêGý¨õ£~ÔúQ?êGý¨õ£~ÔúQ?êGý¨õ£~ÔúQ?êȹîcŸþõ·Ÿ?~üå_þñï?ÿøû?/¿ÿò×_T–ª¢ºüŸË·ÿ£Ÿ?þ÷ÛÏŸ?þk.ýã÷ÛoýÛÿ]¾o8ÿíÿ®×ºDQ] „Ö|^cûýéŠùMqû;õ÷æßþwÑí7×Ûµ³žv¯|§)C÷çû{Oæ w~ûûFà—)·¿‹XÜkžy#ülêaïÕæš®e¹öȓܞßžs¹xL¦âp¾Ýw2¿Ë=:êÔoà^󎆃 +»8˜à¼_mJ{ïÙáPÎÃÁàš˜¿s/W+¨úšÜ'Ûæ™çú™}uÌì}‡JÇòû{UÔÏKñÕßÖ.ÛË Ÿ2éÁöö]™OÀvDObÕÁöüø™cØöñß>Kƒ‘îÁÖ82»v¥úÛ‘1Ø­ö+±½]W7ü.Eÿ½¦³ÕÛ7mpÈE Û éÀâbzT'o©žkƒŽSÓn¾¾¦ÛÀ²œ÷^cŸš÷––—Dð>S?û0ÌŠÇúÙ§K‡ï¾{èRöµP—®úµ8¨t™=V—LðŒÍé­K6ÓyŒn•Å‹å±íäi˜ý±ý|¯ ŒÇå©‚á,Ã\ǹ®äÐc˜â~ s¹>?†™ÄKY?Óž#/ÅÒq÷RÙGú¨Ñù}:2¿/Ü«{ú¨S»2÷ÞÍï/õ;M[7uú¾=çTLè;¾Ú}‡‘O%ýu>]Æëu׺:x]Ôk¸~‡žš¿¹NWäZ¯Çõ˜~OæÚÞ/Ç êR×¥×v}?®óy Îæ@zS粇{%¸?Ù{Ózq¼Zòvkã:KÅŠy½>¶Ê²f‰uE§fQ«v=´ÝqQac›éMû݆n6¸n$Ó¡h±›È^—NÌ^§êß¿2sY Líû\f<3hdzsf»;ÙÎõûî"úÚknªyFaÿŽí³N^fƒ÷ì(Â÷þJþ™ï•‘$\¤ˆ»Ï«ýŒêÁ¸‹¬¹{®Üe³™“¶—²èW²j«“¹Ð—ŸðÓÃË™>੘Á“Ã=·<9¾b»Ã[ÖmÙq7Æök†ýŠ-†Úë#ö+ û¥;sJ«?²è6­Ã1œjÓFx¦Ͱi‰o®­aÓ²i‡J× {ï˜ýêð ?Ø/×¥Â~ð„ýši¿”ï#°_ٯض 7³úàúLp W°e©µSÊÚ±Ì{/å.hû&tá¡mÓ#vMÔÉ%Ðy¾›ã;;¥ÅZ9Ñ>ÙgÎóGx¦ͰiJÌ!Y» Ó¦)aà ¡C™5í¢Ã'ü¬d¿2ÑÇ$í~fŒlÕL[•vdÂV…e«Ü¼¾ôY:ûUÖߎ¿:ÃY 㯬½.3Æ6mæÞ±öëÁØ´ ¬S:›OØ3ÜÂÕJ¶,¶©ûÁ.딪¹\Ê딬S?N.¼®Ó§Ö§¸½.g_”X«Œ&¬Qvø…¯@×(GxžͰg®g|¼­õÉü~ÞØkÏ¿ðõa[–þÄüfˆ#ìØ ;&ÖİcñGÊÚþy½öKð OöEÊíwÉrÞ˜ß3¿ºÉü¸‹~$À³’…ØóDÔQ念K‚_øú`’û1¯ ,áþ„þ$ø=È”õ•`ç%âŒj³·åöä§ìc¥¬­|tm%þªÉ½ÌGذ™{^دp}ÂJá–u|)’ û]د0ì—û®èìsa»ž÷ÁO8k¿Ûø!ŽálE_°ÂÛ¬Df‰öìx¦Í<+Y |±iûÙïêp WìyÅÏ·pÇ:%ë”Á÷)Ú¯ËÓ§ì¬OÜÂÕ†ö½F¸£O¡OÙDìÖŒµ—`ý)äž—%™àKÑá¾VìWâŽ_±ˆÐŒñ„=›¹–œˆó©Ø³mŨÊ'Ø´Çpöá}1±^|#a€'lڛưýžëp gŸiM8 ö’ygÂþØ.ãSw¸…«mY)æ©XÓ,—sÆ&k˜›èKÈ?µÏx/~ák ?)>œw*!ïÔ+óNaÇv8ÏOˆM|¬—„XÕ/‰õ¢ðS vnŸŠ9¾Ì£÷hïEð O®Qp„ýZ—:ay·c±Çp6`ÏÔîÞ9á ›¶ ÎEÂ>r6Í­‡%ÂoBŒÏ&ŹHØ?ÞÜ™¤îØ{aïeãäÈ·ú”œIêp WœIÒ¾¿¸÷ð„-cÍr7v¬óþR´Ö,·ï+ΚåëüÄ æ÷»÷/˜çoÂO¼ÀOü%q”ì<›¶#?ñ·pµÁ8J=ܱfÉšå&üÅc!}ÊþüÅãûÜìðÐy„'ì>ãÌùñÇgü`yŽ ûìØ~ö‘¯ð´¡9þoÌï™ßof~Ï>Øþ|’·pO’˜Û³ÿõœORLlÞÝÙ1Á+%ø»ù‰ûbåcž°g ìYƾã&ó…dæìŽá,à3Û:Àvl¦Äý]úñGœÍߤ´±óúÌïéG䜑xˆaú_b?Øq ?>5aL,8†³‘~% ̯„;ìÜ ;—‰œÖظýÍû¿ðÀ¦óg’yE8ÂŽÍôÅTslY€û1…È?‘ *&ø` náêÃóÿLµœ;ÖXºOÉñíßíùËŸþö'ZÌ÷U'þR9Î6læ:fÎò }ùËN{ˆúϸ ®ƒåœ!"çžêäè-Ç9ÂŽ-ðáר² ÏKû•tÖÀôDp=[Õž¹½´Hä~Û6 7²Æ¦½äü¸óiŦí+^œà®6˜s¯‡;Ö,Y³ÜÄ8_þ}Æä×øño2éwô)ô)Á÷)±Hw»Öá¾VìWâι°R¬Ád=ë.±H_‹4>âØ³ð|Å2ïÛ];öQ RÁ-\}xO¬1¬t'éOز~¯~J»ŽA*8†³@c‘Œð„M›aÓ´?#Áæ†Ö0Ë ¶¬Ã-\mh_l„» í›ó¼ÐÖ0…l¬a®¯Of×ñú41b6¯Oo,&L¨ûüìÉì;fŸf_æ¡MS¸{wÜ>;ÌËrÄE¾­aÓ²i©È…˜tæSsÃE÷9Ýá*ðyÿwø.á»´‰qrÄþØnýa#öÅ‚ñ‡Õþwããˆ}±§c[»ünر}Ŷ¶¼ÂS þJa¿æác~¿ï5˘y~ðû0Yl,<ë’ôb×û#Åø¹1V–;;׿óGŠw|.ÿ‘-[£Q"O2}É{r²¦²Ïœ‡¬£¬ 1jŸãªúX¬‡©e¼1'aN²‰õyÅšÖn}%k[›ð•Pä5|Ú—8ålÄîì˜àž6æCœnì|cbú‘ÄÏéGvŸ½`m~sqtx£¡ÙÄÙFÎko'†n!úõ·igä8«N.¶Äç•lÅŒ6zV[‹=¾Gö¬|Þ–¥ö½ ïºæºÑ;#›Ñ‘ÜÚ=×>T-Wö%žïêŸøßÍÁѼÃ<×èž¾ÔúX}æ^\½*})ì§Å°z¯³µ»]éíWý;úº'öRïwL_ oS$üÂ¥ßÿ¿&Á-\m,ÉwÌŸ˜?mbþ¿o—9H:üÂWàó§˜ø}/ÉAbûoìY€gac1>.EûPÞ®MÊEbï…³mšêÌmrqm(IOØ´™6MùsÇØ´mš›³D"Ö…ëû;}ý MÃÙŠ~é…¡ÔôO¹ð­I;6m€§ÝÙ4½‚!yâÂm±“Å9hrïc;êŸFθaÞÒÇYVbî©Å¾œœ‡vmZJ¾¸§ýÙ"á#…M oM‰õ}×b¯ÿý£¿ðõu4áƒØÌ-ž”yž-ÌåËùç0}tÓ‰ç:ðòÁõÿ¸0ÈÛá?…ÿþSoÍí”Чíò¶å®6æ?5ÀþSøOmbž„Oî~ý§bür?>ŠÅÿjÄ× %—þSøOá?…ÿ6 ÿ©]øOý ðŸÂ ÿ)ìþSøOá?µ½½™Â÷ Ø´­£uø…¯@ý§Fxžá?µ«½f·®ìÚ]Ž/UÐ6,cj¹vïÈ—*´1Yéû{lØ×Í:ÃÙ×ÍÜùá;Ó— ðĺÙÌs:yG&lZXã2¹fV¾´h‡ÎêŽ÷ÀÙ$?c)GfßgmR#ÿ_ë6Ré~,äK„ß±å*Û+Æë½º¦üß"ÿÅÒ¿¯cÆóõmP¯ü¼s+“¹÷þÙ›êºv,'íûðÖü#Û—/Êå~´\èGÝ{:ÂÞ ÜÂíÖsw5÷Uðã&Õ9ÿwÖk¦¬Õ¤~Œ¹é¼ñI;G|kãu|ÇB!l¯³ÅÙ„ý„ØÿvcᾸ¦ï®ãs·¶ZNŒãžoØO™ß¼»Ž¡ÙÝÄ·ñÝÚÝw×Nát¥œƒGÍÙ¿ð»i›œù=ŸÍÛäXŒåšÄ»ëøjŸÏ¢s>-ëOÅ¿ÏÌëÆ¡Úî»ë’o¯ÐýMr,çzÊÏa[>eï®c(}maKr°µÄw×;´¾¶hŸ+ÞåüçÝu ¥Í–Þ3\º8­J´½TøüÄúw×;4~õŽlr@½á÷³}îÖøMÏnÌXL´SÊÇ%Úü¼6õujúÚw×ñÕ\x,"&Ëî¹ÌÅ^]Òwfk\>cwár?\Æ¿ª72N…ÏVÒ©ë_ŸhƒóœWù9Ešë¼!gñQrþ‚×d£þ1™¿ç‹ÎY½5êùn^‹v¯]ó:·½&ò}Š„ÿZÞi·ÙŒ¾'ÛðžÀ”ùëõÜ@Î٣Ķ£ä3;B<¨£ÄS šK%tÔ•è>¦ÊCNՎׅרçò%oÜÂmkS×—_÷]Ûäw×34?D%æ|ÙÎ×—Þ]ÏP¹-ÂíÖúÛ¼ÝÇ4|O];LÛ1¯vÁmÖc“ß]ÏWÏ}Á±}n4q½¿Ø`Lœ%möÝõ qMXŒ=6¿&‹±p—Ûw×s­¾691ºõÛÚùõLì;&m[´JB;)íó»cËeæü~wÝ×3¥"fá£:'Þ‡`·c¦5êÒ˜Iœ=ÛÕX¸Û¯®QOx}¯ª?6Ùæx}ÆO\Ö5ÛIL}ós•z†ØVõ›ÇÄEöWodŒ¼NCéWÆëÞ}Jרg(ñàcñÛ­­–Â"ýk@C7‰X »7¥6ûîzÂéúcá£qª6–¯b)§jC뇯ˆÿ®7~Îyê¸éÝõ|w{Õ"ÕTN³íÉ¥b¤{;"wÖÇëÏŸ?¿¾5ž_i«ß}&V½8ÇÎ’|ï®'ÜÂíZãæd±O—ìÅ¿»î!æ½Ö¾_Ú…/TÒs>vz†Ö†·œ#ëÙ6¼•ÜYÏÄ‚ŠÅÜãˆcéw×ýÕ{EQ'·²šÉó–Ϻ?Óžß]÷∳¨›mÓIgý#ögßZýñ»ë"¯[Œ wæHÂ?¹;Y¥ž!Ž¡Ëƒøg”òÏx%·éƸ훩v.ˆV¬¾5êš=yMwmß]ÏWóªÄXQÆØQsäE‡ÝŽŸÖ¨gh1£gÏg2רg(þr‰ðÇ/w°†‘‰¢¨û«×0 K?ë­Åĺæ;Y«ZÂs~ }ߤÓj÷¶º8€O]"b•ÌAüšgÕ™/äíó“øÎWŽm"âNöbà|Œ–r_Šý!ч}‡WrŸuòrEókëùSöåœ^£ž!«KÿŒCí ®Q÷Ç[å†óOg"ªêä£+;møÝõ i¼Unô,Ù«ÚðΔÁóöyžÒ'ká’Œç5êþê}¹Ï2µ~åÎcßÜÕó×ß~þøñ—ýùÇ¿ÿüãïÿ¼üþË_IʨL«Ëÿ¹üqû?úùã¿ýüùã¿æÒ?~¿ýðvñ׿ýßåö2Ù ›MǨçUÿÿý}ÿýEÙò’çÔ©ÄsõÈDÝj‘Ó´æ<ß56Õãnøgy¯Ñ¡ßW<\êß6ú–ÙçÄ5ï†³Š»Ä^7϶¿1z¥oïK3ûýÅ—Èr[ýnâûåó†žåŠÎ¬þÛggºçy.Wr¤÷±Yb'Ÿx^…gÚ‰Û˜ Ë5µ®æ»SsvžÅ£w˶Ÿgžcmej8Žk¬š{S‰®K{}ã|¨<#‡‘C®;d‰Xø~_#ß«ö÷•Œ§ÝŸÙuœLÄ/E÷Ñý£êþ ¤ýºêêPW‚Ñ1êÙ3.KÓ)£²ÂÜn†{IýiFKET6›ßôŒ˜”ZoÄd;ÔˆId=™Ôk”Ï÷UK‰­üvæSýöZ¿Ïa&uýÌór[s_ÓÃè'z˜ÊÐ=¹ŸV'1“<ùgêKÝòÓ¨ÿ7“~¯Ú?k­÷‰IÕŠ³bÊ\ò6€I®~nWYªÛ££“my»4¿K}Ñy sßoÍgn í­|‹ß˜ûÌü3J¼…Ô‘ÿ¾¢îÔ~û]eE£ö{ZÖÖþöz­Ÿ©ŒI¸ú9­+FeTto³ôÎëDX¢“°¯I•õ±Úƒ9Õr»5Ñ#Îuó‰–Ûùg¿p¼/×£šùÄ ó€;™Î^ÏúÆbùÌõ±¹s4·–(¶V©´Ÿ¹·„©”#³ï³ëjÅ–KùâótÛŽ­Ù¨­I휴°í?éØÝž×ÞÙmm@êmÁ}Ò/´1ªs£ë;–v|±7Ø›€ìÍÛÚñ—ý{‰/bڟ눶CÛ9DÛQ~ül°múºž~«Yß;cr×öœ/à?ð\øvâ}ù6¼ª6ϱóâ,¹_ZÑÓÍ»ö+çÒíU¶C£wnU¬y—¶m×~Æ‘—»Z=Kü;ä8«wK)•ˆ%¬x` Ë,UKR·zŸoíãªëϦ\íõk]ªå¡¢]ªÅûÏQf?ÏpËJÉ$wˆ –“Žè:¡ö½m,»ÍL½a;¸çùS¶ö2;å2ïɬ)n]sS²îµìþ^Yö†“ ;áÜÒd(ŠÖµbcn´»]´;w¼ 4œt§ßt:Õèn^;‡†(f~XÒ § ¿fô££õuî¼]ç ?8TJãbÂH©<Û²²Á¸§íÿ])í§Ûô3™r©7Ûæ•ž‘QîÈ(> ‹D:c£-±Ò¢^Íöy§Ž¼ß‚[1yêÊß•/.LÊËa?g“3öÎìÏô’ÚÊÒLJÏ~"\}ß]4—¿Kçã5eiÊ;w‹·âMÝÒ{›Qáv¶ºûß6÷ÆãøzŸMTï-ëÒÈQˆ^Mµ{7áݤ«T1cÄ&[öʦêE††§Sî=³96®:b°ÙÔ‰EÏŸùk G'ñîóˆ[ÕÄúõöÀ:Î&ôÀ—“wu1î-Z¸ÜTëÖÕŨÝXÑ~ãŠY×hîӿɧ?o¨Éóϸ“ë{~©Öx¾·WŒ*n±œNï‡tÛš[οöwÍž{Öœ¢Îãß'Åz²<”5 GÊ›KøQ !°¢†¾k°k°k°k°ëýcò×RäØäŒkØk–+Ö6å_Á?íÿ³ü¿<1@Oˆá±œJ¹ ]¥ÀîëÿN3ó ¹ðÔ'Ë{ÔÉç:¦Ë=I†Ã—Q9!ž±É$`b^®uÞ¤kTÿ½¸\Æ¿?Gu¬â-Uøø”Å‹ä>íÿMÌåI÷º¸“ù¶ŠŒ¹YlPþ꓆+m¶þñ¶.ârÆm={ðÝcÎje{pù@ýKìÑÖìÑÎ{„9ÆÀ¦`S˜Ï`SÆlGe?ŒŽ|ßÇlWQ–ÞmÍZy² /ù‚@ì.y&6Ž”SpN^;EN;rÚ½AË:œ¤s2ëVϺýŸFí…n· ¾h“ óLÝóÌ&Ó°]XvŸñeþµø,þ>‰t£wÊoäÅýsîž{úü-éÏ#ÏIÅ}vÓÃ}WñW¶Ûlâ2"kŸxbéfÐ&9Ô ›D°ùû‡îwCŸS9¼ï€Þaq™ms*{oYøƒ¿ñ§ì¡‹¾޽„¿m·¿ª,^À_açJð÷^þúœ’hôððçûµLÌ÷²v©¸*°Ÿ´?øƒ?øƒ?øÛ<…߯ƒ¿}µ¿ ÷æ¦î ç+¼ÌÅÁË|'/Ùe”}PöAÙeðÇ>(ü±Ê>(ëøôðð·}Pl…öwœö—ÔswxÃnÂüÁûŸ´?ö?Ùÿë`÷š]ÛÍØk~É>¥¶ äØkf¯™½fÆ\ï\kHí˜KÁß+øË,žÌYiðøzÀ¾ð‡¯íû rý¤Â5wÖÁ¬Á¬Á¬Á¬Á¬Á¬Á¬‰UÿþCøá?Äú1þCø±Cûƒ?øÃþð‚?ü‡èÿàþàœðGûƒ?øƒ?üg_µ?Èéþî½P÷~öžƒÙ{Ä8íÇÆÜk섹—8üË÷ÅØS\;ú2p[7ÖÎWÑ·=¯Y³VŒ}·}âÆš$únànûS nï×7Ö“XOb=‰þõ$pc=‰õ$츱žtÌ|hèÛ¼óil„û|xͽkèwÊ^³ëΡa§;mÚéY£ÏfLWØï´•C N~¸zÆÅÇ…2ý±Ý¹À¸Ñ1-b…EâuuÚÎh½]¿·ë•X\<ª^{ž(-¬y<`Í/7JÔ­‰DQ»”§NÑ=×&-ž§Äõü«ý;#GÖgѳp-zv0‹wz²)‘óÊ×Zv}®ån¬¼‘5«w:Ì€¡©£µ\îÞ\¯Ó3ÜÉÖ¨c5tYv›zDmëX=»°Çö'_—%‘-Ó¼îMÒ¸¶šF†ê3÷=I*åÈìûœõ,¼åjþ—£ÏXÈ—tFÕzF/8¿J晸µV‚SÛ¦/b÷¬ÿ÷}od¸Z]/DoqöuÍc¯Íû´X9(íÈf‹Óº±=<¸>úÿ=Œín±€UÏv:étT£›è¦Ð­©z©¬]{…^Z[ìÞQég6M/CŸ%7¸MSupî•MÕí¤z–ãÓñ{òïî›%‰—µ’ý*ôÒéo,ÆS™¿Ö¬|Ä»ÏÃQq§Ö¯wv“gé”ÙÍ©ž}˜YH*f#Å©^CºšIlZÇŠžðWªõ±G¿É'üÆQz^¯˜5= e­R(ÊÖŠéFÀ (ÊübVÒÁB ¿ä—Àe°Wá7h×LÍ{Ý:ZjOÃ6| _ÂAÁ©ç1^(ƒóA5ë¾Fã󛊬 \ç:×¹Îu®së\_v½ÚÓ+À…ëa_göpÊ9Í|¢|ùùäóS†ÓE­ª°?{ÿ!Õ)ÚçñºÑžœ¿ú‡þ¡èú‡þ¡èú‡þ¡èú÷~ý{*–J@,•Nýú#EÆi¼ ˜Ê”à)*Y<¥¼¾6xI¥éôÀ*žþÛ¯äµA1Ô uÿá~‹åtê¿n‚ñäW~ô™wtC™R(”v1áv¯× ×átm–øÂf„XžápíbÆZiÌæ¬jˆ}o{¼tø9‹‹0Ï>gíø‘œ{æÜ3çžñÇ?|öÅ ½w¿]û¢£÷RÞ­c½À~¿óHà5Xƒ5Xƒ5Xƒ5Xƒ5Xƒ5Xƒ5Xƒ5Xƒ5Xƒ5Xƒ5Xƒ5Xƒ5Xƒ5Xƒ5Xƒ5Xƒ5Xƒ5Xƒ5Xƒ5Xƒ5Xƒ5Xƒ5Xƒ5Xƒ5Xƒ5Xƒ5Xƒ5Xƒ5Xƒ5Xƒ5Xƒ5Xƒ5Xƒ5Xƒ5Xƒ5Xƒ5Xƒ5Xƒ5Xƒ5Xƒ5Xƒ5Xÿ{o×+9Îäw^Ÿó)ŽkaÏÓ35mQ¢Þ›Êg, ’ít<'·ÝÐ:uZÌÊï¶Mºx&–ý0ßC{{wf¿‡¶ŽMÓC«±í®ùbB»ÊþNlOôÚ{½ŸûöX1´-ñÒ¶ÿ–†­wåк@è]  t.к@èò º¨j­ mDû‹öiUa®wUÊÏ´Á!–íHl}™í—k¿÷Í:Þµ³lŸµ ˜O‡»w<”Yìw=ß»üNë¡åÁؾ/;Í_çò[Ýö!{V׎Y{LXÙf¤¥¼"_Ûîsì;'yçZv5‰é)›šà‹Ú®*yPÖçøÖññ¦i[9”¥±m›þ”ýÛ‹1èÆj(lnIs­ü‰:óÊ'Ç܆'p^?||~zúñë÷÷_¿¿ÿòy÷åå§—<Í£òê·Ý{ñ3|~úóãóóÓæÒ§/Å}ÅÅ?ÿ¾+ÞLL½Å÷}QOWü*K^›Z²èòw´ëma%˜oQÉeÏï zî‹q|²ËúïÑÁ¼1¢X^1Ÿ…üËÐ#¯ïUŒžsÏúîQÌ|cÆ}9öãq2bŸÚû{îÙ¬l,í_‡±˜Z¼3“Êq­¢ß|lW¥ë–*¬Uu“,úo¥×êH¬O6bMáütÛ·¡»†êêá©]s}{ôz1±ºz<~Í8v8 _‰ä þ)gøHœëãà5¦É„urnר÷ôùí+ý~òf<{ßU•QØ»{h_‰½l/>nø¸5Xƒ5Xƒ5Xƒ5Xƒ5Xƒ5Xƒ5Xƒ5Xƒ5Xƒ5Xƒ5Xƒ5Xƒµûé!{éð.r¬Á¬Á¬Á¬Áz²OjêOü®ÒO/hÊCâsm_èÖYéäNçØ}õŸ}æøÕ~ßú¦Ž=£ÞG#0‰yÞà Þà Þà Þà Þà Þà Þà Þà Þà Þà Þà Þà}!Þ6q”MÀ]Ù6€=¼Þà Þà Þ«Æ{6±ûoÅqå+S¾Þèšà›/f}uâÿ÷å7(ŸßY6ð0<Üs-jüþîÆÃʾ×}fb=“Úºø¾ž™l†¯ákø¾†¯¯ÆÃ%FIõ»üœ)O{}V$öô\NÌ(Î@5Xƒµ÷gûlËr.M×u¶oH§ëÍ• óDäí¸#um®ÄÔòfù·n®ÆNŽÅnó$&q¤GäIÌŠúƒÑ=‹z²°¿ê0?Réiâ#igÊní+2NN9s*õÃÓÍ855ŸåÒa_³ø^¤µ)‡L}ëØ¦€­Å·H :˜òsê0»Bêe€5†›¶isͨþ±MåY³ÈÏ¿™¶;ø·R=ñŒ{ÄjdBªRÕdw tûSÛ¬o¦¨èPšæ~æúÊ×'tG [íG<²±u×õ ݨŠû[i‡:#·îy¿>ò~¹÷nð\\¿ôÌìCrÁûûŽ,v8ÕôH«õÇPÞõÛ¤±íÖ¶Mg‹tï,·Ö+£öÙN~e›EŸœoÌàZC‡á˜¥FñÚ ©Òž–©nߪO“>]Ňbv¿ÕE¶Â•E4Á œÌkÀÔÝ1껾ÊÔZXk6’Z|7¿‹6–¬Ñ°÷s_ñìd*KÏÚ8–ÛM È N·ñ£ÛFÆKWXëĬoÅ”¨+¦…ec晽]-Ù*^‹&½¾VùÏͧ)Z|7¹Ó]þtcÃ2ïr¿]®m™ü-©ì]2u½€šnFöÓ®×}UZù°Ó æ[´»÷ïÛNNî¨é›ù›ryä“ÓŽûX[Ý=%KwrÕ—eWµëuß®_–0x¶§´ð4ptžœäicïÊ6´ ‡ÅqoÛüštsÃ÷Ø8“ÐÏu¹¦á spO1ŠŠ½ú¹­Yä¾K6øIß¹§àö .ñÅ?VîÔÓš6¢öZDï+Uªö¡pê¹PW´] ¹õP=íÙ¹`é¸ÕûŸNåÜŠña§fÇs)˜µ6‚Ju$“zü9_¦pÜø[ &Q£¬e1 7-É_†ÕóH$ÎEÌå£q³¶þR'ÏÁ^º®^hÖ¥]Þú>¡·uãUÌÝú]GjemÆXXÝXˆ'¨E÷3kl*«±!YÿåUÈó ÎÄÍR7VÂf³òù ™~8Ÿ3æÙ`íÇž{ì °'Àž{ØqÙ€—À ¹/Ý@.¥‚—Xwaéê…k±;ÇM,…ÅËËÈŽ¼œ<ϤàvÖÚr¾5¸¸Û¬æ…dü¾&¸­Ço§>B™°^ïµ53Vnn÷YT|Ckë¹UlÃsö^ïÊÀÛx‡©'ñˆx‡Qä#‚슺"{Ž;+¾ëWVgƒÝ÷ú|qÜ–AÞß-’šÖ¯Õ9óúoæl·™óĹs´Ÿ7íúÆÖYÞkƒª˜w*]õÅ=gÞaBM~R=gœ“ØÏ#ÎB­ZÕñæ¨Úc"³DѼ£³<$ýØ5Ãá'ÂÝFn©]½dІ¸ßÞ›DgûÞwìZ ¢}Ș‹c£~ä‡KÀÚUsdXü£!²Æ†± šmœ1ã=µ±)rSѳÊ*lÔ­úù½7jú©Ø²Êª\Jð‚êL_»cÓS”Œ ‘%¢”ôE²pÑ­ûJ9˜PZ7f–Éì f®‡M¸­TD1Ù›(6&嫳ž}Æt§ïº²³eüÚ~gë{ZõÃüVÑ5âÄÏ™'xmц³+x¿ã¬³(Lõõ8w ®¸oƪ[¥yG±·Ø ÍîçÎàÍÜZÄËØ÷~ŸšPT7Iç€ëPdœ¹:†\|þ¢®<ȪNo¢_•Ϊ‰aXÓ;l tõo{°Ì%š”dHhLÐûÁôv4>E÷©cZ5¨æBç«âšwpÉFZ+pÊ8 f– eêüÞÁ¨·mcVÁ„Ýæ]Ñ/b~fíèý}ƶÛ¨{{$›ÃÈþõ®†Ò8c«{k` ØÚ@eV:¦ìãê3kV!ul¿¾{¶{¶Õu³ŠÙí›zûlf‘§6³hÅ6³±ÑŒ£fÖ_ë6‚¯‘ï5W+g«îgؤ©tQtt5“Åö™¬Ùºsá:e:why‡ÈS5<-J "‡†21Z^a\J­rˆ¦Ùı6ûI.¬ÆÒÇÑåOðVرôtyL==ò^5éŠe¸¢¥òÝ-S¡$cöâF_¬W4[a‰8¦¥>‰N¤>I[Ñí÷uòjua¼”HßwMHÞì~M¹—Ò‘<¾oÖÅx…û Sœ’Ôuv൳¹]ÕmÛ&§»ÒWÞOÇkÌì0¦Î:kàN8é¹~ć«à·­]!…ÂAH÷Ø9ö‡Ï¹•Sâl¹hG&=©¶”(í‚Nåö8K=#ìO3Üåï^)j="‡F)QÂ*Ÿ«ùlÿ&Á’ÇB$D€ÌM€À‡ËâÃ4hgÄs¸Ôêì¶1í–ïÈņX, ïÂpþ¨ §ß)•ˆ©/Ñ··)pf1©\²££¦íè¸ûN V' z…J&Ö‡ò˜Â±f¼¯çuéýÖø–™ßq'ËUY¢þâ²:ÉÌNû¼ÿ½î·«ÛxM«¤lMe´}Å.…ök¥½ïŘ²º×ôJ±0S[U•}OCš^©1ã,ó;Îòj¿OÝ: Et™ÜÒR‰(ÀIÛœÎgâ¬Dô.™˜7G‚Âyã<›­Ë+òCwÎôHþˆ&nwËmoë´Y;æ1þê¹& ;Gõ´øÞ÷÷Ìb4˜×4[¬£8úh‘íX Ú¥Ãî&—Ë;—îStyD/ÜŽ×i®Ýæsæû“¼xåŒÆc7/m÷%ã;f¿’×"a³="ã#|4óÿ%‡îFã%ðI­Ý»‹UÝŸ7aÒr§Û?©¿Ô}é“Ì=—é~‚!.C¯m9W˜£|š{À¬Á¬Á¬Á¬Á¬Á¬Á¬Á¬Á¬Á¬Á¬Á¿Oö±ðûÄï¿Oü>ñûÄï¿Oü>ñûÄï¿Ï»è~±Ðõ2‘ö'o‚.ƒ1>`ˆ_(v5l˜` Ö` Ö` Ö` Ö7ÛßSžbÞi|Ÿƒ5Xƒ5Xƒ5Xƒ5Xƒ5Xƒ5Xƒ5Xƒ5Xƒ5Xƒ5Xƒ5Xƒ5Xƒ5Xƒ5Xƒ5Xƒ5çÁñ_ç<8çÁ9Îyp΃sœóàœç<8çÁ9ÎypÎ2ƒ!r»Xƒ5Xƒ5Xƒ5Xƒ5Xƒ5Xƒ5Xƒ5Xƒ5Xƒ5Xƒ5Xƒ5Xƒ5Xƒ5Xƒ5Xƒ5Xƒ5Xƒ5Xƒ5XŸÀZ7g,¼Ã[·ÏÀßð÷Ͱvg…·8ët óA¬ã~ŒRëÿjžã™Ö9žóFΞPžÉ*ïßT8Ê18Ê/þŒ3aC<îÍ <Ó´g^íaÞ`Þ`ÞXß¼1á|&r“ö0¯0¯0¯0¯0¯¬µ=>>?=ýøõûû¯ßßù¼ûòòÓ‹RY–—¿íÞ‹ßÁóÓŸŸŸŸþ0—>})n,.~øù÷@f˜lªOó;+¾ï‹í‹qpåXÑ#îq%Pâ9=pO:þ}C%‹.ÇA»Þ¦ƒã9Ï=º£êËfsû:‚àü²ÝÙ±eJrÙ»¦µ=þw3)Ü«-'ÛøÓÊK8‘767nºìùRÞÏ{u÷™ígÖ÷«—J+Õ+«‡Åy"”³JçÒbqQ*…nÔm êöæjwãó؆甌nñ?6ãØÀ‹‹ &øHÚkL“ 5¹]hÞÓé c´¨Û°gÕ$ªøóXðÏ›`/ÚWb/Û ö`öóÃ>› ï³8™eyŸs4›þ2%Øshu½øºzŒ¶˜k#¿Ã=yL SG­7ß:°§èûÙFßN1uO½6ÕPÜG«£Ø»D{ñ^¹†Î âÞݘþÐú\³ H ÿBéU´`ü3þ¡?ô‡þÐß9cAÆ?ô‡þÐúCèý¡?ô‡þÐúCèý¡?ô‡þÐ%ô¡?ãßúDÐùý¡ÿÈ€±õÍ„Ðßkú—AE’uÓŸ`Æ3k°k°k°k°k°k°k°k°k°k°k°ö9Nqá[dXƒ5Xƒ5Xƒ5Xƒ5XÏ>¶¨Kw«¸¢Q»µÆ˜¸¢~ú“vh…?)ô‡þÐúCèý¡ÿºwò>9»ÖéÓª®òoÚêËö³¥—'ö»††‰KžëÞ ýY÷äÒ°4q}¯i¡ÉÆþ-m÷Ã7 ^’”&™NDŽÒp G©JÚyIwÅ;UQoféorš|:­ò©Üæ4-~ïtuŸ{¶•[Pä+Ÿ‰›ümÚòp+ŸY\Ýâý—7û~“ëómz[Z÷&ýmQ†§ÞúsŒ%™Ÿ)ÆlžáÕ¥›²|Ö~lû]w'7·ë¯/7Ÿ6G´áâéÔæD/çÅ"g¶½e"?½»fsœOÎqïrÚçö}úÐŒ4yJ ›¶ÜrZ=—.i_¸/;®ÝîsÆÖI~¾·é4=žkt»UÅ#e}nL¹1¶*D ãÒ2º¾”}Û7´.Ç«åíZMJ:êJ$êÌ­úÁï½*‹NÆh,Ϊ~IQâ»Iq?t_´9#{zúóÕ3²ß+‹û#оæûòJC½äFßevxœù—ù̊Ͻ²´;q¿áí¡âxßãUã¾×â@UgíÀŸìŠ8°qWÖ‘µûÞr ´©å¹ò¹îfˆØÈ(7-ìÆÈ¨ÏÜ:£_²Á)yf'ÎÛˆ´šƒßO<ëÞ•ŽØÔô„öWÅ:mœ¼>Ý9‹s0 Fn3žnOãθ)¿ç‚ÖŒ™éòI5gB]j¾–,R¿MÅØ½5âë°ìûœŠc†`†`†`†`†¬¯<¦©ñJÖ8>2±®‡Z×Ý÷©Øf#c´,W:îàs^8¹ÕahcßÔö±îo0…/Á Áð&’þ€ô` Ö` Ö` Ö` Öø¶áÛ¶8ß¶îooÛÙXaéìÈYǧ0=g½b{½ô)“~}~šNþecPr´OT¼b?M%ü2»¾šØ©Æc©;±ÿ£+îÛiöµñ C0C0C0C0ÄgŸ½•øìúŽÏ>)øìÁ—`†ø›±Ï Ö` Ö` Ö` Ö` Ö` Ö` Ö` Ö` Öø½ã÷Žß;~ïø½ã÷Žß;~ïø½ã†`†`†`†`x •XßDgúó‚åõ|£üÍË5a†¿9>©Ø]oNÓµÛUñÙCüÞÙçeO¬Á¬Á¬Á¬Á¬Á¬Á¬Á¬Á¬Á¬Á¬Á¬Á¬Á¬Á¬Á¬Á¬Á¬Á¬Á¬Á¬Á¬Á¬Á¬Á¬Á¬Á¬Á¬Á¬Á¬Á¬Á¬ïã=¯Š—˜wÚŸÃç` Ö` Ö` Ö` Ö` Ö` Ö` Ö` Ö` Ö` Ö` Ö` Ö` Ö` Ö` Ö` Ö` Ö` Ö` Ö` Ö` Ö` Ö` Ö` Ö` Ö` Ö` Ö` Ö` Ö` Ö` Ö` Ö` Ö^`ŒÄ:9‚õÈþ™¿•øXZ8Ì’íôþŸª»Ä¨x—.®iÛ‡²-±m—¡sVѯÄ1_gMƒÎûõ‰÷G¯ <#ë˜kð<OÁSð<5kž2ñØ2x žº¡œ*ž‹tóÙwm6<•Ù>Œá«´ºÿ®ë"ѾrmäÚÀZ”µ(Xƒ5Xƒ5X/3îs(æúS˜‡Vÿ æ§û–Xlª¾™z®u;컌~Z¶Ç½'ß½µü í{òêeY»ï©lóÆþÍòRù\ØicÒèÏ«ò~=R?/Þ;¬zúW~wíÝ4t18Õººä™ý-ÆcïoÇ3ûö8Âé ®ôp\ùJû«c=„åÎêí’«©8›wF+Æ8ìðnÔÁÔaìä_f1V½`Îiµv]%Îi…W‰£Ãa'>Ï‘ÁJ±Ô‚Gå÷D|ž#k­|Y%¦I†çb™€!‚!‚!‚!z†¡ë›Èê|`y–™ØŸÌÀr–;±ïyL³¯e±¦î~.x½h»ë5iºv»êJÇ® CöyÙçk°k°k°k°ë˱Ä8îÇ&µzºyög¹¨óvgšqæån~|C¿‚½éj>E]?¢Xؓՙm ÚþpW]ëK³\´ÙL¥Ю3&T3VZ›ÚvG›k¶ûFû„´¶Ñ6Ú¶¨¶©fïÿªm”û¶‘˜çn°Ÿë³ýý*m ­Î ßó¸ã/íK[}Æ‘¶-¯m>ƒ4ç³=s?33‡³(s:ãÞDÛhm£m Z{úÎÚ‹µmc}Ãú†õ ëtÚFÛhÛ}Ûvå3˜s8Û8·3ƒèÀ葬!Xß°¾a}Ãú†õ m£m´õ ëÖ7¬oXß\½m>>?=ýøõûû¯ßßù¼ûòòÓKEAX^þ¶{/~ÏO~|~~úÃ\úô¥¸±¸øáçß3s8§h쾨|_0I``>·E廢싆h{mšRUX¾ÆÖ§´hCõ꣱/6öÜ]Úœ1{°Ö%‡Î»uÎôuÏ´ ô>æ½óMsQ¬•f•ëÉŽ±4ih¬m›j‡6®‘{6˜hO%løáÍOÅI9§úH;ŽîÕk+§¥\°4ÏxG%Ö­¡-yÇ'"Ó“˜&Bw :|þ¾øŒ±‡cÎW¾õö=:Hdc4¢9Á¶j¾ ¬&b‹iZæ´Žâž}Á6iq=(Ô£½ÑNÞªgÍ5w_©Í×¶Z<»~vWt%7šOÑŽWSÒN½Au©éæ¹±í5ZS·Ny_·½añ=x›^Lýç<÷èbÄÁ£ŠŽÏv³¹}û‚`b™½¼ÄOµ=þwc–¸äý·(åjmç_»‹ºÎ{¶»FϦïç–p"onL—ô²þÌŠfêò¾î%¿ºÏ×éïJô x}楔ǡГqÏ…ÊÑ¢çß³é»~ ¾»…î¢òÛŒ…œ˜MYÚ\½Ëzæçp¼n‹(ÊÒÜØk¶oç•{·Y/œ&aï:§gç1ÌýG‰}·Ø'sq5uO>ŽÈN=iŸÒÚpë}»5íW:ûõØýÊèòýÊØåI°4.Û5¹Þ&k™7ÅÅbµq.ëýM}ÁþæÛÐò¬’ÖMÏÞÆÔk2>â¦i›‰ jö â ó¼Ûè¾3ê¹vêq¾(›Æ·çàÝñŒø<´ûEñ}ù\îË?‘ØÖW¶%·û7Ù}öíÚ¶mäkß>U:Ñwfò¾¨ÀÃ;Ÿ݉¡›[š¥ Íœ¯[Ù~}ºKch6 AÄ>´ËΠ¤U"d]Þć=E'dÚD™–vÚ„LóG¦…‚>©ðUKš34Ge™ íh§Vo ­•ÛÏ´Á,–íHl}Y;ç_=Þ2‹›ÍæÚ™j‘0jhôP™öøtyKŸ)sc+sS!ƒóÆ—ý?ÍÁ¿s”¼M¯¸Vr~B©ðÚ Z¤â9Õ¬gÒPøé¦}åßó†§Ëº•¸¾Ègq ç´½nwmJ“åI‡™¼^Ò¦'¦³¤vIÂÆoê€f‘¥Yt8ï”ã[wü§TûŒE¯” %¤Â© ·ÎBº2B¾æÓŠ1œ¸ïé¾ßˆo*gSTVÑÆÜÿ5ïïÉÅûwAõÞtßv†Ú¦Õµ¡ví |¶E½ù¾ýnYÞvÍ{붇ÕâÚl6Oëß»fCAIgÕ~oæœÁÔ!Vu_ßúû°éÃØÕ•5ïÌGЦۿÔòœyáyÓWã–¿Ûö¾¢à¾>÷щ<3K´cåÓüíݹ§YùÜo*{›ÿ¥,OÃ3õÕÎ;uÏ;¬4í18»Ïp7ýZ¸ß7H5:oš¾çà½öy‡Q5s„|;GðÜF̱˜û¶‚¯¡7ÛçÓh¾4Œ:¥Î;tâ ¸k±Íq½ëô­ø~‚†uÞ#›_¨¦aضCž¢¡{®n÷Úóñõ:_Í›†5ù‰17:y’4g>Ëï[‹Y2b&¢Ý^>?–†gCÕ¬#–@»:Y\ë±fñãôª2ÔêУehÚæ³ÆŸ‡³—¡a3\l$jÛ2Ý™Ž¾ûœ MÆÊP)·•}O2‘†wL¢a$ì§V‡Z„ ÍĘKŘSãt™î|4HÃμ[Ûit{Ÿmô8W¢á¥¹_YË·–c uþеËôµ k†óe$ºúùº::ò}øÝôìÐ v}û‘IØ`ö·´=¸cöÑÊ=â´ dà¾ËböåÌþ˜)ò»)CÏ™:’´ÿP‡Û¯6Ï—8¢ö~æPÝuÁp[ë:ãæ@ˆy羨'Ý5Ï™½9ã瑼îË%™ŸîèÒ%tEn裶ïD¸“9mÝ%ªíÎtõC=ï>FNí‰pסv’¨s­ï¾¾kNÍ Úîέ½®×]ë¹Ï7¼uG8Þ¬Ç@Z¹a…öÜwíR£:üœ4.`µ‹U\M#n\$Ñá1‡V8í‘òáh¿ÝÕqÛ)§¡Sn1Q˜ŽðŠÉòf „D·•·Là$¿•ÖµÄßX¯ MóýìbêS6ÔωRÎ&ÛËŠ›-uÖÌ@á›Á€YhÁÓûÎFÚ¶Å9¡uÊ+ÿÞu,•÷ÅÓñãø:¦Îú°Ô®‘rußâC¾-qÛÚƒT¡pÔb±g1÷‡Ï¹Y»¬7¯JÝŽLH}Õ–žiØðälxQOp4UMÐéÞ6)áh郻xÌXΑ¬> ·‹©¬}®×q3õ‰EÏ .'ú60ecü3•mÞìÄWþˆcJ`ƒß]ûõ µž½¢Pê|öpf‰?g–¸IñÁúfွ]ßÈМŽ÷¤‰7m—K®ÍJËQÓ\fùQô«>ÝécÝÞ7!gÒã=í 31~úäEŽ@ ÈäÀépÙÒÞél›õ–’¼M¼æ¶]íªiVÇÈãiÇÈï*:í[š˜$ ‚ãG—.ZéG¬ô;ýë]íçzÌiÌ2aζúvcí³&aNLHšû¸*&Áνƒ^`ç‰5Òž$î®.IQ4¤$9LV¦1=‘he®xÞdG{ìnö„äp}8µ³­Nìjë|Ì^B™ Æ:7lŠ*2“Øfß³»ëíæîª”†©‘²n%Kxh®)BÖ-]g’1Â&nx¥^²l›ßÉ1Eú\aêQ.2Ênì©þïf/¾ùÞY÷¾ñEˆ‘{¾díñqt÷•îc܂ґíKÏiŸ|Üøë;U¸ž½¬æäüèÓ mÜêú×ÏÎõ¾ƒïà;øî |wÉyòÛÍÿ=šr’#Ž4¹¼¬ÝMƒ`š£¢+é0Y»=å;ÏÐaÌ;Zò\÷chÒDœKÓTGSón_u˜íöˆ³?½F>KG»«¶ýÈut}Yßç'žÕ=sÔ¦=G™gÖ÷6ýGnÓv¼ïÙd#æŽ×öÜaÚ§¢þ>ovÇûÜ—3[âµSסõ-øÔ|¤u|GZëûÑú¦µ}^ê j×þ8 ßÛé>oúlLðuŸóÚ+Aû}6îñîÐI|_ÿ-rÂá%ö¸|ŠZä%Ÿ¢ù£&—‹ïz4‡b‡žÐÇÓ|°Gè„üš ¿¬/A‰§F~-2Çu‡ÆÐÌS™v„NÈ´‰9®3ç™æ—L³8¸¸åå³§òZ zBäW"‚Áæ§é„üš(¿T3G ¿<“_¡È‹žŠãîù]¬C[hõY[9¥D^¤­‹M¥×ò­{÷˜lÓw#Úf'ŽçÌ)Ï)2˜ sŠë|·ÆO›\bµ­?)ŸÔá>hæÑ:ÿigœÒYç{+Ó”a™à¡D„ Ïûé }î$¿1ÇDíyæMUeUÜi²Ê/YåÖõ¹È«„­2<¡uh Í<Ñ¿’¶]æi÷ŽucF¦ÍÀN©E| S{Æ‚¶ÐêN²,²)ûÁ‰ÈœžO;ì”Ø)½×“³†×™S<›S”È6ò¨NšÐ‘}¡—§6Ê#tBžMgnG?ž—}2=\7öÊ3A_èõ`Y ú‰õÍc䘰‰!Çfâ”´ýózå— +tz°/RÚ¤>—n¬ïYß{=$ÞÅ<âáYÉLìù"jþvT. úB¯Î'i£ó:]àº1Ÿ0Ÿx¿c_ñv]"ΨÖ{[nO~Ì>VŒmå¡¶•Tø«F‡~0C4B†MÜóB~ùë– °¤ãKØïB~ù!¿´H^œÓÙu†~„þbã‡th Íîè –52«žDŠàƒ=û:!Ó&ž•̾ȴåìwuh ­<Ùó Gžo vJì”ÞÏ)º±Ë3§,lN´…V3Ú÷:B;ææ”YÄnM°½xëO!÷¼´(Ñ_Š}¡×ç•°ãWœ LG'8F'äÙD[r$Χ"Ïæ£*!Ó:4†fÞö⃠tB¦!Ó8¶Ü3`C3Ï´Fœ»Êº3bl‘ñ©;´…Vw”e¹XwÆÂ¦™ŸO3l˜Ø0g1—j™ñ^:ô…^óIöà¼Sy§®™w 9¶Àu¾ /ôò4ÖËcc½(ü”¼]ÛÇb/óèÚ{t…NžÚ(h„ü:#.uÄ>òbu±¡Ù€MŒ˜YÄëÓ3‹ ãë>?{2ËŽÙ§Ù—9)ÓÔÚÝ:nŸf_æj9â‚f¬!Ó<’i±È…uÖcsÇ9Ý¡•çëþ#´Ãw ߥYèÉûc‹õ‡ ØóÆV7uèÇûbǶvùÝcËŠmmé <õW òëÌ<|¬ï—m³ Yç{¿3@'dÚ™–~q>JñàiäÖ>Ù'³™Å€Ç.ɼ!öñq]°?RˆŸ«z°²´³ký¤pÁçòOɲ{Ì#JäIf.¹MÎCl*ËÌyˆå¾±ƒö9®rÞ…=LG7Ö$¬IfaŸWØ´ë+¡°mÍÂWB‘×ðb_☳‹“c‚®Ðif>ÄñÌÎG 3DÍydañÙ3l󳋣;@7ææ‘Yœmä¼ö|bèfbŽQ?;#ÇYmr±EM^ÉVÜÉ`¦gµµØã;%ÏòËeYlë­é®+Z×|gÚfx$µrÏUµ+yïwýšû¦àhê0ï5¼§w?–Ÿià ®_%¿döÓbXÖëdí ŒÜ.ùöµº¹î‚}„¸ñ;f®óз)~áÒïŒ_“ -´šY’Ú±~bý4‹õSHü¾Eæ éÐzy¾~ ‰ßw•$vþFžyx6úq.ƇjäÚ¨\$öYhvG™¦:k›T\ÊEÒC'dÚD™¦šsÇÈ4eš[³"Ö…›û;sý L4†fwôKÏšJõü” ßš¸#Óè´8™¦ïàGHž8ÇF(t²°³}lú§‘3n˜nñ•ã,+±öÔb_N®C»2-&_ÜÅþlð‘B¦ùgGS¾ïÆBØðÿIÿ(A_èõ;šðA¬×–ŽOòÓtBž™Ë—óÏ~úèÆ#Ïu:B—ÚÿÃN À` oo€ÿþSøOÝ4·SÄœ¶ÈsØ–¶ÐjfþS´Ã ÿ©Y¬“ðÉ]®ÿTˆ_îÃ×O¡ø­ŽøÄäÀ ÿ)ü§ðŸB¦á?µÿ©l¤ŸþSøOá?…<à ÿ)ü§æ·7“5s2mav´}¡—§þSGè„<ÃjQ{ÍήìÆ]Š/•×2,:µ´… ò¥òM'Ë›ù¶@»Y‡ÆÐìv3w>AøÎ´ô²:a7›xN'í´ ™æ—^&mf™ —ãðÔYAã%Ðl”Ÿ±lGbë³2©n¿÷Õ)y?í‹„ß±¥•7²Wèë½¼¦š=¾³üó¦¾ZÇ §óÛ _ øy§¶MæÙUø$7êë½c9éfo­?’ÐòJ¹Ü×– }­{OkØ»¶Ðvî¹»ÖšûÊ{½IuΆ{Í[MÜ蘳εsÄ·ì·ìã-ö2!{,NFì'„ͽ‹Ð…ûâšÞº÷XÛ8Ûj>2Ž{:cYú&w£fŒ/Vî޺КÞ)çàZsöA_è;k™œ4{>³—ɡХMâÖ}¼¶ÏgÖ9Ÿ ûS6Âï3ixcUc÷ÖýöÉ·Wðþ,i,×zªYö|ÊnÝG_æÚÌ–he¶Ä[÷Û·¹6kŸ+^äúçÖ}ôeÌæÿlhéâ´*1öbáóŽ˜wnÝoßè«$“#ú };çξ± ³Ó³‘rJ5q‰f¿®›>Õsí­ûxmZfx,"&Ëâi™Š½º¨'îÌÜhy‰Ü…–Ë¡e83øZsh88>[Q§¯c|}‚®s®åçÌh­sƒœÅkÉù» ºF3õIDû3q9ëœÕ»G?oM׬ÇkÑt:^£ù>Â-íŒÛdÂÜ“ÌxO`ÌúõýœAÎÙµÄ\c«®œcçœ|·î'´…¶÷Ò›£Ä>=g/þÖ}÷1ïµnæ¥EøÕD=çcïÑO߯ðœsd]:†ç’;ë’XP¡X{¬Q—¾u߯½Wtr+«‰tžóY÷KÆó­ûîS\qu¶c:êØ?Âæì[k>¾u?}¤ëcC…5R.t®¤GÏÊ~ž_l¨Kuè|%þùŒü3®IÛxf´í[©v.ˆV¬¾{ôÓ7y,òš.ZߺŸ×¦«º¢Œ±£FæÈ «?Ý£Ÿ¾Å`ŒDŒž%ŸÉ¼G?}ñ—‹„?~¾F"Šò ï×¶ad"–~*ì­ÙȾ¦ ±UCçtEû¾Q;§Õâeu¶ŸºHÄ*ÊW˜ƒøÖ}¿6Ug½¶ÏWŒ¢wzçØ&"îd/ÎÇè\ÚçbHÌaÇáš´O:y¹‚‘ùµõ‚ü)ûrNߣŸ>éÕyóŽUí Þ£ï>ê[ùŒóO'"ªêä£Ë;cøÖýôIßÊgz–ìZcx gÊ óüé>?=ýøõûû¯ßßù¼ûòòÓK”y\^þ¶{/~ÏO~|~~úÃ\úô¥¸±¸øáçßwoÅ ŠîŠÆ§EÃ÷¢˜FgEEûÐ^ÛT÷E÷qûYw_Pܳ/®mõÈg r£ |5Â'h—}P=[¾Ï>«ÍßtÓÞî; ¡å;_Ó¦/òÝÌ±ï ‹OUÜ£tó¼aÀºÛªåß:ÏšºÍ³-lÓñÏa»ÝžÀ6¬p0ëâPÖÛƒí.îàP”m\1XÉ6–¿T” ¦«x)Ž«Á˜b`½U8—ÊöÛ0¾œC÷—dWÝÛ X\¾+¬¥PåÀŠìuóþÄðâoº¨3NìßwM ìÀ+ïÙù¾¡w¹¢+œì»ÝóŽN{ )ÛwõÛßeûÄûJLãà ÇCíÛWó7‡© ­ WÙ{’ít¬&·m[Õ'zêg°ûØöÕ´{ò5ÒÐ&6>¡™§›tB¦)ÓBdÚ";4†f3i!2íl™–‹ ÀÈ3Wa;ðëÀ³ !4¹“¼rŽ`©˜g¢‘›?ôòZn úŸ”Yúˆ¼}2ïOƒÃþÕm}³Ÿüºm—m 3ÑξµÎ\qö\Ñ9¬Ë|±ìCèÐêNÂóiÜÁœáýœ!˜2g,ÿ 4ôz`’VÕv¨;vÐyv†<ë$2Fž­#)74óP¦¡2m¢LÆ‘i MÈÙ &Íî´¾O;kxÝ 0wdÚidZ"‚¦ Ïü=ì¨DpŒ í°|Tž%íEÐk&sèæ½l Åù%¶Ì3äÚ­eW—ÿJž³¸ÕvPa»+Ûi±½„ÏÕ£;õð`ÞÌ5ZYºéÎ8‰+­“¸Wƒ|÷ƒ4j|­E£[à°¶yp­óÈýxÖæ³F»âZìrk´¯¬Å>Ab†eø”Ð{™û¼k<‡¶–s\k<³Žó,½õU4æœ~ÑÏh_Ï6ÕYpcWrçØÍw÷Ûœ5â¦è´¹¿¯öù´ègR”·DüÍœ 7:AÔœ{×AûùxÓ®olå½ö\¼¶gëËsìö9óŽ8­l*8<ƒžÄGÐu$̃aÎsíZØg"2¡Œ>®žˆ-“µUOFšS“ëoÝ{;¨µ¥ÜiK¡§Î³EdÒQA^ÅáÕÞ·o»yŽ“26qv7~®ØQdLžo½«ÚP~¦MßcÙŽÄÖçÜ/\{´ø½¯ø·äÛP´Ïò´ùtX{Ã#Ç®‚ܵ´ªË]sõ–zŸî¼KÆ š-àQü(s*a¦SÍt OΜ'cKsgÞxyýäoÝ<“D#ù:¹"/Êå¡àÅ@˜ld¬Ø¸¢•Ü.)yNUøÌo‡xñ@?{ޤEí­¹ÔéÊn)Eöoaó¾ò~§ã„ýý ^Tó$–´ÂÍ<³Q«²Nôªîu£]×Wc?µÞPû©õ†'Ü,QëU‚Ë5ÞzYº–m´ô7õ9¬Í«rT¸ÃUvÔ²žŽÛ˜ÙF[ž m½cÚZ÷1²×mûú6ûáøa6ü\nqPRüê6Îý$c±?À§ø!ôT&wÚ·(ÙŒ?@ð°yP-g¼—¼»†õ,Á,Ár2–ª²à•ïËKàõÏ:§;'u‰_­÷ì…e;këcò¾ò}NGÚ Ç÷¼ÁÜÕq@ÓLXT¥óûîˆR…I2Âi‚¶«¢.U¼{_¼o·µÞ{Š3/†ªÚ¸ßföšu.0›ýeðw»9Ÿï*‡>/žÉÌçþÐ4÷‹ P–ºÉ>eIcýÆ8·t£3:bófñ}Ä™÷νÁncÊg±¡¸ús\"É:˜ÙÍeQf£1 ›ÍøUÈï°I’ ýg Uðù±üfyn:ã,ùÁyÎl%}I‡—Ü×Úî·¶?;ç½ò3¬ú†N0až”IGàöðØ u¨Ïk=_׋m×Ö×Ö½¥ß^S‡¼¶žö(]èÚúƵçô¹Î›™›ú¶â`Lnà–G³ûžLõ`¾ög϶Cæ§GôSÖâu…מ?KÍ‚‡` –` –` –` –` –` –` –` –` –œÄ™z'É‚1'qL¨J³Í·Wöûf‹-±¡0Í;âXl½‰S;›muÇ…Ì l¬(óLÒ"3 ýÝ W"3žpz'¿a6µHDï×™Qm6áó®9‘‘‡¾‡Â£i/"²OÉ""Cç O”ú3îDKü8—)tLíöoâ¶|7¯9äpꆛkÑk#dVÜ™–2á…!Æxë÷®=Åõµ'ÆóÅÔñ6åĤl_¸/ƒ‘3“§¢Ã‡Ç}"+þQü/!HßȦL³©vã£;Ÿãºîí^Ù¿þÔGJªA•h·³ñÔóÎçëÀõûêøì#ïï^7°Mªß÷ömo[àØ4?œ•ò4…“)kÖ8fãÝö£p~¦Ü×:õ•wD«ÅfH—­/¬÷¤Í¨^;Åç§¶(Å´ ¦¤d¹hÝ«š,w­u¯ÇŸví”)“ìǪI‡2«1ä0¹ÓÖ°vüëRÃlÛ|¦»¶Hy_|ûcOɈúhúCÿì ô¿U ñ¨ùtuÆI¸œCÎAèý¡?ô‡þè¹è^Œ=èý—¸åøÙ+Ú‡ìeì¡÷ ÷0ö{ÐúCèÏÜËÜËØƒþÐúCèýѽнðkBÎ!ç ?ô‡þÐúCô\lŒŒ=d/ôǯ ¿&Æc½‡±ÇØCïþÐú/†þúJé$n5÷êvü–Åͽè=ìk²¯ÉƒŽý¡?ô‡þÐûz.c±ý¡?ûšÈ^ÆÞüÆÞX“jòúêöêizú×›–!QA6&Õí¦ŠíoÒÐÆ›*çQ_Ñqûwß;¶dÑåïè–àma%  •ݶJ¡¼ï¤`¦,´(›GfæÅÈxÊuÊ¡löëˈÃ\8:Ñ~æÂÑMÊUækOî·n¸F¯1•|t ÏüDn¡[¬ƒEûÊ5°kÃRÖÁjÂZH=ÆQc/Û‹Ÿ~` Ö` Ö` Ö` Ö` Ö` Ö` Ö` Ö` Ö` Ö` Ö` Ö` Ö` Ö` Ö` Ö` Ö` Ö` Ö` Ö` Ö` Ö`}›s3cÏÛ›ûãª\óüŒÞVm¯Ï0¹3%ñeçkÎnÛ¶9¯eÎçÇAu–Ü/2ç¼Ë¶¸wdâl¹;kž7˜m_M_G_ýñÄú]\óY~ß·ùYÛóéåýaÿ ~.¨øº>à Þà Þà Þà Þà Þà Þà Þà Þà Þà Þà Þà Þà Þà ÞwÅÛí[Û¸ùå÷Ì^×ÐÞoðoðoðoð^.ÞcòŲ‰­/ùˆ”ðíæ' Eû"‘7Çbþp¾ˆ-]Ýç뮢H^ÌÚqËG­]”X³d š|=†.µÿq(òCmàaxØVö½îÓÅŒ+ßó²®¾†¯ákø¾†¯ákøzþ|Í9KÎY‚5Xƒ5X?䬶ºá9myœ3Ú~Ù­mÀ¬ÁÚ/¬u''°“Ûõ<‘Vu•ÓVÛÏ–¬Ošu Ã3qk÷ÞHÈeÝäNusN6X¸ßnÎé3íº§l£èC9רù4xý ò#Û¤Éiª³òê·I™>?ýùñùùé:¹r•Gy·©ò.kCÇMõi~g›ÞÐÞ´o{¼è÷¸brŽ×Ïé{Òñï*Ytù;Úõ6½”9²ßæWæš¿|³y|Žôce»³cË”äÚùÓ‡‹Úÿ»±ÏÜ«-'Û:áÞÝ›?í¦œQ‰¼±YPßÕqÜñWî¬½Ì «[Ë}du½sŸßÏ_n]"“‹Õƒ ˜R®2FßÎ+Óëªì¥yš1T¸Q-t$Œ7a”ïzÒÃT×XžZ˜1¦Œ5f•ÙÖ¸stÓ£³ÙЭwȈ5‹Í‘ä RglŒœ»ùá5¦Iã\qϼ¢Á]7ù’¶óG݆%lö)kPƒ½:±Ñw ìEû¬Ó^°{°ŸöÙyïî½'ög¿V{—àÜŠù˧@ü™Ð«èäqÏï³7¹;ÅÔ=õÚE-­Žbï6„÷â½r1aíP:*Çö^]á’Úuô‡þÐàšuú‡þ ¥dê2þÿÐúC诠ÿšéŸXœ ?òúCèý¡?ô‡þÐúCèý¡?ô‡þÐú¯ˆþ!ôgü[Ÿ€ú#ÿ¡?ô‡þÐúCÿeП –±¬ƒ„q>¾%Ð-XÏé,”çë3ÎB“ànºP‡VèÂóÓ…Ýȃщ›ÄÁ•°‰‡o°N‚þÐúÏãœD¢¡ÿ¢éoÏG2þÿÐúCè_Óú3þ¡?ô‡þÐúCèý¡?ô‡þË¡¿‚þŒèý¡?ô‡þkœÿKAÆ?ô‡þ» +ã`>“ú3þ¯DéªÄïl^¼ÁùÎG€5Xƒ5Xƒ5Xƒ5Xƒ5Xƒ5Xƒ5Xƒ5Xƒ5Xƒ5Xƒ5Xƒ5Xƒµ‡X«b­àkø¬Á¬Á¬Á¬Á¬Áz1x]Ì0-p&¯Ÿ~öZë¯vcÌñV,Æ]\aŸ¸˜ËÚò‡ýlñb"Þ‘4¾¹Ç|lÝßJì^;üïøyÓŒ“÷¸¡}ͯò÷Þ¶céîÞ§Åxò îöÅòJðúáãóÓÓ_¿¿ÿúýý—Ï»//?½äY¦Ê«ßvïÅÏðùéÏÏÏO˜KŸ¾÷?üü{fäNñ®ÀðKP}š’ÙÏ$ªÞ]>â^-j«Þ24BêVï+JE¯U‹Çލ(ï)-Gvù÷9FíWvt¤#%«¾\¢jÛŽòšª¤˜½åß3Û7çÕ.ï‹§cõjUI-3ºun?ÓFZŒ©³ìMnÄ×}‹f»æ¾pkgvá•_?6ø†ûÃçÜÌ“X‰ä¢™éëšWÆgŇ¡—X-ÇöÓÑeßœž¨i¼iÆ|l¥u­¡YI^Ïôú‚™þŠm¨g¾¼*GåÞ@»ÓHÈÌ·íÔ˜«ŸÙW3…ù4¥wö‰2-&Ÿ``ò Š.ª¨ú”%3‘MaÜ\—÷êâû~ß÷·Ã + f,‰‘²9”µwdf¤ÔØÙ-1ç/m†KÆK”(÷#ÏÏØºKœ6Õì¦w‚ŸbÛ.««&¡Ð?§êý÷ëïoéú‰Èy°¸–µuýÅá ]Û­G{qpšX0"gÚœùAWýtgl“!±xø†ÃÕׇ;»ž528kd—ÔèœÌªeÙÙlLÜš±l›h{©EåGÖt:ŒG,év;{’ß÷f+%vl1K¿ÖoM¹´¨|½}7CÆð$|p¼”Ú.8œ.ÖTµ3ß7àA¡Pª²¿¢<ƒõÉõݵD–Ÿ|ãôµuêS”Ë ú(…Âz“B¡ô¯«Òk½ëp·Wé$»Ÿ‡’²{(6fäªörC»G–\Á÷öŽû¹¾ïGz$ž¹õ¸§?³h_éÓœŸða˜“_³¾‚×Ý-0—xëùÇžâ-Ú¶(Ìáo°k°k°k°k°k°k°k°k°k°k°k°k°k°k°k°k°k°k°k°k°k°k°k°ö-÷‚^`î5!çBhÏ$Î,†ªŒ™ŸÈþÚ@rîoe¤â茘™=ï×GÞߊ™©E\L}ŸkkÅ‘~ÓoúM¿é7ý¦ßô›~ÓoúM¿é7ý¦ßô›~ÓoúM¿é7ý¦ßô›~ÓoúM¿é7ý¦ßô›~ÓoúM¿é7ý¦ßôûžýÆ7ßx°k°k°k°k°k°æì)gO9{ÊÙSlXô›~ÓoúM¿é7ýÙoUéÑЛ~ÓoúM¿é7ý¦ßô›~ÓoúM¿é7ý¦ßôûÆý¶m0Ï@oúM¿é7ý¦ßôÛ¿ýçû½é7ý¦ßô›~ÓoΞâo>?=ýøõûû¯ßßù¼ûòòÓK©X•—¿íÞ‹ßÁóÓŸŸŸŸþ0—>})n,.~øù÷ݦxi .³©>Íï¬ø¾ß¥hX`Hn'–èô=J|ƒ3ê8R‚tÄ}›ëÖ9ª]o®æ³[Üß§”,_^™ŠÁ\‹ŠæÙî{9..}~}½ žÅ¤T÷z5‘”ÓƒG¢,“K5gèH$$ÞˆDÂ.±²n']î Ø6ÈllbasˆÀ$(.“%gî$EîÖ9Îë$Ρí{r…ÄÙwLâì5¦É„DäyEƒ»&#O: É]’”|tbrwï=±í+±—í{°¿ûÈ.†Çâ5‹çKä¹¶í)¯©JŽ—÷X¯3ÛÇÈâ+ï‹§c§Õ‚7.h¦‹{tn?Óf±;¦ÎÄêeµý»ëG|¨‡”Øm-?†Í½õ³aƒs¸?|ÎÍ{e½yUêvd"è¬ÅºæÅ°Ñ/È‚“ØÃN<{wï=±í+±—íe„ïáûec¯Œ;üÞ>:ЦG§ ì&ƒjtgCpm,Û¼·¿^³·:Œk[Ò\«õŸ¨{;¤~Lßz70”J#±í_;ÝÖîU쫽‹rÿÂân>÷¡ý{ÑŽÔÜ“UûîÙô­¹/0ûŵ­÷¬¬Ó”¡g³íá³›M¥[Ü«)Å÷,ÏšwnM?ì³åþŒ®Ú²ïiOdö4Š÷ìv &cqئö¾´ÁAþ çp(y,=|Võsƒ‘îÁÖÈQ7®Tÿ822²5~%¶ÅuUà·ËúŸ52û(ßðü&–EÁcÐÌßcÇ Ú4Ïæê²1èhjÆÍëëx˜çÓê5ò©®7·t‰Ý'òg†Ivš?ûxIâðÖ÷ì ^J^Ïä¥ýˆyí T|ž<¿”—Ì|p‰ÌéíK2‚¦=í1¼•gWn'ËÀHðç{e`x¼=¥y®³?NëÀêĽ:Lv¨Ãìö#u˜|X‡E—¼z§yß5t7×ÜT‡™ÂcÙD&iž} ÇÍkC:¶“Ÿf^5õšuÓX»Ow ­|âíRÒT[wÚ¥ãu'³ï7FwÚFý2ùêºS~ÝI] ;V¯ª;å› t§}µvš2ß•v‹+êNWÓa¢ê0't¹×‰:L–ŸOÓ ˜¦Ã_®«è0Y»=å;ÏÐaÌ;Zò\÷cøOÓTGSón_u˜íöˆ³?½F>KG»«¶ýÈut}Yßç'žÕ=sÔ¦=G™gÖ÷»ªN3ÖMŸÞŠ÷l²sÇk{î0íSQŸ7»ã}º¶®^;uZß‚OÍ÷AZÇw¤µ¾­ñ÷hZÛ神 vU_ze×Ûé>oúlìÝuŸóÚ+Aû}6>ôÉT:‰œ2fÇÝXCz†£¥²ûäÄæ×’-ãiN:×t¸4ÊX‹ç„_æyvÛ¶ÍÆfæàF*åIé(“„b£*o𞜉Yàáê/Û?±^yÊÆ¦9øÜÛO»©…‘µv(’'v"Ë3™/y¥è;§«îÆg—žÐ§‡>a{ÃpNÙ:9ÜSK'G¯°ª§¤‡jhwŒNȯ ò+´jápüòG~9>O¿Gq¢OÈ´¡™§2íidZÔŒ 7ÖiÉ4‹CÉë™}ö˜üêÐúx ¿Ü| ùu„Nȯ‰òK5sòË3ùe#fÔz˜;”ÐÅ:´…Ve±•SÊÊ1WÔù´[ÌaZýàƒ´z!Žå¾Î)ò° sŠë|·ÆwrJ [>R>ÙÍ<]ç¡2m‚LSMàÖùžÊ4%dXÖ–âô¯z\tè }î$¿1ÇDíyæMUeUÜi²Ê/YåÖõ¹ˆî¨„­2<¡uh Í<Ñ¿’¶]æiešÃ>D¦ÍÂNédÚY&h ­î$Ë"!›2±l}ýJº¤çÓ;%vJïý‘¢f-œâÙœâöºœ¾¬„­2a£ìÐzyì‹4@'äÙyææðY6+ûdz¸nì•g‚¾ÐëÁ²,ôë›!!Ç&È1aCŽÍÄ)iûçõʯ¼:=Ð)µ‹Î§ë{Ö÷^Ï#I£w1xxV2{¾ŽÜ:?á#)è ½8Ÿ¤Î['§9ƒnÌ'Ì'ÞÛ‹cì+Þ®KÄÕzoËíɧ#lÅ1¶•‡ÚVRá¯úÁ Ñ6Ñùå¯OX.|À’Ž/E4b/ùå‡ürË:¾`È®Ë}ð#|ð?¤CchvG_°¬‘Yõ|ŠyIõøêGøê_|V2ø"Ó–³ßÕ¡-´òdÏ+y¾u€vØ)±Sz?§èÆ.Ïœ²°9EÐZÍhßëí˜S˜Sf»5Áöâ­?…ÜóÒ¢D#|):ô…^wœWÂŽ_q.l0àgmÉ‘8ŸŠ<›WŒªt„LëК=x_LØ‹b$ Ð ™v†Lã ØrÏ€uh Í<=Óz„NÈ´3Îè³?¶¼øÔÚB«;ʲ\¬;caÓÌϧ6Ll˜³˜KÈ?µìx/äž:>ŸdÄz!ïÔÕòN!ǸÎô…^žÆz rlb¬…Ÿ’·kûX¬ñe½S{/‚®ÐÉSå_×”ZÌ È°åébC³y¦FÐî–úØ:!ÓΈs±ì¥Lsö°HøMýlTœ‹ˆýãÙI:B;ö^Ø{™…ž4ã†9e!g’:´…VœIÒÍ|q ÷Ð Y†Ír1r,ëþ\Œl–ó÷Çfy=?ñŒõýâýÄ3Öù³ðÏð¿J%»ŽA¦-ÈO¼C[h5Ã8J=´Ãf‰Írþâ¡hsÊòüÅÃÃÜìÐËÃü =tBžá3ΚŸq|ÆW–ç8#gûâö‘]¡ÓŒÖøtc}Ïú~6ë{öÁ–ç“$h ­<òIÒk{ö¿.óI ‰Í»89&è f¤Ð x6bö»–—7$cŸk–9C²™í{10(qF•¸ËËÒ¡/ôºóœ’üÝúD‰}±ü4ggȳ„}ÇYæ IF¬Ù;4†fŸ™ 2íŒ3 gòí&h ͤ§)Qò¦´üÂè„L;cO?@OóV¦…Â&å\,â*ŒÙÛÐÓ¼ÒÓ’½ý=í*±“ðSšÏ¾Œ¹EÇÄNÂOižñøô }—ØŸaN ÅÞ1zòr϶bËœÇÙVl™×‰YßÒrcðÇø/y}¶u€Fȱ‰~ügôéÇp6–~üÁÌÎë³¾g‘kFâ!ú陉ý`G³Pøñ©:± 14;2¯$žùõ¡rn‚œKDNkdÜòÖý‚¾Ð˦óg’yEh„›è‹©Ì‘eîÇd"ÿD*x(áƒ)h ­¼þODQçÓ6¯ç”ßþÅž¿Lñéè|¢Åz_uâ/åÇi„ ›hÇL9Cîµ/ÞAÿ—A;XÊr/rî©NŽÞü8cgøðkd™×çÇ¥üŠ:60=Ò\#Ïî*ÏÜ^Z r¿†mЛY#Ó®r~Üù´"Ó–/NÐZÍ0ç^í°Yb³œ…žŒ/ÿ2còküøg‡ôí˜S˜S¼ŸS2b‘.v/¬C_èuÇy%ìœ Ë… &é±»dÄ"½J,ÒPøˆ#ÏüóKßî²Èر§b ÚB«ï‰å"†•îÄ  ²ì ¿× ?¥EÇ 4†fžÆ"9B'dÚ™¦›3Ø0gdÃÌGȲm¡ÕŒöÅŽÐÎkù¦Å:Ï7¦h6Ì;ÄëÓĆYt¼>MŒ˜YÄëÓ3‹ ãë>?{2ËŽÙ§Ù—9)ÓÔÚÝ:nŸf_æj9â‚f¬!Ó<’i±È…uÖcsÇ9Ý¡•çëþ#´Ãw ߥYèÉûc‹õ‡ ØóÆV7uèÇûbǶvùÝcËŠmmé <õW òëÌ<|¬ï—m³ Yç{¿3@'dÚ™–~q>JñàiäÖ>Ù'³™Å€Ç.ɼ!öñq]°?RˆŸ«z°²´³ký¤pÁçòOɲ{Ì#JäIf.¹MÎCl*ËÌyˆå¾±ƒö9®rÞ…=LG7Ö$¬IfaŸWØ´ë+¡°mÍÂWB‘×ðb_☳‹“c‚®Ðif>ÄñÌÎG 3DÍydañÙ3l󳋣;@7ææ‘Yœmä¼ö|bèfbŽQ?;#ÇYmr±EM^ÉVÜÉ`¦gµµØã;%ÏòËeYlë­é®+Z×|gÚfx$µrÏUµ+yïwýšû¦àhê0ï5¼§w?–Ÿià ®_%¿döÓbXÖëdí ŒÜ.ùöµº¹î‚}„¸ñ;f®óз)~áÒïŒ_“ -´šY’Ú±~bý4‹õSHü¾Eæ éÐzy¾~ ‰ßw•$vþFžyx6úq.ƇjäÚ¨\$öYhvG™¦:k›T\ÊEÒC'dÚD™¦šsÇÈ4eš[³"Ö…›û;sý L4†fwôKÏšJõü” ßš¸#Óè´8™¦ïàGHž8ÇF(t²°³}lú§‘3n˜nñ•ã,+±öÔb_N®C»2-&_ÜÅþlð‘B¦ùgGS¾ïÆBØðÿIÿ(A_èõ;šðA¬×–ŽOòÓtBž™Ë—óÏ~úèÆ#Ïu:B—ÚÿÃN À` oo€ÿþSøOÝ4·SÄœ¶ÈsØ–¶ÐjfþS´Ã ÿ©Y¬“ðÉ]®ÿTˆ_îÃ×O¡ø­ŽøÄäÀ ÿ)ü§ðŸB¦á?µÿ©l¤ŸþSøOá?…<à ÿ)ü§æ·7“5s2mav´}¡—§þSGè„<ÃjQ{ÍήìÆ]Š/•×2,:µ´… ò¥òM'Ë›ù¶@»Y‡ÆÐìv3w>AøÎ´ô²:a7›xN'í´ ™æ—^&mf™ —ãðÔYAã%Ðl”Ÿ±lGbë³2©n¿÷Õ)y?í‹„ß±¥•7²Wèë½¼¦š=¾³üó¦¾ZÇ §óÛ _ øy§¶MæÙUø$7êë½c9éfo­?’ÐòJ¹Ü×– }­{OkØ»¶Ðvî¹»ÖšûÊ{½IuΆ{Í[MÜ蘳εsÄ·ì·ìã-ö2!{,NFì'„ͽ‹Ð…ûâšÞº÷XÛ8Ûj>2Ž{:cYú&w£fŒ/Vî޺КÞ)çàZsöA_è;k™œ4{>³—ɡХMâÖ}¼¶ÏgÖ9Ÿ ûS6Âï3ixcUc÷ÖýöÉ·Wðþ,i,×zªYö|ÊnÝG_æÚÌ–he¶Ä[÷Û·¹6kŸ+^äúçÖ}ôeÌæÿlhéâ´*1öbáóŽ˜wnÝoßè«$“#ú };çξ± ³Ó³‘rJ5q‰f¿®›>Õsí­ûxmZfx,"&Ëâi™Š½º¨'îÌÜhy‰Ü…–Ë¡e83øZsh88>[Q§¯c|}‚®s®åçÌh­sƒœÅkÉù» ºF3õIDû3q9ëœÕ»G?oM׬ÇkÑt:^£ù>Â-íŒÛdÂÜ“ÌxO`ÌúõýœAÎÙµÄ\cŒz—~ú8Võ}ˆ3äï-û¸Fšú2¯®®K÷1½G?¡+t½eÜÿPÜ;7ÿ˜\ø¼ÄBoÊ=è³Oú°ˆùº(}8îŒÙ[÷šÞ³6šª™å%9—¦jFvákÄù×3?Ï>VoÒ3Ú¿»ÄoBö5YÙùõ{ôÝ×1|ë3±êÊ9vÎÉ×që~B[h{/}*Z@ìÓsöÞoÝwó^ëf^Z„MÔs>öýôm Ï9GÖ¥cx.¹³.‰ _Í5êÒ·îûµ÷‚Nne5‘Îs>ë~Éx¾uß}Šk ΢ÎvLGóasö­5ߺŸ>Òuޱ¡ÂÎ):WÒ£ge?Ï/6Ô¥:t¾Œ|Fþפm<3Úö­T;D+Vß=úé›<yM-oÝÏkÓU ]QÆØQ#sä ‹ÕŸîÑOßb0F"FZð<{~úâG ÿû|6ŒDåA߯mÃÈD,ýTØ[³‘}Mb«:‡ÎéŠö}£vN«ÅËêl¾V‘ˆU”¯0ñ­û~m:«Îz!mŸ§EïôÎ1/DÜÉ^ œÑ¹´ÏÅþ˜ÃŽÃ5iŸtòr#ókëùÙõ圾G?}Ò«óæ«Ú¼Gß}Ô·òçŸNDTÕÉG—wÆð­û链•ÏôŒÑµÆðÎAçùÓyÌœ¬…H¼2:ߣï×Þ‡û,cû—/<ÖÍA??||~zúñë÷÷_¿¿ÿòy÷åå§—(ò¸¼üm÷^üžŸþüøüüô‡¹ôéKqcqñÃϿʦú4¿³âû¾èÈÞT`:»=^ôˆ{\ ”xNÜ“ŽßPÉ¢ËßqЮ·éÅàxÎs.fà̱l6·¯#Î/Û[¦$—½kJQÛã/•ùÀ¢ÚB¹q 'òÆæÆíQ—=_ÊûkȆý°W×yl7<½ü²};¯Ü»zÖ8WŠ}©®[½>ÕZ(û•¯#»ŠíÂÅ.x̨¼n—/®¥Xø¼ÙQq6zS¡¤vqbPñÀ3å"Ƽ?µ‹’´1ž–u‡ÕÂÉÔ].~"{½hO’ô×iP®vqTÞ7² ò}CïrE'³]ƒYëöºÅ`ÙŽø0àRèÚ'ÞWÒ#‹Ô`¸McûiþVã)0N¶ãqðÓDðÑ)<]â€!LE¿LipØÇº½obÌÃB·ý²}a&Ú0Ä÷ùŒ°·†ÿQØ»{h_˜uÚ ö`öóÃ>› ï³Æ1ånØgyŸÐsò™é/¡˜¿Ná/“ _Q‘†z8­Ö«ÎÔsÎnÛVèÍ7 ƶ¥¦¥èû%/²¸Íˆ)×FoX=›"™Ü ö‘¥á^¼W®!‚ñks­äýÐÞWà’Úuô‡þÐàšªd;ô_(ý£v"Æ?ãúCè¿bú+èÏø‡þÐúCèý¡?ô‡þÐúCèý¡?ô‡þÐú¯Šþ!ôgü[Ÿ€ú#ÿ¡?ô€#nìƒÐßOú¬’dÝô÷Ú?¸ÂÙÁ[œ{¼p”&9Xƒ5XsŽgà TÅ9žèo"xúô‡þžÐ_AÆÿ ì8a;øþ>Ðø2N¿¨õ™´êwù7mõûÙÒIñޤá«cüáþVbøÚуœ^³iô¥ÿ¸áZo‘¿÷¶ýKÿ@$þÊOðt·/–gzƒsê8Ó"6gx¥Øœ†׌Íy2æg0ü7%¾ÇÁõâj¦ÝøbWˆs¦óŒÏy×’¬¸>“1?ÊUK!ŸöàJ¡P(”™£“—ú)XøG›˜¶yÕgô= åfEM°ù´J8öÞÃØÔJåáApêøHÐéÒh¶¯ …“NË u!A§ :MÐi‚ï|ìÁìÁìÁìýÓ1Gážø¡¯­»ÄÈ8((ë„à’Ŷ]Ö¤t~qÎSk:ï×'Þßr¨IšÏÚQæÄ5x ž‚§à)x ž‚§à)x ž‚§à)xjæ<åŠî?HOÁSÈ)x ž‚§à)x ž‚§à)x ž‚§à)x ž‚§à)x ž‚§à)x žº"OÅM bx žBNÁSð<OÁSð<OÁSð<OÁSð<OÁSð<OÁSð<OÁSð<OÁSð<OÁSð<OÁSð<OÁSð<OÁSð<OÁSð<OÁSð<OÁSð<OÁSð<OÁSð<OÁSð<OÁSð<OÁSð<OÁSð<OÁSð<OÁSð<OÁSð<OÁSð<OÁSð<OÁSð<OÁSð<OÁSð<OÁSð<OÁSKà)C·2{Z}ÂSðr ž‚§à)x ž‚§à)x ž‚§à)x ž‚§à)x ž‚§à)x ž‚§à)x ž‚§ÖÅSåžq OÁSÈ)x ž‚§à)x ž‚§à©Gó”ÓÍá)x 9OÁSð<OÁSð<OÁSð<OÁS¤ÆOÓÔkž}„\§Nêô®NWtϵl©“:©s¡uÆ6–F¶ÔIÔIÔIÔIÔIÔIÔIÔIÔIÔIÔIÔyÝ:—o'­>Á–:©“:©sÞužÊ¡¶Ôyiް¥Nê¤Nê¤Nê¤Nê¤Nê¤Nê¤Nê¤Nê¤Nê¼M|‚GœyžkSϸ߿Ÿ>>?=ýøõûû¯ßßù¼ûòòÓK¨ò$(/Û½¿ƒç§??>??ýa.}úRÜX\üðóï»·âÛªÁAДÌ&'2Ÿû¢¢}ÑØ}ѱ´ø ŠìãæYy_Pܳ/®mõ¸g__«ÄCûôø³ÙöðÙ9ož+ÚþjJñ= ų¦/ãoMûì³Ú\ÓU[ö=퉊w†Å{v;[÷~<ÛÜÞ—NÇÁÔáê;ölIð.&yÓk‡\wpȧ᠋û#ó=mÚŽÄ! Fà°·¼ÖyvUm”}iáN_ O¨êÝCï4})ÛØóNóiÚ*ßFÕ5¥›1ctýÎmõξ>š€*hp(y,=|Võ9õÎcØöÑ+|«úÞ‹­š[ûÕ?ŽBûL/¶Å¸V~»¬ÿÙ1²¨çÍ<)‹ÎƒavÞÌÕecðR>é닃§øÄ·öœâÛíVð¾@&ÄãdÂг§d‚yö@&eSakúô¦+¦ôÙŒÕ%ô¹E{%h¿±ÏÆ•’UªNVÇŠ³T(^•>u,Q™ÌÑöÑ󛬱ÜPÏ*G»ò]¡M"i“6Ö‰E¢Én¡¡ÀH^'Æ4 rdQÆ$ÈTÕý×LŽ©·U»ëöÄ"açg´k[ÕW¶ËÌ9·ÄÁ ªEÙ÷ŽÌ~í»6M› Ö“ƒe ,\ýeû'Ö+±n‘zð¹·ŸA5j1G§î½‘xWdé“Ù¯é_nÛª-?¦dªñùÁ O—l6>A£llòZ#ÇJ9Z9ZäÕvt¢2k‚ÌR÷ ¹åÜÒÃ]nùGŽ}B–uh Í<•gGè„L›¨‡% îÈ4ešãumÇEØ‘kz„~&h Íqû˜I£ œC7ææ¯ç±d>ñp>±¼ÒòyÕ¯äQ™$h ­d[I…_ktè3D#dØÄ=¯_W¯ýÂrá–tü)¢{^)~®ÞÈ1+#?N#äØÄ½.Û"@–-6‰ 14»“ˆ“0@'dÙ²,'NÂ¢Ï C3ãóÐ ™vÆ9}öÅ–˺C[h5³Ø/ûbØ0g<§hÎ/:ö‹æ¼ñ,b¿è™/öÑw,C–-vÍ/è ½<ý2@#äØÄØ/ŠœU‹Œý¢ÈW5ËØ/Š\U¬ïgœ×…Ø®Ëô{ˆëêß«nêìÍçB\Wtbä:1:1:1:1:1s :1gÁæ­'3§0§(»•½ÇåëÐzyñggäÐáüÄrc$vh Í<–i!ç'®"ÓtW™¶ÐX‰‚ÆÐìÁ±S‘û;îÈ´:-N¦éû»†M»ižŽPð|(Ö ÒV–Œð}´†v´³X_vJèknŽŠ:º[Üñ úÚñ•kÐEÇWR¬?½^¡ò ›65ljØÔ°©aSæ†Læ†MñqsÿÀ![Z:!ÉÂliqÑ­*é]Õ†ò3m°‹e;[Ÿ›·]{´ø½¯ÆHÉû¡hŸæÓÑÊÙ ?¬>^ñmÏòmÌ¿)‘Gr"¿ òUÜ/KSÛ&óì*b;†Í<¶È8 ·îãµm}2&÷˜¸Cq³¶ZlÌž[öÑóœªkÈIº¶x:kˆG㽜ÍD.ÉΞ¦ûÛQš& oÌ’¦çž¹u¿}Ú;ëä€_CžûEŸf,ƒ/9?ÌD._ñ|ûZχC_軤ó_k9?µÆ=ûµìyC[h»„\fkɶƜNkɉ´?’`!û:Çh{ë~ÎÀGh->6ÐÚ>T?NÛ¶´ºù¿‡ƒ]/F&'=ºÔ­ûé㸺åìÇm(Ö8ÝùöÖýô<§ÓZr"­‚®Iã#¶hºÞ²Ÿçõ\s^ÌÙÑY Ñ1±+ÃÛ-.¡ó­ûî›JÆ* .«oÝOc¯%4]G<åÅÓ9ž±ãR:Ç3²m\cþMšû1ÿ:[Oܑշî§ç1aÖSet£¾vÎäÂ.™ôŒ×lFú²ã[gcN;ôsÖËñ¹š±cÌZèýôÅn‰=“%øµ&¢¨…öýF1&×£q18‹¿ðu±“÷kÜ8èû‡ÏOO?~ýþþë÷÷_>ï¾¼üô¢’@åååo»÷âwðüôçÇçç§?Ì¥O_Š‹‹~þ=+^í.+ùîòwL*É´û__û¯¼àÎmw˜¥w¨g{Á³ûsž«±d/LJ:ÌYñœŽD€¥ÌâévR°n ‘cDƵ©'ìHâ°N§nCl¼J%ê§¥*'Ÿ SÕAz,˶Z^0õ:ÌMÂ8ÓÏ:Yܾz®T¥/TuÅ68¡Þ ŠdÚ²áú-®×‹— l¸î!oZ')°éÁÆ„ò¡-õ÷Zq}!s¢®_‘§4Õ&šNÿê¶¾ º ;z·í²ma&òÝ÷Ñ&ŸæcM}a³·q­øó·Ž/_â´©øÁí÷¸>¹=˜³âÄwÞ©{Þ)÷Ä"+6·÷k¿»3|îZëÜ…¸/ÜŠ˜­›N rÕÔ“f‡õÕŸAÛüà0J­)Óá_¿Gµýdêçì~X9­Ø½,9´ö¥í°4òƒ~º#¿Ý»êq”VSèÐÔ[‰¤‰ý^ïíÅnnl%Ñ¡Iº•Óo„L?è»Kvì×4IÛýªÎ öLÉzÔŒ¼+ W9ƒ°únvÂ6kö»yoªÜWŸæoæÞ$¨~›ï†¬æ{ßó*zþp†ŽB?gèH(…k™¡§f-  w“¬%ng<%c‰—òÐö4<é×Òu Séæf¨±c·ž%Ãj6;–¯Ýîsxù$]S6Ä3Y/Ÿ‹ûpÉ‚:ó`AÝÁ¡öÖjÌ콩ֽÚtaS}–~,›j¦Ý›Ã2qÅÆÇŠq+j¾›îºÊ´’À7“ŠÑšK-{èïµu»?ÞVx®¿øˆÙ©"çYôëðßN•×èüg}/È2ʵŠzcnfnfnfnž67_R˜›)”õÌ]ô¥¥Ì]7)Ö¹bNó\Û®Ûw^/Lý>°нÀµíÉ&#÷]<ñâñS±‡;zëÞi¢aÖiÇ<Ó‚+ðó-p{àG÷Á¬Á¬Á¬Á¬Á¬Á¬Á¬Á¬Á¬Á¬Á¬Á¬Á¬Á¬Á¬Á¬Áút`6Ÿ1ï´>‡ÏÁ¬Á¬Á¬Á¬Á¬Á¬Á¬Á¬Á¬Á¬Á¬Á¬Á¬Á¬Á¬Á¬Á¬Á¬Á¬Á¬Á¬Á¬Á¬Á¬Á¬Wõ99Š]ÞÒå)®iæb6oE?mÞ\÷lª”ËØe0×"3z'ƒùCrOh×Ms‹|ߣòÃ9Ž'å4N«ü¶¦_&ƒ½ékù™6uÉüÄ%N›ŠFn<»þÖ|‰·¶u6ù°zW·_[ ã†Ö5¯Ëß»vîܾö¸û7Ééü€|ÎŒ­äç#è]  t.кxD—ÎÚê¨n­Ð­Ñ­cÈ>è]  t.к@—ÓÅ­ŸK:1~  t.к@è]  t™]»ž1ëš :1~  t.к@è]  t.>ÑeŒ\,Û‘Øú2Û/×-~NÒ‡®×wÎ7þÉ,ö»žï]^ ÏØïSvœUåíÓ—6ï¾¼üô’'¡*¯~Û½?Ãç§??>??ýa.}úRÜW\üðóï»âÝÁÄÔ[|ׯÕï¬ø¾/Ú˜±WüÛ*¯íßF–hû®²¨êÓý½¼fÇAºïyŸ,›Ãkû|.‘ŧ÷ïÉñgûÊ)löñ}±1¶@_°é­C5%T–Oí]ó©z0Qj¸Ïfμ ?xDSŸøýZ4=…É1š#ÖÁ/²¿fN=èϦ]ëïaóó©’ê3éÜg06¼Ö‡áæƉÄwßîÓ©>G}výÝn/ës¢ïÐç­ áœhµ½.­Â‘´Ú·/Ûà°Ïs–iz?R¦E·‘iJŸ'Óê}ØkË4æŒåÌ[¿æŒ{÷™9ƒ9ƒ9™vqâú¼„> —‘ËCr¹¯Ï‘ªìº¥¹ÖšuU”iaì­Œ¸Cµñx_mD¯•Áxô†‡²&²ic7:f1¹B Ê3‚!žÌÐkL“ ›=¹Ýh¹gpOѾ2ÀgÞlöÌ>ȧžàóÞAUEÛ–XuÞúúÁTo µîÿ¦ê—sNHóö&jŸ³ÅdG—¼â»K+™vºÏÒéÀÊ÷òš§zï ÚrJpôÌW‚­«îÓ9tŒº5ýÛñغîcð­ Ø ß^[·›Óü¥š s'ç0wï=õÕ‚×jïÜu‡¤Ò?GaŸÚuʽuå\`ŸŠµÒܱ‡ïÁìÁy¿ymÛ¶‘^›¶l#ØF°`AWFw@wè•Ñ€î ðGw8[%ö§f" T¯®¦Å<¨&ÎozEØö`V>ŸŒÀv*ß*ëx´Fl±×ªDâyíÝa@& ; ;\¤;„Mpô‡;ÈŠà„AÏ{£eE¼2ýÁá¨{q}-A7»•ÍÌé×ÔÀu@wèê¼<{¶#åï”À<«ÃUËš({áÙ3÷&FÈ^°=m2n§Oþ‚íûù±5p÷Z6Û|]ØÖ%<·Q°ë"½6_ŸL0Ϻ¹Ì}{mI2a¨2Pc„Q¶ÓxK­¤|®`N†+ƒÀÙ`s÷¢õTº^DëìŸBk¡Ç^"3¡õCi}×½{½²}#—Ä>:³ÓE´XÿÙ¿G.xè9ö å¿ígÑãhÊÚS]a0\©ßCö),: Y{žä[=ÎÝçã ¶ÃØ–úͽæVü9'ùxâsˆßØãþžsÖÛjížÖþ; ;ƒí lÓ :1|{¿‚¾kø{¢;€=؃=gÚ9ÓÎ:…u çÒ8—ƹ´GÄå\úçÒÐ8—ƹ4Î¥q.siœKã\r–siœKã\çÒ8—ƹ4üî9—­9—­9—ƹ4Î¥q.siœKã\çÒ8—ƹ4üÀŸ ü=ñ÷ÄßOü=ñ÷Dw@w@wX¾¯çÔù-ÆO†ùm9óÛb±=å“u®¥Õ÷³|æhó '䫾÷ùÒ°“«šó¥è œá|óçC|>„¹‹¹ [9¶rl ذ•c+ÇVŽîð¨uïUÎfÄœÃá´†ÖÐú¡¶ŠKÎW;ž¥õà÷ë©}—yö¦znÒïo~͵ïÃÏPE úÖþ!¿é‹Ö93»3l}Á»œ‚ÿáÿ%óÿmŸs·/Î݆wõö'÷µ“Í}ßzî{ÃsÞûçœãÌÍ=–ÛÜã¥Í=&ÙÜã~Í=¶ÖÜãWÍÝ>8÷8Cs3÷ø4s¹2÷¸&s2÷øsÝç±¹Açžsî9.áìž^Ù=5vOìžØ=±{b÷Äî‰Ý»'vOìžØ=±{b÷Äî‰Ý»'vì> °û\Û þYÿ<0vOìžØ=Ñ}Ð}˜»ævVÛÛüÛÿ¸sÂ>>?=ýøõûû¯ßßù¼ûòòÓ‹Ò±JËËßvïÅïàùéÏÏÏO˜KŸ¾7?üü{V¼(?]é^O_Çß›¼Š¿«ÃûzÿÞý,:­ìïK۶ϯûVmZ*Ö“ë.묹dOÇljÌ]ñ¬ŽD œpãÿèž@€C‡æÇ½©• }$hP'XO·Î¡à ÞvºV0ã±ýtÁ),mË6FMP ‡µàe $ʨéé¸ÅE´ª&½«&Šò3m„±¶|ÚzÇ´µîcd¯ÇÍÆ_¢áø~€À§Agì1öàøá?Œ *ùçÞÁYö3É/#«›Èjø½½™±Ç؃à‡[òƒuXõR&wÚ·(ÙŒ_«aÛNaŸ5û€wÃ^´¯Ä>;f.çX3ÇšÁ¬Á¬Á¬Á¬Á¬Á¬Á¬Á¬Á¬Á¬Á¬gäï2%\uçüÊ-|^jš9…­è§ÝÃwϦúA>1†Ö™¨s×»x‹m¸JŽú “£~̵)yÖ‚Ã1Ó7ž]H°úÒ_-ï§–Ú𞉠鹩x-à»ðFoÀðÆZy£|6†7à ä¼oÀð¼oÀð¼13ÞPð¼Ü€7à xÞ€7®ÌúʼÛ³n¹ý´çªá—™ñK8BndÈd ó ¼1‚7BxÞ@nÀð¼oÀð¼oÀðÆ#xƒó9œÏk°k°kÎSrž’ó”¬wà ÖÂð¼o §,ó’&ìçÂÈ xÞ€7à xÞ€7à xÞ€7à xÞ€7à xÞ€7à xÞ€7à xÞ€7àÙò†‹éub‡À/ð ²Þ€7–Í Þ€7œ§ä|Xƒ5Xƒ5Xƒ5Xƒ5Xƒ5Xƒõ*±vçþ…·«ÿæƒXÇý¥Öî`ž]âÙý%Ú„èÓ–i»G <èDŸè}¢Oô‰>Ñ'úDŸè}º×Y^hGŸuæo‘tùrtq-.ï‡kÓÁç~b;µµeäö{n}êRKmmh¹m¿>ôÏ+Ÿ›ÈMúl÷ù¢øDß³‰}l_]Ÿ#ÛïØ~æMßûùáãóÓÓ_¿¿ÿúýý—Ï»//?½¨0ÕqyùÛî½øç‘–õæU©Ûa"å{-Ö5¯† ?Άó‘ü§í¢éŠ boàµÄiS.s K×'·Ø;+PNçºçŽ¿Ëú’æ3ÜM¿nÅ÷Mt¦ä9Õ,öA¤ûžƒ÷n/¿‰à5Û#ï‰ÅsváíþVÒ/o×r‘¬„Ñ :?PÒ,iØufN+ÖßOа66 |Ž¥áàs+¤á»é×fGCeŸÍ«2«€Tcõ¢ô·ŠSq¨8]È!n°¾Ö¡8ˆî[€ѶE)í:3¿Ö*ŸÉ€¿hgIÙd ²¬Á¬Á¬Á¬Áz.úw4ÁÖ,ï¿·þ-Úf=m‡ïá{°käùxy®<ãêÆÁÙ<{?‰½¸û`ǘÀí„ßIÙ~À;Œ’ì ÿí=þÛøKâ/¹hÿmÆ:cýác=hJ0Ö9«ñ¸³»y47y4†fèÈd 2…õ̲Ω ‰ïwÌEH„ƒ‡À8Æ!0ÿ…íHýcÓ‡ƒ`ã Ø¼‚A?èw7ú©væ›V´ôìP^B¿y¿D¸ˆ~2%ô»ýTOtCÆóôƒ~Я™×±ÞKÚå »ôcüA?èý ô›'ý²f¿ú‹ƒm <ØÆ>(û ìƒ²Ê>(z8ôƒ~ìƒB?öAÙÅŽÏüý ô[Æ>¨ÌÚÉø[Ïø³Y9¡rúA?èÇþ'ãýOö?Á𽿕ì5ëJ²×Ì^3{Íè\7µ5ÄVçRÐïôK,ž¬YÐúáëýðõ€~øz0þŸÐOÚOJÜ ¾Ðù ý ôcÏùÚû á’¦ä´Š½göžÁÚëd©S÷û•ü: ù5|Ö`ý¬IÀLf0¯$3¸Ý7öïÂoKÞïeŸùnËÄý<ø ÜÀ Ü–{FÜnÏoØ“°'aOb~ÀžnØ“°'!ßÀ {Ò:cÂoÓbð|nØ‘à7âaGÂŽôèø<ÄÅa^7ì–à†Ý»%ò Üæ_?8Æ)¸ÛbqËì¹»È>·³gåì³5f{sÿ‡ÏOO?~ýþþë÷÷_>ï¾¼üô’çIR^ý¶{/~ÏO~|~~úÃ\úô¥¸¯¸øáçßUѶ hgðfÏçu¼%.hØóÂæsoÚ²¯þl«úÊרê”M¨^­#qìq#Ž+º#…º}ܰ/¤ÇÐ1À±Ç4ÙdðHbçØe·¾¡£ÞEGUöÞ…œ?¨;®†a]§í¯Ê÷¸æMӛ̢!³èUåj6.GɃàò¶ß$0L§aÖðþ˜`<Èd2äb f/CD?!W”!ÑZ½Óù*¿kᔤ„,чt½™|I«Õ«ÁCï*¬ÊÏ´iûUÁñée,ÖÂVÏ·V°™è‡˜§[¿wíÕðecê¸-SƒûËTÆ×stÞç¬þb«?åŒ#ÆÑJÇQFö¯Öˆ³1®¾›Jú—XãZ«…+¯™=öÖÍÖØ?eÉòãEm·ÚS÷?¢˜C>ç>›g<›Ž¿×¹MO­#y=¿}I§}»â]û]O?¶÷¥—E3di›4ßß6ã±>·mÒýØ{6g¾?>ÆO¹Ÿã+{{L½ûMSTÜþåþ–ÒrAñ¹oÝbV)SËÍåËT™M»?U—·QþÒ4OæÅƒWéóæzólK(õ¦5d²á®ü†lðO?óŒ(ëÿ”u®eoQºv=Y‚Îß»6޾gÌ‘zù;Ž×q­bÚ¥Ôeï¨Íýªg',Š÷Â\„ßµz/$—ð»g˜¯1Þ'ñÌO„NºÅaÈŽ“T݆‘‡!½ßãCÞ!_kÛžV”5ëÙPþýD4¶©˜9¤0¦Îcás侚ã­»­åGª©~VîîŸs㿬7¯JÝŽLx°XKǺ¾ƒ ðã²ø1^bîàŒã¯ÚÃÌê .b´÷»Qáø¾oàø¾oàø¾oàø¾oùFO8/¢›ÐêW÷™Wö™ïô­ÿ k¢Óóµ³oÐvÄp±®K'·QY½Ù> Vx”jŠ8 *FE,ždŒ©ÝµßÇ4ngxXÜQ"uç­-áhV†œ±ÆX›ÏýØ­äEª§ÔŽ8ÈÏ8¦7t,/H/w¥1®ËWwÏy[X (×*&ÛLyÔÔ0YxQ"+ÏŒ‹ý”ë”12"Ø7y/£Ýáê\'™ŸËsÝÄÁ³ÏN<;g¼ V¬÷÷6=ˆö9óÃbLÁdÈ­Í=Xƒ5Xƒ5Xƒ5Xƒ5Xƒ5Xƒ5Xƒ5Xƒ5Xƒ5Xƒ5Xƒ5Xƒ5Xƒ5Xƒ5Xƒ5Xƒ5Xƒ5Xƒ5Xƒ5Xƒ5Xƒ5Xƒ5Xƒ5Xƒ5Xƒ5X{{Îqlx™}üŠçõ¶j{}æÔŒ/;yvÛ¶ÍùÚV†s‡¥ìV¿#!A\ˆ¼Á|lûjú <úê'Ö_gµß÷m~Ö.œY "ñ\Pñu}À¼Á¼Á¼Á¼Á¼Á¼Á¼Á¼Á¼Á¼Á¼Á¼Á¼Á¼ïŠ·Û·V6Îk\Å4.¯khïƒ7xƒ7xƒ7xƒ7x/ï1éâbÙŽÄÖ—‰ôqJøˆvÓÉ…¢}‘Hwf18_Ä–®îóõF×Q$/fí<£Ö.J¬Y²†?u\Ñ¥ö?E:¿ < {ÂÃʾ×}ºaå{^Ö•À×ð5| _Ã×ð5| _ÏŸ¯9gÉ9K°k°뻟ÕV7>§-Ï‚sFÛ/»µ  Xƒõ¬u'¯¸“%µìJ«ºÊ¿i+ìgKþ$ÍÚÄá™8=Ú½7²B7ù—Là ÷ÛÉÁ^9nuñ²¢ea+ãƒ×"ǺM¼ž¡*¯~Û™´ëáóÓŸŸŸŸþ¨´W¹ØMþwµir¸+SLîxsmW]3ß·EÉwöïªj—6ß³ªmA\åUÍ»Üý‘x÷Pžø(8HIÿ^’d_U=9õ{tbê\rÚ÷)áP²ÛM±µJäR“oE­HrϦú1ÓðC¦ƒ+N¥(‹ªå»y¾œ”]ÒÛ4äåµÐÞ§…BYBbÍÖ\Qšy’êw¢ÄûºSŽH1?z+0±Ï¥ïÚ¾+‡®S»jÞ7ÚLäd Øv- Œ±ŒûeP5r>ÍÃóâ‡õBg‘Ð.‚!íÂjÚjÚjÙ¦Òöq¥9ñ¬è÷¸¨ñ÷¶Êkû·ß ©êy›V2M™R ëß½ÎÜûJ©µ«¿_”‡÷ùõŽØ†žôyÅEÝÿÌo-¯Â;ÐÔ¨dà ­•œ.\/ÅÊrhÝK“ä~æ=·ôJVjâK®°sy†yï\óœ×˜&ÎI<¹àž»ÁIÛKHš,f¿+¬„bì®pt‚wäë±u˜ÅYy¿ôÐJij±ÀEWÅ™1K³yt¡™rb;ô‘v´ÌM‚FS~ƒ-Ø‚-Ø‚-Ø‚-Ø‚-Ø‚-Ø‚-ØÎ [ë®ð,Ø‚-Ø‚-Ø‚-Ø>Ûlá[°[°[°[°[°[°[°[°[°ÅgŸqdØ‚í° šë` Ÿ‚-Ø‚-Ø‚-zûËX‡º|ɘ[°[°[°[°]¶ØÂ·` ¶` ¶` ¶`{3lÃa#X§` ¶` ¶` ¶` ¶ø}ã÷ß72lÁlÁlÁlÁlÁlÁlÁlÁlÁl=À6WxlÁlÁlÁlïŽm ¶ð-Ø‚-Ø‚-Ø‚-Ø‚-Ø‚-Øâ3ŽÏ8>ãȰ[°[°[°[°[°ÛEżOxlÁlÁlÁl/Ç6è¶ð-Ø‚-Ø‚-Ø‚-Ø‚-Ø‚-Ø‚-Ø‚-Ø‚-Ø‚-¾âøŠÃ·È°[°[°[°[°[°[°[°[ÎÀ«` ¶` ¶` ¶`Ëù°…oÁlÁlÁlÁlÁlÁlg‹mñl·¿ƒé L N¹ÀìÈwÓÞAßè¸ß‡ÛÕežõ}ìÑ>ÚGûhí£}³h_>.?#ØÑ>ÚGû.n_~´öÑ>ÚGûhí£}´Ï“öÍuð¢}´o&ã5¸S{‚C9ýhí£}«n_~´öѾ ÛëWàEûhí£}´öÑ>ÚGûhí£}´oVíKÀŽöÑ>Úw£ö¥àGûhí£}´öÑ>ÚGûhí£}Sc̃í£}´ïVñ“ÁöÑ>ÚGûhí£}´öÑ>ÚGûhí£}´›3˜Ñ>ÚGû°9Ó>ÚGûhí[uûòóø•ãæÃÇç§§¿~ÿõûû/Ÿw_^~z Uœdååo»÷âwðüôçÇçç§?Ì¥O_Š‹‹~þ=3Á·UãUQñ~ßÓ°Ì\+¹/îÙoŠF׃BØÛ½UÏškî¾`SÝ»ÕâÙý𳻢#yZ|/ÚñjJÚ©7¨ž-ßgŸÕ&›®Þ·ïyçØ¾Áá³ò¾É} §÷eöeW-Édé©‚P¹¢Ý±dPIQObú6ÉŒ!YÆ5÷¶”¾Ð¾«øLsÁØ‘½¾¯¼/ÒPÒ¨±mï;•€J'U[RûNÝöš¾”} šYö;´̵O¼¯ÄÔÖ!…ÇP»F÷5°‚`o1ÞÛ{÷Uï8ªúhÞ—Úþ˜ç]zû=2y×5ÛP ±Ôâ³éHt¿ÃJ –vê5Ç£ª¹fÚ¦w•€5ýi=¯-?wßõ\ ;õ„ö “»–õ¼;žŸ‡v’‹ïËçæoŽô¶jwùþÈ&†I,¶AõlÍSÛ øzlÛ¶U}C ìÜD]¿#³ßCû®MÓæ4?#ižÀÃÕ_¶b½ƒÁCåçÞ~š’Uu—rÝ)$yUWK ,}K+{øÏѬÄÉÊMmåGš êКõÐ,ì(œC´ËΠ¤U"d]neh~šNÈ´‰2-í´ ™æL }R±ðK,½NÉ2A[hõY[Y– yeéw.í¼–o‚NÊ6}D®‰>Õ:|§u[ßìgÃnÛeÛÂL´³oݘ3§œ=§„qÊÉ:æÏôdk?pƹrl¸ùE·u­^ùÔ¡14{žìæ%tâPèÉGè„L› Ó”Àžµÿ2×þC3O×þGè„L› Ó’f=‚<[к_Ð:ÍhÍ?@7ïeÚ”½H}]yvk™Õå¿8lp“¡´OXl/áócõèN=<˜7sŒÙc,é¦;ãÄî.ÇŸI“ÀrÏö¦ÃKù,ûÏWÙ~¯ß‚ŸÖ¢G¬u^ãºw-ëÆ5Úi×bç\Ó¾îš÷E×è“´Ÿž>‡ñ,R#üÅwE?£¢½*«>_³ÊOÚøR«¢Î]dý³‹’íªOó;¶újé74×Ëû6Ö¯Úð]lý ƒê{9~Ï,AÐíší,Ûj¿—>çéuK¸=,Æ¿¼ï:eÆe_}öñ­ã3÷;ìò´QÃÛ§xÔÜ· ªñkʶX£gEQ»ê÷&œ8ÖÒËÆ©¯cym¢e°ñ“–Œu …ù‰ù‰ù‰ù‰rïRîCêʵÖËê†mDN­· SÍS_)ñšw›{´åЫ'h'û8ÿŒ ²ù*ÙÛÞ¸·nC4ó¶Ûwnâá’m{®mÞäý÷›úMÿë8 &Þ†‰»±›Nwgç6÷ñV‰aOßc3%¢ßAW¤Û÷.TÐ$‚Ã@ Îaf/ ãf£B: ¥Aã˜Q^·Nι£r ê(#¶Ô‘í9ÝP;%u|Åé<˜>ÇÁ•UŽôÓÄÚ¼QVQô¼ÊnÛlûeÁüÚO¡P.-‡êNfÚŽŽ„‹ýF¸Äï÷ayŒ½ÏÍzÐ ylˆ4ë¦[Y\Kˆ´P¸9–øq,a1¡ò&6åÃÂ;ä'BÎ)ăžplP¸qÝ {Ñ>Z£nïܱw&†±G6Õ±í+±—í{°{°{°{°{oõ{/Cæ-1\Xƒ5Xƒ5Xƒ5X÷ÛÍÓ‘˜Ë#· ½zv1¹Oø«8µ¡ZB¶%·ŸisÔ6–íHl}/×-~ïm.'Ý´³lŸÅ?𣽗„é’8}4ùÕ~ß·ÇÜÉãÇI爽 Ð=f.ù ~_àø~_àø~_àø~_àø~_àø~_àø~_àø~_àø~_àø~_àø~_àø~_àø~_àø~_àø~_àø~_àø~_àø~_àø~_àøeŽü¢;yŽ\ÜÀ:Na*²>v³;ÊXƒICw‡u‰sÖÄ.L"P7x»˜‡iØ`á~'Çò“Øô¬eEÊxÓ6žcoöÉ$MS‘2¬òOþùÏÏ¿½¿¾Ú¼|úòþò¾ûíý/ÿÇÿùÿãËß–I¤>¾T?ª7ýоùÛ÷/¿˜6¯¿íþb.|Ú~|yûúõóË¿í¾½}ým÷ÃóÏO¿½û¾©Þln|ù[Sûp?û§OÛþ‡ç§Í¿¾~+îù믿ì?}Þýòåõ¯»úg“­ê?¾ÿõ×ÿø?¿ÿ¶ù×Ïÿÿ–ÿ>·Wmµw·ýõüf~þ¥ý†Ìû—æêË~ ~x1)7ýV¼bÿ—ÿû×_þæýeóm÷ú¾{)j+o|ÿjºW¶úåßoÿÓË¿ÿíç/%¨/Õ?ÓÕ¢g»oß¾~ûKñÿ—¯?˜Úž p¿ûò¢Šï>?YðŠî·_Ý}iZòñåÃÿü»®ÿîë@«ÌC/WlÐ÷/Ÿ¾~)«Ú|ýR¼©„ýo‹ïæÖêÇ—¯_~©.üùR|¤4ûãåÇâGñåýïÿK•­´àÛ¿O¦Ýýµì¡}äGûšõ† ‹^3®X1bÉrŸ>~-/ï÷Ÿ¿ÿö¯1?ž>ýò//-Þ¿ï>®þhP¦ë¿•wüËû¿¾|ÝÛ×™oYaU'UûëëçÏ_7¢E e«‘3Ü8ûŽ·ïûýîÛËþë7×´M»ŠæÖ^½zÿÛn÷?þòõãKðñå¿ýã?þ_¿ü·üï? Tôm÷??}ÙººJ†5 ‚mF1p¶¶ç½>¾¨âç/ÿî§5ت×m!IêfÑ"ÓŠýæs!l*¾øþåó§/ÿãPTmýË÷/¿}ú—/»í‚wþ]ÉÔÍ…ç²Êÿõ¿^ –Þ2Åu­¾Iö²ø¯šÿ²-êÞº_¶Ÿ qkQÔ´ÇÉÉòõKþñ÷_w›÷#×úùË¿ÿño+ž4Bö‡V[E«Ê—Öïø¯»ÍîÓ¿ußQÖ_¾E¾B<_pöSAÐÝ_Ä5Ió—ÝçB„4ÝÕúÿTÝýëëûæ_wewkµ^ZQÃŒÿ üâÛq‹þíõó¹8Ú§ú©x¥y—…F^¶Ì渭jSYoËþkuëæëv×F·—¿¾þöÛ®5Þm]Á?<ÿy0墿/ÿöµ¸ÏÌ®æbR!…73~}7ýzùíÓÿ·ûºÿK=×þð»Wþ)øçJîC¯šÂË×ÿ¹zKñõïþîÃU%÷SgÚÿXT_`aíºUô¨°Åÿæ®ßZ½+ßù÷Žªå¯#OV÷UO #include "isisd/isis_lsp.c" struct thread_master *master; int isis_sock_init(struct isis_circuit *circuit); int isis_sock_init(struct isis_circuit *circuit) { return 0; } struct zebra_privs_t isisd_privs; static void test_lsp_build_list_nonzero_ht(void) { uint8_t lsp_id1[8] = { 0x00, 0x00, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00 }; uint8_t lsp_id_end[8] = { 0x00, 0x00, 0x00, 0x00, 0x00, 0x01, 0x5f, 0x00 }; uint8_t lsp_id2[8] = { 0x00, 0x00, 0x00, 0x00, 0x00, 0x02, 0x00, 0x00 }; struct isis_area *area = calloc(sizeof(*area), 1); area->lsp_mtu = 1500; struct lspdb_head _lspdb, *lspdb = &_lspdb; lsp_db_init(&_lspdb); struct isis_lsp *lsp1 = lsp_new(area, lsp_id1, 6000, 0, 0, 0, NULL, ISIS_LEVEL2); lsp_insert(lspdb, lsp1); struct isis_lsp *lsp2 = lsp_new(area, lsp_id2, 6000, 0, 0, 0, NULL, ISIS_LEVEL2); lsp_insert(lspdb, lsp2); struct list *list = list_new(); lsp_build_list_nonzero_ht(lspdb, lsp_id1, lsp_id_end, list); assert(list->count == 1); assert(listgetdata(listhead(list)) == lsp1); list_delete_all_node(list); lsp_id_end[5] = 0x03; lsp_id_end[6] = 0x00; lsp_build_list_nonzero_ht(lspdb, lsp_id1, lsp_id_end, list); assert(list->count == 2); assert(listgetdata(listhead(list)) == lsp1); assert(listgetdata(listtail(list)) == lsp2); list_delete_all_node(list); memcpy(lsp_id1, lsp_id2, sizeof(lsp_id1)); lsp_build_list_nonzero_ht(lspdb, lsp_id1, lsp_id_end, list); assert(list->count == 1); assert(listgetdata(listhead(list)) == lsp2); list_delete_all_node(list); lsp_id1[5] = 0x03; lsp_id_end[5] = 0x04; lsp_build_list_nonzero_ht(lspdb, lsp_id1, lsp_id_end, list); assert(list->count == 0); list_delete_all_node(list); lsp_id1[5] = 0x00; lsp_build_list_nonzero_ht(lspdb, lsp_id1, lsp_id_end, list); assert(list->count == 2); assert(listgetdata(listhead(list)) == lsp1); assert(listgetdata(listtail(list)) == lsp2); list_delete_all_node(list); } int main(int argc, char **argv) { isis = calloc(sizeof(*isis), 1); test_lsp_build_list_nonzero_ht(); return 0; } frr-7.2.1/tests/isisd/test_isis_lspdb.py0000644000000000000000000000017313610377563015236 00000000000000import frrtest class TestIsisLSPDB(frrtest.TestMultiOut): program = './test_isis_lspdb' TestIsisLSPDB.exit_cleanly() frr-7.2.1/tests/isisd/test_isis_vertex_queue.c0000644000000000000000000000561413610377563016452 00000000000000#include #include "isisd/isis_spf.c" struct thread_master *master; int isis_sock_init(struct isis_circuit *circuit); int isis_sock_init(struct isis_circuit *circuit) { return 0; } struct zebra_privs_t isisd_privs; static struct isis_vertex **vertices; static size_t vertex_count; static void setup_test_vertices(void) { struct isis_spftree t = { }; struct prefix_pair p = { }; uint8_t node_id[7]; vertices = XMALLOC(MTYPE_TMP, sizeof(*vertices) * 16); p.dest.family = AF_INET; p.dest.prefixlen = 24; inet_pton(AF_INET, "192.168.1.0", &p.dest.u.prefix4); vertices[vertex_count] = isis_vertex_new(&t, &p, VTYPE_IPREACH_TE); vertices[vertex_count]->d_N = 20; vertex_count++; p.dest.family = AF_INET; p.dest.prefixlen = 24; inet_pton(AF_INET, "192.168.2.0", &p.dest.u.prefix4); vertices[vertex_count] = isis_vertex_new(&t, &p, VTYPE_IPREACH_TE); vertices[vertex_count]->d_N = 20; vertex_count++; memset(node_id, 0, sizeof(node_id)); node_id[6] = 1; vertices[vertex_count] = isis_vertex_new(&t, node_id, VTYPE_PSEUDO_TE_IS); vertices[vertex_count]->d_N = 15; vertex_count++; memset(node_id, 0, sizeof(node_id)); node_id[5] = 2; vertices[vertex_count] = isis_vertex_new(&t, node_id, VTYPE_NONPSEUDO_TE_IS); vertices[vertex_count]->d_N = 15; vertex_count++; p.dest.family = AF_INET; p.dest.prefixlen = 24; inet_pton(AF_INET, "192.168.3.0", &p.dest.u.prefix4); vertices[vertex_count] = isis_vertex_new(&t, &p, VTYPE_IPREACH_TE); vertices[vertex_count]->d_N = 20; vertex_count++; }; static void cleanup_test_vertices(void) { for (size_t i = 0; i < vertex_count; i++) isis_vertex_del(vertices[i]); XFREE(MTYPE_TMP, vertices); vertex_count = 0; } static void test_ordered(void) { struct isis_vertex_queue q; isis_vertex_queue_init(&q, NULL, true); for (size_t i = 0; i < vertex_count; i++) isis_vertex_queue_insert(&q, vertices[i]); assert(isis_vertex_queue_count(&q) == vertex_count); for (size_t i = 0; i < vertex_count; i++) { assert(isis_find_vertex(&q, &vertices[i]->N, vertices[i]->type) == vertices[i]); } assert(isis_vertex_queue_pop(&q) == vertices[2]); assert(isis_find_vertex(&q, &vertices[2]->N, vertices[2]->type) == NULL); assert(isis_vertex_queue_pop(&q) == vertices[3]); assert(isis_find_vertex(&q, &vertices[3]->N, vertices[3]->type) == NULL); assert(isis_vertex_queue_pop(&q) == vertices[0]); assert(isis_find_vertex(&q, &vertices[0]->N, vertices[0]->type) == NULL); assert(isis_vertex_queue_pop(&q) == vertices[1]); assert(isis_find_vertex(&q, &vertices[1]->N, vertices[1]->type) == NULL); isis_vertex_queue_delete(&q, vertices[4]); assert(isis_find_vertex(&q, &vertices[4]->N, vertices[4]->type) == NULL); assert(isis_vertex_queue_count(&q) == 0); assert(isis_vertex_queue_pop(&q) == NULL); isis_vertex_queue_free(&q); } int main(int argc, char **argv) { setup_test_vertices(); test_ordered(); cleanup_test_vertices(); return 0; } frr-7.2.1/tests/isisd/test_isis_vertex_queue.py0000644000000000000000000000021613610377563016651 00000000000000import frrtest class TestIsisVertexQueue(frrtest.TestMultiOut): program = './test_isis_vertex_queue' TestIsisVertexQueue.exit_cleanly() frr-7.2.1/tests/lib/0000755000000000000000000000000013610377563011204 500000000000000frr-7.2.1/tests/lib/cli/0000755000000000000000000000000013610377563011753 500000000000000frr-7.2.1/tests/lib/cli/common_cli.c0000644000000000000000000000452513610377563014164 00000000000000/* * generic CLI test helper functions * * Copyright (C) 2015 by David Lamparter, * for Open Source Routing / NetDEF, Inc. * * Quagga is free software; you can redistribute it and/or modify it * under the terms of the GNU General Public License as published by the * Free Software Foundation; either version 2, or (at your option) any * later version. * * Quagga 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; see the file COPYING; if not, write to the Free Software * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA */ #include #include "thread.h" #include "vty.h" #include "command.h" #include "memory.h" #include "memory_vty.h" #include "log.h" #include "common_cli.h" struct thread_master *master; int dump_args(struct vty *vty, const char *descr, int argc, struct cmd_token *argv[]) { int i; vty_out(vty, "%s with %d args.\n", descr, argc); for (i = 0; i < argc; i++) { vty_out(vty, "[%02d] %s@%s: %s\n", i, argv[i]->text, argv[i]->varname, argv[i]->arg); } return CMD_SUCCESS; } static void vty_do_exit(int isexit) { printf("\nend.\n"); cmd_terminate(); vty_terminate(); nb_terminate(); yang_terminate(); thread_master_free(master); closezlog(); log_memstats(stderr, "testcli"); if (!isexit) exit(0); } /* main routine. */ int main(int argc, char **argv) { struct thread thread; /* Set umask before anything for security */ umask(0027); /* master init. */ master = thread_master_create(NULL); openzlog("common-cli", "NONE", 0, LOG_CONS | LOG_NDELAY | LOG_PID, LOG_DAEMON); zlog_set_level(ZLOG_DEST_SYSLOG, ZLOG_DISABLED); zlog_set_level(ZLOG_DEST_STDOUT, ZLOG_DISABLED); zlog_set_level(ZLOG_DEST_MONITOR, LOG_DEBUG); /* Library inits. */ cmd_init(1); cmd_hostname_set("test"); cmd_domainname_set("test.domain"); vty_init(master, false); memory_init(); yang_init(); nb_init(master, NULL, 0); test_init(argc, argv); vty_stdio(vty_do_exit); /* Fetch next active thread. */ while (thread_fetch(master, &thread)) thread_call(&thread); /* Not reached. */ exit(0); } frr-7.2.1/tests/lib/cli/common_cli.h0000644000000000000000000000347413610377563014173 00000000000000/* * generic CLI test helper functions * * Copyright (C) 2015 by David Lamparter, * for Open Source Routing / NetDEF, Inc. * * Quagga is free software; you can redistribute it and/or modify it * under the terms of the GNU General Public License as published by the * Free Software Foundation; either version 2, or (at your option) any * later version. * * Quagga 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; see the file COPYING; if not, write to the Free Software * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA */ #ifndef _COMMON_CLI_H #define _COMMON_CLI_H #include "zebra.h" #include "vty.h" #include "command.h" /* function to be implemented by test */ extern void test_init(int argc, char **argv); /* functions provided by common cli * (includes main()) */ extern struct thread_master *master; extern int dump_args(struct vty *vty, const char *descr, int argc, struct cmd_token *argv[]); #define DUMMY_HELPSTR \ "00\n01\n02\n03\n04\n05\n06\n07\n08\n09\n" \ "10\n11\n12\n13\n14\n15\n16\n17\n18\n19\n" \ "20\n21\n22\n23\n24\n25\n26\n27\n28\n29\n" #define DUMMY_DEFUN(name, cmdstr) \ DEFUN(name, name##_cmd, cmdstr, DUMMY_HELPSTR) \ { \ return dump_args(vty, #name, argc, argv); \ } #endif /* _COMMON_CLI_H */ frr-7.2.1/tests/lib/cli/test_cli.c0000644000000000000000000000600613610377563013647 00000000000000/* * CLI/command dummy handling tester * * Copyright (C) 2015 by David Lamparter, * for Open Source Routing / NetDEF, Inc. * * Quagga is free software; you can redistribute it and/or modify it * under the terms of the GNU General Public License as published by the * Free Software Foundation; either version 2, or (at your option) any * later version. * * Quagga 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; see the file COPYING; if not, write to the Free Software * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA */ #include #include "prefix.h" #include "common_cli.h" DUMMY_DEFUN(cmd0, "arg ipv4 A.B.C.D"); DUMMY_DEFUN(cmd1, "arg ipv4m A.B.C.D/M"); DUMMY_DEFUN(cmd2, "arg ipv6 X:X::X:X$foo"); DUMMY_DEFUN(cmd3, "arg ipv6m X:X::X:X/M"); DUMMY_DEFUN(cmd4, "arg range (5-15)"); DUMMY_DEFUN(cmd5, "pat a < a|b>"); DUMMY_DEFUN(cmd6, "pat b "); DUMMY_DEFUN(cmd7, "pat c A.B.C.D"); DUMMY_DEFUN(cmd8, "pat d { foo A.B.C.D$foo|bar X:X::X:X$bar| baz } [final]"); DUMMY_DEFUN(cmd9, "pat e [ WORD ]"); DUMMY_DEFUN(cmd10, "pat f [key]"); DUMMY_DEFUN(cmd11, "alt a WORD"); DUMMY_DEFUN(cmd12, "alt a A.B.C.D"); DUMMY_DEFUN(cmd13, "alt a X:X::X:X"); DUMMY_DEFUN(cmd14, "pat g { foo A.B.C.D$foo|foo|bar X:X::X:X$bar| baz } [final]"); #include "tests/lib/cli/test_cli_clippy.c" DEFPY(magic_test, magic_test_cmd, "magic (0-100) {ipv4net A.B.C.D/M|X:X::X:X$ipv6}", "1\n2\n3\n4\n5\n") { char buf[256]; vty_out(vty, "def: %s\n", self->string); vty_out(vty, "num: %ld\n", magic); vty_out(vty, "ipv4: %s\n", prefix2str(ipv4net, buf, sizeof(buf))); vty_out(vty, "ipv6: %s\n", inet_ntop(AF_INET6, &ipv6, buf, sizeof(buf))); return CMD_SUCCESS; } void test_init(int argc, char **argv) { size_t repeat = argc > 1 ? strtoul(argv[1], NULL, 0) : 223; install_element(ENABLE_NODE, &cmd0_cmd); install_element(ENABLE_NODE, &cmd1_cmd); install_element(ENABLE_NODE, &cmd2_cmd); install_element(ENABLE_NODE, &cmd3_cmd); install_element(ENABLE_NODE, &cmd4_cmd); install_element(ENABLE_NODE, &cmd5_cmd); install_element(ENABLE_NODE, &cmd6_cmd); install_element(ENABLE_NODE, &cmd7_cmd); install_element(ENABLE_NODE, &cmd8_cmd); install_element(ENABLE_NODE, &cmd9_cmd); install_element(ENABLE_NODE, &cmd10_cmd); install_element(ENABLE_NODE, &cmd11_cmd); install_element(ENABLE_NODE, &cmd12_cmd); install_element(ENABLE_NODE, &cmd13_cmd); for (size_t i = 0; i < repeat; i++) { uninstall_element(ENABLE_NODE, &cmd5_cmd); install_element(ENABLE_NODE, &cmd5_cmd); } for (size_t i = 0; i < repeat; i++) { uninstall_element(ENABLE_NODE, &cmd13_cmd); install_element(ENABLE_NODE, &cmd13_cmd); } install_element(ENABLE_NODE, &cmd14_cmd); install_element(ENABLE_NODE, &magic_test_cmd); } frr-7.2.1/tests/lib/cli/test_cli.in0000644000000000000000000000225713610377563014037 00000000000000echo this is a test message echo foo bla ? baz echo arg ipv4 1.2.3.4 arg ipv4 1.2.?3.4 arg ipv4 1.2.3 arg ipv4 1.2.3.4.5 arg ipv4 1.a.3.4 arg ipv4 blah arg ipv4m 1.2.3.0/24 arg ipv4m 1.2.?3.0/24 arg ipv4m 1.2.3/9 arg ipv4m 1.2.3.4.5/6 arg ipv4m 1.a.3.4 arg ipv4m blah arg ipv4m 1.2.3.0/999 arg ipv4m 1.2.3.0/a9 arg ipv4m 1.2.3.0/9a arg ipv6 de4d:b33f::cafe arg ipv6 de4d:b3?3f::caf?e arg ipv6 de4d:b3 3f::caf?e arg ipv6 de4d:b33f:z::cafe arg ipv6 de4d:b33f:cafe: arg ipv6 :: arg ipv6 ::/ arg ipv6 1:2:3:4:5:6:7:8:9:0:1:2:3:4:5:6:7:8:9:0:1:2:3:4:5:6:7:8:9:0 arg ipv6 12::34::56 arg ipv6m dead:beef:cafe::/64 arg ipv6m dead:be?ef:cafe:?:/64 arg range 4 arg range 5 arg range 9? arg range 15 arg range 16 arg range -1 arg range 99999999999999999999999999999999999999999 arg ? pa pat pat a pat a a pat a ?b pat a c? pat a a x pat c a pat c a 1.2.3.4 pat c b 2.3.4 pat c c ?x pat d pat d pat d foo 1.2.3.4 pat d foo pat d noooo pat d bar 1::2 pat d bar 1::2 foo 3.4.5.6 pat d ba?z pat d foo 3.4.5.6 baz pat e pat e f pat e f g pat e 1.2.3.4 pat f pat f foo pat f key alt a a?b alt a 1 .2?.3.4 alt a 1 :2? ::?3 conf t do pat d baz exit show run conf t hostname foohost do show run frr-7.2.1/tests/lib/cli/test_cli.py0000644000000000000000000000014613610377563014054 00000000000000import frrtest class TestCli(frrtest.TestRefOut): program = './test_cli' built_refout = True frr-7.2.1/tests/lib/cli/test_cli.refout0000644000000000000000000001646513610377563014743 00000000000000test# echo this is a test message this is a test message test# echo foo bla % There is no matched command. test# echo foo bla baz foo bla baz test# echo % Command incomplete. test# test# arg ipv4 1.2.3.4 cmd0 with 3 args. [00] arg@(null): arg [01] ipv4@(null): ipv4 [02] A.B.C.D@ipv4: 1.2.3.4 test# arg ipv4 1.2. A.B.C.D 02 test# arg ipv4 1.2.3.4 cmd0 with 3 args. [00] arg@(null): arg [01] ipv4@(null): ipv4 [02] A.B.C.D@ipv4: 1.2.3.4 test# arg ipv4 1.2.3 % [NONE] Unknown command: arg ipv4 1.2.3 test# arg ipv4 1.2.3.4.5 % [NONE] Unknown command: arg ipv4 1.2.3.4.5 test# arg ipv4 1.a.3.4 % [NONE] Unknown command: arg ipv4 1.a.3.4 test# arg ipv4 blah % [NONE] Unknown command: arg ipv4 blah test# test# arg ipv4m 1.2.3.0/24 cmd1 with 3 args. [00] arg@(null): arg [01] ipv4m@(null): ipv4m [02] A.B.C.D/M@ipv4m: 1.2.3.0/24 test# arg ipv4m 1.2. A.B.C.D/M 02 test# arg ipv4m 1.2.3.0/24 cmd1 with 3 args. [00] arg@(null): arg [01] ipv4m@(null): ipv4m [02] A.B.C.D/M@ipv4m: 1.2.3.0/24 test# arg ipv4m 1.2.3/9 % [NONE] Unknown command: arg ipv4m 1.2.3/9 test# arg ipv4m 1.2.3.4.5/6 % [NONE] Unknown command: arg ipv4m 1.2.3.4.5/6 test# arg ipv4m 1.a.3.4 % [NONE] Unknown command: arg ipv4m 1.a.3.4 test# arg ipv4m blah % [NONE] Unknown command: arg ipv4m blah test# arg ipv4m 1.2.3.0/999 % [NONE] Unknown command: arg ipv4m 1.2.3.0/999 test# arg ipv4m 1.2.3.0/a9 % [NONE] Unknown command: arg ipv4m 1.2.3.0/a9 test# arg ipv4m 1.2.3.0/9a % [NONE] Unknown command: arg ipv4m 1.2.3.0/9a test# test# arg ipv6 de4d:b33f::cafe cmd2 with 3 args. [00] arg@(null): arg [01] ipv6@(null): ipv6 [02] X:X::X:X@foo: de4d:b33f::cafe test# arg ipv6 de4d:b3 X:X::X:X 02 test# arg ipv6 de4d:b33f::caf X:X::X:X 02 test# arg ipv6 de4d:b33f::cafe cmd2 with 3 args. [00] arg@(null): arg [01] ipv6@(null): ipv6 [02] X:X::X:X@foo: de4d:b33f::cafe test# arg ipv6 de4d:b3 test# arg ipv6 de4d:b33f::caf X:X::X:X 02 test# arg ipv6 de4d:b33f::cafe cmd2 with 3 args. [00] arg@(null): arg [01] ipv6@(null): ipv6 [02] X:X::X:X@foo: de4d:b33f::cafe test# arg ipv6 de4d:b33f:z::cafe % [NONE] Unknown command: arg ipv6 de4d:b33f:z::cafe test# arg ipv6 de4d:b33f:cafe: % [NONE] Unknown command: arg ipv6 de4d:b33f:cafe: test# arg ipv6 :: cmd2 with 3 args. [00] arg@(null): arg [01] ipv6@(null): ipv6 [02] X:X::X:X@foo: :: test# arg ipv6 ::/ % [NONE] Unknown command: arg ipv6 ::/ test# arg ipv6 1:2:3:4:5:6:7:8:9:0:1:2:3:4:5:6:7:8:9:0:1:2:3:4:5:6:7:8:9:0 % [NONE] Unknown command: arg ipv6 1:2:3:4:5:6:7:8:9:0:1:2:3:4:5:6:7:8:9:0:1:2:3:4:5:6:7:8:9:0 test# arg ipv6 12::34::56 % [NONE] Unknown command: arg ipv6 12::34::56 test# arg ipv6m dead:beef:cafe::/64 cmd3 with 3 args. [00] arg@(null): arg [01] ipv6m@(null): ipv6m [02] X:X::X:X/M@ipv6m: dead:beef:cafe::/64 test# arg ipv6m dead:be X:X::X:X/M 02 test# arg ipv6m dead:beef:cafe: X:X::X:X/M 02 test# arg ipv6m dead:beef:cafe::/64 cmd3 with 3 args. [00] arg@(null): arg [01] ipv6m@(null): ipv6m [02] X:X::X:X/M@ipv6m: dead:beef:cafe::/64 test# test# arg range 4 % [NONE] Unknown command: arg range 4 test# arg range 5 cmd4 with 3 args. [00] arg@(null): arg [01] range@(null): range [02] (5-15)@range: 5 test# arg range 9 (5-15) 02 test# arg range 9 cmd4 with 3 args. [00] arg@(null): arg [01] range@(null): range [02] (5-15)@range: 9 test# arg range 15 cmd4 with 3 args. [00] arg@(null): arg [01] range@(null): range [02] (5-15)@range: 15 test# arg range 16 % [NONE] Unknown command: arg range 16 test# arg range -1 % [NONE] Unknown command: arg range -1 test# arg range 99999999999999999999999999999999999999999 % [NONE] Unknown command: arg range 99999999999999999999999999999999999999999 test# test# arg ipv4 01 ipv4m 01 ipv6 01 ipv6m 01 range 01 test# arg % Command incomplete. test# test# pa test# papat % Command incomplete. test# pat a b c d e f g test# pat % Command incomplete. test# test# pat a % Command incomplete. test# pat a a cmd5 with 3 args. [00] pat@(null): pat [01] a@(null): a [02] a@(null): a test# pat a a 02 b 03 test# pat a b cmd5 with 3 args. [00] pat@(null): pat [01] a@(null): a [02] b@(null): b test# pat a c % There is no matched command. test# pat a c % [NONE] Unknown command: pat a c test# pat a a x % [NONE] Unknown command: pat a a x test# test# pat c a % Command incomplete. test# pat c a 1.2.3.4 cmd7 with 4 args. [00] pat@(null): pat [01] c@(null): c [02] a@(null): a [03] A.B.C.D@(null): 1.2.3.4 test# pat c b 2.3.4 % [NONE] Unknown command: pat c b 2.3.4 test# pat c c A.B.C.D 05 test# pat c c x % [NONE] Unknown command: pat c c x test# test# pat d % Command incomplete. test# pat d bar baz foo test# pat d % Command incomplete. test# pat d foo 1.2.3.4 cmd8 with 4 args. [00] pat@(null): pat [01] d@(null): d [02] foo@(null): foo [03] A.B.C.D@foo: 1.2.3.4 test# pat d foo % Command incomplete. test# pat d noooo % [NONE] Unknown command: pat d noooo test# pat d bar 1::2 cmd8 with 4 args. [00] pat@(null): pat [01] d@(null): d [02] bar@(null): bar [03] X:X::X:X@bar: 1::2 test# pat d bar 1::2 foo 3.4.5.6 cmd8 with 6 args. [00] pat@(null): pat [01] d@(null): d [02] bar@(null): bar [03] X:X::X:X@bar: 1::2 [04] foo@(null): foo [05] A.B.C.D@foo: 3.4.5.6 test# pat d ba bar 04 baz 06 test# pat d baz cmd8 with 3 args. [00] pat@(null): pat [01] d@(null): d [02] baz@(null): baz test# pat d foo 3.4.5.6 baz cmd8 with 5 args. [00] pat@(null): pat [01] d@(null): d [02] foo@(null): foo [03] A.B.C.D@foo: 3.4.5.6 [04] baz@(null): baz test# test# pat e cmd9 with 2 args. [00] pat@(null): pat [01] e@(null): e test# pat e f cmd9 with 3 args. [00] pat@(null): pat [01] e@(null): e [02] WORD@e: f test# pat e f g % [NONE] Unknown command: pat e f g test# pat e 1.2.3.4 cmd9 with 3 args. [00] pat@(null): pat [01] e@(null): e [02] WORD@e: 1.2.3.4 test# test# pat f cmd10 with 2 args. [00] pat@(null): pat [01] f@(null): f test# pat f foo % [NONE] Unknown command: pat f foo test# pat f key cmd10 with 3 args. [00] pat@(null): pat [01] f@(null): f [02] key@(null): key test# test# alt a test# alt a a WORD 02 X:X::X:X 02 test# alt a ab cmd11 with 3 args. [00] alt@(null): alt [01] a@(null): a [02] WORD@a: ab test# alt a 1 test# alt a 1.2 A.B.C.D 02 WORD 02 test# alt a 1.2.3.4 cmd12 with 3 args. [00] alt@(null): alt [01] a@(null): a [02] A.B.C.D@a: 1.2.3.4 test# alt a 1 test# alt a 1:2 WORD 02 X:X::X:X 02 test# alt a 1:2 test# alt a 1:2:: WORD 02 X:X::X:X 02 test# alt a 1:2::3 cmd13 with 3 args. [00] alt@(null): alt [01] a@(null): a [02] X:X::X:X@a: 1:2::3 test# test# conf t test(config)# do pat d baz cmd8 with 3 args. [00] pat@(null): pat [01] d@(null): d [02] baz@(null): baz test(config)# exit test# test# show run Current configuration: ! frr version 7.2.1 frr defaults traditional ! hostname test domainname test.domain ! ! ! line vty ! end test# conf t test(config)# hostname foohost foohost(config)# do show run Current configuration: ! frr version 7.2.1 frr defaults traditional ! hostname foohost domainname test.domain ! ! ! line vty ! end foohost(config)# end. frr-7.2.1/tests/lib/cli/test_cli.refout.in0000644000000000000000000001651513610377563015344 00000000000000test# echo this is a test message this is a test message test# echo foo bla % There is no matched command. test# echo foo bla baz foo bla baz test# echo % Command incomplete. test# test# arg ipv4 1.2.3.4 cmd0 with 3 args. [00] arg@(null): arg [01] ipv4@(null): ipv4 [02] A.B.C.D@ipv4: 1.2.3.4 test# arg ipv4 1.2. A.B.C.D 02 test# arg ipv4 1.2.3.4 cmd0 with 3 args. [00] arg@(null): arg [01] ipv4@(null): ipv4 [02] A.B.C.D@ipv4: 1.2.3.4 test# arg ipv4 1.2.3 % [NONE] Unknown command: arg ipv4 1.2.3 test# arg ipv4 1.2.3.4.5 % [NONE] Unknown command: arg ipv4 1.2.3.4.5 test# arg ipv4 1.a.3.4 % [NONE] Unknown command: arg ipv4 1.a.3.4 test# arg ipv4 blah % [NONE] Unknown command: arg ipv4 blah test# test# arg ipv4m 1.2.3.0/24 cmd1 with 3 args. [00] arg@(null): arg [01] ipv4m@(null): ipv4m [02] A.B.C.D/M@ipv4m: 1.2.3.0/24 test# arg ipv4m 1.2. A.B.C.D/M 02 test# arg ipv4m 1.2.3.0/24 cmd1 with 3 args. [00] arg@(null): arg [01] ipv4m@(null): ipv4m [02] A.B.C.D/M@ipv4m: 1.2.3.0/24 test# arg ipv4m 1.2.3/9 % [NONE] Unknown command: arg ipv4m 1.2.3/9 test# arg ipv4m 1.2.3.4.5/6 % [NONE] Unknown command: arg ipv4m 1.2.3.4.5/6 test# arg ipv4m 1.a.3.4 % [NONE] Unknown command: arg ipv4m 1.a.3.4 test# arg ipv4m blah % [NONE] Unknown command: arg ipv4m blah test# arg ipv4m 1.2.3.0/999 % [NONE] Unknown command: arg ipv4m 1.2.3.0/999 test# arg ipv4m 1.2.3.0/a9 % [NONE] Unknown command: arg ipv4m 1.2.3.0/a9 test# arg ipv4m 1.2.3.0/9a % [NONE] Unknown command: arg ipv4m 1.2.3.0/9a test# test# arg ipv6 de4d:b33f::cafe cmd2 with 3 args. [00] arg@(null): arg [01] ipv6@(null): ipv6 [02] X:X::X:X@foo: de4d:b33f::cafe test# arg ipv6 de4d:b3 X:X::X:X 02 test# arg ipv6 de4d:b33f::caf X:X::X:X 02 test# arg ipv6 de4d:b33f::cafe cmd2 with 3 args. [00] arg@(null): arg [01] ipv6@(null): ipv6 [02] X:X::X:X@foo: de4d:b33f::cafe test# arg ipv6 de4d:b3 test# arg ipv6 de4d:b33f::caf X:X::X:X 02 test# arg ipv6 de4d:b33f::cafe cmd2 with 3 args. [00] arg@(null): arg [01] ipv6@(null): ipv6 [02] X:X::X:X@foo: de4d:b33f::cafe test# arg ipv6 de4d:b33f:z::cafe % [NONE] Unknown command: arg ipv6 de4d:b33f:z::cafe test# arg ipv6 de4d:b33f:cafe: % [NONE] Unknown command: arg ipv6 de4d:b33f:cafe: test# arg ipv6 :: cmd2 with 3 args. [00] arg@(null): arg [01] ipv6@(null): ipv6 [02] X:X::X:X@foo: :: test# arg ipv6 ::/ % [NONE] Unknown command: arg ipv6 ::/ test# arg ipv6 1:2:3:4:5:6:7:8:9:0:1:2:3:4:5:6:7:8:9:0:1:2:3:4:5:6:7:8:9:0 % [NONE] Unknown command: arg ipv6 1:2:3:4:5:6:7:8:9:0:1:2:3:4:5:6:7:8:9:0:1:2:3:4:5:6:7:8:9:0 test# arg ipv6 12::34::56 % [NONE] Unknown command: arg ipv6 12::34::56 test# arg ipv6m dead:beef:cafe::/64 cmd3 with 3 args. [00] arg@(null): arg [01] ipv6m@(null): ipv6m [02] X:X::X:X/M@ipv6m: dead:beef:cafe::/64 test# arg ipv6m dead:be X:X::X:X/M 02 test# arg ipv6m dead:beef:cafe: X:X::X:X/M 02 test# arg ipv6m dead:beef:cafe::/64 cmd3 with 3 args. [00] arg@(null): arg [01] ipv6m@(null): ipv6m [02] X:X::X:X/M@ipv6m: dead:beef:cafe::/64 test# test# arg range 4 % [NONE] Unknown command: arg range 4 test# arg range 5 cmd4 with 3 args. [00] arg@(null): arg [01] range@(null): range [02] (5-15)@range: 5 test# arg range 9 (5-15) 02 test# arg range 9 cmd4 with 3 args. [00] arg@(null): arg [01] range@(null): range [02] (5-15)@range: 9 test# arg range 15 cmd4 with 3 args. [00] arg@(null): arg [01] range@(null): range [02] (5-15)@range: 15 test# arg range 16 % [NONE] Unknown command: arg range 16 test# arg range -1 % [NONE] Unknown command: arg range -1 test# arg range 99999999999999999999999999999999999999999 % [NONE] Unknown command: arg range 99999999999999999999999999999999999999999 test# test# arg ipv4 01 ipv4m 01 ipv6 01 ipv6m 01 range 01 test# arg % Command incomplete. test# test# pa test# papat % Command incomplete. test# pat a b c d e f g test# pat % Command incomplete. test# test# pat a % Command incomplete. test# pat a a cmd5 with 3 args. [00] pat@(null): pat [01] a@(null): a [02] a@(null): a test# pat a a 02 b 03 test# pat a b cmd5 with 3 args. [00] pat@(null): pat [01] a@(null): a [02] b@(null): b test# pat a c % There is no matched command. test# pat a c % [NONE] Unknown command: pat a c test# pat a a x % [NONE] Unknown command: pat a a x test# test# pat c a % Command incomplete. test# pat c a 1.2.3.4 cmd7 with 4 args. [00] pat@(null): pat [01] c@(null): c [02] a@(null): a [03] A.B.C.D@(null): 1.2.3.4 test# pat c b 2.3.4 % [NONE] Unknown command: pat c b 2.3.4 test# pat c c A.B.C.D 05 test# pat c c x % [NONE] Unknown command: pat c c x test# test# pat d % Command incomplete. test# pat d bar baz foo test# pat d % Command incomplete. test# pat d foo 1.2.3.4 cmd8 with 4 args. [00] pat@(null): pat [01] d@(null): d [02] foo@(null): foo [03] A.B.C.D@foo: 1.2.3.4 test# pat d foo % Command incomplete. test# pat d noooo % [NONE] Unknown command: pat d noooo test# pat d bar 1::2 cmd8 with 4 args. [00] pat@(null): pat [01] d@(null): d [02] bar@(null): bar [03] X:X::X:X@bar: 1::2 test# pat d bar 1::2 foo 3.4.5.6 cmd8 with 6 args. [00] pat@(null): pat [01] d@(null): d [02] bar@(null): bar [03] X:X::X:X@bar: 1::2 [04] foo@(null): foo [05] A.B.C.D@foo: 3.4.5.6 test# pat d ba bar 04 baz 06 test# pat d baz cmd8 with 3 args. [00] pat@(null): pat [01] d@(null): d [02] baz@(null): baz test# pat d foo 3.4.5.6 baz cmd8 with 5 args. [00] pat@(null): pat [01] d@(null): d [02] foo@(null): foo [03] A.B.C.D@foo: 3.4.5.6 [04] baz@(null): baz test# test# pat e cmd9 with 2 args. [00] pat@(null): pat [01] e@(null): e test# pat e f cmd9 with 3 args. [00] pat@(null): pat [01] e@(null): e [02] WORD@e: f test# pat e f g % [NONE] Unknown command: pat e f g test# pat e 1.2.3.4 cmd9 with 3 args. [00] pat@(null): pat [01] e@(null): e [02] WORD@e: 1.2.3.4 test# test# pat f cmd10 with 2 args. [00] pat@(null): pat [01] f@(null): f test# pat f foo % [NONE] Unknown command: pat f foo test# pat f key cmd10 with 3 args. [00] pat@(null): pat [01] f@(null): f [02] key@(null): key test# test# alt a test# alt a a WORD 02 X:X::X:X 02 test# alt a ab cmd11 with 3 args. [00] alt@(null): alt [01] a@(null): a [02] WORD@a: ab test# alt a 1 test# alt a 1.2 A.B.C.D 02 WORD 02 test# alt a 1.2.3.4 cmd12 with 3 args. [00] alt@(null): alt [01] a@(null): a [02] A.B.C.D@a: 1.2.3.4 test# alt a 1 test# alt a 1:2 WORD 02 X:X::X:X 02 test# alt a 1:2 test# alt a 1:2:: WORD 02 X:X::X:X 02 test# alt a 1:2::3 cmd13 with 3 args. [00] alt@(null): alt [01] a@(null): a [02] X:X::X:X@a: 1:2::3 test# test# conf t test(config)# do pat d baz cmd8 with 3 args. [00] pat@(null): pat [01] d@(null): d [02] baz@(null): baz test(config)# exit test# test# show run Current configuration: ! frr version @PACKAGE_VERSION@ frr defaults @DFLT_NAME@ ! hostname test domainname test.domain ! ! ! line vty ! end test# conf t test(config)# hostname foohost foohost(config)# do show run Current configuration: ! frr version @PACKAGE_VERSION@ frr defaults @DFLT_NAME@ ! hostname foohost domainname test.domain ! ! ! line vty ! end foohost(config)# end. frr-7.2.1/tests/lib/cli/test_commands.c0000644000000000000000000002136313610377563014704 00000000000000/* * Test code for lib/command.c * * Copyright (C) 2013 by Open Source Routing. * Copyright (C) 2013 by Internet Systems Consortium, Inc. ("ISC") * * This program reads in a list of commandlines from stdin * and calls all the public functions of lib/command.c for * both the given command lines and fuzzed versions thereof. * * The output is currently not validated but only logged. It can * be diffed to find regressions between versions. * * Quagga is free software; you can redistribute it and/or modify it * under the terms of the GNU General Public License as published by the * Free Software Foundation; either version 2, or (at your option) any * later version. * * Quagga 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; see the file COPYING; if not, write to the Free Software * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA */ #define REALLY_NEED_PLAIN_GETOPT 1 #include #include #include #include #include "command.h" #include "memory.h" #include "vector.h" #include "prng.h" extern vector cmdvec; extern struct cmd_node vty_node; extern void test_init_cmd(void); /* provided in test-commands-defun.c */ struct thread_master *master; /* dummy for libfrr*/ static vector test_cmds; static char test_buf[32768]; static struct cmd_node bgp_node = { BGP_NODE, "%s(config-router)# ", }; static struct cmd_node rip_node = { RIP_NODE, "%s(config-router)# ", }; static struct cmd_node isis_node = { ISIS_NODE, "%s(config-router)# ", }; static struct cmd_node interface_node = { INTERFACE_NODE, "%s(config-if)# ", }; static struct cmd_node rmap_node = {RMAP_NODE, "%s(config-route-map)# "}; static struct cmd_node zebra_node = {ZEBRA_NODE, "%s(config-router)# "}; static struct cmd_node bgp_vpnv4_node = {BGP_VPNV4_NODE, "%s(config-router-af)# "}; static struct cmd_node bgp_ipv4_node = {BGP_IPV4_NODE, "%s(config-router-af)# "}; static struct cmd_node bgp_ipv4m_node = {BGP_IPV4M_NODE, "%s(config-router-af)# "}; static struct cmd_node bgp_ipv6_node = {BGP_IPV6_NODE, "%s(config-router-af)# "}; static struct cmd_node bgp_ipv6m_node = {BGP_IPV6M_NODE, "%s(config-router-af)# "}; static struct cmd_node ospf_node = {OSPF_NODE, "%s(config-router)# "}; static struct cmd_node ripng_node = {RIPNG_NODE, "%s(config-router)# "}; static struct cmd_node ospf6_node = {OSPF6_NODE, "%s(config-ospf6)# "}; static struct cmd_node keychain_node = {KEYCHAIN_NODE, "%s(config-keychain)# "}; static struct cmd_node keychain_key_node = {KEYCHAIN_KEY_NODE, "%s(config-keychain-key)# "}; static int test_callback(const struct cmd_element *cmd, struct vty *vty, int argc, struct cmd_token *argv[]) { int offset; int rv; int i; offset = 0; rv = snprintf(test_buf, sizeof(test_buf), "'%s'", cmd->string); if (rv < 0) abort(); offset += rv; for (i = 0; i < argc; i++) { rv = snprintf(test_buf + offset, sizeof(test_buf) - offset, "%s'%s'", (i == 0) ? ": " : ", ", argv[i]->arg); if (rv < 0) abort(); offset += rv; } return CMD_SUCCESS; } static void test_load(void) { char line[4096]; test_cmds = vector_init(VECTOR_MIN_SIZE); while (fgets(line, sizeof(line), stdin) != NULL) { if (strlen(line)) line[strlen(line) - 1] = '\0'; if (line[0] == '#') continue; vector_set(test_cmds, XSTRDUP(MTYPE_TMP, line)); } } static void test_init(void) { unsigned int node; unsigned int i; struct cmd_node *cnode; struct cmd_element *cmd; cmd_init(1); yang_init(); nb_init(master, NULL, 0); install_node(&bgp_node, NULL); install_node(&rip_node, NULL); install_node(&interface_node, NULL); install_node(&rmap_node, NULL); install_node(&zebra_node, NULL); install_node(&bgp_vpnv4_node, NULL); install_node(&bgp_ipv4_node, NULL); install_node(&bgp_ipv4m_node, NULL); install_node(&bgp_ipv6_node, NULL); install_node(&bgp_ipv6m_node, NULL); install_node(&ospf_node, NULL); install_node(&ripng_node, NULL); install_node(&ospf6_node, NULL); install_node(&keychain_node, NULL); install_node(&keychain_key_node, NULL); install_node(&isis_node, NULL); install_node(&vty_node, NULL); test_init_cmd(); for (node = 0; node < vector_active(cmdvec); node++) if ((cnode = vector_slot(cmdvec, node)) != NULL) for (i = 0; i < vector_active(cnode->cmd_vector); i++) if ((cmd = vector_slot(cnode->cmd_vector, i)) != NULL) { cmd->daemon = 0; cmd->func = test_callback; } test_load(); vty_init_vtysh(); } static void test_terminate(void) { unsigned int i; vty_terminate(); for (i = 0; i < vector_active(test_cmds); i++) XFREE(MTYPE_TMP, vector_slot(test_cmds, i)); vector_free(test_cmds); cmd_terminate(); nb_terminate(); yang_terminate(); } static void test_run(struct prng *prng, struct vty *vty, const char *cmd, unsigned int edit_dist, unsigned int node_index, int verbose) { const char *test_str; vector vline; int ret; unsigned int i; char **completions; unsigned int j; struct cmd_node *cnode; vector descriptions; int appended_null; int no_match; test_str = prng_fuzz( prng, cmd, "abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789-_:. /", edit_dist); vline = cmd_make_strvec(test_str); if (vline == NULL) return; appended_null = 0; for (i = 0; i < vector_active(cmdvec); i++) if ((cnode = vector_slot(cmdvec, i)) != NULL) { if (node_index != (unsigned int)-1 && i != node_index) continue; if (appended_null) { vector_unset(vline, vector_active(vline) - 1); appended_null = 0; } vty->node = cnode->node; test_buf[0] = '\0'; ret = cmd_execute_command(vline, vty, NULL, 0); no_match = (ret == CMD_ERR_NO_MATCH); if (verbose || !no_match) printf("execute relaxed '%s'@%d: rv==%d%s%s\n", test_str, cnode->node, ret, (test_buf[0] != '\0') ? ", " : "", test_buf); vty->node = cnode->node; test_buf[0] = '\0'; ret = cmd_execute_command_strict(vline, vty, NULL); if (verbose || !no_match) printf("execute strict '%s'@%d: rv==%d%s%s\n", test_str, cnode->node, ret, (test_buf[0] != '\0') ? ", " : "", test_buf); if (isspace((unsigned char)test_str[ strlen(test_str) - 1])) { vector_set(vline, NULL); appended_null = 1; } vty->node = cnode->node; completions = cmd_complete_command(vline, vty, &ret); if (verbose || !no_match) printf("complete '%s'@%d: rv==%d\n", test_str, cnode->node, ret); if (completions != NULL) { for (j = 0; completions[j] != NULL; j++) { printf(" '%s'\n", completions[j]); XFREE(MTYPE_TMP, completions[j]); } XFREE(MTYPE_TMP, completions); } vty->node = cnode->node; descriptions = cmd_describe_command(vline, vty, &ret); if (verbose || !no_match) printf("describe '%s'@%d: rv==%d\n", test_str, cnode->node, ret); if (descriptions != NULL) { for (j = 0; j < vector_active(descriptions); j++) { struct cmd_token *ct = vector_slot(descriptions, j); printf(" '%s' '%s'\n", ct->text, ct->desc); } vector_free(descriptions); } } cmd_free_strvec(vline); } int main(int argc, char **argv) { int opt; struct prng *prng; struct vty *vty; unsigned int edit_distance; unsigned int max_edit_distance; unsigned int node_index; int verbose; unsigned int test_cmd; unsigned int iteration; unsigned int num_iterations; max_edit_distance = 3; node_index = -1; verbose = 0; while ((opt = getopt(argc, argv, "e:n:v")) != -1) { switch (opt) { case 'e': max_edit_distance = atoi(optarg); break; case 'n': node_index = atoi(optarg); break; case 'v': verbose++; break; default: fprintf(stderr, "Usage: %s [-e ] [-n ] [-v]\n", argv[0]); exit(1); break; } } test_init(); prng = prng_new(0); vty = vty_new(); vty->type = VTY_TERM; fprintf(stderr, "Progress:\n0/%u", vector_active(test_cmds)); for (test_cmd = 0; test_cmd < vector_active(test_cmds); test_cmd++) { for (edit_distance = 0; edit_distance <= max_edit_distance; edit_distance++) { num_iterations = 1 << edit_distance; num_iterations *= num_iterations * num_iterations; for (iteration = 0; iteration < num_iterations; iteration++) test_run(prng, vty, vector_slot(test_cmds, test_cmd), edit_distance, node_index, verbose); } fprintf(stderr, "\r%u/%u", test_cmd + 1, vector_active(test_cmds)); } fprintf(stderr, "\nDone.\n"); vty_close(vty); prng_free(prng); test_terminate(); return 0; } frr-7.2.1/tests/lib/cli/test_commands.in0000644000000000000000000003166113610377563015072 00000000000000# # # Some randomly chosen valid commands # # area 0 virtual-link 1.2.3.4 authentication null message-digest-key 1 md5 VARIABLE area 0 virtual-link 1.2.3.4 dead-interval 1 hello-interval 1 retransmit-interval 1 transmit-delay 1 area 0 virtual-link 1.2.3.4 retransmit-interval 1 retransmit-interval 1 dead-interval 1 retransmit-interval 1 area 1.2.3.4 virtual-link 1.2.3.4 hello-interval 1 hello-interval 1 dead-interval 1 area 1.2.3.4 virtual-link 1.2.3.4 retransmit-interval 1 retransmit-interval 1 transmit-delay 1 dead-interval 1 clear bgp 1 out clear bgp ipv6 2001:db8::1 out clear bgp view VARIABLE * soft clear ip bgp 1.2.3.4 ipv4 multicast out ipv6 nd prefix 2001:db8::/32 infinite infinite no-autoconfig ipv6 route 2001:db8::/32 2001:db8::1 VARIABLE 1 network 1.0.0.0/8 area 0 no area 1.2.3.4 virtual-link 1.2.3.4 hello-interval dead-interval hello-interval transmit-delay no area 1.2.3.4 virtual-link 1.2.3.4 hello-interval dead-interval retransmit-interval transmit-delay no area 1.2.3.4 virtual-link 1.2.3.4 retransmit-interval dead-interval retransmit-interval hello-interval no area 1.2.3.4 virtual-link 1.2.3.4 transmit-delay retransmit-interval retransmit-interval hello-interval no bgp graceful-restart no ipv6 nd mtu 1 no neighbor 1.2.3.4 distribute-list 1 in no neighbor 2001:db8::1 send-community both no neighbor VARIABLE maximum-prefix redistribute isis route-map VARIABLE metric 0 metric-type 2 redistribute rip metric 0 route-map VARIABLE metric-type 1 show bgp community VARIABLE local-AS no-export VARIABLE exact-match show bgp ipv6 community no-advertise no-export no-export no-export show bgp ipv6 community VARIABLE local-AS local-AS no-advertise exact-match show bgp ipv6 community VARIABLE local-AS VARIABLE local-AS exact-match show bgp view VARIABLE show bgp view VARIABLE ipv4 multicast community no-export no-export local-AS VARIABLE show bgp view VARIABLE ipv4 unicast community local-AS no-advertise no-advertise local-AS show bgp view VARIABLE ipv4 unicast community no-export VARIABLE no-advertise show bgp view VARIABLE ipv4 unicast community VARIABLE local-AS no-export VARIABLE show bgp view VARIABLE ipv6 multicast community no-advertise VARIABLE no-advertise local-AS show ip bgp community no-advertise local-AS no-advertise VARIABLE show ip bgp ipv4 unicast community no-advertise VARIABLE no-advertise local-AS exact-match show ip bgp ipv4 unicast community no-export VARIABLE local-AS VARIABLE exact-match show ip bgp ipv4 unicast community no-export VARIABLE no-export local-AS exact-match show ipv6 bgp community no-export no-export VARIABLE VARIABLE show ipv6 bgp community VARIABLE local-AS no-advertise no-advertise exact-match show ipv6 mbgp community local-AS local-AS local-AS VARIABLE exact-match show ipv6 mbgp community local-AS local-AS no-export no-export exact-match show ipv6 mbgp community no-export no-export local-AS no-export exact-match show ipv6 ospf6 database as-external dump show ipv6 ospf6 database inter-prefix 1.2.3.4 detail show ipv6 ospf6 database intra-prefix 1.2.3.4 internal # # # Slightly Fuzzed commands # # a8ra 0 range 1.0.0.0/8 adverOise accept-lifetime VARIABE 1 VA6IABLE 19I3 VARIABLE 1 VARIABLE 1993 arAea 1.2.M.4 virtual-link 1.2.3.4 dead-interval 1 dead-interval 1 dead-inter6val 1 transmit-delay 1 area 0 virtu0al-link 1.2.3.i hello-interval 1 ello-interval 1 transmit-delay 1 retransmit-interval 1 area 0 virtual-lin 1.2.3.4 retransmit-interval 1 tranwmit-delay 1 retransmit-interval 1 retransmit-interval 1 area 0 virtual-link 1.2.3.4 retransmit-interal 1 trasmit-dely 1 area 1.2.3.4 virtual-link 1.2.3.4 deadCinterval 1 dead-intervalK 1 retransmit-interval 1 dead-interval 1 area 1.2.3.4 virtual-link 1.2.3.4 dead-intervalo I1 dead-interval 1 retransmit-interval1 dead-interval 1 area 1.2.3.4 virtual-link 1.2.3.4 hello-interval 1 dead-interva 1 retransmit-interval 1 transmit-delay 1 area 1.2.3.4 virtuyl-link 1.2.3.4 dead-interval 1 dead-inervalI 1 retransmit-interval 1 dead-interval 1 area 1.2.3.4 virual-link 1.2.34 retransmit-interval 1 dead-interval 1 dead-interva 1 area1.2.83.4 virtual-link 1.2.3.4 retra0smit-interval 1 dead-interval 1 dead-interval 1 clear bgAp 2001g:dbK::1 clear ip bgp 1.2.3.4 pv4 mlticat out cleau bg i2001:db8::1 rsclient de:ug ospf6 messag2 lsreq :recv how ip bgp communiQy no-advertise no-adve:tise no-advertise ip route 1.0Q0.0/8 1.2.3.s4 reGject ipv6 nd prefix 2O01:db8::/32 0 infinEite off-link ipv6 nwd prefix 2001:db8::/32 0 infinite oUUff-link ipv6 route 2001:db8::/32q2001:db8:k: blackhole 1 kshow ip rIute bgp matcch peer .2.30.4 mcogin mhow ipv6 mbgp community o-advertise yocal-AS no-advertise neighbor1.2..4 attribute-unchnged next-hop neihbcr 2001:d b8::1 distribute-list 1 in nko key-tqring no area 0 viertual-link 1.2.3k.4 retransmit-iterval retransmit-interval retransmit-interval hello-interval no area 0 virtual-link 1.2.3.4 dead-intaerval dead-intervIl hello-interval retransmit-interval no area 0 virtual-link 1.2.3.4 retransmit-interval retransmit-intervIl dead-interval tranImit-deqlay no area 0 virtual-link S1.2.3.4 d-ead-interval hello-interval transmit-deay transmit-delay no area 1.2.3.4 virtua -link 1.2.3.4 transmit-delay hello-interval hello-interval retransmt-interval no area 1.2.3.4 virtual-link 1.2.3.4 dea-iterval retransmit-interva- dead-interval hello-interval no area 1.2.3.4 virtual-link 1.2.3.4 hello-interSval dead-interval retransmit-interval transmitdelay no a:rea 1.2.3.4 virtual-link 1.2.3.4 retransmit-interSvalW dead-interval retransmit-interval hello-interval noarea 1.2.3.4 virtual-link 1.2.3.4 retransmit-interval trynsmit-delay hello-interval no area 1.2.3.4 virtual-link 1.2.3.4 transmt:delay retransmit-interval retransmit-interval dead-Mnterval no ares 1.2.3.4 virtual-link 1.2.3.4 dead-interval retransmit-interval dead-inesval retransmit-interval no ayrea 1.2.3.4 virtual-link 1.2.3.4 retransmit-interval transmi-delay hello-interval no bg2 grace2fuy-restart no debug ospk6 nter2face noimatch ipv6 addrMss VARIABLE nomStch iA next-hop prefix-list no neighbCr 200 :db8::1oroute-map VARIABLE export no neighbor VARIABLE attributeaw8changed next-hop no orea 1.2.3.4 virtual-link 1.2.3.4 retransmit-interval ead-interval retransmit-inteSval hello-interval no ospcdead-inkerval no redistribute kernelrote-map VARIABLE metric 0 no redistribute s4taik metric 0 nos Ceighbor 1.2.3.4 route-mapEVARIABLE in o :neighbor VAIABLE attribute-unchanged next-hop ooa router ip redistribute isis meGtric-type2 Q route-map VARIABLE redistribute static metric-type 1 metri 0 rowute-map VARIABLE set-Koveroadbit sh2w ipv6 mbgp comAunity VARIABLE shgw bgp ipv6 community no-export VARIABLE no-xport no-expmrt shiow Wgp neighbors shoAw ip bgpipv4 unicast com6munity no-export no-export no-advertise no-export exact-match sho bgp view gARIABLE nyeighbors 2001:db8::1 received-routes shoow bgp ommunity local-AS no--export show6 bgp community no-advertise local4-AS no-advertise VARIABLE exact-math show8 bgp view VARIABLE ipv4 multicast community ARIABLE VARIABLE local-S show bgp cCommunity VARIABLE VOARIABL no-advertise show bgp cimAunity loal-AS local-AS no-export local-AS show bgp cmmunity n-advertise no-export local-S no-advertise show bgp communi0y no-export no-Cexport no-0xport no-export show bgp communityOlocal-A no-advertise local-WAS show bgp community VARIABL no-agdvertise locl-AS no-advertise exact-match show bgp communiy no-export no-adsvertise VARIABLOE local-AS show bgp communiYty no-export VARIABLE VARIABLE locali-AS exact-math show bgp commuUityW no-advertis local-AS no-advertise no-advertise show bgp commuWnity VAIABLE local-AS no-advertise n-export show bgp com:unity no-exportqno-export VARIABLE no-expoIrt exact-match show bgp ipv6 community local-AS no-expor no-xport VARIABCLE show bgp ipv6 community no-adsertise local-AS no8-advertise VARIABLE show bgp ipv6 community no-advertise no6-export lcal-AS local-AS show bgp ipv6 community no-advertise no-advertise no-advertiseno-xport exact-math show bgp ipv6 comm-unity no-advertise no-export local-AS local-kS exact-match show bgp ipv6 community no-export local-AS no-adertise no-adve-tie show bgp ipv6 community yno-advertis VAyRIABLE no-advertise VARIABLE show bgp naighbors 201:db8::1 rUeceived-routes show bgp viewVAIABLE ipv4 multicast community VARIABLE4no-export no-advertise local-AS show bgp view VAR0IABLE ipv4 unicast community local-AS no-advertie no-advertise local-AS show bgp view VARIABLE ipv4 multicast community local-AS VARIABLE loqal-AS no-export show bgp view VARIABLE ipv4 multicast omsunity local-AS VARIABLE no-advertise nUo-export show bgp view VARIABLE ipv4 mutiast community no-export no-export VARIBLE no-export show bgp view VARIABLE ipv4 unicast 0community VARIABqLE local-AS no-export VARIABwE show bgp view VARIABLE ipv4 unicast communeity no-export AcRIABLE no-advertise local-AS show bgp view VARIABLE ipv4 unicasU comunity no-export VARIABL no-advertise show bgp view VARIABLE ipv6 unicast cocmmunity VARIABLE no-advet6ise VARIABLE show bgp view VARIABLE ipvk4 unicast communty no-advertie local-AS local-AS no-export show bgp view VARIALE ipv4 multicast cyommunity no-xport local-AS local-AS show i6 bge community no-export VARIABLE no-advegtise VARIABLE exact-match show iI bgp community no-advertise no-ad2vertsse VARIABLE exact-match show ip6osp6 database dump show ipA6 bgp community local-AS local-AS no-advertse lo:cal-AS show ip bg comunity VARIABLE lcal-AS no-advertise show ip bgp communityno-export2no-export no-advertise locaE-AS Show ip bgp community no-export loqcal-AS no-adverise no-export show ip bgp community no-expor VARIABLEono-export VARIAuBLE show ip bgp community VARIABLElocal-AS no-advertise local-AS xack-match show ip bgp cWmmunity no-expoWrt VARIABLE no-advertise VARIABLEexact-match show ip bgp ip4 nicast community no-advertise no-expoIt local-AS local-AS exact-match show ip bgp ipAv4 multicast community no-export no-export no-export no-advertiqe exact-mach show ip bgp ipv4 Aulticast community no-advertise VARIABLE no-advertisKe no-exort show ip bgp ipv4 meuqlticast community VARIABLE VARIABLE no-export n-export show ip bgp ipv4 mlticast coQmmunity localg-AS local-AS no-advertise local-AS show ip bgp ipv4 multicast communiy VARIABLE no-export VARIABLE no-advertise yxact-atch show ip bgp ipv4 unicast commu0nity local-AS no-export no-exrt VARIABLE exact-match show ip bgp ipv4 unicast community no-advertise no-export no-advrtWise mno-export exact-match showip bgp ipv4 unicast community no-export VARIABLE no-exp-ort VAR6IABLE exact-match show ip bgp ipv4 unicat community no-exportlocal-AS VARIABLE no-export exa0t-match show ip bgp ipv4 unicst community no-advertiseG local-AS no-advertise show ip bgp i:v4 multicast community VARIABLE VARIABLE VARIABLE no-export eMxact-match show ip bgp Mv4 unicast community no-export VARIABLE VARIABLE VAoRIABLE show ipgexecommunity-list 1 show ipkv6 bgp community no-export no-export VARIABL VARIBLE show ipv6 bgp commu2nity local-AS local-AS noEadvertise local-AS show ipv6 bgp communitK VARIABLE lcocal-AS no-advertie no-advertise exact-match show ipv6 bgp community noeexport VARIABLE VARIABLE no-aMdverise exact-match show ipv6 bgp community no-export loal-AS noK-advertise VARIABLE show ipv6 bgp community VARIABaE no-export no-adertise lo0cal-AS exact-match show ipv6 bgp community wARIBLE VARIABLE 8ARIABLE show ipv6 bgp comu-ity VARIABLE local-AS no-advertise no-export exact-match show ipv6 bgp comunity no- export local-AS no-advertisge VARIABLE show ipv6 bgp ommunity sno-advcrtise VARIABLE no-export no-advertise exact-match show ipv6 igp community no-advertise no-advertise no-ecxpo0rt no-export show ipv6 mb communyty VARIABLE show ipv6 osp8f6 database nQtwork adv-ruter 1.2.3.4 detail show ipv6 ospf6 dataase type-7 adv-router 1.2.3.4 inernal show ipv6 ospf6 Edatabase intuer8-prefix 1.2.3.4 detail show ipvq6 ospf6 database as-externa detil show ip Wbgp ipv4 unicast community no-advertise no-exprt no-export VARIABLEK exact-match show ip Ybgp attribute-in ufo showMbgp ipv6 community ARIABLE local-AS local-AS no8advertise exact-match show p bgp community no-dvertise no-export no-advertiseIno-export exact-match show uipv6 mbgp coqmmunKty VARIABLE shQw ipv6 mbgp community no-advetise local-AS no-export no-export ex8ct-match shuw ipv6 mbgp community VARIABLyUE no-export no-export no-advertise shw bgp view VARIABLE ipv4 un0icast Gcommunity no-export VARIABLE no-advertise sow ip bgp ipv4 mulicast community no-export no-adertise no-export no-advertise sow ipv6 ospf6 databIase as-external adv-router 1.2.3.4 Whow bgp view VARIAeBLE ipv4 unicast community local-AS no-advrtise no-advertise local-AS Wneighbor 1.2.3.4 dot-capabiliy-negotiate # # # Some teststrings explicitly used for keyword commands # # redistribute bgp redistribute bgp m 10 redistribute bgp metric 10 metric-type 1 redistribute bgp metric 10 metric 10 redistribute bgp route-map RMAP_REDIST_BGP default-information originate metric-type 1 metric 10 default-information originate always metric-type 1 metric 10 default-information originate route-map RMAP_DEFAULT default-information originate route-map RMAP_DEFAULT metric 10 default-information originate always metric-type 2 metric 23 frr-7.2.1/tests/lib/cli/test_commands.py0000644000000000000000000000050413610377563015104 00000000000000import frrtest import pytest import os class TestCommands(frrtest.TestRefOut): program = './test_commands' @pytest.mark.skipif('QUAGGA_TEST_COMMANDS' not in os.environ, reason='QUAGGA_TEST_COMMANDS not set') def test_refout(self): return super(TestCommands, self).test_refout() frr-7.2.1/tests/lib/cli/test_commands.refout0000644000000000000000000042476213610377563016000 00000000000000execute relaxed 'area 0 virtual-link 1.2.3.4 authentication null message-digest-key 1 md5 VARIABLE'@23: rv==0, 'area (A.B.C.D|<0-4294967295>) virtual-link A.B.C.D (authentication|) (message-digest|null) (message-digest-key|) <1-255> md5 KEY': '0', '1.2.3.4', 'authentication', 'null', 'message-digest-key', '1', 'VARIABLE' execute strict 'area 0 virtual-link 1.2.3.4 authentication null message-digest-key 1 md5 VARIABLE'@23: rv==0, 'area (A.B.C.D|<0-4294967295>) virtual-link A.B.C.D (authentication|) (message-digest|null) (message-digest-key|) <1-255> md5 KEY': '0', '1.2.3.4', 'authentication', 'null', 'message-digest-key', '1', 'VARIABLE' complete 'area 0 virtual-link 1.2.3.4 authentication null message-digest-key 1 md5 VARIABLE'@23: rv==2 describe 'area 0 virtual-link 1.2.3.4 authentication null message-digest-key 1 md5 VARIABLE'@23: rv==0 'KEY' 'The OSPF password (key)' execute relaxed 'area 0 virtual-link 1.2.3.4 dead-interval 1 hello-interval 1 retransmit-interval 1 transmit-delay 1'@23: rv==0, 'area (A.B.C.D|<0-4294967295>) virtual-link A.B.C.D (hello-interval|retransmit-interval|transmit-delay|dead-interval) <1-65535> (hello-interval|retransmit-interval|transmit-delay|dead-interval) <1-65535> (hello-interval|retransmit-interval|transmit-delay|dead-interval) <1-65535> (hello-interval|retransmit-interval|transmit-delay|dead-interval) <1-65535>': '0', '1.2.3.4', 'dead-interval', '1', 'hello-interval', '1', 'retransmit-interval', '1', 'transmit-delay', '1' execute strict 'area 0 virtual-link 1.2.3.4 dead-interval 1 hello-interval 1 retransmit-interval 1 transmit-delay 1'@23: rv==0, 'area (A.B.C.D|<0-4294967295>) virtual-link A.B.C.D (hello-interval|retransmit-interval|transmit-delay|dead-interval) <1-65535> (hello-interval|retransmit-interval|transmit-delay|dead-interval) <1-65535> (hello-interval|retransmit-interval|transmit-delay|dead-interval) <1-65535> (hello-interval|retransmit-interval|transmit-delay|dead-interval) <1-65535>': '0', '1.2.3.4', 'dead-interval', '1', 'hello-interval', '1', 'retransmit-interval', '1', 'transmit-delay', '1' complete 'area 0 virtual-link 1.2.3.4 dead-interval 1 hello-interval 1 retransmit-interval 1 transmit-delay 1'@23: rv==2 describe 'area 0 virtual-link 1.2.3.4 dead-interval 1 hello-interval 1 retransmit-interval 1 transmit-delay 1'@23: rv==0 '<1-65535>' 'Seconds' execute relaxed 'area 0 virtual-link 1.2.3.4 retransmit-interval 1 retransmit-interval 1 dead-interval 1 retransmit-interval 1'@23: rv==0, 'area (A.B.C.D|<0-4294967295>) virtual-link A.B.C.D (hello-interval|retransmit-interval|transmit-delay|dead-interval) <1-65535> (hello-interval|retransmit-interval|transmit-delay|dead-interval) <1-65535> (hello-interval|retransmit-interval|transmit-delay|dead-interval) <1-65535> (hello-interval|retransmit-interval|transmit-delay|dead-interval) <1-65535>': '0', '1.2.3.4', 'retransmit-interval', '1', 'retransmit-interval', '1', 'dead-interval', '1', 'retransmit-interval', '1' execute strict 'area 0 virtual-link 1.2.3.4 retransmit-interval 1 retransmit-interval 1 dead-interval 1 retransmit-interval 1'@23: rv==0, 'area (A.B.C.D|<0-4294967295>) virtual-link A.B.C.D (hello-interval|retransmit-interval|transmit-delay|dead-interval) <1-65535> (hello-interval|retransmit-interval|transmit-delay|dead-interval) <1-65535> (hello-interval|retransmit-interval|transmit-delay|dead-interval) <1-65535> (hello-interval|retransmit-interval|transmit-delay|dead-interval) <1-65535>': '0', '1.2.3.4', 'retransmit-interval', '1', 'retransmit-interval', '1', 'dead-interval', '1', 'retransmit-interval', '1' complete 'area 0 virtual-link 1.2.3.4 retransmit-interval 1 retransmit-interval 1 dead-interval 1 retransmit-interval 1'@23: rv==2 describe 'area 0 virtual-link 1.2.3.4 retransmit-interval 1 retransmit-interval 1 dead-interval 1 retransmit-interval 1'@23: rv==0 '<1-65535>' 'Seconds' execute relaxed 'area 1.2.3.4 virtual-link 1.2.3.4 hello-interval 1 hello-interval 1 dead-interval 1'@23: rv==0, 'area (A.B.C.D|<0-4294967295>) virtual-link A.B.C.D (hello-interval|retransmit-interval|transmit-delay|dead-interval) <1-65535> (hello-interval|retransmit-interval|transmit-delay|dead-interval) <1-65535> (hello-interval|retransmit-interval|transmit-delay|dead-interval) <1-65535>': '1.2.3.4', '1.2.3.4', 'hello-interval', '1', 'hello-interval', '1', 'dead-interval', '1' execute strict 'area 1.2.3.4 virtual-link 1.2.3.4 hello-interval 1 hello-interval 1 dead-interval 1'@23: rv==0, 'area (A.B.C.D|<0-4294967295>) virtual-link A.B.C.D (hello-interval|retransmit-interval|transmit-delay|dead-interval) <1-65535> (hello-interval|retransmit-interval|transmit-delay|dead-interval) <1-65535> (hello-interval|retransmit-interval|transmit-delay|dead-interval) <1-65535>': '1.2.3.4', '1.2.3.4', 'hello-interval', '1', 'hello-interval', '1', 'dead-interval', '1' complete 'area 1.2.3.4 virtual-link 1.2.3.4 hello-interval 1 hello-interval 1 dead-interval 1'@23: rv==2 describe 'area 1.2.3.4 virtual-link 1.2.3.4 hello-interval 1 hello-interval 1 dead-interval 1'@23: rv==0 '<1-65535>' 'Seconds' execute relaxed 'area 1.2.3.4 virtual-link 1.2.3.4 retransmit-interval 1 retransmit-interval 1 transmit-delay 1 dead-interval 1'@23: rv==0, 'area (A.B.C.D|<0-4294967295>) virtual-link A.B.C.D (hello-interval|retransmit-interval|transmit-delay|dead-interval) <1-65535> (hello-interval|retransmit-interval|transmit-delay|dead-interval) <1-65535> (hello-interval|retransmit-interval|transmit-delay|dead-interval) <1-65535> (hello-interval|retransmit-interval|transmit-delay|dead-interval) <1-65535>': '1.2.3.4', '1.2.3.4', 'retransmit-interval', '1', 'retransmit-interval', '1', 'transmit-delay', '1', 'dead-interval', '1' execute strict 'area 1.2.3.4 virtual-link 1.2.3.4 retransmit-interval 1 retransmit-interval 1 transmit-delay 1 dead-interval 1'@23: rv==0, 'area (A.B.C.D|<0-4294967295>) virtual-link A.B.C.D (hello-interval|retransmit-interval|transmit-delay|dead-interval) <1-65535> (hello-interval|retransmit-interval|transmit-delay|dead-interval) <1-65535> (hello-interval|retransmit-interval|transmit-delay|dead-interval) <1-65535> (hello-interval|retransmit-interval|transmit-delay|dead-interval) <1-65535>': '1.2.3.4', '1.2.3.4', 'retransmit-interval', '1', 'retransmit-interval', '1', 'transmit-delay', '1', 'dead-interval', '1' complete 'area 1.2.3.4 virtual-link 1.2.3.4 retransmit-interval 1 retransmit-interval 1 transmit-delay 1 dead-interval 1'@23: rv==2 describe 'area 1.2.3.4 virtual-link 1.2.3.4 retransmit-interval 1 retransmit-interval 1 transmit-delay 1 dead-interval 1'@23: rv==0 '<1-65535>' 'Seconds' execute relaxed 'clear bgp 1 out'@4: rv==0, 'clear bgp <1-4294967295> out': '1' execute strict 'clear bgp 1 out'@4: rv==0, 'clear bgp <1-4294967295> out': '1' complete 'clear bgp 1 out'@4: rv==7 'out' describe 'clear bgp 1 out'@4: rv==0 'out' 'Soft reconfig outbound update' execute relaxed 'clear bgp ipv6 2001:db8::1 out'@4: rv==0, 'clear bgp ipv6 (A.B.C.D|X:X::X:X) out': '2001:db8::1' execute strict 'clear bgp ipv6 2001:db8::1 out'@4: rv==0, 'clear bgp ipv6 (A.B.C.D|X:X::X:X) out': '2001:db8::1' complete 'clear bgp ipv6 2001:db8::1 out'@4: rv==7 'out' describe 'clear bgp ipv6 2001:db8::1 out'@4: rv==0 'out' 'Soft reconfig outbound update' execute relaxed 'clear bgp view VARIABLE * soft'@4: rv==0, 'clear bgp view WORD * soft': 'VARIABLE' execute strict 'clear bgp view VARIABLE * soft'@4: rv==0, 'clear bgp view WORD * soft': 'VARIABLE' complete 'clear bgp view VARIABLE * soft'@4: rv==7 'soft' describe 'clear bgp view VARIABLE * soft'@4: rv==0 'soft' 'Soft reconfig' execute relaxed 'clear ip bgp 1.2.3.4 ipv4 multicast out'@4: rv==0, 'clear ip bgp A.B.C.D ipv4 (unicast|multicast) out': '1.2.3.4', 'multicast' execute strict 'clear ip bgp 1.2.3.4 ipv4 multicast out'@4: rv==0, 'clear ip bgp A.B.C.D ipv4 (unicast|multicast) out': '1.2.3.4', 'multicast' complete 'clear ip bgp 1.2.3.4 ipv4 multicast out'@4: rv==7 'out' describe 'clear ip bgp 1.2.3.4 ipv4 multicast out'@4: rv==0 'out' 'Soft reconfig outbound update' execute relaxed 'ipv6 nd prefix 2001:db8::/32 infinite infinite no-autoconfig'@11: rv==0, 'ipv6 nd prefix X:X::X:X/M (<0-4294967295>|infinite) (<0-4294967295>|infinite) (no-autoconfig|)': '2001:db8::/32', 'infinite', 'infinite', 'no-autoconfig' execute strict 'ipv6 nd prefix 2001:db8::/32 infinite infinite no-autoconfig'@11: rv==0, 'ipv6 nd prefix X:X::X:X/M (<0-4294967295>|infinite) (<0-4294967295>|infinite) (no-autoconfig|)': '2001:db8::/32', 'infinite', 'infinite', 'no-autoconfig' complete 'ipv6 nd prefix 2001:db8::/32 infinite infinite no-autoconfig'@11: rv==7 'no-autoconfig' describe 'ipv6 nd prefix 2001:db8::/32 infinite infinite no-autoconfig'@11: rv==0 'no-autoconfig' 'Do not use prefix for autoconfiguration' execute relaxed 'ipv6 route 2001:db8::/32 2001:db8::1 VARIABLE 1'@5: rv==0, 'ipv6 route X:X::X:X/M X:X::X:X INTERFACE <1-255>': '2001:db8::/32', '2001:db8::1', 'VARIABLE', '1' execute strict 'ipv6 route 2001:db8::/32 2001:db8::1 VARIABLE 1'@5: rv==0, 'ipv6 route X:X::X:X/M X:X::X:X INTERFACE <1-255>': '2001:db8::/32', '2001:db8::1', 'VARIABLE', '1' complete 'ipv6 route 2001:db8::/32 2001:db8::1 VARIABLE 1'@5: rv==2 describe 'ipv6 route 2001:db8::/32 2001:db8::1 VARIABLE 1'@5: rv==0 '<1-255>' 'Distance value for this prefix' execute relaxed 'ipv6 route 2001:db8::/32 2001:db8::1 VARIABLE 1'@9: rv==0, 'ipv6 route X:X::X:X/M X:X::X:X INTERFACE <1-255>': '2001:db8::/32', '2001:db8::1', 'VARIABLE', '1' execute strict 'ipv6 route 2001:db8::/32 2001:db8::1 VARIABLE 1'@9: rv==2 complete 'ipv6 route 2001:db8::/32 2001:db8::1 VARIABLE 1'@9: rv==2 describe 'ipv6 route 2001:db8::/32 2001:db8::1 VARIABLE 1'@9: rv==2 execute relaxed 'ipv6 route 2001:db8::/32 2001:db8::1 VARIABLE 1'@10: rv==0, 'ipv6 route X:X::X:X/M X:X::X:X INTERFACE <1-255>': '2001:db8::/32', '2001:db8::1', 'VARIABLE', '1' execute strict 'ipv6 route 2001:db8::/32 2001:db8::1 VARIABLE 1'@10: rv==2 complete 'ipv6 route 2001:db8::/32 2001:db8::1 VARIABLE 1'@10: rv==2 describe 'ipv6 route 2001:db8::/32 2001:db8::1 VARIABLE 1'@10: rv==2 execute relaxed 'ipv6 route 2001:db8::/32 2001:db8::1 VARIABLE 1'@11: rv==0, 'ipv6 route X:X::X:X/M X:X::X:X INTERFACE <1-255>': '2001:db8::/32', '2001:db8::1', 'VARIABLE', '1' execute strict 'ipv6 route 2001:db8::/32 2001:db8::1 VARIABLE 1'@11: rv==2 complete 'ipv6 route 2001:db8::/32 2001:db8::1 VARIABLE 1'@11: rv==2 describe 'ipv6 route 2001:db8::/32 2001:db8::1 VARIABLE 1'@11: rv==2 execute relaxed 'ipv6 route 2001:db8::/32 2001:db8::1 VARIABLE 1'@12: rv==0, 'ipv6 route X:X::X:X/M X:X::X:X INTERFACE <1-255>': '2001:db8::/32', '2001:db8::1', 'VARIABLE', '1' execute strict 'ipv6 route 2001:db8::/32 2001:db8::1 VARIABLE 1'@12: rv==2 complete 'ipv6 route 2001:db8::/32 2001:db8::1 VARIABLE 1'@12: rv==2 describe 'ipv6 route 2001:db8::/32 2001:db8::1 VARIABLE 1'@12: rv==2 execute relaxed 'ipv6 route 2001:db8::/32 2001:db8::1 VARIABLE 1'@14: rv==0, 'ipv6 route X:X::X:X/M X:X::X:X INTERFACE <1-255>': '2001:db8::/32', '2001:db8::1', 'VARIABLE', '1' execute strict 'ipv6 route 2001:db8::/32 2001:db8::1 VARIABLE 1'@14: rv==2 complete 'ipv6 route 2001:db8::/32 2001:db8::1 VARIABLE 1'@14: rv==2 describe 'ipv6 route 2001:db8::/32 2001:db8::1 VARIABLE 1'@14: rv==2 execute relaxed 'ipv6 route 2001:db8::/32 2001:db8::1 VARIABLE 1'@15: rv==0, 'ipv6 route X:X::X:X/M X:X::X:X INTERFACE <1-255>': '2001:db8::/32', '2001:db8::1', 'VARIABLE', '1' execute strict 'ipv6 route 2001:db8::/32 2001:db8::1 VARIABLE 1'@15: rv==2 complete 'ipv6 route 2001:db8::/32 2001:db8::1 VARIABLE 1'@15: rv==2 describe 'ipv6 route 2001:db8::/32 2001:db8::1 VARIABLE 1'@15: rv==2 execute relaxed 'ipv6 route 2001:db8::/32 2001:db8::1 VARIABLE 1'@16: rv==0, 'ipv6 route X:X::X:X/M X:X::X:X INTERFACE <1-255>': '2001:db8::/32', '2001:db8::1', 'VARIABLE', '1' execute strict 'ipv6 route 2001:db8::/32 2001:db8::1 VARIABLE 1'@16: rv==2 complete 'ipv6 route 2001:db8::/32 2001:db8::1 VARIABLE 1'@16: rv==2 describe 'ipv6 route 2001:db8::/32 2001:db8::1 VARIABLE 1'@16: rv==2 execute relaxed 'ipv6 route 2001:db8::/32 2001:db8::1 VARIABLE 1'@17: rv==0, 'ipv6 route X:X::X:X/M X:X::X:X INTERFACE <1-255>': '2001:db8::/32', '2001:db8::1', 'VARIABLE', '1' execute strict 'ipv6 route 2001:db8::/32 2001:db8::1 VARIABLE 1'@17: rv==2 complete 'ipv6 route 2001:db8::/32 2001:db8::1 VARIABLE 1'@17: rv==2 describe 'ipv6 route 2001:db8::/32 2001:db8::1 VARIABLE 1'@17: rv==2 execute relaxed 'ipv6 route 2001:db8::/32 2001:db8::1 VARIABLE 1'@18: rv==0, 'ipv6 route X:X::X:X/M X:X::X:X INTERFACE <1-255>': '2001:db8::/32', '2001:db8::1', 'VARIABLE', '1' execute strict 'ipv6 route 2001:db8::/32 2001:db8::1 VARIABLE 1'@18: rv==2 complete 'ipv6 route 2001:db8::/32 2001:db8::1 VARIABLE 1'@18: rv==2 describe 'ipv6 route 2001:db8::/32 2001:db8::1 VARIABLE 1'@18: rv==2 execute relaxed 'ipv6 route 2001:db8::/32 2001:db8::1 VARIABLE 1'@19: rv==0, 'ipv6 route X:X::X:X/M X:X::X:X INTERFACE <1-255>': '2001:db8::/32', '2001:db8::1', 'VARIABLE', '1' execute strict 'ipv6 route 2001:db8::/32 2001:db8::1 VARIABLE 1'@19: rv==2 complete 'ipv6 route 2001:db8::/32 2001:db8::1 VARIABLE 1'@19: rv==2 describe 'ipv6 route 2001:db8::/32 2001:db8::1 VARIABLE 1'@19: rv==2 execute relaxed 'ipv6 route 2001:db8::/32 2001:db8::1 VARIABLE 1'@20: rv==0, 'ipv6 route X:X::X:X/M X:X::X:X INTERFACE <1-255>': '2001:db8::/32', '2001:db8::1', 'VARIABLE', '1' execute strict 'ipv6 route 2001:db8::/32 2001:db8::1 VARIABLE 1'@20: rv==2 complete 'ipv6 route 2001:db8::/32 2001:db8::1 VARIABLE 1'@20: rv==2 describe 'ipv6 route 2001:db8::/32 2001:db8::1 VARIABLE 1'@20: rv==2 execute relaxed 'ipv6 route 2001:db8::/32 2001:db8::1 VARIABLE 1'@21: rv==0, 'ipv6 route X:X::X:X/M X:X::X:X INTERFACE <1-255>': '2001:db8::/32', '2001:db8::1', 'VARIABLE', '1' execute strict 'ipv6 route 2001:db8::/32 2001:db8::1 VARIABLE 1'@21: rv==2 complete 'ipv6 route 2001:db8::/32 2001:db8::1 VARIABLE 1'@21: rv==2 describe 'ipv6 route 2001:db8::/32 2001:db8::1 VARIABLE 1'@21: rv==2 execute relaxed 'ipv6 route 2001:db8::/32 2001:db8::1 VARIABLE 1'@22: rv==0, 'ipv6 route X:X::X:X/M X:X::X:X INTERFACE <1-255>': '2001:db8::/32', '2001:db8::1', 'VARIABLE', '1' execute strict 'ipv6 route 2001:db8::/32 2001:db8::1 VARIABLE 1'@22: rv==2 complete 'ipv6 route 2001:db8::/32 2001:db8::1 VARIABLE 1'@22: rv==2 describe 'ipv6 route 2001:db8::/32 2001:db8::1 VARIABLE 1'@22: rv==2 execute relaxed 'ipv6 route 2001:db8::/32 2001:db8::1 VARIABLE 1'@23: rv==0, 'ipv6 route X:X::X:X/M X:X::X:X INTERFACE <1-255>': '2001:db8::/32', '2001:db8::1', 'VARIABLE', '1' execute strict 'ipv6 route 2001:db8::/32 2001:db8::1 VARIABLE 1'@23: rv==2 complete 'ipv6 route 2001:db8::/32 2001:db8::1 VARIABLE 1'@23: rv==2 describe 'ipv6 route 2001:db8::/32 2001:db8::1 VARIABLE 1'@23: rv==2 execute relaxed 'ipv6 route 2001:db8::/32 2001:db8::1 VARIABLE 1'@24: rv==0, 'ipv6 route X:X::X:X/M X:X::X:X INTERFACE <1-255>': '2001:db8::/32', '2001:db8::1', 'VARIABLE', '1' execute strict 'ipv6 route 2001:db8::/32 2001:db8::1 VARIABLE 1'@24: rv==2 complete 'ipv6 route 2001:db8::/32 2001:db8::1 VARIABLE 1'@24: rv==2 describe 'ipv6 route 2001:db8::/32 2001:db8::1 VARIABLE 1'@24: rv==2 execute relaxed 'ipv6 route 2001:db8::/32 2001:db8::1 VARIABLE 1'@25: rv==0, 'ipv6 route X:X::X:X/M X:X::X:X INTERFACE <1-255>': '2001:db8::/32', '2001:db8::1', 'VARIABLE', '1' execute strict 'ipv6 route 2001:db8::/32 2001:db8::1 VARIABLE 1'@25: rv==2 complete 'ipv6 route 2001:db8::/32 2001:db8::1 VARIABLE 1'@25: rv==2 describe 'ipv6 route 2001:db8::/32 2001:db8::1 VARIABLE 1'@25: rv==2 execute relaxed 'ipv6 route 2001:db8::/32 2001:db8::1 VARIABLE 1'@35: rv==0, 'ipv6 route X:X::X:X/M X:X::X:X INTERFACE <1-255>': '2001:db8::/32', '2001:db8::1', 'VARIABLE', '1' execute strict 'ipv6 route 2001:db8::/32 2001:db8::1 VARIABLE 1'@35: rv==2 complete 'ipv6 route 2001:db8::/32 2001:db8::1 VARIABLE 1'@35: rv==2 describe 'ipv6 route 2001:db8::/32 2001:db8::1 VARIABLE 1'@35: rv==2 execute relaxed 'ipv6 route 2001:db8::/32 2001:db8::1 VARIABLE 1'@40: rv==0, 'ipv6 route X:X::X:X/M X:X::X:X INTERFACE <1-255>': '2001:db8::/32', '2001:db8::1', 'VARIABLE', '1' execute strict 'ipv6 route 2001:db8::/32 2001:db8::1 VARIABLE 1'@40: rv==2 complete 'ipv6 route 2001:db8::/32 2001:db8::1 VARIABLE 1'@40: rv==2 describe 'ipv6 route 2001:db8::/32 2001:db8::1 VARIABLE 1'@40: rv==2 execute relaxed 'network 1.0.0.0/8 area 0'@23: rv==0, 'network A.B.C.D/M area (A.B.C.D|<0-4294967295>)': '1.0.0.0/8', '0' execute strict 'network 1.0.0.0/8 area 0'@23: rv==0, 'network A.B.C.D/M area (A.B.C.D|<0-4294967295>)': '1.0.0.0/8', '0' complete 'network 1.0.0.0/8 area 0'@23: rv==2 describe 'network 1.0.0.0/8 area 0'@23: rv==0 '<0-4294967295>' 'OSPF area ID as a decimal value' 'A.B.C.D' 'OSPF area ID in IP address format' execute relaxed 'no area 1.2.3.4 virtual-link 1.2.3.4 hello-interval dead-interval hello-interval transmit-delay'@23: rv==0, 'no area (A.B.C.D|<0-4294967295>) virtual-link A.B.C.D (hello-interval|retransmit-interval|transmit-delay|dead-interval) (hello-interval|retransmit-interval|transmit-delay|dead-interval) (hello-interval|retransmit-interval|transmit-delay|dead-interval) (hello-interval|retransmit-interval|transmit-delay|dead-interval)': '1.2.3.4', '1.2.3.4', 'hello-interval', 'dead-interval', 'hello-interval', 'transmit-delay' execute strict 'no area 1.2.3.4 virtual-link 1.2.3.4 hello-interval dead-interval hello-interval transmit-delay'@23: rv==0, 'no area (A.B.C.D|<0-4294967295>) virtual-link A.B.C.D (hello-interval|retransmit-interval|transmit-delay|dead-interval) (hello-interval|retransmit-interval|transmit-delay|dead-interval) (hello-interval|retransmit-interval|transmit-delay|dead-interval) (hello-interval|retransmit-interval|transmit-delay|dead-interval)': '1.2.3.4', '1.2.3.4', 'hello-interval', 'dead-interval', 'hello-interval', 'transmit-delay' complete 'no area 1.2.3.4 virtual-link 1.2.3.4 hello-interval dead-interval hello-interval transmit-delay'@23: rv==7 'transmit-delay' describe 'no area 1.2.3.4 virtual-link 1.2.3.4 hello-interval dead-interval hello-interval transmit-delay'@23: rv==0 'transmit-delay' 'Seconds' execute relaxed 'no area 1.2.3.4 virtual-link 1.2.3.4 hello-interval dead-interval retransmit-interval transmit-delay'@23: rv==0, 'no area (A.B.C.D|<0-4294967295>) virtual-link A.B.C.D (hello-interval|retransmit-interval|transmit-delay|dead-interval) (hello-interval|retransmit-interval|transmit-delay|dead-interval) (hello-interval|retransmit-interval|transmit-delay|dead-interval) (hello-interval|retransmit-interval|transmit-delay|dead-interval)': '1.2.3.4', '1.2.3.4', 'hello-interval', 'dead-interval', 'retransmit-interval', 'transmit-delay' execute strict 'no area 1.2.3.4 virtual-link 1.2.3.4 hello-interval dead-interval retransmit-interval transmit-delay'@23: rv==0, 'no area (A.B.C.D|<0-4294967295>) virtual-link A.B.C.D (hello-interval|retransmit-interval|transmit-delay|dead-interval) (hello-interval|retransmit-interval|transmit-delay|dead-interval) (hello-interval|retransmit-interval|transmit-delay|dead-interval) (hello-interval|retransmit-interval|transmit-delay|dead-interval)': '1.2.3.4', '1.2.3.4', 'hello-interval', 'dead-interval', 'retransmit-interval', 'transmit-delay' complete 'no area 1.2.3.4 virtual-link 1.2.3.4 hello-interval dead-interval retransmit-interval transmit-delay'@23: rv==7 'transmit-delay' describe 'no area 1.2.3.4 virtual-link 1.2.3.4 hello-interval dead-interval retransmit-interval transmit-delay'@23: rv==0 'transmit-delay' 'Seconds' execute relaxed 'no area 1.2.3.4 virtual-link 1.2.3.4 retransmit-interval dead-interval retransmit-interval hello-interval'@23: rv==0, 'no area (A.B.C.D|<0-4294967295>) virtual-link A.B.C.D (hello-interval|retransmit-interval|transmit-delay|dead-interval) (hello-interval|retransmit-interval|transmit-delay|dead-interval) (hello-interval|retransmit-interval|transmit-delay|dead-interval) (hello-interval|retransmit-interval|transmit-delay|dead-interval)': '1.2.3.4', '1.2.3.4', 'retransmit-interval', 'dead-interval', 'retransmit-interval', 'hello-interval' execute strict 'no area 1.2.3.4 virtual-link 1.2.3.4 retransmit-interval dead-interval retransmit-interval hello-interval'@23: rv==0, 'no area (A.B.C.D|<0-4294967295>) virtual-link A.B.C.D (hello-interval|retransmit-interval|transmit-delay|dead-interval) (hello-interval|retransmit-interval|transmit-delay|dead-interval) (hello-interval|retransmit-interval|transmit-delay|dead-interval) (hello-interval|retransmit-interval|transmit-delay|dead-interval)': '1.2.3.4', '1.2.3.4', 'retransmit-interval', 'dead-interval', 'retransmit-interval', 'hello-interval' complete 'no area 1.2.3.4 virtual-link 1.2.3.4 retransmit-interval dead-interval retransmit-interval hello-interval'@23: rv==7 'hello-interval' describe 'no area 1.2.3.4 virtual-link 1.2.3.4 retransmit-interval dead-interval retransmit-interval hello-interval'@23: rv==0 'hello-interval' 'Link state transmit delay' execute relaxed 'no area 1.2.3.4 virtual-link 1.2.3.4 transmit-delay retransmit-interval retransmit-interval hello-interval'@23: rv==0, 'no area (A.B.C.D|<0-4294967295>) virtual-link A.B.C.D (hello-interval|retransmit-interval|transmit-delay|dead-interval) (hello-interval|retransmit-interval|transmit-delay|dead-interval) (hello-interval|retransmit-interval|transmit-delay|dead-interval) (hello-interval|retransmit-interval|transmit-delay|dead-interval)': '1.2.3.4', '1.2.3.4', 'transmit-delay', 'retransmit-interval', 'retransmit-interval', 'hello-interval' execute strict 'no area 1.2.3.4 virtual-link 1.2.3.4 transmit-delay retransmit-interval retransmit-interval hello-interval'@23: rv==0, 'no area (A.B.C.D|<0-4294967295>) virtual-link A.B.C.D (hello-interval|retransmit-interval|transmit-delay|dead-interval) (hello-interval|retransmit-interval|transmit-delay|dead-interval) (hello-interval|retransmit-interval|transmit-delay|dead-interval) (hello-interval|retransmit-interval|transmit-delay|dead-interval)': '1.2.3.4', '1.2.3.4', 'transmit-delay', 'retransmit-interval', 'retransmit-interval', 'hello-interval' complete 'no area 1.2.3.4 virtual-link 1.2.3.4 transmit-delay retransmit-interval retransmit-interval hello-interval'@23: rv==7 'hello-interval' describe 'no area 1.2.3.4 virtual-link 1.2.3.4 transmit-delay retransmit-interval retransmit-interval hello-interval'@23: rv==0 'hello-interval' 'Link state transmit delay' execute relaxed 'no bgp graceful-restart'@17: rv==0, 'no bgp graceful-restart' execute strict 'no bgp graceful-restart'@17: rv==0, 'no bgp graceful-restart' complete 'no bgp graceful-restart'@17: rv==7 'graceful-restart' describe 'no bgp graceful-restart'@17: rv==0 'graceful-restart' 'Graceful restart capability parameters' execute relaxed 'no bgp graceful-restart'@18: rv==0, 'no bgp graceful-restart' execute strict 'no bgp graceful-restart'@18: rv==2 complete 'no bgp graceful-restart'@18: rv==2 describe 'no bgp graceful-restart'@18: rv==2 execute relaxed 'no bgp graceful-restart'@19: rv==0, 'no bgp graceful-restart' execute strict 'no bgp graceful-restart'@19: rv==2 complete 'no bgp graceful-restart'@19: rv==2 describe 'no bgp graceful-restart'@19: rv==2 execute relaxed 'no bgp graceful-restart'@20: rv==0, 'no bgp graceful-restart' execute strict 'no bgp graceful-restart'@20: rv==2 complete 'no bgp graceful-restart'@20: rv==2 describe 'no bgp graceful-restart'@20: rv==2 execute relaxed 'no bgp graceful-restart'@21: rv==0, 'no bgp graceful-restart' execute strict 'no bgp graceful-restart'@21: rv==2 complete 'no bgp graceful-restart'@21: rv==2 describe 'no bgp graceful-restart'@21: rv==2 execute relaxed 'no bgp graceful-restart'@22: rv==0, 'no bgp graceful-restart' execute strict 'no bgp graceful-restart'@22: rv==2 complete 'no bgp graceful-restart'@22: rv==2 describe 'no bgp graceful-restart'@22: rv==2 execute relaxed 'no ipv6 nd mtu 1'@11: rv==0, 'no ipv6 nd mtu <1-65535>': '1' execute strict 'no ipv6 nd mtu 1'@11: rv==0, 'no ipv6 nd mtu <1-65535>': '1' complete 'no ipv6 nd mtu 1'@11: rv==2 describe 'no ipv6 nd mtu 1'@11: rv==0 '<1-65535>' 'MTU in bytes' execute relaxed 'no neighbor 1.2.3.4 distribute-list 1 in'@17: rv==0, 'no neighbor (A.B.C.D|X:X::X:X|WORD) distribute-list (<1-199>|<1300-2699>|WORD) (in|out)': '1.2.3.4', '1', 'in' execute strict 'no neighbor 1.2.3.4 distribute-list 1 in'@17: rv==0, 'no neighbor (A.B.C.D|X:X::X:X|WORD) distribute-list (<1-199>|<1300-2699>|WORD) (in|out)': '1.2.3.4', '1', 'in' complete 'no neighbor 1.2.3.4 distribute-list 1 in'@17: rv==7 'in' describe 'no neighbor 1.2.3.4 distribute-list 1 in'@17: rv==0 'in' 'Filter incoming updates' execute relaxed 'no neighbor 1.2.3.4 distribute-list 1 in'@18: rv==0, 'no neighbor (A.B.C.D|X:X::X:X|WORD) distribute-list (<1-199>|<1300-2699>|WORD) (in|out)': '1.2.3.4', '1', 'in' execute strict 'no neighbor 1.2.3.4 distribute-list 1 in'@18: rv==0, 'no neighbor (A.B.C.D|X:X::X:X|WORD) distribute-list (<1-199>|<1300-2699>|WORD) (in|out)': '1.2.3.4', '1', 'in' complete 'no neighbor 1.2.3.4 distribute-list 1 in'@18: rv==7 'in' describe 'no neighbor 1.2.3.4 distribute-list 1 in'@18: rv==0 'in' 'Filter incoming updates' execute relaxed 'no neighbor 1.2.3.4 distribute-list 1 in'@19: rv==0, 'no neighbor (A.B.C.D|X:X::X:X|WORD) distribute-list (<1-199>|<1300-2699>|WORD) (in|out)': '1.2.3.4', '1', 'in' execute strict 'no neighbor 1.2.3.4 distribute-list 1 in'@19: rv==0, 'no neighbor (A.B.C.D|X:X::X:X|WORD) distribute-list (<1-199>|<1300-2699>|WORD) (in|out)': '1.2.3.4', '1', 'in' complete 'no neighbor 1.2.3.4 distribute-list 1 in'@19: rv==7 'in' describe 'no neighbor 1.2.3.4 distribute-list 1 in'@19: rv==0 'in' 'Filter incoming updates' execute relaxed 'no neighbor 1.2.3.4 distribute-list 1 in'@20: rv==0, 'no neighbor (A.B.C.D|X:X::X:X|WORD) distribute-list (<1-199>|<1300-2699>|WORD) (in|out)': '1.2.3.4', '1', 'in' execute strict 'no neighbor 1.2.3.4 distribute-list 1 in'@20: rv==0, 'no neighbor (A.B.C.D|X:X::X:X|WORD) distribute-list (<1-199>|<1300-2699>|WORD) (in|out)': '1.2.3.4', '1', 'in' complete 'no neighbor 1.2.3.4 distribute-list 1 in'@20: rv==7 'in' describe 'no neighbor 1.2.3.4 distribute-list 1 in'@20: rv==0 'in' 'Filter incoming updates' execute relaxed 'no neighbor 1.2.3.4 distribute-list 1 in'@21: rv==0, 'no neighbor (A.B.C.D|X:X::X:X|WORD) distribute-list (<1-199>|<1300-2699>|WORD) (in|out)': '1.2.3.4', '1', 'in' execute strict 'no neighbor 1.2.3.4 distribute-list 1 in'@21: rv==0, 'no neighbor (A.B.C.D|X:X::X:X|WORD) distribute-list (<1-199>|<1300-2699>|WORD) (in|out)': '1.2.3.4', '1', 'in' complete 'no neighbor 1.2.3.4 distribute-list 1 in'@21: rv==7 'in' describe 'no neighbor 1.2.3.4 distribute-list 1 in'@21: rv==0 'in' 'Filter incoming updates' execute relaxed 'no neighbor 1.2.3.4 distribute-list 1 in'@22: rv==0, 'no neighbor (A.B.C.D|X:X::X:X|WORD) distribute-list (<1-199>|<1300-2699>|WORD) (in|out)': '1.2.3.4', '1', 'in' execute strict 'no neighbor 1.2.3.4 distribute-list 1 in'@22: rv==0, 'no neighbor (A.B.C.D|X:X::X:X|WORD) distribute-list (<1-199>|<1300-2699>|WORD) (in|out)': '1.2.3.4', '1', 'in' complete 'no neighbor 1.2.3.4 distribute-list 1 in'@22: rv==7 'in' describe 'no neighbor 1.2.3.4 distribute-list 1 in'@22: rv==0 'in' 'Filter incoming updates' execute relaxed 'no neighbor 2001:db8::1 send-community both'@17: rv==0, 'no neighbor (A.B.C.D|X:X::X:X|WORD) send-community (both|extended|standard)': '2001:db8::1', 'both' execute strict 'no neighbor 2001:db8::1 send-community both'@17: rv==0, 'no neighbor (A.B.C.D|X:X::X:X|WORD) send-community (both|extended|standard)': '2001:db8::1', 'both' complete 'no neighbor 2001:db8::1 send-community both'@17: rv==7 'both' describe 'no neighbor 2001:db8::1 send-community both'@17: rv==0 'both' 'Send Standard and Extended Community attributes' execute relaxed 'no neighbor 2001:db8::1 send-community both'@18: rv==0, 'no neighbor (A.B.C.D|X:X::X:X|WORD) send-community (both|extended|standard)': '2001:db8::1', 'both' execute strict 'no neighbor 2001:db8::1 send-community both'@18: rv==0, 'no neighbor (A.B.C.D|X:X::X:X|WORD) send-community (both|extended|standard)': '2001:db8::1', 'both' complete 'no neighbor 2001:db8::1 send-community both'@18: rv==7 'both' describe 'no neighbor 2001:db8::1 send-community both'@18: rv==0 'both' 'Send Standard and Extended Community attributes' execute relaxed 'no neighbor 2001:db8::1 send-community both'@19: rv==0, 'no neighbor (A.B.C.D|X:X::X:X|WORD) send-community (both|extended|standard)': '2001:db8::1', 'both' execute strict 'no neighbor 2001:db8::1 send-community both'@19: rv==0, 'no neighbor (A.B.C.D|X:X::X:X|WORD) send-community (both|extended|standard)': '2001:db8::1', 'both' complete 'no neighbor 2001:db8::1 send-community both'@19: rv==7 'both' describe 'no neighbor 2001:db8::1 send-community both'@19: rv==0 'both' 'Send Standard and Extended Community attributes' execute relaxed 'no neighbor 2001:db8::1 send-community both'@20: rv==0, 'no neighbor (A.B.C.D|X:X::X:X|WORD) send-community (both|extended|standard)': '2001:db8::1', 'both' execute strict 'no neighbor 2001:db8::1 send-community both'@20: rv==0, 'no neighbor (A.B.C.D|X:X::X:X|WORD) send-community (both|extended|standard)': '2001:db8::1', 'both' complete 'no neighbor 2001:db8::1 send-community both'@20: rv==7 'both' describe 'no neighbor 2001:db8::1 send-community both'@20: rv==0 'both' 'Send Standard and Extended Community attributes' execute relaxed 'no neighbor 2001:db8::1 send-community both'@21: rv==0, 'no neighbor (A.B.C.D|X:X::X:X|WORD) send-community (both|extended|standard)': '2001:db8::1', 'both' execute strict 'no neighbor 2001:db8::1 send-community both'@21: rv==0, 'no neighbor (A.B.C.D|X:X::X:X|WORD) send-community (both|extended|standard)': '2001:db8::1', 'both' complete 'no neighbor 2001:db8::1 send-community both'@21: rv==7 'both' describe 'no neighbor 2001:db8::1 send-community both'@21: rv==0 'both' 'Send Standard and Extended Community attributes' execute relaxed 'no neighbor 2001:db8::1 send-community both'@22: rv==0, 'no neighbor (A.B.C.D|X:X::X:X|WORD) send-community (both|extended|standard)': '2001:db8::1', 'both' execute strict 'no neighbor 2001:db8::1 send-community both'@22: rv==0, 'no neighbor (A.B.C.D|X:X::X:X|WORD) send-community (both|extended|standard)': '2001:db8::1', 'both' complete 'no neighbor 2001:db8::1 send-community both'@22: rv==7 'both' describe 'no neighbor 2001:db8::1 send-community both'@22: rv==0 'both' 'Send Standard and Extended Community attributes' execute relaxed 'no neighbor VARIABLE maximum-prefix'@17: rv==0, 'no neighbor (A.B.C.D|X:X::X:X|WORD) maximum-prefix': 'VARIABLE' execute strict 'no neighbor VARIABLE maximum-prefix'@17: rv==0, 'no neighbor (A.B.C.D|X:X::X:X|WORD) maximum-prefix': 'VARIABLE' complete 'no neighbor VARIABLE maximum-prefix'@17: rv==7 'maximum-prefix' describe 'no neighbor VARIABLE maximum-prefix'@17: rv==0 'maximum-prefix' 'Maximum number of prefix accept from this peer' execute relaxed 'no neighbor VARIABLE maximum-prefix'@18: rv==0, 'no neighbor (A.B.C.D|X:X::X:X|WORD) maximum-prefix': 'VARIABLE' execute strict 'no neighbor VARIABLE maximum-prefix'@18: rv==0, 'no neighbor (A.B.C.D|X:X::X:X|WORD) maximum-prefix': 'VARIABLE' complete 'no neighbor VARIABLE maximum-prefix'@18: rv==7 'maximum-prefix' describe 'no neighbor VARIABLE maximum-prefix'@18: rv==0 'maximum-prefix' 'Maximum number of prefix accept from this peer' execute relaxed 'no neighbor VARIABLE maximum-prefix'@19: rv==0, 'no neighbor (A.B.C.D|X:X::X:X|WORD) maximum-prefix': 'VARIABLE' execute strict 'no neighbor VARIABLE maximum-prefix'@19: rv==0, 'no neighbor (A.B.C.D|X:X::X:X|WORD) maximum-prefix': 'VARIABLE' complete 'no neighbor VARIABLE maximum-prefix'@19: rv==7 'maximum-prefix' describe 'no neighbor VARIABLE maximum-prefix'@19: rv==0 'maximum-prefix' 'Maximum number of prefix accept from this peer' execute relaxed 'no neighbor VARIABLE maximum-prefix'@20: rv==0, 'no neighbor (A.B.C.D|X:X::X:X|WORD) maximum-prefix': 'VARIABLE' execute strict 'no neighbor VARIABLE maximum-prefix'@20: rv==0, 'no neighbor (A.B.C.D|X:X::X:X|WORD) maximum-prefix': 'VARIABLE' complete 'no neighbor VARIABLE maximum-prefix'@20: rv==7 'maximum-prefix' describe 'no neighbor VARIABLE maximum-prefix'@20: rv==0 'maximum-prefix' 'Maximum number of prefix accept from this peer' execute relaxed 'no neighbor VARIABLE maximum-prefix'@21: rv==0, 'no neighbor (A.B.C.D|X:X::X:X|WORD) maximum-prefix': 'VARIABLE' execute strict 'no neighbor VARIABLE maximum-prefix'@21: rv==0, 'no neighbor (A.B.C.D|X:X::X:X|WORD) maximum-prefix': 'VARIABLE' complete 'no neighbor VARIABLE maximum-prefix'@21: rv==7 'maximum-prefix' describe 'no neighbor VARIABLE maximum-prefix'@21: rv==0 'maximum-prefix' 'Maximum number of prefix accept from this peer' execute relaxed 'no neighbor VARIABLE maximum-prefix'@22: rv==0, 'no neighbor (A.B.C.D|X:X::X:X|WORD) maximum-prefix': 'VARIABLE' execute strict 'no neighbor VARIABLE maximum-prefix'@22: rv==0, 'no neighbor (A.B.C.D|X:X::X:X|WORD) maximum-prefix': 'VARIABLE' complete 'no neighbor VARIABLE maximum-prefix'@22: rv==7 'maximum-prefix' describe 'no neighbor VARIABLE maximum-prefix'@22: rv==0 'maximum-prefix' 'Maximum number of prefix accept from this peer' execute relaxed 'redistribute isis route-map VARIABLE metric 0 metric-type 2'@23: rv==0, 'redistribute (kernel|connected|static|rip|isis|bgp) {metric <0-16777214>|metric-type (1|2)|route-map WORD}': 'isis', '0', '2', 'VARIABLE' execute strict 'redistribute isis route-map VARIABLE metric 0 metric-type 2'@23: rv==0, 'redistribute (kernel|connected|static|rip|isis|bgp) {metric <0-16777214>|metric-type (1|2)|route-map WORD}': 'isis', '0', '2', 'VARIABLE' complete 'redistribute isis route-map VARIABLE metric 0 metric-type 2'@23: rv==7 '2' describe 'redistribute isis route-map VARIABLE metric 0 metric-type 2'@23: rv==0 '2' 'Set OSPF External Type 2 metrics' execute relaxed 'redistribute rip metric 0 route-map VARIABLE metric-type 1'@23: rv==0, 'redistribute (kernel|connected|static|rip|isis|bgp) {metric <0-16777214>|metric-type (1|2)|route-map WORD}': 'rip', '0', '1', 'VARIABLE' execute strict 'redistribute rip metric 0 route-map VARIABLE metric-type 1'@23: rv==0, 'redistribute (kernel|connected|static|rip|isis|bgp) {metric <0-16777214>|metric-type (1|2)|route-map WORD}': 'rip', '0', '1', 'VARIABLE' complete 'redistribute rip metric 0 route-map VARIABLE metric-type 1'@23: rv==7 '1' describe 'redistribute rip metric 0 route-map VARIABLE metric-type 1'@23: rv==0 '1' 'Set OSPF External Type 1 metrics' execute relaxed 'show bgp community VARIABLE local-AS no-export VARIABLE exact-match'@1: rv==0, 'show bgp community (AA:NN|local-AS|no-advertise|no-export) (AA:NN|local-AS|no-advertise|no-export) (AA:NN|local-AS|no-advertise|no-export) (AA:NN|local-AS|no-advertise|no-export) exact-match': 'VARIABLE', 'local-AS', 'no-export', 'VARIABLE' execute strict 'show bgp community VARIABLE local-AS no-export VARIABLE exact-match'@1: rv==0, 'show bgp community (AA:NN|local-AS|no-advertise|no-export) (AA:NN|local-AS|no-advertise|no-export) (AA:NN|local-AS|no-advertise|no-export) (AA:NN|local-AS|no-advertise|no-export) exact-match': 'VARIABLE', 'local-AS', 'no-export', 'VARIABLE' complete 'show bgp community VARIABLE local-AS no-export VARIABLE exact-match'@1: rv==7 'exact-match' describe 'show bgp community VARIABLE local-AS no-export VARIABLE exact-match'@1: rv==0 'exact-match' 'Exact match of the communities' execute relaxed 'show bgp community VARIABLE local-AS no-export VARIABLE exact-match'@2: rv==0, 'show bgp community (AA:NN|local-AS|no-advertise|no-export) (AA:NN|local-AS|no-advertise|no-export) (AA:NN|local-AS|no-advertise|no-export) (AA:NN|local-AS|no-advertise|no-export) exact-match': 'VARIABLE', 'local-AS', 'no-export', 'VARIABLE' execute strict 'show bgp community VARIABLE local-AS no-export VARIABLE exact-match'@2: rv==0, 'show bgp community (AA:NN|local-AS|no-advertise|no-export) (AA:NN|local-AS|no-advertise|no-export) (AA:NN|local-AS|no-advertise|no-export) (AA:NN|local-AS|no-advertise|no-export) exact-match': 'VARIABLE', 'local-AS', 'no-export', 'VARIABLE' complete 'show bgp community VARIABLE local-AS no-export VARIABLE exact-match'@2: rv==7 'exact-match' describe 'show bgp community VARIABLE local-AS no-export VARIABLE exact-match'@2: rv==0 'exact-match' 'Exact match of the communities' execute relaxed 'show bgp community VARIABLE local-AS no-export VARIABLE exact-match'@4: rv==0, 'show bgp community (AA:NN|local-AS|no-advertise|no-export) (AA:NN|local-AS|no-advertise|no-export) (AA:NN|local-AS|no-advertise|no-export) (AA:NN|local-AS|no-advertise|no-export) exact-match': 'VARIABLE', 'local-AS', 'no-export', 'VARIABLE' execute strict 'show bgp community VARIABLE local-AS no-export VARIABLE exact-match'@4: rv==0, 'show bgp community (AA:NN|local-AS|no-advertise|no-export) (AA:NN|local-AS|no-advertise|no-export) (AA:NN|local-AS|no-advertise|no-export) (AA:NN|local-AS|no-advertise|no-export) exact-match': 'VARIABLE', 'local-AS', 'no-export', 'VARIABLE' complete 'show bgp community VARIABLE local-AS no-export VARIABLE exact-match'@4: rv==7 'exact-match' describe 'show bgp community VARIABLE local-AS no-export VARIABLE exact-match'@4: rv==0 'exact-match' 'Exact match of the communities' execute relaxed 'show bgp ipv6 community no-advertise no-export no-export no-export'@1: rv==0, 'show bgp ipv6 community (AA:NN|local-AS|no-advertise|no-export) (AA:NN|local-AS|no-advertise|no-export) (AA:NN|local-AS|no-advertise|no-export) (AA:NN|local-AS|no-advertise|no-export)': 'no-advertise', 'no-export', 'no-export', 'no-export' execute strict 'show bgp ipv6 community no-advertise no-export no-export no-export'@1: rv==0, 'show bgp ipv6 community (AA:NN|local-AS|no-advertise|no-export) (AA:NN|local-AS|no-advertise|no-export) (AA:NN|local-AS|no-advertise|no-export) (AA:NN|local-AS|no-advertise|no-export)': 'no-advertise', 'no-export', 'no-export', 'no-export' complete 'show bgp ipv6 community no-advertise no-export no-export no-export'@1: rv==7 'no-export' describe 'show bgp ipv6 community no-advertise no-export no-export no-export'@1: rv==0 'AA:NN' 'Community number where AA and NN are <0-65535>' 'no-export' 'Do not export to next AS (well-known community)' execute relaxed 'show bgp ipv6 community no-advertise no-export no-export no-export'@2: rv==0, 'show bgp ipv6 community (AA:NN|local-AS|no-advertise|no-export) (AA:NN|local-AS|no-advertise|no-export) (AA:NN|local-AS|no-advertise|no-export) (AA:NN|local-AS|no-advertise|no-export)': 'no-advertise', 'no-export', 'no-export', 'no-export' execute strict 'show bgp ipv6 community no-advertise no-export no-export no-export'@2: rv==0, 'show bgp ipv6 community (AA:NN|local-AS|no-advertise|no-export) (AA:NN|local-AS|no-advertise|no-export) (AA:NN|local-AS|no-advertise|no-export) (AA:NN|local-AS|no-advertise|no-export)': 'no-advertise', 'no-export', 'no-export', 'no-export' complete 'show bgp ipv6 community no-advertise no-export no-export no-export'@2: rv==7 'no-export' describe 'show bgp ipv6 community no-advertise no-export no-export no-export'@2: rv==0 'AA:NN' 'Community number where AA and NN are <0-65535>' 'no-export' 'Do not export to next AS (well-known community)' execute relaxed 'show bgp ipv6 community no-advertise no-export no-export no-export'@4: rv==0, 'show bgp ipv6 community (AA:NN|local-AS|no-advertise|no-export) (AA:NN|local-AS|no-advertise|no-export) (AA:NN|local-AS|no-advertise|no-export) (AA:NN|local-AS|no-advertise|no-export)': 'no-advertise', 'no-export', 'no-export', 'no-export' execute strict 'show bgp ipv6 community no-advertise no-export no-export no-export'@4: rv==0, 'show bgp ipv6 community (AA:NN|local-AS|no-advertise|no-export) (AA:NN|local-AS|no-advertise|no-export) (AA:NN|local-AS|no-advertise|no-export) (AA:NN|local-AS|no-advertise|no-export)': 'no-advertise', 'no-export', 'no-export', 'no-export' complete 'show bgp ipv6 community no-advertise no-export no-export no-export'@4: rv==7 'no-export' describe 'show bgp ipv6 community no-advertise no-export no-export no-export'@4: rv==0 'AA:NN' 'Community number where AA and NN are <0-65535>' 'no-export' 'Do not export to next AS (well-known community)' execute relaxed 'show bgp ipv6 community VARIABLE local-AS local-AS no-advertise exact-match'@1: rv==0, 'show bgp ipv6 community (AA:NN|local-AS|no-advertise|no-export) (AA:NN|local-AS|no-advertise|no-export) (AA:NN|local-AS|no-advertise|no-export) (AA:NN|local-AS|no-advertise|no-export) exact-match': 'VARIABLE', 'local-AS', 'local-AS', 'no-advertise' execute strict 'show bgp ipv6 community VARIABLE local-AS local-AS no-advertise exact-match'@1: rv==0, 'show bgp ipv6 community (AA:NN|local-AS|no-advertise|no-export) (AA:NN|local-AS|no-advertise|no-export) (AA:NN|local-AS|no-advertise|no-export) (AA:NN|local-AS|no-advertise|no-export) exact-match': 'VARIABLE', 'local-AS', 'local-AS', 'no-advertise' complete 'show bgp ipv6 community VARIABLE local-AS local-AS no-advertise exact-match'@1: rv==7 'exact-match' describe 'show bgp ipv6 community VARIABLE local-AS local-AS no-advertise exact-match'@1: rv==0 'exact-match' 'Exact match of the communities' execute relaxed 'show bgp ipv6 community VARIABLE local-AS local-AS no-advertise exact-match'@2: rv==0, 'show bgp ipv6 community (AA:NN|local-AS|no-advertise|no-export) (AA:NN|local-AS|no-advertise|no-export) (AA:NN|local-AS|no-advertise|no-export) (AA:NN|local-AS|no-advertise|no-export) exact-match': 'VARIABLE', 'local-AS', 'local-AS', 'no-advertise' execute strict 'show bgp ipv6 community VARIABLE local-AS local-AS no-advertise exact-match'@2: rv==0, 'show bgp ipv6 community (AA:NN|local-AS|no-advertise|no-export) (AA:NN|local-AS|no-advertise|no-export) (AA:NN|local-AS|no-advertise|no-export) (AA:NN|local-AS|no-advertise|no-export) exact-match': 'VARIABLE', 'local-AS', 'local-AS', 'no-advertise' complete 'show bgp ipv6 community VARIABLE local-AS local-AS no-advertise exact-match'@2: rv==7 'exact-match' describe 'show bgp ipv6 community VARIABLE local-AS local-AS no-advertise exact-match'@2: rv==0 'exact-match' 'Exact match of the communities' execute relaxed 'show bgp ipv6 community VARIABLE local-AS local-AS no-advertise exact-match'@4: rv==0, 'show bgp ipv6 community (AA:NN|local-AS|no-advertise|no-export) (AA:NN|local-AS|no-advertise|no-export) (AA:NN|local-AS|no-advertise|no-export) (AA:NN|local-AS|no-advertise|no-export) exact-match': 'VARIABLE', 'local-AS', 'local-AS', 'no-advertise' execute strict 'show bgp ipv6 community VARIABLE local-AS local-AS no-advertise exact-match'@4: rv==0, 'show bgp ipv6 community (AA:NN|local-AS|no-advertise|no-export) (AA:NN|local-AS|no-advertise|no-export) (AA:NN|local-AS|no-advertise|no-export) (AA:NN|local-AS|no-advertise|no-export) exact-match': 'VARIABLE', 'local-AS', 'local-AS', 'no-advertise' complete 'show bgp ipv6 community VARIABLE local-AS local-AS no-advertise exact-match'@4: rv==7 'exact-match' describe 'show bgp ipv6 community VARIABLE local-AS local-AS no-advertise exact-match'@4: rv==0 'exact-match' 'Exact match of the communities' execute relaxed 'show bgp ipv6 community VARIABLE local-AS VARIABLE local-AS exact-match'@1: rv==0, 'show bgp ipv6 community (AA:NN|local-AS|no-advertise|no-export) (AA:NN|local-AS|no-advertise|no-export) (AA:NN|local-AS|no-advertise|no-export) (AA:NN|local-AS|no-advertise|no-export) exact-match': 'VARIABLE', 'local-AS', 'VARIABLE', 'local-AS' execute strict 'show bgp ipv6 community VARIABLE local-AS VARIABLE local-AS exact-match'@1: rv==0, 'show bgp ipv6 community (AA:NN|local-AS|no-advertise|no-export) (AA:NN|local-AS|no-advertise|no-export) (AA:NN|local-AS|no-advertise|no-export) (AA:NN|local-AS|no-advertise|no-export) exact-match': 'VARIABLE', 'local-AS', 'VARIABLE', 'local-AS' complete 'show bgp ipv6 community VARIABLE local-AS VARIABLE local-AS exact-match'@1: rv==7 'exact-match' describe 'show bgp ipv6 community VARIABLE local-AS VARIABLE local-AS exact-match'@1: rv==0 'exact-match' 'Exact match of the communities' execute relaxed 'show bgp ipv6 community VARIABLE local-AS VARIABLE local-AS exact-match'@2: rv==0, 'show bgp ipv6 community (AA:NN|local-AS|no-advertise|no-export) (AA:NN|local-AS|no-advertise|no-export) (AA:NN|local-AS|no-advertise|no-export) (AA:NN|local-AS|no-advertise|no-export) exact-match': 'VARIABLE', 'local-AS', 'VARIABLE', 'local-AS' execute strict 'show bgp ipv6 community VARIABLE local-AS VARIABLE local-AS exact-match'@2: rv==0, 'show bgp ipv6 community (AA:NN|local-AS|no-advertise|no-export) (AA:NN|local-AS|no-advertise|no-export) (AA:NN|local-AS|no-advertise|no-export) (AA:NN|local-AS|no-advertise|no-export) exact-match': 'VARIABLE', 'local-AS', 'VARIABLE', 'local-AS' complete 'show bgp ipv6 community VARIABLE local-AS VARIABLE local-AS exact-match'@2: rv==7 'exact-match' describe 'show bgp ipv6 community VARIABLE local-AS VARIABLE local-AS exact-match'@2: rv==0 'exact-match' 'Exact match of the communities' execute relaxed 'show bgp ipv6 community VARIABLE local-AS VARIABLE local-AS exact-match'@4: rv==0, 'show bgp ipv6 community (AA:NN|local-AS|no-advertise|no-export) (AA:NN|local-AS|no-advertise|no-export) (AA:NN|local-AS|no-advertise|no-export) (AA:NN|local-AS|no-advertise|no-export) exact-match': 'VARIABLE', 'local-AS', 'VARIABLE', 'local-AS' execute strict 'show bgp ipv6 community VARIABLE local-AS VARIABLE local-AS exact-match'@4: rv==0, 'show bgp ipv6 community (AA:NN|local-AS|no-advertise|no-export) (AA:NN|local-AS|no-advertise|no-export) (AA:NN|local-AS|no-advertise|no-export) (AA:NN|local-AS|no-advertise|no-export) exact-match': 'VARIABLE', 'local-AS', 'VARIABLE', 'local-AS' complete 'show bgp ipv6 community VARIABLE local-AS VARIABLE local-AS exact-match'@4: rv==7 'exact-match' describe 'show bgp ipv6 community VARIABLE local-AS VARIABLE local-AS exact-match'@4: rv==0 'exact-match' 'Exact match of the communities' execute relaxed 'show bgp view VARIABLE'@1: rv==4 execute strict 'show bgp view VARIABLE'@1: rv==4 complete 'show bgp view VARIABLE'@1: rv==2 describe 'show bgp view VARIABLE'@1: rv==0 'WORD' 'View name' execute relaxed 'show bgp view VARIABLE'@2: rv==0, 'show bgp view WORD': 'VARIABLE' execute strict 'show bgp view VARIABLE'@2: rv==0, 'show bgp view WORD': 'VARIABLE' complete 'show bgp view VARIABLE'@2: rv==2 describe 'show bgp view VARIABLE'@2: rv==0 'WORD' 'View name' execute relaxed 'show bgp view VARIABLE'@4: rv==0, 'show bgp view WORD': 'VARIABLE' execute strict 'show bgp view VARIABLE'@4: rv==0, 'show bgp view WORD': 'VARIABLE' complete 'show bgp view VARIABLE'@4: rv==2 describe 'show bgp view VARIABLE'@4: rv==0 'WORD' 'View name' execute relaxed 'show bgp view VARIABLE ipv4 multicast community no-export no-export local-AS VARIABLE'@1: rv==0, 'show bgp view WORD (ipv4|ipv6) (unicast|multicast) community (AA:NN|local-AS|no-advertise|no-export) (AA:NN|local-AS|no-advertise|no-export) (AA:NN|local-AS|no-advertise|no-export) (AA:NN|local-AS|no-advertise|no-export)': 'VARIABLE', 'ipv4', 'multicast', 'no-export', 'no-export', 'local-AS', 'VARIABLE' execute strict 'show bgp view VARIABLE ipv4 multicast community no-export no-export local-AS VARIABLE'@1: rv==0, 'show bgp view WORD (ipv4|ipv6) (unicast|multicast) community (AA:NN|local-AS|no-advertise|no-export) (AA:NN|local-AS|no-advertise|no-export) (AA:NN|local-AS|no-advertise|no-export) (AA:NN|local-AS|no-advertise|no-export)': 'VARIABLE', 'ipv4', 'multicast', 'no-export', 'no-export', 'local-AS', 'VARIABLE' complete 'show bgp view VARIABLE ipv4 multicast community no-export no-export local-AS VARIABLE'@1: rv==2 describe 'show bgp view VARIABLE ipv4 multicast community no-export no-export local-AS VARIABLE'@1: rv==0 'AA:NN' 'Community number where AA and NN are <0-65535>' execute relaxed 'show bgp view VARIABLE ipv4 multicast community no-export no-export local-AS VARIABLE'@2: rv==0, 'show bgp view WORD (ipv4|ipv6) (unicast|multicast) community (AA:NN|local-AS|no-advertise|no-export) (AA:NN|local-AS|no-advertise|no-export) (AA:NN|local-AS|no-advertise|no-export) (AA:NN|local-AS|no-advertise|no-export)': 'VARIABLE', 'ipv4', 'multicast', 'no-export', 'no-export', 'local-AS', 'VARIABLE' execute strict 'show bgp view VARIABLE ipv4 multicast community no-export no-export local-AS VARIABLE'@2: rv==0, 'show bgp view WORD (ipv4|ipv6) (unicast|multicast) community (AA:NN|local-AS|no-advertise|no-export) (AA:NN|local-AS|no-advertise|no-export) (AA:NN|local-AS|no-advertise|no-export) (AA:NN|local-AS|no-advertise|no-export)': 'VARIABLE', 'ipv4', 'multicast', 'no-export', 'no-export', 'local-AS', 'VARIABLE' complete 'show bgp view VARIABLE ipv4 multicast community no-export no-export local-AS VARIABLE'@2: rv==2 describe 'show bgp view VARIABLE ipv4 multicast community no-export no-export local-AS VARIABLE'@2: rv==0 'AA:NN' 'Community number where AA and NN are <0-65535>' execute relaxed 'show bgp view VARIABLE ipv4 multicast community no-export no-export local-AS VARIABLE'@4: rv==0, 'show bgp view WORD (ipv4|ipv6) (unicast|multicast) community (AA:NN|local-AS|no-advertise|no-export) (AA:NN|local-AS|no-advertise|no-export) (AA:NN|local-AS|no-advertise|no-export) (AA:NN|local-AS|no-advertise|no-export)': 'VARIABLE', 'ipv4', 'multicast', 'no-export', 'no-export', 'local-AS', 'VARIABLE' execute strict 'show bgp view VARIABLE ipv4 multicast community no-export no-export local-AS VARIABLE'@4: rv==0, 'show bgp view WORD (ipv4|ipv6) (unicast|multicast) community (AA:NN|local-AS|no-advertise|no-export) (AA:NN|local-AS|no-advertise|no-export) (AA:NN|local-AS|no-advertise|no-export) (AA:NN|local-AS|no-advertise|no-export)': 'VARIABLE', 'ipv4', 'multicast', 'no-export', 'no-export', 'local-AS', 'VARIABLE' complete 'show bgp view VARIABLE ipv4 multicast community no-export no-export local-AS VARIABLE'@4: rv==2 describe 'show bgp view VARIABLE ipv4 multicast community no-export no-export local-AS VARIABLE'@4: rv==0 'AA:NN' 'Community number where AA and NN are <0-65535>' execute relaxed 'show bgp view VARIABLE ipv4 unicast community local-AS no-advertise no-advertise local-AS'@1: rv==0, 'show bgp view WORD (ipv4|ipv6) (unicast|multicast) community (AA:NN|local-AS|no-advertise|no-export) (AA:NN|local-AS|no-advertise|no-export) (AA:NN|local-AS|no-advertise|no-export) (AA:NN|local-AS|no-advertise|no-export)': 'VARIABLE', 'ipv4', 'unicast', 'local-AS', 'no-advertise', 'no-advertise', 'local-AS' execute strict 'show bgp view VARIABLE ipv4 unicast community local-AS no-advertise no-advertise local-AS'@1: rv==0, 'show bgp view WORD (ipv4|ipv6) (unicast|multicast) community (AA:NN|local-AS|no-advertise|no-export) (AA:NN|local-AS|no-advertise|no-export) (AA:NN|local-AS|no-advertise|no-export) (AA:NN|local-AS|no-advertise|no-export)': 'VARIABLE', 'ipv4', 'unicast', 'local-AS', 'no-advertise', 'no-advertise', 'local-AS' complete 'show bgp view VARIABLE ipv4 unicast community local-AS no-advertise no-advertise local-AS'@1: rv==7 'local-AS' describe 'show bgp view VARIABLE ipv4 unicast community local-AS no-advertise no-advertise local-AS'@1: rv==0 'AA:NN' 'Community number where AA and NN are <0-65535>' 'local-AS' 'Do not send outside local AS (well-known community)' execute relaxed 'show bgp view VARIABLE ipv4 unicast community local-AS no-advertise no-advertise local-AS'@2: rv==0, 'show bgp view WORD (ipv4|ipv6) (unicast|multicast) community (AA:NN|local-AS|no-advertise|no-export) (AA:NN|local-AS|no-advertise|no-export) (AA:NN|local-AS|no-advertise|no-export) (AA:NN|local-AS|no-advertise|no-export)': 'VARIABLE', 'ipv4', 'unicast', 'local-AS', 'no-advertise', 'no-advertise', 'local-AS' execute strict 'show bgp view VARIABLE ipv4 unicast community local-AS no-advertise no-advertise local-AS'@2: rv==0, 'show bgp view WORD (ipv4|ipv6) (unicast|multicast) community (AA:NN|local-AS|no-advertise|no-export) (AA:NN|local-AS|no-advertise|no-export) (AA:NN|local-AS|no-advertise|no-export) (AA:NN|local-AS|no-advertise|no-export)': 'VARIABLE', 'ipv4', 'unicast', 'local-AS', 'no-advertise', 'no-advertise', 'local-AS' complete 'show bgp view VARIABLE ipv4 unicast community local-AS no-advertise no-advertise local-AS'@2: rv==7 'local-AS' describe 'show bgp view VARIABLE ipv4 unicast community local-AS no-advertise no-advertise local-AS'@2: rv==0 'AA:NN' 'Community number where AA and NN are <0-65535>' 'local-AS' 'Do not send outside local AS (well-known community)' execute relaxed 'show bgp view VARIABLE ipv4 unicast community local-AS no-advertise no-advertise local-AS'@4: rv==0, 'show bgp view WORD (ipv4|ipv6) (unicast|multicast) community (AA:NN|local-AS|no-advertise|no-export) (AA:NN|local-AS|no-advertise|no-export) (AA:NN|local-AS|no-advertise|no-export) (AA:NN|local-AS|no-advertise|no-export)': 'VARIABLE', 'ipv4', 'unicast', 'local-AS', 'no-advertise', 'no-advertise', 'local-AS' execute strict 'show bgp view VARIABLE ipv4 unicast community local-AS no-advertise no-advertise local-AS'@4: rv==0, 'show bgp view WORD (ipv4|ipv6) (unicast|multicast) community (AA:NN|local-AS|no-advertise|no-export) (AA:NN|local-AS|no-advertise|no-export) (AA:NN|local-AS|no-advertise|no-export) (AA:NN|local-AS|no-advertise|no-export)': 'VARIABLE', 'ipv4', 'unicast', 'local-AS', 'no-advertise', 'no-advertise', 'local-AS' complete 'show bgp view VARIABLE ipv4 unicast community local-AS no-advertise no-advertise local-AS'@4: rv==7 'local-AS' describe 'show bgp view VARIABLE ipv4 unicast community local-AS no-advertise no-advertise local-AS'@4: rv==0 'AA:NN' 'Community number where AA and NN are <0-65535>' 'local-AS' 'Do not send outside local AS (well-known community)' execute relaxed 'show bgp view VARIABLE ipv4 unicast community no-export VARIABLE no-advertise'@1: rv==0, 'show bgp view WORD (ipv4|ipv6) (unicast|multicast) community (AA:NN|local-AS|no-advertise|no-export) (AA:NN|local-AS|no-advertise|no-export) (AA:NN|local-AS|no-advertise|no-export)': 'VARIABLE', 'ipv4', 'unicast', 'no-export', 'VARIABLE', 'no-advertise' execute strict 'show bgp view VARIABLE ipv4 unicast community no-export VARIABLE no-advertise'@1: rv==0, 'show bgp view WORD (ipv4|ipv6) (unicast|multicast) community (AA:NN|local-AS|no-advertise|no-export) (AA:NN|local-AS|no-advertise|no-export) (AA:NN|local-AS|no-advertise|no-export)': 'VARIABLE', 'ipv4', 'unicast', 'no-export', 'VARIABLE', 'no-advertise' complete 'show bgp view VARIABLE ipv4 unicast community no-export VARIABLE no-advertise'@1: rv==7 'no-advertise' describe 'show bgp view VARIABLE ipv4 unicast community no-export VARIABLE no-advertise'@1: rv==0 'AA:NN' 'Community number where AA and NN are <0-65535>' 'no-advertise' 'Do not advertise to any peer (well-known community)' execute relaxed 'show bgp view VARIABLE ipv4 unicast community no-export VARIABLE no-advertise'@2: rv==0, 'show bgp view WORD (ipv4|ipv6) (unicast|multicast) community (AA:NN|local-AS|no-advertise|no-export) (AA:NN|local-AS|no-advertise|no-export) (AA:NN|local-AS|no-advertise|no-export)': 'VARIABLE', 'ipv4', 'unicast', 'no-export', 'VARIABLE', 'no-advertise' execute strict 'show bgp view VARIABLE ipv4 unicast community no-export VARIABLE no-advertise'@2: rv==0, 'show bgp view WORD (ipv4|ipv6) (unicast|multicast) community (AA:NN|local-AS|no-advertise|no-export) (AA:NN|local-AS|no-advertise|no-export) (AA:NN|local-AS|no-advertise|no-export)': 'VARIABLE', 'ipv4', 'unicast', 'no-export', 'VARIABLE', 'no-advertise' complete 'show bgp view VARIABLE ipv4 unicast community no-export VARIABLE no-advertise'@2: rv==7 'no-advertise' describe 'show bgp view VARIABLE ipv4 unicast community no-export VARIABLE no-advertise'@2: rv==0 'AA:NN' 'Community number where AA and NN are <0-65535>' 'no-advertise' 'Do not advertise to any peer (well-known community)' execute relaxed 'show bgp view VARIABLE ipv4 unicast community no-export VARIABLE no-advertise'@4: rv==0, 'show bgp view WORD (ipv4|ipv6) (unicast|multicast) community (AA:NN|local-AS|no-advertise|no-export) (AA:NN|local-AS|no-advertise|no-export) (AA:NN|local-AS|no-advertise|no-export)': 'VARIABLE', 'ipv4', 'unicast', 'no-export', 'VARIABLE', 'no-advertise' execute strict 'show bgp view VARIABLE ipv4 unicast community no-export VARIABLE no-advertise'@4: rv==0, 'show bgp view WORD (ipv4|ipv6) (unicast|multicast) community (AA:NN|local-AS|no-advertise|no-export) (AA:NN|local-AS|no-advertise|no-export) (AA:NN|local-AS|no-advertise|no-export)': 'VARIABLE', 'ipv4', 'unicast', 'no-export', 'VARIABLE', 'no-advertise' complete 'show bgp view VARIABLE ipv4 unicast community no-export VARIABLE no-advertise'@4: rv==7 'no-advertise' describe 'show bgp view VARIABLE ipv4 unicast community no-export VARIABLE no-advertise'@4: rv==0 'AA:NN' 'Community number where AA and NN are <0-65535>' 'no-advertise' 'Do not advertise to any peer (well-known community)' execute relaxed 'show bgp view VARIABLE ipv4 unicast community VARIABLE local-AS no-export VARIABLE'@1: rv==0, 'show bgp view WORD (ipv4|ipv6) (unicast|multicast) community (AA:NN|local-AS|no-advertise|no-export) (AA:NN|local-AS|no-advertise|no-export) (AA:NN|local-AS|no-advertise|no-export) (AA:NN|local-AS|no-advertise|no-export)': 'VARIABLE', 'ipv4', 'unicast', 'VARIABLE', 'local-AS', 'no-export', 'VARIABLE' execute strict 'show bgp view VARIABLE ipv4 unicast community VARIABLE local-AS no-export VARIABLE'@1: rv==0, 'show bgp view WORD (ipv4|ipv6) (unicast|multicast) community (AA:NN|local-AS|no-advertise|no-export) (AA:NN|local-AS|no-advertise|no-export) (AA:NN|local-AS|no-advertise|no-export) (AA:NN|local-AS|no-advertise|no-export)': 'VARIABLE', 'ipv4', 'unicast', 'VARIABLE', 'local-AS', 'no-export', 'VARIABLE' complete 'show bgp view VARIABLE ipv4 unicast community VARIABLE local-AS no-export VARIABLE'@1: rv==2 describe 'show bgp view VARIABLE ipv4 unicast community VARIABLE local-AS no-export VARIABLE'@1: rv==0 'AA:NN' 'Community number where AA and NN are <0-65535>' execute relaxed 'show bgp view VARIABLE ipv4 unicast community VARIABLE local-AS no-export VARIABLE'@2: rv==0, 'show bgp view WORD (ipv4|ipv6) (unicast|multicast) community (AA:NN|local-AS|no-advertise|no-export) (AA:NN|local-AS|no-advertise|no-export) (AA:NN|local-AS|no-advertise|no-export) (AA:NN|local-AS|no-advertise|no-export)': 'VARIABLE', 'ipv4', 'unicast', 'VARIABLE', 'local-AS', 'no-export', 'VARIABLE' execute strict 'show bgp view VARIABLE ipv4 unicast community VARIABLE local-AS no-export VARIABLE'@2: rv==0, 'show bgp view WORD (ipv4|ipv6) (unicast|multicast) community (AA:NN|local-AS|no-advertise|no-export) (AA:NN|local-AS|no-advertise|no-export) (AA:NN|local-AS|no-advertise|no-export) (AA:NN|local-AS|no-advertise|no-export)': 'VARIABLE', 'ipv4', 'unicast', 'VARIABLE', 'local-AS', 'no-export', 'VARIABLE' complete 'show bgp view VARIABLE ipv4 unicast community VARIABLE local-AS no-export VARIABLE'@2: rv==2 describe 'show bgp view VARIABLE ipv4 unicast community VARIABLE local-AS no-export VARIABLE'@2: rv==0 'AA:NN' 'Community number where AA and NN are <0-65535>' execute relaxed 'show bgp view VARIABLE ipv4 unicast community VARIABLE local-AS no-export VARIABLE'@4: rv==0, 'show bgp view WORD (ipv4|ipv6) (unicast|multicast) community (AA:NN|local-AS|no-advertise|no-export) (AA:NN|local-AS|no-advertise|no-export) (AA:NN|local-AS|no-advertise|no-export) (AA:NN|local-AS|no-advertise|no-export)': 'VARIABLE', 'ipv4', 'unicast', 'VARIABLE', 'local-AS', 'no-export', 'VARIABLE' execute strict 'show bgp view VARIABLE ipv4 unicast community VARIABLE local-AS no-export VARIABLE'@4: rv==0, 'show bgp view WORD (ipv4|ipv6) (unicast|multicast) community (AA:NN|local-AS|no-advertise|no-export) (AA:NN|local-AS|no-advertise|no-export) (AA:NN|local-AS|no-advertise|no-export) (AA:NN|local-AS|no-advertise|no-export)': 'VARIABLE', 'ipv4', 'unicast', 'VARIABLE', 'local-AS', 'no-export', 'VARIABLE' complete 'show bgp view VARIABLE ipv4 unicast community VARIABLE local-AS no-export VARIABLE'@4: rv==2 describe 'show bgp view VARIABLE ipv4 unicast community VARIABLE local-AS no-export VARIABLE'@4: rv==0 'AA:NN' 'Community number where AA and NN are <0-65535>' execute relaxed 'show bgp view VARIABLE ipv6 multicast community no-advertise VARIABLE no-advertise local-AS'@1: rv==0, 'show bgp view WORD (ipv4|ipv6) (unicast|multicast) community (AA:NN|local-AS|no-advertise|no-export) (AA:NN|local-AS|no-advertise|no-export) (AA:NN|local-AS|no-advertise|no-export) (AA:NN|local-AS|no-advertise|no-export)': 'VARIABLE', 'ipv6', 'multicast', 'no-advertise', 'VARIABLE', 'no-advertise', 'local-AS' execute strict 'show bgp view VARIABLE ipv6 multicast community no-advertise VARIABLE no-advertise local-AS'@1: rv==0, 'show bgp view WORD (ipv4|ipv6) (unicast|multicast) community (AA:NN|local-AS|no-advertise|no-export) (AA:NN|local-AS|no-advertise|no-export) (AA:NN|local-AS|no-advertise|no-export) (AA:NN|local-AS|no-advertise|no-export)': 'VARIABLE', 'ipv6', 'multicast', 'no-advertise', 'VARIABLE', 'no-advertise', 'local-AS' complete 'show bgp view VARIABLE ipv6 multicast community no-advertise VARIABLE no-advertise local-AS'@1: rv==7 'local-AS' describe 'show bgp view VARIABLE ipv6 multicast community no-advertise VARIABLE no-advertise local-AS'@1: rv==0 'AA:NN' 'Community number where AA and NN are <0-65535>' 'local-AS' 'Do not send outside local AS (well-known community)' execute relaxed 'show bgp view VARIABLE ipv6 multicast community no-advertise VARIABLE no-advertise local-AS'@2: rv==0, 'show bgp view WORD (ipv4|ipv6) (unicast|multicast) community (AA:NN|local-AS|no-advertise|no-export) (AA:NN|local-AS|no-advertise|no-export) (AA:NN|local-AS|no-advertise|no-export) (AA:NN|local-AS|no-advertise|no-export)': 'VARIABLE', 'ipv6', 'multicast', 'no-advertise', 'VARIABLE', 'no-advertise', 'local-AS' execute strict 'show bgp view VARIABLE ipv6 multicast community no-advertise VARIABLE no-advertise local-AS'@2: rv==0, 'show bgp view WORD (ipv4|ipv6) (unicast|multicast) community (AA:NN|local-AS|no-advertise|no-export) (AA:NN|local-AS|no-advertise|no-export) (AA:NN|local-AS|no-advertise|no-export) (AA:NN|local-AS|no-advertise|no-export)': 'VARIABLE', 'ipv6', 'multicast', 'no-advertise', 'VARIABLE', 'no-advertise', 'local-AS' complete 'show bgp view VARIABLE ipv6 multicast community no-advertise VARIABLE no-advertise local-AS'@2: rv==7 'local-AS' describe 'show bgp view VARIABLE ipv6 multicast community no-advertise VARIABLE no-advertise local-AS'@2: rv==0 'AA:NN' 'Community number where AA and NN are <0-65535>' 'local-AS' 'Do not send outside local AS (well-known community)' execute relaxed 'show bgp view VARIABLE ipv6 multicast community no-advertise VARIABLE no-advertise local-AS'@4: rv==0, 'show bgp view WORD (ipv4|ipv6) (unicast|multicast) community (AA:NN|local-AS|no-advertise|no-export) (AA:NN|local-AS|no-advertise|no-export) (AA:NN|local-AS|no-advertise|no-export) (AA:NN|local-AS|no-advertise|no-export)': 'VARIABLE', 'ipv6', 'multicast', 'no-advertise', 'VARIABLE', 'no-advertise', 'local-AS' execute strict 'show bgp view VARIABLE ipv6 multicast community no-advertise VARIABLE no-advertise local-AS'@4: rv==0, 'show bgp view WORD (ipv4|ipv6) (unicast|multicast) community (AA:NN|local-AS|no-advertise|no-export) (AA:NN|local-AS|no-advertise|no-export) (AA:NN|local-AS|no-advertise|no-export) (AA:NN|local-AS|no-advertise|no-export)': 'VARIABLE', 'ipv6', 'multicast', 'no-advertise', 'VARIABLE', 'no-advertise', 'local-AS' complete 'show bgp view VARIABLE ipv6 multicast community no-advertise VARIABLE no-advertise local-AS'@4: rv==7 'local-AS' describe 'show bgp view VARIABLE ipv6 multicast community no-advertise VARIABLE no-advertise local-AS'@4: rv==0 'AA:NN' 'Community number where AA and NN are <0-65535>' 'local-AS' 'Do not send outside local AS (well-known community)' execute relaxed 'show ip bgp community no-advertise local-AS no-advertise VARIABLE'@1: rv==0, 'show ip bgp community (AA:NN|local-AS|no-advertise|no-export) (AA:NN|local-AS|no-advertise|no-export) (AA:NN|local-AS|no-advertise|no-export) (AA:NN|local-AS|no-advertise|no-export)': 'no-advertise', 'local-AS', 'no-advertise', 'VARIABLE' execute strict 'show ip bgp community no-advertise local-AS no-advertise VARIABLE'@1: rv==0, 'show ip bgp community (AA:NN|local-AS|no-advertise|no-export) (AA:NN|local-AS|no-advertise|no-export) (AA:NN|local-AS|no-advertise|no-export) (AA:NN|local-AS|no-advertise|no-export)': 'no-advertise', 'local-AS', 'no-advertise', 'VARIABLE' complete 'show ip bgp community no-advertise local-AS no-advertise VARIABLE'@1: rv==2 describe 'show ip bgp community no-advertise local-AS no-advertise VARIABLE'@1: rv==0 'AA:NN' 'Community number where AA and NN are <0-65535>' execute relaxed 'show ip bgp community no-advertise local-AS no-advertise VARIABLE'@2: rv==0, 'show ip bgp community (AA:NN|local-AS|no-advertise|no-export) (AA:NN|local-AS|no-advertise|no-export) (AA:NN|local-AS|no-advertise|no-export) (AA:NN|local-AS|no-advertise|no-export)': 'no-advertise', 'local-AS', 'no-advertise', 'VARIABLE' execute strict 'show ip bgp community no-advertise local-AS no-advertise VARIABLE'@2: rv==0, 'show ip bgp community (AA:NN|local-AS|no-advertise|no-export) (AA:NN|local-AS|no-advertise|no-export) (AA:NN|local-AS|no-advertise|no-export) (AA:NN|local-AS|no-advertise|no-export)': 'no-advertise', 'local-AS', 'no-advertise', 'VARIABLE' complete 'show ip bgp community no-advertise local-AS no-advertise VARIABLE'@2: rv==2 describe 'show ip bgp community no-advertise local-AS no-advertise VARIABLE'@2: rv==0 'AA:NN' 'Community number where AA and NN are <0-65535>' execute relaxed 'show ip bgp community no-advertise local-AS no-advertise VARIABLE'@4: rv==0, 'show ip bgp community (AA:NN|local-AS|no-advertise|no-export) (AA:NN|local-AS|no-advertise|no-export) (AA:NN|local-AS|no-advertise|no-export) (AA:NN|local-AS|no-advertise|no-export)': 'no-advertise', 'local-AS', 'no-advertise', 'VARIABLE' execute strict 'show ip bgp community no-advertise local-AS no-advertise VARIABLE'@4: rv==0, 'show ip bgp community (AA:NN|local-AS|no-advertise|no-export) (AA:NN|local-AS|no-advertise|no-export) (AA:NN|local-AS|no-advertise|no-export) (AA:NN|local-AS|no-advertise|no-export)': 'no-advertise', 'local-AS', 'no-advertise', 'VARIABLE' complete 'show ip bgp community no-advertise local-AS no-advertise VARIABLE'@4: rv==2 describe 'show ip bgp community no-advertise local-AS no-advertise VARIABLE'@4: rv==0 'AA:NN' 'Community number where AA and NN are <0-65535>' execute relaxed 'show ip bgp ipv4 unicast community no-advertise VARIABLE no-advertise local-AS exact-match'@1: rv==0, 'show ip bgp ipv4 (unicast|multicast) community (AA:NN|local-AS|no-advertise|no-export) (AA:NN|local-AS|no-advertise|no-export) (AA:NN|local-AS|no-advertise|no-export) (AA:NN|local-AS|no-advertise|no-export) exact-match': 'unicast', 'no-advertise', 'VARIABLE', 'no-advertise', 'local-AS' execute strict 'show ip bgp ipv4 unicast community no-advertise VARIABLE no-advertise local-AS exact-match'@1: rv==0, 'show ip bgp ipv4 (unicast|multicast) community (AA:NN|local-AS|no-advertise|no-export) (AA:NN|local-AS|no-advertise|no-export) (AA:NN|local-AS|no-advertise|no-export) (AA:NN|local-AS|no-advertise|no-export) exact-match': 'unicast', 'no-advertise', 'VARIABLE', 'no-advertise', 'local-AS' complete 'show ip bgp ipv4 unicast community no-advertise VARIABLE no-advertise local-AS exact-match'@1: rv==7 'exact-match' describe 'show ip bgp ipv4 unicast community no-advertise VARIABLE no-advertise local-AS exact-match'@1: rv==0 'exact-match' 'Exact match of the communities' execute relaxed 'show ip bgp ipv4 unicast community no-advertise VARIABLE no-advertise local-AS exact-match'@2: rv==0, 'show ip bgp ipv4 (unicast|multicast) community (AA:NN|local-AS|no-advertise|no-export) (AA:NN|local-AS|no-advertise|no-export) (AA:NN|local-AS|no-advertise|no-export) (AA:NN|local-AS|no-advertise|no-export) exact-match': 'unicast', 'no-advertise', 'VARIABLE', 'no-advertise', 'local-AS' execute strict 'show ip bgp ipv4 unicast community no-advertise VARIABLE no-advertise local-AS exact-match'@2: rv==0, 'show ip bgp ipv4 (unicast|multicast) community (AA:NN|local-AS|no-advertise|no-export) (AA:NN|local-AS|no-advertise|no-export) (AA:NN|local-AS|no-advertise|no-export) (AA:NN|local-AS|no-advertise|no-export) exact-match': 'unicast', 'no-advertise', 'VARIABLE', 'no-advertise', 'local-AS' complete 'show ip bgp ipv4 unicast community no-advertise VARIABLE no-advertise local-AS exact-match'@2: rv==7 'exact-match' describe 'show ip bgp ipv4 unicast community no-advertise VARIABLE no-advertise local-AS exact-match'@2: rv==0 'exact-match' 'Exact match of the communities' execute relaxed 'show ip bgp ipv4 unicast community no-advertise VARIABLE no-advertise local-AS exact-match'@4: rv==0, 'show ip bgp ipv4 (unicast|multicast) community (AA:NN|local-AS|no-advertise|no-export) (AA:NN|local-AS|no-advertise|no-export) (AA:NN|local-AS|no-advertise|no-export) (AA:NN|local-AS|no-advertise|no-export) exact-match': 'unicast', 'no-advertise', 'VARIABLE', 'no-advertise', 'local-AS' execute strict 'show ip bgp ipv4 unicast community no-advertise VARIABLE no-advertise local-AS exact-match'@4: rv==0, 'show ip bgp ipv4 (unicast|multicast) community (AA:NN|local-AS|no-advertise|no-export) (AA:NN|local-AS|no-advertise|no-export) (AA:NN|local-AS|no-advertise|no-export) (AA:NN|local-AS|no-advertise|no-export) exact-match': 'unicast', 'no-advertise', 'VARIABLE', 'no-advertise', 'local-AS' complete 'show ip bgp ipv4 unicast community no-advertise VARIABLE no-advertise local-AS exact-match'@4: rv==7 'exact-match' describe 'show ip bgp ipv4 unicast community no-advertise VARIABLE no-advertise local-AS exact-match'@4: rv==0 'exact-match' 'Exact match of the communities' execute relaxed 'show ip bgp ipv4 unicast community no-export VARIABLE local-AS VARIABLE exact-match'@1: rv==0, 'show ip bgp ipv4 (unicast|multicast) community (AA:NN|local-AS|no-advertise|no-export) (AA:NN|local-AS|no-advertise|no-export) (AA:NN|local-AS|no-advertise|no-export) (AA:NN|local-AS|no-advertise|no-export) exact-match': 'unicast', 'no-export', 'VARIABLE', 'local-AS', 'VARIABLE' execute strict 'show ip bgp ipv4 unicast community no-export VARIABLE local-AS VARIABLE exact-match'@1: rv==0, 'show ip bgp ipv4 (unicast|multicast) community (AA:NN|local-AS|no-advertise|no-export) (AA:NN|local-AS|no-advertise|no-export) (AA:NN|local-AS|no-advertise|no-export) (AA:NN|local-AS|no-advertise|no-export) exact-match': 'unicast', 'no-export', 'VARIABLE', 'local-AS', 'VARIABLE' complete 'show ip bgp ipv4 unicast community no-export VARIABLE local-AS VARIABLE exact-match'@1: rv==7 'exact-match' describe 'show ip bgp ipv4 unicast community no-export VARIABLE local-AS VARIABLE exact-match'@1: rv==0 'exact-match' 'Exact match of the communities' execute relaxed 'show ip bgp ipv4 unicast community no-export VARIABLE local-AS VARIABLE exact-match'@2: rv==0, 'show ip bgp ipv4 (unicast|multicast) community (AA:NN|local-AS|no-advertise|no-export) (AA:NN|local-AS|no-advertise|no-export) (AA:NN|local-AS|no-advertise|no-export) (AA:NN|local-AS|no-advertise|no-export) exact-match': 'unicast', 'no-export', 'VARIABLE', 'local-AS', 'VARIABLE' execute strict 'show ip bgp ipv4 unicast community no-export VARIABLE local-AS VARIABLE exact-match'@2: rv==0, 'show ip bgp ipv4 (unicast|multicast) community (AA:NN|local-AS|no-advertise|no-export) (AA:NN|local-AS|no-advertise|no-export) (AA:NN|local-AS|no-advertise|no-export) (AA:NN|local-AS|no-advertise|no-export) exact-match': 'unicast', 'no-export', 'VARIABLE', 'local-AS', 'VARIABLE' complete 'show ip bgp ipv4 unicast community no-export VARIABLE local-AS VARIABLE exact-match'@2: rv==7 'exact-match' describe 'show ip bgp ipv4 unicast community no-export VARIABLE local-AS VARIABLE exact-match'@2: rv==0 'exact-match' 'Exact match of the communities' execute relaxed 'show ip bgp ipv4 unicast community no-export VARIABLE local-AS VARIABLE exact-match'@4: rv==0, 'show ip bgp ipv4 (unicast|multicast) community (AA:NN|local-AS|no-advertise|no-export) (AA:NN|local-AS|no-advertise|no-export) (AA:NN|local-AS|no-advertise|no-export) (AA:NN|local-AS|no-advertise|no-export) exact-match': 'unicast', 'no-export', 'VARIABLE', 'local-AS', 'VARIABLE' execute strict 'show ip bgp ipv4 unicast community no-export VARIABLE local-AS VARIABLE exact-match'@4: rv==0, 'show ip bgp ipv4 (unicast|multicast) community (AA:NN|local-AS|no-advertise|no-export) (AA:NN|local-AS|no-advertise|no-export) (AA:NN|local-AS|no-advertise|no-export) (AA:NN|local-AS|no-advertise|no-export) exact-match': 'unicast', 'no-export', 'VARIABLE', 'local-AS', 'VARIABLE' complete 'show ip bgp ipv4 unicast community no-export VARIABLE local-AS VARIABLE exact-match'@4: rv==7 'exact-match' describe 'show ip bgp ipv4 unicast community no-export VARIABLE local-AS VARIABLE exact-match'@4: rv==0 'exact-match' 'Exact match of the communities' execute relaxed 'show ip bgp ipv4 unicast community no-export VARIABLE no-export local-AS exact-match'@1: rv==0, 'show ip bgp ipv4 (unicast|multicast) community (AA:NN|local-AS|no-advertise|no-export) (AA:NN|local-AS|no-advertise|no-export) (AA:NN|local-AS|no-advertise|no-export) (AA:NN|local-AS|no-advertise|no-export) exact-match': 'unicast', 'no-export', 'VARIABLE', 'no-export', 'local-AS' execute strict 'show ip bgp ipv4 unicast community no-export VARIABLE no-export local-AS exact-match'@1: rv==0, 'show ip bgp ipv4 (unicast|multicast) community (AA:NN|local-AS|no-advertise|no-export) (AA:NN|local-AS|no-advertise|no-export) (AA:NN|local-AS|no-advertise|no-export) (AA:NN|local-AS|no-advertise|no-export) exact-match': 'unicast', 'no-export', 'VARIABLE', 'no-export', 'local-AS' complete 'show ip bgp ipv4 unicast community no-export VARIABLE no-export local-AS exact-match'@1: rv==7 'exact-match' describe 'show ip bgp ipv4 unicast community no-export VARIABLE no-export local-AS exact-match'@1: rv==0 'exact-match' 'Exact match of the communities' execute relaxed 'show ip bgp ipv4 unicast community no-export VARIABLE no-export local-AS exact-match'@2: rv==0, 'show ip bgp ipv4 (unicast|multicast) community (AA:NN|local-AS|no-advertise|no-export) (AA:NN|local-AS|no-advertise|no-export) (AA:NN|local-AS|no-advertise|no-export) (AA:NN|local-AS|no-advertise|no-export) exact-match': 'unicast', 'no-export', 'VARIABLE', 'no-export', 'local-AS' execute strict 'show ip bgp ipv4 unicast community no-export VARIABLE no-export local-AS exact-match'@2: rv==0, 'show ip bgp ipv4 (unicast|multicast) community (AA:NN|local-AS|no-advertise|no-export) (AA:NN|local-AS|no-advertise|no-export) (AA:NN|local-AS|no-advertise|no-export) (AA:NN|local-AS|no-advertise|no-export) exact-match': 'unicast', 'no-export', 'VARIABLE', 'no-export', 'local-AS' complete 'show ip bgp ipv4 unicast community no-export VARIABLE no-export local-AS exact-match'@2: rv==7 'exact-match' describe 'show ip bgp ipv4 unicast community no-export VARIABLE no-export local-AS exact-match'@2: rv==0 'exact-match' 'Exact match of the communities' execute relaxed 'show ip bgp ipv4 unicast community no-export VARIABLE no-export local-AS exact-match'@4: rv==0, 'show ip bgp ipv4 (unicast|multicast) community (AA:NN|local-AS|no-advertise|no-export) (AA:NN|local-AS|no-advertise|no-export) (AA:NN|local-AS|no-advertise|no-export) (AA:NN|local-AS|no-advertise|no-export) exact-match': 'unicast', 'no-export', 'VARIABLE', 'no-export', 'local-AS' execute strict 'show ip bgp ipv4 unicast community no-export VARIABLE no-export local-AS exact-match'@4: rv==0, 'show ip bgp ipv4 (unicast|multicast) community (AA:NN|local-AS|no-advertise|no-export) (AA:NN|local-AS|no-advertise|no-export) (AA:NN|local-AS|no-advertise|no-export) (AA:NN|local-AS|no-advertise|no-export) exact-match': 'unicast', 'no-export', 'VARIABLE', 'no-export', 'local-AS' complete 'show ip bgp ipv4 unicast community no-export VARIABLE no-export local-AS exact-match'@4: rv==7 'exact-match' describe 'show ip bgp ipv4 unicast community no-export VARIABLE no-export local-AS exact-match'@4: rv==0 'exact-match' 'Exact match of the communities' execute relaxed 'show ipv6 bgp community no-export no-export VARIABLE VARIABLE'@2: rv==0, 'show ipv6 bgp community (AA:NN|local-AS|no-advertise|no-export) (AA:NN|local-AS|no-advertise|no-export) (AA:NN|local-AS|no-advertise|no-export) (AA:NN|local-AS|no-advertise|no-export)': 'no-export', 'no-export', 'VARIABLE', 'VARIABLE' execute strict 'show ipv6 bgp community no-export no-export VARIABLE VARIABLE'@2: rv==0, 'show ipv6 bgp community (AA:NN|local-AS|no-advertise|no-export) (AA:NN|local-AS|no-advertise|no-export) (AA:NN|local-AS|no-advertise|no-export) (AA:NN|local-AS|no-advertise|no-export)': 'no-export', 'no-export', 'VARIABLE', 'VARIABLE' complete 'show ipv6 bgp community no-export no-export VARIABLE VARIABLE'@2: rv==2 describe 'show ipv6 bgp community no-export no-export VARIABLE VARIABLE'@2: rv==0 'AA:NN' 'Community number where AA and NN are <0-65535>' execute relaxed 'show ipv6 bgp community no-export no-export VARIABLE VARIABLE'@4: rv==0, 'show ipv6 bgp community (AA:NN|local-AS|no-advertise|no-export) (AA:NN|local-AS|no-advertise|no-export) (AA:NN|local-AS|no-advertise|no-export) (AA:NN|local-AS|no-advertise|no-export)': 'no-export', 'no-export', 'VARIABLE', 'VARIABLE' execute strict 'show ipv6 bgp community no-export no-export VARIABLE VARIABLE'@4: rv==0, 'show ipv6 bgp community (AA:NN|local-AS|no-advertise|no-export) (AA:NN|local-AS|no-advertise|no-export) (AA:NN|local-AS|no-advertise|no-export) (AA:NN|local-AS|no-advertise|no-export)': 'no-export', 'no-export', 'VARIABLE', 'VARIABLE' complete 'show ipv6 bgp community no-export no-export VARIABLE VARIABLE'@4: rv==2 describe 'show ipv6 bgp community no-export no-export VARIABLE VARIABLE'@4: rv==0 'AA:NN' 'Community number where AA and NN are <0-65535>' execute relaxed 'show ipv6 bgp community VARIABLE local-AS no-advertise no-advertise exact-match'@2: rv==0, 'show ipv6 bgp community (AA:NN|local-AS|no-advertise|no-export) (AA:NN|local-AS|no-advertise|no-export) (AA:NN|local-AS|no-advertise|no-export) (AA:NN|local-AS|no-advertise|no-export) exact-match': 'VARIABLE', 'local-AS', 'no-advertise', 'no-advertise' execute strict 'show ipv6 bgp community VARIABLE local-AS no-advertise no-advertise exact-match'@2: rv==0, 'show ipv6 bgp community (AA:NN|local-AS|no-advertise|no-export) (AA:NN|local-AS|no-advertise|no-export) (AA:NN|local-AS|no-advertise|no-export) (AA:NN|local-AS|no-advertise|no-export) exact-match': 'VARIABLE', 'local-AS', 'no-advertise', 'no-advertise' complete 'show ipv6 bgp community VARIABLE local-AS no-advertise no-advertise exact-match'@2: rv==7 'exact-match' describe 'show ipv6 bgp community VARIABLE local-AS no-advertise no-advertise exact-match'@2: rv==0 'exact-match' 'Exact match of the communities' execute relaxed 'show ipv6 bgp community VARIABLE local-AS no-advertise no-advertise exact-match'@4: rv==0, 'show ipv6 bgp community (AA:NN|local-AS|no-advertise|no-export) (AA:NN|local-AS|no-advertise|no-export) (AA:NN|local-AS|no-advertise|no-export) (AA:NN|local-AS|no-advertise|no-export) exact-match': 'VARIABLE', 'local-AS', 'no-advertise', 'no-advertise' execute strict 'show ipv6 bgp community VARIABLE local-AS no-advertise no-advertise exact-match'@4: rv==0, 'show ipv6 bgp community (AA:NN|local-AS|no-advertise|no-export) (AA:NN|local-AS|no-advertise|no-export) (AA:NN|local-AS|no-advertise|no-export) (AA:NN|local-AS|no-advertise|no-export) exact-match': 'VARIABLE', 'local-AS', 'no-advertise', 'no-advertise' complete 'show ipv6 bgp community VARIABLE local-AS no-advertise no-advertise exact-match'@4: rv==7 'exact-match' describe 'show ipv6 bgp community VARIABLE local-AS no-advertise no-advertise exact-match'@4: rv==0 'exact-match' 'Exact match of the communities' execute relaxed 'show ipv6 mbgp community local-AS local-AS local-AS VARIABLE exact-match'@2: rv==0, 'show ipv6 mbgp community (AA:NN|local-AS|no-advertise|no-export) (AA:NN|local-AS|no-advertise|no-export) (AA:NN|local-AS|no-advertise|no-export) (AA:NN|local-AS|no-advertise|no-export) exact-match': 'local-AS', 'local-AS', 'local-AS', 'VARIABLE' execute strict 'show ipv6 mbgp community local-AS local-AS local-AS VARIABLE exact-match'@2: rv==0, 'show ipv6 mbgp community (AA:NN|local-AS|no-advertise|no-export) (AA:NN|local-AS|no-advertise|no-export) (AA:NN|local-AS|no-advertise|no-export) (AA:NN|local-AS|no-advertise|no-export) exact-match': 'local-AS', 'local-AS', 'local-AS', 'VARIABLE' complete 'show ipv6 mbgp community local-AS local-AS local-AS VARIABLE exact-match'@2: rv==7 'exact-match' describe 'show ipv6 mbgp community local-AS local-AS local-AS VARIABLE exact-match'@2: rv==0 'exact-match' 'Exact match of the communities' execute relaxed 'show ipv6 mbgp community local-AS local-AS local-AS VARIABLE exact-match'@4: rv==0, 'show ipv6 mbgp community (AA:NN|local-AS|no-advertise|no-export) (AA:NN|local-AS|no-advertise|no-export) (AA:NN|local-AS|no-advertise|no-export) (AA:NN|local-AS|no-advertise|no-export) exact-match': 'local-AS', 'local-AS', 'local-AS', 'VARIABLE' execute strict 'show ipv6 mbgp community local-AS local-AS local-AS VARIABLE exact-match'@4: rv==0, 'show ipv6 mbgp community (AA:NN|local-AS|no-advertise|no-export) (AA:NN|local-AS|no-advertise|no-export) (AA:NN|local-AS|no-advertise|no-export) (AA:NN|local-AS|no-advertise|no-export) exact-match': 'local-AS', 'local-AS', 'local-AS', 'VARIABLE' complete 'show ipv6 mbgp community local-AS local-AS local-AS VARIABLE exact-match'@4: rv==7 'exact-match' describe 'show ipv6 mbgp community local-AS local-AS local-AS VARIABLE exact-match'@4: rv==0 'exact-match' 'Exact match of the communities' execute relaxed 'show ipv6 mbgp community local-AS local-AS no-export no-export exact-match'@2: rv==0, 'show ipv6 mbgp community (AA:NN|local-AS|no-advertise|no-export) (AA:NN|local-AS|no-advertise|no-export) (AA:NN|local-AS|no-advertise|no-export) (AA:NN|local-AS|no-advertise|no-export) exact-match': 'local-AS', 'local-AS', 'no-export', 'no-export' execute strict 'show ipv6 mbgp community local-AS local-AS no-export no-export exact-match'@2: rv==0, 'show ipv6 mbgp community (AA:NN|local-AS|no-advertise|no-export) (AA:NN|local-AS|no-advertise|no-export) (AA:NN|local-AS|no-advertise|no-export) (AA:NN|local-AS|no-advertise|no-export) exact-match': 'local-AS', 'local-AS', 'no-export', 'no-export' complete 'show ipv6 mbgp community local-AS local-AS no-export no-export exact-match'@2: rv==7 'exact-match' describe 'show ipv6 mbgp community local-AS local-AS no-export no-export exact-match'@2: rv==0 'exact-match' 'Exact match of the communities' execute relaxed 'show ipv6 mbgp community local-AS local-AS no-export no-export exact-match'@4: rv==0, 'show ipv6 mbgp community (AA:NN|local-AS|no-advertise|no-export) (AA:NN|local-AS|no-advertise|no-export) (AA:NN|local-AS|no-advertise|no-export) (AA:NN|local-AS|no-advertise|no-export) exact-match': 'local-AS', 'local-AS', 'no-export', 'no-export' execute strict 'show ipv6 mbgp community local-AS local-AS no-export no-export exact-match'@4: rv==0, 'show ipv6 mbgp community (AA:NN|local-AS|no-advertise|no-export) (AA:NN|local-AS|no-advertise|no-export) (AA:NN|local-AS|no-advertise|no-export) (AA:NN|local-AS|no-advertise|no-export) exact-match': 'local-AS', 'local-AS', 'no-export', 'no-export' complete 'show ipv6 mbgp community local-AS local-AS no-export no-export exact-match'@4: rv==7 'exact-match' describe 'show ipv6 mbgp community local-AS local-AS no-export no-export exact-match'@4: rv==0 'exact-match' 'Exact match of the communities' execute relaxed 'show ipv6 mbgp community no-export no-export local-AS no-export exact-match'@2: rv==0, 'show ipv6 mbgp community (AA:NN|local-AS|no-advertise|no-export) (AA:NN|local-AS|no-advertise|no-export) (AA:NN|local-AS|no-advertise|no-export) (AA:NN|local-AS|no-advertise|no-export) exact-match': 'no-export', 'no-export', 'local-AS', 'no-export' execute strict 'show ipv6 mbgp community no-export no-export local-AS no-export exact-match'@2: rv==0, 'show ipv6 mbgp community (AA:NN|local-AS|no-advertise|no-export) (AA:NN|local-AS|no-advertise|no-export) (AA:NN|local-AS|no-advertise|no-export) (AA:NN|local-AS|no-advertise|no-export) exact-match': 'no-export', 'no-export', 'local-AS', 'no-export' complete 'show ipv6 mbgp community no-export no-export local-AS no-export exact-match'@2: rv==7 'exact-match' describe 'show ipv6 mbgp community no-export no-export local-AS no-export exact-match'@2: rv==0 'exact-match' 'Exact match of the communities' execute relaxed 'show ipv6 mbgp community no-export no-export local-AS no-export exact-match'@4: rv==0, 'show ipv6 mbgp community (AA:NN|local-AS|no-advertise|no-export) (AA:NN|local-AS|no-advertise|no-export) (AA:NN|local-AS|no-advertise|no-export) (AA:NN|local-AS|no-advertise|no-export) exact-match': 'no-export', 'no-export', 'local-AS', 'no-export' execute strict 'show ipv6 mbgp community no-export no-export local-AS no-export exact-match'@4: rv==0, 'show ipv6 mbgp community (AA:NN|local-AS|no-advertise|no-export) (AA:NN|local-AS|no-advertise|no-export) (AA:NN|local-AS|no-advertise|no-export) (AA:NN|local-AS|no-advertise|no-export) exact-match': 'no-export', 'no-export', 'local-AS', 'no-export' complete 'show ipv6 mbgp community no-export no-export local-AS no-export exact-match'@4: rv==7 'exact-match' describe 'show ipv6 mbgp community no-export no-export local-AS no-export exact-match'@4: rv==0 'exact-match' 'Exact match of the communities' execute relaxed 'show ipv6 ospf6 database as-external dump'@2: rv==0, 'show ipv6 ospf6 database (router|network|inter-prefix|inter-router|as-external|group-membership|type-7|link|intra-prefix) (detail|dump|internal)': 'as-external', 'dump' execute strict 'show ipv6 ospf6 database as-external dump'@2: rv==0, 'show ipv6 ospf6 database (router|network|inter-prefix|inter-router|as-external|group-membership|type-7|link|intra-prefix) (detail|dump|internal)': 'as-external', 'dump' complete 'show ipv6 ospf6 database as-external dump'@2: rv==7 'dump' describe 'show ipv6 ospf6 database as-external dump'@2: rv==0 'dump' 'Dump LSAs' execute relaxed 'show ipv6 ospf6 database as-external dump'@4: rv==0, 'show ipv6 ospf6 database (router|network|inter-prefix|inter-router|as-external|group-membership|type-7|link|intra-prefix) (detail|dump|internal)': 'as-external', 'dump' execute strict 'show ipv6 ospf6 database as-external dump'@4: rv==0, 'show ipv6 ospf6 database (router|network|inter-prefix|inter-router|as-external|group-membership|type-7|link|intra-prefix) (detail|dump|internal)': 'as-external', 'dump' complete 'show ipv6 ospf6 database as-external dump'@4: rv==7 'dump' describe 'show ipv6 ospf6 database as-external dump'@4: rv==0 'dump' 'Dump LSAs' execute relaxed 'show ipv6 ospf6 database inter-prefix 1.2.3.4 detail'@2: rv==0, 'show ipv6 ospf6 database (router|network|inter-prefix|inter-router|as-external|group-membership|type-7|link|intra-prefix) A.B.C.D (detail|dump|internal)': 'inter-prefix', '1.2.3.4', 'detail' execute strict 'show ipv6 ospf6 database inter-prefix 1.2.3.4 detail'@2: rv==0, 'show ipv6 ospf6 database (router|network|inter-prefix|inter-router|as-external|group-membership|type-7|link|intra-prefix) A.B.C.D (detail|dump|internal)': 'inter-prefix', '1.2.3.4', 'detail' complete 'show ipv6 ospf6 database inter-prefix 1.2.3.4 detail'@2: rv==7 'detail' describe 'show ipv6 ospf6 database inter-prefix 1.2.3.4 detail'@2: rv==0 'detail' 'Display details of LSAs' execute relaxed 'show ipv6 ospf6 database inter-prefix 1.2.3.4 detail'@4: rv==0, 'show ipv6 ospf6 database (router|network|inter-prefix|inter-router|as-external|group-membership|type-7|link|intra-prefix) A.B.C.D (detail|dump|internal)': 'inter-prefix', '1.2.3.4', 'detail' execute strict 'show ipv6 ospf6 database inter-prefix 1.2.3.4 detail'@4: rv==0, 'show ipv6 ospf6 database (router|network|inter-prefix|inter-router|as-external|group-membership|type-7|link|intra-prefix) A.B.C.D (detail|dump|internal)': 'inter-prefix', '1.2.3.4', 'detail' complete 'show ipv6 ospf6 database inter-prefix 1.2.3.4 detail'@4: rv==7 'detail' describe 'show ipv6 ospf6 database inter-prefix 1.2.3.4 detail'@4: rv==0 'detail' 'Display details of LSAs' execute relaxed 'show ipv6 ospf6 database intra-prefix 1.2.3.4 internal'@2: rv==0, 'show ipv6 ospf6 database (router|network|inter-prefix|inter-router|as-external|group-membership|type-7|link|intra-prefix) A.B.C.D (detail|dump|internal)': 'intra-prefix', '1.2.3.4', 'internal' execute strict 'show ipv6 ospf6 database intra-prefix 1.2.3.4 internal'@2: rv==0, 'show ipv6 ospf6 database (router|network|inter-prefix|inter-router|as-external|group-membership|type-7|link|intra-prefix) A.B.C.D (detail|dump|internal)': 'intra-prefix', '1.2.3.4', 'internal' complete 'show ipv6 ospf6 database intra-prefix 1.2.3.4 internal'@2: rv==7 'internal' describe 'show ipv6 ospf6 database intra-prefix 1.2.3.4 internal'@2: rv==0 'internal' 'Display LSA's internal information' execute relaxed 'show ipv6 ospf6 database intra-prefix 1.2.3.4 internal'@4: rv==0, 'show ipv6 ospf6 database (router|network|inter-prefix|inter-router|as-external|group-membership|type-7|link|intra-prefix) A.B.C.D (detail|dump|internal)': 'intra-prefix', '1.2.3.4', 'internal' execute strict 'show ipv6 ospf6 database intra-prefix 1.2.3.4 internal'@4: rv==0, 'show ipv6 ospf6 database (router|network|inter-prefix|inter-router|as-external|group-membership|type-7|link|intra-prefix) A.B.C.D (detail|dump|internal)': 'intra-prefix', '1.2.3.4', 'internal' complete 'show ipv6 ospf6 database intra-prefix 1.2.3.4 internal'@4: rv==7 'internal' describe 'show ipv6 ospf6 database intra-prefix 1.2.3.4 internal'@4: rv==0 'internal' 'Display LSA's internal information' execute relaxed 'area 1.2.3.4 virtual-link 1.2.3.4 hello-interval 1 dead-interva 1 retransmit-interval 1 transmit-delay 1'@23: rv==0, 'area (A.B.C.D|<0-4294967295>) virtual-link A.B.C.D (hello-interval|retransmit-interval|transmit-delay|dead-interval) <1-65535> (hello-interval|retransmit-interval|transmit-delay|dead-interval) <1-65535> (hello-interval|retransmit-interval|transmit-delay|dead-interval) <1-65535> (hello-interval|retransmit-interval|transmit-delay|dead-interval) <1-65535>': '1.2.3.4', '1.2.3.4', 'hello-interval', '1', 'dead-interva', '1', 'retransmit-interval', '1', 'transmit-delay', '1' execute strict 'area 1.2.3.4 virtual-link 1.2.3.4 hello-interval 1 dead-interva 1 retransmit-interval 1 transmit-delay 1'@23: rv==2 complete 'area 1.2.3.4 virtual-link 1.2.3.4 hello-interval 1 dead-interva 1 retransmit-interval 1 transmit-delay 1'@23: rv==2 describe 'area 1.2.3.4 virtual-link 1.2.3.4 hello-interval 1 dead-interva 1 retransmit-interval 1 transmit-delay 1'@23: rv==0 '<1-65535>' 'Seconds' execute relaxed 'show bgp community VARIABL no-agdvertise locl-AS no-advertise exact-match'@1: rv==0, 'show bgp community (AA:NN|local-AS|no-advertise|no-export) (AA:NN|local-AS|no-advertise|no-export) (AA:NN|local-AS|no-advertise|no-export) (AA:NN|local-AS|no-advertise|no-export) exact-match': 'VARIABL', 'no-agdvertise', 'locl-AS', 'no-advertise' execute strict 'show bgp community VARIABL no-agdvertise locl-AS no-advertise exact-match'@1: rv==0, 'show bgp community (AA:NN|local-AS|no-advertise|no-export) (AA:NN|local-AS|no-advertise|no-export) (AA:NN|local-AS|no-advertise|no-export) (AA:NN|local-AS|no-advertise|no-export) exact-match': 'VARIABL', 'no-agdvertise', 'locl-AS', 'no-advertise' complete 'show bgp community VARIABL no-agdvertise locl-AS no-advertise exact-match'@1: rv==7 'exact-match' describe 'show bgp community VARIABL no-agdvertise locl-AS no-advertise exact-match'@1: rv==0 'exact-match' 'Exact match of the communities' execute relaxed 'show bgp community VARIABL no-agdvertise locl-AS no-advertise exact-match'@2: rv==0, 'show bgp community (AA:NN|local-AS|no-advertise|no-export) (AA:NN|local-AS|no-advertise|no-export) (AA:NN|local-AS|no-advertise|no-export) (AA:NN|local-AS|no-advertise|no-export) exact-match': 'VARIABL', 'no-agdvertise', 'locl-AS', 'no-advertise' execute strict 'show bgp community VARIABL no-agdvertise locl-AS no-advertise exact-match'@2: rv==0, 'show bgp community (AA:NN|local-AS|no-advertise|no-export) (AA:NN|local-AS|no-advertise|no-export) (AA:NN|local-AS|no-advertise|no-export) (AA:NN|local-AS|no-advertise|no-export) exact-match': 'VARIABL', 'no-agdvertise', 'locl-AS', 'no-advertise' complete 'show bgp community VARIABL no-agdvertise locl-AS no-advertise exact-match'@2: rv==7 'exact-match' describe 'show bgp community VARIABL no-agdvertise locl-AS no-advertise exact-match'@2: rv==0 'exact-match' 'Exact match of the communities' execute relaxed 'show bgp community VARIABL no-agdvertise locl-AS no-advertise exact-match'@4: rv==0, 'show bgp community (AA:NN|local-AS|no-advertise|no-export) (AA:NN|local-AS|no-advertise|no-export) (AA:NN|local-AS|no-advertise|no-export) (AA:NN|local-AS|no-advertise|no-export) exact-match': 'VARIABL', 'no-agdvertise', 'locl-AS', 'no-advertise' execute strict 'show bgp community VARIABL no-agdvertise locl-AS no-advertise exact-match'@4: rv==0, 'show bgp community (AA:NN|local-AS|no-advertise|no-export) (AA:NN|local-AS|no-advertise|no-export) (AA:NN|local-AS|no-advertise|no-export) (AA:NN|local-AS|no-advertise|no-export) exact-match': 'VARIABL', 'no-agdvertise', 'locl-AS', 'no-advertise' complete 'show bgp community VARIABL no-agdvertise locl-AS no-advertise exact-match'@4: rv==7 'exact-match' describe 'show bgp community VARIABL no-agdvertise locl-AS no-advertise exact-match'@4: rv==0 'exact-match' 'Exact match of the communities' execute relaxed 'show bgp ipv6 community local-AS no-expor no-xport VARIABCLE'@1: rv==0, 'show bgp ipv6 community (AA:NN|local-AS|no-advertise|no-export) (AA:NN|local-AS|no-advertise|no-export) (AA:NN|local-AS|no-advertise|no-export) (AA:NN|local-AS|no-advertise|no-export)': 'local-AS', 'no-expor', 'no-xport', 'VARIABCLE' execute strict 'show bgp ipv6 community local-AS no-expor no-xport VARIABCLE'@1: rv==0, 'show bgp ipv6 community (AA:NN|local-AS|no-advertise|no-export) (AA:NN|local-AS|no-advertise|no-export) (AA:NN|local-AS|no-advertise|no-export) (AA:NN|local-AS|no-advertise|no-export)': 'local-AS', 'no-expor', 'no-xport', 'VARIABCLE' complete 'show bgp ipv6 community local-AS no-expor no-xport VARIABCLE'@1: rv==2 describe 'show bgp ipv6 community local-AS no-expor no-xport VARIABCLE'@1: rv==0 'AA:NN' 'Community number where AA and NN are <0-65535>' execute relaxed 'show bgp ipv6 community local-AS no-expor no-xport VARIABCLE'@2: rv==0, 'show bgp ipv6 community (AA:NN|local-AS|no-advertise|no-export) (AA:NN|local-AS|no-advertise|no-export) (AA:NN|local-AS|no-advertise|no-export) (AA:NN|local-AS|no-advertise|no-export)': 'local-AS', 'no-expor', 'no-xport', 'VARIABCLE' execute strict 'show bgp ipv6 community local-AS no-expor no-xport VARIABCLE'@2: rv==0, 'show bgp ipv6 community (AA:NN|local-AS|no-advertise|no-export) (AA:NN|local-AS|no-advertise|no-export) (AA:NN|local-AS|no-advertise|no-export) (AA:NN|local-AS|no-advertise|no-export)': 'local-AS', 'no-expor', 'no-xport', 'VARIABCLE' complete 'show bgp ipv6 community local-AS no-expor no-xport VARIABCLE'@2: rv==2 describe 'show bgp ipv6 community local-AS no-expor no-xport VARIABCLE'@2: rv==0 'AA:NN' 'Community number where AA and NN are <0-65535>' execute relaxed 'show bgp ipv6 community local-AS no-expor no-xport VARIABCLE'@4: rv==0, 'show bgp ipv6 community (AA:NN|local-AS|no-advertise|no-export) (AA:NN|local-AS|no-advertise|no-export) (AA:NN|local-AS|no-advertise|no-export) (AA:NN|local-AS|no-advertise|no-export)': 'local-AS', 'no-expor', 'no-xport', 'VARIABCLE' execute strict 'show bgp ipv6 community local-AS no-expor no-xport VARIABCLE'@4: rv==0, 'show bgp ipv6 community (AA:NN|local-AS|no-advertise|no-export) (AA:NN|local-AS|no-advertise|no-export) (AA:NN|local-AS|no-advertise|no-export) (AA:NN|local-AS|no-advertise|no-export)': 'local-AS', 'no-expor', 'no-xport', 'VARIABCLE' complete 'show bgp ipv6 community local-AS no-expor no-xport VARIABCLE'@4: rv==2 describe 'show bgp ipv6 community local-AS no-expor no-xport VARIABCLE'@4: rv==0 'AA:NN' 'Community number where AA and NN are <0-65535>' execute relaxed 'show bgp ipv6 community no-adsertise local-AS no8-advertise VARIABLE'@1: rv==0, 'show bgp ipv6 community (AA:NN|local-AS|no-advertise|no-export) (AA:NN|local-AS|no-advertise|no-export) (AA:NN|local-AS|no-advertise|no-export) (AA:NN|local-AS|no-advertise|no-export)': 'no-adsertise', 'local-AS', 'no8-advertise', 'VARIABLE' execute strict 'show bgp ipv6 community no-adsertise local-AS no8-advertise VARIABLE'@1: rv==0, 'show bgp ipv6 community (AA:NN|local-AS|no-advertise|no-export) (AA:NN|local-AS|no-advertise|no-export) (AA:NN|local-AS|no-advertise|no-export) (AA:NN|local-AS|no-advertise|no-export)': 'no-adsertise', 'local-AS', 'no8-advertise', 'VARIABLE' complete 'show bgp ipv6 community no-adsertise local-AS no8-advertise VARIABLE'@1: rv==2 describe 'show bgp ipv6 community no-adsertise local-AS no8-advertise VARIABLE'@1: rv==0 'AA:NN' 'Community number where AA and NN are <0-65535>' execute relaxed 'show bgp ipv6 community no-adsertise local-AS no8-advertise VARIABLE'@2: rv==0, 'show bgp ipv6 community (AA:NN|local-AS|no-advertise|no-export) (AA:NN|local-AS|no-advertise|no-export) (AA:NN|local-AS|no-advertise|no-export) (AA:NN|local-AS|no-advertise|no-export)': 'no-adsertise', 'local-AS', 'no8-advertise', 'VARIABLE' execute strict 'show bgp ipv6 community no-adsertise local-AS no8-advertise VARIABLE'@2: rv==0, 'show bgp ipv6 community (AA:NN|local-AS|no-advertise|no-export) (AA:NN|local-AS|no-advertise|no-export) (AA:NN|local-AS|no-advertise|no-export) (AA:NN|local-AS|no-advertise|no-export)': 'no-adsertise', 'local-AS', 'no8-advertise', 'VARIABLE' complete 'show bgp ipv6 community no-adsertise local-AS no8-advertise VARIABLE'@2: rv==2 describe 'show bgp ipv6 community no-adsertise local-AS no8-advertise VARIABLE'@2: rv==0 'AA:NN' 'Community number where AA and NN are <0-65535>' execute relaxed 'show bgp ipv6 community no-adsertise local-AS no8-advertise VARIABLE'@4: rv==0, 'show bgp ipv6 community (AA:NN|local-AS|no-advertise|no-export) (AA:NN|local-AS|no-advertise|no-export) (AA:NN|local-AS|no-advertise|no-export) (AA:NN|local-AS|no-advertise|no-export)': 'no-adsertise', 'local-AS', 'no8-advertise', 'VARIABLE' execute strict 'show bgp ipv6 community no-adsertise local-AS no8-advertise VARIABLE'@4: rv==0, 'show bgp ipv6 community (AA:NN|local-AS|no-advertise|no-export) (AA:NN|local-AS|no-advertise|no-export) (AA:NN|local-AS|no-advertise|no-export) (AA:NN|local-AS|no-advertise|no-export)': 'no-adsertise', 'local-AS', 'no8-advertise', 'VARIABLE' complete 'show bgp ipv6 community no-adsertise local-AS no8-advertise VARIABLE'@4: rv==2 describe 'show bgp ipv6 community no-adsertise local-AS no8-advertise VARIABLE'@4: rv==0 'AA:NN' 'Community number where AA and NN are <0-65535>' execute relaxed 'show bgp ipv6 community no-advertise no6-export lcal-AS local-AS'@1: rv==0, 'show bgp ipv6 community (AA:NN|local-AS|no-advertise|no-export) (AA:NN|local-AS|no-advertise|no-export) (AA:NN|local-AS|no-advertise|no-export) (AA:NN|local-AS|no-advertise|no-export)': 'no-advertise', 'no6-export', 'lcal-AS', 'local-AS' execute strict 'show bgp ipv6 community no-advertise no6-export lcal-AS local-AS'@1: rv==0, 'show bgp ipv6 community (AA:NN|local-AS|no-advertise|no-export) (AA:NN|local-AS|no-advertise|no-export) (AA:NN|local-AS|no-advertise|no-export) (AA:NN|local-AS|no-advertise|no-export)': 'no-advertise', 'no6-export', 'lcal-AS', 'local-AS' complete 'show bgp ipv6 community no-advertise no6-export lcal-AS local-AS'@1: rv==7 'local-AS' describe 'show bgp ipv6 community no-advertise no6-export lcal-AS local-AS'@1: rv==0 'AA:NN' 'Community number where AA and NN are <0-65535>' 'local-AS' 'Do not send outside local AS (well-known community)' execute relaxed 'show bgp ipv6 community no-advertise no6-export lcal-AS local-AS'@2: rv==0, 'show bgp ipv6 community (AA:NN|local-AS|no-advertise|no-export) (AA:NN|local-AS|no-advertise|no-export) (AA:NN|local-AS|no-advertise|no-export) (AA:NN|local-AS|no-advertise|no-export)': 'no-advertise', 'no6-export', 'lcal-AS', 'local-AS' execute strict 'show bgp ipv6 community no-advertise no6-export lcal-AS local-AS'@2: rv==0, 'show bgp ipv6 community (AA:NN|local-AS|no-advertise|no-export) (AA:NN|local-AS|no-advertise|no-export) (AA:NN|local-AS|no-advertise|no-export) (AA:NN|local-AS|no-advertise|no-export)': 'no-advertise', 'no6-export', 'lcal-AS', 'local-AS' complete 'show bgp ipv6 community no-advertise no6-export lcal-AS local-AS'@2: rv==7 'local-AS' describe 'show bgp ipv6 community no-advertise no6-export lcal-AS local-AS'@2: rv==0 'AA:NN' 'Community number where AA and NN are <0-65535>' 'local-AS' 'Do not send outside local AS (well-known community)' execute relaxed 'show bgp ipv6 community no-advertise no6-export lcal-AS local-AS'@4: rv==0, 'show bgp ipv6 community (AA:NN|local-AS|no-advertise|no-export) (AA:NN|local-AS|no-advertise|no-export) (AA:NN|local-AS|no-advertise|no-export) (AA:NN|local-AS|no-advertise|no-export)': 'no-advertise', 'no6-export', 'lcal-AS', 'local-AS' execute strict 'show bgp ipv6 community no-advertise no6-export lcal-AS local-AS'@4: rv==0, 'show bgp ipv6 community (AA:NN|local-AS|no-advertise|no-export) (AA:NN|local-AS|no-advertise|no-export) (AA:NN|local-AS|no-advertise|no-export) (AA:NN|local-AS|no-advertise|no-export)': 'no-advertise', 'no6-export', 'lcal-AS', 'local-AS' complete 'show bgp ipv6 community no-advertise no6-export lcal-AS local-AS'@4: rv==7 'local-AS' describe 'show bgp ipv6 community no-advertise no6-export lcal-AS local-AS'@4: rv==0 'AA:NN' 'Community number where AA and NN are <0-65535>' 'local-AS' 'Do not send outside local AS (well-known community)' execute relaxed 'show bgp ipv6 community no-advertise no-advertise no-advertiseno-xport exact-math'@1: rv==0, 'show bgp ipv6 community (AA:NN|local-AS|no-advertise|no-export) (AA:NN|local-AS|no-advertise|no-export) (AA:NN|local-AS|no-advertise|no-export) (AA:NN|local-AS|no-advertise|no-export)': 'no-advertise', 'no-advertise', 'no-advertiseno-xport', 'exact-math' execute strict 'show bgp ipv6 community no-advertise no-advertise no-advertiseno-xport exact-math'@1: rv==0, 'show bgp ipv6 community (AA:NN|local-AS|no-advertise|no-export) (AA:NN|local-AS|no-advertise|no-export) (AA:NN|local-AS|no-advertise|no-export) (AA:NN|local-AS|no-advertise|no-export)': 'no-advertise', 'no-advertise', 'no-advertiseno-xport', 'exact-math' complete 'show bgp ipv6 community no-advertise no-advertise no-advertiseno-xport exact-math'@1: rv==2 describe 'show bgp ipv6 community no-advertise no-advertise no-advertiseno-xport exact-math'@1: rv==0 'AA:NN' 'Community number where AA and NN are <0-65535>' execute relaxed 'show bgp ipv6 community no-advertise no-advertise no-advertiseno-xport exact-math'@2: rv==0, 'show bgp ipv6 community (AA:NN|local-AS|no-advertise|no-export) (AA:NN|local-AS|no-advertise|no-export) (AA:NN|local-AS|no-advertise|no-export) (AA:NN|local-AS|no-advertise|no-export)': 'no-advertise', 'no-advertise', 'no-advertiseno-xport', 'exact-math' execute strict 'show bgp ipv6 community no-advertise no-advertise no-advertiseno-xport exact-math'@2: rv==0, 'show bgp ipv6 community (AA:NN|local-AS|no-advertise|no-export) (AA:NN|local-AS|no-advertise|no-export) (AA:NN|local-AS|no-advertise|no-export) (AA:NN|local-AS|no-advertise|no-export)': 'no-advertise', 'no-advertise', 'no-advertiseno-xport', 'exact-math' complete 'show bgp ipv6 community no-advertise no-advertise no-advertiseno-xport exact-math'@2: rv==2 describe 'show bgp ipv6 community no-advertise no-advertise no-advertiseno-xport exact-math'@2: rv==0 'AA:NN' 'Community number where AA and NN are <0-65535>' execute relaxed 'show bgp ipv6 community no-advertise no-advertise no-advertiseno-xport exact-math'@4: rv==0, 'show bgp ipv6 community (AA:NN|local-AS|no-advertise|no-export) (AA:NN|local-AS|no-advertise|no-export) (AA:NN|local-AS|no-advertise|no-export) (AA:NN|local-AS|no-advertise|no-export)': 'no-advertise', 'no-advertise', 'no-advertiseno-xport', 'exact-math' execute strict 'show bgp ipv6 community no-advertise no-advertise no-advertiseno-xport exact-math'@4: rv==0, 'show bgp ipv6 community (AA:NN|local-AS|no-advertise|no-export) (AA:NN|local-AS|no-advertise|no-export) (AA:NN|local-AS|no-advertise|no-export) (AA:NN|local-AS|no-advertise|no-export)': 'no-advertise', 'no-advertise', 'no-advertiseno-xport', 'exact-math' complete 'show bgp ipv6 community no-advertise no-advertise no-advertiseno-xport exact-math'@4: rv==2 describe 'show bgp ipv6 community no-advertise no-advertise no-advertiseno-xport exact-math'@4: rv==0 'AA:NN' 'Community number where AA and NN are <0-65535>' execute relaxed 'show bgp ipv6 community no-export local-AS no-adertise no-adve-tie'@1: rv==0, 'show bgp ipv6 community (AA:NN|local-AS|no-advertise|no-export) (AA:NN|local-AS|no-advertise|no-export) (AA:NN|local-AS|no-advertise|no-export) (AA:NN|local-AS|no-advertise|no-export)': 'no-export', 'local-AS', 'no-adertise', 'no-adve-tie' execute strict 'show bgp ipv6 community no-export local-AS no-adertise no-adve-tie'@1: rv==0, 'show bgp ipv6 community (AA:NN|local-AS|no-advertise|no-export) (AA:NN|local-AS|no-advertise|no-export) (AA:NN|local-AS|no-advertise|no-export) (AA:NN|local-AS|no-advertise|no-export)': 'no-export', 'local-AS', 'no-adertise', 'no-adve-tie' complete 'show bgp ipv6 community no-export local-AS no-adertise no-adve-tie'@1: rv==2 describe 'show bgp ipv6 community no-export local-AS no-adertise no-adve-tie'@1: rv==0 'AA:NN' 'Community number where AA and NN are <0-65535>' execute relaxed 'show bgp ipv6 community no-export local-AS no-adertise no-adve-tie'@2: rv==0, 'show bgp ipv6 community (AA:NN|local-AS|no-advertise|no-export) (AA:NN|local-AS|no-advertise|no-export) (AA:NN|local-AS|no-advertise|no-export) (AA:NN|local-AS|no-advertise|no-export)': 'no-export', 'local-AS', 'no-adertise', 'no-adve-tie' execute strict 'show bgp ipv6 community no-export local-AS no-adertise no-adve-tie'@2: rv==0, 'show bgp ipv6 community (AA:NN|local-AS|no-advertise|no-export) (AA:NN|local-AS|no-advertise|no-export) (AA:NN|local-AS|no-advertise|no-export) (AA:NN|local-AS|no-advertise|no-export)': 'no-export', 'local-AS', 'no-adertise', 'no-adve-tie' complete 'show bgp ipv6 community no-export local-AS no-adertise no-adve-tie'@2: rv==2 describe 'show bgp ipv6 community no-export local-AS no-adertise no-adve-tie'@2: rv==0 'AA:NN' 'Community number where AA and NN are <0-65535>' execute relaxed 'show bgp ipv6 community no-export local-AS no-adertise no-adve-tie'@4: rv==0, 'show bgp ipv6 community (AA:NN|local-AS|no-advertise|no-export) (AA:NN|local-AS|no-advertise|no-export) (AA:NN|local-AS|no-advertise|no-export) (AA:NN|local-AS|no-advertise|no-export)': 'no-export', 'local-AS', 'no-adertise', 'no-adve-tie' execute strict 'show bgp ipv6 community no-export local-AS no-adertise no-adve-tie'@4: rv==0, 'show bgp ipv6 community (AA:NN|local-AS|no-advertise|no-export) (AA:NN|local-AS|no-advertise|no-export) (AA:NN|local-AS|no-advertise|no-export) (AA:NN|local-AS|no-advertise|no-export)': 'no-export', 'local-AS', 'no-adertise', 'no-adve-tie' complete 'show bgp ipv6 community no-export local-AS no-adertise no-adve-tie'@4: rv==2 describe 'show bgp ipv6 community no-export local-AS no-adertise no-adve-tie'@4: rv==0 'AA:NN' 'Community number where AA and NN are <0-65535>' execute relaxed 'show bgp ipv6 community yno-advertis VAyRIABLE no-advertise VARIABLE'@1: rv==0, 'show bgp ipv6 community (AA:NN|local-AS|no-advertise|no-export) (AA:NN|local-AS|no-advertise|no-export) (AA:NN|local-AS|no-advertise|no-export) (AA:NN|local-AS|no-advertise|no-export)': 'yno-advertis', 'VAyRIABLE', 'no-advertise', 'VARIABLE' execute strict 'show bgp ipv6 community yno-advertis VAyRIABLE no-advertise VARIABLE'@1: rv==0, 'show bgp ipv6 community (AA:NN|local-AS|no-advertise|no-export) (AA:NN|local-AS|no-advertise|no-export) (AA:NN|local-AS|no-advertise|no-export) (AA:NN|local-AS|no-advertise|no-export)': 'yno-advertis', 'VAyRIABLE', 'no-advertise', 'VARIABLE' complete 'show bgp ipv6 community yno-advertis VAyRIABLE no-advertise VARIABLE'@1: rv==2 describe 'show bgp ipv6 community yno-advertis VAyRIABLE no-advertise VARIABLE'@1: rv==0 'AA:NN' 'Community number where AA and NN are <0-65535>' execute relaxed 'show bgp ipv6 community yno-advertis VAyRIABLE no-advertise VARIABLE'@2: rv==0, 'show bgp ipv6 community (AA:NN|local-AS|no-advertise|no-export) (AA:NN|local-AS|no-advertise|no-export) (AA:NN|local-AS|no-advertise|no-export) (AA:NN|local-AS|no-advertise|no-export)': 'yno-advertis', 'VAyRIABLE', 'no-advertise', 'VARIABLE' execute strict 'show bgp ipv6 community yno-advertis VAyRIABLE no-advertise VARIABLE'@2: rv==0, 'show bgp ipv6 community (AA:NN|local-AS|no-advertise|no-export) (AA:NN|local-AS|no-advertise|no-export) (AA:NN|local-AS|no-advertise|no-export) (AA:NN|local-AS|no-advertise|no-export)': 'yno-advertis', 'VAyRIABLE', 'no-advertise', 'VARIABLE' complete 'show bgp ipv6 community yno-advertis VAyRIABLE no-advertise VARIABLE'@2: rv==2 describe 'show bgp ipv6 community yno-advertis VAyRIABLE no-advertise VARIABLE'@2: rv==0 'AA:NN' 'Community number where AA and NN are <0-65535>' execute relaxed 'show bgp ipv6 community yno-advertis VAyRIABLE no-advertise VARIABLE'@4: rv==0, 'show bgp ipv6 community (AA:NN|local-AS|no-advertise|no-export) (AA:NN|local-AS|no-advertise|no-export) (AA:NN|local-AS|no-advertise|no-export) (AA:NN|local-AS|no-advertise|no-export)': 'yno-advertis', 'VAyRIABLE', 'no-advertise', 'VARIABLE' execute strict 'show bgp ipv6 community yno-advertis VAyRIABLE no-advertise VARIABLE'@4: rv==0, 'show bgp ipv6 community (AA:NN|local-AS|no-advertise|no-export) (AA:NN|local-AS|no-advertise|no-export) (AA:NN|local-AS|no-advertise|no-export) (AA:NN|local-AS|no-advertise|no-export)': 'yno-advertis', 'VAyRIABLE', 'no-advertise', 'VARIABLE' complete 'show bgp ipv6 community yno-advertis VAyRIABLE no-advertise VARIABLE'@4: rv==2 describe 'show bgp ipv6 community yno-advertis VAyRIABLE no-advertise VARIABLE'@4: rv==0 'AA:NN' 'Community number where AA and NN are <0-65535>' execute relaxed 'show bgp view VAR0IABLE ipv4 unicast community local-AS no-advertie no-advertise local-AS'@1: rv==0, 'show bgp view WORD (ipv4|ipv6) (unicast|multicast) community (AA:NN|local-AS|no-advertise|no-export) (AA:NN|local-AS|no-advertise|no-export) (AA:NN|local-AS|no-advertise|no-export) (AA:NN|local-AS|no-advertise|no-export)': 'VAR0IABLE', 'ipv4', 'unicast', 'local-AS', 'no-advertie', 'no-advertise', 'local-AS' execute strict 'show bgp view VAR0IABLE ipv4 unicast community local-AS no-advertie no-advertise local-AS'@1: rv==0, 'show bgp view WORD (ipv4|ipv6) (unicast|multicast) community (AA:NN|local-AS|no-advertise|no-export) (AA:NN|local-AS|no-advertise|no-export) (AA:NN|local-AS|no-advertise|no-export) (AA:NN|local-AS|no-advertise|no-export)': 'VAR0IABLE', 'ipv4', 'unicast', 'local-AS', 'no-advertie', 'no-advertise', 'local-AS' complete 'show bgp view VAR0IABLE ipv4 unicast community local-AS no-advertie no-advertise local-AS'@1: rv==7 'local-AS' describe 'show bgp view VAR0IABLE ipv4 unicast community local-AS no-advertie no-advertise local-AS'@1: rv==0 'AA:NN' 'Community number where AA and NN are <0-65535>' 'local-AS' 'Do not send outside local AS (well-known community)' execute relaxed 'show bgp view VAR0IABLE ipv4 unicast community local-AS no-advertie no-advertise local-AS'@2: rv==0, 'show bgp view WORD (ipv4|ipv6) (unicast|multicast) community (AA:NN|local-AS|no-advertise|no-export) (AA:NN|local-AS|no-advertise|no-export) (AA:NN|local-AS|no-advertise|no-export) (AA:NN|local-AS|no-advertise|no-export)': 'VAR0IABLE', 'ipv4', 'unicast', 'local-AS', 'no-advertie', 'no-advertise', 'local-AS' execute strict 'show bgp view VAR0IABLE ipv4 unicast community local-AS no-advertie no-advertise local-AS'@2: rv==0, 'show bgp view WORD (ipv4|ipv6) (unicast|multicast) community (AA:NN|local-AS|no-advertise|no-export) (AA:NN|local-AS|no-advertise|no-export) (AA:NN|local-AS|no-advertise|no-export) (AA:NN|local-AS|no-advertise|no-export)': 'VAR0IABLE', 'ipv4', 'unicast', 'local-AS', 'no-advertie', 'no-advertise', 'local-AS' complete 'show bgp view VAR0IABLE ipv4 unicast community local-AS no-advertie no-advertise local-AS'@2: rv==7 'local-AS' describe 'show bgp view VAR0IABLE ipv4 unicast community local-AS no-advertie no-advertise local-AS'@2: rv==0 'AA:NN' 'Community number where AA and NN are <0-65535>' 'local-AS' 'Do not send outside local AS (well-known community)' execute relaxed 'show bgp view VAR0IABLE ipv4 unicast community local-AS no-advertie no-advertise local-AS'@4: rv==0, 'show bgp view WORD (ipv4|ipv6) (unicast|multicast) community (AA:NN|local-AS|no-advertise|no-export) (AA:NN|local-AS|no-advertise|no-export) (AA:NN|local-AS|no-advertise|no-export) (AA:NN|local-AS|no-advertise|no-export)': 'VAR0IABLE', 'ipv4', 'unicast', 'local-AS', 'no-advertie', 'no-advertise', 'local-AS' execute strict 'show bgp view VAR0IABLE ipv4 unicast community local-AS no-advertie no-advertise local-AS'@4: rv==0, 'show bgp view WORD (ipv4|ipv6) (unicast|multicast) community (AA:NN|local-AS|no-advertise|no-export) (AA:NN|local-AS|no-advertise|no-export) (AA:NN|local-AS|no-advertise|no-export) (AA:NN|local-AS|no-advertise|no-export)': 'VAR0IABLE', 'ipv4', 'unicast', 'local-AS', 'no-advertie', 'no-advertise', 'local-AS' complete 'show bgp view VAR0IABLE ipv4 unicast community local-AS no-advertie no-advertise local-AS'@4: rv==7 'local-AS' describe 'show bgp view VAR0IABLE ipv4 unicast community local-AS no-advertie no-advertise local-AS'@4: rv==0 'AA:NN' 'Community number where AA and NN are <0-65535>' 'local-AS' 'Do not send outside local AS (well-known community)' execute relaxed 'show bgp view VARIABLE ipv4 multicast community local-AS VARIABLE loqal-AS no-export'@1: rv==0, 'show bgp view WORD (ipv4|ipv6) (unicast|multicast) community (AA:NN|local-AS|no-advertise|no-export) (AA:NN|local-AS|no-advertise|no-export) (AA:NN|local-AS|no-advertise|no-export) (AA:NN|local-AS|no-advertise|no-export)': 'VARIABLE', 'ipv4', 'multicast', 'local-AS', 'VARIABLE', 'loqal-AS', 'no-export' execute strict 'show bgp view VARIABLE ipv4 multicast community local-AS VARIABLE loqal-AS no-export'@1: rv==0, 'show bgp view WORD (ipv4|ipv6) (unicast|multicast) community (AA:NN|local-AS|no-advertise|no-export) (AA:NN|local-AS|no-advertise|no-export) (AA:NN|local-AS|no-advertise|no-export) (AA:NN|local-AS|no-advertise|no-export)': 'VARIABLE', 'ipv4', 'multicast', 'local-AS', 'VARIABLE', 'loqal-AS', 'no-export' complete 'show bgp view VARIABLE ipv4 multicast community local-AS VARIABLE loqal-AS no-export'@1: rv==7 'no-export' describe 'show bgp view VARIABLE ipv4 multicast community local-AS VARIABLE loqal-AS no-export'@1: rv==0 'AA:NN' 'Community number where AA and NN are <0-65535>' 'no-export' 'Do not export to next AS (well-known community)' execute relaxed 'show bgp view VARIABLE ipv4 multicast community local-AS VARIABLE loqal-AS no-export'@2: rv==0, 'show bgp view WORD (ipv4|ipv6) (unicast|multicast) community (AA:NN|local-AS|no-advertise|no-export) (AA:NN|local-AS|no-advertise|no-export) (AA:NN|local-AS|no-advertise|no-export) (AA:NN|local-AS|no-advertise|no-export)': 'VARIABLE', 'ipv4', 'multicast', 'local-AS', 'VARIABLE', 'loqal-AS', 'no-export' execute strict 'show bgp view VARIABLE ipv4 multicast community local-AS VARIABLE loqal-AS no-export'@2: rv==0, 'show bgp view WORD (ipv4|ipv6) (unicast|multicast) community (AA:NN|local-AS|no-advertise|no-export) (AA:NN|local-AS|no-advertise|no-export) (AA:NN|local-AS|no-advertise|no-export) (AA:NN|local-AS|no-advertise|no-export)': 'VARIABLE', 'ipv4', 'multicast', 'local-AS', 'VARIABLE', 'loqal-AS', 'no-export' complete 'show bgp view VARIABLE ipv4 multicast community local-AS VARIABLE loqal-AS no-export'@2: rv==7 'no-export' describe 'show bgp view VARIABLE ipv4 multicast community local-AS VARIABLE loqal-AS no-export'@2: rv==0 'AA:NN' 'Community number where AA and NN are <0-65535>' 'no-export' 'Do not export to next AS (well-known community)' execute relaxed 'show bgp view VARIABLE ipv4 multicast community local-AS VARIABLE loqal-AS no-export'@4: rv==0, 'show bgp view WORD (ipv4|ipv6) (unicast|multicast) community (AA:NN|local-AS|no-advertise|no-export) (AA:NN|local-AS|no-advertise|no-export) (AA:NN|local-AS|no-advertise|no-export) (AA:NN|local-AS|no-advertise|no-export)': 'VARIABLE', 'ipv4', 'multicast', 'local-AS', 'VARIABLE', 'loqal-AS', 'no-export' execute strict 'show bgp view VARIABLE ipv4 multicast community local-AS VARIABLE loqal-AS no-export'@4: rv==0, 'show bgp view WORD (ipv4|ipv6) (unicast|multicast) community (AA:NN|local-AS|no-advertise|no-export) (AA:NN|local-AS|no-advertise|no-export) (AA:NN|local-AS|no-advertise|no-export) (AA:NN|local-AS|no-advertise|no-export)': 'VARIABLE', 'ipv4', 'multicast', 'local-AS', 'VARIABLE', 'loqal-AS', 'no-export' complete 'show bgp view VARIABLE ipv4 multicast community local-AS VARIABLE loqal-AS no-export'@4: rv==7 'no-export' describe 'show bgp view VARIABLE ipv4 multicast community local-AS VARIABLE loqal-AS no-export'@4: rv==0 'AA:NN' 'Community number where AA and NN are <0-65535>' 'no-export' 'Do not export to next AS (well-known community)' execute relaxed 'show ip bgp community no-expor VARIABLEono-export VARIAuBLE'@1: rv==0, 'show ip bgp community (AA:NN|local-AS|no-advertise|no-export) (AA:NN|local-AS|no-advertise|no-export) (AA:NN|local-AS|no-advertise|no-export)': 'no-expor', 'VARIABLEono-export', 'VARIAuBLE' execute strict 'show ip bgp community no-expor VARIABLEono-export VARIAuBLE'@1: rv==0, 'show ip bgp community (AA:NN|local-AS|no-advertise|no-export) (AA:NN|local-AS|no-advertise|no-export) (AA:NN|local-AS|no-advertise|no-export)': 'no-expor', 'VARIABLEono-export', 'VARIAuBLE' complete 'show ip bgp community no-expor VARIABLEono-export VARIAuBLE'@1: rv==2 describe 'show ip bgp community no-expor VARIABLEono-export VARIAuBLE'@1: rv==0 'AA:NN' 'Community number where AA and NN are <0-65535>' execute relaxed 'show ip bgp community no-expor VARIABLEono-export VARIAuBLE'@2: rv==0, 'show ip bgp community (AA:NN|local-AS|no-advertise|no-export) (AA:NN|local-AS|no-advertise|no-export) (AA:NN|local-AS|no-advertise|no-export)': 'no-expor', 'VARIABLEono-export', 'VARIAuBLE' execute strict 'show ip bgp community no-expor VARIABLEono-export VARIAuBLE'@2: rv==0, 'show ip bgp community (AA:NN|local-AS|no-advertise|no-export) (AA:NN|local-AS|no-advertise|no-export) (AA:NN|local-AS|no-advertise|no-export)': 'no-expor', 'VARIABLEono-export', 'VARIAuBLE' complete 'show ip bgp community no-expor VARIABLEono-export VARIAuBLE'@2: rv==2 describe 'show ip bgp community no-expor VARIABLEono-export VARIAuBLE'@2: rv==0 'AA:NN' 'Community number where AA and NN are <0-65535>' execute relaxed 'show ip bgp community no-expor VARIABLEono-export VARIAuBLE'@4: rv==0, 'show ip bgp community (AA:NN|local-AS|no-advertise|no-export) (AA:NN|local-AS|no-advertise|no-export) (AA:NN|local-AS|no-advertise|no-export)': 'no-expor', 'VARIABLEono-export', 'VARIAuBLE' execute strict 'show ip bgp community no-expor VARIABLEono-export VARIAuBLE'@4: rv==0, 'show ip bgp community (AA:NN|local-AS|no-advertise|no-export) (AA:NN|local-AS|no-advertise|no-export) (AA:NN|local-AS|no-advertise|no-export)': 'no-expor', 'VARIABLEono-export', 'VARIAuBLE' complete 'show ip bgp community no-expor VARIABLEono-export VARIAuBLE'@4: rv==2 describe 'show ip bgp community no-expor VARIABLEono-export VARIAuBLE'@4: rv==0 'AA:NN' 'Community number where AA and NN are <0-65535>' execute relaxed 'show ip bgp community VARIABLElocal-AS no-advertise local-AS xack-match'@1: rv==0, 'show ip bgp community (AA:NN|local-AS|no-advertise|no-export) (AA:NN|local-AS|no-advertise|no-export) (AA:NN|local-AS|no-advertise|no-export) (AA:NN|local-AS|no-advertise|no-export)': 'VARIABLElocal-AS', 'no-advertise', 'local-AS', 'xack-match' execute strict 'show ip bgp community VARIABLElocal-AS no-advertise local-AS xack-match'@1: rv==0, 'show ip bgp community (AA:NN|local-AS|no-advertise|no-export) (AA:NN|local-AS|no-advertise|no-export) (AA:NN|local-AS|no-advertise|no-export) (AA:NN|local-AS|no-advertise|no-export)': 'VARIABLElocal-AS', 'no-advertise', 'local-AS', 'xack-match' complete 'show ip bgp community VARIABLElocal-AS no-advertise local-AS xack-match'@1: rv==2 describe 'show ip bgp community VARIABLElocal-AS no-advertise local-AS xack-match'@1: rv==0 'AA:NN' 'Community number where AA and NN are <0-65535>' execute relaxed 'show ip bgp community VARIABLElocal-AS no-advertise local-AS xack-match'@2: rv==0, 'show ip bgp community (AA:NN|local-AS|no-advertise|no-export) (AA:NN|local-AS|no-advertise|no-export) (AA:NN|local-AS|no-advertise|no-export) (AA:NN|local-AS|no-advertise|no-export)': 'VARIABLElocal-AS', 'no-advertise', 'local-AS', 'xack-match' execute strict 'show ip bgp community VARIABLElocal-AS no-advertise local-AS xack-match'@2: rv==0, 'show ip bgp community (AA:NN|local-AS|no-advertise|no-export) (AA:NN|local-AS|no-advertise|no-export) (AA:NN|local-AS|no-advertise|no-export) (AA:NN|local-AS|no-advertise|no-export)': 'VARIABLElocal-AS', 'no-advertise', 'local-AS', 'xack-match' complete 'show ip bgp community VARIABLElocal-AS no-advertise local-AS xack-match'@2: rv==2 describe 'show ip bgp community VARIABLElocal-AS no-advertise local-AS xack-match'@2: rv==0 'AA:NN' 'Community number where AA and NN are <0-65535>' execute relaxed 'show ip bgp community VARIABLElocal-AS no-advertise local-AS xack-match'@4: rv==0, 'show ip bgp community (AA:NN|local-AS|no-advertise|no-export) (AA:NN|local-AS|no-advertise|no-export) (AA:NN|local-AS|no-advertise|no-export) (AA:NN|local-AS|no-advertise|no-export)': 'VARIABLElocal-AS', 'no-advertise', 'local-AS', 'xack-match' execute strict 'show ip bgp community VARIABLElocal-AS no-advertise local-AS xack-match'@4: rv==0, 'show ip bgp community (AA:NN|local-AS|no-advertise|no-export) (AA:NN|local-AS|no-advertise|no-export) (AA:NN|local-AS|no-advertise|no-export) (AA:NN|local-AS|no-advertise|no-export)': 'VARIABLElocal-AS', 'no-advertise', 'local-AS', 'xack-match' complete 'show ip bgp community VARIABLElocal-AS no-advertise local-AS xack-match'@4: rv==2 describe 'show ip bgp community VARIABLElocal-AS no-advertise local-AS xack-match'@4: rv==0 'AA:NN' 'Community number where AA and NN are <0-65535>' execute relaxed 'show ip bgp ipv4 unicast community no-advertise no-export no-advrtWise mno-export exact-match'@1: rv==0, 'show ip bgp ipv4 (unicast|multicast) community (AA:NN|local-AS|no-advertise|no-export) (AA:NN|local-AS|no-advertise|no-export) (AA:NN|local-AS|no-advertise|no-export) (AA:NN|local-AS|no-advertise|no-export) exact-match': 'unicast', 'no-advertise', 'no-export', 'no-advrtWise', 'mno-export' execute strict 'show ip bgp ipv4 unicast community no-advertise no-export no-advrtWise mno-export exact-match'@1: rv==0, 'show ip bgp ipv4 (unicast|multicast) community (AA:NN|local-AS|no-advertise|no-export) (AA:NN|local-AS|no-advertise|no-export) (AA:NN|local-AS|no-advertise|no-export) (AA:NN|local-AS|no-advertise|no-export) exact-match': 'unicast', 'no-advertise', 'no-export', 'no-advrtWise', 'mno-export' complete 'show ip bgp ipv4 unicast community no-advertise no-export no-advrtWise mno-export exact-match'@1: rv==7 'exact-match' describe 'show ip bgp ipv4 unicast community no-advertise no-export no-advrtWise mno-export exact-match'@1: rv==0 'exact-match' 'Exact match of the communities' execute relaxed 'show ip bgp ipv4 unicast community no-advertise no-export no-advrtWise mno-export exact-match'@2: rv==0, 'show ip bgp ipv4 (unicast|multicast) community (AA:NN|local-AS|no-advertise|no-export) (AA:NN|local-AS|no-advertise|no-export) (AA:NN|local-AS|no-advertise|no-export) (AA:NN|local-AS|no-advertise|no-export) exact-match': 'unicast', 'no-advertise', 'no-export', 'no-advrtWise', 'mno-export' execute strict 'show ip bgp ipv4 unicast community no-advertise no-export no-advrtWise mno-export exact-match'@2: rv==0, 'show ip bgp ipv4 (unicast|multicast) community (AA:NN|local-AS|no-advertise|no-export) (AA:NN|local-AS|no-advertise|no-export) (AA:NN|local-AS|no-advertise|no-export) (AA:NN|local-AS|no-advertise|no-export) exact-match': 'unicast', 'no-advertise', 'no-export', 'no-advrtWise', 'mno-export' complete 'show ip bgp ipv4 unicast community no-advertise no-export no-advrtWise mno-export exact-match'@2: rv==7 'exact-match' describe 'show ip bgp ipv4 unicast community no-advertise no-export no-advrtWise mno-export exact-match'@2: rv==0 'exact-match' 'Exact match of the communities' execute relaxed 'show ip bgp ipv4 unicast community no-advertise no-export no-advrtWise mno-export exact-match'@4: rv==0, 'show ip bgp ipv4 (unicast|multicast) community (AA:NN|local-AS|no-advertise|no-export) (AA:NN|local-AS|no-advertise|no-export) (AA:NN|local-AS|no-advertise|no-export) (AA:NN|local-AS|no-advertise|no-export) exact-match': 'unicast', 'no-advertise', 'no-export', 'no-advrtWise', 'mno-export' execute strict 'show ip bgp ipv4 unicast community no-advertise no-export no-advrtWise mno-export exact-match'@4: rv==0, 'show ip bgp ipv4 (unicast|multicast) community (AA:NN|local-AS|no-advertise|no-export) (AA:NN|local-AS|no-advertise|no-export) (AA:NN|local-AS|no-advertise|no-export) (AA:NN|local-AS|no-advertise|no-export) exact-match': 'unicast', 'no-advertise', 'no-export', 'no-advrtWise', 'mno-export' complete 'show ip bgp ipv4 unicast community no-advertise no-export no-advrtWise mno-export exact-match'@4: rv==7 'exact-match' describe 'show ip bgp ipv4 unicast community no-advertise no-export no-advrtWise mno-export exact-match'@4: rv==0 'exact-match' 'Exact match of the communities' execute relaxed 'show ipv6 bgp community noeexport VARIABLE VARIABLE no-aMdverise exact-match'@2: rv==0, 'show ipv6 bgp community (AA:NN|local-AS|no-advertise|no-export) (AA:NN|local-AS|no-advertise|no-export) (AA:NN|local-AS|no-advertise|no-export) (AA:NN|local-AS|no-advertise|no-export) exact-match': 'noeexport', 'VARIABLE', 'VARIABLE', 'no-aMdverise' execute strict 'show ipv6 bgp community noeexport VARIABLE VARIABLE no-aMdverise exact-match'@2: rv==0, 'show ipv6 bgp community (AA:NN|local-AS|no-advertise|no-export) (AA:NN|local-AS|no-advertise|no-export) (AA:NN|local-AS|no-advertise|no-export) (AA:NN|local-AS|no-advertise|no-export) exact-match': 'noeexport', 'VARIABLE', 'VARIABLE', 'no-aMdverise' complete 'show ipv6 bgp community noeexport VARIABLE VARIABLE no-aMdverise exact-match'@2: rv==7 'exact-match' describe 'show ipv6 bgp community noeexport VARIABLE VARIABLE no-aMdverise exact-match'@2: rv==0 'exact-match' 'Exact match of the communities' execute relaxed 'show ipv6 bgp community noeexport VARIABLE VARIABLE no-aMdverise exact-match'@4: rv==0, 'show ipv6 bgp community (AA:NN|local-AS|no-advertise|no-export) (AA:NN|local-AS|no-advertise|no-export) (AA:NN|local-AS|no-advertise|no-export) (AA:NN|local-AS|no-advertise|no-export) exact-match': 'noeexport', 'VARIABLE', 'VARIABLE', 'no-aMdverise' execute strict 'show ipv6 bgp community noeexport VARIABLE VARIABLE no-aMdverise exact-match'@4: rv==0, 'show ipv6 bgp community (AA:NN|local-AS|no-advertise|no-export) (AA:NN|local-AS|no-advertise|no-export) (AA:NN|local-AS|no-advertise|no-export) (AA:NN|local-AS|no-advertise|no-export) exact-match': 'noeexport', 'VARIABLE', 'VARIABLE', 'no-aMdverise' complete 'show ipv6 bgp community noeexport VARIABLE VARIABLE no-aMdverise exact-match'@4: rv==7 'exact-match' describe 'show ipv6 bgp community noeexport VARIABLE VARIABLE no-aMdverise exact-match'@4: rv==0 'exact-match' 'Exact match of the communities' execute relaxed 'show ipv6 bgp community no-export loal-AS noK-advertise VARIABLE'@2: rv==0, 'show ipv6 bgp community (AA:NN|local-AS|no-advertise|no-export) (AA:NN|local-AS|no-advertise|no-export) (AA:NN|local-AS|no-advertise|no-export) (AA:NN|local-AS|no-advertise|no-export)': 'no-export', 'loal-AS', 'noK-advertise', 'VARIABLE' execute strict 'show ipv6 bgp community no-export loal-AS noK-advertise VARIABLE'@2: rv==0, 'show ipv6 bgp community (AA:NN|local-AS|no-advertise|no-export) (AA:NN|local-AS|no-advertise|no-export) (AA:NN|local-AS|no-advertise|no-export) (AA:NN|local-AS|no-advertise|no-export)': 'no-export', 'loal-AS', 'noK-advertise', 'VARIABLE' complete 'show ipv6 bgp community no-export loal-AS noK-advertise VARIABLE'@2: rv==2 describe 'show ipv6 bgp community no-export loal-AS noK-advertise VARIABLE'@2: rv==0 'AA:NN' 'Community number where AA and NN are <0-65535>' execute relaxed 'show ipv6 bgp community no-export loal-AS noK-advertise VARIABLE'@4: rv==0, 'show ipv6 bgp community (AA:NN|local-AS|no-advertise|no-export) (AA:NN|local-AS|no-advertise|no-export) (AA:NN|local-AS|no-advertise|no-export) (AA:NN|local-AS|no-advertise|no-export)': 'no-export', 'loal-AS', 'noK-advertise', 'VARIABLE' execute strict 'show ipv6 bgp community no-export loal-AS noK-advertise VARIABLE'@4: rv==0, 'show ipv6 bgp community (AA:NN|local-AS|no-advertise|no-export) (AA:NN|local-AS|no-advertise|no-export) (AA:NN|local-AS|no-advertise|no-export) (AA:NN|local-AS|no-advertise|no-export)': 'no-export', 'loal-AS', 'noK-advertise', 'VARIABLE' complete 'show ipv6 bgp community no-export loal-AS noK-advertise VARIABLE'@4: rv==2 describe 'show ipv6 bgp community no-export loal-AS noK-advertise VARIABLE'@4: rv==0 'AA:NN' 'Community number where AA and NN are <0-65535>' execute relaxed 'show ipv6 bgp community VARIABaE no-export no-adertise lo0cal-AS exact-match'@2: rv==0, 'show ipv6 bgp community (AA:NN|local-AS|no-advertise|no-export) (AA:NN|local-AS|no-advertise|no-export) (AA:NN|local-AS|no-advertise|no-export) (AA:NN|local-AS|no-advertise|no-export) exact-match': 'VARIABaE', 'no-export', 'no-adertise', 'lo0cal-AS' execute strict 'show ipv6 bgp community VARIABaE no-export no-adertise lo0cal-AS exact-match'@2: rv==0, 'show ipv6 bgp community (AA:NN|local-AS|no-advertise|no-export) (AA:NN|local-AS|no-advertise|no-export) (AA:NN|local-AS|no-advertise|no-export) (AA:NN|local-AS|no-advertise|no-export) exact-match': 'VARIABaE', 'no-export', 'no-adertise', 'lo0cal-AS' complete 'show ipv6 bgp community VARIABaE no-export no-adertise lo0cal-AS exact-match'@2: rv==7 'exact-match' describe 'show ipv6 bgp community VARIABaE no-export no-adertise lo0cal-AS exact-match'@2: rv==0 'exact-match' 'Exact match of the communities' execute relaxed 'show ipv6 bgp community VARIABaE no-export no-adertise lo0cal-AS exact-match'@4: rv==0, 'show ipv6 bgp community (AA:NN|local-AS|no-advertise|no-export) (AA:NN|local-AS|no-advertise|no-export) (AA:NN|local-AS|no-advertise|no-export) (AA:NN|local-AS|no-advertise|no-export) exact-match': 'VARIABaE', 'no-export', 'no-adertise', 'lo0cal-AS' execute strict 'show ipv6 bgp community VARIABaE no-export no-adertise lo0cal-AS exact-match'@4: rv==0, 'show ipv6 bgp community (AA:NN|local-AS|no-advertise|no-export) (AA:NN|local-AS|no-advertise|no-export) (AA:NN|local-AS|no-advertise|no-export) (AA:NN|local-AS|no-advertise|no-export) exact-match': 'VARIABaE', 'no-export', 'no-adertise', 'lo0cal-AS' complete 'show ipv6 bgp community VARIABaE no-export no-adertise lo0cal-AS exact-match'@4: rv==7 'exact-match' describe 'show ipv6 bgp community VARIABaE no-export no-adertise lo0cal-AS exact-match'@4: rv==0 'exact-match' 'Exact match of the communities' execute relaxed 'show ipv6 bgp community wARIBLE VARIABLE 8ARIABLE'@2: rv==0, 'show ipv6 bgp community (AA:NN|local-AS|no-advertise|no-export) (AA:NN|local-AS|no-advertise|no-export) (AA:NN|local-AS|no-advertise|no-export)': 'wARIBLE', 'VARIABLE', '8ARIABLE' execute strict 'show ipv6 bgp community wARIBLE VARIABLE 8ARIABLE'@2: rv==0, 'show ipv6 bgp community (AA:NN|local-AS|no-advertise|no-export) (AA:NN|local-AS|no-advertise|no-export) (AA:NN|local-AS|no-advertise|no-export)': 'wARIBLE', 'VARIABLE', '8ARIABLE' complete 'show ipv6 bgp community wARIBLE VARIABLE 8ARIABLE'@2: rv==2 describe 'show ipv6 bgp community wARIBLE VARIABLE 8ARIABLE'@2: rv==0 'AA:NN' 'Community number where AA and NN are <0-65535>' execute relaxed 'show ipv6 bgp community wARIBLE VARIABLE 8ARIABLE'@4: rv==0, 'show ipv6 bgp community (AA:NN|local-AS|no-advertise|no-export) (AA:NN|local-AS|no-advertise|no-export) (AA:NN|local-AS|no-advertise|no-export)': 'wARIBLE', 'VARIABLE', '8ARIABLE' execute strict 'show ipv6 bgp community wARIBLE VARIABLE 8ARIABLE'@4: rv==0, 'show ipv6 bgp community (AA:NN|local-AS|no-advertise|no-export) (AA:NN|local-AS|no-advertise|no-export) (AA:NN|local-AS|no-advertise|no-export)': 'wARIBLE', 'VARIABLE', '8ARIABLE' complete 'show ipv6 bgp community wARIBLE VARIABLE 8ARIABLE'@4: rv==2 describe 'show ipv6 bgp community wARIBLE VARIABLE 8ARIABLE'@4: rv==0 'AA:NN' 'Community number where AA and NN are <0-65535>' execute relaxed 'redistribute bgp'@14: rv==0, 'redistribute (kernel|connected|static|ospf|isis|bgp)': 'bgp' execute strict 'redistribute bgp'@14: rv==0, 'redistribute (kernel|connected|static|ospf|isis|bgp)': 'bgp' complete 'redistribute bgp'@14: rv==7 'bgp' describe 'redistribute bgp'@14: rv==0 'bgp' 'Border Gateway Protocol (BGP)' execute relaxed 'redistribute bgp'@15: rv==0, 'redistribute (kernel|connected|static|ospf6|isis|bgp)': 'bgp' execute strict 'redistribute bgp'@15: rv==0, 'redistribute (kernel|connected|static|ospf6|isis|bgp)': 'bgp' complete 'redistribute bgp'@15: rv==7 'bgp' describe 'redistribute bgp'@15: rv==0 'bgp' 'Border Gateway Protocol (BGP)' execute relaxed 'redistribute bgp'@16: rv==0, 'redistribute (kernel|connected|static|rip|ripng|ospf|ospf6|isis|bgp)': 'bgp' execute strict 'redistribute bgp'@16: rv==0, 'redistribute (kernel|connected|static|rip|ripng|ospf|ospf6|isis|bgp)': 'bgp' complete 'redistribute bgp'@16: rv==7 'bgp' describe 'redistribute bgp'@16: rv==0 'bgp' 'Border Gateway Protocol (BGP)' execute relaxed 'redistribute bgp'@23: rv==0, 'redistribute (kernel|connected|static|rip|isis|bgp) {metric <0-16777214>|metric-type (1|2)|route-map WORD}': 'bgp', '(null)', '(null)', '(null)' execute strict 'redistribute bgp'@23: rv==0, 'redistribute (kernel|connected|static|rip|isis|bgp) {metric <0-16777214>|metric-type (1|2)|route-map WORD}': 'bgp', '(null)', '(null)', '(null)' complete 'redistribute bgp'@23: rv==7 'bgp' describe 'redistribute bgp'@23: rv==0 'bgp' 'Border Gateway Protocol (BGP)' execute relaxed 'redistribute bgp'@24: rv==0, 'redistribute (kernel|connected|static|ripng|isis|bgp)': 'bgp' execute strict 'redistribute bgp'@24: rv==0, 'redistribute (kernel|connected|static|ripng|isis|bgp)': 'bgp' complete 'redistribute bgp'@24: rv==7 'bgp' describe 'redistribute bgp'@24: rv==0 'bgp' 'Border Gateway Protocol (BGP)' execute relaxed 'redistribute bgp m 10'@14: rv==0, 'redistribute (kernel|connected|static|ospf|isis|bgp) metric <0-16>': 'bgp', '10' execute strict 'redistribute bgp m 10'@14: rv==2 complete 'redistribute bgp m 10'@14: rv==2 describe 'redistribute bgp m 10'@14: rv==0 '<0-16>' 'Metric value' execute relaxed 'redistribute bgp m 10'@15: rv==0, 'redistribute (kernel|connected|static|ospf6|isis|bgp) metric <0-16>': 'bgp', '10' execute strict 'redistribute bgp m 10'@15: rv==2 complete 'redistribute bgp m 10'@15: rv==2 describe 'redistribute bgp m 10'@15: rv==0 '<0-16>' 'Metric value' execute relaxed 'redistribute bgp m 10'@23: rv==3 execute strict 'redistribute bgp m 10'@23: rv==2 complete 'redistribute bgp m 10'@23: rv==3 describe 'redistribute bgp m 10'@23: rv==3 execute relaxed 'redistribute bgp metric 10 metric-type 1'@23: rv==0, 'redistribute (kernel|connected|static|rip|isis|bgp) {metric <0-16777214>|metric-type (1|2)|route-map WORD}': 'bgp', '10', '1', '(null)' execute strict 'redistribute bgp metric 10 metric-type 1'@23: rv==0, 'redistribute (kernel|connected|static|rip|isis|bgp) {metric <0-16777214>|metric-type (1|2)|route-map WORD}': 'bgp', '10', '1', '(null)' complete 'redistribute bgp metric 10 metric-type 1'@23: rv==7 '1' describe 'redistribute bgp metric 10 metric-type 1'@23: rv==0 '1' 'Set OSPF External Type 1 metrics' execute relaxed 'redistribute bgp route-map RMAP_REDIST_BGP'@14: rv==0, 'redistribute (kernel|connected|static|ospf|isis|bgp) route-map WORD': 'bgp', 'RMAP_REDIST_BGP' execute strict 'redistribute bgp route-map RMAP_REDIST_BGP'@14: rv==0, 'redistribute (kernel|connected|static|ospf|isis|bgp) route-map WORD': 'bgp', 'RMAP_REDIST_BGP' complete 'redistribute bgp route-map RMAP_REDIST_BGP'@14: rv==2 describe 'redistribute bgp route-map RMAP_REDIST_BGP'@14: rv==0 'WORD' 'Pointer to route-map entries' execute relaxed 'redistribute bgp route-map RMAP_REDIST_BGP'@15: rv==0, 'redistribute (kernel|connected|static|ospf6|isis|bgp) route-map WORD': 'bgp', 'RMAP_REDIST_BGP' execute strict 'redistribute bgp route-map RMAP_REDIST_BGP'@15: rv==0, 'redistribute (kernel|connected|static|ospf6|isis|bgp) route-map WORD': 'bgp', 'RMAP_REDIST_BGP' complete 'redistribute bgp route-map RMAP_REDIST_BGP'@15: rv==2 describe 'redistribute bgp route-map RMAP_REDIST_BGP'@15: rv==0 'WORD' 'Pointer to route-map entries' execute relaxed 'redistribute bgp route-map RMAP_REDIST_BGP'@23: rv==0, 'redistribute (kernel|connected|static|rip|isis|bgp) {metric <0-16777214>|metric-type (1|2)|route-map WORD}': 'bgp', '(null)', '(null)', 'RMAP_REDIST_BGP' execute strict 'redistribute bgp route-map RMAP_REDIST_BGP'@23: rv==0, 'redistribute (kernel|connected|static|rip|isis|bgp) {metric <0-16777214>|metric-type (1|2)|route-map WORD}': 'bgp', '(null)', '(null)', 'RMAP_REDIST_BGP' complete 'redistribute bgp route-map RMAP_REDIST_BGP'@23: rv==2 describe 'redistribute bgp route-map RMAP_REDIST_BGP'@23: rv==0 'WORD' 'Pointer to route-map entries' execute relaxed 'redistribute bgp route-map RMAP_REDIST_BGP'@24: rv==0, 'redistribute (kernel|connected|static|ripng|isis|bgp) route-map WORD': 'bgp', 'RMAP_REDIST_BGP' execute strict 'redistribute bgp route-map RMAP_REDIST_BGP'@24: rv==0, 'redistribute (kernel|connected|static|ripng|isis|bgp) route-map WORD': 'bgp', 'RMAP_REDIST_BGP' complete 'redistribute bgp route-map RMAP_REDIST_BGP'@24: rv==2 describe 'redistribute bgp route-map RMAP_REDIST_BGP'@24: rv==0 'WORD' 'Route map name' execute relaxed 'default-information originate metric-type 1 metric 10'@23: rv==0, 'default-information originate {always|metric <0-16777214>|metric-type (1|2)|route-map WORD}': '(null)', '10', '1', '(null)' execute strict 'default-information originate metric-type 1 metric 10'@23: rv==0, 'default-information originate {always|metric <0-16777214>|metric-type (1|2)|route-map WORD}': '(null)', '10', '1', '(null)' complete 'default-information originate metric-type 1 metric 10'@23: rv==2 describe 'default-information originate metric-type 1 metric 10'@23: rv==0 '<0-16777214>' 'OSPF metric' execute relaxed 'default-information originate always metric-type 1 metric 10'@23: rv==0, 'default-information originate {always|metric <0-16777214>|metric-type (1|2)|route-map WORD}': 'always', '10', '1', '(null)' execute strict 'default-information originate always metric-type 1 metric 10'@23: rv==0, 'default-information originate {always|metric <0-16777214>|metric-type (1|2)|route-map WORD}': 'always', '10', '1', '(null)' complete 'default-information originate always metric-type 1 metric 10'@23: rv==2 describe 'default-information originate always metric-type 1 metric 10'@23: rv==0 '<0-16777214>' 'OSPF metric' execute relaxed 'default-information originate route-map RMAP_DEFAULT'@23: rv==0, 'default-information originate {always|metric <0-16777214>|metric-type (1|2)|route-map WORD}': '(null)', '(null)', '(null)', 'RMAP_DEFAULT' execute strict 'default-information originate route-map RMAP_DEFAULT'@23: rv==0, 'default-information originate {always|metric <0-16777214>|metric-type (1|2)|route-map WORD}': '(null)', '(null)', '(null)', 'RMAP_DEFAULT' complete 'default-information originate route-map RMAP_DEFAULT'@23: rv==2 describe 'default-information originate route-map RMAP_DEFAULT'@23: rv==0 'WORD' 'Pointer to route-map entries' execute relaxed 'default-information originate route-map RMAP_DEFAULT metric 10'@23: rv==0, 'default-information originate {always|metric <0-16777214>|metric-type (1|2)|route-map WORD}': '(null)', '10', '(null)', 'RMAP_DEFAULT' execute strict 'default-information originate route-map RMAP_DEFAULT metric 10'@23: rv==0, 'default-information originate {always|metric <0-16777214>|metric-type (1|2)|route-map WORD}': '(null)', '10', '(null)', 'RMAP_DEFAULT' complete 'default-information originate route-map RMAP_DEFAULT metric 10'@23: rv==2 describe 'default-information originate route-map RMAP_DEFAULT metric 10'@23: rv==0 '<0-16777214>' 'OSPF metric' execute relaxed 'default-information originate always metric-type 2 metric 23'@23: rv==0, 'default-information originate {always|metric <0-16777214>|metric-type (1|2)|route-map WORD}': 'always', '23', '2', '(null)' execute strict 'default-information originate always metric-type 2 metric 23'@23: rv==0, 'default-information originate {always|metric <0-16777214>|metric-type (1|2)|route-map WORD}': 'always', '23', '2', '(null)' complete 'default-information originate always metric-type 2 metric 23'@23: rv==2 describe 'default-information originate always metric-type 2 metric 23'@23: rv==0 '<0-16777214>' 'OSPF metric' frr-7.2.1/tests/lib/cxxcompat.c0000644000000000000000000000643513610377563013306 00000000000000/* * C++ compatibility compile-time smoketest * Copyright (C) 2019 David Lamparter for NetDEF, Inc. * * This program is free software; you can redistribute it and/or modify it * under the terms of the 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; see the file COPYING; if not, write to the Free Software * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA */ #define test__cplusplus #include "lib/zebra.h" #include "lib/agg_table.h" #include "lib/bfd.h" #include "lib/bitfield.h" #include "lib/buffer.h" #include "lib/checksum.h" #include "lib/command.h" #include "lib/command_graph.h" #include "lib/command_match.h" #include "lib/compiler.h" #include "lib/csv.h" #include "lib/debug.h" #include "lib/distribute.h" #include "lib/ferr.h" #include "lib/filter.h" #include "lib/frr_pthread.h" #include "lib/frratomic.h" #include "lib/frrstr.h" #include "lib/getopt.h" #include "lib/graph.h" #include "lib/hash.h" #include "lib/hook.h" #include "lib/id_alloc.h" #include "lib/if.h" #include "lib/if_rmap.h" #include "lib/imsg.h" #include "lib/ipaddr.h" #include "lib/jhash.h" #include "lib/json.h" #include "lib/keychain.h" #include "lib/lib_errors.h" #include "lib/libfrr.h" #include "lib/libospf.h" #include "lib/linklist.h" #include "lib/log.h" #include "lib/md5.h" #include "lib/memory.h" #include "lib/memory_vty.h" #include "lib/mlag.h" #include "lib/module.h" #include "lib/monotime.h" #include "lib/mpls.h" #include "lib/network.h" #include "lib/nexthop.h" #include "lib/nexthop_group.h" #include "lib/northbound.h" #include "lib/northbound_cli.h" #include "lib/northbound_db.h" #include "lib/ns.h" #include "lib/openbsd-tree.h" #include "lib/pbr.h" #include "lib/plist.h" #include "lib/prefix.h" #include "lib/privs.h" #include "lib/ptm_lib.h" #include "lib/pw.h" #include "lib/qobj.h" #include "lib/queue.h" #include "lib/ringbuf.h" #include "lib/routemap.h" #include "lib/sbuf.h" #include "lib/sha256.h" #include "lib/sigevent.h" #include "lib/skiplist.h" #include "lib/sockopt.h" #include "lib/sockunion.h" #include "lib/spf_backoff.h" #include "lib/srcdest_table.h" #include "lib/stream.h" #include "lib/table.h" #include "lib/termtable.h" #include "lib/thread.h" #include "lib/typesafe.h" #include "lib/typerb.h" #include "lib/vector.h" #include "lib/vlan.h" #include "lib/vrf.h" #include "lib/vty.h" #include "lib/vxlan.h" #include "lib/wheel.h" /* #include "lib/workqueue.h" -- macro problem with STAILQ_LAST */ #include "lib/yang.h" #include "lib/yang_translator.h" #include "lib/yang_wrappers.h" #include "lib/zassert.h" #include "lib/zclient.h" PREDECL_RBTREE_UNIQ(footree) struct foo { int dummy; struct footree_item item; }; static int foocmp(const struct foo *a, const struct foo *b) { return memcmp(&a->dummy, &b->dummy, sizeof(a->dummy)); } DECLARE_RBTREE_UNIQ(footree, struct foo, item, foocmp) int main(int argc, char **argv) { return 0; } frr-7.2.1/tests/lib/northbound/0000755000000000000000000000000013610377563013366 500000000000000frr-7.2.1/tests/lib/northbound/test_oper_data.c0000644000000000000000000002465413610377563016462 00000000000000/* * Copyright (C) 2018 NetDEF, Inc. * Renato Westphal * * This program is free software; you can redistribute it and/or modify it * under the terms of the 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; see the file COPYING; if not, write to the Free Software * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA */ #include #include "thread.h" #include "vty.h" #include "command.h" #include "memory.h" #include "memory_vty.h" #include "log.h" #include "northbound.h" static struct thread_master *master; struct troute { struct prefix_ipv4 prefix; struct in_addr nexthop; char ifname[IFNAMSIZ]; uint8_t metric; bool active; }; struct tvrf { char name[32]; struct list *interfaces; struct list *routes; }; static struct list *vrfs; /* * XPath: /frr-test-module:frr-test-module/vrfs/vrf */ static const void * frr_test_module_vrfs_vrf_get_next(const void *parent_list_entry, const void *list_entry) { struct listnode *node; if (list_entry == NULL) node = listhead(vrfs); else node = listnextnode((struct listnode *)list_entry); return node; } static int frr_test_module_vrfs_vrf_get_keys(const void *list_entry, struct yang_list_keys *keys) { const struct tvrf *vrf; vrf = listgetdata((struct listnode *)list_entry); keys->num = 1; strlcpy(keys->key[0], vrf->name, sizeof(keys->key[0])); return NB_OK; } static const void * frr_test_module_vrfs_vrf_lookup_entry(const void *parent_list_entry, const struct yang_list_keys *keys) { struct listnode *node; struct tvrf *vrf; const char *vrfname; vrfname = keys->key[0]; for (ALL_LIST_ELEMENTS_RO(vrfs, node, vrf)) { if (strmatch(vrf->name, vrfname)) return node; } return NULL; } /* * XPath: /frr-test-module:frr-test-module/vrfs/vrf/name */ static struct yang_data * frr_test_module_vrfs_vrf_name_get_elem(const char *xpath, const void *list_entry) { const struct tvrf *vrf; vrf = listgetdata((struct listnode *)list_entry); return yang_data_new_string(xpath, vrf->name); } /* * XPath: /frr-test-module:frr-test-module/vrfs/vrf/interfaces/interface */ static struct yang_data * frr_test_module_vrfs_vrf_interfaces_interface_get_elem(const char *xpath, const void *list_entry) { const char *interface; interface = listgetdata((struct listnode *)list_entry); return yang_data_new_string(xpath, interface); } static const void *frr_test_module_vrfs_vrf_interfaces_interface_get_next( const void *parent_list_entry, const void *list_entry) { const struct tvrf *vrf; struct listnode *node; vrf = listgetdata((struct listnode *)parent_list_entry); if (list_entry == NULL) node = listhead(vrf->interfaces); else node = listnextnode((struct listnode *)list_entry); return node; } /* * XPath: /frr-test-module:frr-test-module/vrfs/vrf/routes/route */ static const void * frr_test_module_vrfs_vrf_routes_route_get_next(const void *parent_list_entry, const void *list_entry) { const struct tvrf *vrf; struct listnode *node; vrf = listgetdata((struct listnode *)parent_list_entry); if (list_entry == NULL) node = listhead(vrf->routes); else node = listnextnode((struct listnode *)list_entry); return node; } /* * XPath: /frr-test-module:frr-test-module/vrfs/vrf/routes/route/prefix */ static struct yang_data * frr_test_module_vrfs_vrf_routes_route_prefix_get_elem(const char *xpath, const void *list_entry) { const struct troute *route; route = listgetdata((struct listnode *)list_entry); return yang_data_new_ipv4p(xpath, &route->prefix); } /* * XPath: /frr-test-module:frr-test-module/vrfs/vrf/routes/route/next-hop */ static struct yang_data * frr_test_module_vrfs_vrf_routes_route_next_hop_get_elem(const char *xpath, const void *list_entry) { const struct troute *route; route = listgetdata((struct listnode *)list_entry); return yang_data_new_ipv4(xpath, &route->nexthop); } /* * XPath: /frr-test-module:frr-test-module/vrfs/vrf/routes/route/interface */ static struct yang_data * frr_test_module_vrfs_vrf_routes_route_interface_get_elem(const char *xpath, const void *list_entry) { const struct troute *route; route = listgetdata((struct listnode *)list_entry); return yang_data_new_string(xpath, route->ifname); } /* * XPath: /frr-test-module:frr-test-module/vrfs/vrf/routes/route/metric */ static struct yang_data * frr_test_module_vrfs_vrf_routes_route_metric_get_elem(const char *xpath, const void *list_entry) { const struct troute *route; route = listgetdata((struct listnode *)list_entry); return yang_data_new_uint8(xpath, route->metric); } /* * XPath: /frr-test-module:frr-test-module/vrfs/vrf/routes/route/active */ static struct yang_data * frr_test_module_vrfs_vrf_routes_route_active_get_elem(const char *xpath, const void *list_entry) { const struct troute *route; route = listgetdata((struct listnode *)list_entry); if (route->active) return yang_data_new(xpath, NULL); return NULL; } /* clang-format off */ const struct frr_yang_module_info frr_test_module_info = { .name = "frr-test-module", .nodes = { { .xpath = "/frr-test-module:frr-test-module/vrfs/vrf", .cbs.get_next = frr_test_module_vrfs_vrf_get_next, .cbs.get_keys = frr_test_module_vrfs_vrf_get_keys, .cbs.lookup_entry = frr_test_module_vrfs_vrf_lookup_entry, }, { .xpath = "/frr-test-module:frr-test-module/vrfs/vrf/name", .cbs.get_elem = frr_test_module_vrfs_vrf_name_get_elem, }, { .xpath = "/frr-test-module:frr-test-module/vrfs/vrf/interfaces/interface", .cbs.get_elem = frr_test_module_vrfs_vrf_interfaces_interface_get_elem, .cbs.get_next = frr_test_module_vrfs_vrf_interfaces_interface_get_next, }, { .xpath = "/frr-test-module:frr-test-module/vrfs/vrf/routes/route", .cbs.get_next = frr_test_module_vrfs_vrf_routes_route_get_next, }, { .xpath = "/frr-test-module:frr-test-module/vrfs/vrf/routes/route/prefix", .cbs.get_elem = frr_test_module_vrfs_vrf_routes_route_prefix_get_elem, }, { .xpath = "/frr-test-module:frr-test-module/vrfs/vrf/routes/route/next-hop", .cbs.get_elem = frr_test_module_vrfs_vrf_routes_route_next_hop_get_elem, }, { .xpath = "/frr-test-module:frr-test-module/vrfs/vrf/routes/route/interface", .cbs.get_elem = frr_test_module_vrfs_vrf_routes_route_interface_get_elem, }, { .xpath = "/frr-test-module:frr-test-module/vrfs/vrf/routes/route/metric", .cbs.get_elem = frr_test_module_vrfs_vrf_routes_route_metric_get_elem, }, { .xpath = "/frr-test-module:frr-test-module/vrfs/vrf/routes/route/active", .cbs.get_elem = frr_test_module_vrfs_vrf_routes_route_active_get_elem, }, { .xpath = NULL, }, } }; /* clang-format on */ static const struct frr_yang_module_info *modules[] = { &frr_test_module_info, }; static void create_data(unsigned int num_vrfs, unsigned int num_interfaces, unsigned int num_routes) { struct prefix_ipv4 base_prefix; struct in_addr base_nexthop; (void)str2prefix_ipv4("10.0.0.0/32", &base_prefix); (void)inet_pton(AF_INET, "172.16.0.0", &base_nexthop); vrfs = list_new(); /* Create VRFs. */ for (unsigned int i = 0; i < num_vrfs; i++) { struct tvrf *vrf; vrf = XCALLOC(MTYPE_TMP, sizeof(*vrf)); snprintf(vrf->name, sizeof(vrf->name), "vrf%u", i); vrf->interfaces = list_new(); vrf->routes = list_new(); /* Create interfaces. */ for (unsigned int j = 0; j < num_interfaces; j++) { char ifname[32]; char *interface; snprintf(ifname, sizeof(ifname), "eth%u", j); interface = XSTRDUP(MTYPE_TMP, ifname); listnode_add(vrf->interfaces, interface); } /* Create routes. */ for (unsigned int j = 0; j < num_routes; j++) { struct troute *route; route = XCALLOC(MTYPE_TMP, sizeof(*route)); memcpy(&route->prefix, &base_prefix, sizeof(route->prefix)); route->prefix.prefix.s_addr = htonl(ntohl(route->prefix.prefix.s_addr) + j); memcpy(&route->nexthop, &base_nexthop, sizeof(route->nexthop)); route->nexthop.s_addr = htonl(ntohl(route->nexthop.s_addr) + j); snprintf(route->ifname, sizeof(route->ifname), "eth%u", j); route->metric = j % 256; route->active = (j % 2 == 0); listnode_add(vrf->routes, route); } listnode_add(vrfs, vrf); } } static void interface_delete(void *ptr) { char *interface = ptr; XFREE(MTYPE_TMP, interface); } static void route_delete(void *ptr) { struct troute *route = ptr; XFREE(MTYPE_TMP, route); } static void vrf_delete(void *ptr) { struct tvrf *vrf = ptr; vrf->interfaces->del = interface_delete; list_delete(&vrf->interfaces); vrf->routes->del = route_delete; list_delete(&vrf->routes); XFREE(MTYPE_TMP, vrf); } static void delete_data(void) { vrfs->del = vrf_delete; list_delete(&vrfs); } static void vty_do_exit(int isexit) { printf("\nend.\n"); delete_data(); cmd_terminate(); vty_terminate(); nb_terminate(); yang_terminate(); thread_master_free(master); closezlog(); log_memstats(stderr, "test-nb-oper-data"); if (!isexit) exit(0); } /* main routine. */ int main(int argc, char **argv) { struct thread thread; unsigned int num_vrfs = 2; unsigned int num_interfaces = 4; unsigned int num_routes = 6; if (argc > 1) num_vrfs = atoi(argv[1]); if (argc > 2) num_interfaces = atoi(argv[2]); if (argc > 3) num_routes = atoi(argv[3]); /* Set umask before anything for security */ umask(0027); /* master init. */ master = thread_master_create(NULL); openzlog("test-nb-oper-data", "NONE", 0, LOG_CONS | LOG_NDELAY | LOG_PID, LOG_DAEMON); zlog_set_level(ZLOG_DEST_SYSLOG, ZLOG_DISABLED); zlog_set_level(ZLOG_DEST_STDOUT, ZLOG_DISABLED); zlog_set_level(ZLOG_DEST_MONITOR, LOG_DEBUG); /* Library inits. */ cmd_init(1); cmd_hostname_set("test"); vty_init(master, false); memory_init(); yang_init(); nb_init(master, modules, array_size(modules)); /* Create artificial data. */ create_data(num_vrfs, num_interfaces, num_routes); /* Read input from .in file. */ vty_stdio(vty_do_exit); /* Fetch next active thread. */ while (thread_fetch(master, &thread)) thread_call(&thread); /* Not reached. */ exit(0); } frr-7.2.1/tests/lib/northbound/test_oper_data.in0000644000000000000000000000007413610377563016634 00000000000000show yang operational-data /frr-test-module:frr-test-module frr-7.2.1/tests/lib/northbound/test_oper_data.py0000644000000000000000000000013313610377563016652 00000000000000import frrtest class TestNbOperData(frrtest.TestRefOut): program = './test_oper_data' frr-7.2.1/tests/lib/northbound/test_oper_data.refout0000644000000000000000000000623413610377563017536 00000000000000test# show yang operational-data /frr-test-module:frr-test-module { "frr-test-module:frr-test-module": { "vrfs": { "vrf": [ { "name": "vrf0", "interfaces": { "interface": [ "eth0", "eth1", "eth2", "eth3" ] }, "routes": { "route": [ { "prefix": "10.0.0.0/32", "next-hop": "172.16.0.0", "interface": "eth0", "metric": 0, "active": [null] }, { "prefix": "10.0.0.1/32", "next-hop": "172.16.0.1", "interface": "eth1", "metric": 1 }, { "prefix": "10.0.0.2/32", "next-hop": "172.16.0.2", "interface": "eth2", "metric": 2, "active": [null] }, { "prefix": "10.0.0.3/32", "next-hop": "172.16.0.3", "interface": "eth3", "metric": 3 }, { "prefix": "10.0.0.4/32", "next-hop": "172.16.0.4", "interface": "eth4", "metric": 4, "active": [null] }, { "prefix": "10.0.0.5/32", "next-hop": "172.16.0.5", "interface": "eth5", "metric": 5 } ] } }, { "name": "vrf1", "interfaces": { "interface": [ "eth0", "eth1", "eth2", "eth3" ] }, "routes": { "route": [ { "prefix": "10.0.0.0/32", "next-hop": "172.16.0.0", "interface": "eth0", "metric": 0, "active": [null] }, { "prefix": "10.0.0.1/32", "next-hop": "172.16.0.1", "interface": "eth1", "metric": 1 }, { "prefix": "10.0.0.2/32", "next-hop": "172.16.0.2", "interface": "eth2", "metric": 2, "active": [null] }, { "prefix": "10.0.0.3/32", "next-hop": "172.16.0.3", "interface": "eth3", "metric": 3 }, { "prefix": "10.0.0.4/32", "next-hop": "172.16.0.4", "interface": "eth4", "metric": 4, "active": [null] }, { "prefix": "10.0.0.5/32", "next-hop": "172.16.0.5", "interface": "eth5", "metric": 5 } ] } } ] } } } test# end. frr-7.2.1/tests/lib/test_atomlist.c0000644000000000000000000002126313610377563014167 00000000000000/* * Copyright (c) 2016-2018 David Lamparter, for NetDEF, Inc. * * Permission to use, copy, modify, and distribute this software for any * purpose with or without fee is hereby granted, provided that the above * copyright notice and this permission notice appear in all copies. * * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. */ #ifdef HAVE_CONFIG_H #include "config.h" #endif #include #include #include #include #include #include #include #include "atomlist.h" #include "seqlock.h" #include "monotime.h" /* * maybe test: * - alist_del_hint * - alist_next_safe * - asort_del_hint * - asort_next_safe */ static struct seqlock sqlo; PREDECL_ATOMLIST(alist) PREDECL_ATOMSORT_UNIQ(asort) struct item { uint64_t val1; struct alist_item chain; struct asort_item sortc; uint64_t val2; }; DECLARE_ATOMLIST(alist, struct item, chain) static int icmp(const struct item *a, const struct item *b); DECLARE_ATOMSORT_UNIQ(asort, struct item, sortc, icmp) static int icmp(const struct item *a, const struct item *b) { if (a->val1 > b->val1) return 1; if (a->val1 < b->val1) return -1; return 0; } #define NITEM 10000 struct item itm[NITEM]; static struct alist_head ahead; static struct asort_head shead; #define NTHREADS 4 static struct testthread { pthread_t pt; struct seqlock sqlo; size_t counter, nullops; } thr[NTHREADS]; struct testrun { struct testrun *next; int lineno; const char *desc; ssize_t prefill; bool sorted; void (*func)(unsigned int offset); }; struct testrun *runs = NULL; #define NOCLEAR -1 #define deftestrun(name, _desc, _prefill, _sorted) \ static void trfunc_##name(unsigned int offset); \ struct testrun tr_##name = { \ .desc = _desc, \ .lineno = __LINE__, \ .prefill = _prefill, \ .func = &trfunc_##name, \ .sorted = _sorted }; \ static void __attribute__((constructor)) trsetup_##name(void) \ { \ struct testrun **inspos = &runs; \ while (*inspos && (*inspos)->lineno < tr_##name.lineno) \ inspos = &(*inspos)->next; \ tr_##name.next = *inspos; \ *inspos = &tr_##name; \ } \ static void trfunc_##name(unsigned int offset) \ { \ size_t i = 0, n = 0; #define endtestrun \ thr[offset].counter = i; \ thr[offset].nullops = n; \ } deftestrun(add, "add vs. add", 0, false) for (; i < NITEM / NTHREADS; i++) alist_add_head(&ahead, &itm[i * NTHREADS + offset]); endtestrun deftestrun(del, "del vs. del", NOCLEAR, false) for (; i < NITEM / NTHREADS / 10; i++) alist_del(&ahead, &itm[i * NTHREADS + offset]); endtestrun deftestrun(addtail, "add_tail vs. add_tail", 0, false) for (; i < NITEM / NTHREADS; i++) alist_add_tail(&ahead, &itm[i * NTHREADS + offset]); endtestrun deftestrun(pop, "pop vs. pop", NOCLEAR, false) for (; i < NITEM / NTHREADS; ) if (alist_pop(&ahead)) i++; else n++; endtestrun deftestrun(headN_vs_pop1, "add_head(N) vs. pop(1)", 1, false); if (offset == 0) { struct item *dr = NULL; for (i = n = 0; i < NITEM; ) { dr = alist_pop(&ahead); if (dr) i++; else n++; } } else { for (i = offset; i < NITEM; i += NTHREADS) alist_add_head(&ahead, &itm[i]); i = 0; } endtestrun deftestrun(head1_vs_popN, "add_head(1) vs. pop(N)", 0, false); if (offset < NTHREADS - 1) { struct item *dr = NULL; for (i = n = 0; i < NITEM / NTHREADS; ) { dr = alist_pop(&ahead); if (dr) i++; else n++; } } else { for (i = 0; i < NITEM; i++) alist_add_head(&ahead, &itm[i]); i = 0; } endtestrun deftestrun(headN_vs_popN, "add_head(N) vs. pop(N)", NTHREADS / 2, false) if (offset < NTHREADS / 2) { struct item *dr = NULL; for (i = n = 0; i < NITEM * 2 / NTHREADS; ) { dr = alist_pop(&ahead); if (dr) i++; else n++; } } else { for (i = offset; i < NITEM; i += NTHREADS) alist_add_head(&ahead, &itm[i]); i = 0; } endtestrun deftestrun(tailN_vs_pop1, "add_tail(N) vs. pop(1)", 1, false) if (offset == 0) { struct item *dr = NULL; for (i = n = 0; i < NITEM - (NITEM / NTHREADS); ) { dr = alist_pop(&ahead); if (dr) i++; else n++; } } else { for (i = offset; i < NITEM; i += NTHREADS) alist_add_tail(&ahead, &itm[i]); i = 0; } endtestrun deftestrun(tail1_vs_popN, "add_tail(1) vs. pop(N)", 0, false) if (offset < NTHREADS - 1) { struct item *dr = NULL; for (i = n = 0; i < NITEM / NTHREADS; ) { dr = alist_pop(&ahead); if (dr) i++; else n++; } } else { for (i = 0; i < NITEM; i++) alist_add_tail(&ahead, &itm[i]); i = 0; } endtestrun deftestrun(sort_add, "add_sort vs. add_sort", 0, true) for (; i < NITEM / NTHREADS / 10; i++) asort_add(&shead, &itm[i * NTHREADS + offset]); endtestrun deftestrun(sort_del, "del_sort vs. del_sort", NOCLEAR, true) for (; i < NITEM / NTHREADS / 10; i++) asort_del(&shead, &itm[i * NTHREADS + offset]); endtestrun deftestrun(sort_add_del, "add_sort vs. del_sort", NTHREADS / 2, true) if (offset < NTHREADS / 2) { for (; i < NITEM / NTHREADS / 10; i++) asort_del(&shead, &itm[i * NTHREADS + offset]); } else { for (; i < NITEM / NTHREADS / 10; i++) asort_add(&shead, &itm[i * NTHREADS + offset]); } endtestrun static void *thr1func(void *arg) { struct testthread *p = arg; unsigned int offset = (unsigned int)(p - &thr[0]); seqlock_val_t sv; struct testrun *tr; for (tr = runs; tr; tr = tr->next) { sv = seqlock_bump(&p->sqlo) - SEQLOCK_INCR; seqlock_wait(&sqlo, sv); tr->func(offset); } seqlock_bump(&p->sqlo); return NULL; } static void clear_list(size_t prefill) { size_t i; memset(&ahead, 0, sizeof(ahead)); memset(&shead, 0, sizeof(shead)); memset(itm, 0, sizeof(itm)); for (i = 0; i < NITEM; i++) { itm[i].val1 = itm[i].val2 = i; if ((i % NTHREADS) < prefill) { alist_add_tail(&ahead, &itm[i]); asort_add(&shead, &itm[i]); } } } static void run_tr(struct testrun *tr) { const char *desc = tr->desc; struct timeval tv; int64_t delta; seqlock_val_t sv; size_t c = 0, s = 0, n = 0; struct item *item, *prev, dummy; printf("[%02u] %35s %s\n", seqlock_cur(&sqlo) >> 2, "", desc); fflush(stdout); if (tr->prefill != NOCLEAR) clear_list(tr->prefill); monotime(&tv); sv = seqlock_bump(&sqlo) - SEQLOCK_INCR; for (size_t i = 0; i < NTHREADS; i++) { seqlock_wait(&thr[i].sqlo, seqlock_cur(&sqlo)); s += thr[i].counter; n += thr[i].nullops; thr[i].counter = 0; thr[i].nullops = 0; } delta = monotime_since(&tv, NULL); if (tr->sorted) { uint64_t prevval = 0; frr_each(asort, &shead, item) { assert(item->val1 >= prevval); prevval = item->val1; c++; } assert(c == asort_count(&shead)); } else { prev = &dummy; frr_each(alist, &ahead, item) { assert(item != prev); prev = item; c++; assert(c <= NITEM); } assert(c == alist_count(&ahead)); } printf("\033[1A[%02u] %9"PRId64"us c=%5zu s=%5zu n=%5zu %s\n", sv >> 2, delta, c, s, n, desc); } #ifdef BASIC_TESTS static void dump(const char *lbl) { struct item *item, *safe; size_t ctr = 0; printf("dumping %s:\n", lbl); frr_each_safe(alist, &ahead, item) { printf("%s %3zu %p %3"PRIu64" %3"PRIu64"\n", lbl, ctr++, (void *)item, item->val1, item->val2); } } static void basic_tests(void) { size_t i; memset(&ahead, 0, sizeof(ahead)); memset(itm, 0, sizeof(itm)); for (i = 0; i < NITEM; i++) itm[i].val1 = itm[i].val2 = i; assert(alist_first(&ahead) == NULL); dump(""); alist_add_head(&ahead, &itm[0]); dump(""); alist_add_head(&ahead, &itm[1]); dump(""); alist_add_tail(&ahead, &itm[2]); dump(""); alist_add_tail(&ahead, &itm[3]); dump(""); alist_del(&ahead, &itm[1]); dump(""); printf("POP: %p\n", alist_pop(&ahead)); dump(""); printf("POP: %p\n", alist_pop(&ahead)); printf("POP: %p\n", alist_pop(&ahead)); printf("POP: %p\n", alist_pop(&ahead)); printf("POP: %p\n", alist_pop(&ahead)); dump(""); } #else #define basic_tests() do { } while (0) #endif int main(int argc, char **argv) { size_t i; basic_tests(); seqlock_init(&sqlo); seqlock_acquire_val(&sqlo, SEQLOCK_STARTVAL); for (i = 0; i < NTHREADS; i++) { seqlock_init(&thr[i].sqlo); seqlock_acquire(&thr[i].sqlo, &sqlo); thr[i].counter = 0; thr[i].nullops = 0; pthread_create(&thr[i].pt, NULL, thr1func, &thr[i]); } struct testrun *tr; for (tr = runs; tr; tr = tr->next) run_tr(tr); for (i = 0; i < NTHREADS; i++) pthread_join(thr[i].pt, NULL); return 0; } frr-7.2.1/tests/lib/test_atomlist.py0000644000000000000000000000016713610377563014375 00000000000000import frrtest class TestAtomlist(frrtest.TestMultiOut): program = './test_atomlist' TestAtomlist.exit_cleanly() frr-7.2.1/tests/lib/test_buffer.c0000644000000000000000000000260013610377563013576 00000000000000/* * Copyright (C) 2004 Paul Jakma * * This file is part of Quagga. * * Quagga is free software; you can redistribute it and/or modify it * under the terms of the GNU General Public License as published by the * Free Software Foundation; either version 2, or (at your option) any * later version. * * Quagga 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; see the file COPYING; if not, write to the Free Software * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA */ #include #include #include #include struct thread_master *master; int main(int argc, char **argv) { struct buffer *b1, *b2; int n; char junk[3]; char c = 'a'; memory_init(); if ((argc != 2) || (sscanf(argv[1], "%d%1s", &n, junk) != 1)) { fprintf(stderr, "Usage: %s \n", *argv); return 1; } b1 = buffer_new(0); b2 = buffer_new(1024); while (n-- > 0) { buffer_put(b1, &c, 1); buffer_put(b2, &c, 1); if (c++ == 'z') c = 'a'; buffer_reset(b1); buffer_reset(b2); } buffer_free(b1); buffer_free(b2); return 0; } frr-7.2.1/tests/lib/test_checksum.c0000644000000000000000000002455213610377563014141 00000000000000/* * Copyright (C) 2008 Sun Microsystems, Inc. * * This file is part of Quagga. * * Quagga is free software; you can redistribute it and/or modify it * under the terms of the GNU General Public License as published by the * Free Software Foundation; either version 2, or (at your option) any * later version. * * Quagga 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; see the file COPYING; if not, write to the Free Software * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA */ #include #include #include #include "checksum.h" struct thread_master *master; struct acc_vals { int c0; int c1; }; struct csum_vals { struct acc_vals a; int x; int y; }; static struct csum_vals ospfd_vals, isisd_vals; typedef size_t testsz_t; typedef uint16_t testoff_t; /* Fletcher Checksum -- Refer to RFC1008. */ #define MODX 4102U /* The final reduction phase. * This one should be the original ospfd version */ static uint16_t reduce_ospfd(struct csum_vals *vals, testsz_t len, testoff_t off) { #define x vals->x #define y vals->y #define c0 vals->a.c0 #define c1 vals->a.c1 x = ((len - off - 1) * c0 - c1) % 255; if (x <= 0) x += 255; y = 510 - c0 - x; if (y > 255) y -= 255; /* take care endian issue. */ return htons((x << 8) + y); #undef x #undef y #undef c0 #undef c1 } /* slightly different concatenation */ static uint16_t reduce_ospfd1(struct csum_vals *vals, testsz_t len, testoff_t off) { #define x vals->x #define y vals->y #define c0 vals->a.c0 #define c1 vals->a.c1 x = ((len - off - 1) * c0 - c1) % 255; if (x <= 0) x += 255; y = 510 - c0 - x; if (y > 255) y -= 255; /* take care endian issue. */ return htons((x << 8) | (y & 0xff)); #undef x #undef y #undef c0 #undef c1 } /* original isisd version */ static uint16_t reduce_isisd(struct csum_vals *vals, testsz_t len, testoff_t off) { #define x vals->x #define y vals->y #define c0 vals->a.c0 #define c1 vals->a.c1 uint32_t mul; mul = (len - off) * (c0); x = mul - c0 - c1; y = c1 - mul - 1; if (y > 0) y++; if (x < 0) x--; x %= 255; y %= 255; if (x == 0) x = 255; if (y == 0) y = 1; return htons((x << 8) | (y & 0xff)); #undef x #undef y #undef c0 #undef c1 } /* Is the -1 in y wrong perhaps? */ static uint16_t reduce_isisd_yfix(struct csum_vals *vals, testsz_t len, testoff_t off) { #define x vals->x #define y vals->y #define c0 vals->a.c0 #define c1 vals->a.c1 uint32_t mul; mul = (len - off) * (c0); x = mul - c0 - c1; y = c1 - mul; if (y > 0) y++; if (x < 0) x--; x %= 255; y %= 255; if (x == 0) x = 255; if (y == 0) y = 1; return htons((x << 8) | (y & 0xff)); #undef x #undef y #undef c0 #undef c1 } /* Move the mods yp */ static uint16_t reduce_isisd_mod(struct csum_vals *vals, testsz_t len, testoff_t off) { #define x vals->x #define y vals->y #define c0 vals->a.c0 #define c1 vals->a.c1 uint32_t mul; mul = (len - off) * (c0); x = mul - c1 - c0; y = c1 - mul - 1; x %= 255; y %= 255; if (y > 0) y++; if (x < 0) x--; if (x == 0) x = 255; if (y == 0) y = 1; return htons((x << 8) | (y & 0xff)); #undef x #undef y #undef c0 #undef c1 } /* Move the mods up + fix y */ static uint16_t reduce_isisd_mody(struct csum_vals *vals, testsz_t len, testoff_t off) { #define x vals->x #define y vals->y #define c0 vals->a.c0 #define c1 vals->a.c1 uint32_t mul; mul = (len - off) * (c0); x = mul - c0 - c1; y = c1 - mul; x %= 255; y %= 255; if (y > 0) y++; if (x < 0) x--; if (x == 0) x = 255; if (y == 0) y = 1; return htons((x << 8) | (y & 0xff)); #undef x #undef y #undef c0 #undef c1 } struct reductions_t { const char *name; uint16_t (*f)(struct csum_vals *, testsz_t, testoff_t); } reducts[] = { {.name = "ospfd", .f = reduce_ospfd}, {.name = "ospfd-1", .f = reduce_ospfd1}, {.name = "isisd", .f = reduce_isisd}, {.name = "isisd-yfix", .f = reduce_isisd_yfix}, {.name = "isisd-mod", .f = reduce_isisd_mod}, {.name = "isisd-mody", .f = reduce_isisd_mody}, {NULL, NULL}, }; /* The original ospfd checksum */ static uint16_t ospfd_checksum(uint8_t *buffer, testsz_t len, testoff_t off) { uint8_t *sp, *ep, *p, *q; int c0 = 0, c1 = 0; int x, y; uint16_t checksum, *csum; csum = (uint16_t *)(buffer + off); *(csum) = 0; sp = buffer; for (ep = sp + len; sp < ep; sp = q) { q = sp + MODX; if (q > ep) q = ep; for (p = sp; p < q; p++) { c0 += *p; c1 += c0; } c0 %= 255; c1 %= 255; } ospfd_vals.a.c0 = c0; ospfd_vals.a.c1 = c1; // printf ("%s: len %u, off %u, c0 %d, c1 %d\n", // __func__, len, off, c0, c1); x = ((int)(len - off - 1) * (int)c0 - (int)c1) % 255; if (x <= 0) x += 255; y = 510 - c0 - x; if (y > 255) y -= 255; ospfd_vals.x = x; ospfd_vals.y = y; buffer[off] = x; buffer[off + 1] = y; /* take care endian issue. */ checksum = htons((x << 8) | (y & 0xff)); return (checksum); } /* the original, broken isisd checksum */ static uint16_t iso_csum_create(uint8_t *buffer, testsz_t len, testoff_t off) { uint8_t *p; int x; int y; uint32_t mul; uint32_t c0; uint32_t c1; uint16_t checksum, *csum; int i, init_len, partial_len; checksum = 0; csum = (uint16_t *)(buffer + off); *(csum) = checksum; p = buffer; c0 = 0; c1 = 0; init_len = len; while (len != 0) { partial_len = MIN(len, MODX); for (i = 0; i < partial_len; i++) { c0 = c0 + *(p++); c1 += c0; } c0 = c0 % 255; c1 = c1 % 255; len -= partial_len; } isisd_vals.a.c0 = c0; isisd_vals.a.c1 = c1; mul = (init_len - off) * c0; x = mul - c1 - c0; y = c1 - mul - 1; if (y > 0) y++; if (x < 0) x--; x %= 255; y %= 255; if (x == 0) x = 255; if (y == 0) y = 1; isisd_vals.x = x; isisd_vals.y = y; checksum = htons((x << 8) | (y & 0xFF)); *(csum) = checksum; /* return the checksum for user usage */ return checksum; } static int verify(uint8_t *buffer, testsz_t len) { uint8_t *p; uint32_t c0; uint32_t c1; int i, partial_len; p = buffer; c0 = 0; c1 = 0; while (len) { partial_len = MIN(len, 5803U); for (i = 0; i < partial_len; i++) { c0 = c0 + *(p++); c1 += c0; } c0 = c0 % 255; c1 = c1 % 255; len -= partial_len; } if (c0 == 0 && c1 == 0) return 0; return 1; } static int /* return checksum in low-order 16 bits */ in_cksum_optimized(void *parg, int nbytes) { unsigned short *ptr = parg; register long sum; /* assumes long == 32 bits */ register unsigned short answer; /* assumes unsigned short == 16 bits */ register int count; /* * Our algorithm is simple, using a 32-bit accumulator (sum), * we add sequential 16-bit words to it, and at the end, fold back * all the carry bits from the top 16 bits into the lower 16 bits. */ sum = 0; count = nbytes >> 1; /* div by 2 */ for (ptr--; count; --count) sum += *++ptr; if (nbytes & 1) /* Odd */ sum += *(uint8_t *)(++ptr); /* one byte only */ /* * Add back carry outs from top 16 bits to low 16 bits. */ sum = (sum >> 16) + (sum & 0xffff); /* add high-16 to low-16 */ sum += (sum >> 16); /* add carry */ answer = ~sum; /* ones-complement, then truncate to 16 bits */ return (answer); } static int /* return checksum in low-order 16 bits */ in_cksum_rfc(void *parg, int count) /* from RFC 1071 */ { unsigned short *addr = parg; /* Compute Internet Checksum for "count" bytes * beginning at location "addr". */ register long sum = 0; while (count > 1) { /* This is the inner loop */ sum += *addr++; count -= 2; } /* Add left-over byte, if any */ if (count > 0) { sum += *(uint8_t *)addr; } /* Fold 32-bit sum to 16 bits */ while (sum >> 16) sum = (sum & 0xffff) + (sum >> 16); return ~sum; } int main(int argc, char **argv) { /* 60017 65629 702179 */ #define MAXDATALEN 60017 #define BUFSIZE MAXDATALEN + sizeof(uint16_t) uint8_t buffer[BUFSIZE]; int exercise = 0; #define EXERCISESTEP 257 srandom(time(NULL)); while (1) { uint16_t ospfd, isisd, lib, in_csum, in_csum_res, in_csum_rfc; int i, j; exercise += EXERCISESTEP; exercise %= MAXDATALEN; for (i = 0; i < exercise; i += sizeof(long int)) { long int rand = random(); for (j = sizeof(long int); j > 0; j--) buffer[i + (sizeof(long int) - j)] = (rand >> (j * 8)) & 0xff; } in_csum = in_cksum(buffer, exercise); in_csum_res = in_cksum_optimized(buffer, exercise); in_csum_rfc = in_cksum_rfc(buffer, exercise); if (in_csum_res != in_csum || in_csum != in_csum_rfc) printf("verify: in_chksum failed in_csum:%x, in_csum_res:%x," "in_csum_rfc %x, len:%d\n", in_csum, in_csum_res, in_csum_rfc, exercise); ospfd = ospfd_checksum(buffer, exercise + sizeof(uint16_t), exercise); if (verify(buffer, exercise + sizeof(uint16_t))) printf("verify: ospfd failed\n"); isisd = iso_csum_create(buffer, exercise + sizeof(uint16_t), exercise); if (verify(buffer, exercise + sizeof(uint16_t))) printf("verify: isisd failed\n"); lib = fletcher_checksum(buffer, exercise + sizeof(uint16_t), exercise); if (verify(buffer, exercise + sizeof(uint16_t))) printf("verify: lib failed\n"); if (ospfd != lib) { printf("Mismatch in values at size %d\n" "ospfd: 0x%04x\tc0: %d\tc1: %d\tx: %d\ty: %d\n" "isisd: 0x%04x\tc0: %d\tc1: %d\tx: %d\ty: %d\n" "lib: 0x%04x\n", exercise, ospfd, ospfd_vals.a.c0, ospfd_vals.a.c1, ospfd_vals.x, ospfd_vals.y, isisd, isisd_vals.a.c0, isisd_vals.a.c1, isisd_vals.x, isisd_vals.y, lib); /* Investigate reduction phase discrepencies */ if (ospfd_vals.a.c0 == isisd_vals.a.c0 && ospfd_vals.a.c1 == isisd_vals.a.c1) { printf("\n"); for (i = 0; reducts[i].name != NULL; i++) { ospfd = reducts[i].f( &ospfd_vals, exercise + sizeof(uint16_t), exercise); printf("%20s: x: %02x, y %02x, checksum 0x%04x\n", reducts[i].name, ospfd_vals.x & 0xff, ospfd_vals.y & 0xff, ospfd); } } printf("\n uint8_t testdata [] = {\n "); for (i = 0; i < exercise; i++) { printf("0x%02x,%s", buffer[i], (i + 1) % 8 ? " " : "\n "); } printf("\n}\n"); exit(1); } } } frr-7.2.1/tests/lib/test_graph.c0000644000000000000000000000435213610377563013434 00000000000000/* * Test graph data structure. * Copyright (C) 2018 Cumulus Networks, Inc. * Quentin Young * * This program is free software; you can redistribute it and/or modify it * under the terms of the 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; see the file COPYING; if not, write to the Free Software * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA */ #include #include #include #include #define NUMNODES 32 static void graph_custom_print_cb(struct graph_node *gn, struct buffer *buf) { char nbuf[64]; char *gname = gn->data; for (unsigned int i = 0; i < vector_active(gn->to); i++) { struct graph_node *adj = vector_slot(gn->to, i); char *name = adj->data; snprintf(nbuf, sizeof(nbuf), " n%s -> n%s;\n", gname, name); buffer_putstr(buf, nbuf); } } int main(int argc, char **argv) { struct graph *g = graph_new(); struct graph_node *gn[NUMNODES]; char names[NUMNODES][16]; /* create vertices */ for (unsigned int i = 0; i < NUMNODES; i++) { snprintf(names[i], sizeof(names[i]), "%u", i); gn[i] = graph_new_node(g, names[i], NULL); } /* create edges */ for (unsigned int i = 1; i < NUMNODES - 1; i++) { graph_add_edge(gn[0], gn[i]); graph_add_edge(gn[i], gn[i + 1]); } graph_add_edge(gn[0], gn[NUMNODES - 1]); graph_add_edge(gn[NUMNODES - 1], gn[1]); /* print DOT */ char *dumped = graph_dump_dot(g, gn[0], graph_custom_print_cb); fprintf(stdout, "%s", dumped); XFREE(MTYPE_TMP, dumped); /* remove some edges */ for (unsigned int i = NUMNODES - 1; i > NUMNODES / 2; --i) for (unsigned int j = 0; j < NUMNODES; j++) graph_remove_edge(gn[i], gn[j]); /* remove some nodes */ for (unsigned int i = 0; i < NUMNODES / 2; i++) graph_delete_node(g, gn[i]); graph_delete_graph(g); } frr-7.2.1/tests/lib/test_graph.py0000644000000000000000000000012213610377563013631 00000000000000import frrtest class TestGraph(frrtest.TestRefOut): program = './test_graph' frr-7.2.1/tests/lib/test_graph.refout0000644000000000000000000000166213610377563014517 00000000000000digraph { n0 -> n1; n0 -> n2; n0 -> n3; n0 -> n4; n0 -> n5; n0 -> n6; n0 -> n7; n0 -> n8; n0 -> n9; n0 -> n10; n0 -> n11; n0 -> n12; n0 -> n13; n0 -> n14; n0 -> n15; n0 -> n16; n0 -> n17; n0 -> n18; n0 -> n19; n0 -> n20; n0 -> n21; n0 -> n22; n0 -> n23; n0 -> n24; n0 -> n25; n0 -> n26; n0 -> n27; n0 -> n28; n0 -> n29; n0 -> n30; n0 -> n31; n31 -> n1; n1 -> n2; n2 -> n3; n3 -> n4; n4 -> n5; n5 -> n6; n6 -> n7; n7 -> n8; n8 -> n9; n9 -> n10; n10 -> n11; n11 -> n12; n12 -> n13; n13 -> n14; n14 -> n15; n15 -> n16; n16 -> n17; n17 -> n18; n18 -> n19; n19 -> n20; n20 -> n21; n21 -> n22; n22 -> n23; n23 -> n24; n24 -> n25; n25 -> n26; n26 -> n27; n27 -> n28; n28 -> n29; n29 -> n30; n30 -> n31; } frr-7.2.1/tests/lib/test_heavy.c0000644000000000000000000000514613610377563013451 00000000000000/* * This file is part of Quagga. * * Quagga is free software; you can redistribute it and/or modify it * under the terms of the GNU General Public License as published by the * Free Software Foundation; either version 2, or (at your option) any * later version. * * Quagga 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; see the file COPYING; if not, write to the Free Software * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA */ /* This programme shows the effects of 'heavy' long-running functions * on the cooperative threading model. * * Run it with a config file containing 'password whatever', telnet to it * (it defaults to port 4000) and enter the 'clear foo string' command. * then type whatever and observe that the vty interface is unresponsive * for quite a period of time, due to the clear_something command * taking a very long time to complete. */ #include #include "thread.h" #include "vty.h" #include "command.h" #include "memory.h" #include #include "tests.h" enum { ITERS_FIRST = 0, ITERS_ERR = 100, ITERS_LATER = 400, ITERS_PRINT = 10, ITERS_MAX = 1000, }; static void slow_func(struct vty *vty, const char *str, const int i) { double x = 1; int j; for (j = 0; j < 300; j++) x += sin(x) * j; if ((i % ITERS_LATER) == 0) printf("%s: %d, temporary error, save this somehow and do it later..\n", __func__, i); if ((i % ITERS_ERR) == 0) printf("%s: hard error\n", __func__); if ((i % ITERS_PRINT) == 0) printf("%s did %d, x = %g\n", str, i, x); } static void clear_something(struct vty *vty, const char *str) { int i; /* this could be like iterating through 150k of route_table * or worse, iterating through a list of peers, to bgp_stop them with * each having 150k route tables to process... */ for (i = ITERS_FIRST; i < ITERS_MAX; i++) slow_func(vty, str, i); } DEFUN (clear_foo, clear_foo_cmd, "clear foo LINE...", "clear command\n" "arbitrary string\n") { char *str; if (!argc) { vty_out(vty, "%% string argument required\n"); return CMD_WARNING; } str = argv_concat(argv, argc, 0); clear_something(vty, str); XFREE(MTYPE_TMP, str); return CMD_SUCCESS; } static void slow_vty_init(void) { install_element(VIEW_NODE, &clear_foo_cmd); } void test_init(void) { slow_vty_init(); } frr-7.2.1/tests/lib/test_heavy_thread.c0000644000000000000000000000607013610377563014775 00000000000000/* * This file is part of Quagga. * * Quagga is free software; you can redistribute it and/or modify it * under the terms of the GNU General Public License as published by the * Free Software Foundation; either version 2, or (at your option) any * later version. * * Quagga 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; see the file COPYING; if not, write to the Free Software * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA */ /* This programme shows the effects of 'heavy' long-running functions * on the cooperative threading model, as demonstrated by heavy.c, and how * they can be mitigated using a background thread. * * Run it with a config file containing 'password whatever', telnet to it * (it defaults to port 4000) and enter the 'clear foo string' command. * then type whatever and observe that, unlike heavy.c, the vty interface * remains responsive. */ #include #include #include "thread.h" #include "vty.h" #include "command.h" #include "memory.h" #include "log.h" #include "tests.h" extern struct thread_master *master; enum { ITERS_FIRST = 0, ITERS_ERR = 100, ITERS_LATER = 400, ITERS_PRINT = 10, ITERS_MAX = 1000, }; struct work_state { struct vty *vty; char *str; int i; }; static void slow_func(struct vty *vty, const char *str, const int i) { double x = 1; int j; for (j = 0; j < 300; j++) x += sin(x) * j; if ((i % ITERS_LATER) == 0) printf("%s: %d, temporary error, save this somehow and do it later..\n", __func__, i); if ((i % ITERS_ERR) == 0) printf("%s: hard error\n", __func__); if ((i % ITERS_PRINT) == 0) printf("%s did %d, x = %g\n", str, i, x); } static int clear_something(struct thread *thread) { struct work_state *ws = THREAD_ARG(thread); /* this could be like iterating through 150k of route_table * or worse, iterating through a list of peers, to bgp_stop them with * each having 150k route tables to process... */ while (ws->i < ITERS_MAX) { slow_func(ws->vty, ws->str, ws->i); ws->i++; if (thread_should_yield(thread)) { thread_add_timer_msec(master, clear_something, ws, 0, NULL); return 0; } } /* All done! */ XFREE(MTYPE_TMP, ws->str); XFREE(MTYPE_TMP, ws); return 0; } DEFUN (clear_foo, clear_foo_cmd, "clear foo LINE...", "clear command\n" "arbitrary string\n") { char *str; struct work_state *ws; if (!argc) { vty_out(vty, "%% string argument required\n"); return CMD_WARNING; } str = argv_concat(argv, argc, 0); ws = XMALLOC(MTYPE_TMP, sizeof(*ws)); ws->str = XSTRDUP(MTYPE_TMP, str); ws->vty = vty; ws->i = ITERS_FIRST; thread_add_timer_msec(master, clear_something, ws, 0, NULL); return CMD_SUCCESS; } void test_init(void) { install_element(VIEW_NODE, &clear_foo_cmd); } frr-7.2.1/tests/lib/test_heavy_wq.c0000644000000000000000000000750313610377563014157 00000000000000/* * This file is part of Quagga. * * Quagga is free software; you can redistribute it and/or modify it * under the terms of the GNU General Public License as published by the * Free Software Foundation; either version 2, or (at your option) any * later version. * * Quagga 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; see the file COPYING; if not, write to the Free Software * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA */ /* This programme shows the effects of 'heavy' long-running functions * on the cooperative threading model. * * Run it with a config file containing 'password whatever', telnet to it * (it defaults to port 4000) and enter the 'clear foo string' command. * then type whatever and observe that the vty interface is unresponsive * for quite a period of time, due to the clear_something command * taking a very long time to complete. */ #include #include "thread.h" #include "vty.h" #include "command.h" #include "memory.h" #include "log.h" #include "workqueue.h" #include #include "tests.h" DEFINE_MGROUP(TEST_HEAVYWQ, "heavy-wq test") DEFINE_MTYPE_STATIC(TEST_HEAVYWQ, WQ_NODE, "heavy_wq_node") DEFINE_MTYPE_STATIC(TEST_HEAVYWQ, WQ_NODE_STR, "heavy_wq_node->str") extern struct thread_master *master; static struct work_queue *heavy_wq; struct heavy_wq_node { char *str; int i; }; enum { ITERS_FIRST = 0, ITERS_ERR = 100, ITERS_LATER = 400, ITERS_PRINT = 10, ITERS_MAX = 1000, }; static void heavy_wq_add(struct vty *vty, const char *str, int i) { struct heavy_wq_node *hn; hn = XCALLOC(MTYPE_WQ_NODE, sizeof(struct heavy_wq_node)); hn->i = i; hn->str = XSTRDUP(MTYPE_WQ_NODE_STR, str); work_queue_add(heavy_wq, hn); return; } static void slow_func_err(struct work_queue *wq, struct work_queue_item *item) { printf("%s: running error function\n", __func__); } static void slow_func_del(struct work_queue *wq, void *data) { struct heavy_wq_node *hn = data; assert(hn && hn->str); printf("%s: %s\n", __func__, hn->str); XFREE(MTYPE_WQ_NODE_STR, hn->str); hn->str = NULL; XFREE(MTYPE_WQ_NODE, hn); } static wq_item_status slow_func(struct work_queue *wq, void *data) { struct heavy_wq_node *hn = data; double x = 1; int j; assert(hn && hn->str); for (j = 0; j < 300; j++) x += sin(x) * j; if ((hn->i % ITERS_LATER) == 0) return WQ_RETRY_LATER; if ((hn->i % ITERS_ERR) == 0) return WQ_RETRY_NOW; if ((hn->i % ITERS_PRINT) == 0) printf("%s did %d, x = %g\n", hn->str, hn->i, x); return WQ_SUCCESS; } static void clear_something(struct vty *vty, const char *str) { int i; /* this could be like iterating through 150k of route_table * or worse, iterating through a list of peers, to bgp_stop them with * each having 150k route tables to process... */ for (i = ITERS_FIRST; i < ITERS_MAX; i++) heavy_wq_add(vty, str, i); } DEFUN (clear_foo, clear_foo_cmd, "clear foo LINE...", "clear command\n" "arbitrary string\n") { char *str; if (!argc) { vty_out(vty, "%% string argument required\n"); return CMD_WARNING; } str = argv_concat(argv, argc, 0); clear_something(vty, str); XFREE(MTYPE_TMP, str); return CMD_SUCCESS; } static int heavy_wq_init(void) { heavy_wq = work_queue_new(master, "heavy_work_queue"); heavy_wq->spec.workfunc = &slow_func; heavy_wq->spec.errorfunc = &slow_func_err; heavy_wq->spec.del_item_data = &slow_func_del; heavy_wq->spec.max_retries = 3; heavy_wq->spec.hold = 1000; return 0; } void test_init(void) { install_element(VIEW_NODE, &clear_foo_cmd); heavy_wq_init(); } frr-7.2.1/tests/lib/test_idalloc.c0000644000000000000000000001264713610377563013750 00000000000000#ifdef HAVE_CONFIG_H #include "config.h" #endif #include "id_alloc.h" #include #include #include #include #define IDS_PER_PAGE (1<<(IDALLOC_OFFSET_BITS + IDALLOC_WORD_BITS)) char allocated_markers[IDS_PER_PAGE*3]; int main(int argc, char **argv) { int i, val; uint32_t pg; struct id_alloc *a; /* 1. Rattle test, shake it a little and make sure it doesn't make any * noise :) */ a = idalloc_new("Rattle test"); for (i = 0; i < 1000000; i++) assert(idalloc_allocate(a) != 0); idalloc_destroy(a); /* 2. Reserve a few low IDs, make sure they are skipped by normal * allocation. */ a = idalloc_new("Low Reservations"); assert(idalloc_reserve(a, 1) == 1); assert(idalloc_reserve(a, 3) == 3); assert(idalloc_reserve(a, 5) == 5); for (i = 0; i < 100; i++) { val = idalloc_allocate(a); assert(val != 1 && val != 3 && val != 5); } idalloc_destroy(a); /* 3. Single page testing. Check that IDs are kept unique, and all IDs * in the existing page are allocated before a new page is added. */ memset(allocated_markers, 0, sizeof(allocated_markers)); allocated_markers[IDALLOC_INVALID] = 1; a = idalloc_new("Single Page"); /* reserve the rest of the first page */ for (i = 0; i < IDS_PER_PAGE - 1; i++) { val = idalloc_allocate(a); assert(val < IDS_PER_PAGE); assert(allocated_markers[val] == 0); assert(a->capacity == IDS_PER_PAGE); allocated_markers[val] = 1; } /* Check that the count is right */ assert(a->allocated == IDS_PER_PAGE); /* Free some IDs out of the middle. */ idalloc_free(a, 300); allocated_markers[300] = 0; idalloc_free(a, 400); allocated_markers[400] = 0; idalloc_free(a, 500); allocated_markers[500] = 0; assert(a->allocated == IDS_PER_PAGE-3); /* Allocate the three IDs back and make sure they are pulled from the * set just freed */ for (i = 0; i < 3; i++) { val = idalloc_allocate(a); assert(val < IDS_PER_PAGE); assert(allocated_markers[val] == 0); assert(a->capacity == IDS_PER_PAGE); allocated_markers[val] = 1; } idalloc_destroy(a); /* 4. Multi-page testing. */ memset(allocated_markers, 0, sizeof(allocated_markers)); allocated_markers[IDALLOC_INVALID] = 1; a = idalloc_new("Multi-page"); /* reserve the rest of the first page and all of the second and third */ for (i = 0; i < 3 * IDS_PER_PAGE - 1; i++) { val = idalloc_allocate(a); assert(val < 3*IDS_PER_PAGE); assert(allocated_markers[val] == 0); allocated_markers[val] = 1; } assert(a->capacity == 3*IDS_PER_PAGE); assert(a->allocated == 3*IDS_PER_PAGE); /* Free two IDs from each page. */ for (i = 0; i < 3; i++) { idalloc_free(a, 7 + i*IDS_PER_PAGE); allocated_markers[7 + i*IDS_PER_PAGE] = 0; idalloc_free(a, 4 + i*IDS_PER_PAGE); allocated_markers[4 + i*IDS_PER_PAGE] = 0; } assert(a->allocated == 3*IDS_PER_PAGE - 6); /* Allocate the six IDs back and make sure they are pulled from the set * just freed. */ for (i = 0; i < 6; i++) { val = idalloc_allocate(a); assert(val < 3*IDS_PER_PAGE); assert(allocated_markers[val] == 0); assert(a->capacity == 3*IDS_PER_PAGE); allocated_markers[val] = 1; } assert(a->capacity == 3*IDS_PER_PAGE); assert(a->allocated == 3*IDS_PER_PAGE); /* Walk each allocated ID. Free it, then re-allocate it back. */ for (i = 1; i < 3 * IDS_PER_PAGE - 1; i++) { idalloc_free(a, i); val = idalloc_allocate(a); assert(val == i); assert(a->capacity == 3*IDS_PER_PAGE); assert(a->allocated == 3*IDS_PER_PAGE); } idalloc_destroy(a); /* 5. Weird Reservations * idalloc_reserve exists primarily to black out low numbered IDs that * are reserved for special cases. However, we will test it for more * complex use cases to avoid unpleasant surprises. */ memset(allocated_markers, 0, sizeof(allocated_markers)); allocated_markers[IDALLOC_INVALID] = 1; a = idalloc_new("Weird Reservations"); /* Start with 3 pages fully allocated. */ for (i = 0; i < 3 * IDS_PER_PAGE - 1; i++) { val = idalloc_allocate(a); assert(val < 3*IDS_PER_PAGE); assert(allocated_markers[val] == 0); allocated_markers[val] = 1; } assert(a->capacity == 3*IDS_PER_PAGE); assert(a->allocated == 3*IDS_PER_PAGE); /* Free a bit out of each of the three pages. Then reserve one of the * three freed IDs. Finally, allocate the other two freed IDs. Do this * each of three ways. (Reserve out of the first, seconds then third * page.) * The intent here is to exercise the rare cases on reserve_bit's * linked-list removal in the case that it is not removing the first * page with a free bit in its list of pages with free bits. */ for (pg = 0; pg < 3; pg++) { /* free a bit out of each of the three pages */ for (i = 0; i < 3; i++) { idalloc_free(a, i*IDS_PER_PAGE + 17); allocated_markers[i*IDS_PER_PAGE + 17] = 0; } assert(a->capacity == 3*IDS_PER_PAGE); assert(a->allocated == 3*IDS_PER_PAGE-3); /* Reserve one of the freed IDs */ assert(idalloc_reserve(a, pg*IDS_PER_PAGE + 17) == pg*IDS_PER_PAGE + 17); allocated_markers[pg*IDS_PER_PAGE + 17] = 1; assert(a->capacity == 3*IDS_PER_PAGE); assert(a->allocated == 3*IDS_PER_PAGE-2); /* Allocate the other two back */ for (i = 0; i < 2; i++) { val = idalloc_allocate(a); assert(val < 3*IDS_PER_PAGE); assert(allocated_markers[val] == 0); allocated_markers[val] = 1; } assert(a->capacity == 3*IDS_PER_PAGE); assert(a->allocated == 3*IDS_PER_PAGE); } idalloc_destroy(a); puts("ID Allocator test successful.\n"); return 0; } frr-7.2.1/tests/lib/test_memory.c0000644000000000000000000000647313610377563013651 00000000000000/* * This file is part of Quagga. * * Quagga is free software; you can redistribute it and/or modify it * under the terms of the GNU General Public License as published by the * Free Software Foundation; either version 2, or (at your option) any * later version. * * Quagga 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; see the file COPYING; if not, write to the Free Software * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA */ #include #include DEFINE_MGROUP(TEST_MEMORY, "memory test") DEFINE_MTYPE_STATIC(TEST_MEMORY, TEST, "generic test mtype") /* Memory torture tests * * Tests below are generic but comments are focused on interaction with * Paul's proposed memory 'quick' cache, which may never be included in * CVS */ struct thread_master *master; #if 0 /* set to 1 to use system alloc directly */ #undef XMALLOC #undef XCALLOC #undef XREALLOC #undef XFREE #define XMALLOC(T,S) malloc((S)) #define XCALLOC(T,S) calloc(1, (S)) #define XREALLOC(T,P,S) realloc((P),(S)) #define XFREE(T,P) free((P)) #endif #define TIMES 10 int main(int argc, char **argv) { void *a[10]; int i; printf("malloc x, malloc x, free, malloc x, free free\n\n"); /* simple case, test cache */ for (i = 0; i < TIMES; i++) { a[0] = XMALLOC(MTYPE_TEST, 1024); memset(a[0], 1, 1024); a[1] = XMALLOC(MTYPE_TEST, 1024); memset(a[1], 1, 1024); XFREE(MTYPE_TEST, a[0]); /* should go to cache */ a[0] = XMALLOC(MTYPE_TEST, 1024); /* should be satisfied from cache */ XFREE(MTYPE_TEST, a[0]); XFREE(MTYPE_TEST, a[1]); } printf("malloc x, malloc y, free x, malloc y, free free\n\n"); /* cache should go invalid, valid, invalid, etc.. */ for (i = 0; i < TIMES; i++) { a[0] = XMALLOC(MTYPE_TEST, 512); memset(a[0], 1, 512); a[1] = XMALLOC(MTYPE_TEST, 1024); /* invalidate cache */ memset(a[1], 1, 1024); XFREE(MTYPE_TEST, a[0]); a[0] = XMALLOC(MTYPE_TEST, 1024); XFREE(MTYPE_TEST, a[0]); XFREE(MTYPE_TEST, a[1]); /* cache should become valid again on next request */ } printf("calloc\n\n"); /* test calloc */ for (i = 0; i < TIMES; i++) { a[0] = XCALLOC(MTYPE_TEST, 1024); memset(a[0], 1, 1024); a[1] = XCALLOC(MTYPE_TEST, 512); /* invalidate cache */ memset(a[1], 1, 512); XFREE(MTYPE_TEST, a[1]); XFREE(MTYPE_TEST, a[0]); /* alloc == 0, cache can become valid again on next request */ } printf("calloc and realloc\n\n"); /* check calloc + realloc */ for (i = 0; i < TIMES; i++) { printf("calloc a0 1024\n"); a[0] = XCALLOC(MTYPE_TEST, 1024); memset(a[0], 1, 1024 / 2); printf("calloc 1 1024\n"); a[1] = XCALLOC(MTYPE_TEST, 1024); memset(a[1], 1, 1024 / 2); printf("realloc 0 1024\n"); a[3] = XREALLOC(MTYPE_TEST, a[0], 2048); /* invalidate cache */ if (a[3] != NULL) a[0] = a[3]; memset(a[0], 1, 1024); printf("calloc 2 512\n"); a[2] = XCALLOC(MTYPE_TEST, 512); memset(a[2], 1, 512); printf("free 1 0 2\n"); XFREE(MTYPE_TEST, a[1]); XFREE(MTYPE_TEST, a[0]); XFREE(MTYPE_TEST, a[2]); /* alloc == 0, cache valid next request */ } return 0; } frr-7.2.1/tests/lib/test_nexthop_iter.c0000644000000000000000000001577213610377563015053 00000000000000/* * Recursive Nexthop Iterator test. * This tests the ALL_NEXTHOPS macro. * * Copyright (C) 2012 by Open Source Routing. * Copyright (C) 2012 by Internet Systems Consortium, Inc. ("ISC") * * This file is part of Quagga * * Quagga is free software; you can redistribute it and/or modify it * under the terms of the GNU General Public License as published by the * Free Software Foundation; either version 2, or (at your option) any * later version. * * Quagga 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; see the file COPYING; if not, write to the Free Software * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA */ #include #include "zebra/rib.h" #include "prng.h" struct thread_master *master; static int verbose; static void str_append(char **buf, const char *repr) { if (*buf) { *buf = realloc(*buf, strlen(*buf) + strlen(repr) + 1); assert(*buf); strncpy((*buf) + strlen(*buf), repr, strlen(repr) + 1); } else { *buf = strdup(repr); assert(*buf); } } static void str_appendf(char **buf, const char *format, ...) { va_list ap; int rv; char *pbuf; va_start(ap, format); rv = vasprintf(&pbuf, format, ap); va_end(ap); assert(rv >= 0); str_append(buf, pbuf); free(pbuf); } /* This structure contains a nexthop chain * and its expected representation */ struct nexthop_chain { /* Head of the chain */ struct nexthop_group head; /* Last nexthop in top chain */ struct nexthop *current_top; /* Last nexthop in current recursive chain */ struct nexthop *current_recursive; /* Expected string representation. */ char *repr; }; static struct nexthop_chain *nexthop_chain_new(void) { struct nexthop_chain *rv; rv = calloc(sizeof(*rv), 1); assert(rv); return rv; } static void nexthop_chain_add_top(struct nexthop_chain *nc) { struct nexthop *nh; nh = calloc(sizeof(*nh), 1); assert(nh); if (nc->head.nexthop) { nc->current_top->next = nh; nh->prev = nc->current_top; nc->current_top = nh; } else { nc->head.nexthop = nc->current_top = nh; } nc->current_recursive = NULL; str_appendf(&nc->repr, "%p\n", nh); } static void add_string_representation(char **repr, struct nexthop *nh) { struct nexthop *parent; /* add indentations first */ parent = nh->rparent; while (parent) { str_appendf(repr, " "); parent = parent->rparent; } str_appendf(repr, "%p\n", nh); } static void start_recursive_chain(struct nexthop_chain *nc, struct nexthop *nh) { SET_FLAG(nc->current_top->flags, NEXTHOP_FLAG_RECURSIVE); nc->current_top->resolved = nh; nh->rparent = nc->current_top; nc->current_recursive = nh; } static void nexthop_chain_add_recursive(struct nexthop_chain *nc) { struct nexthop *nh; nh = calloc(sizeof(*nh), 1); assert(nh); assert(nc->current_top); if (nc->current_recursive) { nc->current_recursive->next = nh; nh->prev = nc->current_recursive; nh->rparent = nc->current_recursive->rparent; nc->current_recursive = nh; } else start_recursive_chain(nc, nh); add_string_representation(&nc->repr, nh); } static void nexthop_chain_add_recursive_level(struct nexthop_chain *nc) { struct nexthop *nh; nh = calloc(sizeof(*nh), 1); assert(nh); assert(nc->current_top); if (nc->current_recursive) { SET_FLAG(nc->current_recursive->flags, NEXTHOP_FLAG_RECURSIVE); nc->current_recursive->resolved = nh; nh->rparent = nc->current_recursive; nc->current_recursive = nh; } else start_recursive_chain(nc, nh); add_string_representation(&nc->repr, nh); } static void nexthop_clear_recursive(struct nexthop *tcur) { if (!tcur) return; if (CHECK_FLAG(tcur->flags, NEXTHOP_FLAG_RECURSIVE)) nexthop_clear_recursive(tcur->resolved); if (tcur->next) nexthop_clear_recursive(tcur->next); free(tcur); } static void nexthop_chain_clear(struct nexthop_chain *nc) { nexthop_clear_recursive(nc->head.nexthop); nc->head.nexthop = nc->current_top = nc->current_recursive = NULL; free(nc->repr); nc->repr = NULL; } static void nexthop_chain_free(struct nexthop_chain *nc) { if (!nc) return; nexthop_chain_clear(nc); free(nc); } /* This function builds a string representation of * the nexthop chain using the ALL_NEXTHOPS macro. * It verifies that the ALL_NEXTHOPS macro iterated * correctly over the nexthop chain by comparing the * generated representation with the expected representation. */ static void nexthop_chain_verify_iter(struct nexthop_chain *nc) { struct nexthop *nh; char *repr = NULL; for (ALL_NEXTHOPS(nc->head, nh)) add_string_representation(&repr, nh); if (repr && verbose) printf("===\n%s", repr); assert((!repr && !nc->repr) || (repr && nc->repr && !strcmp(repr, nc->repr))); free(repr); } /* This test run builds a simple nexthop chain * with some recursive nexthops and verifies that * the iterator works correctly in each stage along * the way. */ static void test_run_first(void) { struct nexthop_chain *nc; nc = nexthop_chain_new(); nexthop_chain_verify_iter(nc); nexthop_chain_add_top(nc); nexthop_chain_verify_iter(nc); nexthop_chain_add_top(nc); nexthop_chain_verify_iter(nc); nexthop_chain_add_recursive(nc); nexthop_chain_verify_iter(nc); nexthop_chain_add_recursive(nc); nexthop_chain_verify_iter(nc); nexthop_chain_add_top(nc); nexthop_chain_verify_iter(nc); nexthop_chain_add_top(nc); nexthop_chain_verify_iter(nc); nexthop_chain_add_top(nc); nexthop_chain_verify_iter(nc); nexthop_chain_add_recursive(nc); nexthop_chain_verify_iter(nc); nexthop_chain_add_recursive(nc); nexthop_chain_verify_iter(nc); nexthop_chain_add_recursive(nc); nexthop_chain_verify_iter(nc); nexthop_chain_add_recursive_level(nc); nexthop_chain_verify_iter(nc); nexthop_chain_add_recursive(nc); nexthop_chain_verify_iter(nc); nexthop_chain_add_recursive(nc); nexthop_chain_verify_iter(nc); nexthop_chain_add_top(nc); nexthop_chain_verify_iter(nc); nexthop_chain_free(nc); } /* This test run builds numerous random * nexthop chain configurations and verifies * that the iterator correctly progresses * through each. */ static void test_run_prng(void) { struct nexthop_chain *nc; struct prng *prng; int i; nc = nexthop_chain_new(); prng = prng_new(0); for (i = 0; i < 1000000; i++) { switch (prng_rand(prng) % 10) { case 0: nexthop_chain_clear(nc); break; case 1: case 2: case 3: case 4: case 5: nexthop_chain_add_top(nc); break; case 6: case 7: case 8: if (nc->current_top) nexthop_chain_add_recursive(nc); break; case 9: if (nc->current_top) nexthop_chain_add_recursive_level(nc); break; } nexthop_chain_verify_iter(nc); } nexthop_chain_free(nc); prng_free(prng); } int main(int argc, char **argv) { if (argc >= 2 && !strcmp("-v", argv[1])) verbose = 1; test_run_first(); printf("Simple test passed.\n"); test_run_prng(); printf("PRNG test passed.\n"); } frr-7.2.1/tests/lib/test_nexthop_iter.py0000644000000000000000000000030213610377563015240 00000000000000import frrtest class TestNexthopIter(frrtest.TestMultiOut): program = './test_nexthop_iter' TestNexthopIter.onesimple('Simple test passed.') TestNexthopIter.onesimple('PRNG test passed.') frr-7.2.1/tests/lib/test_ntop.c0000644000000000000000000000446213610377563013315 00000000000000/* * frr_inet_ntop() unit test * Copyright (C) 2019 David Lamparter * * This program is free software; you can redistribute it and/or modify it * under the terms of the 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; see the file COPYING; if not, write to the Free Software * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA */ #ifdef HAVE_CONFIG_H #include "config.h" #endif #include #include "tests/helpers/c/prng.h" /* NB: libfrr is NOT linked for this unit test! */ #define INET_NTOP_NO_OVERRIDE #include "lib/ntop.c" int main(int argc, char **argv) { size_t i, j, k, l; struct in_addr i4; struct in6_addr i6, i6check; char buf1[64], buf2[64]; const char *rv; struct prng *prng; prng = prng_new(0); /* IPv4 */ for (i = 0; i < 1000; i++) { i4.s_addr = prng_rand(prng); assert(frr_inet_ntop(AF_INET, &i4, buf1, sizeof(buf1))); assert(inet_ntop(AF_INET, &i4, buf2, sizeof(buf2))); assert(!strcmp(buf1, buf2)); } /* check size limit */ for (i = 0; i < sizeof(buf1); i++) { memset(buf2, 0xcc, sizeof(buf2)); rv = frr_inet_ntop(AF_INET, &i4, buf2, i); if (i < strlen(buf1) + 1) assert(!rv); else assert(rv && !strcmp(buf1, buf2)); } /* IPv6 */ for (i = 0; i < 10000; i++) { uint16_t *i6w = (uint16_t *)&i6; for (j = 0; j < 8; j++) i6w[j] = prng_rand(prng); /* clear some words */ l = prng_rand(prng) & 7; for (j = 0; j < l; j++) { uint32_t num = __builtin_ctz(prng_rand(prng)); uint32_t where = prng_rand(prng) & 7; for (k = where; k < where + num && k < 8; k++) i6w[k] = 0; } assert(frr_inet_ntop(AF_INET6, &i6, buf1, sizeof(buf1))); assert(inet_ntop(AF_INET6, &i6, buf2, sizeof(buf2))); if (strcmp(buf1, buf2)) printf("%-40s (FRR) != (SYS) %-40s\n", buf1, buf2); assert(inet_pton(AF_INET6, buf1, &i6check)); assert(!memcmp(&i6, &i6check, sizeof(i6))); } return 0; } frr-7.2.1/tests/lib/test_ntop.py0000644000000000000000000000015313610377563013514 00000000000000import frrtest class TestNtop(frrtest.TestMultiOut): program = './test_ntop' TestNtop.exit_cleanly() frr-7.2.1/tests/lib/test_prefix2str.c0000644000000000000000000000430713610377563014443 00000000000000/* * prefix2str() unit test * Copyright (C) 2019 David Lamparter * Portions: * Copyright (C) 2019 Cumulus Networks, Inc * Quentin Young * * This program is free software; you can redistribute it and/or modify it * under the terms of the 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; see the file COPYING; if not, write to the Free Software * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA */ #include #include "lib/prefix.h" #include "tests/helpers/c/prng.h" int main(int argc, char **argv) { size_t i, j, k, l; struct in6_addr i6; char buf1[64], buf2[64], ntopbuf[64]; struct prng *prng; struct prefix p = {}; prng = prng_new(0); /* IPv4 */ p.family = AF_INET; for (i = 0; i < 1000; i++) { p.u.prefix = prng_rand(prng); p.prefixlen = prng_rand(prng) >> 26; snprintf(buf1, sizeof(buf1), "%s/%d", inet_ntop(AF_INET, &p.u.prefix4, ntopbuf, sizeof(ntopbuf)), p.prefixlen); prefix2str(&p, buf2, sizeof(buf2)); assert(!strcmp(buf1, buf2)); fprintf(stdout, "%s\n", buf1); } /* IPv6 */ p.family = AF_INET6; for (i = 0; i < 10000; i++) { uint16_t *i6w = (uint16_t *)&i6; for (j = 0; j < 8; j++) i6w[j] = prng_rand(prng); /* clear some words */ l = prng_rand(prng) & 7; for (j = 0; j < l; j++) { uint32_t num = __builtin_ctz(prng_rand(prng)); uint32_t where = prng_rand(prng) & 7; for (k = where; k < where + num && k < 8; k++) i6w[k] = 0; } p.prefixlen = prng_rand(prng) >> 24; memcpy(&p.u.prefix, &i6, sizeof(i6)); snprintf(buf1, sizeof(buf1), "%s/%d", inet_ntop(AF_INET6, &p.u.prefix6, ntopbuf, sizeof(ntopbuf)), p.prefixlen); prefix2str(&p, buf2, sizeof(buf2)); assert(!strcmp(buf1, buf2)); fprintf(stdout, "%s\n", buf1); } return 0; } frr-7.2.1/tests/lib/test_prefix2str.py0000644000000000000000000000017513610377563014650 00000000000000import frrtest class TestPrefix2str(frrtest.TestMultiOut): program = './test_prefix2str' TestPrefix2str.exit_cleanly() frr-7.2.1/tests/lib/test_printfrr.c0000644000000000000000000000766313610377563014211 00000000000000/* * printfrr() unit test * Copyright (C) 2019 David Lamparter * * This program is free software; you can redistribute it and/or modify it * under the terms of the 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; see the file COPYING; if not, write to the Free Software * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA */ #include "zebra.h" #include #include "lib/printfrr.h" #include "lib/memory.h" #include "lib/prefix.h" static int errors; static void printcmp(const char *fmt, ...) PRINTFRR(1, 2); static void printcmp(const char *fmt, ...) { va_list ap; char buf[256], bufrr[256], *p; int cmp; memset(bufrr, 0xcc, sizeof(bufrr)); va_start(ap, fmt); vsnprintf(buf, sizeof(buf), fmt, ap); va_end(ap); va_start(ap, fmt); vsnprintfrr(bufrr, sizeof(bufrr), fmt, ap); va_end(ap); cmp = strcmp(buf, bufrr); /* OS dependent "+nan" vs. "nan" */ if (cmp && (p = strstr(bufrr, "+nan"))) { p[0] = ' '; if (!strcmp(buf, bufrr)) cmp = 0; p[0] = '+'; } printf("fmt: \"%s\"\nsys: \"%s\"\nfrr: \"%s\"\n%s\n\n", fmt, buf, bufrr, cmp ? "ERROR" : "ok"); if (cmp) errors++; } static void printchk(const char *ref, const char *fmt, ...) PRINTFRR(2, 3); static void printchk(const char *ref, const char *fmt, ...) { va_list ap; char bufrr[256]; memset(bufrr, 0xcc, sizeof(bufrr)); va_start(ap, fmt); vsnprintfrr(bufrr, sizeof(bufrr), fmt, ap); va_end(ap); printf("fmt: \"%s\"\nref: \"%s\"\nfrr: \"%s\"\n%s\n\n", fmt, ref, bufrr, strcmp(ref, bufrr) ? "ERROR" : "ok"); if (strcmp(ref, bufrr)) errors++; } int main(int argc, char **argv) { size_t i; float flts[] = { 123.456789, 23.456789e-30, 3.456789e+30, INFINITY, NAN, }; uint64_t ui64 = 0xfeed1278cafef00d; struct in_addr ip; char *p; char buf[256]; printcmp("%d %u %d %u", 123, 123, -456, -456); printcmp("%lld %llu %lld %llu", 123LL, 123LL, -456LL, -456LL); printcmp("%-20s,%20s,%.20s", "test", "test", "test"); printcmp("%-3s,%3s,%.3s", "test", "test", "test"); printcmp("%-6.3s,%6.3s,%6.3s", "test", "test", "test"); printcmp("%*s,%*s,%.*s", -3, "test", 3, "test", 3, "test"); for (i = 0; i < array_size(flts); i++) { printcmp("%-6.3e,%6.3e,%+06.3e", flts[i], flts[i], flts[i]); printcmp("%-6.3f,%6.3f,%+06.3f", flts[i], flts[i], flts[i]); printcmp("%-6.3g,%6.3g,%+06.3g", flts[i], flts[i], flts[i]); printcmp("%-6.3a,%6.3a,%+06.3a", flts[i], flts[i], flts[i]); } printchk("-77385308584349683 18369358765125201933 feed1278cafef00d", "%Ld %Lu %Lx", ui64, ui64, ui64); inet_aton("192.168.1.2", &ip); printchk("192.168.1.2", "%pI4", &ip); printchk(" 192.168.1.2", "%20pI4", &ip); printcmp("%p", &ip); snprintfrr(buf, sizeof(buf), "test%s", "#1"); csnprintfrr(buf, sizeof(buf), "test%s", "#2"); assert(strcmp(buf, "test#1test#2") == 0); p = asnprintfrr(MTYPE_TMP, buf, sizeof(buf), "test%s", "#3"); assert(p == buf); assert(strcmp(buf, "test#3") == 0); p = asnprintfrr(MTYPE_TMP, buf, 4, "test%s", "#4"); assert(p != buf); assert(strcmp(p, "test#4") == 0); XFREE(MTYPE_TMP, p); p = asprintfrr(MTYPE_TMP, "test%s", "#5"); assert(strcmp(p, "test#5") == 0); XFREE(MTYPE_TMP, p); struct prefix_sg sg; sg.src.s_addr = INADDR_ANY; sg.grp.s_addr = INADDR_ANY; printchk("(*,*)", "%pSG4", &sg); inet_aton("192.168.1.2", &sg.src); printchk("(192.168.1.2,*)", "%pSG4", &sg); inet_aton("224.1.2.3", &sg.grp); printchk("(192.168.1.2,224.1.2.3)", "%pSG4", &sg); sg.src.s_addr = INADDR_ANY; printchk("(*,224.1.2.3)", "%pSG4", &sg); return !!errors; } frr-7.2.1/tests/lib/test_printfrr.py0000644000000000000000000000016713610377563014407 00000000000000import frrtest class TestPrintfrr(frrtest.TestMultiOut): program = './test_printfrr' TestPrintfrr.exit_cleanly() frr-7.2.1/tests/lib/test_privs.c0000644000000000000000000000617713610377563013505 00000000000000/* * This file is part of Quagga. * * Quagga is free software; you can redistribute it and/or modify it * under the terms of the GNU General Public License as published by the * Free Software Foundation; either version 2, or (at your option) any * later version. * * Quagga 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; see the file COPYING; if not, write to the Free Software * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA */ #include #include #include "getopt.h" #include "privs.h" #include "memory.h" #include "memory_vty.h" zebra_capabilities_t _caps_p[] = { ZCAP_NET_RAW, ZCAP_BIND, ZCAP_NET_ADMIN, ZCAP_DAC_OVERRIDE, }; struct zebra_privs_t test_privs = { #if defined(FRR_USER) && defined(FRR_GROUP) .user = FRR_USER, .group = FRR_GROUP, #endif #if defined(VTY_GROUP) .vty_group = VTY_GROUP, #endif .caps_p = _caps_p, .cap_num_p = array_size(_caps_p), .cap_num_i = 0}; struct option longopts[] = {{"help", no_argument, NULL, 'h'}, {"user", required_argument, NULL, 'u'}, {"group", required_argument, NULL, 'g'}, {0}}; /* Help information display. */ static void usage(char *progname, int status) { if (status != 0) fprintf(stderr, "Try `%s --help' for more information.\n", progname); else { printf("Usage : %s [OPTION...]\n\ Daemon which does 'slow' things.\n\n\ -u, --user User to run as\n\ -g, --group Group to run as\n\ -h, --help Display this help and exit\n\ \n\ Report bugs to %s\n", progname, FRR_BUG_ADDRESS); } exit(status); } struct thread_master *master; /* main routine. */ int main(int argc, char **argv) { char *p; char *progname; struct zprivs_ids_t ids; /* Set umask before anything for security */ umask(0027); /* get program name */ progname = ((p = strrchr(argv[0], '/')) ? ++p : argv[0]); while (1) { int opt; opt = getopt_long(argc, argv, "hu:g:", longopts, 0); if (opt == EOF) break; switch (opt) { case 0: break; case 'u': test_privs.user = optarg; break; case 'g': test_privs.group = optarg; break; case 'h': usage(progname, 0); break; default: usage(progname, 1); break; } } /* Library inits. */ memory_init(); zprivs_preinit(&test_privs); zprivs_init(&test_privs); #define PRIV_STATE() \ ((test_privs.current_state() == ZPRIVS_RAISED) ? "Raised" : "Lowered") printf("%s\n", PRIV_STATE()); frr_with_privs(&test_privs) { printf("%s\n", PRIV_STATE()); } printf("%s\n", PRIV_STATE()); zprivs_get_ids(&ids); /* terminate privileges */ zprivs_terminate(&test_privs); /* but these should continue to work... */ printf("%s\n", PRIV_STATE()); frr_with_privs(&test_privs) { printf("%s\n", PRIV_STATE()); } printf("%s\n", PRIV_STATE()); zprivs_get_ids(&ids); printf("terminating\n"); return 0; } frr-7.2.1/tests/lib/test_ringbuf.c0000644000000000000000000001317013610377563013765 00000000000000/* * Circular buffer tests. * Copyright (C) 2017 Cumulus Networks * Quentin Young * * This program is free software; you can redistribute it and/or modify it * under the terms of the 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; see the file COPYING; if not, write to the Free Software * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA */ #include #include #include "ringbuf.h" static void validate_state(struct ringbuf *buf, size_t size, size_t contains) { assert(buf->size == size); assert(ringbuf_remain(buf) == contains); assert(ringbuf_space(buf) == buf->size - contains); assert(buf->empty != (bool)contains); } int main(int argc, char **argv) { struct ringbuf *soil = ringbuf_new(BUFSIZ); validate_state(soil, BUFSIZ, 0); /* verify reset functionality on clean buffer */ printf("Validating reset on empty buffer...\n"); ringbuf_reset(soil); validate_state(soil, BUFSIZ, 0); /* put one byte */ printf("Validating write...\n"); uint8_t walnut = 47; assert(ringbuf_put(soil, &walnut, sizeof(walnut)) == 1); validate_state(soil, BUFSIZ, 1); /* validate read limitations */ printf("Validating read limits...\n"); uint8_t nuts[2]; assert(ringbuf_get(soil, &nuts, sizeof(nuts)) == 1); /* reset */ printf("Validating reset on full buffer...\n"); ringbuf_reset(soil); validate_state(soil, BUFSIZ, 0); /* copy stack garbage to buffer */ printf("Validating big write...\n"); uint8_t compost[BUFSIZ]; assert(ringbuf_put(soil, &compost, sizeof(compost)) == BUFSIZ); validate_state(soil, BUFSIZ, BUFSIZ); assert(soil->start == 0); assert(soil->end == 0); /* read 15 bytes of garbage */ printf("Validating read...\n"); assert(ringbuf_get(soil, &compost, 15) == 15); validate_state(soil, BUFSIZ, BUFSIZ - 15); assert(soil->start == 15); assert(soil->end == 0); /* put another 10 bytes and validate wraparound */ printf("Validating wraparound...\n"); assert(ringbuf_put(soil, &compost[BUFSIZ/2], 10) == 10); validate_state(soil, BUFSIZ, BUFSIZ - 15 + 10); assert(soil->start == 15); assert(soil->end == 10); /* put another 15 bytes and validate state */ printf("Validating size limits...\n"); assert(ringbuf_put(soil, &compost, 15) == 5); validate_state(soil, BUFSIZ, BUFSIZ); /* read entire buffer */ printf("Validating big read...\n"); assert(ringbuf_get(soil, &compost, BUFSIZ) == BUFSIZ); validate_state(soil, BUFSIZ, 0); assert(soil->empty = true); assert(soil->start == soil->end); assert(soil->start == 15); /* read empty buffer */ printf("Validating empty read...\n"); assert(ringbuf_get(soil, &compost, 1) == 0); validate_state(soil, BUFSIZ, 0); /* reset, validate state */ printf("Validating reset...\n"); ringbuf_reset(soil); validate_state(soil, BUFSIZ, 0); assert(soil->start == 0); assert(soil->end == 0); /* wipe, validate state */ printf("Validating wipe...\n"); memset(&compost, 0x00, sizeof(compost)); ringbuf_wipe(soil); assert(memcmp(&compost, soil->data, sizeof(compost)) == 0); /* validate maximum write */ printf("Validating very big write...\n"); const char flower[BUFSIZ * 2]; assert(ringbuf_put(soil, &flower, sizeof(flower)) == BUFSIZ); validate_state(soil, BUFSIZ, BUFSIZ); /* wipe, validate state */ printf("Validating wipe...\n"); memset(&compost, 0x00, sizeof(compost)); ringbuf_wipe(soil); assert(memcmp(&compost, soil->data, sizeof(compost)) == 0); /* validate simple data encode / decode */ const char *organ = "seed"; printf("Encoding: '%s'\n", organ); assert(ringbuf_put(soil, organ, strlen(organ)) == 4); char water[strlen(organ) + 1]; assert(ringbuf_get(soil, &water, strlen(organ)) == 4); water[strlen(organ)] = '\0'; printf("Retrieved: '%s'\n", water); validate_state(soil, BUFSIZ, 0); /* validate simple data encode / decode across ring boundary */ soil->start = soil->size - 2; soil->end = soil->start; const char *phloem = "root"; printf("Encoding: '%s'\n", phloem); assert(ringbuf_put(soil, phloem, strlen(phloem)) == 4); char xylem[strlen(phloem) + 1]; assert(ringbuf_get(soil, &xylem, 100) == 4); xylem[strlen(phloem)] = '\0'; printf("Retrieved: '%s'\n", xylem); ringbuf_wipe(soil); /* validate simple data peek across ring boundary */ soil->start = soil->size - 2; soil->end = soil->start; const char *cytoplasm = "tree"; printf("Encoding: '%s'\n", cytoplasm); assert(ringbuf_put(soil, cytoplasm, strlen(cytoplasm)) == 4); char chloroplast[strlen(cytoplasm) + 1]; assert(ringbuf_peek(soil, 2, &chloroplast[0], 100) == 2); assert(ringbuf_peek(soil, 0, &chloroplast[2], 2) == 2); chloroplast[strlen(cytoplasm)] = '\0'; assert(!strcmp(chloroplast, "eetr")); printf("Retrieved: '%s'\n", chloroplast); printf("Deleting...\n"); ringbuf_del(soil); printf("Creating new buffer...\n"); soil = ringbuf_new(15); soil->start = soil->end = 7; /* validate data encode of excessive data */ const char *twenty = "vascular plants----"; char sixteen[16]; printf("Encoding: %s\n", twenty); assert(ringbuf_put(soil, twenty, strlen(twenty)) == 15); assert(ringbuf_get(soil, sixteen, 20)); sixteen[15] = '\0'; printf("Retrieved: %s\n", sixteen); assert(!strcmp(sixteen, "vascular plants")); printf("Deleting...\n"); ringbuf_del(soil); printf("Done.\n"); return 0; } frr-7.2.1/tests/lib/test_ringbuf.py0000644000000000000000000000016413610377563014172 00000000000000import frrtest class TestRingbuf(frrtest.TestMultiOut): program = './test_ringbuf' TestRingbuf.exit_cleanly() frr-7.2.1/tests/lib/test_segv.c0000644000000000000000000000372413610377563013301 00000000000000/* * SEGV / backtrace handling test. * * copied from test-sig.c * * Copyright (C) 2013 by David Lamparter, Open Source Routing. * Copyright (C) 2013 by Internet Systems Consortium, Inc. ("ISC") * * This file is part of Quagga * * Quagga is free software; you can redistribute it and/or modify it * under the terms of the GNU General Public License as published by the * Free Software Foundation; either version 2, or (at your option) any * later version. * * Quagga 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; see the file COPYING; if not, write to the Free Software * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA */ #include #include #include "lib/log.h" #include "lib/memory.h" struct quagga_signal_t sigs[] = {}; struct thread_master *master; void func1(int *arg); void func3(void); void func1(int *arg) { int *null = NULL; *null += 1; *arg = 1; } static void func2(size_t depth, int *arg) { /* variable stack frame size */ int buf[depth]; for (size_t i = 0; i < depth; i++) buf[i] = arg[i] + 1; if (depth > 0) func2(depth - 1, buf); else func1(&buf[0]); for (size_t i = 0; i < depth; i++) buf[i] = arg[i] + 2; } void func3(void) { int buf[6]; func2(6, buf); } static int threadfunc(struct thread *thread) { func3(); return 0; } int main(void) { master = thread_master_create(NULL); signal_init(master, array_size(sigs), sigs); openzlog("testsegv", "NONE", 0, LOG_CONS | LOG_NDELAY | LOG_PID, LOG_DAEMON); zlog_set_level(ZLOG_DEST_SYSLOG, ZLOG_DISABLED); zlog_set_level(ZLOG_DEST_STDOUT, LOG_DEBUG); zlog_set_level(ZLOG_DEST_MONITOR, ZLOG_DISABLED); thread_execute(master, threadfunc, 0, 0); exit(0); } frr-7.2.1/tests/lib/test_seqlock.c0000644000000000000000000000540013610377563013767 00000000000000/* * basic test for seqlock * * Copyright (C) 2015 David Lamparter * * This program is free software; you can redistribute it and/or modify it * under the terms of the GNU General Public License as published by the Free * Software Foundation; either version 2 of the License, or (at your option) * any later version. * * This program is distributed in the hope that it will be useful, but WITHOUT * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for * more details. * * You should have received a copy of the GNU General Public License along with * this program; if not, write to the Free Software Foundation, Inc., 59 Temple * Place, Suite 330, Boston, MA 02111-1307 USA */ #ifdef HAVE_CONFIG_H #include "config.h" #endif #include #include #include #include #include #include #include #include "monotime.h" #include "seqlock.h" static struct seqlock sqlo; static pthread_t thr1; static struct timeval start; static void writestr(const char *str) { struct iovec iov[2]; char buf[32]; int64_t usec = monotime_since(&start, NULL); snprintf(buf, sizeof(buf), "[%02"PRId64"] ", usec / 100000); iov[0].iov_base = buf; iov[0].iov_len = strlen(buf); iov[1].iov_base = (char *)str; iov[1].iov_len = strlen(str); writev(1, iov, 2); } static void *thr1func(void *arg) { assert(!seqlock_held(&sqlo)); assert(seqlock_check(&sqlo, 1)); seqlock_wait(&sqlo, 1); writestr("thr1 (unheld)\n"); sleep(2); assert(seqlock_held(&sqlo)); assert(seqlock_check(&sqlo, 1)); seqlock_wait(&sqlo, 1); writestr("thr1 @1\n"); seqlock_wait(&sqlo, 5); writestr("thr1 @5\n"); seqlock_wait(&sqlo, 9); writestr("thr1 @9\n"); seqlock_wait(&sqlo, 13); writestr("thr1 @13\n"); seqlock_wait(&sqlo, 17); writestr("thr1 @17\n"); seqlock_wait(&sqlo, 21); writestr("thr1 @21\n"); return NULL; } int main(int argc, char **argv) { monotime(&start); seqlock_init(&sqlo); assert(!seqlock_held(&sqlo)); seqlock_acquire_val(&sqlo, 1); assert(seqlock_held(&sqlo)); assert(seqlock_cur(&sqlo) == 1); assert(seqlock_bump(&sqlo) == 1); assert(seqlock_cur(&sqlo) == 5); assert(seqlock_bump(&sqlo) == 5); assert(seqlock_bump(&sqlo) == 9); assert(seqlock_bump(&sqlo) == 13); assert(seqlock_cur(&sqlo) == 17); assert(seqlock_held(&sqlo)); seqlock_release(&sqlo); assert(!seqlock_held(&sqlo)); pthread_create(&thr1, NULL, thr1func, NULL); sleep(1); writestr("main @5\n"); seqlock_acquire_val(&sqlo, 5); sleep(2); writestr("main @9\n"); seqlock_bump(&sqlo); sleep(1); writestr("main @17\n"); seqlock_acquire_val(&sqlo, 17); sleep(1); writestr("main @release\n"); seqlock_release(&sqlo); sleep(1); } frr-7.2.1/tests/lib/test_sig.c0000644000000000000000000000323113610377563013110 00000000000000/* * This file is part of Quagga. * * Quagga is free software; you can redistribute it and/or modify it * under the terms of the GNU General Public License as published by the * Free Software Foundation; either version 2, or (at your option) any * later version. * * Quagga 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; see the file COPYING; if not, write to the Free Software * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA */ #include #include #include "lib/log.h" #include "lib/memory.h" static void sighup(void) { printf("processed hup\n"); } static void sigusr1(void) { printf("processed usr1\n"); } static void sigusr2(void) { printf("processed usr2\n"); } struct quagga_signal_t sigs[] = {{ .signal = SIGHUP, .handler = &sighup, }, { .signal = SIGUSR1, .handler = &sigusr1, }, { .signal = SIGUSR2, .handler = &sigusr2, }}; struct thread_master *master; struct thread t; int main(void) { master = thread_master_create(NULL); signal_init(master, array_size(sigs), sigs); openzlog("testsig", "NONE", 0, LOG_CONS | LOG_NDELAY | LOG_PID, LOG_DAEMON); zlog_set_level(ZLOG_DEST_SYSLOG, ZLOG_DISABLED); zlog_set_level(ZLOG_DEST_STDOUT, LOG_DEBUG); zlog_set_level(ZLOG_DEST_MONITOR, ZLOG_DISABLED); while (thread_fetch(master, &t)) thread_call(&t); exit(0); } frr-7.2.1/tests/lib/test_srcdest_table.c0000644000000000000000000002543713610377563015160 00000000000000/* * Test srcdest table for correctness. * * Copyright (C) 2017 by David Lamparter & Christian Franke, * Open Source Routing / NetDEF Inc. * * This file is part of FreeRangeRouting (FRR) * * FRR is free software; you can redistribute it and/or modify it * under the terms of the GNU General Public License as published by the * Free Software Foundation; either version 2, or (at your option) any * later version. * * FRR 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; see the file COPYING; if not, write to the Free Software * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA */ #include #include "hash.h" #include "memory.h" #include "prefix.h" #include "prng.h" #include "srcdest_table.h" #include "table.h" /* Copied from ripngd/ripng_nexthop.h - maybe the whole s6_addr32 thing * should be added by autoconf if not present? */ #ifndef s6_addr32 #if defined(SUNOS_5) /* Some SunOS define s6_addr32 only to kernel */ #define s6_addr32 _S6_un._S6_u32 #else #define s6_addr32 __u6_addr.__u6_addr32 #endif /* SUNOS_5 */ #endif /*s6_addr32*/ struct thread_master *master; /* This structure is copied from lib/srcdest_table.c to which it is * private as far as other parts of Quagga are concerned. */ struct srcdest_rnode { /* must be first in structure for casting to/from route_node */ ROUTE_NODE_FIELDS; struct route_table *src_table; }; struct test_state { struct route_table *table; struct hash *log; }; static char *format_srcdest(const struct prefix_ipv6 *dst_p, const struct prefix_ipv6 *src_p) { char dst_str[BUFSIZ]; char src_str[BUFSIZ]; char *rv; int ec; prefix2str((const struct prefix *)dst_p, dst_str, sizeof(dst_str)); if (src_p && src_p->prefixlen) prefix2str((const struct prefix *)src_p, src_str, sizeof(src_str)); else src_str[0] = '\0'; ec = asprintf(&rv, "%s%s%s", dst_str, (src_str[0] != '\0') ? " from " : "", src_str); assert(ec > 0); return rv; } static unsigned int log_key(const void *data) { const struct prefix *hash_entry = data; struct prefix_ipv6 *dst_p = (struct prefix_ipv6 *)&hash_entry[0]; struct prefix_ipv6 *src_p = (struct prefix_ipv6 *)&hash_entry[1]; unsigned int hash = 0; unsigned int i; hash = (hash * 33) ^ (unsigned int)dst_p->prefixlen; for (i = 0; i < 4; i++) hash = (hash * 33) ^ (unsigned int)dst_p->prefix.s6_addr32[i]; hash = (hash * 33) ^ (unsigned int)src_p->prefixlen; if (src_p->prefixlen) for (i = 0; i < 4; i++) hash = (hash * 33) ^ (unsigned int)src_p->prefix.s6_addr32[i]; return hash; } static bool log_cmp(const void *a, const void *b) { if (a == NULL || b == NULL) return false; return !memcmp(a, b, 2 * sizeof(struct prefix)); } static void log_free(void *data) { XFREE(MTYPE_TMP, data); } static void *log_alloc(void *data) { void *rv = XMALLOC(MTYPE_TMP, 2 * sizeof(struct prefix)); memcpy(rv, data, 2 * sizeof(struct prefix)); return rv; } static struct test_state *test_state_new(void) { struct test_state *rv; rv = XCALLOC(MTYPE_TMP, sizeof(*rv)); assert(rv); rv->table = srcdest_table_init(); assert(rv->table); rv->log = hash_create(log_key, log_cmp, NULL); return rv; } static void test_state_free(struct test_state *test) { route_table_finish(test->table); hash_clean(test->log, log_free); hash_free(test->log); XFREE(MTYPE_TMP, test); } static void test_state_add_route(struct test_state *test, struct prefix_ipv6 *dst_p, struct prefix_ipv6 *src_p) { struct route_node *rn = srcdest_rnode_get(test->table, (struct prefix *)dst_p, src_p); struct prefix hash_entry[2]; memset(hash_entry, 0, sizeof(hash_entry)); memcpy(&hash_entry[0], dst_p, sizeof(*dst_p)); memcpy(&hash_entry[1], src_p, sizeof(*src_p)); if (rn->info) { route_unlock_node(rn); assert(hash_lookup(test->log, hash_entry) != NULL); return; } else { assert(hash_lookup(test->log, hash_entry) == NULL); } rn->info = (void *)0xdeadbeef; hash_get(test->log, hash_entry, log_alloc); }; static void test_state_del_route(struct test_state *test, struct prefix_ipv6 *dst_p, struct prefix_ipv6 *src_p) { struct route_node *rn = srcdest_rnode_lookup( test->table, (struct prefix *)dst_p, src_p); struct prefix hash_entry[2]; memset(hash_entry, 0, sizeof(hash_entry)); memcpy(&hash_entry[0], dst_p, sizeof(*dst_p)); memcpy(&hash_entry[1], src_p, sizeof(*src_p)); if (!rn) { assert(!hash_lookup(test->log, hash_entry)); return; } assert(rn->info == (void *)0xdeadbeef); rn->info = NULL; route_unlock_node(rn); route_unlock_node(rn); struct prefix *hash_entry_intern = hash_release(test->log, hash_entry); assert(hash_entry_intern != NULL); XFREE(MTYPE_TMP, hash_entry_intern); } static void verify_log(struct hash_bucket *bucket, void *arg) { struct test_state *test = arg; struct prefix *hash_entry = bucket->data; struct prefix *dst_p = &hash_entry[0]; struct prefix_ipv6 *src_p = (struct prefix_ipv6 *)&hash_entry[1]; struct route_node *rn = srcdest_rnode_lookup(test->table, dst_p, src_p); assert(rn); assert(rn->info == (void *)0xdeadbeef); route_unlock_node(rn); } static void dump_log(struct hash_bucket *bucket, void *arg) { struct prefix *hash_entry = bucket->data; struct prefix_ipv6 *dst_p = (struct prefix_ipv6 *)&hash_entry[0]; struct prefix_ipv6 *src_p = (struct prefix_ipv6 *)&hash_entry[1]; char *route_id = format_srcdest(dst_p, src_p); fprintf(stderr, " %s\n", route_id); free(route_id); } static void test_dump(struct test_state *test) { fprintf(stderr, "Contents of hash table:\n"); hash_iterate(test->log, dump_log, test); fprintf(stderr, "\n"); } static void test_failed(struct test_state *test, const char *message, const struct prefix_ipv6 *dst_p, const struct prefix_ipv6 *src_p) { char *route_id = format_srcdest(dst_p, src_p); fprintf(stderr, "Test failed. Error: %s\n", message); fprintf(stderr, "Route in question: %s\n", route_id); free(route_id); test_dump(test); assert(3 == 4); } static void test_state_verify(struct test_state *test) { struct route_node *rn; struct prefix hash_entry[2]; memset(hash_entry, 0, sizeof(hash_entry)); /* Verify that there are no elements in the table which have never * been added */ for (rn = route_top(test->table); rn; rn = srcdest_route_next(rn)) { const struct prefix_ipv6 *dst_p, *src_p; /* While we are iterating, we hold a lock on the current * route_node, * so all the lock counts we check for take that into account; * in idle * state all the numbers will be exactly one less. * * Also this makes quite some assumptions based on the current * implementation details of route_table and srcdest_table - * another * valid implementation might trigger assertions here. */ if (rnode_is_dstnode(rn)) { struct srcdest_rnode *srn = (struct srcdest_rnode *)rn; unsigned int expected_lock = 1; /* We are in the loop */ if (rn->info != NULL) /* The route node is not internal */ expected_lock++; if (srn->src_table != NULL) /* There's a source table associated with rn */ expected_lock++; if (rn->lock != expected_lock) test_failed( test, "Dest rnode lock count doesn't match expected count!", (struct prefix_ipv6 *)&rn->p, NULL); } else { unsigned int expected_lock = 1; /* We are in the loop */ if (rn->info != NULL) /* The route node is not internal */ expected_lock++; if (rn->lock != expected_lock) { srcdest_rnode_prefixes( rn, (const struct prefix **)&dst_p, (const struct prefix **)&src_p); test_failed( test, "Src rnode lock count doesn't match expected count!", dst_p, src_p); } } if (!rn->info) continue; assert(rn->info == (void *)0xdeadbeef); srcdest_rnode_prefixes(rn, (const struct prefix **)&dst_p, (const struct prefix **)&src_p); memcpy(&hash_entry[0], dst_p, sizeof(*dst_p)); if (src_p) memcpy(&hash_entry[1], src_p, sizeof(*src_p)); else memset(&hash_entry[1], 0, sizeof(hash_entry[1])); if (hash_lookup(test->log, hash_entry) == NULL) test_failed(test, "Route is missing in hash", dst_p, src_p); } /* Verify that all added elements are still in the table */ hash_iterate(test->log, verify_log, test); } static void get_rand_prefix(struct prng *prng, struct prefix_ipv6 *p) { int i; memset(p, 0, sizeof(*p)); for (i = 0; i < 4; i++) p->prefix.s6_addr32[i] = prng_rand(prng); p->prefixlen = prng_rand(prng) % 129; p->family = AF_INET6; apply_mask((struct prefix *)p); } static void get_rand_prefix_pair(struct prng *prng, struct prefix_ipv6 *dst_p, struct prefix_ipv6 *src_p) { get_rand_prefix(prng, dst_p); if ((prng_rand(prng) % 4) == 0) { get_rand_prefix(prng, src_p); if (src_p->prefixlen) return; } memset(src_p, 0, sizeof(*src_p)); } static void test_state_add_rand_route(struct test_state *test, struct prng *prng) { struct prefix_ipv6 dst_p, src_p; get_rand_prefix_pair(prng, &dst_p, &src_p); test_state_add_route(test, &dst_p, &src_p); } static void test_state_del_rand_route(struct test_state *test, struct prng *prng) { struct prefix_ipv6 dst_p, src_p; get_rand_prefix_pair(prng, &dst_p, &src_p); test_state_del_route(test, &dst_p, &src_p); } static void test_state_del_one_route(struct test_state *test, struct prng *prng) { unsigned int which_route; if (test->log->count == 0) return; which_route = prng_rand(prng) % test->log->count; struct route_node *rn; const struct prefix *dst_p, *src_p; struct prefix_ipv6 dst6_p, src6_p; for (rn = route_top(test->table); rn; rn = srcdest_route_next(rn)) { if (!rn->info) continue; if (!which_route) { route_unlock_node(rn); break; } which_route--; } assert(rn); srcdest_rnode_prefixes(rn, (const struct prefix **)&dst_p, (const struct prefix **)&src_p); memcpy(&dst6_p, dst_p, sizeof(dst6_p)); if (src_p) memcpy(&src6_p, src_p, sizeof(src6_p)); else memset(&src6_p, 0, sizeof(src6_p)); test_state_del_route(test, &dst6_p, &src6_p); } static void run_prng_test(void) { struct test_state *test = test_state_new(); struct prng *prng = prng_new(0); size_t i; for (i = 0; i < 1000; i++) { switch (prng_rand(prng) % 10) { case 0: case 1: case 2: case 3: case 4: test_state_add_rand_route(test, prng); break; case 5: case 6: case 7: test_state_del_one_route(test, prng); break; case 8: case 9: test_state_del_rand_route(test, prng); break; } test_state_verify(test); } prng_free(prng); test_state_free(test); } int main(int argc, char *argv[]) { run_prng_test(); printf("PRNG Test successful.\n"); return 0; } frr-7.2.1/tests/lib/test_srcdest_table.py0000644000000000000000000000023013610377563015346 00000000000000import frrtest class TestSrcdestTable(frrtest.TestMultiOut): program = './test_srcdest_table' TestSrcdestTable.onesimple('PRNG Test successful.') frr-7.2.1/tests/lib/test_stream.c0000644000000000000000000000334613610377563013630 00000000000000/* Simple stream test. * * Copyright (C) 2006 Sun Microsystems, Inc. * * This file is part of Quagga. * * Quagga is free software; you can redistribute it and/or modify it * under the terms of the GNU General Public License as published by the * Free Software Foundation; either version 2, or (at your option) any * later version. * * Quagga 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; see the file COPYING; if not, write to the Free Software * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA */ #include #include #include static unsigned long long ham = 0xdeadbeefdeadbeef; struct thread_master *master; static void print_stream(struct stream *s) { size_t getp = stream_get_getp(s); printf("endp: %zu, readable: %zu, writeable: %zu\n", stream_get_endp(s), STREAM_READABLE(s), STREAM_WRITEABLE(s)); while (STREAM_READABLE(s)) { printf("0x%x ", *stream_pnt(s)); stream_forward_getp(s, 1); } printf("\n"); /* put getp back to where it was */ stream_set_getp(s, getp); } int main(void) { struct stream *s; s = stream_new(1024); stream_putc(s, ham); stream_putw(s, ham); stream_putl(s, ham); stream_putq(s, ham); print_stream(s); stream_resize_inplace(&s, stream_get_endp(s)); print_stream(s); printf("c: 0x%hhx\n", stream_getc(s)); printf("w: 0x%hx\n", stream_getw(s)); printf("l: 0x%x\n", stream_getl(s)); printf("q: 0x%" PRIx64 "\n", stream_getq(s)); return 0; } frr-7.2.1/tests/lib/test_stream.py0000644000000000000000000000012413610377563014025 00000000000000import frrtest class TestStream(frrtest.TestRefOut): program = './test_stream' frr-7.2.1/tests/lib/test_stream.refout0000644000000000000000000000043313610377563014704 00000000000000endp: 15, readable: 15, writeable: 1009 0xef 0xbe 0xef 0xde 0xad 0xbe 0xef 0xde 0xad 0xbe 0xef 0xde 0xad 0xbe 0xef endp: 15, readable: 15, writeable: 0 0xef 0xbe 0xef 0xde 0xad 0xbe 0xef 0xde 0xad 0xbe 0xef 0xde 0xad 0xbe 0xef c: 0xef w: 0xbeef l: 0xdeadbeef q: 0xdeadbeefdeadbeef frr-7.2.1/tests/lib/test_table.c0000644000000000000000000002603113610377563013420 00000000000000/* * Routing table test * Copyright (C) 2012 OSR. * * This file is part of Quagga * * Quagga is free software; you can redistribute it and/or modify it * under the terms of the GNU General Public License as published by the * Free Software Foundation; either version 2, or (at your option) any * later version. * * Quagga 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; see the file COPYING; if not, write to the Free Software * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA */ #include #include "prefix.h" #include "table.h" /* * test_node_t * * Information that is kept for each node in the radix tree. */ typedef struct test_node_t_ { /* * Human readable representation of the string. Allocated using * malloc()/dup(). */ char *prefix_str; } test_node_t; struct thread_master *master; /* * add_node * * Add the given prefix (passed in as a string) to the given table. */ static void add_node(struct route_table *table, const char *prefix_str) { struct prefix_ipv4 p; test_node_t *node; struct route_node *rn; assert(prefix_str); if (str2prefix_ipv4(prefix_str, &p) <= 0) { assert(0); } rn = route_node_get(table, (struct prefix *)&p); if (rn->info) { assert(0); return; } node = malloc(sizeof(test_node_t)); assert(node); node->prefix_str = strdup(prefix_str); assert(node->prefix_str); rn->info = node; } /* * add_nodes * * Convenience function to add a bunch of nodes together. * * The arguments must be prefixes in string format, with a NULL as the * last argument. */ static void add_nodes(struct route_table *table, ...) { va_list arglist; char *prefix; va_start(arglist, table); prefix = va_arg(arglist, char *); while (prefix) { add_node(table, prefix); prefix = va_arg(arglist, char *); } va_end(arglist); } /* * print_subtree * * Recursive function to print a route node and its children. * * @see print_table */ static void print_subtree(struct route_node *rn, const char *legend, int indent_level) { char buf[PREFIX2STR_BUFFER]; int i; /* * Print this node first. */ for (i = 0; i < indent_level; i++) { printf(" "); } prefix2str(&rn->p, buf, sizeof(buf)); printf("%s: %s", legend, buf); if (!rn->info) { printf(" (internal)"); } printf("\n"); if (rn->l_left) { print_subtree(rn->l_left, "Left", indent_level + 1); } if (rn->l_right) { print_subtree(rn->l_right, "Right", indent_level + 1); } } /* * print_table * * Function that prints out the internal structure of a route table. */ static void print_table(struct route_table *table) { struct route_node *rn; rn = table->top; if (!rn) { printf("\n"); return; } print_subtree(rn, "Top", 0); } /* * clear_table * * Remove all nodes from the given table. */ static void clear_table(struct route_table *table) { route_table_iter_t iter; struct route_node *rn; test_node_t *node; route_table_iter_init(&iter, table); while ((rn = route_table_iter_next(&iter))) { node = rn->info; if (!node) { continue; } rn->info = NULL; route_unlock_node(rn); free(node->prefix_str); free(node); } route_table_iter_cleanup(&iter); assert(table->top == NULL); } /* * verify_next_by_iterating * * Iterate over the tree to make sure that the first prefix after * target_pfx is the expected one. Note that target_pfx may not be * present in the tree. */ static void verify_next_by_iterating(struct route_table *table, struct prefix *target_pfx, struct prefix *next_pfx) { route_table_iter_t iter; struct route_node *rn; route_table_iter_init(&iter, table); while ((rn = route_table_iter_next(&iter))) { if (route_table_prefix_iter_cmp(&rn->p, target_pfx) > 0) { assert(!prefix_cmp(&rn->p, next_pfx)); break; } } if (!rn) { assert(!next_pfx); } route_table_iter_cleanup(&iter); } /* * verify_next * * Verifies that route_table_get_next() returns the expected result * (result) for the prefix string 'target'. */ static void verify_next(struct route_table *table, const char *target, const char *next) { struct prefix_ipv4 target_pfx, next_pfx; struct route_node *rn; char result_buf[PREFIX2STR_BUFFER]; if (str2prefix_ipv4(target, &target_pfx) <= 0) { assert(0); } rn = route_table_get_next(table, (struct prefix *)&target_pfx); if (rn) { prefix2str(&rn->p, result_buf, sizeof(result_buf)); } else { snprintf(result_buf, sizeof(result_buf), "(Null)"); } printf("\n"); print_table(table); printf("Verifying successor of %s. Expected: %s, Result: %s\n", target, next ? next : "(Null)", result_buf); if (!rn) { assert(!next); verify_next_by_iterating(table, (struct prefix *)&target_pfx, NULL); return; } assert(next); if (str2prefix_ipv4(next, &next_pfx) <= 0) { assert(0); } if (prefix_cmp(&rn->p, (struct prefix *)&next_pfx)) { assert(0); } route_unlock_node(rn); verify_next_by_iterating(table, (struct prefix *)&target_pfx, (struct prefix *)&next_pfx); } /* * test_get_next */ static void test_get_next(void) { struct route_table *table; printf("\n\nTesting route_table_get_next()\n"); table = route_table_init(); /* * Target exists in tree, but has no successor. */ add_nodes(table, "1.0.1.0/24", NULL); verify_next(table, "1.0.1.0/24", NULL); clear_table(table); /* * Target exists in tree, and there is a node in its left subtree. */ add_nodes(table, "1.0.1.0/24", "1.0.1.0/25", NULL); verify_next(table, "1.0.1.0/24", "1.0.1.0/25"); clear_table(table); /* * Target exists in tree, and there is a node in its right subtree. */ add_nodes(table, "1.0.1.0/24", "1.0.1.128/25", NULL); verify_next(table, "1.0.1.0/24", "1.0.1.128/25"); clear_table(table); /* * Target exists in the tree, next node is outside subtree. */ add_nodes(table, "1.0.1.0/24", "1.1.0.0/16", NULL); verify_next(table, "1.0.1.0/24", "1.1.0.0/16"); clear_table(table); /* * The target node does not exist in the tree for all the test cases * below this point. */ /* * There is no successor in the tree. */ add_nodes(table, "1.0.0.0/16", NULL); verify_next(table, "1.0.1.0/24", NULL); clear_table(table); /* * There exists a node that would be in the target's left subtree. */ add_nodes(table, "1.0.0.0/16", "1.0.1.0/25", NULL); verify_next(table, "1.0.1.0/24", "1.0.1.0/25"); clear_table(table); /* * There exists a node would be in the target's right subtree. */ add_nodes(table, "1.0.0.0/16", "1.0.1.128/25", NULL); verify_next(table, "1.0.1.0/24", "1.0.1.128/25"); clear_table(table); /* * A search for the target reaches a node where there are no child * nodes in the direction of the target (left), but the node has a * right child. */ add_nodes(table, "1.0.0.0/16", "1.0.128.0/17", NULL); verify_next(table, "1.0.0.0/17", "1.0.128.0/17"); clear_table(table); /* * A search for the target reaches a node with no children. We have * to go upwards in the tree to find a successor. */ add_nodes(table, "1.0.0.0/16", "1.0.0.0/24", "1.0.1.0/24", "1.0.128.0/17", NULL); verify_next(table, "1.0.1.0/25", "1.0.128.0/17"); clear_table(table); /* * A search for the target reaches a node where neither the node nor * the target prefix contain each other. * * In first case below the node succeeds the target. * * In the second case, the node comes before the target, so we have * to go up the tree looking for a successor. */ add_nodes(table, "1.0.0.0/16", "1.0.1.0/24", NULL); verify_next(table, "1.0.0.0/24", "1.0.1.0/24"); clear_table(table); add_nodes(table, "1.0.0.0/16", "1.0.0.0/24", "1.0.1.0/25", "1.0.128.0/17", NULL); verify_next(table, "1.0.1.128/25", "1.0.128.0/17"); clear_table(table); route_table_finish(table); } /* * verify_prefix_iter_cmp */ static void verify_prefix_iter_cmp(const char *p1, const char *p2, int exp_result) { struct prefix_ipv4 p1_pfx, p2_pfx; int result; if (str2prefix_ipv4(p1, &p1_pfx) <= 0) { assert(0); } if (str2prefix_ipv4(p2, &p2_pfx) <= 0) { assert(0); } result = route_table_prefix_iter_cmp((struct prefix *)&p1_pfx, (struct prefix *)&p2_pfx); printf("Verifying cmp(%s, %s) returns %d\n", p1, p2, exp_result); assert(exp_result == result); /* * Also check the reverse comparision. */ result = route_table_prefix_iter_cmp((struct prefix *)&p2_pfx, (struct prefix *)&p1_pfx); if (exp_result) { exp_result = -exp_result; } printf("Verifying cmp(%s, %s) returns %d\n", p1, p2, exp_result); assert(result == exp_result); } /* * test_prefix_iter_cmp * * Tests comparision of prefixes according to order of iteration. */ static void test_prefix_iter_cmp(void) { printf("\n\nTesting route_table_prefix_iter_cmp()\n"); verify_prefix_iter_cmp("1.0.0.0/8", "1.0.0.0/8", 0); verify_prefix_iter_cmp("1.0.0.0/8", "1.0.0.0/16", -1); verify_prefix_iter_cmp("1.0.0.0/16", "1.128.0.0/16", -1); } /* * verify_iter_with_pause * * Iterates over a tree using two methods: 'normal' iteration, and an * iterator that pauses at each node. Verifies that the two methods * yield the same results. */ static void verify_iter_with_pause(struct route_table *table) { unsigned long num_nodes; struct route_node *rn, *iter_rn; route_table_iter_t iter_space; route_table_iter_t *iter = &iter_space; route_table_iter_init(iter, table); num_nodes = 0; for (rn = route_top(table); rn; rn = route_next(rn)) { num_nodes++; route_table_iter_pause(iter); assert(iter->current == NULL); if (route_table_iter_started(iter)) { assert(iter->state == RT_ITER_STATE_PAUSED); } else { assert(rn == table->top); assert(iter->state == RT_ITER_STATE_INIT); } iter_rn = route_table_iter_next(iter); /* * Make sure both iterations return the same node. */ assert(rn == iter_rn); } assert(num_nodes == route_table_count(table)); route_table_iter_pause(iter); iter_rn = route_table_iter_next(iter); assert(iter_rn == NULL); assert(iter->state == RT_ITER_STATE_DONE); assert(route_table_iter_next(iter) == NULL); assert(iter->state == RT_ITER_STATE_DONE); route_table_iter_cleanup(iter); print_table(table); printf("Verified pausing iteration on tree with %lu nodes\n", num_nodes); } /* * test_iter_pause */ static void test_iter_pause(void) { struct route_table *table; int i, num_prefixes; const char *prefixes[] = {"1.0.1.0/24", "1.0.1.0/25", "1.0.1.128/25", "1.0.2.0/24", "2.0.0.0/8"}; num_prefixes = array_size(prefixes); printf("\n\nTesting that route_table_iter_pause() works as expected\n"); table = route_table_init(); for (i = 0; i < num_prefixes; i++) { add_nodes(table, prefixes[i], NULL); } verify_iter_with_pause(table); clear_table(table); route_table_finish(table); } /* * run_tests */ static void run_tests(void) { test_prefix_iter_cmp(); test_get_next(); test_iter_pause(); } /* * main */ int main(void) { run_tests(); } frr-7.2.1/tests/lib/test_table.py0000644000000000000000000000037413610377563013630 00000000000000import frrtest class TestTable(frrtest.TestMultiOut): program = './test_table' for i in range(6): TestTable.onesimple('Verifying cmp') for i in range(11): TestTable.onesimple('Verifying successor') TestTable.onesimple('Verified pausing') frr-7.2.1/tests/lib/test_timer_correctness.c0000644000000000000000000001125013610377563016060 00000000000000/* * Test program to verify that scheduled timers are executed in the * correct order. * * Copyright (C) 2013 by Open Source Routing. * Copyright (C) 2013 by Internet Systems Consortium, Inc. ("ISC") * * This file is part of Quagga * * Quagga is free software; you can redistribute it and/or modify it * under the terms of the GNU General Public License as published by the * Free Software Foundation; either version 2, or (at your option) any * later version. * * Quagga 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; see the file COPYING; if not, write to the Free Software * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA */ #include #include #include #include "memory.h" #include "prng.h" #include "thread.h" #define SCHEDULE_TIMERS 800 #define REMOVE_TIMERS 200 #define TIMESTR_LEN strlen("4294967296.999999") struct thread_master *master; static size_t log_buf_len; static size_t log_buf_pos; static char *log_buf; static size_t expected_buf_len; static size_t expected_buf_pos; static char *expected_buf; static struct prng *prng; static struct thread **timers; static int timers_pending; static void terminate_test(void) { int exit_code; if (strcmp(log_buf, expected_buf)) { fprintf(stderr, "Expected output and received output differ.\n"); fprintf(stderr, "---Expected output: ---\n%s", expected_buf); fprintf(stderr, "---Actual output: ---\n%s", log_buf); exit_code = 1; } else { printf("Expected output and actual output match.\n"); exit_code = 0; } thread_master_free(master); XFREE(MTYPE_TMP, log_buf); XFREE(MTYPE_TMP, expected_buf); prng_free(prng); XFREE(MTYPE_TMP, timers); exit(exit_code); } static int timer_func(struct thread *thread) { int rv; rv = snprintf(log_buf + log_buf_pos, log_buf_len - log_buf_pos, "%s\n", (char *)thread->arg); assert(rv >= 0); log_buf_pos += rv; assert(log_buf_pos < log_buf_len); XFREE(MTYPE_TMP, thread->arg); timers_pending--; if (!timers_pending) terminate_test(); return 0; } static int cmp_timeval(const void *a, const void *b) { const struct timeval *ta = *(struct timeval * const *)a; const struct timeval *tb = *(struct timeval * const *)b; if (timercmp(ta, tb, <)) return -1; if (timercmp(ta, tb, >)) return 1; return 0; } int main(int argc, char **argv) { int i, j; struct thread t; struct timeval **alarms; master = thread_master_create(NULL); log_buf_len = SCHEDULE_TIMERS * (TIMESTR_LEN + 1) + 1; log_buf_pos = 0; log_buf = XMALLOC(MTYPE_TMP, log_buf_len); expected_buf_len = SCHEDULE_TIMERS * (TIMESTR_LEN + 1) + 1; expected_buf_pos = 0; expected_buf = XMALLOC(MTYPE_TMP, expected_buf_len); prng = prng_new(0); timers = XMALLOC(MTYPE_TMP, SCHEDULE_TIMERS * sizeof(*timers)); for (i = 0; i < SCHEDULE_TIMERS; i++) { long interval_msec; int ret; char *arg; /* Schedule timers to expire in 0..5 seconds */ interval_msec = prng_rand(prng) % 5000; arg = XMALLOC(MTYPE_TMP, TIMESTR_LEN + 1); timers[i] = NULL; thread_add_timer_msec(master, timer_func, arg, interval_msec, &timers[i]); ret = snprintf(arg, TIMESTR_LEN + 1, "%lld.%06lld", (long long)timers[i]->u.sands.tv_sec, (long long)timers[i]->u.sands.tv_usec); assert(ret > 0); assert((size_t)ret < TIMESTR_LEN + 1); timers_pending++; } for (i = 0; i < REMOVE_TIMERS; i++) { int index; index = prng_rand(prng) % SCHEDULE_TIMERS; if (!timers[index]) continue; XFREE(MTYPE_TMP, timers[index]->arg); thread_cancel(timers[index]); timers[index] = NULL; timers_pending--; } /* We create an array of pointers to the alarm times and sort * that array. That sorted array is used to generate a string * representing the expected "output" of the timers when they * are run. */ j = 0; alarms = XMALLOC(MTYPE_TMP, timers_pending * sizeof(*alarms)); for (i = 0; i < SCHEDULE_TIMERS; i++) { if (!timers[i]) continue; alarms[j++] = &timers[i]->u.sands; } qsort(alarms, j, sizeof(*alarms), cmp_timeval); for (i = 0; i < j; i++) { int ret; ret = snprintf(expected_buf + expected_buf_pos, expected_buf_len - expected_buf_pos, "%lld.%06lld\n", (long long)alarms[i]->tv_sec, (long long)alarms[i]->tv_usec); assert(ret > 0); expected_buf_pos += ret; assert(expected_buf_pos < expected_buf_len); } XFREE(MTYPE_TMP, alarms); while (thread_fetch(master, &t)) thread_call(&t); return 0; } frr-7.2.1/tests/lib/test_timer_correctness.py0000644000000000000000000000026713610377563016274 00000000000000import frrtest class TestTimerCorrectness(frrtest.TestMultiOut): program = './test_timer_correctness' TestTimerCorrectness.onesimple('Expected output and actual output match.') frr-7.2.1/tests/lib/test_timer_performance.c0000644000000000000000000000541413610377563016034 00000000000000/* * Test program which measures the time it takes to schedule and * remove timers. * * Copyright (C) 2013 by Open Source Routing. * Copyright (C) 2013 by Internet Systems Consortium, Inc. ("ISC") * * This file is part of Quagga * * Quagga is free software; you can redistribute it and/or modify it * under the terms of the GNU General Public License as published by the * Free Software Foundation; either version 2, or (at your option) any * later version. * * Quagga 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; see the file COPYING; if not, write to the Free Software * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA */ #include #include #include #include "thread.h" #include "prng.h" #define SCHEDULE_TIMERS 1000000 #define REMOVE_TIMERS 500000 struct thread_master *master; static int dummy_func(struct thread *thread) { return 0; } int main(int argc, char **argv) { struct prng *prng; int i; struct thread **timers; struct timeval tv_start, tv_lap, tv_stop; unsigned long t_schedule, t_remove; master = thread_master_create(NULL); prng = prng_new(0); timers = calloc(SCHEDULE_TIMERS, sizeof(*timers)); /* create thread structures so they won't be allocated during the * time measurement */ for (i = 0; i < SCHEDULE_TIMERS; i++) { timers[i] = NULL; thread_add_timer_msec(master, dummy_func, NULL, 0, &timers[i]); } for (i = 0; i < SCHEDULE_TIMERS; i++) thread_cancel(timers[i]); monotime(&tv_start); for (i = 0; i < SCHEDULE_TIMERS; i++) { long interval_msec; interval_msec = prng_rand(prng) % (100 * SCHEDULE_TIMERS); timers[i] = NULL; thread_add_timer_msec(master, dummy_func, NULL, interval_msec, &timers[i]); } monotime(&tv_lap); for (i = 0; i < REMOVE_TIMERS; i++) { int index; index = prng_rand(prng) % SCHEDULE_TIMERS; if (timers[index]) thread_cancel(timers[index]); timers[index] = NULL; } monotime(&tv_stop); t_schedule = 1000 * (tv_lap.tv_sec - tv_start.tv_sec); t_schedule += (tv_lap.tv_usec - tv_start.tv_usec) / 1000; t_remove = 1000 * (tv_stop.tv_sec - tv_lap.tv_sec); t_remove += (tv_stop.tv_usec - tv_lap.tv_usec) / 1000; printf("Scheduling %d random timers took %lu.%03lu seconds.\n", SCHEDULE_TIMERS, t_schedule / 1000, t_schedule % 1000); printf("Removing %d random timers took %lu.%03lu seconds.\n", REMOVE_TIMERS, t_remove / 1000, t_remove % 1000); fflush(stdout); free(timers); thread_master_free(master); prng_free(prng); return 0; } frr-7.2.1/tests/lib/test_ttable.c0000644000000000000000000001242413610377563013605 00000000000000/* * ASCII table generator. * Copyright (C) 2017 Cumulus Networks * Quentin Young * * This program is free software; you can redistribute it and/or modify it * under the terms of the 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; see the file COPYING; if not, write to the Free Software * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA */ #include #include #include int main(int argc, char **argv) { char *table; struct ttable *tt = ttable_new(&ttable_styles[TTSTYLE_ASCII]); /* test printf compatibility and dimension counters */ ttable_add_row(tt, "%s|%s|%s", "Column 1", "Column 2", "Column 3"); assert(tt->ncols == 3); assert(tt->nrows == 1); table = ttable_dump(tt, "\n"); fprintf(stdout, "%s\n", table); XFREE(MTYPE_TMP, table); /* add new row with 1 column, assert that it is not added */ assert(ttable_add_row(tt, "%s", "Garbage") == NULL); assert(tt->ncols == 3); assert(tt->nrows == 1); table = ttable_dump(tt, "\n"); fprintf(stdout, "%s\n", table); XFREE(MTYPE_TMP, table); /* add new row, assert that it is added */ assert(ttable_add_row(tt, "%s|%s|%s", "a", "b", "c")); assert(tt->ncols == 3); assert(tt->nrows == 2); table = ttable_dump(tt, "\n"); fprintf(stdout, "%s\n", table); XFREE(MTYPE_TMP, table); /* add empty row, assert that it is added */ assert(ttable_add_row(tt, "||")); assert(tt->ncols == 3); assert(tt->nrows == 3); table = ttable_dump(tt, "\n"); fprintf(stdout, "%s\n", table); XFREE(MTYPE_TMP, table); /* delete 1st row, assert that it is removed */ ttable_del_row(tt, 0); assert(tt->ncols == 3); assert(tt->nrows == 2); table = ttable_dump(tt, "\n"); fprintf(stdout, "%s\n", table); XFREE(MTYPE_TMP, table); /* delete last row, assert that it is removed */ ttable_del_row(tt, 0); assert(tt->ncols == 3); assert(tt->nrows == 1); table = ttable_dump(tt, "\n"); fprintf(stdout, "%s\n", table); XFREE(MTYPE_TMP, table); /* delete the remaining row, check dumping an empty table */ ttable_del_row(tt, 0); assert(tt->ncols == 0); assert(tt->nrows == 0); table = ttable_dump(tt, "\n"); fprintf(stdout, "%s\n", table); XFREE(MTYPE_TMP, table); /* add new row */ ttable_add_row(tt, "%s|%s||%s|%9d", "slick", "black", "triple", 1337); assert(tt->ncols == 5); assert(tt->nrows == 1); table = ttable_dump(tt, "\n"); fprintf(stdout, "%s\n", table); XFREE(MTYPE_TMP, table); /* add bigger row */ ttable_add_row(tt, "%s|%s||%s|%s", "nebula dusk session streets twilight " "pioneer beats yeah", "prarie dog", "cornmeal", ":O -*_-*"); assert(tt->ncols == 5); assert(tt->nrows == 2); table = ttable_dump(tt, "\n"); fprintf(stdout, "%s\n", table); XFREE(MTYPE_TMP, table); /* insert new row at beginning */ ttable_insert_row(tt, 0, "%s|%s||%d|%lf", "converting", "vegetarians", 2, 2015.0); assert(tt->ncols == 5); assert(tt->nrows == 3); table = ttable_dump(tt, "\n"); fprintf(stdout, "%s\n", table); XFREE(MTYPE_TMP, table); /* insert new row at end */ ttable_insert_row(tt, tt->nrows - 1, "%s|%s||%d|%ld", "converting", "vegetarians", 1, 2003L); assert(tt->ncols == 5); assert(tt->nrows == 4); table = ttable_dump(tt, "\n"); fprintf(stdout, "%s\n", table); XFREE(MTYPE_TMP, table); /* insert new row at middle */ ttable_insert_row(tt, 1, "%s|%s||%s|%ld", "she", "pioneer", "aki", 1l); assert(tt->ncols == 5); assert(tt->nrows == 5); table = ttable_dump(tt, "\n"); fprintf(stdout, "%s\n", table); XFREE(MTYPE_TMP, table); /* set alignment */ ttable_align(tt, 0, 1, 2, 2, LEFT); assert(tt->ncols == 5); assert(tt->nrows == 5); table = ttable_dump(tt, "\n"); fprintf(stdout, "%s\n", table); XFREE(MTYPE_TMP, table); ttable_align(tt, 0, 1, 5, 1, RIGHT); assert(tt->ncols == 5); assert(tt->nrows == 5); table = ttable_dump(tt, "\n"); fprintf(stdout, "%s\n", table); XFREE(MTYPE_TMP, table); /* set padding */ ttable_pad(tt, 0, 1, 1, 1, RIGHT, 2); assert(tt->ncols == 5); assert(tt->nrows == 5); table = ttable_dump(tt, "\n"); fprintf(stdout, "%s\n", table); XFREE(MTYPE_TMP, table); ttable_pad(tt, 0, 0, 5, 4, LEFT, 2); assert(tt->ncols == 5); assert(tt->nrows == 5); table = ttable_dump(tt, "\n"); fprintf(stdout, "%s\n", table); XFREE(MTYPE_TMP, table); /* restyle */ tt->style.cell.border.bottom_on = false; tt->style.cell.border.top_on = false; tt->style.cell.border.right_on = false; tt->style.cell.border.left_on = false; ttable_restyle(tt); /* top & double bottom border for top row */ ttable_rowseps(tt, 0, BOTTOM, true, '-'); ttable_rowseps(tt, 1, TOP, true, '-'); table = ttable_dump(tt, "\n"); fprintf(stdout, "%s\n", table); XFREE(MTYPE_TMP, table); /* column separators for leftmost column */ ttable_colseps(tt, 0, RIGHT, true, '|'); table = ttable_dump(tt, "\n"); fprintf(stdout, "%s\n", table); XFREE(MTYPE_TMP, table); /* delete table */ ttable_del(tt); } frr-7.2.1/tests/lib/test_ttable.py0000644000000000000000000000012413610377563014005 00000000000000import frrtest class TestTTable(frrtest.TestRefOut): program = './test_ttable' frr-7.2.1/tests/lib/test_ttable.refout0000644000000000000000000002453013610377563014670 00000000000000 |--------------------------------| | Column 1 | Column 2 | Column 3 | |--------------------------------| |--------------------------------| | Column 1 | Column 2 | Column 3 | |--------------------------------| |--------------------------------| | Column 1 | Column 2 | Column 3 | |----------+----------+----------| | a | b | c | |--------------------------------| |--------------------------------| | Column 1 | Column 2 | Column 3 | |----------+----------+----------| | a | b | c | |----------+----------+----------| | | | | |--------------------------------| |-----------| | a | b | c | |---+---+---| | | | | |-----------| |--------| | | | | |--------| || || |---------------------------------------| | slick | black | | triple | 1337 | |---------------------------------------| |------------------------------------------------------------------------------------------------| | slick | black | | triple | 1337 | |---------------------------------------------------------+------------+--+----------+-----------| | nebula dusk session streets twilight pioneer beats yeah | prarie dog | | cornmeal | :O -*_-* | |------------------------------------------------------------------------------------------------| |---------------------------------------------------------------------------------------------------| | converting | vegetarians | | 2 | 2015.000000 | |---------------------------------------------------------+-------------+--+----------+-------------| | slick | black | | triple | 1337 | |---------------------------------------------------------+-------------+--+----------+-------------| | nebula dusk session streets twilight pioneer beats yeah | prarie dog | | cornmeal | :O -*_-* | |---------------------------------------------------------------------------------------------------| |---------------------------------------------------------------------------------------------------| | converting | vegetarians | | 2 | 2015.000000 | |---------------------------------------------------------+-------------+--+----------+-------------| | slick | black | | triple | 1337 | |---------------------------------------------------------+-------------+--+----------+-------------| | converting | vegetarians | | 1 | 2003 | |---------------------------------------------------------+-------------+--+----------+-------------| | nebula dusk session streets twilight pioneer beats yeah | prarie dog | | cornmeal | :O -*_-* | |---------------------------------------------------------------------------------------------------| |---------------------------------------------------------------------------------------------------| | converting | vegetarians | | 2 | 2015.000000 | |---------------------------------------------------------+-------------+--+----------+-------------| | she | pioneer | | aki | 1 | |---------------------------------------------------------+-------------+--+----------+-------------| | slick | black | | triple | 1337 | |---------------------------------------------------------+-------------+--+----------+-------------| | converting | vegetarians | | 1 | 2003 | |---------------------------------------------------------+-------------+--+----------+-------------| | nebula dusk session streets twilight pioneer beats yeah | prarie dog | | cornmeal | :O -*_-* | |---------------------------------------------------------------------------------------------------| |---------------------------------------------------------------------------------------------------| | converting | vegetarians | | 2 | 2015.000000 | |---------------------------------------------------------+-------------+--+----------+-------------| | she | pioneer | | aki | 1 | |---------------------------------------------------------+-------------+--+----------+-------------| | slick | black | | triple | 1337 | |---------------------------------------------------------+-------------+--+----------+-------------| | converting | vegetarians | | 1 | 2003 | |---------------------------------------------------------+-------------+--+----------+-------------| | nebula dusk session streets twilight pioneer beats yeah | prarie dog | | cornmeal | :O -*_-* | |---------------------------------------------------------------------------------------------------| |---------------------------------------------------------------------------------------------------| | converting | vegetarians | | 2 | 2015.000000 | |---------------------------------------------------------+-------------+--+----------+-------------| | she | pioneer | | aki | 1 | |---------------------------------------------------------+-------------+--+----------+-------------| | slick | black | | triple | 1337 | |---------------------------------------------------------+-------------+--+----------+-------------| | converting | vegetarians | | 1 | 2003 | |---------------------------------------------------------+-------------+--+----------+-------------| | nebula dusk session streets twilight pioneer beats yeah | prarie dog | | cornmeal | :O -*_-* | |---------------------------------------------------------------------------------------------------| |----------------------------------------------------------------------------------------------------| | converting | vegetarians | | 2 | 2015.000000 | |---------------------------------------------------------+--------------+--+----------+-------------| | she | pioneer | | aki | 1 | |---------------------------------------------------------+--------------+--+----------+-------------| | slick | black | | triple | 1337 | |---------------------------------------------------------+--------------+--+----------+-------------| | converting | vegetarians | | 1 | 2003 | |---------------------------------------------------------+--------------+--+----------+-------------| | nebula dusk session streets twilight pioneer beats yeah | prarie dog | | cornmeal | :O -*_-* | |----------------------------------------------------------------------------------------------------| |--------------------------------------------------------------------------------------------------------| | converting | vegetarians | | 2 | 2015.000000 | |----------------------------------------------------------+---------------+---+-----------+-------------| | she | pioneer | | aki | 1 | |----------------------------------------------------------+---------------+---+-----------+-------------| | slick | black | | triple | 1337 | |----------------------------------------------------------+---------------+---+-----------+-------------| | converting | vegetarians | | 1 | 2003 | |----------------------------------------------------------+---------------+---+-----------+-------------| | nebula dusk session streets twilight pioneer beats yeah | prarie dog | | cornmeal | :O -*_-* | |--------------------------------------------------------------------------------------------------------| |-----------------------------------------------------------------------------------------------| | converting vegetarians 2 2015.000000 | |-----------------------------------------------------------------------------------------------| |-----------------------------------------------------------------------------------------------| | she pioneer aki 1 | | slick black triple 1337 | | converting vegetarians 1 2003 | | nebula dusk session streets twilight pioneer beats yeah prarie dog cornmeal :O -*_-* | |-----------------------------------------------------------------------------------------------| |------------------------------------------------------------------------------------------------| | converting | vegetarians 2 2015.000000 | |---------------------------------------------------------+--------------------------------------| |---------------------------------------------------------+--------------------------------------| | she | pioneer aki 1 | | slick | black triple 1337 | | converting | vegetarians 1 2003 | | nebula dusk session streets twilight pioneer beats yeah | prarie dog cornmeal :O -*_-* | |------------------------------------------------------------------------------------------------| frr-7.2.1/tests/lib/test_typelist.c0000644000000000000000000001022113610377563014200 00000000000000/* * Copyright (c) 2016-2018 David Lamparter, for NetDEF, Inc. * * Permission to use, copy, modify, and distribute this software for any * purpose with or without fee is hereby granted, provided that the above * copyright notice and this permission notice appear in all copies. * * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. */ #ifdef HAVE_CONFIG_H #include "config.h" #endif #include #include #include #include #include #include #include #include #define WNO_ATOMLIST_UNSAFE_FIND #include "typesafe.h" #include "atomlist.h" #include "memory.h" #include "monotime.h" #include "jhash.h" #include "sha256.h" #include "tests/helpers/c/prng.h" /* note: these macros are layered 2-deep because that makes the C * preprocessor expand the "type" argument. Otherwise, you get * "PREDECL_type" instead of "PREDECL_LIST" */ #define _concat(a, b) a ## b #define concat(a, b) _concat(a, b) #define _str(x) #x #define str(x) _str(x) #define _PREDECL(type, ...) PREDECL_##type(__VA_ARGS__) #define PREDECL(type, ...) _PREDECL(type, __VA_ARGS__) #define _DECLARE(type, ...) DECLARE_##type(__VA_ARGS__) #define DECLARE(type, ...) _DECLARE(type, __VA_ARGS__) #define T_SORTED (1 << 0) #define T_UNIQ (1 << 1) #define T_HASH (1 << 2) #define T_HEAP (1 << 3) #define T_ATOMIC (1 << 4) #define _T_LIST (0) #define _T_DLIST (0) #define _T_ATOMLIST (0 | T_ATOMIC) #define _T_HEAP (T_SORTED | T_HEAP) #define _T_SORTLIST_UNIQ (T_SORTED | T_UNIQ) #define _T_SORTLIST_NONUNIQ (T_SORTED) #define _T_HASH (T_SORTED | T_UNIQ | T_HASH) #define _T_SKIPLIST_UNIQ (T_SORTED | T_UNIQ) #define _T_SKIPLIST_NONUNIQ (T_SORTED) #define _T_RBTREE_UNIQ (T_SORTED | T_UNIQ) #define _T_RBTREE_NONUNIQ (T_SORTED) #define _T_ATOMSORT_UNIQ (T_SORTED | T_UNIQ | T_ATOMIC) #define _T_ATOMSORT_NONUNIQ (T_SORTED | T_ATOMIC) #define _T_TYPE(type) _T_##type #define IS_SORTED(type) (_T_TYPE(type) & T_SORTED) #define IS_UNIQ(type) (_T_TYPE(type) & T_UNIQ) #define IS_HASH(type) (_T_TYPE(type) & T_HASH) #define IS_HEAP(type) (_T_TYPE(type) & T_HEAP) #define IS_ATOMIC(type) (_T_TYPE(type) & T_ATOMIC) static struct timeval ref, ref0; static void ts_start(void) { monotime(&ref0); monotime(&ref); } static void ts_ref(const char *text) { int64_t us; us = monotime_since(&ref, NULL); printf("%7"PRId64"us %s\n", us, text); monotime(&ref); } static void ts_end(void) { int64_t us; us = monotime_since(&ref0, NULL); printf("%7"PRId64"us total\n", us); } #define TYPE LIST #include "test_typelist.h" #define TYPE DLIST #include "test_typelist.h" #define TYPE ATOMLIST #include "test_typelist.h" #define TYPE HEAP #include "test_typelist.h" #define TYPE SORTLIST_UNIQ #include "test_typelist.h" #define TYPE SORTLIST_NONUNIQ #include "test_typelist.h" #define TYPE HASH #include "test_typelist.h" #define TYPE HASH_collisions #define REALTYPE HASH #define SHITTY_HASH #include "test_typelist.h" #undef SHITTY_HASH #define TYPE SKIPLIST_UNIQ #include "test_typelist.h" #define TYPE SKIPLIST_NONUNIQ #include "test_typelist.h" #define TYPE RBTREE_UNIQ #include "test_typelist.h" #define TYPE RBTREE_NONUNIQ #include "test_typelist.h" #define TYPE ATOMSORT_UNIQ #include "test_typelist.h" #define TYPE ATOMSORT_NONUNIQ #include "test_typelist.h" int main(int argc, char **argv) { srandom(1); test_LIST(); test_DLIST(); test_ATOMLIST(); test_HEAP(); test_SORTLIST_UNIQ(); test_SORTLIST_NONUNIQ(); test_HASH(); test_HASH_collisions(); test_SKIPLIST_UNIQ(); test_SKIPLIST_NONUNIQ(); test_RBTREE_UNIQ(); test_RBTREE_NONUNIQ(); test_ATOMSORT_UNIQ(); test_ATOMSORT_NONUNIQ(); log_memstats_stderr("test: "); return 0; } frr-7.2.1/tests/lib/test_typelist.h0000644000000000000000000003201313610377563014210 00000000000000/* * Copyright (c) 2019 David Lamparter, for NetDEF, Inc. * * Permission to use, copy, modify, and distribute this software for any * purpose with or without fee is hereby granted, provided that the above * copyright notice and this permission notice appear in all copies. * * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. */ /* C++ called, they want their templates back */ #define item concat(item_, TYPE) #define itm concat(itm_, TYPE) #define head concat(head_, TYPE) #define list concat(TYPE, ) #define list_head concat(TYPE, _head) #define list_item concat(TYPE, _item) #define list_cmp concat(TYPE, _cmp) #define list_hash concat(TYPE, _hash) #define list_init concat(TYPE, _init) #define list_fini concat(TYPE, _fini) #define list_first concat(TYPE, _first) #define list_next concat(TYPE, _next) #define list_next_safe concat(TYPE, _next_safe) #define list_count concat(TYPE, _count) #define list_add concat(TYPE, _add) #define list_add_head concat(TYPE, _add_head) #define list_add_tail concat(TYPE, _add_tail) #define list_add_after concat(TYPE, _add_after) #define list_find concat(TYPE, _find) #define list_find_lt concat(TYPE, _find_lt) #define list_find_gteq concat(TYPE, _find_gteq) #define list_del concat(TYPE, _del) #define list_pop concat(TYPE, _pop) #define ts_hash concat(ts_hash_, TYPE) #ifndef REALTYPE #define REALTYPE TYPE #endif PREDECL(REALTYPE, list) struct item { uint64_t val; struct list_item itm; int scratchpad; }; #if IS_SORTED(REALTYPE) static int list_cmp(const struct item *a, const struct item *b); #if IS_HASH(REALTYPE) static uint32_t list_hash(const struct item *a); DECLARE(REALTYPE, list, struct item, itm, list_cmp, list_hash) static uint32_t list_hash(const struct item *a) { #ifdef SHITTY_HASH /* crappy hash to get some hash collisions */ return a->val ^ (a->val << 29) ^ 0x55AA0000U; #else return jhash_1word(a->val, 0xdeadbeef); #endif } #else DECLARE(REALTYPE, list, struct item, itm, list_cmp) #endif static int list_cmp(const struct item *a, const struct item *b) { if (a->val > b->val) return 1; if (a->val < b->val) return -1; return 0; } #else /* !IS_SORTED */ DECLARE(REALTYPE, list, struct item, itm) #endif #define NITEM 10000 struct item itm[NITEM]; static struct list_head head = concat(INIT_, REALTYPE)(head); static void ts_hash(const char *text, const char *expect) { int64_t us = monotime_since(&ref, NULL); SHA256_CTX ctx; struct item *item; unsigned i = 0; uint8_t hash[32]; char hashtext[65]; uint32_t swap_count, count; count = list_count(&head); swap_count = htonl(count); SHA256_Init(&ctx); SHA256_Update(&ctx, &swap_count, sizeof(swap_count)); frr_each (list, &head, item) { struct { uint32_t val_upper, val_lower, index; } hashitem = { htonl(item->val >> 32), htonl(item->val & 0xFFFFFFFFULL), htonl(i), }; SHA256_Update(&ctx, &hashitem, sizeof(hashitem)); i++; assert(i <= count); } SHA256_Final(hash, &ctx); for (i = 0; i < sizeof(hash); i++) sprintf(hashtext + i * 2, "%02x", hash[i]); printf("%7"PRId64"us %-25s %s%s\n", us, text, expect ? " " : "*", hashtext); if (expect && strcmp(expect, hashtext)) { printf("%-21s %s\n", "EXPECTED:", expect); assert(0); } monotime(&ref); } /* hashes will have different item ordering */ #if IS_HASH(REALTYPE) || IS_HEAP(REALTYPE) #define ts_hashx(pos, csum) ts_hash(pos, NULL) #else #define ts_hashx(pos, csum) ts_hash(pos, csum) #endif static void concat(test_, TYPE)(void) { size_t i, j, k, l; struct prng *prng; struct item *item, *prev __attribute__((unused)); struct item dummy __attribute__((unused)); memset(itm, 0, sizeof(itm)); for (i = 0; i < NITEM; i++) itm[i].val = i; printf("%s start\n", str(TYPE)); ts_start(); list_init(&head); assert(list_first(&head) == NULL); ts_hash("init", "df3f619804a92fdb4057192dc43dd748ea778adc52bc498ce80524c014b81119"); #if IS_SORTED(REALTYPE) prng = prng_new(0); k = 0; for (i = 0; i < NITEM; i++) { j = prng_rand(prng) % NITEM; if (itm[j].scratchpad == 0) { list_add(&head, &itm[j]); itm[j].scratchpad = 1; k++; } #if !IS_HEAP(REALTYPE) else assert(list_add(&head, &itm[j]) == &itm[j]); #endif } assert(list_count(&head) == k); assert(list_first(&head) != NULL); ts_hashx("fill", "a538546a6e6ab0484e925940aa8dd02fd934408bbaed8cb66a0721841584d838"); k = 0; prev = NULL; frr_each(list, &head, item) { #if IS_HASH(REALTYPE) || IS_HEAP(REALTYPE) /* hash table doesn't give sorting */ (void)prev; #else assert(!prev || prev->val < item->val); #endif prev = item; k++; } assert(list_count(&head) == k); ts_ref("walk"); #if IS_UNIQ(REALTYPE) prng_free(prng); prng = prng_new(0); for (i = 0; i < NITEM; i++) { j = prng_rand(prng) % NITEM; dummy.val = j; assert(list_find(&head, &dummy) == &itm[j]); } ts_ref("find"); for (i = 0; i < NITEM; i++) { j = prng_rand(prng) % NITEM; memset(&dummy, 0, sizeof(dummy)); dummy.val = j; if (itm[j].scratchpad) assert(list_add(&head, &dummy) == &itm[j]); else { assert(list_add(&head, &dummy) == NULL); assert(list_del(&head, &dummy) != NULL); } } ts_hashx("add-dup", "a538546a6e6ab0484e925940aa8dd02fd934408bbaed8cb66a0721841584d838"); #elif IS_HEAP(REALTYPE) /* heap - partially sorted. */ prev = NULL; l = k / 2; for (i = 0; i < l; i++) { item = list_pop(&head); if (prev) assert(prev->val < item->val); item->scratchpad = 0; k--; prev = item; } ts_hash("pop", NULL); #else /* !IS_UNIQ(REALTYPE) && !IS_HEAP(REALTYPE) */ for (i = 0; i < NITEM; i++) { j = prng_rand(prng) % NITEM; memset(&dummy, 0, sizeof(dummy)); dummy.val = j; list_add(&head, &dummy); if (itm[j].scratchpad) { struct item *lt, *gteq, dummy2; assert(list_next(&head, &itm[j]) == &dummy || list_next(&head, &dummy) == &itm[j]); memset(&dummy2, 0, sizeof(dummy)); dummy2.val = j; lt = list_find_lt(&head, &dummy2); gteq = list_find_gteq(&head, &dummy2); assert(gteq == &itm[j] || gteq == &dummy); if (lt) assert(list_next(&head, lt) == &itm[j] || list_next(&head, lt) == &dummy); else assert(list_first(&head) == &itm[j] || list_first(&head) == &dummy); } else if (list_next(&head, &dummy)) assert(list_next(&head, &dummy)->val > j); assert(list_del(&head, &dummy) != NULL); } ts_hash("add-dup+find_{lt,gteq}", "a538546a6e6ab0484e925940aa8dd02fd934408bbaed8cb66a0721841584d838"); #endif #if !IS_HASH(REALTYPE) && !IS_HEAP(REALTYPE) prng_free(prng); prng = prng_new(123456); l = 0; for (i = 0; i < NITEM; i++) { struct item *lt, *gteq, *tmp; j = prng_rand(prng) % NITEM; dummy.val = j; lt = list_find_lt(&head, &dummy); gteq = list_find_gteq(&head, &dummy); if (lt) { assert(lt->val < j); tmp = list_next(&head, lt); assert(tmp == gteq); assert(!tmp || tmp->val >= j); } else assert(gteq == list_first(&head)); if (gteq) assert(gteq->val >= j); } ts_ref("find_{lt,gteq}"); #endif /* !IS_HASH */ prng_free(prng); prng = prng_new(0); l = 0; for (i = 0; i < NITEM; i++) { (void)prng_rand(prng); j = prng_rand(prng) % NITEM; if (itm[j].scratchpad == 1) { assert(list_del(&head, &itm[j]) != NULL); itm[j].scratchpad = 0; l++; } } assert(l + list_count(&head) == k); ts_hashx("del", "cb2e5d80f08a803ef7b56c15e981b681adcea214bebc2f55e12e0bfb242b07ca"); frr_each_safe(list, &head, item) { assert(item->scratchpad != 0); if (item->val & 1) { assert(list_del(&head, item) != NULL); item->scratchpad = 0; l++; } } assert(l + list_count(&head) == k); ts_hashx("frr_each_safe+del", "e0beb71dd963a75af05b722b8e71b61b304587d860c8accdc4349067542b86bb"); #else /* !IS_SORTED */ prng = prng_new(0); k = 0; for (i = 0; i < NITEM; i++) { j = prng_rand(prng) % NITEM; if (itm[j].scratchpad == 0) { list_add_tail(&head, &itm[j]); itm[j].scratchpad = 1; k++; } } assert(list_count(&head) == k); assert(list_first(&head) != NULL); ts_hash("fill / add_tail", "eabfcf1413936daaf20965abced95762f45110a6619b84aac7d38481bce4ea19"); for (i = 0; i < NITEM / 2; i++) { j = prng_rand(prng) % NITEM; if (itm[j].scratchpad == 1) { assert(list_del(&head, &itm[j]) != NULL); itm[j].scratchpad = 0; k--; } } ts_hash("del-prng", "86d568a95eb429dab3162976c5a5f3f75aabc835932cd682aa280b6923549564"); l = 0; while ((item = list_pop(&head))) { assert(item->scratchpad != 0); item->scratchpad = 0; l++; } assert(l == k); assert(list_count(&head) == 0); assert(list_first(&head) == NULL); ts_hash("pop", "df3f619804a92fdb4057192dc43dd748ea778adc52bc498ce80524c014b81119"); prng_free(prng); prng = prng_new(0x1e5a2d69); k = 0; for (i = 0; i < NITEM; i++) { j = prng_rand(prng) % NITEM; if (itm[j].scratchpad == 0) { list_add_head(&head, &itm[j]); itm[j].scratchpad = 1; k++; } } assert(list_count(&head) == k); assert(list_first(&head) != NULL); ts_hash("fill / add_head", "3084d8f8a28b8c756ccc0a92d60d86f6d776273734ddc3f9e1d89526f5ca2795"); for (i = 0; i < NITEM / 2; i++) { j = prng_rand(prng) % NITEM; if (itm[j].scratchpad == 1) { assert(list_del(&head, &itm[j]) != NULL); itm[j].scratchpad = 0; k--; } } ts_hash("del-prng", "dc916fa7ea4418792c7c8232d74df2887f9975ead4222f4b977be6bc0b52285e"); l = 0; while ((item = list_pop(&head))) { assert(item->scratchpad != 0); item->scratchpad = 0; l++; } assert(l == k); assert(list_count(&head) == 0); assert(list_first(&head) == NULL); ts_hash("pop", "df3f619804a92fdb4057192dc43dd748ea778adc52bc498ce80524c014b81119"); prng_free(prng); prng = prng_new(0x692d1e5a); k = 0; for (i = 0; i < NITEM; i++) { j = prng_rand(prng) % NITEM; if (itm[j].scratchpad == 0) { if (prng_rand(prng) & 1) { list_add_tail(&head, &itm[j]); } else { list_add_head(&head, &itm[j]); } itm[j].scratchpad = 1; k++; } } assert(list_count(&head) == k); assert(list_first(&head) != NULL); ts_hash("fill / add_{head,tail}", "93fa180a575c96e4b6c3775c2de7843ee3254dd6ed5af699bbe155f994114b06"); for (i = 0; i < NITEM * 3; i++) { int op = prng_rand(prng); j = prng_rand(prng) % NITEM; if (op & 1) { /* delete or pop */ if (op & 2) { item = list_pop(&head); if (!item) continue; } else { item = &itm[j]; if (item->scratchpad == 0) continue; assert(list_del(&head, item) != NULL); } item->scratchpad = 0; k--; } else { item = &itm[j]; if (item->scratchpad != 0) continue; item->scratchpad = 1; k++; switch ((op >> 1) & 1) { case 0: list_add_head(&head, item); break; case 1: list_add_tail(&head, item); break; default: assert(0); } } } assert(list_count(&head) == k); assert(list_first(&head) != NULL); ts_hash("prng add/del", "4909f31d06bb006efca4dfeebddb8de071733ddf502f89b6d532155208bbc6df"); #if !IS_ATOMIC(REALTYPE) /* variant with add_after */ for (i = 0; i < NITEM * 3; i++) { int op = prng_rand(prng); j = prng_rand(prng) % NITEM; if (op & 1) { /* delete or pop */ if (op & 2) { item = list_pop(&head); if (!item) continue; } else { item = &itm[j]; if (item->scratchpad == 0) continue; assert(list_del(&head, item) != NULL); } item->scratchpad = 0; k--; } else { item = &itm[j]; if (item->scratchpad != 0) continue; item->scratchpad = 1; k++; switch ((op >> 1) & 3) { case 0: list_add_head(&head, item); break; case 1: list_add_tail(&head, item); break; case 2: case 3: prev = NULL; l = 0; do { j = prng_rand(prng) % NITEM; prev = &itm[j]; if (prev->scratchpad == 0 || prev == item) prev = NULL; l++; } while (!prev && l < 10); list_add_after(&head, prev, item); break; default: assert(0); } } } assert(list_count(&head) == k); assert(list_first(&head) != NULL); ts_hash("prng add/after/del", "84c5fc83294eabebb9808ccbba32a303c4fca084db87ed1277d2bae1f8c5bee4"); #endif l = 0; #endif while ((item = list_pop(&head))) { assert(item->scratchpad != 0); item->scratchpad = 0; l++; } assert(l == k); assert(list_count(&head) == 0); assert(list_first(&head) == NULL); ts_hash("pop", "df3f619804a92fdb4057192dc43dd748ea778adc52bc498ce80524c014b81119"); list_fini(&head); ts_ref("fini"); ts_end(); printf("%s end\n", str(TYPE)); } #undef ts_hashx #undef item #undef itm #undef head #undef list #undef list_head #undef list_item #undef list_cmp #undef list_hash #undef list_init #undef list_fini #undef list_first #undef list_next #undef list_next_safe #undef list_count #undef list_add #undef list_add_head #undef list_add_tail #undef list_add_after #undef list_find #undef list_find_lt #undef list_find_gteq #undef list_del #undef list_pop #undef REALTYPE #undef TYPE frr-7.2.1/tests/lib/test_typelist.py0000644000000000000000000000124513610377563014414 00000000000000import frrtest class TestTypelist(frrtest.TestMultiOut): program = './test_typelist' TestTypelist.onesimple('LIST end') TestTypelist.onesimple('DLIST end') TestTypelist.onesimple('ATOMLIST end') TestTypelist.onesimple('HEAP end') TestTypelist.onesimple('SORTLIST_UNIQ end') TestTypelist.onesimple('SORTLIST_NONUNIQ end') TestTypelist.onesimple('HASH end') TestTypelist.onesimple('HASH_collisions end') TestTypelist.onesimple('SKIPLIST_UNIQ end') TestTypelist.onesimple('SKIPLIST_NONUNIQ end') TestTypelist.onesimple('RBTREE_UNIQ end') TestTypelist.onesimple('RBTREE_NONUNIQ end') TestTypelist.onesimple('ATOMSORT_UNIQ end') TestTypelist.onesimple('ATOMSORT_NONUNIQ end') frr-7.2.1/tests/lib/test_zlog.c0000644000000000000000000000310513610377563013301 00000000000000/* * Zlog tests. * Copyright (C) 2018 Cumulus Networks, Inc. * Quentin Young * * This program is free software; you can redistribute it and/or modify it * under the terms of the 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; see the file COPYING; if not, write to the Free Software * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA */ #include #include #include "log.h" /* maximum amount of data to hexdump */ #define MAXDATA 16384 /* * Test hexdump functionality. * * At the moment, not crashing is considered success. */ static bool test_zlog_hexdump(void) { unsigned int nl = 1; do { long d[nl]; for (unsigned int i = 0; i < nl; i++) d[i] = random(); zlog_hexdump(d, nl * sizeof(long)); } while (++nl * sizeof(long) <= MAXDATA); return true; } bool (*tests[])(void) = { test_zlog_hexdump, }; int main(int argc, char **argv) { openzlog("testzlog", "NONE", 0, LOG_CONS | LOG_NDELAY | LOG_PID, LOG_ERR); zlog_set_file("test_zlog.log", LOG_DEBUG); for (unsigned int i = 0; i < array_size(tests); i++) if (!tests[i]()) return 1; return 0; } frr-7.2.1/tests/lib/test_zlog.py0000644000000000000000000000012213610377563013503 00000000000000import frrtest class TestZlog(frrtest.TestMultiOut): program = './test_zlog' frr-7.2.1/tests/lib/test_zmq.c0000644000000000000000000001627513610377563013151 00000000000000/* * ZeroMQ event test * Copyright (C) 2017 David Lamparter, for NetDEF, Inc. * * This program is free software; you can redistribute it and/or modify it * under the terms of the 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; see the file COPYING; if not, write to the Free Software * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA */ #include #include "memory.h" #include "sigevent.h" #include "frr_zmq.h" DEFINE_MTYPE_STATIC(LIB, TESTBUF, "zmq test buffer") DEFINE_MTYPE_STATIC(LIB, ZMQMSG, "zmq message") static struct thread_master *master; static void msg_buf_free(void *data, void *hint) { XFREE(MTYPE_TESTBUF, data); } static int recv_delim(void *zmqsock) { /* receive delim */ zmq_msg_t zdelim; int more; zmq_msg_init(&zdelim); zmq_msg_recv(&zdelim, zmqsock, 0); more = zmq_msg_more(&zdelim); zmq_msg_close(&zdelim); return more; } static void send_delim(void *zmqsock) { /* Send delim */ zmq_msg_t zdelim; zmq_msg_init(&zdelim); zmq_msg_send(&zdelim, zmqsock, ZMQ_SNDMORE); zmq_msg_close(&zdelim); } static void run_client(int syncfd) { int i, j; char buf[32]; char dummy; void *zmqctx = NULL; void *zmqsock; int more; read(syncfd, &dummy, 1); zmqctx = zmq_ctx_new(); zmq_ctx_set(zmqctx, ZMQ_IPV6, 1); zmqsock = zmq_socket(zmqctx, ZMQ_DEALER); if (zmq_connect(zmqsock, "tcp://127.0.0.1:17171")) { perror("zmq_connect"); exit(1); } /* single-part */ for (i = 0; i < 8; i++) { snprintf(buf, sizeof(buf), "msg #%d %c%c%c", i, 'a' + i, 'b' + i, 'c' + i); printf("client send: %s\n", buf); fflush(stdout); send_delim(zmqsock); zmq_send(zmqsock, buf, strlen(buf) + 1, 0); more = recv_delim(zmqsock); while (more) { zmq_recv(zmqsock, buf, sizeof(buf), 0); printf("client recv: %s\n", buf); size_t len = sizeof(more); if (zmq_getsockopt(zmqsock, ZMQ_RCVMORE, &more, &len)) break; } } /* multipart */ for (i = 2; i < 5; i++) { printf("---\n"); send_delim(zmqsock); zmq_msg_t part; for (j = 1; j <= i; j++) { char *dyn = XMALLOC(MTYPE_TESTBUF, 32); snprintf(dyn, 32, "part %d/%d", j, i); printf("client send: %s\n", dyn); fflush(stdout); zmq_msg_init_data(&part, dyn, strlen(dyn) + 1, msg_buf_free, NULL); zmq_msg_send(&part, zmqsock, j < i ? ZMQ_SNDMORE : 0); } recv_delim(zmqsock); do { char *data; zmq_msg_recv(&part, zmqsock, 0); data = zmq_msg_data(&part); more = zmq_msg_more(&part); printf("client recv (more: %d): %s\n", more, data); } while (more); zmq_msg_close(&part); } /* write callback */ printf("---\n"); snprintf(buf, 32, "Done receiving"); printf("client send: %s\n", buf); fflush(stdout); send_delim(zmqsock); zmq_send(zmqsock, buf, strlen(buf) + 1, 0); /* wait for message from server */ more = recv_delim(zmqsock); while (more) { zmq_recv(zmqsock, buf, sizeof(buf), 0); printf("client recv: %s\n", buf); size_t len = sizeof(more); if (zmq_getsockopt(zmqsock, ZMQ_RCVMORE, &more, &len)) break; } zmq_close(zmqsock); zmq_ctx_term(zmqctx); } static struct frrzmq_cb *cb; static void recv_id_and_delim(void *zmqsock, zmq_msg_t *msg_id) { /* receive id */ zmq_msg_init(msg_id); zmq_msg_recv(msg_id, zmqsock, 0); /* receive delim */ recv_delim(zmqsock); } static void send_id_and_delim(void *zmqsock, zmq_msg_t *msg_id) { /* Send Id */ zmq_msg_send(msg_id, zmqsock, ZMQ_SNDMORE); send_delim(zmqsock); } static void serverwritefn(void *arg, void *zmqsock) { zmq_msg_t *msg_id = (zmq_msg_t *)arg; char buf[32] = "Test write callback"; size_t i; for (i = 0; i < strlen(buf); i++) buf[i] = toupper(buf[i]); printf("server send: %s\n", buf); fflush(stdout); send_id_and_delim(zmqsock, msg_id); zmq_send(zmqsock, buf, strlen(buf) + 1, 0); /* send just once */ frrzmq_thread_cancel(&cb, &cb->write); zmq_msg_close(msg_id); XFREE(MTYPE_ZMQMSG, msg_id); } static void serverpartfn(void *arg, void *zmqsock, zmq_msg_t *msg, unsigned partnum) { static int num = 0; int more = zmq_msg_more(msg); char *in = zmq_msg_data(msg); size_t i; zmq_msg_t reply; char *out; /* Id */ if (partnum == 0) { send_id_and_delim(zmqsock, msg); return; } /* Delim */ if (partnum == 1) return; printf("server recv part %u (more: %d): %s\n", partnum, more, in); fflush(stdout); out = XMALLOC(MTYPE_TESTBUF, strlen(in) + 1); for (i = 0; i < strlen(in); i++) out[i] = toupper(in[i]); out[i] = '\0'; zmq_msg_init_data(&reply, out, strlen(out) + 1, msg_buf_free, NULL); zmq_msg_send(&reply, zmqsock, ZMQ_SNDMORE); if (more) return; out = XMALLOC(MTYPE_TESTBUF, 32); snprintf(out, 32, "msg# was %u", partnum); zmq_msg_init_data(&reply, out, strlen(out) + 1, msg_buf_free, NULL); zmq_msg_send(&reply, zmqsock, 0); zmq_msg_close(&reply); if (++num < 7) return; /* write callback test */ char buf[32]; zmq_msg_t *msg_id = XMALLOC(MTYPE_ZMQMSG, sizeof(zmq_msg_t)); recv_id_and_delim(zmqsock, msg_id); zmq_recv(zmqsock, buf, sizeof(buf), 0); printf("server recv: %s\n", buf); fflush(stdout); frrzmq_thread_add_write_msg(master, serverwritefn, NULL, msg_id, zmqsock, &cb); } static void serverfn(void *arg, void *zmqsock) { static int num = 0; zmq_msg_t msg_id; char buf[32]; size_t i; recv_id_and_delim(zmqsock, &msg_id); zmq_recv(zmqsock, buf, sizeof(buf), 0); printf("server recv: %s\n", buf); fflush(stdout); for (i = 0; i < strlen(buf); i++) buf[i] = toupper(buf[i]); send_id_and_delim(zmqsock, &msg_id); zmq_msg_close(&msg_id); zmq_send(zmqsock, buf, strlen(buf) + 1, 0); if (++num < 4) return; /* change to multipart callback */ frrzmq_thread_cancel(&cb, &cb->read); frrzmq_thread_cancel(&cb, &cb->write); frrzmq_thread_add_read_part(master, serverpartfn, NULL, NULL, zmqsock, &cb); } static void sigchld(void) { printf("child exited.\n"); frrzmq_thread_cancel(&cb, &cb->read); frrzmq_thread_cancel(&cb, &cb->write); } static struct quagga_signal_t sigs[] = { { .signal = SIGCHLD, .handler = sigchld, }, }; static void run_server(int syncfd) { void *zmqsock; char dummy = 0; struct thread t; master = thread_master_create(NULL); signal_init(master, array_size(sigs), sigs); frrzmq_init(); zmqsock = zmq_socket(frrzmq_context, ZMQ_ROUTER); if (zmq_bind(zmqsock, "tcp://*:17171")) { perror("zmq_bind"); exit(1); } frrzmq_thread_add_read_msg(master, serverfn, NULL, NULL, zmqsock, &cb); write(syncfd, &dummy, sizeof(dummy)); while (thread_fetch(master, &t)) thread_call(&t); zmq_close(zmqsock); frrzmq_finish(); thread_master_free(master); log_memstats_stderr("test"); } int main(void) { int syncpipe[2]; pid_t child; if (pipe(syncpipe)) { perror("pipe"); exit(1); } child = fork(); if (child < 0) { perror("fork"); exit(1); } else if (child == 0) { run_client(syncpipe[0]); exit(0); } run_server(syncpipe[1]); exit(0); } frr-7.2.1/tests/ospf6d/0000755000000000000000000000000013610377563011637 500000000000000frr-7.2.1/tests/ospf6d/test_lsdb.c0000644000000000000000000001375513610377563013721 00000000000000/* * CLI/command dummy handling tester * * Copyright (C) 2015 by David Lamparter, * for Open Source Routing / NetDEF, Inc. * * Quagga is free software; you can redistribute it and/or modify it * under the terms of the GNU General Public License as published by the * Free Software Foundation; either version 2, or (at your option) any * later version. * * Quagga 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; see the file COPYING; if not, write to the Free Software * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA */ #include #include "prefix.h" #include "vector.h" #include "vty.h" #include "ospf6d/ospf6_lsa.h" #include "ospf6d/ospf6_lsdb.h" #include "tests/lib/cli/common_cli.h" #include "tests/ospf6d/test_lsdb_clippy.c" static struct ospf6_lsdb *lsdb; static struct ospf6_lsa **lsas = NULL; static size_t lsa_count = 0; static void lsa_check_resize(size_t len) { struct ospf6_lsa **templsas; if (lsa_count >= len) return; templsas = realloc(lsas, len * sizeof(lsas[0])); if (templsas) lsas = templsas; else return; memset(lsas + lsa_count, 0, sizeof(lsas[0]) * (len - lsa_count)); lsa_count = len; } DEFPY(lsa_set, lsa_set_cmd, "lsa set (0-999999)$idx {type (0-65535)|id A.B.C.D|adv A.B.C.D}", "LSA\n" "set\n" "LSA index in array\n" "OSPF6 type code\n" "OSPF6 type code\n" "LS-ID\n" "LS-ID\n" "Advertising router\n" "Advertising router\n") { struct ospf6_lsa_header hdr; memset(&hdr, 0, sizeof(hdr)); hdr.type = htons(type); hdr.id = id.s_addr; hdr.adv_router = adv.s_addr; lsa_check_resize(idx + 1); if (lsas[idx]) ospf6_lsa_unlock(lsas[idx]); lsas[idx] = ospf6_lsa_create_headeronly(&hdr); ospf6_lsa_lock(lsas[idx]); return CMD_SUCCESS; } DEFPY(lsa_drop, lsa_drop_cmd, "lsa drop (0-999999)$idx", "LSA\n" "drop reference\n" "LSA index in array\n") { if ((size_t)idx >= lsa_count) return CMD_SUCCESS; if (lsas[idx]->lock != 1) vty_out(vty, "refcount at %u\n", lsas[idx]->lock); ospf6_lsa_unlock(lsas[idx]); lsas[idx] = NULL; return CMD_SUCCESS; } DEFPY(lsdb_add, lsdb_add_cmd, "lsdb add (0-999999)$idx", "LSDB\n" "insert LSA into LSDB\n" "LSA index in array\n") { ospf6_lsdb_add(lsas[idx], lsdb); return CMD_SUCCESS; } DEFPY(lsdb_remove, lsdb_remove_cmd, "lsdb remove (0-999999)$idx", "LSDB\n" "remove LSA from LSDB\n" "LSA index in array\n") { ospf6_lsdb_remove(lsas[idx], lsdb); return CMD_SUCCESS; } static void lsa_show_oneline(struct vty *vty, struct ospf6_lsa *lsa) { char adv_router[64], id[64]; if (!lsa) { vty_out(vty, "lsa = NULL\n"); return; } inet_ntop(AF_INET, &lsa->header->id, id, sizeof(id)); inet_ntop(AF_INET, &lsa->header->adv_router, adv_router, sizeof(adv_router)); vty_out(vty, "type %u adv %s id %s\n", ntohs(lsa->header->type), adv_router, id); } DEFPY(lsdb_walk, lsdb_walk_cmd, "lsdb walk", "LSDB\n" "walk entries\n") { struct ospf6_lsa *lsa; unsigned cnt = 0; for (ALL_LSDB(lsdb, lsa)) { lsa_show_oneline(vty, lsa); cnt++; } vty_out(vty, "%u entries.\n", cnt); return CMD_SUCCESS; } DEFPY(lsdb_walk_type, lsdb_walk_type_cmd, "lsdb walk type (0-65535)", "LSDB\n" "walk entries\n" "entry type\n" "entry type\n") { struct ospf6_lsa *lsa; unsigned cnt = 0; type = htons(type); for (ALL_LSDB_TYPED(lsdb, type, lsa)) { lsa_show_oneline(vty, lsa); cnt++; } vty_out(vty, "%u entries.\n", cnt); return CMD_SUCCESS; } DEFPY(lsdb_walk_type_adv, lsdb_walk_type_adv_cmd, "lsdb walk type (0-65535) adv A.B.C.D", "LSDB\n" "walk entries\n" "entry type\n" "entry type\n" "advertising router ID\n" "advertising router ID\n") { struct ospf6_lsa *lsa; unsigned cnt = 0; type = htons(type); for (ALL_LSDB_TYPED_ADVRTR(lsdb, type, adv.s_addr, lsa)) { lsa_show_oneline(vty, lsa); cnt++; } vty_out(vty, "%u entries.\n", cnt); return CMD_SUCCESS; } DEFPY(lsdb_get, lsdb_get_cmd, "lsdb type (0-65535) adv A.B.C.D id A.B.C.D", "LSDB\n" "get entry's successor\n" "entry type\n" "entry type\n" "advertising router ID\n" "advertising router ID\n" "LS-ID\n" "LS-ID\n") { struct ospf6_lsa *lsa; type = htons(type); if (!strcmp(argv[1]->text, "get-next")) lsa = ospf6_lsdb_lookup_next(type, id.s_addr, adv.s_addr, lsdb); else lsa = ospf6_lsdb_lookup(type, id.s_addr, adv.s_addr, lsdb); lsa_show_oneline(vty, lsa); return CMD_SUCCESS; } DEFPY(lsa_refcounts, lsa_refcounts_cmd, "lsa refcounts", "LSA\n" "show reference counts\n") { for (size_t i = 0; i < lsa_count; i++) if (lsas[i]) vty_out(vty, "[%zu] %u\n", i, lsas[i]->lock); return CMD_SUCCESS; } DEFPY(lsdb_create, lsdb_create_cmd, "lsdb create", "LSDB\n" "create LSDB\n") { if (lsdb) ospf6_lsdb_delete(lsdb); lsdb = ospf6_lsdb_create(NULL); return CMD_SUCCESS; } DEFPY(lsdb_delete, lsdb_delete_cmd, "lsdb delete", "LSDB\n" "delete LSDB\n") { ospf6_lsdb_delete(lsdb); lsdb = NULL; return CMD_SUCCESS; } struct zebra_privs_t ospf6d_privs; void test_init(int argc, char **argv) { ospf6_lsa_init(); install_element(ENABLE_NODE, &lsa_set_cmd); install_element(ENABLE_NODE, &lsa_refcounts_cmd); install_element(ENABLE_NODE, &lsa_drop_cmd); install_element(ENABLE_NODE, &lsdb_create_cmd); install_element(ENABLE_NODE, &lsdb_delete_cmd); install_element(ENABLE_NODE, &lsdb_add_cmd); install_element(ENABLE_NODE, &lsdb_remove_cmd); install_element(ENABLE_NODE, &lsdb_walk_cmd); install_element(ENABLE_NODE, &lsdb_walk_type_cmd); install_element(ENABLE_NODE, &lsdb_walk_type_adv_cmd); install_element(ENABLE_NODE, &lsdb_get_cmd); } frr-7.2.1/tests/ospf6d/test_lsdb.in0000644000000000000000000000233413610377563014074 00000000000000lsa set 0 type 1 adv 1.2.3.4 id 0.0.0.1 lsa set 1 type 1 adv 1.2.3.4 id 0.0.0.2 lsa set 2 type 2 adv 1.2.3.4 id 0.0.0.3 lsa set 3 type 2 adv 128.2.3.4 id 0.0.0.4 lsa set 4 type 2 adv 128.2.3.4 id 0.0.0.5 lsa set 5 type 3 adv 0.0.0.1 id 0.0.0.6 lsa refcounts lsdb create lsdb walk lsdb walk type 1 lsdb walk type 2 lsdb get type 1 adv 1.2.3.4 id 0.0.0.2 lsdb get-next type 1 adv 1.2.3.4 id 0.0.0.2 lsa refcounts lsdb add 0 lsdb add 1 lsa refcounts lsdb walk lsdb walk type 1 lsdb walk type 2 lsdb get type 1 adv 1.2.3.4 id 0.0.0.2 lsdb get-next type 1 adv 1.2.3.4 id 0.0.0.2 lsa refcounts lsdb remove 0 lsdb add 2 lsdb add 3 lsdb add 4 lsa refcounts lsdb walk lsdb walk type 1 lsdb walk type 2 lsdb get type 1 adv 1.2.3.4 id 0.0.0.2 lsdb get-next type 1 adv 1.2.3.4 id 0.0.0.2 lsa refcounts lsdb add 5 lsa refcounts lsdb walk lsdb walk type 1 lsdb walk type 2 lsdb get type 1 adv 1.2.3.4 id 0.0.0.2 lsdb get-next type 1 adv 1.2.3.4 id 0.0.0.2 lsa refcounts lsdb remove 1 lsdb remove 5 lsa refcounts lsdb walk lsdb walk type 1 lsdb walk type 2 lsdb get type 1 adv 1.2.3.4 id 0.0.0.2 lsdb get-next type 1 adv 1.2.3.4 id 0.0.0.2 lsa refcounts lsdb delete lsa refcounts lsa drop 0 lsa drop 1 lsa drop 2 lsa drop 3 lsa drop 4 lsa drop 5 frr-7.2.1/tests/ospf6d/test_lsdb.py0000644000000000000000000000012013610377563014105 00000000000000import frrtest class TestLSDB(frrtest.TestRefOut): program = './test_lsdb' frr-7.2.1/tests/ospf6d/test_lsdb.refout0000644000000000000000000000665113610377563015000 00000000000000test# lsa set 0 type 1 adv 1.2.3.4 id 0.0.0.1 test# lsa set 1 type 1 adv 1.2.3.4 id 0.0.0.2 test# lsa set 2 type 2 adv 1.2.3.4 id 0.0.0.3 test# lsa set 3 type 2 adv 128.2.3.4 id 0.0.0.4 test# lsa set 4 type 2 adv 128.2.3.4 id 0.0.0.5 test# lsa set 5 type 3 adv 0.0.0.1 id 0.0.0.6 test# lsa refcounts [0] 1 [1] 1 [2] 1 [3] 1 [4] 1 [5] 1 test# test# lsdb create test# test# lsdb walk 0 entries. test# lsdb walk type 1 0 entries. test# lsdb walk type 2 0 entries. test# lsdb get type 1 adv 1.2.3.4 id 0.0.0.2 lsa = NULL test# lsdb get-next type 1 adv 1.2.3.4 id 0.0.0.2 lsa = NULL test# lsa refcounts [0] 1 [1] 1 [2] 1 [3] 1 [4] 1 [5] 1 test# test# lsdb add 0 test# lsdb add 1 test# lsa refcounts [0] 2 [1] 2 [2] 1 [3] 1 [4] 1 [5] 1 test# test# lsdb walk type 1 adv 1.2.3.4 id 0.0.0.1 type 1 adv 1.2.3.4 id 0.0.0.2 2 entries. test# lsdb walk type 1 type 1 adv 1.2.3.4 id 0.0.0.1 type 1 adv 1.2.3.4 id 0.0.0.2 2 entries. test# lsdb walk type 2 0 entries. test# lsdb get type 1 adv 1.2.3.4 id 0.0.0.2 type 1 adv 1.2.3.4 id 0.0.0.2 test# lsdb get-next type 1 adv 1.2.3.4 id 0.0.0.2 lsa = NULL test# lsa refcounts [0] 2 [1] 2 [2] 1 [3] 1 [4] 1 [5] 1 test# test# lsdb remove 0 test# lsdb add 2 test# lsdb add 3 test# lsdb add 4 test# lsa refcounts [0] 1 [1] 2 [2] 2 [3] 2 [4] 2 [5] 1 test# test# lsdb walk type 1 adv 1.2.3.4 id 0.0.0.2 type 2 adv 1.2.3.4 id 0.0.0.3 type 2 adv 128.2.3.4 id 0.0.0.4 type 2 adv 128.2.3.4 id 0.0.0.5 4 entries. test# lsdb walk type 1 type 1 adv 1.2.3.4 id 0.0.0.2 1 entries. test# lsdb walk type 2 type 2 adv 1.2.3.4 id 0.0.0.3 type 2 adv 128.2.3.4 id 0.0.0.4 type 2 adv 128.2.3.4 id 0.0.0.5 3 entries. test# lsdb get type 1 adv 1.2.3.4 id 0.0.0.2 type 1 adv 1.2.3.4 id 0.0.0.2 test# lsdb get-next type 1 adv 1.2.3.4 id 0.0.0.2 type 2 adv 1.2.3.4 id 0.0.0.3 test# lsa refcounts [0] 1 [1] 2 [2] 2 [3] 2 [4] 2 [5] 1 test# test# lsdb add 5 test# lsa refcounts [0] 1 [1] 2 [2] 2 [3] 2 [4] 2 [5] 2 test# test# lsdb walk type 1 adv 1.2.3.4 id 0.0.0.2 type 2 adv 1.2.3.4 id 0.0.0.3 type 2 adv 128.2.3.4 id 0.0.0.4 type 2 adv 128.2.3.4 id 0.0.0.5 type 3 adv 0.0.0.1 id 0.0.0.6 5 entries. test# lsdb walk type 1 type 1 adv 1.2.3.4 id 0.0.0.2 1 entries. test# lsdb walk type 2 type 2 adv 1.2.3.4 id 0.0.0.3 type 2 adv 128.2.3.4 id 0.0.0.4 type 2 adv 128.2.3.4 id 0.0.0.5 3 entries. test# lsdb get type 1 adv 1.2.3.4 id 0.0.0.2 type 1 adv 1.2.3.4 id 0.0.0.2 test# lsdb get-next type 1 adv 1.2.3.4 id 0.0.0.2 type 2 adv 1.2.3.4 id 0.0.0.3 test# lsa refcounts [0] 1 [1] 2 [2] 2 [3] 2 [4] 2 [5] 2 test# test# lsdb remove 1 test# lsdb remove 5 test# lsa refcounts [0] 1 [1] 1 [2] 2 [3] 2 [4] 2 [5] 1 test# test# lsdb walk type 2 adv 1.2.3.4 id 0.0.0.3 type 2 adv 128.2.3.4 id 0.0.0.4 type 2 adv 128.2.3.4 id 0.0.0.5 3 entries. test# lsdb walk type 1 0 entries. test# lsdb walk type 2 type 2 adv 1.2.3.4 id 0.0.0.3 type 2 adv 128.2.3.4 id 0.0.0.4 type 2 adv 128.2.3.4 id 0.0.0.5 3 entries. test# lsdb get type 1 adv 1.2.3.4 id 0.0.0.2 lsa = NULL test# lsdb get-next type 1 adv 1.2.3.4 id 0.0.0.2 type 2 adv 1.2.3.4 id 0.0.0.3 test# lsa refcounts [0] 1 [1] 1 [2] 2 [3] 2 [4] 2 [5] 1 test# test# lsdb delete test# test# lsa refcounts [0] 1 [1] 1 [2] 1 [3] 1 [4] 1 [5] 1 test# lsa drop 0 test# lsa drop 1 test# lsa drop 2 test# lsa drop 3 test# lsa drop 4 test# lsa drop 5 test# test# end. frr-7.2.1/tests/runtests.py0000644000000000000000000000023513610377563012617 00000000000000import pytest import sys import os sys.path.append(os.path.join(os.path.dirname(__file__), 'helpers','python')) raise SystemExit(pytest.main(sys.argv[1:])) frr-7.2.1/tests/subdir.am0000644000000000000000000003472413610377563012177 00000000000000# # tests # if BGPD TESTS_BGPD = \ tests/bgpd/test_aspath \ tests/bgpd/test_capability \ tests/bgpd/test_packet \ tests/bgpd/test_peer_attr \ tests/bgpd/test_ecommunity \ tests/bgpd/test_mp_attr \ tests/bgpd/test_mpath \ tests/bgpd/test_bgp_table else TESTS_BGPD = endif if ISISD if SOLARIS TESTS_ISISD = else TESTS_ISISD = \ tests/isisd/test_fuzz_isis_tlv \ tests/isisd/test_isis_lspdb \ tests/isisd/test_isis_vertex_queue \ # end endif else TESTS_ISISD = endif if OSPF6D TESTS_OSPF6D = \ tests/ospf6d/test_lsdb \ # end else TESTS_OSPF6D = endif tests/lib/cli/tests_lib_cli_test_cli-test_cli.$(OBJEXT): tests/lib/cli/test_cli_clippy.c tests/lib/cli/test_cli-test_cli.$(OBJEXT): tests/lib/cli/test_cli_clippy.c tests/ospf6d/tests_ospf6d_test_lsdb-test_lsdb.$(OBJEXT): tests/ospf6d/test_lsdb_clippy.c tests/ospf6d/test_lsdb-test_lsdb.$(OBJEXT): tests/ospf6d/test_lsdb_clippy.c check_PROGRAMS = \ tests/lib/cxxcompat \ tests/lib/test_atomlist \ tests/lib/test_buffer \ tests/lib/test_checksum \ tests/lib/test_heavy_thread \ tests/lib/test_heavy_wq \ tests/lib/test_heavy \ tests/lib/test_idalloc \ tests/lib/test_memory \ tests/lib/test_nexthop_iter \ tests/lib/test_ntop \ tests/lib/test_prefix2str \ tests/lib/test_printfrr \ tests/lib/test_privs \ tests/lib/test_ringbuf \ tests/lib/test_srcdest_table \ tests/lib/test_segv \ tests/lib/test_seqlock \ tests/lib/test_sig \ tests/lib/test_stream \ tests/lib/test_table \ tests/lib/test_timer_correctness \ tests/lib/test_timer_performance \ tests/lib/test_ttable \ tests/lib/test_typelist \ tests/lib/test_zlog \ tests/lib/test_graph \ tests/lib/cli/test_cli \ tests/lib/cli/test_commands \ tests/lib/northbound/test_oper_data \ $(TESTS_BGPD) \ $(TESTS_ISISD) \ $(TESTS_OSPF6D) \ # end if ZEROMQ check_PROGRAMS += \ tests/lib/test_zmq \ # end endif tests/lib/cli/test_commands_defun.c: vtysh/vtysh_cmd.c sed \ -e 's%"vtysh/vtysh\.h"%"tests/helpers/c/tests.h"%' \ -e 's/vtysh_init_cmd/test_init_cmd/' \ -e 's/VTYSH_[A-Z][A-Z_0-9]*/0/g' \ < vtysh/vtysh_cmd.c \ > "$@" CLEANFILES += tests/lib/cli/test_commands_defun.c tests/isisd/test_fuzz_isis_tlv_tests.h: $(top_srcdir)/tests/isisd/test_fuzz_isis_tlv_tests.h.gz gzip -d < $(top_srcdir)/tests/isisd/test_fuzz_isis_tlv_tests.h.gz > "$@" CLEANFILES += tests/isisd/test_fuzz_isis_tlv_tests.h tests/isisd/tests_isisd_test_fuzz_isis_tlv-test_fuzz_isis_tlv.$(OBJEXT): \ tests/isisd/test_fuzz_isis_tlv_tests.h tests/isisd/test_fuzz_isis_tlv-test_fuzz_isis_tlv.$(OBJEXT): \ tests/isisd/test_fuzz_isis_tlv_tests.h noinst_HEADERS += \ tests/helpers/c/prng.h \ tests/helpers/c/tests.h \ tests/lib/cli/common_cli.h \ tests/lib/test_typelist.h \ # end # # *sigh* - there is no way to get CPPFLAGS or CFLAGS for a group of files :( # TESTS_CPPFLAGS = $(AM_CPPFLAGS) \ -I$(top_srcdir)/tests/helpers/c \ -I$(top_builddir)/tests/helpers/c \ # end TESTS_CFLAGS = \ $(LIBYANG_CFLAGS) \ $(SAN_FLAGS) \ # end # note no -Werror ALL_TESTS_LDADD = lib/libfrr.la $(LIBCAP) BGP_TEST_LDADD = bgpd/libbgp.a $(RFPLDADD) $(ALL_TESTS_LDADD) -lm ISISD_TEST_LDADD = isisd/libisis.a $(ALL_TESTS_LDADD) OSPF6_TEST_LDADD = ospf6d/libospf6.a $(ALL_TESTS_LDADD) tests_bgpd_test_aspath_CFLAGS = $(TESTS_CFLAGS) tests_bgpd_test_aspath_CPPFLAGS = $(TESTS_CPPFLAGS) tests_bgpd_test_aspath_LDADD = $(BGP_TEST_LDADD) tests_bgpd_test_aspath_SOURCES = tests/bgpd/test_aspath.c tests_bgpd_test_bgp_table_CFLAGS = $(TESTS_CFLAGS) tests_bgpd_test_bgp_table_CPPFLAGS = $(TESTS_CPPFLAGS) tests_bgpd_test_bgp_table_LDADD = $(BGP_TEST_LDADD) tests_bgpd_test_bgp_table_SOURCES = tests/bgpd/test_bgp_table.c tests_bgpd_test_capability_CFLAGS = $(TESTS_CFLAGS) tests_bgpd_test_capability_CPPFLAGS = $(TESTS_CPPFLAGS) tests_bgpd_test_capability_LDADD = $(BGP_TEST_LDADD) tests_bgpd_test_capability_SOURCES = tests/bgpd/test_capability.c tests_bgpd_test_ecommunity_CFLAGS = $(TESTS_CFLAGS) tests_bgpd_test_ecommunity_CPPFLAGS = $(TESTS_CPPFLAGS) tests_bgpd_test_ecommunity_LDADD = $(BGP_TEST_LDADD) tests_bgpd_test_ecommunity_SOURCES = tests/bgpd/test_ecommunity.c tests_bgpd_test_mp_attr_CFLAGS = $(TESTS_CFLAGS) tests_bgpd_test_mp_attr_CPPFLAGS = $(TESTS_CPPFLAGS) tests_bgpd_test_mp_attr_LDADD = $(BGP_TEST_LDADD) tests_bgpd_test_mp_attr_SOURCES = tests/bgpd/test_mp_attr.c tests_bgpd_test_mpath_CFLAGS = $(TESTS_CFLAGS) tests_bgpd_test_mpath_CPPFLAGS = $(TESTS_CPPFLAGS) tests_bgpd_test_mpath_LDADD = $(BGP_TEST_LDADD) tests_bgpd_test_mpath_SOURCES = tests/bgpd/test_mpath.c tests_bgpd_test_packet_CFLAGS = $(TESTS_CFLAGS) tests_bgpd_test_packet_CPPFLAGS = $(TESTS_CPPFLAGS) tests_bgpd_test_packet_LDADD = $(BGP_TEST_LDADD) tests_bgpd_test_packet_SOURCES = tests/bgpd/test_packet.c tests_bgpd_test_peer_attr_CFLAGS = $(TESTS_CFLAGS) tests_bgpd_test_peer_attr_CPPFLAGS = $(TESTS_CPPFLAGS) tests_bgpd_test_peer_attr_LDADD = $(BGP_TEST_LDADD) tests_bgpd_test_peer_attr_SOURCES = tests/bgpd/test_peer_attr.c tests_isisd_test_fuzz_isis_tlv_CFLAGS = $(TESTS_CFLAGS) -I$(top_builddir)/tests/isisd tests_isisd_test_fuzz_isis_tlv_CPPFLAGS = $(TESTS_CPPFLAGS) -I$(top_builddir)/tests/isisd tests_isisd_test_fuzz_isis_tlv_LDADD = $(ISISD_TEST_LDADD) tests_isisd_test_fuzz_isis_tlv_SOURCES = tests/isisd/test_fuzz_isis_tlv.c nodist_tests_isisd_test_fuzz_isis_tlv_SOURCES = tests/isisd/test_fuzz_isis_tlv_tests.h tests_isisd_test_isis_lspdb_CFLAGS = $(TESTS_CFLAGS) tests_isisd_test_isis_lspdb_CPPFLAGS = $(TESTS_CPPFLAGS) tests_isisd_test_isis_lspdb_LDADD = $(ISISD_TEST_LDADD) tests_isisd_test_isis_lspdb_SOURCES = tests/isisd/test_isis_lspdb.c tests_isisd_test_isis_vertex_queue_CFLAGS = $(TESTS_CFLAGS) tests_isisd_test_isis_vertex_queue_CPPFLAGS = $(TESTS_CPPFLAGS) tests_isisd_test_isis_vertex_queue_LDADD = $(ISISD_TEST_LDADD) tests_isisd_test_isis_vertex_queue_SOURCES = tests/isisd/test_isis_vertex_queue.c tests_lib_cxxcompat_CFLAGS = $(TESTS_CFLAGS) $(CXX_COMPAT_CFLAGS) $(WERROR) tests_lib_cxxcompat_CPPFLAGS = $(TESTS_CPPFLAGS) tests_lib_cxxcompat_SOURCES = tests/lib/cxxcompat.c tests_lib_cxxcompat_LDADD = $(ALL_TESTS_LDADD) tests_lib_cli_test_cli_CFLAGS = $(TESTS_CFLAGS) tests_lib_cli_test_cli_CPPFLAGS = $(TESTS_CPPFLAGS) tests_lib_cli_test_cli_LDADD = $(ALL_TESTS_LDADD) tests_lib_cli_test_cli_SOURCES = tests/lib/cli/test_cli.c tests/lib/cli/common_cli.c tests_lib_cli_test_commands_CFLAGS = $(TESTS_CFLAGS) tests_lib_cli_test_commands_CPPFLAGS = $(TESTS_CPPFLAGS) tests_lib_cli_test_commands_LDADD = $(ALL_TESTS_LDADD) nodist_tests_lib_cli_test_commands_SOURCES = tests/lib/cli/test_commands_defun.c tests_lib_cli_test_commands_SOURCES = tests/lib/cli/test_commands.c tests/helpers/c/prng.c tests_lib_northbound_test_oper_data_CFLAGS = $(TESTS_CFLAGS) tests_lib_northbound_test_oper_data_CPPFLAGS = $(TESTS_CPPFLAGS) tests_lib_northbound_test_oper_data_LDADD = $(ALL_TESTS_LDADD) tests_lib_northbound_test_oper_data_SOURCES = tests/lib/northbound/test_oper_data.c nodist_tests_lib_northbound_test_oper_data_SOURCES = yang/frr-test-module.yang.c tests_lib_test_atomlist_CFLAGS = $(TESTS_CFLAGS) tests_lib_test_atomlist_CPPFLAGS = $(TESTS_CPPFLAGS) tests_lib_test_atomlist_LDADD = $(ALL_TESTS_LDADD) tests_lib_test_atomlist_SOURCES = tests/lib/test_atomlist.c tests_lib_test_buffer_CFLAGS = $(TESTS_CFLAGS) tests_lib_test_buffer_CPPFLAGS = $(TESTS_CPPFLAGS) tests_lib_test_buffer_LDADD = $(ALL_TESTS_LDADD) tests_lib_test_buffer_SOURCES = tests/lib/test_buffer.c tests_lib_test_checksum_CFLAGS = $(TESTS_CFLAGS) tests_lib_test_checksum_CPPFLAGS = $(TESTS_CPPFLAGS) tests_lib_test_checksum_LDADD = $(ALL_TESTS_LDADD) tests_lib_test_checksum_SOURCES = tests/lib/test_checksum.c tests_lib_test_graph_CFLAGS = $(TESTS_CFLAGS) tests_lib_test_graph_CPPFLAGS = $(TESTS_CPPFLAGS) tests_lib_test_graph_LDADD = $(ALL_TESTS_LDADD) tests_lib_test_graph_SOURCES = tests/lib/test_graph.c tests_lib_test_heavy_CFLAGS = $(TESTS_CFLAGS) tests_lib_test_heavy_CPPFLAGS = $(TESTS_CPPFLAGS) tests_lib_test_heavy_LDADD = $(ALL_TESTS_LDADD) -lm tests_lib_test_heavy_SOURCES = tests/lib/test_heavy.c tests/helpers/c/main.c tests_lib_test_heavy_thread_CFLAGS = $(TESTS_CFLAGS) tests_lib_test_heavy_thread_CPPFLAGS = $(TESTS_CPPFLAGS) tests_lib_test_heavy_thread_LDADD = $(ALL_TESTS_LDADD) -lm tests_lib_test_heavy_thread_SOURCES = tests/lib/test_heavy_thread.c tests/helpers/c/main.c tests_lib_test_heavy_wq_CFLAGS = $(TESTS_CFLAGS) tests_lib_test_heavy_wq_CPPFLAGS = $(TESTS_CPPFLAGS) tests_lib_test_heavy_wq_LDADD = $(ALL_TESTS_LDADD) -lm tests_lib_test_heavy_wq_SOURCES = tests/lib/test_heavy_wq.c tests/helpers/c/main.c tests_lib_test_idalloc_CFLAGS = $(TESTS_CFLAGS) tests_lib_test_idalloc_LDADD = $(ALL_TESTS_LDADD) tests_lib_test_idalloc_SOURCES = tests/lib/test_idalloc.c tests_lib_test_memory_CFLAGS = $(TESTS_CFLAGS) tests_lib_test_memory_CPPFLAGS = $(TESTS_CPPFLAGS) tests_lib_test_memory_LDADD = $(ALL_TESTS_LDADD) tests_lib_test_memory_SOURCES = tests/lib/test_memory.c tests_lib_test_nexthop_iter_CFLAGS = $(TESTS_CFLAGS) tests_lib_test_nexthop_iter_CPPFLAGS = $(TESTS_CPPFLAGS) tests_lib_test_nexthop_iter_LDADD = $(ALL_TESTS_LDADD) tests_lib_test_nexthop_iter_SOURCES = tests/lib/test_nexthop_iter.c tests/helpers/c/prng.c tests_lib_test_ntop_CFLAGS = $(TESTS_CFLAGS) tests_lib_test_ntop_CPPFLAGS = $(TESTS_CPPFLAGS) tests_lib_test_ntop_LDADD = # none tests_lib_test_ntop_SOURCES = tests/lib/test_ntop.c tests/helpers/c/prng.c tests_lib_test_prefix2str_CFLAGS = $(TESTS_CFLAGS) tests_lib_test_prefix2str_CPPFLAGS = $(TESTS_CPPFLAGS) tests_lib_test_prefix2str_LDADD = $(ALL_TESTS_LDADD) tests_lib_test_prefix2str_SOURCES = tests/lib/test_prefix2str.c tests/helpers/c/prng.c tests_lib_test_printfrr_CFLAGS = $(TESTS_CFLAGS) tests_lib_test_printfrr_CPPFLAGS = $(TESTS_CPPFLAGS) tests_lib_test_printfrr_LDADD = $(ALL_TESTS_LDADD) tests_lib_test_printfrr_SOURCES = tests/lib/test_printfrr.c tests_lib_test_privs_CFLAGS = $(TESTS_CFLAGS) tests_lib_test_privs_CPPFLAGS = $(TESTS_CPPFLAGS) tests_lib_test_privs_LDADD = $(ALL_TESTS_LDADD) tests_lib_test_privs_SOURCES = tests/lib/test_privs.c tests_lib_test_ringbuf_CFLAGS = $(TESTS_CFLAGS) tests_lib_test_ringbuf_CPPFLAGS = $(TESTS_CPPFLAGS) tests_lib_test_ringbuf_LDADD = $(ALL_TESTS_LDADD) tests_lib_test_ringbuf_SOURCES = tests/lib/test_ringbuf.c tests_lib_test_segv_CFLAGS = $(TESTS_CFLAGS) tests_lib_test_segv_CPPFLAGS = $(TESTS_CPPFLAGS) tests_lib_test_segv_LDADD = $(ALL_TESTS_LDADD) tests_lib_test_segv_SOURCES = tests/lib/test_segv.c tests_lib_test_seqlock_CFLAGS = $(TESTS_CFLAGS) tests_lib_test_seqlock_CPPFLAGS = $(TESTS_CPPFLAGS) tests_lib_test_seqlock_LDADD = $(ALL_TESTS_LDADD) tests_lib_test_seqlock_SOURCES = tests/lib/test_seqlock.c tests_lib_test_sig_CFLAGS = $(TESTS_CFLAGS) tests_lib_test_sig_CPPFLAGS = $(TESTS_CPPFLAGS) tests_lib_test_sig_LDADD = $(ALL_TESTS_LDADD) tests_lib_test_sig_SOURCES = tests/lib/test_sig.c tests_lib_test_srcdest_table_CFLAGS = $(TESTS_CFLAGS) tests_lib_test_srcdest_table_CPPFLAGS = $(TESTS_CPPFLAGS) tests_lib_test_srcdest_table_LDADD = $(ALL_TESTS_LDADD) tests_lib_test_srcdest_table_SOURCES = tests/lib/test_srcdest_table.c tests/helpers/c/prng.c tests_lib_test_stream_CFLAGS = $(TESTS_CFLAGS) tests_lib_test_stream_CPPFLAGS = $(TESTS_CPPFLAGS) tests_lib_test_stream_LDADD = $(ALL_TESTS_LDADD) tests_lib_test_stream_SOURCES = tests/lib/test_stream.c tests_lib_test_table_CFLAGS = $(TESTS_CFLAGS) tests_lib_test_table_CPPFLAGS = $(TESTS_CPPFLAGS) tests_lib_test_table_LDADD = $(ALL_TESTS_LDADD) -lm tests_lib_test_table_SOURCES = tests/lib/test_table.c tests_lib_test_timer_correctness_CFLAGS = $(TESTS_CFLAGS) tests_lib_test_timer_correctness_CPPFLAGS = $(TESTS_CPPFLAGS) tests_lib_test_timer_correctness_LDADD = $(ALL_TESTS_LDADD) tests_lib_test_timer_correctness_SOURCES = tests/lib/test_timer_correctness.c tests/helpers/c/prng.c tests_lib_test_timer_performance_CFLAGS = $(TESTS_CFLAGS) tests_lib_test_timer_performance_CPPFLAGS = $(TESTS_CPPFLAGS) tests_lib_test_timer_performance_LDADD = $(ALL_TESTS_LDADD) tests_lib_test_timer_performance_SOURCES = tests/lib/test_timer_performance.c tests/helpers/c/prng.c tests_lib_test_ttable_CFLAGS = $(TESTS_CFLAGS) tests_lib_test_ttable_CPPFLAGS = $(TESTS_CPPFLAGS) tests_lib_test_ttable_LDADD = $(ALL_TESTS_LDADD) tests_lib_test_ttable_SOURCES = tests/lib/test_ttable.c tests_lib_test_typelist_CFLAGS = $(TESTS_CFLAGS) tests_lib_test_typelist_CPPFLAGS = $(TESTS_CPPFLAGS) tests_lib_test_typelist_LDADD = $(ALL_TESTS_LDADD) tests_lib_test_typelist_SOURCES = tests/lib/test_typelist.c tests/helpers/c/prng.c tests_lib_test_zlog_CFLAGS = $(TESTS_CFLAGS) tests_lib_test_zlog_CPPFLAGS = $(TESTS_CPPFLAGS) tests_lib_test_zlog_LDADD = $(ALL_TESTS_LDADD) tests_lib_test_zlog_SOURCES = tests/lib/test_zlog.c tests_lib_test_zmq_CFLAGS = $(TESTS_CFLAGS) $(ZEROMQ_CFLAGS) tests_lib_test_zmq_CPPFLAGS = $(TESTS_CPPFLAGS) tests_lib_test_zmq_LDADD = lib/libfrrzmq.la $(ALL_TESTS_LDADD) $(ZEROMQ_LIBS) tests_lib_test_zmq_SOURCES = tests/lib/test_zmq.c tests_ospf6d_test_lsdb_CFLAGS = $(TESTS_CFLAGS) tests_ospf6d_test_lsdb_CPPFLAGS = $(TESTS_CPPFLAGS) tests_ospf6d_test_lsdb_LDADD = $(OSPF6_TEST_LDADD) tests_ospf6d_test_lsdb_SOURCES = tests/ospf6d/test_lsdb.c tests/lib/cli/common_cli.c EXTRA_DIST += \ tests/runtests.py \ tests/bgpd/test_aspath.py \ tests/bgpd/test_capability.py \ tests/bgpd/test_ecommunity.py \ tests/bgpd/test_mp_attr.py \ tests/bgpd/test_mpath.py \ tests/bgpd/test_peer_attr.py \ tests/helpers/python/frrsix.py \ tests/helpers/python/frrtest.py \ tests/isisd/test_fuzz_isis_tlv.py \ tests/isisd/test_fuzz_isis_tlv_tests.h.gz \ tests/isisd/test_isis_lspdb.py \ tests/isisd/test_isis_vertex_queue.py \ tests/lib/cli/test_commands.in \ tests/lib/cli/test_commands.py \ tests/lib/cli/test_commands.refout \ tests/lib/cli/test_cli.in \ tests/lib/cli/test_cli.py \ tests/lib/cli/test_cli.refout \ tests/lib/northbound/test_oper_data.in \ tests/lib/northbound/test_oper_data.py \ tests/lib/northbound/test_oper_data.refout \ tests/lib/test_atomlist.py \ tests/lib/test_nexthop_iter.py \ tests/lib/test_ntop.py \ tests/lib/test_prefix2str.py \ tests/lib/test_printfrr.py \ tests/lib/test_ringbuf.py \ tests/lib/test_srcdest_table.py \ tests/lib/test_stream.py \ tests/lib/test_stream.refout \ tests/lib/test_table.py \ tests/lib/test_timer_correctness.py \ tests/lib/test_ttable.py \ tests/lib/test_ttable.refout \ tests/lib/test_typelist.py \ tests/lib/test_zlog.py \ tests/lib/test_graph.py \ tests/lib/test_graph.refout \ tests/ospf6d/test_lsdb.py \ tests/ospf6d/test_lsdb.in \ tests/ospf6d/test_lsdb.refout \ # end .PHONY: tests/tests.xml tests/tests.xml: $(check_PROGRAMS) ( cd tests; $(PYTHON) ../$(srcdir)/tests/runtests.py --junitxml=tests.xml -v ../$(srcdir)/tests; ) check: tests/tests.xml clean-local: clean-tests .PHONY: clean-tests clean-tests: -rm -f tests/tests.xml frr-7.2.1/tests/topotests/0000755000000000000000000000000013610377563012502 500000000000000frr-7.2.1/tests/topotests/subdir.am0000644000000000000000000000030213610377563014224 00000000000000TOPOTESTS_DIR = tests/topotests topotests-build: ## Builds docker images for topotests $(TOPOTESTS_DIR)/docker/build.sh topotests: ## Runs topotests $(TOPOTESTS_DIR)/docker/frr-topotests.sh frr-7.2.1/tools/0000755000000000000000000000000013610377563010434 500000000000000frr-7.2.1/tools/Makefile0000644000000000000000000000023313610377563012012 00000000000000all: ALWAYS @echo please specify a target to build %: ALWAYS @$(MAKE) -s -C .. tools/$@ Makefile: #nothing ALWAYS: .PHONY: ALWAYS makefiles .SUFFIXES: frr-7.2.1/tools/build-debian-package.sh0000755000000000000000000000103213610377563014617 00000000000000#!/bin/sh # # Written by Daniil Baturin, 2018 # This file is public domain set -e cd "`dirname $0`" cd .. if [ "`id -u`" = 0 ]; then echo "Running as root - installing dependencies" apt-get install fakeroot debhelper devscripts mk-build-deps --install debian/control exit 0 fi git diff-index --quiet HEAD || echo "Warning: git working directory is not clean!" echo "Preparing the build" tools/tarsource.sh -V echo "Building the Debian package" if test $# -eq 0; then dpkg-buildpackage -b -uc -us else dpkg-buildpackage "$@" fi frr-7.2.1/tools/etc/0000755000000000000000000000000013610377563011207 500000000000000frr-7.2.1/tools/etc/frr/0000755000000000000000000000000013610377563012000 500000000000000frr-7.2.1/tools/etc/frr/daemons0000644000000000000000000000431013610377563013267 00000000000000# This file tells the frr package which daemons to start. # # Sample configurations for these daemons can be found in # /usr/share/doc/frr/examples/. # # ATTENTION: # # When activating a daemon for the first time, a config file, even if it is # empty, has to be present *and* be owned by the user and group "frr", else # the daemon will not be started by /etc/init.d/frr. The permissions should # be u=rw,g=r,o=. # When using "vtysh" such a config file is also needed. It should be owned by # group "frrvty" and set to ug=rw,o= though. Check /etc/pam.d/frr, too. # # The watchfrr and zebra daemons are always started. # bgpd=no ospfd=no ospf6d=no ripd=no ripngd=no isisd=no pimd=no ldpd=no nhrpd=no eigrpd=no babeld=no sharpd=no pbrd=no bfdd=no fabricd=no vrrpd=no # # If this option is set the /etc/init.d/frr script automatically loads # the config via "vtysh -b" when the servers are started. # Check /etc/pam.d/frr if you intend to use "vtysh"! # vtysh_enable=yes zebra_options=" -A 127.0.0.1 -s 90000000" bgpd_options=" -A 127.0.0.1" ospfd_options=" -A 127.0.0.1" ospf6d_options=" -A ::1" ripd_options=" -A 127.0.0.1" ripngd_options=" -A ::1" isisd_options=" -A 127.0.0.1" pimd_options=" -A 127.0.0.1" ldpd_options=" -A 127.0.0.1" nhrpd_options=" -A 127.0.0.1" eigrpd_options=" -A 127.0.0.1" babeld_options=" -A 127.0.0.1" sharpd_options=" -A 127.0.0.1" pbrd_options=" -A 127.0.0.1" staticd_options="-A 127.0.0.1" bfdd_options=" -A 127.0.0.1" fabricd_options="-A 127.0.0.1" vrrpd_options=" -A 127.0.0.1" # # This is the maximum number of FD's that will be available. # Upon startup this is read by the control files and ulimit # is called. Uncomment and use a reasonable value for your # setup if you are expecting a large number of peers in # say BGP. #MAX_FDS=1024 # The list of daemons to watch is automatically generated by the init script. #watchfrr_options="" # for debugging purposes, you can specify a "wrap" command to start instead # of starting the daemon directly, e.g. to use valgrind on ospfd: # ospfd_wrap="/usr/bin/valgrind" # or you can use "all_wrap" for all daemons, e.g. to use perf record: # all_wrap="/usr/bin/perf record --call-graph -" # the normal daemon command is added to this at the end. frr-7.2.1/tools/etc/frr/daemons.conf0000644000000000000000000000007113610377563014213 00000000000000# this file is deprecated, please use "daemons" instead. frr-7.2.1/tools/etc/frr/frr.conf0000644000000000000000000000017013610377563013356 00000000000000# default to using syslog. /etc/rsyslog.d/45-frr.conf places the log # in /var/log/frr/frr.log log syslog informational frr-7.2.1/tools/etc/frr/support_bundle_commands.conf0000644000000000000000000000317613610377563017524 00000000000000# FRR Support Bundle Command List # Do Not modify the lines that start with # PROC_NAME, CMD_LIST_START and CMD_LIST_END # Add the new command for each process between # CMD_LIST_START and CMD_LIST_END lines # BGP Support Bundle Command List PROC_NAME:bgp CMD_LIST_START show bgp summary show ip bgp show ip bgp neighbors show ip bgp summary show ip bgp statistics show ip bgp update-groups advertise-queue show ip bgp update-groups advertised-routes show ip bgp update-groups packet-queue show ip bgp update-groups statistics show ip bgp peer-group show ip bgp memory show bgp ipv6 show bgp ipv6 neighbors show bgp ipv6 summary show bgp ipv6 update-groups advertise-queue show bgp ipv6 update-groups advertised-routes show bgp ipv6 update-groups packet-queue show bgp ipv6 update-groups statistics show ip bgp statistics show bgp evpn route CMD_LIST_END # Zebra Support Bundle Command List PROC_NAME:zebra CMD_LIST_START show zebra show zebra client summary show ip route show route-map show memory show interface show vrf show error all show work-queues show running-config show thread cpu show thread poll show daemons show version CMD_LIST_END # OSPF Support Bundle Command List # PROC_NAME:ospf # CMD_LIST_START # CMD_LIST_END # RIP Support Bundle Command List # PROC_NAME:rip # CMD_LIST_START # CMD_LIST_END # ISIS Support Bundle Command List # PROC_NAME:isis # CMD_LIST_START # CMD_LIST_END # BFD Support Bundle Command List # PROC_NAME:bfd # CMD_LIST_START # CMD_LIST_END # STATIC Support Bundle Command List # PROC_NAME:static # CMD_LIST_START # CMD_LIST_END # PIM Support Bundle Command List # PROC_NAME:pim # CMD_LIST_START # CMD_LIST_END frr-7.2.1/tools/etc/frr/vtysh.conf0000644000000000000000000000004013610377563013736 00000000000000service integrated-vtysh-config frr-7.2.1/tools/etc/iproute2/0000755000000000000000000000000013610377563012760 500000000000000frr-7.2.1/tools/etc/iproute2/rt_protos.d/0000755000000000000000000000000013610377563015235 500000000000000frr-7.2.1/tools/etc/iproute2/rt_protos.d/frr.conf0000644000000000000000000000030513610377563016613 00000000000000# Additional protocol strings defined by frr for each of its daemons 186 bgp 187 isis 188 ospf 189 rip 190 ripng 191 nhrp 192 eigrp 193 ldp 194 sharp 195 pbr 196 static 197 openfabric frr-7.2.1/tools/etc/rsyslog.d/0000755000000000000000000000000013610377563013133 500000000000000frr-7.2.1/tools/etc/rsyslog.d/45-frr.conf0000644000000000000000000000235713610377563014750 00000000000000# The lines below cause all FRR daemons and process to go # to /var/log/frr/frr.log, then drops the message so it does # not also go to /var/log/syslog, so the messages are not duplicated $outchannel frr_log,/var/log/frr/frr.log if $programname == 'babeld' or $programname == 'bgpd' or $programname == 'eigrpd' or $programname == 'frr' or $programname == 'isisd' or $programname == 'fabricd' or $programname == 'ldpd' or $programname == 'nhrpd' or $programname == 'ospf6d' or $programname == 'ospfd' or $programname == 'pimd' or $programname == 'ripd' or $programname == 'ripngd' or $programname == 'vrrpd' or $programname == 'watchfrr' or $programname == 'zebra' then :omfile:$frr_log if $programname == 'babeld' or $programname == 'bgpd' or $programname == 'eigrpd' or $programname == 'frr' or $programname == 'isisd' or $programname == 'fabricd' or $programname == 'ldpd' or $programname == 'nhrpd' or $programname == 'ospf6d' or $programname == 'ospfd' or $programname == 'pimd' or $programname == 'ripd' or $programname == 'ripngd' or $programname == 'vrrpd' or $programname == 'watchfrr' or $programname == 'zebra' then stop frr-7.2.1/tools/frr-reload0000755000000000000000000000030613610377563012336 00000000000000#!/bin/sh if test -e /usr/lib/frr/frr-reload.py; then exec /usr/lib/frr/frr-reload.py --reload /etc/frr/frr.conf fi >&2 echo "Please install frr-pythontools package. Required for reload" exit 1 frr-7.2.1/tools/frr-reload.py0000755000000000000000000015053613610377563013000 00000000000000#!/usr/bin/python # Frr Reloader # Copyright (C) 2014 Cumulus Networks, Inc. # # This file is part of Frr. # # Frr is free software; you can redistribute it and/or modify it # under the terms of the GNU General Public License as published by the # Free Software Foundation; either version 2, or (at your option) any # later version. # # Frr 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 Frr; see the file COPYING. If not, write to the Free # Software Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA # 02111-1307, USA. # """ This program - reads a frr configuration text file - reads frr's current running configuration via "vtysh -c 'show running'" - compares the two configs and determines what commands to execute to synchronize frr's running configuration with the configuation in the text file """ from __future__ import print_function, unicode_literals import argparse import copy import logging import os import random import re import string import subprocess import sys from collections import OrderedDict try: from ipaddress import IPv6Address, ip_network except ImportError: from ipaddr import IPv6Address, IPNetwork from pprint import pformat try: dict.iteritems except AttributeError: # Python 3 def iteritems(d): return iter(d.items()) else: # Python 2 def iteritems(d): return d.iteritems() log = logging.getLogger(__name__) class VtyshMarkException(Exception): pass class Context(object): """ A Context object represents a section of frr configuration such as: ! interface swp3 description swp3 -> r8's swp1 ipv6 nd suppress-ra link-detect ! or a single line context object such as this: ip forwarding """ def __init__(self, keys, lines): self.keys = keys self.lines = lines # Keep a dictionary of the lines, this is to make it easy to tell if a # line exists in this Context self.dlines = OrderedDict() for ligne in lines: self.dlines[ligne] = True def add_lines(self, lines): """ Add lines to specified context """ self.lines.extend(lines) for ligne in lines: self.dlines[ligne] = True class Config(object): """ A frr configuration is stored in a Config object. A Config object contains a dictionary of Context objects where the Context keys ('router ospf' for example) are our dictionary key. """ def __init__(self): self.lines = [] self.contexts = OrderedDict() def load_from_file(self, filename): """ Read configuration from specified file and slurp it into internal memory The internal representation has been marked appropriately by passing it through vtysh with the -m parameter """ log.info('Loading Config object from file %s', filename) try: file_output = subprocess.check_output(['/usr/bin/vtysh', '-m', '-f', filename], stderr=subprocess.STDOUT) except subprocess.CalledProcessError as e: ve = VtyshMarkException(e) ve.output = e.output raise ve for line in file_output.decode('utf-8').split('\n'): line = line.strip() # Compress duplicate whitespaces line = ' '.join(line.split()) if ":" in line: qv6_line = get_normalized_ipv6_line(line) self.lines.append(qv6_line) else: self.lines.append(line) self.load_contexts() def load_from_show_running(self): """ Read running configuration and slurp it into internal memory The internal representation has been marked appropriately by passing it through vtysh with the -m parameter """ log.info('Loading Config object from vtysh show running') try: config_text = subprocess.check_output( "/usr/bin/vtysh -c 'show run' | /usr/bin/tail -n +4 | /usr/bin/vtysh -m -f -", shell=True, stderr=subprocess.STDOUT) except subprocess.CalledProcessError as e: ve = VtyshMarkException(e) ve.output = e.output raise ve for line in config_text.decode('utf-8').split('\n'): line = line.strip() if (line == 'Building configuration...' or line == 'Current configuration:' or not line): continue self.lines.append(line) self.load_contexts() def get_lines(self): """ Return the lines read in from the configuration """ return '\n'.join(self.lines) def get_contexts(self): """ Return the parsed context as strings for display, log etc. """ for (_, ctx) in sorted(iteritems(self.contexts)): print(str(ctx) + '\n') def save_contexts(self, key, lines): """ Save the provided key and lines as a context """ if not key: return ''' IP addresses specified in "network" statements, "ip prefix-lists" etc. can differ in the host part of the specification the user provides and what the running config displays. For example, user can specify 11.1.1.1/24, and the running config displays this as 11.1.1.0/24. Ensure we don't do a needless operation for such lines. IS-IS & OSPFv3 have no "network" support. ''' re_key_rt = re.match(r'(ip|ipv6)\s+route\s+([A-Fa-f:.0-9/]+)(.*)$', key[0]) if re_key_rt: addr = re_key_rt.group(2) if '/' in addr: try: if 'ipaddress' not in sys.modules: newaddr = IPNetwork(addr) key[0] = '%s route %s/%s%s' % (re_key_rt.group(1), newaddr.network, newaddr.prefixlen, re_key_rt.group(3)) else: newaddr = ip_network(addr, strict=False) key[0] = '%s route %s/%s%s' % (re_key_rt.group(1), str(newaddr.network_address), newaddr.prefixlen, re_key_rt.group(3)) except ValueError: pass re_key_rt = re.match( r'(ip|ipv6)\s+prefix-list(.*)(permit|deny)\s+([A-Fa-f:.0-9/]+)(.*)$', key[0] ) if re_key_rt: addr = re_key_rt.group(4) if '/' in addr: try: if 'ipaddress' not in sys.modules: newaddr = '%s/%s' % (IPNetwork(addr).network, IPNetwork(addr).prefixlen) else: network_addr = ip_network(addr, strict=False) newaddr = '%s/%s' % (str(network_addr.network_address), network_addr.prefixlen) except ValueError: newaddr = addr else: newaddr = addr legestr = re_key_rt.group(5) re_lege = re.search(r'(.*)le\s+(\d+)\s+ge\s+(\d+)(.*)', legestr) if re_lege: legestr = '%sge %s le %s%s' % (re_lege.group(1), re_lege.group(3), re_lege.group(2), re_lege.group(4)) re_lege = re.search(r'(.*)ge\s+(\d+)\s+le\s+(\d+)(.*)', legestr) if (re_lege and ((re_key_rt.group(1) == "ip" and re_lege.group(3) == "32") or (re_key_rt.group(1) == "ipv6" and re_lege.group(3) == "128"))): legestr = '%sge %s%s' % (re_lege.group(1), re_lege.group(2), re_lege.group(4)) key[0] = '%s prefix-list%s%s %s%s' % (re_key_rt.group(1), re_key_rt.group(2), re_key_rt.group(3), newaddr, legestr) if lines and key[0].startswith('router bgp'): newlines = [] for line in lines: re_net = re.match(r'network\s+([A-Fa-f:.0-9/]+)(.*)$', line) if re_net: addr = re_net.group(1) if '/' not in addr and key[0].startswith('router bgp'): # This is most likely an error because with no # prefixlen, BGP treats the prefixlen as 8 addr = addr + '/8' try: if 'ipaddress' not in sys.modules: newaddr = IPNetwork(addr) line = 'network %s/%s %s' % (newaddr.network, newaddr.prefixlen, re_net.group(2)) else: network_addr = ip_network(addr, strict=False) line = 'network %s/%s %s' % (str(network_addr.network_address), network_addr.prefixlen, re_net.group(2)) newlines.append(line) except ValueError: # Really this should be an error. Whats a network # without an IP Address following it ? newlines.append(line) else: newlines.append(line) lines = newlines ''' More fixups in user specification and what running config shows. "null0" in routes must be replaced by Null0. ''' if (key[0].startswith('ip route') or key[0].startswith('ipv6 route') and 'null0' in key[0]): key[0] = re.sub(r'\s+null0(\s*$)', ' Null0', key[0]) if lines: if tuple(key) not in self.contexts: ctx = Context(tuple(key), lines) self.contexts[tuple(key)] = ctx else: ctx = self.contexts[tuple(key)] ctx.add_lines(lines) else: if tuple(key) not in self.contexts: ctx = Context(tuple(key), []) self.contexts[tuple(key)] = ctx def load_contexts(self): """ Parse the configuration and create contexts for each appropriate block """ current_context_lines = [] ctx_keys = [] ''' The end of a context is flagged via the 'end' keyword: ! interface swp52 ipv6 nd suppress-ra link-detect ! end router bgp 10 bgp router-id 10.0.0.1 bgp log-neighbor-changes no bgp default ipv4-unicast neighbor EBGP peer-group neighbor EBGP advertisement-interval 1 neighbor EBGP timers connect 10 neighbor 2001:40:1:4::6 remote-as 40 neighbor 2001:40:1:8::a remote-as 40 ! end address-family ipv6 neighbor IBGPv6 activate neighbor 2001:10::2 peer-group IBGPv6 neighbor 2001:10::3 peer-group IBGPv6 exit-address-family ! end address-family evpn neighbor LEAF activate advertise-all-vni vni 10100 rd 65000:10100 route-target import 10.1.1.1:10100 route-target export 10.1.1.1:10100 exit-vni exit-address-family ! end router ospf ospf router-id 10.0.0.1 log-adjacency-changes detail timers throttle spf 0 50 5000 ! end ''' # The code assumes that its working on the output from the "vtysh -m" # command. That provides the appropriate markers to signify end of # a context. This routine uses that to build the contexts for the # config. # # There are single line contexts such as "log file /media/node/zebra.log" # and multi-line contexts such as "router ospf" and subcontexts # within a context such as "address-family" within "router bgp" # In each of these cases, the first line of the context becomes the # key of the context. So "router bgp 10" is the key for the non-address # family part of bgp, "router bgp 10, address-family ipv6 unicast" is # the key for the subcontext and so on. ctx_keys = [] main_ctx_key = [] new_ctx = True # the keywords that we know are single line contexts. bgp in this case # is not the main router bgp block, but enabling multi-instance oneline_ctx_keywords = ("access-list ", "agentx", "allow-external-route-update", "bgp ", "debug ", "domainname ", "dump ", "enable ", "frr ", "hostname ", "ip ", "ipv6 ", "log ", "mpls", "no ", "password ", "ptm-enable", "router-id ", "service ", "table ", "username ", "zebra ", "vrrp autoconfigure") for line in self.lines: if not line: continue if line.startswith('!') or line.startswith('#'): continue # one line contexts if new_ctx is True and any(line.startswith(keyword) for keyword in oneline_ctx_keywords): self.save_contexts(ctx_keys, current_context_lines) # Start a new context main_ctx_key = [] ctx_keys = [line, ] current_context_lines = [] log.debug('LINE %-50s: entering new context, %-50s', line, ctx_keys) self.save_contexts(ctx_keys, current_context_lines) new_ctx = True elif line == "end": self.save_contexts(ctx_keys, current_context_lines) log.debug('LINE %-50s: exiting old context, %-50s', line, ctx_keys) # Start a new context new_ctx = True main_ctx_key = [] ctx_keys = [] current_context_lines = [] elif line == "exit-vrf": self.save_contexts(ctx_keys, current_context_lines) current_context_lines.append(line) log.debug('LINE %-50s: append to current_context_lines, %-50s', line, ctx_keys) #Start a new context new_ctx = True main_ctx_key = [] ctx_keys = [] current_context_lines = [] elif line in ["exit-address-family", "exit", "exit-vnc"]: # if this exit is for address-family ipv4 unicast, ignore the pop if main_ctx_key: self.save_contexts(ctx_keys, current_context_lines) # Start a new context ctx_keys = copy.deepcopy(main_ctx_key) current_context_lines = [] log.debug('LINE %-50s: popping from subcontext to ctx%-50s', line, ctx_keys) elif line == "exit-vni": if sub_main_ctx_key: self.save_contexts(ctx_keys, current_context_lines) # Start a new context ctx_keys = copy.deepcopy(sub_main_ctx_key) current_context_lines = [] log.debug('LINE %-50s: popping from sub-subcontext to ctx%-50s', line, ctx_keys) elif new_ctx is True: if not main_ctx_key: ctx_keys = [line, ] else: ctx_keys = copy.deepcopy(main_ctx_key) main_ctx_key = [] current_context_lines = [] new_ctx = False log.debug('LINE %-50s: entering new context, %-50s', line, ctx_keys) elif (line.startswith("address-family ") or line.startswith("vnc defaults") or line.startswith("vnc l2-group") or line.startswith("vnc nve-group")): main_ctx_key = [] # Save old context first self.save_contexts(ctx_keys, current_context_lines) current_context_lines = [] main_ctx_key = copy.deepcopy(ctx_keys) log.debug('LINE %-50s: entering sub-context, append to ctx_keys', line) if line == "address-family ipv6": ctx_keys.append("address-family ipv6 unicast") elif line == "address-family ipv4": ctx_keys.append("address-family ipv4 unicast") elif line == "address-family evpn": ctx_keys.append("address-family l2vpn evpn") else: ctx_keys.append(line) elif ((line.startswith("vni ") and len(ctx_keys) == 2 and ctx_keys[0].startswith('router bgp') and ctx_keys[1] == 'address-family l2vpn evpn')): # Save old context first self.save_contexts(ctx_keys, current_context_lines) current_context_lines = [] sub_main_ctx_key = copy.deepcopy(ctx_keys) log.debug('LINE %-50s: entering sub-sub-context, append to ctx_keys', line) ctx_keys.append(line) else: # Continuing in an existing context, add non-commented lines to it current_context_lines.append(line) log.debug('LINE %-50s: append to current_context_lines, %-50s', line, ctx_keys) # Save the context of the last one self.save_contexts(ctx_keys, current_context_lines) def line_to_vtysh_conft(ctx_keys, line, delete): """ Return the vtysh command for the specified context line """ cmd = [] cmd.append('vtysh') cmd.append('-c') cmd.append('conf t') if line: for ctx_key in ctx_keys: cmd.append('-c') cmd.append(ctx_key) line = line.lstrip() if delete: cmd.append('-c') if line.startswith('no '): cmd.append('%s' % line[3:]) else: cmd.append('no %s' % line) else: cmd.append('-c') cmd.append(line) # If line is None then we are typically deleting an entire # context ('no router ospf' for example) else: if delete: # Only put the 'no' on the last sub-context for ctx_key in ctx_keys: cmd.append('-c') if ctx_key == ctx_keys[-1]: cmd.append('no %s' % ctx_key) else: cmd.append('%s' % ctx_key) else: for ctx_key in ctx_keys: cmd.append('-c') cmd.append(ctx_key) return cmd def line_for_vtysh_file(ctx_keys, line, delete): """ Return the command as it would appear in frr.conf """ cmd = [] if line: for (i, ctx_key) in enumerate(ctx_keys): cmd.append(' ' * i + ctx_key) line = line.lstrip() indent = len(ctx_keys) * ' ' if delete: if line.startswith('no '): cmd.append('%s%s' % (indent, line[3:])) else: cmd.append('%sno %s' % (indent, line)) else: cmd.append(indent + line) # If line is None then we are typically deleting an entire # context ('no router ospf' for example) else: if delete: # Only put the 'no' on the last sub-context for ctx_key in ctx_keys: if ctx_key == ctx_keys[-1]: cmd.append('no %s' % ctx_key) else: cmd.append('%s' % ctx_key) else: for ctx_key in ctx_keys: cmd.append(ctx_key) cmd = '\n' + '\n'.join(cmd) # There are some commands that are on by default so their "no" form will be # displayed in the config. "no bgp default ipv4-unicast" is one of these. # If we need to remove this line we do so by adding "bgp default ipv4-unicast", # not by doing a "no no bgp default ipv4-unicast" cmd = cmd.replace('no no ', '') return cmd def get_normalized_ipv6_line(line): """ Return a normalized IPv6 line as produced by frr, with all letters in lower case and trailing and leading zeros removed, and only the network portion present if the IPv6 word is a network """ norm_line = "" words = line.split(' ') for word in words: if ":" in word: norm_word = None if "/" in word: try: if 'ipaddress' not in sys.modules: v6word = IPNetwork(word) norm_word = '%s/%s' % (v6word.network, v6word.prefixlen) else: v6word = ip_network(word, strict=False) norm_word = '%s/%s' % (str(v6word.network_address), v6word.prefixlen) except ValueError: pass if not norm_word: try: norm_word = '%s' % IPv6Address(word) except ValueError: norm_word = word else: norm_word = word norm_line = norm_line + " " + norm_word return norm_line.strip() def line_exist(lines, target_ctx_keys, target_line, exact_match=True): for (ctx_keys, line) in lines: if ctx_keys == target_ctx_keys: if exact_match: if line == target_line: return True else: if line.startswith(target_line): return True return False def ignore_delete_re_add_lines(lines_to_add, lines_to_del): # Quite possibly the most confusing (while accurate) variable names in history lines_to_add_to_del = [] lines_to_del_to_del = [] for (ctx_keys, line) in lines_to_del: deleted = False if ctx_keys[0].startswith('router bgp') and line: if line.startswith('neighbor '): ''' BGP changed how it displays swpX peers that are part of peer-group. Older versions of frr would display these on separate lines: neighbor swp1 interface neighbor swp1 peer-group FOO but today we display via a single line neighbor swp1 interface peer-group FOO This change confuses frr-reload.py so check to see if we are deleting neighbor swp1 interface peer-group FOO and adding neighbor swp1 interface neighbor swp1 peer-group FOO If so then chop the del line and the corresponding add lines ''' re_swpx_int_peergroup = re.search('neighbor (\S+) interface peer-group (\S+)', line) re_swpx_int_v6only_peergroup = re.search('neighbor (\S+) interface v6only peer-group (\S+)', line) if re_swpx_int_peergroup or re_swpx_int_v6only_peergroup: swpx_interface = None swpx_peergroup = None if re_swpx_int_peergroup: swpx = re_swpx_int_peergroup.group(1) peergroup = re_swpx_int_peergroup.group(2) swpx_interface = "neighbor %s interface" % swpx elif re_swpx_int_v6only_peergroup: swpx = re_swpx_int_v6only_peergroup.group(1) peergroup = re_swpx_int_v6only_peergroup.group(2) swpx_interface = "neighbor %s interface v6only" % swpx swpx_peergroup = "neighbor %s peer-group %s" % (swpx, peergroup) found_add_swpx_interface = line_exist(lines_to_add, ctx_keys, swpx_interface) found_add_swpx_peergroup = line_exist(lines_to_add, ctx_keys, swpx_peergroup) tmp_ctx_keys = tuple(list(ctx_keys)) if not found_add_swpx_peergroup: tmp_ctx_keys = list(ctx_keys) tmp_ctx_keys.append('address-family ipv4 unicast') tmp_ctx_keys = tuple(tmp_ctx_keys) found_add_swpx_peergroup = line_exist(lines_to_add, tmp_ctx_keys, swpx_peergroup) if not found_add_swpx_peergroup: tmp_ctx_keys = list(ctx_keys) tmp_ctx_keys.append('address-family ipv6 unicast') tmp_ctx_keys = tuple(tmp_ctx_keys) found_add_swpx_peergroup = line_exist(lines_to_add, tmp_ctx_keys, swpx_peergroup) if found_add_swpx_interface and found_add_swpx_peergroup: deleted = True lines_to_del_to_del.append((ctx_keys, line)) lines_to_add_to_del.append((ctx_keys, swpx_interface)) lines_to_add_to_del.append((tmp_ctx_keys, swpx_peergroup)) ''' Changing the bfd timers on neighbors is allowed without doing a delete/add process. Since doing a "no neighbor blah bfd ..." will cause the peer to bounce unnecessarily, just skip the delete and just do the add. ''' re_nbr_bfd_timers = re.search(r'neighbor (\S+) bfd (\S+) (\S+) (\S+)', line) if re_nbr_bfd_timers: nbr = re_nbr_bfd_timers.group(1) bfd_nbr = "neighbor %s" % nbr for (ctx_keys, add_line) in lines_to_add: re_add_nbr_bfd_timers = re.search(r'neighbor (\S+) bfd (\S+) (\S+) (\S+)', add_line) if re_add_nbr_bfd_timers: found_add_bfd_nbr = line_exist(lines_to_add, ctx_keys, bfd_nbr, False) if found_add_bfd_nbr: lines_to_del_to_del.append((ctx_keys, line)) ''' We changed how we display the neighbor interface command. Older versions of frr would display the following: neighbor swp1 interface neighbor swp1 remote-as external neighbor swp1 capability extended-nexthop but today we display via a single line neighbor swp1 interface remote-as external and capability extended-nexthop is no longer needed because we automatically enable it when the neighbor is of type interface. This change confuses frr-reload.py so check to see if we are deleting neighbor swp1 interface remote-as (external|internal|ASNUM) and adding neighbor swp1 interface neighbor swp1 remote-as (external|internal|ASNUM) neighbor swp1 capability extended-nexthop If so then chop the del line and the corresponding add lines ''' re_swpx_int_remoteas = re.search('neighbor (\S+) interface remote-as (\S+)', line) re_swpx_int_v6only_remoteas = re.search('neighbor (\S+) interface v6only remote-as (\S+)', line) if re_swpx_int_remoteas or re_swpx_int_v6only_remoteas: swpx_interface = None swpx_remoteas = None if re_swpx_int_remoteas: swpx = re_swpx_int_remoteas.group(1) remoteas = re_swpx_int_remoteas.group(2) swpx_interface = "neighbor %s interface" % swpx elif re_swpx_int_v6only_remoteas: swpx = re_swpx_int_v6only_remoteas.group(1) remoteas = re_swpx_int_v6only_remoteas.group(2) swpx_interface = "neighbor %s interface v6only" % swpx swpx_remoteas = "neighbor %s remote-as %s" % (swpx, remoteas) found_add_swpx_interface = line_exist(lines_to_add, ctx_keys, swpx_interface) found_add_swpx_remoteas = line_exist(lines_to_add, ctx_keys, swpx_remoteas) tmp_ctx_keys = tuple(list(ctx_keys)) if found_add_swpx_interface and found_add_swpx_remoteas: deleted = True lines_to_del_to_del.append((ctx_keys, line)) lines_to_add_to_del.append((ctx_keys, swpx_interface)) lines_to_add_to_del.append((tmp_ctx_keys, swpx_remoteas)) ''' We made the 'bgp bestpath as-path multipath-relax' command automatically assume 'no-as-set' since the lack of this option caused weird routing problems. When the running config is shown in releases with this change, the no-as-set keyword is not shown as it is the default. This causes frr-reload to unnecessarily unapply this option only to apply it back again, causing unnecessary session resets. ''' if 'multipath-relax' in line: re_asrelax_new = re.search('^bgp\s+bestpath\s+as-path\s+multipath-relax$', line) old_asrelax_cmd = 'bgp bestpath as-path multipath-relax no-as-set' found_asrelax_old = line_exist(lines_to_add, ctx_keys, old_asrelax_cmd) if re_asrelax_new and found_asrelax_old: deleted = True lines_to_del_to_del.append((ctx_keys, line)) lines_to_add_to_del.append((ctx_keys, old_asrelax_cmd)) ''' If we are modifying the BGP table-map we need to avoid a del/add and instead modify the table-map in place via an add. This is needed to avoid installing all routes in the RIB the second the 'no table-map' is issued. ''' if line.startswith('table-map'): found_table_map = line_exist(lines_to_add, ctx_keys, 'table-map', False) if found_table_map: lines_to_del_to_del.append((ctx_keys, line)) ''' More old-to-new config handling. ip import-table no longer accepts distance, but we honor the old syntax. But 'show running' shows only the new syntax. This causes an unnecessary 'no import-table' followed by the same old 'ip import-table' which causes perturbations in announced routes leading to traffic blackholes. Fix this issue. ''' re_importtbl = re.search('^ip\s+import-table\s+(\d+)$', ctx_keys[0]) if re_importtbl: table_num = re_importtbl.group(1) for ctx in lines_to_add: if ctx[0][0].startswith('ip import-table %s distance' % table_num): lines_to_del_to_del.append((('ip import-table %s' % table_num,), None)) lines_to_add_to_del.append((ctx[0], None)) ''' ip/ipv6 prefix-list can be specified without a seq number. However, the running config always adds 'seq x', where x is a number incremented by 5 for every element, to the prefix list. So, ignore such lines as well. Sample prefix-list lines: ip prefix-list PR-TABLE-2 seq 5 permit 20.8.2.0/24 le 32 ip prefix-list PR-TABLE-2 seq 10 permit 20.8.2.0/24 le 32 ipv6 prefix-list vrfdev6-12 permit 2000:9:2::/64 gt 64 ''' re_ip_pfxlst = re.search('^(ip|ipv6)(\s+prefix-list\s+)(\S+\s+)(seq \d+\s+)(permit|deny)(.*)$', ctx_keys[0]) if re_ip_pfxlst: tmpline = (re_ip_pfxlst.group(1) + re_ip_pfxlst.group(2) + re_ip_pfxlst.group(3) + re_ip_pfxlst.group(5) + re_ip_pfxlst.group(6)) for ctx in lines_to_add: if ctx[0][0] == tmpline: lines_to_del_to_del.append((ctx_keys, None)) lines_to_add_to_del.append(((tmpline,), None)) if (len(ctx_keys) == 3 and ctx_keys[0].startswith('router bgp') and ctx_keys[1] == 'address-family l2vpn evpn' and ctx_keys[2].startswith('vni')): re_route_target = re.search('^route-target import (.*)$', line) if line is not None else False if re_route_target: rt = re_route_target.group(1).strip() route_target_import_line = line route_target_export_line = "route-target export %s" % rt route_target_both_line = "route-target both %s" % rt found_route_target_export_line = line_exist(lines_to_del, ctx_keys, route_target_export_line) found_route_target_both_line = line_exist(lines_to_add, ctx_keys, route_target_both_line) ''' If the running configs has route-target import 1:1 route-target export 1:1 and the config we are reloading against has route-target both 1:1 then we can ignore deleting the import/export and ignore adding the 'both' ''' if found_route_target_export_line and found_route_target_both_line: lines_to_del_to_del.append((ctx_keys, route_target_import_line)) lines_to_del_to_del.append((ctx_keys, route_target_export_line)) lines_to_add_to_del.append((ctx_keys, route_target_both_line)) if not deleted: found_add_line = line_exist(lines_to_add, ctx_keys, line) if found_add_line: lines_to_del_to_del.append((ctx_keys, line)) lines_to_add_to_del.append((ctx_keys, line)) else: ''' We have commands that used to be displayed in the global part of 'router bgp' that are now displayed under 'address-family ipv4 unicast' # old way router bgp 64900 neighbor ISL advertisement-interval 0 vs. # new way router bgp 64900 address-family ipv4 unicast neighbor ISL advertisement-interval 0 Look to see if we are deleting it in one format just to add it back in the other ''' if ctx_keys[0].startswith('router bgp') and len(ctx_keys) > 1 and ctx_keys[1] == 'address-family ipv4 unicast': tmp_ctx_keys = list(ctx_keys)[:-1] tmp_ctx_keys = tuple(tmp_ctx_keys) found_add_line = line_exist(lines_to_add, tmp_ctx_keys, line) if found_add_line: lines_to_del_to_del.append((ctx_keys, line)) lines_to_add_to_del.append((tmp_ctx_keys, line)) for (ctx_keys, line) in lines_to_del_to_del: lines_to_del.remove((ctx_keys, line)) for (ctx_keys, line) in lines_to_add_to_del: lines_to_add.remove((ctx_keys, line)) return (lines_to_add, lines_to_del) def ignore_unconfigurable_lines(lines_to_add, lines_to_del): """ There are certain commands that cannot be removed. Remove those commands from lines_to_del. """ lines_to_del_to_del = [] for (ctx_keys, line) in lines_to_del: if (ctx_keys[0].startswith('frr version') or ctx_keys[0].startswith('frr defaults') or ctx_keys[0].startswith('password') or ctx_keys[0].startswith('line vty') or # This is technically "no"able but if we did so frr-reload would # stop working so do not let the user shoot themselves in the foot # by removing this. ctx_keys[0].startswith('service integrated-vtysh-config')): log.info("(%s, %s) cannot be removed" % (pformat(ctx_keys), line)) lines_to_del_to_del.append((ctx_keys, line)) for (ctx_keys, line) in lines_to_del_to_del: lines_to_del.remove((ctx_keys, line)) return (lines_to_add, lines_to_del) def compare_context_objects(newconf, running): """ Create a context diff for the two specified contexts """ # Compare the two Config objects to find the lines that we need to add/del lines_to_add = [] lines_to_del = [] delete_bgpd = False # Find contexts that are in newconf but not in running # Find contexts that are in running but not in newconf for (running_ctx_keys, running_ctx) in iteritems(running.contexts): if running_ctx_keys not in newconf.contexts: # We check that the len is 1 here so that we only look at ('router bgp 10') # and not ('router bgp 10', 'address-family ipv4 unicast'). The # latter could cause a false delete_bgpd positive if ipv4 unicast is in # running but not in newconf. if "router bgp" in running_ctx_keys[0] and len(running_ctx_keys) == 1: delete_bgpd = True lines_to_del.append((running_ctx_keys, None)) # We cannot do 'no interface' or 'no vrf' in FRR, and so deal with it elif running_ctx_keys[0].startswith('interface') or running_ctx_keys[0].startswith('vrf'): for line in running_ctx.lines: lines_to_del.append((running_ctx_keys, line)) # If this is an address-family under 'router bgp' and we are already deleting the # entire 'router bgp' context then ignore this sub-context elif "router bgp" in running_ctx_keys[0] and len(running_ctx_keys) > 1 and delete_bgpd: continue # Delete an entire vni sub-context under "address-family l2vpn evpn" elif ("router bgp" in running_ctx_keys[0] and len(running_ctx_keys) > 2 and running_ctx_keys[1].startswith('address-family l2vpn evpn') and running_ctx_keys[2].startswith('vni ')): lines_to_del.append((running_ctx_keys, None)) elif ("router bgp" in running_ctx_keys[0] and len(running_ctx_keys) > 1 and running_ctx_keys[1].startswith('address-family')): # There's no 'no address-family' support and so we have to # delete each line individually again for line in running_ctx.lines: lines_to_del.append((running_ctx_keys, line)) # Non-global context elif running_ctx_keys and not any("address-family" in key for key in running_ctx_keys): lines_to_del.append((running_ctx_keys, None)) elif running_ctx_keys and not any("vni" in key for key in running_ctx_keys): lines_to_del.append((running_ctx_keys, None)) # Global context else: for line in running_ctx.lines: lines_to_del.append((running_ctx_keys, line)) # Find the lines within each context to add # Find the lines within each context to del for (newconf_ctx_keys, newconf_ctx) in iteritems(newconf.contexts): if newconf_ctx_keys in running.contexts: running_ctx = running.contexts[newconf_ctx_keys] for line in newconf_ctx.lines: if line not in running_ctx.dlines: lines_to_add.append((newconf_ctx_keys, line)) for line in running_ctx.lines: if line not in newconf_ctx.dlines: lines_to_del.append((newconf_ctx_keys, line)) for (newconf_ctx_keys, newconf_ctx) in iteritems(newconf.contexts): if newconf_ctx_keys not in running.contexts: lines_to_add.append((newconf_ctx_keys, None)) for line in newconf_ctx.lines: lines_to_add.append((newconf_ctx_keys, line)) (lines_to_add, lines_to_del) = ignore_delete_re_add_lines(lines_to_add, lines_to_del) (lines_to_add, lines_to_del) = ignore_unconfigurable_lines(lines_to_add, lines_to_del) return (lines_to_add, lines_to_del) def vtysh_config_available(): """ Return False if no frr daemon is running or some other vtysh session is in 'configuration terminal' mode which will prevent us from making any configuration changes. """ try: cmd = ['/usr/bin/vtysh', '-c', 'conf t'] output = subprocess.check_output(cmd, stderr=subprocess.STDOUT).strip() if 'VTY configuration is locked by other VTY' in output.decode('utf-8'): print(output) log.error("'%s' returned\n%s\n" % (' '.join(cmd), output)) return False except subprocess.CalledProcessError as e: msg = "vtysh could not connect with any frr daemons" print(msg) log.error(msg) return False return True if __name__ == '__main__': # Command line options parser = argparse.ArgumentParser(description='Dynamically apply diff in frr configs') parser.add_argument('--input', help='Read running config from file instead of "show running"') group = parser.add_mutually_exclusive_group(required=True) group.add_argument('--reload', action='store_true', help='Apply the deltas', default=False) group.add_argument('--test', action='store_true', help='Show the deltas', default=False) parser.add_argument('--debug', action='store_true', help='Enable debugs', default=False) parser.add_argument('--stdout', action='store_true', help='Log to STDOUT', default=False) parser.add_argument('filename', help='Location of new frr config file') parser.add_argument('--overwrite', action='store_true', help='Overwrite frr.conf with running config output', default=False) args = parser.parse_args() # Logging # For --test log to stdout # For --reload log to /var/log/frr/frr-reload.log if args.test or args.stdout: logging.basicConfig(level=logging.INFO, format='%(asctime)s %(levelname)5s: %(message)s') # Color the errors and warnings in red logging.addLevelName(logging.ERROR, "\033[91m %s\033[0m" % logging.getLevelName(logging.ERROR)) logging.addLevelName(logging.WARNING, "\033[91m%s\033[0m" % logging.getLevelName(logging.WARNING)) elif args.reload: if not os.path.isdir('/var/log/frr/'): os.makedirs('/var/log/frr/') logging.basicConfig(filename='/var/log/frr/frr-reload.log', level=logging.INFO, format='%(asctime)s %(levelname)5s: %(message)s') # argparse should prevent this from happening but just to be safe... else: raise Exception('Must specify --reload or --test') log = logging.getLogger(__name__) # Verify the new config file is valid if not os.path.isfile(args.filename): msg = "Filename %s does not exist" % args.filename print(msg) log.error(msg) sys.exit(1) if not os.path.getsize(args.filename): msg = "Filename %s is an empty file" % args.filename print(msg) log.error(msg) sys.exit(1) # Verify that 'service integrated-vtysh-config' is configured vtysh_filename = '/etc/frr/vtysh.conf' service_integrated_vtysh_config = True if os.path.isfile(vtysh_filename): with open(vtysh_filename, 'r') as fh: for line in fh.readlines(): line = line.strip() if line == 'no service integrated-vtysh-config': service_integrated_vtysh_config = False break if not service_integrated_vtysh_config: msg = "'service integrated-vtysh-config' is not configured, this is required for 'service frr reload'" print(msg) log.error(msg) sys.exit(1) if args.debug: log.setLevel(logging.DEBUG) log.info('Called via "%s"', str(args)) # Create a Config object from the config generated by newconf newconf = Config() newconf.load_from_file(args.filename) reload_ok = True if args.test: # Create a Config object from the running config running = Config() if args.input: running.load_from_file(args.input) else: running.load_from_show_running() (lines_to_add, lines_to_del) = compare_context_objects(newconf, running) lines_to_configure = [] if lines_to_del: print("\nLines To Delete") print("===============") for (ctx_keys, line) in lines_to_del: if line == '!': continue cmd = line_for_vtysh_file(ctx_keys, line, True) lines_to_configure.append(cmd) print(cmd) if lines_to_add: print("\nLines To Add") print("============") for (ctx_keys, line) in lines_to_add: if line == '!': continue cmd = line_for_vtysh_file(ctx_keys, line, False) lines_to_configure.append(cmd) print(cmd) elif args.reload: # We will not be able to do anything, go ahead and exit(1) if not vtysh_config_available(): sys.exit(1) log.debug('New Frr Config\n%s', newconf.get_lines()) # This looks a little odd but we have to do this twice...here is why # If the user had this running bgp config: # # router bgp 10 # neighbor 1.1.1.1 remote-as 50 # neighbor 1.1.1.1 route-map FOO out # # and this config in the newconf config file # # router bgp 10 # neighbor 1.1.1.1 remote-as 999 # neighbor 1.1.1.1 route-map FOO out # # # Then the script will do # - no neighbor 1.1.1.1 remote-as 50 # - neighbor 1.1.1.1 remote-as 999 # # The problem is the "no neighbor 1.1.1.1 remote-as 50" will also remove # the "neighbor 1.1.1.1 route-map FOO out" line...so we compare the # configs again to put this line back. # There are many keywords in FRR that can only appear one time under # a context, take "bgp router-id" for example. If the config that we are # reloading against has the following: # # router bgp 10 # bgp router-id 1.1.1.1 # bgp router-id 2.2.2.2 # # The final config needs to contain "bgp router-id 2.2.2.2". On the # first pass we will add "bgp router-id 2.2.2.2" but then on the second # pass we will see that "bgp router-id 1.1.1.1" is missing and add that # back which cancels out the "bgp router-id 2.2.2.2". The fix is for the # second pass to include all of the "adds" from the first pass. lines_to_add_first_pass = [] for x in range(2): running = Config() running.load_from_show_running() log.debug('Running Frr Config (Pass #%d)\n%s', x, running.get_lines()) (lines_to_add, lines_to_del) = compare_context_objects(newconf, running) if x == 0: lines_to_add_first_pass = lines_to_add else: lines_to_add.extend(lines_to_add_first_pass) # Only do deletes on the first pass. The reason being if we # configure a bgp neighbor via "neighbor swp1 interface" FRR # will automatically add: # # interface swp1 # ipv6 nd ra-interval 10 # no ipv6 nd suppress-ra # ! # # but those lines aren't in the config we are reloading against so # on the 2nd pass they will show up in lines_to_del. This could # apply to other scenarios as well where configuring FOO adds BAR # to the config. if lines_to_del and x == 0: for (ctx_keys, line) in lines_to_del: if line == '!': continue # 'no' commands are tricky, we can't just put them in a file and # vtysh -f that file. See the next comment for an explanation # of their quirks cmd = line_to_vtysh_conft(ctx_keys, line, True) original_cmd = cmd # Some commands in frr are picky about taking a "no" of the entire line. # OSPF is bad about this, you can't "no" the entire line, you have to "no" # only the beginning. If we hit one of these command an exception will be # thrown. Catch it and remove the last '-c', 'FOO' from cmd and try again. # # Example: # frr(config-if)# ip ospf authentication message-digest 1.1.1.1 # frr(config-if)# no ip ospf authentication message-digest 1.1.1.1 # % Unknown command. # frr(config-if)# no ip ospf authentication message-digest # % Unknown command. # frr(config-if)# no ip ospf authentication # frr(config-if)# while True: try: _ = subprocess.check_output(cmd, stderr=subprocess.STDOUT) except subprocess.CalledProcessError: # - Pull the last entry from cmd (this would be # 'no ip ospf authentication message-digest 1.1.1.1' in # our example above # - Split that last entry by whitespace and drop the last word log.info('Failed to execute %s', ' '.join(cmd)) last_arg = cmd[-1].split(' ') if len(last_arg) <= 2: log.error('"%s" we failed to remove this command', original_cmd) break new_last_arg = last_arg[0:-1] cmd[-1] = ' '.join(new_last_arg) else: log.info('Executed "%s"', ' '.join(cmd)) break if lines_to_add: lines_to_configure = [] for (ctx_keys, line) in lines_to_add: if line == '!': continue cmd = line_for_vtysh_file(ctx_keys, line, False) lines_to_configure.append(cmd) if lines_to_configure: random_string = ''.join(random.SystemRandom().choice( string.ascii_uppercase + string.digits) for _ in range(6)) filename = "/var/run/frr/reload-%s.txt" % random_string log.info("%s content\n%s" % (filename, pformat(lines_to_configure))) with open(filename, 'w') as fh: for line in lines_to_configure: fh.write(line + '\n') try: subprocess.check_output(['/usr/bin/vtysh', '-f', filename], stderr=subprocess.STDOUT) except subprocess.CalledProcessError as e: log.warning("frr-reload.py failed due to\n%s" % e.output) reload_ok = False os.unlink(filename) # Make these changes persistent if args.overwrite or args.filename != '/etc/frr/frr.conf': subprocess.call(['/usr/bin/vtysh', '-c', 'write']) if not reload_ok: sys.exit(1) frr-7.2.1/tools/frr.in0000755000000000000000000003440013610377563011501 00000000000000#!/bin/bash # ### BEGIN INIT INFO # Provides: frr # Required-Start: $local_fs $network $remote_fs $syslog # Required-Stop: $local_fs $network $remote_fs $syslog # Default-Start: 2 3 4 5 # Default-Stop: 0 1 6 # Short-Description: start and stop the Frr routing suite # Description: Frr is a routing suite for IP routing protocols like # BGP, OSPF, RIP and others. This script contols the main # daemon "frr" as well as the individual protocol daemons. ### END INIT INFO # PATH=/bin:/usr/bin:/sbin:/usr/sbin D_PATH="@CFG_SBIN@" # /usr/lib/frr C_PATH="@CFG_SYSCONF@" # /etc/frr V_PATH="@CFG_STATE@" # /var/run/frr VTYSH="@vtysh_bin@" # /usr/bin/vtysh FRR_USER="@enable_user@" # frr FRR_GROUP="@enable_group@" # frr FRR_VTY_GROUP="@enable_vty_group@" # frrvty # Local Daemon selection may be done by using /etc/frr/daemons. # See /usr/share/doc/frr/README.Debian.gz for further information. # Keep zebra first and do not list watchfrr! DAEMONS="zebra bgpd ripd ripngd ospfd ospf6d isisd babeld pimd ldpd nhrpd eigrpd sharpd pbrd staticd bfdd fabricd vrrpd" MAX_INSTANCES=5 RELOAD_SCRIPT="$D_PATH/frr-reload.py" if [ -e /lib/lsb/init-functions ]; then . /lib/lsb/init-functions fi if [ -f $D_PATH/ssd ]; then SSD=$D_PATH/ssd else SSD=`which start-stop-daemon` fi # Print the name of the pidfile. pidfile() { echo "$V_PATH/$1.pid" } # Print the name of the vtysh. vtyfile() { echo "$V_PATH/$1.vty" } chownfrr() { test -n "$FRR_USER" && chown "$FRR_USER" "$1" test -n "$FRR_GROUP" && chgrp "$FRR_GROUP" "$1" } # Check if daemon is started by using the pidfile. started() { [ ! -e `pidfile $1` ] && return 3 if [ -n "$2" ] && [ "$2" == "log" ]; then status_of_proc -p `pidfile $1` $1 $1 && return 0 || return $? else kill -0 `cat \`pidfile $1\`` 2> /dev/null || return 1 return 0 fi } # Loads the config via vtysh -b if configured to do so. vtysh_b () { # Rember, that all variables have been incremented by 1 in convert_daemon_prios() if [ "$vtysh_enable" = 2 -a -f $C_PATH/frr.conf ]; then $VTYSH -b -n fi } # Check if the daemon is activated and if its executable and config files # are in place. # params: daemon name # returns: 0=ok, 1=error check_daemon() { if [ $1 != "watchfrr" -a $1 != "vtysh_enable" ]; then # check for daemon binary if [ ! -x "$D_PATH/$1" ]; then return 1; fi fi # If the integrated config file is used the others are not checked. if [ -r "$C_PATH/frr.conf" ]; then return 0 fi # vtysh_enable has no config file nor binary so skip check. # (Not sure why vtysh_enable is in this list but does not hurt) if [ $1 != "watchfrr" -a $1 != "vtysh_enable" ]; then # check for config file if [ -n "$2" ]; then if [ ! -r "$C_PATH/$1-$2.conf" ]; then touch "$C_PATH/$1-$2.conf" chownfrr "$C_PATH/$1-$2.conf" fi elif [ ! -r "$C_PATH/$1.conf" ]; then touch "$C_PATH/$1.conf" chownfrr "$C_PATH/$1.conf" fi fi return 0 } # Starts the server if it's not alrady running according to the pid file. # The Frr daemons creates the pidfile when starting. start() { local dmn inst dmn="$1" inst="$2" ulimit -n $MAX_FDS > /dev/null 2> /dev/null if [ "$dmn" = "watchfrr" ]; then # We may need to restart watchfrr if new daemons are added and/or # removed if started "$dmn" ; then stop watchfrr else # Echo only once. watchfrr is printed in the stop above echo -n " $dmn" fi eval "set - $watchfrr_options" ${SSD} \ --start \ --pidfile=`pidfile $dmn` \ --exec "$D_PATH/$dmn" \ -- \ "$@" elif [ -n "$inst" ]; then echo -n " $dmn-$inst" if ! check_daemon $dmn $inst ; then echo -n " (binary does not exist)" return; fi ${SSD} \ --start \ --pidfile=`pidfile $dmn-$inst` \ --exec "$D_PATH/$dmn" \ -- \ `eval echo "$""$dmn""_options"` -n "$inst" else if ! check_daemon $dmn; then echo -n " (binary does not exist)" return; fi if [ "$valgrind_enable" = "yes" ]; then ${SSD} \ --start \ --pidfile=`pidfile $dmn` \ --exec "$valgrind" \ -- --trace-children=no --leak-check=full --log-file=/var/log/frr/$dmn-valgrind.log $D_PATH/$dmn \ `eval echo "$""$dmn""_options"` else ${SSD} \ --start \ --pidfile=`pidfile $dmn` \ --exec "$D_PATH/$dmn" \ -- \ `eval echo "$""$dmn""_options"` fi fi # Start the staticd automatically if [ "$dmn" = "zebra" ]; then echo -n "starting staticd since zebra is running" if ! check_daemon staticd ; then echo -n " (binary does not exist)" return; fi ${SSD} \ --start \ --pidfile=`pidfile staticd` \ --exec "$D_PATH/staticd" \ -- \ `eval echo "$"staticd"_options"` fi } # Stop the daemon given in the parameter, printing its name to the terminal. stop() { local inst if [ -n "$2" ]; then inst="$1-$2" else inst="$1" fi if ! started "$inst" ; then echo -n " ($inst)" return 0 else PIDFILE=`pidfile $inst` PID=`cat $PIDFILE 2>/dev/null` kill -2 $PID 2>/dev/null # # Now we have to wait until $DAEMON has _really_ stopped. # if test -n "$PID" && kill -0 $PID 2>/dev/null; then cnt=0 while kill -0 $PID 2>/dev/null; do cnt=`expr $cnt + 1` if [ $cnt -gt 60 ]; then # Waited 120 secs now, fail. echo -n "Failed.. " break fi sleep 2 done fi rm -f `pidfile $inst` rm -f `vtyfile $inst` if [ "$1" = "zebra" ]; then echo -n "Stopping staticd since zebra is running" stop staticd fi fi } # Converts values from /etc/frr/daemons to all-numeric values. convert_daemon_prios() { for name in $DAEMONS zebra vtysh_enable watchfrr_enable; do # First, assign the value set by the user to $value eval value=\${${name}:0:3} # Daemon not activated or entry missing? if [ "$value" = "no" -o "$value" = "" ]; then value=0; fi # These strings parsed for backwards compatibility. if [ "$value" = "yes" -o "$value" = "true" ]; then value=1; fi # Zebra is threatened special. It must be between 0=off and the first # user assigned value "1" so we increase all other enabled daemons' values. if [ "$name" != "zebra" -a "$value" -gt 0 ]; then value=`expr "$value" + 1`; fi # If e.g. name is zebra then we set "zebra=yes". eval $name=$value done } # Starts watchfrr for all wanted daemons. start_watchfrr() { local daemon_name local daemon_prio local found_one local daemon_inst # Start the monitor daemon only if desired. if [ 0 -eq "$watchfrr_enable" ]; then return fi # Check variable type if declare -p watchfrr_options | grep -q '^declare \-a'; then # old array support watchfrr_options="${watchfrr_options[@]}" fi # Which daemons have been started? found_one=0 for daemon_name in $DAEMONS; do eval daemon_prio=\$$daemon_name if [ "$daemon_prio" -gt 0 ]; then eval "daemon_inst=\${${daemon_name}_instances//,/ }" if [ -n "$daemon_inst" ]; then for inst in ${daemon_inst}; do eval "inst_disable=\${${daemon_name}_${inst}}" if [ -z ${inst_disable} ] || [ ${inst_disable} != 0 ]; then if check_daemon $daemon_name $inst; then watchfrr_options="$watchfrr_options ${daemon_name}-${inst}" fi fi done else if check_daemon $daemon_name; then watchfrr_options="$watchfrr_options $daemon_name" fi fi found_one=1 fi done # Start if at least one daemon is activated. if [ $found_one -eq 1 ]; then start watchfrr echo "." fi } # Stopps watchfrr. stop_watchfrr() { echo -n "Stopping Frr monitor daemon:" stop watchfrr echo "." } # Stops all daemons that have a lower level of priority than the given. # (technically if daemon_prio >= wanted_prio) stop_prio() { local wanted_prio local daemon_prio local daemon_list local daemon_inst local inst if [ -n "$2" ] && [[ "$2" =~ (.*)-(.*) ]]; then daemon=${BASH_REMATCH[1]} inst=${BASH_REMATCH[2]} else daemon="$2" fi wanted_prio=$1 daemon_list=${daemon:-$DAEMONS} echo -n "Stopping Frr daemons (prio:$wanted_prio):" for prio_i in `seq 10 -1 $wanted_prio`; do for daemon_name in $daemon_list; do eval daemon_prio=\${${daemon_name}:0:3} daemon_inst="" if [ $daemon_prio -eq $prio_i ]; then eval "daemon_inst=\${${daemon_name}_instances//,/ }" if [ -n "$daemon_inst" ]; then for i in ${daemon_inst}; do if [ -n "$inst" ] && [ "$i" == "$inst" ]; then stop "$daemon_name" "$inst" elif [ x"$inst" == x ]; then stop "$daemon_name" "$i" fi done else stop "$daemon_name" fi fi done done echo "." if [ -z "$inst" ]; then # Now stop other daemons that're prowling, coz the daemons file changed echo -n "Stopping other frr daemons" if [ -n "$daemon" ]; then eval "file_list_suffix="$V_PATH"/"$daemon*"" else eval "file_list_suffix="$V_PATH/*"" fi for pidfile in $file_list_suffix.pid; do PID=`cat $pidfile 2>/dev/null` ${SSD} --stop --quiet --oknodo --pidfile "$pidfile" echo -n "." rm -rf "$pidfile" done echo "." echo -n "Removing remaining .vty files" for vtyfile in $file_list_suffix.vty; do rm -rf "$vtyfile" done echo "." fi } # Starts all daemons that have a higher level of priority than the given. # (technically if daemon_prio <= wanted_prio) start_prio() { local wanted_prio local daemon_prio local daemon_list local daemon_name local daemon_inst local inst if [ -n "$2" ] && [[ "$2" =~ (.*)-(.*) ]]; then daemon=${BASH_REMATCH[1]} inst=${BASH_REMATCH[2]} else daemon="$2" fi wanted_prio=$1 daemon_list=${daemon:-$DAEMONS} for prio_i in `seq 1 $wanted_prio`; do for daemon_name in $daemon_list; do eval daemon_prio=\$${daemon_name} daemon_inst="" if [ $daemon_prio -eq $prio_i ]; then eval "daemon_inst=\${${daemon_name}_instances//,/ }" if [ -n "$daemon_inst" ]; then if [ `echo "$daemon_inst" | wc -w` -gt ${MAX_INSTANCES} ]; then echo "Max instances supported is ${MAX_INSTANCES}. Aborting" exit 1 fi # Check if we're starting again by switching from single instance # to MI version if started "$daemon_name"; then PIDFILE=`pidfile $daemon_name` ${SSD} \ --stop --quiet --oknodo \ --pidfile "$PIDFILE" \ --exec "$D_PATH/$daemon_name" rm -f `pidfile $1` rm -f `vtyfile $1` fi for i in ${daemon_inst}; do if [ -n "$inst" ] && [ "$i" == "$inst" ]; then start "$daemon_name" "$inst" elif [ x"$inst" == x ]; then start "$daemon_name" "$i" fi done else # Check if we're starting again by switching from # single instance to MI version eval "file_list_suffix="$V_PATH"/"$daemon_name-*"" for pidfile in $file_list_suffix.pid; do ${SSD} --stop --quiet --oknodo --pidfile "$pidfile" rm -rf "$pidfile" done for vtyfile in $file_list_suffix.vty; do rm -rf "$vtyfile" done start "$daemon_name" fi fi done done } check_status() { local daemon_name local daemon_prio local daemon_inst local failed_status=0 if [ -n "$1" ] && [[ "$1" =~ (.*)-(.*) ]]; then daemon=${BASH_REMATCH[1]} inst=${BASH_REMATCH[2]} else daemon="$1" fi daemon_list=${daemon:-$DAEMONS} # Which daemons have been started? for daemon_name in $daemon_list; do eval daemon_prio=\$$daemon_name if [ "$daemon_prio" -gt 0 ]; then eval "daemon_inst=\${${daemon_name}_instances//,/ }" if [ -n "$daemon_inst" ]; then for i in ${daemon_inst}; do if [ -n "$inst" -a "$inst" = "$i" ]; then started "$1" "log" || failed_status=$? elif [ -z "$inst" ]; then started "$daemon_name-$i" "log" || failed_status=$? fi done else started "$daemon_name" "log" || failed_status=$? fi fi done # All daemons that need to have been started are up and running return $failed_status } ######################################################### # Main program # ######################################################### # Config broken but script must exit silently. [ ! -r "$C_PATH/daemons" ] && exit 0 # Load configuration . "$C_PATH/daemons" if [ -e "$C_PATH/daemons.conf" ]; then . "$C_PATH/daemons.conf" fi # Read configuration variable file if it is present [ -r /etc/default/frr ] && . /etc/default/frr MAX_INSTANCES=${MAX_INSTANCES:=5} # Set priority of un-startable daemons to 'no' and substitute 'yes' to '0' convert_daemon_prios if [ ! -d $V_PATH ]; then echo "Creating $V_PATH" mkdir -p $V_PATH chownfrr $V_PATH chmod 755 /$V_PATH fi if [ -n "$3" ] && [ "$3" != "all" ]; then dmn="$2"-"$3" elif [ -n "$2" ] && [ "$2" != "all" ]; then dmn="$2" fi case "$1" in start) # Try to load this necessary (at least for 2.6) module. if [ -d /lib/modules/`uname -r` ] ; then echo "Loading capability module if not yet done." set +e; LC_ALL=C modprobe -a capability 2>&1 | egrep -v "(not found|Can't locate)"; set -e fi # Start all daemons cd $C_PATH/ if [ "$2" != "watchfrr" ]; then start_prio 10 $dmn fi start_watchfrr vtysh_b ;; 1|2|3|4|5|6|7|8|9|10) # Stop/start daemons for the appropriate priority level stop_prio $1 start_prio $1 vtysh_b ;; stop|0) # Stop all daemons at level '0' or 'stop' stop_watchfrr if [ "$dmn" != "watchfrr" ]; then [ -n "${dmn}" ] && eval "${dmn/-/_}=0" stop_prio 0 $dmn fi if [ [ -n "$dmn" ] && [ "$dmn" != "zebra" ] ]; then [ -n "$dmn" ] && eval "${dmn/-/_}=0" start_watchfrr fi ;; reload) # Just apply the commands that have changed, no restart necessary if [ ! -x "$RELOAD_SCRIPT" ]; then echo "Please install frr-pythontools package. Required for reload" exit 0 fi NEW_CONFIG_FILE="${2:-$C_PATH/frr.conf}" [ ! -r $NEW_CONFIG_FILE ] && echo "Unable to read new configuration file $NEW_CONFIG_FILE" && exit 1 echo "Applying only incremental changes to running configuration from frr.conf" "$RELOAD_SCRIPT" --reload $C_PATH/frr.conf exit $? ;; status) check_status $dmn exit $? ;; restart|force-reload) $0 stop $dmn sleep 1 $0 start $dmn ;; *) echo "Usage: /etc/init.d/frr {start|stop|status|reload|restart|force-reload|} [daemon]" echo " E.g. '/etc/init.d/frr 5' would start all daemons with a prio 1-5." echo " reload applies only modifications from the running config to all daemons." echo " reload neither restarts starts any daemon nor starts any new ones." echo " Read /usr/share/doc/frr/README.Debian for details." exit 1 ;; esac echo "Exiting from the script" exit 0 frr-7.2.1/tools/frr.service0000644000000000000000000000105113610377563012524 00000000000000[Unit] Description=FRRouting Documentation=https://frrouting.readthedocs.io/en/latest/setup.html Wants=network.target After=network-pre.target systemd-sysctl.service Before=network.target OnFailure=heartbeat-failed@%n.service [Service] Nice=-5 Type=forking NotifyAccess=all StartLimitInterval=3m StartLimitBurst=3 TimeoutSec=2m WatchdogSec=60s RestartSec=5 Restart=on-abnormal LimitNOFILE=1024 ExecStart=/usr/lib/frr/frrinit.sh start ExecStop=/usr/lib/frr/frrinit.sh stop ExecReload=/usr/lib/frr/frrinit.sh reload [Install] WantedBy=multi-user.target frr-7.2.1/tools/frrcommon.sh.in0000644000000000000000000001616613610377563013331 00000000000000#!/bin/bash # # This is a "library" of sorts for use by the other FRR shell scripts. It # has most of the daemon start/stop logic, but expects the following shell # functions/commands to be provided by the "calling" script: # # log_success_msg # log_warning_msg # log_failure_msg # # (coincidentally, these are LSB standard functions.) # # Sourcing this file in a shell script will load FRR config variables but # not perform any action. Note there is an "exit 1" if the main config # file does not exist. # # This script should be installed in @CFG_SBIN@/frrcommon.sh PATH=/bin:/usr/bin:/sbin:/usr/sbin D_PATH="@CFG_SBIN@" # /usr/lib/frr C_PATH="@CFG_SYSCONF@" # /etc/frr V_PATH="@CFG_STATE@" # /var/run/frr VTYSH="@vtysh_bin@" # /usr/bin/vtysh FRR_USER="@enable_user@" # frr FRR_GROUP="@enable_group@" # frr FRR_VTY_GROUP="@enable_vty_group@" # frrvty # ORDER MATTERS FOR $DAEMONS! # - keep zebra first # - watchfrr does NOT belong in this list DAEMONS="zebra bgpd ripd ripngd ospfd ospf6d isisd babeld pimd ldpd nhrpd eigrpd sharpd pbrd staticd bfdd fabricd vrrpd" RELOAD_SCRIPT="$D_PATH/frr-reload.py" # # general helpers # debug() { [ -n "$watchfrr_debug" ] || return 0 printf '%s %s(%s):' "`date +%Y-%m-%dT%H:%M:%S.%N`" "$0" $$ >&2 # this is to show how arguments are split regarding whitespace & co. # (e.g. for use with `debug "message" "$@"`) while [ $# -gt 0 ]; do printf ' "%s"' "$1" >&2 shift done printf '\n' >&2 } chownfrr() { [ -n "$FRR_USER" ] && chown "$FRR_USER" "$1" [ -n "$FRR_GROUP" ] && chgrp "$FRR_GROUP" "$1" } vtysh_b () { [ "$1" = "watchfrr" ] && return 0 [ -r "$C_PATH/frr.conf" ] || return 0 if [ -n "$1" ]; then "$VTYSH" -b -n -d "$1" else "$VTYSH" -b -n fi } daemon_inst() { # note this sets global variables ($dmninst, $daemon, $inst) dmninst="$1" daemon="${dmninst%-*}" inst="" [ "$daemon" != "$dmninst" ] && inst="${dmninst#*-}" } daemon_list() { # note $1 and $2 specify names for global variables to be set local enabled disabled evar dvar enabled="" disabled="" evar="$1" dvar="$2" for daemon in $DAEMONS; do eval cfg=\$$daemon eval inst=\$${daemon}_instances [ "$daemon" = zebra -o "$daemon" = staticd ] && cfg=yes if [ -n "$cfg" -a "$cfg" != "no" -a "$cfg" != "0" ]; then if ! daemon_prep "$daemon" "$inst"; then continue fi debug "$daemon enabled" enabled="$enabled $daemon" if [ -n "$inst" ]; then debug "$daemon multi-instance $inst" oldifs="${IFS}" IFS="${IFS}," for i in $inst; do enabled="$enabled $daemon-$i" done IFS="${oldifs}" fi else debug "$daemon disabled" disabled="$disabled $daemon" fi done enabled="${enabled# }" disabled="${disabled# }" [ -z "$evar" ] && echo "$enabled" [ -n "$evar" ] && eval $evar="\"$enabled\"" [ -n "$dvar" ] && eval $dvar="\"$disabled\"" } # # individual daemon management # daemon_prep() { local daemon inst cfg daemon="$1" inst="$2" [ "$daemon" = "watchfrr" ] && return 0 [ -x "$D_PATH/$daemon" ] || { log_failure_msg "cannot start $daemon${inst:+ (instance $inst)}: daemon binary not installed" return 1 } [ -r "$C_PATH/frr.conf" ] && return 0 cfg="$C_PATH/$daemon${inst:+-$inst}.conf" if [ ! -r "$cfg" ]; then touch "$cfg" chownfrr "$cfg" fi return 0 } daemon_start() { local dmninst daemon inst args instopt wrap bin daemon_inst "$1" ulimit -n $MAX_FDS > /dev/null 2> /dev/null daemon_prep "$daemon" "$inst" || return 1 if test ! -d "$V_PATH"; then mkdir -p "$V_PATH" chown frr "$V_PATH" fi eval wrap="\$${daemon}_wrap" bin="$D_PATH/$daemon" instopt="${inst:+-n $inst}" eval args="\$${daemon}_options" if eval "$all_wrap $wrap $bin -d $instopt $args"; then log_success_msg "Started $dmninst" vtysh_b "$daemon" else log_failure_msg "Failed to start $dmninst!" fi } daemon_stop() { local dmninst daemon inst pidfile vtyfile pid cnt fail daemon_inst "$1" pidfile="$V_PATH/$daemon${inst:+-$inst}.pid" vtyfile="$V_PATH/$daemon${inst:+-$inst}.vty" [ -r "$pidfile" ] || fail="pid file not found" [ -z "$fail" ] && pid="`cat \"$pidfile\"`" [ -z "$fail" -a -z "$pid" ] && fail="pid file is empty" [ -n "$fail" ] || kill -0 "$pid" 2>/dev/null || fail="pid $pid not running" if [ -n "$fail" ]; then log_failure_msg "Cannot stop $dmninst: $fail" return 1 fi debug "kill -2 $pid" kill -2 "$pid" cnt=1200 while kill -0 "$pid" 2>/dev/null; do sleep .1 [ $(( cnt -= 1 )) -gt 0 ] || break done if kill -0 "$pid" 2>/dev/null; then log_failure_msg "Failed to stop $dmninst, pid $pid still running" still_running=1 return 1 else log_success_msg "Stopped $dmninst" rm -f "$pidfile" return 0 fi } daemon_status() { local dmninst daemon inst pidfile pid fail daemon_inst "$1" pidfile="$V_PATH/$daemon${inst:+-$inst}.pid" [ -r "$pidfile" ] || return 3 pid="`cat \"$pidfile\"`" [ -z "$pid" ] && return 1 kill -0 "$pid" 2>/dev/null || return 1 return 0 } print_status() { daemon_status "$1" rv=$? if [ "$rv" -eq 0 ]; then log_success_msg "Status of $1: running" else log_failure_msg "Status of $1: FAILED" fi return $rv } # # all-daemon commands # all_start() { daemon_list daemons for dmninst in $daemons; do daemon_start "$dmninst" done } all_stop() { local pids reversed daemon_list daemons disabled [ "$1" = "--reallyall" ] && daemons="$daemons $disabled" reversed="" for dmninst in $daemons; do reversed="$dmninst $reversed" done for dmninst in $reversed; do daemon_stop "$dmninst" & pids="$pids $!" done for pid in $pids; do wait $pid done } all_status() { local fail daemon_list daemons fail=0 for dmninst in $daemons; do print_status "$dmninst" || fail=1 done return $fail } # # config sourcing # load_old_config() { oldcfg="$1" [ -r "$oldcfg" ] || return 0 [ -s "$oldcfg" ] || return 0 grep -v '^[[:blank:]]*\(#\|$\)' "$oldcfg" > /dev/null || return 0 log_warning_msg "Reading deprecated $oldcfg. Please move its settings to $C_PATH/daemons and remove it." # save off settings from daemons for the OR below for dmn in $DAEMONS; do eval "_new_$dmn=\${$dmn:-no}"; done . "$oldcfg" # OR together the daemon enabling options between config files for dmn in $DAEMONS; do eval "test \$_new_$dmn != no && $dmn=\$_new_$dmn; unset _new_$dmn"; done } [ -r "$C_PATH/daemons" ] || { log_failure_msg "cannot run $@: $C_PATH/daemons does not exist" exit 1 } . "$C_PATH/daemons" load_old_config "$C_PATH/daemons.conf" load_old_config "/etc/default/frr" load_old_config "/etc/sysconfig/frr" if { declare -p watchfrr_options 2>/dev/null || true; } | grep -q '^declare \-a'; then log_warning_msg "watchfrr_options contains a bash array value." \ "The configured value is intentionally ignored since it is likely wrong." \ "Please remove or fix the setting." unset watchfrr_options fi # # other defaults and dispatch # frrcommon_main() { local cmd debug "frrcommon_main" "$@" cmd="$1" shift if [ "$1" = "all" -o -z "$1" ]; then case "$cmd" in start) all_start;; stop) all_stop;; restart) all_stop all_start ;; *) $cmd "$@";; esac else case "$cmd" in start) daemon_start "$@";; stop) daemon_stop "$@";; restart) daemon_stop "$@" daemon_start "$@" ;; *) $cmd "$@";; esac fi } frr-7.2.1/tools/frrinit.sh.in0000644000000000000000000000554713610377563013005 00000000000000#!/bin/bash # ### BEGIN INIT INFO # Provides: frr # Required-Start: $local_fs $network $remote_fs $syslog # Required-Stop: $local_fs $network $remote_fs $syslog # Default-Start: 2 3 4 5 # Default-Stop: 0 1 6 # Short-Description: start and stop the FRR routing suite # Description: FRR is a routing suite for IP routing protocols like # BGP, OSPF, RIP and others. This script contols the main # "watchfrr" daemon. ### END INIT INFO # # This is the main init script for FRR. It mostly wraps frrcommon.sh which # provides the actual functions to start/stop/restart things. # if [ -r "/lib/lsb/init-functions" ]; then . /lib/lsb/init-functions else log_success_msg() { echo "$@" } log_warning_msg() { echo "$@" >&2 } log_failure_msg() { echo "$@" >&2 } fi self="`dirname $0`" if [ -r "$self/frrcommon.sh" ]; then . "$self/frrcommon.sh" else . "@CFG_SBIN@/frrcommon.sh" fi case "$1" in start) daemon_list daemons watchfrr_options="$watchfrr_options $daemons" daemon_start watchfrr ;; stop) daemon_stop watchfrr all_stop --reallyall exit ${still_running:-0} ;; restart|force-reload) daemon_stop watchfrr all_stop --reallyall daemon_list daemons watchfrr_options="$watchfrr_options $daemons" daemon_start watchfrr ;; status) fail=0 print_status watchfrr || fail=1 all_status || fail=1 exit $fail ;; reload) if [ ! -x "$RELOAD_SCRIPT" ]; then log_failure_msg "The frr-pythontools package is required for reload functionality." exit 1 fi # systemd doesn't set WATCHDOG_USEC for reload commands. watchfrr_pidfile="$V_PATH/watchfrr.pid" watchfrr_pid="`cat \"$watchfrr_pidfile\"`" if [ -d "/proc/$watchfrr_pid" ]; then wdt="`tr '\0' '\n' < /proc/$watchfrr_pid/environ | grep '^WATCHDOG_USEC='`" wdt="${wdt#WATCHDOG_USEC=}" [ -n "$wdt" ] && : ${WATCHDOG_USEC:=$wdt} [ -n "$WATCHDOG_USEC" ] && export WATCHDOG_USEC fi # restart watchfrr to pick up added daemons. # NB: This will NOT cause the other daemons to be restarted. daemon_list daemons watchfrr_options="$watchfrr_options $daemons" daemon_stop watchfrr && \ daemon_start watchfrr # make systemd not kill watchfrr after ExecReload completes # 3 goats were sacrificed to restore sanity after coding this watchfrr_pid="`cat \"$watchfrr_pidfile\"`" if [ -f "/proc/$watchfrr_pid/cgroup" -a -d "/sys/fs/cgroup/systemd" ]; then cg="`egrep '^[0-9]+:name=systemd:' \"/proc/$watchfrr_pid/cgroup\"`" cg="${cg#*:*:}" cgmain="$cg" cgmain="${cgmain%/.control}" cgmain="${cgmain%/control}" [ -n "$cg" -a "$cg" != "$cgmain" ] && \ echo "$watchfrr_pid" > "/sys/fs/cgroup/systemd/$cgmain/tasks" fi NEW_CONFIG_FILE="${2:-$C_PATH/frr.conf}" [ ! -r $NEW_CONFIG_FILE ] && log_failure_msg "Unable to read new configuration file $NEW_CONFIG_FILE" && exit 1 "$RELOAD_SCRIPT" --reload "$NEW_CONFIG_FILE" exit $? ;; *) log_failure_msg "Unknown command: $1" >&2 exit 1 esac frr-7.2.1/tools/gen_northbound_callbacks.c0000644000000000000000000002030413610377563015531 00000000000000/* * Copyright (C) 2018 NetDEF, Inc. * Renato Westphal * * This program is free software; you can redistribute it and/or modify it * under the terms of the 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; see the file COPYING; if not, write to the Free Software * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA */ #define REALLY_NEED_PLAIN_GETOPT 1 #include #include #include "yang.h" #include "northbound.h" static void __attribute__((noreturn)) usage(int status) { extern const char *__progname; fprintf(stderr, "usage: %s [-h] [-p path] MODULE\n", __progname); exit(status); } static struct nb_callback_info { int operation; bool optional; char return_type[32]; char return_value[32]; char arguments[128]; } nb_callbacks[] = { { .operation = NB_OP_CREATE, .return_type = "int ", .return_value = "NB_OK", .arguments = "enum nb_event event, const struct lyd_node *dnode, union nb_resource *resource", }, { .operation = NB_OP_MODIFY, .return_type = "int ", .return_value = "NB_OK", .arguments = "enum nb_event event, const struct lyd_node *dnode, union nb_resource *resource", }, { .operation = NB_OP_DESTROY, .return_type = "int ", .return_value = "NB_OK", .arguments = "enum nb_event event, const struct lyd_node *dnode", }, { .operation = NB_OP_MOVE, .return_type = "int ", .return_value = "NB_OK", .arguments = "enum nb_event event, const struct lyd_node *dnode", }, { .operation = NB_OP_APPLY_FINISH, .optional = true, .return_type = "void ", .return_value = "", .arguments = "const struct lyd_node *dnode", }, { .operation = NB_OP_GET_ELEM, .return_type = "struct yang_data *", .return_value = "NULL", .arguments = "const char *xpath, const void *list_entry", }, { .operation = NB_OP_GET_NEXT, .return_type = "const void *", .return_value = "NULL", .arguments = "const void *parent_list_entry, const void *list_entry", }, { .operation = NB_OP_GET_KEYS, .return_type = "int ", .return_value = "NB_OK", .arguments = "const void *list_entry, struct yang_list_keys *keys", }, { .operation = NB_OP_LOOKUP_ENTRY, .return_type = "const void *", .return_value = "NULL", .arguments = "const void *parent_list_entry, const struct yang_list_keys *keys", }, { .operation = NB_OP_RPC, .return_type = "int ", .return_value = "NB_OK", .arguments = "const char *xpath, const struct list *input, struct list *output", }, { /* sentinel */ .operation = -1, }, }; static void replace_hyphens_by_underscores(char *str) { char *p; p = str; while ((p = strchr(p, '-')) != NULL) *p++ = '_'; } static void generate_callback_name(struct lys_node *snode, enum nb_operation operation, char *buffer, size_t size) { struct list *snodes; struct listnode *ln; snodes = list_new(); for (; snode; snode = lys_parent(snode)) { /* Skip schema-only snodes. */ if (CHECK_FLAG(snode->nodetype, LYS_USES | LYS_CHOICE | LYS_CASE | LYS_INPUT | LYS_OUTPUT)) continue; listnode_add_head(snodes, snode); } memset(buffer, 0, size); for (ALL_LIST_ELEMENTS_RO(snodes, ln, snode)) { strlcat(buffer, snode->name, size); strlcat(buffer, "_", size); } strlcat(buffer, nb_operation_name(operation), size); list_delete(&snodes); replace_hyphens_by_underscores(buffer); } static void generate_callback(const struct nb_callback_info *ncinfo, const char *cb_name) { printf("static %s%s(%s)\n{\n", ncinfo->return_type, cb_name, ncinfo->arguments); switch (ncinfo->operation) { case NB_OP_CREATE: case NB_OP_MODIFY: case NB_OP_DESTROY: case NB_OP_MOVE: printf("\tswitch (event) {\n" "\tcase NB_EV_VALIDATE:\n" "\tcase NB_EV_PREPARE:\n" "\tcase NB_EV_ABORT:\n" "\tcase NB_EV_APPLY:\n" "\t\t/* TODO: implement me. */\n" "\t\tbreak;\n" "\t}\n\n" ); break; default: printf("\t/* TODO: implement me. */\n"); break; } printf("\treturn %s;\n}\n\n", ncinfo->return_value); } static int generate_callbacks(const struct lys_node *snode, void *arg) { bool first = true; switch (snode->nodetype) { case LYS_CONTAINER: case LYS_LEAF: case LYS_LEAFLIST: case LYS_LIST: case LYS_NOTIF: case LYS_RPC: break; default: return YANG_ITER_CONTINUE; } for (struct nb_callback_info *cb = &nb_callbacks[0]; cb->operation != -1; cb++) { char cb_name[BUFSIZ]; if (cb->optional || !nb_operation_is_valid(cb->operation, snode)) continue; if (first) { char xpath[XPATH_MAXLEN]; yang_snode_get_path(snode, YANG_PATH_DATA, xpath, sizeof(xpath)); printf("/*\n" " * XPath: %s\n" " */\n", xpath); first = false; } generate_callback_name((struct lys_node *)snode, cb->operation, cb_name, sizeof(cb_name)); generate_callback(cb, cb_name); } return YANG_ITER_CONTINUE; } static int generate_nb_nodes(const struct lys_node *snode, void *arg) { bool first = true; switch (snode->nodetype) { case LYS_CONTAINER: case LYS_LEAF: case LYS_LEAFLIST: case LYS_LIST: case LYS_NOTIF: case LYS_RPC: break; default: return YANG_ITER_CONTINUE; } for (struct nb_callback_info *cb = &nb_callbacks[0]; cb->operation != -1; cb++) { char cb_name[BUFSIZ]; if (cb->optional || !nb_operation_is_valid(cb->operation, snode)) continue; if (first) { char xpath[XPATH_MAXLEN]; yang_snode_get_path(snode, YANG_PATH_DATA, xpath, sizeof(xpath)); printf("\t\t{\n" "\t\t\t.xpath = \"%s\",\n", xpath); printf("\t\t\t.cbs = {\n"); first = false; } generate_callback_name((struct lys_node *)snode, cb->operation, cb_name, sizeof(cb_name)); printf("\t\t\t\t.%s = %s,\n", nb_operation_name(cb->operation), cb_name); } if (!first) { printf("\t\t\t}\n"); printf("\t\t},\n"); } return YANG_ITER_CONTINUE; } int main(int argc, char *argv[]) { const char *search_path = NULL; struct yang_module *module; char module_name_underscores[64]; struct stat st; int opt; while ((opt = getopt(argc, argv, "hp:")) != -1) { switch (opt) { case 'h': usage(EXIT_SUCCESS); /* NOTREACHED */ case 'p': if (stat(optarg, &st) == -1) { fprintf(stderr, "error: invalid search path '%s': %s\n", optarg, strerror(errno)); exit(EXIT_FAILURE); } if (S_ISDIR(st.st_mode) == 0) { fprintf(stderr, "error: search path is not directory"); exit(EXIT_FAILURE); } search_path = optarg; break; default: usage(EXIT_FAILURE); /* NOTREACHED */ } } argc -= optind; argv += optind; if (argc != 1) usage(EXIT_FAILURE); yang_init(); if (search_path) ly_ctx_set_searchdir(ly_native_ctx, search_path); /* Load all FRR native models to ensure all augmentations are loaded. */ yang_module_load_all(); module = yang_module_find(argv[0]); if (!module) /* Non-native FRR module (e.g. modules from unit tests). */ module = yang_module_load(argv[0]); /* Create a nb_node for all YANG schema nodes. */ nb_nodes_create(); /* Generate callback functions. */ yang_snodes_iterate_module(module->info, generate_callbacks, 0, NULL); strlcpy(module_name_underscores, module->name, sizeof(module_name_underscores)); replace_hyphens_by_underscores(module_name_underscores); /* Generate frr_yang_module_info array. */ printf("/* clang-format off */\n" "const struct frr_yang_module_info %s_info = {\n" "\t.name = \"%s\",\n" "\t.nodes = {\n", module_name_underscores, module->name); yang_snodes_iterate_module(module->info, generate_nb_nodes, 0, NULL); printf("\t\t{\n" "\t\t\t.xpath = NULL,\n" "\t\t},\n"); printf("\t}\n" "};\n"); /* Cleanup and exit. */ nb_nodes_delete(); yang_terminate(); return 0; } frr-7.2.1/tools/gen_yang_deviations.c0000644000000000000000000000371313610377563014540 00000000000000/* * Copyright (C) 2018 NetDEF, Inc. * Renato Westphal * * This program is free software; you can redistribute it and/or modify it * under the terms of the 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; see the file COPYING; if not, write to the Free Software * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA */ #define REALLY_NEED_PLAIN_GETOPT 1 #include #include #include "yang.h" #include "northbound.h" static void __attribute__((noreturn)) usage(int status) { fprintf(stderr, "usage: gen_yang_deviations [-h] MODULE\n"); exit(status); } static int generate_yang_deviation(const struct lys_node *snode, void *arg) { char xpath[XPATH_MAXLEN]; yang_snode_get_path(snode, YANG_PATH_SCHEMA, xpath, sizeof(xpath)); printf(" deviation \"%s\" {\n", xpath); printf(" deviate not-supported;\n"); printf(" }\n\n"); return YANG_ITER_CONTINUE; } int main(int argc, char *argv[]) { struct yang_module *module; int opt; while ((opt = getopt(argc, argv, "h")) != -1) { switch (opt) { case 'h': usage(EXIT_SUCCESS); /* NOTREACHED */ default: usage(EXIT_FAILURE); /* NOTREACHED */ } } argc -= optind; argv += optind; if (argc != 1) usage(EXIT_FAILURE); yang_init(); /* Load YANG module. */ module = yang_module_load(argv[0]); /* Generate deviations. */ yang_snodes_iterate_module(module->info, generate_yang_deviation, YANG_ITER_FILTER_IMPLICIT, NULL); /* Cleanup and exit. */ yang_terminate(); return 0; } frr-7.2.1/tools/multiple-bgpd.sh0000644000000000000000000000433413610377563013461 00000000000000#!/bin/bash # Public domain, not copyrighted.. NUM=5 VTYBASE=2610 ASBASE=64560 BGPD=/path/to/bgpd PREFIX=192.168.145. #PREFIX=3ffe:123:456:: ADDRPLEN=32 CONFBASE=/tmp PIDBASE=/var/run/frr CHOWNSTR=frr:frr for H in `seq 1 ${NUM}` ; do CONF="${CONFBASE}"/bgpd${H}.conf ADDR=${PREFIX}${H} if [ ! -e "$CONF" ] ; then # This sets up a ring of bgpd peerings NEXT=$(( ($H % ${NUM}) + 1 )) PREV=$(( (($H + $NUM - 2) % ${NUM}) + 1 )) NEXTADDR="${PREFIX}${NEXT}" NEXTAS=$((${ASBASE} + $NEXT)) PREVADDR="${PREFIX}${PREV}" PREVAS=$((${ASBASE} + $PREV)) ASN=$((64560+${H})) # Edit config to suit. cat > "$CONF" <<- EOF password whatever service advanced-vty ! router bgp ${ASN} bgp router-id ${ADDR} network 10.${H}.1.0/24 pathlimit 1 network 10.${H}.2.0/24 pathlimit 2 network 10.${H}.3.0/24 pathlimit 3 neighbor default peer-group neighbor default update-source ${ADDR} neighbor default capability orf prefix-list both neighbor default soft-reconfiguration inbound neighbor default route-map test out neighbor ${NEXTADDR} remote-as ${NEXTAS} neighbor ${NEXTADDR} peer-group default neighbor ${PREVADDR} remote-as ${PREVAS} neighbor ${PREVADDR} peer-group default ! address-family ipv6 network 3ffe:${H}::/48 network 3ffe:${H}:1::/48 pathlimit 1 network 3ffe:${H}:2::/48 pathlimit 3 network 3ffe:${H}:3::/48 pathlimit 3 neighbor default activate neighbor default capability orf prefix-list both neighbor default default-originate neighbor default route-map test out neighbor ${NEXTADDR} peer-group default neighbor ${PREVADDR} peer-group default exit-address-family ! ! bgpd still has problems with extcommunity rt/soo route-map test permit 10 set extcommunity rt ${ASN}:1 set extcommunity soo ${ASN}:2 set community ${ASN}:1 line vty ! end EOF chown ${CHOWNSTR} "$CONF" fi # You may want to automatically add configure a local address # on a loop interface. # # Solaris: ifconfig vni${H} plumb ${ADDR}/${ADDRPLEN} up # Linux: ip address add ${ADDR}/${ADDRPLEN} dev lo 2> /dev/null ${BGPD} -i "${PIDBASE}"/bgpd${H}.pid \ -l ${ADDR} \ -f "${CONF}" \ -P $((${VTYBASE}+${H})) \ -d done frr-7.2.1/tools/permutations.c0000644000000000000000000000517313610377563013260 00000000000000/* * Generates all possible matching inputs for a command string. * -- * Copyright (C) 2016 Cumulus Networks, Inc. * * This file is part of GNU Zebra. * * GNU Zebra is free software; you can redistribute it and/or modify it * under the terms of the GNU General Public License as published by the * Free Software Foundation; either version 2, or (at your option) any * later version. * * GNU Zebra 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; see the file COPYING; if not, write to the Free Software * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA */ #ifdef HAVE_CONFIG_H #include "config.h" #endif #include "command.h" #include "graph.h" #include "vector.h" #define USAGE "usage: permutations " void permute(struct graph_node *); void pretty_print_graph(struct graph_node *start, int level); int main(int argc, char *argv[]) { if (argc < 2) { fprintf(stdout, USAGE "\n"); exit(EXIT_SUCCESS); } struct cmd_element *cmd = XCALLOC(MTYPE_TMP, sizeof(struct cmd_element)); cmd->string = strdup(argv[1]); struct graph *graph = graph_new(); struct cmd_token *token = cmd_token_new(START_TKN, cmd->attr, NULL, NULL); graph_new_node(graph, token, NULL); cmd_graph_parse(graph, cmd); permute(vector_slot(graph->nodes, 0)); } void permute(struct graph_node *start) { static struct list *position = NULL; if (!position) position = list_new(); struct cmd_token *stok = start->data; struct graph_node *gnn; struct listnode *ln; // recursive dfs listnode_add(position, start); for (unsigned int i = 0; i < vector_active(start->to); i++) { struct graph_node *gn = vector_slot(start->to, i); struct cmd_token *tok = gn->data; if (tok->attr == CMD_ATTR_HIDDEN || tok->attr == CMD_ATTR_DEPRECATED) continue; else if (tok->type == END_TKN || gn == start) { fprintf(stdout, " "); for (ALL_LIST_ELEMENTS_RO(position, ln, gnn)) { struct cmd_token *tt = gnn->data; if (tt->type < SPECIAL_TKN) fprintf(stdout, " %s", tt->text); } if (gn == start) fprintf(stdout, "..."); fprintf(stdout, "\n"); } else { bool skip = false; if (stok->type == FORK_TKN && tok->type != FORK_TKN) for (ALL_LIST_ELEMENTS_RO(position, ln, gnn)) if (gnn == gn) { skip = true; break; } if (!skip) permute(gn); } } list_delete_node(position, listtail(position)); } frr-7.2.1/tools/rrcheck.pl0000644000000000000000000000560313610377563012336 00000000000000#!/usr/bin/env perl ## ## Read BGPd logfile and lookup RR's whois database. ## ## Copyright (c) 1997 Kunihiro Ishiguro ## use Socket; ## Configuration variables $whois_host = "whois.jpix.ad.jp"; #$logfile = "/usr/local/sbin/logfile" $logfile = shift || die "Please specify filename"; ## mail routine { local ($prefix, $origin); open (LOG, $logfile) || die "can't open $logfile"; $index = ''; while ($index) { $index = ; if ($index =~ /[bgpd]/) { break; } } while () { if (/([\d\.\/]+)\s+([\d\.]+)\s+(\d+)\s+(\d+)\s+([\d ]+)\s+[ie\?]/) { $prefix = $1; $nexthop = $2; $med = $3; $dummy = $4; $aspath = $5; ($origin) = ($aspath =~ /([\d]+)$/); print "$nexthop [$origin] $prefix $aspath "; $ret = &whois_check ($prefix, $origin); if ($ret == 0) { print "Check OK\n"; } elsif ($ret == 1){ print "AS orgin mismatch\n"; } else { print "prefix doesn't exist \n"; } } } } sub whois_check { local ($prefix, $origin) = @_; local ($rr_prefix, $rr_origin) = (); local (@result); $origin = "AS" . $origin; @result = &whois ($prefix); $prefix_match = 0; foreach (@result) { if (/^route:.*\s([\d\.\/]+)$/) { $rr_prefix = $1; } if (/^origin:.*\s(AS[\d]+)$/) { $rr_origin = $1; if ($prefix eq $rr_prefix and $origin eq $rr_origin) { return 0; } elsif ($prefix eq $rr_prefix) { $prefix_match = 1; } } } # alarm_mail ($prefix, $origin, @result); if ($prefix_match) { return 1; } else { return 2; } } ## get port of whois sub get_whois_port { local ($name, $aliases, $port, $proto) = getservbyname ("whois", "tcp"); return ($port, $proto); } ## whois lookup sub whois { local ($query) = @_; local ($port, $proto) = &get_whois_port; local (@result); if ($whois_host=~ /^\s*\d+\.\d+\.\d+\.\d+\s*$/) { $address = pack ("C4",split(/\./,$host)); } else { $address = (gethostbyname ($whois_host))[4]; } socket (SOCKET, PF_INET, SOCK_STREAM, $proto); if (connect (SOCKET, sockaddr_in ($port, $address))) { local ($oldhandle) = select (SOCKET); $| = 1; select($oldhandle); print SOCKET "$query\r\n"; @result = ; return @result; } } ## sub alarm_mail { local ($prefix, $origin, @result) = @_; open (MAIL, "|$mailer -t $mail_address") || die "can't open $mailer"; print MAIL "From: root\@rr1.jpix.ad.jp\n"; print MAIL "Subject: RR $origin $prefix\n"; print MAIL "MIME-Version: 1.0\n"; print MAIL "Content-Type: text/plain; charset=us-ascii \n\n"; print MAIL "RR Lookup Error Report\n"; print MAIL "======================\n"; print MAIL "Announced route : $prefix from $origin\n\n"; print MAIL "@result"; close MAIL; } frr-7.2.1/tools/rrlookup.pl0000644000000000000000000000546413610377563012577 00000000000000#!/usr/bin/env perl ## ## Read BGPd logfile and lookup RR's whois database. ## ## Copyright (c) 1997 Kunihiro Ishiguro ## use Socket; ## Configuration variables $whois_host = "whois.jpix.ad.jp"; #$mail_address = "toshio\@iri.co.jp"; $mail_address = "kunihiro\@zebra.org"; $mailer = "/usr/sbin/sendmail -oi"; #$logfile = "/usr/local/sbin/logfile" $logfile = "logfile"; $lookuplog = "lookuplog"; ## mail routine { local ($prefix, $origin); open (LOG, $logfile) || die "can't open $logfile"; open (LOOKUP, ">$lookuplog") || die "can't open $lookuplog"; for (;;) { while () { if (/Update\S+ ([\d\.\/]+) .* (\d+) [ie\?]/) { $prefix = $1; $origin = $2; $ret = &whois_check ($prefix, $origin); if ($ret) { print LOOKUP "$prefix AS$origin : Check OK\n"; } else { print LOOKUP "$prefix AS$origin : Error\n"; } # fflush (LOOKUP); } } sleep (3); } } sub whois_check { local ($prefix, $origin) = @_; local ($rr_prefix, $rr_origin) = (); local (@result); $origin = "AS" . $origin; # print "$prefix $origin\n"; @result = &whois ($prefix); foreach (@result) { if (/^route:.*\s([\d\.\/]+)$/) { $rr_prefix = $1; } if (/^origin:.*\s(AS[\d]+)$/) { $rr_origin = $1; if ($prefix eq $rr_prefix and $origin eq $rr_origin) { return 1; } } } alarm_mail ($prefix, $origin, @result); return 0; } ## get port of whois sub get_whois_port { local ($name, $aliases, $port, $proto) = getservbyname ("whois", "tcp"); return ($port, $proto); } ## whois lookup sub whois { local ($query) = @_; local ($port, $proto) = &get_whois_port; local (@result); if ($whois_host=~ /^\s*\d+\.\d+\.\d+\.\d+\s*$/) { $address = pack ("C4",split(/\./,$host)); } else { $address = (gethostbyname ($whois_host))[4]; } socket (SOCKET, PF_INET, SOCK_STREAM, $proto); if (connect (SOCKET, sockaddr_in ($port, $address))) { local ($oldhandle) = select (SOCKET); $| = 1; select($oldhandle); print SOCKET "$query\r\n"; @result = ; return @result; } } ## sub alarm_mail { local ($prefix, $origin, @result) = @_; open (MAIL, "|$mailer -t $mail_address") || die "can't open $mailer"; print MAIL "From: root\@rr1.jpix.ad.jp\n"; print MAIL "Subject: RR $origin $prefix\n"; print MAIL "MIME-Version: 1.0\n"; print MAIL "Content-Type: text/plain; charset=us-ascii \n\n"; print MAIL "RR Lookup Error Report\n"; print MAIL "======================\n"; print MAIL "Announced route : $prefix from $origin\n\n"; print MAIL "@result"; close MAIL; } frr-7.2.1/tools/start-stop-daemon.c0000644000000000000000000006542413610377563014114 00000000000000/* * A rewrite of the original Debian's start-stop-daemon Perl script * in C (faster - it is executed many times during system startup). * * Written by Marek Michalkiewicz , * public domain. Based conceptually on start-stop-daemon.pl, by Ian * Jackson . May be used and distributed * freely for any purpose. Changes by Christian Schwarz * , to make output conform to the Debian * Console Message Standard, also placed in public domain. Minor * changes by Klee Dienes , also placed in the Public * Domain. * * Changes by Ben Collins , added --chuid, --background * and --make-pidfile options, placed in public domain aswell. * * Port to OpenBSD by Sontri Tomo Huynh * and Andreas Schuldei * * Changes by Ian Jackson: added --retry (and associated rearrangements). * * Modified for Gentoo rc-scripts by Donny Davies : * I removed the BSD/Hurd/OtherOS stuff, added #include * and stuck in a #define VERSION "1.9.18". Now it compiles without * the whole automake/config.h dance. */ #ifdef HAVE_CONFIG_H #include "config.h" #endif #ifdef HAVE_LXC #define _GNU_SOURCE #include #endif /* HAVE_LXC */ #include #undef VERSION #define VERSION "1.9.18" #define MIN_POLL_INTERVAL 20000 /*us*/ #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #ifdef linux #include #endif static int testmode = 0; static int quietmode = 0; static int exitnodo = 1; static int start = 0; static int stop = 0; static int background = 0; static int mpidfile = 0; static int signal_nr = 15; static const char *signal_str = NULL; static int user_id = -1; static int runas_uid = -1; static int runas_gid = -1; static const char *userspec = NULL; static char *changeuser = NULL; static const char *changegroup = NULL; static char *changeroot = NULL; static const char *cmdname = NULL; static char *execname = NULL; static char *startas = NULL; static const char *pidfile = NULL; static char what_stop[1024]; static const char *schedule_str = NULL; static const char *progname = ""; static int nicelevel = 0; static struct stat exec_stat; struct pid_list { struct pid_list *next; pid_t pid; }; static struct pid_list *found = NULL; static struct pid_list *killed = NULL; struct schedule_item { enum { sched_timeout, sched_signal, sched_goto, sched_forever } type; int value; /* seconds, signal no., or index into array */ /* sched_forever is only seen within parse_schedule and callees */ }; static int schedule_length; static struct schedule_item *schedule = NULL; LIST_HEAD(namespace_head, namespace); struct namespace { LIST_ENTRY(namespace) list; const char *path; int nstype; }; static struct namespace_head namespace_head; static void *xmalloc(int size); static void push(struct pid_list **list, pid_t pid); static void do_help(void); static void parse_options(int argc, char *const *argv); static int pid_is_user(pid_t pid, uid_t uid); static int pid_is_cmd(pid_t pid, const char *name); static void check(pid_t pid); static void do_pidfile(const char *name); static void do_stop(int signal_nr, int quietmode, int *n_killed, int *n_notkilled, int retry_nr); static int pid_is_exec(pid_t pid, const struct stat *esb); #ifdef __GNUC__ static void fatal(const char *format, ...) __attribute__((noreturn, format(printf, 1, 2))); static void badusage(const char *msg) __attribute__((noreturn)); #else static void fatal(const char *format, ...); static void badusage(const char *msg); #endif /* This next part serves only to construct the TVCALC macro, which * is used for doing arithmetic on struct timeval's. It works like this: * TVCALC(result, expression); * where result is a struct timeval (and must be an lvalue) and * expression is the single expression for both components. In this * expression you can use the special values TVELEM, which when fed a * const struct timeval* gives you the relevant component, and * TVADJUST. TVADJUST is necessary when subtracting timevals, to make * it easier to renormalise. Whenver you subtract timeval elements, * you must make sure that TVADJUST is added to the result of the * subtraction (before any resulting multiplication or what have you). * TVELEM must be linear in TVADJUST. */ typedef long tvselector(const struct timeval *); static long tvselector_sec(const struct timeval *tv) { return tv->tv_sec; } static long tvselector_usec(const struct timeval *tv) { return tv->tv_usec; } #define TVCALC_ELEM(result, expr, sec, adj) \ { \ const long TVADJUST = adj; \ long (*const TVELEM)(const struct timeval *) = \ tvselector_##sec; \ (result).tv_##sec = (expr); \ } #define TVCALC(result, expr) \ do { \ TVCALC_ELEM(result, expr, sec, (-1)); \ TVCALC_ELEM(result, expr, usec, (+1000000)); \ (result).tv_sec += (result).tv_usec / 1000000; \ (result).tv_usec %= 1000000; \ } while (0) static void fatal(const char *format, ...) { va_list arglist; fprintf(stderr, "%s: ", progname); va_start(arglist, format); vfprintf(stderr, format, arglist); va_end(arglist); putc('\n', stderr); exit(2); } static void *xmalloc(int size) { void *ptr; ptr = malloc(size); if (ptr) return ptr; fatal("malloc(%d) failed", size); } static void xgettimeofday(struct timeval *tv) { if (gettimeofday(tv, 0) != 0) fatal("gettimeofday failed: %s", strerror(errno)); } static void push(struct pid_list **list, pid_t pid) { struct pid_list *p; p = xmalloc(sizeof(*p)); p->next = *list; p->pid = pid; *list = p; } static void clear(struct pid_list **list) { struct pid_list *here, *next; for (here = *list; here != NULL; here = next) { next = here->next; free(here); } *list = NULL; } #ifdef linux static const char *next_dirname(const char *s) { const char *cur; cur = (const char *)s; if (*cur != '\0') { for (; *cur != '/'; ++cur) if (*cur == '\0') return cur; for (; *cur == '/'; ++cur) ; } return cur; } static void add_namespace(const char *path) { int nstype; const char *nsdirname, *nsname, *cur; struct namespace *namespace; cur = (const char *)path; nsdirname = nsname = ""; while ((cur = next_dirname(cur))[0] != '\0') { nsdirname = nsname; nsname = cur; } if (!strncmp(nsdirname, "ipcns/", strlen("ipcns/"))) nstype = CLONE_NEWIPC; else if (!strncmp(nsdirname, "netns/", strlen("netns/"))) nstype = CLONE_NEWNET; else if (!strncmp(nsdirname, "utcns/", strlen("utcns/"))) nstype = CLONE_NEWUTS; else badusage("invalid namepspace path"); namespace = xmalloc(sizeof(*namespace)); namespace->path = (const char *)path; namespace->nstype = nstype; LIST_INSERT_HEAD(&namespace_head, namespace, list); } #endif #ifdef HAVE_LXC static void set_namespaces(void) { struct namespace *namespace; int fd; LIST_FOREACH (namespace, &namespace_head, list) { if ((fd = open(namespace->path, O_RDONLY)) == -1) fatal("open namespace %s: %s", namespace->path, strerror(errno)); if (setns(fd, namespace->nstype) == -1) fatal("setns %s: %s", namespace->path, strerror(errno)); } } #else static void set_namespaces(void) { if (!LIST_EMPTY(&namespace_head)) fatal("LCX namespaces not supported"); } #endif static void do_help(void) { printf("start-stop-daemon " VERSION " for Debian - small and fast C version written by\n" "Marek Michalkiewicz , public domain.\n" "\n" "Usage:\n" " start-stop-daemon -S|--start options ... -- arguments ...\n" " start-stop-daemon -K|--stop options ...\n" " start-stop-daemon -H|--help\n" " start-stop-daemon -V|--version\n" "\n" "Options (at least one of --exec|--pidfile|--user is required):\n" " -x|--exec program to start/check if it is running\n" " -p|--pidfile pid file to check\n" " -c|--chuid \n" " change to this user/group before starting process\n" " -u|--user | stop processes owned by this user\n" " -n|--name stop processes with this name\n" " -s|--signal signal to send (default TERM)\n" " -a|--startas program to start (default is )\n" " -N|--nicelevel add incr to the process's nice level\n" " -b|--background force the process to detach\n" " -m|--make-pidfile create the pidfile before starting\n" " -R|--retry check whether processes die, and retry\n" " -t|--test test mode, don't do anything\n" " -o|--oknodo exit status 0 (not 1) if nothing done\n" " -q|--quiet be more quiet\n" " -v|--verbose be more verbose\n" "Retry is |//... where is one of\n" " -|[-] send that signal\n" " wait that many seconds\n" " forever repeat remainder forever\n" "or may be just , meaning //KILL/\n" "\n" "Exit status: 0 = done 1 = nothing done (=> 0 if --oknodo)\n" " 3 = trouble 2 = with --retry, processes wouldn't die\n"); } static void badusage(const char *msg) { if (msg) fprintf(stderr, "%s: %s\n", progname, msg); fprintf(stderr, "Try `%s --help' for more information.\n", progname); exit(3); } struct sigpair { const char *name; int signal; }; const struct sigpair siglist[] = { {"ABRT", SIGABRT}, {"ALRM", SIGALRM}, {"FPE", SIGFPE}, {"HUP", SIGHUP}, {"ILL", SIGILL}, {"INT", SIGINT}, {"KILL", SIGKILL}, {"PIPE", SIGPIPE}, {"QUIT", SIGQUIT}, {"SEGV", SIGSEGV}, {"TERM", SIGTERM}, {"USR1", SIGUSR1}, {"USR2", SIGUSR2}, {"CHLD", SIGCHLD}, {"CONT", SIGCONT}, {"STOP", SIGSTOP}, {"TSTP", SIGTSTP}, {"TTIN", SIGTTIN}, {"TTOU", SIGTTOU}}; static int parse_integer(const char *string, int *value_r) { unsigned long ul; char *ep; if (!string[0]) return -1; ul = strtoul(string, &ep, 10); if (ul > INT_MAX || *ep != '\0') return -1; *value_r = ul; return 0; } static int parse_signal(const char *signal_str, int *signal_nr) { unsigned int i; if (parse_integer(signal_str, signal_nr) == 0) return 0; for (i = 0; i < sizeof(siglist) / sizeof(siglist[0]); i++) { if (strcmp(signal_str, siglist[i].name) == 0) { *signal_nr = siglist[i].signal; return 0; } } return -1; } static void parse_schedule_item(const char *string, struct schedule_item *item) { const char *after_hyph; if (!strcmp(string, "forever")) { item->type = sched_forever; } else if (isdigit((unsigned char)string[0])) { item->type = sched_timeout; if (parse_integer(string, &item->value) != 0) badusage("invalid timeout value in schedule"); } else if ((after_hyph = string + (string[0] == '-')) && parse_signal(after_hyph, &item->value) == 0) { item->type = sched_signal; } else { badusage( "invalid schedule item (must be [-], " "-, or `forever'"); } } static void parse_schedule(const char *schedule_str) { char item_buf[20]; const char *slash; int count, repeatat; ptrdiff_t str_len; count = 0; for (slash = schedule_str; *slash; slash++) if (*slash == '/') count++; schedule_length = (count == 0) ? 4 : count + 1; schedule = xmalloc(sizeof(*schedule) * schedule_length); if (count == 0) { schedule[0].type = sched_signal; schedule[0].value = signal_nr; parse_schedule_item(schedule_str, &schedule[1]); if (schedule[1].type != sched_timeout) { badusage( "--retry takes timeout, or schedule list" " of at least two items"); } schedule[2].type = sched_signal; schedule[2].value = SIGKILL; schedule[3] = schedule[1]; } else { count = 0; repeatat = -1; while (schedule_str != NULL) { slash = strchr(schedule_str, '/'); str_len = slash ? slash - schedule_str : (ptrdiff_t)strlen(schedule_str); if (str_len >= (ptrdiff_t)sizeof(item_buf)) badusage( "invalid schedule item: far too long" " (you must delimit items with slashes)"); memcpy(item_buf, schedule_str, str_len); item_buf[str_len] = 0; schedule_str = slash ? slash + 1 : NULL; parse_schedule_item(item_buf, &schedule[count]); if (schedule[count].type == sched_forever) { if (repeatat >= 0) badusage( "invalid schedule: `forever'" " appears more than once"); repeatat = count; continue; } count++; } if (repeatat >= 0) { schedule[count].type = sched_goto; schedule[count].value = repeatat; count++; } assert(count == schedule_length); } } static void parse_options(int argc, char *const *argv) { static struct option longopts[] = { {"help", 0, NULL, 'H'}, {"stop", 0, NULL, 'K'}, {"start", 0, NULL, 'S'}, {"version", 0, NULL, 'V'}, {"startas", 1, NULL, 'a'}, {"name", 1, NULL, 'n'}, {"oknodo", 0, NULL, 'o'}, {"pidfile", 1, NULL, 'p'}, {"quiet", 0, NULL, 'q'}, {"signal", 1, NULL, 's'}, {"test", 0, NULL, 't'}, {"user", 1, NULL, 'u'}, {"chroot", 1, NULL, 'r'}, {"namespace", 1, NULL, 'd'}, {"verbose", 0, NULL, 'v'}, {"exec", 1, NULL, 'x'}, {"chuid", 1, NULL, 'c'}, {"nicelevel", 1, NULL, 'N'}, {"background", 0, NULL, 'b'}, {"make-pidfile", 0, NULL, 'm'}, {"retry", 1, NULL, 'R'}, {NULL, 0, NULL, 0}}; int c; for (;;) { c = getopt_long(argc, argv, "HKSVa:n:op:qr:d:s:tu:vx:c:N:bmR:", longopts, (int *)0); if (c == -1) break; switch (c) { case 'H': /* --help */ do_help(); exit(0); case 'K': /* --stop */ stop = 1; break; case 'S': /* --start */ start = 1; break; case 'V': /* --version */ printf("start-stop-daemon " VERSION "\n"); exit(0); case 'a': /* --startas */ startas = optarg; break; case 'n': /* --name */ cmdname = optarg; break; case 'o': /* --oknodo */ exitnodo = 0; break; case 'p': /* --pidfile */ pidfile = optarg; break; case 'q': /* --quiet */ quietmode = 1; break; case 's': /* --signal */ signal_str = optarg; break; case 't': /* --test */ testmode = 1; break; case 'u': /* --user | */ userspec = optarg; break; case 'v': /* --verbose */ quietmode = -1; break; case 'x': /* --exec */ execname = optarg; break; case 'c': /* --chuid | */ changeuser = strtok(optarg, ":"); changegroup = strtok(NULL, ":"); break; case 'r': /* --chroot /new/root */ changeroot = optarg; break; case 'd': /* --namespace /.../||/name */ #ifdef linux add_namespace(optarg); #endif break; case 'N': /* --nice */ nicelevel = atoi(optarg); break; case 'b': /* --background */ background = 1; break; case 'm': /* --make-pidfile */ mpidfile = 1; break; case 'R': /* --retry | */ schedule_str = optarg; break; default: badusage(NULL); /* message printed by getopt */ } } if (signal_str != NULL) { if (parse_signal(signal_str, &signal_nr) != 0) badusage( "signal value must be numeric or name" " of signal (KILL, INTR, ...)"); } if (schedule_str != NULL) { parse_schedule(schedule_str); } if (start == stop) badusage("need one of --start or --stop"); if (!execname && !pidfile && !userspec && !cmdname) badusage( "need at least one of --exec, --pidfile, --user or --name"); if (!startas) startas = execname; if (start && !startas) badusage("--start needs --exec or --startas"); if (mpidfile && pidfile == NULL) badusage("--make-pidfile is only relevant with --pidfile"); if (background && !start) badusage("--background is only relevant with --start"); } static int pid_is_exec(pid_t pid, const struct stat *esb) { struct stat sb; char buf[32]; sprintf(buf, "/proc/%ld/exe", (long)pid); if (stat(buf, &sb) != 0) return 0; return (sb.st_dev == esb->st_dev && sb.st_ino == esb->st_ino); } static int pid_is_user(pid_t pid, uid_t uid) { struct stat sb; char buf[32]; sprintf(buf, "/proc/%ld", (long)pid); if (stat(buf, &sb) != 0) return 0; return (sb.st_uid == uid); } static int pid_is_cmd(pid_t pid, const char *name) { char buf[32]; FILE *f; int c; sprintf(buf, "/proc/%ld/stat", (long)pid); f = fopen(buf, "r"); if (!f) return 0; while ((c = getc(f)) != EOF && c != '(') ; if (c != '(') { fclose(f); return 0; } /* this hopefully handles command names containing ')' */ while ((c = getc(f)) != EOF && c == *name) name++; fclose(f); return (c == ')' && *name == '\0'); } static void check(pid_t pid) { if (execname && !pid_is_exec(pid, &exec_stat)) return; if (userspec && !pid_is_user(pid, user_id)) return; if (cmdname && !pid_is_cmd(pid, cmdname)) return; push(&found, pid); } static void do_pidfile(const char *name) { FILE *f; long pid; f = fopen(name, "r"); if (f) { if (fscanf(f, "%ld", &pid) == 1) check((pid_t)pid); fclose(f); } else if (errno != ENOENT) fatal("open pidfile %s: %s", name, strerror(errno)); } /* WTA: this needs to be an autoconf check for /proc/pid existance. */ static void do_procinit(void) { DIR *procdir; struct dirent *entry; int foundany; long pid; procdir = opendir("/proc"); if (!procdir) fatal("opendir /proc: %s", strerror(errno)); foundany = 0; while ((entry = readdir(procdir)) != NULL) { if (sscanf(entry->d_name, "%ld", &pid) != 1) continue; foundany++; check((pid_t)pid); } closedir(procdir); if (!foundany) fatal("nothing in /proc - not mounted?"); } static void do_findprocs(void) { clear(&found); if (pidfile) do_pidfile(pidfile); else do_procinit(); } /* return 1 on failure */ static void do_stop(int signal_nr, int quietmode, int *n_killed, int *n_notkilled, int retry_nr) { struct pid_list *p; do_findprocs(); *n_killed = 0; *n_notkilled = 0; if (!found) return; clear(&killed); for (p = found; p; p = p->next) { if (testmode) printf("Would send signal %d to %ld.\n", signal_nr, (long)p->pid); else if (kill(p->pid, signal_nr) == 0) { push(&killed, p->pid); (*n_killed)++; } else { printf("%s: warning: failed to kill %ld: %s\n", progname, (long)p->pid, strerror(errno)); (*n_notkilled)++; } } if (quietmode < 0 && killed) { printf("Stopped %s (pid", what_stop); for (p = killed; p; p = p->next) printf(" %ld", (long)p->pid); putchar(')'); if (retry_nr > 0) printf(", retry #%d", retry_nr); printf(".\n"); } } static void set_what_stop(const char *str) { strncpy(what_stop, str, sizeof(what_stop)); what_stop[sizeof(what_stop) - 1] = '\0'; } static int run_stop_schedule(void) { int r, position, n_killed, n_notkilled, value, ratio, anykilled, retry_nr; struct timeval stopat, before, after, interval, maxinterval; if (testmode) { if (schedule != NULL) { printf("Ignoring --retry in test mode\n"); schedule = NULL; } } if (cmdname) set_what_stop(cmdname); else if (execname) set_what_stop(execname); else if (pidfile) sprintf(what_stop, "process in pidfile `%.200s'", pidfile); else if (userspec) sprintf(what_stop, "process(es) owned by `%.200s'", userspec); else fatal("internal error, please report"); anykilled = 0; retry_nr = 0; n_killed = 0; if (schedule == NULL) { do_stop(signal_nr, quietmode, &n_killed, &n_notkilled, 0); if (n_notkilled > 0 && quietmode <= 0) printf("%d pids were not killed\n", n_notkilled); if (n_killed) anykilled = 1; goto x_finished; } for (position = 0; position < schedule_length;) { value = schedule[position].value; n_notkilled = 0; switch (schedule[position].type) { case sched_goto: position = value; continue; case sched_signal: do_stop(value, quietmode, &n_killed, &n_notkilled, retry_nr++); if (!n_killed) goto x_finished; else anykilled = 1; goto next_item; case sched_timeout: /* We want to keep polling for the processes, to see if * they've exited, * or until the timeout expires. * * This is a somewhat complicated algorithm to try to * ensure that we * notice reasonably quickly when all the processes have * exited, but * don't spend too much CPU time polling. In * particular, on a fast * machine with quick-exiting daemons we don't want to * delay system * shutdown too much, whereas on a slow one, or where * processes are * taking some time to exit, we want to increase the * polling * interval. * * The algorithm is as follows: we measure the elapsed * time it takes * to do one poll(), and wait a multiple of this time * for the next * poll. However, if that would put us past the end of * the timeout * period we wait only as long as the timeout period, * but in any case * we always wait at least MIN_POLL_INTERVAL (20ms). * The multiple * (`ratio') starts out as 2, and increases by 1 for * each poll to a * maximum of 10; so we use up to between 30% and 10% of * the * machine's resources (assuming a few reasonable things * about system * performance). */ xgettimeofday(&stopat); stopat.tv_sec += value; ratio = 1; for (;;) { xgettimeofday(&before); if (timercmp(&before, &stopat, >)) goto next_item; do_stop(0, 1, &n_killed, &n_notkilled, 0); if (!n_killed) goto x_finished; xgettimeofday(&after); if (!timercmp(&after, &stopat, <)) goto next_item; if (ratio < 10) ratio++; TVCALC(interval, ratio * (TVELEM(&after) - TVELEM(&before) + TVADJUST)); TVCALC(maxinterval, TVELEM(&stopat) - TVELEM(&after) + TVADJUST); if (timercmp(&interval, &maxinterval, >)) interval = maxinterval; if (interval.tv_sec == 0 && interval.tv_usec <= MIN_POLL_INTERVAL) interval.tv_usec = MIN_POLL_INTERVAL; r = select(0, 0, 0, 0, &interval); if (r < 0 && errno != EINTR) fatal("select() failed for pause: %s", strerror(errno)); } default: assert(!"schedule[].type value must be valid"); } next_item: position++; } if (quietmode <= 0) printf("Program %s, %d process(es), refused to die.\n", what_stop, n_killed); return 2; x_finished: if (!anykilled) { if (quietmode <= 0) printf("No %s found running; none killed.\n", what_stop); return exitnodo; } else { return 0; } } /* int main(int argc, char **argv) NONRETURNING; */ int main(int argc, char **argv) { progname = argv[0]; LIST_INIT(&namespace_head); parse_options(argc, argv); argc -= optind; argv += optind; if (execname && stat(execname, &exec_stat)) fatal("stat %s: %s", execname, strerror(errno)); if (userspec && sscanf(userspec, "%d", &user_id) != 1) { struct passwd *pw; pw = getpwnam(userspec); if (!pw) fatal("user `%s' not found\n", userspec); user_id = pw->pw_uid; } if (changegroup && sscanf(changegroup, "%d", &runas_gid) != 1) { struct group *gr = getgrnam(changegroup); if (!gr) fatal("group `%s' not found\n", changegroup); runas_gid = gr->gr_gid; } if (changeuser && sscanf(changeuser, "%d", &runas_uid) != 1) { struct passwd *pw = getpwnam(changeuser); if (!pw) fatal("user `%s' not found\n", changeuser); runas_uid = pw->pw_uid; if (changegroup == NULL) { /* pass the default group of this user */ changegroup = ""; /* just empty */ runas_gid = pw->pw_gid; } } if (stop) { int i = run_stop_schedule(); exit(i); } do_findprocs(); if (found) { if (quietmode <= 0) printf("%s already running.\n", execname); exit(exitnodo); } if (testmode) { printf("Would start %s ", startas); while (argc-- > 0) printf("%s ", *argv++); if (changeuser != NULL) { printf(" (as user %s[%d]", changeuser, runas_uid); if (changegroup != NULL) printf(", and group %s[%d])", changegroup, runas_gid); else printf(")"); } if (changeroot != NULL) printf(" in directory %s", changeroot); if (nicelevel) printf(", and add %i to the priority", nicelevel); printf(".\n"); exit(0); } if (quietmode < 0) printf("Starting %s...\n", startas); *--argv = startas; if (changeroot != NULL) { if (chdir(changeroot) < 0) fatal("Unable to chdir() to %s", changeroot); if (chroot(changeroot) < 0) fatal("Unable to chroot() to %s", changeroot); } if (changeuser != NULL) { if (setgid(runas_gid)) fatal("Unable to set gid to %d", runas_gid); if (initgroups(changeuser, runas_gid)) fatal("Unable to set initgroups() with gid %d", runas_gid); if (setuid(runas_uid)) fatal("Unable to set uid to %s", changeuser); } if (background) { /* ok, we need to detach this process */ int i, fd; if (quietmode < 0) printf("Detaching to start %s...", startas); i = fork(); if (i < 0) { fatal("Unable to fork.\n"); } if (i) { /* parent */ if (quietmode < 0) printf("done.\n"); exit(0); } /* child continues here */ /* now close all extra fds */ for (i = getdtablesize() - 1; i >= 0; --i) close(i); /* change tty */ fd = open("/dev/tty", O_RDWR); if (fd >= 0) { if (ioctl(fd, TIOCNOTTY, 0) < 0) printf("ioctl TIOCNOTTY failed: %s\n", strerror(errno)); close(fd); } chdir("/"); umask(022); /* set a default for dumb programs */ setpgid(0, 0); /* set the process group */ fd = open("/dev/null", O_RDWR); /* stdin */ if (fd >= 0) { dup(fd); /* stdout */ dup(fd); /* stderr */ } } if (nicelevel) { errno = 0; if (nice(nicelevel) < 0 && errno) fatal("Unable to alter nice level by %i: %s", nicelevel, strerror(errno)); } if (mpidfile && pidfile != NULL) { /* user wants _us_ to make the pidfile :) */ FILE *pidf = fopen(pidfile, "w"); pid_t pidt = getpid(); if (pidf == NULL) fatal("Unable to open pidfile `%s' for writing: %s", pidfile, strerror(errno)); fprintf(pidf, "%ld\n", (long)pidt); fclose(pidf); } set_namespaces(); execv(startas, argv); fatal("Unable to start %s: %s", startas, strerror(errno)); } frr-7.2.1/tools/subdir.am0000644000000000000000000000166313610377563012171 00000000000000# # tools # noinst_PROGRAMS += \ tools/permutations \ tools/gen_northbound_callbacks \ tools/gen_yang_deviations \ # end sbin_PROGRAMS += tools/ssd sbin_SCRIPTS += \ tools/frr-reload \ tools/frr-reload.py \ tools/frr \ \ tools/frrcommon.sh \ tools/frrinit.sh \ tools/watchfrr.sh \ # end tools_permutations_SOURCES = tools/permutations.c tools_permutations_LDADD = lib/libfrr.la tools_gen_northbound_callbacks_SOURCES = tools/gen_northbound_callbacks.c tools_gen_northbound_callbacks_LDADD = lib/libfrr.la $(LIBYANG_LIBS) tools_gen_yang_deviations_SOURCES = tools/gen_yang_deviations.c tools_gen_yang_deviations_LDADD = lib/libfrr.la $(LIBYANG_LIBS) tools_ssd_SOURCES = tools/start-stop-daemon.c EXTRA_DIST += \ tools/etc \ tools/frr-reload \ tools/frr-reload.py \ tools/frr.service \ tools/multiple-bgpd.sh \ tools/rrcheck.pl \ tools/rrlookup.pl \ tools/zc.pl \ tools/zebra.el \ tools/build-debian-package.sh \ # end frr-7.2.1/tools/watchfrr.sh.in0000644000000000000000000000133513610377563013137 00000000000000#!/bin/bash # # This is NOT the init script! This is the watchfrr start/stop/restart # command handler, passed to watchfrr with the -s/-r/-k commands. It is used # internally by watchfrr to start the protocol daemons with the appropriate # options. # # This script should be installed in @CFG_SBIN@/watchfrr.sh log_success_msg() { : } log_warning_msg() { echo "$@" >&2 [ -x /usr/bin/logger ] && echo "$@" \ | /usr/bin/logger -t watchfrr.sh -p daemon.warn } log_failure_msg() { echo "$@" >&2 [ -x /usr/bin/logger ] && echo "$@" \ | /usr/bin/logger -t watchfrr.sh -p daemon.err } self="`dirname $0`" if [ -r "$self/frrcommon.sh" ]; then . "$self/frrcommon.sh" else . "@CFG_SBIN@/frrcommon.sh" fi frrcommon_main "$@" frr-7.2.1/tools/zc.pl0000755000000000000000000000533613610377563011337 00000000000000#!/usr/bin/env perl ## ## Zebra interactive console ## Copyright (C) 2000 Vladimir B. Grebenschikov ## ## This file is part of GNU Zebra. ## ## GNU Zebra is free software; you can redistribute it and/or modify it ## under the terms of the GNU General Public License as published by the ## Free Software Foundation; either version 2, or (at your option) any ## later version. ## ## GNU Zebra is distributed in the hope that it will be useful, but ## WITHOUT ANY WARRANTY; without even the implied warranty of ## MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU ## General Public License for more details. ## ## You should have received a copy of the GNU General Public License ## along with GNU Zebra; see the file COPYING. If not, write to the ## Free Software Foundation, Inc., 59 Temple Place - Suite 330, ## Boston, MA 02111-1307, USA. use Net::Telnet (); use Getopt::Std; #use strict; my $host = `hostname -s`; $host =~ s/\s//g; my $port = 'zebra'; my $server = 'localhost'; # Check arguments &getopts ('l:e:czborh'); &usage () if $opt_h; # main { my $login_pass = $opt_l || $ENV{ZEBRA_PASSWORD} || 'zebra'; my $enable_pass = $opt_e || $ENV{ZEBRA_ENABLE} || ''; my $port = ($opt_z ? 'zebra' : 0) || ($opt_b ? 'bgpd' : 0) || ($opt_o ? 'ospfd' : 0) || ($opt_r ? 'ripd' : 0) || 'zebra'; my $cmd = join (' ', @ARGV); my $t = new Net::Telnet (Timeout => 10, Prompt => '/[\>\#] $/', Port => $port); $t->open ($server); $t->cmd ($login_pass); if ($enable_pass) { $t->cmd (String => 'en', Prompt => '/Password: /'); $t->cmd ($enable_pass); } $t->cmd ('conf t') if "$opt_c"; if ($cmd) { docmd ($t, $cmd); exit (0); } my $prompt = sprintf ("%s%s# ", $host, ($port eq 'zebra') ? '' : "/$port"); print "\nZEBRA interactive console ($port)\n\n" if -t STDIN; while (1) { $| = 1; print $prompt if -t STDIN; chomp ($cmd = <>); if (!defined ($cmd)) { print "\n" if -t STDIN; exit(0); } exit (0) if ($cmd eq 'q' || $cmd eq 'quit'); docmd ($t, $cmd) if $cmd !~ /^\s*$/; } exit(0); } sub docmd { my ($t, $cmd) = @_; my @lines = $t->cmd ($cmd); print join ('', grep (!/[\>\#] $/, @lines)), "\n"; } sub usage { print "USAGE: $0 [-l LOGIN_PASSWORD] [-e ENABLE_PASSWORD] [-z|-b|-o|-r|-h] []\n", "\t-l - specify login password\n", "\t-e - specify enable password\n", "\t-c - execute command in configure mode\n", "\t-z - connect to zebra daemon\n", "\t-b - connect to bgpd daemon\n", "\t-o - connect to ospfd daemon\n", "\t-r - connect to ripd daemon\n", "\t-h - help\n"; exit (1); } frr-7.2.1/tools/zebra.el0000644000000000000000000000620013610377563011777 00000000000000;; -*- lisp -*- ;;; zebra-mode.el -- major mode for editing zebra configuration file. ;; Copyright (C) 1998 Kunihiro Ishiguro ;; Author: 1998 Kunihiro Ishiguro ;; SeonMeyong HEO ;; Maintainer: kunihiro@zebra.org ;; seirios@Matrix.IRI.Co.JP ;; Created: Jan 28 1998 ;; Version: Alpha 0.2 ;; Keywords: zebra bgpd ripd ripngd languages ;; You can get the latest version of zebra from ;; ;; http://www.zebra.org/ ;; ;; Install this Emacs Lisp code ;; ;; Compile zebra.el ;; % $(EMACS) -batch -f batch-byte-compile zebra.el ;; Install zebra.el,zebra.elc to Emacs-load-path ;; % cp zebra.el zebra.elc $(emacs-load-path) ;; Add .emacs or (site-load.el | site-start.el) ;; (auto-load 'zebra-mode "zebra" nil t) ;; (auto-load 'bgp-mode "zebra" nil t) ;; (auto-load 'rip-mode "zebra" nil t) ;; ;;; Code: ;; Set keywords (defvar zebra-font-lock-keywords (list '("#.*$" . font-lock-comment-face) '("!.*$" . font-lock-comment-face) '("no\\|interface" . font-lock-type-face) '("ip6\\|ip\\|route\\|address" . font-lock-function-name-face) '("ipforward\\|ipv6forward" . font-lock-keyword-face) '("hostname\\|password\\|enable\\|logfile\\|no" . font-lock-keyword-face)) "Default value to highlight in zebra mode.") (defvar bgp-font-lock-keywords (list '("#.*$" . font-lock-comment-face) '("!.*$" . font-lock-comment-face) '("no\\|router" . font-lock-type-face) '("bgp\\|router-id\\|neighbor\\|network" . font-lock-function-name-face) '("ebgp\\|multihop\\|next\\|zebra\\|remote-as" . font-lock-keyword-face) '("hostname\\|password\\|enable\\|logfile\\|no" . font-lock-keyword-face)) "Default value to highlight in bgp mode.") (defvar rip-font-lock-keywords (list '("#.*$" . font-lock-comment-face) '("!.*$" . font-lock-comment-face) '("no\\|router\\|interface\\|ipv6\\|ip6\\|ip" . font-lock-type-face) '("ripng\\|rip\\|recive\\|advertize\\|accept" . font-lock-function-name-face) '("version\\|network" . font-lock-function-name-face) '("default\\|none\\|zebra" . font-lock-keyword-face) '("hostname\\|password\\|enable\\|logfile\\|no" . font-lock-keyword-face)) "Default value to highlight in bgp mode.") ;; set font-lock-mode (defun zebra-font-lock () (make-local-variable 'font-lock-defaults) (setq font-lock-defaults '(zebra-font-lock-keywords nil t))) (defun bgp-font-lock () (make-local-variable 'font-lock-defaults) (setq font-lock-defaults '(bgp-font-lock-keywords nil t))) (defun rip-font-lock () (make-local-variable 'font-lock-defaults) (setq font-lock-defaults '(rip-font-lock-keywords nil t))) ;; define Major mode (defun major-mode-define () (interactive) (progn (setq comment-start "[#!]" comment-end "" comment-start-skip "!+ ") (run-hooks 'zebra-mode-hook) (cond ((string< "20" emacs-version) (font-lock-mode))))) (defun zebra-mode () (progn (setq mode-name "zebra") (zebra-font-lock)) (major-mode-define)) (defun bgp-mode () (progn (setq mode-name "bgp") (bgp-font-lock)) (major-mode-define)) (defun rip-mode () (progn (setq mode-name "rip") (rip-font-lock)) (major-mode-define)) frr-7.2.1/vrrpd/0000755000000000000000000000000013610377563010431 500000000000000frr-7.2.1/vrrpd/Makefile0000644000000000000000000000021713610377563012011 00000000000000all: ALWAYS @$(MAKE) -s -C .. vrrp/vrrp %: ALWAYS @$(MAKE) -s -C .. vrrp/$@ Makefile: #nothing ALWAYS: .PHONY: ALWAYS makefiles .SUFFIXES: frr-7.2.1/vrrpd/subdir.am0000644000000000000000000000137713610377563012170 00000000000000# # vrrpd # if VRRPD noinst_LIBRARIES += vrrpd/libvrrp.a sbin_PROGRAMS += vrrpd/vrrpd # dist_examples_DATA += staticd/staticd.conf.sample vtysh_scan += $(top_srcdir)/vrrpd/vrrp_vty.c man8 += $(MANBUILD)/frr-vrrpd.8 endif vrrpd_libvrrp_a_SOURCES = \ vrrpd/vrrp.c \ vrrpd/vrrp_arp.c \ vrrpd/vrrp_debug.c \ vrrpd/vrrp_ndisc.c \ vrrpd/vrrp_packet.c \ vrrpd/vrrp_vty.c \ vrrpd/vrrp_zebra.c \ # end noinst_HEADERS += \ vrrpd/vrrp.h \ vrrpd/vrrp_arp.h \ vrrpd/vrrp_debug.h \ vrrpd/vrrp_ndisc.h \ vrrpd/vrrp_packet.h \ vrrpd/vrrp_vty.h \ vrrpd/vrrp_zebra.h \ # end vrrpd/vrrp_vty_clippy.c: $(CLIPPY_DEPS) vrrpd/vrrp_vty.$(OBJEXT): vrrpd/vrrp_vty_clippy.c vrrpd_vrrpd_SOURCES = vrrpd/vrrp_main.c vrrpd_vrrpd_LDADD = vrrpd/libvrrp.a lib/libfrr.la @LIBCAP@ frr-7.2.1/vrrpd/vrrp.c0000644000000000000000000017606013610377563011520 00000000000000/* * VRRP global definitions and state machine. * Copyright (C) 2018-2019 Cumulus Networks, Inc. * Quentin Young * * This program is free software; you can redistribute it and/or modify it * under the terms of the 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; see the file COPYING; if not, write to the Free Software * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA */ #include #include "lib/hash.h" #include "lib/hook.h" #include "lib/if.h" #include "lib/linklist.h" #include "lib/memory.h" #include "lib/network.h" #include "lib/prefix.h" #include "lib/sockopt.h" #include "lib/sockunion.h" #include "lib/vrf.h" #include "lib/vty.h" #include "vrrp.h" #include "vrrp_arp.h" #include "vrrp_debug.h" #include "vrrp_ndisc.h" #include "vrrp_packet.h" #include "vrrp_zebra.h" #define VRRP_LOGPFX "[CORE] " DEFINE_MTYPE_STATIC(VRRPD, VRRP_IP, "VRRP IP address") DEFINE_MTYPE_STATIC(VRRPD, VRRP_RTR, "VRRP Router") /* statics */ struct hash *vrrp_vrouters_hash; bool vrrp_autoconfig_is_on; int vrrp_autoconfig_version; struct vrrp_defaults vd; const char *vrrp_state_names[3] = { [VRRP_STATE_INITIALIZE] = "Initialize", [VRRP_STATE_MASTER] = "Master", [VRRP_STATE_BACKUP] = "Backup", }; const char *vrrp_event_names[2] = { [VRRP_EVENT_STARTUP] = "Startup", [VRRP_EVENT_SHUTDOWN] = "Shutdown", }; /* Utility functions ------------------------------------------------------- */ /* * Sets an ethaddr to RFC-defined Virtual Router MAC address. * * mac * ethaddr to set * * v6 * Whether this is a V6 or V4 Virtual Router MAC * * vrid * Virtual Router Identifier */ static void vrrp_mac_set(struct ethaddr *mac, bool v6, uint8_t vrid) { /* * V4: 00-00-5E-00-01-{VRID} * V6: 00-00-5E-00-02-{VRID} */ mac->octet[0] = 0x00; mac->octet[1] = 0x00; mac->octet[2] = 0x5E; mac->octet[3] = 0x00; mac->octet[4] = v6 ? 0x02 : 0x01; mac->octet[5] = vrid; } /* * Recalculates and sets skew_time and master_down_interval based * values. * * r * VRRP Router to operate on */ static void vrrp_recalculate_timers(struct vrrp_router *r) { uint16_t mdiadv = r->vr->version == 3 ? r->master_adver_interval : r->vr->advertisement_interval; uint16_t skm = (r->vr->version == 3) ? r->master_adver_interval : 100; r->skew_time = ((256 - r->vr->priority) * skm) / 256; r->master_down_interval = 3 * mdiadv; r->master_down_interval += r->skew_time; } /* * Determines if a VRRP router is the owner of the specified address. * * The determining factor for whether an interface is the address owner is * simply whether the address is assigned to the VRRP base interface by someone * other than vrrpd. * * This function should always return the correct answer regardless of * master/backup status. * * ifp * The interface to check owernship of. This should be the base interface of * a VRRP router. * * vr * Virtual Router * * Returns: * whether or not vr owns the specified address */ static bool vrrp_is_owner(struct interface *ifp, struct ipaddr *addr) { /* * This code sanity checks implicit ownership configuration. Ideally, * the way we determine address ownership status for this VRRP router * is by looking at whether our VIPs are also assigned to the base * interface, and therefore count as "real" addresses. This frees the * user from having to manually configure priority 255 to indicate * address ownership. However, this means one of the VIPs will be used * as the source address for VRRP advertisements, which in turn means * that other VRRP routers will be receiving packets with a source * address they themselves have. This causes lots of different issues * so for now we're disabling this and forcing the user to configure * priority 255 to indicate ownership. */ return false; #if 0 struct prefix p; p.family = IS_IPADDR_V4(addr) ? AF_INET : AF_INET6; p.prefixlen = IS_IPADDR_V4(addr) ? IPV4_MAX_BITLEN : IPV6_MAX_BITLEN; memcpy(&p.u, &addr->ip, sizeof(addr->ip)); return !!connected_lookup_prefix_exact(ifp, &p); #endif } /* * Whether an interface has a MAC address that matches the VRRP RFC. * * ifp * Interface to check * * Returns: * Whether the interface has a VRRP mac or not */ static bool vrrp_ifp_has_vrrp_mac(struct interface *ifp) { struct ethaddr vmac4; struct ethaddr vmac6; vrrp_mac_set(&vmac4, 0, 0x00); vrrp_mac_set(&vmac6, 1, 0x00); return !memcmp(ifp->hw_addr, vmac4.octet, sizeof(vmac4.octet) - 1) || !memcmp(ifp->hw_addr, vmac6.octet, sizeof(vmac6.octet) - 1); } /* * Lookup a Virtual Router instance given a macvlan subinterface. * * The VRID is extracted from the interface MAC and the 2-tuple (iface, vrid) * is used to look up any existing instances that match the interface. It does * not matter whether the instance is already bound to the interface or not. * * mvl_ifp * Interface pointer to use to lookup. Should be a macvlan device. * * Returns: * Virtual Router, if found * NULL otherwise */ static struct vrrp_vrouter *vrrp_lookup_by_if_mvl(struct interface *mvl_ifp) { struct interface *p; if (!mvl_ifp || mvl_ifp->link_ifindex == 0 || !vrrp_ifp_has_vrrp_mac(mvl_ifp)) { if (mvl_ifp && mvl_ifp->link_ifindex == 0) DEBUGD(&vrrp_dbg_zebra, VRRP_LOGPFX "Interface %s has no parent ifindex; disregarding", mvl_ifp->name); if (mvl_ifp && !vrrp_ifp_has_vrrp_mac(mvl_ifp)) DEBUGD(&vrrp_dbg_zebra, VRRP_LOGPFX "Interface %s has a non-VRRP MAC; disregarding", mvl_ifp->name); return NULL; } p = if_lookup_by_index(mvl_ifp->link_ifindex, VRF_DEFAULT); uint8_t vrid = mvl_ifp->hw_addr[5]; return vrrp_lookup(p, vrid); } /* * Lookup the Virtual Router instances configured on a particular interface. * * ifp * Interface pointer to use to lookup. Should not be a macvlan device. * * Returns: * List of virtual routers found */ static struct list *vrrp_lookup_by_if(struct interface *ifp) { struct list *l = hash_to_list(vrrp_vrouters_hash); struct listnode *ln, *nn; struct vrrp_vrouter *vr; for (ALL_LIST_ELEMENTS(l, ln, nn, vr)) if (vr->ifp != ifp) list_delete_node(l, ln); return l; } /* * Lookup any Virtual Router instances associated with a particular interface. * This is a combination of the results from vrrp_lookup_by_if_mvl and * vrrp_lookup_by_if. * * Suppose the system interface list looks like the following: * * eth0 * \- eth0-v0 00:00:5e:00:01:01 * \- eth0-v1 00:00:5e:00:02:01 * \- eth0-v2 00:00:5e:00:01:0a * * Passing eth0-v2 to this function will give you the VRRP instance configured * on eth0 with VRID 10. Passing eth0-v0 or eth0-v1 will give you the VRRP * instance configured on eth0 with VRID 1. Passing eth0 will give you both. * * ifp * Interface pointer to use to lookup. Can be any interface. * * Returns: * List of virtual routers found */ static struct list *vrrp_lookup_by_if_any(struct interface *ifp) { struct vrrp_vrouter *vr; struct list *vrs; vr = vrrp_lookup_by_if_mvl(ifp); vrs = vr ? list_new() : vrrp_lookup_by_if(ifp); if (vr) listnode_add(vrs, vr); return vrs; } /* Configuration controllers ----------------------------------------------- */ void vrrp_check_start(struct vrrp_vrouter *vr) { struct vrrp_router *r; bool start; const char *whynot = NULL; if (vr->shutdown || vr->ifp == NULL) return; r = vr->v4; /* Must not already be started */ start = r->fsm.state == VRRP_STATE_INITIALIZE; /* Must have a parent interface */ start = start && (vr->ifp != NULL); whynot = (!start && !whynot) ? "No base interface" : NULL; #if 0 /* Parent interface must be up */ start = start && if_is_operative(vr->ifp); #endif /* Parent interface must have at least one v4 */ start = start && vr->ifp->connected->count > 1; whynot = (!start && !whynot) ? "No primary IPv4 address" : NULL; /* Must have a macvlan interface */ start = start && (r->mvl_ifp != NULL); whynot = (!start && !whynot) ? "No VRRP interface" : NULL; #if 0 /* Macvlan interface must be admin up */ start = start && CHECK_FLAG(r->mvl_ifp->flags, IFF_UP); #endif /* Must have at least one VIP configured */ start = start && r->addrs->count > 0; whynot = (!start && !whynot) ? "No Virtual IP address configured" : NULL; if (start) vrrp_event(r, VRRP_EVENT_STARTUP); else if (whynot) zlog_warn(VRRP_LOGPFX VRRP_LOGPFX_VRID VRRP_LOGPFX_FAM "Refusing to start Virtual Router: %s", vr->vrid, family2str(r->family), whynot); r = vr->v6; /* Must not already be started */ start = r->fsm.state == VRRP_STATE_INITIALIZE; /* Must not be v2 */ start = start && vr->version != 2; whynot = (!start && !whynot) ? "VRRPv2 does not support v6" : NULL; /* Must have a parent interface */ start = start && (vr->ifp != NULL); whynot = (!start && !whynot) ? "No base interface" : NULL; #if 0 /* Parent interface must be up */ start = start && if_is_operative(vr->ifp); #endif /* Must have a macvlan interface */ start = start && (r->mvl_ifp != NULL); whynot = (!start && !whynot) ? "No VRRP interface" : NULL; #if 0 /* Macvlan interface must be admin up */ start = start && CHECK_FLAG(r->mvl_ifp->flags, IFF_UP); /* Macvlan interface must have a link local */ start = start && connected_get_linklocal(r->mvl_ifp); whynot = (!start && !whynot) ? "No link local address configured" : NULL; /* Macvlan interface must have a v6 IP besides the link local */ start = start && (r->mvl_ifp->connected->count >= 2); whynot = (!start && !whynot) ? "No Virtual IP configured on macvlan device" : NULL; #endif /* Must have at least one VIP configured */ start = start && r->addrs->count > 0; whynot = (!start && !whynot) ? "No Virtual IP address configured" : NULL; if (start) vrrp_event(r, VRRP_EVENT_STARTUP); else if (whynot) zlog_warn(VRRP_LOGPFX VRRP_LOGPFX_VRID VRRP_LOGPFX_FAM "Refusing to start Virtual Router: %s", vr->vrid, family2str(r->family), whynot); } void vrrp_set_priority(struct vrrp_vrouter *vr, uint8_t priority) { vr->priority = priority; vr->v4->priority = priority; vr->v6->priority = priority; } void vrrp_set_advertisement_interval(struct vrrp_vrouter *vr, uint16_t advertisement_interval) { if (vr->advertisement_interval == advertisement_interval) return; vr->advertisement_interval = advertisement_interval; vrrp_recalculate_timers(vr->v4); vrrp_recalculate_timers(vr->v6); } static bool vrrp_has_ip(struct vrrp_vrouter *vr, struct ipaddr *ip) { struct vrrp_router *r = ip->ipa_type == IPADDR_V4 ? vr->v4 : vr->v6; struct listnode *ln; struct ipaddr *iter; for (ALL_LIST_ELEMENTS_RO(r->addrs, ln, iter)) if (!memcmp(&iter->ip, &ip->ip, IPADDRSZ(ip))) return true; return false; } int vrrp_add_ip(struct vrrp_router *r, struct ipaddr *ip) { int af = (ip->ipa_type == IPADDR_V6) ? AF_INET6 : AF_INET; assert(r->family == af); assert(!(r->vr->version == 2 && ip->ipa_type == IPADDR_V6)); if (vrrp_has_ip(r->vr, ip)) return 0; if (!vrrp_is_owner(r->vr->ifp, ip) && r->is_owner) { char ipbuf[INET6_ADDRSTRLEN]; inet_ntop(r->family, &ip->ip, ipbuf, sizeof(ipbuf)); zlog_err( VRRP_LOGPFX VRRP_LOGPFX_VRID VRRP_LOGPFX_FAM "This VRRP router is not the address owner of %s, but is the address owner of other addresses; this config is unsupported.", r->vr->vrid, family2str(r->family), ipbuf); return -1; } struct ipaddr *new = XCALLOC(MTYPE_VRRP_IP, sizeof(struct ipaddr)); *new = *ip; listnode_add(r->addrs, new); if (r->fsm.state == VRRP_STATE_MASTER) { switch (r->family) { case AF_INET: vrrp_garp_send(r, &new->ipaddr_v4); break; case AF_INET6: vrrp_ndisc_una_send(r, new); break; } } return 0; } int vrrp_add_ipv4(struct vrrp_vrouter *vr, struct in_addr v4) { struct ipaddr ip; ip.ipa_type = IPADDR_V4; ip.ipaddr_v4 = v4; return vrrp_add_ip(vr->v4, &ip); } int vrrp_add_ipv6(struct vrrp_vrouter *vr, struct in6_addr v6) { assert(vr->version != 2); struct ipaddr ip; ip.ipa_type = IPADDR_V6; ip.ipaddr_v6 = v6; return vrrp_add_ip(vr->v6, &ip); } int vrrp_del_ip(struct vrrp_router *r, struct ipaddr *ip) { struct listnode *ln, *nn; struct ipaddr *iter; int ret = 0; if (!vrrp_has_ip(r->vr, ip)) return 0; for (ALL_LIST_ELEMENTS(r->addrs, ln, nn, iter)) if (!memcmp(&iter->ip, &ip->ip, IPADDRSZ(ip))) list_delete_node(r->addrs, ln); /* * NB: Deleting the last address and then issuing a shutdown will cause * transmission of a priority 0 VRRP Advertisement - as per the RFC - * but it will have no addresses. This is not forbidden in the RFC but * might confuse other implementations. */ if (r->addrs->count == 0 && r->fsm.state != VRRP_STATE_INITIALIZE) ret = vrrp_event(r, VRRP_EVENT_SHUTDOWN); return ret; } int vrrp_del_ipv6(struct vrrp_vrouter *vr, struct in6_addr v6) { struct ipaddr ip; ip.ipa_type = IPADDR_V6; ip.ipaddr_v6 = v6; return vrrp_del_ip(vr->v6, &ip); } int vrrp_del_ipv4(struct vrrp_vrouter *vr, struct in_addr v4) { struct ipaddr ip; ip.ipa_type = IPADDR_V4; ip.ipaddr_v4 = v4; return vrrp_del_ip(vr->v4, &ip); } /* Creation and destruction ------------------------------------------------ */ static void vrrp_router_addr_list_del_cb(void *val) { struct ipaddr *ip = val; XFREE(MTYPE_VRRP_IP, ip); } /* * Search for a suitable macvlan subinterface we can attach to, and if found, * attach to it. * * r * Router to attach to interface * * Returns: * Whether an interface was successfully attached */ static bool vrrp_attach_interface(struct vrrp_router *r) { /* Search for existing interface with computed MAC address */ struct interface **ifps; size_t ifps_cnt = if_lookup_by_hwaddr( r->vmac.octet, sizeof(r->vmac.octet), &ifps, VRF_DEFAULT); /* * Filter to only those macvlan interfaces whose parent is the base * interface this VRRP router is configured on. * * If there are still multiple interfaces we just select the first one, * as it should be functionally identical to the others. */ unsigned int candidates = 0; struct interface *selection = NULL; for (unsigned int i = 0; i < ifps_cnt; i++) { if (ifps[i]->link_ifindex != r->vr->ifp->ifindex) ifps[i] = NULL; else { selection = selection ? selection : ifps[i]; candidates++; } } if (ifps_cnt) XFREE(MTYPE_TMP, ifps); char ethstr[ETHER_ADDR_STRLEN]; prefix_mac2str(&r->vmac, ethstr, sizeof(ethstr)); assert(!!selection == !!candidates); if (candidates == 0) zlog_warn(VRRP_LOGPFX VRRP_LOGPFX_VRID VRRP_LOGPFX_FAM "Interface: None (no interface found w/ MAC %s)", r->vr->vrid, family2str(r->family), ethstr); else if (candidates > 1) zlog_warn(VRRP_LOGPFX VRRP_LOGPFX_VRID VRRP_LOGPFX_FAM "Interface: Multiple interfaces found; using %s", r->vr->vrid, family2str(r->family), selection->name); else zlog_info(VRRP_LOGPFX VRRP_LOGPFX_VRID VRRP_LOGPFX_FAM "Interface: %s", r->vr->vrid, family2str(r->family), selection->name); r->mvl_ifp = selection; return !!r->mvl_ifp; } static struct vrrp_router *vrrp_router_create(struct vrrp_vrouter *vr, int family) { struct vrrp_router *r = XCALLOC(MTYPE_VRRP_RTR, sizeof(struct vrrp_router)); r->family = family; r->sock_rx = -1; r->sock_tx = -1; r->vr = vr; r->addrs = list_new(); r->addrs->del = vrrp_router_addr_list_del_cb; r->priority = vr->priority; r->fsm.state = VRRP_STATE_INITIALIZE; vrrp_mac_set(&r->vmac, family == AF_INET6, vr->vrid); vrrp_attach_interface(r); return r; } static void vrrp_router_destroy(struct vrrp_router *r) { if (r->is_active) vrrp_event(r, VRRP_EVENT_SHUTDOWN); if (r->sock_rx >= 0) close(r->sock_rx); if (r->sock_tx >= 0) close(r->sock_tx); /* FIXME: also delete list elements */ list_delete(&r->addrs); XFREE(MTYPE_VRRP_RTR, r); } struct vrrp_vrouter *vrrp_vrouter_create(struct interface *ifp, uint8_t vrid, uint8_t version) { struct vrrp_vrouter *vr = vrrp_lookup(ifp, vrid); if (vr) return vr; if (version != 2 && version != 3) return NULL; vr = XCALLOC(MTYPE_VRRP_RTR, sizeof(struct vrrp_vrouter)); vr->ifp = ifp; vr->version = version; vr->vrid = vrid; vr->priority = vd.priority; vr->preempt_mode = vd.preempt_mode; vr->accept_mode = vd.accept_mode; vr->shutdown = vd.shutdown; vr->v4 = vrrp_router_create(vr, AF_INET); vr->v6 = vrrp_router_create(vr, AF_INET6); vrrp_set_advertisement_interval(vr, vd.advertisement_interval); hash_get(vrrp_vrouters_hash, vr, hash_alloc_intern); return vr; } void vrrp_vrouter_destroy(struct vrrp_vrouter *vr) { vrrp_router_destroy(vr->v4); vrrp_router_destroy(vr->v6); hash_release(vrrp_vrouters_hash, vr); XFREE(MTYPE_VRRP_RTR, vr); } struct vrrp_vrouter *vrrp_lookup(struct interface *ifp, uint8_t vrid) { struct vrrp_vrouter vr; vr.vrid = vrid; vr.ifp = ifp; return hash_lookup(vrrp_vrouters_hash, &vr); } /* Network ----------------------------------------------------------------- */ /* Forward decls */ static void vrrp_change_state(struct vrrp_router *r, int to); static int vrrp_adver_timer_expire(struct thread *thread); static int vrrp_master_down_timer_expire(struct thread *thread); /* * Finds the first connected address of the appropriate family on a VRRP * router's interface and binds the Tx socket of the VRRP router to that * address. * * Also sets src field of vrrp_router. * * r * VRRP router to operate on * * Returns: * 0 on success * -1 on failure */ static int vrrp_bind_to_primary_connected(struct vrrp_router *r) { char ipstr[INET6_ADDRSTRLEN]; struct interface *ifp; /* * A slight quirk: the RFC specifies that advertisements under IPv6 must * be transmitted using the link local address of the source interface */ ifp = r->family == AF_INET ? r->vr->ifp : r->mvl_ifp; struct listnode *ln; struct connected *c = NULL; for (ALL_LIST_ELEMENTS_RO(ifp->connected, ln, c)) if (c->address->family == r->family) { if (r->family == AF_INET6 && IN6_IS_ADDR_LINKLOCAL(&c->address->u.prefix6)) break; else if (r->family == AF_INET) break; } if (c == NULL) { zlog_err(VRRP_LOGPFX VRRP_LOGPFX_VRID VRRP_LOGPFX_FAM "Failed to find address to bind on %s", r->vr->vrid, family2str(r->family), ifp->name); return -1; } union sockunion su; memset(&su, 0x00, sizeof(su)); switch (r->family) { case AF_INET: r->src.ipa_type = IPADDR_V4; r->src.ipaddr_v4 = c->address->u.prefix4; su.sin.sin_family = AF_INET; su.sin.sin_addr = c->address->u.prefix4; break; case AF_INET6: r->src.ipa_type = IPADDR_V6; r->src.ipaddr_v6 = c->address->u.prefix6; su.sin6.sin6_family = AF_INET6; su.sin6.sin6_scope_id = ifp->ifindex; su.sin6.sin6_addr = c->address->u.prefix6; break; } int ret = 0; sockopt_reuseaddr(r->sock_tx); if (bind(r->sock_tx, (const struct sockaddr *)&su, sizeof(su)) < 0) { zlog_err( VRRP_LOGPFX VRRP_LOGPFX_VRID VRRP_LOGPFX_FAM "Failed to bind Tx socket to primary IP address %s: %s", r->vr->vrid, family2str(r->family), inet_ntop(r->family, (const void *)&c->address->u.prefix, ipstr, sizeof(ipstr)), safe_strerror(errno)); ret = -1; } else { DEBUGD(&vrrp_dbg_sock, VRRP_LOGPFX VRRP_LOGPFX_VRID VRRP_LOGPFX_FAM "Bound Tx socket to primary IP address %s", r->vr->vrid, family2str(r->family), inet_ntop(r->family, (const void *)&c->address->u.prefix, ipstr, sizeof(ipstr))); } return ret; } /* * Create and multicast a VRRP ADVERTISEMENT message. * * r * VRRP Router for which to send ADVERTISEMENT */ static void vrrp_send_advertisement(struct vrrp_router *r) { struct vrrp_pkt *pkt; ssize_t pktsz; struct ipaddr *addrs[r->addrs->count]; union sockunion dest; if (r->src.ipa_type == IPADDR_NONE && vrrp_bind_to_primary_connected(r) < 0) return; list_to_array(r->addrs, (void **)addrs, r->addrs->count); pktsz = vrrp_pkt_adver_build(&pkt, &r->src, r->vr->version, r->vr->vrid, r->priority, r->vr->advertisement_interval, r->addrs->count, (struct ipaddr **)&addrs); if (DEBUG_MODE_CHECK(&vrrp_dbg_pkt, DEBUG_MODE_ALL)) zlog_hexdump(pkt, (size_t)pktsz); const char *group = r->family == AF_INET ? VRRP_MCASTV4_GROUP_STR : VRRP_MCASTV6_GROUP_STR; (void)str2sockunion(group, &dest); ssize_t sent = sendto(r->sock_tx, pkt, (size_t)pktsz, 0, &dest.sa, sockunion_sizeof(&dest)); vrrp_pkt_free(pkt); if (sent < 0) { zlog_warn(VRRP_LOGPFX VRRP_LOGPFX_VRID VRRP_LOGPFX_FAM "Failed to send VRRP Advertisement: %s", r->vr->vrid, family2str(r->family), safe_strerror(errno)); } else { ++r->stats.adver_tx_cnt; } } /* * Receive and parse VRRP advertisement. * * By the time we get here all fields have been validated for basic correctness * and the packet is a valid VRRP packet. * * However, we have not validated whether the VRID is correct for this virtual * router, nor whether the priority is correct (i.e. is not 255 when we are the * address owner), nor whether the advertisement interval equals our own * configured value (this check is only performed in VRRPv2). * * r * VRRP Router associated with the socket this advertisement was received on * * src * Source address of sender * * pkt * The advertisement they sent * * pktsize * Size of advertisement * * Returns: * -1 if advertisement is invalid * 0 otherwise */ static int vrrp_recv_advertisement(struct vrrp_router *r, struct ipaddr *src, struct vrrp_pkt *pkt, size_t pktsize) { char sipstr[INET6_ADDRSTRLEN]; char dipstr[INET6_ADDRSTRLEN]; ipaddr2str(src, sipstr, sizeof(sipstr)); ipaddr2str(&r->src, dipstr, sizeof(dipstr)); char dumpbuf[BUFSIZ]; vrrp_pkt_adver_dump(dumpbuf, sizeof(dumpbuf), pkt); DEBUGD(&vrrp_dbg_proto, VRRP_LOGPFX VRRP_LOGPFX_VRID VRRP_LOGPFX_FAM "Received VRRP Advertisement from %s:\n%s", r->vr->vrid, family2str(r->family), sipstr, dumpbuf); /* Check that VRID matches our configured VRID */ if (pkt->hdr.vrid != r->vr->vrid) { DEBUGD(&vrrp_dbg_proto, VRRP_LOGPFX VRRP_LOGPFX_VRID VRRP_LOGPFX_FAM "Datagram invalid: Advertisement contains VRID %" PRIu8 " which does not match our instance", r->vr->vrid, family2str(r->family), pkt->hdr.vrid); return -1; } /* Verify that we are not the IPvX address owner */ if (r->is_owner) { DEBUGD(&vrrp_dbg_proto, VRRP_LOGPFX VRRP_LOGPFX_VRID VRRP_LOGPFX_FAM "Datagram invalid: Received advertisement but we are the address owner", r->vr->vrid, family2str(r->family)); return -1; } /* If v2, verify that adver time matches ours */ bool adveq = (pkt->hdr.v2.adver_int == MAX(r->vr->advertisement_interval / 100, 1)); if (r->vr->version == 2 && !adveq) { DEBUGD(&vrrp_dbg_proto, VRRP_LOGPFX VRRP_LOGPFX_VRID VRRP_LOGPFX_FAM "Datagram invalid: Received advertisement with advertisement interval %" PRIu8 " unequal to our configured value %u", r->vr->vrid, family2str(r->family), pkt->hdr.v2.adver_int, MAX(r->vr->advertisement_interval / 100, 1)); return -1; } /* Check that # IPs received matches our # configured IPs */ if (pkt->hdr.naddr != r->addrs->count) DEBUGD(&vrrp_dbg_proto, VRRP_LOGPFX VRRP_LOGPFX_VRID VRRP_LOGPFX_FAM "Datagram has %" PRIu8 " addresses, but this VRRP instance has %u", r->vr->vrid, family2str(r->family), pkt->hdr.naddr, r->addrs->count); ++r->stats.adver_rx_cnt; int addrcmp; switch (r->fsm.state) { case VRRP_STATE_MASTER: addrcmp = memcmp(&src->ip, &r->src.ip, IPADDRSZ(src)); if (pkt->hdr.priority == 0) { vrrp_send_advertisement(r); THREAD_OFF(r->t_adver_timer); thread_add_timer_msec( master, vrrp_adver_timer_expire, r, r->vr->advertisement_interval * 10, &r->t_adver_timer); } else if (pkt->hdr.priority > r->priority || ((pkt->hdr.priority == r->priority) && addrcmp > 0)) { zlog_info( VRRP_LOGPFX VRRP_LOGPFX_VRID VRRP_LOGPFX_FAM "Received advertisement from %s w/ priority %" PRIu8 "; switching to Backup", r->vr->vrid, family2str(r->family), sipstr, pkt->hdr.priority); THREAD_OFF(r->t_adver_timer); if (r->vr->version == 3) { r->master_adver_interval = htons(pkt->hdr.v3.adver_int); } vrrp_recalculate_timers(r); THREAD_OFF(r->t_master_down_timer); thread_add_timer_msec(master, vrrp_master_down_timer_expire, r, r->master_down_interval * 10, &r->t_master_down_timer); vrrp_change_state(r, VRRP_STATE_BACKUP); } else { /* Discard advertisement */ DEBUGD(&vrrp_dbg_proto, VRRP_LOGPFX VRRP_LOGPFX_VRID VRRP_LOGPFX_FAM "Discarding advertisement from %s (%" PRIu8 " <= %" PRIu8 " & %s <= %s)", r->vr->vrid, family2str(r->family), sipstr, pkt->hdr.priority, r->priority, sipstr, dipstr); } break; case VRRP_STATE_BACKUP: if (pkt->hdr.priority == 0) { THREAD_OFF(r->t_master_down_timer); thread_add_timer_msec( master, vrrp_master_down_timer_expire, r, r->skew_time * 10, &r->t_master_down_timer); } else if (r->vr->preempt_mode == false || pkt->hdr.priority >= r->priority) { if (r->vr->version == 3) { r->master_adver_interval = ntohs(pkt->hdr.v3.adver_int); } vrrp_recalculate_timers(r); THREAD_OFF(r->t_master_down_timer); thread_add_timer_msec(master, vrrp_master_down_timer_expire, r, r->master_down_interval * 10, &r->t_master_down_timer); } else if (r->vr->preempt_mode == true && pkt->hdr.priority < r->priority) { /* Discard advertisement */ DEBUGD(&vrrp_dbg_proto, VRRP_LOGPFX VRRP_LOGPFX_VRID VRRP_LOGPFX_FAM "Discarding advertisement from %s (%" PRIu8 " < %" PRIu8 " & preempt = true)", r->vr->vrid, family2str(r->family), sipstr, pkt->hdr.priority, r->priority); } break; case VRRP_STATE_INITIALIZE: zlog_err(VRRP_LOGPFX VRRP_LOGPFX_VRID VRRP_LOGPFX_FAM "Received ADVERTISEMENT in state %s; this is a bug", r->vr->vrid, family2str(r->family), vrrp_state_names[r->fsm.state]); break; } return 0; } /* * Read and process next IPvX datagram. */ static int vrrp_read(struct thread *thread) { struct vrrp_router *r = thread->arg; struct vrrp_pkt *pkt; ssize_t pktsize; ssize_t nbytes; bool resched; char errbuf[BUFSIZ]; struct sockaddr_storage sa; uint8_t control[64]; struct ipaddr src = {}; struct msghdr m = {}; struct iovec iov; iov.iov_base = r->ibuf; iov.iov_len = sizeof(r->ibuf); m.msg_name = &sa; m.msg_namelen = sizeof(sa); m.msg_iov = &iov; m.msg_iovlen = 1; m.msg_control = control; m.msg_controllen = sizeof(control); nbytes = recvmsg(r->sock_rx, &m, MSG_DONTWAIT); if ((nbytes < 0 && ERRNO_IO_RETRY(errno))) { resched = true; goto done; } else if (nbytes <= 0) { vrrp_event(r, VRRP_EVENT_SHUTDOWN); resched = false; goto done; } if (DEBUG_MODE_CHECK(&vrrp_dbg_pkt, DEBUG_MODE_ALL)) { DEBUGD(&vrrp_dbg_pkt, VRRP_LOGPFX VRRP_LOGPFX_VRID VRRP_LOGPFX_FAM "Datagram rx: ", r->vr->vrid, family2str(r->family)); zlog_hexdump(r->ibuf, nbytes); } pktsize = vrrp_pkt_parse_datagram(r->family, r->vr->version, &m, nbytes, &src, &pkt, errbuf, sizeof(errbuf)); if (pktsize < 0) DEBUGD(&vrrp_dbg_pkt, VRRP_LOGPFX VRRP_LOGPFX_VRID VRRP_LOGPFX_FAM "Datagram invalid: %s", r->vr->vrid, family2str(r->family), errbuf); else vrrp_recv_advertisement(r, &src, pkt, pktsize); resched = true; done: memset(r->ibuf, 0x00, sizeof(r->ibuf)); if (resched) thread_add_read(master, vrrp_read, r, r->sock_rx, &r->t_read); return 0; } /* * Creates and configures VRRP router sockets. * * This function: * - Creates two sockets, one for Tx, one for Rx * - Joins the Rx socket to the appropriate VRRP multicast group * - Sets the Tx socket to set the TTL (v4) or Hop Limit (v6) field to 255 for * all transmitted IPvX packets * - Requests the kernel to deliver IPv6 header values needed to validate VRRP * packets * * If any of the above fail, the sockets are closed. The only exception is if * the TTL / Hop Limit settings fail; these are logged, but configuration * proceeds. * * The first connected address on the Virtual Router's interface is used as the * interface address. * * r * VRRP Router for which to create listen socket * * Returns: * 0 on success * -1 on failure */ static int vrrp_socket(struct vrrp_router *r) { int ret; bool failed = false; frr_with_privs(&vrrp_privs) { r->sock_rx = socket(r->family, SOCK_RAW, IPPROTO_VRRP); r->sock_tx = socket(r->family, SOCK_RAW, IPPROTO_VRRP); } if (r->sock_rx < 0 || r->sock_tx < 0) { const char *rxtx = r->sock_rx < 0 ? "Rx" : "Tx"; zlog_warn(VRRP_LOGPFX VRRP_LOGPFX_VRID VRRP_LOGPFX_FAM "Can't create VRRP %s socket", r->vr->vrid, family2str(r->family), rxtx); failed = true; goto done; } /* Configure sockets */ if (r->family == AF_INET) { /* Set Tx socket to always Tx with TTL set to 255 */ int ttl = 255; ret = setsockopt(r->sock_tx, IPPROTO_IP, IP_MULTICAST_TTL, &ttl, sizeof(ttl)); if (ret < 0) { zlog_warn( VRRP_LOGPFX VRRP_LOGPFX_VRID VRRP_LOGPFX_FAM "Failed to set outgoing multicast TTL count to 255; RFC 5798 compliant implementations will drop our packets", r->vr->vrid, family2str(r->family)); } /* Set Tx socket DSCP byte */ setsockopt_ipv4_tos(r->sock_tx, IPTOS_PREC_INTERNETCONTROL); /* Turn off multicast loop on Tx */ setsockopt_ipv4_multicast_loop(r->sock_tx, 0); /* Bind Rx socket to exact interface */ frr_with_privs(&vrrp_privs) { ret = setsockopt(r->sock_rx, SOL_SOCKET, SO_BINDTODEVICE, r->vr->ifp->name, strlen(r->vr->ifp->name)); } if (ret) { zlog_warn(VRRP_LOGPFX VRRP_LOGPFX_VRID VRRP_LOGPFX_FAM "Failed to bind Rx socket to %s: %s", r->vr->vrid, family2str(r->family), r->vr->ifp->name, safe_strerror(errno)); failed = true; goto done; } DEBUGD(&vrrp_dbg_sock, VRRP_LOGPFX VRRP_LOGPFX_VRID VRRP_LOGPFX_FAM "Bound Rx socket to %s", r->vr->vrid, family2str(r->family), r->vr->ifp->name); /* Bind Rx socket to v4 multicast address */ struct sockaddr_in sa = {0}; sa.sin_family = AF_INET; sa.sin_addr.s_addr = htonl(VRRP_MCASTV4_GROUP); if (bind(r->sock_rx, (struct sockaddr *)&sa, sizeof(sa))) { zlog_err( VRRP_LOGPFX VRRP_LOGPFX_VRID VRRP_LOGPFX_FAM "Failed to bind Rx socket to VRRP multicast group: %s", r->vr->vrid, family2str(r->family), safe_strerror(errno)); failed = true; goto done; } DEBUGD(&vrrp_dbg_sock, VRRP_LOGPFX VRRP_LOGPFX_VRID VRRP_LOGPFX_FAM "Bound Rx socket to VRRP multicast group", r->vr->vrid, family2str(r->family)); /* Join Rx socket to VRRP IPv4 multicast group */ assert(listhead(r->vr->ifp->connected)); struct connected *c = listhead(r->vr->ifp->connected)->data; struct in_addr v4 = c->address->u.prefix4; ret = setsockopt_ipv4_multicast(r->sock_rx, IP_ADD_MEMBERSHIP, v4, htonl(VRRP_MCASTV4_GROUP), r->vr->ifp->ifindex); if (ret < 0) { zlog_warn(VRRP_LOGPFX VRRP_LOGPFX_VRID "Failed to join VRRP %s multicast group", r->vr->vrid, family2str(r->family)); failed = true; goto done; } DEBUGD(&vrrp_dbg_sock, VRRP_LOGPFX VRRP_LOGPFX_VRID VRRP_LOGPFX_FAM "Joined VRRP multicast group", r->vr->vrid, family2str(r->family)); /* Set outgoing interface for advertisements */ struct ip_mreqn mreqn = {}; mreqn.imr_ifindex = r->mvl_ifp->ifindex; ret = setsockopt(r->sock_tx, IPPROTO_IP, IP_MULTICAST_IF, (void *)&mreqn, sizeof(mreqn)); if (ret < 0) { zlog_warn( VRRP_LOGPFX VRRP_LOGPFX_VRID VRRP_LOGPFX_FAM "Could not set %s as outgoing multicast interface", r->vr->vrid, family2str(r->family), r->mvl_ifp->name); failed = true; goto done; } DEBUGD(&vrrp_dbg_sock, VRRP_LOGPFX VRRP_LOGPFX_VRID VRRP_LOGPFX_FAM "Set %s as outgoing multicast interface", r->vr->vrid, family2str(r->family), r->mvl_ifp->name); /* Select and bind source address */ if (vrrp_bind_to_primary_connected(r) < 0) { failed = true; goto done; } } else if (r->family == AF_INET6) { /* Always transmit IPv6 packets with hop limit set to 255 */ ret = setsockopt_ipv6_multicast_hops(r->sock_tx, 255); if (ret < 0) { zlog_warn( VRRP_LOGPFX VRRP_LOGPFX_VRID VRRP_LOGPFX_FAM "Failed to set outgoing multicast hop count to 255; RFC 5798 compliant implementations will drop our packets", r->vr->vrid, family2str(r->family)); } /* Set Tx socket DSCP byte */ setsockopt_ipv6_tclass(r->sock_tx, IPTOS_PREC_INTERNETCONTROL); /* Request hop limit delivery */ setsockopt_ipv6_hoplimit(r->sock_rx, 1); if (ret < 0) { zlog_warn(VRRP_LOGPFX VRRP_LOGPFX_VRID VRRP_LOGPFX_FAM "Failed to request IPv6 Hop Limit delivery", r->vr->vrid, family2str(r->family)); failed = true; goto done; } /* Turn off multicast loop on Tx */ setsockopt_ipv6_multicast_loop(r->sock_tx, 0); /* Bind Rx socket to exact interface */ frr_with_privs(&vrrp_privs) { ret = setsockopt(r->sock_rx, SOL_SOCKET, SO_BINDTODEVICE, r->vr->ifp->name, strlen(r->vr->ifp->name)); } if (ret) { zlog_warn(VRRP_LOGPFX VRRP_LOGPFX_VRID VRRP_LOGPFX_FAM "Failed to bind Rx socket to %s: %s", r->vr->vrid, family2str(r->family), r->vr->ifp->name, safe_strerror(errno)); failed = true; goto done; } DEBUGD(&vrrp_dbg_sock, VRRP_LOGPFX VRRP_LOGPFX_VRID VRRP_LOGPFX_FAM "Bound Rx socket to %s", r->vr->vrid, family2str(r->family), r->vr->ifp->name); /* Bind Rx socket to v6 multicast address */ struct sockaddr_in6 sa = {0}; sa.sin6_family = AF_INET6; inet_pton(AF_INET6, VRRP_MCASTV6_GROUP_STR, &sa.sin6_addr); if (bind(r->sock_rx, (struct sockaddr *)&sa, sizeof(sa))) { zlog_err( VRRP_LOGPFX VRRP_LOGPFX_VRID VRRP_LOGPFX_FAM "Failed to bind Rx socket to VRRP multicast group: %s", r->vr->vrid, family2str(r->family), safe_strerror(errno)); failed = true; goto done; } DEBUGD(&vrrp_dbg_sock, VRRP_LOGPFX VRRP_LOGPFX_VRID VRRP_LOGPFX_FAM "Bound Rx socket to VRRP multicast group", r->vr->vrid, family2str(r->family)); /* Join VRRP IPv6 multicast group */ struct ipv6_mreq mreq; inet_pton(AF_INET6, VRRP_MCASTV6_GROUP_STR, &mreq.ipv6mr_multiaddr); mreq.ipv6mr_interface = r->vr->ifp->ifindex; ret = setsockopt(r->sock_rx, IPPROTO_IPV6, IPV6_JOIN_GROUP, &mreq, sizeof(mreq)); if (ret < 0) { zlog_warn(VRRP_LOGPFX VRRP_LOGPFX_VRID VRRP_LOGPFX_FAM "Failed to join VRRP multicast group", r->vr->vrid, family2str(r->family)); failed = true; goto done; } DEBUGD(&vrrp_dbg_sock, VRRP_LOGPFX VRRP_LOGPFX_VRID VRRP_LOGPFX_FAM "Joined VRRP multicast group", r->vr->vrid, family2str(r->family)); /* Set outgoing interface for advertisements */ ret = setsockopt(r->sock_tx, IPPROTO_IPV6, IPV6_MULTICAST_IF, &r->mvl_ifp->ifindex, sizeof(ifindex_t)); if (ret < 0) { zlog_warn( VRRP_LOGPFX VRRP_LOGPFX_VRID VRRP_LOGPFX_FAM "Could not set %s as outgoing multicast interface", r->vr->vrid, family2str(r->family), r->mvl_ifp->name); failed = true; goto done; } DEBUGD(&vrrp_dbg_sock, VRRP_LOGPFX VRRP_LOGPFX_VRID VRRP_LOGPFX_FAM "Set %s as outgoing multicast interface", r->vr->vrid, family2str(r->family), r->mvl_ifp->name); } done: ret = 0; if (failed) { zlog_warn(VRRP_LOGPFX VRRP_LOGPFX_VRID VRRP_LOGPFX_FAM "Failed to initialize VRRP router", r->vr->vrid, family2str(r->family)); if (r->sock_rx >= 0) { close(r->sock_rx); r->sock_rx = -1; } if (r->sock_tx >= 0) { close(r->sock_tx); r->sock_tx = -1; } ret = -1; } return ret; } /* State machine ----------------------------------------------------------- */ DEFINE_HOOK(vrrp_change_state_hook, (struct vrrp_router *r, int to), (r, to)); /* * Handle any necessary actions during state change to MASTER state. * * r * VRRP Router to operate on */ static void vrrp_change_state_master(struct vrrp_router *r) { /* Enable ND Router Advertisements */ if (r->family == AF_INET6) vrrp_zebra_radv_set(r, true); /* Set protodown off */ vrrp_zclient_send_interface_protodown(r->mvl_ifp, false); /* * If protodown is already off, we can send our stuff, otherwise we * have to delay until the interface is all the way up */ if (if_is_operative(r->mvl_ifp)) { vrrp_send_advertisement(r); if (r->family == AF_INET) vrrp_garp_send_all(r); else if (r->family == AF_INET6) vrrp_ndisc_una_send_all(r); } else { DEBUGD(&vrrp_dbg_proto, VRRP_LOGPFX VRRP_LOGPFX_VRID VRRP_LOGPFX_FAM "Delaying VRRP advertisement until interface is up", r->vr->vrid, family2str(r->family)); r->advert_pending = true; if (r->family == AF_INET) { DEBUGD(&vrrp_dbg_proto, VRRP_LOGPFX VRRP_LOGPFX_VRID VRRP_LOGPFX_FAM "Delaying VRRP gratuitous ARPs until interface is up", r->vr->vrid, family2str(r->family)); r->garp_pending = true; } else if (r->family == AF_INET6) { DEBUGD(&vrrp_dbg_proto, VRRP_LOGPFX VRRP_LOGPFX_VRID VRRP_LOGPFX_FAM "Delaying VRRP unsolicited neighbor advertisement until interface is up", r->vr->vrid, family2str(r->family)); r->ndisc_pending = true; } } } /* * Handle any necessary actions during state change to BACKUP state. * * r * Virtual Router to operate on */ static void vrrp_change_state_backup(struct vrrp_router *r) { /* Disable ND Router Advertisements */ if (r->family == AF_INET6) vrrp_zebra_radv_set(r, false); /* Disable Adver_Timer */ THREAD_OFF(r->t_adver_timer); r->advert_pending = false; r->garp_pending = false; r->ndisc_pending = false; memset(&r->src, 0x00, sizeof(r->src)); vrrp_zclient_send_interface_protodown(r->mvl_ifp, true); } /* * Handle any necessary actions during state change to INITIALIZE state. * * This is not called for initial startup, only when transitioning from MASTER * or BACKUP. * * r * VRRP Router to operate on */ static void vrrp_change_state_initialize(struct vrrp_router *r) { r->master_adver_interval = 0; vrrp_recalculate_timers(r); r->advert_pending = false; r->garp_pending = false; r->ndisc_pending = false; /* Disable ND Router Advertisements */ if (r->family == AF_INET6) vrrp_zebra_radv_set(r, false); } void (*vrrp_change_state_handlers[])(struct vrrp_router *vr) = { [VRRP_STATE_MASTER] = vrrp_change_state_master, [VRRP_STATE_BACKUP] = vrrp_change_state_backup, [VRRP_STATE_INITIALIZE] = vrrp_change_state_initialize, }; /* * Change Virtual Router FSM position. Handles transitional actions and calls * any subscribers to the state change hook. * * r * Virtual Router for which to change state * * to * State to change to */ static void vrrp_change_state(struct vrrp_router *r, int to) { if (r->fsm.state == to) return; /* Call our handlers, then any subscribers */ vrrp_change_state_handlers[to](r); hook_call(vrrp_change_state_hook, r, to); zlog_info(VRRP_LOGPFX VRRP_LOGPFX_VRID VRRP_LOGPFX_FAM "%s -> %s", r->vr->vrid, family2str(r->family), vrrp_state_names[r->fsm.state], vrrp_state_names[to]); r->fsm.state = to; ++r->stats.trans_cnt; } /* * Called when Adver_Timer expires. */ static int vrrp_adver_timer_expire(struct thread *thread) { struct vrrp_router *r = thread->arg; DEBUGD(&vrrp_dbg_proto, VRRP_LOGPFX VRRP_LOGPFX_VRID VRRP_LOGPFX_FAM "Adver_Timer expired", r->vr->vrid, family2str(r->family)); if (r->fsm.state == VRRP_STATE_MASTER) { /* Send an ADVERTISEMENT */ vrrp_send_advertisement(r); /* Reset the Adver_Timer to Advertisement_Interval */ thread_add_timer_msec(master, vrrp_adver_timer_expire, r, r->vr->advertisement_interval * 10, &r->t_adver_timer); } else { zlog_err(VRRP_LOGPFX VRRP_LOGPFX_VRID VRRP_LOGPFX_FAM "Adver_Timer expired in state '%s'; this is a bug", r->vr->vrid, family2str(r->family), vrrp_state_names[r->fsm.state]); } return 0; } /* * Called when Master_Down_Timer expires. */ static int vrrp_master_down_timer_expire(struct thread *thread) { struct vrrp_router *r = thread->arg; zlog_info(VRRP_LOGPFX VRRP_LOGPFX_VRID VRRP_LOGPFX_FAM "Master_Down_Timer expired", r->vr->vrid, family2str(r->family)); thread_add_timer_msec(master, vrrp_adver_timer_expire, r, r->vr->advertisement_interval * 10, &r->t_adver_timer); vrrp_change_state(r, VRRP_STATE_MASTER); return 0; } /* * Event handler for Startup event. * * Creates sockets, sends advertisements and ARP requests, starts timers, * and transitions the Virtual Router to either Master or Backup states. * * This function will also initialize the program's global ARP subsystem if it * has not yet been initialized. * * r * VRRP Router on which to apply Startup event * * Returns: * < 0 if the session socket could not be created, or the state is not * Initialize * 0 on success */ static int vrrp_startup(struct vrrp_router *r) { /* May only be called when the state is Initialize */ if (r->fsm.state != VRRP_STATE_INITIALIZE) return -1; /* Must have a valid macvlan interface available */ if (r->mvl_ifp == NULL && !vrrp_attach_interface(r)) { zlog_warn(VRRP_LOGPFX VRRP_LOGPFX_VRID VRRP_LOGPFX_FAM "No appropriate interface found", r->vr->vrid, family2str(r->family)); return -1; } /* Initialize global gratuitous ARP socket if necessary */ if (r->family == AF_INET && !vrrp_garp_is_init()) vrrp_garp_init(); if (r->family == AF_INET6 && !vrrp_ndisc_is_init()) vrrp_ndisc_init(); /* Create socket */ if (r->sock_rx < 0 || r->sock_tx < 0) { int ret = vrrp_socket(r); if (ret < 0 || r->sock_tx < 0 || r->sock_rx < 0) return ret; } /* Schedule listener */ thread_add_read(master, vrrp_read, r, r->sock_rx, &r->t_read); /* Configure effective priority */ assert(listhead(r->addrs)); struct ipaddr *primary = (struct ipaddr *)listhead(r->addrs)->data; char ipbuf[INET6_ADDRSTRLEN]; inet_ntop(r->family, &primary->ip.addr, ipbuf, sizeof(ipbuf)); if (r->vr->priority == VRRP_PRIO_MASTER || vrrp_is_owner(r->vr->ifp, primary)) { r->priority = VRRP_PRIO_MASTER; vrrp_recalculate_timers(r); zlog_info( VRRP_LOGPFX VRRP_LOGPFX_VRID VRRP_LOGPFX_FAM "%s has priority set to 255 or owns primary Virtual Router IP %s; electing self as Master", r->vr->vrid, family2str(r->family), r->vr->ifp->name, ipbuf); } if (r->priority == VRRP_PRIO_MASTER) { thread_add_timer_msec(master, vrrp_adver_timer_expire, r, r->vr->advertisement_interval * 10, &r->t_adver_timer); vrrp_change_state(r, VRRP_STATE_MASTER); } else { r->master_adver_interval = r->vr->advertisement_interval; vrrp_recalculate_timers(r); thread_add_timer_msec(master, vrrp_master_down_timer_expire, r, r->master_down_interval * 10, &r->t_master_down_timer); vrrp_change_state(r, VRRP_STATE_BACKUP); } r->is_active = true; return 0; } /* * Shuts down a Virtual Router and transitions it to Initialize. * * This call must be idempotent; it is safe to call multiple times on the same * VRRP Router. */ static int vrrp_shutdown(struct vrrp_router *r) { uint8_t saved_prio; switch (r->fsm.state) { case VRRP_STATE_MASTER: /* Send an ADVERTISEMENT with Priority = 0 */ saved_prio = r->priority; r->priority = 0; vrrp_send_advertisement(r); r->priority = saved_prio; break; case VRRP_STATE_BACKUP: break; case VRRP_STATE_INITIALIZE: DEBUGD(&vrrp_dbg_proto, VRRP_LOGPFX VRRP_LOGPFX_VRID VRRP_LOGPFX_FAM "Received '%s' event in '%s' state; ignoring", r->vr->vrid, family2str(r->family), vrrp_event_names[VRRP_EVENT_SHUTDOWN], vrrp_state_names[VRRP_STATE_INITIALIZE]); break; } /* Cancel all timers */ THREAD_OFF(r->t_adver_timer); THREAD_OFF(r->t_master_down_timer); THREAD_OFF(r->t_read); THREAD_OFF(r->t_write); /* Protodown macvlan */ vrrp_zclient_send_interface_protodown(r->mvl_ifp, true); /* Throw away our source address */ memset(&r->src, 0x00, sizeof(r->src)); if (r->sock_rx > 0) { close(r->sock_rx); r->sock_rx = -1; } if (r->sock_tx > 0) { close(r->sock_tx); r->sock_tx = -1; } vrrp_change_state(r, VRRP_STATE_INITIALIZE); r->is_active = false; return 0; } static int (*vrrp_event_handlers[])(struct vrrp_router *r) = { [VRRP_EVENT_STARTUP] = vrrp_startup, [VRRP_EVENT_SHUTDOWN] = vrrp_shutdown, }; /* * Spawn a VRRP FSM event on a VRRP Router. * * vr * VRRP Router on which to spawn event * * event * The event to spawn * * Returns: * -1 on failure * 0 otherwise */ int vrrp_event(struct vrrp_router *r, int event) { zlog_info(VRRP_LOGPFX VRRP_LOGPFX_VRID VRRP_LOGPFX_FAM "'%s' event", r->vr->vrid, family2str(r->family), vrrp_event_names[event]); return vrrp_event_handlers[event](r); } /* Autoconfig -------------------------------------------------------------- */ /* * Set the configured addresses for this VRRP instance to exactly the addresses * present on its macvlan subinterface(s). * * vr * VRRP router to act on */ static void vrrp_autoconfig_autoaddrupdate(struct vrrp_router *r) { struct listnode *ln; struct connected *c = NULL; bool is_v6_ll; char ipbuf[INET6_ADDRSTRLEN]; if (!r->mvl_ifp) return; DEBUGD(&vrrp_dbg_auto, VRRP_LOGPFX VRRP_LOGPFX_VRID VRRP_LOGPFX_FAM "Setting Virtual IP list to match IPv4 addresses on %s", r->vr->vrid, family2str(r->family), r->mvl_ifp->name); for (ALL_LIST_ELEMENTS_RO(r->mvl_ifp->connected, ln, c)) { is_v6_ll = (c->address->family == AF_INET6 && IN6_IS_ADDR_LINKLOCAL(&c->address->u.prefix6)); if (c->address->family == r->family && !is_v6_ll) { inet_ntop(r->family, &c->address->u.prefix, ipbuf, sizeof(ipbuf)); DEBUGD(&vrrp_dbg_auto, VRRP_LOGPFX VRRP_LOGPFX_VRID VRRP_LOGPFX_FAM "Adding %s", r->vr->vrid, family2str(r->family), ipbuf); if (r->family == AF_INET) vrrp_add_ipv4(r->vr, c->address->u.prefix4); else if (r->vr->version == 3) vrrp_add_ipv6(r->vr, c->address->u.prefix6); } } vrrp_check_start(r->vr); if (r->addrs->count == 0 && r->fsm.state != VRRP_STATE_INITIALIZE) { DEBUGD(&vrrp_dbg_auto, VRRP_LOGPFX VRRP_LOGPFX_VRID VRRP_LOGPFX_FAM "Virtual IP list is empty; shutting down", r->vr->vrid, family2str(r->family)); vrrp_event(r, VRRP_EVENT_SHUTDOWN); } } static struct vrrp_vrouter * vrrp_autoconfig_autocreate(struct interface *mvl_ifp) { struct interface *p; struct vrrp_vrouter *vr; p = if_lookup_by_index(mvl_ifp->link_ifindex, VRF_DEFAULT); if (!p) return NULL; uint8_t vrid = mvl_ifp->hw_addr[5]; uint8_t fam = mvl_ifp->hw_addr[4]; DEBUGD(&vrrp_dbg_auto, VRRP_LOGPFX VRRP_LOGPFX_VRID VRRP_LOGPFX_FAM "Autoconfiguring VRRP on %s", vrid, family2str(fam), p->name); vr = vrrp_vrouter_create(p, vrid, vrrp_autoconfig_version); if (!vr) { zlog_warn(VRRP_LOGPFX VRRP_LOGPFX_VRID VRRP_LOGPFX_FAM "Failed to autoconfigure VRRP on %s", vrid, family2str(fam), p->name); return NULL; } vr->autoconf = true; /* * If these interfaces are protodown on, we need to un-protodown them * in order to get Zebra to send us their addresses so we can * autoconfigure them. */ if (vr->v4->mvl_ifp) vrrp_zclient_send_interface_protodown(vr->v4->mvl_ifp, false); if (vr->v6->mvl_ifp) vrrp_zclient_send_interface_protodown(vr->v6->mvl_ifp, false); /* If they're not, we can go ahead and add the addresses we have */ vrrp_autoconfig_autoaddrupdate(vr->v4); vrrp_autoconfig_autoaddrupdate(vr->v6); return vr; } /* * Callback to notify autoconfig of interface add. * * If the interface is a VRRP-compatible device, and there is no existing VRRP * router running on it, one is created. All addresses on the interface are * added to the router. * * ifp * Interface to operate on * * Returns: * -1 on failure * 0 otherwise */ static int vrrp_autoconfig_if_add(struct interface *ifp) { bool created = false; struct vrrp_vrouter *vr; if (!vrrp_autoconfig_is_on) return 0; if (!ifp || !ifp->link_ifindex || !vrrp_ifp_has_vrrp_mac(ifp)) return -1; vr = vrrp_lookup_by_if_mvl(ifp); if (!vr) { vr = vrrp_autoconfig_autocreate(ifp); created = true; } if (!vr || vr->autoconf == false) return 0; if (!created) { /* * We didn't create it, but it has already been autoconfigured. * Try to attach this interface to the existing instance. */ if (!vr->v4->mvl_ifp) { vrrp_attach_interface(vr->v4); /* If we just attached it, make sure it's turned on */ if (vr->v4->mvl_ifp) { vrrp_zclient_send_interface_protodown( vr->v4->mvl_ifp, false); /* * If it's already up, we can go ahead and add * the addresses we have */ vrrp_autoconfig_autoaddrupdate(vr->v4); } } if (!vr->v6->mvl_ifp) { vrrp_attach_interface(vr->v6); /* If we just attached it, make sure it's turned on */ if (vr->v6->mvl_ifp) { vrrp_zclient_send_interface_protodown( vr->v6->mvl_ifp, false); /* * If it's already up, we can go ahead and add * the addresses we have */ vrrp_autoconfig_autoaddrupdate(vr->v6); } } } return 0; } /* * Callback to notify autoconfig of interface delete. * * If the interface is a VRRP-compatible device, and a VRRP router is running * on it, and that VRRP router was automatically configured, it will be * deleted. If that was the last router for the corresponding VRID (i.e., if * this interface was a v4 VRRP interface and no v6 router is configured for * the same VRID) then the entire virtual router is deleted. * * ifp * Interface to operate on * * Returns: * -1 on failure * 0 otherwise */ static int vrrp_autoconfig_if_del(struct interface *ifp) { if (!vrrp_autoconfig_is_on) return 0; struct vrrp_vrouter *vr; struct listnode *ln; struct list *vrs; vrs = vrrp_lookup_by_if_any(ifp); for (ALL_LIST_ELEMENTS_RO(vrs, ln, vr)) if (vr->autoconf && (!vr->ifp || (!vr->v4->mvl_ifp && !vr->v6->mvl_ifp))) { DEBUGD(&vrrp_dbg_auto, VRRP_LOGPFX VRRP_LOGPFX_VRID "All VRRP interfaces for instance deleted; destroying autoconfigured VRRP router", vr->vrid); vrrp_vrouter_destroy(vr); } list_delete(&vrs); return 0; } /* * Callback to notify autoconfig of interface up. * * Creates VRRP instance on interface if it does not exist. Otherwise does * nothing. * * ifp * Interface to operate on * * Returns: * -1 on failure * 0 otherwise */ static int vrrp_autoconfig_if_up(struct interface *ifp) { if (!vrrp_autoconfig_is_on) return 0; struct vrrp_vrouter *vr = vrrp_lookup_by_if_mvl(ifp); if (vr && !vr->autoconf) return 0; if (!vr) { vrrp_autoconfig_if_add(ifp); return 0; } return 0; } /* * Callback to notify autoconfig of interface down. * * Does nothing. An interface down event is accompanied by address deletion * events for all the addresses on the interface; if an autoconfigured VRRP * router exists on this interface, then it will have all its addresses deleted * and end up in Initialize. * * ifp * Interface to operate on * * Returns: * -1 on failure * 0 otherwise */ static int vrrp_autoconfig_if_down(struct interface *ifp) { if (!vrrp_autoconfig_is_on) return 0; return 0; } /* * Callback to notify autoconfig of a new interface address. * * If a VRRP router exists on this interface, its address list is updated to * match the new address list. If no addresses remain, a Shutdown event is * issued to the VRRP router. * * ifp * Interface to operate on * * Returns: * -1 on failure * 0 otherwise * */ static int vrrp_autoconfig_if_address_add(struct interface *ifp) { if (!vrrp_autoconfig_is_on) return 0; struct vrrp_vrouter *vr = vrrp_lookup_by_if_mvl(ifp); if (vr && vr->autoconf) { if (vr->v4->mvl_ifp == ifp) vrrp_autoconfig_autoaddrupdate(vr->v4); else if (vr->v6->mvl_ifp == ifp) vrrp_autoconfig_autoaddrupdate(vr->v6); } return 0; } /* * Callback to notify autoconfig of a removed interface address. * * If a VRRP router exists on this interface, its address list is updated to * match the new address list. If no addresses remain, a Shutdown event is * issued to the VRRP router. * * ifp * Interface to operate on * * Returns: * -1 on failure * 0 otherwise * */ static int vrrp_autoconfig_if_address_del(struct interface *ifp) { if (!vrrp_autoconfig_is_on) return 0; struct vrrp_vrouter *vr = vrrp_lookup_by_if_mvl(ifp); if (vr && vr->autoconf) { if (vr->v4->mvl_ifp == ifp) vrrp_autoconfig_autoaddrupdate(vr->v4); else if (vr->v6->mvl_ifp == ifp) vrrp_autoconfig_autoaddrupdate(vr->v6); } return 0; } int vrrp_autoconfig(void) { if (!vrrp_autoconfig_is_on) return 0; struct vrf *vrf = vrf_lookup_by_id(VRF_DEFAULT); struct interface *ifp; FOR_ALL_INTERFACES (vrf, ifp) vrrp_autoconfig_if_add(ifp); return 0; } void vrrp_autoconfig_on(int version) { vrrp_autoconfig_is_on = true; vrrp_autoconfig_version = version; vrrp_autoconfig(); } void vrrp_autoconfig_off(void) { vrrp_autoconfig_is_on = false; struct list *ll = hash_to_list(vrrp_vrouters_hash); struct listnode *ln; struct vrrp_vrouter *vr; for (ALL_LIST_ELEMENTS_RO(ll, ln, vr)) if (vr->autoconf) vrrp_vrouter_destroy(vr); list_delete(&ll); } /* Interface tracking ------------------------------------------------------ */ /* * Bind any pending interfaces. * * mvl_ifp * macvlan interface that some VRRP instances might want to bind to */ static void vrrp_bind_pending(struct interface *mvl_ifp) { struct vrrp_vrouter *vr; DEBUGD(&vrrp_dbg_zebra, VRRP_LOGPFX "Searching for instances that could use interface %s", mvl_ifp->name); vr = vrrp_lookup_by_if_mvl(mvl_ifp); if (vr) { DEBUGD(&vrrp_dbg_zebra, VRRP_LOGPFX VRRP_LOGPFX_VRID "<-- This instance can probably use interface %s", vr->vrid, mvl_ifp->name); if (mvl_ifp->hw_addr[4] == 0x01 && !vr->v4->mvl_ifp) vrrp_attach_interface(vr->v4); else if (mvl_ifp->hw_addr[4] == 0x02 && !vr->v6->mvl_ifp) vrrp_attach_interface(vr->v6); } } void vrrp_if_up(struct interface *ifp) { struct vrrp_vrouter *vr; struct listnode *ln; struct list *vrs; vrrp_bind_pending(ifp); vrs = vrrp_lookup_by_if_any(ifp); for (ALL_LIST_ELEMENTS_RO(vrs, ln, vr)) { vrrp_check_start(vr); if (!if_is_operative(ifp)) continue; /* * Handle the situation in which we performed a state * transition on this VRRP router but needed to wait for the * macvlan interface to come up to perform some actions */ if (ifp == vr->v4->mvl_ifp) { if (vr->v4->advert_pending) { DEBUGD(&vrrp_dbg_proto, VRRP_LOGPFX VRRP_LOGPFX_VRID VRRP_LOGPFX_FAM "Interface up; sending pending advertisement", vr->vrid, family2str(vr->v4->family)); vrrp_send_advertisement(vr->v4); vr->v4->advert_pending = false; } if (vr->v4->garp_pending) { DEBUGD(&vrrp_dbg_proto, VRRP_LOGPFX VRRP_LOGPFX_VRID VRRP_LOGPFX_FAM "Interface up; sending pending gratuitous ARP", vr->vrid, family2str(vr->v4->family)); vrrp_garp_send_all(vr->v4); vr->v4->garp_pending = false; } } if (ifp == vr->v6->mvl_ifp) { if (vr->v6->advert_pending) { DEBUGD(&vrrp_dbg_proto, VRRP_LOGPFX VRRP_LOGPFX_VRID VRRP_LOGPFX_FAM "Interface up; sending pending advertisement", vr->vrid, family2str(vr->v6->family)); vrrp_send_advertisement(vr->v6); vr->v6->advert_pending = false; } if (vr->v6->ndisc_pending) { DEBUGD(&vrrp_dbg_proto, VRRP_LOGPFX VRRP_LOGPFX_VRID VRRP_LOGPFX_FAM "Interface up; sending pending Unsolicited Neighbor Advertisement", vr->vrid, family2str(vr->v6->family)); vrrp_ndisc_una_send_all(vr->v6); vr->v6->ndisc_pending = false; } } } list_delete(&vrs); vrrp_autoconfig_if_up(ifp); } void vrrp_if_down(struct interface *ifp) { struct vrrp_vrouter *vr; struct listnode *ln; struct list *vrs; vrrp_bind_pending(ifp); vrs = vrrp_lookup_by_if_any(ifp); for (ALL_LIST_ELEMENTS_RO(vrs, ln, vr)) { vrrp_check_start(vr); if (vr->ifp == ifp || vr->v4->mvl_ifp == ifp || vr->v6->mvl_ifp == ifp) { DEBUGD(&vrrp_dbg_auto, VRRP_LOGPFX VRRP_LOGPFX_VRID "Interface %s down", vr->vrid, ifp->name); } } list_delete(&vrs); vrrp_autoconfig_if_down(ifp); } void vrrp_if_add(struct interface *ifp) { vrrp_bind_pending(ifp); /* thanks, zebra */ if (CHECK_FLAG(ifp->flags, IFF_UP)) vrrp_if_up(ifp); vrrp_autoconfig_if_add(ifp); } void vrrp_if_del(struct interface *ifp) { struct listnode *ln; struct vrrp_vrouter *vr; struct list *vrs = vrrp_lookup_by_if_any(ifp); vrrp_if_down(ifp); for (ALL_LIST_ELEMENTS_RO(vrs, ln, vr)) { if ((vr->v4->mvl_ifp == ifp || vr->ifp == ifp) && vr->v4->fsm.state != VRRP_STATE_INITIALIZE) { vrrp_event(vr->v4, VRRP_EVENT_SHUTDOWN); vr->v4->mvl_ifp = NULL; } else if ((vr->v6->mvl_ifp == ifp || vr->ifp == ifp) && vr->v6->fsm.state != VRRP_STATE_INITIALIZE) { vrrp_event(vr->v6, VRRP_EVENT_SHUTDOWN); vr->v6->mvl_ifp = NULL; } } list_delete(&vrs); vrrp_autoconfig_if_del(ifp); } void vrrp_if_address_add(struct interface *ifp) { struct vrrp_vrouter *vr; struct listnode *ln; struct list *vrs; /* * We have to do a wide search here, because we need to know when a v6 * macvlan device gets a new address. This is because the macvlan link * local is used as the source address for v6 advertisements, and hence * "do I have a link local" constitutes an activation condition for v6 * virtual routers. */ vrs = vrrp_lookup_by_if_any(ifp); for (ALL_LIST_ELEMENTS_RO(vrs, ln, vr)) vrrp_check_start(vr); list_delete(&vrs); vrrp_autoconfig_if_address_add(ifp); } void vrrp_if_address_del(struct interface *ifp) { /* * Zebra is stupid and sends us address deletion notifications * when any of the following condition sets are met: * * - if_is_operative && address deleted * - if_is_operative -> !if_is_operative * * Note that the second one is nonsense, because Zebra behaves as * though an interface going down means all the addresses on that * interface got deleted. Which is a problem for autoconfig because all * the addresses on an interface going away means the VRRP session goes * to Initialize. However interfaces go down whenever we transition to * Backup, so this effectively means that for autoconfigured instances * we actually end up in Initialize whenever we try to go into Backup. * * Also, Zebra does NOT send us notifications when: * - !if_is_operative && address deleted * * Which means if we're in backup and an address is deleted out from * under us, we won't even know. * * The only solution here is to only resynchronize our address list * when: * * - An interfaces comes up * - An interface address is added * - An interface address is deleted AND the interface is up * * Even though this is only a problem with autoconfig at the moment I'm * papering over Zebra's braindead semantics here. Every piece of code * in this function should be protected by a check that the interface * is up. */ if (if_is_operative(ifp)) vrrp_autoconfig_if_address_del(ifp); } /* Other ------------------------------------------------------------------- */ int vrrp_config_write_interface(struct vty *vty) { struct list *vrs = hash_to_list(vrrp_vrouters_hash); struct listnode *ln, *ipln; struct vrrp_vrouter *vr; int writes = 0; for (ALL_LIST_ELEMENTS_RO(vrs, ln, vr)) { vty_frame(vty, "interface %s\n", vr->ifp->name); ++writes; vty_out(vty, " vrrp %" PRIu8 "%s\n", vr->vrid, vr->version == 2 ? " version 2" : ""); ++writes; if (vr->shutdown != vd.shutdown && ++writes) vty_out(vty, " %svrrp %" PRIu8 " shutdown\n", vr->shutdown ? "" : "no ", vr->vrid); if (vr->preempt_mode != vd.preempt_mode && ++writes) vty_out(vty, " %svrrp %" PRIu8 " preempt\n", vr->preempt_mode ? "" : "no ", vr->vrid); if (vr->accept_mode != vd.accept_mode && ++writes) vty_out(vty, " %svrrp %" PRIu8 " accept\n", vr->accept_mode ? "" : "no ", vr->vrid); if (vr->advertisement_interval != vd.advertisement_interval && ++writes) vty_out(vty, " vrrp %" PRIu8 " advertisement-interval %d\n", vr->vrid, vr->advertisement_interval * CS2MS); if (vr->priority != vd.priority && ++writes) vty_out(vty, " vrrp %" PRIu8 " priority %" PRIu8 "\n", vr->vrid, vr->priority); struct ipaddr *ip; for (ALL_LIST_ELEMENTS_RO(vr->v4->addrs, ipln, ip)) { char ipbuf[INET6_ADDRSTRLEN]; ipaddr2str(ip, ipbuf, sizeof(ipbuf)); vty_out(vty, " vrrp %" PRIu8 " ip %s\n", vr->vrid, ipbuf); ++writes; } for (ALL_LIST_ELEMENTS_RO(vr->v6->addrs, ipln, ip)) { char ipbuf[INET6_ADDRSTRLEN]; ipaddr2str(ip, ipbuf, sizeof(ipbuf)); vty_out(vty, " vrrp %" PRIu8 " ipv6 %s\n", vr->vrid, ipbuf); ++writes; } vty_endframe(vty, "!\n"); } list_delete(&vrs); return writes; } int vrrp_config_write_global(struct vty *vty) { unsigned int writes = 0; if (vrrp_autoconfig_is_on && ++writes) vty_out(vty, "vrrp autoconfigure%s\n", vrrp_autoconfig_version == 2 ? " version 2" : ""); if (vd.priority != VRRP_DEFAULT_PRIORITY && ++writes) vty_out(vty, "vrrp default priority %" PRIu8 "\n", vd.priority); if (vd.advertisement_interval != VRRP_DEFAULT_ADVINT && ++writes) vty_out(vty, "vrrp default advertisement-interval %" PRIu16 "\n", vd.advertisement_interval * CS2MS); if (vd.preempt_mode != VRRP_DEFAULT_PREEMPT && ++writes) vty_out(vty, "%svrrp default preempt\n", !vd.preempt_mode ? "no " : ""); if (vd.accept_mode != VRRP_DEFAULT_ACCEPT && ++writes) vty_out(vty, "%svrrp default accept\n", !vd.accept_mode ? "no " : ""); if (vd.shutdown != VRRP_DEFAULT_SHUTDOWN && ++writes) vty_out(vty, "%svrrp default shutdown\n", !vd.shutdown ? "no " : ""); return writes; } static unsigned int vrrp_hash_key(const void *arg) { const struct vrrp_vrouter *vr = arg; char key[IFNAMSIZ + 64]; snprintf(key, sizeof(key), "%s@%" PRIu8, vr->ifp->name, vr->vrid); return string_hash_make(key); } static bool vrrp_hash_cmp(const void *arg1, const void *arg2) { const struct vrrp_vrouter *vr1 = arg1; const struct vrrp_vrouter *vr2 = arg2; if (vr1->ifp != vr2->ifp) return 0; if (vr1->vrid != vr2->vrid) return 0; return 1; } void vrrp_init(void) { /* Set default defaults */ vd.priority = VRRP_DEFAULT_PRIORITY; vd.advertisement_interval = VRRP_DEFAULT_ADVINT; vd.preempt_mode = VRRP_DEFAULT_PREEMPT; vd.accept_mode = VRRP_DEFAULT_ACCEPT; vd.shutdown = VRRP_DEFAULT_SHUTDOWN; vrrp_autoconfig_version = 3; vrrp_vrouters_hash = hash_create(&vrrp_hash_key, vrrp_hash_cmp, "VRRP virtual router hash"); vrf_init(NULL, NULL, NULL, NULL, NULL); } void vrrp_fini(void) { /* Destroy all instances */ struct list *vrs = hash_to_list(vrrp_vrouters_hash); struct listnode *ln; struct vrrp_vrouter *vr; for (ALL_LIST_ELEMENTS_RO(vrs, ln, vr)) vrrp_vrouter_destroy(vr); list_delete(&vrs); hash_clean(vrrp_vrouters_hash, NULL); hash_free(vrrp_vrouters_hash); } frr-7.2.1/vrrpd/vrrp.h0000644000000000000000000003506313610377563011522 00000000000000/* * VRRP global definitions and state machine. * Copyright (C) 2018-2019 Cumulus Networks, Inc. * Quentin Young * * This program is free software; you can redistribute it and/or modify it * under the terms of the 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; see the file COPYING; if not, write to the Free Software * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA */ #ifndef __VRRP_H__ #define __VRRP_H__ #include #include #include "lib/memory.h" #include "lib/hash.h" #include "lib/hook.h" #include "lib/if.h" #include "lib/linklist.h" #include "lib/privs.h" #include "lib/stream.h" #include "lib/thread.h" #include "lib/vty.h" /* Global definitions */ #define VRRP_RADV_INT 16 #define VRRP_PRIO_MASTER 255 #define VRRP_MCASTV4_GROUP_STR "224.0.0.18" #define VRRP_MCASTV6_GROUP_STR "ff02:0:0:0:0:0:0:12" #define VRRP_MCASTV4_GROUP 0xe0000012 #define VRRP_MCASTV6_GROUP 0xff020000000000000000000000000012 #define IPPROTO_VRRP 112 #define VRRP_LOGPFX_VRID "[VRID %u] " #define VRRP_LOGPFX_FAM "[%s] " /* Default defaults */ #define VRRP_DEFAULT_PRIORITY 100 #define VRRP_DEFAULT_ADVINT 100 #define VRRP_DEFAULT_PREEMPT true #define VRRP_DEFAULT_ACCEPT true #define VRRP_DEFAULT_SHUTDOWN false /* User compatibility constant */ #define CS2MS 10 DECLARE_MGROUP(VRRPD) /* Configured defaults */ struct vrrp_defaults { uint8_t priority; uint16_t advertisement_interval; bool preempt_mode; bool accept_mode; bool shutdown; }; extern struct vrrp_defaults vd; /* threadmaster */ extern struct thread_master *master; /* privileges */ extern struct zebra_privs_t vrrp_privs; /* Global hash of all Virtual Routers */ extern struct hash *vrrp_vrouters_hash; /* * VRRP Router. * * This struct contains all state for a particular VRRP Router operating * in a Virtual Router for either IPv4 or IPv6. */ struct vrrp_router { /* * Whether this VRRP Router is active. */ bool is_active; /* Whether we are the address owner */ bool is_owner; /* Rx socket: Rx from parent of mvl_ifp */ int sock_rx; /* Tx socket; Tx from mvl_ifp */ int sock_tx; /* macvlan interface */ struct interface *mvl_ifp; /* Source address for advertisements */ struct ipaddr src; /* Socket read buffer */ uint8_t ibuf[IP_MAXPACKET]; /* * Address family of this Virtual Router. * Either AF_INET or AF_INET6. */ int family; /* * Virtual Router this VRRP Router is participating in. */ struct vrrp_vrouter *vr; /* * One or more IPvX addresses associated with this Virtual * Router. The first address must be the "primary" address this * Virtual Router is backing up in the case of IPv4. In the case of * IPv6 it must be the link-local address of vr->ifp. * * Type: struct ipaddr * */ struct list *addrs; /* * This flag says whether we are waiting on an interface up * notification from Zebra before we send an ADVERTISEMENT. */ bool advert_pending; /* * If this is an IPv4 VRRP router, this flag says whether we are * waiting on an interface up notification from Zebra before we send * gratuitous ARP packets for all our addresses. Should never be true * if family == AF_INET6. */ bool garp_pending; /* * If this is an IPv6 VRRP router, this flag says whether we are * waiting on an interface up notification from Zebra before we send * Unsolicited Neighbor Advertisement packets for all our addresses. * Should never be true if family == AF_INET. */ bool ndisc_pending; /* * Effective priority * => vr->priority if we are Backup * => 255 if we are Master */ uint8_t priority; /* * Advertisement interval contained in ADVERTISEMENTS received from the * Master (centiseconds) */ uint16_t master_adver_interval; /* * Time to skew Master_Down_Interval in centiseconds. Calculated as: * (((256 - priority) * Master_Adver_Interval) / 256) */ uint16_t skew_time; /* * Time interval for Backup to declare Master down (centiseconds). * Calculated as: * (3 * Master_Adver_Interval) + Skew_time */ uint16_t master_down_interval; /* * The MAC address used for the source MAC address in VRRP * advertisements, advertised in ARP requests/responses, and advertised * in ND Neighbor Advertisements. */ struct ethaddr vmac; struct { int state; } fsm; struct { /* Total number of advertisements sent and received */ uint32_t adver_tx_cnt; uint32_t adver_rx_cnt; /* Total number of gratuitous ARPs sent */ uint32_t garp_tx_cnt; /* Total number of unsolicited Neighbor Advertisements sent */ uint32_t una_tx_cnt; /* Total number of state transitions */ uint32_t trans_cnt; } stats; struct thread *t_master_down_timer; struct thread *t_adver_timer; struct thread *t_read; struct thread *t_write; }; /* * VRRP Virtual Router. * * This struct contains all state and configuration for a given Virtual Router * Identifier on a given interface, both v4 and v6. * * RFC5798 s. 1 states: * "Within a VRRP router, the virtual routers in each of the IPv4 and IPv6 * address families are a domain unto themselves and do not overlap." * * This implementation has chosen the tuple (interface, VRID) as the key for a * particular VRRP Router, and the rest of the program is designed around this * assumption. Additionally, base protocol configuration parameters such as the * advertisement interval and (configured) priority are shared between v4 and * v6 instances. This corresponds to the choice made by other industrial * implementations. */ struct vrrp_vrouter { /* Whether this instance was automatically configured */ bool autoconf; /* Whether this VRRP router is in administrative shutdown */ bool shutdown; /* Interface */ struct interface *ifp; /* Version */ uint8_t version; /* Virtual Router Identifier */ uint32_t vrid; /* Configured priority */ uint8_t priority; /* * Time interval between ADVERTISEMENTS (centiseconds). Default is 100 * centiseconds (1 second). */ uint16_t advertisement_interval; /* * Controls whether a (starting or restarting) higher-priority Backup * router preempts a lower-priority Master router. Values are True to * allow preemption and False to prohibit preemption. Default is True. */ bool preempt_mode; /* * Controls whether a virtual router in Master state will accept * packets addressed to the address owner's IPvX address as its own if * it is not the IPvX address owner. The default is False. */ bool accept_mode; struct vrrp_router *v4; struct vrrp_router *v6; }; /* * Initialize VRRP global datastructures. */ void vrrp_init(void); /* * Destroy all VRRP instances and gracefully shutdown. * * For instances in Master state, VRRP advertisements with 0 priority will be * sent if possible to notify Backup routers that we are going away. */ void vrrp_fini(void); /* Creation and destruction ------------------------------------------------ */ /* * Create and register a new VRRP Virtual Router. * * ifp * Base interface to configure VRRP on * * vrid * Virtual Router Identifier */ struct vrrp_vrouter *vrrp_vrouter_create(struct interface *ifp, uint8_t vrid, uint8_t version); /* * Destroy a VRRP Virtual Router, freeing all its resources. * * If there are any running VRRP instances, these are stopped and destroyed. */ void vrrp_vrouter_destroy(struct vrrp_vrouter *vr); /* Configuration controllers ----------------------------------------------- */ /* * Check if a Virtual Router ought to be started, and if so, start it. * * vr * Virtual Router to checkstart */ void vrrp_check_start(struct vrrp_vrouter *vr); /* * Change the configured priority of a VRRP Virtual Router. * * Note that this only changes the configured priority of the Virtual Router. * The currently effective priority will not be changed; to change the * effective priority, the Virtual Router must be restarted by issuing a * VRRP_EVENT_SHUTDOWN followed by a VRRP_EVENT_STARTUP. * * vr * Virtual Router to change priority of * * priority * New priority */ void vrrp_set_priority(struct vrrp_vrouter *vr, uint8_t priority); /* * Set Advertisement Interval on this Virtual Router. * * vr * Virtual Router to change priority of * * advertisement_interval * New advertisement interval */ void vrrp_set_advertisement_interval(struct vrrp_vrouter *vr, uint16_t advertisement_interval); /* * Add an IPvX address to a VRRP Virtual Router. * * r * Virtual Router to add IPvx address to * * ip * Address to add * * activate * Whether to automatically start the VRRP router if this is the first IP * address added. * * Returns: * -1 on error * 0 otherwise */ int vrrp_add_ip(struct vrrp_router *r, struct ipaddr *ip); /* * Add an IPv4 address to a VRRP Virtual Router. * * vr * Virtual Router to add IPv4 address to * * v4 * Address to add * * activate * Whether to automatically start the VRRP router if this is the first IP * address added. * * Returns: * -1 on error * 0 otherwise */ int vrrp_add_ipv4(struct vrrp_vrouter *vr, struct in_addr v4); /* * Add an IPv6 address to a VRRP Virtual Router. * * vr * Virtual Router to add IPv6 address to * * v6 * Address to add * * activate * Whether to automatically start the VRRP router if this is the first IP * address added. * * Returns: * -1 on error * 0 otherwise */ int vrrp_add_ipv6(struct vrrp_vrouter *vr, struct in6_addr v6); /* * Remove an IP address from a VRRP Virtual Router. * * r * Virtual Router to remove IP address from * * ip * Address to remove * * deactivate * Whether to automatically stop the VRRP router if removing v4 would leave * us with an empty address list. If this is not true and ip is the only IP * address backed up by this virtual router, this function will not remove * the address and return failure. * * Returns: * -1 on error * 0 otherwise */ int vrrp_del_ip(struct vrrp_router *r, struct ipaddr *ip); /* * Remove an IPv4 address from a VRRP Virtual Router. * * vr * Virtual Router to remove IPv4 address from * * v4 * Address to remove * * deactivate * Whether to automatically stop the VRRP router if removing v4 would leave * us with an empty address list. If this is not true and v4 is the only * IPv4 address backed up by this virtual router, this function will not * remove the address and return failure. * * Returns: * -1 on error * 0 otherwise */ int vrrp_del_ipv4(struct vrrp_vrouter *vr, struct in_addr v4); /* * Remove an IPv6 address from a VRRP Virtual Router. * * vr * Virtual Router to remove IPv6 address from * * v6 * Address to remove * * deactivate * Whether to automatically stop the VRRP router if removing v5 would leave * us with an empty address list. If this is not true and v4 is the only * IPv6 address backed up by this virtual router, this function will not * remove the address and return failure. * * Returns: * -1 on error * 0 otherwise */ int vrrp_del_ipv6(struct vrrp_vrouter *vr, struct in6_addr v6); /* State machine ----------------------------------------------------------- */ #define VRRP_STATE_INITIALIZE 0 #define VRRP_STATE_MASTER 1 #define VRRP_STATE_BACKUP 2 #define VRRP_EVENT_STARTUP 0 #define VRRP_EVENT_SHUTDOWN 1 extern const char *vrrp_state_names[3]; extern const char *vrrp_event_names[2]; /* * This hook called whenever the state of a Virtual Router changes, after the * specific internal state handlers have run. * * Use this if you need to react to state changes to perform non-critical * tasks. Critical tasks should go in the internal state change handlers. */ DECLARE_HOOK(vrrp_change_state_hook, (struct vrrp_router *r, int to), (r, to)); /* * Trigger a VRRP event on a given Virtual Router.. * * vr * Virtual Router to operate on * * event * Event to kick off. All event related processing will have completed upon * return of this function. * * Returns: * < 0 if the event created an error * 0 otherwise */ int vrrp_event(struct vrrp_router *r, int event); /* Autoconfig -------------------------------------------------------------- */ /* * Search for and automatically configure VRRP instances on interfaces. * * ifp * Interface to autoconfig. If it is a macvlan interface and has a VRRP MAC, * a VRRP instance corresponding to VMAC assigned to macvlan will be created * on the parent interface and all addresses on the macvlan interface except * the v6 link local will be configured as VRRP addresses. If NULL, this * treatment will be applied to all existing interfaces matching the above * criterion. * * Returns: * -1 on failure * 0 otherwise */ int vrrp_autoconfig(void); /* * Enable autoconfiguration. * * Calling this function will cause vrrpd to automatically configure VRRP * instances on existing compatible macvlan interfaces. These instances will * react to interface up/down and address add/delete events to keep themselves * in sync with the available interfaces. * * version * VRRP version to use for autoconfigured instances. Must be 2 or 3. */ void vrrp_autoconfig_on(int version); /* * Disable autoconfiguration. * * Calling this function will delete all existing autoconfigured VRRP instances. */ void vrrp_autoconfig_off(void); /* Interface Tracking ------------------------------------------------------ */ void vrrp_if_add(struct interface *ifp); void vrrp_if_del(struct interface *ifp); void vrrp_if_up(struct interface *ifp); void vrrp_if_down(struct interface *ifp); void vrrp_if_address_add(struct interface *ifp); void vrrp_if_address_del(struct interface *ifp); /* Other ------------------------------------------------------------------- */ /* * Write interface block-level configuration to vty. * * vty * vty to write config to * * Returns: * # of lines written */ int vrrp_config_write_interface(struct vty *vty); /* * Write global level configuration to vty. * * vty * vty to write config to * * Returns: * # of lines written */ int vrrp_config_write_global(struct vty *vty); /* * Find VRRP Virtual Router by Virtual Router ID */ struct vrrp_vrouter *vrrp_lookup(struct interface *ifp, uint8_t vrid); #endif /* __VRRP_H__ */ frr-7.2.1/vrrpd/vrrp_arp.c0000644000000000000000000001355713610377563012363 00000000000000/* * VRRP ARP handling. * Copyright (C) 2001-2017 Alexandre Cassen * Portions: * Copyright (C) 2018-2019 Cumulus Networks, Inc. * Quentin Young * * This program is free software; you can redistribute it and/or modify it * under the terms of the 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; see the file COPYING; if not, write to the Free Software * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA */ #include #include #include #include #include "lib/if.h" #include "lib/linklist.h" #include "lib/log.h" #include "lib/memory.h" #include "lib/prefix.h" #include "vrrp.h" #include "vrrp_arp.h" #include "vrrp_debug.h" #define VRRP_LOGPFX "[ARP] " /* * The size of the garp packet buffer should be the large enough to hold the * largest arp packet to be sent + the size of the link layer header for the * corresponding protocol. In this case we hardcode for Ethernet. */ #define GARP_BUFFER_SIZE \ sizeof(struct ether_header) + sizeof(struct arphdr) + 2 * ETH_ALEN \ + 2 * sizeof(struct in_addr) /* static vars */ static int garp_fd = -1; /* Send the gratuitous ARP message */ static ssize_t vrrp_send_garp(struct interface *ifp, uint8_t *buf, ssize_t pack_len) { struct sockaddr_ll sll; ssize_t len; /* Build the dst device */ memset(&sll, 0, sizeof(sll)); sll.sll_family = AF_PACKET; sll.sll_protocol = ETH_P_ARP; sll.sll_ifindex = (int)ifp->ifindex; sll.sll_halen = ifp->hw_addr_len; memset(sll.sll_addr, 0xFF, ETH_ALEN); /* Send packet */ len = sendto(garp_fd, buf, pack_len, 0, (struct sockaddr *)&sll, sizeof(sll)); return len; } /* Build a gratuitous ARP message over a specific interface */ static ssize_t vrrp_build_garp(uint8_t *buf, struct interface *ifp, struct in_addr *v4) { uint8_t *arp_ptr; if (ifp->hw_addr_len == 0) return -1; /* Build Ethernet header */ struct ether_header *eth = (struct ether_header *)buf; memset(eth->ether_dhost, 0xFF, ETH_ALEN); memcpy(eth->ether_shost, ifp->hw_addr, ETH_ALEN); eth->ether_type = htons(ETHERTYPE_ARP); /* Build ARP payload */ struct arphdr *arph = (struct arphdr *)(buf + ETHER_HDR_LEN); arph->ar_hrd = htons(HWTYPE_ETHER); arph->ar_pro = htons(ETHERTYPE_IP); arph->ar_hln = ifp->hw_addr_len; arph->ar_pln = sizeof(struct in_addr); arph->ar_op = htons(ARPOP_REQUEST); arp_ptr = (uint8_t *)(arph + 1); /* Source MAC: us */ memcpy(arp_ptr, ifp->hw_addr, ifp->hw_addr_len); arp_ptr += ifp->hw_addr_len; /* Source IP: us */ memcpy(arp_ptr, v4, sizeof(struct in_addr)); arp_ptr += sizeof(struct in_addr); /* Dest MAC: broadcast */ memset(arp_ptr, 0xFF, ETH_ALEN); arp_ptr += ifp->hw_addr_len; /* Dest IP: us */ memcpy(arp_ptr, v4, sizeof(struct in_addr)); arp_ptr += sizeof(struct in_addr); return arp_ptr - buf; } void vrrp_garp_send(struct vrrp_router *r, struct in_addr *v4) { struct interface *ifp = r->mvl_ifp; uint8_t garpbuf[GARP_BUFFER_SIZE]; ssize_t garpbuf_len; ssize_t sent_len; char astr[INET_ADDRSTRLEN]; /* If the interface doesn't support ARP, don't try sending */ if (ifp->flags & IFF_NOARP) { zlog_warn( VRRP_LOGPFX VRRP_LOGPFX_VRID VRRP_LOGPFX_FAM "Unable to send gratuitous ARP on %s; has IFF_NOARP", r->vr->vrid, family2str(r->family), ifp->name); return; } /* Build garp */ garpbuf_len = vrrp_build_garp(garpbuf, ifp, v4); if (garpbuf_len < 0) { zlog_warn( VRRP_LOGPFX VRRP_LOGPFX_VRID VRRP_LOGPFX_FAM "Unable to send gratuitous ARP on %s; MAC address unknown", r->vr->vrid, family2str(r->family), ifp->name); return; }; /* Send garp */ inet_ntop(AF_INET, v4, astr, sizeof(astr)); DEBUGD(&vrrp_dbg_arp, VRRP_LOGPFX VRRP_LOGPFX_VRID VRRP_LOGPFX_FAM "Sending gratuitous ARP on %s for %s", r->vr->vrid, family2str(r->family), ifp->name, astr); if (DEBUG_MODE_CHECK(&vrrp_dbg_arp, DEBUG_MODE_ALL)) zlog_hexdump(garpbuf, garpbuf_len); sent_len = vrrp_send_garp(ifp, garpbuf, garpbuf_len); if (sent_len < 0) zlog_warn(VRRP_LOGPFX VRRP_LOGPFX_VRID VRRP_LOGPFX_FAM "Error sending gratuitous ARP on %s for %s", r->vr->vrid, family2str(r->family), ifp->name, astr); else ++r->stats.garp_tx_cnt; } void vrrp_garp_send_all(struct vrrp_router *r) { assert(r->family == AF_INET); struct interface *ifp = r->mvl_ifp; /* If the interface doesn't support ARP, don't try sending */ if (ifp->flags & IFF_NOARP) { zlog_warn( VRRP_LOGPFX VRRP_LOGPFX_VRID VRRP_LOGPFX_FAM "Unable to send gratuitous ARP on %s; has IFF_NOARP\n", r->vr->vrid, family2str(r->family), ifp->name); return; } struct listnode *ln; struct ipaddr *ip; for (ALL_LIST_ELEMENTS_RO(r->addrs, ln, ip)) vrrp_garp_send(r, &ip->ipaddr_v4); } void vrrp_garp_init(void) { /* Create the socket descriptor */ /* FIXME: why ETH_P_RARP? */ errno = 0; frr_with_privs(&vrrp_privs) { garp_fd = socket(PF_PACKET, SOCK_RAW | SOCK_CLOEXEC, htons(ETH_P_RARP)); } if (garp_fd > 0) { DEBUGD(&vrrp_dbg_sock, VRRP_LOGPFX "Initialized gratuitous ARP socket"); DEBUGD(&vrrp_dbg_arp, VRRP_LOGPFX "Initialized gratuitous ARP subsystem"); } else { zlog_err(VRRP_LOGPFX "Error initializing gratuitous ARP subsystem"); } } void vrrp_garp_fini(void) { close(garp_fd); garp_fd = -1; DEBUGD(&vrrp_dbg_arp, VRRP_LOGPFX "Deinitialized gratuitous ARP subsystem"); } bool vrrp_garp_is_init(void) { return garp_fd > 0; } frr-7.2.1/vrrpd/vrrp_arp.h0000644000000000000000000000234113610377563012355 00000000000000/* * VRRP ARP handling. * Copyright (C) 2018-2019 Cumulus Networks, Inc. * Quentin Young * * This program is free software; you can redistribute it and/or modify it * under the terms of the 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; see the file COPYING; if not, write to the Free Software * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA */ #ifndef __VRRP_ARP_H__ #define __VRRP_ARP_H__ #include #include "vrrp.h" /* FIXME: Use the kernel define for this */ #define HWTYPE_ETHER 1 extern void vrrp_garp_init(void); extern void vrrp_garp_fini(void); extern bool vrrp_garp_is_init(void); extern void vrrp_garp_send(struct vrrp_router *vr, struct in_addr *v4); extern void vrrp_garp_send_all(struct vrrp_router *vr); #endif /* __VRRP_ARP_H__ */ frr-7.2.1/vrrpd/vrrp_debug.c0000644000000000000000000000701113610377563012653 00000000000000/* * VRRP debugging. * Copyright (C) 2019 Cumulus Networks, Inc. * Quentin Young * * This program is free software; you can redistribute it and/or modify it * under the terms of the 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; see the file COPYING; if not, write to the Free Software * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA */ #include #include "lib/command.h" #include "lib/debug.h" #include "lib/vector.h" #include "vrrp_debug.h" /* clang-format off */ struct debug vrrp_dbg_arp = {0, "VRRP ARP"}; struct debug vrrp_dbg_auto = {0, "VRRP autoconfiguration events"}; struct debug vrrp_dbg_ndisc = {0, "VRRP Neighbor Discovery"}; struct debug vrrp_dbg_pkt = {0, "VRRP packets"}; struct debug vrrp_dbg_proto = {0, "VRRP protocol events"}; struct debug vrrp_dbg_sock = {0, "VRRP sockets"}; struct debug vrrp_dbg_zebra = {0, "VRRP Zebra events"}; struct debug *vrrp_debugs[] = { &vrrp_dbg_arp, &vrrp_dbg_auto, &vrrp_dbg_ndisc, &vrrp_dbg_pkt, &vrrp_dbg_proto, &vrrp_dbg_sock, &vrrp_dbg_zebra }; const char *vrrp_debugs_conflines[] = { "debug vrrp arp", "debug vrrp autoconfigure", "debug vrrp ndisc", "debug vrrp packets", "debug vrrp protocol", "debug vrrp sockets", "debug vrrp zebra", }; /* clang-format on */ /* * Set or unset flags on all debugs for vrrpd. * * flags * The flags to set * * set * Whether to set or unset the specified flags */ static void vrrp_debug_set_all(uint32_t flags, bool set) { for (unsigned int i = 0; i < array_size(vrrp_debugs); i++) { DEBUG_FLAGS_SET(vrrp_debugs[i], flags, set); /* if all modes have been turned off, don't preserve options */ if (!DEBUG_MODE_CHECK(vrrp_debugs[i], DEBUG_MODE_ALL)) DEBUG_CLEAR(vrrp_debugs[i]); } } static int vrrp_debug_config_write_helper(struct vty *vty, bool config) { uint32_t mode = DEBUG_MODE_ALL; if (config) mode = DEBUG_MODE_CONF; for (unsigned int i = 0; i < array_size(vrrp_debugs); i++) if (DEBUG_MODE_CHECK(vrrp_debugs[i], mode)) vty_out(vty, "%s\n", vrrp_debugs_conflines[i]); return 0; } int vrrp_config_write_debug(struct vty *vty) { return vrrp_debug_config_write_helper(vty, true); } int vrrp_debug_status_write(struct vty *vty) { return vrrp_debug_config_write_helper(vty, false); } void vrrp_debug_set(struct interface *ifp, uint8_t vrid, int vtynode, bool onoff, bool proto, bool autoconf, bool pkt, bool sock, bool ndisc, bool arp, bool zebra) { uint32_t mode = DEBUG_NODE2MODE(vtynode); if (proto) DEBUG_MODE_SET(&vrrp_dbg_proto, mode, onoff); if (autoconf) DEBUG_MODE_SET(&vrrp_dbg_auto, mode, onoff); if (pkt) DEBUG_MODE_SET(&vrrp_dbg_pkt, mode, onoff); if (sock) DEBUG_MODE_SET(&vrrp_dbg_sock, mode, onoff); if (ndisc) DEBUG_MODE_SET(&vrrp_dbg_ndisc, mode, onoff); if (arp) DEBUG_MODE_SET(&vrrp_dbg_arp, mode, onoff); if (zebra) DEBUG_MODE_SET(&vrrp_dbg_zebra, mode, onoff); } /* ------------------------------------------------------------------------- */ struct debug_callbacks vrrp_dbg_cbs = {.debug_set_all = vrrp_debug_set_all}; void vrrp_debug_init(void) { debug_init(&vrrp_dbg_cbs); } frr-7.2.1/vrrpd/vrrp_debug.h0000644000000000000000000000420413610377563012661 00000000000000/* * VRRP debugging. * Copyright (C) 2019 Cumulus Networks, Inc. * Quentin Young * * This program is free software; you can redistribute it and/or modify it * under the terms of the 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; see the file COPYING; if not, write to the Free Software * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA */ #ifndef __VRRP_DEBUG_H__ #define __VRRP_DEBUG_H__ #include #include "lib/debug.h" /* VRRP debugging records */ struct debug vrrp_dbg_arp; struct debug vrrp_dbg_auto; struct debug vrrp_dbg_ndisc; struct debug vrrp_dbg_pkt; struct debug vrrp_dbg_proto; struct debug vrrp_dbg_sock; struct debug vrrp_dbg_zebra; /* * Initialize VRRP debugging. * * Installs VTY commands and registers callbacks. */ void vrrp_debug_init(void); /* * Print VRRP debugging configuration. * * vty * VTY to print debugging configuration to. */ int vrrp_config_write_debug(struct vty *vty); /* * Print VRRP debugging configuration, human readable form. * * vty * VTY to print debugging configuration to. */ int vrrp_debug_status_write(struct vty *vty); /* * Set debugging status. * * ifp * Interface to set status on * * vrid * VRID of instance to set status on * * vtynode * vty->node * * onoff * Whether to turn the specified debugs on or off * * proto * Turn protocol debugging on or off * * autoconf * Turn autoconfiguration debugging on or off * * pkt * Turn packet debugging on or off */ void vrrp_debug_set(struct interface *ifp, uint8_t vrid, int vtynode, bool onoff, bool proto, bool autoconf, bool pkt, bool sock, bool ndisc, bool arp, bool zebra); #endif /* __VRRP_DEBUG_H__ */ frr-7.2.1/vrrpd/vrrp_main.c0000644000000000000000000000635513610377563012523 00000000000000/* * VRRP entry point. * Copyright (C) 2018-2019 Cumulus Networks, Inc. * Quentin Young * * This program is free software; you can redistribute it and/or modify it * under the terms of the 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; see the file COPYING; if not, write to the Free Software * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA */ #include #include #include "lib/command.h" #include "lib/filter.h" #include "lib/getopt.h" #include "lib/if.h" #include "lib/libfrr.h" #include "lib/log.h" #include "lib/memory.h" #include "lib/nexthop.h" #include "lib/privs.h" #include "lib/sigevent.h" #include "lib/thread.h" #include "lib/vrf.h" #include "vrrp.h" #include "vrrp_debug.h" #include "vrrp_vty.h" #include "vrrp_zebra.h" DEFINE_MGROUP(VRRPD, "vrrpd") char backup_config_file[256]; zebra_capabilities_t _caps_p[] = { ZCAP_NET_RAW, }; struct zebra_privs_t vrrp_privs = { #if defined(FRR_USER) && defined(FRR_GROUP) .user = FRR_USER, .group = FRR_GROUP, #endif #if defined(VTY_GROUP) .vty_group = VTY_GROUP, #endif .caps_p = _caps_p, .cap_num_p = array_size(_caps_p), .cap_num_i = 0}; struct option longopts[] = { {0} }; /* Master of threads. */ struct thread_master *master; /* SIGHUP handler. */ static void sighup(void) { zlog_info("SIGHUP received"); } /* SIGINT / SIGTERM handler. */ static void __attribute__((noreturn)) sigint(void) { zlog_notice("Terminating on signal"); vrrp_fini(); exit(0); } /* SIGUSR1 handler. */ static void sigusr1(void) { zlog_rotate(); } struct quagga_signal_t vrrp_signals[] = { { .signal = SIGHUP, .handler = &sighup, }, { .signal = SIGUSR1, .handler = &sigusr1, }, { .signal = SIGINT, .handler = &sigint, }, { .signal = SIGTERM, .handler = &sigint, }, }; static const struct frr_yang_module_info *vrrp_yang_modules[] = { &frr_interface_info, }; #define VRRP_VTY_PORT 2619 FRR_DAEMON_INFO(vrrpd, VRRP, .vty_port = VRRP_VTY_PORT, .proghelp = "Virtual Router Redundancy Protocol", .signals = vrrp_signals, .n_signals = array_size(vrrp_signals), .privs = &vrrp_privs, .yang_modules = vrrp_yang_modules, .n_yang_modules = array_size(vrrp_yang_modules), ) int main(int argc, char **argv, char **envp) { frr_preinit(&vrrpd_di, argc, argv); frr_opt_add("", longopts, ""); while (1) { int opt; opt = frr_getopt(argc, argv, NULL); if (opt == EOF) break; switch (opt) { case 0: break; default: frr_help_exit(1); break; } } master = frr_init(); access_list_init(); vrrp_debug_init(); vrrp_zebra_init(); vrrp_vty_init(); vrrp_init(); snprintf(backup_config_file, sizeof(backup_config_file), "%s/vrrpd.conf", frr_sysconfdir); vrrpd_di.backup_config_file = backup_config_file; frr_config_fork(); frr_run(master); /* Not reached. */ return 0; } frr-7.2.1/vrrpd/vrrp_ndisc.c0000644000000000000000000001513413610377563012672 00000000000000/* * VRRP Neighbor Discovery. * Copyright (C) 2019 Cumulus Networks, Inc. * Quentin Young * * Portions: * Copyright (C) 2001-2017 Alexandre Cassen * * This program is free software; you can redistribute it and/or modify it * under the terms of the 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; see the file COPYING; if not, write to the Free Software * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA */ #include #include #include #include #include #include "lib/checksum.h" #include "lib/if.h" #include "lib/ipaddr.h" #include "lib/log.h" #include "vrrp_debug.h" #include "vrrp_ndisc.h" #define VRRP_LOGPFX "[NDISC] " #define VRRP_NDISC_HOPLIMIT 255 #define VRRP_NDISC_SIZE \ ETHER_HDR_LEN + sizeof(struct ip6_hdr) \ + sizeof(struct nd_neighbor_advert) \ + sizeof(struct nd_opt_hdr) + ETH_ALEN /* static vars */ static int ndisc_fd = -1; /* * Build an unsolicited Neighbour Advertisement. * * ifp * Interface to send Neighbor Advertisement on * * ip * IP address to send Neighbor Advertisement for * * buf * Buffer to fill with IPv6 Neighbor Advertisement message. Includes * Ethernet header. * * bufsiz * Size of buf. * * Returns; * -1 if bufsiz is too small * 0 otherwise */ static int vrrp_ndisc_una_build(struct interface *ifp, struct ipaddr *ip, uint8_t *buf, size_t bufsiz) { if (bufsiz < VRRP_NDISC_SIZE) return -1; memset(buf, 0x00, bufsiz); struct ether_header *eth = (struct ether_header *)buf; struct ip6_hdr *ip6h = (struct ip6_hdr *)((char *)eth + ETHER_HDR_LEN); struct nd_neighbor_advert *ndh = (struct nd_neighbor_advert *)((char *)ip6h + sizeof(struct ip6_hdr)); struct icmp6_hdr *icmp6h = &ndh->nd_na_hdr; struct nd_opt_hdr *nd_opt_h = (struct nd_opt_hdr *)((char *)ndh + sizeof(struct nd_neighbor_advert)); char *nd_opt_lladdr = (char *)((char *)nd_opt_h + sizeof(struct nd_opt_hdr)); char *lladdr = (char *)ifp->hw_addr; /* * An IPv6 packet with a multicast destination address DST, consisting * of the sixteen octets DST[1] through DST[16], is transmitted to the * Ethernet multicast address whose first two octets are the value 3333 * hexadecimal and whose last four octets are the last four octets of * DST. * - RFC2464.7 * * In this case we are sending to the all nodes multicast address, so * the last four octets are 0x00 0x00 0x00 0x01. */ memset(eth->ether_dhost, 0, ETH_ALEN); eth->ether_dhost[0] = 0x33; eth->ether_dhost[1] = 0x33; eth->ether_dhost[5] = 1; /* Set source Ethernet address to interface link layer address */ memcpy(eth->ether_shost, lladdr, ETH_ALEN); eth->ether_type = htons(ETHERTYPE_IPV6); /* IPv6 Header */ ip6h->ip6_vfc = 6 << 4; ip6h->ip6_plen = htons(sizeof(struct nd_neighbor_advert) + sizeof(struct nd_opt_hdr) + ETH_ALEN); ip6h->ip6_nxt = IPPROTO_ICMPV6; ip6h->ip6_hlim = VRRP_NDISC_HOPLIMIT; memcpy(&ip6h->ip6_src, &ip->ipaddr_v6, sizeof(struct in6_addr)); /* All nodes multicast address */ ip6h->ip6_dst.s6_addr[0] = 0xFF; ip6h->ip6_dst.s6_addr[1] = 0x02; ip6h->ip6_dst.s6_addr[15] = 0x01; /* ICMPv6 Header */ ndh->nd_na_type = ND_NEIGHBOR_ADVERT; ndh->nd_na_flags_reserved |= ND_NA_FLAG_ROUTER; ndh->nd_na_flags_reserved |= ND_NA_FLAG_OVERRIDE; memcpy(&ndh->nd_na_target, &ip->ipaddr_v6, sizeof(struct in6_addr)); /* NDISC Option header */ nd_opt_h->nd_opt_type = ND_OPT_TARGET_LINKADDR; nd_opt_h->nd_opt_len = 1; memcpy(nd_opt_lladdr, lladdr, ETH_ALEN); /* Compute checksum */ uint32_t len = sizeof(struct nd_neighbor_advert) + sizeof(struct nd_opt_hdr) + ETH_ALEN; struct ipv6_ph ph = {}; ph.src = ip6h->ip6_src; ph.dst = ip6h->ip6_dst; ph.ulpl = htonl(len); ph.next_hdr = IPPROTO_ICMPV6; /* Suppress static analysis warnings about accessing icmp6 oob */ void *offset = icmp6h; icmp6h->icmp6_cksum = in_cksum_with_ph6(&ph, offset, len); return 0; } int vrrp_ndisc_una_send(struct vrrp_router *r, struct ipaddr *ip) { assert(r->family == AF_INET6); int ret = 0; struct interface *ifp = r->mvl_ifp; uint8_t buf[VRRP_NDISC_SIZE]; ret = vrrp_ndisc_una_build(ifp, ip, buf, sizeof(buf)); if (ret == -1) return ret; struct sockaddr_ll sll; ssize_t len; /* Build the dst device */ memset(&sll, 0, sizeof(sll)); sll.sll_family = AF_PACKET; memcpy(sll.sll_addr, ifp->hw_addr, ETH_ALEN); sll.sll_halen = ETH_ALEN; sll.sll_ifindex = (int)ifp->ifindex; char ipbuf[INET6_ADDRSTRLEN]; ipaddr2str(ip, ipbuf, sizeof(ipbuf)); DEBUGD(&vrrp_dbg_ndisc, VRRP_LOGPFX VRRP_LOGPFX_VRID VRRP_LOGPFX_FAM "Sending unsolicited Neighbor Advertisement on %s for %s", r->vr->vrid, family2str(r->family), ifp->name, ipbuf); if (DEBUG_MODE_CHECK(&vrrp_dbg_ndisc, DEBUG_MODE_ALL) && DEBUG_MODE_CHECK(&vrrp_dbg_pkt, DEBUG_MODE_ALL)) zlog_hexdump(buf, VRRP_NDISC_SIZE); len = sendto(ndisc_fd, buf, VRRP_NDISC_SIZE, 0, (struct sockaddr *)&sll, sizeof(sll)); if (len < 0) { zlog_err( VRRP_LOGPFX VRRP_LOGPFX_VRID VRRP_LOGPFX_FAM "Error sending unsolicited Neighbor Advertisement on %s for %s", r->vr->vrid, family2str(r->family), ifp->name, ipbuf); ret = -1; } else { ++r->stats.una_tx_cnt; } return ret; } int vrrp_ndisc_una_send_all(struct vrrp_router *r) { assert(r->family == AF_INET6); struct listnode *ln; struct ipaddr *ip; for (ALL_LIST_ELEMENTS_RO(r->addrs, ln, ip)) vrrp_ndisc_una_send(r, ip); return 0; } void vrrp_ndisc_init(void) { frr_with_privs(&vrrp_privs) { ndisc_fd = socket(AF_PACKET, SOCK_RAW, htons(ETH_P_IPV6)); } if (ndisc_fd > 0) { DEBUGD(&vrrp_dbg_sock, VRRP_LOGPFX "Initialized Neighbor Discovery socket"); DEBUGD(&vrrp_dbg_ndisc, VRRP_LOGPFX "Initialized Neighbor Discovery subsystem"); } else { zlog_err(VRRP_LOGPFX "Error initializing Neighbor Discovery socket"); } } void vrrp_ndisc_fini(void) { close(ndisc_fd); ndisc_fd = -1; DEBUGD(&vrrp_dbg_ndisc, VRRP_LOGPFX "Deinitialized Neighbor Discovery subsystem"); } bool vrrp_ndisc_is_init(void) { return ndisc_fd > 0; } frr-7.2.1/vrrpd/vrrp_ndisc.h0000644000000000000000000000345213610377563012677 00000000000000/* * VRRP Neighbor Discovery. * Copyright (C) 2019 Cumulus Networks, Inc. * Quentin Young * * This program is free software; you can redistribute it and/or modify it * under the terms of the 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; see the file COPYING; if not, write to the Free Software * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA */ #ifndef __VRRP_NDISC_H__ #define __VRRP_NDISC_H__ #include #include #include #include "vrrp.h" /* * Initialize VRRP neighbor discovery. */ extern void vrrp_ndisc_init(void); /* * Check whether VRRP Neighbor Discovery is initialized. * * Returns: * True if initialized, false otherwise */ extern bool vrrp_ndisc_is_init(void); /* * Finish VRRP Neighbor Discovery. */ extern void vrrp_ndisc_fini(void); /* * Send VRRP Neighbor Advertisement. * * ifp * Interface to transmit on * * ip * IPv6 address to send Neighbor Advertisement for * * Returns: * -1 on failure * 0 otherwise */ extern int vrrp_ndisc_una_send(struct vrrp_router *r, struct ipaddr *ip); /* * Send VRRP Neighbor Advertisements for all virtual IPs. * * r * Virtual Router to send NA's for * * Returns: * -1 on failure * 0 otherwise */ extern int vrrp_ndisc_una_send_all(struct vrrp_router *r); #endif /* __VRRP_NDISC_H__ */ frr-7.2.1/vrrpd/vrrp_packet.c0000644000000000000000000002140313610377563013035 00000000000000/* * VRRP packet crafting. * Copyright (C) 2018-2019 Cumulus Networks, Inc. * Quentin Young * * This program is free software; you can redistribute it and/or modify it * under the terms of the 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; see the file COPYING; if not, write to the Free Software * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA */ #include #include #include #include #include "lib/checksum.h" #include "lib/ipaddr.h" #include "lib/memory.h" #include "vrrp.h" #include "vrrp_debug.h" #include "vrrp_packet.h" DEFINE_MTYPE_STATIC(VRRPD, VRRP_PKT, "VRRP packet") /* clang-format off */ const char *vrrp_packet_names[16] = { [0] = "Unknown", [VRRP_TYPE_ADVERTISEMENT] = "ADVERTISEMENT", [2] = "Unknown", [3] = "Unknown", [4] = "Unknown", [5] = "Unknown", [6] = "Unknown", [7] = "Unknown", [8] = "Unknown", [9] = "Unknown", [10] = "Unknown", [11] = "Unknown", [12] = "Unknown", [13] = "Unknown", [14] = "Unknown", [15] = "Unknown", }; /* clang-format on */ /* * Compute the VRRP checksum. * * Checksum is not set in the packet, just computed. * * pkt * VRRP packet, fully filled out except for checksum field. * * pktsize * sizeof(*pkt) * * src * IP address that pkt will be transmitted from. * * Returns: * VRRP checksum in network byte order. */ static uint16_t vrrp_pkt_checksum(struct vrrp_pkt *pkt, size_t pktsize, struct ipaddr *src) { uint16_t chksum; bool v6 = (src->ipa_type == IPADDR_V6); uint16_t chksum_pre = pkt->hdr.chksum; pkt->hdr.chksum = 0; if (v6) { struct ipv6_ph ph = {}; ph.src = src->ipaddr_v6; inet_pton(AF_INET6, VRRP_MCASTV6_GROUP_STR, &ph.dst); ph.ulpl = htons(pktsize); ph.next_hdr = IPPROTO_VRRP; chksum = in_cksum_with_ph6(&ph, pkt, pktsize); } else if (!v6 && ((pkt->hdr.vertype >> 4) == 3)) { struct ipv4_ph ph = {}; ph.src = src->ipaddr_v4; inet_pton(AF_INET, VRRP_MCASTV4_GROUP_STR, &ph.dst); ph.proto = IPPROTO_VRRP; ph.len = htons(pktsize); chksum = in_cksum_with_ph4(&ph, pkt, pktsize); } else if (!v6 && ((pkt->hdr.vertype >> 4) == 2)) { chksum = in_cksum(pkt, pktsize); } else { assert(!"Invalid VRRP protocol version"); } pkt->hdr.chksum = chksum_pre; return chksum; } ssize_t vrrp_pkt_adver_build(struct vrrp_pkt **pkt, struct ipaddr *src, uint8_t version, uint8_t vrid, uint8_t prio, uint16_t max_adver_int, uint8_t numip, struct ipaddr **ips) { bool v6 = false; size_t addrsz = 0; assert(version >= 2 && version <= 3); if (numip > 0) { v6 = IS_IPADDR_V6(ips[0]); addrsz = IPADDRSZ(ips[0]); } assert(!(version == 2 && v6)); size_t pktsize = VRRP_PKT_SIZE(v6 ? AF_INET6 : AF_INET, version, numip); *pkt = XCALLOC(MTYPE_VRRP_PKT, pktsize); (*pkt)->hdr.vertype |= version << 4; (*pkt)->hdr.vertype |= VRRP_TYPE_ADVERTISEMENT; (*pkt)->hdr.vrid = vrid; (*pkt)->hdr.priority = prio; (*pkt)->hdr.naddr = numip; if (version == 3) (*pkt)->hdr.v3.adver_int = htons(max_adver_int); else if (version == 2) { (*pkt)->hdr.v2.auth_type = 0; (*pkt)->hdr.v2.adver_int = MAX(max_adver_int / 100, 1); } uint8_t *aptr = (void *)(*pkt)->addrs; for (int i = 0; i < numip; i++) { memcpy(aptr, &ips[i]->ip.addr, addrsz); aptr += addrsz; } (*pkt)->hdr.chksum = vrrp_pkt_checksum(*pkt, pktsize, src); return pktsize; } void vrrp_pkt_free(struct vrrp_pkt *pkt) { XFREE(MTYPE_VRRP_PKT, pkt); } size_t vrrp_pkt_adver_dump(char *buf, size_t buflen, struct vrrp_pkt *pkt) { if (buflen < 1) return 0; char tmpbuf[BUFSIZ]; size_t rs = 0; struct vrrp_hdr *hdr = &pkt->hdr; buf[0] = 0x00; snprintf(tmpbuf, sizeof(tmpbuf), "version %u, ", (hdr->vertype >> 4)); rs += strlcat(buf, tmpbuf, buflen); snprintf(tmpbuf, sizeof(tmpbuf), "type %u (%s), ", (hdr->vertype & 0x0F), vrrp_packet_names[(hdr->vertype & 0x0F)]); rs += strlcat(buf, tmpbuf, buflen); snprintf(tmpbuf, sizeof(tmpbuf), "vrid %u, ", hdr->vrid); rs += strlcat(buf, tmpbuf, buflen); snprintf(tmpbuf, sizeof(tmpbuf), "priority %u, ", hdr->priority); rs += strlcat(buf, tmpbuf, buflen); snprintf(tmpbuf, sizeof(tmpbuf), "#%u addresses, ", hdr->naddr); rs += strlcat(buf, tmpbuf, buflen); snprintf(tmpbuf, sizeof(tmpbuf), "max adver int %u, ", ntohs(hdr->v3.adver_int)); rs += strlcat(buf, tmpbuf, buflen); snprintf(tmpbuf, sizeof(tmpbuf), "checksum %x", ntohs(hdr->chksum)); rs += strlcat(buf, tmpbuf, buflen); return rs; } ssize_t vrrp_pkt_parse_datagram(int family, int version, struct msghdr *m, size_t read, struct ipaddr *src, struct vrrp_pkt **pkt, char *errmsg, size_t errmsg_len) { /* Source (MAC & IP), Dest (MAC & IP) TTL validation done by kernel */ size_t addrsz = (family == AF_INET) ? sizeof(struct in_addr) : sizeof(struct in6_addr); size_t pktsize; uint8_t *buf = m->msg_iov->iov_base; #define VRRP_PKT_VCHECK(cond, _f, ...) \ do { \ if (!(cond)) { \ if (errmsg) \ snprintf(errmsg, errmsg_len, (_f), \ ##__VA_ARGS__); \ return -1; \ } \ } while (0) /* IPvX header check */ if (family == AF_INET) { VRRP_PKT_VCHECK( read >= sizeof(struct ip), "Datagram not large enough to contain IP header"); struct ip *ip = (struct ip *)buf; /* IP total length check */ VRRP_PKT_VCHECK( ntohs(ip->ip_len) == read, "IPv4 packet length field does not match # received bytes; %" PRIu16 "!= %zu", ntohs(ip->ip_len), read); /* TTL check */ VRRP_PKT_VCHECK(ip->ip_ttl == 255, "IPv4 TTL is %" PRIu8 "; should be 255", ip->ip_ttl); *pkt = (struct vrrp_pkt *)(buf + (ip->ip_hl << 2)); pktsize = read - (ip->ip_hl << 2); /* IP empty packet check */ VRRP_PKT_VCHECK(pktsize > 0, "IPv4 packet has no payload"); /* Extract source address */ struct sockaddr_in *sa = m->msg_name; src->ipa_type = IPADDR_V4; src->ipaddr_v4 = sa->sin_addr; } else if (family == AF_INET6) { struct cmsghdr *c; for (c = CMSG_FIRSTHDR(m); c != NULL; CMSG_NXTHDR(m, c)) { if (c->cmsg_level == IPPROTO_IPV6 && c->cmsg_type == IPV6_HOPLIMIT) break; } VRRP_PKT_VCHECK(!!c, "IPv6 Hop Limit not received"); uint8_t *hoplimit = CMSG_DATA(c); VRRP_PKT_VCHECK(*hoplimit == 255, "IPv6 Hop Limit is %" PRIu8 "; should be 255", *hoplimit); *pkt = (struct vrrp_pkt *)buf; pktsize = read; /* Extract source address */ struct sockaddr_in6 *sa = m->msg_name; src->ipa_type = IPADDR_V6; memcpy(&src->ipaddr_v6, &sa->sin6_addr, sizeof(struct in6_addr)); } else { assert(!"Unknown address family"); } /* Size check */ size_t minsize = (family == AF_INET) ? VRRP_MIN_PKT_SIZE_V4 : VRRP_MIN_PKT_SIZE_V6; size_t maxsize = (family == AF_INET) ? VRRP_MAX_PKT_SIZE_V4 : VRRP_MAX_PKT_SIZE_V6; VRRP_PKT_VCHECK(pktsize >= minsize, "VRRP packet is undersized (%zu < %zu)", pktsize, minsize); VRRP_PKT_VCHECK(pktsize <= maxsize, "VRRP packet is oversized (%zu > %zu)", pktsize, maxsize); /* Version check */ uint8_t pktver = (*pkt)->hdr.vertype >> 4; VRRP_PKT_VCHECK(pktver == version, "Bad version %u", pktver); /* Checksum check */ uint16_t chksum = vrrp_pkt_checksum(*pkt, pktsize, src); VRRP_PKT_VCHECK((*pkt)->hdr.chksum == chksum, "Bad VRRP checksum %" PRIx16 "; should be %" PRIx16 "", (*pkt)->hdr.chksum, chksum); /* Type check */ VRRP_PKT_VCHECK(((*pkt)->hdr.vertype & 0x0F) == 1, "Bad type %" PRIu8, (*pkt)->hdr.vertype & 0x0f); /* Exact size check */ size_t ves = VRRP_PKT_SIZE(family, pktver, (*pkt)->hdr.naddr); VRRP_PKT_VCHECK(pktsize == ves, "Packet has incorrect # addresses%s", pktver == 2 ? " or missing auth fields" : ""); /* auth type check */ if (version == 2) VRRP_PKT_VCHECK((*pkt)->hdr.v2.auth_type == 0, "Bad authentication type %" PRIu8, (*pkt)->hdr.v2.auth_type); /* Addresses check */ char vbuf[INET6_ADDRSTRLEN]; uint8_t *p = (uint8_t *)(*pkt)->addrs; for (uint8_t i = 0; i < (*pkt)->hdr.naddr; i++) { VRRP_PKT_VCHECK(inet_ntop(family, p, vbuf, sizeof(vbuf)), "Bad IP address, #%" PRIu8, i); p += addrsz; } /* Everything checks out */ return pktsize; } frr-7.2.1/vrrpd/vrrp_packet.h0000644000000000000000000001300113610377563013035 00000000000000/* * VRRP packet crafting. * Copyright (C) 2018-2019 Cumulus Networks, Inc. * Quentin Young * * This program is free software; you can redistribute it and/or modify it * under the terms of the 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; see the file COPYING; if not, write to the Free Software * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA */ #ifndef __VRRP_PACKET_H__ #define __VRRP_PACKET_H__ #include #include "lib/ipaddr.h" #include "lib/memory.h" #include "lib/prefix.h" #define VRRP_TYPE_ADVERTISEMENT 1 extern const char *vrrp_packet_names[16]; /* * Shared header for VRRPv2/v3 packets. */ struct vrrp_hdr { /* * H L H L * 0000 0000 * ver type */ uint8_t vertype; uint8_t vrid; uint8_t priority; uint8_t naddr; union { struct { uint8_t auth_type; /* advertisement interval (in sec) */ uint8_t adver_int; } v2; struct { /* * advertisement interval (in centiseconds) * H L H L * 0000 000000000000 * rsvd adver_int */ uint16_t adver_int; } v3; }; uint16_t chksum; } __attribute__((packed)); #define VRRP_HDR_SIZE sizeof(struct vrrp_hdr) struct vrrp_pkt { struct vrrp_hdr hdr; /* * When used, this is actually an array of one or the other, not an * array of union. If N v4 addresses are stored then * sizeof(addrs) == N * sizeof(struct in_addr). * * Under v2, the last 2 entries in this array are the authentication * data fields. We don't support auth in v2 so these are always just 8 * bytes of 0x00. */ union { struct in_addr v4; struct in6_addr v6; } addrs[]; } __attribute__((packed)); #define VRRP_PKT_SIZE(_f, _ver, _naddr) \ ({ \ size_t _asz = ((_f) == AF_INET) ? sizeof(struct in_addr) \ : sizeof(struct in6_addr); \ size_t _auth = 2 * sizeof(uint32_t) * (3 - (_ver)); \ sizeof(struct vrrp_hdr) + (_asz * (_naddr)) + _auth; \ }) #define VRRP_MIN_PKT_SIZE_V4 VRRP_PKT_SIZE(AF_INET, 3, 1) #define VRRP_MAX_PKT_SIZE_V4 VRRP_PKT_SIZE(AF_INET, 2, 255) #define VRRP_MIN_PKT_SIZE_V6 VRRP_PKT_SIZE(AF_INET6, 3, 1) #define VRRP_MAX_PKT_SIZE_V6 VRRP_PKT_SIZE(AF_INET6, 3, 255) #define VRRP_MIN_PKT_SIZE VRRP_MIN_PKT_SIZE_V4 #define VRRP_MAX_PKT_SIZE VRRP_MAX_PKT_SIZE_V6 /* * Builds a VRRP ADVERTISEMENT packet. * * pkt * Pointer to store pointer to result buffer in * * src * Source address packet will be transmitted from. This is needed to compute * the VRRP checksum. The returned packet must be sent in an IP datagram with * the source address equal to this field, or the checksum will be invalid. * * version * VRRP version; must be 2 or 3 * * vrid * Virtual Router Identifier * * prio * Virtual Router Priority * * max_adver_int * time between ADVERTISEMENTs * * v6 * whether 'ips' is an array of v4 or v6 addresses * * numip * number of IPvX addresses in 'ips' * * ips * array of pointer to either struct in_addr (v6 = false) or struct in6_addr * (v6 = true) */ ssize_t vrrp_pkt_adver_build(struct vrrp_pkt **pkt, struct ipaddr *src, uint8_t version, uint8_t vrid, uint8_t prio, uint16_t max_adver_int, uint8_t numip, struct ipaddr **ips); /* free memory allocated by vrrp_pkt_adver_build's pkt arg */ void vrrp_pkt_free(struct vrrp_pkt *pkt); /* * Dumps a VRRP ADVERTISEMENT packet to a string. * * Currently only dumps the header. * * buf * Buffer to store string representation * * buflen * Size of buf * * pkt * Packet to dump to a string * * Returns: * # bytes written to buf */ size_t vrrp_pkt_adver_dump(char *buf, size_t buflen, struct vrrp_pkt *pkt); /* * Parses a VRRP packet, checking for illegal or invalid data. * * This function parses both VRRPv2 and VRRPv3 packets. Which version is * expected is determined by the version argument. For example, if version is 3 * and the received packet has version field 2 it will fail to parse. * * Note that this function only checks whether the packet itself is a valid * VRRP packet. It is up to the caller to validate whether the VRID is correct, * priority and timer values are correct, etc. * * family * Address family of received packet * * version * VRRP version to use for validation * * m * msghdr containing results of recvmsg() on VRRP router socket * * read * Return value of recvmsg() on VRRP router socket; must be non-negative * * src * Pointer to struct ipaddr to store address of datagram sender * * pkt * Pointer to pointer to set to location of VRRP packet within buf * * errmsg * Buffer to store human-readable error message in case of error; may be * NULL, in which case no message will be stored * * errmsg_len * Size of errmsg * * Returns: * Size of VRRP packet, or -1 upon error */ ssize_t vrrp_pkt_parse_datagram(int family, int version, struct msghdr *m, size_t read, struct ipaddr *src, struct vrrp_pkt **pkt, char *errmsg, size_t errmsg_len); #endif /* __VRRP_PACKET_H__ */ frr-7.2.1/vrrpd/vrrp_vty.c0000644000000000000000000005405613610377563012422 00000000000000/* * VRRP CLI commands. * Copyright (C) 2018-2019 Cumulus Networks, Inc. * Quentin Young * * This program is free software; you can redistribute it and/or modify it * under the terms of the 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; see the file COPYING; if not, write to the Free Software * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA */ #include #include "lib/command.h" #include "lib/if.h" #include "lib/ipaddr.h" #include "lib/json.h" #include "lib/prefix.h" #include "lib/termtable.h" #include "lib/vty.h" #include "vrrp.h" #include "vrrp_debug.h" #include "vrrp_vty.h" #ifndef VTYSH_EXTRACT_PL #include "vrrpd/vrrp_vty_clippy.c" #endif #define VRRP_STR "Virtual Router Redundancy Protocol\n" #define VRRP_VRID_STR "Virtual Router ID\n" #define VRRP_PRIORITY_STR "Virtual Router Priority\n" #define VRRP_ADVINT_STR "Virtual Router Advertisement Interval\n" #define VRRP_IP_STR "Virtual Router IPv4 address\n" #define VRRP_VERSION_STR "VRRP protocol version\n" #define VROUTER_GET_VTY(_vty, _ifp, _vrid, _vr) \ do { \ _vr = vrrp_lookup(_ifp, _vrid); \ if (!_vr) { \ vty_out(_vty, \ "%% Please configure VRRP instance %u\n", \ (unsigned int)_vrid); \ return CMD_WARNING_CONFIG_FAILED; \ } \ } while (0) /* clang-format off */ DEFPY(vrrp_vrid, vrrp_vrid_cmd, "[no] vrrp (1-255)$vrid [version (2-3)]", NO_STR VRRP_STR VRRP_VRID_STR VRRP_VERSION_STR VRRP_VERSION_STR) { VTY_DECLVAR_CONTEXT(interface, ifp); struct vrrp_vrouter *vr = vrrp_lookup(ifp, vrid); if (version == 0) version = 3; if (no && vr) vrrp_vrouter_destroy(vr); else if (no && !vr) vty_out(vty, "%% VRRP instance %ld does not exist on %s\n", vrid, ifp->name); else if (!vr) vrrp_vrouter_create(ifp, vrid, version); else if (vr) vty_out(vty, "%% VRRP instance %ld already exists on %s\n", vrid, ifp->name); return CMD_SUCCESS; } DEFPY(vrrp_shutdown, vrrp_shutdown_cmd, "[no] vrrp (1-255)$vrid shutdown", NO_STR VRRP_STR VRRP_VRID_STR "Force VRRP router into administrative shutdown\n") { VTY_DECLVAR_CONTEXT(interface, ifp); struct vrrp_vrouter *vr; VROUTER_GET_VTY(vty, ifp, vrid, vr); if (!no) { if (vr->v4->fsm.state != VRRP_STATE_INITIALIZE) vrrp_event(vr->v4, VRRP_EVENT_SHUTDOWN); if (vr->v6->fsm.state != VRRP_STATE_INITIALIZE) vrrp_event(vr->v6, VRRP_EVENT_SHUTDOWN); vr->shutdown = true; } else { vr->shutdown = false; vrrp_check_start(vr); } return CMD_SUCCESS; } DEFPY(vrrp_priority, vrrp_priority_cmd, "[no] vrrp (1-255)$vrid priority (1-254)", NO_STR VRRP_STR VRRP_VRID_STR VRRP_PRIORITY_STR "Priority value") { VTY_DECLVAR_CONTEXT(interface, ifp); struct vrrp_vrouter *vr; uint8_t newprio = no ? vd.priority : priority; VROUTER_GET_VTY(vty, ifp, vrid, vr); vrrp_set_priority(vr, newprio); return CMD_SUCCESS; } DEFPY(vrrp_advertisement_interval, vrrp_advertisement_interval_cmd, "[no] vrrp (1-255)$vrid advertisement-interval (10-40950)", NO_STR VRRP_STR VRRP_VRID_STR VRRP_ADVINT_STR "Advertisement interval in milliseconds; must be multiple of 10") { VTY_DECLVAR_CONTEXT(interface, ifp); struct vrrp_vrouter *vr; uint16_t newadvint = no ? vd.advertisement_interval * 10 : advertisement_interval; if (newadvint % 10 != 0) { vty_out(vty, "%% Value must be a multiple of 10\n"); return CMD_WARNING_CONFIG_FAILED; } /* all internal computations are in centiseconds */ newadvint /= CS2MS; VROUTER_GET_VTY(vty, ifp, vrid, vr); vrrp_set_advertisement_interval(vr, newadvint); return CMD_SUCCESS; } DEFPY(vrrp_ip, vrrp_ip_cmd, "[no] vrrp (1-255)$vrid ip A.B.C.D", NO_STR VRRP_STR VRRP_VRID_STR "Add IPv4 address\n" VRRP_IP_STR) { VTY_DECLVAR_CONTEXT(interface, ifp); struct vrrp_vrouter *vr; bool deactivated = false; bool activated = false; bool failed = false; int ret = CMD_SUCCESS; int oldstate; VROUTER_GET_VTY(vty, ifp, vrid, vr); bool will_activate = (vr->v4->fsm.state == VRRP_STATE_INITIALIZE); if (no) { oldstate = vr->v4->fsm.state; failed = vrrp_del_ipv4(vr, ip); vrrp_check_start(vr); deactivated = (vr->v4->fsm.state == VRRP_STATE_INITIALIZE && oldstate != VRRP_STATE_INITIALIZE); } else { oldstate = vr->v4->fsm.state; failed = vrrp_add_ipv4(vr, ip); vrrp_check_start(vr); activated = (vr->v4->fsm.state != VRRP_STATE_INITIALIZE && oldstate == VRRP_STATE_INITIALIZE); } if (activated) vty_out(vty, "%% Activated IPv4 Virtual Router %ld\n", vrid); if (deactivated) vty_out(vty, "%% Deactivated IPv4 Virtual Router %ld\n", vrid); if (failed) { vty_out(vty, "%% Failed to %s virtual IP\n", no ? "remove" : "add"); ret = CMD_WARNING_CONFIG_FAILED; if (will_activate && !activated) { vty_out(vty, "%% Failed to activate IPv4 Virtual Router %ld\n", vrid); } } return ret; } DEFPY(vrrp_ip6, vrrp_ip6_cmd, "[no] vrrp (1-255)$vrid ipv6 X:X::X:X", NO_STR VRRP_STR VRRP_VRID_STR "Add IPv6 address\n" VRRP_IP_STR) { VTY_DECLVAR_CONTEXT(interface, ifp); struct vrrp_vrouter *vr; bool deactivated = false; bool activated = false; bool failed = false; int ret = CMD_SUCCESS; int oldstate; VROUTER_GET_VTY(vty, ifp, vrid, vr); if (vr->version != 3) { vty_out(vty, "%% Cannot add IPv6 address to VRRPv2 virtual router\n"); return CMD_WARNING_CONFIG_FAILED; } bool will_activate = (vr->v6->fsm.state == VRRP_STATE_INITIALIZE); if (no) { oldstate = vr->v6->fsm.state; failed = vrrp_del_ipv6(vr, ipv6); vrrp_check_start(vr); deactivated = (vr->v6->fsm.state == VRRP_STATE_INITIALIZE && oldstate != VRRP_STATE_INITIALIZE); } else { oldstate = vr->v6->fsm.state; failed = vrrp_add_ipv6(vr, ipv6); vrrp_check_start(vr); activated = (vr->v6->fsm.state != VRRP_STATE_INITIALIZE && oldstate == VRRP_STATE_INITIALIZE); } if (activated) vty_out(vty, "%% Activated IPv6 Virtual Router %ld\n", vrid); if (deactivated) vty_out(vty, "%% Deactivated IPv6 Virtual Router %ld\n", vrid); if (failed) { vty_out(vty, "%% Failed to %s virtual IP\n", no ? "remove" : "add"); ret = CMD_WARNING_CONFIG_FAILED; if (will_activate && !activated) { vty_out(vty, "%% Failed to activate IPv6 Virtual Router %ld\n", vrid); } } return ret; } DEFPY(vrrp_preempt, vrrp_preempt_cmd, "[no] vrrp (1-255)$vrid preempt", NO_STR VRRP_STR VRRP_VRID_STR "Preempt mode\n") { VTY_DECLVAR_CONTEXT(interface, ifp); struct vrrp_vrouter *vr; VROUTER_GET_VTY(vty, ifp, vrid, vr); vr->preempt_mode = !no; return CMD_SUCCESS; } DEFPY(vrrp_autoconfigure, vrrp_autoconfigure_cmd, "[no] vrrp autoconfigure [version (2-3)]", NO_STR VRRP_STR "Automatically set up VRRP instances on VRRP-compatible interfaces\n" "Version for automatically configured instances\n" VRRP_VERSION_STR) { version = version ? version : 3; if (!no) vrrp_autoconfig_on(version); else vrrp_autoconfig_off(); return CMD_SUCCESS; } DEFPY(vrrp_default, vrrp_default_cmd, "[no] vrrp default ", NO_STR VRRP_STR "Configure defaults for new VRRP instances\n" VRRP_ADVINT_STR "Advertisement interval in milliseconds\n" "Preempt mode\n" VRRP_PRIORITY_STR "Priority value\n" "Force VRRP router into administrative shutdown\n") { if (adv) { if (advint % 10 != 0) { vty_out(vty, "%% Value must be a multiple of 10\n"); return CMD_WARNING_CONFIG_FAILED; } /* all internal computations are in centiseconds */ advint /= CS2MS; vd.advertisement_interval = no ? VRRP_DEFAULT_ADVINT : advint; } if (p) vd.preempt_mode = !no; if (prio) vd.priority = no ? VRRP_DEFAULT_PRIORITY : prioval; if (s) vd.shutdown = !no; return CMD_SUCCESS; } /* clang-format on */ /* * Build JSON representation of VRRP instance. * * vr * VRRP router to build json object from * * Returns: * JSON representation of VRRP instance. Must be freed by caller. */ static struct json_object *vrrp_build_json(struct vrrp_vrouter *vr) { char ethstr4[ETHER_ADDR_STRLEN]; char ethstr6[ETHER_ADDR_STRLEN]; char ipstr[INET6_ADDRSTRLEN]; const char *stastr4 = vrrp_state_names[vr->v4->fsm.state]; const char *stastr6 = vrrp_state_names[vr->v6->fsm.state]; char sipstr4[INET6_ADDRSTRLEN] = {}; char sipstr6[INET6_ADDRSTRLEN] = {}; struct listnode *ln; struct ipaddr *ip; struct json_object *j = json_object_new_object(); struct json_object *v4 = json_object_new_object(); struct json_object *v4_stats = json_object_new_object(); struct json_object *v4_addrs = json_object_new_array(); struct json_object *v6 = json_object_new_object(); struct json_object *v6_stats = json_object_new_object(); struct json_object *v6_addrs = json_object_new_array(); prefix_mac2str(&vr->v4->vmac, ethstr4, sizeof(ethstr4)); prefix_mac2str(&vr->v6->vmac, ethstr6, sizeof(ethstr6)); json_object_int_add(j, "vrid", vr->vrid); json_object_int_add(j, "version", vr->version); json_object_boolean_add(j, "autoconfigured", vr->autoconf); json_object_boolean_add(j, "shutdown", vr->shutdown); json_object_boolean_add(j, "preemptMode", vr->preempt_mode); json_object_boolean_add(j, "acceptMode", vr->accept_mode); json_object_string_add(j, "interface", vr->ifp->name); json_object_int_add(j, "advertisementInterval", vr->advertisement_interval * CS2MS); /* v4 */ json_object_string_add(v4, "interface", vr->v4->mvl_ifp ? vr->v4->mvl_ifp->name : ""); json_object_string_add(v4, "vmac", ethstr4); ipaddr2str(&vr->v4->src, sipstr4, sizeof(sipstr4)); json_object_string_add(v4, "primaryAddress", sipstr4); json_object_string_add(v4, "status", stastr4); json_object_int_add(v4, "effectivePriority", vr->v4->priority); json_object_int_add(v4, "masterAdverInterval", vr->v4->master_adver_interval * CS2MS); json_object_int_add(v4, "skewTime", vr->v4->skew_time * CS2MS); json_object_int_add(v4, "masterDownInterval", vr->v4->master_down_interval * CS2MS); /* v4 stats */ json_object_int_add(v4_stats, "adverTx", vr->v4->stats.adver_tx_cnt); json_object_int_add(v4_stats, "adverRx", vr->v4->stats.adver_rx_cnt); json_object_int_add(v4_stats, "garpTx", vr->v4->stats.garp_tx_cnt); json_object_int_add(v4_stats, "transitions", vr->v4->stats.trans_cnt); json_object_object_add(v4, "stats", v4_stats); /* v4 addrs */ if (vr->v4->addrs->count) { for (ALL_LIST_ELEMENTS_RO(vr->v4->addrs, ln, ip)) { inet_ntop(vr->v4->family, &ip->ipaddr_v4, ipstr, sizeof(ipstr)); json_object_array_add(v4_addrs, json_object_new_string(ipstr)); } } json_object_object_add(v4, "addresses", v4_addrs); json_object_object_add(j, "v4", v4); /* v6 */ json_object_string_add(v6, "interface", vr->v6->mvl_ifp ? vr->v6->mvl_ifp->name : ""); json_object_string_add(v6, "vmac", ethstr6); ipaddr2str(&vr->v6->src, sipstr6, sizeof(sipstr6)); if (strlen(sipstr6) == 0 && vr->v6->src.ip.addr == 0x00) strlcat(sipstr6, "::", sizeof(sipstr6)); json_object_string_add(v6, "primaryAddress", sipstr6); json_object_string_add(v6, "status", stastr6); json_object_int_add(v6, "effectivePriority", vr->v6->priority); json_object_int_add(v6, "masterAdverInterval", vr->v6->master_adver_interval * CS2MS); json_object_int_add(v6, "skewTime", vr->v6->skew_time * CS2MS); json_object_int_add(v6, "masterDownInterval", vr->v6->master_down_interval * CS2MS); /* v6 stats */ json_object_int_add(v6_stats, "adverTx", vr->v6->stats.adver_tx_cnt); json_object_int_add(v6_stats, "adverRx", vr->v6->stats.adver_rx_cnt); json_object_int_add(v6_stats, "neighborAdverTx", vr->v6->stats.una_tx_cnt); json_object_int_add(v6_stats, "transitions", vr->v6->stats.trans_cnt); json_object_object_add(v6, "stats", v6_stats); /* v6 addrs */ if (vr->v6->addrs->count) { for (ALL_LIST_ELEMENTS_RO(vr->v6->addrs, ln, ip)) { inet_ntop(vr->v6->family, &ip->ipaddr_v6, ipstr, sizeof(ipstr)); json_object_array_add(v6_addrs, json_object_new_string(ipstr)); } } json_object_object_add(v6, "addresses", v6_addrs); json_object_object_add(j, "v6", v6); return j; } /* * Dump VRRP instance status to VTY. * * vty * vty to dump to * * vr * VRRP router to dump */ static void vrrp_show(struct vty *vty, struct vrrp_vrouter *vr) { char ethstr4[ETHER_ADDR_STRLEN]; char ethstr6[ETHER_ADDR_STRLEN]; char ipstr[INET6_ADDRSTRLEN]; const char *stastr4 = vrrp_state_names[vr->v4->fsm.state]; const char *stastr6 = vrrp_state_names[vr->v6->fsm.state]; char sipstr4[INET6_ADDRSTRLEN] = {}; char sipstr6[INET6_ADDRSTRLEN] = {}; struct listnode *ln; struct ipaddr *ip; struct ttable *tt = ttable_new(&ttable_styles[TTSTYLE_BLANK]); ttable_add_row(tt, "%s|%" PRIu32, "Virtual Router ID", vr->vrid); ttable_add_row(tt, "%s|%" PRIu8, "Protocol Version", vr->version); ttable_add_row(tt, "%s|%s", "Autoconfigured", vr->autoconf ? "Yes" : "No"); ttable_add_row(tt, "%s|%s", "Shutdown", vr->shutdown ? "Yes" : "No"); ttable_add_row(tt, "%s|%s", "Interface", vr->ifp->name); prefix_mac2str(&vr->v4->vmac, ethstr4, sizeof(ethstr4)); prefix_mac2str(&vr->v6->vmac, ethstr6, sizeof(ethstr6)); ttable_add_row(tt, "%s|%s", "VRRP interface (v4)", vr->v4->mvl_ifp ? vr->v4->mvl_ifp->name : "None"); ttable_add_row(tt, "%s|%s", "VRRP interface (v6)", vr->v6->mvl_ifp ? vr->v6->mvl_ifp->name : "None"); ipaddr2str(&vr->v4->src, sipstr4, sizeof(sipstr4)); ipaddr2str(&vr->v6->src, sipstr6, sizeof(sipstr6)); if (strlen(sipstr6) == 0 && vr->v6->src.ip.addr == 0x00) strlcat(sipstr6, "::", sizeof(sipstr6)); ttable_add_row(tt, "%s|%s", "Primary IP (v4)", sipstr4); ttable_add_row(tt, "%s|%s", "Primary IP (v6)", sipstr6); ttable_add_row(tt, "%s|%s", "Virtual MAC (v4)", ethstr4); ttable_add_row(tt, "%s|%s", "Virtual MAC (v6)", ethstr6); ttable_add_row(tt, "%s|%s", "Status (v4)", stastr4); ttable_add_row(tt, "%s|%s", "Status (v6)", stastr6); ttable_add_row(tt, "%s|%" PRIu8, "Priority", vr->priority); ttable_add_row(tt, "%s|%" PRIu8, "Effective Priority (v4)", vr->v4->priority); ttable_add_row(tt, "%s|%" PRIu8, "Effective Priority (v6)", vr->v6->priority); ttable_add_row(tt, "%s|%s", "Preempt Mode", vr->preempt_mode ? "Yes" : "No"); ttable_add_row(tt, "%s|%s", "Accept Mode", vr->accept_mode ? "Yes" : "No"); ttable_add_row(tt, "%s|%d ms", "Advertisement Interval", vr->advertisement_interval * CS2MS); ttable_add_row(tt, "%s|%d ms", "Master Advertisement Interval (v4)", vr->v4->master_adver_interval * CS2MS); ttable_add_row(tt, "%s|%d ms", "Master Advertisement Interval (v6)", vr->v6->master_adver_interval * CS2MS); ttable_add_row(tt, "%s|%" PRIu32, "Advertisements Tx (v4)", vr->v4->stats.adver_tx_cnt); ttable_add_row(tt, "%s|%" PRIu32, "Advertisements Tx (v6)", vr->v6->stats.adver_tx_cnt); ttable_add_row(tt, "%s|%" PRIu32, "Advertisements Rx (v4)", vr->v4->stats.adver_rx_cnt); ttable_add_row(tt, "%s|%" PRIu32, "Advertisements Rx (v6)", vr->v6->stats.adver_rx_cnt); ttable_add_row(tt, "%s|%" PRIu32, "Gratuitous ARP Tx (v4)", vr->v4->stats.garp_tx_cnt); ttable_add_row(tt, "%s|%" PRIu32, "Neigh. Adverts Tx (v6)", vr->v6->stats.una_tx_cnt); ttable_add_row(tt, "%s|%" PRIu32, "State transitions (v4)", vr->v4->stats.trans_cnt); ttable_add_row(tt, "%s|%" PRIu32, "State transitions (v6)", vr->v6->stats.trans_cnt); ttable_add_row(tt, "%s|%d ms", "Skew Time (v4)", vr->v4->skew_time * CS2MS); ttable_add_row(tt, "%s|%d ms", "Skew Time (v6)", vr->v6->skew_time * CS2MS); ttable_add_row(tt, "%s|%d ms", "Master Down Interval (v4)", vr->v4->master_down_interval * CS2MS); ttable_add_row(tt, "%s|%d ms", "Master Down Interval (v6)", vr->v6->master_down_interval * CS2MS); ttable_add_row(tt, "%s|%u", "IPv4 Addresses", vr->v4->addrs->count); char fill[35]; memset(fill, '.', sizeof(fill)); fill[sizeof(fill) - 1] = 0x00; if (vr->v4->addrs->count) { for (ALL_LIST_ELEMENTS_RO(vr->v4->addrs, ln, ip)) { inet_ntop(vr->v4->family, &ip->ipaddr_v4, ipstr, sizeof(ipstr)); ttable_add_row(tt, "%s|%s", fill, ipstr); } } ttable_add_row(tt, "%s|%u", "IPv6 Addresses", vr->v6->addrs->count); if (vr->v6->addrs->count) { for (ALL_LIST_ELEMENTS_RO(vr->v6->addrs, ln, ip)) { inet_ntop(vr->v6->family, &ip->ipaddr_v6, ipstr, sizeof(ipstr)); ttable_add_row(tt, "%s|%s", fill, ipstr); } } char *table = ttable_dump(tt, "\n"); vty_out(vty, "\n%s\n", table); XFREE(MTYPE_TMP, table); ttable_del(tt); } /* * Sort comparator, used when sorting VRRP instances for display purposes. * * Sorts by interface name first, then by VRID ascending. */ static int vrrp_instance_display_sort_cmp(const void **d1, const void **d2) { const struct vrrp_vrouter *vr1 = *d1; const struct vrrp_vrouter *vr2 = *d2; int result; result = strcmp(vr1->ifp->name, vr2->ifp->name); result += !result * (vr1->vrid - vr2->vrid); return result; } /* clang-format off */ DEFPY(vrrp_vrid_show, vrrp_vrid_show_cmd, "show vrrp [interface INTERFACE$ifn] [(1-255)$vrid] [json$json]", SHOW_STR VRRP_STR INTERFACE_STR "Only show VRRP instances on this interface\n" VRRP_VRID_STR JSON_STR) { struct vrrp_vrouter *vr; struct listnode *ln; struct list *ll = hash_to_list(vrrp_vrouters_hash); struct json_object *j = json_object_new_array(); list_sort(ll, vrrp_instance_display_sort_cmp); for (ALL_LIST_ELEMENTS_RO(ll, ln, vr)) { if (ifn && !strmatch(ifn, vr->ifp->name)) continue; if (vrid && ((uint8_t) vrid) != vr->vrid) continue; if (!json) vrrp_show(vty, vr); else json_object_array_add(j, vrrp_build_json(vr)); } if (json) vty_out(vty, "%s\n", json_object_to_json_string_ext( j, JSON_C_TO_STRING_PRETTY)); json_object_free(j); list_delete(&ll); return CMD_SUCCESS; } DEFPY(vrrp_vrid_show_summary, vrrp_vrid_show_summary_cmd, "show vrrp [interface INTERFACE$ifn] [(1-255)$vrid] summary", SHOW_STR VRRP_STR INTERFACE_STR "Only show VRRP instances on this interface\n" VRRP_VRID_STR "Summarize all VRRP instances\n") { struct vrrp_vrouter *vr; struct listnode *ln; struct list *ll = hash_to_list(vrrp_vrouters_hash); list_sort(ll, vrrp_instance_display_sort_cmp); struct ttable *tt = ttable_new(&ttable_styles[TTSTYLE_BLANK]); ttable_add_row( tt, "Interface|VRID|Priority|IPv4|IPv6|State (v4)|State (v6)"); ttable_rowseps(tt, 0, BOTTOM, true, '-'); for (ALL_LIST_ELEMENTS_RO(ll, ln, vr)) { if (ifn && !strmatch(ifn, vr->ifp->name)) continue; if (vrid && ((uint8_t)vrid) != vr->vrid) continue; ttable_add_row( tt, "%s|%" PRIu8 "|%" PRIu8 "|%d|%d|%s|%s", vr->ifp->name, vr->vrid, vr->priority, vr->v4->addrs->count, vr->v6->addrs->count, vr->v4->fsm.state == VRRP_STATE_MASTER ? "Master" : "Backup", vr->v6->fsm.state == VRRP_STATE_MASTER ? "Master" : "Backup"); } char *table = ttable_dump(tt, "\n"); vty_out(vty, "\n%s\n", table); XFREE(MTYPE_TMP, table); ttable_del(tt); list_delete(&ll); return CMD_SUCCESS; } DEFPY(debug_vrrp, debug_vrrp_cmd, "[no] debug vrrp [{protocol$proto|autoconfigure$ac|packets$pkt|sockets$sock|ndisc$ndisc|arp$arp|zebra$zebra}]", NO_STR DEBUG_STR VRRP_STR "Debug protocol state\n" "Debug autoconfiguration\n" "Debug sent and received packets\n" "Debug socket creation and configuration\n" "Debug Neighbor Discovery\n" "Debug ARP\n" "Debug Zebra events\n") { /* If no specific are given on/off them all */ if (strmatch(argv[argc - 1]->text, "vrrp")) vrrp_debug_set(NULL, 0, vty->node, !no, true, true, true, true, true, true, true); else vrrp_debug_set(NULL, 0, vty->node, !no, !!proto, !!ac, !!pkt, !!sock, !!ndisc, !!arp, !!zebra); return CMD_SUCCESS; } DEFUN_NOSH (show_debugging_vrrp, show_debugging_vrrp_cmd, "show debugging [vrrp]", SHOW_STR DEBUG_STR "VRRP information\n") { vty_out(vty, "VRRP debugging status:\n"); vrrp_debug_status_write(vty); return CMD_SUCCESS; } /* clang-format on */ static struct cmd_node interface_node = {INTERFACE_NODE, "%s(config-if)# ", 1}; static struct cmd_node debug_node = {DEBUG_NODE, "", 1}; static struct cmd_node vrrp_node = {VRRP_NODE, "", 1}; void vrrp_vty_init(void) { install_node(&debug_node, vrrp_config_write_debug); install_node(&interface_node, vrrp_config_write_interface); install_node(&vrrp_node, vrrp_config_write_global); if_cmd_init(); install_element(VIEW_NODE, &vrrp_vrid_show_cmd); install_element(VIEW_NODE, &vrrp_vrid_show_summary_cmd); install_element(VIEW_NODE, &show_debugging_vrrp_cmd); install_element(VIEW_NODE, &debug_vrrp_cmd); install_element(CONFIG_NODE, &debug_vrrp_cmd); install_element(CONFIG_NODE, &vrrp_autoconfigure_cmd); install_element(CONFIG_NODE, &vrrp_default_cmd); install_element(INTERFACE_NODE, &vrrp_vrid_cmd); install_element(INTERFACE_NODE, &vrrp_shutdown_cmd); install_element(INTERFACE_NODE, &vrrp_priority_cmd); install_element(INTERFACE_NODE, &vrrp_advertisement_interval_cmd); install_element(INTERFACE_NODE, &vrrp_ip_cmd); install_element(INTERFACE_NODE, &vrrp_ip6_cmd); install_element(INTERFACE_NODE, &vrrp_preempt_cmd); } frr-7.2.1/vrrpd/vrrp_vty.h0000644000000000000000000000164713610377563012425 00000000000000/* * VRRP CLI commands. * Copyright (C) 2018-2019 Cumulus Networks, Inc. * Quentin Young * * This program is free software; you can redistribute it and/or modify it * under the terms of the 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; see the file COPYING; if not, write to the Free Software * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA */ #ifndef __VRRP_VTY_H__ #define __VRRP_VTY_H__ void vrrp_vty_init(void); #endif /* __VRRP_VTY_H__ */ frr-7.2.1/vrrpd/vrrp_zebra.c0000644000000000000000000001556313610377563012703 00000000000000/* * VRRP Zebra interfacing. * Copyright (C) 2018-2019 Cumulus Networks, Inc. * Quentin Young * * This program is free software; you can redistribute it and/or modify it * under the terms of the 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; see the file COPYING; if not, write to the Free Software * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA */ #include #include "lib/if.h" #include "lib/linklist.h" #include "lib/log.h" #include "lib/prefix.h" #include "lib/vty.h" #include "lib/zclient.h" #include "vrrp.h" #include "vrrp_debug.h" #include "vrrp_zebra.h" #define VRRP_LOGPFX "[ZEBRA] " static struct zclient *zclient; static void vrrp_zebra_debug_if_state(struct interface *ifp, vrf_id_t vrf_id, const char *func) { DEBUGD(&vrrp_dbg_zebra, "%s: %s index %d(%u) parent %d mac %02x:%02x:%02x:%02x:%02x:%02x flags %ld metric %d mtu %d operative %d", func, ifp->name, vrf_id, ifp->link_ifindex, ifp->ifindex, ifp->hw_addr[0], ifp->hw_addr[1], ifp->hw_addr[2], ifp->hw_addr[3], ifp->hw_addr[4], ifp->hw_addr[5], (long)ifp->flags, ifp->metric, ifp->mtu, if_is_operative(ifp)); } static void vrrp_zebra_debug_if_dump_address(struct interface *ifp, const char *func) { struct connected *ifc; struct listnode *node; DEBUGD(&vrrp_dbg_zebra, "%s: interface %s addresses:", func, ifp->name); for (ALL_LIST_ELEMENTS_RO(ifp->connected, node, ifc)) { struct prefix *p = ifc->address; DEBUGD(&vrrp_dbg_zebra, "%s: interface %s address %s %s", func, ifp->name, inet_ntoa(p->u.prefix4), CHECK_FLAG(ifc->flags, ZEBRA_IFA_SECONDARY) ? "secondary" : "primary"); } } static void vrrp_zebra_connected(struct zclient *zclient) { zclient_send_reg_requests(zclient, VRF_DEFAULT); } /* Router-id update message from zebra. */ static int vrrp_router_id_update_zebra(int command, struct zclient *zclient, zebra_size_t length, vrf_id_t vrf_id) { struct prefix router_id; zebra_router_id_update_read(zclient->ibuf, &router_id); return 0; } static int vrrp_zebra_if_add(int command, struct zclient *zclient, zebra_size_t length, vrf_id_t vrf_id) { struct interface *ifp; /* * zebra api adds/dels interfaces using the same call * interface_add_read below, see comments in lib/zclient.c */ ifp = zebra_interface_add_read(zclient->ibuf, vrf_id); if (!ifp) return 0; vrrp_zebra_debug_if_state(ifp, vrf_id, __func__); vrrp_if_add(ifp); return 0; } static int vrrp_zebra_if_del(int command, struct zclient *zclient, zebra_size_t length, vrf_id_t vrf_id) { struct interface *ifp; ifp = zebra_interface_state_read(zclient->ibuf, vrf_id); if (!ifp) return 0; vrrp_zebra_debug_if_state(ifp, vrf_id, __func__); vrrp_if_del(ifp); if_set_index(ifp, IFINDEX_INTERNAL); return 0; } static int vrrp_zebra_if_state_up(int command, struct zclient *zclient, zebra_size_t length, vrf_id_t vrf_id) { struct interface *ifp; /* * zebra api notifies interface up/down events by using the same call * zebra_interface_state_read below, see comments in lib/zclient.c ifp = * zebra_interface_state_read(zclient->ibuf, vrf_id); */ ifp = zebra_interface_state_read(zclient->ibuf, vrf_id); if (!ifp) return 0; vrrp_zebra_debug_if_state(ifp, vrf_id, __func__); vrrp_if_up(ifp); return 0; } static int vrrp_zebra_if_state_down(int command, struct zclient *zclient, zebra_size_t length, vrf_id_t vrf_id) { struct interface *ifp; /* * zebra api notifies interface up/down events by using the same call * zebra_interface_state_read below, see comments in lib/zclient.c */ ifp = zebra_interface_state_read(zclient->ibuf, vrf_id); if (!ifp) return 0; vrrp_zebra_debug_if_state(ifp, vrf_id, __func__); vrrp_if_down(ifp); return 0; } static int vrrp_zebra_if_address_add(int command, struct zclient *zclient, zebra_size_t length, vrf_id_t vrf_id) { struct connected *c; /* * zebra api notifies address adds/dels events by using the same call * interface_add_read below, see comments in lib/zclient.c * * zebra_interface_address_read(ZEBRA_INTERFACE_ADDRESS_ADD, ...) * will add address to interface list by calling * connected_add_by_prefix() */ c = zebra_interface_address_read(command, zclient->ibuf, vrf_id); if (!c) return 0; vrrp_zebra_debug_if_state(c->ifp, vrf_id, __func__); vrrp_zebra_debug_if_dump_address(c->ifp, __func__); vrrp_if_address_add(c->ifp); return 0; } static int vrrp_zebra_if_address_del(int command, struct zclient *client, zebra_size_t length, vrf_id_t vrf_id) { struct connected *c; /* * zebra api notifies address adds/dels events by using the same call * interface_add_read below, see comments in lib/zclient.c * * zebra_interface_address_read(ZEBRA_INTERFACE_ADDRESS_DELETE, ...) * will remove address from interface list by calling * connected_delete_by_prefix() */ c = zebra_interface_address_read(command, client->ibuf, vrf_id); if (!c) return 0; vrrp_zebra_debug_if_state(c->ifp, vrf_id, __func__); vrrp_zebra_debug_if_dump_address(c->ifp, __func__); vrrp_if_address_del(c->ifp); return 0; } void vrrp_zebra_radv_set(struct vrrp_router *r, bool enable) { DEBUGD(&vrrp_dbg_zebra, VRRP_LOGPFX VRRP_LOGPFX_VRID "Requesting Zebra to turn router advertisements %s for %s", r->vr->vrid, enable ? "on" : "off", r->mvl_ifp->name); zclient_send_interface_radv_req(zclient, VRF_DEFAULT, r->mvl_ifp, enable, VRRP_RADV_INT); } int vrrp_zclient_send_interface_protodown(struct interface *ifp, bool down) { DEBUGD(&vrrp_dbg_zebra, VRRP_LOGPFX "Requesting Zebra to set %s protodown %s", ifp->name, down ? "on" : "off"); return zclient_send_interface_protodown(zclient, VRF_DEFAULT, ifp, down); } void vrrp_zebra_init(void) { /* Socket for receiving updates from Zebra daemon */ zclient = zclient_new(master, &zclient_options_default); zclient->zebra_connected = vrrp_zebra_connected; zclient->router_id_update = vrrp_router_id_update_zebra; zclient->interface_add = vrrp_zebra_if_add; zclient->interface_delete = vrrp_zebra_if_del; zclient->interface_up = vrrp_zebra_if_state_up; zclient->interface_down = vrrp_zebra_if_state_down; zclient->interface_address_add = vrrp_zebra_if_address_add; zclient->interface_address_delete = vrrp_zebra_if_address_del; zclient_init(zclient, ZEBRA_ROUTE_VRRP, 0, &vrrp_privs); zlog_notice("%s: zclient socket initialized", __PRETTY_FUNCTION__); } frr-7.2.1/vrrpd/vrrp_zebra.h0000644000000000000000000000220413610377563012674 00000000000000/* * VRRP Zebra interfacing. * Copyright (C) 2018-2019 Cumulus Networks, Inc. * Quentin Young * * This program is free software; you can redistribute it and/or modify it * under the terms of the 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; see the file COPYING; if not, write to the Free Software * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA */ #ifndef __VRRP_ZEBRA_H__ #define __VRRP_ZEBRA_H__ #include #include "lib/if.h" extern void vrrp_zebra_init(void); extern void vrrp_zebra_radv_set(struct vrrp_router *r, bool enable); extern int vrrp_zclient_send_interface_protodown(struct interface *ifp, bool down); #endif /* __VRRP_ZEBRA_H__ */ frr-7.2.1/vtysh/0000755000000000000000000000000013610377563010451 500000000000000frr-7.2.1/vtysh/Makefile0000644000000000000000000000022213610377563012025 00000000000000all: ALWAYS @$(MAKE) -s -C .. vtysh/vtysh %: ALWAYS @$(MAKE) -s -C .. vtysh/$@ Makefile: #nothing ALWAYS: .PHONY: ALWAYS makefiles .SUFFIXES: frr-7.2.1/vtysh/extract.pl.in0000755000000000000000000001677413610377563013027 00000000000000#! @PERL@ ## ## @configure_input@ ## ## Virtual terminal interface shell command extractor. ## Copyright (C) 2000 Kunihiro Ishiguro ## ## This file is part of GNU Zebra. ## ## GNU Zebra is free software; you can redistribute it and/or modify it ## under the terms of the GNU General Public License as published by the ## Free Software Foundation; either version 2, or (at your option) any ## later version. ## ## GNU Zebra is distributed in the hope that it will be useful, but ## WITHOUT ANY WARRANTY; without even the implied warranty of ## MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU ## General Public License for more details. ## ## You should have received a copy of the GNU General Public License ## along with GNU Zebra; see the file COPYING. If not, write to the Free ## Software Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA ## 02111-1307, USA. ## print < #include "command.h" #include "linklist.h" #include "vtysh/vtysh.h" EOF my $cli_stomp = 0; sub scan_file { my ( $file, $fabricd) = @_; $cppadd = $fabricd ? "-DFABRICD=1" : ""; open (FH, "@CPP@ -DHAVE_CONFIG_H -DVTYSH_EXTRACT_PL -Ivtysh/@top_builddir@ -Ivtysh/@top_srcdir@ -Ivtysh/@top_srcdir@/lib -Ivtysh/@top_builddir@/lib -Ivtysh/@top_srcdir@/bgpd -Ivtysh/@top_srcdir@/bgpd/rfapi @LUA_INCLUDE@ @CPPFLAGS@ $cppadd $file |"); local $/; undef $/; $line = ; close (FH); # ?: makes a group non-capturing @defun = ($line =~ /((?:DEFUN|DEFUN_HIDDEN|ALIAS|ALIAS_HIDDEN)\s*\(.+?\));?\s?\s?\n/sg); @install = ($line =~ /install_element\s*\(\s*[0-9A-Z_]+,\s*&[^;]*;\s*\n/sg); # DEFUN process foreach (@defun) { # $_ will contain the entire string including the DEFUN, ALIAS, etc. # We need to extract the DEFUN/ALIAS from everything in ()s. # The /s at the end tells the regex to allow . to match newlines. $_ =~ /^(.*?)\s*\((.*)\)$/s; my (@defun_array); $defun_or_alias = $1; @defun_array = split (/,/, $2); if ($defun_or_alias =~ /_HIDDEN/) { $hidden = 1; } else { $hidden = 0; } $defun_array[0] = ''; # Actual input command string. $str = "$defun_array[2]"; $str =~ s/^\s+//g; $str =~ s/\s+$//g; # Get VTY command structure. This is needed for searching # install_element() command. $cmd = "$defun_array[1]"; $cmd =~ s/^\s+//g; $cmd =~ s/\s+$//g; if ($fabricd) { $cmd = "fabricd_" . $cmd; } # $protocol is VTYSH_PROTO format for redirection of user input if ($file =~ /lib\/keychain\.c$/) { $protocol = "VTYSH_RIPD|VTYSH_EIGRPD"; } elsif ($file =~ /lib\/routemap\.c$/) { $protocol = "VTYSH_RMAP"; } elsif ($file =~ /lib\/vrf\.c$/) { $protocol = "VTYSH_VRF"; } elsif ($file =~ /lib\/if\.c$/) { $protocol = "VTYSH_INTERFACE"; } elsif ($file =~ /lib\/filter\.c$/) { $protocol = "VTYSH_ALL"; } elsif ($file =~ /lib\/agentx\.c$/) { $protocol = "VTYSH_RIPD|VTYSH_OSPFD|VTYSH_OSPF6D|VTYSH_BGPD|VTYSH_ZEBRA"; } elsif ($file =~ /lib\/nexthop_group\.c$/) { $protocol = "VTYSH_PBRD | VTYSH_SHARPD"; } elsif ($file =~ /lib\/plist\.c$/) { if ($defun_array[1] =~ m/ipv6/) { $protocol = "VTYSH_RIPNGD|VTYSH_OSPF6D|VTYSH_BGPD|VTYSH_ZEBRA|VTYSH_BABELD|VTYSH_ISISD|VTYSH_FABRICD"; } else { $protocol = "VTYSH_RIPD|VTYSH_OSPFD|VTYSH_BGPD|VTYSH_ZEBRA|VTYSH_PIMD|VTYSH_EIGRPD|VTYSH_BABELD|VTYSH_ISISD|VTYSH_FABRICD"; } } elsif ($file =~ /lib\/distribute\.c$/) { if ($defun_array[1] =~ m/ipv6/) { $protocol = "VTYSH_RIPNGD"; } else { $protocol = "VTYSH_RIPD"; } } elsif ($file =~ /lib\/if_rmap\.c$/) { if ($defun_array[1] =~ m/ipv6/) { $protocol = "VTYSH_RIPNGD"; } else { $protocol = "VTYSH_RIPD"; } } elsif ($file =~ /lib\/vty\.c$/) { $protocol = "VTYSH_ALL"; } elsif ($file =~ /librfp\/.*\.c$/ || $file =~ /rfapi\/.*\.c$/) { $protocol = "VTYSH_BGPD"; } elsif ($fabricd) { $protocol = "VTYSH_FABRICD"; } else { ($protocol) = ($file =~ /^.*\/([a-z0-9]+)\/[a-zA-Z0-9_\-]+\.c$/); $protocol = "VTYSH_" . uc $protocol; } # Append _vtysh to structure then build DEFUN again $defun_array[1] = $cmd . "_vtysh"; $defun_body = join (", ", @defun_array); # $cmd -> $str hash for lookup if (exists($cmd2str{$cmd})) { warn "Duplicate CLI Function: $cmd\n"; warn "\tFrom cli: $cmd2str{$cmd} to New cli: $str\n"; warn "\tOriginal Protocol: $cmd2proto{$cmd} to New Protocol: $protocol\n"; $cli_stomp++; } $cmd2str{$cmd} = $str; $cmd2defun{$cmd} = $defun_body; $cmd2proto{$cmd} = $protocol; $cmd2hidden{$cmd} = $hidden; } # install_element() process foreach (@install) { my (@element_array); @element_array = split (/,/); # Install node $enode = $element_array[0]; $enode =~ s/^\s+//g; $enode =~ s/\s+$//g; ($enode) = ($enode =~ /([0-9A-Z_]+)$/); # VTY command structure. ($ecmd) = ($element_array[1] =~ /&([^\)]+)/); $ecmd =~ s/^\s+//g; $ecmd =~ s/\s+$//g; if ($fabricd) { $ecmd = "fabricd_" . $ecmd; } # Register $ecmd if (defined ($cmd2str{$ecmd})) { my ($key); $key = $enode . "," . $cmd2str{$ecmd}; $ocmd{$key} = $ecmd; $odefun{$key} = $cmd2defun{$ecmd}; if ($cmd2hidden{$ecmd}) { $defsh{$key} = "DEFSH_HIDDEN" } else { $defsh{$key} = "DEFSH" } push (@{$oproto{$key}}, $cmd2proto{$ecmd}); } } } foreach (@ARGV) { if (/\/isisd\//) { # We scan all the IS-IS files twice, once for isisd, # once for fabricd. Exceptions are made for the files # that are not shared between the two. if (/isis_vty_isisd.c/) { scan_file($_, 0); } elsif (/isis_vty_fabricd.c/) { scan_file($_, 1); } else { scan_file($_, 0); scan_file($_, 1); } } else { scan_file($_, 0); } } # When we have cli commands that map to the same function name, we # can introduce subtle bugs due to code not being called when # we think it is. # # If extract.pl fails with a error message and you've been # modifying the cli, then go back and fix your code to # not have cli command function collisions. # please fix your code before submittal if ($cli_stomp) { warn "There are $cli_stomp command line stomps\n"; } # Check finaly alive $cmd; foreach (keys %odefun) { my ($node, $str) = (split (/,/)); my ($cmd) = $ocmd{$_}; $live{$cmd} = $_; } # Output DEFSH foreach (sort keys %live) { my ($proto); my ($key); $key = $live{$_}; $proto = join ("|", @{$oproto{$key}}); printf "$defsh{$key} ($proto$odefun{$key})\n\n"; } # Output install_element print < vtysh/vtysh_cmd.c frr-7.2.1/vtysh/vtysh.c0000644000000000000000000032371513610377563011725 00000000000000/* Virtual terminal interface shell. * Copyright (C) 2000 Kunihiro Ishiguro * * This file is part of GNU Zebra. * * GNU Zebra is free software; you can redistribute it and/or modify it * under the terms of the GNU General Public License as published by the * Free Software Foundation; either version 2, or (at your option) any * later version. * * GNU Zebra 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; see the file COPYING; if not, write to the Free Software * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA */ #include #include #include #include #include #include #include #include #include #include #include #include "linklist.h" #include "command.h" #include "memory.h" #include "filter.h" #include "vtysh/vtysh.h" #include "log.h" #include "bgpd/bgp_vty.h" #include "ns.h" #include "vrf.h" #include "libfrr.h" #include "command_graph.h" #include "frrstr.h" #include "json.h" #include "ferr.h" DEFINE_MTYPE_STATIC(MVTYSH, VTYSH_CMD, "Vtysh cmd copy") /* Struct VTY. */ struct vty *vty; /* VTY shell pager name. */ char *vtysh_pager_name = NULL; /* VTY shell client structure */ struct vtysh_client { int fd; const char *name; int flag; char path[MAXPATHLEN]; struct vtysh_client *next; }; /* Some utility functions for working on vtysh-specific vty tasks */ static FILE *vty_open_pager(struct vty *vty) { if (vty->is_paged) return vty->of; if (!vtysh_pager_name) return NULL; vty->of_saved = vty->of; vty->of = popen(vtysh_pager_name, "w"); if (vty->of == NULL) { vty->of = vty->of_saved; perror("popen"); exit(1); } vty->is_paged = true; return vty->of; } static int vty_close_pager(struct vty *vty) { if (!vty->is_paged) return 0; fflush(vty->of); if (pclose(vty->of) == -1) { perror("pclose"); exit(1); } vty->of = vty->of_saved; vty->is_paged = false; return 0; } static void vtysh_pager_envdef(bool fallback) { char *pager_defined; pager_defined = getenv("VTYSH_PAGER"); if (pager_defined) vtysh_pager_name = strdup(pager_defined); else if (fallback) vtysh_pager_name = strdup(VTYSH_PAGER); } /* --- */ struct vtysh_client vtysh_client[] = { {.fd = -1, .name = "zebra", .flag = VTYSH_ZEBRA, .next = NULL}, {.fd = -1, .name = "ripd", .flag = VTYSH_RIPD, .next = NULL}, {.fd = -1, .name = "ripngd", .flag = VTYSH_RIPNGD, .next = NULL}, {.fd = -1, .name = "ospfd", .flag = VTYSH_OSPFD, .next = NULL}, {.fd = -1, .name = "ospf6d", .flag = VTYSH_OSPF6D, .next = NULL}, {.fd = -1, .name = "ldpd", .flag = VTYSH_LDPD, .next = NULL}, {.fd = -1, .name = "bgpd", .flag = VTYSH_BGPD, .next = NULL}, {.fd = -1, .name = "isisd", .flag = VTYSH_ISISD, .next = NULL}, {.fd = -1, .name = "pimd", .flag = VTYSH_PIMD, .next = NULL}, {.fd = -1, .name = "nhrpd", .flag = VTYSH_NHRPD, .next = NULL}, {.fd = -1, .name = "eigrpd", .flag = VTYSH_EIGRPD, .next = NULL}, {.fd = -1, .name = "babeld", .flag = VTYSH_BABELD, .next = NULL}, {.fd = -1, .name = "sharpd", .flag = VTYSH_SHARPD, .next = NULL}, {.fd = -1, .name = "fabricd", .flag = VTYSH_FABRICD, .next = NULL}, {.fd = -1, .name = "watchfrr", .flag = VTYSH_WATCHFRR, .next = NULL}, {.fd = -1, .name = "pbrd", .flag = VTYSH_PBRD, .next = NULL}, {.fd = -1, .name = "staticd", .flag = VTYSH_STATICD, .next = NULL}, {.fd = -1, .name = "bfdd", .flag = VTYSH_BFDD, .next = NULL}, {.fd = -1, .name = "vrrpd", .flag = VTYSH_VRRPD, .next = NULL}, }; /* Searches for client by name, returns index */ static int vtysh_client_lookup(const char *name) { int idx = -1; for (unsigned int i = 0; i < array_size(vtysh_client); i++) { if (strmatch(vtysh_client[i].name, name)) { idx = i; break; } } return idx; } enum vtysh_write_integrated vtysh_write_integrated = WRITE_INTEGRATED_UNSPECIFIED; static int vtysh_reconnect(struct vtysh_client *vclient); static void vclient_close(struct vtysh_client *vclient) { if (vclient->fd >= 0) { if (vty->of) vty_out(vty, "Warning: closing connection to %s because of an I/O error!\n", vclient->name); close(vclient->fd); /* indicate as candidate for reconnect */ vclient->fd = VTYSH_WAS_ACTIVE; } } /* * Send a CLI command to a client and read the response. * * Output will be printed to vty->of. If you want to suppress output, set that * to NULL. * * vclient * the client to send the command to * * line * the command to send * * callback * if non-null, this will be called with each line of output received from * the client passed in the second parameter * * cbarg * optional first argument to pass to callback * * Returns: * a status code */ static int vtysh_client_run(struct vtysh_client *vclient, const char *line, void (*callback)(void *, const char *), void *cbarg) { int ret; char stackbuf[4096]; char *buf = stackbuf; size_t bufsz = sizeof(stackbuf); char *bufvalid, *end = NULL; char terminator[3] = {0, 0, 0}; /* vclinet was previously active, try to reconnect */ if (vclient->fd == VTYSH_WAS_ACTIVE) { ret = vtysh_reconnect(vclient); if (ret < 0) goto out_err; } if (vclient->fd < 0) return CMD_SUCCESS; ret = write(vclient->fd, line, strlen(line) + 1); if (ret <= 0) { /* close connection and try to reconnect */ vclient_close(vclient); ret = vtysh_reconnect(vclient); if (ret < 0) goto out_err; /* retry line */ ret = write(vclient->fd, line, strlen(line) + 1); if (ret <= 0) goto out_err; } bufvalid = buf; do { ssize_t nread = read(vclient->fd, bufvalid, buf + bufsz - bufvalid - 1); if (nread < 0 && (errno == EINTR || errno == EAGAIN)) continue; if (nread <= 0) { if (vty->of) vty_out(vty, "vtysh: error reading from %s: %s (%d)", vclient->name, safe_strerror(errno), errno); goto out_err; } bufvalid += nread; /* Null terminate so we may pass this to *printf later. */ bufvalid[0] = '\0'; /* * We expect string output from daemons, so instead of looking * for the full 3 null bytes of the terminator, we check for * just one instead and assume it is the first byte of the * terminator. The presence of the full terminator is checked * later. */ if (bufvalid - buf >= 4) end = memmem(bufvalid - 4, 4, "\0", 1); /* * calculate # bytes we have, up to & not including the * terminator if present */ size_t textlen = (end ? end : bufvalid) - buf; bool b = false; /* feed line processing callback if present */ while (callback && bufvalid > buf && (end > buf || !end)) { textlen = (end ? end : bufvalid) - buf; char *eol = memchr(buf, '\n', textlen); if (eol) /* line break */ *eol++ = '\0'; else if (end == buf) /* * no line break, end of input, no text left * before end; nothing to write */ b = true; else if (end) /* no nl, end of input, but some text left */ eol = end; else if (bufvalid == buf + bufsz - 1) { /* * no nl, no end of input, no buffer space; * realloc */ char *new; bufsz *= 2; if (buf == stackbuf) { new = XMALLOC(MTYPE_TMP, bufsz); memcpy(new, stackbuf, sizeof(stackbuf)); } else new = XREALLOC(MTYPE_TMP, buf, bufsz); bufvalid = bufvalid - buf + new; buf = new; /* if end != NULL, we won't be reading more * data... */ assert(end == NULL); b = true; } else b = true; if (b) break; /* eol is at line end now, either \n => \0 or \0\0\0 */ assert(eol && eol <= bufvalid); if (vty->of) vty_out(vty, "%s\n", buf); callback(cbarg, buf); /* shift back data and adjust bufvalid */ memmove(buf, eol, bufvalid - eol); bufvalid -= eol - buf; if (end) end -= eol - buf; } /* else if no callback, dump raw */ if (!callback) { if (vty->of) vty_out(vty, "%s", buf); memmove(buf, buf + textlen, bufvalid - buf - textlen); bufvalid -= textlen; if (end) end -= textlen; /* * ---------------------------------------------------- * At this point `buf` should be in one of two states: * - Empty (i.e. buf == bufvalid) * - Contains up to 4 bytes of the terminator * ---------------------------------------------------- */ assert(((buf == bufvalid) || (bufvalid - buf <= 4 && buf[0] == 0x00))); } /* if we have the terminator, break */ if (end && bufvalid - buf == 4) { assert(!memcmp(buf, terminator, 3)); ret = buf[3]; break; } } while (true); goto out; out_err: vclient_close(vclient); ret = CMD_SUCCESS; out: if (buf != stackbuf) XFREE(MTYPE_TMP, buf); return ret; } static int vtysh_client_run_all(struct vtysh_client *head_client, const char *line, int continue_on_err, void (*callback)(void *, const char *), void *cbarg) { struct vtysh_client *client; int rc, rc_all = CMD_SUCCESS; int correct_instance = 0, wrong_instance = 0; for (client = head_client; client; client = client->next) { rc = vtysh_client_run(client, line, callback, cbarg); if (rc == CMD_NOT_MY_INSTANCE) { wrong_instance++; continue; } if (client->fd > 0) correct_instance++; if (rc != CMD_SUCCESS) { if (!continue_on_err) return rc; rc_all = rc; } } if (wrong_instance && !correct_instance && vty->of) { vty_out(vty, "%% [%s]: command ignored as it targets an instance that is not running\n", head_client->name); rc_all = CMD_WARNING_CONFIG_FAILED; } return rc_all; } /* * Execute command against all daemons. * * head_client * where to start walking in the daemon list * * line * the specific command to execute * * Returns: * a status code */ static int vtysh_client_execute(struct vtysh_client *head_client, const char *line) { return vtysh_client_run_all(head_client, line, 0, NULL, NULL); } /* Execute by name */ static int vtysh_client_execute_name(const char *name, const char *line) { int ret = CMD_SUCCESS; int idx_client = -1; idx_client = vtysh_client_lookup(name); if (idx_client != -1) ret = vtysh_client_execute(&vtysh_client[idx_client], line); else { vty_out(vty, "Client not found\n"); ret = CMD_WARNING; } return ret; } /* * Retrieve all running config from daemons and parse it with the vtysh config * parser. Returned output is not displayed to the user. * * head_client * where to start walking in the daemon list * * line * the specific command to execute */ static void vtysh_client_config(struct vtysh_client *head_client, char *line) { /* watchfrr currently doesn't load any config, and has some hardcoded * settings that show up in "show run". skip it here (for now at * least) so we don't get that mangled up in config-write. */ if (head_client->flag == VTYSH_WATCHFRR) return; /* suppress output to user */ vty->of_saved = vty->of; vty->of = NULL; vtysh_client_run_all(head_client, line, 1, vtysh_config_parse_line, NULL); vty->of = vty->of_saved; } /* Command execution over the vty interface. */ static int vtysh_execute_func(const char *line, int pager) { int ret, cmd_stat; unsigned int i; vector vline; const struct cmd_element *cmd; int tried = 0; int saved_ret, saved_node; /* Split readline string up into the vector. */ vline = cmd_make_strvec(line); if (vline == NULL) return CMD_SUCCESS; if (user_mode) { if (strncmp("en", vector_slot(vline, 0), 2) == 0) { cmd_free_strvec(vline); vty_out(vty, "%% Command not allowed: enable\n"); return CMD_WARNING; } } saved_ret = ret = cmd_execute(vty, line, &cmd, 1); saved_node = vty->node; /* * If command doesn't succeeded in current node, try to walk up in node * tree. Changing vty->node is enough to try it just out without actual * walkup in the vtysh. */ while (ret != CMD_SUCCESS && ret != CMD_SUCCESS_DAEMON && ret != CMD_WARNING && ret != CMD_WARNING_CONFIG_FAILED && vty->node > CONFIG_NODE) { vty->node = node_parent(vty->node); ret = cmd_execute(vty, line, &cmd, 1); tried++; } vty->node = saved_node; /* * If command succeeded in any other node than current (tried > 0) we * have to move into node in the vtysh where it succeeded. */ if (ret == CMD_SUCCESS || ret == CMD_SUCCESS_DAEMON || ret == CMD_WARNING) { if ((saved_node == BGP_VPNV4_NODE || saved_node == BGP_VPNV6_NODE || saved_node == BGP_IPV4_NODE || saved_node == BGP_IPV6_NODE || saved_node == BGP_FLOWSPECV4_NODE || saved_node == BGP_FLOWSPECV6_NODE || saved_node == BGP_IPV4M_NODE || saved_node == BGP_IPV4L_NODE || saved_node == BGP_IPV6L_NODE || saved_node == BGP_IPV6M_NODE || saved_node == BGP_EVPN_NODE || saved_node == LDP_IPV4_NODE || saved_node == LDP_IPV6_NODE) && (tried == 1)) { vtysh_execute("exit-address-family"); } else if ((saved_node == BGP_EVPN_VNI_NODE) && (tried == 1)) { vtysh_execute("exit-vni"); } else if (saved_node == BGP_VRF_POLICY_NODE && (tried == 1)) { vtysh_execute("exit-vrf-policy"); } else if ((saved_node == BGP_VNC_DEFAULTS_NODE || saved_node == BGP_VNC_NVE_GROUP_NODE || saved_node == BGP_VNC_L2_GROUP_NODE) && (tried == 1)) { vtysh_execute("exit-vnc"); } else if (saved_node == VRF_NODE && (tried == 1)) { vtysh_execute("exit-vrf"); } else if ((saved_node == KEYCHAIN_KEY_NODE || saved_node == LDP_PSEUDOWIRE_NODE || saved_node == LDP_IPV4_IFACE_NODE || saved_node == LDP_IPV6_IFACE_NODE) && (tried == 1)) { vtysh_execute("exit"); } else if (tried) { vtysh_execute("end"); vtysh_execute("configure"); } } /* * If command didn't succeed in any node, continue with return value * from first try. */ else if (tried) { ret = saved_ret; } cmd_free_strvec(vline); cmd_stat = ret; switch (ret) { case CMD_WARNING: case CMD_WARNING_CONFIG_FAILED: if (vty->type == VTY_FILE) vty_out(vty, "Warning...\n"); break; case CMD_ERR_AMBIGUOUS: vty_out(vty, "%% Ambiguous command: %s\n", line); break; case CMD_ERR_NO_MATCH: vty_out(vty, "%% Unknown command: %s\n", line); break; case CMD_ERR_INCOMPLETE: vty_out(vty, "%% Command incomplete: %s\n", line); break; case CMD_SUCCESS_DAEMON: { /* * FIXME: Don't open pager for exit commands. popen() causes * problems if exited from vtysh at all. This hack shouldn't * cause any problem but is really ugly. */ if (pager && strncmp(line, "exit", 4)) vty_open_pager(vty); if (!strcmp(cmd->string, "configure")) { for (i = 0; i < array_size(vtysh_client); i++) { cmd_stat = vtysh_client_execute( &vtysh_client[i], line); if (cmd_stat == CMD_WARNING) break; } if (cmd_stat) { line = "end"; vline = cmd_make_strvec(line); if (vline == NULL) { if (vty->is_paged) vty_close_pager(vty); return CMD_SUCCESS; } ret = cmd_execute_command(vline, vty, &cmd, 1); cmd_free_strvec(vline); if (ret != CMD_SUCCESS_DAEMON) break; } else if (cmd->func) { (*cmd->func)(cmd, vty, 0, NULL); break; } } cmd_stat = CMD_SUCCESS; struct vtysh_client *vc; for (i = 0; i < array_size(vtysh_client); i++) { if (cmd->daemon & vtysh_client[i].flag) { if (vtysh_client[i].fd < 0 && (cmd->daemon == vtysh_client[i].flag)) { for (vc = &vtysh_client[i]; vc; vc = vc->next) if (vc->fd == VTYSH_WAS_ACTIVE) vtysh_reconnect(vc); } if (vtysh_client[i].fd < 0 && (cmd->daemon == vtysh_client[i].flag)) { bool any_inst = false; for (vc = &vtysh_client[i]; vc; vc = vc->next) any_inst = any_inst || (vc->fd > 0); if (!any_inst) { fprintf(stderr, "%s is not running\n", vtysh_client[i].name); continue; } } cmd_stat = vtysh_client_execute( &vtysh_client[i], line); if (cmd_stat != CMD_SUCCESS) break; } } if (cmd_stat != CMD_SUCCESS) break; if (cmd->func) (*cmd->func)(cmd, vty, 0, NULL); } } if (vty->is_paged) vty_close_pager(vty); return cmd_stat; } int vtysh_execute_no_pager(const char *line) { return vtysh_execute_func(line, 0); } int vtysh_execute(const char *line) { return vtysh_execute_func(line, 1); } static char *trim(char *s) { size_t size; char *end; size = strlen(s); if (!size) return s; end = s + size - 1; while (end >= s && isspace((unsigned char)*end)) end--; *(end + 1) = '\0'; while (*s && isspace((unsigned char)*s)) s++; return s; } int vtysh_mark_file(const char *filename) { struct vty *vty; FILE *confp = NULL; int ret; vector vline; int tried = 0; const struct cmd_element *cmd; int saved_ret, prev_node; int lineno = 0; char *vty_buf_copy = NULL; char *vty_buf_trimmed = NULL; if (strncmp("-", filename, 1) == 0) confp = stdin; else confp = fopen(filename, "r"); if (confp == NULL) { fprintf(stderr, "%% Can't open config file %s due to '%s'.\n", filename, safe_strerror(errno)); return (CMD_ERR_NO_FILE); } vty = vty_new(); vty->wfd = STDERR_FILENO; vty->type = VTY_TERM; vty->node = CONFIG_NODE; vtysh_execute_no_pager("enable"); vtysh_execute_no_pager("configure"); vty_buf_copy = XCALLOC(MTYPE_VTYSH_CMD, VTY_BUFSIZ); while (fgets(vty->buf, VTY_BUFSIZ, confp)) { lineno++; tried = 0; strlcpy(vty_buf_copy, vty->buf, VTY_BUFSIZ); vty_buf_trimmed = trim(vty_buf_copy); switch (vty->node) { case LDP_IPV4_IFACE_NODE: if (strncmp(vty_buf_copy, " ", 3)) { vty_out(vty, " end\n"); vty->node = LDP_IPV4_NODE; } break; case LDP_IPV6_IFACE_NODE: if (strncmp(vty_buf_copy, " ", 3)) { vty_out(vty, " end\n"); vty->node = LDP_IPV6_NODE; } break; case LDP_PSEUDOWIRE_NODE: if (strncmp(vty_buf_copy, " ", 2)) { vty_out(vty, " end\n"); vty->node = LDP_L2VPN_NODE; } break; default: break; } if (vty_buf_trimmed[0] == '!' || vty_buf_trimmed[0] == '#') { vty_out(vty, "%s", vty->buf); continue; } /* Split readline string up into the vector. */ vline = cmd_make_strvec(vty->buf); if (vline == NULL) { vty_out(vty, "%s", vty->buf); continue; } /* * Ignore the "end" lines, we will generate these where * appropriate */ if (strlen(vty_buf_trimmed) == 3 && strncmp("end", vty_buf_trimmed, 3) == 0) { cmd_free_strvec(vline); continue; } prev_node = vty->node; saved_ret = ret = cmd_execute_command_strict(vline, vty, &cmd); /* * If command doesn't succeeded in current node, try to walk up * in node tree. Changing vty->node is enough to try it just * out without actual walkup in the vtysh. */ while (ret != CMD_SUCCESS && ret != CMD_SUCCESS_DAEMON && ret != CMD_WARNING && ret != CMD_WARNING_CONFIG_FAILED && vty->node > CONFIG_NODE) { vty->node = node_parent(vty->node); ret = cmd_execute_command_strict(vline, vty, &cmd); tried++; } /* * If command succeeded in any other node than current (tried > * 0) we have to move into node in the vtysh where it * succeeded. */ if (ret == CMD_SUCCESS || ret == CMD_SUCCESS_DAEMON || ret == CMD_WARNING) { if ((prev_node == BGP_VPNV4_NODE || prev_node == BGP_VPNV6_NODE || prev_node == BGP_IPV4_NODE || prev_node == BGP_IPV6_NODE || prev_node == BGP_FLOWSPECV4_NODE || prev_node == BGP_FLOWSPECV6_NODE || prev_node == BGP_IPV4L_NODE || prev_node == BGP_IPV6L_NODE || prev_node == BGP_IPV4M_NODE || prev_node == BGP_IPV6M_NODE || prev_node == BGP_EVPN_NODE) && (tried == 1)) { vty_out(vty, "exit-address-family\n"); } else if ((prev_node == BGP_EVPN_VNI_NODE) && (tried == 1)) { vty_out(vty, "exit-vni\n"); } else if ((prev_node == KEYCHAIN_KEY_NODE) && (tried == 1)) { vty_out(vty, "exit\n"); } else if (tried) { vty_out(vty, "end\n"); } } /* * If command didn't succeed in any node, continue with return * value from first try. */ else if (tried) { ret = saved_ret; vty->node = prev_node; } cmd_free_strvec(vline); switch (ret) { case CMD_WARNING: case CMD_WARNING_CONFIG_FAILED: if (vty->type == VTY_FILE) fprintf(stderr, "line %d: Warning...: %s\n", lineno, vty->buf); fclose(confp); vty_close(vty); XFREE(MTYPE_VTYSH_CMD, vty_buf_copy); return ret; case CMD_ERR_AMBIGUOUS: fprintf(stderr, "line %d: %% Ambiguous command: %s\n", lineno, vty->buf); fclose(confp); vty_close(vty); XFREE(MTYPE_VTYSH_CMD, vty_buf_copy); return CMD_ERR_AMBIGUOUS; case CMD_ERR_NO_MATCH: fprintf(stderr, "line %d: %% Unknown command: %s\n", lineno, vty->buf); fclose(confp); vty_close(vty); XFREE(MTYPE_VTYSH_CMD, vty_buf_copy); return CMD_ERR_NO_MATCH; case CMD_ERR_INCOMPLETE: fprintf(stderr, "line %d: %% Command incomplete: %s\n", lineno, vty->buf); fclose(confp); vty_close(vty); XFREE(MTYPE_VTYSH_CMD, vty_buf_copy); return CMD_ERR_INCOMPLETE; case CMD_SUCCESS: vty_out(vty, "%s", vty->buf); if (strmatch(vty_buf_trimmed, "exit-vrf")) vty_out(vty, "end\n"); break; case CMD_SUCCESS_DAEMON: { int cmd_stat; vty_out(vty, "%s", vty->buf); if (strmatch(vty_buf_trimmed, "exit-vrf")) vty_out(vty, "end\n"); cmd_stat = vtysh_client_execute(&vtysh_client[0], vty->buf); if (cmd_stat != CMD_SUCCESS) break; if (cmd->func) (*cmd->func)(cmd, vty, 0, NULL); } } } /* This is the end */ vty_out(vty, "\nend\n"); vty_close(vty); XFREE(MTYPE_VTYSH_CMD, vty_buf_copy); if (confp != stdin) fclose(confp); return (0); } /* Configration make from file. */ int vtysh_config_from_file(struct vty *vty, FILE *fp) { int ret; const struct cmd_element *cmd; int lineno = 0; /* once we have an error, we remember & return that */ int retcode = CMD_SUCCESS; while (fgets(vty->buf, VTY_BUFSIZ, fp)) { lineno++; ret = command_config_read_one_line(vty, &cmd, lineno, 1); switch (ret) { case CMD_WARNING: case CMD_WARNING_CONFIG_FAILED: if (vty->type == VTY_FILE) fprintf(stderr, "line %d: Warning[%d]...: %s\n", lineno, vty->node, vty->buf); retcode = ret; break; case CMD_ERR_AMBIGUOUS: fprintf(stderr, "line %d: %% Ambiguous command[%d]: %s\n", lineno, vty->node, vty->buf); retcode = CMD_ERR_AMBIGUOUS; break; case CMD_ERR_NO_MATCH: fprintf(stderr, "line %d: %% Unknown command[%d]: %s", lineno, vty->node, vty->buf); retcode = CMD_ERR_NO_MATCH; break; case CMD_ERR_INCOMPLETE: fprintf(stderr, "line %d: %% Command incomplete[%d]: %s\n", lineno, vty->node, vty->buf); retcode = CMD_ERR_INCOMPLETE; break; case CMD_SUCCESS_DAEMON: { unsigned int i; int cmd_stat = CMD_SUCCESS; for (i = 0; i < array_size(vtysh_client); i++) { if (cmd->daemon & vtysh_client[i].flag) { cmd_stat = vtysh_client_execute( &vtysh_client[i], vty->buf); /* * CMD_WARNING - Can mean that the * command was parsed successfully but * it was already entered in a few * spots. As such if we receive a * CMD_WARNING from a daemon we * shouldn't stop talking to the other * daemons for the particular command. */ if (cmd_stat != CMD_SUCCESS && cmd_stat != CMD_WARNING) { fprintf(stderr, "line %d: Failure to communicate[%d] to %s, line: %s\n", lineno, cmd_stat, vtysh_client[i].name, vty->buf); retcode = cmd_stat; break; } } } if (cmd_stat != CMD_SUCCESS) break; if (cmd->func) (*cmd->func)(cmd, vty, 0, NULL); } } } return (retcode); } /* * Function processes cli commands terminated with '?' character when entered * through either 'vtysh' or 'vtysh -c' interfaces. */ static int vtysh_process_questionmark(const char *input, int input_len) { int ret, width = 0; unsigned int i; vector vline, describe; struct cmd_token *token; if (!input) return 1; vline = cmd_make_strvec(input); /* In case of '> ?'. */ if (vline == NULL) { vline = vector_init(1); vector_set(vline, NULL); } else if (input_len && isspace((unsigned char)input[input_len - 1])) vector_set(vline, NULL); describe = cmd_describe_command(vline, vty, &ret); /* Ambiguous and no match error. */ switch (ret) { case CMD_ERR_AMBIGUOUS: cmd_free_strvec(vline); vector_free(describe); vty_out(vty, "%% Ambiguous command.\n"); rl_on_new_line(); return 0; break; case CMD_ERR_NO_MATCH: cmd_free_strvec(vline); if (describe) vector_free(describe); vty_out(vty, "%% There is no matched command.\n"); rl_on_new_line(); return 0; break; } /* Get width of command string. */ width = 0; for (i = 0; i < vector_active(describe); i++) if ((token = vector_slot(describe, i)) != NULL) { if (token->text[0] == '\0') continue; int len = strlen(token->text); if (width < len) width = len; } for (i = 0; i < vector_active(describe); i++) if ((token = vector_slot(describe, i)) != NULL) { if (!token->desc) vty_out(vty, " %-s\n", token->text); else vty_out(vty, " %-*s %s\n", width, token->text, token->desc); if (IS_VARYING_TOKEN(token->type)) { const char *ref = vector_slot( vline, vector_active(vline) - 1); vector varcomps = vector_init(VECTOR_MIN_SIZE); cmd_variable_complete(token, ref, varcomps); if (vector_active(varcomps) > 0) { int rows, cols; rl_get_screen_size(&rows, &cols); char *ac = cmd_variable_comp2str( varcomps, cols); vty_out(vty, "%s\n", ac); XFREE(MTYPE_TMP, ac); } vector_free(varcomps); } } cmd_free_strvec(vline); vector_free(describe); return 0; } /* * Entry point for user commands terminated with '?' character and typed through * the usual vtysh's stdin interface. This is the function being registered with * readline() api's. */ static int vtysh_rl_describe(int a, int b) { int ret; vty_out(vty, "\n"); ret = vtysh_process_questionmark(rl_line_buffer, rl_end); rl_on_new_line(); return ret; } /* * Function in charged of processing vtysh instructions terminating with '?' * character and received through the 'vtysh -c' interface. If user's * instruction is well-formatted, we will call the same processing routine * utilized by the traditional vtysh's stdin interface. */ int vtysh_execute_command_questionmark(char *input) { int input_len, qmark_count = 0; const char *str; if (!(input && *input)) return 1; /* Finding out question_mark count and strlen */ for (str = input; *str; ++str) { if (*str == '?') qmark_count++; } input_len = str - input; /* * Verify that user's input terminates in '?' and that patterns such as * 'cmd ? subcmd ?' are prevented. */ if (qmark_count != 1 || input[input_len - 1] != '?') return 1; /* * Questionmark-processing function is not expecting to receive '?' * character in input string. */ input[input_len - 1] = '\0'; return vtysh_process_questionmark(input, input_len - 1); } /* Result of cmd_complete_command() call will be stored here * and used in new_completion() in order to put the space in * correct places only. */ int complete_status; static char *command_generator(const char *text, int state) { vector vline; static char **matched = NULL; static int index = 0; /* First call. */ if (!state) { index = 0; if (vty->node == AUTH_NODE || vty->node == AUTH_ENABLE_NODE) return NULL; vline = cmd_make_strvec(rl_line_buffer); if (vline == NULL) return NULL; if (rl_end && isspace((unsigned char)rl_line_buffer[rl_end - 1])) vector_set(vline, NULL); matched = cmd_complete_command(vline, vty, &complete_status); cmd_free_strvec(vline); } if (matched && matched[index]) /* * this is free()'d by readline, but we leak 1 count of * MTYPE_COMPLETION */ return matched[index++]; XFREE(MTYPE_TMP, matched); matched = NULL; return NULL; } static char **new_completion(const char *text, int start, int end) { char **matches; matches = rl_completion_matches(text, command_generator); if (matches) { rl_point = rl_end; if (complete_status != CMD_COMPLETE_FULL_MATCH) /* only append a space on full match */ rl_completion_append_character = '\0'; } return matches; } /* Vty node structures. */ static struct cmd_node bgp_node = { BGP_NODE, "%s(config-router)# ", }; static struct cmd_node rip_node = { RIP_NODE, "%s(config-router)# ", }; static struct cmd_node isis_node = { ISIS_NODE, "%s(config-router)# ", }; static struct cmd_node openfabric_node = { OPENFABRIC_NODE, "%s(config-router)# ", }; static struct cmd_node interface_node = { INTERFACE_NODE, "%s(config-if)# ", }; static struct cmd_node pw_node = { PW_NODE, "%s(config-pw)# ", }; static struct cmd_node vrf_node = { VRF_NODE, "%s(config-vrf)# ", }; static struct cmd_node nh_group_node = { NH_GROUP_NODE, "%s(config-nh-group)# ", }; static struct cmd_node rmap_node = {RMAP_NODE, "%s(config-route-map)# "}; static struct cmd_node pbr_map_node = {PBRMAP_NODE, "%s(config-pbr-map)# "}; static struct cmd_node zebra_node = {ZEBRA_NODE, "%s(config-router)# "}; static struct cmd_node bgp_vpnv4_node = {BGP_VPNV4_NODE, "%s(config-router-af)# "}; static struct cmd_node bgp_vpnv6_node = {BGP_VPNV6_NODE, "%s(config-router-af)# "}; static struct cmd_node bgp_flowspecv4_node = {BGP_FLOWSPECV4_NODE, "%s(config-router-af)# "}; static struct cmd_node bgp_flowspecv6_node = {BGP_FLOWSPECV6_NODE, "%s(config-router-af)# "}; static struct cmd_node bgp_ipv4_node = {BGP_IPV4_NODE, "%s(config-router-af)# "}; static struct cmd_node bgp_ipv4m_node = {BGP_IPV4M_NODE, "%s(config-router-af)# "}; static struct cmd_node bgp_ipv4l_node = {BGP_IPV4L_NODE, "%s(config-router-af)# "}; static struct cmd_node bgp_ipv6_node = {BGP_IPV6_NODE, "%s(config-router-af)# "}; static struct cmd_node bgp_ipv6m_node = {BGP_IPV6M_NODE, "%s(config-router-af)# "}; static struct cmd_node bgp_evpn_node = {BGP_EVPN_NODE, "%s(config-router-af)# "}; static struct cmd_node bgp_evpn_vni_node = {BGP_EVPN_VNI_NODE, "%s(config-router-af-vni)# "}; static struct cmd_node bgp_ipv6l_node = {BGP_IPV6L_NODE, "%s(config-router-af)# "}; static struct cmd_node bgp_vnc_defaults_node = { BGP_VNC_DEFAULTS_NODE, "%s(config-router-vnc-defaults)# "}; static struct cmd_node bgp_vnc_nve_group_node = { BGP_VNC_NVE_GROUP_NODE, "%s(config-router-vnc-nve-group)# "}; static struct cmd_node bgp_vrf_policy_node = {BGP_VRF_POLICY_NODE, "%s(config-router-vrf-policy)# "}; static struct cmd_node bgp_vnc_l2_group_node = { BGP_VNC_L2_GROUP_NODE, "%s(config-router-vnc-l2-group)# "}; static struct cmd_node bmp_node = {BMP_NODE, "%s(config-bgp-bmp)# "}; static struct cmd_node ospf_node = {OSPF_NODE, "%s(config-router)# "}; static struct cmd_node eigrp_node = {EIGRP_NODE, "%s(config-router)# "}; static struct cmd_node babel_node = {BABEL_NODE, "%s(config-router)# "}; static struct cmd_node ripng_node = {RIPNG_NODE, "%s(config-router)# "}; static struct cmd_node ospf6_node = {OSPF6_NODE, "%s(config-ospf6)# "}; static struct cmd_node ldp_node = {LDP_NODE, "%s(config-ldp)# "}; static struct cmd_node ldp_ipv4_node = {LDP_IPV4_NODE, "%s(config-ldp-af)# "}; static struct cmd_node ldp_ipv6_node = {LDP_IPV6_NODE, "%s(config-ldp-af)# "}; static struct cmd_node ldp_ipv4_iface_node = {LDP_IPV4_IFACE_NODE, "%s(config-ldp-af-if)# "}; static struct cmd_node ldp_ipv6_iface_node = {LDP_IPV6_IFACE_NODE, "%s(config-ldp-af-if)# "}; static struct cmd_node ldp_l2vpn_node = {LDP_L2VPN_NODE, "%s(config-l2vpn)# "}; static struct cmd_node ldp_pseudowire_node = {LDP_PSEUDOWIRE_NODE, "%s(config-l2vpn-pw)# "}; static struct cmd_node keychain_node = {KEYCHAIN_NODE, "%s(config-keychain)# "}; static struct cmd_node keychain_key_node = {KEYCHAIN_KEY_NODE, "%s(config-keychain-key)# "}; struct cmd_node link_params_node = { LINK_PARAMS_NODE, "%s(config-link-params)# ", }; static struct cmd_node rpki_node = {RPKI_NODE, "%s(config-rpki)# ", 1}; #if HAVE_BFDD > 0 static struct cmd_node bfd_node = { BFD_NODE, "%s(config-bfd)# ", }; static struct cmd_node bfd_peer_node = { BFD_PEER_NODE, "%s(config-bfd-peer)# ", }; #endif /* HAVE_BFDD */ /* Defined in lib/vty.c */ extern struct cmd_node vty_node; /* When '^Z' is received from vty, move down to the enable mode. */ static int vtysh_end(void) { switch (vty->node) { case VIEW_NODE: case ENABLE_NODE: /* Nothing to do. */ break; default: vty->node = ENABLE_NODE; break; } return CMD_SUCCESS; } DEFUNSH(VTYSH_REALLYALL, vtysh_end_all, vtysh_end_all_cmd, "end", "End current mode and change to enable mode\n") { return vtysh_end(); } DEFUNSH(VTYSH_BGPD, router_bgp, router_bgp_cmd, "router bgp [(1-4294967295)$instasn [ WORD]]", ROUTER_STR BGP_STR AS_STR "BGP view\nBGP VRF\n" "View/VRF name\n") { vty->node = BGP_NODE; return CMD_SUCCESS; } #ifdef KEEP_OLD_VPN_COMMANDS DEFUNSH(VTYSH_BGPD, address_family_vpnv4, address_family_vpnv4_cmd, "address-family vpnv4 [unicast]", "Enter Address Family command mode\n" "Address Family\n" "Address Family modifier\n") { vty->node = BGP_VPNV4_NODE; return CMD_SUCCESS; } DEFUNSH(VTYSH_BGPD, address_family_vpnv6, address_family_vpnv6_cmd, "address-family vpnv6 [unicast]", "Enter Address Family command mode\n" "Address Family\n" "Address Family modifier\n") { vty->node = BGP_VPNV6_NODE; return CMD_SUCCESS; } #endif /* KEEP_OLD_VPN_COMMANDS */ DEFUNSH(VTYSH_BGPD, address_family_ipv4, address_family_ipv4_cmd, "address-family ipv4 [unicast]", "Enter Address Family command mode\n" "Address Family\n" "Address Family Modifier\n") { vty->node = BGP_IPV4_NODE; return CMD_SUCCESS; } DEFUNSH(VTYSH_BGPD, address_family_flowspecv4, address_family_flowspecv4_cmd, "address-family ipv4 flowspec", "Enter Address Family command mode\n" "Address Family\n" "Address Family Modifier\n") { vty->node = BGP_FLOWSPECV4_NODE; return CMD_SUCCESS; } DEFUNSH(VTYSH_BGPD, address_family_flowspecv6, address_family_flowspecv6_cmd, "address-family ipv6 flowspec", "Enter Address Family command mode\n" "Address Family\n" "Address Family Modifier\n") { vty->node = BGP_FLOWSPECV6_NODE; return CMD_SUCCESS; } DEFUNSH(VTYSH_BGPD, address_family_ipv4_multicast, address_family_ipv4_multicast_cmd, "address-family ipv4 multicast", "Enter Address Family command mode\n" "Address Family\n" "Address Family modifier\n") { vty->node = BGP_IPV4M_NODE; return CMD_SUCCESS; } DEFUNSH(VTYSH_BGPD, address_family_ipv4_vpn, address_family_ipv4_vpn_cmd, "address-family ipv4 vpn", "Enter Address Family command mode\n" "Address Family\n" "Address Family modifier\n") { vty->node = BGP_VPNV4_NODE; return CMD_SUCCESS; } DEFUNSH(VTYSH_BGPD, address_family_ipv4_labeled_unicast, address_family_ipv4_labeled_unicast_cmd, "address-family ipv4 labeled-unicast", "Enter Address Family command mode\n" "Address Family\n" "Address Family modifier\n") { vty->node = BGP_IPV4L_NODE; return CMD_SUCCESS; } DEFUNSH(VTYSH_BGPD, address_family_ipv6, address_family_ipv6_cmd, "address-family ipv6 [unicast]", "Enter Address Family command mode\n" "Address Family\n" "Address Family modifier\n") { vty->node = BGP_IPV6_NODE; return CMD_SUCCESS; } DEFUNSH(VTYSH_BGPD, address_family_ipv6_multicast, address_family_ipv6_multicast_cmd, "address-family ipv6 multicast", "Enter Address Family command mode\n" "Address Family\n" "Address Family modifier\n") { vty->node = BGP_IPV6M_NODE; return CMD_SUCCESS; } DEFUNSH(VTYSH_BGPD, address_family_ipv6_vpn, address_family_ipv6_vpn_cmd, "address-family ipv6 vpn", "Enter Address Family command mode\n" "Address Family\n" "Address Family modifier\n") { vty->node = BGP_VPNV6_NODE; return CMD_SUCCESS; } DEFUNSH(VTYSH_BGPD, address_family_ipv6_labeled_unicast, address_family_ipv6_labeled_unicast_cmd, "address-family ipv6 labeled-unicast", "Enter Address Family command mode\n" "Address Family\n" "Address Family modifier\n") { vty->node = BGP_IPV6L_NODE; return CMD_SUCCESS; } DEFUNSH(VTYSH_BGPD, rpki, rpki_cmd, "rpki", "Enable rpki and enter rpki configuration mode\n") { vty->node = RPKI_NODE; return CMD_SUCCESS; } DEFUNSH(VTYSH_BGPD, bmp_targets, bmp_targets_cmd, "bmp targets BMPTARGETS", "BGP Monitoring Protocol\n" "Create BMP target group\n" "Name of the BMP target group\n") { vty->node = BMP_NODE; return CMD_SUCCESS; } DEFUNSH(VTYSH_BGPD, address_family_evpn, address_family_evpn_cmd, "address-family ", "Enter Address Family command mode\n" "Address Family\n" "Address Family modifier\n") { vty->node = BGP_EVPN_NODE; return CMD_SUCCESS; } #if defined(HAVE_CUMULUS) DEFUNSH_HIDDEN(VTYSH_BGPD, address_family_evpn2, address_family_evpn2_cmd, "address-family evpn", "Enter Address Family command mode\n" "EVPN Address family\n") { vty->node = BGP_EVPN_NODE; return CMD_SUCCESS; } #endif DEFUNSH(VTYSH_BGPD, bgp_evpn_vni, bgp_evpn_vni_cmd, "vni " CMD_VNI_RANGE, "VXLAN Network Identifier\n" "VNI number\n") { vty->node = BGP_EVPN_VNI_NODE; return CMD_SUCCESS; } #if defined(ENABLE_BGP_VNC) DEFUNSH(VTYSH_BGPD, vnc_defaults, vnc_defaults_cmd, "vnc defaults", "VNC/RFP related configuration\n" "Configure default NVE group\n") { vty->node = BGP_VNC_DEFAULTS_NODE; return CMD_SUCCESS; } DEFUNSH(VTYSH_BGPD, vnc_nve_group, vnc_nve_group_cmd, "vnc nve-group NAME", "VNC/RFP related configuration\n" "Configure a NVE group\n" "Group name\n") { vty->node = BGP_VNC_NVE_GROUP_NODE; return CMD_SUCCESS; } DEFUNSH(VTYSH_BGPD, vnc_vrf_policy, vnc_vrf_policy_cmd, "vrf-policy NAME", "Configure a VRF policy group\n" "Group name\n") { vty->node = BGP_VRF_POLICY_NODE; return CMD_SUCCESS; } DEFUNSH(VTYSH_BGPD, vnc_l2_group, vnc_l2_group_cmd, "vnc l2-group NAME", "VNC/RFP related configuration\n" "Configure a L2 group\n" "Group name\n") { vty->node = BGP_VNC_L2_GROUP_NODE; return CMD_SUCCESS; } #endif DEFUNSH(VTYSH_KEYS, key_chain, key_chain_cmd, "key chain WORD", "Authentication key management\n" "Key-chain management\n" "Key-chain name\n") { vty->node = KEYCHAIN_NODE; return CMD_SUCCESS; } DEFUNSH(VTYSH_KEYS, key, key_cmd, "key (0-2147483647)", "Configure a key\n" "Key identifier number\n") { vty->node = KEYCHAIN_KEY_NODE; return CMD_SUCCESS; } DEFUNSH(VTYSH_RIPD, router_rip, router_rip_cmd, "router rip [vrf NAME]", ROUTER_STR "RIP\n" VRF_CMD_HELP_STR) { vty->node = RIP_NODE; return CMD_SUCCESS; } DEFUNSH(VTYSH_RIPNGD, router_ripng, router_ripng_cmd, "router ripng [vrf NAME]", ROUTER_STR "RIPng\n" VRF_CMD_HELP_STR) { vty->node = RIPNG_NODE; return CMD_SUCCESS; } DEFUNSH(VTYSH_OSPFD, router_ospf, router_ospf_cmd, "router ospf [(1-65535)] [vrf NAME]", "Enable a routing process\n" "Start OSPF configuration\n" "Instance ID\n" VRF_CMD_HELP_STR) { vty->node = OSPF_NODE; return CMD_SUCCESS; } DEFUNSH(VTYSH_EIGRPD, router_eigrp, router_eigrp_cmd, "router eigrp (1-65535) [vrf NAME]", "Enable a routing process\n" "Start EIGRP configuration\n" "AS number to use\n" VRF_CMD_HELP_STR) { vty->node = EIGRP_NODE; return CMD_SUCCESS; } DEFUNSH(VTYSH_BABELD, router_babel, router_babel_cmd, "router babel", "Enable a routing process\n" "Make Babel instance command\n") { vty->node = BABEL_NODE; return CMD_SUCCESS; } DEFUNSH(VTYSH_OSPF6D, router_ospf6, router_ospf6_cmd, "router ospf6", ROUTER_STR OSPF6_STR) { vty->node = OSPF6_NODE; return CMD_SUCCESS; } #if defined(HAVE_LDPD) DEFUNSH(VTYSH_LDPD, ldp_mpls_ldp, ldp_mpls_ldp_cmd, "mpls ldp", "Global MPLS configuration subcommands\n" "Label Distribution Protocol\n") { vty->node = LDP_NODE; return CMD_SUCCESS; } DEFUNSH(VTYSH_LDPD, ldp_address_family_ipv4, ldp_address_family_ipv4_cmd, "address-family ipv4", "Configure Address Family and its parameters\n" "IPv4\n") { vty->node = LDP_IPV4_NODE; return CMD_SUCCESS; } DEFUNSH(VTYSH_LDPD, ldp_address_family_ipv6, ldp_address_family_ipv6_cmd, "address-family ipv6", "Configure Address Family and its parameters\n" "IPv6\n") { vty->node = LDP_IPV6_NODE; return CMD_SUCCESS; } DEFUNSH(VTYSH_LDPD, ldp_exit_address_family, ldp_exit_address_family_cmd, "exit-address-family", "Exit from Address Family configuration mode\n") { if (vty->node == LDP_IPV4_NODE || vty->node == LDP_IPV6_NODE) vty->node = LDP_NODE; return CMD_SUCCESS; } DEFUNSH(VTYSH_LDPD, ldp_interface_ifname, ldp_interface_ifname_cmd, "interface IFNAME", "Enable LDP on an interface and enter interface submode\n" "Interface's name\n") { switch (vty->node) { case LDP_IPV4_NODE: vty->node = LDP_IPV4_IFACE_NODE; break; case LDP_IPV6_NODE: vty->node = LDP_IPV6_IFACE_NODE; break; default: break; } return CMD_SUCCESS; } DEFUNSH(VTYSH_LDPD, ldp_l2vpn_word_type_vpls, ldp_l2vpn_word_type_vpls_cmd, "l2vpn WORD type vpls", "Configure l2vpn commands\n" "L2VPN name\n" "L2VPN type\n" "Virtual Private LAN Service\n") { vty->node = LDP_L2VPN_NODE; return CMD_SUCCESS; } DEFUNSH(VTYSH_LDPD, ldp_member_pseudowire_ifname, ldp_member_pseudowire_ifname_cmd, "member pseudowire IFNAME", "L2VPN member configuration\n" "Pseudowire interface\n" "Interface's name\n") { vty->node = LDP_PSEUDOWIRE_NODE; return CMD_SUCCESS; } #endif DEFUNSH(VTYSH_ISISD, router_isis, router_isis_cmd, "router isis WORD", ROUTER_STR "ISO IS-IS\n" "ISO Routing area tag\n") { vty->node = ISIS_NODE; return CMD_SUCCESS; } DEFUNSH(VTYSH_FABRICD, router_openfabric, router_openfabric_cmd, "router openfabric WORD", ROUTER_STR "OpenFabric routing protocol\n" "ISO Routing area tag\n") { vty->node = OPENFABRIC_NODE; return CMD_SUCCESS; } DEFUNSH(VTYSH_RMAP, vtysh_route_map, vtysh_route_map_cmd, "route-map WORD (1-65535)", "Create route-map or enter route-map command mode\n" "Route map tag\n" "Route map denies set operations\n" "Route map permits set operations\n" "Sequence to insert to/delete from existing route-map entry\n") { vty->node = RMAP_NODE; return CMD_SUCCESS; } DEFUNSH(VTYSH_PBRD, vtysh_pbr_map, vtysh_pbr_map_cmd, "pbr-map PBRMAP seq (1-700)", "Create pbr-map or enter pbr-map command mode\n" "The name of the PBR MAP\n" "Sequence to insert to/delete from existing pbr-map entry\n" "Sequence number\n") { vty->node = PBRMAP_NODE; return CMD_SUCCESS; } #if HAVE_BFDD > 0 DEFUNSH(VTYSH_BFDD, bfd_enter, bfd_enter_cmd, "bfd", "Configure BFD peers\n") { vty->node = BFD_NODE; return CMD_SUCCESS; } DEFUNSH(VTYSH_BFDD, bfd_peer_enter, bfd_peer_enter_cmd, "peer [{multihop|local-address |interface IFNAME|vrf NAME}]", "Configure peer\n" "IPv4 peer address\n" "IPv6 peer address\n" "Configure multihop\n" "Configure local address\n" "IPv4 local address\n" "IPv6 local address\n" INTERFACE_STR "Configure interface name to use\n" "Configure VRF\n" "Configure VRF name\n") { vty->node = BFD_PEER_NODE; return CMD_SUCCESS; } #endif /* HAVE_BFDD */ DEFSH(VTYSH_PBRD, vtysh_no_pbr_map_cmd, "no pbr-map PBRMAP [seq (1-700)]", NO_STR "Delete pbr-map\n" "The name of the PBR MAP\n" "Sequence to delete from existing pbr-map entry\n" "Sequence number\n") DEFUNSH(VTYSH_ALL, vtysh_line_vty, vtysh_line_vty_cmd, "line vty", "Configure a terminal line\n" "Virtual terminal\n") { vty->node = VTY_NODE; return CMD_SUCCESS; } DEFUNSH(VTYSH_REALLYALL, vtysh_enable, vtysh_enable_cmd, "enable", "Turn on privileged mode command\n") { vty->node = ENABLE_NODE; return CMD_SUCCESS; } DEFUNSH(VTYSH_REALLYALL, vtysh_disable, vtysh_disable_cmd, "disable", "Turn off privileged mode command\n") { if (vty->node == ENABLE_NODE) vty->node = VIEW_NODE; return CMD_SUCCESS; } DEFUNSH(VTYSH_REALLYALL, vtysh_config_terminal, vtysh_config_terminal_cmd, "configure [terminal]", "Configuration from vty interface\n" "Configuration terminal\n") { vty->node = CONFIG_NODE; return CMD_SUCCESS; } static int vtysh_exit(struct vty *vty) { switch (vty->node) { case VIEW_NODE: case ENABLE_NODE: exit(0); break; case CONFIG_NODE: vty->node = ENABLE_NODE; break; case INTERFACE_NODE: case PW_NODE: case VRF_NODE: case NH_GROUP_NODE: case ZEBRA_NODE: case BGP_NODE: case RIP_NODE: case RIPNG_NODE: case OSPF_NODE: case OSPF6_NODE: case EIGRP_NODE: case BABEL_NODE: case LDP_NODE: case LDP_L2VPN_NODE: case ISIS_NODE: case OPENFABRIC_NODE: case RMAP_NODE: case PBRMAP_NODE: case VTY_NODE: case KEYCHAIN_NODE: case BFD_NODE: case RPKI_NODE: vtysh_execute("end"); vtysh_execute("configure"); vty->node = CONFIG_NODE; break; case BGP_VPNV4_NODE: case BGP_VPNV6_NODE: case BGP_IPV4_NODE: case BGP_IPV4M_NODE: case BGP_IPV4L_NODE: case BGP_IPV6_NODE: case BGP_IPV6M_NODE: case BGP_IPV6L_NODE: case BGP_FLOWSPECV4_NODE: case BGP_FLOWSPECV6_NODE: case BGP_VRF_POLICY_NODE: case BGP_EVPN_NODE: case BGP_VNC_DEFAULTS_NODE: case BGP_VNC_NVE_GROUP_NODE: case BGP_VNC_L2_GROUP_NODE: case BMP_NODE: vty->node = BGP_NODE; break; case BGP_EVPN_VNI_NODE: vty->node = BGP_EVPN_NODE; break; case LDP_IPV4_NODE: case LDP_IPV6_NODE: vty->node = LDP_NODE; break; case LDP_IPV4_IFACE_NODE: vty->node = LDP_IPV4_NODE; break; case LDP_IPV6_IFACE_NODE: vty->node = LDP_IPV6_NODE; break; case LDP_PSEUDOWIRE_NODE: vty->node = LDP_L2VPN_NODE; break; case KEYCHAIN_KEY_NODE: vty->node = KEYCHAIN_NODE; break; case LINK_PARAMS_NODE: vty->node = INTERFACE_NODE; break; case BFD_PEER_NODE: vty->node = BFD_NODE; break; default: break; } return CMD_SUCCESS; } DEFUNSH(VTYSH_REALLYALL, vtysh_exit_all, vtysh_exit_all_cmd, "exit", "Exit current mode and down to previous mode\n") { return vtysh_exit(vty); } DEFUNSH(VTYSH_ALL, vtysh_quit_all, vtysh_quit_all_cmd, "quit", "Exit current mode and down to previous mode\n") { return vtysh_exit_all(self, vty, argc, argv); } DEFUNSH(VTYSH_BGPD, exit_address_family, exit_address_family_cmd, "exit-address-family", "Exit from Address Family configuration mode\n") { if (vty->node == BGP_IPV4_NODE || vty->node == BGP_IPV4M_NODE || vty->node == BGP_IPV4L_NODE || vty->node == BGP_VPNV4_NODE || vty->node == BGP_VPNV6_NODE || vty->node == BGP_IPV6_NODE || vty->node == BGP_IPV6L_NODE || vty->node == BGP_IPV6M_NODE || vty->node == BGP_EVPN_NODE || vty->node == BGP_FLOWSPECV4_NODE || vty->node == BGP_FLOWSPECV6_NODE) vty->node = BGP_NODE; return CMD_SUCCESS; } DEFUNSH(VTYSH_BGPD, exit_vni, exit_vni_cmd, "exit-vni", "Exit from VNI mode\n") { if (vty->node == BGP_EVPN_VNI_NODE) vty->node = BGP_EVPN_NODE; return CMD_SUCCESS; } DEFUNSH(VTYSH_BGPD, exit_vnc_config, exit_vnc_config_cmd, "exit-vnc", "Exit from VNC configuration mode\n") { if (vty->node == BGP_VNC_DEFAULTS_NODE || vty->node == BGP_VNC_NVE_GROUP_NODE || vty->node == BGP_VNC_L2_GROUP_NODE) vty->node = BGP_NODE; return CMD_SUCCESS; } DEFUNSH(VTYSH_BGPD, rpki_exit, rpki_exit_cmd, "exit", "Exit current mode and down to previous mode\n") { vtysh_exit(vty); return CMD_SUCCESS; } DEFUNSH(VTYSH_BGPD, rpki_quit, rpki_quit_cmd, "quit", "Exit current mode and down to previous mode\n") { return rpki_exit(self, vty, argc, argv); } DEFUNSH(VTYSH_BGPD, bmp_exit, bmp_exit_cmd, "exit", "Exit current mode and down to previous mode\n") { vtysh_exit(vty); return CMD_SUCCESS; } DEFUNSH(VTYSH_BGPD, bmp_quit, bmp_quit_cmd, "quit", "Exit current mode and down to previous mode\n") { return bmp_exit(self, vty, argc, argv); } DEFUNSH(VTYSH_VRF, exit_vrf_config, exit_vrf_config_cmd, "exit-vrf", "Exit from VRF configuration mode\n") { if (vty->node == VRF_NODE) vty->node = CONFIG_NODE; return CMD_SUCCESS; } DEFUNSH(VTYSH_BGPD, exit_vrf_policy, exit_vrf_policy_cmd, "exit-vrf-policy", "Exit from VRF policy configuration mode\n") { if (vty->node == BGP_VRF_POLICY_NODE) vty->node = BGP_NODE; return CMD_SUCCESS; } DEFUNSH(VTYSH_RIPD, vtysh_exit_ripd, vtysh_exit_ripd_cmd, "exit", "Exit current mode and down to previous mode\n") { return vtysh_exit(vty); } DEFUNSH(VTYSH_RIPD, vtysh_quit_ripd, vtysh_quit_ripd_cmd, "quit", "Exit current mode and down to previous mode\n") { return vtysh_exit_ripd(self, vty, argc, argv); } DEFUNSH(VTYSH_RIPNGD, vtysh_exit_ripngd, vtysh_exit_ripngd_cmd, "exit", "Exit current mode and down to previous mode\n") { return vtysh_exit(vty); } DEFUNSH(VTYSH_RIPNGD, vtysh_quit_ripngd, vtysh_quit_ripngd_cmd, "quit", "Exit current mode and down to previous mode\n") { return vtysh_exit_ripngd(self, vty, argc, argv); } DEFUNSH(VTYSH_RMAP, vtysh_exit_rmap, vtysh_exit_rmap_cmd, "exit", "Exit current mode and down to previous mode\n") { return vtysh_exit(vty); } DEFUNSH(VTYSH_RMAP, vtysh_quit_rmap, vtysh_quit_rmap_cmd, "quit", "Exit current mode and down to previous mode\n") { return vtysh_exit_rmap(self, vty, argc, argv); } DEFUNSH(VTYSH_PBRD, vtysh_exit_pbr_map, vtysh_exit_pbr_map_cmd, "exit", "Exit current mode and down to previous mode\n") { return vtysh_exit(vty); } DEFUNSH(VTYSH_PBRD, vtysh_quit_pbr_map, vtysh_quit_pbr_map_cmd, "quit", "Exit current mode and down to previous mode\n") { return vtysh_exit_rmap(self, vty, argc, argv); } DEFUNSH(VTYSH_BGPD, vtysh_exit_bgpd, vtysh_exit_bgpd_cmd, "exit", "Exit current mode and down to previous mode\n") { return vtysh_exit(vty); } DEFUNSH(VTYSH_BGPD, vtysh_quit_bgpd, vtysh_quit_bgpd_cmd, "quit", "Exit current mode and down to previous mode\n") { return vtysh_exit_bgpd(self, vty, argc, argv); } DEFUNSH(VTYSH_OSPFD, vtysh_exit_ospfd, vtysh_exit_ospfd_cmd, "exit", "Exit current mode and down to previous mode\n") { return vtysh_exit(vty); } DEFUNSH(VTYSH_OSPFD, vtysh_quit_ospfd, vtysh_quit_ospfd_cmd, "quit", "Exit current mode and down to previous mode\n") { return vtysh_exit_ospfd(self, vty, argc, argv); } DEFUNSH(VTYSH_EIGRPD, vtysh_exit_eigrpd, vtysh_exit_eigrpd_cmd, "exit", "Exit current mode and down to previous mode\n") { return vtysh_exit(vty); } DEFUNSH(VTYSH_EIGRPD, vtysh_quit_eigrpd, vtysh_quit_eigrpd_cmd, "quit", "Exit current mode and down to previous mode\n") { return vtysh_exit(vty); } DEFUNSH(VTYSH_EIGRPD, vtysh_exit_babeld, vtysh_exit_babeld_cmd, "exit", "Exit current mode and down to previous mode\n") { return vtysh_exit(vty); } DEFUNSH(VTYSH_BABELD, vtysh_quit_babeld, vtysh_quit_babeld_cmd, "quit", "Exit current mode and down to previous mode\n") { return vtysh_exit(vty); } DEFUNSH(VTYSH_OSPF6D, vtysh_exit_ospf6d, vtysh_exit_ospf6d_cmd, "exit", "Exit current mode and down to previous mode\n") { return vtysh_exit(vty); } DEFUNSH(VTYSH_OSPF6D, vtysh_quit_ospf6d, vtysh_quit_ospf6d_cmd, "quit", "Exit current mode and down to previous mode\n") { return vtysh_exit_ospf6d(self, vty, argc, argv); } #if defined(HAVE_LDPD) DEFUNSH(VTYSH_LDPD, vtysh_exit_ldpd, vtysh_exit_ldpd_cmd, "exit", "Exit current mode and down to previous mode\n") { return vtysh_exit(vty); } ALIAS(vtysh_exit_ldpd, vtysh_quit_ldpd_cmd, "quit", "Exit current mode and down to previous mode\n") #endif DEFUNSH(VTYSH_ISISD, vtysh_exit_isisd, vtysh_exit_isisd_cmd, "exit", "Exit current mode and down to previous mode\n") { return vtysh_exit(vty); } DEFUNSH(VTYSH_ISISD, vtysh_quit_isisd, vtysh_quit_isisd_cmd, "quit", "Exit current mode and down to previous mode\n") { return vtysh_exit_isisd(self, vty, argc, argv); } #if HAVE_BFDD > 0 DEFUNSH(VTYSH_BFDD, vtysh_exit_bfdd, vtysh_exit_bfdd_cmd, "exit", "Exit current mode and down to previous mode\n") { return vtysh_exit(vty); } ALIAS(vtysh_exit_bfdd, vtysh_quit_bfdd_cmd, "quit", "Exit current mode and down to previous mode\n") #endif DEFUNSH(VTYSH_FABRICD, vtysh_exit_fabricd, vtysh_exit_fabricd_cmd, "exit", "Exit current mode and down to previous mode\n") { return vtysh_exit(vty); } DEFUNSH(VTYSH_FABRICD, vtysh_quit_fabricd, vtysh_quit_fabricd_cmd, "quit", "Exit current mode and down to previous mode\n") { return vtysh_exit_fabricd(self, vty, argc, argv); } DEFUNSH(VTYSH_ALL, vtysh_exit_line_vty, vtysh_exit_line_vty_cmd, "exit", "Exit current mode and down to previous mode\n") { return vtysh_exit(vty); } DEFUNSH(VTYSH_ALL, vtysh_quit_line_vty, vtysh_quit_line_vty_cmd, "quit", "Exit current mode and down to previous mode\n") { return vtysh_exit_line_vty(self, vty, argc, argv); } DEFUNSH(VTYSH_INTERFACE, vtysh_interface, vtysh_interface_cmd, "interface IFNAME [vrf NAME]", "Select an interface to configure\n" "Interface's name\n" VRF_CMD_HELP_STR) { vty->node = INTERFACE_NODE; return CMD_SUCCESS; } DEFUNSH(VTYSH_ZEBRA, vtysh_pseudowire, vtysh_pseudowire_cmd, "pseudowire IFNAME", "Static pseudowire configuration\n" "Pseudowire name\n") { vty->node = PW_NODE; return CMD_SUCCESS; } DEFUNSH(VTYSH_PBRD | VTYSH_SHARPD, vtysh_nexthop_group, vtysh_nexthop_group_cmd, "nexthop-group NHGNAME", "Nexthop Group configuration\n" "Name of the Nexthop Group\n") { vty->node = NH_GROUP_NODE; return CMD_SUCCESS; } DEFSH(VTYSH_PBRD | VTYSH_SHARPD, vtysh_no_nexthop_group_cmd, "no nexthop-group NHGNAME", NO_STR "Nexthop Group Configuration\n" "Name of the Nexthop Group\n") DEFUNSH(VTYSH_VRF, vtysh_vrf, vtysh_vrf_cmd, "vrf NAME", "Select a VRF to configure\n" "VRF's name\n") { vty->node = VRF_NODE; return CMD_SUCCESS; } DEFSH(VTYSH_ZEBRA, vtysh_vrf_netns_cmd, "netns NAME", "Attach VRF to a Namespace\n" "The file name in " NS_RUN_DIR ", or a full pathname\n") DEFSH(VTYSH_ZEBRA, vtysh_no_vrf_netns_cmd, "no netns [NAME]", NO_STR "Detach VRF from a Namespace\n" "The file name in " NS_RUN_DIR ", or a full pathname\n") DEFUNSH(VTYSH_VRF, vtysh_exit_vrf, vtysh_exit_vrf_cmd, "exit", "Exit current mode and down to previous mode\n") { return vtysh_exit(vty); } DEFUNSH(VTYSH_VRF, vtysh_quit_vrf, vtysh_quit_vrf_cmd, "quit", "Exit current mode and down to previous mode\n") { return vtysh_exit_vrf(self, vty, argc, argv); } DEFUNSH(VTYSH_PBRD | VTYSH_SHARPD, vtysh_exit_nexthop_group, vtysh_exit_nexthop_group_cmd, "exit", "Exit current mode and down to previous mode\n") { return vtysh_exit(vty); } DEFUNSH(VTYSH_PBRD | VTYSH_SHARPD, vtysh_quit_nexthop_group, vtysh_quit_nexthop_group_cmd, "quit", "Exit current mode and down to previous mode\n") { return vtysh_exit_nexthop_group(self, vty, argc, argv); } DEFUNSH(VTYSH_INTERFACE, vtysh_exit_interface, vtysh_exit_interface_cmd, "exit", "Exit current mode and down to previous mode\n") { return vtysh_exit(vty); } DEFUNSH(VTYSH_INTERFACE, vtysh_quit_interface, vtysh_quit_interface_cmd, "quit", "Exit current mode and down to previous mode\n") { return vtysh_exit_interface(self, vty, argc, argv); } DEFUN (vtysh_show_poll, vtysh_show_poll_cmd, "show thread poll", SHOW_STR "Thread information\n" "Thread Poll Information\n") { unsigned int i; int ret = CMD_SUCCESS; char line[100]; snprintf(line, sizeof(line), "do show thread poll\n"); for (i = 0; i < array_size(vtysh_client); i++) if (vtysh_client[i].fd >= 0) { vty_out(vty, "Thread statistics for %s:\n", vtysh_client[i].name); ret = vtysh_client_execute(&vtysh_client[i], line); vty_out(vty, "\n"); } return ret; } DEFUN (vtysh_show_thread, vtysh_show_thread_cmd, "show thread cpu [FILTER]", SHOW_STR "Thread information\n" "Thread CPU usage\n" "Display filter (rwtexb)\n") { unsigned int i; int idx = 0; int ret = CMD_SUCCESS; char line[100]; const char *filter = argv_find(argv, argc, "FILTER", &idx) ? argv[idx]->arg : ""; snprintf(line, sizeof(line), "do show thread cpu %s\n", filter); for (i = 0; i < array_size(vtysh_client); i++) if (vtysh_client[i].fd >= 0) { vty_out(vty, "Thread statistics for %s:\n", vtysh_client[i].name); ret = vtysh_client_execute(&vtysh_client[i], line); vty_out(vty, "\n"); } return ret; } DEFUN (vtysh_show_work_queues, vtysh_show_work_queues_cmd, "show work-queues", SHOW_STR "Work Queue information\n") { unsigned int i; int ret = CMD_SUCCESS; char line[] = "do show work-queues\n"; for (i = 0; i < array_size(vtysh_client); i++) if (vtysh_client[i].fd >= 0) { vty_out(vty, "Work queue statistics for %s:\n", vtysh_client[i].name); ret = vtysh_client_execute(&vtysh_client[i], line); vty_out(vty, "\n"); } return ret; } DEFUN (vtysh_show_work_queues_daemon, vtysh_show_work_queues_daemon_cmd, "show work-queues " DAEMONS_LIST, SHOW_STR "Work Queue information\n" DAEMONS_STR) { int idx_protocol = 2; return vtysh_client_execute_name(argv[idx_protocol]->text, "show work-queues\n"); } DEFUNSH(VTYSH_ZEBRA, vtysh_link_params, vtysh_link_params_cmd, "link-params", LINK_PARAMS_STR) { vty->node = LINK_PARAMS_NODE; return CMD_SUCCESS; } DEFUNSH(VTYSH_ZEBRA, exit_link_params, exit_link_params_cmd, "exit-link-params", "Exit from Link Params configuration node\n") { if (vty->node == LINK_PARAMS_NODE) vty->node = INTERFACE_NODE; return CMD_SUCCESS; } static int show_per_daemon(const char *line, const char *headline) { unsigned int i; int ret = CMD_SUCCESS; for (i = 0; i < array_size(vtysh_client); i++) if (vtysh_client[i].fd >= 0) { vty_out(vty, headline, vtysh_client[i].name); ret = vtysh_client_execute(&vtysh_client[i], line); vty_out(vty, "\n"); } return ret; } DEFUNSH_HIDDEN (0x00, vtysh_debug_all, vtysh_debug_all_cmd, "[no] debug all", NO_STR DEBUG_STR "Toggle all debugs on or off\n") { return CMD_SUCCESS; } DEFUN (vtysh_show_debugging, vtysh_show_debugging_cmd, "show debugging", SHOW_STR DEBUG_STR) { return show_per_daemon("do show debugging\n", ""); } DEFUN (vtysh_show_debugging_hashtable, vtysh_show_debugging_hashtable_cmd, "show debugging hashtable [statistics]", SHOW_STR DEBUG_STR "Statistics about hash tables\n" "Statistics about hash tables\n") { vty_out(vty, "\n"); vty_out(vty, "Load factor (LF) - average number of elements across all buckets\n"); vty_out(vty, "Full load factor (FLF) - average number of elements across full buckets\n\n"); vty_out(vty, "Standard deviation (SD) is calculated for both the LF and FLF\n"); vty_out(vty, "and indicates the typical deviation of bucket chain length\n"); vty_out(vty, "from the value in the corresponding load factor.\n\n"); return show_per_daemon("do show debugging hashtable\n", "Hashtable statistics for %s:\n"); } DEFUN (vtysh_show_error_code, vtysh_show_error_code_cmd, "show error <(1-4294967296)|all> [json]", SHOW_STR "Information on errors\n" "Error code to get info about\n" "Information on all errors\n" JSON_STR) { uint32_t arg = 0; if (!strmatch(argv[2]->text, "all")) arg = strtoul(argv[2]->arg, NULL, 10); /* If it's not a shared code, send it to all the daemons */ if (arg < LIB_FERR_START || arg > LIB_FERR_END) { char *fcmd = argv_concat(argv, argc, 0); char cmd[256]; snprintf(cmd, sizeof(cmd), "do %s", fcmd); show_per_daemon(cmd, ""); XFREE(MTYPE_TMP, fcmd); /* Otherwise, print it ourselves to avoid duplication */ } else { bool json = strmatch(argv[argc - 1]->text, "json"); if (!strmatch(argv[2]->text, "all")) arg = strtoul(argv[2]->arg, NULL, 10); log_ref_display(vty, arg, json); } return CMD_SUCCESS; } /* Memory */ DEFUN (vtysh_show_memory, vtysh_show_memory_cmd, "show memory", SHOW_STR "Memory statistics\n") { return show_per_daemon("do show memory\n", "Memory statistics for %s:\n"); } DEFUN (vtysh_show_modules, vtysh_show_modules_cmd, "show modules", SHOW_STR "Loaded modules\n") { return show_per_daemon("do show modules\n", "Module information for %s:\n"); } /* Logging commands. */ DEFUN (vtysh_show_logging, vtysh_show_logging_cmd, "show logging", SHOW_STR "Show current logging configuration\n") { return show_per_daemon("do show logging\n", "Logging configuration for %s:\n"); } DEFUNSH(VTYSH_ALL, vtysh_log_stdout, vtysh_log_stdout_cmd, "log stdout", "Logging control\n" "Set stdout logging level\n") { return CMD_SUCCESS; } DEFUNSH(VTYSH_ALL, vtysh_log_stdout_level, vtysh_log_stdout_level_cmd, "log stdout ", "Logging control\n" "Set stdout logging level\n" LOG_LEVEL_DESC) { return CMD_SUCCESS; } DEFUNSH(VTYSH_ALL, no_vtysh_log_stdout, no_vtysh_log_stdout_cmd, "no log stdout [LEVEL]", NO_STR "Logging control\n" "Cancel logging to stdout\n" "Logging level\n") { return CMD_SUCCESS; } DEFUNSH(VTYSH_ALL, vtysh_log_file, vtysh_log_file_cmd, "log file FILENAME", "Logging control\n" "Logging to file\n" "Logging filename\n") { return CMD_SUCCESS; } DEFUNSH(VTYSH_ALL, vtysh_log_file_level, vtysh_log_file_level_cmd, "log file FILENAME ", "Logging control\n" "Logging to file\n" "Logging filename\n" LOG_LEVEL_DESC) { return CMD_SUCCESS; } DEFUNSH(VTYSH_ALL, no_vtysh_log_file, no_vtysh_log_file_cmd, "no log file [FILENAME [LEVEL]]", NO_STR "Logging control\n" "Cancel logging to file\n" "Logging file name\n" "Logging level\n") { return CMD_SUCCESS; } DEFUNSH(VTYSH_ALL, vtysh_log_monitor, vtysh_log_monitor_cmd, "log monitor []", "Logging control\n" "Set terminal line (monitor) logging level\n" LOG_LEVEL_DESC) { return CMD_SUCCESS; } DEFUNSH(VTYSH_ALL, no_vtysh_log_monitor, no_vtysh_log_monitor_cmd, "no log monitor [LEVEL]", NO_STR "Logging control\n" "Disable terminal line (monitor) logging\n" "Logging level\n") { return CMD_SUCCESS; } DEFUNSH(VTYSH_ALL, vtysh_log_syslog, vtysh_log_syslog_cmd, "log syslog []", "Logging control\n" "Set syslog logging level\n" LOG_LEVEL_DESC) { return CMD_SUCCESS; } DEFUNSH(VTYSH_ALL, no_vtysh_log_syslog, no_vtysh_log_syslog_cmd, "no log syslog []", NO_STR "Logging control\n" "Cancel logging to syslog\n" LOG_LEVEL_DESC) { return CMD_SUCCESS; } DEFUNSH(VTYSH_ALL, vtysh_log_facility, vtysh_log_facility_cmd, "log facility ", "Logging control\n" "Facility parameter for syslog messages\n" LOG_FACILITY_DESC) { return CMD_SUCCESS; } DEFUNSH(VTYSH_ALL, no_vtysh_log_facility, no_vtysh_log_facility_cmd, "no log facility []", NO_STR "Logging control\n" "Reset syslog facility to default (daemon)\n" LOG_FACILITY_DESC) { return CMD_SUCCESS; } DEFUNSH(VTYSH_ALL, vtysh_log_record_priority, vtysh_log_record_priority_cmd, "log record-priority", "Logging control\n" "Log the priority of the message within the message\n") { return CMD_SUCCESS; } DEFUNSH(VTYSH_ALL, no_vtysh_log_record_priority, no_vtysh_log_record_priority_cmd, "no log record-priority", NO_STR "Logging control\n" "Do not log the priority of the message within the message\n") { return CMD_SUCCESS; } DEFUNSH(VTYSH_ALL, vtysh_log_timestamp_precision, vtysh_log_timestamp_precision_cmd, "log timestamp precision (0-6)", "Logging control\n" "Timestamp configuration\n" "Set the timestamp precision\n" "Number of subsecond digits\n") { return CMD_SUCCESS; } DEFUNSH(VTYSH_ALL, vtysh_debug_memstats, vtysh_debug_memstats_cmd, "[no] debug memstats-at-exit", NO_STR "Debug\n" "Print memory statistics at exit\n") { return CMD_SUCCESS; } DEFUNSH(VTYSH_ALL, no_vtysh_log_timestamp_precision, no_vtysh_log_timestamp_precision_cmd, "no log timestamp precision", NO_STR "Logging control\n" "Timestamp configuration\n" "Reset the timestamp precision to the default value of 0\n") { return CMD_SUCCESS; } DEFUNSH(VTYSH_ALL, vtysh_service_password_encrypt, vtysh_service_password_encrypt_cmd, "service password-encryption", "Set up miscellaneous service\n" "Enable encrypted passwords\n") { return CMD_SUCCESS; } DEFUNSH(VTYSH_ALL, no_vtysh_service_password_encrypt, no_vtysh_service_password_encrypt_cmd, "no service password-encryption", NO_STR "Set up miscellaneous service\n" "Enable encrypted passwords\n") { return CMD_SUCCESS; } DEFUNSH(VTYSH_ALL, vtysh_config_password, vtysh_password_cmd, "password [(8-8)] LINE", "Modify the terminal connection password\n" "Specifies a HIDDEN password will follow\n" "The password string\n") { return CMD_SUCCESS; } DEFUNSH(VTYSH_ALL, no_vtysh_config_password, no_vtysh_password_cmd, "no password", NO_STR "Modify the terminal connection password\n") { vty_out(vty, NO_PASSWD_CMD_WARNING); return CMD_SUCCESS; } DEFUNSH(VTYSH_ALL, vtysh_config_enable_password, vtysh_enable_password_cmd, "enable password [(8-8)] LINE", "Modify enable password parameters\n" "Assign the privileged level password\n" "Specifies a HIDDEN password will follow\n" "The 'enable' password string\n") { return CMD_SUCCESS; } DEFUNSH(VTYSH_ALL, no_vtysh_config_enable_password, no_vtysh_enable_password_cmd, "no enable password", NO_STR "Modify enable password parameters\n" "Assign the privileged level password\n") { vty_out(vty, NO_PASSWD_CMD_WARNING); return CMD_SUCCESS; } /* Log filter */ DEFUN (vtysh_log_filter, vtysh_log_filter_cmd, "[no] log-filter WORD ["DAEMONS_LIST"]", NO_STR FILTER_LOG_STR "String to filter by\n" DAEMONS_STR) { char *filter = NULL; char *daemon = NULL; int found = 0; int idx = 0; int daemon_idx = 2; int total_len = 0; int len = 0; char line[ZLOG_FILTER_LENGTH_MAX + 20]; found = argv_find(argv, argc, "no", &idx); if (found == 1) { len = snprintf(line, sizeof(line), "no log-filter"); daemon_idx += 1; } else len = snprintf(line, sizeof(line), "log-filter"); total_len += len; idx = 1; found = argv_find(argv, argc, "WORD", &idx); if (found != 1) { vty_out(vty, "%% No filter string given\n"); return CMD_WARNING; } filter = argv[idx]->arg; if (strnlen(filter, ZLOG_FILTER_LENGTH_MAX + 1) > ZLOG_FILTER_LENGTH_MAX) { vty_out(vty, "%% Filter is too long\n"); return CMD_WARNING; } len = snprintf(line + total_len, sizeof(line) - total_len, " %s\n", filter); if ((len < 0) || (size_t)(total_len + len) > sizeof(line)) { vty_out(vty, "%% Error buffering filter to daemons\n"); return CMD_ERR_INCOMPLETE; } if (argc >= (daemon_idx + 1)) daemon = argv[daemon_idx]->text; if (daemon != NULL) { vty_out(vty, "Applying log filter change to %s:\n", daemon); return vtysh_client_execute_name(daemon, line); } else return show_per_daemon(line, "Applying log filter change to %s:\n"); } /* Clear log filters */ DEFUN (vtysh_log_filter_clear, vtysh_log_filter_clear_cmd, "log-filter clear ["DAEMONS_LIST"]", FILTER_LOG_STR CLEAR_STR DAEMONS_STR) { char *daemon = NULL; int daemon_idx = 2; char line[] = "clear log-filter\n"; if (argc >= (daemon_idx + 1)) daemon = argv[daemon_idx]->text; if (daemon != NULL) { vty_out(vty, "Clearing all filters applied to %s:\n", daemon); return vtysh_client_execute_name(daemon, line); } else return show_per_daemon(line, "Clearing all filters applied to %s:\n"); } /* Show log filter */ DEFUN (vtysh_show_log_filter, vtysh_show_log_filter_cmd, "show log-filter", SHOW_STR FILTER_LOG_STR) { char line[] = "do show log-filter\n"; return show_per_daemon(line, "Log filters applied to %s:\n"); } DEFUN (vtysh_write_terminal, vtysh_write_terminal_cmd, "write terminal ["DAEMONS_LIST"]", "Write running configuration to memory, network, or terminal\n" "Write to terminal\n" DAEMONS_STR) { unsigned int i; char line[] = "do write terminal\n"; vty_out(vty, "Building configuration...\n"); vty_out(vty, "\nCurrent configuration:\n"); vty_out(vty, "!\n"); for (i = 0; i < array_size(vtysh_client); i++) if ((argc < 3) || (strmatch(vtysh_client[i].name, argv[2]->text))) vtysh_client_config(&vtysh_client[i], line); /* Integrate vtysh specific configuration. */ vty_open_pager(vty); vtysh_config_write(); vtysh_config_dump(); vty_close_pager(vty); vty_out(vty, "end\n"); return CMD_SUCCESS; } DEFUN (vtysh_show_running_config, vtysh_show_running_config_cmd, "show running-config ["DAEMONS_LIST"]", SHOW_STR "Current operating configuration\n" DAEMONS_STR) { return vtysh_write_terminal(self, vty, argc, argv); } DEFUN (vtysh_integrated_config, vtysh_integrated_config_cmd, "service integrated-vtysh-config", "Set up miscellaneous service\n" "Write configuration into integrated file\n") { vtysh_write_integrated = WRITE_INTEGRATED_YES; return CMD_SUCCESS; } DEFUN (no_vtysh_integrated_config, no_vtysh_integrated_config_cmd, "no service integrated-vtysh-config", NO_STR "Set up miscellaneous service\n" "Write configuration into integrated file\n") { vtysh_write_integrated = WRITE_INTEGRATED_NO; return CMD_SUCCESS; } static void backup_config_file(const char *fbackup) { char *integrate_sav = NULL; size_t integrate_sav_sz = strlen(fbackup) + strlen(CONF_BACKUP_EXT) + 1; integrate_sav = malloc(integrate_sav_sz); strlcpy(integrate_sav, fbackup, integrate_sav_sz); strlcat(integrate_sav, CONF_BACKUP_EXT, integrate_sav_sz); /* Move current configuration file to backup config file. */ if (unlink(integrate_sav) != 0) { vty_out(vty, "Warning: %s unlink failed\n", integrate_sav); } if (rename(fbackup, integrate_sav) != 0) { vty_out(vty, "Error renaming %s to %s\n", fbackup, integrate_sav); } free(integrate_sav); } int vtysh_write_config_integrated(void) { unsigned int i; char line[] = "do write terminal\n"; FILE *fp; int fd; #ifdef FRR_USER struct passwd *pwentry; #endif #ifdef FRR_GROUP struct group *grentry; #endif uid_t uid = -1; gid_t gid = -1; struct stat st; int err = 0; vty_out(vty, "Building Configuration...\n"); backup_config_file(frr_config); fp = fopen(frr_config, "w"); if (fp == NULL) { vty_out(vty, "%% Error: failed to open configuration file %s: %s\n", frr_config, safe_strerror(errno)); return CMD_WARNING_CONFIG_FAILED; } fd = fileno(fp); for (i = 0; i < array_size(vtysh_client); i++) vtysh_client_config(&vtysh_client[i], line); vtysh_config_write(); vty->of_saved = vty->of; vty->of = fp; vtysh_config_dump(); vty->of = vty->of_saved; if (fchmod(fd, CONFIGFILE_MASK) != 0) { printf("%% Warning: can't chmod configuration file %s: %s\n", frr_config, safe_strerror(errno)); err++; } #ifdef FRR_USER pwentry = getpwnam(FRR_USER); if (pwentry) uid = pwentry->pw_uid; else { printf("%% Warning: could not look up user \"%s\"\n", FRR_USER); err++; } #endif #ifdef FRR_GROUP grentry = getgrnam(FRR_GROUP); if (grentry) gid = grentry->gr_gid; else { printf("%% Warning: could not look up group \"%s\"\n", FRR_GROUP); err++; } #endif if (!fstat(fd, &st)) { if (st.st_uid == uid) uid = -1; if (st.st_gid == gid) gid = -1; if ((uid != (uid_t)-1 || gid != (gid_t)-1) && fchown(fd, uid, gid)) { printf("%% Warning: can't chown configuration file %s: %s\n", frr_config, safe_strerror(errno)); err++; } } else { printf("%% Warning: stat() failed on %s: %s\n", frr_config, safe_strerror(errno)); err++; } fclose(fp); printf("Integrated configuration saved to %s\n", frr_config); if (err) return CMD_WARNING; printf("[OK]\n"); return CMD_SUCCESS; } static bool want_config_integrated(void) { struct stat s; switch (vtysh_write_integrated) { case WRITE_INTEGRATED_UNSPECIFIED: if (stat(frr_config, &s) && errno == ENOENT) return false; return true; case WRITE_INTEGRATED_NO: return false; case WRITE_INTEGRATED_YES: return true; } return true; } DEFUN (vtysh_write_memory, vtysh_write_memory_cmd, "write []", "Write running configuration to memory, network, or terminal\n" "Write configuration to the file (same as write file)\n" "Write configuration to the file (same as write memory)\n") { int ret = CMD_SUCCESS; char line[] = "do write memory\n"; unsigned int i; vty_out(vty, "Note: this version of vtysh never writes vtysh.conf\n"); /* If integrated frr.conf explicitely set. */ if (want_config_integrated()) { ret = CMD_WARNING_CONFIG_FAILED; /* first attempt to use watchfrr if it's available */ bool used_watchfrr = false; for (i = 0; i < array_size(vtysh_client); i++) if (vtysh_client[i].flag == VTYSH_WATCHFRR) break; if (i < array_size(vtysh_client) && vtysh_client[i].fd != -1) { used_watchfrr = true; ret = vtysh_client_execute(&vtysh_client[i], "do write integrated"); } /* * If we didn't use watchfrr, fallback to writing the config * ourselves */ if (!used_watchfrr) { printf("\nWarning: attempting direct configuration write without " "watchfrr.\nFile permissions and ownership may be " "incorrect, or write may fail.\n\n"); ret = vtysh_write_config_integrated(); } return ret; } vty_out(vty, "Building Configuration...\n"); for (i = 0; i < array_size(vtysh_client); i++) ret = vtysh_client_execute(&vtysh_client[i], line); return ret; } DEFUN (vtysh_copy_running_config, vtysh_copy_running_config_cmd, "copy running-config startup-config", "Copy from one file to another\n" "Copy from current system configuration\n" "Copy to startup configuration\n") { return vtysh_write_memory(self, vty, argc, argv); } DEFUN (vtysh_terminal_paginate, vtysh_terminal_paginate_cmd, "[no] terminal paginate", NO_STR "Set terminal line parameters\n" "Use pager for output scrolling\n") { free(vtysh_pager_name); vtysh_pager_name = NULL; if (strcmp(argv[0]->text, "no")) vtysh_pager_envdef(true); return CMD_SUCCESS; } DEFUN (vtysh_terminal_length, vtysh_terminal_length_cmd, "[no] terminal length (0-4294967295)", NO_STR "Set terminal line parameters\n" "Set number of lines on a screen\n" "Number of lines on screen (0 for no pausing, nonzero to use pager)\n") { int idx_number = 2; unsigned long lines; free(vtysh_pager_name); vtysh_pager_name = NULL; if (!strcmp(argv[0]->text, "no") || !strcmp(argv[1]->text, "no")) { /* "terminal no length" = use VTYSH_PAGER */ vtysh_pager_envdef(true); return CMD_SUCCESS; } lines = strtoul(argv[idx_number]->arg, NULL, 10); if (lines != 0) { vty_out(vty, "%% The \"terminal length\" command is deprecated and its value is ignored.\n" "%% Please use \"terminal paginate\" instead with OS TTY length handling.\n"); vtysh_pager_envdef(true); } return CMD_SUCCESS; } ALIAS_DEPRECATED(vtysh_terminal_length, vtysh_terminal_no_length_cmd, "terminal no length", "Set terminal line parameters\n" NO_STR "Set number of lines on a screen\n") DEFUN (vtysh_show_daemons, vtysh_show_daemons_cmd, "show daemons", SHOW_STR "Show list of running daemons\n") { unsigned int i; for (i = 0; i < array_size(vtysh_client); i++) if (vtysh_client[i].fd >= 0) vty_out(vty, " %s", vtysh_client[i].name); vty_out(vty, "\n"); return CMD_SUCCESS; } /* Execute command in child process. */ static void execute_command(const char *command, int argc, const char *arg1, const char *arg2) { pid_t pid; int status; /* Call fork(). */ pid = fork(); if (pid < 0) { /* Failure of fork(). */ fprintf(stderr, "Can't fork: %s\n", safe_strerror(errno)); exit(1); } else if (pid == 0) { /* This is child process. */ switch (argc) { case 0: execlp(command, command, (const char *)NULL); break; case 1: execlp(command, command, arg1, (const char *)NULL); break; case 2: execlp(command, command, arg1, arg2, (const char *)NULL); break; } /* When execlp suceed, this part is not executed. */ fprintf(stderr, "Can't execute %s: %s\n", command, safe_strerror(errno)); exit(1); } else { /* This is parent. */ execute_flag = 1; wait4(pid, &status, 0, NULL); execute_flag = 0; } } DEFUN (vtysh_ping, vtysh_ping_cmd, "ping WORD", "Send echo messages\n" "Ping destination address or hostname\n") { int idx = 1; argv_find(argv, argc, "WORD", &idx); execute_command("ping", 1, argv[idx]->arg, NULL); return CMD_SUCCESS; } ALIAS(vtysh_ping, vtysh_ping_ip_cmd, "ping ip WORD", "Send echo messages\n" "IP echo\n" "Ping destination address or hostname\n") DEFUN (vtysh_traceroute, vtysh_traceroute_cmd, "traceroute WORD", "Trace route to destination\n" "Trace route to destination address or hostname\n") { int idx = 1; argv_find(argv, argc, "WORD", &idx); execute_command("traceroute", 1, argv[idx]->arg, NULL); return CMD_SUCCESS; } ALIAS(vtysh_traceroute, vtysh_traceroute_ip_cmd, "traceroute ip WORD", "Trace route to destination\n" "IP trace\n" "Trace route to destination address or hostname\n") DEFUN (vtysh_mtrace, vtysh_mtrace_cmd, "mtrace WORD [WORD]", "Multicast trace route to multicast source\n" "Multicast trace route to multicast source address\n" "Multicast trace route for multicast group address\n") { if (argc == 2) execute_command("mtracebis", 1, argv[1]->arg, NULL); else execute_command("mtracebis", 2, argv[1]->arg, argv[2]->arg); return CMD_SUCCESS; } DEFUN (vtysh_ping6, vtysh_ping6_cmd, "ping ipv6 WORD", "Send echo messages\n" "IPv6 echo\n" "Ping destination address or hostname\n") { execute_command("ping6", 1, argv[2]->arg, NULL); return CMD_SUCCESS; } DEFUN (vtysh_traceroute6, vtysh_traceroute6_cmd, "traceroute ipv6 WORD", "Trace route to destination\n" "IPv6 trace\n" "Trace route to destination address or hostname\n") { execute_command("traceroute6", 1, argv[2]->arg, NULL); return CMD_SUCCESS; } #if defined(HAVE_SHELL_ACCESS) DEFUN (vtysh_telnet, vtysh_telnet_cmd, "telnet WORD", "Open a telnet connection\n" "IP address or hostname of a remote system\n") { execute_command("telnet", 1, argv[1]->arg, NULL); return CMD_SUCCESS; } DEFUN (vtysh_telnet_port, vtysh_telnet_port_cmd, "telnet WORD PORT", "Open a telnet connection\n" "IP address or hostname of a remote system\n" "TCP Port number\n") { execute_command("telnet", 2, argv[1]->arg, argv[2]->arg); return CMD_SUCCESS; } DEFUN (vtysh_ssh, vtysh_ssh_cmd, "ssh WORD", "Open an ssh connection\n" "[user@]host\n") { execute_command("ssh", 1, argv[1]->arg, NULL); return CMD_SUCCESS; } DEFUN (vtysh_start_shell, vtysh_start_shell_cmd, "start-shell", "Start UNIX shell\n") { execute_command("sh", 0, NULL, NULL); return CMD_SUCCESS; } DEFUN (vtysh_start_bash, vtysh_start_bash_cmd, "start-shell bash", "Start UNIX shell\n" "Start bash\n") { execute_command("bash", 0, NULL, NULL); return CMD_SUCCESS; } DEFUN (vtysh_start_zsh, vtysh_start_zsh_cmd, "start-shell zsh", "Start UNIX shell\n" "Start Z shell\n") { execute_command("zsh", 0, NULL, NULL); return CMD_SUCCESS; } #endif DEFUN (config_list, config_list_cmd, "list [permutations]", "Print command list\n" "Print all possible command permutations\n") { return cmd_list_cmds(vty, argc == 2); } DEFUN (vtysh_output_file, vtysh_output_file_cmd, "output file FILE", "Direct vtysh output to file\n" "Direct vtysh output to file\n" "Path to dump output to\n") { const char *path = argv[argc - 1]->arg; vty->of = fopen(path, "a"); if (!vty->of) { vty_out(vty, "Failed to open file '%s': %s\n", path, safe_strerror(errno)); vty->of = stdout; } return CMD_SUCCESS; } DEFUN (no_vtysh_output_file, no_vtysh_output_file_cmd, "no output file [FILE]", NO_STR "Direct vtysh output to file\n" "Direct vtysh output to file\n" "Path to dump output to\n") { if (vty->of != stdout) { fclose(vty->of); vty->of = stdout; } return CMD_SUCCESS; } DEFUN(find, find_cmd, "find REGEX", "Find CLI command matching a regular expression\n" "Search pattern (POSIX regex)\n") { char *pattern = argv[1]->arg; const struct cmd_node *node; const struct cmd_element *cli; vector clis; regex_t exp = {}; int cr = regcomp(&exp, pattern, REG_NOSUB | REG_EXTENDED); if (cr != 0) { switch (cr) { case REG_BADBR: vty_out(vty, "%% Invalid \\{...\\} expression\n"); break; case REG_BADRPT: vty_out(vty, "%% Bad repetition operator\n"); break; case REG_BADPAT: vty_out(vty, "%% Regex syntax error\n"); break; case REG_ECOLLATE: vty_out(vty, "%% Invalid collating element\n"); break; case REG_ECTYPE: vty_out(vty, "%% Invalid character class name\n"); break; case REG_EESCAPE: vty_out(vty, "%% Regex ended with escape character (\\)\n"); break; case REG_ESUBREG: vty_out(vty, "%% Invalid number in \\digit construction\n"); break; case REG_EBRACK: vty_out(vty, "%% Unbalanced square brackets\n"); break; case REG_EPAREN: vty_out(vty, "%% Unbalanced parentheses\n"); break; case REG_EBRACE: vty_out(vty, "%% Unbalanced braces\n"); break; case REG_ERANGE: vty_out(vty, "%% Invalid endpoint in range expression\n"); break; case REG_ESPACE: vty_out(vty, "%% Failed to compile (out of memory)\n"); break; } goto done; } for (unsigned int i = 0; i < vector_active(cmdvec); i++) { node = vector_slot(cmdvec, i); if (!node) continue; clis = node->cmd_vector; for (unsigned int j = 0; j < vector_active(clis); j++) { cli = vector_slot(clis, j); if (regexec(&exp, cli->string, 0, NULL, 0) == 0) vty_out(vty, " (%s) %s\n", node_names[node->node], cli->string); } } done: regfree(&exp); return CMD_SUCCESS; } DEFUN_HIDDEN(show_cli_graph_vtysh, show_cli_graph_vtysh_cmd, "show cli graph", SHOW_STR "CLI reflection\n" "Dump current command space as DOT graph\n") { struct cmd_node *cn = vector_slot(cmdvec, vty->node); char *dot = cmd_graph_dump_dot(cn->cmdgraph); vty_out(vty, "%s\n", dot); XFREE(MTYPE_TMP, dot); return CMD_SUCCESS; } static void vtysh_install_default(enum node_type node) { install_element(node, &config_list_cmd); install_element(node, &find_cmd); install_element(node, &show_cli_graph_vtysh_cmd); install_element(node, &vtysh_output_file_cmd); install_element(node, &no_vtysh_output_file_cmd); } /* Making connection to protocol daemon. */ static int vtysh_connect(struct vtysh_client *vclient) { int ret; int sock, len; struct sockaddr_un addr; struct stat s_stat; const char *path; if (!vclient->path[0]) snprintf(vclient->path, sizeof(vclient->path), "%s/%s.vty", vtydir, vclient->name); path = vclient->path; /* Stat socket to see if we have permission to access it. */ ret = stat(path, &s_stat); if (ret < 0 && errno != ENOENT) { fprintf(stderr, "vtysh_connect(%s): stat = %s\n", path, safe_strerror(errno)); exit(1); } if (ret >= 0) { if (!S_ISSOCK(s_stat.st_mode)) { fprintf(stderr, "vtysh_connect(%s): Not a socket\n", path); exit(1); } } sock = socket(AF_UNIX, SOCK_STREAM, 0); if (sock < 0) { #ifdef DEBUG fprintf(stderr, "vtysh_connect(%s): socket = %s\n", path, safe_strerror(errno)); #endif /* DEBUG */ return -1; } memset(&addr, 0, sizeof(struct sockaddr_un)); addr.sun_family = AF_UNIX; strlcpy(addr.sun_path, path, sizeof(addr.sun_path)); #ifdef HAVE_STRUCT_SOCKADDR_UN_SUN_LEN len = addr.sun_len = SUN_LEN(&addr); #else len = sizeof(addr.sun_family) + strlen(addr.sun_path); #endif /* HAVE_STRUCT_SOCKADDR_UN_SUN_LEN */ ret = connect(sock, (struct sockaddr *)&addr, len); if (ret < 0) { #ifdef DEBUG fprintf(stderr, "vtysh_connect(%s): connect = %s\n", path, safe_strerror(errno)); #endif /* DEBUG */ close(sock); return -1; } vclient->fd = sock; return 0; } static int vtysh_reconnect(struct vtysh_client *vclient) { int ret; fprintf(stderr, "Warning: connecting to %s...", vclient->name); ret = vtysh_connect(vclient); if (ret < 0) { fprintf(stderr, "failed!\n"); return ret; } fprintf(stderr, "success!\n"); if (vtysh_client_execute(vclient, "enable") < 0) return -1; return vtysh_execute_no_pager("end"); } /* Return true if str ends with suffix, else return false */ static int ends_with(const char *str, const char *suffix) { if (!str || !suffix) return 0; size_t lenstr = strlen(str); size_t lensuffix = strlen(suffix); if (lensuffix > lenstr) return 0; return strncmp(str + lenstr - lensuffix, suffix, lensuffix) == 0; } static void vtysh_client_sorted_insert(struct vtysh_client *head_client, struct vtysh_client *client) { struct vtysh_client *prev_node, *current_node; prev_node = head_client; current_node = head_client->next; while (current_node) { if (strcmp(current_node->path, client->path) > 0) break; prev_node = current_node; current_node = current_node->next; } client->next = current_node; prev_node->next = client; } #define MAXIMUM_INSTANCES 10 static void vtysh_update_all_instances(struct vtysh_client *head_client) { struct vtysh_client *client; DIR *dir; struct dirent *file; int n = 0; if (head_client->flag != VTYSH_OSPFD) return; /* ls vty_sock_dir and look for all files ending in .vty */ dir = opendir(vtydir); if (dir) { while ((file = readdir(dir)) != NULL) { if (frrstr_startswith(file->d_name, "ospfd-") && ends_with(file->d_name, ".vty")) { if (n == MAXIMUM_INSTANCES) { fprintf(stderr, "Parsing %s, client limit(%d) reached!\n", vtydir, n); break; } client = (struct vtysh_client *)malloc( sizeof(struct vtysh_client)); client->fd = -1; client->name = "ospfd"; client->flag = VTYSH_OSPFD; snprintf(client->path, sizeof(client->path), "%s/%s", vtydir, file->d_name); client->next = NULL; vtysh_client_sorted_insert(head_client, client); n++; } } closedir(dir); } } static int vtysh_connect_all_instances(struct vtysh_client *head_client) { struct vtysh_client *client; int rc = 0; vtysh_update_all_instances(head_client); client = head_client->next; while (client) { if (vtysh_connect(client) == 0) rc++; client = client->next; } return rc; } int vtysh_connect_all(const char *daemon_name) { unsigned int i; int rc = 0; int matches = 0; for (i = 0; i < array_size(vtysh_client); i++) { if (!daemon_name || !strcmp(daemon_name, vtysh_client[i].name)) { matches++; if (vtysh_connect(&vtysh_client[i]) == 0) rc++; rc += vtysh_connect_all_instances(&vtysh_client[i]); } } if (!matches) fprintf(stderr, "Error: no daemons match name %s!\n", daemon_name); return rc; } /* To disable readline's filename completion. */ static char *vtysh_completion_entry_function(const char *ignore, int invoking_key) { return NULL; } void vtysh_readline_init(void) { /* readline related settings. */ rl_initialize(); rl_bind_key('?', (rl_command_func_t *)vtysh_rl_describe); rl_completion_entry_function = vtysh_completion_entry_function; rl_attempted_completion_function = new_completion; } char *vtysh_prompt(void) { static char buf[512]; snprintf(buf, sizeof buf, cmd_prompt(vty->node), cmd_hostname_get()); return buf; } static void vtysh_ac_line(void *arg, const char *line) { vector comps = arg; size_t i; for (i = 0; i < vector_active(comps); i++) if (!strcmp(line, (char *)vector_slot(comps, i))) return; vector_set(comps, XSTRDUP(MTYPE_COMPLETION, line)); } static void vtysh_autocomplete(vector comps, struct cmd_token *token) { char accmd[256]; size_t i; snprintf(accmd, sizeof(accmd), "autocomplete %d %s %s", token->type, token->text, token->varname ? token->varname : "-"); vty->of_saved = vty->of; vty->of = NULL; for (i = 0; i < array_size(vtysh_client); i++) vtysh_client_run_all(&vtysh_client[i], accmd, 1, vtysh_ac_line, comps); vty->of = vty->of_saved; } static const struct cmd_variable_handler vtysh_var_handler[] = { {/* match all */ .tokenname = NULL, .varname = NULL, .completions = vtysh_autocomplete}, {.completions = NULL}}; void vtysh_uninit(void) { if (vty->of != stdout) fclose(vty->of); } void vtysh_init_vty(void) { /* Make vty structure. */ vty = vty_new(); vty->type = VTY_SHELL; vty->node = VIEW_NODE; /* set default output */ vty->of = stdout; vtysh_pager_envdef(false); /* Initialize commands. */ cmd_init(0); cmd_variable_handler_register(vtysh_var_handler); /* Install nodes. */ install_node(&bgp_node, NULL); install_node(&rip_node, NULL); install_node(&interface_node, NULL); install_node(&pw_node, NULL); install_node(&link_params_node, NULL); install_node(&vrf_node, NULL); install_node(&nh_group_node, NULL); install_node(&rmap_node, NULL); install_node(&pbr_map_node, NULL); install_node(&zebra_node, NULL); install_node(&bgp_vpnv4_node, NULL); install_node(&bgp_vpnv6_node, NULL); install_node(&bgp_flowspecv4_node, NULL); install_node(&bgp_flowspecv6_node, NULL); install_node(&bgp_ipv4_node, NULL); install_node(&bgp_ipv4m_node, NULL); install_node(&bgp_ipv4l_node, NULL); install_node(&bgp_ipv6_node, NULL); install_node(&bgp_ipv6m_node, NULL); install_node(&bgp_ipv6l_node, NULL); install_node(&bgp_vrf_policy_node, NULL); install_node(&bgp_evpn_node, NULL); install_node(&bgp_evpn_vni_node, NULL); install_node(&bgp_vnc_defaults_node, NULL); install_node(&bgp_vnc_nve_group_node, NULL); install_node(&bgp_vnc_l2_group_node, NULL); install_node(&ospf_node, NULL); install_node(&eigrp_node, NULL); install_node(&babel_node, NULL); install_node(&ripng_node, NULL); install_node(&ospf6_node, NULL); install_node(&ldp_node, NULL); install_node(&ldp_ipv4_node, NULL); install_node(&ldp_ipv6_node, NULL); install_node(&ldp_ipv4_iface_node, NULL); install_node(&ldp_ipv6_iface_node, NULL); install_node(&ldp_l2vpn_node, NULL); install_node(&ldp_pseudowire_node, NULL); install_node(&keychain_node, NULL); install_node(&keychain_key_node, NULL); install_node(&isis_node, NULL); install_node(&openfabric_node, NULL); install_node(&vty_node, NULL); install_node(&rpki_node, NULL); install_node(&bmp_node, NULL); #if HAVE_BFDD > 0 install_node(&bfd_node, NULL); install_node(&bfd_peer_node, NULL); #endif /* HAVE_BFDD */ struct cmd_node *node; for (unsigned int i = 0; i < vector_active(cmdvec); i++) { node = vector_slot(cmdvec, i); if (!node || node->node == VIEW_NODE) continue; vtysh_install_default(node->node); } install_element(VIEW_NODE, &vtysh_enable_cmd); install_element(ENABLE_NODE, &vtysh_config_terminal_cmd); install_element(ENABLE_NODE, &vtysh_disable_cmd); /* "exit" command. */ install_element(VIEW_NODE, &vtysh_exit_all_cmd); install_element(CONFIG_NODE, &vtysh_exit_all_cmd); install_element(VIEW_NODE, &vtysh_quit_all_cmd); install_element(CONFIG_NODE, &vtysh_quit_all_cmd); install_element(RIP_NODE, &vtysh_exit_ripd_cmd); install_element(RIP_NODE, &vtysh_quit_ripd_cmd); install_element(RIPNG_NODE, &vtysh_exit_ripngd_cmd); install_element(RIPNG_NODE, &vtysh_quit_ripngd_cmd); install_element(OSPF_NODE, &vtysh_exit_ospfd_cmd); install_element(OSPF_NODE, &vtysh_quit_ospfd_cmd); install_element(EIGRP_NODE, &vtysh_exit_eigrpd_cmd); install_element(EIGRP_NODE, &vtysh_quit_eigrpd_cmd); install_element(BABEL_NODE, &vtysh_exit_babeld_cmd); install_element(BABEL_NODE, &vtysh_quit_babeld_cmd); install_element(OSPF6_NODE, &vtysh_exit_ospf6d_cmd); install_element(OSPF6_NODE, &vtysh_quit_ospf6d_cmd); #if defined(HAVE_LDPD) install_element(LDP_NODE, &vtysh_exit_ldpd_cmd); install_element(LDP_NODE, &vtysh_quit_ldpd_cmd); install_element(LDP_IPV4_NODE, &vtysh_exit_ldpd_cmd); install_element(LDP_IPV4_NODE, &vtysh_quit_ldpd_cmd); install_element(LDP_IPV4_NODE, &ldp_exit_address_family_cmd); install_element(LDP_IPV6_NODE, &vtysh_exit_ldpd_cmd); install_element(LDP_IPV6_NODE, &vtysh_quit_ldpd_cmd); install_element(LDP_IPV6_NODE, &ldp_exit_address_family_cmd); install_element(LDP_IPV4_IFACE_NODE, &vtysh_exit_ldpd_cmd); install_element(LDP_IPV4_IFACE_NODE, &vtysh_quit_ldpd_cmd); install_element(LDP_IPV6_IFACE_NODE, &vtysh_exit_ldpd_cmd); install_element(LDP_IPV6_IFACE_NODE, &vtysh_quit_ldpd_cmd); install_element(LDP_L2VPN_NODE, &vtysh_exit_ldpd_cmd); install_element(LDP_L2VPN_NODE, &vtysh_quit_ldpd_cmd); install_element(LDP_PSEUDOWIRE_NODE, &vtysh_exit_ldpd_cmd); install_element(LDP_PSEUDOWIRE_NODE, &vtysh_quit_ldpd_cmd); #endif install_element(BGP_NODE, &vtysh_exit_bgpd_cmd); install_element(BGP_NODE, &vtysh_quit_bgpd_cmd); install_element(BGP_VPNV4_NODE, &vtysh_exit_bgpd_cmd); install_element(BGP_VPNV4_NODE, &vtysh_quit_bgpd_cmd); install_element(BGP_VPNV6_NODE, &vtysh_exit_bgpd_cmd); install_element(BGP_VPNV6_NODE, &vtysh_quit_bgpd_cmd); install_element(BGP_FLOWSPECV4_NODE, &vtysh_exit_bgpd_cmd); install_element(BGP_FLOWSPECV4_NODE, &vtysh_quit_bgpd_cmd); install_element(BGP_FLOWSPECV6_NODE, &vtysh_exit_bgpd_cmd); install_element(BGP_FLOWSPECV6_NODE, &vtysh_quit_bgpd_cmd); install_element(BGP_IPV4_NODE, &vtysh_exit_bgpd_cmd); install_element(BGP_IPV4_NODE, &vtysh_quit_bgpd_cmd); install_element(BGP_IPV4M_NODE, &vtysh_exit_bgpd_cmd); install_element(BGP_IPV4M_NODE, &vtysh_quit_bgpd_cmd); install_element(BGP_IPV4L_NODE, &vtysh_exit_bgpd_cmd); install_element(BGP_IPV4L_NODE, &vtysh_quit_bgpd_cmd); install_element(BGP_IPV6_NODE, &vtysh_exit_bgpd_cmd); install_element(BGP_IPV6_NODE, &vtysh_quit_bgpd_cmd); install_element(BGP_IPV6M_NODE, &vtysh_exit_bgpd_cmd); install_element(BGP_IPV6M_NODE, &vtysh_quit_bgpd_cmd); install_element(BGP_EVPN_NODE, &vtysh_quit_bgpd_cmd); install_element(BGP_EVPN_NODE, &vtysh_exit_bgpd_cmd); install_element(BGP_EVPN_VNI_NODE, &vtysh_exit_bgpd_cmd); install_element(BGP_EVPN_VNI_NODE, &vtysh_quit_bgpd_cmd); install_element(BGP_IPV6L_NODE, &vtysh_exit_bgpd_cmd); install_element(BGP_IPV6L_NODE, &vtysh_quit_bgpd_cmd); #if defined(ENABLE_BGP_VNC) install_element(BGP_VRF_POLICY_NODE, &vtysh_exit_bgpd_cmd); install_element(BGP_VRF_POLICY_NODE, &vtysh_quit_bgpd_cmd); install_element(BGP_VNC_DEFAULTS_NODE, &vtysh_exit_bgpd_cmd); install_element(BGP_VNC_DEFAULTS_NODE, &vtysh_quit_bgpd_cmd); install_element(BGP_VNC_NVE_GROUP_NODE, &vtysh_exit_bgpd_cmd); install_element(BGP_VNC_NVE_GROUP_NODE, &vtysh_quit_bgpd_cmd); install_element(BGP_VNC_L2_GROUP_NODE, &vtysh_exit_bgpd_cmd); install_element(BGP_VNC_L2_GROUP_NODE, &vtysh_quit_bgpd_cmd); #endif install_element(ISIS_NODE, &vtysh_exit_isisd_cmd); install_element(ISIS_NODE, &vtysh_quit_isisd_cmd); install_element(OPENFABRIC_NODE, &vtysh_exit_fabricd_cmd); install_element(OPENFABRIC_NODE, &vtysh_quit_fabricd_cmd); install_element(KEYCHAIN_NODE, &vtysh_exit_ripd_cmd); install_element(KEYCHAIN_NODE, &vtysh_quit_ripd_cmd); install_element(KEYCHAIN_KEY_NODE, &vtysh_exit_ripd_cmd); install_element(KEYCHAIN_KEY_NODE, &vtysh_quit_ripd_cmd); install_element(RMAP_NODE, &vtysh_exit_rmap_cmd); install_element(RMAP_NODE, &vtysh_quit_rmap_cmd); install_element(PBRMAP_NODE, &vtysh_exit_pbr_map_cmd); install_element(PBRMAP_NODE, &vtysh_quit_pbr_map_cmd); #if HAVE_BFDD > 0 /* Enter node. */ install_element(CONFIG_NODE, &bfd_enter_cmd); install_element(BFD_NODE, &bfd_peer_enter_cmd); /* Exit/quit node. */ install_element(BFD_NODE, &vtysh_exit_bfdd_cmd); install_element(BFD_NODE, &vtysh_quit_bfdd_cmd); install_element(BFD_PEER_NODE, &vtysh_exit_bfdd_cmd); install_element(BFD_PEER_NODE, &vtysh_quit_bfdd_cmd); /* End/exit all. */ install_element(BFD_NODE, &vtysh_end_all_cmd); install_element(BFD_PEER_NODE, &vtysh_end_all_cmd); #endif /* HAVE_BFDD */ install_element(VTY_NODE, &vtysh_exit_line_vty_cmd); install_element(VTY_NODE, &vtysh_quit_line_vty_cmd); /* "end" command. */ install_element(CONFIG_NODE, &vtysh_end_all_cmd); install_element(ENABLE_NODE, &vtysh_end_all_cmd); install_element(RIP_NODE, &vtysh_end_all_cmd); install_element(RIPNG_NODE, &vtysh_end_all_cmd); install_element(OSPF_NODE, &vtysh_end_all_cmd); install_element(EIGRP_NODE, &vtysh_end_all_cmd); install_element(BABEL_NODE, &vtysh_end_all_cmd); install_element(OSPF6_NODE, &vtysh_end_all_cmd); install_element(LDP_NODE, &vtysh_end_all_cmd); install_element(LDP_IPV4_NODE, &vtysh_end_all_cmd); install_element(LDP_IPV6_NODE, &vtysh_end_all_cmd); install_element(LDP_IPV4_IFACE_NODE, &vtysh_end_all_cmd); install_element(LDP_IPV6_IFACE_NODE, &vtysh_end_all_cmd); install_element(LDP_L2VPN_NODE, &vtysh_end_all_cmd); install_element(LDP_PSEUDOWIRE_NODE, &vtysh_end_all_cmd); install_element(BGP_NODE, &vtysh_end_all_cmd); install_element(BGP_IPV4_NODE, &vtysh_end_all_cmd); install_element(BGP_IPV4M_NODE, &vtysh_end_all_cmd); install_element(BGP_IPV4L_NODE, &vtysh_end_all_cmd); install_element(BGP_VPNV4_NODE, &vtysh_end_all_cmd); install_element(BGP_VPNV6_NODE, &vtysh_end_all_cmd); install_element(BGP_FLOWSPECV4_NODE, &vtysh_end_all_cmd); install_element(BGP_FLOWSPECV6_NODE, &vtysh_end_all_cmd); install_element(BGP_IPV6_NODE, &vtysh_end_all_cmd); install_element(BGP_IPV6M_NODE, &vtysh_end_all_cmd); install_element(BGP_IPV6L_NODE, &vtysh_end_all_cmd); install_element(BGP_VRF_POLICY_NODE, &vtysh_end_all_cmd); install_element(BGP_EVPN_NODE, &vtysh_end_all_cmd); install_element(BGP_EVPN_VNI_NODE, &vtysh_end_all_cmd); install_element(BGP_VNC_DEFAULTS_NODE, &vtysh_end_all_cmd); install_element(BGP_VNC_NVE_GROUP_NODE, &vtysh_end_all_cmd); install_element(BGP_VNC_L2_GROUP_NODE, &vtysh_end_all_cmd); install_element(ISIS_NODE, &vtysh_end_all_cmd); install_element(OPENFABRIC_NODE, &vtysh_end_all_cmd); install_element(KEYCHAIN_NODE, &vtysh_end_all_cmd); install_element(KEYCHAIN_KEY_NODE, &vtysh_end_all_cmd); install_element(RMAP_NODE, &vtysh_end_all_cmd); install_element(PBRMAP_NODE, &vtysh_end_all_cmd); install_element(VTY_NODE, &vtysh_end_all_cmd); install_element(INTERFACE_NODE, &vtysh_end_all_cmd); install_element(INTERFACE_NODE, &vtysh_exit_interface_cmd); install_element(LINK_PARAMS_NODE, &exit_link_params_cmd); install_element(LINK_PARAMS_NODE, &vtysh_end_all_cmd); install_element(LINK_PARAMS_NODE, &vtysh_exit_interface_cmd); install_element(INTERFACE_NODE, &vtysh_quit_interface_cmd); install_element(PW_NODE, &vtysh_end_all_cmd); install_element(PW_NODE, &vtysh_exit_interface_cmd); install_element(PW_NODE, &vtysh_quit_interface_cmd); install_element(CONFIG_NODE, &vtysh_nexthop_group_cmd); install_element(NH_GROUP_NODE, &vtysh_end_all_cmd); install_element(NH_GROUP_NODE, &vtysh_exit_nexthop_group_cmd); install_element(NH_GROUP_NODE, &vtysh_quit_nexthop_group_cmd); install_element(VRF_NODE, &vtysh_end_all_cmd); install_element(VRF_NODE, &vtysh_exit_vrf_cmd); install_element(VRF_NODE, &vtysh_quit_vrf_cmd); install_element(CONFIG_NODE, &router_eigrp_cmd); install_element(CONFIG_NODE, &router_babel_cmd); install_element(CONFIG_NODE, &router_rip_cmd); install_element(CONFIG_NODE, &router_ripng_cmd); install_element(CONFIG_NODE, &router_ospf_cmd); install_element(CONFIG_NODE, &router_ospf6_cmd); #if defined(HAVE_LDPD) install_element(CONFIG_NODE, &ldp_mpls_ldp_cmd); install_element(LDP_NODE, &ldp_address_family_ipv4_cmd); install_element(LDP_NODE, &ldp_address_family_ipv6_cmd); install_element(LDP_IPV4_NODE, &ldp_interface_ifname_cmd); install_element(LDP_IPV6_NODE, &ldp_interface_ifname_cmd); install_element(CONFIG_NODE, &ldp_l2vpn_word_type_vpls_cmd); install_element(LDP_L2VPN_NODE, &ldp_member_pseudowire_ifname_cmd); #endif install_element(CONFIG_NODE, &router_isis_cmd); install_element(CONFIG_NODE, &router_openfabric_cmd); install_element(CONFIG_NODE, &router_bgp_cmd); #ifdef KEEP_OLD_VPN_COMMANDS install_element(BGP_NODE, &address_family_vpnv4_cmd); install_element(BGP_NODE, &address_family_vpnv6_cmd); #endif /* KEEP_OLD_VPN_COMMANDS */ #if defined(ENABLE_BGP_VNC) install_element(BGP_NODE, &vnc_vrf_policy_cmd); install_element(BGP_NODE, &vnc_defaults_cmd); install_element(BGP_NODE, &vnc_nve_group_cmd); install_element(BGP_NODE, &vnc_l2_group_cmd); #endif install_element(BGP_NODE, &address_family_ipv4_cmd); install_element(BGP_NODE, &address_family_ipv4_multicast_cmd); install_element(BGP_NODE, &address_family_ipv4_vpn_cmd); install_element(BGP_NODE, &address_family_ipv4_labeled_unicast_cmd); install_element(BGP_NODE, &address_family_ipv6_cmd); install_element(BGP_NODE, &address_family_ipv6_multicast_cmd); install_element(BGP_NODE, &address_family_ipv6_vpn_cmd); install_element(BGP_NODE, &address_family_ipv6_labeled_unicast_cmd); install_element(BGP_NODE, &address_family_evpn_cmd); install_element(BGP_NODE, &address_family_flowspecv4_cmd); install_element(BGP_NODE, &address_family_flowspecv6_cmd); #if defined(HAVE_CUMULUS) install_element(BGP_NODE, &address_family_evpn2_cmd); #endif install_element(BGP_VPNV4_NODE, &exit_address_family_cmd); install_element(BGP_VPNV6_NODE, &exit_address_family_cmd); install_element(BGP_IPV4_NODE, &exit_address_family_cmd); install_element(BGP_IPV4M_NODE, &exit_address_family_cmd); install_element(BGP_IPV4L_NODE, &exit_address_family_cmd); install_element(BGP_IPV6_NODE, &exit_address_family_cmd); install_element(BGP_IPV6M_NODE, &exit_address_family_cmd); install_element(BGP_EVPN_NODE, &exit_address_family_cmd); install_element(BGP_IPV6L_NODE, &exit_address_family_cmd); install_element(BGP_FLOWSPECV4_NODE, &exit_address_family_cmd); install_element(BGP_FLOWSPECV6_NODE, &exit_address_family_cmd); install_element(BGP_NODE, &bmp_targets_cmd); install_element(BMP_NODE, &bmp_exit_cmd); install_element(BMP_NODE, &bmp_quit_cmd); install_element(BMP_NODE, &vtysh_end_all_cmd); install_element(CONFIG_NODE, &rpki_cmd); install_element(RPKI_NODE, &rpki_exit_cmd); install_element(RPKI_NODE, &rpki_quit_cmd); install_element(RPKI_NODE, &vtysh_end_all_cmd); /* EVPN commands */ install_element(BGP_EVPN_NODE, &bgp_evpn_vni_cmd); install_element(BGP_EVPN_VNI_NODE, &exit_vni_cmd); install_element(BGP_VRF_POLICY_NODE, &exit_vrf_policy_cmd); install_element(BGP_VNC_DEFAULTS_NODE, &exit_vnc_config_cmd); install_element(BGP_VNC_NVE_GROUP_NODE, &exit_vnc_config_cmd); install_element(BGP_VNC_L2_GROUP_NODE, &exit_vnc_config_cmd); install_element(CONFIG_NODE, &key_chain_cmd); install_element(CONFIG_NODE, &vtysh_route_map_cmd); install_element(CONFIG_NODE, &vtysh_pbr_map_cmd); install_element(CONFIG_NODE, &vtysh_no_pbr_map_cmd); install_element(CONFIG_NODE, &vtysh_line_vty_cmd); install_element(KEYCHAIN_NODE, &key_cmd); install_element(KEYCHAIN_NODE, &key_chain_cmd); install_element(KEYCHAIN_KEY_NODE, &key_chain_cmd); install_element(CONFIG_NODE, &vtysh_interface_cmd); install_element(CONFIG_NODE, &vtysh_pseudowire_cmd); install_element(INTERFACE_NODE, &vtysh_link_params_cmd); install_element(ENABLE_NODE, &vtysh_show_running_config_cmd); install_element(ENABLE_NODE, &vtysh_copy_running_config_cmd); install_element(CONFIG_NODE, &vtysh_vrf_cmd); install_element(VRF_NODE, &vtysh_vrf_netns_cmd); install_element(VRF_NODE, &vtysh_no_vrf_netns_cmd); install_element(VRF_NODE, &exit_vrf_config_cmd); install_element(CONFIG_NODE, &vtysh_no_nexthop_group_cmd); /* "write terminal" command. */ install_element(ENABLE_NODE, &vtysh_write_terminal_cmd); install_element(CONFIG_NODE, &vtysh_integrated_config_cmd); install_element(CONFIG_NODE, &no_vtysh_integrated_config_cmd); /* "write memory" command. */ install_element(ENABLE_NODE, &vtysh_write_memory_cmd); install_element(CONFIG_NODE, &vtysh_terminal_paginate_cmd); install_element(VIEW_NODE, &vtysh_terminal_paginate_cmd); install_element(VIEW_NODE, &vtysh_terminal_length_cmd); install_element(VIEW_NODE, &vtysh_terminal_no_length_cmd); install_element(VIEW_NODE, &vtysh_show_daemons_cmd); install_element(VIEW_NODE, &vtysh_ping_cmd); install_element(VIEW_NODE, &vtysh_ping_ip_cmd); install_element(VIEW_NODE, &vtysh_traceroute_cmd); install_element(VIEW_NODE, &vtysh_traceroute_ip_cmd); install_element(VIEW_NODE, &vtysh_mtrace_cmd); install_element(VIEW_NODE, &vtysh_ping6_cmd); install_element(VIEW_NODE, &vtysh_traceroute6_cmd); #if defined(HAVE_SHELL_ACCESS) install_element(VIEW_NODE, &vtysh_telnet_cmd); install_element(VIEW_NODE, &vtysh_telnet_port_cmd); install_element(VIEW_NODE, &vtysh_ssh_cmd); #endif #if defined(HAVE_SHELL_ACCESS) install_element(ENABLE_NODE, &vtysh_start_shell_cmd); install_element(ENABLE_NODE, &vtysh_start_bash_cmd); install_element(ENABLE_NODE, &vtysh_start_zsh_cmd); #endif /* debugging */ install_element(VIEW_NODE, &vtysh_show_debugging_cmd); install_element(VIEW_NODE, &vtysh_show_error_code_cmd); install_element(VIEW_NODE, &vtysh_show_debugging_hashtable_cmd); install_element(ENABLE_NODE, &vtysh_debug_all_cmd); install_element(CONFIG_NODE, &vtysh_debug_all_cmd); install_element(ENABLE_NODE, &vtysh_debug_memstats_cmd); install_element(CONFIG_NODE, &vtysh_debug_memstats_cmd); /* misc lib show commands */ install_element(VIEW_NODE, &vtysh_show_memory_cmd); install_element(VIEW_NODE, &vtysh_show_modules_cmd); install_element(VIEW_NODE, &vtysh_show_work_queues_cmd); install_element(VIEW_NODE, &vtysh_show_work_queues_daemon_cmd); install_element(VIEW_NODE, &vtysh_show_thread_cmd); install_element(VIEW_NODE, &vtysh_show_poll_cmd); /* Logging */ install_element(VIEW_NODE, &vtysh_show_logging_cmd); install_element(VIEW_NODE, &vtysh_show_log_filter_cmd); install_element(CONFIG_NODE, &vtysh_log_filter_cmd); install_element(CONFIG_NODE, &vtysh_log_filter_clear_cmd); install_element(CONFIG_NODE, &vtysh_log_stdout_cmd); install_element(CONFIG_NODE, &vtysh_log_stdout_level_cmd); install_element(CONFIG_NODE, &no_vtysh_log_stdout_cmd); install_element(CONFIG_NODE, &vtysh_log_file_cmd); install_element(CONFIG_NODE, &vtysh_log_file_level_cmd); install_element(CONFIG_NODE, &no_vtysh_log_file_cmd); install_element(CONFIG_NODE, &vtysh_log_monitor_cmd); install_element(CONFIG_NODE, &no_vtysh_log_monitor_cmd); install_element(CONFIG_NODE, &vtysh_log_syslog_cmd); install_element(CONFIG_NODE, &no_vtysh_log_syslog_cmd); install_element(CONFIG_NODE, &vtysh_log_facility_cmd); install_element(CONFIG_NODE, &no_vtysh_log_facility_cmd); install_element(CONFIG_NODE, &vtysh_log_record_priority_cmd); install_element(CONFIG_NODE, &no_vtysh_log_record_priority_cmd); install_element(CONFIG_NODE, &vtysh_log_timestamp_precision_cmd); install_element(CONFIG_NODE, &no_vtysh_log_timestamp_precision_cmd); install_element(CONFIG_NODE, &vtysh_service_password_encrypt_cmd); install_element(CONFIG_NODE, &no_vtysh_service_password_encrypt_cmd); install_element(CONFIG_NODE, &vtysh_password_cmd); install_element(CONFIG_NODE, &no_vtysh_password_cmd); install_element(CONFIG_NODE, &vtysh_enable_password_cmd); install_element(CONFIG_NODE, &no_vtysh_enable_password_cmd); } frr-7.2.1/vtysh/vtysh.conf.sample0000644000000000000000000000020013610377563013665 00000000000000! ! Sample configuration file for vtysh. ! !service integrated-vtysh-config !hostname quagga-router !username root nopassword ! frr-7.2.1/vtysh/vtysh.h0000644000000000000000000000711113610377563011717 00000000000000/* Virtual terminal interface shell. * Copyright (C) 2000 Kunihiro Ishiguro * * This file is part of GNU Zebra. * * GNU Zebra is free software; you can redistribute it and/or modify it * under the terms of the GNU General Public License as published by the * Free Software Foundation; either version 2, or (at your option) any * later version. * * GNU Zebra 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; see the file COPYING; if not, write to the Free Software * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA */ #ifndef VTYSH_H #define VTYSH_H #include "memory.h" DECLARE_MGROUP(MVTYSH) #define VTYSH_ZEBRA 0x00001 #define VTYSH_RIPD 0x00002 #define VTYSH_RIPNGD 0x00004 #define VTYSH_OSPFD 0x00008 #define VTYSH_OSPF6D 0x00010 #define VTYSH_BGPD 0x00020 #define VTYSH_ISISD 0x00040 #define VTYSH_PIMD 0x00080 #define VTYSH_LDPD 0x00100 #define VTYSH_WATCHFRR 0x00200 #define VTYSH_NHRPD 0x00400 #define VTYSH_EIGRPD 0x00800 #define VTYSH_BABELD 0x01000 #define VTYSH_SHARPD 0x02000 #define VTYSH_PBRD 0x04000 #define VTYSH_STATICD 0x08000 #define VTYSH_BFDD 0x10000 #define VTYSH_FABRICD 0x20000 #define VTYSH_VRRPD 0x40000 #define VTYSH_WAS_ACTIVE (-2) /* commands in REALLYALL are crucial to correct vtysh operation */ #define VTYSH_REALLYALL ~0U /* watchfrr is not in ALL since library CLI functions should not be * run on it (logging & co. should stay in a fixed/frozen config, and * things like prefix lists are not even initialised) */ #define VTYSH_ALL VTYSH_ZEBRA|VTYSH_RIPD|VTYSH_RIPNGD|VTYSH_OSPFD|VTYSH_OSPF6D|VTYSH_LDPD|VTYSH_BGPD|VTYSH_ISISD|VTYSH_PIMD|VTYSH_NHRPD|VTYSH_EIGRPD|VTYSH_BABELD|VTYSH_SHARPD|VTYSH_PBRD|VTYSH_STATICD|VTYSH_BFDD|VTYSH_FABRICD|VTYSH_VRRPD #define VTYSH_RMAP VTYSH_ZEBRA|VTYSH_RIPD|VTYSH_RIPNGD|VTYSH_OSPFD|VTYSH_OSPF6D|VTYSH_BGPD|VTYSH_ISISD|VTYSH_PIMD|VTYSH_EIGRPD|VTYSH_SHARPD|VTYSH_FABRICD #define VTYSH_INTERFACE VTYSH_ZEBRA|VTYSH_RIPD|VTYSH_RIPNGD|VTYSH_OSPFD|VTYSH_OSPF6D|VTYSH_ISISD|VTYSH_PIMD|VTYSH_NHRPD|VTYSH_EIGRPD|VTYSH_BABELD|VTYSH_PBRD|VTYSH_FABRICD|VTYSH_VRRPD #define VTYSH_VRF VTYSH_ZEBRA|VTYSH_PIMD|VTYSH_STATICD #define VTYSH_KEYS VTYSH_RIPD|VTYSH_EIGRPD enum vtysh_write_integrated { WRITE_INTEGRATED_UNSPECIFIED, WRITE_INTEGRATED_NO, WRITE_INTEGRATED_YES }; extern enum vtysh_write_integrated vtysh_write_integrated; extern char frr_config[]; extern char vtydir[]; void vtysh_init_vty(void); void vtysh_uninit(void); void vtysh_init_cmd(void); extern int vtysh_connect_all(const char *optional_daemon_name); void vtysh_readline_init(void); void vtysh_user_init(void); int vtysh_execute(const char *); int vtysh_execute_no_pager(const char *); int vtysh_execute_command_questionmark(char *input); char *vtysh_prompt(void); void vtysh_config_write(void); int vtysh_config_from_file(struct vty *, FILE *); void config_add_line(struct list *, const char *); int vtysh_mark_file(const char *filename); int vtysh_read_config(const char *); int vtysh_write_config_integrated(void); void vtysh_config_parse_line(void *, const char *); void vtysh_config_dump(void); void vtysh_config_init(void); void suid_on(void); void suid_off(void); /* Child process execution flag. */ extern int execute_flag; extern struct vty *vty; extern int user_mode; #endif /* VTYSH_H */ frr-7.2.1/vtysh/vtysh_config.c0000644000000000000000000004206613610377563013247 00000000000000/* Configuration generator. * Copyright (C) 2000 Kunihiro Ishiguro * * This file is part of GNU Zebra. * * GNU Zebra is free software; you can redistribute it and/or modify it * under the terms of the GNU General Public License as published by the * Free Software Foundation; either version 2, or (at your option) any * later version. * * GNU Zebra 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; see the file COPYING; if not, write to the Free Software * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA */ #include #include "command.h" #include "linklist.h" #include "memory.h" #include "typesafe.h" #include "vtysh/vtysh.h" #include "vtysh/vtysh_user.h" DEFINE_MGROUP(MVTYSH, "vtysh") DEFINE_MTYPE_STATIC(MVTYSH, VTYSH_CONFIG, "Vtysh configuration") DEFINE_MTYPE_STATIC(MVTYSH, VTYSH_CONFIG_LINE, "Vtysh configuration line") vector configvec; PREDECL_RBTREE_UNIQ(config_master); struct config { /* Configuration node name. */ char *name; /* Configuration string line. */ struct list *line; /* Configuration can be nest. */ struct config *config; /* Index of this config. */ uint32_t index; /* Node entry for the typed Red-black tree */ struct config_master_item rbt_item; }; struct list *config_top; static int line_cmp(char *c1, char *c2) { return strcmp(c1, c2); } static void line_del(char *line) { XFREE(MTYPE_VTYSH_CONFIG_LINE, line); } static struct config *config_new(void) { struct config *config; config = XCALLOC(MTYPE_VTYSH_CONFIG, sizeof(struct config)); return config; } static int config_cmp(const struct config *c1, const struct config *c2) { return strcmp(c1->name, c2->name); } static void config_del(struct config *config) { list_delete(&config->line); XFREE(MTYPE_VTYSH_CONFIG_LINE, config->name); XFREE(MTYPE_VTYSH_CONFIG, config); } DECLARE_RBTREE_UNIQ(config_master, struct config, rbt_item, config_cmp) static struct config *config_get(int index, const char *line) { struct config *config; struct config_master_head *master; master = vector_lookup_ensure(configvec, index); if (!master) { master = XMALLOC(MTYPE_VTYSH_CONFIG, sizeof(struct config_master_head)); config_master_init(master); vector_set_index(configvec, index, master); } const struct config config_ref = { .name = (char *)line }; config = config_master_find(master, &config_ref); if (!config) { config = config_new(); config->line = list_new(); config->line->del = (void (*)(void *))line_del; config->line->cmp = (int (*)(void *, void *))line_cmp; config->name = XSTRDUP(MTYPE_VTYSH_CONFIG_LINE, line); config->index = index; config_master_add(master, config); } return config; } void config_add_line(struct list *config, const char *line) { listnode_add(config, XSTRDUP(MTYPE_VTYSH_CONFIG_LINE, line)); } static void config_add_line_uniq(struct list *config, const char *line) { struct listnode *node, *nnode; char *pnt; for (ALL_LIST_ELEMENTS(config, node, nnode, pnt)) { if (strcmp(pnt, line) == 0) return; } listnode_add_sort(config, XSTRDUP(MTYPE_VTYSH_CONFIG_LINE, line)); } /* * Add a line that should only be shown once, and always show at the end of the * config block. * * If the line already exists, it will be moved to the end of the block. If it * does not exist, it will be added at the end of the block. * * Note that this only makes sense when there is just one such line that should * show up at the very end of a config block. Furthermore, if the same block * can show up from multiple daemons, all of them must make sure to print the * line at the end of their config, otherwise the line will show at the end of * the config for the last daemon that printed it. * * Here is a motivating example with the 'exit-vrf' command. Suppose we receive * a config from Zebra like so: * * vrf BLUE * ip route A * ip route B * exit-vrf * * Then suppose we later receive this config from PIM: * * vrf BLUE * ip msdp mesh-group MyGroup member 1.2.3.4 * exit-vrf * * Then we will combine them into one config block like so: * * vrf BLUE * ip route A * ip route B * ip msdp mesh-group MyGroup member 1.2.3.4 * exit-vrf * * Because PIM also sent us an 'exit-vrf', we noticed that we already had one * under the 'vrf BLUE' config block and so we moved it to the end of the * config block again. If PIM had neglected to send us 'exit-vrf', the result * would be this: * * vrf BLUE * ip route A * ip route B * exit-vrf * ip msdp mesh-group MyGroup member 1.2.3.4 * * Therefore, daemons that share config blocks must take care to consistently * print the same block terminators. * * Ideally this would be solved by adding a string to struct config that is * always printed at the end when dumping a config. However, this would only * work when the user is using integrated config. In the non-integrated config * case, daemons are responsible for writing their own config files, and so the * must be able to print these blocks correctly independently of vtysh, which * means they are the ones that need to handle printing the block terminators * and VTYSH needs to be smart enough to combine them properly. * * --- * * config * The config to add the line to * * line * The line to add to the end of the config */ static void config_add_line_uniq_end(struct list *config, const char *line) { struct listnode *node; char *pnt; for (ALL_LIST_ELEMENTS_RO(config, node, pnt)) { if (strcmp(pnt, line) == 0) break; } if (!node) config_add_line(config, line); else listnode_move_to_tail(config, node); } void vtysh_config_parse_line(void *arg, const char *line) { char c; static struct config *config = NULL; if (!line) return; c = line[0]; if (c == '\0') return; switch (c) { /* Suppress exclamation points ! and commented lines. The !s are * generated * dynamically in vtysh_config_dump() */ case '!': case '#': break; case ' ': /* Store line to current configuration. */ if (config) { if (strncmp(line, " link-params", strlen(" link-params")) == 0) { config_add_line(config->line, line); config->index = LINK_PARAMS_NODE; } else if (strncmp(line, " ip multicast boundary", strlen(" ip multicast boundary")) == 0) { config_add_line_uniq_end(config->line, line); } else if (strncmp(line, " ip igmp query-interval", strlen(" ip igmp query-interval")) == 0) { config_add_line_uniq_end(config->line, line); } else if (config->index == LINK_PARAMS_NODE && strncmp(line, " exit-link-params", strlen(" exit")) == 0) { config_add_line(config->line, line); config->index = INTERFACE_NODE; } else if (config->index == VRF_NODE && strncmp(line, " exit-vrf", strlen(" exit-vrf")) == 0) { config_add_line_uniq_end(config->line, line); } else if (!strncmp(line, " vrrp", strlen(" vrrp")) || !strncmp(line, " no vrrp", strlen(" no vrrp"))) { config_add_line(config->line, line); } else if (config->index == RMAP_NODE || config->index == INTERFACE_NODE || config->index == VTY_NODE || config->index == VRF_NODE || config->index == NH_GROUP_NODE) config_add_line_uniq(config->line, line); else config_add_line(config->line, line); } else config_add_line(config_top, line); break; default: if (strncmp(line, "interface", strlen("interface")) == 0) config = config_get(INTERFACE_NODE, line); else if (strncmp(line, "pseudowire", strlen("pseudowire")) == 0) config = config_get(PW_NODE, line); else if (strncmp(line, "vrf", strlen("vrf")) == 0) config = config_get(VRF_NODE, line); else if (strncmp(line, "nexthop-group", strlen("nexthop-group")) == 0) config = config_get(NH_GROUP_NODE, line); else if (strncmp(line, "router-id", strlen("router-id")) == 0) config = config_get(ZEBRA_NODE, line); else if (strncmp(line, "router rip", strlen("router rip")) == 0) config = config_get(RIP_NODE, line); else if (strncmp(line, "router ripng", strlen("router ripng")) == 0) config = config_get(RIPNG_NODE, line); else if (strncmp(line, "router eigrp", strlen("router eigrp")) == 0) config = config_get(EIGRP_NODE, line); else if (strncmp(line, "router babel", strlen("router babel")) == 0) config = config_get(BABEL_NODE, line); else if (strncmp(line, "router ospf", strlen("router ospf")) == 0) config = config_get(OSPF_NODE, line); else if (strncmp(line, "router ospf6", strlen("router ospf6")) == 0) config = config_get(OSPF6_NODE, line); else if (strncmp(line, "mpls ldp", strlen("mpls ldp")) == 0) config = config_get(LDP_NODE, line); else if (strncmp(line, "l2vpn", strlen("l2vpn")) == 0) config = config_get(LDP_L2VPN_NODE, line); else if (strncmp(line, "router bgp", strlen("router bgp")) == 0) config = config_get(BGP_NODE, line); else if (strncmp(line, "router isis", strlen("router isis")) == 0) config = config_get(ISIS_NODE, line); else if (strncmp(line, "router openfabric", strlen("router openfabric")) == 0) config = config_get(OPENFABRIC_NODE, line); else if (strncmp(line, "route-map", strlen("route-map")) == 0) config = config_get(RMAP_NODE, line); else if (strncmp(line, "pbr-map", strlen("pbr-map")) == 0) config = config_get(PBRMAP_NODE, line); else if (strncmp(line, "access-list", strlen("access-list")) == 0) config = config_get(ACCESS_NODE, line); else if (strncmp(line, "ipv6 access-list", strlen("ipv6 access-list")) == 0) config = config_get(ACCESS_IPV6_NODE, line); else if (strncmp(line, "mac access-list", strlen("mac access-list")) == 0) config = config_get(ACCESS_MAC_NODE, line); else if (strncmp(line, "ip prefix-list", strlen("ip prefix-list")) == 0) config = config_get(PREFIX_NODE, line); else if (strncmp(line, "ipv6 prefix-list", strlen("ipv6 prefix-list")) == 0) config = config_get(PREFIX_IPV6_NODE, line); else if (strncmp(line, "bgp as-path access-list", strlen("bgp as-path access-list")) == 0) config = config_get(AS_LIST_NODE, line); else if (strncmp(line, "bgp community-list", strlen("bgp community-list")) == 0 || strncmp(line, "bgp extcommunity-list", strlen("bgp extcommunity-list")) == 0 || strncmp(line, "bgp large-community-list", strlen("bgp large-community-list")) == 0) config = config_get(COMMUNITY_LIST_NODE, line); else if (strncmp(line, "ip route", strlen("ip route")) == 0) config = config_get(IP_NODE, line); else if (strncmp(line, "ipv6 route", strlen("ipv6 route")) == 0) config = config_get(IP_NODE, line); else if (strncmp(line, "key", strlen("key")) == 0) config = config_get(KEYCHAIN_NODE, line); else if (strncmp(line, "line", strlen("line")) == 0) config = config_get(VTY_NODE, line); else if ((strncmp(line, "ipv6 forwarding", strlen("ipv6 forwarding")) == 0) || (strncmp(line, "ip forwarding", strlen("ip forwarding")) == 0)) config = config_get(FORWARDING_NODE, line); else if (strncmp(line, "debug vrf", strlen("debug vrf")) == 0) config = config_get(VRF_DEBUG_NODE, line); else if (strncmp(line, "debug northbound", strlen("debug northbound")) == 0) config = config_get(NORTHBOUND_DEBUG_NODE, line); else if (strncmp(line, "debug route-map", strlen("debug route-map")) == 0) config = config_get(RMAP_DEBUG_NODE, line); else if (strncmp(line, "debug resolver", strlen("debug resolver")) == 0) config = config_get(RESOLVER_DEBUG_NODE, line); else if (strncmp(line, "debug", strlen("debug")) == 0) config = config_get(DEBUG_NODE, line); else if (strncmp(line, "password", strlen("password")) == 0 || strncmp(line, "enable password", strlen("enable password")) == 0) config = config_get(AAA_NODE, line); else if (strncmp(line, "ip protocol", strlen("ip protocol")) == 0) config = config_get(PROTOCOL_NODE, line); else if (strncmp(line, "ipv6 protocol", strlen("ipv6 protocol")) == 0) config = config_get(PROTOCOL_NODE, line); else if (strncmp(line, "ip nht", strlen("ip nht")) == 0) config = config_get(PROTOCOL_NODE, line); else if (strncmp(line, "ipv6 nht", strlen("ipv6 nht")) == 0) config = config_get(PROTOCOL_NODE, line); else if (strncmp(line, "mpls", strlen("mpls")) == 0) config = config_get(MPLS_NODE, line); else if (strncmp(line, "bfd", strlen("bfd")) == 0) config = config_get(BFD_NODE, line); else { if (strncmp(line, "log", strlen("log")) == 0 || strncmp(line, "hostname", strlen("hostname")) == 0 || strncmp(line, "frr", strlen("frr")) == 0 || strncmp(line, "agentx", strlen("agentx")) == 0 || strncmp(line, "no log", strlen("no log")) == 0 || strncmp(line, "no ip prefix-list", strlen("no ip prefix-list")) == 0 || strncmp(line, "no ipv6 prefix-list", strlen("no ipv6 prefix-list")) == 0) config_add_line_uniq(config_top, line); else config_add_line(config_top, line); config = NULL; } break; } } /* Macro to check delimiter is needed between each configuration line * or not. */ #define NO_DELIMITER(I) \ ((I) == ACCESS_NODE || (I) == PREFIX_NODE || (I) == IP_NODE \ || (I) == AS_LIST_NODE || (I) == COMMUNITY_LIST_NODE \ || (I) == ACCESS_IPV6_NODE || (I) == ACCESS_MAC_NODE \ || (I) == PREFIX_IPV6_NODE || (I) == FORWARDING_NODE \ || (I) == DEBUG_NODE || (I) == AAA_NODE || (I) == VRF_DEBUG_NODE \ || (I) == NORTHBOUND_DEBUG_NODE || (I) == RMAP_DEBUG_NODE \ || (I) == RESOLVER_DEBUG_NODE || (I) == MPLS_NODE) /* Display configuration to file pointer. */ void vtysh_config_dump(void) { struct listnode *node, *nnode; struct listnode *mnode, *mnnode; struct config *config; struct config_master_head *master; char *line; unsigned int i; for (ALL_LIST_ELEMENTS(config_top, node, nnode, line)) vty_out(vty, "%s\n", line); vty_out(vty, "!\n"); for (i = 0; i < vector_active(configvec); i++) if ((master = vector_slot(configvec, i)) != NULL) { while ((config = config_master_pop(master))) { /* Don't print empty sections for interface. * Route maps on the * other hand could have a legitimate empty * section at the end. * VRF is handled in the backend, we could have * "configured" VRFs with static routes which * are not under the VRF node. */ if (config->index == INTERFACE_NODE && list_isempty(config->line)) continue; vty_out(vty, "%s\n", config->name); for (ALL_LIST_ELEMENTS(config->line, mnode, mnnode, line)) vty_out(vty, "%s\n", line); if (!NO_DELIMITER(i)) vty_out(vty, "!\n"); config_del(config); } if (NO_DELIMITER(i)) vty_out(vty, "!\n"); } for (i = 0; i < vector_active(configvec); i++) if ((master = vector_slot(configvec, i)) != NULL) { config_master_fini(master); XFREE(MTYPE_VTYSH_CONFIG, master); vector_slot(configvec, i) = NULL; } list_delete_all_node(config_top); } /* Read up configuration file from file_name. */ static int vtysh_read_file(FILE *confp) { struct vty *vty; int ret; vty = vty_new(); vty->wfd = STDERR_FILENO; vty->type = VTY_TERM; vty->node = CONFIG_NODE; vtysh_execute_no_pager("enable"); vtysh_execute_no_pager("configure terminal"); /* Execute configuration file. */ ret = vtysh_config_from_file(vty, confp); vtysh_execute_no_pager("end"); vtysh_execute_no_pager("disable"); vty_close(vty); return (ret); } /* Read up configuration file from config_default_dir. */ int vtysh_read_config(const char *config_default_dir) { FILE *confp = NULL; int ret; confp = fopen(config_default_dir, "r"); if (confp == NULL) { fprintf(stderr, "%% Can't open configuration file %s due to '%s'.\n", config_default_dir, safe_strerror(errno)); return (CMD_ERR_NO_FILE); } ret = vtysh_read_file(confp); fclose(confp); return (ret); } /* We don't write vtysh specific into file from vtysh. vtysh.conf should * be edited by hand. So, we handle only "write terminal" case here and * integrate vtysh specific conf with conf from daemons. */ void vtysh_config_write(void) { char line[512]; if (cmd_hostname_get()) { snprintf(line, sizeof(line), "hostname %s", cmd_hostname_get()); vtysh_config_parse_line(NULL, line); } if (cmd_domainname_get()) { sprintf(line, "domainname %s", cmd_domainname_get()); vtysh_config_parse_line(NULL, line); } if (vtysh_write_integrated == WRITE_INTEGRATED_NO) vtysh_config_parse_line(NULL, "no service integrated-vtysh-config"); if (vtysh_write_integrated == WRITE_INTEGRATED_YES) vtysh_config_parse_line(NULL, "service integrated-vtysh-config"); user_config_write(); } void vtysh_config_init(void) { config_top = list_new(); config_top->del = (void (*)(void *))line_del; configvec = vector_init(1); } frr-7.2.1/vtysh/vtysh_main.c0000644000000000000000000004001613610377563012717 00000000000000/* Virtual terminal interface shell. * Copyright (C) 2000 Kunihiro Ishiguro * * This file is part of GNU Zebra. * * GNU Zebra is free software; you can redistribute it and/or modify it * under the terms of the GNU General Public License as published by the * Free Software Foundation; either version 2, or (at your option) any * later version. * * GNU Zebra 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; see the file COPYING; if not, write to the Free Software * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA */ #include #include #include #include #include #include #include #include #include /* * The append_history function only appears in newer versions * of the readline library it appears like. Since we don't * need this just silently ignore the code on these * ancient platforms. */ #if !defined HAVE_APPEND_HISTORY #define append_history(A, B) #endif #include #include "getopt.h" #include "command.h" #include "memory.h" #include "linklist.h" #include "memory_vty.h" #include "libfrr.h" #include "ferr.h" #include "lib_errors.h" #include "vtysh/vtysh.h" #include "vtysh/vtysh_user.h" /* VTY shell program name. */ char *progname; /* SUID mode */ static uid_t elevuid, realuid; static gid_t elevgid, realgid; #define VTYSH_CONFIG_NAME "vtysh.conf" #define FRR_CONFIG_NAME "frr.conf" /* Configuration file name and directory. */ static char vtysh_config[MAXPATHLEN * 3]; char frr_config[MAXPATHLEN * 3]; char vtydir[MAXPATHLEN]; static char history_file[MAXPATHLEN]; /* Flag for indicate executing child command. */ int execute_flag = 0; /* Flag to indicate if in user/unprivileged mode. */ int user_mode; /* For sigsetjmp() & siglongjmp(). */ static sigjmp_buf jmpbuf; /* Flag for avoid recursive siglongjmp() call. */ static int jmpflag = 0; /* A static variable for holding the line. */ static char *line_read; /* Master of threads. */ struct thread_master *master; /* Command logging */ FILE *logfile; /* SIGTSTP handler. This function care user's ^Z input. */ static void sigtstp(int sig) { /* Execute "end" command. */ vtysh_execute("end"); /* Initialize readline. */ rl_initialize(); printf("\n"); /* Check jmpflag for duplicate siglongjmp(). */ if (!jmpflag) return; jmpflag = 0; /* Back to main command loop. */ siglongjmp(jmpbuf, 1); } /* SIGINT handler. This function care user's ^Z input. */ static void sigint(int sig) { /* Check this process is not child process. */ if (!execute_flag) { rl_initialize(); printf("\n"); rl_forced_update_display(); } } /* Signale wrapper for vtysh. We don't use sigevent because * vtysh doesn't use threads. TODO */ static void vtysh_signal_set(int signo, void (*func)(int)) { struct sigaction sig; struct sigaction osig; sig.sa_handler = func; sigemptyset(&sig.sa_mask); sig.sa_flags = 0; #ifdef SA_RESTART sig.sa_flags |= SA_RESTART; #endif /* SA_RESTART */ sigaction(signo, &sig, &osig); } /* Initialization of signal handles. */ static void vtysh_signal_init(void) { vtysh_signal_set(SIGINT, sigint); vtysh_signal_set(SIGTSTP, sigtstp); vtysh_signal_set(SIGPIPE, SIG_IGN); } /* Help information display. */ static void usage(int status) { if (status != 0) fprintf(stderr, "Try `%s --help' for more information.\n", progname); else printf("Usage : %s [OPTION...]\n\n" "Integrated shell for FRR. \n\n" "-b, --boot Execute boot startup configuration\n" "-c, --command Execute argument as command\n" "-d, --daemon Connect only to the specified daemon\n" "-f, --inputfile Execute commands from specific file and exit\n" "-E, --echo Echo prompt and command in -c mode\n" "-C, --dryrun Check configuration for validity and exit\n" "-m, --markfile Mark input file with context end\n" " --vty_socket Override vty socket path\n" " --config_dir Override config directory path\n" "-N --pathspace Insert prefix into config & socket paths\n" "-u --user Run as an unprivileged user\n" "-w, --writeconfig Write integrated config (frr.conf) and exit\n" "-h, --help Display this help and exit\n\n" "Note that multiple commands may be executed from the command\n" "line by passing multiple -c args, or by embedding linefeed\n" "characters in one or more of the commands.\n\n" "Report bugs to %s\n", progname, FRR_BUG_ADDRESS); exit(status); } /* VTY shell options, we use GNU getopt library. */ #define OPTION_VTYSOCK 1000 #define OPTION_CONFDIR 1001 struct option longopts[] = { {"boot", no_argument, NULL, 'b'}, /* For compatibility with older zebra/quagga versions */ {"eval", required_argument, NULL, 'e'}, {"command", required_argument, NULL, 'c'}, {"daemon", required_argument, NULL, 'd'}, {"vty_socket", required_argument, NULL, OPTION_VTYSOCK}, {"config_dir", required_argument, NULL, OPTION_CONFDIR}, {"inputfile", required_argument, NULL, 'f'}, {"echo", no_argument, NULL, 'E'}, {"dryrun", no_argument, NULL, 'C'}, {"help", no_argument, NULL, 'h'}, {"noerror", no_argument, NULL, 'n'}, {"mark", no_argument, NULL, 'm'}, {"writeconfig", no_argument, NULL, 'w'}, {"pathspace", required_argument, NULL, 'N'}, {"user", no_argument, NULL, 'u'}, {0}}; /* Read a string, and return a pointer to it. Returns NULL on EOF. */ static char *vtysh_rl_gets(void) { HIST_ENTRY *last; /* If the buffer has already been allocated, return the memory * to the free pool. */ if (line_read) { free(line_read); line_read = NULL; } /* Get a line from the user. Change prompt according to node. XXX. */ line_read = readline(vtysh_prompt()); /* If the line has any text in it, save it on the history. But only if * last command in history isn't the same one. */ if (line_read && *line_read) { using_history(); last = previous_history(); if (!last || strcmp(last->line, line_read) != 0) { add_history(line_read); append_history(1, history_file); } } return (line_read); } static void log_it(const char *line) { time_t t = time(NULL); struct tm *tmp = localtime(&t); const char *user = getenv("USER"); char tod[64]; if (!user) user = "boot"; strftime(tod, sizeof tod, "%Y%m%d-%H:%M.%S", tmp); fprintf(logfile, "%s:%s %s\n", tod, user, line); } static int flock_fd; static void vtysh_flock_config(const char *flock_file) { int count = 0; flock_fd = open(flock_file, O_RDONLY, 0644); if (flock_fd < 0) { fprintf(stderr, "Unable to create lock file: %s, %s\n", flock_file, safe_strerror(errno)); return; } while (count < 400 && (flock(flock_fd, LOCK_EX | LOCK_NB) < 0)) { count++; usleep(500000); } if (count >= 400) fprintf(stderr, "Flock of %s failed, continuing this may cause issues\n", flock_file); } static void vtysh_unflock_config(void) { flock(flock_fd, LOCK_UN); close(flock_fd); } void suid_on(void) { if (elevuid != realuid && seteuid(elevuid)) { perror("seteuid(on)"); exit(1); } if (elevgid != realgid && setegid(elevgid)) { perror("setegid(on)"); exit(1); } } void suid_off(void) { if (elevuid != realuid && seteuid(realuid)) { perror("seteuid(off)"); exit(1); } if (elevgid != realgid && setegid(realgid)) { perror("setegid(off)"); exit(1); } } /* VTY shell main routine. */ int main(int argc, char **argv, char **env) { char *p; int opt; int dryrun = 0; int boot_flag = 0; const char *daemon_name = NULL; const char *inputfile = NULL; struct cmd_rec { char *line; struct cmd_rec *next; } *cmd = NULL; struct cmd_rec *tail = NULL; int echo_command = 0; int no_error = 0; int markfile = 0; int writeconfig = 0; int ret = 0; char *homedir = NULL; int ditch_suid = 0; char sysconfdir[MAXPATHLEN]; const char *pathspace_arg = NULL; char pathspace[MAXPATHLEN] = ""; /* SUID: drop down to calling user & go back up when needed */ elevuid = geteuid(); elevgid = getegid(); realuid = getuid(); realgid = getgid(); suid_off(); user_mode = 0; /* may be set in options processing */ /* Preserve name of myself. */ progname = ((p = strrchr(argv[0], '/')) ? ++p : argv[0]); strlcpy(sysconfdir, frr_sysconfdir, sizeof(sysconfdir)); frr_init_vtydir(); strlcpy(vtydir, frr_vtydir, sizeof(vtydir)); /* Option handling. */ while (1) { opt = getopt_long(argc, argv, "be:c:d:nf:mEhCwN:u", longopts, 0); if (opt == EOF) break; switch (opt) { case 0: break; case 'b': boot_flag = 1; break; case 'e': case 'c': { struct cmd_rec *cr; cr = XMALLOC(MTYPE_TMP, sizeof(*cr)); cr->line = optarg; cr->next = NULL; if (tail) tail->next = cr; else cmd = cr; tail = cr; } break; case OPTION_VTYSOCK: ditch_suid = 1; /* option disables SUID */ strlcpy(vtydir, optarg, sizeof(vtydir)); break; case OPTION_CONFDIR: ditch_suid = 1; /* option disables SUID */ snprintf(sysconfdir, sizeof(sysconfdir), "%s/", optarg); break; case 'N': if (strchr(optarg, '/') || strchr(optarg, '.')) { fprintf(stderr, "slashes or dots are not permitted in the --pathspace option.\n"); exit(1); } pathspace_arg = optarg; snprintf(pathspace, sizeof(pathspace), "%s/", optarg); break; case 'd': daemon_name = optarg; break; case 'f': inputfile = optarg; break; case 'm': markfile = 1; break; case 'n': no_error = 1; break; case 'E': echo_command = 1; break; case 'C': dryrun = 1; break; case 'u': user_mode = 1; break; case 'w': writeconfig = 1; break; case 'h': usage(0); break; default: usage(1); break; } } if (ditch_suid) { elevuid = realuid; elevgid = realgid; } if (markfile + writeconfig + dryrun + boot_flag > 1) { fprintf(stderr, "Invalid combination of arguments. Please specify at " "most one of:\n\t-b, -C, -m, -w\n"); return 1; } if (inputfile && (writeconfig || boot_flag)) { fprintf(stderr, "WARNING: Combinining the -f option with -b or -w is " "NOT SUPPORTED since its\nresults are inconsistent!\n"); } snprintf(vtysh_config, sizeof(vtysh_config), "%s%s%s", sysconfdir, pathspace, VTYSH_CONFIG_NAME); snprintf(frr_config, sizeof(frr_config), "%s%s%s", sysconfdir, pathspace, FRR_CONFIG_NAME); if (pathspace_arg) { strlcat(vtydir, "/", sizeof(vtydir)); strlcat(vtydir, pathspace_arg, sizeof(vtydir)); } /* Initialize user input buffer. */ line_read = NULL; setlinebuf(stdout); /* Signal and others. */ vtysh_signal_init(); /* Make vty structure and register commands. */ vtysh_init_vty(); vtysh_init_cmd(); vtysh_user_init(); vtysh_config_init(); vty_init_vtysh(); if (!user_mode) { /* Read vtysh configuration file before connecting to daemons. * (file may not be readable to calling user in SUID mode) */ suid_on(); vtysh_read_config(vtysh_config); suid_off(); } /* Error code library system */ log_ref_init(); lib_error_init(); if (markfile) { if (!inputfile) { fprintf(stderr, "-f option MUST be specified with -m option\n"); return (1); } return (vtysh_mark_file(inputfile)); } /* Start execution only if not in dry-run mode */ if (dryrun && !cmd) { if (inputfile) { ret = vtysh_read_config(inputfile); } else { ret = vtysh_read_config(frr_config); } exit(ret); } if (dryrun && cmd && cmd->line) { if (!user_mode) vtysh_execute("enable"); while (cmd) { struct cmd_rec *cr; char *cmdnow = cmd->line, *next; do { next = strchr(cmdnow, '\n'); if (next) *next++ = '\0'; if (echo_command) printf("%s%s\n", vtysh_prompt(), cmdnow); ret = vtysh_execute_no_pager(cmdnow); if (!no_error && !(ret == CMD_SUCCESS || ret == CMD_SUCCESS_DAEMON || ret == CMD_WARNING)) exit(1); } while ((cmdnow = next) != NULL); cr = cmd; cmd = cmd->next; XFREE(MTYPE_TMP, cr); } exit(ret); } /* Ignore error messages */ if (no_error) { if (freopen("/dev/null", "w", stdout) == NULL) { fprintf(stderr, "Exiting: Failed to duplicate stdout with -n option"); exit(1); } } /* SUID: go back up elevated privs */ suid_on(); /* Make sure we pass authentication before proceeding. */ vtysh_auth(); /* Do not connect until we have passed authentication. */ if (vtysh_connect_all(daemon_name) <= 0) { fprintf(stderr, "Exiting: failed to connect to any daemons.\n"); if (no_error) exit(0); else exit(1); } /* SUID: back down, don't need privs further on */ suid_off(); if (writeconfig) { if (user_mode) { fprintf(stderr, "writeconfig cannot be used when running as an unprivileged user.\n"); if (no_error) exit(0); else exit(1); } vtysh_execute("enable"); return vtysh_write_config_integrated(); } if (inputfile) { vtysh_flock_config(inputfile); ret = vtysh_read_config(inputfile); vtysh_unflock_config(); exit(ret); } /* * Setup history file for use by both -c and regular input * If we can't find the home directory, then don't store * the history information */ homedir = vtysh_get_home(); if (homedir) { snprintf(history_file, sizeof(history_file), "%s/.history_frr", homedir); if (read_history(history_file) != 0) { int fp; fp = open(history_file, O_CREAT | O_EXCL, S_IRUSR | S_IWUSR); if (fp != -1) close(fp); read_history(history_file); } } if (getenv("VTYSH_LOG")) { const char *logpath = getenv("VTYSH_LOG"); logfile = fopen(logpath, "a"); if (!logfile) { fprintf(stderr, "Failed to open logfile (%s): %s\n", logpath, strerror(errno)); exit(1); } } /* If eval mode. */ if (cmd && cmd->line) { /* Enter into enable node. */ if (!user_mode) vtysh_execute("enable"); while (cmd != NULL) { char *eol; while ((eol = strchr(cmd->line, '\n')) != NULL) { *eol = '\0'; add_history(cmd->line); append_history(1, history_file); if (echo_command) printf("%s%s\n", vtysh_prompt(), cmd->line); if (logfile) log_it(cmd->line); ret = vtysh_execute_no_pager(cmd->line); if (!no_error && !(ret == CMD_SUCCESS || ret == CMD_SUCCESS_DAEMON || ret == CMD_WARNING)) exit(1); cmd->line = eol + 1; } add_history(cmd->line); append_history(1, history_file); if (echo_command) printf("%s%s\n", vtysh_prompt(), cmd->line); if (logfile) log_it(cmd->line); /* * Parsing logic for regular commands will be different * than for those commands requiring further * processing, such as cli instructions terminating * with question-mark character. */ if (!vtysh_execute_command_questionmark(cmd->line)) ret = CMD_SUCCESS; else ret = vtysh_execute_no_pager(cmd->line); if (!no_error && !(ret == CMD_SUCCESS || ret == CMD_SUCCESS_DAEMON || ret == CMD_WARNING)) exit(1); { struct cmd_rec *cr; cr = cmd; cmd = cmd->next; XFREE(MTYPE_TMP, cr); } } history_truncate_file(history_file, 1000); exit(0); } /* Boot startup configuration file. */ if (boot_flag) { vtysh_flock_config(frr_config); ret = vtysh_read_config(frr_config); vtysh_unflock_config(); if (ret) { fprintf(stderr, "Configuration file[%s] processing failure: %d\n", frr_config, ret); if (no_error) exit(0); else exit(ret); } else exit(0); } vtysh_readline_init(); vty_hello(vty); /* Enter into enable node. */ if (!user_mode) vtysh_execute("enable"); /* Preparation for longjmp() in sigtstp(). */ sigsetjmp(jmpbuf, 1); jmpflag = 1; /* Main command loop. */ while (vtysh_rl_gets()) vtysh_execute(line_read); vtysh_uninit(); history_truncate_file(history_file, 1000); printf("\n"); /* Rest in peace. */ exit(0); } frr-7.2.1/vtysh/vtysh_user.c0000644000000000000000000001073413610377563012755 00000000000000/* User authentication for vtysh. * Copyright (C) 2000 Kunihiro Ishiguro * * This file is part of GNU Zebra. * * GNU Zebra is free software; you can redistribute it and/or modify it * under the terms of the GNU General Public License as published by the * Free Software Foundation; either version 2, or (at your option) any * later version. * * GNU Zebra 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; see the file COPYING; if not, write to the Free Software * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA */ #include #include #include #ifdef USE_PAM #include #ifdef HAVE_PAM_MISC_H #include #endif #ifdef HAVE_OPENPAM_H #include #endif #endif /* USE_PAM */ #include "memory.h" #include "linklist.h" #include "command.h" #include "vtysh/vtysh_user.h" /* * Compiler is warning about prototypes not being declared. * The DEFUNSH and DEFUN macro's are messing with the * compiler I believe. This is just to make it happy. */ #ifdef USE_PAM static int vtysh_pam(const char *); #endif int vtysh_auth(void); void vtysh_user_init(void); extern struct list *config_top; extern void config_add_line(struct list *config, const char *line); #ifdef USE_PAM static struct pam_conv conv = {PAM_CONV_FUNC, NULL}; static int vtysh_pam(const char *user) { int ret; pam_handle_t *pamh = NULL; /* Start PAM. */ ret = pam_start(FRR_PAM_NAME, user, &conv, &pamh); /* Is user really user? */ if (ret == PAM_SUCCESS) ret = pam_authenticate(pamh, 0); if (ret != PAM_SUCCESS) fprintf(stderr, "vtysh_pam: Failure to initialize pam: %s(%d)", pam_strerror(pamh, ret), ret); /* close Linux-PAM */ if (pam_end(pamh, ret) != PAM_SUCCESS) { pamh = NULL; fprintf(stderr, "vtysh_pam: failed to release authenticator: %s(%d)\n", pam_strerror(pamh, ret), ret); exit(1); } return ret == PAM_SUCCESS ? 0 : 1; } #endif /* USE_PAM */ struct vtysh_user { char *name; uint8_t nopassword; }; struct list *userlist; static struct vtysh_user *user_new(void) { return XCALLOC(MTYPE_TMP, sizeof(struct vtysh_user)); } static struct vtysh_user *user_lookup(const char *name) { struct listnode *node, *nnode; struct vtysh_user *user; for (ALL_LIST_ELEMENTS(userlist, node, nnode, user)) { if (strcmp(user->name, name) == 0) return user; } return NULL; } void user_config_write(void) { struct listnode *node, *nnode; struct vtysh_user *user; char line[128]; for (ALL_LIST_ELEMENTS(userlist, node, nnode, user)) { if (user->nopassword) { sprintf(line, "username %s nopassword", user->name); config_add_line(config_top, line); } } } static struct vtysh_user *user_get(const char *name) { struct vtysh_user *user; user = user_lookup(name); if (user) return user; user = user_new(); user->name = strdup(name); listnode_add(userlist, user); return user; } DEFUN (vtysh_banner_motd_file, vtysh_banner_motd_file_cmd, "banner motd file FILE", "Set banner\n" "Banner for motd\n" "Banner from a file\n" "Filename\n") { int idx_file = 3; return cmd_banner_motd_file(argv[idx_file]->arg); } DEFUN (username_nopassword, username_nopassword_cmd, "username WORD nopassword", "\n" "\n" "\n") { int idx_word = 1; struct vtysh_user *user; user = user_get(argv[idx_word]->arg); user->nopassword = 1; return CMD_SUCCESS; } int vtysh_auth(void) { struct vtysh_user *user; struct passwd *passwd; if ((passwd = getpwuid(geteuid())) == NULL) { fprintf(stderr, "could not lookup user ID %d\n", (int)geteuid()); exit(1); } user = user_lookup(passwd->pw_name); if (user && user->nopassword) /* Pass through */; else { #ifdef USE_PAM if (vtysh_pam(passwd->pw_name)) exit(0); #endif /* USE_PAM */ } return 0; } char *vtysh_get_home(void) { struct passwd *passwd; char *homedir; if ((homedir = getenv("HOME")) != NULL) return homedir; /* Fallback if HOME is undefined */ passwd = getpwuid(getuid()); return passwd ? passwd->pw_dir : NULL; } void vtysh_user_init(void) { userlist = list_new(); install_element(CONFIG_NODE, &username_nopassword_cmd); install_element(CONFIG_NODE, &vtysh_banner_motd_file_cmd); } frr-7.2.1/vtysh/vtysh_user.h0000644000000000000000000000176713610377563012770 00000000000000/* User authentication for vtysh. * Copyright (C) 2000 Kunihiro Ishiguro * * This file is part of GNU Zebra. * * GNU Zebra is free software; you can redistribute it and/or modify it * under the terms of the GNU General Public License as published by the * Free Software Foundation; either version 2, or (at your option) any * later version. * * GNU Zebra 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; see the file COPYING; if not, write to the Free Software * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA */ #ifndef _VTYSH_USER_H #define _VTYSH_USER_H int vtysh_auth(void); void vtysh_user_init(void); void user_config_write(void); char *vtysh_get_home(void); #endif /* _VTYSH_USER_H */ frr-7.2.1/watchfrr/0000755000000000000000000000000013610377563011114 500000000000000frr-7.2.1/watchfrr/Makefile0000644000000000000000000000023313610377563012472 00000000000000all: ALWAYS @$(MAKE) -s -C .. watchfrr/watchfrr %: ALWAYS @$(MAKE) -s -C .. watchfrr/$@ Makefile: #nothing ALWAYS: .PHONY: ALWAYS makefiles .SUFFIXES: frr-7.2.1/watchfrr/subdir.am0000644000000000000000000000062513610377563012646 00000000000000# # watchfrr # if WATCHFRR sbin_PROGRAMS += watchfrr/watchfrr vtysh_scan += $(top_srcdir)/watchfrr/watchfrr_vty.c man8 += $(MANBUILD)/frr-watchfrr.8 endif noinst_HEADERS += \ watchfrr/watchfrr.h \ watchfrr/watchfrr_errors.h \ # end watchfrr_watchfrr_LDADD = lib/libfrr.la $(LIBCAP) watchfrr_watchfrr_SOURCES = \ watchfrr/watchfrr.c \ watchfrr/watchfrr_errors.c \ watchfrr/watchfrr_vty.c \ # end frr-7.2.1/watchfrr/watchfrr.c0000644000000000000000000010705013610377563013023 00000000000000/* * Monitor status of frr daemons and restart if necessary. * * Copyright (C) 2004 Andrew J. Schorr * * This program is free software; you can redistribute it and/or modify * it under the terms of the 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; see the file COPYING; if not, write to the Free Software * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA */ #include #include #include #include #include #include #include "command.h" #include "memory_vty.h" #include "libfrr.h" #include "lib_errors.h" #include #include #include #include #include #include "watchfrr.h" #include "watchfrr_errors.h" #ifndef MIN #define MIN(X,Y) (((X) <= (Y)) ? (X) : (Y)) #endif /* Macros to help randomize timers. */ #define JITTER(X) ((random() % ((X)+1))-((X)/2)) #define FUZZY(X) ((X)+JITTER((X)/20)) #define DEFAULT_PERIOD 5 #define DEFAULT_TIMEOUT 90 #define DEFAULT_RESTART_TIMEOUT 20 #define DEFAULT_LOGLEVEL LOG_INFO #define DEFAULT_MIN_RESTART 60 #define DEFAULT_MAX_RESTART 600 #define DEFAULT_RESTART_CMD WATCHFRR_SH_PATH " restart %s" #define DEFAULT_START_CMD WATCHFRR_SH_PATH " start %s" #define DEFAULT_STOP_CMD WATCHFRR_SH_PATH " stop %s" #define PING_TOKEN "PING" DEFINE_MGROUP(WATCHFRR, "watchfrr") DEFINE_MTYPE_STATIC(WATCHFRR, WATCHFRR_DAEMON, "watchfrr daemon entry") /* Needs to be global, referenced somewhere inside libfrr. */ struct thread_master *master; static bool watch_only = false; typedef enum { PHASE_NONE = 0, PHASE_INIT, PHASE_STOPS_PENDING, PHASE_WAITING_DOWN, PHASE_ZEBRA_RESTART_PENDING, PHASE_WAITING_ZEBRA_UP } restart_phase_t; static const char *phase_str[] = { "Idle", "Startup", "Stop jobs running", "Waiting for other daemons to come down", "Zebra restart job running", "Waiting for zebra to come up", "Start jobs running", }; #define PHASE_TIMEOUT (3*gs.restart_timeout) #define STARTUP_TIMEOUT 55 * 1000 struct restart_info { const char *name; const char *what; pid_t pid; struct timeval time; long interval; struct thread *t_kill; int kills; }; static struct global_state { restart_phase_t phase; struct thread *t_phase_hanging; struct thread *t_startup_timeout; const char *vtydir; long period; long timeout; long restart_timeout; long min_restart_interval; long max_restart_interval; struct daemon *daemons; const char *restart_command; const char *start_command; const char *stop_command; struct restart_info restart; int loglevel; struct daemon *special; /* points to zebra when doing phased restart */ int numdaemons; int numpids; int numdown; /* # of daemons that are not UP or UNRESPONSIVE */ } gs = { .phase = PHASE_INIT, .vtydir = frr_vtydir, .period = 1000 * DEFAULT_PERIOD, .timeout = DEFAULT_TIMEOUT, .restart_timeout = DEFAULT_RESTART_TIMEOUT, .loglevel = DEFAULT_LOGLEVEL, .min_restart_interval = DEFAULT_MIN_RESTART, .max_restart_interval = DEFAULT_MAX_RESTART, .restart_command = DEFAULT_RESTART_CMD, .start_command = DEFAULT_START_CMD, .stop_command = DEFAULT_STOP_CMD, }; typedef enum { DAEMON_INIT, DAEMON_DOWN, DAEMON_CONNECTING, DAEMON_UP, DAEMON_UNRESPONSIVE } daemon_state_t; #define IS_UP(DMN) \ (((DMN)->state == DAEMON_UP) || ((DMN)->state == DAEMON_UNRESPONSIVE)) static const char *state_str[] = { "Init", "Down", "Connecting", "Up", "Unresponsive", }; struct daemon { const char *name; daemon_state_t state; int fd; struct timeval echo_sent; unsigned int connect_tries; struct thread *t_wakeup; struct thread *t_read; struct thread *t_write; struct daemon *next; struct restart_info restart; }; #define OPTION_MINRESTART 2000 #define OPTION_MAXRESTART 2001 #define OPTION_DRY 2002 static const struct option longopts[] = { {"daemon", no_argument, NULL, 'd'}, {"statedir", required_argument, NULL, 'S'}, {"loglevel", required_argument, NULL, 'l'}, {"interval", required_argument, NULL, 'i'}, {"timeout", required_argument, NULL, 't'}, {"restart-timeout", required_argument, NULL, 'T'}, {"restart", required_argument, NULL, 'r'}, {"start-command", required_argument, NULL, 's'}, {"kill-command", required_argument, NULL, 'k'}, {"dry", no_argument, NULL, OPTION_DRY}, {"min-restart-interval", required_argument, NULL, OPTION_MINRESTART}, {"max-restart-interval", required_argument, NULL, OPTION_MAXRESTART}, {"pid-file", required_argument, NULL, 'p'}, {"blank-string", required_argument, NULL, 'b'}, {"help", no_argument, NULL, 'h'}, {"version", no_argument, NULL, 'v'}, {NULL, 0, NULL, 0}}; static int try_connect(struct daemon *dmn); static int wakeup_send_echo(struct thread *t_wakeup); static void try_restart(struct daemon *dmn); static void phase_check(void); static void restart_done(struct daemon *dmn); static const char *progname; static void printhelp(FILE *target) { fprintf(target, "Usage : %s [OPTION...] ...\n\n\ Watchdog program to monitor status of frr daemons and try to restart\n\ them if they are down or unresponsive. It determines whether a daemon is\n\ up based on whether it can connect to the daemon's vty unix stream socket.\n\ It then repeatedly sends echo commands over that socket to determine whether\n\ the daemon is responsive. If the daemon crashes, we will receive an EOF\n\ on the socket connection and know immediately that the daemon is down.\n\n\ The daemons to be monitored should be listed on the command line.\n\n\ In order to avoid attempting to restart the daemons in a fast loop,\n\ the -m and -M options allow you to control the minimum delay between\n\ restart commands. The minimum restart delay is recalculated each time\n\ a restart is attempted: if the time since the last restart attempt exceeds\n\ twice the -M value, then the restart delay is set to the -m value.\n\ Otherwise, the interval is doubled (but capped at the -M value).\n\n", progname); fprintf(target, "Options:\n\ -d, --daemon Run in daemon mode. In this mode, error messages are sent\n\ to syslog instead of stdout.\n\ -S, --statedir Set the vty socket directory (default is %s)\n\ -l, --loglevel Set the logging level (default is %d).\n\ The value should range from %d (LOG_EMERG) to %d (LOG_DEBUG),\n\ but it can be set higher than %d if extra-verbose debugging\n\ messages are desired.\n\ --min-restart-interval\n\ Set the minimum seconds to wait between invocations of daemon\n\ restart commands (default is %d).\n\ --max-restart-interval\n\ Set the maximum seconds to wait between invocations of daemon\n\ restart commands (default is %d).\n\ -i, --interval Set the status polling interval in seconds (default is %d)\n\ -t, --timeout Set the unresponsiveness timeout in seconds (default is %d)\n\ -T, --restart-timeout\n\ Set the restart (kill) timeout in seconds (default is %d).\n\ If any background jobs are still running after this much\n\ time has elapsed, they will be killed.\n\ -r, --restart Supply a Bourne shell command to use to restart a single\n\ daemon. The command string should include '%%s' where the\n\ name of the daemon should be substituted.\n\ (default: '%s')\n\ -s, --start-command\n\ Supply a Bourne shell to command to use to start a single\n\ daemon. The command string should include '%%s' where the\n\ name of the daemon should be substituted.\n\ (default: '%s')\n\ -k, --kill-command\n\ Supply a Bourne shell to command to use to stop a single\n\ daemon. The command string should include '%%s' where the\n\ name of the daemon should be substituted.\n\ (default: '%s')\n\ --dry Do not start or restart anything, just log.\n\ -p, --pid-file Set process identifier file name\n\ (default is %s/watchfrr.pid).\n\ -b, --blank-string\n\ When the supplied argument string is found in any of the\n\ various shell command arguments (-r, -s, or -k), replace\n\ it with a space. This is an ugly hack to circumvent problems\n\ passing command-line arguments with embedded spaces.\n\ -v, --version Print program version\n\ -h, --help Display this help and exit\n", frr_vtydir, DEFAULT_LOGLEVEL, LOG_EMERG, LOG_DEBUG, LOG_DEBUG, DEFAULT_MIN_RESTART, DEFAULT_MAX_RESTART, DEFAULT_PERIOD, DEFAULT_TIMEOUT, DEFAULT_RESTART_TIMEOUT, DEFAULT_RESTART_CMD, DEFAULT_START_CMD, DEFAULT_STOP_CMD, frr_vtydir); } static pid_t run_background(char *shell_cmd) { pid_t child; switch (child = fork()) { case -1: flog_err_sys(EC_LIB_SYSTEM_CALL, "fork failed, cannot run command [%s]: %s", shell_cmd, safe_strerror(errno)); return -1; case 0: /* Child process. */ /* Use separate process group so child processes can be killed * easily. */ if (setpgid(0, 0) < 0) zlog_warn("warning: setpgid(0,0) failed: %s", safe_strerror(errno)); { char shell[] = "sh"; char dashc[] = "-c"; char *const argv[4] = {shell, dashc, shell_cmd, NULL}; execv("/bin/sh", argv); flog_err_sys(EC_LIB_SYSTEM_CALL, "execv(/bin/sh -c '%s') failed: %s", shell_cmd, safe_strerror(errno)); _exit(127); } default: /* Parent process: we will reap the child later. */ flog_err_sys(EC_LIB_SYSTEM_CALL, "Forked background command [pid %d]: %s", (int)child, shell_cmd); return child; } } static struct timeval *time_elapsed(struct timeval *result, const struct timeval *start_time) { gettimeofday(result, NULL); result->tv_sec -= start_time->tv_sec; result->tv_usec -= start_time->tv_usec; while (result->tv_usec < 0) { result->tv_usec += 1000000L; result->tv_sec--; } return result; } static int restart_kill(struct thread *t_kill) { struct restart_info *restart = THREAD_ARG(t_kill); struct timeval delay; time_elapsed(&delay, &restart->time); zlog_warn( "Warning: %s %s child process %d still running after " "%ld seconds, sending signal %d", restart->what, restart->name, (int)restart->pid, (long)delay.tv_sec, (restart->kills ? SIGKILL : SIGTERM)); kill(-restart->pid, (restart->kills ? SIGKILL : SIGTERM)); restart->kills++; restart->t_kill = NULL; thread_add_timer(master, restart_kill, restart, gs.restart_timeout, &restart->t_kill); return 0; } static struct restart_info *find_child(pid_t child) { struct daemon *dmn; if (gs.restart.pid == child) return &gs.restart; for (dmn = gs.daemons; dmn; dmn = dmn->next) { if (dmn->restart.pid == child) return &dmn->restart; } return NULL; } static void sigchild(void) { pid_t child; int status; const char *name; const char *what; struct restart_info *restart; struct daemon *dmn; switch (child = waitpid(-1, &status, WNOHANG)) { case -1: flog_err_sys(EC_LIB_SYSTEM_CALL, "waitpid failed: %s", safe_strerror(errno)); return; case 0: zlog_warn("SIGCHLD received, but waitpid did not reap a child"); return; } if (child == integrated_write_pid) { integrated_write_sigchld(status); return; } if ((restart = find_child(child)) != NULL) { name = restart->name; what = restart->what; restart->pid = 0; gs.numpids--; thread_cancel(restart->t_kill); restart->t_kill = NULL; /* Update restart time to reflect the time the command * completed. */ gettimeofday(&restart->time, NULL); } else { flog_err_sys( EC_LIB_SYSTEM_CALL, "waitpid returned status for an unknown child process %d", (int)child); name = "(unknown)"; what = "background"; } if (WIFSTOPPED(status)) zlog_warn("warning: %s %s process %d is stopped", what, name, (int)child); else if (WIFSIGNALED(status)) zlog_warn("%s %s process %d terminated due to signal %d", what, name, (int)child, WTERMSIG(status)); else if (WIFEXITED(status)) { if (WEXITSTATUS(status) != 0) zlog_warn( "%s %s process %d exited with non-zero status %d", what, name, (int)child, WEXITSTATUS(status)); else { zlog_debug("%s %s process %d exited normally", what, name, (int)child); if (restart && restart != &gs.restart) { dmn = container_of(restart, struct daemon, restart); restart_done(dmn); } else if (restart) for (dmn = gs.daemons; dmn; dmn = dmn->next) restart_done(dmn); } } else flog_err_sys( EC_LIB_SYSTEM_CALL, "cannot interpret %s %s process %d wait status 0x%x", what, name, (int)child, status); phase_check(); } static int run_job(struct restart_info *restart, const char *cmdtype, const char *command, int force, int update_interval) { struct timeval delay; if (gs.loglevel > LOG_DEBUG + 1) zlog_debug("attempting to %s %s", cmdtype, restart->name); if (restart->pid) { if (gs.loglevel > LOG_DEBUG + 1) zlog_debug( "cannot %s %s, previous pid %d still running", cmdtype, restart->name, (int)restart->pid); return -1; } /* Note: time_elapsed test must come before the force test, since we need to make sure that delay is initialized for use below in updating the restart interval. */ if ((time_elapsed(&delay, &restart->time)->tv_sec < restart->interval) && !force) { if (gs.loglevel > LOG_DEBUG + 1) zlog_debug( "postponing %s %s: " "elapsed time %ld < retry interval %ld", cmdtype, restart->name, (long)delay.tv_sec, restart->interval); return -1; } gettimeofday(&restart->time, NULL); restart->kills = 0; { char cmd[strlen(command) + strlen(restart->name) + 1]; snprintf(cmd, sizeof(cmd), command, restart->name); if ((restart->pid = run_background(cmd)) > 0) { restart->t_kill = NULL; thread_add_timer(master, restart_kill, restart, gs.restart_timeout, &restart->t_kill); restart->what = cmdtype; gs.numpids++; } else restart->pid = 0; } /* Calculate the new restart interval. */ if (update_interval) { if (delay.tv_sec > 2 * gs.max_restart_interval) restart->interval = gs.min_restart_interval; else if ((restart->interval *= 2) > gs.max_restart_interval) restart->interval = gs.max_restart_interval; if (gs.loglevel > LOG_DEBUG + 1) zlog_debug("restart %s interval is now %ld", restart->name, restart->interval); } return restart->pid; } #define SET_READ_HANDLER(DMN) \ do { \ (DMN)->t_read = NULL; \ thread_add_read(master, handle_read, (DMN), (DMN)->fd, \ &(DMN)->t_read); \ } while (0); #define SET_WAKEUP_DOWN(DMN) \ do { \ (DMN)->t_wakeup = NULL; \ thread_add_timer_msec(master, wakeup_down, (DMN), \ FUZZY(gs.period), &(DMN)->t_wakeup); \ } while (0); #define SET_WAKEUP_UNRESPONSIVE(DMN) \ do { \ (DMN)->t_wakeup = NULL; \ thread_add_timer_msec(master, wakeup_unresponsive, (DMN), \ FUZZY(gs.period), &(DMN)->t_wakeup); \ } while (0); #define SET_WAKEUP_ECHO(DMN) \ do { \ (DMN)->t_wakeup = NULL; \ thread_add_timer_msec(master, wakeup_send_echo, (DMN), \ FUZZY(gs.period), &(DMN)->t_wakeup); \ } while (0); static int wakeup_down(struct thread *t_wakeup) { struct daemon *dmn = THREAD_ARG(t_wakeup); dmn->t_wakeup = NULL; if (try_connect(dmn) < 0) SET_WAKEUP_DOWN(dmn); if ((dmn->connect_tries > 1) && (dmn->state != DAEMON_UP)) try_restart(dmn); return 0; } static int wakeup_init(struct thread *t_wakeup) { struct daemon *dmn = THREAD_ARG(t_wakeup); dmn->t_wakeup = NULL; if (try_connect(dmn) < 0) { flog_err(EC_WATCHFRR_CONNECTION, "%s state -> down : initial connection attempt failed", dmn->name); dmn->state = DAEMON_DOWN; } phase_check(); return 0; } static void restart_done(struct daemon *dmn) { if (dmn->state != DAEMON_DOWN) { zlog_warn("wtf?"); return; } if (dmn->t_wakeup) THREAD_OFF(dmn->t_wakeup); if (try_connect(dmn) < 0) SET_WAKEUP_DOWN(dmn); } static void daemon_down(struct daemon *dmn, const char *why) { if (IS_UP(dmn) || (dmn->state == DAEMON_INIT)) flog_err(EC_WATCHFRR_CONNECTION, "%s state -> down : %s", dmn->name, why); else if (gs.loglevel > LOG_DEBUG) zlog_debug("%s still down : %s", dmn->name, why); if (IS_UP(dmn)) gs.numdown++; dmn->state = DAEMON_DOWN; if (dmn->fd >= 0) { close(dmn->fd); dmn->fd = -1; } THREAD_OFF(dmn->t_read); THREAD_OFF(dmn->t_write); THREAD_OFF(dmn->t_wakeup); if (try_connect(dmn) < 0) SET_WAKEUP_DOWN(dmn); phase_check(); } static int handle_read(struct thread *t_read) { struct daemon *dmn = THREAD_ARG(t_read); static const char resp[sizeof(PING_TOKEN) + 4] = PING_TOKEN "\n"; char buf[sizeof(resp) + 100]; ssize_t rc; struct timeval delay; dmn->t_read = NULL; if ((rc = read(dmn->fd, buf, sizeof(buf))) < 0) { char why[100]; if (ERRNO_IO_RETRY(errno)) { /* Pretend it never happened. */ SET_READ_HANDLER(dmn); return 0; } snprintf(why, sizeof(why), "unexpected read error: %s", safe_strerror(errno)); daemon_down(dmn, why); return 0; } if (rc == 0) { daemon_down(dmn, "read returned EOF"); return 0; } if (!dmn->echo_sent.tv_sec) { char why[sizeof(buf) + 100]; snprintf(why, sizeof(why), "unexpected read returns %d bytes: %.*s", (int)rc, (int)rc, buf); daemon_down(dmn, why); return 0; } /* We are expecting an echo response: is there any chance that the response would not be returned entirely in the first read? That seems inconceivable... */ if ((rc != sizeof(resp)) || memcmp(buf, resp, sizeof(resp))) { char why[100 + sizeof(buf)]; snprintf(why, sizeof(why), "read returned bad echo response of %d bytes " "(expecting %u): %.*s", (int)rc, (unsigned int)sizeof(resp), (int)rc, buf); daemon_down(dmn, why); return 0; } time_elapsed(&delay, &dmn->echo_sent); dmn->echo_sent.tv_sec = 0; if (dmn->state == DAEMON_UNRESPONSIVE) { if (delay.tv_sec < gs.timeout) { dmn->state = DAEMON_UP; zlog_warn( "%s state -> up : echo response received after %ld.%06ld " "seconds", dmn->name, (long)delay.tv_sec, (long)delay.tv_usec); } else zlog_warn( "%s: slow echo response finally received after %ld.%06ld " "seconds", dmn->name, (long)delay.tv_sec, (long)delay.tv_usec); } else if (gs.loglevel > LOG_DEBUG + 1) zlog_debug("%s: echo response received after %ld.%06ld seconds", dmn->name, (long)delay.tv_sec, (long)delay.tv_usec); SET_READ_HANDLER(dmn); if (dmn->t_wakeup) thread_cancel(dmn->t_wakeup); SET_WAKEUP_ECHO(dmn); return 0; } /* * Wait till we notice that all daemons are ready before * we send we are ready to systemd */ static void daemon_send_ready(int exitcode) { FILE *fp; static int sent = 0; char started[1024]; if (sent) return; if (exitcode == 0) zlog_notice("all daemons up, doing startup-complete notify"); else if (gs.numdown < gs.numdaemons) flog_err(EC_WATCHFRR_CONNECTION, "startup did not complete within timeout" " (%d/%d daemons running)", gs.numdaemons - gs.numdown, gs.numdaemons); else { flog_err(EC_WATCHFRR_CONNECTION, "all configured daemons failed to start" " -- exiting watchfrr"); exit(exitcode); } frr_detach(); snprintf(started, sizeof(started), "%s%s", frr_vtydir, "watchfrr.started"); fp = fopen(started, "w"); if (fp) fclose(fp); #if defined HAVE_SYSTEMD systemd_send_started(master, 0); #endif sent = 1; } static void daemon_up(struct daemon *dmn, const char *why) { dmn->state = DAEMON_UP; gs.numdown--; dmn->connect_tries = 0; zlog_notice("%s state -> up : %s", dmn->name, why); if (gs.numdown == 0) daemon_send_ready(0); SET_WAKEUP_ECHO(dmn); phase_check(); } static int check_connect(struct thread *t_write) { struct daemon *dmn = THREAD_ARG(t_write); int sockerr; socklen_t reslen = sizeof(sockerr); dmn->t_write = NULL; if (getsockopt(dmn->fd, SOL_SOCKET, SO_ERROR, (char *)&sockerr, &reslen) < 0) { zlog_warn("%s: check_connect: getsockopt failed: %s", dmn->name, safe_strerror(errno)); daemon_down(dmn, "getsockopt failed checking connection success"); return 0; } if ((reslen == sizeof(sockerr)) && sockerr) { char why[100]; snprintf( why, sizeof(why), "getsockopt reports that connection attempt failed: %s", safe_strerror(sockerr)); daemon_down(dmn, why); return 0; } daemon_up(dmn, "delayed connect succeeded"); return 0; } static int wakeup_connect_hanging(struct thread *t_wakeup) { struct daemon *dmn = THREAD_ARG(t_wakeup); char why[100]; dmn->t_wakeup = NULL; snprintf(why, sizeof(why), "connection attempt timed out after %ld seconds", gs.timeout); daemon_down(dmn, why); return 0; } /* Making connection to protocol daemon. */ static int try_connect(struct daemon *dmn) { int sock; struct sockaddr_un addr; socklen_t len; if (gs.loglevel > LOG_DEBUG + 1) zlog_debug("%s: attempting to connect", dmn->name); dmn->connect_tries++; memset(&addr, 0, sizeof(struct sockaddr_un)); addr.sun_family = AF_UNIX; snprintf(addr.sun_path, sizeof(addr.sun_path), "%s/%s.vty", gs.vtydir, dmn->name); #ifdef HAVE_STRUCT_SOCKADDR_UN_SUN_LEN len = addr.sun_len = SUN_LEN(&addr); #else len = sizeof(addr.sun_family) + strlen(addr.sun_path); #endif /* HAVE_STRUCT_SOCKADDR_UN_SUN_LEN */ /* Quick check to see if we might succeed before we go to the trouble of creating a socket. */ if (access(addr.sun_path, W_OK) < 0) { if (errno != ENOENT) flog_err_sys(EC_LIB_SYSTEM_CALL, "%s: access to socket %s denied: %s", dmn->name, addr.sun_path, safe_strerror(errno)); return -1; } if ((sock = socket(AF_UNIX, SOCK_STREAM, 0)) < 0) { flog_err_sys(EC_LIB_SOCKET, "%s(%s): cannot make socket: %s", __func__, addr.sun_path, safe_strerror(errno)); return -1; } if (set_nonblocking(sock) < 0 || set_cloexec(sock) < 0) { flog_err_sys(EC_LIB_SYSTEM_CALL, "%s(%s): set_nonblocking/cloexec(%d) failed", __func__, addr.sun_path, sock); close(sock); return -1; } if (connect(sock, (struct sockaddr *)&addr, len) < 0) { if ((errno != EINPROGRESS) && (errno != EWOULDBLOCK)) { if (gs.loglevel > LOG_DEBUG) zlog_debug("%s(%s): connect failed: %s", __func__, addr.sun_path, safe_strerror(errno)); close(sock); return -1; } if (gs.loglevel > LOG_DEBUG) zlog_debug("%s: connection in progress", dmn->name); dmn->state = DAEMON_CONNECTING; dmn->fd = sock; dmn->t_write = NULL; thread_add_write(master, check_connect, dmn, dmn->fd, &dmn->t_write); dmn->t_wakeup = NULL; thread_add_timer(master, wakeup_connect_hanging, dmn, gs.timeout, &dmn->t_wakeup); SET_READ_HANDLER(dmn); return 0; } dmn->fd = sock; SET_READ_HANDLER(dmn); daemon_up(dmn, "connect succeeded"); return 1; } static int phase_hanging(struct thread *t_hanging) { gs.t_phase_hanging = NULL; flog_err(EC_WATCHFRR_CONNECTION, "Phase [%s] hanging for %ld seconds, aborting phased restart", phase_str[gs.phase], PHASE_TIMEOUT); gs.phase = PHASE_NONE; return 0; } static void set_phase(restart_phase_t new_phase) { gs.phase = new_phase; if (gs.t_phase_hanging) thread_cancel(gs.t_phase_hanging); gs.t_phase_hanging = NULL; thread_add_timer(master, phase_hanging, NULL, PHASE_TIMEOUT, &gs.t_phase_hanging); } static void phase_check(void) { struct daemon *dmn; switch (gs.phase) { case PHASE_NONE: break; case PHASE_INIT: for (dmn = gs.daemons; dmn; dmn = dmn->next) if (dmn->state == DAEMON_INIT) return; /* startup complete, everything out of INIT */ gs.phase = PHASE_NONE; for (dmn = gs.daemons; dmn; dmn = dmn->next) if (dmn->state == DAEMON_DOWN) { SET_WAKEUP_DOWN(dmn); try_restart(dmn); } break; case PHASE_STOPS_PENDING: if (gs.numpids) break; zlog_info( "Phased restart: all routing daemon stop jobs have completed."); set_phase(PHASE_WAITING_DOWN); /*FALLTHRU*/ case PHASE_WAITING_DOWN: if (gs.numdown + IS_UP(gs.special) < gs.numdaemons) break; zlog_info("Phased restart: all routing daemons now down."); run_job(&gs.special->restart, "restart", gs.restart_command, 1, 1); set_phase(PHASE_ZEBRA_RESTART_PENDING); /*FALLTHRU*/ case PHASE_ZEBRA_RESTART_PENDING: if (gs.special->restart.pid) break; zlog_info("Phased restart: %s restart job completed.", gs.special->name); set_phase(PHASE_WAITING_ZEBRA_UP); /*FALLTHRU*/ case PHASE_WAITING_ZEBRA_UP: if (!IS_UP(gs.special)) break; zlog_info("Phased restart: %s is now up.", gs.special->name); { struct daemon *dmn; for (dmn = gs.daemons; dmn; dmn = dmn->next) { if (dmn != gs.special) run_job(&dmn->restart, "start", gs.start_command, 1, 0); } } gs.phase = PHASE_NONE; THREAD_OFF(gs.t_phase_hanging); zlog_notice("Phased global restart has completed."); break; } } static void try_restart(struct daemon *dmn) { if (watch_only) return; if (dmn != gs.special) { if ((gs.special->state == DAEMON_UP) && (gs.phase == PHASE_NONE)) run_job(&dmn->restart, "restart", gs.restart_command, 0, 1); else zlog_debug( "%s: postponing restart attempt because master %s daemon " "not up [%s], or phased restart in progress", dmn->name, gs.special->name, state_str[gs.special->state]); return; } if ((gs.phase != PHASE_NONE) || gs.numpids) { if (gs.loglevel > LOG_DEBUG + 1) zlog_debug( "postponing phased global restart: restart already in " "progress [%s], or outstanding child processes [%d]", phase_str[gs.phase], gs.numpids); return; } /* Is it too soon for a restart? */ { struct timeval delay; if (time_elapsed(&delay, &gs.special->restart.time)->tv_sec < gs.special->restart.interval) { if (gs.loglevel > LOG_DEBUG + 1) zlog_debug( "postponing phased global restart: " "elapsed time %ld < retry interval %ld", (long)delay.tv_sec, gs.special->restart.interval); return; } } run_job(&gs.restart, "restart", gs.restart_command, 0, 1); } static int wakeup_unresponsive(struct thread *t_wakeup) { struct daemon *dmn = THREAD_ARG(t_wakeup); dmn->t_wakeup = NULL; if (dmn->state != DAEMON_UNRESPONSIVE) flog_err(EC_WATCHFRR_CONNECTION, "%s: no longer unresponsive (now %s), " "wakeup should have been cancelled!", dmn->name, state_str[dmn->state]); else { SET_WAKEUP_UNRESPONSIVE(dmn); try_restart(dmn); } return 0; } static int wakeup_no_answer(struct thread *t_wakeup) { struct daemon *dmn = THREAD_ARG(t_wakeup); dmn->t_wakeup = NULL; dmn->state = DAEMON_UNRESPONSIVE; flog_err(EC_WATCHFRR_CONNECTION, "%s state -> unresponsive : no response yet to ping " "sent %ld seconds ago", dmn->name, gs.timeout); SET_WAKEUP_UNRESPONSIVE(dmn); try_restart(dmn); return 0; } static int wakeup_send_echo(struct thread *t_wakeup) { static const char echocmd[] = "echo " PING_TOKEN; ssize_t rc; struct daemon *dmn = THREAD_ARG(t_wakeup); dmn->t_wakeup = NULL; if (((rc = write(dmn->fd, echocmd, sizeof(echocmd))) < 0) || ((size_t)rc != sizeof(echocmd))) { char why[100 + sizeof(echocmd)]; snprintf(why, sizeof(why), "write '%s' returned %d instead of %u", echocmd, (int)rc, (unsigned int)sizeof(echocmd)); daemon_down(dmn, why); } else { gettimeofday(&dmn->echo_sent, NULL); dmn->t_wakeup = NULL; thread_add_timer(master, wakeup_no_answer, dmn, gs.timeout, &dmn->t_wakeup); } return 0; } bool check_all_up(void) { struct daemon *dmn; for (dmn = gs.daemons; dmn; dmn = dmn->next) if (dmn->state != DAEMON_UP) return false; return true; } void watchfrr_status(struct vty *vty) { struct daemon *dmn; struct timeval delay; vty_out(vty, "watchfrr global phase: %s\n", phase_str[gs.phase]); if (gs.restart.pid) vty_out(vty, " global restart running, pid %ld\n", (long)gs.restart.pid); for (dmn = gs.daemons; dmn; dmn = dmn->next) { vty_out(vty, " %-20s %s\n", dmn->name, state_str[dmn->state]); if (dmn->restart.pid) vty_out(vty, " restart running, pid %ld\n", (long)dmn->restart.pid); else if (dmn->state == DAEMON_DOWN && time_elapsed(&delay, &dmn->restart.time)->tv_sec < dmn->restart.interval) vty_out(vty, " restarting in %jd seconds" " (%jds backoff interval)\n", (intmax_t)dmn->restart.interval - (intmax_t)delay.tv_sec, (intmax_t)dmn->restart.interval); } } static void sigint(void) { zlog_notice("Terminating on signal"); systemd_send_stopping(); exit(0); } static int valid_command(const char *cmd) { char *p; return ((p = strchr(cmd, '%')) != NULL) && (*(p + 1) == 's') && !strchr(p + 1, '%'); } /* This is an ugly hack to circumvent problems with passing command-line arguments that contain spaces. The fix is to use a configuration file. */ static char *translate_blanks(const char *cmd, const char *blankstr) { char *res; char *p; size_t bslen = strlen(blankstr); if (!(res = strdup(cmd))) { perror("strdup"); exit(1); } while ((p = strstr(res, blankstr)) != NULL) { *p = ' '; if (bslen != 1) memmove(p + 1, p + bslen, strlen(p + bslen) + 1); } return res; } static int startup_timeout(struct thread *t_wakeup) { daemon_send_ready(1); return 0; } static void watchfrr_init(int argc, char **argv) { const char *special = "zebra"; int i; struct daemon *dmn, **add = &gs.daemons; char alldaemons[512] = "", *p = alldaemons; thread_add_timer_msec(master, startup_timeout, NULL, STARTUP_TIMEOUT, &gs.t_startup_timeout); for (i = optind; i < argc; i++) { dmn = XCALLOC(MTYPE_WATCHFRR_DAEMON, sizeof(*dmn)); dmn->name = dmn->restart.name = argv[i]; dmn->state = DAEMON_INIT; gs.numdaemons++; gs.numdown++; dmn->fd = -1; dmn->t_wakeup = NULL; thread_add_timer_msec(master, wakeup_init, dmn, 0, &dmn->t_wakeup); dmn->restart.interval = gs.min_restart_interval; *add = dmn; add = &dmn->next; if (!strcmp(dmn->name, special)) gs.special = dmn; } if (!gs.daemons) { fprintf(stderr, "Must specify one or more daemons to monitor.\n\n"); frr_help_exit(1); } if (!watch_only && !gs.special) { fprintf(stderr, "\"%s\" daemon must be in daemon lists\n\n", special); frr_help_exit(1); } for (dmn = gs.daemons; dmn; dmn = dmn->next) { snprintf(p, alldaemons + sizeof(alldaemons) - p, "%s%s", (p == alldaemons) ? "" : " ", dmn->name); p += strlen(p); } zlog_notice("%s %s watching [%s]%s", progname, FRR_VERSION, alldaemons, watch_only ? ", monitor mode" : ""); } struct zebra_privs_t watchfrr_privs = { #ifdef VTY_GROUP .vty_group = VTY_GROUP, #endif }; static struct quagga_signal_t watchfrr_signals[] = { { .signal = SIGINT, .handler = sigint, }, { .signal = SIGTERM, .handler = sigint, }, { .signal = SIGCHLD, .handler = sigchild, }, }; FRR_DAEMON_INFO(watchfrr, WATCHFRR, .flags = FRR_NO_PRIVSEP | FRR_NO_TCPVTY | FRR_LIMITED_CLI | FRR_NO_CFG_PID_DRY | FRR_NO_ZCLIENT | FRR_DETACH_LATER, .printhelp = printhelp, .copyright = "Copyright 2004 Andrew J. Schorr", .signals = watchfrr_signals, .n_signals = array_size(watchfrr_signals), .privs = &watchfrr_privs, ) #define DEPRECATED_OPTIONS "aAezR:" int main(int argc, char **argv) { int opt; const char *blankstr = NULL; frr_preinit(&watchfrr_di, argc, argv); progname = watchfrr_di.progname; frr_opt_add("b:dk:l:i:p:r:S:s:t:T:" DEPRECATED_OPTIONS, longopts, ""); gs.restart.name = "all"; while ((opt = frr_getopt(argc, argv, NULL)) != EOF) { if (opt && opt < 128 && strchr(DEPRECATED_OPTIONS, opt)) { fprintf(stderr, "The -%c option no longer exists.\n" "Please refer to the watchfrr(8) man page.\n", opt); exit(1); } switch (opt) { case 0: break; case 'b': blankstr = optarg; break; case OPTION_DRY: watch_only = true; break; case 'k': if (!valid_command(optarg)) { fprintf(stderr, "Invalid kill command, must contain '%%s': %s\n", optarg); frr_help_exit(1); } gs.stop_command = optarg; break; case 'l': { char garbage[3]; if ((sscanf(optarg, "%d%1s", &gs.loglevel, garbage) != 1) || (gs.loglevel < LOG_EMERG)) { fprintf(stderr, "Invalid loglevel argument: %s\n", optarg); frr_help_exit(1); } } break; case OPTION_MINRESTART: { char garbage[3]; if ((sscanf(optarg, "%ld%1s", &gs.min_restart_interval, garbage) != 1) || (gs.min_restart_interval < 0)) { fprintf(stderr, "Invalid min_restart_interval argument: %s\n", optarg); frr_help_exit(1); } } break; case OPTION_MAXRESTART: { char garbage[3]; if ((sscanf(optarg, "%ld%1s", &gs.max_restart_interval, garbage) != 1) || (gs.max_restart_interval < 0)) { fprintf(stderr, "Invalid max_restart_interval argument: %s\n", optarg); frr_help_exit(1); } } break; case 'i': { char garbage[3]; int period; if ((sscanf(optarg, "%d%1s", &period, garbage) != 1) || (gs.period < 1)) { fprintf(stderr, "Invalid interval argument: %s\n", optarg); frr_help_exit(1); } gs.period = 1000 * period; } break; case 'p': watchfrr_di.pid_file = optarg; break; case 'r': if (!valid_command(optarg)) { fprintf(stderr, "Invalid restart command, must contain '%%s': %s\n", optarg); frr_help_exit(1); } gs.restart_command = optarg; break; case 's': if (!valid_command(optarg)) { fprintf(stderr, "Invalid start command, must contain '%%s': %s\n", optarg); frr_help_exit(1); } gs.start_command = optarg; break; case 'S': gs.vtydir = optarg; break; case 't': { char garbage[3]; if ((sscanf(optarg, "%ld%1s", &gs.timeout, garbage) != 1) || (gs.timeout < 1)) { fprintf(stderr, "Invalid timeout argument: %s\n", optarg); frr_help_exit(1); } } break; case 'T': { char garbage[3]; if ((sscanf(optarg, "%ld%1s", &gs.restart_timeout, garbage) != 1) || (gs.restart_timeout < 1)) { fprintf(stderr, "Invalid restart timeout argument: %s\n", optarg); frr_help_exit(1); } } break; default: fputs("Invalid option.\n", stderr); frr_help_exit(1); } } if (watch_only && (gs.start_command || gs.stop_command || gs.restart_command)) { fputs("Options -r/-s/-k are not used when --dry is active.\n", stderr); } if (!watch_only && (!gs.restart_command || !gs.start_command || !gs.stop_command)) { fprintf(stderr, "Options -s (start), -k (kill), and -r (restart) are required.\n"); frr_help_exit(1); } if (blankstr) { if (gs.restart_command) gs.restart_command = translate_blanks(gs.restart_command, blankstr); if (gs.start_command) gs.start_command = translate_blanks(gs.start_command, blankstr); if (gs.stop_command) gs.stop_command = translate_blanks(gs.stop_command, blankstr); } gs.restart.interval = gs.min_restart_interval; master = frr_init(); watchfrr_error_init(); watchfrr_init(argc, argv); watchfrr_vty_init(); frr_config_fork(); zlog_set_level(ZLOG_DEST_MONITOR, ZLOG_DISABLED); if (watchfrr_di.daemon_mode) zlog_set_level(ZLOG_DEST_SYSLOG, MIN(gs.loglevel, LOG_DEBUG)); else zlog_set_level(ZLOG_DEST_STDOUT, MIN(gs.loglevel, LOG_DEBUG)); frr_run(master); systemd_send_stopping(); /* Not reached. */ return 0; } frr-7.2.1/watchfrr/watchfrr.h0000644000000000000000000000247413610377563013034 00000000000000/* * Common definitions for watchfrr API socket. * * Copyright (C) 2016 David Lamparter for NetDEF, Inc. * * This program is free software; you can redistribute it and/or modify * it under the terms of the 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; see the file COPYING; if not, write to the Free Software * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA */ #ifndef FRR_WATCHFRR_H #define FRR_WATCHFRR_H #include "lib/memory.h" DECLARE_MGROUP(WATCHFRR) extern void watchfrr_vty_init(void); extern pid_t integrated_write_pid; extern void integrated_write_sigchld(int status); struct vty; extern void watchfrr_status(struct vty *vty); /* * Check if all daemons we are monitoring are in the DAEMON_UP state. * * Returns: * True if they are all DAEMON_UP, false otherwise. */ extern bool check_all_up(void); #endif /* FRR_WATCHFRR_H */ frr-7.2.1/watchfrr/watchfrr_errors.c0000644000000000000000000000321113610377563014411 00000000000000/* * Watchfrr-specific error messages. * Copyright (C) 2018 Cumulus Networks, Inc. * Donald Sharp * * This program is free software; you can redistribute it and/or modify it * under the terms of the 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; see the file COPYING; if not, write to the Free Software * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA */ #include #include "lib/ferr.h" #include "watchfrr_errors.h" /* clang-format off */ static struct log_ref ferr_watchfrr_err[] = { { .code = EC_WATCHFRR_CONNECTION, .title = "WATCHFRR Connection Error", .description = "WATCHFRR has detected a connectivity issue with one of the FRR daemons", .suggestion = "Ensure that FRR is still running and if not please open an Issue" }, { .code = EC_WATCHFRR_UNEXPECTED_DAEMONS, .title = "WATCHFRR wrong daemons to watch", .description = "As part of WATCHFRR startup you must specify 1 or more daemons to monitor", .suggestion = "Update your startup scripts to include zebra and any other daemon you would like to monitor", }, { .code = END_FERR, } }; /* clang-format on */ void watchfrr_error_init(void) { log_ref_add(ferr_watchfrr_err); } frr-7.2.1/watchfrr/watchfrr_errors.h0000644000000000000000000000211113610377563014414 00000000000000/* * Watchfrr-specific error messages. * Copyright (C) 2018 Cumulus Networks, Inc. * Donald Sharp * * This program is free software; you can redistribute it and/or modify it * under the terms of the 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; see the file COPYING; if not, write to the Free Software * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA */ #ifndef __WATCHFRR_ERRORS_H__ #define __WATCHFRR_ERRORS_H__ #include "lib/ferr.h" enum watchfrr_log_refs { EC_WATCHFRR_CONNECTION = WATCHFRR_FERR_START, EC_WATCHFRR_UNEXPECTED_DAEMONS, }; extern void watchfrr_error_init(void); #endif frr-7.2.1/watchfrr/watchfrr_vty.c0000644000000000000000000001163013610377563013723 00000000000000/* * watchfrr CLI functions. * * Copyright (C) 2016 David Lamparter for NetDEF, Inc. * * This program is free software; you can redistribute it and/or modify * it under the terms of the 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; see the file COPYING; if not, write to the Free Software * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA */ #include #include #include "memory.h" #include "log.h" #include "vty.h" #include "command.h" #include "watchfrr.h" pid_t integrated_write_pid; static int integrated_result_fd; DEFUN(config_write_integrated, config_write_integrated_cmd, "write integrated", "Write running configuration to memory, network, or terminal\n" "Write integrated all-daemon frr.conf file\n") { pid_t child; sigset_t oldmask, sigmask; const char *e_inprog = "Configuration write already in progress."; const char *e_dmn = "Not all daemons are up, cannot write config."; if (integrated_write_pid != -1) { vty_out(vty, "%% %s\n", e_inprog); return CMD_WARNING; } /* check that all daemons are up before clobbering config */ if (!check_all_up()) { vty_out(vty, "%% %s\n", e_dmn); /* * vtysh interprets this return value to mean that it should * not try to write the config itself */ return CMD_WARNING_CONFIG_FAILED; } fflush(stdout); fflush(stderr); /* need to temporarily block SIGCHLD because it could arrive between * fork() call and setting the integrated_write_pid variable. This * would mean the completion call gets lost and this hangs forever. */ sigemptyset(&oldmask); sigemptyset(&sigmask); sigaddset(&sigmask, SIGCHLD); sigprocmask(SIG_BLOCK, &sigmask, &oldmask); child = fork(); if (child == -1) { vty_out(vty, "%% configuration write fork() failed: %s.\n", safe_strerror(errno)); sigprocmask(SIG_SETMASK, &oldmask, NULL); return CMD_WARNING; } if (child != 0) { /* note: the VTY won't write a command return value to vtysh; * the * session temporarily enters an intentional "hang" state. This * is * to make sure latency in vtysh doing the config write (several * seconds is not rare to see) does not interfere with * watchfrr's * supervisor job. * * The fd is duplicated here so we don't need to hold a vty * pointer * (which could become invalid in the meantime). */ integrated_write_pid = child; integrated_result_fd = dup(vty->wfd); sigprocmask(SIG_SETMASK, &oldmask, NULL); return CMD_SUSPEND; } /* redirect stdout/stderr to vty session. Note vty->wfd is marked * CLOEXEC, but dup2 will clear that flag. */ dup2(vty->wfd, 1); dup2(vty->wfd, 2); /* don't allow the user to pass parameters, we're root here! * should probably harden vtysh at some point too... */ execl(VTYSH_BIN_PATH, "vtysh", "-w", NULL); /* unbuffered write; we just messed with stdout... */ char msg[512]; snprintf(msg, sizeof(msg), "error executing %s: %s\n", VTYSH_BIN_PATH, safe_strerror(errno)); write(1, msg, strlen(msg)); exit(1); } DEFUN_NOSH (show_debugging_watchfrr, show_debugging_watchfrr_cmd, "show debugging [watchfrr]", SHOW_STR DEBUG_STR WATCHFRR_STR) { return CMD_SUCCESS; } DEFUN (show_watchfrr, show_watchfrr_cmd, "show watchfrr", SHOW_STR WATCHFRR_STR) { watchfrr_status(vty); return CMD_SUCCESS; } void integrated_write_sigchld(int status) { uint8_t reply[4] = {0, 0, 0, CMD_WARNING}; if (WIFEXITED(status)) { zlog_info("configuration write completed with exit code %d", WEXITSTATUS(status)); reply[3] = WEXITSTATUS(status); } else if (WIFSIGNALED(status)) { zlog_warn("configuration write terminated by signal %d", WTERMSIG(status)); } else { zlog_warn("configuration write terminated"); } if (reply[3] != CMD_SUCCESS) { /* failure might be silent in vtysh without this */ static const char msg[] = "% Configuration write failed.\n"; write(integrated_result_fd, msg, strlen(msg)); } /* don't care about failures here, if the connection is broken the * return value will just be lost. */ write(integrated_result_fd, reply, sizeof(reply)); close(integrated_result_fd); integrated_write_pid = -1; } void watchfrr_vty_init(void) { integrated_write_pid = -1; install_element(ENABLE_NODE, &config_write_integrated_cmd); install_element(ENABLE_NODE, &show_debugging_watchfrr_cmd); install_element(CONFIG_NODE, &show_debugging_watchfrr_cmd); install_element(VIEW_NODE, &show_watchfrr_cmd); } frr-7.2.1/yang/0000755000000000000000000000000013610377563010232 500000000000000frr-7.2.1/yang/embedmodel.py0000644000000000000000000000372413610377563012627 00000000000000#!/usr/bin/python3 # # YANG module to C wrapper # written 2018 by David Lamparter, placed in Public Domain. import sys, string, re inname = sys.argv[1] outname = sys.argv[2] # these are regexes to avoid a compile-time/host dependency on yang-tools # or python-yang. Cross-compiling FRR is already somewhat involved, no need # to make it even harder. re_name = re.compile(r'\bmodule\s+([^\s]+)\s+\{') re_rev = re.compile(r'\brevision\s+([\d-]+)\s+\{') template = '''/* autogenerated by embedmodel.py. DO NOT EDIT */ #include #include "yang.h" static const char model[] = \t"%s"; static struct yang_module_embed embed = { \t.mod_name = "%s", \t.mod_rev = "%s", \t.data = model, \t.format = %s, }; static void embed_register(void) __attribute__((_CONSTRUCTOR(2000))); static void embed_register(void) { \tyang_module_embed(&embed); } ''' passchars = set(string.printable) - set('\\\'"%\r\n\t\x0b\x0c') def escapech(char): if char in passchars: return char if char == '\n': return '\\n' if char == '\t': return '\\t' if char in '"\\\'': return '\\' + char return '\\x%02x' % (ord(char)) def escape(line): return ''.join([escapech(i) for i in line]) with open(inname, 'r') as fd: data = fd.read() # XML support isn't actively used currently, but it's here in case the need # arises. It does avoid the regex'ing. if ' FRR Development List: "; description "This module defines a model for managing FRR bfdd daemon."; revision 2019-05-09 { description "Initial revision."; reference "RFC 5880: Bidirectional Forwarding Detection (BFD). RFC 5881: Bidirectional Forwarding Detection (BFD) for IPv4 and IPv6 (Single Hop). RFC 5882: Bidirectional Forwarding Detection (BFD) for Multihop Paths."; } /* * BFD types declaration. */ typedef multiplier { description "Detection multiplier"; type uint8 { range 2..255; } } typedef discriminator { description "BFD session identification"; type uint32 { range 1..4294967295; } } typedef state { description "BFD session state"; type enumeration { enum admin-down { value 0; description "Administratively down"; } enum down { value 1; description "Down"; } enum init { value 2; description "Initializing"; } enum up { value 3; description "Up"; } } } typedef diagnostic { description "BFD session diagnostic"; type enumeration { enum ok { value 0; description "Ok"; } enum control-expired { value 1; description "Control timer expired"; } enum echo-failed { value 2; description "Echo function failed"; } enum neighbor-down { value 3; description "Neighbor signaled session down"; } enum forwarding-reset { value 4; description "Forwarding plane reset"; } enum path-down { value 5; description "Path down"; } enum concatenated-path-down { value 6; description "Concatenated path down"; } enum administratively-down { value 7; description "Administratively down"; } enum reverse-concat-path-down { value 8; description "Reverse concatenated path down"; } } } /* * Shared BFD items. */ grouping session-common { description "Common BFD session settings"; leaf detection-multiplier { type multiplier; default 3; description "Local session detection multiplier"; } leaf desired-transmission-interval { type uint32; units microseconds; default 300000; description "Minimum desired control packet transmission interval"; } leaf required-receive-interval { type uint32; units microseconds; default 300000; description "Minimum required control packet receive interval"; } leaf administrative-down { type boolean; default true; description "Disables or enables the session administratively"; } } grouping session-echo { description "BFD session echo settings"; leaf echo-mode { type boolean; default false; description "Use echo packets to detect failures"; } leaf desired-echo-transmission-interval { type uint32; units microseconds; default 50000; description "Minimum desired control packet transmission interval"; } } grouping session-states { /* * Local settings. */ leaf local-discriminator { type discriminator; description "Local session identifier"; } leaf local-state { type state; description "Local session state"; } leaf local-diagnostic { type diagnostic; description "Local session diagnostic"; } leaf local-multiplier { type multiplier; description "Local session current multiplier"; } /* * Remote settings. */ leaf remote-discriminator { type discriminator; description "Remote session identifier"; } leaf remote-state { type state; description "Remote session state"; } leaf remote-diagnostic { type diagnostic; description "Local session diagnostic"; } leaf remote-multiplier { type multiplier; description "Remote session detection multiplier"; } /* * Negotiated settings. */ leaf negotiated-transmission-interval { description "Negotiated transmit interval"; type uint32; units microseconds; } leaf negotiated-receive-interval { description "Negotiated receive interval"; type uint32; units microseconds; } leaf detection-mode { description "Detection mode"; type enumeration { enum async-with-echo { value "1"; description "Async with echo"; } enum async-without-echo { value "2"; description "Async without echo"; } enum demand-with-echo { value "3"; description "Demand with echo"; } enum demand-without-echo { value "4"; description "Demand without echo"; } } } /* * Statistics. */ leaf last-down-time { type yang:date-and-time; description "Time and date of the last time session was down"; } leaf last-up-time { type yang:date-and-time; description "Time and date of the last time session was up"; } leaf session-down-count { type uint32; description "Number of times the session went down"; } leaf session-up-count { type uint32; description "Number of times the session went up"; } leaf control-packet-input-count { type uint64; description "Number of control packets received"; } leaf control-packet-output-count { type uint64; description "Number of control packets sent"; } /* * Echo mode operational data. */ leaf negotiated-echo-transmission-interval { type uint32; units microseconds; description "Negotiated echo transmit interval"; } /* * Statistics. */ leaf echo-packet-input-count { type uint64; description "Number of echo packets received"; } leaf echo-packet-output-count { type uint64; description "Number of echo packets sent"; } } /* * BFD operational. */ container bfdd { container bfd { presence "Present if the BFD protocol is enabled"; container sessions { list single-hop { key "dest-addr interface vrf"; description "List of single hop sessions"; leaf dest-addr { type inet:ip-address; description "IP address of the peer"; } leaf interface { type string { length "0..16"; } description "Interface to use to contact peer"; } leaf vrf { type string; description "Virtual Routing Domain name"; } leaf source-addr { type inet:ip-address; description "Local IP address"; } uses session-common; uses session-echo; container stats { uses session-states; config false; } } list multi-hop { key "source-addr dest-addr interface vrf"; description "List of multi hop sessions"; leaf source-addr { type inet:ip-address; description "Local IP address"; } leaf dest-addr { type inet:ip-address; description "IP address of the peer"; } leaf interface { type string { length "0..16"; } description "Interface to use to contact peer"; } leaf vrf { type string; description "Virtual Routing Domain name"; } uses session-common; container stats { uses session-states; config false; } } } } } } frr-7.2.1/yang/frr-eigrpd.yang0000644000000000000000000001675713610377563013113 00000000000000module frr-eigrpd { yang-version 1.1; namespace "http://frrouting.org/yang/eigrpd"; prefix frr-eigrpd; import ietf-inet-types { prefix inet; } import ietf-yang-types { prefix yang; } import frr-interface { prefix frr-interface; } import frr-route-types { prefix frr-route-types; } organization "Free Range Routing"; contact "FRR Users List: FRR Development List: "; description "This module defines a model for managing FRR eigrpd daemon."; revision 2019-06-19 { description "Initial revision."; reference "RFC 7868: Cisco's Enhanced Interior Gateway Routing Protocol (EIGRP)."; } /* * Types specification. */ typedef autonomous-system { description "Administrative domain identification for a network"; type uint16 { range 1..65535; } } typedef authentication-type { description "Authentication types"; type enumeration { enum none { description "Don't authenticate"; value 0; } enum text { description "User defined text"; value 1; } enum md5 { description "MD5 algorithm"; value 2; } enum hmac-sha-256 { description "HMAC SHA256 algorithm"; value 3; } } } /* * EIGRP operational data. */ container eigrpd { list instance { key "asn vrf"; description "EIGRP autonomous system instance"; leaf asn { description "Autonomous System Number"; type autonomous-system; } leaf vrf { description "Virtual Routing Domain name"; type string { length "0..16"; } } /* * Configurations. */ leaf router-id { description "Router identification"; type inet:ipv4-address; } leaf-list passive-interface { description "List of suppressed interfaces"; type string { length "1..16"; } } leaf active-time { description "ACTIVE time limit in seconds (0 disables limit)"; type uint16 { range "0..65535"; } units seconds; default 180; } leaf variance { description "Control load balance variance"; type uint8 { range "1..128"; } } leaf maximum-paths { description "Most number of paths to forward packets to"; type uint8 { range "1..32"; } } container metric-weights { description "Metrics and parameters for advertisement. EIGRP calculates the composite metric with the following formula: metric = 256 * ({(K1*BW) + [(K2*BW)/(256-LOAD)] + (K3*DELAY)} * (K5/(REL+K4))) Composite calculation: K5 metric =[(K1*Net-Throughput) + Latency)+(K6*ExtAttr)] * ------ K4+Rel RFC 7868 Sections 5.5 and 5.6.2.5."; leaf K1 { description "Bandwidth coefficient."; type uint8 { range "0..255"; } } leaf K2 { description "Bandwidth on load coefficient."; type uint8 { range "0..255"; } } leaf K3 { description "Delay or latency-based coefficient."; type uint8 { range "0..255"; } } leaf K4 { description "Link quality coefficient."; type uint8 { range "0..255"; } } leaf K5 { description "Packet loss coefficient."; type uint8 { range "0..255"; } } leaf K6 { description "Jitter coefficient."; type uint8 { range "0..255"; } } } leaf-list network { description "Enable EIGRP on the specific network"; type inet:ipv4-prefix; } leaf-list neighbor { description "Specific EIGRP neighbor"; type inet:ipv4-prefix; } list redistribute { description "Redistribute routes learned from other routing protocols"; key "protocol"; leaf protocol { description "Routing protocol"; type frr-route-types:frr-route-types-v4; must '. != "eigrp"'; } leaf route-map { description "Applies the conditions of the specified route-map to routes that are redistributed into the EIGRP routing instance"; type string { length "1..max"; } } container metrics { description "Metric for the redistributed routes"; leaf bandwidth { description "Bandwidth metric in Kbits per second"; type uint32 { range "1..4294967295"; } } leaf delay { description "Delay metric"; units microseconds; type uint32 { range "0..4294967295"; } } leaf reliability { description "Reliability metric"; type uint32 { range "0..255"; } } leaf load { description "Effective bandwidth usage"; type uint32 { range "1..255"; } } leaf mtu { description "Path Maximum Transmission Unit"; type uint32 { range "1..65535"; } } } } } } /* * EIGRP interface configurations. */ augment "/frr-interface:lib/frr-interface:interface" { container eigrp { description "EIGRP interface parameters"; leaf delay { description "Throughput delay"; type uint32 { range "1..16777215"; } default 10; } leaf bandwidth { description "Interface bandwidth value"; type uint32 { range "1..10000000"; } default 100000; } leaf hello-interval { description "Hello packet interval"; type uint16 { range "1..65535"; } units seconds; default 5; } leaf hold-time { description "Timeout amount to consider neighbor down"; type uint16 { range "1..65535"; } units seconds; default 15; } leaf split-horizon { description "Perform split horizon loop preventing technique"; type boolean; default true; } /* * Per AS configuration. */ list instance { description "Autonomous System specific configuration"; key "asn"; leaf asn { description "Autonomous System Number"; type autonomous-system; } leaf-list summarize-addresses { description "Peform address summarization"; type inet:ipv4-prefix; } leaf authentication { description "Authentication digest algorithm"; type authentication-type; default "none"; } leaf keychain { description "FRR key chain name to use with authentication"; type string; } } } } } frr-7.2.1/yang/frr-interface.yang0000644000000000000000000000167413610377563013571 00000000000000module frr-interface { yang-version 1.1; namespace "http://frrouting.org/yang/interface"; prefix frr-interface; organization "Free Range Routing"; contact "FRR Users List: FRR Development List: "; description "This module defines a model for managing FRR interfaces."; revision 2018-03-28 { description "Initial revision."; } container lib { list interface { key "name vrf"; description "Interface."; leaf name { type string { length "1..16"; } description "Interface name."; } leaf vrf { type string { length "1..36"; } description "VRF this interface is associated with."; } leaf description { type string; description "Interface description."; } } } } frr-7.2.1/yang/frr-isisd.yang0000644000000000000000000010401113610377563012731 00000000000000module frr-isisd { yang-version 1.1; namespace "http://frrouting.org/yang/isisd"; prefix frr-isisd; import ietf-yang-types { prefix yang; } import ietf-inet-types { prefix inet; } import frr-interface { prefix frr-interface; } import frr-route-types { prefix frr-route-types; } organization "Free Range Routing"; contact "FRR Users List: FRR Development List: "; description "This module defines a model for managing FRR isisd daemon."; revision 2018-07-26 { description "Initial revision."; reference "ISO/IEC 10589:2002."; } typedef level { type enumeration { enum "level-1" { value 1; description "This enum indicates L1-only capability."; } enum "level-2" { value 2; description "This enum indicates L2-only capability."; } enum "level-1-2" { value 3; description "This enum indicates capability for both levels."; } } description "This type defines IS-IS level of an object."; } typedef network-type { type enumeration { enum "unknown" { value 0; description "Unknown network type. Only valid as a state."; } enum "broadcast" { value 1; description "Broadcast circuit network-type."; } enum "point-to-point" { value 2; description "Point-to-point circuit network-type."; } enum "loopback" { value 3; description "Loopback circuit network-type. Only valid as a state."; } } } typedef lsp-id { type string { pattern "[0-9A-Fa-f]{4}\\.[0-9A-Fa-f]{4}\\.[0-9A-Fa-f]{4}\\.[0-9][0-9]-[0-9][0-9]"; } description "This type defines the IS-IS LSP ID format using a pattern, An example LSP ID is 0143.0438.AeF0.02-01"; } typedef system-id { type string { pattern "[0-9A-Fa-f]{4}\\.[0-9A-Fa-f]{4}\\.[0-9A-Fa-f]{4}"; } description "This type defines IS-IS system-id using a pattern, An example system-id is 0143.0438.AeF0"; } typedef net-address { type string { pattern "[a-fA-F0-9]{2}(\\.[a-fA-F0-9]{4}){3,9}\\.[a-fA-F0-9]{2}"; } description "This type defines an OSI NET address using a pattern, An example net-address is 49.0123.6452.1972.00"; } typedef if-state-type { type enumeration { enum "up" { value 0; description "Up state."; } enum "down" { value 1; description "Down state"; } } description "This type defines the state of an interface"; } typedef adj-state-type { type enumeration { enum "up" { value 0; description "State indicates the adjacency is established."; } enum "down" { value 1; description "State indicates the adjacency is NOT established."; } enum "init" { value 2; description "State indicates the adjacency is establishing."; } enum "failed" { value 3; description "State indicates the adjacency is failed."; } } description "This type defines states of an adjacency"; } typedef metric-style-type { type enumeration { enum "narrow" { value 0; description "This enum describes narrow metric style"; reference "RFC1195"; } enum "wide" { value 1; description "This enum describes wide metric style"; reference "RFC5305"; } enum "transition" { value 2; description "This enum describes transition metric style"; } } } grouping redistribute-attributes { description "Common optional attributes of any redistribute entry."; leaf route-map { type string { length "1..max"; } description "Applies the conditions of the specified route-map to routes that are redistributed into this routing instance."; } leaf metric { type uint32 { range "0..16777215"; } default "0"; description "Metric used for the redistributed route. If 0, the default-metric attribute is used instead."; } } grouping redistribute-default { description "Redistribution of default route within a level."; leaf always { type boolean; default "false"; description "Always advertise default route."; } uses redistribute-attributes; } grouping isis-password { description "Authentication attributes or an IS-IS area or domain."; leaf password { type string { length "1..254"; } mandatory true; description "Actual password."; } leaf password-type { type enumeration { enum "clear" { value 1; description "Clear-text password type."; } enum "md5" { value 54; description "MD5 password type."; } } mandatory true; description "Type of password used."; } } grouping isis-area-password { uses isis-password; leaf authenticate-snp { type enumeration { enum "none" { value 0; description "No SNP authentication."; } enum "send-only" { value 1; description "Send authenticated PDUs but do not check on receiving."; } enum "validate" { value 3; description "Send authenticated PDUs and check on receiving."; } } default "none"; description "SNP PDUs authentication."; } } grouping notification-instance-hdr { description "Instance specific IS-IS notification data grouping"; leaf routing-instance { type string; description "Name of the routing-instance instance."; } leaf routing-protocol-name { type string; description "Name of the IS-IS instance."; } leaf isis-level { type level; description "IS-IS level of the instance."; } } grouping notification-interface-hdr { description "Interface specific IS-IS notification data grouping"; leaf interface-name { type string; description "IS-IS interface name"; } leaf interface-level { type level; description "IS-IS level of the interface."; } leaf extended-circuit-id { type uint32; description "Eextended circuit-id of the interface."; } } container isis { description "Configuration of the IS-IS routing daemon."; list instance { key "area-tag"; description "IS-IS routing instance."; leaf area-tag { type string; description "Area-tag associated to this routing instance."; } leaf is-type { type level; default "level-1"; description "Level of the IS-IS routing instance (OSI only)."; } leaf-list area-address { type net-address; max-elements 3; description "List of OSI NET addresses for this protocol instance."; } leaf dynamic-hostname { type boolean; default "true"; description "Dynamic hostname support for IS-IS."; } leaf attached { type boolean; default "false"; description "If true, identify as L1/L2 router for inter-area traffic."; } leaf overload { type boolean; default "false"; description "If true, avoid any transit traffic."; } leaf metric-style { type metric-style-type; must ". = 'wide' or count(../multi-topology/*) = 0"; default "wide"; description "Define the style of TLVs metric supported."; } leaf purge-originator { type boolean; default "false"; description "Use the RFC 6232 purge-originator."; reference "RFC6232"; } container lsp { description "Configuration of Link-State Packets (LSP) parameters"; leaf mtu { type uint16 { range "128..4352"; } default "1497"; description "MTU of an LSP."; } container refresh-interval { description ""; leaf level-1 { type uint16; units "seconds"; default "900"; description "LSP refresh interval for level-1."; } leaf level-2 { type uint16; units "seconds"; default "900"; description "LSP refresh interval for level-2."; } } container maximum-lifetime { description "Maximum LSP lifetime."; leaf level-1 { type uint16 { range "350..65535"; } units "seconds"; must ". >= ../../refresh-interval/level-1 + 300"; default "1200"; description "Maximum LSP lifetime for level-1."; } leaf level-2 { type uint16 { range "350..65535"; } units "seconds"; must ". >= ../../refresh-interval/level-2 + 300"; default "1200"; description "Maximum LSP lifetime for level-2."; } } container generation-interval { description "Minimum LSP regeneration interval."; leaf level-1 { type uint16 { range "1..120"; } units "seconds"; must ". < ../../refresh-interval/level-1"; default "30"; description "Minimum time allowed before level-1 LSP retransmissions."; } leaf level-2 { type uint16 { range "1..120"; } units "seconds"; must ". < ../../refresh-interval/level-2"; default "30"; description "Minimum time allowed before level-2 LSP retransmissions."; } } } container spf { description "Parameters related to the Shortest Path First algorithm."; container ietf-backoff-delay { presence "Present if IETF SPF back-off delay is enabled."; description "SPF back-off delay algorithm parameters (see RFC 8405)."; leaf init-delay { type uint16 { range "0..60000"; } units "msec"; mandatory true; description "Delay used while in QUIET state"; } leaf short-delay { type uint16 { range "0..60000"; } units "msec"; mandatory true; description "Delay used while in SHORT_WAIT state"; } leaf long-delay { type uint16 { range "0..60000"; } units "msec"; mandatory true; description "Delay used while in LONG_WAIT state"; } leaf hold-down { type uint16 { range "0..60000"; } units "msec"; mandatory true; description "Time with no received IGP events before considering IGP stable"; } leaf time-to-learn { type uint16 { range "0..60000"; } units "msec"; mandatory true; description "Maximum duration needed to learn all the events related to a single failure"; } } container minimum-interval { description "Minimum interval between consecutive executions of the SPF algorithm."; leaf level-1 { type uint16 { range "1..120"; } units "seconds"; default "1"; description "Minimum time between consecutive level-1 SPFs."; } leaf level-2 { type uint16 { range "1..120"; } units "seconds"; default "1"; description "Minimum time between consecutive level-2 SPFs."; } } } container area-password { presence "Present if authentication is required for IS level-1."; description "Authentication password for an IS-IS area."; uses isis-area-password; } container domain-password { presence "Present if authentication is required for IS level-2."; description "Authentication password for an IS-IS domain."; uses isis-area-password; } container default-information-originate { description "Distribution of default information."; list ipv4 { key "level"; description "Distribute default route for IPv4."; leaf level { type level; must "(. != \"level-1-2\") and ((../../../is-type = \"level-1-2\") or (. = ../../../is-type))"; } uses redistribute-default; } list ipv6 { key "level"; description "Distribute default route for IPv6."; leaf level { type level; must "(. != \"level-1-2\") and ((../../../is-type = \"level-1-2\") or (. = ../../../is-type))"; } uses redistribute-default; } } container redistribute { description "Redistributes routes learned from other routing protocols."; list ipv4 { key "protocol level"; description "IPv4 route redistribution."; leaf protocol { type frr-route-types:frr-route-types-v4; must ". != \"isis\""; description "Originating routing protocol for the IPv4 routes."; } leaf level { type level; must "(. != \"level-1-2\") and ((../../../is-type = \"level-1-2\") or (. = ../../../is-type))"; description "IS-IS level into which the routes should be redistributed."; } uses redistribute-attributes; } list ipv6 { key "protocol level"; description "IPv6 route redistribution."; leaf protocol { type frr-route-types:frr-route-types-v6; must ". != \"isis\""; description "Originating routing protocol for the IPv6 routes."; } leaf level { type level; must "(. != \"level-1-2\") and ((../../../is-type = \"level-1-2\") or (. = ../../../is-type))"; description "IS-IS level into which the routes should be redistributed."; } uses redistribute-attributes; } } container multi-topology { description "IS-IS topologies configured for this area."; container ipv4-multicast { presence "Present if a separate IPv4-multicast topology is configured for this area."; description "IPv4 multicast topology."; leaf overload { type boolean; default "false"; } } container ipv4-management { presence "Present if a separate IPv4-management topology is configured for this area."; description "IPv4 management topology."; leaf overload { type boolean; default "false"; } } container ipv6-unicast { presence "Present if a separate IPv6-unicast topology is configured for this area."; description "IPv6 unicast topology."; leaf overload { type boolean; default "false"; } } container ipv6-multicast { presence "Present if a separate IPv6-multicast topology is configured for this area."; description "IPv6 multicast topology."; leaf overload { type boolean; default "false"; } } container ipv6-management { presence "Present if a separate IPv6-management topology is configured for this area."; description "IPv6 management topology."; leaf overload { type boolean; default "false"; } } container ipv6-dstsrc { presence "Present if a separate IPv6 destination-source topology is configured for this area."; description "IPv6 destination-source topology."; leaf overload { type boolean; default "false"; } } } leaf log-adjacency-changes { type boolean; default "false"; description "Log changes to the IS-IS adjacencies in this area."; } container mpls-te { presence "Present if MPLS-TE is enabled."; description "Enable MPLS-TE functionality."; leaf router-address { type inet:ipv4-address; description "Stable IP address of the advertising router."; } } } } augment "/frr-interface:lib/frr-interface:interface" { description "Extends interface model with IS-IS related parameters."; container isis { presence "Present if an IS-IS circuit is defined for this interface."; description "IS-IS interface parameters."; leaf area-tag { type string; mandatory true; description "Area-tag associated to this circuit."; } leaf ipv4-routing { type boolean; default "false"; description "Routing IS-IS IPv4 traffic over this circuit."; } leaf ipv6-routing { type boolean; default "false"; description "Routing IS-IS IPv6 traffic over this circuit."; } leaf circuit-type { type level; default "level-1-2"; description "IS-type of this circuit."; } leaf bfd-monitoring { type boolean; default false; description "Monitor IS-IS peers on this circuit."; } container csnp-interval { description "Complete Sequence Number PDU (CSNP) generation interval."; leaf level-1 { type uint16 { range "1..600"; } units "seconds"; default "10"; description "CNSP interval for level-1"; } leaf level-2 { type uint16 { range "1..600"; } units "seconds"; default "10"; description "CNSP interval for level-2"; } } container psnp-interval { description "Partial Sequence Number PDU (PSNP) generation interval."; leaf level-1 { type uint16 { range "1..120"; } units "seconds"; default "2"; description "PNSP interval for level-1"; } leaf level-2 { type uint16 { range "1..120"; } units "seconds"; default "2"; description "PCNSP interval for level-2"; } } container hello { description "Parameters related to IS-IS hello PDUs."; leaf padding { type boolean; default "true"; description "Add padding to IS-IS hello PDUs."; } container interval { description "Interval between consecutive hello messages."; leaf level-1 { type uint32 { range "1..600"; } units "seconds"; default "3"; description "Holding time for level-1; interval will depend on multiplier."; } leaf level-2 { type uint32 { range "1..600"; } units "seconds"; default "3"; description "Holding time for level-2; interval will depend on multiplier."; } } container multiplier { description "Multiplier for the hello messages holding time."; leaf level-1 { type uint16 { range "2..100"; } default "10"; description "Multiplier for the hello holding time."; } leaf level-2 { type uint16 { range "2..100"; } default "10"; description "Multiplier for the hello holding time."; } } } container metric { description "Default metric for this IS-IS circuit."; leaf level-1 { type uint32 { range "0..16777215"; } must ". < 64 or /frr-isisd:isis/instance[area-tag = current()/../../area-tag]/metric-style = 'wide'"; default "10"; description "Default level-1 metric for this IS-IS circuit."; } leaf level-2 { type uint32 { range "0..16777215"; } must ". < 64 or /frr-isisd:isis/instance[area-tag = current()/../../area-tag]/metric-style = 'wide'"; default "10"; description "Default level-2 metric for this IS-IS circuit."; } } container priority { description "Priority for Designated Router election."; leaf level-1 { type uint8 { range "0..127"; } default "64"; description "Level-1 priority for this IS-IS circuit."; } leaf level-2 { type uint8 { range "0..127"; } default "64"; description "Level-2 priority for this IS-IS circuit."; } } leaf network-type { type network-type; default "broadcast"; must "(. = \"point-to-point\") or (. = \"broadcast\")"; description "Explicitly configured type of IS-IS circuit (broadcast or point-to-point)."; } leaf passive { type boolean; default "false"; description "Interface is in passive mode."; } container password { presence "Present if a password is set for this IS interface."; uses isis-password; } leaf disable-three-way-handshake { type boolean; default "false"; description "Disables three-way handshake when creating new adjacencies."; } container multi-topology { description "IS-IS topologies configured on this circuit."; leaf ipv4-unicast { type boolean; default "true"; description "IPv4 unicast topology."; } leaf ipv4-multicast { type boolean; default "true"; description "IPv4 multicast topology."; } leaf ipv4-management { type boolean; default "true"; description "IPv4 management topology."; } leaf ipv6-unicast { type boolean; default "true"; description "IPv6 unicast topology."; } leaf ipv6-multicast { type boolean; default "true"; description "IPv6 multicast topology."; } leaf ipv6-management { type boolean; default "true"; description "IPv6 management topology."; } leaf ipv6-dstsrc { type boolean; default "true"; description "IPv6 destination-source topology."; } } } } notification database-overload { description "This notification is sent when an IS-IS instance overload state changes."; uses notification-instance-hdr; leaf overload { type enumeration { enum "off" { value 0; description "Indicates IS-IS instance has left overload state"; } enum "on" { value 1; description "Indicates IS-IS instance has entered overload state"; } } description "New overload state of the IS-IS instance"; } } notification lsp-too-large { description "This notification is sent when we attempt to propagate an LSP that is larger than the dataLinkBlockSize for the circuit. The notification generation must be throttled with at least 5 seconds between successive notifications."; uses notification-instance-hdr; uses notification-interface-hdr; leaf pdu-size { type uint32; description "Size of the LSP PDU"; } leaf lsp-id { type lsp-id; description "LSP ID"; } } notification if-state-change { description "This notification is sent when an interface state change is detected."; uses notification-instance-hdr; uses notification-interface-hdr; leaf state { type if-state-type; description "Interface state."; } } notification corrupted-lsp-detected { description "This notification is sent when we find that an LSP that was stored in memory has become corrupted."; uses notification-instance-hdr; leaf lsp-id { type lsp-id; description "LSP ID"; } } notification attempt-to-exceed-max-sequence { description "This notification is sent when the system wraps the 32-bit sequence counter of an LSP."; uses notification-instance-hdr; leaf lsp-id { type lsp-id; description "LSP ID"; } } notification id-len-mismatch { description "This notification is sent when we receive a PDU with a different value for the System ID length. The notification generation must be throttled with at least 5 seconds between successive notifications."; uses notification-instance-hdr; uses notification-interface-hdr; leaf pdu-field-len { type uint8; description "Size of the ID length in the received PDU"; } leaf raw-pdu { type binary; description "Received raw PDU."; } } notification max-area-addresses-mismatch { description "This notification is sent when we receive a PDU with a different value for the Maximum Area Addresses. The notification generation must be throttled with at least 5 seconds between successive notifications."; uses notification-instance-hdr; uses notification-interface-hdr; leaf max-area-addresses { type uint8; description "Received number of supported areas"; } leaf raw-pdu { type binary; description "Received raw PDU."; } } notification own-lsp-purge { description "This notification is sent when the system receives a PDU with its own system ID and zero age."; uses notification-instance-hdr; uses notification-interface-hdr; leaf lsp-id { type lsp-id; description "LSP ID"; } } notification sequence-number-skipped { description "This notification is sent when the system receives a PDU with its own system ID and different contents. The system has to reoriginate the LSP with a higher sequence number."; uses notification-instance-hdr; uses notification-interface-hdr; leaf lsp-id { type lsp-id; description "LSP ID"; } } notification authentication-type-failure { description "This notification is sent when the system receives a PDU with the wrong authentication type field. The notification generation must be throttled with at least 5 seconds between successive notifications."; uses notification-instance-hdr; uses notification-interface-hdr; leaf raw-pdu { type binary; description "Received raw PDU."; } } notification authentication-failure { description "This notification is sent when the system receives a PDU with the wrong authentication information. The notification generation must be throttled with with at least 5 seconds between successive notifications."; uses notification-instance-hdr; uses notification-interface-hdr; leaf raw-pdu { type binary; description "Received raw PDU."; } } notification version-skew { description "This notification is sent when the system receives a PDU with a different protocol version number. The notification generation must be throttled with at least 5 seconds between successive notifications."; uses notification-instance-hdr; uses notification-interface-hdr; leaf protocol-version { type uint8; description "Protocol version received in the PDU."; } leaf raw-pdu { type binary; description "Received raw PDU."; } } notification area-mismatch { description "This notification is sent when the system receives a Hello PDU from an IS that does not share any area address. The notification generation must be throttled with at least 5 seconds between successive notifications."; uses notification-instance-hdr; uses notification-interface-hdr; leaf raw-pdu { type binary; description "Received raw PDU."; } } notification rejected-adjacency { description "This notification is sent when the system receives a Hello PDU from an IS but does not establish an adjacency for some reason. The notification generation must be throttled with at least 5 seconds between successive notifications."; uses notification-instance-hdr; uses notification-interface-hdr; leaf raw-pdu { type binary; description "Received raw PDU."; } leaf reason { type string; description "The system may provide a reason to reject the adjacency. If the reason is not available, an empty string will be returned."; } } notification lsp-error-detected { description "This notification is sent when the system receives an LSP with a parse error. The notification generation must be throttled with at least 5 seconds between successive notifications."; uses notification-instance-hdr; uses notification-interface-hdr; leaf lsp-id { type lsp-id; description "LSP ID."; } leaf raw-pdu { type binary; description "Received raw PDU."; } leaf error-offset { type uint32; description "If the problem is a malformed TLV, the error-offset points to the start of the TLV. If the problem is with the LSP header, the error-offset points to the errant byte"; } leaf tlv-type { type uint8; description "If the problem is a malformed TLV, the tlv-type is set to the type value of the suspicious TLV. Otherwise, this leaf is not present."; } } notification adjacency-state-change { description "This notification is sent when an IS-IS adjacency moves to Up state or to Down state."; uses notification-instance-hdr; uses notification-interface-hdr; leaf neighbor { type string; description "Name of the neighbor. If the name of the neighbor is not available, it is not returned."; } leaf neighbor-system-id { type system-id; description "Neighbor system-id"; } leaf state { type adj-state-type; description "New state of the IS-IS adjacency."; } leaf reason { type string; description "If the adjacency is going to DOWN, this leaf provides a reason for the adjacency going down. The reason is provided as a text. If the adjacency is going to UP, no reason is provided."; } } notification lsp-received { description "This notification is sent when an LSP is received. The notification generation must be throttled with at least 5 seconds between successive notifications."; uses notification-instance-hdr; uses notification-interface-hdr; leaf lsp-id { type lsp-id; description "LSP ID"; } leaf sequence { type uint32; description "Sequence number of the received LSP."; } leaf received-timestamp { type yang:timestamp; description "Timestamp when the LSP was received."; } leaf neighbor-system-id { type system-id; description "Neighbor system-id of LSP sender"; } } notification lsp-generation { description "This notification is sent when an LSP is regenerated. The notification generation must be throttled with at least 5 seconds between successive notifications."; uses notification-instance-hdr; leaf lsp-id { type lsp-id; description "LSP ID"; } leaf sequence { type uint32; description "Sequence number of the received LSP."; } leaf send-timestamp { type yang:timestamp; description "Timestamp when our LSP was regenerated."; } } } frr-7.2.1/yang/frr-module-translator.yang0000644000000000000000000000276613610377563015310 00000000000000module frr-module-translator { yang-version 1.1; namespace "http://frrouting.org/yang/frr-module-translator"; prefix frr-module-translator; organization "Free Range Routing"; contact "FRR Users List: FRR Development List: "; description "A model for FRR YANG module translators."; revision 2018-07-31 { description "Initial revision."; } container frr-module-translator { leaf family { type string { length "0 .. 32"; } mandatory true; description "Family of YANG models."; } list module { key "name"; ordered-by user; description "YANG module."; leaf name { type string; description "Module name."; } leaf deviations { type string; mandatory true; description "Module containing the YANG deviations."; } list mappings { key "custom"; description "YANG mappings between the custom module and FRR native modules."; leaf custom { type string { length "0 .. 256"; } description "YANG path of the custom module."; } leaf native { type string { length "0 .. 256"; } mandatory true; description "Corresponding path of the native YANG modules"; } } } } } frr-7.2.1/yang/frr-ripd.yang0000644000000000000000000003716713610377563012575 00000000000000module frr-ripd { yang-version 1.1; namespace "http://frrouting.org/yang/ripd"; prefix frr-ripd; import ietf-inet-types { prefix inet; } import ietf-yang-types { prefix yang; } import frr-interface { prefix frr-interface; } import frr-route-types { prefix frr-route-types; } organization "Free Range Routing"; contact "FRR Users List: FRR Development List: "; description "This module defines a model for managing FRR ripd daemon."; revision 2017-12-06 { description "Initial revision."; reference "RFC 1058: Routing Information Protocol. RFC 2453: RIP Version 2."; } container ripd { /* * Routing instance configuration. */ list instance { key "vrf"; description "RIP routing instance."; leaf vrf { type string; description "VRF name."; } leaf allow-ecmp { type boolean; default "false"; description "Allow equal-cost multi-path."; } leaf default-information-originate { type boolean; default "false"; description "Control distribution of default route."; } leaf default-metric { type uint8 { range "1..16"; } default "1"; description "Default metric of redistributed routes."; } container distance { description "Administrative distance."; leaf default { type uint8 { range "0..255"; } default "0"; description "Default administrative distance."; } list source { key "prefix"; description "Custom administrative distance per IP prefix."; leaf prefix { type inet:ipv4-prefix; description "Prefix."; } leaf distance { type uint8 { range "1..255"; } mandatory true; description "Administrative distance."; } leaf access-list { type string; description "Access-list name."; } } } leaf-list explicit-neighbor { type inet:ipv4-address; description "Specifies the RIP neighbors. Useful for a non-broadcast multiple access (NBMA) network."; } leaf-list network { type inet:ipv4-prefix; description "Enable RIP on the specified IP network."; } leaf-list interface { type string { length "1..16"; } description "Enable RIP on the specified interface."; } list offset-list { key "interface direction"; description "Offset-list to modify route metric."; leaf interface { type string; description "Interface to match. Use '*' to match all interfaces."; } leaf direction { type enumeration { enum in { value 0; description "Incoming updates."; } enum out { value 1; description "Outgoing updates."; } } description "Incoming or outgoing updates."; } leaf access-list { type string; mandatory true; description "Access-list name."; } leaf metric { type uint8 { range "0..16"; } mandatory true; description "Route metric."; } } leaf passive-default { type boolean; default "false"; description "Control whether interfaces are in the passive mode by default or not."; } leaf-list passive-interface { when "../passive-default = 'false'"; type string { length "1..16"; } description "A list of interfaces where the sending of RIP packets is disabled."; } leaf-list non-passive-interface { when "../passive-default = 'true'"; type string { length "1..16"; } description "A list of interfaces where the sending of RIP packets is enabled."; } list redistribute { key "protocol"; description "Redistributes routes learned from other routing protocols."; leaf protocol { type frr-route-types:frr-route-types-v4; description "Routing protocol."; must '. != "rip"'; } leaf route-map { type string { length "1..max"; } description "Applies the conditions of the specified route-map to routes that are redistributed into the RIP routing instance."; } leaf metric { type uint8 { range "0..16"; } description "Metric used for the redistributed route. If a metric is not specified, the metric configured with the default-metric attribute in RIP router configuration is used. If the default-metric attribute has not been configured, the default metric for redistributed routes is 0."; } } leaf-list static-route { type inet:ipv4-prefix; description "RIP static routes."; } container timers { description "Settings of basic timers"; leaf flush-interval { type uint32 { range "5..2147483647"; } units "seconds"; default "120"; description "Interval before a route is flushed from the routing table."; } leaf holddown-interval { type uint32 { range "5..2147483647"; } units "seconds"; default "180"; description "Interval before better routes are released."; } leaf update-interval { type uint32 { range "5..2147483647"; } units "seconds"; default "30"; description "Interval at which RIP updates are sent."; } } container version { leaf receive { type enumeration { enum "1" { value 1; description "Accept RIPv1 updates only."; } enum "2" { value 2; description "Accept RIPv2 updates only."; } enum "1-2" { value 3; description "Accept both RIPv1 and RIPv2 updates."; } } default "1-2"; description "Advertisement reception - Version control."; } leaf send { type enumeration { enum "1" { value 1; description "Send RIPv1 updates only."; } enum "2" { value 2; description "Send RIPv2 updates only."; } } default "2"; description "Advertisement transmission - Version control."; } must '(./receive = "1" and ./send = "1") or ' + '(./receive = "2" and ./send = "2") or ' + '(./receive = "1-2" and ./send = "2")'; } /* * Operational data. */ container state { config false; description "Operational data."; container neighbors { description "Neighbor information."; list neighbor { key "address"; description "A RIP neighbor."; leaf address { type inet:ipv4-address; description "IP address that a RIP neighbor is using as its source address."; } leaf last-update { type yang:date-and-time; description "The time when the most recent RIP update was received from this neighbor."; } leaf bad-packets-rcvd { type yang:counter32; description "The number of RIP invalid packets received from this neighbor which were subsequently discarded for any reason (e.g. a version 0 packet, or an unknown command type)."; } leaf bad-routes-rcvd { type yang:counter32; description "The number of routes received from this neighbor, in valid RIP packets, which were ignored for any reason (e.g. unknown address family, or invalid metric)."; } } } container routes { description "Route information."; list route { key "prefix"; description "A RIP IPv4 route."; leaf prefix { type inet:ipv4-prefix; description "IP address (in the form A.B.C.D) and prefix length, separated by the slash (/) character. The range of values for the prefix-length is 0 to 32."; } leaf next-hop { type inet:ipv4-address; description "Next hop IPv4 address."; } leaf interface { type string; description "The interface that the route uses."; } leaf metric { type uint8 { range "0..16"; } description "Route metric."; } } } } } } /* * Per-interface configuration data */ augment "/frr-interface:lib/frr-interface:interface" { container rip { description "RIP interface parameters."; leaf split-horizon { type enumeration { enum "disabled" { value 0; description "Disables split-horizon processing."; } enum "simple" { value 1; description "Enables simple split-horizon processing."; } enum "poison-reverse" { value 2; description "Enables split-horizon processing with poison reverse."; } } default "simple"; description "Controls RIP split-horizon processing on the specified interface."; } leaf v2-broadcast { type boolean; default "false"; description "Send IP broadcast v2 update."; } leaf version-receive { type enumeration { enum "unspecified" { value 0; description "Inherit configuration from the routing instance."; } enum "1" { value 1; description "Accept RIPv1 updates only."; } enum "2" { value 2; description "Accept RIPv2 updates only."; } enum "both" { value 3; description "Accept both RIPv1 and RIPv2 updates."; } enum "none" { value 4; description "Do not accept neither RIPv1 nor RIPv2 updates."; } } default "unspecified"; description "Advertisement reception - Version control."; } leaf version-send { type enumeration { enum "unspecified" { value 0; description "Inherit configuration from the routing instance."; } enum "1" { value 1; description "Send RIPv1 updates only."; } enum "2" { value 2; description "Send RIPv2 updates only."; } enum "both" { value 3; description "Send both RIPv1 and RIPv2 updates."; } enum "none" { value 4; description "Do not send neither RIPv1 nor RIPv2 updates."; } } default "unspecified"; description "Advertisement transmission - Version control."; } container authentication-scheme { description "Specify the authentication scheme for the RIP interface"; leaf mode { type enumeration { enum "none" { value 0; description "No authentication."; } enum "plain-text" { value 2; description "Plain-text authentication."; } enum "md5" { value 3; description "MD5 authentication."; } } default "none"; description "Specify the authentication mode."; } leaf md5-auth-length { when "../mode = 'md5'"; type enumeration { enum "16" { value 16; description "RFC compatible."; } enum "20" { value 20; description "Old ripd compatible."; } } default "20"; description "MD5 authentication data length."; } } choice authentication-data { description "Choose whether to use a simple password or a key-chain."; leaf authentication-password { type string { length "1..16"; } description "Authentication string."; } leaf authentication-key-chain { type string; description "Key-chain name."; } } } } /* * RPCs */ rpc clear-rip-route { description "Clears RIP routes from the IP routing table and routes redistributed into the RIP protocol."; input { leaf vrf { type string; description "VRF name identifying a specific RIP instance. This leaf is optional for the rpc. If it is specified, the rpc will clear all routes in the specified RIP instance; if it is not specified, the rpc will clear all routes in all RIP instances."; } } } /* * Notifications */ notification authentication-type-failure { description "This notification is sent when the system receives a PDU with the wrong authentication type field."; leaf interface-name { type string; description "Describes the name of the RIP interface."; } leaf raw-pdu { type binary; description "Received raw PDU."; } } notification authentication-failure { description "This notification is sent when the system receives a PDU with the wrong authentication information."; leaf interface-name { type string; description "Describes the name of the RIP interface."; } leaf raw-pdu { type binary; description "Received raw PDU."; } } } frr-7.2.1/yang/frr-ripngd.yang0000644000000000000000000002141413610377563013106 00000000000000module frr-ripngd { yang-version 1.1; namespace "http://frrouting.org/yang/ripngd"; prefix frr-ripngd; import ietf-inet-types { prefix inet; } import ietf-yang-types { prefix yang; } import frr-interface { prefix frr-interface; } import frr-route-types { prefix frr-route-types; } organization "Free Range Routing"; contact "FRR Users List: FRR Development List: "; description "This module defines a model for managing FRR ripngd daemon."; revision 2018-11-27 { description "Initial revision."; reference "RFC 2080: RIPng for IPv6."; } container ripngd { /* * Routing instance configuration. */ list instance { key "vrf"; description "RIPng routing instance."; leaf vrf { type string; description "VRF name."; } leaf allow-ecmp { type boolean; default "false"; description "Allow equal-cost multi-path."; } leaf default-information-originate { type boolean; default "false"; description "Control distribution of default route."; } leaf default-metric { type uint8 { range "1..16"; } default "1"; description "Default metric of redistributed routes."; } leaf-list network { type inet:ipv6-prefix; description "Enable RIPng on the specified IPv6 network."; } leaf-list interface { type string { length "1..16"; } description "Enable RIPng on the specified interface."; } list offset-list { key "interface direction"; description "Offset-list to modify route metric."; leaf interface { type string; description "Interface to match. Use '*' to match all interfaces."; } leaf direction { type enumeration { enum in { value 0; description "Incoming updates."; } enum out { value 1; description "Outgoing updates."; } } description "Incoming or outgoing updates."; } leaf access-list { type string; mandatory true; description "Access-list name."; } leaf metric { type uint8 { range "0..16"; } mandatory true; description "Route metric."; } } leaf-list passive-interface { type string { length "1..16"; } description "A list of interfaces where the sending of RIPng packets is disabled."; } list redistribute { key "protocol"; description "Redistributes routes learned from other routing protocols."; leaf protocol { type frr-route-types:frr-route-types-v6; description "Routing protocol."; must '. != "ripng"'; } leaf route-map { type string { length "1..max"; } description "Applies the conditions of the specified route-map to routes that are redistributed into the RIPng routing instance."; } leaf metric { type uint8 { range "0..16"; } description "Metric used for the redistributed route. If a metric is not specified, the metric configured with the default-metric attribute in RIPng router configuration is used. If the default-metric attribute has not been configured, the default metric for redistributed routes is 0."; } } leaf-list static-route { type inet:ipv6-prefix; description "RIPng static routes."; } leaf-list aggregate-address { type inet:ipv6-prefix; description "RIPng aggregate route announcement."; } container timers { description "Settings of basic timers"; leaf flush-interval { type uint16 { range "1..65535"; } units "seconds"; default "120"; description "Interval before a route is flushed from the routing table."; } leaf holddown-interval { type uint16 { range "1..65535"; } units "seconds"; default "180"; description "Interval before better routes are released."; } leaf update-interval { type uint16 { range "1..65535"; } units "seconds"; default "30"; description "Interval at which RIPng updates are sent."; } } /* * Operational data. */ container state { config false; description "Operational data."; container neighbors { description "Neighbor information."; list neighbor { key "address"; description "A RIPng neighbor."; leaf address { type inet:ipv6-address; description "IPv6 address that a RIPng neighbor is using as its source address."; } leaf last-update { type yang:date-and-time; description "The time when the most recent RIPng update was received from this neighbor."; } leaf bad-packets-rcvd { type yang:counter32; description "The number of RIPng invalid packets received from this neighbor which were subsequently discarded for any reason (e.g. a version 0 packet, or an unknown command type)."; } leaf bad-routes-rcvd { type yang:counter32; description "The number of routes received from this neighbor, in valid RIPng packets, which were ignored for any reason (e.g. unknown address family, or invalid metric)."; } } } container routes { description "Route information."; list route { key "prefix"; description "A RIPng IPv6 route."; leaf prefix { type inet:ipv6-prefix; description "IPv6 address and prefix length, in the format specified in RFC6991."; } leaf next-hop { type inet:ipv6-address; description "Next hop IPv6 address."; } leaf interface { type string; description "The interface that the route uses."; } leaf metric { type uint8 { range "0..16"; } description "Route metric."; } } } } } } /* * Per-interface configuration data */ augment "/frr-interface:lib/frr-interface:interface" { container ripng { description "RIPng interface parameters."; leaf split-horizon { type enumeration { enum "disabled" { value 0; description "Disables split-horizon processing."; } enum "simple" { value 1; description "Enables simple split-horizon processing."; } enum "poison-reverse" { value 2; description "Enables split-horizon processing with poison reverse."; } } default "simple"; description "Controls RIPng split-horizon processing on the specified interface."; } } } /* * RPCs */ rpc clear-ripng-route { description "Clears RIPng routes from the IPv6 routing table and routes redistributed into the RIPng protocol."; input { leaf vrf { type string; description "VRF name identifying a specific RIPng instance. This leaf is optional for the rpc. If it is specified, the rpc will clear all routes in the specified RIPng instance; if it is not specified, the rpc will clear all routes in all RIPng instances."; } } } } frr-7.2.1/yang/frr-route-types.yang0000644000000000000000000000336213610377563014125 00000000000000module frr-route-types { yang-version 1.1; namespace "http://frrouting.org/yang/route-types"; prefix frr-route-types; organization "Free Range Routing"; contact "FRR Users List: FRR Development List: "; description "This module defines typedefs for route types."; revision 2018-03-28 { description "Initial revision."; } typedef frr-route-types-v4 { type enumeration { enum kernel { value 1; } enum connected { value 2; } enum static { value 3; } enum rip { value 4; } enum ospf { value 6; } enum isis { value 8; } enum bgp { value 9; } enum eigrp { value 11; } enum nhrp { value 12; } enum table { value 15; } enum vnc { value 17; } enum babel { value 22; } enum sharp { value 23; } enum openfabric { value 26; } } } typedef frr-route-types-v6 { type enumeration { enum kernel { value 1; } enum connected { value 2; } enum static { value 3; } enum ripng { value 5; } enum ospf6 { value 7; } enum isis { value 8; } enum bgp { value 9; } enum nhrp { value 12; } enum table { value 15; } enum vnc { value 17; } enum babel { value 22; } enum sharp { value 23; } enum openfabric { value 26; } } } } frr-7.2.1/yang/frr-test-module.yang0000644000000000000000000000203413610377563014062 00000000000000module frr-test-module { yang-version 1.1; namespace "urn:frr-test-module"; prefix frr-test-module; import ietf-inet-types { prefix inet; } import ietf-yang-types { prefix yang; } import frr-interface { prefix frr-interface; } revision 2018-11-26 { description "Initial revision."; } container frr-test-module { config false; container vrfs { list vrf { key "name"; leaf name { type string; } container interfaces { leaf-list interface { type string; } } container routes { list route { leaf prefix { type inet:ipv4-prefix; } leaf next-hop { type inet:ipv4-address; } leaf interface { type string; } leaf metric { type uint8; } leaf active { type empty; } } } } } } } frr-7.2.1/yang/libyang_plugins/0000755000000000000000000000000013610377563013420 500000000000000frr-7.2.1/yang/libyang_plugins/subdir.am0000644000000000000000000000033313610377563015146 00000000000000# # libyang user types # # XXX: disable support for libyang custom user types temporarily to facilitate # the transition from libyang 0.x to libyang 1.x. #lib_libfrr_la_SOURCES += yang/libyang_plugins/frr_user_types.c frr-7.2.1/yang/subdir.am0000644000000000000000000000214013610377563011756 00000000000000SUFFIXES += .yang .yang.c .yin .yin.c EXTRA_DIST += yang/embedmodel.py .yang.yang.c: $(AM_V_GEN)$(PYTHON) $(top_srcdir)/yang/embedmodel.py $^ $@ .yin.yin.c: $(AM_V_GEN)$(PYTHON) $(top_srcdir)/yang/embedmodel.py $^ $@ # use .yang.c files like this: # # ripd_ripd_SOURCES = \ # ... # nodist_ripd_ripd_SOURCES = \ # yang/frr-ripd.yang.c \ # # end # # Note that putting the .yang.c file into a static library.a will NOT work # because the entire file is "optimized out" since it does not contain any # global symbols :(. Just put it in the daemon. Dynamic libraries.so work # without problems, as seen in libfrr. dist_yangmodels_DATA += yang/frr-module-translator.yang dist_yangmodels_DATA += yang/frr-test-module.yang dist_yangmodels_DATA += yang/frr-interface.yang dist_yangmodels_DATA += yang/frr-route-types.yang if BFDD dist_yangmodels_DATA += yang/frr-bfdd.yang endif if EIGRPD dist_yangmodels_DATA += yang/frr-eigrpd.yang endif if RIPD dist_yangmodels_DATA += yang/frr-ripd.yang endif if RIPNGD dist_yangmodels_DATA += yang/frr-ripngd.yang endif if ISISD dist_yangmodels_DATA += yang/frr-isisd.yang endif frr-7.2.1/ylwrap0000755000000000000000000001531413610377563010464 00000000000000#! /bin/sh # ylwrap - wrapper for lex/yacc invocations. scriptversion=2018-03-07.03; # UTC # Copyright (C) 1996-2018 Free Software Foundation, Inc. # # Written by Tom Tromey . # # This program is free software; you can redistribute it and/or modify # it under the terms of the GNU General Public License as published by # the Free Software Foundation; either version 2, or (at your option) # any later version. # # This program is distributed in the hope that it will be useful, # but WITHOUT ANY WARRANTY; without even the implied warranty of # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the # GNU General Public License for more details. # # You should have received a copy of the GNU General Public License # along with this program. If not, see . # As a special exception to the GNU General Public License, if you # distribute this file as part of a program that contains a # configuration script generated by Autoconf, you may include it under # the same distribution terms that you use for the rest of that program. # This file is maintained in Automake, please report # bugs to or send patches to # . get_dirname () { case $1 in */*|*\\*) printf '%s\n' "$1" | sed -e 's|\([\\/]\)[^\\/]*$|\1|';; # Otherwise, we want the empty string (not "."). esac } # guard FILE # ---------- # The CPP macro used to guard inclusion of FILE. guard () { printf '%s\n' "$1" \ | sed \ -e 'y/abcdefghijklmnopqrstuvwxyz/ABCDEFGHIJKLMNOPQRSTUVWXYZ/' \ -e 's/[^ABCDEFGHIJKLMNOPQRSTUVWXYZ]/_/g' \ -e 's/__*/_/g' } # quote_for_sed [STRING] # ---------------------- # Return STRING (or stdin) quoted to be used as a sed pattern. quote_for_sed () { case $# in 0) cat;; 1) printf '%s\n' "$1";; esac \ | sed -e 's|[][\\.*]|\\&|g' } case "$1" in '') echo "$0: No files given. Try '$0 --help' for more information." 1>&2 exit 1 ;; --basedir) basedir=$2 shift 2 ;; -h|--h*) cat <<\EOF Usage: ylwrap [--help|--version] INPUT [OUTPUT DESIRED]... -- PROGRAM [ARGS]... Wrapper for lex/yacc invocations, renaming files as desired. INPUT is the input file OUTPUT is one file PROG generates DESIRED is the file we actually want instead of OUTPUT PROGRAM is program to run ARGS are passed to PROG Any number of OUTPUT,DESIRED pairs may be used. Report bugs to . EOF exit $? ;; -v|--v*) echo "ylwrap $scriptversion" exit $? ;; esac # The input. input=$1 shift # We'll later need for a correct munging of "#line" directives. input_sub_rx=`get_dirname "$input" | quote_for_sed` case $input in [\\/]* | ?:[\\/]*) # Absolute path; do nothing. ;; *) # Relative path. Make it absolute. input=`pwd`/$input ;; esac input_rx=`get_dirname "$input" | quote_for_sed` # Since DOS filename conventions don't allow two dots, # the DOS version of Bison writes out y_tab.c instead of y.tab.c # and y_tab.h instead of y.tab.h. Test to see if this is the case. y_tab_nodot=false if test -f y_tab.c || test -f y_tab.h; then y_tab_nodot=true fi # The parser itself, the first file, is the destination of the .y.c # rule in the Makefile. parser=$1 # A sed program to s/FROM/TO/g for all the FROM/TO so that, for # instance, we rename #include "y.tab.h" into #include "parse.h" # during the conversion from y.tab.c to parse.c. sed_fix_filenames= # Also rename header guards, as Bison 2.7 for instance uses its header # guard in its implementation file. sed_fix_header_guards= while test $# -ne 0; do if test x"$1" = x"--"; then shift break fi from=$1 # Handle y_tab.c and y_tab.h output by DOS if $y_tab_nodot; then case $from in "y.tab.c") from=y_tab.c;; "y.tab.h") from=y_tab.h;; esac fi shift to=$1 shift sed_fix_filenames="${sed_fix_filenames}s|"`quote_for_sed "$from"`"|$to|g;" sed_fix_header_guards="${sed_fix_header_guards}s|"`guard "$from"`"|"`guard "$to"`"|g;" done # The program to run. prog=$1 shift # Make any relative path in $prog absolute. case $prog in [\\/]* | ?:[\\/]*) ;; *[\\/]*) prog=`pwd`/$prog ;; esac dirname=ylwrap$$ do_exit="cd '`pwd`' && rm -rf $dirname > /dev/null 2>&1;"' (exit $ret); exit $ret' trap "ret=129; $do_exit" 1 trap "ret=130; $do_exit" 2 trap "ret=141; $do_exit" 13 trap "ret=143; $do_exit" 15 mkdir $dirname || exit 1 cd $dirname case $# in 0) "$prog" "$input" ;; *) "$prog" "$@" "$input" ;; esac ret=$? if test $ret -eq 0; then for from in * do to=`printf '%s\n' "$from" | sed "$sed_fix_filenames"` if test -f "$from"; then # If $2 is an absolute path name, then just use that, # otherwise prepend '../'. case $to in [\\/]* | ?:[\\/]*) target=$to;; *) target=../$to;; esac # Do not overwrite unchanged header files to avoid useless # recompilations. Always update the parser itself: it is the # destination of the .y.c rule in the Makefile. Divert the # output of all other files to a temporary file so we can # compare them to existing versions. if test $from != $parser; then realtarget=$target target=tmp-`printf '%s\n' "$target" | sed 's|.*[\\/]||g'` fi # Munge "#line" or "#" directives. Don't let the resulting # debug information point at an absolute srcdir. Use the real # output file name, not yy.lex.c for instance. Adjust the # include guards too. sed -e "/^#/!b" \ -e "s|$input_rx|$input_sub_rx|" \ -e "$sed_fix_filenames" \ -e "$sed_fix_header_guards" \ "$from" >"$target" || ret=$? # Check whether files must be updated. if test "$from" != "$parser"; then if test -f "$realtarget" && cmp -s "$realtarget" "$target"; then echo "$to is unchanged" rm -f "$target" else echo "updating $to" mv -f "$target" "$realtarget" fi fi else # A missing file is only an error for the parser. This is a # blatant hack to let us support using "yacc -d". If -d is not # specified, don't fail when the header file is "missing". if test "$from" = "$parser"; then ret=1 fi fi done fi # Remove the directory. cd .. rm -rf $dirname exit $ret # Local Variables: # mode: shell-script # sh-indentation: 2 # eval: (add-hook 'before-save-hook 'time-stamp) # time-stamp-start: "scriptversion=" # time-stamp-format: "%:y-%02m-%02d.%02H" # time-stamp-time-zone: "UTC0" # time-stamp-end: "; # UTC" # End: frr-7.2.1/zebra/0000755000000000000000000000000013610377563010377 500000000000000frr-7.2.1/zebra/Makefile0000644000000000000000000000022213610377563011753 00000000000000all: ALWAYS @$(MAKE) -s -C .. zebra/zebra %: ALWAYS @$(MAKE) -s -C .. zebra/$@ Makefile: #nothing ALWAYS: .PHONY: ALWAYS makefiles .SUFFIXES: frr-7.2.1/zebra/connected.c0000644000000000000000000003423413610377563012433 00000000000000/* * Address linked list routine. * Copyright (C) 1997, 98 Kunihiro Ishiguro * * This file is part of GNU Zebra. * * GNU Zebra is free software; you can redistribute it and/or modify it * under the terms of the GNU General Public License as published by the * Free Software Foundation; either version 2, or (at your option) any * later version. * * GNU Zebra 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; see the file COPYING; if not, write to the Free Software * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA */ #include #include "prefix.h" #include "linklist.h" #include "if.h" #include "table.h" #include "rib.h" #include "table.h" #include "log.h" #include "memory.h" #include "zebra_memory.h" #include "vty.h" #include "zebra/debug.h" #include "zebra/zserv.h" #include "zebra/redistribute.h" #include "zebra/interface.h" #include "zebra/connected.h" #include "zebra/rtadv.h" #include "zebra/zebra_mpls.h" #include "zebra/debug.h" #include "zebra/zebra_errors.h" /* communicate the withdrawal of a connected address */ static void connected_withdraw(struct connected *ifc) { if (!ifc) return; /* Update interface address information to protocol daemon. */ if (CHECK_FLAG(ifc->conf, ZEBRA_IFC_REAL)) { zebra_interface_address_delete_update(ifc->ifp, ifc); if (ifc->address->family == AF_INET) if_subnet_delete(ifc->ifp, ifc); connected_down(ifc->ifp, ifc); UNSET_FLAG(ifc->conf, ZEBRA_IFC_REAL); } /* The address is not in the kernel anymore, so clear the flag */ UNSET_FLAG(ifc->conf, ZEBRA_IFC_QUEUED); if (!CHECK_FLAG(ifc->conf, ZEBRA_IFC_CONFIGURED)) { listnode_delete(ifc->ifp->connected, ifc); connected_free(ifc); } } static void connected_announce(struct interface *ifp, struct connected *ifc) { if (!ifc) return; if (!if_is_loopback(ifp) && ifc->address->family == AF_INET && !IS_ZEBRA_IF_VRF(ifp)) { if (ifc->address->prefixlen == 32) SET_FLAG(ifc->flags, ZEBRA_IFA_UNNUMBERED); else UNSET_FLAG(ifc->flags, ZEBRA_IFA_UNNUMBERED); } listnode_add(ifp->connected, ifc); /* Update interface address information to protocol daemon. */ if (ifc->address->family == AF_INET) if_subnet_add(ifp, ifc); zebra_interface_address_add_update(ifp, ifc); if (if_is_operative(ifp)) { connected_up(ifp, ifc); } } /* If same interface address is already exist... */ struct connected *connected_check(struct interface *ifp, union prefixconstptr pu) { const struct prefix *p = pu.p; struct connected *ifc; struct listnode *node; for (ALL_LIST_ELEMENTS_RO(ifp->connected, node, ifc)) if (prefix_same(ifc->address, p)) return ifc; return NULL; } /* same, but with peer address */ struct connected *connected_check_ptp(struct interface *ifp, union prefixconstptr pu, union prefixconstptr du) { const struct prefix *p = pu.p; const struct prefix *d = du.p; struct connected *ifc; struct listnode *node; for (ALL_LIST_ELEMENTS_RO(ifp->connected, node, ifc)) { if (!prefix_same(ifc->address, p)) continue; if (!CONNECTED_PEER(ifc) && !d) return ifc; if (CONNECTED_PEER(ifc) && d && prefix_same(ifc->destination, d)) return ifc; } return NULL; } /* Check if two ifc's describe the same address in the same state */ static int connected_same(struct connected *ifc1, struct connected *ifc2) { if (ifc1->ifp != ifc2->ifp) return 0; if (ifc1->flags != ifc2->flags) return 0; if (ifc1->conf != ifc2->conf) return 0; if (ifc1->destination) if (!ifc2->destination) return 0; if (ifc2->destination) if (!ifc1->destination) return 0; if (ifc1->destination && ifc2->destination) if (!prefix_same(ifc1->destination, ifc2->destination)) return 0; return 1; } /* Handle changes to addresses and send the neccesary announcements * to clients. */ static void connected_update(struct interface *ifp, struct connected *ifc) { struct connected *current; /* Check same connected route. */ current = connected_check_ptp(ifp, ifc->address, ifc->destination); if (current) { if (CHECK_FLAG(current->conf, ZEBRA_IFC_CONFIGURED)) SET_FLAG(ifc->conf, ZEBRA_IFC_CONFIGURED); /* Avoid spurious withdraws, this might be just the kernel * 'reflecting' * back an address we have already added. */ if (connected_same(current, ifc)) { /* nothing to do */ connected_free(ifc); return; } /* Clear the configured flag on the old ifc, so it will be freed * by * connected withdraw. */ UNSET_FLAG(current->conf, ZEBRA_IFC_CONFIGURED); connected_withdraw( current); /* implicit withdraw - freebsd does this */ } /* If the connected is new or has changed, announce it, if it is usable */ if (CHECK_FLAG(ifc->conf, ZEBRA_IFC_REAL)) connected_announce(ifp, ifc); } /* Called from if_up(). */ void connected_up(struct interface *ifp, struct connected *ifc) { afi_t afi; struct prefix p; struct nexthop nh = { .type = NEXTHOP_TYPE_IFINDEX, .ifindex = ifp->ifindex, .vrf_id = ifp->vrf_id, }; struct zebra_vrf *zvrf; uint32_t metric; zvrf = zebra_vrf_lookup_by_id(ifp->vrf_id); if (!zvrf) { flog_err(EC_ZEBRA_VRF_NOT_FOUND, "%s: Received Up for interface but no associated zvrf: %d", __PRETTY_FUNCTION__, ifp->vrf_id); return; } if (!CHECK_FLAG(ifc->conf, ZEBRA_IFC_REAL)) return; PREFIX_COPY(&p, CONNECTED_PREFIX(ifc)); /* Apply mask to the network. */ apply_mask(&p); afi = family2afi(p.family); switch (afi) { case AFI_IP: /* * In case of connected address is 0.0.0.0/0 we treat it tunnel * address. */ if (prefix_ipv4_any((struct prefix_ipv4 *)&p)) return; break; case AFI_IP6: #ifndef GNU_LINUX /* XXX: It is already done by rib_bogus_ipv6 within rib_add */ if (IN6_IS_ADDR_UNSPECIFIED(&p.u.prefix6)) return; #endif break; default: flog_warn(EC_ZEBRA_CONNECTED_AFI_UNKNOWN, "Received unknown AFI: %s", afi2str(afi)); return; break; } metric = (ifc->metric < (uint32_t)METRIC_MAX) ? ifc->metric : ifp->metric; rib_add(afi, SAFI_UNICAST, zvrf->vrf->vrf_id, ZEBRA_ROUTE_CONNECT, 0, 0, &p, NULL, &nh, zvrf->table_id, metric, 0, 0, 0); rib_add(afi, SAFI_MULTICAST, zvrf->vrf->vrf_id, ZEBRA_ROUTE_CONNECT, 0, 0, &p, NULL, &nh, zvrf->table_id, metric, 0, 0, 0); /* Schedule LSP forwarding entries for processing, if appropriate. */ if (zvrf->vrf->vrf_id == VRF_DEFAULT) { if (IS_ZEBRA_DEBUG_MPLS) { char buf[PREFIX_STRLEN]; zlog_debug( "%u: IF %s IP %s address add/up, scheduling MPLS processing", zvrf->vrf->vrf_id, ifp->name, prefix2str(&p, buf, sizeof(buf))); } mpls_mark_lsps_for_processing(zvrf, &p); } } /* Add connected IPv4 route to the interface. */ void connected_add_ipv4(struct interface *ifp, int flags, struct in_addr *addr, uint16_t prefixlen, struct in_addr *dest, const char *label, uint32_t metric) { struct prefix_ipv4 *p; struct connected *ifc; if (ipv4_martian(addr)) return; /* Make connected structure. */ ifc = connected_new(); ifc->ifp = ifp; ifc->flags = flags; ifc->metric = metric; /* If we get a notification from the kernel, * we can safely assume the address is known to the kernel */ SET_FLAG(ifc->conf, ZEBRA_IFC_QUEUED); /* Allocate new connected address. */ p = prefix_ipv4_new(); p->family = AF_INET; p->prefix = *addr; p->prefixlen = CHECK_FLAG(flags, ZEBRA_IFA_PEER) ? IPV4_MAX_PREFIXLEN : prefixlen; ifc->address = (struct prefix *)p; /* If there is a peer address. */ if (CONNECTED_PEER(ifc)) { /* validate the destination address */ if (dest) { p = prefix_ipv4_new(); p->family = AF_INET; p->prefix = *dest; p->prefixlen = prefixlen; ifc->destination = (struct prefix *)p; if (IPV4_ADDR_SAME(addr, dest)) flog_warn( EC_ZEBRA_IFACE_SAME_LOCAL_AS_PEER, "warning: interface %s has same local and peer " "address %s, routing protocols may malfunction", ifp->name, inet_ntoa(*addr)); } else { zlog_debug( "warning: %s called for interface %s " "with peer flag set, but no peer address supplied", __func__, ifp->name); UNSET_FLAG(ifc->flags, ZEBRA_IFA_PEER); } } /* no destination address was supplied */ if (!dest && (prefixlen == IPV4_MAX_PREFIXLEN) && if_is_pointopoint(ifp)) zlog_debug( "warning: PtP interface %s with addr %s/%d needs a " "peer address", ifp->name, inet_ntoa(*addr), prefixlen); /* Label of this address. */ if (label) ifc->label = XSTRDUP(MTYPE_CONNECTED_LABEL, label); /* For all that I know an IPv4 address is always ready when we receive * the notification. So it should be safe to set the REAL flag here. */ SET_FLAG(ifc->conf, ZEBRA_IFC_REAL); connected_update(ifp, ifc); } void connected_down(struct interface *ifp, struct connected *ifc) { afi_t afi; struct prefix p; struct nexthop nh = { .type = NEXTHOP_TYPE_IFINDEX, .ifindex = ifp->ifindex, .vrf_id = ifp->vrf_id, }; struct zebra_vrf *zvrf; zvrf = zebra_vrf_lookup_by_id(ifp->vrf_id); if (!zvrf) { flog_err(EC_ZEBRA_VRF_NOT_FOUND, "%s: Received Up for interface but no associated zvrf: %d", __PRETTY_FUNCTION__, ifp->vrf_id); return; } if (!CHECK_FLAG(ifc->conf, ZEBRA_IFC_REAL)) return; PREFIX_COPY(&p, CONNECTED_PREFIX(ifc)); /* Apply mask to the network. */ apply_mask(&p); afi = family2afi(p.family); switch (afi) { case AFI_IP: /* * In case of connected address is 0.0.0.0/0 we treat it tunnel * address. */ if (prefix_ipv4_any((struct prefix_ipv4 *)&p)) return; break; case AFI_IP6: if (IN6_IS_ADDR_UNSPECIFIED(&p.u.prefix6)) return; break; default: zlog_info("Unknown AFI: %s", afi2str(afi)); break; } /* * Same logic as for connected_up(): push the changes into the * head. */ rib_delete(afi, SAFI_UNICAST, zvrf->vrf->vrf_id, ZEBRA_ROUTE_CONNECT, 0, 0, &p, NULL, &nh, zvrf->table_id, 0, 0, false); rib_delete(afi, SAFI_MULTICAST, zvrf->vrf->vrf_id, ZEBRA_ROUTE_CONNECT, 0, 0, &p, NULL, &nh, zvrf->table_id, 0, 0, false); /* Schedule LSP forwarding entries for processing, if appropriate. */ if (zvrf->vrf->vrf_id == VRF_DEFAULT) { if (IS_ZEBRA_DEBUG_MPLS) { char buf[PREFIX_STRLEN]; zlog_debug( "%u: IF %s IP %s address down, scheduling MPLS processing", zvrf->vrf->vrf_id, ifp->name, prefix2str(&p, buf, sizeof(buf))); } mpls_mark_lsps_for_processing(zvrf, &p); } } static void connected_delete_helper(struct connected *ifc, struct prefix *p) { struct interface *ifp; if (!ifc) return; ifp = ifc->ifp; connected_withdraw(ifc); /* Schedule LSP forwarding entries for processing, if appropriate. */ if (ifp->vrf_id == VRF_DEFAULT) { if (IS_ZEBRA_DEBUG_MPLS) { char buf[PREFIX_STRLEN]; zlog_debug( "%u: IF %s IP %s address delete, scheduling MPLS processing", ifp->vrf_id, ifp->name, prefix2str(p, buf, sizeof(buf))); } mpls_mark_lsps_for_processing(vrf_info_lookup(ifp->vrf_id), p); } } /* Delete connected IPv4 route to the interface. */ void connected_delete_ipv4(struct interface *ifp, int flags, struct in_addr *addr, uint16_t prefixlen, struct in_addr *dest) { struct prefix p, d; struct connected *ifc; memset(&p, 0, sizeof(struct prefix)); p.family = AF_INET; p.u.prefix4 = *addr; p.prefixlen = CHECK_FLAG(flags, ZEBRA_IFA_PEER) ? IPV4_MAX_PREFIXLEN : prefixlen; if (dest) { memset(&d, 0, sizeof(struct prefix)); d.family = AF_INET; d.u.prefix4 = *dest; d.prefixlen = prefixlen; ifc = connected_check_ptp(ifp, &p, &d); } else ifc = connected_check_ptp(ifp, &p, NULL); connected_delete_helper(ifc, &p); } /* Add connected IPv6 route to the interface. */ void connected_add_ipv6(struct interface *ifp, int flags, struct in6_addr *addr, struct in6_addr *dest, uint16_t prefixlen, const char *label, uint32_t metric) { struct prefix_ipv6 *p; struct connected *ifc; if (ipv6_martian(addr)) return; /* Make connected structure. */ ifc = connected_new(); ifc->ifp = ifp; ifc->flags = flags; ifc->metric = metric; /* If we get a notification from the kernel, * we can safely assume the address is known to the kernel */ SET_FLAG(ifc->conf, ZEBRA_IFC_QUEUED); /* Allocate new connected address. */ p = prefix_ipv6_new(); p->family = AF_INET6; IPV6_ADDR_COPY(&p->prefix, addr); p->prefixlen = prefixlen; ifc->address = (struct prefix *)p; if (dest) { p = prefix_ipv6_new(); p->family = AF_INET6; IPV6_ADDR_COPY(&p->prefix, dest); p->prefixlen = prefixlen; ifc->destination = (struct prefix *)p; } else { if (CHECK_FLAG(ifc->flags, ZEBRA_IFA_PEER)) { zlog_debug( "warning: %s called for interface %s with peer flag set, but no peer address supplied", __func__, ifp->name); UNSET_FLAG(ifc->flags, ZEBRA_IFA_PEER); } } /* Label of this address. */ if (label) ifc->label = XSTRDUP(MTYPE_CONNECTED_LABEL, label); /* On Linux, we only get here when DAD is complete, therefore we can set * ZEBRA_IFC_REAL. * * On BSD, there currently doesn't seem to be a way to check for * completion of * DAD, so we replicate the old behaviour and set ZEBRA_IFC_REAL, * although DAD * might still be running. */ SET_FLAG(ifc->conf, ZEBRA_IFC_REAL); connected_update(ifp, ifc); } void connected_delete_ipv6(struct interface *ifp, struct in6_addr *address, struct in6_addr *dest, uint16_t prefixlen) { struct prefix p, d; struct connected *ifc; memset(&p, 0, sizeof(struct prefix)); p.family = AF_INET6; memcpy(&p.u.prefix6, address, sizeof(struct in6_addr)); p.prefixlen = prefixlen; if (dest) { memset(&d, 0, sizeof(struct prefix)); d.family = AF_INET6; IPV6_ADDR_COPY(&d.u.prefix6, dest); d.prefixlen = prefixlen; ifc = connected_check_ptp(ifp, &p, &d); } else ifc = connected_check_ptp(ifp, &p, NULL); connected_delete_helper(ifc, &p); } int connected_is_unnumbered(struct interface *ifp) { struct connected *connected; struct listnode *node; for (ALL_LIST_ELEMENTS_RO(ifp->connected, node, connected)) { if (CHECK_FLAG(connected->conf, ZEBRA_IFC_REAL) && connected->address->family == AF_INET) return CHECK_FLAG(connected->flags, ZEBRA_IFA_UNNUMBERED); } return 1; } frr-7.2.1/zebra/connected.h0000644000000000000000000000433113610377563012433 00000000000000/* * Interface's address and mask. * Copyright (C) 1997 Kunihiro Ishiguro * * This file is part of GNU Zebra. * * GNU Zebra is free software; you can redistribute it and/or modify it * under the terms of the GNU General Public License as published by the * Free Software Foundation; either version 2, or (at your option) any * later version. * * GNU Zebra 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; see the file COPYING; if not, write to the Free Software * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA */ #ifndef _ZEBRA_CONNECTED_H #define _ZEBRA_CONNECTED_H #include #include #include "lib/if.h" #include "lib/prefix.h" #ifdef __cplusplus extern "C" { #endif extern struct connected *connected_check(struct interface *ifp, union prefixconstptr p); extern struct connected *connected_check_ptp(struct interface *ifp, union prefixconstptr p, union prefixconstptr d); extern void connected_add_ipv4(struct interface *ifp, int flags, struct in_addr *addr, uint16_t prefixlen, struct in_addr *dest, const char *label, uint32_t metric); extern void connected_delete_ipv4(struct interface *ifp, int flags, struct in_addr *addr, uint16_t prefixlen, struct in_addr *dest); extern void connected_delete_ipv4_unnumbered(struct connected *ifc); extern void connected_up(struct interface *ifp, struct connected *ifc); extern void connected_down(struct interface *ifp, struct connected *ifc); extern void connected_add_ipv6(struct interface *ifp, int flags, struct in6_addr *address, struct in6_addr *dest, uint16_t prefixlen, const char *label, uint32_t metric); extern void connected_delete_ipv6(struct interface *ifp, struct in6_addr *address, struct in6_addr *dest, uint16_t prefixlen); extern int connected_is_unnumbered(struct interface *); #ifdef __cplusplus } #endif #endif /*_ZEBRA_CONNECTED_H */ frr-7.2.1/zebra/debug.c0000644000000000000000000004066113610377563011560 00000000000000/* * Zebra debug related function * Copyright (C) 1999 Kunihiro Ishiguro * * This file is part of GNU Zebra. * * GNU Zebra is free software; you can redistribute it and/or modify it * under the terms of the GNU General Public License as published by the * Free Software Foundation; either version 2, or (at your option) any * later version. * * GNU Zebra 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; see the file COPYING; if not, write to the Free Software * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA */ #include #include "command.h" #include "debug.h" #ifndef VTYSH_EXTRACT_PL #include "zebra/debug_clippy.c" #endif /* For debug statement. */ unsigned long zebra_debug_event; unsigned long zebra_debug_packet; unsigned long zebra_debug_kernel; unsigned long zebra_debug_rib; unsigned long zebra_debug_fpm; unsigned long zebra_debug_nht; unsigned long zebra_debug_mpls; unsigned long zebra_debug_vxlan; unsigned long zebra_debug_pw; unsigned long zebra_debug_dplane; unsigned long zebra_debug_mlag; DEFINE_HOOK(zebra_debug_show_debugging, (struct vty *vty), (vty)); DEFUN_NOSH (show_debugging_zebra, show_debugging_zebra_cmd, "show debugging [zebra]", SHOW_STR "Debugging information\n" "Zebra configuration\n") { vty_out(vty, "Zebra debugging status:\n"); if (IS_ZEBRA_DEBUG_EVENT) vty_out(vty, " Zebra event debugging is on\n"); if (IS_ZEBRA_DEBUG_PACKET) { if (IS_ZEBRA_DEBUG_SEND && IS_ZEBRA_DEBUG_RECV) { vty_out(vty, " Zebra packet%s debugging is on\n", IS_ZEBRA_DEBUG_DETAIL ? " detail" : ""); } else { if (IS_ZEBRA_DEBUG_SEND) vty_out(vty, " Zebra packet send%s debugging is on\n", IS_ZEBRA_DEBUG_DETAIL ? " detail" : ""); else vty_out(vty, " Zebra packet receive%s debugging is on\n", IS_ZEBRA_DEBUG_DETAIL ? " detail" : ""); } } if (IS_ZEBRA_DEBUG_KERNEL) vty_out(vty, " Zebra kernel debugging is on\n"); if (IS_ZEBRA_DEBUG_KERNEL_MSGDUMP_SEND) vty_out(vty, " Zebra kernel netlink message dumps (send) are on\n"); if (IS_ZEBRA_DEBUG_KERNEL_MSGDUMP_RECV) vty_out(vty, " Zebra kernel netlink message dumps (recv) are on\n"); /* Check here using flags as the 'macro' does an OR */ if (CHECK_FLAG(zebra_debug_rib, ZEBRA_DEBUG_RIB_DETAILED)) vty_out(vty, " Zebra RIB detailed debugging is on\n"); else if (CHECK_FLAG(zebra_debug_rib, ZEBRA_DEBUG_RIB)) vty_out(vty, " Zebra RIB debugging is on\n"); if (IS_ZEBRA_DEBUG_FPM) vty_out(vty, " Zebra FPM debugging is on\n"); if (IS_ZEBRA_DEBUG_NHT_DETAILED) vty_out(vty, " Zebra detailed next-hop tracking debugging is on\n"); else if (IS_ZEBRA_DEBUG_NHT) vty_out(vty, " Zebra next-hop tracking debugging is on\n"); if (IS_ZEBRA_DEBUG_MPLS) vty_out(vty, " Zebra MPLS debugging is on\n"); if (IS_ZEBRA_DEBUG_VXLAN) vty_out(vty, " Zebra VXLAN debugging is on\n"); if (IS_ZEBRA_DEBUG_PW) vty_out(vty, " Zebra pseudowire debugging is on\n"); if (IS_ZEBRA_DEBUG_DPLANE_DETAIL) vty_out(vty, " Zebra detailed dataplane debugging is on\n"); else if (IS_ZEBRA_DEBUG_DPLANE) vty_out(vty, " Zebra dataplane debugging is on\n"); if (IS_ZEBRA_DEBUG_MLAG) vty_out(vty, " Zebra mlag debugging is on\n"); hook_call(zebra_debug_show_debugging, vty); return CMD_SUCCESS; } DEFUN (debug_zebra_events, debug_zebra_events_cmd, "debug zebra events", DEBUG_STR "Zebra configuration\n" "Debug option set for zebra events\n") { zebra_debug_event = ZEBRA_DEBUG_EVENT; return CMD_SUCCESS; } DEFUN (debug_zebra_nht, debug_zebra_nht_cmd, "debug zebra nht [detailed]", DEBUG_STR "Zebra configuration\n" "Debug option set for zebra next hop tracking\n" "Debug option set for detailed info\n") { int idx = 0; zebra_debug_nht = ZEBRA_DEBUG_NHT; if (argv_find(argv, argc, "detailed", &idx)) zebra_debug_nht |= ZEBRA_DEBUG_NHT_DETAILED; return CMD_SUCCESS; } DEFUN (debug_zebra_mpls, debug_zebra_mpls_cmd, "debug zebra mpls", DEBUG_STR "Zebra configuration\n" "Debug option set for zebra MPLS LSPs\n") { zebra_debug_mpls = ZEBRA_DEBUG_MPLS; return CMD_SUCCESS; } DEFUN (debug_zebra_vxlan, debug_zebra_vxlan_cmd, "debug zebra vxlan", DEBUG_STR "Zebra configuration\n" "Debug option set for zebra VxLAN (EVPN)\n") { zebra_debug_vxlan = ZEBRA_DEBUG_VXLAN; return CMD_SUCCESS; } DEFUN (debug_zebra_pw, debug_zebra_pw_cmd, "[no] debug zebra pseudowires", NO_STR DEBUG_STR "Zebra configuration\n" "Debug option set for zebra pseudowires\n") { if (strmatch(argv[0]->text, "no")) UNSET_FLAG(zebra_debug_pw, ZEBRA_DEBUG_PW); else SET_FLAG(zebra_debug_pw, ZEBRA_DEBUG_PW); return CMD_SUCCESS; } DEFUN (debug_zebra_packet, debug_zebra_packet_cmd, "debug zebra packet [] [detail]", DEBUG_STR "Zebra configuration\n" "Debug option set for zebra packet\n" "Debug option set for receive packet\n" "Debug option set for send packet\n" "Debug option set for detailed info\n") { int idx = 0; zebra_debug_packet = ZEBRA_DEBUG_PACKET; if (argv_find(argv, argc, "send", &idx)) SET_FLAG(zebra_debug_packet, ZEBRA_DEBUG_SEND); else if (argv_find(argv, argc, "recv", &idx)) SET_FLAG(zebra_debug_packet, ZEBRA_DEBUG_RECV); else { SET_FLAG(zebra_debug_packet, ZEBRA_DEBUG_SEND); SET_FLAG(zebra_debug_packet, ZEBRA_DEBUG_RECV); } if (argv_find(argv, argc, "detail", &idx)) SET_FLAG(zebra_debug_packet, ZEBRA_DEBUG_DETAIL); return CMD_SUCCESS; } DEFUN (debug_zebra_kernel, debug_zebra_kernel_cmd, "debug zebra kernel", DEBUG_STR "Zebra configuration\n" "Debug option set for zebra between kernel interface\n") { SET_FLAG(zebra_debug_kernel, ZEBRA_DEBUG_KERNEL); if (IS_ZEBRA_DEBUG_KERNEL_MSGDUMP_RECV) UNSET_FLAG(zebra_debug_kernel, ZEBRA_DEBUG_KERNEL_MSGDUMP_RECV); if (IS_ZEBRA_DEBUG_KERNEL_MSGDUMP_SEND) UNSET_FLAG(zebra_debug_kernel, ZEBRA_DEBUG_KERNEL_MSGDUMP_SEND); return CMD_SUCCESS; } DEFUN (debug_zebra_kernel_msgdump, debug_zebra_kernel_msgdump_cmd, "debug zebra kernel msgdump []", DEBUG_STR "Zebra configuration\n" "Debug option set for zebra between kernel interface\n" "Dump raw netlink messages, sent and received\n" "Dump raw netlink messages received\n" "Dump raw netlink messages sent\n") { int idx = 0; if (argv_find(argv, argc, "recv", &idx)) { SET_FLAG(zebra_debug_kernel, ZEBRA_DEBUG_KERNEL_MSGDUMP_RECV); if (IS_ZEBRA_DEBUG_KERNEL_MSGDUMP_SEND) UNSET_FLAG(zebra_debug_kernel, ZEBRA_DEBUG_KERNEL_MSGDUMP_SEND); } else if (argv_find(argv, argc, "send", &idx)) { SET_FLAG(zebra_debug_kernel, ZEBRA_DEBUG_KERNEL_MSGDUMP_SEND); if (IS_ZEBRA_DEBUG_KERNEL_MSGDUMP_RECV) UNSET_FLAG(zebra_debug_kernel, ZEBRA_DEBUG_KERNEL_MSGDUMP_RECV); } else { SET_FLAG(zebra_debug_kernel, ZEBRA_DEBUG_KERNEL_MSGDUMP_RECV); SET_FLAG(zebra_debug_kernel, ZEBRA_DEBUG_KERNEL_MSGDUMP_SEND); } return CMD_SUCCESS; } DEFUN (debug_zebra_rib, debug_zebra_rib_cmd, "debug zebra rib [detailed]", DEBUG_STR "Zebra configuration\n" "Debug RIB events\n" "Detailed debugs\n") { int idx = 0; SET_FLAG(zebra_debug_rib, ZEBRA_DEBUG_RIB); if (argv_find(argv, argc, "detailed", &idx)) SET_FLAG(zebra_debug_rib, ZEBRA_DEBUG_RIB_DETAILED); return CMD_SUCCESS; } DEFUN (debug_zebra_fpm, debug_zebra_fpm_cmd, "debug zebra fpm", DEBUG_STR "Zebra configuration\n" "Debug zebra FPM events\n") { SET_FLAG(zebra_debug_fpm, ZEBRA_DEBUG_FPM); return CMD_SUCCESS; } DEFUN (debug_zebra_dplane, debug_zebra_dplane_cmd, "debug zebra dplane [detailed]", DEBUG_STR "Zebra configuration\n" "Debug zebra dataplane events\n" "Detailed debug information\n") { int idx = 0; SET_FLAG(zebra_debug_dplane, ZEBRA_DEBUG_DPLANE); if (argv_find(argv, argc, "detailed", &idx)) SET_FLAG(zebra_debug_dplane, ZEBRA_DEBUG_DPLANE_DETAILED); return CMD_SUCCESS; } DEFPY (debug_zebra_mlag, debug_zebra_mlag_cmd, "[no$no] debug zebra mlag", NO_STR DEBUG_STR "Zebra configuration\n" "Debug option set for mlag events\n") { if (no) UNSET_FLAG(zebra_debug_mlag, ZEBRA_DEBUG_MLAG); else SET_FLAG(zebra_debug_mlag, ZEBRA_DEBUG_MLAG); return CMD_SUCCESS; } DEFUN (no_debug_zebra_events, no_debug_zebra_events_cmd, "no debug zebra events", NO_STR DEBUG_STR "Zebra configuration\n" "Debug option set for zebra events\n") { zebra_debug_event = 0; return CMD_SUCCESS; } DEFUN (no_debug_zebra_nht, no_debug_zebra_nht_cmd, "no debug zebra nht [detailed]", NO_STR DEBUG_STR "Zebra configuration\n" "Debug option set for zebra next hop tracking\n" "Debug option set for detailed info\n") { zebra_debug_nht = 0; return CMD_SUCCESS; } DEFUN (no_debug_zebra_mpls, no_debug_zebra_mpls_cmd, "no debug zebra mpls", NO_STR DEBUG_STR "Zebra configuration\n" "Debug option set for zebra MPLS LSPs\n") { zebra_debug_mpls = 0; return CMD_SUCCESS; } DEFUN (no_debug_zebra_vxlan, no_debug_zebra_vxlan_cmd, "no debug zebra vxlan", NO_STR DEBUG_STR "Zebra configuration\n" "Debug option set for zebra VxLAN (EVPN)\n") { zebra_debug_vxlan = 0; return CMD_SUCCESS; } DEFUN (no_debug_zebra_packet, no_debug_zebra_packet_cmd, "no debug zebra packet [] [detail]", NO_STR DEBUG_STR "Zebra configuration\n" "Debug option set for zebra packet\n" "Debug option set for receive packet\n" "Debug option set for send packet\n" "Debug option set for detailed info\n") { zebra_debug_packet = 0; return CMD_SUCCESS; } DEFUN (no_debug_zebra_kernel, no_debug_zebra_kernel_cmd, "no debug zebra kernel", NO_STR DEBUG_STR "Zebra configuration\n" "Debug option set for zebra between kernel interface\n") { zebra_debug_kernel = 0; return CMD_SUCCESS; } DEFUN (no_debug_zebra_kernel_msgdump, no_debug_zebra_kernel_msgdump_cmd, "no debug zebra kernel msgdump []", NO_STR DEBUG_STR "Zebra configuration\n" "Debug option set for zebra between kernel interface\n" "Dump raw netlink messages, sent and received\n" "Dump raw netlink messages received\n" "Dump raw netlink messages sent\n") { zebra_debug_kernel = 0; return CMD_SUCCESS; } DEFUN (no_debug_zebra_rib, no_debug_zebra_rib_cmd, "no debug zebra rib [detailed]", NO_STR DEBUG_STR "Zebra configuration\n" "Debug zebra RIB\n" "Detailed debugs\n") { zebra_debug_rib = 0; return CMD_SUCCESS; } DEFUN (no_debug_zebra_fpm, no_debug_zebra_fpm_cmd, "no debug zebra fpm", NO_STR DEBUG_STR "Zebra configuration\n" "Debug zebra FPM events\n") { zebra_debug_fpm = 0; return CMD_SUCCESS; } DEFUN (no_debug_zebra_dplane, no_debug_zebra_dplane_cmd, "no debug zebra dplane", NO_STR DEBUG_STR "Zebra configuration\n" "Debug zebra dataplane events\n") { zebra_debug_dplane = 0; return CMD_SUCCESS; } /* Debug node. */ struct cmd_node debug_node = {DEBUG_NODE, "", /* Debug node has no interface. */ 1}; static int config_write_debug(struct vty *vty) { int write = 0; if (IS_ZEBRA_DEBUG_EVENT) { vty_out(vty, "debug zebra events\n"); write++; } if (IS_ZEBRA_DEBUG_PACKET) { if (IS_ZEBRA_DEBUG_SEND && IS_ZEBRA_DEBUG_RECV) { vty_out(vty, "debug zebra packet%s\n", IS_ZEBRA_DEBUG_DETAIL ? " detail" : ""); write++; } else { if (IS_ZEBRA_DEBUG_SEND) vty_out(vty, "debug zebra packet send%s\n", IS_ZEBRA_DEBUG_DETAIL ? " detail" : ""); else vty_out(vty, "debug zebra packet recv%s\n", IS_ZEBRA_DEBUG_DETAIL ? " detail" : ""); write++; } } if (IS_ZEBRA_DEBUG_KERNEL) { if (IS_ZEBRA_DEBUG_KERNEL_MSGDUMP_SEND && IS_ZEBRA_DEBUG_KERNEL_MSGDUMP_RECV) { vty_out(vty, "debug zebra kernel msgdump\n"); write++; } else if (IS_ZEBRA_DEBUG_KERNEL_MSGDUMP_RECV) { vty_out(vty, "debug zebra kernel msgdump recv\n"); write++; } else if (IS_ZEBRA_DEBUG_KERNEL_MSGDUMP_SEND) { vty_out(vty, "debug zebra kernel msgdump send\n"); write++; } else { vty_out(vty, "debug zebra kernel\n"); write++; } } if (CHECK_FLAG(zebra_debug_rib, ZEBRA_DEBUG_RIB_DETAILED)) { vty_out(vty, "debug zebra rib detailed\n"); write++; } else if (CHECK_FLAG(zebra_debug_rib, ZEBRA_DEBUG_RIB)) { vty_out(vty, "debug zebra rib\n"); write++; } if (IS_ZEBRA_DEBUG_FPM) { vty_out(vty, "debug zebra fpm\n"); write++; } if (IS_ZEBRA_DEBUG_NHT_DETAILED) { vty_out(vty, "debug zebra nht detailed\n"); write++; } else if (IS_ZEBRA_DEBUG_NHT) { vty_out(vty, "debug zebra nht\n"); write++; } if (IS_ZEBRA_DEBUG_MPLS) { vty_out(vty, "debug zebra mpls\n"); write++; } if (IS_ZEBRA_DEBUG_VXLAN) { vty_out(vty, "debug zebra vxlan\n"); write++; } if (IS_ZEBRA_DEBUG_PW) { vty_out(vty, "debug zebra pseudowires\n"); write++; } if (CHECK_FLAG(zebra_debug_dplane, ZEBRA_DEBUG_DPLANE_DETAILED)) { vty_out(vty, "debug zebra dplane detailed\n"); write++; } else if (CHECK_FLAG(zebra_debug_dplane, ZEBRA_DEBUG_DPLANE)) { vty_out(vty, "debug zebra dplane\n"); write++; } return write; } void zebra_debug_init(void) { zebra_debug_event = 0; zebra_debug_packet = 0; zebra_debug_kernel = 0; zebra_debug_rib = 0; zebra_debug_fpm = 0; zebra_debug_mpls = 0; zebra_debug_vxlan = 0; zebra_debug_pw = 0; zebra_debug_dplane = 0; zebra_debug_mlag = 0; zebra_debug_nht = 0; install_node(&debug_node, config_write_debug); install_element(VIEW_NODE, &show_debugging_zebra_cmd); install_element(ENABLE_NODE, &debug_zebra_events_cmd); install_element(ENABLE_NODE, &debug_zebra_nht_cmd); install_element(ENABLE_NODE, &debug_zebra_mpls_cmd); install_element(ENABLE_NODE, &debug_zebra_vxlan_cmd); install_element(ENABLE_NODE, &debug_zebra_pw_cmd); install_element(ENABLE_NODE, &debug_zebra_packet_cmd); install_element(ENABLE_NODE, &debug_zebra_kernel_cmd); install_element(ENABLE_NODE, &debug_zebra_kernel_msgdump_cmd); install_element(ENABLE_NODE, &debug_zebra_rib_cmd); install_element(ENABLE_NODE, &debug_zebra_fpm_cmd); install_element(ENABLE_NODE, &debug_zebra_dplane_cmd); install_element(ENABLE_NODE, &debug_zebra_mlag_cmd); install_element(ENABLE_NODE, &no_debug_zebra_events_cmd); install_element(ENABLE_NODE, &no_debug_zebra_nht_cmd); install_element(ENABLE_NODE, &no_debug_zebra_mpls_cmd); install_element(ENABLE_NODE, &no_debug_zebra_vxlan_cmd); install_element(ENABLE_NODE, &no_debug_zebra_packet_cmd); install_element(ENABLE_NODE, &no_debug_zebra_kernel_cmd); install_element(ENABLE_NODE, &no_debug_zebra_kernel_msgdump_cmd); install_element(ENABLE_NODE, &no_debug_zebra_rib_cmd); install_element(ENABLE_NODE, &no_debug_zebra_fpm_cmd); install_element(ENABLE_NODE, &no_debug_zebra_dplane_cmd); install_element(CONFIG_NODE, &debug_zebra_events_cmd); install_element(CONFIG_NODE, &debug_zebra_nht_cmd); install_element(CONFIG_NODE, &debug_zebra_mpls_cmd); install_element(CONFIG_NODE, &debug_zebra_vxlan_cmd); install_element(CONFIG_NODE, &debug_zebra_pw_cmd); install_element(CONFIG_NODE, &debug_zebra_packet_cmd); install_element(CONFIG_NODE, &debug_zebra_kernel_cmd); install_element(CONFIG_NODE, &debug_zebra_kernel_msgdump_cmd); install_element(CONFIG_NODE, &debug_zebra_rib_cmd); install_element(CONFIG_NODE, &debug_zebra_fpm_cmd); install_element(CONFIG_NODE, &debug_zebra_dplane_cmd); install_element(CONFIG_NODE, &no_debug_zebra_events_cmd); install_element(CONFIG_NODE, &no_debug_zebra_nht_cmd); install_element(CONFIG_NODE, &no_debug_zebra_mpls_cmd); install_element(CONFIG_NODE, &no_debug_zebra_vxlan_cmd); install_element(CONFIG_NODE, &no_debug_zebra_packet_cmd); install_element(CONFIG_NODE, &no_debug_zebra_kernel_cmd); install_element(CONFIG_NODE, &no_debug_zebra_kernel_msgdump_cmd); install_element(CONFIG_NODE, &no_debug_zebra_rib_cmd); install_element(CONFIG_NODE, &no_debug_zebra_fpm_cmd); install_element(CONFIG_NODE, &no_debug_zebra_dplane_cmd); } frr-7.2.1/zebra/debug.h0000644000000000000000000000740513610377563011564 00000000000000/* * Zebra debug related function * Copyright (C) 1999 Kunihiro Ishiguro * * This file is part of GNU Zebra. * * GNU Zebra is free software; you can redistribute it and/or modify it * under the terms of the GNU General Public License as published by the * Free Software Foundation; either version 2, or (at your option) any * later version. * * GNU Zebra 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; see the file COPYING; if not, write to the Free Software * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA */ #ifndef _ZEBRA_DEBUG_H #define _ZEBRA_DEBUG_H #include "lib/vty.h" #ifdef __cplusplus extern "C" { #endif /* Debug flags. */ #define ZEBRA_DEBUG_EVENT 0x01 #define ZEBRA_DEBUG_PACKET 0x01 #define ZEBRA_DEBUG_SEND 0x20 #define ZEBRA_DEBUG_RECV 0x40 #define ZEBRA_DEBUG_DETAIL 0x80 #define ZEBRA_DEBUG_KERNEL 0x01 #define ZEBRA_DEBUG_KERNEL_MSGDUMP_SEND 0x20 #define ZEBRA_DEBUG_KERNEL_MSGDUMP_RECV 0x40 #define ZEBRA_DEBUG_RIB 0x01 #define ZEBRA_DEBUG_RIB_DETAILED 0x02 #define ZEBRA_DEBUG_FPM 0x01 #define ZEBRA_DEBUG_NHT 0x01 #define ZEBRA_DEBUG_NHT_DETAILED 0x02 #define ZEBRA_DEBUG_MPLS 0x01 #define ZEBRA_DEBUG_VXLAN 0x01 #define ZEBRA_DEBUG_PW 0x01 #define ZEBRA_DEBUG_DPLANE 0x01 #define ZEBRA_DEBUG_DPLANE_DETAILED 0x02 #define ZEBRA_DEBUG_MLAG 0x01 /* Debug related macro. */ #define IS_ZEBRA_DEBUG_EVENT (zebra_debug_event & ZEBRA_DEBUG_EVENT) #define IS_ZEBRA_DEBUG_PACKET (zebra_debug_packet & ZEBRA_DEBUG_PACKET) #define IS_ZEBRA_DEBUG_SEND (zebra_debug_packet & ZEBRA_DEBUG_SEND) #define IS_ZEBRA_DEBUG_RECV (zebra_debug_packet & ZEBRA_DEBUG_RECV) #define IS_ZEBRA_DEBUG_DETAIL (zebra_debug_packet & ZEBRA_DEBUG_DETAIL) #define IS_ZEBRA_DEBUG_KERNEL (zebra_debug_kernel & ZEBRA_DEBUG_KERNEL) #define IS_ZEBRA_DEBUG_KERNEL_MSGDUMP_SEND \ (zebra_debug_kernel & ZEBRA_DEBUG_KERNEL_MSGDUMP_SEND) #define IS_ZEBRA_DEBUG_KERNEL_MSGDUMP_RECV \ (zebra_debug_kernel & ZEBRA_DEBUG_KERNEL_MSGDUMP_RECV) #define IS_ZEBRA_DEBUG_RIB \ (zebra_debug_rib & (ZEBRA_DEBUG_RIB | ZEBRA_DEBUG_RIB_DETAILED)) #define IS_ZEBRA_DEBUG_RIB_DETAILED (zebra_debug_rib & ZEBRA_DEBUG_RIB_DETAILED) #define IS_ZEBRA_DEBUG_FPM (zebra_debug_fpm & ZEBRA_DEBUG_FPM) #define IS_ZEBRA_DEBUG_NHT (zebra_debug_nht & ZEBRA_DEBUG_NHT) #define IS_ZEBRA_DEBUG_NHT_DETAILED (zebra_debug_nht & ZEBRA_DEBUG_NHT_DETAILED) #define IS_ZEBRA_DEBUG_MPLS (zebra_debug_mpls & ZEBRA_DEBUG_MPLS) #define IS_ZEBRA_DEBUG_VXLAN (zebra_debug_vxlan & ZEBRA_DEBUG_VXLAN) #define IS_ZEBRA_DEBUG_PW (zebra_debug_pw & ZEBRA_DEBUG_PW) #define IS_ZEBRA_DEBUG_DPLANE (zebra_debug_dplane & ZEBRA_DEBUG_DPLANE) #define IS_ZEBRA_DEBUG_DPLANE_DETAIL \ (zebra_debug_dplane & ZEBRA_DEBUG_DPLANE_DETAILED) #define IS_ZEBRA_DEBUG_MLAG (zebra_debug_mlag & ZEBRA_DEBUG_MLAG) extern unsigned long zebra_debug_event; extern unsigned long zebra_debug_packet; extern unsigned long zebra_debug_kernel; extern unsigned long zebra_debug_rib; extern unsigned long zebra_debug_fpm; extern unsigned long zebra_debug_nht; extern unsigned long zebra_debug_mpls; extern unsigned long zebra_debug_vxlan; extern unsigned long zebra_debug_pw; extern unsigned long zebra_debug_dplane; extern unsigned long zebra_debug_mlag; extern void zebra_debug_init(void); DECLARE_HOOK(zebra_debug_show_debugging, (struct vty *vty), (vty)); #ifdef __cplusplus } #endif #endif /* _ZEBRA_DEBUG_H */ frr-7.2.1/zebra/if_ioctl.c0000644000000000000000000001641513610377563012262 00000000000000/* * Interface looking up by ioctl (). * Copyright (C) 1997, 98 Kunihiro Ishiguro * * This file is part of GNU Zebra. * * GNU Zebra is free software; you can redistribute it and/or modify it * under the terms of the GNU General Public License as published by the * Free Software Foundation; either version 2, or (at your option) any * later version. * * GNU Zebra 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; see the file COPYING; if not, write to the Free Software * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA */ #include #ifdef OPEN_BSD #include "if.h" #include "sockunion.h" #include "prefix.h" #include "ioctl.h" #include "connected.h" #include "memory.h" #include "zebra_memory.h" #include "log.h" #include "vrf.h" #include "vty.h" #include "lib_errors.h" #include "zebra/interface.h" #include "zebra/rib.h" #include "zebra/rt.h" #include "zebra/zebra_errors.h" #include /* Interface looking up using infamous SIOCGIFCONF. */ static int interface_list_ioctl(void) { int ret; int sock; #define IFNUM_BASE 32 int ifnum; struct ifreq *ifreq; struct ifconf ifconf; struct interface *ifp; int n; int lastlen; /* Normally SIOCGIFCONF works with AF_INET socket. */ sock = socket(AF_INET, SOCK_DGRAM, 0); if (sock < 0) { flog_err_sys(EC_LIB_SOCKET, "Can't make AF_INET socket stream: %s", safe_strerror(errno)); return -1; } /* Set initial ifreq count. This will be double when SIOCGIFCONF fail. Solaris has SIOCGIFNUM. */ #ifdef SIOCGIFNUM ret = ioctl(sock, SIOCGIFNUM, &ifnum); if (ret < 0) ifnum = IFNUM_BASE; else ifnum++; #else ifnum = IFNUM_BASE; #endif /* SIOCGIFNUM */ ifconf.ifc_buf = NULL; lastlen = 0; /* Loop until SIOCGIFCONF success. */ for (;;) { ifconf.ifc_len = sizeof(struct ifreq) * ifnum; ifconf.ifc_buf = XREALLOC(MTYPE_TMP, ifconf.ifc_buf, ifconf.ifc_len); ret = ioctl(sock, SIOCGIFCONF, &ifconf); if (ret < 0) { flog_err_sys(EC_LIB_SYSTEM_CALL, "SIOCGIFCONF: %s", safe_strerror(errno)); goto end; } /* Repeatedly get info til buffer fails to grow. */ if (ifconf.ifc_len > lastlen) { lastlen = ifconf.ifc_len; ifnum += 10; continue; } /* Success. */ break; } /* Allocate interface. */ ifreq = ifconf.ifc_req; #ifdef OPEN_BSD for (n = 0; n < ifconf.ifc_len;) { unsigned int size; ifreq = (struct ifreq *)((caddr_t)ifconf.ifc_req + n); ifp = if_get_by_name(ifreq->ifr_name, VRF_DEFAULT); if_add_update(ifp); size = ifreq->ifr_addr.sa_len; if (size < sizeof(ifreq->ifr_addr)) size = sizeof(ifreq->ifr_addr); size += sizeof(ifreq->ifr_name); n += size; } #else for (n = 0; n < ifconf.ifc_len; n += sizeof(struct ifreq)) { ifp = if_get_by_name(ifreq->ifr_name, VRF_DEFAULT); if_add_update(ifp); ifreq++; } #endif /* OPEN_BSD */ end: close(sock); XFREE(MTYPE_TMP, ifconf.ifc_buf); return ret; } /* Get interface's index by ioctl. */ static int if_get_index(struct interface *ifp) { if_set_index(ifp, if_nametoindex(ifp->name)); return ifp->ifindex; } #ifdef SIOCGIFHWADDR static int if_get_hwaddr(struct interface *ifp) { int ret; struct ifreq ifreq; int i; strlcpy(ifreq.ifr_name, ifp->name, sizeof(ifreq.ifr_name)); ifreq.ifr_addr.sa_family = AF_INET; /* Fetch Hardware address if available. */ ret = vrf_if_ioctl(SIOCGIFHWADDR, (caddr_t)&ifreq, ifp->vrf_id); if (ret < 0) ifp->hw_addr_len = 0; else { memcpy(ifp->hw_addr, ifreq.ifr_hwaddr.sa_data, 6); for (i = 0; i < 6; i++) if (ifp->hw_addr[i] != 0) break; if (i == 6) ifp->hw_addr_len = 0; else ifp->hw_addr_len = 6; } return 0; } #endif /* SIOCGIFHWADDR */ static int if_getaddrs(void) { int ret; struct ifaddrs *ifap; struct ifaddrs *ifapfree; struct interface *ifp; int prefixlen; ret = getifaddrs(&ifap); if (ret != 0) { flog_err_sys(EC_LIB_SYSTEM_CALL, "getifaddrs(): %s", safe_strerror(errno)); return -1; } for (ifapfree = ifap; ifap; ifap = ifap->ifa_next) { if (ifap->ifa_addr == NULL) { flog_err( EC_LIB_INTERFACE, "%s: nonsensical ifaddr with NULL ifa_addr, ifname %s", __func__, (ifap->ifa_name ? ifap->ifa_name : "(null)")); continue; } ifp = if_lookup_by_name(ifap->ifa_name, VRF_DEFAULT); if (ifp == NULL) { flog_err(EC_LIB_INTERFACE, "if_getaddrs(): Can't lookup interface %s\n", ifap->ifa_name); continue; } if (ifap->ifa_addr->sa_family == AF_INET) { struct sockaddr_in *addr; struct sockaddr_in *mask; struct sockaddr_in *dest; struct in_addr *dest_pnt; int flags = 0; addr = (struct sockaddr_in *)ifap->ifa_addr; mask = (struct sockaddr_in *)ifap->ifa_netmask; prefixlen = ip_masklen(mask->sin_addr); dest_pnt = NULL; if (if_is_pointopoint(ifp) && ifap->ifa_dstaddr && !IPV4_ADDR_SAME(&addr->sin_addr, &((struct sockaddr_in *) ifap->ifa_dstaddr) ->sin_addr)) { dest = (struct sockaddr_in *)ifap->ifa_dstaddr; dest_pnt = &dest->sin_addr; flags = ZEBRA_IFA_PEER; } else if (ifap->ifa_broadaddr && !IPV4_ADDR_SAME( &addr->sin_addr, &((struct sockaddr_in *) ifap->ifa_broadaddr) ->sin_addr)) { dest = (struct sockaddr_in *) ifap->ifa_broadaddr; dest_pnt = &dest->sin_addr; } connected_add_ipv4(ifp, flags, &addr->sin_addr, prefixlen, dest_pnt, NULL, METRIC_MAX); } if (ifap->ifa_addr->sa_family == AF_INET6) { struct sockaddr_in6 *addr; struct sockaddr_in6 *mask; int flags = 0; addr = (struct sockaddr_in6 *)ifap->ifa_addr; mask = (struct sockaddr_in6 *)ifap->ifa_netmask; prefixlen = ip6_masklen(mask->sin6_addr); #if defined(KAME) if (IN6_IS_ADDR_LINKLOCAL(&addr->sin6_addr)) { addr->sin6_scope_id = ntohs(*(uint16_t *)&addr->sin6_addr .s6_addr[2]); addr->sin6_addr.s6_addr[2] = addr->sin6_addr.s6_addr[3] = 0; } #endif connected_add_ipv6(ifp, flags, &addr->sin6_addr, NULL, prefixlen, NULL, METRIC_MAX); } } freeifaddrs(ifapfree); return 0; } /* Fetch interface information via ioctl(). */ static void interface_info_ioctl() { struct vrf *vrf = vrf_lookup_by_id(VRF_DEFAULT); struct interface *ifp; FOR_ALL_INTERFACES (vrf, ifp) { if_get_index(ifp); #ifdef SIOCGIFHWADDR if_get_hwaddr(ifp); #endif /* SIOCGIFHWADDR */ if_get_flags(ifp); if_get_mtu(ifp); if_get_metric(ifp); } } /* Lookup all interface information. */ void interface_list(struct zebra_ns *zns) { zlog_info("interface_list: NS %u", zns->ns_id); /* Linux can do both proc & ioctl, ioctl is the only way to get interface aliases in 2.2 series kernels. */ #ifdef HAVE_PROC_NET_DEV interface_list_proc(); #endif /* HAVE_PROC_NET_DEV */ interface_list_ioctl(); /* After listing is done, get index, address, flags and other interface's information. */ interface_info_ioctl(); if_getaddrs(); #if defined(HAVE_PROC_NET_IF_INET6) /* Linux provides interface's IPv6 address via /proc/net/if_inet6. */ ifaddr_proc_ipv6(); #endif /* HAVE_PROC_NET_IF_INET6 */ } #endif /* OPEN_BSD */ frr-7.2.1/zebra/if_ioctl_solaris.c0000644000000000000000000002166013610377563014014 00000000000000/* * Interface looking up by ioctl () on Solaris. * Copyright (C) 1997, 98 Kunihiro Ishiguro * * This file is part of GNU Zebra. * * GNU Zebra is free software; you can redistribute it and/or modify it * under the terms of the GNU General Public License as published by the * Free Software Foundation; either version 2, or (at your option) any * later version. * * GNU Zebra 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; see the file COPYING; if not, write to the Free Software * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA */ #include #ifdef SUNOS_5 #include "if.h" #include "sockunion.h" #include "prefix.h" #include "ioctl.h" #include "connected.h" #include "memory.h" #include "zebra_memory.h" #include "log.h" #include "privs.h" #include "vrf.h" #include "vty.h" #include "lib_errors.h" #include "zebra/interface.h" #include "zebra/ioctl_solaris.h" #include "zebra/rib.h" #include "zebra/rt.h" #include "zebra/zebra_errors.h" static int if_get_addr(struct interface *, struct sockaddr *, const char *); static void interface_info_ioctl(struct interface *); extern struct zebra_privs_t zserv_privs; static int interface_list_ioctl(int af) { int ret; int sock; #define IFNUM_BASE 32 struct lifnum lifn; int ifnum; struct lifreq *lifreq; struct lifconf lifconf; struct interface *ifp; int n; size_t needed, lastneeded = 0; char *buf = NULL; frr_with_privs(&zserv_privs) { sock = socket(af, SOCK_DGRAM, 0); } if (sock < 0) { flog_err_sys(EC_LIB_SOCKET, "Can't make %s socket stream: %s", (af == AF_INET ? "AF_INET" : "AF_INET6"), safe_strerror(errno)); return -1; } calculate_lifc_len: frr_with_privs(&zserv_privs) { lifn.lifn_family = af; lifn.lifn_flags = LIFC_NOXMIT; /* we want NOXMIT interfaces too */ ret = ioctl(sock, SIOCGLIFNUM, &lifn); } if (ret < 0) { flog_err_sys(EC_LIB_SYSTEM_CALL, "interface_list_ioctl: SIOCGLIFNUM failed %s", safe_strerror(errno)); close(sock); return -1; } ifnum = lifn.lifn_count; /* * When calculating the buffer size needed, add a small number * of interfaces to those we counted. We do this to capture * the interface status of potential interfaces which may have * been plumbed between the SIOCGLIFNUM and the SIOCGLIFCONF. */ needed = (ifnum + 4) * sizeof(struct lifreq); if (needed > lastneeded || needed < lastneeded / 2) { if (buf != NULL) XFREE(MTYPE_TMP, buf); buf = XMALLOC(MTYPE_TMP, needed); } lastneeded = needed; lifconf.lifc_family = af; lifconf.lifc_flags = LIFC_NOXMIT; lifconf.lifc_len = needed; lifconf.lifc_buf = buf; frr_with_privs(&zserv_privs) { ret = ioctl(sock, SIOCGLIFCONF, &lifconf); } if (ret < 0) { if (errno == EINVAL) goto calculate_lifc_len; flog_err_sys(EC_LIB_SYSTEM_CALL, "SIOCGLIFCONF: %s", safe_strerror(errno)); goto end; } /* Allocate interface. */ lifreq = lifconf.lifc_req; for (n = 0; n < lifconf.lifc_len; n += sizeof(struct lifreq)) { /* we treat Solaris logical interfaces as addresses, because * that is * how PF_ROUTE on Solaris treats them. Hence we can not * directly use * the lifreq_name to get the ifp. We need to normalise the * name * before attempting get. * * Solaris logical interface names are in the form of: * : */ unsigned int normallen = 0; uint64_t lifflags; /* We should exclude ~IFF_UP interfaces, as we'll find out about * them * coming up later through RTM_NEWADDR message on the route * socket. */ if (if_get_flags_direct(lifreq->lifr_name, &lifflags, lifreq->lifr_addr.ss_family) || !CHECK_FLAG(lifflags, IFF_UP)) { lifreq++; continue; } /* Find the normalised name */ while ((normallen < sizeof(lifreq->lifr_name)) && (*(lifreq->lifr_name + normallen) != '\0') && (*(lifreq->lifr_name + normallen) != ':')) normallen++; ifp = if_get_by_name(lifreq->lifr_name, VRF_DEFAULT); if (lifreq->lifr_addr.ss_family == AF_INET) ifp->flags |= IFF_IPV4; if (lifreq->lifr_addr.ss_family == AF_INET6) { ifp->flags |= IFF_IPV6; } if_add_update(ifp); interface_info_ioctl(ifp); /* If a logical interface pass the full name so it can be * as a label on the address */ if (*(lifreq->lifr_name + normallen) != '\0') if_get_addr(ifp, (struct sockaddr *)&lifreq->lifr_addr, lifreq->lifr_name); else if_get_addr(ifp, (struct sockaddr *)&lifreq->lifr_addr, NULL); /* Poke the interface flags. Lets IFF_UP mangling kick in */ if_flags_update(ifp, ifp->flags); lifreq++; } end: close(sock); XFREE(MTYPE_TMP, lifconf.lifc_buf); return ret; } /* Get interface's index by ioctl. */ static int if_get_index(struct interface *ifp) { int ret; struct lifreq lifreq; lifreq_set_name(&lifreq, ifp->name); if (ifp->flags & IFF_IPV4) ret = AF_IOCTL(AF_INET, SIOCGLIFINDEX, (caddr_t)&lifreq); else if (ifp->flags & IFF_IPV6) ret = AF_IOCTL(AF_INET6, SIOCGLIFINDEX, (caddr_t)&lifreq); else ret = -1; if (ret < 0) { flog_err_sys(EC_LIB_SYSTEM_CALL, "SIOCGLIFINDEX(%s) failed", ifp->name); return ret; } /* OK we got interface index. */ #ifdef ifr_ifindex if_set_index(ifp, lifreq.lifr_ifindex); #else if_set_index(ifp, lifreq.lifr_index); #endif return ifp->ifindex; } /* Interface address lookup by ioctl. This function only looks up IPv4 address. */ #define ADDRLEN(sa) \ (((sa)->sa_family == AF_INET ? sizeof(struct sockaddr_in) \ : sizeof(struct sockaddr_in6))) #define SIN(s) ((struct sockaddr_in *)(s)) #define SIN6(s) ((struct sockaddr_in6 *)(s)) /* Retrieve address information for the given ifp */ static int if_get_addr(struct interface *ifp, struct sockaddr *addr, const char *label) { int ret; struct lifreq lifreq; struct sockaddr_storage mask, dest; char *dest_pnt = NULL; uint8_t prefixlen = 0; afi_t af; int flags = 0; /* Interface's name and address family. * We need to use the logical interface name / label, if we've been * given one, in order to get the right address */ strlcpy(lifreq.lifr_name, (label ? label : ifp->name), sizeof(lifreq.lifr_name)); /* Interface's address. */ memcpy(&lifreq.lifr_addr, addr, ADDRLEN(addr)); af = addr->sa_family; /* Point to point or broad cast address pointer init. */ dest_pnt = NULL; if (AF_IOCTL(af, SIOCGLIFDSTADDR, (caddr_t)&lifreq) >= 0) { memcpy(&dest, &lifreq.lifr_dstaddr, ADDRLEN(addr)); if (af == AF_INET) dest_pnt = (char *)&(SIN(&dest)->sin_addr); else dest_pnt = (char *)&(SIN6(&dest)->sin6_addr); flags = ZEBRA_IFA_PEER; } if (af == AF_INET) { ret = if_ioctl(SIOCGLIFNETMASK, (caddr_t)&lifreq); if (ret < 0) { if (errno != EADDRNOTAVAIL) { flog_err_sys(EC_LIB_SYSTEM_CALL, "SIOCGLIFNETMASK (%s) fail: %s", ifp->name, safe_strerror(errno)); return ret; } return 0; } memcpy(&mask, &lifreq.lifr_addr, ADDRLEN(addr)); prefixlen = ip_masklen(SIN(&mask)->sin_addr); if (!dest_pnt && (if_ioctl(SIOCGLIFBRDADDR, (caddr_t)&lifreq) >= 0)) { memcpy(&dest, &lifreq.lifr_broadaddr, sizeof(struct sockaddr_in)); dest_pnt = (char *)&SIN(&dest)->sin_addr; } } else if (af == AF_INET6) { if (if_ioctl_ipv6(SIOCGLIFSUBNET, (caddr_t)&lifreq) < 0) { if (ifp->flags & IFF_POINTOPOINT) prefixlen = IPV6_MAX_BITLEN; else flog_err_sys(EC_LIB_SYSTEM_CALL, "SIOCGLIFSUBNET (%s) fail: %s", ifp->name, safe_strerror(errno)); } else { prefixlen = lifreq.lifr_addrlen; } } /* Set address to the interface. */ if (af == AF_INET) connected_add_ipv4(ifp, flags, &SIN(addr)->sin_addr, prefixlen, (struct in_addr *)dest_pnt, label, METRIC_MAX); else if (af == AF_INET6) connected_add_ipv6(ifp, flags, &SIN6(addr)->sin6_addr, NULL, prefixlen, label, METRIC_MAX); return 0; } /* Fetch interface information via ioctl(). */ static void interface_info_ioctl(struct interface *ifp) { if_get_index(ifp); if_get_flags(ifp); if_get_mtu(ifp); if_get_metric(ifp); } /* Lookup all interface information. */ void interface_list(struct zebra_ns *zns) { if (zns->ns_id != NS_DEFAULT) { zlog_debug("interface_list: ignore NS %u", zns->ns_id); return; } interface_list_ioctl(AF_INET); interface_list_ioctl(AF_INET6); interface_list_ioctl(AF_UNSPEC); } struct connected *if_lookup_linklocal(struct interface *ifp) { struct listnode *node; struct connected *ifc; if (ifp == NULL) return NULL; for (ALL_LIST_ELEMENTS_RO(ifp->connected, node, ifc)) { if ((ifc->address->family == AF_INET6) && (IN6_IS_ADDR_LINKLOCAL(&ifc->address->u.prefix6))) return ifc; } return NULL; } #endif /* SUNOS_5 */ frr-7.2.1/zebra/if_netlink.c0000644000000000000000000011612113610377563012607 00000000000000/* * Interface looking up by netlink. * Copyright (C) 1998 Kunihiro Ishiguro * * This file is part of GNU Zebra. * * GNU Zebra is free software; you can redistribute it and/or modify it * under the terms of the GNU General Public License as published by the * Free Software Foundation; either version 2, or (at your option) any * later version. * * GNU Zebra 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; see the file COPYING; if not, write to the Free Software * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA */ #include #ifdef GNU_LINUX /* The following definition is to workaround an issue in the Linux kernel * header files with redefinition of 'struct in6_addr' in both * netinet/in.h and linux/in6.h. * Reference - https://sourceware.org/ml/libc-alpha/2013-01/msg00599.html */ #define _LINUX_IN6_H #include #include #include #include #include #include #include "linklist.h" #include "if.h" #include "log.h" #include "prefix.h" #include "connected.h" #include "table.h" #include "memory.h" #include "zebra_memory.h" #include "rib.h" #include "thread.h" #include "privs.h" #include "nexthop.h" #include "vrf.h" #include "vrf_int.h" #include "mpls.h" #include "lib_errors.h" #include "vty.h" #include "zebra/zserv.h" #include "zebra/zebra_ns.h" #include "zebra/zebra_vrf.h" #include "zebra/rt.h" #include "zebra/redistribute.h" #include "zebra/interface.h" #include "zebra/debug.h" #include "zebra/rtadv.h" #include "zebra/zebra_ptm.h" #include "zebra/zebra_mpls.h" #include "zebra/kernel_netlink.h" #include "zebra/if_netlink.h" #include "zebra/zebra_errors.h" #include "zebra/zebra_vxlan.h" extern struct zebra_privs_t zserv_privs; /* Note: on netlink systems, there should be a 1-to-1 mapping between interface names and ifindex values. */ static void set_ifindex(struct interface *ifp, ifindex_t ifi_index, struct zebra_ns *zns) { struct interface *oifp; if (((oifp = if_lookup_by_index_per_ns(zns, ifi_index)) != NULL) && (oifp != ifp)) { if (ifi_index == IFINDEX_INTERNAL) flog_err( EC_LIB_INTERFACE, "Netlink is setting interface %s ifindex to reserved internal value %u", ifp->name, ifi_index); else { if (IS_ZEBRA_DEBUG_KERNEL) zlog_debug( "interface index %d was renamed from %s to %s", ifi_index, oifp->name, ifp->name); if (if_is_up(oifp)) flog_err( EC_LIB_INTERFACE, "interface rename detected on up interface: index %d was renamed from %s to %s, results are uncertain!", ifi_index, oifp->name, ifp->name); if_delete_update(oifp); } } if_set_index(ifp, ifi_index); } /* Utility function to parse hardware link-layer address and update ifp */ static void netlink_interface_update_hw_addr(struct rtattr **tb, struct interface *ifp) { int i; if (tb[IFLA_ADDRESS]) { int hw_addr_len; hw_addr_len = RTA_PAYLOAD(tb[IFLA_ADDRESS]); if (hw_addr_len > INTERFACE_HWADDR_MAX) zlog_debug("Hardware address is too large: %d", hw_addr_len); else { ifp->hw_addr_len = hw_addr_len; memcpy(ifp->hw_addr, RTA_DATA(tb[IFLA_ADDRESS]), hw_addr_len); for (i = 0; i < hw_addr_len; i++) if (ifp->hw_addr[i] != 0) break; if (i == hw_addr_len) ifp->hw_addr_len = 0; else ifp->hw_addr_len = hw_addr_len; } } } static enum zebra_link_type netlink_to_zebra_link_type(unsigned int hwt) { switch (hwt) { case ARPHRD_ETHER: return ZEBRA_LLT_ETHER; case ARPHRD_EETHER: return ZEBRA_LLT_EETHER; case ARPHRD_AX25: return ZEBRA_LLT_AX25; case ARPHRD_PRONET: return ZEBRA_LLT_PRONET; case ARPHRD_IEEE802: return ZEBRA_LLT_IEEE802; case ARPHRD_ARCNET: return ZEBRA_LLT_ARCNET; case ARPHRD_APPLETLK: return ZEBRA_LLT_APPLETLK; case ARPHRD_DLCI: return ZEBRA_LLT_DLCI; case ARPHRD_ATM: return ZEBRA_LLT_ATM; case ARPHRD_METRICOM: return ZEBRA_LLT_METRICOM; case ARPHRD_IEEE1394: return ZEBRA_LLT_IEEE1394; case ARPHRD_EUI64: return ZEBRA_LLT_EUI64; case ARPHRD_INFINIBAND: return ZEBRA_LLT_INFINIBAND; case ARPHRD_SLIP: return ZEBRA_LLT_SLIP; case ARPHRD_CSLIP: return ZEBRA_LLT_CSLIP; case ARPHRD_SLIP6: return ZEBRA_LLT_SLIP6; case ARPHRD_CSLIP6: return ZEBRA_LLT_CSLIP6; case ARPHRD_RSRVD: return ZEBRA_LLT_RSRVD; case ARPHRD_ADAPT: return ZEBRA_LLT_ADAPT; case ARPHRD_ROSE: return ZEBRA_LLT_ROSE; case ARPHRD_X25: return ZEBRA_LLT_X25; case ARPHRD_PPP: return ZEBRA_LLT_PPP; case ARPHRD_CISCO: return ZEBRA_LLT_CHDLC; case ARPHRD_LAPB: return ZEBRA_LLT_LAPB; case ARPHRD_RAWHDLC: return ZEBRA_LLT_RAWHDLC; case ARPHRD_TUNNEL: return ZEBRA_LLT_IPIP; case ARPHRD_TUNNEL6: return ZEBRA_LLT_IPIP6; case ARPHRD_FRAD: return ZEBRA_LLT_FRAD; case ARPHRD_SKIP: return ZEBRA_LLT_SKIP; case ARPHRD_LOOPBACK: return ZEBRA_LLT_LOOPBACK; case ARPHRD_LOCALTLK: return ZEBRA_LLT_LOCALTLK; case ARPHRD_FDDI: return ZEBRA_LLT_FDDI; case ARPHRD_SIT: return ZEBRA_LLT_SIT; case ARPHRD_IPDDP: return ZEBRA_LLT_IPDDP; case ARPHRD_IPGRE: return ZEBRA_LLT_IPGRE; case ARPHRD_PIMREG: return ZEBRA_LLT_PIMREG; case ARPHRD_HIPPI: return ZEBRA_LLT_HIPPI; case ARPHRD_ECONET: return ZEBRA_LLT_ECONET; case ARPHRD_IRDA: return ZEBRA_LLT_IRDA; case ARPHRD_FCPP: return ZEBRA_LLT_FCPP; case ARPHRD_FCAL: return ZEBRA_LLT_FCAL; case ARPHRD_FCPL: return ZEBRA_LLT_FCPL; case ARPHRD_FCFABRIC: return ZEBRA_LLT_FCFABRIC; case ARPHRD_IEEE802_TR: return ZEBRA_LLT_IEEE802_TR; case ARPHRD_IEEE80211: return ZEBRA_LLT_IEEE80211; #ifdef ARPHRD_IEEE802154 case ARPHRD_IEEE802154: return ZEBRA_LLT_IEEE802154; #endif #ifdef ARPHRD_IP6GRE case ARPHRD_IP6GRE: return ZEBRA_LLT_IP6GRE; #endif #ifdef ARPHRD_IEEE802154_PHY case ARPHRD_IEEE802154_PHY: return ZEBRA_LLT_IEEE802154_PHY; #endif default: return ZEBRA_LLT_UNKNOWN; } } static void netlink_determine_zebra_iftype(const char *kind, zebra_iftype_t *zif_type) { *zif_type = ZEBRA_IF_OTHER; if (!kind) return; if (strcmp(kind, "vrf") == 0) *zif_type = ZEBRA_IF_VRF; else if (strcmp(kind, "bridge") == 0) *zif_type = ZEBRA_IF_BRIDGE; else if (strcmp(kind, "vlan") == 0) *zif_type = ZEBRA_IF_VLAN; else if (strcmp(kind, "vxlan") == 0) *zif_type = ZEBRA_IF_VXLAN; else if (strcmp(kind, "macvlan") == 0) *zif_type = ZEBRA_IF_MACVLAN; else if (strcmp(kind, "veth") == 0) *zif_type = ZEBRA_IF_VETH; else if (strcmp(kind, "bond") == 0) *zif_type = ZEBRA_IF_BOND; else if (strcmp(kind, "bond_slave") == 0) *zif_type = ZEBRA_IF_BOND_SLAVE; } #define parse_rtattr_nested(tb, max, rta) \ netlink_parse_rtattr((tb), (max), RTA_DATA(rta), RTA_PAYLOAD(rta)) static void netlink_vrf_change(struct nlmsghdr *h, struct rtattr *tb, const char *name) { struct ifinfomsg *ifi; struct rtattr *linkinfo[IFLA_INFO_MAX + 1]; struct rtattr *attr[IFLA_VRF_MAX + 1]; struct vrf *vrf; struct zebra_vrf *zvrf; uint32_t nl_table_id; ifi = NLMSG_DATA(h); memset(linkinfo, 0, sizeof linkinfo); parse_rtattr_nested(linkinfo, IFLA_INFO_MAX, tb); if (!linkinfo[IFLA_INFO_DATA]) { if (IS_ZEBRA_DEBUG_KERNEL) zlog_debug( "%s: IFLA_INFO_DATA missing from VRF message: %s", __func__, name); return; } memset(attr, 0, sizeof attr); parse_rtattr_nested(attr, IFLA_VRF_MAX, linkinfo[IFLA_INFO_DATA]); if (!attr[IFLA_VRF_TABLE]) { if (IS_ZEBRA_DEBUG_KERNEL) zlog_debug( "%s: IFLA_VRF_TABLE missing from VRF message: %s", __func__, name); return; } nl_table_id = *(uint32_t *)RTA_DATA(attr[IFLA_VRF_TABLE]); if (h->nlmsg_type == RTM_NEWLINK) { if (IS_ZEBRA_DEBUG_KERNEL) zlog_debug("RTM_NEWLINK for VRF %s(%u) table %u", name, ifi->ifi_index, nl_table_id); /* * vrf_get is implied creation if it does not exist */ vrf = vrf_get((vrf_id_t)ifi->ifi_index, name); // It would create vrf if (!vrf) { flog_err(EC_LIB_INTERFACE, "VRF %s id %u not created", name, ifi->ifi_index); return; } /* * This is the only place that we get the actual kernel table_id * being used. We need it to set the table_id of the routes * we are passing to the kernel.... And to throw some totally * awesome parties. that too. * * At this point we *must* have a zvrf because the vrf_create * callback creates one. We *must* set the table id * before the vrf_enable because of( at the very least ) * static routes being delayed for installation until * during the vrf_enable callbacks. */ zvrf = (struct zebra_vrf *)vrf->info; zvrf->table_id = nl_table_id; /* Enable the created VRF. */ if (!vrf_enable(vrf)) { flog_err(EC_LIB_INTERFACE, "Failed to enable VRF %s id %u", name, ifi->ifi_index); return; } } else // h->nlmsg_type == RTM_DELLINK { if (IS_ZEBRA_DEBUG_KERNEL) zlog_debug("RTM_DELLINK for VRF %s(%u)", name, ifi->ifi_index); vrf = vrf_lookup_by_id((vrf_id_t)ifi->ifi_index); if (!vrf) { flog_warn(EC_ZEBRA_VRF_NOT_FOUND, "%s: vrf not found", __func__); return; } vrf_delete(vrf); } } static uint32_t get_iflink_speed(struct interface *interface) { struct ifreq ifdata; struct ethtool_cmd ecmd; int sd; int rc; const char *ifname = interface->name; /* initialize struct */ memset(&ifdata, 0, sizeof(ifdata)); /* set interface name */ strlcpy(ifdata.ifr_name, ifname, sizeof(ifdata.ifr_name)); /* initialize ethtool interface */ memset(&ecmd, 0, sizeof(ecmd)); ecmd.cmd = ETHTOOL_GSET; /* ETHTOOL_GLINK */ ifdata.ifr_data = (caddr_t)&ecmd; /* use ioctl to get IP address of an interface */ frr_with_privs(&zserv_privs) { sd = vrf_socket(PF_INET, SOCK_DGRAM, IPPROTO_IP, interface->vrf_id, NULL); if (sd < 0) { if (IS_ZEBRA_DEBUG_KERNEL) zlog_debug("Failure to read interface %s speed: %d %s", ifname, errno, safe_strerror(errno)); return 0; } /* Get the current link state for the interface */ rc = vrf_ioctl(interface->vrf_id, sd, SIOCETHTOOL, (char *)&ifdata); } if (rc < 0) { if (errno != EOPNOTSUPP && IS_ZEBRA_DEBUG_KERNEL) zlog_debug( "IOCTL failure to read interface %s speed: %d %s", ifname, errno, safe_strerror(errno)); ecmd.speed_hi = 0; ecmd.speed = 0; } close(sd); return ((uint32_t)ecmd.speed_hi << 16) | ecmd.speed; } uint32_t kernel_get_speed(struct interface *ifp) { return get_iflink_speed(ifp); } static int netlink_extract_bridge_info(struct rtattr *link_data, struct zebra_l2info_bridge *bridge_info) { struct rtattr *attr[IFLA_BR_MAX + 1]; memset(bridge_info, 0, sizeof(*bridge_info)); memset(attr, 0, sizeof attr); parse_rtattr_nested(attr, IFLA_BR_MAX, link_data); if (attr[IFLA_BR_VLAN_FILTERING]) bridge_info->vlan_aware = *(uint8_t *)RTA_DATA(attr[IFLA_BR_VLAN_FILTERING]); return 0; } static int netlink_extract_vlan_info(struct rtattr *link_data, struct zebra_l2info_vlan *vlan_info) { struct rtattr *attr[IFLA_VLAN_MAX + 1]; vlanid_t vid_in_msg; memset(vlan_info, 0, sizeof(*vlan_info)); memset(attr, 0, sizeof attr); parse_rtattr_nested(attr, IFLA_VLAN_MAX, link_data); if (!attr[IFLA_VLAN_ID]) { if (IS_ZEBRA_DEBUG_KERNEL) zlog_debug("IFLA_VLAN_ID missing from VLAN IF message"); return -1; } vid_in_msg = *(vlanid_t *)RTA_DATA(attr[IFLA_VLAN_ID]); vlan_info->vid = vid_in_msg; return 0; } static int netlink_extract_vxlan_info(struct rtattr *link_data, struct zebra_l2info_vxlan *vxl_info) { struct rtattr *attr[IFLA_VXLAN_MAX + 1]; vni_t vni_in_msg; struct in_addr vtep_ip_in_msg; memset(vxl_info, 0, sizeof(*vxl_info)); memset(attr, 0, sizeof attr); parse_rtattr_nested(attr, IFLA_VXLAN_MAX, link_data); if (!attr[IFLA_VXLAN_ID]) { if (IS_ZEBRA_DEBUG_KERNEL) zlog_debug( "IFLA_VXLAN_ID missing from VXLAN IF message"); return -1; } vni_in_msg = *(vni_t *)RTA_DATA(attr[IFLA_VXLAN_ID]); vxl_info->vni = vni_in_msg; if (!attr[IFLA_VXLAN_LOCAL]) { if (IS_ZEBRA_DEBUG_KERNEL) zlog_debug( "IFLA_VXLAN_LOCAL missing from VXLAN IF message"); } else { vtep_ip_in_msg = *(struct in_addr *)RTA_DATA(attr[IFLA_VXLAN_LOCAL]); vxl_info->vtep_ip = vtep_ip_in_msg; } if (attr[IFLA_VXLAN_GROUP]) { vxl_info->mcast_grp = *(struct in_addr *)RTA_DATA(attr[IFLA_VXLAN_GROUP]); } return 0; } /* * Extract and save L2 params (of interest) for an interface. When a * bridge interface is added or updated, take further actions to map * its members. Likewise, for VxLAN interface. */ static void netlink_interface_update_l2info(struct interface *ifp, struct rtattr *link_data, int add) { if (!link_data) return; if (IS_ZEBRA_IF_BRIDGE(ifp)) { struct zebra_l2info_bridge bridge_info; netlink_extract_bridge_info(link_data, &bridge_info); zebra_l2_bridge_add_update(ifp, &bridge_info, add); } else if (IS_ZEBRA_IF_VLAN(ifp)) { struct zebra_l2info_vlan vlan_info; netlink_extract_vlan_info(link_data, &vlan_info); zebra_l2_vlanif_update(ifp, &vlan_info); } else if (IS_ZEBRA_IF_VXLAN(ifp)) { struct zebra_l2info_vxlan vxlan_info; netlink_extract_vxlan_info(link_data, &vxlan_info); zebra_l2_vxlanif_add_update(ifp, &vxlan_info, add); } } static int netlink_bridge_interface(struct nlmsghdr *h, int len, ns_id_t ns_id, int startup) { char *name = NULL; struct ifinfomsg *ifi; struct rtattr *tb[IFLA_MAX + 1]; struct interface *ifp; struct rtattr *aftb[IFLA_BRIDGE_MAX + 1]; struct { uint16_t flags; uint16_t vid; } * vinfo; vlanid_t access_vlan; /* Fetch name and ifindex */ ifi = NLMSG_DATA(h); memset(tb, 0, sizeof tb); netlink_parse_rtattr(tb, IFLA_MAX, IFLA_RTA(ifi), len); if (tb[IFLA_IFNAME] == NULL) return -1; name = (char *)RTA_DATA(tb[IFLA_IFNAME]); /* The interface should already be known, if not discard. */ ifp = if_lookup_by_index_per_ns(zebra_ns_lookup(ns_id), ifi->ifi_index); if (!ifp) { zlog_debug("Cannot find bridge IF %s(%u)", name, ifi->ifi_index); return 0; } if (!IS_ZEBRA_IF_VXLAN(ifp)) return 0; /* We are only interested in the access VLAN i.e., AF_SPEC */ if (!tb[IFLA_AF_SPEC]) return 0; /* There is a 1-to-1 mapping of VLAN to VxLAN - hence * only 1 access VLAN is accepted. */ memset(aftb, 0, sizeof aftb); parse_rtattr_nested(aftb, IFLA_BRIDGE_MAX, tb[IFLA_AF_SPEC]); if (!aftb[IFLA_BRIDGE_VLAN_INFO]) return 0; vinfo = RTA_DATA(aftb[IFLA_BRIDGE_VLAN_INFO]); if (!(vinfo->flags & BRIDGE_VLAN_INFO_PVID)) return 0; access_vlan = (vlanid_t)vinfo->vid; if (IS_ZEBRA_DEBUG_KERNEL) zlog_debug("Access VLAN %u for VxLAN IF %s(%u)", access_vlan, name, ifi->ifi_index); zebra_l2_vxlanif_update_access_vlan(ifp, access_vlan); return 0; } /* * Called from interface_lookup_netlink(). This function is only used * during bootstrap. */ static int netlink_interface(struct nlmsghdr *h, ns_id_t ns_id, int startup) { int len; struct ifinfomsg *ifi; struct rtattr *tb[IFLA_MAX + 1]; struct rtattr *linkinfo[IFLA_MAX + 1]; struct interface *ifp; char *name = NULL; char *kind = NULL; char *desc = NULL; char *slave_kind = NULL; struct zebra_ns *zns = NULL; vrf_id_t vrf_id = VRF_DEFAULT; zebra_iftype_t zif_type = ZEBRA_IF_OTHER; zebra_slave_iftype_t zif_slave_type = ZEBRA_IF_SLAVE_NONE; ifindex_t bridge_ifindex = IFINDEX_INTERNAL; ifindex_t link_ifindex = IFINDEX_INTERNAL; ifindex_t bond_ifindex = IFINDEX_INTERNAL; struct zebra_if *zif; struct vrf *vrf = NULL; zns = zebra_ns_lookup(ns_id); ifi = NLMSG_DATA(h); if (h->nlmsg_type != RTM_NEWLINK) return 0; len = h->nlmsg_len - NLMSG_LENGTH(sizeof(struct ifinfomsg)); if (len < 0) { zlog_err("%s: Message received from netlink is of a broken size: %d %zu", __PRETTY_FUNCTION__, h->nlmsg_len, (size_t)NLMSG_LENGTH(sizeof(struct ifinfomsg))); return -1; } /* We are interested in some AF_BRIDGE notifications. */ if (ifi->ifi_family == AF_BRIDGE) return netlink_bridge_interface(h, len, ns_id, startup); /* Looking up interface name. */ memset(tb, 0, sizeof tb); memset(linkinfo, 0, sizeof linkinfo); netlink_parse_rtattr(tb, IFLA_MAX, IFLA_RTA(ifi), len); /* check for wireless messages to ignore */ if ((tb[IFLA_WIRELESS] != NULL) && (ifi->ifi_change == 0)) { if (IS_ZEBRA_DEBUG_KERNEL) zlog_debug("%s: ignoring IFLA_WIRELESS message", __func__); return 0; } if (tb[IFLA_IFNAME] == NULL) return -1; name = (char *)RTA_DATA(tb[IFLA_IFNAME]); if (tb[IFLA_IFALIAS]) desc = (char *)RTA_DATA(tb[IFLA_IFALIAS]); if (tb[IFLA_LINKINFO]) { parse_rtattr_nested(linkinfo, IFLA_INFO_MAX, tb[IFLA_LINKINFO]); if (linkinfo[IFLA_INFO_KIND]) kind = RTA_DATA(linkinfo[IFLA_INFO_KIND]); if (linkinfo[IFLA_INFO_SLAVE_KIND]) slave_kind = RTA_DATA(linkinfo[IFLA_INFO_SLAVE_KIND]); if ((slave_kind != NULL) && strcmp(slave_kind, "bond") == 0) netlink_determine_zebra_iftype("bond_slave", &zif_type); else netlink_determine_zebra_iftype(kind, &zif_type); } /* If VRF, create the VRF structure itself. */ if (zif_type == ZEBRA_IF_VRF && !vrf_is_backend_netns()) { netlink_vrf_change(h, tb[IFLA_LINKINFO], name); vrf_id = (vrf_id_t)ifi->ifi_index; } if (tb[IFLA_MASTER]) { if (slave_kind && (strcmp(slave_kind, "vrf") == 0) && !vrf_is_backend_netns()) { zif_slave_type = ZEBRA_IF_SLAVE_VRF; vrf_id = *(uint32_t *)RTA_DATA(tb[IFLA_MASTER]); } else if (slave_kind && (strcmp(slave_kind, "bridge") == 0)) { zif_slave_type = ZEBRA_IF_SLAVE_BRIDGE; bridge_ifindex = *(ifindex_t *)RTA_DATA(tb[IFLA_MASTER]); } else if (slave_kind && (strcmp(slave_kind, "bond") == 0)) { zif_slave_type = ZEBRA_IF_SLAVE_BOND; bond_ifindex = *(ifindex_t *)RTA_DATA(tb[IFLA_MASTER]); } else zif_slave_type = ZEBRA_IF_SLAVE_OTHER; } if (vrf_is_backend_netns()) vrf_id = (vrf_id_t)ns_id; /* If linking to another interface, note it. */ if (tb[IFLA_LINK]) link_ifindex = *(ifindex_t *)RTA_DATA(tb[IFLA_LINK]); vrf = vrf_get(vrf_id, NULL); /* Add interface. * We add by index first because in some cases such as the master * interface, we have the index before we have the name. Fixing * back references on the slave interfaces is painful if not done * this way, i.e. by creating by ifindex. */ ifp = if_get_by_ifindex(ifi->ifi_index, vrf_id); set_ifindex(ifp, ifi->ifi_index, zns); /* add it to ns struct */ strlcpy(ifp->name, name, sizeof(ifp->name)); IFNAME_RB_INSERT(vrf, ifp); ifp->flags = ifi->ifi_flags & 0x0000fffff; ifp->mtu6 = ifp->mtu = *(uint32_t *)RTA_DATA(tb[IFLA_MTU]); ifp->metric = 0; ifp->speed = get_iflink_speed(ifp); ifp->ptm_status = ZEBRA_PTM_STATUS_UNKNOWN; /* Set zebra interface type */ zebra_if_set_ziftype(ifp, zif_type, zif_slave_type); if (IS_ZEBRA_IF_VRF(ifp)) SET_FLAG(ifp->status, ZEBRA_INTERFACE_VRF_LOOPBACK); /* * Just set the @link/lower-device ifindex. During nldump interfaces are * not ordered in any fashion so we may end up getting upper devices * before lower devices. We will setup the real linkage once the dump * is complete. */ zif = (struct zebra_if *)ifp->info; zif->link_ifindex = link_ifindex; if (desc) { XFREE(MTYPE_TMP, zif->desc); zif->desc = XSTRDUP(MTYPE_TMP, desc); } /* Hardware type and address. */ ifp->ll_type = netlink_to_zebra_link_type(ifi->ifi_type); netlink_interface_update_hw_addr(tb, ifp); if_add_update(ifp); /* Extract and save L2 interface information, take additional actions. */ netlink_interface_update_l2info(ifp, linkinfo[IFLA_INFO_DATA], 1); if (IS_ZEBRA_IF_BRIDGE_SLAVE(ifp)) zebra_l2if_update_bridge_slave(ifp, bridge_ifindex); else if (IS_ZEBRA_IF_BOND_SLAVE(ifp)) zebra_l2if_update_bond_slave(ifp, bond_ifindex); return 0; } /* Request for specific interface or address information from the kernel */ static int netlink_request_intf_addr(struct nlsock *netlink_cmd, int family, int type, uint32_t filter_mask) { struct { struct nlmsghdr n; struct ifinfomsg ifm; char buf[256]; } req; /* Form the request, specifying filter (rtattr) if needed. */ memset(&req, 0, sizeof(req)); req.n.nlmsg_type = type; req.n.nlmsg_flags = NLM_F_ROOT | NLM_F_MATCH | NLM_F_REQUEST; req.n.nlmsg_len = NLMSG_LENGTH(sizeof(struct ifinfomsg)); req.ifm.ifi_family = family; /* Include filter, if specified. */ if (filter_mask) addattr32(&req.n, sizeof(req), IFLA_EXT_MASK, filter_mask); return netlink_request(netlink_cmd, &req.n); } /* Interface lookup by netlink socket. */ int interface_lookup_netlink(struct zebra_ns *zns) { int ret; struct zebra_dplane_info dp_info; struct nlsock *netlink_cmd = &zns->netlink_cmd; /* Capture key info from ns struct */ zebra_dplane_info_from_zns(&dp_info, zns, true /*is_cmd*/); /* Get interface information. */ ret = netlink_request_intf_addr(netlink_cmd, AF_PACKET, RTM_GETLINK, 0); if (ret < 0) return ret; ret = netlink_parse_info(netlink_interface, netlink_cmd, &dp_info, 0, 1); if (ret < 0) return ret; /* Get interface information - for bridge interfaces. */ ret = netlink_request_intf_addr(netlink_cmd, AF_BRIDGE, RTM_GETLINK, RTEXT_FILTER_BRVLAN); if (ret < 0) return ret; ret = netlink_parse_info(netlink_interface, netlink_cmd, &dp_info, 0, 0); if (ret < 0) return ret; /* Get interface information - for bridge interfaces. */ ret = netlink_request_intf_addr(netlink_cmd, AF_BRIDGE, RTM_GETLINK, RTEXT_FILTER_BRVLAN); if (ret < 0) return ret; ret = netlink_parse_info(netlink_interface, netlink_cmd, &dp_info, 0, 0); if (ret < 0) return ret; /* fixup linkages */ zebra_if_update_all_links(); /* Get IPv4 address of the interfaces. */ ret = netlink_request_intf_addr(netlink_cmd, AF_INET, RTM_GETADDR, 0); if (ret < 0) return ret; ret = netlink_parse_info(netlink_interface_addr, netlink_cmd, &dp_info, 0, 1); if (ret < 0) return ret; /* Get IPv6 address of the interfaces. */ ret = netlink_request_intf_addr(netlink_cmd, AF_INET6, RTM_GETADDR, 0); if (ret < 0) return ret; ret = netlink_parse_info(netlink_interface_addr, netlink_cmd, &dp_info, 0, 1); if (ret < 0) return ret; return 0; } int kernel_interface_set_master(struct interface *master, struct interface *slave) { struct zebra_ns *zns = zebra_ns_lookup(NS_DEFAULT); struct { struct nlmsghdr n; struct ifinfomsg ifa; char buf[NL_PKT_BUF_SIZE]; } req; memset(&req, 0, sizeof req); req.n.nlmsg_len = NLMSG_LENGTH(sizeof(struct ifinfomsg)); req.n.nlmsg_flags = NLM_F_REQUEST; req.n.nlmsg_type = RTM_SETLINK; req.n.nlmsg_pid = zns->netlink_cmd.snl.nl_pid; req.ifa.ifi_index = slave->ifindex; addattr_l(&req.n, sizeof req, IFLA_MASTER, &master->ifindex, 4); addattr_l(&req.n, sizeof req, IFLA_LINK, &slave->ifindex, 4); return netlink_talk(netlink_talk_filter, &req.n, &zns->netlink_cmd, zns, 0); } /* Interface address modification. */ static int netlink_address_ctx(const struct zebra_dplane_ctx *ctx) { int bytelen; const struct prefix *p; int cmd; const char *label; struct { struct nlmsghdr n; struct ifaddrmsg ifa; char buf[NL_PKT_BUF_SIZE]; } req; p = dplane_ctx_get_intf_addr(ctx); memset(&req, 0, sizeof(req) - NL_PKT_BUF_SIZE); bytelen = (p->family == AF_INET ? 4 : 16); req.n.nlmsg_len = NLMSG_LENGTH(sizeof(struct ifaddrmsg)); req.n.nlmsg_flags = NLM_F_REQUEST; if (dplane_ctx_get_op(ctx) == DPLANE_OP_ADDR_INSTALL) cmd = RTM_NEWADDR; else cmd = RTM_DELADDR; req.n.nlmsg_type = cmd; req.ifa.ifa_family = p->family; req.ifa.ifa_index = dplane_ctx_get_ifindex(ctx); addattr_l(&req.n, sizeof(req), IFA_LOCAL, &p->u.prefix, bytelen); if (p->family == AF_INET) { if (dplane_ctx_intf_is_connected(ctx)) { p = dplane_ctx_get_intf_dest(ctx); addattr_l(&req.n, sizeof(req), IFA_ADDRESS, &p->u.prefix, bytelen); } else if (cmd == RTM_NEWADDR) { struct in_addr broad = { .s_addr = ipv4_broadcast_addr(p->u.prefix4.s_addr, p->prefixlen) }; addattr_l(&req.n, sizeof(req), IFA_BROADCAST, &broad, bytelen); } } /* p is now either address or destination/bcast addr */ req.ifa.ifa_prefixlen = p->prefixlen; if (dplane_ctx_intf_is_secondary(ctx)) SET_FLAG(req.ifa.ifa_flags, IFA_F_SECONDARY); if (dplane_ctx_intf_has_label(ctx)) { label = dplane_ctx_get_intf_label(ctx); addattr_l(&req.n, sizeof(req), IFA_LABEL, label, strlen(label) + 1); } return netlink_talk_info(netlink_talk_filter, &req.n, dplane_ctx_get_ns(ctx), 0); } enum zebra_dplane_result kernel_address_update_ctx(struct zebra_dplane_ctx *ctx) { return (netlink_address_ctx(ctx) == 0 ? ZEBRA_DPLANE_REQUEST_SUCCESS : ZEBRA_DPLANE_REQUEST_FAILURE); } int netlink_interface_addr(struct nlmsghdr *h, ns_id_t ns_id, int startup) { int len; struct ifaddrmsg *ifa; struct rtattr *tb[IFA_MAX + 1]; struct interface *ifp; void *addr; void *broad; uint8_t flags = 0; char *label = NULL; struct zebra_ns *zns; uint32_t metric = METRIC_MAX; uint32_t kernel_flags = 0; zns = zebra_ns_lookup(ns_id); ifa = NLMSG_DATA(h); if (ifa->ifa_family != AF_INET && ifa->ifa_family != AF_INET6) { flog_warn( EC_ZEBRA_UNKNOWN_FAMILY, "Invalid address family: %u received from kernel interface addr change: %s", ifa->ifa_family, nl_msg_type_to_str(h->nlmsg_type)); return 0; } if (h->nlmsg_type != RTM_NEWADDR && h->nlmsg_type != RTM_DELADDR) return 0; len = h->nlmsg_len - NLMSG_LENGTH(sizeof(struct ifaddrmsg)); if (len < 0) { zlog_err("%s: Message received from netlink is of a broken size: %d %zu", __PRETTY_FUNCTION__, h->nlmsg_len, (size_t)NLMSG_LENGTH(sizeof(struct ifaddrmsg))); return -1; } memset(tb, 0, sizeof tb); netlink_parse_rtattr(tb, IFA_MAX, IFA_RTA(ifa), len); ifp = if_lookup_by_index_per_ns(zns, ifa->ifa_index); if (ifp == NULL) { flog_err( EC_LIB_INTERFACE, "netlink_interface_addr can't find interface by index %d", ifa->ifa_index); return -1; } /* Flags passed through */ if (tb[IFA_FLAGS]) kernel_flags = *(int *)RTA_DATA(tb[IFA_FLAGS]); else kernel_flags = ifa->ifa_flags; if (IS_ZEBRA_DEBUG_KERNEL) /* remove this line to see initial ifcfg */ { char buf[BUFSIZ]; zlog_debug("netlink_interface_addr %s %s flags 0x%x:", nl_msg_type_to_str(h->nlmsg_type), ifp->name, kernel_flags); if (tb[IFA_LOCAL]) zlog_debug(" IFA_LOCAL %s/%d", inet_ntop(ifa->ifa_family, RTA_DATA(tb[IFA_LOCAL]), buf, BUFSIZ), ifa->ifa_prefixlen); if (tb[IFA_ADDRESS]) zlog_debug(" IFA_ADDRESS %s/%d", inet_ntop(ifa->ifa_family, RTA_DATA(tb[IFA_ADDRESS]), buf, BUFSIZ), ifa->ifa_prefixlen); if (tb[IFA_BROADCAST]) zlog_debug(" IFA_BROADCAST %s/%d", inet_ntop(ifa->ifa_family, RTA_DATA(tb[IFA_BROADCAST]), buf, BUFSIZ), ifa->ifa_prefixlen); if (tb[IFA_LABEL] && strcmp(ifp->name, RTA_DATA(tb[IFA_LABEL]))) zlog_debug(" IFA_LABEL %s", (char *)RTA_DATA(tb[IFA_LABEL])); if (tb[IFA_CACHEINFO]) { struct ifa_cacheinfo *ci = RTA_DATA(tb[IFA_CACHEINFO]); zlog_debug(" IFA_CACHEINFO pref %d, valid %d", ci->ifa_prefered, ci->ifa_valid); } } /* logic copied from iproute2/ip/ipaddress.c:print_addrinfo() */ if (tb[IFA_LOCAL] == NULL) tb[IFA_LOCAL] = tb[IFA_ADDRESS]; if (tb[IFA_ADDRESS] == NULL) tb[IFA_ADDRESS] = tb[IFA_LOCAL]; /* local interface address */ addr = (tb[IFA_LOCAL] ? RTA_DATA(tb[IFA_LOCAL]) : NULL); /* is there a peer address? */ if (tb[IFA_ADDRESS] && memcmp(RTA_DATA(tb[IFA_ADDRESS]), RTA_DATA(tb[IFA_LOCAL]), RTA_PAYLOAD(tb[IFA_ADDRESS]))) { broad = RTA_DATA(tb[IFA_ADDRESS]); SET_FLAG(flags, ZEBRA_IFA_PEER); } else /* seeking a broadcast address */ broad = (tb[IFA_BROADCAST] ? RTA_DATA(tb[IFA_BROADCAST]) : NULL); /* addr is primary key, SOL if we don't have one */ if (addr == NULL) { zlog_debug("%s: NULL address", __func__); return -1; } /* Flags. */ if (kernel_flags & IFA_F_SECONDARY) SET_FLAG(flags, ZEBRA_IFA_SECONDARY); /* Label */ if (tb[IFA_LABEL]) label = (char *)RTA_DATA(tb[IFA_LABEL]); if (label && strcmp(ifp->name, label) == 0) label = NULL; if (tb[IFA_RT_PRIORITY]) metric = *(uint32_t *)RTA_DATA(tb[IFA_RT_PRIORITY]); /* Register interface address to the interface. */ if (ifa->ifa_family == AF_INET) { if (ifa->ifa_prefixlen > IPV4_MAX_BITLEN) { zlog_err( "Invalid prefix length: %u received from kernel interface addr change: %s", ifa->ifa_prefixlen, nl_msg_type_to_str(h->nlmsg_type)); return -1; } if (h->nlmsg_type == RTM_NEWADDR) connected_add_ipv4(ifp, flags, (struct in_addr *)addr, ifa->ifa_prefixlen, (struct in_addr *)broad, label, metric); else connected_delete_ipv4( ifp, flags, (struct in_addr *)addr, ifa->ifa_prefixlen, NULL); } if (ifa->ifa_family == AF_INET6) { if (ifa->ifa_prefixlen > IPV6_MAX_BITLEN) { zlog_err( "Invalid prefix length: %u received from kernel interface addr change: %s", ifa->ifa_prefixlen, nl_msg_type_to_str(h->nlmsg_type)); return -1; } if (h->nlmsg_type == RTM_NEWADDR) { /* Only consider valid addresses; we'll not get a * notification from * the kernel till IPv6 DAD has completed, but at init * time, Quagga * does query for and will receive all addresses. */ if (!(kernel_flags & (IFA_F_DADFAILED | IFA_F_TENTATIVE))) connected_add_ipv6(ifp, flags, (struct in6_addr *)addr, (struct in6_addr *)broad, ifa->ifa_prefixlen, label, metric); } else connected_delete_ipv6(ifp, (struct in6_addr *)addr, NULL, ifa->ifa_prefixlen); } return 0; } int netlink_link_change(struct nlmsghdr *h, ns_id_t ns_id, int startup) { int len; struct ifinfomsg *ifi; struct rtattr *tb[IFLA_MAX + 1]; struct rtattr *linkinfo[IFLA_MAX + 1]; struct interface *ifp; char *name = NULL; char *kind = NULL; char *desc = NULL; char *slave_kind = NULL; struct zebra_ns *zns; vrf_id_t vrf_id = VRF_DEFAULT; zebra_iftype_t zif_type = ZEBRA_IF_OTHER; zebra_slave_iftype_t zif_slave_type = ZEBRA_IF_SLAVE_NONE; ifindex_t bridge_ifindex = IFINDEX_INTERNAL; ifindex_t bond_ifindex = IFINDEX_INTERNAL; ifindex_t link_ifindex = IFINDEX_INTERNAL; uint8_t old_hw_addr[INTERFACE_HWADDR_MAX]; struct zebra_if *zif; zns = zebra_ns_lookup(ns_id); ifi = NLMSG_DATA(h); /* assume if not default zns, then new VRF */ if (!(h->nlmsg_type == RTM_NEWLINK || h->nlmsg_type == RTM_DELLINK)) { /* If this is not link add/delete message so print warning. */ zlog_debug("netlink_link_change: wrong kernel message %s", nl_msg_type_to_str(h->nlmsg_type)); return 0; } if (!(ifi->ifi_family == AF_UNSPEC || ifi->ifi_family == AF_BRIDGE || ifi->ifi_family == AF_INET6)) { flog_warn( EC_ZEBRA_UNKNOWN_FAMILY, "Invalid address family: %u received from kernel link change: %s", ifi->ifi_family, nl_msg_type_to_str(h->nlmsg_type)); return 0; } len = h->nlmsg_len - NLMSG_LENGTH(sizeof(struct ifinfomsg)); if (len < 0) { zlog_err("%s: Message received from netlink is of a broken size %d %zu", __PRETTY_FUNCTION__, h->nlmsg_len, (size_t)NLMSG_LENGTH(sizeof(struct ifinfomsg))); return -1; } /* We are interested in some AF_BRIDGE notifications. */ if (ifi->ifi_family == AF_BRIDGE) return netlink_bridge_interface(h, len, ns_id, startup); /* Looking up interface name. */ memset(tb, 0, sizeof tb); memset(linkinfo, 0, sizeof linkinfo); netlink_parse_rtattr(tb, IFLA_MAX, IFLA_RTA(ifi), len); /* check for wireless messages to ignore */ if ((tb[IFLA_WIRELESS] != NULL) && (ifi->ifi_change == 0)) { if (IS_ZEBRA_DEBUG_KERNEL) zlog_debug("%s: ignoring IFLA_WIRELESS message", __func__); return 0; } if (tb[IFLA_IFNAME] == NULL) return -1; name = (char *)RTA_DATA(tb[IFLA_IFNAME]); if (tb[IFLA_LINKINFO]) { parse_rtattr_nested(linkinfo, IFLA_INFO_MAX, tb[IFLA_LINKINFO]); if (linkinfo[IFLA_INFO_KIND]) kind = RTA_DATA(linkinfo[IFLA_INFO_KIND]); if (linkinfo[IFLA_INFO_SLAVE_KIND]) slave_kind = RTA_DATA(linkinfo[IFLA_INFO_SLAVE_KIND]); netlink_determine_zebra_iftype(kind, &zif_type); } /* If linking to another interface, note it. */ if (tb[IFLA_LINK]) link_ifindex = *(ifindex_t *)RTA_DATA(tb[IFLA_LINK]); if (tb[IFLA_IFALIAS]) { desc = (char *)RTA_DATA(tb[IFLA_IFALIAS]); } /* If VRF, create or update the VRF structure itself. */ if (zif_type == ZEBRA_IF_VRF && !vrf_is_backend_netns()) { netlink_vrf_change(h, tb[IFLA_LINKINFO], name); vrf_id = (vrf_id_t)ifi->ifi_index; } /* See if interface is present. */ ifp = if_lookup_by_name_per_ns(zns, name); if (h->nlmsg_type == RTM_NEWLINK) { if (tb[IFLA_MASTER]) { if (slave_kind && (strcmp(slave_kind, "vrf") == 0) && !vrf_is_backend_netns()) { zif_slave_type = ZEBRA_IF_SLAVE_VRF; vrf_id = *(uint32_t *)RTA_DATA(tb[IFLA_MASTER]); } else if (slave_kind && (strcmp(slave_kind, "bridge") == 0)) { zif_slave_type = ZEBRA_IF_SLAVE_BRIDGE; bridge_ifindex = *(ifindex_t *)RTA_DATA(tb[IFLA_MASTER]); } else if (slave_kind && (strcmp(slave_kind, "bond") == 0)) { zif_slave_type = ZEBRA_IF_SLAVE_BOND; bond_ifindex = *(ifindex_t *)RTA_DATA(tb[IFLA_MASTER]); } else zif_slave_type = ZEBRA_IF_SLAVE_OTHER; } if (vrf_is_backend_netns()) vrf_id = (vrf_id_t)ns_id; if (ifp == NULL || !CHECK_FLAG(ifp->status, ZEBRA_INTERFACE_ACTIVE)) { /* Add interface notification from kernel */ if (IS_ZEBRA_DEBUG_KERNEL) zlog_debug( "RTM_NEWLINK ADD for %s(%u) vrf_id %u type %d " "sl_type %d master %u flags 0x%x", name, ifi->ifi_index, vrf_id, zif_type, zif_slave_type, bridge_ifindex, ifi->ifi_flags); if (ifp == NULL) { /* unknown interface */ ifp = if_get_by_name(name, vrf_id); } else { /* pre-configured interface, learnt now */ if (ifp->vrf_id != vrf_id) if_update_to_new_vrf(ifp, vrf_id); } /* Update interface information. */ set_ifindex(ifp, ifi->ifi_index, zns); ifp->flags = ifi->ifi_flags & 0x0000fffff; if (!tb[IFLA_MTU]) { zlog_debug( "RTM_NEWLINK for interface %s(%u) without MTU set", name, ifi->ifi_index); return 0; } ifp->mtu6 = ifp->mtu = *(int *)RTA_DATA(tb[IFLA_MTU]); ifp->metric = 0; ifp->ptm_status = ZEBRA_PTM_STATUS_UNKNOWN; /* Set interface type */ zebra_if_set_ziftype(ifp, zif_type, zif_slave_type); if (IS_ZEBRA_IF_VRF(ifp)) SET_FLAG(ifp->status, ZEBRA_INTERFACE_VRF_LOOPBACK); /* Update link. */ zebra_if_update_link(ifp, link_ifindex, ns_id); netlink_interface_update_hw_addr(tb, ifp); /* Inform clients, install any configured addresses. */ if_add_update(ifp); /* Extract and save L2 interface information, take * additional actions. */ netlink_interface_update_l2info( ifp, linkinfo[IFLA_INFO_DATA], 1); if (IS_ZEBRA_IF_BRIDGE_SLAVE(ifp)) zebra_l2if_update_bridge_slave(ifp, bridge_ifindex); else if (IS_ZEBRA_IF_BOND_SLAVE(ifp)) zebra_l2if_update_bond_slave(ifp, bond_ifindex); } else if (ifp->vrf_id != vrf_id) { /* VRF change for an interface. */ if (IS_ZEBRA_DEBUG_KERNEL) zlog_debug( "RTM_NEWLINK vrf-change for %s(%u) " "vrf_id %u -> %u flags 0x%x", name, ifp->ifindex, ifp->vrf_id, vrf_id, ifi->ifi_flags); if_handle_vrf_change(ifp, vrf_id); } else { bool was_bridge_slave, was_bond_slave; /* Interface update. */ if (IS_ZEBRA_DEBUG_KERNEL) zlog_debug( "RTM_NEWLINK update for %s(%u) " "sl_type %d master %u flags 0x%x", name, ifp->ifindex, zif_slave_type, bridge_ifindex, ifi->ifi_flags); set_ifindex(ifp, ifi->ifi_index, zns); if (!tb[IFLA_MTU]) { zlog_debug( "RTM_NEWLINK for interface %s(%u) without MTU set", name, ifi->ifi_index); return 0; } ifp->mtu6 = ifp->mtu = *(int *)RTA_DATA(tb[IFLA_MTU]); ifp->metric = 0; /* Update interface type - NOTE: Only slave_type can * change. */ was_bridge_slave = IS_ZEBRA_IF_BRIDGE_SLAVE(ifp); was_bond_slave = IS_ZEBRA_IF_BOND_SLAVE(ifp); zebra_if_set_ziftype(ifp, zif_type, zif_slave_type); memcpy(old_hw_addr, ifp->hw_addr, INTERFACE_HWADDR_MAX); netlink_interface_update_hw_addr(tb, ifp); if (if_is_no_ptm_operative(ifp)) { ifp->flags = ifi->ifi_flags & 0x0000fffff; if (!if_is_no_ptm_operative(ifp)) { if (IS_ZEBRA_DEBUG_KERNEL) zlog_debug( "Intf %s(%u) has gone DOWN", name, ifp->ifindex); if_down(ifp); } else if (if_is_operative(ifp)) { /* Must notify client daemons of new * interface status. */ if (IS_ZEBRA_DEBUG_KERNEL) zlog_debug( "Intf %s(%u) PTM up, notifying clients", name, ifp->ifindex); zebra_interface_up_update(ifp); /* Update EVPN VNI when SVI MAC change */ if (IS_ZEBRA_IF_VLAN(ifp) && memcmp(old_hw_addr, ifp->hw_addr, INTERFACE_HWADDR_MAX)) { struct interface *link_if; link_if = if_lookup_by_index_per_ns( zebra_ns_lookup(NS_DEFAULT), link_ifindex); if (link_if) zebra_vxlan_svi_up(ifp, link_if); } } } else { ifp->flags = ifi->ifi_flags & 0x0000fffff; if (if_is_operative(ifp)) { if (IS_ZEBRA_DEBUG_KERNEL) zlog_debug( "Intf %s(%u) has come UP", name, ifp->ifindex); if_up(ifp); } else { if (IS_ZEBRA_DEBUG_KERNEL) zlog_debug( "Intf %s(%u) has gone DOWN", name, ifp->ifindex); if_down(ifp); } } /* Extract and save L2 interface information, take * additional actions. */ netlink_interface_update_l2info( ifp, linkinfo[IFLA_INFO_DATA], 0); if (IS_ZEBRA_IF_BRIDGE_SLAVE(ifp) || was_bridge_slave) zebra_l2if_update_bridge_slave(ifp, bridge_ifindex); else if (IS_ZEBRA_IF_BOND_SLAVE(ifp) || was_bond_slave) zebra_l2if_update_bond_slave(ifp, bond_ifindex); } } else { /* Delete interface notification from kernel */ if (ifp == NULL) { if (IS_ZEBRA_DEBUG_KERNEL) zlog_debug( "RTM_DELLINK for unknown interface %s(%u)", name, ifi->ifi_index); return 0; } if (IS_ZEBRA_DEBUG_KERNEL) zlog_debug("RTM_DELLINK for %s(%u)", name, ifp->ifindex); UNSET_FLAG(ifp->status, ZEBRA_INTERFACE_VRF_LOOPBACK); /* Special handling for bridge or VxLAN interfaces. */ if (IS_ZEBRA_IF_BRIDGE(ifp)) zebra_l2_bridge_del(ifp); else if (IS_ZEBRA_IF_VXLAN(ifp)) zebra_l2_vxlanif_del(ifp); if (!IS_ZEBRA_IF_VRF(ifp)) if_delete_update(ifp); } zif = ifp->info; if (zif) { XFREE(MTYPE_TMP, zif->desc); if (desc) zif->desc = XSTRDUP(MTYPE_TMP, desc); } return 0; } int netlink_protodown(struct interface *ifp, bool down) { struct zebra_ns *zns = zebra_ns_lookup(NS_DEFAULT); struct { struct nlmsghdr n; struct ifinfomsg ifa; char buf[NL_PKT_BUF_SIZE]; } req; memset(&req, 0, sizeof(req)); req.n.nlmsg_len = NLMSG_LENGTH(sizeof(struct ifinfomsg)); req.n.nlmsg_flags = NLM_F_REQUEST; req.n.nlmsg_type = RTM_SETLINK; req.n.nlmsg_pid = zns->netlink_cmd.snl.nl_pid; req.ifa.ifi_index = ifp->ifindex; addattr_l(&req.n, sizeof(req), IFLA_PROTO_DOWN, &down, sizeof(down)); addattr_l(&req.n, sizeof(req), IFLA_LINK, &ifp->ifindex, 4); return netlink_talk(netlink_talk_filter, &req.n, &zns->netlink_cmd, zns, 0); } /* Interface information read by netlink. */ void interface_list(struct zebra_ns *zns) { interface_lookup_netlink(zns); } #endif /* GNU_LINUX */ frr-7.2.1/zebra/if_netlink.h0000644000000000000000000000300313610377563012606 00000000000000/* Header file exported by if_netlink.c to zebra. * Copyright (C) 1997, 98, 99 Kunihiro Ishiguro * * This file is part of GNU Zebra. * * GNU Zebra is free software; you can redistribute it and/or modify it * under the terms of the GNU General Public License as published by the * Free Software Foundation; either version 2, or (at your option) any * later version. * * GNU Zebra 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; see the file COPYING; if not, write to the Free Software * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA */ #ifndef _ZEBRA_IF_NETLINK_H #define _ZEBRA_IF_NETLINK_H #ifdef HAVE_NETLINK #ifdef __cplusplus extern "C" { #endif extern int netlink_interface_addr(struct nlmsghdr *h, ns_id_t ns_id, int startup); extern int netlink_link_change(struct nlmsghdr *h, ns_id_t ns_id, int startup); extern int interface_lookup_netlink(struct zebra_ns *zns); /* * Set protodown status of interface. * * ifp * Interface to set protodown on. * * down * If true, set protodown on. If false, set protodown off. * * Returns: * 0 */ int netlink_protodown(struct interface *ifp, bool down); #ifdef __cplusplus } #endif #endif /* HAVE_NETLINK */ #endif /* _ZEBRA_IF_NETLINK_H */ frr-7.2.1/zebra/if_sysctl.c0000644000000000000000000000744013610377563012467 00000000000000/* * Get interface's address and mask information by sysctl() function. * Copyright (C) 1997, 98 Kunihiro Ishiguro * * This file is part of GNU Zebra. * * GNU Zebra is free software; you can redistribute it and/or modify it * under the terms of the GNU General Public License as published by the * Free Software Foundation; either version 2, or (at your option) any * later version. * * GNU Zebra 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; see the file COPYING; if not, write to the Free Software * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA */ #include #if !defined(GNU_LINUX) && !defined(OPEN_BSD) && !defined(SUNOS_5) #include "if.h" #include "sockunion.h" #include "prefix.h" #include "connected.h" #include "memory.h" #include "zebra_memory.h" #include "ioctl.h" #include "log.h" #include "interface.h" #include "vrf.h" #include "zebra/rt.h" #include "zebra/kernel_socket.h" #include "zebra/rib.h" #include "zebra/zebra_errors.h" void ifstat_update_sysctl(void) { caddr_t ref, buf, end; size_t bufsiz; struct if_msghdr *ifm; struct interface *ifp; #define MIBSIZ 6 int mib[MIBSIZ] = { CTL_NET, PF_ROUTE, 0, 0, /* AF_INET & AF_INET6 */ NET_RT_IFLIST, 0}; /* Query buffer size. */ if (sysctl(mib, MIBSIZ, NULL, &bufsiz, NULL, 0) < 0) { flog_warn(EC_ZEBRA_SYSCTL_FAILED, "sysctl() error by %s", safe_strerror(errno)); return; } /* We free this memory at the end of this function. */ ref = buf = XMALLOC(MTYPE_TMP, bufsiz); /* Fetch interface information into allocated buffer. */ if (sysctl(mib, MIBSIZ, buf, &bufsiz, NULL, 0) < 0) { flog_warn(EC_ZEBRA_SYSCTL_FAILED, "sysctl error by %s", safe_strerror(errno)); XFREE(MTYPE_TMP, ref); return; } /* Parse both interfaces and addresses. */ for (end = buf + bufsiz; buf < end; buf += ifm->ifm_msglen) { ifm = (struct if_msghdr *)buf; if (ifm->ifm_type == RTM_IFINFO) { ifp = if_lookup_by_index(ifm->ifm_index, VRF_DEFAULT); if (ifp) ifp->stats = ifm->ifm_data; } } /* Free sysctl buffer. */ XFREE(MTYPE_TMP, ref); return; } /* Interface listing up function using sysctl(). */ void interface_list(struct zebra_ns *zns) { caddr_t ref, buf, end; size_t bufsiz; struct if_msghdr *ifm; #define MIBSIZ 6 int mib[MIBSIZ] = { CTL_NET, PF_ROUTE, 0, 0, /* AF_INET & AF_INET6 */ NET_RT_IFLIST, 0}; if (zns->ns_id != NS_DEFAULT) { zlog_debug("interface_list: ignore NS %u", zns->ns_id); return; } /* Query buffer size. */ if (sysctl(mib, MIBSIZ, NULL, &bufsiz, NULL, 0) < 0) { flog_err_sys(EC_ZEBRA_IFLIST_FAILED, "Could not enumerate interfaces: %s", safe_strerror(errno)); return; } /* We free this memory at the end of this function. */ ref = buf = XMALLOC(MTYPE_TMP, bufsiz); /* Fetch interface information into allocated buffer. */ if (sysctl(mib, MIBSIZ, buf, &bufsiz, NULL, 0) < 0) { flog_err_sys(EC_ZEBRA_IFLIST_FAILED, "Could not enumerate interfaces: %s", safe_strerror(errno)); return; } /* Parse both interfaces and addresses. */ for (end = buf + bufsiz; buf < end; buf += ifm->ifm_msglen) { ifm = (struct if_msghdr *)buf; switch (ifm->ifm_type) { case RTM_IFINFO: ifm_read(ifm); break; case RTM_NEWADDR: ifam_read((struct ifa_msghdr *)ifm); break; default: zlog_info("interfaces_list(): unexpected message type"); XFREE(MTYPE_TMP, ref); return; break; } } /* Free sysctl buffer. */ XFREE(MTYPE_TMP, ref); } #endif /* !defined(GNU_LINUX) && !defined(OPEN_BSD) && !defined(SUNOS_5) */ frr-7.2.1/zebra/interface.c0000644000000000000000000025367413610377563012444 00000000000000/* * Interface function. * Copyright (C) 1997, 1999 Kunihiro Ishiguro * * This file is part of GNU Zebra. * * GNU Zebra is free software; you can redistribute it and/or modify it * under the terms of the GNU General Public License as published by the * Free Software Foundation; either version 2, or (at your option) any * later version. * * GNU Zebra 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; see the file COPYING; if not, write to the Free Software * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA */ #include #include "if.h" #include "lib_errors.h" #include "vty.h" #include "sockunion.h" #include "prefix.h" #include "command.h" #include "memory.h" #include "zebra_memory.h" #include "ioctl.h" #include "connected.h" #include "log.h" #include "zclient.h" #include "vrf.h" #include "zebra/rtadv.h" #include "zebra_ns.h" #include "zebra_vrf.h" #include "zebra/interface.h" #include "zebra/rib.h" #include "zebra/rt.h" #include "zebra/zebra_router.h" #include "zebra/redistribute.h" #include "zebra/debug.h" #include "zebra/irdp.h" #include "zebra/zebra_ptm.h" #include "zebra/rt_netlink.h" #include "zebra/if_netlink.h" #include "zebra/interface.h" #include "zebra/zebra_vxlan.h" #include "zebra/zebra_errors.h" DEFINE_MTYPE_STATIC(ZEBRA, ZINFO, "Zebra Interface Information") #define ZEBRA_PTM_SUPPORT DEFINE_HOOK(zebra_if_extra_info, (struct vty * vty, struct interface *ifp), (vty, ifp)) DEFINE_HOOK(zebra_if_config_wr, (struct vty * vty, struct interface *ifp), (vty, ifp)) static void if_down_del_nbr_connected(struct interface *ifp); static int if_zebra_speed_update(struct thread *thread) { struct interface *ifp = THREAD_ARG(thread); struct zebra_if *zif = ifp->info; uint32_t new_speed; bool changed = false; zif->speed_update = NULL; new_speed = kernel_get_speed(ifp); if (new_speed != ifp->speed) { zlog_info("%s: %s old speed: %u new speed: %u", __PRETTY_FUNCTION__, ifp->name, ifp->speed, new_speed); ifp->speed = new_speed; if_add_update(ifp); changed = true; } if (changed || new_speed == UINT32_MAX) thread_add_timer(zrouter.master, if_zebra_speed_update, ifp, 5, &zif->speed_update); return 1; } static void zebra_if_node_destroy(route_table_delegate_t *delegate, struct route_table *table, struct route_node *node) { if (node->info) list_delete((struct list **)&node->info); route_node_destroy(delegate, table, node); } route_table_delegate_t zebra_if_table_delegate = { .create_node = route_node_create, .destroy_node = zebra_if_node_destroy}; /* Called when new interface is added. */ static int if_zebra_new_hook(struct interface *ifp) { struct zebra_if *zebra_if; zebra_if = XCALLOC(MTYPE_ZINFO, sizeof(struct zebra_if)); zebra_if->multicast = IF_ZEBRA_MULTICAST_UNSPEC; zebra_if->shutdown = IF_ZEBRA_SHUTDOWN_OFF; zebra_ptm_if_init(zebra_if); ifp->ptm_enable = zebra_ptm_get_enable_state(); #if defined(HAVE_RTADV) { /* Set default router advertise values. */ struct rtadvconf *rtadv; rtadv = &zebra_if->rtadv; rtadv->AdvSendAdvertisements = 0; rtadv->MaxRtrAdvInterval = RTADV_MAX_RTR_ADV_INTERVAL; rtadv->MinRtrAdvInterval = RTADV_MIN_RTR_ADV_INTERVAL; rtadv->AdvIntervalTimer = 0; rtadv->AdvManagedFlag = 0; rtadv->AdvOtherConfigFlag = 0; rtadv->AdvHomeAgentFlag = 0; rtadv->AdvLinkMTU = 0; rtadv->AdvReachableTime = 0; rtadv->AdvRetransTimer = 0; rtadv->AdvCurHopLimit = 0; rtadv->AdvDefaultLifetime = -1; /* derive from MaxRtrAdvInterval */ rtadv->HomeAgentPreference = 0; rtadv->HomeAgentLifetime = -1; /* derive from AdvDefaultLifetime */ rtadv->AdvIntervalOption = 0; rtadv->DefaultPreference = RTADV_PREF_MEDIUM; rtadv->AdvPrefixList = list_new(); rtadv->AdvRDNSSList = list_new(); rtadv->AdvDNSSLList = list_new(); } #endif /* HAVE_RTADV */ memset(&zebra_if->neigh_mac[0], 0, 6); /* Initialize installed address chains tree. */ zebra_if->ipv4_subnets = route_table_init_with_delegate(&zebra_if_table_delegate); ifp->info = zebra_if; /* * Some platforms are telling us that the interface is * up and ready to go. When we check the speed we * sometimes get the wrong value. Wait a couple * of seconds and ask again. Hopefully it's all settled * down upon startup. */ thread_add_timer(zrouter.master, if_zebra_speed_update, ifp, 15, &zebra_if->speed_update); return 0; } /* Called when interface is deleted. */ static int if_zebra_delete_hook(struct interface *ifp) { struct zebra_if *zebra_if; if (ifp->info) { zebra_if = ifp->info; /* Free installed address chains tree. */ if (zebra_if->ipv4_subnets) route_table_finish(zebra_if->ipv4_subnets); #if defined(HAVE_RTADV) struct rtadvconf *rtadv; rtadv = &zebra_if->rtadv; list_delete(&rtadv->AdvPrefixList); list_delete(&rtadv->AdvRDNSSList); list_delete(&rtadv->AdvDNSSLList); #endif /* HAVE_RTADV */ XFREE(MTYPE_TMP, zebra_if->desc); THREAD_OFF(zebra_if->speed_update); XFREE(MTYPE_ZINFO, zebra_if); } return 0; } /* Build the table key */ static void if_build_key(uint32_t ifindex, struct prefix *p) { p->family = AF_INET; p->prefixlen = IPV4_MAX_BITLEN; p->u.prefix4.s_addr = ifindex; } /* Link an interface in a per NS interface tree */ struct interface *if_link_per_ns(struct zebra_ns *ns, struct interface *ifp) { struct prefix p; struct route_node *rn; if (ifp->ifindex == IFINDEX_INTERNAL) return NULL; if_build_key(ifp->ifindex, &p); rn = route_node_get(ns->if_table, &p); if (rn->info) { ifp = (struct interface *)rn->info; route_unlock_node(rn); /* get */ return ifp; } rn->info = ifp; ifp->node = rn; return ifp; } /* Delete a VRF. This is called in vrf_terminate(). */ void if_unlink_per_ns(struct interface *ifp) { ifp->node->info = NULL; route_unlock_node(ifp->node); ifp->node = NULL; } /* Look up an interface by identifier within a NS */ struct interface *if_lookup_by_index_per_ns(struct zebra_ns *ns, uint32_t ifindex) { struct prefix p; struct route_node *rn; struct interface *ifp = NULL; if_build_key(ifindex, &p); rn = route_node_lookup(ns->if_table, &p); if (rn) { ifp = (struct interface *)rn->info; route_unlock_node(rn); /* lookup */ } return ifp; } /* Look up an interface by name within a NS */ struct interface *if_lookup_by_name_per_ns(struct zebra_ns *ns, const char *ifname) { struct route_node *rn; struct interface *ifp; for (rn = route_top(ns->if_table); rn; rn = route_next(rn)) { ifp = (struct interface *)rn->info; if (ifp && strcmp(ifp->name, ifname) == 0) return (ifp); } return NULL; } const char *ifindex2ifname_per_ns(struct zebra_ns *zns, unsigned int ifindex) { struct interface *ifp; return ((ifp = if_lookup_by_index_per_ns(zns, ifindex)) != NULL) ? ifp->name : "unknown"; } /* Tie an interface address to its derived subnet list of addresses. */ int if_subnet_add(struct interface *ifp, struct connected *ifc) { struct route_node *rn; struct zebra_if *zebra_if; struct prefix cp; struct list *addr_list; assert(ifp && ifp->info && ifc); zebra_if = ifp->info; /* Get address derived subnet node and associated address list, while marking address secondary attribute appropriately. */ cp = *CONNECTED_PREFIX(ifc); apply_mask(&cp); rn = route_node_get(zebra_if->ipv4_subnets, &cp); if ((addr_list = rn->info)) SET_FLAG(ifc->flags, ZEBRA_IFA_SECONDARY); else { UNSET_FLAG(ifc->flags, ZEBRA_IFA_SECONDARY); rn->info = addr_list = list_new(); route_lock_node(rn); } /* Tie address at the tail of address list. */ listnode_add(addr_list, ifc); /* Return list element count. */ return (addr_list->count); } /* Untie an interface address from its derived subnet list of addresses. */ int if_subnet_delete(struct interface *ifp, struct connected *ifc) { struct route_node *rn; struct zebra_if *zebra_if; struct list *addr_list; struct prefix cp; assert(ifp && ifp->info && ifc); zebra_if = ifp->info; cp = *CONNECTED_PREFIX(ifc); apply_mask(&cp); /* Get address derived subnet node. */ rn = route_node_lookup(zebra_if->ipv4_subnets, &cp); if (!(rn && rn->info)) { flog_warn(EC_ZEBRA_REMOVE_ADDR_UNKNOWN_SUBNET, "Trying to remove an address from an unknown subnet." " (please report this bug)"); return -1; } route_unlock_node(rn); /* Untie address from subnet's address list. */ addr_list = rn->info; /* Deleting an address that is not registered is a bug. * In any case, we shouldn't decrement the lock counter if the address * is unknown. */ if (!listnode_lookup(addr_list, ifc)) { flog_warn( EC_ZEBRA_REMOVE_UNREGISTERED_ADDR, "Trying to remove an address from a subnet where it is not" " currently registered. (please report this bug)"); return -1; } listnode_delete(addr_list, ifc); route_unlock_node(rn); /* Return list element count, if not empty. */ if (addr_list->count) { /* If deleted address is primary, mark subsequent one as such * and distribute. */ if (!CHECK_FLAG(ifc->flags, ZEBRA_IFA_SECONDARY)) { ifc = listgetdata( (struct listnode *)listhead(addr_list)); zebra_interface_address_delete_update(ifp, ifc); UNSET_FLAG(ifc->flags, ZEBRA_IFA_SECONDARY); /* XXX: Linux kernel removes all the secondary addresses * when the primary * address is removed. We could try to work around that, * though this is * non-trivial. */ zebra_interface_address_add_update(ifp, ifc); } return addr_list->count; } /* Otherwise, free list and route node. */ list_delete(&addr_list); rn->info = NULL; route_unlock_node(rn); return 0; } /* if_flags_mangle: A place for hacks that require mangling * or tweaking the interface flags. * * ******************** Solaris flags hacks ************************** * * Solaris IFF_UP flag reflects only the primary interface as the * routing socket only sends IFINFO for the primary interface. Hence * ~IFF_UP does not per se imply all the logical interfaces are also * down - which we only know of as addresses. Instead we must determine * whether the interface really is up or not according to how many * addresses are still attached. (Solaris always sends RTM_DELADDR if * an interface, logical or not, goes ~IFF_UP). * * Ie, we mangle IFF_UP to *additionally* reflect whether or not there * are addresses left in struct connected, not just the actual underlying * IFF_UP flag. * * We must hence remember the real state of IFF_UP, which we do in * struct zebra_if.primary_state. * * Setting IFF_UP within zebra to administratively shutdown the * interface will affect only the primary interface/address on Solaris. ************************End Solaris flags hacks *********************** */ static void if_flags_mangle(struct interface *ifp, uint64_t *newflags) { #ifdef SUNOS_5 struct zebra_if *zif = ifp->info; zif->primary_state = *newflags & (IFF_UP & 0xff); if (CHECK_FLAG(zif->primary_state, IFF_UP) || listcount(ifp->connected) > 0) SET_FLAG(*newflags, IFF_UP); else UNSET_FLAG(*newflags, IFF_UP); #endif /* SUNOS_5 */ } /* Update the flags field of the ifp with the new flag set provided. * Take whatever actions are required for any changes in flags we care * about. * * newflags should be the raw value, as obtained from the OS. */ void if_flags_update(struct interface *ifp, uint64_t newflags) { if_flags_mangle(ifp, &newflags); if (if_is_no_ptm_operative(ifp)) { /* operative -> inoperative? */ ifp->flags = newflags; if (!if_is_operative(ifp)) if_down(ifp); } else { /* inoperative -> operative? */ ifp->flags = newflags; if (if_is_operative(ifp)) if_up(ifp); } } /* Wake up configured address if it is not in current kernel address. */ static void if_addr_wakeup(struct interface *ifp) { struct listnode *node, *nnode; struct connected *ifc; struct prefix *p; enum zebra_dplane_result dplane_res; for (ALL_LIST_ELEMENTS(ifp->connected, node, nnode, ifc)) { p = ifc->address; if (CHECK_FLAG(ifc->conf, ZEBRA_IFC_CONFIGURED) && !CHECK_FLAG(ifc->conf, ZEBRA_IFC_QUEUED)) { /* Address check. */ if (p->family == AF_INET) { if (!if_is_up(ifp)) { /* Assume zebra is configured like * following: * * interface gre0 * ip addr 192.0.2.1/24 * ! * * As soon as zebra becomes first aware * that gre0 exists in the * kernel, it will set gre0 up and * configure its addresses. * * (This may happen at startup when the * interface already exists * or during runtime when the interface * is added to the kernel) * * XXX: IRDP code is calling here via * if_add_update - this seems * somewhat weird. * XXX: RUNNING is not a settable flag * on any system * I (paulj) am aware of. */ if_set_flags(ifp, IFF_UP | IFF_RUNNING); if_refresh(ifp); } dplane_res = dplane_intf_addr_set(ifp, ifc); if (dplane_res == ZEBRA_DPLANE_REQUEST_FAILURE) { flog_err_sys( EC_ZEBRA_IFACE_ADDR_ADD_FAILED, "Can't set interface's address: %s", dplane_res2str(dplane_res)); continue; } SET_FLAG(ifc->conf, ZEBRA_IFC_QUEUED); /* The address will be advertised to zebra * clients when the notification * from the kernel has been received. * It will also be added to the interface's * subnet list then. */ } if (p->family == AF_INET6) { if (!if_is_up(ifp)) { /* See long comment above */ if_set_flags(ifp, IFF_UP | IFF_RUNNING); if_refresh(ifp); } dplane_res = dplane_intf_addr_set(ifp, ifc); if (dplane_res == ZEBRA_DPLANE_REQUEST_FAILURE) { flog_err_sys( EC_ZEBRA_IFACE_ADDR_ADD_FAILED, "Can't set interface's address: %s", dplane_res2str(dplane_res)); continue; } SET_FLAG(ifc->conf, ZEBRA_IFC_QUEUED); /* The address will be advertised to zebra * clients when the notification * from the kernel has been received. */ } } } } /* Handle interface addition */ void if_add_update(struct interface *ifp) { struct zebra_if *if_data; struct zebra_ns *zns; struct zebra_vrf *zvrf = vrf_info_lookup(ifp->vrf_id); /* case interface populate before vrf enabled */ if (zvrf->zns) zns = zvrf->zns; else zns = zebra_ns_lookup(NS_DEFAULT); if_link_per_ns(zns, ifp); if_data = ifp->info; assert(if_data); if (if_data->multicast == IF_ZEBRA_MULTICAST_ON) if_set_flags(ifp, IFF_MULTICAST); else if (if_data->multicast == IF_ZEBRA_MULTICAST_OFF) if_unset_flags(ifp, IFF_MULTICAST); zebra_ptm_if_set_ptm_state(ifp, if_data); zebra_interface_add_update(ifp); if (!CHECK_FLAG(ifp->status, ZEBRA_INTERFACE_ACTIVE)) { SET_FLAG(ifp->status, ZEBRA_INTERFACE_ACTIVE); if (if_data->shutdown == IF_ZEBRA_SHUTDOWN_ON) { if (IS_ZEBRA_DEBUG_KERNEL) zlog_debug( "interface %s vrf %u index %d is shutdown. " "Won't wake it up.", ifp->name, ifp->vrf_id, ifp->ifindex); return; } if_addr_wakeup(ifp); if (IS_ZEBRA_DEBUG_KERNEL) zlog_debug( "interface %s vrf %u index %d becomes active.", ifp->name, ifp->vrf_id, ifp->ifindex); } else { if (IS_ZEBRA_DEBUG_KERNEL) zlog_debug("interface %s vrf %u index %d is added.", ifp->name, ifp->vrf_id, ifp->ifindex); } } /* Install connected routes corresponding to an interface. */ static void if_install_connected(struct interface *ifp) { struct listnode *node; struct listnode *next; struct connected *ifc; if (ifp->connected) { for (ALL_LIST_ELEMENTS(ifp->connected, node, next, ifc)) { if (CHECK_FLAG(ifc->conf, ZEBRA_IFC_REAL)) zebra_interface_address_add_update(ifp, ifc); connected_up(ifp, ifc); } } } /* Uninstall connected routes corresponding to an interface. */ static void if_uninstall_connected(struct interface *ifp) { struct listnode *node; struct listnode *next; struct connected *ifc; if (ifp->connected) { for (ALL_LIST_ELEMENTS(ifp->connected, node, next, ifc)) { zebra_interface_address_delete_update(ifp, ifc); connected_down(ifp, ifc); } } } /* Uninstall and delete connected routes corresponding to an interface. */ /* TODO - Check why IPv4 handling here is different from install or if_down */ static void if_delete_connected(struct interface *ifp) { struct connected *ifc; struct prefix cp; struct route_node *rn; struct zebra_if *zebra_if; struct listnode *node; struct listnode *last = NULL; zebra_if = ifp->info; if (!ifp->connected) return; while ((node = (last ? last->next : listhead(ifp->connected)))) { ifc = listgetdata(node); cp = *CONNECTED_PREFIX(ifc); apply_mask(&cp); if (cp.family == AF_INET && (rn = route_node_lookup(zebra_if->ipv4_subnets, &cp))) { struct listnode *anode; struct listnode *next; struct listnode *first; struct list *addr_list; route_unlock_node(rn); addr_list = (struct list *)rn->info; /* Remove addresses, secondaries first. */ first = listhead(addr_list); if (first) for (anode = first->next; anode || first; anode = next) { if (!anode) { anode = first; first = NULL; } next = anode->next; ifc = listgetdata(anode); connected_down(ifp, ifc); /* XXX: We have to send notifications * here explicitly, because we destroy * the ifc before receiving the * notification about the address being * deleted. */ zebra_interface_address_delete_update( ifp, ifc); UNSET_FLAG(ifc->conf, ZEBRA_IFC_REAL); UNSET_FLAG(ifc->conf, ZEBRA_IFC_QUEUED); /* Remove from subnet chain. */ list_delete_node(addr_list, anode); route_unlock_node(rn); /* Remove from interface address list * (unconditionally). */ if (!CHECK_FLAG(ifc->conf, ZEBRA_IFC_CONFIGURED)) { listnode_delete(ifp->connected, ifc); connected_free(ifc); } else last = node; } /* Free chain list and respective route node. */ list_delete(&addr_list); rn->info = NULL; route_unlock_node(rn); } else if (cp.family == AF_INET6) { connected_down(ifp, ifc); zebra_interface_address_delete_update(ifp, ifc); UNSET_FLAG(ifc->conf, ZEBRA_IFC_REAL); UNSET_FLAG(ifc->conf, ZEBRA_IFC_QUEUED); if (CHECK_FLAG(ifc->conf, ZEBRA_IFC_CONFIGURED)) last = node; else { listnode_delete(ifp->connected, ifc); connected_free(ifc); } } else { last = node; } } } /* Handle an interface delete event */ void if_delete_update(struct interface *ifp) { struct zebra_if *zif; if (if_is_up(ifp)) { flog_err( EC_LIB_INTERFACE, "interface %s vrf %u index %d is still up while being deleted.", ifp->name, ifp->vrf_id, ifp->ifindex); return; } if (!CHECK_FLAG(ifp->status, ZEBRA_INTERFACE_ACTIVE)) return; /* Mark interface as inactive */ UNSET_FLAG(ifp->status, ZEBRA_INTERFACE_ACTIVE); if (IS_ZEBRA_DEBUG_KERNEL) zlog_debug("interface %s vrf %u index %d is now inactive.", ifp->name, ifp->vrf_id, ifp->ifindex); /* Delete connected routes from the kernel. */ if_delete_connected(ifp); /* Send out notification on interface delete. */ zebra_interface_delete_update(ifp); if_unlink_per_ns(ifp); /* Update ifindex after distributing the delete message. This is in case any client needs to have the old value of ifindex available while processing the deletion. Each client daemon is responsible for setting ifindex to IFINDEX_INTERNAL after processing the interface deletion message. */ if_set_index(ifp, IFINDEX_INTERNAL); ifp->node = NULL; /* if the ifp is in a vrf, move it to default so vrf can be deleted if * desired. This operation is not done for netns implementation to avoid * collision with interface with the same name in the default vrf (can * occur with this implementation whereas it is not possible with * vrf-lite). */ if (ifp->vrf_id && !vrf_is_backend_netns()) if_handle_vrf_change(ifp, VRF_DEFAULT); /* Reset some zebra interface params to default values. */ zif = ifp->info; if (zif) { zif->zif_type = ZEBRA_IF_OTHER; zif->zif_slave_type = ZEBRA_IF_SLAVE_NONE; memset(&zif->l2info, 0, sizeof(union zebra_l2if_info)); memset(&zif->brslave_info, 0, sizeof(struct zebra_l2info_brslave)); } } /* VRF change for an interface */ void if_handle_vrf_change(struct interface *ifp, vrf_id_t vrf_id) { vrf_id_t old_vrf_id; old_vrf_id = ifp->vrf_id; /* Uninstall connected routes. */ if_uninstall_connected(ifp); /* Delete any IPv4 neighbors created to implement RFC 5549 */ if_nbr_ipv6ll_to_ipv4ll_neigh_del_all(ifp); /* Delete all neighbor addresses learnt through IPv6 RA */ if_down_del_nbr_connected(ifp); /* Send out notification on interface VRF change. */ /* This is to issue an UPDATE or a DELETE, as appropriate. */ zebra_interface_vrf_update_del(ifp, vrf_id); /* update VRF */ if_update_to_new_vrf(ifp, vrf_id); /* Send out notification on interface VRF change. */ /* This is to issue an ADD, if needed. */ zebra_interface_vrf_update_add(ifp, old_vrf_id); /* Install connected routes (in new VRF). */ if (if_is_operative(ifp)) if_install_connected(ifp); } static void ipv6_ll_address_to_mac(struct in6_addr *address, uint8_t *mac) { mac[0] = address->s6_addr[8] ^ 0x02; mac[1] = address->s6_addr[9]; mac[2] = address->s6_addr[10]; mac[3] = address->s6_addr[13]; mac[4] = address->s6_addr[14]; mac[5] = address->s6_addr[15]; } void if_nbr_mac_to_ipv4ll_neigh_update(struct interface *ifp, char mac[6], struct in6_addr *address, int add) { struct zebra_vrf *zvrf = vrf_info_lookup(ifp->vrf_id); struct zebra_if *zif = ifp->info; char buf[16] = "169.254.0.1"; struct in_addr ipv4_ll; ns_id_t ns_id; inet_pton(AF_INET, buf, &ipv4_ll); ns_id = zvrf->zns->ns_id; /* * Remove and re-add any existing neighbor entry for this address, * since Netlink doesn't currently offer update message types. */ kernel_neigh_update(0, ifp->ifindex, ipv4_ll.s_addr, mac, 6, ns_id); /* Add new neighbor entry. * * We force installation even if current neighbor entry is the same. * Since this function is used to refresh our MAC entries after an * interface flap, if we don't force in our custom entries with their * state set to PERMANENT or REACHABLE then the kernel will attempt to * resolve our leftover entries, fail, mark them unreachable and then * they'll be useless to us. */ if (add) kernel_neigh_update(add, ifp->ifindex, ipv4_ll.s_addr, mac, 6, ns_id); memcpy(&zif->neigh_mac[0], &mac[0], 6); /* * We need to note whether or not we originated a v6 * neighbor entry for this interface. So that when * someone unwisely accidently deletes this entry * we can shove it back in. */ zif->v6_2_v4_ll_neigh_entry = !!add; memcpy(&zif->v6_2_v4_ll_addr6, address, sizeof(*address)); zvrf->neigh_updates++; } void if_nbr_ipv6ll_to_ipv4ll_neigh_update(struct interface *ifp, struct in6_addr *address, int add) { char mac[6]; ipv6_ll_address_to_mac(address, (uint8_t *)mac); if_nbr_mac_to_ipv4ll_neigh_update(ifp, mac, address, add); } static void if_nbr_ipv6ll_to_ipv4ll_neigh_add_all(struct interface *ifp) { if (listhead(ifp->nbr_connected)) { struct nbr_connected *nbr_connected; struct listnode *node; for (ALL_LIST_ELEMENTS_RO(ifp->nbr_connected, node, nbr_connected)) if_nbr_ipv6ll_to_ipv4ll_neigh_update( ifp, &nbr_connected->address->u.prefix6, 1); } } void if_nbr_ipv6ll_to_ipv4ll_neigh_del_all(struct interface *ifp) { if (listhead(ifp->nbr_connected)) { struct nbr_connected *nbr_connected; struct listnode *node; for (ALL_LIST_ELEMENTS_RO(ifp->nbr_connected, node, nbr_connected)) if_nbr_ipv6ll_to_ipv4ll_neigh_update( ifp, &nbr_connected->address->u.prefix6, 0); } } static void if_down_del_nbr_connected(struct interface *ifp) { struct nbr_connected *nbr_connected; struct listnode *node, *nnode; for (ALL_LIST_ELEMENTS(ifp->nbr_connected, node, nnode, nbr_connected)) { listnode_delete(ifp->nbr_connected, nbr_connected); nbr_connected_free(nbr_connected); } } /* Interface is up. */ void if_up(struct interface *ifp) { struct zebra_if *zif; struct interface *link_if; struct zebra_vrf *zvrf = vrf_info_lookup(ifp->vrf_id); zif = ifp->info; zif->up_count++; quagga_timestamp(2, zif->up_last, sizeof(zif->up_last)); /* Notify the protocol daemons. */ if (ifp->ptm_enable && (ifp->ptm_status == ZEBRA_PTM_STATUS_DOWN)) { flog_warn(EC_ZEBRA_PTM_NOT_READY, "%s: interface %s hasn't passed ptm check\n", __func__, ifp->name); return; } zebra_interface_up_update(ifp); if_nbr_ipv6ll_to_ipv4ll_neigh_add_all(ifp); #if defined(HAVE_RTADV) /* Enable fast tx of RA if enabled && RA interval is not in msecs */ if (zif->rtadv.AdvSendAdvertisements && (zif->rtadv.MaxRtrAdvInterval >= 1000)) { zif->rtadv.inFastRexmit = 1; zif->rtadv.NumFastReXmitsRemain = RTADV_NUM_FAST_REXMITS; } #endif /* Install connected routes to the kernel. */ if_install_connected(ifp); /* Handle interface up for specific types for EVPN. Non-VxLAN interfaces * are checked to see if (remote) neighbor entries need to be installed * on them for ARP suppression. */ if (IS_ZEBRA_IF_VXLAN(ifp)) zebra_vxlan_if_up(ifp); else if (IS_ZEBRA_IF_BRIDGE(ifp)) { link_if = ifp; zebra_vxlan_svi_up(ifp, link_if); } else if (IS_ZEBRA_IF_VLAN(ifp)) { link_if = if_lookup_by_index_per_ns(zvrf->zns, zif->link_ifindex); if (link_if) zebra_vxlan_svi_up(ifp, link_if); } } /* Interface goes down. We have to manage different behavior of based OS. */ void if_down(struct interface *ifp) { struct zebra_if *zif; struct interface *link_if; struct zebra_vrf *zvrf = vrf_info_lookup(ifp->vrf_id); zif = ifp->info; zif->down_count++; quagga_timestamp(2, zif->down_last, sizeof(zif->down_last)); /* Handle interface down for specific types for EVPN. Non-VxLAN * interfaces * are checked to see if (remote) neighbor entries need to be purged * for ARP suppression. */ if (IS_ZEBRA_IF_VXLAN(ifp)) zebra_vxlan_if_down(ifp); else if (IS_ZEBRA_IF_BRIDGE(ifp)) { link_if = ifp; zebra_vxlan_svi_down(ifp, link_if); } else if (IS_ZEBRA_IF_VLAN(ifp)) { link_if = if_lookup_by_index_per_ns(zvrf->zns, zif->link_ifindex); if (link_if) zebra_vxlan_svi_down(ifp, link_if); } /* Notify to the protocol daemons. */ zebra_interface_down_update(ifp); /* Uninstall connected routes from the kernel. */ if_uninstall_connected(ifp); if_nbr_ipv6ll_to_ipv4ll_neigh_del_all(ifp); /* Delete all neighbor addresses learnt through IPv6 RA */ if_down_del_nbr_connected(ifp); } void if_refresh(struct interface *ifp) { if_get_flags(ifp); } void zebra_if_update_link(struct interface *ifp, ifindex_t link_ifindex, ns_id_t ns_id) { struct zebra_if *zif; if (IS_ZEBRA_IF_VETH(ifp)) return; zif = (struct zebra_if *)ifp->info; zif->link_ifindex = link_ifindex; zif->link = if_lookup_by_index_per_ns(zebra_ns_lookup(ns_id), link_ifindex); } /* * during initial link dump kernel does not order lower devices before * upper devices so we need to fixup link dependencies at the end of dump */ void zebra_if_update_all_links(void) { struct route_node *rn; struct interface *ifp; struct zebra_if *zif; struct zebra_ns *ns; if (IS_ZEBRA_DEBUG_KERNEL) zlog_info("fixup link dependencies"); ns = zebra_ns_lookup(NS_DEFAULT); for (rn = route_top(ns->if_table); rn; rn = route_next(rn)) { ifp = (struct interface *)rn->info; if (!ifp) continue; zif = ifp->info; if ((zif->link_ifindex != IFINDEX_INTERNAL) && !zif->link) { zif->link = if_lookup_by_index_per_ns(ns, zif->link_ifindex); if (IS_ZEBRA_DEBUG_KERNEL) zlog_debug("interface %s/%d's lower fixup to %s/%d", ifp->name, ifp->ifindex, zif->link?zif->link->name:"unk", zif->link_ifindex); } } } void zebra_if_set_protodown(struct interface *ifp, bool down) { #ifdef HAVE_NETLINK netlink_protodown(ifp, down); #else zlog_warn("Protodown is not supported on this platform"); #endif } /* Output prefix string to vty. */ static int prefix_vty_out(struct vty *vty, struct prefix *p) { char str[INET6_ADDRSTRLEN]; inet_ntop(p->family, &p->u.prefix, str, sizeof(str)); vty_out(vty, "%s", str); return strlen(str); } /* Dump if address information to vty. */ static void connected_dump_vty(struct vty *vty, struct connected *connected) { struct prefix *p; /* Print interface address. */ p = connected->address; vty_out(vty, " %s ", prefix_family_str(p)); prefix_vty_out(vty, p); vty_out(vty, "/%d", p->prefixlen); /* If there is destination address, print it. */ if (CONNECTED_PEER(connected) && connected->destination) { vty_out(vty, " peer "); prefix_vty_out(vty, connected->destination); vty_out(vty, "/%d", connected->destination->prefixlen); } if (CHECK_FLAG(connected->flags, ZEBRA_IFA_SECONDARY)) vty_out(vty, " secondary"); if (CHECK_FLAG(connected->flags, ZEBRA_IFA_UNNUMBERED)) vty_out(vty, " unnumbered"); if (connected->label) vty_out(vty, " %s", connected->label); vty_out(vty, "\n"); } /* Dump interface neighbor address information to vty. */ static void nbr_connected_dump_vty(struct vty *vty, struct nbr_connected *connected) { struct prefix *p; /* Print interface address. */ p = connected->address; vty_out(vty, " %s ", prefix_family_str(p)); prefix_vty_out(vty, p); vty_out(vty, "/%d", p->prefixlen); vty_out(vty, "\n"); } static const char *zebra_ziftype_2str(zebra_iftype_t zif_type) { switch (zif_type) { case ZEBRA_IF_OTHER: return "Other"; break; case ZEBRA_IF_BRIDGE: return "Bridge"; break; case ZEBRA_IF_VLAN: return "Vlan"; break; case ZEBRA_IF_VXLAN: return "Vxlan"; break; case ZEBRA_IF_VRF: return "VRF"; break; case ZEBRA_IF_VETH: return "VETH"; break; case ZEBRA_IF_BOND: return "bond"; case ZEBRA_IF_BOND_SLAVE: return "bond_slave"; case ZEBRA_IF_MACVLAN: return "macvlan"; default: return "Unknown"; break; } } /* Interface's brief information print out to vty interface. */ static void ifs_dump_brief_vty(struct vty *vty, struct vrf *vrf) { struct connected *connected; struct listnode *node; struct route_node *rn; struct zebra_if *zebra_if; struct prefix *p; struct interface *ifp; bool print_header = true; FOR_ALL_INTERFACES (vrf, ifp) { char global_pfx[PREFIX_STRLEN] = {0}; char buf[PREFIX_STRLEN] = {0}; bool first_pfx_printed = false; if (print_header) { vty_out(vty, "%-16s%-8s%-16s%s\n", "Interface", "Status", "VRF", "Addresses"); vty_out(vty, "%-16s%-8s%-16s%s\n", "---------", "------", "---", "---------"); print_header = false; /* We have at least 1 iface */ } zebra_if = ifp->info; vty_out(vty, "%-16s", ifp->name); if (if_is_up(ifp)) vty_out(vty, "%-8s", "up"); else vty_out(vty, "%-8s", "down"); vty_out(vty, "%-16s", vrf->name); for (rn = route_top(zebra_if->ipv4_subnets); rn; rn = route_next(rn)) { if (!rn->info) continue; uint32_t list_size = listcount((struct list *)rn->info); for (ALL_LIST_ELEMENTS_RO((struct list *)rn->info, node, connected)) { if (!CHECK_FLAG(connected->flags, ZEBRA_IFA_SECONDARY)) { p = connected->address; prefix2str(p, buf, sizeof(buf)); if (first_pfx_printed) { /* padding to prepare row only for ip addr */ vty_out(vty, "%-40s", ""); if (list_size > 1) vty_out(vty, "+ "); vty_out(vty, "%s\n", buf); } else { if (list_size > 1) vty_out(vty, "+ "); vty_out(vty, "%s\n", buf); } first_pfx_printed = true; break; } } } uint32_t v6_list_size = 0; for (ALL_LIST_ELEMENTS_RO(ifp->connected, node, connected)) { if (CHECK_FLAG(connected->conf, ZEBRA_IFC_REAL) && (connected->address->family == AF_INET6)) v6_list_size++; } for (ALL_LIST_ELEMENTS_RO(ifp->connected, node, connected)) { if (CHECK_FLAG(connected->conf, ZEBRA_IFC_REAL) && !CHECK_FLAG(connected->flags, ZEBRA_IFA_SECONDARY) && (connected->address->family == AF_INET6)) { p = connected->address; /* Don't print link local pfx */ if (!IN6_IS_ADDR_LINKLOCAL(&p->u.prefix6)) { prefix2str(p, global_pfx, PREFIX_STRLEN); if (first_pfx_printed) { /* padding to prepare row only for ip addr */ vty_out(vty, "%-40s", ""); if (v6_list_size > 1) vty_out(vty, "+ "); vty_out(vty, "%s\n", global_pfx); } else { if (v6_list_size > 1) vty_out(vty, "+ "); vty_out(vty, "%s\n", global_pfx); } first_pfx_printed = true; break; } } } if (!first_pfx_printed) vty_out(vty, "\n"); } vty_out(vty, "\n"); } /* Interface's information print out to vty interface. */ static void if_dump_vty(struct vty *vty, struct interface *ifp) { struct connected *connected; struct nbr_connected *nbr_connected; struct listnode *node; struct route_node *rn; struct zebra_if *zebra_if; struct vrf *vrf; zebra_if = ifp->info; vty_out(vty, "Interface %s is ", ifp->name); if (if_is_up(ifp)) { vty_out(vty, "up, line protocol "); if (CHECK_FLAG(ifp->status, ZEBRA_INTERFACE_LINKDETECTION)) { if (if_is_running(ifp)) vty_out(vty, "is up\n"); else vty_out(vty, "is down\n"); } else { vty_out(vty, "detection is disabled\n"); } } else { vty_out(vty, "down\n"); } vty_out(vty, " Link ups: %5u last: %s\n", zebra_if->up_count, zebra_if->up_last[0] ? zebra_if->up_last : "(never)"); vty_out(vty, " Link downs: %5u last: %s\n", zebra_if->down_count, zebra_if->down_last[0] ? zebra_if->down_last : "(never)"); zebra_ptm_show_status(vty, ifp); vrf = vrf_lookup_by_id(ifp->vrf_id); vty_out(vty, " vrf: %s\n", vrf->name); if (ifp->desc) vty_out(vty, " Description: %s\n", ifp->desc); if (zebra_if->desc) vty_out(vty, " OS Description: %s\n", zebra_if->desc); if (ifp->ifindex == IFINDEX_INTERNAL) { vty_out(vty, " pseudo interface\n"); return; } else if (!CHECK_FLAG(ifp->status, ZEBRA_INTERFACE_ACTIVE)) { vty_out(vty, " index %d inactive interface\n", ifp->ifindex); return; } vty_out(vty, " index %d metric %d mtu %d speed %u ", ifp->ifindex, ifp->metric, ifp->mtu, ifp->speed); if (ifp->mtu6 != ifp->mtu) vty_out(vty, "mtu6 %d ", ifp->mtu6); vty_out(vty, "\n flags: %s\n", if_flag_dump(ifp->flags)); /* Hardware address. */ vty_out(vty, " Type: %s\n", if_link_type_str(ifp->ll_type)); if (ifp->hw_addr_len != 0) { int i; vty_out(vty, " HWaddr: "); for (i = 0; i < ifp->hw_addr_len; i++) vty_out(vty, "%s%02x", i == 0 ? "" : ":", ifp->hw_addr[i]); vty_out(vty, "\n"); } /* Bandwidth in Mbps */ if (ifp->bandwidth != 0) { vty_out(vty, " bandwidth %u Mbps", ifp->bandwidth); vty_out(vty, "\n"); } for (rn = route_top(zebra_if->ipv4_subnets); rn; rn = route_next(rn)) { if (!rn->info) continue; for (ALL_LIST_ELEMENTS_RO((struct list *)rn->info, node, connected)) connected_dump_vty(vty, connected); } for (ALL_LIST_ELEMENTS_RO(ifp->connected, node, connected)) { if (CHECK_FLAG(connected->conf, ZEBRA_IFC_REAL) && (connected->address->family == AF_INET6)) connected_dump_vty(vty, connected); } vty_out(vty, " Interface Type %s\n", zebra_ziftype_2str(zebra_if->zif_type)); if (IS_ZEBRA_IF_BRIDGE(ifp)) { struct zebra_l2info_bridge *bridge_info; bridge_info = &zebra_if->l2info.br; vty_out(vty, " Bridge VLAN-aware: %s\n", bridge_info->vlan_aware ? "yes" : "no"); } else if (IS_ZEBRA_IF_VLAN(ifp)) { struct zebra_l2info_vlan *vlan_info; vlan_info = &zebra_if->l2info.vl; vty_out(vty, " VLAN Id %u\n", vlan_info->vid); } else if (IS_ZEBRA_IF_VXLAN(ifp)) { struct zebra_l2info_vxlan *vxlan_info; vxlan_info = &zebra_if->l2info.vxl; vty_out(vty, " VxLAN Id %u", vxlan_info->vni); if (vxlan_info->vtep_ip.s_addr != INADDR_ANY) vty_out(vty, " VTEP IP: %s", inet_ntoa(vxlan_info->vtep_ip)); if (vxlan_info->access_vlan) vty_out(vty, " Access VLAN Id %u\n", vxlan_info->access_vlan); if (vxlan_info->mcast_grp.s_addr != INADDR_ANY) vty_out(vty, " Mcast Group %s", inet_ntoa(vxlan_info->mcast_grp)); vty_out(vty, "\n"); } if (IS_ZEBRA_IF_BRIDGE_SLAVE(ifp)) { struct zebra_l2info_brslave *br_slave; br_slave = &zebra_if->brslave_info; if (br_slave->bridge_ifindex != IFINDEX_INTERNAL) { if (br_slave->br_if) vty_out(vty, " Master interface: %s\n", br_slave->br_if->name); else vty_out(vty, " Master ifindex: %u\n", br_slave->bridge_ifindex); } } if (IS_ZEBRA_IF_BOND_SLAVE(ifp)) { struct zebra_l2info_bondslave *bond_slave; bond_slave = &zebra_if->bondslave_info; if (bond_slave->bond_ifindex != IFINDEX_INTERNAL) { if (bond_slave->bond_if) vty_out(vty, " Master interface: %s\n", bond_slave->bond_if->name); else vty_out(vty, " Master ifindex: %u\n", bond_slave->bond_ifindex); } } if (zebra_if->link_ifindex != IFINDEX_INTERNAL) { if (zebra_if->link) vty_out(vty, " Parent interface: %s\n", zebra_if->link->name); else vty_out(vty, " Parent ifindex: %d\n", zebra_if->link_ifindex); } if (HAS_LINK_PARAMS(ifp)) { int i; struct if_link_params *iflp = ifp->link_params; vty_out(vty, " Traffic Engineering Link Parameters:\n"); if (IS_PARAM_SET(iflp, LP_TE_METRIC)) vty_out(vty, " TE metric %u\n", iflp->te_metric); if (IS_PARAM_SET(iflp, LP_MAX_BW)) vty_out(vty, " Maximum Bandwidth %g (Byte/s)\n", iflp->max_bw); if (IS_PARAM_SET(iflp, LP_MAX_RSV_BW)) vty_out(vty, " Maximum Reservable Bandwidth %g (Byte/s)\n", iflp->max_rsv_bw); if (IS_PARAM_SET(iflp, LP_UNRSV_BW)) { vty_out(vty, " Unreserved Bandwidth per Class Type in Byte/s:\n"); for (i = 0; i < MAX_CLASS_TYPE; i += 2) vty_out(vty, " [%d]: %g (Bytes/sec),\t[%d]: %g (Bytes/sec)\n", i, iflp->unrsv_bw[i], i + 1, iflp->unrsv_bw[i + 1]); } if (IS_PARAM_SET(iflp, LP_ADM_GRP)) vty_out(vty, " Administrative Group:%u\n", iflp->admin_grp); if (IS_PARAM_SET(iflp, LP_DELAY)) { vty_out(vty, " Link Delay Average: %u (micro-sec.)", iflp->av_delay); if (IS_PARAM_SET(iflp, LP_MM_DELAY)) { vty_out(vty, " Min: %u (micro-sec.)", iflp->min_delay); vty_out(vty, " Max: %u (micro-sec.)", iflp->max_delay); } vty_out(vty, "\n"); } if (IS_PARAM_SET(iflp, LP_DELAY_VAR)) vty_out(vty, " Link Delay Variation %u (micro-sec.)\n", iflp->delay_var); if (IS_PARAM_SET(iflp, LP_PKT_LOSS)) vty_out(vty, " Link Packet Loss %g (in %%)\n", iflp->pkt_loss); if (IS_PARAM_SET(iflp, LP_AVA_BW)) vty_out(vty, " Available Bandwidth %g (Byte/s)\n", iflp->ava_bw); if (IS_PARAM_SET(iflp, LP_RES_BW)) vty_out(vty, " Residual Bandwidth %g (Byte/s)\n", iflp->res_bw); if (IS_PARAM_SET(iflp, LP_USE_BW)) vty_out(vty, " Utilized Bandwidth %g (Byte/s)\n", iflp->use_bw); if (IS_PARAM_SET(iflp, LP_RMT_AS)) vty_out(vty, " Neighbor ASBR IP: %s AS: %u \n", inet_ntoa(iflp->rmt_ip), iflp->rmt_as); } hook_call(zebra_if_extra_info, vty, ifp); if (listhead(ifp->nbr_connected)) vty_out(vty, " Neighbor address(s):\n"); for (ALL_LIST_ELEMENTS_RO(ifp->nbr_connected, node, nbr_connected)) nbr_connected_dump_vty(vty, nbr_connected); #ifdef HAVE_PROC_NET_DEV /* Statistics print out using proc file system. */ vty_out(vty, " %lu input packets (%lu multicast), %lu bytes, " "%lu dropped\n", ifp->stats.rx_packets, ifp->stats.rx_multicast, ifp->stats.rx_bytes, ifp->stats.rx_dropped); vty_out(vty, " %lu input errors, %lu length, %lu overrun," " %lu CRC, %lu frame\n", ifp->stats.rx_errors, ifp->stats.rx_length_errors, ifp->stats.rx_over_errors, ifp->stats.rx_crc_errors, ifp->stats.rx_frame_errors); vty_out(vty, " %lu fifo, %lu missed\n", ifp->stats.rx_fifo_errors, ifp->stats.rx_missed_errors); vty_out(vty, " %lu output packets, %lu bytes, %lu dropped\n", ifp->stats.tx_packets, ifp->stats.tx_bytes, ifp->stats.tx_dropped); vty_out(vty, " %lu output errors, %lu aborted, %lu carrier," " %lu fifo, %lu heartbeat\n", ifp->stats.tx_errors, ifp->stats.tx_aborted_errors, ifp->stats.tx_carrier_errors, ifp->stats.tx_fifo_errors, ifp->stats.tx_heartbeat_errors); vty_out(vty, " %lu window, %lu collisions\n", ifp->stats.tx_window_errors, ifp->stats.collisions); #endif /* HAVE_PROC_NET_DEV */ #ifdef HAVE_NET_RT_IFLIST /* Statistics print out using sysctl (). */ vty_out(vty, " input packets %llu, bytes %llu, dropped %llu," " multicast packets %llu\n", (unsigned long long)ifp->stats.ifi_ipackets, (unsigned long long)ifp->stats.ifi_ibytes, (unsigned long long)ifp->stats.ifi_iqdrops, (unsigned long long)ifp->stats.ifi_imcasts); vty_out(vty, " input errors %llu\n", (unsigned long long)ifp->stats.ifi_ierrors); vty_out(vty, " output packets %llu, bytes %llu," " multicast packets %llu\n", (unsigned long long)ifp->stats.ifi_opackets, (unsigned long long)ifp->stats.ifi_obytes, (unsigned long long)ifp->stats.ifi_omcasts); vty_out(vty, " output errors %llu\n", (unsigned long long)ifp->stats.ifi_oerrors); vty_out(vty, " collisions %llu\n", (unsigned long long)ifp->stats.ifi_collisions); #endif /* HAVE_NET_RT_IFLIST */ } static void interface_update_stats(void) { #ifdef HAVE_PROC_NET_DEV /* If system has interface statistics via proc file system, update statistics. */ ifstat_update_proc(); #endif /* HAVE_PROC_NET_DEV */ #ifdef HAVE_NET_RT_IFLIST ifstat_update_sysctl(); #endif /* HAVE_NET_RT_IFLIST */ } struct cmd_node interface_node = {INTERFACE_NODE, "%s(config-if)# ", 1}; #ifndef VTYSH_EXTRACT_PL #include "zebra/interface_clippy.c" #endif /* Show all interfaces to vty. */ DEFPY(show_interface, show_interface_cmd, "show interface [vrf NAME$vrf_name] [brief$brief]", SHOW_STR "Interface status and configuration\n" VRF_CMD_HELP_STR "Interface status and configuration summary\n") { struct vrf *vrf; struct interface *ifp; vrf_id_t vrf_id = VRF_DEFAULT; interface_update_stats(); if (vrf_name) VRF_GET_ID(vrf_id, vrf_name, false); /* All interface print. */ vrf = vrf_lookup_by_id(vrf_id); if (brief) { ifs_dump_brief_vty(vty, vrf); } else { FOR_ALL_INTERFACES (vrf, ifp) { if_dump_vty(vty, ifp); } } return CMD_SUCCESS; } /* Show all interfaces to vty. */ DEFPY (show_interface_vrf_all, show_interface_vrf_all_cmd, "show interface vrf all [brief$brief]", SHOW_STR "Interface status and configuration\n" VRF_ALL_CMD_HELP_STR "Interface status and configuration summary\n") { struct vrf *vrf; struct interface *ifp; interface_update_stats(); /* All interface print. */ RB_FOREACH (vrf, vrf_name_head, &vrfs_by_name) { if (brief) { ifs_dump_brief_vty(vty, vrf); } else { FOR_ALL_INTERFACES (vrf, ifp) if_dump_vty(vty, ifp); } } return CMD_SUCCESS; } /* Show specified interface to vty. */ DEFUN (show_interface_name_vrf, show_interface_name_vrf_cmd, "show interface IFNAME vrf NAME", SHOW_STR "Interface status and configuration\n" "Interface name\n" VRF_CMD_HELP_STR) { int idx_ifname = 2; int idx_name = 4; struct interface *ifp; vrf_id_t vrf_id; interface_update_stats(); VRF_GET_ID(vrf_id, argv[idx_name]->arg, false); /* Specified interface print. */ ifp = if_lookup_by_name(argv[idx_ifname]->arg, vrf_id); if (ifp == NULL) { vty_out(vty, "%% Can't find interface %s\n", argv[idx_ifname]->arg); return CMD_WARNING; } if_dump_vty(vty, ifp); return CMD_SUCCESS; } /* Show specified interface to vty. */ DEFUN (show_interface_name_vrf_all, show_interface_name_vrf_all_cmd, "show interface IFNAME [vrf all]", SHOW_STR "Interface status and configuration\n" "Interface name\n" VRF_ALL_CMD_HELP_STR) { int idx_ifname = 2; struct vrf *vrf; struct interface *ifp; int found = 0; interface_update_stats(); /* All interface print. */ RB_FOREACH (vrf, vrf_name_head, &vrfs_by_name) { /* Specified interface print. */ ifp = if_lookup_by_name(argv[idx_ifname]->arg, vrf->vrf_id); if (ifp) { if_dump_vty(vty, ifp); found++; } } if (!found) { vty_out(vty, "%% Can't find interface %s\n", argv[idx_ifname]->arg); return CMD_WARNING; } return CMD_SUCCESS; } static void if_show_description(struct vty *vty, vrf_id_t vrf_id) { struct vrf *vrf = vrf_lookup_by_id(vrf_id); struct interface *ifp; vty_out(vty, "Interface Status Protocol Description\n"); FOR_ALL_INTERFACES (vrf, ifp) { int len; struct zebra_if *zif; bool intf_desc; intf_desc = false; len = vty_out(vty, "%s", ifp->name); vty_out(vty, "%*s", (16 - len), " "); if (if_is_up(ifp)) { vty_out(vty, "up "); if (CHECK_FLAG(ifp->status, ZEBRA_INTERFACE_LINKDETECTION)) { if (if_is_running(ifp)) vty_out(vty, "up "); else vty_out(vty, "down "); } else { vty_out(vty, "unknown "); } } else { vty_out(vty, "down down "); } if (ifp->desc) { intf_desc = true; vty_out(vty, "%s", ifp->desc); } zif = ifp->info; if (zif && zif->desc) { vty_out(vty, "%s%s", intf_desc ? "\n " : "", zif->desc); } vty_out(vty, "\n"); } } DEFUN (show_interface_desc, show_interface_desc_cmd, "show interface description [vrf NAME]", SHOW_STR "Interface status and configuration\n" "Interface description\n" VRF_CMD_HELP_STR) { vrf_id_t vrf_id = VRF_DEFAULT; if (argc > 3) VRF_GET_ID(vrf_id, argv[4]->arg, false); if_show_description(vty, vrf_id); return CMD_SUCCESS; } DEFUN (show_interface_desc_vrf_all, show_interface_desc_vrf_all_cmd, "show interface description vrf all", SHOW_STR "Interface status and configuration\n" "Interface description\n" VRF_ALL_CMD_HELP_STR) { struct vrf *vrf; RB_FOREACH (vrf, vrf_name_head, &vrfs_by_name) if (!RB_EMPTY(if_name_head, &vrf->ifaces_by_name)) { vty_out(vty, "\n\tVRF %u\n\n", vrf->vrf_id); if_show_description(vty, vrf->vrf_id); } return CMD_SUCCESS; } DEFUN (multicast, multicast_cmd, "multicast", "Set multicast flag to interface\n") { VTY_DECLVAR_CONTEXT(interface, ifp); int ret; struct zebra_if *if_data; if (CHECK_FLAG(ifp->status, ZEBRA_INTERFACE_ACTIVE)) { ret = if_set_flags(ifp, IFF_MULTICAST); if (ret < 0) { vty_out(vty, "Can't set multicast flag\n"); return CMD_WARNING_CONFIG_FAILED; } if_refresh(ifp); } if_data = ifp->info; if_data->multicast = IF_ZEBRA_MULTICAST_ON; return CMD_SUCCESS; } DEFUN (no_multicast, no_multicast_cmd, "no multicast", NO_STR "Unset multicast flag to interface\n") { VTY_DECLVAR_CONTEXT(interface, ifp); int ret; struct zebra_if *if_data; if (CHECK_FLAG(ifp->status, ZEBRA_INTERFACE_ACTIVE)) { ret = if_unset_flags(ifp, IFF_MULTICAST); if (ret < 0) { vty_out(vty, "Can't unset multicast flag\n"); return CMD_WARNING_CONFIG_FAILED; } if_refresh(ifp); } if_data = ifp->info; if_data->multicast = IF_ZEBRA_MULTICAST_OFF; return CMD_SUCCESS; } DEFUN (linkdetect, linkdetect_cmd, "link-detect", "Enable link detection on interface\n") { VTY_DECLVAR_CONTEXT(interface, ifp); int if_was_operative; if_was_operative = if_is_no_ptm_operative(ifp); SET_FLAG(ifp->status, ZEBRA_INTERFACE_LINKDETECTION); /* When linkdetection is enabled, if might come down */ if (!if_is_no_ptm_operative(ifp) && if_was_operative) if_down(ifp); /* FIXME: Will defer status change forwarding if interface does not come down! */ return CMD_SUCCESS; } DEFUN (no_linkdetect, no_linkdetect_cmd, "no link-detect", NO_STR "Disable link detection on interface\n") { VTY_DECLVAR_CONTEXT(interface, ifp); int if_was_operative; if_was_operative = if_is_no_ptm_operative(ifp); UNSET_FLAG(ifp->status, ZEBRA_INTERFACE_LINKDETECTION); /* Interface may come up after disabling link detection */ if (if_is_operative(ifp) && !if_was_operative) if_up(ifp); /* FIXME: see linkdetect_cmd */ return CMD_SUCCESS; } DEFUN (shutdown_if, shutdown_if_cmd, "shutdown", "Shutdown the selected interface\n") { VTY_DECLVAR_CONTEXT(interface, ifp); int ret; struct zebra_if *if_data; if (ifp->ifindex != IFINDEX_INTERNAL) { ret = if_unset_flags(ifp, IFF_UP); if (ret < 0) { vty_out(vty, "Can't shutdown interface\n"); return CMD_WARNING_CONFIG_FAILED; } if_refresh(ifp); } if_data = ifp->info; if_data->shutdown = IF_ZEBRA_SHUTDOWN_ON; return CMD_SUCCESS; } DEFUN (no_shutdown_if, no_shutdown_if_cmd, "no shutdown", NO_STR "Shutdown the selected interface\n") { VTY_DECLVAR_CONTEXT(interface, ifp); int ret; struct zebra_if *if_data; if (ifp->ifindex != IFINDEX_INTERNAL) { ret = if_set_flags(ifp, IFF_UP | IFF_RUNNING); if (ret < 0) { vty_out(vty, "Can't up interface\n"); return CMD_WARNING_CONFIG_FAILED; } if_refresh(ifp); /* Some addresses (in particular, IPv6 addresses on Linux) get * removed when the interface goes down. They need to be * readded. */ if_addr_wakeup(ifp); } if_data = ifp->info; if_data->shutdown = IF_ZEBRA_SHUTDOWN_OFF; return CMD_SUCCESS; } DEFUN (bandwidth_if, bandwidth_if_cmd, "bandwidth (1-100000)", "Set bandwidth informational parameter\n" "Bandwidth in megabits\n") { int idx_number = 1; VTY_DECLVAR_CONTEXT(interface, ifp); unsigned int bandwidth; bandwidth = strtol(argv[idx_number]->arg, NULL, 10); /* bandwidth range is <1-100000> */ if (bandwidth < 1 || bandwidth > 100000) { vty_out(vty, "Bandwidth is invalid\n"); return CMD_WARNING_CONFIG_FAILED; } ifp->bandwidth = bandwidth; /* force protocols to recalculate routes due to cost change */ if (if_is_operative(ifp)) zebra_interface_up_update(ifp); return CMD_SUCCESS; } DEFUN (no_bandwidth_if, no_bandwidth_if_cmd, "no bandwidth [(1-100000)]", NO_STR "Set bandwidth informational parameter\n" "Bandwidth in megabits\n") { VTY_DECLVAR_CONTEXT(interface, ifp); ifp->bandwidth = 0; /* force protocols to recalculate routes due to cost change */ if (if_is_operative(ifp)) zebra_interface_up_update(ifp); return CMD_SUCCESS; } struct cmd_node link_params_node = { LINK_PARAMS_NODE, "%s(config-link-params)# ", 1, }; static void link_param_cmd_set_uint32(struct interface *ifp, uint32_t *field, uint32_t type, uint32_t value) { /* Update field as needed */ if (IS_PARAM_UNSET(ifp->link_params, type) || *field != value) { *field = value; SET_PARAM(ifp->link_params, type); /* force protocols to update LINK STATE due to parameters change */ if (if_is_operative(ifp)) zebra_interface_parameters_update(ifp); } } static void link_param_cmd_set_float(struct interface *ifp, float *field, uint32_t type, float value) { /* Update field as needed */ if (IS_PARAM_UNSET(ifp->link_params, type) || *field != value) { *field = value; SET_PARAM(ifp->link_params, type); /* force protocols to update LINK STATE due to parameters change */ if (if_is_operative(ifp)) zebra_interface_parameters_update(ifp); } } static void link_param_cmd_unset(struct interface *ifp, uint32_t type) { if (ifp->link_params == NULL) return; /* Unset field */ UNSET_PARAM(ifp->link_params, type); /* force protocols to update LINK STATE due to parameters change */ if (if_is_operative(ifp)) zebra_interface_parameters_update(ifp); } DEFUN_NOSH (link_params, link_params_cmd, "link-params", LINK_PARAMS_STR) { /* vty->qobj_index stays the same @ interface pointer */ vty->node = LINK_PARAMS_NODE; return CMD_SUCCESS; } DEFUN_NOSH (exit_link_params, exit_link_params_cmd, "exit-link-params", "Exit from Link Params configuration mode\n") { if (vty->node == LINK_PARAMS_NODE) vty->node = INTERFACE_NODE; return CMD_SUCCESS; } /* Specific Traffic Engineering parameters commands */ DEFUN (link_params_enable, link_params_enable_cmd, "enable", "Activate link parameters on this interface\n") { VTY_DECLVAR_CONTEXT(interface, ifp); /* This command could be issue at startup, when activate MPLS TE */ /* on a new interface or after a ON / OFF / ON toggle */ /* In all case, TE parameters are reset to their default factory */ if (IS_ZEBRA_DEBUG_EVENT) zlog_debug( "Link-params: enable TE link parameters on interface %s", ifp->name); if (!if_link_params_get(ifp)) { if (IS_ZEBRA_DEBUG_EVENT) zlog_debug( "Link-params: failed to init TE link parameters %s", ifp->name); return CMD_WARNING_CONFIG_FAILED; } /* force protocols to update LINK STATE due to parameters change */ if (if_is_operative(ifp)) zebra_interface_parameters_update(ifp); return CMD_SUCCESS; } DEFUN (no_link_params_enable, no_link_params_enable_cmd, "no enable", NO_STR "Disable link parameters on this interface\n") { VTY_DECLVAR_CONTEXT(interface, ifp); zlog_debug("MPLS-TE: disable TE link parameters on interface %s", ifp->name); if_link_params_free(ifp); /* force protocols to update LINK STATE due to parameters change */ if (if_is_operative(ifp)) zebra_interface_parameters_update(ifp); return CMD_SUCCESS; } /* STANDARD TE metrics */ DEFUN (link_params_metric, link_params_metric_cmd, "metric (0-4294967295)", "Link metric for MPLS-TE purpose\n" "Metric value in decimal\n") { int idx_number = 1; VTY_DECLVAR_CONTEXT(interface, ifp); struct if_link_params *iflp = if_link_params_get(ifp); uint32_t metric; metric = strtoul(argv[idx_number]->arg, NULL, 10); /* Update TE metric if needed */ link_param_cmd_set_uint32(ifp, &iflp->te_metric, LP_TE_METRIC, metric); return CMD_SUCCESS; } DEFUN (no_link_params_metric, no_link_params_metric_cmd, "no metric", NO_STR "Disable Link Metric on this interface\n") { VTY_DECLVAR_CONTEXT(interface, ifp); /* Unset TE Metric */ link_param_cmd_unset(ifp, LP_TE_METRIC); return CMD_SUCCESS; } DEFUN (link_params_maxbw, link_params_maxbw_cmd, "max-bw BANDWIDTH", "Maximum bandwidth that can be used\n" "Bytes/second (IEEE floating point format)\n") { int idx_bandwidth = 1; VTY_DECLVAR_CONTEXT(interface, ifp); struct if_link_params *iflp = if_link_params_get(ifp); float bw; if (sscanf(argv[idx_bandwidth]->arg, "%g", &bw) != 1) { vty_out(vty, "link_params_maxbw: fscanf: %s\n", safe_strerror(errno)); return CMD_WARNING_CONFIG_FAILED; } /* Check that Maximum bandwidth is not lower than other bandwidth * parameters */ if ((bw <= iflp->max_rsv_bw) || (bw <= iflp->unrsv_bw[0]) || (bw <= iflp->unrsv_bw[1]) || (bw <= iflp->unrsv_bw[2]) || (bw <= iflp->unrsv_bw[3]) || (bw <= iflp->unrsv_bw[4]) || (bw <= iflp->unrsv_bw[5]) || (bw <= iflp->unrsv_bw[6]) || (bw <= iflp->unrsv_bw[7]) || (bw <= iflp->ava_bw) || (bw <= iflp->res_bw) || (bw <= iflp->use_bw)) { vty_out(vty, "Maximum Bandwidth could not be lower than others bandwidth\n"); return CMD_WARNING_CONFIG_FAILED; } /* Update Maximum Bandwidth if needed */ link_param_cmd_set_float(ifp, &iflp->max_bw, LP_MAX_BW, bw); return CMD_SUCCESS; } DEFUN (link_params_max_rsv_bw, link_params_max_rsv_bw_cmd, "max-rsv-bw BANDWIDTH", "Maximum bandwidth that may be reserved\n" "Bytes/second (IEEE floating point format)\n") { int idx_bandwidth = 1; VTY_DECLVAR_CONTEXT(interface, ifp); struct if_link_params *iflp = if_link_params_get(ifp); float bw; if (sscanf(argv[idx_bandwidth]->arg, "%g", &bw) != 1) { vty_out(vty, "link_params_max_rsv_bw: fscanf: %s\n", safe_strerror(errno)); return CMD_WARNING_CONFIG_FAILED; } /* Check that bandwidth is not greater than maximum bandwidth parameter */ if (bw > iflp->max_bw) { vty_out(vty, "Maximum Reservable Bandwidth could not be greater than Maximum Bandwidth (%g)\n", iflp->max_bw); return CMD_WARNING_CONFIG_FAILED; } /* Update Maximum Reservable Bandwidth if needed */ link_param_cmd_set_float(ifp, &iflp->max_rsv_bw, LP_MAX_RSV_BW, bw); return CMD_SUCCESS; } DEFUN (link_params_unrsv_bw, link_params_unrsv_bw_cmd, "unrsv-bw (0-7) BANDWIDTH", "Unreserved bandwidth at each priority level\n" "Priority\n" "Bytes/second (IEEE floating point format)\n") { int idx_number = 1; int idx_bandwidth = 2; VTY_DECLVAR_CONTEXT(interface, ifp); struct if_link_params *iflp = if_link_params_get(ifp); int priority; float bw; /* We don't have to consider about range check here. */ if (sscanf(argv[idx_number]->arg, "%d", &priority) != 1) { vty_out(vty, "link_params_unrsv_bw: fscanf: %s\n", safe_strerror(errno)); return CMD_WARNING_CONFIG_FAILED; } if (sscanf(argv[idx_bandwidth]->arg, "%g", &bw) != 1) { vty_out(vty, "link_params_unrsv_bw: fscanf: %s\n", safe_strerror(errno)); return CMD_WARNING_CONFIG_FAILED; } /* Check that bandwidth is not greater than maximum bandwidth parameter */ if (bw > iflp->max_bw) { vty_out(vty, "UnReserved Bandwidth could not be greater than Maximum Bandwidth (%g)\n", iflp->max_bw); return CMD_WARNING_CONFIG_FAILED; } /* Update Unreserved Bandwidth if needed */ link_param_cmd_set_float(ifp, &iflp->unrsv_bw[priority], LP_UNRSV_BW, bw); return CMD_SUCCESS; } DEFUN (link_params_admin_grp, link_params_admin_grp_cmd, "admin-grp BITPATTERN", "Administrative group membership\n" "32-bit Hexadecimal value (e.g. 0xa1)\n") { int idx_bitpattern = 1; VTY_DECLVAR_CONTEXT(interface, ifp); struct if_link_params *iflp = if_link_params_get(ifp); unsigned long value; if (sscanf(argv[idx_bitpattern]->arg, "0x%lx", &value) != 1) { vty_out(vty, "link_params_admin_grp: fscanf: %s\n", safe_strerror(errno)); return CMD_WARNING_CONFIG_FAILED; } /* Update Administrative Group if needed */ link_param_cmd_set_uint32(ifp, &iflp->admin_grp, LP_ADM_GRP, value); return CMD_SUCCESS; } DEFUN (no_link_params_admin_grp, no_link_params_admin_grp_cmd, "no admin-grp", NO_STR "Disable Administrative group membership on this interface\n") { VTY_DECLVAR_CONTEXT(interface, ifp); /* Unset Admin Group */ link_param_cmd_unset(ifp, LP_ADM_GRP); return CMD_SUCCESS; } /* RFC5392 & RFC5316: INTER-AS */ DEFUN (link_params_inter_as, link_params_inter_as_cmd, "neighbor A.B.C.D as (1-4294967295)", "Configure remote ASBR information (Neighbor IP address and AS number)\n" "Remote IP address in dot decimal A.B.C.D\n" "Remote AS number\n" "AS number in the range <1-4294967295>\n") { int idx_ipv4 = 1; int idx_number = 3; VTY_DECLVAR_CONTEXT(interface, ifp); struct if_link_params *iflp = if_link_params_get(ifp); struct in_addr addr; uint32_t as; if (!inet_aton(argv[idx_ipv4]->arg, &addr)) { vty_out(vty, "Please specify Router-Addr by A.B.C.D\n"); return CMD_WARNING_CONFIG_FAILED; } as = strtoul(argv[idx_number]->arg, NULL, 10); /* Update Remote IP and Remote AS fields if needed */ if (IS_PARAM_UNSET(iflp, LP_RMT_AS) || iflp->rmt_as != as || iflp->rmt_ip.s_addr != addr.s_addr) { iflp->rmt_as = as; iflp->rmt_ip.s_addr = addr.s_addr; SET_PARAM(iflp, LP_RMT_AS); /* force protocols to update LINK STATE due to parameters change */ if (if_is_operative(ifp)) zebra_interface_parameters_update(ifp); } return CMD_SUCCESS; } DEFUN (no_link_params_inter_as, no_link_params_inter_as_cmd, "no neighbor", NO_STR "Remove Neighbor IP address and AS number for Inter-AS TE\n") { VTY_DECLVAR_CONTEXT(interface, ifp); struct if_link_params *iflp = if_link_params_get(ifp); /* Reset Remote IP and AS neighbor */ iflp->rmt_as = 0; iflp->rmt_ip.s_addr = 0; UNSET_PARAM(iflp, LP_RMT_AS); /* force protocols to update LINK STATE due to parameters change */ if (if_is_operative(ifp)) zebra_interface_parameters_update(ifp); return CMD_SUCCESS; } /* RFC7471: OSPF Traffic Engineering (TE) Metric extensions & * draft-ietf-isis-metric-extensions-07.txt */ DEFUN (link_params_delay, link_params_delay_cmd, "delay (0-16777215) [min (0-16777215) max (0-16777215)]", "Unidirectional Average Link Delay\n" "Average delay in micro-second as decimal (0...16777215)\n" "Minimum delay\n" "Minimum delay in micro-second as decimal (0...16777215)\n" "Maximum delay\n" "Maximum delay in micro-second as decimal (0...16777215)\n") { /* Get and Check new delay values */ uint32_t delay = 0, low = 0, high = 0; delay = strtoul(argv[1]->arg, NULL, 10); if (argc == 6) { low = strtoul(argv[3]->arg, NULL, 10); high = strtoul(argv[5]->arg, NULL, 10); } VTY_DECLVAR_CONTEXT(interface, ifp); struct if_link_params *iflp = if_link_params_get(ifp); uint8_t update = 0; if (argc == 2) { /* Check new delay value against old Min and Max delays if set */ if (IS_PARAM_SET(iflp, LP_MM_DELAY) && (delay <= iflp->min_delay || delay >= iflp->max_delay)) { vty_out(vty, "Average delay should be comprise between Min (%d) and Max (%d) delay\n", iflp->min_delay, iflp->max_delay); return CMD_WARNING_CONFIG_FAILED; } /* Update delay if value is not set or change */ if (IS_PARAM_UNSET(iflp, LP_DELAY) || iflp->av_delay != delay) { iflp->av_delay = delay; SET_PARAM(iflp, LP_DELAY); update = 1; } /* Unset Min and Max delays if already set */ if (IS_PARAM_SET(iflp, LP_MM_DELAY)) { iflp->min_delay = 0; iflp->max_delay = 0; UNSET_PARAM(iflp, LP_MM_DELAY); update = 1; } } else { /* Check new delays value coherency */ if (delay <= low || delay >= high) { vty_out(vty, "Average delay should be comprise between Min (%d) and Max (%d) delay\n", low, high); return CMD_WARNING_CONFIG_FAILED; } /* Update Delays if needed */ if (IS_PARAM_UNSET(iflp, LP_DELAY) || IS_PARAM_UNSET(iflp, LP_MM_DELAY) || iflp->av_delay != delay || iflp->min_delay != low || iflp->max_delay != high) { iflp->av_delay = delay; SET_PARAM(iflp, LP_DELAY); iflp->min_delay = low; iflp->max_delay = high; SET_PARAM(iflp, LP_MM_DELAY); update = 1; } } /* force protocols to update LINK STATE due to parameters change */ if (update == 1 && if_is_operative(ifp)) zebra_interface_parameters_update(ifp); return CMD_SUCCESS; } DEFUN (no_link_params_delay, no_link_params_delay_cmd, "no delay", NO_STR "Disable Unidirectional Average, Min & Max Link Delay on this interface\n") { VTY_DECLVAR_CONTEXT(interface, ifp); struct if_link_params *iflp = if_link_params_get(ifp); /* Unset Delays */ iflp->av_delay = 0; UNSET_PARAM(iflp, LP_DELAY); iflp->min_delay = 0; iflp->max_delay = 0; UNSET_PARAM(iflp, LP_MM_DELAY); /* force protocols to update LINK STATE due to parameters change */ if (if_is_operative(ifp)) zebra_interface_parameters_update(ifp); return CMD_SUCCESS; } DEFUN (link_params_delay_var, link_params_delay_var_cmd, "delay-variation (0-16777215)", "Unidirectional Link Delay Variation\n" "delay variation in micro-second as decimal (0...16777215)\n") { int idx_number = 1; VTY_DECLVAR_CONTEXT(interface, ifp); struct if_link_params *iflp = if_link_params_get(ifp); uint32_t value; value = strtoul(argv[idx_number]->arg, NULL, 10); /* Update Delay Variation if needed */ link_param_cmd_set_uint32(ifp, &iflp->delay_var, LP_DELAY_VAR, value); return CMD_SUCCESS; } DEFUN (no_link_params_delay_var, no_link_params_delay_var_cmd, "no delay-variation", NO_STR "Disable Unidirectional Delay Variation on this interface\n") { VTY_DECLVAR_CONTEXT(interface, ifp); /* Unset Delay Variation */ link_param_cmd_unset(ifp, LP_DELAY_VAR); return CMD_SUCCESS; } DEFUN (link_params_pkt_loss, link_params_pkt_loss_cmd, "packet-loss PERCENTAGE", "Unidirectional Link Packet Loss\n" "percentage of total traffic by 0.000003% step and less than 50.331642%\n") { int idx_percentage = 1; VTY_DECLVAR_CONTEXT(interface, ifp); struct if_link_params *iflp = if_link_params_get(ifp); float fval; if (sscanf(argv[idx_percentage]->arg, "%g", &fval) != 1) { vty_out(vty, "link_params_pkt_loss: fscanf: %s\n", safe_strerror(errno)); return CMD_WARNING_CONFIG_FAILED; } if (fval > MAX_PKT_LOSS) fval = MAX_PKT_LOSS; /* Update Packet Loss if needed */ link_param_cmd_set_float(ifp, &iflp->pkt_loss, LP_PKT_LOSS, fval); return CMD_SUCCESS; } DEFUN (no_link_params_pkt_loss, no_link_params_pkt_loss_cmd, "no packet-loss", NO_STR "Disable Unidirectional Link Packet Loss on this interface\n") { VTY_DECLVAR_CONTEXT(interface, ifp); /* Unset Packet Loss */ link_param_cmd_unset(ifp, LP_PKT_LOSS); return CMD_SUCCESS; } DEFUN (link_params_res_bw, link_params_res_bw_cmd, "res-bw BANDWIDTH", "Unidirectional Residual Bandwidth\n" "Bytes/second (IEEE floating point format)\n") { int idx_bandwidth = 1; VTY_DECLVAR_CONTEXT(interface, ifp); struct if_link_params *iflp = if_link_params_get(ifp); float bw; if (sscanf(argv[idx_bandwidth]->arg, "%g", &bw) != 1) { vty_out(vty, "link_params_res_bw: fscanf: %s\n", safe_strerror(errno)); return CMD_WARNING_CONFIG_FAILED; } /* Check that bandwidth is not greater than maximum bandwidth parameter */ if (bw > iflp->max_bw) { vty_out(vty, "Residual Bandwidth could not be greater than Maximum Bandwidth (%g)\n", iflp->max_bw); return CMD_WARNING_CONFIG_FAILED; } /* Update Residual Bandwidth if needed */ link_param_cmd_set_float(ifp, &iflp->res_bw, LP_RES_BW, bw); return CMD_SUCCESS; } DEFUN (no_link_params_res_bw, no_link_params_res_bw_cmd, "no res-bw", NO_STR "Disable Unidirectional Residual Bandwidth on this interface\n") { VTY_DECLVAR_CONTEXT(interface, ifp); /* Unset Residual Bandwidth */ link_param_cmd_unset(ifp, LP_RES_BW); return CMD_SUCCESS; } DEFUN (link_params_ava_bw, link_params_ava_bw_cmd, "ava-bw BANDWIDTH", "Unidirectional Available Bandwidth\n" "Bytes/second (IEEE floating point format)\n") { int idx_bandwidth = 1; VTY_DECLVAR_CONTEXT(interface, ifp); struct if_link_params *iflp = if_link_params_get(ifp); float bw; if (sscanf(argv[idx_bandwidth]->arg, "%g", &bw) != 1) { vty_out(vty, "link_params_ava_bw: fscanf: %s\n", safe_strerror(errno)); return CMD_WARNING_CONFIG_FAILED; } /* Check that bandwidth is not greater than maximum bandwidth parameter */ if (bw > iflp->max_bw) { vty_out(vty, "Available Bandwidth could not be greater than Maximum Bandwidth (%g)\n", iflp->max_bw); return CMD_WARNING_CONFIG_FAILED; } /* Update Residual Bandwidth if needed */ link_param_cmd_set_float(ifp, &iflp->ava_bw, LP_AVA_BW, bw); return CMD_SUCCESS; } DEFUN (no_link_params_ava_bw, no_link_params_ava_bw_cmd, "no ava-bw", NO_STR "Disable Unidirectional Available Bandwidth on this interface\n") { VTY_DECLVAR_CONTEXT(interface, ifp); /* Unset Available Bandwidth */ link_param_cmd_unset(ifp, LP_AVA_BW); return CMD_SUCCESS; } DEFUN (link_params_use_bw, link_params_use_bw_cmd, "use-bw BANDWIDTH", "Unidirectional Utilised Bandwidth\n" "Bytes/second (IEEE floating point format)\n") { int idx_bandwidth = 1; VTY_DECLVAR_CONTEXT(interface, ifp); struct if_link_params *iflp = if_link_params_get(ifp); float bw; if (sscanf(argv[idx_bandwidth]->arg, "%g", &bw) != 1) { vty_out(vty, "link_params_use_bw: fscanf: %s\n", safe_strerror(errno)); return CMD_WARNING_CONFIG_FAILED; } /* Check that bandwidth is not greater than maximum bandwidth parameter */ if (bw > iflp->max_bw) { vty_out(vty, "Utilised Bandwidth could not be greater than Maximum Bandwidth (%g)\n", iflp->max_bw); return CMD_WARNING_CONFIG_FAILED; } /* Update Utilized Bandwidth if needed */ link_param_cmd_set_float(ifp, &iflp->use_bw, LP_USE_BW, bw); return CMD_SUCCESS; } DEFUN (no_link_params_use_bw, no_link_params_use_bw_cmd, "no use-bw", NO_STR "Disable Unidirectional Utilised Bandwidth on this interface\n") { VTY_DECLVAR_CONTEXT(interface, ifp); /* Unset Utilised Bandwidth */ link_param_cmd_unset(ifp, LP_USE_BW); return CMD_SUCCESS; } static int ip_address_install(struct vty *vty, struct interface *ifp, const char *addr_str, const char *peer_str, const char *label) { struct zebra_if *if_data; struct prefix_ipv4 lp, pp; struct connected *ifc; struct prefix_ipv4 *p; int ret; enum zebra_dplane_result dplane_res; if_data = ifp->info; ret = str2prefix_ipv4(addr_str, &lp); if (ret <= 0) { vty_out(vty, "%% Malformed address \n"); return CMD_WARNING_CONFIG_FAILED; } if (ipv4_martian(&lp.prefix)) { vty_out(vty, "%% Invalid address\n"); return CMD_WARNING_CONFIG_FAILED; } if (peer_str) { if (lp.prefixlen != 32) { vty_out(vty, "%% Local prefix length for P-t-P address must be /32\n"); return CMD_WARNING_CONFIG_FAILED; } ret = str2prefix_ipv4(peer_str, &pp); if (ret <= 0) { vty_out(vty, "%% Malformed peer address\n"); return CMD_WARNING_CONFIG_FAILED; } } ifc = connected_check_ptp(ifp, &lp, peer_str ? &pp : NULL); if (!ifc) { ifc = connected_new(); ifc->ifp = ifp; /* Address. */ p = prefix_ipv4_new(); *p = lp; ifc->address = (struct prefix *)p; if (peer_str) { SET_FLAG(ifc->flags, ZEBRA_IFA_PEER); p = prefix_ipv4_new(); *p = pp; ifc->destination = (struct prefix *)p; } /* Label. */ if (label) ifc->label = XSTRDUP(MTYPE_CONNECTED_LABEL, label); /* Add to linked list. */ listnode_add(ifp->connected, ifc); } /* This address is configured from zebra. */ if (!CHECK_FLAG(ifc->conf, ZEBRA_IFC_CONFIGURED)) SET_FLAG(ifc->conf, ZEBRA_IFC_CONFIGURED); /* In case of this route need to install kernel. */ if (!CHECK_FLAG(ifc->conf, ZEBRA_IFC_QUEUED) && CHECK_FLAG(ifp->status, ZEBRA_INTERFACE_ACTIVE) && !(if_data && if_data->shutdown == IF_ZEBRA_SHUTDOWN_ON)) { /* Some system need to up the interface to set IP address. */ if (!if_is_up(ifp)) { if_set_flags(ifp, IFF_UP | IFF_RUNNING); if_refresh(ifp); } dplane_res = dplane_intf_addr_set(ifp, ifc); if (dplane_res == ZEBRA_DPLANE_REQUEST_FAILURE) { vty_out(vty, "%% Can't set interface IP address: %s.\n", dplane_res2str(dplane_res)); return CMD_WARNING_CONFIG_FAILED; } SET_FLAG(ifc->conf, ZEBRA_IFC_QUEUED); /* The address will be advertised to zebra clients when the * notification * from the kernel has been received. * It will also be added to the subnet chain list, then. */ } return CMD_SUCCESS; } static int ip_address_uninstall(struct vty *vty, struct interface *ifp, const char *addr_str, const char *peer_str, const char *label) { struct prefix_ipv4 lp, pp; struct connected *ifc; int ret; enum zebra_dplane_result dplane_res; /* Convert to prefix structure. */ ret = str2prefix_ipv4(addr_str, &lp); if (ret <= 0) { vty_out(vty, "%% Malformed address \n"); return CMD_WARNING_CONFIG_FAILED; } if (peer_str) { if (lp.prefixlen != 32) { vty_out(vty, "%% Local prefix length for P-t-P address must be /32\n"); return CMD_WARNING_CONFIG_FAILED; } ret = str2prefix_ipv4(peer_str, &pp); if (ret <= 0) { vty_out(vty, "%% Malformed peer address\n"); return CMD_WARNING_CONFIG_FAILED; } } /* Check current interface address. */ ifc = connected_check_ptp(ifp, &lp, peer_str ? &pp : NULL); if (!ifc) { vty_out(vty, "%% Can't find address\n"); return CMD_WARNING_CONFIG_FAILED; } /* This is not configured address. */ if (!CHECK_FLAG(ifc->conf, ZEBRA_IFC_CONFIGURED)) return CMD_WARNING_CONFIG_FAILED; UNSET_FLAG(ifc->conf, ZEBRA_IFC_CONFIGURED); /* This is not real address or interface is not active. */ if (!CHECK_FLAG(ifc->conf, ZEBRA_IFC_QUEUED) || !CHECK_FLAG(ifp->status, ZEBRA_INTERFACE_ACTIVE)) { listnode_delete(ifp->connected, ifc); connected_free(ifc); return CMD_WARNING_CONFIG_FAILED; } /* This is real route. */ dplane_res = dplane_intf_addr_unset(ifp, ifc); if (dplane_res == ZEBRA_DPLANE_REQUEST_FAILURE) { vty_out(vty, "%% Can't unset interface IP address: %s.\n", dplane_res2str(dplane_res)); return CMD_WARNING_CONFIG_FAILED; } UNSET_FLAG(ifc->conf, ZEBRA_IFC_QUEUED); /* we will receive a kernel notification about this route being removed. * this will trigger its removal from the connected list. */ return CMD_SUCCESS; } DEFUN (ip_address, ip_address_cmd, "ip address A.B.C.D/M", "Interface Internet Protocol config commands\n" "Set the IP address of an interface\n" "IP address (e.g. 10.0.0.1/8)\n") { int idx_ipv4_prefixlen = 2; VTY_DECLVAR_CONTEXT(interface, ifp); return ip_address_install(vty, ifp, argv[idx_ipv4_prefixlen]->arg, NULL, NULL); } DEFUN (no_ip_address, no_ip_address_cmd, "no ip address A.B.C.D/M", NO_STR "Interface Internet Protocol config commands\n" "Set the IP address of an interface\n" "IP Address (e.g. 10.0.0.1/8)\n") { int idx_ipv4_prefixlen = 3; VTY_DECLVAR_CONTEXT(interface, ifp); return ip_address_uninstall(vty, ifp, argv[idx_ipv4_prefixlen]->arg, NULL, NULL); } DEFUN(ip_address_peer, ip_address_peer_cmd, "ip address A.B.C.D peer A.B.C.D/M", "Interface Internet Protocol config commands\n" "Set the IP address of an interface\n" "Local IP (e.g. 10.0.0.1) for P-t-P address\n" "Specify P-t-P address\n" "Peer IP address (e.g. 10.0.0.1/8)\n") { VTY_DECLVAR_CONTEXT(interface, ifp); return ip_address_install(vty, ifp, argv[2]->arg, argv[4]->arg, NULL); } DEFUN(no_ip_address_peer, no_ip_address_peer_cmd, "no ip address A.B.C.D peer A.B.C.D/M", NO_STR "Interface Internet Protocol config commands\n" "Set the IP address of an interface\n" "Local IP (e.g. 10.0.0.1) for P-t-P address\n" "Specify P-t-P address\n" "Peer IP address (e.g. 10.0.0.1/8)\n") { VTY_DECLVAR_CONTEXT(interface, ifp); return ip_address_uninstall(vty, ifp, argv[3]->arg, argv[5]->arg, NULL); } #ifdef HAVE_NETLINK DEFUN (ip_address_label, ip_address_label_cmd, "ip address A.B.C.D/M label LINE", "Interface Internet Protocol config commands\n" "Set the IP address of an interface\n" "IP address (e.g. 10.0.0.1/8)\n" "Label of this address\n" "Label\n") { int idx_ipv4_prefixlen = 2; int idx_line = 4; VTY_DECLVAR_CONTEXT(interface, ifp); return ip_address_install(vty, ifp, argv[idx_ipv4_prefixlen]->arg, NULL, argv[idx_line]->arg); } DEFUN (no_ip_address_label, no_ip_address_label_cmd, "no ip address A.B.C.D/M label LINE", NO_STR "Interface Internet Protocol config commands\n" "Set the IP address of an interface\n" "IP address (e.g. 10.0.0.1/8)\n" "Label of this address\n" "Label\n") { int idx_ipv4_prefixlen = 3; int idx_line = 5; VTY_DECLVAR_CONTEXT(interface, ifp); return ip_address_uninstall(vty, ifp, argv[idx_ipv4_prefixlen]->arg, NULL, argv[idx_line]->arg); } #endif /* HAVE_NETLINK */ static int ipv6_address_install(struct vty *vty, struct interface *ifp, const char *addr_str, const char *peer_str, const char *label) { struct zebra_if *if_data; struct prefix_ipv6 cp; struct connected *ifc; struct prefix_ipv6 *p; int ret; enum zebra_dplane_result dplane_res; if_data = ifp->info; ret = str2prefix_ipv6(addr_str, &cp); if (ret <= 0) { vty_out(vty, "%% Malformed address \n"); return CMD_WARNING_CONFIG_FAILED; } if (ipv6_martian(&cp.prefix)) { vty_out(vty, "%% Invalid address\n"); return CMD_WARNING_CONFIG_FAILED; } ifc = connected_check(ifp, (struct prefix *)&cp); if (!ifc) { ifc = connected_new(); ifc->ifp = ifp; /* Address. */ p = prefix_ipv6_new(); *p = cp; ifc->address = (struct prefix *)p; /* Label. */ if (label) ifc->label = XSTRDUP(MTYPE_CONNECTED_LABEL, label); /* Add to linked list. */ listnode_add(ifp->connected, ifc); } /* This address is configured from zebra. */ if (!CHECK_FLAG(ifc->conf, ZEBRA_IFC_CONFIGURED)) SET_FLAG(ifc->conf, ZEBRA_IFC_CONFIGURED); /* In case of this route need to install kernel. */ if (!CHECK_FLAG(ifc->conf, ZEBRA_IFC_QUEUED) && CHECK_FLAG(ifp->status, ZEBRA_INTERFACE_ACTIVE) && !(if_data && if_data->shutdown == IF_ZEBRA_SHUTDOWN_ON)) { /* Some system need to up the interface to set IP address. */ if (!if_is_up(ifp)) { if_set_flags(ifp, IFF_UP | IFF_RUNNING); if_refresh(ifp); } dplane_res = dplane_intf_addr_set(ifp, ifc); if (dplane_res == ZEBRA_DPLANE_REQUEST_FAILURE) { vty_out(vty, "%% Can't set interface IP address: %s.\n", dplane_res2str(dplane_res)); return CMD_WARNING_CONFIG_FAILED; } SET_FLAG(ifc->conf, ZEBRA_IFC_QUEUED); /* The address will be advertised to zebra clients when the * notification * from the kernel has been received. */ } return CMD_SUCCESS; } /* Return true if an ipv6 address is configured on ifp */ int ipv6_address_configured(struct interface *ifp) { struct connected *connected; struct listnode *node; for (ALL_LIST_ELEMENTS_RO(ifp->connected, node, connected)) if (CHECK_FLAG(connected->conf, ZEBRA_IFC_REAL) && (connected->address->family == AF_INET6)) return 1; return 0; } static int ipv6_address_uninstall(struct vty *vty, struct interface *ifp, const char *addr_str, const char *peer_str, const char *label) { struct prefix_ipv6 cp; struct connected *ifc; int ret; enum zebra_dplane_result dplane_res; /* Convert to prefix structure. */ ret = str2prefix_ipv6(addr_str, &cp); if (ret <= 0) { vty_out(vty, "%% Malformed address \n"); return CMD_WARNING_CONFIG_FAILED; } /* Check current interface address. */ ifc = connected_check(ifp, (struct prefix *)&cp); if (!ifc) { vty_out(vty, "%% Can't find address\n"); return CMD_WARNING_CONFIG_FAILED; } /* This is not configured address. */ if (!CHECK_FLAG(ifc->conf, ZEBRA_IFC_CONFIGURED)) return CMD_WARNING_CONFIG_FAILED; UNSET_FLAG(ifc->conf, ZEBRA_IFC_CONFIGURED); /* This is not real address or interface is not active. */ if (!CHECK_FLAG(ifc->conf, ZEBRA_IFC_QUEUED) || !CHECK_FLAG(ifp->status, ZEBRA_INTERFACE_ACTIVE)) { listnode_delete(ifp->connected, ifc); connected_free(ifc); return CMD_WARNING_CONFIG_FAILED; } /* This is real route. */ dplane_res = dplane_intf_addr_unset(ifp, ifc); if (dplane_res == ZEBRA_DPLANE_REQUEST_FAILURE) { vty_out(vty, "%% Can't unset interface IP address: %s.\n", dplane_res2str(dplane_res)); return CMD_WARNING_CONFIG_FAILED; } UNSET_FLAG(ifc->conf, ZEBRA_IFC_QUEUED); /* This information will be propagated to the zclients when the * kernel notification is received. */ return CMD_SUCCESS; } DEFUN (ipv6_address, ipv6_address_cmd, "ipv6 address X:X::X:X/M", "Interface IPv6 config commands\n" "Set the IP address of an interface\n" "IPv6 address (e.g. 3ffe:506::1/48)\n") { int idx_ipv6_prefixlen = 2; VTY_DECLVAR_CONTEXT(interface, ifp); return ipv6_address_install(vty, ifp, argv[idx_ipv6_prefixlen]->arg, NULL, NULL); } DEFUN (no_ipv6_address, no_ipv6_address_cmd, "no ipv6 address X:X::X:X/M", NO_STR "Interface IPv6 config commands\n" "Set the IP address of an interface\n" "IPv6 address (e.g. 3ffe:506::1/48)\n") { int idx_ipv6_prefixlen = 3; VTY_DECLVAR_CONTEXT(interface, ifp); return ipv6_address_uninstall(vty, ifp, argv[idx_ipv6_prefixlen]->arg, NULL, NULL); } static int link_params_config_write(struct vty *vty, struct interface *ifp) { int i; if ((ifp == NULL) || !HAS_LINK_PARAMS(ifp)) return -1; struct if_link_params *iflp = ifp->link_params; vty_out(vty, " link-params\n"); vty_out(vty, " enable\n"); if (IS_PARAM_SET(iflp, LP_TE_METRIC) && iflp->te_metric != ifp->metric) vty_out(vty, " metric %u\n", iflp->te_metric); if (IS_PARAM_SET(iflp, LP_MAX_BW) && iflp->max_bw != iflp->default_bw) vty_out(vty, " max-bw %g\n", iflp->max_bw); if (IS_PARAM_SET(iflp, LP_MAX_RSV_BW) && iflp->max_rsv_bw != iflp->default_bw) vty_out(vty, " max-rsv-bw %g\n", iflp->max_rsv_bw); if (IS_PARAM_SET(iflp, LP_UNRSV_BW)) { for (i = 0; i < 8; i++) if (iflp->unrsv_bw[i] != iflp->default_bw) vty_out(vty, " unrsv-bw %d %g\n", i, iflp->unrsv_bw[i]); } if (IS_PARAM_SET(iflp, LP_ADM_GRP)) vty_out(vty, " admin-grp 0x%x\n", iflp->admin_grp); if (IS_PARAM_SET(iflp, LP_DELAY)) { vty_out(vty, " delay %u", iflp->av_delay); if (IS_PARAM_SET(iflp, LP_MM_DELAY)) { vty_out(vty, " min %u", iflp->min_delay); vty_out(vty, " max %u", iflp->max_delay); } vty_out(vty, "\n"); } if (IS_PARAM_SET(iflp, LP_DELAY_VAR)) vty_out(vty, " delay-variation %u\n", iflp->delay_var); if (IS_PARAM_SET(iflp, LP_PKT_LOSS)) vty_out(vty, " packet-loss %g\n", iflp->pkt_loss); if (IS_PARAM_SET(iflp, LP_AVA_BW)) vty_out(vty, " ava-bw %g\n", iflp->ava_bw); if (IS_PARAM_SET(iflp, LP_RES_BW)) vty_out(vty, " res-bw %g\n", iflp->res_bw); if (IS_PARAM_SET(iflp, LP_USE_BW)) vty_out(vty, " use-bw %g\n", iflp->use_bw); if (IS_PARAM_SET(iflp, LP_RMT_AS)) vty_out(vty, " neighbor %s as %u\n", inet_ntoa(iflp->rmt_ip), iflp->rmt_as); vty_out(vty, " exit-link-params\n"); return 0; } static int if_config_write(struct vty *vty) { struct vrf *vrf0; struct interface *ifp; zebra_ptm_write(vty); RB_FOREACH (vrf0, vrf_name_head, &vrfs_by_name) FOR_ALL_INTERFACES (vrf0, ifp) { struct zebra_if *if_data; struct listnode *addrnode; struct connected *ifc; struct prefix *p; struct vrf *vrf; if_data = ifp->info; vrf = vrf_lookup_by_id(ifp->vrf_id); if (ifp->vrf_id == VRF_DEFAULT) vty_frame(vty, "interface %s\n", ifp->name); else vty_frame(vty, "interface %s vrf %s\n", ifp->name, vrf->name); if (if_data) { if (if_data->shutdown == IF_ZEBRA_SHUTDOWN_ON) vty_out(vty, " shutdown\n"); zebra_ptm_if_write(vty, if_data); } if (ifp->desc) vty_out(vty, " description %s\n", ifp->desc); /* Assign bandwidth here to avoid unnecessary interface flap while processing config script */ if (ifp->bandwidth != 0) vty_out(vty, " bandwidth %u\n", ifp->bandwidth); if (!CHECK_FLAG(ifp->status, ZEBRA_INTERFACE_LINKDETECTION)) vty_out(vty, " no link-detect\n"); for (ALL_LIST_ELEMENTS_RO(ifp->connected, addrnode, ifc)) { if (CHECK_FLAG(ifc->conf, ZEBRA_IFC_CONFIGURED)) { char buf[INET6_ADDRSTRLEN]; p = ifc->address; vty_out(vty, " ip%s address %s", p->family == AF_INET ? "" : "v6", inet_ntop(p->family, &p->u.prefix, buf, sizeof(buf))); if (CONNECTED_PEER(ifc)) { p = ifc->destination; vty_out(vty, " peer %s", inet_ntop(p->family, &p->u.prefix, buf, sizeof(buf))); } vty_out(vty, "/%d", p->prefixlen); if (ifc->label) vty_out(vty, " label %s", ifc->label); vty_out(vty, "\n"); } } if (if_data) { if (if_data->multicast != IF_ZEBRA_MULTICAST_UNSPEC) vty_out(vty, " %smulticast\n", if_data->multicast == IF_ZEBRA_MULTICAST_ON ? "" : "no "); } hook_call(zebra_if_config_wr, vty, ifp); link_params_config_write(vty, ifp); vty_endframe(vty, "!\n"); } return 0; } /* Allocate and initialize interface vector. */ void zebra_if_init(void) { /* Initialize interface and new hook. */ hook_register_prio(if_add, 0, if_zebra_new_hook); hook_register_prio(if_del, 0, if_zebra_delete_hook); /* Install configuration write function. */ install_node(&interface_node, if_config_write); install_node(&link_params_node, NULL); if_cmd_init(); install_element(VIEW_NODE, &show_interface_cmd); install_element(VIEW_NODE, &show_interface_vrf_all_cmd); install_element(VIEW_NODE, &show_interface_name_vrf_cmd); install_element(VIEW_NODE, &show_interface_name_vrf_all_cmd); install_element(ENABLE_NODE, &show_interface_desc_cmd); install_element(ENABLE_NODE, &show_interface_desc_vrf_all_cmd); install_element(INTERFACE_NODE, &multicast_cmd); install_element(INTERFACE_NODE, &no_multicast_cmd); install_element(INTERFACE_NODE, &linkdetect_cmd); install_element(INTERFACE_NODE, &no_linkdetect_cmd); install_element(INTERFACE_NODE, &shutdown_if_cmd); install_element(INTERFACE_NODE, &no_shutdown_if_cmd); install_element(INTERFACE_NODE, &bandwidth_if_cmd); install_element(INTERFACE_NODE, &no_bandwidth_if_cmd); install_element(INTERFACE_NODE, &ip_address_cmd); install_element(INTERFACE_NODE, &no_ip_address_cmd); install_element(INTERFACE_NODE, &ip_address_peer_cmd); install_element(INTERFACE_NODE, &no_ip_address_peer_cmd); install_element(INTERFACE_NODE, &ipv6_address_cmd); install_element(INTERFACE_NODE, &no_ipv6_address_cmd); #ifdef HAVE_NETLINK install_element(INTERFACE_NODE, &ip_address_label_cmd); install_element(INTERFACE_NODE, &no_ip_address_label_cmd); #endif /* HAVE_NETLINK */ install_element(INTERFACE_NODE, &link_params_cmd); install_default(LINK_PARAMS_NODE); install_element(LINK_PARAMS_NODE, &link_params_enable_cmd); install_element(LINK_PARAMS_NODE, &no_link_params_enable_cmd); install_element(LINK_PARAMS_NODE, &link_params_metric_cmd); install_element(LINK_PARAMS_NODE, &no_link_params_metric_cmd); install_element(LINK_PARAMS_NODE, &link_params_maxbw_cmd); install_element(LINK_PARAMS_NODE, &link_params_max_rsv_bw_cmd); install_element(LINK_PARAMS_NODE, &link_params_unrsv_bw_cmd); install_element(LINK_PARAMS_NODE, &link_params_admin_grp_cmd); install_element(LINK_PARAMS_NODE, &no_link_params_admin_grp_cmd); install_element(LINK_PARAMS_NODE, &link_params_inter_as_cmd); install_element(LINK_PARAMS_NODE, &no_link_params_inter_as_cmd); install_element(LINK_PARAMS_NODE, &link_params_delay_cmd); install_element(LINK_PARAMS_NODE, &no_link_params_delay_cmd); install_element(LINK_PARAMS_NODE, &link_params_delay_var_cmd); install_element(LINK_PARAMS_NODE, &no_link_params_delay_var_cmd); install_element(LINK_PARAMS_NODE, &link_params_pkt_loss_cmd); install_element(LINK_PARAMS_NODE, &no_link_params_pkt_loss_cmd); install_element(LINK_PARAMS_NODE, &link_params_ava_bw_cmd); install_element(LINK_PARAMS_NODE, &no_link_params_ava_bw_cmd); install_element(LINK_PARAMS_NODE, &link_params_res_bw_cmd); install_element(LINK_PARAMS_NODE, &no_link_params_res_bw_cmd); install_element(LINK_PARAMS_NODE, &link_params_use_bw_cmd); install_element(LINK_PARAMS_NODE, &no_link_params_use_bw_cmd); install_element(LINK_PARAMS_NODE, &exit_link_params_cmd); } frr-7.2.1/zebra/interface.h0000644000000000000000000003363113610377563012436 00000000000000 /* Interface function header. * Copyright (C) 1999 Kunihiro Ishiguro * * This file is part of GNU Zebra. * * GNU Zebra is free software; you can redistribute it and/or modify it * under the terms of the GNU General Public License as published by the * Free Software Foundation; either version 2, or (at your option) any * later version. * * GNU Zebra 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; see the file COPYING; if not, write to the Free Software * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA */ #ifndef _ZEBRA_INTERFACE_H #define _ZEBRA_INTERFACE_H #include "redistribute.h" #include "vrf.h" #include "hook.h" #include "zebra/zebra_l2.h" #ifdef __cplusplus extern "C" { #endif /* For interface multicast configuration. */ #define IF_ZEBRA_MULTICAST_UNSPEC 0 #define IF_ZEBRA_MULTICAST_ON 1 #define IF_ZEBRA_MULTICAST_OFF 2 /* For interface shutdown configuration. */ #define IF_ZEBRA_SHUTDOWN_OFF 0 #define IF_ZEBRA_SHUTDOWN_ON 1 #if defined(HAVE_RTADV) /* Router advertisement parameter. From RFC4861, RFC6275 and RFC4191. */ struct rtadvconf { /* A flag indicating whether or not the router sends periodic Router Advertisements and responds to Router Solicitations. Default: false */ int AdvSendAdvertisements; /* The maximum time allowed between sending unsolicited multicast Router Advertisements from the interface, in milliseconds. MUST be no less than 70 ms [RFC6275 7.5] and no greater than 1800000 ms [RFC4861 6.2.1]. Default: 600000 milliseconds */ int MaxRtrAdvInterval; #define RTADV_MAX_RTR_ADV_INTERVAL 600000 /* The minimum time allowed between sending unsolicited multicast Router Advertisements from the interface, in milliseconds. MUST be no less than 30 ms [RFC6275 7.5]. MUST be no greater than .75 * MaxRtrAdvInterval. Default: 0.33 * MaxRtrAdvInterval */ int MinRtrAdvInterval; /* This field is currently unused. */ #define RTADV_MIN_RTR_ADV_INTERVAL (0.33 * RTADV_MAX_RTR_ADV_INTERVAL) /* Unsolicited Router Advertisements' interval timer. */ int AdvIntervalTimer; /* The true/false value to be placed in the "Managed address configuration" flag field in the Router Advertisement. See [ADDRCONF]. Default: false */ int AdvManagedFlag; /* The true/false value to be placed in the "Other stateful configuration" flag field in the Router Advertisement. See [ADDRCONF]. Default: false */ int AdvOtherConfigFlag; /* The value to be placed in MTU options sent by the router. A value of zero indicates that no MTU options are sent. Default: 0 */ int AdvLinkMTU; /* The value to be placed in the Reachable Time field in the Router Advertisement messages sent by the router. The value zero means unspecified (by this router). MUST be no greater than 3,600,000 milliseconds (1 hour). Default: 0 */ uint32_t AdvReachableTime; #define RTADV_MAX_REACHABLE_TIME 3600000 /* The value to be placed in the Retrans Timer field in the Router Advertisement messages sent by the router. The value zero means unspecified (by this router). Default: 0 */ int AdvRetransTimer; /* The default value to be placed in the Cur Hop Limit field in the Router Advertisement messages sent by the router. The value should be set to that current diameter of the Internet. The value zero means unspecified (by this router). Default: The value specified in the "Assigned Numbers" RFC [ASSIGNED] that was in effect at the time of implementation. */ int AdvCurHopLimit; /* The value to be placed in the Router Lifetime field of Router Advertisements sent from the interface, in seconds. MUST be either zero or between MaxRtrAdvInterval and 9000 seconds. A value of zero indicates that the router is not to be used as a default router. Default: 3 * MaxRtrAdvInterval */ int AdvDefaultLifetime; #define RTADV_MAX_RTRLIFETIME 9000 /* 2.5 hours */ /* A list of prefixes to be placed in Prefix Information options in Router Advertisement messages sent from the interface. Default: all prefixes that the router advertises via routing protocols as being on-link for the interface from which the advertisement is sent. The link-local prefix SHOULD NOT be included in the list of advertised prefixes. */ struct list *AdvPrefixList; /* The true/false value to be placed in the "Home agent" flag field in the Router Advertisement. See [RFC6275 7.1]. Default: false */ int AdvHomeAgentFlag; #ifndef ND_RA_FLAG_HOME_AGENT #define ND_RA_FLAG_HOME_AGENT 0x20 #endif /* The value to be placed in Home Agent Information option if Home Flag is set. Default: 0 */ int HomeAgentPreference; /* The value to be placed in Home Agent Information option if Home Flag is set. Lifetime (seconds) MUST not be greater than 18.2 hours. The value 0 has special meaning: use of AdvDefaultLifetime value. Default: 0 */ int HomeAgentLifetime; #define RTADV_MAX_HALIFETIME 65520 /* 18.2 hours */ /* The true/false value to insert or not an Advertisement Interval option. See [RFC 6275 7.3] Default: false */ int AdvIntervalOption; /* The value to be placed in the Default Router Preference field of a router advertisement. See [RFC 4191 2.1 & 2.2] Default: 0 (medium) */ int DefaultPreference; #define RTADV_PREF_MEDIUM 0x0 /* Per RFC4191. */ /* * List of recursive DNS servers to include in the RDNSS option. * See [RFC8106 5.1] * * Default: empty list; do not emit RDNSS option */ struct list *AdvRDNSSList; /* * List of DNS search domains to include in the DNSSL option. * See [RFC8106 5.2] * * Default: empty list; do not emit DNSSL option */ struct list *AdvDNSSLList; uint8_t inFastRexmit; /* True if we're rexmits faster than usual */ /* Track if RA was configured by BGP or by the Operator or both */ uint8_t ra_configured; /* Was RA configured? */ #define BGP_RA_CONFIGURED (1<<0) /* BGP configured RA? */ #define VTY_RA_CONFIGURED (1<<1) /* Operator configured RA? */ #define VTY_RA_INTERVAL_CONFIGURED (1<<2) /* Operator configured RA interval */ int NumFastReXmitsRemain; /* Loaded first with number of fast rexmits to do */ #define RTADV_FAST_REXMIT_PERIOD 1 /* 1 sec */ #define RTADV_NUM_FAST_REXMITS 4 /* Fast Rexmit RA 4 times on certain events */ }; struct rtadv_rdnss { /* Address of recursive DNS server to advertise */ struct in6_addr addr; /* * Lifetime in seconds; all-ones means infinity, zero * stop using it. */ uint32_t lifetime; /* If lifetime not set, use a default of 3*MaxRtrAdvInterval */ int lifetime_set; }; /* * [RFC1035 2.3.4] sets the maximum length of a domain name (a sequence of * labels, each prefixed by a length octet) at 255 octets. */ #define RTADV_MAX_ENCODED_DOMAIN_NAME 255 struct rtadv_dnssl { /* Domain name without trailing root zone dot (NUL-terminated) */ char name[RTADV_MAX_ENCODED_DOMAIN_NAME - 1]; /* Name encoded as in [RFC1035 3.1] */ uint8_t encoded_name[RTADV_MAX_ENCODED_DOMAIN_NAME]; /* Actual length of encoded_name */ size_t encoded_len; /* Lifetime as for RDNSS */ uint32_t lifetime; int lifetime_set; }; #endif /* HAVE_RTADV */ /* Zebra interface type - ones of interest. */ typedef enum { ZEBRA_IF_OTHER = 0, /* Anything else */ ZEBRA_IF_VXLAN, /* VxLAN interface */ ZEBRA_IF_VRF, /* VRF device */ ZEBRA_IF_BRIDGE, /* bridge device */ ZEBRA_IF_VLAN, /* VLAN sub-interface */ ZEBRA_IF_MACVLAN, /* MAC VLAN interface*/ ZEBRA_IF_VETH, /* VETH interface*/ ZEBRA_IF_BOND, /* Bond */ ZEBRA_IF_BOND_SLAVE, /* Bond */ } zebra_iftype_t; /* Zebra "slave" interface type */ typedef enum { ZEBRA_IF_SLAVE_NONE, /* Not a slave */ ZEBRA_IF_SLAVE_VRF, /* Member of a VRF */ ZEBRA_IF_SLAVE_BRIDGE, /* Member of a bridge */ ZEBRA_IF_SLAVE_BOND, /* Bond member */ ZEBRA_IF_SLAVE_OTHER, /* Something else - e.g., bond slave */ } zebra_slave_iftype_t; struct irdp_interface; /* `zebra' daemon local interface structure. */ struct zebra_if { /* Shutdown configuration. */ uint8_t shutdown; /* Multicast configuration. */ uint8_t multicast; /* Router advertise configuration. */ uint8_t rtadv_enable; /* Installed addresses chains tree. */ struct route_table *ipv4_subnets; /* Information about up/down changes */ unsigned int up_count; char up_last[QUAGGA_TIMESTAMP_LEN]; unsigned int down_count; char down_last[QUAGGA_TIMESTAMP_LEN]; #if defined(HAVE_RTADV) struct rtadvconf rtadv; unsigned int ra_sent, ra_rcvd; #endif /* HAVE_RTADV */ struct irdp_interface *irdp; #ifdef HAVE_STRUCT_SOCKADDR_DL union { /* note that sdl_storage is never accessed, it only exists to * make space. * all actual uses refer to sdl - but use sizeof(sdl_storage)! * this fits * best with C aliasing rules. */ struct sockaddr_dl sdl; struct sockaddr_storage sdl_storage; }; #endif #ifdef SUNOS_5 /* the real IFF_UP state of the primary interface. * need this to differentiate between all interfaces being * down (but primary still plumbed) and primary having gone * ~IFF_UP, and all addresses gone. */ uint8_t primary_state; #endif /* SUNOS_5 */ /* ptm enable configuration */ uint8_t ptm_enable; /* Zebra interface and "slave" interface type */ zebra_iftype_t zif_type; zebra_slave_iftype_t zif_slave_type; /* Additional L2 info, depends on zif_type */ union zebra_l2if_info l2info; /* For members of a bridge, link to bridge. */ /* Note: If additional fields become necessary, this can be modified to * be a pointer to a dynamically allocd struct. */ struct zebra_l2info_brslave brslave_info; struct zebra_l2info_bondslave bondslave_info; /* Link fields - for sub-interfaces. */ ifindex_t link_ifindex; struct interface *link; struct thread *speed_update; /* * Does this interface have a v6 to v4 ll neighbor entry * for bgp unnumbered? */ bool v6_2_v4_ll_neigh_entry; char neigh_mac[6]; struct in6_addr v6_2_v4_ll_addr6; /* The description of the interface */ char *desc; }; DECLARE_HOOK(zebra_if_extra_info, (struct vty * vty, struct interface *ifp), (vty, ifp)) DECLARE_HOOK(zebra_if_config_wr, (struct vty * vty, struct interface *ifp), (vty, ifp)) static inline void zebra_if_set_ziftype(struct interface *ifp, zebra_iftype_t zif_type, zebra_slave_iftype_t zif_slave_type) { struct zebra_if *zif; zif = (struct zebra_if *)ifp->info; zif->zif_type = zif_type; zif->zif_slave_type = zif_slave_type; } #define IS_ZEBRA_IF_VRF(ifp) \ (((struct zebra_if *)(ifp->info))->zif_type == ZEBRA_IF_VRF) #define IS_ZEBRA_IF_BRIDGE(ifp) \ (((struct zebra_if *)(ifp->info))->zif_type == ZEBRA_IF_BRIDGE) #define IS_ZEBRA_IF_VLAN(ifp) \ (((struct zebra_if *)(ifp->info))->zif_type == ZEBRA_IF_VLAN) #define IS_ZEBRA_IF_VXLAN(ifp) \ (((struct zebra_if *)(ifp->info))->zif_type == ZEBRA_IF_VXLAN) #define IS_ZEBRA_IF_MACVLAN(ifp) \ (((struct zebra_if *)(ifp->info))->zif_type == ZEBRA_IF_MACVLAN) #define IS_ZEBRA_IF_VETH(ifp) \ (((struct zebra_if *)(ifp->info))->zif_type == ZEBRA_IF_VETH) #define IS_ZEBRA_IF_BRIDGE_SLAVE(ifp) \ (((struct zebra_if *)(ifp->info))->zif_slave_type \ == ZEBRA_IF_SLAVE_BRIDGE) #define IS_ZEBRA_IF_VRF_SLAVE(ifp) \ (((struct zebra_if *)(ifp->info))->zif_slave_type == ZEBRA_IF_SLAVE_VRF) #define IS_ZEBRA_IF_BOND_SLAVE(ifp) \ (((struct zebra_if *)(ifp->info))->zif_slave_type \ == ZEBRA_IF_SLAVE_BOND) extern void zebra_if_init(void); extern struct interface *if_lookup_by_index_per_ns(struct zebra_ns *, uint32_t); extern struct interface *if_lookup_by_name_per_ns(struct zebra_ns *, const char *); extern struct interface *if_link_per_ns(struct zebra_ns *, struct interface *); extern const char *ifindex2ifname_per_ns(struct zebra_ns *, unsigned int); extern void if_unlink_per_ns(struct interface *); extern void if_nbr_mac_to_ipv4ll_neigh_update(struct interface *fip, char mac[6], struct in6_addr *address, int add); extern void if_nbr_ipv6ll_to_ipv4ll_neigh_update(struct interface *ifp, struct in6_addr *address, int add); extern void if_nbr_ipv6ll_to_ipv4ll_neigh_del_all(struct interface *ifp); extern void if_delete_update(struct interface *ifp); extern void if_add_update(struct interface *ifp); extern void if_up(struct interface *); extern void if_down(struct interface *); extern void if_refresh(struct interface *); extern void if_flags_update(struct interface *, uint64_t); extern int if_subnet_add(struct interface *, struct connected *); extern int if_subnet_delete(struct interface *, struct connected *); extern int ipv6_address_configured(struct interface *ifp); extern void if_handle_vrf_change(struct interface *ifp, vrf_id_t vrf_id); extern void zebra_if_update_link(struct interface *ifp, ifindex_t link_ifindex, ns_id_t ns_id); extern void zebra_if_update_all_links(void); extern void zebra_if_set_protodown(struct interface *ifp, bool down); extern void vrf_add_update(struct vrf *vrfp); #ifdef HAVE_PROC_NET_DEV extern void ifstat_update_proc(void); #endif /* HAVE_PROC_NET_DEV */ #ifdef HAVE_NET_RT_IFLIST extern void ifstat_update_sysctl(void); #endif /* HAVE_NET_RT_IFLIST */ #ifdef HAVE_PROC_NET_DEV extern int interface_list_proc(void); #endif /* HAVE_PROC_NET_DEV */ #ifdef HAVE_PROC_NET_IF_INET6 extern int ifaddr_proc_ipv6(void); #endif /* HAVE_PROC_NET_IF_INET6 */ #ifdef __cplusplus } #endif #endif /* _ZEBRA_INTERFACE_H */ frr-7.2.1/zebra/ioctl.c0000644000000000000000000003657713610377563011617 00000000000000/* * Common ioctl functions. * Copyright (C) 1997, 98 Kunihiro Ishiguro * * This file is part of GNU Zebra. * * GNU Zebra is free software; you can redistribute it and/or modify it * under the terms of the GNU General Public License as published by the * Free Software Foundation; either version 2, or (at your option) any * later version. * * GNU Zebra 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; see the file COPYING; if not, write to the Free Software * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA */ #include #include "linklist.h" #include "if.h" #include "prefix.h" #include "ioctl.h" #include "log.h" #include "privs.h" #include "lib_errors.h" #include "vty.h" #include "zebra/rib.h" #include "zebra/rt.h" #include "zebra/interface.h" #include "zebra/zebra_errors.h" #include "zebra/debug.h" #ifndef SUNOS_5 #ifdef HAVE_BSD_LINK_DETECT #include #endif /* HAVE_BSD_LINK_DETECT*/ extern struct zebra_privs_t zserv_privs; /* clear and set interface name string */ void ifreq_set_name(struct ifreq *ifreq, struct interface *ifp) { strlcpy(ifreq->ifr_name, ifp->name, sizeof(ifreq->ifr_name)); } /* call ioctl system call */ int if_ioctl(unsigned long request, caddr_t buffer) { int sock; int ret; int err = 0; frr_with_privs(&zserv_privs) { sock = socket(AF_INET, SOCK_DGRAM, 0); if (sock < 0) { zlog_err("Cannot create UDP socket: %s", safe_strerror(errno)); exit(1); } if ((ret = ioctl(sock, request, buffer)) < 0) err = errno; } close(sock); if (ret < 0) { errno = err; return ret; } return 0; } /* call ioctl system call */ int vrf_if_ioctl(unsigned long request, caddr_t buffer, vrf_id_t vrf_id) { int sock; int ret; int err = 0; frr_with_privs(&zserv_privs) { sock = vrf_socket(AF_INET, SOCK_DGRAM, 0, vrf_id, NULL); if (sock < 0) { zlog_err("Cannot create UDP socket: %s", safe_strerror(errno)); exit(1); } ret = vrf_ioctl(vrf_id, sock, request, buffer); if (ret < 0) err = errno; } close(sock); if (ret < 0) { errno = err; return ret; } return 0; } #ifndef HAVE_NETLINK static int if_ioctl_ipv6(unsigned long request, caddr_t buffer) { int sock; int ret; int err = 0; frr_with_privs(&zserv_privs) { sock = socket(AF_INET6, SOCK_DGRAM, 0); if (sock < 0) { zlog_err("Cannot create IPv6 datagram socket: %s", safe_strerror(errno)); exit(1); } if ((ret = ioctl(sock, request, buffer)) < 0) err = errno; } close(sock); if (ret < 0) { errno = err; return ret; } return 0; } #endif /* ! HAVE_NETLINK */ /* * get interface metric * -- if value is not avaliable set -1 */ void if_get_metric(struct interface *ifp) { #ifdef SIOCGIFMETRIC struct ifreq ifreq; ifreq_set_name(&ifreq, ifp); if (vrf_if_ioctl(SIOCGIFMETRIC, (caddr_t)&ifreq, ifp->vrf_id) < 0) return; ifp->metric = ifreq.ifr_metric; if (ifp->metric == 0) ifp->metric = 1; #else /* SIOCGIFMETRIC */ ifp->metric = -1; #endif /* SIOCGIFMETRIC */ } /* get interface MTU */ void if_get_mtu(struct interface *ifp) { struct ifreq ifreq; ifreq_set_name(&ifreq, ifp); #if defined(SIOCGIFMTU) if (vrf_if_ioctl(SIOCGIFMTU, (caddr_t)&ifreq, ifp->vrf_id) < 0) { zlog_info("Can't lookup mtu by ioctl(SIOCGIFMTU)"); ifp->mtu6 = ifp->mtu = -1; return; } #ifdef SUNOS_5 ifp->mtu6 = ifp->mtu = ifreq.ifr_metric; #else ifp->mtu6 = ifp->mtu = ifreq.ifr_mtu; #endif /* SUNOS_5 */ /* propogate */ zebra_interface_up_update(ifp); #else zlog_info("Can't lookup mtu on this system"); ifp->mtu6 = ifp->mtu = -1; #endif } /* * Handler for interface address programming via the zebra dplane, * for non-netlink platforms. This handler dispatches to per-platform * helpers, based on the operation requested. */ #ifndef HAVE_NETLINK /* Prototypes: these are placed in this block so that they're only seen * on non-netlink platforms. */ static int if_set_prefix_ctx(const struct zebra_dplane_ctx *ctx); static int if_unset_prefix_ctx(const struct zebra_dplane_ctx *ctx); static int if_set_prefix6_ctx(const struct zebra_dplane_ctx *ctx); static int if_unset_prefix6_ctx(const struct zebra_dplane_ctx *ctx); enum zebra_dplane_result kernel_address_update_ctx( struct zebra_dplane_ctx *ctx) { int ret = -1; const struct prefix *p; p = dplane_ctx_get_intf_addr(ctx); if (dplane_ctx_get_op(ctx) == DPLANE_OP_ADDR_INSTALL) { if (p->family == AF_INET) ret = if_set_prefix_ctx(ctx); else ret = if_set_prefix6_ctx(ctx); } else if (dplane_ctx_get_op(ctx) == DPLANE_OP_ADDR_UNINSTALL) { if (p->family == AF_INET) ret = if_unset_prefix_ctx(ctx); else ret = if_unset_prefix6_ctx(ctx); } else { if (IS_ZEBRA_DEBUG_DPLANE) zlog_debug("Invalid op in interface-addr install"); } return (ret == 0 ? ZEBRA_DPLANE_REQUEST_SUCCESS : ZEBRA_DPLANE_REQUEST_FAILURE); } #endif /* !HAVE_NETLINK */ #ifdef HAVE_NETLINK /* TODO -- remove; no use of these apis with netlink any longer */ #else /* ! HAVE_NETLINK */ #ifdef HAVE_STRUCT_IFALIASREQ /* * Helper for interface-addr install, non-netlink */ static int if_set_prefix_ctx(const struct zebra_dplane_ctx *ctx) { int ret; struct ifaliasreq addreq; struct sockaddr_in addr, mask, peer; struct prefix_ipv4 *p; p = (struct prefix_ipv4 *)dplane_ctx_get_intf_addr(ctx); memset(&addreq, 0, sizeof(addreq)); strlcpy((char *)&addreq.ifra_name, dplane_ctx_get_ifname(ctx), sizeof(addreq.ifra_name)); memset(&addr, 0, sizeof(struct sockaddr_in)); addr.sin_addr = p->prefix; addr.sin_family = p->family; #ifdef HAVE_STRUCT_SOCKADDR_IN_SIN_LEN addr.sin_len = sizeof(struct sockaddr_in); #endif memcpy(&addreq.ifra_addr, &addr, sizeof(struct sockaddr_in)); if (dplane_ctx_intf_is_connected(ctx)) { p = (struct prefix_ipv4 *)dplane_ctx_get_intf_dest(ctx); memset(&mask, 0, sizeof(struct sockaddr_in)); peer.sin_addr = p->prefix; peer.sin_family = p->family; #ifdef HAVE_STRUCT_SOCKADDR_IN_SIN_LEN peer.sin_len = sizeof(struct sockaddr_in); #endif memcpy(&addreq.ifra_broadaddr, &peer, sizeof(struct sockaddr_in)); } memset(&mask, 0, sizeof(struct sockaddr_in)); masklen2ip(p->prefixlen, &mask.sin_addr); mask.sin_family = p->family; #ifdef HAVE_STRUCT_SOCKADDR_IN_SIN_LEN mask.sin_len = sizeof(struct sockaddr_in); #endif memcpy(&addreq.ifra_mask, &mask, sizeof(struct sockaddr_in)); ret = if_ioctl(SIOCAIFADDR, (caddr_t)&addreq); if (ret < 0) return ret; return 0; } /* * Helper for interface-addr un-install, non-netlink */ static int if_unset_prefix_ctx(const struct zebra_dplane_ctx *ctx) { int ret; struct ifaliasreq addreq; struct sockaddr_in addr, mask, peer; struct prefix_ipv4 *p; p = (struct prefix_ipv4 *)dplane_ctx_get_intf_addr(ctx); memset(&addreq, 0, sizeof(addreq)); strlcpy((char *)&addreq.ifra_name, dplane_ctx_get_ifname(ctx), sizeof(addreq.ifra_name)); memset(&addr, 0, sizeof(struct sockaddr_in)); addr.sin_addr = p->prefix; addr.sin_family = p->family; #ifdef HAVE_STRUCT_SOCKADDR_IN_SIN_LEN addr.sin_len = sizeof(struct sockaddr_in); #endif memcpy(&addreq.ifra_addr, &addr, sizeof(struct sockaddr_in)); if (dplane_ctx_intf_is_connected(ctx)) { p = (struct prefix_ipv4 *)dplane_ctx_get_intf_dest(ctx); memset(&mask, 0, sizeof(struct sockaddr_in)); peer.sin_addr = p->prefix; peer.sin_family = p->family; #ifdef HAVE_STRUCT_SOCKADDR_IN_SIN_LEN peer.sin_len = sizeof(struct sockaddr_in); #endif memcpy(&addreq.ifra_broadaddr, &peer, sizeof(struct sockaddr_in)); } memset(&mask, 0, sizeof(struct sockaddr_in)); masklen2ip(p->prefixlen, &mask.sin_addr); mask.sin_family = p->family; #ifdef HAVE_STRUCT_SOCKADDR_IN_SIN_LEN mask.sin_len = sizeof(struct sockaddr_in); #endif memcpy(&addreq.ifra_mask, &mask, sizeof(struct sockaddr_in)); ret = if_ioctl(SIOCDIFADDR, (caddr_t)&addreq); if (ret < 0) return ret; return 0; } #else /* Set up interface's address, netmask (and broadcas? ). Linux or Solaris uses ifname:number semantics to set IP address aliases. */ int if_set_prefix_ctx(const struct zebra_dplane_ctx *ctx) { int ret; struct ifreq ifreq; struct sockaddr_in addr; struct sockaddr_in broad; struct sockaddr_in mask; struct prefix_ipv4 ifaddr; struct prefix_ipv4 *p; p = (struct prefix_ipv4 *)dplane_ctx_get_intf_addr(ctx); ifaddr = *p; strlcpy(ifreq.ifr_name, dplane_ctx_get_ifname(ctx), sizeof(ifreq.ifr_name)); addr.sin_addr = p->prefix; addr.sin_family = p->family; memcpy(&ifreq.ifr_addr, &addr, sizeof(struct sockaddr_in)); ret = if_ioctl(SIOCSIFADDR, (caddr_t)&ifreq); if (ret < 0) return ret; /* We need mask for make broadcast addr. */ masklen2ip(p->prefixlen, &mask.sin_addr); if (dplane_ctx_intf_is_broadcast(ctx)) { apply_mask_ipv4(&ifaddr); addr.sin_addr = ifaddr.prefix; broad.sin_addr.s_addr = (addr.sin_addr.s_addr | ~mask.sin_addr.s_addr); broad.sin_family = p->family; memcpy(&ifreq.ifr_broadaddr, &broad, sizeof(struct sockaddr_in)); ret = if_ioctl(SIOCSIFBRDADDR, (caddr_t)&ifreq); if (ret < 0) return ret; } mask.sin_family = p->family; #ifdef SUNOS_5 memcpy(&mask, &ifreq.ifr_addr, sizeof(mask)); #else memcpy(&ifreq.ifr_addr, &mask, sizeof(struct sockaddr_in)); #endif /* SUNOS5 */ ret = if_ioctl(SIOCSIFNETMASK, (caddr_t)&ifreq); if (ret < 0) return ret; return 0; } /* Set up interface's address, netmask (and broadcas? ). Linux or Solaris uses ifname:number semantics to set IP address aliases. */ int if_unset_prefix_ctx(const struct zebra_dplane_ctx *ctx) { int ret; struct ifreq ifreq; struct sockaddr_in addr; struct prefix_ipv4 *p; p = (struct prefix_ipv4 *)dplane_ctx_get_intf_addr(ctx); strlcpy(ifreq.ifr_name, dplane_ctx_get_ifname(ctx), sizeof(ifreq.ifr_name)); memset(&addr, 0, sizeof(struct sockaddr_in)); addr.sin_family = p->family; memcpy(&ifreq.ifr_addr, &addr, sizeof(struct sockaddr_in)); ret = if_ioctl(SIOCSIFADDR, (caddr_t)&ifreq); if (ret < 0) return ret; return 0; } #endif /* HAVE_STRUCT_IFALIASREQ */ #endif /* HAVE_NETLINK */ /* get interface flags */ void if_get_flags(struct interface *ifp) { int ret; struct ifreq ifreq; #ifdef HAVE_BSD_LINK_DETECT struct ifmediareq ifmr; #endif /* HAVE_BSD_LINK_DETECT */ ifreq_set_name(&ifreq, ifp); ret = vrf_if_ioctl(SIOCGIFFLAGS, (caddr_t)&ifreq, ifp->vrf_id); if (ret < 0) { flog_err_sys(EC_LIB_SYSTEM_CALL, "vrf_if_ioctl(SIOCGIFFLAGS) failed: %s", safe_strerror(errno)); return; } #ifdef HAVE_BSD_LINK_DETECT /* Detect BSD link-state at start-up */ /* Per-default, IFF_RUNNING is held high, unless link-detect says * otherwise - we abuse IFF_RUNNING inside zebra as a link-state flag, * following practice on Linux and Solaris kernels */ SET_FLAG(ifreq.ifr_flags, IFF_RUNNING); if (CHECK_FLAG(ifp->status, ZEBRA_INTERFACE_LINKDETECTION)) { (void)memset(&ifmr, 0, sizeof(ifmr)); strlcpy(ifmr.ifm_name, ifp->name, sizeof(ifmr.ifm_name)); /* Seems not all interfaces implement this ioctl */ if (if_ioctl(SIOCGIFMEDIA, (caddr_t)&ifmr) == -1 && errno != EINVAL) flog_err_sys(EC_LIB_SYSTEM_CALL, "if_ioctl(SIOCGIFMEDIA) failed: %s", safe_strerror(errno)); else if (ifmr.ifm_status & IFM_AVALID) /* Link state is valid */ { if (ifmr.ifm_status & IFM_ACTIVE) SET_FLAG(ifreq.ifr_flags, IFF_RUNNING); else UNSET_FLAG(ifreq.ifr_flags, IFF_RUNNING); } } #endif /* HAVE_BSD_LINK_DETECT */ if_flags_update(ifp, (ifreq.ifr_flags & 0x0000ffff)); } /* Set interface flags */ int if_set_flags(struct interface *ifp, uint64_t flags) { int ret; struct ifreq ifreq; memset(&ifreq, 0, sizeof(struct ifreq)); ifreq_set_name(&ifreq, ifp); ifreq.ifr_flags = ifp->flags; ifreq.ifr_flags |= flags; ret = vrf_if_ioctl(SIOCSIFFLAGS, (caddr_t)&ifreq, ifp->vrf_id); if (ret < 0) { zlog_info("can't set interface flags"); return ret; } return 0; } /* Unset interface's flag. */ int if_unset_flags(struct interface *ifp, uint64_t flags) { int ret; struct ifreq ifreq; memset(&ifreq, 0, sizeof(struct ifreq)); ifreq_set_name(&ifreq, ifp); ifreq.ifr_flags = ifp->flags; ifreq.ifr_flags &= ~flags; ret = vrf_if_ioctl(SIOCSIFFLAGS, (caddr_t)&ifreq, ifp->vrf_id); if (ret < 0) { zlog_info("can't unset interface flags"); return ret; } return 0; } #ifndef LINUX_IPV6 /* Netlink has its own code */ #ifdef HAVE_STRUCT_IN6_ALIASREQ #ifndef ND6_INFINITE_LIFETIME #define ND6_INFINITE_LIFETIME 0xffffffffL #endif /* ND6_INFINITE_LIFETIME */ /* * Helper for interface-addr install, non-netlink */ static int if_set_prefix6_ctx(const struct zebra_dplane_ctx *ctx) { int ret; struct in6_aliasreq addreq; struct sockaddr_in6 addr; struct sockaddr_in6 mask; struct prefix_ipv6 *p; p = (struct prefix_ipv6 *)dplane_ctx_get_intf_addr(ctx); memset(&addreq, 0, sizeof(addreq)); strlcpy((char *)&addreq.ifra_name, dplane_ctx_get_ifname(ctx), sizeof(addreq.ifra_name)); memset(&addr, 0, sizeof(struct sockaddr_in6)); addr.sin6_addr = p->prefix; addr.sin6_family = p->family; #ifdef HAVE_STRUCT_SOCKADDR_IN_SIN_LEN addr.sin6_len = sizeof(struct sockaddr_in6); #endif memcpy(&addreq.ifra_addr, &addr, sizeof(struct sockaddr_in6)); memset(&mask, 0, sizeof(struct sockaddr_in6)); masklen2ip6(p->prefixlen, &mask.sin6_addr); mask.sin6_family = p->family; #ifdef HAVE_STRUCT_SOCKADDR_IN_SIN_LEN mask.sin6_len = sizeof(struct sockaddr_in6); #endif memcpy(&addreq.ifra_prefixmask, &mask, sizeof(struct sockaddr_in6)); addreq.ifra_lifetime.ia6t_vltime = 0xffffffff; addreq.ifra_lifetime.ia6t_pltime = 0xffffffff; #ifdef HAVE_STRUCT_IF6_ALIASREQ_IFRA_LIFETIME addreq.ifra_lifetime.ia6t_pltime = ND6_INFINITE_LIFETIME; addreq.ifra_lifetime.ia6t_vltime = ND6_INFINITE_LIFETIME; #endif ret = if_ioctl_ipv6(SIOCAIFADDR_IN6, (caddr_t)&addreq); if (ret < 0) return ret; return 0; } /* * Helper for interface-addr un-install, non-netlink */ static int if_unset_prefix6_ctx(const struct zebra_dplane_ctx *ctx) { int ret; struct in6_aliasreq addreq; struct sockaddr_in6 addr; struct sockaddr_in6 mask; struct prefix_ipv6 *p; p = (struct prefix_ipv6 *)dplane_ctx_get_intf_addr(ctx); memset(&addreq, 0, sizeof(addreq)); strlcpy((char *)&addreq.ifra_name, dplane_ctx_get_ifname(ctx), sizeof(addreq.ifra_name)); memset(&addr, 0, sizeof(struct sockaddr_in6)); addr.sin6_addr = p->prefix; addr.sin6_family = p->family; #ifdef HAVE_STRUCT_SOCKADDR_IN_SIN_LEN addr.sin6_len = sizeof(struct sockaddr_in6); #endif memcpy(&addreq.ifra_addr, &addr, sizeof(struct sockaddr_in6)); memset(&mask, 0, sizeof(struct sockaddr_in6)); masklen2ip6(p->prefixlen, &mask.sin6_addr); mask.sin6_family = p->family; #ifdef HAVE_STRUCT_SOCKADDR_IN_SIN_LEN mask.sin6_len = sizeof(struct sockaddr_in6); #endif memcpy(&addreq.ifra_prefixmask, &mask, sizeof(struct sockaddr_in6)); #ifdef HAVE_STRUCT_IF6_ALIASREQ_IFRA_LIFETIME addreq.ifra_lifetime.ia6t_pltime = ND6_INFINITE_LIFETIME; addreq.ifra_lifetime.ia6t_vltime = ND6_INFINITE_LIFETIME; #endif ret = if_ioctl_ipv6(SIOCDIFADDR_IN6, (caddr_t)&addreq); if (ret < 0) return ret; return 0; } #else /* The old, pre-dataplane code here just returned, so we're retaining that * choice. */ static int if_set_prefix6_ctx(const struct zebra_dplane_ctx *ctx) { return 0; } static int if_unset_prefix6_ctx(const struct zebra_dplane_ctx *ctx) { return 0; } #endif /* HAVE_STRUCT_IN6_ALIASREQ */ #endif /* LINUX_IPV6 */ #endif /* !SUNOS_5 */ frr-7.2.1/zebra/ioctl.h0000644000000000000000000000353313610377563011606 00000000000000/* * Common ioctl functions. * Copyright (C) 1998 Kunihiro Ishiguro * * This file is part of GNU Zebra. * * GNU Zebra is free software; you can redistribute it and/or modify it * under the terms of the GNU General Public License as published by the * Free Software Foundation; either version 2, or (at your option) any * later version. * * GNU Zebra 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; see the file COPYING; if not, write to the Free Software * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA */ #ifndef _ZEBRA_IOCTL_H #define _ZEBRA_IOCTL_H #ifdef __cplusplus extern "C" { #endif /* Prototypes. */ extern void ifreq_set_name(struct ifreq *, struct interface *); extern int if_ioctl(unsigned long, caddr_t); extern int vrf_if_ioctl(unsigned long request, caddr_t buffer, vrf_id_t vrf_id); extern int if_set_flags(struct interface *, uint64_t); extern int if_unset_flags(struct interface *, uint64_t); extern void if_get_flags(struct interface *); extern void if_get_metric(struct interface *); extern void if_get_mtu(struct interface *); #ifdef SOLARIS_IPV6 extern int if_ioctl_ipv6(unsigned long, caddr_t); extern struct connected *if_lookup_linklocal(struct interface *); #define AF_IOCTL(af, request, buffer) \ ((af) == AF_INET ? if_ioctl(request, buffer) \ : if_ioctl_ipv6(request, buffer)) #else /* SOLARIS_IPV6 */ #define AF_IOCTL(af, request, buffer) if_ioctl(request, buffer) #endif /* SOLARIS_IPV6 */ #ifdef __cplusplus } #endif #endif /* _ZEBRA_IOCTL_H */ frr-7.2.1/zebra/ioctl_solaris.c0000644000000000000000000002330713610377563013336 00000000000000/* * Common ioctl functions for Solaris. * Copyright (C) 1997, 98 Kunihiro Ishiguro * * This file is part of GNU Zebra. * * GNU Zebra is free software; you can redistribute it and/or modify it * under the terms of the GNU General Public License as published by the * Free Software Foundation; either version 2, or (at your option) any * later version. * * GNU Zebra 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; see the file COPYING; if not, write to the Free Software * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA */ #include #ifdef SUNOS_5 #include "linklist.h" #include "if.h" #include "prefix.h" #include "ioctl.h" #include "log.h" #include "privs.h" #include "vty.h" #include "vrf.h" #include "lib_errors.h" #include "zebra/rib.h" #include "zebra/rt.h" #include "zebra/interface.h" #include "zebra/ioctl_solaris.h" #include "zebra/zebra_errors.h" #include "zebra/debug.h" extern struct zebra_privs_t zserv_privs; /* Prototypes */ static int if_set_prefix_ctx(const struct zebra_dplane_ctx *ctx); static int if_unset_prefix_ctx(const struct zebra_dplane_ctx *ctx); static int if_set_prefix6_ctx(const struct zebra_dplane_ctx *ctx); static int if_unset_prefix6_ctx(const struct zebra_dplane_ctx *ctx); /* clear and set interface name string */ void lifreq_set_name(struct lifreq *lifreq, const char *ifname) { strlcpy(lifreq->lifr_name, ifname, sizeof(lifreq->lifr_name)); } int vrf_if_ioctl(unsigned long request, caddr_t buffer, vrf_id_t vrf_id) { return if_ioctl(request, buffer); } /* call ioctl system call */ int if_ioctl(unsigned long request, caddr_t buffer) { int sock; int ret; int err; frr_with_privs(&zserv_privs) { sock = socket(AF_INET, SOCK_DGRAM, 0); if (sock < 0) { zlog_err("Cannot create UDP socket: %s", safe_strerror(errno)); exit(1); } if ((ret = ioctl(sock, request, buffer)) < 0) err = errno; } close(sock); if (ret < 0) { errno = err; return ret; } return 0; } int if_ioctl_ipv6(unsigned long request, caddr_t buffer) { int sock; int ret; int err; frr_with_privs(&zserv_privs) { sock = socket(AF_INET6, SOCK_DGRAM, 0); if (sock < 0) { zlog_err("Cannot create IPv6 datagram socket: %s", safe_strerror(errno)); exit(1); } if ((ret = ioctl(sock, request, buffer)) < 0) err = errno; } close(sock); if (ret < 0) { errno = err; return ret; } return 0; } /* * get interface metric * -- if value is not avaliable set -1 */ void if_get_metric(struct interface *ifp) { struct lifreq lifreq; int ret; lifreq_set_name(&lifreq, ifp->name); if (ifp->flags & IFF_IPV4) ret = AF_IOCTL(AF_INET, SIOCGLIFMETRIC, (caddr_t)&lifreq); #ifdef SOLARIS_IPV6 else if (ifp->flags & IFF_IPV6) ret = AF_IOCTL(AF_INET6, SIOCGLIFMETRIC, (caddr_t)&lifreq); #endif /* SOLARIS_IPV6 */ else ret = -1; if (ret < 0) return; ifp->metric = lifreq.lifr_metric; if (ifp->metric == 0) ifp->metric = 1; } /* get interface MTU */ void if_get_mtu(struct interface *ifp) { struct lifreq lifreq; int ret; uint8_t changed = 0; if (ifp->flags & IFF_IPV4) { lifreq_set_name(&lifreq, ifp->name); ret = AF_IOCTL(AF_INET, SIOCGLIFMTU, (caddr_t)&lifreq); if (ret < 0) { zlog_info( "Can't lookup mtu on %s by ioctl(SIOCGLIFMTU)", ifp->name); ifp->mtu = -1; } else { ifp->mtu = lifreq.lifr_metric; changed = 1; } } if (ifp->flags & IFF_IPV6) { memset(&lifreq, 0, sizeof(lifreq)); lifreq_set_name(&lifreq, ifp->name); ret = AF_IOCTL(AF_INET6, SIOCGLIFMTU, (caddr_t)&lifreq); if (ret < 0) { zlog_info( "Can't lookup mtu6 on %s by ioctl(SIOCGIFMTU)", ifp->name); ifp->mtu6 = -1; } else { ifp->mtu6 = lifreq.lifr_metric; changed = 1; } } if (changed) zebra_interface_up_update(ifp); } /* * */ enum zebra_dplane_result kernel_address_update_ctx( struct zebra_dplane_ctx *ctx) { int ret = -1; const struct prefix *p; p = dplane_ctx_get_intf_addr(ctx); if (dplane_ctx_get_op(ctx) == DPLANE_OP_ADDR_INSTALL) { if (p->family == AF_INET) ret = if_set_prefix_ctx(ctx); else ret = if_set_prefix6_ctx(ctx); } else if (dplane_ctx_get_op(ctx) == DPLANE_OP_ADDR_UNINSTALL) { if (p->family == AF_INET) ret = if_unset_prefix_ctx(ctx); else ret = if_unset_prefix6_ctx(ctx); } else { if (IS_ZEBRA_DEBUG_DPLANE) zlog_debug("Invalid op in interface-addr install"); } return (ret == 0 ? ZEBRA_DPLANE_REQUEST_SUCCESS : ZEBRA_DPLANE_REQUEST_FAILURE); } /* Set up interface's address, netmask (and broadcast? ). Solaris uses ifname:number semantics to set IP address aliases. */ static int if_set_prefix_ctx(const struct zebra_dplane_ctx *ctx) { int ret; struct ifreq ifreq; struct sockaddr_in addr, broad, mask; struct prefix_ipv4 ifaddr; struct prefix_ipv4 *p; p = (struct prefix_ipv4 *)dplane_ctx_get_intf_addr(ctx); ifaddr = *p; strlcpy(ifreq.ifr_name, dplane_ctx_get_ifname(ctx), sizeof(ifreq.ifr_name)); addr.sin_addr = p->prefix; addr.sin_family = p->family; memcpy(&ifreq.ifr_addr, &addr, sizeof(struct sockaddr_in)); ret = if_ioctl(SIOCSIFADDR, (caddr_t)&ifreq); if (ret < 0) return ret; /* We need mask for make broadcast addr. */ masklen2ip(p->prefixlen, &mask.sin_addr); if (dplane_ctx_intf_is_broadcast(ctx)) { apply_mask_ipv4(&ifaddr); addr.sin_addr = ifaddr.prefix; broad.sin_addr.s_addr = (addr.sin_addr.s_addr | ~mask.sin_addr.s_addr); broad.sin_family = p->family; memcpy(&ifreq.ifr_broadaddr, &broad, sizeof(struct sockaddr_in)); ret = if_ioctl(SIOCSIFBRDADDR, (caddr_t)&ifreq); if (ret < 0) return ret; } mask.sin_family = p->family; #ifdef SUNOS_5 memcpy(&mask, &ifreq.ifr_addr, sizeof(mask)); #else memcpy(&ifreq.ifr_netmask, &mask, sizeof(struct sockaddr_in)); #endif /* SUNOS_5 */ ret = if_ioctl(SIOCSIFNETMASK, (caddr_t)&ifreq); return ((ret < 0) ? ret : 0); } /* Set up interface's address, netmask (and broadcast). Solaris uses ifname:number semantics to set IP address aliases. */ static int if_unset_prefix_ctx(const struct zebra_dplane_ctx *ctx) { int ret; struct ifreq ifreq; struct sockaddr_in addr; struct prefix_ipv4 *p; p = (struct prefix_ipv4 *)dplane_ctx_get_intf_addr(ctx); strlcpy(ifreq.ifr_name, dplane_ctx_get_ifname(ctx), sizeof(ifreq.ifr_name)); memset(&addr, 0, sizeof(struct sockaddr_in)); addr.sin_family = p->family; memcpy(&ifreq.ifr_addr, &addr, sizeof(struct sockaddr_in)); ret = if_ioctl(SIOCSIFADDR, (caddr_t)&ifreq); if (ret < 0) return ret; return 0; } /* Get just the flags for the given name. * Used by the normal 'if_get_flags' function, as well * as the bootup interface-list code, which has to peek at per-address * flags in order to figure out which ones should be ignored.. */ int if_get_flags_direct(const char *ifname, uint64_t *flags, unsigned int af) { struct lifreq lifreq; int ret; lifreq_set_name(&lifreq, ifname); ret = AF_IOCTL(af, SIOCGLIFFLAGS, (caddr_t)&lifreq); if (ret) zlog_debug("%s: ifname %s, error %s (%d)", __func__, ifname, safe_strerror(errno), errno); *flags = lifreq.lifr_flags; return ret; } /* get interface flags */ void if_get_flags(struct interface *ifp) { int ret4 = 0, ret6 = 0; uint64_t newflags = 0; uint64_t tmpflags; if (ifp->flags & IFF_IPV4) { ret4 = if_get_flags_direct(ifp->name, &tmpflags, AF_INET); if (!ret4) newflags |= tmpflags; else if (errno == ENXIO) { /* it's gone */ UNSET_FLAG(ifp->flags, IFF_UP); if_flags_update(ifp, ifp->flags); } } if (ifp->flags & IFF_IPV6) { ret6 = if_get_flags_direct(ifp->name, &tmpflags, AF_INET6); if (!ret6) newflags |= tmpflags; else if (errno == ENXIO) { /* it's gone */ UNSET_FLAG(ifp->flags, IFF_UP); if_flags_update(ifp, ifp->flags); } } /* only update flags if one of above succeeded */ if (!(ret4 && ret6)) if_flags_update(ifp, newflags); } /* Set interface flags */ int if_set_flags(struct interface *ifp, uint64_t flags) { int ret; struct lifreq lifreq; lifreq_set_name(&lifreq, ifp->name); lifreq.lifr_flags = ifp->flags; lifreq.lifr_flags |= flags; if (ifp->flags & IFF_IPV4) ret = AF_IOCTL(AF_INET, SIOCSLIFFLAGS, (caddr_t)&lifreq); else if (ifp->flags & IFF_IPV6) ret = AF_IOCTL(AF_INET6, SIOCSLIFFLAGS, (caddr_t)&lifreq); else ret = -1; if (ret < 0) zlog_info("can't set interface flags on %s: %s", ifp->name, safe_strerror(errno)); else ret = 0; return ret; } /* Unset interface's flag. */ int if_unset_flags(struct interface *ifp, uint64_t flags) { int ret; struct lifreq lifreq; lifreq_set_name(&lifreq, ifp->name); lifreq.lifr_flags = ifp->flags; lifreq.lifr_flags &= ~flags; if (ifp->flags & IFF_IPV4) ret = AF_IOCTL(AF_INET, SIOCSLIFFLAGS, (caddr_t)&lifreq); else if (ifp->flags & IFF_IPV6) ret = AF_IOCTL(AF_INET6, SIOCSLIFFLAGS, (caddr_t)&lifreq); else ret = -1; if (ret < 0) zlog_info("can't unset interface flags"); else ret = 0; return ret; } /* Interface's address add/delete functions. */ static int if_set_prefix6_ctx(const struct zebra_dplane_ctx *ctx) { char addrbuf[PREFIX_STRLEN]; prefix2str(dplane_ctx_get_intf_addr(ctx), addrbuf, sizeof(addrbuf)); flog_warn(EC_LIB_DEVELOPMENT, "Can't set %s on interface %s", addrbuf, dplane_ctx_get_ifname(ctx)); return 0; } static int if_unset_prefix6_ctx(const struct zebra_dplane_ctx *ctx) { char addrbuf[PREFIX_STRLEN]; prefix2str(dplane_ctx_get_intf_addr(ctx), addrbuf, sizeof(addrbuf)); flog_warn(EC_LIB_DEVELOPMENT, "Can't delete %s on interface %s", addrbuf, dplane_ctx_get_ifname(ctx)); return 0; } #endif /* SUNOS_5 */ frr-7.2.1/zebra/ioctl_solaris.h0000644000000000000000000000216413610377563013341 00000000000000/* * Interface looking up by ioctl () on Solaris. * Copyright (C) 1999 Kunihiro Ishiguro * * This file is part of Quagga. * * Quagga is free software; you can redistribute it and/or modify it * under the terms of the GNU General Public License as published by the * Free Software Foundation; either version 2, or (at your option) any * later version. * * Quagga 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; see the file COPYING; if not, write to the Free Software * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA */ #ifndef _ZEBRA_IF_IOCTL_SOLARIS_H #define _ZEBRA_IF_IOCTL_SOLARIS_H #ifdef __cplusplus extern "C" { #endif void lifreq_set_name(struct lifreq *, const char *); int if_get_flags_direct(const char *, uint64_t *, unsigned int af); #ifdef __cplusplus } #endif #endif /* _ZEBRA_IF_IOCTL_SOLARIS_H */ frr-7.2.1/zebra/ipforward.h0000644000000000000000000000223213610377563012464 00000000000000/* IP forward settings. * Copyright (C) 1999 Kunihiro Ishiguro * * This file is part of GNU Zebra. * * GNU Zebra is free software; you can redistribute it and/or modify it * under the terms of the GNU General Public License as published by the * Free Software Foundation; either version 2, or (at your option) any * later version. * * GNU Zebra 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; see the file COPYING; if not, write to the Free Software * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA */ #ifndef _ZEBRA_IPFORWARD_H #define _ZEBRA_IPFORWARD_H #ifdef __cplusplus extern "C" { #endif extern int ipforward(void); extern int ipforward_on(void); extern int ipforward_off(void); extern int ipforward_ipv6(void); extern int ipforward_ipv6_on(void); extern int ipforward_ipv6_off(void); #ifdef __cplusplus } #endif #endif /* _ZEBRA_IPFORWARD_H */ frr-7.2.1/zebra/ipforward_proc.c0000644000000000000000000000570013610377563013505 00000000000000/* * Fetch ipforward value by reading /proc filesystem. * Copyright (C) 1997 Kunihiro Ishiguro * * This file is part of GNU Zebra. * * GNU Zebra is free software; you can redistribute it and/or modify it * under the terms of the GNU General Public License as published by the * Free Software Foundation; either version 2, or (at your option) any * later version. * * GNU Zebra 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; see the file COPYING; if not, write to the Free Software * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA */ #include #ifdef GNU_LINUX #include "log.h" #include "privs.h" #include "zebra/ipforward.h" extern struct zebra_privs_t zserv_privs; char proc_net_snmp[] = "/proc/net/snmp"; static void dropline(FILE *fp) { int c; while ((c = getc(fp)) != '\n') ; } int ipforward(void) { int ret = 0; FILE *fp; int ipforwarding = 0; char buf[10]; fp = fopen(proc_net_snmp, "r"); if (fp == NULL) return -1; /* We don't care about the first line. */ dropline(fp); /* Get ip_statistics.IpForwarding : 1 => ip forwarding enabled 2 => ip forwarding off. */ if (fgets(buf, 6, fp)) ret = sscanf(buf, "Ip: %d", &ipforwarding); fclose(fp); if (ret == 1 && ipforwarding == 1) return 1; return 0; } /* char proc_ipv4_forwarding[] = "/proc/sys/net/ipv4/conf/all/forwarding"; */ char proc_ipv4_forwarding[] = "/proc/sys/net/ipv4/ip_forward"; int ipforward_on(void) { FILE *fp; frr_with_privs(&zserv_privs) { fp = fopen(proc_ipv4_forwarding, "w"); if (fp == NULL) { return -1; } fprintf(fp, "1\n"); fclose(fp); } return ipforward(); } int ipforward_off(void) { FILE *fp; frr_with_privs(&zserv_privs) { fp = fopen(proc_ipv4_forwarding, "w"); if (fp == NULL) { return -1; } fprintf(fp, "0\n"); fclose(fp); } return ipforward(); } char proc_ipv6_forwarding[] = "/proc/sys/net/ipv6/conf/all/forwarding"; int ipforward_ipv6(void) { int ret = 0; FILE *fp; char buf[5]; int ipforwarding = 0; fp = fopen(proc_ipv6_forwarding, "r"); if (fp == NULL) return -1; if (fgets(buf, 2, fp)) ret = sscanf(buf, "%d", &ipforwarding); fclose(fp); if (ret != 1) return 0; return ipforwarding; } int ipforward_ipv6_on(void) { FILE *fp; frr_with_privs(&zserv_privs) { fp = fopen(proc_ipv6_forwarding, "w"); if (fp == NULL) { return -1; } fprintf(fp, "1\n"); fclose(fp); } return ipforward_ipv6(); } int ipforward_ipv6_off(void) { FILE *fp; frr_with_privs(&zserv_privs) { fp = fopen(proc_ipv6_forwarding, "w"); if (fp == NULL) { return -1; } fprintf(fp, "0\n"); fclose(fp); } return ipforward_ipv6(); } #endif /* GNU_LINUX */ frr-7.2.1/zebra/ipforward_solaris.c0000644000000000000000000000760413610377563014223 00000000000000/* * ipforward value get function for solaris. * Copyright (C) 1997 Kunihiro Ishiguro * * This file is part of GNU Zebra. * * GNU Zebra is free software; you can redistribute it and/or modify it * under the terms of the GNU General Public License as published by the * Free Software Foundation; either version 2, or (at your option) any * later version. * * GNU Zebra 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; see the file COPYING; if not, write to the Free Software * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA */ #include #ifdef SUNOS_5 #include "log.h" #include "prefix.h" #include "lib_errors.h" #include "privs.h" #include "zebra/ipforward.h" #include "zebra/zebra_errors.h" /* ** Solaris should define IP_DEV_NAME in , but we'll save ** configure.in changes for another day. We can use the same device ** for both IPv4 and IPv6. */ /* #include */ #ifndef IP_DEV_NAME #define IP_DEV_NAME "/dev/ip" #endif extern struct zebra_privs_t zserv_privs; /* This is a limited ndd style function that operates one integer ** value only. Errors return -1. ND_SET commands return 0 on ** success. ND_GET commands return the value on success (which could ** be -1 and be confused for an error). The parameter is the string ** name of the parameter being referenced. */ static int solaris_nd(const int cmd, const char *parameter, const int value) { #define ND_BUFFER_SIZE 1024 int fd; char nd_buf[ND_BUFFER_SIZE]; struct strioctl strioctl; const char *device = IP_DEV_NAME; int retval; memset(nd_buf, '\0', ND_BUFFER_SIZE); /* ** ND_SET takes a NULL delimited list of strings further terminated ** buy a NULL. ND_GET returns a list in a similar layout, although ** here we only use the first result. */ if (cmd == ND_SET) snprintf(nd_buf, ND_BUFFER_SIZE, "%s%c%d%c", parameter, '\0', value, '\0'); else if (cmd == ND_GET) snprintf(nd_buf, ND_BUFFER_SIZE, "%s", parameter); else { flog_err_sys(EC_LIB_SYSTEM_CALL, "internal error - inappropriate command given to " "solaris_nd()%s:%d", __FILE__, __LINE__); return -1; } strioctl.ic_cmd = cmd; strioctl.ic_timout = 0; strioctl.ic_len = ND_BUFFER_SIZE; strioctl.ic_dp = nd_buf; frr_with_privs(&zserv_privs) { if ((fd = open(device, O_RDWR)) < 0) { flog_err_sys(EC_LIB_SYSTEM_CALL, "failed to open device %s - %s", device, safe_strerror(errno)); return -1; } if (ioctl(fd, I_STR, &strioctl) < 0) { close(fd); flog_err_sys(EC_LIB_SYSTEM_CALL, "ioctl I_STR failed on device %s - %s", device, safe_strerror(errno)); return -1; } close(fd); } if (cmd == ND_GET) { errno = 0; retval = atoi(nd_buf); if (errno) { zlog_debug( "failed to convert returned value to integer - %s", safe_strerror(errno)); retval = -1; } } else { retval = 0; } return retval; } static int solaris_nd_set(const char *parameter, const int value) { return solaris_nd(ND_SET, parameter, value); } static int solaris_nd_get(const char *parameter) { return solaris_nd(ND_GET, parameter, 0); } int ipforward(void) { return solaris_nd_get("ip_forwarding"); } int ipforward_on(void) { (void)solaris_nd_set("ip_forwarding", 1); return ipforward(); } int ipforward_off(void) { (void)solaris_nd_set("ip_forwarding", 0); return ipforward(); } int ipforward_ipv6(void) { return solaris_nd_get("ip6_forwarding"); } int ipforward_ipv6_on(void) { (void)solaris_nd_set("ip6_forwarding", 1); return ipforward_ipv6(); } int ipforward_ipv6_off(void) { (void)solaris_nd_set("ip6_forwarding", 0); return ipforward_ipv6(); } #endif /* SUNOS_5 */ frr-7.2.1/zebra/ipforward_sysctl.c0000644000000000000000000000644413610377563014071 00000000000000/* IP forward control by sysctl function. * Copyright (C) 1997, 1999 Kunihiro Ishiguro * * This file is part of GNU Zebra. * * GNU Zebra is free software; you can redistribute it and/or modify it * under the terms of the GNU General Public License as published by the * Free Software Foundation; either version 2, or (at your option) any * later version. * * GNU Zebra 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; see the file COPYING; if not, write to the Free Software * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA */ #include #if !defined(GNU_LINUX) && !defined(SUNOS_5) #include "privs.h" #include "zebra/ipforward.h" #include "zebra/zebra_errors.h" #include "log.h" #include "lib_errors.h" #define MIB_SIZ 4 extern struct zebra_privs_t zserv_privs; /* IPv4 forwarding control MIB. */ int mib[MIB_SIZ] = {CTL_NET, PF_INET, IPPROTO_IP, IPCTL_FORWARDING}; int ipforward(void) { size_t len; int ipforwarding = 0; len = sizeof ipforwarding; if (sysctl(mib, MIB_SIZ, &ipforwarding, &len, 0, 0) < 0) { flog_err_sys(EC_LIB_SYSTEM_CALL, "Can't get ipforwarding value"); return -1; } return ipforwarding; } int ipforward_on(void) { size_t len; int ipforwarding = 1; len = sizeof ipforwarding; frr_with_privs(&zserv_privs) { if (sysctl(mib, MIB_SIZ, NULL, NULL, &ipforwarding, len) < 0) { flog_err_sys(EC_LIB_SYSTEM_CALL, "Can't set ipforwarding on"); return -1; } } return ipforwarding; } int ipforward_off(void) { size_t len; int ipforwarding = 0; len = sizeof ipforwarding; frr_with_privs(&zserv_privs) { if (sysctl(mib, MIB_SIZ, NULL, NULL, &ipforwarding, len) < 0) { flog_err_sys(EC_LIB_SYSTEM_CALL, "Can't set ipforwarding on"); return -1; } } return ipforwarding; } /* IPv6 forwarding control MIB. */ int mib_ipv6[MIB_SIZ] = {CTL_NET, PF_INET6, #if defined(BSD_V6_SYSCTL) IPPROTO_IPV6, IPV6CTL_FORWARDING #else /* NOT BSD_V6_SYSCTL */ IPPROTO_IP, IP6CTL_FORWARDING #endif /* BSD_V6_SYSCTL */ }; int ipforward_ipv6(void) { size_t len; int ip6forwarding = 0; len = sizeof ip6forwarding; frr_with_privs(&zserv_privs) { if (sysctl(mib_ipv6, MIB_SIZ, &ip6forwarding, &len, 0, 0) < 0) { flog_err_sys(EC_LIB_SYSTEM_CALL, "can't get ip6forwarding value"); return -1; } } return ip6forwarding; } int ipforward_ipv6_on(void) { size_t len; int ip6forwarding = 1; len = sizeof ip6forwarding; frr_with_privs(&zserv_privs) { if (sysctl(mib_ipv6, MIB_SIZ, NULL, NULL, &ip6forwarding, len) < 0) { flog_err_sys(EC_LIB_SYSTEM_CALL, "can't get ip6forwarding value"); return -1; } } return ip6forwarding; } int ipforward_ipv6_off(void) { size_t len; int ip6forwarding = 0; len = sizeof ip6forwarding; frr_with_privs(&zserv_privs) { if (sysctl(mib_ipv6, MIB_SIZ, NULL, NULL, &ip6forwarding, len) < 0) { flog_err_sys(EC_LIB_SYSTEM_CALL, "can't get ip6forwarding value"); return -1; } } return ip6forwarding; } #endif /* !defined(GNU_LINUX) && !defined(SUNOS_5) */ frr-7.2.1/zebra/irdp.h0000644000000000000000000001151613610377563011432 00000000000000/* ICMP Router Discovery Messages * Copyright (C) 1997, 2000 Kunihiro Ishiguro * * This file is part of GNU Zebra. * * GNU Zebra is free software; you can redistribute it and/or modify it * under the terms of the GNU General Public License as published by the * Free Software Foundation; either version 2, or (at your option) any * later version. * * GNU Zebra 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; see the file COPYING; if not, write to the Free Software * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA */ /* * This file is modified and completed for the Zebra IRDP implementation * by Robert Olsson, Swedish University of Agricultural Sciences */ #ifndef _IRDP_H #define _IRDP_H #include "lib/vty.h" #ifdef __cplusplus extern "C" { #endif /* ICMP Messages */ #ifndef ICMP_ROUTERADVERT #define ICMP_ROUTERADVERT 9 #endif /* ICMP_ROUTERADVERT */ #ifndef ICMP_ROUTERSOLICIT #define ICMP_ROUTERSOLICIT 10 #endif /* ICMP_ROUTERSOLICT */ /* Multicast groups */ #ifndef INADDR_ALLHOSTS_GROUP #define INADDR_ALLHOSTS_GROUP 0xe0000001U /* 224.0.0.1 */ #endif /* INADDR_ALLHOSTS_GROUP */ #ifndef INADDR_ALLRTRS_GROUP #define INADDR_ALLRTRS_GROUP 0xe0000002U /* 224.0.0.2 */ #endif /* INADDR_ALLRTRS_GROUP */ /* Default irdp packet interval */ #define IRDP_DEFAULT_INTERVAL 300 /* Router constants from RFC1256 */ #define MAX_INITIAL_ADVERT_INTERVAL 16 #define MAX_INITIAL_ADVERTISEMENTS 3 #define MAX_RESPONSE_DELAY 2 #define IRDP_MAXADVERTINTERVAL 600 #define IRDP_MINADVERTINTERVAL 450 /* 0.75*600 */ #define IRDP_LIFETIME 1350 /* 3*450 */ #define IRDP_PREFERENCE 0 #define ICMP_MINLEN 8 #define IRDP_LAST_ADVERT_MESSAGES 2 /* The last adverts with Holdtime 0 */ #define IRDP_RX_BUF 1500 /* Comments comes from RFC1256 ICMP Router Discovery Messages. The IP destination address to be used for multicast Router Advertisements sent from the interface. The only permissible values are the all-systems multicast address, 224.0.0.1, or the limited-broadcast address, 255.255.255.255. (The all-systems address is preferred wherever possible, i.e., on any link where all listening hosts support IP multicast.) Default: 224.0.0.1 if the router supports IP multicast on the interface, else 255.255.255.255 The maximum time allowed between sending multicast Router Advertisements from the interface, in seconds. Must be no less than 4 seconds and no greater than 1800 seconds. Default: 600 seconds The minimum time allowed between sending unsolicited multicast Router Advertisements from the interface, in seconds. Must be no less than 3 seconds and no greater than MaxAdvertisementInterval. Default: 0.75 * MaxAdvertisementInterval The value to be placed in the Lifetime field of Router Advertisements sent from the interface, in seconds. Must be no less than MaxAdvertisementInterval and no greater than 9000 seconds. Default: 3 * MaxAdvertisementInterval The preferability of the address as a default router address, relative to other router addresses on the same subnet. A 32-bit, signed, twos-complement integer, with higher values meaning more preferable. The minimum value (hex 80000000) is used to indicate that the address, even though it may be advertised, is not to be used by neighboring hosts as a default router address. Default: 0 */ struct irdp_interface { bool started; unsigned long MaxAdvertInterval; unsigned long MinAdvertInterval; unsigned long Preference; uint32_t flags; #define IF_ACTIVE (1<<0) /* ICMP Active */ #define IF_BROADCAST (1<<1) /* 255.255.255.255 */ #define IF_SOLICIT (1<<2) /* Solicit active */ #define IF_DEBUG_MESSAGES (1<<3) #define IF_DEBUG_PACKET (1<<4) #define IF_DEBUG_MISC (1<<5) #define IF_SHUTDOWN (1<<6) struct interface *ifp; struct thread *t_advertise; unsigned long irdp_sent; uint16_t Lifetime; struct list *AdvPrefList; }; struct Adv { struct in_addr ip; int pref; }; extern void irdp_if_init(void); extern int irdp_sock_init(void); extern int irdp_config_write(struct vty *, struct interface *); extern int irdp_send_thread(struct thread *t_advert); extern void irdp_advert_off(struct interface *ifp); extern void process_solicit(struct interface *ifp); extern int irdp_read_raw(struct thread *r); extern void send_packet(struct interface *ifp, struct stream *s, uint32_t dst, struct prefix *p, uint32_t ttl); #ifdef __cplusplus } #endif #endif /* _IRDP_H */ frr-7.2.1/zebra/irdp_interface.c0000644000000000000000000004272713610377563013455 00000000000000/* * * Copyright (C) 1997, 2000 * Portions: * Swedish University of Agricultural Sciences * Robert Olsson * Kunihiro Ishiguro * * Thanks to Jens Laas at Swedish University of Agricultural Sciences * for reviewing and tests. * * This file is part of GNU Zebra. * * GNU Zebra is free software; you can redistribute it and/or modify it * under the terms of the GNU General Public License as published by the * Free Software Foundation; either version 2, or (at your option) any * later version. * * GNU Zebra 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; see the file COPYING; if not, write to the Free Software * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA */ #include #include "if.h" #include "vty.h" #include "sockunion.h" #include "prefix.h" #include "command.h" #include "memory.h" #include "zebra_memory.h" #include "stream.h" #include "ioctl.h" #include "connected.h" #include "log.h" #include "zclient.h" #include "thread.h" #include "lib_errors.h" #include "zebra/interface.h" #include "zebra/rtadv.h" #include "zebra/rib.h" #include "zebra/zebra_router.h" #include "zebra/redistribute.h" #include "zebra/irdp.h" #include "zebra/zebra_errors.h" #include #include "if.h" #include "sockunion.h" #include "log.h" extern int irdp_sock; DEFINE_MTYPE_STATIC(ZEBRA, IRDP_IF, "IRDP interface data") #define IRDP_CONFIGED \ do { \ if (!irdp) { \ vty_out(vty, \ "Please Configure IRDP before using this command\n"); \ return CMD_WARNING_CONFIG_FAILED; \ } \ } while (0) static struct irdp_interface *irdp_if_get(struct interface *ifp) { struct zebra_if *zi = ifp->info; if (!zi) return NULL; if (!zi->irdp) zi->irdp = XCALLOC(MTYPE_IRDP_IF, sizeof(*zi->irdp)); if (!zi->irdp->started) return NULL; return zi->irdp; } static int irdp_if_delete(struct interface *ifp) { struct zebra_if *zi = ifp->info; if (!zi) return 0; XFREE(MTYPE_IRDP_IF, zi->irdp); return 0; } static const char *inet_2a(uint32_t a, char *b) { sprintf(b, "%u.%u.%u.%u", (a)&0xFF, (a >> 8) & 0xFF, (a >> 16) & 0xFF, (a >> 24) & 0xFF); return b; } static struct prefix *irdp_get_prefix(struct interface *ifp) { struct listnode *node; struct connected *ifc; if (ifp->connected) for (ALL_LIST_ELEMENTS_RO(ifp->connected, node, ifc)) return ifc->address; return NULL; } /* Join to the add/leave multicast group. */ static int if_group(struct interface *ifp, int sock, uint32_t group, int add_leave) { struct ip_mreq m; struct prefix *p; int ret; char b1[INET_ADDRSTRLEN]; memset(&m, 0, sizeof(m)); m.imr_multiaddr.s_addr = htonl(group); p = irdp_get_prefix(ifp); if (!p) { flog_warn(EC_ZEBRA_NO_IFACE_ADDR, "IRDP: can't get address for %s", ifp->name); return 1; } m.imr_interface = p->u.prefix4; ret = setsockopt(sock, IPPROTO_IP, add_leave, (char *)&m, sizeof(struct ip_mreq)); if (ret < 0) flog_err_sys(EC_LIB_SOCKET, "IRDP: %s can't setsockopt %s: %s", add_leave == IP_ADD_MEMBERSHIP ? "join group" : "leave group", inet_2a(group, b1), safe_strerror(errno)); return ret; } static int if_add_group(struct interface *ifp) { struct zebra_if *zi = ifp->info; struct irdp_interface *irdp = zi->irdp; int ret; char b1[INET_ADDRSTRLEN]; if (!irdp) return -1; ret = if_group(ifp, irdp_sock, INADDR_ALLRTRS_GROUP, IP_ADD_MEMBERSHIP); if (ret < 0) { return ret; } if (irdp->flags & IF_DEBUG_MISC) zlog_debug("IRDP: Adding group %s for %s", inet_2a(htonl(INADDR_ALLRTRS_GROUP), b1), ifp->name); return 0; } static int if_drop_group(struct interface *ifp) { struct zebra_if *zi = ifp->info; struct irdp_interface *irdp = zi->irdp; int ret; char b1[INET_ADDRSTRLEN]; if (!irdp) return -1; ret = if_group(ifp, irdp_sock, INADDR_ALLRTRS_GROUP, IP_DROP_MEMBERSHIP); if (ret < 0) return ret; if (irdp->flags & IF_DEBUG_MISC) zlog_debug("IRDP: Leaving group %s for %s", inet_2a(htonl(INADDR_ALLRTRS_GROUP), b1), ifp->name); return 0; } static void if_set_defaults(struct irdp_interface *irdp) { irdp->MaxAdvertInterval = IRDP_MAXADVERTINTERVAL; irdp->MinAdvertInterval = IRDP_MINADVERTINTERVAL; irdp->Preference = IRDP_PREFERENCE; irdp->Lifetime = IRDP_LIFETIME; } static struct Adv *Adv_new(void) { return XCALLOC(MTYPE_TMP, sizeof(struct Adv)); } static void Adv_free(struct Adv *adv) { XFREE(MTYPE_TMP, adv); } static void irdp_if_start(struct interface *ifp, int multicast, int set_defaults) { struct zebra_if *zi = ifp->info; struct irdp_interface *irdp = zi->irdp; struct listnode *node; struct connected *ifc; uint32_t timer, seed; assert(irdp); irdp->started = true; if (irdp->flags & IF_ACTIVE) { zlog_debug("IRDP: Interface is already active %s", ifp->name); return; } if ((irdp_sock < 0) && ((irdp_sock = irdp_sock_init()) < 0)) { flog_warn(EC_ZEBRA_IRDP_CANNOT_ACTIVATE_IFACE, "IRDP: Cannot activate interface %s (cannot create " "IRDP socket)", ifp->name); return; } irdp->flags |= IF_ACTIVE; if (!multicast) irdp->flags |= IF_BROADCAST; if_add_update(ifp); if (!(ifp->flags & IFF_UP)) { flog_warn(EC_ZEBRA_IRDP_IFACE_DOWN, "IRDP: Interface is down %s", ifp->name); } /* Shall we cancel if_start if if_add_group fails? */ if (multicast) { if_add_group(ifp); if (!(ifp->flags & (IFF_MULTICAST | IFF_ALLMULTI))) { flog_warn(EC_ZEBRA_IRDP_IFACE_MCAST_DISABLED, "IRDP: Interface not multicast enabled %s", ifp->name); } } if (set_defaults) if_set_defaults(irdp); irdp->irdp_sent = 0; /* The spec suggests this for randomness */ seed = 0; if (ifp->connected) for (ALL_LIST_ELEMENTS_RO(ifp->connected, node, ifc)) { seed = ifc->address->u.prefix4.s_addr; break; } srandom(seed); timer = (random() % IRDP_DEFAULT_INTERVAL) + 1; irdp->AdvPrefList = list_new(); irdp->AdvPrefList->del = (void (*)(void *))Adv_free; /* Destructor */ /* And this for startup. Speed limit from 1991 :-). But it's OK*/ if (irdp->irdp_sent < MAX_INITIAL_ADVERTISEMENTS && timer > MAX_INITIAL_ADVERT_INTERVAL) timer = MAX_INITIAL_ADVERT_INTERVAL; if (irdp->flags & IF_DEBUG_MISC) zlog_debug("IRDP: Init timer for %s set to %u", ifp->name, timer); irdp->t_advertise = NULL; thread_add_timer(zrouter.master, irdp_send_thread, ifp, timer, &irdp->t_advertise); } static void irdp_if_stop(struct interface *ifp) { struct zebra_if *zi = ifp->info; struct irdp_interface *irdp = zi->irdp; if (irdp == NULL) { zlog_debug("Interface %s structure is NULL", ifp->name); return; } if (!(irdp->flags & IF_ACTIVE)) { zlog_debug("Interface is not active %s", ifp->name); return; } if (!(irdp->flags & IF_BROADCAST)) if_drop_group(ifp); irdp_advert_off(ifp); list_delete(&irdp->AdvPrefList); irdp->flags = 0; } static void irdp_if_shutdown(struct interface *ifp) { struct zebra_if *zi = ifp->info; struct irdp_interface *irdp = zi->irdp; if (!irdp) return; if (irdp->flags & IF_SHUTDOWN) { zlog_debug("IRDP: Interface is already shutdown %s", ifp->name); return; } irdp->flags |= IF_SHUTDOWN; irdp->flags &= ~IF_ACTIVE; if (!(irdp->flags & IF_BROADCAST)) if_drop_group(ifp); /* Tell the hosts we are out of service */ irdp_advert_off(ifp); } static void irdp_if_no_shutdown(struct interface *ifp) { struct irdp_interface *irdp = irdp_if_get(ifp); if (!irdp) return; if (!(irdp->flags & IF_SHUTDOWN)) { zlog_debug("IRDP: Interface is not shutdown %s", ifp->name); return; } irdp->flags &= ~IF_SHUTDOWN; irdp_if_start(ifp, irdp->flags & IF_BROADCAST ? false : true, false); } /* Write configuration to user */ int irdp_config_write(struct vty *vty, struct interface *ifp) { struct zebra_if *zi = ifp->info; struct irdp_interface *irdp = zi->irdp; struct Adv *adv; struct listnode *node; char b1[INET_ADDRSTRLEN]; if (!irdp) return 0; if (irdp->flags & IF_ACTIVE || irdp->flags & IF_SHUTDOWN) { if (irdp->flags & IF_SHUTDOWN) vty_out(vty, " ip irdp shutdown \n"); if (irdp->flags & IF_BROADCAST) vty_out(vty, " ip irdp broadcast\n"); else vty_out(vty, " ip irdp multicast\n"); vty_out(vty, " ip irdp preference %ld\n", irdp->Preference); for (ALL_LIST_ELEMENTS_RO(irdp->AdvPrefList, node, adv)) vty_out(vty, " ip irdp address %s preference %d\n", inet_2a(adv->ip.s_addr, b1), adv->pref); vty_out(vty, " ip irdp holdtime %d\n", irdp->Lifetime); vty_out(vty, " ip irdp minadvertinterval %ld\n", irdp->MinAdvertInterval); vty_out(vty, " ip irdp maxadvertinterval %ld\n", irdp->MaxAdvertInterval); } return 0; } DEFUN (ip_irdp_multicast, ip_irdp_multicast_cmd, "ip irdp multicast", IP_STR "ICMP Router discovery on this interface\n" "Use multicast mode\n") { VTY_DECLVAR_CONTEXT(interface, ifp); irdp_if_get(ifp); irdp_if_start(ifp, true, true); return CMD_SUCCESS; } DEFUN (ip_irdp_broadcast, ip_irdp_broadcast_cmd, "ip irdp broadcast", IP_STR "ICMP Router discovery on this interface\n" "Use broadcast mode\n") { VTY_DECLVAR_CONTEXT(interface, ifp); irdp_if_get(ifp); irdp_if_start(ifp, false, true); return CMD_SUCCESS; } DEFUN (no_ip_irdp, no_ip_irdp_cmd, "no ip irdp", NO_STR IP_STR "Disable ICMP Router discovery on this interface\n") { VTY_DECLVAR_CONTEXT(interface, ifp); irdp_if_stop(ifp); return CMD_SUCCESS; } DEFUN (ip_irdp_shutdown, ip_irdp_shutdown_cmd, "ip irdp shutdown", IP_STR "ICMP Router discovery on this interface\n" "ICMP Router discovery shutdown on this interface\n") { VTY_DECLVAR_CONTEXT(interface, ifp); irdp_if_shutdown(ifp); return CMD_SUCCESS; } DEFUN (no_ip_irdp_shutdown, no_ip_irdp_shutdown_cmd, "no ip irdp shutdown", NO_STR IP_STR "ICMP Router discovery on this interface\n" "ICMP Router discovery no shutdown on this interface\n") { VTY_DECLVAR_CONTEXT(interface, ifp); irdp_if_no_shutdown(ifp); return CMD_SUCCESS; } DEFUN (ip_irdp_holdtime, ip_irdp_holdtime_cmd, "ip irdp holdtime (0-9000)", IP_STR "ICMP Router discovery on this interface\n" "Set holdtime value\n" "Holdtime value in seconds. Default is 1800 seconds\n") { int idx_number = 3; VTY_DECLVAR_CONTEXT(interface, ifp); struct irdp_interface *irdp = irdp_if_get(ifp); IRDP_CONFIGED; irdp->Lifetime = atoi(argv[idx_number]->arg); return CMD_SUCCESS; } DEFUN (ip_irdp_minadvertinterval, ip_irdp_minadvertinterval_cmd, "ip irdp minadvertinterval (3-1800)", IP_STR "ICMP Router discovery on this interface\n" "Set minimum time between advertisement\n" "Minimum advertisement interval in seconds\n") { int idx_number = 3; VTY_DECLVAR_CONTEXT(interface, ifp); struct irdp_interface *irdp = irdp_if_get(ifp); IRDP_CONFIGED; if ((unsigned)atoi(argv[idx_number]->arg) <= irdp->MaxAdvertInterval) { irdp->MinAdvertInterval = atoi(argv[idx_number]->arg); return CMD_SUCCESS; } else { vty_out(vty, "%% MinAdvertInterval must be less than or equal to " "MaxAdvertInterval\n"); return CMD_WARNING_CONFIG_FAILED; } } DEFUN (ip_irdp_maxadvertinterval, ip_irdp_maxadvertinterval_cmd, "ip irdp maxadvertinterval (4-1800)", IP_STR "ICMP Router discovery on this interface\n" "Set maximum time between advertisement\n" "Maximum advertisement interval in seconds\n") { int idx_number = 3; VTY_DECLVAR_CONTEXT(interface, ifp); struct irdp_interface *irdp = irdp_if_get(ifp); IRDP_CONFIGED; if (irdp->MinAdvertInterval <= (unsigned)atoi(argv[idx_number]->arg)) { irdp->MaxAdvertInterval = atoi(argv[idx_number]->arg); return CMD_SUCCESS; } else { vty_out(vty, "%% MaxAdvertInterval must be greater than or equal to " "MinAdvertInterval\n"); return CMD_WARNING_CONFIG_FAILED; } } /* DEFUN needs to be fixed for negative ranages... * "ip irdp preference <-2147483648-2147483647>", * Be positive for now. :-) */ DEFUN (ip_irdp_preference, ip_irdp_preference_cmd, "ip irdp preference (0-2147483647)", IP_STR "ICMP Router discovery on this interface\n" "Set default preference level for this interface\n" "Preference level\n") { int idx_number = 3; VTY_DECLVAR_CONTEXT(interface, ifp); struct irdp_interface *irdp = irdp_if_get(ifp); IRDP_CONFIGED; irdp->Preference = atoi(argv[idx_number]->arg); return CMD_SUCCESS; } DEFUN (ip_irdp_address_preference, ip_irdp_address_preference_cmd, "ip irdp address A.B.C.D preference (0-2147483647)", IP_STR "Alter ICMP Router discovery preference on this interface\n" "Set IRDP address for advertise\n" "IPv4 address\n" "Specify IRDP non-default preference to advertise\n" "Preference level\n") { int idx_ipv4 = 3; int idx_number = 5; VTY_DECLVAR_CONTEXT(interface, ifp); struct irdp_interface *irdp = irdp_if_get(ifp); struct listnode *node; struct in_addr ip; int pref; int ret; struct Adv *adv; IRDP_CONFIGED; ret = inet_aton(argv[idx_ipv4]->arg, &ip); if (!ret) return CMD_WARNING_CONFIG_FAILED; pref = atoi(argv[idx_number]->arg); for (ALL_LIST_ELEMENTS_RO(irdp->AdvPrefList, node, adv)) if (adv->ip.s_addr == ip.s_addr) return CMD_SUCCESS; adv = Adv_new(); adv->ip = ip; adv->pref = pref; listnode_add(irdp->AdvPrefList, adv); return CMD_SUCCESS; } DEFUN (no_ip_irdp_address_preference, no_ip_irdp_address_preference_cmd, "no ip irdp address A.B.C.D preference (0-2147483647)", NO_STR IP_STR "Alter ICMP Router discovery preference on this interface\n" "Select IRDP address\n" "IPv4 address\n" "Reset ICMP Router discovery preference on this interface\n" "Old preference level\n") { int idx_ipv4 = 4; VTY_DECLVAR_CONTEXT(interface, ifp); struct irdp_interface *irdp = irdp_if_get(ifp); struct listnode *node, *nnode; struct in_addr ip; int ret; struct Adv *adv; IRDP_CONFIGED; ret = inet_aton(argv[idx_ipv4]->arg, &ip); if (!ret) return CMD_WARNING_CONFIG_FAILED; for (ALL_LIST_ELEMENTS(irdp->AdvPrefList, node, nnode, adv)) { if (adv->ip.s_addr == ip.s_addr) { listnode_delete(irdp->AdvPrefList, adv); break; } } return CMD_SUCCESS; } DEFUN (ip_irdp_debug_messages, ip_irdp_debug_messages_cmd, "ip irdp debug messages", IP_STR "ICMP Router discovery debug Averts. and Solicits (short)\n" "IRDP debugging options\n" "Enable debugging for IRDP messages\n") { VTY_DECLVAR_CONTEXT(interface, ifp); struct irdp_interface *irdp = irdp_if_get(ifp); IRDP_CONFIGED; irdp->flags |= IF_DEBUG_MESSAGES; return CMD_SUCCESS; } DEFUN (ip_irdp_debug_misc, ip_irdp_debug_misc_cmd, "ip irdp debug misc", IP_STR "ICMP Router discovery debug Averts. and Solicits (short)\n" "IRDP debugging options\n" "Enable debugging for miscellaneous IRDP events\n") { VTY_DECLVAR_CONTEXT(interface, ifp); struct irdp_interface *irdp = irdp_if_get(ifp); IRDP_CONFIGED; irdp->flags |= IF_DEBUG_MISC; return CMD_SUCCESS; } DEFUN (ip_irdp_debug_packet, ip_irdp_debug_packet_cmd, "ip irdp debug packet", IP_STR "ICMP Router discovery debug Averts. and Solicits (short)\n" "IRDP debugging options\n" "Enable debugging for IRDP packets\n") { VTY_DECLVAR_CONTEXT(interface, ifp); struct irdp_interface *irdp = irdp_if_get(ifp); IRDP_CONFIGED; irdp->flags |= IF_DEBUG_PACKET; return CMD_SUCCESS; } DEFUN (ip_irdp_debug_disable, ip_irdp_debug_disable_cmd, "ip irdp debug disable", IP_STR "ICMP Router discovery debug Averts. and Solicits (short)\n" "IRDP debugging options\n" "Disable debugging for all IRDP events\n") { VTY_DECLVAR_CONTEXT(interface, ifp); struct irdp_interface *irdp = irdp_if_get(ifp); IRDP_CONFIGED; irdp->flags &= ~IF_DEBUG_PACKET; irdp->flags &= ~IF_DEBUG_MESSAGES; irdp->flags &= ~IF_DEBUG_MISC; return CMD_SUCCESS; } void irdp_if_init(void) { hook_register(zebra_if_config_wr, irdp_config_write); hook_register(if_del, irdp_if_delete); install_element(INTERFACE_NODE, &ip_irdp_broadcast_cmd); install_element(INTERFACE_NODE, &ip_irdp_multicast_cmd); install_element(INTERFACE_NODE, &no_ip_irdp_cmd); install_element(INTERFACE_NODE, &ip_irdp_shutdown_cmd); install_element(INTERFACE_NODE, &no_ip_irdp_shutdown_cmd); install_element(INTERFACE_NODE, &ip_irdp_holdtime_cmd); install_element(INTERFACE_NODE, &ip_irdp_maxadvertinterval_cmd); install_element(INTERFACE_NODE, &ip_irdp_minadvertinterval_cmd); install_element(INTERFACE_NODE, &ip_irdp_preference_cmd); install_element(INTERFACE_NODE, &ip_irdp_address_preference_cmd); install_element(INTERFACE_NODE, &no_ip_irdp_address_preference_cmd); install_element(INTERFACE_NODE, &ip_irdp_debug_messages_cmd); install_element(INTERFACE_NODE, &ip_irdp_debug_misc_cmd); install_element(INTERFACE_NODE, &ip_irdp_debug_packet_cmd); install_element(INTERFACE_NODE, &ip_irdp_debug_disable_cmd); } frr-7.2.1/zebra/irdp_main.c0000644000000000000000000001747213610377563012440 00000000000000/* * * Copyright (C) 2000 Robert Olsson. * Swedish University of Agricultural Sciences * * This file is part of GNU Zebra. * * GNU Zebra is free software; you can redistribute it and/or modify it * under the terms of the GNU General Public License as published by the * Free Software Foundation; either version 2, or (at your option) any * later version. * * GNU Zebra 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; see the file COPYING; if not, write to the Free Software * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA */ /* * This work includes work with the following copywrite: * * Copyright (C) 1997, 2000 Kunihiro Ishiguro * */ /* * Thanks to Jens Laas at Swedish University of Agricultural Sciences * for reviewing and tests. */ #include #include "if.h" #include "vty.h" #include "sockunion.h" #include "sockopt.h" #include "prefix.h" #include "command.h" #include "memory.h" #include "zebra_memory.h" #include "stream.h" #include "ioctl.h" #include "connected.h" #include "log.h" #include "zclient.h" #include "thread.h" #include "privs.h" #include "libfrr.h" #include "lib_errors.h" #include "version.h" #include "zebra/interface.h" #include "zebra/rtadv.h" #include "zebra/rib.h" #include "zebra/zebra_router.h" #include "zebra/redistribute.h" #include "zebra/irdp.h" #include "zebra/zebra_errors.h" #include #include "checksum.h" #include "if.h" #include "sockunion.h" #include "log.h" /* GLOBAL VARS */ extern struct zebra_privs_t zserv_privs; struct thread *t_irdp_raw; /* Timer interval of irdp. */ int irdp_timer_interval = IRDP_DEFAULT_INTERVAL; int irdp_sock_init(void) { int ret, i; int save_errno; int sock; frr_with_privs(&zserv_privs) { sock = socket(AF_INET, SOCK_RAW, IPPROTO_ICMP); save_errno = errno; } if (sock < 0) { flog_err_sys(EC_LIB_SOCKET, "IRDP: can't create irdp socket %s", safe_strerror(save_errno)); return sock; }; i = 1; ret = setsockopt(sock, IPPROTO_IP, IP_TTL, (void *)&i, sizeof(i)); if (ret < 0) { flog_err_sys(EC_LIB_SOCKET, "IRDP: can't do irdp sockopt %s", safe_strerror(errno)); close(sock); return ret; }; ret = setsockopt_ifindex(AF_INET, sock, 1); if (ret < 0) { flog_err_sys(EC_LIB_SOCKET, "IRDP: can't do irdp sockopt %s", safe_strerror(errno)); close(sock); return ret; }; t_irdp_raw = NULL; thread_add_read(zrouter.master, irdp_read_raw, NULL, sock, &t_irdp_raw); return sock; } static int get_pref(struct irdp_interface *irdp, struct prefix *p) { struct listnode *node; struct Adv *adv; /* Use default preference or use the override pref */ if (irdp->AdvPrefList == NULL) return irdp->Preference; for (ALL_LIST_ELEMENTS_RO(irdp->AdvPrefList, node, adv)) if (p->u.prefix4.s_addr == adv->ip.s_addr) return adv->pref; return irdp->Preference; } /* Make ICMP Router Advertisement Message. */ static int make_advertisement_packet(struct interface *ifp, struct prefix *p, struct stream *s) { struct zebra_if *zi = ifp->info; struct irdp_interface *irdp = zi->irdp; int size; int pref; uint16_t checksum; pref = get_pref(irdp, p); stream_putc(s, ICMP_ROUTERADVERT); /* Type. */ stream_putc(s, 0); /* Code. */ stream_putw(s, 0); /* Checksum. */ stream_putc(s, 1); /* Num address. */ stream_putc(s, 2); /* Address Entry Size. */ if (irdp->flags & IF_SHUTDOWN) stream_putw(s, 0); else stream_putw(s, irdp->Lifetime); stream_putl(s, htonl(p->u.prefix4.s_addr)); /* Router address. */ stream_putl(s, pref); /* in_cksum return network byte order value */ size = 16; checksum = in_cksum(s->data, size); stream_putw_at(s, 2, htons(checksum)); return size; } static void irdp_send(struct interface *ifp, struct prefix *p, struct stream *s) { struct zebra_if *zi = ifp->info; struct irdp_interface *irdp = zi->irdp; char buf[PREFIX_STRLEN]; uint32_t dst; uint32_t ttl = 1; if (!irdp) return; if (!(ifp->flags & IFF_UP)) return; if (irdp->flags & IF_BROADCAST) dst = INADDR_BROADCAST; else dst = htonl(INADDR_ALLHOSTS_GROUP); if (irdp->flags & IF_DEBUG_MESSAGES) zlog_debug("IRDP: TX Advert on %s %s Holdtime=%d Preference=%d", ifp->name, prefix2str(p, buf, sizeof buf), irdp->flags & IF_SHUTDOWN ? 0 : irdp->Lifetime, get_pref(irdp, p)); send_packet(ifp, s, dst, p, ttl); } static void irdp_advertisement(struct interface *ifp, struct prefix *p) { struct stream *s; s = stream_new(128); make_advertisement_packet(ifp, p, s); irdp_send(ifp, p, s); stream_free(s); } int irdp_send_thread(struct thread *t_advert) { uint32_t timer, tmp; struct interface *ifp = THREAD_ARG(t_advert); struct zebra_if *zi = ifp->info; struct irdp_interface *irdp = zi->irdp; struct prefix *p; struct listnode *node, *nnode; struct connected *ifc; if (!irdp) return 0; irdp->flags &= ~IF_SOLICIT; if (ifp->connected) for (ALL_LIST_ELEMENTS(ifp->connected, node, nnode, ifc)) { p = ifc->address; if (p->family != AF_INET) continue; irdp_advertisement(ifp, p); irdp->irdp_sent++; } tmp = irdp->MaxAdvertInterval - irdp->MinAdvertInterval; timer = random() % (tmp + 1); timer = irdp->MinAdvertInterval + timer; if (irdp->irdp_sent < MAX_INITIAL_ADVERTISEMENTS && timer > MAX_INITIAL_ADVERT_INTERVAL) timer = MAX_INITIAL_ADVERT_INTERVAL; if (irdp->flags & IF_DEBUG_MISC) zlog_debug("IRDP: New timer for %s set to %u", ifp->name, timer); irdp->t_advertise = NULL; thread_add_timer(zrouter.master, irdp_send_thread, ifp, timer, &irdp->t_advertise); return 0; } void irdp_advert_off(struct interface *ifp) { struct zebra_if *zi = ifp->info; struct irdp_interface *irdp = zi->irdp; struct listnode *node, *nnode; int i; struct connected *ifc; struct prefix *p; if (!irdp) return; if (irdp->t_advertise) thread_cancel(irdp->t_advertise); irdp->t_advertise = NULL; if (ifp->connected) for (ALL_LIST_ELEMENTS(ifp->connected, node, nnode, ifc)) { p = ifc->address; /* Output some packets with Lifetime 0 we should add a wait... */ for (i = 0; i < IRDP_LAST_ADVERT_MESSAGES; i++) { irdp->irdp_sent++; irdp_advertisement(ifp, p); } } } void process_solicit(struct interface *ifp) { struct zebra_if *zi = ifp->info; struct irdp_interface *irdp = zi->irdp; uint32_t timer; if (!irdp) return; /* When SOLICIT is active we reject further incoming solicits this keeps down the answering rate so we don't have think about DoS attacks here. */ if (irdp->flags & IF_SOLICIT) return; irdp->flags |= IF_SOLICIT; if (irdp->t_advertise) thread_cancel(irdp->t_advertise); irdp->t_advertise = NULL; timer = (random() % MAX_RESPONSE_DELAY) + 1; irdp->t_advertise = NULL; thread_add_timer(zrouter.master, irdp_send_thread, ifp, timer, &irdp->t_advertise); } static int irdp_finish(void) { struct vrf *vrf; struct interface *ifp; struct zebra_if *zi; struct irdp_interface *irdp; zlog_info("IRDP: Received shutdown notification."); RB_FOREACH (vrf, vrf_id_head, &vrfs_by_id) FOR_ALL_INTERFACES (vrf, ifp) { zi = ifp->info; if (!zi) continue; irdp = zi->irdp; if (!irdp) continue; if (irdp->flags & IF_ACTIVE) { irdp->flags |= IF_SHUTDOWN; irdp_advert_off(ifp); } } return 0; } static int irdp_init(struct thread_master *master) { irdp_if_init(); hook_register(frr_early_fini, irdp_finish); return 0; } static int irdp_module_init(void) { hook_register(frr_late_init, irdp_init); return 0; } FRR_MODULE_SETUP(.name = "zebra_irdp", .version = FRR_VERSION, .description = "zebra IRDP module", .init = irdp_module_init, ) frr-7.2.1/zebra/irdp_packet.c0000644000000000000000000002054113610377563012752 00000000000000/* * * Copyright (C) 2000 Robert Olsson. * Swedish University of Agricultural Sciences * * This file is part of GNU Zebra. * * GNU Zebra is free software; you can redistribute it and/or modify it * under the terms of the GNU General Public License as published by the * Free Software Foundation; either version 2, or (at your option) any * later version. * * GNU Zebra 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; see the file COPYING; if not, write to the Free Software * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA */ /* * This work includes work with the following copywrite: * * Copyright (C) 1997, 2000 Kunihiro Ishiguro * */ /* * Thanks to Jens Laas at Swedish University of Agricultural Sciences * for reviewing and tests. */ #include #include #include "checksum.h" #include "command.h" #include "connected.h" #include "if.h" #include "ioctl.h" #include "log.h" #include "log.h" #include "memory.h" #include "prefix.h" #include "sockopt.h" #include "sockunion.h" #include "sockunion.h" #include "stream.h" #include "thread.h" #include "vty.h" #include "zclient.h" #include "lib_errors.h" #include "zebra_memory.h" #include "zebra/interface.h" #include "zebra/rtadv.h" #include "zebra/rib.h" #include "zebra/zebra_router.h" #include "zebra/redistribute.h" #include "zebra/irdp.h" #include "zebra/zebra_errors.h" /* GLOBAL VARS */ int irdp_sock = -1; extern struct thread *t_irdp_raw; static void parse_irdp_packet(char *p, int len, struct interface *ifp) { struct ip *ip = (struct ip *)p; struct icmphdr *icmp; struct in_addr src; int ip_hlen, iplen, datalen; struct zebra_if *zi; struct irdp_interface *irdp; zi = ifp->info; if (!zi) return; irdp = zi->irdp; if (!irdp) return; ip_hlen = ip->ip_hl << 2; sockopt_iphdrincl_swab_systoh(ip); iplen = ip->ip_len; datalen = len - ip_hlen; src = ip->ip_src; if (len != iplen) { flog_err(EC_ZEBRA_IRDP_LEN_MISMATCH, "IRDP: RX length doesn't match IP length"); return; } if (iplen < ICMP_MINLEN) { flog_err(EC_ZEBRA_IRDP_LEN_MISMATCH, "IRDP: RX ICMP packet too short from %s\n", inet_ntoa(src)); return; } /* XXX: RAW doesn't receive link-layer, surely? ??? */ /* Check so we don't checksum packets longer than oure RX_BUF - (ethlen + len of IP-header) 14+20 */ if (iplen > IRDP_RX_BUF - 34) { flog_err(EC_ZEBRA_IRDP_LEN_MISMATCH, "IRDP: RX ICMP packet too long from %s\n", inet_ntoa(src)); return; } icmp = (struct icmphdr *)(p + ip_hlen); /* check icmp checksum */ if (in_cksum(icmp, datalen) != icmp->checksum) { flog_warn( EC_ZEBRA_IRDP_BAD_CHECKSUM, "IRDP: RX ICMP packet from %s. Bad checksum, silently ignored", inet_ntoa(src)); return; } /* Handle just only IRDP */ if (!(icmp->type == ICMP_ROUTERADVERT || icmp->type == ICMP_ROUTERSOLICIT)) return; if (icmp->code != 0) { flog_warn( EC_ZEBRA_IRDP_BAD_TYPE_CODE, "IRDP: RX packet type %d from %s. Bad ICMP type code, silently ignored", icmp->type, inet_ntoa(src)); return; } if (!((ntohl(ip->ip_dst.s_addr) == INADDR_BROADCAST) && (irdp->flags & IF_BROADCAST)) || (ntohl(ip->ip_dst.s_addr) == INADDR_ALLRTRS_GROUP && !(irdp->flags & IF_BROADCAST))) { flog_warn( EC_ZEBRA_IRDP_BAD_RX_FLAGS, "IRDP: RX illegal from %s to %s while %s operates in %s; Please correct settings\n", inet_ntoa(src), ntohl(ip->ip_dst.s_addr) == INADDR_ALLRTRS_GROUP ? "multicast" : inet_ntoa(ip->ip_dst), ifp->name, irdp->flags & IF_BROADCAST ? "broadcast" : "multicast"); return; } switch (icmp->type) { case ICMP_ROUTERADVERT: break; case ICMP_ROUTERSOLICIT: if (irdp->flags & IF_DEBUG_MESSAGES) zlog_debug("IRDP: RX Solicit on %s from %s", ifp->name, inet_ntoa(src)); process_solicit(ifp); break; default: flog_warn( EC_ZEBRA_IRDP_BAD_TYPE_CODE, "IRDP: RX packet type %d from %s. Bad ICMP type code, silently ignored", icmp->type, inet_ntoa(src)); } } static int irdp_recvmsg(int sock, uint8_t *buf, int size, int *ifindex) { struct msghdr msg; struct iovec iov; char adata[CMSG_SPACE(SOPT_SIZE_CMSG_PKTINFO_IPV4())]; int ret; memset(&msg, 0, sizeof(msg)); msg.msg_name = (void *)0; msg.msg_namelen = 0; msg.msg_iov = &iov; msg.msg_iovlen = 1; msg.msg_control = (void *)adata; msg.msg_controllen = sizeof adata; iov.iov_base = buf; iov.iov_len = size; ret = recvmsg(sock, &msg, 0); if (ret < 0) { flog_warn(EC_LIB_SOCKET, "IRDP: recvmsg: read error %s", safe_strerror(errno)); return ret; } if (msg.msg_flags & MSG_TRUNC) { flog_warn(EC_LIB_SOCKET, "IRDP: recvmsg: truncated message"); return ret; } if (msg.msg_flags & MSG_CTRUNC) { flog_warn(EC_LIB_SOCKET, "IRDP: recvmsg: truncated control message"); return ret; } *ifindex = getsockopt_ifindex(AF_INET, &msg); return ret; } int irdp_read_raw(struct thread *r) { struct interface *ifp; struct zebra_if *zi; struct irdp_interface *irdp; char buf[IRDP_RX_BUF]; int ret, ifindex = 0; int irdp_sock = THREAD_FD(r); t_irdp_raw = NULL; thread_add_read(zrouter.master, irdp_read_raw, NULL, irdp_sock, &t_irdp_raw); ret = irdp_recvmsg(irdp_sock, (uint8_t *)buf, IRDP_RX_BUF, &ifindex); if (ret < 0) flog_warn(EC_LIB_SOCKET, "IRDP: RX Error length = %d", ret); ifp = if_lookup_by_index(ifindex, VRF_DEFAULT); if (!ifp) return ret; zi = ifp->info; if (!zi) return ret; irdp = zi->irdp; if (!irdp) return ret; if (!(irdp->flags & IF_ACTIVE)) { if (irdp->flags & IF_DEBUG_MISC) zlog_debug("IRDP: RX ICMP for disabled interface %s", ifp->name); return 0; } if (irdp->flags & IF_DEBUG_PACKET) { int i; zlog_debug("IRDP: RX (idx %d) ", ifindex); for (i = 0; i < ret; i++) zlog_debug("IRDP: RX %x ", buf[i] & 0xFF); } parse_irdp_packet(buf, ret, ifp); return ret; } void send_packet(struct interface *ifp, struct stream *s, uint32_t dst, struct prefix *p, uint32_t ttl) { static struct sockaddr_in sockdst = {AF_INET}; struct ip *ip; struct icmphdr *icmp; struct msghdr *msg; struct cmsghdr *cmsg; struct iovec iovector; char msgbuf[256]; char buf[256]; struct in_pktinfo *pktinfo; unsigned long src; uint8_t on; if (!(ifp->flags & IFF_UP)) return; if (p) src = ntohl(p->u.prefix4.s_addr); else src = 0; /* Is filled in */ ip = (struct ip *)buf; ip->ip_hl = sizeof(struct ip) >> 2; ip->ip_v = IPVERSION; ip->ip_tos = 0xC0; ip->ip_off = 0L; ip->ip_p = 1; /* IP_ICMP */ ip->ip_ttl = ttl; ip->ip_src.s_addr = src; ip->ip_dst.s_addr = dst; icmp = (struct icmphdr *)(buf + sizeof(struct ip)); /* Merge IP header with icmp packet */ assert(stream_get_endp(s) < (sizeof(buf) - sizeof(struct ip))); stream_get(icmp, s, stream_get_endp(s)); /* icmp->checksum is already calculated */ ip->ip_len = sizeof(struct ip) + stream_get_endp(s); on = 1; if (setsockopt(irdp_sock, IPPROTO_IP, IP_HDRINCL, (char *)&on, sizeof(on)) < 0) zlog_debug("sendto %s", safe_strerror(errno)); if (dst == INADDR_BROADCAST) { on = 1; if (setsockopt(irdp_sock, SOL_SOCKET, SO_BROADCAST, (char *)&on, sizeof(on)) < 0) zlog_debug("sendto %s", safe_strerror(errno)); } if (dst != INADDR_BROADCAST) setsockopt_ipv4_multicast_loop(irdp_sock, 0); memset(&sockdst, 0, sizeof(sockdst)); sockdst.sin_family = AF_INET; sockdst.sin_addr.s_addr = dst; cmsg = (struct cmsghdr *)(msgbuf + sizeof(struct msghdr)); cmsg->cmsg_len = sizeof(struct cmsghdr) + sizeof(struct in_pktinfo); cmsg->cmsg_level = SOL_IP; cmsg->cmsg_type = IP_PKTINFO; pktinfo = (struct in_pktinfo *)CMSG_DATA(cmsg); pktinfo->ipi_ifindex = ifp->ifindex; pktinfo->ipi_spec_dst.s_addr = src; pktinfo->ipi_addr.s_addr = src; iovector.iov_base = (void *)buf; iovector.iov_len = ip->ip_len; msg = (struct msghdr *)msgbuf; msg->msg_name = &sockdst; msg->msg_namelen = sizeof(sockdst); msg->msg_iov = &iovector; msg->msg_iovlen = 1; msg->msg_control = cmsg; msg->msg_controllen = cmsg->cmsg_len; sockopt_iphdrincl_swab_htosys(ip); if (sendmsg(irdp_sock, msg, 0) < 0) { zlog_debug("sendto %s", safe_strerror(errno)); } /* printf("TX on %s idx %d\n", ifp->name, ifp->ifindex); */ } frr-7.2.1/zebra/kernel_netlink.c0000644000000000000000000007743613610377563013510 00000000000000/* Kernel communication using netlink interface. * Copyright (C) 1999 Kunihiro Ishiguro * * This file is part of GNU Zebra. * * GNU Zebra is free software; you can redistribute it and/or modify it * under the terms of the GNU General Public License as published by the * Free Software Foundation; either version 2, or (at your option) any * later version. * * GNU Zebra 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; see the file COPYING; if not, write to the Free Software * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA */ #include #if defined(HANDLE_NETLINK_FUZZING) #include #include #include "libfrr.h" #endif /* HANDLE_NETLINK_FUZZING */ #ifdef HAVE_NETLINK #include "linklist.h" #include "if.h" #include "log.h" #include "prefix.h" #include "connected.h" #include "table.h" #include "memory.h" #include "zebra_memory.h" #include "rib.h" #include "thread.h" #include "privs.h" #include "nexthop.h" #include "vrf.h" #include "mpls.h" #include "lib_errors.h" //#include "zebra/zserv.h" #include "zebra/zebra_router.h" #include "zebra/zebra_ns.h" #include "zebra/zebra_vrf.h" #include "zebra/rt.h" #include "zebra/debug.h" #include "zebra/kernel_netlink.h" #include "zebra/rt_netlink.h" #include "zebra/if_netlink.h" #include "zebra/rule_netlink.h" #include "zebra/zebra_errors.h" #ifndef SO_RCVBUFFORCE #define SO_RCVBUFFORCE (33) #endif /* Hack for GNU libc version 2. */ #ifndef MSG_TRUNC #define MSG_TRUNC 0x20 #endif /* MSG_TRUNC */ #ifndef NLMSG_TAIL #define NLMSG_TAIL(nmsg) \ ((struct rtattr *)(((uint8_t *)(nmsg)) \ + NLMSG_ALIGN((nmsg)->nlmsg_len))) #endif #ifndef RTA_TAIL #define RTA_TAIL(rta) \ ((struct rtattr *)(((uint8_t *)(rta)) + RTA_ALIGN((rta)->rta_len))) #endif #ifndef RTNL_FAMILY_IP6MR #define RTNL_FAMILY_IP6MR 129 #endif #ifndef RTPROT_MROUTED #define RTPROT_MROUTED 17 #endif static const struct message nlmsg_str[] = {{RTM_NEWROUTE, "RTM_NEWROUTE"}, {RTM_DELROUTE, "RTM_DELROUTE"}, {RTM_GETROUTE, "RTM_GETROUTE"}, {RTM_NEWLINK, "RTM_NEWLINK"}, {RTM_DELLINK, "RTM_DELLINK"}, {RTM_GETLINK, "RTM_GETLINK"}, {RTM_NEWADDR, "RTM_NEWADDR"}, {RTM_DELADDR, "RTM_DELADDR"}, {RTM_GETADDR, "RTM_GETADDR"}, {RTM_NEWNEIGH, "RTM_NEWNEIGH"}, {RTM_DELNEIGH, "RTM_DELNEIGH"}, {RTM_GETNEIGH, "RTM_GETNEIGH"}, {RTM_NEWRULE, "RTM_NEWRULE"}, {RTM_DELRULE, "RTM_DELRULE"}, {RTM_GETRULE, "RTM_GETRULE"}, {0}}; static const struct message rtproto_str[] = { {RTPROT_REDIRECT, "redirect"}, {RTPROT_KERNEL, "kernel"}, {RTPROT_BOOT, "boot"}, {RTPROT_STATIC, "static"}, {RTPROT_GATED, "GateD"}, {RTPROT_RA, "router advertisement"}, {RTPROT_MRT, "MRT"}, {RTPROT_ZEBRA, "Zebra"}, #ifdef RTPROT_BIRD {RTPROT_BIRD, "BIRD"}, #endif /* RTPROT_BIRD */ {RTPROT_MROUTED, "mroute"}, {RTPROT_BGP, "BGP"}, {RTPROT_OSPF, "OSPF"}, {RTPROT_ISIS, "IS-IS"}, {RTPROT_RIP, "RIP"}, {RTPROT_RIPNG, "RIPNG"}, {RTPROT_ZSTATIC, "static"}, {0}}; static const struct message family_str[] = {{AF_INET, "ipv4"}, {AF_INET6, "ipv6"}, {AF_BRIDGE, "bridge"}, {RTNL_FAMILY_IPMR, "ipv4MR"}, {RTNL_FAMILY_IP6MR, "ipv6MR"}, {0}}; static const struct message rttype_str[] = {{RTN_UNSPEC, "none"}, {RTN_UNICAST, "unicast"}, {RTN_LOCAL, "local"}, {RTN_BROADCAST, "broadcast"}, {RTN_ANYCAST, "anycast"}, {RTN_MULTICAST, "multicast"}, {RTN_BLACKHOLE, "blackhole"}, {RTN_UNREACHABLE, "unreachable"}, {RTN_PROHIBIT, "prohibited"}, {RTN_THROW, "throw"}, {RTN_NAT, "nat"}, {RTN_XRESOLVE, "resolver"}, {0}}; extern struct thread_master *master; extern uint32_t nl_rcvbufsize; extern struct zebra_privs_t zserv_privs; int netlink_talk_filter(struct nlmsghdr *h, ns_id_t ns_id, int startup) { /* * This is an error condition that must be handled during * development. * * The netlink_talk_filter function is used for communication * down the netlink_cmd pipe and we are expecting * an ack being received. So if we get here * then we did not receive the ack and instead * received some other message in an unexpected * way. */ zlog_debug("%s: ignoring message type 0x%04x(%s) NS %u", __func__, h->nlmsg_type, nl_msg_type_to_str(h->nlmsg_type), ns_id); return 0; } static int netlink_recvbuf(struct nlsock *nl, uint32_t newsize) { uint32_t oldsize; socklen_t newlen = sizeof(newsize); socklen_t oldlen = sizeof(oldsize); int ret; ret = getsockopt(nl->sock, SOL_SOCKET, SO_RCVBUF, &oldsize, &oldlen); if (ret < 0) { flog_err_sys(EC_LIB_SOCKET, "Can't get %s receive buffer size: %s", nl->name, safe_strerror(errno)); return -1; } /* Try force option (linux >= 2.6.14) and fall back to normal set */ frr_with_privs(&zserv_privs) { ret = setsockopt(nl->sock, SOL_SOCKET, SO_RCVBUFFORCE, &nl_rcvbufsize, sizeof(nl_rcvbufsize)); } if (ret < 0) ret = setsockopt(nl->sock, SOL_SOCKET, SO_RCVBUF, &nl_rcvbufsize, sizeof(nl_rcvbufsize)); if (ret < 0) { flog_err_sys(EC_LIB_SOCKET, "Can't set %s receive buffer size: %s", nl->name, safe_strerror(errno)); return -1; } ret = getsockopt(nl->sock, SOL_SOCKET, SO_RCVBUF, &newsize, &newlen); if (ret < 0) { flog_err_sys(EC_LIB_SOCKET, "Can't get %s receive buffer size: %s", nl->name, safe_strerror(errno)); return -1; } zlog_info("Setting netlink socket receive buffer size: %u -> %u", oldsize, newsize); return 0; } /* Make socket for Linux netlink interface. */ static int netlink_socket(struct nlsock *nl, unsigned long groups, ns_id_t ns_id) { int ret; struct sockaddr_nl snl; int sock; int namelen; frr_with_privs(&zserv_privs) { sock = ns_socket(AF_NETLINK, SOCK_RAW, NETLINK_ROUTE, ns_id); if (sock < 0) { zlog_err("Can't open %s socket: %s", nl->name, safe_strerror(errno)); return -1; } memset(&snl, 0, sizeof snl); snl.nl_family = AF_NETLINK; snl.nl_groups = groups; /* Bind the socket to the netlink structure for anything. */ ret = bind(sock, (struct sockaddr *)&snl, sizeof snl); } if (ret < 0) { zlog_err("Can't bind %s socket to group 0x%x: %s", nl->name, snl.nl_groups, safe_strerror(errno)); close(sock); return -1; } /* multiple netlink sockets will have different nl_pid */ namelen = sizeof snl; ret = getsockname(sock, (struct sockaddr *)&snl, (socklen_t *)&namelen); if (ret < 0 || namelen != sizeof snl) { flog_err_sys(EC_LIB_SOCKET, "Can't get %s socket name: %s", nl->name, safe_strerror(errno)); close(sock); return -1; } nl->snl = snl; nl->sock = sock; return ret; } static int netlink_information_fetch(struct nlmsghdr *h, ns_id_t ns_id, int startup) { /* * When we handle new message types here * because we are starting to install them * then lets check the netlink_install_filter * and see if we should add the corresponding * allow through entry there. * Probably not needed to do but please * think about it. */ switch (h->nlmsg_type) { case RTM_NEWROUTE: return netlink_route_change(h, ns_id, startup); case RTM_DELROUTE: return netlink_route_change(h, ns_id, startup); case RTM_NEWLINK: return netlink_link_change(h, ns_id, startup); case RTM_DELLINK: return netlink_link_change(h, ns_id, startup); case RTM_NEWADDR: return netlink_interface_addr(h, ns_id, startup); case RTM_DELADDR: return netlink_interface_addr(h, ns_id, startup); case RTM_NEWNEIGH: return netlink_neigh_change(h, ns_id); case RTM_DELNEIGH: return netlink_neigh_change(h, ns_id); case RTM_NEWRULE: return netlink_rule_change(h, ns_id, startup); case RTM_DELRULE: return netlink_rule_change(h, ns_id, startup); default: /* * If we have received this message then * we have made a mistake during development * and we need to write some code to handle * this message type or not ask for * it to be sent up to us */ flog_err(EC_ZEBRA_UNKNOWN_NLMSG, "Unknown netlink nlmsg_type %s(%d) vrf %u\n", nl_msg_type_to_str(h->nlmsg_type), h->nlmsg_type, ns_id); break; } return 0; } #if defined(HANDLE_NETLINK_FUZZING) /* Using globals here to avoid adding function parameters */ /* Keep distinct filenames for netlink fuzzy collection */ static unsigned int netlink_file_counter = 1; /* File name to read fuzzed netlink from */ static char netlink_fuzz_file[MAXPATHLEN] = ""; /* Flag for whether to read from file or not */ bool netlink_read; /** * netlink_read_init() - Starts the message parser * @fname: Filename to read. */ void netlink_read_init(const char *fname) { struct zebra_dplane_info dp_info; snprintf(netlink_fuzz_file, MAXPATHLEN, "%s", fname); /* Creating this fake socket for testing purposes */ struct zebra_ns *zns = zebra_ns_lookup(NS_DEFAULT); /* Capture key info from zns struct */ zebra_dplane_info_from_zns(&dp_info, zns, false); netlink_parse_info(netlink_information_fetch, &zns->netlink, &dp_info, 1, 0); } /** * netlink_write_incoming() - Writes all data received from netlink to a file * @buf: Data from netlink. * @size: Size of data. * @counter: Counter for keeping filenames distinct. */ static void netlink_write_incoming(const char *buf, const unsigned int size, unsigned int counter) { char fname[MAXPATHLEN]; FILE *f; snprintf(fname, MAXPATHLEN, "%s/%s_%u", frr_vtydir, "netlink", counter); frr_with_privs(&zserv_privs) { f = fopen(fname, "w"); } if (f) { fwrite(buf, 1, size, f); fclose(f); } } /** * netlink_read_file() - Reads netlink data from file * @buf: Netlink buffer being overwritten. * @fname: File name to read from. * * Return: Size of file. */ static long netlink_read_file(char *buf, const char *fname) { FILE *f; long file_bytes = -1; frr_with_privs(&zserv_privs) { f = fopen(fname, "r"); } if (f) { fseek(f, 0, SEEK_END); file_bytes = ftell(f); rewind(f); fread(buf, NL_RCV_PKT_BUF_SIZE, 1, f); fclose(f); } return file_bytes; } #endif /* HANDLE_NETLINK_FUZZING */ static int kernel_read(struct thread *thread) { struct zebra_ns *zns = (struct zebra_ns *)THREAD_ARG(thread); struct zebra_dplane_info dp_info; /* Capture key info from ns struct */ zebra_dplane_info_from_zns(&dp_info, zns, false); netlink_parse_info(netlink_information_fetch, &zns->netlink, &dp_info, 5, 0); zns->t_netlink = NULL; thread_add_read(zrouter.master, kernel_read, zns, zns->netlink.sock, &zns->t_netlink); return 0; } /* * Filter out messages from self that occur on listener socket, * caused by our actions on the command socket(s) * * When we add new Netlink message types we probably * do not need to add them here as that we are filtering * on the routes we actually care to receive( which is rarer * then the normal course of operations). We are intentionally * allowing some messages from ourselves through * ( I'm looking at you Interface based netlink messages ) * so that we only had to write one way to handle incoming * address add/delete changes. */ static void netlink_install_filter(int sock, __u32 pid, __u32 dplane_pid) { /* * BPF_JUMP instructions and where you jump to are based upon * 0 as being the next statement. So count from 0. Writing * this down because every time I look at this I have to * re-remember it. */ struct sock_filter filter[] = { /* * Logic: * if (nlmsg_pid == pid || * nlmsg_pid == dplane_pid) { * if (the incoming nlmsg_type == * RTM_NEWADDR | RTM_DELADDR) * keep this message * else * skip this message * } else * keep this netlink message */ /* * 0: Load the nlmsg_pid into the BPF register */ BPF_STMT(BPF_LD | BPF_ABS | BPF_W, offsetof(struct nlmsghdr, nlmsg_pid)), /* * 1: Compare to pid */ BPF_JUMP(BPF_JMP | BPF_JEQ | BPF_K, htonl(pid), 1, 0), /* * 2: Compare to dplane pid */ BPF_JUMP(BPF_JMP | BPF_JEQ | BPF_K, htonl(dplane_pid), 0, 4), /* * 3: Load the nlmsg_type into BPF register */ BPF_STMT(BPF_LD | BPF_ABS | BPF_H, offsetof(struct nlmsghdr, nlmsg_type)), /* * 4: Compare to RTM_NEWADDR */ BPF_JUMP(BPF_JMP | BPF_JEQ | BPF_K, htons(RTM_NEWADDR), 2, 0), /* * 5: Compare to RTM_DELADDR */ BPF_JUMP(BPF_JMP | BPF_JEQ | BPF_K, htons(RTM_DELADDR), 1, 0), /* * 6: This is the end state of we want to skip the * message */ BPF_STMT(BPF_RET | BPF_K, 0), /* 7: This is the end state of we want to keep * the message */ BPF_STMT(BPF_RET | BPF_K, 0xffff), }; struct sock_fprog prog = { .len = array_size(filter), .filter = filter, }; if (setsockopt(sock, SOL_SOCKET, SO_ATTACH_FILTER, &prog, sizeof(prog)) < 0) flog_err_sys(EC_LIB_SOCKET, "Can't install socket filter: %s\n", safe_strerror(errno)); } void netlink_parse_rtattr(struct rtattr **tb, int max, struct rtattr *rta, int len) { while (RTA_OK(rta, len)) { if (rta->rta_type <= max) tb[rta->rta_type] = rta; rta = RTA_NEXT(rta, len); } } /** * netlink_parse_rtattr_nested() - Parses a nested route attribute * @tb: Pointer to array for storing rtattr in. * @max: Max number to store. * @rta: Pointer to rtattr to look for nested items in. */ void netlink_parse_rtattr_nested(struct rtattr **tb, int max, struct rtattr *rta) { netlink_parse_rtattr(tb, max, RTA_DATA(rta), RTA_PAYLOAD(rta)); } int addattr_l(struct nlmsghdr *n, unsigned int maxlen, int type, const void *data, unsigned int alen) { int len; struct rtattr *rta; len = RTA_LENGTH(alen); if (NLMSG_ALIGN(n->nlmsg_len) + RTA_ALIGN(len) > maxlen) return -1; rta = (struct rtattr *)(((char *)n) + NLMSG_ALIGN(n->nlmsg_len)); rta->rta_type = type; rta->rta_len = len; if (data) memcpy(RTA_DATA(rta), data, alen); else assert(alen == 0); n->nlmsg_len = NLMSG_ALIGN(n->nlmsg_len) + RTA_ALIGN(len); return 0; } int rta_addattr_l(struct rtattr *rta, unsigned int maxlen, int type, const void *data, unsigned int alen) { unsigned int len; struct rtattr *subrta; len = RTA_LENGTH(alen); if (RTA_ALIGN(rta->rta_len) + RTA_ALIGN(len) > maxlen) return -1; subrta = (struct rtattr *)(((char *)rta) + RTA_ALIGN(rta->rta_len)); subrta->rta_type = type; subrta->rta_len = len; if (data) memcpy(RTA_DATA(subrta), data, alen); else assert(alen == 0); rta->rta_len = NLMSG_ALIGN(rta->rta_len) + RTA_ALIGN(len); return 0; } int addattr16(struct nlmsghdr *n, unsigned int maxlen, int type, uint16_t data) { return addattr_l(n, maxlen, type, &data, sizeof(uint16_t)); } int addattr32(struct nlmsghdr *n, unsigned int maxlen, int type, int data) { return addattr_l(n, maxlen, type, &data, sizeof(uint32_t)); } struct rtattr *addattr_nest(struct nlmsghdr *n, int maxlen, int type) { struct rtattr *nest = NLMSG_TAIL(n); addattr_l(n, maxlen, type, NULL, 0); return nest; } int addattr_nest_end(struct nlmsghdr *n, struct rtattr *nest) { nest->rta_len = (uint8_t *)NLMSG_TAIL(n) - (uint8_t *)nest; return n->nlmsg_len; } struct rtattr *rta_nest(struct rtattr *rta, int maxlen, int type) { struct rtattr *nest = RTA_TAIL(rta); rta_addattr_l(rta, maxlen, type, NULL, 0); return nest; } int rta_nest_end(struct rtattr *rta, struct rtattr *nest) { nest->rta_len = (uint8_t *)RTA_TAIL(rta) - (uint8_t *)nest; return rta->rta_len; } const char *nl_msg_type_to_str(uint16_t msg_type) { return lookup_msg(nlmsg_str, msg_type, ""); } const char *nl_rtproto_to_str(uint8_t rtproto) { return lookup_msg(rtproto_str, rtproto, ""); } const char *nl_family_to_str(uint8_t family) { return lookup_msg(family_str, family, ""); } const char *nl_rttype_to_str(uint8_t rttype) { return lookup_msg(rttype_str, rttype, ""); } #define NLA_OK(nla, len) \ ((len) >= (int)sizeof(struct nlattr) \ && (nla)->nla_len >= sizeof(struct nlattr) \ && (nla)->nla_len <= (len)) #define NLA_NEXT(nla, attrlen) \ ((attrlen) -= NLA_ALIGN((nla)->nla_len), \ (struct nlattr *)(((char *)(nla)) + NLA_ALIGN((nla)->nla_len))) #define NLA_LENGTH(len) (NLA_ALIGN(sizeof(struct nlattr)) + (len)) #define NLA_DATA(nla) ((struct nlattr *)(((char *)(nla)) + NLA_LENGTH(0))) #define ERR_NLA(err, inner_len) \ ((struct nlattr *)(((char *)(err)) \ + NLMSG_ALIGN(sizeof(struct nlmsgerr)) \ + NLMSG_ALIGN((inner_len)))) static void netlink_parse_nlattr(struct nlattr **tb, int max, struct nlattr *nla, int len) { while (NLA_OK(nla, len)) { if (nla->nla_type <= max) tb[nla->nla_type] = nla; nla = NLA_NEXT(nla, len); } } static void netlink_parse_extended_ack(struct nlmsghdr *h) { struct nlattr *tb[NLMSGERR_ATTR_MAX + 1] = {}; const struct nlmsgerr *err = (const struct nlmsgerr *)NLMSG_DATA(h); const struct nlmsghdr *err_nlh = NULL; /* Length not including nlmsghdr */ uint32_t len = 0; /* Inner error netlink message length */ uint32_t inner_len = 0; const char *msg = NULL; uint32_t off = 0; if (!(h->nlmsg_flags & NLM_F_CAPPED)) inner_len = (uint32_t)NLMSG_PAYLOAD(&err->msg, 0); len = (uint32_t)(NLMSG_PAYLOAD(h, sizeof(struct nlmsgerr)) - inner_len); netlink_parse_nlattr(tb, NLMSGERR_ATTR_MAX, ERR_NLA(err, inner_len), len); if (tb[NLMSGERR_ATTR_MSG]) msg = (const char *)NLA_DATA(tb[NLMSGERR_ATTR_MSG]); if (tb[NLMSGERR_ATTR_OFFS]) { off = *(uint32_t *)NLA_DATA(tb[NLMSGERR_ATTR_OFFS]); if (off > h->nlmsg_len) { zlog_err("Invalid offset for NLMSGERR_ATTR_OFFS"); } else if (!(h->nlmsg_flags & NLM_F_CAPPED)) { /* * Header of failed message * we are not doing anything currently with it * but noticing it for later. */ err_nlh = &err->msg; zlog_debug("%s: Received %s extended Ack", __PRETTY_FUNCTION__, nl_msg_type_to_str(err_nlh->nlmsg_type)); } } if (msg && *msg != '\0') { bool is_err = !!err->error; if (is_err) zlog_err("Extended Error: %s", msg); else flog_warn(EC_ZEBRA_NETLINK_EXTENDED_WARNING, "Extended Warning: %s", msg); } } /* * netlink_parse_info * * Receive message from netlink interface and pass those information * to the given function. * * filter -> Function to call to read the results * nl -> netlink socket information * zns -> The zebra namespace data * count -> How many we should read in, 0 means as much as possible * startup -> Are we reading in under startup conditions? passed to * the filter. */ int netlink_parse_info(int (*filter)(struct nlmsghdr *, ns_id_t, int), const struct nlsock *nl, const struct zebra_dplane_info *zns, int count, int startup) { int status; int ret = 0; int error; int read_in = 0; while (1) { char buf[NL_RCV_PKT_BUF_SIZE]; struct iovec iov = {.iov_base = buf, .iov_len = sizeof buf}; struct sockaddr_nl snl; struct msghdr msg = {.msg_name = (void *)&snl, .msg_namelen = sizeof snl, .msg_iov = &iov, .msg_iovlen = 1}; struct nlmsghdr *h; if (count && read_in >= count) return 0; #if defined(HANDLE_NETLINK_FUZZING) /* Check if reading and filename is set */ if (netlink_read && '\0' != netlink_fuzz_file[0]) { zlog_debug("Reading netlink fuzz file"); status = netlink_read_file(buf, netlink_fuzz_file); snl.nl_pid = 0; } else { status = recvmsg(nl->sock, &msg, 0); } #else status = recvmsg(nl->sock, &msg, 0); #endif /* HANDLE_NETLINK_FUZZING */ if (status < 0) { if (errno == EINTR) continue; if (errno == EWOULDBLOCK || errno == EAGAIN) break; flog_err(EC_ZEBRA_RECVMSG_OVERRUN, "%s recvmsg overrun: %s", nl->name, safe_strerror(errno)); /* * In this case we are screwed. * There is no good way to * recover zebra at this point. */ exit(-1); continue; } if (status == 0) { flog_err_sys(EC_LIB_SOCKET, "%s EOF", nl->name); return -1; } if (msg.msg_namelen != sizeof snl) { flog_err(EC_ZEBRA_NETLINK_LENGTH_ERROR, "%s sender address length error: length %d", nl->name, msg.msg_namelen); return -1; } if (IS_ZEBRA_DEBUG_KERNEL_MSGDUMP_RECV) { zlog_debug("%s: << netlink message dump [recv]", __func__); zlog_hexdump(buf, status); } #if defined(HANDLE_NETLINK_FUZZING) if (!netlink_read) { zlog_debug("Writing incoming netlink message"); netlink_write_incoming(buf, status, netlink_file_counter++); } #endif /* HANDLE_NETLINK_FUZZING */ read_in++; for (h = (struct nlmsghdr *)buf; (status >= 0 && NLMSG_OK(h, (unsigned int)status)); h = NLMSG_NEXT(h, status)) { /* Finish of reading. */ if (h->nlmsg_type == NLMSG_DONE) return ret; /* Error handling. */ if (h->nlmsg_type == NLMSG_ERROR) { struct nlmsgerr *err = (struct nlmsgerr *)NLMSG_DATA(h); int errnum = err->error; int msg_type = err->msg.nlmsg_type; if (h->nlmsg_len < NLMSG_LENGTH(sizeof(struct nlmsgerr))) { flog_err(EC_ZEBRA_NETLINK_LENGTH_ERROR, "%s error: message truncated", nl->name); return -1; } /* * Parse the extended information before * we actually handle it. * At this point in time we do not * do anything other than report the * issue. */ if (h->nlmsg_flags & NLM_F_ACK_TLVS) netlink_parse_extended_ack(h); /* If the error field is zero, then this is an * ACK */ if (err->error == 0) { if (IS_ZEBRA_DEBUG_KERNEL) { zlog_debug( "%s: %s ACK: type=%s(%u), seq=%u, pid=%u", __FUNCTION__, nl->name, nl_msg_type_to_str( err->msg.nlmsg_type), err->msg.nlmsg_type, err->msg.nlmsg_seq, err->msg.nlmsg_pid); } /* return if not a multipart message, * otherwise continue */ if (!(h->nlmsg_flags & NLM_F_MULTI)) return 0; continue; } /* Deal with errors that occur because of races * in link handling */ if (zns->is_cmd && ((msg_type == RTM_DELROUTE && (-errnum == ENODEV || -errnum == ESRCH)) || (msg_type == RTM_NEWROUTE && (-errnum == ENETDOWN || -errnum == EEXIST)))) { if (IS_ZEBRA_DEBUG_KERNEL) zlog_debug( "%s: error: %s type=%s(%u), seq=%u, pid=%u", nl->name, safe_strerror(-errnum), nl_msg_type_to_str( msg_type), msg_type, err->msg.nlmsg_seq, err->msg.nlmsg_pid); return 0; } /* We see RTM_DELNEIGH when shutting down an * interface with an IPv4 * link-local. The kernel should have already * deleted the neighbor * so do not log these as an error. */ if (msg_type == RTM_DELNEIGH || (zns->is_cmd && msg_type == RTM_NEWROUTE && (-errnum == ESRCH || -errnum == ENETUNREACH))) { /* This is known to happen in some * situations, don't log * as error. */ if (IS_ZEBRA_DEBUG_KERNEL) zlog_debug( "%s error: %s, type=%s(%u), seq=%u, pid=%u", nl->name, safe_strerror(-errnum), nl_msg_type_to_str( msg_type), msg_type, err->msg.nlmsg_seq, err->msg.nlmsg_pid); } else flog_err( EC_ZEBRA_UNEXPECTED_MESSAGE, "%s error: %s, type=%s(%u), seq=%u, pid=%u", nl->name, safe_strerror(-errnum), nl_msg_type_to_str(msg_type), msg_type, err->msg.nlmsg_seq, err->msg.nlmsg_pid); return -1; } /* OK we got netlink message. */ if (IS_ZEBRA_DEBUG_KERNEL) zlog_debug( "netlink_parse_info: %s type %s(%u), len=%d, seq=%u, pid=%u", nl->name, nl_msg_type_to_str(h->nlmsg_type), h->nlmsg_type, h->nlmsg_len, h->nlmsg_seq, h->nlmsg_pid); /* * Ignore messages that maybe sent from * other actors besides the kernel */ if (snl.nl_pid != 0) { zlog_debug("Ignoring message from pid %u", snl.nl_pid); continue; } error = (*filter)(h, zns->ns_id, startup); if (error < 0) { zlog_debug("%s filter function error", nl->name); ret = error; } } /* After error care. */ if (msg.msg_flags & MSG_TRUNC) { flog_err(EC_ZEBRA_NETLINK_LENGTH_ERROR, "%s error: message truncated", nl->name); continue; } if (status) { flog_err(EC_ZEBRA_NETLINK_LENGTH_ERROR, "%s error: data remnant size %d", nl->name, status); return -1; } } return ret; } /* * netlink_talk_info * * sendmsg() to netlink socket then recvmsg(). * Calls netlink_parse_info to parse returned data * * filter -> The filter to read final results from kernel * nlmsghdr -> The data to send to the kernel * dp_info -> The dataplane and netlink socket information * startup -> Are we reading in under startup conditions * This is passed through eventually to filter. */ int netlink_talk_info(int (*filter)(struct nlmsghdr *, ns_id_t, int startup), struct nlmsghdr *n, const struct zebra_dplane_info *dp_info, int startup) { int status = 0; struct sockaddr_nl snl; struct iovec iov; struct msghdr msg; int save_errno = 0; const struct nlsock *nl; memset(&snl, 0, sizeof snl); memset(&iov, 0, sizeof iov); memset(&msg, 0, sizeof msg); iov.iov_base = n; iov.iov_len = n->nlmsg_len; msg.msg_name = (void *)&snl; msg.msg_namelen = sizeof snl; msg.msg_iov = &iov; msg.msg_iovlen = 1; snl.nl_family = AF_NETLINK; nl = &(dp_info->nls); n->nlmsg_seq = nl->seq; n->nlmsg_pid = nl->snl.nl_pid; if (IS_ZEBRA_DEBUG_KERNEL) zlog_debug( "netlink_talk: %s type %s(%u), len=%d seq=%u flags 0x%x", nl->name, nl_msg_type_to_str(n->nlmsg_type), n->nlmsg_type, n->nlmsg_len, n->nlmsg_seq, n->nlmsg_flags); /* Send message to netlink interface. */ frr_with_privs(&zserv_privs) { status = sendmsg(nl->sock, &msg, 0); save_errno = errno; } if (IS_ZEBRA_DEBUG_KERNEL_MSGDUMP_SEND) { zlog_debug("%s: >> netlink message dump [sent]", __func__); zlog_hexdump(n, n->nlmsg_len); } if (status < 0) { flog_err_sys(EC_LIB_SOCKET, "netlink_talk sendmsg() error: %s", safe_strerror(save_errno)); return -1; } /* * Get reply from netlink socket. * The reply should either be an acknowlegement or an error. */ return netlink_parse_info(filter, nl, dp_info, 0, startup); } /* * Synchronous version of netlink_talk_info. Converts args to suit the * common version, which is suitable for both sync and async use. */ int netlink_talk(int (*filter)(struct nlmsghdr *, ns_id_t, int startup), struct nlmsghdr *n, struct nlsock *nl, struct zebra_ns *zns, int startup) { struct zebra_dplane_info dp_info; /* Increment sequence number before capturing snapshot of ns socket * info. */ nl->seq++; /* Capture info in intermediate info struct */ zebra_dplane_info_from_zns(&dp_info, zns, (nl == &(zns->netlink_cmd))); return netlink_talk_info(filter, n, &dp_info, startup); } /* Issue request message to kernel via netlink socket. GET messages * are issued through this interface. */ int netlink_request(struct nlsock *nl, struct nlmsghdr *n) { int ret; struct sockaddr_nl snl; /* Check netlink socket. */ if (nl->sock < 0) { flog_err_sys(EC_LIB_SOCKET, "%s socket isn't active.", nl->name); return -1; } /* Fill common fields for all requests. */ n->nlmsg_pid = nl->snl.nl_pid; n->nlmsg_seq = ++nl->seq; memset(&snl, 0, sizeof snl); snl.nl_family = AF_NETLINK; /* Raise capabilities and send message, then lower capabilities. */ frr_with_privs(&zserv_privs) { ret = sendto(nl->sock, (void *)n, n->nlmsg_len, 0, (struct sockaddr *)&snl, sizeof snl); } if (ret < 0) { zlog_err("%s sendto failed: %s", nl->name, safe_strerror(errno)); return -1; } return 0; } /* Exported interface function. This function simply calls netlink_socket (). */ void kernel_init(struct zebra_ns *zns) { uint32_t groups; #if defined SOL_NETLINK int one, ret; #endif /* * Initialize netlink sockets * * If RTMGRP_XXX exists use that, but at some point * I think the kernel developers realized that * keeping track of all the different values would * lead to confusion, so we need to convert the * RTNLGRP_XXX to a bit position for ourself */ groups = RTMGRP_LINK | RTMGRP_IPV4_ROUTE | RTMGRP_IPV4_IFADDR | RTMGRP_IPV6_ROUTE | RTMGRP_IPV6_IFADDR | RTMGRP_IPV4_MROUTE | RTMGRP_NEIGH | ((uint32_t) 1 << (RTNLGRP_IPV4_RULE - 1)) | ((uint32_t) 1 << (RTNLGRP_IPV6_RULE - 1)); snprintf(zns->netlink.name, sizeof(zns->netlink.name), "netlink-listen (NS %u)", zns->ns_id); zns->netlink.sock = -1; if (netlink_socket(&zns->netlink, groups, zns->ns_id) < 0) { zlog_err("Failure to create %s socket", zns->netlink.name); exit(-1); } snprintf(zns->netlink_cmd.name, sizeof(zns->netlink_cmd.name), "netlink-cmd (NS %u)", zns->ns_id); zns->netlink_cmd.sock = -1; if (netlink_socket(&zns->netlink_cmd, 0, zns->ns_id) < 0) { zlog_err("Failure to create %s socket", zns->netlink_cmd.name); exit(-1); } snprintf(zns->netlink_dplane.name, sizeof(zns->netlink_dplane.name), "netlink-dp (NS %u)", zns->ns_id); zns->netlink_dplane.sock = -1; if (netlink_socket(&zns->netlink_dplane, 0, zns->ns_id) < 0) { zlog_err("Failure to create %s socket", zns->netlink_dplane.name); exit(-1); } /* * SOL_NETLINK is not available on all platforms yet * apparently. It's in bits/socket.h which I am not * sure that we want to pull into our build system. */ #if defined SOL_NETLINK /* * Let's tell the kernel that we want to receive extended * ACKS over our command socket(s) */ one = 1; ret = setsockopt(zns->netlink_cmd.sock, SOL_NETLINK, NETLINK_EXT_ACK, &one, sizeof(one)); if (ret < 0) zlog_notice("Registration for extended cmd ACK failed : %d %s", errno, safe_strerror(errno)); one = 1; ret = setsockopt(zns->netlink_dplane.sock, SOL_NETLINK, NETLINK_EXT_ACK, &one, sizeof(one)); if (ret < 0) zlog_notice("Registration for extended dp ACK failed : %d %s", errno, safe_strerror(errno)); #endif /* Register kernel socket. */ if (fcntl(zns->netlink.sock, F_SETFL, O_NONBLOCK) < 0) flog_err_sys(EC_LIB_SOCKET, "Can't set %s socket flags: %s", zns->netlink.name, safe_strerror(errno)); if (fcntl(zns->netlink_cmd.sock, F_SETFL, O_NONBLOCK) < 0) zlog_err("Can't set %s socket error: %s(%d)", zns->netlink_cmd.name, safe_strerror(errno), errno); if (fcntl(zns->netlink_dplane.sock, F_SETFL, O_NONBLOCK) < 0) zlog_err("Can't set %s socket error: %s(%d)", zns->netlink_dplane.name, safe_strerror(errno), errno); /* Set receive buffer size if it's set from command line */ if (nl_rcvbufsize) netlink_recvbuf(&zns->netlink, nl_rcvbufsize); netlink_install_filter(zns->netlink.sock, zns->netlink_cmd.snl.nl_pid, zns->netlink_dplane.snl.nl_pid); zns->t_netlink = NULL; thread_add_read(zrouter.master, kernel_read, zns, zns->netlink.sock, &zns->t_netlink); rt_netlink_init(); } void kernel_terminate(struct zebra_ns *zns, bool complete) { THREAD_READ_OFF(zns->t_netlink); if (zns->netlink.sock >= 0) { close(zns->netlink.sock); zns->netlink.sock = -1; } if (zns->netlink_cmd.sock >= 0) { close(zns->netlink_cmd.sock); zns->netlink_cmd.sock = -1; } /* During zebra shutdown, we need to leave the dataplane socket * around until all work is done. */ if (complete) { if (zns->netlink_dplane.sock >= 0) { close(zns->netlink_dplane.sock); zns->netlink_dplane.sock = -1; } } } #endif /* HAVE_NETLINK */ frr-7.2.1/zebra/kernel_netlink.h0000644000000000000000000000611113610377563013473 00000000000000/* Declarations and definitions for kernel interaction over netlink * Copyright (C) 2016 Cumulus Networks, Inc. * * This file is part of GNU Zebra. * * GNU Zebra is free software; you can redistribute it and/or modify it * under the terms of the GNU General Public License as published by the * Free Software Foundation; either version 2, or (at your option) any * later version. * * GNU Zebra 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; see the file COPYING; if not, write to the Free Software * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA */ #ifndef _ZEBRA_KERNEL_NETLINK_H #define _ZEBRA_KERNEL_NETLINK_H #ifdef __cplusplus extern "C" { #endif #ifdef HAVE_NETLINK #define NL_RCV_PKT_BUF_SIZE 32768 #define NL_PKT_BUF_SIZE 8192 extern void netlink_parse_rtattr(struct rtattr **tb, int max, struct rtattr *rta, int len); extern void netlink_parse_rtattr_nested(struct rtattr **tb, int max, struct rtattr *rta); extern int addattr_l(struct nlmsghdr *n, unsigned int maxlen, int type, const void *data, unsigned int alen); extern int rta_addattr_l(struct rtattr *rta, unsigned int maxlen, int type, const void *data, unsigned int alen); extern int addattr16(struct nlmsghdr *n, unsigned int maxlen, int type, uint16_t data); extern int addattr32(struct nlmsghdr *n, unsigned int maxlen, int type, int data); extern struct rtattr *addattr_nest(struct nlmsghdr *n, int maxlen, int type); extern int addattr_nest_end(struct nlmsghdr *n, struct rtattr *nest); extern struct rtattr *rta_nest(struct rtattr *rta, int maxlen, int type); extern int rta_nest_end(struct rtattr *rta, struct rtattr *nest); extern const char *nl_msg_type_to_str(uint16_t msg_type); extern const char *nl_rtproto_to_str(uint8_t rtproto); extern const char *nl_family_to_str(uint8_t family); extern const char *nl_rttype_to_str(uint8_t rttype); #if defined(HANDLE_NETLINK_FUZZING) extern bool netlink_read; extern void netlink_read_init(const char *fname); #endif /* HANDLE_NETLINK_FUZZING */ extern int netlink_parse_info(int (*filter)(struct nlmsghdr *, ns_id_t, int), const struct nlsock *nl, const struct zebra_dplane_info *dp_info, int count, int startup); extern int netlink_talk_filter(struct nlmsghdr *h, ns_id_t ns, int startup); extern int netlink_talk(int (*filter)(struct nlmsghdr *, ns_id_t, int startup), struct nlmsghdr *n, struct nlsock *nl, struct zebra_ns *zns, int startup); /* Version with 'info' struct only */ int netlink_talk_info(int (*filter)(struct nlmsghdr *, ns_id_t, int startup), struct nlmsghdr *n, const struct zebra_dplane_info *dp_info, int startup); extern int netlink_request(struct nlsock *nl, struct nlmsghdr *n); #endif /* HAVE_NETLINK */ #ifdef __cplusplus } #endif #endif /* _ZEBRA_KERNEL_NETLINK_H */ frr-7.2.1/zebra/kernel_socket.c0000644000000000000000000011475713610377563013332 00000000000000/* Kernel communication using routing socket. * Copyright (C) 1999 Kunihiro Ishiguro * * This file is part of GNU Zebra. * * GNU Zebra is free software; you can redistribute it and/or modify it * under the terms of the GNU General Public License as published by the * Free Software Foundation; either version 2, or (at your option) any * later version. * * GNU Zebra 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; see the file COPYING; if not, write to the Free Software * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA */ #include #ifndef HAVE_NETLINK #include #ifdef __OpenBSD__ #include #endif #include "if.h" #include "prefix.h" #include "sockunion.h" #include "connected.h" #include "memory.h" #include "zebra_memory.h" #include "ioctl.h" #include "log.h" #include "table.h" #include "rib.h" #include "privs.h" #include "vrf.h" #include "lib_errors.h" #include "zebra/rt.h" #include "zebra/interface.h" #include "zebra/zebra_router.h" #include "zebra/debug.h" #include "zebra/kernel_socket.h" #include "zebra/rib.h" #include "zebra/zebra_errors.h" #include "zebra/zebra_ptm.h" extern struct zebra_privs_t zserv_privs; /* * Historically, the BSD routing socket has aligned data following a * struct sockaddr to sizeof(long), which was 4 bytes on some * platforms, and 8 bytes on others. NetBSD 6 changed the routing * socket to align to sizeof(uint64_t), which is 8 bytes. OS X * appears to align to sizeof(int), which is 4 bytes. * * Alignment of zero-sized sockaddrs is nonsensical, but historically * BSD defines RT_ROUNDUP(0) to be the alignment interval (rather than * 0). We follow this practice without questioning it, but it is a * bug if quagga calls ROUNDUP with 0. */ #ifdef __APPLE__ #define ROUNDUP_TYPE int #else #define ROUNDUP_TYPE long #endif /* * Because of these varying conventions, the only sane approach is for * the header to define some flavor of ROUNDUP macro. */ /* OS X (Xcode as of 2014-12) is known not to define RT_ROUNDUP */ #if defined(RT_ROUNDUP) #define ROUNDUP(a) RT_ROUNDUP(a) #endif /* defined(RT_ROUNDUP) */ #if defined(SUNOS_5) /* Solaris has struct sockaddr_in[6] definitions at 16 / 32 bytes size, * so the whole concept doesn't really apply. */ #define ROUNDUP(a) (a) #endif /* * If ROUNDUP has not yet been defined in terms of platform-provided * defines, attempt to cope with heuristics. */ #if !defined(ROUNDUP) /* * If you're porting to a platform that changed RT_ROUNDUP but doesn't * have it in its headers, this will break rather obviously and you'll * have to fix it here. */ #define ROUNDUP(a) \ ((a) > 0 ? (1 + (((a)-1) | (sizeof(ROUNDUP_TYPE) - 1))) \ : sizeof(ROUNDUP_TYPE)) #endif /* defined(ROUNDUP) */ #if defined(SA_SIZE) /* SAROUNDUP is the only thing we need, and SA_SIZE provides that */ #define SAROUNDUP(a) SA_SIZE(a) #else /* !SA_SIZE */ /* * Given a pointer (sockaddr or void *), return the number of bytes * taken up by the sockaddr and any padding needed for alignment. */ #if defined(HAVE_STRUCT_SOCKADDR_SA_LEN) #define SAROUNDUP(X) ROUNDUP(((struct sockaddr *)(X))->sa_len) #else /* * One would hope all fixed-size structure definitions are aligned, * but round them up nonetheless. */ #define SAROUNDUP(X) \ (((struct sockaddr *)(X))->sa_family == AF_INET \ ? ROUNDUP(sizeof(struct sockaddr_in)) \ : (((struct sockaddr *)(X))->sa_family == AF_INET6 \ ? ROUNDUP(sizeof(struct sockaddr_in6)) \ : (((struct sockaddr *)(X))->sa_family == AF_LINK \ ? ROUNDUP(sizeof(struct sockaddr_dl)) \ : sizeof(struct sockaddr)))) #endif /* HAVE_STRUCT_SOCKADDR_SA_LEN */ #endif /* !SA_SIZE */ /* Routing socket message types. */ const struct message rtm_type_str[] = {{RTM_ADD, "RTM_ADD"}, {RTM_DELETE, "RTM_DELETE"}, {RTM_CHANGE, "RTM_CHANGE"}, {RTM_GET, "RTM_GET"}, {RTM_LOSING, "RTM_LOSING"}, {RTM_REDIRECT, "RTM_REDIRECT"}, {RTM_MISS, "RTM_MISS"}, #ifdef RTM_LOCK {RTM_LOCK, "RTM_LOCK"}, #endif /* RTM_LOCK */ #ifdef OLDADD {RTM_OLDADD, "RTM_OLDADD"}, #endif /* RTM_OLDADD */ #ifdef RTM_OLDDEL {RTM_OLDDEL, "RTM_OLDDEL"}, #endif /* RTM_OLDDEL */ #ifdef RTM_RESOLVE {RTM_RESOLVE, "RTM_RESOLVE"}, #endif /* RTM_RESOLVE */ {RTM_NEWADDR, "RTM_NEWADDR"}, {RTM_DELADDR, "RTM_DELADDR"}, {RTM_IFINFO, "RTM_IFINFO"}, #ifdef RTM_OIFINFO {RTM_OIFINFO, "RTM_OIFINFO"}, #endif /* RTM_OIFINFO */ #ifdef RTM_NEWMADDR {RTM_NEWMADDR, "RTM_NEWMADDR"}, #endif /* RTM_NEWMADDR */ #ifdef RTM_DELMADDR {RTM_DELMADDR, "RTM_DELMADDR"}, #endif /* RTM_DELMADDR */ #ifdef RTM_IFANNOUNCE {RTM_IFANNOUNCE, "RTM_IFANNOUNCE"}, #endif /* RTM_IFANNOUNCE */ {0}}; static const struct message rtm_flag_str[] = {{RTF_UP, "UP"}, {RTF_GATEWAY, "GATEWAY"}, {RTF_HOST, "HOST"}, {RTF_REJECT, "REJECT"}, {RTF_DYNAMIC, "DYNAMIC"}, {RTF_MODIFIED, "MODIFIED"}, {RTF_DONE, "DONE"}, #ifdef RTF_MASK {RTF_MASK, "MASK"}, #endif /* RTF_MASK */ #ifdef RTF_CLONING {RTF_CLONING, "CLONING"}, #endif /* RTF_CLONING */ #ifdef RTF_XRESOLVE {RTF_XRESOLVE, "XRESOLVE"}, #endif /* RTF_XRESOLVE */ #ifdef RTF_LLINFO {RTF_LLINFO, "LLINFO"}, #endif /* RTF_LLINFO */ {RTF_STATIC, "STATIC"}, {RTF_BLACKHOLE, "BLACKHOLE"}, #ifdef RTF_PRIVATE {RTF_PRIVATE, "PRIVATE"}, #endif /* RTF_PRIVATE */ {RTF_PROTO1, "PROTO1"}, {RTF_PROTO2, "PROTO2"}, #ifdef RTF_PRCLONING {RTF_PRCLONING, "PRCLONING"}, #endif /* RTF_PRCLONING */ #ifdef RTF_WASCLONED {RTF_WASCLONED, "WASCLONED"}, #endif /* RTF_WASCLONED */ #ifdef RTF_PROTO3 {RTF_PROTO3, "PROTO3"}, #endif /* RTF_PROTO3 */ #ifdef RTF_PINNED {RTF_PINNED, "PINNED"}, #endif /* RTF_PINNED */ #ifdef RTF_LOCAL {RTF_LOCAL, "LOCAL"}, #endif /* RTF_LOCAL */ #ifdef RTF_BROADCAST {RTF_BROADCAST, "BROADCAST"}, #endif /* RTF_BROADCAST */ #ifdef RTF_MULTICAST {RTF_MULTICAST, "MULTICAST"}, #endif /* RTF_MULTICAST */ #ifdef RTF_MULTIRT {RTF_MULTIRT, "MULTIRT"}, #endif /* RTF_MULTIRT */ #ifdef RTF_SETSRC {RTF_SETSRC, "SETSRC"}, #endif /* RTF_SETSRC */ {0}}; /* Kernel routing update socket. */ int routing_sock = -1; /* Kernel dataplane routing update socket, used in the dataplane pthread * context. */ int dplane_routing_sock = -1; /* Yes I'm checking ugly routing socket behavior. */ /* #define DEBUG */ size_t _rta_get(caddr_t sap, void *destp, size_t destlen, bool checkaf); size_t rta_get(caddr_t sap, void *dest, size_t destlen); size_t rta_getattr(caddr_t sap, void *destp, size_t destlen); size_t rta_getsdlname(caddr_t sap, void *dest, short *destlen); const char *rtatostr(unsigned int flags, char *buf, size_t buflen); /* Supported address family check. */ static inline int af_check(int family) { if (family == AF_INET) return 1; if (family == AF_INET6) return 1; return 0; } size_t _rta_get(caddr_t sap, void *destp, size_t destlen, bool checkaf) { struct sockaddr *sa = (struct sockaddr *)sap; struct sockaddr_dl *sdl; uint8_t *dest = destp; size_t tlen, copylen; #ifdef HAVE_STRUCT_SOCKADDR_SA_LEN copylen = sa->sa_len; tlen = (copylen == 0) ? sizeof(ROUNDUP_TYPE) : ROUNDUP(copylen); #else /* !HAVE_STRUCT_SOCKADDR_SA_LEN */ copylen = tlen = SAROUNDUP(sap); #endif /* !HAVE_STRUCT_SOCKADDR_SA_LEN */ if (copylen > 0 && dest != NULL) { if (checkaf && af_check(sa->sa_family) == 0) return tlen; /* * Handle sockaddr_dl corner case: * RTA_NETMASK might be AF_LINK, but it doesn't anything * relevant (e.g. zeroed out fields). Check for this * case and avoid warning log message. */ if (sa->sa_family == AF_LINK) { sdl = (struct sockaddr_dl *)sa; if (sdl->sdl_index == 0 || sdl->sdl_nlen == 0) copylen = destlen; } if (copylen > destlen) { zlog_warn( "%s: destination buffer too small (%zu vs %zu)", __func__, copylen, destlen); memcpy(dest, sap, destlen); } else memcpy(dest, sap, copylen); } return tlen; } size_t rta_get(caddr_t sap, void *destp, size_t destlen) { return _rta_get(sap, destp, destlen, true); } size_t rta_getattr(caddr_t sap, void *destp, size_t destlen) { return _rta_get(sap, destp, destlen, false); } size_t rta_getsdlname(caddr_t sap, void *destp, short *destlen) { struct sockaddr_dl *sdl = (struct sockaddr_dl *)sap; uint8_t *dest = destp; size_t tlen, copylen; copylen = sdl->sdl_nlen; #ifdef HAVE_STRUCT_SOCKADDR_SA_LEN struct sockaddr *sa = (struct sockaddr *)sap; tlen = (sa->sa_len == 0) ? sizeof(ROUNDUP_TYPE) : ROUNDUP(sa->sa_len); #else /* !HAVE_STRUCT_SOCKADDR_SA_LEN */ tlen = SAROUNDUP(sap); #endif /* !HAVE_STRUCT_SOCKADDR_SA_LEN */ if (copylen > 0 && dest != NULL && sdl->sdl_family == AF_LINK) { if (copylen > IFNAMSIZ) { zlog_warn( "%s: destination buffer too small (%zu vs %d)", __func__, copylen, IFNAMSIZ); memcpy(dest, sdl->sdl_data, IFNAMSIZ); dest[IFNAMSIZ] = 0; *destlen = IFNAMSIZ; } else { memcpy(dest, sdl->sdl_data, copylen); dest[copylen] = 0; *destlen = copylen; } } else *destlen = 0; return tlen; } const char *rtatostr(unsigned int flags, char *buf, size_t buflen) { const char *flagstr, *bufstart; int bit, wlen; char ustr[32]; /* Hold the pointer to the buffer beginning. */ bufstart = buf; for (bit = 1; bit; bit <<= 1) { if ((flags & bit) == 0) continue; switch (bit) { case RTA_DST: flagstr = "DST"; break; case RTA_GATEWAY: flagstr = "GATEWAY"; break; case RTA_NETMASK: flagstr = "NETMASK"; break; #ifdef RTA_GENMASK case RTA_GENMASK: flagstr = "GENMASK"; break; #endif /* RTA_GENMASK */ case RTA_IFP: flagstr = "IFP"; break; case RTA_IFA: flagstr = "IFA"; break; #ifdef RTA_AUTHOR case RTA_AUTHOR: flagstr = "AUTHOR"; break; #endif /* RTA_AUTHOR */ case RTA_BRD: flagstr = "BRD"; break; #ifdef RTA_SRC case RTA_SRC: flagstr = "SRC"; break; #endif /* RTA_SRC */ #ifdef RTA_SRCMASK case RTA_SRCMASK: flagstr = "SRCMASK"; break; #endif /* RTA_SRCMASK */ #ifdef RTA_LABEL case RTA_LABEL: flagstr = "LABEL"; break; #endif /* RTA_LABEL */ default: snprintf(ustr, sizeof(ustr), "0x%x", bit); flagstr = ustr; break; } wlen = snprintf(buf, buflen, "%s,", flagstr); buf += wlen; buflen -= wlen; } /* Check for empty buffer. */ if (bufstart != buf) buf--; /* Remove the last comma. */ *buf = 0; return bufstart; } /* Dump routing table flag for debug purpose. */ static void rtm_flag_dump(int flag) { const struct message *mes; static char buf[BUFSIZ]; buf[0] = '\0'; for (mes = rtm_flag_str; mes->key != 0; mes++) { if (mes->key & flag) { strlcat(buf, mes->str, BUFSIZ); strlcat(buf, " ", BUFSIZ); } } zlog_debug("Kernel: %s", buf); } #ifdef RTM_IFANNOUNCE /* Interface adding function */ static int ifan_read(struct if_announcemsghdr *ifan) { struct interface *ifp; ifp = if_lookup_by_index(ifan->ifan_index, VRF_DEFAULT); if (ifp) assert((ifp->ifindex == ifan->ifan_index) || (ifp->ifindex == IFINDEX_INTERNAL)); if ((ifp == NULL) || ((ifp->ifindex == IFINDEX_INTERNAL) && (ifan->ifan_what == IFAN_ARRIVAL))) { if (IS_ZEBRA_DEBUG_KERNEL) zlog_debug( "%s: creating interface for ifindex %d, name %s", __func__, ifan->ifan_index, ifan->ifan_name); /* Create Interface */ ifp = if_get_by_name(ifan->ifan_name, VRF_DEFAULT); if_set_index(ifp, ifan->ifan_index); if_get_metric(ifp); if_add_update(ifp); } else if (ifp != NULL && ifan->ifan_what == IFAN_DEPARTURE) if_delete_update(ifp); if_get_flags(ifp); if_get_mtu(ifp); if_get_metric(ifp); if (IS_ZEBRA_DEBUG_KERNEL) zlog_debug("%s: interface %s index %d", __func__, ifan->ifan_name, ifan->ifan_index); return 0; } #endif /* RTM_IFANNOUNCE */ #ifdef HAVE_BSD_IFI_LINK_STATE /* BSD link detect translation */ static void bsd_linkdetect_translate(struct if_msghdr *ifm) { if ((ifm->ifm_data.ifi_link_state >= LINK_STATE_UP) || (ifm->ifm_data.ifi_link_state == LINK_STATE_UNKNOWN)) SET_FLAG(ifm->ifm_flags, IFF_RUNNING); else UNSET_FLAG(ifm->ifm_flags, IFF_RUNNING); } #endif /* HAVE_BSD_IFI_LINK_STATE */ static enum zebra_link_type sdl_to_zebra_link_type(unsigned int sdlt) { switch (sdlt) { case IFT_ETHER: return ZEBRA_LLT_ETHER; case IFT_X25: return ZEBRA_LLT_X25; case IFT_FDDI: return ZEBRA_LLT_FDDI; case IFT_PPP: return ZEBRA_LLT_PPP; case IFT_LOOP: return ZEBRA_LLT_LOOPBACK; case IFT_SLIP: return ZEBRA_LLT_SLIP; case IFT_ARCNET: return ZEBRA_LLT_ARCNET; case IFT_ATM: return ZEBRA_LLT_ATM; case IFT_LOCALTALK: return ZEBRA_LLT_LOCALTLK; case IFT_HIPPI: return ZEBRA_LLT_HIPPI; #ifdef IFT_IEEE1394 case IFT_IEEE1394: return ZEBRA_LLT_IEEE1394; #endif default: return ZEBRA_LLT_UNKNOWN; } } /* * Handle struct if_msghdr obtained from reading routing socket or * sysctl (from interface_list). There may or may not be sockaddrs * present after the header. */ int ifm_read(struct if_msghdr *ifm) { struct interface *ifp = NULL; struct sockaddr_dl *sdl = NULL; char ifname[IFNAMSIZ]; short ifnlen = 0; int maskbit; caddr_t cp; char fbuf[64]; /* terminate ifname at head (for strnlen) and tail (for safety) */ ifname[IFNAMSIZ - 1] = '\0'; /* paranoia: sanity check structure */ if (ifm->ifm_msglen < sizeof(struct if_msghdr)) { flog_err(EC_ZEBRA_NETLINK_LENGTH_ERROR, "ifm_read: ifm->ifm_msglen %d too short\n", ifm->ifm_msglen); return -1; } /* * Check for a sockaddr_dl following the message. First, point to * where a socakddr might be if one follows the message. */ cp = (void *)(ifm + 1); #ifdef SUNOS_5 /* * XXX This behavior should be narrowed to only the kernel versions * for which the structures returned do not match the headers. * * if_msghdr_t on 64 bit kernels in Solaris 9 and earlier versions * is 12 bytes larger than the 32 bit version. */ if (((struct sockaddr *)cp)->sa_family == AF_UNSPEC) cp = cp + 12; #endif /* Look up for RTA_IFP and skip others. */ for (maskbit = 1; maskbit; maskbit <<= 1) { if ((maskbit & ifm->ifm_addrs) == 0) continue; if (maskbit != RTA_IFP) { cp += rta_get(cp, NULL, 0); continue; } /* Save the pointer to the structure. */ sdl = (struct sockaddr_dl *)cp; cp += rta_getsdlname(cp, ifname, &ifnlen); } if (IS_ZEBRA_DEBUG_KERNEL) zlog_debug("%s: sdl ifname %s addrs {%s}", __func__, (ifnlen ? ifname : "(nil)"), rtatostr(ifm->ifm_addrs, fbuf, sizeof(fbuf))); /* * Look up on ifindex first, because ifindices are the primary handle * for * interfaces across the user/kernel boundary, for most systems. (Some * messages, such as up/down status changes on NetBSD, do not include a * sockaddr_dl). */ if ((ifp = if_lookup_by_index(ifm->ifm_index, VRF_DEFAULT)) != NULL) { /* we have an ifp, verify that the name matches as some systems, * eg Solaris, have a 1:many association of ifindex:ifname * if they dont match, we dont have the correct ifp and should * set it back to NULL to let next check do lookup by name */ if (ifnlen && (strncmp(ifp->name, ifname, IFNAMSIZ) != 0)) { if (IS_ZEBRA_DEBUG_KERNEL) zlog_debug( "%s: ifp name %s doesn't match sdl name %s", __func__, ifp->name, ifname); ifp = NULL; } } /* * If we dont have an ifp, try looking up by name. Particularly as some * systems (Solaris) have a 1:many mapping of ifindex:ifname - the * ifname * is therefore our unique handle to that interface. * * Interfaces specified in the configuration file for which the ifindex * has not been determined will have ifindex == IFINDEX_INTERNAL, and * such * interfaces are found by this search, and then their ifindex values * can * be filled in. */ if ((ifp == NULL) && ifnlen) ifp = if_lookup_by_name(ifname, VRF_DEFAULT); /* * If ifp still does not exist or has an invalid index * (IFINDEX_INTERNAL), * create or fill in an interface. */ if ((ifp == NULL) || (ifp->ifindex == IFINDEX_INTERNAL)) { /* * To create or fill in an interface, a sockaddr_dl (via * RTA_IFP) is required. */ if (!ifnlen) { zlog_debug("Interface index %d (new) missing ifname", ifm->ifm_index); return -1; } #ifndef RTM_IFANNOUNCE /* Down->Down interface should be ignored here. * See further comment below. */ if (!CHECK_FLAG(ifm->ifm_flags, IFF_UP)) return 0; #endif /* !RTM_IFANNOUNCE */ if (ifp == NULL) { /* Interface that zebra was not previously aware of, so * create. */ ifp = if_create(ifname, VRF_DEFAULT); if (IS_ZEBRA_DEBUG_KERNEL) zlog_debug("%s: creating ifp for ifindex %d", __func__, ifm->ifm_index); } if (IS_ZEBRA_DEBUG_KERNEL) zlog_debug( "%s: updated/created ifp, ifname %s, ifindex %d", __func__, ifp->name, ifp->ifindex); /* * Fill in newly created interface structure, or larval * structure with ifindex IFINDEX_INTERNAL. */ if_set_index(ifp, ifm->ifm_index); #ifdef HAVE_BSD_IFI_LINK_STATE /* translate BSD kernel msg for link-state */ bsd_linkdetect_translate(ifm); #endif /* HAVE_BSD_IFI_LINK_STATE */ if_flags_update(ifp, ifm->ifm_flags); #if defined(__bsdi__) if_kvm_get_mtu(ifp); #else if_get_mtu(ifp); #endif /* __bsdi__ */ if_get_metric(ifp); /* * XXX sockaddr_dl contents can be larger than the structure * definition. There are 2 big families here: * - BSD has sdl_len + sdl_data[16] + overruns sdl_data * we MUST use sdl_len here or we'll truncate data. * - Solaris has no sdl_len, but sdl_data[244] * presumably, it's not going to run past that, so sizeof() * is fine here. * a nonzero ifnlen from rta_getsdlname() means sdl is valid */ ifp->ll_type = ZEBRA_LLT_UNKNOWN; ifp->hw_addr_len = 0; if (ifnlen) { #ifdef HAVE_STRUCT_SOCKADDR_DL_SDL_LEN memcpy(&((struct zebra_if *)ifp->info)->sdl, sdl, sdl->sdl_len); #else memcpy(&((struct zebra_if *)ifp->info)->sdl, sdl, sizeof(struct sockaddr_dl)); #endif /* HAVE_STRUCT_SOCKADDR_DL_SDL_LEN */ ifp->ll_type = sdl_to_zebra_link_type(sdl->sdl_type); if (sdl->sdl_alen <= sizeof(ifp->hw_addr)) { memcpy(ifp->hw_addr, LLADDR(sdl), sdl->sdl_alen); ifp->hw_addr_len = sdl->sdl_alen; } } if_add_update(ifp); } else /* * Interface structure exists. Adjust stored flags from * notification. If interface has up->down or down->up * transition, call state change routines (to adjust routes, * notify routing daemons, etc.). (Other flag changes are stored * but apparently do not trigger action.) */ { if (ifp->ifindex != ifm->ifm_index) { zlog_debug( "%s: index mismatch, ifname %s, ifp index %d, " "ifm index %d", __func__, ifp->name, ifp->ifindex, ifm->ifm_index); return -1; } #ifdef HAVE_BSD_IFI_LINK_STATE /* translate BSD kernel msg for link-state */ bsd_linkdetect_translate(ifm); #endif /* HAVE_BSD_IFI_LINK_STATE */ /* update flags and handle operative->inoperative transition, if * any */ if_flags_update(ifp, ifm->ifm_flags); #ifndef RTM_IFANNOUNCE if (!if_is_up(ifp)) { /* No RTM_IFANNOUNCE on this platform, so we can never * distinguish between ~IFF_UP and delete. We must * presume * it has been deleted. * Eg, Solaris will not notify us of unplumb. * * XXX: Fixme - this should be runtime detected * So that a binary compiled on a system with IFANNOUNCE * will still behave correctly if run on a platform * without */ if_delete_update(ifp); } #endif /* RTM_IFANNOUNCE */ if (if_is_up(ifp)) { #if defined(__bsdi__) if_kvm_get_mtu(ifp); #else if_get_mtu(ifp); #endif /* __bsdi__ */ if_get_metric(ifp); } } #ifdef HAVE_NET_RT_IFLIST ifp->stats = ifm->ifm_data; #endif /* HAVE_NET_RT_IFLIST */ ifp->speed = ifm->ifm_data.ifi_baudrate / 1000000; if (IS_ZEBRA_DEBUG_KERNEL) zlog_debug("%s: interface %s index %d", __func__, ifp->name, ifp->ifindex); return 0; } /* Address read from struct ifa_msghdr. */ static void ifam_read_mesg(struct ifa_msghdr *ifm, union sockunion *addr, union sockunion *mask, union sockunion *brd, char *ifname, short *ifnlen) { caddr_t pnt, end; union sockunion dst; union sockunion gateway; int maskbit; char fbuf[64]; pnt = (caddr_t)(ifm + 1); end = ((caddr_t)ifm) + ifm->ifam_msglen; /* Be sure structure is cleared */ memset(mask, 0, sizeof(union sockunion)); memset(addr, 0, sizeof(union sockunion)); memset(brd, 0, sizeof(union sockunion)); memset(&dst, 0, sizeof(union sockunion)); memset(&gateway, 0, sizeof(union sockunion)); /* We fetch each socket variable into sockunion. */ for (maskbit = 1; maskbit; maskbit <<= 1) { if ((maskbit & ifm->ifam_addrs) == 0) continue; switch (maskbit) { case RTA_DST: pnt += rta_get(pnt, &dst, sizeof(dst)); break; case RTA_GATEWAY: pnt += rta_get(pnt, &gateway, sizeof(gateway)); break; case RTA_NETMASK: pnt += rta_getattr(pnt, mask, sizeof(*mask)); break; case RTA_IFP: pnt += rta_getsdlname(pnt, ifname, ifnlen); break; case RTA_IFA: pnt += rta_get(pnt, addr, sizeof(*addr)); break; case RTA_BRD: pnt += rta_get(pnt, brd, sizeof(*brd)); break; default: pnt += rta_get(pnt, NULL, 0); break; } if (pnt > end) { zlog_warn("%s: overflow detected (pnt:%p end:%p)", __func__, pnt, end); break; } } if (IS_ZEBRA_DEBUG_KERNEL) { switch (sockunion_family(addr)) { case AF_INET: case AF_INET6: { char buf[4][INET6_ADDRSTRLEN]; int masklen = (sockunion_family(addr) == AF_INET) ? ip_masklen(mask->sin.sin_addr) : ip6_masklen(mask->sin6.sin6_addr); zlog_debug( "%s: ifindex %d, ifname %s, ifam_addrs {%s}, " "ifam_flags 0x%x, addr %s/%d broad %s dst %s " "gateway %s", __func__, ifm->ifam_index, (ifnlen ? ifname : "(nil)"), rtatostr(ifm->ifam_addrs, fbuf, sizeof(fbuf)), ifm->ifam_flags, sockunion2str(addr, buf[0], sizeof(buf[0])), masklen, sockunion2str(brd, buf[1], sizeof(buf[1])), sockunion2str(&dst, buf[2], sizeof(buf[2])), sockunion2str(&gateway, buf[2], sizeof(buf[2]))); } break; default: zlog_debug("%s: ifindex %d, ifname %s, ifam_addrs {%s}", __func__, ifm->ifam_index, (ifnlen ? ifname : "(nil)"), rtatostr(ifm->ifam_addrs, fbuf, sizeof(fbuf))); break; } } /* Assert read up end point matches to end point */ pnt = (caddr_t)ROUNDUP((size_t)pnt); if (pnt != (caddr_t)ROUNDUP((size_t)end)) zlog_debug("ifam_read() doesn't read all socket data"); } /* Interface's address information get. */ int ifam_read(struct ifa_msghdr *ifam) { struct interface *ifp = NULL; union sockunion addr, mask, brd; char ifname[INTERFACE_NAMSIZ]; short ifnlen = 0; char isalias = 0; int flags = 0; ifname[0] = ifname[INTERFACE_NAMSIZ - 1] = '\0'; /* Allocate and read address information. */ ifam_read_mesg(ifam, &addr, &mask, &brd, ifname, &ifnlen); if ((ifp = if_lookup_by_index(ifam->ifam_index, VRF_DEFAULT)) == NULL) { flog_warn(EC_ZEBRA_UNKNOWN_INTERFACE, "%s: no interface for ifname %s, index %d", __func__, ifname, ifam->ifam_index); return -1; } if (ifnlen && strncmp(ifp->name, ifname, INTERFACE_NAMSIZ)) isalias = 1; /* N.B. The info in ifa_msghdr does not tell us whether the RTA_BRD field contains a broadcast address or a peer address, so we are forced to rely upon the interface type. */ if (if_is_pointopoint(ifp)) SET_FLAG(flags, ZEBRA_IFA_PEER); #if 0 /* it might seem cute to grab the interface metric here, however * we're processing an address update message, and so some systems * (e.g. FBSD) dont bother to fill in ifam_metric. Disabled, but left * in deliberately, as comment. */ ifp->metric = ifam->ifam_metric; #endif /* Add connected address. */ switch (sockunion_family(&addr)) { case AF_INET: if (ifam->ifam_type == RTM_NEWADDR) connected_add_ipv4(ifp, flags, &addr.sin.sin_addr, ip_masklen(mask.sin.sin_addr), &brd.sin.sin_addr, (isalias ? ifname : NULL), METRIC_MAX); else connected_delete_ipv4(ifp, flags, &addr.sin.sin_addr, ip_masklen(mask.sin.sin_addr), &brd.sin.sin_addr); break; case AF_INET6: /* Unset interface index from link-local address when IPv6 stack is KAME. */ if (IN6_IS_ADDR_LINKLOCAL(&addr.sin6.sin6_addr)) { SET_IN6_LINKLOCAL_IFINDEX(addr.sin6.sin6_addr, 0); } if (ifam->ifam_type == RTM_NEWADDR) connected_add_ipv6(ifp, flags, &addr.sin6.sin6_addr, NULL, ip6_masklen(mask.sin6.sin6_addr), (isalias ? ifname : NULL), METRIC_MAX); else connected_delete_ipv6(ifp, &addr.sin6.sin6_addr, NULL, ip6_masklen(mask.sin6.sin6_addr)); break; default: /* Unsupported family silently ignore... */ break; } /* Check interface flag for implicit up of the interface. */ if_refresh(ifp); #ifdef SUNOS_5 /* In addition to lacking IFANNOUNCE, on SUNOS IFF_UP is strange. * See comments for SUNOS_5 in interface.c::if_flags_mangle. * * Here we take care of case where the real IFF_UP was previously * unset (as kept in struct zebra_if.primary_state) and the mangled * IFF_UP (ie IFF_UP set || listcount(connected) has now transitioned * to unset due to the lost non-primary address having DELADDR'd. * * we must delete the interface, because in between here and next * event for this interface-name the administrator could unplumb * and replumb the interface. */ if (!if_is_up(ifp)) if_delete_update(ifp); #endif /* SUNOS_5 */ return 0; } /* Interface function for reading kernel routing table information. */ static int rtm_read_mesg(struct rt_msghdr *rtm, union sockunion *dest, union sockunion *mask, union sockunion *gate, char *ifname, short *ifnlen) { caddr_t pnt, end; int maskbit; /* Pnt points out socket data start point. */ pnt = (caddr_t)(rtm + 1); end = ((caddr_t)rtm) + rtm->rtm_msglen; /* rt_msghdr version check. */ if (rtm->rtm_version != RTM_VERSION) flog_warn(EC_ZEBRA_RTM_VERSION_MISMATCH, "Routing message version different %d should be %d." "This may cause problem\n", rtm->rtm_version, RTM_VERSION); /* Be sure structure is cleared */ memset(dest, 0, sizeof(union sockunion)); memset(gate, 0, sizeof(union sockunion)); memset(mask, 0, sizeof(union sockunion)); /* We fetch each socket variable into sockunion. */ /* We fetch each socket variable into sockunion. */ for (maskbit = 1; maskbit; maskbit <<= 1) { if ((maskbit & rtm->rtm_addrs) == 0) continue; switch (maskbit) { case RTA_DST: pnt += rta_get(pnt, dest, sizeof(*dest)); break; case RTA_GATEWAY: pnt += rta_get(pnt, gate, sizeof(*gate)); break; case RTA_NETMASK: pnt += rta_getattr(pnt, mask, sizeof(*mask)); break; case RTA_IFP: pnt += rta_getsdlname(pnt, ifname, ifnlen); break; default: pnt += rta_get(pnt, NULL, 0); break; } if (pnt > end) { zlog_warn("%s: overflow detected (pnt:%p end:%p)", __func__, pnt, end); break; } } /* If there is netmask information set it's family same as destination family*/ if (rtm->rtm_addrs & RTA_NETMASK) mask->sa.sa_family = dest->sa.sa_family; /* Assert read up to the end of pointer. */ if (pnt != end) zlog_debug("rtm_read() doesn't read all socket data."); return rtm->rtm_flags; } void rtm_read(struct rt_msghdr *rtm) { int flags; uint8_t zebra_flags; union sockunion dest, mask, gate; char ifname[INTERFACE_NAMSIZ + 1]; short ifnlen = 0; struct nexthop nh; struct prefix p; ifindex_t ifindex = 0; afi_t afi; char fbuf[64]; zebra_flags = 0; /* Read destination and netmask and gateway from rtm message structure. */ flags = rtm_read_mesg(rtm, &dest, &mask, &gate, ifname, &ifnlen); if (!(flags & RTF_DONE)) return; if (IS_ZEBRA_DEBUG_KERNEL) zlog_debug("%s: got rtm of type %d (%s) addrs {%s}", __func__, rtm->rtm_type, lookup_msg(rtm_type_str, rtm->rtm_type, NULL), rtatostr(rtm->rtm_addrs, fbuf, sizeof(fbuf))); #ifdef RTF_CLONED /*bsdi, netbsd 1.6*/ if (flags & RTF_CLONED) return; #endif #ifdef RTF_WASCLONED /*freebsd*/ if (flags & RTF_WASCLONED) return; #endif if ((rtm->rtm_type == RTM_ADD || rtm->rtm_type == RTM_CHANGE) && !(flags & RTF_UP)) return; /* This is connected route. */ if (!(flags & RTF_GATEWAY)) return; if (flags & RTF_PROTO1) SET_FLAG(zebra_flags, ZEBRA_FLAG_SELFROUTE); memset(&nh, 0, sizeof(nh)); nh.vrf_id = VRF_DEFAULT; /* This is a reject or blackhole route */ if (flags & RTF_REJECT) { nh.type = NEXTHOP_TYPE_BLACKHOLE; nh.bh_type = BLACKHOLE_REJECT; } else if (flags & RTF_BLACKHOLE) { nh.type = NEXTHOP_TYPE_BLACKHOLE; nh.bh_type = BLACKHOLE_NULL; } /* * Ignore our own messages. */ if (rtm->rtm_type != RTM_GET && rtm->rtm_pid == pid) return; if (dest.sa.sa_family == AF_INET) { afi = AFI_IP; p.family = AF_INET; p.u.prefix4 = dest.sin.sin_addr; if (flags & RTF_HOST) p.prefixlen = IPV4_MAX_PREFIXLEN; else p.prefixlen = ip_masklen(mask.sin.sin_addr); if (!nh.type) { nh.type = NEXTHOP_TYPE_IPV4; nh.gate.ipv4 = gate.sin.sin_addr; } } else if (dest.sa.sa_family == AF_INET6) { afi = AFI_IP6; p.family = AF_INET6; p.u.prefix6 = dest.sin6.sin6_addr; if (flags & RTF_HOST) p.prefixlen = IPV6_MAX_PREFIXLEN; else p.prefixlen = ip6_masklen(mask.sin6.sin6_addr); #ifdef KAME if (IN6_IS_ADDR_LINKLOCAL(&gate.sin6.sin6_addr)) { ifindex = IN6_LINKLOCAL_IFINDEX(gate.sin6.sin6_addr); SET_IN6_LINKLOCAL_IFINDEX(gate.sin6.sin6_addr, 0); } #endif /* KAME */ if (!nh.type) { nh.type = ifindex ? NEXTHOP_TYPE_IPV6_IFINDEX : NEXTHOP_TYPE_IPV6; nh.gate.ipv6 = gate.sin6.sin6_addr; nh.ifindex = ifindex; } } else return; /* * CHANGE: delete the old prefix, we have no further information * to specify the route really */ if (rtm->rtm_type == RTM_CHANGE) rib_delete(afi, SAFI_UNICAST, VRF_DEFAULT, ZEBRA_ROUTE_KERNEL, 0, zebra_flags, &p, NULL, NULL, RT_TABLE_MAIN, 0, 0, true); if (rtm->rtm_type == RTM_GET || rtm->rtm_type == RTM_ADD || rtm->rtm_type == RTM_CHANGE) rib_add(afi, SAFI_UNICAST, VRF_DEFAULT, ZEBRA_ROUTE_KERNEL, 0, zebra_flags, &p, NULL, &nh, RT_TABLE_MAIN, 0, 0, 0, 0); else rib_delete(afi, SAFI_UNICAST, VRF_DEFAULT, ZEBRA_ROUTE_KERNEL, 0, zebra_flags, &p, NULL, &nh, RT_TABLE_MAIN, 0, 0, true); } /* Interface function for the kernel routing table updates. Support * for RTM_CHANGE will be needed. * Exported only for rt_socket.c */ int rtm_write(int message, union sockunion *dest, union sockunion *mask, union sockunion *gate, union sockunion *mpls, unsigned int index, enum blackhole_type bh_type, int metric) { int ret; caddr_t pnt; struct interface *ifp; /* Sequencial number of routing message. */ static int msg_seq = 0; /* Struct of rt_msghdr and buffer for storing socket's data. */ struct { struct rt_msghdr rtm; char buf[512]; } msg; if (dplane_routing_sock < 0) return ZEBRA_ERR_EPERM; /* Clear and set rt_msghdr values */ memset(&msg, 0, sizeof(struct rt_msghdr)); msg.rtm.rtm_version = RTM_VERSION; msg.rtm.rtm_type = message; msg.rtm.rtm_seq = msg_seq++; msg.rtm.rtm_addrs = RTA_DST; msg.rtm.rtm_addrs |= RTA_GATEWAY; msg.rtm.rtm_flags = RTF_UP; #ifdef __OpenBSD__ msg.rtm.rtm_flags |= RTF_MPATH; msg.rtm.rtm_fmask = RTF_MPLS; #endif msg.rtm.rtm_index = index; if (metric != 0) { msg.rtm.rtm_rmx.rmx_hopcount = metric; msg.rtm.rtm_inits |= RTV_HOPCOUNT; } ifp = if_lookup_by_index(index, VRF_DEFAULT); if (gate && (message == RTM_ADD || message == RTM_CHANGE)) msg.rtm.rtm_flags |= RTF_GATEWAY; /* When RTF_CLONING is unavailable on BSD, should we set some * other flag instead? */ #ifdef RTF_CLONING if (!gate && (message == RTM_ADD || message == RTM_CHANGE) && ifp && (ifp->flags & IFF_POINTOPOINT) == 0) msg.rtm.rtm_flags |= RTF_CLONING; #endif /* RTF_CLONING */ /* If no protocol specific gateway is specified, use link address for gateway. */ if (!gate) { if (!ifp) { char dest_buf[INET_ADDRSTRLEN] = "NULL", mask_buf[INET_ADDRSTRLEN] = "255.255.255.255"; if (dest) inet_ntop(AF_INET, &dest->sin.sin_addr, dest_buf, INET_ADDRSTRLEN); if (mask) inet_ntop(AF_INET, &mask->sin.sin_addr, mask_buf, INET_ADDRSTRLEN); flog_warn( EC_ZEBRA_RTM_NO_GATEWAY, "%s: %s/%s: gate == NULL and no gateway found for ifindex %d", __func__, dest_buf, mask_buf, index); return -1; } gate = (union sockunion *)&((struct zebra_if *)ifp->info)->sdl; } if (mask) msg.rtm.rtm_addrs |= RTA_NETMASK; else if (message == RTM_ADD || message == RTM_CHANGE) msg.rtm.rtm_flags |= RTF_HOST; #ifdef __OpenBSD__ if (mpls) { msg.rtm.rtm_addrs |= RTA_SRC; msg.rtm.rtm_flags |= RTF_MPLS; if (mpls->smpls.smpls_label != htonl(MPLS_LABEL_IMPLICIT_NULL << MPLS_LABEL_OFFSET)) msg.rtm.rtm_mpls = MPLS_OP_PUSH; } #endif /* Tagging route with flags */ msg.rtm.rtm_flags |= (RTF_PROTO1); switch (bh_type) { case BLACKHOLE_UNSPEC: break; case BLACKHOLE_REJECT: msg.rtm.rtm_flags |= RTF_REJECT; break; default: msg.rtm.rtm_flags |= RTF_BLACKHOLE; break; } #define SOCKADDRSET(X, R) \ if (msg.rtm.rtm_addrs & (R)) { \ int len = SAROUNDUP(X); \ memcpy(pnt, (caddr_t)(X), len); \ pnt += len; \ } pnt = (caddr_t)msg.buf; /* Write each socket data into rtm message buffer */ SOCKADDRSET(dest, RTA_DST); SOCKADDRSET(gate, RTA_GATEWAY); SOCKADDRSET(mask, RTA_NETMASK); #ifdef __OpenBSD__ SOCKADDRSET(mpls, RTA_SRC); #endif msg.rtm.rtm_msglen = pnt - (caddr_t)&msg; ret = write(dplane_routing_sock, &msg, msg.rtm.rtm_msglen); if (ret != msg.rtm.rtm_msglen) { if (errno == EEXIST) return ZEBRA_ERR_RTEXIST; if (errno == ENETUNREACH) return ZEBRA_ERR_RTUNREACH; if (errno == ESRCH) return ZEBRA_ERR_RTNOEXIST; flog_err_sys(EC_LIB_SOCKET, "%s: write : %s (%d)", __func__, safe_strerror(errno), errno); return ZEBRA_ERR_KERNEL; } return ZEBRA_ERR_NOERROR; } #include "thread.h" #include "zebra/zserv.h" /* For debug purpose. */ static void rtmsg_debug(struct rt_msghdr *rtm) { char fbuf[64]; zlog_debug("Kernel: Len: %d Type: %s", rtm->rtm_msglen, lookup_msg(rtm_type_str, rtm->rtm_type, NULL)); rtm_flag_dump(rtm->rtm_flags); zlog_debug("Kernel: message seq %d", rtm->rtm_seq); zlog_debug("Kernel: pid %lld, rtm_addrs {%s}", (long long)rtm->rtm_pid, rtatostr(rtm->rtm_addrs, fbuf, sizeof(fbuf))); } /* This is pretty gross, better suggestions welcome -- mhandler */ #ifndef RTAX_MAX #ifdef RTA_NUMBITS #define RTAX_MAX RTA_NUMBITS #else #define RTAX_MAX 8 #endif /* RTA_NUMBITS */ #endif /* RTAX_MAX */ /* Kernel routing table and interface updates via routing socket. */ static int kernel_read(struct thread *thread) { int sock; int nbytes; struct rt_msghdr *rtm; /* * This must be big enough for any message the kernel might send. * Rather than determining how many sockaddrs of what size might be * in each particular message, just use RTAX_MAX of sockaddr_storage * for each. Note that the sockaddrs must be after each message * definition, or rather after whichever happens to be the largest, * since the buffer needs to be big enough for a message and the * sockaddrs together. */ union { /* Routing information. */ struct { struct rt_msghdr rtm; struct sockaddr_storage addr[RTAX_MAX]; } r; /* Interface information. */ struct { struct if_msghdr ifm; struct sockaddr_storage addr[RTAX_MAX]; } im; /* Interface address information. */ struct { struct ifa_msghdr ifa; struct sockaddr_storage addr[RTAX_MAX]; } ia; #ifdef RTM_IFANNOUNCE /* Interface arrival/departure */ struct { struct if_announcemsghdr ifan; struct sockaddr_storage addr[RTAX_MAX]; } ian; #endif /* RTM_IFANNOUNCE */ } buf; /* Fetch routing socket. */ sock = THREAD_FD(thread); nbytes = read(sock, &buf, sizeof buf); if (nbytes <= 0) { if (nbytes < 0 && errno != EWOULDBLOCK && errno != EAGAIN) flog_err_sys(EC_LIB_SOCKET, "routing socket error: %s", safe_strerror(errno)); return 0; } thread_add_read(zrouter.master, kernel_read, NULL, sock, NULL); if (IS_ZEBRA_DEBUG_KERNEL) rtmsg_debug(&buf.r.rtm); rtm = &buf.r.rtm; /* * Ensure that we didn't drop any data, so that processing routines * can assume they have the whole message. */ if (rtm->rtm_msglen != nbytes) { zlog_debug( "kernel_read: rtm->rtm_msglen %d, nbytes %d, type %d\n", rtm->rtm_msglen, nbytes, rtm->rtm_type); return -1; } switch (rtm->rtm_type) { case RTM_ADD: case RTM_DELETE: case RTM_CHANGE: rtm_read(rtm); break; case RTM_IFINFO: ifm_read(&buf.im.ifm); break; case RTM_NEWADDR: case RTM_DELADDR: ifam_read(&buf.ia.ifa); break; #ifdef RTM_IFANNOUNCE case RTM_IFANNOUNCE: ifan_read(&buf.ian.ifan); break; #endif /* RTM_IFANNOUNCE */ default: if (IS_ZEBRA_DEBUG_KERNEL) zlog_debug("Unprocessed RTM_type: %d", rtm->rtm_type); break; } return 0; } /* Make routing socket. */ static void routing_socket(struct zebra_ns *zns) { frr_with_privs(&zserv_privs) { routing_sock = ns_socket(AF_ROUTE, SOCK_RAW, 0, zns->ns_id); dplane_routing_sock = ns_socket(AF_ROUTE, SOCK_RAW, 0, zns->ns_id); } if (routing_sock < 0) { flog_err_sys(EC_LIB_SOCKET, "Can't init kernel routing socket"); return; } if (dplane_routing_sock < 0) { flog_err_sys(EC_LIB_SOCKET, "Can't init kernel dataplane routing socket"); return; } /* XXX: Socket should be NONBLOCK, however as we currently * discard failed writes, this will lead to inconsistencies. * For now, socket must be blocking. */ /*if (fcntl (routing_sock, F_SETFL, O_NONBLOCK) < 0) zlog_warn ("Can't set O_NONBLOCK to routing socket");*/ /* kernel_read needs rewrite. */ thread_add_read(zrouter.master, kernel_read, NULL, routing_sock, NULL); } /* Exported interface function. This function simply calls routing_socket (). */ void kernel_init(struct zebra_ns *zns) { routing_socket(zns); } void kernel_terminate(struct zebra_ns *zns, bool complete) { return; } #endif /* !HAVE_NETLINK */ frr-7.2.1/zebra/kernel_socket.h0000644000000000000000000000313413610377563013321 00000000000000/* * Exported kernel_socket functions, exported only for convenience of * sysctl methods. * * This file is part of Quagga. * * Quagga is free software; you can redistribute it and/or modify it * under the terms of the GNU General Public License as published by the * Free Software Foundation; either version 2, or (at your option) any * later version. * * Quagga 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; see the file COPYING; if not, write to the Free Software * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA */ #ifndef __ZEBRA_KERNEL_SOCKET_H #define __ZEBRA_KERNEL_SOCKET_H #ifdef __cplusplus extern "C" { #endif /* Error codes of zebra. */ #define ZEBRA_ERR_NOERROR 0 #define ZEBRA_ERR_RTEXIST -1 #define ZEBRA_ERR_RTUNREACH -2 #define ZEBRA_ERR_EPERM -3 #define ZEBRA_ERR_RTNOEXIST -4 #define ZEBRA_ERR_KERNEL -5 extern void rtm_read(struct rt_msghdr *); extern int ifam_read(struct ifa_msghdr *); extern int ifm_read(struct if_msghdr *); extern int rtm_write(int, union sockunion *, union sockunion *, union sockunion *, union sockunion *, unsigned int, enum blackhole_type, int); extern const struct message rtm_type_str[]; #ifdef __cplusplus } #endif #endif /* __ZEBRA_KERNEL_SOCKET_H */ frr-7.2.1/zebra/label_manager.c0000644000000000000000000003220713610377563013240 00000000000000/* * Label Manager for FRR * * Copyright (C) 2017 by Bingen Eguzkitza, * Volta Networks Inc. * * This file is part of FreeRangeRouting (FRR) * * FRR is free software; you can redistribute it and/or modify it * under the terms of the GNU General Public License as published by the * Free Software Foundation; either version 2, or (at your option) any * later version. * * FRR 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; see the file COPYING; if not, write to the Free Software * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA */ #include #include #include #include #include "lib/log.h" #include "lib/memory.h" #include "lib/mpls.h" #include "lib/network.h" #include "lib/stream.h" #include "lib/zclient.h" #include "lib/libfrr.h" //#include "zebra/zserv.h" #include "zebra/zebra_router.h" #include "zebra/label_manager.h" #include "zebra/zebra_errors.h" #include "zebra/zapi_msg.h" #include "zebra/debug.h" #define CONNECTION_DELAY 5 struct label_manager lbl_mgr; DEFINE_MGROUP(LBL_MGR, "Label Manager"); DEFINE_MTYPE_STATIC(LBL_MGR, LM_CHUNK, "Label Manager Chunk"); /* define hooks for the basic API, so that it can be specialized or served * externally */ DEFINE_HOOK(lm_client_connect, (uint8_t proto, uint16_t instance, vrf_id_t vrf_id), (proto, instance, vrf_id)); DEFINE_HOOK(lm_client_disconnect, (uint8_t proto, uint16_t instance), (proto, instance)); DEFINE_HOOK(lm_get_chunk, (struct label_manager_chunk * *lmc, uint8_t proto, uint16_t instance, uint8_t keep, uint32_t size, uint32_t base, vrf_id_t vrf_id), (lmc, proto, instance, keep, size, base, vrf_id)); DEFINE_HOOK(lm_release_chunk, (uint8_t proto, uint16_t instance, uint32_t start, uint32_t end), (proto, instance, start, end)); DEFINE_HOOK(lm_cbs_inited, (), ()); /* define wrappers to be called in zapi_msg.c (as hooks must be called in * source file where they were defined) */ void lm_client_connect_call(uint8_t proto, uint16_t instance, vrf_id_t vrf_id) { hook_call(lm_client_connect, proto, instance, vrf_id); } void lm_get_chunk_call(struct label_manager_chunk **lmc, uint8_t proto, uint16_t instance, uint8_t keep, uint32_t size, uint32_t base, vrf_id_t vrf_id) { hook_call(lm_get_chunk, lmc, proto, instance, keep, size, base, vrf_id); } void lm_release_chunk_call(uint8_t proto, uint16_t instance, uint32_t start, uint32_t end) { hook_call(lm_release_chunk, proto, instance, start, end); } /* forward declarations of the static functions to be used for some hooks */ static int label_manager_connect(uint8_t proto, uint16_t instance, vrf_id_t vrf_id); static int label_manager_disconnect(uint8_t proto, uint16_t instance); static int label_manager_get_chunk(struct label_manager_chunk **lmc, uint8_t proto, uint16_t instance, uint8_t keep, uint32_t size, uint32_t base, vrf_id_t vrf_id); void delete_label_chunk(void *val) { XFREE(MTYPE_LM_CHUNK, val); } /** * Release label chunks from a client. * * Called on client disconnection or reconnection. It only releases chunks * with empty keep value. * * @param proto Daemon protocol of client, to identify the owner * @param instance Instance, to identify the owner * @return Number of chunks released */ int release_daemon_label_chunks(uint8_t proto, unsigned short instance) { struct listnode *node; struct label_manager_chunk *lmc; int count = 0; int ret; if (IS_ZEBRA_DEBUG_PACKET) zlog_debug("%s: Releasing chunks for client proto %s, instance %d", __func__, zebra_route_string(proto), instance); for (ALL_LIST_ELEMENTS_RO(lbl_mgr.lc_list, node, lmc)) { if (lmc->proto == proto && lmc->instance == instance && lmc->keep == 0) { ret = release_label_chunk(lmc->proto, lmc->instance, lmc->start, lmc->end); if (ret == 0) count++; } } if (IS_ZEBRA_DEBUG_PACKET) zlog_debug("%s: Released %d label chunks", __func__, count); return count; } int lm_client_disconnect_cb(struct zserv *client) { uint8_t proto = client->proto; uint16_t instance = client->instance; hook_call(lm_client_disconnect, proto, instance); return 0; } void lm_hooks_register(void) { hook_register(lm_client_connect, label_manager_connect); hook_register(lm_client_disconnect, label_manager_disconnect); hook_register(lm_get_chunk, label_manager_get_chunk); hook_register(lm_release_chunk, release_label_chunk); } void lm_hooks_unregister(void) { hook_unregister(lm_client_connect, label_manager_connect); hook_unregister(lm_client_disconnect, label_manager_disconnect); hook_unregister(lm_get_chunk, label_manager_get_chunk); hook_unregister(lm_release_chunk, release_label_chunk); } /** * Init label manager (or proxy to an external one) */ void label_manager_init(void) { lbl_mgr.lc_list = list_new(); lbl_mgr.lc_list->del = delete_label_chunk; hook_register(zserv_client_close, lm_client_disconnect_cb); /* register default hooks for the label manager actions */ lm_hooks_register(); /* notify any external module that we are done */ hook_call(lm_cbs_inited); } /* alloc and fill a label chunk */ struct label_manager_chunk *create_label_chunk(uint8_t proto, unsigned short instance, uint8_t keep, uint32_t start, uint32_t end) { /* alloc chunk, fill it and return it */ struct label_manager_chunk *lmc = XCALLOC(MTYPE_LM_CHUNK, sizeof(struct label_manager_chunk)); lmc->start = start; lmc->end = end; lmc->proto = proto; lmc->instance = instance; lmc->keep = keep; return lmc; } /* attempt to get a specific label chunk */ static struct label_manager_chunk * assign_specific_label_chunk(uint8_t proto, unsigned short instance, uint8_t keep, uint32_t size, uint32_t base) { struct label_manager_chunk *lmc; struct listnode *node, *next = NULL; struct listnode *first_node = NULL; struct listnode *last_node = NULL; struct listnode *insert_node = NULL; /* precompute last label from base and size */ uint32_t end = base + size - 1; /* sanities */ if ((base < MPLS_LABEL_UNRESERVED_MIN) || (end > MPLS_LABEL_UNRESERVED_MAX)) { zlog_err("Invalid LM request arguments: base: %u, size: %u", base, size); return NULL; } /* Scan the existing chunks to see if the requested range of labels * falls inside any of such chunks */ for (ALL_LIST_ELEMENTS_RO(lbl_mgr.lc_list, node, lmc)) { /* skip chunks for labels < base */ if (base > lmc->end) continue; /* requested range is not covered by any existing, free chunk. * Therefore, need to insert a chunk */ if ((end < lmc->start) && !first_node) { insert_node = node; break; } if (!first_node) first_node = node; /* if chunk is used, cannot honor request */ if (lmc->proto != NO_PROTO) return NULL; if (end < lmc->end) { last_node = node; break; } } /* insert chunk between existing chunks */ if (insert_node) { lmc = create_label_chunk(proto, instance, keep, base, end); listnode_add_before(lbl_mgr.lc_list, insert_node, lmc); return lmc; } if (first_node) { /* get node past the last one, if there */ if (last_node) last_node = listnextnode(last_node); /* delete node coming after the above chunk whose labels are * included in the previous one */ for (node = first_node; node && (node != last_node); node = next) { next = listnextnode(node); list_delete_node(lbl_mgr.lc_list, node); } lmc = create_label_chunk(proto, instance, keep, base, end); if (last_node) listnode_add_before(lbl_mgr.lc_list, last_node, lmc); else listnode_add(lbl_mgr.lc_list, lmc); return lmc; } else { /* create a new chunk past all the existing ones and link at * tail */ lmc = create_label_chunk(proto, instance, keep, base, end); listnode_add(lbl_mgr.lc_list, lmc); return lmc; } } /** * Core function, assigns label chunks * * It first searches through the list to check if there's one available * (previously released). Otherwise it creates and assigns a new one * * @param proto Daemon protocol of client, to identify the owner * @param instance Instance, to identify the owner * @param keep If set, avoid garbage collection * @param size Size of the label chunk * @param base Desired starting label of the chunk; if MPLS_LABEL_BASE_ANY it does not apply * @return Pointer to the assigned label chunk, or NULL if the request could not be satisfied */ struct label_manager_chunk *assign_label_chunk(uint8_t proto, unsigned short instance, uint8_t keep, uint32_t size, uint32_t base) { struct label_manager_chunk *lmc; struct listnode *node; uint32_t prev_end = 0; /* handle chunks request with a specific base label */ if (base != MPLS_LABEL_BASE_ANY) return assign_specific_label_chunk(proto, instance, keep, size, base); /* appease scan-build, who gets confused by the use of macros */ assert(lbl_mgr.lc_list); /* first check if there's one available */ for (ALL_LIST_ELEMENTS_RO(lbl_mgr.lc_list, node, lmc)) { if (lmc->proto == NO_PROTO && lmc->end - lmc->start + 1 == size) { lmc->proto = proto; lmc->instance = instance; lmc->keep = keep; return lmc; } /* check if we hadve a "hole" behind us that we can squeeze into */ if ((lmc->start > prev_end) && (lmc->start - prev_end >= size)) { lmc = create_label_chunk(proto, instance, keep, prev_end + 1, prev_end + size); listnode_add_before(lbl_mgr.lc_list, node, lmc); return lmc; } prev_end = lmc->end; } /* otherwise create a new one */ uint32_t start_free; if (list_isempty(lbl_mgr.lc_list)) start_free = MPLS_LABEL_UNRESERVED_MIN; else start_free = ((struct label_manager_chunk *)listgetdata( listtail(lbl_mgr.lc_list))) ->end + 1; if (start_free > MPLS_LABEL_UNRESERVED_MAX - size + 1) { flog_err(EC_ZEBRA_LM_EXHAUSTED_LABELS, "Reached max labels. Start: %u, size: %u", start_free, size); return NULL; } /* create chunk and link at tail */ lmc = create_label_chunk(proto, instance, keep, start_free, start_free + size - 1); listnode_add(lbl_mgr.lc_list, lmc); return lmc; } /** * Core function, release no longer used label chunks * * @param proto Daemon protocol of client, to identify the owner * @param instance Instance, to identify the owner * @param start First label of the chunk * @param end Last label of the chunk * @return 0 on success, -1 otherwise */ int release_label_chunk(uint8_t proto, unsigned short instance, uint32_t start, uint32_t end) { struct listnode *node; struct label_manager_chunk *lmc; int ret = -1; /* check that size matches */ if (IS_ZEBRA_DEBUG_PACKET) zlog_debug("Releasing label chunk: %u - %u", start, end); /* find chunk and disown */ for (ALL_LIST_ELEMENTS_RO(lbl_mgr.lc_list, node, lmc)) { if (lmc->start != start) continue; if (lmc->end != end) continue; if (lmc->proto != proto || lmc->instance != instance) { flog_err(EC_ZEBRA_LM_DAEMON_MISMATCH, "%s: Daemon mismatch!!", __func__); continue; } lmc->proto = NO_PROTO; lmc->instance = 0; lmc->keep = 0; ret = 0; break; } if (ret != 0) flog_err(EC_ZEBRA_LM_UNRELEASED_CHUNK, "%s: Label chunk not released!!", __func__); return ret; } /* default functions to be called on hooks */ static int label_manager_connect(uint8_t proto, uint16_t instance, vrf_id_t vrf_id) { /* * Release previous labels of same protocol and instance. * This is done in case it restarted from an unexpected shutdown. */ release_daemon_label_chunks(proto, instance); return lm_client_connect_response(proto, instance, vrf_id, 0); } static int label_manager_disconnect(uint8_t proto, uint16_t instance) { release_daemon_label_chunks(proto, instance); return 0; } static int label_manager_get_chunk(struct label_manager_chunk **lmc, uint8_t proto, uint16_t instance, uint8_t keep, uint32_t size, uint32_t base, vrf_id_t vrf_id) { *lmc = assign_label_chunk(proto, instance, keep, size, base); return lm_get_chunk_response(*lmc, proto, instance, vrf_id); } /* Respond to a connect request */ int lm_client_connect_response(uint8_t proto, uint16_t instance, vrf_id_t vrf_id, uint8_t result) { struct zserv *client = zserv_find_client(proto, instance); if (!client) { zlog_err("%s: could not find client for daemon %s instance %u", __func__, zebra_route_string(proto), instance); return 1; } return zsend_label_manager_connect_response(client, vrf_id, result); } /* Respond to a get_chunk request */ int lm_get_chunk_response(struct label_manager_chunk *lmc, uint8_t proto, uint16_t instance, vrf_id_t vrf_id) { struct zserv *client = zserv_find_client(proto, instance); if (!client) { zlog_err("%s: could not find client for daemon %s instance %u", __func__, zebra_route_string(proto), instance); return 1; } return zsend_assign_label_chunk_response(client, vrf_id, proto, instance, lmc); } void label_manager_close(void) { list_delete(&lbl_mgr.lc_list); } frr-7.2.1/zebra/label_manager.h0000644000000000000000000001070013610377563013237 00000000000000/* * Label Manager header * * Copyright (C) 2017 by Bingen Eguzkitza, * Volta Networks Inc. * * This file is part of FreeRangeRouting (FRR) * * FRR is free software; you can redistribute it and/or modify it * under the terms of the GNU General Public License as published by the * Free Software Foundation; either version 2, or (at your option) any * later version. * * FRR 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; see the file COPYING; if not, write to the Free Software * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA */ #ifndef _LABEL_MANAGER_H #define _LABEL_MANAGER_H #include #include "lib/linklist.h" #include "lib/thread.h" #include "lib/hook.h" #include "zebra/zserv.h" #ifdef __cplusplus extern "C" { #endif #define NO_PROTO 0 /* * Label chunk struct * Client daemon which the chunk belongs to can be identified by either * proto (daemon protocol) + instance. * If the client then passes a non-empty value to keep field when it requests * for chunks, the chunks won't be garbage collected and the client will be * responsible of its release. * Otherwise, if the keep field is not set (value 0) for the chunk, it will be * automatically released when the client disconnects or when it reconnects * (in case it died unexpectedly, we can know it's the same because it will have * the same proto and instance values) */ struct label_manager_chunk { uint8_t proto; unsigned short instance; uint8_t keep; uint32_t start; /* First label of the chunk */ uint32_t end; /* Last label of the chunk */ }; /* declare hooks for the basic API, so that it can be specialized or served * externally. Also declare a hook when those functions have been registered, * so that any external module wanting to replace those can react */ DECLARE_HOOK(lm_client_connect, (uint8_t proto, uint16_t instance, vrf_id_t vrf_id), (proto, instance, vrf_id)); DECLARE_HOOK(lm_client_disconnect, (uint8_t proto, uint16_t instance), (proto, instance)); DECLARE_HOOK(lm_get_chunk, (struct label_manager_chunk * *lmc, uint8_t proto, uint16_t instance, uint8_t keep, uint32_t size, uint32_t base, vrf_id_t vrf_id), (lmc, proto, instance, keep, size, base, vrf_id)); DECLARE_HOOK(lm_release_chunk, (uint8_t proto, uint16_t instance, uint32_t start, uint32_t end), (proto, instance, start, end)); DECLARE_HOOK(lm_cbs_inited, (), ()); /* declare wrappers to be called in zapi_msg.c (as hooks must be called in * source file where they were defined) */ void lm_client_connect_call(uint8_t proto, uint16_t instance, vrf_id_t vrf_id); void lm_get_chunk_call(struct label_manager_chunk **lmc, uint8_t proto, uint16_t instance, uint8_t keep, uint32_t size, uint32_t base, vrf_id_t vrf_id); void lm_release_chunk_call(uint8_t proto, uint16_t instance, uint32_t start, uint32_t end); /* API for an external LM to return responses for requests */ int lm_client_connect_response(uint8_t proto, uint16_t instance, vrf_id_t vrf_id, uint8_t result); int lm_get_chunk_response(struct label_manager_chunk *lmc, uint8_t proto, uint16_t instance, vrf_id_t vrf_id); /* convenience function to allocate an lmc to be consumed by the above API */ struct label_manager_chunk *create_label_chunk(uint8_t proto, unsigned short instance, uint8_t keep, uint32_t start, uint32_t end); void delete_label_chunk(void *val); /* register/unregister callbacks for hooks */ void lm_hooks_register(void); void lm_hooks_unregister(void); /* * Main label manager struct * Holds a linked list of label chunks. */ struct label_manager { struct list *lc_list; }; void label_manager_init(void); struct label_manager_chunk *assign_label_chunk(uint8_t proto, unsigned short instance, uint8_t keep, uint32_t size, uint32_t base); int release_label_chunk(uint8_t proto, unsigned short instance, uint32_t start, uint32_t end); int lm_client_disconnect_cb(struct zserv *client); int release_daemon_label_chunks(uint8_t proto, unsigned short instance); void label_manager_close(void); #ifdef __cplusplus } #endif #endif /* _LABEL_MANAGER_H */ frr-7.2.1/zebra/main.c0000644000000000000000000002626213610377563011417 00000000000000/* zebra daemon main routine. * Copyright (C) 1997, 98 Kunihiro Ishiguro * * This file is part of GNU Zebra. * * GNU Zebra is free software; you can redistribute it and/or modify it * under the terms of the GNU General Public License as published by the * Free Software Foundation; either version 2, or (at your option) any * later version. * * GNU Zebra 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; see the file COPYING; if not, write to the Free Software * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA */ #include #include #include "getopt.h" #include "command.h" #include "thread.h" #include "filter.h" #include "memory.h" #include "zebra_memory.h" #include "memory_vty.h" #include "prefix.h" #include "log.h" #include "plist.h" #include "privs.h" #include "sigevent.h" #include "vrf.h" #include "libfrr.h" #include "routemap.h" #include "frr_pthread.h" #include "zebra/zebra_router.h" #include "zebra/zebra_errors.h" #include "zebra/rib.h" #include "zebra/zserv.h" #include "zebra/debug.h" #include "zebra/router-id.h" #include "zebra/irdp.h" #include "zebra/rtadv.h" #include "zebra/zebra_ptm.h" #include "zebra/zebra_ns.h" #include "zebra/redistribute.h" #include "zebra/zebra_mpls.h" #include "zebra/label_manager.h" #include "zebra/zebra_netns_notify.h" #include "zebra/zebra_rnh.h" #include "zebra/zebra_pbr.h" #include "zebra/zebra_vxlan.h" #if defined(HANDLE_NETLINK_FUZZING) #include "zebra/kernel_netlink.h" #endif /* HANDLE_NETLINK_FUZZING */ #define ZEBRA_PTM_SUPPORT /* process id. */ pid_t pid; /* Pacify zclient.o in libfrr, which expects this variable. */ struct thread_master *master; /* Route retain mode flag. */ int retain_mode = 0; /* Allow non-quagga entities to delete quagga routes */ int allow_delete = 0; int graceful_restart; bool v6_rr_semantics = false; #ifdef HAVE_NETLINK /* Receive buffer size for netlink socket */ uint32_t nl_rcvbufsize = 4194304; #endif /* HAVE_NETLINK */ #define OPTION_V6_RR_SEMANTICS 2000 /* Command line options. */ struct option longopts[] = { {"batch", no_argument, NULL, 'b'}, {"allow_delete", no_argument, NULL, 'a'}, {"keep_kernel", no_argument, NULL, 'k'}, {"socket", required_argument, NULL, 'z'}, {"ecmp", required_argument, NULL, 'e'}, {"retain", no_argument, NULL, 'r'}, {"vrfdefaultname", required_argument, NULL, 'o'}, {"graceful_restart", required_argument, NULL, 'K'}, #ifdef HAVE_NETLINK {"vrfwnetns", no_argument, NULL, 'n'}, {"nl-bufsize", required_argument, NULL, 's'}, {"v6-rr-semantics", no_argument, NULL, OPTION_V6_RR_SEMANTICS}, #endif /* HAVE_NETLINK */ {0}}; zebra_capabilities_t _caps_p[] = { ZCAP_NET_ADMIN, ZCAP_SYS_ADMIN, ZCAP_NET_RAW, }; /* zebra privileges to run with */ struct zebra_privs_t zserv_privs = { #if defined(FRR_USER) && defined(FRR_GROUP) .user = FRR_USER, .group = FRR_GROUP, #endif #ifdef VTY_GROUP .vty_group = VTY_GROUP, #endif .caps_p = _caps_p, .cap_num_p = array_size(_caps_p), .cap_num_i = 0}; /* SIGHUP handler. */ static void sighup(void) { zlog_info("SIGHUP received"); /* Reload of config file. */ ; } /* SIGINT handler. */ static void sigint(void) { struct vrf *vrf; struct zebra_vrf *zvrf; struct listnode *ln, *nn; struct zserv *client; static bool sigint_done; if (sigint_done) return; sigint_done = true; zlog_notice("Terminating on signal"); frr_early_fini(); zebra_dplane_pre_finish(); for (ALL_LIST_ELEMENTS(zrouter.client_list, ln, nn, client)) zserv_close_client(client); zserv_close(); list_delete_all_node(zrouter.client_list); zebra_ptm_finish(); if (retain_mode) RB_FOREACH (vrf, vrf_name_head, &vrfs_by_name) { zvrf = vrf->info; if (zvrf) SET_FLAG(zvrf->flags, ZEBRA_VRF_RETAIN); } if (zrouter.lsp_process_q) work_queue_free_and_null(&zrouter.lsp_process_q); vrf_terminate(); ns_walk_func(zebra_ns_early_shutdown); zebra_ns_notify_close(); access_list_reset(); prefix_list_reset(); route_map_finish(); list_delete(&zrouter.client_list); /* Indicate that all new dplane work has been enqueued. When that * work is complete, the dataplane will enqueue an event * with the 'finalize' function. */ zebra_dplane_finish(); } /* * Final shutdown step for the zebra main thread. This is run after all * async update processing has completed. */ int zebra_finalize(struct thread *dummy) { zlog_info("Zebra final shutdown"); /* Final shutdown of ns resources */ ns_walk_func(zebra_ns_final_shutdown); /* Stop dplane thread and finish any cleanup */ zebra_dplane_shutdown(); zebra_router_terminate(); frr_fini(); exit(0); } /* SIGUSR1 handler. */ static void sigusr1(void) { zlog_rotate(); } struct quagga_signal_t zebra_signals[] = { { .signal = SIGHUP, .handler = &sighup, }, { .signal = SIGUSR1, .handler = &sigusr1, }, { .signal = SIGINT, .handler = &sigint, }, { .signal = SIGTERM, .handler = &sigint, }, }; static const struct frr_yang_module_info *zebra_yang_modules[] = { &frr_interface_info, }; FRR_DAEMON_INFO( zebra, ZEBRA, .vty_port = ZEBRA_VTY_PORT, .flags = FRR_NO_ZCLIENT, .proghelp = "Daemon which manages kernel routing table management " "and\nredistribution between different routing protocols.", .signals = zebra_signals, .n_signals = array_size(zebra_signals), .privs = &zserv_privs, .yang_modules = zebra_yang_modules, .n_yang_modules = array_size(zebra_yang_modules), ) /* Main startup routine. */ int main(int argc, char **argv) { // int batch_mode = 0; char *zserv_path = NULL; char *vrf_default_name_configured = NULL; struct sockaddr_storage dummy; socklen_t dummylen; #if defined(HANDLE_ZAPI_FUZZING) char *zapi_fuzzing = NULL; #endif /* HANDLE_ZAPI_FUZZING */ #if defined(HANDLE_NETLINK_FUZZING) char *netlink_fuzzing = NULL; #endif /* HANDLE_NETLINK_FUZZING */ graceful_restart = 0; vrf_configure_backend(VRF_BACKEND_VRF_LITE); frr_preinit(&zebra_di, argc, argv); frr_opt_add( "baz:e:o:rK:" #ifdef HAVE_NETLINK "s:n" #endif #if defined(HANDLE_ZAPI_FUZZING) "c:" #endif /* HANDLE_ZAPI_FUZZING */ #if defined(HANDLE_NETLINK_FUZZING) "w:" #endif /* HANDLE_NETLINK_FUZZING */ , longopts, " -b, --batch Runs in batch mode\n" " -a, --allow_delete Allow other processes to delete zebra routes\n" " -z, --socket Set path of zebra socket\n" " -e, --ecmp Specify ECMP to use.\n" " -r, --retain When program terminates, retain added route by zebra.\n" " -o, --vrfdefaultname Set default VRF name.\n" " -K, --graceful_restart Graceful restart at the kernel level, timer in seconds for expiration\n" #ifdef HAVE_NETLINK " -n, --vrfwnetns Use NetNS as VRF backend\n" " -s, --nl-bufsize Set netlink receive buffer size\n" " --v6-rr-semantics Use v6 RR semantics\n" #endif /* HAVE_NETLINK */ #if defined(HANDLE_ZAPI_FUZZING) " -c Bypass normal startup and use this file for testing of zapi\n" #endif /* HANDLE_ZAPI_FUZZING */ #if defined(HANDLE_NETLINK_FUZZING) " -w Bypass normal startup and use this file for testing of netlink input\n" #endif /* HANDLE_NETLINK_FUZZING */ ); while (1) { int opt = frr_getopt(argc, argv, NULL); if (opt == EOF) break; switch (opt) { case 0: break; case 'b': // batch_mode = 1; break; case 'a': allow_delete = 1; break; case 'e': zrouter.multipath_num = atoi(optarg); if (zrouter.multipath_num > MULTIPATH_NUM || zrouter.multipath_num <= 0) { flog_err( EC_ZEBRA_BAD_MULTIPATH_NUM, "Multipath Number specified must be less than %d and greater than 0", MULTIPATH_NUM); return 1; } break; case 'o': vrf_default_name_configured = optarg; break; case 'z': zserv_path = optarg; if (!frr_zclient_addr(&dummy, &dummylen, optarg)) { fprintf(stderr, "Invalid zserv socket path: %s\n", optarg); exit(1); } break; case 'r': retain_mode = 1; break; case 'K': graceful_restart = atoi(optarg); break; #ifdef HAVE_NETLINK case 's': nl_rcvbufsize = atoi(optarg); break; case 'n': vrf_configure_backend(VRF_BACKEND_NETNS); break; case OPTION_V6_RR_SEMANTICS: v6_rr_semantics = true; break; #endif /* HAVE_NETLINK */ #if defined(HANDLE_ZAPI_FUZZING) case 'c': zapi_fuzzing = optarg; break; #endif /* HANDLE_ZAPI_FUZZING */ #if defined(HANDLE_NETLINK_FUZZING) case 'w': netlink_fuzzing = optarg; /* This ensures we are aren't writing any of the * startup netlink messages that happen when we * just want to read. */ netlink_read = true; break; #endif /* HANDLE_NETLINK_FUZZING */ default: frr_help_exit(1); break; } } zrouter.master = frr_init(); /* Initialize pthread library */ frr_pthread_init(); /* Zebra related initialize. */ zebra_router_init(); zserv_init(); rib_init(); zebra_if_init(); zebra_debug_init(); router_id_cmd_init(); /* * Initialize NS( and implicitly the VRF module), and make kernel * routing socket. */ zebra_ns_init((const char *)vrf_default_name_configured); zebra_vty_init(); access_list_init(); prefix_list_init(); #if defined(HAVE_RTADV) rtadv_cmd_init(); #endif /* PTM socket */ #ifdef ZEBRA_PTM_SUPPORT zebra_ptm_init(); #endif zebra_mpls_init(); zebra_mpls_vty_init(); zebra_pw_vty_init(); zebra_pbr_init(); /* For debug purpose. */ /* SET_FLAG (zebra_debug_event, ZEBRA_DEBUG_EVENT); */ /* Process the configuration file. Among other configuration * directives we can meet those installing static routes. Such * requests will not be executed immediately, but queued in * zebra->ribq structure until we enter the main execution loop. * The notifications from kernel will show originating PID equal * to that after daemon() completes (if ever called). */ frr_config_fork(); /* After we have successfully acquired the pidfile, we can be sure * about being the only copy of zebra process, which is submitting * changes to the FIB. * Clean up zebra-originated routes. The requests will be sent to OS * immediately, so originating PID in notifications from kernel * will be equal to the current getpid(). To know about such routes, * we have to have route_read() called before. */ zrouter.startup_time = monotime(NULL); thread_add_timer(zrouter.master, rib_sweep_route, NULL, graceful_restart, NULL); /* Needed for BSD routing socket. */ pid = getpid(); /* Start dataplane system */ zebra_dplane_start(); /* Start Zebra API server */ zserv_start(zserv_path); /* Init label manager */ label_manager_init(); /* RNH init */ zebra_rnh_init(); /* Config handler Init */ zebra_evpn_init(); /* Error init */ zebra_error_init(); #if defined(HANDLE_ZAPI_FUZZING) if (zapi_fuzzing) { zserv_read_file(zapi_fuzzing); exit(0); } #endif /* HANDLE_ZAPI_FUZZING */ #if defined(HANDLE_NETLINK_FUZZING) if (netlink_fuzzing) { netlink_read_init(netlink_fuzzing); exit(0); } #endif /* HANDLE_NETLINK_FUZZING */ frr_run(zrouter.master); /* Not reached... */ return 0; } frr-7.2.1/zebra/redistribute.c0000644000000000000000000005644513610377563013206 00000000000000/* Redistribution Handler * Copyright (C) 1998 Kunihiro Ishiguro * * This file is part of GNU Zebra. * * GNU Zebra is free software; you can redistribute it and/or modify it * under the terms of the GNU General Public License as published by the * Free Software Foundation; either version 2, or (at your option) any * later version. * * GNU Zebra 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; see the file COPYING; if not, write to the Free Software * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA */ #include #include "vector.h" #include "vty.h" #include "command.h" #include "prefix.h" #include "table.h" #include "stream.h" #include "zclient.h" #include "linklist.h" #include "log.h" #include "vrf.h" #include "srcdest_table.h" #include "zebra/rib.h" #include "zebra/zebra_router.h" #include "zebra/zebra_ns.h" #include "zebra/zebra_vrf.h" #include "zebra/zebra_routemap.h" #include "zebra/redistribute.h" #include "zebra/debug.h" #include "zebra/router-id.h" #include "zebra/zapi_msg.h" #include "zebra/zebra_memory.h" #include "zebra/zebra_vxlan.h" #include "zebra/zebra_errors.h" #define ZEBRA_PTM_SUPPORT /* array holding redistribute info about table redistribution */ /* bit AFI is set if that AFI is redistributing routes from this table */ static int zebra_import_table_used[AFI_MAX][ZEBRA_KERNEL_TABLE_MAX]; static uint32_t zebra_import_table_distance[AFI_MAX][ZEBRA_KERNEL_TABLE_MAX]; int is_zebra_import_table_enabled(afi_t afi, vrf_id_t vrf_id, uint32_t table_id) { /* * Make sure that what we are called with actualy makes sense */ if (afi == AFI_MAX) return 0; if (is_zebra_valid_kernel_table(table_id) && table_id < ZEBRA_KERNEL_TABLE_MAX) return zebra_import_table_used[afi][table_id]; return 0; } static void zebra_redistribute_default(struct zserv *client, vrf_id_t vrf_id) { int afi; struct prefix p; struct route_table *table; struct route_node *rn; struct route_entry *newre; for (afi = AFI_IP; afi <= AFI_IP6; afi++) { /* Lookup table. */ table = zebra_vrf_table(afi, SAFI_UNICAST, vrf_id); if (!table) continue; /* Lookup default route. */ memset(&p, 0, sizeof(p)); p.family = afi2family(afi); rn = route_node_lookup(table, &p); if (!rn) continue; RNODE_FOREACH_RE (rn, newre) { if (CHECK_FLAG(newre->flags, ZEBRA_FLAG_SELECTED) && newre->distance != DISTANCE_INFINITY) zsend_redistribute_route( ZEBRA_REDISTRIBUTE_ROUTE_ADD, client, &rn->p, NULL, newre); } route_unlock_node(rn); } } /* Redistribute routes. */ static void zebra_redistribute(struct zserv *client, int type, unsigned short instance, vrf_id_t vrf_id, int afi) { struct route_entry *newre; struct route_table *table; struct route_node *rn; table = zebra_vrf_table(afi, SAFI_UNICAST, vrf_id); if (!table) return; for (rn = route_top(table); rn; rn = srcdest_route_next(rn)) RNODE_FOREACH_RE (rn, newre) { const struct prefix *dst_p, *src_p; char buf[PREFIX_STRLEN]; srcdest_rnode_prefixes(rn, &dst_p, &src_p); if (IS_ZEBRA_DEBUG_EVENT) zlog_debug( "%s: client %s %s(%u) checking: selected=%d, type=%d, distance=%d, metric=%d zebra_check_addr=%d", __func__, zebra_route_string(client->proto), prefix2str(dst_p, buf, sizeof(buf)), vrf_id, CHECK_FLAG(newre->flags, ZEBRA_FLAG_SELECTED), newre->type, newre->distance, newre->metric, zebra_check_addr(dst_p)); if (!CHECK_FLAG(newre->flags, ZEBRA_FLAG_SELECTED)) continue; if ((type != ZEBRA_ROUTE_ALL && (newre->type != type || newre->instance != instance))) continue; if (newre->distance == DISTANCE_INFINITY) continue; if (!zebra_check_addr(dst_p)) continue; zsend_redistribute_route(ZEBRA_REDISTRIBUTE_ROUTE_ADD, client, dst_p, src_p, newre); } } /* Either advertise a route for redistribution to registered clients or */ /* withdraw redistribution if add cannot be done for client */ void redistribute_update(const struct prefix *p, const struct prefix *src_p, const struct route_entry *re, const struct route_entry *prev_re) { struct listnode *node, *nnode; struct zserv *client; int send_redistribute; int afi; char buf[PREFIX_STRLEN]; if (IS_ZEBRA_DEBUG_RIB) { zlog_debug( "%u:%s: Redist update re %p (%s), old %p (%s)", re->vrf_id, prefix2str(p, buf, sizeof(buf)), re, zebra_route_string(re->type), prev_re, prev_re ? zebra_route_string(prev_re->type) : "None"); } afi = family2afi(p->family); if (!afi) { flog_warn(EC_ZEBRA_REDISTRIBUTE_UNKNOWN_AF, "%s: Unknown AFI/SAFI prefix received\n", __FUNCTION__); return; } if (!zebra_check_addr(p)) { if (IS_ZEBRA_DEBUG_RIB) zlog_debug("Redist update filter prefix %s", prefix2str(p, buf, sizeof(buf))); return; } for (ALL_LIST_ELEMENTS(zrouter.client_list, node, nnode, client)) { send_redistribute = 0; if (is_default_prefix(p) && vrf_bitmap_check(client->redist_default[afi], re->vrf_id)) send_redistribute = 1; else if (vrf_bitmap_check(client->redist[afi][ZEBRA_ROUTE_ALL], re->vrf_id)) send_redistribute = 1; else if (re->instance && redist_check_instance( &client->mi_redist[afi][re->type], re->instance)) send_redistribute = 1; else if (vrf_bitmap_check(client->redist[afi][re->type], re->vrf_id)) send_redistribute = 1; if (send_redistribute) { if (IS_ZEBRA_DEBUG_EVENT) { zlog_debug( "%s: client %s %s(%u), type=%d, distance=%d, metric=%d", __func__, zebra_route_string(client->proto), prefix2str(p, buf, sizeof(buf)), re->vrf_id, re->type, re->distance, re->metric); } zsend_redistribute_route(ZEBRA_REDISTRIBUTE_ROUTE_ADD, client, p, src_p, re); } else if (prev_re && ((re->instance && redist_check_instance( &client->mi_redist[afi] [prev_re->type], re->instance)) || vrf_bitmap_check( client->redist[afi][prev_re->type], re->vrf_id))) { zsend_redistribute_route(ZEBRA_REDISTRIBUTE_ROUTE_DEL, client, p, src_p, prev_re); } } } /* * During a route delete, where 'new_re' is NULL, redist a delete to all * clients registered for the type of 'old_re'. * During a route update, redist a delete to any clients who will not see * an update when the new route is installed. There are cases when a client * may have seen a redist for 'old_re', but will not see * the redist for 'new_re'. */ void redistribute_delete(const struct prefix *p, const struct prefix *src_p, const struct route_entry *old_re, const struct route_entry *new_re) { struct listnode *node, *nnode; struct zserv *client; int afi; char buf[PREFIX_STRLEN]; vrf_id_t vrfid; if (old_re) vrfid = old_re->vrf_id; else if (new_re) vrfid = new_re->vrf_id; else return; if (IS_ZEBRA_DEBUG_RIB) { zlog_debug( "%u:%s: Redist del: re %p (%s), new re %p (%s)", vrfid, prefix2str(p, buf, sizeof(buf)), old_re, old_re ? zebra_route_string(old_re->type) : "None", new_re, new_re ? zebra_route_string(new_re->type) : "None"); } /* Add DISTANCE_INFINITY check. */ if (old_re && (old_re->distance == DISTANCE_INFINITY)) { if (IS_ZEBRA_DEBUG_RIB) zlog_debug("\tSkipping due to Infinite Distance"); return; } afi = family2afi(p->family); if (!afi) { flog_warn(EC_ZEBRA_REDISTRIBUTE_UNKNOWN_AF, "%s: Unknown AFI/SAFI prefix received\n", __func__); return; } /* Skip invalid (e.g. linklocal) prefix */ if (!zebra_check_addr(p)) { if (IS_ZEBRA_DEBUG_RIB) { zlog_debug( "%u:%s: Redist del old: skipping invalid prefix", vrfid, prefix2str(p, buf, sizeof(buf))); } return; } for (ALL_LIST_ELEMENTS(zrouter.client_list, node, nnode, client)) { if (new_re) { /* Skip this client if it will receive an update for the * 'new' re */ if (is_default_prefix(p) && vrf_bitmap_check(client->redist_default[afi], new_re->vrf_id)) continue; else if (vrf_bitmap_check( client->redist[afi][ZEBRA_ROUTE_ALL], new_re->vrf_id)) continue; else if (new_re->instance && redist_check_instance( &client->mi_redist[afi][new_re->type], new_re->instance)) continue; else if (vrf_bitmap_check( client->redist[afi][new_re->type], new_re->vrf_id)) continue; } /* Send a delete for the 'old' re to any subscribed client. */ if (old_re && (vrf_bitmap_check(client->redist[afi][ZEBRA_ROUTE_ALL], old_re->vrf_id) || (old_re->instance && redist_check_instance( &client->mi_redist[afi][old_re->type], old_re->instance)) || vrf_bitmap_check(client->redist[afi][old_re->type], old_re->vrf_id))) { zsend_redistribute_route(ZEBRA_REDISTRIBUTE_ROUTE_DEL, client, p, src_p, old_re); } } } void zebra_redistribute_add(ZAPI_HANDLER_ARGS) { afi_t afi = 0; int type = 0; unsigned short instance; STREAM_GETC(msg, afi); STREAM_GETC(msg, type); STREAM_GETW(msg, instance); if (IS_ZEBRA_DEBUG_EVENT) zlog_debug( "%s: client proto %s afi=%d, wants %s, vrf %u, instance=%d", __func__, zebra_route_string(client->proto), afi, zebra_route_string(type), zvrf_id(zvrf), instance); if (afi == 0 || afi >= AFI_MAX) { flog_warn(EC_ZEBRA_REDISTRIBUTE_UNKNOWN_AF, "%s: Specified afi %d does not exist", __PRETTY_FUNCTION__, afi); return; } if (type == 0 || type >= ZEBRA_ROUTE_MAX) { zlog_debug("%s: Specified Route Type %d does not exist", __PRETTY_FUNCTION__, type); return; } if (instance) { if (!redist_check_instance(&client->mi_redist[afi][type], instance)) { redist_add_instance(&client->mi_redist[afi][type], instance); zebra_redistribute(client, type, instance, zvrf_id(zvrf), afi); } } else { if (!vrf_bitmap_check(client->redist[afi][type], zvrf_id(zvrf))) { if (IS_ZEBRA_DEBUG_EVENT) zlog_debug("%s: setting vrf %u redist bitmap", __func__, zvrf_id(zvrf)); vrf_bitmap_set(client->redist[afi][type], zvrf_id(zvrf)); zebra_redistribute(client, type, 0, zvrf_id(zvrf), afi); } } stream_failure: return; } void zebra_redistribute_delete(ZAPI_HANDLER_ARGS) { afi_t afi = 0; int type = 0; unsigned short instance; STREAM_GETC(msg, afi); STREAM_GETC(msg, type); STREAM_GETW(msg, instance); if (afi == 0 || afi >= AFI_MAX) { flog_warn(EC_ZEBRA_REDISTRIBUTE_UNKNOWN_AF, "%s: Specified afi %d does not exist", __PRETTY_FUNCTION__, afi); return; } if (type == 0 || type >= ZEBRA_ROUTE_MAX) { zlog_debug("%s: Specified Route Type %d does not exist", __PRETTY_FUNCTION__, type); return; } /* * NOTE: no need to withdraw the previously advertised routes. The * clients * themselves should keep track of the received routes from zebra and * withdraw them when necessary. */ if (instance) redist_del_instance(&client->mi_redist[afi][type], instance); else vrf_bitmap_unset(client->redist[afi][type], zvrf_id(zvrf)); stream_failure: return; } void zebra_redistribute_default_add(ZAPI_HANDLER_ARGS) { afi_t afi = 0; STREAM_GETC(msg, afi); if (afi == 0 || afi >= AFI_MAX) { flog_warn(EC_ZEBRA_REDISTRIBUTE_UNKNOWN_AF, "%s: Specified afi %u does not exist", __PRETTY_FUNCTION__, afi); return; } vrf_bitmap_set(client->redist_default[afi], zvrf_id(zvrf)); zebra_redistribute_default(client, zvrf_id(zvrf)); stream_failure: return; } void zebra_redistribute_default_delete(ZAPI_HANDLER_ARGS) { afi_t afi = 0; STREAM_GETC(msg, afi); if (afi == 0 || afi >= AFI_MAX) { flog_warn(EC_ZEBRA_REDISTRIBUTE_UNKNOWN_AF, "%s: Specified afi %u does not exist", __PRETTY_FUNCTION__, afi); return; } vrf_bitmap_unset(client->redist_default[afi], zvrf_id(zvrf)); stream_failure: return; } /* Interface up information. */ void zebra_interface_up_update(struct interface *ifp) { struct listnode *node, *nnode; struct zserv *client; if (IS_ZEBRA_DEBUG_EVENT) zlog_debug("MESSAGE: ZEBRA_INTERFACE_UP %s(%u)", ifp->name, ifp->vrf_id); if (ifp->ptm_status || !ifp->ptm_enable) { for (ALL_LIST_ELEMENTS(zrouter.client_list, node, nnode, client)) { zsend_interface_update(ZEBRA_INTERFACE_UP, client, ifp); zsend_interface_link_params(client, ifp); } } } /* Interface down information. */ void zebra_interface_down_update(struct interface *ifp) { struct listnode *node, *nnode; struct zserv *client; if (IS_ZEBRA_DEBUG_EVENT) zlog_debug("MESSAGE: ZEBRA_INTERFACE_DOWN %s(%u)", ifp->name, ifp->vrf_id); for (ALL_LIST_ELEMENTS(zrouter.client_list, node, nnode, client)) { zsend_interface_update(ZEBRA_INTERFACE_DOWN, client, ifp); } } /* Interface information update. */ void zebra_interface_add_update(struct interface *ifp) { struct listnode *node, *nnode; struct zserv *client; if (IS_ZEBRA_DEBUG_EVENT) zlog_debug("MESSAGE: ZEBRA_INTERFACE_ADD %s(%u)", ifp->name, ifp->vrf_id); for (ALL_LIST_ELEMENTS(zrouter.client_list, node, nnode, client)) { client->ifadd_cnt++; zsend_interface_add(client, ifp); zsend_interface_link_params(client, ifp); } } void zebra_interface_delete_update(struct interface *ifp) { struct listnode *node, *nnode; struct zserv *client; if (IS_ZEBRA_DEBUG_EVENT) zlog_debug("MESSAGE: ZEBRA_INTERFACE_DELETE %s(%u)", ifp->name, ifp->vrf_id); for (ALL_LIST_ELEMENTS(zrouter.client_list, node, nnode, client)) { client->ifdel_cnt++; zsend_interface_delete(client, ifp); } } /* Interface address addition. */ void zebra_interface_address_add_update(struct interface *ifp, struct connected *ifc) { struct listnode *node, *nnode; struct zserv *client; struct prefix *p; if (IS_ZEBRA_DEBUG_EVENT) { char buf[PREFIX_STRLEN]; p = ifc->address; zlog_debug("MESSAGE: ZEBRA_INTERFACE_ADDRESS_ADD %s on %s(%u)", prefix2str(p, buf, sizeof(buf)), ifp->name, ifp->vrf_id); } if (!CHECK_FLAG(ifc->conf, ZEBRA_IFC_REAL)) flog_warn( EC_ZEBRA_ADVERTISING_UNUSABLE_ADDR, "WARNING: advertising address to clients that is not yet usable."); zebra_vxlan_add_del_gw_macip(ifp, ifc->address, 1); router_id_add_address(ifc); for (ALL_LIST_ELEMENTS(zrouter.client_list, node, nnode, client)) if (CHECK_FLAG(ifc->conf, ZEBRA_IFC_REAL)) { client->connected_rt_add_cnt++; zsend_interface_address(ZEBRA_INTERFACE_ADDRESS_ADD, client, ifp, ifc); } } /* Interface address deletion. */ void zebra_interface_address_delete_update(struct interface *ifp, struct connected *ifc) { struct listnode *node, *nnode; struct zserv *client; struct prefix *p; if (IS_ZEBRA_DEBUG_EVENT) { char buf[PREFIX_STRLEN]; p = ifc->address; zlog_debug("MESSAGE: ZEBRA_INTERFACE_ADDRESS_DELETE %s on %s(%u)", prefix2str(p, buf, sizeof(buf)), ifp->name, ifp->vrf_id); } zebra_vxlan_add_del_gw_macip(ifp, ifc->address, 0); router_id_del_address(ifc); for (ALL_LIST_ELEMENTS(zrouter.client_list, node, nnode, client)) if (CHECK_FLAG(ifc->conf, ZEBRA_IFC_REAL)) { client->connected_rt_del_cnt++; zsend_interface_address(ZEBRA_INTERFACE_ADDRESS_DELETE, client, ifp, ifc); } } /* Interface VRF change. May need to delete from clients not interested in * the new VRF. Note that this function is invoked *prior* to the VRF change. */ void zebra_interface_vrf_update_del(struct interface *ifp, vrf_id_t new_vrf_id) { struct listnode *node, *nnode; struct zserv *client; if (IS_ZEBRA_DEBUG_EVENT) zlog_debug( "MESSAGE: ZEBRA_INTERFACE_VRF_UPDATE/DEL %s VRF Id %u -> %u", ifp->name, ifp->vrf_id, new_vrf_id); for (ALL_LIST_ELEMENTS(zrouter.client_list, node, nnode, client)) { /* Need to delete if the client is not interested in the new * VRF. */ zsend_interface_update(ZEBRA_INTERFACE_DOWN, client, ifp); client->ifdel_cnt++; zsend_interface_delete(client, ifp); zsend_interface_vrf_update(client, ifp, new_vrf_id); } } /* Interface VRF change. This function is invoked *post* VRF change and sends an * add to clients who are interested in the new VRF but not in the old VRF. */ void zebra_interface_vrf_update_add(struct interface *ifp, vrf_id_t old_vrf_id) { struct listnode *node, *nnode; struct zserv *client; if (IS_ZEBRA_DEBUG_EVENT) zlog_debug( "MESSAGE: ZEBRA_INTERFACE_VRF_UPDATE/ADD %s VRF Id %u -> %u", ifp->name, old_vrf_id, ifp->vrf_id); for (ALL_LIST_ELEMENTS(zrouter.client_list, node, nnode, client)) { /* Need to add if the client is interested in the new VRF. */ client->ifadd_cnt++; zsend_interface_add(client, ifp); zsend_interface_addresses(client, ifp); } } int zebra_add_import_table_entry(struct zebra_vrf *zvrf, struct route_node *rn, struct route_entry *re, const char *rmap_name) { struct route_entry *newre; struct route_entry *same; struct prefix p; route_map_result_t ret = RMAP_PERMITMATCH; afi_t afi; afi = family2afi(rn->p.family); if (rmap_name) ret = zebra_import_table_route_map_check( afi, re->type, re->instance, &rn->p, re->ng.nexthop, zvrf->vrf->vrf_id, re->tag, rmap_name); if (ret != RMAP_PERMITMATCH) { UNSET_FLAG(re->flags, ZEBRA_FLAG_SELECTED); zebra_del_import_table_entry(zvrf, rn, re); return 0; } prefix_copy(&p, &rn->p); RNODE_FOREACH_RE (rn, same) { if (CHECK_FLAG(same->status, ROUTE_ENTRY_REMOVED)) continue; if (same->type == re->type && same->instance == re->instance && same->table == re->table && same->type != ZEBRA_ROUTE_CONNECT) break; } if (same) { UNSET_FLAG(same->flags, ZEBRA_FLAG_SELECTED); zebra_del_import_table_entry(zvrf, rn, same); } newre = XCALLOC(MTYPE_RE, sizeof(struct route_entry)); newre->type = ZEBRA_ROUTE_TABLE; newre->distance = zebra_import_table_distance[afi][re->table]; newre->flags = re->flags; newre->metric = re->metric; newre->mtu = re->mtu; newre->table = zvrf->table_id; newre->nexthop_num = 0; newre->uptime = monotime(NULL); newre->instance = re->table; route_entry_copy_nexthops(newre, re->ng.nexthop); rib_add_multipath(afi, SAFI_UNICAST, &p, NULL, newre); return 0; } int zebra_del_import_table_entry(struct zebra_vrf *zvrf, struct route_node *rn, struct route_entry *re) { struct prefix p; afi_t afi; afi = family2afi(rn->p.family); prefix_copy(&p, &rn->p); rib_delete(afi, SAFI_UNICAST, zvrf->vrf->vrf_id, ZEBRA_ROUTE_TABLE, re->table, re->flags, &p, NULL, re->ng.nexthop, zvrf->table_id, re->metric, re->distance, false); return 0; } /* Assuming no one calls this with the main routing table */ int zebra_import_table(afi_t afi, vrf_id_t vrf_id, uint32_t table_id, uint32_t distance, const char *rmap_name, int add) { struct route_table *table; struct route_entry *re; struct route_node *rn; struct zebra_vrf *zvrf = zebra_vrf_lookup_by_id(vrf_id); if (!is_zebra_valid_kernel_table(table_id) || (table_id == RT_TABLE_MAIN)) return (-1); if (afi >= AFI_MAX) return (-1); table = zebra_vrf_table_with_table_id(afi, SAFI_UNICAST, vrf_id, table_id); if (table == NULL) { return 0; } else if (IS_ZEBRA_DEBUG_RIB) { zlog_debug("%s routes from table %d", add ? "Importing" : "Unimporting", table_id); } if (add) { if (rmap_name) zebra_add_import_table_route_map(afi, rmap_name, table_id); else { rmap_name = zebra_get_import_table_route_map(afi, table_id); if (rmap_name) { zebra_del_import_table_route_map(afi, table_id); rmap_name = NULL; } } zebra_import_table_used[afi][table_id] = 1; zebra_import_table_distance[afi][table_id] = distance; } else { zebra_import_table_used[afi][table_id] = 0; zebra_import_table_distance[afi][table_id] = ZEBRA_TABLE_DISTANCE_DEFAULT; rmap_name = zebra_get_import_table_route_map(afi, table_id); if (rmap_name) { zebra_del_import_table_route_map(afi, table_id); rmap_name = NULL; } } for (rn = route_top(table); rn; rn = route_next(rn)) { /* For each entry in the non-default routing table, * add the entry in the main table */ if (!rn->info) continue; RNODE_FOREACH_RE (rn, re) { if (CHECK_FLAG(re->status, ROUTE_ENTRY_REMOVED)) continue; break; } if (!re) continue; if (((afi == AFI_IP) && (rn->p.family == AF_INET)) || ((afi == AFI_IP6) && (rn->p.family == AF_INET6))) { if (add) zebra_add_import_table_entry(zvrf, rn, re, rmap_name); else zebra_del_import_table_entry(zvrf, rn, re); } } return 0; } int zebra_import_table_config(struct vty *vty, vrf_id_t vrf_id) { int i; afi_t afi; int write = 0; char afi_str[AFI_MAX][10] = {"", "ip", "ipv6", "ethernet"}; const char *rmap_name; for (afi = AFI_IP; afi < AFI_MAX; afi++) { for (i = 1; i < ZEBRA_KERNEL_TABLE_MAX; i++) { if (!is_zebra_import_table_enabled(afi, vrf_id, i)) continue; if (zebra_import_table_distance[afi][i] != ZEBRA_TABLE_DISTANCE_DEFAULT) { vty_out(vty, "%s import-table %d distance %d", afi_str[afi], i, zebra_import_table_distance[afi][i]); } else { vty_out(vty, "%s import-table %d", afi_str[afi], i); } rmap_name = zebra_get_import_table_route_map(afi, i); if (rmap_name) vty_out(vty, " route-map %s", rmap_name); vty_out(vty, "\n"); write = 1; } } return write; } static void zebra_import_table_rm_update_vrf_afi(struct zebra_vrf *zvrf, afi_t afi, int table_id, const char *rmap) { struct route_table *table; struct route_entry *re; struct route_node *rn; const char *rmap_name; rmap_name = zebra_get_import_table_route_map(afi, table_id); if ((!rmap_name) || (strcmp(rmap_name, rmap) != 0)) return; table = zebra_vrf_table_with_table_id(afi, SAFI_UNICAST, zvrf->vrf->vrf_id, table_id); if (!table) { if (IS_ZEBRA_DEBUG_RIB_DETAILED) zlog_debug("%s: Table id=%d not found", __func__, table_id); return; } for (rn = route_top(table); rn; rn = route_next(rn)) { /* * For each entry in the non-default routing table, * add the entry in the main table */ if (!rn->info) continue; RNODE_FOREACH_RE (rn, re) { if (CHECK_FLAG(re->status, ROUTE_ENTRY_REMOVED)) continue; break; } if (!re) continue; if (((afi == AFI_IP) && (rn->p.family == AF_INET)) || ((afi == AFI_IP6) && (rn->p.family == AF_INET6))) zebra_add_import_table_entry(zvrf, rn, re, rmap_name); } return; } static void zebra_import_table_rm_update_vrf(struct zebra_vrf *zvrf, const char *rmap) { afi_t afi; int i; for (afi = AFI_IP; afi < AFI_MAX; afi++) { for (i = 1; i < ZEBRA_KERNEL_TABLE_MAX; i++) { if (!is_zebra_import_table_enabled( afi, zvrf->vrf->vrf_id, i)) continue; zebra_import_table_rm_update_vrf_afi(zvrf, afi, i, rmap); } } } void zebra_import_table_rm_update(const char *rmap) { struct vrf *vrf; struct zebra_vrf *zvrf; RB_FOREACH (vrf, vrf_name_head, &vrfs_by_name) { zvrf = vrf->info; if (!zvrf) continue; zebra_import_table_rm_update_vrf(zvrf, rmap); } } /* Interface parameters update */ void zebra_interface_parameters_update(struct interface *ifp) { struct listnode *node, *nnode; struct zserv *client; if (IS_ZEBRA_DEBUG_EVENT) zlog_debug("MESSAGE: ZEBRA_INTERFACE_LINK_PARAMS %s(%u)", ifp->name, ifp->vrf_id); for (ALL_LIST_ELEMENTS(zrouter.client_list, node, nnode, client)) zsend_interface_link_params(client, ifp); } frr-7.2.1/zebra/redistribute.h0000644000000000000000000000652513610377563013205 00000000000000/* * Redistribution Handler * Copyright (C) 1999 Kunihiro Ishiguro * * This file is part of GNU Zebra. * * GNU Zebra is free software; you can redistribute it and/or modify it * under the terms of the GNU General Public License as published by the * Free Software Foundation; either version 2, or (at your option) any * later version. * * GNU Zebra 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; see the file COPYING; if not, write to the Free Software * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA */ #ifndef _ZEBRA_REDISTRIBUTE_H #define _ZEBRA_REDISTRIBUTE_H #include "table.h" #include "vty.h" #include "vrf.h" #include "zebra/zserv.h" #include "zebra/rib.h" #ifdef __cplusplus extern "C" { #endif /* ZAPI command handlers */ extern void zebra_redistribute_add(ZAPI_HANDLER_ARGS); extern void zebra_redistribute_delete(ZAPI_HANDLER_ARGS); extern void zebra_redistribute_default_add(ZAPI_HANDLER_ARGS); extern void zebra_redistribute_default_delete(ZAPI_HANDLER_ARGS); /* ----------------- */ extern void redistribute_update(const struct prefix *p, const struct prefix *src_p, const struct route_entry *re, const struct route_entry *prev_re); /* * During a route delete, where 'new_re' is NULL, redist a delete to all * clients registered for the type of 'old_re'. * During a route update, redist a delete to any clients who will not see * an update when the new route is installed. There are cases when a client * may have seen a redist for 'old_re', but will not see * the redist for 'new_re'. */ void redistribute_delete(const struct prefix *p, const struct prefix *src_p, const struct route_entry *old_re, const struct route_entry *new_re); extern void zebra_interface_up_update(struct interface *); extern void zebra_interface_down_update(struct interface *); extern void zebra_interface_add_update(struct interface *); extern void zebra_interface_delete_update(struct interface *); extern void zebra_interface_address_add_update(struct interface *, struct connected *); extern void zebra_interface_address_delete_update(struct interface *, struct connected *c); extern void zebra_interface_parameters_update(struct interface *); extern void zebra_interface_vrf_update_del(struct interface *, vrf_id_t new_vrf_id); extern void zebra_interface_vrf_update_add(struct interface *, vrf_id_t old_vrf_id); extern int zebra_import_table(afi_t afi, vrf_id_t vrf_id, uint32_t table_id, uint32_t distance, const char *rmap_name, int add); extern int zebra_add_import_table_entry(struct zebra_vrf *zvrf, struct route_node *rn, struct route_entry *re, const char *rmap_name); extern int zebra_del_import_table_entry(struct zebra_vrf *zvrf, struct route_node *rn, struct route_entry *re); extern int is_zebra_import_table_enabled(afi_t, vrf_id_t vrf_id, uint32_t table_id); extern int zebra_import_table_config(struct vty *, vrf_id_t vrf_id); extern void zebra_import_table_rm_update(const char *rmap); #ifdef __cplusplus } #endif #endif /* _ZEBRA_REDISTRIBUTE_H */ frr-7.2.1/zebra/rib.h0000644000000000000000000003337313610377563011255 00000000000000/* * Routing Information Base header * Copyright (C) 1997 Kunihiro Ishiguro * * This file is part of GNU Zebra. * * GNU Zebra is free software; you can redistribute it and/or modify it * under the terms of the GNU General Public License as published by the * Free Software Foundation; either version 2, or (at your option) any * later version. * * GNU Zebra 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; see the file COPYING; if not, write to the Free Software * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA */ #ifndef _ZEBRA_RIB_H #define _ZEBRA_RIB_H #include "zebra.h" #include "hook.h" #include "typesafe.h" #include "linklist.h" #include "prefix.h" #include "table.h" #include "queue.h" #include "nexthop.h" #include "nexthop_group.h" #include "vrf.h" #include "if.h" #include "mpls.h" #include "srcdest_table.h" #ifdef __cplusplus extern "C" { #endif typedef enum { RNH_NEXTHOP_TYPE, RNH_IMPORT_CHECK_TYPE } rnh_type_t; PREDECL_LIST(rnh_list) /* Nexthop structure. */ struct rnh { uint8_t flags; #define ZEBRA_NHT_CONNECTED 0x1 #define ZEBRA_NHT_DELETED 0x2 #define ZEBRA_NHT_EXACT_MATCH 0x4 /* VRF identifier. */ vrf_id_t vrf_id; afi_t afi; rnh_type_t type; uint32_t seqno; struct route_entry *state; struct prefix resolved_route; struct list *client_list; /* pseudowires dependent on this nh */ struct list *zebra_pseudowire_list; struct route_node *node; /* * if this has been filtered for the client */ int filtered[ZEBRA_ROUTE_MAX]; struct rnh_list_item rnh_list_item; }; #define DISTANCE_INFINITY 255 #define ZEBRA_KERNEL_TABLE_MAX 252 /* support for no more than this rt tables */ PREDECL_LIST(re_list) struct route_entry { /* Link list. */ struct re_list_item next; /* Nexthop structure (from RIB) */ struct nexthop_group ng; /* Nexthop group from FIB (optional) */ struct nexthop_group fib_ng; /* Tag */ route_tag_t tag; /* Uptime. */ time_t uptime; /* Type fo this route. */ int type; /* VRF identifier. */ vrf_id_t vrf_id; /* Which routing table */ uint32_t table; /* Metric */ uint32_t metric; /* MTU */ uint32_t mtu; uint32_t nexthop_mtu; /* Flags of this route. * This flag's definition is in lib/zebra.h ZEBRA_FLAG_* and is exposed * to clients via Zserv */ uint32_t flags; /* RIB internal status */ uint32_t status; #define ROUTE_ENTRY_REMOVED 0x1 /* The Route Entry has changed */ #define ROUTE_ENTRY_CHANGED 0x2 /* The Label has changed on the Route entry */ #define ROUTE_ENTRY_LABELS_CHANGED 0x4 /* Route is queued for Installation into the Data Plane */ #define ROUTE_ENTRY_QUEUED 0x8 /* Route is installed into the Data Plane */ #define ROUTE_ENTRY_INSTALLED 0x10 /* Route has Failed installation into the Data Plane in some manner */ #define ROUTE_ENTRY_FAILED 0x20 /* Nexthop information. */ uint8_t nexthop_num; uint8_t nexthop_active_num; /* Sequence value incremented for each dataplane operation */ uint32_t dplane_sequence; /* Source protocol instance */ uint16_t instance; /* Distance. */ uint8_t distance; }; #define RIB_SYSTEM_ROUTE(R) RSYSTEM_ROUTE((R)->type) #define RIB_KERNEL_ROUTE(R) RKERNEL_ROUTE((R)->type) /* meta-queue structure: * sub-queue 0: connected, kernel * sub-queue 1: static * sub-queue 2: RIP, RIPng, OSPF, OSPF6, IS-IS, EIGRP, NHRP * sub-queue 3: iBGP, eBGP * sub-queue 4: any other origin (if any) */ #define MQ_SIZE 5 struct meta_queue { struct list *subq[MQ_SIZE]; uint32_t size; /* sum of lengths of all subqueues */ }; /* * Structure that represents a single destination (prefix). */ typedef struct rib_dest_t_ { /* * Back pointer to the route node for this destination. This helps * us get to the prefix that this structure is for. */ struct route_node *rnode; /* * Doubly-linked list of routes for this prefix. */ struct re_list_head routes; struct route_entry *selected_fib; /* * Flags, see below. */ uint32_t flags; /* * The list of nht prefixes that have ended up * depending on this route node. * After route processing is returned from * the data plane we will run evaluate_rnh * on these prefixes. */ struct rnh_list_head nht; /* * Linkage to put dest on the FPM processing queue. */ TAILQ_ENTRY(rib_dest_t_) fpm_q_entries; } rib_dest_t; DECLARE_LIST(rnh_list, struct rnh, rnh_list_item); DECLARE_LIST(re_list, struct route_entry, next); #define RIB_ROUTE_QUEUED(x) (1 << (x)) // If MQ_SIZE is modified this value needs to be updated. #define RIB_ROUTE_ANY_QUEUED 0x1F /* * The maximum qindex that can be used. */ #define ZEBRA_MAX_QINDEX (MQ_SIZE - 1) /* * This flag indicates that a given prefix has been 'advertised' to * the FPM to be installed in the forwarding plane. */ #define RIB_DEST_SENT_TO_FPM (1 << (ZEBRA_MAX_QINDEX + 1)) /* * This flag is set when we need to send an update to the FPM about a * dest. */ #define RIB_DEST_UPDATE_FPM (1 << (ZEBRA_MAX_QINDEX + 2)) #define RIB_DEST_UPDATE_LSPS (1 << (ZEBRA_MAX_QINDEX + 3)) /* * Macro to iterate over each route for a destination (prefix). */ #define RE_DEST_FOREACH_ROUTE(dest, re) \ for ((re) = (dest) ? re_list_first(&((dest)->routes)) : NULL; (re); \ (re) = re_list_next(&((dest)->routes), (re))) /* * Same as above, but allows the current node to be unlinked. */ #define RE_DEST_FOREACH_ROUTE_SAFE(dest, re, next) \ for ((re) = (dest) ? re_list_first(&((dest)->routes)) : NULL; \ (re) && ((next) = re_list_next(&((dest)->routes), (re)), 1); \ (re) = (next)) #define RNODE_FOREACH_RE(rn, re) \ RE_DEST_FOREACH_ROUTE (rib_dest_from_rnode(rn), re) #define RNODE_FOREACH_RE_SAFE(rn, re, next) \ RE_DEST_FOREACH_ROUTE_SAFE (rib_dest_from_rnode(rn), re, next) #if defined(HAVE_RTADV) /* Structure which hold status of router advertisement. */ struct rtadv { int sock; int adv_if_count; int adv_msec_if_count; struct thread *ra_read; struct thread *ra_timer; }; #endif /* HAVE_RTADV */ /* * rib_table_info_t * * Structure that is hung off of a route_table that holds information about * the table. */ typedef struct rib_table_info_t_ { /* * Back pointer to zebra_vrf. */ struct zebra_vrf *zvrf; afi_t afi; safi_t safi; } rib_table_info_t; typedef enum { RIB_TABLES_ITER_S_INIT, RIB_TABLES_ITER_S_ITERATING, RIB_TABLES_ITER_S_DONE } rib_tables_iter_state_t; /* * Structure that holds state for iterating over all tables in the * Routing Information Base. */ typedef struct rib_tables_iter_t_ { vrf_id_t vrf_id; int afi_safi_ix; rib_tables_iter_state_t state; } rib_tables_iter_t; /* Events/reasons triggering a RIB update. */ typedef enum { RIB_UPDATE_RMAP_CHANGE, RIB_UPDATE_OTHER } rib_update_event_t; extern struct nexthop *route_entry_nexthop_ifindex_add(struct route_entry *re, ifindex_t ifindex, vrf_id_t nh_vrf_id); extern struct nexthop * route_entry_nexthop_blackhole_add(struct route_entry *re, enum blackhole_type bh_type); extern struct nexthop *route_entry_nexthop_ipv4_add(struct route_entry *re, struct in_addr *ipv4, struct in_addr *src, vrf_id_t nh_vrf_id); extern struct nexthop * route_entry_nexthop_ipv4_ifindex_add(struct route_entry *re, struct in_addr *ipv4, struct in_addr *src, ifindex_t ifindex, vrf_id_t nh_vrf_id); extern void route_entry_nexthop_delete(struct route_entry *re, struct nexthop *nexthop); extern struct nexthop *route_entry_nexthop_ipv6_add(struct route_entry *re, struct in6_addr *ipv6, vrf_id_t nh_vrf_id); extern struct nexthop * route_entry_nexthop_ipv6_ifindex_add(struct route_entry *re, struct in6_addr *ipv6, ifindex_t ifindex, vrf_id_t nh_vrf_id); extern void route_entry_nexthop_add(struct route_entry *re, struct nexthop *nexthop); extern void route_entry_copy_nexthops(struct route_entry *re, struct nexthop *nh); #define route_entry_dump(prefix, src, re) _route_entry_dump(__func__, prefix, src, re) extern void _route_entry_dump(const char *func, union prefixconstptr pp, union prefixconstptr src_pp, const struct route_entry *re); extern void rib_lookup_and_dump(struct prefix_ipv4 *p, vrf_id_t vrf_id); extern void rib_lookup_and_pushup(struct prefix_ipv4 *p, vrf_id_t vrf_id); #define ZEBRA_RIB_LOOKUP_ERROR -1 #define ZEBRA_RIB_FOUND_EXACT 0 #define ZEBRA_RIB_FOUND_NOGATE 1 #define ZEBRA_RIB_FOUND_CONNECTED 2 #define ZEBRA_RIB_NOTFOUND 3 extern int is_zebra_valid_kernel_table(uint32_t table_id); extern int is_zebra_main_routing_table(uint32_t table_id); extern int zebra_check_addr(const struct prefix *p); extern void rib_delnode(struct route_node *rn, struct route_entry *re); extern void rib_install_kernel(struct route_node *rn, struct route_entry *re, struct route_entry *old); extern void rib_uninstall_kernel(struct route_node *rn, struct route_entry *re); /* NOTE: * All rib_add function will not just add prefix into RIB, but * also implicitly withdraw equal prefix of same type. */ extern int rib_add(afi_t afi, safi_t safi, vrf_id_t vrf_id, int type, unsigned short instance, int flags, struct prefix *p, struct prefix_ipv6 *src_p, const struct nexthop *nh, uint32_t table_id, uint32_t metric, uint32_t mtu, uint8_t distance, route_tag_t tag); extern int rib_add_multipath(afi_t afi, safi_t safi, struct prefix *p, struct prefix_ipv6 *src_p, struct route_entry *re); extern void rib_delete(afi_t afi, safi_t safi, vrf_id_t vrf_id, int type, unsigned short instance, int flags, struct prefix *p, struct prefix_ipv6 *src_p, const struct nexthop *nh, uint32_t table_id, uint32_t metric, uint8_t distance, bool fromkernel); extern struct route_entry *rib_match(afi_t afi, safi_t safi, vrf_id_t vrf_id, union g_addr *addr, struct route_node **rn_out); extern struct route_entry *rib_match_ipv4_multicast(vrf_id_t vrf_id, struct in_addr addr, struct route_node **rn_out); extern struct route_entry *rib_lookup_ipv4(struct prefix_ipv4 *p, vrf_id_t vrf_id); extern void rib_update(vrf_id_t vrf_id, rib_update_event_t event); extern void rib_update_table(struct route_table *table, rib_update_event_t event); extern int rib_sweep_route(struct thread *t); extern void rib_sweep_table(struct route_table *table); extern void rib_close_table(struct route_table *table); extern void rib_init(void); extern unsigned long rib_score_proto(uint8_t proto, unsigned short instance); extern unsigned long rib_score_proto_table(uint8_t proto, unsigned short instance, struct route_table *table); extern void rib_queue_add(struct route_node *rn); extern void meta_queue_free(struct meta_queue *mq); extern int zebra_rib_labeled_unicast(struct route_entry *re); extern struct route_table *rib_table_ipv6; extern void rib_unlink(struct route_node *rn, struct route_entry *re); extern int rib_gc_dest(struct route_node *rn); extern struct route_table *rib_tables_iter_next(rib_tables_iter_t *iter); extern uint8_t route_distance(int type); extern void zebra_rib_evaluate_rn_nexthops(struct route_node *rn, uint32_t seq); /* * Inline functions. */ /* * rib_table_info */ static inline rib_table_info_t *rib_table_info(struct route_table *table) { return (rib_table_info_t *)route_table_get_info(table); } /* * rib_dest_from_rnode */ static inline rib_dest_t *rib_dest_from_rnode(struct route_node *rn) { return (rib_dest_t *)rn->info; } /* * rnode_to_ribs * * Returns a pointer to the list of routes corresponding to the given * route_node. */ static inline struct route_entry *rnode_to_ribs(struct route_node *rn) { rib_dest_t *dest; dest = rib_dest_from_rnode(rn); if (!dest) return NULL; return re_list_first(&dest->routes); } /* * rib_dest_prefix */ static inline struct prefix *rib_dest_prefix(rib_dest_t *dest) { return &dest->rnode->p; } /* * rib_dest_af * * Returns the address family that the destination is for. */ static inline uint8_t rib_dest_af(rib_dest_t *dest) { return dest->rnode->p.family; } /* * rib_dest_table */ static inline struct route_table *rib_dest_table(rib_dest_t *dest) { return srcdest_rnode_table(dest->rnode); } /* * rib_dest_vrf */ static inline struct zebra_vrf *rib_dest_vrf(rib_dest_t *dest) { return rib_table_info(rib_dest_table(dest))->zvrf; } /* * Create the rib_dest_t and attach it to the specified node */ extern rib_dest_t *zebra_rib_create_dest(struct route_node *rn); /* * rib_tables_iter_init */ static inline void rib_tables_iter_init(rib_tables_iter_t *iter) { memset(iter, 0, sizeof(*iter)); iter->state = RIB_TABLES_ITER_S_INIT; } /* * rib_tables_iter_started * * Returns true if this iterator has started iterating over the set of * tables. */ static inline int rib_tables_iter_started(rib_tables_iter_t *iter) { return iter->state != RIB_TABLES_ITER_S_INIT; } /* * rib_tables_iter_cleanup */ static inline void rib_tables_iter_cleanup(rib_tables_iter_t *iter) { iter->state = RIB_TABLES_ITER_S_DONE; } DECLARE_HOOK(rib_update, (struct route_node * rn, const char *reason), (rn, reason)) /* * Access active nexthop-group, either RIB or FIB version */ static inline struct nexthop_group *rib_active_nhg(struct route_entry *re) { if (re->fib_ng.nexthop) return &(re->fib_ng); else return &(re->ng); } extern void zebra_vty_init(void); extern pid_t pid; extern bool v6_rr_semantics; #ifdef __cplusplus } #endif #endif /*_ZEBRA_RIB_H */ frr-7.2.1/zebra/router-id.c0000644000000000000000000001546013610377563012403 00000000000000/* * Router ID for zebra daemon. * * Copyright (C) 2004 James R. Leu * * This file is part of Quagga routing suite. * * Quagga is free software; you can redistribute it and/or modify it * under the terms of the GNU General Public License as published by the * Free Software Foundation; either version 2, or (at your option) any * later version. * * Quagga 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; see the file COPYING; if not, write to the Free Software * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA */ #include #include "if.h" #include "vty.h" #include "sockunion.h" #include "prefix.h" #include "stream.h" #include "command.h" #include "memory.h" #include "zebra_memory.h" #include "ioctl.h" #include "connected.h" #include "network.h" #include "log.h" #include "table.h" #include "rib.h" #include "vrf.h" #include "zebra/zebra_router.h" #include "zebra/zapi_msg.h" #include "zebra/zebra_vrf.h" #include "zebra/router-id.h" #include "zebra/redistribute.h" static struct connected *router_id_find_node(struct list *l, struct connected *ifc) { struct listnode *node; struct connected *c; for (ALL_LIST_ELEMENTS_RO(l, node, c)) if (prefix_same(ifc->address, c->address)) return c; return NULL; } static int router_id_bad_address(struct connected *ifc) { if (ifc->address->family != AF_INET) return 1; /* non-redistributable addresses shouldn't be used for RIDs either */ if (!zebra_check_addr(ifc->address)) return 1; return 0; } void router_id_get(struct prefix *p, vrf_id_t vrf_id) { struct listnode *node; struct connected *c; struct zebra_vrf *zvrf = vrf_info_get(vrf_id); p->u.prefix4.s_addr = 0; p->family = AF_INET; p->prefixlen = 32; if (zvrf->rid_user_assigned.u.prefix4.s_addr) p->u.prefix4.s_addr = zvrf->rid_user_assigned.u.prefix4.s_addr; else if (!list_isempty(zvrf->rid_lo_sorted_list)) { node = listtail(zvrf->rid_lo_sorted_list); c = listgetdata(node); p->u.prefix4.s_addr = c->address->u.prefix4.s_addr; } else if (!list_isempty(zvrf->rid_all_sorted_list)) { node = listtail(zvrf->rid_all_sorted_list); c = listgetdata(node); p->u.prefix4.s_addr = c->address->u.prefix4.s_addr; } } static void router_id_set(struct prefix *p, vrf_id_t vrf_id) { struct prefix p2; struct listnode *node; struct zserv *client; struct zebra_vrf *zvrf; if (p->u.prefix4.s_addr == 0) /* unset */ { zvrf = vrf_info_lookup(vrf_id); if (!zvrf) return; } else /* set */ zvrf = vrf_info_get(vrf_id); zvrf->rid_user_assigned.u.prefix4.s_addr = p->u.prefix4.s_addr; router_id_get(&p2, vrf_id); for (ALL_LIST_ELEMENTS_RO(zrouter.client_list, node, client)) zsend_router_id_update(client, &p2, vrf_id); } void router_id_add_address(struct connected *ifc) { struct list *l = NULL; struct listnode *node; struct prefix before; struct prefix after; struct zserv *client; struct zebra_vrf *zvrf = vrf_info_get(ifc->ifp->vrf_id); if (router_id_bad_address(ifc)) return; router_id_get(&before, zvrf_id(zvrf)); if (if_is_loopback(ifc->ifp)) l = zvrf->rid_lo_sorted_list; else l = zvrf->rid_all_sorted_list; if (!router_id_find_node(l, ifc)) listnode_add_sort(l, ifc); router_id_get(&after, zvrf_id(zvrf)); if (prefix_same(&before, &after)) return; for (ALL_LIST_ELEMENTS_RO(zrouter.client_list, node, client)) zsend_router_id_update(client, &after, zvrf_id(zvrf)); } void router_id_del_address(struct connected *ifc) { struct connected *c; struct list *l; struct prefix after; struct prefix before; struct listnode *node; struct zserv *client; struct zebra_vrf *zvrf = vrf_info_get(ifc->ifp->vrf_id); if (router_id_bad_address(ifc)) return; router_id_get(&before, zvrf_id(zvrf)); if (if_is_loopback(ifc->ifp)) l = zvrf->rid_lo_sorted_list; else l = zvrf->rid_all_sorted_list; if ((c = router_id_find_node(l, ifc))) listnode_delete(l, c); router_id_get(&after, zvrf_id(zvrf)); if (prefix_same(&before, &after)) return; for (ALL_LIST_ELEMENTS_RO(zrouter.client_list, node, client)) zsend_router_id_update(client, &after, zvrf_id(zvrf)); } void router_id_write(struct vty *vty) { struct vrf *vrf; struct zebra_vrf *zvrf; RB_FOREACH (vrf, vrf_name_head, &vrfs_by_name) if ((zvrf = vrf->info) != NULL) if (zvrf->rid_user_assigned.u.prefix4.s_addr) { if (zvrf_id(zvrf) == VRF_DEFAULT) vty_out(vty, "router-id %s\n", inet_ntoa( zvrf->rid_user_assigned .u.prefix4)); else vty_out(vty, "router-id %s vrf %s\n", inet_ntoa( zvrf->rid_user_assigned .u.prefix4), zvrf_name(zvrf)); } } DEFUN (router_id, router_id_cmd, "router-id A.B.C.D [vrf NAME]", "Manually set the router-id\n" "IP address to use for router-id\n" VRF_CMD_HELP_STR) { int idx_ipv4 = 1; int idx_name = 3; struct prefix rid; vrf_id_t vrf_id = VRF_DEFAULT; rid.u.prefix4.s_addr = inet_addr(argv[idx_ipv4]->arg); if (!rid.u.prefix4.s_addr) return CMD_WARNING_CONFIG_FAILED; rid.prefixlen = 32; rid.family = AF_INET; if (argc > 2) VRF_GET_ID(vrf_id, argv[idx_name]->arg, false); router_id_set(&rid, vrf_id); return CMD_SUCCESS; } DEFUN (no_router_id, no_router_id_cmd, "no router-id [A.B.C.D [vrf NAME]]", NO_STR "Remove the manually configured router-id\n" "IP address to use for router-id\n" VRF_CMD_HELP_STR) { int idx_name = 4; struct prefix rid; vrf_id_t vrf_id = VRF_DEFAULT; rid.u.prefix4.s_addr = 0; rid.prefixlen = 0; rid.family = AF_INET; if (argc > 3) VRF_GET_ID(vrf_id, argv[idx_name]->arg, false); router_id_set(&rid, vrf_id); return CMD_SUCCESS; } static int router_id_cmp(void *a, void *b) { const struct connected *ifa = (const struct connected *)a; const struct connected *ifb = (const struct connected *)b; return IPV4_ADDR_CMP(&ifa->address->u.prefix4.s_addr, &ifb->address->u.prefix4.s_addr); } void router_id_cmd_init(void) { install_element(CONFIG_NODE, &router_id_cmd); install_element(CONFIG_NODE, &no_router_id_cmd); } void router_id_init(struct zebra_vrf *zvrf) { zvrf->rid_all_sorted_list = &zvrf->_rid_all_sorted_list; zvrf->rid_lo_sorted_list = &zvrf->_rid_lo_sorted_list; memset(zvrf->rid_all_sorted_list, 0, sizeof(zvrf->_rid_all_sorted_list)); memset(zvrf->rid_lo_sorted_list, 0, sizeof(zvrf->_rid_lo_sorted_list)); memset(&zvrf->rid_user_assigned, 0, sizeof(zvrf->rid_user_assigned)); zvrf->rid_all_sorted_list->cmp = router_id_cmp; zvrf->rid_lo_sorted_list->cmp = router_id_cmp; zvrf->rid_user_assigned.family = AF_INET; zvrf->rid_user_assigned.prefixlen = 32; } frr-7.2.1/zebra/router-id.h0000644000000000000000000000250513610377563012404 00000000000000/* * Router ID for zebra daemon. * * Copyright (C) 2004 James R. Leu * * This file is part of Quagga routing suite. * * Quagga is free software; you can redistribute it and/or modify it * under the terms of the GNU General Public License as published by the * Free Software Foundation; either version 2, or (at your option) any * later version. * * Quagga 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; see the file COPYING; if not, write to the Free Software * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA */ #ifndef _ROUTER_ID_H_ #define _ROUTER_ID_H_ #include #include "memory.h" #include "prefix.h" #include "zclient.h" #include "if.h" #ifdef __cplusplus extern "C" { #endif extern void router_id_add_address(struct connected *); extern void router_id_del_address(struct connected *); extern void router_id_init(struct zebra_vrf *); extern void router_id_cmd_init(void); extern void router_id_write(struct vty *); extern void router_id_get(struct prefix *, vrf_id_t); #ifdef __cplusplus } #endif #endif frr-7.2.1/zebra/rt.h0000644000000000000000000000614013610377563011116 00000000000000/* * kernel routing table update prototype. * Copyright (C) 1998 Kunihiro Ishiguro * * This file is part of GNU Zebra. * * GNU Zebra is free software; you can redistribute it and/or modify it * under the terms of the GNU General Public License as published by the * Free Software Foundation; either version 2, or (at your option) any * later version. * * GNU Zebra 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; see the file COPYING; if not, write to the Free Software * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA */ #ifndef _ZEBRA_RT_H #define _ZEBRA_RT_H #include "prefix.h" #include "if.h" #include "vlan.h" #include "vxlan.h" #include "zebra/rib.h" #include "zebra/zebra_ns.h" #include "zebra/zebra_mpls.h" #include "zebra/zebra_dplane.h" #ifdef __cplusplus extern "C" { #endif #define RKERNEL_ROUTE(type) ((type) == ZEBRA_ROUTE_KERNEL) #define RSYSTEM_ROUTE(type) \ ((RKERNEL_ROUTE(type)) || (type) == ZEBRA_ROUTE_CONNECT) /* * Update or delete a route, LSP, pseudowire, or vxlan MAC from the kernel, * using info from a dataplane context. */ extern enum zebra_dplane_result kernel_route_update( struct zebra_dplane_ctx *ctx); extern enum zebra_dplane_result kernel_lsp_update( struct zebra_dplane_ctx *ctx); enum zebra_dplane_result kernel_pw_update(struct zebra_dplane_ctx *ctx); enum zebra_dplane_result kernel_address_update_ctx( struct zebra_dplane_ctx *ctx); enum zebra_dplane_result kernel_mac_update_ctx(struct zebra_dplane_ctx *ctx); enum zebra_dplane_result kernel_neigh_update_ctx(struct zebra_dplane_ctx *ctx); extern int kernel_neigh_update(int cmd, int ifindex, uint32_t addr, char *lla, int llalen, ns_id_t ns_id); extern int kernel_interface_set_master(struct interface *master, struct interface *slave); extern int mpls_kernel_init(void); extern uint32_t kernel_get_speed(struct interface *ifp); extern int kernel_get_ipmr_sg_stats(struct zebra_vrf *zvrf, void *mroute); /* * Southbound Initialization routines to get initial starting * state. */ extern void interface_list(struct zebra_ns *zns); extern void kernel_init(struct zebra_ns *zns); extern void kernel_terminate(struct zebra_ns *zns, bool complete); extern void macfdb_read(struct zebra_ns *zns); extern void macfdb_read_for_bridge(struct zebra_ns *zns, struct interface *ifp, struct interface *br_if); extern void macfdb_read_specific_mac(struct zebra_ns *zns, struct interface *br_if, struct ethaddr *mac, vlanid_t vid); extern void neigh_read(struct zebra_ns *zns); extern void neigh_read_for_vlan(struct zebra_ns *zns, struct interface *ifp); extern void neigh_read_specific_ip(struct ipaddr *ip, struct interface *vlan_if); extern void route_read(struct zebra_ns *zns); #ifdef __cplusplus } #endif #endif /* _ZEBRA_RT_H */ frr-7.2.1/zebra/rt_netlink.c0000644000000000000000000023673113610377563012650 00000000000000/* Kernel routing table updates using netlink over GNU/Linux system. * Copyright (C) 1997, 98, 99 Kunihiro Ishiguro * * This file is part of GNU Zebra. * * GNU Zebra is free software; you can redistribute it and/or modify it * under the terms of the GNU General Public License as published by the * Free Software Foundation; either version 2, or (at your option) any * later version. * * GNU Zebra 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; see the file COPYING; if not, write to the Free Software * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA */ #include #ifdef HAVE_NETLINK #include #include #include #include #include /* Hack for GNU libc version 2. */ #ifndef MSG_TRUNC #define MSG_TRUNC 0x20 #endif /* MSG_TRUNC */ #include "linklist.h" #include "if.h" #include "log.h" #include "prefix.h" #include "connected.h" #include "table.h" #include "memory.h" #include "zebra_memory.h" #include "rib.h" #include "thread.h" #include "privs.h" #include "nexthop.h" #include "vrf.h" #include "vty.h" #include "mpls.h" #include "vxlan.h" #include "zebra/zapi_msg.h" #include "zebra/zebra_ns.h" #include "zebra/zebra_vrf.h" #include "zebra/rt.h" #include "zebra/redistribute.h" #include "zebra/interface.h" #include "zebra/debug.h" #include "zebra/rtadv.h" #include "zebra/zebra_ptm.h" #include "zebra/zebra_mpls.h" #include "zebra/kernel_netlink.h" #include "zebra/rt_netlink.h" #include "zebra/zebra_mroute.h" #include "zebra/zebra_vxlan.h" #include "zebra/zebra_errors.h" #ifndef AF_MPLS #define AF_MPLS 28 #endif static vlanid_t filter_vlan = 0; struct gw_family_t { uint16_t filler; uint16_t family; union g_addr gate; }; char ipv4_ll_buf[16] = "169.254.0.1"; struct in_addr ipv4_ll; /* * The ipv4_ll data structure is used for all 5549 * additions to the kernel. Let's figure out the * correct value one time instead for every * install/remove of a 5549 type route */ void rt_netlink_init(void) { inet_pton(AF_INET, ipv4_ll_buf, &ipv4_ll); } /* * Mapping from dataplane neighbor flags to netlink flags */ static uint8_t neigh_flags_to_netlink(uint8_t dplane_flags) { uint8_t flags = 0; if (dplane_flags & DPLANE_NTF_EXT_LEARNED) flags |= NTF_EXT_LEARNED; if (dplane_flags & DPLANE_NTF_ROUTER) flags |= NTF_ROUTER; return flags; } /* * Mapping from dataplane neighbor state to netlink state */ static uint16_t neigh_state_to_netlink(uint16_t dplane_state) { uint16_t state = 0; if (dplane_state & DPLANE_NUD_REACHABLE) state |= NUD_REACHABLE; if (dplane_state & DPLANE_NUD_STALE) state |= NUD_STALE; if (dplane_state & DPLANE_NUD_NOARP) state |= NUD_NOARP; if (dplane_state & DPLANE_NUD_PROBE) state |= NUD_PROBE; return state; } static inline int is_selfroute(int proto) { if ((proto == RTPROT_BGP) || (proto == RTPROT_OSPF) || (proto == RTPROT_ZSTATIC) || (proto == RTPROT_ZEBRA) || (proto == RTPROT_ISIS) || (proto == RTPROT_RIPNG) || (proto == RTPROT_NHRP) || (proto == RTPROT_EIGRP) || (proto == RTPROT_LDP) || (proto == RTPROT_BABEL) || (proto == RTPROT_RIP) || (proto == RTPROT_SHARP) || (proto == RTPROT_PBR) || (proto == RTPROT_OPENFABRIC)) { return 1; } return 0; } static inline int zebra2proto(int proto) { switch (proto) { case ZEBRA_ROUTE_BABEL: proto = RTPROT_BABEL; break; case ZEBRA_ROUTE_BGP: proto = RTPROT_BGP; break; case ZEBRA_ROUTE_OSPF: case ZEBRA_ROUTE_OSPF6: proto = RTPROT_OSPF; break; case ZEBRA_ROUTE_STATIC: proto = RTPROT_ZSTATIC; break; case ZEBRA_ROUTE_ISIS: proto = RTPROT_ISIS; break; case ZEBRA_ROUTE_RIP: proto = RTPROT_RIP; break; case ZEBRA_ROUTE_RIPNG: proto = RTPROT_RIPNG; break; case ZEBRA_ROUTE_NHRP: proto = RTPROT_NHRP; break; case ZEBRA_ROUTE_EIGRP: proto = RTPROT_EIGRP; break; case ZEBRA_ROUTE_LDP: proto = RTPROT_LDP; break; case ZEBRA_ROUTE_SHARP: proto = RTPROT_SHARP; break; case ZEBRA_ROUTE_PBR: proto = RTPROT_PBR; break; case ZEBRA_ROUTE_OPENFABRIC: proto = RTPROT_OPENFABRIC; break; case ZEBRA_ROUTE_TABLE: proto = RTPROT_ZEBRA; break; default: /* * When a user adds a new protocol this will show up * to let them know to do something about it. This * is intentionally a warn because we should see * this as part of development of a new protocol */ zlog_debug( "%s: Please add this protocol(%d) to proper rt_netlink.c handling", __PRETTY_FUNCTION__, proto); proto = RTPROT_ZEBRA; break; } return proto; } static inline int proto2zebra(int proto, int family) { switch (proto) { case RTPROT_BABEL: proto = ZEBRA_ROUTE_BABEL; break; case RTPROT_BGP: proto = ZEBRA_ROUTE_BGP; break; case RTPROT_OSPF: proto = (family == AFI_IP) ? ZEBRA_ROUTE_OSPF : ZEBRA_ROUTE_OSPF6; break; case RTPROT_ISIS: proto = ZEBRA_ROUTE_ISIS; break; case RTPROT_RIP: proto = ZEBRA_ROUTE_RIP; break; case RTPROT_RIPNG: proto = ZEBRA_ROUTE_RIPNG; break; case RTPROT_NHRP: proto = ZEBRA_ROUTE_NHRP; break; case RTPROT_EIGRP: proto = ZEBRA_ROUTE_EIGRP; break; case RTPROT_LDP: proto = ZEBRA_ROUTE_LDP; break; case RTPROT_STATIC: case RTPROT_ZSTATIC: proto = ZEBRA_ROUTE_STATIC; break; case RTPROT_SHARP: proto = ZEBRA_ROUTE_SHARP; break; case RTPROT_PBR: proto = ZEBRA_ROUTE_PBR; break; case RTPROT_OPENFABRIC: proto = ZEBRA_ROUTE_OPENFABRIC; break; default: /* * When a user adds a new protocol this will show up * to let them know to do something about it. This * is intentionally a warn because we should see * this as part of development of a new protocol */ zlog_debug( "%s: Please add this protocol(%d) to proper rt_netlink.c handling", __PRETTY_FUNCTION__, proto); proto = ZEBRA_ROUTE_KERNEL; break; } return proto; } /* Pending: create an efficient table_id (in a tree/hash) based lookup) */ static vrf_id_t vrf_lookup_by_table(uint32_t table_id, ns_id_t ns_id) { struct vrf *vrf; struct zebra_vrf *zvrf; RB_FOREACH (vrf, vrf_id_head, &vrfs_by_id) { zvrf = vrf->info; if (zvrf == NULL) continue; /* case vrf with netns : match the netnsid */ if (vrf_is_backend_netns()) { if (ns_id == zvrf_id(zvrf)) return zvrf_id(zvrf); } else { /* VRF is VRF_BACKEND_VRF_LITE */ if (zvrf->table_id != table_id) continue; return zvrf_id(zvrf); } } return VRF_DEFAULT; } /** * @parse_encap_mpls() - Parses encapsulated mpls attributes * @tb: Pointer to rtattr to look for nested items in. * @labels: Pointer to store labels in. * * Return: Number of mpls labels found. */ static int parse_encap_mpls(struct rtattr *tb, mpls_label_t *labels) { struct rtattr *tb_encap[MPLS_IPTUNNEL_MAX + 1] = {0}; mpls_lse_t *lses = NULL; int num_labels = 0; uint32_t ttl = 0; uint32_t bos = 0; uint32_t exp = 0; mpls_label_t label = 0; netlink_parse_rtattr_nested(tb_encap, MPLS_IPTUNNEL_MAX, tb); lses = (mpls_lse_t *)RTA_DATA(tb_encap[MPLS_IPTUNNEL_DST]); while (!bos && num_labels < MPLS_MAX_LABELS) { mpls_lse_decode(lses[num_labels], &label, &ttl, &exp, &bos); labels[num_labels++] = label; } return num_labels; } /* Looking up routing table by netlink interface. */ static int netlink_route_change_read_unicast(struct nlmsghdr *h, ns_id_t ns_id, int startup) { int len; struct rtmsg *rtm; struct rtattr *tb[RTA_MAX + 1]; uint8_t flags = 0; struct prefix p; struct prefix_ipv6 src_p = {}; vrf_id_t vrf_id; char anyaddr[16] = {0}; int proto = ZEBRA_ROUTE_KERNEL; int index = 0; int table; int metric = 0; uint32_t mtu = 0; uint8_t distance = 0; route_tag_t tag = 0; void *dest = NULL; void *gate = NULL; void *prefsrc = NULL; /* IPv4 preferred source host address */ void *src = NULL; /* IPv6 srcdest source prefix */ enum blackhole_type bh_type = BLACKHOLE_UNSPEC; /* MPLS labels */ mpls_label_t labels[MPLS_MAX_LABELS] = {0}; int num_labels = 0; rtm = NLMSG_DATA(h); if (startup && h->nlmsg_type != RTM_NEWROUTE) return 0; switch (rtm->rtm_type) { case RTN_UNICAST: break; case RTN_BLACKHOLE: bh_type = BLACKHOLE_NULL; break; case RTN_UNREACHABLE: bh_type = BLACKHOLE_REJECT; break; case RTN_PROHIBIT: bh_type = BLACKHOLE_ADMINPROHIB; break; default: if (IS_ZEBRA_DEBUG_KERNEL) zlog_debug("Route rtm_type: %s(%d) intentionally ignoring", nl_rttype_to_str(rtm->rtm_type), rtm->rtm_type); return 0; } len = h->nlmsg_len - NLMSG_LENGTH(sizeof(struct rtmsg)); if (len < 0) { zlog_err("%s: Message received from netlink is of a broken size %d %zu", __PRETTY_FUNCTION__, h->nlmsg_len, (size_t)NLMSG_LENGTH(sizeof(struct rtmsg))); return -1; } memset(tb, 0, sizeof tb); netlink_parse_rtattr(tb, RTA_MAX, RTM_RTA(rtm), len); if (rtm->rtm_flags & RTM_F_CLONED) return 0; if (rtm->rtm_protocol == RTPROT_REDIRECT) return 0; if (rtm->rtm_protocol == RTPROT_KERNEL) return 0; if (!startup && is_selfroute(rtm->rtm_protocol) && h->nlmsg_type == RTM_NEWROUTE) { if (IS_ZEBRA_DEBUG_KERNEL) zlog_debug("Route type: %d Received that we think we have originated, ignoring", rtm->rtm_protocol); return 0; } /* We don't care about change notifications for the MPLS table. */ /* TODO: Revisit this. */ if (rtm->rtm_family == AF_MPLS) return 0; /* Table corresponding to route. */ if (tb[RTA_TABLE]) table = *(int *)RTA_DATA(tb[RTA_TABLE]); else table = rtm->rtm_table; /* Map to VRF */ vrf_id = vrf_lookup_by_table(table, ns_id); if (vrf_id == VRF_DEFAULT) { if (!is_zebra_valid_kernel_table(table) && !is_zebra_main_routing_table(table)) return 0; } /* Route which inserted by Zebra. */ if (is_selfroute(rtm->rtm_protocol)) { flags |= ZEBRA_FLAG_SELFROUTE; proto = proto2zebra(rtm->rtm_protocol, rtm->rtm_family); } if (tb[RTA_OIF]) index = *(int *)RTA_DATA(tb[RTA_OIF]); if (tb[RTA_DST]) dest = RTA_DATA(tb[RTA_DST]); else dest = anyaddr; if (tb[RTA_SRC]) src = RTA_DATA(tb[RTA_SRC]); else src = anyaddr; if (tb[RTA_PREFSRC]) prefsrc = RTA_DATA(tb[RTA_PREFSRC]); if (tb[RTA_GATEWAY]) gate = RTA_DATA(tb[RTA_GATEWAY]); if (tb[RTA_PRIORITY]) metric = *(int *)RTA_DATA(tb[RTA_PRIORITY]); #if defined(SUPPORT_REALMS) if (tb[RTA_FLOW]) tag = *(uint32_t *)RTA_DATA(tb[RTA_FLOW]); #endif if (tb[RTA_METRICS]) { struct rtattr *mxrta[RTAX_MAX + 1]; memset(mxrta, 0, sizeof mxrta); netlink_parse_rtattr(mxrta, RTAX_MAX, RTA_DATA(tb[RTA_METRICS]), RTA_PAYLOAD(tb[RTA_METRICS])); if (mxrta[RTAX_MTU]) mtu = *(uint32_t *)RTA_DATA(mxrta[RTAX_MTU]); } if (rtm->rtm_family == AF_INET) { p.family = AF_INET; if (rtm->rtm_dst_len > IPV4_MAX_BITLEN) { zlog_err( "Invalid destination prefix length: %u received from kernel route change", rtm->rtm_dst_len); return -1; } memcpy(&p.u.prefix4, dest, 4); p.prefixlen = rtm->rtm_dst_len; if (rtm->rtm_src_len != 0) { char buf[PREFIX_STRLEN]; flog_warn( EC_ZEBRA_UNSUPPORTED_V4_SRCDEST, "unsupported IPv4 sourcedest route (dest %s vrf %u)", prefix2str(&p, buf, sizeof(buf)), vrf_id); return 0; } /* Force debug below to not display anything for source */ src_p.prefixlen = 0; } else if (rtm->rtm_family == AF_INET6) { p.family = AF_INET6; if (rtm->rtm_dst_len > IPV6_MAX_BITLEN) { zlog_err( "Invalid destination prefix length: %u received from kernel route change", rtm->rtm_dst_len); return -1; } memcpy(&p.u.prefix6, dest, 16); p.prefixlen = rtm->rtm_dst_len; src_p.family = AF_INET6; if (rtm->rtm_src_len > IPV6_MAX_BITLEN) { zlog_err( "Invalid source prefix length: %u received from kernel route change", rtm->rtm_src_len); return -1; } memcpy(&src_p.prefix, src, 16); src_p.prefixlen = rtm->rtm_src_len; } /* * For ZEBRA_ROUTE_KERNEL types: * * The metric/priority of the route received from the kernel * is a 32 bit number. We are going to interpret the high * order byte as the Admin Distance and the low order 3 bytes * as the metric. * * This will allow us to do two things: * 1) Allow the creation of kernel routes that can be * overridden by zebra. * 2) Allow the old behavior for 'most' kernel route types * if a user enters 'ip route ...' v4 routes get a metric * of 0 and v6 routes get a metric of 1024. Both of these * values will end up with a admin distance of 0, which * will cause them to win for the purposes of zebra. */ if (proto == ZEBRA_ROUTE_KERNEL) { distance = (metric >> 24) & 0xFF; metric = (metric & 0x00FFFFFF); } if (IS_ZEBRA_DEBUG_KERNEL) { char buf[PREFIX_STRLEN]; char buf2[PREFIX_STRLEN]; zlog_debug("%s %s%s%s vrf %u(%u) metric: %d Admin Distance: %d", nl_msg_type_to_str(h->nlmsg_type), prefix2str(&p, buf, sizeof(buf)), src_p.prefixlen ? " from " : "", src_p.prefixlen ? prefix2str(&src_p, buf2, sizeof(buf2)) : "", vrf_id, table, metric, distance); } afi_t afi = AFI_IP; if (rtm->rtm_family == AF_INET6) afi = AFI_IP6; if (h->nlmsg_type == RTM_NEWROUTE) { struct interface *ifp; vrf_id_t nh_vrf_id = vrf_id; if (!tb[RTA_MULTIPATH]) { struct nexthop nh; size_t sz = (afi == AFI_IP) ? 4 : 16; memset(&nh, 0, sizeof(nh)); if (bh_type == BLACKHOLE_UNSPEC) { if (index && !gate) nh.type = NEXTHOP_TYPE_IFINDEX; else if (index && gate) nh.type = (afi == AFI_IP) ? NEXTHOP_TYPE_IPV4_IFINDEX : NEXTHOP_TYPE_IPV6_IFINDEX; else if (!index && gate) nh.type = (afi == AFI_IP) ? NEXTHOP_TYPE_IPV4 : NEXTHOP_TYPE_IPV6; else { nh.type = NEXTHOP_TYPE_BLACKHOLE; nh.bh_type = bh_type; } } else { nh.type = NEXTHOP_TYPE_BLACKHOLE; nh.bh_type = bh_type; } nh.ifindex = index; if (prefsrc) memcpy(&nh.src, prefsrc, sz); if (gate) memcpy(&nh.gate, gate, sz); if (index) { ifp = if_lookup_by_index_per_ns( zebra_ns_lookup(ns_id), index); if (ifp) nh_vrf_id = ifp->vrf_id; } nh.vrf_id = nh_vrf_id; if (tb[RTA_ENCAP] && tb[RTA_ENCAP_TYPE] && *(uint16_t *)RTA_DATA(tb[RTA_ENCAP_TYPE]) == LWTUNNEL_ENCAP_MPLS) { num_labels = parse_encap_mpls(tb[RTA_ENCAP], labels); } if (rtm->rtm_flags & RTNH_F_ONLINK) SET_FLAG(nh.flags, NEXTHOP_FLAG_ONLINK); if (num_labels) nexthop_add_labels(&nh, ZEBRA_LSP_STATIC, num_labels, labels); rib_add(afi, SAFI_UNICAST, vrf_id, proto, 0, flags, &p, &src_p, &nh, table, metric, mtu, distance, tag); } else { /* This is a multipath route */ struct route_entry *re; struct rtnexthop *rtnh = (struct rtnexthop *)RTA_DATA(tb[RTA_MULTIPATH]); len = RTA_PAYLOAD(tb[RTA_MULTIPATH]); re = XCALLOC(MTYPE_RE, sizeof(struct route_entry)); re->type = proto; re->distance = distance; re->flags = flags; re->metric = metric; re->mtu = mtu; re->vrf_id = vrf_id; re->table = table; re->nexthop_num = 0; re->uptime = monotime(NULL); re->tag = tag; for (;;) { struct nexthop *nh = NULL; if (len < (int)sizeof(*rtnh) || rtnh->rtnh_len > len) break; index = rtnh->rtnh_ifindex; if (index) { /* * Yes we are looking this up * for every nexthop and just * using the last one looked * up right now */ ifp = if_lookup_by_index_per_ns( zebra_ns_lookup(ns_id), index); if (ifp) nh_vrf_id = ifp->vrf_id; else { flog_warn( EC_ZEBRA_UNKNOWN_INTERFACE, "%s: Unknown interface %u specified, defaulting to VRF_DEFAULT", __PRETTY_FUNCTION__, index); nh_vrf_id = VRF_DEFAULT; } } else nh_vrf_id = vrf_id; gate = 0; if (rtnh->rtnh_len > sizeof(*rtnh)) { memset(tb, 0, sizeof(tb)); netlink_parse_rtattr( tb, RTA_MAX, RTNH_DATA(rtnh), rtnh->rtnh_len - sizeof(*rtnh)); if (tb[RTA_GATEWAY]) gate = RTA_DATA( tb[RTA_GATEWAY]); if (tb[RTA_ENCAP] && tb[RTA_ENCAP_TYPE] && *(uint16_t *)RTA_DATA( tb[RTA_ENCAP_TYPE]) == LWTUNNEL_ENCAP_MPLS) { num_labels = parse_encap_mpls( tb[RTA_ENCAP], labels); } } if (gate) { if (rtm->rtm_family == AF_INET) { if (index) nh = route_entry_nexthop_ipv4_ifindex_add( re, gate, prefsrc, index, nh_vrf_id); else nh = route_entry_nexthop_ipv4_add( re, gate, prefsrc, nh_vrf_id); } else if (rtm->rtm_family == AF_INET6) { if (index) nh = route_entry_nexthop_ipv6_ifindex_add( re, gate, index, nh_vrf_id); else nh = route_entry_nexthop_ipv6_add( re, gate, nh_vrf_id); } } else nh = route_entry_nexthop_ifindex_add( re, index, nh_vrf_id); if (nh && num_labels) nexthop_add_labels(nh, ZEBRA_LSP_STATIC, num_labels, labels); if (nh && (rtnh->rtnh_flags & RTNH_F_ONLINK)) SET_FLAG(nh->flags, NEXTHOP_FLAG_ONLINK); if (rtnh->rtnh_len == 0) break; len -= NLMSG_ALIGN(rtnh->rtnh_len); rtnh = RTNH_NEXT(rtnh); } zserv_nexthop_num_warn(__func__, (const struct prefix *)&p, re->nexthop_num); if (re->nexthop_num == 0) XFREE(MTYPE_RE, re); else rib_add_multipath(afi, SAFI_UNICAST, &p, &src_p, re); } } else { if (!tb[RTA_MULTIPATH]) { struct nexthop nh; size_t sz = (afi == AFI_IP) ? 4 : 16; memset(&nh, 0, sizeof(nh)); if (bh_type == BLACKHOLE_UNSPEC) { if (index && !gate) nh.type = NEXTHOP_TYPE_IFINDEX; else if (index && gate) nh.type = (afi == AFI_IP) ? NEXTHOP_TYPE_IPV4_IFINDEX : NEXTHOP_TYPE_IPV6_IFINDEX; else if (!index && gate) nh.type = (afi == AFI_IP) ? NEXTHOP_TYPE_IPV4 : NEXTHOP_TYPE_IPV6; else { nh.type = NEXTHOP_TYPE_BLACKHOLE; nh.bh_type = BLACKHOLE_UNSPEC; } } else { nh.type = NEXTHOP_TYPE_BLACKHOLE; nh.bh_type = bh_type; } nh.ifindex = index; if (gate) memcpy(&nh.gate, gate, sz); rib_delete(afi, SAFI_UNICAST, vrf_id, proto, 0, flags, &p, &src_p, &nh, table, metric, distance, true); } else { /* XXX: need to compare the entire list of nexthops * here for NLM_F_APPEND stupidity */ rib_delete(afi, SAFI_UNICAST, vrf_id, proto, 0, flags, &p, &src_p, NULL, table, metric, distance, true); } } return 0; } static struct mcast_route_data *mroute = NULL; static int netlink_route_change_read_multicast(struct nlmsghdr *h, ns_id_t ns_id, int startup) { int len; struct rtmsg *rtm; struct rtattr *tb[RTA_MAX + 1]; struct mcast_route_data *m; struct mcast_route_data mr; int iif = 0; int count; int oif[256]; int oif_count = 0; char sbuf[40]; char gbuf[40]; char oif_list[256] = "\0"; vrf_id_t vrf; int table; if (mroute) m = mroute; else { memset(&mr, 0, sizeof(mr)); m = &mr; } rtm = NLMSG_DATA(h); len = h->nlmsg_len - NLMSG_LENGTH(sizeof(struct rtmsg)); memset(tb, 0, sizeof tb); netlink_parse_rtattr(tb, RTA_MAX, RTM_RTA(rtm), len); if (tb[RTA_TABLE]) table = *(int *)RTA_DATA(tb[RTA_TABLE]); else table = rtm->rtm_table; vrf = vrf_lookup_by_table(table, ns_id); if (tb[RTA_IIF]) iif = *(int *)RTA_DATA(tb[RTA_IIF]); if (tb[RTA_SRC]) m->sg.src = *(struct in_addr *)RTA_DATA(tb[RTA_SRC]); if (tb[RTA_DST]) m->sg.grp = *(struct in_addr *)RTA_DATA(tb[RTA_DST]); if (tb[RTA_EXPIRES]) m->lastused = *(unsigned long long *)RTA_DATA(tb[RTA_EXPIRES]); if (tb[RTA_MULTIPATH]) { struct rtnexthop *rtnh = (struct rtnexthop *)RTA_DATA(tb[RTA_MULTIPATH]); len = RTA_PAYLOAD(tb[RTA_MULTIPATH]); for (;;) { if (len < (int)sizeof(*rtnh) || rtnh->rtnh_len > len) break; oif[oif_count] = rtnh->rtnh_ifindex; oif_count++; if (rtnh->rtnh_len == 0) break; len -= NLMSG_ALIGN(rtnh->rtnh_len); rtnh = RTNH_NEXT(rtnh); } } if (IS_ZEBRA_DEBUG_KERNEL) { struct interface *ifp = NULL; struct zebra_vrf *zvrf = NULL; strlcpy(sbuf, inet_ntoa(m->sg.src), sizeof(sbuf)); strlcpy(gbuf, inet_ntoa(m->sg.grp), sizeof(gbuf)); for (count = 0; count < oif_count; count++) { ifp = if_lookup_by_index(oif[count], vrf); char temp[256]; sprintf(temp, "%s(%d) ", ifp ? ifp->name : "Unknown", oif[count]); strlcat(oif_list, temp, sizeof(oif_list)); } zvrf = zebra_vrf_lookup_by_id(vrf); ifp = if_lookup_by_index(iif, vrf); zlog_debug( "MCAST VRF: %s(%d) %s (%s,%s) IIF: %s(%d) OIF: %s jiffies: %lld", (zvrf ? zvrf->vrf->name : "Unknown"), vrf, nl_msg_type_to_str(h->nlmsg_type), sbuf, gbuf, ifp ? ifp->name : "Unknown", iif, oif_list, m->lastused); } return 0; } int netlink_route_change(struct nlmsghdr *h, ns_id_t ns_id, int startup) { int len; struct rtmsg *rtm; rtm = NLMSG_DATA(h); if (!(h->nlmsg_type == RTM_NEWROUTE || h->nlmsg_type == RTM_DELROUTE)) { /* If this is not route add/delete message print warning. */ zlog_debug("Kernel message: %s NS %u", nl_msg_type_to_str(h->nlmsg_type), ns_id); return 0; } if (!(rtm->rtm_family == AF_INET || rtm->rtm_family == AF_INET6 || rtm->rtm_family == RTNL_FAMILY_IPMR )) { flog_warn( EC_ZEBRA_UNKNOWN_FAMILY, "Invalid address family: %u received from kernel route change: %s", rtm->rtm_family, nl_msg_type_to_str(h->nlmsg_type)); return 0; } /* Connected route. */ if (IS_ZEBRA_DEBUG_KERNEL) zlog_debug("%s %s %s proto %s NS %u", nl_msg_type_to_str(h->nlmsg_type), nl_family_to_str(rtm->rtm_family), nl_rttype_to_str(rtm->rtm_type), nl_rtproto_to_str(rtm->rtm_protocol), ns_id); len = h->nlmsg_len - NLMSG_LENGTH(sizeof(struct rtmsg)); if (len < 0) { zlog_err("%s: Message received from netlink is of a broken size: %d %zu", __PRETTY_FUNCTION__, h->nlmsg_len, (size_t)NLMSG_LENGTH(sizeof(struct rtmsg))); return -1; } if (rtm->rtm_type == RTN_MULTICAST) netlink_route_change_read_multicast(h, ns_id, startup); else netlink_route_change_read_unicast(h, ns_id, startup); return 0; } /* Request for specific route information from the kernel */ static int netlink_request_route(struct zebra_ns *zns, int family, int type) { struct { struct nlmsghdr n; struct rtmsg rtm; } req; /* Form the request, specifying filter (rtattr) if needed. */ memset(&req, 0, sizeof(req)); req.n.nlmsg_type = type; req.n.nlmsg_flags = NLM_F_ROOT | NLM_F_MATCH | NLM_F_REQUEST; req.n.nlmsg_len = NLMSG_LENGTH(sizeof(struct rtmsg)); req.rtm.rtm_family = family; return netlink_request(&zns->netlink_cmd, &req.n); } /* Routing table read function using netlink interface. Only called bootstrap time. */ int netlink_route_read(struct zebra_ns *zns) { int ret; struct zebra_dplane_info dp_info; zebra_dplane_info_from_zns(&dp_info, zns, true /*is_cmd*/); /* Get IPv4 routing table. */ ret = netlink_request_route(zns, AF_INET, RTM_GETROUTE); if (ret < 0) return ret; ret = netlink_parse_info(netlink_route_change_read_unicast, &zns->netlink_cmd, &dp_info, 0, 1); if (ret < 0) return ret; /* Get IPv6 routing table. */ ret = netlink_request_route(zns, AF_INET6, RTM_GETROUTE); if (ret < 0) return ret; ret = netlink_parse_info(netlink_route_change_read_unicast, &zns->netlink_cmd, &dp_info, 0, 1); if (ret < 0) return ret; return 0; } static void _netlink_route_nl_add_gateway_info(uint8_t route_family, uint8_t gw_family, struct nlmsghdr *nlmsg, size_t req_size, int bytelen, const struct nexthop *nexthop) { if (route_family == AF_MPLS) { struct gw_family_t gw_fam; gw_fam.family = gw_family; if (gw_family == AF_INET) memcpy(&gw_fam.gate.ipv4, &nexthop->gate.ipv4, bytelen); else memcpy(&gw_fam.gate.ipv6, &nexthop->gate.ipv6, bytelen); addattr_l(nlmsg, req_size, RTA_VIA, &gw_fam.family, bytelen + 2); } else { if (gw_family == AF_INET) addattr_l(nlmsg, req_size, RTA_GATEWAY, &nexthop->gate.ipv4, bytelen); else addattr_l(nlmsg, req_size, RTA_GATEWAY, &nexthop->gate.ipv6, bytelen); } } static void _netlink_route_rta_add_gateway_info(uint8_t route_family, uint8_t gw_family, struct rtattr *rta, struct rtnexthop *rtnh, size_t req_size, int bytelen, const struct nexthop *nexthop) { if (route_family == AF_MPLS) { struct gw_family_t gw_fam; gw_fam.family = gw_family; if (gw_family == AF_INET) memcpy(&gw_fam.gate.ipv4, &nexthop->gate.ipv4, bytelen); else memcpy(&gw_fam.gate.ipv6, &nexthop->gate.ipv6, bytelen); rta_addattr_l(rta, req_size, RTA_VIA, &gw_fam.family, bytelen + 2); rtnh->rtnh_len += RTA_LENGTH(bytelen + 2); } else { if (gw_family == AF_INET) rta_addattr_l(rta, req_size, RTA_GATEWAY, &nexthop->gate.ipv4, bytelen); else rta_addattr_l(rta, req_size, RTA_GATEWAY, &nexthop->gate.ipv6, bytelen); rtnh->rtnh_len += sizeof(struct rtattr) + bytelen; } } /* This function takes a nexthop as argument and adds * the appropriate netlink attributes to an existing * netlink message. * * @param routedesc: Human readable description of route type * (direct/recursive, single-/multipath) * @param bytelen: Length of addresses in bytes. * @param nexthop: Nexthop information * @param nlmsg: nlmsghdr structure to fill in. * @param req_size: The size allocated for the message. */ static void _netlink_route_build_singlepath(const char *routedesc, int bytelen, const struct nexthop *nexthop, struct nlmsghdr *nlmsg, struct rtmsg *rtmsg, size_t req_size, int cmd) { struct mpls_label_stack *nh_label; mpls_lse_t out_lse[MPLS_MAX_LABELS]; int num_labels = 0; char label_buf[256]; /* * label_buf is *only* currently used within debugging. * As such when we assign it we are guarding it inside * a debug test. If you want to change this make sure * you fix this assumption */ label_buf[0] = '\0'; assert(nexthop); char label_buf1[20]; nh_label = nexthop->nh_label; for (int i = 0; nh_label && i < nh_label->num_labels; i++) { if (nh_label->label[i] == MPLS_LABEL_IMPLICIT_NULL) continue; if (IS_ZEBRA_DEBUG_KERNEL) { if (!num_labels) sprintf(label_buf, "label %u", nh_label->label[i]); else { sprintf(label_buf1, "/%u", nh_label->label[i]); strlcat(label_buf, label_buf1, sizeof(label_buf)); } } out_lse[num_labels] = mpls_lse_encode(nh_label->label[i], 0, 0, 0); num_labels++; } if (num_labels) { /* Set the BoS bit */ out_lse[num_labels - 1] |= htonl(1 << MPLS_LS_S_SHIFT); if (rtmsg->rtm_family == AF_MPLS) addattr_l(nlmsg, req_size, RTA_NEWDST, &out_lse, num_labels * sizeof(mpls_lse_t)); else { struct rtattr *nest; uint16_t encap = LWTUNNEL_ENCAP_MPLS; addattr_l(nlmsg, req_size, RTA_ENCAP_TYPE, &encap, sizeof(uint16_t)); nest = addattr_nest(nlmsg, req_size, RTA_ENCAP); addattr_l(nlmsg, req_size, MPLS_IPTUNNEL_DST, &out_lse, num_labels * sizeof(mpls_lse_t)); addattr_nest_end(nlmsg, nest); } } if (CHECK_FLAG(nexthop->flags, NEXTHOP_FLAG_ONLINK)) rtmsg->rtm_flags |= RTNH_F_ONLINK; if (rtmsg->rtm_family == AF_INET && (nexthop->type == NEXTHOP_TYPE_IPV6 || nexthop->type == NEXTHOP_TYPE_IPV6_IFINDEX)) { rtmsg->rtm_flags |= RTNH_F_ONLINK; addattr_l(nlmsg, req_size, RTA_GATEWAY, &ipv4_ll, 4); addattr32(nlmsg, req_size, RTA_OIF, nexthop->ifindex); if (nexthop->rmap_src.ipv4.s_addr && (cmd == RTM_NEWROUTE)) addattr_l(nlmsg, req_size, RTA_PREFSRC, &nexthop->rmap_src.ipv4, bytelen); else if (nexthop->src.ipv4.s_addr && (cmd == RTM_NEWROUTE)) addattr_l(nlmsg, req_size, RTA_PREFSRC, &nexthop->src.ipv4, bytelen); if (IS_ZEBRA_DEBUG_KERNEL) zlog_debug( " 5549: _netlink_route_build_singlepath() (%s): " "nexthop via %s %s if %u(%u)", routedesc, ipv4_ll_buf, label_buf, nexthop->ifindex, nexthop->vrf_id); return; } if (nexthop->type == NEXTHOP_TYPE_IPV4 || nexthop->type == NEXTHOP_TYPE_IPV4_IFINDEX) { /* Send deletes to the kernel without specifying the next-hop */ if (cmd != RTM_DELROUTE) _netlink_route_nl_add_gateway_info( rtmsg->rtm_family, AF_INET, nlmsg, req_size, bytelen, nexthop); if (cmd == RTM_NEWROUTE) { if (nexthop->rmap_src.ipv4.s_addr) addattr_l(nlmsg, req_size, RTA_PREFSRC, &nexthop->rmap_src.ipv4, bytelen); else if (nexthop->src.ipv4.s_addr) addattr_l(nlmsg, req_size, RTA_PREFSRC, &nexthop->src.ipv4, bytelen); } if (IS_ZEBRA_DEBUG_KERNEL) zlog_debug( "netlink_route_multipath() (%s): " "nexthop via %s %s if %u(%u)", routedesc, inet_ntoa(nexthop->gate.ipv4), label_buf, nexthop->ifindex, nexthop->vrf_id); } if (nexthop->type == NEXTHOP_TYPE_IPV6 || nexthop->type == NEXTHOP_TYPE_IPV6_IFINDEX) { _netlink_route_nl_add_gateway_info(rtmsg->rtm_family, AF_INET6, nlmsg, req_size, bytelen, nexthop); if (cmd == RTM_NEWROUTE) { if (!IN6_IS_ADDR_UNSPECIFIED(&nexthop->rmap_src.ipv6)) addattr_l(nlmsg, req_size, RTA_PREFSRC, &nexthop->rmap_src.ipv6, bytelen); else if (!IN6_IS_ADDR_UNSPECIFIED(&nexthop->src.ipv6)) addattr_l(nlmsg, req_size, RTA_PREFSRC, &nexthop->src.ipv6, bytelen); } if (IS_ZEBRA_DEBUG_KERNEL) zlog_debug( "netlink_route_multipath() (%s): " "nexthop via %s %s if %u(%u)", routedesc, inet6_ntoa(nexthop->gate.ipv6), label_buf, nexthop->ifindex, nexthop->vrf_id); } /* * We have the ifindex so we should always send it * This is especially useful if we are doing route * leaking. */ if (nexthop->type != NEXTHOP_TYPE_BLACKHOLE) addattr32(nlmsg, req_size, RTA_OIF, nexthop->ifindex); if (nexthop->type == NEXTHOP_TYPE_IFINDEX) { if (cmd == RTM_NEWROUTE) { if (nexthop->rmap_src.ipv4.s_addr) addattr_l(nlmsg, req_size, RTA_PREFSRC, &nexthop->rmap_src.ipv4, bytelen); else if (nexthop->src.ipv4.s_addr) addattr_l(nlmsg, req_size, RTA_PREFSRC, &nexthop->src.ipv4, bytelen); } if (IS_ZEBRA_DEBUG_KERNEL) zlog_debug( "netlink_route_multipath() (%s): " "nexthop via if %u(%u)", routedesc, nexthop->ifindex, nexthop->vrf_id); } } /* This function takes a nexthop as argument and * appends to the given rtattr/rtnexthop pair the * representation of the nexthop. If the nexthop * defines a preferred source, the src parameter * will be modified to point to that src, otherwise * it will be kept unmodified. * * @param routedesc: Human readable description of route type * (direct/recursive, single-/multipath) * @param bytelen: Length of addresses in bytes. * @param nexthop: Nexthop information * @param rta: rtnetlink attribute structure * @param rtnh: pointer to an rtnetlink nexthop structure * @param src: pointer pointing to a location where * the prefsrc should be stored. */ static void _netlink_route_build_multipath(const char *routedesc, int bytelen, const struct nexthop *nexthop, struct rtattr *rta, struct rtnexthop *rtnh, struct rtmsg *rtmsg, const union g_addr **src) { struct mpls_label_stack *nh_label; mpls_lse_t out_lse[MPLS_MAX_LABELS]; int num_labels = 0; char label_buf[256]; rtnh->rtnh_len = sizeof(*rtnh); rtnh->rtnh_flags = 0; rtnh->rtnh_hops = 0; rta->rta_len += rtnh->rtnh_len; /* * label_buf is *only* currently used within debugging. * As such when we assign it we are guarding it inside * a debug test. If you want to change this make sure * you fix this assumption */ label_buf[0] = '\0'; assert(nexthop); char label_buf1[20]; nh_label = nexthop->nh_label; for (int i = 0; nh_label && i < nh_label->num_labels; i++) { if (nh_label->label[i] == MPLS_LABEL_IMPLICIT_NULL) continue; if (IS_ZEBRA_DEBUG_KERNEL) { if (!num_labels) sprintf(label_buf, "label %u", nh_label->label[i]); else { sprintf(label_buf1, "/%u", nh_label->label[i]); strlcat(label_buf, label_buf1, sizeof(label_buf)); } } out_lse[num_labels] = mpls_lse_encode(nh_label->label[i], 0, 0, 0); num_labels++; } if (num_labels) { /* Set the BoS bit */ out_lse[num_labels - 1] |= htonl(1 << MPLS_LS_S_SHIFT); if (rtmsg->rtm_family == AF_MPLS) { rta_addattr_l(rta, NL_PKT_BUF_SIZE, RTA_NEWDST, &out_lse, num_labels * sizeof(mpls_lse_t)); rtnh->rtnh_len += RTA_LENGTH(num_labels * sizeof(mpls_lse_t)); } else { struct rtattr *nest; uint16_t encap = LWTUNNEL_ENCAP_MPLS; int len = rta->rta_len; rta_addattr_l(rta, NL_PKT_BUF_SIZE, RTA_ENCAP_TYPE, &encap, sizeof(uint16_t)); nest = rta_nest(rta, NL_PKT_BUF_SIZE, RTA_ENCAP); rta_addattr_l(rta, NL_PKT_BUF_SIZE, MPLS_IPTUNNEL_DST, &out_lse, num_labels * sizeof(mpls_lse_t)); rta_nest_end(rta, nest); rtnh->rtnh_len += rta->rta_len - len; } } if (CHECK_FLAG(nexthop->flags, NEXTHOP_FLAG_ONLINK)) rtnh->rtnh_flags |= RTNH_F_ONLINK; if (rtmsg->rtm_family == AF_INET && (nexthop->type == NEXTHOP_TYPE_IPV6 || nexthop->type == NEXTHOP_TYPE_IPV6_IFINDEX)) { bytelen = 4; rtnh->rtnh_flags |= RTNH_F_ONLINK; rta_addattr_l(rta, NL_PKT_BUF_SIZE, RTA_GATEWAY, &ipv4_ll, bytelen); rtnh->rtnh_len += sizeof(struct rtattr) + bytelen; rtnh->rtnh_ifindex = nexthop->ifindex; if (nexthop->rmap_src.ipv4.s_addr) *src = &nexthop->rmap_src; else if (nexthop->src.ipv4.s_addr) *src = &nexthop->src; if (IS_ZEBRA_DEBUG_KERNEL) zlog_debug( " 5549: netlink_route_build_multipath() (%s): " "nexthop via %s %s if %u", routedesc, ipv4_ll_buf, label_buf, nexthop->ifindex); return; } if (nexthop->type == NEXTHOP_TYPE_IPV4 || nexthop->type == NEXTHOP_TYPE_IPV4_IFINDEX) { _netlink_route_rta_add_gateway_info(rtmsg->rtm_family, AF_INET, rta, rtnh, NL_PKT_BUF_SIZE, bytelen, nexthop); if (nexthop->rmap_src.ipv4.s_addr) *src = &nexthop->rmap_src; else if (nexthop->src.ipv4.s_addr) *src = &nexthop->src; if (IS_ZEBRA_DEBUG_KERNEL) zlog_debug( "netlink_route_multipath() (%s): " "nexthop via %s %s if %u", routedesc, inet_ntoa(nexthop->gate.ipv4), label_buf, nexthop->ifindex); } if (nexthop->type == NEXTHOP_TYPE_IPV6 || nexthop->type == NEXTHOP_TYPE_IPV6_IFINDEX) { _netlink_route_rta_add_gateway_info(rtmsg->rtm_family, AF_INET6, rta, rtnh, NL_PKT_BUF_SIZE, bytelen, nexthop); if (!IN6_IS_ADDR_UNSPECIFIED(&nexthop->rmap_src.ipv6)) *src = &nexthop->rmap_src; else if (!IN6_IS_ADDR_UNSPECIFIED(&nexthop->src.ipv6)) *src = &nexthop->src; if (IS_ZEBRA_DEBUG_KERNEL) zlog_debug( "netlink_route_multipath() (%s): " "nexthop via %s %s if %u", routedesc, inet6_ntoa(nexthop->gate.ipv6), label_buf, nexthop->ifindex); } /* * We have figured out the ifindex so we should always send it * This is especially useful if we are doing route * leaking. */ if (nexthop->type != NEXTHOP_TYPE_BLACKHOLE) rtnh->rtnh_ifindex = nexthop->ifindex; /* ifindex */ if (nexthop->type == NEXTHOP_TYPE_IFINDEX) { if (nexthop->rmap_src.ipv4.s_addr) *src = &nexthop->rmap_src; else if (nexthop->src.ipv4.s_addr) *src = &nexthop->src; if (IS_ZEBRA_DEBUG_KERNEL) zlog_debug( "netlink_route_multipath() (%s): " "nexthop via if %u", routedesc, nexthop->ifindex); } } static inline void _netlink_mpls_build_singlepath(const char *routedesc, const zebra_nhlfe_t *nhlfe, struct nlmsghdr *nlmsg, struct rtmsg *rtmsg, size_t req_size, int cmd) { int bytelen; uint8_t family; family = NHLFE_FAMILY(nhlfe); bytelen = (family == AF_INET ? 4 : 16); _netlink_route_build_singlepath(routedesc, bytelen, nhlfe->nexthop, nlmsg, rtmsg, req_size, cmd); } static inline void _netlink_mpls_build_multipath(const char *routedesc, const zebra_nhlfe_t *nhlfe, struct rtattr *rta, struct rtnexthop *rtnh, struct rtmsg *rtmsg, const union g_addr **src) { int bytelen; uint8_t family; family = NHLFE_FAMILY(nhlfe); bytelen = (family == AF_INET ? 4 : 16); _netlink_route_build_multipath(routedesc, bytelen, nhlfe->nexthop, rta, rtnh, rtmsg, src); } /* Log debug information for netlink_route_multipath * if debug logging is enabled. * * @param cmd: Netlink command which is to be processed * @param p: Prefix for which the change is due * @param family: Address family which the change concerns * @param zvrf: The vrf we are in * @param tableid: The table we are working on */ static void _netlink_route_debug(int cmd, const struct prefix *p, int family, vrf_id_t vrfid, uint32_t tableid) { if (IS_ZEBRA_DEBUG_KERNEL) { char buf[PREFIX_STRLEN]; zlog_debug( "netlink_route_multipath(): %s %s vrf %u(%u)", nl_msg_type_to_str(cmd), prefix2str(p, buf, sizeof(buf)), vrfid, tableid); } } static void _netlink_mpls_debug(int cmd, uint32_t label, const char *routedesc) { if (IS_ZEBRA_DEBUG_KERNEL) zlog_debug("netlink_mpls_multipath() (%s): %s %u/20", routedesc, nl_msg_type_to_str(cmd), label); } static int netlink_neigh_update(int cmd, int ifindex, uint32_t addr, char *lla, int llalen, ns_id_t ns_id) { struct { struct nlmsghdr n; struct ndmsg ndm; char buf[256]; } req; struct zebra_ns *zns = zebra_ns_lookup(ns_id); memset(&req, 0, sizeof(req)); req.n.nlmsg_len = NLMSG_LENGTH(sizeof(struct ndmsg)); req.n.nlmsg_flags = NLM_F_CREATE | NLM_F_REQUEST; req.n.nlmsg_type = cmd; // RTM_NEWNEIGH or RTM_DELNEIGH req.n.nlmsg_pid = zns->netlink_cmd.snl.nl_pid; req.ndm.ndm_family = AF_INET; req.ndm.ndm_state = NUD_PERMANENT; req.ndm.ndm_ifindex = ifindex; req.ndm.ndm_type = RTN_UNICAST; addattr_l(&req.n, sizeof(req), NDA_DST, &addr, 4); addattr_l(&req.n, sizeof(req), NDA_LLADDR, lla, llalen); return netlink_talk(netlink_talk_filter, &req.n, &zns->netlink_cmd, zns, 0); } /* * Routing table change via netlink interface, using a dataplane context object */ static int netlink_route_multipath(int cmd, struct zebra_dplane_ctx *ctx) { int bytelen; struct nexthop *nexthop = NULL; unsigned int nexthop_num; int family; const char *routedesc; int setsrc = 0; union g_addr src; const struct prefix *p, *src_p; uint32_t table_id; struct { struct nlmsghdr n; struct rtmsg r; char buf[NL_PKT_BUF_SIZE]; } req; p = dplane_ctx_get_dest(ctx); src_p = dplane_ctx_get_src(ctx); family = PREFIX_FAMILY(p); memset(&req, 0, sizeof(req) - NL_PKT_BUF_SIZE); bytelen = (family == AF_INET ? 4 : 16); req.n.nlmsg_len = NLMSG_LENGTH(sizeof(struct rtmsg)); req.n.nlmsg_flags = NLM_F_CREATE | NLM_F_REQUEST; if ((cmd == RTM_NEWROUTE) && ((p->family == AF_INET) || v6_rr_semantics)) req.n.nlmsg_flags |= NLM_F_REPLACE; req.n.nlmsg_type = cmd; req.n.nlmsg_pid = dplane_ctx_get_ns(ctx)->nls.snl.nl_pid; req.r.rtm_family = family; req.r.rtm_dst_len = p->prefixlen; req.r.rtm_src_len = src_p ? src_p->prefixlen : 0; req.r.rtm_scope = RT_SCOPE_UNIVERSE; if (cmd == RTM_DELROUTE) req.r.rtm_protocol = zebra2proto(dplane_ctx_get_old_type(ctx)); else req.r.rtm_protocol = zebra2proto(dplane_ctx_get_type(ctx)); /* * blackhole routes are not RTN_UNICAST, they are * RTN_ BLACKHOLE|UNREACHABLE|PROHIBIT * so setting this value as a RTN_UNICAST would * cause the route lookup of just the prefix * to fail. So no need to specify this for * the RTM_DELROUTE case */ if (cmd != RTM_DELROUTE) req.r.rtm_type = RTN_UNICAST; addattr_l(&req.n, sizeof(req), RTA_DST, &p->u.prefix, bytelen); if (src_p) addattr_l(&req.n, sizeof(req), RTA_SRC, &src_p->u.prefix, bytelen); /* Metric. */ /* Hardcode the metric for all routes coming from zebra. Metric isn't * used * either by the kernel or by zebra. Its purely for calculating best * path(s) * by the routing protocol and for communicating with protocol peers. */ addattr32(&req.n, sizeof(req), RTA_PRIORITY, NL_DEFAULT_ROUTE_METRIC); #if defined(SUPPORT_REALMS) { route_tag_t tag; if (cmd == RTM_DELROUTE) tag = dplane_ctx_get_old_tag(ctx); else tag = dplane_ctx_get_tag(ctx); if (tag > 0 && tag <= 255) addattr32(&req.n, sizeof(req), RTA_FLOW, tag); } #endif /* Table corresponding to this route. */ table_id = dplane_ctx_get_table(ctx); if (table_id < 256) req.r.rtm_table = table_id; else { req.r.rtm_table = RT_TABLE_UNSPEC; addattr32(&req.n, sizeof(req), RTA_TABLE, table_id); } _netlink_route_debug(cmd, p, family, dplane_ctx_get_vrf(ctx), table_id); /* * If we are not updating the route and we have received * a route delete, then all we need to fill in is the * prefix information to tell the kernel to schwack * it. */ if (cmd == RTM_DELROUTE) goto skip; if (dplane_ctx_get_mtu(ctx) || dplane_ctx_get_nh_mtu(ctx)) { char buf[NL_PKT_BUF_SIZE]; struct rtattr *rta = (void *)buf; uint32_t mtu = dplane_ctx_get_mtu(ctx); uint32_t nexthop_mtu = dplane_ctx_get_nh_mtu(ctx); if (!mtu || (nexthop_mtu && nexthop_mtu < mtu)) mtu = nexthop_mtu; rta->rta_type = RTA_METRICS; rta->rta_len = RTA_LENGTH(0); rta_addattr_l(rta, NL_PKT_BUF_SIZE, RTAX_MTU, &mtu, sizeof(mtu)); addattr_l(&req.n, NL_PKT_BUF_SIZE, RTA_METRICS, RTA_DATA(rta), RTA_PAYLOAD(rta)); } /* Count overall nexthops so we can decide whether to use singlepath * or multipath case. */ nexthop_num = 0; for (ALL_NEXTHOPS_PTR(dplane_ctx_get_ng(ctx), nexthop)) { if (CHECK_FLAG(nexthop->flags, NEXTHOP_FLAG_RECURSIVE)) continue; if (cmd == RTM_NEWROUTE && !NEXTHOP_IS_ACTIVE(nexthop->flags)) continue; nexthop_num++; } /* Singlepath case. */ if (nexthop_num == 1) { nexthop_num = 0; for (ALL_NEXTHOPS_PTR(dplane_ctx_get_ng(ctx), nexthop)) { /* * So we want to cover 2 types of blackhole * routes here: * 1) A normal blackhole route( ala from a static * install. * 2) A recursively resolved blackhole route */ if (nexthop->type == NEXTHOP_TYPE_BLACKHOLE) { switch (nexthop->bh_type) { case BLACKHOLE_ADMINPROHIB: req.r.rtm_type = RTN_PROHIBIT; break; case BLACKHOLE_REJECT: req.r.rtm_type = RTN_UNREACHABLE; break; default: req.r.rtm_type = RTN_BLACKHOLE; break; } goto skip; } if (CHECK_FLAG(nexthop->flags, NEXTHOP_FLAG_RECURSIVE)) { if (setsrc) continue; if (family == AF_INET) { if (nexthop->rmap_src.ipv4.s_addr != 0) { src.ipv4 = nexthop->rmap_src.ipv4; setsrc = 1; } else if (nexthop->src.ipv4.s_addr != 0) { src.ipv4 = nexthop->src.ipv4; setsrc = 1; } } else if (family == AF_INET6) { if (!IN6_IS_ADDR_UNSPECIFIED( &nexthop->rmap_src.ipv6)) { src.ipv6 = nexthop->rmap_src.ipv6; setsrc = 1; } else if ( !IN6_IS_ADDR_UNSPECIFIED( &nexthop->src.ipv6)) { src.ipv6 = nexthop->src.ipv6; setsrc = 1; } } continue; } if ((cmd == RTM_NEWROUTE && NEXTHOP_IS_ACTIVE(nexthop->flags))) { routedesc = nexthop->rparent ? "recursive, single-path" : "single-path"; _netlink_route_build_singlepath( routedesc, bytelen, nexthop, &req.n, &req.r, sizeof(req), cmd); nexthop_num++; break; } } if (setsrc && (cmd == RTM_NEWROUTE)) { if (family == AF_INET) addattr_l(&req.n, sizeof(req), RTA_PREFSRC, &src.ipv4, bytelen); else if (family == AF_INET6) addattr_l(&req.n, sizeof(req), RTA_PREFSRC, &src.ipv6, bytelen); } } else { /* Multipath case */ char buf[NL_PKT_BUF_SIZE]; struct rtattr *rta = (void *)buf; struct rtnexthop *rtnh; const union g_addr *src1 = NULL; rta->rta_type = RTA_MULTIPATH; rta->rta_len = RTA_LENGTH(0); rtnh = RTA_DATA(rta); nexthop_num = 0; for (ALL_NEXTHOPS_PTR(dplane_ctx_get_ng(ctx), nexthop)) { if (CHECK_FLAG(nexthop->flags, NEXTHOP_FLAG_RECURSIVE)) { /* This only works for IPv4 now */ if (setsrc) continue; if (family == AF_INET) { if (nexthop->rmap_src.ipv4.s_addr != 0) { src.ipv4 = nexthop->rmap_src.ipv4; setsrc = 1; } else if (nexthop->src.ipv4.s_addr != 0) { src.ipv4 = nexthop->src.ipv4; setsrc = 1; } } else if (family == AF_INET6) { if (!IN6_IS_ADDR_UNSPECIFIED( &nexthop->rmap_src.ipv6)) { src.ipv6 = nexthop->rmap_src.ipv6; setsrc = 1; } else if ( !IN6_IS_ADDR_UNSPECIFIED( &nexthop->src.ipv6)) { src.ipv6 = nexthop->src.ipv6; setsrc = 1; } } continue; } if ((cmd == RTM_NEWROUTE && NEXTHOP_IS_ACTIVE(nexthop->flags))) { routedesc = nexthop->rparent ? "recursive, multipath" : "multipath"; nexthop_num++; _netlink_route_build_multipath( routedesc, bytelen, nexthop, rta, rtnh, &req.r, &src1); rtnh = RTNH_NEXT(rtnh); if (!setsrc && src1) { if (family == AF_INET) src.ipv4 = src1->ipv4; else if (family == AF_INET6) src.ipv6 = src1->ipv6; setsrc = 1; } } } if (setsrc && (cmd == RTM_NEWROUTE)) { if (family == AF_INET) addattr_l(&req.n, sizeof(req), RTA_PREFSRC, &src.ipv4, bytelen); else if (family == AF_INET6) addattr_l(&req.n, sizeof(req), RTA_PREFSRC, &src.ipv6, bytelen); if (IS_ZEBRA_DEBUG_KERNEL) zlog_debug("Setting source"); } if (rta->rta_len > RTA_LENGTH(0)) addattr_l(&req.n, NL_PKT_BUF_SIZE, RTA_MULTIPATH, RTA_DATA(rta), RTA_PAYLOAD(rta)); } /* If there is no useful nexthop then return. */ if (nexthop_num == 0) { if (IS_ZEBRA_DEBUG_KERNEL) zlog_debug( "netlink_route_multipath(): No useful nexthop."); return 0; } skip: /* Talk to netlink socket. */ return netlink_talk_info(netlink_talk_filter, &req.n, dplane_ctx_get_ns(ctx), 0); } int kernel_get_ipmr_sg_stats(struct zebra_vrf *zvrf, void *in) { uint32_t actual_table; int suc = 0; struct mcast_route_data *mr = (struct mcast_route_data *)in; struct { struct nlmsghdr n; struct ndmsg ndm; char buf[256]; } req; mroute = mr; struct zebra_ns *zns; zns = zvrf->zns; memset(&req, 0, sizeof(req)); req.n.nlmsg_len = NLMSG_LENGTH(sizeof(struct ndmsg)); req.n.nlmsg_flags = NLM_F_REQUEST; req.n.nlmsg_pid = zns->netlink_cmd.snl.nl_pid; req.ndm.ndm_family = RTNL_FAMILY_IPMR; req.n.nlmsg_type = RTM_GETROUTE; addattr_l(&req.n, sizeof(req), RTA_IIF, &mroute->ifindex, 4); addattr_l(&req.n, sizeof(req), RTA_OIF, &mroute->ifindex, 4); addattr_l(&req.n, sizeof(req), RTA_SRC, &mroute->sg.src.s_addr, 4); addattr_l(&req.n, sizeof(req), RTA_DST, &mroute->sg.grp.s_addr, 4); /* * What? * * So during the namespace cleanup we started storing * the zvrf table_id for the default table as RT_TABLE_MAIN * which is what the normal routing table for ip routing is. * This change caused this to break our lookups of sg data * because prior to this change the zvrf->table_id was 0 * and when the pim multicast kernel code saw a 0, * it was auto-translated to RT_TABLE_DEFAULT. But since * we are now passing in RT_TABLE_MAIN there is no auto-translation * and the kernel goes screw you and the delicious cookies you * are trying to give me. So now we have this little hack. */ actual_table = (zvrf->table_id == RT_TABLE_MAIN) ? RT_TABLE_DEFAULT : zvrf->table_id; addattr_l(&req.n, sizeof(req), RTA_TABLE, &actual_table, 4); suc = netlink_talk(netlink_route_change_read_multicast, &req.n, &zns->netlink_cmd, zns, 0); mroute = NULL; return suc; } /* * Update or delete a prefix from the kernel, * using info from a dataplane context. */ enum zebra_dplane_result kernel_route_update(struct zebra_dplane_ctx *ctx) { int cmd, ret; const struct prefix *p = dplane_ctx_get_dest(ctx); struct nexthop *nexthop; if (dplane_ctx_get_op(ctx) == DPLANE_OP_ROUTE_DELETE) { cmd = RTM_DELROUTE; } else if (dplane_ctx_get_op(ctx) == DPLANE_OP_ROUTE_INSTALL) { cmd = RTM_NEWROUTE; } else if (dplane_ctx_get_op(ctx) == DPLANE_OP_ROUTE_UPDATE) { if (p->family == AF_INET || v6_rr_semantics) { /* Single 'replace' operation */ cmd = RTM_NEWROUTE; /* * With route replace semantics in place * for v4 routes and the new route is a system * route we do not install anything. * The problem here is that the new system * route should cause us to withdraw from * the kernel the old non-system route */ if (RSYSTEM_ROUTE(dplane_ctx_get_type(ctx)) && !RSYSTEM_ROUTE(dplane_ctx_get_old_type(ctx))) (void)netlink_route_multipath(RTM_DELROUTE, ctx); } else { /* * So v6 route replace semantics are not in * the kernel at this point as I understand it. * so let's do a delete then an add. * In the future once v6 route replace semantics * are in we can figure out what to do here to * allow working with old and new kernels. * * I'm also intentionally ignoring the failure case * of the route delete. If that happens yeah we're * screwed. */ if (!RSYSTEM_ROUTE(dplane_ctx_get_old_type(ctx))) (void)netlink_route_multipath(RTM_DELROUTE, ctx); cmd = RTM_NEWROUTE; } } else { return ZEBRA_DPLANE_REQUEST_FAILURE; } if (!RSYSTEM_ROUTE(dplane_ctx_get_type(ctx))) ret = netlink_route_multipath(cmd, ctx); else ret = 0; if ((cmd == RTM_NEWROUTE) && (ret == 0)) { /* Update installed nexthops to signal which have been * installed. */ for (ALL_NEXTHOPS_PTR(dplane_ctx_get_ng(ctx), nexthop)) { if (CHECK_FLAG(nexthop->flags, NEXTHOP_FLAG_RECURSIVE)) continue; if (CHECK_FLAG(nexthop->flags, NEXTHOP_FLAG_ACTIVE)) { SET_FLAG(nexthop->flags, NEXTHOP_FLAG_FIB); } } } return (ret == 0 ? ZEBRA_DPLANE_REQUEST_SUCCESS : ZEBRA_DPLANE_REQUEST_FAILURE); } int kernel_neigh_update(int add, int ifindex, uint32_t addr, char *lla, int llalen, ns_id_t ns_id) { return netlink_neigh_update(add ? RTM_NEWNEIGH : RTM_DELNEIGH, ifindex, addr, lla, llalen, ns_id); } /* * Add remote VTEP to the flood list for this VxLAN interface (VNI). This * is done by adding an FDB entry with a MAC of 00:00:00:00:00:00. */ static int netlink_vxlan_flood_update_ctx(const struct zebra_dplane_ctx *ctx, int cmd) { struct { struct nlmsghdr n; struct ndmsg ndm; char buf[256]; } req; uint8_t dst_mac[6] = {0x0, 0x0, 0x0, 0x0, 0x0, 0x0}; const struct ipaddr *addr; memset(&req, 0, sizeof(req)); req.n.nlmsg_len = NLMSG_LENGTH(sizeof(struct ndmsg)); req.n.nlmsg_flags = NLM_F_REQUEST; if (cmd == RTM_NEWNEIGH) req.n.nlmsg_flags |= (NLM_F_CREATE | NLM_F_APPEND); req.n.nlmsg_type = cmd; req.ndm.ndm_family = PF_BRIDGE; req.ndm.ndm_state = NUD_NOARP | NUD_PERMANENT; req.ndm.ndm_flags |= NTF_SELF; // Handle by "self", not "master" addattr_l(&req.n, sizeof(req), NDA_LLADDR, &dst_mac, 6); req.ndm.ndm_ifindex = dplane_ctx_get_ifindex(ctx); addr = dplane_ctx_neigh_get_ipaddr(ctx); addattr_l(&req.n, sizeof(req), NDA_DST, &(addr->ipaddr_v4), 4); return netlink_talk_info(netlink_talk_filter, &req.n, dplane_ctx_get_ns(ctx), 0); } #ifndef NDA_RTA #define NDA_RTA(r) \ ((struct rtattr *)(((char *)(r)) + NLMSG_ALIGN(sizeof(struct ndmsg)))) #endif static int netlink_macfdb_change(struct nlmsghdr *h, int len, ns_id_t ns_id) { struct ndmsg *ndm; struct interface *ifp; struct zebra_if *zif; struct rtattr *tb[NDA_MAX + 1]; struct interface *br_if; struct ethaddr mac; vlanid_t vid = 0; struct prefix vtep_ip; int vid_present = 0, dst_present = 0; char buf[ETHER_ADDR_STRLEN]; char vid_buf[20]; char dst_buf[30]; bool sticky; ndm = NLMSG_DATA(h); /* We only process macfdb notifications if EVPN is enabled */ if (!is_evpn_enabled()) return 0; /* The interface should exist. */ ifp = if_lookup_by_index_per_ns(zebra_ns_lookup(ns_id), ndm->ndm_ifindex); if (!ifp || !ifp->info) { if (IS_ZEBRA_DEBUG_KERNEL) zlog_debug("\t%s without associated interface: %u", __PRETTY_FUNCTION__, ndm->ndm_ifindex); return 0; } /* The interface should be something we're interested in. */ if (!IS_ZEBRA_IF_BRIDGE_SLAVE(ifp)) { if (IS_ZEBRA_DEBUG_KERNEL) zlog_debug("\t%s Not interested in %s, not a slave", __PRETTY_FUNCTION__, ifp->name); return 0; } /* Drop "permanent" entries. */ if (ndm->ndm_state & NUD_PERMANENT) { if (IS_ZEBRA_DEBUG_KERNEL) zlog_debug("\t%s Entry is PERMANENT, dropping", __PRETTY_FUNCTION__); return 0; } zif = (struct zebra_if *)ifp->info; if ((br_if = zif->brslave_info.br_if) == NULL) { if (IS_ZEBRA_DEBUG_KERNEL) zlog_debug( "%s family %s IF %s(%u) brIF %u - no bridge master", nl_msg_type_to_str(h->nlmsg_type), nl_family_to_str(ndm->ndm_family), ifp->name, ndm->ndm_ifindex, zif->brslave_info.bridge_ifindex); return 0; } /* Parse attributes and extract fields of interest. */ memset(tb, 0, sizeof tb); netlink_parse_rtattr(tb, NDA_MAX, NDA_RTA(ndm), len); if (!tb[NDA_LLADDR]) { if (IS_ZEBRA_DEBUG_KERNEL) zlog_debug("%s family %s IF %s(%u) brIF %u - no LLADDR", nl_msg_type_to_str(h->nlmsg_type), nl_family_to_str(ndm->ndm_family), ifp->name, ndm->ndm_ifindex, zif->brslave_info.bridge_ifindex); return 0; } if (RTA_PAYLOAD(tb[NDA_LLADDR]) != ETH_ALEN) { if (IS_ZEBRA_DEBUG_KERNEL) zlog_debug( "%s family %s IF %s(%u) brIF %u - LLADDR is not MAC, len %lu", nl_msg_type_to_str(h->nlmsg_type), nl_family_to_str(ndm->ndm_family), ifp->name, ndm->ndm_ifindex, zif->brslave_info.bridge_ifindex, (unsigned long)RTA_PAYLOAD(tb[NDA_LLADDR])); return 0; } memcpy(&mac, RTA_DATA(tb[NDA_LLADDR]), ETH_ALEN); if ((NDA_VLAN <= NDA_MAX) && tb[NDA_VLAN]) { vid_present = 1; vid = *(uint16_t *)RTA_DATA(tb[NDA_VLAN]); sprintf(vid_buf, " VLAN %u", vid); } if (tb[NDA_DST]) { /* TODO: Only IPv4 supported now. */ dst_present = 1; vtep_ip.family = AF_INET; vtep_ip.prefixlen = IPV4_MAX_BITLEN; memcpy(&(vtep_ip.u.prefix4.s_addr), RTA_DATA(tb[NDA_DST]), IPV4_MAX_BYTELEN); sprintf(dst_buf, " dst %s", inet_ntoa(vtep_ip.u.prefix4)); } sticky = !!(ndm->ndm_state & NUD_NOARP); if (IS_ZEBRA_DEBUG_KERNEL) zlog_debug("Rx %s family %s IF %s(%u)%s %sMAC %s%s", nl_msg_type_to_str(h->nlmsg_type), nl_family_to_str(ndm->ndm_family), ifp->name, ndm->ndm_ifindex, vid_present ? vid_buf : "", sticky ? "sticky " : "", prefix_mac2str(&mac, buf, sizeof(buf)), dst_present ? dst_buf : ""); if (filter_vlan && vid != filter_vlan) { if (IS_ZEBRA_DEBUG_KERNEL) zlog_debug("\tFiltered due to filter vlan: %d", filter_vlan); return 0; } /* If add or update, do accordingly if learnt on a "local" interface; if * the notification is over VxLAN, this has to be related to * multi-homing, * so perform an implicit delete of any local entry (if it exists). */ if (h->nlmsg_type == RTM_NEWNEIGH) { if (IS_ZEBRA_IF_VXLAN(ifp)) return zebra_vxlan_check_del_local_mac(ifp, br_if, &mac, vid); return zebra_vxlan_local_mac_add_update(ifp, br_if, &mac, vid, sticky); } /* This is a delete notification. * 1. For a MAC over VxLan, check if it needs to be refreshed(readded) * 2. For a MAC over "local" interface, delete the mac * Note: We will get notifications from both bridge driver and VxLAN * driver. * Ignore the notification from VxLan driver as it is also generated * when mac moves from remote to local. */ if (dst_present) { if (IS_ZEBRA_DEBUG_KERNEL) zlog_debug("\tNo Destination Present"); return 0; } if (IS_ZEBRA_IF_VXLAN(ifp)) return zebra_vxlan_check_readd_remote_mac(ifp, br_if, &mac, vid); return zebra_vxlan_local_mac_del(ifp, br_if, &mac, vid); } static int netlink_macfdb_table(struct nlmsghdr *h, ns_id_t ns_id, int startup) { int len; struct ndmsg *ndm; if (h->nlmsg_type != RTM_NEWNEIGH) return 0; /* Length validity. */ len = h->nlmsg_len - NLMSG_LENGTH(sizeof(struct ndmsg)); if (len < 0) return -1; /* We are interested only in AF_BRIDGE notifications. */ ndm = NLMSG_DATA(h); if (ndm->ndm_family != AF_BRIDGE) return 0; return netlink_macfdb_change(h, len, ns_id); } /* Request for MAC FDB information from the kernel */ static int netlink_request_macs(struct nlsock *netlink_cmd, int family, int type, ifindex_t master_ifindex) { struct { struct nlmsghdr n; struct ifinfomsg ifm; char buf[256]; } req; /* Form the request, specifying filter (rtattr) if needed. */ memset(&req, 0, sizeof(req)); req.n.nlmsg_type = type; req.n.nlmsg_flags = NLM_F_ROOT | NLM_F_MATCH | NLM_F_REQUEST; req.n.nlmsg_len = NLMSG_LENGTH(sizeof(struct ifinfomsg)); req.ifm.ifi_family = family; if (master_ifindex) addattr32(&req.n, sizeof(req), IFLA_MASTER, master_ifindex); return netlink_request(netlink_cmd, &req.n); } /* * MAC forwarding database read using netlink interface. This is invoked * at startup. */ int netlink_macfdb_read(struct zebra_ns *zns) { int ret; struct zebra_dplane_info dp_info; zebra_dplane_info_from_zns(&dp_info, zns, true /*is_cmd*/); /* Get bridge FDB table. */ ret = netlink_request_macs(&zns->netlink_cmd, AF_BRIDGE, RTM_GETNEIGH, 0); if (ret < 0) return ret; /* We are reading entire table. */ filter_vlan = 0; ret = netlink_parse_info(netlink_macfdb_table, &zns->netlink_cmd, &dp_info, 0, 1); return ret; } /* * MAC forwarding database read using netlink interface. This is for a * specific bridge and matching specific access VLAN (if VLAN-aware bridge). */ int netlink_macfdb_read_for_bridge(struct zebra_ns *zns, struct interface *ifp, struct interface *br_if) { struct zebra_if *br_zif; struct zebra_if *zif; struct zebra_l2info_vxlan *vxl; struct zebra_dplane_info dp_info; int ret = 0; zebra_dplane_info_from_zns(&dp_info, zns, true /*is_cmd*/); /* Save VLAN we're filtering on, if needed. */ br_zif = (struct zebra_if *)br_if->info; zif = (struct zebra_if *)ifp->info; vxl = &zif->l2info.vxl; if (IS_ZEBRA_IF_BRIDGE_VLAN_AWARE(br_zif)) filter_vlan = vxl->access_vlan; /* Get bridge FDB table for specific bridge - we do the VLAN filtering. */ ret = netlink_request_macs(&zns->netlink_cmd, AF_BRIDGE, RTM_GETNEIGH, br_if->ifindex); if (ret < 0) return ret; ret = netlink_parse_info(netlink_macfdb_table, &zns->netlink_cmd, &dp_info, 0, 0); /* Reset VLAN filter. */ filter_vlan = 0; return ret; } /* Request for MAC FDB for a specific MAC address in VLAN from the kernel */ static int netlink_request_specific_mac_in_bridge(struct zebra_ns *zns, int family, int type, struct interface *br_if, struct ethaddr *mac, vlanid_t vid) { struct { struct nlmsghdr n; struct ndmsg ndm; char buf[256]; } req; struct zebra_if *br_zif; char buf[ETHER_ADDR_STRLEN]; memset(&req, 0, sizeof(req)); req.n.nlmsg_len = NLMSG_LENGTH(sizeof(struct ndmsg)); req.n.nlmsg_type = type; /* RTM_GETNEIGH */ req.n.nlmsg_flags = NLM_F_REQUEST; req.ndm.ndm_family = family; /* AF_BRIDGE */ /* req.ndm.ndm_state = NUD_REACHABLE; */ addattr_l(&req.n, sizeof(req), NDA_LLADDR, mac, 6); br_zif = (struct zebra_if *)br_if->info; if (IS_ZEBRA_IF_BRIDGE_VLAN_AWARE(br_zif) && vid > 0) addattr16(&req.n, sizeof(req), NDA_VLAN, vid); addattr32(&req.n, sizeof(req), NDA_MASTER, br_if->ifindex); if (IS_ZEBRA_DEBUG_KERNEL) zlog_debug("%s: Tx family %s IF %s(%u) MAC %s vid %u", __PRETTY_FUNCTION__, nl_family_to_str(req.ndm.ndm_family), br_if->name, br_if->ifindex, prefix_mac2str(mac, buf, sizeof(buf)), vid); return netlink_request(&zns->netlink_cmd, &req.n); } int netlink_macfdb_read_specific_mac(struct zebra_ns *zns, struct interface *br_if, struct ethaddr *mac, vlanid_t vid) { int ret = 0; struct zebra_dplane_info dp_info; zebra_dplane_info_from_zns(&dp_info, zns, true /*is_cmd*/); /* Get bridge FDB table for specific bridge - we do the VLAN filtering. */ ret = netlink_request_specific_mac_in_bridge(zns, AF_BRIDGE, RTM_GETNEIGH, br_if, mac, vid); if (ret < 0) return ret; ret = netlink_parse_info(netlink_macfdb_table, &zns->netlink_cmd, &dp_info, 1, 0); return ret; } /* * Netlink-specific handler for MAC updates using dataplane context object. */ static enum zebra_dplane_result netlink_macfdb_update_ctx(struct zebra_dplane_ctx *ctx) { struct { struct nlmsghdr n; struct ndmsg ndm; char buf[256]; } req; int ret; int dst_alen; struct zebra_if *zif; struct interface *br_if; struct zebra_if *br_zif; int vid_present = 0; char vid_buf[20]; struct zebra_ns *zns; struct interface *ifp; int cmd; struct in_addr vtep_ip; vlanid_t vid; if (dplane_ctx_get_op(ctx) == DPLANE_OP_MAC_INSTALL) cmd = RTM_NEWNEIGH; else cmd = RTM_DELNEIGH; /* Locate zebra ns and interface objects from context data */ zns = zebra_ns_lookup(dplane_ctx_get_ns(ctx)->ns_id); if (zns == NULL) { /* Nothing to be done */ if (IS_ZEBRA_DEBUG_KERNEL) zlog_debug("MAC %s on IF %s(%u) - zebra ns unknown", (cmd == RTM_NEWNEIGH) ? "add" : "del", dplane_ctx_get_ifname(ctx), dplane_ctx_get_ifindex(ctx)); return ZEBRA_DPLANE_REQUEST_FAILURE; } ifp = if_lookup_by_index_per_ns(zns, dplane_ctx_get_ifindex(ctx)); if (ifp == NULL) { /* Nothing to be done */ /* Nothing to be done */ if (IS_ZEBRA_DEBUG_KERNEL) zlog_debug("MAC %s on IF %s(%u) - interface unknown", (cmd == RTM_NEWNEIGH) ? "add" : "del", dplane_ctx_get_ifname(ctx), dplane_ctx_get_ifindex(ctx)); return ZEBRA_DPLANE_REQUEST_FAILURE; } vid = dplane_ctx_mac_get_vlan(ctx); zif = ifp->info; if ((br_if = zif->brslave_info.br_if) == NULL) { if (IS_ZEBRA_DEBUG_KERNEL) zlog_debug("MAC %s on IF %s(%u) - no mapping to bridge", (cmd == RTM_NEWNEIGH) ? "add" : "del", ifp->name, ifp->ifindex); return ZEBRA_DPLANE_REQUEST_FAILURE; } memset(&req, 0, sizeof(req)); req.n.nlmsg_len = NLMSG_LENGTH(sizeof(struct ndmsg)); req.n.nlmsg_flags = NLM_F_REQUEST; if (cmd == RTM_NEWNEIGH) req.n.nlmsg_flags |= (NLM_F_CREATE | NLM_F_REPLACE); req.n.nlmsg_type = cmd; req.ndm.ndm_family = AF_BRIDGE; req.ndm.ndm_flags |= NTF_SELF | NTF_MASTER; req.ndm.ndm_state = NUD_REACHABLE; if (dplane_ctx_mac_is_sticky(ctx)) req.ndm.ndm_state |= NUD_NOARP; else req.ndm.ndm_flags |= NTF_EXT_LEARNED; addattr_l(&req.n, sizeof(req), NDA_LLADDR, dplane_ctx_mac_get_addr(ctx), 6); req.ndm.ndm_ifindex = ifp->ifindex; dst_alen = 4; // TODO: hardcoded vtep_ip = *(dplane_ctx_mac_get_vtep_ip(ctx)); addattr_l(&req.n, sizeof(req), NDA_DST, &vtep_ip, dst_alen); br_zif = (struct zebra_if *)br_if->info; if (IS_ZEBRA_IF_BRIDGE_VLAN_AWARE(br_zif) && vid > 0) { addattr16(&req.n, sizeof(req), NDA_VLAN, vid); vid_present = 1; sprintf(vid_buf, " VLAN %u", vid); } addattr32(&req.n, sizeof(req), NDA_MASTER, br_if->ifindex); if (IS_ZEBRA_DEBUG_KERNEL) { char ipbuf[PREFIX_STRLEN]; char buf[ETHER_ADDR_STRLEN]; char dst_buf[PREFIX_STRLEN + 10]; inet_ntop(AF_INET, &vtep_ip, ipbuf, sizeof(ipbuf)); snprintf(dst_buf, sizeof(dst_buf), " dst %s", ipbuf); prefix_mac2str(dplane_ctx_mac_get_addr(ctx), buf, sizeof(buf)); zlog_debug("Tx %s family %s IF %s(%u)%s %sMAC %s%s", nl_msg_type_to_str(cmd), nl_family_to_str(req.ndm.ndm_family), ifp->name, ifp->ifindex, vid_present ? vid_buf : "", dplane_ctx_mac_is_sticky(ctx) ? "sticky " : "", buf, dst_buf); } ret = netlink_talk_info(netlink_talk_filter, &req.n, dplane_ctx_get_ns(ctx), 0); if (ret == 0) return ZEBRA_DPLANE_REQUEST_SUCCESS; else return ZEBRA_DPLANE_REQUEST_FAILURE; } /* * In the event the kernel deletes ipv4 link-local neighbor entries created for * 5549 support, re-install them. */ static void netlink_handle_5549(struct ndmsg *ndm, struct zebra_if *zif, struct interface *ifp, struct ipaddr *ip, bool handle_failed) { if (ndm->ndm_family != AF_INET) return; if (!zif->v6_2_v4_ll_neigh_entry) return; if (ipv4_ll.s_addr != ip->ip._v4_addr.s_addr) return; if (handle_failed && ndm->ndm_state & NUD_FAILED) { zlog_info("Neighbor Entry for %s has entered a failed state, not reinstalling", ifp->name); return; } if_nbr_ipv6ll_to_ipv4ll_neigh_update(ifp, &zif->v6_2_v4_ll_addr6, true); } #define NUD_VALID \ (NUD_PERMANENT | NUD_NOARP | NUD_REACHABLE | NUD_PROBE | NUD_STALE \ | NUD_DELAY) static int netlink_ipneigh_change(struct nlmsghdr *h, int len, ns_id_t ns_id) { struct ndmsg *ndm; struct interface *ifp; struct zebra_if *zif; struct rtattr *tb[NDA_MAX + 1]; struct interface *link_if; struct ethaddr mac; struct ipaddr ip; char buf[ETHER_ADDR_STRLEN]; char buf2[INET6_ADDRSTRLEN]; int mac_present = 0; bool is_ext; bool is_router; ndm = NLMSG_DATA(h); /* The interface should exist. */ ifp = if_lookup_by_index_per_ns(zebra_ns_lookup(ns_id), ndm->ndm_ifindex); if (!ifp || !ifp->info) return 0; zif = (struct zebra_if *)ifp->info; /* Parse attributes and extract fields of interest. */ memset(tb, 0, sizeof tb); netlink_parse_rtattr(tb, NDA_MAX, NDA_RTA(ndm), len); if (!tb[NDA_DST]) { zlog_debug("%s family %s IF %s(%u) - no DST", nl_msg_type_to_str(h->nlmsg_type), nl_family_to_str(ndm->ndm_family), ifp->name, ndm->ndm_ifindex); return 0; } memset(&ip, 0, sizeof(struct ipaddr)); ip.ipa_type = (ndm->ndm_family == AF_INET) ? IPADDR_V4 : IPADDR_V6; memcpy(&ip.ip.addr, RTA_DATA(tb[NDA_DST]), RTA_PAYLOAD(tb[NDA_DST])); /* if kernel deletes our rfc5549 neighbor entry, re-install it */ if (h->nlmsg_type == RTM_DELNEIGH && (ndm->ndm_state & NUD_PERMANENT)) { netlink_handle_5549(ndm, zif, ifp, &ip, false); if (IS_ZEBRA_DEBUG_KERNEL) zlog_debug( "\tNeighbor Entry Received is a 5549 entry, finished"); return 0; } /* if kernel marks our rfc5549 neighbor entry invalid, re-install it */ if (h->nlmsg_type == RTM_NEWNEIGH && !(ndm->ndm_state & NUD_VALID)) netlink_handle_5549(ndm, zif, ifp, &ip, true); /* The neighbor is present on an SVI. From this, we locate the * underlying * bridge because we're only interested in neighbors on a VxLAN bridge. * The bridge is located based on the nature of the SVI: * (a) In the case of a VLAN-aware bridge, the SVI is a L3 VLAN * interface * and is linked to the bridge * (b) In the case of a VLAN-unaware bridge, the SVI is the bridge * inteface * itself */ if (IS_ZEBRA_IF_VLAN(ifp)) { link_if = if_lookup_by_index_per_ns(zebra_ns_lookup(ns_id), zif->link_ifindex); if (!link_if) return 0; } else if (IS_ZEBRA_IF_BRIDGE(ifp)) link_if = ifp; else { if (IS_ZEBRA_DEBUG_KERNEL) zlog_debug( "\tNeighbor Entry received is not on a VLAN or a BRIDGE, ignoring"); return 0; } memset(&mac, 0, sizeof(struct ethaddr)); if (h->nlmsg_type == RTM_NEWNEIGH) { if (tb[NDA_LLADDR]) { if (RTA_PAYLOAD(tb[NDA_LLADDR]) != ETH_ALEN) { if (IS_ZEBRA_DEBUG_KERNEL) zlog_debug( "%s family %s IF %s(%u) - LLADDR is not MAC, len %lu", nl_msg_type_to_str( h->nlmsg_type), nl_family_to_str( ndm->ndm_family), ifp->name, ndm->ndm_ifindex, (unsigned long)RTA_PAYLOAD( tb[NDA_LLADDR])); return 0; } mac_present = 1; memcpy(&mac, RTA_DATA(tb[NDA_LLADDR]), ETH_ALEN); } is_ext = !!(ndm->ndm_flags & NTF_EXT_LEARNED); is_router = !!(ndm->ndm_flags & NTF_ROUTER); if (IS_ZEBRA_DEBUG_KERNEL) zlog_debug( "Rx %s family %s IF %s(%u) IP %s MAC %s state 0x%x flags 0x%x", nl_msg_type_to_str(h->nlmsg_type), nl_family_to_str(ndm->ndm_family), ifp->name, ndm->ndm_ifindex, ipaddr2str(&ip, buf2, sizeof(buf2)), mac_present ? prefix_mac2str(&mac, buf, sizeof(buf)) : "", ndm->ndm_state, ndm->ndm_flags); /* If the neighbor state is valid for use, process as an add or * update * else process as a delete. Note that the delete handling may * result * in re-adding the neighbor if it is a valid "remote" neighbor. */ if (ndm->ndm_state & NUD_VALID) return zebra_vxlan_handle_kernel_neigh_update( ifp, link_if, &ip, &mac, ndm->ndm_state, is_ext, is_router); return zebra_vxlan_handle_kernel_neigh_del(ifp, link_if, &ip); } if (IS_ZEBRA_DEBUG_KERNEL) zlog_debug("Rx %s family %s IF %s(%u) IP %s", nl_msg_type_to_str(h->nlmsg_type), nl_family_to_str(ndm->ndm_family), ifp->name, ndm->ndm_ifindex, ipaddr2str(&ip, buf2, sizeof(buf2))); /* Process the delete - it may result in re-adding the neighbor if it is * a valid "remote" neighbor. */ return zebra_vxlan_handle_kernel_neigh_del(ifp, link_if, &ip); } static int netlink_neigh_table(struct nlmsghdr *h, ns_id_t ns_id, int startup) { int len; struct ndmsg *ndm; if (h->nlmsg_type != RTM_NEWNEIGH) return 0; /* Length validity. */ len = h->nlmsg_len - NLMSG_LENGTH(sizeof(struct ndmsg)); if (len < 0) return -1; /* We are interested only in AF_INET or AF_INET6 notifications. */ ndm = NLMSG_DATA(h); if (ndm->ndm_family != AF_INET && ndm->ndm_family != AF_INET6) return 0; return netlink_neigh_change(h, len); } /* Request for IP neighbor information from the kernel */ static int netlink_request_neigh(struct nlsock *netlink_cmd, int family, int type, ifindex_t ifindex) { struct { struct nlmsghdr n; struct ndmsg ndm; char buf[256]; } req; /* Form the request, specifying filter (rtattr) if needed. */ memset(&req, 0, sizeof(req)); req.n.nlmsg_type = type; req.n.nlmsg_flags = NLM_F_ROOT | NLM_F_MATCH | NLM_F_REQUEST; req.n.nlmsg_len = NLMSG_LENGTH(sizeof(struct ndmsg)); req.ndm.ndm_family = family; if (ifindex) addattr32(&req.n, sizeof(req), NDA_IFINDEX, ifindex); return netlink_request(netlink_cmd, &req.n); } /* * IP Neighbor table read using netlink interface. This is invoked * at startup. */ int netlink_neigh_read(struct zebra_ns *zns) { int ret; struct zebra_dplane_info dp_info; zebra_dplane_info_from_zns(&dp_info, zns, true /*is_cmd*/); /* Get IP neighbor table. */ ret = netlink_request_neigh(&zns->netlink_cmd, AF_UNSPEC, RTM_GETNEIGH, 0); if (ret < 0) return ret; ret = netlink_parse_info(netlink_neigh_table, &zns->netlink_cmd, &dp_info, 0, 1); return ret; } /* * IP Neighbor table read using netlink interface. This is for a specific * VLAN device. */ int netlink_neigh_read_for_vlan(struct zebra_ns *zns, struct interface *vlan_if) { int ret = 0; struct zebra_dplane_info dp_info; zebra_dplane_info_from_zns(&dp_info, zns, true /*is_cmd*/); ret = netlink_request_neigh(&zns->netlink_cmd, AF_UNSPEC, RTM_GETNEIGH, vlan_if->ifindex); if (ret < 0) return ret; ret = netlink_parse_info(netlink_neigh_table, &zns->netlink_cmd, &dp_info, 0, 0); return ret; } /* * Request for a specific IP in VLAN (SVI) device from IP Neighbor table, * read using netlink interface. */ static int netlink_request_specific_neigh_in_vlan(struct zebra_ns *zns, int type, struct ipaddr *ip, ifindex_t ifindex) { struct { struct nlmsghdr n; struct ndmsg ndm; char buf[256]; } req; int ipa_len; /* Form the request, specifying filter (rtattr) if needed. */ memset(&req, 0, sizeof(req)); req.n.nlmsg_len = NLMSG_LENGTH(sizeof(struct ndmsg)); req.n.nlmsg_flags = NLM_F_REQUEST; req.n.nlmsg_type = type; /* RTM_GETNEIGH */ req.ndm.ndm_ifindex = ifindex; if (IS_IPADDR_V4(ip)) { ipa_len = IPV4_MAX_BYTELEN; req.ndm.ndm_family = AF_INET; } else { ipa_len = IPV6_MAX_BYTELEN; req.ndm.ndm_family = AF_INET6; } addattr_l(&req.n, sizeof(req), NDA_DST, &ip->ip.addr, ipa_len); return netlink_request(&zns->netlink_cmd, &req.n); } int netlink_neigh_read_specific_ip(struct ipaddr *ip, struct interface *vlan_if) { int ret = 0; struct zebra_ns *zns; struct zebra_vrf *zvrf = zebra_vrf_lookup_by_id(vlan_if->vrf_id); char buf[INET6_ADDRSTRLEN]; struct zebra_dplane_info dp_info; zns = zvrf->zns; zebra_dplane_info_from_zns(&dp_info, zns, true /*is_cmd*/); if (IS_ZEBRA_DEBUG_KERNEL) zlog_debug("%s: neigh request IF %s(%u) IP %s vrf_id %u", __PRETTY_FUNCTION__, vlan_if->name, vlan_if->ifindex, ipaddr2str(ip, buf, sizeof(buf)), vlan_if->vrf_id); ret = netlink_request_specific_neigh_in_vlan(zns, RTM_GETNEIGH, ip, vlan_if->ifindex); if (ret < 0) return ret; ret = netlink_parse_info(netlink_neigh_table, &zns->netlink_cmd, &dp_info, 1, 0); return ret; } int netlink_neigh_change(struct nlmsghdr *h, ns_id_t ns_id) { int len; struct ndmsg *ndm; if (!(h->nlmsg_type == RTM_NEWNEIGH || h->nlmsg_type == RTM_DELNEIGH)) return 0; /* Length validity. */ len = h->nlmsg_len - NLMSG_LENGTH(sizeof(struct ndmsg)); if (len < 0) { zlog_err("%s: Message received from netlink is of a broken size %d %zu", __PRETTY_FUNCTION__, h->nlmsg_len, (size_t)NLMSG_LENGTH(sizeof(struct ndmsg))); return -1; } /* Is this a notification for the MAC FDB or IP neighbor table? */ ndm = NLMSG_DATA(h); if (ndm->ndm_family == AF_BRIDGE) return netlink_macfdb_change(h, len, ns_id); if (ndm->ndm_type != RTN_UNICAST) return 0; if (ndm->ndm_family == AF_INET || ndm->ndm_family == AF_INET6) return netlink_ipneigh_change(h, len, ns_id); else { flog_warn( EC_ZEBRA_UNKNOWN_FAMILY, "Invalid address family: %u received from kernel neighbor change: %s", ndm->ndm_family, nl_msg_type_to_str(h->nlmsg_type)); return 0; } return 0; } /* * Utility neighbor-update function, using info from dplane context. */ static int netlink_neigh_update_ctx(const struct zebra_dplane_ctx *ctx, int cmd) { struct { struct nlmsghdr n; struct ndmsg ndm; char buf[256]; } req; int ipa_len; char buf[INET6_ADDRSTRLEN]; char buf2[ETHER_ADDR_STRLEN]; const struct ipaddr *ip; const struct ethaddr *mac; uint8_t flags; uint16_t state; memset(&req, 0, sizeof(req)); ip = dplane_ctx_neigh_get_ipaddr(ctx); mac = dplane_ctx_neigh_get_mac(ctx); if (is_zero_mac(mac)) mac = NULL; flags = neigh_flags_to_netlink(dplane_ctx_neigh_get_flags(ctx)); state = neigh_state_to_netlink(dplane_ctx_neigh_get_state(ctx)); req.n.nlmsg_len = NLMSG_LENGTH(sizeof(struct ndmsg)); req.n.nlmsg_flags = NLM_F_REQUEST; if (cmd == RTM_NEWNEIGH) req.n.nlmsg_flags |= (NLM_F_CREATE | NLM_F_REPLACE); req.n.nlmsg_type = cmd; // RTM_NEWNEIGH or RTM_DELNEIGH req.ndm.ndm_family = IS_IPADDR_V4(ip) ? AF_INET : AF_INET6; req.ndm.ndm_state = state; req.ndm.ndm_ifindex = dplane_ctx_get_ifindex(ctx); req.ndm.ndm_type = RTN_UNICAST; req.ndm.ndm_flags = flags; ipa_len = IS_IPADDR_V4(ip) ? IPV4_MAX_BYTELEN : IPV6_MAX_BYTELEN; addattr_l(&req.n, sizeof(req), NDA_DST, &ip->ip.addr, ipa_len); if (mac) addattr_l(&req.n, sizeof(req), NDA_LLADDR, mac, 6); if (IS_ZEBRA_DEBUG_KERNEL) zlog_debug("Tx %s family %s IF %s(%u) Neigh %s MAC %s flags 0x%x state 0x%x", nl_msg_type_to_str(cmd), nl_family_to_str(req.ndm.ndm_family), dplane_ctx_get_ifname(ctx), dplane_ctx_get_ifindex(ctx), ipaddr2str(ip, buf, sizeof(buf)), mac ? prefix_mac2str(mac, buf2, sizeof(buf2)) : "null", flags, state); return netlink_talk_info(netlink_talk_filter, &req.n, dplane_ctx_get_ns(ctx), 0); } /* * Update MAC, using dataplane context object. */ enum zebra_dplane_result kernel_mac_update_ctx(struct zebra_dplane_ctx *ctx) { return netlink_macfdb_update_ctx(ctx); } enum zebra_dplane_result kernel_neigh_update_ctx(struct zebra_dplane_ctx *ctx) { int ret = -1; switch (dplane_ctx_get_op(ctx)) { case DPLANE_OP_NEIGH_INSTALL: case DPLANE_OP_NEIGH_UPDATE: ret = netlink_neigh_update_ctx(ctx, RTM_NEWNEIGH); break; case DPLANE_OP_NEIGH_DELETE: ret = netlink_neigh_update_ctx(ctx, RTM_DELNEIGH); break; case DPLANE_OP_VTEP_ADD: ret = netlink_vxlan_flood_update_ctx(ctx, RTM_NEWNEIGH); break; case DPLANE_OP_VTEP_DELETE: ret = netlink_vxlan_flood_update_ctx(ctx, RTM_DELNEIGH); break; default: break; } return (ret == 0 ? ZEBRA_DPLANE_REQUEST_SUCCESS : ZEBRA_DPLANE_REQUEST_FAILURE); } /* * MPLS label forwarding table change via netlink interface, using dataplane * context information. */ int netlink_mpls_multipath(int cmd, struct zebra_dplane_ctx *ctx) { mpls_lse_t lse; const zebra_nhlfe_t *nhlfe; struct nexthop *nexthop = NULL; unsigned int nexthop_num; const char *routedesc; int route_type; struct { struct nlmsghdr n; struct rtmsg r; char buf[NL_PKT_BUF_SIZE]; } req; memset(&req, 0, sizeof(req) - NL_PKT_BUF_SIZE); /* * Count # nexthops so we can decide whether to use singlepath * or multipath case. */ nexthop_num = 0; for (nhlfe = dplane_ctx_get_nhlfe(ctx); nhlfe; nhlfe = nhlfe->next) { nexthop = nhlfe->nexthop; if (!nexthop) continue; if (cmd == RTM_NEWROUTE) { /* Count all selected NHLFEs */ if (CHECK_FLAG(nhlfe->flags, NHLFE_FLAG_SELECTED) && CHECK_FLAG(nexthop->flags, NEXTHOP_FLAG_ACTIVE)) nexthop_num++; } else { /* DEL */ /* Count all installed NHLFEs */ if (CHECK_FLAG(nhlfe->flags, NHLFE_FLAG_INSTALLED) && CHECK_FLAG(nexthop->flags, NEXTHOP_FLAG_FIB)) nexthop_num++; } } if ((nexthop_num == 0) || (!dplane_ctx_get_best_nhlfe(ctx) && (cmd != RTM_DELROUTE))) return 0; req.n.nlmsg_len = NLMSG_LENGTH(sizeof(struct rtmsg)); req.n.nlmsg_flags = NLM_F_CREATE | NLM_F_REQUEST; req.n.nlmsg_type = cmd; req.n.nlmsg_pid = dplane_ctx_get_ns(ctx)->nls.snl.nl_pid; req.r.rtm_family = AF_MPLS; req.r.rtm_table = RT_TABLE_MAIN; req.r.rtm_dst_len = MPLS_LABEL_LEN_BITS; req.r.rtm_scope = RT_SCOPE_UNIVERSE; req.r.rtm_type = RTN_UNICAST; if (cmd == RTM_NEWROUTE) { /* We do a replace to handle update. */ req.n.nlmsg_flags |= NLM_F_REPLACE; /* set the protocol value if installing */ route_type = re_type_from_lsp_type( dplane_ctx_get_best_nhlfe(ctx)->type); req.r.rtm_protocol = zebra2proto(route_type); } /* Fill destination */ lse = mpls_lse_encode(dplane_ctx_get_in_label(ctx), 0, 0, 1); addattr_l(&req.n, sizeof(req), RTA_DST, &lse, sizeof(mpls_lse_t)); /* Fill nexthops (paths) based on single-path or multipath. The paths * chosen depend on the operation. */ if (nexthop_num == 1) { routedesc = "single-path"; _netlink_mpls_debug(cmd, dplane_ctx_get_in_label(ctx), routedesc); nexthop_num = 0; for (nhlfe = dplane_ctx_get_nhlfe(ctx); nhlfe; nhlfe = nhlfe->next) { nexthop = nhlfe->nexthop; if (!nexthop) continue; if ((cmd == RTM_NEWROUTE && (CHECK_FLAG(nhlfe->flags, NHLFE_FLAG_SELECTED) && CHECK_FLAG(nexthop->flags, NEXTHOP_FLAG_ACTIVE))) || (cmd == RTM_DELROUTE && (CHECK_FLAG(nhlfe->flags, NHLFE_FLAG_INSTALLED) && CHECK_FLAG(nexthop->flags, NEXTHOP_FLAG_FIB)))) { /* Add the gateway */ _netlink_mpls_build_singlepath( routedesc, nhlfe, &req.n, &req.r, sizeof(req), cmd); nexthop_num++; break; } } } else { /* Multipath case */ char buf[NL_PKT_BUF_SIZE]; struct rtattr *rta = (void *)buf; struct rtnexthop *rtnh; const union g_addr *src1 = NULL; rta->rta_type = RTA_MULTIPATH; rta->rta_len = RTA_LENGTH(0); rtnh = RTA_DATA(rta); routedesc = "multipath"; _netlink_mpls_debug(cmd, dplane_ctx_get_in_label(ctx), routedesc); nexthop_num = 0; for (nhlfe = dplane_ctx_get_nhlfe(ctx); nhlfe; nhlfe = nhlfe->next) { nexthop = nhlfe->nexthop; if (!nexthop) continue; if ((cmd == RTM_NEWROUTE && (CHECK_FLAG(nhlfe->flags, NHLFE_FLAG_SELECTED) && CHECK_FLAG(nexthop->flags, NEXTHOP_FLAG_ACTIVE))) || (cmd == RTM_DELROUTE && (CHECK_FLAG(nhlfe->flags, NHLFE_FLAG_INSTALLED) && CHECK_FLAG(nexthop->flags, NEXTHOP_FLAG_FIB)))) { nexthop_num++; /* Build the multipath */ _netlink_mpls_build_multipath(routedesc, nhlfe, rta, rtnh, &req.r, &src1); rtnh = RTNH_NEXT(rtnh); } } /* Add the multipath */ if (rta->rta_len > RTA_LENGTH(0)) addattr_l(&req.n, NL_PKT_BUF_SIZE, RTA_MULTIPATH, RTA_DATA(rta), RTA_PAYLOAD(rta)); } /* Talk to netlink socket. */ return netlink_talk_info(netlink_talk_filter, &req.n, dplane_ctx_get_ns(ctx), 0); } #endif /* HAVE_NETLINK */ frr-7.2.1/zebra/rt_netlink.h0000644000000000000000000000564313610377563012651 00000000000000/* Header file exported by rt_netlink.c to zebra. * Copyright (C) 1997, 98, 99 Kunihiro Ishiguro * * This file is part of GNU Zebra. * * GNU Zebra is free software; you can redistribute it and/or modify it * under the terms of the GNU General Public License as published by the * Free Software Foundation; either version 2, or (at your option) any * later version. * * GNU Zebra 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; see the file COPYING; if not, write to the Free Software * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA */ #ifndef _ZEBRA_RT_NETLINK_H #define _ZEBRA_RT_NETLINK_H #ifdef HAVE_NETLINK #include "zebra/zebra_mpls.h" #include "zebra/zebra_dplane.h" #ifdef __cplusplus extern "C" { #endif #define NL_DEFAULT_ROUTE_METRIC 20 /* * Additional protocol strings to push into routes * If we add anything new here please make sure * to update: * zebra2proto Function * proto2zebra Function * is_selfroute Function * tools/frr To flush the route upon exit * * Finally update this file to allow iproute2 to * know about this new route. * tools/etc/iproute2/rt_protos.d */ #define RTPROT_BGP 186 #define RTPROT_ISIS 187 #define RTPROT_OSPF 188 #define RTPROT_RIP 189 #define RTPROT_RIPNG 190 #if !defined(RTPROT_BABEL) #define RTPROT_BABEL 42 #endif #define RTPROT_NHRP 191 #define RTPROT_EIGRP 192 #define RTPROT_LDP 193 #define RTPROT_SHARP 194 #define RTPROT_PBR 195 #define RTPROT_ZSTATIC 196 #define RTPROT_OPENFABRIC 197 void rt_netlink_init(void); /* MPLS label forwarding table change, using dataplane context information. */ extern int netlink_mpls_multipath(int cmd, struct zebra_dplane_ctx *ctx); extern int netlink_route_change(struct nlmsghdr *h, ns_id_t ns_id, int startup); extern int netlink_route_read(struct zebra_ns *zns); extern int netlink_neigh_change(struct nlmsghdr *h, ns_id_t ns_id); extern int netlink_macfdb_read(struct zebra_ns *zns); extern int netlink_macfdb_read_for_bridge(struct zebra_ns *zns, struct interface *ifp, struct interface *br_if); extern int netlink_neigh_read(struct zebra_ns *zns); extern int netlink_neigh_read_for_vlan(struct zebra_ns *zns, struct interface *vlan_if); extern int netlink_macfdb_read_specific_mac(struct zebra_ns *zns, struct interface *br_if, struct ethaddr *mac, uint16_t vid); extern int netlink_neigh_read_specific_ip(struct ipaddr *ip, struct interface *vlan_if); #ifdef __cplusplus } #endif #endif /* HAVE_NETLINK */ #endif /* _ZEBRA_RT_NETLINK_H */ frr-7.2.1/zebra/rt_socket.c0000644000000000000000000002533413610377563012467 00000000000000/* * Kernel routing table updates by routing socket. * Copyright (C) 1997, 98 Kunihiro Ishiguro * * This file is part of GNU Zebra. * * GNU Zebra is free software; you can redistribute it and/or modify it * under the terms of the GNU General Public License as published by the * Free Software Foundation; either version 2, or (at your option) any * later version. * * GNU Zebra 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; see the file COPYING; if not, write to the Free Software * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA */ #include #ifndef HAVE_NETLINK #ifdef __OpenBSD__ #include #endif #include "if.h" #include "prefix.h" #include "sockunion.h" #include "log.h" #include "privs.h" #include "vxlan.h" #include "lib_errors.h" #include "zebra/debug.h" #include "zebra/rib.h" #include "zebra/rt.h" #include "zebra/kernel_socket.h" #include "zebra/zebra_mpls.h" #include "zebra/zebra_errors.h" extern struct zebra_privs_t zserv_privs; #ifdef __OpenBSD__ static int kernel_rtm_add_labels(struct mpls_label_stack *nh_label, struct sockaddr_mpls *smpls) { if (nh_label->num_labels > 1) { flog_warn(EC_ZEBRA_MAX_LABELS_PUSH, "%s: can't push %u labels at " "once (maximum is 1)", __func__, nh_label->num_labels); return -1; } memset(smpls, 0, sizeof(*smpls)); smpls->smpls_len = sizeof(*smpls); smpls->smpls_family = AF_MPLS; smpls->smpls_label = htonl(nh_label->label[0] << MPLS_LABEL_OFFSET); return 0; } #endif /* Interface between zebra message and rtm message. */ static int kernel_rtm(int cmd, const struct prefix *p, const struct nexthop_group *ng, uint32_t metric) { union sockunion sin_dest, sin_mask, sin_gate; #ifdef __OpenBSD__ struct sockaddr_mpls smpls; #endif union sockunion *smplsp = NULL; struct nexthop *nexthop; int nexthop_num = 0; ifindex_t ifindex = 0; bool gate = false; int error; char gate_buf[INET6_BUFSIZ]; char prefix_buf[PREFIX_STRLEN]; enum blackhole_type bh_type = BLACKHOLE_UNSPEC; prefix2str(p, prefix_buf, sizeof(prefix_buf)); /* * We only have the ability to ADD or DELETE at this point * in time. */ if (cmd != RTM_ADD && cmd != RTM_DELETE) { if (IS_ZEBRA_DEBUG_KERNEL) zlog_debug("%s: %s odd command %s", __func__, prefix_buf, lookup_msg(rtm_type_str, cmd, NULL)); return 0; } memset(&sin_dest, 0, sizeof(sin_dest)); memset(&sin_gate, 0, sizeof(sin_gate)); memset(&sin_mask, 0, sizeof(sin_mask)); switch (p->family) { case AF_INET: sin_dest.sin.sin_family = AF_INET; sin_dest.sin.sin_addr = p->u.prefix4; sin_gate.sin.sin_family = AF_INET; #ifdef HAVE_STRUCT_SOCKADDR_SA_LEN sin_dest.sin.sin_len = sizeof(struct sockaddr_in); sin_gate.sin.sin_len = sizeof(struct sockaddr_in); #endif /* HAVE_STRUCT_SOCKADDR_SA_LEN */ break; case AF_INET6: sin_dest.sin6.sin6_family = AF_INET6; sin_dest.sin6.sin6_addr = p->u.prefix6; sin_gate.sin6.sin6_family = AF_INET6; #ifdef HAVE_STRUCT_SOCKADDR_SA_LEN sin_dest.sin6.sin6_len = sizeof(struct sockaddr_in6); sin_gate.sin6.sin6_len = sizeof(struct sockaddr_in6); #endif /* HAVE_STRUCT_SOCKADDR_SA_LEN */ break; } /* Make gateway. */ for (ALL_NEXTHOPS_PTR(ng, nexthop)) { /* * We only want to use the actual good nexthops */ if (CHECK_FLAG(nexthop->flags, NEXTHOP_FLAG_RECURSIVE) || !CHECK_FLAG(nexthop->flags, NEXTHOP_FLAG_ACTIVE)) continue; smplsp = NULL; gate = false; snprintf(gate_buf, sizeof(gate_buf), "NULL"); switch (nexthop->type) { case NEXTHOP_TYPE_IPV4: case NEXTHOP_TYPE_IPV4_IFINDEX: sin_gate.sin.sin_addr = nexthop->gate.ipv4; sin_gate.sin.sin_family = AF_INET; #ifdef HAVE_STRUCT_SOCKADDR_SA_LEN sin_gate.sin.sin_len = sizeof(struct sockaddr_in); #endif /* HAVE_STRUCT_SOCKADDR_SA_LEN */ ifindex = nexthop->ifindex; gate = true; break; case NEXTHOP_TYPE_IPV6: case NEXTHOP_TYPE_IPV6_IFINDEX: sin_gate.sin6.sin6_addr = nexthop->gate.ipv6; sin_gate.sin6.sin6_family = AF_INET6; #ifdef HAVE_STRUCT_SOCKADDR_SA_LEN sin_gate.sin6.sin6_len = sizeof(struct sockaddr_in6); #endif /* HAVE_STRUCT_SOCKADDR_SA_LEN */ ifindex = nexthop->ifindex; /* Under kame set interface index to link local address */ #ifdef KAME #define SET_IN6_LINKLOCAL_IFINDEX(a, i) \ do { \ (a).s6_addr[2] = ((i) >> 8) & 0xff; \ (a).s6_addr[3] = (i)&0xff; \ } while (0) if (IN6_IS_ADDR_LINKLOCAL(&sin_gate.sin6.sin6_addr)) SET_IN6_LINKLOCAL_IFINDEX( sin_gate.sin6.sin6_addr, ifindex); #endif /* KAME */ gate = true; break; case NEXTHOP_TYPE_IFINDEX: ifindex = nexthop->ifindex; break; case NEXTHOP_TYPE_BLACKHOLE: bh_type = nexthop->bh_type; switch (p->family) { case AFI_IP: { struct in_addr loopback; loopback.s_addr = htonl(INADDR_LOOPBACK); sin_gate.sin.sin_addr = loopback; #ifdef HAVE_STRUCT_SOCKADDR_SA_LEN sin_gate.sin.sin_len = sizeof(struct sockaddr_in); #endif /* HAVE_STRUCT_SOCKADDR_SA_LEN */ gate = true; } break; case AFI_IP6: break; } } switch (p->family) { case AF_INET: masklen2ip(p->prefixlen, &sin_mask.sin.sin_addr); sin_mask.sin.sin_family = AF_INET; #ifdef HAVE_STRUCT_SOCKADDR_SA_LEN sin_mask.sin.sin_len = sizeof(struct sockaddr_in); #endif /* HAVE_STRUCT_SOCKADDR_SA_LEN */ break; case AF_INET6: masklen2ip6(p->prefixlen, &sin_mask.sin6.sin6_addr); sin_mask.sin6.sin6_family = AF_INET6; #ifdef HAVE_STRUCT_SOCKADDR_SA_LEN sin_mask.sin6.sin6_len = sizeof(struct sockaddr_in6); #endif /* HAVE_STRUCT_SOCKADDR_SA_LEN */ break; } #ifdef __OpenBSD__ if (nexthop->nh_label) { if (kernel_rtm_add_labels(nexthop->nh_label, &smpls) != 0) continue; smplsp = (union sockunion *)&smpls; } #endif error = rtm_write(cmd, &sin_dest, &sin_mask, gate ? &sin_gate : NULL, smplsp, ifindex, bh_type, metric); if (IS_ZEBRA_DEBUG_KERNEL) { if (!gate) { zlog_debug( "%s: %s: attention! gate not found for re", __func__, prefix_buf); } else { switch (p->family) { case AFI_IP: inet_ntop(AF_INET, &sin_gate.sin.sin_addr, gate_buf, sizeof(gate_buf)); break; case AFI_IP6: inet_ntop(AF_INET6, &sin_gate.sin6.sin6_addr, gate_buf, sizeof(gate_buf)); break; default: snprintf(gate_buf, sizeof(gate_buf), "(invalid-af)"); break; } } } switch (error) { /* We only flag nexthops as being in FIB if * rtm_write() did its work. */ case ZEBRA_ERR_NOERROR: nexthop_num++; if (IS_ZEBRA_DEBUG_KERNEL) zlog_debug("%s: %s: successfully did NH %s", __func__, prefix_buf, gate_buf); if (cmd == RTM_ADD) SET_FLAG(nexthop->flags, NEXTHOP_FLAG_FIB); break; /* The only valid case for this error is * kernel's failure to install a multipath * route, which is common for FreeBSD. This * should be ignored silently, but logged as an error * otherwise. */ case ZEBRA_ERR_RTEXIST: if (cmd != RTM_ADD) flog_err(EC_LIB_SYSTEM_CALL, "%s: rtm_write() returned %d for command %d", __func__, error, cmd); continue; /* Note any unexpected status returns */ default: flog_err( EC_LIB_SYSTEM_CALL, "%s: %s: rtm_write() unexpectedly returned %d for command %s", __func__, prefix_buf, error, lookup_msg(rtm_type_str, cmd, NULL)); break; } } /* for (ALL_NEXTHOPS(...))*/ /* If there was no useful nexthop, then complain. */ if (nexthop_num == 0) { if (IS_ZEBRA_DEBUG_KERNEL) zlog_debug( "%s: No useful nexthops were found in RIB prefix %s", __func__, prefix_buf); return 1; } return 0; /*XXX*/ } /* * Update or delete a prefix from the kernel, * using info from a dataplane context struct. */ enum zebra_dplane_result kernel_route_update(struct zebra_dplane_ctx *ctx) { enum zebra_dplane_result res = ZEBRA_DPLANE_REQUEST_SUCCESS; uint32_t type, old_type; if (dplane_ctx_get_src(ctx) != NULL) { zlog_err("route add: IPv6 sourcedest routes unsupported!"); return ZEBRA_DPLANE_REQUEST_FAILURE; } type = dplane_ctx_get_type(ctx); old_type = dplane_ctx_get_old_type(ctx); frr_with_privs(&zserv_privs) { if (dplane_ctx_get_op(ctx) == DPLANE_OP_ROUTE_DELETE) { if (!RSYSTEM_ROUTE(type)) kernel_rtm(RTM_DELETE, dplane_ctx_get_dest(ctx), dplane_ctx_get_ng(ctx), dplane_ctx_get_metric(ctx)); } else if (dplane_ctx_get_op(ctx) == DPLANE_OP_ROUTE_INSTALL) { if (!RSYSTEM_ROUTE(type)) kernel_rtm(RTM_ADD, dplane_ctx_get_dest(ctx), dplane_ctx_get_ng(ctx), dplane_ctx_get_metric(ctx)); } else if (dplane_ctx_get_op(ctx) == DPLANE_OP_ROUTE_UPDATE) { /* Must do delete and add separately - * no update available */ if (!RSYSTEM_ROUTE(old_type)) kernel_rtm(RTM_DELETE, dplane_ctx_get_dest(ctx), dplane_ctx_get_old_ng(ctx), dplane_ctx_get_old_metric(ctx)); if (!RSYSTEM_ROUTE(type)) kernel_rtm(RTM_ADD, dplane_ctx_get_dest(ctx), dplane_ctx_get_ng(ctx), dplane_ctx_get_metric(ctx)); } else { zlog_err("Invalid routing socket update op %s (%u)", dplane_op2str(dplane_ctx_get_op(ctx)), dplane_ctx_get_op(ctx)); res = ZEBRA_DPLANE_REQUEST_FAILURE; } } /* Elevated privs */ if (RSYSTEM_ROUTE(type) && dplane_ctx_get_op(ctx) != DPLANE_OP_ROUTE_DELETE) { struct nexthop *nexthop; for (ALL_NEXTHOPS_PTR(dplane_ctx_get_ng(ctx), nexthop)) { if (CHECK_FLAG(nexthop->flags, NEXTHOP_FLAG_RECURSIVE)) continue; if (CHECK_FLAG(nexthop->flags, NEXTHOP_FLAG_ACTIVE)) { SET_FLAG(nexthop->flags, NEXTHOP_FLAG_FIB); } } } return res; } int kernel_neigh_update(int add, int ifindex, uint32_t addr, char *lla, int llalen, ns_id_t ns_id) { /* TODO */ return 0; } /* NYI on routing-socket platforms, but we've always returned 'success'... */ enum zebra_dplane_result kernel_neigh_update_ctx(struct zebra_dplane_ctx *ctx) { return ZEBRA_DPLANE_REQUEST_SUCCESS; } extern int kernel_get_ipmr_sg_stats(struct zebra_vrf *zvrf, void *mroute) { return 0; } /* * Update MAC, using dataplane context object. No-op here for now. */ enum zebra_dplane_result kernel_mac_update_ctx(struct zebra_dplane_ctx *ctx) { return ZEBRA_DPLANE_REQUEST_SUCCESS; } extern int kernel_interface_set_master(struct interface *master, struct interface *slave) { return 0; } uint32_t kernel_get_speed(struct interface *ifp) { return ifp->speed; } #endif /* !HAVE_NETLINK */ frr-7.2.1/zebra/rtadv.c0000644000000000000000000017342113610377563011613 00000000000000/* Router advertisement * Copyright (C) 2016 Cumulus Networks * Copyright (C) 2005 6WIND * Copyright (C) 1999 Kunihiro Ishiguro * * This file is part of GNU Zebra. * * GNU Zebra is free software; you can redistribute it and/or modify it * under the terms of the GNU General Public License as published by the * Free Software Foundation; either version 2, or (at your option) any * later version. * * GNU Zebra 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; see the file COPYING; if not, write to the Free Software * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA */ #include #include "memory.h" #include "zebra_memory.h" #include "sockopt.h" #include "thread.h" #include "if.h" #include "stream.h" #include "log.h" #include "prefix.h" #include "linklist.h" #include "command.h" #include "privs.h" #include "vrf.h" #include "ns.h" #include "lib_errors.h" #include "zebra/interface.h" #include "zebra/rtadv.h" #include "zebra/debug.h" #include "zebra/rib.h" #include "zebra/zapi_msg.h" #include "zebra/zebra_vrf.h" #include "zebra/zebra_errors.h" #include "zebra/zebra_router.h" extern struct zebra_privs_t zserv_privs; #if defined(HAVE_RTADV) DEFINE_MTYPE_STATIC(ZEBRA, RTADV_PREFIX, "Router Advertisement Prefix") #ifdef OPEN_BSD #include #endif /* If RFC2133 definition is used. */ #ifndef IPV6_JOIN_GROUP #define IPV6_JOIN_GROUP IPV6_ADD_MEMBERSHIP #endif #ifndef IPV6_LEAVE_GROUP #define IPV6_LEAVE_GROUP IPV6_DROP_MEMBERSHIP #endif #define ALLNODE "ff02::1" #define ALLROUTER "ff02::2" DEFINE_MTYPE_STATIC(ZEBRA, RTADV_RDNSS, "Router Advertisement RDNSS") DEFINE_MTYPE_STATIC(ZEBRA, RTADV_DNSSL, "Router Advertisement DNSSL") /* Order is intentional. Matches RFC4191. This array is also used for command matching, so only modify with care. */ const char *rtadv_pref_strs[] = {"medium", "high", "INVALID", "low", 0}; enum rtadv_event { RTADV_START, RTADV_STOP, RTADV_TIMER, RTADV_TIMER_MSEC, RTADV_READ }; static void rtadv_event(struct zebra_vrf *, enum rtadv_event, int); static int if_join_all_router(int, struct interface *); static int if_leave_all_router(int, struct interface *); static int rtadv_get_socket(struct zebra_vrf *zvrf) { if (zvrf->rtadv.sock > 0) return zvrf->rtadv.sock; return zrouter.rtadv_sock; } static int rtadv_increment_received(struct zebra_vrf *zvrf, ifindex_t *ifindex) { int ret = -1; struct interface *iface; struct zebra_if *zif; iface = if_lookup_by_index(*ifindex, zvrf->vrf->vrf_id); if (iface && iface->info) { zif = iface->info; zif->ra_rcvd++; ret = 0; } return ret; } static int rtadv_recv_packet(struct zebra_vrf *zvrf, int sock, uint8_t *buf, int buflen, struct sockaddr_in6 *from, ifindex_t *ifindex, int *hoplimit) { int ret; struct msghdr msg; struct iovec iov; struct cmsghdr *cmsgptr; struct in6_addr dst; char adata[1024]; /* Fill in message and iovec. */ memset(&msg, 0, sizeof(msg)); msg.msg_name = (void *)from; msg.msg_namelen = sizeof(struct sockaddr_in6); msg.msg_iov = &iov; msg.msg_iovlen = 1; msg.msg_control = (void *)adata; msg.msg_controllen = sizeof adata; iov.iov_base = buf; iov.iov_len = buflen; /* If recvmsg fail return minus value. */ ret = recvmsg(sock, &msg, 0); if (ret < 0) return ret; for (cmsgptr = CMSG_FIRSTHDR(&msg); cmsgptr != NULL; cmsgptr = CMSG_NXTHDR(&msg, cmsgptr)) { /* I want interface index which this packet comes from. */ if (cmsgptr->cmsg_level == IPPROTO_IPV6 && cmsgptr->cmsg_type == IPV6_PKTINFO) { struct in6_pktinfo *ptr; ptr = (struct in6_pktinfo *)CMSG_DATA(cmsgptr); *ifindex = ptr->ipi6_ifindex; memcpy(&dst, &ptr->ipi6_addr, sizeof(ptr->ipi6_addr)); } /* Incoming packet's hop limit. */ if (cmsgptr->cmsg_level == IPPROTO_IPV6 && cmsgptr->cmsg_type == IPV6_HOPLIMIT) { int *hoptr = (int *)CMSG_DATA(cmsgptr); *hoplimit = *hoptr; } } rtadv_increment_received(zvrf, ifindex); return ret; } #define RTADV_MSG_SIZE 4096 /* Send router advertisement packet. */ static void rtadv_send_packet(int sock, struct interface *ifp) { struct msghdr msg; struct iovec iov; struct cmsghdr *cmsgptr; struct in6_pktinfo *pkt; struct sockaddr_in6 addr; static void *adata = NULL; unsigned char buf[RTADV_MSG_SIZE]; struct nd_router_advert *rtadv; int ret; int len = 0; struct zebra_if *zif; struct rtadv_prefix *rprefix; uint8_t all_nodes_addr[] = {0xff, 0x02, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1}; struct listnode *node; uint16_t pkt_RouterLifetime; /* * Allocate control message bufffer. This is dynamic because * CMSG_SPACE is not guaranteed not to call a function. Note that * the size will be different on different architectures due to * differing alignment rules. */ if (adata == NULL) { /* XXX Free on shutdown. */ adata = calloc(1, CMSG_SPACE(sizeof(struct in6_pktinfo))); if (adata == NULL) { zlog_debug( "rtadv_send_packet: can't malloc control data"); exit(-1); } } /* Logging of packet. */ if (IS_ZEBRA_DEBUG_PACKET) zlog_debug("%s(%u): Tx RA, socket %u", ifp->name, ifp->ifindex, sock); /* Fill in sockaddr_in6. */ memset(&addr, 0, sizeof(struct sockaddr_in6)); addr.sin6_family = AF_INET6; #ifdef SIN6_LEN addr.sin6_len = sizeof(struct sockaddr_in6); #endif /* SIN6_LEN */ addr.sin6_port = htons(IPPROTO_ICMPV6); IPV6_ADDR_COPY(&addr.sin6_addr, all_nodes_addr); /* Fetch interface information. */ zif = ifp->info; /* Make router advertisement message. */ rtadv = (struct nd_router_advert *)buf; rtadv->nd_ra_type = ND_ROUTER_ADVERT; rtadv->nd_ra_code = 0; rtadv->nd_ra_cksum = 0; rtadv->nd_ra_curhoplimit = 64; /* RFC4191: Default Router Preference is 0 if Router Lifetime is 0. */ rtadv->nd_ra_flags_reserved = zif->rtadv.AdvDefaultLifetime == 0 ? 0 : zif->rtadv.DefaultPreference; rtadv->nd_ra_flags_reserved <<= 3; if (zif->rtadv.AdvManagedFlag) rtadv->nd_ra_flags_reserved |= ND_RA_FLAG_MANAGED; if (zif->rtadv.AdvOtherConfigFlag) rtadv->nd_ra_flags_reserved |= ND_RA_FLAG_OTHER; if (zif->rtadv.AdvHomeAgentFlag) rtadv->nd_ra_flags_reserved |= ND_RA_FLAG_HOME_AGENT; /* Note that according to Neighbor Discovery (RFC 4861 [18]), * AdvDefaultLifetime is by default based on the value of * MaxRtrAdvInterval. AdvDefaultLifetime is used in the Router Lifetime * field of Router Advertisements. Given that this field is expressed * in seconds, a small MaxRtrAdvInterval value can result in a zero * value for this field. To prevent this, routers SHOULD keep * AdvDefaultLifetime in at least one second, even if the use of * MaxRtrAdvInterval would result in a smaller value. -- RFC6275, 7.5 */ pkt_RouterLifetime = zif->rtadv.AdvDefaultLifetime != -1 ? zif->rtadv.AdvDefaultLifetime : MAX(1, 0.003 * zif->rtadv.MaxRtrAdvInterval); rtadv->nd_ra_router_lifetime = htons(pkt_RouterLifetime); rtadv->nd_ra_reachable = htonl(zif->rtadv.AdvReachableTime); rtadv->nd_ra_retransmit = htonl(0); len = sizeof(struct nd_router_advert); /* If both the Home Agent Preference and Home Agent Lifetime are set to * their default values specified above, this option SHOULD NOT be * included in the Router Advertisement messages sent by this home * agent. -- RFC6275, 7.4 */ if (zif->rtadv.AdvHomeAgentFlag && (zif->rtadv.HomeAgentPreference || zif->rtadv.HomeAgentLifetime != -1)) { struct nd_opt_homeagent_info *ndopt_hai = (struct nd_opt_homeagent_info *)(buf + len); ndopt_hai->nd_opt_hai_type = ND_OPT_HA_INFORMATION; ndopt_hai->nd_opt_hai_len = 1; ndopt_hai->nd_opt_hai_reserved = 0; ndopt_hai->nd_opt_hai_preference = htons(zif->rtadv.HomeAgentPreference); /* 16-bit unsigned integer. The lifetime associated with the * home * agent in units of seconds. The default value is the same as * the * Router Lifetime, as specified in the main body of the Router * Advertisement. The maximum value corresponds to 18.2 hours. * A * value of 0 MUST NOT be used. -- RFC6275, 7.5 */ ndopt_hai->nd_opt_hai_lifetime = htons(zif->rtadv.HomeAgentLifetime != -1 ? zif->rtadv.HomeAgentLifetime : MAX(1, pkt_RouterLifetime) /* 0 is OK for RL, but not for HAL*/ ); len += sizeof(struct nd_opt_homeagent_info); } if (zif->rtadv.AdvIntervalOption) { struct nd_opt_adv_interval *ndopt_adv = (struct nd_opt_adv_interval *)(buf + len); ndopt_adv->nd_opt_ai_type = ND_OPT_ADV_INTERVAL; ndopt_adv->nd_opt_ai_len = 1; ndopt_adv->nd_opt_ai_reserved = 0; ndopt_adv->nd_opt_ai_interval = htonl(zif->rtadv.MaxRtrAdvInterval); len += sizeof(struct nd_opt_adv_interval); } /* Fill in prefix. */ for (ALL_LIST_ELEMENTS_RO(zif->rtadv.AdvPrefixList, node, rprefix)) { struct nd_opt_prefix_info *pinfo; pinfo = (struct nd_opt_prefix_info *)(buf + len); pinfo->nd_opt_pi_type = ND_OPT_PREFIX_INFORMATION; pinfo->nd_opt_pi_len = 4; pinfo->nd_opt_pi_prefix_len = rprefix->prefix.prefixlen; pinfo->nd_opt_pi_flags_reserved = 0; if (rprefix->AdvOnLinkFlag) pinfo->nd_opt_pi_flags_reserved |= ND_OPT_PI_FLAG_ONLINK; if (rprefix->AdvAutonomousFlag) pinfo->nd_opt_pi_flags_reserved |= ND_OPT_PI_FLAG_AUTO; if (rprefix->AdvRouterAddressFlag) pinfo->nd_opt_pi_flags_reserved |= ND_OPT_PI_FLAG_RADDR; pinfo->nd_opt_pi_valid_time = htonl(rprefix->AdvValidLifetime); pinfo->nd_opt_pi_preferred_time = htonl(rprefix->AdvPreferredLifetime); pinfo->nd_opt_pi_reserved2 = 0; IPV6_ADDR_COPY(&pinfo->nd_opt_pi_prefix, &rprefix->prefix.prefix); #ifdef DEBUG { uint8_t buf[INET6_ADDRSTRLEN]; zlog_debug("DEBUG %s", inet_ntop(AF_INET6, &pinfo->nd_opt_pi_prefix, buf, INET6_ADDRSTRLEN)); } #endif /* DEBUG */ len += sizeof(struct nd_opt_prefix_info); } /* Hardware address. */ if (ifp->hw_addr_len != 0) { buf[len++] = ND_OPT_SOURCE_LINKADDR; /* Option length should be rounded up to next octet if the link address does not end on an octet boundary. */ buf[len++] = (ifp->hw_addr_len + 9) >> 3; memcpy(buf + len, ifp->hw_addr, ifp->hw_addr_len); len += ifp->hw_addr_len; /* Pad option to end on an octet boundary. */ memset(buf + len, 0, -(ifp->hw_addr_len + 2) & 0x7); len += -(ifp->hw_addr_len + 2) & 0x7; } /* MTU */ if (zif->rtadv.AdvLinkMTU) { struct nd_opt_mtu *opt = (struct nd_opt_mtu *)(buf + len); opt->nd_opt_mtu_type = ND_OPT_MTU; opt->nd_opt_mtu_len = 1; opt->nd_opt_mtu_reserved = 0; opt->nd_opt_mtu_mtu = htonl(zif->rtadv.AdvLinkMTU); len += sizeof(struct nd_opt_mtu); } /* * There is no limit on the number of configurable recursive DNS * servers or search list entries. We don't want the RA message * to exceed the link's MTU (risking fragmentation) or even * blow the stack buffer allocated for it. */ size_t max_len = MIN(ifp->mtu6 - 40, sizeof(buf)); /* Recursive DNS servers */ struct rtadv_rdnss *rdnss; for (ALL_LIST_ELEMENTS_RO(zif->rtadv.AdvRDNSSList, node, rdnss)) { size_t opt_len = sizeof(struct nd_opt_rdnss) + sizeof(struct in6_addr); if (len + opt_len > max_len) { zlog_warn( "%s(%u): Tx RA: RDNSS option would exceed MTU, omitting it", ifp->name, ifp->ifindex); goto no_more_opts; } struct nd_opt_rdnss *opt = (struct nd_opt_rdnss *)(buf + len); opt->nd_opt_rdnss_type = ND_OPT_RDNSS; opt->nd_opt_rdnss_len = opt_len / 8; opt->nd_opt_rdnss_reserved = 0; opt->nd_opt_rdnss_lifetime = htonl( rdnss->lifetime_set ? rdnss->lifetime : MAX(1, 0.003 * zif->rtadv.MaxRtrAdvInterval)); len += sizeof(struct nd_opt_rdnss); IPV6_ADDR_COPY(buf + len, &rdnss->addr); len += sizeof(struct in6_addr); } /* DNS search list */ struct rtadv_dnssl *dnssl; for (ALL_LIST_ELEMENTS_RO(zif->rtadv.AdvDNSSLList, node, dnssl)) { size_t opt_len = sizeof(struct nd_opt_dnssl) + ((dnssl->encoded_len + 7) & ~7); if (len + opt_len > max_len) { zlog_warn( "%s(%u): Tx RA: DNSSL option would exceed MTU, omitting it", ifp->name, ifp->ifindex); goto no_more_opts; } struct nd_opt_dnssl *opt = (struct nd_opt_dnssl *)(buf + len); opt->nd_opt_dnssl_type = ND_OPT_DNSSL; opt->nd_opt_dnssl_len = opt_len / 8; opt->nd_opt_dnssl_reserved = 0; opt->nd_opt_dnssl_lifetime = htonl( dnssl->lifetime_set ? dnssl->lifetime : MAX(1, 0.003 * zif->rtadv.MaxRtrAdvInterval)); len += sizeof(struct nd_opt_dnssl); memcpy(buf + len, dnssl->encoded_name, dnssl->encoded_len); len += dnssl->encoded_len; /* Zero-pad to 8-octet boundary */ while (len % 8) buf[len++] = '\0'; } no_more_opts: msg.msg_name = (void *)&addr; msg.msg_namelen = sizeof(struct sockaddr_in6); msg.msg_iov = &iov; msg.msg_iovlen = 1; msg.msg_control = (void *)adata; msg.msg_controllen = CMSG_SPACE(sizeof(struct in6_pktinfo)); msg.msg_flags = 0; iov.iov_base = buf; iov.iov_len = len; cmsgptr = CMSG_FIRSTHDR(&msg); cmsgptr->cmsg_len = CMSG_LEN(sizeof(struct in6_pktinfo)); cmsgptr->cmsg_level = IPPROTO_IPV6; cmsgptr->cmsg_type = IPV6_PKTINFO; pkt = (struct in6_pktinfo *)CMSG_DATA(cmsgptr); memset(&pkt->ipi6_addr, 0, sizeof(struct in6_addr)); pkt->ipi6_ifindex = ifp->ifindex; ret = sendmsg(sock, &msg, 0); if (ret < 0) { flog_err_sys(EC_LIB_SOCKET, "%s(%u): Tx RA failed, socket %u error %d (%s)", ifp->name, ifp->ifindex, sock, errno, safe_strerror(errno)); } else zif->ra_sent++; } static int rtadv_timer(struct thread *thread) { struct zebra_vrf *zvrf = THREAD_ARG(thread); struct vrf *vrf; struct interface *ifp; struct zebra_if *zif; int period; zvrf->rtadv.ra_timer = NULL; if (zvrf->rtadv.adv_msec_if_count == 0) { period = 1000; /* 1 s */ rtadv_event(zvrf, RTADV_TIMER, 1 /* 1 s */); } else { period = 10; /* 10 ms */ rtadv_event(zvrf, RTADV_TIMER_MSEC, 10 /* 10 ms */); } RB_FOREACH (vrf, vrf_id_head, &vrfs_by_id) FOR_ALL_INTERFACES (vrf, ifp) { if (if_is_loopback(ifp) || CHECK_FLAG(ifp->status, ZEBRA_INTERFACE_VRF_LOOPBACK) || !if_is_operative(ifp)) continue; zif = ifp->info; if (zif->rtadv.AdvSendAdvertisements) { if (zif->rtadv.inFastRexmit) { /* We assume we fast rexmit every sec so * no * additional vars */ if (--zif->rtadv.NumFastReXmitsRemain <= 0) zif->rtadv.inFastRexmit = 0; if (IS_ZEBRA_DEBUG_SEND) zlog_debug( "Fast RA Rexmit on interface %s", ifp->name); rtadv_send_packet(rtadv_get_socket(zvrf), ifp); } else { zif->rtadv.AdvIntervalTimer -= period; if (zif->rtadv.AdvIntervalTimer <= 0) { /* FIXME: using MaxRtrAdvInterval each time isn't what section 6.2.4 of RFC4861 tells to do. */ zif->rtadv.AdvIntervalTimer = zif->rtadv .MaxRtrAdvInterval; rtadv_send_packet( rtadv_get_socket(zvrf), ifp); } } } } return 0; } static void rtadv_process_solicit(struct interface *ifp) { struct zebra_vrf *zvrf = vrf_info_lookup(ifp->vrf_id); assert(zvrf); rtadv_send_packet(rtadv_get_socket(zvrf), ifp); } /* * This function processes optional attributes off of * end of a RA packet received. At this point in * time we only care about this in one situation * which is when a interface does not have a LL * v6 address. We still need to be able to install * the mac address for v4 to v6 resolution */ static void rtadv_process_optional(uint8_t *optional, unsigned int len, struct interface *ifp, struct sockaddr_in6 *addr) { char *mac; while (len > 0) { struct nd_opt_hdr *opt_hdr = (struct nd_opt_hdr *)optional; switch(opt_hdr->nd_opt_type) { case ND_OPT_SOURCE_LINKADDR: mac = (char *)(optional+2); if_nbr_mac_to_ipv4ll_neigh_update(ifp, mac, &addr->sin6_addr, 1); break; default: break; } len -= 8 * opt_hdr->nd_opt_len; optional += 8 * opt_hdr->nd_opt_len; } } static void rtadv_process_advert(uint8_t *msg, unsigned int len, struct interface *ifp, struct sockaddr_in6 *addr) { struct nd_router_advert *radvert; char addr_str[INET6_ADDRSTRLEN]; struct zebra_if *zif; struct prefix p; zif = ifp->info; inet_ntop(AF_INET6, &addr->sin6_addr, addr_str, INET6_ADDRSTRLEN); if (len < sizeof(struct nd_router_advert)) { if (IS_ZEBRA_DEBUG_PACKET) zlog_debug("%s(%u): Rx RA with invalid length %d from %s", ifp->name, ifp->ifindex, len, addr_str); return; } if (!IN6_IS_ADDR_LINKLOCAL(&addr->sin6_addr)) { rtadv_process_optional(msg + sizeof(struct nd_router_advert), len - sizeof(struct nd_router_advert), ifp, addr); if (IS_ZEBRA_DEBUG_PACKET) zlog_debug("%s(%u): Rx RA with non-linklocal source address from %s", ifp->name, ifp->ifindex, addr_str); return; } radvert = (struct nd_router_advert *)msg; if ((radvert->nd_ra_curhoplimit && zif->rtadv.AdvCurHopLimit) && (radvert->nd_ra_curhoplimit != zif->rtadv.AdvCurHopLimit)) { flog_warn( EC_ZEBRA_RA_PARAM_MISMATCH, "%s(%u): Rx RA - our AdvCurHopLimit doesn't agree with %s", ifp->name, ifp->ifindex, addr_str); } if ((radvert->nd_ra_flags_reserved & ND_RA_FLAG_MANAGED) && !zif->rtadv.AdvManagedFlag) { flog_warn( EC_ZEBRA_RA_PARAM_MISMATCH, "%s(%u): Rx RA - our AdvManagedFlag doesn't agree with %s", ifp->name, ifp->ifindex, addr_str); } if ((radvert->nd_ra_flags_reserved & ND_RA_FLAG_OTHER) && !zif->rtadv.AdvOtherConfigFlag) { flog_warn( EC_ZEBRA_RA_PARAM_MISMATCH, "%s(%u): Rx RA - our AdvOtherConfigFlag doesn't agree with %s", ifp->name, ifp->ifindex, addr_str); } if ((radvert->nd_ra_reachable && zif->rtadv.AdvReachableTime) && (ntohl(radvert->nd_ra_reachable) != zif->rtadv.AdvReachableTime)) { flog_warn( EC_ZEBRA_RA_PARAM_MISMATCH, "%s(%u): Rx RA - our AdvReachableTime doesn't agree with %s", ifp->name, ifp->ifindex, addr_str); } if ((radvert->nd_ra_retransmit && zif->rtadv.AdvRetransTimer) && (ntohl(radvert->nd_ra_retransmit) != (unsigned int)zif->rtadv.AdvRetransTimer)) { flog_warn( EC_ZEBRA_RA_PARAM_MISMATCH, "%s(%u): Rx RA - our AdvRetransTimer doesn't agree with %s", ifp->name, ifp->ifindex, addr_str); } /* Create entry for neighbor if not known. */ p.family = AF_INET6; IPV6_ADDR_COPY(&p.u.prefix6, &addr->sin6_addr); p.prefixlen = IPV6_MAX_PREFIXLEN; if (!nbr_connected_check(ifp, &p)) nbr_connected_add_ipv6(ifp, &addr->sin6_addr); } static void rtadv_process_packet(uint8_t *buf, unsigned int len, ifindex_t ifindex, int hoplimit, struct sockaddr_in6 *from, struct zebra_vrf *zvrf) { struct icmp6_hdr *icmph; struct interface *ifp; struct zebra_if *zif; char addr_str[INET6_ADDRSTRLEN]; inet_ntop(AF_INET6, &from->sin6_addr, addr_str, INET6_ADDRSTRLEN); /* Interface search. */ ifp = if_lookup_by_index(ifindex, zvrf->vrf->vrf_id); if (ifp == NULL) { flog_warn(EC_ZEBRA_UNKNOWN_INTERFACE, "RA/RS received on unknown IF %u from %s", ifindex, addr_str); return; } if (IS_ZEBRA_DEBUG_PACKET) zlog_debug("%s(%u): Rx RA/RS len %d from %s", ifp->name, ifp->ifindex, len, addr_str); if (if_is_loopback(ifp) || CHECK_FLAG(ifp->status, ZEBRA_INTERFACE_VRF_LOOPBACK)) return; /* Check interface configuration. */ zif = ifp->info; if (!zif->rtadv.AdvSendAdvertisements) return; /* ICMP message length check. */ if (len < sizeof(struct icmp6_hdr)) { zlog_debug("%s(%u): Rx RA with Invalid ICMPV6 packet length %d", ifp->name, ifp->ifindex, len); return; } icmph = (struct icmp6_hdr *)buf; /* ICMP message type check. */ if (icmph->icmp6_type != ND_ROUTER_SOLICIT && icmph->icmp6_type != ND_ROUTER_ADVERT) { zlog_debug("%s(%u): Rx RA - Unwanted ICMPV6 message type %d", ifp->name, ifp->ifindex, icmph->icmp6_type); return; } /* Hoplimit check. */ if (hoplimit >= 0 && hoplimit != 255) { zlog_debug("%s(%u): Rx RA - Invalid hoplimit %d", ifp->name, ifp->ifindex, hoplimit); return; } /* Check ICMP message type. */ if (icmph->icmp6_type == ND_ROUTER_SOLICIT) rtadv_process_solicit(ifp); else if (icmph->icmp6_type == ND_ROUTER_ADVERT) rtadv_process_advert(buf, len, ifp, from); return; } static int rtadv_read(struct thread *thread) { int sock; int len; uint8_t buf[RTADV_MSG_SIZE]; struct sockaddr_in6 from; ifindex_t ifindex = 0; int hoplimit = -1; struct zebra_vrf *zvrf = THREAD_ARG(thread); sock = THREAD_FD(thread); zvrf->rtadv.ra_read = NULL; /* Register myself. */ rtadv_event(zvrf, RTADV_READ, sock); len = rtadv_recv_packet(zvrf, sock, buf, sizeof(buf), &from, &ifindex, &hoplimit); if (len < 0) { flog_err_sys(EC_LIB_SOCKET, "RA/RS recv failed, socket %u error %s", sock, safe_strerror(errno)); return len; } rtadv_process_packet(buf, (unsigned)len, ifindex, hoplimit, &from, zvrf); return 0; } static int rtadv_make_socket(ns_id_t ns_id) { int sock = -1; int ret = 0; struct icmp6_filter filter; frr_with_privs(&zserv_privs) { sock = ns_socket(AF_INET6, SOCK_RAW, IPPROTO_ICMPV6, ns_id); } if (sock < 0) { return -1; } ret = setsockopt_ipv6_pktinfo(sock, 1); if (ret < 0) { close(sock); return ret; } ret = setsockopt_ipv6_multicast_loop(sock, 0); if (ret < 0) { close(sock); return ret; } ret = setsockopt_ipv6_unicast_hops(sock, 255); if (ret < 0) { close(sock); return ret; } ret = setsockopt_ipv6_multicast_hops(sock, 255); if (ret < 0) { close(sock); return ret; } ret = setsockopt_ipv6_hoplimit(sock, 1); if (ret < 0) { close(sock); return ret; } ICMP6_FILTER_SETBLOCKALL(&filter); ICMP6_FILTER_SETPASS(ND_ROUTER_SOLICIT, &filter); ICMP6_FILTER_SETPASS(ND_ROUTER_ADVERT, &filter); ret = setsockopt(sock, IPPROTO_ICMPV6, ICMP6_FILTER, &filter, sizeof(struct icmp6_filter)); if (ret < 0) { zlog_info("ICMP6_FILTER set fail: %s", safe_strerror(errno)); close(sock); return ret; } return sock; } static struct rtadv_prefix *rtadv_prefix_new(void) { return XCALLOC(MTYPE_RTADV_PREFIX, sizeof(struct rtadv_prefix)); } static void rtadv_prefix_free(struct rtadv_prefix *rtadv_prefix) { XFREE(MTYPE_RTADV_PREFIX, rtadv_prefix); } static struct rtadv_prefix *rtadv_prefix_lookup(struct list *rplist, struct prefix_ipv6 *p) { struct listnode *node; struct rtadv_prefix *rprefix; for (ALL_LIST_ELEMENTS_RO(rplist, node, rprefix)) if (prefix_same((struct prefix *)&rprefix->prefix, (struct prefix *)p)) return rprefix; return NULL; } static struct rtadv_prefix *rtadv_prefix_get(struct list *rplist, struct prefix_ipv6 *p) { struct rtadv_prefix *rprefix; rprefix = rtadv_prefix_lookup(rplist, p); if (rprefix) return rprefix; rprefix = rtadv_prefix_new(); memcpy(&rprefix->prefix, p, sizeof(struct prefix_ipv6)); listnode_add(rplist, rprefix); return rprefix; } static void rtadv_prefix_set(struct zebra_if *zif, struct rtadv_prefix *rp) { struct rtadv_prefix *rprefix; rprefix = rtadv_prefix_get(zif->rtadv.AdvPrefixList, &rp->prefix); /* Set parameters. */ rprefix->AdvValidLifetime = rp->AdvValidLifetime; rprefix->AdvPreferredLifetime = rp->AdvPreferredLifetime; rprefix->AdvOnLinkFlag = rp->AdvOnLinkFlag; rprefix->AdvAutonomousFlag = rp->AdvAutonomousFlag; rprefix->AdvRouterAddressFlag = rp->AdvRouterAddressFlag; } static int rtadv_prefix_reset(struct zebra_if *zif, struct rtadv_prefix *rp) { struct rtadv_prefix *rprefix; rprefix = rtadv_prefix_lookup(zif->rtadv.AdvPrefixList, &rp->prefix); if (rprefix != NULL) { listnode_delete(zif->rtadv.AdvPrefixList, (void *)rprefix); rtadv_prefix_free(rprefix); return 1; } else return 0; } static void ipv6_nd_suppress_ra_set(struct interface *ifp, ipv6_nd_suppress_ra_status status) { struct zebra_if *zif; struct zebra_vrf *zvrf; zif = ifp->info; zvrf = vrf_info_lookup(ifp->vrf_id); if (status == RA_SUPPRESS) { /* RA is currently enabled */ if (zif->rtadv.AdvSendAdvertisements) { zif->rtadv.AdvSendAdvertisements = 0; zif->rtadv.AdvIntervalTimer = 0; zvrf->rtadv.adv_if_count--; if_leave_all_router(rtadv_get_socket(zvrf), ifp); if (zvrf->rtadv.adv_if_count == 0) rtadv_event(zvrf, RTADV_STOP, 0); } } else { if (!zif->rtadv.AdvSendAdvertisements) { zif->rtadv.AdvSendAdvertisements = 1; zif->rtadv.AdvIntervalTimer = 0; zvrf->rtadv.adv_if_count++; if (zif->rtadv.MaxRtrAdvInterval >= 1000) { /* Enable Fast RA only when RA interval is in * secs */ zif->rtadv.inFastRexmit = 1; zif->rtadv.NumFastReXmitsRemain = RTADV_NUM_FAST_REXMITS; } if_join_all_router(rtadv_get_socket(zvrf), ifp); if (zvrf->rtadv.adv_if_count == 1) rtadv_event(zvrf, RTADV_START, rtadv_get_socket(zvrf)); } } } /* * Handle client (BGP) message to enable or disable IPv6 RA on an interface. * Note that while the client could request RA on an interface on which the * operator has not enabled RA, RA won't be disabled upon client request * if the operator has explicitly enabled RA. The enable request can also * specify a RA interval (in seconds). */ static void zebra_interface_radv_set(ZAPI_HANDLER_ARGS, int enable) { struct stream *s; ifindex_t ifindex; struct interface *ifp; struct zebra_if *zif; int ra_interval_rxd; s = msg; /* Get interface index and RA interval. */ STREAM_GETL(s, ifindex); STREAM_GETL(s, ra_interval_rxd); if (ra_interval_rxd < 0) { zlog_warn( "Requested RA interval %d is garbage; ignoring request", ra_interval_rxd); return; } unsigned int ra_interval = ra_interval_rxd; if (IS_ZEBRA_DEBUG_EVENT) zlog_debug("%u: IF %u RA %s from client %s, interval %ums", zvrf_id(zvrf), ifindex, enable ? "enable" : "disable", zebra_route_string(client->proto), ra_interval); /* Locate interface and check VRF match. */ ifp = if_lookup_by_index(ifindex, zvrf->vrf->vrf_id); if (!ifp) { flog_warn(EC_ZEBRA_UNKNOWN_INTERFACE, "%u: IF %u RA %s client %s - interface unknown", zvrf_id(zvrf), ifindex, enable ? "enable" : "disable", zebra_route_string(client->proto)); return; } if (ifp->vrf_id != zvrf_id(zvrf)) { zlog_debug( "%u: IF %u RA %s client %s - VRF mismatch, IF VRF %u", zvrf_id(zvrf), ifindex, enable ? "enable" : "disable", zebra_route_string(client->proto), ifp->vrf_id); return; } zif = ifp->info; if (enable) { SET_FLAG(zif->rtadv.ra_configured, BGP_RA_CONFIGURED); ipv6_nd_suppress_ra_set(ifp, RA_ENABLE); if (ra_interval && (ra_interval * 1000) < (unsigned int) zif->rtadv.MaxRtrAdvInterval && !CHECK_FLAG(zif->rtadv.ra_configured, VTY_RA_INTERVAL_CONFIGURED)) zif->rtadv.MaxRtrAdvInterval = ra_interval * 1000; } else { UNSET_FLAG(zif->rtadv.ra_configured, BGP_RA_CONFIGURED); if (!CHECK_FLAG(zif->rtadv.ra_configured, VTY_RA_INTERVAL_CONFIGURED)) zif->rtadv.MaxRtrAdvInterval = RTADV_MAX_RTR_ADV_INTERVAL; if (!CHECK_FLAG(zif->rtadv.ra_configured, VTY_RA_CONFIGURED)) ipv6_nd_suppress_ra_set(ifp, RA_SUPPRESS); } stream_failure: return; } void zebra_interface_radv_disable(ZAPI_HANDLER_ARGS) { zebra_interface_radv_set(client, hdr, msg, zvrf, 0); } void zebra_interface_radv_enable(ZAPI_HANDLER_ARGS) { zebra_interface_radv_set(client, hdr, msg, zvrf, 1); } DEFUN (ipv6_nd_suppress_ra, ipv6_nd_suppress_ra_cmd, "ipv6 nd suppress-ra", "Interface IPv6 config commands\n" "Neighbor discovery\n" "Suppress Router Advertisement\n") { VTY_DECLVAR_CONTEXT(interface, ifp); struct zebra_if *zif = ifp->info; if (if_is_loopback(ifp) || CHECK_FLAG(ifp->status, ZEBRA_INTERFACE_VRF_LOOPBACK)) { vty_out(vty, "Cannot configure IPv6 Router Advertisements on this interface\n"); return CMD_WARNING_CONFIG_FAILED; } if (!CHECK_FLAG(zif->rtadv.ra_configured, BGP_RA_CONFIGURED)) ipv6_nd_suppress_ra_set(ifp, RA_SUPPRESS); UNSET_FLAG(zif->rtadv.ra_configured, VTY_RA_CONFIGURED); return CMD_SUCCESS; } DEFUN (no_ipv6_nd_suppress_ra, no_ipv6_nd_suppress_ra_cmd, "no ipv6 nd suppress-ra", NO_STR "Interface IPv6 config commands\n" "Neighbor discovery\n" "Suppress Router Advertisement\n") { VTY_DECLVAR_CONTEXT(interface, ifp); struct zebra_if *zif = ifp->info; if (if_is_loopback(ifp) || CHECK_FLAG(ifp->status, ZEBRA_INTERFACE_VRF_LOOPBACK)) { vty_out(vty, "Cannot configure IPv6 Router Advertisements on this interface\n"); return CMD_WARNING_CONFIG_FAILED; } ipv6_nd_suppress_ra_set(ifp, RA_ENABLE); SET_FLAG(zif->rtadv.ra_configured, VTY_RA_CONFIGURED); return CMD_SUCCESS; } DEFUN (ipv6_nd_ra_interval_msec, ipv6_nd_ra_interval_msec_cmd, "ipv6 nd ra-interval msec (70-1800000)", "Interface IPv6 config commands\n" "Neighbor discovery\n" "Router Advertisement interval\n" "Router Advertisement interval in milliseconds\n" "Router Advertisement interval in milliseconds\n") { int idx_number = 4; VTY_DECLVAR_CONTEXT(interface, ifp); unsigned interval; struct zebra_if *zif = ifp->info; struct zebra_vrf *zvrf; zvrf = vrf_info_lookup(ifp->vrf_id); interval = strtoul(argv[idx_number]->arg, NULL, 10); if ((zif->rtadv.AdvDefaultLifetime != -1 && interval > (unsigned)zif->rtadv.AdvDefaultLifetime * 1000)) { vty_out(vty, "This ra-interval would conflict with configured ra-lifetime!\n"); return CMD_WARNING_CONFIG_FAILED; } if (zif->rtadv.MaxRtrAdvInterval % 1000) zvrf->rtadv.adv_msec_if_count--; if (interval % 1000) zvrf->rtadv.adv_msec_if_count++; SET_FLAG(zif->rtadv.ra_configured, VTY_RA_INTERVAL_CONFIGURED); zif->rtadv.MaxRtrAdvInterval = interval; zif->rtadv.MinRtrAdvInterval = 0.33 * interval; zif->rtadv.AdvIntervalTimer = 0; return CMD_SUCCESS; } DEFUN (ipv6_nd_ra_interval, ipv6_nd_ra_interval_cmd, "ipv6 nd ra-interval (1-1800)", "Interface IPv6 config commands\n" "Neighbor discovery\n" "Router Advertisement interval\n" "Router Advertisement interval in seconds\n") { int idx_number = 3; VTY_DECLVAR_CONTEXT(interface, ifp); unsigned interval; struct zebra_if *zif = ifp->info; struct zebra_vrf *zvrf; zvrf = vrf_info_lookup(ifp->vrf_id); interval = strtoul(argv[idx_number]->arg, NULL, 10); if ((zif->rtadv.AdvDefaultLifetime != -1 && interval > (unsigned)zif->rtadv.AdvDefaultLifetime)) { vty_out(vty, "This ra-interval would conflict with configured ra-lifetime!\n"); return CMD_WARNING_CONFIG_FAILED; } if (zif->rtadv.MaxRtrAdvInterval % 1000) zvrf->rtadv.adv_msec_if_count--; /* convert to milliseconds */ interval = interval * 1000; SET_FLAG(zif->rtadv.ra_configured, VTY_RA_INTERVAL_CONFIGURED); zif->rtadv.MaxRtrAdvInterval = interval; zif->rtadv.MinRtrAdvInterval = 0.33 * interval; zif->rtadv.AdvIntervalTimer = 0; return CMD_SUCCESS; } DEFUN (no_ipv6_nd_ra_interval, no_ipv6_nd_ra_interval_cmd, "no ipv6 nd ra-interval [<(1-1800)|msec (1-1800000)>]", NO_STR "Interface IPv6 config commands\n" "Neighbor discovery\n" "Router Advertisement interval\n" "Router Advertisement interval in seconds\n" "Specify millisecond router advertisement interval\n" "Router Advertisement interval in milliseconds\n") { VTY_DECLVAR_CONTEXT(interface, ifp); struct zebra_if *zif = ifp->info; struct zebra_vrf *zvrf = NULL; zvrf = vrf_info_lookup(ifp->vrf_id); if (zif->rtadv.MaxRtrAdvInterval % 1000) zvrf->rtadv.adv_msec_if_count--; UNSET_FLAG(zif->rtadv.ra_configured, VTY_RA_INTERVAL_CONFIGURED); if (CHECK_FLAG(zif->rtadv.ra_configured, BGP_RA_CONFIGURED)) zif->rtadv.MaxRtrAdvInterval = 10000; else zif->rtadv.MaxRtrAdvInterval = RTADV_MAX_RTR_ADV_INTERVAL; zif->rtadv.AdvIntervalTimer = zif->rtadv.MaxRtrAdvInterval; zif->rtadv.MinRtrAdvInterval = RTADV_MIN_RTR_ADV_INTERVAL; return CMD_SUCCESS; } DEFUN (ipv6_nd_ra_lifetime, ipv6_nd_ra_lifetime_cmd, "ipv6 nd ra-lifetime (0-9000)", "Interface IPv6 config commands\n" "Neighbor discovery\n" "Router lifetime\n" "Router lifetime in seconds (0 stands for a non-default gw)\n") { int idx_number = 3; VTY_DECLVAR_CONTEXT(interface, ifp); struct zebra_if *zif = ifp->info; int lifetime; lifetime = strtoul(argv[idx_number]->arg, NULL, 10); /* The value to be placed in the Router Lifetime field * of Router Advertisements sent from the interface, * in seconds. MUST be either zero or between * MaxRtrAdvInterval and 9000 seconds. -- RFC4861, 6.2.1 */ if ((lifetime != 0 && lifetime * 1000 < zif->rtadv.MaxRtrAdvInterval)) { vty_out(vty, "This ra-lifetime would conflict with configured ra-interval\n"); return CMD_WARNING_CONFIG_FAILED; } zif->rtadv.AdvDefaultLifetime = lifetime; return CMD_SUCCESS; } DEFUN (no_ipv6_nd_ra_lifetime, no_ipv6_nd_ra_lifetime_cmd, "no ipv6 nd ra-lifetime [(0-9000)]", NO_STR "Interface IPv6 config commands\n" "Neighbor discovery\n" "Router lifetime\n" "Router lifetime in seconds (0 stands for a non-default gw)\n") { VTY_DECLVAR_CONTEXT(interface, ifp); struct zebra_if *zif = ifp->info; zif->rtadv.AdvDefaultLifetime = -1; return CMD_SUCCESS; } DEFUN (ipv6_nd_reachable_time, ipv6_nd_reachable_time_cmd, "ipv6 nd reachable-time (1-3600000)", "Interface IPv6 config commands\n" "Neighbor discovery\n" "Reachable time\n" "Reachable time in milliseconds\n") { int idx_number = 3; VTY_DECLVAR_CONTEXT(interface, ifp); struct zebra_if *zif = ifp->info; zif->rtadv.AdvReachableTime = strtoul(argv[idx_number]->arg, NULL, 10); return CMD_SUCCESS; } DEFUN (no_ipv6_nd_reachable_time, no_ipv6_nd_reachable_time_cmd, "no ipv6 nd reachable-time [(1-3600000)]", NO_STR "Interface IPv6 config commands\n" "Neighbor discovery\n" "Reachable time\n" "Reachable time in milliseconds\n") { VTY_DECLVAR_CONTEXT(interface, ifp); struct zebra_if *zif = ifp->info; zif->rtadv.AdvReachableTime = 0; return CMD_SUCCESS; } DEFUN (ipv6_nd_homeagent_preference, ipv6_nd_homeagent_preference_cmd, "ipv6 nd home-agent-preference (0-65535)", "Interface IPv6 config commands\n" "Neighbor discovery\n" "Home Agent preference\n" "preference value (default is 0, least preferred)\n") { int idx_number = 3; VTY_DECLVAR_CONTEXT(interface, ifp); struct zebra_if *zif = ifp->info; zif->rtadv.HomeAgentPreference = strtoul(argv[idx_number]->arg, NULL, 10); return CMD_SUCCESS; } DEFUN (no_ipv6_nd_homeagent_preference, no_ipv6_nd_homeagent_preference_cmd, "no ipv6 nd home-agent-preference [(0-65535)]", NO_STR "Interface IPv6 config commands\n" "Neighbor discovery\n" "Home Agent preference\n" "preference value (default is 0, least preferred)\n") { VTY_DECLVAR_CONTEXT(interface, ifp); struct zebra_if *zif = ifp->info; zif->rtadv.HomeAgentPreference = 0; return CMD_SUCCESS; } DEFUN (ipv6_nd_homeagent_lifetime, ipv6_nd_homeagent_lifetime_cmd, "ipv6 nd home-agent-lifetime (0-65520)", "Interface IPv6 config commands\n" "Neighbor discovery\n" "Home Agent lifetime\n" "Home Agent lifetime in seconds (0 to track ra-lifetime)\n") { int idx_number = 3; VTY_DECLVAR_CONTEXT(interface, ifp); struct zebra_if *zif = ifp->info; zif->rtadv.HomeAgentLifetime = strtoul(argv[idx_number]->arg, NULL, 10); return CMD_SUCCESS; } DEFUN (no_ipv6_nd_homeagent_lifetime, no_ipv6_nd_homeagent_lifetime_cmd, "no ipv6 nd home-agent-lifetime [(0-65520)]", NO_STR "Interface IPv6 config commands\n" "Neighbor discovery\n" "Home Agent lifetime\n" "Home Agent lifetime in seconds (0 to track ra-lifetime)\n") { VTY_DECLVAR_CONTEXT(interface, ifp); struct zebra_if *zif = ifp->info; zif->rtadv.HomeAgentLifetime = -1; return CMD_SUCCESS; } DEFUN (ipv6_nd_managed_config_flag, ipv6_nd_managed_config_flag_cmd, "ipv6 nd managed-config-flag", "Interface IPv6 config commands\n" "Neighbor discovery\n" "Managed address configuration flag\n") { VTY_DECLVAR_CONTEXT(interface, ifp); struct zebra_if *zif = ifp->info; zif->rtadv.AdvManagedFlag = 1; return CMD_SUCCESS; } DEFUN (no_ipv6_nd_managed_config_flag, no_ipv6_nd_managed_config_flag_cmd, "no ipv6 nd managed-config-flag", NO_STR "Interface IPv6 config commands\n" "Neighbor discovery\n" "Managed address configuration flag\n") { VTY_DECLVAR_CONTEXT(interface, ifp); struct zebra_if *zif = ifp->info; zif->rtadv.AdvManagedFlag = 0; return CMD_SUCCESS; } DEFUN (ipv6_nd_homeagent_config_flag, ipv6_nd_homeagent_config_flag_cmd, "ipv6 nd home-agent-config-flag", "Interface IPv6 config commands\n" "Neighbor discovery\n" "Home Agent configuration flag\n") { VTY_DECLVAR_CONTEXT(interface, ifp); struct zebra_if *zif = ifp->info; zif->rtadv.AdvHomeAgentFlag = 1; return CMD_SUCCESS; } DEFUN (no_ipv6_nd_homeagent_config_flag, no_ipv6_nd_homeagent_config_flag_cmd, "no ipv6 nd home-agent-config-flag", NO_STR "Interface IPv6 config commands\n" "Neighbor discovery\n" "Home Agent configuration flag\n") { VTY_DECLVAR_CONTEXT(interface, ifp); struct zebra_if *zif = ifp->info; zif->rtadv.AdvHomeAgentFlag = 0; return CMD_SUCCESS; } DEFUN (ipv6_nd_adv_interval_config_option, ipv6_nd_adv_interval_config_option_cmd, "ipv6 nd adv-interval-option", "Interface IPv6 config commands\n" "Neighbor discovery\n" "Advertisement Interval Option\n") { VTY_DECLVAR_CONTEXT(interface, ifp); struct zebra_if *zif = ifp->info; zif->rtadv.AdvIntervalOption = 1; return CMD_SUCCESS; } DEFUN (no_ipv6_nd_adv_interval_config_option, no_ipv6_nd_adv_interval_config_option_cmd, "no ipv6 nd adv-interval-option", NO_STR "Interface IPv6 config commands\n" "Neighbor discovery\n" "Advertisement Interval Option\n") { VTY_DECLVAR_CONTEXT(interface, ifp); struct zebra_if *zif = ifp->info; zif->rtadv.AdvIntervalOption = 0; return CMD_SUCCESS; } DEFUN (ipv6_nd_other_config_flag, ipv6_nd_other_config_flag_cmd, "ipv6 nd other-config-flag", "Interface IPv6 config commands\n" "Neighbor discovery\n" "Other statefull configuration flag\n") { VTY_DECLVAR_CONTEXT(interface, ifp); struct zebra_if *zif = ifp->info; zif->rtadv.AdvOtherConfigFlag = 1; return CMD_SUCCESS; } DEFUN (no_ipv6_nd_other_config_flag, no_ipv6_nd_other_config_flag_cmd, "no ipv6 nd other-config-flag", NO_STR "Interface IPv6 config commands\n" "Neighbor discovery\n" "Other statefull configuration flag\n") { VTY_DECLVAR_CONTEXT(interface, ifp); struct zebra_if *zif = ifp->info; zif->rtadv.AdvOtherConfigFlag = 0; return CMD_SUCCESS; } DEFUN (ipv6_nd_prefix, ipv6_nd_prefix_cmd, "ipv6 nd prefix X:X::X:X/M [<(0-4294967295)|infinite> <(0-4294967295)|infinite>] []", "Interface IPv6 config commands\n" "Neighbor discovery\n" "Prefix information\n" "IPv6 prefix\n" "Valid lifetime in seconds\n" "Infinite valid lifetime\n" "Preferred lifetime in seconds\n" "Infinite preferred lifetime\n" "Set Router Address flag\n" "Do not use prefix for onlink determination\n" "Do not use prefix for autoconfiguration\n" "Do not use prefix for autoconfiguration\n" "Do not use prefix for onlink determination\n") { /* prelude */ char *prefix = argv[3]->arg; int lifetimes = (argc > 4) && (argv[4]->type == RANGE_TKN || strmatch(argv[4]->text, "infinite")); int routeropts = lifetimes ? argc > 6 : argc > 4; int idx_routeropts = routeropts ? (lifetimes ? 6 : 4) : 0; char *lifetime = NULL, *preflifetime = NULL; int routeraddr = 0, offlink = 0, noautoconf = 0; if (lifetimes) { lifetime = argv[4]->type == RANGE_TKN ? argv[4]->arg : argv[4]->text; preflifetime = argv[5]->type == RANGE_TKN ? argv[5]->arg : argv[5]->text; } if (routeropts) { routeraddr = strmatch(argv[idx_routeropts]->text, "router-address"); if (!routeraddr) { offlink = (argc > idx_routeropts + 1 || strmatch(argv[idx_routeropts]->text, "off-link")); noautoconf = (argc > idx_routeropts + 1 || strmatch(argv[idx_routeropts]->text, "no-autoconfig")); } } /* business */ VTY_DECLVAR_CONTEXT(interface, ifp); struct zebra_if *zebra_if = ifp->info; int ret; struct rtadv_prefix rp; ret = str2prefix_ipv6(prefix, &rp.prefix); if (!ret) { vty_out(vty, "Malformed IPv6 prefix\n"); return CMD_WARNING_CONFIG_FAILED; } apply_mask_ipv6(&rp.prefix); /* RFC4861 4.6.2 */ rp.AdvOnLinkFlag = !offlink; rp.AdvAutonomousFlag = !noautoconf; rp.AdvRouterAddressFlag = routeraddr; rp.AdvValidLifetime = RTADV_VALID_LIFETIME; rp.AdvPreferredLifetime = RTADV_PREFERRED_LIFETIME; if (lifetimes) { rp.AdvValidLifetime = strmatch(lifetime, "infinite") ? UINT32_MAX : strtoll(lifetime, NULL, 10); rp.AdvPreferredLifetime = strmatch(preflifetime, "infinite") ? UINT32_MAX : strtoll(preflifetime, NULL, 10); if (rp.AdvPreferredLifetime > rp.AdvValidLifetime) { vty_out(vty, "Invalid preferred lifetime\n"); return CMD_WARNING_CONFIG_FAILED; } } rtadv_prefix_set(zebra_if, &rp); return CMD_SUCCESS; } DEFUN (no_ipv6_nd_prefix, no_ipv6_nd_prefix_cmd, "no ipv6 nd prefix X:X::X:X/M [<(0-4294967295)|infinite> <(0-4294967295)|infinite>] []", NO_STR "Interface IPv6 config commands\n" "Neighbor discovery\n" "Prefix information\n" "IPv6 prefix\n" "Valid lifetime in seconds\n" "Infinite valid lifetime\n" "Preferred lifetime in seconds\n" "Infinite preferred lifetime\n" "Set Router Address flag\n" "Do not use prefix for onlink determination\n" "Do not use prefix for autoconfiguration\n" "Do not use prefix for autoconfiguration\n" "Do not use prefix for onlink determination\n") { VTY_DECLVAR_CONTEXT(interface, ifp); struct zebra_if *zebra_if = ifp->info; int ret; struct rtadv_prefix rp; char *prefix = argv[4]->arg; ret = str2prefix_ipv6(prefix, &rp.prefix); if (!ret) { vty_out(vty, "Malformed IPv6 prefix\n"); return CMD_WARNING_CONFIG_FAILED; } apply_mask_ipv6(&rp.prefix); /* RFC4861 4.6.2 */ ret = rtadv_prefix_reset(zebra_if, &rp); if (!ret) { vty_out(vty, "Non-existant IPv6 prefix\n"); return CMD_WARNING_CONFIG_FAILED; } return CMD_SUCCESS; } DEFUN (ipv6_nd_router_preference, ipv6_nd_router_preference_cmd, "ipv6 nd router-preference ", "Interface IPv6 config commands\n" "Neighbor discovery\n" "Default router preference\n" "High default router preference\n" "Medium default router preference (default)\n" "Low default router preference\n") { int idx_high_medium_low = 3; VTY_DECLVAR_CONTEXT(interface, ifp); struct zebra_if *zif = ifp->info; int i = 0; while (0 != rtadv_pref_strs[i]) { if (strncmp(argv[idx_high_medium_low]->arg, rtadv_pref_strs[i], 1) == 0) { zif->rtadv.DefaultPreference = i; return CMD_SUCCESS; } i++; } return CMD_ERR_NO_MATCH; } DEFUN (no_ipv6_nd_router_preference, no_ipv6_nd_router_preference_cmd, "no ipv6 nd router-preference []", NO_STR "Interface IPv6 config commands\n" "Neighbor discovery\n" "Default router preference\n" "High default router preference\n" "Medium default router preference (default)\n" "Low default router preference\n") { VTY_DECLVAR_CONTEXT(interface, ifp); struct zebra_if *zif = ifp->info; zif->rtadv.DefaultPreference = RTADV_PREF_MEDIUM; /* Default per RFC4191. */ return CMD_SUCCESS; } DEFUN (ipv6_nd_mtu, ipv6_nd_mtu_cmd, "ipv6 nd mtu (1-65535)", "Interface IPv6 config commands\n" "Neighbor discovery\n" "Advertised MTU\n" "MTU in bytes\n") { int idx_number = 3; VTY_DECLVAR_CONTEXT(interface, ifp); struct zebra_if *zif = ifp->info; zif->rtadv.AdvLinkMTU = strtoul(argv[idx_number]->arg, NULL, 10); return CMD_SUCCESS; } DEFUN (no_ipv6_nd_mtu, no_ipv6_nd_mtu_cmd, "no ipv6 nd mtu [(1-65535)]", NO_STR "Interface IPv6 config commands\n" "Neighbor discovery\n" "Advertised MTU\n" "MTU in bytes\n") { VTY_DECLVAR_CONTEXT(interface, ifp); struct zebra_if *zif = ifp->info; zif->rtadv.AdvLinkMTU = 0; return CMD_SUCCESS; } static struct rtadv_rdnss *rtadv_rdnss_new(void) { return XCALLOC(MTYPE_RTADV_RDNSS, sizeof(struct rtadv_rdnss)); } static void rtadv_rdnss_free(struct rtadv_rdnss *rdnss) { XFREE(MTYPE_RTADV_RDNSS, rdnss); } static struct rtadv_rdnss *rtadv_rdnss_lookup(struct list *list, struct rtadv_rdnss *rdnss) { struct listnode *node; struct rtadv_rdnss *p; for (ALL_LIST_ELEMENTS_RO(list, node, p)) if (IPV6_ADDR_SAME(&p->addr, &rdnss->addr)) return p; return NULL; } static struct rtadv_rdnss *rtadv_rdnss_get(struct list *list, struct rtadv_rdnss *rdnss) { struct rtadv_rdnss *p; p = rtadv_rdnss_lookup(list, rdnss); if (p) return p; p = rtadv_rdnss_new(); memcpy(p, rdnss, sizeof(struct rtadv_rdnss)); listnode_add(list, p); return p; } static void rtadv_rdnss_set(struct zebra_if *zif, struct rtadv_rdnss *rdnss) { struct rtadv_rdnss *p; p = rtadv_rdnss_get(zif->rtadv.AdvRDNSSList, rdnss); p->lifetime = rdnss->lifetime; p->lifetime_set = rdnss->lifetime_set; } static int rtadv_rdnss_reset(struct zebra_if *zif, struct rtadv_rdnss *rdnss) { struct rtadv_rdnss *p; p = rtadv_rdnss_lookup(zif->rtadv.AdvRDNSSList, rdnss); if (p) { listnode_delete(zif->rtadv.AdvRDNSSList, p); rtadv_rdnss_free(p); return 1; } return 0; } static struct rtadv_dnssl *rtadv_dnssl_new(void) { return XCALLOC(MTYPE_RTADV_DNSSL, sizeof(struct rtadv_dnssl)); } static void rtadv_dnssl_free(struct rtadv_dnssl *dnssl) { XFREE(MTYPE_RTADV_DNSSL, dnssl); } static struct rtadv_dnssl *rtadv_dnssl_lookup(struct list *list, struct rtadv_dnssl *dnssl) { struct listnode *node; struct rtadv_dnssl *p; for (ALL_LIST_ELEMENTS_RO(list, node, p)) if (!strcasecmp(p->name, dnssl->name)) return p; return NULL; } static struct rtadv_dnssl *rtadv_dnssl_get(struct list *list, struct rtadv_dnssl *dnssl) { struct rtadv_dnssl *p; p = rtadv_dnssl_lookup(list, dnssl); if (p) return p; p = rtadv_dnssl_new(); memcpy(p, dnssl, sizeof(struct rtadv_dnssl)); listnode_add(list, p); return p; } static void rtadv_dnssl_set(struct zebra_if *zif, struct rtadv_dnssl *dnssl) { struct rtadv_dnssl *p; p = rtadv_dnssl_get(zif->rtadv.AdvDNSSLList, dnssl); memcpy(p, dnssl, sizeof(struct rtadv_dnssl)); } static int rtadv_dnssl_reset(struct zebra_if *zif, struct rtadv_dnssl *dnssl) { struct rtadv_dnssl *p; p = rtadv_dnssl_lookup(zif->rtadv.AdvDNSSLList, dnssl); if (p) { listnode_delete(zif->rtadv.AdvDNSSLList, p); rtadv_dnssl_free(p); return 1; } return 0; } /* * Convert dotted domain name (with or without trailing root zone dot) to * sequence of length-prefixed labels, as described in [RFC1035 3.1]. Write up * to strlen(in) + 2 octets to out. * * Returns the number of octets written to out or -1 if in does not constitute * a valid domain name. */ static int rtadv_dnssl_encode(uint8_t *out, const char *in) { const char *label_start, *label_end; size_t outp; outp = 0; label_start = in; while (*label_start) { size_t label_len; label_end = strchr(label_start, '.'); if (label_end == NULL) label_end = label_start + strlen(label_start); label_len = label_end - label_start; if (label_len >= 64) return -1; /* labels must be 63 octets or less */ out[outp++] = (uint8_t)label_len; memcpy(out + outp, label_start, label_len); outp += label_len; label_start += label_len; if (*label_start == '.') label_start++; } out[outp++] = '\0'; return outp; } DEFUN(ipv6_nd_rdnss, ipv6_nd_rdnss_cmd, "ipv6 nd rdnss X:X::X:X [<(0-4294967295)|infinite>]", "Interface IPv6 config commands\n" "Neighbor discovery\n" "Recursive DNS server information\n" "IPv6 address\n" "Valid lifetime in seconds\n" "Infinite valid lifetime\n") { VTY_DECLVAR_CONTEXT(interface, ifp); struct zebra_if *zif = ifp->info; struct rtadv_rdnss rdnss = {}; if (inet_pton(AF_INET6, argv[3]->arg, &rdnss.addr) != 1) { vty_out(vty, "Malformed IPv6 address\n"); return CMD_WARNING_CONFIG_FAILED; } if (argc > 4) { char *lifetime = argv[4]->type == RANGE_TKN ? argv[4]->arg : argv[4]->text; rdnss.lifetime = strmatch(lifetime, "infinite") ? UINT32_MAX : strtoll(lifetime, NULL, 10); rdnss.lifetime_set = 1; } rtadv_rdnss_set(zif, &rdnss); return CMD_SUCCESS; } DEFUN(no_ipv6_nd_rdnss, no_ipv6_nd_rdnss_cmd, "no ipv6 nd rdnss X:X::X:X [<(0-4294967295)|infinite>]", NO_STR "Interface IPv6 config commands\n" "Neighbor discovery\n" "Recursive DNS server information\n" "IPv6 address\n" "Valid lifetime in seconds\n" "Infinite valid lifetime\n") { VTY_DECLVAR_CONTEXT(interface, ifp); struct zebra_if *zif = ifp->info; struct rtadv_rdnss rdnss = {}; if (inet_pton(AF_INET6, argv[4]->arg, &rdnss.addr) != 1) { vty_out(vty, "Malformed IPv6 address\n"); return CMD_WARNING_CONFIG_FAILED; } if (rtadv_rdnss_reset(zif, &rdnss) != 1) { vty_out(vty, "Non-existant RDNSS address\n"); return CMD_WARNING_CONFIG_FAILED; } return CMD_SUCCESS; } DEFUN(ipv6_nd_dnssl, ipv6_nd_dnssl_cmd, "ipv6 nd dnssl SUFFIX [<(0-4294967295)|infinite>]", "Interface IPv6 config commands\n" "Neighbor discovery\n" "DNS search list information\n" "Domain name suffix\n" "Valid lifetime in seconds\n" "Infinite valid lifetime\n") { VTY_DECLVAR_CONTEXT(interface, ifp); struct zebra_if *zif = ifp->info; struct rtadv_dnssl dnssl = {}; size_t len; int ret; len = strlcpy(dnssl.name, argv[3]->arg, sizeof(dnssl.name)); if (len == 0 || len >= sizeof(dnssl.name)) { vty_out(vty, "Malformed DNS search domain\n"); return CMD_WARNING_CONFIG_FAILED; } if (dnssl.name[len - 1] == '.') { /* * Allow, but don't require, a trailing dot signifying the root * zone. Canonicalize by cutting it off if present. */ dnssl.name[len - 1] = '\0'; len--; } if (argc > 4) { char *lifetime = argv[4]->type == RANGE_TKN ? argv[4]->arg : argv[4]->text; dnssl.lifetime = strmatch(lifetime, "infinite") ? UINT32_MAX : strtoll(lifetime, NULL, 10); dnssl.lifetime_set = 1; } ret = rtadv_dnssl_encode(dnssl.encoded_name, dnssl.name); if (ret < 0) { vty_out(vty, "Malformed DNS search domain\n"); return CMD_WARNING_CONFIG_FAILED; } dnssl.encoded_len = ret; rtadv_dnssl_set(zif, &dnssl); return CMD_SUCCESS; } DEFUN(no_ipv6_nd_dnssl, no_ipv6_nd_dnssl_cmd, "no ipv6 nd dnssl SUFFIX [<(0-4294967295)|infinite>]", NO_STR "Interface IPv6 config commands\n" "Neighbor discovery\n" "DNS search list information\n" "Domain name suffix\n" "Valid lifetime in seconds\n" "Infinite valid lifetime\n") { VTY_DECLVAR_CONTEXT(interface, ifp); struct zebra_if *zif = ifp->info; struct rtadv_dnssl dnssl = {}; size_t len; len = strlcpy(dnssl.name, argv[4]->arg, sizeof(dnssl.name)); if (len == 0 || len >= sizeof(dnssl.name)) { vty_out(vty, "Malformed DNS search domain\n"); return CMD_WARNING_CONFIG_FAILED; } if (dnssl.name[len - 1] == '.') { dnssl.name[len - 1] = '\0'; len--; } if (rtadv_dnssl_reset(zif, &dnssl) != 1) { vty_out(vty, "Non-existant DNS search domain\n"); return CMD_WARNING_CONFIG_FAILED; } return CMD_SUCCESS; } /* Dump interface ND information to vty. */ static int nd_dump_vty(struct vty *vty, struct interface *ifp) { struct zebra_if *zif; struct rtadvconf *rtadv; int interval; zif = (struct zebra_if *)ifp->info; rtadv = &zif->rtadv; if (rtadv->AdvSendAdvertisements) { vty_out(vty, " ND advertised reachable time is %d milliseconds\n", rtadv->AdvReachableTime); vty_out(vty, " ND advertised retransmit interval is %d milliseconds\n", rtadv->AdvRetransTimer); vty_out(vty, " ND router advertisements sent: %d rcvd: %d\n", zif->ra_sent, zif->ra_rcvd); interval = rtadv->MaxRtrAdvInterval; if (interval % 1000) vty_out(vty, " ND router advertisements are sent every " "%d milliseconds\n", interval); else vty_out(vty, " ND router advertisements are sent every " "%d seconds\n", interval / 1000); if (rtadv->AdvDefaultLifetime != -1) vty_out(vty, " ND router advertisements live for %d seconds\n", rtadv->AdvDefaultLifetime); else vty_out(vty, " ND router advertisements lifetime tracks ra-interval\n"); vty_out(vty, " ND router advertisement default router preference is " "%s\n", rtadv_pref_strs[rtadv->DefaultPreference]); if (rtadv->AdvManagedFlag) vty_out(vty, " Hosts use DHCP to obtain routable addresses.\n"); else vty_out(vty, " Hosts use stateless autoconfig for addresses.\n"); if (rtadv->AdvHomeAgentFlag) { vty_out(vty, " ND router advertisements with Home Agent flag bit set.\n"); if (rtadv->HomeAgentLifetime != -1) vty_out(vty, " Home Agent lifetime is %u seconds\n", rtadv->HomeAgentLifetime); else vty_out(vty, " Home Agent lifetime tracks ra-lifetime\n"); vty_out(vty, " Home Agent preference is %u\n", rtadv->HomeAgentPreference); } if (rtadv->AdvIntervalOption) vty_out(vty, " ND router advertisements with Adv. Interval option.\n"); } return 0; } /* Write configuration about router advertisement. */ static int rtadv_config_write(struct vty *vty, struct interface *ifp) { struct zebra_if *zif; struct listnode *node; struct rtadv_prefix *rprefix; struct rtadv_rdnss *rdnss; struct rtadv_dnssl *dnssl; char buf[PREFIX_STRLEN]; int interval; zif = ifp->info; if (!(if_is_loopback(ifp) || CHECK_FLAG(ifp->status, ZEBRA_INTERFACE_VRF_LOOPBACK))) { if (zif->rtadv.AdvSendAdvertisements && CHECK_FLAG(zif->rtadv.ra_configured, VTY_RA_CONFIGURED)) vty_out(vty, " no ipv6 nd suppress-ra\n"); } interval = zif->rtadv.MaxRtrAdvInterval; if (CHECK_FLAG(zif->rtadv.ra_configured, VTY_RA_INTERVAL_CONFIGURED)) { if (interval % 1000) vty_out(vty, " ipv6 nd ra-interval msec %d\n", interval); else if (interval != RTADV_MAX_RTR_ADV_INTERVAL) vty_out(vty, " ipv6 nd ra-interval %d\n", interval / 1000); } if (zif->rtadv.AdvIntervalOption) vty_out(vty, " ipv6 nd adv-interval-option\n"); if (zif->rtadv.AdvDefaultLifetime != -1) vty_out(vty, " ipv6 nd ra-lifetime %d\n", zif->rtadv.AdvDefaultLifetime); if (zif->rtadv.HomeAgentPreference) vty_out(vty, " ipv6 nd home-agent-preference %u\n", zif->rtadv.HomeAgentPreference); if (zif->rtadv.HomeAgentLifetime != -1) vty_out(vty, " ipv6 nd home-agent-lifetime %u\n", zif->rtadv.HomeAgentLifetime); if (zif->rtadv.AdvHomeAgentFlag) vty_out(vty, " ipv6 nd home-agent-config-flag\n"); if (zif->rtadv.AdvReachableTime) vty_out(vty, " ipv6 nd reachable-time %d\n", zif->rtadv.AdvReachableTime); if (zif->rtadv.AdvManagedFlag) vty_out(vty, " ipv6 nd managed-config-flag\n"); if (zif->rtadv.AdvOtherConfigFlag) vty_out(vty, " ipv6 nd other-config-flag\n"); if (zif->rtadv.DefaultPreference != RTADV_PREF_MEDIUM) vty_out(vty, " ipv6 nd router-preference %s\n", rtadv_pref_strs[zif->rtadv.DefaultPreference]); if (zif->rtadv.AdvLinkMTU) vty_out(vty, " ipv6 nd mtu %d\n", zif->rtadv.AdvLinkMTU); for (ALL_LIST_ELEMENTS_RO(zif->rtadv.AdvPrefixList, node, rprefix)) { vty_out(vty, " ipv6 nd prefix %s", prefix2str(&rprefix->prefix, buf, sizeof(buf))); if ((rprefix->AdvValidLifetime != RTADV_VALID_LIFETIME) || (rprefix->AdvPreferredLifetime != RTADV_PREFERRED_LIFETIME)) { if (rprefix->AdvValidLifetime == UINT32_MAX) vty_out(vty, " infinite"); else vty_out(vty, " %u", rprefix->AdvValidLifetime); if (rprefix->AdvPreferredLifetime == UINT32_MAX) vty_out(vty, " infinite"); else vty_out(vty, " %u", rprefix->AdvPreferredLifetime); } if (!rprefix->AdvOnLinkFlag) vty_out(vty, " off-link"); if (!rprefix->AdvAutonomousFlag) vty_out(vty, " no-autoconfig"); if (rprefix->AdvRouterAddressFlag) vty_out(vty, " router-address"); vty_out(vty, "\n"); } for (ALL_LIST_ELEMENTS_RO(zif->rtadv.AdvRDNSSList, node, rdnss)) { char buf[INET6_ADDRSTRLEN]; vty_out(vty, " ipv6 nd rdnss %s", inet_ntop(AF_INET6, &rdnss->addr, buf, sizeof(buf))); if (rdnss->lifetime_set) { if (rdnss->lifetime == UINT32_MAX) vty_out(vty, " infinite"); else vty_out(vty, " %u", rdnss->lifetime); } vty_out(vty, "\n"); } for (ALL_LIST_ELEMENTS_RO(zif->rtadv.AdvDNSSLList, node, dnssl)) { vty_out(vty, " ipv6 nd dnssl %s", dnssl->name); if (dnssl->lifetime_set) { if (dnssl->lifetime == UINT32_MAX) vty_out(vty, " infinite"); else vty_out(vty, " %u", dnssl->lifetime); } vty_out(vty, "\n"); } return 0; } static void rtadv_event(struct zebra_vrf *zvrf, enum rtadv_event event, int val) { struct rtadv *rtadv = &zvrf->rtadv; switch (event) { case RTADV_START: thread_add_read(zrouter.master, rtadv_read, zvrf, val, &rtadv->ra_read); thread_add_event(zrouter.master, rtadv_timer, zvrf, 0, &rtadv->ra_timer); break; case RTADV_STOP: THREAD_OFF(rtadv->ra_timer); THREAD_OFF(rtadv->ra_read); break; case RTADV_TIMER: thread_add_timer(zrouter.master, rtadv_timer, zvrf, val, &rtadv->ra_timer); break; case RTADV_TIMER_MSEC: thread_add_timer_msec(zrouter.master, rtadv_timer, zvrf, val, &rtadv->ra_timer); break; case RTADV_READ: thread_add_read(zrouter.master, rtadv_read, zvrf, val, &rtadv->ra_read); break; default: break; } return; } void rtadv_init(struct zebra_vrf *zvrf) { if (vrf_is_backend_netns()) { zvrf->rtadv.sock = rtadv_make_socket(zvrf->zns->ns_id); zrouter.rtadv_sock = -1; } else { zvrf->rtadv.sock = -1; if (zrouter.rtadv_sock < 0) zrouter.rtadv_sock = rtadv_make_socket(zvrf->zns->ns_id); } } void rtadv_terminate(struct zebra_vrf *zvrf) { rtadv_event(zvrf, RTADV_STOP, 0); if (zvrf->rtadv.sock >= 0) { close(zvrf->rtadv.sock); zvrf->rtadv.sock = -1; } else if (zrouter.rtadv_sock >= 0) { close(zrouter.rtadv_sock); zrouter.rtadv_sock = -1; } zvrf->rtadv.adv_if_count = 0; zvrf->rtadv.adv_msec_if_count = 0; } void rtadv_cmd_init(void) { hook_register(zebra_if_extra_info, nd_dump_vty); hook_register(zebra_if_config_wr, rtadv_config_write); install_element(INTERFACE_NODE, &ipv6_nd_suppress_ra_cmd); install_element(INTERFACE_NODE, &no_ipv6_nd_suppress_ra_cmd); install_element(INTERFACE_NODE, &ipv6_nd_ra_interval_cmd); install_element(INTERFACE_NODE, &ipv6_nd_ra_interval_msec_cmd); install_element(INTERFACE_NODE, &no_ipv6_nd_ra_interval_cmd); install_element(INTERFACE_NODE, &ipv6_nd_ra_lifetime_cmd); install_element(INTERFACE_NODE, &no_ipv6_nd_ra_lifetime_cmd); install_element(INTERFACE_NODE, &ipv6_nd_reachable_time_cmd); install_element(INTERFACE_NODE, &no_ipv6_nd_reachable_time_cmd); install_element(INTERFACE_NODE, &ipv6_nd_managed_config_flag_cmd); install_element(INTERFACE_NODE, &no_ipv6_nd_managed_config_flag_cmd); install_element(INTERFACE_NODE, &ipv6_nd_other_config_flag_cmd); install_element(INTERFACE_NODE, &no_ipv6_nd_other_config_flag_cmd); install_element(INTERFACE_NODE, &ipv6_nd_homeagent_config_flag_cmd); install_element(INTERFACE_NODE, &no_ipv6_nd_homeagent_config_flag_cmd); install_element(INTERFACE_NODE, &ipv6_nd_homeagent_preference_cmd); install_element(INTERFACE_NODE, &no_ipv6_nd_homeagent_preference_cmd); install_element(INTERFACE_NODE, &ipv6_nd_homeagent_lifetime_cmd); install_element(INTERFACE_NODE, &no_ipv6_nd_homeagent_lifetime_cmd); install_element(INTERFACE_NODE, &ipv6_nd_adv_interval_config_option_cmd); install_element(INTERFACE_NODE, &no_ipv6_nd_adv_interval_config_option_cmd); install_element(INTERFACE_NODE, &ipv6_nd_prefix_cmd); install_element(INTERFACE_NODE, &no_ipv6_nd_prefix_cmd); install_element(INTERFACE_NODE, &ipv6_nd_router_preference_cmd); install_element(INTERFACE_NODE, &no_ipv6_nd_router_preference_cmd); install_element(INTERFACE_NODE, &ipv6_nd_mtu_cmd); install_element(INTERFACE_NODE, &no_ipv6_nd_mtu_cmd); install_element(INTERFACE_NODE, &ipv6_nd_rdnss_cmd); install_element(INTERFACE_NODE, &no_ipv6_nd_rdnss_cmd); install_element(INTERFACE_NODE, &ipv6_nd_dnssl_cmd); install_element(INTERFACE_NODE, &no_ipv6_nd_dnssl_cmd); } static int if_join_all_router(int sock, struct interface *ifp) { int ret; struct ipv6_mreq mreq; memset(&mreq, 0, sizeof(struct ipv6_mreq)); inet_pton(AF_INET6, ALLROUTER, &mreq.ipv6mr_multiaddr); mreq.ipv6mr_interface = ifp->ifindex; ret = setsockopt(sock, IPPROTO_IPV6, IPV6_JOIN_GROUP, (char *)&mreq, sizeof mreq); if (ret < 0) flog_err_sys(EC_LIB_SOCKET, "%s(%u): Failed to join group, socket %u error %s", ifp->name, ifp->ifindex, sock, safe_strerror(errno)); if (IS_ZEBRA_DEBUG_EVENT) zlog_debug( "%s(%u): Join All-Routers multicast group, socket %u", ifp->name, ifp->ifindex, sock); return 0; } static int if_leave_all_router(int sock, struct interface *ifp) { int ret; struct ipv6_mreq mreq; memset(&mreq, 0, sizeof(struct ipv6_mreq)); inet_pton(AF_INET6, ALLROUTER, &mreq.ipv6mr_multiaddr); mreq.ipv6mr_interface = ifp->ifindex; ret = setsockopt(sock, IPPROTO_IPV6, IPV6_LEAVE_GROUP, (char *)&mreq, sizeof mreq); if (ret < 0) flog_err_sys( EC_LIB_SOCKET, "%s(%u): Failed to leave group, socket %u error %s", ifp->name, ifp->ifindex, sock, safe_strerror(errno)); if (IS_ZEBRA_DEBUG_EVENT) zlog_debug( "%s(%u): Leave All-Routers multicast group, socket %u", ifp->name, ifp->ifindex, sock); return 0; } #else void rtadv_init(struct zebra_vrf *zvrf) { /* Empty.*/; } void rtadv_terminate(struct zebra_vrf *zvrf) { /* Empty.*/; } void rtadv_cmd_init(void) { /* Empty.*/; } #endif /* HAVE_RTADV */ frr-7.2.1/zebra/rtadv.h0000644000000000000000000001026713610377563011616 00000000000000/* Router advertisement * Copyright (C) 2005 6WIND * Copyright (C) 1999 Kunihiro Ishiguro * * This file is part of GNU Zebra. * * GNU Zebra is free software; you can redistribute it and/or modify it * under the terms of the GNU General Public License as published by the * Free Software Foundation; either version 2, or (at your option) any * later version. * * GNU Zebra 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; see the file COPYING; if not, write to the Free Software * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA */ #ifndef _ZEBRA_RTADV_H #define _ZEBRA_RTADV_H #include "vty.h" #include "zebra/interface.h" #ifdef __cplusplus extern "C" { #endif /* NB: RTADV is defined in zebra/interface.h above */ #if defined(HAVE_RTADV) /* Router advertisement prefix. */ struct rtadv_prefix { /* Prefix to be advertised. */ struct prefix_ipv6 prefix; /* The value to be placed in the Valid Lifetime in the Prefix */ uint32_t AdvValidLifetime; #define RTADV_VALID_LIFETIME 2592000 /* The value to be placed in the on-link flag */ int AdvOnLinkFlag; /* The value to be placed in the Preferred Lifetime in the Prefix Information option, in seconds.*/ uint32_t AdvPreferredLifetime; #define RTADV_PREFERRED_LIFETIME 604800 /* The value to be placed in the Autonomous Flag. */ int AdvAutonomousFlag; /* The value to be placed in the Router Address Flag [RFC6275 7.2]. */ int AdvRouterAddressFlag; #ifndef ND_OPT_PI_FLAG_RADDR #define ND_OPT_PI_FLAG_RADDR 0x20 #endif }; /* RFC4584 Extension to Sockets API for Mobile IPv6 */ #ifndef ND_OPT_ADV_INTERVAL #define ND_OPT_ADV_INTERVAL 7 /* Adv Interval Option */ #endif #ifndef ND_OPT_HA_INFORMATION #define ND_OPT_HA_INFORMATION 8 /* HA Information Option */ #endif #ifndef HAVE_STRUCT_ND_OPT_ADV_INTERVAL struct nd_opt_adv_interval { /* Advertisement interval option */ uint8_t nd_opt_ai_type; uint8_t nd_opt_ai_len; uint16_t nd_opt_ai_reserved; uint32_t nd_opt_ai_interval; } __attribute__((__packed__)); #else #ifndef HAVE_STRUCT_ND_OPT_ADV_INTERVAL_ND_OPT_AI_TYPE /* fields may have to be renamed */ #define nd_opt_ai_type nd_opt_adv_interval_type #define nd_opt_ai_len nd_opt_adv_interval_len #define nd_opt_ai_reserved nd_opt_adv_interval_reserved #define nd_opt_ai_interval nd_opt_adv_interval_ival #endif #endif #ifndef HAVE_STRUCT_ND_OPT_HOMEAGENT_INFO struct nd_opt_homeagent_info { /* Home Agent info */ uint8_t nd_opt_hai_type; uint8_t nd_opt_hai_len; uint16_t nd_opt_hai_reserved; uint16_t nd_opt_hai_preference; uint16_t nd_opt_hai_lifetime; } __attribute__((__packed__)); #endif #ifndef ND_OPT_RDNSS #define ND_OPT_RDNSS 25 #endif #ifndef ND_OPT_DNSSL #define ND_OPT_DNSSL 31 #endif #ifndef HAVE_STRUCT_ND_OPT_RDNSS struct nd_opt_rdnss { /* Recursive DNS server option [RFC8106 5.1] */ uint8_t nd_opt_rdnss_type; uint8_t nd_opt_rdnss_len; uint16_t nd_opt_rdnss_reserved; uint32_t nd_opt_rdnss_lifetime; /* Followed by one or more IPv6 addresses */ } __attribute__((__packed__)); #endif #ifndef HAVE_STRUCT_ND_OPT_DNSSL struct nd_opt_dnssl { /* DNS search list option [RFC8106 5.2] */ uint8_t nd_opt_dnssl_type; uint8_t nd_opt_dnssl_len; uint16_t nd_opt_dnssl_reserved; uint32_t nd_opt_dnssl_lifetime; /* * Followed by one or more domain names encoded as in [RFC1035 3.1]. * Multiple domain names are concatenated after encoding. In any case, * the result is zero-padded to a multiple of 8 octets. */ } __attribute__((__packed__)); #endif extern const char *rtadv_pref_strs[]; #endif /* HAVE_RTADV */ typedef enum { RA_ENABLE = 0, RA_SUPPRESS, } ipv6_nd_suppress_ra_status; extern void rtadv_init(struct zebra_vrf *zvrf); extern void rtadv_terminate(struct zebra_vrf *zvrf); extern void rtadv_cmd_init(void); extern void zebra_interface_radv_disable(ZAPI_HANDLER_ARGS); extern void zebra_interface_radv_enable(ZAPI_HANDLER_ARGS); #ifdef __cplusplus } #endif #endif /* _ZEBRA_RTADV_H */ frr-7.2.1/zebra/rtread_getmsg.c0000644000000000000000000001627713610377563013327 00000000000000/* * Kernel routing table readup by getmsg(2) * Copyright (C) 1999 Michael Handler * * This file is part of GNU Zebra. * * GNU Zebra is free software; you can redistribute it and/or modify it * under the terms of the GNU General Public License as published by the * Free Software Foundation; either version 2, or (at your option) any * later version. * * GNU Zebra 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; see the file COPYING; if not, write to the Free Software * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA */ #include #ifdef SUNOS_5 #include "prefix.h" #include "log.h" #include "if.h" #include "vrf.h" #include "vty.h" #include "lib_errors.h" #include "zebra/rib.h" #include "zebra/rt.h" #include "zebra/zebra_pbr.h" #include "zebra/zebra_errors.h" /* Thank you, Solaris, for polluting application symbol namespace. */ #undef hook_register #undef hook_unregister #include #include /* Solaris defines these in both and , sigh */ #ifdef SUNOS_5 #include #ifndef T_CURRENT #define T_CURRENT MI_T_CURRENT #endif /* T_CURRENT */ #ifndef IRE_CACHE #define IRE_CACHE 0x0020 /* Cached Route entry */ #endif /* IRE_CACHE */ #ifndef IRE_HOST_REDIRECT #define IRE_HOST_REDIRECT 0x0200 /* Host route entry from redirects */ #endif /* IRE_HOST_REDIRECT */ #ifndef IRE_CACHETABLE #define IRE_CACHETABLE (IRE_CACHE | IRE_BROADCAST | IRE_LOCAL | IRE_LOOPBACK) #endif /* IRE_CACHETABLE */ #undef IPOPT_EOL #undef IPOPT_NOP #undef IPOPT_LSRR #undef IPOPT_RR #undef IPOPT_SSRR #endif /* SUNOS_5 */ #include #include #include /* device to read IP routing table from */ #ifndef _PATH_GETMSG_ROUTE #define _PATH_GETMSG_ROUTE "/dev/ip" #endif /* _PATH_GETMSG_ROUTE */ #define RT_BUFSIZ 8192 static void handle_route_entry(mib2_ipRouteEntry_t *routeEntry) { struct prefix prefix; struct in_addr tmpaddr; struct nexthop nh; uint8_t zebra_flags = 0; if (routeEntry->ipRouteInfo.re_ire_type & IRE_CACHETABLE) return; if (routeEntry->ipRouteInfo.re_ire_type & IRE_HOST_REDIRECT) zebra_flags |= ZEBRA_FLAG_SELFROUTE; prefix.family = AF_INET; tmpaddr.s_addr = routeEntry->ipRouteDest; prefix.u.prefix4 = tmpaddr; tmpaddr.s_addr = routeEntry->ipRouteMask; prefix.prefixlen = ip_masklen(tmpaddr); memset(&nh, 0, sizeof(nh)); nh.vrf_id = VRF_DEFAULT; nh.type = NEXTHOP_TYPE_IPV4; nh.gate.ipv4.s_addr = routeEntry->ipRouteNextHop; rib_add(AFI_IP, SAFI_UNICAST, VRF_DEFAULT, ZEBRA_ROUTE_KERNEL, 0, zebra_flags, &prefix, NULL, &nh, 0, 0, 0, 0, 0); } void route_read(struct zebra_ns *zns) { char storage[RT_BUFSIZ]; struct T_optmgmt_req *TLIreq = (struct T_optmgmt_req *)storage; struct T_optmgmt_ack *TLIack = (struct T_optmgmt_ack *)storage; struct T_error_ack *TLIerr = (struct T_error_ack *)storage; struct opthdr *MIB2hdr; mib2_ipRouteEntry_t *routeEntry, *lastRouteEntry; struct strbuf msgdata; int flags, dev, retval, process; if ((dev = open(_PATH_GETMSG_ROUTE, O_RDWR)) == -1) { flog_err_sys(EC_LIB_SYSTEM_CALL, "can't open %s: %s", _PATH_GETMSG_ROUTE, safe_strerror(errno)); return; } TLIreq->PRIM_type = T_OPTMGMT_REQ; TLIreq->OPT_offset = sizeof(struct T_optmgmt_req); TLIreq->OPT_length = sizeof(struct opthdr); TLIreq->MGMT_flags = T_CURRENT; MIB2hdr = (struct opthdr *)&TLIreq[1]; MIB2hdr->level = MIB2_IP; MIB2hdr->name = 0; MIB2hdr->len = 0; msgdata.buf = storage; msgdata.len = sizeof(struct T_optmgmt_req) + sizeof(struct opthdr); flags = 0; if (putmsg(dev, &msgdata, NULL, flags) == -1) { flog_err_sys(EC_LIB_SOCKET, "putmsg failed: %s", safe_strerror(errno)); goto exit; } MIB2hdr = (struct opthdr *)&TLIack[1]; msgdata.maxlen = sizeof(storage); while (1) { flags = 0; retval = getmsg(dev, &msgdata, NULL, &flags); if (retval == -1) { flog_err_sys(EC_LIB_SYSTEM_CALL, "getmsg(ctl) failed: %s", safe_strerror(errno)); goto exit; } /* This is normal loop termination */ if (retval == 0 && (size_t)msgdata.len >= sizeof(struct T_optmgmt_ack) && TLIack->PRIM_type == T_OPTMGMT_ACK && TLIack->MGMT_flags == T_SUCCESS && MIB2hdr->len == 0) break; if ((size_t)msgdata.len >= sizeof(struct T_error_ack) && TLIerr->PRIM_type == T_ERROR_ACK) { zlog_debug("getmsg(ctl) returned T_ERROR_ACK: %s", safe_strerror((TLIerr->TLI_error == TSYSERR) ? TLIerr->UNIX_error : EPROTO)); break; } /* should dump more debugging info to the log statement, like what GateD does in this instance, but not critical yet. */ if (retval != MOREDATA || (size_t)msgdata.len < sizeof(struct T_optmgmt_ack) || TLIack->PRIM_type != T_OPTMGMT_ACK || TLIack->MGMT_flags != T_SUCCESS) { errno = ENOMSG; zlog_debug("getmsg(ctl) returned bizarreness"); break; } /* MIB2_IP_21 is the the pseudo-MIB2 ipRouteTable entry, see . "This isn't the MIB data you're looking for." */ process = (MIB2hdr->level == MIB2_IP && MIB2hdr->name == MIB2_IP_21) ? 1 : 0; /* getmsg writes the data buffer out completely, not to the closest smaller multiple. Unless reassembling data structures across buffer boundaries is your idea of a good time, set maxlen to the closest smaller multiple of the size of the datastructure you're retrieving. */ msgdata.maxlen = sizeof(storage) - (sizeof(storage) % sizeof(mib2_ipRouteEntry_t)); msgdata.len = 0; flags = 0; do { retval = getmsg(dev, NULL, &msgdata, &flags); if (retval == -1) { flog_err_sys(EC_LIB_SYSTEM_CALL, "getmsg(data) failed: %s", safe_strerror(errno)); goto exit; } if (!(retval == 0 || retval == MOREDATA)) { zlog_debug("getmsg(data) returned %d", retval); goto exit; } if (process) { if (msgdata.len % sizeof(mib2_ipRouteEntry_t) != 0) { zlog_debug( "getmsg(data) returned " "msgdata.len = %d (%% sizeof (mib2_ipRouteEntry_t) != 0)", msgdata.len); goto exit; } routeEntry = (mib2_ipRouteEntry_t *)msgdata.buf; lastRouteEntry = (mib2_ipRouteEntry_t *)(msgdata.buf + msgdata.len); do { handle_route_entry(routeEntry); } while (++routeEntry < lastRouteEntry); } } while (retval == MOREDATA); } exit: close(dev); } /* Only implemented for netlink method */ void macfdb_read(struct zebra_ns *zns) { } void macfdb_read_for_bridge(struct zebra_ns *zns, struct interface *ifp, struct interface *br_if) { } void macfdb_read_specific_mac(struct zebra_ns *zns, struct interface *br_if, struct ethaddr *mac, vlanid_t vid) { } void neigh_read(struct zebra_ns *zns) { } void neigh_read_for_vlan(struct zebra_ns *zns, struct interface *vlan_if) { } void neigh_read_specific_ip(struct ipaddr *ip, struct interface *vlan_if) { } void kernel_read_pbr_rules(struct zebra_ns *zns) { } #endif /* SUNOS_5 */ frr-7.2.1/zebra/rtread_netlink.c0000644000000000000000000000357313610377563013500 00000000000000/* * Kernel routing table readup by netlink * Copyright (C) 1998 Kunihiro Ishiguro * * This file is part of GNU Zebra. * * GNU Zebra is free software; you can redistribute it and/or modify it * under the terms of the GNU General Public License as published by the * Free Software Foundation; either version 2, or (at your option) any * later version. * * GNU Zebra 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; see the file COPYING; if not, write to the Free Software * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA */ #include #ifdef GNU_LINUX #include "vty.h" #include "zebra/rt.h" #include "zebra/zebra_pbr.h" #include "zebra/rt_netlink.h" #include "zebra/rule_netlink.h" void route_read(struct zebra_ns *zns) { netlink_route_read(zns); } void macfdb_read(struct zebra_ns *zns) { netlink_macfdb_read(zns); } void macfdb_read_for_bridge(struct zebra_ns *zns, struct interface *ifp, struct interface *br_if) { netlink_macfdb_read_for_bridge(zns, ifp, br_if); } void macfdb_read_specific_mac(struct zebra_ns *zns, struct interface *br_if, struct ethaddr *mac, vlanid_t vid) { netlink_macfdb_read_specific_mac(zns, br_if, mac, vid); } void neigh_read(struct zebra_ns *zns) { netlink_neigh_read(zns); } void neigh_read_for_vlan(struct zebra_ns *zns, struct interface *vlan_if) { netlink_neigh_read_for_vlan(zns, vlan_if); } void neigh_read_specific_ip(struct ipaddr *ip, struct interface *vlan_if) { netlink_neigh_read_specific_ip(ip, vlan_if); } void kernel_read_pbr_rules(struct zebra_ns *zns) { netlink_rules_read(zns); } #endif /* GNU_LINUX */ frr-7.2.1/zebra/rtread_sysctl.c0000644000000000000000000000537613610377563013360 00000000000000/* * Kernel routing table read by sysctl function. * Copyright (C) 1997, 98 Kunihiro Ishiguro * * This file is part of GNU Zebra. * * GNU Zebra is free software; you can redistribute it and/or modify it * under the terms of the GNU General Public License as published by the * Free Software Foundation; either version 2, or (at your option) any * later version. * * GNU Zebra 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; see the file COPYING; if not, write to the Free Software * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA */ #include #if !defined(GNU_LINUX) && !defined(SUNOS_5) #include "memory.h" #include "zebra_memory.h" #include "log.h" #include "vrf.h" #include "zebra/rt.h" #include "zebra/kernel_socket.h" #include "zebra/zebra_pbr.h" #include "zebra/zebra_errors.h" /* Kernel routing table read up by sysctl function. */ void route_read(struct zebra_ns *zns) { caddr_t buf, end, ref; size_t bufsiz; struct rt_msghdr *rtm; #define MIBSIZ 6 int mib[MIBSIZ] = {CTL_NET, PF_ROUTE, 0, 0, NET_RT_DUMP, 0}; if (zns->ns_id != NS_DEFAULT) return; /* Get buffer size. */ if (sysctl(mib, MIBSIZ, NULL, &bufsiz, NULL, 0) < 0) { flog_warn(EC_ZEBRA_SYSCTL_FAILED, "sysctl fail: %s", safe_strerror(errno)); return; } /* Allocate buffer. */ ref = buf = XMALLOC(MTYPE_TMP, bufsiz); /* Read routing table information by calling sysctl(). */ if (sysctl(mib, MIBSIZ, buf, &bufsiz, NULL, 0) < 0) { flog_warn(EC_ZEBRA_SYSCTL_FAILED, "sysctl() fail by %s", safe_strerror(errno)); XFREE(MTYPE_TMP, ref); return; } for (end = buf + bufsiz; buf < end; buf += rtm->rtm_msglen) { rtm = (struct rt_msghdr *)buf; /* We must set RTF_DONE here, so rtm_read() doesn't ignore the * message. */ SET_FLAG(rtm->rtm_flags, RTF_DONE); rtm_read(rtm); } /* Free buffer. */ XFREE(MTYPE_TMP, ref); return; } /* Only implemented for the netlink method. */ void macfdb_read(struct zebra_ns *zns) { } void macfdb_read_for_bridge(struct zebra_ns *zns, struct interface *ifp, struct interface *br_if) { } void macfdb_read_specific_mac(struct zebra_ns *zns, struct interface *br_if, struct ethaddr *mac, vlanid_t vid) { } void neigh_read(struct zebra_ns *zns) { } void neigh_read_for_vlan(struct zebra_ns *zns, struct interface *vlan_if) { } void neigh_read_specific_ip(struct ipaddr *ip, struct interface *vlan_if) { } void kernel_read_pbr_rules(struct zebra_ns *zns) { } #endif /* !defined(GNU_LINUX) && !defined(SUNOS_5) */ frr-7.2.1/zebra/rule_netlink.c0000644000000000000000000002004113610377563013153 00000000000000/* * Zebra Policy Based Routing (PBR) interaction with the kernel using * netlink. * Copyright (C) 2018 Cumulus Networks, Inc. * * This file is part of FRR. * * FRR is free software; you can redistribute it and/or modify it * under the terms of the GNU General Public License as published by the * Free Software Foundation; either version 2, or (at your option) any * later version. * * FRR 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 FRR; see the file COPYING. If not, write to the Free * Software Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA * 02111-1307, USA. */ #include #ifdef HAVE_NETLINK #include "if.h" #include "prefix.h" #include "vrf.h" #include #include "zebra/zserv.h" #include "zebra/zebra_ns.h" #include "zebra/zebra_vrf.h" #include "zebra/rt.h" #include "zebra/interface.h" #include "zebra/debug.h" #include "zebra/rtadv.h" #include "zebra/kernel_netlink.h" #include "zebra/rule_netlink.h" #include "zebra/zebra_pbr.h" #include "zebra/zebra_errors.h" /* definitions */ /* static function declarations */ /* Private functions */ /* Install or uninstall specified rule for a specific interface. * Form netlink message and ship it. Currently, notify status after * waiting for netlink status. */ static int netlink_rule_update(int cmd, struct zebra_pbr_rule *rule) { int family; int bytelen; struct { struct nlmsghdr n; struct fib_rule_hdr frh; char buf[NL_PKT_BUF_SIZE]; } req; struct zebra_ns *zns = zebra_ns_lookup(NS_DEFAULT); struct sockaddr_nl snl; char buf1[PREFIX_STRLEN]; char buf2[PREFIX_STRLEN]; memset(&req, 0, sizeof(req) - NL_PKT_BUF_SIZE); family = PREFIX_FAMILY(&rule->rule.filter.src_ip); bytelen = (family == AF_INET ? 4 : 16); req.n.nlmsg_type = cmd; req.n.nlmsg_len = NLMSG_LENGTH(sizeof(struct rtmsg)); req.n.nlmsg_flags = NLM_F_REQUEST; req.n.nlmsg_pid = zns->netlink_cmd.snl.nl_pid; req.frh.family = family; req.frh.action = FR_ACT_TO_TBL; /* rule's pref # */ addattr32(&req.n, sizeof(req), FRA_PRIORITY, rule->rule.priority); /* interface on which applied */ if (rule->ifp) addattr_l(&req.n, sizeof(req), FRA_IFNAME, rule->ifp->name, strlen(rule->ifp->name) + 1); /* source IP, if specified */ if (IS_RULE_FILTERING_ON_SRC_IP(rule)) { req.frh.src_len = rule->rule.filter.src_ip.prefixlen; addattr_l(&req.n, sizeof(req), FRA_SRC, &rule->rule.filter.src_ip.u.prefix, bytelen); } /* destination IP, if specified */ if (IS_RULE_FILTERING_ON_DST_IP(rule)) { req.frh.dst_len = rule->rule.filter.dst_ip.prefixlen; addattr_l(&req.n, sizeof(req), FRA_DST, &rule->rule.filter.dst_ip.u.prefix, bytelen); } /* fwmark, if specified */ if (IS_RULE_FILTERING_ON_FWMARK(rule)) { addattr32(&req.n, sizeof(req), FRA_FWMARK, rule->rule.filter.fwmark); } /* Route table to use to forward, if filter criteria matches. */ if (rule->rule.action.table < 256) req.frh.table = rule->rule.action.table; else { req.frh.table = RT_TABLE_UNSPEC; addattr32(&req.n, sizeof(req), FRA_TABLE, rule->rule.action.table); } if (IS_ZEBRA_DEBUG_KERNEL) zlog_debug( "Tx %s family %s IF %s(%u) Pref %u Fwmark %u Src %s Dst %s Table %u", nl_msg_type_to_str(cmd), nl_family_to_str(family), rule->ifp ? rule->ifp->name : "Unknown", rule->ifp ? rule->ifp->ifindex : 0, rule->rule.priority, rule->rule.filter.fwmark, prefix2str(&rule->rule.filter.src_ip, buf1, sizeof(buf1)), prefix2str(&rule->rule.filter.dst_ip, buf2, sizeof(buf2)), rule->rule.action.table); /* Ship off the message. * Note: Currently, netlink_talk() is a blocking call which returns * back the status. */ memset(&snl, 0, sizeof(snl)); snl.nl_family = AF_NETLINK; return netlink_talk(netlink_talk_filter, &req.n, &zns->netlink_cmd, zns, 0); } /* Public functions */ /* * Install specified rule for a specific interface. The preference is what * goes in the rule to denote relative ordering; it may or may not be the * same as the rule's user-defined sequence number. */ enum zebra_dplane_result kernel_add_pbr_rule(struct zebra_pbr_rule *rule) { int ret = 0; ret = netlink_rule_update(RTM_NEWRULE, rule); kernel_pbr_rule_add_del_status(rule, (!ret) ? ZEBRA_DPLANE_INSTALL_SUCCESS : ZEBRA_DPLANE_INSTALL_FAILURE); return ZEBRA_DPLANE_REQUEST_SUCCESS; } /* * Uninstall specified rule for a specific interface. */ enum zebra_dplane_result kernel_del_pbr_rule(struct zebra_pbr_rule *rule) { int ret = 0; ret = netlink_rule_update(RTM_DELRULE, rule); kernel_pbr_rule_add_del_status(rule, (!ret) ? ZEBRA_DPLANE_DELETE_SUCCESS : ZEBRA_DPLANE_DELETE_FAILURE); return ZEBRA_DPLANE_REQUEST_SUCCESS; } /* * Handle netlink notification informing a rule add or delete. * Handling of an ADD is TBD. * DELs are notified up, if other attributes indicate it may be a * notification of interest. The expectation is that if this corresponds * to a PBR rule added by FRR, it will be readded. */ int netlink_rule_change(struct nlmsghdr *h, ns_id_t ns_id, int startup) { struct zebra_ns *zns; struct fib_rule_hdr *frh; struct rtattr *tb[FRA_MAX + 1]; int len; char *ifname; struct zebra_pbr_rule rule = {}; char buf1[PREFIX_STRLEN]; char buf2[PREFIX_STRLEN]; /* Basic validation followed by extracting attributes. */ if (h->nlmsg_type != RTM_NEWRULE && h->nlmsg_type != RTM_DELRULE) return 0; /* TBD */ if (h->nlmsg_type == RTM_NEWRULE) return 0; len = h->nlmsg_len - NLMSG_LENGTH(sizeof(struct fib_rule_hdr)); if (len < 0) { zlog_err("%s: Message received from netlink is of a broken size: %d %zu", __PRETTY_FUNCTION__, h->nlmsg_len, (size_t)NLMSG_LENGTH(sizeof(struct fib_rule_hdr))); return -1; } frh = NLMSG_DATA(h); if (frh->family != AF_INET && frh->family != AF_INET6) { flog_warn( EC_ZEBRA_NETLINK_INVALID_AF, "Invalid address family: %u received from kernel rule change: %u", frh->family, h->nlmsg_type); return 0; } if (frh->action != FR_ACT_TO_TBL) return 0; memset(tb, 0, sizeof(tb)); netlink_parse_rtattr(tb, FRA_MAX, RTM_RTA(frh), len); /* TBD: We don't care about rules not specifying an IIF. */ if (tb[FRA_IFNAME] == NULL) return 0; /* If we don't know the interface, we don't care. */ ifname = (char *)RTA_DATA(tb[FRA_IFNAME]); zns = zebra_ns_lookup(ns_id); rule.ifp = if_lookup_by_name_per_ns(zns, ifname); if (!rule.ifp) return 0; if (tb[FRA_PRIORITY]) rule.rule.priority = *(uint32_t *)RTA_DATA(tb[FRA_PRIORITY]); if (tb[FRA_SRC]) { if (frh->family == AF_INET) memcpy(&rule.rule.filter.src_ip.u.prefix4, RTA_DATA(tb[FRA_SRC]), 4); else memcpy(&rule.rule.filter.src_ip.u.prefix6, RTA_DATA(tb[FRA_SRC]), 16); rule.rule.filter.src_ip.prefixlen = frh->src_len; rule.rule.filter.filter_bm |= PBR_FILTER_SRC_IP; } if (tb[FRA_DST]) { if (frh->family == AF_INET) memcpy(&rule.rule.filter.dst_ip.u.prefix4, RTA_DATA(tb[FRA_DST]), 4); else memcpy(&rule.rule.filter.dst_ip.u.prefix6, RTA_DATA(tb[FRA_DST]), 16); rule.rule.filter.dst_ip.prefixlen = frh->dst_len; rule.rule.filter.filter_bm |= PBR_FILTER_DST_IP; } if (tb[FRA_TABLE]) rule.rule.action.table = *(uint32_t *)RTA_DATA(tb[FRA_TABLE]); else rule.rule.action.table = frh->table; if (IS_ZEBRA_DEBUG_KERNEL) zlog_debug( "Rx %s family %s IF %s(%u) Pref %u Src %s Dst %s Table %u", nl_msg_type_to_str(h->nlmsg_type), nl_family_to_str(frh->family), rule.ifp->name, rule.ifp->ifindex, rule.rule.priority, prefix2str(&rule.rule.filter.src_ip, buf1, sizeof(buf1)), prefix2str(&rule.rule.filter.dst_ip, buf2, sizeof(buf2)), rule.rule.action.table); return kernel_pbr_rule_del(&rule); } /* * Get to know existing PBR rules in the kernel - typically called at startup. * TBD. */ int netlink_rules_read(struct zebra_ns *zns) { return 0; } #endif /* HAVE_NETLINK */ frr-7.2.1/zebra/rule_netlink.h0000644000000000000000000000261313610377563013165 00000000000000/* * Zebra Policy Based Routing (PBR) interaction with the kernel using * netlink - public definitions and function declarations. * Copyright (C) 2018 Cumulus Networks, Inc. * * This file is part of FRR. * * FRR is free software; you can redistribute it and/or modify it * under the terms of the GNU General Public License as published by the * Free Software Foundation; either version 2, or (at your option) any * later version. * * FRR 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 FRR; see the file COPYING. If not, write to the Free * Software Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA * 02111-1307, USA. */ #ifndef _ZEBRA_RULE_NETLINK_H #define _ZEBRA_RULE_NETLINK_H #ifdef HAVE_NETLINK #ifdef __cplusplus extern "C" { #endif /* * Handle netlink notification informing a rule add or delete. */ extern int netlink_rule_change(struct nlmsghdr *h, ns_id_t ns_id, int startup); /* * Get to know existing PBR rules in the kernel - typically called at startup. */ extern int netlink_rules_read(struct zebra_ns *zns); #ifdef __cplusplus } #endif #endif /* HAVE_NETLINK */ #endif /* _ZEBRA_RULE_NETLINK_H */ frr-7.2.1/zebra/rule_socket.c0000644000000000000000000000334713610377563013011 00000000000000/* * Zebra Policy Based Routing (PBR) interaction with the kernel using * netlink. * Copyright (C) 2018 Cumulus Networks, Inc. * Donald Sharp * * This file is part of FRR. * * FRR is free software; you can redistribute it and/or modify it * under the terms of the GNU General Public License as published by the * Free Software Foundation; either version 2, or (at your option) any * later version. * * FRR 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 FRR; see the file COPYING. If not, write to the Free * Software Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA * 02111-1307, USA. */ #include #ifndef HAVE_NETLINK #include "if.h" #include "prefix.h" #include "vrf.h" #include "lib_errors.h" #include "zebra/zserv.h" #include "zebra/zebra_ns.h" #include "zebra/zebra_vrf.h" #include "zebra/rt.h" #include "zebra/interface.h" #include "zebra/debug.h" #include "zebra/rtadv.h" #include "zebra/kernel_netlink.h" #include "zebra/rule_netlink.h" #include "zebra/zebra_pbr.h" #include "zebra/zebra_errors.h" enum zebra_dplane_result kernel_add_pbr_rule(struct zebra_pbr_rule *rule) { flog_err(EC_LIB_UNAVAILABLE, "%s not Implemented for this platform", __PRETTY_FUNCTION__); return ZEBRA_DPLANE_REQUEST_FAILURE; } enum zebra_dplane_result kernel_del_pbr_rule(struct zebra_pbr_rule *rule) { flog_err(EC_LIB_UNAVAILABLE, "%s not Implemented for this platform", __PRETTY_FUNCTION__); return ZEBRA_DPLANE_REQUEST_FAILURE; } #endif frr-7.2.1/zebra/subdir.am0000644000000000000000000001072313610377563012131 00000000000000# # zebra # if ZEBRA sbin_PROGRAMS += zebra/zebra dist_examples_DATA += zebra/zebra.conf.sample vtysh_scan += \ $(top_srcdir)/zebra/debug.c \ $(top_srcdir)/zebra/interface.c \ $(top_srcdir)/zebra/router-id.c \ $(top_srcdir)/zebra/rtadv.c \ $(top_srcdir)/zebra/zebra_mlag.c \ $(top_srcdir)/zebra/zebra_mpls_vty.c \ $(top_srcdir)/zebra/zebra_ptm.c \ $(top_srcdir)/zebra/zebra_pw.c \ $(top_srcdir)/zebra/zebra_routemap.c \ $(top_srcdir)/zebra/zebra_vty.c \ $(top_srcdir)/zebra/zserv.c \ # end # can be loaded as DSO - always include for vtysh vtysh_scan += $(top_srcdir)/zebra/irdp_interface.c vtysh_scan += $(top_srcdir)/zebra/zebra_fpm.c if IRDP module_LTLIBRARIES += zebra/zebra_irdp.la endif if SNMP module_LTLIBRARIES += zebra/zebra_snmp.la endif if FPM module_LTLIBRARIES += zebra/zebra_fpm.la endif man8 += $(MANBUILD)/frr-zebra.8 ## endif ZEBRA endif zebra_zebra_LDADD = lib/libfrr.la $(LIBCAP) zebra_zebra_SOURCES = \ zebra/connected.c \ zebra/debug.c \ zebra/if_ioctl.c \ zebra/if_ioctl_solaris.c \ zebra/if_netlink.c \ zebra/if_sysctl.c \ zebra/interface.c \ zebra/ioctl.c \ zebra/ioctl_solaris.c \ zebra/ipforward_proc.c \ zebra/ipforward_solaris.c \ zebra/ipforward_sysctl.c \ zebra/kernel_netlink.c \ zebra/kernel_socket.c \ zebra/label_manager.c \ zebra/main.c \ zebra/redistribute.c \ zebra/router-id.c \ zebra/rt_netlink.c \ zebra/rt_socket.c \ zebra/rtadv.c \ zebra/rtread_getmsg.c \ zebra/rtread_netlink.c \ zebra/rtread_sysctl.c \ zebra/rule_netlink.c \ zebra/rule_socket.c \ zebra/zebra_mlag.c \ zebra/zebra_l2.c \ zebra/zebra_memory.c \ zebra/zebra_dplane.c \ zebra/zebra_mpls.c \ zebra/zebra_mpls_netlink.c \ zebra/zebra_mpls_openbsd.c \ zebra/zebra_mpls_null.c \ zebra/zebra_mpls_vty.c \ zebra/zebra_mroute.c \ zebra/zebra_nhg.c \ zebra/zebra_ns.c \ zebra/zebra_pbr.c \ zebra/zebra_ptm.c \ zebra/zebra_ptm_redistribute.c \ zebra/zebra_pw.c \ zebra/zebra_rib.c \ zebra/zebra_router.c \ zebra/zebra_rnh.c \ zebra/zebra_routemap.c \ zebra/zebra_vrf.c \ zebra/zebra_vty.c \ zebra/zebra_vxlan.c \ zebra/zserv.c \ zebra/zebra_netns_id.c \ zebra/zebra_netns_notify.c \ zebra/table_manager.c \ zebra/zapi_msg.c \ zebra/zebra_errors.c \ # end zebra/debug_clippy.c: $(CLIPPY_DEPS) zebra/debug.$(OBJEXT): zebra/debug_clippy.c zebra/zebra_mlag_clippy.c: $(CLIPPY_DEPS) zebra/zebra_mlag.$(OBJEXT): zebra/zebra_mlag_clippy.c zebra/zebra_vty_clippy.c: $(CLIPPY_DEPS) zebra/interface_clippy.c: $(CLIPPY_DEPS) zebra/interface.$(OBJEXT): zebra/interface_clippy.c zebra/zebra_vty.$(OBJEXT): zebra/zebra_vty_clippy.c zebra/zebra_routemap_clippy.c: $(CLIPPY_DEPS) zebra/zebra_routemap.$(OBJEXT): zebra/zebra_routemap_clippy.c noinst_HEADERS += \ zebra/connected.h \ zebra/debug.h \ zebra/if_netlink.h \ zebra/interface.h \ zebra/ioctl.h \ zebra/ioctl_solaris.h \ zebra/ipforward.h \ zebra/irdp.h \ zebra/kernel_netlink.h \ zebra/kernel_socket.h \ zebra/label_manager.h \ zebra/redistribute.h \ zebra/rib.h \ zebra/router-id.h \ zebra/rt.h \ zebra/rt_netlink.h \ zebra/rtadv.h \ zebra/rule_netlink.h \ zebra/zebra_mlag.h \ zebra/zebra_fpm_private.h \ zebra/zebra_l2.h \ zebra/zebra_dplane.h \ zebra/zebra_memory.h \ zebra/zebra_mpls.h \ zebra/zebra_mroute.h \ zebra/zebra_nhg.h \ zebra/zebra_ns.h \ zebra/zebra_pbr.h \ zebra/zebra_ptm.h \ zebra/zebra_ptm_redistribute.h \ zebra/zebra_pw.h \ zebra/zebra_rnh.h \ zebra/zebra_routemap.h \ zebra/zebra_router.h \ zebra/zebra_vrf.h \ zebra/zebra_vxlan.h \ zebra/zebra_vxlan_private.h \ zebra/zserv.h \ zebra/zebra_netns_id.h \ zebra/zebra_netns_notify.h \ zebra/table_manager.h \ zebra/zapi_msg.h \ zebra/zebra_errors.h \ # end zebra_zebra_irdp_la_SOURCES = \ zebra/irdp_interface.c \ zebra/irdp_main.c \ zebra/irdp_packet.c \ # end zebra_zebra_irdp_la_LDFLAGS = -avoid-version -module -shared -export-dynamic zebra_zebra_snmp_la_SOURCES = zebra/zebra_snmp.c zebra_zebra_snmp_la_CFLAGS = $(WERROR) $(SNMP_CFLAGS) -std=gnu99 zebra_zebra_snmp_la_LDFLAGS = -avoid-version -module -shared -export-dynamic zebra_zebra_snmp_la_LIBADD = lib/libfrrsnmp.la zebra_zebra_fpm_la_LDFLAGS = -avoid-version -module -shared -export-dynamic zebra_zebra_fpm_la_LIBADD = zebra_zebra_fpm_la_SOURCES = zebra/zebra_fpm.c zebra_zebra_fpm_la_SOURCES += zebra/zebra_fpm_netlink.c if HAVE_PROTOBUF zebra_zebra_fpm_la_LIBADD += fpm/libfrrfpm_pb.la qpb/libfrr_pb.la $(PROTOBUF_C_LIBS) zebra_zebra_fpm_la_SOURCES += zebra/zebra_fpm_protobuf.c if DEV_BUILD zebra_zebra_fpm_la_SOURCES += zebra/zebra_fpm_dt.c endif endif frr-7.2.1/zebra/table_manager.c0000644000000000000000000001451013610377563013245 00000000000000/* zebra table Manager for routing table identifier management * Copyright (C) 2018 6WIND * * This program is free software; you can redistribute it and/or modify it * under the terms of the 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; see the file COPYING; if not, write to the Free Software * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA */ #include "zebra.h" #include #include #include #include "lib/log.h" #include "lib/memory.h" #include "lib/table.h" #include "lib/network.h" #include "lib/stream.h" #include "lib/zclient.h" #include "lib/libfrr.h" #include "lib/vrf.h" #include "zebra/zserv.h" #include "zebra/zebra_vrf.h" #include "zebra/label_manager.h" /* for NO_PROTO */ #include "zebra/table_manager.h" #include "zebra/zebra_errors.h" /* routing table identifiers * */ #ifdef SUNOS_5 /* SunOS */ #else #if !defined(GNU_LINUX) && !defined(SUNOS_5) /* BSD systems */ #else /* Linux Systems */ #define RT_TABLE_ID_LOCAL 255 #define RT_TABLE_ID_MAIN 254 #define RT_TABLE_ID_DEFAULT 253 #define RT_TABLE_ID_COMPAT 252 #define RT_TABLE_ID_UNSPEC 0 #endif /* !def(GNU_LINUX) && !defined(SUNOS_5) */ #endif /* SUNOS_5 */ #define RT_TABLE_ID_UNRESERVED_MIN 1 #define RT_TABLE_ID_UNRESERVED_MAX 0xffffffff struct table_manager tbl_mgr; DEFINE_MGROUP(TABLE_MGR, "Table Manager"); DEFINE_MTYPE_STATIC(TABLE_MGR, TM_CHUNK, "Table Manager Chunk"); static void delete_table_chunk(void *val) { XFREE(MTYPE_TM_CHUNK, val); } /** * Init table manager */ void table_manager_enable(ns_id_t ns_id) { if (ns_id != NS_DEFAULT) return; tbl_mgr.lc_list = list_new(); tbl_mgr.lc_list->del = delete_table_chunk; hook_register(zserv_client_close, release_daemon_table_chunks); } /** * Core function, assigns table chunks * * It first searches through the list to check if there's one available * (previously released). Otherwise it creates and assigns a new one * * @param proto Daemon protocol of client, to identify the owner * @param instance Instance, to identify the owner * @para size Size of the table chunk * @return Pointer to the assigned table chunk */ struct table_manager_chunk *assign_table_chunk(uint8_t proto, uint16_t instance, uint32_t size) { struct table_manager_chunk *tmc; struct listnode *node; uint32_t start; /* first check if there's one available */ for (ALL_LIST_ELEMENTS_RO(tbl_mgr.lc_list, node, tmc)) { if (tmc->proto == NO_PROTO && tmc->end - tmc->start + 1 == size) { tmc->proto = proto; tmc->instance = instance; return tmc; } } /* otherwise create a new one */ tmc = XCALLOC(MTYPE_TM_CHUNK, sizeof(struct table_manager_chunk)); if (!tmc) return NULL; /* table RT IDs range are [1;252] and [256;0xffffffff] * - check if the requested range can be within the first range, * otherwise elect second one * - TODO : vrf-lites have their own table identifier. * In that case, table_id should be removed from the table range. */ if (list_isempty(tbl_mgr.lc_list)) start = RT_TABLE_ID_UNRESERVED_MIN; else start = ((struct table_manager_chunk *)listgetdata( listtail(tbl_mgr.lc_list)))->end + 1; #ifdef SUNOS_5 /* SunOS */ #else #if !defined(GNU_LINUX) && !defined(SUNOS_5) /* BSD systems */ #else /* Linux Systems */ /* if not enough room space between MIN and COMPAT, * then begin after LOCAL */ if (start < RT_TABLE_ID_COMPAT && (size > RT_TABLE_ID_COMPAT - RT_TABLE_ID_UNRESERVED_MIN)) start = RT_TABLE_ID_LOCAL + 1; #endif /* !def(GNU_LINUX) && !defined(SUNOS_5) */ #endif /* SUNOS_5 */ tmc->start = start; if (RT_TABLE_ID_UNRESERVED_MAX - size + 1 < start) { flog_err(EC_ZEBRA_TM_EXHAUSTED_IDS, "Reached max table id. Start/Size %u/%u", start, size); XFREE(MTYPE_TM_CHUNK, tmc); return NULL; } tmc->end = tmc->start + size - 1; tmc->proto = proto; tmc->instance = instance; listnode_add(tbl_mgr.lc_list, tmc); return tmc; } /** * Core function, release no longer used table chunks * * @param proto Daemon protocol of client, to identify the owner * @param instance Instance, to identify the owner * @param start First table RT ID of the chunk * @param end Last table RT ID of the chunk * @return 0 on success, -1 otherwise */ int release_table_chunk(uint8_t proto, uint16_t instance, uint32_t start, uint32_t end) { struct listnode *node; struct table_manager_chunk *tmc; int ret = -1; /* check that size matches */ zlog_debug("Releasing table chunk: %u - %u", start, end); /* find chunk and disown */ for (ALL_LIST_ELEMENTS_RO(tbl_mgr.lc_list, node, tmc)) { if (tmc->start != start) continue; if (tmc->end != end) continue; if (tmc->proto != proto || tmc->instance != instance) { flog_err(EC_ZEBRA_TM_DAEMON_MISMATCH, "%s: Daemon mismatch!!", __func__); continue; } tmc->proto = NO_PROTO; tmc->instance = 0; ret = 0; break; } if (ret != 0) flog_err(EC_ZEBRA_TM_UNRELEASED_CHUNK, "%s: Table chunk not released!!", __func__); return ret; } /** * Release table chunks from a client. * * Called on client disconnection or reconnection. It only releases chunks * with empty keep value. * * @param client the client to release chunks from * @return Number of chunks released */ int release_daemon_table_chunks(struct zserv *client) { uint8_t proto = client->proto; uint16_t instance = client->instance; struct listnode *node; struct table_manager_chunk *tmc; int count = 0; int ret; for (ALL_LIST_ELEMENTS_RO(tbl_mgr.lc_list, node, tmc)) { if (tmc->proto == proto && tmc->instance == instance) { ret = release_table_chunk(tmc->proto, tmc->instance, tmc->start, tmc->end); if (ret == 0) count++; } } zlog_debug("%s: Released %d table chunks", __func__, count); return count; } void table_manager_disable(ns_id_t ns_id) { if (ns_id != NS_DEFAULT) return; list_delete(&tbl_mgr.lc_list); } frr-7.2.1/zebra/table_manager.h0000644000000000000000000000450113610377563013251 00000000000000/* zebra table Manager for routing table identifier management * Copyright (C) 2018 6WIND * * This program is free software; you can redistribute it and/or modify it * under the terms of the 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; see the file COPYING; if not, write to the Free Software * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA */ #ifndef _TABLE_MANAGER_H #define _TABLE_MANAGER_H #include #include "lib/linklist.h" #include "lib/thread.h" #include "lib/ns.h" #include "zebra/zserv.h" #ifdef __cplusplus extern "C" { #endif /* * Table chunk struct * Client daemon which the chunk belongs to can be identified by either * proto (daemon protocol) + instance + VRF. * If the client then passes a non-empty value to keep field when it requests * for chunks, the chunks won't be garbage collected and the client will be * responsible of its release. * Otherwise, if the keep field is not set (value 0) for the chunk, it will be * automatically released when the client disconnects or when it reconnects * (in case it died unexpectedly, we can know it's the same because it will have * the same proto and instance values) */ struct table_manager_chunk { vrf_id_t vrf_id; uint8_t proto; uint16_t instance; uint32_t start; /* First table RT ID of the chunk */ uint32_t end; /* Last table RT ID of the chunk */ }; /* * Main table manager struct * Holds a linked list of table chunks. */ struct table_manager { struct list *lc_list; }; void table_manager_enable(ns_id_t ns_id); struct table_manager_chunk *assign_table_chunk(uint8_t proto, uint16_t instance, uint32_t size); int release_table_chunk(uint8_t proto, uint16_t instance, uint32_t start, uint32_t end); int release_daemon_table_chunks(struct zserv *client); void table_manager_disable(ns_id_t ns_id); #ifdef __cplusplus } #endif #endif /* _TABLE_MANAGER_H */ frr-7.2.1/zebra/zapi_msg.c0000644000000000000000000020372213610377563012302 00000000000000/* * Zebra API message creation & consumption. * Portions: * Copyright (C) 1997-1999 Kunihiro Ishiguro * Copyright (C) 2015-2018 Cumulus Networks, Inc. * et al. * * This program is free software; you can redistribute it and/or modify it * under the terms of the 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; see the file COPYING; if not, write to the Free Software * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA */ #include #include #include "lib/prefix.h" #include "lib/command.h" #include "lib/if.h" #include "lib/thread.h" #include "lib/stream.h" #include "lib/memory.h" #include "lib/table.h" #include "lib/network.h" #include "lib/sockunion.h" #include "lib/log.h" #include "lib/zclient.h" #include "lib/privs.h" #include "lib/network.h" #include "lib/buffer.h" #include "lib/nexthop.h" #include "lib/vrf.h" #include "lib/libfrr.h" #include "lib/sockopt.h" #include "zebra/zebra_router.h" #include "zebra/rib.h" #include "zebra/zebra_memory.h" #include "zebra/zebra_ns.h" #include "zebra/zebra_vrf.h" #include "zebra/router-id.h" #include "zebra/redistribute.h" #include "zebra/debug.h" #include "zebra/zebra_rnh.h" #include "zebra/rt_netlink.h" #include "zebra/interface.h" #include "zebra/zebra_ptm.h" #include "zebra/rtadv.h" #include "zebra/zebra_mpls.h" #include "zebra/zebra_mroute.h" #include "zebra/zebra_vxlan.h" #include "zebra/rt.h" #include "zebra/zebra_pbr.h" #include "zebra/table_manager.h" #include "zebra/zapi_msg.h" #include "zebra/zebra_errors.h" #include "zebra/zebra_mlag.h" /* Encoding helpers -------------------------------------------------------- */ static void zserv_encode_interface(struct stream *s, struct interface *ifp) { /* Interface information. */ struct zebra_if *zif = ifp->info; stream_put(s, ifp->name, INTERFACE_NAMSIZ); stream_putl(s, ifp->ifindex); stream_putc(s, ifp->status); stream_putq(s, ifp->flags); stream_putc(s, ifp->ptm_enable); stream_putc(s, ifp->ptm_status); stream_putl(s, ifp->metric); stream_putl(s, ifp->speed); stream_putl(s, ifp->mtu); stream_putl(s, ifp->mtu6); stream_putl(s, ifp->bandwidth); stream_putl(s, zif->link_ifindex); stream_putl(s, ifp->ll_type); stream_putl(s, ifp->hw_addr_len); if (ifp->hw_addr_len) stream_put(s, ifp->hw_addr, ifp->hw_addr_len); /* Then, Traffic Engineering parameters if any */ if (HAS_LINK_PARAMS(ifp) && IS_LINK_PARAMS_SET(ifp->link_params)) { stream_putc(s, 1); zebra_interface_link_params_write(s, ifp); } else stream_putc(s, 0); /* Write packet size. */ stream_putw_at(s, 0, stream_get_endp(s)); } static void zserv_encode_vrf(struct stream *s, struct zebra_vrf *zvrf) { struct vrf_data data; const char *netns_name = zvrf_ns_name(zvrf); data.l.table_id = zvrf->table_id; if (netns_name) strlcpy(data.l.netns_name, basename((char *)netns_name), NS_NAMSIZ); else memset(data.l.netns_name, 0, NS_NAMSIZ); /* Pass the tableid and the netns NAME */ stream_put(s, &data, sizeof(struct vrf_data)); /* Interface information. */ stream_put(s, zvrf_name(zvrf), VRF_NAMSIZ); /* Write packet size. */ stream_putw_at(s, 0, stream_get_endp(s)); } static int zserv_encode_nexthop(struct stream *s, struct nexthop *nexthop) { stream_putl(s, nexthop->vrf_id); stream_putc(s, nexthop->type); switch (nexthop->type) { case NEXTHOP_TYPE_IPV4: case NEXTHOP_TYPE_IPV4_IFINDEX: stream_put_in_addr(s, &nexthop->gate.ipv4); stream_putl(s, nexthop->ifindex); break; case NEXTHOP_TYPE_IPV6: stream_put(s, &nexthop->gate.ipv6, 16); break; case NEXTHOP_TYPE_IPV6_IFINDEX: stream_put(s, &nexthop->gate.ipv6, 16); stream_putl(s, nexthop->ifindex); break; case NEXTHOP_TYPE_IFINDEX: stream_putl(s, nexthop->ifindex); break; default: /* do nothing */ break; } return 1; } /* Send handlers ----------------------------------------------------------- */ /* Interface is added. Send ZEBRA_INTERFACE_ADD to client. */ /* * This function is called in the following situations: * - in response to a 3-byte ZEBRA_INTERFACE_ADD request * from the client. * - at startup, when zebra figures out the available interfaces * - when an interface is added (where support for * RTM_IFANNOUNCE or AF_NETLINK sockets is available), or when * an interface is marked IFF_UP (i.e., an RTM_IFINFO message is * received) */ int zsend_interface_add(struct zserv *client, struct interface *ifp) { struct stream *s = stream_new(ZEBRA_MAX_PACKET_SIZ); zclient_create_header(s, ZEBRA_INTERFACE_ADD, ifp->vrf_id); zserv_encode_interface(s, ifp); client->ifadd_cnt++; return zserv_send_message(client, s); } /* Interface deletion from zebra daemon. */ int zsend_interface_delete(struct zserv *client, struct interface *ifp) { struct stream *s = stream_new(ZEBRA_MAX_PACKET_SIZ); zclient_create_header(s, ZEBRA_INTERFACE_DELETE, ifp->vrf_id); zserv_encode_interface(s, ifp); client->ifdel_cnt++; return zserv_send_message(client, s); } int zsend_vrf_add(struct zserv *client, struct zebra_vrf *zvrf) { struct stream *s = stream_new(ZEBRA_MAX_PACKET_SIZ); zclient_create_header(s, ZEBRA_VRF_ADD, zvrf_id(zvrf)); zserv_encode_vrf(s, zvrf); client->vrfadd_cnt++; return zserv_send_message(client, s); } /* VRF deletion from zebra daemon. */ int zsend_vrf_delete(struct zserv *client, struct zebra_vrf *zvrf) { struct stream *s = stream_new(ZEBRA_MAX_PACKET_SIZ); zclient_create_header(s, ZEBRA_VRF_DELETE, zvrf_id(zvrf)); zserv_encode_vrf(s, zvrf); client->vrfdel_cnt++; return zserv_send_message(client, s); } int zsend_interface_link_params(struct zserv *client, struct interface *ifp) { struct stream *s = stream_new(ZEBRA_MAX_PACKET_SIZ); if (!ifp->link_params) { stream_free(s); return 0; } zclient_create_header(s, ZEBRA_INTERFACE_LINK_PARAMS, ifp->vrf_id); /* Add Interface Index */ stream_putl(s, ifp->ifindex); /* Then TE Link Parameters */ if (zebra_interface_link_params_write(s, ifp) == 0) { stream_free(s); return 0; } /* Write packet size. */ stream_putw_at(s, 0, stream_get_endp(s)); return zserv_send_message(client, s); } /* Interface address is added/deleted. Send ZEBRA_INTERFACE_ADDRESS_ADD or * ZEBRA_INTERFACE_ADDRESS_DELETE to the client. * * A ZEBRA_INTERFACE_ADDRESS_ADD is sent in the following situations: * - in response to a 3-byte ZEBRA_INTERFACE_ADD request * from the client, after the ZEBRA_INTERFACE_ADD has been * sent from zebra to the client * - redistribute new address info to all clients in the following situations * - at startup, when zebra figures out the available interfaces * - when an interface is added (where support for * RTM_IFANNOUNCE or AF_NETLINK sockets is available), or when * an interface is marked IFF_UP (i.e., an RTM_IFINFO message is * received) * - for the vty commands "ip address A.B.C.D/M [